diff --git a/config.def.h b/config.def.h index fa14f40..d8f6d97 100644 --- a/config.def.h +++ b/config.def.h @@ -81,7 +81,9 @@ static const Key keys[] = { { MODKEY, XK_l, setmfact, {.f = +0.05} }, { MODKEY, XK_Return, zoom, {0} }, { MODKEY, XK_Tab, view, {0} }, - { MODKEY|ShiftMask, XK_c, killclient, {0} }, + { MODKEY|ShiftMask, XK_c, hideclient, {0} }, + { MODKEY|ControlMask, XK_c, reopenclient, {0} }, + { MODKEY|ControlMask|ShiftMask, XK_c, killclient, {0} }, { MODKEY, XK_t, setlayout, {.v = &layouts[0]} }, { MODKEY, XK_y, setlayout, {.v = &layouts[1]} }, { MODKEY, XK_m, setlayout, {.v = &layouts[2]} }, diff --git a/dwm-marc.mom b/dwm-marc.mom index 7ff5c02..b5caeb8 100644 --- a/dwm-marc.mom +++ b/dwm-marc.mom @@ -32,9 +32,13 @@ By \f(CWMod\fP, I mean the Super Key (Mod4). .ITEM \f(CWMod+i\fP/\f(CWMod+d\fP \(en Increase/decrease number of masters .ITEM -\f(CWMod+q\fP \(en Close focused window +\f(CWMod+q\fP \(en Close focused window (kept hidden until the next close) .ITEM -\f(CWMod+Shift+q\fP \(en Quit dwm +\f(CWMod+Shift+q\fP \(en Reopen the last closed window, like Ctrl+Shift+t in a browser +.ITEM +\f(CWMod+Ctrl+q\fP \(en Kill focused window for real +.ITEM +\f(CWMod+Shift+r\fP \(en Quit dwm .ITEM \f(CWMod+b\fP \(en Toggle status bar .LIST OFF @@ -176,4 +180,5 @@ YouTube video links open automatically in mpv (via greasemonkey + mpv:// handler .HEADING 1 "Patches Applied" .PP This build includes: gaps, pertag, statuscmd, center, rainbow border, -rainbow bar, preserve-on-restart, and the triple column (tcl) layout. +rainbow bar, preserve-on-restart, reopen (undo close), and the triple +column (tcl) layout. diff --git a/dwm.c b/dwm.c index 3b19265..d010f28 100644 --- a/dwm.c +++ b/dwm.c @@ -187,9 +187,11 @@ static pid_t getstatusbarpid(); static int gettextprop(Window w, Atom atom, char *text, unsigned int size); static void grabbuttons(Client *c, int focused); static void grabkeys(void); +static void hideclient(const Arg *arg); static void incnmaster(const Arg *arg); static void keypress(XEvent *e); static void killclient(const Arg *arg); +static void killwindow(Client *c); static void manage(Window w, XWindowAttributes *wa); static void mappingnotify(XEvent *e); static void maprequest(XEvent *e); @@ -200,6 +202,7 @@ static Client *nexttiled(Client *c); static void pop(Client *c); static void propertynotify(XEvent *e); static void quit(const Arg *arg); +static void reopenclient(const Arg *arg); static void restoresession(void); static Monitor *recttomon(int x, int y, int w, int h); static void resize(Client *c, int x, int y, int w, int h, int interact); @@ -296,6 +299,12 @@ static int rainbow_bar_img_w = 0, rainbow_bar_img_h = 0; static int rainbow_enabled; static int rainbow_bar_enabled; +/* reopen: the last window closed with hideclient() lives on with tags = 0 + * (invisible on every view) until reopenclient() restores its tags or the + * next hideclient() kills it for real */ +static Client *hidden = NULL; +static unsigned int hidden_tags; + /* configuration, allows nested code to access above variables */ #include "config.h" @@ -1228,6 +1237,30 @@ grabkeys(void) } } +void +hideclient(const Arg *arg) +{ + Client *c = selmon->sel; + Client *previous = hidden; + unsigned int previous_tags = hidden_tags; + + if (!c) + return; + hidden = c; + hidden_tags = c->tags; + c->tags = 0; + focus(NULL); + arrange(selmon); + /* limbo holds one window: hiding this one kills the previous one. + * It gets its tags back so a "save changes?" dialog the app may open + * in response lands on a real tag instead of inheriting tags = 0. + * Done after arrange() so the dying window is not tiled in for a frame */ + if (previous) { + previous->tags = previous_tags; + killwindow(previous); + } +} + void incnmaster(const Arg *arg) { @@ -1268,11 +1301,17 @@ killclient(const Arg *arg) { if (!selmon->sel) return; - if (!sendevent(selmon->sel, wmatom[WMDelete])) { + killwindow(selmon->sel); +} + +void +killwindow(Client *c) +{ + if (!sendevent(c, wmatom[WMDelete])) { XGrabServer(dpy); XSetErrorHandler(xerrordummy); XSetCloseDownMode(dpy, DestroyAll); - XKillClient(dpy, selmon->sel->win); + XKillClient(dpy, c->win); XSync(dpy, False); XSetErrorHandler(xerror); XUngrabServer(dpy); @@ -1634,7 +1673,9 @@ quit(const Arg *arg) for (m = mons; m; m = m->next) { i = 0; for (c = m->clients; c; c = c->next, i++) { - long tags = (long)c->tags; + /* save the hidden window with its real tags so it comes back + * after a restart instead of staying invisible forever */ + long tags = (long)(c == hidden ? hidden_tags : c->tags); long mon = (long)m->num; long idx = (long)i; XChangeProperty(dpy, c->win, dwmatom_tags, XA_CARDINAL, 32, @@ -1659,6 +1700,28 @@ quit(const Arg *arg) running = 0; } +void +reopenclient(const Arg *arg) +{ + Client *c = hidden; + + if (!c) + return; + c->tags = hidden_tags; + hidden = NULL; + if (c->mon != selmon) { + unfocus(selmon->sel, 0); + selmon = c->mon; + } + /* bring the window back where it lived; switch the view there if needed */ + if (!ISVISIBLE(c)) { + Arg a = {.ui = c->tags}; + view(&a); + } + focus(c); + arrange(selmon); +} + Monitor * recttomon(int x, int y, int w, int h) { @@ -2292,6 +2355,10 @@ unmanage(Client *c, int destroyed) Monitor *m = c->mon; XWindowChanges wc; + /* the hidden app exited on its own: forget it, or reopenclient() + * would touch freed memory */ + if (c == hidden) + hidden = NULL; detach(c); detachstack(c); if (!destroyed) {