Hi all, I've written this patch for focusing and pushing clients around the stack, which is more general than the push patch and also extends the vanilla focus capabilities of dwm.
I'm attaching the documentation and the patch, both of them already pull requested in the sites repo. Regards -- Carlos
diff --git a/config.def.h b/config.def.h index 77ff358..236800d 100644 --- a/config.def.h +++ b/config.def.h @@ -41,6 +41,13 @@ static const Layout layouts[] = { { MODKEY|ControlMask, KEY, toggleview, {.ui = 1 << TAG} }, \ { MODKEY|ShiftMask, KEY, tag, {.ui = 1 << TAG} }, \ { MODKEY|ControlMask|ShiftMask, KEY, toggletag, {.ui = 1 << TAG} }, +#define STACKKEYS(MOD,ACTION) \ + { MOD, XK_j, ACTION##stack, {.i = INC(+1) } }, \ + { MOD, XK_k, ACTION##stack, {.i = INC(-1) } }, \ + { MOD, XK_grave, ACTION##stack, {.i = 0 } }, \ + { MOD, XK_q, ACTION##stack, {.i = 1 } }, \ + { MOD, XK_a, ACTION##stack, {.i = 2 } }, \ + { MOD, XK_z, ACTION##stack, {.i = -1 } }, /* helper for spawning shell commands in the pre dwm-5.0 fashion */ #define SHCMD(cmd) { .v = (const char*[]){ "/bin/sh", "-c", cmd, NULL } } @@ -54,8 +61,8 @@ static Key keys[] = { { MODKEY, XK_p, spawn, {.v = dmenucmd } }, { MODKEY|ShiftMask, XK_Return, spawn, {.v = termcmd } }, { MODKEY, XK_b, togglebar, {0} }, - { MODKEY, XK_j, focusstack, {.i = +1 } }, - { MODKEY, XK_k, focusstack, {.i = -1 } }, + STACKKEYS(MODKEY, focus) + STACKKEYS(MODKEY|ShiftMask, push) { MODKEY, XK_i, incnmaster, {.i = +1 } }, { MODKEY, XK_d, incnmaster, {.i = -1 } }, { MODKEY, XK_h, setmfact, {.f = -0.05} }, @@ -83,7 +90,7 @@ static Key keys[] = { TAGKEYS( XK_7, 6) TAGKEYS( XK_8, 7) TAGKEYS( XK_9, 8) - { MODKEY|ShiftMask, XK_q, quit, {0} }, + { MODKEY|ShiftMask, XK_BackSpace, quit, {0} }, }; /* button definitions */ diff --git a/dwm.c b/dwm.c index 1d78655..8bded5d 100644 --- a/dwm.c +++ b/dwm.c @@ -43,17 +43,22 @@ /* macros */ #define BUTTONMASK (ButtonPressMask|ButtonReleaseMask) #define CLEANMASK(mask) (mask & ~(numlockmask|LockMask) & (ShiftMask|ControlMask|Mod1Mask|Mod2Mask|Mod3Mask|Mod4Mask|Mod5Mask)) +#define GETINC(X) ((X) < 0 ? X + 1000 : X - 1000) +#define INC(X) ((X) < 0 ? X - 1000 : X + 1000) #define INTERSECT(x,y,w,h,m) (MAX(0, MIN((x)+(w),(m)->wx+(m)->ww) - MAX((x),(m)->wx)) \ * MAX(0, MIN((y)+(h),(m)->wy+(m)->wh) - MAX((y),(m)->wy))) +#define ISINC(X) ((X) <= -1000 || (X) >= 1000) #define ISVISIBLE(C) ((C->tags & C->mon->tagset[C->mon->seltags])) #define LENGTH(X) (sizeof X / sizeof X[0]) #define MAX(A, B) ((A) > (B) ? (A) : (B)) #define MIN(A, B) ((A) < (B) ? (A) : (B)) +#define MOD(N,M) ((N)%(M) < 0 ? (N)%(M) + (M) : (N)%(M)) #define MOUSEMASK (BUTTONMASK|PointerMotionMask) #define WIDTH(X) ((X)->w + 2 * (X)->bw) #define HEIGHT(X) ((X)->h + 2 * (X)->bw) #define TAGMASK ((1 << LENGTH(tags)) - 1) #define TEXTW(X) (textnw(X, strlen(X)) + dc.font.height) +#define TRUNC(X,A,B) (MAX((A), MIN((X), (B)))) /* enums */ enum { CurNormal, CurResize, CurMove, CurLast }; /* cursor */ @@ -205,6 +210,7 @@ static void movemouse(const Arg *arg); static Client *nexttiled(Client *c); static void pop(Client *); static void propertynotify(XEvent *e); +static void pushstack(const Arg *arg); static void quit(const Arg *arg); static Monitor *recttomon(int x, int y, int w, int h); static void resize(Client *c, int x, int y, int w, int h, Bool interact); @@ -224,6 +230,7 @@ static void setup(void); static void showhide(Client *c); static void sigchld(int unused); static void spawn(const Arg *arg); +static int stackpos(const Arg *arg); static void tag(const Arg *arg); static void tagmon(const Arg *arg); static int textnw(const char *text, unsigned int len); @@ -887,28 +894,16 @@ focusmon(const Arg *arg) { void focusstack(const Arg *arg) { - Client *c = NULL, *i; + int i = stackpos(arg); + Client *c, *p; - if(!selmon->sel) + if(i < 0) return; - if(arg->i > 0) { - for(c = selmon->sel->next; c && !ISVISIBLE(c); c = c->next); - if(!c) - for(c = selmon->clients; c && !ISVISIBLE(c); c = c->next); - } - else { - for(i = selmon->clients; i != selmon->sel; i = i->next) - if(ISVISIBLE(i)) - c = i; - if(!c) - for(; i; i = i->next) - if(ISVISIBLE(i)) - c = i; - } - if(c) { - focus(c); - restack(selmon); - } + + for(p = NULL, c = selmon->clients; c && (i || !ISVISIBLE(c)); + i -= ISVISIBLE(c) ? 1 : 0, p = c, c = c->next); + focus(c ? c : p); + restack(selmon); } Atom @@ -1324,6 +1319,29 @@ propertynotify(XEvent *e) { } void +pushstack(const Arg *arg) { + int i = stackpos(arg); + Client *sel = selmon->sel, *c, *p; + + if(i < 0) + return; + else if(i == 0) { + detach(sel); + attach(sel); + } + else { + for(p = NULL, c = selmon->clients; c; p = c, c = c->next) + if(!(i -= (ISVISIBLE(c) && c != sel))) + break; + c = c ? c : p; + detach(sel); + sel->next = c->next; + c->next = sel; + } + arrange(selmon); +} + +void quit(const Arg *arg) { running = False; } @@ -1674,6 +1692,29 @@ spawn(const Arg *arg) { } } +int +stackpos(const Arg *arg) { + int n, i; + Client *c; + + if(!selmon->clients) + return -1; + + if(ISINC(arg->i)) { + if(!selmon->sel) + return -1; + for(i = 0, c = selmon->clients; c != selmon->sel; i += ISVISIBLE(c) ? 1 : 0, c = c->next); + for(n = i; c; n += ISVISIBLE(c) ? 1 : 0, c = c->next); + return MOD(i + GETINC(arg->i), n); + } + else if(arg->i < 0) { + for(i = 0, c = selmon->clients; c; i += ISVISIBLE(c) ? 1 : 0, c = c->next); + return MAX(i + arg->i, 0); + } + else + return arg->i; +} + void tag(const Arg *arg) { if(selmon->sel && arg->ui & TAGMASK) {
Stacker ======= Description ----------- This patch provides basic utilities for managing the client stack. It implements two new commands: `focusstack` (which is a replacement for the original `focusstack` command) and `pushstack`. The first one is for focusing clients while the second one moves clients around the stack. Both commands take the same kind of argument: * Pass `INC(inc)` to focus/push relatively to the selected client. This will wrap around the stack limits. * Pass a positive number to focus/push relatively to the beginning of the stack. Out of limit values will be truncated to the position of the last visible client and won't wrap around. * Pass a negative number to focus/push relatively to the last visible client in the stack. Here -1 means the last client, -2 the previous to last client, etc. Out of limit values will be truncated to the position of the first visible client (0) and won't wrap around. Default key bindings -------------------- There are two parallel sets of bindings: one for the `focus*` family and the other for the `push*` family. The keys are the same for both sets but they do differ in the modifiers: simply `MODKEY` for the `focus*` family and `MODKEY|ShiftMask` for the `push*` family. Key | Argument | Description :------:|:-----------:|----------------------- `j` | `INC(+1)` | Next to selected `k` | `INC(-1)` | Previous to selected \` | `0` | First position `q` | `1` | Second position `a` | `2` | Third position `z` | `-1` | Last position The \`, `q`, `a`, `z` keys are aligned more or less in the same keyboard column in the us layout. The first three are intended to be used as quick positional shortcuts to specific applications. So if you have 9 tags you get 9\*3=27 shortcuts in a two-level hierarchy of windows. The `z` key is more like "I don't care so much about you just right now but you can still live in this tag". Notice that `MODKEY|ShiftMask+q` collides with the default binding for quitting dwm, which stacker changes to `MODKEY|ShiftMask+BackSpace`. Download -------- * [dwm-6.0-stacker.diff](dwm-6.0-stacker.diff) Author ------ * Carlos Pita (memeplex) <carlosjosep...@gmail.com>