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__