From c0a50b817c5e142ca7e6419b88b69da72b0dd46d Mon Sep 17 00:00:00 2001 From: Jonathan Hodgson Date: Mon, 22 Feb 2021 21:53:08 +0000 Subject: [PATCH] Applies the multikey patch This allows me to have the same key do different things based on the number of times it is pushed. This is going to be useful on the pinephone so I can have double click or click and hold on the limited number of physical buttons --- config.def.h | 134 +++++++++++++++++++++++++++------------------------ config.mk | 2 +- dwm.c | 104 +++++++++++++++++++++++++++++++++++++-- 3 files changed, 171 insertions(+), 69 deletions(-) diff --git a/config.def.h b/config.def.h index 4b4ed24..fab6021 100644 --- a/config.def.h +++ b/config.def.h @@ -37,7 +37,7 @@ static const Rule rules[] = { { "xterm-256color", NULL, NULL, 0, 0, 1, 1, 0, 0, -1 }, { "Thunderbird", NULL, NULL, 1 << 8 , 0, 0, 0, 0, 0, -1 }, { NULL, NULL, "noswallow", 0, 0, 0, 1, 0, 0, -1 }, - { "svkbd", NULL, NULL, 0, 1, 0, 1, 0, 1, -1 }, + { "svkbd", NULL, NULL, 0, 1, 0, 1, 0, 1, -1 }, { "Onboard-settings", NULL, NULL, 0, 0, 0, 0, 0, 0, -1 } }; @@ -63,10 +63,10 @@ static const Layout layouts[] = { /* key definitions */ #define MODKEY Mod4Mask #define TAGKEYS(KEY,TAG) \ - { MODKEY, KEY, view, {.ui = 1 << TAG} }, \ - { MODKEY|ControlMask, KEY, toggleview, {.ui = 1 << TAG} }, \ - { MODKEY|ShiftMask, KEY, tag, {.ui = 1 << TAG} }, \ - { MODKEY|ControlMask|ShiftMask, KEY, toggletag, {.ui = 1 << TAG} }, + { MODKEY, KEY, 0, view, {.ui = 1 << TAG} }, \ + { MODKEY|ControlMask, KEY, 0, toggleview, {.ui = 1 << TAG} }, \ + { MODKEY|ShiftMask, KEY, 0, tag, {.ui = 1 << TAG} }, \ + { MODKEY|ControlMask|ShiftMask, KEY, 0, toggletag, {.ui = 1 << TAG} }, /* helper for spawning shell commands in the pre dwm-5.0 fashion */ #define SHCMD(cmd) { .v = (const char*[]){ "/bin/sh", "-c", cmd, NULL } } @@ -125,69 +125,76 @@ static const char *backgroundDetails[] = { "background", "--only-notify", NULL } static const char *powerMenu[] = { "rofi-shutdown", NULL }; #include "movestack.c" + +#define MULTIKEY_THRESHOLD_MS_PRESS 200 +#define MULTIKEY_THRESHOLD_MS_HOLD 700 + + static Key keys[] = { - /* modifier key function argument */ - { MODKEY, XK_p, spawn, {.v = dmenucmd } }, - { MODKEY, XK_Return, spawn, {.v = termcmd } }, - { MODKEY|ControlMask, XK_Return, spawn, {.v = lfcmd } }, - { MODKEY, XK_b, togglebar, {0} }, - { MODKEY, XK_j, focusstack, {.i = +1 } }, - { MODKEY, XK_k, focusstack, {.i = -1 } }, - { MODKEY|ShiftMask, XK_j, movestack, {.i = +1 } }, - { MODKEY|ShiftMask, XK_k, movestack, {.i = -1 } }, - { MODKEY, XK_i, incnmaster, {.i = +1 } }, - { MODKEY|ShiftMask, XK_i, incnmaster, {.i = -1 } }, - { MODKEY, XK_h, setmfact, {.f = -0.05} }, - { MODKEY, XK_l, setmfact, {.f = +0.05} }, - { MODKEY|ShiftMask, XK_l, spawn, {.v = logout} }, - { MODKEY|ShiftMask, XK_Return, zoom, {0} }, - { MODKEY, XK_Tab, toggleAttachBelow, {0} }, - { MODKEY, XK_q, killclient, {0} }, - { MODKEY, XK_t, setlayout, {.v = &layouts[0]} }, - { MODKEY, XK_m, setlayout, {.v = &layouts[2]} }, - { MODKEY|ShiftMask, XK_t, setlayout, {.v = &layouts[3]} }, - { MODKEY, XK_f, setlayout, {.v = &layouts[4]} }, - { MODKEY|ShiftMask, XK_f, setlayout, {.v = &layouts[5]} }, - { MODKEY, XK_space, setlayout, {0} }, - { MODKEY|ShiftMask, XK_space, togglefloating, {0} }, - { MODKEY, XK_0, view, {.ui = ~0 } }, - { MODKEY|ShiftMask, XK_0, tag, {.ui = ~0 } }, - { MODKEY, XK_comma, focusmon, {.i = -1 } }, - { MODKEY, XK_period, focusmon, {.i = +1 } }, - { MODKEY|ShiftMask, XK_comma, tagmon, {.i = -1 } }, - { MODKEY|ShiftMask, XK_period, tagmon, {.i = +1 } }, - { 0, XK_Print, spawn, {.v = fullscreenshot } }, - { ControlMask, XK_Print, spawn, {.v = activescreenshot } }, - { ShiftMask, XK_Print, spawn, {.v = selectscreenshot } }, - { 0, XF86XK_AudioPlay, spawn, {.v = playpause } }, - { MODKEY|ShiftMask, XK_d, spawn, {.v = date } }, - { MODKEY|ShiftMask, XK_b, spawn, {.v = battery} }, - { MODKEY|ShiftMask, XK_Insert, spawn, {.v = greenclip } }, + /* modifier key count function argument */ + { MODKEY, XK_p, 0, spawn, {.v = dmenucmd } }, + { MODKEY, XK_Return, 0, spawn, {.v = termcmd } }, + { MODKEY|ControlMask, XK_Return, 0, spawn, {.v = lfcmd } }, + { MODKEY, XK_b, 0, togglebar, {0} }, + { MODKEY, XK_j, 0, focusstack, {.i = +1 } }, + { MODKEY, XK_k, 0, focusstack, {.i = -1 } }, + { MODKEY|ShiftMask, XK_j, 0, movestack, {.i = +1 } }, + { MODKEY|ShiftMask, XK_k, 0, movestack, {.i = -1 } }, + { MODKEY, XK_i, 0, incnmaster, {.i = +1 } }, + { MODKEY|ShiftMask, XK_i, 0, incnmaster, {.i = -1 } }, + { MODKEY, XK_h, 0, setmfact, {.f = -0.05} }, + { MODKEY, XK_l, 0, setmfact, {.f = +0.05} }, + { MODKEY|ShiftMask, XK_l, 0, spawn, {.v = logout} }, + { MODKEY|ShiftMask, XK_Return, 0, zoom, {0} }, + { MODKEY, XK_Tab, 0, toggleAttachBelow, {0} }, + { MODKEY, XK_q, 0, killclient, {0} }, + { MODKEY, XK_t, 0, setlayout, {.v = &layouts[0]} }, + { MODKEY, XK_m, 0, setlayout, {.v = &layouts[2]} }, + { MODKEY|ShiftMask, XK_t, 0, setlayout, {.v = &layouts[3]} }, + { MODKEY, XK_f, 0, setlayout, {.v = &layouts[4]} }, + { MODKEY|ShiftMask, XK_f, 0, setlayout, {.v = &layouts[5]} }, + { MODKEY, XK_space, 0, setlayout, {0} }, + { MODKEY|ShiftMask, XK_space, 0, togglefloating, {0} }, + { MODKEY, XK_0, 0, view, {.ui = ~0 } }, + { MODKEY|ShiftMask, XK_0, 0, tag, {.ui = ~0 } }, + { MODKEY, XK_comma, 0, focusmon, {.i = -1 } }, + { MODKEY, XK_period, 0, focusmon, {.i = +1 } }, + { MODKEY|ShiftMask, XK_comma, 0, tagmon, {.i = -1 } }, + { MODKEY|ShiftMask, XK_period, 0, tagmon, {.i = +1 } }, + { 0, XK_Print, 0, spawn, {.v = fullscreenshot } }, + { ControlMask, XK_Print, 0, spawn, {.v = activescreenshot } }, + { ShiftMask, XK_Print, 0, spawn, {.v = selectscreenshot } }, + { 0, XF86XK_AudioPlay, 0, spawn, {.v = playpause } }, + { MODKEY|ShiftMask, XK_d, 0, spawn, {.v = date } }, + { MODKEY|ShiftMask, XK_b, 0, spawn, {.v = battery} }, + { MODKEY|ShiftMask, XK_Insert, 0, spawn, {.v = greenclip } }, //Applications - { MODKEY|ShiftMask, XK_q, spawn, {.v = qutebrowser } }, - { MODKEY , XK_s, spawn, {.v = surf } }, - { MODKEY, XK_c, spawn, {.v = firefox } }, - { MODKEY|ShiftMask, XK_c, spawn, {.v = chromium } }, + { MODKEY|ShiftMask, XK_q, 0, spawn, {.v = qutebrowser } }, + { MODKEY, XK_s, 0, spawn, {.v = surf } }, + { MODKEY, XK_c, 0, spawn, {.v = firefox } }, + { MODKEY|ShiftMask, XK_c, 0, spawn, {.v = chromium } }, //Dmenu / Rofi - { MODKEY, XK_u, spawn, {.v = unicode } }, - { MODKEY, XK_y, spawn, {.v = youtube } }, - { MODKEY, XK_a, spawn, {.v = offlineArchWiki } }, - { MODKEY|ShiftMask, XK_a, spawn, {.v = screenlayout } }, - { MODKEY|ShiftMask, XK_m, spawn, {.v = manPages } }, - { MODKEY|ShiftMask, XK_p, spawn, {.v = ports } }, - { MODKEY, XK_w, spawn, {.v = whichproject } }, - { MODKEY|ShiftMask, XK_w, spawn, {.v = project } }, - { MODKEY, XK_d, spawn, {.v = pass } }, + { MODKEY, XK_u, 0, spawn, {.v = unicode } }, + { MODKEY, XK_y, 0, spawn, {.v = youtube } }, + { MODKEY, XK_a, 0, spawn, {.v = offlineArchWiki } }, + { MODKEY|ShiftMask, XK_a, 0, spawn, {.v = screenlayout } }, + { MODKEY|ShiftMask, XK_m, 0, spawn, {.v = manPages } }, + { MODKEY|ShiftMask, XK_p, 0, spawn, {.v = ports } }, + { MODKEY, XK_w, 0, spawn, {.v = whichproject } }, + { MODKEY|ShiftMask, XK_w, 0, spawn, {.v = project } }, + { MODKEY, XK_d, 0, spawn, {.v = pass } }, //Background - { MODKEY, XK_e, spawn, {.v = setBackgroundRandom } }, - { MODKEY|ControlMask, XK_e, spawn, {.v = backgroundDetails } }, + { MODKEY, XK_e, 0, spawn, {.v = setBackgroundRandom } }, + { MODKEY|ControlMask, XK_e, 0, spawn, {.v = backgroundDetails } }, //Special keys - { 0, XF86XK_AudioRaiseVolume, spawn, {.v = volumeUp } }, - { 0, XF86XK_AudioLowerVolume, spawn, {.v = volumeDown } }, - { 0, XF86XK_AudioMute, spawn, {.v = volumeToggle } }, - { 0, XF86XK_MonBrightnessUp, spawn, {.v = brightnessUp } }, - { 0, XF86XK_MonBrightnessDown, spawn, {.v = brightnessDown } }, - { 0, XF86XK_PowerOff, spawn, {.v = powerMenu } }, + { 0, XF86XK_AudioRaiseVolume, 0, spawn, {.v = volumeUp } }, + { 0, XF86XK_AudioLowerVolume, 0, spawn, {.v = volumeDown } }, + { 0, XF86XK_AudioMute, 0, spawn, {.v = volumeToggle } }, + { 0, XF86XK_MonBrightnessUp, 0, spawn, {.v = brightnessUp } }, + { 0, XF86XK_MonBrightnessDown, 0, spawn, {.v = brightnessDown } }, + { 0, XF86XK_PowerOff, 0, spawn, {.v = powerMenu } }, + { MODKEY|ShiftMask, XK_r, 0, quit, {0} }, + TAGKEYS( XK_1, 0) TAGKEYS( XK_2, 1) TAGKEYS( XK_3, 2) @@ -197,7 +204,6 @@ static Key keys[] = { TAGKEYS( XK_7, 6) TAGKEYS( XK_8, 7) TAGKEYS( XK_9, 8) - { MODKEY|ShiftMask, XK_r, quit, {0} }, }; /* button definitions */ diff --git a/config.mk b/config.mk index b77641d..eb52280 100644 --- a/config.mk +++ b/config.mk @@ -22,7 +22,7 @@ FREETYPEINC = /usr/include/freetype2 # includes and libs INCS = -I${X11INC} -I${FREETYPEINC} -LIBS = -L${X11LIB} -lX11 ${XINERAMALIBS} ${FREETYPELIBS} -lX11-xcb -lxcb -lxcb-res +LIBS = -L${X11LIB} -lrt -lX11 ${XINERAMALIBS} ${FREETYPELIBS} -lX11-xcb -lxcb -lxcb-res # flags CPPFLAGS = -D_DEFAULT_SOURCE -D_BSD_SOURCE -D_POSIX_C_SOURCE=200809L -DVERSION=\"${VERSION}\" ${XINERAMAFLAGS} diff --git a/dwm.c b/dwm.c index 14a1be2..cd1419b 100644 --- a/dwm.c +++ b/dwm.c @@ -30,6 +30,7 @@ #include #include #include +#include #include #include #include @@ -40,6 +41,7 @@ #include #endif /* XINERAMA */ #include +#include #include #include @@ -106,6 +108,7 @@ struct Client { typedef struct { unsigned int mod; KeySym keysym; + unsigned int npresses; void (*func)(const Arg *); const Arg arg; } Key; @@ -187,6 +190,10 @@ static void grabbuttons(Client *c, int focused); static void grabkeys(void); static void incnmaster(const Arg *arg); static void keypress(XEvent *e); +static void keypresstimerdispatch(int msduration, int data); +static void keypresstimerdone(union sigval timer_data); +static void keypresstimerdonesync(int idx); +static void keyrelease(XEvent *e); static void killclient(const Arg *arg); static void manage(Window w, XWindowAttributes *wa); static void mappingnotify(XEvent *e); @@ -273,13 +280,14 @@ static void (*handler[LASTEvent]) (XEvent *) = { [Expose] = expose, [FocusIn] = focusin, [KeyPress] = keypress, + [KeyRelease] = keyrelease, [MappingNotify] = mappingnotify, [MapRequest] = maprequest, [MotionNotify] = motionnotify, [PropertyNotify] = propertynotify, [UnmapNotify] = unmapnotify }; -static Atom wmatom[WMLast], netatom[NetLast]; +static Atom timeratom, wmatom[WMLast], netatom[NetLast]; static int running = 1; static Cur *cursor[CurLast]; static Clr **scheme; @@ -290,6 +298,10 @@ static Window root, wmcheckwin; static xcb_connection_t *xcon; +static int multikeypendingindex = -1; +static timer_t multikeypendingtimer = NULL; +static int multikeyup = 1; + /* configuration, allows nested code to access above variables */ #include "config.h" @@ -604,6 +616,11 @@ clientmessage(XEvent *e) XClientMessageEvent *cme = &e->xclient; Client *c = wintoclient(cme->window); + if (cme->message_type == timeratom) { + keypresstimerdonesync(cme->data.s[0]); + return; + } + if (!c) return; if (cme->message_type == netatom[NetWMState]) { @@ -1070,11 +1087,88 @@ keypress(XEvent *e) { ev = &e->xkey; keysym = XKeycodeToKeysym(dpy, (KeyCode)ev->keycode, 0); - for (i = 0; i < LENGTH(keys); i++) + for (i = 0; i < LENGTH(keys); i++){ if (keysym == keys[i].keysym && CLEANMASK(keys[i].mod) == CLEANMASK(ev->state) - && keys[i].func) - keys[i].func(&(keys[i].arg)); + && keys[i].func){ + // E.g. Normal functionality case - npresses 0 == keydown immediate fn + if (keys[i].npresses == 0) { + keys[i].func(&(keys[i].arg)); + break; + } + + // Multikey functionality - find index of key, set global, & dispatch + if ( + (multikeypendingindex == -1 && multikeyup && keys[i].npresses == 1) || + (multikeypendingindex != -1 && keys[multikeypendingindex].npresses + 1 == keys[i].npresses) + ) { + multikeyup = 0; + multikeypendingindex = i; + keypresstimerdispatch(MULTIKEY_THRESHOLD_MS_PRESS, i); + break; + } + } + } +} + +void +keypresstimerdispatch(int msduration, int data) +{ + struct sigevent timer_signal_event; + struct itimerspec timer_period; + // Clear out the old timer if any set,and dispatch new timer + if (multikeypendingtimer != NULL) timer_delete(multikeypendingtimer); + timer_signal_event.sigev_notify = SIGEV_THREAD; + timer_signal_event.sigev_notify_function = keypresstimerdone; + timer_signal_event.sigev_value.sival_int = data; + timer_signal_event.sigev_notify_attributes = NULL; + timer_create(CLOCK_MONOTONIC, &timer_signal_event, &multikeypendingtimer); + timer_period.it_value.tv_sec = 0; + timer_period.it_value.tv_nsec = msduration * 1000000; + timer_period.it_interval.tv_sec = 0; + timer_period.it_interval.tv_nsec = 0; + timer_settime(multikeypendingtimer, 0, &timer_period, NULL); +} + +void +keypresstimerdone(union sigval timer_data) +{ + XEvent ev; + memset(&ev, 0, sizeof ev); + ev.xclient.type = ClientMessage; + ev.xclient.window = root; + ev.xclient.message_type = timeratom; + ev.xclient.format = 16; + ev.xclient.data.s[0] = ((short) timer_data.sival_int); + XSendEvent(dpy, root, False, SubstructureRedirectMask, &ev); + XSync(dpy, False); +} + +void +keypresstimerdonesync(int idx) +{ + int i, maxidx; + if (keys[idx].npresses == 1 && !multikeyup) { + // Dispatch hold key + maxidx = -1; + for (i = 0; i < LENGTH(keys); i++) + if (keys[i].keysym == keys[idx].keysym) maxidx = i; + if (maxidx != -1) + keypresstimerdispatch( + MULTIKEY_THRESHOLD_MS_HOLD - MULTIKEY_THRESHOLD_MS_PRESS, + maxidx + ); + } else if (keys[idx].func) { + // Run the actual keys' fn + keys[idx].func(&(keys[idx].arg)); + multikeypendingindex = -1; + } +} + +void +keyrelease(XEvent *e) +{ + multikeyup = 1; } void @@ -2439,6 +2533,7 @@ zoom(const Arg *arg) int main(int argc, char *argv[]) { + XInitThreads(); if (argc == 2 && !strcmp("-v", argv[1])) die("dwm-"VERSION); else if (argc != 1) @@ -2449,6 +2544,7 @@ main(int argc, char *argv[]) die("dwm: cannot open display"); if (!(xcon = XGetXCBConnection(dpy))) die("dwm: cannot get xcb connection\n"); + XkbSetDetectableAutoRepeat(dpy, True, NULL); checkotherwm(); setup(); #ifdef __OpenBSD__