From: Michael D. Lowis Date: Wed, 2 Jan 2019 02:51:38 +0000 (-0500) Subject: refactored x11 code to share more code between gui apps X-Git-Url: https://git.mdlowis.com/?a=commitdiff_plain;h=06366778cbe6af64ee43507611aac4135e4038a5;p=projs%2Ftide.git refactored x11 code to share more code between gui apps --- diff --git a/Makefile b/Makefile index e83abd3..1836afa 100644 --- 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 43cc9b0..c8c7516 100644 --- 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 diff --git a/inc/win.h b/inc/win.h index a939c30..5d555e7 100644 --- 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, diff --git a/inc/x11.h b/inc/x11.h index f416a6e..cd85b1b 100644 --- a/inc/x11.h +++ b/inc/x11.h @@ -1,14 +1,6 @@ #include #include #include -#include -#include -#include -#include -#include - -#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 index 0000000..131fb3d --- /dev/null +++ b/src/lib/win.c @@ -0,0 +1,486 @@ +#define _POSIX_C_SOURCE 200809L +#define _XOPEN_SOURCE 700 +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#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; +} diff --git a/src/lib/x11.c b/src/lib/x11.c index 956f70e..91e0012 100644 --- a/src/lib/x11.c +++ b/src/lib/x11.c @@ -1,612 +1,74 @@ -#define _POSIX_C_SOURCE 200809L -#define _XOPEN_SOURCE 700 -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include #include +#include -#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 index 0000000..b0c6897 --- /dev/null +++ b/src/lib/x11_gc.c @@ -0,0 +1,113 @@ +#include +#include + +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 index 0000000..a57e9a1 --- /dev/null +++ b/src/lib/x11_sel.c @@ -0,0 +1,129 @@ +#include + +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); +} + diff --git a/src/pick.c b/src/pick.c index acd10e8..c605e55 100644 --- a/src/pick.c +++ b/src/pick.c @@ -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); } diff --git a/src/tide.c b/src/tide.c index 0be9c02..ca99bf2 100644 --- a/src/tide.c +++ b/src/tide.c @@ -6,6 +6,9 @@ #include #include +#define PRIMARY 0 +#define CLIPBOARD 1 + #define INCLUDE_DEFS #include "config.h"