Re: [dev] A general approach to master-slave layouts
On Mon, Nov 7, 2011 at 3:28 AM, Connor Lane Smith c...@lubutu.com wrote: Hey, I've been thinking about this patch for a while, and I've knocked together a patch which takes an alternative approach, which seems to result in a simpler diff. In my patch each layout has three arrangement functions, one for the master, one the slave, and one a 'meta-layout' which defines how the master and slave booths are laid out. So dwm's tile() is achieved with an htile() meta-layout and vtile() master and slave. The patch is +12 lines over dwm tip. Thanks, cls This is nice. I've played around with it, and have had some progress: * Fixed the nmaster == 0 case. The original patch would create an empty master area. * Expoit the mfact 0 possibility. When mfact is set to negative, use the slave area as master area, and vice versa. This makes it possible to put masters on the right in tile, for example. Adjusting setmfact() for this, for now, but we should probably make this more flexible... * Port my original spiral layout to play with this patch. I've attached the patch and the spiral layout as attachments. And a question. What is your plan on dealing with mfact/nmaster? As I've always insisted, they should be both monitor- and layout-specific. What's your opinion, and how would you approach this? Thanks diff -r ee36ffbd4252 config.def.h --- a/config.def.h Sun Nov 06 20:36:23 2011 +0100 +++ b/config.def.h Mon Nov 07 11:03:20 2011 +0800 @@ -29,9 +29,9 @@ static const Layout layouts[] = { /* symbol arrange function */ - { []=, tile },/* first entry is default */ - { , NULL },/* no layout function means floating behavior */ - { [M], monocle }, + { []=, tileh, tilev, tilev }, /* first entry is default */ + { , NULL,NULL,NULL },/* no layout function means floating behavior */ + { [M], monocle, monocle, monocle }, }; /* key definitions */ diff -r ee36ffbd4252 dwm.c --- a/dwm.c Sun Nov 06 20:36:23 2011 +0100 +++ b/dwm.c Mon Nov 07 11:03:20 2011 +0800 @@ -121,7 +121,9 @@ typedef struct { const char *symbol; - void (*arrange)(Monitor *); + void (*arrange)(Client *, float, XRectangle *, XRectangle *); + void (*master)(Client *, float, XRectangle *, XRectangle *); + void (*slave)(Client *, float, XRectangle *, XRectangle *); } Layout; struct Monitor { @@ -199,7 +201,7 @@ static void manage(Window w, XWindowAttributes *wa); static void mappingnotify(XEvent *e); static void maprequest(XEvent *e); -static void monocle(Monitor *m); +static void monocle(Client *c, float fact, XRectangle *r, XRectangle *rp); static void movemouse(const Arg *arg); static Client *nexttiled(Client *c); static void pop(Client *); @@ -226,7 +228,8 @@ static void tag(const Arg *arg); static void tagmon(const Arg *arg); static int textnw(const char *text, unsigned int len); -static void tile(Monitor *); +static void tileh(Client *c, float fact, XRectangle *r, XRectangle *rp); +static void tilev(Client *c, float fact, XRectangle *r, XRectangle *rp); static void togglebar(const Arg *arg); static void togglefloating(const Arg *arg); static void toggletag(const Arg *arg); @@ -403,9 +406,28 @@ void arrangemon(Monitor *m) { + XRectangle r, rm, rt = { m-wx, m-wy, m-ww, m-wh }; + Client *c; + float f; + int i, n; + strncpy(m-ltsymbol, m-lt[m-sellt]-symbol, sizeof m-ltsymbol); - if(m-lt[m-sellt]-arrange) - m-lt[m-sellt]-arrange(m); + if(m-lt[m-sellt]-arrange) { + for(n = 0, c = nexttiled(m-clients); c; c = nexttiled(c-next), n++); + if(n 0) { + if((f = (n m-nmaster) ? (m-nmaster == 0 ? 0 : m-mfact) : 1) 0) { +rm = rt; +m-lt[m-sellt]-arrange(NULL, 1 + f, rm, rt); + } + else +m-lt[m-sellt]-arrange(NULL, f, rt, rm); + for(i = 0, c = nexttiled(m-clients); c; c = nexttiled(c-next), i++) +if(i m-nmaster) + m-lt[m-sellt]-master(c, 1.0 / (MIN(n, m-nmaster) - i), rm, r); +else + m-lt[m-sellt]-slave(c, 1.0 / (n - i), rt, r); + } + } restack(m); } @@ -1188,17 +1210,13 @@ } void -monocle(Monitor *m) { - unsigned int n = 0; - Client *c; - - for(c = m-clients; c; c = c-next) - if(ISVISIBLE(c)) - n++; - if(n 0) /* override layout symbol */ - snprintf(m-ltsymbol, sizeof m-ltsymbol, [%d], n); - for(c = nexttiled(m-clients); c; c = nexttiled(c-next)) - resize(c, m-wx, m-wy, m-ww - 2 * c-bw, m-wh - 2 * c-bw, False); +monocle(Client *c, float fact, XRectangle *r, XRectangle *rp) { + rp-x = r-x; + rp-y = r-y; + rp-width = r-width; + rp-height = r-height; + if(c) + resize(c, r-x, r-y, r-width - (2*c-bw), r-height - (2*c-bw), False); } void @@ -1553,13 +1571,15 @@ void setmfact(const Arg *arg) { float f; + int i; if(!arg || !selmon-lt[selmon-sellt]-arrange) return; - f = arg-f 1.0 ? arg-f + selmon-mfact : arg-f - 1.0; + i = selmon-mfact 0 ? -1 : 1; + f = arg-f 1.0 ? arg-f + selmon-mfact * i : arg-f - 1.0; if(f 0.1 || f 0.9) return; - selmon-mfact = f; +
Re: [dev] A general approach to master-slave layouts
On Mon, Nov 7, 2011 at 11:15 AM, lolilolicon loliloli...@gmail.com wrote: * Port my original spiral layout to play with this patch. I've simplified the spiral layout by rusing tile{h,v}. Nice. void spiral(Client *c, float fact, XRectangle *r, XRectangle *rp) { if(fact == 0) { rp-width = rp-height = 0; return; } static int pos = 1; float f = fact == 1 ? 1 : 0.5; switch(pos) { case 0: /* left */ tileh(c, f, r, rp); pos = 1; break; case 1: /* top */ tilev(c, f, r, rp); pos = 2; break; case 2: /* right */ tileh(c, f, ((XRectangle){ r-x + r-width * (1 - f), r-y, r-width, r-height }), rp); r-width -= rp-width; pos = 3; break; case 3: /* bottom */ tilev(c, f, ((XRectangle){ r-x, r-y + r-height * (1 - f), r-width, r-height }), rp); r-height -= rp-height; pos = 0; break; } if(fact == 1) pos = 1; /* reset to initial value */ }
Re: [dev] Re: [dwm] A general approach to master-slave layouts
I believe every master-slave layout, i.e., layouts where mfact/nmaster make sense, should own its own mfact/nmaster value, not to be disturbed by other layouts. As said before, `col' and `tile' for example just can't share the same mfact and still both look good. Consequently, I decided to update the patch so mfact and nmaster are now monitor- and layout-specific. This should make the master-slave layouts play nice with each other and thus make the idea more complete. Also, the nice thing about this approach is that the config.h interface is left unchanged. mfact and nmaster values of each layout are initialized to the global value mfact and nmaster; the user can also implement layouts that force rules on mfact and nmaster if desired. diff -r 904e923827cb -r 983f8ffd9f7c config.def.h --- a/config.def.h Mon Oct 31 20:09:27 2011 +0100 +++ b/config.def.h Wed Nov 02 20:15:22 2011 +0800 @@ -32,6 +32,8 @@ { []=, tile },/* first entry is default */ { , NULL },/* no layout function means floating behavior */ { [M], monocle }, + { TTT, bstack }, + { |||, col }, }; /* key definitions */ @@ -66,6 +68,8 @@ { MODKEY, XK_t, setlayout, {.v = layouts[0]} }, { MODKEY, XK_f, setlayout, {.v = layouts[1]} }, { MODKEY, XK_m, setlayout, {.v = layouts[2]} }, + { MODKEY, XK_s, setlayout, {.v = layouts[3]} }, + { MODKEY, XK_c, setlayout, {.v = layouts[4]} }, { MODKEY, XK_space, setlayout, {0} }, { MODKEY|ShiftMask, XK_space, togglefloating, {0} }, { MODKEY, XK_0, view, {.ui = ~0 } }, diff -r 904e923827cb -r 983f8ffd9f7c dwm.c --- a/dwm.c Mon Oct 31 20:09:27 2011 +0100 +++ b/dwm.c Wed Nov 02 20:15:22 2011 +0800 @@ -122,26 +122,9 @@ void (*arrange)(Monitor *); } Layout; -struct Monitor { - char ltsymbol[16]; - float mfact; - int nmaster; - int num; - int by; /* bar geometry */ - int mx, my, mw, mh; /* screen size */ - int wx, wy, ww, wh; /* window area */ - unsigned int seltags; - unsigned int sellt; - unsigned int tagset[2]; - Bool showbar; - Bool topbar; - Client *clients; - Client *sel; - Client *stack; - Monitor *next; - Window barwin; - const Layout *lt[2]; -}; +typedef struct { + int x, y, w, h; +} Rect; typedef struct { const char *class; @@ -153,18 +136,23 @@ } Rule; /* function declarations */ +static void apply_mslts(Monitor *m, Bool hsplit, + void (*mltf)(Client **, Rect *, unsigned int), /* master layout function */ + void (*sltf)(Client **, Rect *, unsigned int)); /* slave layout function */ static void applyrules(Client *c); static Bool applysizehints(Client *c, int *x, int *y, int *w, int *h, Bool interact); static void arrange(Monitor *m); static void arrangemon(Monitor *m); static void attach(Client *c); static void attachstack(Client *c); +static void bstack(Monitor *); static void buttonpress(XEvent *e); static void checkotherwm(void); static void cleanup(void); static void cleanupmon(Monitor *mon); static void clearurgent(Client *c); static void clientmessage(XEvent *e); +static void col(Monitor *); static void configure(Client *c); static void configurenotify(XEvent *e); static void configurerequest(XEvent *e); @@ -185,6 +173,7 @@ static void focusmon(const Arg *arg); static void focusstack(const Arg *arg); static unsigned long getcolor(const char *colstr); +static unsigned int getlayoutindex(const Layout *lt); static Bool getrootptr(int *x, int *y); static long getstate(Window w); static Bool gettextprop(Window w, Atom atom, char *text, unsigned int size); @@ -194,6 +183,8 @@ static void initfont(const char *fontstr); static void keypress(XEvent *e); static void killclient(const Arg *arg); +static void lt_hstack(Client **c, Rect *r, unsigned int n); +static void lt_vstack(Client **c, Rect *r, unsigned int n); static void manage(Window w, XWindowAttributes *wa); static void mappingnotify(XEvent *e); static void maprequest(XEvent *e); @@ -281,11 +272,92 @@ /* configuration, allows nested code to access above variables */ #include config.h +struct Monitor { + char ltsymbol[16]; + float mfact[LENGTH(layouts)]; + int nmaster[LENGTH(layouts)]; + int num; + int by; /* bar geometry */ + int mx, my, mw, mh; /* screen size */ + int wx, wy, ww, wh; /* window area */ + unsigned int seltags; + unsigned int sellt; + unsigned int tagset[2]; + Bool showbar; + Bool topbar; + Client *clients; + Client *sel; + Client
Re: [dev] Re: [dwm] A general approach to master-slave layouts
On Tue, Nov 1, 2011 at 6:20 PM, Anselm R Garbe garb...@gmail.com wrote: On 1 November 2011 00:07, lolilolicon loliloli...@gmail.com wrote: Indeed mfact and nmaster being members of Layout does make more sense, and I made a patch which includes this change. Note that this may seem to add some SLOCs, but it actually reduces the amount of code required to implement the same layouts by avoiding code duplication. See how tile, bstack and col are each defined using just a one-liner. By defining two layout algorithms `lt_vstack` and `lt_hstack`, in combination with the hsplit switch, one can define 2 ** 2 * 2 = 8 such layouts, and if you count the (masters|slaves)-only layouts as separate ones, we got 10. Add a third layout algorithm, and you have 3 ** 2 * 2 + 3 = 21. Sure, not all layouts are useful for everyone, but hopefully this will produce some interesting layouts suitable for your particular setup. Thanks for you patch, I looked at it and it is indeed interesting. However it needs further testing and review in order to be a candidate for mainline at some point. Can't agree more. Some remarks: The change of the Layout struct makes it a lot harder to define layouts, as now one also has to understand the variables nmaster/mfact. Also nmaster/mfact are now layout specific variables that might not be used by other layouts. This lacks a bit conceptual clarity imho. I also agree with what you said here, but let me clarify my intention. I really think it more useful to make mfact/nmaster layout-specific, otherwise I wounldn't have made the change to the Layout struct. For example, on my 1280x800 screen, mfact == 0.75 combined with nmaster == 2 in the n?col layout makes a nice layout, but the combination is very bad for the tile layout. As such, sharing mfact/nmaster across layouts isn't exactly nice, nor is it dynamic enough. But now I realize another problem with moving mfact/nmaster to Layout. The issue is two monitors should be able to use different mfact/nmaster values for the same layout; also, the setmfact/incnmaster functions will not update the unselected monitor, but will have their effects all of a sudden next time that monitor is arranged. This makes me want to make nmaster/mfact specific to the monitor *and* the layout. And I also prefer achieving this in the least intrusive way possible. What I'd really prefer is keeping the interface intact we had, a layout is just a function -- I have no objections that this function calls other functions or set up some variables to fit its needs. This would keep it equally simple to the user to define Layouts and leave the interface to be a function, rather than a function + variables. You are absolutely right. Now that I think of it, we can temporarily set m-mfact and/or m-nmaster in a layout function before calling apply_mslts, and restore the values afterwards. For example, define the col layout like this: /* int term_width is the width of a terminal (e.g. 80 characters) */ void col(Monitor *m) { float mfact = m-mfact; int nmaster = m-nmaster; /* masters will be term_width wide */ m-nmaster = MIN(nmaster, m-ww / term_width); m-mfact = (float)term_width * m-nmaster / m-ww; apply_mslts(m, False, lt_hstack, lt_vstack); m-mfact = mfact; m-nmaster = nmaster; } A bit back-and-forth with the mfact calculation (since we will calculate back to the width in apply_mslts), but it's a fair compromise, I guess. Also I'm not absolutely happy about the introduction of the Booth struct, I would rename that into Rect as we have used a similar name in other areas. Having said this, I'm in favor of *not* using XRectangle where possible, in order to keep the core code of dwm X agnostic (which is one 6.0 goal btw). Bah, Booth is cute! Just kidding; I knew it would sound strange and probably have to be renamed. Here we go, Rect it is. Cheers, Anselm
Re: [dev] Re: [dwm] A general approach to master-slave layouts
On Tue, Nov 1, 2011 at 11:38 PM, Anselm R Garbe garb...@gmail.com wrote: On 1 November 2011 16:27, lolilolicon loliloli...@gmail.com wrote: But now I realize another problem with moving mfact/nmaster to Layout. The issue is two monitors should be able to use different mfact/nmaster values for the same layout; also, the setmfact/incnmaster functions will not update the unselected monitor, but will have their effects all of a sudden next time that monitor is arranged. This makes me want to make nmaster/mfact specific to the monitor *and* the layout. And I also prefer achieving this in the least intrusive way possible. Exactly this is the main problem. You could work around it with changing monitor as follows: struct Monitor { char ltsymbol[16]; - float mfact; - int nmaster; + float mfact[LENGTH(layouts)]; + int nmaster[LENGTH(layouts)]; int num; int by; /* bar geometry */ int mx, my, mw, mh; /* screen size */ however this would require some reshuffling of the config.h inclusion and also changes in various places. So I doubt it would be necessary at all. Just stick with nmaster/mfact in Monitor. Hmm, I think the difficulty is to track the index of the current layout in the layouts array. I'm not sure how. I'd like to stick with the current Monitor struct. You are absolutely right. Now that I think of it, we can temporarily set m-mfact and/or m-nmaster in a layout function before calling apply_mslts, and restore the values afterwards. For example, define the col layout like this: /* int term_width is the width of a terminal (e.g. 80 characters) */ void col(Monitor *m) { float mfact = m-mfact; int nmaster = m-nmaster; /* masters will be term_width wide */ m-nmaster = MIN(nmaster, m-ww / term_width); m-mfact = (float)term_width * m-nmaster / m-ww; apply_mslts(m, False, lt_hstack, lt_vstack); m-mfact = mfact; m-nmaster = nmaster; } A bit back-and-forth with the mfact calculation (since we will calculate back to the width in apply_mslts), but it's a fair compromise, I guess. This could work for a patch, but I don't think this is a great way of programming for mainline, since manipulating and restoring nmaster/mfact for the time being is quite ugly. ;) Well, it's a compromise. Would you feel it's less ugly if I define the apply_mslts function like this: tatic void apply_mslts(Monitor *m, Bool hsplit, float mfact, int nmaster, void (*mltf)(Client **, Booth *, unsigned int), void (*sltf)(Client **, Booth *, unsigned int)) { /* uses mfact instead of m-mfact, nmaster intead of m-nmaster */ } and define the layouts like: void col(Monitor *m) { int nmaster = MIN(m-nmaster, m-ww / term_width); float mfact = (float)term_width * nmaster / m-ww; apply_mslts(m, False, mfact, nmaster, lt_hstack, lt_vstack); } void tile(Monitor *m) { apply_mslts(m, False, m-mfact, m-nmaster, lt_vstack, lt_vstack); } Basically the same thing, I guess it's still ugly, but does it feel less ugly? :D Cheers, Anselm
[dev] A general approach to master-slave layouts
The idea of having more than one master windows is brilliant. The `tile' layout in current hg tip basically splits the master and slave areas vertically, and tiles windows in each of the two areas using a vertical stacking algorithm. The `ncol' layout does it slightly differently by tilling the master windows using a horizontal stacking algorithm. The `n?bstack' layout also does it slightly differently, by splitting the master and slave areas horizontally, and tiles windows in each of them using a horizontal stacking algorithm. All of these layouts fit in a generalized model. Basically, there're two things that differentiate one master-slave layout from another: 1. How the master and slave areas are separated. Practically, the most useful way to do this is simply split the screen in two parts, either vertically, or horizontally. 2. What layout is used in each of the master and slave areas. We can use different layouts for the masters and the slaves. We should reuse the layout algorithms when we can. With this model in mind, I came up with the function apply_mslts(), i.e. apply master-slave layouts, which takes care of the two things stated above. The actual layout algorithms are defined in the lt_* functions: lt_hstack tiles windows in a horizontal stack, i.e. columns. lt_vstack tiles windows in a vertical stack, i.e. rows. lt_grid tiles windows in a (gapless) grid. lt_monocle stacks windows, each maximized to fill the booth. The booth is the box in which the layout is performed. Such functions are simpler and easier to read. And they are reusable. To define a master-slave layout is as simple as: static void grid(Monitor *m) { apply_mslts(m, False, lt_vstack, lt_grid); } Here `False` means split master and slave areas vertically; lt_vstack is applied to the master area, and lt_grid to the slaves. I've never seen such a layout before, you may find it interesting too. To test this out, simply grab the hg tip, #include mslts.c in config.h, add some example layouts such as `grid', bind a key combo, `make`, and run. Adjust the nmaster value with Mod + d/i, and mfact with Mod + h/j to see the effects. Also, this approach accepts nmaster being 0, in which case, all windows are slaves, and are thus tiled using the slaves' layout algorithm. This should be the case for the current hg tip, too, IMO. Finally, I'm not really good at C yet, so the code could probably use a whole lot of improvement. I'd really appreciate it if you could review the code and help me improve it, because I really like this idea. The code is in the attachment. typedef struct { int x, y, w, h; } Booth; static void apply_lt(Monitor *m, void (*ltf)(Client **, Booth *, unsigned int)); static void apply_mslts(Monitor *m, Bool hsplit, void (*mltf)(Client **, Booth *, unsigned int), /* master layout func */ void (*sltf)(Client **, Booth *, unsigned int)); /* slave layout func */ static void lt_hstack(Client **c, Booth *b, unsigned int n); static void lt_vstack(Client **c, Booth *b, unsigned int n); static void lt_grid(Client **c, Booth *b, unsigned int n); static void lt_monocle(Client **c, Booth *b, unsigned int n); /* Example master-slave layouts */ static void ntile(Monitor *m); static void ncol(Monitor *m); static void bstack(Monitor *m); static void grid(Monitor *m); static void ntile(Monitor *m) { apply_mslts(m, False, lt_vstack, lt_vstack); } static void ncol(Monitor *m) { apply_mslts(m, False, lt_hstack, lt_vstack); } static void bstack(Monitor *m) { apply_mslts(m, True, lt_hstack, lt_hstack); } static void grid(Monitor *m) { apply_mslts(m, False, lt_vstack, lt_grid); } /* Functions that apply layouts */ static void apply_lt(Monitor *m, void (*ltf)(Client **, Booth *, unsigned int)) { unsigned int n; Client *c; for(n = 0, c = nexttiled(m-clients); c; c = nexttiled(c-next), n++); if(n == 0) return; Booth b = { .x = m-wx, .y = m-wy, .w = m-ww, .h = m-wh }; (*ltf)(c, b, n); } static void apply_mslts(Monitor *m, Bool hsplit, void (*mltf)(Client **, Booth *, unsigned int), void (*sltf)(Client **, Booth *, unsigned int)) { unsigned int nm, n; Client *c; for(n = 0, c = nexttiled(m-clients); c; c = nexttiled(c-next), n++); if(n == 0) return; nm = MIN(n, m-nmaster); /* number of masters */ if (nm == 0) { /* all slaves */ c = m-clients; Booth b = { .x = m-wx, .y = m-wy, .w = m-ww, .h = m-wh }; (*sltf)(c, b, n); } else if(n nm) { /* masters and slaves */ c = m-clients; if(hsplit) { /* masters above slaves */ Booth b = { .x = m-wx, .y = m-wy, .w = m-ww, .h = m-wh * m-mfact }; (*mltf)(c, b, nm); b.y += b.h; b.h = m-wh - b.h; (*sltf)(c, b, n - nm); } else { /* masters at the left of slaves */ Booth b = { .x = m-wx, .y = m-wy, .w = m-ww * m-mfact, .h = m-wh }; (*mltf)(c, b, nm); b.x += b.w; b.w = m-ww - b.w; (*sltf)(c, b, n - nm); } } else { /* all masters */ c = m-clients; Booth b = {
[dev] [dwm] A general approach to master-slave layouts
The idea of having more than one master window is brilliant. The `tile' layout in current hg tip basically splits the master and slave areas vertically, and tiles windows in each of the two areas using a vertical stacking algorithm. The `ncol' layout does it slightly differently by tilling the master windows using a horizontal stacking algorithm. The `n?bstack' layout also does it slightly differently, by splitting the master and slave areas horizontally, and tiles windows in each of them using a horizontal stacking algorithm. All of these layouts fit in a generalized model. Basically, there're two things that differentiate one master-slave layout from another: 1. How the master and slave areas are separated. Practically, the most useful way to do this is simply split the screen in two parts, either vertically, or horizontally. 2. What layout is used in each of the master and slave areas. We can use different layouts for the masters and the slaves. We should reuse the layout algorithms when we can. With this model in mind, I came up with the function apply_mslts(), i.e. apply master-slave layouts, which takes care of the two things stated above. The actual layout algorithms are defined in the lt_* functions: lt_hstack tiles windows in a horizontal stack, i.e. columns. lt_vstack tiles windows in a vertical stack, i.e. rows. lt_grid tiles windows in a (gapless) grid. lt_monocle stacks windows, each maximized to fill the booth. The booth is the box in which the layout is performed. Such functions are simpler and easier to read. And they are reusable. To define a master-slave layout is as simple as: static void grid(Monitor *m) { apply_mslts(m, False, lt_vstack, lt_grid); } Here `False` means split master and slave areas vertically; lt_vstack is applied to the master area, and lt_grid to the slaves. I've never seen such a layout before, you may find it interesting too. To test this out, simply grab the hg tip, #include mslts.c in config.h, add some example layouts such as `grid', bind a key combo, `make`, and run. Adjust the nmaster value with Mod + d/i, and mfact with Mod + h/l to see the effects. Also, this approach accepts nmaster being 0, in which case, all windows are slaves, and are thus tiled using the slaves' layout algorithm. This should be the case for the current hg tip, too, IMO. Finally, I'm not really good at C yet, so the code could probably use a whole lot of improvement. I'd really appreciate it if you could review the code and help me improve it, because I really like this idea. The code is in the attachment. /* Resend this due to missing [dwm] tag in the subject, and an error in the code. Sorry for the inconvenience. */
Re: [dev] Re: [dwm] A general approach to master-slave layouts
On Mon, Oct 31, 2011 at 10:19 PM, Thomas Dahms thmsd...@googlemail.com wrote: That looks interesting. I have one suggestion for a simplification: I guess you can get rid of the functions combining the master and slave layouts by modifying setlayout() to take three arguments (the two layouts and the direction of master/slave splitting). This way you could combine any two layouts in the key binding section of config.h. I don't think we can do this with the current Arg. I also don't see the benefit of this over defining the layouts. If the user wants to combine two layout algorithms, he can always define a layout in as few as three lines in config.h. Please elaborate if I misunderstand. Also, the code is so far in no way in conflict with the dwm code. This general approach is also more flexible. For example, the example `ntile' layout can replace `tile' in dwm.c, and `monocle' in dwm.c can be defined via `apply_lt` using `lt_monocle`. Actually, `apply_lt` can be removed, since `apply_mslts` does the same thing when nmaster == 0. I'm thinking of making nmaster a member of the Layout structure, instead of the current Monitor. This way, we can make all windows slaves in the `grid' layout, while having two masters when we switch to `tile'. The same applies to mfact- the `ncol' layout certainly needs a bigger mfact than `tile', for example. Finally, neither nmaster nor mfact makes sense in non-master-slave layouts (if you still use those, that is ;). Oh, and it would be nice if mfact increased when nmaster increased in the `ncol' layout, too. Maybe another function as a member of Layout, which would be executed to set mfact as a hook whenever nmaster changes? Since the current tile, monocle, and proposed bstack layouts are each a special case of the mslts approach, and mslts is more powerful (and I do think is simpler and easier to read), and if you care, mslts will likely decrease the SLOC due to removal of duplicated code, I think mslts could be accepted into mainline. What do you think? -- Thomas Dahms
Re: [dev] Re: [dwm] A general approach to master-slave layouts
On Tue, Nov 1, 2011 at 12:11 AM, lolilolicon loliloli...@gmail.com wrote: Actually, `apply_lt` can be removed, since `apply_mslts` does the same thing when nmaster == 0. I'm thinking of making nmaster a member of the Layout structure, instead of the current Monitor. This way, we can make all windows slaves in the `grid' layout, while having two masters when we switch to `tile'. The same applies to mfact- the `ncol' layout certainly needs a bigger mfact than `tile', for example. Finally, neither nmaster nor mfact makes sense in non-master-slave layouts (if you still use those, that is ;). Oh, and it would be nice if mfact increased when nmaster increased in the `ncol' layout, too. Maybe another function as a member of Layout, which would be executed to set mfact as a hook whenever nmaster changes? Indeed mfact and nmaster being members of Layout does make more sense, and I made a patch which includes this change. Since I don't know much about hg, I just did a `hg export`, and attach the produced patch as attachment instead of inline. The commit message should describe what has been done, and the code should explain itself. Note that this may seem to add some SLOCs, but it actually reduces the amount of code required to implement the same layouts by avoiding code duplication. See how tile, bstack and col are each defined using just a one-liner. By defining two layout algorithms `lt_vstack` and `lt_hstack`, in combination with the hsplit switch, one can define 2 ** 2 * 2 = 8 such layouts, and if you count the (masters|slaves)-only layouts as separate ones, we got 10. Add a third layout algorithm, and you have 3 ** 2 * 2 + 3 = 21. Sure, not all layouts are useful for everyone, but hopefully this will produce some interesting layouts suitable for your particular setup. # HG changeset patch # User lolilolicon loliloli...@gmail.com # Date 1320098001 -28800 # Node ID f35ce5cc96363a813f91e64f6eda30504052eeed # Parent 904e923827cb010abb7a31298264548946616d92 A general approach to master-slave layouts This makes the actual code implementing layout algorithms reusable. Apply two separate layout algorithms to the master area and the slave area. The master and slave areas are separated by either vertical or horizontal split. The `tile' algorithm is split out as `lt_vstack` and the `tile' function is now just a one-liner. Due to the reusability, the `bstack' and `col' layouts are added with few lines of code. Other interesting layout combinations can be easily added in the same manner. Move mfact and nmaster to the Layout structure, so that each layout can have its own mfact and nmaster. This makes sense when using several master-slave layouts, e.g., the `col' layout will usually use a mfact larger than `tile'. diff --git a/config.def.h b/config.def.h --- a/config.def.h +++ b/config.def.h @@ -23,15 +23,15 @@ }; /* layout(s) */ -static const float mfact = 0.55; /* factor of master area size [0.05..0.95] */ -static const int nmaster = 1;/* number of clients in master area */ static const Bool resizehints = True; /* True means respect size hints in tiled resizals */ -static const Layout layouts[] = { - /* symbol arrange function */ - { []=, tile },/* first entry is default */ - { , NULL },/* no layout function means floating behavior */ - { [M], monocle }, +static Layout layouts[] = { + /* symbol arrange function mfactnmaster */ + { []=, tile,0.55,1 }, /* first entry is default */ + { , NULL,0.5, 0 }, /* no layout function means floating behavior */ + { [M], monocle, 0.5, 0 }, + { TTT, bstack, 0.55,1 }, + { |||, col, 0.75,2 }, }; /* key definitions */ @@ -66,6 +66,8 @@ { MODKEY, XK_t, setlayout, {.v = layouts[0]} }, { MODKEY, XK_f, setlayout, {.v = layouts[1]} }, { MODKEY, XK_m, setlayout, {.v = layouts[2]} }, + { MODKEY, XK_s, setlayout, {.v = layouts[3]} }, + { MODKEY, XK_c, setlayout, {.v = layouts[4]} }, { MODKEY, XK_space, setlayout, {0} }, { MODKEY|ShiftMask, XK_space, togglefloating, {0} }, { MODKEY, XK_0, view, {.ui = ~0 } }, diff --git a/dwm.c b/dwm.c --- a/dwm.c +++ b/dwm.c @@ -71,6 +71,10 @@ } Arg; typedef struct { + int x, y, w, h; +} Booth; + +typedef struct { unsigned int click; unsigned int mask; unsigned int button; @@ -120,12 +124,12 @@ typedef struct { const char *symbol; void (*arrange)(Monitor *); + float mfact; + int nmaster; } Layout; struct Monitor { char ltsymbol[16]; - float mfact; - int nmaster; int num; int by; /* bar geometry */ int mx, my, mw, mh; /* screen size */ @@ -140,7 +144,7
Re: [dev] Re: [dwm] A general approach to master-slave layouts
On Tue, Nov 1, 2011 at 7:36 AM, Rob robpill...@gmail.com wrote: I don't have much time today, or possibly tomorrow, but I'm interested in this patch, it sounds almost like it recurses on each sub-section of the total area, applying a different layout function each time, except it's limited to two calls, one for the master area and one for the slave. It's only one patch, which I sent in my last mail, so not much really :) The core is the `apply_mslts' funtion. It walks all clients like any tiling layout would do, e.g., the `tile' function defined in the patch simply calls `apply_mslts(m, False, lt_vstack, lt_vstack)`, and it should do the layout exactly the same way the original `tile' would do, no extra wasted work. The code should be clear, but maybe note that the mltf/sltf functions modify `c'. Either way, I'm hoping to try out your patch(es) at some point this week, and hoping to mess around with the key bindings, I assume you can change the master layout while keeping the slave one the same with a binding, right? Not really. DWM knows only about a single layout per monitor, even if we set the masters to temporarily use a different layout algorithm, but not update the currently selected layout name, DWM will rearrange the masters back to the selected layout whenever arrangemon() is called, which happens probably before you realize it ;). That said, you can always define two layouts which differ only in the master layout, (e.g. `tile' and `col'). Or if we feel progressive, let's make DWM aware of the master layout as well as the slave layout, although I don't see practical need so far. Similarly, I also considered to enable toggling master/slave splitting direction, effectively rotating the layout, or even allow flipping, but then thought it not useful in practice... Cheers, Rob
Re: [dev] A general approach to master-slave layouts
On Tue, Nov 1, 2011 at 8:41 AM, Connor Lane Smith c...@lubutu.com wrote: Hey, On 31/10/2011, lolilolicon loliloli...@gmail.com wrote: The idea of having more than one master windows is brilliant. The `tile' layout in current hg tip basically splits the master and slave areas vertically, and tiles windows in each of the two areas using a vertical stacking algorithm. I'll be interested to see where this goes. The code is still a bit raw at the moment, but I like the idea. I wonder how simple we can get this patch... Indeed, I post it here for all your suckless energy to do my money laundry ;) cls
Re: [dev] Ranger, a textbased filemanager
On Mon, Oct 24, 2011 at 6:01 AM, Bjartur Thorlacius svartma...@gmail.com wrote: if the task is 'waste a bunch of screen real estate' then no, ranger is a far better choice The current combination of a shell and a terminal emulator is horrible. I've found myself doing stuff like: exec /dev/tty1 /dev/tty2 2/dev/tty1 #optionally stty -echo while read do clear $REPLY done Why in Mímir's name do error messages and output get written to where my input is echoed? This could to be an interesting idea. I'm thinking 3 column terminal (stdin, stdout, stderr), as well as remote control...
Re: [dev] [dmenu] dmenu_run improvements
On 07/24/11 at 01:34am, Dave Reisner wrote: #!/bin/sh CACHE=${XDG_CACHE_HOME:-$HOME/.cache}/dmenu_run IFS=: LC_COLLLATE=C You got a typo here: LC_COLLATE gencache() { lsx $PATH | sort -u $CACHE } Since LC_COLLATE is only used for sort, we might as well just use: LC_COLLATE=C sort if [ ! -e $CACHE ]; then mkdir -p ${CACHE%/*} gencache fi for path in $PATH; do if [ $path -nt $CACHE ]; then gencache break fi done Much better than `ls -t', congratulations. If $CACHE does not exist, there's no need to compare timestamps, so let's puts an `else' there. unset IFS cmd=$(dmenu $@ $CACHE) eval exec $cmd Yeah, I see you just hate backticks :P
Re: [dev] [dmenu] dmenu_run improvements
On 07/24/11 at 11:38am, anonymous wrote: On Sun, Jul 24, 2011 at 02:34:22PM +0800, lolilolicon wrote: unset IFS cmd=$(dmenu $@ $CACHE) eval exec $cmd Yeah, I see you just hate backticks :P There is a difference: % echo `echo '\\'` \ % echo $(echo '\\') \\ You're right. The backquoted form is broken even with no nesting. Now I'm officially a member of the anti-backticks club. Thank you.
Re: [dev] [dmenu] dmenu_run improvements
On Sun, Jul 24, 2011 at 4:40 PM, Anselm R Garbe garb...@gmail.com wrote: On 24 July 2011 08:38, anonymous p37si...@lavabit.com wrote: On Sun, Jul 24, 2011 at 02:34:22PM +0800, lolilolicon wrote: unset IFS cmd=$(dmenu $@ $CACHE) eval exec $cmd Yeah, I see you just hate backticks :P There is a difference: % echo `echo '\\'` \ % echo $(echo '\\') \\ Yes, but bash'isms are a NO GO in suckless.org shell scripts :) Cheers, Anselm This is no bashism. AFAIK, $(command) is in the POSIX shell specification: http://pubs.opengroup.org/onlinepubs/009695399/utilities/xcu_chap02.html#tag_02_06_03
Re: [dev] [dmenu] dmenu_run improvements
On Sun, Jul 24, 2011 at 4:39 PM, Anselm R Garbe garb...@gmail.com wrote: On 24 July 2011 06:34, Dave Reisner d...@falconindy.com wrote: On Sun, Jul 24, 2011 at 12:35:19PM +0800, lolilolicon wrote: dmenu_run doesn't really run the user input as a shell command line. For instance, run dmenu_run from a terminal, then in the menu type: echo hello\ world The terminal output is hello\ world instead of hello world. `eval' solves the problem, but when it comes to eval, I can't really be sure, so please point out the possible errors/risks. Assuredly the least of the evils in this script -- the eval is necessary. More noteworthy is the parsing of ls here, which can easily be refactored out and still be /bin/sh compat. It also explodes on first run, and then there's the needless invocation of mkdir every time the cache is regenerated. suckless could suck a lot less at shell scripting. dmenu_run has gone through several iterations of bad and worse. d #!/bin/sh CACHE=${XDG_CACHE_HOME:-$HOME/.cache}/dmenu_run IFS=: LC_COLLLATE=C gencache() { lsx $PATH | sort -u $CACHE } if [ ! -e $CACHE ]; then mkdir -p ${CACHE%/*} gencache fi for path in $PATH; do if [ $path -nt $CACHE ]; then gencache break fi done Am I right that the previous loop runs gencache n times, under the assumption that each path component contains newer files? Kind regards, Anselm No, note the `break'.
Re: [dev] [dmenu] dmenu_run improvements
On Sun, Jul 24, 2011 at 5:06 PM, Anselm R Garbe garb...@gmail.com wrote: Nevertheless, I can see that script should be improved (not using the ls -dt invocation), but I prefer the back ticks (even if $(..) is POSIX, there were a couple of issues in the past that I barely remember) and I want explicit test calls, I don't like the [ ... ] syntax sugar. Thanks, Anselm Fine, as long as it doesn't break anything, I can live with these two backticks. Since I'm the thread starter, may I conclude this with the following? #!/bin/sh CACHE=${XDG_CACHE_HOME:-$HOME/.cache}/dmenu_run ( IFS=: gen_cache() { lsx $PATH | LC_COLLATE=C sort -u $CACHE } if test -e $CACHE; then for path in $PATH; do if test $path -nt $CACHE; then gen_cache break fi done else mkdir -p ${CACHE%/*} gen_cache fi ) cmd=`dmenu $@ $CACHE` eval exec $cmd NOTE: The (subshell) is used so the declarations won't get inherented, but the user still has access to the $CACHE and $cmd environments.
Re: [dev] [dmenu] dmenu_run improvements
On Sun, Jul 24, 2011 at 6:41 PM, lolilolicon loliloli...@gmail.com wrote: cmd=`dmenu $@ $CACHE` eval exec $cmd NOTE: The (subshell) is used so the declarations won't get inherented, but the user still has access to the $CACHE and $cmd environments. Hmm, maybe instead of `eval exec', we could just use `exec sh -c' instead? cmd=`dmenu $@ $CACHE` || exit $? exec sh -c $cmd The benefits are: 1. Only exported environment variables will be available to the user command. 2. The user can run any shell command, including, e.g., if true; then echo hello\ world; fi Correct me if I'm missing something here. Thanks.
Re: [dev] [dmenu] dmenu_run improvements
On Sun, Jul 24, 2011 at 9:09 PM, Connor Lane Smith c...@lubutu.com wrote: On 24 July 2011 06:34, Dave Reisner d...@falconindy.com wrote: if [ $path -nt $CACHE ]; then 'test -nt' is non-portable. I think you've just discovered why we use the 'ls -dt' hack. I agree that dmenu_run isn't the nicest script in existence. But because of the tedious limitations of POSIX we don't have much choice. Sorry, but can you give an example where `test -nt' is not available? Or can you point out what do you refer to to determine the portability of a shell script? According to Greg's wiki [1], `test -nt' is not supported by dash, although it works here with dash 0.5.6.1. Even if you must support the older versions that does not know about `test -nt', you can use find: test `find $path -prune -newer $CACHE` as a drop-in replacement, which is better than `ls -dt' after all. [1] http://mywiki.wooledge.org/Bashism
Re: [dev] Re: [dmenu] dmenu_run improvements
On Sun, Jul 24, 2011 at 10:08 PM, Christian Neukirchen chneukirc...@gmail.com wrote: anonymous p37si...@lavabit.com writes: On Sun, Jul 24, 2011 at 02:34:22PM +0800, lolilolicon wrote: unset IFS cmd=$(dmenu $@ $CACHE) eval exec $cmd Yeah, I see you just hate backticks :P There is a difference: % echo `echo '\\'` \ % echo $(echo '\\') \\ That must be a bashism, can't reproduce in dash, mksh, zsh. -- Christian Neukirchen chneukirc...@gmail.com http://chneukirchen.org The builtin echo may differ from /bin/echo. For example, here in dash: $ echo '\\' \ $ echo `echo '\\'` \ $ echo $(echo '\\') \ $ /bin/echo '\\' \\ $ /bin/echo `/bin/echo '\\'` \ $ /bin/echo $(/bin/echo '\\') \\ In fact the builtin echo really sucks: $ echo \\ \ $ echo \
Re: [dev] [dmenu] dmenu_run improvements
On Sun, Jul 24, 2011 at 10:09 PM, Dave Reisner d...@falconindy.com wrote: On Sun, Jul 24, 2011 at 10:00:09PM +0800, lolilolicon wrote: Sorry, but can you give an example where `test -nt' is not available? Or can you point out what do you refer to to determine the portability of a shell script? I usually reference opengroup for POSIX util specs. I do love the wooledge wiki, though. http://pubs.opengroup.org/onlinepubs/95399/utilities/test.html d Thanks Dave for the tip. No wonder, I was looking at the Shell Command Language page. Cheers.
Re: [dev] Re: [dmenu] dmenu_run improvements
On Sun, Jul 24, 2011 at 10:30 PM, lolilolicon loliloli...@gmail.com wrote: On Sun, Jul 24, 2011 at 10:08 PM, Christian Neukirchen chneukirc...@gmail.com wrote: anonymous p37si...@lavabit.com writes: On Sun, Jul 24, 2011 at 02:34:22PM +0800, lolilolicon wrote: unset IFS cmd=$(dmenu $@ $CACHE) eval exec $cmd Yeah, I see you just hate backticks :P There is a difference: % echo `echo '\\'` \ % echo $(echo '\\') \\ That must be a bashism, can't reproduce in dash, mksh, zsh. -- Christian Neukirchen chneukirc...@gmail.com http://chneukirchen.org The builtin echo may differ from /bin/echo. For example, here in dash: $ echo '\\' \ $ echo `echo '\\'` \ $ echo $(echo '\\') \ $ /bin/echo '\\' \\ $ /bin/echo `/bin/echo '\\'` \ $ /bin/echo $(/bin/echo '\\') \\ In fact the builtin echo really sucks: $ echo \\ \ $ echo \ Sorry, but please allow me to rephrase the issue. The `echo' builtin in dash by default behaves like `echo -e' in bash. So it's a bit tricky to reproduce the difference in dash: $ echo `echo ''` \ $ echo $(echo '') \\ Or, just use `printf %s': $ printf '%s\n' `printf '%s\n' '\\'` \ $ printf '%s\n' $(printf '%s\n' '\\') \\ For the record, the sucky `echo' is what POSIX defines: http://pubs.opengroup.org/onlinepubs/9699919799/utilities/echo.html And thank you Szabolcs Nagy for pointing me to the lastes spec. Cheers.
[dev] [dmenu] dmenu_run improvements
dmenu_run doesn't really run the user input as a shell command line. For instance, run dmenu_run from a terminal, then in the menu type: echo hello\ world The terminal output is hello\ world instead of hello world. `eval' solves the problem, but when it comes to eval, I can't really be sure, so please point out the possible errors/risks. `LC_ALL=C' is for sort. diff -up a/dmenu_run b/dmenu_run --- a/dmenu_run 1970-01-01 00:00:00.0 + +++ b/dmenu_run 1970-01-01 00:00:00.0 + @@ -1,9 +1,9 @@ #!/bin/sh CACHE=${XDG_CACHE_HOME:-$HOME/.cache}/dmenu_run ( - IFS=: + IFS=: LC_ALL=C if test `ls -dt $PATH $CACHE 2 /dev/null | sed 1q` != $CACHE; then mkdir -p `dirname $CACHE` lsx $PATH | sort -u $CACHE fi ) -cmd=`dmenu $@ $CACHE` exec $cmd +cmd=`dmenu $@ $CACHE` eval exec $cmd
[dev][dmenu] loadfont() segmentation fault patch
To reproduce the segmentation fault: dmenu -fn -windows-montecarlo-medium-r-normal--11-110-72-72-c-60-microsoft-cp1252 hello The culprit is loadfont(). This font[1] triggers XLoadQueryFont(), and loadfont() fails in the for loop. At least that's what my quick debug indicated. I did a diff between dmenu 4.3.1 and 4.4. Do we really want this twisted pretentious piece of code inside loadfont()? I for one like the 4.3.1 code better. And it worked. Patch attached inline. I do it the humble way. [1] The font is MonteCarlo: http://bok.net/MonteCarlo/downloads/MonteCarlo-PCF.tgz --- dmenu-4.4-a/draw.c 2011-07-19 20:31:28.0 + +++ dmenu-4.4-b/draw.c 2011-07-22 12:34:35.026736893 + @@ -121,23 +121,25 @@ initfont(DC *dc, const char *fontstr) { Bool loadfont(DC *dc, const char *fontstr) { char *def, **missing, **names; - int i, n = 1; + int i, n; XFontStruct **xfonts; if(!*fontstr) return False; - if((dc-font.set = XCreateFontSet(dc-dpy, fontstr, missing, n, def))) + if((dc-font.set = XCreateFontSet(dc-dpy, fontstr, missing, n, def))) { n = XFontsOfFontSet(dc-font.set, xfonts, names); - else if((dc-font.xfont = XLoadQueryFont(dc-dpy, fontstr))) - xfonts = dc-font.xfont; - else - n = 0; - - for(i = 0; i n; i++) { - dc-font.ascent = MAX(dc-font.ascent, xfonts[i]-ascent); - dc-font.descent = MAX(dc-font.descent, xfonts[i]-descent); - dc-font.width = MAX(dc-font.width, xfonts[i]-max_bounds.width); + for(i = 0; i n; i++) { + dc-font.ascent = MAX(dc-font.ascent, xfonts[i]-ascent); + dc-font.descent = MAX(dc-font.descent, xfonts[i]-descent); + dc-font.width = MAX(dc-font.width, xfonts[i]-max_bounds.width); + } + } + else if((dc-font.xfont = XLoadQueryFont(dc-dpy, fontstr))) { + dc-font.ascent = dc-font.xfont-ascent; + dc-font.descent = dc-font.xfont-descent; + dc-font.width = dc-font.xfont-max_bounds.width; } + if(missing) XFreeStringList(missing); return (dc-font.set || dc-font.xfont);
[dev][dwm] Floating window center position fix
I have a rule in config.h for MPlayer to always float. The bug was observed when I played a 720p video whose window was 1280 pixels so it filled the width of my laptop screen. The MPlayer window was supposed to be centered, but a border line on the left was clearly visible. For further confirmation, I set borderpx to 20. My observation was obviously right. The patch is appended at the end. Another thing I've observed but didn't care to fix when playing the video: If the MPlayer window is always created in tag 4 (as per a rule), but I run the mplayer command in xterm in tag 1, the visibility of the MPlayer window border depends on some timing: 1) If I switch from tag 1 to tag 4 _after_ the MPlayer window is created, the border is visible. 2) If I switch from tag 1 to tag 4 _before_ the MPlayer window is created, the border is invisible. It seems for dwm the border exists, just not drawn. If I switch to another tag and back, or do a focusstack() using the default mod1+j combo or pretty about anything, the border will then be drawn. Relevant entries in my test config.h are: static const unsigned int borderpx = 20; static const Rule rules[] = { { MPlayer, NULL, NULL, 1 3, True, -1 }, }; And following is my patch for the center position bug: Patch for dwm 5.9 diff -up a/dwm.c b/dwm.c --- a/dwm.c 1970-01-01 00:00:00.0 + +++ b/dwm.c 1970-01-01 00:00:00.0 + @@ -624,9 +624,9 @@ configurerequest(XEvent *e) { if(ev-value_mask CWHeight) c-h = ev-height; if((c-x + c-w) m-mx + m-mw c-isfloating) - c-x = m-mx + (m-mw / 2 - c-w / 2); /* center in x direction */ + c-x = m-mx + (m-mw / 2 - WIDTH(c) / 2); /* center in x direction */ if((c-y + c-h) m-my + m-mh c-isfloating) - c-y = m-my + (m-mh / 2 - c-h / 2); /* center in y direction */ + c-y = m-my + (m-mh / 2 - HEIGHT(c) / 2); /* center in y direction */ if((ev-value_mask (CWX|CWY)) !(ev-value_mask (CWWidth|CWHeight))) configure(c); if(ISVISIBLE(c))