tRefactor clipboard handling - ledit - Text editor (WIP) | |
git clone git://lumidify.org/ledit.git (fast, but not encrypted) | |
git clone https://lumidify.org/git/ledit.git (encrypted, but very slow) | |
Log | |
Files | |
Refs | |
README | |
LICENSE | |
--- | |
commit af728a8b3107fc9c18562adc9ed7dbca46dbda4d | |
parent b195be7aa66957888ba1f996b8c24fde7179fbc8 | |
Author: lumidify <[email protected]> | |
Date: Thu, 1 Sep 2022 22:02:13 +0200 | |
Refactor clipboard handling | |
Diffstat: | |
M Makefile | 2 ++ | |
M buffer.c | 3 ++- | |
M buffer.h | 6 ++++-- | |
A clipboard.c | 372 +++++++++++++++++++++++++++++… | |
A clipboard.h | 28 ++++++++++++++++++++++++++++ | |
M keys_basic.c | 2 +- | |
M ledit.c | 16 +++++++++------- | |
M txtbuf.c | 37 +++++++++++++++++++++++++++++… | |
M txtbuf.h | 26 ++++++++++++++++++++++++++ | |
M view.c | 30 ++++++++++++++---------------- | |
M window.c | 247 +----------------------------… | |
M window.h | 52 +++--------------------------… | |
12 files changed, 502 insertions(+), 319 deletions(-) | |
--- | |
diff --git a/Makefile b/Makefile | |
t@@ -31,6 +31,7 @@ OBJ = \ | |
util.o \ | |
draw_util.o \ | |
window.o \ | |
+ clipboard.o \ | |
pango-compat.o | |
SRC = ${OBJ:.o=.c} | |
t@@ -55,6 +56,7 @@ HDR = \ | |
cleanup.h \ | |
macros.h \ | |
pango-compat.h \ | |
+ clipboard.h \ | |
uglycrap.h | |
CONFIGHDR = \ | |
diff --git a/buffer.c b/buffer.c | |
t@@ -225,9 +225,10 @@ marklist_create(void) { | |
} | |
ledit_buffer * | |
-buffer_create(ledit_common *common) { | |
+buffer_create(ledit_common *common, ledit_clipboard *clipboard) { | |
ledit_buffer *buffer = ledit_malloc(sizeof(ledit_buffer)); | |
buffer->common = common; | |
+ buffer->clipboard = clipboard; | |
buffer->undo = undo_stack_create(); | |
buffer->marklist = marklist_create(); | |
diff --git a/buffer.h b/buffer.h | |
t@@ -34,7 +34,9 @@ typedef struct { | |
/* TODO: advisory lock on file */ | |
struct ledit_buffer { | |
- ledit_common *common; /* common stuff, e.g. display, etc. */ | |
+ ledit_common *common; /* common stuff, e.g. display, etc. -… | |
+ ledit_clipboard *clipboard; /* this also doesn't really belong he… | |
+ /* FIXME: add some sort of manager that holds shared stuff like clipbo… | |
char *filename; /* last opened filename */ | |
struct timespec file_mtime; /* last modified time of file */ | |
undo_stack *undo; /* undo manager */ | |
t@@ -55,7 +57,7 @@ struct ledit_buffer { | |
/* | |
* Create a new buffer with one empty line | |
*/ | |
-ledit_buffer *buffer_create(ledit_common *common); | |
+ledit_buffer *buffer_create(ledit_common *common, ledit_clipboard *clipboard); | |
/* | |
* Lock all views except the given view. | |
diff --git a/clipboard.c b/clipboard.c | |
t@@ -0,0 +1,372 @@ | |
+#include <time.h> | |
+#include <stdio.h> | |
+#include <stdlib.h> | |
+#include <string.h> | |
+ | |
+#include <X11/Xlib.h> | |
+#include <X11/Xatom.h> | |
+ | |
+#include "util.h" | |
+#include "memory.h" | |
+#include "common.h" | |
+#include "clipboard.h" | |
+#include "macros.h" | |
+#include "config.h" | |
+ | |
+/* clipboard handling largely stolen from st (https://st.suckless.org), | |
+ with some *inspiration* taken from SDL (https://libsdl.org), mainly | |
+ the idea to create a separate window just for clipboard handling */ | |
+ | |
+static Window get_clipboard_window(ledit_clipboard *clip); | |
+static Bool check_window(Display *dpy, XEvent *event, XPointer arg); | |
+static txtbuf *get_text(ledit_clipboard *clip, int primary); | |
+static int clipboard_propnotify(ledit_clipboard *clip, XEvent *e, txtbuf *buf); | |
+static int clipboard_selnotify(ledit_clipboard *clip, XEvent *e, txtbuf *buf); | |
+static void clipboard_selrequest(ledit_clipboard *clip, XEvent *e); | |
+ | |
+#define MODBIT(x, set, bit) ((set) ? ((x) |= (bit)) : ((x) &= ~(bit))) | |
+ | |
+struct ledit_clipboard { | |
+ txtbuf *primary; | |
+ txtbuf *clipboard; | |
+ ledit_common *common; | |
+ Window window; | |
+ Atom xtarget; | |
+ XSetWindowAttributes wattrs; | |
+}; | |
+ | |
+ledit_clipboard * | |
+clipboard_create(ledit_common *common) { | |
+ ledit_clipboard *clip = ledit_malloc(sizeof(ledit_clipboard)); | |
+ clip->primary = txtbuf_new(); | |
+ clip->clipboard = txtbuf_new(); | |
+ clip->common = common; | |
+ clip->window = None; | |
+ clip->xtarget = None; | |
+ #ifdef X_HAVE_UTF8_STRING | |
+ clip->xtarget = XInternAtom(common->dpy, "UTF8_STRING", False); | |
+ #endif | |
+ if (clip->xtarget == None) | |
+ clip->xtarget = XA_STRING; | |
+ clip->wattrs.event_mask = 0; | |
+ return clip; | |
+} | |
+ | |
+void | |
+clipboard_destroy(ledit_clipboard *clip) { | |
+ txtbuf_destroy(clip->primary); | |
+ txtbuf_destroy(clip->clipboard); | |
+ if (clip->window != None) | |
+ XDestroyWindow(clip->common->dpy, clip->window); | |
+ free(clip); | |
+} | |
+ | |
+static Window | |
+get_clipboard_window(ledit_clipboard *clip) { | |
+ if (clip->window == None) { | |
+ clip->window = XCreateWindow( | |
+ clip->common->dpy, DefaultRootWindow(clip->common->dpy), | |
+ -10, -10, 1, 1, 0, CopyFromParent, InputOnly, CopyFromPare… | |
+ ); | |
+ XFlush(clip->common->dpy); | |
+ } | |
+ return clip->window; | |
+} | |
+ | |
+void | |
+clipboard_set_primary_text(ledit_clipboard *clip, char *text) { | |
+ Window window = get_clipboard_window(clip); | |
+ txtbuf_set_text(clip->primary, text); | |
+ XSetSelectionOwner(clip->common->dpy, XA_PRIMARY, window, CurrentTime); | |
+} | |
+ | |
+txtbuf * | |
+clipboard_get_primary_buffer(ledit_clipboard *clip) { | |
+ return clip->primary; | |
+} | |
+ | |
+void | |
+clipboard_set_primary_selection_owner(ledit_clipboard *clip) { | |
+ Window window = get_clipboard_window(clip); | |
+ XSetSelectionOwner(clip->common->dpy, XA_PRIMARY, window, CurrentTime); | |
+} | |
+ | |
+void | |
+clipboard_set_clipboard_text(ledit_clipboard *clip, char *text) { | |
+ Atom clip_atom; | |
+ Window window = get_clipboard_window(clip); | |
+ clip_atom = XInternAtom(clip->common->dpy, "CLIPBOARD", False); | |
+ txtbuf_set_text(clip->clipboard, text); | |
+ XSetSelectionOwner(clip->common->dpy, clip_atom, window, CurrentTime); | |
+} | |
+ | |
+txtbuf * | |
+clipboard_get_clipboard_buffer(ledit_clipboard *clip) { | |
+ return clip->clipboard; | |
+} | |
+ | |
+void | |
+clipboard_set_clipboard_selection_owner(ledit_clipboard *clip) { | |
+ Atom clip_atom; | |
+ Window window = get_clipboard_window(clip); | |
+ clip_atom = XInternAtom(clip->common->dpy, "CLIPBOARD", False); | |
+ XSetSelectionOwner(clip->common->dpy, clip_atom, window, CurrentTime); | |
+} | |
+ | |
+void | |
+clipboard_primary_to_clipboard(ledit_clipboard *clip) { | |
+ Atom clip_atom; | |
+ if (clip->primary->len > 0) { | |
+ Window window = get_clipboard_window(clip); | |
+ txtbuf_copy(clip->clipboard, clip->primary); | |
+ clip_atom = XInternAtom(clip->common->dpy, "CLIPBOARD", False); | |
+ XSetSelectionOwner(clip->common->dpy, clip_atom, window, Curre… | |
+ } | |
+} | |
+ | |
+int | |
+clipboard_filter_event(ledit_clipboard *clip, XEvent *e) { | |
+ if (clip->window != None && e->xany.window == clip->window) { | |
+ if (e->type == SelectionRequest) | |
+ clipboard_selrequest(clip, e); | |
+ /* other events are discarded since there | |
+ was no request to get the clipboard text */ | |
+ return 1; | |
+ } | |
+ return 0; | |
+} | |
+ | |
+static Bool | |
+check_window(Display *dpy, XEvent *event, XPointer arg) { | |
+ (void)dpy; | |
+ return *(Window *)arg == event->xany.window; | |
+} | |
+ | |
+/* WARNING: The returned txtbuf needs to be copied before further processing! … | |
+static txtbuf * | |
+get_text(ledit_clipboard *clip, int primary) { | |
+ txtbuf *buf = primary ? clip->primary : clip->clipboard; | |
+ txtbuf_clear(buf); | |
+ Window window = get_clipboard_window(clip); | |
+ struct timespec now, elapsed, last, start, sleep_time; | |
+ sleep_time.tv_sec = 0; | |
+ clock_gettime(CLOCK_MONOTONIC, &start); | |
+ last = start; | |
+ XEvent event; | |
+ while (1) { | |
+ /* FIXME: I have no idea how inefficient this is */ | |
+ if (XCheckIfEvent(clip->common->dpy, &event, &check_window, (X… | |
+ switch (event.type) { | |
+ case SelectionNotify: | |
+ if (!clipboard_selnotify(clip, &event,… | |
+ return buf; | |
+ break; | |
+ case PropertyNotify: | |
+ if (!clipboard_propnotify(clip, &event… | |
+ return buf; | |
+ break; | |
+ case SelectionRequest: | |
+ clipboard_selrequest(clip, &event); | |
+ break; | |
+ default: | |
+ break; | |
+ } | |
+ } | |
+ clock_gettime(CLOCK_MONOTONIC, &now); | |
+ ledit_timespecsub(&now, &start, &elapsed); | |
+ if (elapsed.tv_sec > 0) { | |
+ if (primary) | |
+ clipboard_set_primary_text(clip, ""); | |
+ else | |
+ clipboard_set_clipboard_text(clip, ""); | |
+ return NULL; | |
+ } | |
+ ledit_timespecsub(&now, &last, &elapsed); | |
+ if (elapsed.tv_sec == 0 && elapsed.tv_nsec < TICK) { | |
+ sleep_time.tv_nsec = TICK - elapsed.tv_nsec; | |
+ nanosleep(&sleep_time, NULL); | |
+ } | |
+ last = now; | |
+ } | |
+ return NULL; | |
+} | |
+ | |
+txtbuf * | |
+clipboard_get_clipboard_text(ledit_clipboard *clip) { | |
+ Atom clip_atom; | |
+ clip_atom = XInternAtom(clip->common->dpy, "CLIPBOARD", False); | |
+ Window window = get_clipboard_window(clip); | |
+ Window owner = XGetSelectionOwner(clip->common->dpy, clip_atom); | |
+ if (owner == None) { | |
+ return NULL; | |
+ } else if (owner == window) { | |
+ return clip->clipboard; | |
+ } else { | |
+ XConvertSelection(clip->common->dpy, clip_atom, clip->xtarget,… | |
+ return get_text(clip, 0); | |
+ } | |
+} | |
+ | |
+txtbuf * | |
+clipboard_get_primary_text(ledit_clipboard *clip) { | |
+ Window window = get_clipboard_window(clip); | |
+ Window owner = XGetSelectionOwner(clip->common->dpy, XA_PRIMARY); | |
+ if (owner == None) { | |
+ return NULL; | |
+ } else if (owner == window) { | |
+ return clip->primary; | |
+ } else { | |
+ XConvertSelection(clip->common->dpy, XA_PRIMARY, clip->xtarget… | |
+ return get_text(clip, 1); | |
+ } | |
+} | |
+ | |
+/* 0 means the transfer is done, 1 means there might be more */ | |
+static int | |
+clipboard_propnotify(ledit_clipboard *clip, XEvent *e, txtbuf *buf) { | |
+ XPropertyEvent *xpev; | |
+ Atom clipboard = XInternAtom(clip->common->dpy, "CLIPBOARD", False); | |
+ xpev = &e->xproperty; | |
+ if (xpev->state == PropertyNewValue && (xpev->atom == XA_PRIMARY || xp… | |
+ return clipboard_selnotify(clip, e, buf); | |
+ } | |
+ return 1; | |
+} | |
+ | |
+/* FIXME: test this properly because I don't really understand all of it */ | |
+/* 0 means the transfer is done, 1 means there might be more */ | |
+static int | |
+clipboard_selnotify(ledit_clipboard *clip, XEvent *e, txtbuf *buf) { | |
+ unsigned long nitems, ofs, rem; | |
+ int format; | |
+ unsigned char *data; | |
+ Atom type, incratom, property = None; | |
+ | |
+ incratom = XInternAtom(clip->common->dpy, "INCR", 0); | |
+ | |
+ ofs = 0; | |
+ if (e->type == SelectionNotify) { | |
+ property = e->xselection.property; | |
+ } else if (e->type == PropertyNotify) { | |
+ property = e->xproperty.atom; | |
+ } | |
+ | |
+ if (property == None) | |
+ return 1; | |
+ | |
+ Window window = get_clipboard_window(clip); | |
+ do { | |
+ /* FIXME: proper error logging */ | |
+ /* FIXME: show error message in window */ | |
+ if (XGetWindowProperty( | |
+ clip->common->dpy, window, property, ofs, BUFSIZ/4, False, | |
+ AnyPropertyType, &type, &format, &nitems, &rem, &data)) { | |
+ fprintf(stderr, "Clipboard allocation failed\n"); | |
+ return 0; | |
+ } | |
+ | |
+ if (e->type == PropertyNotify && nitems == 0 && rem == 0) { | |
+ /* | |
+ * If there is some PropertyNotify with no data, then | |
+ * this is the signal of the selection owner that all | |
+ * data has been transferred. We won't need to receive | |
+ * PropertyNotify events anymore. | |
+ */ | |
+ MODBIT(clip->wattrs.event_mask, 0, PropertyChangeMask); | |
+ XChangeWindowAttributes(clip->common->dpy, window, CWE… | |
+ return 0; | |
+ } | |
+ | |
+ if (type == incratom) { | |
+ /* | |
+ * Activate the PropertyNotify events so we receive | |
+ * when the selection owner sends us the next | |
+ * chunk of data. | |
+ */ | |
+ MODBIT(clip->wattrs.event_mask, 1, PropertyChangeMask); | |
+ XChangeWindowAttributes(clip->common->dpy, window, CWE… | |
+ | |
+ /* | |
+ * Deleting the property is the transfer start signal. | |
+ */ | |
+ XDeleteProperty(clip->common->dpy, window, (int)proper… | |
+ continue; | |
+ } | |
+ | |
+ /* FIXME: XGetWindowProperty takes data as unsigned char, so i… | |
+ txtbuf_appendn(buf, (char *)data, (size_t)(nitems * format / 8… | |
+ XFree(data); | |
+ if (!(clip->wattrs.event_mask & PropertyChangeMask)) | |
+ return 0; | |
+ /* number of 32-bit chunks returned */ | |
+ ofs += nitems * format / 32; | |
+ } while (rem > 0); | |
+ | |
+ /* | |
+ * Deleting the property again tells the selection owner to send the | |
+ * next data chunk in the property. | |
+ */ | |
+ XDeleteProperty(clip->common->dpy, window, (int)property); | |
+ return 1; | |
+} | |
+ | |
+static void | |
+clipboard_selrequest(ledit_clipboard *clip, XEvent *e) | |
+{ | |
+ XSelectionRequestEvent *xsre; | |
+ XSelectionEvent xev; | |
+ Atom xa_targets, string, clip_atom; | |
+ char *seltext; | |
+ | |
+ xsre = (XSelectionRequestEvent *) e; | |
+ xev.type = SelectionNotify; | |
+ xev.requestor = xsre->requestor; | |
+ xev.selection = xsre->selection; | |
+ xev.target = xsre->target; | |
+ xev.time = xsre->time; | |
+ if (xsre->property == None) | |
+ xsre->property = xsre->target; | |
+ | |
+ /* reject */ | |
+ xev.property = None; | |
+ | |
+ xa_targets = XInternAtom(clip->common->dpy, "TARGETS", 0); | |
+ if (xsre->target == xa_targets) { | |
+ /* respond with the supported type */ | |
+ string = clip->xtarget; | |
+ XChangeProperty( | |
+ xsre->display, xsre->requestor, xsre->property, | |
+ XA_ATOM, 32, PropModeReplace, (unsigned char *) &string, 1 | |
+ ); | |
+ xev.property = xsre->property; | |
+ } else if (xsre->target == clip->xtarget || xsre->target == XA_STRING)… | |
+ /* | |
+ * xith XA_STRING non ascii characters may be incorrect in the | |
+ * requestor. It is not our problem, use utf8. | |
+ */ | |
+ clip_atom = XInternAtom(clip->common->dpy, "CLIPBOARD", 0); | |
+ if (xsre->selection == XA_PRIMARY) { | |
+ seltext = clip->primary->text; | |
+ } else if (xsre->selection == clip_atom) { | |
+ seltext = clip->clipboard->text; | |
+ } else { | |
+ fprintf( | |
+ stderr, | |
+ "Unhandled clipboard selection 0x%lx\n", xsre->sel… | |
+ ); | |
+ return; | |
+ } | |
+ /* FIXME: Should this handle sending data in multiple chunks? … | |
+ if (seltext != NULL) { | |
+ XChangeProperty( | |
+ xsre->display, xsre->requestor, xsre->property, xs… | |
+ 8, PropModeReplace, (unsigned char *)seltext, strl… | |
+ ); | |
+ xev.property = xsre->property; | |
+ } | |
+ } | |
+ | |
+ /* all done, send a notification to the listener */ | |
+ if (!XSendEvent(xsre->display, xsre->requestor, 1, 0, (XEvent *)&xev)) | |
+ fprintf(stderr, "Error sending SelectionNotify event\n"); | |
+} | |
diff --git a/clipboard.h b/clipboard.h | |
t@@ -0,0 +1,28 @@ | |
+#ifndef _CLIPBOARD_H_ | |
+#define _CLIPBOARD_H_ | |
+ | |
+#include <X11/Xlib.h> | |
+#include "common.h" | |
+#include "txtbuf.h" | |
+ | |
+typedef struct ledit_clipboard ledit_clipboard; | |
+ | |
+ledit_clipboard *clipboard_create(ledit_common *common); | |
+void clipboard_destroy(ledit_clipboard *clip); | |
+void clipboard_set_primary_text(ledit_clipboard *clip, char *text); | |
+txtbuf *clipboard_get_primary_buffer(ledit_clipboard *clip); | |
+void clipboard_set_primary_selection_owner(ledit_clipboard *clip); | |
+void clipboard_set_clipboard_text(ledit_clipboard *clip, char *text); | |
+txtbuf *clipboard_get_clipboard_buffer(ledit_clipboard *clip); | |
+void clipboard_set_clipboard_selection_owner(ledit_clipboard *clip); | |
+void clipboard_primary_to_clipboard(ledit_clipboard *clip); | |
+/* 1 means the event was used by the clipboard, 0 means it wasn't */ | |
+int clipboard_filter_event(ledit_clipboard *clip, XEvent *e); | |
+ | |
+/* WARNING: The returned txtbuf is owned by the clipboard and must | |
+ be copied before further processing and especially before any | |
+ further clipboard functions are called. */ | |
+txtbuf *clipboard_get_clipboard_text(ledit_clipboard *clip); | |
+txtbuf *clipboard_get_primary_text(ledit_clipboard *clip); | |
+ | |
+#endif /* _CLIPBOARD_H_ */ | |
diff --git a/keys_basic.c b/keys_basic.c | |
t@@ -2330,7 +2330,7 @@ clipcopy(ledit_view *view, char *text, size_t len) { | |
if (!key_stack_empty()) | |
return err_invalid_key(view); | |
/* FIXME: abstract this through view */ | |
- clipboard_primary_to_clipboard(view->window); | |
+ clipboard_primary_to_clipboard(view->buffer->clipboard); | |
discard_repetition_stack(); | |
return (struct action){ACTION_NONE, NULL}; | |
} | |
diff --git a/ledit.c b/ledit.c | |
t@@ -47,8 +47,9 @@ static void redraw(void); | |
static void change_keyboard(char *lang); | |
static void key_press(ledit_view *view, XEvent *event); | |
-ledit_buffer *buffer = NULL; | |
ledit_common common; | |
+ledit_clipboard *clipboard = NULL; | |
+ledit_buffer *buffer = NULL; | |
size_t cur_lang = 0; | |
static void | |
t@@ -114,6 +115,8 @@ mainloop(void) { | |
change_kbd = 1; | |
continue; | |
} | |
+ if (clipboard_filter_event(clipboard, &event)) | |
+ continue; | |
if (XFilterEvent(&event, None)) | |
continue; | |
ledit_view *view = NULL; | |
t@@ -153,11 +156,6 @@ mainloop(void) { | |
running = 0; | |
} | |
break; | |
- case SelectionNotify: | |
- case PropertyNotify: | |
- case SelectionRequest: | |
- window_clipboard_event(view->window, &event); | |
- break; | |
default: | |
break; | |
} | |
t@@ -320,7 +318,9 @@ setup(int argc, char *argv[]) { | |
ledit_debug_fmt("Time to load config (total): %lld seconds, %ld nanose… | |
#endif | |
- buffer = buffer_create(&common); | |
+ clipboard = clipboard_create(&common); | |
+ | |
+ buffer = buffer_create(&common, clipboard); | |
buffer_add_view(buffer, NORMAL, 0, 0, 0); | |
/* FIXME: don't access view directly here */ | |
ledit_view *view = buffer->views[0]; | |
t@@ -450,6 +450,8 @@ ledit_cleanup(void) { | |
basic_key_cleanup(); | |
command_key_cleanup(); | |
key_processing_cleanup(); | |
+ if (clipboard) | |
+ clipboard_destroy(clipboard); | |
if (buffer) | |
buffer_destroy(buffer); | |
config_cleanup(&common); | |
diff --git a/txtbuf.c b/txtbuf.c | |
t@@ -52,6 +52,35 @@ txtbuf_fmt(txtbuf *buf, char *fmt, ...) { | |
} | |
void | |
+txtbuf_set_text(txtbuf *buf, char *text) { | |
+ txtbuf_set_textn(buf, text, strlen(text)); | |
+} | |
+ | |
+void | |
+txtbuf_set_textn(txtbuf *buf, char *text, size_t len) { | |
+ txtbuf_resize(buf, len); | |
+ buf->len = len; | |
+ memmove(buf->text, text, len); | |
+ buf->text[buf->len] = '\0'; | |
+} | |
+ | |
+void | |
+txtbuf_append(txtbuf *buf, char *text) { | |
+ txtbuf_appendn(buf, text, strlen(text)); | |
+} | |
+ | |
+/* FIXME: some sort of append that does not resize until there's not enough | |
+ space so a buffer that will be filled up anyways doesn't have to be | |
+ constantly resized */ | |
+void | |
+txtbuf_appendn(txtbuf *buf, char *text, size_t len) { | |
+ txtbuf_resize(buf, add_sz(buf->len, len)); | |
+ memmove(buf->text + buf->len, text, len); | |
+ buf->len += len; | |
+ buf->text[buf->len] = '\0'; | |
+} | |
+ | |
+void | |
txtbuf_resize(txtbuf *buf, size_t sz) { | |
/* always leave room for extra \0 */ | |
size_t cap = ideal_array_size(buf->cap, add_sz(sz, 1)); | |
t@@ -104,3 +133,11 @@ int | |
txtbuf_eql(txtbuf *buf1, txtbuf *buf2) { | |
return txtbuf_cmp(buf1, buf2) == 0; | |
} | |
+ | |
+void | |
+txtbuf_clear(txtbuf *buf) { | |
+ if (buf->len > 0) { | |
+ buf->len = 0; | |
+ buf->text[0] = '\0'; | |
+ } | |
+} | |
diff --git a/txtbuf.h b/txtbuf.h | |
t@@ -37,6 +37,26 @@ txtbuf *txtbuf_new_from_char_len(char *str, size_t len); | |
void txtbuf_fmt(txtbuf *buf, char *fmt, ...); | |
/* | |
+ * Replace the stored text in 'buf' with 'text'. | |
+ */ | |
+void txtbuf_set_text(txtbuf *buf, char *text); | |
+ | |
+/* | |
+ * Same as txtbuf_set_text, but with explicit length for 'text'. | |
+ */ | |
+void txtbuf_set_textn(txtbuf *buf, char *text, size_t len); | |
+ | |
+/* | |
+ * Append 'text' to the text stored in 'buf'. | |
+ */ | |
+void txtbuf_append(txtbuf *buf, char *text); | |
+ | |
+/* | |
+ * Same as txtbuf_append, but with explicit length for 'text'. | |
+ */ | |
+void txtbuf_appendn(txtbuf *buf, char *text, size_t len); | |
+ | |
+/* | |
* Compare the text of two txtbuf's like 'strcmp'. | |
*/ | |
int txtbuf_cmp(txtbuf *buf1, txtbuf *buf2); | |
t@@ -68,4 +88,10 @@ void txtbuf_copy(txtbuf *dst, txtbuf *src); | |
*/ | |
txtbuf *txtbuf_dup(txtbuf *src); | |
+/* | |
+ * Clear the text, but do not reduce the internal capacity | |
+ * (for efficiency if it will be filled up again anyways). | |
+ */ | |
+void txtbuf_clear(txtbuf *buf); | |
+ | |
#endif | |
diff --git a/view.c b/view.c | |
t@@ -15,6 +15,7 @@ | |
#include "pango-compat.h" | |
#include "memory.h" | |
#include "common.h" | |
+#include "clipboard.h" | |
#include "txtbuf.h" | |
#include "undo.h" | |
#include "cache.h" | |
t@@ -48,7 +49,6 @@ static void view_redraw_text(ledit_view *view); | |
/* Callbacks */ | |
static void view_button_handler(void *data, XEvent *event); | |
static void view_scroll_handler(void *view, long pos); | |
-static void paste_callback(void *data, char *text, size_t len); | |
/* Render a line onto a pixmap that is assigned from the cache. */ | |
static void render_line(ledit_view *view, size_t line_index); | |
t@@ -108,7 +108,7 @@ view_create(ledit_buffer *buffer, ledit_mode mode, size_t … | |
ledit_view *view = ledit_malloc(sizeof(ledit_view)); | |
view->mode = mode; | |
view->buffer = buffer; | |
- view->window = window_create(buffer->common, mode); | |
+ view->window = window_create(buffer->common, buffer->clipboard, mode); | |
view->cache = cache_create(buffer->common->dpy); | |
view->lock_text = NULL; | |
view->cur_action = (struct action){ACTION_NONE, NULL}; | |
t@@ -1727,10 +1727,9 @@ view_ensure_cursor_shown(ledit_view *view) { | |
/* lines and bytes need to be sorted already! */ | |
static void | |
copy_selection_to_x_primary(ledit_view *view, size_t line1, size_t byte1, size… | |
- /* FIXME: let window handle this */ | |
- txtbuf *primary = window_get_primary_clipboard_buffer(); | |
+ txtbuf *primary = clipboard_get_primary_buffer(view->buffer->clipboard… | |
buffer_copy_text_to_txtbuf(view->buffer, primary, line1, byte1, line2,… | |
- XSetSelectionOwner(view->buffer->common->dpy, XA_PRIMARY, view->window… | |
+ clipboard_set_primary_selection_owner(view->buffer->clipboard); | |
/* | |
FIXME | |
if (XGetSelectionOwner(state.dpy, XA_PRIMARY) != state.win) | |
t@@ -2044,11 +2043,8 @@ view_redo(ledit_view *view, int num) { | |
view_wipe_line_cursor_attrs(view, view->cur_line); | |
} | |
-/* FIXME: this could give weird results if the paste occurs in multiple | |
- chunks and something else happens inbetween */ | |
static void | |
-paste_callback(void *data, char *text, size_t len) { | |
- ledit_view *view = (ledit_view *)data; | |
+paste_txtbuf(ledit_view *view, txtbuf *buf) { | |
if (view->mode == NORMAL) | |
view_wipe_line_cursor_attrs(view, view->cur_line); | |
ledit_range cur_range; | |
t@@ -2058,7 +2054,7 @@ paste_callback(void *data, char *text, size_t len) { | |
cur_range.line2 = cur_range.byte2 = 0; | |
buffer_insert_with_undo( | |
view->buffer, cur_range, 1, 1, view->mode, | |
- view->cur_line, view->cur_index, text, len, | |
+ view->cur_line, view->cur_index, buf->text, buf->len, | |
&view->cur_line, &view->cur_index | |
); | |
view_ensure_cursor_shown(view); | |
t@@ -2066,16 +2062,18 @@ paste_callback(void *data, char *text, size_t len) { | |
view_set_line_cursor_attrs(view, view->cur_line, view->cur_ind… | |
} | |
-/* FIXME: guard against view being destroyed before paste callback is nulled */ | |
- | |
void | |
view_paste_clipboard(ledit_view *view) { | |
- window_set_paste_callback(view->window, &paste_callback, view); | |
- clipboard_paste_clipboard(view->window); | |
+ txtbuf *buf = clipboard_get_clipboard_text(view->buffer->clipboard); | |
+ if (!buf) | |
+ return; /* FIXME: warning? */ | |
+ paste_txtbuf(view, buf); | |
} | |
void | |
view_paste_primary(ledit_view *view) { | |
- window_set_paste_callback(view->window, &paste_callback, view); | |
- clipboard_paste_primary(view->window); | |
+ txtbuf *buf = clipboard_get_primary_text(view->buffer->clipboard); | |
+ if (!buf) | |
+ return; /* FIXME: warning? */ | |
+ paste_txtbuf(view, buf); | |
} | |
diff --git a/window.c b/window.c | |
t@@ -47,19 +47,6 @@ struct bottom_bar { | |
int min_pos; /* minimum position cursor can be at */ | |
}; | |
-/* clipboard handling largely stolen from st (simple terminal) */ | |
- | |
-#define MODBIT(x, set, bit) ((set) ? ((x) |= (bit)) : ((x) &= ~(bit))) | |
- | |
-/* FIXME: maybe move this to the window struct just in case any | |
- conflicts happen */ | |
-/* FIXME: the paste handling is a bit weird because the text can come | |
- in several chunks - can this cause issues in certain cases? */ | |
-struct { | |
- txtbuf *primary; | |
- char *clipboard; | |
-} xsel = {NULL, NULL}; | |
- | |
/* | |
* Recalculate the size of the actual text area (which is managed by the view). | |
*/ | |
t@@ -79,17 +66,6 @@ static void get_scroll_pos_height(ledit_window *windown, do… | |
*/ | |
static void set_scroll_pos(ledit_window *window, double pos); | |
-/* event handling for clipboard events */ | |
-static void clipboard_propnotify(ledit_window *window, XEvent *e); | |
-static void clipboard_selnotify(ledit_window *window, XEvent *e); | |
-static void clipboard_selrequest(ledit_window *window, XEvent *e); | |
- | |
-txtbuf * | |
-window_get_primary_clipboard_buffer(void) { | |
- /* FIXME: check if NULL */ | |
- return xsel.primary; | |
-} | |
- | |
/* FIXME: shouldn't window->bottom_text_shown also be true when message_shown?… | |
/* FIXME: guard against negative width/height */ | |
static void | |
t@@ -410,12 +386,6 @@ window_set_scroll_callback(ledit_window *window, void (*c… | |
} | |
void | |
-window_set_paste_callback(ledit_window *window, void (*cb)(void *, char *, siz… | |
- window->paste_callback = cb; | |
- window->paste_cb_data = data; | |
-} | |
- | |
-void | |
window_set_button_callback(ledit_window *window, void (*cb)(void *, XEvent *),… | |
window->button_callback = cb; | |
window->button_cb_data = data; | |
t@@ -514,7 +484,7 @@ xximspot(ledit_window *window, int x, int y) { | |
} | |
ledit_window * | |
-window_create(ledit_common *common, ledit_mode mode) { | |
+window_create(ledit_common *common, ledit_clipboard *clipboard, ledit_mode mod… | |
XGCValues gcv; | |
ledit_theme *theme = config_get_theme(); | |
t@@ -528,11 +498,9 @@ window_create(ledit_common *common, ledit_mode mode) { | |
window->w = 500; | |
window->h = 500; | |
window->mode_extra_text = NULL; | |
- window->paste_callback = NULL; | |
window->scroll_callback = NULL; | |
window->button_callback = NULL; | |
window->resize_callback = NULL; | |
- window->paste_cb_data = NULL; | |
window->scroll_cb_data = NULL; | |
window->button_cb_data = NULL; | |
window->resize_cb_data = NULL; | |
t@@ -576,6 +544,8 @@ window_create(ledit_common *common, ledit_mode mode) { | |
XSetWMProtocols(common->dpy, window->xwin, &window->wm_delete_msg, 1); | |
window->common = common; | |
+ /* FIXME: not used yet - this will be used later when clipboard suppor… | |
+ window->clipboard = clipboard; | |
window->bb = ledit_malloc(sizeof(bottom_bar)); | |
window->bb->mode = pango_layout_new(window->context); | |
t@@ -612,19 +582,7 @@ window_create(ledit_common *common, ledit_mode mode) { | |
XMapWindow(common->dpy, window->xwin); | |
- /* FIXME: why is this part of the window? */ | |
- window->xtarget = XInternAtom(common->dpy, "UTF8_STRING", 0); | |
- window->paste_callback = NULL; | |
- if (window->xtarget == None) | |
- window->xtarget = XA_STRING; | |
- | |
window->cursor_text = XCreateFontCursor(window->common->dpy, XC_xterm); | |
- /* | |
- ledit_clear_window(window); | |
- ledit_redraw_window(window); | |
- */ | |
- if (xsel.primary == NULL) | |
- xsel.primary = txtbuf_new(); | |
/* FIXME: maybe delay this (i.e. move to different function)? */ | |
XMapWindow(common->dpy, window->xwin); | |
t@@ -671,12 +629,6 @@ window_destroy(ledit_window *window) { | |
} | |
void | |
-window_cleanup(void) { | |
- txtbuf_destroy(xsel.primary); | |
- free(xsel.clipboard); | |
-} | |
- | |
-void | |
window_clear(ledit_window *window) { | |
ledit_theme *theme = config_get_theme(); | |
XSetForeground(window->common->dpy, window->gc, theme->text_bg.pixel); | |
t@@ -829,181 +781,6 @@ window_resize(ledit_window *window, int w, int h) { | |
} | |
void | |
-clipboard_primary_to_clipboard(ledit_window *window) { | |
- Atom clipboard; | |
- | |
- free(xsel.clipboard); | |
- xsel.clipboard = NULL; | |
- | |
- /* FIXME: don't copy if text empty (no selection)? */ | |
- if (xsel.primary->text != NULL) { | |
- xsel.clipboard = ledit_strdup(xsel.primary->text); | |
- clipboard = XInternAtom(window->common->dpy, "CLIPBOARD", 0); | |
- XSetSelectionOwner(window->common->dpy, clipboard, window->xwi… | |
- } | |
-} | |
- | |
-void | |
-clipboard_paste_clipboard(ledit_window *window) { | |
- Atom clipboard; | |
- | |
- clipboard = XInternAtom(window->common->dpy, "CLIPBOARD", 0); | |
- XConvertSelection(window->common->dpy, clipboard, window->xtarget, cli… | |
-} | |
- | |
-void | |
-clipboard_paste_primary(ledit_window *window) { | |
- XConvertSelection(window->common->dpy, XA_PRIMARY, window->xtarget, XA… | |
-} | |
- | |
-static void | |
-clipboard_propnotify(ledit_window *window, XEvent *e) { | |
- XPropertyEvent *xpev; | |
- Atom clipboard = XInternAtom(window->common->dpy, "CLIPBOARD", 0); | |
- | |
- xpev = &e->xproperty; | |
- if (xpev->state == PropertyNewValue && | |
- (xpev->atom == XA_PRIMARY || | |
- xpev->atom == clipboard)) { | |
- clipboard_selnotify(window, e); | |
- } | |
-} | |
- | |
-static void | |
-clipboard_selnotify(ledit_window *window, XEvent *e) { | |
- unsigned long nitems, ofs, rem; | |
- int format; | |
- unsigned char *data; | |
- Atom type, incratom, property = None; | |
- | |
- incratom = XInternAtom(window->common->dpy, "INCR", 0); | |
- | |
- ofs = 0; | |
- if (e->type == SelectionNotify) { | |
- property = e->xselection.property; | |
- } else if (e->type == PropertyNotify) { | |
- property = e->xproperty.atom; | |
- } | |
- | |
- if (property == None) | |
- return; | |
- | |
- do { | |
- /* FIXME: proper error logging */ | |
- if (XGetWindowProperty(window->common->dpy, window->xwin, prop… | |
- BUFSIZ/4, False, AnyPropertyType, | |
- &type, &format, &nitems, &rem, | |
- &data)) { | |
- fprintf(stderr, "Clipboard allocation failed\n"); | |
- return; | |
- } | |
- | |
- if (e->type == PropertyNotify && nitems == 0 && rem == 0) { | |
- /* | |
- * If there is some PropertyNotify with no data, then | |
- * this is the signal of the selection owner that all | |
- * data has been transferred. We won't need to receive | |
- * PropertyNotify events anymore. | |
- */ | |
- MODBIT(window->wattrs.event_mask, 0, PropertyChangeMas… | |
- XChangeWindowAttributes(window->common->dpy, window->x… | |
- /* remove callback - this has to be set again the next… | |
- something is pasted */ | |
- window->paste_callback = NULL; | |
- window->paste_cb_data = NULL; | |
- } | |
- | |
- if (type == incratom) { | |
- /* | |
- * Activate the PropertyNotify events so we receive | |
- * when the selection owner sends us the next | |
- * chunk of data. | |
- */ | |
- MODBIT(window->wattrs.event_mask, 1, PropertyChangeMas… | |
- XChangeWindowAttributes(window->common->dpy, window->x… | |
- | |
- /* | |
- * Deleting the property is the transfer start signal. | |
- */ | |
- XDeleteProperty(window->common->dpy, window->xwin, (in… | |
- continue; | |
- } | |
- | |
- /* FIXME: XGetWindowProperty takes data as unsigned char, so i… | |
- /* FIXME: buffer all pasted text and only send it in one go in… | |
- if (window->paste_callback) | |
- window->paste_callback(window->paste_cb_data, (char *)… | |
- XFree(data); | |
- /* number of 32-bit chunks returned */ | |
- ofs += nitems * format / 32; | |
- } while (rem > 0); | |
- | |
- /* | |
- * Deleting the property again tells the selection owner to send the | |
- * next data chunk in the property. | |
- */ | |
- XDeleteProperty(window->common->dpy, window->xwin, (int)property); | |
-} | |
- | |
-static void | |
-clipboard_selrequest(ledit_window *window, XEvent *e) | |
-{ | |
- XSelectionRequestEvent *xsre; | |
- XSelectionEvent xev; | |
- Atom xa_targets, string, clipboard; | |
- char *seltext; | |
- | |
- xsre = (XSelectionRequestEvent *) e; | |
- xev.type = SelectionNotify; | |
- xev.requestor = xsre->requestor; | |
- xev.selection = xsre->selection; | |
- xev.target = xsre->target; | |
- xev.time = xsre->time; | |
- if (xsre->property == None) | |
- xsre->property = xsre->target; | |
- | |
- /* reject */ | |
- xev.property = None; | |
- | |
- xa_targets = XInternAtom(window->common->dpy, "TARGETS", 0); | |
- if (xsre->target == xa_targets) { | |
- /* respond with the supported type */ | |
- string = window->xtarget; | |
- XChangeProperty(xsre->display, xsre->requestor, xsre->property, | |
- XA_ATOM, 32, PropModeReplace, | |
- (unsigned char *) &string, 1); | |
- xev.property = xsre->property; | |
- } else if (xsre->target == window->xtarget || xsre->target == XA_STRIN… | |
- /* | |
- * xith XA_STRING non ascii characters may be incorrect in the | |
- * requestor. It is not our problem, use utf8. | |
- */ | |
- clipboard = XInternAtom(window->common->dpy, "CLIPBOARD", 0); | |
- if (xsre->selection == XA_PRIMARY) { | |
- seltext = xsel.primary->text; | |
- } else if (xsre->selection == clipboard) { | |
- seltext = xsel.clipboard; | |
- } else { | |
- fprintf(stderr, | |
- "Unhandled clipboard selection 0x%lx\n", | |
- xsre->selection); | |
- return; | |
- } | |
- if (seltext != NULL) { | |
- XChangeProperty(xsre->display, xsre->requestor, | |
- xsre->property, xsre->target, | |
- 8, PropModeReplace, | |
- (unsigned char *)seltext, strlen(selte… | |
- xev.property = xsre->property; | |
- } | |
- } | |
- | |
- /* all done, send a notification to the listener */ | |
- if (!XSendEvent(xsre->display, xsre->requestor, 1, 0, (XEvent *) &xev)) | |
- fprintf(stderr, "Error sending SelectionNotify event\n"); | |
-} | |
- | |
-void | |
window_register_button_press(ledit_window *window, XEvent *event) { | |
int scroll_delta; | |
if (event->xbutton.button == Button4 || | |
t@@ -1114,21 +891,3 @@ window_drag_motion(ledit_window *window, XEvent *event) { | |
window->redraw = 1; | |
} | |
} | |
- | |
-void | |
-window_clipboard_event(ledit_window *window, XEvent *event) { | |
- /* FIXME: paste in bottom bar */ | |
- switch (event->type) { | |
- case SelectionNotify: | |
- clipboard_selnotify(window, event); | |
- break; | |
- case PropertyNotify: | |
- clipboard_propnotify(window, event); | |
- break; | |
- case SelectionRequest: | |
- clipboard_selrequest(window, event); | |
- break; | |
- default: | |
- break; | |
- } | |
-} | |
diff --git a/window.h b/window.h | |
t@@ -17,6 +17,7 @@ | |
#include <pango/pangoxft.h> | |
#include "common.h" | |
+#include "clipboard.h" | |
#include "txtbuf.h" | |
typedef struct bottom_bar bottom_bar; | |
t@@ -37,7 +38,6 @@ typedef struct { | |
but that might be changed at some point */ | |
XSetWindowAttributes wattrs; | |
Atom wm_delete_msg; /* used to check when window is closed */ | |
- Atom xtarget; /* used for clipboard handling */ | |
int w; /* width of window */ | |
int h; /* height of window */ | |
t@@ -79,14 +79,13 @@ typedef struct { | |
XPoint spot; | |
XVaNestedList spotlist; | |
- ledit_common *common; | |
+ ledit_common *common; /* shared with others */ | |
+ ledit_clipboard *clipboard; /* also shared */ | |
/* various callbacks */ | |
- void (*paste_callback)(void *, char *, size_t); | |
void (*scroll_callback)(void *, long); | |
void (*button_callback)(void *, XEvent *); | |
void (*resize_callback)(void *); | |
- void *paste_cb_data; | |
void *scroll_cb_data; | |
void *button_cb_data; | |
void *resize_cb_data; | |
t@@ -98,18 +97,13 @@ typedef struct { | |
/* | |
* Create a window with initial mode 'mode'. | |
*/ | |
-ledit_window *window_create(ledit_common *common, ledit_mode mode); | |
+ledit_window *window_create(ledit_common *common, ledit_clipboard *clipboard, … | |
/* | |
* Destroy a window. | |
*/ | |
void window_destroy(ledit_window *window); | |
-/* | |
- * Clean up global data. | |
- */ | |
-void window_cleanup(void); | |
- | |
/* FIXME: this is a bit confusing because there's a difference between editable | |
text shown and non-editable message shown */ | |
t@@ -246,14 +240,6 @@ void window_set_scroll_pos(ledit_window *window, long pos… | |
void window_set_scroll_callback(ledit_window *window, void (*cb)(void *, long)… | |
/* | |
- * Set a callback that is called with text from the clipboard/primary | |
- * selection when it becomes available after 'clipboard_paste_clipboard' | |
- * or 'clipboard_paste_primary' is called. | |
- * The callback is called with 'data', the text, and the length of the text. | |
- */ | |
-void window_set_paste_callback(ledit_window *window, void (*cb)(void *, char *… | |
- | |
-/* | |
* Set a callback that is called when Button1 is clicked or released | |
* in the text area. | |
*/ | |
t@@ -304,31 +290,6 @@ void window_resize(ledit_window *window, int w, int h); | |
void window_get_textview_size(ledit_window *window, int *w_ret, int *h_ret); | |
/* | |
- * Move the X primary selection the the clipboard. | |
- */ | |
-void clipboard_primary_to_clipboard(ledit_window *window); | |
- | |
-/* | |
- * Paste the X clipboard. The text is handed to the paste callback | |
- * when it becomes available. | |
- */ | |
-void clipboard_paste_clipboard(ledit_window *window); | |
- | |
-/* | |
- * Paste the X primary selection. The text is handed to the paste | |
- * callback when it becomes available. | |
- */ | |
-void clipboard_paste_primary(ledit_window *window); | |
- | |
-/* | |
- * Get the buffer used to store the text of the primary selection. | |
- * This is sort of hacky because the view uses it to write the | |
- * selection directly to this buffer and then set the selection | |
- * owner. That should probably be changed at some point. | |
- */ | |
-txtbuf *window_get_primary_clipboard_buffer(void); | |
- | |
-/* | |
* Handle a button press. | |
* This does not filter events like the register function! | |
*/ | |
t@@ -346,11 +307,6 @@ void window_button_release(ledit_window *window, XEvent *… | |
void window_drag_motion(ledit_window *window, XEvent *event); | |
/* | |
- * Handle a clipboard event (SelectionRequest, {Selection,Property}Notify). | |
- */ | |
-void window_clipboard_event(ledit_window *window, XEvent *event); | |
- | |
-/* | |
* Set the pixel position of the input method context. | |
* This is used by some input method editors to show an editor at | |
* the position that text is being inserted at. |