]> git.corax.cc Git - dwm/commitdiff
Introduce workspaces to replace tags experimental workspaces
authorMatthias Kruk <m@m10k.eu>
Tue, 27 Apr 2021 21:45:52 +0000 (06:45 +0900)
committerMatthias Kruk <m@m10k.eu>
Thu, 29 Apr 2021 08:10:23 +0000 (17:10 +0900)
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
dwm.c

index 415600e6afd30932d47ce18e373193485ed4e4c4..b8b628a6e5a97beff5c951b9a82e64bfb94184e5 100755 (executable)
@@ -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 30ecdd1f32091bc27dde75187b35efdd5dd4dd72..bdcebfb2d59f67315af26711ead4784d56da9039 100755 (executable)
--- a/dwm.c
+++ b/dwm.c
  * 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().
 #include <X11/Xft/Xft.h>
 #include <pango/pango.h>
 #include <pango/pangoxft.h>
-#ifdef XINERAMA
 #include <X11/extensions/Xinerama.h>
-#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()) {