commit af728a8b3107fc9c18562adc9ed7dbca46dbda4d
parent b195be7aa66957888ba1f996b8c24fde7179fbc8
Author: lumidify <nobody@lumidify.org>
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
@@ -31,6 +31,7 @@ OBJ = \
util.o \
draw_util.o \
window.o \
+ clipboard.o \
pango-compat.o
SRC = ${OBJ:.o=.c}
@@ -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
@@ -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
@@ -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. - this doesn't really belong here */
+ ledit_clipboard *clipboard; /* this also doesn't really belong here */
+ /* FIXME: add some sort of manager that holds shared stuff like clipboard and manages the buffer and views */
char *filename; /* last opened filename */
struct timespec file_mtime; /* last modified time of file */
undo_stack *undo; /* undo manager */
@@ -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
@@ -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, CopyFromParent, 0, &clip->wattrs
+ );
+ 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, CurrentTime);
+ }
+}
+
+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, (XPointer)&window)) {
+ switch (event.type) {
+ case SelectionNotify:
+ if (!clipboard_selnotify(clip, &event, buf))
+ return buf;
+ break;
+ case PropertyNotify:
+ if (!clipboard_propnotify(clip, &event, buf))
+ 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, clip_atom, window, CurrentTime);
+ 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, XA_PRIMARY, window, CurrentTime);
+ 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 || xpev->atom == clipboard)) {
+ 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, CWEventMask, &clip->wattrs);
+ 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, CWEventMask, &clip->wattrs);
+
+ /*
+ * Deleting the property is the transfer start signal.
+ */
+ XDeleteProperty(clip->common->dpy, window, (int)property);
+ continue;
+ }
+
+ /* FIXME: XGetWindowProperty takes data as unsigned char, so it is casted here. Why? */
+ 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->selection
+ );
+ return;
+ }
+ /* FIXME: Should this handle sending data in multiple chunks? */
+ if (seltext != NULL) {
+ XChangeProperty(
+ xsre->display, xsre->requestor, xsre->property, xsre->target,
+ 8, PropModeReplace, (unsigned char *)seltext, strlen(seltext)
+ );
+ 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
@@ -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
@@ -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
@@ -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
@@ -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;
@@ -153,11 +156,6 @@ mainloop(void) {
running = 0;
}
break;
- case SelectionNotify:
- case PropertyNotify:
- case SelectionRequest:
- window_clipboard_event(view->window, &event);
- break;
default:
break;
}
@@ -320,7 +318,9 @@ setup(int argc, char *argv[]) {
ledit_debug_fmt("Time to load config (total): %lld seconds, %ld nanoseconds\n", (long long)elapsed.tv_sec, elapsed.tv_nsec);
#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];
@@ -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
@@ -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));
@@ -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
@@ -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);
@@ -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
@@ -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"
@@ -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);
@@ -108,7 +108,7 @@ view_create(ledit_buffer *buffer, ledit_mode mode, size_t line, size_t pos) {
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};
@@ -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_t line2, size_t byte2) {
- /* 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, byte2);
- XSetSelectionOwner(view->buffer->common->dpy, XA_PRIMARY, view->window->xwin, CurrentTime);
+ clipboard_set_primary_selection_owner(view->buffer->clipboard);
/*
FIXME
if (XGetSelectionOwner(state.dpy, XA_PRIMARY) != state.win)
@@ -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;
@@ -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);
@@ -2066,16 +2062,18 @@ paste_callback(void *data, char *text, size_t len) {
view_set_line_cursor_attrs(view, view->cur_line, view->cur_index);
}
-/* 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
@@ -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).
*/
@@ -79,17 +66,6 @@ static void get_scroll_pos_height(ledit_window *windown, double *pos, double *he
*/
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
@@ -410,12 +386,6 @@ window_set_scroll_callback(ledit_window *window, void (*cb)(void *, long), void
}
void
-window_set_paste_callback(ledit_window *window, void (*cb)(void *, char *, size_t), void *data) {
- window->paste_callback = cb;
- window->paste_cb_data = data;
-}
-
-void
window_set_button_callback(ledit_window *window, void (*cb)(void *, XEvent *), void *data) {
window->button_callback = cb;
window->button_cb_data = data;
@@ -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 mode) {
XGCValues gcv;
ledit_theme *theme = config_get_theme();
@@ -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;
@@ -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 support is added to the bottom bar */
+ window->clipboard = clipboard;
window->bb = ledit_malloc(sizeof(bottom_bar));
window->bb->mode = pango_layout_new(window->context);
@@ -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);
@@ -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);
@@ -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->xwin, CurrentTime);
- }
-}
-
-void
-clipboard_paste_clipboard(ledit_window *window) {
- Atom clipboard;
-
- clipboard = XInternAtom(window->common->dpy, "CLIPBOARD", 0);
- XConvertSelection(window->common->dpy, clipboard, window->xtarget, clipboard, window->xwin, CurrentTime);
-}
-
-void
-clipboard_paste_primary(ledit_window *window) {
- XConvertSelection(window->common->dpy, XA_PRIMARY, window->xtarget, XA_PRIMARY, window->xwin, CurrentTime);
-}
-
-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, property, ofs,
- 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, PropertyChangeMask);
- XChangeWindowAttributes(window->common->dpy, window->xwin, CWEventMask, &window->wattrs);
- /* remove callback - this has to be set again the next time
- 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, PropertyChangeMask);
- XChangeWindowAttributes(window->common->dpy, window->xwin, CWEventMask, &window->wattrs);
-
- /*
- * Deleting the property is the transfer start signal.
- */
- XDeleteProperty(window->common->dpy, window->xwin, (int)property);
- continue;
- }
-
- /* FIXME: XGetWindowProperty takes data as unsigned char, so it is casted here. Why? */
- /* FIXME: buffer all pasted text and only send it in one go in the end */
- if (window->paste_callback)
- window->paste_callback(window->paste_cb_data, (char *)data, (size_t)(nitems * format / 8));
- 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_STRING) {
- /*
- * 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(seltext));
- 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 ||
@@ -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
@@ -17,6 +17,7 @@
#include <pango/pangoxft.h>
#include "common.h"
+#include "clipboard.h"
#include "txtbuf.h"
typedef struct bottom_bar bottom_bar;
@@ -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 */
@@ -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;
@@ -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, ledit_mode mode);
/*
* 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 */
@@ -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), void *data);
/*
- * 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 *, size_t), void *data);
-
-/*
* Set a callback that is called when Button1 is clicked or released
* in the text area.
*/
@@ -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!
*/
@@ -346,11 +307,6 @@ void window_button_release(ledit_window *window, XEvent *event);
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.