]> git.mdlowis.com Git - projs/tide.git/commitdiff
remove win.c and move its code to tide.c since it is not actually shared
authorMichael D. Lowis <mike@mdlowis.com>
Wed, 2 Jan 2019 03:06:50 +0000 (22:06 -0500)
committerMichael D. Lowis <mike@mdlowis.com>
Wed, 2 Jan 2019 03:06:50 +0000 (22:06 -0500)
Makefile
TODO.md
src/lib/win.c [deleted file]
src/tide.c

index 1836afaa54fb3a77fcdd3f69f1856c30e60f9b67..324fab15e20eb8606dea4d7af9320615226e91ed 100644 (file)
--- a/Makefile
+++ b/Makefile
@@ -7,7 +7,6 @@ LIBEDIT_OBJS = \
        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
diff --git a/TODO.md b/TODO.md
index c8c7516101b12f5adc0c7e2effd07e0e1a77ebe7..1bcc5be8a8cc3d4d61eece08355b07b004c41fd1 100644 (file)
--- a/TODO.md
+++ b/TODO.md
@@ -20,7 +20,6 @@
 
 ## BACKLOG
 
-* move mouse handlers back into tide.c
 * Add matching logic for "", '', ``, and <>
 * B2+B1 click executes command with selection as argument
 * implement script for toggling between header and source file
diff --git a/src/lib/win.c b/src/lib/win.c
deleted file mode 100644 (file)
index 131fb3d..0000000
+++ /dev/null
@@ -1,486 +0,0 @@
-#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 ca99bf2ca66f743696bf3de73fb839b55940f1b7..47fa00bf234af797dd41796ccc7ffd395ac22c22 100644 (file)
@@ -1,13 +1,19 @@
+//#include <limits.h>
+//#include <locale.h>
+//#include <sys/time.h>
+//#include <sys/types.h>
+//#include <ctype.h>
+
+#define _POSIX_C_SOURCE 200809L
 #define _XOPEN_SOURCE 700
 #include <stdc.h>
 #include <utf.h>
 #include <edit.h>
 #include <win.h>
+#include <x11.h>
 #include <ctype.h>
 #include <unistd.h>
-
-#define PRIMARY 0
-#define CLIPBOARD 1
+#include <locale.h>
 
 #define INCLUDE_DEFS
 #include "config.h"
@@ -19,6 +25,466 @@ typedef struct {
 
 char* ARGV0;
 static Tag Builtins[16];
+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)))
+
+/* X11 Window Code
+ ******************************************************************************/
+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;
+}
 
 /* Tag/Cmd Execution
  ******************************************************************************/