JasonWoof Got questions, comments, patches, etc.? Contact Jason Woofenden
Remove path from Linux spectrwm.desktop.
[spectrwm.git] / spectrwm.c
index 91b5755..5ad56a7 100644 (file)
@@ -355,8 +355,7 @@ int          bar_version = 0;
 int             bar_enabled = 1;
 int             bar_border_width = 1;
 int             bar_at_bottom = 0;
-int             bar_extra = 1;
-int             bar_extra_running = 0;
+int             bar_extra = 0;
 int             bar_verbose = 1;
 int             bar_height = 0;
 int             bar_justify = SWM_BAR_JUSTIFY_LEFT;
@@ -375,6 +374,8 @@ int          focus_default = SWM_STACK_TOP;
 int             spawn_position = SWM_STACK_TOP;
 int             disable_border = 0;
 int             border_width = 1;
+int             region_padding = 0;
+int             tile_gap = 0;
 int             verbose_layout = 0;
 time_t          time_started;
 pid_t           bar_pid;
@@ -385,6 +386,8 @@ int          bar_font_legacy = 1;
 char           *bar_fonts;
 XftColor        bar_font_color;
 struct passwd  *pwd;
+char           *startup_exception;
+unsigned int    nr_exceptions = 0;
 
 /* layout manager data */
 struct swm_geometry {
@@ -521,6 +524,7 @@ struct workspace {
 enum {
        SWM_S_COLOR_BAR,
        SWM_S_COLOR_BAR_BORDER,
+       SWM_S_COLOR_BAR_BORDER_UNFOCUS,
        SWM_S_COLOR_BAR_FONT,
        SWM_S_COLOR_FOCUS,
        SWM_S_COLOR_UNFOCUS,
@@ -606,6 +610,8 @@ struct quirk {
 #define SWM_Q_XTERM_FONTADJ    (1<<3)  /* adjust xterm fonts when resizing */
 #define SWM_Q_FULLSCREEN       (1<<4)  /* remove border */
 #define SWM_Q_FOCUSPREV                (1<<5)  /* focus on caller */
+#define SWM_Q_NOFOCUSONMAP     (1<<6)  /* Don't focus on window when mapped. */
+#define SWM_Q_FOCUSONMAP_SINGLE        (1<<7)  /* Only focus if single win of type. */
 };
 TAILQ_HEAD(quirk_list, quirk);
 struct quirk_list              quirks = TAILQ_HEAD_INITIALIZER(quirks);
@@ -703,12 +709,15 @@ struct cursors {
        {"top_right_corner", XC_top_right_corner, XCB_CURSOR_NONE},
 };
 
+#define        SWM_SPAWN_OPTIONAL              0x1
+
 /* spawn */
 struct spawn_prog {
        TAILQ_ENTRY(spawn_prog) entry;
        char                    *name;
        int                     argc;
        char                    **argv;
+       int                     flags;
 };
 TAILQ_HEAD(spawn_list, spawn_prog);
 struct spawn_list              spawns = TAILQ_HEAD_INITIALIZER(spawns);
@@ -845,7 +854,7 @@ void         bar_class_title_name(char *, size_t, struct swm_region *);
 void    bar_cleanup(struct swm_region *);
 void    bar_extra_setup(void);
 void    bar_extra_stop(void);
-void    bar_extra_update(void);
+int     bar_extra_update(void);
 void    bar_fmt(const char *, char *, struct swm_region *, size_t);
 void    bar_fmt_expand(char *, size_t);
 void    bar_draw(void);
@@ -853,8 +862,7 @@ void         bar_print(struct swm_region *, const char *);
 void    bar_print_legacy(struct swm_region *, const char *);
 void    bar_replace(char *, char *, struct swm_region *, size_t);
 void    bar_replace_pad(char *, int *, size_t);
-char *  bar_replace_seq(char *, char *, struct swm_region *, size_t *,
-            size_t);
+char   *bar_replace_seq(char *, char *, struct swm_region *, size_t *, size_t);
 void    bar_setup(struct swm_region *);
 void    bar_title_name(char *, size_t, struct swm_region *);
 void    bar_toggle(struct swm_region *, union arg *);
@@ -922,7 +930,9 @@ char        *get_notify_mode_label(uint8_t);
 #endif
 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);
+int     get_screen_count(void);
 #ifdef SWM_DEBUG
 char   *get_stack_mode_name(uint8_t);
 #endif
@@ -956,7 +966,7 @@ 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 *);
@@ -1004,7 +1014,7 @@ int        setkeymapping(char *, char *, int);
 int     setlayout(char *, char *, int);
 void    setquirk(const char *, const char *, unsigned long);
 void    setscreencolor(char *, int, int);
-void    setspawn(const char *, const char *);
+void    setspawn(const char *, const char *, int);
 void    setup_ewmh(void);
 void    setup_globals(void);
 void    setup_keys(void);
@@ -1021,15 +1031,15 @@ void     sort_windows(struct ws_win_list *);
 void    spawn(int, union arg *, int);
 void    spawn_custom(struct swm_region *, union arg *, const char *);
 int     spawn_expand(struct swm_region *, union arg *, const char *, char ***);
-void    spawn_insert(const char *, const char *);
+void    spawn_insert(const char *, const char *, int);
 void    spawn_remove(struct spawn_prog *);
-void    spawn_replace(struct spawn_prog *, const char *, const char *);
+void    spawn_replace(struct spawn_prog *, const char *, const char *, int);
 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 *);
-char *  strdupsafe(const char *);
+char   *strdupsafe(const char *);
 void    swapwin(struct swm_region *, union arg *);
 void    switchws(struct swm_region *, union arg *);
 void    teardown_ewmh(void);
@@ -1042,6 +1052,7 @@ void       unmap_window(struct ws_win *);
 void    updatenumlockmask(void);
 void    update_modkey(unsigned int);
 void    update_window(struct ws_win *);
+void    validate_spawns(void);
 int     validate_win(struct ws_win *);
 int     validate_ws(struct workspace *);
 /*void  visibilitynotify(xcb_visibility_notify_event_t *);*/
@@ -1050,6 +1061,8 @@ pid_t      window_get_pid(xcb_window_t);
 void    wkill(struct swm_region *, union arg *);
 void    workaround(void);
 void    xft_init(struct swm_region *);
+void    _add_startup_exception(const char *, va_list);
+void    add_startup_exception(const char *, ...);
 
 RB_PROTOTYPE(key_tree, key, entry, key_cmp);
 RB_GENERATE(key_tree, key, entry, key_cmp);
@@ -1174,6 +1187,40 @@ 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)
+{
+       struct swm_region       *rr;
+       int                      ridx = 0;
+
+       if (r == NULL)
+               return -1;
+
+       TAILQ_FOREACH(rr, &r->s->rl, entry) {
+               if (rr == r)
+                       break;
+               ++ridx;
+       }
+
+       if (rr == NULL)
+               return -1;
+
+       return ridx;
+}
+
 void
 focus_flush(void)
 {
@@ -1247,7 +1294,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(). */
 
@@ -1271,7 +1318,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 */
@@ -1620,7 +1667,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] = "#";
@@ -1630,7 +1677,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 */
@@ -1672,15 +1719,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");
@@ -1723,7 +1770,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>");
@@ -1951,7 +1998,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) {
@@ -2201,7 +2248,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)
@@ -2214,8 +2261,15 @@ bar_draw(void)
                                continue;
                        }
 
-                       bar_fmt(fmtexp, fmtnew, r, sizeof fmtnew);
-                       bar_replace(fmtnew, fmtrep, r, sizeof fmtrep);
+                       if (startup_exception)
+                               snprintf(fmtrep, sizeof fmtrep, "total "
+                                   "exceptions: %d, first exception: %s",
+                                   nr_exceptions,
+                                   startup_exception);
+                       else {
+                               bar_fmt(fmtexp, fmtnew, r, sizeof fmtnew);
+                               bar_replace(fmtnew, fmtrep, r, sizeof fmtrep);
+                       }
                        if (bar_font_legacy)
                                bar_print_legacy(r, fmtrep);
                        else
@@ -2224,16 +2278,22 @@ bar_draw(void)
        }
 }
 
-/* Reads external script output; call when stdin is readable. */
-void
+/*
+ * Reads external script output; call when stdin is readable.
+ * Returns 1 if bar_ext was updated; otherwise 0.
+ */
+int
 bar_extra_update(void)
 {
        size_t          len;
        char            b[SWM_BAR_MAX];
-       int             redraw = 0;
+       int             changed = 0;
 
-       if (bar_enabled && bar_extra && bar_extra_running) {
-               while (fgets(b, sizeof(b), stdin) != NULL) {
+       if (!bar_extra)
+               return changed;
+
+       while (fgets(b, sizeof(b), stdin) != NULL) {
+               if (bar_enabled) {
                        len = strlen(b);
                        if (b[len - 1] == '\n') {
                                /* Remove newline. */
@@ -2249,38 +2309,21 @@ bar_extra_update(void)
                                /* Append new output to bar. */
                                strlcat(bar_ext, b, sizeof(bar_ext));
 
-                               redraw = 1;
+                               changed = 1;
                        } else {
                                /* Buffer output. */
                                strlcat(bar_ext_buf, b, sizeof(bar_ext_buf));
                        }
                }
-
-               if (errno != EAGAIN) {
-                       warn("bar_action failed");
-                       bar_extra_stop();
-               }
-       } else {
-               /*
-                * Attempt to drain stdin, so it doesn't cause the main loop to
-                * call us as fast as it can.
-                */
-               while (fgets(b, sizeof(b), stdin) != NULL);
-
-               if (!bar_enabled)
-                       return;
-
-               /* Clear bar script output if bar script is not running. */
-               if (bar_ext[0] != '\0') {
-                       bar_ext[0] = '\0';
-                       redraw = 1;
-               }
        }
 
-       if (redraw) {
-               bar_draw();
-               xcb_flush(conn);
+       if (errno != EAGAIN) {
+               warn("bar_action failed");
+               bar_extra_stop();
+               changed = 1;
        }
+
+       return changed;
 }
 
 void
@@ -2309,7 +2352,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) {
@@ -2331,20 +2374,20 @@ void
 bar_extra_setup(void)
 {
        /* do this here because the conf file is in memory */
-       if (bar_extra && !bar_extra_running && bar_argv[0]) {
+       if (!bar_extra && bar_argv[0]) {
                /* launch external status app */
-               bar_extra_running = 1;
+               bar_extra = 1;
                if (pipe(bar_pipe) == -1)
                        err(1, "pipe error");
                socket_setnonblock(bar_pipe[0]);
                socket_setnonblock(bar_pipe[1]); /* XXX hmmm, really? */
 
                /* Set stdin to read from the pipe. */
-               if (dup2(bar_pipe[0], 0) == -1)
+               if (dup2(bar_pipe[0], STDIN_FILENO) == -1)
                        err(1, "dup2");
 
                /* Set stdout to write to the pipe. */
-               if (dup2(bar_pipe[1], 1) == -1)
+               if (dup2(bar_pipe[1], STDOUT_FILENO) == -1)
                        err(1, "dup2");
 
                if (signal(SIGPIPE, SIG_IGN) == SIG_ERR)
@@ -2514,8 +2557,9 @@ bar_setup(struct swm_region *r)
        /* Assume region is unfocused when we create the bar. */
        r->bar->id = xcb_generate_id(conn);
        wa[0] = r->s->c[SWM_S_COLOR_BAR].pixel;
-       wa[1] = r->s->c[SWM_S_COLOR_UNFOCUS].pixel;
-       wa[2] = XCB_EVENT_MASK_EXPOSURE;
+       wa[1] = r->s->c[SWM_S_COLOR_BAR_BORDER_UNFOCUS].pixel;
+       wa[2] = XCB_EVENT_MASK_EXPOSURE | XCB_EVENT_MASK_POINTER_MOTION |
+           XCB_EVENT_MASK_POINTER_MOTION_HINT;
 
        xcb_create_window(conn, XCB_COPY_FROM_PARENT, r->bar->id, r->s->root,
            X(r->bar), Y(r->bar), WIDTH(r->bar), HEIGHT(r->bar),
@@ -2793,7 +2837,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)
@@ -2888,7 +2932,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;
@@ -2940,7 +2984,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,
@@ -2957,7 +3001,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)
@@ -3052,7 +3096,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++) {
@@ -3075,7 +3119,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++) {
@@ -3095,7 +3139,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++) {
@@ -3326,11 +3370,6 @@ set_region(struct swm_region *r)
        if (r == NULL)
                return;
 
-       /* Skip if only one region on this screen. */
-       rf = TAILQ_FIRST(&r->s->rl);
-       if (TAILQ_NEXT(rf, entry) == NULL)
-               goto out;
-
        rf = r->s->r_focus;
        /* Unfocus old region bar. */
        if (rf) {
@@ -3339,14 +3378,13 @@ set_region(struct swm_region *r)
 
                xcb_change_window_attributes(conn, rf->bar->id,
                    XCB_CW_BORDER_PIXEL,
-                   &r->s->c[SWM_S_COLOR_UNFOCUS].pixel);
+                   &r->s->c[SWM_S_COLOR_BAR_BORDER_UNFOCUS].pixel);
        }
 
        /* Set region bar border to focus_color. */
        xcb_change_window_attributes(conn, r->bar->id,
            XCB_CW_BORDER_PIXEL, &r->s->c[SWM_S_COLOR_BAR_BORDER].pixel);
 
-out:
        r->s->r_focus = r;
 }
 
@@ -3521,7 +3559,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;
@@ -3545,7 +3583,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;
@@ -3948,7 +3986,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;
@@ -3957,10 +3995,12 @@ stack(void) {
                        DNPRINTF(SWM_D_STACK, "stack: workspace: %d "
                            "(screen: %d, region: %d)\n", r->ws->idx, i, j++);
 
-                       /* start with screen geometry, adjust for bar */
+                       /* Adjust stack area for region bar and padding. */
                        g = r->g;
-                       g.w -= 2 * border_width;
-                       g.h -= 2 * border_width;
+                       g.x += region_padding;
+                       g.y += region_padding;
+                       g.w -= 2 * border_width + 2 * region_padding;
+                       g.h -= 2 * border_width + 2 * region_padding;
                        if (bar_enabled && r->ws->bar_enabled) {
                                if (!bar_at_bottom)
                                        g.y += bar_height;
@@ -4196,9 +4236,7 @@ stack_master(struct workspace *ws, struct swm_geometry *g, int rot, int flip)
        /*  stack all the tiled windows */
        i = j = 0, s = stacks;
        TAILQ_FOREACH(win, &ws->winlist, entry) {
-               if (win->transient || win->floating)
-                       continue;
-               if (win->iconic)
+               if (win->transient || win->floating || win->iconic)
                        continue;
 
                if (win->ewmh_flags & EWMH_F_FULLSCREEN) {
@@ -4216,16 +4254,20 @@ stack_master(struct workspace *ws, struct swm_geometry *g, int rot, int flip)
                        if (flip)
                                win_g.x = r_g.x;
                        else
-                               win_g.x += win_g.w + 2 * border_width;
+                               win_g.x += win_g.w + 2 * border_width +
+                                   tile_gap;
                        win_g.w = (r_g.w - msize -
-                           (stacks * 2 * border_width)) / stacks;
+                           (stacks * (2 * border_width + tile_gap))) / stacks;
                        if (s == 1)
                                win_g.w += (r_g.w - msize -
-                                   (stacks * 2 * border_width)) % stacks;
+                                   (stacks * (2 * border_width + tile_gap))) %
+                                   stacks;
                        s--;
                        j = 0;
                }
-               win_g.h = hrh - 2 * border_width;
+
+               win_g.h = hrh - 2 * border_width - tile_gap;
+
                if (rot) {
                        h_inc = win->sh.width_inc;
                        h_base = win->sh.base_width;
@@ -4233,6 +4275,7 @@ stack_master(struct workspace *ws, struct swm_geometry *g, int rot, int flip)
                        h_inc = win->sh.height_inc;
                        h_base = win->sh.base_height;
                }
+
                if (j == colno - 1) {
                        win_g.h = hrh + extra;
                } else if (h_inc > 1 && h_inc < h_slice) {
@@ -4252,7 +4295,7 @@ stack_master(struct workspace *ws, struct swm_geometry *g, int rot, int flip)
                if (j == 0)
                        win_g.y = r_g.y;
                else
-                       win_g.y += last_h + 2 * border_width;
+                       win_g.y += last_h + 2 * border_width + tile_gap;
 
                if (disable_border && !(bar_enabled && ws->bar_enabled) &&
                    winno == 1){
@@ -4262,6 +4305,7 @@ stack_master(struct workspace *ws, struct swm_geometry *g, int rot, int flip)
                } else {
                        bordered = 1;
                }
+
                if (rot) {
                        if (X(win) != win_g.y || Y(win) != win_g.x ||
                            WIDTH(win) != win_g.h || HEIGHT(win) != win_g.w) {
@@ -4429,7 +4473,7 @@ max_stack(struct workspace *ws, struct swm_geometry *g)
 {
        struct swm_geometry     gg = *g;
        struct ws_win           *w, *win = NULL, *parent = NULL;
-       int                     winno, num_screens;
+       int                     winno;
 
        DNPRINTF(SWM_D_STACK, "max_stack: workspace: %d\n", ws->idx);
 
@@ -4454,11 +4498,13 @@ max_stack(struct workspace *ws, struct swm_geometry *g)
        DNPRINTF(SWM_D_STACK, "max_stack: win: 0x%x\n", win->id);
 
        /* maximize all top level windows */
-       num_screens = xcb_setup_roots_length(xcb_get_setup(conn));
        TAILQ_FOREACH(w, &ws->winlist, entry) {
                if (w->transient || w->iconic)
                        continue;
 
+               if (!w->mapped && w != win)
+                       map_window(w);
+
                if (w->floating && !w->floatmaxed) {
                        /*
                         * retain geometry for retrieval on exit from
@@ -4482,14 +4528,9 @@ max_stack(struct workspace *ws, struct swm_geometry *g)
 
                        update_window(w);
                }
-
-               /* Unmap unwanted windows if not multi-screen. */
-               if (num_screens <= 1 && outputs <= 1 && w != win &&
-                   w != parent && w->transient != win->id)
-                       unmap_window(w);
        }
 
-       /* If a parent exists, map it first. */
+       /* If a parent exists, map/raise it first. */
        if (parent) {
                map_window(parent);
 
@@ -4502,10 +4543,10 @@ max_stack(struct workspace *ws, struct swm_geometry *g)
                        }
        }
 
-       /* Map focused window. */
+       /* Map/raise focused window. */
        map_window(win);
 
-       /* Finally, map children of focus window. */
+       /* 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);
@@ -4520,7 +4561,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;
@@ -4702,7 +4743,7 @@ get_win_name(xcb_window_t win)
                        free(r);
                        /* Use WM_NAME instead; no UTF-8. */
                        c = xcb_get_property(conn, 0, win, XCB_ATOM_WM_NAME,
-                               XCB_GET_PROPERTY_TYPE_ANY, 0, UINT_MAX);
+                           XCB_GET_PROPERTY_TYPE_ANY, 0, UINT_MAX);
                        r = xcb_get_property_reply(conn, c, NULL);
 
                        if (!r)
@@ -4714,7 +4755,7 @@ get_win_name(xcb_window_t win)
                }
                if (r->length > 0)
                        name = strndup(xcb_get_property_value(r),
-                                  xcb_get_property_value_length(r));
+                           xcb_get_property_value_length(r));
 
                free(r);
        }
@@ -5270,7 +5311,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;
 
@@ -5300,6 +5341,12 @@ resize(struct ws_win *win, union arg *args)
 
        focus_flush();
 
+       /* It's possible for win to have been freed during focus_flush(). */
+       if (validate_win(win)) {
+               DNPRINTF(SWM_D_EVENT, "move: invalid win.\n");
+               goto out;
+       }
+
        switch (args->id) {
        case SWM_ARG_ID_WIDTHSHRINK:
                WIDTH(win) -= SWM_RESIZE_STEPS;
@@ -5356,7 +5403,7 @@ resize(struct ws_win *win, union arg *args)
 
        xcb_flush(conn);
        resizing = 1;
-       while ((evt = xcb_wait_for_event(conn)) && resizing) {
+       while (resizing && (evt = xcb_wait_for_event(conn))) {
                switch (XCB_EVENT_RESPONSE_TYPE(evt)) {
                case XCB_BUTTON_RELEASE:
                        DNPRINTF(SWM_D_EVENT, "resize: BUTTON_RELEASE\n");
@@ -5419,6 +5466,12 @@ resize(struct ws_win *win, union arg *args)
                        break;
                default:
                        event_handle(evt);
+
+                       /* It's possible for win to have been freed above. */
+                       if (validate_win(win)) {
+                               DNPRINTF(SWM_D_EVENT, "move: invalid win.\n");
+                               goto out;
+                       }
                        break;
                }
                free(evt);
@@ -5428,7 +5481,7 @@ resize(struct ws_win *win, union arg *args)
                xcb_flush(conn);
        }
        store_float_geom(win,r);
-
+out:
        xcb_ungrab_pointer(conn, XCB_CURRENT_TIME);
        free(xpr);
        DNPRINTF(SWM_D_EVENT, "resize: done.\n");
@@ -5456,7 +5509,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;
 
@@ -5487,6 +5540,12 @@ move(struct ws_win *win, union arg *args)
 
        focus_flush();
 
+       /* It's possible for win to have been freed during focus_flush(). */
+       if (validate_win(win)) {
+               DNPRINTF(SWM_D_EVENT, "move: invalid win.\n");
+               goto out;
+       }
+
        move_stp = 0;
        switch (args->id) {
        case SWM_ARG_ID_MOVELEFT:
@@ -5529,7 +5588,7 @@ move(struct ws_win *win, union arg *args)
 
        xcb_flush(conn);
        moving = 1;
-       while ((evt = xcb_wait_for_event(conn)) && moving) {
+       while (moving && (evt = xcb_wait_for_event(conn))) {
                switch (XCB_EVENT_RESPONSE_TYPE(evt)) {
                case XCB_BUTTON_RELEASE:
                        DNPRINTF(SWM_D_EVENT, "move: BUTTON_RELEASE\n");
@@ -5551,6 +5610,12 @@ move(struct ws_win *win, union arg *args)
                        break;
                default:
                        event_handle(evt);
+
+                       /* It's possible for win to have been freed above. */
+                       if (validate_win(win)) {
+                               DNPRINTF(SWM_D_EVENT, "move: invalid win.\n");
+                               goto out;
+                       }
                        break;
                }
                free(evt);
@@ -5560,6 +5625,7 @@ move(struct ws_win *win, union arg *args)
                xcb_flush(conn);
        }
        store_float_geom(win, r);
+out:
        free(qpr);
        xcb_ungrab_pointer(conn, XCB_CURRENT_TIME);
        DNPRINTF(SWM_D_EVENT, "move: done.\n");
@@ -5816,6 +5882,13 @@ spawn_expand(struct swm_region *r, union arg *args, const char *spawn_name,
                            strdup(r->s->c[SWM_S_COLOR_UNFOCUS].name))
                            == NULL)
                                err(1, "spawn_custom color unfocus");
+               } else if (!strcasecmp(ap, "$region_index")) {
+                       if (asprintf(&real_args[i], "%d",
+                           get_region_index(r) + 1) < 1)
+                               err(1, "spawn_custom region index");
+               } else if (!strcasecmp(ap, "$workspace_index")) {
+                       if (asprintf(&real_args[i], "%d", r->ws->idx + 1) < 1)
+                               err(1, "spawn_custom workspace index");
                } else {
                        /* no match --> copy as is */
                        if ((real_args[i] = strdup(ap)) == NULL)
@@ -5877,9 +5950,9 @@ spawn_select(struct swm_region *r, union arg *args, const char *spawn_name,
                err(1, "cannot fork");
                break;
        case 0: /* child */
-               if (dup2(select_list_pipe[0], 0) == -1)
+               if (dup2(select_list_pipe[0], STDIN_FILENO) == -1)
                        err(1, "dup2");
-               if (dup2(select_resp_pipe[1], 1) == -1)
+               if (dup2(select_resp_pipe[1], STDOUT_FILENO) == -1)
                        err(1, "dup2");
                close(select_list_pipe[1]);
                close(select_resp_pipe[0]);
@@ -5897,7 +5970,7 @@ spawn_select(struct swm_region *r, union arg *args, const char *spawn_name,
 }
 
 void
-spawn_insert(const char *name, const char *args)
+spawn_insert(const char *name, const char *args, int flags)
 {
        char                    *arg, *cp, *ptr;
        struct spawn_prog       *sp;
@@ -5926,6 +5999,8 @@ spawn_insert(const char *name, const char *args)
        }
        free(cp);
 
+       sp->flags = flags;
+
        TAILQ_INSERT_TAIL(&spawns, sp, entry);
        DNPRINTF(SWM_D_SPAWN, "spawn_insert: leave\n");
 }
@@ -5948,18 +6023,7 @@ spawn_remove(struct spawn_prog *sp)
 }
 
 void
-spawn_replace(struct spawn_prog *sp, const char *name, const char *args)
-{
-       DNPRINTF(SWM_D_SPAWN, "spawn_replace: %s [%s]\n", sp->name, name);
-
-       spawn_remove(sp);
-       spawn_insert(name, args);
-
-       DNPRINTF(SWM_D_SPAWN, "spawn_replace: leave\n");
-}
-
-void
-setspawn(const char *name, const char *args)
+setspawn(const char *name, const char *args, int flags)
 {
        struct spawn_prog       *sp;
 
@@ -5968,38 +6032,31 @@ setspawn(const char *name, const char *args)
        if (name == NULL)
                return;
 
-       TAILQ_FOREACH(sp, &spawns, entry) {
+       /* Remove any old spawn under the same name. */
+       TAILQ_FOREACH(sp, &spawns, entry)
                if (!strcmp(sp->name, name)) {
-                       if (*args == '\0')
-                               spawn_remove(sp);
-                       else
-                               spawn_replace(sp, name, args);
-                       DNPRINTF(SWM_D_SPAWN, "setspawn: leave\n");
-                       return;
+                       spawn_remove(sp);
+                       break;
                }
-       }
-       if (*args == '\0') {
+
+       if (*args != '\0')
+               spawn_insert(name, args, flags);
+       else
                warnx("error: setspawn: cannot find program: %s", name);
-               return;
-       }
 
-       spawn_insert(name, args);
        DNPRINTF(SWM_D_SPAWN, "setspawn: leave\n");
 }
 
 int
 setconfspawn(char *selector, char *value, int flags)
 {
-       char *args;
-
-       /* suppress unused warning since var is needed */
-       (void)flags;
+       char            *args;
 
        args = expand_tilde(value);
 
        DNPRINTF(SWM_D_SPAWN, "setconfspawn: [%s] [%s]\n", selector, args);
 
-       setspawn(selector, args);
+       setspawn(selector, args, flags);
        free(args);
 
        DNPRINTF(SWM_D_SPAWN, "setconfspawn: done.\n");
@@ -6007,20 +6064,57 @@ setconfspawn(char *selector, char *value, int flags)
 }
 
 void
+validate_spawns(void)
+{
+       struct spawn_prog       *sp;
+       char                    which[PATH_MAX];
+       size_t                  i;
+
+       struct key              *kp;
+
+       RB_FOREACH(kp, key_tree, &keys) {
+               if (kp->funcid != KF_SPAWN_CUSTOM)
+                       continue;
+
+               /* find program */
+               TAILQ_FOREACH(sp, &spawns, entry) {
+                       if (!strcasecmp(kp->spawn_name, sp->name))
+                               break;
+               }
+
+               if (sp == NULL || sp->flags & SWM_SPAWN_OPTIONAL)
+                       continue;
+
+               /* verify we have the goods */
+               snprintf(which, sizeof which, "which %s", sp->argv[0]);
+               DNPRINTF(SWM_D_CONF, "validate_spawns: which %s\n",
+                   sp->argv[0]);
+               for (i = strlen("which "); i < strlen(which); i++)
+                       if (which[i] == ' ') {
+                               which[i] = '\0';
+                               break;
+                       }
+               if (system(which) != 0)
+                       add_startup_exception("could not find %s",
+                           &which[strlen("which ")]);
+       }
+}
+
+void
 setup_spawn(void)
 {
+       setconfspawn("lock",            "xlock",                0);
+
        setconfspawn("term",            "xterm",                0);
        setconfspawn("spawn_term",      "xterm",                0);
-       setconfspawn("screenshot_all",  "screenshot.sh full",   0);
-       setconfspawn("screenshot_wind", "screenshot.sh window", 0);
-       setconfspawn("lock",            "xlock",                0);
-       setconfspawn("initscr",         "initscreen.sh",        0);
+
        setconfspawn("menu",            "dmenu_run"
                                        " -fn $bar_font"
                                        " -nb $bar_color"
                                        " -nf $bar_font_color"
                                        " -sb $bar_border"
                                        " -sf $bar_color",      0);
+
        setconfspawn("search",          "dmenu"
                                        " -i"
                                        " -fn $bar_font"
@@ -6028,6 +6122,7 @@ setup_spawn(void)
                                        " -nf $bar_font_color"
                                        " -sb $bar_border"
                                        " -sf $bar_color",      0);
+
        setconfspawn("name_workspace",  "dmenu"
                                        " -p Workspace"
                                        " -fn $bar_font"
@@ -6035,6 +6130,11 @@ setup_spawn(void)
                                        " -nf $bar_font_color"
                                        " -sb $bar_border"
                                        " -sf $bar_color",      0);
+
+        /* These are not verified for existence, even with a binding set. */
+       setconfspawn("screenshot_all",  "screenshot.sh full",   SWM_SPAWN_OPTIONAL);
+       setconfspawn("screenshot_wind", "screenshot.sh window", SWM_SPAWN_OPTIONAL);
+       setconfspawn("initscr",         "initscreen.sh",        SWM_SPAWN_OPTIONAL);
 }
 
 /* key bindings */
@@ -6208,8 +6308,8 @@ setconfbinding(char *selector, char *value, int flags)
        for (kfid = 0; kfid < KF_INVALID; (kfid)++) {
                if (strncasecmp(selector, keyfuncs[kfid].name,
                    SWM_FUNCNAME_LEN) == 0) {
-                       DNPRINTF(SWM_D_KEY, "setconfbinding: %s: match\n",
-                           selector);
+                       DNPRINTF(SWM_D_KEY, "setconfbinding: %s: match "
+                           "keyfunc\n", selector);
                        if (parsekeys(value, mod_key, &mod, &ks) == 0) {
                                setkeybinding(mod, ks, kfid, NULL);
                                return (0);
@@ -6220,8 +6320,8 @@ setconfbinding(char *selector, char *value, int flags)
        /* search by custom spawn name */
        TAILQ_FOREACH(sp, &spawns, entry) {
                if (strcasecmp(selector, sp->name) == 0) {
-                       DNPRINTF(SWM_D_KEY, "setconfbinding: %s: match\n",
-                           selector);
+                       DNPRINTF(SWM_D_KEY, "setconfbinding: %s: match "
+                           "spawn\n", selector);
                        if (parsekeys(value, mod_key, &mod, &ks) == 0) {
                                setkeybinding(mod, ks, KF_SPAWN_CUSTOM,
                                    sp->name);
@@ -6373,24 +6473,25 @@ clear_keys(void)
 int
 setkeymapping(char *selector, char *value, int flags)
 {
-       char                    keymapping_file[PATH_MAX];
+       char                    *keymapping_file;
 
        /* suppress unused warnings since vars are needed */
        (void)selector;
        (void)flags;
 
        DNPRINTF(SWM_D_KEY, "setkeymapping: enter\n");
-       if (value[0] == '~')
-               snprintf(keymapping_file, sizeof keymapping_file, "%s/%s",
-                   pwd->pw_dir, &value[1]);
-       else
-               strlcpy(keymapping_file, value, sizeof keymapping_file);
+
+       keymapping_file = expand_tilde(value);
+
        clear_keys();
        /* load new key bindings; if it fails, revert to default bindings */
        if (conf_load(keymapping_file, SWM_CONF_KEYMAPPING)) {
                clear_keys();
                setup_keys();
        }
+
+       free(keymapping_file);
+
        DNPRINTF(SWM_D_KEY, "setkeymapping: leave\n");
        return (0);
 }
@@ -6441,7 +6542,7 @@ 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;
@@ -6494,6 +6595,8 @@ const char *quirkname[] = {
        "XTERM_FONTADJ",
        "FULLSCREEN",
        "FOCUSPREV",
+       "NOFOCUSONMAP",
+       "FOCUSONMAP_SINGLE",
 };
 
 /* SWM_Q_WS: retain '|' for back compat for now (2009-08-11) */
@@ -6673,12 +6776,14 @@ enum {
        SWM_S_FOCUS_CLOSE_WRAP,
        SWM_S_FOCUS_DEFAULT,
        SWM_S_FOCUS_MODE,
+       SWM_S_REGION_PADDING,
        SWM_S_SPAWN_ORDER,
        SWM_S_SPAWN_TERM,
        SWM_S_SS_APP,
        SWM_S_SS_ENABLED,
        SWM_S_STACK_ENABLED,
        SWM_S_TERM_WIDTH,
+       SWM_S_TILE_GAP,
        SWM_S_TITLE_CLASS_ENABLED,
        SWM_S_TITLE_NAME_ENABLED,
        SWM_S_URGENT_ENABLED,
@@ -6723,7 +6828,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);
@@ -6828,6 +6933,11 @@ setconfvalue(char *selector, char *value, int flags)
                else
                        errx(1, "focus_mode");
                break;
+       case SWM_S_REGION_PADDING:
+               region_padding = atoi(value);
+               if (region_padding < 0)
+                       region_padding = 0;
+               break;
        case SWM_S_SPAWN_ORDER:
                if (!strcmp(value, "first"))
                        spawn_position = SWM_STACK_BOTTOM;
@@ -6858,6 +6968,11 @@ setconfvalue(char *selector, char *value, int flags)
                if (term_width < 0)
                        term_width = 0;
                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);
                break;
@@ -7044,7 +7159,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];
@@ -7098,6 +7213,7 @@ struct config_option configopt[] = {
        { "bar_action",                 setconfvalue,   SWM_S_BAR_ACTION },
        { "bar_at_bottom",              setconfvalue,   SWM_S_BAR_AT_BOTTOM },
        { "bar_border",                 setconfcolor,   SWM_S_COLOR_BAR_BORDER },
+       { "bar_border_unfocus",         setconfcolor,   SWM_S_COLOR_BAR_BORDER_UNFOCUS },
        { "bar_border_width",           setconfvalue,   SWM_S_BAR_BORDER_WIDTH },
        { "bar_color",                  setconfcolor,   SWM_S_COLOR_BAR },
        { "bar_delay",                  setconfvalue,   SWM_S_BAR_DELAY },
@@ -7127,12 +7243,14 @@ struct config_option configopt[] = {
        { "program",                    setconfspawn,   0 },
        { "quirk",                      setconfquirk,   0 },
        { "region",                     setconfregion,  0 },
+       { "region_padding",             setconfvalue,   SWM_S_REGION_PADDING },
        { "screenshot_app",             setconfvalue,   SWM_S_SS_APP },
        { "screenshot_enabled",         setconfvalue,   SWM_S_SS_ENABLED },
        { "spawn_position",             setconfvalue,   SWM_S_SPAWN_ORDER },
        { "spawn_term",                 setconfvalue,   SWM_S_SPAWN_TERM },
        { "stack_enabled",              setconfvalue,   SWM_S_STACK_ENABLED },
        { "term_width",                 setconfvalue,   SWM_S_TERM_WIDTH },
+       { "tile_gap",                   setconfvalue,   SWM_S_TILE_GAP },
        { "title_class_enabled",        setconfvalue,   SWM_S_TITLE_CLASS_ENABLED },
        { "title_name_enabled",         setconfvalue,   SWM_S_TITLE_NAME_ENABLED },
        { "urgent_enabled",             setconfvalue,   SWM_S_URGENT_ENABLED },
@@ -7141,11 +7259,36 @@ struct config_option configopt[] = {
        { "workspace_limit",            setconfvalue,   SWM_S_WORKSPACE_LIMIT },
 };
 
+void
+_add_startup_exception(const char *fmt, va_list ap)
+{
+       if (vasprintf(&startup_exception, fmt, ap) == -1)
+               warn("%s: asprintf", __func__);
+}
+
+void
+add_startup_exception(const char *fmt, ...)
+{
+       va_list ap;
+
+       nr_exceptions++;
+
+       if (startup_exception)
+               return;
+
+       /* force bar to be enabled due to exception */
+       bar_enabled = 1;
+
+       va_start(ap, fmt);
+       _add_startup_exception(fmt, ap);
+       va_end(ap);
+}
+
 int
 conf_load(const char *filename, int keymapping)
 {
        FILE                    *config;
-       char                    *line, *cp, *optsub, *optval;
+       char                    *line = NULL, *cp, *optsub, *optval = NULL;
        size_t                  linelen, lineno = 0;
        int                     wordlen, i, optidx;
        struct config_option    *opt = NULL;
@@ -7156,14 +7299,20 @@ conf_load(const char *filename, int keymapping)
                warnx("conf_load: no filename");
                return (1);
        }
+
+       DNPRINTF(SWM_D_CONF, "conf_load: open %s\n", filename);
+
        if ((config = fopen(filename, "r")) == NULL) {
                warn("conf_load: fopen: %s", filename);
                return (1);
        }
 
        while (!feof(config)) {
-               if ((line = fparseln(config, &linelen, &lineno, NULL, 0))
-                   == NULL) {
+               if (line)
+                       free(line);
+
+               if ((line = fparseln(config, &linelen, &lineno, NULL,
+                   FPARSELN_UNESCCOMM | FPARSELN_UNESCCONT)) == NULL) {
                        if (ferror(config))
                                err(1, "%s", filename);
                        else
@@ -7173,15 +7322,14 @@ conf_load(const char *filename, int keymapping)
                cp += strspn(cp, " \t\n"); /* eat whitespace */
                if (cp[0] == '\0') {
                        /* empty line */
-                       free(line);
                        continue;
                }
                /* get config option */
                wordlen = strcspn(cp, "=[ \t\n");
                if (wordlen == 0) {
-                       warnx("%s: line %zd: no option found",
+                       add_startup_exception("%s: line %zd: no option found",
                            filename, lineno);
-                       goto out;
+                       continue;
                }
                optidx = -1;
                for (i = 0; i < LENGTH(configopt); i++) {
@@ -7193,17 +7341,20 @@ conf_load(const char *filename, int keymapping)
                        }
                }
                if (optidx == -1) {
-                       warnx("%s: line %zd: unknown option %.*s",
-                           filename, lineno, wordlen, cp);
-                       goto out;
+                       add_startup_exception("%s: line %zd: unknown option "
+                           "%.*s", filename, lineno, wordlen, cp);
+                       continue;
                }
                if (keymapping && opt && strcmp(opt->optname, "bind")) {
-                       warnx("%s: line %zd: invalid option %.*s",
-                           filename, lineno, wordlen, cp);
-                       goto out;
+                       add_startup_exception("%s: line %zd: invalid option "
+                           "%.*s", filename, lineno, wordlen, cp);
+                       continue;
                }
                cp += wordlen;
                cp += strspn(cp, " \t\n"); /* eat whitespace */
+
+               /* from here on out we call goto invalid to continue */
+
                /* get [selector] if any */
                optsub = NULL;
                if (*cp == '[') {
@@ -7211,17 +7362,17 @@ conf_load(const char *filename, int keymapping)
                        wordlen = strcspn(cp, "]");
                        if (*cp != ']') {
                                if (wordlen == 0) {
-                                       warnx("%s: line %zd: syntax error",
-                                           filename, lineno);
-                                       goto out;
+                                       add_startup_exception("%s: line %zd: "
+                                           "syntax error", filename, lineno);
+                                       goto invalid;
                                }
 
                                if (asprintf(&optsub, "%.*s", wordlen, cp) ==
                                    -1) {
-                                       warnx("%s: line %zd: unable to allocate"
-                                           "memory for selector", filename,
-                                           lineno);
-                                       goto out;
+                                       add_startup_exception("%s: line %zd: "
+                                           "unable to allocatememory for "
+                                           "selector", filename, lineno);
+                                       goto invalid;
                                }
                        }
                        cp += wordlen;
@@ -7230,27 +7381,36 @@ conf_load(const char *filename, int keymapping)
                cp += strspn(cp, "= \t\n"); /* eat trailing */
                /* get RHS value */
                optval = strdup(cp);
+               if (strlen(optval) == 0) {
+                       add_startup_exception("%s: line %zd: must supply value "
+                           "to %s", filename, lineno,
+                           configopt[optidx].optname);
+                       goto invalid;
+               }
                /* call function to deal with it all */
                if (configopt[optidx].func(optsub, optval,
-                   configopt[optidx].funcflags) != 0)
-                       errx(1, "%s: line %zd: invalid data for %s",
-                           filename, lineno, configopt[optidx].optname);
-               free(optval);
-               free(optsub);
-               free(line);
+                   configopt[optidx].funcflags) != 0) {
+                       add_startup_exception("%s: line %zd: invalid data for "
+                           "%s", filename, lineno, configopt[optidx].optname);
+                       goto invalid;
+               }
+invalid:
+               if (optval) {
+                       free(optval);
+                       optval = NULL;
+               }
+               if (optsub) {
+                       free(optsub);
+                       optsub = NULL;
+               }
        }
 
+       if (line)
+               free(line);
        fclose(config);
        DNPRINTF(SWM_D_CONF, "conf_load: end\n");
 
        return (0);
-
-out:
-       free(line);
-       fclose(config);
-       DNPRINTF(SWM_D_CONF, "conf_load: end with error.\n");
-
-       return (1);
 }
 
 void
@@ -7647,7 +7807,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))
@@ -8114,7 +8274,7 @@ enternotify(xcb_enter_notify_event_t *e)
                        /* If no windows on pointer region, then focus root. */
                        r = root_to_region(e->root, SWM_CK_POINTER);
                        if (r == NULL) {
-                               DNPRINTF(SWM_D_EVENT, "enterntoify: "
+                               DNPRINTF(SWM_D_EVENT, "enternotify: "
                                    "NULL region; ignoring.\n");
                                return;
                        }
@@ -8129,7 +8289,7 @@ enternotify(xcb_enter_notify_event_t *e)
                focus_win(get_focus_magic(win));
        }
 
-       focus_flush();
+       xcb_flush(conn);
 }
 
 #ifdef SWM_DEBUG
@@ -8183,7 +8343,7 @@ mappingnotify(xcb_mapping_notify_event_t *e)
 void
 maprequest(xcb_map_request_event_t *e)
 {
-       struct ws_win           *win;
+       struct ws_win           *win, *w = NULL;
        xcb_get_window_attributes_reply_t *war;
 
        DNPRINTF(SWM_D_EVENT, "maprequest: win 0x%x\n",
@@ -8207,8 +8367,25 @@ maprequest(xcb_map_request_event_t *e)
            (war->map_state == XCB_MAP_STATE_VIEWABLE));
 
        /* The new window should get focus; prepare. */
-       if (focus_mode != SWM_FOCUS_FOLLOW)
-               win->ws->focus_pending = get_focus_magic(win);
+       if (focus_mode != SWM_FOCUS_FOLLOW &&
+           !(win->quirks & SWM_Q_NOFOCUSONMAP)) {
+               if (win->quirks & SWM_Q_FOCUSONMAP_SINGLE) {
+                       /* See if other wins of same type are already mapped. */
+                       TAILQ_FOREACH(w, &win->ws->winlist, entry) {
+                               if (w == win || !w->mapped)
+                                       continue;
+
+                               if (!strcmp(w->ch.class_name,
+                                   win->ch.class_name) &&
+                                   !strcmp(w->ch.instance_name,
+                                   win->ch.instance_name))
+                                       break;
+                       }
+               }
+
+               if (w == NULL)
+                       win->ws->focus_pending = get_focus_magic(win);
+       }
 
        /* All windows need to be mapped if they are in the current workspace.*/
        if (win->ws->r)
@@ -8238,7 +8415,7 @@ motionnotify(xcb_motion_notify_event_t *e)
 
        last_event_time = e->time;
 
-       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 == e->root)
                        break;
@@ -8530,7 +8707,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);
@@ -8648,6 +8825,7 @@ scan_xrandr(int i)
        int                                             ncrtc = 0;
 #endif /* SWM_XRR_HAS_CRTC */
        struct swm_region                               *r;
+       struct ws_win                                   *win;
        int                                             num_screens;
        xcb_randr_get_screen_resources_current_cookie_t src;
        xcb_randr_get_screen_resources_current_reply_t  *srr;
@@ -8661,7 +8839,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");
 
@@ -8721,6 +8899,16 @@ scan_xrandr(int i)
                    screen->height_in_pixels);
 
 out:
+       /* Cleanup unused previously visible workspaces. */
+       TAILQ_FOREACH(r, &screens[i].orl, entry) {
+               TAILQ_FOREACH(win, &r->ws->winlist, entry)
+                       unmap_window(win);
+
+               /* The screen shouldn't focus on an unused region. */
+               if (screens[i].r_focus == r)
+                       screens[i].r_focus = NULL;
+       }
+
        DNPRINTF(SWM_D_MISC, "scan_xrandr: done.\n");
 }
 
@@ -8732,7 +8920,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)
@@ -8747,10 +8935,22 @@ screenchange(xcb_randr_screen_change_notify_event_t *e)
        print_win_geom(e->root);
 #endif
        /* add bars to all regions */
-       for (i = 0; i < num_screens; i++)
+       for (i = 0; i < num_screens; i++) {
                TAILQ_FOREACH(r, &screens[i].rl, entry)
                        bar_setup(r);
+       }
+
        stack();
+
+       /* Make sure a region has focus on each screen. */
+       for (i = 0; i < num_screens; i++) {
+               if (screens[i].r_focus == NULL) {
+                       r = TAILQ_FIRST(&screens[i].rl);
+                       if (r != NULL)
+                               focus_region(r);
+               }
+       }
+
        bar_draw();
        focus_flush();
 }
@@ -8771,7 +8971,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);
@@ -8869,7 +9069,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 "
@@ -8908,6 +9108,8 @@ setup_screens(void)
                setscreencolor("red", i + 1, SWM_S_COLOR_FOCUS);
                setscreencolor("rgb:88/88/88", i + 1, SWM_S_COLOR_UNFOCUS);
                setscreencolor("rgb:00/80/80", i + 1, SWM_S_COLOR_BAR_BORDER);
+               setscreencolor("rgb:00/40/40", i + 1,
+                   SWM_S_COLOR_BAR_BORDER_UNFOCUS);
                setscreencolor("black", i + 1, SWM_S_COLOR_BAR);
                setscreencolor("rgb:a0/a0/a0", i + 1, SWM_S_COLOR_BAR_FONT);
 
@@ -8986,7 +9188,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;
 
@@ -9015,14 +9217,13 @@ shutdown_cleanup(void)
                err(1, "can't disable alarm");
 
        bar_extra_stop();
-       bar_extra = 1;
        unmap_all();
 
        cursors_cleanup();
 
        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);
@@ -9229,6 +9430,8 @@ noconfig:
        if (cfile)
                conf_load(cfile, SWM_CONF_DEFAULT);
 
+       validate_spawns();
+
        setup_ewmh();
        /* set some values to work around bad programs */
        workaround();
@@ -9239,7 +9442,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);
@@ -9276,7 +9479,10 @@ noconfig:
                }
 
                FD_ZERO(&rd);
-               FD_SET(STDIN_FILENO, &rd);
+
+               if (bar_extra)
+                       FD_SET(STDIN_FILENO, &rd);
+
                FD_SET(xfd, &rd);
                tv.tv_sec = 1;
                tv.tv_usec = 0;
@@ -9300,6 +9506,9 @@ noconfig:
                        stdin_ready = 0;
                        bar_extra_update();
                }
+
+               bar_draw();
+               xcb_flush(conn);
        }
 done:
        shutdown_cleanup();