From 55625704b65c57d802869eaa7392a81fbca076e5 Mon Sep 17 00:00:00 2001 From: Matthias Kruk Date: Wed, 28 Apr 2021 06:45:52 +0900 Subject: [PATCH] Introduce workspaces to replace tags Tags are a nice idea, but they were clearly not designed for environments with multiple monitors. For example, in an environment with three screens, moving a window from one screen to another can require as many as 7 key- strokes (assuming neither the source nor the destination tag are selected). To make the grouping of windows more intuitive, this commit introduces workspaces. In simple terms, a workspace is a screenful of windows (or monitorful of clients, in X parlance). There are 12 workspaces in the default configuration. Unlike tags, there is only one set of workspaces, and all monitors can display the same workspaces. Meaning if I have a set of windows that I want to display on a different monitor, I can point that monitor to the workspace and it will display the windows without the need to move around windows. Better yet, because layouts are still set on a per-monitor basis, it is possible to use different monitors to view the same windows in a different layout. However, note that two monitors cannot display the same workspace at the same time, since that would mean that a window has two different sizes and positions at the same time. Pointing a monitor to a workspace that is being displayed on another monitor will make the monitors swap workspaces instead. --- config.def.h | 184 ++++--- dwm.c | 1483 +++++++++++++++++++++++++++----------------------- 2 files changed, 913 insertions(+), 754 deletions(-) diff --git a/config.def.h b/config.def.h index 415600e..b8b628a 100755 --- a/config.def.h +++ b/config.def.h @@ -2,22 +2,42 @@ /* appearance */ static const char font[] = "YOzNFb 8"; -static const char normbordercolor[] = "#000000"; -static const char normbgcolor[] = "#000000"; -static const char normfgcolor[] = "#bbbbbb"; -static const char selbordercolor[] = "#0048bf"; -static const char selbgcolor[] = "#000000"; -static const char selfgcolor[] = "#0048bf"; /* default: eeeeee */ + +static struct color_set theme[PalLast] = { + { /* PalNormal */ + .border = "#ffffff", + .background = "#ffffff", + .foreground = "#000000" + }, { /* PalSelected */ + .border = "#bb2323", + .background = "#bb2323", + .foreground = "#ffffff" + }, { /* PalNotSelected */ + .border = "#486c9c", + .background = "#486c9c", + .foreground = "#ffffff" + }, { /* PalOther */ + .border = "#d2d2d2", + .background = "#d2d2d2", + .foreground = "#ffffff" + } +}; + +static const char dmenu_normal_bg[] = "#ffffff"; +static const char dmenu_normal_fg[] = "#000000"; +static const char dmenu_sel_bg[] = "#bb2323"; +static const char dmenu_sel_fg[] = "#ffffff"; + static const unsigned int borderpx = 1; /* border pixel of windows */ static const unsigned int snap = 32; /* snap pixel */ static const Bool topbar = True; /* False means bottom bar */ static const Bool statusmarkup = True; /* True means use pango markup in status message */ /* tagging */ -static const char *tags[] = { "1", "2", "3", "4", "5", "6", "7", "8", "9" }; +static const char *tags[] = { "1", "2", "3", "4", "5", "6", "7", "8", "9", "10", "11", "12" }; static const struct rule rules[] = { - /* class instance title tags mask isfloating monitor */ + /* class instance title tags mask isfloating workspace */ { "Gimp", NULL, NULL, 0, True, -1 }, { "Firefox", NULL, NULL, 0, False, -1 }, }; @@ -39,96 +59,86 @@ static const struct layout layouts[] = { /* key definitions */ #define MODKEY Mod1Mask #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, show_workspace, {.ui = TAG} }, \ + { MODKEY|ShiftMask, KEY, move_focused, {.ui = 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 const char *dmenucmd[] = { "dmenu_run", "-fn", font, "-nb", normbgcolor, "-nf", normfgcolor, "-sb", selbgcolor, "-sf", selfgcolor, NULL }; +static const char *dmenucmd[] = { "dmenu_run", + "-fn", font, + "-nb", dmenu_normal_bg, + "-nf", dmenu_normal_fg, + "-sb", dmenu_sel_bg, + "-sf", dmenu_sel_fg, + NULL }; static const char *termcmd[] = { "uxterm", NULL }; static struct key keys[] = { - /* modifier key function argument */ - - /* get the zap keyboard shortcut back */ - { MODKEY|ControlMask, XK_BackSpace, quit, {0} }, - /* invoke dmenu */ - { MODKEY, XK_p, spawn, {.v = dmenucmd } }, - /* open a terminal */ - { MODKEY|ShiftMask, XK_Return, spawn, {.v = termcmd } }, -#ifndef M10K - { MODKEY, XK_b, togglebar, {0} }, -#endif /* ! M10K */ - /* move focus to left screen */ - { MODKEY, XK_q, focusmon, {.i = -1 } }, - /* move window to left screen */ - { MODKEY|ShiftMask, XK_q, tagmon, {.i = -1 } }, - /* move focus to right screen */ - { MODKEY, XK_e, focusmon, {.i = +1 } }, - /* move window to right screen */ - { MODKEY|ShiftMask, XK_e, tagmon, {.i = +1 } }, - /* focus on left/above window (bookshelf/bookstack) */ - { MODKEY, XK_a, focusstack, {.i = -1 } }, - /* focus on right/below window (bookshelf/bookstack) */ - { MODKEY, XK_d, focusstack, {.i = +1 } }, - /* move window to master */ - { MODKEY, XK_Return, zoom, {0} }, - /* move to previous tag */ - { MODKEY, XK_Tab, view, {0} }, - /* kill focused window */ - { MODKEY|ShiftMask, XK_x, killclient, {0} }, - { MODKEY, XK_t, setlayout, {.v = &layouts[LAYOUT_BOOKSHELF]} }, - { MODKEY, XK_y, setlayout, {.v = &layouts[LAYOUT_BOOKSTACK]} }, - { MODKEY, XK_r, setlayout, {.v = &layouts[LAYOUT_FLOAT]} }, - { MODKEY, XK_i, kbptr_move, {.ui = KBPTR_NORTH} }, - { MODKEY | ShiftMask, XK_i, kbptr_move, {.ui = KBPTR_NORTH | KBPTR_HALFSTEP} }, - { MODKEY, XK_l, kbptr_move, {.ui = KBPTR_EAST} }, - { MODKEY | ShiftMask, XK_l, kbptr_move, {.ui = KBPTR_EAST | KBPTR_HALFSTEP} }, - { MODKEY, XK_k, kbptr_move, {.ui = KBPTR_SOUTH} }, - { MODKEY | ShiftMask, XK_k, kbptr_move, {.ui = KBPTR_SOUTH | KBPTR_HALFSTEP} }, - { MODKEY, XK_j, kbptr_move, {.ui = KBPTR_WEST} }, - { MODKEY | ShiftMask, XK_j, kbptr_move, {.ui = KBPTR_WEST | KBPTR_HALFSTEP} }, - { MODKEY, XK_u, kbptr_move, {.ui = KBPTR_CENTER} }, - { MODKEY, XK_semicolon, kbptr_click, {.ui = KBPTR_LEFT} }, - { MODKEY | ShiftMask, XK_semicolon, kbptr_click, {.ui = KBPTR_RIGHT} }, - { MODKEY | ControlMask, XK_semicolon, kbptr_click, {.ui = KBPTR_MIDDLE} }, -#ifndef M10K - { MODKEY, XK_space, setlayout, {0} }, - { MODKEY|ShiftMask, XK_space, togglefloating, {0} }, -#endif /* ! M10K */ - /* show all tags */ - { MODKEY, XK_0, view, {.ui = ~0 } }, - /* move focused window to all tags */ - { MODKEY|ShiftMask, XK_0, tag, {.ui = ~0 } }, - TAGKEYS( XK_1, 0) - TAGKEYS( XK_2, 1) - TAGKEYS( XK_3, 2) - TAGKEYS( XK_4, 3) - TAGKEYS( XK_5, 4) - TAGKEYS( XK_6, 5) - TAGKEYS( XK_7, 6) - TAGKEYS( XK_8, 7) - TAGKEYS( XK_9, 8) + /* modifier key function argument */ + + /* get the zap keyboard shortcut back */ + { MODKEY|ControlMask, XK_BackSpace, quit, {0} }, + /* invoke dmenu */ + { MODKEY, XK_p, spawn, {.v = dmenucmd} }, + /* open a terminal */ + { MODKEY|ShiftMask, XK_Return, spawn, {.v = termcmd} }, + /* move focus to left screen */ + { MODKEY, XK_q, focusmon, {.i = -1} }, + /* move window to left screen */ + { MODKEY|ShiftMask, XK_q, tagmon, {.i = -1} }, + /* move focus to right screen */ + { MODKEY, XK_e, focusmon, {.i = +1} }, + /* move window to right screen */ + { MODKEY|ShiftMask, XK_e, tagmon, {.i = +1} }, + /* focus on left/above window (bookshelf/bookstack) */ + { MODKEY, XK_a, focusstack, {.i = -1} }, + /* focus on right/below window (bookshelf/bookstack) */ + { MODKEY, XK_d, focusstack, {.i = +1} }, + /* move to previous tag */ + { MODKEY, XK_Tab, show_workspace, {.i = -1} }, + /* kill focused window */ + { MODKEY|ShiftMask, XK_x, killclient, {0} }, + + { MODKEY, XK_t, setlayout, { .v = &layouts[LAYOUT_BOOKSHELF]} }, + { MODKEY, XK_y, setlayout, { .v = &layouts[LAYOUT_BOOKSTACK]} }, + + { MODKEY, XK_i, kbptr_move, {.ui = KBPTR_NORTH} }, + { MODKEY | ShiftMask, XK_i, kbptr_move, {.ui = KBPTR_NORTH | KBPTR_HALFSTEP} }, + { MODKEY, XK_l, kbptr_move, {.ui = KBPTR_EAST} }, + { MODKEY | ShiftMask, XK_l, kbptr_move, {.ui = KBPTR_EAST | KBPTR_HALFSTEP} }, + { MODKEY, XK_k, kbptr_move, {.ui = KBPTR_SOUTH} }, + { MODKEY | ShiftMask, XK_k, kbptr_move, {.ui = KBPTR_SOUTH | KBPTR_HALFSTEP} }, + { MODKEY, XK_j, kbptr_move, {.ui = KBPTR_WEST} }, + { MODKEY | ShiftMask, XK_j, kbptr_move, {.ui = KBPTR_WEST | KBPTR_HALFSTEP} }, + { MODKEY, XK_u, kbptr_move, {.ui = KBPTR_CENTER} }, + { MODKEY, XK_semicolon, kbptr_click, {.ui = KBPTR_LEFT} }, + { MODKEY | ShiftMask, XK_semicolon, kbptr_click, {.ui = KBPTR_RIGHT} }, + { MODKEY | ControlMask, XK_semicolon, kbptr_click, {.ui = KBPTR_MIDDLE} }, + + TAGKEYS( XK_1, 0) + TAGKEYS( XK_2, 1) + TAGKEYS( XK_3, 2) + TAGKEYS( XK_4, 3) + TAGKEYS( XK_5, 4) + TAGKEYS( XK_6, 5) + TAGKEYS( XK_7, 6) + TAGKEYS( XK_8, 7) + TAGKEYS( XK_9, 8) + TAGKEYS( XK_0, 9) + TAGKEYS( XK_minus, 10) + TAGKEYS( XK_asciicircum, 11) }; /* button definitions */ /* click can be ClkLtSymbol, ClkStatusText, ClkWinTitle, ClkClientWin, or ClkRootWin */ static struct button buttons[] = { - /* click event mask button function argument */ - { ClkLtSymbol, 0, Button1, setlayout, {0} }, - { ClkWinTitle, 0, Button2, zoom, {0} }, -#ifndef M10K - { ClkStatusText, 0, Button2, spawn, {.v = termcmd } }, -#endif /* ! M10K */ - { ClkClientWin, MODKEY, Button1, movemouse, {0} }, - { ClkClientWin, MODKEY, Button2, togglefloating, {0} }, - { ClkClientWin, MODKEY, Button3, resizemouse, {0} }, - { ClkTagBar, 0, Button1, view, {0} }, - { ClkTagBar, 0, Button3, toggleview, {0} }, - { ClkTagBar, MODKEY, Button1, tag, {0} }, - { ClkTagBar, MODKEY, Button3, toggletag, {0} }, + /* click event mask button function argument */ + { ClkLtSymbol, 0, Button1, setlayout, {0} }, + { ClkClientWin, MODKEY, Button1, movemouse, {0} }, + { ClkClientWin, MODKEY, Button2, togglefloating, {0} }, + { ClkClientWin, MODKEY, Button3, resizemouse, {0} }, + { ClkTagBar, 0, Button1, show_workspace, {0} }, + { ClkTagBar, MODKEY, Button1, move_focused, {0} }, }; diff --git a/dwm.c b/dwm.c index 30ecdd1..bdcebfb 100755 --- a/dwm.c +++ b/dwm.c @@ -10,12 +10,6 @@ * whenever a new event has been fetched. This allows event dispatching * in O(1) time. * - * Each child of the root window is called a client, except windows which have - * set the override_redirect flag. Clients are organized in a linked client - * list on each monitor, the focus history is remembered through a stack list - * on each monitor. Each client contains a bit array to indicate the tags of a - * client. - * * Keys and tagging rules are organized as arrays and defined in config.h. * * To understand everything else, start reading main(). @@ -42,15 +36,13 @@ #include #include #include -#ifdef XINERAMA #include -#endif /* XINERAMA */ #define BUTTONMASK (ButtonPressMask | ButtonReleaseMask) #define ALLMODMASK (Mod1Mask | Mod2Mask | Mod3Mask | Mod4Mask | Mod5Mask) #define ALLMASK (ShiftMask | ControlMask | ALLMODMASK) #define CLEANMASK(mask) (mask & ~(numlockmask|LockMask) & ALLMASK) -#define ISVISIBLE(C) ((C->tags & C->mon->tagset[C->mon->seltags])) +#define ISVISIBLE(C) ((C->workspace->viewer)) #define LENGTH(X) (sizeof X / sizeof X[0]) #ifndef MAX #define MAX(A, B) ((A) > (B) ? (A) : (B)) @@ -92,6 +84,14 @@ enum { ColLast }; +enum { + PalNormal, + PalSelected, + PalNotSelected, + PalOther, + PalLast +}; + /* EWMH atoms */ enum { NetSupported, @@ -154,6 +154,10 @@ struct rect { int h; }; +struct workspace; +struct client; +struct monitor; + struct client { char name[256]; float mina; @@ -172,27 +176,30 @@ struct client { int w; int oldw; } border; - unsigned int tags; Bool isfixed, isfloating, isurgent, neverfocus, oldstate, isfullscreen; struct client *next; - struct client *snext; - struct monitor *mon; + struct workspace *workspace; Window win; }; +struct palette { + unsigned long color[ColLast]; + XftColor xcolor[ColLast]; +}; + +struct color_set { + char *border; + char *background; + char *foreground; +}; + struct draw_context { struct rect geom; - unsigned long norm[ColLast]; - unsigned long sel[ColLast]; + struct palette palette[PalLast]; Drawable drawable; GC gc; - - struct { - XftColor norm[ColLast]; - XftColor sel[ColLast]; - XftDraw *drawable; - } xft; + XftDraw *draw; struct { int ascent; @@ -221,25 +228,30 @@ struct monitor { struct rect geom; struct rect win_geom; - unsigned int seltags; unsigned int sellt; unsigned int tagset[2]; + struct workspace *workspace; + struct workspace *previous_workspace; Bool topbar; - struct client *clients; - struct client *sel; - struct client *stack; struct monitor *next; Window barwin; const struct layout *lt[2]; }; +struct workspace { + struct client *clients; + struct client *focused; + struct monitor *viewer; + int num; +}; + struct rule { const char *class; const char *instance; const char *title; unsigned int tags; Bool isfloating; - int monitor; + int workspace; }; #define KBPTR_CENTER 0 @@ -267,41 +279,48 @@ static void kbptr_click(const union arg *arg); static struct kbptr kbptr = { 0, 0, 0, 0 }; -/* function declarations */ -static void applyrules(struct client *c); -static Bool applysizehints(struct client *c, int *x, int *y, - int *w, int *h, Bool interact); -static void arrange(struct monitor *m); -static void arrangemon(struct monitor *m); -static void attach(struct client *c); -static void attachstack(struct client *c); +static void workspace_add_client(struct workspace *workspace, + struct client *client); +static void workspace_remove_client(struct workspace *workspace, + struct client *client); +static struct workspace* workspace_get_current(void); +static int workspace_is_urgent(struct workspace *workspace); +static void workspace_update(struct workspace *workspace); + +static int monitor_has_focus(struct monitor *mon); +static void monitor_focus(struct monitor *mon); +static void monitor_free(struct monitor *mon); +static int monitor_is_floating(struct monitor *mon); +static void monitor_resize_barwin(struct monitor *m); + +static void client_move_to_workspace(struct client *client, struct workspace *workspace); + +static void client_apply_rules(struct client *c); +static Bool client_apply_size_hints(struct client *c, int *x, int *y, + int *w, int *h, Bool interact); +static void monitor_arrange_clients(struct monitor *m); static void buttonpress(XEvent *e); static int checkotherwm(void); static void cleanup(void); -static void cleanupmon(struct monitor *mon); static void clearurgent(struct client *c); static void clientmessage(XEvent *e); -static void configure(struct client *c); +static void client_configure(struct client *c); static void configurenotify(XEvent *e); static void configurerequest(XEvent *e); static struct monitor *createmon(void); static void destroynotify(XEvent *e); static void detach(struct client *c); -static void detachstack(struct client *c); static void die(const char *errstr, ...); static struct monitor *dirtomon(int dir); static void drawbar(struct monitor *m); static void drawbars(void); -static void drawsquare(Bool filled, Bool empty, Bool invert, - unsigned long col[ColLast]); -static void drawtext(const char *text, unsigned long col[ColLast], - Bool invert); +static void drawsquare(Bool filled, Bool empty, Bool invert, int palette); +static void drawtext(const char *text, int pal, Bool invert); static void enternotify(XEvent *e); static void expose(XEvent *e); static void focus(struct client *c); static void focusin(XEvent *e); -static void focusmon(const union arg *arg); -static void focusstack(const union arg *arg); + static unsigned long getcolor(const char *colstr, XftColor *color); static Bool getrootptr(int *x, int *y); static long getstate(Window w); @@ -310,21 +329,19 @@ static void grabbuttons(struct client *c, Bool focused); static void grabkeys(void); static void initfont(const char *fontstr); static void keypress(XEvent *e); -static void killclient(const union arg *arg); + static void manage(Window w, XWindowAttributes *wa); static void mappingnotify(XEvent *e); static void maprequest(XEvent *e); static void motionnotify(XEvent *e); -static void movemouse(const union arg *arg); + static struct client *nexttiled(struct client *c); -static void pop(struct client *); static void propertynotify(XEvent *e); -static void quit(const union arg *arg); + static struct monitor *recttomon(int x, int y, int w, int h); -static void resize(struct client *c, int x, int y, int w, int h, Bool interact); -static void resizebarwin(struct monitor *m); -static void resizeclient(struct client *c, int x, int y, int w, int h); -static void resizemouse(const union arg *arg); +static void client_resize_hints(struct client *c, int x, int y, int w, int h, Bool interact); +static void client_resize(struct client *c, int x, int y, int w, int h); + static void restack(struct monitor *m); static void run(void); static void scan(void); @@ -334,17 +351,12 @@ static void sendmon(struct client *c, struct monitor *m); static void setclientstate(struct client *c, long state); static void setfocus(struct client *c); static void setfullscreen(struct client *c, Bool fullscreen); -static void setlayout(const union arg *arg); static void setup(void); static void showhide(struct client *c); static void sigchld(int unused); -static void spawn(const union arg *arg); -static void tag(const union arg *arg); -static void tagmon(const union arg *arg); + static int textnw(const char *text, unsigned int len); -static void togglefloating(const union arg *arg); -static void toggletag(const union arg *arg); -static void toggleview(const union arg *arg); + static void unfocus(struct client *c, Bool setfocus); static void unmanage(struct client *c, Bool destroyed); static void unmapnotify(XEvent *e); @@ -352,27 +364,40 @@ static Bool updategeom(void); static void updatebarpos(struct monitor *m); static void updatebars(void); static void updatenumlockmask(void); -static void updatesizehints(struct client *c); +static void client_update_size_hints(struct client *c); static void updatestatus(void); -static void updatewindowtype(struct client *c); -static void updatetitle(struct client *c); -static void updatewmhints(struct client *c); -static void view(const union arg *arg); +static void client_update_window_type(struct client *c); +static void client_update_title(struct client *c); +static void client_update_wm_hints(struct client *c); static struct client *wintoclient(Window w); static struct monitor *wintomon(Window w); static int xerror(Display *dpy, XErrorEvent *ee); static int xerrordummy(Display *dpy, XErrorEvent *ee); static int xerrorstart(Display *dpy, XErrorEvent *ee); -static void zoom(const union arg *arg); static Atom getatomprop(struct client *c, Atom prop); static void bookshelf(struct monitor *mon); static void bookstack(struct monitor *mon); +static void spawn(const union arg *arg); +static void tagmon(const union arg *arg); +static void togglefloating(const union arg *arg); +static void show_workspace(const union arg *arg); +static void move_focused(const union arg *arg); +static void focusmon(const union arg *arg); +static void focusstack(const union arg *arg); +static void killclient(const union arg *arg); +static void movemouse(const union arg *arg); +static void quit(const union arg *arg); +static void resizemouse(const union arg *arg); +static void setlayout(const union arg *arg); + + static const char broken[] = "broken"; static char stext[512]; static int screen; static int screen_width; static int screen_height; +static int num_monitors = 0; static int bar_height; static int bar_lwidth = 0; /* bar geometry */ static int (*xerrorxlib)(Display *, XErrorEvent *); @@ -401,6 +426,7 @@ static struct draw_context dc; static struct monitor *mons = NULL; static struct monitor *selmon = NULL; static Window root; +static struct workspace workspaces[12] = { 0 }; /* configuration, allows nested code to access above variables */ #include "config.h" @@ -410,47 +436,59 @@ struct NumTags { char limitexceeded[LENGTH(tags) > 31 ? -1 : 1]; }; -/* function implementations */ -void applyrules(struct client *c) +static int rule_match(struct rule const *rule, XClassHint *class_hint, const char *client_name) { const char *class, *instance; - unsigned int i; - const struct rule *r; - struct monitor *m; - XClassHint ch = { NULL, NULL }; - /* rule matching */ - c->isfloating = c->tags = 0; - XGetClassHint(dpy, c->win, &ch); - class = ch.res_class ? ch.res_class : broken; - instance = ch.res_name ? ch.res_name : broken; + class = class_hint->res_class ? class_hint->res_class : broken; + instance = class_hint->res_name ? class_hint->res_name : broken; - for(i = 0; i < LENGTH(rules); i++) { - r = &rules[i]; + if(rule->title && !strstr(client_name, rule->title)) { + return(FALSE); + } - if((!r->title || strstr(c->name, r->title)) && - (!r->class || strstr(class, r->class)) && - (!r->instance || strstr(instance, r->instance))) { - c->isfloating = r->isfloating; - c->tags |= r->tags; + if(rule->class && !strstr(class, rule->class)) { + return(FALSE); + } - for(m = mons; m && m->num != r->monitor; m = m->next); + if(rule->instance && !strstr(instance, rule->instance)) { + return(FALSE); + } - if(m) { - c->mon = m; + return(TRUE); +} + +void client_apply_rules(struct client *client) +{ + struct workspace *workspace; + XClassHint class_hint; + unsigned int i; + + memset(&class_hint, 0, sizeof(class_hint)); + client->isfloating = 0; + workspace = workspace_get_current(); + + XGetClassHint(dpy, client->win, &class_hint); + + for(i = 0; i < LENGTH(rules); i++) { + if(rule_match(&rules[i], &class_hint, client->name)) { + client->isfloating = rules[i].isfloating; + + if(rules[i].workspace > 0) { + workspace = &workspaces[rules[i].workspace]; } } } - if(ch.res_class) { - XFree(ch.res_class); + if(class_hint.res_class) { + XFree(class_hint.res_class); } - if(ch.res_name) { - XFree(ch.res_name); + if(class_hint.res_name) { + XFree(class_hint.res_name); } - c->tags = c->tags & TAGMASK ? c->tags & TAGMASK : c->mon->tagset[c->mon->seltags]; + client_move_to_workspace(client, workspace); return; } @@ -465,12 +503,14 @@ static inline int client_get_height(struct client *client) return(client->geom.h + 2 * client->border.w); } -Bool applysizehints(struct client *c, int *x, int *y, int *w, int *h, Bool interact) +static Bool client_apply_size_hints(struct client *c, + int *x, int *y, int *w, int *h, + Bool interact) { Bool baseismin; - struct monitor *m; + struct monitor *mon; - m = c->mon; + mon = c->workspace->viewer; /* set minimum possible */ *w = MAX(1, *w); @@ -490,17 +530,17 @@ Bool applysizehints(struct client *c, int *x, int *y, int *w, int *h, Bool inter *y = 0; } } else { - if(*x >= m->win_geom.x + m->win_geom.w) { - *x = m->win_geom.x + m->win_geom.w - client_get_width(c); + if(*x >= mon->win_geom.x + mon->win_geom.w) { + *x = mon->win_geom.x + mon->win_geom.w - client_get_width(c); } - if(*y >= m->win_geom.y + m->win_geom.h) { - *y = m->win_geom.y + m->win_geom.h - client_get_height(c); + if(*y >= mon->win_geom.y + mon->win_geom.h) { + *y = mon->win_geom.y + mon->win_geom.h - client_get_height(c); } - if(*x + *w + 2 * c->border.w <= m->win_geom.x) { - *x = m->win_geom.x; + if(*x + *w + 2 * c->border.w <= mon->win_geom.x) { + *x = mon->win_geom.x; } - if(*y + *h + 2 * c->border.w <= m->win_geom.y) { - *y = m->win_geom.y; + if(*y + *h + 2 * c->border.w <= mon->win_geom.y) { + *y = mon->win_geom.y; } } @@ -511,7 +551,7 @@ Bool applysizehints(struct client *c, int *x, int *y, int *w, int *h, Bool inter *w = bar_height; } - if(resizehints || c->isfloating || !c->mon->lt[c->mon->sellt]->arrange) { + if(resizehints || c->isfloating || (mon && !monitor_is_floating(mon))) { /* * "The min_aspect and max_aspect fields are fractions with the * numerator first and the denominator second, and they allow a @@ -571,75 +611,82 @@ Bool applysizehints(struct client *c, int *x, int *y, int *w, int *h, Bool inter return(*x != c->geom.x || *y != c->geom.y || *w != c->geom.w || *h != c->geom.h); } -void arrange(struct monitor *m) +void workspace_update(struct workspace *workspace) { - if(m) { - showhide(m->stack); - } else { - for(m = mons; m; m = m->next) { - showhide(m->stack); - } - } + if(workspace) { + showhide(workspace->clients); - if(m) { - arrangemon(m); + if(workspace->viewer) { + monitor_arrange_clients(workspace->viewer); + } } else { - for(m = mons; m; m = m->next) { - arrangemon(m); + int i; + + for(i = 0; i < LENGTH(workspaces); i++) { + workspace_update(&workspaces[i]); } } return; } -void arrangemon(struct monitor *m) +void monitor_arrange_clients(struct monitor *monitor) { - strncpy(m->ltsymbol, m->lt[m->sellt]->symbol, sizeof(m->ltsymbol)); + strncpy(monitor->ltsymbol, + monitor->lt[monitor->sellt]->symbol, + sizeof(monitor->ltsymbol)); - if(m->lt[m->sellt]->arrange) { - m->lt[m->sellt]->arrange(m); + if(!monitor_is_floating(monitor)) { + monitor->lt[monitor->sellt]->arrange(monitor); } - restack(m); + restack(monitor); return; } -void attach(struct client *c) +static void monitor_focus(struct monitor *mon) { - c->next = c->mon->clients; - c->mon->clients = c; + if(selmon->workspace->focused) { + unfocus(selmon->workspace->focused, True); + } + + selmon = mon; + focus(NULL); return; } -void attachstack(struct client *c) +static int monitor_has_focus(struct monitor *mon) { - c->snext = c->mon->stack; - c->mon->stack = c; + if(mon == selmon) { + return(TRUE); + } - return; + return(FALSE); } -void buttonpress(XEvent *e) +void buttonpress(XEvent *event) { unsigned int i, x, click; - union arg arg = {0}; - struct client *c; - struct monitor *m; - XButtonPressedEvent *ev = &e->xbutton; + union arg arg; + struct client *client; + struct monitor *event_monitor; + XButtonPressedEvent *ev; + ev = &event->xbutton; + arg.ui = 0; click = ClkRootWin; - /* focus monitor if necessary */ - if((m = wintomon(ev->window)) && m != selmon) { - unfocus(selmon->sel, True); - selmon = m; - focus(NULL); + event_monitor = wintomon(ev->window); + + if(event_monitor && !monitor_has_focus(event_monitor)) { + monitor_focus(event_monitor); } if(ev->window == selmon->barwin) { - i = x = 0; + i = 0; + x = 0; do { x += TEXTW(tags[i]); @@ -647,7 +694,7 @@ void buttonpress(XEvent *e) if(i < LENGTH(tags)) { click = ClkTagBar; - arg.ui = 1 << i; + arg.ui = i; } else if(ev->x < x + bar_lwidth) { click = ClkLtSymbol; } else if(ev->x > selmon->win_geom.w - TEXTW(stext)) { @@ -655,8 +702,8 @@ void buttonpress(XEvent *e) } else { click = ClkWinTitle; } - } else if((c = wintoclient(ev->window))) { - focus(c); + } else if((client = wintoclient(ev->window))) { + focus(client); click = ClkClientWin; } @@ -692,7 +739,6 @@ int checkotherwm(void) void cleanup(void) { struct layout foo; - struct monitor *m; union arg a; int i; @@ -700,28 +746,29 @@ void cleanup(void) foo.symbol = ""; foo.arrange = NULL; - view(&a); + show_workspace(&a); selmon->lt[selmon->sellt] = &foo; - for(m = mons; m; m = m->next) { - while(m->stack) { - unmanage(m->stack, False); + for(i = 0; i < LENGTH(workspaces); i++) { + while(workspaces[i].clients) { + unmanage(workspaces[i].clients, False); } } XUngrabKey(dpy, AnyKey, AnyModifier, root); XFreePixmap(dpy, dc.drawable); - for(i = ColBorder; i < ColLast; i++) { - XftColorFree(dpy, DefaultVisual(dpy, screen), - DefaultColormap(dpy, screen), - dc.xft.norm + i); - XftColorFree(dpy, DefaultVisual(dpy, screen), - DefaultColormap(dpy, screen), - dc.xft.sel + i); + for(i = 0; i < PalLast; i++) { + int col; + + for(col = 0; col < ColLast; col++) { + XftColorFree(dpy, DefaultVisual(dpy, screen), + DefaultColormap(dpy, screen), + &dc.palette[i].xcolor[col]); + } } - XftDrawDestroy(dc.xft.drawable); + XftDrawDestroy(dc.draw); g_object_unref(dc.font.layout); XFreeGC(dpy, dc.gc); XFreeCursor(dpy, cursor[CurNormal]); @@ -729,7 +776,7 @@ void cleanup(void) XFreeCursor(dpy, cursor[CurMove]); while(mons) { - cleanupmon(mons); + monitor_free(mons); } XSync(dpy, False); @@ -738,73 +785,78 @@ void cleanup(void) return; } -void cleanupmon(struct monitor *mon) +static void monitor_free(struct monitor *mon) { - struct monitor *m; + struct monitor **cur; - if(mon == mons) { - mons = mons->next; - } else { - for(m = mons; m && m->next != mon; m = m->next); - m->next = mon->next; + for(cur = &mons; *cur; cur = &((*cur)->next)) { + if(*cur == mon) { + *cur = mon->next; + break; + } } XUnmapWindow(dpy, mon->barwin); XDestroyWindow(dpy, mon->barwin); free(mon); + num_monitors--; return; } -void clearurgent(struct client *c) +void clearurgent(struct client *client) { - XWMHints *wmh; + XWMHints *hints; - c->isurgent = False; - if(!(wmh = XGetWMHints(dpy, c->win))) + client->isurgent = False; + hints = XGetWMHints(dpy, client->win); + + if(!hints) { return; - wmh->flags &= ~XUrgencyHint; - XSetWMHints(dpy, c->win, wmh); - XFree(wmh); + } + + hints->flags &= ~XUrgencyHint; + XSetWMHints(dpy, client->win, hints); + XFree(hints); return; } -void clientmessage(XEvent *e) +void clientmessage(XEvent *event) { - XClientMessageEvent *cme; - struct client *c; + XClientMessageEvent *cmsg_event; + struct client *client; - cme = &e->xclient; - c = wintoclient(cme->window); + cmsg_event = &event->xclient; + client = wintoclient(cmsg_event->window); - if(!c) { + if(!client) { return; } - if(cme->message_type == netatom[NetWMState]) { - if(cme->data.l[1] == netatom[NetWMFullscreen] || - cme->data.l[2] == netatom[NetWMFullscreen]) { + if(cmsg_event->message_type == netatom[NetWMState]) { + if(cmsg_event->data.l[1] == netatom[NetWMFullscreen] || + cmsg_event->data.l[2] == netatom[NetWMFullscreen]) { int fs; - fs = cme->data.l[0] == 1 /* _NET_WM_STATE_ADD */ || - (cme->data.l[0] == 2 /* _NET_WM_STATE_TOGGLE */ && - !c->isfullscreen); + fs = cmsg_event->data.l[0] == 1 /* _NET_WM_STATE_ADD */ || + (cmsg_event->data.l[0] == 2 /* _NET_WM_STATE_TOGGLE */ && + !client->isfullscreen); - setfullscreen(c, fs); + setfullscreen(client, fs); } - } else if(cme->message_type == netatom[NetActiveWindow]) { - if(!ISVISIBLE(c)) { - c->mon->seltags ^= 1; - c->mon->tagset[c->mon->seltags] = c->tags; + } else if(cmsg_event->message_type == netatom[NetActiveWindow]) { + focus(client); + + if(client->workspace->viewer) { + workspace_update(client->workspace); } - pop(c); } return; } -void configure(struct client *c) +void client_configure(struct client *c) { XConfigureEvent ce; @@ -842,15 +894,15 @@ void configurenotify(XEvent *e) dc.drawable = XCreatePixmap(dpy, root, screen_width, bar_height, DefaultDepth(dpy, screen)); - XftDrawChange(dc.xft.drawable, dc.drawable); + XftDrawChange(dc.draw, dc.drawable); updatebars(); for(m = mons; m; m = m->next) { - resizebarwin(m); + monitor_resize_barwin(m); } focus(NULL); - arrange(NULL); + workspace_update(NULL); } } @@ -869,50 +921,52 @@ void configurerequest(XEvent *e) if((c = wintoclient(ev->window))) { if(ev->value_mask & CWBorderWidth) { c->border.w = ev->border_width; - } else if(c->isfloating || !selmon->lt[selmon->sellt]->arrange) { - m = c->mon; + } else if(c->isfloating || monitor_is_floating(selmon)) { + m = c->workspace->viewer; - if(ev->value_mask & CWX) { - c->old_geom.x = c->geom.x; - c->geom.x = m->geom.x + ev->x; - } - if(ev->value_mask & CWY) { - c->old_geom.y = c->geom.y; - c->geom.y = m->geom.y + ev->y; - } - if(ev->value_mask & CWWidth) { - c->old_geom.w = c->geom.w; - c->geom.w = ev->width; - } - if(ev->value_mask & CWHeight) { - c->old_geom.h = c->geom.h; - c->geom.h = ev->height; - } - - if(c->isfloating) { - if((c->geom.x + c->geom.w) > m->geom.x + m->geom.w) { - /* center in x direction */ - c->geom.x = m->geom.x + - (m->geom.w / 2 - client_get_width(c) / 2); + if(m) { + if(ev->value_mask & CWX) { + c->old_geom.x = c->geom.x; + c->geom.x = m->geom.x + ev->x; } - if((c->geom.y + c->geom.h) > m->geom.y + m->geom.h) { - /* center in y direction */ - c->geom.y = m->geom.y + - (m->geom.h / 2 - client_get_height(c) / 2); + if(ev->value_mask & CWY) { + c->old_geom.y = c->geom.y; + c->geom.y = m->geom.y + ev->y; + } + if(ev->value_mask & CWWidth) { + c->old_geom.w = c->geom.w; + c->geom.w = ev->width; + } + if(ev->value_mask & CWHeight) { + c->old_geom.h = c->geom.h; + c->geom.h = ev->height; } - } - if((ev->value_mask & (CWX|CWY)) && - !(ev->value_mask & (CWWidth|CWHeight))) { - configure(c); - } + if(c->isfloating) { + if((c->geom.x + c->geom.w) > m->geom.x + m->geom.w) { + /* center in x direction */ + c->geom.x = m->geom.x + + (m->geom.w / 2 - client_get_width(c) / 2); + } + if((c->geom.y + c->geom.h) > m->geom.y + m->geom.h) { + /* center in y direction */ + c->geom.y = m->geom.y + + (m->geom.h / 2 - client_get_height(c) / 2); + } + } - if(ISVISIBLE(c)) { - XMoveResizeWindow(dpy, c->win, c->geom.x, c->geom.y, - c->geom.w, c->geom.h); + if((ev->value_mask & (CWX | CWY)) && + !(ev->value_mask & (CWWidth | CWHeight))) { + client_configure(c); + } + + if(ISVISIBLE(c)) { + XMoveResizeWindow(dpy, c->win, c->geom.x, c->geom.y, + c->geom.w, c->geom.h); + } } } else { - configure(c); + client_configure(c); } } else { wc.x = ev->x; @@ -933,6 +987,7 @@ void configurerequest(XEvent *e) struct monitor *createmon(void) { struct monitor *m; + int i; m = (struct monitor *)calloc(1, sizeof(*m)); @@ -946,7 +1001,17 @@ struct monitor *createmon(void) m->lt[0] = &layouts[0]; m->lt[1] = &layouts[1 % LENGTH(layouts)]; + for(i = 0; i < LENGTH(workspaces); i++) { + if(!workspaces[i].viewer) { + m->workspace = &workspaces[i]; + m->previous_workspace = &workspaces[i]; + workspaces[i].viewer = m; + break; + } + } + strncpy(m->ltsymbol, layouts[0].symbol, sizeof m->ltsymbol); + num_monitors++; return(m); } @@ -963,31 +1028,46 @@ void destroynotify(XEvent *e) return; } -void detach(struct client *c) +static void workspace_add_client(struct workspace *workspace, + struct client *client) { - struct client **tc; + struct client **cur; + + for(cur = &(workspace->clients); *cur; cur = &((*cur)->next)); + *cur = client; - for(tc = &c->mon->clients; *tc && *tc != c; tc = &(*tc)->next); - *tc = c->next; + client->workspace = workspace; + client->next = NULL; return; } -void detachstack(struct client *c) +static void workspace_remove_client(struct workspace *workspace, + struct client *client) { - struct client **tc, *t; + struct client **cur; - for(tc = &c->mon->stack; *tc && *tc != c; tc = &(*tc)->snext); - *tc = c->snext; - - if(c == c->mon->sel) { - for(t = c->mon->stack; t && !ISVISIBLE(t); t = t->snext); - c->mon->sel = t; + for(cur = &(workspace->clients); *cur; cur = &((*cur)->next)) { + if(*cur == client) { + *cur = client->next; + break; + } } return; } +static struct workspace* workspace_get_current(void) +{ + return(selmon->workspace); +} + +static void detach(struct client *client) +{ + workspace_remove_client(client->workspace, client); + return; +} + void die(const char *errstr, ...) { va_list ap; @@ -1016,47 +1096,63 @@ struct monitor *dirtomon(int dir) return(m); } -void wipe_bar(struct monitor *mon, unsigned long *col) +void wipe_bar(struct monitor *mon, int pal) { - XSetForeground(dpy, dc.gc, col[ColBG]); + XSetForeground(dpy, dc.gc, dc.palette[pal].color[ColBG]); XFillRectangle(dpy, dc.drawable, dc.gc, 0, 0, mon->win_geom.w, bar_height); return; } -void drawbar(struct monitor *m) +static int workspace_is_urgent(struct workspace *workspace) { - int x; - unsigned int i, occ = 0, urg = 0; - unsigned long *col; - struct client *c; - - resizebarwin(m); - - for(c = m->clients; c; c = c->next) { - occ |= c->tags; + struct client *client; - if(c->isurgent) { - urg |= c->tags; + for(client = workspace->clients; client; client = client->next) { + if(client->isurgent) { + return(TRUE); } } - wipe_bar(m, m == selmon ? dc.sel : dc.norm); + return(FALSE); +} + +void drawbar(struct monitor *m) +{ + struct client *focused; + unsigned int i; + int x; + + monitor_resize_barwin(m); + wipe_bar(m, m == selmon ? PalSelected : PalNormal); + focused = selmon->workspace->focused; dc.geom.x = 0; for(i = 0; i < LENGTH(tags); i++) { + int pal; + int urgent; + dc.geom.w = TEXTW(tags[i]); - col = m->tagset[m->seltags] & 1 << i ? dc.sel : dc.norm; - drawtext(tags[i], col, urg & 1 << i); - drawsquare(m == selmon && selmon->sel && selmon->sel->tags & 1 << i, - occ & 1 << i, urg & 1 << i, col); + urgent = workspace_is_urgent(&workspaces[i]); + + if(!workspaces[i].viewer) { + pal = PalNormal; + } else if(workspaces[i].viewer == m) { + pal = m == selmon ? PalSelected : PalNotSelected; + } else { + pal = PalOther; + } + + drawtext(tags[i], pal, urgent); + drawsquare(m == selmon && focused && focused->workspace->num == i, + workspaces[i].clients ? TRUE : FALSE, urgent, pal); dc.geom.x += dc.geom.w; } dc.geom.w = TEXTW(m->ltsymbol); bar_lwidth = dc.geom.w; - drawtext(m->ltsymbol, dc.norm, False); + drawtext(m->ltsymbol, PalNormal, False); dc.geom.x += dc.geom.w; x = dc.geom.x; @@ -1072,7 +1168,7 @@ void drawbar(struct monitor *m) dc.geom.w = m->win_geom.w - x; } - drawtext(stext, dc.norm, False); + drawtext(stext, PalNormal, False); XCopyArea(dpy, dc.drawable, m->barwin, dc.gc, 0, 0, m->win_geom.w, bar_height, 0, 0); @@ -1092,12 +1188,13 @@ void drawbars(void) return; } -void drawsquare(Bool filled, Bool empty, Bool invert, unsigned long col[ColLast]) +void drawsquare(Bool filled, Bool empty, Bool invert, int palette) { int x; - XSetForeground(dpy, dc.gc, col[invert ? ColBG : ColFG]); + XSetForeground(dpy, dc.gc, dc.palette[palette].color[invert ? ColBG : ColFG]); x = (dc.font.ascent + dc.font.descent + 2) / 4; + if(filled) { XFillRectangle(dpy, dc.drawable, dc.gc, dc.geom.x + 1, dc.geom.y + 1, @@ -1111,12 +1208,12 @@ void drawsquare(Bool filled, Bool empty, Bool invert, unsigned long col[ColLast] return; } -void drawtext(const char *text, unsigned long col[ColLast], Bool invert) +void drawtext(const char *text, int pal, Bool invert) { char buf[512]; int i, x, y, h, len, olen; - XSetForeground(dpy, dc.gc, col[invert ? ColFG : ColBG]); + XSetForeground(dpy, dc.gc, dc.palette[pal].color[invert ? ColFG : ColBG]); XFillRectangle(dpy, dc.drawable, dc.gc, dc.geom.x, dc.geom.y, dc.geom.w, dc.geom.h); @@ -1132,7 +1229,7 @@ void drawtext(const char *text, unsigned long col[ColLast], Bool invert) /* shorten text if necessary (this could wreak havoc with pango markup but fortunately dc.w is adjusted to the width of the status text and not the other way around) */ - for(len = MIN(olen, sizeof buf); len && textnw(text, len) > dc.geom.w - h; len--); + for(len = MIN(olen, sizeof(buf)); len && textnw(text, len) > dc.geom.w - h; len--); if(len <= 0) { return; @@ -1144,16 +1241,16 @@ void drawtext(const char *text, unsigned long col[ColLast], Bool invert) for(i = len; i && i > len - 3; buf[--i] = '.'); } - if(text == stext && statusmarkup) { + /* FIXME: Remove statusmarkup */ + if(text == stext && statusmarkup) { /* FIXME: stext? */ pango_layout_set_markup(dc.font.layout, buf, len); } else { pango_layout_set_text(dc.font.layout, buf, len); } - pango_xft_render_layout(dc.xft.drawable, - (col == dc.norm ? dc.xft.norm : dc.xft.sel) + - (invert ? ColBG : ColFG), + pango_xft_render_layout(dc.draw, &dc.palette[pal].xcolor[invert ? ColBG : ColFG], dc.font.layout, x * PANGO_SCALE, y * PANGO_SCALE); + if(text == stext && statusmarkup) { /* clear markup attributes */ pango_layout_set_attributes(dc.font.layout, NULL); @@ -1174,12 +1271,12 @@ void enternotify(XEvent *e) } c = wintoclient(ev->window); - m = c ? c->mon : wintomon(ev->window); + m = c ? c->workspace->viewer : wintomon(ev->window); if(m != selmon) { - unfocus(selmon->sel, True); + unfocus(selmon->workspace->focused, True); selmon = m; - } else if(!c || c == selmon->sel) { + } else if(!c || c == selmon->workspace->focused) { return; } @@ -1202,34 +1299,33 @@ void expose(XEvent *e) void focus(struct client *c) { + struct client *focused; + if(!c || !ISVISIBLE(c)) { - for(c = selmon->stack; c && !ISVISIBLE(c); c = c->snext); + for(c = selmon->workspace->clients; c && !ISVISIBLE(c); c = c->next); } - /* was if(selmon->sel) */ - if(selmon->sel && selmon->sel != c) { - unfocus(selmon->sel, False); + focused = selmon->workspace->focused; + + if(focused && focused != c) { + unfocus(focused, False); } if(c) { - if(c->mon != selmon) { - selmon = c->mon; - } + selmon = c->workspace->viewer; if(c->isurgent) { clearurgent(c); } - detachstack(c); - attachstack(c); grabbuttons(c, True); - XSetWindowBorder(dpy, c->win, dc.sel[ColBorder]); + XSetWindowBorder(dpy, c->win, dc.palette[PalSelected].color[ColBorder]); setfocus(c); } else { XSetInputFocus(dpy, root, RevertToPointerRoot, CurrentTime); } - selmon->sel = c; + selmon->workspace->focused = c; drawbars(); return; @@ -1238,10 +1334,14 @@ void focus(struct client *c) void focusin(XEvent *e) { /* there are some broken focus acquiring clients */ - XFocusChangeEvent *ev = &e->xfocus; + XFocusChangeEvent *ev; + struct client *focused; + + ev = &e->xfocus; + focused = selmon->workspace->focused; - if(selmon->sel && ev->window != selmon->sel->win) { - setfocus(selmon->sel); + if(focused && ev->window != focused->win) { + setfocus(focused); } return; @@ -1250,6 +1350,7 @@ void focusin(XEvent *e) void focusmon(const union arg *arg) { struct monitor *m; + union arg ptrdst; if(!mons->next) { return; @@ -1259,28 +1360,38 @@ void focusmon(const union arg *arg) return; } - unfocus(selmon->sel, True); + unfocus(selmon->workspace->focused, True); selmon = m; focus(NULL); + ptrdst.ui = KBPTR_CENTER; + kbptr_move(&ptrdst); + return; } void focusstack(const union arg *arg) { - struct client *c = NULL, *i; + struct client *focused; + struct client *c; + + focused = selmon->workspace->focused; + c = NULL; - if(!selmon->sel) { + if(!focused) { return; } if(arg->i > 0) { - for(c = selmon->sel->next; c && !ISVISIBLE(c); c = c->next); + for(c = focused->next; c && !ISVISIBLE(c); c = c->next); + if(!c) { - for(c = selmon->clients; c && !ISVISIBLE(c); c = c->next); + for(c = selmon->workspace->clients; c && !ISVISIBLE(c); c = c->next); } } else { - for(i = selmon->clients; i != selmon->sel; i = i->next) { + struct client *i; + + for(i = selmon->workspace->clients; i != focused; i = i->next) { if(ISVISIBLE(i)) { c = i; } @@ -1296,8 +1407,13 @@ void focusstack(const union arg *arg) } if(c) { + union arg arg; + focus(c); restack(selmon); + + arg.ui = KBPTR_CENTER; + kbptr_move(&arg); } return; @@ -1530,13 +1646,17 @@ void keypress(XEvent *e) void killclient(const union arg *arg) { - if(selmon->sel && !sendevent(selmon->sel->win, wmatom[WMDelete], - NoEventMask, wmatom[WMDelete], - CurrentTime, 0 , 0, 0)) { + struct client *focused; + + focused = selmon->workspace->focused; + + if(focused && !sendevent(focused->win, wmatom[WMDelete], + NoEventMask, wmatom[WMDelete], + CurrentTime, 0 , 0, 0)) { XGrabServer(dpy); XSetErrorHandler(xerrordummy); XSetCloseDownMode(dpy, DestroyAll); - XKillClient(dpy, selmon->sel->win); + XKillClient(dpy, focused->win); XSync(dpy, False); XSetErrorHandler(xerror); XUngrabServer(dpy); @@ -1549,6 +1669,7 @@ void manage(Window w, XWindowAttributes *wa) { struct client *c; struct client *t; + struct monitor *mon; Window trans; XWindowChanges wc; @@ -1560,17 +1681,16 @@ void manage(Window w, XWindowAttributes *wa) } c->win = w; - updatetitle(c); + client_update_title(c); if(XGetTransientForHint(dpy, w, &trans) && (t = wintoclient(trans))) { - c->mon = t->mon; - c->tags = t->tags; + c->workspace->viewer = t->workspace->viewer; } else { - c->mon = selmon; - applyrules(c); + client_apply_rules(c); } - /* geometry */ + mon = c->workspace->viewer; + c->geom.x = wa->x; c->geom.y = wa->y; c->geom.w = wa->width; @@ -1579,28 +1699,30 @@ void manage(Window w, XWindowAttributes *wa) c->border.oldw = wa->border_width; - if(c->geom.x + client_get_width(c) > c->mon->geom.x + c->mon->geom.w) { - c->geom.x = c->mon->geom.x + c->mon->geom.w - client_get_width(c); + if(c->geom.x + client_get_width(c) > mon->geom.x + mon->geom.w) { + c->geom.x = mon->geom.x + mon->geom.w - client_get_width(c); } - if(c->geom.y + client_get_height(c) > c->mon->geom.y + c->mon->geom.h) { - c->geom.y = c->mon->geom.y + c->mon->geom.h - client_get_height(c); + if(c->geom.y + client_get_height(c) > mon->geom.y + mon->geom.h) { + c->geom.y = mon->geom.y + mon->geom.h - client_get_height(c); } - c->geom.x = MAX(c->geom.x, c->mon->geom.x); + c->geom.x = MAX(c->geom.x, mon->geom.x); /* only fix client y-offset, if the client center might cover the bar */ - c->geom.y = MAX(c->geom.y, (c->mon->by == c->mon->geom.y && - (c->geom.x + (c->geom.w / 2) >= c->mon->win_geom.x) && - (c->geom.x + (c->geom.w / 2) < c->mon->win_geom.x + - c->mon->win_geom.w)) ? bar_height : c->mon->geom.y); + c->geom.y = MAX(c->geom.y, (mon->by == mon->geom.y && + (c->geom.x + (c->geom.w / 2) >= mon->win_geom.x) && + (c->geom.x + (c->geom.w / 2) < mon->win_geom.x + + mon->win_geom.w)) ? bar_height : mon->geom.y); c->border.w = borderpx; wc.border_width = c->border.w; XConfigureWindow(dpy, w, CWBorderWidth, &wc); - XSetWindowBorder(dpy, w, dc.norm[ColBorder]); - configure(c); /* propagates border_width, if size doesn't change */ - updatewindowtype(c); - updatesizehints(c); - updatewmhints(c); + XSetWindowBorder(dpy, w, dc.palette[PalNormal].color[ColBorder]); + + client_configure(c); /* propagates border_width, if size doesn't change */ + client_update_window_type(c); + client_update_size_hints(c); + client_update_wm_hints(c); + XSelectInput(dpy, w, EnterWindowMask | FocusChangeMask | PropertyChangeMask | StructureNotifyMask); grabbuttons(c, False); @@ -1613,20 +1735,19 @@ void manage(Window w, XWindowAttributes *wa) XRaiseWindow(dpy, c->win); } - attach(c); - attachstack(c); + workspace_add_client(mon->workspace, c); /* some windows require this */ XMoveResizeWindow(dpy, c->win, c->geom.x + 2 * screen_width, c->geom.y, c->geom.w, c->geom.h); setclientstate(c, NormalState); - if (c->mon == selmon) { - unfocus(selmon->sel, False); + if(mon == selmon) { + unfocus(selmon->workspace->focused, False); } - c->mon->sel = c; - arrange(c->mon); + c->workspace->focused = c; + workspace_update(mon->workspace); XMapWindow(dpy, c->win); focus(NULL); @@ -1667,7 +1788,7 @@ void monocle(struct monitor *m) unsigned int n = 0; struct client *c; - for(c = m->clients; c; c = c->next) { + for(c = m->workspace->clients; c; c = c->next) { if(ISVISIBLE(c)) { n++; } @@ -1677,14 +1798,14 @@ void monocle(struct monitor *m) snprintf(m->ltsymbol, sizeof m->ltsymbol, "[%d]", n); } - for(c = nexttiled(m->clients); c; c = nexttiled(c->next)) { + for(c = nexttiled(m->workspace->clients); c; c = nexttiled(c->next)) { int border; border = 2 + c->border.w; - resize(c, m->win_geom.x, m->win_geom.y, - m->win_geom.w - border, m->win_geom.h - border, - False); + client_resize_hints(c, m->win_geom.x, m->win_geom.y, + m->win_geom.w - border, m->win_geom.h - border, + False); } return; @@ -1716,7 +1837,7 @@ void movemouse(const union arg *arg) struct monitor *m; XEvent ev; - if(!(c = selmon->sel)) { + if(!(c = selmon->workspace->focused)) { return; } @@ -1759,14 +1880,17 @@ void movemouse(const union arg *arg) client_get_height(c); } - if(!c->isfloating && selmon->lt[selmon->sellt]->arrange && + if(!c->isfloating && !monitor_is_floating(selmon) && (abs(nx - c->geom.x) > snap || abs(ny - c->geom.y) > snap)) { togglefloating(NULL); } } - if(!selmon->lt[selmon->sellt]->arrange || c->isfloating) - resize(c, nx, ny, c->geom.w, c->geom.h, True); + + if(monitor_is_floating(selmon) || c->isfloating) { + client_resize_hints(c, nx, ny, c->geom.w, c->geom.h, True); + } + break; } } while(ev.type != ButtonRelease); @@ -1793,16 +1917,6 @@ struct client *nexttiled(struct client *c) return(c); } -void pop(struct client *c) -{ - detach(c); - attach(c); - focus(c); - arrange(c->mon); - - return; -} - void propertynotify(XEvent *e) { struct client *c; @@ -1823,26 +1937,26 @@ void propertynotify(XEvent *e) case XA_WM_TRANSIENT_FOR: if(!c->isfloating && (XGetTransientForHint(dpy, c->win, &trans)) && (c->isfloating = (wintoclient(trans)) != NULL)) - arrange(c->mon); + workspace_update(c->workspace); break; case XA_WM_NORMAL_HINTS: - updatesizehints(c); + client_update_size_hints(c); break; case XA_WM_HINTS: - updatewmhints(c); + client_update_wm_hints(c); drawbars(); break; } if(ev->atom == XA_WM_NAME || ev->atom == netatom[NetWMName]) { - updatetitle(c); + client_update_title(c); drawbars(); } if(ev->atom == netatom[NetWMWindowType]) { - updatewindowtype(c); + client_update_window_type(c); } } @@ -1900,23 +2014,23 @@ struct monitor *recttomon(int x, int y, int w, int h) return(r); } -void resize(struct client *c, int x, int y, int w, int h, Bool interact) +void client_resize_hints(struct client *c, int x, int y, int w, int h, Bool interact) { - if(applysizehints(c, &x, &y, &w, &h, interact)) { - resizeclient(c, x, y, w, h); + if(client_apply_size_hints(c, &x, &y, &w, &h, interact)) { + client_resize(c, x, y, w, h); } return; } -void resizebarwin(struct monitor *m) +void monitor_resize_barwin(struct monitor *m) { XMoveResizeWindow(dpy, m->barwin, m->win_geom.x, m->by, m->win_geom.w, bar_height); return; } -void resizeclient(struct client *c, int x, int y, int w, int h) +void client_resize(struct client *c, int x, int y, int w, int h) { XWindowChanges wc; @@ -1934,7 +2048,7 @@ void resizeclient(struct client *c, int x, int y, int w, int h) wc.border_width = c->border.w; XConfigureWindow(dpy, c->win, CWX | CWY | CWWidth | CWHeight | CWBorderWidth, &wc); - configure(c); + client_configure(c); XSync(dpy, False); return; @@ -1948,7 +2062,7 @@ void resizemouse(const union arg *arg) struct monitor *m; XEvent ev; - if(!(c = selmon->sel)) { + if(!(c = selmon->workspace->focused)) { return; } @@ -1965,6 +2079,8 @@ void resizemouse(const union arg *arg) c->geom.w + c->border.w - 1, c->geom.h + c->border.w - 1); + m = c->workspace->viewer; + do { XMaskEvent(dpy, MOUSEMASK | ExposureMask | SubstructureRedirectMask, &ev); @@ -1977,20 +2093,19 @@ void resizemouse(const union arg *arg) case MotionNotify: nw = MAX(ev.xmotion.x - ocx - 2 * c->border.w + 1, 1); nh = MAX(ev.xmotion.y - ocy - 2 * c->border.w + 1, 1); - if(c->mon->win_geom.x + nw >= selmon->win_geom.x && - c->mon->win_geom.x + nw <= (selmon->win_geom.x + - selmon->win_geom.w) && - c->mon->win_geom.y + nh >= selmon->win_geom.y && - c->mon->win_geom.y + nh <= (selmon->win_geom.y + - selmon->win_geom.h)) { - if(!c->isfloating && selmon->lt[selmon->sellt]->arrange - && (abs(nw - c->geom.w) > snap || - abs(nh - c->geom.h) > snap)) { + if(m->win_geom.x + nw >= selmon->win_geom.x && + m->win_geom.x + nw <= (selmon->win_geom.x + + selmon->win_geom.w) && + m->win_geom.y + nh >= selmon->win_geom.y && + m->win_geom.y + nh <= (selmon->win_geom.y + + selmon->win_geom.h)) { + if(!c->isfloating && !monitor_is_floating(selmon) && + (abs(nw - c->geom.w) > snap || abs(nh - c->geom.h) > snap)) { togglefloating(NULL); } } - if(!selmon->lt[selmon->sellt]->arrange || c->isfloating) { - resize(c, c->geom.x, c->geom.y, nw, nh, True); + if(monitor_is_floating(selmon) || c->isfloating) { + client_resize_hints(c, c->geom.x, c->geom.y, nw, nh, True); } break; @@ -2015,29 +2130,34 @@ void resizemouse(const union arg *arg) return; } -void restack(struct monitor *m) +void restack(struct monitor *monitor) { - struct client *c; + struct client *focused; XEvent ev; XWindowChanges wc; - drawbar(m); + drawbar(monitor); + + focused = monitor->workspace->focused; - if(!m->sel) { + if(!focused) { return; } - if(m->sel->isfloating || !m->lt[m->sellt]->arrange) { - XRaiseWindow(dpy, m->sel->win); + if(focused->isfloating || monitor_is_floating(monitor)) { + XRaiseWindow(dpy, focused->win); } - if(m->lt[m->sellt]->arrange) { + if(!monitor_is_floating(monitor)) { + struct client *client; + wc.stack_mode = Below; - wc.sibling = m->barwin; - for(c = m->stack; c; c = c->snext) { - if(!c->isfloating && ISVISIBLE(c)) { - XConfigureWindow(dpy, c->win, CWSibling|CWStackMode, &wc); - wc.sibling = c->win; + wc.sibling = monitor->barwin; + + for(client = monitor->workspace->clients; client; client = client->next) { + if(!client->isfloating && ISVISIBLE(client)) { + XConfigureWindow(dpy, client->win, CWSibling|CWStackMode, &wc); + wc.sibling = client->win; } } } @@ -2100,30 +2220,24 @@ void scan(void) return; } -void sendmon(struct client *c, struct monitor *m) +void sendmon(struct client *client, struct monitor *monitor) { - if(c->mon == m) { + if(client->workspace == monitor->workspace) { return; } - unfocus(c, True); - detach(c); - detachstack(c); - c->mon = m; - c->tags = m->tagset[m->seltags]; /* assign tags of target monitor */ - attach(c); - attachstack(c); + client_move_to_workspace(client, monitor->workspace); focus(NULL); - arrange(NULL); + workspace_update(NULL); return; } -void setclientstate(struct client *c, long state) +void setclientstate(struct client *client, long state) { long data[] = { state, None }; - XChangeProperty(dpy, c->win, wmatom[WMState], wmatom[WMState], 32, + XChangeProperty(dpy, client->win, wmatom[WMState], wmatom[WMState], 32, PropModeReplace, (unsigned char*)data, 2); return; @@ -2224,11 +2338,11 @@ static void kbptr_move_on(struct client *client, const union arg *arg) static void kbptr_move(const union arg *arg) { - if(!selmon || !selmon->sel) { + if(!selmon || !selmon->workspace->focused) { return; } - kbptr_move_on(selmon->sel, arg); + kbptr_move_on(selmon->workspace->focused, arg); return; } @@ -2302,11 +2416,11 @@ static void kbptr_click(const union arg *arg) { struct client *client; - if(!selmon || !selmon->sel) { + if(!selmon || !selmon->workspace->focused) { return; } - client = selmon->sel; + client = selmon->workspace->focused; do_button(client, arg->ui, ButtonPress); usleep(100000); @@ -2315,46 +2429,43 @@ static void kbptr_click(const union arg *arg) return; } -void setfocus(struct client *c) +void setfocus(struct client *client) { - union arg ptrdst = { .ui = KBPTR_CENTER }; - - if(!c->neverfocus) { - XSetInputFocus(dpy, c->win, RevertToPointerRoot, CurrentTime); + if(!client->neverfocus) { + XSetInputFocus(dpy, client->win, RevertToPointerRoot, CurrentTime); } - sendevent(c->win, wmatom[WMTakeFocus], NoEventMask, + sendevent(client->win, wmatom[WMTakeFocus], NoEventMask, wmatom[WMTakeFocus], CurrentTime, 0, 0, 0); - kbptr_move_on(c, &ptrdst); - return; } -void setfullscreen(struct client *c, Bool fullscreen) +void setfullscreen(struct client *client, Bool fullscreen) { if(fullscreen) { - XChangeProperty(dpy, c->win, netatom[NetWMState], + XChangeProperty(dpy, client->win, netatom[NetWMState], XA_ATOM, 32, PropModeReplace, (unsigned char*)&netatom[NetWMFullscreen], 1); - c->isfullscreen = True; - c->oldstate = c->isfloating; - c->border.oldw = c->border.w; - c->border.w = 0; - c->isfloating = True; - resizeclient(c, c->mon->geom.x, c->mon->geom.y, - c->mon->geom.w, c->mon->geom.h); - XRaiseWindow(dpy, c->win); + client->isfullscreen = True; + client->oldstate = client->isfloating; + client->border.oldw = client->border.w; + client->border.w = 0; + client->isfloating = True; + client_resize(client, client->workspace->viewer->geom.x, client->workspace->viewer->geom.y, + client->workspace->viewer->geom.w, client->workspace->viewer->geom.h); + XRaiseWindow(dpy, client->win); } else { - XChangeProperty(dpy, c->win, netatom[NetWMState], + XChangeProperty(dpy, client->win, netatom[NetWMState], XA_ATOM, 32, PropModeReplace, (unsigned char*)0, 0); - c->isfullscreen = False; - c->isfloating = c->oldstate; - c->border.w = c->border.oldw; - memcpy(&(c->geom), &(c->old_geom), sizeof(c->geom)); - resizeclient(c, c->geom.x, c->geom.y, c->geom.w, c->geom.h); - arrange(c->mon); + client->isfullscreen = False; + client->isfloating = client->oldstate; + client->border.w = client->border.oldw; + memcpy(&(client->geom), &(client->old_geom), sizeof(client->geom)); + client_resize(client, client->geom.x, client->geom.y, + client->geom.w, client->geom.h); + workspace_update(client->workspace); } return; @@ -2373,8 +2484,8 @@ void setlayout(const union arg *arg) strncpy(selmon->ltsymbol, selmon->lt[selmon->sellt]->symbol, sizeof(selmon->ltsymbol)); - if(selmon->sel) { - arrange(selmon); + if(selmon->workspace->focused) { + workspace_update(selmon->workspace); } else { drawbar(selmon); } @@ -2382,9 +2493,25 @@ void setlayout(const union arg *arg) return; } -void setup(void) +static void palette_init(struct palette *pal, struct color_set *set) +{ + pal->color[ColBorder] = getcolor(set->border, &pal->xcolor[ColBorder]); + pal->color[ColBG] = getcolor(set->background, &pal->xcolor[ColBG]); + pal->color[ColFG] = getcolor(set->foreground, &pal->xcolor[ColFG]); + + return; +} + +static void setup(void) { XSetWindowAttributes wa; + int i; + + memset(workspaces, 0, sizeof(workspaces)); + + for(i = 0; i < LENGTH(workspaces); i++) { + workspaces[i].num = i; + } /* clean up any zombies immediately */ sigchld(0); @@ -2427,15 +2554,13 @@ void setup(void) cursor[CurMove] = XCreateFontCursor(dpy, XC_fleur); /* init appearance */ - dc.norm[ColBorder] = getcolor(normbordercolor, dc.xft.norm + ColBorder); - dc.norm[ColBG] = getcolor(normbgcolor, dc.xft.norm + ColBG); - dc.norm[ColFG] = getcolor(normfgcolor, dc.xft.norm + ColFG); - dc.sel[ColBorder] = getcolor(selbordercolor, dc.xft.sel + ColBorder); - dc.sel[ColBG] = getcolor(selbgcolor, dc.xft.sel + ColBG); - dc.sel[ColFG] = getcolor(selfgcolor, dc.xft.sel + ColFG); + for(i = 0; i < PalLast; i++) { + palette_init(&dc.palette[i], &theme[i]); + } + dc.drawable = XCreatePixmap(dpy, root, DisplayWidth(dpy, screen), bar_height, DefaultDepth(dpy, screen)); - dc.xft.drawable = XftDrawCreate(dpy, dc.drawable, DefaultVisual(dpy, screen), + dc.draw = XftDrawCreate(dpy, dc.drawable, DefaultVisual(dpy, screen), DefaultColormap(dpy, screen)); dc.gc = XCreateGC(dpy, root, 0, NULL); XSetLineAttributes(dpy, dc.gc, 1, LineSolid, CapButt, JoinMiter); @@ -2459,24 +2584,39 @@ void setup(void) return; } -void showhide(struct client *c) +static int monitor_is_floating(struct monitor *mon) { - if(!c) { + if(mon->lt[mon->sellt]->arrange) { + return(FALSE); + } + + return(TRUE); +} + +void showhide(struct client *client) +{ + struct monitor *monitor; + + if(!client) { return; } - if(ISVISIBLE(c)) { /* show clients top down */ - XMoveWindow(dpy, c->win, c->geom.x, c->geom.y); + monitor = client->workspace->viewer; + + if(monitor) { /* show clients top down */ + XMoveWindow(dpy, client->win, client->geom.x, client->geom.y); - if((!c->mon->lt[c->mon->sellt]->arrange || c->isfloating) && - !c->isfullscreen) { - resize(c, c->geom.x, c->geom.y, c->geom.w, c->geom.h, False); + if((monitor_is_floating(monitor) || client->isfloating) && !client->isfullscreen) { + client_resize_hints(client, client->geom.x, client->geom.y, + client->geom.w, client->geom.h, False); } - showhide(c->snext); + showhide(client->next); } else { /* hide clients bottom up */ - showhide(c->snext); - XMoveWindow(dpy, c->win, client_get_width(c) * -2, c->geom.y); + showhide(client->next); + + /* FIXME: Shouldn't XUnmapWindow() be used here? */ + XMoveWindow(dpy, client->win, client_get_width(client) * -2, client->geom.y); } return; @@ -2511,27 +2651,56 @@ void spawn(const union arg *arg) return; } -void tag(const union arg *arg) +static void client_move_to_workspace(struct client *client, struct workspace *workspace) +{ + if(client->workspace) { + workspace_remove_client(client->workspace, client); + } + + workspace_add_client(workspace, client); + + return; +} + +static void move_focused(const union arg *arg) +{ + struct client *focused; + + focused = selmon->workspace->focused; + + if(focused) { + client_move_to_workspace(focused, &workspaces[arg->ui]); + focus(NULL); + workspace_update(selmon->workspace); + workspace_update(&workspaces[arg->ui]); + } + + return; +} + +#if 0 +static void tag(const union arg *arg) { if(selmon->sel && arg->ui & TAGMASK) { selmon->sel->tags = arg->ui & TAGMASK; focus(NULL); - arrange(selmon); + workspace_update(selmon->workspace); } return; } +#endif -void tagmon(const union arg *arg) +static void tagmon(const union arg *arg) { - if(selmon->sel && mons->next) { - sendmon(selmon->sel, dirtomon(arg->i)); + if(selmon->workspace->focused && mons->next) { + sendmon(selmon->workspace->focused, dirtomon(arg->i)); } return; } -int textnw(const char *text, unsigned int len) +static int textnw(const char *text, unsigned int len) { PangoRectangle r; @@ -2551,12 +2720,12 @@ int textnw(const char *text, unsigned int len) return(r.width / PANGO_SCALE); } -static int count_tiled_clients(struct monitor *mon) +static int count_tiled_clients(struct workspace *workspace) { struct client *tiled; int n; - tiled = nexttiled(mon->clients); + tiled = nexttiled(workspace->clients); n = 0; while(tiled) { @@ -2567,14 +2736,14 @@ static int count_tiled_clients(struct monitor *mon) return(n); } -static void bookshelf(struct monitor *m) +static void bookshelf(struct monitor *monitor) { int n, w, x; int extraw; - struct client *c; + struct client *client; - n = count_tiled_clients(m); + n = count_tiled_clients(monitor->workspace); if(!n) { /* nothing to do */ @@ -2589,65 +2758,65 @@ static void bookshelf(struct monitor *m) * first implemented this on a laptop and things broke when I * plugged in a second screen at work. */ - w = m->win_geom.w / n; - x = m->win_geom.x; + w = monitor->win_geom.w / n; + x = monitor->win_geom.x; /* make the first window a bit larger if the width can't be divided evenly */ - extraw = m->win_geom.w - (w * n); + extraw = monitor->win_geom.w - (w * n); /* assign positions and sizes to all clients on this screen */ - for(c = nexttiled(m->clients); c; c = nexttiled(c->next)) { + for(client = nexttiled(monitor->workspace->clients); client; client = nexttiled(client->next)) { int border; - border = 2 * c->border.w; - c->incw = 0; - c->inch = 0; + border = 2 * client->border.w; + client->incw = 0; + client->inch = 0; - resize(c, x, m->win_geom.y, w + extraw - border, - m->win_geom.h - border, False); + client_resize_hints(client, x, monitor->win_geom.y, w + extraw - border, + monitor->win_geom.h - border, False); /* * Since all clients are horizontally aligned and the * same size, only the x coordinate changes. */ - x += client_get_width(c); + x += client_get_width(client); extraw = 0; } return; } -static void bookstack(struct monitor *m) +static void bookstack(struct monitor *monitor) { int n, h, y; int extrah; - struct client *c; + struct client *client; /* * The logic is essentially the same as in bookshelf(), though * much more vertical, literally speaking. */ - n = count_tiled_clients(m); + n = count_tiled_clients(monitor->workspace); if(!n) { return; } - h = m->win_geom.h / n; - y = m->win_geom.y; - extrah = m->win_geom.h - (h * n); + h = monitor->win_geom.h / n; + y = monitor->win_geom.y; + extrah = monitor->win_geom.h - (h * n); - for(c = nexttiled(m->clients); c; c = nexttiled(c->next)) { + for(client = nexttiled(monitor->workspace->clients); client; client = nexttiled(client->next)) { int border; - border = 2 * c->border.w; - c->incw = 0; - c->inch = 0; + border = 2 * client->border.w; + client->incw = 0; + client->inch = 0; - resize(c, m->win_geom.x, y, m->win_geom.w - border, - h + extrah - border, False); - y += client_get_height(c); + client_resize_hints(client, monitor->win_geom.x, y, monitor->win_geom.w - border, + h + extrah - border, False); + y += client_get_height(client); extrah = 0; } @@ -2656,62 +2825,34 @@ static void bookstack(struct monitor *m) void togglefloating(const union arg *arg) { - if(!selmon->sel) { - return; - } - - selmon->sel->isfloating = !selmon->sel->isfloating || selmon->sel->isfixed; - - if(selmon->sel->isfloating) { - resize(selmon->sel, selmon->sel->geom.x, selmon->sel->geom.y, - selmon->sel->geom.w, selmon->sel->geom.h, False); - } + struct client *focused; - arrange(selmon); - - return; -} - -void toggletag(const union arg *arg) -{ - unsigned int newtags; + focused = selmon->workspace->focused; - if(!selmon->sel) { + if(!focused) { return; } - newtags = selmon->sel->tags ^ (arg->ui & TAGMASK); + focused->isfloating = !focused->isfloating || focused->isfixed; - if(newtags) { - selmon->sel->tags = newtags; - focus(NULL); - arrange(selmon); + if(focused->isfloating) { + client_resize_hints(focused, focused->geom.x, focused->geom.y, + focused->geom.w, focused->geom.h, False); } - return; -} - -void toggleview(const union arg *arg) -{ - unsigned int newtagset = selmon->tagset[selmon->seltags] ^ (arg->ui & TAGMASK); - - if(newtagset) { - selmon->tagset[selmon->seltags] = newtagset; - focus(NULL); - arrange(selmon); - } + workspace_update(selmon->workspace); return; } -void unfocus(struct client *c, Bool setfocus) +void unfocus(struct client *client, Bool setfocus) { - if(!c) { + if(!client) { return; } - grabbuttons(c, False); - XSetWindowBorder(dpy, c->win, dc.norm[ColBorder]); + grabbuttons(client, False); + XSetWindowBorder(dpy, client->win, dc.palette[PalNormal].color[ColBorder]); if(setfocus) { XSetInputFocus(dpy, root, RevertToPointerRoot, CurrentTime); @@ -2720,44 +2861,47 @@ void unfocus(struct client *c, Bool setfocus) return; } -void unmanage(struct client *c, Bool destroyed) +void unmanage(struct client *client, Bool destroyed) { - struct monitor *m = c->mon; + struct monitor *monitor; XWindowChanges wc; + monitor = client->workspace->viewer; + /* The server grab construct avoids race conditions. */ - detach(c); - detachstack(c); + detach(client); if(!destroyed) { - wc.border_width = c->border.oldw; + wc.border_width = client->border.oldw; XGrabServer(dpy); XSetErrorHandler(xerrordummy); - XConfigureWindow(dpy, c->win, CWBorderWidth, &wc); /* restore border */ - XUngrabButton(dpy, AnyButton, AnyModifier, c->win); - setclientstate(c, WithdrawnState); + XConfigureWindow(dpy, client->win, CWBorderWidth, &wc); /* restore border */ + XUngrabButton(dpy, AnyButton, AnyModifier, client->win); + setclientstate(client, WithdrawnState); XSync(dpy, False); XSetErrorHandler(xerror); XUngrabServer(dpy); } - free(c); + free(client); focus(NULL); - arrange(m); + workspace_update(monitor->workspace); return; } -void unmapnotify(XEvent *e) +void unmapnotify(XEvent *event) { - struct client *c; - XUnmapEvent *ev = &e->xunmap; + struct client *client; + XUnmapEvent *ev; - if((c = wintoclient(ev->window))) { + ev = &event->xunmap; + + if((client = wintoclient(ev->window))) { if(ev->send_event) { - setclientstate(c, WithdrawnState); + setclientstate(client, WithdrawnState); } else { - unmanage(c, False); + unmanage(client, False); } } @@ -2766,37 +2910,36 @@ void unmapnotify(XEvent *e) void updatebars(void) { - unsigned int w; - struct monitor *m; + struct monitor *monitor; - XSetWindowAttributes wa = { + XSetWindowAttributes attrs = { .override_redirect = True, .background_pixmap = ParentRelative, .event_mask = ButtonPressMask|ExposureMask }; - for(m = mons; m; m = m->next) { - w = m->win_geom.w; - - m->barwin = XCreateWindow(dpy, root, m->win_geom.x, m->by, - w, bar_height, 0, DefaultDepth(dpy, screen), - CopyFromParent, DefaultVisual(dpy, screen), - CWOverrideRedirect|CWBackPixmap|CWEventMask, &wa); + for(monitor = mons; monitor; monitor = monitor->next) { + monitor->barwin = XCreateWindow(dpy, root, monitor->win_geom.x, monitor->by, + monitor->win_geom.w, bar_height, 0, + DefaultDepth(dpy, screen), CopyFromParent, + DefaultVisual(dpy, screen), + CWOverrideRedirect | CWBackPixmap | CWEventMask, + &attrs); - XDefineCursor(dpy, m->barwin, cursor[CurNormal]); - XMapRaised(dpy, m->barwin); + XDefineCursor(dpy, monitor->barwin, cursor[CurNormal]); + XMapRaised(dpy, monitor->barwin); } return; } -void updatebarpos(struct monitor *m) +void updatebarpos(struct monitor *monitor) { - m->win_geom.y = m->geom.y; - m->win_geom.h = m->geom.h; - m->win_geom.h -= bar_height; - m->by = m->topbar ? m->win_geom.y : m->win_geom.y + m->win_geom.h; - m->win_geom.y = m->topbar ? m->win_geom.y + bar_height : m->win_geom.y; + monitor->win_geom.y = monitor->geom.y; + monitor->win_geom.h = monitor->geom.h; + monitor->win_geom.h -= bar_height; + monitor->by = monitor->topbar ? monitor->win_geom.y : monitor->win_geom.y + monitor->win_geom.h; + monitor->win_geom.y = monitor->topbar ? monitor->win_geom.y + bar_height : monitor->win_geom.y; return; } @@ -2805,15 +2948,12 @@ Bool updategeom(void) { Bool dirty = False; -#ifdef XINERAMA if(XineramaIsActive(dpy)) { int i, j, n, nn; - struct client *c; - struct monitor *m; XineramaScreenInfo *info = XineramaQueryScreens(dpy, &nn); XineramaScreenInfo *unique = NULL; - for(n = 0, m = mons; m; m = m->next, n++); + n = num_monitors; /* only consider unique geometries as separate screens */ if(!(unique = (XineramaScreenInfo *)malloc(sizeof(*unique) * nn))) { @@ -2830,6 +2970,8 @@ Bool updategeom(void) nn = j; if(n <= nn) { + struct monitor *monitor; + for(i = 0; i < (nn - n); i++) { /* new monitors available */ struct monitor **ptr; @@ -2837,51 +2979,40 @@ Bool updategeom(void) *ptr = createmon(); } - for(i = 0, m = mons; i < nn && m; m = m->next, i++) { - if(i >= n || (unique[i].x_org != m->geom.x || - unique[i].y_org != m->geom.y || - unique[i].width != m->geom.w || - unique[i].height != m->geom.h)) { + for(i = 0, monitor = mons; i < nn && monitor; monitor = monitor->next, i++) { + if(i >= n || (unique[i].x_org != monitor->geom.x || + unique[i].y_org != monitor->geom.y || + unique[i].width != monitor->geom.w || + unique[i].height != monitor->geom.h)) { dirty = True; - m->num = i; - m->geom.x = unique[i].x_org; - m->geom.y = unique[i].y_org; - m->geom.w = unique[i].width; - m->geom.h = unique[i].height; + monitor->num = i; + monitor->geom.x = unique[i].x_org; + monitor->geom.y = unique[i].y_org; + monitor->geom.w = unique[i].width; + monitor->geom.h = unique[i].height; - memcpy(&(m->win_geom), &(m->geom), - sizeof(m->win_geom)); + memcpy(&(monitor->win_geom), &(monitor->geom), + sizeof(monitor->win_geom)); - updatebarpos(m); + updatebarpos(monitor); } } } else { /* less monitors available nn < n */ - for(i = nn; i < n; i++) { - for(m = mons; m && m->next; m = m->next); + struct monitor *monitor; - while(m->clients) { - dirty = True; - c = m->clients; - m->clients = c->next; - detachstack(c); - c->mon = mons; - attach(c); - attachstack(c); - } + for(i = nn; i < n; i++) { + for(monitor = mons; monitor && monitor->next; monitor = monitor->next); - if(m == selmon) { + if(monitor == selmon) { selmon = mons; } - cleanupmon(m); + monitor_free(monitor); } } free(unique); - } else -#endif /* XINERAMA */ - /* default monitor setup */ - { + } else { if(!mons) { mons = createmon(); } @@ -2924,72 +3055,72 @@ void updatenumlockmask(void) return; } -void updatesizehints(struct client *c) +void client_update_size_hints(struct client *client) { long msize; XSizeHints size; - if(!XGetWMNormalHints(dpy, c->win, &size, &msize)) { + if(!XGetWMNormalHints(dpy, client->win, &size, &msize)) { /* size is uninitialized, ensure that size.flags aren't used */ size.flags = PSize; } if(size.flags & PBaseSize) { - c->basew = size.base_width; - c->baseh = size.base_height; + client->basew = size.base_width; + client->baseh = size.base_height; } else if(size.flags & PMinSize) { - c->basew = size.min_width; - c->baseh = size.min_height; + client->basew = size.min_width; + client->baseh = size.min_height; } else { - c->basew = c->baseh = 0; + client->basew = client->baseh = 0; } if(size.flags & PResizeInc) { - c->incw = size.width_inc; - c->inch = size.height_inc; + client->incw = size.width_inc; + client->inch = size.height_inc; } else { - c->incw = c->inch = 0; + client->incw = client->inch = 0; } if(size.flags & PMaxSize) { - c->maxw = size.max_width; - c->maxh = size.max_height; + client->maxw = size.max_width; + client->maxh = size.max_height; } else { - c->maxw = c->maxh = 0; + client->maxw = client->maxh = 0; } if(size.flags & PMinSize) { - c->minw = size.min_width; - c->minh = size.min_height; + client->minw = size.min_width; + client->minh = size.min_height; } else if(size.flags & PBaseSize) { - c->minw = size.base_width; - c->minh = size.base_height; + client->minw = size.base_width; + client->minh = size.base_height; } else { - c->minw = c->minh = 0; + client->minw = client->minh = 0; } if(size.flags & PAspect) { - c->mina = (float)size.min_aspect.y / size.min_aspect.x; - c->maxa = (float)size.max_aspect.x / size.max_aspect.y; + client->mina = (float)size.min_aspect.y / size.min_aspect.x; + client->maxa = (float)size.max_aspect.x / size.max_aspect.y; } else { - c->maxa = c->mina = 0.0; + client->maxa = client->mina = 0.0; } - c->isfixed = (c->maxw && c->minw && c->maxh && c->minh - && c->maxw == c->minw && c->maxh == c->minh); + client->isfixed = (client->maxw && client->minw && client->maxh && client->minh && + client->maxw == client->minw && client->maxh == client->minh); return; } -void updatetitle(struct client *c) +void client_update_title(struct client *client) { - if(!gettextprop(c->win, netatom[NetWMName], c->name, sizeof c->name)) { - gettextprop(c->win, XA_WM_NAME, c->name, sizeof c->name); + if(!gettextprop(client->win, netatom[NetWMName], client->name, sizeof(client->name))) { + gettextprop(client->win, XA_WM_NAME, client->name, sizeof(client->name)); } - if(c->name[0] == '\0') { + if(client->name[0] == '\0') { /* hack to mark broken clients */ - strcpy(c->name, broken); + strcpy(client->name, broken); } return; @@ -3006,73 +3137,110 @@ void updatestatus(void) return; } -void updatewindowtype(struct client *c) +void client_update_window_type(struct client *client) { - Atom state = getatomprop(c, netatom[NetWMState]); - Atom wtype = getatomprop(c, netatom[NetWMWindowType]); + Atom state = getatomprop(client, netatom[NetWMState]); + Atom wtype = getatomprop(client, netatom[NetWMWindowType]); if(state == netatom[NetWMFullscreen]) { - setfullscreen(c, True); + setfullscreen(client, True); } if(wtype == netatom[NetWMWindowTypeDialog]) { - c->isfloating = True; + client->isfloating = True; } return; } -void updatewmhints(struct client *c) +void client_update_wm_hints(struct client *client) { - XWMHints *wmh; + XWMHints *hints; - if((wmh = XGetWMHints(dpy, c->win))) { - if(c == selmon->sel && wmh->flags & XUrgencyHint) { - wmh->flags &= ~XUrgencyHint; - XSetWMHints(dpy, c->win, wmh); + if((hints = XGetWMHints(dpy, client->win))) { + if(client == selmon->workspace->focused && hints->flags & XUrgencyHint) { + hints->flags &= ~XUrgencyHint; + XSetWMHints(dpy, client->win, hints); } else { - c->isurgent = (wmh->flags & XUrgencyHint) ? True : False; + client->isurgent = (hints->flags & XUrgencyHint) ? True : False; } - if(wmh->flags & InputHint) { - c->neverfocus = !wmh->input; - } else { - c->neverfocus = False; - } + client->neverfocus = (hints->flags & InputHint) ? !hints->input : False; - XFree(wmh); + XFree(hints); } return; } -void view(const union arg *arg) +static void monitor_swap_workspace(struct monitor *mona, struct monitor *monb) { - if((arg->ui & TAGMASK) == selmon->tagset[selmon->seltags]) { + struct workspace *swap; + + mona->previous_workspace = mona->workspace; + monb->previous_workspace = monb->workspace; + + swap = mona->workspace; + mona->workspace = monb->workspace; + monb->workspace = swap; + + mona->workspace->viewer = mona; + monb->workspace->viewer = monb; + + workspace_update(NULL); + + return; +} + +static void monitor_show_workspace(struct monitor *mon, struct workspace *workspace) +{ + mon->workspace->viewer = NULL; + mon->previous_workspace = mon->workspace; + mon->workspace = workspace; + workspace->viewer = mon; + + workspace_update(NULL); + + return; +} + +static void show_workspace(const union arg *arg) +{ + struct workspace *target; + struct workspace *current; + + target = arg->ui < LENGTH(workspaces) ? &workspaces[arg->ui] : selmon->previous_workspace; + current = workspace_get_current(); + + if(current == target) { return; } - selmon->seltags ^= 1; /* toggle sel tagset */ - - if(arg->ui & TAGMASK) { - selmon->tagset[selmon->seltags] = arg->ui & TAGMASK; + if(target->viewer) { + /* + * Target workspace is visible on another monitor. We + * can't have two monitors show the same workspace, so + * we swap workspaces instead. + */ + monitor_swap_workspace(selmon, target->viewer); + } else { + monitor_show_workspace(selmon, target); } - arrange(selmon); focus(NULL); return; } -struct client *wintoclient(Window w) +struct client *wintoclient(Window window) { - struct client *c; - struct monitor *m; + struct client *client; + struct monitor *monitor; - for(m = mons; m; m = m->next) { - for(c = m->clients; c; c = c->next) { - if(c->win == w) { - return(c); + for(monitor = mons; monitor; monitor = monitor->next) { + for(client = monitor->workspace->clients; client; client = client->next) { + if(client->win == window) { + return(client); } } } @@ -3080,24 +3248,24 @@ struct client *wintoclient(Window w) return(NULL); } -struct monitor *wintomon(Window w) +struct monitor *wintomon(Window window) { + struct client *client; + struct monitor *monitor; int x, y; - struct client *c; - struct monitor *m; - if(w == root && getrootptr(&x, &y)) { + if(window == root && getrootptr(&x, &y)) { return(recttomon(x, y, 1, 1)); } - for(m = mons; m; m = m->next) { - if(w == m->barwin) { - return(m); + for(monitor = mons; monitor; monitor = monitor->next) { + if(window == monitor->barwin) { + return(monitor); } } - if((c = wintoclient(w))) { - return(c->mon); + if((client = wintoclient(window))) { + return(client->workspace->viewer); } return(selmon); @@ -3159,31 +3327,12 @@ int xerrorstart(Display *dpy, XErrorEvent *ee) return(-1); } -void zoom(const union arg *arg) -{ - struct client *c = selmon->sel; - - if(!selmon->lt[selmon->sellt]->arrange || - (selmon->sel && selmon->sel->isfloating)) { - return; - } - - if(c == nexttiled(selmon->clients)) { - if(!c || !(c = nexttiled(c->next))){ - return; - } - } - - pop(c); - return; -} - int main(int argc, char *argv[]) { if(argc == 2 && !strcmp("-v", argv[1])) { die("d "VERSION", © 2020-2021 Matthias Kruk, based on dwm-6.0\n"); } else if(argc != 1) { - die("usage: dwm [-v]\n"); + die("usage: d [-v]\n"); } if(!setlocale(LC_CTYPE, "") || !XSupportsLocale()) { -- 2.47.3