X-Git-Url: https://git.danieliu.xyz/?a=blobdiff_plain;f=x.c;h=09ee61248d62a54d8170ae952d58346913aedc03;hb=30967a2a66c125cd68ad2c44df11218ccad1105d;hp=4cf6b21ce97109703d09504849973daec2bebe38;hpb=28ad28839985e965c9ca06a9a202523414c84ac4;p=st.git diff --git a/x.c b/x.c index 4cf6b21..09ee612 100644 --- a/x.c +++ b/x.c @@ -14,11 +14,13 @@ #include #include #include +#include -static char *argv0; +char *argv0; #include "arg.h" #include "st.h" #include "win.h" +#include "normalMode.h" /* types used in config.h */ typedef struct { @@ -45,6 +47,19 @@ typedef struct { signed char appcursor; /* application cursor */ } Key; +/* Xresources preferences */ +enum resource_type { + STRING = 0, + INTEGER = 1, + FLOAT = 2 +}; + +typedef struct { + char *name; + enum resource_type type; + void *dst; +} ResourcePref; + /* X modifiers */ #define XK_ANY_MOD UINT_MAX #define XK_NO_MOD 0 @@ -253,6 +268,7 @@ static char *opt_name = NULL; static char *opt_title = NULL; static int oldbutton = 3; /* button event on startup: 3 = release */ +static int cursorblinks = 0; void clipcopy(const Arg *dummy) @@ -261,6 +277,7 @@ clipcopy(const Arg *dummy) free(xsel.clipboard); xsel.clipboard = NULL; + xsetsel(getsel()); if (xsel.primary != NULL) { xsel.clipboard = xstrdup(xsel.primary); @@ -772,6 +789,8 @@ xloadcolor(int i, const char *name, Color *ncolor) return XftColorAllocName(xw.dpy, xw.vis, xw.cmap, name, ncolor); } +void normalMode() { historyModeToggle((win.mode ^=MODE_NORMAL) & MODE_NORMAL); } + void xloadcols(void) { @@ -828,8 +847,8 @@ xclear(int x1, int y1, int x2, int y2) void xhints(void) { - XClassHint class = {opt_name ? opt_name : termname, - opt_class ? opt_class : termname}; + XClassHint class = {opt_name ? opt_name : "st", + opt_class ? opt_class : "St"}; XWMHints wm = {.flags = InputHint, .input = 1}; XSizeHints *sizeh; @@ -1104,8 +1123,6 @@ xinit(int cols, int rows) pid_t thispid = getpid(); XColor xmousefg, xmousebg; - if (!(xw.dpy = XOpenDisplay(NULL))) - die("can't open display\n"); xw.scr = XDefaultScreen(xw.dpy); xw.vis = XDefaultVisual(xw.dpy, xw.scr); @@ -1225,8 +1242,10 @@ xmakeglyphfontspecs(XftGlyphFontSpec *specs, const Glyph *glyphs, int len, int x for (i = 0, xp = winx, yp = winy + font->ascent; i < len; ++i) { /* Fetch rune and mode for current glyph. */ - rune = glyphs[i].u; - mode = glyphs[i].mode; + Glyph g = glyphs[i]; + historyOverlay(x+i, y, &g); + rune = g.u; + mode = g.mode; /* Skip dummy wide-character spacing. */ if (mode == ATTR_WDUMMY) @@ -1526,15 +1545,19 @@ xdrawcursor(int cx, int cy, Glyph g, int ox, int oy, Glyph og) /* draw the new one */ if (IS_SET(MODE_FOCUSED)) { switch (win.cursor) { - case 7: /* st extension: snowman (U+2603) */ - g.u = 0x2603; - case 0: /* Blinking Block */ - case 1: /* Blinking Block (Default) */ - case 2: /* Steady Block */ + case 0: /* Blinking block */ + case 1: /* Blinking block (default) */ + if (IS_SET(MODE_BLINK)) + break; + /* FALLTHROUGH */ + case 2: /* Steady block */ xdrawglyph(g, cx, cy); break; - case 3: /* Blinking Underline */ - case 4: /* Steady Underline */ + case 3: /* Blinking underline */ + if (IS_SET(MODE_BLINK)) + break; + /* FALLTHROUGH */ + case 4: /* Steady underline */ XftDrawRect(xw.draw, &drawcol, borderpx + cx * win.cw, borderpx + (cy + 1) * win.ch - \ @@ -1542,12 +1565,23 @@ xdrawcursor(int cx, int cy, Glyph g, int ox, int oy, Glyph og) win.cw, cursorthickness); break; case 5: /* Blinking bar */ + if (IS_SET(MODE_BLINK)) + break; + /* FALLTHROUGH */ case 6: /* Steady bar */ XftDrawRect(xw.draw, &drawcol, borderpx + cx * win.cw, borderpx + cy * win.ch, cursorthickness, win.ch); break; + case 7: /* Blinking st cursor */ + if (IS_SET(MODE_BLINK)) + break; + /* FALLTHROUGH */ + case 8: /* Steady st cursor */ + g.u = stcursor; + xdrawglyph(g, cx, cy); + break; } } else { XftDrawRect(xw.draw, &drawcol, @@ -1608,6 +1642,7 @@ xdrawline(Line line, int x1, int y1, int x2) i = ox = 0; for (x = x1; x < x2 && i < numspecs; x++) { new = line[x]; + historyOverlay(x, y1, &new); if (new.mode == ATTR_WDUMMY) continue; if (selected(x, y1)) @@ -1689,10 +1724,12 @@ xsetmode(int set, unsigned int flags) int xsetcursor(int cursor) { - DEFAULT(cursor, 1); - if (!BETWEEN(cursor, 0, 6)) + if (!BETWEEN(cursor, 0, 8)) /* 7-8: st extensions */ return 1; win.cursor = cursor; + cursorblinks = win.cursor == 0 || win.cursor == 1 || + win.cursor == 3 || win.cursor == 5 || + win.cursor == 7; return 0; } @@ -1800,6 +1837,11 @@ kpress(XEvent *ev) len = XmbLookupString(xw.ime.xic, e, buf, sizeof buf, &ksym, &status); else len = XLookupString(e, buf, sizeof buf, &ksym, NULL); + if (IS_SET(MODE_NORMAL)) { + if (kpressHist(buf, len, match(ControlMask, e->state), &ksym) + == finished) normalMode(); + return; + } /* 1. shortcuts */ for (bp = shortcuts; bp < shortcuts + LEN(shortcuts); bp++) { if (ksym == bp->keysym && match(bp->mod, e->state)) { @@ -1867,10 +1909,9 @@ run(void) XEvent ev; int w = win.w, h = win.h; fd_set rfd; - int xfd = XConnectionNumber(xw.dpy), xev, blinkset = 0, dodraw = 0; - int ttyfd; - struct timespec drawtimeout, *tv = NULL, now, last, lastblink; - long deltatime; + int xfd = XConnectionNumber(xw.dpy), ttyfd, xev, drawing; + struct timespec seltv, *tv, now, lastblink, trigger; + double timeout; /* Waiting for window mapping */ do { @@ -1891,83 +1932,135 @@ run(void) ttyfd = ttynew(opt_line, shell, opt_io, opt_cmd); cresize(w, h); - clock_gettime(CLOCK_MONOTONIC, &last); - lastblink = last; - - for (xev = actionfps;;) { + for (timeout = -1, drawing = 0, lastblink = (struct timespec){0};;) { FD_ZERO(&rfd); FD_SET(ttyfd, &rfd); FD_SET(xfd, &rfd); + if (XPending(xw.dpy)) + timeout = 0; /* existing events might not set xfd */ + + seltv.tv_sec = timeout / 1E3; + seltv.tv_nsec = 1E6 * (timeout - 1E3 * seltv.tv_sec); + tv = timeout >= 0 ? &seltv : NULL; + if (pselect(MAX(xfd, ttyfd)+1, &rfd, NULL, NULL, tv, NULL) < 0) { if (errno == EINTR) continue; die("select failed: %s\n", strerror(errno)); } - if (FD_ISSET(ttyfd, &rfd)) { - ttyread(); - if (blinktimeout) { - blinkset = tattrset(ATTR_BLINK); - if (!blinkset) - MODBIT(win.mode, 0, MODE_BLINK); - } - } + clock_gettime(CLOCK_MONOTONIC, &now); - if (FD_ISSET(xfd, &rfd)) - xev = actionfps; + if (FD_ISSET(ttyfd, &rfd)) + ttyread(); - clock_gettime(CLOCK_MONOTONIC, &now); - drawtimeout.tv_sec = 0; - drawtimeout.tv_nsec = (1000 * 1E6)/ xfps; - tv = &drawtimeout; - - dodraw = 0; - if (blinktimeout && TIMEDIFF(now, lastblink) > blinktimeout) { - tsetdirtattr(ATTR_BLINK); - win.mode ^= MODE_BLINK; - lastblink = now; - dodraw = 1; - } - deltatime = TIMEDIFF(now, last); - if (deltatime > 1000 / (xev ? xfps : actionfps)) { - dodraw = 1; - last = now; + xev = 0; + while (XPending(xw.dpy)) { + xev = 1; + XNextEvent(xw.dpy, &ev); + if (XFilterEvent(&ev, None)) + continue; + if (handler[ev.type]) + (handler[ev.type])(&ev); } - if (dodraw) { - while (XPending(xw.dpy)) { - XNextEvent(xw.dpy, &ev); - if (XFilterEvent(&ev, None)) - continue; - if (handler[ev.type]) - (handler[ev.type])(&ev); + /* + * To reduce flicker and tearing, when new content or event + * triggers drawing, we first wait a bit to ensure we got + * everything, and if nothing new arrives - we draw. + * We start with trying to wait minlatency ms. If more content + * arrives sooner, we retry with shorter and shorter periods, + * and eventually draw even without idle after maxlatency ms. + * Typically this results in low latency while interacting, + * maximum latency intervals during `cat huge.txt`, and perfect + * sync with periodic updates from animations/key-repeats/etc. + */ + if (FD_ISSET(ttyfd, &rfd) || xev) { + if (!drawing) { + trigger = now; + if (IS_SET(MODE_BLINK)) { + win.mode ^= MODE_BLINK; + } + lastblink = now; + drawing = 1; } + timeout = (maxlatency - TIMEDIFF(now, trigger)) \ + / maxlatency * minlatency; + if (timeout > 0) + continue; /* we have time, try to find idle */ + } - draw(); - XFlush(xw.dpy); - - if (xev && !FD_ISSET(xfd, &rfd)) - xev--; - if (!FD_ISSET(ttyfd, &rfd) && !FD_ISSET(xfd, &rfd)) { - if (blinkset) { - if (TIMEDIFF(now, lastblink) \ - > blinktimeout) { - drawtimeout.tv_nsec = 1000; - } else { - drawtimeout.tv_nsec = (1E6 * \ - (blinktimeout - \ - TIMEDIFF(now, - lastblink))); - } - drawtimeout.tv_sec = \ - drawtimeout.tv_nsec / 1E9; - drawtimeout.tv_nsec %= (long)1E9; - } else { - tv = NULL; - } + /* idle detected or maxlatency exhausted -> draw */ + timeout = -1; + if (blinktimeout && (cursorblinks || tattrset(ATTR_BLINK))) { + timeout = blinktimeout - TIMEDIFF(now, lastblink); + if (timeout <= 0) { + if (-timeout > blinktimeout) /* start visible */ + win.mode |= MODE_BLINK; + win.mode ^= MODE_BLINK; + tsetdirtattr(ATTR_BLINK); + lastblink = now; + timeout = blinktimeout; } } + + draw(); + XFlush(xw.dpy); + drawing = 0; + } +} + +int +resource_load(XrmDatabase db, char *name, enum resource_type rtype, void *dst) +{ + char **sdst = dst; + int *idst = dst; + float *fdst = dst; + + char fullname[256]; + char fullclass[256]; + char *type; + XrmValue ret; + + snprintf(fullname, sizeof(fullname), "%s.%s", + opt_name ? opt_name : "st", name); + snprintf(fullclass, sizeof(fullclass), "%s.%s", + opt_class ? opt_class : "St", name); + fullname[sizeof(fullname) - 1] = fullclass[sizeof(fullclass) - 1] = '\0'; + + XrmGetResource(db, fullname, fullclass, &type, &ret); + if (ret.addr == NULL || strncmp("String", type, 64)) + return 1; + + switch (rtype) { + case STRING: + *sdst = ret.addr; + break; + case INTEGER: + *idst = strtoul(ret.addr, NULL, 10); + break; + case FLOAT: + *fdst = strtof(ret.addr, NULL); + break; } + return 0; +} + +void +config_init(void) +{ + char *resm; + XrmDatabase db; + ResourcePref *p; + + XrmInitialize(); + resm = XResourceManagerString(xw.dpy); + if (!resm) + return; + + db = XrmGetStringDatabase(resm); + for (p = resources; p < resources + LEN(resources); p++) + resource_load(db, p->name, p->type, p->dst); } void @@ -1988,7 +2081,7 @@ main(int argc, char *argv[]) { xw.l = xw.t = 0; xw.isfixed = False; - win.cursor = cursorshape; + xsetcursor(cursorstyle); ARGBEGIN { case 'a': @@ -2043,6 +2136,11 @@ run: setlocale(LC_CTYPE, ""); XSetLocaleModifiers(""); + + if(!(xw.dpy = XOpenDisplay(NULL))) + die("Can't open display\n"); + + config_init(); cols = MAX(cols, 1); rows = MAX(rows, 1); tnew(cols, rows);