URI: 
       tRefactor clipboard handling - ledit - Text editor (WIP)
  HTML git clone git://lumidify.org/ledit.git (fast, but not encrypted)
  HTML git clone https://lumidify.org/git/ledit.git (encrypted, but very slow)
   DIR Log
   DIR Files
   DIR Refs
   DIR README
   DIR LICENSE
       ---
   DIR commit af728a8b3107fc9c18562adc9ed7dbca46dbda4d
   DIR parent b195be7aa66957888ba1f996b8c24fde7179fbc8
  HTML 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(-)
       ---
   DIR 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 = \
   DIR 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();
        
   DIR 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. - 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 */
       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.
   DIR 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, 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");
       +}
   DIR 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_ */
   DIR 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};
        }
   DIR 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 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];
       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);
   DIR 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';
       +        }
       +}
   DIR 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
   DIR 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 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};
       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_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)
       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_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);
        }
   DIR 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, 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
       t@@ -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;
       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 mode) {
                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 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);
       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->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 ||
       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;
       -        }
       -}
   DIR 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, 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 */
        
       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), 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.
         */
       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 *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.