]> code.delx.au - spectrwm/blobdiff - spectrwm.c
Add ability to set tile_gap to negative values.
[spectrwm] / spectrwm.c
index 059c2ac5182ab271ca5d3a6f71feca0b7fed9b5c..00304b547c8c8256fee343cc45d9bf3924241f1a 100644 (file)
@@ -5,10 +5,10 @@
  * Copyright (c) 2009 Pierre-Yves Ritschard <pyr@spootnik.org>
  * Copyright (c) 2010 Tuukka Kataja <stuge@xor.fi>
  * Copyright (c) 2011 Jason L. Wright <jason@thought.net>
- * Copyright (c) 2011-2012 Reginald Kennedy <rk@rejii.com>
+ * Copyright (c) 2011-2013 Reginald Kennedy <rk@rejii.com>
  * Copyright (c) 2011-2012 Lawrence Teo <lteo@lteo.net>
  * Copyright (c) 2011-2012 Tiago Cunha <tcunha@gmx.com>
- * Copyright (c) 2012 David Hill <dhill@mindcry.org>
+ * Copyright (c) 2012-2013 David Hill <dhill@mindcry.org>
  *
  * Permission to use, copy, modify, and distribute this software for any
  * purpose with or without fee is hereby granted, provided that the above
@@ -243,9 +243,19 @@ u_int32_t          swm_debug = 0
 #define SH_INC_W(w)            (w)->sh.width_inc
 #define SH_INC_H(w)            (w)->sh.height_inc
 #define SWM_MAX_FONT_STEPS     (3)
-#define WINID(w)               ((w) ? (w)->id : 0)
+#define WINID(w)               ((w) ? (w)->id : XCB_WINDOW_NONE)
 #define YESNO(x)               ((x) ? "yes" : "no")
 
+/* Constrain Window flags */
+#define SWM_CW_RESIZABLE       (0x01)
+#define SWM_CW_SOFTBOUNDARY    (0x02)
+#define SWM_CW_HARDBOUNDARY    (0x04)
+#define SWM_CW_RIGHT           (0x10)
+#define SWM_CW_LEFT            (0x20)
+#define SWM_CW_BOTTOM          (0x40)
+#define SWM_CW_TOP             (0x80)
+#define SWM_CW_ALLSIDES                (0xf0)
+
 #define SWM_FOCUS_DEFAULT      (0)
 #define SWM_FOCUS_FOLLOW       (1)
 #define SWM_FOCUS_MANUAL       (2)
@@ -288,6 +298,7 @@ Display                     *display;
 xcb_connection_t       *conn;
 xcb_key_symbols_t      *syms;
 
+int                    boundary_width = 50;
 int                    cycle_empty = 0;
 int                    cycle_visible = 0;
 int                    term_width = 0;
@@ -577,6 +588,8 @@ union arg {
 #define SWM_ARG_ID_CYCLERG_DOWN        (43)
 #define SWM_ARG_ID_CYCLEWS_UP_ALL      (44)
 #define SWM_ARG_ID_CYCLEWS_DOWN_ALL    (45)
+#define SWM_ARG_ID_CYCLEWS_MOVE_UP     (46)
+#define SWM_ARG_ID_CYCLEWS_MOVE_DOWN   (47)
 #define SWM_ARG_ID_STACKINC    (50)
 #define SWM_ARG_ID_STACKDEC    (51)
 #define SWM_ARG_ID_SS_ALL      (60)
@@ -831,8 +844,10 @@ enum keyfuncid {
        KF_WS_22,
        KF_WS_NEXT,
        KF_WS_NEXT_ALL,
+       KF_WS_NEXT_MOVE,
        KF_WS_PREV,
        KF_WS_PREV_ALL,
+       KF_WS_PREV_MOVE,
        KF_WS_PRIOR,
        KF_DUMPWINS, /* MUST BE LAST */
        KF_INVALID
@@ -879,7 +894,7 @@ int  conf_load(const char *, int);
 void    configurenotify(xcb_configure_notify_event_t *);
 void    configurerequest(xcb_configure_request_event_t *);
 void    config_win(struct ws_win *, xcb_configure_request_event_t *);
-void    constrain_window(struct ws_win *, struct swm_region *, int);
+void    constrain_window(struct ws_win *, struct swm_geometry *, int *);
 int     count_win(struct workspace *, int);
 void    cursors_cleanup(void);
 void    cursors_load(void);
@@ -932,6 +947,8 @@ struct ws_win       *get_pointer_win(xcb_window_t);
 struct ws_win  *get_region_focus(struct swm_region *);
 int     get_region_index(struct swm_region *);
 xcb_screen_t   *get_screen(int);
+xcb_window_t    get_sibling(struct ws_win *, int);
+int     get_screen_count(void);
 #ifdef SWM_DEBUG
 char   *get_stack_mode_name(uint8_t);
 #endif
@@ -956,16 +973,16 @@ void       kill_refs(struct ws_win *);
 #ifdef SWM_DEBUG
 void    leavenotify(xcb_leave_notify_event_t *);
 #endif
-void    load_float_geom(struct ws_win *, struct swm_region *);
+void    load_float_geom(struct ws_win *);
 struct ws_win  *manage_window(xcb_window_t, uint16_t);
-void    map_window(struct ws_win *);
+void    map_window(struct ws_win *, xcb_window_t);
 void    mapnotify(xcb_map_notify_event_t *);
 void    mappingnotify(xcb_mapping_notify_event_t *);
 void    maprequest(xcb_map_request_event_t *);
 void    motionnotify(xcb_motion_notify_event_t *);
 void    move(struct ws_win *, union arg *);
 void    move_step(struct swm_region *, union arg *);
-uint32_t name_to_pixel(const char *);
+uint32_t name_to_pixel(int, const char *);
 void    name_workspace(struct swm_region *, union arg *);
 void    new_region(struct swm_screen *, int, int, int, int);
 int     parsekeys(char *, unsigned int, unsigned int *, KeySym *);
@@ -983,6 +1000,9 @@ void        quirk_replace(struct quirk *, const char *, const char *,
             unsigned long);
 void    quit(struct swm_region *, union arg *);
 void    raise_toggle(struct swm_region *, union arg *);
+void    region_containment(struct ws_win *, struct swm_region *, int);
+struct swm_region      *region_under(struct swm_screen *, int, int);
+void    regionize(struct ws_win *, int, int);
 void    resize(struct ws_win *, union arg *);
 void    resize_step(struct swm_region *, union arg *);
 void    restart(struct swm_region *, union arg *);
@@ -990,10 +1010,10 @@ struct swm_region        *root_to_region(xcb_window_t, int);
 void    screenchange(xcb_randr_screen_change_notify_event_t *);
 void    scan_xrandr(int);
 void    search_do_resp(void);
-void    search_resp_name_workspace(const char *, unsigned long);
+void    search_resp_name_workspace(const char *, size_t);
 void    search_resp_search_window(const char *);
 void    search_resp_search_workspace(const char *);
-void    search_resp_uniconify(const char *, unsigned long);
+void    search_resp_uniconify(const char *, size_t);
 void    search_win(struct swm_region *, union arg *);
 void    search_win_cleanup(void);
 void    search_workspace(struct swm_region *, union arg *);
@@ -1037,7 +1057,7 @@ void       spawn_select(struct swm_region *, union arg *, const char *, int *);
 void    stack_config(struct swm_region *, union arg *);
 void    stack_floater(struct ws_win *, struct swm_region *);
 void    stack_master(struct workspace *, struct swm_geometry *, int, int);
-void    store_float_geom(struct ws_win *, struct swm_region *);
+void    store_float_geom(struct ws_win *);
 char   *strdupsafe(const char *);
 void    swapwin(struct swm_region *, union arg *);
 void    switchws(struct swm_region *, union arg *);
@@ -1056,6 +1076,7 @@ int        validate_win(struct ws_win *);
 int     validate_ws(struct workspace *);
 /*void  visibilitynotify(xcb_visibility_notify_event_t *);*/
 void    version(struct swm_region *, union arg *);
+void    win_to_ws(struct ws_win *, int, int);
 pid_t   window_get_pid(xcb_window_t);
 void    wkill(struct swm_region *, union arg *);
 void    workaround(void);
@@ -1186,6 +1207,19 @@ get_screen(int screen)
        return (NULL);
 }
 
+int
+get_screen_count(void)
+{
+       const xcb_setup_t       *r;
+
+       if ((r = xcb_get_setup(conn)) == NULL) {
+               DNPRINTF(SWM_D_MISC, "get_screen_count: xcb_get_setup\n");
+               check_conn();
+       }
+
+       return xcb_setup_roots_length(r);
+}
+
 int
 get_region_index(struct swm_region *r)
 {
@@ -1280,7 +1314,7 @@ setup_ewmh(void)
        for (i = 0; i < LENGTH(ewmh); i++)
                ewmh[i].atom = get_atom_from_string(ewmh[i].name);
 
-       num_screens = xcb_setup_roots_length(xcb_get_setup(conn));
+       num_screens = get_screen_count();
        for (i = 0; i < num_screens; i++) {
                /* Support check window will be created by workaround(). */
 
@@ -1304,7 +1338,7 @@ teardown_ewmh(void)
 
        sup_check = get_atom_from_string("_NET_SUPPORTING_WM_CHECK");
        sup_list = get_atom_from_string("_NET_SUPPORTED");
-       num_screens = xcb_setup_roots_length(xcb_get_setup(conn));
+       num_screens = get_screen_count();
 
        for (i = 0; i < num_screens; i++) {
                /* Get the support check window and destroy it */
@@ -1383,12 +1417,12 @@ ewmh_set_win_fullscreen(struct ws_win *win, int fs)
 
        if (fs) {
                if (!win->g_floatvalid)
-                       store_float_geom(win, win->ws->r);
+                       store_float_geom(win);
 
                win->g = win->ws->r->g;
                win->bordered = 0;
        } else {
-               load_float_geom(win, win->ws->r);
+               load_float_geom(win);
        }
 
        return (1);
@@ -1653,7 +1687,7 @@ find_pid(pid_t pid)
 }
 
 uint32_t
-name_to_pixel(const char *colorname)
+name_to_pixel(int sidx, const char *colorname)
 {
        uint32_t                        result = 0;
        char                            cname[32] = "#";
@@ -1663,7 +1697,7 @@ name_to_pixel(const char *colorname)
        xcb_alloc_named_color_reply_t   *nr;
        uint16_t                        rr, gg, bb;
 
-       screen = xcb_setup_roots_iterator(xcb_get_setup(conn)).data;
+       screen = get_screen(sidx);
        cmap = screen->default_colormap;
 
        /* color is in format rgb://rr/gg/bb */
@@ -1705,15 +1739,15 @@ setscreencolor(char *val, int i, int c)
 {
        int     num_screens;
 
-       num_screens = xcb_setup_roots_length(xcb_get_setup(conn));
+       num_screens = get_screen_count();
        if (i > 0 && i <= num_screens) {
-               screens[i - 1].c[c].pixel = name_to_pixel(val);
+               screens[i - 1].c[c].pixel = name_to_pixel(i - 1, val);
                free(screens[i - 1].c[c].name);
                if ((screens[i - 1].c[c].name = strdup(val)) == NULL)
                        err(1, "strdup");
        } else if (i == -1) {
                for (i = 0; i < num_screens; i++) {
-                       screens[i].c[c].pixel = name_to_pixel(val);
+                       screens[i].c[c].pixel = name_to_pixel(0, val);
                        free(screens[i].c[c].name);
                        if ((screens[i].c[c].name = strdup(val)) == NULL)
                                err(1, "strdup");
@@ -1756,7 +1790,7 @@ custom_region(char *val)
        int                             sidx, num_screens;
        xcb_screen_t                    *screen;
 
-       num_screens = xcb_setup_roots_length(xcb_get_setup(conn));
+       num_screens = get_screen_count();
        if (sscanf(val, "screen[%d]:%ux%u+%u+%u", &sidx, &w, &h, &x, &y) != 5)
                errx(1, "invalid custom region, "
                    "should be 'screen[<n>]:<n>x<n>+<n>+<n>");
@@ -1971,12 +2005,12 @@ bar_window_name(char *s, size_t sz, struct swm_region *r)
        free(title);
 }
 
-int            urgent[SWM_WS_MAX];
 void
 bar_urgent(char *s, size_t sz)
 {
        struct ws_win           *win;
        int                     i, j, num_screens;
+       int                     urgent[SWM_WS_MAX];
        char                    b[8];
        xcb_get_property_cookie_t       c;
        xcb_icccm_wm_hints_t    hints;
@@ -1984,7 +2018,7 @@ bar_urgent(char *s, size_t sz)
        for (i = 0; i < workspace_limit; i++)
                urgent[i] = 0;
 
-       num_screens = xcb_setup_roots_length(xcb_get_setup(conn));
+       num_screens = get_screen_count();
        for (i = 0; i < num_screens; i++)
                for (j = 0; j < workspace_limit; j++)
                        TAILQ_FOREACH(win, &screens[i].ws[j].winlist, entry) {
@@ -2234,7 +2268,7 @@ bar_draw(void)
        /* expand the format by first passing it through strftime(3) */
        bar_fmt_expand(fmtexp, sizeof fmtexp);
 
-       num_screens = xcb_setup_roots_length(xcb_get_setup(conn));
+       num_screens = get_screen_count();
        for (i = 0; i < num_screens; i++) {
                TAILQ_FOREACH(r, &screens[i].rl, entry) {
                        if (r->bar == NULL)
@@ -2338,7 +2372,7 @@ bar_toggle(struct swm_region *r, union arg *args)
        }
 
        /* update bars as necessary */
-       num_screens = xcb_setup_roots_length(xcb_get_setup(conn));
+       num_screens = get_screen_count();
        for (i = 0; i < num_screens; i++)
                TAILQ_FOREACH(tmpr, &screens[i].rl, entry)
                        if (tmpr->bar) {
@@ -2779,18 +2813,27 @@ quit(struct swm_region *r, union arg *args)
 }
 
 void
-map_window(struct ws_win *win)
+map_window(struct ws_win *win, xcb_window_t sibling)
 {
-       uint32_t        val = XCB_STACK_MODE_ABOVE;
+       uint16_t                mode = XCB_CONFIG_WINDOW_STACK_MODE;
+       uint32_t                val[2];
+       int                     i = 0;
+
+       /* If sibling is specified, stack right above it. */
+       if (sibling != XCB_WINDOW_NONE) {
+               mode |= XCB_CONFIG_WINDOW_SIBLING;
+               val[i++] = sibling;
+       }
+
+       val[i] = XCB_STACK_MODE_ABOVE;
 
        if (win == NULL)
                return;
 
-       DNPRINTF(SWM_D_EVENT, "map_window: win 0x%x, mapped: %s\n", win->id,
-           YESNO(win->mapped));
+       DNPRINTF(SWM_D_EVENT, "map_window: win 0x%x, mapped: %s, "
+           "sibling: 0x%x\n", win->id, YESNO(win->mapped), sibling);
 
-       xcb_configure_window(conn, win->id,
-           XCB_CONFIG_WINDOW_STACK_MODE, &val);
+       xcb_configure_window(conn, win->id, mode, val);
 
        if (win->mapped)
                return;
@@ -2823,7 +2866,7 @@ unmap_all(void)
        struct ws_win           *win;
        int                     i, j, num_screens;
 
-       num_screens = xcb_setup_roots_length(xcb_get_setup(conn));
+       num_screens = get_screen_count();
        for (i = 0; i < num_screens; i++)
                for (j = 0; j < workspace_limit; j++)
                        TAILQ_FOREACH(win, &screens[i].ws[j].winlist, entry)
@@ -2918,7 +2961,7 @@ root_to_region(xcb_window_t root, int check)
 
        DNPRINTF(SWM_D_MISC, "root_to_region: window: 0x%x\n", root);
 
-       num_screens = xcb_setup_roots_length(xcb_get_setup(conn));
+       num_screens = get_screen_count();
        for (i = 0; i < num_screens; i++)
                if (screens[i].root == root)
                        break;
@@ -2961,6 +3004,8 @@ root_to_region(xcb_window_t root, int check)
        if (r == NULL && check & SWM_CK_FALLBACK)
                r = TAILQ_FIRST(&screens[i].rl);
 
+       DNPRINTF(SWM_D_MISC, "root_to_region: idx: %d\n", get_region_index(r));
+
        return (r);
 }
 
@@ -2970,7 +3015,7 @@ find_unmanaged_window(xcb_window_t id)
        struct ws_win           *win;
        int                     i, j, num_screens;
 
-       num_screens = xcb_setup_roots_length(xcb_get_setup(conn));
+       num_screens = get_screen_count();
        for (i = 0; i < num_screens; i++)
                for (j = 0; j < workspace_limit; j++)
                        TAILQ_FOREACH(win, &screens[i].ws[j].unmanagedlist,
@@ -2987,7 +3032,7 @@ find_window(xcb_window_t id)
        int                     i, j, num_screens;
        xcb_query_tree_reply_t  *r;
 
-       num_screens = xcb_setup_roots_length(xcb_get_setup(conn));
+       num_screens = get_screen_count();
        for (i = 0; i < num_screens; i++)
                for (j = 0; j < workspace_limit; j++)
                        TAILQ_FOREACH(win, &screens[i].ws[j].winlist, entry)
@@ -3082,7 +3127,7 @@ kill_refs(struct ws_win *win)
        if (win == NULL)
                return;
 
-       num_screens = xcb_setup_roots_length(xcb_get_setup(conn));
+       num_screens = get_screen_count();
        for (i = 0; i < num_screens; i++)
                TAILQ_FOREACH(r, &screens[i].rl, entry)
                        for (x = 0; x < workspace_limit; x++) {
@@ -3105,7 +3150,7 @@ validate_win(struct ws_win *testwin)
        if (testwin == NULL)
                return (0);
 
-       num_screens = xcb_setup_roots_length(xcb_get_setup(conn));
+       num_screens = get_screen_count();
        for (i = 0; i < num_screens; i++)
                TAILQ_FOREACH(r, &screens[i].rl, entry)
                        for (x = 0; x < workspace_limit; x++) {
@@ -3125,7 +3170,7 @@ validate_ws(struct workspace *testws)
        int                     i, x, num_screens;
 
        /* validate all ws */
-       num_screens = xcb_setup_roots_length(xcb_get_setup(conn));
+       num_screens = get_screen_count();
        for (i = 0; i < num_screens; i++)
                TAILQ_FOREACH(r, &screens[i].rl, entry)
                        for (x = 0; x < workspace_limit; x++) {
@@ -3136,6 +3181,42 @@ validate_ws(struct workspace *testws)
        return (1);
 }
 
+xcb_window_t
+get_sibling(struct ws_win *win, int mode)
+{
+       struct ws_win           *w = win;
+
+       switch (mode) {
+       case SWM_STACK_TOP:
+               TAILQ_FOREACH_REVERSE(w, &w->ws->winlist, ws_win_list, entry)
+                       if (w != win && !w->floating && !w->iconic)
+                               break;
+               break;
+       case SWM_STACK_ABOVE:
+               do {
+                       w = TAILQ_NEXT(w, entry);
+               } while (w != NULL && (w == win || w->floating || w->iconic));
+               break;
+       case SWM_STACK_BELOW:
+               do {
+                       w = TAILQ_PREV(w, ws_win_list, entry);
+               } while (w != NULL && (w == win || w->floating || w->iconic));
+               break;
+       case SWM_STACK_BOTTOM:
+               TAILQ_FOREACH(w, &w->ws->winlist, entry)
+                       if (w != win && !w->floating && !w->iconic)
+                               break;
+               break;
+       default:
+               w = NULL;
+       }
+
+       if (w == NULL)
+               return (win->ws->r->id);
+       else
+               return (w->id);
+}
+
 void
 unfocus_win(struct ws_win *win)
 {
@@ -3167,6 +3248,8 @@ unfocus_win(struct ws_win *win)
        }
 
        if (win->ws->focus == win) {
+               if (tile_gap < 0 && !win->floating)
+                       map_window(win, get_sibling(win, SWM_STACK_BELOW));
                win->ws->focus = NULL;
                win->ws->focus_prev = win;
        }
@@ -3268,22 +3351,30 @@ focus_win(struct ws_win *win)
                    ws->always_raise) {
                        /* If a parent exists, map it first. */
                        if (parent) {
-                               map_window(parent);
+                               map_window(parent, XCB_WINDOW_NONE);
 
                                /* Map siblings next. */
                                TAILQ_FOREACH(w, &ws->winlist, entry)
                                        if (w != win && !w->iconic &&
                                            w->transient == parent->id)
-                                               map_window(w);
+                                               map_window(w, XCB_WINDOW_NONE);
                        }
 
                        /* Map focused window. */
-                       map_window(win);
+                       map_window(win, XCB_WINDOW_NONE);
 
                        /* Finally, map children of focus window. */
                        TAILQ_FOREACH(w, &ws->winlist, entry)
                                if (w->transient == win->id && !w->iconic)
-                                       map_window(w);
+                                       map_window(w, XCB_WINDOW_NONE);
+               } else if (tile_gap < 0 && !win->floating) {
+                       /*
+                        * Windows overlap in the layout.
+                        * Raise focused win above all tiled wins.
+                        */
+                       if (tile_gap < 0 && !win->floating)
+                               map_window(win,
+                                   get_sibling(win, SWM_STACK_TOP));
                }
 
                set_region(ws->r);
@@ -3394,7 +3485,7 @@ focus_region(struct swm_region *r)
                if (old_r)
                        unfocus_win(old_r->ws->focus);
 
-               xcb_set_input_focus(conn, XCB_INPUT_FOCUS_PARENT, r->s->root,
+               xcb_set_input_focus(conn, XCB_INPUT_FOCUS_PARENT, r->id,
                    XCB_CURRENT_TIME);
 
                /* Clear bar since empty. */
@@ -3445,8 +3536,9 @@ switchws(struct swm_region *r, union arg *args)
        this_r->ws = new_ws;
        new_ws->r = this_r;
 
-       /* Set focus_pending before stacking. */
-       if (focus_mode != SWM_FOCUS_FOLLOW)
+       /* Set focus_pending before stacking, if needed. */
+       if (focus_mode != SWM_FOCUS_FOLLOW && (!new_ws->focus_pending ||
+           validate_win(new_ws->focus_pending)))
                new_ws->focus_pending = get_region_focus(new_ws->r);
 
        stack();
@@ -3460,17 +3552,16 @@ switchws(struct swm_region *r, union arg *args)
        if (old_ws->r && focus_mode != SWM_FOCUS_FOLLOW) {
                if (new_ws->focus_pending) {
                        focus_win(new_ws->focus_pending);
-               } else {
-                       /* Empty region, focus on root. */
-                       xcb_set_input_focus(conn, XCB_INPUT_FOCUS_PARENT,
-                           new_ws->r->s[new_ws->r->s->idx].root,
-                           XCB_CURRENT_TIME);
+                       new_ws->focus_pending = NULL;
                }
        }
 
-       /* Clear bar if new ws is empty. */
-       if (new_ws->focus_pending == NULL)
+       /* Clear bar and set focus on region input win if new ws is empty. */
+       if (new_ws->focus_pending == NULL && new_ws->focus == NULL) {
+               xcb_set_input_focus(conn, XCB_INPUT_FOCUS_PARENT, r->id,
+                   XCB_CURRENT_TIME);
                bar_draw();
+       }
 
        focus_flush();
 
@@ -3483,30 +3574,32 @@ cyclews(struct swm_region *r, union arg *args)
        union                   arg a;
        struct swm_screen       *s = r->s;
        int                     cycle_all = 0;
+       int                     move = 0;
 
        DNPRINTF(SWM_D_WS, "cyclews: id: %d, screen[%d]:%dx%d+%d+%d, ws: %d\n",
            args->id, r->s->idx, WIDTH(r), HEIGHT(r), X(r), Y(r), r->ws->idx);
 
        a.id = r->ws->idx;
+
        do {
                switch (args->id) {
+               case SWM_ARG_ID_CYCLEWS_MOVE_UP:
+                       move = 1;
+                       /* FALLTHROUGH */
                case SWM_ARG_ID_CYCLEWS_UP_ALL:
                        cycle_all = 1;
                        /* FALLTHROUGH */
                case SWM_ARG_ID_CYCLEWS_UP:
-                       if (a.id < workspace_limit - 1)
-                               a.id++;
-                       else
-                               a.id = 0;
+                       a.id = (a.id < workspace_limit - 1) ? a.id + 1 : 0;
                        break;
+               case SWM_ARG_ID_CYCLEWS_MOVE_DOWN:
+                       move = 1;
+                       /* FALLTHROUGH */
                case SWM_ARG_ID_CYCLEWS_DOWN_ALL:
                        cycle_all = 1;
                        /* FALLTHROUGH */
                case SWM_ARG_ID_CYCLEWS_DOWN:
-                       if (a.id > 0)
-                               a.id--;
-                       else
-                               a.id = workspace_limit - 1;
+                       a.id = (a.id > 0) ? a.id - 1 : workspace_limit - 1;
                        break;
                default:
                        return;
@@ -3518,6 +3611,9 @@ cyclews(struct swm_region *r, union arg *args)
                if (!cycle_visible && s->ws[a.id].r != NULL)
                        continue;
 
+               if (move)
+                       send_to_ws(r, &a);
+
                switchws(r, &a);
        } while (a.id != r->ws->idx);
 }
@@ -3545,7 +3641,7 @@ focusrg(struct swm_region *r, union arg *args)
        int                     ridx = args->id, i, num_screens;
        struct swm_region       *rr = NULL;
 
-       num_screens = xcb_setup_roots_length(xcb_get_setup(conn));
+       num_screens = get_screen_count();
        /* do nothing if we don't have more than one screen */
        if (!(num_screens > 1 || outputs > 1))
                return;
@@ -3569,7 +3665,7 @@ cyclerg(struct swm_region *r, union arg *args)
        struct swm_region       *rr = NULL;
        int                     i, num_screens;
 
-       num_screens = xcb_setup_roots_length(xcb_get_setup(conn));
+       num_screens = get_screen_count();
        /* do nothing if we don't have more than one screen */
        if (!(num_screens > 1 || outputs > 1))
                return;
@@ -3972,7 +4068,7 @@ stack(void) {
 
        DNPRINTF(SWM_D_STACK, "stack: begin\n");
 
-       num_screens = xcb_setup_roots_length(xcb_get_setup(conn));
+       num_screens = get_screen_count();
        for (i = 0; i < num_screens; i++) {
 #ifdef SWM_DEBUG
                j = 0;
@@ -4005,15 +4101,15 @@ stack(void) {
 }
 
 void
-store_float_geom(struct ws_win *win, struct swm_region *r)
+store_float_geom(struct ws_win *win)
 {
-       if (win == NULL || r == NULL)
+       if (win == NULL || win->ws->r == NULL)
                return;
 
        /* retain window geom and region geom */
        win->g_float = win->g;
-       win->g_float.x -= X(r);
-       win->g_float.y -= Y(r);
+       win->g_float.x -= X(win->ws->r);
+       win->g_float.y -= Y(win->ws->r);
        win->g_floatvalid = 1;
        DNPRINTF(SWM_D_MISC, "store_float_geom: window: 0x%x, g: (%d,%d)"
            " %d x %d, g_float: (%d,%d) %d x %d\n", win->id, X(win), Y(win),
@@ -4022,15 +4118,15 @@ store_float_geom(struct ws_win *win, struct swm_region *r)
 }
 
 void
-load_float_geom(struct ws_win *win, struct swm_region *r)
+load_float_geom(struct ws_win *win)
 {
-       if (win == NULL || r == NULL)
+       if (win == NULL || win->ws->r == NULL)
                return;
 
        if (win->g_floatvalid) {
                win->g = win->g_float;
-               X(win) += X(r);
-               Y(win) += Y(r);
+               X(win) += X(win->ws->r);
+               Y(win) += Y(win->ws->r);
                DNPRINTF(SWM_D_MISC, "load_float_geom: window: 0x%x, g: (%d,%d)"
                    "%d x %d\n", win->id, X(win), Y(win), WIDTH(win),
                    HEIGHT(win));
@@ -4055,7 +4151,7 @@ stack_floater(struct ws_win *win, struct swm_region *r)
        if (win->floatmaxed || (r != r->ws->old_r &&
            !(win->ewmh_flags & EWMH_F_FULLSCREEN))) {
                /* update geometry for the new region */
-               load_float_geom(win, r);
+               load_float_geom(win);
        }
 
        win->floatmaxed = 0;
@@ -4065,7 +4161,7 @@ stack_floater(struct ws_win *win, struct swm_region *r)
         */
        if (win->ewmh_flags & EWMH_F_FULLSCREEN) {
                if (!win->g_floatvalid)
-                       store_float_geom(win, win->ws->r);
+                       store_float_geom(win);
 
                win->g = r->g;
        }
@@ -4102,11 +4198,11 @@ stack_floater(struct ws_win *win, struct swm_region *r)
                X(win) = X(r) + (WIDTH(r) - WIDTH(win)) /  2 - BORDER(win);
                Y(win) = Y(r) + (HEIGHT(r) - HEIGHT(win)) / 2 - BORDER(win);
 
-               store_float_geom(win, r);
+               store_float_geom(win);
        }
 
-       /* keep window within region bounds */
-       constrain_window(win, r, 0);
+       /* Ensure at least 1 pixel of the window is in the region. */
+       region_containment(win, r, SWM_CW_ALLSIDES);
 
        update_window(win);
 }
@@ -4234,20 +4330,24 @@ stack_master(struct workspace *ws, struct swm_geometry *g, int rot, int flip)
                        colno = (winno - mwin) / stacks;
                        if (s <= (winno - mwin) % stacks)
                                colno++;
-                       split = split + colno;
-                       hrh = (r_g.h / colno);
+                       split += colno;
+                       hrh = r_g.h / colno;
                        extra = r_g.h - (colno * hrh);
-                       if (flip)
-                               win_g.x = r_g.x;
-                       else
+
+                       if (!flip)
                                win_g.x += win_g.w + 2 * border_width +
                                    tile_gap;
+
                        win_g.w = (r_g.w - msize -
                            (stacks * (2 * border_width + tile_gap))) / stacks;
                        if (s == 1)
                                win_g.w += (r_g.w - msize -
                                    (stacks * (2 * border_width + tile_gap))) %
                                    stacks;
+
+                       if (flip)
+                               win_g.x -= win_g.w + 2 * border_width +
+                                   tile_gap;
                        s--;
                        j = 0;
                }
@@ -4322,13 +4422,17 @@ stack_master(struct workspace *ws, struct swm_geometry *g, int rot, int flip)
                        update_window(win);
                }
 
-               map_window(win);
+               map_window(win, get_sibling(win, SWM_STACK_BELOW));
 
                last_h = win_g.h;
                i++;
                j++;
        }
 
+       /* Map/raise focused tiled window to top if windows could overlap. */
+       if (tile_gap < 0 && ws->focus != NULL && !ws->focus->floating)
+               map_window(ws->focus, get_sibling(ws->focus, SWM_STACK_TOP));
+
 notiles:
        /* now, stack all the floaters and transients */
        TAILQ_FOREACH(win, &ws->winlist, entry) {
@@ -4342,13 +4446,13 @@ notiles:
                }
 
                stack_floater(win, ws->r);
-               map_window(win);
+               map_window(win, XCB_WINDOW_NONE);
        }
 
        /* Make sure fs_win is stacked last so it's on top. */
        if (fs_win) {
                stack_floater(fs_win, ws->r);
-               map_window(fs_win);
+               map_window(fs_win, XCB_WINDOW_NONE);
        }
 }
 
@@ -4475,6 +4579,8 @@ max_stack(struct workspace *ws, struct swm_geometry *g)
                win = ws->focus_pending;
        else if (ws->focus)
                win = ws->focus;
+       else if (ws->focus_prev)
+               win = ws->focus_prev;
        else
                win = TAILQ_FIRST(&ws->winlist);
 
@@ -4489,14 +4595,14 @@ max_stack(struct workspace *ws, struct swm_geometry *g)
                        continue;
 
                if (!w->mapped && w != win)
-                       map_window(w);
+                       map_window(w, XCB_WINDOW_NONE);
 
                if (w->floating && !w->floatmaxed) {
                        /*
                         * retain geometry for retrieval on exit from
                         * max_stack mode
                         */
-                       store_float_geom(w, ws->r);
+                       store_float_geom(w);
                        w->floatmaxed = 1;
                }
 
@@ -4518,25 +4624,25 @@ max_stack(struct workspace *ws, struct swm_geometry *g)
 
        /* If a parent exists, map/raise it first. */
        if (parent) {
-               map_window(parent);
+               map_window(parent, XCB_WINDOW_NONE);
 
                /* Map siblings next. */
                TAILQ_FOREACH(w, &ws->winlist, entry)
                        if (w != win && !w->iconic &&
                            w->transient == parent->id) {
                                stack_floater(w, ws->r);
-                               map_window(w);
+                               map_window(w, XCB_WINDOW_NONE);
                        }
        }
 
        /* Map/raise focused window. */
-       map_window(win);
+       map_window(win, XCB_WINDOW_NONE);
 
        /* Finally, map/raise children of focus window. */
        TAILQ_FOREACH(w, &ws->winlist, entry)
                if (w->transient == win->id && !w->iconic) {
                        stack_floater(w, ws->r);
-                       map_window(w);
+                       map_window(w, XCB_WINDOW_NONE);
                }
 }
 
@@ -4547,7 +4653,7 @@ send_to_rg(struct swm_region *r, union arg *args)
        struct swm_region       *rr = NULL;
        union arg               a;
 
-       num_screens = xcb_setup_roots_length(xcb_get_setup(conn));
+       num_screens = get_screen_count();
        /* do nothing if we don't have more than one screen */
        if (!(num_screens > 1 || outputs > 1))
                return;
@@ -4566,31 +4672,74 @@ send_to_rg(struct swm_region *r, union arg *args)
        send_to_ws(r, &a);
 }
 
+struct swm_region *
+region_under(struct swm_screen *s, int x, int y)
+{
+       struct swm_region       *r = NULL;
+
+       TAILQ_FOREACH(r, &s->rl, entry) {
+               DNPRINTF(SWM_D_MISC, "region_under: ws: %d, region g: (%d,%d) "
+                   "%d x %d, coords: (%d,%d)\n", r->ws->idx, X(r), Y(r),
+                   WIDTH(r), HEIGHT(r), x, y);
+               if (X(r) <= x && x < MAX_X(r))
+                       if (Y(r) <= y && y < MAX_Y(r))
+                               return (r);
+       }
+
+       return (NULL);
+}
+
 void
 send_to_ws(struct swm_region *r, union arg *args)
 {
        int                     wsid = args->id;
-       struct ws_win           *win = NULL, *parent;
-       struct workspace        *ws, *nws, *pws;
-       char                    ws_idx_str[SWM_PROPLEN];
-
-       if (wsid >= workspace_limit)
-               return;
+       struct ws_win           *win = NULL;
 
        if (r && r->ws && r->ws->focus)
                win = r->ws->focus;
        else
                return;
 
-       if (win->ws->idx == wsid)
+       DNPRINTF(SWM_D_MOVE, "send_to_ws: win 0x%x, ws %d\n", win->id, wsid);
+
+       win_to_ws(win, wsid, 1);
+
+       /* Restack and set new focus. */
+       stack();
+
+       if (focus_mode != SWM_FOCUS_FOLLOW) {
+               if (r->ws->focus_pending) {
+                       focus_win(r->ws->focus_pending);
+                       r->ws->focus_pending = NULL;
+               } else {
+                       xcb_set_input_focus(conn,
+                           XCB_INPUT_FOCUS_PARENT, r->id,
+                           XCB_CURRENT_TIME);
+               }
+       }
+
+       focus_flush();
+}
+
+void
+win_to_ws(struct ws_win *win, int wsid, int unfocus)
+{
+       struct ws_win           *parent;
+       struct workspace        *ws, *nws, *pws;
+       char                    ws_idx_str[SWM_PROPLEN];
+
+       if (wsid >= workspace_limit)
                return;
 
-       DNPRINTF(SWM_D_MOVE, "send_to_ws: win 0x%x, ws %d -> %d\n", win->id,
-           win->ws->idx, wsid);
+       if (win->ws->idx == wsid)
+               return;
 
        ws = win->ws;
        nws = &win->s->ws[wsid];
 
+       DNPRINTF(SWM_D_MOVE, "win_to_ws: win 0x%x, ws %d -> %d\n", win->id,
+           ws->idx, wsid);
+
        /* Update the window's workspace property: _SWM_WS */
        if (snprintf(ws_idx_str, SWM_PROPLEN, "%d", nws->idx) < SWM_PROPLEN) {
                if (focus_mode != SWM_FOCUS_FOLLOW)
@@ -4609,11 +4758,10 @@ send_to_ws(struct swm_region *r, union arg *args)
 
                                        unfocus_win(parent);
 
-                                       if (focus_mode != SWM_FOCUS_FOLLOW)
+                                       if (focus_mode != SWM_FOCUS_FOLLOW) {
                                                pws->focus = pws->focus_pending;
-
-                                       if (focus_mode != SWM_FOCUS_FOLLOW)
                                                pws->focus_pending = NULL;
+                                       }
                                }
 
                                /* Don't unmap parent if new ws is visible */
@@ -4625,7 +4773,7 @@ send_to_ws(struct swm_region *r, union arg *args)
                                TAILQ_INSERT_TAIL(&nws->winlist, parent, entry);
                                parent->ws = nws;
 
-                               DNPRINTF(SWM_D_PROP, "send_to_ws: set "
+                               DNPRINTF(SWM_D_PROP, "win_to_ws: set "
                                    "property: _SWM_WS: %s\n", ws_idx_str);
                                xcb_change_property(conn, XCB_PROP_MODE_REPLACE,
                                    parent->id, a_swm_ws, XCB_ATOM_STRING, 8,
@@ -4633,7 +4781,8 @@ send_to_ws(struct swm_region *r, union arg *args)
                        }
                }
 
-               unfocus_win(win);
+               if (unfocus)
+                       unfocus_win(win);
 
                /* Don't unmap if new ws is visible */
                if (nws->r == NULL)
@@ -4648,24 +4797,14 @@ send_to_ws(struct swm_region *r, union arg *args)
                unfocus_win(nws->focus);
                nws->focus = win;
 
-               DNPRINTF(SWM_D_PROP, "send_to_ws: set property: _SWM_WS: %s\n",
+               DNPRINTF(SWM_D_PROP, "win_to_ws: set property: _SWM_WS: %s\n",
                    ws_idx_str);
                xcb_change_property(conn, XCB_PROP_MODE_REPLACE, win->id,
                    a_swm_ws, XCB_ATOM_STRING, 8, strlen(ws_idx_str),
                    ws_idx_str);
-
-               /* Restack and set new focus. */
-               stack();
-
-               if (focus_mode != SWM_FOCUS_FOLLOW) {
-                       focus_win(ws->focus_pending);
-                       ws->focus_pending = NULL;
-               }
-
-               focus_flush();
        }
 
-       DNPRINTF(SWM_D_MOVE, "send_to_ws: done.\n");
+       DNPRINTF(SWM_D_MOVE, "win_to_ws: done.\n");
 }
 
 void
@@ -4971,7 +5110,7 @@ search_win(struct swm_region *r, union arg *args)
 }
 
 void
-search_resp_uniconify(const char *resp, unsigned long len)
+search_resp_uniconify(const char *resp, size_t len)
 {
        char                    *name;
        struct ws_win           *win;
@@ -5001,7 +5140,7 @@ search_resp_uniconify(const char *resp, unsigned long len)
 }
 
 void
-search_resp_name_workspace(const char *resp, unsigned long len)
+search_resp_name_workspace(const char *resp, size_t len)
 {
        struct workspace        *ws;
 
@@ -5097,7 +5236,7 @@ search_do_resp(void)
 {
        ssize_t                 rbytes;
        char                    *resp;
-       unsigned long           len;
+       size_t                  len;
 
        DNPRINTF(SWM_D_MISC, "search_do_resp:\n");
 
@@ -5186,11 +5325,11 @@ floating_toggle_win(struct ws_win *win)
        if (win->floating) {
                if (!win->floatmaxed) {
                        /* retain position for refloat */
-                       store_float_geom(win, r);
+                       store_float_geom(win);
                }
                win->floating = 0;
        } else {
-               load_float_geom(win, r);
+               load_float_geom(win);
                win->floating = 1;
        }
 
@@ -5225,37 +5364,85 @@ floating_toggle(struct swm_region *r, union arg *args)
 }
 
 void
-constrain_window(struct ws_win *win, struct swm_region *r, int resizable)
+region_containment(struct ws_win *win, struct swm_region *r, int opts)
 {
-       if (MAX_X(win) + BORDER(win) > MAX_X(r)) {
-               if (resizable)
-                       WIDTH(win) = MAX_X(r) - X(win) - BORDER(win);
+       struct swm_geometry             g = r->g;
+       int                             rt, lt, tp, bm, bw;
+
+       bw = (opts & SWM_CW_SOFTBOUNDARY) ? boundary_width : 0;
+
+       /*
+        * Perpendicular distance of each side of the window to the respective
+        * side of the region boundary.  Positive values indicate the side of
+        * the window has passed beyond the region boundary.
+        */
+       rt = opts & SWM_CW_RIGHT ? MAX_X(win) + BORDER(win) - MAX_X(r) : bw;
+       lt = opts & SWM_CW_LEFT ? X(r) - X(win) + BORDER(win) : bw;
+       bm = opts & SWM_CW_BOTTOM ? MAX_Y(win) + BORDER(win) - MAX_Y(r) : bw;
+       tp = opts & SWM_CW_TOP ? Y(r) - Y(win) + BORDER(win) : bw;
+
+       DNPRINTF(SWM_D_MISC, "region_containment: win 0x%x, rt: %d, lt: %d, "
+           "bm: %d, tp: %d, SOFTBOUNDARY: %s, HARDBOUNDARY: %s\n", win->id, rt,
+           lt, bm, tp, YESNO(opts & SWM_CW_SOFTBOUNDARY),
+           YESNO(opts & SWM_CW_HARDBOUNDARY));
+
+       /*
+        * Disable containment if any of the flagged sides went beyond the
+        * containment boundary, or if containment is disabled.
+        */
+       if (!(opts & SWM_CW_HARDBOUNDARY || opts & SWM_CW_SOFTBOUNDARY) ||
+           (bw != 0 && ((rt > bw) || (lt > bw) || (bm > bw) || (tp > bw)))) {
+               /* Make sure window has at least 1 pixel in the region */
+               g.x += 1 - WIDTH(win);
+               g.y += 1 - HEIGHT(win);
+               g.w += 2 * WIDTH(win) - 2;
+               g.h += 2 * HEIGHT(win) - 2;
+       }
+
+       constrain_window(win, &g, &opts);
+}
+
+/* Move or resize a window so that flagged side(s) fit into the supplied box. */
+void
+constrain_window(struct ws_win *win, struct swm_geometry *b, int *opts)
+{
+       DNPRINTF(SWM_D_MISC, "constrain_window: window: 0x%x, (x,y) w x h: "
+           "(%d,%d) %d x %d, box: (x,y) w x h: (%d,%d) %d x %d, rt: %s, "
+           "lt: %s, bt: %s, tp: %s, allow resize: %s\n", win->id, X(win),
+           Y(win), WIDTH(win), HEIGHT(win), b->x, b->y, b->w, b->h,
+           YESNO(*opts & SWM_CW_RIGHT), YESNO(*opts & SWM_CW_LEFT),
+           YESNO(*opts & SWM_CW_BOTTOM), YESNO(*opts & SWM_CW_TOP),
+           YESNO(*opts & SWM_CW_RESIZABLE));
+
+       if ((*opts & SWM_CW_RIGHT) && MAX_X(win) + BORDER(win) > b->x + b->w) {
+               if (*opts & SWM_CW_RESIZABLE)
+                       WIDTH(win) = b->x + b->w - X(win) - BORDER(win);
                else
-                       X(win) = MAX_X(r)- WIDTH(win) - BORDER(win);
+                       X(win) = b->x + b->w - WIDTH(win) - BORDER(win);
        }
 
-       if (X(win) + BORDER(win) < X(r)) {
-               if (resizable)
-                       WIDTH(win) -= X(r) - X(win) - BORDER(win);
+       if ((*opts & SWM_CW_LEFT) && X(win) + BORDER(win) < b->x) {
+               if (*opts & SWM_CW_RESIZABLE)
+                       WIDTH(win) -= b->x - X(win) - BORDER(win);
 
-               X(win) = X(r) - BORDER(win);
+               X(win) = b->x - BORDER(win);
        }
 
-       if (MAX_Y(win) + BORDER(win) > MAX_Y(r)) {
-               if (resizable)
-                       HEIGHT(win) = MAX_Y(r) - Y(win) - BORDER(win);
+       if ((*opts & SWM_CW_BOTTOM) && MAX_Y(win) + BORDER(win) > b->y + b->h) {
+               if (*opts & SWM_CW_RESIZABLE)
+                       HEIGHT(win) = b->y + b->h - Y(win) - BORDER(win);
                else
-                       Y(win) = MAX_Y(r) - HEIGHT(win) - BORDER(win);
+                       Y(win) = b->y + b->h - HEIGHT(win) - BORDER(win);
        }
 
-       if (Y(win) + BORDER(win) < Y(r)) {
-               if (resizable)
-                       HEIGHT(win) -= Y(r) - Y(win) - BORDER(win);
+       if ((*opts & SWM_CW_TOP) && Y(win) + BORDER(win) < b->y) {
+               if (*opts & SWM_CW_RESIZABLE)
+                       HEIGHT(win) -= b->y - Y(win) - BORDER(win);
 
-               Y(win) = Y(r) - BORDER(win);
+               Y(win) = b->y - BORDER(win);
        }
 
-       if (resizable) {
+       if (*opts & SWM_CW_RESIZABLE) {
                if (WIDTH(win) < 1)
                        WIDTH(win) = 1;
                if (HEIGHT(win) < 1)
@@ -5297,7 +5484,7 @@ resize(struct ws_win *win, union arg *args)
        int                     top = 0, left = 0, resizing;
        int                     dx, dy;
        xcb_cursor_t                    cursor;
-       xcb_query_pointer_reply_t       *xpr;
+       xcb_query_pointer_reply_t       *xpr = NULL;
        xcb_generic_event_t             *evt;
        xcb_motion_notify_event_t       *mne;
 
@@ -5329,7 +5516,7 @@ resize(struct ws_win *win, union arg *args)
 
        /* It's possible for win to have been freed during focus_flush(). */
        if (validate_win(win)) {
-               DNPRINTF(SWM_D_EVENT, "move: invalid win.\n");
+               DNPRINTF(SWM_D_EVENT, "resize: invalid win.\n");
                goto out;
        }
 
@@ -5354,12 +5541,17 @@ resize(struct ws_win *win, union arg *args)
                break;
        }
        if (resize_stp) {
-               constrain_window(win, r, 1);
+               region_containment(win, r, SWM_CW_ALLSIDES | SWM_CW_RESIZABLE |
+                   SWM_CW_HARDBOUNDARY);
                update_window(win);
-               store_float_geom(win,r);
+               store_float_geom(win);
                return;
        }
 
+       region_containment(win, r, SWM_CW_ALLSIDES | SWM_CW_RESIZABLE |
+           SWM_CW_SOFTBOUNDARY);
+       update_window(win);
+
        /* get cursor offset from window root */
        xpr = xcb_query_pointer_reply(conn, xcb_query_pointer(conn, win->id),
            NULL);
@@ -5441,11 +5633,13 @@ resize(struct ws_win *win, union arg *args)
                                WIDTH(win) = g.w + dx;
                        }
 
-                       constrain_window(win, r, 1);
-
                        /* not free, don't sync more than 120 times / second */
                        if ((mne->time - timestamp) > (1000 / 120) ) {
                                timestamp = mne->time;
+                               regionize(win, mne->root_x, mne->root_y);
+                               region_containment(win, r, SWM_CW_ALLSIDES |
+                                   SWM_CW_RESIZABLE | SWM_CW_HARDBOUNDARY |
+                                   SWM_CW_SOFTBOUNDARY);
                                update_window(win);
                                xcb_flush(conn);
                        }
@@ -5455,7 +5649,7 @@ resize(struct ws_win *win, union arg *args)
 
                        /* It's possible for win to have been freed above. */
                        if (validate_win(win)) {
-                               DNPRINTF(SWM_D_EVENT, "move: invalid win.\n");
+                               DNPRINTF(SWM_D_EVENT, "resize: invalid win.\n");
                                goto out;
                        }
                        break;
@@ -5463,10 +5657,12 @@ resize(struct ws_win *win, union arg *args)
                free(evt);
        }
        if (timestamp) {
+               region_containment(win, r, SWM_CW_ALLSIDES | SWM_CW_RESIZABLE |
+                   SWM_CW_HARDBOUNDARY | SWM_CW_SOFTBOUNDARY);
                update_window(win);
                xcb_flush(conn);
        }
-       store_float_geom(win,r);
+       store_float_geom(win);
 out:
        xcb_ungrab_pointer(conn, XCB_CURRENT_TIME);
        free(xpr);
@@ -5487,6 +5683,23 @@ resize_step(struct swm_region *r, union arg *args)
        focus_flush();
 }
 
+/* Try to set window region based on supplied coordinates or window center. */
+void
+regionize(struct ws_win *win, int x, int y)
+{
+       struct swm_region *r = NULL;
+
+       r = region_under(win->s, x, y);
+       if (r == NULL)
+               r = region_under(win->s, X(win) + WIDTH(win) / 2,
+                   Y(win) + HEIGHT(win) / 2);
+
+       if (r && r != win->ws->r) {
+               win_to_ws(win, r->ws->idx, 0);
+               set_region(r);
+       }
+}
+
 #define SWM_MOVE_STEPS (50)
 
 void
@@ -5495,7 +5708,7 @@ move(struct ws_win *win, union arg *args)
        xcb_timestamp_t         timestamp = 0;
        int                     move_stp = 0, moving;
        struct swm_region       *r = NULL;
-       xcb_query_pointer_reply_t       *qpr;
+       xcb_query_pointer_reply_t       *qpr = NULL;
        xcb_generic_event_t             *evt;
        xcb_motion_notify_event_t       *mne;
 
@@ -5513,16 +5726,19 @@ move(struct ws_win *win, union arg *args)
        if (win->ws->cur_layout == &layouts[SWM_MAX_STACK] && !win->transient)
                return;
 
-       win->manual = 1;
+       if (!win->manual) {
+               win->manual = 1;
+               ewmh_update_win_state(win, ewmh[_SWM_WM_STATE_MANUAL].atom,
+                   _NET_WM_STATE_ADD);
+       }
+
+       /* When a stacked win is moved, float it and restack. */
        if (!win->floating && !win->transient) {
-               store_float_geom(win, r);
+               store_float_geom(win);
                ewmh_update_win_state(win, ewmh[_NET_WM_STATE_ABOVE].atom,
                    _NET_WM_STATE_ADD);
+               stack();
        }
-       ewmh_update_win_state(win, ewmh[_SWM_WM_STATE_MANUAL].atom,
-           _NET_WM_STATE_ADD);
-
-       stack();
 
        focus_flush();
 
@@ -5554,9 +5770,10 @@ move(struct ws_win *win, union arg *args)
                break;
        }
        if (move_stp) {
-               constrain_window(win, r, 0);
+               regionize(win, -1, -1);
+               region_containment(win, win->ws->r, SWM_CW_ALLSIDES);
                update_window(win);
-               store_float_geom(win, r);
+               store_float_geom(win);
                return;
        }
 
@@ -5572,6 +5789,10 @@ move(struct ws_win *win, union arg *args)
                return;
        }
 
+       regionize(win, qpr->root_x, qpr->root_y);
+       region_containment(win, win->ws->r, SWM_CW_ALLSIDES |
+           SWM_CW_SOFTBOUNDARY);
+       update_window(win);
        xcb_flush(conn);
        moving = 1;
        while (moving && (evt = xcb_wait_for_event(conn))) {
@@ -5582,14 +5803,16 @@ move(struct ws_win *win, union arg *args)
                        break;
                case XCB_MOTION_NOTIFY:
                        mne = (xcb_motion_notify_event_t *)evt;
+                       DNPRINTF(SWM_D_EVENT, "motion: root: 0x%x\n", mne->root);
                        X(win) = mne->root_x - qpr->win_x - border_width;
                        Y(win) = mne->root_y - qpr->win_y - border_width;
 
-                       constrain_window(win, r, 0);
-
                        /* not free, don't sync more than 120 times / second */
                        if ((mne->time - timestamp) > (1000 / 120) ) {
                                timestamp = mne->time;
+                               regionize(win, mne->root_x, mne->root_y);
+                               region_containment(win, win->ws->r,
+                                   SWM_CW_ALLSIDES | SWM_CW_SOFTBOUNDARY);
                                update_window(win);
                                xcb_flush(conn);
                        }
@@ -5607,10 +5830,12 @@ move(struct ws_win *win, union arg *args)
                free(evt);
        }
        if (timestamp) {
+               region_containment(win, win->ws->r, SWM_CW_ALLSIDES |
+                   SWM_CW_SOFTBOUNDARY);
                update_window(win);
                xcb_flush(conn);
        }
-       store_float_geom(win, r);
+       store_float_geom(win);
 out:
        free(qpr);
        xcb_ungrab_pointer(conn, XCB_CURRENT_TIME);
@@ -5748,8 +5973,10 @@ struct keyfunc {
        { "ws_22",              switchws,       {.id = 21} },
        { "ws_next",            cyclews,        {.id = SWM_ARG_ID_CYCLEWS_UP} },
        { "ws_next_all",        cyclews,        {.id = SWM_ARG_ID_CYCLEWS_UP_ALL} },
+       { "ws_next_move",       cyclews,        {.id = SWM_ARG_ID_CYCLEWS_MOVE_UP} },
        { "ws_prev",            cyclews,        {.id = SWM_ARG_ID_CYCLEWS_DOWN} },
        { "ws_prev_all",        cyclews,        {.id = SWM_ARG_ID_CYCLEWS_DOWN_ALL} },
+       { "ws_prev_move",       cyclews,        {.id = SWM_ARG_ID_CYCLEWS_MOVE_DOWN} },
        { "ws_prior",           priorws,        {0} },
        { "dumpwins",           dumpwins,       {0} }, /* MUST BE LAST */
        { "invalid key func",   NULL,           {0} },
@@ -6438,6 +6665,8 @@ setup_keys(void)
        setkeybinding(MODKEY,           XK_Left,        KF_WS_PREV,     NULL);
        setkeybinding(MODKEY,           XK_Up,          KF_WS_NEXT_ALL, NULL);
        setkeybinding(MODKEY,           XK_Down,        KF_WS_PREV_ALL, NULL);
+       setkeybinding(MODKEY_SHIFT,     XK_Up,          KF_WS_NEXT_MOVE,NULL);
+       setkeybinding(MODKEY_SHIFT,     XK_Down,        KF_WS_PREV_MOVE,NULL);
        setkeybinding(MODKEY,           XK_a,           KF_WS_PRIOR,    NULL);
 #ifdef SWM_DEBUG
        setkeybinding(MODKEY_SHIFT,     XK_d,           KF_DUMPWINS,    NULL);
@@ -6528,13 +6757,23 @@ grabkeys(void)
        modifiers[2] = XCB_MOD_MASK_LOCK;
        modifiers[3] = numlockmask | XCB_MOD_MASK_LOCK;
 
-       num_screens = xcb_setup_roots_length(xcb_get_setup(conn));
+       num_screens = get_screen_count();
        for (k = 0; k < num_screens; k++) {
                if (TAILQ_EMPTY(&screens[k].rl))
                        continue;
                xcb_ungrab_key(conn, XCB_GRAB_ANY, screens[k].root,
                        XCB_MOD_MASK_ANY);
                RB_FOREACH(kp, key_tree, &keys) {
+                       /* Skip unused ws binds. */
+                       if ((int)kp->funcid > KF_WS_1 + workspace_limit - 1 &&
+                           kp->funcid <= KF_WS_22)
+                               continue;
+
+                       /* Skip unused mvws binds. */
+                       if ((int)kp->funcid > KF_MVWS_1 + workspace_limit - 1 &&
+                           kp->funcid <= KF_MVWS_22)
+                               continue;
+
                        if ((code = xcb_key_symbols_get_keycode(syms,
                                        kp->keysym))) {
                                for (j = 0; j < LENGTH(modifiers); j++)
@@ -6752,6 +6991,7 @@ enum {
        SWM_S_BAR_FORMAT,
        SWM_S_BAR_JUSTIFY,
        SWM_S_BORDER_WIDTH,
+       SWM_S_BOUNDARY_WIDTH,
        SWM_S_CLOCK_ENABLED,
        SWM_S_CLOCK_FORMAT,
        SWM_S_CYCLE_EMPTY,
@@ -6814,7 +7054,7 @@ setconfvalue(char *selector, char *value, int flags)
                        errx(1, "setconfvalue: bar_enabled_ws: invalid "
                            "workspace %d.", ws_id + 1);
 
-               num_screens = xcb_setup_roots_length(xcb_get_setup(conn));
+               num_screens = get_screen_count();
                for (i = 0; i < num_screens; i++) {
                        ws = (struct workspace *)&screens[i].ws;
                        ws[ws_id].bar_enabled = atoi(value);
@@ -6861,6 +7101,11 @@ setconfvalue(char *selector, char *value, int flags)
                if (border_width < 0)
                        border_width = 0;
                break;
+       case SWM_S_BOUNDARY_WIDTH:
+               boundary_width = atoi(value);
+               if (boundary_width < 0)
+                       boundary_width = 0;
+               break;
        case SWM_S_CLOCK_ENABLED:
                clock_enabled = atoi(value);
                break;
@@ -6956,8 +7201,6 @@ setconfvalue(char *selector, char *value, int flags)
                break;
        case SWM_S_TILE_GAP:
                tile_gap = atoi(value);
-               if (tile_gap < 0)
-                       tile_gap = 0;
                break;
        case SWM_S_TITLE_CLASS_ENABLED:
                title_class_enabled = atoi(value);
@@ -7145,7 +7388,7 @@ setlayout(char *selector, char *value, int flags)
                    "<master_grow>:<master_add>:<stack_inc>:<always_raise>:"
                    "<type>'");
 
-       num_screens = xcb_setup_roots_length(xcb_get_setup(conn));
+       num_screens = get_screen_count();
        for (i = 0; i < num_screens; i++) {
                ws = (struct workspace *)&screens[i].ws;
                ws[ws_id].cur_layout = &layouts[st];
@@ -7211,6 +7454,7 @@ struct config_option configopt[] = {
        { "bar_justify",                setconfvalue,   SWM_S_BAR_JUSTIFY },
        { "bind",                       setconfbinding, 0 },
        { "border_width",               setconfvalue,   SWM_S_BORDER_WIDTH },
+       { "boundary_width",             setconfvalue,   SWM_S_BOUNDARY_WIDTH },
        { "clock_enabled",              setconfvalue,   SWM_S_CLOCK_ENABLED },
        { "clock_format",               setconfvalue,   SWM_S_CLOCK_FORMAT },
        { "color_focus",                setconfcolor,   SWM_S_COLOR_FOCUS },
@@ -7585,8 +7829,6 @@ manage_window(xcb_window_t id, uint16_t mapped)
        win->ewmh_flags = 0;
        win->s = r->s;  /* this never changes */
 
-       store_float_geom(win, r);
-
        /* Get WM_SIZE_HINTS. */
        xcb_icccm_get_wm_normal_hints_reply(conn,
            xcb_icccm_get_wm_normal_hints(conn, win->id),
@@ -7648,6 +7890,9 @@ manage_window(xcb_window_t id, uint16_t mapped)
                    ws_idx_str);
        }
 
+       /* WS must already be set for this to work. */
+       store_float_geom(win);
+
        /* Handle EWMH */
        ewmh_autoquirk(win);
 
@@ -7691,7 +7936,8 @@ manage_window(xcb_window_t id, uint16_t mapped)
 
        /* Make sure window is positioned inside its region, if its active. */
        if (win->ws->r) {
-               constrain_window(win, win->ws->r, 0);
+               region_containment(win, r, SWM_CW_ALLSIDES |
+                   SWM_CW_HARDBOUNDARY);
                update_window(win);
        }
 
@@ -7793,7 +8039,7 @@ expose(xcb_expose_event_t *e)
 
        DNPRINTF(SWM_D_EVENT, "expose: window: 0x%x\n", e->window);
 
-       num_screens = xcb_setup_roots_length(xcb_get_setup(conn));
+       num_screens = get_screen_count();
        for (i = 0; i < num_screens; i++)
                TAILQ_FOREACH(r, &screens[i].rl, entry)
                        if (e->window == WINID(r->bar))
@@ -8160,6 +8406,9 @@ destroynotify(xcb_destroy_notify_event_t *e)
                if (win->ws->focus_pending) {
                        focus_win(win->ws->focus_pending);
                        win->ws->focus_pending = NULL;
+               } else if (win == win->ws->focus && win->ws->r) {
+                       xcb_set_input_focus(conn, XCB_INPUT_FOCUS_PARENT,
+                           win->ws->r->id, XCB_CURRENT_TIME);
                }
        }
 
@@ -8361,8 +8610,12 @@ maprequest(xcb_map_request_event_t *e)
                                if (w == win || !w->mapped)
                                        continue;
 
-                               if (!strcmp(w->ch.class_name,
+                               if (w->ch.class_name &&
+                                   win->ch.class_name &&
+                                   !strcmp(w->ch.class_name,
                                    win->ch.class_name) &&
+                                   w->ch.instance_name &&
+                                   win->ch.instance_name &&
                                    !strcmp(w->ch.instance_name,
                                    win->ch.instance_name))
                                        break;
@@ -8401,7 +8654,10 @@ motionnotify(xcb_motion_notify_event_t *e)
 
        last_event_time = e->time;
 
-       num_screens = xcb_setup_roots_length(xcb_get_setup(conn));
+       if (focus_mode == SWM_FOCUS_MANUAL)
+               return;
+
+       num_screens = get_screen_count();
        for (i = 0; i < num_screens; i++)
                if (screens[i].root == e->root)
                        break;
@@ -8452,6 +8708,7 @@ void
 propertynotify(xcb_property_notify_event_t *e)
 {
        struct ws_win           *win;
+       struct workspace        *ws;
 #ifdef SWM_DEBUG
        char                    *name;
 
@@ -8465,28 +8722,39 @@ propertynotify(xcb_property_notify_event_t *e)
        if (win == NULL)
                return;
 
+       ws = win->ws;
+
        last_event_time = e->time;
 
        if (e->atom == a_swm_iconic) {
                if (e->state == XCB_PROPERTY_NEW_VALUE) {
                        if (focus_mode != SWM_FOCUS_FOLLOW)
-                               win->ws->focus_pending = get_focus_prev(win);
+                               ws->focus_pending = get_focus_prev(win);
 
                        unfocus_win(win);
                        unmap_window(win);
 
-                       if (win->ws->r) {
+                       if (ws->r) {
                                stack();
+
                                if (focus_mode != SWM_FOCUS_FOLLOW) {
-                                       focus_win(win->ws->focus_pending);
-                                       win->ws->focus_pending = NULL;
+                                       if (ws->focus_pending) {
+                                               focus_win(ws->focus_pending);
+                                               ws->focus_pending = NULL;
+                                       } else {
+                                               xcb_set_input_focus(conn,
+                                                   XCB_INPUT_FOCUS_PARENT,
+                                                   ws->r->id,
+                                                   XCB_CURRENT_TIME);
+                                       }
                                }
+
                                focus_flush();
                        }
                } else if (e->state == XCB_PROPERTY_DELETE) {
                        /* The window is no longer iconic, restack ws. */
                        if (focus_mode != SWM_FOCUS_FOLLOW)
-                               win->ws->focus_pending = get_focus_magic(win);
+                               ws->focus_pending = get_focus_magic(win);
 
                        stack();
 
@@ -8498,9 +8766,9 @@ propertynotify(xcb_property_notify_event_t *e)
                if (e->state == XCB_PROPERTY_NEW_VALUE) {
                        if (focus_mode != SWM_FOCUS_FOLLOW) {
                                if (win->mapped &&
-                                   win->ws->focus_pending == win) {
-                                       focus_win(win->ws->focus_pending);
-                                       win->ws->focus_pending = NULL;
+                                   ws->focus_pending == win) {
+                                       focus_win(ws->focus_pending);
+                                       ws->focus_pending = NULL;
                                }
                        }
                }
@@ -8555,11 +8823,12 @@ unmapnotify(xcb_unmap_notify_event_t *e)
                if (focus_mode == SWM_FOCUS_FOLLOW) {
                        if (ws->r)
                                focus_win(get_pointer_win(ws->r->s->root));
-               } else {
-                       if (ws->focus_pending) {
-                               focus_win(ws->focus_pending);
-                               ws->focus_pending = NULL;
-                       }
+               } else if (ws->focus_pending) {
+                       focus_win(ws->focus_pending);
+                       ws->focus_pending = NULL;
+               } else if (ws->focus == NULL && ws->r) {
+                       xcb_set_input_focus(conn, XCB_INPUT_FOCUS_PARENT,
+                           ws->r->id, XCB_CURRENT_TIME);
                }
        }
 
@@ -8693,7 +8962,7 @@ enable_wm(void)
        xcb_generic_error_t     *error;
 
        /* this causes an error if some other window manager is running */
-       num_screens = xcb_setup_roots_length(xcb_get_setup(conn));
+       num_screens = get_screen_count();
        for (i = 0; i < num_screens; i++) {
                if ((sc = get_screen(i)) == NULL)
                        errx(1, "ERROR: can't get screen %d.", i);
@@ -8825,7 +9094,7 @@ scan_xrandr(int i)
        if ((screen = get_screen(i)) == NULL)
                errx(1, "ERROR: can't get screen %d.", i);
 
-       num_screens = xcb_setup_roots_length(xcb_get_setup(conn));
+       num_screens = get_screen_count();
        if (i >= num_screens)
                errx(1, "scan_xrandr: invalid screen");
 
@@ -8906,7 +9175,7 @@ screenchange(xcb_randr_screen_change_notify_event_t *e)
 
        DNPRINTF(SWM_D_EVENT, "screenchange: root: 0x%x\n", e->root);
 
-       num_screens = xcb_setup_roots_length(xcb_get_setup(conn));
+       num_screens = get_screen_count();
        /* silly event doesn't include the screen index */
        for (i = 0; i < num_screens; i++)
                if (screens[i].root == e->root)
@@ -8957,7 +9226,7 @@ grab_windows(void)
        xcb_get_property_cookie_t               pc;
 
        DNPRINTF(SWM_D_INIT, "grab_windows: begin\n");
-       num_screens = xcb_setup_roots_length(xcb_get_setup(conn));
+       num_screens = get_screen_count();
        for (i = 0; i < num_screens; i++) {
                qtc = xcb_query_tree(conn, screens[i].root);
                qtr = xcb_query_tree_reply(conn, qtc, NULL);
@@ -9055,7 +9324,7 @@ setup_screens(void)
        xcb_randr_query_version_cookie_t        c;
        xcb_randr_query_version_reply_t         *r;
 
-       num_screens = xcb_setup_roots_length(xcb_get_setup(conn));
+       num_screens = get_screen_count();
        if ((screens = calloc(num_screens,
             sizeof(struct swm_screen))) == NULL)
                err(1, "setup_screens: calloc: failed to allocate memory for "
@@ -9174,7 +9443,7 @@ workaround(void)
        /* work around sun jdk bugs, code from wmname */
        netwmcheck = get_atom_from_string("_NET_SUPPORTING_WM_CHECK");
 
-       num_screens = xcb_setup_roots_length(xcb_get_setup(conn));
+       num_screens = get_screen_count();
        for (i = 0; i < num_screens; i++) {
                root = screens[i].root;
 
@@ -9209,7 +9478,7 @@ shutdown_cleanup(void)
 
        teardown_ewmh();
 
-       num_screens = xcb_setup_roots_length(xcb_get_setup(conn));
+       num_screens = get_screen_count();
        for (i = 0; i < num_screens; ++i) {
                if (screens[i].bar_gc != 0)
                        xcb_free_gc(conn, screens[i].bar_gc);
@@ -9428,7 +9697,7 @@ noconfig:
                setenv("SWM_STARTED", "YES", 1);
 
        /* setup all bars */
-       num_screens = xcb_setup_roots_length(xcb_get_setup(conn));
+       num_screens = get_screen_count();
        for (i = 0; i < num_screens; i++)
                TAILQ_FOREACH(r, &screens[i].rl, entry)
                        bar_setup(r);