URI: 
       tAdd basic (buggy) clipboard support - 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 25deb9532df29d093281a2b542be2463809d7379
   DIR parent 413b7e3a74968e128ede783a25145dc5aea70c99
  HTML Author: lumidify <nobody@lumidify.org>
       Date:   Mon, 17 May 2021 22:59:57 +0200
       
       Add basic (buggy) clipboard support
       
       Diffstat:
         M common.h                            |       1 +
         M ledit.c                             |     292 ++++++++++++++++++++++++++++++-
       
       2 files changed, 288 insertions(+), 5 deletions(-)
       ---
   DIR diff --git a/common.h b/common.h
       t@@ -28,5 +28,6 @@ typedef struct {
                XftColor fg;
                XftColor bg;
                XftColor scroll_bg;
       +        XSetWindowAttributes wattrs;
                Atom wm_delete_msg;
        } ledit_common_state;
   DIR diff --git a/ledit.c b/ledit.c
       t@@ -2,6 +2,7 @@
        /* FIXME: Fix cursor movement, especially buffer->trailing and writing at end of line */
        /* FIXME: horizontal scrolling (also need cache to avoid too large pixmaps) */
        /* FIXME: sort out types for indices (currently just int, but that might overflow) */
       +/* TODO: allow extending selection with shift+mouse like in e.g. gtk */
        #include <math.h>
        #include <stdio.h>
        #include <errno.h>
       t@@ -11,6 +12,7 @@
        #include <unistd.h>
        #include <locale.h>
        #include <X11/Xlib.h>
       +#include <X11/Xatom.h>
        #include <X11/Xutil.h>
        #include <X11/keysym.h>
        #include <X11/XF86keysym.h>
       t@@ -123,6 +125,208 @@ static void get_new_line_softline(
            int *new_line_ret, int *new_softline_ret
        );
        
       +/* clipboard handling largely stolen from st (simple terminal) */
       +
       +#define MODBIT(x, set, bit) ((set) ? ((x) |= (bit)) : ((x) &= ~(bit)))
       +
       +struct {
       +        Atom xtarget;
       +        char *primary;
       +        size_t primary_alloc;
       +        char *clipboard;
       +} xsel;
       +
       +void
       +clipcopy(void)
       +{
       +        Atom clipboard;
       +
       +        free(xsel.clipboard);
       +        xsel.clipboard = NULL;
       +
       +        if (xsel.primary != NULL) {
       +                xsel.clipboard = ledit_strdup(xsel.primary);
       +                clipboard = XInternAtom(state.dpy, "CLIPBOARD", 0);
       +                XSetSelectionOwner(state.dpy, clipboard, state.win, CurrentTime);
       +        }
       +}
       +
       +void
       +clippaste(void)
       +{
       +        Atom clipboard;
       +
       +        clipboard = XInternAtom(state.dpy, "CLIPBOARD", 0);
       +        XConvertSelection(state.dpy, clipboard, xsel.xtarget, clipboard,
       +                        state.win, CurrentTime);
       +}
       +
       +void
       +selpaste(void)
       +{
       +        XConvertSelection(state.dpy, XA_PRIMARY, xsel.xtarget, XA_PRIMARY,
       +                        state.win, CurrentTime);
       +}
       +
       +void selnotify(XEvent *e);
       +
       +void
       +propnotify(XEvent *e)
       +{
       +        XPropertyEvent *xpev;
       +        Atom clipboard = XInternAtom(state.dpy, "CLIPBOARD", 0);
       +
       +        xpev = &e->xproperty;
       +        if (xpev->state == PropertyNewValue &&
       +                        (xpev->atom == XA_PRIMARY ||
       +                         xpev->atom == clipboard)) {
       +                selnotify(e);
       +        }
       +}
       +
       +void
       +selnotify(XEvent *e)
       +{
       +        unsigned long nitems, ofs, rem;
       +        int format;
       +        unsigned char *data, *last, *repl;
       +        Atom type, incratom, property = None;
       +
       +        incratom = XInternAtom(state.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 {
       +                if (XGetWindowProperty(state.dpy, state.win, 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(state.wattrs.event_mask, 0, PropertyChangeMask);
       +                        XChangeWindowAttributes(state.dpy, state.win, CWEventMask, &state.wattrs);
       +                }
       +
       +                if (type == incratom) {
       +                        /*
       +                         * Activate the PropertyNotify events so we receive
       +                         * when the selection owner sends us the next
       +                         * chunk of data.
       +                         */
       +                        MODBIT(state.wattrs.event_mask, 1, PropertyChangeMask);
       +                        XChangeWindowAttributes(state.dpy, state.win, CWEventMask, &state.wattrs);
       +
       +                        /*
       +                         * Deleting the property is the transfer start signal.
       +                         */
       +                        XDeleteProperty(state.dpy, state.win, (int)property);
       +                        continue;
       +                }
       +
       +                /* FIXME: Is this needed for ledit? I don't think so, right? */
       +                /*
       +                 * As seen in getsel:
       +                 * Line endings are inconsistent in the terminal and GUI world
       +                 * copy and pasting. When receiving some selection data,
       +                 * replace all '\n' with '\r'.
       +                 * FIXME: Fix the computer world.
       +                 */
       +                /*
       +                repl = data;
       +                last = data + nitems * format / 8;
       +                while ((repl = memchr(repl, '\n', last - repl))) {
       +                        *repl++ = '\r';
       +                }
       +                */
       +
       +                printf("%.*s\n", (int)(nitems * format / 8), (char*)data);
       +                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(state.dpy, state.win, (int)property);
       +}
       +
       +void
       +selrequest(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(state.dpy, "TARGETS", 0);
       +        if (xsre->target == xa_targets) {
       +                /* respond with the supported type */
       +                string = xsel.xtarget;
       +                XChangeProperty(xsre->display, xsre->requestor, xsre->property,
       +                                XA_ATOM, 32, PropModeReplace,
       +                                (unsigned char *) &string, 1);
       +                xev.property = xsre->property;
       +        } else if (xsre->target == xsel.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(state.dpy, "CLIPBOARD", 0);
       +                if (xsre->selection == XA_PRIMARY) {
       +                        seltext = xsel.primary;
       +                } 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");
       +}
       +
        static void
        get_new_line_softline(
            int cur_line, int cur_index, int movement,
       t@@ -464,6 +668,16 @@ mainloop(void) {
                                case ClientMessage:
                                        if ((Atom)event.xclient.data.l[0] == state.wm_delete_msg)
                                                running = 0;
       +                                break;
       +                        case SelectionNotify:
       +                                selnotify(&event);
       +                                break;
       +                        case PropertyNotify:
       +                                propnotify(&event);
       +                                break;
       +                        case SelectionRequest:
       +                                selrequest(&event);
       +                                break;
                                default:
                                        break;
                                }
       t@@ -548,17 +762,21 @@ setup(int argc, char *argv[]) {
                state.depth = DefaultDepth(state.dpy, state.screen);
                state.cm = DefaultColormap(state.dpy, state.screen);
        
       -        memset(&attrs, 0, sizeof(attrs));
       -        attrs.background_pixel = BlackPixel(state.dpy, state.screen);
       -        attrs.colormap = state.cm;
       +        memset(&state.wattrs, 0, sizeof(attrs));
       +        state.wattrs.background_pixel = BlackPixel(state.dpy, state.screen);
       +        state.wattrs.colormap = state.cm;
                /* this causes the window contents to be kept
                 * when it is resized, leading to less flicker */
       -        attrs.bit_gravity = NorthWestGravity;
       +        state.wattrs.bit_gravity = NorthWestGravity;
       +        /* FIXME: FocusChangeMask? */
       +        state.wattrs.event_mask = KeyPressMask |
       +            ExposureMask | VisibilityChangeMask | StructureNotifyMask |
       +            ButtonMotionMask | ButtonPressMask | ButtonReleaseMask;
                state.win = XCreateWindow(
                    state.dpy, DefaultRootWindow(state.dpy), 0, 0,
                    state.w, state.h, 0, state.depth,
                    InputOutput, state.vis,
       -            CWBackPixel | CWColormap | CWBitGravity, &attrs
       +            CWBackPixel | CWColormap | CWBitGravity | CWEventMask, &state.wattrs
                );
                XSetStandardProperties(state.dpy, state.win, "ledit", NULL, None, argv, argc, NULL);
        
       t@@ -581,17 +799,20 @@ setup(int argc, char *argv[]) {
                XftColorAllocName(state.dpy, state.vis, state.cm, "#FFFFFF", &state.bg);
                XftColorAllocName(state.dpy, state.vis, state.cm, "#CCCCCC", &state.scroll_bg);
        
       +        /*
                XSelectInput(
                    state.dpy, state.win,
                    StructureNotifyMask | KeyPressMask |
                    ButtonPressMask | ButtonReleaseMask |
                    PointerMotionMask | ExposureMask
                );
       +        */
        
                state.wm_delete_msg = XInternAtom(state.dpy, "WM_DELETE_WINDOW", False);
                XSetWMProtocols(state.dpy, state.win, &state.wm_delete_msg, 1);
        
                /* blatantly stolen from st (simple terminal) */
       +        /* FIXME: get improved input handling from newer version of st */
                if ((state.xim = XOpenIM(state.dpy, NULL, NULL, NULL)) == NULL) {
                        XSetLocaleModifiers("@im=local");
                        if ((state.xim =  XOpenIM(state.dpy, NULL, NULL, NULL)) == NULL) {
       t@@ -630,6 +851,13 @@ setup(int argc, char *argv[]) {
                key_stack.len = key_stack.alloc = 0;
                key_stack.stack = NULL;
        
       +        xsel.primary = NULL;
       +        xsel.primary_alloc = 0;
       +        xsel.clipboard = NULL;
       +        xsel.xtarget = XInternAtom(state.dpy, "UTF8_STRING", 0);
       +        if (xsel.xtarget == None)
       +                xsel.xtarget = XA_STRING;
       +
                redraw();
        }
        
       t@@ -783,6 +1011,57 @@ sort_selection(int *line1, int *byte1, int *line2, int *byte2) {
                }
        }
        
       +/* FIXME: when selecting with mouse, only call this when button is released */
       +/* lines and bytes need to be sorted already! */
       +static void
       +copy_selection_to_x_primary(int line1, int byte1, int line2, int byte2) {
       +        size_t len = 0;
       +        ledit_line *ll1 = ledit_get_line(buffer, line1);
       +        ledit_line *ll2 = ledit_get_line(buffer, line2);
       +        if (line1 == line2) {
       +                len = byte2 - byte1;
       +        } else {
       +                /* + 1 for newline */
       +                len = ll1->len - byte1 + byte2 + 1;
       +                for (int i = line1 + 1; i < line2; i++) {
       +                        ledit_line *ll = ledit_get_line(buffer, i);
       +                        len += ll->len + 1;
       +                }
       +        }
       +        len += 1; /* nul */
       +        if (len > xsel.primary_alloc) {
       +                /* FIXME: maybe allocate a bit more */
       +                xsel.primary = ledit_realloc(xsel.primary, len);
       +                xsel.primary_alloc = len;
       +        }
       +        if (line1 == line2) {
       +                memcpy(xsel.primary, ll1->text + byte1, byte2 - byte1);
       +                xsel.primary[byte2 - byte1] = '\0';
       +        } else {
       +                size_t cur_pos = 0;
       +                memcpy(xsel.primary, ll1->text + byte1, ll1->len - byte1);
       +                cur_pos += ll1->len - byte1;
       +                xsel.primary[cur_pos] = '\n';
       +                cur_pos++;
       +                for (int i = line1 + 1; i < line2; i++) {
       +                        ledit_line *ll = ledit_get_line(buffer, i);
       +                        memcpy(xsel.primary + cur_pos, ll->text, ll->len);
       +                        cur_pos += ll->len;
       +                        xsel.primary[cur_pos] = '\n';
       +                        cur_pos++;
       +                }
       +                memcpy(xsel.primary + cur_pos, ll2->text, byte2);
       +                cur_pos += byte2;
       +                xsel.primary[cur_pos] = '\0';
       +        }
       +        XSetSelectionOwner(state.dpy, XA_PRIMARY, state.win, CurrentTime);
       +        /*
       +        FIXME
       +        if (XGetSelectionOwner(state.dpy, XA_PRIMARY) != state.win)
       +                selclear();
       +        */
       +}
       +
        static void
        set_selection(int line1, int byte1, int line2, int byte2) {
                if (line1 == buffer->sel.line1 && line2 == buffer->sel.line2 &&
       t@@ -820,6 +1099,7 @@ set_selection(int line1, int byte1, int line2, int byte2) {
                                        }
                                }
                        }
       +                copy_selection_to_x_primary(l1_new, b1_new, l2_new, b2_new);
                }
                buffer->sel.line1 = line1;
                buffer->sel.byte1 = byte1;
       t@@ -1257,6 +1537,8 @@ static struct key keys_en[] = {
                {"d",  0, NORMAL|VISUAL, KEY_ANY, KEY_MOTION|KEY_NUMBERALLOWED, &key_d},
                {"v",  0, NORMAL, KEY_ANY, KEY_ANY, &enter_visual},
                {"o",  0, VISUAL, KEY_ANY, KEY_ANY, &switch_selection_end},
       +        {"y",  0, NORMAL|VISUAL, KEY_ANY, KEY_ANY, &clipcopy},
       +        {"p",  0, NORMAL|VISUAL, KEY_ANY, KEY_ANY, &clippaste}
        };
        
        static struct key keys_ur[] = {