]> git.armaanb.net Git - st.git/blobdiff - x.c
fix: correctly encode mouse buttons >= 8 in X10 and SGR mode
[st.git] / x.c
diff --git a/x.c b/x.c
index 48a66765c182e5a2c6f38821415c4b04b34d8898..8bf998e1dedb7ad592a03b985db2e606f9523ec9 100644 (file)
--- a/x.c
+++ b/x.c
@@ -15,7 +15,7 @@
 #include <X11/Xft/Xft.h>
 #include <X11/XKBlib.h>
 
-static char *argv0;
+char *argv0;
 #include "arg.h"
 #include "st.h"
 #include "win.h"
@@ -93,7 +93,7 @@ typedef struct {
        Window win;
        Drawable buf;
        GlyphFontSpec *specbuf; /* font spec buffer used for rendering */
-       Atom xembed, wmdeletewin, netwmname, netwmpid;
+       Atom xembed, wmdeletewin, netwmname, netwmiconname, netwmpid;
        struct {
                XIM xim;
                XIC xic;
@@ -171,6 +171,7 @@ static void kpress(XEvent *);
 static void cmessage(XEvent *);
 static void resize(XEvent *);
 static void focus(XEvent *);
+static uint buttonmask(uint);
 static int mouseaction(XEvent *, uint);
 static void brelease(XEvent *);
 static void bpress(XEvent *);
@@ -386,7 +387,9 @@ mousereport(XEvent *e)
                        button = 3;
                } else {
                        button -= Button1;
-                       if (button >= 3)
+                       if (button >= 7)
+                               button += 128 - 7;
+                       else if (button >= 3)
                                button += 64 - 3;
                }
                if (e->xbutton.type == ButtonPress) {
@@ -423,16 +426,30 @@ mousereport(XEvent *e)
        ttywrite(buf, len, 0);
 }
 
+uint
+buttonmask(uint button)
+{
+       return button == Button1 ? Button1Mask
+            : button == Button2 ? Button2Mask
+            : button == Button3 ? Button3Mask
+            : button == Button4 ? Button4Mask
+            : button == Button5 ? Button5Mask
+            : 0;
+}
+
 int
 mouseaction(XEvent *e, uint release)
 {
        MouseShortcut *ms;
 
+       /* ignore Button<N>mask for Button<N> - it's set on release */
+       uint state = e->xbutton.state & ~buttonmask(e->xbutton.button);
+
        for (ms = mshortcuts; ms < mshortcuts + LEN(mshortcuts); ms++) {
                if (ms->release == release &&
                    ms->button == e->xbutton.button &&
-                   (match(ms->mod, e->xbutton.state) ||  /* exact or forced */
-                    match(ms->mod, e->xbutton.state & ~forcemousemod))) {
+                   (match(ms->mod, state) ||  /* exact or forced */
+                    match(ms->mod, state & ~forcemousemod))) {
                        ms->func(&(ms->arg));
                        return 1;
                }
@@ -1171,6 +1188,7 @@ xinit(int cols, int rows)
        xw.xembed = XInternAtom(xw.dpy, "_XEMBED", False);
        xw.wmdeletewin = XInternAtom(xw.dpy, "WM_DELETE_WINDOW", False);
        xw.netwmname = XInternAtom(xw.dpy, "_NET_WM_NAME", False);
+       xw.netwmiconname = XInternAtom(xw.dpy, "_NET_WM_ICON_NAME", False);
        XSetWMProtocols(xw.dpy, xw.win, &xw.wmdeletewin, 1);
 
        xw.netwmpid = XInternAtom(xw.dpy, "_NET_WM_PID", False);
@@ -1511,8 +1529,9 @@ 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 7: /* st extension */
+                       g.u = 0x2603; /* snowman (U+2603) */
+                       /* FALLTHROUGH */
                case 0: /* Blinking Block */
                case 1: /* Blinking Block (Default) */
                case 2: /* Steady Block */
@@ -1563,6 +1582,19 @@ xsetenv(void)
        setenv("WINDOWID", buf, 1);
 }
 
+void
+xseticontitle(char *p)
+{
+       XTextProperty prop;
+       DEFAULT(p, opt_title);
+
+       Xutf8TextListToTextProperty(xw.dpy, &p, 1, XUTF8StringStyle,
+                       &prop);
+       XSetWMIconName(xw.dpy, xw.win, &prop);
+       XSetTextProperty(xw.dpy, xw.win, &prop, xw.netwmiconname);
+       XFree(prop.value);
+}
+
 void
 xsettitle(char *p)
 {
@@ -1674,8 +1706,7 @@ xsetmode(int set, unsigned int flags)
 int
 xsetcursor(int cursor)
 {
-       DEFAULT(cursor, 1);
-       if (!BETWEEN(cursor, 0, 6))
+       if (!BETWEEN(cursor, 0, 7)) /* 7: st extension */
                return 1;
        win.cursor = cursor;
        return 0;
@@ -1852,10 +1883,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 {
@@ -1876,82 +1906,77 @@ 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;
+                               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 && 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;
        }
 }
 
@@ -1973,7 +1998,7 @@ main(int argc, char *argv[])
 {
        xw.l = xw.t = 0;
        xw.isfixed = False;
-       win.cursor = cursorshape;
+       xsetcursor(cursorshape);
 
        ARGBEGIN {
        case 'a':