git.strcat.st

/strcat/wm.git/ - summarytreelogarchivereleases

subject
add masterstack tile layout & more wmc configuration
commit
a13e9b0799f56b6db3c967c21b9b83fef0083f16
date
2026-05-31T18:08:05Z
message
diff
 Makefile                  |   5 +
 config.mk                 |   1 +
 extras/bar.c              | 155 +++++++++++++++++++++++++-
 extras/bar.h              |   5 +
 extras/ewmh/ewmh.c        |   6 +
 extras/ewmh/ewmh.h        |   1 +
 extras/tile/masterstack.c | 257 +++++++++++++++++++++++++++++++++++++++++++
 extras/tile/masterstack.h |  12 ++
 wm.c                      | 274 ++++++++++++++++++++++++++++++++++++++++++----
 wmc.c                     |  57 ++++++++--
 10 files changed, 740 insertions(+), 33 deletions(-)

diff --git a/Makefile b/Makefile
index 8cafd27..6305c32 100644
--- a/Makefile
+++ b/Makefile
@@ -22,6 +22,11 @@ WM_EXTRAS += extras/bar.c
 CPPFLAGS += -DBAR
 endif
 
+ifeq ($(MASTERSTACK),1)
+WM_EXTRAS += extras/tile/masterstack.c
+CPPFLAGS += -DMASTERSTACK
+endif
+
 all: wm wmc
 
 wm: wm.c
diff --git a/config.mk b/config.mk
index 908d91d..d2cfe1e 100644
--- a/config.mk
+++ b/config.mk
@@ -2,6 +2,7 @@
 EWMH=1
 XINERAMA=1
 BAR=1
+MASTERSTACK=1
 
 # uncomment on obsd and other systems which use the X11R6 path.
 #CPPFLAGS += -I/usr/X11R6/include
diff --git a/extras/bar.c b/extras/bar.c
index e67898c..20a9743 100644
--- a/extras/bar.c
+++ b/extras/bar.c
@@ -1,4 +1,5 @@
 #include <X11/Xlib.h>
+#include <X11/Xatom.h>
 #include <X11/Xutil.h>
 #ifdef XINERAMA
 #include <X11/extensions/Xinerama.h>
@@ -7,7 +8,7 @@
 #include <stdio.h>
 #include <string.h>
 
-#define bar_height 18
+#define default_bar_height 18
 #define bar_padding 6
 #define max_status_len 512
 #define max_title_len 512
@@ -29,8 +30,12 @@ static GC bar_gc;
 static XFontStruct *bar_font;
 static int bar_screen;
 static int bar_selected_tag;
+static int bar_selected_tags[max_monitors];
 static int bar_tag_count;
 static int bar_monitor_count;
+static int bar_height;
+static int bar_show_empty = 1;
+static int bar_workspace_usage[max_monitors][max_tags];
 static int bar_margin_x;
 static int bar_margin_y;
 static int bar_is_bottom;
@@ -45,8 +50,29 @@ static char bar_title[max_title_len];
 static struct bar_monitor bar_monitors[max_monitors];
 
 void bar_reconfigure(void);
+void bar_draw(void);
 static unsigned long bar_alloc_color(const char *color_name, unsigned long fallback);
 
+static XFontStruct *
+bar_load_font(const char *font_name)
+{
+	XFontStruct *font;
+	char family[256];
+	int i;
+
+	if (bar_display == NULL || font_name == NULL || font_name[0] == '\0')
+	    return NULL;
+	font = XLoadQueryFont(bar_display, font_name);
+	if (font != NULL)
+	    return font;
+	for (i = 0; font_name[i] != '\0' && font_name[i] != ':' && i < (int)sizeof(family) - 1; i++)
+	    family[i] = font_name[i];
+	family[i] = '\0';
+	if (i > 0)
+	    return XLoadQueryFont(bar_display, family);
+	return NULL;
+}
+
 static int
 bar_text_width(const char *text)
 {
@@ -55,6 +81,23 @@ bar_text_width(const char *text)
 	return XTextWidth(bar_font, text, (int)strlen(text));
 }
 
+static int
+bar_text_baseline(void)
+{
+	int text_height;
+	int top_pad;
+
+	if (bar_font == NULL)
+	    return bar_height - 5;
+	text_height = bar_font->ascent + bar_font->descent;
+	if (text_height < 1)
+	    return bar_height - 5;
+	top_pad = (bar_height - text_height) / 2;
+	if (top_pad < 0)
+	    top_pad = 0;
+	return top_pad + bar_font->ascent;
+}
+
 static void
 bar_draw_rect(Window window, unsigned long color, int x, int y, int width, int height)
 {
@@ -125,9 +168,18 @@ bar_load_monitors(void)
 static void
 bar_create_windows(void)
 {
+	Atom net_wm_window_type;
+	Atom net_wm_window_type_dock;
 	XSetWindowAttributes attrs;
 	int i, window_x, window_y, window_width;
+	XClassHint class_hint;
+	char class_name[] = "wmbar";
+	char class_class[] = "wmbar";
 
+	net_wm_window_type = XInternAtom(bar_display, "_NET_WM_WINDOW_TYPE", False);
+	net_wm_window_type_dock = XInternAtom(bar_display, "_NET_WM_WINDOW_TYPE_DOCK", False);
+	class_hint.res_name = class_name;
+	class_hint.res_class = class_class;
 	attrs.override_redirect = True;
 	attrs.background_pixel = bar_bg_color;
 	attrs.event_mask = ExposureMask;
@@ -147,6 +199,9 @@ bar_create_windows(void)
 	        DefaultDepth(bar_display, bar_screen), CopyFromParent,
 	        DefaultVisual(bar_display, bar_screen),
 	        CWOverrideRedirect | CWBackPixel | CWEventMask, &attrs);
+	    XSetClassHint(bar_display, bar_monitors[i].window, &class_hint);
+	    XChangeProperty(bar_display, bar_monitors[i].window, net_wm_window_type, XA_ATOM, 32,
+	        PropModeReplace, (unsigned char *)&net_wm_window_type_dock, 1);
 	    XMapRaised(bar_display, bar_monitors[i].window);
 	}
 }
@@ -157,6 +212,7 @@ bar_init(Display *display, Window root_window)
 	bar_display = display;
 	bar_root = root_window;
 	bar_screen = DefaultScreen(display);
+	bar_height = default_bar_height;
 	bar_font = XLoadQueryFont(display, "fixed");
 	if (bar_font == NULL)
 	    return 0;
@@ -189,10 +245,62 @@ bar_set_tags(const int *tags, int count, int selected_tag)
 
 	bar_tag_count = count > max_tags ? max_tags : count;
 	bar_selected_tag = selected_tag;
+	for (i = 0; i < max_monitors; i++)
+	    bar_selected_tags[i] = selected_tag;
 	for (i = 0; i < bar_tag_count; i++)
 	    bar_tags[i] = tags[i];
 }
 
+void
+bar_set_selected_tags(const int *selected_tags, int count)
+{
+	int i;
+	int max_count;
+
+	if (selected_tags == NULL || count < 1)
+	    return;
+	max_count = count;
+	if (max_count > max_monitors)
+	    max_count = max_monitors;
+	for (i = 0; i < max_count; i++)
+	    bar_selected_tags[i] = selected_tags[i];
+	for (; i < max_monitors; i++)
+	    bar_selected_tags[i] = bar_selected_tags[0];
+}
+
+void
+bar_set_show_empty(int show_empty)
+{
+	bar_show_empty = show_empty ? 1 : 0;
+	bar_draw();
+}
+
+void
+bar_set_workspace_usage(const int *usage, int monitor_count, int workspace_count)
+{
+	int m, w;
+	int mc, wc;
+
+	if (usage == NULL)
+	    return;
+	mc = monitor_count;
+	wc = workspace_count;
+	if (mc < 1)
+	    mc = 1;
+	if (mc > max_monitors)
+	    mc = max_monitors;
+	if (wc < 1)
+	    wc = 1;
+	if (wc > max_tags)
+	    wc = max_tags;
+	for (m = 0; m < max_monitors; m++)
+	    for (w = 0; w < max_tags; w++)
+	        bar_workspace_usage[m][w] = 0;
+	for (m = 0; m < mc; m++)
+	    for (w = 0; w < wc; w++)
+	        bar_workspace_usage[m][w] = usage[m * wc + w] ? 1 : 0;
+}
+
 void
 bar_set_title(const char *title)
 {
@@ -206,35 +314,45 @@ bar_draw(void)
 {
 	int m, x, i, tw, tagw, title_x, status_x, bar_width;
 	char tag_text[8];
+	int baseline;
 
 	if (bar_display == NULL || bar_monitor_count < 1)
 	    return;
+	baseline = bar_text_baseline();
 	for (m = 0; m < bar_monitor_count; m++) {
+	    int selected_tag;
+
 	    if (bar_monitors[m].window == None)
 	        continue;
+	    selected_tag = bar_selected_tags[m];
 	    bar_width = bar_monitors[m].bar_width;
 	    bar_draw_rect(bar_monitors[m].window, bar_bg_color, 0, 0, bar_width, bar_height);
 	    x = 0;
 	    for (i = 0; i < bar_tag_count; i++) {
+	        int show_tag;
+
+	        show_tag = bar_show_empty || i == selected_tag || bar_workspace_usage[m][i];
+	        if (!show_tag)
+	            continue;
 	        snprintf(tag_text, sizeof(tag_text), "%d", bar_tags[i]);
 	        tw = bar_text_width(tag_text);
 	        tagw = tw + (bar_padding * 2);
-	        if (i == bar_selected_tag) {
+	        if (i == selected_tag) {
 	            bar_draw_rect(bar_monitors[m].window, bar_sel_bg_color, x, 0, tagw, bar_height);
-	            bar_draw_text(bar_monitors[m].window, bar_sel_accent_color, x + bar_padding, bar_height - 5, tag_text);
+	            bar_draw_text(bar_monitors[m].window, bar_sel_accent_color, x + bar_padding, baseline, tag_text);
 	        } else {
-	            bar_draw_text(bar_monitors[m].window, bar_fg_color, x + bar_padding, bar_height - 5, tag_text);
+	            bar_draw_text(bar_monitors[m].window, bar_fg_color, x + bar_padding, baseline, tag_text);
 	        }
 	        x += tagw + bar_padding;
 	    }
 	    status_x = bar_width - bar_padding - bar_text_width(bar_status);
 	    if (status_x < x + bar_padding)
 	        status_x = x + bar_padding;
-	    bar_draw_text(bar_monitors[m].window, bar_fg_color, status_x, bar_height - 5, bar_status);
+	    bar_draw_text(bar_monitors[m].window, bar_fg_color, status_x, baseline, bar_status);
 	    title_x = x + bar_padding;
 	    tw = bar_text_width(bar_title);
 	    if (title_x + tw < status_x - bar_padding)
-	        bar_draw_text(bar_monitors[m].window, bar_sel_fg_color, title_x, bar_height - 5, bar_title);
+	        bar_draw_text(bar_monitors[m].window, bar_sel_fg_color, title_x, baseline, bar_title);
 	}
 	XFlush(bar_display);
 }
@@ -262,6 +380,31 @@ bar_set_bottom(int is_bottom)
 	bar_reconfigure();
 }
 
+void
+bar_set_height(int height)
+{
+	if (height < 8)
+	    height = 8;
+	bar_height = height;
+	bar_reconfigure();
+}
+
+void
+bar_set_font(const char *font_name)
+{
+	XFontStruct *new_font;
+
+	new_font = bar_load_font(font_name);
+	if (new_font == NULL)
+	    return;
+	if (bar_font != NULL)
+	    XFreeFont(bar_display, bar_font);
+	bar_font = new_font;
+	if (bar_gc != None)
+	    XSetFont(bar_display, bar_gc, bar_font->fid);
+	bar_draw();
+}
+
 static unsigned long
 bar_alloc_color(const char *color_name, unsigned long fallback)
 {
diff --git a/extras/bar.h b/extras/bar.h
index f12da4b..664a20e 100644
--- a/extras/bar.h
+++ b/extras/bar.h
@@ -6,9 +6,14 @@
 int bar_init(Display *display, Window root_window);
 void bar_set_status(const char *status);
 void bar_set_tags(const int *tags, int count, int selected_tag);
+void bar_set_selected_tags(const int *selected_tags, int count);
 void bar_set_title(const char *title);
 void bar_set_margins(int margin_x, int margin_y);
 void bar_set_bottom(int is_bottom);
+void bar_set_height(int height);
+void bar_set_font(const char *font_name);
+void bar_set_show_empty(int show_empty);
+void bar_set_workspace_usage(const int *usage, int monitor_count, int workspace_count);
 void bar_set_colors(const char *fg, const char *bg, const char *sel_fg,
     const char *sel_bg, const char *sel_accent);
 void bar_reconfigure(void);
diff --git a/extras/ewmh/ewmh.c b/extras/ewmh/ewmh.c
index 8bc8b78..45e23bd 100644
--- a/extras/ewmh/ewmh.c
+++ b/extras/ewmh/ewmh.c
@@ -90,3 +90,9 @@ ewmh_sync(Display *display, Window root_window,
 	        PropModeReplace, (unsigned char *)&desktop, 1);
 	}
 }
+
+int
+ewmh_is_internal_window(Window window)
+{
+	return window == wm_check_window;
+}
diff --git a/extras/ewmh/ewmh.h b/extras/ewmh/ewmh.h
index a307b91..85f176a 100644
--- a/extras/ewmh/ewmh.h
+++ b/extras/ewmh/ewmh.h
@@ -8,5 +8,6 @@ void ewmh_set_current_desktop(Display *display, Window root_window, int current_
 void ewmh_set_active_window(Display *display, Window root_window, Window window);
 void ewmh_sync(Display *display, Window root_window,
     Window *windows, int *window_workspace, int window_count);
+int ewmh_is_internal_window(Window window);
 
 #endif
diff --git a/extras/tile/masterstack.c b/extras/tile/masterstack.c
new file mode 100644
index 0000000..0eaa2bd
--- /dev/null
+++ b/extras/tile/masterstack.c
@@ -0,0 +1,257 @@
+#include <X11/Xlib.h>
+#include <X11/Xatom.h>
+
+#include "masterstack.h"
+#ifdef XINERAMA
+#include "../xinerama.h"
+#endif
+
+#define max_workspace_windows 4096
+#define max_monitors 16
+#define master_factor_num 3
+#define master_factor_den 5
+
+static int inner_gap = 0;
+static int outer_gap = 0;
+static int top_gap = 0;
+
+void
+tile_masterstack_set_gaps(int inner, int outer)
+{
+	if (inner < 0)
+	    inner = inner_gap;
+	if (outer < 0)
+	    outer = outer_gap;
+	if (inner < 0)
+	    inner = 0;
+	if (outer < 0)
+	    outer = 0;
+	inner_gap = inner;
+	outer_gap = outer;
+}
+
+void
+tile_masterstack_set_top_gap(int top)
+{
+	if (top < 0)
+	    top = 0;
+	top_gap = top;
+}
+
+static int
+is_normal_window_type(Display *display, Window window)
+{
+	Atom net_wm_window_type;
+	Atom net_wm_window_type_normal;
+	Atom actual_type;
+	int actual_format;
+	unsigned long nitems, bytes_after;
+	unsigned char *prop_data;
+	int i;
+
+	net_wm_window_type = XInternAtom(display, "_NET_WM_WINDOW_TYPE", False);
+	net_wm_window_type_normal = XInternAtom(display, "_NET_WM_WINDOW_TYPE_NORMAL", False);
+	prop_data = NULL;
+	if (XGetWindowProperty(display, window, net_wm_window_type, 0, 8, False, XA_ATOM,
+	    &actual_type, &actual_format, &nitems, &bytes_after, &prop_data) != Success)
+	    return 1;
+	if (prop_data == NULL)
+	    return 1;
+	if (actual_type != XA_ATOM || actual_format != 32 || nitems < 1) {
+	    XFree(prop_data);
+	    return 1;
+	}
+	for (i = 0; i < (int)nitems; i++) {
+	    Atom wt;
+
+	    wt = ((Atom *)prop_data)[i];
+	    if (wt == net_wm_window_type_normal) {
+	        XFree(prop_data);
+	        return 1;
+	    }
+	}
+	XFree(prop_data);
+	return 0;
+}
+
+static int
+find_window_index(Window *windows, int window_count, Window window)
+{
+	int i;
+
+	for (i = 0; i < window_count; i++)
+	    if (windows[i] == window)
+	        return i;
+	return -1;
+}
+
+static int
+is_tile_candidate(Display *display, Window root_window, Window window,
+    Window *windows, int *window_workspace, int *window_floating,
+    int window_count, int current_workspace)
+{
+	XWindowAttributes window_attrs;
+	int idx;
+
+	if (window == None || window == PointerRoot || window == root_window)
+	    return 0;
+	if (!XGetWindowAttributes(display, window, &window_attrs))
+	    return 0;
+	if (window_attrs.override_redirect)
+	    return 0;
+	if (window_attrs.map_state != IsViewable)
+	    return 0;
+	if (!is_normal_window_type(display, window))
+	    return 0;
+	idx = find_window_index(windows, window_count, window);
+	if (idx < 0)
+	    return 0;
+	if (window_floating[idx])
+	    return 0;
+	if (window_workspace[idx] != current_workspace)
+	    return 0;
+	return 1;
+}
+
+static void
+apply_masterstack_region(Display *display, Window *region_windows, int region_count,
+    int x, int y, int width, int height)
+{
+	int i;
+	int area_x, area_y, area_w, area_h;
+
+	if (region_count < 1)
+	    return;
+	area_x = x + outer_gap;
+	area_y = y + outer_gap + top_gap;
+	area_w = width - (outer_gap * 2);
+	area_h = height - (outer_gap * 2) - top_gap;
+	if (area_w < 1)
+	    area_w = 1;
+	if (area_h < 1)
+	    area_h = 1;
+	if (region_count == 1) {
+	    XMoveResizeWindow(display, region_windows[0], area_x, area_y,
+	        (unsigned int)area_w, (unsigned int)area_h);
+	    return;
+	}
+
+	{
+	    int master_width, stack_width, stack_count, stack_y;
+	    int avail_w, avail_h, total_vgap;
+	    int stack_h_each, stack_h_extra;
+
+	    avail_w = area_w - inner_gap;
+	    if (avail_w < 2)
+	        avail_w = area_w;
+	    master_width = (avail_w * master_factor_num) / master_factor_den;
+	    if (master_width < 1)
+	        master_width = 1;
+	    if (master_width >= avail_w)
+	        master_width = avail_w - 1;
+	    if (master_width < 1)
+	        master_width = avail_w;
+	    stack_width = avail_w - master_width;
+	    if (stack_width < 1)
+	        stack_width = 1;
+	    if (master_width < 1)
+	        master_width = 1;
+	    XMoveResizeWindow(display, region_windows[0], area_x, area_y,
+	        (unsigned int)master_width, (unsigned int)area_h);
+	    stack_count = region_count - 1;
+	    if (stack_count < 1)
+	        return;
+
+	    total_vgap = inner_gap * (stack_count - 1);
+	    avail_h = area_h - total_vgap;
+	    if (avail_h < stack_count)
+	        avail_h = stack_count;
+	    stack_h_each = avail_h / stack_count;
+	    stack_h_extra = avail_h % stack_count;
+	    if (stack_h_each < 1)
+	        stack_h_each = 1;
+	    stack_y = area_y;
+	    for (i = 1; i < region_count; i++) {
+	        int h;
+
+	        h = stack_h_each;
+	        if ((i - 1) < stack_h_extra)
+	            h++;
+	        XMoveResizeWindow(display, region_windows[i], area_x + master_width + inner_gap, stack_y,
+	            (unsigned int)stack_width, (unsigned int)h);
+	        stack_y += h;
+	        if (i < region_count - 1)
+	            stack_y += inner_gap;
+	    }
+	}
+}
+
+void
+tile_masterstack_apply(Display *display, Window root_window,
+    Window *windows, int *window_workspace, int *window_floating,
+    int window_count, int current_workspace)
+{
+	XWindowAttributes root_attrs;
+	Window tiled[max_workspace_windows];
+	int tiled_count;
+	int i;
+	int monitor_count;
+	int monitor_x[max_monitors], monitor_y[max_monitors];
+	int monitor_w[max_monitors], monitor_h[max_monitors];
+
+	if (!XGetWindowAttributes(display, root_window, &root_attrs))
+	    return;
+
+	tiled_count = 0;
+	for (i = 0; i < window_count && tiled_count < max_workspace_windows; i++) {
+	    if (is_tile_candidate(display, root_window, windows[i],
+	            windows, window_workspace, window_floating, window_count, current_workspace))
+	        tiled[tiled_count++] = windows[i];
+	}
+	if (tiled_count < 1)
+	    return;
+
+	monitor_count = 1;
+	monitor_x[0] = 0;
+	monitor_y[0] = 0;
+	monitor_w[0] = root_attrs.width;
+	monitor_h[0] = root_attrs.height;
+
+#ifdef XINERAMA
+	monitor_count = xinerama_init(display, root_window);
+	if (monitor_count < 1)
+	    monitor_count = 1;
+	if (monitor_count > max_monitors)
+	    monitor_count = max_monitors;
+	for (i = 0; i < monitor_count; i++) {
+	    if (!xinerama_monitor_geometry(i, &monitor_x[i], &monitor_y[i],
+	            &monitor_w[i], &monitor_h[i])) {
+	        monitor_x[i] = monitor_x[0];
+	        monitor_y[i] = monitor_y[0];
+	        monitor_w[i] = monitor_w[0];
+	        monitor_h[i] = monitor_h[0];
+	    }
+	}
+#endif
+
+	for (i = 0; i < monitor_count; i++) {
+	    Window mon_windows[max_workspace_windows];
+	    int mon_count;
+	    int k;
+
+	    mon_count = 0;
+	    for (k = 0; k < tiled_count; k++) {
+	        int mon;
+
+#ifdef XINERAMA
+	        mon = xinerama_monitor_for_window(display, root_window, tiled[k]);
+#else
+	        mon = 0;
+#endif
+	        if (mon == i)
+	            mon_windows[mon_count++] = tiled[k];
+	    }
+	    apply_masterstack_region(display, mon_windows, mon_count,
+	        monitor_x[i], monitor_y[i], monitor_w[i], monitor_h[i]);
+	}
+}
diff --git a/extras/tile/masterstack.h b/extras/tile/masterstack.h
new file mode 100644
index 0000000..6fa5729
--- /dev/null
+++ b/extras/tile/masterstack.h
@@ -0,0 +1,12 @@
+#ifndef MASTERSTACK_H
+#define MASTERSTACK_H
+
+#include <X11/Xlib.h>
+
+void tile_masterstack_apply(Display *display, Window root_window,
+    Window *windows, int *window_workspace, int *window_floating,
+    int window_count, int current_workspace);
+void tile_masterstack_set_gaps(int inner_gap, int outer_gap);
+void tile_masterstack_set_top_gap(int top_gap);
+
+#endif
diff --git a/wm.c b/wm.c
index 4b98a9f..3afc661 100644
--- a/wm.c
+++ b/wm.c
@@ -7,6 +7,9 @@
 #ifdef XINERAMA
 #include "extras/xinerama.h"
 #endif
+#ifdef MASTERSTACK
+#include "extras/tile/masterstack.h"
+#endif
 #ifdef BAR
 #include "extras/bar.h"
 #endif
@@ -26,20 +29,97 @@
 #define cmd_bar_sf 10
 #define cmd_bar_sb 11
 #define cmd_bar_sa 12
+#define cmd_gap_inner 13
+#define cmd_gap_outer 14
+#define cmd_gap_top 15
+#define cmd_tile_focused 16
+#define cmd_bar_height 17
+#define cmd_bar_font 18
+#define cmd_bar_showempty 19
 #define num_workspaces 9
 #define modifier_variants 4
+#define max_monitors 16
 
 static Window windows[max_windows];
-static int window_workspace[max_windows], window_count, current_workspace;
+static int window_workspace[max_windows], window_floating[max_windows], window_count;
+static int current_workspace[max_monitors], monitor_count;
 static Atom net_wm_desktop_atom;
 #ifdef BAR
 static Atom wm_bar_margin_x_atom, wm_bar_margin_y_atom, wm_bar_bottom_atom;
 static Atom wm_bar_nf_atom, wm_bar_nb_atom, wm_bar_sf_atom, wm_bar_sb_atom, wm_bar_sa_atom;
+static Atom wm_bar_height_atom, wm_bar_font_atom;
+static Atom wm_bar_showempty_atom;
 static Atom net_wm_name_atom, utf8_string_atom;
 #endif
+#ifdef MASTERSTACK
+static Atom wm_gap_inner_atom, wm_gap_outer_atom, wm_gap_top_atom;
+#endif
 static int is_root_child(Display *display, Window root_window, Window window);
 static void select_window_events(Display *display, Window window);
 
+static int
+monitor_for_window(Display *display, Window root_window, Window window)
+{
+#ifdef XINERAMA
+	int mon;
+
+	mon = xinerama_monitor_for_window(display, root_window, window);
+	if (mon < 0 || mon >= monitor_count)
+	    return 0;
+	return mon;
+#else
+	(void)display;
+	(void)root_window;
+	(void)window;
+	return 0;
+#endif
+}
+
+static int
+active_monitor(Display *display, Window root_window)
+{
+	Window root_return, child_return;
+	int root_x, root_y, window_x, window_y;
+	unsigned int mask;
+
+	if (!XQueryPointer(display, root_window, &root_return, &child_return,
+	    &root_x, &root_y, &window_x, &window_y, &mask))
+	    return 0;
+#ifdef XINERAMA
+	{
+	    int mon;
+
+	    mon = xinerama_monitor_for_point(root_x, root_y);
+	    if (mon < 0 || mon >= monitor_count)
+	        return 0;
+	    return mon;
+	}
+#else
+	(void)root_x;
+	(void)root_y;
+	return 0;
+#endif
+}
+
+#ifdef MASTERSTACK
+static void
+apply_layout(Display *display, Window root_window)
+{
+	int mon;
+
+	mon = active_monitor(display, root_window);
+	tile_masterstack_apply(display, root_window, windows, window_workspace,
+	    window_floating, window_count, current_workspace[mon]);
+}
+#else
+static void
+apply_layout(Display *display, Window root_window)
+{
+	(void)display;
+	(void)root_window;
+}
+#endif
+
 static int
 find_window_index(Window window)
 {
@@ -139,10 +219,34 @@ static void
 refresh_bar(Display *display)
 {
 	static int tags[num_workspaces] = { 1, 2, 3, 4, 5, 6, 7, 8, 9 };
+	int mon_tags[max_monitors];
+	int usage[max_monitors * num_workspaces];
 	Window focused_window;
 	int revert_to;
+	int i, j;
 
-	bar_set_tags(tags, num_workspaces, current_workspace);
+	for (i = 0; i < max_monitors * num_workspaces; i++)
+	    usage[i] = 0;
+	for (i = 0; i < window_count; i++) {
+	    int mon;
+	    int ws;
+
+	    mon = monitor_for_window(display, DefaultRootWindow(display), windows[i]);
+	    ws = window_workspace[i];
+	    if (mon >= 0 && mon < max_monitors && ws >= 0 && ws < num_workspaces)
+	        usage[mon * num_workspaces + ws] = 1;
+	}
+	for (i = 0; i < monitor_count && i < max_monitors; i++)
+	    mon_tags[i] = current_workspace[i];
+	if (i < 1) {
+	    mon_tags[0] = 0;
+	    i = 1;
+	}
+	for (j = i; j < max_monitors; j++)
+	    mon_tags[j] = mon_tags[0];
+	bar_set_tags(tags, num_workspaces, mon_tags[0]);
+	bar_set_selected_tags(mon_tags, i);
+	bar_set_workspace_usage(usage, monitor_count, num_workspaces);
 	XGetInputFocus(display, &focused_window, &revert_to);
 	set_bar_title_from_window(display, focused_window);
 	bar_set_status("");
@@ -172,7 +276,8 @@ get_window_index(Window window)
 	if (window == None || window_count >= max_windows)
 	    return -1;
 	windows[window_count] = window;
-	window_workspace[window_count] = current_workspace;
+	window_workspace[window_count] = 0;
+	window_floating[window_count] = 0;
 	return window_count++;
 }
 
@@ -205,7 +310,7 @@ get_window_desktop(Display *display, Window window)
 	return (int)desktop;
 }
 
-#ifdef BAR
+#if defined(BAR) || defined(MASTERSTACK)
 static int
 read_root_int_property(Display *display, Window root_window, Atom atom, int default_value)
 {
@@ -239,7 +344,9 @@ write_root_int_property(Display *display, Window root_window, Atom atom, int val
 	XChangeProperty(display, root_window, atom, XA_CARDINAL, 32, PropModeReplace,
 	    (unsigned char *)&card, 1);
 }
+#endif
 
+#ifdef BAR
 static char *
 read_root_string_property(Display *display, Window root_window, Atom atom)
 {
@@ -292,6 +399,7 @@ remove_window(Window window)
 	        for (; i < window_count - 1; i++) {
 	            windows[i] = windows[i + 1];
 	            window_workspace[i] = window_workspace[i + 1];
+	            window_floating[i] = window_floating[i + 1];
 	        }
 	        window_count--;
 	        return;
@@ -333,6 +441,8 @@ is_managed_window(Display *display, Window root_window, Window window)
 {
 	XWindowAttributes window_attrs;
 
+	if (ewmh_is_internal_window(window))
+	    return 0;
 	if (!is_root_child(display, root_window, window))
 	    return 0;
 	get_window_attrs(display, window, &window_attrs);
@@ -378,18 +488,23 @@ is_root_child(Display *display, Window root_window, Window window)
 }
 
 static void
-show_workspace(Display *display, Window root_window)
+show_workspace(Display *display, Window root_window, int target_monitor)
 {
 	Window root_return, parent, *children;
 	int i, j, window_index;
 
 	if (XQueryTree(display, root_window, &root_return, &parent, &children, (unsigned int *)&j)) {
 	    for (i = 0; i < j; i++) {
+	        int mon;
+
 	        if (!is_managed_window(display, root_window, children[i]))
 	            continue;
+	        mon = monitor_for_window(display, root_window, children[i]);
+	        if (mon != target_monitor)
+	            continue;
 	        window_index = get_window_index(children[i]);
 	        if (window_index >= 0) {
-	            if (window_workspace[window_index] == current_workspace)
+	            if (window_workspace[window_index] == current_workspace[target_monitor])
 	                XMapWindow(display, children[i]);
 	            else
 	                XUnmapWindow(display, children[i]);
@@ -417,6 +532,9 @@ main(int argc, char **argv)
 	is_dragging = 0;
 	drag_window = None;
 	(void)argc;
+	monitor_count = 1;
+	for (i = 0; i < max_monitors; i++)
+	    current_workspace[i] = 0;
 	if ((display = XOpenDisplay(NULL)) == NULL)
 	    return 1;
 	XSetErrorHandler(x_error_handler);
@@ -433,13 +551,25 @@ main(int argc, char **argv)
 	wm_bar_sf_atom = XInternAtom(display, "_WM_BAR_SF", False);
 	wm_bar_sb_atom = XInternAtom(display, "_WM_BAR_SB", False);
 	wm_bar_sa_atom = XInternAtom(display, "_WM_BAR_SA", False);
+	wm_bar_height_atom = XInternAtom(display, "_WM_BAR_HEIGHT", False);
+	wm_bar_font_atom = XInternAtom(display, "_WM_BAR_FONT", False);
+	wm_bar_showempty_atom = XInternAtom(display, "_WM_BAR_SHOWEMPTY", False);
 	net_wm_name_atom = XInternAtom(display, "_NET_WM_NAME", False);
 	utf8_string_atom = XInternAtom(display, "UTF8_STRING", False);
+#endif
+#ifdef MASTERSTACK
+	wm_gap_inner_atom = XInternAtom(display, "_WM_GAP_INNER", False);
+	wm_gap_outer_atom = XInternAtom(display, "_WM_GAP_OUTER", False);
+	wm_gap_top_atom = XInternAtom(display, "_WM_GAP_TOP", False);
 #endif
 	XSelectInput(display, root_window, SubstructureNotifyMask | StructureNotifyMask);
 	track_windows(display, root_window);
 #ifdef XINERAMA
-	xinerama_init(display, root_window);
+	monitor_count = xinerama_init(display, root_window);
+	if (monitor_count < 1)
+	    monitor_count = 1;
+	if (monitor_count > max_monitors)
+	    monitor_count = max_monitors;
 #endif
 	ewmh_init(display, root_window);
 	ewmh_sync(display, root_window, windows, window_workspace, window_count);
@@ -463,8 +593,26 @@ main(int argc, char **argv)
 	bar_set_margins(read_root_int_property(display, root_window, wm_bar_margin_x_atom, 0),
 	    read_root_int_property(display, root_window, wm_bar_margin_y_atom, 0));
 	bar_set_bottom(read_root_int_property(display, root_window, wm_bar_bottom_atom, 0) ? 1 : 0);
+	bar_set_height(read_root_int_property(display, root_window, wm_bar_height_atom, 18));
+	bar_set_show_empty(read_root_int_property(display, root_window, wm_bar_showempty_atom, 1));
+	{
+	    char *font_name;
+
+	    font_name = read_root_string_property(display, root_window, wm_bar_font_atom);
+	    if (font_name != NULL) {
+	        bar_set_font(font_name);
+	        free(font_name);
+	    }
+	}
 	refresh_bar(display);
 #endif
+#ifdef MASTERSTACK
+	tile_masterstack_set_gaps(
+	    read_root_int_property(display, root_window, wm_gap_inner_atom, 0),
+	    read_root_int_property(display, root_window, wm_gap_outer_atom, 0));
+	tile_masterstack_set_top_gap(
+	    read_root_int_property(display, root_window, wm_gap_top_atom, 0));
+#endif
 
 	grab_button_variants(display, 1, 0, root_window, GrabModeSync);
 	grab_button_variants(display, 1, mod_key, root_window, GrabModeAsync);
@@ -474,20 +622,36 @@ main(int argc, char **argv)
 	    XNextEvent(display, &event);
 	    if (event.type == ClientMessage && event.xclient.message_type == control_atom) {
 	        int command;
-#ifdef BAR
+#if defined(BAR) || defined(MASTERSTACK)
 	        int value;
 #endif
 
 	        command = (int)event.xclient.data.l[0];
 	        i = (int)event.xclient.data.l[1];
 	       
-#ifdef BAR
+#if defined(BAR) || defined(MASTERSTACK)
 	        value = (int)event.xclient.data.l[1];
+#endif
+#ifdef BAR
 	        if (command == cmd_bar_position) {
 	            bar_set_bottom(value ? 1 : 0);
 	            write_root_int_property(display, root_window, wm_bar_bottom_atom, value ? 1 : 0);
 	            refresh_bar(display);
 	            continue;
+	        } else if (command == cmd_bar_height) {
+	            bar_set_height(value);
+	            write_root_int_property(display, root_window, wm_bar_height_atom, value);
+	            refresh_bar(display);
+	            continue;
+	        } else if (command == cmd_bar_showempty) {
+	            int show_empty;
+
+	            show_empty = read_root_int_property(display, root_window, wm_bar_showempty_atom, 1);
+	            show_empty = show_empty ? 0 : 1;
+	            bar_set_show_empty(show_empty);
+	            write_root_int_property(display, root_window, wm_bar_showempty_atom, show_empty);
+	            refresh_bar(display);
+	            continue;
 	        } else if (command == cmd_bar_margin_x) {
 	            bar_set_margins(value, -1);
 	            write_root_int_property(display, root_window, wm_bar_margin_x_atom, value);
@@ -498,7 +662,7 @@ main(int argc, char **argv)
 	            write_root_int_property(display, root_window, wm_bar_margin_y_atom, value);
 	            refresh_bar(display);
 	            continue;
-	        } else if (command == cmd_bar_nf || command == cmd_bar_nb ||
+	        } else if (command == cmd_bar_font || command == cmd_bar_nf || command == cmd_bar_nb ||
 	            command == cmd_bar_sf || command == cmd_bar_sb || command == cmd_bar_sa) {
 	            Atom source_atom;
 	            Atom actual_type;
@@ -513,7 +677,10 @@ main(int argc, char **argv)
 	                &prop_data) == Success &&
 	                prop_data != NULL && actual_type == XA_STRING &&
 	                actual_format == 8 && nitems > 0) {
-	                if (command == cmd_bar_nf) {
+	                if (command == cmd_bar_font) {
+	                    bar_set_font((char *)prop_data);
+	                    write_root_string_property(display, root_window, wm_bar_font_atom, (char *)prop_data);
+	                } else if (command == cmd_bar_nf) {
 	                    bar_set_colors((char *)prop_data, NULL, NULL, NULL, NULL);
 	                    write_root_string_property(display, root_window, wm_bar_nf_atom, (char *)prop_data);
 	                } else if (command == cmd_bar_nb) {
@@ -536,12 +703,47 @@ main(int argc, char **argv)
 	            XDeleteProperty(display, root_window, source_atom);
 	            continue;
 	        }
+#endif
+#ifdef MASTERSTACK
+	        if (command == cmd_gap_inner) {
+	            tile_masterstack_set_gaps(value, -1);
+	            write_root_int_property(display, root_window, wm_gap_inner_atom, value);
+	            apply_layout(display, root_window);
+	            continue;
+	        } else if (command == cmd_gap_outer) {
+	            tile_masterstack_set_gaps(-1, value);
+	            write_root_int_property(display, root_window, wm_gap_outer_atom, value);
+	            apply_layout(display, root_window);
+	            continue;
+	        } else if (command == cmd_gap_top) {
+	            tile_masterstack_set_top_gap(value);
+	            write_root_int_property(display, root_window, wm_gap_top_atom, value);
+	            apply_layout(display, root_window);
+	            continue;
+	        }
 #endif
 	        if (command == cmd_kill) {
 	            XGetInputFocus(display, &focused_window, &j);
 	            if (!is_root_child(display, root_window, focused_window))
 	                continue;
 	            XKillClient(display, focused_window);
+	        } else if (command == cmd_tile_focused) {
+	            XGetInputFocus(display, &focused_window, &j);
+	            if (!is_root_child(display, root_window, focused_window))
+	                continue;
+	            j = get_window_index(focused_window);
+	            if (j >= 0) {
+	                int mon;
+
+	                mon = monitor_for_window(display, root_window, focused_window);
+	                window_floating[j] = 0;
+	                window_workspace[j] = current_workspace[mon];
+	                apply_layout(display, root_window);
+	                ewmh_sync(display, root_window, windows, window_workspace, window_count);
+#ifdef BAR
+	                refresh_bar(display);
+#endif
+	            }
 	        } else if (command == cmd_reload) {
 #ifdef BAR
 	            bar_cleanup();
@@ -550,11 +752,17 @@ main(int argc, char **argv)
 	            execvp(argv[0], argv);
 	            return 1;
 	        } else if (i >= 0 && i < num_workspaces) {
-	            if (command == cmd_workspace && i != current_workspace) {
+	            if (command == cmd_workspace) {
+	                int mon;
+
+	                mon = active_monitor(display, root_window);
+	                if (i == current_workspace[mon])
+	                    continue;
 	                track_windows(display, root_window);
-	                current_workspace = i;
-	                show_workspace(display, root_window);
-	                ewmh_set_current_desktop(display, root_window, current_workspace);
+	                current_workspace[mon] = i;
+	                show_workspace(display, root_window, mon);
+	                apply_layout(display, root_window);
+	                ewmh_set_current_desktop(display, root_window, current_workspace[mon]);
 	                ewmh_sync(display, root_window, windows, window_workspace, window_count);
 #ifdef BAR
 	                refresh_bar(display);
@@ -566,9 +774,14 @@ main(int argc, char **argv)
 	                ewmh_set_active_window(display, root_window, focused_window);
 	                j = get_window_index(focused_window);
 	                if (j >= 0) {
+	                    int mon;
+
+	                    mon = monitor_for_window(display, root_window, focused_window);
 	                    window_workspace[j] = i;
-	                    if (i != current_workspace)
+	                    window_floating[j] = 0;
+	                    if (i != current_workspace[mon])
 	                        XUnmapWindow(display, focused_window);
+	                    apply_layout(display, root_window);
 	                    ewmh_sync(display, root_window, windows, window_workspace, window_count);
 #ifdef BAR
 	                    refresh_bar(display);
@@ -578,6 +791,7 @@ main(int argc, char **argv)
 	        }
 	    } else if (event.type == DestroyNotify) {
 	        remove_window(event.xdestroywindow.window);
+	        apply_layout(display, root_window);
 	        ewmh_sync(display, root_window, windows, window_workspace, window_count);
 #ifdef BAR
 	        refresh_bar(display);
@@ -589,9 +803,18 @@ main(int argc, char **argv)
 	        if (!is_managed_window(display, root_window, mapped_window))
 	            continue;
 	        if (find_window_index(mapped_window) < 0) {
+	            int mon;
+
 	            place_window_at_pointer(display, root_window, mapped_window);
 	            select_window_events(display, mapped_window);
 	            get_window_index(mapped_window);
+	            mon = monitor_for_window(display, root_window, mapped_window);
+	            j = find_window_index(mapped_window);
+	            if (j >= 0) {
+	                window_workspace[j] = current_workspace[mon];
+	                window_floating[j] = 0;
+	            }
+	            apply_layout(display, root_window);
 	            ewmh_sync(display, root_window, windows, window_workspace, window_count);
 #ifdef BAR
 	            refresh_bar(display);
@@ -607,8 +830,17 @@ main(int argc, char **argv)
 	        ewmh_set_active_window(display, root_window, drag_window);
 	        XSetInputFocus(display, drag_window, RevertToPointerRoot, CurrentTime);
 	        j = get_window_index(drag_window);
-	        if (j >= 0)
-	            window_workspace[j] = current_workspace;
+	        if (j >= 0) {
+	            int mon;
+
+	            mon = monitor_for_window(display, root_window, drag_window);
+	            window_workspace[j] = current_workspace[mon];
+	            if ((event.xbutton.state & mod_key) &&
+	                (event.xbutton.button == 1 || event.xbutton.button == 3)) {
+	                window_floating[j] = 1;
+	                apply_layout(display, root_window);
+	            }
+	        }
 	        ewmh_sync(display, root_window, windows, window_workspace, window_count);
 #ifdef BAR
 	        refresh_bar(display);
@@ -673,7 +905,11 @@ main(int argc, char **argv)
 	    } else if (event.type == ConfigureNotify &&
 	        event.xconfigure.window == root_window) {
 #ifdef XINERAMA
-	        xinerama_init(display, root_window);
+	        monitor_count = xinerama_init(display, root_window);
+	        if (monitor_count < 1)
+	            monitor_count = 1;
+	        if (monitor_count > max_monitors)
+	            monitor_count = max_monitors;
 #endif
 #ifdef BAR
 	        bar_reconfigure();
diff --git a/wmc.c b/wmc.c
index 415def1..916d0a8 100644
--- a/wmc.c
+++ b/wmc.c
@@ -1,6 +1,7 @@
 #include <X11/Xlib.h>
 #include <X11/Xatom.h>
 
+#include <unistd.h>
 #include <stdio.h>
 #include <stdlib.h>
 #include <string.h>
@@ -17,6 +18,13 @@
 #define cmd_bar_sf 10
 #define cmd_bar_sb 11
 #define cmd_bar_sa 12
+#define cmd_gap_inner 13
+#define cmd_gap_outer 14
+#define cmd_gap_top 15
+#define cmd_tile_focused 16
+#define cmd_bar_height 17
+#define cmd_bar_font 18
+#define cmd_bar_showempty 19
 
 int
 main(int argc, char **argv)
@@ -33,6 +41,8 @@ main(int argc, char **argv)
 	value = 0;
 	if (argc == 2 && !strcmp(argv[1], "kill"))
 	    command = cmd_kill;
+	else if (argc == 2 && !strcmp(argv[1], "tile"))
+	    command = cmd_tile_focused;
 	else if (argc == 2 && !strcmp(argv[1], "reload"))
 	    command = cmd_reload;
 	else if (argc == 3 && !strcmp(argv[1], "bar")) {
@@ -42,11 +52,23 @@ main(int argc, char **argv)
 	    } else if (!strcmp(argv[2], "bottom")) {
 	        command = cmd_bar_position;
 	        value = 1;
+	    } else if (!strcmp(argv[2], "showempty")) {
+	        command = cmd_bar_showempty;
+	        value = 0;
 	    } else {
-	        fprintf(stderr, "usage: wmc bar top|bottom\n");
+	        fprintf(stderr, "usage: wmc bar top|bottom|showempty\n");
 	        return 1;
 	    }
 	} else if (argc == 4 && !strcmp(argv[1], "bar")) {
+	    if (!strcmp(argv[2], "height")) {
+	        value = (int)strtol(argv[3], &end_ptr, 10);
+	        if (*end_ptr != '\0' || value < 1)
+	            return 1;
+	        command = cmd_bar_height;
+	    } else if (!strcmp(argv[2], "font")) {
+	        command = cmd_bar_font;
+	        value = 0;
+	    } else {
 	    if (!strcmp(argv[2], "nf"))
 	        command = cmd_bar_nf;
 	    else if (!strcmp(argv[2], "nb"))
@@ -69,10 +91,26 @@ main(int argc, char **argv)
 	        command = cmd_bar_margin_y;
 	    else {
 	        fprintf(stderr, "usage: wmc bar mx <px> | wmc bar my <px> | "
+	            "wmc bar height <px> | wmc bar font <xfont> | "
 	            "wmc bar nf|nb|sf|sb|sa <color>\n");
 	        return 1;
 	    }
 	    }
+	    }
+	} else if (argc == 4 && !strcmp(argv[1], "gap")) {
+	    value = (int)strtol(argv[3], &end_ptr, 10);
+	    if (*end_ptr != '\0' || value < 0)
+	        return 1;
+	    if (!strcmp(argv[2], "inner"))
+	        command = cmd_gap_inner;
+	    else if (!strcmp(argv[2], "outer"))
+	        command = cmd_gap_outer;
+	    else if (!strcmp(argv[2], "top"))
+	        command = cmd_gap_top;
+	    else {
+	        fprintf(stderr, "usage: wmc gap inner <px> | wmc gap outer <px> | wmc gap top <px>\n");
+	        return 1;
+	    }
 	}
 	else if (argc == 2)
 	    workspace = (int)strtol(argv[1], &end_ptr, 10);
@@ -83,15 +121,19 @@ main(int argc, char **argv)
 	    workspace = (int)strtol(argv[2], &end_ptr, 10);
 	else {
 	    fprintf(stderr, "usage: wmc [ws] 1-9 | wmc move 1-9 | wmc kill | wmc reload | "
-	        "wmc bar top|bottom | wmc bar mx <px> | wmc bar my <px> | "
-	        "wmc bar nf|nb|sf|sb|sa <color>\n");
+	        "wmc tile | "
+	        "wmc bar top|bottom|showempty | wmc bar mx <px> | wmc bar my <px> | "
+	        "wmc bar height <px> | wmc bar font <xfont> | "
+	        "wmc bar nf|nb|sf|sb|sa <color> | wmc gap inner|outer|top <px>\n");
 	    return 1;
 	}
 	if ((command == cmd_workspace || command == cmd_move) &&
 	    (*end_ptr != '\0' || workspace < 1 || workspace > 9))
 	    return 1;
-	if (command == cmd_bar_position || command == cmd_bar_margin_x ||
-	    command == cmd_bar_margin_y)
+	if (command == cmd_bar_position || command == cmd_bar_showempty || command == cmd_bar_margin_x ||
+	    command == cmd_bar_margin_y || command == cmd_bar_height ||
+	    command == cmd_gap_inner ||
+	    command == cmd_gap_outer || command == cmd_gap_top)
 	    workspace = value;
 	if ((display = XOpenDisplay(NULL)) == NULL)
 	    return 1;
@@ -105,7 +147,7 @@ main(int argc, char **argv)
 	event.xclient.data.l[0] = command;
 	if (command == cmd_workspace || command == cmd_move)
 	    event.xclient.data.l[1] = workspace > 0 ? workspace - 1 : 0;
-	else if (command >= cmd_bar_nf && command <= cmd_bar_sa) {
+	else if (command == cmd_bar_font || (command >= cmd_bar_nf && command <= cmd_bar_sa)) {
 	    Atom payload_atom;
 	    char atom_name[64];
 	    static unsigned long seq;
@@ -121,6 +163,5 @@ main(int argc, char **argv)
 	    event.xclient.data.l[1] = workspace;
 	XSendEvent(display, root_window, False, SubstructureNotifyMask, &event);
 	XFlush(display);
-	XCloseDisplay(display);
-	return 0;
+	_exit(0);
 }