]> git.mdlowis.com Git - projs/tide.git/commitdiff
Switch cut/copy/paste over to using X11 clipboard selection
authorMichael D. Lowis <mike.lowis@gentex.com>
Mon, 30 Jan 2017 17:46:03 +0000 (12:46 -0500)
committerMichael D. Lowis <mike.lowis@gentex.com>
Mon, 30 Jan 2017 17:46:03 +0000 (12:46 -0500)
Makefile
TODO.md
config.mk
inc/edit.h
inc/x11.h
lib/x11.c
tests/xedit.c
xedit.c

index 27359b4fb28c06c9c575f314d3c4608764b78c6c..2d626fe6ef773cb2a01c852419b11ada7108d84e 100644 (file)
--- a/Makefile
+++ b/Makefile
@@ -55,3 +55,5 @@ libedit.a: $(LIBEDIT_OBJS)
        $(AR) $(ARFLAGS) $@ $^
 
 unittests: $(TEST_OBJS) libedit.a
+
+-include *.d lib/*.d tests/*.d
\ No newline at end of file
diff --git a/TODO.md b/TODO.md
index a42e607c0f2f1398163562d31ba10716f2355635..6c9197450357e3d6f95125b213559f2e608a4a22 100644 (file)
--- a/TODO.md
+++ b/TODO.md
@@ -2,6 +2,7 @@
 
 Up Next:
 
+* Num Lock causes modifier checks to fail in xedit.c
 * Implement X Selection protocol for handling clipboard and primary selections
 * Right click in tags region should search edit region
 * Add keyboard shortcut to highlight the thing under the cursor
@@ -32,6 +33,7 @@ The Future:
 
 # Auxillary Programs
 
-* Acme-like window manager
+* Visual diff tool
 * Win-like terminal emulator
 * File browser
+* Acme-like window manager
index d1c8dd3b35c7a18b7546b54c0c012eeb63fb3469..3a9bc823fc44f6ed15ace4f24be9766c42fcb049 100644 (file)
--- a/config.mk
+++ b/config.mk
@@ -4,8 +4,8 @@
 PREFIX = $(HOME)
 
 # Compiler Setup
-CC = c99
-CFLAGS = -g -O0 $(INCS)
+CC = cc
+CFLAGS = --std=c99 -MMD -g -O0 $(INCS)
 
 #CC = gcc
 #CFLAGS  = --std=c99 -Wall -Wextra -Werror $(INCS)
index 6a7327c1d55351e6c48a6411ba96931628076eee..43e925be28f916c782457accb0e2fdf172bb3b2b 100644 (file)
@@ -282,3 +282,4 @@ enum {
         0xff859900    \
     }
 #define DEFAULT_TAGS "Quit Save Undo Redo Cut Copy Paste | Find "
+
index 7563f541e66c536b81deccfe436562b2b45307ed..97cb1503f82a001bbe70cf9f4fd39d743ee3f7da 100644 (file)
--- a/inc/x11.h
+++ b/inc/x11.h
@@ -121,6 +121,12 @@ enum {
     ModAny        = ModWindows-1
 };
 
+/* Selection identifiers */
+enum {
+    PRIMARY   = 0,
+    CLIPBOARD = 1
+};
+
 void x11_init(XConfig* cfg);
 void x11_deinit(void);
 int x11_keymods(void);
@@ -141,3 +147,5 @@ void x11_font_getglyph(XFont font, XGlyphSpec* spec, uint32_t rune);
 size_t x11_font_getglyphs(XGlyphSpec* specs, const XGlyph* glyphs, int len, XFont font, int x, int y);
 void x11_draw_glyphs(int fg, int bg, XGlyphSpec* glyphs, size_t nglyphs);
 void x11_draw_utf8(XFont font, int fg, int bg, int x, int y, char* str);
+bool x11_getsel(int selid, void(*cbfn)(char*));
+bool x11_setsel(int selid, char* str);
index 26d25a10cd1ba21e84110850a237688e983559c5..51919d44b80eca17885aa5fd43e02b3d431b6e86 100644 (file)
--- a/lib/x11.c
+++ b/lib/x11.c
@@ -6,6 +6,11 @@
 #include <utf.h>
 #include <locale.h>
 
+static struct XSel* selfetch(Atom atom); 
+static void selclear(XEvent* evnt);
+static void selnotify(XEvent* evnt);
+static void selrequest(XEvent* evnt);
+
 #ifndef MAXFONTS
 #define MAXFONTS 16
 #endif
@@ -47,6 +52,16 @@ static struct {
 } X;
 static XConfig* Config;
 static int Mods;
+static Atom SelTarget;
+static struct XSel {
+    char* name;
+    Atom atom;
+    char* text;
+    void (*callback)(char*);
+} Selections[] = {
+    { .name = "PRIMARY" },
+    { .name = "CLIPBOARD" },
+};
 
 static void xftcolor(XftColor* xc, uint32_t c) {
     xc->color.alpha = 0xFF | ((c & 0xFF000000) >> 16);
@@ -76,6 +91,12 @@ void x11_init(XConfig* cfg) {
     X.colormap = wa.colormap;
     X.screen   = DefaultScreen(X.display);
     X.depth    = DefaultDepth(X.display, X.screen);
+    /* initialize selection atoms */
+    for (int i = 0; i < (sizeof(Selections) / sizeof(Selections[0])); i++)
+        Selections[i].atom = XInternAtom(X.display, Selections[i].name, 0);
+    SelTarget = XInternAtom(X.display, "UTF8_STRING", 0);
+    if (SelTarget == None)
+        SelTarget = XInternAtom(X.display, "STRING", 0);
 }
 
 int x11_keymods(void) {
@@ -273,12 +294,15 @@ void x11_loop(void) {
             XNextEvent(X.display, &e);
             if (!XFilterEvent(&e, None)) {
                 switch (e.type) {
-                    case FocusIn:       if (X.xic) XSetICFocus(X.xic);   break;
-                    case FocusOut:      if (X.xic) XUnsetICFocus(X.xic); break;
-                    case KeyPress:      handle_key(&e);                  break;
-                    case ButtonRelease: handle_mouse(&e);                break;
-                    case ButtonPress:   handle_mouse(&e);                break;
-                    case MotionNotify:  handle_mouse(&e);                break;
+                    case FocusIn:          if (X.xic) XSetICFocus(X.xic);   break;
+                    case FocusOut:         if (X.xic) XUnsetICFocus(X.xic); break;
+                    case KeyPress:         handle_key(&e);                  break;
+                    case ButtonRelease:    handle_mouse(&e);                break;
+                    case ButtonPress:      handle_mouse(&e);                break;
+                    case MotionNotify:     handle_mouse(&e);                break;
+                    case SelectionClear:   selclear(&e);                    break;
+                    case SelectionNotify:  selnotify(&e);                   break;
+                    case SelectionRequest: selrequest(&e);                  break;
                     case ClientMessage: {
                             Atom wmDeleteMessage = XInternAtom(X.display, "WM_DELETE_WINDOW", False);
                             if (e.xclient.data.l[0] == wmDeleteMessage)
@@ -461,3 +485,94 @@ void x11_draw_utf8(XFont fnt, int fg, int bg, int x, int y, char* str) {
 void x11_warp_mouse(int x, int y) {
     XWarpPointer(X.display, X.window, X.window, 0, 0, X.width, X.height, x, y);
 }
+
+/* Selection Handling
+ *****************************************************************************/
+
+static char* readprop(Display* disp, Window win, Atom prop) {
+    Atom type;
+    int format;
+    unsigned long nitems, nleft;
+    unsigned char* ret = NULL;
+    int nread = 1024;
+
+    // Read the property in progressively larger chunks until the entire
+    // property has been read (nleft == 0)
+    do {
+        if (ret) XFree(ret);
+        XGetWindowProperty(disp, win, prop, 0, nread, False, AnyPropertyType,
+                           &type, &format, &nitems, &nleft,
+                           &ret);
+        nread *= 2;
+    } while (nleft != 0);
+
+    return (char*)ret;
+}
+
+static struct XSel* selfetch(Atom atom) {
+    for (int i = 0; i < (sizeof(Selections) / sizeof(Selections[0])); i++)
+        if (atom == Selections[i].atom)
+            return &Selections[i];
+    return NULL;
+}
+
+static void selclear(XEvent* evnt) {
+    struct XSel* sel = selfetch(evnt->xselectionrequest.selection);
+    if (!sel) return;
+    free(sel->text);
+    sel->text = NULL;
+}
+
+static void selnotify(XEvent* evnt) {
+    /* bail if the selection cannot be converted */
+    if (evnt->xselection.property == None)
+        return;
+    struct XSel* sel = selfetch( evnt->xselection.selection );
+    char* propdata = readprop(X.display, X.window, sel->atom);
+    if (evnt->xselection.target == SelTarget) {
+        void(*cbfn)(char*) = sel->callback;
+        sel->callback = NULL;
+        cbfn(propdata);
+    }
+    /* cleanup */
+    if (propdata) XFree(propdata);
+}
+
+static void selrequest(XEvent* evnt) {
+    XEvent s;
+    struct XSel* sel = selfetch( evnt->xselectionrequest.selection );
+    s.xselection.type      = SelectionNotify;
+    s.xselection.property  = evnt->xselectionrequest.property;;
+    s.xselection.requestor = evnt->xselectionrequest.requestor;;
+    s.xselection.selection = evnt->xselectionrequest.selection;;
+    s.xselection.target    = evnt->xselectionrequest.target;;
+    s.xselection.time      = evnt->xselectionrequest.time;;
+    XChangeProperty(X.display, 
+        s.xselection.requestor,
+        s.xselection.property,
+        SelTarget,
+        8, PropModeReplace, (unsigned char*)sel->text, strlen(sel->text));
+    XSendEvent(X.display, evnt->xselectionrequest.requestor, True, 0, &s);
+}
+
+bool x11_getsel(int selid, void(*cbfn)(char*)) {
+    struct XSel* sel = &(Selections[selid]);
+    if (sel->callback) return false;
+    sel->callback = cbfn;    
+    XConvertSelection(X.display, sel->atom, SelTarget, sel->atom, X.window, CurrentTime);
+    XFlush(X.display);
+    return true;
+}
+
+bool x11_setsel(int selid, char* str) {
+    struct XSel* sel = &(Selections[selid]);
+    if (!sel || !str || !*str) {
+        free(str);
+        return false;
+    } else {
+        sel->text = str;
+        XSetSelectionOwner(X.display, sel->atom, X.window, CurrentTime);
+        XFlush(X.display);
+        return true;
+    }
+}
index 7d8a89813d4500e1025d0f4f9a29ce398941611f..51464577ef553a29e5b143f7351d1a5aa42355b1 100644 (file)
@@ -9,6 +9,8 @@ enum {
 // Test Globals
 int Mods = 0;
 int ExitCode = 0;
+char* PrimaryText = NULL;
+char* ClipboardText = NULL;
 
 // fake out the exit routine
 void mockexit(int code) {
@@ -70,6 +72,22 @@ void x11_deinit(void) {
     mockexit(0);
 }
 
+bool x11_getsel(int selid, void(*cbfn)(char*)) {
+    cbfn(selid == PRIMARY ? PrimaryText : ClipboardText);
+    return true;
+}
+
+bool x11_setsel(int selid, char* str) {
+    if (selid == PRIMARY) {
+        free(PrimaryText);
+        PrimaryText = str;
+    } else {
+        free(ClipboardText);
+        ClipboardText = str;
+    }   
+    return true;
+}
+
 /* Unit Tests
  *****************************************************************************/
 TEST_SUITE(XeditTests) {
diff --git a/xedit.c b/xedit.c
index ba59599205f68b37b5cf8d958f6c9b0696944c73..9f7602b35590c0c12c5113e76493cbd48f8b0f1d 100644 (file)
--- a/xedit.c
+++ b/xedit.c
@@ -181,10 +181,8 @@ static KeyBinding Bindings[] = {
  *****************************************************************************/
 #ifdef __MACH__
 static char* CopyCmd[]  = { "pbcopy", NULL };
-static char* PasteCmd[] = { "pbpaste", NULL };
 #else
 static char* CopyCmd[]  = { "xsel", "-bi", NULL };
-static char* PasteCmd[] = { "xsel", "-bo", NULL };
 #endif
 static char* ShellCmd[] = { "/bin/sh", "-c", NULL, NULL };
 static char* PickFileCmd[] = { "xfilepick", ".", NULL };
@@ -523,25 +521,21 @@ static void tag_redo(void) {
 
 static void cut(void) {
     char* str = view_getstr(currview(), NULL);
-    if (str && *str) {
-        cmdwrite(CopyCmd, str, NULL);
-        delete();
-    }
-    free(str);
+    x11_setsel(CLIPBOARD, str);
+    if (str && *str) delete();
 }
 
 static void copy(void) {
     char* str = view_getstr(currview(), NULL);
-    if (str && *str)
-        cmdwrite(CopyCmd, str, NULL);
-    free(str);
+    x11_setsel(CLIPBOARD, str);
+}
+
+static void onpaste(char* text) {
+    view_putstr(currview(), text);
 }
 
 static void paste(void) {
-    char* str = cmdread(PasteCmd, NULL);
-    if (str && *str)
-        view_putstr(currview(), str);
-    free(str);
+    assert(x11_getsel(CLIPBOARD, onpaste));
 }
 
 static void search(void) {