From fc3faddf8124b73c4d0b09fc9eafe78eb10c79a3 Mon Sep 17 00:00:00 2001 From: "Michael D. Lowis" Date: Mon, 30 Jan 2017 12:46:03 -0500 Subject: [PATCH] Switch cut/copy/paste over to using X11 clipboard selection --- Makefile | 2 + TODO.md | 4 +- config.mk | 4 +- inc/edit.h | 1 + inc/x11.h | 8 ++++ lib/x11.c | 127 +++++++++++++++++++++++++++++++++++++++++++++++--- tests/xedit.c | 18 +++++++ xedit.c | 22 ++++----- 8 files changed, 163 insertions(+), 23 deletions(-) diff --git a/Makefile b/Makefile index 27359b4..2d626fe 100644 --- 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 a42e607..6c91974 100644 --- 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 diff --git a/config.mk b/config.mk index d1c8dd3..3a9bc82 100644 --- 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) diff --git a/inc/edit.h b/inc/edit.h index 6a7327c..43e925b 100644 --- a/inc/edit.h +++ b/inc/edit.h @@ -282,3 +282,4 @@ enum { 0xff859900 \ } #define DEFAULT_TAGS "Quit Save Undo Redo Cut Copy Paste | Find " + diff --git a/inc/x11.h b/inc/x11.h index 7563f54..97cb150 100644 --- 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); diff --git a/lib/x11.c b/lib/x11.c index 26d25a1..51919d4 100644 --- a/lib/x11.c +++ b/lib/x11.c @@ -6,6 +6,11 @@ #include #include +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; + } +} diff --git a/tests/xedit.c b/tests/xedit.c index 7d8a898..5146457 100644 --- a/tests/xedit.c +++ b/tests/xedit.c @@ -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 ba59599..9f7602b 100644 --- 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) { -- 2.51.0