diff --git a/config.def.h b/config.def.h index 3c730da..78e1843 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 }, - { "Onboard", 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,32 +63,27 @@ 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 } } /* commands */ static char dmenumon[2] = "0"; /* component of dmenucmd, manipulated in spawn() */ -static const char *dmenucmd[] = { "rofi", "-show", "drun", "-modi", "drun", "-theme", "themes/launchpad.rasi", NULL }; +static const char *dmenucmd[] = { "launcher", NULL }; //static const char *dmenucmd[] = { "dmenu_run", "-m", dmenumon, "-fn", dmenufont, "-nb", col_gray1, "-nf", col_gray3, "-sb", col_cyan, "-sf", col_gray4, NULL }; static const char *termcmd[] = { "folder-shell", NULL }; -static const char *lfcmd[] = { "folder-shell", "lf", NULL }; - static const char *fullscreenshot[] = { "screenshot", NULL }; static const char *activescreenshot[] = { "screenshot", "window", NULL }; static const char *selectscreenshot[] = { "screenshot", "select", NULL }; -static const char *greenclip[] = { "rofi", "-modi", "clipboard:greenclip print", "-show", "clipboard", "-run-command", "{cmd}", NULL }; +static const char *selectscreenrecord[] = { "screenrecord", "select", NULL }; -static const char *qutebrowser[] = { "qutebrowser", NULL }; -static const char *surf[] = { "tabbed", "-c", "surf", "-e", NULL }; static const char *chromium[] = { "chromium", NULL }; -static const char *bigchromium[] = { "chromium", "--force-device-scale-factor=2", NULL }; static const char *firefox[] = { "firefox-developer-edition", NULL }; static const char *date[] = { "datetime", NULL }; @@ -101,7 +96,6 @@ static const char *playpause[] = { "playerctl", "play-pause", NULL }; static const char *logout[] = { "rofi-shutdown", NULL }; -static const char *offlineArchWiki[] = { "offline-aw", NULL }; static const char *manPages[] = { "man-page-pdf", NULL }; static const char *ports[] = { "ports", NULL }; @@ -119,75 +113,117 @@ static const char *volumeToggle[] = { "volume", "toggle", NULL }; static const char *brightnessUp[] = { "brightness", "up", NULL }; static const char *brightnessDown[] = { "brightness", "down", NULL }; -static const char *setBackgroundRandom[] = { "rofi-background", "--earth", NULL }; -static const char *backgroundDetails[] = { "background", "--only-notify", NULL }; -static const char *unity_hud[] = { "hud-menu.py" }; +static const char *lockScreen[] = { "screenlock", "--suspend", NULL }; +static const char *powerMenu[] = { "rofi-shutdown", NULL }; + +static const char *toggleKeyboard[] = { "toggleKeyboard", NULL }; + +static const char *actOnLast[] = { "actOnLast", NULL }; +static const char *actOnLastDefault[] = { "actOnLast", "--first", NULL }; + +static const char *dunstClose[] = { "dunstctl", "close", NULL }; +static const char *dunstOpenLast[] = { "dunstctl", "history-pop", 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 } }, - //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 } }, - //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_x, spawn, {.v = unity_hud } }, - //Background - { MODKEY, XK_e, spawn, {.v = setBackgroundRandom } }, - { MODKEY|ControlMask, XK_e, 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 } }, + /* modifier key count function argument */ + { MODKEY, XK_Return, 0, spawn, {.v = termcmd } }, + //{ MODKEY|ShiftMask, XK_Return, 0, zoom, {0} }, + + { MODKEY, XK_Tab, 0, toggleAttachBelow, {0} }, + { MODKEY|ShiftMask, XK_Tab, 0, spawn, {.v = dunstOpenLast} }, + + { MODKEY, XK_space, 0, spawn, {.v = dunstClose} }, + + { MODKEY|ShiftMask, XK_a, 0, spawn, {.v = screenlayout } }, + + { MODKEY, XK_b, 0, togglebar, {0} }, + { MODKEY|ShiftMask, XK_b, 0, spawn, {.v = battery} }, + + { MODKEY, XK_c, 0, spawn, {.v = firefox } }, + { MODKEY|ShiftMask, XK_c, 0, spawn, {.v = chromium } }, + + { MODKEY, XK_d, 0, spawn, {.v = pass } }, + { MODKEY|ShiftMask, XK_d, 0, spawn, {.v = date } }, + + { MODKEY, XK_f, 0, setlayout, {.v = &layouts[4]} }, + { MODKEY|ShiftMask, XK_f, 0, setlayout, {.v = &layouts[5]} }, + + { MODKEY, XK_h, 0, setmfact, {.f = -0.05} }, + + { MODKEY, XK_i, 0, incnmaster, {.i = +1 } }, + { MODKEY|ShiftMask, XK_i, 0, incnmaster, {.i = -1 } }, + + { MODKEY, XK_j, 0, focusstack, {.i = +1 } }, + { MODKEY|ShiftMask, XK_j, 0, movestack, {.i = +1 } }, + + { MODKEY, XK_k, 0, focusstack, {.i = -1 } }, + { MODKEY|ShiftMask, XK_k, 0, movestack, {.i = -1 } }, + + { MODKEY, XK_l, 0, setmfact, {.f = +0.05} }, + { MODKEY|ShiftMask, XK_l, 0, spawn, {.v = logout} }, + + { MODKEY, XK_m, 0, setlayout, {.v = &layouts[2]} }, + { MODKEY|ShiftMask, XK_m, 0, spawn, {.v = manPages } }, + + { MODKEY, XK_o, 0, spawn, {.v = actOnLast } }, + { MODKEY|ShiftMask, XK_o, 0, spawn, {.v = actOnLastDefault } }, + + { MODKEY, XK_p, 0, spawn, {.v = dmenucmd } }, + { MODKEY|ShiftMask, XK_p, 0, spawn, {.v = ports } }, + + { MODKEY, XK_q, 0, killclient, {0} }, + + { MODKEY|ShiftMask, XK_r, 0, quit, {0} }, + + { MODKEY, XK_t, 0, setlayout, {.v = &layouts[0]} }, + { MODKEY|ShiftMask, XK_t, 0, setlayout, {.v = &layouts[3]} }, + + { MODKEY, XK_u, 0, spawn, {.v = unicode } }, + + { MODKEY, XK_w, 0, spawn, {.v = whichproject } }, + { MODKEY|ShiftMask, XK_w, 0, spawn, {.v = project } }, + + { MODKEY, XK_y, 0, spawn, {.v = youtube } }, + + { MODKEY, XK_comma, 0, focusmon, {.i = -1 } }, + { MODKEY|ShiftMask, XK_comma, 0, tagmon, {.i = -1 } }, + + { MODKEY, XK_period, 0, focusmon, {.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, XK_F10, 0, spawn, {.v = fullscreenshot } }, + { ControlMask, XK_F10, 0, spawn, {.v = activescreenshot } }, + { ShiftMask, XK_F10, 0, spawn, {.v = selectscreenshot } }, + + { 0, XF86XK_AudioPlay, 0, spawn, {.v = playpause } }, + { 0, XF86XK_AudioPause, 0, spawn, {.v = playpause } }, + { 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 } }, + + { MODKEY, XK_Right, 0, shiftview, {.i = 1 } }, + { MODKEY, XK_Left, 0, shiftview, {.i = -1 } }, + // On the pinephone, I want a single press of the power button to put the phone in sleep mode. + // I want a double press to toggle the keyboard + // I want a hold press to bring up the power menu + // TODO: Make a command for screen lock + { 0, XF86XK_PowerOff, 1, spawn, {.v = lockScreen } }, + { 0, XF86XK_PowerOff, 2, spawn, {.v = toggleKeyboard } }, + { 0, XF86XK_PowerOff, 3, spawn, {.v = powerMenu } }, + TAGKEYS( XK_1, 0) TAGKEYS( XK_2, 1) TAGKEYS( XK_3, 2) @@ -197,7 +233,9 @@ static Key keys[] = { TAGKEYS( XK_7, 6) TAGKEYS( XK_8, 7) TAGKEYS( XK_9, 8) - { MODKEY|ShiftMask, XK_r, quit, {0} }, + + { MODKEY, XK_0, 0, view, {.ui = ~0 } }, + { MODKEY|ShiftMask, XK_0, 0, tag, {.ui = ~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..34a8a60 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); @@ -214,6 +221,7 @@ static void setlayout(const Arg *arg); static void setmfact(const Arg *arg); static void setup(void); static void seturgent(Client *c, int urg); +static void shiftview(const Arg *arg); static void showhide(Client *c); static void sigchld(int unused); static void spawn(const Arg *arg); @@ -273,13 +281,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 +299,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 +617,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 +1088,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 @@ -1779,6 +1874,26 @@ seturgent(Client *c, int urg) XFree(wmh); } +/** Function to shift the current view to the left/right + * + * @param: "arg->i" stores the number of tags to shift right (positive value) + * or left (negative value) + */ +void +shiftview(const Arg *arg) { + Arg shifted; + + if(arg->i > 0) // left circular shift + shifted.ui = (selmon->tagset[selmon->seltags] << arg->i) + | (selmon->tagset[selmon->seltags] >> (LENGTH(tags) - arg->i)); + + else // right circular shift + shifted.ui = selmon->tagset[selmon->seltags] >> (- arg->i) + | selmon->tagset[selmon->seltags] << (LENGTH(tags) + arg->i); + + view(&shifted); +} + void showhide(Client *c) { @@ -2439,6 +2554,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 +2565,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__