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 <nobody@lumidify.org>
Date:   Thu,  1 Sep 2022 22:02:13 +0200

Refactor clipboard handling

Diffstat:
MMakefile | 2++
Mbuffer.c | 3++-
Mbuffer.h | 6++++--
Aclipboard.c | 372+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Aclipboard.h | 28++++++++++++++++++++++++++++
Mkeys_basic.c | 2+-
Mledit.c | 16+++++++++-------
Mtxtbuf.c | 37+++++++++++++++++++++++++++++++++++++
Mtxtbuf.h | 26++++++++++++++++++++++++++
Mview.c | 30++++++++++++++----------------
Mwindow.c | 247+------------------------------------------------------------------------------
Mwindow.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.