Hi,

I would like to share this new patch I've written. It's akin to
flextile although there are some differences:

1) The code is much shorter, partly because it's intended to be
applied on top of pertag and partly because it doesn't deal with
nmaster stuff given that this is already included in dwm-6.0.

2) I've deliberately avoided deck like layouts (i.e. monocle in stack
variations). The reasons are given in the Digressions section of the
attached documentation file.

3) I've included additional mfact like controls for the master and
slave areas. This partially overlaps the cfacts and the stackmfact
patches, but it's not equivalent to any of them.

Here is an excerpt of the documentation:

This patch implements a generalization of the tile layout which adds
two attributes (direction and fact) to three areas (global, master,
stack). The global area is the entire allocatable visual space and
it's subdivided into the master and stack subareas.

The direction of the global area controls the position of the master
area relatively to the stack area and it can be one of `DirHor(0)`
(traditional right stack), `DirVer(1)` (bottom stack), `DirRotHor`
(left stack) and `DirRotVer` (top stack). The direction of the master
and of the stack areas are independently set and can be one of
`DirHor(0)` and `DirVer(1)`.  This combines to a total of 4\*2\*2=16
layouts.

The fact numbers indicate the relative size of the first
subarea/client along the direction of the considered area (i.e. width
for `DirHor` and `DirRotHor` and height for `DirVer` and `DirRotVer`).
A fact of 1 means that the first subarea/client is on par the rest,
while a fact of 2 means that its size must double the size of each of
the remaining subareas/clients, etc.  So the fact for the global area
is similar to the traditional mfact in the sense that it manages the
relative allocation of visual space between the master and stack
subareas, while the fact for the master area stands for the relative
importance of the first master client against the rest of masters and,
similarly, the fact for the stack area stands for the importance of
the first slave client in relation to the rest of slaves.

Cheers
--
Carlos
diff --git a/config.def.h b/config.def.h
index 77ff358..ac895db 100644
--- a/config.def.h
+++ b/config.def.h
@@ -23,7 +23,8 @@ static const Rule rules[] = {
 };
 
 /* layout(s) */
-static const float mfact      = 0.55; /* factor of master area size 
[0.05..0.95] */
+static const int dirs[3]      = { DirHor, DirVer, DirVer }; /* tiling dirs */
+static const float facts[3]   = { 1.1,    1.1,    1.1 };    /* tiling facts */
 static const int nmaster      = 1;    /* number of clients in master area */
 static const Bool resizehints = True; /* True means respect size hints in 
tiled resizals */
 
@@ -41,6 +42,10 @@ 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 TILEKEYS(MOD,G,M,S) \
+       { MOD, XK_r, setdirs,  {.v = (int[])  { INC(G * +1),   INC(M * +1),   
INC(S * +1) } } }, \
+       { MOD, XK_h, setfacts, {.v = (float[]){ INC(G * -0.1), INC(M * -0.1), 
INC(S * -0.1) } } }, \
+       { MOD, XK_l, setfacts, {.v = (float[]){ INC(G * +0.1), INC(M * +0.1), 
INC(S * +0.1) } } },
 
 /* helper for spawning shell commands in the pre dwm-5.0 fashion */
 #define SHCMD(cmd) { .v = (const char*[]){ "/bin/sh", "-c", cmd, NULL } }
@@ -58,8 +63,6 @@ static Key keys[] = {
        { MODKEY,                       XK_k,      focusstack,     {.i = -1 } },
        { MODKEY,                       XK_i,      incnmaster,     {.i = +1 } },
        { MODKEY,                       XK_d,      incnmaster,     {.i = -1 } },
-       { MODKEY,                       XK_h,      setmfact,       {.f = -0.05} 
},
-       { MODKEY,                       XK_l,      setmfact,       {.f = +0.05} 
},
        { MODKEY,                       XK_Return, zoom,           {0} },
        { MODKEY,                       XK_Tab,    view,           {0} },
        { MODKEY|ShiftMask,             XK_c,      killclient,     {0} },
@@ -67,6 +70,12 @@ static Key keys[] = {
        { MODKEY,                       XK_f,      setlayout,      {.v = 
&layouts[1]} },
        { MODKEY,                       XK_m,      setlayout,      {.v = 
&layouts[2]} },
        { MODKEY,                       XK_space,  setlayout,      {0} },
+       TILEKEYS(MODKEY,                                           1, 0, 0)
+       TILEKEYS(MODKEY|ShiftMask,                                 0, 1, 0)
+       TILEKEYS(MODKEY|ControlMask,                               0, 0, 1)
+       TILEKEYS(MODKEY|ShiftMask|ControlMask,                     1, 1, 1)
+       { MODKEY|ShiftMask,             XK_t,      setdirs,        {.v = 
(int[]){ DirHor, DirVer, DirVer } } },
+       { MODKEY|ControlMask,           XK_t,      setdirs,        {.v = 
(int[]){ DirVer, DirHor, DirHor } } },
        { MODKEY|ShiftMask,             XK_space,  togglefloating, {0} },
        { MODKEY,                       XK_0,      view,           {.ui = ~0 } 
},
        { MODKEY|ShiftMask,             XK_0,      tag,            {.ui = ~0 } 
},
diff --git a/dwm.c b/dwm.c
index c9ad1f2..5dd2673 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 */
@@ -64,6 +69,7 @@ enum { NetSupported, NetWMName, NetWMState,
 enum { WMProtocols, WMDelete, WMState, WMTakeFocus, WMLast }; /* default atoms 
*/
 enum { ClkTagBar, ClkLtSymbol, ClkStatusText, ClkWinTitle,
        ClkClientWin, ClkRootWin, ClkLast };             /* clicks */
+enum { DirHor, DirVer, DirRotHor, DirRotVer, DirLast }; /* tiling dirs */
 
 typedef union {
        int i;
@@ -73,6 +79,11 @@ typedef union {
 } Arg;
 
 typedef struct {
+       unsigned int x, y, fx, fy, n, dir;
+       float fact;
+} Area;
+
+typedef struct {
        unsigned int click;
        unsigned int mask;
        unsigned int button;
@@ -127,7 +138,6 @@ typedef struct {
 typedef struct Pertag Pertag;
 struct Monitor {
        char ltsymbol[16];
-       float mfact;
        int nmaster;
        int num;
        int by;               /* bar geometry */
@@ -218,10 +228,11 @@ static void scan(void);
 static Bool sendevent(Client *c, Atom proto);
 static void sendmon(Client *c, Monitor *m);
 static void setclientstate(Client *c, long state);
+static void setdirs(const Arg *arg);
+static void setfacts(const Arg *arg);
 static void setfocus(Client *c);
 static void setfullscreen(Client *c, Bool fullscreen);
 static void setlayout(const Arg *arg);
-static void setmfact(const Arg *arg);
 static void setup(void);
 static void showhide(Client *c);
 static void sigchld(int unused);
@@ -292,7 +303,7 @@ static Window root;
 struct Pertag {
        unsigned int curtag, prevtag; /* current and previous tag */
        int nmasters[LENGTH(tags) + 1]; /* number of windows in master area */
-       float mfacts[LENGTH(tags) + 1]; /* mfacts per tag */
+       Area areas[LENGTH(tags) + 1][3]; /* tiling areas */
        unsigned int sellts[LENGTH(tags) + 1]; /* selected layouts */
        const Layout *ltidxs[LENGTH(tags) + 1][2]; /* matrix of tags and 
layouts indexes  */
        Bool showbars[LENGTH(tags) + 1]; /* display bar for the current tag */
@@ -543,6 +554,7 @@ void
 clientmessage(XEvent *e) {
        XClientMessageEvent *cme = &e->xclient;
        Client *c = wintoclient(cme->window);
+       int i;
 
        if(!c)
                return;
@@ -553,8 +565,8 @@ clientmessage(XEvent *e) {
        }
        else if(cme->message_type == netatom[NetActiveWindow]) {
                if(!ISVISIBLE(c)) {
-                       c->mon->seltags ^= 1;
-                       c->mon->tagset[c->mon->seltags] = c->tags;
+                       for(i=0; !(c->tags & 1 << i); i++);
+                       view(&(Arg){.ui = 1 << i});
                }
                pop(c);
        }
@@ -657,12 +669,11 @@ configurerequest(XEvent *e) {
 Monitor *
 createmon(void) {
        Monitor *m;
-       int i;
+       int i, j;
 
        if(!(m = (Monitor *)calloc(1, sizeof(Monitor))))
                die("fatal: could not malloc() %u bytes\n", sizeof(Monitor));
        m->tagset[0] = m->tagset[1] = 1;
-       m->mfact = mfact;
        m->nmaster = nmaster;
        m->showbar = showbar;
        m->topbar = topbar;
@@ -676,8 +687,11 @@ createmon(void) {
                /* init nmaster */
                m->pertag->nmasters[i] = m->nmaster;
 
-               /* init mfacts */
-               m->pertag->mfacts[i] = m->mfact;
+               /* init tiling dirs and facts */
+               for(j = 0; j < 3; j++) {
+                       m->pertag->areas[i][j].dir = MIN(dirs[j], ((int[]){ 3, 
1, 1 }[j]));
+                       m->pertag->areas[i][j].fact = TRUNC(facts[j], 0.1, 10);
+               }
 
                /* init layouts */
                m->pertag->ltidxs[i][0] = m->lt[0];
@@ -1524,6 +1538,31 @@ setclientstate(Client *c, long state) {
                        PropModeReplace, (unsigned char *)data, 2);
 }
 
+void
+setdirs(const Arg *arg) {
+       int *dirs = (int *)arg->v, i, n;
+       Area *areas = selmon->pertag->areas[selmon->pertag->curtag];
+
+       for(i = 0; i < 3; i++) {
+               n = (int[]){ 4, 2, 2 }[i];
+               areas[i].dir = ISINC(dirs[i]) ?
+                       MOD((int)areas[i].dir + GETINC(dirs[i]), n) : 
TRUNC(dirs[i], 0, n - 1);
+       }
+       arrange(selmon);
+}
+
+void
+setfacts(const Arg *arg) {
+       float *facts = (float *)arg->v;
+       Area *areas = selmon->pertag->areas[selmon->pertag->curtag];
+       int i;
+
+       for(i = 0; i < 3; i++)
+               areas[i].fact = TRUNC(ISINC(facts[i]) ?
+                       areas[i].fact + GETINC(facts[i]) : facts[i], 0.1, 10);
+       arrange(selmon);
+}
+
 Bool
 sendevent(Client *c, Atom proto) {
        int n;
@@ -1599,20 +1638,6 @@ setlayout(const Arg *arg) {
                drawbar(selmon);
 }
 
-/* arg > 1.0 will set mfact absolutly */
-void
-setmfact(const Arg *arg) {
-       float f;
-
-       if(!arg || !selmon->lt[selmon->sellt]->arrange)
-               return;
-       f = arg->f < 1.0 ? arg->f + selmon->mfact : arg->f - 1.0;
-       if(f < 0.1 || f > 0.9)
-               return;
-       selmon->mfact = selmon->pertag->mfacts[selmon->pertag->curtag] = f;
-       arrange(selmon);
-}
-
 void
 setup(void) {
        XSetWindowAttributes wa;
@@ -1736,28 +1761,41 @@ textnw(const char *text, unsigned int len) {
 
 void
 tile(Monitor *m) {
-       unsigned int i, n, h, mw, my, ty;
        Client *c;
+       Area *ga = m->pertag->areas[m->pertag->curtag], *ma = ga + 1, *sa = ga 
+ 2, *a;
+       unsigned int n, i, w, h, ms, ss;
+       float f;
 
+       /* print layout symbols */
+       snprintf(m->ltsymbol, sizeof m->ltsymbol, "%c%c%c",
+               (char[]){ '<', '^', '>', 'v' }[ga->dir],
+               (char[]){ '-', '|' }[ma->dir],
+               (char[]){ '-', '|' }[sa->dir]);
+       /* calculate number of clients */
        for(n = 0, c = nexttiled(m->clients); c; c = nexttiled(c->next), n++);
        if(n == 0)
                return;
-
-       if(n > m->nmaster)
-               mw = m->nmaster ? m->ww * m->mfact : 0;
+       ma->n = MIN(n, m->nmaster), sa->n = n - ma->n;
+       /* calculate area rectangles */
+       f = ma->n == 0 ? 0 : (sa->n == 0 ? 1 : ga->fact / 2);
+       if(ga->dir == DirHor || ga->dir == DirRotHor)
+               ms = f * m->ww, ss = m->ww - ms,
+               ma->x = ga->dir == DirHor ? 0 : ss, ma->y = 0, ma->fx = ma->x + 
ms, ma->fy = m->wh,
+               sa->x = ga->dir == DirHor ? ms : 0, sa->y = 0, sa->fx = sa->x + 
ss, sa->fy = m->wh;
        else
-               mw = m->ww;
-       for(i = my = ty = 0, c = nexttiled(m->clients); c; c = 
nexttiled(c->next), i++)
-               if(i < m->nmaster) {
-                       h = (m->wh - my) / (MIN(n, m->nmaster) - i);
-                       resize(c, m->wx, m->wy + my, mw - (2*c->bw), h - 
(2*c->bw), False);
-                       my += HEIGHT(c);
-               }
-               else {
-                       h = (m->wh - ty) / (n - i);
-                       resize(c, m->wx + mw, m->wy + ty, m->ww - mw - 
(2*c->bw), h - (2*c->bw), False);
-                       ty += HEIGHT(c);
-               }
+               ms = f * m->wh, ss = m->wh - ms,
+               ma->x = 0, ma->y = ga->dir == DirVer ? 0 : ss, ma->fx = m->ww, 
ma->fy = ma->y + ms,
+               sa->x = 0, sa->y = ga->dir == DirVer ? ms : 0, sa->fx = m->ww, 
sa->fy = sa->y + ss;
+       /* tile clients */
+       for(c = nexttiled(m->clients), i = 0; i < n; c = nexttiled(c->next), 
i++) {
+               a = ma->n > 0 ? ma : sa;
+               f = i == 0 || ma->n == 0 ? a->fact : 1, f /= --a->n + f;
+               w = (a->dir == DirVer ? 1 : f) * (a->fx - a->x);
+               h = (a->dir == DirHor ? 1 : f) * (a->fy - a->y);
+               resize(c, m->wx + a->x, m->wy + a->y, w - 2 * c->bw, h - 2 * 
c->bw, False);
+               a->x += a->dir == DirHor ? w : 0;
+               a->y += a->dir == DirVer ? h : 0;
+       }
 }
 
 void
@@ -1813,7 +1851,6 @@ toggleview(const Arg *arg) {
 
                /* apply settings for this view */
                selmon->nmaster = 
selmon->pertag->nmasters[selmon->pertag->curtag];
-               selmon->mfact = selmon->pertag->mfacts[selmon->pertag->curtag];
                selmon->sellt = selmon->pertag->sellts[selmon->pertag->curtag];
                selmon->lt[selmon->sellt] = 
selmon->pertag->ltidxs[selmon->pertag->curtag][selmon->sellt];
                selmon->lt[selmon->sellt^1] = 
selmon->pertag->ltidxs[selmon->pertag->curtag][selmon->sellt^1];
@@ -2117,7 +2154,6 @@ view(const Arg *arg) {
                selmon->pertag->curtag = tmptag;
        }
        selmon->nmaster = selmon->pertag->nmasters[selmon->pertag->curtag];
-       selmon->mfact = selmon->pertag->mfacts[selmon->pertag->curtag];
        selmon->sellt = selmon->pertag->sellts[selmon->pertag->curtag];
        selmon->lt[selmon->sellt] = 
selmon->pertag->ltidxs[selmon->pertag->curtag][selmon->sellt];
        selmon->lt[selmon->sellt^1] = 
selmon->pertag->ltidxs[selmon->pertag->curtag][selmon->sellt^1];
Xtile
=====

Description
-----------

This patch implements a generalization of the tile layout which adds two attributes
(direction and fact) to three areas (global, master, stack). The global area is the entire
allocatable visual space and it's subdivided into the master and stack subareas.

The direction of the global area controls the position of the master area relatively to
the stack area and it can be one of `DirHor(0)` (traditional right stack), `DirVer(1)`
(bottom stack), `DirRotHor` (left stack) and `DirRotVer` (top stack). The direction of the
master and of the stack areas are independently set and can be one of `DirHor(0)` and
`DirVer(1)`.  This combines to a total of 4\*2\*2=16 layouts.

The fact numbers indicate the relative size of the first subarea/client along the
direction of the considered area (i.e. width for `DirHor` and `DirRotHor` and height for
`DirVer` and `DirRotVer`). A fact of 1 means that the first subarea/client is on par the
rest, while a fact of 2 means that its size must double the size of each of the remaining
subareas/clients, etc.  So the fact for the global area is similar to the traditional
mfact in the sense that it manages the relative allocation of visual space between the
master and stack subareas, while the fact for the master area stands for the relative
importance of the first master client against the rest of masters and, similarly, the fact
for the stack area stands for the importance of the first slave client in relation to the
rest of slaves.

xtile adds two new commands to dwm: `setdir` and `setfact` (which supersedes `setmfact`).
Both commands take an array of three values (of type `int` for `setdir` and `float` for
`setfact`), one value for each area (the first one for the global area, the second one for
the master area and the third one for the stack area). If you pass the value `v` as
`INC(v)` it will be taken as a relative increment to be added to the current value,
otherwise it will be taken as an absolute value. Usually the resulting value will be
truncated to the valid range of values for each area/attribute combination, but relative
increments for directions wrap around the limits of the valid range.  Notice that INC(0)
means "do nothing here", so it gives you a way to easily modify the value for some area
while leaving the rest untouched.

Default key bindings
--------------------

The areas are selected by modifiers as follows:

  Modifier            |  Area
----------------------|:-------------------------------:
`MODKEY`              |  Global
`MODKEY+Shift`        |  Master
`MODKEY+Control`      |  Stack
`MODKEY+Shift+Control`|  All three areas simultaneously

Each of the modifiers then combines with each of the following keys up to a total of
4\*3=12 key bindings:

  Key   |      Function
:------:|:-----------------------:
 `r`    |  Rotate direction
 `h`    |  Decrement fact by 10%.
 `l`    |  Increment fact by 10%.

There are two provided default "presets" or "schemas" also:

  Modifier        |  Key   |  Preset
------------------|:------:|:-------------:
`MODKEY+Shift`    | `t`    |  Right stack
`MODKEY+Control`  | `t`    |  Bottom stack

These presets allow to quickly switch between different no-nonsense tilings avoiding the
need to rotate through all the nonsense combinations in-between. But notice that
`MODKEY+Shift+Control+r` (i.e. simultaneously rotate all three areas) usually produces
sensible layouts (due to the way directions were designed to rotate).

You can also easily define your own presets by calling `setdir` and `setfact` as needed.
For example, here is the configuration code for the default presets described above:

    { MODKEY|ShiftMask,   XK_t, setdirs, {.v = (int[]){ DirHor, DirVer, DirVer } } },
    { MODKEY|ControlMask, XK_t, setdirs, {.v = (int[]){ DirVer, DirHor, DirHor } } },

Digressions
-----------

### Why facts per area?

There is some arbitrariness in the way facts are defined by xtile: why facts for the first
master and the first slave and not, say, for the first two clients instead?  Considering
that most real life layouts will have one or two masters and a variable number of slaves,
the road xtile took will enable the user to effectively control the relative size of the
three/four most important clients in a very intuitive way that built on his previous
understanding of the mfact and the master and stack area concepts. OTOH it's not clear to
me how to allow the specification of facts for the first two clients in an intuitive way:

* If there is only one master this alternative approach is equivalent to xtile's one.
* If there are two masters, only one fact will be required to specify the share of the
  master area that belongs to each one, so what to do with the second fact?
* If this second fact is taken as the share of the second master vs the share of the
  rest (the slaves), it's not clear how to define these inter-area shares.

### Why not deck area?

One obvious additional generalization would have been to extrapolate the nmaster idea to
all three areas, or at least to the stack area. So if you allowed only m masters and n
slaves you would end up with m+n tiled windows and with the rest of the clients in the
current tagset stacked or decked "below" the last tiled client. flextile, clients-per-tag
and deck patches provide variations on this kind of layout. I've also implemented a
version of xtile that supports it and even subsumes monocle, but I think this promotes a
bad pattern of usage. Coupled with stack manipulation operations as the ones provided by
the stacker or push patches, there is the temptation to manage visibility by moving the
desired clients in the current tag to the first n+m visible positions of the focus stack
(not to be confused with the stack area). There are a number of problems with this
approach:

* The stack is global to dwm, so pushing around clients in one tag will rearrange them in
  other tags also. This could become a problem if you rely too much on explicit stack
  management.

* The deck area badly violates the principle of least surprise. If you only change focus
  sequentially by using `mod-j`/`mod-k` there is no way to exit the deck at a client
  different to the last/first decked one. If you use the mouse or the `focusstack` command
  provided by the stacker patch to jump directly from the deck to a non-decked client,
  each time you reach the deck again by using `mod-j`/`mod-k` the visible decked client
  will be replaced by the first/last decked one. In general, there is a devilish interplay
  of the focus stack and the z-stack that makes the deck unusable as a tabbed view of the
  decked clients, at least for more than one or two decked clients.

Fortunately, dwm provides a much better mechanism to restrict visibility: tags. IMO there
is no need to provide a half-assed alternative to one of dwm's strongest selling points.

Other patches
-------------

Recommended complementary patches:

* [gaps](gaps): to add mostly useless gaps that nevertheless make more apparent which
  client has the focus.

* [stacker](stacker): to better accommodate the clients to the more elaborate layouts
  allowed by xtile. But I would add: subject to the caveats that I've expressed above.

Mandatory dependencies:

* [pertag](pertag): we all know this one.

Related patches: [bottom stack](bottom_stack), [flextile](flextile).

Download
--------

* [dwm-6.0-xtile.diff](dwm-6.0-xtile.diff)

Author
------

* Carlos Pita (memeplex) <carlosjosep...@gmail.com>

Reply via email to