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
## 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
+++ /dev/null
-#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;
-}
+//#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"
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
******************************************************************************/