]> git.mdlowis.com Git - projs/tide.git/commitdiff
refactored x11 code to share more code between gui apps
authorMichael D. Lowis <mike@mdlowis.com>
Wed, 2 Jan 2019 02:51:38 +0000 (21:51 -0500)
committerMichael D. Lowis <mike@mdlowis.com>
Wed, 2 Jan 2019 02:51:38 +0000 (21:51 -0500)
Makefile
TODO.md
inc/win.h
inc/x11.h
src/lib/win.c [new file with mode: 0644]
src/lib/x11.c
src/lib/x11_gc.c [new file with mode: 0644]
src/lib/x11_sel.c [new file with mode: 0644]
src/pick.c
src/tide.c

index e83abd3733e4658b9b9bd0ec5fcc86a51c807efe..1836afaa54fb3a77fcdd3f69f1856c30e60f9b67 100644 (file)
--- a/Makefile
+++ b/Makefile
@@ -2,12 +2,15 @@ INCS = -Iinc/
 BINS = bin/tide bin/registrar bin/edit bin/fetch bin/pick
 MAN1 = docs/tide.1
 
-LIBEDIT_OBJS =       \
-       src/lib/buf.o    \
-       src/lib/utf8.o   \
-       src/lib/job.o    \
-       src/lib/view.o   \
-       src/lib/x11.o
+LIBEDIT_OBJS = \
+       src/lib/buf.o \
+       src/lib/utf8.o \
+       src/lib/job.o \
+       src/lib/view.o \
+       src/lib/win.o \
+       src/lib/x11.o \
+       src/lib/x11_gc.o \
+       src/lib/x11_sel.o
 
 TEST_BINS = \
        tests/libedit
@@ -47,12 +50,10 @@ gcov:
 libedit.a: $(LIBEDIT_OBJS)
        $(AR) $(ARFLAGS) $@ $^
 
-bin/tide: src/tide.o libedit.a
-bin/registrar: src/registrar.o
 tests/libedit: tests/libedit.o tests/lib/buf.o tests/lib/utf8.o tests/lib/win.o libedit.a
 
 # define implicit rule for building binaries
-bin/%: src/%.o
+bin/%: src/%.o libedit.a
        $(LD) -o $@ $^ $(LDFLAGS)
 
 # load generate dependencies
diff --git a/TODO.md b/TODO.md
index 43cc9b0870cfbcf759f1a7441f5a6898ae385559..c8c7516101b12f5adc0c7e2effd07e0e1a77ebe7 100644 (file)
--- a/TODO.md
+++ b/TODO.md
@@ -15,6 +15,7 @@
 * tide: should re-register with the registrar when a new registrar is launched
 * tide: Line - Get the current line number(s) containing the selection
 * tide: gap buffer does not handle UTF-8 currently
+* tide: holding cut shortcut will segfault eventually, paste probably as well
 * edit: hangs after launching an empty tide instance then trying to open already open file
 
 ## BACKLOG
index a939c3098b94ebb00c9d61c53b11b992a93a05c4..5d555e70319f834ca5c43c0a8dc5dee689de336e 100644 (file)
--- a/inc/win.h
+++ b/inc/win.h
@@ -98,12 +98,6 @@ enum {
     ModAny        = -1
 };
 
-/* Selection identifiers */
-enum {
-    PRIMARY   = 0,
-    CLIPBOARD = 1
-};
-
 enum {
     MouseLeft    = 1,
     MouseMiddle  = 2,
index f416a6e5e92b1fcbd0a06604223e6f2514b43305..cd85b1b69bb0fa9a9c87db583adee0686386223a 100644 (file)
--- a/inc/x11.h
+++ b/inc/x11.h
@@ -1,14 +1,6 @@
 #include <X11/Xlib.h>
 #include <X11/Xatom.h>
 #include <X11/Xft/Xft.h>
-#include <X11/extensions/Xinerama.h>
-#include <sys/types.h>
-#include <sys/wait.h>
-#include <signal.h>
-#include <locale.h>
-
-#pragma GCC diagnostic push
-#pragma GCC diagnostic ignored "-Wunused-function"
 
 typedef struct XConf {
     Bool running;
@@ -28,168 +20,31 @@ typedef struct XConf {
     void (*eventfns[LASTEvent])(struct XConf*, XEvent*);
 } XConf;
 
-static int x11_init(XConf* x) {
-    signal(SIGPIPE, SIG_IGN); // Ignore the SIGPIPE signal
-    setlocale(LC_CTYPE, "");
-    XSetLocaleModifiers("");
-    /* open the X display and get basic attributes */
-    if (!(x->display = XOpenDisplay(0)))
-        return -1;
-    x->root = DefaultRootWindow(x->display);
-    XWindowAttributes wa;
-    XGetWindowAttributes(x->display, x->root, &wa);
-    x->visual   = wa.visual;
-    x->colormap = wa.colormap;
-    x->screen   = DefaultScreen(x->display);
-    x->depth    = DefaultDepth(x->display, x->screen);
-    x->running = True;
-    return 0;
-}
-
-static void x11_mkwin(XConf* x, int width, int height, int evmask) {
-    /* create the main window */
-    x->width = width, x->height = height;
-    x->self = XCreateSimpleWindow(
-        x->display, x->root, 0, 0, x->width, x->height, 0, x->depth, -1);
-    /* register interest in the delete window message */
-    Atom wmDeleteMessage = XInternAtom(x->display, "WM_DELETE_WINDOW", False);
-    XSetWMProtocols(x->display, x->self, &wmDeleteMessage, 1);
-    /* setup window attributes and events */
-    XSetWindowAttributes swa;
-    swa.backing_store = WhenMapped;
-    swa.bit_gravity = NorthWestGravity;
-    XChangeWindowAttributes(x->display, x->self, CWBackingStore|CWBitGravity, &swa);
-    XSelectInput(x->display, x->self, evmask);
-}
-
-static void x11_mkdialog(XConf* x, int width, int height, int evmask) {
-    x11_mkwin(x, width, height, evmask);
-    Atom WindowType = XInternAtom(x->display, "_NET_WM_WINDOW_TYPE", False);
-    Atom DialogType = XInternAtom(x->display, "_NET_WM_WINDOW_TYPE_DIALOG", False);
-    XChangeProperty(x->display, x->self, WindowType, XA_ATOM, 32, PropModeReplace, (unsigned char*)&DialogType, 1);
-}
-
-static int x11_getptr(XConf* x, int* ptrx, int* ptry) {
-    Window root = 0, child = 0;
-    int winx = 0, winy = 0, mask = 0;
-    return XQueryPointer(x->display, x->self, &root, &child, ptrx, ptry, &winx, &winy, (unsigned int*)&mask);
-}
-
-static void x11_centerwin(XConf* x) {
-    int ptrx = 0, ptry = 0;
-    (void)x11_getptr(x, &ptrx, &ptry);
-    int nscreens = 0;
-    XineramaScreenInfo* p_screens = XineramaQueryScreens(x->display, &nscreens);
-    for (int i = 0; i < nscreens; i++) {
-        int minx = p_screens[i].x_org,
-            maxx = p_screens[i].x_org + p_screens[i].width,
-            miny = p_screens[i].y_org,
-            maxy = p_screens[i].y_org + p_screens[i].height;
-        if (minx <= ptrx && ptrx <= maxx && miny <= ptry && ptry <= maxy) {
-            XMoveWindow(x->display, x->self,
-                minx + p_screens[i].width/2 - x->width/2,
-                miny + p_screens[i].height/2 - x->height/2);
-            break;
-        }
-    }
-}
-
-static void x11_init_gc(XConf* x) {
-    /* set input methods */
-    if ((x->xim = XOpenIM(x->display, 0, 0, 0)))
-        x->xic = XCreateIC(x->xim, XNInputStyle, XIMPreeditNothing|XIMStatusNothing, XNClientWindow, x->self, XNFocusWindow, x->self, NULL);
-    /* initialize pixmap and drawing context */
-    x->pixmap = XCreatePixmap(x->display, x->self, x->width, x->height, x->depth);
-    x->xft    = XftDrawCreate(x->display, x->pixmap, x->visual, x->colormap);
-    /* initialize the graphics context */
-    XGCValues gcv;
-    gcv.foreground = WhitePixel(x->display, x->screen);
-    gcv.graphics_exposures = False;
-    x->gc = XCreateGC(x->display, x->self, GCForeground|GCGraphicsExposures, &gcv);
-}
-
-static void x11_show(XConf* x) {
-    /* simulate an initial resize and map the window */
-    XConfigureEvent ce;
-    ce.type   = ConfigureNotify;
-    ce.width  = x->width;
-    ce.height = x->height;
-    XSendEvent(x->display, x->self, False, StructureNotifyMask, (XEvent *)&ce);
-    XMapWindow(x->display, x->self);
-}
-
-static void x11_event_loop(XConf* x, void (*redraw)(XConf* x)) {
-    if (redraw) redraw(x);
-    for (XEvent e; x->running;) {
-        XNextEvent(x->display, &e);
-        if (x->eventfns[e.type])
-            x->eventfns[e.type](x, &e);
-        for (int status; waitpid(-1, &status, WNOHANG) > 0;);
-        if (redraw) redraw(x);
-    }
-}
-
-static XftFont* x11_font_load(XConf* x, char* name) {
-    /* init the library and the base font pattern */
-    if (!FcInit()) return NULL;
-    FcPattern* pattern = FcNameParse((FcChar8 *)name);
-    if (!pattern) return NULL;
-    /* load the base font */
-    FcResult result;
-    FcPattern* match = XftFontMatch(x->display, x->screen, pattern, &result);
-    XftFont* font = NULL;
-    if (match)
-        font = XftFontOpenPattern(x->display, match);
-    FcPatternDestroy(pattern);
-    FcPatternDestroy(match);
-    return font;
-}
-
-static void xftcolor(XConf* x, XftColor* xc, unsigned int c) {
-    #define COLOR(c) ((c) | ((c) >> 8))
-    xc->color.alpha = 0xFFFF;
-    xc->color.red   = COLOR((c & 0x00FF0000) >> 8);
-    xc->color.green = COLOR((c & 0x0000FF00));
-    xc->color.blue  = COLOR((c & 0x000000FF) << 8);
-    XftColorAllocValue(x->display, x->visual, x->colormap, &(xc->color), xc);
-}
-
-static void x11_draw_rect(XConf* x, int color, int px, int py, int width, int height) {
-    XftColor clr;
-    xftcolor(x, &clr, color);
-    XftDrawRect(x->xft, &clr, px, py, width, height);
-    XftColorFree(x->display, x->visual, x->colormap, &clr);
-}
-
-static void x11_draw_glyphs(XConf* x, int color, XftFont* font, XftGlyphSpec* specs, long nspecs) {
-    XftColor clr;
-    xftcolor(x, &clr, color);
-    XftDrawGlyphSpec(x->xft, &clr, font, specs, nspecs);
-    XftColorFree(x->display, x->visual, x->colormap, &clr);
-}
-
-static void x11_flip(XConf* x) {
-    XCopyArea(x->display, x->pixmap, x->self, x->gc, 0, 0, x->width, x->height, 0, 0);
-    XFlush(x->display);
-}
-
-static KeySym x11_getkey(XConf* x, XEvent* e) {
-    char buf[8];
-    KeySym key;
-    Status status;
-    /* Read the key string */
-    if (x->xic)
-        Xutf8LookupString(x->xic, &(e->xkey), buf, sizeof(buf), &key, &status);
-    else
-        XLookupString(&(e->xkey), buf, sizeof(buf), &key, 0);
-    return key;
-}
-
-static void x11_draw_string(XConf* x, XftFont* font, int posx, int posy, int color, char* str) {
-    XftColor clr;
-    xftcolor(x, &clr, color);
-    XftDrawStringUtf8(x->xft, &clr, font, posx, posy, (const FcChar8*)str, strlen(str));
-    XftColorFree(x->display, x->visual, x->colormap, &clr);
-}
-
-#pragma GCC diagnostic pop
+/* Selection identifiers */
+enum {
+    PRIMARY   = 0,
+    CLIPBOARD = 1
+};
+
+int x11_init(XConf* x);
+void x11_mkwin(XConf* x, int width, int height, int evmask);
+void x11_mkdialog(XConf* x, int width, int height, int evmask);
+void x11_event_loop(XConf* x, void (*redraw)(XConf* x));
+int x11_getptr(XConf* x, int* ptrx, int* ptry);
+KeySym x11_getkey(XConf* x, XEvent* e);
+
+void x11_centerwin(XConf* x);
+void x11_init_gc(XConf* x);
+void x11_show(XConf* x);
+XftFont* x11_font_load(XConf* x, char* name);
+void xftcolor(XConf* x, XftColor* xc, unsigned int c);
+void x11_draw_rect(XConf* x, int color, int px, int py, int width, int height);
+void x11_draw_glyphs(XConf* x, int color, XftFont* font, XftGlyphSpec* specs, long nspecs);
+void x11_flip(XConf* x);
+void x11_draw_string(XConf* x, XftFont* font, int posx, int posy, int color, char* str);
+
+void x11_sel_init(XConf* x);
+int x11_sel_get(XConf* x, int selid, void(*cbfn)(char*));
+int x11_sel_set(XConf* x, int selid, char* str);
+void x11_sel_quit(XConf* x, XEvent* e);
+int x11_sel_ready(XConf* x);
diff --git a/src/lib/win.c b/src/lib/win.c
new file mode 100644 (file)
index 0000000..131fb3d
--- /dev/null
@@ -0,0 +1,486 @@
+#define _POSIX_C_SOURCE 200809L
+#define _XOPEN_SOURCE   700
+#include <unistd.h>
+#include <limits.h>
+#include <stdc.h>
+#include <utf.h>
+#include <edit.h>
+#include <win.h>
+#include <locale.h>
+#include <sys/time.h>
+#include <sys/types.h>
+#include <ctype.h>
+#include <x11.h>
+
+#include "config.h"
+
+/******************************************************************************/
+static Time Now;
+static struct XConf X;
+static int KeyBtnState;
+static WinRegion Focused = EDIT;
+static View Regions[NREGIONS];
+static KeyBinding* Keys = NULL;
+static int Divider;
+static int FontSel;
+static bool SyncMouse = false;
+
+int SearchDir = DOWN;
+char* SearchTerm = NULL;
+
+/******************************************************************************/
+
+#define PRESSED(btn) \
+    ((KeyBtnState & (1 << (btn + 7))) == (1 << (btn + 7)))
+
+static void font_load(char* name) {
+    XftFont* font = x11_font_load(&X, name);
+    X.font = (font ? font : X.font); // Update if we found a new font
+}
+
+static void get_position(WinRegion id, int x, int y, size_t* row, size_t* col) {
+    int starty = (id == EDIT ? Divider+3 : 0);
+    int startx = ScrollWidth+3;
+    int maxy = (id == EDIT
+        ? (Divider + (int)(win_view(EDIT)->nrows * X.font->height))
+        : Divider - 4
+    );
+    x = (x < 0 ? 0 : (x > X.width ? X.width : x));
+    y = (y < starty ? starty : (y > maxy ? maxy : y));
+    *row = (y - starty) / X.font->height;
+    *col = (startx <= x ? x - startx : 0);
+}
+
+static void tide_send(char* type) {
+    XEvent ev;
+    memset(&ev, 0, sizeof (ev));
+    Atom xa_registrar = XInternAtom(X.display, "TIDE_REGISTRAR", 0);
+    Window regwin = XGetSelectionOwner(X.display, xa_registrar);
+    if (None != regwin) {
+        ev.xclient.type = ClientMessage;
+        ev.xclient.window = X.self;
+        ev.xclient.message_type = xa_registrar;
+        ev.xclient.format = 32;
+        ev.xclient.data.l[0] = XInternAtom(X.display, type, 0);
+        XSendEvent(X.display, regwin, False, NoEventMask, &ev);
+        XFlush(X.display);
+    }
+}
+
+/******************************************************************************/
+
+static uint32_t special_keys(uint32_t key) {
+    static uint32_t keymap[256] = {
+        /* Function keys */
+        [0xBE] = KEY_F1, [0xBF] = KEY_F2,  [0xC0] = KEY_F3,  [0xC1] = KEY_F4,
+        [0xC2] = KEY_F5, [0xC3] = KEY_F6,  [0xC4] = KEY_F7,  [0xC5] = KEY_F8,
+        [0xC6] = KEY_F9, [0xC7] = KEY_F10, [0xC8] = KEY_F11, [0xC9] = KEY_F12,
+        /* Navigation keys */
+        [0x50] = KEY_HOME,  [0x51] = KEY_LEFT, [0x52] = KEY_UP,
+        [0x53] = KEY_RIGHT, [0x54] = KEY_DOWN, [0x55] = KEY_PGUP,
+        [0x56] = KEY_PGDN,  [0x57] = KEY_END,
+        /* Control keys */
+        [0x08] = '\b', [0x09] = '\t', [0x0d] = '\n', [0x0a] = '\n',
+        /* Miscellaneous */
+        [0x63] = KEY_INSERT, [0x1B] = KEY_ESCAPE, [0xFF] = KEY_DELETE,
+    };
+    /* lookup the key by keysym */
+    key = ((key & 0xFF00) == 0xFF00 ? keymap[key & 0xFF] : key);
+    return (!key ? RUNE_ERR : key);
+}
+
+static uint32_t getkey(XConf* x, XEvent* e) {
+    KeySym key = x11_getkey(x, e);
+    /* if it's ascii, just return it */
+    if (key >= 0x20 && key <= 0x7F)
+        return (uint32_t)key;
+    /* translate special key codes into unicode codepoints */
+    return special_keys(key);
+}
+
+static void mouse_left(WinRegion id, bool pressed, size_t row, size_t col) {
+    static int count = 0;
+    static Time before = 0;
+    if (!pressed) return;
+    count = ((Now - before) <= (uint64_t)ClickTime ? count+1 : 1);
+    before = Now;
+    if (PRESSED(MouseRight)) {
+        puts("fetch tag");
+    }  else if (PRESSED(MouseMiddle)) {
+        puts("exec with arg");
+    } else {
+        if (count == 1)
+            view_setcursor(win_view(id), row, col, win_keymodsset(ModShift));
+        else if (count == 2)
+            view_select(win_view(id), row, col);
+        else if (count == 3)
+            view_selword(win_view(id), row, col);
+    }
+}
+
+static void mouse_middle(WinRegion id, bool pressed, size_t row, size_t col) {
+    if (pressed) return;
+    if (PRESSED(MouseLeft)) {
+        cut(NULL);
+    } else {
+        char* str = view_fetch(win_view(id), row, col, riscmd);
+        if (str) exec(str);
+        free(str);
+    }
+}
+
+static void mouse_right(WinRegion id, bool pressed, size_t row, size_t col) {
+    if (pressed) return;
+    if (PRESSED(MouseLeft)) {
+        paste(NULL);
+    } else {
+        FetchCmd[1] = view_fetch(win_view(id), row, col, risfile);
+        if (job_run(FetchCmd) != 0) {
+            SearchDir *= (win_keymodsset(ModShift) ? -1 : +1);
+            free(SearchTerm);
+            SearchTerm = view_fetch(win_view(id), row, col, risfile);
+            view_findstr(win_view(EDIT), SearchDir, SearchTerm);
+            SyncMouse = true;
+        } else {
+            free(FetchCmd[1]);
+        }
+    }
+}
+
+static void mouse_scroll(WinRegion id, bool pressed, int amount) {
+    if (!pressed)
+        view_scroll(win_view(id), amount);
+}
+
+static void mouse_click(int btn, bool pressed, int x, int y) {
+    size_t row, col;
+    Focused = (y <= Divider ? TAGS : EDIT);
+    get_position(Focused, x, y, &row, &col);
+    switch(btn) {
+        case MouseLeft:    mouse_left(Focused, pressed, row, col);    break;
+        case MouseMiddle:  mouse_middle(Focused, pressed, row, col);  break;
+        case MouseRight:   mouse_right(Focused, pressed, row, col);   break;
+        case MouseWheelUp: mouse_scroll(Focused, pressed, -ScrollBy); break;
+        case MouseWheelDn: mouse_scroll(Focused, pressed, +ScrollBy); break;
+    }
+}
+
+/******************************************************************************/
+
+size_t glyph_width(View* view, int c) {
+    FcChar32 rune = (FcChar32)c;
+    XGlyphInfo extents;
+    XftFont* font = (&Regions[TAGS] == view ? X.tagfont : X.font);
+    XftTextExtents32(X.display, font, &rune, 1, &extents);
+    if (c == '\t')
+        return (TabWidth *  extents.xOff);
+    else
+        return extents.xOff;
+}
+
+static void draw_rect(int color, int x, int y, int width, int height) {
+    x11_draw_rect(&X, Palette[color], x, y, width, height);
+}
+
+static void draw_statbox(void) {
+    draw_rect(VerBdr, ScrollWidth, 0, 1, X.height/4);
+    switch (win_view(EDIT)->buffer.status) {
+        case NORMAL:   draw_rect(TagsBg, 0, 0, ScrollWidth, X.height/4); break;
+        case MODIFIED: draw_rect(Purple, 0, 0, ScrollWidth, X.height/4); break;
+        case OUTDATED: draw_rect(Orange, 0, 0, ScrollWidth, X.height/4); break;
+        case ERRORED:  draw_rect(Red, 0, 0, ScrollWidth, X.height/4);    break;
+    }
+}
+
+static bool draw_csr(View* view, size_t fheight, int x, int y, bool csrdrawn) {
+    if (!csrdrawn && !view_selsize(view)) {
+        int csrclr = (view == &Regions[TAGS] ? TagsCsr : EditCsr);
+        draw_rect(csrclr, x-1, y, 3, 3);
+        draw_rect(csrclr, x, y, 1, fheight);
+        draw_rect(csrclr, x-1, y+fheight-3, 3, 3);
+        csrdrawn = true;
+    }
+    if (SyncMouse && view == &Regions[EDIT]) {
+        XWarpPointer(X.display, X.self, X.self, 0, 0, X.width, X.height, x-4, y + fheight/2);
+        SyncMouse = false;
+    }
+    return csrdrawn;
+}
+
+static void draw_view(View* view, XftFont* font, size_t nrows, drawcsr* csr, int bg, int fg, int sel) {
+    int nspecs = 0;
+    XftGlyphSpec* specs = NULL;
+    size_t fheight = font->height;
+    bool csr_drawn = false;
+    /* draw the view to the window */
+    view_resize(view, (csr->w - csr->x), nrows);
+    view_update(view);
+    draw_rect(bg, csr->x, csr->y, csr->w, ((nrows + 1) * fheight) + 9);
+    for (size_t i = 0; i < nrows; i++) {
+        Row* row = view_getrow(view, i + view->index);
+        size_t x = (csr->x + 2), y = (csr->y + 2 + (i * fheight));
+        for (size_t i = 0; i < row->len; i++) {
+            int rune = row->cols[i].rune;
+            if (rune == '\r' || rune == '\n' ||  rune == '\t')
+                rune = ' ';
+            if (buf_insel(&(view->buffer), row->cols[i].off))
+                draw_rect(sel, x, y, row->cols[i].width, fheight);
+            if (row->cols[i].off == view->buffer.selection.end)
+                csr_drawn = draw_csr(view, fheight, x, y, csr_drawn);
+            specs = realloc(specs, sizeof(XftGlyphSpec) * ++nspecs);
+            specs[nspecs-1].glyph = XftCharIndex(X.display, font, rune);
+            specs[nspecs-1].x = x;
+            specs[nspecs-1].y = y + font->ascent;
+            x += row->cols[i].width;
+        }
+    }
+    x11_draw_glyphs(&X, Palette[fg], font, specs, nspecs);
+    csr->y += (nrows * fheight) + 3;
+    free(specs);
+}
+
+static void draw_hrule(drawcsr* csr) {
+    draw_rect(HorBdr, 0, csr->y + 1, X.width, 1);
+    Divider = csr->y;
+    csr->y += 2;
+}
+
+static void draw_scroll(drawcsr* csr) {
+    View* view = win_view(EDIT);
+    size_t bend = buf_end(win_buf(EDIT));
+    if (bend == 0) bend = 1;
+    if (!view->rows || !view->nrows) return;
+    size_t vbeg = view->rows[0]->off;
+    size_t vend = view->rows[view->nrows-1]->off + view->rows[view->nrows-1]->len;
+    double scroll_vis = (double)(vend - vbeg) / (double)bend;
+    double scroll_off = ((double)vbeg / (double)bend);
+    size_t thumbreg = (csr->h - Divider) + 4;
+    size_t thumboff = (size_t)((thumbreg * scroll_off) + Divider);
+    size_t thumbsz  = (size_t)(thumbreg * scroll_vis);
+    if (thumbsz < 5) thumbsz = 5;
+    draw_rect(VerBdr,   ScrollWidth, Divider + 2,  1,           thumbreg);
+    draw_rect(ScrollBg, 0,           Divider + 2,  ScrollWidth, thumbreg);
+    draw_rect(ScrollFg, 0,           thumboff + 2, ScrollWidth, thumbsz);
+}
+
+/******************************************************************************/
+
+static void xkeypress(XConf* x, XEvent* e) {
+    (void)x;
+    Now = e->xkey.time;
+    Focused = (e->xkey.y <= Divider ? TAGS : EDIT);
+    uint32_t key = getkey(x, e);
+    if (key == RUNE_ERR) return;
+    KeyBtnState = e->xkey.state;
+    int mods = KeyBtnState & (ModCtrl|ModShift|ModAlt);
+    int32_t mkey = tolower(key);
+    for (KeyBinding* bind = Keys; bind && bind->key; bind++) {
+        bool match   = (mkey == bind->key);
+        bool exact   = (bind->mods == mods);
+        bool any     = (bind->mods == ModAny);
+        bool oneplus = ((bind->mods == ModOneOrMore) && (mods & ModOneOrMore));
+        if (match && (exact || oneplus || any)) {
+            bind->fn(bind->arg);
+            return;
+        }
+    }
+    /* fallback to just inserting the rune if it doesn't fall in the private use area.
+     * the private use area is used to encode special keys */
+    if (key < 0xE000 || key > 0xF8FF)
+        view_insert(win_view(FOCUSED), key);
+}
+
+static void xbtnpress(XConf* x, XEvent* e) {
+    (void)x;
+    Now = e->xbutton.time;
+    KeyBtnState = (e->xbutton.state | (1 << (e->xbutton.button + 7)));
+    mouse_click(e->xbutton.button, true, e->xbutton.x,  e->xbutton.y);
+}
+
+static void xbtnrelease(XConf* x, XEvent* e) {
+    (void)x;
+    Now = e->xbutton.time;
+    KeyBtnState = (e->xbutton.state & ~(1 << (e->xbutton.button + 7)));
+    mouse_click(e->xbutton.button, false, e->xbutton.x,  e->xbutton.y);
+}
+
+static void xbtnmotion(XConf* x, XEvent* e) {
+    while (XCheckTypedEvent(x->display, MotionNotify, e));
+    Now = e->xbutton.time;
+    size_t row, col;
+    KeyBtnState = e->xbutton.state;
+    int xpos = e->xbutton.x, ypos = e->xbutton.y;
+    get_position(Focused, xpos, ypos, &row, &col);
+    if (PRESSED(MouseLeft))
+        view_setcursor(win_view(Focused), row, col, true);
+}
+
+static void xclientmsg(XConf* x, XEvent* e) {
+    if ((Atom)(e->xclient.data.l[0]) == XInternAtom(x->display, "WM_DELETE_WINDOW", False))
+        win_quit();
+    else if (e->xclient.message_type == XInternAtom(x->display, "GOTO", False))
+        view_setln(win_view(EDIT), e->xclient.data.l[0]);
+}
+
+static void xupdate(Job* job) {
+    int nevents, nqueued;
+    do {
+        nqueued = XEventsQueued(X.display, QueuedAfterFlush);
+        XGetMotionEvents(X.display, X.self, CurrentTime, CurrentTime, &nevents);
+        for (XEvent e; XPending(X.display);) {
+            XNextEvent(X.display, &e);
+            if (!XFilterEvent(&e, None) && X.eventfns[e.type])
+                (X.eventfns[e.type])(&X, &e);
+            for (int status; waitpid(-1, &status, WNOHANG) > 0;);
+        }
+        if (nqueued || !job) {
+            /* force update the title */
+            win_title(NULL);
+            /* determine the size of the regions */
+            size_t maxtagrows = ((X.height/4) / X.tagfont->height);
+            size_t tagrows = view_limitrows(win_view(TAGS), maxtagrows);
+            size_t tagregsz = (tagrows * X.tagfont->height) + 7;
+            size_t editrows = (X.height - tagregsz) / X.font->height ;
+            /* draw the regions to the window */
+            drawcsr csr = { .w = X.width, .h = X.height };
+            csr.x += ScrollWidth + 1;
+            draw_statbox();
+            draw_view(&Regions[TAGS], X.tagfont, tagrows, &csr, TagsBg, TagsFg, TagsSel);
+            draw_hrule(&csr);
+            draw_view(&Regions[EDIT], X.font, editrows, &csr, EditBg, EditFg, EditSel);
+            draw_scroll(&csr);
+            XCopyArea(X.display, X.pixmap, X.self, X.gc, 0, 0, X.width, X.height, 0, 0);
+        }
+    } while ((nqueued = XEventsQueued(X.display, QueuedAfterFlush)) > 0);
+}
+
+/******************************************************************************/
+
+void win_init(KeyBinding* bindings) {
+    Keys = bindings;
+    view_init(&Regions[TAGS], NULL);
+    view_init(&Regions[EDIT], NULL);
+    signal(SIGPIPE, SIG_IGN); // Ignore the SIGPIPE signal
+    setlocale(LC_CTYPE, "");
+    XSetLocaleModifiers("");
+
+    /* open the X display and get basic attributes */
+    x11_init(&X);
+    font_load(Fonts[FontSel = 0]);
+    X.tagfont = X.font;
+    if (!X.font) {
+        perror("unable to load base font");
+        exit(EXIT_FAILURE);
+    }
+    x11_mkwin(&X, 640, 480, 0
+        | FocusChangeMask
+        | KeyPressMask
+        | ButtonPressMask
+        | ButtonReleaseMask
+        | ButtonMotionMask
+        | StructureNotifyMask
+        | PropertyChangeMask
+        | ExposureMask
+    );
+    x11_init_gc(&X);
+    x11_sel_init(&X);
+    x11_centerwin(&X);
+    x11_show(&X);
+
+    /* register event handlers */
+    X.eventfns[KeyPress] = xkeypress;
+    X.eventfns[ButtonPress] = xbtnpress;
+    X.eventfns[ButtonRelease] = xbtnrelease;
+    X.eventfns[MotionNotify] = xbtnmotion;
+    X.eventfns[ClientMessage] = xclientmsg;
+
+    /* Populate the  tags region */
+    View* view = win_view(TAGS);
+    view_putstr(view, TagString);
+    buf_logclear(&(view->buffer));
+}
+
+void win_title(char* path) {
+    static char prevtitle[4096] = {0};
+    char title[4096] = {0};
+    if (!path) path = win_view(EDIT)->buffer.path;
+    if (!path) path = "*scratch*";
+    snprintf(title, sizeof(title)-1, "[%c%c] %s",
+        (DosLineFeed ? 'C' : 'N'),
+        (ExpandTabs ? 'S' : 'T'),
+        path);
+    if (strcmp(prevtitle, title))
+        XStoreName(X.display, X.self, title);
+    memcpy(prevtitle, title, sizeof(title));
+}
+
+void win_font(char* font) {
+    font_load(font ? font : Fonts[++FontSel % nelem(Fonts)]);
+}
+
+void win_prop_set(char* xname, char* ename, char* value) {
+    if (!value) return;
+    Atom propname = XInternAtom(X.display, xname, 0);
+    XChangeProperty(X.display, X.self, propname, XA_STRING, 8, PropModeReplace,
+        (const unsigned char *)value, strlen(value));
+    if (ename) setenv(ename, value, 1);
+}
+
+void win_update(int ms) {
+    if (job_poll(ms))
+        xupdate(NULL);
+}
+
+void win_loop(void) {
+    X.running = True;
+    XMapWindow(X.display, X.self);
+    tide_send("ADD");
+    job_spawn(ConnectionNumber(X.display), xupdate, 0, 0);
+    while (X.running) {
+        win_update(Timeout);
+    }
+}
+
+void win_quit(void) {
+    static uint64_t before = 0;
+    if ((win_buf(EDIT)->status != MODIFIED) || (Now - before) <= (uint64_t)ClickTime) {
+        tide_send("DEL");
+        X.eventfns[SelectionClear] = x11_sel_quit;
+        XUnmapWindow(X.display, X.self);
+        if (!x11_sel_ready(&X)) {
+            X.running = False;
+        } else {
+            if (fork()) exit(0); /* fork into background if we still have selection */
+        }
+    }
+    before = Now;
+}
+
+void win_togglefocus(void) {
+    int ypos = (Focused == EDIT ? Divider/2 : (X.height - ((X.height-Divider) / 2)));
+    XWarpPointer(X.display, X.self, X.self, 0, 0, X.width, X.height, X.width/2, ypos);
+}
+
+View* win_view(WinRegion id) {
+    return &(Regions[id == FOCUSED ? Focused : id]);
+}
+
+Buf* win_buf(WinRegion id) {
+    return &(Regions[id == FOCUSED ? Focused : id].buffer);
+}
+
+bool win_keymodsset(int mask) {
+    return ((KeyBtnState & mask) == mask);
+}
+
+bool win_sel_get(int selid, void(*cbfn)(char*)) {
+    return x11_sel_get(&X, selid, cbfn);
+}
+
+bool win_sel_set(int selid, char* str) {
+    return x11_sel_set(&X, selid, str);
+}
+
+void win_syncmouse(void) {
+    SyncMouse = true;
+}
index 956f70eb7c72715dd01e03bcae7cb15e986a1e31..91e0012f8a6b5bdf01fc9fb46a7b611c3a08021a 100644 (file)
-#define _POSIX_C_SOURCE 200809L
-#define _XOPEN_SOURCE   700
-#include <unistd.h>
-#include <limits.h>
-#include <stdc.h>
-#include <utf.h>
-#include <edit.h>
-#include <win.h>
-#include <locale.h>
-#include <sys/time.h>
-#include <sys/types.h>
-#include <ctype.h>
 #include <x11.h>
+#include <locale.h>
 
-#include "config.h"
-
-struct XSel {
-    char* name;
-    Atom atom;
-    char* text;
-    void (*callback)(char*);
-};
-
-/******************************************************************************/
-static Time Now;
-static struct XConf X;
-static int KeyBtnState;
-static WinRegion Focused = EDIT;
-static View Regions[NREGIONS];
-static KeyBinding* Keys = NULL;
-static int Divider;
-static Atom SelTarget;
-static int FontSel;
-static bool SyncMouse = false;
-static struct XSel Selections[] = {
-    { .name = "PRIMARY" },
-    { .name = "CLIPBOARD" },
-};
-
-int SearchDir = DOWN;
-char* SearchTerm = NULL;
-
-/******************************************************************************/
-
-#define PRESSED(btn) \
-    ((KeyBtnState & (1 << (btn + 7))) == (1 << (btn + 7)))
-
-static void font_load(char* name) {
-    XftFont* font = x11_font_load(&X, name);
-    X.font = (font ? font : X.font); // Update if we found a new font
-}
-
-static void get_position(WinRegion id, int x, int y, size_t* row, size_t* col) {
-    int starty = (id == EDIT ? Divider+3 : 0);
-    int startx = ScrollWidth+3;
-    int maxy = (id == EDIT
-        ? (Divider + (int)(win_view(EDIT)->nrows * X.font->height))
-        : Divider - 4
-    );
-    x = (x < 0 ? 0 : (x > X.width ? X.width : x));
-    y = (y < starty ? starty : (y > maxy ? maxy : y));
-    *row = (y - starty) / X.font->height;
-    *col = (startx <= x ? x - startx : 0);
-}
-
-static struct XSel* selfetch(Atom atom) {
-    for (unsigned int i = 0; i < (sizeof(Selections) / sizeof(Selections[0])); i++)
-        if (atom == Selections[i].atom)
-            return &Selections[i];
-    return NULL;
-}
-
-static void tide_send(char* type) {
-    XEvent ev;
-    memset(&ev, 0, sizeof (ev));
-    Atom xa_registrar = XInternAtom(X.display, "TIDE_REGISTRAR", 0);
-    Window regwin = XGetSelectionOwner(X.display, xa_registrar);
-    if (None != regwin) {
-        ev.xclient.type = ClientMessage;
-        ev.xclient.window = X.self;
-        ev.xclient.message_type = xa_registrar;
-        ev.xclient.format = 32;
-        ev.xclient.data.l[0] = XInternAtom(X.display, type, 0);
-        XSendEvent(X.display, regwin, False, NoEventMask, &ev);
-        XFlush(X.display);
-    }
-}
-
-/******************************************************************************/
-
-static uint32_t special_keys(uint32_t key) {
-    static uint32_t keymap[256] = {
-        /* Function keys */
-        [0xBE] = KEY_F1, [0xBF] = KEY_F2,  [0xC0] = KEY_F3,  [0xC1] = KEY_F4,
-        [0xC2] = KEY_F5, [0xC3] = KEY_F6,  [0xC4] = KEY_F7,  [0xC5] = KEY_F8,
-        [0xC6] = KEY_F9, [0xC7] = KEY_F10, [0xC8] = KEY_F11, [0xC9] = KEY_F12,
-        /* Navigation keys */
-        [0x50] = KEY_HOME,  [0x51] = KEY_LEFT, [0x52] = KEY_UP,
-        [0x53] = KEY_RIGHT, [0x54] = KEY_DOWN, [0x55] = KEY_PGUP,
-        [0x56] = KEY_PGDN,  [0x57] = KEY_END,
-        /* Control keys */
-        [0x08] = '\b', [0x09] = '\t', [0x0d] = '\n', [0x0a] = '\n',
-        /* Miscellaneous */
-        [0x63] = KEY_INSERT, [0x1B] = KEY_ESCAPE, [0xFF] = KEY_DELETE,
-    };
-    /* lookup the key by keysym */
-    key = ((key & 0xFF00) == 0xFF00 ? keymap[key & 0xFF] : key);
-    return (!key ? RUNE_ERR : key);
-}
-
-static uint32_t getkey(XConf* x, XEvent* e) {
-    KeySym key = x11_getkey(x, e);
-    /* if it's ascii, just return it */
-    if (key >= 0x20 && key <= 0x7F)
-        return (uint32_t)key;
-    /* translate special key codes into unicode codepoints */
-    return special_keys(key);
-}
-
-static void mouse_left(WinRegion id, bool pressed, size_t row, size_t col) {
-    static int count = 0;
-    static Time before = 0;
-    if (!pressed) return;
-    count = ((Now - before) <= (uint64_t)ClickTime ? count+1 : 1);
-    before = Now;
-    if (PRESSED(MouseRight)) {
-        puts("fetch tag");
-    }  else if (PRESSED(MouseMiddle)) {
-        puts("exec with arg");
-    } else {
-        if (count == 1)
-            view_setcursor(win_view(id), row, col, win_keymodsset(ModShift));
-        else if (count == 2)
-            view_select(win_view(id), row, col);
-        else if (count == 3)
-            view_selword(win_view(id), row, col);
-    }
-}
-
-static void mouse_middle(WinRegion id, bool pressed, size_t row, size_t col) {
-    if (pressed) return;
-    if (PRESSED(MouseLeft)) {
-        cut(NULL);
-    } else {
-        char* str = view_fetch(win_view(id), row, col, riscmd);
-        if (str) exec(str);
-        free(str);
-    }
-}
-
-static void mouse_right(WinRegion id, bool pressed, size_t row, size_t col) {
-    if (pressed) return;
-    if (PRESSED(MouseLeft)) {
-        paste(NULL);
-    } else {
-        FetchCmd[1] = view_fetch(win_view(id), row, col, risfile);
-        if (job_run(FetchCmd) != 0) {
-            SearchDir *= (win_keymodsset(ModShift) ? -1 : +1);
-            free(SearchTerm);
-            SearchTerm = view_fetch(win_view(id), row, col, risfile);
-            view_findstr(win_view(EDIT), SearchDir, SearchTerm);
-            SyncMouse = true;
-        } else {
-            free(FetchCmd[1]);
-        }
-    }
-}
-
-static void mouse_scroll(WinRegion id, bool pressed, int amount) {
-    if (!pressed)
-        view_scroll(win_view(id), amount);
-}
-
-static void mouse_click(int btn, bool pressed, int x, int y) {
-    size_t row, col;
-    Focused = (y <= Divider ? TAGS : EDIT);
-    get_position(Focused, x, y, &row, &col);
-    switch(btn) {
-        case MouseLeft:    mouse_left(Focused, pressed, row, col);    break;
-        case MouseMiddle:  mouse_middle(Focused, pressed, row, col);  break;
-        case MouseRight:   mouse_right(Focused, pressed, row, col);   break;
-        case MouseWheelUp: mouse_scroll(Focused, pressed, -ScrollBy); break;
-        case MouseWheelDn: mouse_scroll(Focused, pressed, +ScrollBy); break;
-    }
-}
-
-/******************************************************************************/
-
-size_t glyph_width(View* view, int c) {
-    FcChar32 rune = (FcChar32)c;
-    XGlyphInfo extents;
-    XftFont* font = (&Regions[TAGS] == view ? X.tagfont : X.font);
-    XftTextExtents32(X.display, font, &rune, 1, &extents);
-    if (c == '\t')
-        return (TabWidth *  extents.xOff);
-    else
-        return extents.xOff;
-}
-
-static void draw_rect(int color, int x, int y, int width, int height) {
-    x11_draw_rect(&X, Palette[color], x, y, width, height);
-}
-
-static void draw_statbox(void) {
-    draw_rect(VerBdr, ScrollWidth, 0, 1, X.height/4);
-    switch (win_view(EDIT)->buffer.status) {
-        case NORMAL:   draw_rect(TagsBg, 0, 0, ScrollWidth, X.height/4); break;
-        case MODIFIED: draw_rect(Purple, 0, 0, ScrollWidth, X.height/4); break;
-        case OUTDATED: draw_rect(Orange, 0, 0, ScrollWidth, X.height/4); break;
-        case ERRORED:  draw_rect(Red, 0, 0, ScrollWidth, X.height/4);    break;
-    }
-}
-
-static bool draw_csr(View* view, size_t fheight, int x, int y, bool csrdrawn) {
-    if (!csrdrawn && !view_selsize(view)) {
-        int csrclr = (view == &Regions[TAGS] ? TagsCsr : EditCsr);
-        draw_rect(csrclr, x-1, y, 3, 3);
-        draw_rect(csrclr, x, y, 1, fheight);
-        draw_rect(csrclr, x-1, y+fheight-3, 3, 3);
-        csrdrawn = true;
-    }
-    if (SyncMouse && view == &Regions[EDIT]) {
-        XWarpPointer(X.display, X.self, X.self, 0, 0, X.width, X.height, x-4, y + fheight/2);
-        SyncMouse = false;
-    }
-    return csrdrawn;
-}
-
-static void draw_view(View* view, XftFont* font, size_t nrows, drawcsr* csr, int bg, int fg, int sel) {
-    int nspecs = 0;
-    XftGlyphSpec* specs = NULL;
-    size_t fheight = font->height;
-    bool csr_drawn = false;
-    /* draw the view to the window */
-    view_resize(view, (csr->w - csr->x), nrows);
-    view_update(view);
-    draw_rect(bg, csr->x, csr->y, csr->w, ((nrows + 1) * fheight) + 9);
-    for (size_t i = 0; i < nrows; i++) {
-        Row* row = view_getrow(view, i + view->index);
-        size_t x = (csr->x + 2), y = (csr->y + 2 + (i * fheight));
-        for (size_t i = 0; i < row->len; i++) {
-            int rune = row->cols[i].rune;
-            if (rune == '\r' || rune == '\n' ||  rune == '\t')
-                rune = ' ';
-            if (buf_insel(&(view->buffer), row->cols[i].off))
-                draw_rect(sel, x, y, row->cols[i].width, fheight);
-            if (row->cols[i].off == view->buffer.selection.end)
-                csr_drawn = draw_csr(view, fheight, x, y, csr_drawn);
-            specs = realloc(specs, sizeof(XftGlyphSpec) * ++nspecs);
-            specs[nspecs-1].glyph = XftCharIndex(X.display, font, rune);
-            specs[nspecs-1].x = x;
-            specs[nspecs-1].y = y + font->ascent;
-            x += row->cols[i].width;
-        }
-    }
-    x11_draw_glyphs(&X, Palette[fg], font, specs, nspecs);
-    csr->y += (nrows * fheight) + 3;
-    free(specs);
-}
-
-static void draw_hrule(drawcsr* csr) {
-    draw_rect(HorBdr, 0, csr->y + 1, X.width, 1);
-    Divider = csr->y;
-    csr->y += 2;
-}
-
-static void draw_scroll(drawcsr* csr) {
-    View* view = win_view(EDIT);
-    size_t bend = buf_end(win_buf(EDIT));
-    if (bend == 0) bend = 1;
-    if (!view->rows || !view->nrows) return;
-    size_t vbeg = view->rows[0]->off;
-    size_t vend = view->rows[view->nrows-1]->off + view->rows[view->nrows-1]->len;
-    double scroll_vis = (double)(vend - vbeg) / (double)bend;
-    double scroll_off = ((double)vbeg / (double)bend);
-    size_t thumbreg = (csr->h - Divider) + 4;
-    size_t thumboff = (size_t)((thumbreg * scroll_off) + Divider);
-    size_t thumbsz  = (size_t)(thumbreg * scroll_vis);
-    if (thumbsz < 5) thumbsz = 5;
-    draw_rect(VerBdr,   ScrollWidth, Divider + 2,  1,           thumbreg);
-    draw_rect(ScrollBg, 0,           Divider + 2,  ScrollWidth, thumbreg);
-    draw_rect(ScrollFg, 0,           thumboff + 2, ScrollWidth, thumbsz);
-}
-
-/******************************************************************************/
-
-static void xfocus(XConf* x, XEvent* e) {
-    if (x->xic)
-        (e->type == FocusIn ? XSetICFocus : XUnsetICFocus)(x->xic);
-}
-
-static void xkeypress(XConf* x, XEvent* e) {
-    (void)x;
-    Now = e->xkey.time;
-    Focused = (e->xkey.y <= Divider ? TAGS : EDIT);
-    uint32_t key = getkey(x, e);
-    if (key == RUNE_ERR) return;
-    KeyBtnState = e->xkey.state;
-    int mods = KeyBtnState & (ModCtrl|ModShift|ModAlt);
-    int32_t mkey = tolower(key);
-    for (KeyBinding* bind = Keys; bind && bind->key; bind++) {
-        bool match   = (mkey == bind->key);
-        bool exact   = (bind->mods == mods);
-        bool any     = (bind->mods == ModAny);
-        bool oneplus = ((bind->mods == ModOneOrMore) && (mods & ModOneOrMore));
-        if (match && (exact || oneplus || any)) {
-            bind->fn(bind->arg);
-            return;
-        }
-    }
-    /* fallback to just inserting the rune if it doesn't fall in the private use area.
-     * the private use area is used to encode special keys */
-    if (key < 0xE000 || key > 0xF8FF)
-        view_insert(win_view(FOCUSED), key);
-}
-
-static void xbtnpress(XConf* x, XEvent* e) {
-    (void)x;
-    Now = e->xbutton.time;
-    KeyBtnState = (e->xbutton.state | (1 << (e->xbutton.button + 7)));
-    mouse_click(e->xbutton.button, true, e->xbutton.x,  e->xbutton.y);
-}
-
-static void xbtnrelease(XConf* x, XEvent* e) {
-    (void)x;
-    Now = e->xbutton.time;
-    KeyBtnState = (e->xbutton.state & ~(1 << (e->xbutton.button + 7)));
-    mouse_click(e->xbutton.button, false, e->xbutton.x,  e->xbutton.y);
-}
-
-static void xbtnmotion(XConf* x, XEvent* e) {
-    while (XCheckTypedEvent(x->display, MotionNotify, e));
-    Now = e->xbutton.time;
-    size_t row, col;
-    KeyBtnState = e->xbutton.state;
-    int xpos = e->xbutton.x, ypos = e->xbutton.y;
-    get_position(Focused, xpos, ypos, &row, &col);
-    if (PRESSED(MouseLeft))
-        view_setcursor(win_view(Focused), row, col, true);
-}
-
-static void xselclear(XConf* x, XEvent* e) {
-    (void)x;
-    struct XSel* sel = selfetch(e->xselectionclear.selection);
-    if (!sel) return;
-    free(sel->text);
-    sel->text = NULL;
-}
-
-static void xselquit(XConf* x, XEvent* e) {
-    xselclear(x, e);
-    if (!Selections[PRIMARY].text && !Selections[CLIPBOARD].text)
-        x->running = False;
-}
-
-static void xselnotify(XConf* x, XEvent* e) {
-    /* bail if the selection cannot be converted */
-    if (e->xselection.property == None)
-        return;
-    struct XSel* sel = selfetch( e->xselection.selection );
-    Atom rtype;
-    unsigned long format = 0, nitems = 0, nleft = 0;
-    unsigned char* propdata = NULL;
-    XGetWindowProperty(x->display, x->self, sel->atom, 0, -1, False, AnyPropertyType, &rtype,
-                       (int*)&format, &nitems, &nleft, &propdata);
-    if (e->xselection.target == SelTarget) {
-        void(*cbfn)(char*) = sel->callback;
-        sel->callback = NULL;
-        cbfn((char*)propdata);
-    }
-    /* cleanup */
-    if (propdata) XFree(propdata);
-}
-
-static void xselrequest(XConf* x, XEvent* e) {
-    XEvent s;
-    struct XSel* sel = selfetch( e->xselectionrequest.selection );
-    s.xselection.type      = SelectionNotify;
-    s.xselection.property  = e->xselectionrequest.property;
-    s.xselection.requestor = e->xselectionrequest.requestor;
-    s.xselection.selection = e->xselectionrequest.selection;
-    s.xselection.target    = e->xselectionrequest.target;
-    s.xselection.time      = e->xselectionrequest.time;
-    Atom target    = e->xselectionrequest.target;
-    Atom xatargets = XInternAtom(x->display, "TARGETS", 0);
-    Atom xastring  = XInternAtom(x->display, "STRING", 0);
-    if (target == xatargets) {
-        /* respond with the supported type */
-        XChangeProperty(
-            x->display,
-            s.xselection.requestor,
-            s.xselection.property,
-            XA_ATOM, 32, PropModeReplace,
-            (unsigned char*)&SelTarget, 1);
-    } else if (target == SelTarget || target == xastring) {
-        XChangeProperty(
-            x->display,
-            s.xselection.requestor,
-            s.xselection.property,
-            SelTarget, 8, PropModeReplace,
-            (unsigned char*)sel->text, strlen(sel->text));
-    }
-    XSendEvent(x->display, s.xselection.requestor, True, 0, &s);
-}
-
-static void xclientmsg(XConf* x, XEvent* e) {
-    if ((Atom)(e->xclient.data.l[0]) == XInternAtom(x->display, "WM_DELETE_WINDOW", False))
-        win_quit();
-    else if (e->xclient.message_type == XInternAtom(x->display, "GOTO", False))
-        view_setln(win_view(EDIT), e->xclient.data.l[0]);
-}
-
-static void xresize(XConf* x, XEvent* e) {
-    if (e->xconfigure.width != x->width || e->xconfigure.height != x->height) {
-        x->width  = e->xconfigure.width;
-        x->height = e->xconfigure.height;
-        x->pixmap = XCreatePixmap(x->display, x->self, x->width, x->height, x->depth);
-        x->xft    = XftDrawCreate(x->display, x->pixmap, x->visual, x->colormap);
-    }
-}
-
-static void xupdate(Job* job) {
-    int nevents, nqueued;
-    do {
-        nqueued = XEventsQueued(X.display, QueuedAfterFlush);
-        XGetMotionEvents(X.display, X.self, CurrentTime, CurrentTime, &nevents);
-        for (XEvent e; XPending(X.display);) {
-            XNextEvent(X.display, &e);
-            if (!XFilterEvent(&e, None) && X.eventfns[e.type])
-                (X.eventfns[e.type])(&X, &e);
-            for (int status; waitpid(-1, &status, WNOHANG) > 0;);
-        }
-        if (nqueued || !job) {
-            /* force update the title */
-            win_title(NULL);
-            /* determine the size of the regions */
-            size_t maxtagrows = ((X.height/4) / X.tagfont->height);
-            size_t tagrows = view_limitrows(win_view(TAGS), maxtagrows);
-            size_t tagregsz = (tagrows * X.tagfont->height) + 7;
-            size_t editrows = (X.height - tagregsz) / X.font->height ;
-            /* draw the regions to the window */
-            drawcsr csr = { .w = X.width, .h = X.height };
-            csr.x += ScrollWidth + 1;
-            draw_statbox();
-            draw_view(&Regions[TAGS], X.tagfont, tagrows, &csr, TagsBg, TagsFg, TagsSel);
-            draw_hrule(&csr);
-            draw_view(&Regions[EDIT], X.font, editrows, &csr, EditBg, EditFg, EditSel);
-            draw_scroll(&csr);
-            XCopyArea(X.display, X.pixmap, X.self, X.gc, 0, 0, X.width, X.height, 0, 0);
-        }
-    } while ((nqueued = XEventsQueued(X.display, QueuedAfterFlush)) > 0);
-}
-
-/******************************************************************************/
-
-void win_init(KeyBinding* bindings) {
-    Keys = bindings;
-    view_init(&Regions[TAGS], NULL);
-    view_init(&Regions[EDIT], NULL);
+int x11_init(XConf* x) {
     signal(SIGPIPE, SIG_IGN); // Ignore the SIGPIPE signal
     setlocale(LC_CTYPE, "");
     XSetLocaleModifiers("");
-
     /* open the X display and get basic attributes */
-    x11_init(&X);
-    font_load(Fonts[FontSel = 0]);
-    X.tagfont = X.font;
-    if (!X.font) {
-        perror("unable to load base font");
-        exit(EXIT_FAILURE);
-    }
-    x11_mkwin(&X, 640, 480, 0
-        | FocusChangeMask
-        | KeyPressMask
-        | ButtonPressMask
-        | ButtonReleaseMask
-        | ButtonMotionMask
-        | StructureNotifyMask
-        | PropertyChangeMask
-        | ExposureMask
-    );
-    x11_init_gc(&X);
-    x11_centerwin(&X);
-    x11_show(&X);
-
-    /* register event handlers */
-    X.eventfns[FocusIn] = xfocus;
-    X.eventfns[FocusOut] = xfocus;
-    X.eventfns[KeyPress] = xkeypress;
-    X.eventfns[ButtonPress] = xbtnpress;
-    X.eventfns[ButtonRelease] = xbtnrelease;
-    X.eventfns[MotionNotify] = xbtnmotion;
-    X.eventfns[SelectionClear] = xselclear;
-    X.eventfns[SelectionNotify] = xselnotify;
-    X.eventfns[SelectionRequest] = xselrequest;
-    X.eventfns[ClientMessage] = xclientmsg;
-    X.eventfns[ConfigureNotify] = xresize;
-
-    /* initialize selection atoms */
-    for (unsigned 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);
-
-    /* Populate the  tags region */
-    View* view = win_view(TAGS);
-    view_putstr(view, TagString);
-    buf_logclear(&(view->buffer));
-}
-
-void win_title(char* path) {
-    static char prevtitle[4096] = {0};
-    char title[4096] = {0};
-    if (!path) path = win_view(EDIT)->buffer.path;
-    if (!path) path = "*scratch*";
-    snprintf(title, sizeof(title)-1, "[%c%c] %s",
-        (DosLineFeed ? 'C' : 'N'),
-        (ExpandTabs ? 'S' : 'T'),
-        path);
-    if (strcmp(prevtitle, title))
-        XStoreName(X.display, X.self, title);
-    memcpy(prevtitle, title, sizeof(title));
-}
-
-void win_font(char* font) {
-    font_load(font ? font : Fonts[++FontSel % nelem(Fonts)]);
-}
-
-void win_prop_set(char* xname, char* ename, char* value) {
-    if (!value) return;
-    Atom propname = XInternAtom(X.display, xname, 0);
-    XChangeProperty(X.display, X.self, propname, XA_STRING, 8, PropModeReplace,
-        (const unsigned char *)value, strlen(value));
-    if (ename) setenv(ename, value, 1);
-}
-
-void win_update(int ms) {
-    if (job_poll(ms))
-        xupdate(NULL);
-}
-
-void win_loop(void) {
-    X.running = True;
-    XMapWindow(X.display, X.self);
-    tide_send("ADD");
-    job_spawn(ConnectionNumber(X.display), xupdate, 0, 0);
-    while (X.running) {
-        win_update(Timeout);
-    }
-}
-
-void win_quit(void) {
-    static uint64_t before = 0;
-    if ((win_buf(EDIT)->status != MODIFIED) || (Now - before) <= (uint64_t)ClickTime) {
-        tide_send("DEL");
-        X.eventfns  [SelectionClear] = xselquit;
-        XUnmapWindow(X.display, X.self);
-        if (!Selections[PRIMARY].text && !Selections[CLIPBOARD].text) {
-            X.running = False;
-        } else {
-            if (fork()) exit(0); /* fork into background if we still have selection */
-        }
-    }
-    before = Now;
-}
-
-void win_togglefocus(void) {
-    int ypos = (Focused == EDIT ? Divider/2 : (X.height - ((X.height-Divider) / 2)));
-    XWarpPointer(X.display, X.self, X.self, 0, 0, X.width, X.height, X.width/2, ypos);
-}
-
-View* win_view(WinRegion id) {
-    return &(Regions[id == FOCUSED ? Focused : id]);
-}
-
-Buf* win_buf(WinRegion id) {
-    return &(Regions[id == FOCUSED ? Focused : id].buffer);
-}
-
-bool win_keymodsset(int mask) {
-    return ((KeyBtnState & mask) == mask);
-}
-
-bool win_sel_get(int selid, void(*cbfn)(char*)) {
-    struct XSel* sel = &(Selections[selid]);
-    if (sel->callback) return false;
-    Window owner = XGetSelectionOwner(X.display, sel->atom);
-    if (owner == X.self) {
-        cbfn(sel->text);
-    } else if (owner != None){
-        sel->callback = cbfn;
-        XConvertSelection(X.display, sel->atom, SelTarget, sel->atom, X.self, CurrentTime);
-    }
-    return true;
+    if (!(x->display = XOpenDisplay(0)))
+        return -1;
+    x->root = DefaultRootWindow(x->display);
+    XWindowAttributes wa;
+    XGetWindowAttributes(x->display, x->root, &wa);
+    x->visual   = wa.visual;
+    x->colormap = wa.colormap;
+    x->screen   = DefaultScreen(x->display);
+    x->depth    = DefaultDepth(x->display, x->screen);
+    x->running = True;
+    return 0;
+}
+
+void x11_mkwin(XConf* x, int width, int height, int evmask) {
+    /* create the main window */
+    x->width = width, x->height = height;
+    x->self = XCreateSimpleWindow(
+        x->display, x->root, 0, 0, x->width, x->height, 0, x->depth, -1);
+    /* register interest in the delete window message */
+    Atom wmDeleteMessage = XInternAtom(x->display, "WM_DELETE_WINDOW", False);
+    XSetWMProtocols(x->display, x->self, &wmDeleteMessage, 1);
+    /* setup window attributes and events */
+    XSetWindowAttributes swa;
+    swa.backing_store = WhenMapped;
+    swa.bit_gravity = NorthWestGravity;
+    XChangeWindowAttributes(x->display, x->self, CWBackingStore|CWBitGravity, &swa);
+    XSelectInput(x->display, x->self, evmask);
+}
+
+void x11_mkdialog(XConf* x, int width, int height, int evmask) {
+    x11_mkwin(x, width, height, evmask);
+    Atom WindowType = XInternAtom(x->display, "_NET_WM_WINDOW_TYPE", False);
+    Atom DialogType = XInternAtom(x->display, "_NET_WM_WINDOW_TYPE_DIALOG", False);
+    XChangeProperty(x->display, x->self, WindowType, XA_ATOM, 32, PropModeReplace, (unsigned char*)&DialogType, 1);
+}
+
+void x11_event_loop(XConf* x, void (*redraw)(XConf* x)) {
+    if (redraw) redraw(x);
+    for (XEvent e; x->running;) {
+        XNextEvent(x->display, &e);
+        if (x->eventfns[e.type])
+            x->eventfns[e.type](x, &e);
+        for (int status; waitpid(-1, &status, WNOHANG) > 0;);
+        if (redraw) redraw(x);
+    }
+}
+
+int x11_getptr(XConf* x, int* ptrx, int* ptry) {
+    Window root = 0, child = 0;
+    int winx = 0, winy = 0, mask = 0;
+    return XQueryPointer(x->display, x->self, &root, &child, ptrx, ptry, &winx, &winy, (unsigned int*)&mask);
+}
+
+KeySym x11_getkey(XConf* x, XEvent* e) {
+    char buf[8];
+    KeySym key;
+    Status status;
+    /* Read the key string */
+    if (x->xic)
+        Xutf8LookupString(x->xic, &(e->xkey), buf, sizeof(buf), &key, &status);
+    else
+        XLookupString(&(e->xkey), buf, sizeof(buf), &key, 0);
+    return key;
 }
 
-bool win_sel_set(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.self, CurrentTime);
-        return true;
-    }
-}
 
-void win_syncmouse(void) {
-    SyncMouse = true;
-}
diff --git a/src/lib/x11_gc.c b/src/lib/x11_gc.c
new file mode 100644 (file)
index 0000000..b0c6897
--- /dev/null
@@ -0,0 +1,113 @@
+#include <x11.h>
+#include <X11/extensions/Xinerama.h>
+
+static void xfocus(XConf* x, XEvent* e) {
+    if (x->xic)
+        (e->type == FocusIn ? XSetICFocus : XUnsetICFocus)(x->xic);
+}
+
+static void xresize(XConf* x, XEvent* e) {
+    if (e->xconfigure.width != x->width || e->xconfigure.height != x->height) {
+        x->width  = e->xconfigure.width;
+        x->height = e->xconfigure.height;
+        x->pixmap = XCreatePixmap(x->display, x->self, x->width, x->height, x->depth);
+        x->xft    = XftDrawCreate(x->display, x->pixmap, x->visual, x->colormap);
+    }
+}
+
+void x11_init_gc(XConf* x) {
+    /* set input methods */
+    if ((x->xim = XOpenIM(x->display, 0, 0, 0)))
+        x->xic = XCreateIC(x->xim, XNInputStyle, XIMPreeditNothing|XIMStatusNothing, XNClientWindow, x->self, XNFocusWindow, x->self, NULL);
+    /* initialize pixmap and drawing context */
+    x->pixmap = XCreatePixmap(x->display, x->self, x->width, x->height, x->depth);
+    x->xft    = XftDrawCreate(x->display, x->pixmap, x->visual, x->colormap);
+    /* initialize the graphics context */
+    XGCValues gcv;
+    gcv.foreground = WhitePixel(x->display, x->screen);
+    gcv.graphics_exposures = False;
+    x->gc = XCreateGC(x->display, x->self, GCForeground|GCGraphicsExposures, &gcv);
+    x->eventfns[FocusIn] = xfocus;
+    x->eventfns[FocusOut] = xfocus;
+    x->eventfns[ConfigureNotify] = xresize;
+}
+
+void x11_centerwin(XConf* x) {
+    int ptrx = 0, ptry = 0;
+    (void)x11_getptr(x, &ptrx, &ptry);
+    int nscreens = 0;
+    XineramaScreenInfo* p_screens = XineramaQueryScreens(x->display, &nscreens);
+    for (int i = 0; i < nscreens; i++) {
+        int minx = p_screens[i].x_org,
+            maxx = p_screens[i].x_org + p_screens[i].width,
+            miny = p_screens[i].y_org,
+            maxy = p_screens[i].y_org + p_screens[i].height;
+        if (minx <= ptrx && ptrx <= maxx && miny <= ptry && ptry <= maxy) {
+            XMoveWindow(x->display, x->self,
+                minx + p_screens[i].width/2 - x->width/2,
+                miny + p_screens[i].height/2 - x->height/2);
+            break;
+        }
+    }
+}
+
+void x11_show(XConf* x) {
+    /* simulate an initial resize and map the window */
+    XConfigureEvent ce;
+    ce.type   = ConfigureNotify;
+    ce.width  = x->width;
+    ce.height = x->height;
+    XSendEvent(x->display, x->self, False, StructureNotifyMask, (XEvent *)&ce);
+    XMapWindow(x->display, x->self);
+}
+
+XftFont* x11_font_load(XConf* x, char* name) {
+    /* init the library and the base font pattern */
+    if (!FcInit()) return NULL;
+    FcPattern* pattern = FcNameParse((FcChar8 *)name);
+    if (!pattern) return NULL;
+    /* load the base font */
+    FcResult result;
+    FcPattern* match = XftFontMatch(x->display, x->screen, pattern, &result);
+    XftFont* font = NULL;
+    if (match)
+        font = XftFontOpenPattern(x->display, match);
+    FcPatternDestroy(pattern);
+    FcPatternDestroy(match);
+    return font;
+}
+
+void xftcolor(XConf* x, XftColor* xc, unsigned int c) {
+    #define COLOR(c) ((c) | ((c) >> 8))
+    xc->color.alpha = 0xFFFF;
+    xc->color.red   = COLOR((c & 0x00FF0000) >> 8);
+    xc->color.green = COLOR((c & 0x0000FF00));
+    xc->color.blue  = COLOR((c & 0x000000FF) << 8);
+    XftColorAllocValue(x->display, x->visual, x->colormap, &(xc->color), xc);
+}
+
+void x11_draw_rect(XConf* x, int color, int px, int py, int width, int height) {
+    XftColor clr;
+    xftcolor(x, &clr, color);
+    XftDrawRect(x->xft, &clr, px, py, width, height);
+    XftColorFree(x->display, x->visual, x->colormap, &clr);
+}
+
+void x11_draw_glyphs(XConf* x, int color, XftFont* font, XftGlyphSpec* specs, long nspecs) {
+    XftColor clr;
+    xftcolor(x, &clr, color);
+    XftDrawGlyphSpec(x->xft, &clr, font, specs, nspecs);
+    XftColorFree(x->display, x->visual, x->colormap, &clr);
+}
+
+void x11_flip(XConf* x) {
+    XCopyArea(x->display, x->pixmap, x->self, x->gc, 0, 0, x->width, x->height, 0, 0);
+    XFlush(x->display);
+}
+
+void x11_draw_string(XConf* x, XftFont* font, int posx, int posy, int color, char* str) {
+    XftColor clr;
+    xftcolor(x, &clr, color);
+    XftDrawStringUtf8(x->xft, &clr, font, posx, posy, (const FcChar8*)str, strlen(str));
+    XftColorFree(x->display, x->visual, x->colormap, &clr);
+}
diff --git a/src/lib/x11_sel.c b/src/lib/x11_sel.c
new file mode 100644 (file)
index 0000000..a57e9a1
--- /dev/null
@@ -0,0 +1,129 @@
+#include <x11.h>
+
+struct XSel {
+    char* name;
+    Atom atom;
+    char* text;
+    void (*callback)(char*);
+};
+
+static Atom SelTarget;
+static struct XSel Selections[] = {
+    { .name = "PRIMARY" },
+    { .name = "CLIPBOARD" },
+};
+
+static struct XSel* selfetch(Atom atom) {
+    for (unsigned int i = 0; i < (sizeof(Selections) / sizeof(Selections[0])); i++)
+        if (atom == Selections[i].atom)
+            return &Selections[i];
+    return NULL;
+}
+
+static void xselclear(XConf* x, XEvent* e) {
+    (void)x;
+    struct XSel* sel = selfetch(e->xselectionclear.selection);
+    if (!sel) return;
+    free(sel->text);
+    sel->text = NULL;
+}
+
+static void xselnotify(XConf* x, XEvent* e) {
+    /* bail if the selection cannot be converted */
+    if (e->xselection.property == None)
+        return;
+    struct XSel* sel = selfetch( e->xselection.selection );
+    Atom rtype;
+    unsigned long format = 0, nitems = 0, nleft = 0;
+    unsigned char* propdata = NULL;
+    XGetWindowProperty(x->display, x->self, sel->atom, 0, -1, False, AnyPropertyType, &rtype,
+                       (int*)&format, &nitems, &nleft, &propdata);
+    if (e->xselection.target == SelTarget) {
+        void(*cbfn)(char*) = sel->callback;
+        sel->callback = NULL;
+        cbfn((char*)propdata);
+    }
+    /* cleanup */
+    if (propdata) XFree(propdata);
+}
+
+static void xselrequest(XConf* x, XEvent* e) {
+    XEvent s;
+    struct XSel* sel = selfetch( e->xselectionrequest.selection );
+    s.xselection.type      = SelectionNotify;
+    s.xselection.property  = e->xselectionrequest.property;
+    s.xselection.requestor = e->xselectionrequest.requestor;
+    s.xselection.selection = e->xselectionrequest.selection;
+    s.xselection.target    = e->xselectionrequest.target;
+    s.xselection.time      = e->xselectionrequest.time;
+    Atom target    = e->xselectionrequest.target;
+    Atom xatargets = XInternAtom(x->display, "TARGETS", 0);
+    Atom xastring  = XInternAtom(x->display, "STRING", 0);
+    if (target == xatargets) {
+        /* respond with the supported type */
+        XChangeProperty(
+            x->display,
+            s.xselection.requestor,
+            s.xselection.property,
+            XA_ATOM, 32, PropModeReplace,
+            (unsigned char*)&SelTarget, 1);
+    } else if (target == SelTarget || target == xastring) {
+        XChangeProperty(
+            x->display,
+            s.xselection.requestor,
+            s.xselection.property,
+            SelTarget, 8, PropModeReplace,
+            (unsigned char*)sel->text, strlen(sel->text));
+    }
+    XSendEvent(x->display, s.xselection.requestor, True, 0, &s);
+}
+
+void x11_sel_init(XConf* x) {
+    /* initialize selection atoms */
+    for (unsigned 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);
+    /* setup event handlers */
+    x->eventfns[SelectionClear] = xselclear;
+    x->eventfns[SelectionNotify] = xselnotify;
+    x->eventfns[SelectionRequest] = xselrequest;
+}
+
+int x11_sel_get(XConf* x, int selid, void(*cbfn)(char*)) {
+    struct XSel* sel = &(Selections[selid]);
+    if (sel->callback) return 0;
+    Window owner = XGetSelectionOwner(x->display, sel->atom);
+    if (owner == x->self) {
+        cbfn(sel->text);
+    } else if (owner != None){
+        sel->callback = cbfn;
+        XConvertSelection(x->display, sel->atom, SelTarget, sel->atom, x->self, CurrentTime);
+    }
+    return 1;
+}
+
+int x11_sel_set(XConf* x, int selid, char* str) {
+    struct XSel* sel = &(Selections[selid]);
+    if (!sel || !str || !*str) {
+        free(str);
+        return 0;
+    } else {
+        sel->text = str;
+        XSetSelectionOwner(x->display, sel->atom, x->self, CurrentTime);
+        return 1;
+    }
+}
+
+void x11_sel_quit(XConf* x, XEvent* e) {
+    xselclear(x, e);
+    if (!Selections[PRIMARY].text && !Selections[CLIPBOARD].text)
+        x->running = False;
+}
+
+int x11_sel_ready(XConf* x) {
+    (void)x;
+    return (Selections[PRIMARY].text || Selections[CLIPBOARD].text);
+}
+
index acd10e80a667071a3166e449c2cafda5481d23ba..c605e5585e13c928d909491b6d6d3bb001c79853 100644 (file)
@@ -154,15 +154,6 @@ static void xbtnpress(XConf* x, XEvent* e) {
     }
 }
 
-static void xresize(XConf* x, XEvent* e) {
-    if (e->xconfigure.width != x->width || e->xconfigure.height != x->height) {
-        x->width  = e->xconfigure.width;
-        x->height = e->xconfigure.height;
-        x->pixmap = XCreatePixmap(x->display, x->self, x->width, x->height, x->depth);
-        x->xft    = XftDrawCreate(x->display, x->pixmap, x->visual, x->colormap);
-    }
-}
-
 static void redraw(XConf* x) {
     /* draw the background colors and border */
     size_t fheight = x->font->height;
@@ -218,7 +209,6 @@ static void filter(void) {
     x11_show(&x);
     x.eventfns[KeyPress] = xkeypress;
     x.eventfns[ButtonPress] = xbtnpress;
-    x.eventfns[ConfigureNotify] = xresize;
     x11_event_loop(&x, redraw);
 }
 
index 0be9c02683079cbec6850422f3afa53e7b00c5a6..ca99bf2ca66f743696bf3de73fb839b55940f1b7 100644 (file)
@@ -6,6 +6,9 @@
 #include <ctype.h>
 #include <unistd.h>
 
+#define PRIMARY 0
+#define CLIPBOARD 1
+
 #define INCLUDE_DEFS
 #include "config.h"