summaryrefslogtreecommitdiff
path: root/cmd
diff options
context:
space:
mode:
authorAndrew O. Shadoura <bugzilla@tut.by>2011-05-30 20:12:40 +0300
committerAndrew O. Shadoura <bugzilla@tut.by>2011-05-30 20:12:40 +0300
commit833a761b1ab08b6b88dd902231948fc2a2fd44a7 (patch)
treefda7fafdd345d4995e290e002065abc331147937 /cmd
import the initial version wmii-3.9.2+debian
Diffstat (limited to 'cmd')
-rw-r--r--cmd/Makefile33
-rw-r--r--cmd/click/Makefile22
-rw-r--r--cmd/click/_util.c77
-rw-r--r--cmd/click/dat.h27
-rw-r--r--cmd/click/fns.h4
-rw-r--r--cmd/click/main.c62
-rw-r--r--cmd/clientutil.c50
-rw-r--r--cmd/menu/Makefile36
-rw-r--r--cmd/menu/bindings.c51
-rw-r--r--cmd/menu/caret.c164
-rw-r--r--cmd/menu/dat.h116
-rw-r--r--cmd/menu/event.c334
-rw-r--r--cmd/menu/fns.h51
-rw-r--r--cmd/menu/history.c90
-rw-r--r--cmd/menu/keys.c142
-rw-r--r--cmd/menu/keys.txt49
-rw-r--r--cmd/menu/main.c346
-rw-r--r--cmd/menu/menu.c344
-rw-r--r--cmd/strut/Makefile27
-rw-r--r--cmd/strut/_util.c77
-rw-r--r--cmd/strut/dat.h33
-rw-r--r--cmd/strut/event.c309
-rw-r--r--cmd/strut/ewmh.c84
-rw-r--r--cmd/strut/fns.h18
-rw-r--r--cmd/strut/main.c102
-rw-r--r--cmd/strut/printevent_kludge.c12
-rw-r--r--cmd/strut/win.c102
-rw-r--r--cmd/util.c272
-rw-r--r--cmd/wihack.sh44
-rwxr-xr-xcmd/wmii.rc.rc181
-rwxr-xr-xcmd/wmii.sh.sh219
-rw-r--r--cmd/wmii/Makefile47
-rw-r--r--cmd/wmii/_util.c378
-rw-r--r--cmd/wmii/area.c328
-rw-r--r--cmd/wmii/bar.c300
-rw-r--r--cmd/wmii/client.c1212
-rw-r--r--cmd/wmii/column.c738
-rw-r--r--cmd/wmii/dat.h404
-rw-r--r--cmd/wmii/div.c190
-rw-r--r--cmd/wmii/error.c41
-rw-r--r--cmd/wmii/event.c359
-rw-r--r--cmd/wmii/ewmh.c544
-rw-r--r--cmd/wmii/float.c245
-rw-r--r--cmd/wmii/fns.h328
-rw-r--r--cmd/wmii/frame.c681
-rw-r--r--cmd/wmii/fs.c725
-rw-r--r--cmd/wmii/geom.c94
-rw-r--r--cmd/wmii/key.c244
-rw-r--r--cmd/wmii/layout.c608
-rw-r--r--cmd/wmii/main.c470
-rw-r--r--cmd/wmii/map.c126
-rw-r--r--cmd/wmii/message.c1177
-rw-r--r--cmd/wmii/mouse.c642
-rw-r--r--cmd/wmii/print.c124
-rw-r--r--cmd/wmii/printevent.c994
-rw-r--r--cmd/wmii/printevent.h248
-rw-r--r--cmd/wmii/root.c89
-rw-r--r--cmd/wmii/rule.c107
-rw-r--r--cmd/wmii/screen.c189
-rw-r--r--cmd/wmii/utf.c60
-rw-r--r--cmd/wmii/view.c630
-rw-r--r--cmd/wmii/x11.c1317
-rw-r--r--cmd/wmii/xdnd.c88
-rw-r--r--cmd/wmii/xext.c160
-rw-r--r--cmd/wmii9menu.c341
-rw-r--r--cmd/wmiir.c421
66 files changed, 18127 insertions, 0 deletions
diff --git a/cmd/Makefile b/cmd/Makefile
new file mode 100644
index 0000000..ea8690b
--- /dev/null
+++ b/cmd/Makefile
@@ -0,0 +1,33 @@
+ROOT=..
+include $(ROOT)/mk/hdr.mk
+include $(ROOT)/mk/wmii.mk
+
+wmiir.c: $(ROOT)/mk/wmii.mk
+
+DIRS = wmii \
+ menu
+TARG = wihack \
+ wmii.rc \
+ wmii.sh \
+ wmii9menu \
+ wmiir
+
+OFILES = util.o
+
+LIBS += $(LIBS9)
+CFLAGS += $(INCX11)
+
+include $(ROOT)/mk/many.mk
+include $(ROOT)/mk/dir.mk
+
+OWMIIR=wmiir.o $(OFILES) $(LIBIXP)
+wmiir.out: $(OWMIIR)
+ $(LINK) $@ $(OWMIIR)
+
+wmii/x11.o wmii/xext.o wmii/geom.o wmii/map.o: dall
+ true
+
+O9MENU=wmii9menu.o clientutil.o wmii/x11.o wmii/xext.o wmii/geom.o wmii/map.o $(OFILES) $(LIBIXP)
+wmii9menu.out: $(O9MENU)
+ $(LINK) $@ $(O9MENU) $$(pkg-config --libs $(X11PACKAGES) xrandr xinerama) -lXext
+
diff --git a/cmd/click/Makefile b/cmd/click/Makefile
new file mode 100644
index 0000000..1abf6cb
--- /dev/null
+++ b/cmd/click/Makefile
@@ -0,0 +1,22 @@
+ROOT= ../..
+include ${ROOT}/mk/hdr.mk
+include ${ROOT}/mk/wmii.mk
+
+main.c: ${ROOT}/mk/wmii.mk
+
+TARG = click
+HFILES= dat.h fns.h
+
+PACKAGES += $(X11PACKAGES) xext xrandr xrender xinerama
+
+LIB = $(LIBIXP)
+LIBS += -lm -lXtst $(LIBS9)
+CFLAGS += -DVERSION=\"$(VERSION)\" -DIXP_NEEDAPI=86
+OBJ = main \
+ _util \
+ ../wmii/map \
+ ../wmii/x11 \
+ ../util
+
+include ${ROOT}/mk/one.mk
+
diff --git a/cmd/click/_util.c b/cmd/click/_util.c
new file mode 100644
index 0000000..364ff81
--- /dev/null
+++ b/cmd/click/_util.c
@@ -0,0 +1,77 @@
+/* Copyright ©2008-2010 Kris Maglione <fbsdaemon@gmail.com>
+ * See LICENSE file for license details.
+ */
+#include "dat.h"
+#include <ctype.h>
+#include <string.h>
+#include "fns.h"
+
+#define strbcmp(str, const) (strncmp((str), (const), sizeof(const)-1))
+static int
+getbase(const char **s, long *sign) {
+ const char *p;
+ int ret;
+
+ ret = 10;
+ *sign = 1;
+ if(**s == '-') {
+ *sign = -1;
+ *s += 1;
+ }else if(**s == '+')
+ *s += 1;
+
+ p = *s;
+ if(!strbcmp(p, "0x")) {
+ *s += 2;
+ ret = 16;
+ }
+ else if(isdigit(p[0])) {
+ if(p[1] == 'r') {
+ *s += 2;
+ ret = p[0] - '0';
+ }
+ else if(isdigit(p[1]) && p[2] == 'r') {
+ *s += 3;
+ ret = 10*(p[0]-'0') + (p[1]-'0');
+ }
+ }
+ else if(p[0] == '0') {
+ ret = 8;
+ }
+ if(ret != 10 && (**s == '-' || **s == '+'))
+ *sign = 0;
+ return ret;
+}
+
+bool
+getlong(const char *s, long *ret) {
+ const char *end;
+ char *rend;
+ int base;
+ long sign;
+
+ end = s+strlen(s);
+ base = getbase(&s, &sign);
+ if(sign == 0)
+ return false;
+
+ *ret = sign * strtol(s, &rend, base);
+ return (end == rend);
+}
+
+bool
+getulong(const char *s, ulong *ret) {
+ const char *end;
+ char *rend;
+ int base;
+ long sign;
+
+ end = s+strlen(s);
+ base = getbase(&s, &sign);
+ if(sign < 1)
+ return false;
+
+ *ret = strtoul(s, &rend, base);
+ return (end == rend);
+}
+
diff --git a/cmd/click/dat.h b/cmd/click/dat.h
new file mode 100644
index 0000000..5096865
--- /dev/null
+++ b/cmd/click/dat.h
@@ -0,0 +1,27 @@
+#include <fmt.h>
+#include <stdarg.h>
+#include <stdbool.h>
+#include <stdlib.h>
+#include <unistd.h>
+#include <util.h>
+#include <ixp.h>
+#include <x11.h>
+
+#define BLOCK(x) do { x; }while(0)
+
+#ifndef EXTERN
+# define EXTERN extern
+#endif
+
+EXTERN Window win;
+
+EXTERN char buffer[8092];
+EXTERN char* _buffer;
+
+static char* const _buf_end = buffer + sizeof buffer;
+
+#define bufclear() \
+ BLOCK( _buffer = buffer; _buffer[0] = '\0' )
+#define bufprint(...) \
+ _buffer = seprint(_buffer, _buf_end, __VA_ARGS__)
+
diff --git a/cmd/click/fns.h b/cmd/click/fns.h
new file mode 100644
index 0000000..d41b840
--- /dev/null
+++ b/cmd/click/fns.h
@@ -0,0 +1,4 @@
+
+bool getlong(const char*, long*);
+bool getulong(const char*, ulong*);
+
diff --git a/cmd/click/main.c b/cmd/click/main.c
new file mode 100644
index 0000000..3ddc8ef
--- /dev/null
+++ b/cmd/click/main.c
@@ -0,0 +1,62 @@
+/* Copyright ©2006-2010 Kris Maglione <fbsdaemon@gmail.com>
+ * See LICENSE file for license details.
+ */
+#define EXTERN
+#include "dat.h"
+#include <X11/Xproto.h>
+#include <X11/extensions/XTest.h>
+#include <locale.h>
+#include <string.h>
+#include "fns.h"
+
+static const char version[] = "click-"VERSION", ©2010 Kris Maglione\n";
+
+static void
+usage(void) {
+ fatal("usage: %s [window]\n", argv0);
+}
+
+static void
+click(Window *w, Point p) {
+ Rectangle r;
+ Point rp;
+
+ r = getwinrect(w);
+ rp = subpt(r.max, p);
+
+ XTestFakeMotionEvent(display, 0, rp.x, rp.y, 0);
+
+ XTestFakeButtonEvent(display, 1, true, 0);
+ XTestFakeButtonEvent(display, 1, false, 0);
+
+ XTestFakeMotionEvent(display, 0, r.max.x, r.max.y, 0);
+}
+
+int
+main(int argc, char *argv[]) {
+ char *s;
+
+ ARGBEGIN{
+ default:
+ usage();
+ }ARGEND;
+
+ setlocale(LC_CTYPE, "");
+
+ initdisplay();
+
+ s = ARGF();
+ if(s && !getulong(s, &win.w))
+ usage();
+ if (!s)
+ win.w = getfocus();
+
+ if(argc)
+ usage();
+
+ click(&win, Pt(1, 1));
+
+ XCloseDisplay(display);
+ return 0;
+}
+
diff --git a/cmd/clientutil.c b/cmd/clientutil.c
new file mode 100644
index 0000000..411fe67
--- /dev/null
+++ b/cmd/clientutil.c
@@ -0,0 +1,50 @@
+#define IXP_NO_P9_
+#define IXP_P9_STRUCTS
+#define CLIENTEXTERN
+#include <string.h>
+#include <ixp.h>
+#include <clientutil.h>
+#include <util.h>
+
+static IxpCFid* ctlfid;
+static char ctl[1024];
+static char* ectl;
+
+char*
+readctl(char *key) {
+ char *s, *p;
+ int nkey, n;
+
+ if(ctlfid == nil) {
+ ctlfid = ixp_open(client, "ctl", OREAD);
+ n = ixp_read(ctlfid, ctl, 1023);
+ ectl = ctl + n;
+ ixp_close(ctlfid);
+ }
+
+ nkey = strlen(key);
+ p = ctl - 1;
+ do {
+ p++;
+ if(!strncmp(p, key, nkey)) {
+ p += nkey;
+ s = strchr(p, '\n');
+ n = (s ? s : ectl) - p;
+ s = freelater(emalloc(n + 1));
+ s[n] = '\0';
+ return strncpy(s, p, n);
+ }
+ } while((p = strchr(p, '\n')));
+ return "";
+}
+
+void
+client_init(char* address) {
+ if(address && *address)
+ client = ixp_mount(address);
+ else
+ client = ixp_nsmount("wmii");
+ if(client == nil)
+ fatal("can't mount: %r\n");
+}
+
diff --git a/cmd/menu/Makefile b/cmd/menu/Makefile
new file mode 100644
index 0000000..a2fc9ad
--- /dev/null
+++ b/cmd/menu/Makefile
@@ -0,0 +1,36 @@
+ROOT= ../..
+include $(ROOT)/mk/hdr.mk
+include $(ROOT)/mk/wmii.mk
+
+main.c: $(ROOT)/mk/wmii.mk
+
+bindings.c: keys.txt Makefile
+ ( echo "char binding_spec[] = "; \
+ sed 's/.*/ "&\\n"/' keys.txt; \
+ echo " ;" ) >bindings.c
+
+TARG = wimenu
+HFILES= dat.h fns.h
+
+PACKAGES += $(X11PACKAGES) xext xrandr xrender xinerama
+
+LIB = $(LIBIXP)
+LIBS += -lm $(LIBS9)
+CFLAGS += -DIXP_NEEDAPI=86
+OBJ = main \
+ caret \
+ history \
+ event \
+ menu \
+ keys \
+ bindings \
+ ../wmii/geom \
+ ../wmii/map \
+ ../wmii/printevent \
+ ../wmii/x11 \
+ ../wmii/xext \
+ ../clientutil \
+ ../util
+
+include $(ROOT)/mk/one.mk
+
diff --git a/cmd/menu/bindings.c b/cmd/menu/bindings.c
new file mode 100644
index 0000000..28aad75
--- /dev/null
+++ b/cmd/menu/bindings.c
@@ -0,0 +1,51 @@
+char binding_spec[] =
+ "Control-j Accept\n"
+ "Control-m Accept\n"
+ "Return Accept\n"
+ "Control-Shift-j Accept literal\n"
+ "Control-Shift-m Accept literal\n"
+ "Shift-Return Accept literal\n"
+ "\n"
+ "Escape Reject\n"
+ "Control-Bracketleft Reject\n"
+ "\n"
+ "Left Backward char\n"
+ "Control-b Backward char\n"
+ "Right Forward char\n"
+ "Control-f Forward char\n"
+ "\n"
+ "Mod1-b Backward word\n"
+ "Mod1-f Forward word\n"
+ "\n"
+ "Control-a Backward line\n"
+ "Control-e Forward line\n"
+ "\n"
+ "Control-p History backward\n"
+ "Up History backward\n"
+ "Control-n History forward\n"
+ "Down History forward\n"
+ "\n"
+ "Backspace Kill char\n"
+ "Control-h Kill char\n"
+ "Control-Backspace Kill word\n"
+ "Control-w Kill word\n"
+ "Control-u Kill line\n"
+ "\n"
+ "Tab Complete next\n"
+ "Control-i Complete next\n"
+ "Mod1-l Complete next\n"
+ "\n"
+ "Shift-Tab Complete prev\n"
+ "Control-Shift-i Complete prev\n"
+ "Mod1-h Complete prev\n"
+ "\n"
+ "Prior Complete prevpage\n"
+ "Mod1-k Complete prevpage\n"
+ "Next Complete nextpage\n"
+ "Mod1-j Complete nextpage\n"
+ "Home Complete first\n"
+ "Mod1-g Complete first\n"
+ "End Complete last\n"
+ "Mod1-Shift-g Complete last\n"
+ "\n"
+ ;
diff --git a/cmd/menu/caret.c b/cmd/menu/caret.c
new file mode 100644
index 0000000..c0380e3
--- /dev/null
+++ b/cmd/menu/caret.c
@@ -0,0 +1,164 @@
+#include "dat.h"
+#include <ctype.h>
+#include <string.h>
+#include "fns.h"
+
+static int
+iswordrune(Rune r) {
+ if(isalpharune(r))
+ return 1;
+ return r < 0x80 && (r == '_' || isdigit(r));
+}
+
+static char*
+prev_rune(char *start, char *p, Rune *r) {
+
+ *r = 0;
+ if(p == start)
+ return p;
+ while(p > start && (*(--p)&0xC0) == 0x80)
+ ;
+ chartorune(r, p);
+ return p;
+}
+
+static char*
+next_rune(char *p, Rune *r) {
+ int i;
+
+ *r = 0;
+ if(!*p)
+ return p;
+ i = chartorune(r, p);
+ return p + i;
+}
+
+void
+caret_set(int start, int end) {
+ int len;
+
+ len = input.end - input.string;
+ start = max(0, min(len, start));
+
+ input.pos = input.string + start;
+ if(end < 0)
+ input.pos_end = nil;
+ else
+ input.pos_end = input.string + max(start, end);
+}
+
+char*
+caret_find(int dir, int type) {
+ char *end;
+ char *next, *p;
+ Rune r;
+ int res;
+
+ p = input.pos;
+ if(dir == FORWARD) {
+ end = input.end;
+ switch(type) {
+ case LINE:
+ return end;
+ case WORD:
+ chartorune(&r, p);
+ res = iswordrune(r);
+ while(next=next_rune(p, &r), r && iswordrune(r) == res && !isspacerune(r))
+ p = next;
+ while(next=next_rune(p, &r), r && isspacerune(r))
+ p = next;
+ return p;
+ case CHAR:
+ if(p < end)
+ return p+1;
+ return p;
+ }
+ }
+ else if(dir == BACKWARD) {
+ end = input.string;
+ switch(type) {
+ case LINE:
+ return end;
+ case WORD:
+ while(next=prev_rune(end, p, &r), r && isspacerune(r))
+ p = next;
+ prev_rune(end, p, &r);
+ res = iswordrune(r);
+ while(next=prev_rune(end, p, &r), r && iswordrune(r) == res && !isspacerune(r))
+ p = next;
+ return p;
+ case CHAR:
+ if(p > end)
+ return p-1;
+ return end;
+ }
+ }
+ input.pos_end = nil;
+ return input.pos;
+}
+
+void
+caret_move(int dir, int type) {
+ input.pos = caret_find(dir, type);
+ input.pos_end = nil;
+}
+
+void
+caret_delete(int dir, int type) {
+ char *pos, *p;
+ int n;
+
+ if(input.pos_end)
+ p = input.pos_end;
+ else
+ p = caret_find(dir, type);
+ pos = input.pos;
+ if(p == input.end)
+ input.end = pos;
+ else {
+ if(p < pos) {
+ pos = p;
+ p = input.pos;
+ }
+ n = input.end - p;
+ memmove(pos, p, n);
+ input.pos = pos;
+ input.end = pos + n;
+ }
+ *input.end = '\0';
+ input.pos_end = nil;
+}
+
+void
+caret_insert(char *s, bool clear) {
+ int pos, end, len, size;
+
+ if(s == nil)
+ return;
+ if(clear) {
+ input.pos = input.string;
+ input.end = input.string;
+ }else if(input.pos_end)
+ caret_delete(0, 0);
+
+ len = strlen(s);
+ pos = input.pos - input.string;
+ end = input.end - input.string;
+
+ size = input.size;
+ if(input.size == 0)
+ input.size = 1;
+ while(input.size < end + len + 1)
+ input.size <<= 2;
+ if(input.size != size)
+ input.string = erealloc(input.string, input.size);
+
+ input.pos = input.string + pos;
+ input.end = input.string + end + len;
+ *input.end = '\0';
+ memmove(input.pos + len, input.pos, end - pos);
+ memmove(input.pos, s, len);
+ input.pos += len;
+ input.pos_end = nil;
+}
+
diff --git a/cmd/menu/dat.h b/cmd/menu/dat.h
new file mode 100644
index 0000000..1d805fa
--- /dev/null
+++ b/cmd/menu/dat.h
@@ -0,0 +1,116 @@
+#define _XOPEN_SOURCE 600
+#define IXP_P9_STRUCTS
+#define IXP_NO_P9_
+#include <fmt.h>
+#include <stdarg.h>
+#include <stdbool.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+#include <util.h>
+#include <ixp.h>
+#include <x11.h>
+
+#define BLOCK(x) do { x; }while(0)
+
+#ifndef EXTERN
+# define EXTERN extern
+#endif
+
+enum {
+ FORWARD,
+ BACKWARD,
+ LINE,
+ WORD,
+ CHAR,
+ CARET_LAST,
+};
+
+enum {
+ LACCEPT,
+ LBACKWARD,
+ LCHAR,
+ LCOMPLETE,
+ LFIRST,
+ LFORWARD,
+ LHISTORY,
+ LKILL,
+ LLAST,
+ LLINE,
+ LLITERAL,
+ LNEXT,
+ LNEXTPAGE,
+ LPREV,
+ LPREVPAGE,
+ LREJECT,
+ LWORD,
+};
+
+typedef struct Item Item;
+
+struct Item {
+ char* string;
+ char* retstring;
+ Item* next_link;
+ Item* next;
+ Item* prev;
+ int len;
+ int width;
+};
+
+EXTERN struct {
+ char* string;
+ char* end;
+ char* pos;
+ char* pos_end;
+ int size;
+
+ char* filter;
+ int filter_start;
+} input;
+
+extern char binding_spec[];
+
+EXTERN int numlock;
+
+EXTERN long xtime;
+EXTERN Image* ibuf;
+EXTERN Font* font;
+EXTERN CTuple cnorm, csel;
+EXTERN bool ontop;
+
+EXTERN Cursor cursor[1];
+EXTERN Visual* render_visual;
+
+EXTERN IxpServer srv;
+
+EXTERN Window* barwin;
+
+EXTERN Item* items;
+EXTERN Item* matchfirst;
+EXTERN Item* matchstart;
+EXTERN Item* matchend;
+EXTERN Item* matchidx;
+
+EXTERN Item hist;
+EXTERN Item* histidx;
+
+EXTERN int maxwidth;
+EXTERN int result;
+
+EXTERN char* (*find)(const char*, const char*);
+EXTERN int (*compare)(const char*, const char*, size_t);
+
+EXTERN char* prompt;
+EXTERN int promptw;
+
+EXTERN char buffer[8092];
+EXTERN char* _buffer;
+
+static char* const _buf_end = buffer + sizeof buffer;
+
+#define bufclear() \
+ BLOCK( _buffer = buffer; _buffer[0] = '\0' )
+#define bufprint(...) \
+ _buffer = seprint(_buffer, _buf_end, __VA_ARGS__)
+
diff --git a/cmd/menu/event.c b/cmd/menu/event.c
new file mode 100644
index 0000000..fd524f9
--- /dev/null
+++ b/cmd/menu/event.c
@@ -0,0 +1,334 @@
+/* Copyright ©2006-2010 Kris Maglione <fbsdaemon@gmail.com>
+ * See LICENSE file for license details.
+ */
+#include "dat.h"
+#include "fns.h"
+
+typedef void (*EvHandler)(XEvent*);
+static EvHandler handler[LASTEvent];
+
+void
+dispatch_event(XEvent *e) {
+ if(e->type < nelem(handler)) {
+ if(handler[e->type])
+ handler[e->type](e);
+ }else
+ xext_event(e);
+}
+
+#define handle(w, fn, ev) \
+ BLOCK(if((w)->handler->fn) (w)->handler->fn((w), ev))
+
+static int
+findtime(Display *d, XEvent *e, XPointer v) {
+ Window *w;
+
+ w = (Window*)v;
+ if(e->type == PropertyNotify && e->xproperty.window == w->xid) {
+ xtime = e->xproperty.time;
+ return true;
+ }
+ return false;
+}
+
+void
+xtime_kludge(void) {
+ /* Round trip. */
+ static Window *w;
+ WinAttr wa;
+ XEvent e;
+ long l;
+
+ if(w == nil) {
+ w = createwindow(&scr.root, Rect(0, 0, 1, 1), 0, InputOnly, &wa, 0);
+ selectinput(w, PropertyChangeMask);
+ }
+ changeprop_long(w, "ATOM", "ATOM", &l, 0);
+ sync();
+ XIfEvent(display, &e, findtime, (void*)w);
+}
+
+uint
+flushevents(long event_mask, bool dispatch) {
+ XEvent ev;
+ uint n = 0;
+
+ while(XCheckMaskEvent(display, event_mask, &ev)) {
+ if(dispatch)
+ dispatch_event(&ev);
+ n++;
+ }
+ return n;
+}
+
+static int
+findenter(Display *d, XEvent *e, XPointer v) {
+ long *l;
+
+ USED(d);
+ l = (long*)v;
+ if(*l)
+ return false;
+ if(e->type == EnterNotify)
+ return true;
+ if(e->type == MotionNotify)
+ (*l)++;
+ return false;
+}
+
+/* This isn't perfect. If there were motion events in the queue
+ * before this was called, then it flushes nothing. If we don't
+ * check for them, we might lose a legitamate enter event.
+ */
+uint
+flushenterevents(void) {
+ XEvent e;
+ long l;
+ int n;
+
+ l = 0;
+ n = 0;
+ while(XCheckIfEvent(display, &e, findenter, (void*)&l))
+ n++;
+ return n;
+}
+
+static void
+buttonrelease(XButtonPressedEvent *ev) {
+ Window *w;
+
+ if((w = findwin(ev->window)))
+ handle(w, bup, ev);
+}
+
+static void
+buttonpress(XButtonPressedEvent *ev) {
+ Window *w;
+
+ if((w = findwin(ev->window)))
+ handle(w, bdown, ev);
+ else
+ XAllowEvents(display, ReplayPointer, ev->time);
+}
+
+static void
+configurerequest(XConfigureRequestEvent *ev) {
+ XWindowChanges wc;
+ Window *w;
+
+ if((w = findwin(ev->window)))
+ handle(w, configreq, ev);
+ else{
+ wc.x = ev->x;
+ wc.y = ev->y;
+ wc.width = ev->width;
+ wc.height = ev->height;
+ wc.border_width = ev->border_width;
+ wc.sibling = ev->above;
+ wc.stack_mode = ev->detail;
+ XConfigureWindow(display, ev->window, ev->value_mask, &wc);
+ }
+}
+
+static void
+configurenotify(XConfigureEvent *ev) {
+ Window *w;
+
+ USED(ev);
+ if((w = findwin(ev->window)))
+ handle(w, config, ev);
+}
+
+static void
+clientmessage(XClientMessageEvent *ev) {
+
+ USED(ev);
+}
+
+static void
+destroynotify(XDestroyWindowEvent *ev) {
+ Window *w;
+
+ if((w = findwin(ev->window)))
+ handle(w, destroy, ev);
+}
+
+static void
+enternotify(XCrossingEvent *ev) {
+ Window *w;
+ static int sel_screen;
+
+ xtime = ev->time;
+ if(ev->mode != NotifyNormal)
+ return;
+
+ if((w = findwin(ev->window)))
+ handle(w, enter, ev);
+ else if(ev->window == scr.root.xid)
+ sel_screen = true;
+}
+
+static void
+leavenotify(XCrossingEvent *ev) {
+
+ xtime = ev->time;
+#if 0
+ if((ev->window == scr.root.xid) && !ev->same_screen)
+ sel_screen = true;
+#endif
+}
+
+static void
+focusin(XFocusChangeEvent *ev) {
+ Window *w;
+
+ /* Yes, we're focusing in on nothing, here. */
+ if(ev->detail == NotifyDetailNone) {
+ /* FIXME: Do something. */
+ return;
+ }
+
+ if(!((ev->detail == NotifyNonlinear)
+ ||(ev->detail == NotifyNonlinearVirtual)
+ ||(ev->detail == NotifyVirtual)
+ ||(ev->detail == NotifyInferior)
+ ||(ev->detail == NotifyAncestor)))
+ return;
+ if((ev->mode == NotifyWhileGrabbed)) /* && (screen->hasgrab != &c_root)) */
+ return;
+
+ if((w = findwin(ev->window)))
+ handle(w, focusin, ev);
+#if 0
+ else if(ev->mode == NotifyGrab) {
+ if(ev->window == scr.root.xid)
+ screen->hasgrab = &c_root;
+ /* Some unmanaged window has grabbed focus */
+ else if((c = screen->focus)) {
+ print_focus("focusin", &c_magic, "<magic>");
+ screen->focus = &c_magic;
+ if(c->sel)
+ frame_draw(c->sel);
+ }
+ }
+#endif
+}
+
+static void
+focusout(XFocusChangeEvent *ev) {
+ Window *w;
+
+ if(!((ev->detail == NotifyNonlinear)
+ ||(ev->detail == NotifyNonlinearVirtual)
+ ||(ev->detail == NotifyVirtual)
+ ||(ev->detail == NotifyInferior)
+ ||(ev->detail == NotifyAncestor)))
+ return;
+#if 0
+ if(ev->mode == NotifyUngrab)
+ screen->hasgrab = nil;
+#endif
+
+ if((w = findwin(ev->window)))
+ handle(w, focusout, ev);
+}
+
+static void
+expose(XExposeEvent *ev) {
+ Window *w;
+
+ if(ev->count == 0) {
+ if((w = findwin(ev->window)))
+ handle(w, expose, ev);
+ }
+}
+
+static void
+keypress(XKeyEvent *ev) {
+ Window *w;
+
+ xtime = ev->time;
+ if((w = findwin(ev->window)))
+ handle(w, kdown, ev);
+}
+
+static void
+mappingnotify(XMappingEvent *ev) {
+
+ /* Why do you need me to tell you this? */
+ XRefreshKeyboardMapping(ev);
+}
+
+static void
+maprequest(XMapRequestEvent *ev) {
+
+ USED(ev);
+}
+
+static void
+motionnotify(XMotionEvent *ev) {
+ Window *w;
+
+ xtime = ev->time;
+ if((w = findwin(ev->window)))
+ handle(w, motion, ev);
+}
+
+static void
+propertynotify(XPropertyEvent *ev) {
+ Window *w;
+
+ xtime = ev->time;
+ if((w = findwin(ev->window)))
+ handle(w, property, ev);
+}
+
+static void
+mapnotify(XMapEvent *ev) {
+ Window *w;
+
+ if((w = findwin(ev->window)))
+ handle(w, map, ev);
+}
+
+static void
+unmapnotify(XUnmapEvent *ev) {
+ Window *w;
+
+ if((w = findwin(ev->window)) && (ev->event == w->parent->xid)) {
+ w->mapped = false;
+ if(ev->send_event || w->unmapped-- == 0)
+ handle(w, unmap, ev);
+ }
+}
+
+static EvHandler handler[LASTEvent] = {
+ [ButtonPress] = (EvHandler)buttonpress,
+ [ButtonRelease] = (EvHandler)buttonrelease,
+ [ConfigureRequest] = (EvHandler)configurerequest,
+ [ConfigureNotify] = (EvHandler)configurenotify,
+ [ClientMessage] = (EvHandler)clientmessage,
+ [DestroyNotify] = (EvHandler)destroynotify,
+ [EnterNotify] = (EvHandler)enternotify,
+ [Expose] = (EvHandler)expose,
+ [FocusIn] = (EvHandler)focusin,
+ [FocusOut] = (EvHandler)focusout,
+ [KeyPress] = (EvHandler)keypress,
+ [LeaveNotify] = (EvHandler)leavenotify,
+ [MapNotify] = (EvHandler)mapnotify,
+ [MapRequest] = (EvHandler)maprequest,
+ [MappingNotify] = (EvHandler)mappingnotify,
+ [MotionNotify] = (EvHandler)motionnotify,
+ [PropertyNotify] = (EvHandler)propertynotify,
+ [UnmapNotify] = (EvHandler)unmapnotify,
+};
+
+void
+check_x_event(IxpConn *c) {
+ XEvent ev;
+
+ USED(c);
+ while(XCheckMaskEvent(display, ~0, &ev))
+ dispatch_event(&ev);
+}
+
diff --git a/cmd/menu/fns.h b/cmd/menu/fns.h
new file mode 100644
index 0000000..d95ab7f
--- /dev/null
+++ b/cmd/menu/fns.h
@@ -0,0 +1,51 @@
+
+void check_x_event(IxpConn*);
+void dispatch_event(XEvent*);
+uint flushenterevents(void);
+uint flushevents(long, bool);
+void xtime_kludge(void);
+
+/* caret.c */
+void caret_delete(int, int);
+char* caret_find(int, int);
+void caret_insert(char*, bool);
+void caret_move(int, int);
+void caret_set(int, int);
+
+/* history.c */
+void history_dump(const char*, int);
+char* history_search(int, char*, int);
+
+/* main.c */
+void debug(int, const char*, ...);
+Item* filter_list(Item*, char*);
+void init_screens(int);
+void update_filter(bool);
+void update_input(void);
+
+/* menu.c */
+void menu_draw(void);
+void menu_init(void);
+void menu_show(void);
+
+/* keys.c */
+void parse_keys(char*);
+char** find_key(char*, long);
+int getsym(char*);
+
+/* geom.c */
+Align get_sticky(Rectangle src, Rectangle dst);
+Cursor quad_cursor(Align);
+Align quadrant(Rectangle, Point);
+bool rect_contains_p(Rectangle, Rectangle);
+bool rect_haspoint_p(Point, Rectangle);
+bool rect_intersect_p(Rectangle, Rectangle);
+Rectangle rect_intersection(Rectangle, Rectangle);
+
+/* xext.c */
+void randr_event(XEvent*);
+bool render_argb_p(Visual*);
+void xext_event(XEvent*);
+void xext_init(void);
+Rectangle* xinerama_screens(int*);
+
diff --git a/cmd/menu/history.c b/cmd/menu/history.c
new file mode 100644
index 0000000..38b0598
--- /dev/null
+++ b/cmd/menu/history.c
@@ -0,0 +1,90 @@
+#include "dat.h"
+#include <assert.h>
+#include <bio.h>
+#include <stdio.h>
+#include <string.h>
+#include <unistd.h>
+#include "fns.h"
+
+static void
+splice(Item *i) {
+ if(i->next != nil)
+ i->next->prev = i->prev;
+ if(i->prev != nil)
+ i->prev->next = i->next;
+}
+
+char*
+history_search(int dir, char *string, int n) {
+ Item *i;
+
+ if(dir == FORWARD) {
+ if(histidx == &hist)
+ return hist.string;
+ for(i=histidx->next; i != hist.next; i=i->next)
+ if(!i->string || !compare(i->string, string, n)) {
+ histidx = i;
+ return i->string;
+ }
+ return string;
+ }
+ assert(dir == BACKWARD);
+
+ if(histidx == &hist) {
+ free(hist.string);
+ hist.string = estrdup(input.string);
+ }
+
+ for(i=histidx->prev; i != &hist; i=i->prev)
+ if(!compare(i->string, string, n)) {
+ histidx = i;
+ return i->string;
+ }
+ return string;
+}
+
+void
+history_dump(const char *path, int max) {
+ static char *items[20];
+ static char *tmp;
+ Biobuf b;
+ Item *h, *first;
+ int i, n, fd;
+
+ SET(first);
+ if(fork() != 0)
+ return;
+
+ tmp = smprint("%s.XXXXXX", path);
+ fd = mkstemp(tmp);
+ if(fd < 0) {
+ fprint(2, "%s: Can't create temporary history file %q: %r\n", argv0, path);
+ return;
+ }
+
+ hist.string = input.string;
+ n = 0;
+ hist.next->prev = nil;
+ for(h=&hist; h; h=h->prev) {
+ for(i=0; i < nelem(items); i++)
+ if(items[i] && !strcmp(h->string, items[i])) {
+ splice(h);
+ goto next;
+ }
+ items[n++ % nelem(items)] = h->string;
+ first = h;
+ if(!max || n >= max)
+ break;
+ next:
+ continue;
+ }
+
+ Binit(&b, fd, OWRITE);
+ hist.next = nil;
+ for(h=first; h; h=h->next)
+ Bprint(&b, "%s\n", h->string);
+ Bterm(&b);
+ rename(tmp, path);
+ exit(0);
+}
+
diff --git a/cmd/menu/keys.c b/cmd/menu/keys.c
new file mode 100644
index 0000000..e99f061
--- /dev/null
+++ b/cmd/menu/keys.c
@@ -0,0 +1,142 @@
+#include "dat.h"
+#include <ctype.h>
+#include <strings.h>
+#include <unistd.h>
+#include "fns.h"
+
+typedef struct Key Key;
+
+struct Key {
+ Key* next;
+ long mask;
+ char* key;
+ char** action;
+};
+
+static Key* bindings;
+
+static void
+init_numlock(void) {
+ static int masks[] = {
+ ShiftMask, LockMask, ControlMask, Mod1Mask,
+ Mod2Mask, Mod3Mask, Mod4Mask, Mod5Mask
+ };
+ XModifierKeymap *modmap;
+ KeyCode kcode;
+ int i, max;
+
+ modmap = XGetModifierMapping(display);
+ kcode = keycode("Num_Lock");
+ if(kcode)
+ if(modmap && modmap->max_keypermod > 0) {
+ max = nelem(masks) * modmap->max_keypermod;
+ for(i = 0; i < max; i++)
+ if(modmap->modifiermap[i] == kcode)
+ numlock = masks[i / modmap->max_keypermod];
+ }
+ XFreeModifiermap(modmap);
+}
+
+/*
+ * To do: Find my red black tree implementation.
+ */
+void
+parse_keys(char *spec) {
+ static char *lines[1024];
+ static char *words[16];
+ Key *k;
+ char *p, *line;
+ int mask;
+ int i, nlines, nwords;
+
+ if(!numlock)
+ init_numlock();
+
+ nlines = tokenize(lines, nelem(lines), spec, '\n');
+ for(i=0; i < nlines; i++) {
+ line = lines[i];
+ p = strchr(line, '#');
+ if(p)
+ *p = '\0';
+
+ nwords = stokenize(words, nelem(words) - 1, line, " \t");
+ words[nwords] = nil;
+ if(!words[0])
+ continue;
+ if(parsekey(words[0], &mask, &p)) {
+ k = emallocz(sizeof *k);
+ k->key = p;
+ k->mask = mask;
+ k->action = strlistdup(words + 1);
+ k->next = bindings;
+ bindings = k;
+ }
+ }
+}
+
+char**
+find_key(char *key, long mask) {
+ Key *k;
+
+ /* Horrible hack. */
+ if(!strcmp(key, "ISO_Left_Tab"))
+ key = "Tab";
+
+ mask &= ~(numlock | LockMask) & ((1<<8) - 1);
+ for(k=bindings; k; k=k->next)
+ if(!strcasecmp(k->key, key) && k->mask == mask)
+ return k->action;
+ return nil;
+}
+
+ /* sed 's/"([^"]+)"/L\1/g' | tr 'a-z' 'A-Z' */
+ /* awk '{$1=""; print}' keys.txt | perl -e '$_=lc join "", <>; print join "\n", m/(\w+)/g;' | sort -u | sed 's:.*: "&",:' */
+char *symtab[] = {
+ "accept",
+ "backward",
+ "char",
+ "complete",
+ "first",
+ "forward",
+ "history",
+ "kill",
+ "last",
+ "line",
+ "literal",
+ "next",
+ "nextpage",
+ "prev",
+ "prevpage",
+ "reject",
+ "word",
+};
+
+static int
+_bsearch(char *s, char **tab, int ntab) {
+ int i, n, m, cmp;
+
+ if(s == nil)
+ return -1;
+
+ n = ntab;
+ i = 0;
+ while(n) {
+ m = n/2;
+ cmp = strcasecmp(s, tab[i+m]);
+ if(cmp == 0)
+ return i+m;
+ if(cmp < 0 || m == 0)
+ n = m;
+ else {
+ i += m;
+ n = n-m;
+ }
+ }
+ return -1;
+}
+
+int
+getsym(char *s) {
+ return _bsearch(s, symtab, nelem(symtab));
+}
+
diff --git a/cmd/menu/keys.txt b/cmd/menu/keys.txt
new file mode 100644
index 0000000..d16a6c6
--- /dev/null
+++ b/cmd/menu/keys.txt
@@ -0,0 +1,49 @@
+Control-j Accept
+Control-m Accept
+Return Accept
+Control-Shift-j Accept literal
+Control-Shift-m Accept literal
+Shift-Return Accept literal
+
+Escape Reject
+Control-Bracketleft Reject
+
+Left Backward char
+Control-b Backward char
+Right Forward char
+Control-f Forward char
+
+Mod1-b Backward word
+Mod1-f Forward word
+
+Control-a Backward line
+Control-e Forward line
+
+Control-p History backward
+Up History backward
+Control-n History forward
+Down History forward
+
+Backspace Kill char
+Control-h Kill char
+Control-Backspace Kill word
+Control-w Kill word
+Control-u Kill line
+
+Tab Complete next
+Control-i Complete next
+Mod1-l Complete next
+
+Shift-Tab Complete prev
+Control-Shift-i Complete prev
+Mod1-h Complete prev
+
+Prior Complete prevpage
+Mod1-k Complete prevpage
+Next Complete nextpage
+Mod1-j Complete nextpage
+Home Complete first
+Mod1-g Complete first
+End Complete last
+Mod1-Shift-g Complete last
+
diff --git a/cmd/menu/main.c b/cmd/menu/main.c
new file mode 100644
index 0000000..aaab27d
--- /dev/null
+++ b/cmd/menu/main.c
@@ -0,0 +1,346 @@
+/* Copyright ©2006-2010 Kris Maglione <fbsdaemon@gmail.com>
+ * See LICENSE file for license details.
+ */
+#define IXP_NO_P9_
+#define IXP_P9_STRUCTS
+#define EXTERN
+#include "dat.h"
+#include <X11/Xproto.h>
+#include <locale.h>
+#include <strings.h>
+#include <unistd.h>
+#include <bio.h>
+#include <clientutil.h>
+#include "fns.h"
+#define link _link
+
+static const char version[] = "wimenu-"VERSION", "COPYRIGHT"\n";
+static Biobuf* cmplbuf;
+static Biobuf* inbuf;
+static bool alwaysprint;
+static char* cmdsep;
+
+static void
+usage(void) {
+ fatal("usage: wimenu -i [-h <history>] [-a <address>] [-p <prompt>] [-s <screen>]\n");
+}
+
+static int
+errfmt(Fmt *f) {
+ return fmtstrcpy(f, ixp_errbuf());
+}
+
+/* Stubs. */
+void
+debug(int flag, const char *fmt, ...) {
+ va_list ap;
+
+ USED(flag);
+ va_start(ap, fmt);
+ vfprint(2, fmt, ap);
+ va_end(ap);
+}
+
+void dprint(long, char*, ...);
+void dprint(long mask, char *fmt, ...) {
+ va_list ap;
+
+ USED(mask);
+ va_start(ap, fmt);
+ vfprint(2, fmt, ap);
+ va_end(ap);
+}
+
+static inline void
+splice(Item *i) {
+ i->next->prev = i->prev;
+ i->prev->next = i->next;
+}
+static inline void
+link(Item *i, Item *j) {
+ i->next = j;
+ j->prev = i;
+}
+
+static Item*
+populate_list(Biobuf *buf, bool hist) {
+ Item ret;
+ Item *i;
+ char *p;
+ bool stop;
+
+ stop = !hist && !isatty(buf->fid);
+ i = &ret;
+ while((p = Brdstr(buf, '\n', true))) {
+ if(stop && p[0] == '\0')
+ break;
+ link(i, emallocz(sizeof *i));
+ i->next_link = i->next;
+ i = i->next;
+ i->string = p;
+ i->retstring = p;
+ if(cmdsep && (p = strstr(p, cmdsep))) {
+ *p = '\0';
+ i->retstring = p + strlen(cmdsep);
+ }
+ if(!hist) {
+ i->len = strlen(i->string);
+ i->width = textwidth_l(font, i->string, i->len);
+ if(i->width > maxwidth)
+ maxwidth = i->width;
+ }
+ }
+
+ link(i, &ret);
+ splice(&ret);
+ return ret.next != &ret ? ret.next : nil;
+}
+
+static void
+check_competions(IxpConn *c) {
+ char *s;
+
+ s = Brdstr(cmplbuf, '\n', true);
+ if(!s) {
+ ixp_hangup(c);
+ return;
+ }
+ input.filter_start = strtol(s, nil, 10);
+ items = populate_list(cmplbuf, false);
+ update_filter(false);
+ menu_draw();
+}
+
+Item*
+filter_list(Item *i, char *filter) {
+ static Item exact;
+ Item start, substr;
+ Item *exactp, *startp, *substrp;
+ Item **ip;
+ char *p;
+ int len;
+
+ len = strlen(filter);
+ exactp = &exact;
+ startp = &start;
+ substrp = &substr;
+ for(; i; i=i->next_link)
+ if((p = find(i->string, filter))) {
+ ip = &substrp;
+ if(p == i->string)
+ if(strlen(p) == len)
+ ip = &exactp;
+ else
+ ip = &startp;
+ link(*ip, i);
+ *ip = i;
+ }
+
+ link(substrp, &exact);
+ link(startp, &substr);
+ link(exactp, &start);
+ splice(&substr);
+ splice(&start);
+ splice(&exact);
+ return exact.next;
+}
+
+void
+update_input(void) {
+ if(alwaysprint) {
+ write(1, input.string, input.pos - input.string);
+ write(1, "\n", 1);
+ write(1, input.pos, input.end - input.pos);
+ write(1, "\n", 1);
+ }
+}
+
+void
+update_filter(bool print) {
+ char *filter;
+
+ filter = input.string + min(input.filter_start, input.pos - input.string);
+ if(input.pos < input.end)
+ filter = freelater(estrndup(filter, input.pos - filter));
+
+ matchidx = nil;
+ matchfirst = matchstart = filter_list(items, filter);
+ if(print)
+ update_input();
+}
+
+ErrorCode ignored_xerrors[] = {
+ { 0, }
+};
+
+static void
+end(IxpConn *c) {
+
+ USED(c);
+ srv.running = 0;
+}
+
+static void
+preselect(IxpServer *s) {
+
+ USED(s);
+ check_x_event(nil);
+}
+
+enum { PointerScreen = -1 };
+
+void
+init_screens(int screen_hint) {
+ Rectangle *rects;
+ Point p;
+ int i, n;
+
+ rects = xinerama_screens(&n);
+ if (screen_hint >= 0 && screen_hint < n)
+ /* We were given a valid screen index, use that. */
+ i = screen_hint;
+ else {
+ /* Pick the screen with the pointer, for now. Later,
+ * try for the screen with the focused window first.
+ */
+ p = querypointer(&scr.root);
+ for(i=0; i < n; i++)
+ if(rect_haspoint_p(p, rects[i]))
+ break;
+ if(i == n)
+ i = 0;
+ }
+ scr.rect = rects[i];
+ menu_show();
+}
+
+int
+main(int argc, char *argv[]) {
+ Item *item;
+ static char *address;
+ static char *histfile;
+ static char *keyfile;
+ static bool nokeys;
+ int i;
+ long ndump;
+ int screen;
+
+ quotefmtinstall();
+ fmtinstall('r', errfmt);
+ address = getenv("WMII_ADDRESS");
+ screen = PointerScreen;
+
+ find = strstr;
+ compare = strncmp;
+
+ ndump = -1;
+
+ ARGBEGIN{
+ case 'a':
+ address = EARGF(usage());
+ break;
+ case 'c':
+ alwaysprint = true;
+ break;
+ case 'h':
+ histfile = EARGF(usage());
+ break;
+ case 'i':
+ find = strcasestr;
+ compare = strncasecmp;
+ break;
+ case 'K':
+ nokeys = true;
+ case 'k':
+ keyfile = EARGF(usage());
+ break;
+ case 'n':
+ ndump = strtol(EARGF(usage()), nil, 10);
+ break;
+ case 'p':
+ prompt = EARGF(usage());
+ break;
+ case 's':
+ screen = strtol(EARGF(usage()), nil, 10);
+ break;
+ case 'S':
+ cmdsep = EARGF(usage());
+ break;
+ case 'v':
+ print("%s", version);
+ return 0;
+ default:
+ usage();
+ }ARGEND;
+
+ if(argc)
+ usage();
+
+ setlocale(LC_CTYPE, "");
+
+ initdisplay();
+
+ xext_init();
+ if(!isatty(0))
+ menu_init();
+
+ client_init(address);
+
+ srv.preselect = preselect;
+ ixp_listen(&srv, ConnectionNumber(display), nil, check_x_event, end);
+
+ ontop = !strcmp(readctl("bar on "), "top");
+ loadcolor(&cnorm, readctl("normcolors "));
+ loadcolor(&csel, readctl("focuscolors "));
+ font = loadfont(readctl("font "));
+ sscanf(readctl("fontpad "), "%d %d %d %d", &font->pad.min.x, &font->pad.max.x,
+ &font->pad.min.x, &font->pad.max.y);
+ if(!font)
+ fatal("Can't load font %q", readctl("font "));
+
+ cmplbuf = Bfdopen(0, OREAD);
+ items = populate_list(cmplbuf, false);
+ if(!isatty(cmplbuf->fid))
+ ixp_listen(&srv, cmplbuf->fid, inbuf, check_competions, nil);
+
+ caret_insert("", true);
+ update_filter(false);
+
+ if(!nokeys)
+ parse_keys(binding_spec);
+ if(keyfile) {
+ i = open(keyfile, O_RDONLY);
+ if(read(i, buffer, sizeof(buffer)) > 0)
+ parse_keys(buffer);
+ }
+
+ histidx = &hist;
+ link(&hist, &hist);
+ if(histfile) {
+ inbuf = Bopen(histfile, OREAD);
+ if(inbuf) {
+ item = populate_list(inbuf, true);
+ if(item) {
+ link(item->prev, &hist);
+ link(&hist, item);
+ }
+ Bterm(inbuf);
+ }
+ }
+
+ if(barwin == nil)
+ menu_init();
+
+ init_screens(screen);
+
+ i = ixp_serverloop(&srv);
+ if(i)
+ fprint(2, "%s: error: %r\n", argv0);
+ XCloseDisplay(display);
+
+ if(ndump >= 0 && histfile && result == 0)
+ history_dump(histfile, ndump);
+
+ return result;
+}
+
diff --git a/cmd/menu/menu.c b/cmd/menu/menu.c
new file mode 100644
index 0000000..c947ec4
--- /dev/null
+++ b/cmd/menu/menu.c
@@ -0,0 +1,344 @@
+#include "dat.h"
+#include <ctype.h>
+#include <strings.h>
+#include <unistd.h>
+#include "fns.h"
+
+static Handlers handlers;
+
+static int ltwidth;
+
+static void _menu_draw(bool);
+
+enum {
+ ACCEPT = CARET_LAST,
+ REJECT,
+ HIST,
+ KILL,
+ CMPL_NEXT,
+ CMPL_PREV,
+ CMPL_FIRST,
+ CMPL_LAST,
+ CMPL_NEXT_PAGE,
+ CMPL_PREV_PAGE,
+};
+
+void
+menu_init(void) {
+ WinAttr wa;
+
+ wa.override_redirect = 1;
+ wa.background_pixmap = ParentRelative;
+ wa.event_mask = ExposureMask | KeyPressMask;
+ barwin = createwindow(&scr.root, Rect(-1, -1, 1, 1), scr.depth, InputOutput,
+ &wa, CWOverrideRedirect
+ | CWBackPixmap
+ | CWEventMask);
+ sethandler(barwin, &handlers);
+ mapwin(barwin);
+
+ int i = 0;
+ while(!grabkeyboard(barwin)) {
+ if(i++ > 1000)
+ fatal("can't grab keyboard");
+ usleep(1000);
+ }
+}
+
+static void
+menu_unmap(long id, void *p) {
+
+ USED(id, p);
+ unmapwin(barwin);
+ XFlush(display);
+}
+
+static void
+selectitem(Item *i) {
+ if(i != matchidx) {
+ caret_set(input.filter_start, input.pos - input.string);
+ caret_insert(i->string, 0);
+ matchidx = i;
+ }
+}
+
+static void
+menu_cmd(int op, int motion) {
+ int n;
+
+ switch(op) {
+ case HIST:
+ n = input.pos - input.string;
+ caret_insert(history_search(motion, input.string, n), true);
+ input.pos = input.string + n;
+ break;
+ case KILL:
+ caret_delete(BACKWARD, motion);
+ break;
+ default:
+ goto next;
+ }
+ update_filter(true);
+next:
+ switch(op) {
+ case ACCEPT:
+ srv.running = false;
+ if(!matchidx && matchfirst->retstring && !motion)
+ if(input.filter_start == 0 && input.pos == input.end)
+ menu_cmd(CMPL_FIRST, 0);
+ if(!motion && matchidx && !strcmp(input.string, matchidx->string))
+ print("%s", matchidx->retstring);
+ else
+ print("%s", input.string);
+ break;
+ case REJECT:
+ srv.running = false;
+ result = 1;
+ break;
+ case BACKWARD:
+ case FORWARD:
+ caret_move(op, motion);
+ update_input();
+ break;
+ case CMPL_NEXT:
+ selectitem(matchidx ? matchidx->next : matchfirst);
+ break;
+ case CMPL_PREV:
+ selectitem((matchidx ? matchidx : matchstart)->prev);
+ break;
+ case CMPL_FIRST:
+ matchstart = matchfirst;
+ matchend = nil;
+ selectitem(matchstart);
+ break;
+ case CMPL_LAST:
+ selectitem(matchfirst->prev);
+ break;
+ case CMPL_NEXT_PAGE:
+ if(matchend)
+ selectitem(matchend->next);
+ break;
+ case CMPL_PREV_PAGE:
+ matchend = matchstart->prev;
+ matchidx = nil;
+ _menu_draw(false);
+ selectitem(matchstart);
+ break;
+ }
+ menu_draw();
+}
+
+static void
+_menu_draw(bool draw) {
+ Rectangle r, rd, rp, r2, extent;
+ CTuple *c;
+ Item *i;
+ int inputw, itemoff, end, pad, n, offset;
+
+ r = barwin->r;
+ r = rectsetorigin(r, ZP);
+
+ pad = (font->height & ~1) + font->pad.min.x + font->pad.max.x;
+
+ rd = r;
+ rp = ZR; // SET(rp)
+ if (prompt) {
+ if (!promptw)
+ promptw = textwidth(font, prompt) + 2 * ltwidth + pad;
+ rd.min.x += promptw;
+
+ rp = r;
+ rp.max.x = promptw;
+ }
+
+ inputw = min(Dx(rd) / 3, maxwidth);
+ inputw = max(inputw, textwidth(font, input.string)) + pad;
+ itemoff = inputw + 2 * ltwidth;
+ end = Dx(rd) - ltwidth;
+
+ fill(ibuf, r, cnorm.bg);
+
+ if(matchend && matchidx == matchend->next)
+ matchstart = matchidx;
+ else if(matchidx == matchstart->prev)
+ matchend = matchidx;
+ if (matchend == nil)
+ matchend = matchstart;
+
+ if(matchend == matchstart->prev && matchstart != matchidx) {
+ n = itemoff;
+ matchstart = matchend;
+ for(i=matchend; ; i=i->prev) {
+ n += i->width + pad;
+ if(n > end)
+ break;
+ matchstart = i;
+ if(i == matchfirst)
+ break;
+ }
+ }
+
+ if(!draw)
+ return;
+
+ r2 = rd;
+ for(i=matchstart; i->string; i=i->next) {
+ r2.min.x = promptw + itemoff;
+ itemoff = itemoff + i->width + pad;
+ r2.max.x = promptw + min(itemoff, end);
+ if(i != matchstart && itemoff > end)
+ break;
+
+ c = (i == matchidx) ? &csel : &cnorm;
+ fill(ibuf, r2, c->bg);
+ drawstring(ibuf, font, r2, Center, i->string, c->fg);
+ matchend = i;
+ if(i->next == matchfirst)
+ break;
+ }
+
+ r2 = rd;
+ r2.min.x = promptw + inputw;
+ if(matchstart != matchfirst)
+ drawstring(ibuf, font, r2, West, "<", cnorm.fg);
+ if(matchend->next != matchfirst)
+ drawstring(ibuf, font, r2, East, ">", cnorm.fg);
+
+ r2 = rd;
+ r2.max.x = promptw + inputw;
+ drawstring(ibuf, font, r2, West, input.string, cnorm.fg);
+
+ extent = textextents_l(font, input.string, input.pos - input.string, &offset);
+ r2.min.x = promptw + offset + font->pad.min.x - extent.min.x + pad/2 - 1;
+ r2.max.x = r2.min.x + 2;
+ r2.min.y++;
+ r2.max.y--;
+ border(ibuf, r2, 1, cnorm.border);
+
+ if (prompt)
+ drawstring(ibuf, font, rp, West, prompt, cnorm.fg);
+
+ border(ibuf, rd, 1, cnorm.border);
+ copyimage(barwin, r, ibuf, ZP);
+}
+
+void
+menu_draw(void) {
+ _menu_draw(true);
+}
+
+void
+menu_show(void) {
+ Rectangle r;
+ int height, pad;
+
+ USED(menu_unmap);
+
+ ltwidth = textwidth(font, "<");
+
+ pad = (font->height & ~1)/2;
+ height = labelh(font);
+
+ r = scr.rect;
+ if(ontop)
+ r.max.y = r.min.y + height;
+ else
+ r.min.y = r.max.y - height;
+ reshapewin(barwin, r);
+
+ freeimage(ibuf);
+ ibuf = allocimage(Dx(r), Dy(r), scr.depth);
+
+ mapwin(barwin);
+ raisewin(barwin);
+ menu_draw();
+}
+
+static void
+kdown_event(Window *w, XKeyEvent *e) {
+ char **action, **p;
+ char *key;
+ char buf[32];
+ int num;
+ KeySym ksym;
+
+ buf[0] = 0;
+ num = XLookupString(e, buf, sizeof buf, &ksym, 0);
+ key = XKeysymToString(ksym);
+ if(IsKeypadKey(ksym))
+ if(ksym == XK_KP_Enter)
+ ksym = XK_Return;
+ else if(ksym >= XK_KP_0 && ksym <= XK_KP_9)
+ ksym = (ksym - XK_KP_0) + XK_0;
+
+ if(IsFunctionKey(ksym)
+ || IsMiscFunctionKey(ksym)
+ || IsKeypadKey(ksym)
+ || IsPrivateKeypadKey(ksym)
+ || IsPFKey(ksym))
+ return;
+
+ action = find_key(key, e->state);
+ if(action == nil || action[0] == nil) {
+ if(num && !iscntrl(buf[0])) {
+ caret_insert(buf, false);
+ update_filter(true);
+ menu_draw();
+ }
+ }
+ else {
+ long mask = 0;
+# define have(val) !!(mask & (1 << val))
+ for(p=action+1; *p; p++)
+ mask |= 1 << getsym(*p);
+ int amount = (
+ have(LCHAR) ? CHAR :
+ have(LWORD) ? WORD :
+ have(LLINE) ? LINE :
+ -1);
+ switch(getsym(action[0])) {
+ case LACCEPT:
+ menu_cmd(ACCEPT, have(LLITERAL));
+ break;
+ case LBACKWARD:
+ menu_cmd(BACKWARD, amount);
+ break;
+ case LCOMPLETE:
+ amount = (
+ have(LNEXT) ? CMPL_NEXT :
+ have(LPREV) ? CMPL_PREV :
+ have(LNEXTPAGE) ? CMPL_NEXT_PAGE :
+ have(LPREVPAGE) ? CMPL_PREV_PAGE :
+ have(LFIRST) ? CMPL_FIRST :
+ have(LLAST) ? CMPL_LAST :
+ CMPL_NEXT);
+ menu_cmd(amount, 0);
+ break;
+ case LFORWARD:
+ menu_cmd(FORWARD, amount);
+ break;
+ case LHISTORY:
+ menu_cmd(HIST, have(LBACKWARD) ? BACKWARD : FORWARD);
+ break;
+ case LKILL:
+ menu_cmd(KILL, amount);
+ break;
+ case LREJECT:
+ menu_cmd(REJECT, 0);
+ break;
+ }
+ }
+}
+
+static void
+expose_event(Window *w, XExposeEvent *e) {
+
+ USED(w);
+ menu_draw();
+}
+
+static Handlers handlers = {
+ .expose = expose_event,
+ .kdown = kdown_event,
+};
+
diff --git a/cmd/strut/Makefile b/cmd/strut/Makefile
new file mode 100644
index 0000000..fca4bd2
--- /dev/null
+++ b/cmd/strut/Makefile
@@ -0,0 +1,27 @@
+ROOT= ../..
+include ${ROOT}/mk/hdr.mk
+include ${ROOT}/mk/wmii.mk
+
+main.c: ${ROOT}/mk/wmii.mk
+
+TARG = wistrut
+HFILES= dat.h fns.h
+
+PACKAGES += $(X11PACKAGES) xext xrandr xinerama
+
+LIB = $(LIBIXP)
+LIBS += -lm $(LIBS9)
+CFLAGS += -DIXP_NEEDAPI=86
+OBJ = main \
+ event \
+ ewmh \
+ win \
+ _util \
+ ../wmii/map \
+ ../wmii/printevent \
+ printevent_kludge \
+ ../wmii/x11 \
+ ../util
+
+include ${ROOT}/mk/one.mk
+
diff --git a/cmd/strut/_util.c b/cmd/strut/_util.c
new file mode 100644
index 0000000..364ff81
--- /dev/null
+++ b/cmd/strut/_util.c
@@ -0,0 +1,77 @@
+/* Copyright ©2008-2010 Kris Maglione <fbsdaemon@gmail.com>
+ * See LICENSE file for license details.
+ */
+#include "dat.h"
+#include <ctype.h>
+#include <string.h>
+#include "fns.h"
+
+#define strbcmp(str, const) (strncmp((str), (const), sizeof(const)-1))
+static int
+getbase(const char **s, long *sign) {
+ const char *p;
+ int ret;
+
+ ret = 10;
+ *sign = 1;
+ if(**s == '-') {
+ *sign = -1;
+ *s += 1;
+ }else if(**s == '+')
+ *s += 1;
+
+ p = *s;
+ if(!strbcmp(p, "0x")) {
+ *s += 2;
+ ret = 16;
+ }
+ else if(isdigit(p[0])) {
+ if(p[1] == 'r') {
+ *s += 2;
+ ret = p[0] - '0';
+ }
+ else if(isdigit(p[1]) && p[2] == 'r') {
+ *s += 3;
+ ret = 10*(p[0]-'0') + (p[1]-'0');
+ }
+ }
+ else if(p[0] == '0') {
+ ret = 8;
+ }
+ if(ret != 10 && (**s == '-' || **s == '+'))
+ *sign = 0;
+ return ret;
+}
+
+bool
+getlong(const char *s, long *ret) {
+ const char *end;
+ char *rend;
+ int base;
+ long sign;
+
+ end = s+strlen(s);
+ base = getbase(&s, &sign);
+ if(sign == 0)
+ return false;
+
+ *ret = sign * strtol(s, &rend, base);
+ return (end == rend);
+}
+
+bool
+getulong(const char *s, ulong *ret) {
+ const char *end;
+ char *rend;
+ int base;
+ long sign;
+
+ end = s+strlen(s);
+ base = getbase(&s, &sign);
+ if(sign < 1)
+ return false;
+
+ *ret = strtoul(s, &rend, base);
+ return (end == rend);
+}
+
diff --git a/cmd/strut/dat.h b/cmd/strut/dat.h
new file mode 100644
index 0000000..810d384
--- /dev/null
+++ b/cmd/strut/dat.h
@@ -0,0 +1,33 @@
+#include <fmt.h>
+#include <stdarg.h>
+#include <stdbool.h>
+#include <stdlib.h>
+#include <unistd.h>
+#include <util.h>
+#include <ixp.h>
+#include <x11.h>
+
+#define BLOCK(x) do { x; }while(0)
+
+#ifndef EXTERN
+# define EXTERN extern
+#endif
+
+extern Handlers handlers;
+
+EXTERN bool running;
+
+EXTERN Window win;
+EXTERN Window frame;
+EXTERN long xtime;
+
+EXTERN char buffer[8092];
+EXTERN char* _buffer;
+
+static char* const _buf_end = buffer + sizeof buffer;
+
+#define bufclear() \
+ BLOCK( _buffer = buffer; _buffer[0] = '\0' )
+#define bufprint(...) \
+ _buffer = seprint(_buffer, _buf_end, __VA_ARGS__)
+
diff --git a/cmd/strut/event.c b/cmd/strut/event.c
new file mode 100644
index 0000000..aebd8ba
--- /dev/null
+++ b/cmd/strut/event.c
@@ -0,0 +1,309 @@
+/* Copyright ©2006-2010 Kris Maglione <fbsdaemon@gmail.com>
+ * See LICENSE file for license details.
+ */
+#include "dat.h"
+#include "fns.h"
+
+static void (*handler[LASTEvent])(XEvent*);
+
+void
+dispatch_event(XEvent *e) {
+ /* print("%E\n", e); */
+ if(e->type < nelem(handler) && handler[e->type])
+ handler[e->type](e);
+}
+
+#define handle(w, fn, ev) \
+ BLOCK(if((w)->handler->fn) (w)->handler->fn((w), ev))
+
+#ifdef notdef
+uint
+flushevents(long event_mask, bool dispatch) {
+ XEvent ev;
+ uint n = 0;
+
+ while(XCheckMaskEvent(display, event_mask, &ev)) {
+ if(dispatch)
+ dispatch_event(&ev);
+ n++;
+ }
+ return n;
+}
+
+static int
+findenter(Display *d, XEvent *e, XPointer v) {
+ long *l;
+
+ USED(d);
+ l = (long*)v;
+ if(*l)
+ return false;
+ if(e->type == EnterNotify)
+ return true;
+ if(e->type == MotionNotify)
+ (*l)++;
+ return false;
+}
+
+/* This isn't perfect. If there were motion events in the queue
+ * before this was called, then it flushes nothing. If we don't
+ * check for them, we might lose a legitamate enter event.
+ */
+uint
+flushenterevents(void) {
+ XEvent e;
+ long l;
+ int n;
+
+ l = 0;
+ n = 0;
+ while(XCheckIfEvent(display, &e, findenter, (void*)&l))
+ n++;
+ return n;
+}
+#endif
+
+static int
+findtime(Display *d, XEvent *e, XPointer v) {
+ Window *w;
+
+ w = (Window*)v;
+ if(e->type == PropertyNotify && e->xproperty.window == w->xid) {
+ xtime = e->xproperty.time;
+ return true;
+ }
+ return false;
+}
+
+void
+xtime_kludge(void) {
+ Window *w;
+ WinAttr wa;
+ XEvent e;
+ long l;
+
+ w = createwindow(&scr.root, Rect(0, 0, 1, 1), 0, InputOnly, &wa, 0);
+
+ XSelectInput(display, w->xid, PropertyChangeMask);
+ changeprop_long(w, "ATOM", "ATOM", &l, 0);
+ XIfEvent(display, &e, findtime, (void*)w);
+
+ destroywindow(w);
+}
+
+static void
+buttonrelease(XEvent *e) {
+ XButtonPressedEvent *ev;
+ Window *w;
+
+ ev = &e->xbutton;
+ if((w = findwin(ev->window)))
+ handle(w, bup, ev);
+}
+
+static void
+buttonpress(XEvent *e) {
+ XButtonPressedEvent *ev;
+ Window *w;
+
+ ev = &e->xbutton;
+ if((w = findwin(ev->window)))
+ handle(w, bdown, ev);
+ else
+ XAllowEvents(display, ReplayPointer, ev->time);
+}
+
+static void
+clientmessage(XEvent *e) {
+ XClientMessageEvent *ev;
+
+ ev = &e->xclient;
+ USED(ev);
+}
+
+static void
+configurenotify(XEvent *e) {
+ XConfigureEvent *ev;
+ Window *w;
+
+ ev = &e->xconfigure;
+ if((w = findwin(ev->window)))
+ handle(w, config, ev);
+}
+
+static void
+destroynotify(XEvent *e) {
+ XDestroyWindowEvent *ev;
+ Window *w;
+
+ ev = &e->xdestroywindow;
+ if((w = findwin(ev->window)))
+ handle(w, destroy, ev);
+}
+
+static void
+enternotify(XEvent *e) {
+ XCrossingEvent *ev;
+ Window *w;
+
+ ev = &e->xcrossing;
+ xtime = ev->time;
+ if(ev->mode != NotifyNormal)
+ return;
+
+ if((w = findwin(ev->window)))
+ handle(w, enter, ev);
+}
+
+static void
+leavenotify(XEvent *e) {
+ XCrossingEvent *ev;
+
+ ev = &e->xcrossing;
+ xtime = ev->time;
+}
+
+static void
+focusin(XEvent *e) {
+ XFocusChangeEvent *ev;
+ Window *w;
+
+ ev = &e->xfocus;
+ /* Yes, we're focusing in on nothing, here. */
+ if(ev->detail == NotifyDetailNone) {
+ /* FIXME: Do something. */
+ return;
+ }
+
+ if(!((ev->detail == NotifyNonlinear)
+ ||(ev->detail == NotifyNonlinearVirtual)
+ ||(ev->detail == NotifyVirtual)
+ ||(ev->detail == NotifyInferior)
+ ||(ev->detail == NotifyAncestor)))
+ return;
+ if((ev->mode == NotifyWhileGrabbed))
+ return;
+
+ if((w = findwin(ev->window)))
+ handle(w, focusin, ev);
+}
+
+static void
+focusout(XEvent *e) {
+ XFocusChangeEvent *ev;
+ Window *w;
+
+ ev = &e->xfocus;
+ if(!((ev->detail == NotifyNonlinear)
+ ||(ev->detail == NotifyNonlinearVirtual)
+ ||(ev->detail == NotifyVirtual)
+ ||(ev->detail == NotifyInferior)
+ ||(ev->detail == NotifyAncestor)))
+ return;
+
+ if((w = findwin(ev->window)))
+ handle(w, focusout, ev);
+}
+
+static void
+expose(XEvent *e) {
+ XExposeEvent *ev;
+ Window *w;
+
+ ev = &e->xexpose;
+ if(ev->count == 0) {
+ if((w = findwin(ev->window)))
+ handle(w, expose, ev);
+ }
+}
+
+static void
+keypress(XEvent *e) {
+ XKeyEvent *ev;
+
+ ev = &e->xkey;
+ xtime = ev->time;
+}
+
+static void
+mappingnotify(XEvent *e) {
+ XMappingEvent *ev;
+
+ ev = &e->xmapping;
+ /* Why do you need me to tell you this? */
+ XRefreshKeyboardMapping(ev);
+}
+
+static void
+motionnotify(XEvent *e) {
+ XMotionEvent *ev;
+ Window *w;
+
+ ev = &e->xmotion;
+ xtime = ev->time;
+ if((w = findwin(ev->window)))
+ handle(w, motion, ev);
+}
+
+static void
+propertynotify(XEvent *e) {
+ XPropertyEvent *ev;
+ Window *w;
+
+ ev = &e->xproperty;
+ xtime = ev->time;
+ if((w = findwin(ev->window)))
+ handle(w, property, ev);
+}
+
+static void
+mapnotify(XEvent *e) {
+ XMapEvent *ev;
+ Window *w;
+
+ ev = &e->xmap;
+ if((w = findwin(ev->window)))
+ handle(w, map, ev);
+}
+
+static void
+unmapnotify(XEvent *e) {
+ XUnmapEvent *ev;
+ Window *w;
+
+ ev = &e->xunmap;
+ if((w = findwin(ev->window)) && w->parent && (ev->event == w->parent->xid)) {
+ if(ev->send_event || w->unmapped-- == 0)
+ handle(w, unmap, ev);
+ }
+}
+
+static void (*handler[LASTEvent])(XEvent*) = {
+ [ButtonPress] = buttonpress,
+ [ButtonRelease] = buttonrelease,
+ [ClientMessage] = clientmessage,
+ [ConfigureNotify] = configurenotify,
+ [DestroyNotify] = destroynotify,
+ [EnterNotify] = enternotify,
+ [Expose] = expose,
+ [FocusIn] = focusin,
+ [FocusOut] = focusout,
+ [KeyPress] = keypress,
+ [LeaveNotify] = leavenotify,
+ [MapNotify] = mapnotify,
+ [MappingNotify] = mappingnotify,
+ [MotionNotify] = motionnotify,
+ [PropertyNotify] = propertynotify,
+ [UnmapNotify] = unmapnotify,
+};
+
+void
+xevent_loop(void) {
+ XEvent ev;
+
+ while(running) {
+ XNextEvent(display, &ev);
+ dispatch_event(&ev);
+ }
+}
+
diff --git a/cmd/strut/ewmh.c b/cmd/strut/ewmh.c
new file mode 100644
index 0000000..b56923c
--- /dev/null
+++ b/cmd/strut/ewmh.c
@@ -0,0 +1,84 @@
+/* Copyright ©2007-2010 Kris Maglione <fbsdaemon@gmail.com>
+ * See LICENSE file for license details.
+ */
+#include "dat.h"
+#include <limits.h>
+#include <string.h>
+#include "fns.h"
+
+#define Net(x) ("_NET_" x)
+#define Action(x) ("_NET_WM_ACTION_" x)
+#define State(x) ("_NET_WM_STATE_" x)
+#define Type(x) ("_NET_WM_WINDOW_TYPE_" x)
+#define NET(x) xatom(Net(x))
+#define ACTION(x) xatom(Action(x))
+#define STATE(x) xatom(State(x))
+#define TYPE(x) xatom(Type(x))
+
+enum {
+ Left, Right, Top, Bottom,
+ LeftMin, LeftMax,
+ RightMin, RightMax,
+ TopMin, TopMax,
+ BottomMin, BottomMax,
+ Last
+};
+
+void
+ewmh_getstrut(Window *w, Rectangle struts[4]) {
+ long *strut;
+ ulong n;
+
+ memset(struts, 0, sizeof struts);
+
+ n = getprop_long(w, Net("WM_STRUT_PARTIAL"), "CARDINAL",
+ 0L, &strut, Last);
+ if(n != Last) {
+ free(strut);
+ n = getprop_long(w, Net("WM_STRUT"), "CARDINAL",
+ 0L, &strut, 4L);
+ if(n != 4) {
+ free(strut);
+ return;
+ }
+ strut = erealloc(strut, Last * sizeof *strut);
+ strut[LeftMin] = strut[RightMin] = 0;
+ strut[LeftMax] = strut[RightMax] = INT_MAX;
+ strut[TopMin] = strut[BottomMin] = 0;
+ strut[TopMax] = strut[BottomMax] = INT_MAX;
+ }
+ struts[Left] = Rect(0, strut[LeftMin], strut[Left], strut[LeftMax]);
+ struts[Right] = Rect(-strut[Right], strut[RightMin], 0, strut[RightMax]);
+ struts[Top] = Rect(strut[TopMin], 0, strut[TopMax], strut[Top]);
+ struts[Bottom] = Rect(strut[BottomMin], -strut[Bottom], strut[BottomMax], 0);
+ free(strut);
+}
+
+void
+ewmh_setstrut(Window *w, Rectangle struts[4]) {
+ long strut[Last];
+ int i;
+
+ strut[LeftMin] = struts[Left].min.y;
+ strut[Left] = struts[Left].max.x;
+ strut[LeftMax] = struts[Left].max.y;
+
+ strut[RightMin] = struts[Right].min.y;
+ strut[Right] = -struts[Right].min.x;
+ strut[RightMax] = struts[Right].max.y;
+
+ strut[TopMin] = struts[Top].min.x;
+ strut[Top] = struts[Top].max.y;
+ strut[TopMax] = struts[Top].max.x;
+
+ strut[BottomMin] = struts[Bottom].min.x;
+ strut[Bottom] = -struts[Bottom].min.y;
+ strut[BottomMax] = struts[Bottom].max.x;
+
+ for(i=0; i<Last; i++)
+ if(strut[i] < 0)
+ strut[i] = 0;
+
+ changeprop_long(w, Net("WM_STRUT_PARTIAL"), "CARDINAL", strut, nelem(strut));
+}
+
diff --git a/cmd/strut/fns.h b/cmd/strut/fns.h
new file mode 100644
index 0000000..af6383e
--- /dev/null
+++ b/cmd/strut/fns.h
@@ -0,0 +1,18 @@
+
+void debug(int, const char*, ...);
+void dispatch_event(XEvent*);
+uint flushevents(long, bool);
+uint flushenterevents(void);
+void xevent_loop(void);
+void xtime_kludge(void);
+
+void restrut(void);
+
+bool getlong(const char*, long*);
+bool getulong(const char*, ulong*);
+
+void ewmh_getstrut(Window*, Rectangle[4]);
+void ewmh_setstrut(Window*, Rectangle[4]);
+
+void printevent(XEvent *e);
+
diff --git a/cmd/strut/main.c b/cmd/strut/main.c
new file mode 100644
index 0000000..ef9d27f
--- /dev/null
+++ b/cmd/strut/main.c
@@ -0,0 +1,102 @@
+/* Copyright ©2006-2010 Kris Maglione <fbsdaemon@gmail.com>
+ * See LICENSE file for license details.
+ */
+#define EXTERN
+#include "dat.h"
+#include <X11/Xproto.h>
+#include <locale.h>
+#include <string.h>
+#include "fns.h"
+
+static const char version[] = "witray-"VERSION", ©2007 Kris Maglione\n";
+
+static void
+usage(void) {
+ fatal("usage: %s <window>\n", argv0);
+}
+
+static int
+errfmt(Fmt *f) {
+ return fmtstrcpy(f, ixp_errbuf());
+}
+
+void
+debug(int flag, const char *fmt, ...) {
+ va_list ap;
+
+ USED(flag);
+ va_start(ap, fmt);
+ vfprint(2, fmt, ap);
+ va_end(ap);
+}
+
+ErrorCode ignored_xerrors[] = { {0,} };
+
+static Window
+findframe(Window *w) {
+ XWindow *children;
+ XWindow xw, par, root;
+ Window ret = {0, };
+ uint n;
+
+ for(par=w->xid; par != scr.root.xid; ) {
+ xw = par;
+ XQueryTree(display, xw, &root, &par, &children, &n);
+ XFree(children);
+ }
+ ret.xid = xw;
+ ret.parent = &scr.root;
+ return ret;
+}
+
+static void
+getwinsize(Window *win) {
+ int x, y;
+ uint w, h;
+ XWindow root;
+ uint border, depth;
+
+ XGetGeometry(display, win->xid, &root,
+ &x, &y, &w, &h,
+ &border, &depth);
+ win->r = rectaddpt(Rect(0, 0, w, h),
+ Pt(x+border, y+border));
+}
+
+int
+main(int argc, char *argv[]) {
+ char *s;
+
+ fmtinstall('r', errfmt);
+extern int fmtevent(Fmt*);
+ fmtinstall('E', fmtevent);
+
+ ARGBEGIN{
+ default:
+ usage();
+ }ARGEND;
+
+ s = EARGF(usage());
+ if(!getulong(s, &win.xid))
+ usage();
+
+ if(argc)
+ usage();
+
+ setlocale(LC_CTYPE, "");
+
+ initdisplay();
+
+ frame = findframe(&win);
+ getwinsize(&frame);
+ restrut();
+ sethandler(&frame, &handlers);
+ selectinput(&frame, StructureNotifyMask);
+
+ running = true;
+ xevent_loop();
+
+ XCloseDisplay(display);
+ return 0;
+}
+
diff --git a/cmd/strut/printevent_kludge.c b/cmd/strut/printevent_kludge.c
new file mode 100644
index 0000000..c5e53cb
--- /dev/null
+++ b/cmd/strut/printevent_kludge.c
@@ -0,0 +1,12 @@
+#include "dat.h"
+
+void dprint(const char *fmt, ...);
+void
+dprint(const char *fmt, ...) {
+ va_list ap;
+
+ va_start(ap, fmt);
+ vfprint(2, fmt, ap);
+ va_end(ap);
+}
+
diff --git a/cmd/strut/win.c b/cmd/strut/win.c
new file mode 100644
index 0000000..ba607cf
--- /dev/null
+++ b/cmd/strut/win.c
@@ -0,0 +1,102 @@
+/* Copyright ©2008-2010 Kris Maglione <fbsdaemon@gmail.com>
+ * See LICENSE file for license details.
+ */
+#include "dat.h"
+#include <string.h>
+#include "fns.h"
+
+void
+restrut(void) {
+ enum { Left, Right, Top, Bottom };
+ Rectangle strut[4];
+ Rectangle r;
+
+ r = frame.r;
+ memset(strut, 0, sizeof strut);
+ if(Dx(r) < Dx(scr.rect)/2) {
+ if(r.min.x <= scr.rect.min.x) {
+ strut[Left] = r;
+ strut[Left].min.x = 0;
+ strut[Left].max.x -= scr.rect.min.x;
+ }
+ if(r.max.x >= scr.rect.max.x) {
+ strut[Right] = r;
+ strut[Right].min.x -= scr.rect.max.x;
+ strut[Right].max.x = 0;
+ }
+ }
+ if(Dy(r) < Dy(scr.rect)/2) {
+ if(r.min.y <= scr.rect.min.y) {
+ strut[Top] = r;
+ strut[Top].min.y = 0;
+ strut[Top].max.y -= scr.rect.min.y;
+ }
+ if(r.max.y >= scr.rect.max.y) {
+ strut[Bottom] = r;
+ strut[Bottom].min.y -= scr.rect.max.y;
+ strut[Bottom].max.y = 0;
+ }
+ }
+
+#define pstrut(name) \
+ if(!eqrect(strut[name], ZR)) \
+ fprint(2, "strut["#name"] = %R\n", strut[name])
+ /* Choose the struts which take up the least space.
+ * Not ideal.
+ */
+ if(Dy(strut[Top])) {
+ if(Dx(strut[Left]))
+ if(Dy(strut[Top]) < Dx(strut[Left]))
+ strut[Left] = ZR;
+ else
+ strut[Top] = ZR;
+ if(Dx(strut[Right]))
+ if(Dy(strut[Top]) < Dx(strut[Right]))
+ strut[Right] = ZR;
+ else
+ strut[Top] = ZR;
+ }
+ if(Dy(strut[Bottom])) {
+ if(Dx(strut[Left]))
+ if(Dy(strut[Bottom]) < Dx(strut[Left]))
+ strut[Left] = ZR;
+ else
+ strut[Bottom] = ZR;
+ if(Dx(strut[Right]))
+ if(Dy(strut[Bottom]) < Dx(strut[Right]))
+ strut[Right] = ZR;
+ else
+ strut[Bottom] = ZR;
+ }
+
+#if 0
+ pstrut(Left);
+ pstrut(Right);
+ pstrut(Top);
+ pstrut(Bottom);
+#endif
+
+ ewmh_setstrut(&win, strut);
+}
+
+static void
+config(Window *w, XConfigureEvent *ev) {
+
+ USED(w);
+
+ frame.r = rectaddpt(Rect(0, 0, ev->width, ev->height),
+ Pt(ev->x+ev->border_width, ev->y+ev->border_width));
+ restrut();
+}
+
+static void
+destroy(Window *w, XDestroyWindowEvent *ev) {
+ USED(w, ev);
+ running = false;
+}
+
+Handlers handlers = {
+ .config = config,
+ .destroy = destroy,
+};
+
diff --git a/cmd/util.c b/cmd/util.c
new file mode 100644
index 0000000..2556d28
--- /dev/null
+++ b/cmd/util.c
@@ -0,0 +1,272 @@
+/* Written by Kris Maglione <fbsdaemon at gmail dot com> */
+/* Public domain */
+#include <ctype.h>
+#include <errno.h>
+#include <sys/types.h>
+#include <signal.h>
+#include <stdarg.h>
+#include <stdlib.h>
+#include <stdint.h>
+#include <stdio.h>
+#include <string.h>
+#include <strings.h>
+#include <unistd.h>
+#include <util.h>
+#include <fmt.h>
+
+typedef struct VFmt VFmt;
+struct VFmt {
+ const char *fmt;
+ va_list args;
+};
+
+#ifdef VARARGCK
+# pragma varargck type "V" VFmt*
+#endif
+
+static int
+Vfmt(Fmt *f) {
+ VFmt *vf;
+ int i;
+
+ vf = va_arg(f->args, VFmt*);
+ i = fmtvprint(f, vf->fmt, vf->args);
+ return i;
+}
+
+void
+fatal(const char *fmt, ...) {
+ VFmt fp;
+
+ fmtinstall('V', Vfmt);
+ fmtinstall('\001', Vfmt);
+
+ fp.fmt = fmt;
+ va_start(fp.args, fmt);
+ fprint(2, "%s: fatal: %V\n", argv0, &fp);
+ va_end(fp.args);
+
+ exit(1);
+}
+
+void*
+freelater(void *p) {
+ static char* obj[16];
+ static long nobj;
+ int id;
+
+ id = nobj++ % nelem(obj);
+ free(obj[id]);
+ obj[id] = p;
+ return p;
+}
+
+char*
+vsxprint(const char *fmt, va_list ap) {
+ char *s;
+
+ s = vsmprint(fmt, ap);
+ freelater(s);
+ return s;
+}
+
+char*
+sxprint(const char *fmt, ...) {
+ va_list ap;
+ char *ret;
+
+ va_start(ap, fmt);
+ ret = vsxprint(fmt, ap);
+ va_end(ap);
+ return ret;
+}
+
+void
+_die(char *file, int line, char *msg, ...) {
+ va_list ap;
+
+ va_start(ap, msg);
+ fprint(2, "%s: dieing at %s:%d: %s\n",
+ argv0, file, line,
+ vsxprint(msg, ap));
+ va_end(ap);
+
+ kill(getpid(), SIGABRT);
+ abort(); /* Adds too many frames:
+ * _die()
+ * abort()
+ * raise(SIGABRT)
+ * kill(getpid(), SIGABRT)
+ */
+}
+
+/* Can't malloc */
+static void
+mfatal(char *name, uint size) {
+ const char
+ couldnot[] = ": fatal: Could not ",
+ paren[] = "() ",
+ bytes[] = " bytes\n";
+ char buf[1024];
+ char sizestr[8];
+ int i;
+
+ i = sizeof sizestr;
+ do {
+ sizestr[--i] = '0' + (size%10);
+ size /= 10;
+ } while(size > 0);
+
+ strlcat(buf, argv0, sizeof buf);
+ strlcat(buf, couldnot, sizeof buf);
+ strlcat(buf, name, sizeof buf);
+ strlcat(buf, paren, sizeof buf);
+ strlcat(buf, sizestr+i, sizeof buf);
+ strlcat(buf, bytes, sizeof buf);
+ write(2, buf, strlen(buf));
+
+ exit(1);
+}
+
+void *
+emalloc(uint size) {
+ void *ret = malloc(size);
+ if(!ret)
+ mfatal("malloc", size);
+ return ret;
+}
+
+void *
+emallocz(uint size) {
+ void *ret = emalloc(size);
+ memset(ret, 0, size);
+ return ret;
+}
+
+void *
+erealloc(void *ptr, uint size) {
+ void *ret = realloc(ptr, size);
+ if(!ret)
+ mfatal("realloc", size);
+ return ret;
+}
+
+char*
+estrdup(const char *str) {
+ void *ret = strdup(str);
+ if(!ret)
+ mfatal("strdup", strlen(str));
+ return ret;
+}
+
+char*
+estrndup(const char *str, uint len) {
+ char *ret;
+
+ len = min(len, strlen(str));
+ ret = emalloc(len + 1);
+ memcpy(ret, str, len);
+ ret[len] = '\0';
+ return ret;
+}
+
+
+uint
+tokenize(char *res[], uint reslen, char *str, char delim) {
+ char *s;
+ uint i;
+
+ i = 0;
+ s = str;
+ while(i < reslen && *s) {
+ while(*s == delim)
+ *(s++) = '\0';
+ if(*s)
+ res[i++] = s;
+ while(*s && *s != delim)
+ s++;
+ }
+ return i;
+}
+
+uint
+stokenize(char *res[], uint reslen, char *str, char *delim) {
+ char *s;
+ uint i;
+
+ i = 0;
+ s = str;
+ while(i < reslen && *s) {
+ while(strchr(delim, *s))
+ *(s++) = '\0';
+ if(*s)
+ res[i++] = s;
+ while(*s && !strchr(delim, *s))
+ s++;
+ }
+ return i;
+}
+
+int
+max(int a, int b) {
+ if(a > b)
+ return a;
+ return b;
+}
+
+int
+min(int a, int b) {
+ if(a < b)
+ return a;
+ return b;
+}
+
+int
+utflcpy(char *to, const char *from, int l) {
+ char *p;
+
+ p = utfecpy(to, to+l, from);
+ return p-to;
+}
+
+uint
+strlcat(char *dst, const char *src, uint size) {
+ const char *s;
+ char *d;
+ int n, len;
+
+ d = dst;
+ s = src;
+ n = size;
+ while(n-- > 0 && *d != '\0')
+ d++;
+ len = n;
+
+ while(*s != '\0') {
+ if(n-- > 0)
+ *d++ = *s;
+ s++;
+ }
+ if(len > 0)
+ *d = '\0';
+ return size - n - 1;
+}
+
+/* TODO: Make this UTF-8 compliant. */
+char*
+strcasestr(const char *dst, const char *src) {
+ int dc, sc;
+ int len;
+
+ len = strlen(src) - 1;
+ for(; (sc = *src) && *dst; src++) {
+ sc = tolower(dc);
+ for(; (dc = *dst); dst++) {
+ dc = tolower(dc);
+ if(sc == dc && !strncasecmp(dst+1, src+1, len))
+ return (char*)(uintptr_t)dst;
+ }
+ }
+ return nil;
+}
+
diff --git a/cmd/wihack.sh b/cmd/wihack.sh
new file mode 100644
index 0000000..2c401d4
--- /dev/null
+++ b/cmd/wihack.sh
@@ -0,0 +1,44 @@
+#!/bin/sh -f
+
+usage() {
+ echo 1>&2 Usage: \
+ "$0 [-transient <window>] [-type <window_type>[,...]] [-tags <tags>] <command> [<arg> ...]"
+ exit 1
+}
+
+checkarg='[ ${#@} -gt 0 ] || usage'
+export WMII_HACK_TIME=$(date +%s)
+
+while [ ${#@} -gt 0 ]
+do
+ case $1 in
+ -transient)
+ shift; eval $checkarg
+ export WMII_HACK_TRANSIENT=$1
+ shift;;
+ -type)
+ shift; eval $checkarg
+ export WMII_HACK_TYPE=$1
+ shift;;
+ -tags)
+ shift; eval $checkarg
+ export WMII_HACK_TAGS=$1
+ shift;;
+ -*)
+ usage;;
+ *)
+ break;;
+ esac
+done
+
+eval $checkarg
+
+if [ ! -u "`which $1`" -a ! -g "`which $1`" ]
+then
+ export LD_PRELOAD=libwmii_hack.so
+ export LD_LIBRARY_PATH="@LIBDIR@${LD_LIBRARY_PATH:+:}${LD_LIBRARY_PATH}"
+else
+ unset WMII_HACK_TRANSIENT WMII_HACK_TYPE WMII_HACK_TAGS
+fi
+exec "$@"
+
diff --git a/cmd/wmii.rc.rc b/cmd/wmii.rc.rc
new file mode 100755
index 0000000..392a0d7
--- /dev/null
+++ b/cmd/wmii.rc.rc
@@ -0,0 +1,181 @@
+
+# For the time being, this file follows the lisp bracing
+# convention. i.e.:
+# if(frob this) {
+# frob that
+# if(frob theother) {
+# unfrob this
+# unfrob that}}
+
+wmiiscript=$1
+wmiikeys=()
+
+wi_newline='
+'
+
+echo Start $wmiiscript | wmiir write /event >[2]/dev/null \
+ || exit write
+
+if(~ $scriptname '')
+ scriptname=$wmiiscript
+
+# Blech.
+if(! test -x $PLAN9/bin/read)
+ fn read { sh -c 'read -r x || exit 1; echo $x' }
+
+fn wi_atexit {}
+fn sigexit {
+ wi_atexit
+}
+
+fn wi_fatal {
+ echo $scriptname: Fatal: $*
+ exit fatal
+}
+
+fn wi_notice {
+ xmessage $scriptname: Notice: $*
+}
+
+fn wi_readctl { wmiir read /ctl | sed -n 's/^'$1' (.*)/\1/p' }
+
+wmiifont=`{wi_readctl font}
+wmiinormcol=`{wi_readctl normcolors}
+wmiifocuscol=`{wi_readctl focuscolors}
+
+fn wi_fnmenu {
+ group=$1^Menu-$2 last=$group^_last fns=`{wi_getfuns $group} {
+ shift 2
+ if(! ~ $#fns 0) {
+ res = `{wmii9menu -i $"($last) $fns} \
+ if(! ~ $res '') {
+ ($last) = $res
+ $group-$res $*}}}
+}
+
+fn wi_fn-p {
+ rc -c 'whatis '$1 >[2]/dev/null | grep -s '^fn '
+}
+
+fn wi_proglist {
+ # XXX: maxdepth is not POSIX compliant.
+ ifs=: { find -L `{echo -n $*} -type f -a -maxdepth 1 \
+ '(' -perm -0100 -o -perm -0010 -o -perm -0001 ')' >[2]/dev/null \
+ | sed 's,.*/,,' | sort | uniq}
+}
+
+fn wi_actions {
+ { wi_proglist $WMII_CONFPATH
+ wi_getfuns Action
+ } | sort | uniq
+}
+
+fn wi_script {
+ noprog=true prog=() {
+ if(~ $1 -f) {
+ shift
+ noprog=/dev/null
+ }
+ prog = `{rc -c 'path=$confpath whatis '$1 >[2]/dev/null \
+ | grep -v '^fn |=' || echo $noprog}
+ shift; echo $prog $*}
+}
+
+
+fn wi_initkeys {
+ ifs=() {
+ wmiikeys = `{wmiir read /keys} {
+ mykeys = `{comm -23 \
+ <{wi_getfuns Key | sort | uniq} \
+ <{echo $wmiikeys | sort | uniq}}
+ {echo $wmiikeys; wi_getfuns Key} \
+ | sort | uniq \
+ | wmiir write /keys }}
+ fn wi_atexit {
+ wi_cleankeys
+ }
+}
+
+fn wi_cleankeys {
+ ifs=() {
+ wmiikeys = `{wmiir read /keys} {
+ comm -23 <{echo $wmiikeys | sort | uniq} \
+ <{echo $mykeys} \
+ | wmiir write /keys }}
+}
+
+fn wi_runcmd { @{
+ rfork ns
+ path=$oldpath
+ if(~ $1 -t) {
+ shift
+ * = (wihack -tags `{wmiir read /tag/sel/ctl | sed 1q} $*) }
+ fn `{env | 9 sed -n 's/^fn#([^=]+).*/\1/p'}
+ mykeys=()
+ if(! ~ $* '')
+ eval exec $* & }
+}
+
+fn wi_getfuns {
+ env | sed -n 's/^fn#'^$1^'-([^=]+).*/\1/p' | sort | uniq
+}
+
+for(i in Key Event Action '*Menu')
+ fns=`{wi_getfuns $i} {
+ if(! ~ $fns '')
+ fn $i-^$fns}
+
+fn wi_tags {
+ wmiir ls /tag | sed 's,/,,; /^sel$/d'
+}
+
+fn wi_seltag {
+ wmiir read /tag/sel/ctl | sed 1q
+}
+
+fn wi_selclient {
+ wmiir read /client/sel/ctl | sed 1q
+}
+
+fn wi_readevent {
+ wmiir read /event
+}
+
+fn wi_eventloop {
+ wi_initkeys
+
+ wi_readevent |
+ while(ifs=$wi_ewlinel{wi_event=`{read}}) {
+ ifs=$wi_newline{
+ wi_arg=`{echo $wi_event | sed 's/^[^ ]+ //'}}
+ * = `{echo $wi_event}
+ event = $1; shift
+ Event-$"event $*
+ } >[2]/dev/null </dev/null
+ true
+}
+
+fn Event-Key {
+ Key-$1 $1
+}
+
+fn Event-Quit {
+ exit
+}
+
+fn Event-Start {
+ if(~ $1 $wmiiscript)
+ exit
+}
+
+fn Action {
+ cmd=$1 action=Action-$"cmd { shift
+ if(! ~ $cmd '') {
+ if(wi_fn-p $action)
+ $action $*
+ if not
+ wi_runcmd `{wi_script $cmd} $*
+ }
+ }
+}
+
diff --git a/cmd/wmii.sh.sh b/cmd/wmii.sh.sh
new file mode 100755
index 0000000..d636f26
--- /dev/null
+++ b/cmd/wmii.sh.sh
@@ -0,0 +1,219 @@
+
+[ -z "$scriptname" ] && scriptname="$wmiiscript"
+echo Start $wmiiscript | wmiir write /event 2>/dev/null ||
+ exit 1
+
+wi_newline='
+'
+
+_wi_script() {
+ # Awk script to mangle key/event/action definition spec
+ # into switch-case functions and lists of the cases. Also
+ # generates a simple key binding help text based on KeyGroup
+ # clauses and comments that appear on key lines.
+ #
+ # Each clause (Key, Event, Action) generates a function of the
+ # same name which executes the indented text after the matching
+ # clause. Clauses are selected based on the first argument passed
+ # to the mangled function. Additionally, a variable is created named
+ # for the plouralized version of the clause name (Keys, Events,
+ # Actions) which lists each case value. These are used for actions
+ # menus and to write wmii's /keys file.
+ cat <<'!'
+ BEGIN {
+ arg[1] = "Nop"
+ narg = 1;
+ body = "";
+ keyhelp = ""
+ }
+ function quote(s) {
+ gsub(/'/, "'\\''", s)
+ return "'" s "'"
+ }
+ function addevent() {
+ var = arg[1] "s"
+ for(i=2; i <= narg; i++) {
+ if(body == "")
+ delete a[arg[1],arg[i]]
+ else
+ a[arg[1],arg[i]] = body
+ if(i == 2) {
+ # There's a bug here. Can you spot it?
+ gsub("[^a-zA-Z_0-9]", "_", arg[2]);
+ body = sprintf("%s %s \"$@\"", arg[1], arg[2])
+ }
+ }
+ }
+ /^(Key)Group[ \t]/ {
+ sub(/^[^ \t]+[ \t]+/, "")
+ keyhelp = keyhelp "\n " $0 "\n"
+ }
+ /^(Event|Key|Action|Menu)[ \t]/ {
+ addevent()
+ split($0, tmp, /[ \t]+#[ \t]*/)
+ narg = split(tmp[1], arg)
+ if(arg[1] == "Key" && tmp[2])
+ for (i=2; i <= narg; i++)
+ keyhelp = keyhelp sprintf(" %-20s %s\n",
+ arg[i], tmp[2])
+ body = ""
+ }
+ /^[ \t]/ {
+ sub(/^( |\t)/, "")
+ body = body"\n"$0
+ }
+
+ END {
+ addevent()
+ for(k in a) {
+ split(k, b, SUBSEP)
+ c[b[1]] = c[b[1]] b[2] "\n"
+ if(body != "")
+ d[b[1]] = d[b[1]] quote(b[2]) ")" a[k] "\n;;\n"
+ }
+ for(k in c)
+ printf "%ss=%s\n", k, quote(c[k])
+ for(k in d) {
+ printf "%s() {\n", k
+ printf " %s=$1; shift\n", tolower(k)
+ printf "case $%s in\n%s\n*) return 1\nesac\n", tolower(k), d[k]
+ printf "}\n"
+ }
+ print "KeysHelp=" quote(keyhelp)
+ }
+!
+}
+
+_wi_text() {
+ cat <<'!'
+Event Start
+ if [ "$1" = "$wmiiscript" ]; then
+ exit
+ fi
+Event Key
+ Key "$@"
+!
+ eval "cat <<!
+$( (test ! -t 0 && cat; for a; do eval "$a"; done) | sed '/^[ ]/s/\([$`\\]\)/\\\1/g')
+!
+"
+}
+
+wi_events() {
+ #cho "$(_wi_text "$@" | awk "$(_wi_script)")" | cat -n
+ eval "$(_wi_text "$@" | awk "$(_wi_script)")"
+}
+
+wi_fatal() {
+ echo $scriptname: Fatal: $*
+ exit 1
+}
+
+wi_notice() {
+ xmessage $scriptname: Notice: $*
+}
+
+wi_readctl() {
+ wmiir read /ctl | sed -n 's/^'$1' //p'
+}
+
+wmiifont="$(wi_readctl font)"
+wmiinormcol="$(wi_readctl normcolors)"
+wmiifocuscol="$(wi_readctl focuscolors)"
+
+wi_fnmenu() {
+ group="$1-$2"; shift 2
+ _last="$(echo $group|tr - _)_last"
+ eval "last=\"\$$_last\""
+ res=$(set -- $(echo "$Menus" | awk -v "s=$group" 'BEGIN{n=length(s)}
+ substr($1,1,n) == s{print substr($1,n+2)}')
+ [ $# != 0 ] && wmii9menu -i "$last" "$@")
+ if [ -n "$res" ]; then
+ eval "$_last="'"$res"'
+ Menu $group-$res "$@"
+ fi
+}
+
+wi_proglist() {
+ ls -lL $(echo $* | sed 'y/:/ /') 2>/dev/null \
+ | awk '$1 ~ /^[^d].*x/ { print $NF }' \
+ | sort | uniq
+}
+
+wi_actions() {
+ { wi_proglist $WMII_CONFPATH
+ echo -n "$Actions"
+ } | sort | uniq
+}
+
+wi_runconf() {
+ sflag=""; if [ "$1" = -s ]; then sflag=1; shift; fi
+ which="$(which which)"
+ prog=$(PATH="$WMII_CONFPATH" "$which" -- $1 2>/dev/null); shift
+ if [ -n "$prog" ]; then
+ if [ -z "$sflag" ]
+ then "$prog" "$@"
+ else . "$prog"
+ fi
+ else return 1
+ fi
+}
+
+wi_script() {
+ _noprog=true
+ if [ "$1" = -f ]; then
+ shift
+ _noprog=/dev/null
+ fi
+ which=$(which which)
+ _prog=$(PATH="$WMII_CONFPATH" $which $1 || echo $_noprog); shift
+ shift; echo "$_prog $*"
+}
+
+wi_runcmd() {
+ if [ "$1" = -t ]; then
+ shift
+ set -- wihack -tags $(wmiir read /tag/sel/ctl | sed 1q) "$*"
+ fi
+ eval exec "$*" &
+}
+
+wi_tags() {
+ wmiir ls /tag | sed 's,/,,; /^sel$/d'
+}
+
+wi_seltag() {
+ wmiir read /tag/sel/ctl | sed 1q | tr -d '\012'
+}
+
+wi_selclient() {
+ wmiir read /client/sel/ctl | sed 1q | tr -d '\012'
+}
+
+wi_eventloop() {
+ echo "$Keys" | wmiir write /keys
+
+ if [ "$1" = -i ]
+ then cat
+ else wmiir read /event
+ fi |
+ while read wi_event; do
+ IFS="$wi_newline"
+ wi_arg=$(echo "$wi_event" | sed 's/^[^ ]* //')
+ unset IFS
+ set -- $wi_event
+ event=$1; shift
+ ( Event $event "$@" )
+ done
+ true
+}
+
+action() {
+ action=$1; shift
+ if [ -n "$action" ]; then
+ set +x
+ Action $action "$@" \
+ || wi_runconf $action "$@"
+ fi
+}
+
diff --git a/cmd/wmii/Makefile b/cmd/wmii/Makefile
new file mode 100644
index 0000000..de635ca
--- /dev/null
+++ b/cmd/wmii/Makefile
@@ -0,0 +1,47 @@
+ROOT= ../..
+include $(ROOT)/mk/hdr.mk
+include $(ROOT)/mk/wmii.mk
+
+main.c: $(ROOT)/mk/wmii.mk
+
+TARG = wmii
+HFILES= dat.h fns.h
+
+PACKAGES += $(X11PACKAGES) xext xrandr xrender xinerama
+
+LIB = $(LIBIXP)
+LIBS += -lm $(LIBS9)
+
+CFLAGS += $(INCICONV) -DIXP_NEEDAPI=97
+OBJ = area \
+ bar \
+ client \
+ column \
+ div \
+ error \
+ event \
+ ewmh \
+ float \
+ frame \
+ fs \
+ geom \
+ key \
+ layout \
+ main \
+ map \
+ message \
+ mouse \
+ print \
+ root \
+ rule \
+ printevent\
+ screen \
+ _util \
+ view \
+ xdnd \
+ x11 \
+ xext \
+ ../util
+
+include $(ROOT)/mk/one.mk
+
diff --git a/cmd/wmii/_util.c b/cmd/wmii/_util.c
new file mode 100644
index 0000000..feafba9
--- /dev/null
+++ b/cmd/wmii/_util.c
@@ -0,0 +1,378 @@
+/* Copyright ©2008-2010 Kris Maglione <maglione.k at Gmail>
+ * See LICENSE file for license details.
+ */
+#include "dat.h"
+#include <errno.h>
+#include <fcntl.h>
+#include <stdarg.h>
+#include <sys/signal.h>
+#include <sys/wait.h>
+#include <unistd.h>
+#include <bio.h>
+#include "fns.h"
+
+/* Blech. */
+#define VECTOR(type, nam, c) \
+void \
+vector_##c##init(Vector_##nam *v) { \
+ memset(v, 0, sizeof *v); \
+} \
+ \
+void \
+vector_##c##free(Vector_##nam *v) { \
+ free(v->ary); \
+ memset(v, 0, sizeof *v); \
+} \
+ \
+void \
+vector_##c##push(Vector_##nam *v, type val) { \
+ if(v->n == v->size) { \
+ if(v->size == 0) \
+ v->size = 2; \
+ v->size <<= 2; \
+ v->ary = erealloc(v->ary, v->size * sizeof *v->ary); \
+ } \
+ v->ary[v->n++] = val; \
+} \
+
+VECTOR(long, long, l)
+VECTOR(Rectangle, rect, r)
+VECTOR(void*, ptr, p)
+
+int
+doublefork(void) {
+ pid_t pid;
+ int status;
+
+ switch(pid=fork()) {
+ case -1:
+ fatal("Can't fork(): %r");
+ case 0:
+ switch(pid=fork()) {
+ case -1:
+ fatal("Can't fork(): %r");
+ case 0:
+ return 0;
+ default:
+ exit(0);
+ }
+ default:
+ waitpid(pid, &status, 0);
+ return pid;
+ }
+ /* NOTREACHED */
+}
+
+void
+closeexec(int fd) {
+ if(fcntl(fd, F_SETFD, FD_CLOEXEC) == -1)
+ fatal("can't set %d close on exec: %r", fd);
+}
+
+int
+spawn3(int fd[3], const char *file, char *argv[]) {
+ /* Some ideas from Russ Cox's libthread port. */
+ int p[2];
+ int pid;
+
+ if(pipe(p) < 0)
+ return -1;
+ closeexec(p[1]);
+
+ switch(pid = doublefork()) {
+ case 0:
+ dup2(fd[0], 0);
+ dup2(fd[1], 1);
+ dup2(fd[2], 2);
+
+ execvp(file, argv);
+ write(p[1], &errno, sizeof errno);
+ exit(1);
+ break;
+ default:
+ close(p[1]);
+ if(read(p[0], &errno, sizeof errno) == sizeof errno)
+ pid = -1;
+ close(p[0]);
+ break;
+ case -1: /* can't happen */
+ break;
+ }
+
+ close(fd[0]);
+ /* These could fail if any of these was also a previous fd. */
+ close(fd[1]);
+ close(fd[2]);
+ return pid;
+}
+
+int
+spawn3l(int fd[3], const char *file, ...) {
+ va_list ap;
+ char **argv;
+ int i, n;
+
+ va_start(ap, file);
+ for(n=0; va_arg(ap, char*); n++)
+ ;
+ va_end(ap);
+
+ argv = emalloc((n+1) * sizeof *argv);
+ va_start(ap, file);
+ quotefmtinstall();
+ for(i=0; i <= n; i++)
+ argv[i] = va_arg(ap, char*);
+ va_end(ap);
+
+ i = spawn3(fd, file, argv);
+ free(argv);
+ return i;
+}
+
+#ifdef __linux__
+# define PROGTXT "exe"
+#else
+# define PROGTXT "file"
+#endif
+
+static void
+_backtrace(int pid, char *btarg) {
+ char *proc, *spid, *gdbcmd;
+ int fd[3], p[2];
+ int status, cmdfd;
+
+ gdbcmd = estrdup("/tmp/gdbcmd.XXXXXX");
+ if(pipe(p) < 0)
+ goto done;
+ closeexec(p[0]);
+
+ cmdfd = mkstemp(gdbcmd);
+ if(cmdfd < 0)
+ goto done;
+
+ fprint(cmdfd, "bt %s\n", btarg);
+ fprint(cmdfd, "detach\n");
+ close(cmdfd);
+
+ fd[0] = open("/dev/null", O_RDONLY);
+ fd[1] = p[1];
+ fd[2] = dup(2);
+
+ proc = sxprint("/proc/%d/" PROGTXT, pid);
+ spid = sxprint("%d", pid);
+ if(spawn3l(fd, "gdb", "gdb", "-batch", "-x", gdbcmd, proc, spid, nil) < 0) {
+ unlink(gdbcmd);
+ goto done;
+ }
+
+ Biobuf bp;
+ char *s;
+
+ Binit(&bp, p[0], OREAD);
+ while((s = Brdstr(&bp, '\n', 1))) {
+ Dprint(DStack, "%s\n", s);
+ free(s);
+ }
+ unlink(gdbcmd);
+
+done:
+ free(gdbcmd);
+ kill(pid, SIGKILL);
+ waitpid(pid, &status, 0);
+}
+
+void
+backtrace(char *btarg) {
+ int pid;
+
+ /* Fork so we can backtrace the child. Keep this stack
+ * frame minimal, so the trace is fairly clean.
+ */
+ Debug(DStack)
+ switch(pid = fork()) {
+ case -1:
+ return;
+ case 0:
+ kill(getpid(), SIGSTOP);
+ _exit(0);
+ default:
+ _backtrace(pid, btarg);
+ break;
+ }
+
+}
+
+void
+reinit(Regex *r, char *regx) {
+
+ refree(r);
+
+ if(regx[0] != '\0') {
+ r->regex = estrdup(regx);
+ r->regc = regcomp(regx);
+ }
+}
+
+void
+refree(Regex *r) {
+
+ free(r->regex);
+ free(r->regc);
+ r->regex = nil;
+ r->regc = nil;
+}
+
+void
+uniq(char **toks) {
+ char **p, **q;
+
+ q = toks;
+ if(*q == nil)
+ return;
+ for(p=q+1; *p; p++)
+ if(strcmp(*q, *p))
+ *++q = *p;
+ *++q = nil;
+}
+
+char**
+comm(int cols, char **toka, char **tokb) {
+ Vector_ptr vec;
+ char **ret;
+ int cmp;
+
+ vector_pinit(&vec);
+ while(*toka || *tokb) {
+ if(!*toka)
+ cmp = 1;
+ else if(!*tokb)
+ cmp = -1;
+ else
+ cmp = strcmp(*toka, *tokb);
+ if(cmp < 0) {
+ if(cols & CLeft)
+ vector_ppush(&vec, *toka);
+ toka++;
+ }else if(cmp > 0) {
+ if(cols & CRight)
+ vector_ppush(&vec, *tokb);
+ tokb++;
+ }else {
+ if(cols & CCenter)
+ vector_ppush(&vec, *toka);
+ toka++;
+ tokb++;
+ }
+ }
+ vector_ppush(&vec, nil);
+ ret = strlistdup((char**)vec.ary);
+ free(vec.ary);
+ return ret;
+}
+
+void
+grep(char **list, Reprog *re, int flags) {
+ char **p, **q;
+ int res;
+
+ q = list;
+ for(p=q; *p; p++) {
+ res = 0;
+ if(re)
+ res = regexec(re, *p, nil, 0);
+ if(res && !(flags & GInvert)
+ || !res && (flags & GInvert))
+ *q++ = *p;
+ }
+ *q = nil;
+}
+
+char*
+join(char **list, char *sep) {
+ Fmt f;
+ char **p;
+
+ if(fmtstrinit(&f) < 0)
+ abort();
+
+ for(p=list; *p; p++) {
+ if(p != list)
+ fmtstrcpy(&f, sep);
+ fmtstrcpy(&f, *p);
+ }
+
+ return fmtstrflush(&f);
+}
+
+int
+strlcatprint(char *buf, int len, const char *fmt, ...) {
+ va_list ap;
+ int buflen;
+ int ret;
+
+ va_start(ap, fmt);
+ buflen = strlen(buf);
+ ret = vsnprint(buf+buflen, len-buflen, fmt, ap);
+ va_end(ap);
+ return ret;
+}
+
+char*
+pathsearch(const char *path, const char *file, bool slashok) {
+ char *orig, *p, *s;
+
+ if(!slashok && strchr(file, '/') > file)
+ file = sxprint("%s/%s", getcwd(buffer, sizeof buffer), file);
+ else if(!strncmp(file, "./", 2))
+ file = sxprint("%s/%s", getcwd(buffer, sizeof buffer), file+2);
+ if(file[0] == '/') {
+ if(access(file, X_OK))
+ return strdup(file);
+ return nil;
+ }
+
+ orig = estrdup(path ? path : getenv("PATH"));
+ for(p=orig; (s=strtok(p, ":")); p=nil) {
+ s = smprint("%s/%s", s, file);
+ if(!access(s, X_OK))
+ break;
+ free(s);
+ }
+ free(orig);
+ return s;
+}
+
+int
+unquote(char *buf, char *toks[], int ntoks) {
+ char *s, *t;
+ bool inquote;
+ int n;
+
+ n = 0;
+ s = buf;
+ while(*s && n < ntoks) {
+ while(*s && utfrune(" \t\r\n", *s))
+ s++;
+ inquote = false;
+ toks[n] = s;
+ t = s;
+ while(*s && (inquote || !utfrune(" \t\r\n", *s))) {
+ if(*s == '\'') {
+ if(inquote && s[1] == '\'')
+ *t++ = *s++;
+ else
+ inquote = !inquote;
+ }
+ else
+ *t++ = *s;
+ s++;
+ }
+ if(*s)
+ s++;
+ *t = '\0';
+ if(s != toks[n])
+ n++;
+ }
+ return n;
+}
+
diff --git a/cmd/wmii/area.c b/cmd/wmii/area.c
new file mode 100644
index 0000000..0f94e72
--- /dev/null
+++ b/cmd/wmii/area.c
@@ -0,0 +1,328 @@
+/* Copyright ©2006-2010 Kris Maglione <maglione.k at Gmail>
+ * See LICENSE file for license details.
+ */
+#include "dat.h"
+#include <math.h>
+#include <limits.h>
+#include "fns.h"
+
+Client*
+area_selclient(Area *a) {
+ if(a && a->sel)
+ return a->sel->client;
+ return nil;
+}
+
+int
+area_idx(Area *a) {
+ View *v;
+ Area *ap;
+ uint i;
+
+ v = a->view;
+ if(a->floating)
+ return -1;
+ i = 1;
+ for(ap=v->areas[a->screen]; a != ap; ap=ap->next)
+ i++;
+ return i;
+}
+
+static Rectangle
+area_rect(void *v) {
+ Area *a;
+
+ a = v;
+ return a->r;
+}
+
+Area*
+area_find(View *v, Rectangle r, int dir, bool wrap) {
+ static Vector_ptr vec;
+ Area *a;
+ int s;
+
+ vec.n = 0;
+ foreach_column(v, s, a)
+ vector_ppush(&vec, a);
+
+ return findthing(r, dir, &vec, area_rect, wrap);
+}
+
+int
+afmt(Fmt *f) {
+ Area *a;
+
+ a = va_arg(f->args, Area*);
+ if(a == nil)
+ return fmtstrcpy(f, "<nil>");
+ if(a->floating)
+ return fmtstrcpy(f, "~");
+ if(a->screen > 0 || (f->flags & FmtSharp))
+ return fmtprint(f, "%d:%d", a->screen, area_idx(a));
+ return fmtprint(f, "%d", area_idx(a));
+}
+
+char*
+area_name(Area *a) {
+
+ if(a == nil)
+ return "<nil>";
+ if(a->floating)
+ return "~";
+ return sxprint("%d", area_idx(a));
+}
+
+Area*
+area_create(View *v, Area *pos, int scrn, uint width) {
+ static ushort id = 1;
+ int i, j;
+ uint minwidth, index;
+ int numcols;
+ Area *a;
+
+ assert(!pos || pos->screen == scrn);
+ SET(index);
+ if(v->areas) { /* Creating a column. */
+ minwidth = column_minwidth();
+ index = pos ? area_idx(pos) : 1;
+ numcols = 0;
+ for(a=v->areas[scrn]; a; a=a->next)
+ numcols++;
+
+ /* TODO: Need a better sizing/placing algorithm.
+ */
+ if(width == 0) {
+ if(numcols >= 0) {
+ width = view_newcolwidth(v, index);
+ if (width == 0)
+ width = Dx(v->r[scrn]) / (numcols + 1);
+ }
+ else
+ width = Dx(v->r[scrn]);
+ }
+
+ if(width < minwidth)
+ width = minwidth;
+ minwidth = numcols * minwidth + minwidth;
+ if(minwidth > Dx(v->r[scrn]))
+ return nil;
+
+ i = minwidth - Dx(v->pad[scrn]) - Dx(v->r[scrn]);
+ if(i > 0 && Dx(v->pad[scrn])) {
+ j = min(i/2, v->pad[scrn].min.x);
+ v->pad[scrn].min.x -= j;
+ v->pad[scrn].max.x += i - j;
+ }
+
+ view_scale(v, scrn, Dx(v->r[scrn]) - width);
+ }
+
+ a = emallocz(sizeof *a);
+ a->view = v;
+ a->screen = scrn;
+ a->id = id++;
+ a->floating = !v->floating;
+ if(a->floating)
+ a->mode = Coldefault;
+ else
+ a->mode = def.colmode;
+ a->frame = nil;
+ a->sel = nil;
+
+ a->r = v->r[scrn];
+ a->r.min.x = 0;
+ a->r.max.x = width;
+
+ if(a->floating) {
+ v->floating = a;
+ a->screen = -1;
+ }
+ else if(pos) {
+ a->next = pos->next;
+ a->prev = pos;
+ }
+ else {
+ a->next = v->areas[scrn];
+ v->areas[scrn] = a;
+ }
+ if(a->prev)
+ a->prev->next = a;
+ if(a->next)
+ a->next->prev = a;
+
+ if(v->sel == nil && !a->floating)
+ area_focus(a);
+
+ if(!a->floating)
+ event("CreateColumn %ud\n", index);
+ return a;
+}
+
+void
+area_destroy(Area *a) {
+ Area *newfocus;
+ View *v;
+ int idx;
+
+ v = a->view;
+
+ if(a->frame)
+ die("destroying non-empty area");
+
+ if(v->revert == a)
+ v->revert = nil;
+ if(v->oldsel == a)
+ v->oldsel = nil;
+
+ idx = area_idx(a);
+
+ if(a->prev && !a->prev->floating)
+ newfocus = a->prev;
+ else
+ newfocus = a->next;
+
+ /* Can only destroy the floating area when destroying a
+ * view---after destroying all columns.
+ */
+ assert(!a->floating || !v->areas[0]);
+ if(a->prev)
+ a->prev->next = a->next;
+ else if(!a->floating)
+ v->areas[a->screen] = a->next;
+ else
+ v->floating = nil;
+ if(a->next)
+ a->next->prev = a->prev;
+
+ if(newfocus && v->sel == a)
+ area_focus(newfocus);
+
+ view_arrange(v);
+ event("DestroyArea %d\n", idx);
+
+ free(a);
+}
+
+void
+area_moveto(Area *to, Frame *f) {
+ Area *from;
+
+ assert(to->view == f->view);
+
+ if(f->client->fullscreen >= 0 && !to->floating)
+ return;
+
+ from = f->area;
+ if (from == to)
+ return;
+
+ area_detach(f);
+
+ /* Temporary kludge. */
+ if(!to->floating
+ && to->floating != from->floating
+ && !eqrect(f->colr, ZR))
+ column_attachrect(to, f, f->colr);
+ else
+ area_attach(to, f);
+}
+
+void
+area_setsel(Area *a, Frame *f) {
+ View *v;
+
+ v = a->view;
+ /* XXX: Stack. */
+ for(; f && f->collapsed && f->anext; f=f->anext)
+ ;
+ for(; f && f->collapsed && f->aprev; f=f->aprev)
+ ;
+
+ if(a == v->sel && f)
+ frame_focus(f);
+ else
+ a->sel = f;
+}
+
+void
+area_attach(Area *a, Frame *f) {
+
+ f->area = a;
+ if(a->floating)
+ float_attach(a, f);
+ else
+ column_attach(a, f);
+
+ view_arrange(a->view);
+
+ if(btassert("4 full", a->frame && a->sel == nil))
+ a->sel = a->frame;
+}
+
+void
+area_detach(Frame *f) {
+ View *v;
+ Area *a;
+
+ a = f->area;
+ v = a->view;
+
+ if(a->floating)
+ float_detach(f);
+ else
+ column_detach(f);
+
+ if(v->sel->sel == nil && v->floating->sel)
+ v->sel = v->floating;
+
+ view_arrange(v);
+}
+
+void
+area_focus(Area *a) {
+ Frame *f;
+ View *v;
+ Area *old_a;
+
+ v = a->view;
+ f = a->sel;
+ old_a = v->sel;
+
+ if(!a->floating && view_fullscreen_p(v, a->screen))
+ return;
+
+ v->sel = a;
+ if(!a->floating) {
+ v->selcol = area_idx(a);
+ v->selscreen = a->screen;
+ }
+ if(a != old_a)
+ v->oldsel = nil;
+
+ if((old_a) && (a->floating != old_a->floating)) {
+ v->revert = old_a;
+ if(v->floating->max)
+ view_update(v);
+ }
+
+ if(v != selview)
+ return;
+
+ move_focus(old_a->sel, f);
+
+ if(f)
+ client_focus(f->client);
+ else
+ client_focus(nil);
+
+ if(a != old_a) {
+ event("AreaFocus %a\n", a);
+ /* Deprecated */
+ if(a->floating)
+ event("FocusFloating\n");
+ else
+ event("ColumnFocus %d\n", area_idx(a));
+ }
+}
+
diff --git a/cmd/wmii/bar.c b/cmd/wmii/bar.c
new file mode 100644
index 0000000..fd4ba26
--- /dev/null
+++ b/cmd/wmii/bar.c
@@ -0,0 +1,300 @@
+/* Copyright ©2006-2010 Kris Maglione <maglione.k at Gmail>
+ * See LICENSE file for license details.
+ */
+#include "dat.h"
+#include "fns.h"
+
+static Handlers handlers;
+
+#define foreach_bar(s, b) \
+ for(int __bar_n=0; __bar_n < nelem((s)->bar); __bar_n++) \
+ for((b)=(s)->bar[__bar_n]; (b); (b)=(b)->next)
+
+void
+bar_init(WMScreen *s) {
+ WinAttr wa;
+
+ if(s->barwin) {
+ bar_resize(s);
+ return;
+ }
+
+ s->brect = s->r;
+ s->brect.min.y = s->brect.max.y - labelh(def.font);
+
+ wa.override_redirect = 1;
+ wa.background_pixmap = ParentRelative;
+ wa.event_mask = ExposureMask
+ | ButtonPressMask
+ | ButtonReleaseMask
+ | FocusChangeMask;
+ s->barwin = createwindow(&scr.root, s->brect, scr.depth, InputOutput,
+ &wa, CWOverrideRedirect
+ | CWBackPixmap
+ | CWEventMask);
+ s->barwin->aux = s;
+ xdnd_initwindow(s->barwin);
+ sethandler(s->barwin, &handlers);
+ if(s == screens[0])
+ mapwin(s->barwin);
+}
+
+void
+bar_resize(WMScreen *s) {
+
+ s->brect = s->r;
+ s->brect.min.y = s->r.max.y - labelh(def.font);
+ if(s == screens[0])
+ reshapewin(s->barwin, s->brect);
+ else
+ s->brect.min.y = s->r.max.y;
+ /* FIXME: view_arrange. */
+}
+
+void
+bar_setbounds(WMScreen *s, int left, int right) {
+ Rectangle *r;
+
+ if(s != screens[0])
+ return;
+
+ r = &s->brect;
+ r->min.x = left;
+ r->max.x = right;
+ if(Dy(*r))
+ reshapewin(s->barwin, *r);
+}
+
+void
+bar_sety(WMScreen *s, int y) {
+ Rectangle *r;
+ int dy;
+
+ r = &s->brect;
+
+ dy = Dy(*r);
+ r->min.y = y;
+ r->max.y = y + dy;
+ if(Dy(*r))
+ reshapewin(s->barwin, *r);
+}
+
+Bar*
+bar_create(Bar **bp, const char *name) {
+ static uint id = 1;
+ WMScreen *s, **sp;
+ Bar *b;
+ uint i;
+
+ b = bar_find(*bp, name);;
+ if(b)
+ return b;
+
+ b = emallocz(sizeof *b);
+ b->id = id++;
+ utflcpy(b->name, name, sizeof b->name);
+ b->col = def.normcolor;
+
+ strlcat(b->buf, b->col.colstr, sizeof(b->buf));
+ strlcat(b->buf, " ", sizeof(b->buf));
+ strlcat(b->buf, b->text, sizeof(b->buf));
+
+ SET(i);
+ for(sp=screens; (s = *sp); sp++) {
+ i = bp - s->bar;
+ if(i < nelem(s->bar))
+ break;
+ }
+ b->bar = i;
+ b->screen = s;
+
+ for(; *bp; bp = &bp[0]->next)
+ if(strcmp(bp[0]->name, name) >= 0)
+ break;
+ b->next = *bp;
+ *bp = b;
+
+ return b;
+}
+
+void
+bar_destroy(Bar **bp, Bar *b) {
+ Bar **p;
+
+ for(p = bp; *p; p = &p[0]->next)
+ if(*p == b) break;
+ *p = b->next;
+ free(b);
+}
+
+void
+bar_draw(WMScreen *s) {
+ Bar *b, *tb, *largest, **pb;
+ Rectangle r;
+ Align align;
+ uint width, tw;
+ float shrink;
+
+ /* To do: Generalize this. */
+
+ largest = nil;
+ width = 0;
+ foreach_bar(s, b) {
+ b->r.min = ZP;
+ b->r.max.y = Dy(s->brect);
+ b->r.max.x = (def.font->height & ~1) + def.font->pad.min.x + def.font->pad.max.x;
+ if(b->text && strlen(b->text))
+ b->r.max.x += textwidth(def.font, b->text);
+ width += Dx(b->r);
+ }
+
+ if(width > Dx(s->brect)) { /* Not enough room. Shrink bars until they all fit. */
+ foreach_bar(s, b) {
+ for(pb=&largest; *pb; pb=&pb[0]->smaller)
+ if(Dx(pb[0]->r) < Dx(b->r))
+ break;
+ b->smaller = *pb;
+ *pb = b;
+ }
+ SET(shrink);
+ tw = 0;
+ for(tb=largest; tb; tb=tb->smaller) {
+ width -= Dx(tb->r);
+ tw += Dx(tb->r);
+ shrink = (Dx(s->brect) - width) / (float)tw;
+ if(tb->smaller && Dx(tb->r) * shrink < Dx(tb->smaller->r))
+ continue;
+ if(width + (int)(tw * shrink) <= Dx(s->brect))
+ break;
+ }
+ if(tb)
+ for(b=largest; b != tb->smaller; b=b->smaller)
+ b->r.max.x *= shrink;
+ width += tw * shrink;
+ }
+
+ tb = nil;
+ foreach_bar(s, b) {
+ if(tb)
+ b->r = rectaddpt(b->r, Pt(tb->r.max.x, 0));
+ if(b == s->bar[BRight])
+ b->r.max.x += Dx(s->brect) - width;
+ tb = b;
+ }
+
+ r = rectsubpt(s->brect, s->brect.min);
+ fill(disp.ibuf, r, def.normcolor.bg);
+ border(disp.ibuf, r, 1, def.normcolor.border);
+ foreach_bar(s, b) {
+ align = Center;
+ if(b == s->bar[BRight])
+ align = East;
+ fill(disp.ibuf, b->r, b->col.bg);
+ drawstring(disp.ibuf, def.font, b->r, align, b->text, b->col.fg);
+ border(disp.ibuf, b->r, 1, b->col.border);
+ }
+ copyimage(s->barwin, r, disp.ibuf, ZP);
+}
+
+void
+bar_load(Bar *b) {
+ IxpMsg m;
+ char *p, *q;
+
+ p = b->buf;
+ m = ixp_message(p, strlen(p), 0);
+ msg_parsecolors(&m, &b->col);
+
+ q = (char*)m.end-1;
+ while(q >= (char*)m.pos && *q == '\n')
+ *q-- = '\0';
+
+ q = b->text;
+ utflcpy(q, (char*)m.pos, sizeof b->text);
+
+ p[0] = '\0';
+ strlcat(p, b->col.colstr, sizeof b->buf);
+ strlcat(p, " ", sizeof b->buf);
+ strlcat(p, b->text, sizeof b->buf);
+
+ bar_draw(b->screen);
+}
+
+Bar*
+bar_find(Bar *bp, const char *name) {
+ Bar *b;
+
+ for(b=bp; b; b=b->next)
+ if(!strcmp(b->name, name))
+ break;
+ return b;
+}
+
+static char *barside[] = {
+ [BLeft] = "Left",
+ [BRight] = "Right",
+};
+
+static Bar*
+findbar(WMScreen *s, Point p) {
+ Bar *b;
+
+ foreach_bar(s, b)
+ if(rect_haspoint_p(p, b->r))
+ return b;
+ return nil;
+}
+
+static void
+bdown_event(Window *w, XButtonPressedEvent *e) {
+ WMScreen *s;
+ Bar *b;
+
+ /* Ungrab so a menu can receive events before the button is released */
+ XUngrabPointer(display, e->time);
+ sync();
+
+ s = w->aux;
+ b = findbar(s, Pt(e->x, e->y));
+ if(b)
+ event("%sBarMouseDown %d %s\n", barside[b->bar], e->button, b->name);
+}
+
+static void
+bup_event(Window *w, XButtonPressedEvent *e) {
+ WMScreen *s;
+ Bar *b;
+
+ s = w->aux;
+ b = findbar(s, Pt(e->x, e->y));
+ if(b)
+ event("%sBarClick %d %s\n", barside[b->bar], e->button, b->name);
+}
+
+static Rectangle
+dndmotion_event(Window *w, Point p) {
+ WMScreen *s;
+ Bar *b;
+
+ s = w->aux;
+ b = findbar(s, p);
+ if(b) {
+ event("%sBarDND 1 %s\n", barside[b->bar], b->name);
+ return b->r;
+ }
+ return ZR;
+}
+
+static void
+expose_event(Window *w, XExposeEvent *e) {
+ USED(w, e);
+ bar_draw(w->aux);
+}
+
+static Handlers handlers = {
+ .bdown = bdown_event,
+ .bup = bup_event,
+ .dndmotion = dndmotion_event,
+ .expose = expose_event,
+};
+
diff --git a/cmd/wmii/client.c b/cmd/wmii/client.c
new file mode 100644
index 0000000..5f82455
--- /dev/null
+++ b/cmd/wmii/client.c
@@ -0,0 +1,1212 @@
+/* Copyright ©2004-2006 Anselm R. Garbe <garbeam at gmail dot com>
+ * Copyright ©2006-2010 Kris Maglione <maglione.k at Gmail>
+ * See LICENSE file for license details.
+ */
+#include "dat.h"
+#include <ctype.h>
+#include <strings.h>
+#include <X11/Xatom.h>
+#include "fns.h"
+
+#define Mbsearch(k, l, cmp) bsearch(k, l, nelem(l), sizeof(*l), cmp)
+
+static Handlers handlers;
+
+enum {
+ ClientMask = StructureNotifyMask
+ | PropertyChangeMask
+ | EnterWindowMask
+ | FocusChangeMask,
+ ButtonMask = ButtonPressMask
+ | ButtonReleaseMask,
+};
+
+static Group* group;
+
+static void
+group_init(Client *c) {
+ Group *g;
+ long *ret;
+ XWindow w;
+ long n;
+
+ w = c->w.hints->group;
+ if(w == 0) {
+ /* Not quite ICCCM compliant, but it seems to work. */
+ n = getprop_long(&c->w, "WM_CLIENT_LEADER", "WINDOW", 0L, &ret, 1L);
+ if(n == 0)
+ return;
+ w = *ret;
+ free(ret);
+ }
+
+ for(g=group; g; g=g->next)
+ if(g->leader == w)
+ break;
+ if(g == nil) {
+ g = emallocz(sizeof *g);
+ g->leader = w;
+ g->next = group;
+ group = g;
+ }
+ c->group = g;
+ g->ref++;
+}
+
+static void
+group_remove(Client *c) {
+ Group **gp;
+ Group *g;
+
+ g = c->group;
+ if(g == nil)
+ return;
+ if(g->client == c)
+ g->client = nil;
+ g->ref--;
+ if(g->ref == 0) {
+ for(gp=&group; *gp; gp=&gp[0]->next)
+ if(*gp == g) break;
+ assert(*gp == g);
+ gp[0] = gp[0]->next;
+ free(g);
+ }
+}
+
+Client*
+group_leader(Group *g) {
+ Client *c;
+
+ c = win2client(g->leader);
+ if(c)
+ return c;
+ if(g->client)
+ return g->client;
+ /* Could do better. */
+ for(c=client; c; c=c->next)
+ if(c->frame && c->group == g)
+ break;
+ return c;
+}
+
+Client*
+client_create(XWindow w, XWindowAttributes *wa) {
+ Client **t, *c;
+ WinAttr fwa;
+ Point p;
+ Visual *vis;
+ int depth;
+
+ c = emallocz(sizeof *c);
+ c->fullscreen = -1;
+ c->border = wa->border_width;
+
+ c->r.min = Pt(wa->x, wa->y);
+ c->r.max = addpt(c->r.min,
+ Pt(wa->width, wa->height));
+
+ c->w.type = WWindow;
+ c->w.xid = w;
+ c->w.r = c->r;
+
+ depth = scr.depth;
+ vis = scr.visual;
+ /* XXX: Multihead. */
+ c->ibuf = &ibuf;
+ if(render_argb_p(wa->visual)) {
+ depth = 32;
+ vis = render_visual;
+ c->ibuf = &ibuf32;
+ }
+
+ client_prop(c, xatom("WM_PROTOCOLS"));
+ client_prop(c, xatom("WM_TRANSIENT_FOR"));
+ client_prop(c, xatom("WM_NORMAL_HINTS"));
+ client_prop(c, xatom("WM_HINTS"));
+ client_prop(c, xatom("WM_CLASS"));
+ client_prop(c, xatom("WM_NAME"));
+ client_prop(c, xatom("_MOTIF_WM_HINTS"));
+
+ XSetWindowBorderWidth(display, w, 0);
+ XAddToSaveSet(display, w);
+
+ fwa.background_pixmap = None;
+ fwa.bit_gravity = NorthWestGravity;
+ fwa.border_pixel = 0;
+ fwa.colormap = XCreateColormap(display, scr.root.xid, vis, AllocNone);
+ fwa.event_mask = SubstructureRedirectMask
+ | SubstructureNotifyMask
+ | StructureNotifyMask
+ | ExposureMask
+ | EnterWindowMask
+ | PointerMotionMask
+ | ButtonPressMask
+ | ButtonReleaseMask;
+ fwa.override_redirect = true;
+ c->framewin = createwindow_visual(&scr.root, c->r,
+ depth, vis, InputOutput,
+ &fwa, CWBackPixmap
+ | CWBitGravity
+ /* These next two matter for ARGB windows. Donno why. */
+ | CWBorderPixel
+ | CWColormap
+ | CWEventMask
+ | CWOverrideRedirect);
+ XFreeColormap(display, fwa.colormap);
+
+ c->framewin->aux = c;
+ c->w.aux = c;
+ sethandler(c->framewin, &framehandler);
+ sethandler(&c->w, &handlers);
+
+ selectinput(&c->w, ClientMask);
+
+ p.x = def.border;
+ p.y = labelh(def.font);
+
+ group_init(c);
+
+ grab_button(c->framewin->xid, AnyButton, AnyModifier);
+
+ for(t=&client ;; t=&t[0]->next)
+ if(!*t) {
+ c->next = *t;
+ *t = c;
+ break;
+ }
+
+
+ /*
+ * It's actually possible for a window to be destroyed
+ * before we get a chance to reparent it. Check for that
+ * now, because otherwise we'll wind up mapping a
+ * perceptibly empty frame before it's destroyed.
+ */
+ traperrors(true);
+ reparentwindow(&c->w, c->framewin, p);
+ if(traperrors(false)) {
+ client_destroy(c);
+ return nil;
+ }
+
+ ewmh_initclient(c);
+
+ event("CreateClient %C\n", c);
+ client_manage(c);
+ return c;
+}
+
+static bool
+apply_rules(Client *c) {
+ Rule *r;
+
+ if(def.tagrules.string)
+ for(r=def.tagrules.rule; r; r=r->next)
+ if(regexec(r->regex, c->props, nil, 0))
+ return client_applytags(c, r->value);
+ return false;
+}
+
+void
+client_manage(Client *c) {
+ Client *leader;
+ Frame *f;
+ char *tags;
+ bool rules;
+
+ if(Dx(c->r) == Dx(screen->r))
+ if(Dy(c->r) == Dy(screen->r))
+ if(c->w.ewmh.type == 0)
+ fullscreen(c, true, -1);
+
+ tags = getprop_string(&c->w, "_WMII_TAGS");
+ rules = apply_rules(c);
+
+ leader = win2client(c->trans);
+ if(leader == nil && c->group)
+ leader = group_leader(c->group);
+
+ if(tags) // && (!leader || leader == c || starting))
+ utflcpy(c->tags, tags, sizeof c->tags);
+ else if(leader && !rules)
+ utflcpy(c->tags, leader->tags, sizeof c->tags);
+ free(tags);
+
+ if(c->tags[0])
+ client_applytags(c, c->tags);
+ else
+ client_applytags(c, "sel");
+
+ if(!starting)
+ view_update_all();
+
+ bool newgroup = !c->group
+ || c->group->ref == 1
+ || selclient() && (selclient()->group == c->group)
+ || group_leader(c->group)
+ && !client_viewframe(group_leader(c->group),
+ c->sel->view);
+
+ f = c->sel;
+ if(!(c->w.ewmh.type & TypeSplash))
+ if(newgroup) {
+ /* XXX: Look over this.
+ if(f->area != f->view->sel)
+ f->view->oldsel = f->view->sel;
+ */
+ }else {
+ frame_restack(c->sel, c->sel->area->sel);
+ view_restack(c->sel->view);
+ }
+}
+
+void
+client_destroy(Client *c) {
+ Rectangle r;
+ char *none;
+ Client **tc;
+ bool hide;
+
+ unmapwin(c->framewin);
+ client_seturgent(c, false, UrgClient);
+
+ for(tc=&client; *tc; tc=&tc[0]->next)
+ if(*tc == c) {
+ *tc = c->next;
+ break;
+ }
+
+ r = client_grav(c, ZR);
+
+ hide = false;
+ if(!c->sel || c->sel->view != selview)
+ hide = true;
+
+ XGrabServer(display);
+
+ /* In case the client is already destroyed. */
+ traperrors(true);
+
+ sethandler(&c->w, nil);
+ if(hide)
+ reparentwindow(&c->w, &scr.root, screen->r.max);
+ else
+ reparentwindow(&c->w, &scr.root, r.min);
+
+ if(starting > -1)
+ XRemoveFromSaveSet(display, c->w.xid);
+
+ traperrors(false);
+ XUngrabServer(display);
+
+ none = nil;
+ client_setviews(c, &none);
+ if(starting > -1)
+ client_unmap(c, WithdrawnState);
+ refree(&c->tagre);
+ refree(&c->tagvre);
+ free(c->retags);
+
+ destroywindow(c->framewin);
+
+ ewmh_destroyclient(c);
+ group_remove(c);
+ if(starting > -1)
+ event("DestroyClient %C\n", c);
+
+ flushevents(FocusChangeMask, true);
+ free(c->w.hints);
+ free(c);
+}
+
+/* Convenience functions */
+Frame*
+client_viewframe(Client *c, View *v) {
+ Frame *f;
+
+ for(f=c->frame; f; f=f->cnext)
+ if(f->view == v)
+ break;
+ return f;
+}
+
+Client*
+selclient(void) {
+ if(selview->sel->sel)
+ return selview->sel->sel->client;
+ return nil;
+}
+
+Client*
+win2client(XWindow w) {
+ Client *c;
+ for(c=client; c; c=c->next)
+ if(c->w.xid == w) break;
+ return c;
+}
+
+int
+Cfmt(Fmt *f) {
+ Client *c;
+
+ c = va_arg(f->args, Client*);
+ if(c)
+ return fmtprint(f, "%W", &c->w);
+ return fmtprint(f, "<nil>");
+}
+
+char*
+clientname(Client *c) {
+ if(c)
+ return c->name;
+ return "<nil>";
+}
+
+Rectangle
+client_grav(Client *c, Rectangle rd) {
+ Rectangle r, cr;
+ Point sp;
+ WinHints *h;
+
+ h = c->w.hints;
+
+ if(eqrect(rd, ZR)) {
+ if(c->sel) {
+ r = c->sel->floatr;
+ cr = frame_rect2client(c, r, true);
+ }else {
+ cr = c->r;
+ r = frame_client2rect(c, cr, true);
+ r = rectsetorigin(r, cr.min);
+ }
+ sp = subpt(cr.min, r.min);
+ r = gravitate(r, cr, h->grav);
+ if(!h->gravstatic)
+ r = rectsubpt(r, sp);
+ return frame_rect2client(c, r, true);
+ }else {
+ r = frame_client2rect(c, rd, true);
+ sp = subpt(rd.min, r.min);
+ r = gravitate(rd, r, h->grav);
+ if(!h->gravstatic)
+ r = rectaddpt(r, sp);
+ return frame_client2rect(c, r, true);
+ }
+}
+
+bool
+client_floats_p(Client *c) {
+ return c->trans
+ || c->floating
+ || c->fixedsize
+ || c->titleless
+ || c->borderless
+ || c->fullscreen >= 0
+ || (c->w.ewmh.type & (TypeDialog|TypeSplash|TypeDock));
+}
+
+Frame*
+client_groupframe(Client *c, View *v) {
+ if(c->group && c->group->client)
+ return client_viewframe(c->group->client, v);
+ return nil;
+}
+
+Rectangle
+frame_hints(Frame *f, Rectangle r, Align sticky) {
+ Rectangle or;
+ WinHints h;
+ Point p;
+ Client *c;
+
+ c = f->client;
+ if(c->w.hints == nil)
+ return r;
+
+ or = r;
+ h = frame_gethints(f);
+ r = sizehint(&h, r);
+
+ if(!f->area->floating) {
+ /* Not allowed to grow */
+ if(Dx(r) > Dx(or))
+ r.max.x = r.min.x+Dx(or);
+ if(Dy(r) > Dy(or))
+ r.max.y = r.min.y+Dy(or);
+ }
+
+ p = ZP;
+ if((sticky&(East|West)) == East)
+ p.x = Dx(or) - Dx(r);
+ if((sticky&(North|South)) == South)
+ p.y = Dy(or) - Dy(r);
+ return rectaddpt(r, p);
+}
+
+static void
+client_setstate(Client * c, int state) {
+ long data[] = { state, None };
+
+ changeprop_long(&c->w, "WM_STATE", "WM_STATE", data, nelem(data));
+}
+
+void
+client_map(Client *c) {
+ if(!c->w.mapped) {
+ mapwin(&c->w);
+ client_setstate(c, NormalState);
+ }
+}
+
+void
+client_unmap(Client *c, int state) {
+ if(c->w.mapped)
+ unmapwin(&c->w);
+ client_setstate(c, state);
+}
+
+int
+map_frame(Client *c) {
+ return mapwin(c->framewin);
+}
+
+int
+unmap_frame(Client *c) {
+ return unmapwin(c->framewin);
+}
+
+void
+focus(Client *c, bool user) {
+ View *v;
+ Frame *f;
+
+ USED(user);
+ f = c->sel;
+ if(!f)
+ return;
+ /*
+ if(!user && c->noinput)
+ return;
+ */
+
+ v = f->view;
+ if(v != selview)
+ view_focus(screen, v);
+ frame_focus(c->sel);
+}
+
+void
+client_focus(Client *c) {
+ /* Round trip. */
+
+ if(c && c->group)
+ c->group->client = c;
+
+ sync();
+ flushevents(FocusChangeMask, true);
+
+ Dprint(DFocus, "client_focus([%C]%s)\n", c, clientname(c));
+ Dprint(DFocus, "\t[%C]%s\n\t=> [%C]%s\n",
+ disp.focus, clientname(disp.focus),
+ c, clientname(c));
+ if(disp.focus != c) {
+ if(c) {
+ if(!c->noinput)
+ setfocus(&c->w, RevertToParent);
+ else if(c->proto & ProtoTakeFocus) {
+ xtime_kludge();
+ client_message(c, "WM_TAKE_FOCUS", 0);
+ }
+ }else
+ setfocus(screen->barwin, RevertToParent);
+ event("ClientFocus %C\n", c);
+
+ sync();
+ flushevents(FocusChangeMask, true);
+ }
+}
+
+void
+client_resize(Client *c, Rectangle r) {
+ Frame *f;
+
+ f = c->sel;
+ frame_resize(f, r);
+
+ if(f->view != selview) {
+ client_unmap(c, IconicState);
+ unmap_frame(c);
+ return;
+ }
+
+ c->r = rectaddpt(f->crect, f->r.min);
+
+ if(f->collapsed) {
+ if(f->area->max && !resizing)
+ unmap_frame(c);
+ else {
+ reshapewin(c->framewin, f->r);
+ movewin(&c->w, f->crect.min);
+ map_frame(c);
+ }
+ client_unmap(c, IconicState);
+ }else {
+ client_map(c);
+ reshapewin(c->framewin, f->r);
+ reshapewin(&c->w, f->crect);
+ map_frame(c);
+ client_configure(c);
+ ewmh_framesize(c);
+ }
+}
+
+void
+client_setcursor(Client *c, Cursor cur) {
+ WinAttr wa;
+
+ if(c->cursor != cur) {
+ c->cursor = cur;
+ wa.cursor = cur;
+ setwinattr(c->framewin, &wa, CWCursor);
+ }
+}
+
+void
+client_configure(Client *c) {
+ XConfigureEvent e;
+ Rectangle r;
+
+ r = rectsubpt(c->r, Pt(c->border, c->border));
+
+ e.type = ConfigureNotify;
+ e.event = c->w.xid;
+ e.window = c->w.xid;
+ e.above = None;
+ e.override_redirect = false;
+
+ e.x = r.min.x;
+ e.y = r.min.y;
+ e.width = Dx(r);
+ e.height = Dy(r);
+ e.border_width = c->border;
+
+ sendevent(&c->w, false, StructureNotifyMask, (XEvent*)&e);
+}
+
+void
+client_message(Client *c, char *msg, long l2) {
+ sendmessage(&c->w, "WM_PROTOCOLS", xatom(msg), xtime, l2, 0, 0);
+}
+
+void
+client_kill(Client *c, bool nice) {
+ if(nice && (c->proto & ProtoDelete)) {
+ client_message(c, "WM_DELETE_WINDOW", 0);
+ ewmh_pingclient(c);
+ }else
+ XKillClient(display, c->w.xid);
+}
+
+void
+fullscreen(Client *c, int fullscreen, long screen) {
+ Client *leader;
+ Frame *f;
+ bool wassel;
+
+ if(fullscreen == Toggle)
+ fullscreen = (c->fullscreen >= 0) ^ On;
+ if(fullscreen == (c->fullscreen >= 0))
+ return;
+
+ event("Fullscreen %C %s\n", c, (fullscreen ? "on" : "off"));
+ ewmh_updatestate(c);
+
+ c->fullscreen = -1;
+ if(!fullscreen)
+ for(f=c->frame; f; f=f->cnext) {
+ if(f->oldarea == 0) {
+ frame_resize(f, f->floatr);
+ if(f->view == selview) /* FIXME */
+ client_resize(f->client, f->r);
+
+ }
+ else if(f->oldarea > 0) {
+ wassel = (f == f->area->sel);
+ area_moveto(view_findarea(f->view, f->oldscreen, f->oldarea, true),
+ f);
+ if(wassel)
+ frame_focus(f);
+ }
+ }
+ else {
+ c->fullscreen = 0;
+ if(screen >= 0)
+ c->fullscreen = screen;
+ else if(c->sel)
+ c->fullscreen = ownerscreen(c->r);
+ else if(c->group && (leader = group_leader(c->group)) && leader->sel)
+ c->fullscreen = ownerscreen(leader->r);
+ else if(selclient())
+ c->fullscreen = ownerscreen(selclient()->r);
+
+ for(f=c->frame; f; f=f->cnext)
+ f->oldarea = -1;
+ if((f = c->sel))
+ view_update(f->view);
+ }
+}
+
+void
+client_seturgent(Client *c, int urgent, int from) {
+ XWMHints *wmh;
+ char *cfrom, *cnot;
+ Frame *f, *ff;
+ Area *a;
+ int s;
+
+ if(urgent == Toggle)
+ urgent = c->urgent ^ On;
+
+ cfrom = (from == UrgManager ? "Manager" : "Client");
+ cnot = (urgent ? "" : "Not");
+
+ if(urgent != c->urgent) {
+ event("%sUrgent %C %s\n", cnot, c, cfrom);
+ c->urgent = urgent;
+ ewmh_updatestate(c);
+ if(c->sel) {
+ if(c->sel->view == selview)
+ frame_draw(c->sel);
+ for(f=c->frame; f; f=f->cnext) {
+ SET(ff);
+ if(!urgent)
+ foreach_frame(f->view, s, a, ff)
+ if(ff->client->urgent) break;
+ if(urgent || ff == nil)
+ event("%sUrgentTag %s %s\n",
+ cnot, cfrom, f->view->name);
+ }
+ }
+ }
+
+ if(from == UrgManager) {
+ wmh = XGetWMHints(display, c->w.xid);
+ if(wmh == nil)
+ wmh = emallocz(sizeof *wmh);
+
+ wmh->flags &= ~XUrgencyHint;
+ if(urgent)
+ wmh->flags |= XUrgencyHint;
+ XSetWMHints(display, c->w.xid, wmh);
+ XFree(wmh);
+ }
+}
+
+/* X11 stuff */
+void
+update_class(Client *c) {
+ char *str;
+
+ str = utfrune(c->props, L':');
+ if(str)
+ str = utfrune(str+1, L':');
+ if(str == nil) {
+ strcpy(c->props, "::");
+ str = c->props + 1;
+ }
+ utflcpy(str+1, c->name, sizeof c->props);
+}
+
+static void
+client_updatename(Client *c) {
+ char *str;
+
+ c->name[0] = '\0';
+
+ str = getprop_string(&c->w, "_NET_WM_NAME");
+ if(str == nil)
+ str = getprop_string(&c->w, "WM_NAME");
+ if(str)
+ utflcpy(c->name, str, sizeof c->name);
+ free(str);
+
+ update_class(c);
+ if(c->sel)
+ frame_draw(c->sel);
+}
+
+static void
+updatemwm(Client *c) {
+ enum {
+ All = 0x1,
+ Border = 0x2,
+ Title = 0x8,
+ FlagDecor = 0x2,
+ Flags = 0,
+ Decor = 2,
+ };
+ Rectangle r;
+ ulong *ret;
+ int n;
+
+ /* To quote Metacity, or KWin quoting Metacity:
+ *
+ * We support MWM hints deemed non-stupid
+ *
+ * Our definition of non-stupid is a bit less lenient than
+ * theirs, though. In fact, we don't really even support the
+ * idea of supporting the hints that we support, but apps
+ * like xmms (which no one should use) break if we don't.
+ */
+
+ n = getprop_ulong(&c->w, "_MOTIF_WM_HINTS", "_MOTIF_WM_HINTS",
+ 0L, &ret, 3L);
+
+ /* FIXME: Should somehow handle all frames of a client. */
+ if(c->sel)
+ r = client_grav(c, ZR);
+
+ c->borderless = 0;
+ c->titleless = 0;
+ if(n >= 3 && (ret[Flags] & FlagDecor)) {
+ if(ret[Decor] & All)
+ ret[Decor] ^= ~0;
+ c->borderless = !(ret[Decor] & Border);
+ c->titleless = !(ret[Decor] & Title);
+ }
+ free(ret);
+
+ if(c->sel && false) {
+ c->sel->floatr = client_grav(c, r);
+ if(c->sel->area->floating) {
+ client_resize(c, c->sel->floatr);
+ frame_draw(c->sel);
+ }
+ }
+}
+
+void
+client_prop(Client *c, Atom a) {
+ WinHints h;
+ XWMHints *wmh;
+ char **class;
+ int n;
+
+ if(a == xatom("WM_PROTOCOLS"))
+ c->proto = ewmh_protocols(&c->w);
+ else
+ if(a == xatom("_NET_WM_NAME"))
+ goto wmname;
+ else
+ if(a == xatom("_MOTIF_WM_HINTS"))
+ updatemwm(c);
+ else
+ switch (a) {
+ default:
+ ewmh_prop(c, a);
+ break;
+ case XA_WM_TRANSIENT_FOR:
+ XGetTransientForHint(display, c->w.xid, &c->trans);
+ break;
+ case XA_WM_NORMAL_HINTS:
+ memset(&h, 0, sizeof h);
+ if(c->w.hints)
+ bcopy(c->w.hints, &h, sizeof h);
+ sethints(&c->w);
+ if(c->w.hints)
+ c->fixedsize = eqpt(c->w.hints->min, c->w.hints->max);
+ if(memcmp(&h, c->w.hints, sizeof h))
+ if(c->sel)
+ view_update(c->sel->view);
+ break;
+ case XA_WM_HINTS:
+ wmh = XGetWMHints(display, c->w.xid);
+ if(wmh) {
+ c->noinput = (wmh->flags&InputFocus) && !wmh->input;
+ client_seturgent(c, (wmh->flags & XUrgencyHint) != 0, UrgClient);
+ XFree(wmh);
+ }
+ break;
+ case XA_WM_CLASS:
+ n = getprop_textlist(&c->w, "WM_CLASS", &class);
+ snprint(c->props, sizeof c->props, "%s:%s:",
+ (n > 0 ? class[0] : "<nil>"),
+ (n > 1 ? class[1] : "<nil>"));
+ freestringlist(class);
+ update_class(c);
+ break;
+ case XA_WM_NAME:
+ wmname:
+ client_updatename(c);
+ break;
+ }
+}
+
+/* Handlers */
+static void
+configreq_event(Window *w, XConfigureRequestEvent *e) {
+ Rectangle r, cr;
+ Client *c;
+
+ c = w->aux;
+
+ r = client_grav(c, ZR);
+ r.max = subpt(r.max, r.min);
+
+ if(e->value_mask & CWX)
+ r.min.x = e->x;
+ if(e->value_mask & CWY)
+ r.min.y = e->y;
+ if(e->value_mask & CWWidth)
+ r.max.x = e->width;
+ if(e->value_mask & CWHeight)
+ r.max.y = e->height;
+
+ if(e->value_mask & CWBorderWidth)
+ c->border = e->border_width;
+
+ r.max = addpt(r.min, r.max);
+ cr = r;
+ r = client_grav(c, r);
+
+ if(c->sel->area->floating) {
+ client_resize(c, r);
+ }else {
+ c->sel->floatr = r;
+ client_configure(c);
+ }
+}
+
+static void
+destroy_event(Window *w, XDestroyWindowEvent *e) {
+ USED(w, e);
+
+ client_destroy(w->aux);
+}
+
+static void
+enter_event(Window *w, XCrossingEvent *e) {
+ Client *c;
+
+ c = w->aux;
+ if(e->detail != NotifyInferior) {
+ if(e->detail != NotifyVirtual)
+ if(e->serial != ignoreenter && disp.focus != c) {
+ Dprint(DFocus, "enter_notify([%C]%s)\n", c, c->name);
+ focus(c, false);
+ }
+ client_setcursor(c, cursor[CurNormal]);
+ }else
+ Dprint(DFocus, "enter_notify(%C[NotifyInferior]%s)\n", c, c->name);
+}
+
+static void
+focusin_event(Window *w, XFocusChangeEvent *e) {
+ Client *c, *old;
+
+ c = w->aux;
+
+ print_focus("focusin_event", c, c->name);
+
+ if(e->mode == NotifyGrab)
+ disp.hasgrab = c;
+
+ old = disp.focus;
+ disp.focus = c;
+ if(c != old) {
+ if(c->sel)
+ frame_draw(c->sel);
+ }
+}
+
+static void
+focusout_event(Window *w, XFocusChangeEvent *e) {
+ Client *c;
+
+ c = w->aux;
+ if((e->mode == NotifyWhileGrabbed) && (disp.hasgrab != &c_root)) {
+ if(disp.focus)
+ disp.hasgrab = disp.focus;
+ }else if(disp.focus == c) {
+ print_focus("focusout_event", &c_magic, "<magic>");
+ disp.focus = &c_magic;
+ if(c->sel)
+ frame_draw(c->sel);
+ }
+}
+
+static void
+unmap_event(Window *w, XUnmapEvent *e) {
+ Client *c;
+
+ c = w->aux;
+ if(!e->send_event)
+ c->unmapped--;
+ client_destroy(c);
+}
+
+static void
+map_event(Window *w, XMapEvent *e) {
+ Client *c;
+
+ USED(e);
+
+ c = w->aux;
+ if(c == selclient())
+ client_focus(c);
+}
+
+static void
+property_event(Window *w, XPropertyEvent *e) {
+ Client *c;
+
+ if(e->state == PropertyDelete) /* FIXME */
+ return;
+
+ c = w->aux;
+ client_prop(c, e->atom);
+}
+
+static Handlers handlers = {
+ .configreq = configreq_event,
+ .destroy = destroy_event,
+ .enter = enter_event,
+ .focusin = focusin_event,
+ .focusout = focusout_event,
+ .map = map_event,
+ .unmap = unmap_event,
+ .property = property_event,
+};
+
+/* Other */
+void
+client_setviews(Client *c, char **tags) {
+ Frame **fp, *f;
+ int cmp;
+
+ fp = &c->frame;
+ while(*fp || *tags) {
+ SET(cmp);
+ while(*fp) {
+ if(*tags) {
+ cmp = strcmp(fp[0]->view->name, *tags);
+ if(cmp >= 0)
+ break;
+ }
+
+ f = *fp;
+ view_detach(f);
+ *fp = f->cnext;
+ if(c->sel == f)
+ c->sel = *fp;
+ free(f);
+ }
+ if(*tags) {
+ if(!*fp || cmp > 0) {
+ f = frame_create(c, view_create(*tags));
+ if(f->view == selview || !c->sel)
+ c->sel = f;
+ kludge = c; /* FIXME */
+ view_attach(f->view, f);
+ kludge = nil;
+ f->cnext = *fp;
+ *fp = f;
+ }
+ if(fp[0]) fp=&fp[0]->cnext;
+ tags++;
+ }
+ }
+ if(c->sel == nil)
+ c->sel = c->frame;
+ if(c->sel)
+ frame_draw(c->sel);
+}
+
+static int
+bsstrcmp(const void *a, const void *b) {
+ return strcmp((char*)a, *(char**)b);
+}
+
+static int
+strpcmp(const void *ap, const void *bp) {
+ char **a, **b;
+
+ a = (char**)ap;
+ b = (char**)bp;
+ return strcmp(*a, *b);
+}
+
+static char *badtags[] = {
+ ".",
+ "..",
+ "sel",
+};
+
+char*
+client_extratags(Client *c) {
+ Frame *f;
+ char *toks[32];
+ char **tags;
+ char *s, *s2;
+ int i;
+
+ i = 0;
+ toks[i++] = "";
+ for(f=c->frame; f && i < nelem(toks)-1; f=f->cnext)
+ if(f != c->sel)
+ toks[i++] = f->view->name;
+ toks[i] = nil;
+ tags = comm(CLeft, toks, c->retags);
+
+ s = nil;
+ if(i > 1)
+ s = join(tags, "+");
+ free(tags);
+ if(!c->tagre.regex && !c->tagvre.regex)
+ return s;
+
+ if(c->tagre.regex) {
+ s2 = s;
+ s = smprint("%s+/%s/", s ? s : "", c->tagre.regex);
+ free(s2);
+ }
+ if(c->tagvre.regex) {
+ s2 = s;
+ s = smprint("%s-/%s/", s ? s : "", c->tagvre.regex);
+ free(s2);
+ }
+ return s;
+}
+
+bool
+client_applytags(Client *c, const char *tags) {
+ uint i, j, k, n;
+ bool add, found;
+ char buf[512], last;
+ char *toks[32];
+ char **p;
+ char *cur, *s;
+
+ buf[0] = 0;
+
+ for(n = 0; tags[n]; n++)
+ if(!isspace(tags[n]))
+ break;
+
+ if(tags[n] == '+' || tags[n] == '-')
+ utflcpy(buf, c->tags, sizeof c->tags);
+ else {
+ refree(&c->tagre);
+ refree(&c->tagvre);
+ }
+ strlcat(buf, &tags[n], sizeof buf);
+
+ n = 0;
+ add = true;
+ if(buf[0] == '+')
+ n++;
+ else if(buf[0] == '-') {
+ n++;
+ add = false;
+ }
+
+ found = false;
+
+ j = 0;
+ while(buf[n] && n < sizeof(buf) && j < 32) {
+ /* Check for regex. */
+ if(buf[n] == '/') {
+ for(i=n+1; i < sizeof(buf) - 1; i++)
+ if(buf[i] == '/') break;
+ if(buf[i] == '/') {
+ i++;
+ if(buf[i] == '+'
+ || buf[i] == '-'
+ || buf[i] == '\0') { /* Don't be lenient */
+ buf[i-1] = '\0';
+ if(add)
+ reinit(&c->tagre, buf+n+1);
+ else
+ reinit(&c->tagvre, buf+n+1);
+ last = buf[i];
+ buf[i] = '\0';
+
+ found = true;
+ goto next;
+ }
+ }
+ }
+
+ for(i = n; i < sizeof(buf) - 1; i++)
+ if(buf[i] == '+'
+ || buf[i] == '-'
+ || buf[i] == '\0')
+ break;
+ last = buf[i];
+ buf[i] = '\0';
+
+ trim(buf+n, " \t/");
+
+ cur = nil;
+ if(!strcmp(buf+n, "~"))
+ c->floating = add;
+ else
+ if(!strcmp(buf+n, "!") || !strcmp(buf+n, "sel"))
+ cur = selview->name;
+ else
+ if(!Mbsearch(buf+n, badtags, bsstrcmp))
+ cur = buf+n;
+
+ if(cur && j < nelem(toks)-1) {
+ if(add) {
+ found = true;
+ toks[j++] = cur;
+ }else {
+ for(i = 0, k = 0; i < j; i++)
+ if(strcmp(toks[i], cur))
+ toks[k++] = toks[i];
+ j = k;
+ }
+ }
+
+ next:
+ n = i + 1;
+ if(last == '+')
+ add = true;
+ if(last == '-')
+ add = false;
+ if(last == '\0')
+ break;
+ }
+
+ toks[j] = nil;
+ qsort(toks, j, sizeof *toks, strpcmp);
+ uniq(toks);
+
+ s = join(toks, "+");
+ utflcpy(c->tags, s, sizeof c->tags);
+ if(c->tagre.regex)
+ strlcatprint(c->tags, sizeof c->tags, "+/%s/", c->tagre.regex);
+ if(c->tagvre.regex)
+ strlcatprint(c->tags, sizeof c->tags, "-/%s/", c->tagvre.regex);
+ changeprop_string(&c->w, "_WMII_TAGS", c->tags);
+ free(s);
+
+ free(c->retags);
+ p = view_names();
+ grep(p, c->tagre.regc, 0);
+ grep(p, c->tagvre.regc, GInvert);
+ c->retags = comm(CRight, toks, p);
+ free(p);
+
+ if(c->retags[0] == nil && toks[0] == nil) {
+ toks[0] = "orphans";
+ toks[1] = nil;
+ }
+
+ p = comm(~0, c->retags, toks);
+ client_setviews(c, p);
+ free(p);
+ return found;
+}
+
diff --git a/cmd/wmii/column.c b/cmd/wmii/column.c
new file mode 100644
index 0000000..e94f6a9
--- /dev/null
+++ b/cmd/wmii/column.c
@@ -0,0 +1,738 @@
+/* Copyright ©2004-2006 Anselm R. Garbe <garbeam at gmail dot com>
+ * Copyright ©2006-2010 Kris Maglione <maglione.k at Gmail>
+ * See LICENSE file for license details.
+ */
+#include "dat.h"
+#include <math.h>
+#include <strings.h>
+#include "fns.h"
+
+static void column_resizeframe_h(Frame*, Rectangle);
+
+char *modes[] = {
+ [Coldefault] = "default",
+ [Colstack] = "stack",
+ [Colmax] = "max",
+};
+
+bool
+column_setmode(Area *a, const char *mode) {
+ char *str, *tok, *orig;
+ char add, old;
+
+ /*
+ * The mapping between the current internal
+ * representation and the external interface
+ * is currently a bit complex. That will probably
+ * change.
+ */
+
+ orig = strdup(mode);
+ str = orig;
+ old = '\0';
+ while(*(tok = str)) {
+ add = old;
+ while((old=*str) && !strchr("+-^", old))
+ str++;
+ *str = '\0';
+ if(str > tok) {
+ print("'%s' %c\n", tok, add);
+ if(!strcmp(tok, "max")) {
+ if(add == '\0' || add == '+')
+ a->max = true;
+ else if(add == '-')
+ a->max = false;
+ else
+ a->max = !a->max;
+ }else
+ if(!strcmp(tok, "stack")) {
+ if(add == '\0' || add == '+')
+ a->mode = Colstack;
+ else if(add == '-')
+ a->mode = Coldefault;
+ else
+ a->mode = a->mode == Colstack ? Coldefault : Colstack;
+ }else
+ if(!strcmp(tok, "default")) {
+ if(add == '\0' || add == '+') {
+ a->mode = Coldefault;
+ column_arrange(a, true);
+ }else if(add == '-')
+ a->mode = Colstack;
+ else
+ a->mode = a->mode == Coldefault ? Colstack : Coldefault;
+ }else {
+ free(orig);
+ return false;
+ }
+ }
+ if(old)
+ str++;
+
+ }
+ free(orig);
+ return true;
+}
+
+char*
+column_getmode(Area *a) {
+ return sxprint("%s%cmax", a->mode == Colstack ? "stack" : "default",
+ a->max ? '+' : '-');
+}
+
+int
+column_minwidth(void)
+{
+ return 4 * labelh(def.font);
+}
+
+Area*
+column_new(View *v, Area *pos, int scrn, uint w) {
+ Area *a;
+
+ assert(!pos || !pos->floating && pos->screen == scrn);
+ a = area_create(v, pos, scrn, w);
+ return a;
+#if 0
+ if(!a)
+ return nil;
+
+ view_arrange(v);
+ view_update(v);
+#endif
+}
+
+void
+column_insert(Area *a, Frame *f, Frame *pos) {
+
+ f->area = a;
+ f->client->floating = false;
+ f->screen = a->screen;
+ f->column = area_idx(a);
+ frame_insert(f, pos);
+ if(a->sel == nil)
+ area_setsel(a, f);
+}
+
+/* Temporary. */
+static void
+stack_scale(Frame *first, int height) {
+ Frame *f;
+ Area *a;
+ uint dy;
+ int surplus;
+
+ a = first->area;
+
+ /*
+ * Will need something like this.
+ column_fit(a, &ncol, &nuncol);
+ */
+
+ dy = 0;
+ for(f=first; f && !f->collapsed; f=f->anext)
+ dy += Dy(f->colr);
+
+ /* Distribute the surplus.
+ */
+ surplus = height - dy;
+ for(f=first; f && !f->collapsed; f=f->anext)
+ f->colr.max.y += ((float)Dy(f->r) / dy) * surplus;
+}
+
+static void
+stack_info(Frame *f, Frame **firstp, Frame **lastp, int *dyp, int *nframep) {
+ Frame *ft, *first, *last;
+ int dy, nframe;
+
+ nframe = 0;
+ dy = 0;
+ first = f;
+ last = f;
+
+ for(ft=f; ft && ft->collapsed; ft=ft->anext)
+ ;
+ if(ft && ft != f) {
+ f = ft;
+ dy += Dy(f->colr);
+ }
+ for(ft=f; ft && !ft->collapsed; ft=ft->aprev) {
+ first = ft;
+ nframe++;
+ dy += Dy(ft->colr);
+ }
+ for(ft=f->anext; ft && !ft->collapsed; ft=ft->anext) {
+ if(first == nil)
+ first = ft;
+ last = ft;
+ nframe++;
+ dy += Dy(ft->colr);
+ }
+ if(nframep) *nframep = nframe;
+ if(firstp) *firstp = first;
+ if(lastp) *lastp = last;
+ if(dyp) *dyp = dy;
+}
+
+int
+stack_count(Frame *f, int *mp) {
+ Frame *fp;
+ int n, m;
+
+ n = 0;
+ for(fp=f->aprev; fp && fp->collapsed; fp=fp->aprev)
+ n++;
+ m = ++n;
+ for(fp=f->anext; fp && fp->collapsed; fp=fp->anext)
+ n++;
+ if(mp) *mp = m;
+ return n;
+}
+
+Frame*
+stack_find(Area *a, Frame *f, int dir, bool stack) {
+ Frame *fp;
+
+ switch (dir) {
+ default:
+ die("not reached");
+ case North:
+ if(f)
+ for(f=f->aprev; f && f->collapsed && stack; f=f->aprev)
+ ;
+ else {
+ f = nil;
+ for(fp=a->frame; fp; fp=fp->anext)
+ if(!fp->collapsed || !stack)
+ f = fp;
+ }
+ break;
+ case South:
+ if(f)
+ for(f=f->anext; f && f->collapsed && stack; f=f->anext)
+ ;
+ else
+ for(f=a->frame; f && f->collapsed && stack; f=f->anext)
+ ;
+ break;
+ }
+ return f;
+}
+
+/* TODO: Move elsewhere. */
+bool
+find(Area **ap, Frame **fp, int dir, bool wrap, bool stack) {
+ Rectangle r;
+ Frame *f;
+ Area *a;
+
+ f = *fp;
+ a = *ap;
+ r = f ? f->r : a->r;
+
+ if(dir == North || dir == South) {
+ *fp = stack_find(a, f, dir, stack);
+ if(*fp)
+ return true;
+ if (!a->floating)
+ *ap = area_find(a->view, r, dir, wrap);
+ if(!*ap)
+ return false;
+ *fp = stack_find(*ap, *fp, dir, stack);
+ return true;
+ }
+ if(dir != East && dir != West)
+ die("not reached");
+ *ap = area_find(a->view, r, dir, wrap);
+ if(!*ap)
+ return false;
+ *fp = ap[0]->sel;
+ return true;
+}
+
+void
+column_attach(Area *a, Frame *f) {
+ Frame *first;
+ int nframe, dy, h;
+
+ f->colr = a->r;
+
+ if(a->sel) {
+ stack_info(a->sel, &first, nil, &dy, &nframe);
+ h = dy / (nframe+1);
+ f->colr.max.y = f->colr.min.y + h;
+ stack_scale(first, dy - h);
+ }
+
+ column_insert(a, f, a->sel);
+ column_arrange(a, false);
+}
+
+void
+column_detach(Frame *f) {
+ Frame *first;
+ Area *a;
+ int dy;
+
+ a = f->area;
+ stack_info(f, &first, nil, &dy, nil);
+ if(first && first == f)
+ first = f->anext;
+ column_remove(f);
+ if(a->frame) {
+ if(first)
+ stack_scale(first, dy);
+ column_arrange(a, false);
+ }else if(a->view->areas[a->screen]->next)
+ area_destroy(a);
+}
+
+static void column_scale(Area*);
+
+void
+column_attachrect(Area *a, Frame *f, Rectangle r) {
+ Frame *fp, *pos;
+ int before, after;
+
+ pos = nil;
+ for(fp=a->frame; fp; pos=fp, fp=fp->anext) {
+ if(r.max.y < fp->r.min.y || r.min.y > fp->r.max.y)
+ continue;
+ before = fp->r.min.y - r.min.y;
+ after = -fp->r.max.y + r.max.y;
+ }
+ column_insert(a, f, pos);
+ column_resizeframe_h(f, r);
+ column_scale(a);
+}
+
+void
+column_remove(Frame *f) {
+ Frame *pr;
+ Area *a;
+
+ a = f->area;
+ pr = f->aprev;
+
+ frame_remove(f);
+
+ f->area = nil;
+ if(a->sel == f) {
+ if(pr == nil)
+ pr = a->frame;
+ if(pr && pr->collapsed)
+ if(pr->anext && !pr->anext->collapsed)
+ pr = pr->anext;
+ else
+ pr->collapsed = false;
+ a->sel = nil;
+ area_setsel(a, pr);
+ }
+}
+
+static int
+column_surplus(Area *a) {
+ Frame *f;
+ int surplus;
+
+ surplus = Dy(a->r);
+ for(f=a->frame; f; f=f->anext)
+ surplus -= Dy(f->r);
+ return surplus;
+}
+
+static void
+column_fit(Area *a, uint *n_colp, uint *n_uncolp) {
+ Frame *f, **fp;
+ uint minh, dy;
+ uint n_col, n_uncol;
+ uint col_h, uncol_h;
+ int surplus, i, j;
+
+ /* The minimum heights of collapsed and uncollpsed frames.
+ */
+ minh = labelh(def.font);
+ col_h = labelh(def.font);
+ uncol_h = minh + col_h + 1;
+ if(a->max && !resizing)
+ col_h = 0;
+
+ /* Count collapsed and uncollapsed frames. */
+ n_col = 0;
+ n_uncol = 0;
+ for(f=a->frame; f; f=f->anext) {
+ frame_resize(f, f->colr);
+ if(f->collapsed)
+ n_col++;
+ else
+ n_uncol++;
+ }
+
+ if(n_uncol == 0) {
+ n_uncol++;
+ n_col--;
+ (a->sel ? a->sel : a->frame)->collapsed = false;
+ }
+
+ /* FIXME: Kludge. See frame_attachrect. */
+ dy = Dy(a->view->r[a->screen]) - Dy(a->r);
+ minh = col_h * (n_col + n_uncol - 1) + uncol_h;
+ if(dy && Dy(a->r) < minh)
+ a->r.max.y += min(dy, minh - Dy(a->r));
+
+ surplus = Dy(a->r)
+ - (n_col * col_h)
+ - (n_uncol * uncol_h);
+
+ /* Collapse until there is room */
+ if(surplus < 0) {
+ i = ceil(-1.F * surplus / (uncol_h - col_h));
+ if(i >= n_uncol)
+ i = n_uncol - 1;
+ n_uncol -= i;
+ n_col += i;
+ surplus += i * (uncol_h - col_h);
+ }
+ /* Push to the floating layer until there is room */
+ if(surplus < 0) {
+ i = ceil(-1.F * surplus / col_h);
+ if(i > n_col)
+ i = n_col;
+ n_col -= i;
+ surplus += i * col_h;
+ }
+
+ /* Decide which to collapse and which to float. */
+ j = n_uncol - 1;
+ i = n_col - 1;
+ for(fp=&a->frame; *fp;) {
+ f = *fp;
+ if(f != a->sel) {
+ if(!f->collapsed) {
+ if(j < 0)
+ f->collapsed = true;
+ j--;
+ }
+ if(f->collapsed) {
+ if(i < 0) {
+ f->collapsed = false;
+ area_moveto(f->view->floating, f);
+ continue;
+ }
+ i--;
+ }
+ }
+ /* Doesn't change if we 'continue' */
+ fp = &f->anext;
+ }
+
+ if(n_colp) *n_colp = n_col;
+ if(n_uncolp) *n_uncolp = n_uncol;
+}
+
+void
+column_settle(Area *a) {
+ Frame *f;
+ uint yoff, yoffcr;
+ int surplus, n_uncol, n;
+
+ n_uncol = 0;
+ surplus = column_surplus(a);
+ for(f=a->frame; f; f=f->anext)
+ if(!f->collapsed) n_uncol++;
+
+ if(n_uncol == 0) {
+ fprint(2, "%s: Badness: No uncollapsed frames, column %d, view %q\n",
+ argv0, area_idx(a), a->view->name);
+ return;
+ }
+ if(surplus < 0)
+ fprint(2, "%s: Badness: surplus = %d in column_settle, column %d, view %q\n",
+ argv0, surplus, area_idx(a), a->view->name);
+
+ yoff = a->r.min.y;
+ yoffcr = yoff;
+ n = surplus % n_uncol;
+ surplus /= n_uncol;
+ for(f=a->frame; f; f=f->anext) {
+ f->r = rectsetorigin(f->r, Pt(a->r.min.x, yoff));
+ f->colr = rectsetorigin(f->colr, Pt(a->r.min.x, yoffcr));
+ f->r.min.x = a->r.min.x;
+ f->r.max.x = a->r.max.x;
+ if(def.incmode == ISqueeze && !resizing)
+ if(!f->collapsed) {
+ f->r.max.y += surplus;
+ if(n-- > 0)
+ f->r.max.y++;
+ }
+ yoff = f->r.max.y;
+ yoffcr = f->colr.max.y;
+ }
+}
+
+/*
+ * Returns how much a frame "wants" to grow.
+ */
+static int
+foo(Frame *f) {
+ WinHints h;
+ int maxh;
+
+ h = frame_gethints(f);
+ maxh = 0;
+ if(h.aspect.max.x)
+ maxh = h.baspect.y +
+ (Dx(f->r) - h.baspect.x) *
+ h.aspect.max.y / h.aspect.max.x;
+ maxh = max(maxh, h.max.y);
+
+ if(Dy(f->r) >= maxh)
+ return 0;
+ return h.inc.y - (Dy(f->r) - h.base.y) % h.inc.y;
+}
+
+static int
+comp_frame(const void *a, const void *b) {
+ int ia, ib;
+
+ ia = foo(*(Frame**)a);
+ ib = foo(*(Frame**)b);
+ /*
+ * I'd like to favor the selected client, but
+ * it causes windows to jump as focus changes.
+ */
+ return ia < ib ? -1 :
+ ia > ib ? 1 :
+ 0;
+}
+
+static void
+column_squeeze(Area *a) {
+ static Vector_ptr fvec;
+ Frame *f;
+ int surplus, osurplus, dy, i;
+
+ fvec.n = 0;
+ for(f=a->frame; f; f=f->anext)
+ if(!f->collapsed) {
+ f->r = frame_hints(f, f->r, 0);
+ vector_ppush(&fvec, f);
+ }
+
+ surplus = column_surplus(a);
+ for(osurplus=0; surplus != osurplus;) {
+ osurplus = surplus;
+ qsort(fvec.ary, fvec.n, sizeof *fvec.ary, comp_frame);
+ for(i=0; i < fvec.n; i++) {
+ f=fvec.ary[i];
+ dy = foo(f);
+ if(dy > surplus)
+ break;
+ surplus -= dy;
+ f->r.max.y += dy;
+ }
+ }
+}
+
+/*
+ * Frobs a column. Which is to say, *temporary* kludge.
+ * Essentially seddles the column and resizes its clients.
+ */
+void
+column_frob(Area *a) {
+ Frame *f;
+
+ for(f=a->frame; f; f=f->anext)
+ f->r = f->colr;
+ column_settle(a);
+ if(a->view == selview)
+ for(f=a->frame; f; f=f->anext)
+ client_resize(f->client, f->r);
+}
+
+static void
+column_scale(Area *a) {
+ Frame *f;
+ uint dy;
+ uint ncol, nuncol;
+ uint colh;
+ int surplus;
+
+ if(!a->frame)
+ return;
+
+ column_fit(a, &ncol, &nuncol);
+
+ colh = labelh(def.font);
+ if(a->max && !resizing)
+ colh = 0;
+
+ dy = 0;
+ surplus = Dy(a->r);
+ for(f=a->frame; f; f=f->anext) {
+ if(f->collapsed)
+ f->colr.max.y = f->colr.min.y + colh;
+ else if(Dy(f->colr) == 0)
+ f->colr.max.y++;
+ surplus -= Dy(f->colr);
+ if(!f->collapsed)
+ dy += Dy(f->colr);
+ }
+ for(f=a->frame; f; f=f->anext) {
+ f->dy = Dy(f->colr);
+ f->colr.min.x = a->r.min.x;
+ f->colr.max.x = a->r.max.x;
+ if(!f->collapsed)
+ f->colr.max.y += ((float)f->dy / dy) * surplus;
+ if(btassert("6 full", !(f->collapsed ? Dy(f->r) >= 0 : dy > 0)))
+ warning("Something's fucked: %s:%d:%s()",
+ __FILE__, __LINE__, __func__);
+ frame_resize(f, f->colr);
+ }
+
+ if(def.incmode == ISqueeze && !resizing)
+ column_squeeze(a);
+ column_settle(a);
+}
+
+void
+column_arrange(Area *a, bool dirty) {
+ Frame *f;
+ View *v;
+
+ if(a->floating)
+ float_arrange(a);
+ if(a->floating || !a->frame)
+ return;
+
+ v = a->view;
+
+ switch(a->mode) {
+ case Coldefault:
+ if(dirty)
+ for(f=a->frame; f; f=f->anext)
+ f->colr = Rect(0, 0, 100, 100);
+ break;
+ case Colstack:
+ /* XXX */
+ for(f=a->frame; f; f=f->anext)
+ f->collapsed = (f != a->sel);
+ break;
+ default:
+ fprint(2, "Dieing: %s: screen: %d a: %p mode: %x floating: %d\n",
+ v->name, a->screen, a, a->mode, a->floating);
+ die("not reached");
+ break;
+ }
+ column_scale(a);
+ /* XXX */
+ if(a->sel->collapsed)
+ area_setsel(a, a->sel);
+ if(v == selview) {
+ //view_restack(v);
+ client_resize(a->sel->client, a->sel->r);
+ for(f=a->frame; f; f=f->anext)
+ client_resize(f->client, f->r);
+ }
+}
+
+void
+column_resize(Area *a, int w) {
+ Area *an;
+ int dw;
+
+ an = a->next;
+ assert(an != nil);
+
+ dw = w - Dx(a->r);
+ a->r.max.x += dw;
+ an->r.min.x += dw;
+
+ /* view_arrange(a->view); */
+ view_update(a->view);
+}
+
+static void
+column_resizeframe_h(Frame *f, Rectangle r) {
+ Area *a;
+ Frame *fn, *fp;
+ uint minh;
+
+ minh = labelh(def.font);
+
+ a = f->area;
+ fn = f->anext;
+ fp = f->aprev;
+
+ if(fp)
+ r.min.y = max(r.min.y, fp->colr.min.y + minh);
+ else
+ r.min.y = max(r.min.y, a->r.min.y);
+
+ if(fn)
+ r.max.y = min(r.max.y, fn->colr.max.y - minh);
+ else
+ r.max.y = min(r.max.y, a->r.max.y);
+
+ if(fp) {
+ fp->colr.max.y = r.min.y;
+ frame_resize(fp, fp->colr);
+ }
+ else
+ r.min.y = min(r.min.y, r.max.y - minh);
+
+ if(fn) {
+ fn->colr.min.y = r.max.y;
+ frame_resize(fn, fn->colr);
+ }
+ else
+ r.max.y = max(r.max.y, r.min.y + minh);
+
+ f->colr = r;
+ frame_resize(f, r);
+}
+
+void
+column_resizeframe(Frame *f, Rectangle r) {
+ Area *a, *al, *ar;
+ View *v;
+ uint minw;
+
+ a = f->area;
+ v = a->view;
+
+ minw = column_minwidth();
+
+ al = a->prev;
+ ar = a->next;
+
+ if(al)
+ r.min.x = max(r.min.x, al->r.min.x + minw);
+ else { /* Hm... */
+ r.min.x = max(r.min.x, v->r[a->screen].min.x);
+ r.max.x = max(r.max.x, r.min.x + minw);
+ }
+
+ if(ar)
+ r.max.x = min(r.max.x, ar->r.max.x - minw);
+ else {
+ r.max.x = min(r.max.x, v->r[a->screen].max.x);
+ r.min.x = min(r.min.x, r.max.x - minw);
+ }
+
+ a->r.min.x = r.min.x;
+ a->r.max.x = r.max.x;
+ if(al) {
+ al->r.max.x = a->r.min.x;
+ column_arrange(al, false);
+ }
+ if(ar) {
+ ar->r.min.x = a->r.max.x;
+ column_arrange(ar, false);
+ }
+
+ column_resizeframe_h(f, r);
+
+ view_update(v);
+}
+
diff --git a/cmd/wmii/dat.h b/cmd/wmii/dat.h
new file mode 100644
index 0000000..8038026
--- /dev/null
+++ b/cmd/wmii/dat.h
@@ -0,0 +1,404 @@
+/* Copyright ©2007-2010 Kris Maglione <jg@suckless.org>
+ * See LICENSE file for license details.
+ */
+
+#define _XOPEN_SOURCE 600
+#define IXP_P9_STRUCTS
+#define IXP_NO_P9_
+#include <assert.h>
+#include <regexp9.h>
+#include <stdbool.h>
+#include <stdint.h>
+#include <stdlib.h>
+#include <string.h>
+#include <ixp.h>
+#include <util.h>
+#include <utf.h>
+#include <fmt.h>
+#include <x11.h>
+
+#define FONT "-*-fixed-medium-r-*-*-13-*-*-*-*-*-*-*"
+#define FOCUSCOLORS "#ffffff #335577 #447799"
+#define NORMCOLORS "#222222 #eeeeee #666666"
+
+enum {
+ PingTime = 10000,
+};
+
+enum {
+ CLeft = 1<<0,
+ CCenter = 1<<1,
+ CRight = 1<<2,
+};
+
+enum IncMode {
+ IIgnore,
+ IShow,
+ ISqueeze,
+};
+
+enum {
+ GInvert = 1<<0,
+};
+
+enum {
+ UrgManager,
+ UrgClient,
+};
+
+enum EWMHType {
+ TypeDesktop = 1<<0,
+ TypeDock = 1<<1,
+ TypeToolbar = 1<<2,
+ TypeMenu = 1<<3,
+ TypeUtility = 1<<4,
+ TypeSplash = 1<<5,
+ TypeDialog = 1<<6,
+ TypeNormal = 1<<7,
+};
+
+enum {
+ Coldefault, Colstack, Colmax, Collast
+};
+
+extern char* modes[];
+
+#define TOGGLE(x) \
+ (x == On ? "on" : \
+ x == Off ? "off" : \
+ x == Toggle ? "toggle" : \
+ "<toggle>")
+enum {
+ Off,
+ On,
+ Toggle,
+};
+
+enum Barpos {
+ BBottom,
+ BTop,
+};
+
+enum {
+ CurNormal,
+ CurNECorner, CurNWCorner, CurSECorner, CurSWCorner,
+ CurDHArrow, CurDVArrow, CurMove, CurInput, CurSizing,
+ CurTCross, CurIcon,
+ CurNone,
+ CurLast,
+};
+
+enum {
+ NCOL = 16,
+};
+
+enum Protocols {
+ ProtoDelete = 1<<0,
+ ProtoTakeFocus = 1<<1,
+ ProtoPing = 1<<2,
+};
+
+enum DebugOpt {
+ D9p = 1<<0,
+ DDnd = 1<<1,
+ DEvent = 1<<2,
+ DEwmh = 1<<3,
+ DFocus = 1<<4,
+ DGeneric= 1<<5,
+ DStack = 1<<6,
+ NDebugOpt = 7,
+};
+
+/* Data Structures */
+typedef struct Area Area;
+typedef struct Bar Bar;
+typedef struct Client Client;
+typedef struct Divide Divide;
+typedef struct Frame Frame;
+typedef struct Group Group;
+typedef struct Key Key;
+typedef struct Map Map;
+typedef struct MapEnt MapEnt;
+typedef struct Regex Regex;
+typedef struct Rule Rule;
+typedef struct Ruleset Ruleset;
+typedef struct Strut Strut;
+typedef struct View View;
+typedef struct WMScreen WMScreen;
+
+struct Area {
+ Area* next;
+ Area* prev;
+ Frame* frame;
+ Frame* frame_old;
+ Frame* stack;
+ Frame* sel;
+ View* view;
+ bool floating;
+ ushort id;
+ int mode;
+ int screen;
+ bool max;
+ Rectangle r;
+ Rectangle r_old;
+};
+
+struct Bar {
+ Bar* next;
+ Bar* smaller;
+ char buf[280];
+ char text[256];
+ char name[256];
+ int bar;
+ ushort id;
+ CTuple col;
+ Rectangle r;
+ WMScreen* screen;
+};
+
+struct Regex {
+ char* regex;
+ Reprog* regc;
+};
+
+struct Client {
+ Client* next;
+ Frame* frame;
+ Frame* sel;
+ Window w;
+ Window* framewin;
+ Image** ibuf;
+ XWindow trans;
+ Regex tagre;
+ Regex tagvre;
+ Group* group;
+ Strut* strut;
+ Cursor cursor;
+ Rectangle r;
+ char** retags;
+ char name[256];
+ char tags[256];
+ char props[512];
+ long proto;
+ uint border;
+ int fullscreen;
+ int unmapped;
+ bool floating;
+ bool fixedsize;
+ bool urgent;
+ bool borderless;
+ bool titleless;
+ bool noinput;
+};
+
+struct Divide {
+ Divide* next;
+ Window* w;
+ Area* left;
+ Area* right;
+ bool mapped;
+ int x;
+};
+
+struct Frame {
+ Frame* cnext;
+ Frame* anext;
+ Frame* aprev;
+ Frame* anext_old;
+ Frame* snext;
+ Frame* sprev;
+ Client* client;
+ View* view;
+ Area* area;
+ int oldscreen;
+ int oldarea;
+ int screen;
+ int column;
+ ushort id;
+ bool collapsed;
+ int dy;
+ Rectangle r;
+ Rectangle colr;
+ Rectangle colr_old;
+ Rectangle floatr;
+ Rectangle crect;
+ Rectangle grabbox;
+ Rectangle titlebar;
+};
+
+struct Group {
+ Group* next;
+ XWindow leader;
+ Client *client;
+ int ref;
+};
+
+struct Key {
+ Key* next;
+ Key* lnext;
+ Key* tnext;
+ ushort id;
+ char name[128];
+ ulong mod;
+ KeyCode key;
+};
+
+struct Map {
+ MapEnt**bucket;
+ uint nhash;
+};
+
+struct Rule {
+ Rule* next;
+ Reprog* regex;
+ char value[256];
+
+};
+
+struct Ruleset {
+ Rule* rule;
+ char* string;
+ uint size;
+};
+
+struct Strut {
+ Rectangle left;
+ Rectangle right;
+ Rectangle top;
+ Rectangle bottom;
+};
+
+#define firstarea areas[screen->idx]
+#define screenr r[screen->idx]
+struct View {
+ View* next;
+ char name[256];
+ ushort id;
+ Area* floating;
+ Area** areas;
+ Area* sel;
+ Area* oldsel;
+ Area* revert;
+ int selcol;
+ int selscreen;
+ bool dead;
+ Rectangle *r;
+ Rectangle *pad;
+};
+
+/* Yuck. */
+#define VECTOR(type, nam, c) \
+typedef struct Vector_##nam Vector_##nam; \
+struct Vector_##nam { \
+ type* ary; \
+ long n; \
+ long size; \
+}; \
+void vector_##c##free(Vector_##nam*); \
+void vector_##c##init(Vector_##nam*); \
+void vector_##c##push(Vector_##nam*, type); \
+
+VECTOR(long, long, l)
+VECTOR(Rectangle, rect, r)
+VECTOR(void*, ptr, p)
+#undef VECTOR
+
+#ifndef EXTERN
+# define EXTERN extern
+#endif
+
+/* global variables */
+EXTERN struct {
+ CTuple focuscolor;
+ CTuple normcolor;
+ Font* font;
+ char* keys;
+ uint keyssz;
+ Ruleset tagrules;
+ Ruleset colrules;
+ char grabmod[5];
+ ulong mod;
+ uint border;
+ uint snap;
+ int colmode;
+ int incmode;
+} def;
+
+enum {
+ BLeft, BRight
+};
+
+#define BLOCK(x) do { x; }while(0)
+
+EXTERN struct WMScreen {
+ Bar* bar[2];
+ Window* barwin;
+ bool showing;
+ int barpos;
+ int idx;
+
+ Rectangle r;
+ Rectangle brect;
+} **screens, *screen;
+EXTERN uint nscreens;
+
+EXTERN struct {
+ Client* focus;
+ Client* hasgrab;
+ Image* ibuf;
+ Image* ibuf32;
+ bool sel;
+} disp;
+
+EXTERN Client* client;
+EXTERN View* view;
+EXTERN View* selview;
+EXTERN Key* key;
+EXTERN Divide* divs;
+EXTERN Client c_magic;
+EXTERN Client c_root;
+
+EXTERN Handlers framehandler;
+
+EXTERN char buffer[8092];
+EXTERN char* _buffer;
+static char* const _buf_end = buffer + sizeof buffer;
+
+#define bufclear() \
+ BLOCK( _buffer = buffer; _buffer[0] = '\0' )
+#define bufprint(...) \
+ _buffer = seprint(_buffer, _buf_end, __VA_ARGS__)
+
+/* IXP */
+EXTERN IxpServer srv;
+EXTERN Ixp9Srv p9srv;
+
+/* X11 */
+EXTERN uint valid_mask;
+EXTERN uint numlock_mask;
+EXTERN Image* ibuf;
+EXTERN Image* ibuf32;
+
+EXTERN Cursor cursor[CurLast];
+
+typedef void (*XHandler)(XEvent*);
+EXTERN XHandler handler[LASTEvent];
+
+/* Misc */
+EXTERN int starting;
+EXTERN bool resizing;
+EXTERN long ignoreenter;
+EXTERN char* user;
+EXTERN char* execstr;
+EXTERN int debugflag;
+EXTERN int debugfile;
+EXTERN long xtime;
+EXTERN Visual* render_visual;
+
+EXTERN Client* kludge;
+
+extern char* debugtab[];
+
+#define Debug(x) if(((debugflag|debugfile)&(x)) && setdebug(x))
+#define Dprint(x, ...) BLOCK( if((debugflag|debugfile)&(x)) debug(x, __VA_ARGS__) )
+
diff --git a/cmd/wmii/div.c b/cmd/wmii/div.c
new file mode 100644
index 0000000..986d6a2
--- /dev/null
+++ b/cmd/wmii/div.c
@@ -0,0 +1,190 @@
+/* Copyright ©2006-2010 Kris Maglione <maglione.k at Gmail>
+ * See LICENSE file for license details.
+ */
+#include "dat.h"
+#include "fns.h"
+
+static Image* divimg;
+static Image* divmask;
+static CTuple divcolor;
+static Handlers handlers;
+
+static Divide*
+getdiv(Divide ***dp) {
+ WinAttr wa;
+ Divide *d;
+
+ if(**dp)
+ d = **dp;
+ else {
+ d = emallocz(sizeof *d);
+
+ wa.override_redirect = true;
+ wa.cursor = cursor[CurDHArrow];
+ wa.event_mask =
+ ExposureMask
+ | EnterWindowMask
+ | ButtonPressMask
+ | ButtonReleaseMask;
+ d->w = createwindow(&scr.root, Rect(0, 0, 1, 1), scr.depth,
+ InputOutput, &wa,
+ CWOverrideRedirect
+ | CWEventMask
+ | CWCursor);
+ d->w->aux = d;
+ sethandler(d->w, &handlers);
+ **dp = d;
+ }
+ *dp = &d->next;
+ return d;
+}
+
+static void
+mapdiv(Divide *d) {
+ mapwin(d->w);
+}
+
+static void
+unmapdiv(Divide *d) {
+ unmapwin(d->w);
+}
+
+void
+div_set(Divide *d, int x) {
+ Rectangle r;
+ int scrn;
+
+ scrn = d->left ? d->left->screen : d->right->screen;
+
+ d->x = x;
+ r = rectaddpt(divimg->r, Pt(x - Dx(divimg->r)/2, 0));
+ r.min.y = selview->r[scrn].min.y;
+ r.max.y = selview->r[scrn].max.y;
+
+ reshapewin(d->w, r);
+ mapdiv(d);
+}
+
+static void
+drawimg(Image *img, Color cbg, Color cborder, Divide *d) {
+ Point pt[8];
+ int n, start, w;
+
+ w = Dx(img->r)/2;
+ n = 0;
+ pt[n++] = Pt(w, 0);
+ pt[n++] = Pt(0, 0);
+ pt[n++] = Pt(w - 1, w - 1);
+
+ pt[n++] = Pt(w - 1, Dy(img->r));
+ pt[n++] = Pt(w, pt[n-1].y);
+
+ pt[n++] = Pt(w, w - 1);
+ pt[n++] = Pt(2*w - 1, 0);
+ pt[n++] = Pt(w, 0);
+
+ start = d->left ? 0 : n/2;
+ n = d->right && d->left ? n : n/2;
+
+ fillpoly(img, pt + start, n, cbg);
+ drawpoly(img, pt + start, n, CapNotLast, 1, cborder);
+}
+
+static void
+drawdiv(Divide *d) {
+
+ fill(divmask, divmask->r, (Color){0});
+ drawimg(divmask, (Color){1}, (Color){1}, d);
+ drawimg(divimg, divcolor.bg, divcolor.border, d);
+
+ copyimage(d->w, divimg->r, divimg, ZP);
+ setshapemask(d->w, divmask, ZP);
+}
+
+static void
+update_imgs(void) {
+ Divide *d;
+ int w, h;
+
+ w = 2 * (labelh(def.font) / 3);
+ w = max(w, 10);
+ /* XXX: Multihead. */
+ h = Dy(scr.rect);
+
+ if(divimg) {
+ if(w == Dx(divimg->r) && h == Dy(divimg->r)
+ && !memcmp(&divcolor, &def.normcolor, sizeof divcolor))
+ return;
+ freeimage(divimg);
+ freeimage(divmask);
+ }
+
+ divimg = allocimage(w, h, scr.depth);
+ divmask = allocimage(w, h, 1);
+ divcolor = def.normcolor;
+
+ for(d = divs; d && d->w->mapped; d = d->next)
+ drawdiv(d);
+}
+
+void
+div_update_all(void) {
+ Divide **dp, *d;
+ Area *a, *ap;
+ View *v;
+ int s;
+
+ update_imgs();
+
+ v = selview;
+ dp = &divs;
+ ap = nil;
+ foreach_column(v, s, a) {
+ if (ap && ap->screen != s)
+ ap = nil;
+
+ d = getdiv(&dp);
+ d->left = ap;
+ d->right = a;
+ div_set(d, a->r.min.x);
+ drawdiv(d);
+ ap = a;
+
+ if(!a->next) {
+ d = getdiv(&dp);
+ d->left = a;
+ d->right = nil;
+ div_set(d, a->r.max.x);
+ drawdiv(d);
+ }
+ }
+ for(d = *dp; d; d = d->next)
+ unmapdiv(d);
+}
+
+/* Div Handlers */
+static void
+bdown_event(Window *w, XButtonEvent *e) {
+ Divide *d;
+
+ USED(e);
+
+ d = w->aux;
+ mouse_resizecol(d);
+}
+
+static void
+expose_event(Window *w, XExposeEvent *e) {
+ Divide *d;
+
+ USED(e);
+
+ d = w->aux;
+ drawdiv(d);
+}
+
+static Handlers handlers = {
+ .bdown = bdown_event,
+ .expose = expose_event,
+};
+
diff --git a/cmd/wmii/error.c b/cmd/wmii/error.c
new file mode 100644
index 0000000..3f66653
--- /dev/null
+++ b/cmd/wmii/error.c
@@ -0,0 +1,41 @@
+/* Copyright ©2007-2010 Kris Maglione <jg@suckless.org>
+ * See LICENSE file for license details.
+ */
+
+#include "dat.h"
+#include "fns.h"
+
+static jmp_buf errjmp[16];
+static long nerror;
+
+void
+error(char *fmt, ...) {
+ char errbuf[IXP_ERRMAX];
+ va_list ap;
+
+ va_start(ap, fmt);
+ vsnprint(errbuf, IXP_ERRMAX, fmt, ap);
+ va_end(ap);
+ ixp_errstr(errbuf, IXP_ERRMAX);
+
+ nexterror();
+}
+
+void
+nexterror(void) {
+ assert(nerror > 0);
+ longjmp(errjmp[--nerror], 1);
+}
+
+void
+poperror(void) {
+ assert(nerror > 0);
+ --nerror;
+}
+
+jmp_buf*
+pusherror(void) {
+ assert(nerror < nelem(errjmp));
+ return &errjmp[nerror++];
+}
+
diff --git a/cmd/wmii/event.c b/cmd/wmii/event.c
new file mode 100644
index 0000000..82a51eb
--- /dev/null
+++ b/cmd/wmii/event.c
@@ -0,0 +1,359 @@
+/* Copyright ©2006-2010 Kris Maglione <maglione.k at Gmail>
+ * See LICENSE file for license details.
+ */
+#include "dat.h"
+#include <X11/keysym.h>
+#include "fns.h"
+
+typedef void (*EvHandler)(XEvent*);
+
+void
+dispatch_event(XEvent *e) {
+ Dprint(DEvent, "%E\n", e);
+ if(e->type < nelem(handler)) {
+ if(handler[e->type])
+ handler[e->type](e);
+ }else
+ xext_event(e);
+}
+
+#define handle(w, fn, ev) \
+ BLOCK(if((w)->handler->fn) (w)->handler->fn((w), ev))
+
+static int
+findtime(Display *d, XEvent *e, XPointer v) {
+ Window *w;
+
+ w = (Window*)v;
+ if(e->type == PropertyNotify && e->xproperty.window == w->xid) {
+ xtime = e->xproperty.time;
+ return true;
+ }
+ return false;
+}
+
+void
+xtime_kludge(void) {
+ /* Round trip. */
+ static Window *w;
+ WinAttr wa;
+ XEvent e;
+ long l;
+
+ if(w == nil) {
+ w = createwindow(&scr.root, Rect(0, 0, 1, 1), 0, InputOnly, &wa, 0);
+ selectinput(w, PropertyChangeMask);
+ }
+ changeprop_long(w, "ATOM", "ATOM", &l, 0);
+ sync();
+ XIfEvent(display, &e, findtime, (void*)w);
+}
+
+uint
+flushevents(long event_mask, bool dispatch) {
+ XEvent ev;
+ uint n = 0;
+
+ while(XCheckMaskEvent(display, event_mask, &ev)) {
+ if(dispatch)
+ dispatch_event(&ev);
+ n++;
+ }
+ return n;
+}
+
+static Bool
+findenter(Display *d, XEvent *e, XPointer v) {
+ long *l;
+
+ USED(d);
+ l = (long*)v;
+ if(*l)
+ return false;
+ if(e->type == EnterNotify)
+ return true;
+ if(e->type == MotionNotify)
+ (*l)++;
+ return false;
+}
+
+/* This isn't perfect. If there were motion events in the queue
+ * before this was called, then it flushes nothing. If we don't
+ * check for them, we might lose a legitamate enter event.
+ */
+uint
+flushenterevents(void) {
+ XEvent e;
+ long l;
+ int n;
+
+ l = 0;
+ n = 0;
+ while(XCheckIfEvent(display, &e, findenter, (void*)&l))
+ n++;
+ return n;
+}
+
+static void
+buttonrelease(XButtonPressedEvent *ev) {
+ Window *w;
+
+ if((w = findwin(ev->window)))
+ handle(w, bup, ev);
+}
+
+static void
+buttonpress(XButtonPressedEvent *ev) {
+ Window *w;
+
+ if((w = findwin(ev->window)))
+ handle(w, bdown, ev);
+ else
+ XAllowEvents(display, ReplayPointer, ev->time);
+}
+
+static void
+configurerequest(XConfigureRequestEvent *ev) {
+ XWindowChanges wc;
+ Window *w;
+
+ if((w = findwin(ev->window)))
+ handle(w, configreq, ev);
+ else{
+ wc.x = ev->x;
+ wc.y = ev->y;
+ wc.width = ev->width;
+ wc.height = ev->height;
+ wc.border_width = ev->border_width;
+ wc.sibling = ev->above;
+ wc.stack_mode = ev->detail;
+ XConfigureWindow(display, ev->window, ev->value_mask, &wc);
+ }
+}
+
+static void
+configurenotify(XConfigureEvent *ev) {
+ Window *w;
+
+ ignoreenter = ev->serial;
+ if((w = findwin(ev->window)))
+ handle(w, config, ev);
+}
+
+static void
+clientmessage(XClientMessageEvent *ev) {
+
+ if(ewmh_clientmessage(ev))
+ return;
+ if(xdnd_clientmessage(ev))
+ return;
+}
+
+static void
+destroynotify(XDestroyWindowEvent *ev) {
+ Window *w;
+ Client *c;
+
+ if((w = findwin(ev->window)))
+ handle(w, destroy, ev);
+ else {
+ if((c = win2client(ev->window)))
+ fprint(2, "Badness: Unhandled DestroyNotify: "
+ "Client: %p, Window: %W, Name: %s\n",
+ c, &c->w, c->name);
+ }
+}
+
+static void
+enternotify(XCrossingEvent *ev) {
+ Window *w;
+
+ xtime = ev->time;
+ if(ev->mode != NotifyNormal)
+ return;
+
+ if((w = findwin(ev->window)))
+ handle(w, enter, ev);
+}
+
+static void
+leavenotify(XCrossingEvent *ev) {
+ Window *w;
+
+ xtime = ev->time;
+ if((w = findwin(ev->window)))
+ handle(w, leave, ev);
+}
+
+void
+print_focus(const char *fn, Client *c, const char *to) {
+ Dprint(DFocus, "%s() disp.focus:\n", fn);
+ Dprint(DFocus, "\t%C => %C\n", disp.focus, c);
+ Dprint(DFocus, "\t%s => %s\n", clientname(disp.focus), to);
+}
+
+static void
+focusin(XFocusChangeEvent *ev) {
+ Window *w;
+ Client *c;
+
+ /* Yes, we're focusing in on nothing, here. */
+ if(ev->detail == NotifyDetailNone) {
+ print_focus("focusin", &c_magic, "<magic[none]>");
+ disp.focus = &c_magic;
+ setfocus(screen->barwin, RevertToParent);
+ return;
+ }
+
+ if(!((ev->detail == NotifyNonlinear)
+ ||(ev->detail == NotifyNonlinearVirtual)
+ ||(ev->detail == NotifyVirtual)
+ ||(ev->detail == NotifyInferior)
+ ||(ev->detail == NotifyAncestor)))
+ return;
+ if((ev->mode == NotifyWhileGrabbed) && (disp.hasgrab != &c_root))
+ return;
+
+ if(ev->window == screen->barwin->xid) {
+ print_focus("focusin", nil, "<nil>");
+ disp.focus = nil;
+ }
+ else if((w = findwin(ev->window)))
+ handle(w, focusin, ev);
+ else if(ev->mode == NotifyGrab) {
+ /* Some unmanaged window has grabbed focus */
+ if((c = disp.focus)) {
+ print_focus("focusin", &c_magic, "<magic>");
+ disp.focus = &c_magic;
+ if(c->sel)
+ frame_draw(c->sel);
+ }
+ }
+}
+
+static void
+focusout(XFocusChangeEvent *ev) {
+ XEvent me;
+ Window *w;
+
+ if(!((ev->detail == NotifyNonlinear)
+ ||(ev->detail == NotifyNonlinearVirtual)
+ ||(ev->detail == NotifyVirtual)
+ ||(ev->detail == NotifyInferior)
+ ||(ev->detail == NotifyAncestor)))
+ return;
+ if(ev->mode == NotifyUngrab)
+ disp.hasgrab = nil;
+
+ if((ev->mode == NotifyGrab)
+ && XCheckMaskEvent(display, KeyPressMask, &me))
+ dispatch_event(&me);
+ else if((w = findwin(ev->window)))
+ handle(w, focusout, ev);
+}
+
+static void
+expose(XExposeEvent *ev) {
+ Window *w;
+
+ if(ev->count == 0)
+ if((w = findwin(ev->window)))
+ handle(w, expose, ev);
+}
+
+static void
+keypress(XKeyEvent *ev) {
+ Window *w;
+
+ xtime = ev->time;
+ if((w = findwin(ev->window)))
+ handle(w, kdown, ev);
+}
+
+static void
+mappingnotify(XMappingEvent *ev) {
+
+ XRefreshKeyboardMapping(ev);
+ if(ev->request == MappingKeyboard)
+ update_keys();
+}
+
+static void
+maprequest(XMapRequestEvent *ev) {
+ Window *w;
+
+ if((w = findwin(ev->parent)))
+ handle(w, mapreq, ev);
+}
+
+static void
+motionnotify(XMotionEvent *ev) {
+ Window *w;
+
+ xtime = ev->time;
+ if((w = findwin(ev->window)))
+ handle(w, motion, ev);
+}
+
+static void
+propertynotify(XPropertyEvent *ev) {
+ Window *w;
+
+ xtime = ev->time;
+ if((w = findwin(ev->window)))
+ handle(w, property, ev);
+}
+
+static void
+mapnotify(XMapEvent *ev) {
+ Window *w;
+
+ ignoreenter = ev->serial;
+ if((w = findwin(ev->window)))
+ handle(w, map, ev);
+}
+
+static void
+unmapnotify(XUnmapEvent *ev) {
+ Window *w;
+
+ ignoreenter = ev->serial;
+ if((w = findwin(ev->window)) && (ev->event == w->parent->xid)) {
+ w->mapped = false;
+ if(ev->send_event || w->unmapped-- == 0)
+ handle(w, unmap, ev);
+ }
+}
+
+EvHandler handler[LASTEvent] = {
+ [ButtonPress] = (EvHandler)buttonpress,
+ [ButtonRelease] = (EvHandler)buttonrelease,
+ [ConfigureRequest] = (EvHandler)configurerequest,
+ [ConfigureNotify] = (EvHandler)configurenotify,
+ [ClientMessage] = (EvHandler)clientmessage,
+ [DestroyNotify] = (EvHandler)destroynotify,
+ [EnterNotify] = (EvHandler)enternotify,
+ [Expose] = (EvHandler)expose,
+ [FocusIn] = (EvHandler)focusin,
+ [FocusOut] = (EvHandler)focusout,
+ [KeyPress] = (EvHandler)keypress,
+ [LeaveNotify] = (EvHandler)leavenotify,
+ [MapNotify] = (EvHandler)mapnotify,
+ [MapRequest] = (EvHandler)maprequest,
+ [MappingNotify] = (EvHandler)mappingnotify,
+ [MotionNotify] = (EvHandler)motionnotify,
+ [PropertyNotify] = (EvHandler)propertynotify,
+ [UnmapNotify] = (EvHandler)unmapnotify,
+};
+
+void
+check_x_event(IxpConn *c) {
+ XEvent ev;
+
+ USED(c);
+ while(XPending(display)) {
+ XNextEvent(display, &ev);
+ dispatch_event(&ev);
+ }
+}
+
diff --git a/cmd/wmii/ewmh.c b/cmd/wmii/ewmh.c
new file mode 100644
index 0000000..6509e5f
--- /dev/null
+++ b/cmd/wmii/ewmh.c
@@ -0,0 +1,544 @@
+/* Copyright ©2007-2010 Kris Maglione <maglione.k at Gmail>
+ * See LICENSE file for license details.
+ */
+#include "dat.h"
+#include <limits.h>
+#include "fns.h"
+
+Window *ewmhwin;
+
+static void ewmh_getwinstate(Client*);
+static void ewmh_setstate(Client*, Atom, int);
+
+#define Net(x) ("_NET_" x)
+#define Action(x) Net("WM_ACTION_" x)
+#define State(x) Net("WM_STATE_" x)
+#define Type(x) Net("WM_WINDOW_TYPE_" x)
+#define NET(x) xatom(Net(x))
+#define ACTION(x) xatom(Action(x))
+#define STATE(x) xatom(State(x))
+#define TYPE(x) xatom(Type(x))
+
+void
+ewmh_init(void) {
+ WinAttr wa;
+ char myname[] = "wmii";
+ long win;
+
+ ewmhwin = createwindow(&scr.root,
+ Rect(0, 0, 1, 1), 0 /*depth*/,
+ InputOnly, &wa, 0);
+
+ win = ewmhwin->xid;
+ changeprop_long(&scr.root, Net("SUPPORTING_WM_CHECK"), "WINDOW", &win, 1);
+ changeprop_long(ewmhwin, Net("SUPPORTING_WM_CHECK"), "WINDOW", &win, 1);
+ changeprop_string(ewmhwin, Net("WM_NAME"), myname);
+
+ long zz[] = {0, 0};
+ changeprop_long(&scr.root, Net("DESKTOP_VIEWPORT"), "CARDINAL",
+ zz, 2);
+
+ long supported[] = {
+ /* Misc */
+ NET("SUPPORTED"),
+ /* Root Properties/Messages */
+ NET("ACTIVE_WINDOW"),
+ NET("CLOSE_WINDOW"),
+ NET("CURRENT_DESKTOP"),
+ /* Client Properties */
+ NET("FRAME_EXTENTS"),
+ NET("WM_DESKTOP"),
+ NET("WM_FULLSCREEN_MONITORS"),
+ NET("WM_NAME"),
+ NET("WM_STRUT"),
+ NET("WM_STRUT_PARTIAL"),
+ /* States */
+ NET("WM_STATE"),
+ STATE("DEMANDS_ATTENTION"),
+ STATE("FULLSCREEN"),
+ STATE("SHADED"),
+ /* Window Types */
+ NET("WM_WINDOW_TYPE"),
+ TYPE("DIALOG"),
+ TYPE("DOCK"),
+ TYPE("NORMAL"),
+ TYPE("SPLASH"),
+ /* Actions */
+ NET("WM_ALLOWED_ACTIONS"),
+ ACTION("FULLSCREEN"),
+ /* Desktops */
+ NET("DESKTOP_NAMES"),
+ NET("NUMBER_OF_DESKTOPS"),
+ /* Client List */
+ NET("CLIENT_LIST"),
+ NET("CLIENT_LIST_STACKING"),
+ };
+ changeprop_long(&scr.root, Net("SUPPORTED"), "ATOM", supported, nelem(supported));
+}
+
+void
+ewmh_updateclientlist(void) {
+ Vector_long vec;
+ Client *c;
+
+ vector_linit(&vec);
+ for(c=client; c; c=c->next)
+ vector_lpush(&vec, c->w.xid);
+ changeprop_long(&scr.root, Net("CLIENT_LIST"), "WINDOW", vec.ary, vec.n);
+ free(vec.ary);
+}
+
+void
+ewmh_updatestacking(void) {
+ Vector_long vec;
+ Frame *f;
+ Area *a;
+ View *v;
+ int s;
+
+ vector_linit(&vec);
+
+ for(v=view; v; v=v->next) {
+ foreach_column(v, s, a)
+ for(f=a->frame; f; f=f->anext)
+ if(f->client->sel == f)
+ vector_lpush(&vec, f->client->w.xid);
+ }
+ for(v=view; v; v=v->next) {
+ for(f=v->floating->stack; f; f=f->snext)
+ if(!f->snext) break;
+ for(; f; f=f->sprev)
+ if(f->client->sel == f)
+ vector_lpush(&vec, f->client->w.xid);
+ }
+
+ changeprop_long(&scr.root, Net("CLIENT_LIST_STACKING"), "WINDOW", vec.ary, vec.n);
+ vector_lfree(&vec);
+}
+
+void
+ewmh_initclient(Client *c) {
+ long allowed[] = {
+ ACTION("FULLSCREEN"),
+ };
+
+ changeprop_long(&c->w, Net("WM_ALLOWED_ACTIONS"), "ATOM",
+ allowed, nelem(allowed));
+ ewmh_getwintype(c);
+ ewmh_getwinstate(c);
+ ewmh_getstrut(c);
+ ewmh_updateclientlist();
+}
+
+void
+ewmh_destroyclient(Client *c) {
+ Ewmh *e;
+
+ ewmh_updateclientlist();
+
+ e = &c->w.ewmh;
+ if(e->timer)
+ if(!ixp_unsettimer(&srv, e->timer))
+ fprint(2, "Badness: %C: Can't unset timer\n", c);
+ free(c->strut);
+}
+
+static void
+pingtimeout(long id, void *v) {
+ Client *c;
+
+ USED(id);
+ c = v;
+ event("Unresponsive %C\n", c);
+ c->w.ewmh.ping = 0;
+ c->w.ewmh.timer = 0;
+}
+
+void
+ewmh_pingclient(Client *c) {
+ Ewmh *e;
+
+ if(!(c->proto & ProtoPing))
+ return;
+
+ e = &c->w.ewmh;
+ if(e->ping)
+ return;
+
+ client_message(c, Net("WM_PING"), c->w.xid);
+ e->ping = xtime++;
+ e->timer = ixp_settimer(&srv, PingTime, pingtimeout, c);
+}
+
+int
+ewmh_prop(Client *c, Atom a) {
+ if(a == NET("WM_WINDOW_TYPE"))
+ ewmh_getwintype(c);
+ else
+ if(a == NET("WM_STRUT_PARTIAL"))
+ ewmh_getstrut(c);
+ else
+ return 0;
+ return 1;
+}
+
+typedef struct Prop Prop;
+struct Prop {
+ char* name;
+ long mask;
+ Atom atom;
+};
+
+static long
+getmask(Prop *props, ulong *vals, int n) {
+ Prop *p;
+ long ret;
+
+ if(props[0].atom == 0)
+ for(p=props; p->name; p++)
+ p->atom = xatom(p->name);
+
+ ret = 0;
+ while(n--) {
+ Dprint(DEwmh, "\tvals[%d] = \"%A\"\n", n, vals[n]);
+ for(p=props; p->name; p++)
+ if(p->atom == vals[n]) {
+ ret |= p->mask;
+ break;
+ }
+ }
+ return ret;
+}
+
+static long
+getprop_mask(Window *w, char *prop, Prop *props) {
+ ulong *vals;
+ long n, mask;
+
+ n = getprop_ulong(w, prop, "ATOM",
+ 0L, &vals, 16);
+ mask = getmask(props, vals, n);
+ free(vals);
+ return mask;
+}
+
+void
+ewmh_getwintype(Client *c) {
+ static Prop props[] = {
+ {Type("DESKTOP"), TypeDesktop},
+ {Type("DOCK"), TypeDock},
+ {Type("TOOLBAR"), TypeToolbar},
+ {Type("MENU"), TypeMenu},
+ {Type("UTILITY"), TypeUtility},
+ {Type("SPLASH"), TypeSplash},
+ {Type("DIALOG"), TypeDialog},
+ {Type("NORMAL"), TypeNormal},
+ {0, }
+ };
+ long mask;
+
+ mask = getprop_mask(&c->w, Net("WM_WINDOW_TYPE"), props);
+
+ c->w.ewmh.type = mask;
+ if(mask & TypeDock) {
+ c->borderless = 1;
+ c->titleless = 1;
+ }
+}
+
+static void
+ewmh_getwinstate(Client *c) {
+ ulong *vals;
+ long n;
+
+ n = getprop_ulong(&c->w, Net("WM_STATE"), "ATOM",
+ 0L, &vals, 16);
+ while(--n >= 0)
+ ewmh_setstate(c, vals[n], On);
+ free(vals);
+}
+
+long
+ewmh_protocols(Window *w) {
+ static Prop props[] = {
+ {"WM_DELETE_WINDOW", ProtoDelete},
+ {"WM_TAKE_FOCUS", ProtoTakeFocus},
+ {Net("WM_PING"), ProtoPing},
+ {0, }
+ };
+
+ return getprop_mask(w, "WM_PROTOCOLS", props);
+}
+
+void
+ewmh_getstrut(Client *c) {
+ enum {
+ Left, Right, Top, Bottom,
+ LeftMin, LeftMax,
+ RightMin, RightMax,
+ TopMin, TopMax,
+ BottomMin, BottomMax,
+ Last
+ };
+ long *strut;
+ ulong n;
+
+ if(c->strut == nil)
+ free(c->strut);
+ c->strut = nil;
+
+ n = getprop_long(&c->w, Net("WM_STRUT_PARTIAL"), "CARDINAL",
+ 0L, &strut, Last);
+ if(n != Last) {
+ free(strut);
+ n = getprop_long(&c->w, Net("WM_STRUT"), "CARDINAL",
+ 0L, &strut, 4L);
+ if(n != 4) {
+ free(strut);
+ return;
+ }
+ Dprint(DEwmh, "ewmh_getstrut(%C[%s]) Using WM_STRUT\n", c, clientname(c));
+ strut = erealloc(strut, Last * sizeof *strut);
+ strut[LeftMin] = strut[RightMin] = 0;
+ strut[LeftMax] = strut[RightMax] = INT_MAX;
+ strut[TopMin] = strut[BottomMin] = 0;
+ strut[TopMax] = strut[BottomMax] = INT_MAX;
+ }
+ c->strut = emalloc(sizeof *c->strut);
+ c->strut->left = Rect(0, strut[LeftMin], strut[Left], strut[LeftMax]);
+ c->strut->right = Rect(-strut[Right], strut[RightMin], 0, strut[RightMax]);
+ c->strut->top = Rect(strut[TopMin], 0, strut[TopMax], strut[Top]);
+ c->strut->bottom = Rect(strut[BottomMin], -strut[Bottom], strut[BottomMax], 0);
+ Dprint(DEwmh, "ewmh_getstrut(%C[%s])\n", c, clientname(c));
+ Dprint(DEwmh, "\ttop: %R\n", c->strut->top);
+ Dprint(DEwmh, "\tleft: %R\n", c->strut->left);
+ Dprint(DEwmh, "\tright: %R\n", c->strut->right);
+ Dprint(DEwmh, "\tbottom: %R\n", c->strut->bottom);
+ free(strut);
+ view_update(selview);
+}
+
+static void
+ewmh_setstate(Client *c, Atom state, int action) {
+
+ Dprint(DEwmh, "\tSTATE = %A\n", state);
+ if(state == 0)
+ return;
+
+ if(state == STATE("FULLSCREEN"))
+ fullscreen(c, action, -1);
+ else
+ if(state == STATE("DEMANDS_ATTENTION"))
+ client_seturgent(c, action, UrgClient);
+}
+
+int
+ewmh_clientmessage(XClientMessageEvent *e) {
+ Client *c;
+ View *v;
+ ulong *l;
+ ulong msg;
+ int action, i;
+
+ l = (ulong*)e->data.l;
+ msg = e->message_type;
+ Dprint(DEwmh, "ClientMessage: %A\n", msg);
+
+ if(msg == NET("WM_STATE")) {
+ enum {
+ StateUnset,
+ StateSet,
+ StateToggle,
+ };
+ if(e->format != 32)
+ return -1;
+ c = win2client(e->window);
+ if(c == nil)
+ return 0;
+ switch(l[0]) {
+ case StateUnset: action = Off; break;
+ case StateSet: action = On; break;
+ case StateToggle: action = Toggle; break;
+ default: return -1;
+ }
+ Dprint(DEwmh, "\tAction: %s\n", TOGGLE(action));
+ ewmh_setstate(c, l[1], action);
+ ewmh_setstate(c, l[2], action);
+ return 1;
+ }else
+ if(msg == NET("ACTIVE_WINDOW")) {
+ if(e->format != 32)
+ return -1;
+ Dprint(DEwmh, "\tsource: %ld\n", l[0]);
+ Dprint(DEwmh, "\twindow: 0x%lx\n", e->window);
+ c = win2client(e->window);
+ if(c == nil)
+ return 1;
+ Dprint(DEwmh, "\tclient: %s\n", clientname(c));
+ if(l[0] != 2)
+ return 1;
+ focus(c, true);
+ return 1;
+ }else
+ if(msg == NET("CLOSE_WINDOW")) {
+ if(e->format != 32)
+ return -1;
+ Dprint(DEwmh, "\tsource: %ld\n", l[0]);
+ Dprint(DEwmh, "\twindow: 0x%lx\n", e->window);
+ c = win2client(e->window);
+ if(c == nil)
+ return 1;
+ client_kill(c, true);
+ return 1;
+ }else
+ if(msg == NET("CURRENT_DESKTOP")) {
+ if(e->format != 32)
+ return -1;
+ for(v=view, i=l[0]; v; v=v->next, i--)
+ if(i == 0)
+ break;
+ Dprint(DEwmh, "\t%s\n", v->name);
+ if(i == 0)
+ view_select(v->name);
+ return 1;
+ }else
+ if(msg == xatom("WM_PROTOCOLS")) {
+ if(e->format != 32)
+ return 0;
+ Dprint(DEwmh, "\t%A\n", l[0]);
+ if(l[0] == NET("WM_PING")) {
+ if(e->window != scr.root.xid)
+ return -1;
+ c = win2client(l[2]);
+ if(c == nil)
+ return 1;
+ Dprint(DEwmh, "\tclient = [%C]\"%s\"\n", c, clientname(c));
+ Dprint(DEwmh, "\ttimer = %ld, ping = %ld\n",
+ c->w.ewmh.timer, c->w.ewmh.ping);
+ if(c->w.ewmh.timer)
+ ixp_unsettimer(&srv, c->w.ewmh.timer);
+ c->w.ewmh.timer = 0;
+ c->w.ewmh.ping = 0;
+ return 1;
+ }
+ }
+
+ return 0;
+}
+
+void
+ewmh_framesize(Client *c) {
+ Rectangle r;
+ Frame *f;
+
+ f = c->sel;
+ r.min.x = f->crect.min.x;
+ r.min.y = f->crect.min.y;
+ r.max.x = Dx(f->r) - f->crect.max.x;
+ r.max.y = Dy(f->r) - f->crect.max.y;
+
+ long extents[] = {
+ r.min.x, r.max.x,
+ r.min.y, r.max.y,
+ };
+ changeprop_long(&c->w, Net("FRAME_EXTENTS"), "CARDINAL",
+ extents, nelem(extents));
+}
+
+void
+ewmh_updatestate(Client *c) {
+ long state[16];
+ Frame *f;
+ int i;
+
+ f = c->sel;
+ if(f == nil || f->view != selview)
+ return;
+
+ i = 0;
+ if(f->collapsed)
+ state[i++] = STATE("SHADED");
+ if(c->fullscreen >= 0)
+ state[i++] = STATE("FULLSCREEN");
+ if(c->urgent)
+ state[i++] = STATE("DEMANDS_ATTENTION");
+
+ if(i > 0)
+ changeprop_long(&c->w, Net("WM_STATE"), "ATOM", state, i);
+ else
+ delproperty(&c->w, Net("WM_STATE"));
+
+ if(c->fullscreen >= 0)
+ changeprop_long(&c->w, Net("WM_FULLSCREEN_MONITORS"), "CARDINAL",
+ (long[]) { c->fullscreen, c->fullscreen,
+ c->fullscreen, c->fullscreen },
+ 4);
+ else
+ delproperty(&c->w, Net("WM_FULLSCREEN_MONITORS"));
+}
+
+/* Views */
+void
+ewmh_updateviews(void) {
+ View *v;
+ Vector_ptr tags;
+ long i;
+
+ if(starting)
+ return;
+
+ vector_pinit(&tags);
+ for(v=view, i=0; v; v=v->next, i++)
+ vector_ppush(&tags, v->name);
+ vector_ppush(&tags, nil);
+ changeprop_textlist(&scr.root, Net("DESKTOP_NAMES"), "UTF8_STRING", (char**)tags.ary);
+ changeprop_long(&scr.root, Net("NUMBER_OF_DESKTOPS"), "CARDINAL", &i, 1);
+ vector_pfree(&tags);
+ ewmh_updateview();
+ ewmh_updateclients();
+}
+
+static int
+viewidx(View *v) {
+ View *vp;
+ int i;
+
+ for(vp=view, i=0; vp; vp=vp->next, i++)
+ if(vp == v)
+ break;
+ assert(vp);
+ return i;
+}
+
+void
+ewmh_updateview(void) {
+ long i;
+
+ if(starting)
+ return;
+
+ i = viewidx(selview);
+ changeprop_long(&scr.root, Net("CURRENT_DESKTOP"), "CARDINAL", &i, 1);
+}
+
+void
+ewmh_updateclient(Client *c) {
+ long i;
+
+ i = -1;
+ if(c->sel)
+ i = viewidx(c->sel->view);
+ changeprop_long(&c->w, Net("WM_DESKTOP"), "CARDINAL", &i, 1);
+}
+
+void
+ewmh_updateclients(void) {
+ Client *c;
+
+ if(starting)
+ return;
+
+ for(c=client; c; c=c->next)
+ ewmh_updateclient(c);
+}
+
diff --git a/cmd/wmii/float.c b/cmd/wmii/float.c
new file mode 100644
index 0000000..23998d5
--- /dev/null
+++ b/cmd/wmii/float.c
@@ -0,0 +1,245 @@
+/* Copyright ©2006-2010 Kris Maglione <maglione.k at Gmail>
+ * See LICENSE file for license details.
+ */
+#include "dat.h"
+#include <limits.h>
+#include "fns.h"
+
+static void float_placeframe(Frame*);
+
+void
+float_attach(Area *a, Frame *f) {
+
+ f->client->floating = true;
+
+ f->r = f->floatr;
+ float_placeframe(f);
+ assert(a->sel != f);
+ frame_insert(f, a->sel);
+
+ if(a->sel == nil)
+ area_setsel(a, f);
+}
+
+void
+float_detach(Frame *f) {
+ Frame *pr;
+ Area *a, *sel, *oldsel;
+ View *v;
+
+ v = f->view;
+ a = f->area;
+ sel = view_findarea(v, v->selscreen, v->selcol, false);
+ oldsel = v->oldsel;
+ pr = f->aprev;
+
+ frame_remove(f);
+
+ if(a->sel == f) {
+ if(!pr)
+ pr = a->frame;
+ a->sel = nil;
+ area_setsel(a, pr);
+ }
+ f->area = nil;
+
+ if(oldsel)
+ area_focus(oldsel);
+ else if(!a->frame)
+ if(sel && sel->frame)
+ area_focus(sel);
+}
+
+void
+float_resizeframe(Frame *f, Rectangle r) {
+
+ if(f->area->view == selview)
+ client_resize(f->client, r);
+ else
+ frame_resize(f, r);
+}
+
+void
+float_arrange(Area *a) {
+ Frame *f;
+
+ assert(a->floating);
+
+ switch(a->mode) {
+ case Coldefault:
+ for(f=a->frame; f; f=f->anext)
+ f->collapsed = false;
+ break;
+ case Colstack:
+ for(f=a->frame; f; f=f->anext)
+ f->collapsed = (f != a->sel);
+ break;
+ default:
+ die("not reached");
+ break;
+ }
+ for(f=a->frame; f; f=f->anext)
+ f->r = f->floatr;
+ view_update(a->view);
+}
+
+static void
+rect_push(Vector_rect *vec, Rectangle r) {
+ Rectangle *rp;
+ int i;
+
+ for(i=0; i < vec->n; i++) {
+ rp = &vec->ary[i];
+ if(rect_contains_p(*rp, r))
+ return;
+ if(rect_contains_p(r, *rp)) {
+ *rp = r;
+ return;
+ }
+ }
+ vector_rpush(vec, r);
+}
+
+Vector_rect*
+unique_rects(Vector_rect *vec, Rectangle orig) {
+ static Vector_rect vec1, vec2;
+ Vector_rect *v1, *v2, *v;
+ Rectangle r1, r2;
+ int i, j;
+
+ v1 = &vec1;
+ v2 = &vec2;
+ v1->n = 0;
+ vector_rpush(v1, orig);
+ for(i=0; i < vec->n; i++) {
+ v2->n = 0;
+ r1 = vec->ary[i];
+ for(j=0; j < v1->n; j++) {
+ r2 = v1->ary[j];
+ if(!rect_intersect_p(r1, r2)) {
+ rect_push(v2, r2);
+ continue;
+ }
+ if(r2.min.x < r1.min.x)
+ rect_push(v2, Rect(r2.min.x, r2.min.y, r1.min.x, r2.max.y));
+ if(r2.min.y < r1.min.y)
+ rect_push(v2, Rect(r2.min.x, r2.min.y, r2.max.x, r1.min.y));
+ if(r2.max.x > r1.max.x)
+ rect_push(v2, Rect(r1.max.x, r2.min.y, r2.max.x, r2.max.y));
+ if(r2.max.y > r1.max.y)
+ rect_push(v2, Rect(r2.min.x, r1.max.y, r2.max.x, r2.max.y));
+ }
+ v = v1;
+ v1 = v2;
+ v2 = v;
+ }
+ return v1;
+}
+
+Rectangle
+max_rect(Vector_rect *vec) {
+ Rectangle *r, *rp;
+ int i, a, area;
+
+ area = 0;
+ r = 0;
+ for(i=0; i < vec->n; i++) {
+ rp = &vec->ary[i];
+ a = Dx(*rp) * Dy(*rp);
+ if(a > area) {
+ area = a;
+ r = rp;
+ }
+ }
+ return r ? *r : ZR;
+}
+
+static void
+float_placeframe(Frame *f) {
+ static Vector_rect vec;
+ Vector_rect *vp;
+ Rectangle r;
+ Point dim, p;
+ Client *c;
+ Frame *ff;
+ Area *a, *sel;
+ long area, l;
+ int i, s;
+
+ a = f->area;
+ c = f->client;
+
+ /*
+ if(c->trans)
+ return;
+ */
+
+ if(c->fullscreen >= 0 || c->w.hints->position || starting) {
+ f->r = f->floatr;
+ return;
+ }
+
+ /* Find all rectangles on the floating layer into which
+ * the new frame would fit.
+ */
+ vec.n = 0;
+ for(ff=a->frame; ff; ff=ff->anext)
+ /* TODO: Find out why this check is needed.
+ * The frame hasn't been inserted yet, but somehow,
+ * its old rectangle winds up in the list.
+ */
+ if(ff->client != f->client)
+ vector_rpush(&vec, ff->r);
+
+ /* Decide which screen we want to place this on.
+ * Ideally, it should probably Do the Right Thing
+ * when a screen fills, but what's the right thing?
+ * I think usage will show...
+ */
+ s = -1;
+ ff = client_groupframe(c, f->view);
+ if (f->screen >= 0)
+ s = f->screen;
+ else if (ff)
+ s = ownerscreen(ff->r);
+ else if (selclient())
+ s = ownerscreen(selclient()->sel->r);
+ else {
+ sel = view_findarea(a->view, a->view->selscreen, a->view->selcol, false);
+ if (sel)
+ s = sel->screen;
+ }
+
+ r = s == -1 ? a->r : screens[s]->r;
+ vp = unique_rects(&vec, r);
+
+ area = LONG_MAX;
+ dim.x = Dx(f->r);
+ dim.y = Dy(f->r);
+ p = ZP;
+
+ for(i=0; i < vp->n; i++) {
+ r = vp->ary[i];
+ if(Dx(r) < dim.x || Dy(r) < dim.y)
+ continue;
+ l = Dx(r) * Dy(r);
+ if(l < area) {
+ area = l;
+ p = r.min;
+ }
+ }
+
+ if(area == LONG_MAX) {
+ /* Cascade. */
+ s = max(s, 0);
+ ff = a->sel;
+ if(ff)
+ p = addpt(ff->r.min, Pt(Dy(ff->titlebar), Dy(ff->titlebar)));
+ if(p.x + Dx(f->r) > screens[s]->r.max.x ||
+ p.y + Dy(f->r) > screens[s]->r.max.y)
+ p = screens[s]->r.min;
+ }
+
+ f->floatr = rectsetorigin(f->r, p);
+}
+
diff --git a/cmd/wmii/fns.h b/cmd/wmii/fns.h
new file mode 100644
index 0000000..9b53ef9
--- /dev/null
+++ b/cmd/wmii/fns.h
@@ -0,0 +1,328 @@
+/* Copyright ©2007-2010 Kris Maglione <jg@suckless.org>
+ * See LICENSE file for license details.
+ */
+
+#ifdef VARARGCK
+# pragma varargck argpos debug 2
+# pragma varargck argpos dprint 1
+# pragma varargck argpos event 1
+# pragma varargck argpos warning 1
+#
+# pragma varargck type "a" Area*
+# pragma varargck type "C" Client*
+# pragma varargck type "r" void
+#endif
+
+#define _cond(cond, n) (cond) && __alive++ == n
+#define _cont(cont) (void)(__alive--, cont)
+
+#define with(type, var) \
+ for(type var=(type)-1; (var == (type)-1) && ((var=0) || true);)
+
+/* Grotesque, but worth it. */
+
+#define foreach_area(v, s, a) \
+ with(int, __alive) \
+ with(Area*, __anext) \
+ for(s=0; _cond(s <= nscreens, 0); _cont(s++)) \
+ for((a)=(s < nscreens ? (v)->areas[s] : v->floating), __anext=(a)->next; _cond(a, 1); _cont(((a)=__anext) && (__anext=(a)->next)))
+
+#define foreach_column(v, s, a) \
+ with(int, __alive) \
+ with(Area*, __anext) \
+ for(s=0; _cond(s < nscreens, 0); _cont(s++)) \
+ for((a)=(v)->areas[s], __anext=(a)->next; _cond(a, 1); _cont(((a)=__anext) && (__anext=(a)->next)))
+
+#define foreach_frame(v, s, a, f) \
+ with(Frame*, __fnext) \
+ foreach_area(v, s, a) \
+ for((void)(((f)=(a)->frame) && (__fnext=(f)->anext)); _cond(f, 2); _cont(((f)=__fnext) && (__fnext=(f)->anext)))
+
+#define btassert(arg, cond) \
+ (cond ? fprint(1, __FILE__":%d: failed assertion: " #cond "\n", __LINE__), backtrace(arg), true : false)
+
+/* area.c */
+int afmt(Fmt*);
+void area_attach(Area*, Frame*);
+Area* area_create(View*, Area *pos, int scrn, uint w);
+void area_destroy(Area*);
+void area_detach(Frame*);
+Area* area_find(View*, Rectangle, int, bool);
+void area_focus(Area*);
+int area_idx(Area*);
+void area_moveto(Area*, Frame*);
+char* area_name(Area*);
+Client* area_selclient(Area*);
+void area_setsel(Area*, Frame*);
+
+/* bar.c */
+Bar* bar_create(Bar**, const char*);
+void bar_destroy(Bar**, Bar*);
+void bar_draw(WMScreen*);
+Bar* bar_find(Bar*, const char*);
+void bar_init(WMScreen*);
+void bar_load(Bar*);
+void bar_resize(WMScreen*);
+void bar_sety(WMScreen*, int);
+void bar_setbounds(WMScreen*, int, int);
+
+/* client.c */
+int Cfmt(Fmt *f);
+bool client_applytags(Client*, const char*);
+void client_configure(Client*);
+Client* client_create(XWindow, XWindowAttributes*);
+void client_destroy(Client*);
+char* client_extratags(Client*);
+bool client_floats_p(Client*);
+void client_focus(Client*);
+Frame* client_groupframe(Client*, View*);
+void client_kill(Client*, bool);
+void client_manage(Client*);
+void client_map(Client*);
+void client_message(Client*, char*, long);
+void client_prop(Client*, Atom);
+void client_reparent(Client*, Window*, Point);
+void client_resize(Client*, Rectangle);
+void client_setcursor(Client*, Cursor);
+void client_seturgent(Client*, int, int);
+void client_setviews(Client*, char**);
+void client_unmap(Client*, int state);
+Frame* client_viewframe(Client *c, View *v);
+char* clientname(Client*);
+void focus(Client*, bool restack);
+void fullscreen(Client*, int, long);
+Client* group_leader(Group*);
+int map_frame(Client*);
+Client* selclient(void);
+int unmap_frame(Client*);
+void update_class(Client*);
+Client* win2client(XWindow);
+Rectangle client_grav(Client*, Rectangle);
+
+/* column.c */
+bool column_setmode(Area*, const char*);
+char* column_getmode(Area*);
+void column_arrange(Area*, bool dirty);
+void column_attach(Area*, Frame*);
+void column_attachrect(Area*, Frame*, Rectangle);
+void column_detach(Frame*);
+void column_frob(Area*);
+void column_insert(Area*, Frame*, Frame*);
+int column_minwidth(void);
+Area* column_new(View*, Area*, int, uint);
+void column_remove(Frame*);
+void column_resize(Area*, int);
+void column_resizeframe(Frame*, Rectangle);
+void column_settle(Area*);
+void div_draw(Divide*);
+void div_set(Divide*, int x);
+void div_update_all(void);
+bool find(Area**, Frame**, int, bool, bool);
+int stack_count(Frame*, int*);
+Frame* stack_find(Area*, Frame*, int, bool);
+
+/* error.c */
+#define waserror() setjmp(pusherror())
+void error(char*, ...);
+void nexterror(void);
+void poperror(void);
+jmp_buf* pusherror(void);
+
+/* event.c */
+void check_x_event(IxpConn*);
+void dispatch_event(XEvent*);
+uint flushenterevents(void);
+uint flushevents(long, bool dispatch);
+void print_focus(const char*, Client*, const char*);
+void xtime_kludge(void);
+
+/* ewmh.c */
+int ewmh_clientmessage(XClientMessageEvent*);
+void ewmh_destroyclient(Client*);
+void ewmh_framesize(Client*);
+void ewmh_getstrut(Client*);
+void ewmh_getwintype(Client*);
+void ewmh_init(void);
+void ewmh_initclient(Client*);
+void ewmh_pingclient(Client*);
+int ewmh_prop(Client*, Atom);
+long ewmh_protocols(Window*);
+void ewmh_updateclient(Client*);
+void ewmh_updateclientlist(void);
+void ewmh_updateclients(void);
+void ewmh_updatestacking(void);
+void ewmh_updatestate(Client*);
+void ewmh_updateview(void);
+void ewmh_updateviews(void);
+
+/* float.c */
+void float_arrange(Area*);
+void float_attach(Area*, Frame*);
+void float_detach(Frame*);
+void float_resizeframe(Frame*, Rectangle);
+Vector_rect* unique_rects(Vector_rect*, Rectangle);
+Rectangle max_rect(Vector_rect*);
+
+/* frame.c */
+Frame* frame_create(Client*, View*);
+int frame_delta_h(void);
+void frame_draw(Frame*);
+void frame_draw_all(void);
+void frame_focus(Frame*);
+uint frame_idx(Frame*);
+void frame_insert(Frame*, Frame *pos);
+void frame_remove(Frame*);
+void frame_resize(Frame*, Rectangle);
+bool frame_restack(Frame*, Frame*);
+void frame_swap(Frame*, Frame*);
+int ingrabbox_p(Frame*, int x, int y);
+void move_focus(Frame*, Frame*);
+Rectangle constrain(Rectangle, int);
+Rectangle frame_client2rect(Client*, Rectangle, bool);
+WinHints frame_gethints(Frame*);
+Rectangle frame_hints(Frame*, Rectangle, Align);
+Rectangle frame_rect2client(Client*, Rectangle, bool);
+
+/* fs.c */
+void fs_attach(Ixp9Req*);
+void fs_clunk(Ixp9Req*);
+void fs_create(Ixp9Req*);
+void fs_flush(Ixp9Req*);
+void fs_freefid(Fid*);
+void fs_open(Ixp9Req*);
+void fs_read(Ixp9Req*);
+void fs_remove(Ixp9Req*);
+void fs_stat(Ixp9Req*);
+void fs_walk(Ixp9Req*);
+void fs_write(Ixp9Req*);
+void event(const char*, ...);
+
+/* geom.c */
+Align get_sticky(Rectangle src, Rectangle dst);
+Cursor quad_cursor(Align);
+Align quadrant(Rectangle, Point);
+bool rect_contains_p(Rectangle, Rectangle);
+bool rect_haspoint_p(Point, Rectangle);
+bool rect_intersect_p(Rectangle, Rectangle);
+Rectangle rect_intersection(Rectangle, Rectangle);
+
+/* key.c */
+void init_lock_keys(void);
+void kpress(XWindow, ulong mod, KeyCode);
+void update_keys(void);
+
+/* main.c */
+void init_screens(void);
+void spawn_command(const char*);
+
+/* map.c */
+void** hash_get(Map*, const char*, bool create);
+void* hash_rm(Map*, const char*);
+void** map_get(Map*, ulong, bool create);
+void* map_rm(Map*, ulong);
+
+/* message.c */
+bool getlong(const char*, long*);
+bool getulong(const char*, ulong*);
+char* message_client(Client*, IxpMsg*);
+char* message_root(void*, IxpMsg*);
+char* message_view(View*, IxpMsg*);
+char* msg_debug(IxpMsg*);
+char* msg_getword(IxpMsg*);
+char* msg_parsecolors(IxpMsg*, CTuple*);
+char* msg_selectarea(Area*, IxpMsg*);
+char* msg_sendclient(View*, IxpMsg*, bool swap);
+char* readctl_client(Client*);
+char* readctl_root(void);
+char* readctl_view(View*);
+Area* strarea(View*, ulong, const char*);
+void warning(const char*, ...);
+/* debug */
+void debug(int, const char*, ...);
+void dprint(const char*, ...);
+void dwrite(int, void*, int, bool);
+bool setdebug(int);
+void vdebug(int, const char*, va_list);
+
+/* mouse.c */
+Window* constraintwin(Rectangle);
+void destroyconstraintwin(Window*);
+void grab_button(XWindow, uint button, ulong mod);
+void mouse_checkresize(Frame*, Point, bool);
+void mouse_movegrabbox(Client*, bool);
+void mouse_resize(Client*, Align, bool);
+void mouse_resizecol(Divide*);
+bool readmotion(Point*);
+int readmouse(Point*, uint*);
+Align snap_rect(const Rectangle *rects, int num, Rectangle *current, Align *mask, int snapw);
+
+/* print.c */
+int Ffmt(Fmt*);
+
+/* printevent.c */
+void printevent(XEvent*);
+
+/* root.c */
+void root_init(void);
+
+/* screen.c */
+void* findthing(Rectangle, int, Vector_ptr*, Rectangle(*)(void*), bool);
+int ownerscreen(Rectangle);
+
+/* rule.c */
+void trim(char *str, const char *chars);
+void update_rules(Rule**, const char*);
+
+/* view.c */
+void view_arrange(View*);
+void view_attach(View*, Frame*);
+View* view_create(const char*);
+void view_destroy(View*);
+void view_detach(Frame*);
+Area* view_findarea(View*, int, int, bool);
+void view_focus(WMScreen*, View*);
+bool view_fullscreen_p(View*, int);
+char* view_index(View*);
+void view_init(View*, int iscreen);
+char** view_names(void);
+uint view_newcolwidth(View*, int i);
+void view_restack(View*);
+void view_scale(View*, int, int);
+Client* view_selclient(View*);
+void view_select(const char*);
+void view_update(View*);
+void view_update_all(void);
+void view_update_rect(View*);
+Rectangle* view_rects(View*, uint *num, Frame *ignore);
+
+/* _util.c */
+void backtrace(char*);
+void closeexec(int);
+char** comm(int, char**, char**);
+int doublefork(void);
+void grep(char**, Reprog*, int);
+char* join(char**, char*);
+char* pathsearch(const char*, const char*, bool);
+void refree(Regex*);
+void reinit(Regex*, char*);
+int strlcatprint(char*, int, const char*, ...);
+int spawn3(int[3], const char*, char*[]);
+int spawn3l(int[3], const char*, ...);
+void uniq(char**);
+int unquote(char*, char*[], int);
+
+/* utf.c */
+char* toutf8(const char*);
+char* toutf8n(const char*, size_t);
+
+/* xdnd.c */
+int xdnd_clientmessage(XClientMessageEvent*);
+void xdnd_initwindow(Window*);
+
+/* xext.c */
+void randr_event(XEvent*);
+bool render_argb_p(Visual*);
+void xext_event(XEvent*);
+void xext_init(void);
+Rectangle* xinerama_screens(int*);
+
diff --git a/cmd/wmii/frame.c b/cmd/wmii/frame.c
new file mode 100644
index 0000000..6bedd82
--- /dev/null
+++ b/cmd/wmii/frame.c
@@ -0,0 +1,681 @@
+/* Copyright ©2006-2010 Kris Maglione <maglione.k at Gmail>
+ * See LICENSE file for license details.
+ */
+#include "dat.h"
+#include <math.h>
+#include "fns.h"
+
+uint
+frame_idx(Frame *f) {
+ Frame *fp;
+ uint i;
+
+ fp = f->area->frame;
+ for(i = 1; fp != f; fp = fp->anext)
+ i++;
+ return i;
+}
+
+Frame*
+frame_create(Client *c, View *v) {
+ static ushort id = 1;
+ Frame *f;
+
+ f = emallocz(sizeof *f);
+ f->id = id++;
+ f->client = c;
+ f->view = v;
+
+ if(c->sel) {
+ f->floatr = c->sel->floatr;
+ f->r = c->sel->r;
+ }else {
+ f->r = client_grav(c, c->r);
+ f->floatr = f->r;
+ c->sel = f;
+ }
+ f->collapsed = false;
+ f->screen = -1;
+ f->oldarea = -1;
+ f->oldscreen = -1;
+
+ return f;
+}
+
+void
+frame_remove(Frame *f) {
+ Area *a;
+
+ a = f->area;
+ if(f->aprev)
+ f->aprev->anext = f->anext;
+ if(f->anext)
+ f->anext->aprev = f->aprev;
+ if(f == a->frame)
+ a->frame = f->anext;
+
+ if(a->floating) {
+ if(f->sprev)
+ f->sprev->snext = f->snext;
+ if(f->snext)
+ f->snext->sprev = f->sprev;
+ if(f == a->stack)
+ a->stack = f->snext;
+ }
+ f->anext = f->aprev = f->snext = f->sprev = nil;
+}
+
+void
+frame_insert(Frame *f, Frame *pos) {
+ Area *a;
+
+ a = f->area;
+
+ if(pos) {
+ assert(pos != f);
+ f->aprev = pos;
+ f->anext = pos->anext;
+ }else {
+ assert(f->area->frame != f);
+ f->anext = f->area->frame;
+ f->area->frame = f;
+ }
+ if(f->aprev)
+ f->aprev->anext = f;
+ if(f->anext)
+ f->anext->aprev = f;
+
+ if(a->floating) {
+ assert(f->sprev == nil);
+ frame_restack(f, nil);
+ }
+}
+
+bool
+frame_restack(Frame *f, Frame *above) {
+ Client *c;
+ Frame *fp;
+ Area *a;
+
+ c = f->client;
+ a = f->area;
+ if(!a->floating)
+ return false;
+ if(f == above)
+ return false;
+
+ if(above == nil && !(c->w.ewmh.type & TypeDock))
+ for(fp=a->stack; fp; fp=fp->snext)
+ if(fp->client->w.ewmh.type & TypeDock)
+ above = fp;
+ else
+ break;
+
+ if(f->sprev || f == a->stack)
+ if(f->sprev == above)
+ return false;
+
+ if(f->sprev)
+ f->sprev->snext = f->snext;
+ else if(f->snext)
+ a->stack = f->snext;
+ if(f->snext)
+ f->snext->sprev = f->sprev;
+
+ f->sprev = above;
+ if(above == nil) {
+ f->snext = a->stack;
+ a->stack = f;
+ }
+ else {
+ f->snext = above->snext;
+ above->snext = f;
+ }
+ if(f->snext)
+ f->snext->sprev = f;
+ assert(f->snext != f && f->sprev != f);
+
+ return true;
+}
+
+/* Handlers */
+static void
+bup_event(Window *w, XButtonEvent *e) {
+ if((e->state & def.mod) != def.mod)
+ XAllowEvents(display, ReplayPointer, e->time);
+ else
+ XUngrabPointer(display, e->time);
+ event("ClientClick %C %d\n", w->aux, e->button);
+}
+
+static void
+bdown_event(Window *w, XButtonEvent *e) {
+ Frame *f;
+ Client *c;
+
+ c = w->aux;
+ f = c->sel;
+
+ if((e->state & def.mod) == def.mod) {
+ switch(e->button) {
+ case Button1:
+ focus(c, false);
+ mouse_resize(c, Center, true);
+ break;
+ case Button2:
+ frame_restack(f, nil);
+ view_restack(f->view);
+ focus(c, false);
+ grabpointer(c->framewin, nil, cursor[CurNone], ButtonReleaseMask);
+ break;
+ case Button3:
+ focus(c, false);
+ mouse_resize(c, quadrant(f->r, Pt(e->x_root, e->y_root)), true);
+ break;
+ default:
+ XAllowEvents(display, ReplayPointer, e->time);
+ break;
+ }
+ }else {
+ if(e->button == Button1) {
+ if(!e->subwindow) {
+ frame_restack(f, nil);
+ view_restack(f->view);
+ mouse_checkresize(f, Pt(e->x, e->y), true);
+ }
+
+ if(f->client != selclient())
+ focus(c, false);
+ }
+ if(e->subwindow)
+ XAllowEvents(display, ReplayPointer, e->time);
+ else {
+ /* Ungrab so a menu can receive events before the button is released */
+ XUngrabPointer(display, e->time);
+ sync();
+
+ event("ClientMouseDown %C %d\n", f->client, e->button);
+ }
+ }
+}
+
+static void
+config_event(Window *w, XConfigureEvent *e) {
+
+ USED(w, e);
+}
+
+static void
+enter_event(Window *w, XCrossingEvent *e) {
+ Client *c;
+ Frame *f;
+
+ c = w->aux;
+ f = c->sel;
+ if(disp.focus != c || selclient() != c) {
+ Dprint(DFocus, "enter_notify(f) => [%C]%s%s\n",
+ f->client, f->client->name,
+ ignoreenter == e->serial ? " (ignored)" : "");
+ if(e->detail != NotifyInferior)
+ if(e->serial != ignoreenter && (f->area->floating || !f->collapsed))
+ if(!(c->w.ewmh.type & TypeSplash))
+ focus(f->client, false);
+ }
+ mouse_checkresize(f, Pt(e->x, e->y), false);
+}
+
+static void
+expose_event(Window *w, XExposeEvent *e) {
+ Client *c;
+
+ USED(e);
+
+ c = w->aux;
+ if(c->sel)
+ frame_draw(c->sel);
+ else
+ fprint(2, "Badness: Expose event on a client frame which shouldn't be visible: %C\n",
+ c);
+}
+
+static void
+motion_event(Window *w, XMotionEvent *e) {
+ Client *c;
+
+ c = w->aux;
+ mouse_checkresize(c->sel, Pt(e->x, e->y), false);
+}
+
+Handlers framehandler = {
+ .bup = bup_event,
+ .bdown = bdown_event,
+ .config = config_event,
+ .enter = enter_event,
+ .expose = expose_event,
+ .motion = motion_event,
+};
+
+WinHints
+frame_gethints(Frame *f) {
+ WinHints h;
+ Client *c;
+ Rectangle r;
+ Point d;
+ int minh;
+
+ minh = labelh(def.font);
+
+ c = f->client;
+ h = *c->w.hints;
+
+ r = frame_client2rect(c, ZR, f->area->floating);
+ d = subpt(r.max, r.min);
+
+ if(!f->area->floating && def.incmode == IIgnore)
+ h.inc = Pt(1, 1);
+
+ if(h.min.x < 2*minh)
+ h.min.x = minh + (2*minh) % h.inc.x;
+ if(h.min.y < minh)
+ h.min.y = minh + minh % h.inc.y;
+
+ h.min.x += d.x;
+ h.min.y += d.y;
+ /* Guard against overflow. */
+ h.max.x = max(h.max.x + d.x, h.max.x);
+ h.max.y = max(h.max.y + d.y, h.max.y);
+
+ h.base.x += d.x;
+ h.base.y += d.y;
+ h.baspect.x += d.x;
+ h.baspect.y += d.y;
+
+ h.group = 0;
+ h.grav = ZP;
+ h.gravstatic = 0;
+ h.position = 0;
+ return h;
+}
+
+#define ADJ(PE, ME) \
+ if(c->fullscreen >= 0) \
+ return r; \
+ \
+ if(!floating) { \
+ r.min.x PE 1; \
+ r.min.y PE labelh(def.font); \
+ r.max.x ME 1; \
+ r.max.y ME 1; \
+ }else { \
+ if(!c->borderless) { \
+ r.min.x PE def.border; \
+ r.max.x ME def.border; \
+ r.max.y ME def.border; \
+ } \
+ if(!c->titleless) \
+ r.min.y PE labelh(def.font); \
+ } \
+
+Rectangle
+frame_rect2client(Client *c, Rectangle r, bool floating) {
+
+ ADJ(+=, -=)
+
+ /* Force clients to be at least 1x1 */
+ r.max.x = max(r.max.x, r.min.x+1);
+ r.max.y = max(r.max.y, r.min.y+1);
+ return r;
+}
+
+Rectangle
+frame_client2rect(Client *c, Rectangle r, bool floating) {
+
+ ADJ(-=, +=)
+
+ return r;
+}
+
+#undef ADJ
+
+void
+frame_resize(Frame *f, Rectangle r) {
+ Client *c;
+ Rectangle fr, cr;
+ int collapsed, dx;
+
+ if(btassert("8 full", Dx(r) <= 0 || Dy(r) < 0
+ || Dy(r) == 0 && (!f->area->max || resizing)
+ && !f->collapsed)) {
+ fprint(2, "Frame rect: %R\n", r);
+ r.max.x = min(r.min.x+1, r.max.x);
+ r.max.y = min(r.min.y+1, r.max.y);
+ }
+
+ c = f->client;
+ if(c->fullscreen >= 0) {
+ f->r = screens[c->fullscreen]->r;
+ f->crect = rectsetorigin(f->r, ZP);
+ return;
+ }
+
+ /*
+ if(f->area->floating)
+ f->collapsed = false;
+ */
+
+ fr = frame_hints(f, r, get_sticky(f->r, r));
+ if(f->area->floating && !c->strut)
+ fr = constrain(fr, -1);
+
+ /* Collapse managed frames which are too small */
+ /* XXX. */
+ collapsed = f->collapsed;
+ if(!f->area->floating && f->area->mode == Coldefault) {
+ f->collapsed = false;
+ if(Dy(r) < 2 * labelh(def.font))
+ f->collapsed = true;
+ }
+ if(collapsed != f->collapsed)
+ ewmh_updatestate(c);
+
+ fr.max.x = max(fr.max.x, fr.min.x + 2*labelh(def.font));
+ if(f->collapsed && f->area->floating)
+ fr.max.y = fr.min.y + labelh(def.font);
+
+ cr = frame_rect2client(c, fr, f->area->floating);
+ if(f->area->floating)
+ f->r = fr;
+ else {
+ f->r = r;
+ dx = Dx(r) - Dx(cr);
+ dx -= 2 * (cr.min.x - fr.min.x);
+ cr.min.x += dx / 2;
+ cr.max.x += dx / 2;
+ }
+ f->crect = rectsubpt(cr, f->r.min);
+
+ if(f->area->floating && !f->collapsed)
+ f->floatr = f->r;
+}
+
+static void
+pushlabel(Image *img, Rectangle *rp, char *s, CTuple *col) {
+ Rectangle r;
+ int w;
+
+ w = textwidth(def.font, s) + def.font->height;
+ w = min(w, Dx(*rp) - 30); /* Magic number. */
+ if(w > 0) {
+ r = *rp;
+ rp->max.x -= w;
+ if(0)
+ drawline(img, Pt(rp->max.x, r.min.y+2),
+ Pt(rp->max.x, r.max.y-2),
+ CapButt, 1, col->border);
+ drawstring(img, def.font, r, East,
+ s, col->fg);
+ }
+}
+
+void
+frame_draw(Frame *f) {
+ Rectangle r, fr;
+ Client *c;
+ CTuple *col;
+ Image *img;
+ char *s;
+ uint w;
+ int n, m;
+
+ if(f->view != selview)
+ return;
+ if(f->area == nil) /* Blech. */
+ return;
+
+ c = f->client;
+ img = *c->ibuf;
+ fr = rectsetorigin(c->framewin->r, ZP);
+
+ /* Pick colors. */
+ if(c == selclient() || c == disp.focus)
+ col = &def.focuscolor;
+ else
+ col = &def.normcolor;
+
+ /* Background/border */
+ r = fr;
+ fill(img, r, col->bg);
+ border(img, r, 1, col->border);
+
+ /* Title border */
+ r.max.y = r.min.y + labelh(def.font);
+ border(img, r, 1, col->border);
+
+ f->titlebar = insetrect(r, 3);
+ f->titlebar.max.y += 3;
+
+ /* Odd focus. Unselected, with keyboard focus. */
+ /* Draw a border just inside the titlebar. */
+ if(c != selclient() && c == disp.focus) {
+ border(img, insetrect(r, 1), 1, def.normcolor.bg);
+ border(img, insetrect(r, 2), 1, def.focuscolor.border);
+ }
+
+ /* grabbox */
+ r.min = Pt(2, 2);
+ r.max.y -= 2;
+ r.max.x = r.min.x + Dy(r);
+ f->grabbox = r;
+
+ if(c->urgent)
+ fill(img, r, col->fg);
+ border(img, r, 1, col->border);
+
+ /* Odd focus. Selected, without keyboard focus. */
+ /* Draw a border around the grabbox. */
+ if(c != disp.focus && col == &def.focuscolor)
+ border(img, insetrect(r, -1), 1, def.normcolor.bg);
+
+ /* Draw a border on borderless+titleless selected apps. */
+ if(f->area->floating && c->borderless && c->titleless && !c->fullscreen && c == selclient())
+ setborder(c->framewin, def.border, def.focuscolor.border);
+ else
+ setborder(c->framewin, 0, def.focuscolor.border);
+
+ /* Label */
+ r.min.x = r.max.x;
+ r.max.x = fr.max.x;
+ r.min.y = 0;
+ r.max.y = labelh(def.font);
+ /* Draw count on frames in 'max' columns. */
+ if(f->area->max && !resizing) {
+ /* XXX */
+ n = stack_count(f, &m);
+ s = smprint("%d/%d", m, n);
+ pushlabel(img, &r, s, col);
+ free(s);
+ }
+ /* Label clients with extra tags. */
+ if((s = client_extratags(c))) {
+ pushlabel(img, &r, s, col);
+ free(s);
+ }else /* Make sure floating clients have room for their indicators. */
+ if(c->floating)
+ r.max.x -= Dx(f->grabbox);
+ w = drawstring(img, def.font, r, West,
+ c->name, col->fg);
+
+ /* Draw inner border on floating clients. */
+ if(f->area->floating) {
+ r.min.x = r.min.x + w + 10;
+ r.max.x += Dx(f->grabbox) - 2;
+ r.min.y = f->grabbox.min.y;
+ r.max.y = f->grabbox.max.y;
+ border(img, r, 1, col->border);
+ }
+
+ /* Border increment gaps... */
+ r.min.y = f->crect.min.y;
+ r.min.x = max(1, f->crect.min.x - 1);
+ r.max.x = min(fr.max.x - 1, f->crect.max.x + 1);
+ r.max.y = min(fr.max.y - 1, f->crect.max.y + 1);
+ border(img, r, 1, col->border);
+
+ /* Why? Because some non-ICCCM-compliant apps feel the need to
+ * change the background properties of all of their ancestor windows
+ * in order to implement pseudo-transparency.
+ * What's more, the designers of X11 felt that it would be unfair to
+ * implementers to make it possible to detect, or forbid, such changes.
+ */
+ XSetWindowBackgroundPixmap(display, c->framewin->xid, None);
+
+ copyimage(c->framewin, fr, img, ZP);
+}
+
+void
+frame_draw_all(void) {
+ Client *c;
+
+ for(c=client; c; c=c->next)
+ if(c->sel && c->sel->view == selview)
+ frame_draw(c->sel);
+}
+
+void
+frame_swap(Frame *fa, Frame *fb) {
+ Frame **fp;
+ Client *c;
+
+ if(fa == fb) return;
+
+ for(fp = &fa->client->frame; *fp; fp = &fp[0]->cnext)
+ if(*fp == fa) break;
+ fp[0] = fp[0]->cnext;
+
+ for(fp = &fb->client->frame; *fp; fp = &fp[0]->cnext)
+ if(*fp == fb) break;
+ fp[0] = fp[0]->cnext;
+
+ c = fa->client;
+ fa->client = fb->client;
+ fb->client = c;
+ fb->cnext = c->frame;
+ c->frame = fb;
+
+ c = fa->client;
+ fa->cnext = c->frame;
+ c->frame = fa;
+
+ if(c->sel)
+ view_update(c->sel->view);
+}
+
+void
+move_focus(Frame *old_f, Frame *f) {
+ int noinput;
+
+ noinput = (old_f && old_f->client->noinput) ||
+ (f && f->client->noinput) ||
+ disp.hasgrab != &c_root;
+ if(noinput) {
+ if(old_f)
+ frame_draw(old_f);
+ if(f)
+ frame_draw(f);
+ }
+}
+
+void
+frame_focus(Frame *f) {
+ Frame *old_f, *ff;
+ View *v;
+ Area *a, *old_a;
+
+ v = f->view;
+ a = f->area;
+ old_a = v->sel;
+
+ if(0 && f->collapsed) {
+ for(ff=f; ff->collapsed && ff->anext; ff=ff->anext)
+ ;
+ for(; ff->collapsed && ff->aprev; ff=ff->aprev)
+ ;
+ /* XXX */
+ f->colr.max.y = f->colr.min.y + Dy(ff->colr);
+ ff->colr.max.y = ff->colr.min.y + labelh(def.font);
+ }else if(f->area->mode == Coldefault) {
+ for(; f->collapsed && f->anext; f=f->anext)
+ ;
+ for(; f->collapsed && f->aprev; f=f->aprev)
+ ;
+ }
+
+ old_f = old_a->sel;
+ a->sel = f;
+
+ if(a != old_a)
+ area_focus(f->area);
+ if(old_a != v->oldsel && f != old_f)
+ v->oldsel = nil;
+
+ if(v != selview || a != v->sel || resizing)
+ return;
+
+ move_focus(old_f, f);
+ if(a->floating)
+ float_arrange(a);
+ client_focus(f->client);
+
+ /*
+ if(!a->floating && ((a->mode == Colstack) || (a->mode == Colmax)))
+ */
+ column_arrange(a, false);
+}
+
+int
+frame_delta_h(void) {
+ return def.border + labelh(def.font);
+}
+
+Rectangle
+constrain(Rectangle r, int inset) {
+ WMScreen **sp;
+ WMScreen *s, *sbest;
+ Rectangle isect;
+ Point p;
+ int best, n;
+
+ if(inset < 0)
+ inset = Dy(screen->brect);
+ /*
+ * FIXME: This will cause problems for windows with
+ * D(r) < 2 * inset
+ */
+
+ SET(best);
+ sbest = nil;
+ for(sp=screens; (s = *sp); sp++) {
+ if (!screen->showing)
+ continue;
+ isect = rect_intersection(r, insetrect(s->r, inset));
+ if(Dx(isect) >= 0 && Dy(isect) >= 0)
+ return r;
+ if(Dx(isect) <= 0 && Dy(isect) <= 0)
+ n = max(Dx(isect), Dy(isect));
+ else
+ n = min(Dx(isect), Dy(isect));
+ if(!sbest || n > best) {
+ sbest = s;
+ best = n;
+ }
+ }
+
+ isect = insetrect(sbest->r, inset);
+ p = ZP;
+ p.x -= min(r.max.x - isect.min.x, 0);
+ p.x -= max(r.min.x - isect.max.x, 0);
+ p.y -= min(r.max.y - isect.min.y, 0);
+ p.y -= max(r.min.y - isect.max.y, 0);
+ return rectaddpt(r, p);
+}
+
diff --git a/cmd/wmii/fs.c b/cmd/wmii/fs.c
new file mode 100644
index 0000000..5fdfca4
--- /dev/null
+++ b/cmd/wmii/fs.c
@@ -0,0 +1,725 @@
+/* Copyright ©2006-2010 Kris Maglione <maglione.k at Gmail>
+ * See LICENSE file for license details.
+ */
+#include "dat.h"
+#include <ctype.h>
+#include <stdarg.h>
+#include <time.h>
+#include <unistd.h>
+#include "fns.h"
+
+typedef union IxpFileIdU IxpFileIdU;
+union IxpFileIdU {
+ Bar* bar;
+ Bar** bar_p;
+ CTuple* col;
+ Client* client;
+ Ruleset* rule;
+ View* view;
+ char* buf;
+ void* ref;
+};
+
+#include <ixp_srvutil.h>
+
+static IxpPending events;
+static IxpPending pdebug[NDebugOpt];
+
+/* Constants */
+enum { /* Dirs */
+ FsDBars,
+ FsDClient,
+ FsDClients,
+ FsDDebug,
+ FsDTag,
+ FsDTags,
+ FsRoot,
+ /* Files */
+ FsFBar,
+ FsFCctl,
+ FsFClabel,
+ FsFColRules,
+ FsFCtags,
+ FsFDebug,
+ FsFEvent,
+ FsFKeys,
+ FsFRctl,
+ FsFTagRules,
+ FsFTctl,
+ FsFTindex,
+ FsFprops,
+};
+
+/* Error messages */
+static char
+ Enoperm[] = "permission denied",
+ Enofile[] = "file not found",
+ Ebadvalue[] = "bad value",
+ Einterrupted[] = "interrupted";
+
+/* Macros */
+#define QID(t, i) (((vlong)((t)&0xFF)<<32)|((i)&0xFFFFFFFF))
+
+/* Global Vars */
+/***************/
+Ixp9Srv p9srv = {
+ .open= fs_open,
+ .walk= fs_walk,
+ .read= fs_read,
+ .stat= fs_stat,
+ .write= fs_write,
+ .clunk= fs_clunk,
+ .flush= fs_flush,
+ .attach=fs_attach,
+ .create=fs_create,
+ .remove=fs_remove,
+ .freefid=fs_freefid
+};
+
+/* ad-hoc file tree. Empty names ("") indicate dynamic entries to be filled
+ * in by lookup_file
+ */
+static IxpDirtab
+dirtab_root[]= {{".", QTDIR, FsRoot, 0500|DMDIR },
+ {"rbar", QTDIR, FsDBars, 0700|DMDIR },
+ {"lbar", QTDIR, FsDBars, 0700|DMDIR },
+ {"debug", QTDIR, FsDDebug, 0500|DMDIR, FLHide },
+ {"client", QTDIR, FsDClients, 0500|DMDIR },
+ {"tag", QTDIR, FsDTags, 0500|DMDIR },
+ {"ctl", QTAPPEND, FsFRctl, 0600|DMAPPEND },
+ {"colrules", QTFILE, FsFColRules, 0600 },
+ {"event", QTFILE, FsFEvent, 0600 },
+ {"keys", QTFILE, FsFKeys, 0600 },
+ {"tagrules", QTFILE, FsFTagRules, 0600 },
+ {nil}},
+dirtab_clients[]={{".", QTDIR, FsDClients, 0500|DMDIR },
+ {"", QTDIR, FsDClient, 0500|DMDIR },
+ {nil}},
+dirtab_client[]= {{".", QTDIR, FsDClient, 0500|DMDIR },
+ {"ctl", QTAPPEND, FsFCctl, 0600|DMAPPEND },
+ {"label", QTFILE, FsFClabel, 0600 },
+ {"tags", QTFILE, FsFCtags, 0600 },
+ {"props", QTFILE, FsFprops, 0400 },
+ {nil}},
+dirtab_debug[]= {{".", QTDIR, FsDDebug, 0500|DMDIR, FLHide },
+ {"", QTFILE, FsFDebug, 0400 },
+ {nil}},
+dirtab_bars[]= {{".", QTDIR, FsDBars, 0700|DMDIR },
+ {"", QTFILE, FsFBar, 0600 },
+ {nil}},
+dirtab_tags[]= {{".", QTDIR, FsDTags, 0500|DMDIR },
+ {"", QTDIR, FsDTag, 0500|DMDIR },
+ {nil}},
+dirtab_tag[]= {{".", QTDIR, FsDTag, 0500|DMDIR },
+ {"ctl", QTAPPEND, FsFTctl, 0600|DMAPPEND },
+ {"index", QTFILE, FsFTindex, 0400 },
+ {nil}};
+static IxpDirtab* dirtab[] = {
+ [FsRoot] = dirtab_root,
+ [FsDBars] = dirtab_bars,
+ [FsDClients] = dirtab_clients,
+ [FsDClient] = dirtab_client,
+ [FsDDebug] = dirtab_debug,
+ [FsDTags] = dirtab_tags,
+ [FsDTag] = dirtab_tag,
+};
+typedef char* (*MsgFunc)(void*, IxpMsg*);
+
+void
+event(const char *format, ...) {
+ va_list ap;
+
+ va_start(ap, format);
+ vsnprint(buffer, sizeof buffer, format, ap);
+ va_end(ap);
+
+ ixp_pending_write(&events, buffer, strlen(buffer));
+}
+
+static int dflags;
+
+bool
+setdebug(int flag) {
+ dflags = flag;
+ return true;
+}
+
+void
+vdebug(int flag, const char *fmt, va_list ap) {
+ char *s;
+
+ if(flag == 0)
+ flag = dflags;
+
+ if(!((debugflag|debugfile) & flag))
+ return;
+
+ s = vsmprint(fmt, ap);
+ dwrite(flag, s, strlen(s), false);
+ free(s);
+}
+
+void
+debug(int flag, const char *fmt, ...) {
+ va_list ap;
+
+ va_start(ap, fmt);
+ vdebug(flag, fmt, ap);
+ va_end(ap);
+}
+
+void
+dprint(const char *fmt, ...) {
+ va_list ap;
+
+ va_start(ap, fmt);
+ vdebug(0, fmt, ap);
+ va_end(ap);
+}
+
+void
+dwrite(int flag, void *buf, int n, bool always) {
+ int i;
+
+ if(flag == 0)
+ flag = dflags;
+
+ if(always || debugflag&flag)
+ write(2, buf, n);
+
+ if(debugfile&flag)
+ for(i=0; i < nelem(pdebug); i++)
+ if(flag & (1<<i))
+ ixp_pending_write(pdebug+i, buf, n);
+}
+
+static uint fs_size(IxpFileId*);
+
+static void
+dostat(Stat *s, IxpFileId *f) {
+ s->type = 0;
+ s->dev = 0;
+ s->qid.path = QID(f->tab.type, f->id);
+ s->qid.version = 0;
+ s->qid.type = f->tab.qtype;
+ s->mode = f->tab.perm;
+ s->atime = time(nil);
+ s->mtime = s->atime;
+ s->length = fs_size(f);;
+ s->name = f->tab.name;
+ s->uid = user;
+ s->gid = user;
+ s->muid = user;
+}
+
+/*
+ * All lookups and directory organization should be performed through
+ * lookup_file, mostly through the dirtab[] tree.
+ */
+static IxpFileId*
+lookup_file(IxpFileId *parent, char *name)
+{
+ IxpFileId *ret, *file, **last;
+ IxpDirtab *dir;
+ Client *c;
+ View *v;
+ Bar *b;
+ uint id;
+ int i;
+
+
+ if(!(parent->tab.perm & DMDIR))
+ return nil;
+ dir = dirtab[parent->tab.type];
+ last = &ret;
+ ret = nil;
+ for(; dir->name; dir++) {
+# define push_file(nam) \
+ file = ixp_srv_getfile(); \
+ *last = file; \
+ last = &file->next; \
+ file->tab = *dir; \
+ file->tab.name = estrdup(nam)
+ /* Dynamic dirs */
+ if(dir->name[0] == '\0') {
+ switch(parent->tab.type) {
+ case FsDClients:
+ if(!name || !strcmp(name, "sel")) {
+ if((c = selclient())) {
+ push_file("sel");
+ file->volatil = true;
+ file->p.client = c;
+ file->id = c->w.xid;
+ file->index = c->w.xid;
+ }
+ if(name)
+ goto LastItem;
+ }
+ SET(id);
+ if(name) {
+ id = (uint)strtol(name, &name, 16);
+ if(*name)
+ goto NextItem;
+ }
+ for(c=client; c; c=c->next) {
+ if(!name || c->w.xid == id) {
+ push_file(sxprint("%C", c));
+ file->volatil = true;
+ file->p.client = c;
+ file->id = c->w.xid;
+ file->index = c->w.xid;
+ assert(file->tab.name);
+ if(name)
+ goto LastItem;
+ }
+ }
+ break;
+ case FsDDebug:
+ for(i=0; i < nelem(pdebug); i++)
+ if(!name || !strcmp(name, debugtab[i])) {
+ push_file(debugtab[i]);
+ file->id = i;
+ if(name)
+ goto LastItem;
+ }
+ break;
+ case FsDTags:
+ if(!name || !strcmp(name, "sel")) {
+ if(selview) {
+ push_file("sel");
+ file->volatil = true;
+ file->p.view = selview;
+ file->id = selview->id;
+ }
+ if(name)
+ goto LastItem;
+ }
+ for(v=view; v; v=v->next) {
+ if(!name || !strcmp(name, v->name)) {
+ push_file(v->name);
+ file->volatil = true;
+ file->p.view = v;
+ file->id = v->id;
+ if(name)
+ goto LastItem;
+ }
+ }
+ break;
+ case FsDBars:
+ for(b=*parent->p.bar_p; b; b=b->next) {
+ if(!name || !strcmp(name, b->name)) {
+ push_file(b->name);
+ file->volatil = true;
+ file->p.bar = b;
+ file->id = b->id;
+ if(name)
+ goto LastItem;
+ }
+ }
+ break;
+ }
+ }else /* Static dirs */
+ if(!name && !(dir->flags & FLHide) || name && !strcmp(name, dir->name)) {
+ push_file(file->tab.name);
+ file->id = 0;
+ file->p.ref = parent->p.ref;
+ file->index = parent->index;
+ /* Special considerations: */
+ switch(file->tab.type) {
+ case FsDBars:
+ if(!strcmp(file->tab.name, "lbar"))
+ file->p.bar_p = &screen[0].bar[BLeft];
+ else
+ file->p.bar_p = &screen[0].bar[BRight];
+ file->id = (int)(uintptr_t)file->p.bar_p;
+ break;
+ case FsFColRules:
+ file->p.rule = &def.colrules;
+ break;
+ case FsFTagRules:
+ file->p.rule = &def.tagrules;
+ break;
+ }
+ if(name)
+ goto LastItem;
+ }
+ NextItem:
+ continue;
+# undef push_file
+ }
+LastItem:
+ *last = nil;
+ return ret;
+}
+
+/* Service Functions */
+void
+fs_attach(Ixp9Req *r) {
+ IxpFileId *f;
+
+ f = ixp_srv_getfile();
+ f->tab = dirtab[FsRoot][0];
+ f->tab.name = estrdup("/");
+ r->fid->aux = f;
+ r->fid->qid.type = f->tab.qtype;
+ r->fid->qid.path = QID(f->tab.type, 0);
+ r->ofcall.rattach.qid = r->fid->qid;
+ respond(r, nil);
+}
+
+void
+fs_walk(Ixp9Req *r) {
+
+ ixp_srv_walkandclone(r, lookup_file);
+}
+
+static uint
+fs_size(IxpFileId *f) {
+ switch(f->tab.type) {
+ default:
+ return 0;
+ case FsFColRules:
+ case FsFTagRules:
+ return f->p.rule->size;
+ case FsFKeys:
+ return def.keyssz;
+ case FsFCtags:
+ return strlen(f->p.client->tags);
+ case FsFClabel:
+ return strlen(f->p.client->name);
+ case FsFprops:
+ return strlen(f->p.client->props);
+ }
+}
+
+void
+fs_stat(Ixp9Req *r) {
+ IxpMsg m;
+ Stat s;
+ int size;
+ char *buf;
+ IxpFileId *f;
+
+ f = r->fid->aux;
+
+ if(!ixp_srv_verifyfile(f, lookup_file)) {
+ respond(r, Enofile);
+ return;
+ }
+
+ dostat(&s, f);
+ size = ixp_sizeof_stat(&s);
+ r->ofcall.rstat.nstat = size;
+ buf = emallocz(size);
+
+ m = ixp_message(buf, size, MsgPack);
+ ixp_pstat(&m, &s);
+
+ r->ofcall.rstat.stat = (uchar*)m.data;
+ respond(r, nil);
+}
+
+void
+fs_read(Ixp9Req *r) {
+ char *buf;
+ IxpFileId *f;
+ int n;
+
+ f = r->fid->aux;
+
+ if(!ixp_srv_verifyfile(f, lookup_file)) {
+ respond(r, Enofile);
+ return;
+ }
+
+ if(f->tab.perm & DMDIR && f->tab.perm & 0400) {
+ ixp_srv_readdir(r, lookup_file, dostat);
+ return;
+ }
+ else{
+ if(f->pending) {
+ ixp_pending_respond(r);
+ return;
+ }
+ switch(f->tab.type) {
+ case FsFprops:
+ ixp_srv_readbuf(r, f->p.client->props, strlen(f->p.client->props));
+ respond(r, nil);
+ return;
+ case FsFColRules:
+ case FsFTagRules:
+ ixp_srv_readbuf(r, f->p.rule->string, f->p.rule->size);
+ respond(r, nil);
+ return;
+ case FsFKeys:
+ ixp_srv_readbuf(r, def.keys, def.keyssz);
+ respond(r, nil);
+ return;
+ case FsFCtags:
+ ixp_srv_readbuf(r, f->p.client->tags, strlen(f->p.client->tags));
+ respond(r, nil);
+ return;
+ case FsFClabel:
+ ixp_srv_readbuf(r, f->p.client->name, strlen(f->p.client->name));
+ respond(r, nil);
+ return;
+ case FsFBar:
+ ixp_srv_readbuf(r, f->p.bar->buf, strlen(f->p.bar->buf));
+ respond(r, nil);
+ return;
+ case FsFRctl:
+ buf = readctl_root();
+ ixp_srv_readbuf(r, buf, strlen(buf));
+ respond(r, nil);
+ return;
+ case FsFCctl:
+ buf = readctl_client(f->p.client);
+ ixp_srv_readbuf(r, buf, strlen(buf));
+ respond(r, nil);
+ return;
+ case FsFTindex:
+ buf = view_index(f->p.view);
+ ixp_srv_readbuf(r, buf, strlen(buf));
+ respond(r, nil);
+ return;
+ case FsFTctl:
+ buf = readctl_view(f->p.view);
+ n = strlen(buf);
+ ixp_srv_readbuf(r, buf, n);
+ respond(r, nil);
+ return;
+ }
+ }
+ /* This should not be called if the file is not open for reading. */
+ die("Read called on an unreadable file");
+}
+
+void
+fs_write(Ixp9Req *r) {
+ MsgFunc mf;
+ IxpFileId *f;
+ char *errstr;
+ char *p;
+ uint i;
+
+ if(r->ifcall.io.count == 0) {
+ respond(r, nil);
+ return;
+ }
+ f = r->fid->aux;
+
+ if(!ixp_srv_verifyfile(f, lookup_file)) {
+ respond(r, Enofile);
+ return;
+ }
+
+ switch(f->tab.type) {
+ case FsFColRules:
+ case FsFTagRules:
+ ixp_srv_writebuf(r, &f->p.rule->string, &f->p.rule->size, 0);
+ respond(r, nil);
+ return;
+ case FsFKeys:
+ ixp_srv_writebuf(r, &def.keys, &def.keyssz, 0);
+ respond(r, nil);
+ return;
+ case FsFClabel:
+ ixp_srv_data2cstring(r);
+ utfecpy(f->p.client->name,
+ f->p.client->name+sizeof(client->name),
+ r->ifcall.io.data);
+ frame_draw(f->p.client->sel);
+ update_class(f->p.client);
+ r->ofcall.io.count = r->ifcall.io.count;
+ respond(r, nil);
+ return;
+ case FsFCtags:
+ ixp_srv_data2cstring(r);
+ client_applytags(f->p.client, r->ifcall.io.data);
+ r->ofcall.io.count = r->ifcall.io.count;
+ respond(r, nil);
+ return;
+ case FsFBar:
+ i = strlen(f->p.bar->buf);
+ p = f->p.bar->buf;
+ ixp_srv_writebuf(r, &p, &i, 279);
+ bar_load(f->p.bar);
+ r->ofcall.io.count = i - r->ifcall.io.offset;
+ respond(r, nil);
+ return;
+ case FsFCctl:
+ mf = (MsgFunc)message_client;
+ goto msg;
+ case FsFTctl:
+ mf = (MsgFunc)message_view;
+ goto msg;
+ case FsFRctl:
+ mf = (MsgFunc)message_root;
+ goto msg;
+ msg:
+ errstr = ixp_srv_writectl(r, mf);
+ r->ofcall.io.count = r->ifcall.io.count;
+ respond(r, errstr);
+ return;
+ case FsFEvent:
+ if(r->ifcall.io.data[r->ifcall.io.count-1] == '\n')
+ event("%.*s", (int)r->ifcall.io.count, r->ifcall.io.data);
+ else
+ event("%.*s\n", (int)r->ifcall.io.count, r->ifcall.io.data);
+ r->ofcall.io.count = r->ifcall.io.count;
+ respond(r, nil);
+ return;
+ }
+ /*
+ /* This should not be called if the file is not open for writing. */
+ die("Write called on an unwritable file");
+}
+
+void
+fs_open(Ixp9Req *r) {
+ IxpFileId *f;
+
+ f = r->fid->aux;
+
+ if(!ixp_srv_verifyfile(f, lookup_file)) {
+ respond(r, Enofile);
+ return;
+ }
+
+ switch(f->tab.type) {
+ case FsFEvent:
+ ixp_pending_pushfid(&events, r->fid);
+ break;
+ case FsFDebug:
+ ixp_pending_pushfid(pdebug+f->id, r->fid);
+ debugfile |= 1<<f->id;
+ break;
+ }
+
+ if((r->ifcall.topen.mode&3) == OEXEC
+ || (r->ifcall.topen.mode&3) != OREAD && !(f->tab.perm & 0200)
+ || (r->ifcall.topen.mode&3) != OWRITE && !(f->tab.perm & 0400)
+ || (r->ifcall.topen.mode & ~(3|OAPPEND|OTRUNC)))
+ respond(r, Enoperm);
+ else
+ respond(r, nil);
+}
+
+void
+fs_create(Ixp9Req *r) {
+ IxpFileId *f;
+
+ f = r->fid->aux;
+
+ switch(f->tab.type) {
+ default:
+ respond(r, Enoperm);
+ return;
+ case FsDBars:
+ if(!strlen(r->ifcall.tcreate.name)) {
+ respond(r, Ebadvalue);
+ return;
+ }
+ bar_create(f->p.bar_p, r->ifcall.tcreate.name);
+ f = lookup_file(f, r->ifcall.tcreate.name);
+ if(!f) {
+ respond(r, Enofile);
+ return;
+ }
+ r->ofcall.ropen.qid.type = f->tab.qtype;
+ r->ofcall.ropen.qid.path = QID(f->tab.type, f->id);
+ f->next = r->fid->aux;
+ r->fid->aux = f;
+ respond(r, nil);
+ break;
+ }
+}
+
+void
+fs_remove(Ixp9Req *r) {
+ IxpFileId *f;
+ WMScreen *s;
+
+ f = r->fid->aux;
+ if(!ixp_srv_verifyfile(f, lookup_file)) {
+ respond(r, Enofile);
+ return;
+ }
+
+
+ switch(f->tab.type) {
+ default:
+ respond(r, Enoperm);
+ return;
+ case FsFBar:
+ s = f->p.bar->screen;
+ bar_destroy(f->next->p.bar_p, f->p.bar);
+ bar_draw(s);
+ respond(r, nil);
+ break;
+ }
+}
+
+void
+fs_clunk(Ixp9Req *r) {
+ IxpFileId *f;
+
+ f = r->fid->aux;
+ if(!ixp_srv_verifyfile(f, lookup_file)) {
+ respond(r, nil);
+ return;
+ }
+
+ if(f->pending) {
+ /* Should probably be in freefid */
+ if(ixp_pending_clunk(r)) {
+ if(f->tab.type == FsFDebug)
+ debugfile &= ~(1<<f->id);
+ }
+ return;
+ }
+
+ switch(f->tab.type) {
+ case FsFColRules:
+ update_rules(&f->p.rule->rule, f->p.rule->string);
+ break;
+ case FsFTagRules:
+ update_rules(&f->p.rule->rule, f->p.rule->string);
+ /*
+ for(c=client; c; c=c->next)
+ apply_rules(c);
+ view_update_all();
+ */
+ break;
+ case FsFKeys:
+ update_keys();
+ break;
+ }
+ respond(r, nil);
+}
+
+void
+fs_flush(Ixp9Req *r) {
+ Ixp9Req *or;
+ IxpFileId *f;
+
+ or = r->oldreq;
+ f = or->fid->aux;
+ if(f->pending)
+ ixp_pending_flush(r);
+ /* else die() ? */
+ respond(r->oldreq, Einterrupted);
+ respond(r, nil);
+}
+
+void
+fs_freefid(Fid *f) {
+ IxpFileId *id, *tid;
+
+ tid = f->aux;
+ while((id = tid)) {
+ tid = id->next;
+ ixp_srv_freefile(id);
+ }
+}
+
diff --git a/cmd/wmii/geom.c b/cmd/wmii/geom.c
new file mode 100644
index 0000000..464eb67
--- /dev/null
+++ b/cmd/wmii/geom.c
@@ -0,0 +1,94 @@
+/* Copyright ©2006-2010 Kris Maglione <maglione.k at Gmail>
+ * See LICENSE file for license details.
+ */
+#include "dat.h"
+#include "fns.h"
+
+bool
+rect_haspoint_p(Point pt, Rectangle r) {
+ return (pt.x >= r.min.x) && (pt.x < r.max.x)
+ && (pt.y >= r.min.y) && (pt.y < r.max.y);
+}
+
+bool
+rect_intersect_p(Rectangle r, Rectangle r2) {
+ return r.min.x <= r2.max.x
+ && r.max.x >= r2.min.x
+ && r.min.y <= r2.max.y
+ && r.max.y >= r2.min.y;
+}
+
+Rectangle
+rect_intersection(Rectangle r, Rectangle r2) {
+ Rectangle ret;
+
+ /* ret != canonrect(ret) ≡ no intersection. */
+ ret.min.x = max(r.min.x, r2.min.x);
+ ret.max.x = min(r.max.x, r2.max.x);
+ ret.min.y = max(r.min.y, r2.min.y);
+ ret.max.y = min(r.max.y, r2.max.y);
+ return ret;
+}
+
+bool
+rect_contains_p(Rectangle r, Rectangle r2) {
+ return r2.min.x >= r.min.x
+ && r2.max.x <= r.max.x
+ && r2.min.y >= r.min.y
+ && r2.max.y <= r.max.y;
+}
+
+Align
+quadrant(Rectangle r, Point pt) {
+ Align ret;
+
+ pt = subpt(pt, r.min);
+ ret = 0;
+
+ if(pt.x >= Dx(r) * .5)
+ ret |= East;
+ if(pt.x <= Dx(r) * .5)
+ ret |= West;
+ if(pt.y <= Dy(r) * .5)
+ ret |= North;
+ if(pt.y >= Dy(r) * .5)
+ ret |= South;
+
+ return ret;
+}
+
+Cursor
+quad_cursor(Align align) {
+ switch(align) {
+ case NEast: return cursor[CurNECorner];
+ case NWest: return cursor[CurNWCorner];
+ case SEast: return cursor[CurSECorner];
+ case SWest: return cursor[CurSWCorner];
+ case South:
+ case North: return cursor[CurDVArrow];
+ case East:
+ case West: return cursor[CurDHArrow];
+ default: return cursor[CurMove];
+ }
+}
+
+Align
+get_sticky(Rectangle src, Rectangle dst) {
+ Align corner;
+
+ corner = 0;
+ if(src.min.x != dst.min.x
+ && src.max.x == dst.max.x)
+ corner |= East;
+ else
+ corner |= West;
+
+ if(src.min.y != dst.min.y
+ && src.max.y == dst.max.y)
+ corner |= South;
+ else
+ corner |= North;
+
+ return corner;
+}
+
diff --git a/cmd/wmii/key.c b/cmd/wmii/key.c
new file mode 100644
index 0000000..f2c3471
--- /dev/null
+++ b/cmd/wmii/key.c
@@ -0,0 +1,244 @@
+/* Copyright ©2006-2010 Kris Maglione <fbsdaemon at Gmail>
+ * Copyright ©2004-2006 Anselm R. Garbe <garbeam at gmail dot com>
+ * See LICENSE file for license details.
+ */
+#include "dat.h"
+#include <X11/keysym.h>
+#include "fns.h"
+
+void
+init_lock_keys(void) {
+ static int masks[] = {
+ ShiftMask, LockMask, ControlMask, Mod1Mask, Mod2Mask,
+ Mod3Mask, Mod4Mask, Mod5Mask
+ };
+ XModifierKeymap *modmap;
+ KeyCode numlock;
+ int i, max;
+
+ numlock_mask = 0;
+ modmap = XGetModifierMapping(display);
+ numlock = keycode("Num_Lock");
+ if(numlock)
+ if(modmap && modmap->max_keypermod > 0) {
+ max = nelem(masks) * modmap->max_keypermod;
+ for(i = 0; i < max; i++)
+ if(modmap->modifiermap[i] == numlock)
+ numlock_mask = masks[i / modmap->max_keypermod];
+ }
+ XFreeModifiermap(modmap);
+ valid_mask = 255 & ~(numlock_mask | LockMask);
+}
+
+static void
+freekey(Key *k) {
+ Key *n;
+
+ while((n = k)) {
+ k = k->next;
+ free(n);
+ }
+}
+
+static void
+_grab(XWindow w, int keycode, uint mod) {
+ XGrabKey(display, keycode, mod, w,
+ true, GrabModeAsync, GrabModeAsync);
+}
+
+static void
+grabkey(Key *k) {
+ _grab(scr.root.xid, k->key, k->mod);
+ _grab(scr.root.xid, k->key, k->mod | LockMask);
+ if(numlock_mask) {
+ _grab(scr.root.xid, k->key, k->mod | numlock_mask);
+ _grab(scr.root.xid, k->key, k->mod | numlock_mask | LockMask);
+ }
+}
+
+static void
+ungrabkey(Key *k) {
+ XUngrabKey(display, k->key, k->mod, scr.root.xid);
+ XUngrabKey(display, k->key, k->mod | LockMask, scr.root.xid);
+ if(numlock_mask) {
+ XUngrabKey(display, k->key, k->mod | numlock_mask, scr.root.xid);
+ XUngrabKey(display, k->key, k->mod | numlock_mask | LockMask, scr.root.xid);
+ }
+}
+
+static Key *
+name2key(const char *name) {
+ Key *k;
+
+ for(k=key; k; k=k->lnext)
+ if(!strncmp(k->name, name, sizeof k->name))
+ return k;
+ return nil;
+}
+
+static Key*
+getkey(const char *name) {
+ Key *k, *r;
+ char buf[128];
+ char *seq[8];
+ char *kstr;
+ int mask;
+ uint i, toks;
+ static ushort id = 1;
+
+ r = nil;
+
+ if((k = name2key(name))) {
+ ungrabkey(k);
+ return k;
+ }
+ utflcpy(buf, name, sizeof buf);
+ toks = tokenize(seq, 8, buf, ',');
+ for(i = 0; i < toks; i++) {
+ if(!k)
+ r = k = emallocz(sizeof *k);
+ else {
+ k->next = emallocz(sizeof *k);
+ k = k->next;
+ }
+ utflcpy(k->name, name, sizeof k->name);
+ if(parsekey(seq[i], &mask, &kstr)) {
+ k->key = keycode(kstr);
+ k->mod = mask;
+ }
+ if(k->key == 0) {
+ freekey(r);
+ return nil;
+ }
+ }
+ if(r) {
+ r->id = id++;
+ r->lnext = key;
+ key = r;
+ }
+
+ return r;
+}
+
+static void
+next_keystroke(ulong *mod, KeyCode *code) {
+ XEvent e;
+ KeySym sym;
+ *mod = 0;
+
+ do {
+ XMaskEvent(display, KeyPressMask, &e);
+ *mod |= e.xkey.state & valid_mask;
+ *code = (KeyCode) e.xkey.keycode;
+ sym = XKeycodeToKeysym(display, e.xkey.keycode, 0);
+ } while(IsModifierKey(sym));
+}
+
+static void
+fake_keypress(ulong mod, KeyCode key) {
+ XKeyEvent e;
+ Client *c;
+
+ c = disp.focus;
+ if(c == nil || c->w.xid == 0)
+ return;
+
+ e.time = CurrentTime;
+ e.window = c->w.xid;
+ e.display = display;
+ e.state = mod;
+ e.keycode = key;
+
+ e.type = KeyPress;
+ sendevent(&c->w, true, KeyPressMask, (XEvent*)&e);
+ e.type = KeyRelease;
+ sendevent(&c->w, true, KeyReleaseMask, (XEvent*)&e);
+
+ sync();
+}
+
+static Key *
+match_keys(Key *k, ulong mod, KeyCode keycode, bool seq) {
+ Key *ret, *next;
+ volatile int i; /* shut up ken */
+
+ ret = nil;
+ for(next = k->tnext; k; i = (k=next) && (next=k->tnext)) {
+ if(seq)
+ k = k->next;
+ if(k && (k->mod == mod) && (k->key == keycode)) {
+ k->tnext = ret;
+ ret = k;
+ }
+ }
+ return ret;
+}
+
+static void
+kpress_seq(XWindow w, Key *done) {
+ ulong mod;
+ KeyCode key;
+ Key *found;
+
+ next_keystroke(&mod, &key);
+ found = match_keys(done, mod, key, true);
+ if((done->mod == mod) && (done->key == key))
+ fake_keypress(mod, key); /* double key */
+ else {
+ if(!found)
+ XBell(display, 0);
+ else if(!found->tnext && !found->next)
+ event("Key %s\n", found->name);
+ else
+ kpress_seq(w, found);
+ }
+}
+
+void
+kpress(XWindow w, ulong mod, KeyCode keycode) {
+ Key *k, *found;
+
+ for(k=key; k; k=k->lnext)
+ k->tnext = k->lnext;
+
+ found = match_keys(key, mod, keycode, false);
+ if(!found) /* grabbed but not found */
+ XBell(display, 0);
+ else if(!found->tnext && !found->next)
+ event("Key %s\n", found->name);
+ else {
+ XGrabKeyboard(display, w, true, GrabModeAsync, GrabModeAsync, CurrentTime);
+ flushevents(FocusChangeMask, true);
+ kpress_seq(w, found);
+ XUngrabKeyboard(display, CurrentTime);
+ }
+}
+
+void
+update_keys(void) {
+ Key *k;
+ char *l, *p;
+
+ init_lock_keys();
+ while((k = key)) {
+ key = key->lnext;
+ ungrabkey(k);
+ freekey(k);
+ }
+ for(l = p = def.keys; p && *p;) {
+ if(*p == '\n') {
+ *p = 0;
+ if((k = getkey(l)))
+ grabkey(k);
+ *p = '\n';
+ l = ++p;
+ }
+ else
+ p++;
+ }
+ if(l < p && strlen(l)) {
+ if((k = getkey(l)))
+ grabkey(k);
+ }
+}
+
diff --git a/cmd/wmii/layout.c b/cmd/wmii/layout.c
new file mode 100644
index 0000000..eb70302
--- /dev/null
+++ b/cmd/wmii/layout.c
@@ -0,0 +1,608 @@
+/* Copyright ©2006-2010 Kris Maglione <maglione.k at Gmail>
+ * See LICENSE file for license details.
+ */
+#include "dat.h"
+#include "fns.h"
+
+/* Here be dragons. */
+/* Actually, I'm happy to say, the dragons have dissipated. */
+
+enum {
+ ButtonMask =
+ ButtonPressMask | ButtonReleaseMask,
+ MouseMask =
+ ButtonMask | PointerMotionMask
+};
+
+static Handlers handlers;
+
+enum { OHoriz, OVert };
+typedef struct Framewin Framewin;
+struct Framewin {
+ /* Todo... give these better names. */
+ Window* w;
+ Rectangle grabbox;
+ Frame* f;
+ Area* ra;
+ Point pt;
+ int orientation;
+ int xy;
+ int screen;
+};
+
+static Rectangle
+framerect(Framewin *f) {
+ Rectangle r;
+ Point p;
+ int scrn;
+
+ r.min = ZP;
+ if(f->orientation == OHoriz) {
+ r.max.x = f->xy;
+ r.max.y = f->grabbox.max.y + f->grabbox.min.y;
+ }else {
+ r.max.x = f->grabbox.max.x + f->grabbox.min.x;
+ r.max.y = f->xy;
+ r = rectsubpt(r, Pt(Dx(r)/2, 0));
+ }
+ r = rectaddpt(r, f->pt);
+
+ scrn = f->screen;
+ if (scrn == -1)
+ scrn = max(ownerscreen(f->f->r), 0);
+
+ /* Keep onscreen */
+ p = ZP;
+ p.x -= min(0, r.min.x);
+ p.x -= max(0, r.max.x - screens[scrn]->r.max.x);
+ p.y -= max(0, r.max.y - screens[scrn]->brect.min.y - Dy(r)/2);
+ return rectaddpt(r, p);
+}
+
+static void
+frameadjust(Framewin *f, Point pt, int orientation, int xy) {
+ f->orientation = orientation;
+ f->xy = xy;
+ f->pt = pt;
+}
+
+static Framewin*
+framewin(Frame *f, Point pt, int orientation, int n) {
+ WinAttr wa;
+ Framewin *fw;
+
+ fw = emallocz(sizeof *fw);
+ wa.override_redirect = true;
+ wa.event_mask = ExposureMask;
+ fw->w = createwindow(&scr.root, Rect(0, 0, 1, 1),
+ scr.depth, InputOutput,
+ &wa, CWEventMask);
+ fw->w->aux = fw;
+ sethandler(fw->w, &handlers);
+
+ fw->f = f;
+ fw->screen = f->area->screen;
+ fw->grabbox = f->grabbox;
+ frameadjust(fw, pt, orientation, n);
+ reshapewin(fw->w, framerect(fw));
+
+ mapwin(fw->w);
+ raisewin(fw->w);
+
+ return fw;
+}
+
+static void
+framedestroy(Framewin *f) {
+ destroywindow(f->w);
+ free(f);
+}
+
+static void
+expose_event(Window *w, XExposeEvent *e) {
+ Rectangle r;
+ Framewin *f;
+ Image *buf;
+ CTuple *c;
+
+ USED(e);
+
+ f = w->aux;
+ c = &def.focuscolor;
+ buf = disp.ibuf;
+
+ r = rectsubpt(w->r, w->r.min);
+ fill(buf, r, c->bg);
+ border(buf, r, 1, c->border);
+ border(buf, f->grabbox, 1, c->border);
+ border(buf, insetrect(f->grabbox, -f->grabbox.min.x), 1, c->border);
+
+ copyimage(w, r, buf, ZP);
+}
+
+static Handlers handlers = {
+ .expose = expose_event,
+};
+
+static Area*
+find_area(Point pt) {
+ View *v;
+ Area *a;
+ int s;
+
+ v = selview;
+ for(s=0; s < nscreens; s++) {
+ if(!rect_haspoint_p(pt, screens[s]->r))
+ continue;
+ for(a=v->areas[s]; a; a=a->next)
+ if(pt.x < a->r.max.x)
+ return a;
+ }
+ return nil;
+}
+
+static void
+vplace(Framewin *fw, Point pt) {
+ Vector_long vec = {0};
+ Rectangle r;
+ Frame *f;
+ Area *a;
+ View *v;
+ long l;
+ int hr;
+
+ v = selview;
+
+ a = find_area(pt);
+ if(a == nil)
+ return;
+
+ fw->ra = a;
+ fw->screen = a->screen;
+
+ pt.x = a->r.min.x;
+ frameadjust(fw, pt, OHoriz, Dx(a->r));
+
+ r = fw->w->r;
+ hr = Dy(r)/2;
+ pt.y -= hr;
+
+ if(a->frame == nil)
+ goto done;
+
+ vector_lpush(&vec, a->frame->r.min.y);
+ for(f=a->frame; f; f=f->anext) {
+ if(f == fw->f)
+ vector_lpush(&vec, f->r.min.y + 0*hr);
+ else if(f->collapsed)
+ vector_lpush(&vec, f->r.min.y + 1*hr);
+ else
+ vector_lpush(&vec, f->r.min.y + 2*hr);
+ if(!f->collapsed && f->anext != fw->f)
+ vector_lpush(&vec, f->r.max.y - 2*hr);
+ }
+
+ for(int i=0; i < vec.n; i++) {
+ l = vec.ary[i];
+ if(abs(pt.y - l) < hr) {
+ pt.y = l;
+ break;
+ }
+ }
+ vector_lfree(&vec);
+
+done:
+ pt.x = a->r.min.x;
+ frameadjust(fw, pt, OHoriz, Dx(a->r));
+ reshapewin(fw->w, framerect(fw));
+}
+
+static void
+hplace(Framewin *fw, Point pt) {
+ Area *a;
+ View *v;
+ int minw;
+
+ v = selview;
+
+ a = find_area(pt);
+ if(a == nil)
+ return; /* XXX: Multihead. */
+
+ fw->screen = a->screen;
+ fw->ra = nil;
+ minw = column_minwidth();
+ if(abs(pt.x - a->r.min.x) < minw/2) {
+ pt.x = a->r.min.x;
+ fw->ra = a->prev;
+ }
+ else if(abs(pt.x - a->r.max.x) < minw/2) {
+ pt.x = a->r.max.x;
+ fw->ra = a;
+ }
+
+ pt.y = a->r.min.y;
+ frameadjust(fw, pt, OVert, Dy(a->r));
+ reshapewin(fw->w, framerect(fw));
+}
+
+static Point
+grabboxcenter(Frame *f) {
+ Point p;
+
+ p = addpt(f->r.min, f->grabbox.min);
+ p.x += Dx(f->grabbox)/2;
+ p.y += Dy(f->grabbox)/2;
+ return p;
+}
+
+static int tvcol(Frame*);
+static int thcol(Frame*);
+static int tfloat(Frame*);
+
+enum {
+ TDone,
+ TVCol,
+ THCol,
+ TFloat,
+};
+
+static int (*tramp[])(Frame*) = {
+ 0,
+ tvcol,
+ thcol,
+ tfloat,
+};
+
+/* Trampoline to allow properly tail recursive move/resize routines.
+ * We could probably get away with plain tail calls, but I don't
+ * like the idea.
+ */
+static void
+trampoline(int fn, Frame *f, bool grabbox) {
+
+ while(fn > 0) {
+ resizing = fn != TFloat;
+ view_update(f->view);
+ if(grabbox)
+ warppointer(grabboxcenter(f));
+ //f->collapsed = false;
+ fn = tramp[fn](f);
+ }
+ ungrabpointer();
+ resizing = false;
+ view_update(f->view);
+}
+
+void
+mouse_movegrabbox(Client *c, bool grabmod) {
+ Frame *f;
+ Point p;
+ float x, y;
+
+ f = c->sel;
+
+ SET(x);
+ SET(y);
+ if(grabmod) {
+ p = querypointer(f->client->framewin);
+ x = (float)p.x / Dx(f->r);
+ y = (float)p.y / Dy(f->r);
+ }
+
+ if(f->area->floating)
+ trampoline(TFloat, f, !grabmod);
+ else
+ trampoline(THCol, f, true);
+
+ if(grabmod)
+ warppointer(addpt(f->r.min, Pt(x * Dx(f->r),
+ y * Dy(f->r))));
+ else
+ warppointer(grabboxcenter(f));
+}
+
+static int
+_openstack_down(Frame *f, int h) {
+ int ret;
+ int dy;
+
+ if(f == nil)
+ return 0;;
+ ret = 0;
+ if(!f->collapsed) {
+ dy = Dy(f->colr) - labelh(def.font);
+ if(dy >= h) {
+ f->colr.min.y += h;
+ return h;
+ }else {
+ f->collapsed = true;
+ f->colr.min.y += dy;
+ ret = dy;
+ h -= dy;
+ }
+ }
+ dy = _openstack_down(f->anext, h);
+ f->colr.min.y += dy;
+ f->colr.max.y += dy;
+ return ret + dy;
+}
+
+static int
+_openstack_up(Frame *f, int h) {
+ int ret;
+ int dy;
+
+ if(f == nil)
+ return 0;
+ ret = 0;
+ if(!f->collapsed) {
+ dy = Dy(f->colr) - labelh(def.font);
+ if(dy >= h) {
+ f->colr.max.y -= h;
+ return h;
+ }else {
+ f->collapsed = true;
+ f->colr.max.y -= dy;
+ ret = dy;
+ h -= dy;
+ }
+ }
+ dy = _openstack_up(f->aprev, h);
+ f->colr.min.y -= dy;
+ f->colr.max.y -= dy;
+ return ret + dy;
+}
+
+static void
+column_openstack(Area *a, Frame *f, int h) {
+
+ if(f == nil)
+ _openstack_down(a->frame, h);
+ else {
+ h -= _openstack_down(f->anext, h);
+ if(h)
+ _openstack_up(f->aprev, h);
+ }
+}
+
+static void
+column_drop(Area *a, Frame *f, int y) {
+ Frame *ff;
+ int dy;
+
+ for(ff=a->frame; ff; ff=ff->anext)
+ assert(ff != f);
+
+ if(a->frame == nil || y <= a->frame->r.min.y) {
+ f->collapsed = true;
+ f->colr.min.y = 0;
+ f->colr.max.y = labelh(def.font);
+ column_openstack(a, nil, labelh(def.font));
+ column_insert(a, f, nil);
+ return;
+ }
+ for(ff=a->frame; ff->anext; ff=ff->anext)
+ if(y <= ff->colr.max.y) break;
+
+ y = max(y, ff->colr.min.y + labelh(def.font));
+ y = min(y, ff->colr.max.y);
+ dy = ff->colr.max.y - y;
+ if(dy <= labelh(def.font)) {
+ f->collapsed = true;
+ f->colr.min.y = 0;
+ f->colr.max.y = labelh(def.font);
+ column_openstack(a, ff, labelh(def.font) - dy);
+ }else {
+ f->colr.min.y = y;
+ f->colr.max.y = ff->colr.max.y;
+ ff->colr.max.y = y;
+ }
+ column_insert(a, f, ff);
+}
+
+static int
+thcol(Frame *f) {
+ Framewin *fw;
+ Frame *fp, *fn;
+ Area *a;
+ Point pt, pt2;
+ uint button;
+ int ret, collapsed;
+
+ focus(f->client, false);
+
+ ret = TDone;
+ if(!grabpointer(&scr.root, nil, cursor[CurIcon], MouseMask))
+ return TDone;
+
+ pt = querypointer(&scr.root);
+ pt2.x = f->area->r.min.x;
+ pt2.y = pt.y;
+ fw = framewin(f, pt2, OHoriz, Dx(f->area->r));
+
+ vplace(fw, pt);
+ for(;;)
+ switch (readmouse(&pt, &button)) {
+ case MotionNotify:
+ vplace(fw, pt);
+ break;
+ case ButtonRelease:
+ if(button != 1)
+ continue;
+ SET(collapsed);
+ SET(fp);
+ SET(fn);
+ a = f->area;
+ if(a->floating)
+ area_detach(f);
+ else {
+ collapsed = f->collapsed;
+ fp = f->aprev;
+ fn = f->anext;
+ column_remove(f);
+ if(!f->collapsed)
+ if(fp)
+ fp->colr.max.y = f->colr.max.y;
+ else if(fn && fw->pt.y > fn->r.min.y)
+ fn->colr.min.y = f->colr.min.y;
+ }
+
+ column_drop(fw->ra, f, fw->pt.y);
+ if(!a->floating && collapsed) {
+ /* XXX */
+ for(; fn && fn->collapsed; fn=fn->anext)
+ ;
+ if(fn == nil)
+ for(fn=fp; fn && fn->collapsed; fn=fn->aprev)
+ ;
+ if(fp)
+ fp->colr.max.x += labelh(def.font);
+ }
+
+
+ if(!a->frame && !a->floating && a->view->areas[a->screen]->next)
+ area_destroy(a);
+
+ frame_focus(f);
+ goto done;
+ case ButtonPress:
+ if(button == 2)
+ ret = TVCol;
+ else if(button == 3)
+ ret = TFloat;
+ else
+ continue;
+ goto done;
+ }
+done:
+ framedestroy(fw);
+ return ret;
+}
+
+static int
+tvcol(Frame *f) {
+ Framewin *fw;
+ Window *cwin;
+ WinAttr wa;
+ Rectangle r;
+ Point pt, pt2;
+ uint button;
+ int ret, scrn;
+
+ focus(f->client, false);
+
+ pt = querypointer(&scr.root);
+ pt2.x = pt.x;
+ pt2.y = f->area->r.min.y;
+
+ scrn = f->area->screen > -1 ? f->area->screen : find_area(pt) ? find_area(pt)->screen : 0;
+ r = f->view->r[scrn];
+ fw = framewin(f, pt2, OVert, Dy(r));
+
+ r.min.y += fw->grabbox.min.y + Dy(fw->grabbox)/2;
+ r.max.y = r.min.y + 1;
+ cwin = createwindow(&scr.root, r, 0, InputOnly, &wa, 0);
+ mapwin(cwin);
+
+ ret = TDone;
+ if(!grabpointer(&scr.root, cwin, cursor[CurIcon], MouseMask))
+ goto done;
+
+ hplace(fw, pt);
+ for(;;)
+ switch (readmouse(&pt, &button)) {
+ case MotionNotify:
+ hplace(fw, pt);
+ continue;
+ case ButtonPress:
+ if(button == 2)
+ ret = THCol;
+ else if(button == 3)
+ ret = TFloat;
+ else
+ continue;
+ goto done;
+ case ButtonRelease:
+ if(button != 1)
+ continue;
+ if(fw->ra) {
+ fw->ra = column_new(f->view, fw->ra, screen->idx, 0);
+ area_moveto(fw->ra, f);
+ }
+ goto done;
+ }
+
+done:
+ framedestroy(fw);
+ destroywindow(cwin);
+ return ret;
+}
+
+static int
+tfloat(Frame *f) {
+ Rectangle *rects;
+ Rectangle frect, origin;
+ Point pt, pt1;
+ Client *c;
+ Align align;
+ uint nrect, button;
+ int ret;
+
+ c = f->client;
+ if(!f->area->floating) {
+ if(f->anext)
+ f->anext->colr.min.y = f->colr.min.y;
+ else if(f->aprev)
+ f->aprev->colr.max.y = f->colr.max.y;
+ area_moveto(f->view->floating, f);
+ }
+ map_frame(f->client);
+ focus(f->client, false);
+
+ ret = TDone;
+ if(!grabpointer(c->framewin, nil, cursor[CurMove], MouseMask))
+ return TDone;
+
+ rects = view_rects(f->view, &nrect, f);
+ origin = f->r;
+ frect = f->r;
+
+ pt = querypointer(&scr.root);
+ /* pt1 = grabboxcenter(f); */
+ pt1 = pt;
+ goto case_motion;
+
+shut_up_ken:
+ for(;;pt1=pt)
+ switch (readmouse(&pt, &button)) {
+ default: goto shut_up_ken;
+ case MotionNotify:
+ case_motion:
+ origin = rectaddpt(origin, subpt(pt, pt1));
+ origin = constrain(origin, -1);
+ frect = origin;
+
+ align = Center;
+ snap_rect(rects, nrect, &frect, &align, def.snap);
+
+ frect = frame_hints(f, frect, Center);
+ frect = constrain(frect, -1);
+ client_resize(c, frect);
+ continue;
+ case ButtonRelease:
+ if(button != 1)
+ continue;
+ goto done;
+ case ButtonPress:
+ if(button != 3)
+ continue;
+ unmap_frame(f->client);
+ ret = THCol;
+ goto done;
+ }
+done:
+ free(rects);
+ return ret;
+}
+
diff --git a/cmd/wmii/main.c b/cmd/wmii/main.c
new file mode 100644
index 0000000..fba5d5f
--- /dev/null
+++ b/cmd/wmii/main.c
@@ -0,0 +1,470 @@
+/* Copyright ©2004-2006 Anselm R. Garbe <garbeam at gmail dot com>
+ * Copyright ©2006-2010 Kris Maglione <maglione.k at Gmail>
+ * See LICENSE file for license details.
+ */
+#define EXTERN
+#include "dat.h"
+#include <X11/Xproto.h>
+#include <X11/cursorfont.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <locale.h>
+#include <pwd.h>
+#include <sys/signal.h>
+#include <sys/stat.h>
+#include <unistd.h>
+#include "fns.h"
+
+static const char
+ version[] = "wmii-"VERSION", "COPYRIGHT"\n";
+
+static char* address;
+static char* ns_path;
+static int sleeperfd;
+static int sock;
+static int exitsignal;
+
+static struct sigaction sa;
+static struct passwd* passwd;
+
+static void
+usage(void) {
+ fatal("usage: wmii [-a <address>] [-r <wmiirc>] [-v]\n");
+}
+
+static int
+errfmt(Fmt *f) {
+ return fmtstrcpy(f, ixp_errbuf());
+}
+
+static void
+scan_wins(void) {
+ int i;
+ uint num;
+ XWindow *wins;
+ XWindowAttributes wa;
+ XWindow d1, d2;
+
+ if(XQueryTree(display, scr.root.xid, &d1, &d2, &wins, &num)) {
+ for(i = 0; i < num; i++) {
+ if(!XGetWindowAttributes(display, wins[i], &wa))
+ continue;
+ /* Skip transients. */
+ if(wa.override_redirect || XGetTransientForHint(display, wins[i], &d1))
+ continue;
+ if(wa.map_state == IsViewable)
+ client_create(wins[i], &wa);
+ }
+ /* Manage transients. */
+ for(i = 0; i < num; i++) {
+ if(!XGetWindowAttributes(display, wins[i], &wa))
+ continue;
+ if((XGetTransientForHint(display, wins[i], &d1))
+ && (wa.map_state == IsViewable))
+ client_create(wins[i], &wa);
+ }
+ }
+ if(wins)
+ XFree(wins);
+}
+
+static void
+init_ns(void) {
+ char *s;
+
+ if(address && strncmp(address, "unix!", 5) == 0) {
+ ns_path = estrdup(&address[5]);
+ s = strrchr(ns_path, '/');
+ if(s != nil)
+ *s = '\0';
+ if(ns_path[0] != '/' || ns_path[0] == '\0')
+ fatal("address %q is not an absolute path", address);
+ setenv("NAMESPACE", ns_path, true);
+ }else
+ ns_path = ixp_namespace();
+
+ if(ns_path == nil)
+ fatal("Bad namespace path: %r\n");
+}
+
+static void
+init_environment(void) {
+ init_ns();
+
+ if(address)
+ setenv("WMII_ADDRESS", address, true);
+ else
+ address = smprint("unix!%s/wmii", ns_path);
+ setenv("WMII_CONFPATH", sxprint("%s/.wmii%s:%s/wmii%s",
+ getenv("HOME"), CONFVERSION,
+ CONFPREFIX, CONFVERSION), true);
+}
+
+static void
+create_cursor(int ident, uint shape) {
+ cursor[ident] = XCreateFontCursor(display, shape);
+}
+
+static void
+init_cursors(void) {
+ static char zchar[1];
+ Pixmap pix;
+ XColor black, dummy;
+
+ create_cursor(CurNormal, XC_left_ptr);
+ create_cursor(CurNECorner, XC_top_right_corner);
+ create_cursor(CurNWCorner, XC_top_left_corner);
+ create_cursor(CurSECorner, XC_bottom_right_corner);
+ create_cursor(CurSWCorner, XC_bottom_left_corner);
+ create_cursor(CurMove, XC_fleur);
+ create_cursor(CurDHArrow, XC_sb_h_double_arrow);
+ create_cursor(CurDVArrow, XC_sb_v_double_arrow);
+ create_cursor(CurInput, XC_xterm);
+ create_cursor(CurSizing, XC_sizing);
+ create_cursor(CurIcon, XC_icon);
+ create_cursor(CurTCross, XC_tcross);
+
+ XAllocNamedColor(display, scr.colormap,
+ "black", &black, &dummy);
+ pix = XCreateBitmapFromData(
+ display, scr.root.xid,
+ zchar, 1, 1);
+
+ cursor[CurNone] = XCreatePixmapCursor(display,
+ pix, pix,
+ &black, &black,
+ 0, 0);
+
+ XFreePixmap(display, pix);
+}
+
+/*
+ * There's no way to check accesses to destroyed windows, thus
+ * those cases are ignored (especially on UnmapNotifies).
+ * Other types of errors call Xlib's default error handler, which
+ * calls exit().
+ */
+ErrorCode ignored_xerrors[] = {
+ { 0, BadWindow },
+ { X_SetInputFocus, BadMatch },
+ { X_PolyText8, BadDrawable },
+ { X_PolyFillRectangle, BadDrawable },
+ { X_PolySegment, BadDrawable },
+ { X_ConfigureWindow, BadMatch },
+ { X_GrabKey, BadAccess },
+ { X_GetAtomName, BadAtom },
+ { 0, }
+};
+
+void
+regerror(char *err) {
+ fprint(2, "%s: %s\n", argv0, err);
+}
+
+void
+init_screens(void) {
+ Rectangle *rects;
+ View *v;
+ int i, n, m;
+
+#ifdef notdef
+ d.x = Dx(scr.rect) - Dx(screen->r);
+ d.y = Dy(scr.rect) - Dy(screen->r);
+ for(v=view; v; v=v->next) {
+ v->r.max.x += d.x;
+ v->r.max.y += d.y;
+ }
+#endif
+
+ /* Reallocate screens, zero any new ones. */
+ rects = xinerama_screens(&n);
+ m = max(n, nscreens);
+ screens = erealloc(screens, (m + 1) * sizeof *screens);
+ screens[m] = nil;
+ for(v=view; v; v=v->next) {
+ v->areas = erealloc(v->areas, m * sizeof *v->areas);
+ v->r = erealloc(v->r, m * sizeof *v->r);
+ v->pad = erealloc(v->pad, m * sizeof *v->pad);
+ }
+
+ for(i=nscreens; i < m; i++) {
+ screens[i] = emallocz(sizeof *screens[i]);
+ for(v=view; v; v=v->next)
+ view_init(v, i);
+ }
+
+ nscreens = m;
+
+ /* Reallocate buffers. */
+ freeimage(ibuf);
+ freeimage(ibuf32);
+ ibuf = allocimage(Dx(scr.rect), Dy(scr.rect), scr.depth);
+ ibuf32 = nil; /* Probably shouldn't do this until it's needed. */
+ if(render_visual)
+ ibuf32 = allocimage(Dx(scr.rect), Dy(scr.rect), 32);
+ disp.ibuf = ibuf;
+ disp.ibuf32 = ibuf32;
+
+ /* Resize and initialize screens. */
+ for(i=0; i < nscreens; i++) {
+ screen = screens[i];
+ screen->idx = i;
+
+ screen->showing = i < n;
+ if(screen->showing)
+ screen->r = rects[i];
+ else
+ screen->r = rectsetorigin(screen->r, scr.rect.max);
+ def.snap = Dy(screen->r) / 63;
+ bar_init(screens[i]);
+ }
+ screen = screens[0];
+ if(selview)
+ view_update(selview);
+}
+
+static void
+cleanup(void) {
+ starting = -1;
+ while(client)
+ client_destroy(client);
+ ixp_server_close(&srv);
+ close(sleeperfd);
+}
+
+static void
+cleanup_handler(int signal) {
+ sa.sa_handler = SIG_DFL;
+ sigaction(signal, &sa, nil);
+
+ srv.running = false;
+
+ switch(signal) {
+ case SIGTERM:
+ sa.sa_handler = cleanup_handler;
+ sigaction(SIGALRM, &sa, nil);
+ alarm(1);
+ default:
+ exitsignal = signal;
+ break;
+ case SIGALRM:
+ raise(SIGTERM);
+ case SIGINT:
+ break;
+ }
+}
+
+static void
+init_traps(void) {
+ char buf[1];
+ int fd[2];
+
+ if(pipe(fd) != 0)
+ fatal("Can't pipe(): %r");
+
+ if(doublefork() == 0) {
+ close(fd[1]);
+ close(ConnectionNumber(display));
+ setsid();
+
+ display = XOpenDisplay(nil);
+ if(!display)
+ fatal("Can't open display");
+
+ /* Wait for parent to exit */
+ read(fd[0], buf, 1);
+
+ setfocus(pointerwin, RevertToPointerRoot);
+ XCloseDisplay(display);
+ exit(0);
+ }
+
+ close(fd[0]);
+ sleeperfd = fd[1];
+
+ sa.sa_flags = 0;
+ sa.sa_handler = cleanup_handler;
+ sigaction(SIGINT, &sa, nil);
+ sigaction(SIGTERM, &sa, nil);
+ sigaction(SIGQUIT, &sa, nil);
+ sigaction(SIGHUP, &sa, nil);
+ sigaction(SIGUSR1, &sa, nil);
+ sigaction(SIGUSR2, &sa, nil);
+}
+
+void
+spawn_command(const char *cmd) {
+ char *shell, *p;
+
+
+ if(doublefork() == 0) {
+ if((p = pathsearch(getenv("WMII_CONFPATH"), cmd, true)))
+ cmd = p;
+
+ if(setsid() == -1)
+ fatal("Can't setsid: %r");
+
+ /* Run through the user's shell as a login shell */
+ shell = passwd->pw_shell;
+ if(shell[0] != '/')
+ fatal("Shell is not an absolute path: %s", shell);
+ p = smprint("-%s", strrchr(shell, '/') + 1);
+
+ close(0);
+ open("/dev/null", O_RDONLY);
+
+ execl(shell, p, "-c", cmd, nil);
+ fatal("Can't exec '%s': %r", cmd);
+ /* NOTREACHED */
+ }
+}
+
+static void
+check_preselect(IxpServer *s) {
+ USED(s);
+
+ check_x_event(nil);
+}
+
+static void
+closedisplay(IxpConn *c) {
+ USED(c);
+
+ XCloseDisplay(display);
+}
+
+static void
+printfcall(IxpFcall *f) {
+ Dprint(D9p, "%F\n", f);
+}
+
+int
+main(int argc, char *argv[]) {
+ IxpMsg m;
+ char **oargv;
+ char *wmiirc, *s;
+ int i;
+
+ quotefmtinstall();
+ fmtinstall('r', errfmt);
+ fmtinstall('a', afmt);
+ fmtinstall('C', Cfmt);
+extern int fmtevent(Fmt*);
+ fmtinstall('E', fmtevent);
+
+ wmiirc = "wmiirc";
+
+ oargv = argv;
+ ARGBEGIN{
+ case 'a':
+ address = EARGF(usage());
+ break;
+ case 'r':
+ wmiirc = EARGF(usage());
+ break;
+ case 'v':
+ print("%s", version);
+ exit(0);
+ case 'D':
+ s = EARGF(usage());
+ m = ixp_message(s, strlen(s), 0);
+ msg_debug(&m);
+ break;
+ default:
+ usage();
+ break;
+ }ARGEND;
+
+ if(argc)
+ usage();
+
+ setlocale(LC_CTYPE, "");
+ starting = true;
+
+ initdisplay();
+
+ traperrors(true);
+ selectinput(&scr.root, EnterWindowMask
+ | SubstructureRedirectMask);
+ if(traperrors(false))
+ fatal("another window manager is already running");
+
+ passwd = getpwuid(getuid());
+ user = estrdup(passwd->pw_name);
+
+ init_environment();
+
+ fmtinstall('F', Ffmt);
+ ixp_printfcall = printfcall;
+
+ sock = ixp_announce(address);
+ if(sock < 0)
+ fatal("Can't create socket '%s': %r", address);
+ closeexec(ConnectionNumber(display));
+ closeexec(sock);
+
+ if(wmiirc[0])
+ spawn_command(wmiirc);
+
+ init_traps();
+ init_cursors();
+ init_lock_keys();
+ ewmh_init();
+ xext_init();
+
+ srv.preselect = check_preselect;
+ ixp_listen(&srv, sock, &p9srv, serve_9pcon, nil);
+ ixp_listen(&srv, ConnectionNumber(display), nil, check_x_event, closedisplay);
+
+ def.border = 1;
+ def.colmode = Colstack;
+ def.font = loadfont(FONT);
+ def.incmode = ISqueeze;
+
+ def.mod = Mod1Mask;
+ strcpy(def.grabmod, "Mod1");
+
+ loadcolor(&def.focuscolor, FOCUSCOLORS);
+ loadcolor(&def.normcolor, NORMCOLORS);
+
+ disp.sel = pointerscreen();
+
+ init_screens();
+ root_init();
+
+ disp.focus = nil;
+ setfocus(screen->barwin, RevertToParent);
+ view_select("1");
+
+ scan_wins();
+ starting = false;
+
+ view_update_all();
+ ewmh_updateviews();
+
+ event("FocusTag %s\n", selview->name);
+
+ i = ixp_serverloop(&srv);
+ if(i)
+ fprint(2, "%s: error: %r\n", argv0);
+ else
+ event("Quit");
+
+ cleanup();
+
+ if(exitsignal)
+ raise(exitsignal);
+ if(execstr) {
+ char *toks[32];
+ int n;
+
+ n = unquote(strdup(execstr), toks, nelem(toks)-1);
+ toks[n] = nil;
+ execvp(toks[0], toks);
+ fprint(2, "%s: failed to exec %q: %r\n", argv0, execstr);
+ execvp(argv0, oargv);
+ fatal("failed to exec myself");
+ }
+ return i;
+}
+
diff --git a/cmd/wmii/map.c b/cmd/wmii/map.c
new file mode 100644
index 0000000..08b137a
--- /dev/null
+++ b/cmd/wmii/map.c
@@ -0,0 +1,126 @@
+/* Written by Kris Maglione */
+/* Public domain */
+#include "dat.h"
+#include "fns.h"
+
+/* Edit s/^([a-zA-Z].*)\n([a-z].*) {/\1 \2;/g x/^([^a-zA-Z]|static|$)/-+d s/ (\*map|val|*str)//g */
+
+struct MapEnt {
+ ulong hash;
+ const char* key;
+ void* val;
+ MapEnt* next;
+};
+
+MapEnt *NM;
+
+/* By Dan Bernstein. Public domain. */
+static ulong
+hash(const char *str) {
+ ulong h;
+
+ h = 5381;
+ while (*str != '\0') {
+ h += h << 5; /* h *= 33 */
+ h ^= *str++;
+ }
+ return h;
+}
+
+static void
+insert(MapEnt **e, ulong val, const char *key) {
+ MapEnt *te;
+
+ te = emallocz(sizeof *te);
+ te->hash = val;
+ te->key = key;
+ te->next = *e;
+ *e = te;
+}
+
+static MapEnt**
+map_getp(Map *map, ulong val, int create) {
+ MapEnt **e;
+
+ e = &map->bucket[val%map->nhash];
+ for(; *e; e = &(*e)->next)
+ if((*e)->hash >= val) break;
+ if(*e == nil || (*e)->hash != val) {
+ if(create)
+ insert(e, val, nil);
+ else
+ e = &NM;
+ }
+ return e;
+}
+
+static MapEnt**
+hash_getp(Map *map, const char *str, int create) {
+ MapEnt **e;
+ ulong h;
+ int cmp;
+
+ h = hash(str);
+ e = map_getp(map, h, create);
+ if(*e && (*e)->key == nil)
+ (*e)->key = str;
+ else {
+ SET(cmp);
+ for(; *e; e = &(*e)->next)
+ if((*e)->hash > h || (cmp = strcmp((*e)->key, str)) >= 0)
+ break;
+ if(*e == nil || (*e)->hash > h || cmp > 0)
+ if(create)
+ insert(e, h, str);
+ }
+ return e;
+}
+
+void**
+map_get(Map *map, ulong val, bool create) {
+ MapEnt *e;
+
+ e = *map_getp(map, val, create);
+ return e ? &e->val : nil;
+}
+
+void**
+hash_get(Map *map, const char *str, bool create) {
+ MapEnt *e;
+
+ e = *hash_getp(map, str, create);
+ return e ? &e->val : nil;
+}
+
+void*
+map_rm(Map *map, ulong val) {
+ MapEnt **e, *te;
+ void *ret;
+
+ ret = nil;
+ e = map_getp(map, val, 0);
+ if(*e) {
+ te = *e;
+ ret = te->val;
+ *e = te->next;
+ free(te);
+ }
+ return ret;
+}
+
+void*
+hash_rm(Map *map, const char *str) {
+ MapEnt **e, *te;
+ void *ret;
+
+ ret = nil;
+ e = hash_getp(map, str, 0);
+ if(*e) {
+ te = *e;
+ ret = te->val;
+ *e = te->next;
+ free(te);
+ }
+ return ret;
+}
+
diff --git a/cmd/wmii/message.c b/cmd/wmii/message.c
new file mode 100644
index 0000000..d1dd4a7
--- /dev/null
+++ b/cmd/wmii/message.c
@@ -0,0 +1,1177 @@
+/* Copyright ©2006-2010 Kris Maglione <maglione.k at Gmail>
+ * See LICENSE file for license details.
+ */
+#include "dat.h"
+#include <ctype.h>
+#include "fns.h"
+
+static char* msg_grow(View*, IxpMsg*);
+static char* msg_nudge(View*, IxpMsg*);
+static char* msg_selectframe(Area*, IxpMsg*, int);
+static char* msg_sendframe(Frame*, int, bool);
+
+#define DIR(s) (\
+ s == LUP ? North : \
+ s == LDOWN ? South : \
+ s == LLEFT ? West : \
+ s == LRIGHT ? East : \
+ (abort(), 0))
+
+static char
+ Ebadcmd[] = "bad command",
+ Ebadvalue[] = "bad value",
+ Ebadusage[] = "bad usage";
+
+/* Edit |sort Edit |sed 's/"([^"]+)"/L\1/g' | tr 'a-z' 'A-Z' */
+enum {
+ LFULLSCREEN,
+ LURGENT,
+ LBAR,
+ LBORDER,
+ LCLIENT,
+ LCOLMODE,
+ LDEBUG,
+ LDOWN,
+ LEXEC,
+ LFOCUSCOLORS,
+ LFONT,
+ LFONTPAD,
+ LGRABMOD,
+ LGROW,
+ LINCMODE,
+ LKILL,
+ LLEFT,
+ LNORMCOLORS,
+ LNUDGE,
+ LOFF,
+ LON,
+ LQUIT,
+ LRIGHT,
+ LSELCOLORS,
+ LSELECT,
+ LSEND,
+ LSLAY,
+ LSPAWN,
+ LSWAP,
+ LTOGGLE,
+ LUP,
+ LVIEW,
+ LTILDE,
+};
+char *symtab[] = {
+ "Fullscreen",
+ "Urgent",
+ "bar",
+ "border",
+ "client",
+ "colmode",
+ "debug",
+ "down",
+ "exec",
+ "focuscolors",
+ "font",
+ "fontpad",
+ "grabmod",
+ "grow",
+ "incmode",
+ "kill",
+ "left",
+ "normcolors",
+ "nudge",
+ "off",
+ "on",
+ "quit",
+ "right",
+ "selcolors",
+ "select",
+ "send",
+ "slay",
+ "spawn",
+ "swap",
+ "toggle",
+ "up",
+ "view",
+ "~",
+};
+
+char* debugtab[] = {
+ "9p",
+ "dnd",
+ "event",
+ "ewmh",
+ "focus",
+ "generic",
+ "stack",
+};
+
+static char* barpostab[] = {
+ "bottom",
+ "top",
+};
+static char* incmodetab[] = {
+ "ignore",
+ "show",
+ "squeeze",
+};
+static char* toggletab[] = {
+ "off",
+ "on",
+ "toggle",
+};
+
+/* Edit ,y/^[a-zA-Z].*\n.* {\n/d
+ * Edit s/^([a-zA-Z].*)\n(.*) {\n/\1 \2;\n/
+ * Edit ,x/^static.*\n/d
+ */
+
+static int
+_bsearch(char *s, char **tab, int ntab) {
+ int i, n, m, cmp;
+
+ if(s == nil)
+ return -1;
+
+ n = ntab;
+ i = 0;
+ while(n) {
+ m = n/2;
+ cmp = strcmp(s, tab[i+m]);
+ if(cmp == 0)
+ return i+m;
+ if(cmp < 0 || m == 0)
+ n = m;
+ else {
+ i += m;
+ n = n-m;
+ }
+ }
+ return -1;
+}
+
+static int
+getsym(char *s) {
+ return _bsearch(s, symtab, nelem(symtab));
+}
+
+static bool
+setdef(int *ptr, char *s, char *tab[], int ntab) {
+ int i;
+
+ i = _bsearch(s, tab, ntab);
+ if(i >= 0)
+ *ptr = i;
+ return i >= 0;
+}
+
+static int
+gettoggle(char *s) {
+ switch(getsym(s)) {
+ case LON: return On;
+ case LOFF: return Off;
+ case LTOGGLE: return Toggle;
+ default:
+ return -1;
+ }
+}
+
+static int
+getdirection(IxpMsg *m) {
+ int i;
+
+ switch(i = getsym(msg_getword(m))) {
+ case LLEFT:
+ case LRIGHT:
+ case LUP:
+ case LDOWN:
+ return i;
+ default:
+ return -1;
+ }
+}
+
+static void
+eatrunes(IxpMsg *m, int (*p)(Rune), int val) {
+ Rune r;
+ int n;
+
+ while(m->pos < m->end) {
+ n = chartorune(&r, m->pos);
+ if(p(r) != val)
+ break;
+ m->pos += n;
+ }
+ if(m->pos > m->end)
+ m->pos = m->end;
+}
+
+char*
+msg_getword(IxpMsg *m) {
+ char *ret;
+ Rune r;
+ int n;
+
+ eatrunes(m, isspacerune, true);
+ ret = m->pos;
+ eatrunes(m, isspacerune, false);
+ n = chartorune(&r, m->pos);
+ *m->pos = '\0';
+ m->pos += n;
+ eatrunes(m, isspacerune, true);
+
+ /* Filter out comments. */
+ if(*ret == '#') {
+ *ret = '\0';
+ m->pos = m->end;
+ }
+ if(*ret == '\\')
+ if(ret[1] == '\\' || ret[1] == '#')
+ ret++;
+ if(*ret == '\0')
+ return nil;
+ return ret;
+}
+
+#define strbcmp(str, const) (strncmp((str), (const), sizeof(const)-1))
+static int
+getbase(const char **s, long *sign) {
+ const char *p;
+ int ret;
+
+ ret = 10;
+ *sign = 1;
+ if(**s == '-') {
+ *sign = -1;
+ *s += 1;
+ }else if(**s == '+')
+ *s += 1;
+
+ p = *s;
+ if(!strbcmp(p, "0x")) {
+ *s += 2;
+ ret = 16;
+ }
+ else if(isdigit(p[0])) {
+ if(p[1] == 'r') {
+ *s += 2;
+ ret = p[0] - '0';
+ }
+ else if(isdigit(p[1]) && p[2] == 'r') {
+ *s += 3;
+ ret = 10*(p[0]-'0') + (p[1]-'0');
+ }
+ }
+ else if(p[0] == '0') {
+ ret = 8;
+ }
+ if(ret != 10 && (**s == '-' || **s == '+'))
+ *sign = 0;
+ return ret;
+}
+
+static bool
+getint(const char *s, int *ret) {
+ long l;
+ bool res;
+
+ res = getlong(s, &l);
+ *ret = l;
+ return res;
+}
+
+bool
+getlong(const char *s, long *ret) {
+ const char *end;
+ char *rend;
+ int base;
+ long sign;
+
+ if(s == nil)
+ return false;
+ end = s+strlen(s);
+ base = getbase(&s, &sign);
+ if(sign == 0)
+ return false;
+
+ *ret = sign * strtol(s, &rend, base);
+ return (end == rend);
+}
+
+bool
+getulong(const char *s, ulong *ret) {
+ const char *end;
+ char *rend;
+ int base;
+ long sign;
+
+ if(s == nil)
+ return false;
+ end = s+strlen(s);
+ base = getbase(&s, &sign);
+ if(sign < 1)
+ return false;
+
+ *ret = strtoul(s, &rend, base);
+ return (end == rend);
+}
+
+static char*
+strend(char *s, int n) {
+ int len;
+
+ len = strlen(s);
+ return s + max(0, len - n);
+}
+
+static Client*
+strclient(View *v, char *s) {
+ ulong id;
+
+ /*
+ * sel
+ * 0x<window xid>
+ */
+
+ if(s == nil)
+ return nil;
+ if(!strcmp(s, "sel"))
+ return view_selclient(v);
+ if(getulong(s, &id))
+ return win2client(id);
+
+ return nil;
+}
+
+Area*
+strarea(View *v, ulong scrn, const char *s) {
+ Area *a;
+ char *p;
+ long i;
+
+ /*
+ * sel
+ * ~
+ * <column number>
+ */
+
+ if(s == nil)
+ return nil;
+
+ if((p = strchr(s, ':'))) {
+ *p++ = '\0';
+ if(!strcmp(s, "sel"))
+ scrn = v->selscreen;
+ else if(!getulong(s, &scrn))
+ return nil;
+ s = p;
+ }
+ else if(!strcmp(s, "sel"))
+ return v->sel;
+
+ if(!strcmp(s, "sel")) {
+ if(scrn != v->selscreen)
+ return nil;
+ return v->sel;
+ }
+ if(!strcmp(s, "~"))
+ return v->floating;
+ if(scrn < 0 || !getlong(s, &i) || i == 0)
+ return nil;
+
+ if(i > 0) {
+ for(a = v->areas[scrn]; a; a = a->next)
+ if(i-- == 1) break;
+ }
+ else {
+ /* FIXME: Switch to circularly linked list. */
+ for(a = v->areas[scrn]; a->next; a = a->next)
+ ;
+ for(; a; a = a->prev)
+ if(++i == 0) break;
+ }
+ return a;
+}
+
+static Frame*
+getframe(View *v, int scrn, IxpMsg *m) {
+ Client *c;
+ Frame *f;
+ Area *a;
+ char *s;
+ ulong l;
+
+ s = msg_getword(m);
+ if(!s || !strcmp(s, "client")) {
+ c = strclient(v, msg_getword(m));
+ if(c == nil)
+ return nil;
+ return client_viewframe(c, v);
+ }
+
+ /* XXX: Multihead */
+ a = strarea(v, scrn, s);
+ if(a == nil) {
+ fprint(2, "a == nil\n");
+ return nil;
+ }
+
+ s = msg_getword(m);
+ if(!s)
+ return nil;
+ if(!strcmp(s, "sel"))
+ return a->sel;
+ if(!getulong(s, &l))
+ return nil;
+ for(f=a->frame; f; f=f->anext)
+ if(--l == 0) break;
+ return f;
+}
+
+char*
+readctl_client(Client *c) {
+ bufclear();
+ bufprint("%C\n", c);
+ if(c->fullscreen >= 0)
+ bufprint("Fullscreen %d\n", c->fullscreen);
+ else
+ bufprint("Fullscreen off\n");
+ bufprint("Urgent %s\n", toggletab[(int)c->urgent]);
+ return buffer;
+}
+
+char*
+message_client(Client *c, IxpMsg *m) {
+ char *s;
+ long l;
+ int i;
+
+ s = msg_getword(m);
+
+ /*
+ * Toggle ::= on
+ * | off
+ * | toggle
+ * | <screen>
+ * Fullscreen <toggle>
+ * Urgent <toggle>
+ * kill
+ * slay
+ */
+
+ switch(getsym(s)) {
+ case LFULLSCREEN:
+ s = msg_getword(m);
+ if(getlong(s, &l))
+ fullscreen(c, On, l);
+ else {
+ i = gettoggle(s);
+ if(i == -1)
+ return Ebadusage;
+ fullscreen(c, i, -1);
+ }
+ break;
+ case LKILL:
+ client_kill(c, true);
+ break;
+ case LSLAY:
+ client_kill(c, false);
+ break;
+ case LURGENT:
+ i = gettoggle(msg_getword(m));
+ if(i == -1)
+ return Ebadusage;
+ client_seturgent(c, i, UrgManager);
+ break;
+ default:
+ return Ebadcmd;
+ }
+ return nil;
+}
+
+char*
+message_root(void *p, IxpMsg *m) {
+ Font *fn;
+ char *s, *ret;
+ ulong n;
+ int i;
+
+ USED(p);
+ ret = nil;
+ s = msg_getword(m);
+ if(s == nil)
+ return nil;
+
+ if(!strcmp(s, "backtrace")) {
+ backtrace(m->pos);
+ return nil;
+ }
+
+ switch(getsym(s)) {
+ case LBAR: /* bar on? <"top" | "bottom"> */
+ s = msg_getword(m);
+ if(!strcmp(s, "on"))
+ s = msg_getword(m);
+ if(!setdef(&screen->barpos, s, barpostab, nelem(barpostab)))
+ return Ebadvalue;
+ view_update(selview);
+ break;
+ case LBORDER:
+ if(!getulong(msg_getword(m), &n))
+ return Ebadvalue;
+ def.border = n;
+ view_update(selview);
+ break;
+ case LCOLMODE:
+ s = msg_getword(m);
+ if(!setdef(&def.colmode, s, modes, Collast))
+ return Ebadvalue;
+ break;
+ case LDEBUG:
+ ret = msg_debug(m);
+ break;
+ case LEXEC:
+ execstr = strdup(m->pos);
+ srv.running = 0;
+ break;
+ case LSPAWN:
+ spawn_command(m->pos);
+ break;
+ case LFOCUSCOLORS:
+ ret = msg_parsecolors(m, &def.focuscolor);
+ view_update(selview);
+ break;
+ case LFONT:
+ fn = loadfont(m->pos);
+ if(fn) {
+ freefont(def.font);
+ def.font = fn;
+ for(n=0; n < nscreens; n++)
+ bar_resize(screens[n]);
+ }else
+ ret = "can't load font";
+ view_update(selview);
+ break;
+ case LFONTPAD:
+ if(!getint(msg_getword(m), &def.font->pad.min.x) ||
+ !getint(msg_getword(m), &def.font->pad.max.x) ||
+ !getint(msg_getword(m), &def.font->pad.max.y) ||
+ !getint(msg_getword(m), &def.font->pad.min.y))
+ ret = "invalid rectangle";
+ else {
+ for(n=0; n < nscreens; n++)
+ bar_resize(screens[n]);
+ view_update(selview);
+ }
+ break;
+ case LGRABMOD:
+ s = msg_getword(m);
+ if(!parsekey(s, &i, nil) || i == 0)
+ return Ebadvalue;
+
+ utflcpy(def.grabmod, s, sizeof def.grabmod);
+ def.mod = i;
+ break;
+ case LINCMODE:
+ if(!setdef(&def.incmode, msg_getword(m), incmodetab, nelem(incmodetab)))
+ return Ebadvalue;
+ view_update(selview);
+ break;
+ case LNORMCOLORS:
+ ret = msg_parsecolors(m, &def.normcolor);
+ view_update(selview);
+ break;
+ case LSELCOLORS:
+ warning("selcolors have been removed");
+ return Ebadcmd;
+ case LVIEW:
+ view_select(m->pos);
+ break;
+ case LQUIT:
+ srv.running = 0;
+ break;
+ default:
+ return Ebadcmd;
+ }
+ return ret;
+}
+
+static void
+printdebug(int mask) {
+ int i, j;
+
+ for(i=0, j=0; i < nelem(debugtab); i++)
+ if(mask & (1<<i)) {
+ if(j++ > 0) bufprint(" ");
+ bufprint("%s", debugtab[i]);
+ }
+}
+
+char*
+readctl_root(void) {
+ bufclear();
+ bufprint("bar on %s\n", barpostab[screen->barpos]);
+ bufprint("border %d\n", def.border);
+ bufprint("colmode %s\n", modes[def.colmode]);
+ if(debugflag) {
+ bufprint("debug ");
+ printdebug(debugflag);
+ bufprint("\n");
+ }
+ if(debugfile) {
+ bufprint("debugfile ");
+ printdebug(debugfile);
+ bufprint("\n");
+ }
+ bufprint("focuscolors %s\n", def.focuscolor.colstr);
+ bufprint("font %s\n", def.font->name);
+ bufprint("fontpad %d %d %d %d\n", def.font->pad.min.x, def.font->pad.max.x,
+ def.font->pad.max.y, def.font->pad.min.y);
+ bufprint("grabmod %s\n", def.grabmod);
+ bufprint("incmode %s\n", incmodetab[def.incmode]);
+ bufprint("normcolors %s\n", def.normcolor.colstr);
+ bufprint("view %s\n", selview->name);
+ return buffer;
+}
+
+char*
+message_view(View *v, IxpMsg *m) {
+ Area *a;
+ char *s;
+
+ s = msg_getword(m);
+ if(s == nil)
+ return nil;
+
+ /*
+ * area ::= ~
+ * | <column number>
+ * | sel
+ * direction ::= left
+ * | right
+ * | up
+ * | down
+ * # This *should* be identical to <frame>
+ * place ::= <column number>
+ * #| ~ # This should be, but isn't.
+ * | <direction>
+ * | toggle
+ * colmode ::= default
+ * | stack
+ * | normal
+ * column ::= sel
+ * | <column number>
+ * frame ::= up
+ * | down
+ * | left
+ * | right
+ * | toggle
+ * | client <window xid>
+ * | sel
+ * | ~
+ * | <column> <frame number>
+ * | <column>
+ * amount ::=
+ * | <number>
+ * | <number>px
+ *
+ * colmode <area> <colmode>
+ * select <area>
+ * send <frame> <place>
+ * swap <frame> <place>
+ * grow <thing> <direction> <amount>
+ * nudge <thing> <direction> <amount>
+ * select <ordframe>
+ */
+
+ switch(getsym(s)) {
+ case LCOLMODE:
+ s = msg_getword(m);
+ a = strarea(v, screen->idx, s);
+ if(a == nil) /* || a->floating) */
+ return Ebadvalue;
+
+ s = msg_getword(m);
+ if(s == nil || !column_setmode(a, s))
+ return Ebadvalue;
+
+ column_arrange(a, false);
+ view_restack(v);
+
+ view_update(v);
+ return nil;
+ case LGROW:
+ return msg_grow(v, m);
+ case LNUDGE:
+ return msg_nudge(v, m);
+ case LSELECT:
+ return msg_selectarea(v->sel, m);
+ case LSEND:
+ return msg_sendclient(v, m, false);
+ case LSWAP:
+ return msg_sendclient(v, m, true);
+ default:
+ return Ebadcmd;
+ }
+ /* not reached */
+}
+
+char*
+readctl_view(View *v) {
+ Area *a;
+ int s;
+
+ bufclear();
+ bufprint("%s\n", v->name);
+
+ /* select <area>[ <frame>] */
+ bufprint("select %a", v->sel);
+ if(v->sel->sel)
+ bufprint(" %d", frame_idx(v->sel->sel));
+ bufprint("\n");
+
+ /* select client <client> */
+ if(v->sel->sel)
+ bufprint("select client %C\n", v->sel->sel->client);
+
+ foreach_area(v, s, a)
+ bufprint("colmode %a %s\n", a, column_getmode(a));
+ return buffer;
+}
+
+char*
+msg_debug(IxpMsg *m) {
+ char *opt;
+ int d;
+ char add;
+
+ bufclear();
+ while((opt = msg_getword(m))) {
+ add = '+';
+ if(opt[0] == '+' || opt[0] == '-')
+ add = *opt++;
+ d = _bsearch(opt, debugtab, nelem(debugtab));
+ if(d == -1) {
+ bufprint(", %s", opt);
+ continue;
+ }
+ if(add == '+')
+ debugflag |= 1<<d;
+ else
+ debugflag &= ~(1<<d);
+ }
+ if(buffer[0] != '\0')
+ return sxprint("Bad debug options: %s", buffer+2);
+ return nil;
+}
+
+static bool
+getamt(IxpMsg *m, Point *amt) {
+ char *s, *p;
+ long l;
+
+ s = msg_getword(m);
+ if(s) {
+ p = strend(s, 2);
+ if(!strcmp(p, "px")) {
+ *p = '\0';
+ amt->x = 1;
+ amt->y = 1;
+ }
+
+ if(!getlong(s, &l))
+ return false;
+ amt->x *= l;
+ amt->y *= l;
+ }
+ return true;
+}
+
+static char*
+msg_grow(View *v, IxpMsg *m) {
+ Client *c;
+ Frame *f;
+ Rectangle r;
+ Point amount;
+ int dir;
+
+ f = getframe(v, screen->idx, m);
+ if(f == nil)
+ return "bad frame";
+ c = f->client;
+
+ dir = getdirection(m);
+ if(dir == -1)
+ return "bad direction";
+
+ amount.x = Dy(f->titlebar);
+ amount.y = Dy(f->titlebar);
+ if(amount.x < c->w.hints->inc.x)
+ amount.x = c->w.hints->inc.x;
+ if(amount.y < c->w.hints->inc.y)
+ amount.y = c->w.hints->inc.y;
+
+ if(!getamt(m, &amount))
+ return Ebadvalue;
+
+ if(f->area->floating)
+ r = f->r;
+ else
+ r = f->colr;
+ switch(dir) {
+ case LLEFT: r.min.x -= amount.x; break;
+ case LRIGHT: r.max.x += amount.x; break;
+ case LUP: r.min.y -= amount.y; break;
+ case LDOWN: r.max.y += amount.y; break;
+ default: abort();
+ }
+
+ if(f->area->floating)
+ float_resizeframe(f, r);
+ else
+ column_resizeframe(f, r);
+
+ return nil;
+}
+
+static char*
+msg_nudge(View *v, IxpMsg *m) {
+ Frame *f;
+ Rectangle r;
+ Point amount;
+ int dir;
+
+ f = getframe(v, screen->idx, m);
+ if(f == nil)
+ return "bad frame";
+
+ dir = getdirection(m);
+ if(dir == -1)
+ return "bad direction";
+
+ amount.x = Dy(f->titlebar);
+ amount.y = Dy(f->titlebar);
+ if(!getamt(m, &amount))
+ return Ebadvalue;
+
+ if(f->area->floating)
+ r = f->r;
+ else
+ r = f->colr;
+ switch(dir) {
+ case LLEFT: r = rectaddpt(r, Pt(-amount.x, 0)); break;
+ case LRIGHT: r = rectaddpt(r, Pt( amount.x, 0)); break;
+ case LUP: r = rectaddpt(r, Pt(0, -amount.y)); break;
+ case LDOWN: r = rectaddpt(r, Pt(0, amount.y)); break;
+ default: abort();
+ }
+
+ if(f->area->floating)
+ float_resizeframe(f, r);
+ else
+ column_resizeframe(f, r);
+
+ return nil;
+}
+
+char*
+msg_parsecolors(IxpMsg *m, CTuple *col) {
+ static char Ebad[] = "bad color string";
+ Rune r;
+ char c, *p;
+ int i, j;
+
+ /* '#%6x #%6x #%6x' */
+ p = m->pos;
+ for(i = 0; i < 3 && p < m->end; i++) {
+ if(*p++ != '#')
+ return Ebad;
+ for(j = 0; j < 6; j++)
+ if(p >= m->end || !isxdigit(*p++))
+ return Ebad;
+
+ chartorune(&r, p);
+ if(i < 2) {
+ if(r != ' ')
+ return Ebad;
+ p++;
+ }else if(*p != '\0' && !isspacerune(r))
+ return Ebad;
+ }
+
+ c = *p;
+ *p = '\0';
+ loadcolor(col, m->pos);
+ *p = c;
+
+ m->pos = p;
+ eatrunes(m, isspacerune, true);
+ return nil;
+}
+
+char*
+msg_selectarea(Area *a, IxpMsg *m) {
+ Frame *f;
+ Area *ap;
+ View *v;
+ char *s;
+ ulong i;
+ int sym;
+
+ v = a->view;
+ s = msg_getword(m);
+ sym = getsym(s);
+
+ switch(sym) {
+ case LTOGGLE:
+ if(!a->floating)
+ ap = v->floating;
+ else if(v->revert && v->revert != a)
+ ap = v->revert;
+ else
+ ap = v->firstarea;
+ break;
+ case LLEFT:
+ case LRIGHT:
+ case LUP:
+ case LDOWN:
+ case LCLIENT:
+ return msg_selectframe(a, m, sym);
+ case LTILDE:
+ ap = v->floating;
+ break;
+ default:
+ /* XXX: Multihead */
+ ap = strarea(v, a->screen, s);
+ if(!ap || ap->floating)
+ return Ebadvalue;
+ if((s = msg_getword(m))) {
+ if(!getulong(s, &i))
+ return Ebadvalue;
+ for(f = ap->frame; f; f = f->anext)
+ if(--i == 0) break;
+ if(i != 0)
+ return Ebadvalue;
+ frame_focus(f);
+ return nil;
+ }
+ }
+
+ area_focus(ap);
+ return nil;
+}
+
+static char*
+msg_selectframe(Area *a, IxpMsg *m, int sym) {
+ Client *c;
+ Frame *f, *fp;
+ char *s;
+ bool stack;
+ ulong i, dy;
+
+ f = a->sel;
+ fp = f;
+
+ stack = false;
+ if(sym == LUP || sym == LDOWN) {
+ s = msg_getword(m);
+ if(s)
+ if(!strcmp(s, "stack"))
+ stack = true;
+ else
+ return Ebadvalue;
+ }
+
+ if(sym == LCLIENT) {
+ s = msg_getword(m);
+ if(s == nil || !getulong(s, &i))
+ return "usage: select client <client>";
+ c = win2client(i);
+ if(c == nil)
+ return "unknown client";
+ f = client_viewframe(c, a->view);
+ if(!f)
+ return Ebadvalue;
+ }
+ else {
+ if(!find(&a, &f, DIR(sym), true, stack))
+ return Ebadvalue;
+ }
+
+ area_focus(a);
+
+ if(!f)
+ return nil;
+
+ /* XXX */
+ if(fp && fp->area == a)
+ if(f->collapsed && !f->area->floating && f->area->mode == Coldefault) {
+ dy = Dy(f->colr);
+ f->colr.max.y = f->colr.min.y + Dy(fp->colr);
+ fp->colr.max.y = fp->colr.min.y + dy;
+ column_arrange(a, false);
+ }
+
+ frame_focus(f);
+ frame_restack(f, nil);
+ if(f->view == selview)
+ view_restack(a->view);
+ return nil;
+}
+
+static char*
+sendarea(Frame *f, Area *to, bool swap) {
+ Client *c;
+
+ c = f->client;
+ if(!to)
+ return Ebadvalue;
+
+ if(!swap)
+ area_moveto(to, f);
+ else if(to->sel)
+ frame_swap(f, to->sel);
+ else
+ return Ebadvalue;
+
+ frame_focus(client_viewframe(c, f->view));
+ /* view_arrange(v); */
+ view_update_all();
+ return nil;
+}
+
+char*
+msg_sendclient(View *v, IxpMsg *m, bool swap) {
+ Area *to, *a;
+ Frame *f, *ff;
+ Client *c;
+ char *s;
+ int sym;
+
+ s = msg_getword(m);
+
+ c = strclient(v, s);
+ if(c == nil)
+ return Ebadvalue;
+
+ f = client_viewframe(c, v);
+ if(f == nil)
+ return Ebadvalue;
+
+ a = f->area;
+ to = nil;
+
+ s = msg_getword(m);
+ sym = getsym(s);
+
+ /* FIXME: Should use a helper function. */
+ switch(sym) {
+ case LUP:
+ case LDOWN:
+ return msg_sendframe(f, sym, swap);
+ case LLEFT:
+ if(a->floating)
+ return Ebadvalue;
+ to = a->prev;
+ break;
+ case LRIGHT:
+ if(a->floating)
+ return Ebadvalue;
+ to = a->next;
+ break;
+ case LTOGGLE:
+ if(!a->floating)
+ to = v->floating;
+ else if(f->column)
+ to = view_findarea(v, f->screen, f->column, true);
+ else
+ to = view_findarea(v, v->selscreen, v->selcol, true);
+ break;
+ case LTILDE:
+ if(a->floating)
+ return Ebadvalue;
+ to = v->floating;
+ break;
+ default:
+ to = strarea(v, v->selscreen, s);
+ // to = view_findarea(v, scrn, i, true);
+ break;
+ }
+
+
+ if(!to && !swap) {
+ /* XXX: Multihead - clean this up, move elsewhere. */
+ if(!f->anext && f == f->area->frame) {
+ ff = f;
+ to = a;
+ if(!find(&to, &ff, DIR(sym), false, false))
+ return Ebadvalue;
+ }
+ else {
+ to = (sym == LLEFT) ? nil : a;
+ to = column_new(v, to, a->screen, 0);
+ }
+ }
+
+ return sendarea(f, to, swap);
+}
+
+static char*
+msg_sendframe(Frame *f, int sym, bool swap) {
+ Client *c;
+ Area *a;
+ Frame *fp;
+
+ SET(fp);
+ c = f->client;
+
+ a = f->area;
+ fp = f;
+ if(!find(&a, &fp, DIR(sym), false, false))
+ return Ebadvalue;
+ if(a != f->area)
+ return sendarea(f, a, swap);
+
+ switch(sym) {
+ case LUP:
+ fp = f->aprev;
+ if(!fp)
+ return Ebadvalue;
+ if(!swap)
+ fp = fp->aprev;
+ break;
+ case LDOWN:
+ fp = f->anext;
+ if(!fp)
+ return Ebadvalue;
+ break;
+ default:
+ die("can't get here");
+ }
+
+ if(swap)
+ frame_swap(f, fp);
+ else {
+ frame_remove(f);
+ frame_insert(f, fp);
+ }
+
+ /* view_arrange(f->view); */
+
+ frame_focus(client_viewframe(c, f->view));
+ view_update_all();
+ return nil;
+}
+
+void
+warning(const char *fmt, ...) {
+ va_list ap;
+ char *s;
+
+ va_start(ap, fmt);
+ s = vsmprint(fmt, ap);
+ va_end(ap);
+
+ event("Warning %s\n", s);
+ fprint(2, "%s: warning: %s\n", argv0, s);
+ free(s);
+}
+
diff --git a/cmd/wmii/mouse.c b/cmd/wmii/mouse.c
new file mode 100644
index 0000000..fb7e2ff
--- /dev/null
+++ b/cmd/wmii/mouse.c
@@ -0,0 +1,642 @@
+/* Copyright ©2006-2010 Kris Maglione <maglione.k at Gmail>
+ * See LICENSE file for license details.
+ */
+#include "dat.h"
+#include "fns.h"
+
+/* Here be dragons. */
+
+enum {
+ ButtonMask =
+ ButtonPressMask | ButtonReleaseMask,
+ MouseMask =
+ ButtonMask | PointerMotionMask
+};
+
+static void
+cwin_expose(Window *w, XExposeEvent *e) {
+
+ fill(w, rectsubpt(w->r, w->r.min), def.focuscolor.bg);
+ fill(w, w->r, def.focuscolor.bg);
+}
+
+static Handlers chandler = {
+ .expose = cwin_expose,
+};
+
+Window*
+constraintwin(Rectangle r) {
+ Window *w;
+ WinAttr wa;
+
+ w = createwindow(&scr.root, r, 0, InputOnly, &wa, 0);
+ if(0) {
+ Window *w2;
+
+ w2 = createwindow(&scr.root, r, 0, InputOutput, &wa, 0);
+ selectinput(w2, ExposureMask);
+ w->aux = w2;
+
+ setborder(w2, 1, def.focuscolor.border);
+ sethandler(w2, &chandler);
+ mapwin(w2);
+ raisewin(w2);
+ }
+ mapwin(w);
+ return w;
+}
+
+void
+destroyconstraintwin(Window *w) {
+ Window *w2;
+
+ if(w->aux) {
+ w2 = w->aux;
+ sethandler(w2, nil);
+ destroywindow(w2);
+ }
+ destroywindow(w);
+}
+
+static Window*
+gethsep(Rectangle r) {
+ Window *w;
+ WinAttr wa;
+
+ wa.background_pixel = def.normcolor.border.pixel;
+ w = createwindow(&scr.root, r, scr.depth, InputOutput, &wa, CWBackPixel);
+ mapwin(w);
+ raisewin(w);
+ return w;
+}
+
+static void
+rect_morph(Rectangle *r, Point d, Align *mask) {
+ int n;
+
+ if(*mask & North)
+ r->min.y += d.y;
+ if(*mask & West)
+ r->min.x += d.x;
+ if(*mask & South)
+ r->max.y += d.y;
+ if(*mask & East)
+ r->max.x += d.x;
+
+ if(r->min.x > r->max.x) {
+ n = r->min.x;
+ r->min.x = r->max.x;
+ r->max.x = n;
+ *mask ^= East|West;
+ }
+ if(r->min.y > r->max.y) {
+ n = r->min.y;
+ r->min.y = r->max.y;
+ r->max.y = n;
+ *mask ^= North|South;
+ }
+}
+
+/* Yes, yes, macros are evil. So are patterns. */
+#define frob(x, y) \
+ const Rectangle *rp; \
+ int i, tx; \
+ \
+ for(i=0; i < nrect; i++) { \
+ rp = &rects[i]; \
+ if((rp->min.y <= r->max.y) && (rp->max.y >= r->min.y)) { \
+ tx = rp->min.x; \
+ if(abs(tx - x) <= abs(dx)) \
+ dx = tx - x; \
+ \
+ tx = rp->max.x; \
+ if(abs(tx - x) <= abs(dx)) \
+ dx = tx - x; \
+ } \
+ } \
+ return dx \
+
+static int
+snap_hline(const Rectangle *rects, int nrect, int dx, const Rectangle *r, int y) {
+ frob(y, x);
+}
+
+static int
+snap_vline(const Rectangle *rects, int nrect, int dx, const Rectangle *r, int x) {
+ frob(x, y);
+}
+
+#undef frob
+
+/* Returns a gravity for increment handling. It's normally the
+ * opposite of the mask (the directions that we're resizing in),
+ * unless a snap occurs, in which case, it's the direction of the
+ * snap.
+ */
+Align
+snap_rect(const Rectangle *rects, int num, Rectangle *r, Align *mask, int snap) {
+ Align ret;
+ Point d;
+
+ d.x = snap+1;
+ d.y = snap+1;
+
+ if(*mask&North)
+ d.y = snap_hline(rects, num, d.y, r, r->min.y);
+ if(*mask&South)
+ d.y = snap_hline(rects, num, d.y, r, r->max.y);
+
+ if(*mask&East)
+ d.x = snap_vline(rects, num, d.x, r, r->max.x);
+ if(*mask&West)
+ d.x = snap_vline(rects, num, d.x, r, r->min.x);
+
+ ret = Center;
+ if(abs(d.x) <= snap)
+ ret ^= East|West;
+ else
+ d.x = 0;
+
+ if(abs(d.y) <= snap)
+ ret ^= North|South;
+ else
+ d.y = 0;
+
+ rect_morph(r, d, mask);
+ return ret ^ *mask;
+}
+
+int
+readmouse(Point *p, uint *button) {
+ XEvent ev;
+
+ for(;;) {
+ XMaskEvent(display, MouseMask|ExposureMask|PropertyChangeMask, &ev);
+ switch(ev.type) {
+ case Expose:
+ case NoExpose:
+ case PropertyNotify:
+ dispatch_event(&ev);
+ default:
+ Dprint(DEvent, "readmouse(): ignored: %E\n", &ev);
+ continue;
+ case ButtonPress:
+ case ButtonRelease:
+ *button = ev.xbutton.button;
+ case MotionNotify:
+ p->x = ev.xmotion.x_root;
+ p->y = ev.xmotion.y_root;
+ break;
+ }
+ return ev.type;
+ }
+}
+
+bool
+readmotion(Point *p) {
+ uint button;
+
+ for(;;)
+ switch(readmouse(p, &button)) {
+ case MotionNotify:
+ return true;
+ case ButtonRelease:
+ return false;
+ }
+}
+
+static void
+mouse_resizecolframe(Frame *f, Align align) {
+ Window *cwin, *hwin;
+ Divide *d;
+ View *v;
+ Area *a;
+ Rectangle r;
+ Point pt, min;
+ int s;
+
+ assert((align&(East|West)) != (East|West));
+ assert((align&(North|South)) != (North|South));
+
+ f->collapsed = false;
+
+ v = selview;
+ d = divs;
+ SET(a);
+ foreach_column(v, s, a) {
+ if(a == f->area)
+ break;
+ d = d->next;
+ }
+
+ if(align&East)
+ d = d->next;
+
+ min.x = column_minwidth();
+ min.y = /*frame_delta_h() +*/ labelh(def.font);
+ /* Set the limits of where this box may be dragged. */
+#define frob(pred, f, aprev, rmin, rmax, plus, minus, xy) BLOCK( \
+ if(pred) { \
+ r.rmin.xy = f->aprev->r.rmin.xy plus min.xy; \
+ r.rmax.xy = f->r.rmax.xy minus min.xy; \
+ }else { \
+ r.rmin.xy = a->r.rmin.xy; \
+ r.rmax.xy = r.rmin.xy plus 1; \
+ })
+ if(align&North)
+ frob(f->aprev, f, aprev, min, max, +, -, y);
+ else
+ frob(f->anext, f, anext, max, min, -, +, y);
+ if(align&West)
+ frob(a->prev, a, prev, min, max, +, -, x);
+ else
+ frob(a->next, a, next, max, min, -, +, x);
+#undef frob
+
+ cwin = constraintwin(r);
+
+ r = f->r;
+ if(align&North)
+ r.min.y--;
+ else
+ r.min.y = r.max.y - 1;
+ r.max.y = r.min.y + 2;
+
+ hwin = gethsep(r);
+
+ if(!grabpointer(&scr.root, cwin, cursor[CurSizing], MouseMask))
+ goto done;
+
+ pt.x = ((align&West) ? f->r.min.x : f->r.max.x);
+ pt.y = ((align&North) ? f->r.min.y : f->r.max.y);
+ warppointer(pt);
+
+ while(readmotion(&pt)) {
+ if(align&West)
+ r.min.x = pt.x;
+ else
+ r.max.x = pt.x;
+ r.min.y = ((align&South) ? pt.y : pt.y-1);
+ r.max.y = r.min.y+2;
+
+ div_set(d, pt.x);
+ reshapewin(hwin, r);
+ }
+
+ r = f->r;
+ if(align&West)
+ r.min.x = pt.x;
+ else
+ r.max.x = pt.x;
+ if(align&North)
+ r.min.y = pt.y;
+ else
+ r.max.y = pt.y;
+ column_resizeframe(f, r);
+
+ /* XXX: Magic number... */
+ if(align&West)
+ pt.x = f->r.min.x + 4;
+ else
+ pt.x = f->r.max.x - 4;
+ if(align&North)
+ pt.y = f->r.min.y + 4;
+ else
+ pt.y = f->r.max.y - 4;
+ warppointer(pt);
+
+done:
+ ungrabpointer();
+ destroyconstraintwin(cwin);
+ destroywindow(hwin);
+}
+
+void
+mouse_resizecol(Divide *d) {
+ Window *cwin;
+ View *v;
+ Rectangle r;
+ Point pt;
+ int minw, scrn;
+
+ v = selview;
+
+ scrn = (d->left ? d->left : d->right)->screen;
+
+ pt = querypointer(&scr.root);
+
+ minw = column_minwidth();
+ r.min.x = d->left ? d->left->r.min.x + minw : v->r[scrn].min.x;
+ r.max.x = d->right ? d->right->r.max.x - minw : v->r[scrn].max.x;
+ r.min.y = pt.y;
+ r.max.y = pt.y+1;
+
+ cwin = constraintwin(r);
+
+ if(!grabpointer(&scr.root, cwin, cursor[CurNone], MouseMask))
+ goto done;
+
+ while(readmotion(&pt))
+ div_set(d, pt.x);
+
+ if(d->left)
+ d->left->r.max.x = pt.x;
+ else
+ v->pad[scrn].min.x = pt.x - v->r[scrn].min.x;
+
+ if(d->right)
+ d->right->r.min.x = pt.x;
+ else
+ v->pad[scrn].max.x = pt.x - v->r[scrn].max.x;
+
+ view_arrange(v);
+
+done:
+ ungrabpointer();
+ destroyconstraintwin(cwin);
+}
+
+void
+mouse_resize(Client *c, Align align, bool grabmod) {
+ Rectangle *rects;
+ Rectangle frect, origin;
+ Align grav;
+ Cursor cur;
+ Point d, pt, hr;
+ float rx, ry, hrx, hry;
+ uint nrect;
+ Frame *f;
+
+ f = c->sel;
+ if(f->client->fullscreen >= 0) {
+ ungrabpointer();
+ return;
+ }
+ if(!f->area->floating) {
+ if(align==Center)
+ mouse_movegrabbox(c, grabmod);
+ else
+ mouse_resizecolframe(f, align);
+ return;
+ }
+
+ cur = quad_cursor(align);
+ if(align == Center)
+ cur = cursor[CurSizing];
+
+ if(!grabpointer(c->framewin, nil, cur, MouseMask))
+ return;
+
+ origin = f->r;
+ frect = f->r;
+ rects = view_rects(f->area->view, &nrect, c->frame);
+
+ pt = querypointer(c->framewin);
+ rx = (float)pt.x / Dx(frect);
+ ry = (float)pt.y / Dy(frect);
+
+ pt = querypointer(&scr.root);
+
+ SET(hrx);
+ SET(hry);
+ if(align != Center) {
+ hr = subpt(frect.max, frect.min);
+ hr = divpt(hr, Pt(2, 2));
+ d = hr;
+ if(align&North) d.y -= hr.y;
+ if(align&South) d.y += hr.y;
+ if(align&East) d.x += hr.x;
+ if(align&West) d.x -= hr.x;
+
+ pt = addpt(d, f->r.min);
+ warppointer(pt);
+ }else {
+ hrx = (double)(Dx(scr.rect)
+ + Dx(frect)
+ - 2 * labelh(def.font))
+ / Dx(scr.rect);
+ hry = (double)(Dy(scr.rect)
+ + Dy(frect)
+ - 3 * labelh(def.font))
+ / Dy(scr.rect);
+
+ pt.x = frect.max.x - labelh(def.font);
+ pt.y = frect.max.y - labelh(def.font);
+ d.x = pt.x / hrx;
+ d.y = pt.y / hry;
+
+ warppointer(d);
+ }
+ sync();
+ flushevents(PointerMotionMask, false);
+
+ while(readmotion(&d)) {
+ if(align == Center) {
+ d.x = (d.x * hrx) - pt.x;
+ d.y = (d.y * hry) - pt.y;
+ }else
+ d = subpt(d, pt);
+ pt = addpt(pt, d);
+
+ rect_morph(&origin, d, &align);
+ frect = constrain(origin, -1);
+
+ grav = snap_rect(rects, nrect, &frect, &align, def.snap);
+
+ frect = frame_hints(f, frect, grav);
+ frect = constrain(frect, -1);
+
+ client_resize(c, frect);
+ }
+
+ pt = addpt(c->framewin->r.min,
+ Pt(Dx(frect) * rx,
+ Dy(frect) * ry));
+ if(pt.y > scr.rect.max.y)
+ pt.y = scr.rect.max.y - 1;
+ warppointer(pt);
+
+ free(rects);
+ ungrabpointer();
+}
+
+static int
+pushstack_down(Frame *f, int y) {
+ int ret;
+ int dh, dy;
+
+ if(f == nil)
+ return 0;;
+ ret = 0;
+ dy = y - f->colr.min.y;
+ if(dy < 0)
+ return 0;
+ if(!f->collapsed) {
+ dh = Dy(f->colr) - labelh(def.font);
+ if(dy <= dh) {
+ f->colr.min.y += dy;
+ return dy;
+ }else {
+ f->collapsed = true;
+ f->colr.min.y += dh;
+ ret = dh;
+ dy -= dh;
+ }
+ }
+ dy = pushstack_down(f->anext, f->colr.max.y + dy);
+ f->colr.min.y += dy;
+ f->colr.max.y += dy;
+ return ret + dy;
+}
+
+static int
+pushstack_up(Frame *f, int y) {
+ int ret;
+ int dh, dy;
+
+ if(f == nil)
+ return 0;
+ ret = 0;
+ dy = f->colr.max.y - y;
+ if(dy < 0)
+ return 0;
+ if(!f->collapsed) {
+ dh = Dy(f->colr) - labelh(def.font);
+ if(dy <= dh) {
+ f->colr.max.y -= dy;
+ return dy;
+ }else {
+ f->collapsed = true;
+ f->colr.max.y -= dh;
+ ret = dh;
+ dy -= dh;
+ }
+ }
+ dy = pushstack_up(f->aprev, f->colr.min.y - dy);
+ f->colr.min.y -= dy;
+ f->colr.max.y -= dy;
+ return ret + dy;
+}
+
+static void
+mouse_tempvertresize(Area *a, Point p) {
+ Frame *fa, *fb, *f;
+ Window *cwin;
+ Rectangle r;
+ Point pt;
+ int incmode, nabove, nbelow;
+
+ if(a->mode != Coldefault)
+ return;
+
+ for(fa=a->frame; fa; fa=fa->anext)
+ if(p.y < fa->r.max.y + labelh(def.font)/2)
+ break;
+ if(!(fa && fa->anext))
+ return;
+ fb = fa->anext;
+ nabove=0;
+ nbelow=0;
+ for(f=fa; f; f=f->aprev)
+ nabove++;
+ for(f=fa->anext; f; f=f->anext)
+ nbelow++;
+
+ incmode = def.incmode;
+ def.incmode = IIgnore;
+ resizing = true;
+ column_arrange(a, false);
+
+ r.min.x = p.x;
+ r.max.x = p.x + 1;
+ r.min.y = a->r.min.y + labelh(def.font) * nabove;
+ r.max.y = a->r.max.y - labelh(def.font) * nbelow;
+ cwin = constraintwin(r);
+
+ if(!grabpointer(&scr.root, cwin, cursor[CurDVArrow], MouseMask))
+ goto done;
+
+ for(f=a->frame; f; f=f->anext)
+ f->colr_old = f->colr;
+
+ while(readmotion(&pt)) {
+ for(f=a->frame; f; f=f->anext) {
+ f->collapsed = false;
+ f->colr = f->colr_old;
+ }
+ if(pt.y > p.y)
+ pushstack_down(fb, pt.y);
+ else
+ pushstack_up(fa, pt.y);
+ fa->colr.max.y = pt.y;
+ fb->colr.min.y = pt.y;
+ column_frob(a);
+ }
+
+done:
+ ungrabpointer();
+ destroyconstraintwin(cwin);
+ def.incmode = incmode;
+ resizing = false;
+ column_arrange(a, false);
+}
+
+void
+mouse_checkresize(Frame *f, Point p, bool exec) {
+ Rectangle r;
+ Cursor cur;
+ int q;
+
+ cur = cursor[CurNormal];
+ if(rect_haspoint_p(p, f->crect)) {
+ client_setcursor(f->client, cur);
+ return;
+ }
+
+ r = rectsubpt(f->r, f->r.min);
+ q = quadrant(r, p);
+ if(rect_haspoint_p(p, f->grabbox)) {
+ cur = cursor[CurTCross];
+ if(exec)
+ mouse_movegrabbox(f->client, false);
+ }
+ else if(f->area->floating) {
+ if(p.x <= 2
+ || p.y <= 2
+ || r.max.x - p.x <= 2
+ || r.max.y - p.y <= 2) {
+ cur = quad_cursor(q);
+ if(exec)
+ mouse_resize(f->client, q, false);
+ }
+ else if(exec && rect_haspoint_p(p, f->titlebar))
+ mouse_movegrabbox(f->client, true);
+ }else {
+ if(f->aprev && p.y <= 2
+ || f->anext && r.max.y - p.y <= 2) {
+ cur = cursor[CurDVArrow];
+ if(exec)
+ mouse_tempvertresize(f->area, addpt(p, f->r.min));
+ }
+ }
+
+ if(!exec)
+ client_setcursor(f->client, cur);
+}
+
+static void
+_grab(XWindow w, uint button, ulong mod) {
+ XGrabButton(display, button, mod, w, false, ButtonMask,
+ GrabModeSync, GrabModeAsync, None, None);
+}
+
+/* Doesn't belong here */
+void
+grab_button(XWindow w, uint button, ulong mod) {
+ _grab(w, button, mod);
+ if((mod != AnyModifier) && numlock_mask) {
+ _grab(w, button, mod | numlock_mask);
+ _grab(w, button, mod | numlock_mask | LockMask);
+ }
+}
+
diff --git a/cmd/wmii/print.c b/cmd/wmii/print.c
new file mode 100644
index 0000000..cddf609
--- /dev/null
+++ b/cmd/wmii/print.c
@@ -0,0 +1,124 @@
+#include "dat.h"
+#include <fmt.h>
+#include "fns.h"
+
+static char* fcnames[] = {
+ "TVersion",
+ "RVersion",
+ "TAuth",
+ "RAuth",
+ "TAttach",
+ "RAttach",
+ "TError",
+ "RError",
+ "TFlush",
+ "RFlush",
+ "TWalk",
+ "RWalk",
+ "TOpen",
+ "ROpen",
+ "TCreate",
+ "RCreate",
+ "TRead",
+ "RRead",
+ "TWrite",
+ "RWrite",
+ "TClunk",
+ "RClunk",
+ "TRemove",
+ "RRemove",
+ "TStat",
+ "RStat",
+ "TWStat",
+ "RWStat",
+};
+
+static int
+qid(Fmt *f, Qid *q) {
+ return fmtprint(f, "(%uhd,%uld,%ullx)", q->type, q->version, q->path);
+}
+
+int
+Ffmt(Fmt *f) {
+ Fcall *fcall;
+
+ fcall = va_arg(f->args, Fcall*);
+ fmtprint(f, "% 2d %s\t", fcall->hdr.tag, fcnames[fcall->hdr.type - TVersion]);
+ switch(fcall->hdr.type) {
+ case TVersion:
+ case RVersion:
+ fmtprint(f, " msize: %uld version: \"%s\"", (ulong)fcall->version.msize, fcall->version.version);
+ break;
+ case TAuth:
+ fmtprint(f, " afid: %uld uname: \"%s\" aname: \"%s\"", (ulong)fcall->tauth.afid, fcall->tauth.uname, fcall->tauth.aname);
+ break;
+ case RAuth:
+ fmtprint(f, " aqid: ");
+ qid(f, &fcall->rauth.aqid);
+ break;
+ case RAttach:
+ fmtprint(f, " qid: ");
+ qid(f, &fcall->rattach.qid);
+ break;
+ case TAttach:
+ fmtprint(f, " fid: %uld afid: %uld uname: \"%s\" aname: \"%s\"", (ulong)fcall->hdr.fid, (ulong)fcall->tattach.afid, fcall->tattach.uname, fcall->tattach.aname);
+ break;
+ case RError:
+ fmtprint(f, " \"%s\"", fcall->error.ename);
+ break;
+ case TFlush:
+ fmtprint(f, " oldtag: %uld", (ulong)fcall->tflush.oldtag);
+ break;
+ case TWalk:
+ fmtprint(f, " newfid: %uld wname: {", (ulong)fcall->twalk.newfid);
+ for(int i=0; i<fcall->twalk.nwname; i++) {
+ if(i > 0) fmtprint(f, ", ");
+ fmtprint(f, "\"%s\"", fcall->twalk.wname[i]);
+ }
+ fmtprint(f, "}");
+ break;
+ case RWalk:
+ fmtprint(f, " wqid: {");
+ for(int i=0; i<fcall->rwalk.nwqid; i++) {
+ if(i > 0) fmtprint(f, ", ");
+ qid(f, &fcall->rwalk.wqid[i]);
+ }
+ fmtprint(f, "}");
+ break;
+ case TOpen:
+ fmtprint(f, " fid: %uld mode: %ulo", (ulong)fcall->hdr.fid, (ulong)fcall->topen.mode);
+ break;
+ case ROpen:
+ case RCreate:
+ fmtprint(f, " qid: ");
+ qid(f, &fcall->ropen.qid);
+ fmtprint(f, " %uld", (ulong)fcall->ropen.iounit);
+ break;
+ case TCreate:
+ fmtprint(f, " fid: %uld name: \"%s\" perm: %ulo mode: %ulo", (ulong)fcall->hdr.fid, fcall->tcreate.name, (ulong)fcall->tcreate.perm, (ulong)fcall->tcreate.mode);
+ break;
+ case TRead:
+ fmtprint(f, " fid: %uld offset: %ulld count: %uld", (ulong)fcall->hdr.fid, fcall->tread.offset, (ulong)fcall->tread.count);
+ break;
+ case RRead:
+ fmtprint(f, " data: {data: %uld}", fcall->rread.count);
+ break;
+ case TWrite:
+ fmtprint(f, " fid: %uld offset: %ulld data: {data: %uld}", (ulong)fcall->hdr.fid, fcall->twrite.offset, fcall->twrite.count);
+ break;
+ case RWrite:
+ fmtprint(f, " count: %uld", (ulong)fcall->rwrite.count);
+ break;
+ case TClunk:
+ case TRemove:
+ case TStat:
+ fmtprint(f, " fid: %uld", (ulong)fcall->hdr.fid);
+ break;
+ case RStat:
+ fmtprint(f, " stat: {data: %uld}", fcall->rstat.nstat);
+ break;
+ }
+
+ return 0;
+}
+
diff --git a/cmd/wmii/printevent.c b/cmd/wmii/printevent.c
new file mode 100644
index 0000000..5b52c43
--- /dev/null
+++ b/cmd/wmii/printevent.c
@@ -0,0 +1,994 @@
+/*
+ * Original code posted to comp.sources.x
+ * Modifications by Russ Cox <rsc@swtch.com>.
+ * Further modifications by Kris Maglione <maglione.k at Gmail>
+ */
+
+/*
+ * Path: uunet!wyse!mikew From: mikew@wyse.wyse.com (Mike Wexler) Newsgroups:
+ * comp.sources.x Subject: v02i056: subroutine to print events in human
+ * readable form, Part01/01 Message-ID: <1935@wyse.wyse.com> Date: 22 Dec 88
+ * 19:28:25 GMT Organization: Wyse Technology, San Jose Lines: 1093 Approved:
+ * mikew@wyse.com
+ *
+ * Submitted-by: richsun!darkstar!ken Posting-number: Volume 2, Issue 56
+ * Archive-name: showevent/part01
+ *
+ *
+ * There are times during debugging when it would be real useful to be able to
+ * print the fields of an event in a human readable form. Too many times I
+ * found myself scrounging around in section 8 of the Xlib manual looking for
+ * the valid fields for the events I wanted to see, then adding printf's to
+ * display the numeric values of the fields, and then scanning through X.h
+ * trying to decode the cryptic detail and state fields. After playing with
+ * xev, I decided to write a couple of standard functions that I could keep
+ * in a library and call on whenever I needed a little debugging verbosity.
+ * The first function, GetType(), is useful for returning the string
+ * representation of the type of an event. The second function, ShowEvent(),
+ * is used to display all the fields of an event in a readable format. The
+ * functions are not complicated, in fact, they are mind-numbingly boring -
+ * but that's just the point nobody wants to spend the time writing functions
+ * like this, they just want to have them when they need them.
+ *
+ * A simple, sample program is included which does little else but to
+ * demonstrate the use of these two functions. These functions have saved me
+ * many an hour during debugging and I hope you find some benefit to these.
+ * If you have any comments, suggestions, improvements, or if you find any
+ * blithering errors you can get it touch with me at the following location:
+ *
+ * ken@richsun.UUCP
+ */
+
+#include "dat.h"
+#include <stdarg.h>
+#include <bio.h>
+//#include "fns.h"
+#include "printevent.h"
+#define Window XWindow
+
+#define nil ((void*)0)
+
+typedef struct Pair Pair;
+
+struct Pair {
+ int key;
+ char *val;
+};
+
+static char* sep = " ";
+
+static char *
+search(Pair *lst, int key, char *(*def)(int)) {
+ for(; lst->val; lst++)
+ if(lst->key == key)
+ return lst->val;
+ return def(key);
+}
+
+static char*
+unmask(Pair *list, uint val)
+{
+ Pair *p;
+ char *s, *end;
+ int n;
+
+ buffer[0] = '\0';
+ end = buffer + sizeof buffer;
+ s = buffer;
+
+ n = 0;
+ s = utfecpy(s, end, "(");
+ for (p = list; p->val; p++)
+ if (val & p->key) {
+ if(n++)
+ s = utfecpy(s, end, "|");
+ s = utfecpy(s, end, p->val);
+ }
+ utfecpy(s, end, ")");
+
+ return buffer;
+}
+
+static char *
+strhex(int key) {
+ sprint(buffer, "0x%x", key);
+ return buffer;
+}
+
+static char *
+strdec(int key) {
+ sprint(buffer, "%d", key);
+ return buffer;
+}
+
+static char *
+strign(int key) {
+ USED(key);
+
+ return "?";
+}
+
+/******************************************************************************/
+/**** Miscellaneous routines to convert values to their string equivalents ****/
+/******************************************************************************/
+
+static void
+TInt(Fmt *b, va_list *ap) {
+ fmtprint(b, "%d", va_arg(*ap, int));
+}
+
+static void
+TWindow(Fmt *b, va_list *ap) {
+ Window w;
+
+ w = va_arg(*ap, Window);
+ fmtprint(b, "0x%ux", (uint)w);
+}
+
+static void
+TData(Fmt *b, va_list *ap) {
+ long *l;
+ int i;
+
+ l = va_arg(*ap, long*);
+ fmtprint(b, "{");
+ for (i = 0; i < 5; i++) {
+ if(i > 0)
+ fmtprint(b, ", ");
+ fmtprint(b, "0x%08lx", l[i]);
+ }
+ fmtprint(b, "}");
+}
+
+/* Returns the string equivalent of a timestamp */
+static void
+TTime(Fmt *b, va_list *ap) {
+ ldiv_t d;
+ ulong msec;
+ ulong sec;
+ ulong min;
+ ulong hr;
+ ulong day;
+ Time time;
+
+ time = va_arg(*ap, Time);
+
+ msec = time/1000;
+ d = ldiv(msec, 60);
+ msec = time-msec*1000;
+
+ sec = d.rem;
+ d = ldiv(d.quot, 60);
+ min = d.rem;
+ d = ldiv(d.quot, 24);
+ hr = d.rem;
+ day = d.quot;
+
+#ifdef notdef
+ sprintf(buffer, "%lu day%s %02lu:%02lu:%02lu.%03lu",
+ day, day == 1 ? "" : "(s)", hr, min, sec, msec);
+#endif
+
+ fmtprint(b, "%ludd_%ludh_%ludm_%lud.%03luds", day, hr, min, sec, msec);
+}
+
+/* Returns the string equivalent of a boolean parameter */
+static void
+TBool(Fmt *b, va_list *ap) {
+ static Pair list[] = {
+ {True, "True"},
+ {False, "False"},
+ {0, nil},
+ };
+ Bool key;
+
+ key = va_arg(*ap, Bool);
+ fmtprint(b, "%s", search(list, key, strign));
+}
+
+/* Returns the string equivalent of a property notify state */
+static void
+TPropState(Fmt *b, va_list *ap) {
+ static Pair list[] = {
+ {PropertyNewValue, "PropertyNewValue"},
+ {PropertyDelete, "PropertyDelete"},
+ {0, nil},
+ };
+ uint key;
+
+ key = va_arg(*ap, uint);
+ fmtprint(b, "%s", search(list, key, strign));
+}
+
+/* Returns the string equivalent of a visibility notify state */
+static void
+TVis(Fmt *b, va_list *ap) {
+ static Pair list[] = {
+ {VisibilityUnobscured, "VisibilityUnobscured"},
+ {VisibilityPartiallyObscured, "VisibilityPartiallyObscured"},
+ {VisibilityFullyObscured, "VisibilityFullyObscured"},
+ {0, nil},
+ };
+ int key;
+
+ key = va_arg(*ap, int);
+ fmtprint(b, "%s", search(list, key, strign));
+}
+
+/* Returns the string equivalent of a mask of buttons and/or modifier keys */
+static void
+TModState(Fmt *b, va_list *ap) {
+ static Pair list[] = {
+ {Button1Mask, "Button1Mask"},
+ {Button2Mask, "Button2Mask"},
+ {Button3Mask, "Button3Mask"},
+ {Button4Mask, "Button4Mask"},
+ {Button5Mask, "Button5Mask"},
+ {ShiftMask, "ShiftMask"},
+ {LockMask, "LockMask"},
+ {ControlMask, "ControlMask"},
+ {Mod1Mask, "Mod1Mask"},
+ {Mod2Mask, "Mod2Mask"},
+ {Mod3Mask, "Mod3Mask"},
+ {Mod4Mask, "Mod4Mask"},
+ {Mod5Mask, "Mod5Mask"},
+ {0, nil},
+ };
+ uint state;
+
+ state = va_arg(*ap, uint);
+ fmtprint(b, "%s", unmask(list, state));
+}
+
+/* Returns the string equivalent of a mask of configure window values */
+static void
+TConfMask(Fmt *b, va_list *ap) {
+ static Pair list[] = {
+ {CWX, "CWX"},
+ {CWY, "CWY"},
+ {CWWidth, "CWWidth"},
+ {CWHeight, "CWHeight"},
+ {CWBorderWidth, "CWBorderWidth"},
+ {CWSibling, "CWSibling"},
+ {CWStackMode, "CWStackMode"},
+ {0, nil},
+ };
+ uint valuemask;
+
+ valuemask = va_arg(*ap, uint);
+ fmtprint(b, "%s", unmask(list, valuemask));
+}
+
+/* Returns the string equivalent of a motion hint */
+#if 0
+static void
+IsHint(Fmt *b, va_list *ap) {
+ static Pair list[] = {
+ {NotifyNormal, "NotifyNormal"},
+ {NotifyHint, "NotifyHint"},
+ {0, nil},
+ };
+ char key;
+
+ key = va_arg(*ap, char);
+ fmtprint(b, "%s", search(list, key, strign));
+}
+#endif
+
+/* Returns the string equivalent of an id or the value "None" */
+static void
+TIntNone(Fmt *b, va_list *ap) {
+ static Pair list[] = {
+ {None, "None"},
+ {0, nil},
+ };
+ int key;
+
+ key = va_arg(*ap, int);
+ fmtprint(b, "%s", search(list, key, strhex));
+}
+
+/* Returns the string equivalent of a colormap state */
+static void
+TColMap(Fmt *b, va_list *ap) {
+ static Pair list[] = {
+ {ColormapInstalled, "ColormapInstalled"},
+ {ColormapUninstalled, "ColormapUninstalled"},
+ {0, nil},
+ };
+ int key;
+
+ key = va_arg(*ap, int);
+ fmtprint(b, "%s", search(list, key, strign));
+}
+
+/* Returns the string equivalent of a crossing detail */
+static void
+TXing(Fmt *b, va_list *ap) {
+ static Pair list[] = {
+ {NotifyAncestor, "NotifyAncestor"},
+ {NotifyInferior, "NotifyInferior"},
+ {NotifyVirtual, "NotifyVirtual"},
+ {NotifyNonlinear, "NotifyNonlinear"},
+ {NotifyNonlinearVirtual, "NotifyNonlinearVirtual"},
+ {0, nil},
+ };
+ int key;
+
+ key = va_arg(*ap, int);
+ fmtprint(b, "%s", search(list, key, strign));
+}
+
+/* Returns the string equivalent of a focus change detail */
+static void
+TFocus(Fmt *b, va_list *ap) {
+ static Pair list[] = {
+ {NotifyAncestor, "NotifyAncestor"},
+ {NotifyInferior, "NotifyInferior"},
+ {NotifyVirtual, "NotifyVirtual"},
+ {NotifyNonlinear, "NotifyNonlinear"},
+ {NotifyNonlinearVirtual, "NotifyNonlinearVirtual"},
+ {NotifyPointer, "NotifyPointer"},
+ {NotifyPointerRoot, "NotifyPointerRoot"},
+ {NotifyDetailNone, "NotifyDetailNone"},
+ {0, nil},
+ };
+ int key;
+
+ key = va_arg(*ap, int);
+ fmtprint(b, "%s", search(list, key, strign));
+}
+
+/* Returns the string equivalent of a configure detail */
+static void
+TConfDetail(Fmt *b, va_list *ap) {
+ static Pair list[] = {
+ {Above, "Above"},
+ {Below, "Below"},
+ {TopIf, "TopIf"},
+ {BottomIf, "BottomIf"},
+ {Opposite, "Opposite"},
+ {0, nil},
+ };
+ int key;
+
+ key = va_arg(*ap, int);
+ fmtprint(b, "%s", search(list, key, strign));
+}
+
+/* Returns the string equivalent of a grab mode */
+static void
+TGrabMode(Fmt *b, va_list *ap) {
+ static Pair list[] = {
+ {NotifyNormal, "NotifyNormal"},
+ {NotifyGrab, "NotifyGrab"},
+ {NotifyUngrab, "NotifyUngrab"},
+ {NotifyWhileGrabbed, "NotifyWhileGrabbed"},
+ {0, nil},
+ };
+ int key;
+
+ key = va_arg(*ap, int);
+ fmtprint(b, "%s", search(list, key, strign));
+}
+
+/* Returns the string equivalent of a mapping request */
+static void
+TMapping(Fmt *b, va_list *ap) {
+ static Pair list[] = {
+ {MappingModifier, "MappingModifier"},
+ {MappingKeyboard, "MappingKeyboard"},
+ {MappingPointer, "MappingPointer"},
+ {0, nil},
+ };
+ int key;
+
+ key = va_arg(*ap, int);
+ fmtprint(b, "%s", search(list, key, strign));
+}
+
+/* Returns the string equivalent of a stacking order place */
+static void
+TPlace(Fmt *b, va_list *ap) {
+ static Pair list[] = {
+ {PlaceOnTop, "PlaceOnTop"},
+ {PlaceOnBottom, "PlaceOnBottom"},
+ {0, nil},
+ };
+ int key;
+
+ key = va_arg(*ap, int);
+ fmtprint(b, "%s", search(list, key, strign));
+}
+
+/* Returns the string equivalent of a major code */
+static void
+TMajor(Fmt *b, va_list *ap) {
+ static char *list[] = { XMajors };
+ char *s;
+ uint key;
+
+ key = va_arg(*ap, uint);
+ s = "<nil>";
+ if(key < nelem(list))
+ s = list[key];
+ fmtprint(b, "%s", s);
+}
+
+static char*
+eventtype(int key) {
+ static Pair list[] = {
+ {ButtonPress, "ButtonPress"},
+ {ButtonRelease, "ButtonRelease"},
+ {CirculateNotify, "CirculateNotify"},
+ {CirculateRequest, "CirculateRequest"},
+ {ClientMessage, "ClientMessage"},
+ {ColormapNotify, "ColormapNotify"},
+ {ConfigureNotify, "ConfigureNotify"},
+ {ConfigureRequest, "ConfigureRequest"},
+ {CreateNotify, "CreateNotify"},
+ {DestroyNotify, "DestroyNotify"},
+ {EnterNotify, "EnterNotify"},
+ {Expose, "Expose"},
+ {FocusIn, "FocusIn"},
+ {FocusOut, "FocusOut"},
+ {GraphicsExpose, "GraphicsExpose"},
+ {GravityNotify, "GravityNotify"},
+ {KeyPress, "KeyPress"},
+ {KeyRelease, "KeyRelease"},
+ {KeymapNotify, "KeymapNotify"},
+ {LeaveNotify, "LeaveNotify"},
+ {MapNotify, "MapNotify"},
+ {MapRequest, "MapRequest"},
+ {MappingNotify, "MappingNotify"},
+ {MotionNotify, "MotionNotify"},
+ {NoExpose, "NoExpose"},
+ {PropertyNotify, "PropertyNotify"},
+ {ReparentNotify, "ReparentNotify"},
+ {ResizeRequest, "ResizeRequest"},
+ {SelectionClear, "SelectionClear"},
+ {SelectionNotify, "SelectionNotify"},
+ {SelectionRequest, "SelectionRequest"},
+ {UnmapNotify, "UnmapNotify"},
+ {VisibilityNotify, "VisibilityNotify"},
+ {0, nil},
+ };
+ return search(list, key, strdec);
+}
+/* Returns the string equivalent the keycode contained in the key event */
+static void
+TKeycode(Fmt *b, va_list *ap) {
+ KeySym keysym_str;
+ XKeyEvent *ev;
+ char *keysym_name;
+
+ ev = va_arg(*ap, XKeyEvent*);
+
+ XLookupString(ev, buffer, sizeof buffer, &keysym_str, nil);
+
+ if (keysym_str == NoSymbol)
+ keysym_name = "NoSymbol";
+ else
+ keysym_name = XKeysymToString(keysym_str);
+ if(keysym_name == nil)
+ keysym_name = "(no name)";
+
+ fmtprint(b, "%ud (keysym 0x%x \"%s\")", (int)ev->keycode,
+ (int)keysym_str, keysym_name);
+}
+
+/* Returns the string equivalent of an atom or "None" */
+static void
+TAtom(Fmt *b, va_list *ap) {
+ char *atom_name;
+ Atom atom;
+
+ atom = va_arg(*ap, Atom);
+ atom_name = XGetAtomName(display, atom);
+ fmtprint(b, "%s", atom_name);
+ XFree(atom_name);
+}
+
+#define _(m) #m, ev->m
+#define TEnd nil
+typedef void (*Tfn)(Fmt*, va_list*);
+
+static int
+pevent(Fmt *fmt, void *e, ...) {
+ va_list ap;
+ Tfn fn;
+ XAnyEvent *ev;
+ char *key;
+ int n;
+
+ ev = e;
+ fmtprint(fmt, "%3ld %-20s ", ev->serial, eventtype(ev->type));
+ if(ev->send_event)
+ fmtstrcpy(fmt, "(sendevent) ");
+
+ n = 0;
+ va_start(ap, e);
+ for(;;) {
+ fn = va_arg(ap, Tfn);
+ if(fn == TEnd)
+ break;
+
+ if(n++ != 0)
+ fmtprint(fmt, "%s", sep);
+
+ key = va_arg(ap, char*);
+ fmtprint(fmt, "%s=", key);
+ fn(fmt, &ap);
+ }
+ va_end(ap);
+ return 0;
+}
+
+/*****************************************************************************/
+/*** Routines to print out readable values for the field of various events ***/
+/*****************************************************************************/
+
+static int
+VerbMotion(Fmt *fmt, XEvent *e) {
+ XMotionEvent *ev = &e->xmotion;
+
+ return pevent(fmt, ev,
+ TWindow, _(window),
+ TWindow, _(root),
+ TWindow, _(subwindow),
+ TTime, _(time),
+ TInt, _(x), TInt, _(y),
+ TInt, _(x_root), TInt, _(y_root),
+ TModState, _(state),
+ TBool, _(same_screen),
+ TEnd
+ );
+ //fprintf(stderr, "is_hint=%s%s", IsHint(ev->is_hint), sep);
+}
+
+static int
+VerbButton(Fmt *fmt, XEvent *e) {
+ XButtonEvent *ev = &e->xbutton;
+
+ return pevent(fmt, ev,
+ TWindow, _(window),
+ TWindow, _(root),
+ TWindow, _(subwindow),
+ TTime, _(time),
+ TInt, _(x), TInt, _(y),
+ TInt, _(x_root), TInt, _(y_root),
+ TModState, _(state),
+ TModState, _(button),
+ TBool, _(same_screen),
+ TEnd
+ );
+}
+
+static int
+VerbColormap(Fmt *fmt, XEvent *e) {
+ XColormapEvent *ev = &e->xcolormap;
+
+ return pevent(fmt, ev,
+ TWindow, _(window),
+ TIntNone, _(colormap),
+ TBool, _(new),
+ TColMap, _(state),
+ TEnd
+ );
+}
+
+static int
+VerbCrossing(Fmt *fmt, XEvent *e) {
+ XCrossingEvent *ev = &e->xcrossing;
+
+ return pevent(fmt, ev,
+ TWindow, _(window),
+ TWindow, _(root),
+ TWindow, _(subwindow),
+ TTime, _(time),
+ TInt, _(x), TInt, _(y),
+ TInt, _(x_root), TInt, _(y_root),
+ TGrabMode, _(mode),
+ TXing, _(detail),
+ TBool, _(same_screen),
+ TBool, _(focus),
+ TModState, _(state),
+ TEnd
+ );
+}
+
+static int
+VerbExpose(Fmt *fmt, XEvent *e) {
+ XExposeEvent *ev = &e->xexpose;
+
+ return pevent(fmt, ev,
+ TWindow, _(window),
+ TInt, _(x), TInt, _(y),
+ TInt, _(width), TInt, _(height),
+ TInt, _(count),
+ TEnd
+ );
+}
+
+static int
+VerbGraphicsExpose(Fmt *fmt, XEvent *e) {
+ XGraphicsExposeEvent *ev = &e->xgraphicsexpose;
+
+ return pevent(fmt, ev,
+ TWindow, _(drawable),
+ TInt, _(x), TInt, _(y),
+ TInt, _(width), TInt, _(height),
+ TMajor, _(major_code),
+ TInt, _(minor_code),
+ TEnd
+ );
+}
+
+static int
+VerbNoExpose(Fmt *fmt, XEvent *e) {
+ XNoExposeEvent *ev = &e->xnoexpose;
+
+ return pevent(fmt, ev,
+ TWindow, _(drawable),
+ TMajor, _(major_code),
+ TInt, _(minor_code),
+ TEnd
+ );
+}
+
+static int
+VerbFocus(Fmt *fmt, XEvent *e) {
+ XFocusChangeEvent *ev = &e->xfocus;
+
+ return pevent(fmt, ev,
+ TWindow, _(window),
+ TGrabMode, _(mode),
+ TFocus, _(detail),
+ TEnd
+ );
+}
+
+static int
+VerbKeymap(Fmt *fmt, XEvent *e) {
+ XKeymapEvent *ev = &e->xkeymap;
+ int i;
+
+ fmtprint(fmt, "window=0x%x%s", (int)ev->window, sep);
+ fmtprint(fmt, "key_vector=");
+ for (i = 0; i < 32; i++)
+ fmtprint(fmt, "%02x", ev->key_vector[i]);
+ fmtprint(fmt, "\n");
+ return 0;
+}
+
+static int
+VerbKey(Fmt *fmt, XEvent *e) {
+ XKeyEvent *ev = &e->xkey;
+
+ return pevent(fmt, ev,
+ TWindow, _(window),
+ TWindow, _(root),
+ TWindow, _(subwindow),
+ TTime, _(time),
+ TInt, _(x), TInt, _(y),
+ TInt, _(x_root), TInt, _(y_root),
+ TModState, _(state),
+ TKeycode, "keycode", ev,
+ TBool, _(same_screen),
+ TEnd
+ );
+}
+
+static int
+VerbProperty(Fmt *fmt, XEvent *e) {
+ XPropertyEvent *ev = &e->xproperty;
+
+ return pevent(fmt, ev,
+ TWindow, _(window),
+ TAtom, _(atom),
+ TTime, _(time),
+ TPropState, _(state),
+ TEnd
+ );
+}
+
+static int
+VerbResizeRequest(Fmt *fmt, XEvent *e) {
+ XResizeRequestEvent *ev = &e->xresizerequest;
+
+ return pevent(fmt, ev,
+ TWindow, _(window),
+ TInt, _(width), TInt, _(height),
+ TEnd
+ );
+}
+
+static int
+VerbCirculate(Fmt *fmt, XEvent *e) {
+ XCirculateEvent *ev = &e->xcirculate;
+
+ return pevent(fmt, ev,
+ TWindow, _(event),
+ TWindow, _(window),
+ TPlace, _(place),
+ TEnd
+ );
+}
+
+static int
+VerbConfigure(Fmt *fmt, XEvent *e) {
+ XConfigureEvent *ev = &e->xconfigure;
+
+ return pevent(fmt, ev,
+ TWindow, _(event),
+ TWindow, _(window),
+ TInt, _(x), TInt, _(y),
+ TInt, _(width), TInt, _(height),
+ TInt, _(border_width),
+ TIntNone, _(above),
+ TBool, _(override_redirect),
+ TEnd
+ );
+}
+
+static int
+VerbCreateWindow(Fmt *fmt, XEvent *e) {
+ XCreateWindowEvent *ev = &e->xcreatewindow;
+
+ return pevent(fmt, ev,
+ TWindow, _(parent),
+ TWindow, _(window),
+ TInt, _(x), TInt, _(y),
+ TInt, _(width), TInt, _(height),
+ TInt, _(border_width),
+ TBool, _(override_redirect),
+ TEnd
+ );
+}
+
+static int
+VerbDestroyWindow(Fmt *fmt, XEvent *e) {
+ XDestroyWindowEvent *ev = &e->xdestroywindow;
+
+ return pevent(fmt, ev,
+ TWindow, _(event),
+ TWindow, _(window),
+ TEnd
+ );
+}
+
+static int
+VerbGravity(Fmt *fmt, XEvent *e) {
+ XGravityEvent *ev = &e->xgravity;
+
+ return pevent(fmt, ev,
+ TWindow, _(event),
+ TWindow, _(window),
+ TInt, _(x), TInt, _(y),
+ TEnd
+ );
+}
+
+static int
+VerbMap(Fmt *fmt, XEvent *e) {
+ XMapEvent *ev = &e->xmap;
+
+ return pevent(fmt, ev,
+ TWindow, _(event),
+ TWindow, _(window),
+ TBool, _(override_redirect),
+ TEnd
+ );
+}
+
+static int
+VerbReparent(Fmt *fmt, XEvent *e) {
+ XReparentEvent *ev = &e->xreparent;
+
+ return pevent(fmt, ev,
+ TWindow, _(event),
+ TWindow, _(window),
+ TWindow, _(parent),
+ TInt, _(x), TInt, _(y),
+ TBool, _(override_redirect),
+ TEnd
+ );
+}
+
+static int
+VerbUnmap(Fmt *fmt, XEvent *e) {
+ XUnmapEvent *ev = &e->xunmap;
+
+ return pevent(fmt, ev,
+ TWindow, _(event),
+ TWindow, _(window),
+ TBool, _(from_configure),
+ TEnd
+ );
+}
+
+static int
+VerbCirculateRequest(Fmt *fmt, XEvent *e) {
+ XCirculateRequestEvent *ev = &e->xcirculaterequest;
+
+ return pevent(fmt, ev,
+ TWindow, _(parent),
+ TWindow, _(window),
+ TPlace, _(place),
+ TEnd
+ );
+}
+
+static int
+VerbConfigureRequest(Fmt *fmt, XEvent *e) {
+ XConfigureRequestEvent *ev = &e->xconfigurerequest;
+
+ return pevent(fmt, ev,
+ TWindow, _(parent),
+ TWindow, _(window),
+ TInt, _(x), TInt, _(y),
+ TInt, _(width), TInt, _(height),
+ TInt, _(border_width),
+ TIntNone, _(above),
+ TConfDetail, _(detail),
+ TConfMask, _(value_mask),
+ TEnd
+ );
+}
+
+static int
+VerbMapRequest(Fmt *fmt, XEvent *e) {
+ XMapRequestEvent *ev = &e->xmaprequest;
+
+ return pevent(fmt, ev,
+ TWindow, _(parent),
+ TWindow, _(window),
+ TEnd
+ );
+}
+
+static int
+VerbClient(Fmt *fmt, XEvent *e) {
+ XClientMessageEvent *ev = &e->xclient;
+
+ return pevent(fmt, ev,
+ TWindow, _(window),
+ TAtom, _(message_type),
+ TInt, _(format),
+ TData, "data (as longs)", &ev->data,
+ TEnd
+ );
+}
+
+static int
+VerbMapping(Fmt *fmt, XEvent *e) {
+ XMappingEvent *ev = &e->xmapping;
+
+ return pevent(fmt, ev,
+ TWindow, _(window),
+ TMapping, _(request),
+ TWindow, _(first_keycode),
+ TWindow, _(count),
+ TEnd
+ );
+}
+
+static int
+VerbSelectionClear(Fmt *fmt, XEvent *e) {
+ XSelectionClearEvent *ev = &e->xselectionclear;
+
+ return pevent(fmt, ev,
+ TWindow, _(window),
+ TAtom, _(selection),
+ TTime, _(time),
+ TEnd
+ );
+}
+
+static int
+VerbSelection(Fmt *fmt, XEvent *e) {
+ XSelectionEvent *ev = &e->xselection;
+
+ return pevent(fmt, ev,
+ TWindow, _(requestor),
+ TAtom, _(selection),
+ TAtom, _(target),
+ TAtom, _(property),
+ TTime, _(time),
+ TEnd
+ );
+}
+
+static int
+VerbSelectionRequest(Fmt *fmt, XEvent *e) {
+ XSelectionRequestEvent *ev = &e->xselectionrequest;
+
+ return pevent(fmt, ev,
+ TWindow, _(owner),
+ TWindow, _(requestor),
+ TAtom, _(selection),
+ TAtom, _(target),
+ TAtom, _(property),
+ TTime, _(time),
+ TEnd
+ );
+}
+
+static int
+VerbVisibility(Fmt *fmt, XEvent *e) {
+ XVisibilityEvent *ev = &e->xvisibility;
+
+ return pevent(fmt, ev,
+ TWindow, _(window),
+ TVis, _(state),
+ TEnd
+ );
+}
+
+/******************************************************************************/
+/**************** Print the values of all fields for any event ****************/
+/******************************************************************************/
+
+typedef struct Handler Handler;
+struct Handler {
+ int key;
+ int (*fn)(Fmt*, XEvent*);
+};
+
+int
+fmtevent(Fmt *fmt) {
+ XEvent *e;
+ XAnyEvent *ev;
+ /*
+ fprintf(stderr, "type=%s%s", eventtype(e->xany.type), sep);
+ fprintf(stderr, "serial=%lu%s", ev->serial, sep);
+ fprintf(stderr, "send_event=%s%s", TorF(ev->send_event), sep);
+ fprintf(stderr, "display=0x%p%s", ev->display, sep);
+ */
+ static Handler fns[] = {
+ {MotionNotify, VerbMotion},
+ {ButtonPress, VerbButton},
+ {ButtonRelease, VerbButton},
+ {ColormapNotify, VerbColormap},
+ {EnterNotify, VerbCrossing},
+ {LeaveNotify, VerbCrossing},
+ {Expose, VerbExpose},
+ {GraphicsExpose, VerbGraphicsExpose},
+ {NoExpose, VerbNoExpose},
+ {FocusIn, VerbFocus},
+ {FocusOut, VerbFocus},
+ {KeymapNotify, VerbKeymap},
+ {KeyPress, VerbKey},
+ {KeyRelease, VerbKey},
+ {PropertyNotify, VerbProperty},
+ {ResizeRequest, VerbResizeRequest},
+ {CirculateNotify, VerbCirculate},
+ {ConfigureNotify, VerbConfigure},
+ {CreateNotify, VerbCreateWindow},
+ {DestroyNotify, VerbDestroyWindow},
+ {GravityNotify, VerbGravity},
+ {MapNotify, VerbMap},
+ {ReparentNotify, VerbReparent},
+ {UnmapNotify, VerbUnmap},
+ {CirculateRequest, VerbCirculateRequest},
+ {ConfigureRequest, VerbConfigureRequest},
+ {MapRequest, VerbMapRequest},
+ {ClientMessage, VerbClient},
+ {MappingNotify, VerbMapping},
+ {SelectionClear, VerbSelectionClear},
+ {SelectionNotify, VerbSelection},
+ {SelectionRequest, VerbSelectionRequest},
+ {VisibilityNotify, VerbVisibility},
+ {0, nil},
+ };
+ Handler *p;
+
+ e = va_arg(fmt->args, XEvent*);
+ ev = &e->xany;
+
+ for (p = fns; p->fn; p++)
+ if (p->key == ev->type)
+ return p->fn(fmt, e);
+ return 1;
+}
+
diff --git a/cmd/wmii/printevent.h b/cmd/wmii/printevent.h
new file mode 100644
index 0000000..22d6b25
--- /dev/null
+++ b/cmd/wmii/printevent.h
@@ -0,0 +1,248 @@
+int fmtevent(Fmt *fmt);
+
+enum {
+ X_CreateWindow = 1,
+ X_ChangeWindowAttributes,
+ X_GetWindowAttributes,
+ X_DestroyWindow,
+ X_DestroySubwindows,
+ X_ChangeSaveSet,
+ X_ReparentWindow,
+ X_MapWindow,
+ X_MapSubwindows,
+ X_UnmapWindow,
+ X_UnmapSubwindows,
+ X_ConfigureWindow,
+ X_CirculateWindow,
+ X_GetGeometry,
+ X_QueryTree,
+ X_InternAtom,
+ X_GetAtomName,
+ X_ChangeProperty,
+ X_DeleteProperty,
+ X_GetProperty,
+ X_ListProperties,
+ X_SetSelectionOwner,
+ X_GetSelectionOwner,
+ X_ConvertSelection,
+ X_SendEvent,
+ X_GrabPointer,
+ X_UngrabPointer,
+ X_GrabButton,
+ X_UngrabButton,
+ X_ChangeActivePointerGrab,
+ X_GrabKeyboard,
+ X_UngrabKeyboard,
+ X_GrabKey,
+ X_UngrabKey,
+ X_AllowEvents,
+ X_GrabServer,
+ X_UngrabServer,
+ X_QueryPointer,
+ X_GetMotionEvents,
+ X_TranslateCoords,
+ X_WarpPointer,
+ X_SetInputFocus,
+ X_GetInputFocus,
+ X_QueryKeymap,
+ X_OpenFont,
+ X_CloseFont,
+ X_QueryFont,
+ X_QueryTextExtents,
+ X_ListFonts,
+ X_ListFontsWithInfo,
+ X_SetFontPath,
+ X_GetFontPath,
+ X_CreatePixmap,
+ X_FreePixmap,
+ X_CreateGC,
+ X_ChangeGC,
+ X_CopyGC,
+ X_SetDashes,
+ X_SetClipRectangles,
+ X_FreeGC,
+ X_ClearArea,
+ X_CopyArea,
+ X_CopyPlane,
+ X_PolyPoint,
+ X_PolyLine,
+ X_PolySegment,
+ X_PolyRectangle,
+ X_PolyArc,
+ X_FillPoly,
+ X_PolyFillRectangle,
+ X_PolyFillArc,
+ X_PutImage,
+ X_GetImage,
+ X_PolyText8,
+ X_PolyText16,
+ X_ImageText8,
+ X_ImageText16,
+ X_CreateColormap,
+ X_FreeColormap,
+ X_CopyColormapAndFree,
+ X_InstallColormap,
+ X_UninstallColormap,
+ X_ListInstalledColormaps,
+ X_AllocColor,
+ X_AllocNamedColor,
+ X_AllocColorCells,
+ X_AllocColorPlanes,
+ X_FreeColors,
+ X_StoreColors,
+ X_StoreNamedColor,
+ X_QueryColors,
+ X_LookupColor,
+ X_CreateCursor,
+ X_CreateGlyphCursor,
+ X_FreeCursor,
+ X_RecolorCursor,
+ X_QueryBestSize,
+ X_QueryExtension,
+ X_ListExtensions,
+ X_ChangeKeyboardMapping,
+ X_GetKeyboardMapping,
+ X_ChangeKeyboardControl,
+ X_GetKeyboardControl,
+ X_Bell,
+ X_ChangePointerControl,
+ X_GetPointerControl,
+ X_SetScreenSaver,
+ X_GetScreenSaver,
+ X_ChangeHosts,
+ X_ListHosts,
+ X_SetAccessControl,
+ X_SetCloseDownMode,
+ X_KillClient,
+ X_RotateProperties,
+ X_ForceScreenSaver,
+ X_SetPointerMapping,
+ X_GetPointerMapping,
+ X_SetModifierMapping,
+ X_GetModifierMapping,
+ X_NoOperation,
+};
+
+#define XMajors \
+ "<nil>",\
+ "CreateWindow",\
+ "ChangeWindowAttributes",\
+ "GetWindowAttributes",\
+ "DestroyWindow",\
+ "DestroySubwindows",\
+ "ChangeSaveSet",\
+ "ReparentWindow",\
+ "MapWindow",\
+ "MapSubwindows",\
+ "UnmapWindow",\
+ "UnmapSubwindows",\
+ "ConfigureWindow",\
+ "CirculateWindow",\
+ "GetGeometry",\
+ "QueryTree",\
+ "InternAtom",\
+ "GetAtomName",\
+ "ChangeProperty",\
+ "DeleteProperty",\
+ "GetProperty",\
+ "ListProperties",\
+ "SetSelectionOwner",\
+ "GetSelectionOwner",\
+ "ConvertSelection",\
+ "SendEvent",\
+ "GrabPointer",\
+ "UngrabPointer",\
+ "GrabButton",\
+ "UngrabButton",\
+ "ChangeActivePointerGrab",\
+ "GrabKeyboard",\
+ "UngrabKeyboard",\
+ "GrabKey",\
+ "UngrabKey",\
+ "AllowEvents",\
+ "GrabServer",\
+ "UngrabServer",\
+ "QueryPointer",\
+ "GetMotionEvents",\
+ "TranslateCoords",\
+ "WarpPointer",\
+ "SetInputFocus",\
+ "GetInputFocus",\
+ "QueryKeymap",\
+ "OpenFont",\
+ "CloseFont",\
+ "QueryFont",\
+ "QueryTextExtents",\
+ "ListFonts",\
+ "ListFontsWithInfo",\
+ "SetFontPath",\
+ "GetFontPath",\
+ "CreatePixmap",\
+ "FreePixmap",\
+ "CreateGC",\
+ "ChangeGC",\
+ "CopyGC",\
+ "SetDashes",\
+ "SetClipRectangles",\
+ "FreeGC",\
+ "ClearArea",\
+ "CopyArea",\
+ "CopyPlane",\
+ "PolyPoint",\
+ "PolyLine",\
+ "PolySegment",\
+ "PolyRectangle",\
+ "PolyArc",\
+ "FillPoly",\
+ "PolyFillRectangle",\
+ "PolyFillArc",\
+ "PutImage",\
+ "GetImage",\
+ "PolyText8",\
+ "PolyText16",\
+ "ImageText8",\
+ "ImageText16",\
+ "CreateColormap",\
+ "FreeColormap",\
+ "CopyColormapAndFree",\
+ "InstallColormap",\
+ "UninstallColormap",\
+ "ListInstalledColormaps",\
+ "AllocColor",\
+ "AllocNamedColor",\
+ "AllocColorCells",\
+ "AllocColorPlanes",\
+ "FreeColors",\
+ "StoreColors",\
+ "StoreNamedColor",\
+ "QueryColors",\
+ "LookupColor",\
+ "CreateCursor",\
+ "CreateGlyphCursor",\
+ "FreeCursor",\
+ "RecolorCursor",\
+ "QueryBestSize",\
+ "QueryExtension",\
+ "ListExtensions",\
+ "ChangeKeyboardMapping",\
+ "GetKeyboardMapping",\
+ "ChangeKeyboardControl",\
+ "GetKeyboardControl",\
+ "Bell",\
+ "ChangePointerControl",\
+ "GetPointerControl",\
+ "SetScreenSaver",\
+ "GetScreenSaver",\
+ "ChangeHosts",\
+ "ListHosts",\
+ "SetAccessControl",\
+ "SetCloseDownMode",\
+ "KillClient",\
+ "RotateProperties",\
+ "ForceScreenSaver",\
+ "SetPointerMapping",\
+ "GetPointerMapping",\
+ "SetModifierMapping",\
+ "GetModifierMapping",\
+ "NoOperation",\
+
diff --git a/cmd/wmii/root.c b/cmd/wmii/root.c
new file mode 100644
index 0000000..e3e53e7
--- /dev/null
+++ b/cmd/wmii/root.c
@@ -0,0 +1,89 @@
+/* Copyright ©2008-2010 Kris Maglione <maglione.k at Gmail>
+ * See LICENSE file for license details.
+ */
+#include "dat.h"
+#include "fns.h"
+
+static Handlers handlers;
+
+void
+root_init(void) {
+ WinAttr wa;
+
+ wa.event_mask = EnterWindowMask
+ | FocusChangeMask
+ | LeaveWindowMask
+ | PointerMotionMask
+ | SubstructureNotifyMask
+ | SubstructureRedirectMask;
+ wa.cursor = cursor[CurNormal];
+ setwinattr(&scr.root, &wa,
+ CWEventMask
+ | CWCursor);
+ sethandler(&scr.root, &handlers);
+}
+
+static void
+enter_event(Window *w, XCrossingEvent *e) {
+ disp.sel = true;
+ frame_draw_all();
+}
+
+static void
+leave_event(Window *w, XCrossingEvent *e) {
+ if(!e->same_screen) {
+ disp.sel = false;
+ frame_draw_all();
+ }
+}
+
+static void
+focusin_event(Window *w, XFocusChangeEvent *e) {
+ if(e->mode == NotifyGrab)
+ disp.hasgrab = &c_root;
+}
+
+static void
+mapreq_event(Window *w, XMapRequestEvent *e) {
+ XWindowAttributes wa;
+
+ if(!XGetWindowAttributes(display, e->window, &wa))
+ return;
+ if(wa.override_redirect) {
+ /* Do I really want these? */
+ /* Probably not.
+ XSelectInput(display, e->window,
+ PropertyChangeMask | StructureNotifyMask);
+ */
+ return;
+ }
+ if(!win2client(e->window))
+ client_create(e->window, &wa);
+}
+
+static void
+motion_event(Window *w, XMotionEvent *e) {
+ Rectangle r, r2;
+
+ r = rectsetorigin(Rect(0, 0, 1, 1), Pt(e->x_root, e->y_root));
+ r2 = constrain(r, 0);
+ if(!eqrect(r, r2))
+ warppointer(r2.min);
+}
+
+static void
+kdown_event(Window *w, XKeyEvent *e) {
+
+ e->state &= valid_mask;
+ kpress(w->xid, e->state, (KeyCode)e->keycode);
+}
+
+static Handlers handlers = {
+ .enter = enter_event,
+ .focusin = focusin_event,
+ .kdown = kdown_event,
+ .leave = leave_event,
+ .mapreq = mapreq_event,
+ .motion = motion_event,
+};
+
diff --git a/cmd/wmii/rule.c b/cmd/wmii/rule.c
new file mode 100644
index 0000000..b2f838f
--- /dev/null
+++ b/cmd/wmii/rule.c
@@ -0,0 +1,107 @@
+/* Copyright ©2006 Anselm R. Garbe <garbeam at gmail dot com>
+ * See LICENSE file for license details.
+ */
+
+#include "dat.h"
+#include "fns.h"
+
+void
+trim(char *str, const char *chars) {
+ const char *cp;
+ char *p, *q;
+ char c;
+
+ q = str;
+ for(p=str; *p; p++) {
+ for(cp=chars; (c = *cp); cp++)
+ if(*p == c)
+ break;
+ if(c == '\0')
+ *q++ = *p;
+ }
+ *q = '\0';
+}
+
+/* XXX: I hate this. --KM */
+void
+update_rules(Rule **rule, const char *data) {
+ /* basic rule matching language /regex/ -> value
+ * regex might contain POSIX regex syntax defined in regex(3) */
+ enum {
+ IGNORE,
+ REGEX,
+ VALUE,
+ COMMENT,
+ };
+ int state;
+ Rule *rul;
+ char regex[256], value[256];
+ char *regex_end = regex + sizeof(regex) - 1;
+ char *value_end = value + sizeof(value) - 1;
+ char *r, *v;
+ const char *p;
+ char c;
+
+ SET(r);
+ SET(v);
+
+ if(!data || !strlen(data))
+ return;
+ while((rul = *rule)) {
+ *rule = rul->next;
+ free(rul->regex);
+ free(rul);
+ }
+ state = IGNORE;
+ for(p = data; (c = *p); p++)
+ switch(state) {
+ case COMMENT:
+ if(c == '\n')
+ state = IGNORE;
+ break;
+ case IGNORE:
+ if(c == '#')
+ state = COMMENT;
+ else if(c == '/') {
+ r = regex;
+ state = REGEX;
+ }
+ else if(c == '>') {
+ value[0] = 0;
+ v = value;
+ state = VALUE;
+ }
+ break;
+ case REGEX:
+ if(c == '\\' && p[1] == '/')
+ p++;
+ else if(c == '/') {
+ *r = 0;
+ state = IGNORE;
+ break;
+ }
+ if(r < regex_end)
+ *r++ = c;
+ break;
+ case VALUE:
+ if(c == '\n' || c == '#' || c == 0) {
+ *v = 0;
+ trim(value, " \t");
+ *rule = emallocz(sizeof **rule);
+ (*rule)->regex = regcomp(regex);
+ if((*rule)->regex) {
+ utflcpy((*rule)->value, value, sizeof rul->value);
+ rule = &(*rule)->next;
+ }else
+ free(*rule);
+ state = IGNORE;
+ if(c == '#')
+ state = COMMENT;
+ }
+ else if(v < value_end)
+ *v++ = c;
+ break;
+ default: /* can't happen */
+ die("invalid state");
+ }
+}
diff --git a/cmd/wmii/screen.c b/cmd/wmii/screen.c
new file mode 100644
index 0000000..76d89eb
--- /dev/null
+++ b/cmd/wmii/screen.c
@@ -0,0 +1,189 @@
+/* Copyright ©2006-2010 Kris Maglione <maglione.k at Gmail>
+ * See LICENSE file for license details.
+ */
+#include "dat.h"
+#include <math.h>
+#include <stdlib.h>
+#include "fns.h"
+
+#ifdef notdef
+void
+mapscreens(void) {
+ WMScreen *s, *ss;
+ Rectangle r;
+ int i, j;
+
+#define frob(left, min, max, x, y) \
+ if(Dy(r) > 0) /* If they intersect at some point on this axis */ \
+ if(ss->r.min.x < s->r.min.x) { \
+ if((!s->left) \
+ || (abs(Dy(r)) < abs(s->left.max.x - s->min.x))) \
+ s->left = ss; \
+ }
+
+ /* Variable hell? Certainly. */
+ for(i=0; i < nscreens; i++) {
+ s = screens[i];
+ for(j=0; j < nscreens; j++) {
+ if(i == j)
+ continue;
+ ss = screens[j];
+ r = rect_intersection(ss->r, s->r);
+ frob(left, min, max, x, y);
+ frob(right, max, min, x, y);
+ frob(atop, min, max, y, x);
+ frob(below, max, min, y, x);
+ }
+ }
+#undef frob
+}
+
+int findscreen(Rectangle, int);
+int
+findscreen(Rectangle rect, int direction) {
+ Rectangle r;
+ WMScreen *ss, *s;
+ int best, i, j;
+
+ best = -1;
+#define frob(min, max, x, y)
+ if(Dy(r) > 0) /* If they intersect at some point on this axis */
+ if(ss->r.min.x < rect.min.x) {
+ if(best == -1
+ || (abs(ss->r.max.x - rect.min.x) < abs(screens[best]->r.max.x - rect.min.x)))
+ best = s->idx;
+ }
+
+ /* Variable hell? Certainly. */
+ for(i=0; i < nscreens; i++) {
+ ss = screens[j];
+ r = rect_intersection(ss->r, rect);
+ switch(direction) {
+ default:
+ return -1;
+ case West:
+ frob(min, max, x, y);
+ break;
+ case East:
+ frob(max, min, x, y);
+ break;
+ case North:
+ frob(min, max, y, x);
+ break;
+ case South:
+ frob(max, min, y, x);
+ break;
+ }
+ }
+#undef frob
+}
+#endif
+
+static Rectangle
+leastthing(Rectangle rect, int direction, Vector_ptr *vec, Rectangle (*key)(void*)) {
+ Rectangle r;
+ int i, best, d;
+
+ SET(d);
+ SET(best);
+ for(i=0; i < vec->n; i++) {
+ r = key(vec->ary[i]);
+ switch(direction) {
+ case South: d = r.min.y; break;
+ case North: d = -r.max.y; break;
+ case East: d = r.min.x; break;
+ case West: d = -r.max.x; break;
+ }
+ if(i == 0 || d < best)
+ best = d;
+ }
+ switch(direction) {
+ case South: rect.min.y = rect.max.y = best; break;
+ case North: rect.min.y = rect.max.y = -best; break;
+ case East: rect.min.x = rect.max.x = best; break;
+ case West: rect.min.x = rect.max.x = -best; break;
+ }
+ return rect;
+}
+
+void*
+findthing(Rectangle rect, int direction, Vector_ptr *vec, Rectangle (*key)(void*), bool wrap) {
+ Rectangle isect;
+ Rectangle r, bestisect = {0,}, bestr = {0,};
+ void *best, *p;
+ int i, n;
+
+ best = nil;
+
+ /* For the record, I really hate these macros. */
+#define frob(min, max, LT, x, y) \
+ if(D##y(isect) > 0) /* If they intersect at some point on this axis */ \
+ if(r.min.x LT rect.min.x) { \
+ n = abs(r.max.x - rect.min.x) - abs(bestr.max.x - rect.min.x); \
+ if(best == nil \
+ || n == 0 && D##y(isect) > D##y(bestisect) \
+ || n < 0 \
+ ) { \
+ best = p; \
+ bestr = r; \
+ bestisect = isect; \
+ } \
+ }
+
+ /* Variable hell? Certainly. */
+ for(i=0; i < vec->n; i++) {
+ p = vec->ary[i];
+ r = key(p);
+ isect = rect_intersection(rect, r);
+ switch(direction) {
+ default:
+ die("not reached");
+ /* Not reached */
+ case West:
+ frob(min, max, <, x, y);
+ break;
+ case East:
+ frob(max, min, >, x, y);
+ break;
+ case North:
+ frob(min, max, <, y, x);
+ break;
+ case South:
+ frob(max, min, >, y, x);
+ break;
+ }
+ }
+#undef frob
+ if(!best && wrap) {
+ r = leastthing(rect, direction, vec, key);
+ return findthing(r, direction, vec, key, false);
+ }
+ return best;
+}
+
+static int
+area(Rectangle r) {
+ return Dx(r) * Dy(r) *
+ (Dx(r) < 0 && Dy(r) < 0 ? -1 : 1);
+}
+
+int
+ownerscreen(Rectangle r) {
+ Rectangle isect;
+ int s, a, best, besta;
+
+ SET(besta);
+ best = -1;
+ for(s=0; s < nscreens; s++) {
+ if(!screens[s]->showing)
+ continue;
+ isect = rect_intersection(r, screens[s]->r);
+ a = area(isect);
+ if(best < 0 || a > besta) {
+ besta = a;
+ best = s;
+ }
+ }
+ return best;
+}
+
diff --git a/cmd/wmii/utf.c b/cmd/wmii/utf.c
new file mode 100644
index 0000000..48e2a6d
--- /dev/null
+++ b/cmd/wmii/utf.c
@@ -0,0 +1,60 @@
+/* Public Domain --Kris Maglione */
+#include "dat.h"
+#include <errno.h>
+#include <iconv.h>
+#include <langinfo.h>
+#include <string.h>
+#include "fns.h"
+
+char*
+toutf8n(const char *str, size_t nstr) {
+ static iconv_t cd;
+ static bool haveiconv;
+ char *buf, *pos;
+ size_t nbuf, bsize;
+
+ if(cd == nil) {
+ cd = iconv_open("UTF-8", nl_langinfo(CODESET));
+ if((long)cd == -1)
+ warning("Can't convert from local character encoding to UTF-8");
+ else
+ haveiconv = true;
+ }
+ if(!haveiconv) {
+ buf = emalloc(nstr+1);
+ memcpy(buf, str, nstr);
+ buf[nstr+1] = '\0';
+ return buf;
+ }
+
+ iconv(cd, nil, nil, nil, nil);
+
+ bsize = (nstr+1) << 1;
+ buf = emalloc(bsize);
+ pos = buf;
+ nbuf = bsize-1;
+ /* The (void*) cast is because, while the BSDs declare:
+ * size_t iconv(iconv_t, const char**, size_t*, char**, size_t*),
+ * GNU/Linux and POSIX declare:
+ * size_t iconv(iconv_t, char**, size_t*, char**, size_t*).
+ * This just happens to be safer than declaring our own
+ * prototype.
+ */
+ while(iconv(cd, (void*)&str, &nstr, &pos, &nbuf) == -1)
+ if(errno == E2BIG) {
+ bsize <<= 1;
+ nbuf = pos - buf;
+ buf = erealloc(buf, bsize);
+ pos = buf + nbuf;
+ nbuf = bsize - nbuf - 1;
+ }else
+ break;
+ *pos++ = '\0';
+ return erealloc(buf, pos-buf);
+}
+
+char*
+toutf8(const char *str) {
+ return toutf8n(str, strlen(str));
+}
+
diff --git a/cmd/wmii/view.c b/cmd/wmii/view.c
new file mode 100644
index 0000000..e853003
--- /dev/null
+++ b/cmd/wmii/view.c
@@ -0,0 +1,630 @@
+/* Copyright ©2004-2006 Anselm R. Garbe <garbeam at gmail dot com>
+ * Copyright ©2006-2010 Kris Maglione <maglione.k at Gmail>
+ * See LICENSE file for license details.
+ */
+#include "dat.h"
+#include "fns.h"
+
+static bool
+empty_p(View *v) {
+ Frame *f;
+ Area *a;
+ char **p;
+ int cmp;
+ int s;
+
+ foreach_frame(v, s, a, f) {
+ cmp = 1;
+ for(p=f->client->retags; *p; p++) {
+ cmp = strcmp(*p, v->name);
+ if(cmp >= 0)
+ break;
+ }
+ if(cmp)
+ return false;
+ }
+ return true;
+}
+
+static void
+_view_select(View *v) {
+ if(selview != v) {
+ if(selview)
+ event("UnfocusTag %s\n",selview->name);
+ selview = v;
+ event("FocusTag %s\n", v->name);
+ event("AreaFocus %a\n", v->sel);
+ ewmh_updateview();
+ }
+}
+
+Client*
+view_selclient(View *v) {
+ if(v->sel && v->sel->sel)
+ return v->sel->sel->client;
+ return nil;
+}
+
+bool
+view_fullscreen_p(View *v, int scrn) {
+ Frame *f;
+
+ for(f=v->floating->frame; f; f=f->anext)
+ if(f->client->fullscreen == scrn)
+ return true;
+ return false;
+}
+
+View*
+view_create(const char *name) {
+ static ushort id = 1;
+ View **vp;
+ Client *c;
+ View *v;
+ int i;
+
+ for(vp=&view; *vp; vp=&(*vp)->next) {
+ i = strcmp((*vp)->name, name);
+ if(i == 0)
+ return *vp;
+ if(i > 0)
+ break;
+ }
+
+ v = emallocz(sizeof *v);
+ v->id = id++;
+ v->r = emallocz(nscreens * sizeof *v->r);
+ v->pad = emallocz(nscreens * sizeof *v->pad);
+
+ utflcpy(v->name, name, sizeof v->name);
+
+ event("CreateTag %s\n", v->name);
+ area_create(v, nil, screen->idx, 0);
+
+ v->areas = emallocz(nscreens * sizeof *v->areas);
+
+ for(i=0; i < nscreens; i++)
+ view_init(v, i);
+
+
+ area_focus(v->firstarea);
+
+ v->next = *vp;
+ *vp = v;
+
+ /* FIXME: Belongs elsewhere */
+ /* FIXME: Can do better. */
+ for(c=client; c; c=c->next)
+ if(c != kludge)
+ client_applytags(c, c->tags);
+
+ view_arrange(v);
+ if(!selview)
+ _view_select(v);
+ ewmh_updateviews();
+ return v;
+}
+
+void
+view_init(View *v, int iscreen) {
+ v->r[iscreen] = screens[iscreen]->r;
+ v->areas[iscreen] = nil;
+ column_new(v, nil, iscreen, 0);
+}
+
+void
+view_destroy(View *v) {
+ View **vp;
+ Frame *f;
+ View *tv;
+ Area *a;
+ int s;
+
+ if(v->dead)
+ return;
+ v->dead = true;
+
+ for(vp=&view; *vp; vp=&(*vp)->next)
+ if(*vp == v) break;
+ *vp = v->next;
+ assert(v != v->next);
+
+ /* Detach frames held here by regex tags. */
+ /* FIXME: Can do better. */
+ foreach_frame(v, s, a, f)
+ client_applytags(f->client, f->client->tags);
+
+ foreach_area(v, s, a)
+ area_destroy(a);
+
+ event("DestroyTag %s\n", v->name);
+
+ if(v == selview) {
+ for(tv=view; tv; tv=tv->next)
+ if(tv->next == *vp) break;
+ if(tv == nil)
+ tv = view;
+ if(tv)
+ view_focus(screen, tv);
+ }
+ free(v->areas);
+ free(v->r);
+ free(v);
+ ewmh_updateviews();
+}
+
+Area*
+view_findarea(View *v, int screen, int idx, bool create) {
+ Area *a;
+
+ assert(screen >= 0 && screen < nscreens);
+
+ for(a=v->areas[screen]; a && --idx > 0; a=a->next)
+ if(create && a->next == nil)
+ return area_create(v, a, screen, 0);
+ return a;
+}
+
+static void
+frames_update_sel(View *v) {
+ Frame *f;
+ Area *a;
+ int s;
+
+ foreach_frame(v, s, a, f)
+ f->client->sel = f;
+}
+
+/* Don't let increment hints take up more than half
+ * of the screen, in either direction.
+ */
+static Rectangle
+fix_rect(Rectangle old, Rectangle new) {
+ double r;
+
+ new = rect_intersection(new, old);
+
+ r = (Dy(old) - Dy(new)) / Dy(old);
+ if(r > .5) {
+ r -= .5;
+ new.min.y -= r * (new.min.y - old.min.y);
+ new.max.y += r * (old.max.y - new.max.y);
+ }
+ r = (Dx(old) - Dx(new)) / Dx(old);
+ if(r > .5) {
+ r -= .5;
+ new.min.x -= r * (new.min.x - old.min.x);
+ new.max.x += r * (old.max.x - new.max.x);
+ }
+ return new;
+}
+
+void
+view_update_rect(View *v) {
+ static Vector_rect vec;
+ static Vector_rect *vp;
+ Rectangle r, sr, rr, brect, scrnr;
+ WMScreen *scrn;
+ Strut *strut;
+ Frame *f;
+ int s, i;
+ /* These short variable names are hell, eh? */
+
+ /* XXX:
+ if(v != selview)
+ return false;
+ */
+ vec.n = 0;
+ for(f=v->floating->frame; f; f=f->anext) {
+ strut = f->client->strut;
+ if(!strut)
+ continue;
+ vector_rpush(&vec, strut->top);
+ vector_rpush(&vec, strut->left);
+ vector_rpush(&vec, rectaddpt(strut->right, Pt(scr.rect.max.x, 0)));
+ vector_rpush(&vec, rectaddpt(strut->bottom, Pt(0, scr.rect.max.y)));
+ }
+ /* Find the largest screen space not occupied by struts. */
+ vp = unique_rects(&vec, scr.rect);
+ scrnr = max_rect(vp);
+
+ /* FIXME: Multihead. */
+ v->floating->r = scr.rect;
+
+ for(s=0; s < nscreens; s++) {
+ scrn = screens[s];
+ r = fix_rect(scrn->r, scrnr);
+
+ /* Ugly. Very, very ugly. */
+ /*
+ * Try to find some rectangle near the edge of the
+ * screen where the bar will fit. This way, for
+ * instance, a system tray can be placed there
+ * without taking up too much extra screen real
+ * estate.
+ */
+ rr = r;
+ brect = scrn->brect;
+ for(i=0; i < vp->n; i++) {
+ sr = rect_intersection(vp->ary[i], scrn->r);
+ if(Dx(sr) < Dx(r)/2 || Dy(sr) < Dy(brect))
+ continue;
+ if(scrn->barpos == BTop && sr.min.y < rr.min.y
+ || scrn->barpos != BTop && sr.max.y > rr.max.y)
+ rr = sr;
+ }
+ if(scrn->barpos == BTop) {
+ bar_sety(scrn, rr.min.y);
+ r.min.y = max(r.min.y, scrn->brect.max.y);
+ }else {
+ bar_sety(scrn, rr.max.y - Dy(brect));
+ r.max.y = min(r.max.y, scrn->brect.min.y);
+ }
+ bar_setbounds(scrn, rr.min.x, rr.max.x);
+ v->r[s] = r;
+ }
+}
+
+void
+view_update(View *v) {
+ Client *c;
+ Frame *f;
+ Area *a;
+ int s;
+
+ if(v != selview)
+ return;
+ if(starting)
+ return;
+
+ frames_update_sel(v);
+
+ foreach_frame(v, s, a, f)
+ if(f->client->fullscreen >= 0) {
+ f->collapsed = false;
+ if(!f->area->floating) {
+ f->oldarea = area_idx(f->area);
+ f->oldscreen = f->area->screen;
+ area_moveto(v->floating, f);
+ area_setsel(v->floating, f);
+ }else if(f->oldarea == -1)
+ f->oldarea = 0;
+ }
+
+ view_arrange(v);
+
+ for(c=client; c; c=c->next) {
+ f = c->sel;
+ if((f && f->view == v)
+ && (f->area == v->sel || !(f->area && f->area->max && f->area->floating))) {
+ if(f->area)
+ client_resize(c, f->r);
+ }else {
+ unmap_frame(c);
+ client_unmap(c, IconicState);
+ }
+ ewmh_updatestate(c);
+ ewmh_updateclient(c);
+ }
+
+ view_restack(v);
+ if(!v->sel->floating && view_fullscreen_p(v, v->sel->screen))
+ area_focus(v->floating);
+ else
+ area_focus(v->sel);
+ frame_draw_all();
+}
+
+void
+view_focus(WMScreen *s, View *v) {
+
+ USED(s);
+
+ _view_select(v);
+ view_update(v);
+}
+
+void
+view_select(const char *arg) {
+ char buf[256];
+
+ utflcpy(buf, arg, sizeof buf);
+ trim(buf, " \t+/");
+
+ if(buf[0] == '\0')
+ return;
+ if(!strcmp(buf, ".") || !strcmp(buf, ".."))
+ return;
+
+ _view_select(view_create(buf));
+ view_update_all(); /* performs view_focus */
+}
+
+void
+view_attach(View *v, Frame *f) {
+ Client *c;
+ Frame *ff;
+ Area *a, *oldsel;
+
+ c = f->client;
+
+ oldsel = v->oldsel;
+ a = v->sel;
+ if(client_floats_p(c)) {
+ if(v->sel != v->floating && c->fullscreen < 0)
+ oldsel = v->sel;
+ a = v->floating;
+ }
+ else if((ff = client_groupframe(c, v)))
+ a = ff->area;
+ else if(v->sel->floating) {
+ if(v->oldsel)
+ a = v->oldsel;
+ /* Don't float a frame when starting or when its
+ * last focused frame didn't float. Important when
+ * tagging with +foo.
+ */
+ else if(starting
+ || c->sel && c->sel->area && !c->sel->area->floating)
+ a = v->firstarea;
+ }
+ if(!a->floating && view_fullscreen_p(v, a->screen))
+ a = v->floating;
+
+ area_attach(a, f);
+ /* TODO: Decide whether to focus this frame */
+ bool newgroup = !c->group
+ || c->group->ref == 1
+ || view_selclient(v)
+ && view_selclient(v)->group == c->group
+ || group_leader(c->group)
+ && !client_viewframe(group_leader(c->group),
+ c->sel->view);
+ USED(newgroup);
+
+ if(!(c->w.ewmh.type & (TypeSplash|TypeDock))) {
+ if(!(c->tagre.regex && regexec(c->tagre.regc, v->name, nil, 0)))
+ frame_focus(f);
+ else if(c->group && f->area->sel->client->group == c->group)
+ /* XXX: Stack. */
+ area_setsel(f->area, f);
+ }
+
+ if(oldsel)
+ v->oldsel = oldsel;
+
+ if(c->sel == nil)
+ c->sel = f;
+ view_update(v);
+}
+
+void
+view_detach(Frame *f) {
+ Client *c;
+ View *v;
+
+ v = f->view;
+ c = f->client;
+
+ area_detach(f);
+ if(c->sel == f)
+ c->sel = f->cnext;
+
+ if(v == selview)
+ view_update(v);
+ else if(empty_p(v))
+ view_destroy(v);
+}
+
+char**
+view_names(void) {
+ Vector_ptr vec;
+ View *v;
+
+ vector_pinit(&vec);
+ for(v=view; v; v=v->next)
+ vector_ppush(&vec, v->name);
+ vector_ppush(&vec, nil);
+ return erealloc(vec.ary, vec.n * sizeof *vec.ary);
+}
+
+void
+view_restack(View *v) {
+ static Vector_long wins;
+ Divide *d;
+ Frame *f;
+ Area *a;
+ int s;
+
+ if(v != selview)
+ return;
+
+ wins.n = 0;
+
+ /* *sigh */
+ for(f=v->floating->stack; f; f=f->snext)
+ if(f->client->w.ewmh.type & TypeDock)
+ vector_lpush(&wins, f->client->framewin->xid);
+ else
+ break;
+
+ for(; f; f=f->snext)
+ vector_lpush(&wins, f->client->framewin->xid);
+
+ for(int s=0; s < nscreens; s++)
+ vector_lpush(&wins, screens[s]->barwin->xid);
+
+ for(d = divs; d && d->w->mapped; d = d->next)
+ vector_lpush(&wins, d->w->xid);
+
+ foreach_column(v, s, a)
+ if(a->frame) {
+ vector_lpush(&wins, a->sel->client->framewin->xid);
+ for(f=a->frame; f; f=f->anext)
+ if(f != a->sel)
+ vector_lpush(&wins, f->client->framewin->xid);
+ }
+
+ ewmh_updatestacking();
+ if(wins.n)
+ XRestackWindows(display, (ulong*)wins.ary, wins.n);
+}
+
+void
+view_scale(View *v, int scrn, int width) {
+ uint xoff, numcol;
+ uint minwidth;
+ Area *a;
+ float scale;
+ int dx, minx;
+
+ minwidth = column_minwidth();
+ minx = v->r[scrn].min.x + v->pad[scrn].min.x;
+
+ if(!v->areas[scrn])
+ return;
+
+ numcol = 0;
+ dx = 0;
+ for(a=v->areas[scrn]; a; a=a->next) {
+ numcol++;
+ dx += Dx(a->r);
+ }
+
+ scale = (float)width / dx;
+ xoff = minx;
+ for(a=v->areas[scrn]; a; a=a->next) {
+ a->r.max.x = xoff + Dx(a->r) * scale;
+ a->r.min.x = xoff;
+ if(!a->next)
+ a->r.max.x = v->r[scrn].min.x + width;
+ xoff = a->r.max.x;
+ }
+
+ if(numcol * minwidth > width)
+ return;
+
+ xoff = minx;
+ for(a=v->areas[scrn]; a; a=a->next) {
+ a->r.min.x = xoff;
+
+ if(Dx(a->r) < minwidth)
+ a->r.max.x = xoff + minwidth;
+ if(!a->next)
+ a->r.max.x = minx + width;
+ xoff = a->r.max.x;
+ }
+}
+
+/* XXX: Multihead. */
+void
+view_arrange(View *v) {
+ Area *a;
+ int s;
+
+ if(!v->firstarea)
+ return;
+
+ view_update_rect(v);
+ for(s=0; s < nscreens; s++)
+ view_scale(v, s, Dx(v->r[s]) + Dx(v->pad[s]));
+ foreach_area(v, s, a) {
+ if(a->floating)
+ continue;
+ /* This is wrong... */
+ a->r.min.y = v->r[s].min.y;
+ a->r.max.y = v->r[s].max.y;
+ column_arrange(a, false);
+ }
+ if(v == selview)
+ div_update_all();
+}
+
+Rectangle*
+view_rects(View *v, uint *num, Frame *ignore) {
+ Vector_rect result;
+ Frame *f;
+ int i;
+
+ vector_rinit(&result);
+
+ for(f=v->floating->frame; f; f=f->anext)
+ if(f != ignore)
+ vector_rpush(&result, f->r);
+ for(i=0; i < nscreens; i++) {
+ vector_rpush(&result, v->r[i]);
+ vector_rpush(&result, screens[i]->r);
+ }
+
+ *num = result.n;
+ return result.ary;
+}
+
+void
+view_update_all(void) {
+ View *n, *v, *old;
+
+ old = selview;
+ for(v=view; v; v=v->next)
+ frames_update_sel(v);
+
+ for(v=view; v; v=n) {
+ n=v->next;
+ if(v != old && empty_p(v))
+ view_destroy(v);
+ }
+
+ view_update(selview);
+}
+
+uint
+view_newcolwidth(View *v, int num) {
+ Rule *r;
+ char *toks[16];
+ char buf[sizeof r->value];
+ ulong n;
+
+ for(r=def.colrules.rule; r; r=r->next)
+ if(regexec(r->regex, v->name, nil, 0)) {
+ utflcpy(buf, r->value, sizeof buf);
+ n = tokenize(toks, 16, buf, '+');
+ if(num < n)
+ if(getulong(toks[num], &n))
+ return Dx(v->screenr) * (n / 100.0); /* XXX: Multihead. */
+ break;
+ }
+ return 0;
+}
+
+char*
+view_index(View *v) {
+ Rectangle *r;
+ Frame *f;
+ Area *a;
+ int s;
+
+ bufclear();
+ foreach_area(v, s, a) {
+ if(a->floating)
+ bufprint("# %a %d %d\n", a, Dx(a->r), Dy(a->r));
+ else
+ bufprint("# %a %d %d\n", a, a->r.min.x, Dx(a->r));
+
+ for(f=a->frame; f; f=f->anext) {
+ r = &f->r;
+ if(a->floating)
+ bufprint("%a %C %d %d %d %d %s\n",
+ a, f->client,
+ r->min.x, r->min.y,
+ Dx(*r), Dy(*r),
+ f->client->props);
+ else
+ bufprint("%a %C %d %d %s\n",
+ a, f->client,
+ r->min.y, Dy(*r),
+ f->client->props);
+ }
+ }
+ return buffer;
+}
+
diff --git a/cmd/wmii/x11.c b/cmd/wmii/x11.c
new file mode 100644
index 0000000..dafe85c
--- /dev/null
+++ b/cmd/wmii/x11.c
@@ -0,0 +1,1317 @@
+/* Copyright ©2007-2010 Kris Maglione <maglione.k at Gmail>
+ * See LICENSE file for license details.
+ */
+#define _X11_VISIBLE
+#define pointerwin __pointerwin
+#include "dat.h"
+#include <limits.h>
+#include <math.h>
+#include <strings.h>
+#include <unistd.h>
+#include <bio.h>
+#include "fns.h"
+#undef pointerwin
+
+const Point ZP = {0, 0};
+const Rectangle ZR = {{0, 0}, {0, 0}};
+
+const Window _pointerwin = { .xid = PointerRoot };
+Window* const pointerwin = (Window*)&_pointerwin;
+
+static Map windowmap;
+static Map atommap;
+static MapEnt* wbucket[137];
+static MapEnt* abucket[137];
+
+static int errorhandler(Display*, XErrorEvent*);
+static int (*xlib_errorhandler) (Display*, XErrorEvent*);
+
+static XftColor* xftcolor(Color);
+
+
+/* Rectangles/Points */
+XRectangle
+XRect(Rectangle r) {
+ XRectangle xr;
+
+ xr.x = r.min.x;
+ xr.y = r.min.y;
+ xr.width = Dx(r);
+ xr.height = Dy(r);
+ return xr;
+}
+
+int
+eqrect(Rectangle a, Rectangle b) {
+ return a.min.x==b.min.x && a.max.x==b.max.x
+ && a.min.y==b.min.y && a.max.y==b.max.y;
+}
+
+int
+eqpt(Point p, Point q) {
+ return p.x==q.x && p.y==q.y;
+}
+
+Point
+addpt(Point p, Point q) {
+ p.x += q.x;
+ p.y += q.y;
+ return p;
+}
+
+Point
+subpt(Point p, Point q) {
+ p.x -= q.x;
+ p.y -= q.y;
+ return p;
+}
+
+Point
+mulpt(Point p, Point q) {
+ p.x *= q.x;
+ p.y *= q.y;
+ return p;
+}
+
+Point
+divpt(Point p, Point q) {
+ p.x /= q.x;
+ p.y /= q.y;
+ return p;
+}
+
+Rectangle
+insetrect(Rectangle r, int n) {
+ r.min.x += n;
+ r.min.y += n;
+ r.max.x -= n;
+ r.max.y -= n;
+ return r;
+}
+
+Rectangle
+rectaddpt(Rectangle r, Point p) {
+ r.min.x += p.x;
+ r.max.x += p.x;
+ r.min.y += p.y;
+ r.max.y += p.y;
+ return r;
+}
+
+Rectangle
+rectsubpt(Rectangle r, Point p) {
+ r.min.x -= p.x;
+ r.max.x -= p.x;
+ r.min.y -= p.y;
+ r.max.y -= p.y;
+ return r;
+}
+
+Rectangle
+rectsetorigin(Rectangle r, Point p) {
+ Rectangle ret;
+
+ ret.min.x = p.x;
+ ret.min.y = p.y;
+ ret.max.x = p.x + Dx(r);
+ ret.max.y = p.y + Dy(r);
+ return ret;
+}
+
+/* Formatters */
+static int
+Afmt(Fmt *f) {
+ Atom a;
+ char *s;
+ int i;
+
+ a = va_arg(f->args, Atom);
+ s = XGetAtomName(display, a);
+ i = fmtprint(f, "%s", s);
+ free(s);
+ return i;
+}
+
+static int
+Rfmt(Fmt *f) {
+ Rectangle r;
+
+ r = va_arg(f->args, Rectangle);
+ return fmtprint(f, "%P+%dx%d", r.min, Dx(r), Dy(r));
+}
+
+static int
+Pfmt(Fmt *f) {
+ Point p;
+
+ p = va_arg(f->args, Point);
+ return fmtprint(f, "(%d,%d)", p.x, p.y);
+}
+
+static int
+Wfmt(Fmt *f) {
+ Window *w;
+
+ w = va_arg(f->args, Window*);
+ return fmtprint(f, "0x%ulx", w->xid);
+}
+
+/* Init */
+void
+initdisplay(void) {
+ display = XOpenDisplay(nil);
+ if(display == nil)
+ fatal("Can't open display");
+ scr.screen = DefaultScreen(display);
+ scr.colormap = DefaultColormap(display, scr.screen);
+ scr.visual = DefaultVisual(display, scr.screen);
+ scr.visual32 = DefaultVisual(display, scr.screen);
+ scr.gc = DefaultGC(display, scr.screen);
+ scr.depth = DefaultDepth(display, scr.screen);
+
+ scr.white = WhitePixel(display, scr.screen);
+ scr.black = BlackPixel(display, scr.screen);
+
+ scr.root.xid = RootWindow(display, scr.screen);
+ scr.root.r = Rect(0, 0,
+ DisplayWidth(display, scr.screen),
+ DisplayHeight(display, scr.screen));
+ scr.rect = scr.root.r;
+
+ scr.root.parent = &scr.root;
+
+ windowmap.bucket = wbucket;
+ windowmap.nhash = nelem(wbucket);
+ atommap.bucket = abucket;
+ atommap.nhash = nelem(abucket);
+
+ fmtinstall('A', Afmt);
+ fmtinstall('R', Rfmt);
+ fmtinstall('P', Pfmt);
+ fmtinstall('W', Wfmt);
+
+ xlib_errorhandler = XSetErrorHandler(errorhandler);
+}
+
+/* Error handling */
+
+extern ErrorCode ignored_xerrors[];
+static bool _trap_errors;
+static long nerrors;
+
+static int
+errorhandler(Display *dpy, XErrorEvent *error) {
+ ErrorCode *e;
+
+ USED(dpy);
+
+ if(_trap_errors)
+ nerrors++;
+
+ e = ignored_xerrors;
+ if(e)
+ for(; e->rcode || e->ecode; e++)
+ if((e->rcode == 0 || e->rcode == error->request_code)
+ && (e->ecode == 0 || e->ecode == error->error_code))
+ return 0;
+
+ fprint(2, "%s: fatal error: Xrequest code=%d, Xerror code=%d\n",
+ argv0, error->request_code, error->error_code);
+ return xlib_errorhandler(display, error); /* calls exit() */
+}
+
+int
+traperrors(bool enable) {
+
+ sync();
+ _trap_errors = enable;
+ if (enable)
+ nerrors = 0;
+ return nerrors;
+
+}
+
+/* Images */
+Image*
+allocimage(int w, int h, int depth) {
+ Image *img;
+
+ img = emallocz(sizeof *img);
+ img->type = WImage;
+ img->xid = XCreatePixmap(display, scr.root.xid, w, h, depth);
+ img->gc = XCreateGC(display, img->xid, 0, nil);
+ img->colormap = scr.colormap;
+ img->visual = scr.visual;
+ if(depth == 32)
+ img->visual = scr.visual32;
+ img->depth = depth;
+ img->r = Rect(0, 0, w, h);
+ return img;
+}
+
+void
+freeimage(Image *img) {
+ if(img == nil)
+ return;
+
+ assert(img->type == WImage);
+
+ if(img->xft)
+ XftDrawDestroy(img->xft);
+ XFreePixmap(display, img->xid);
+ XFreeGC(display, img->gc);
+ free(img);
+}
+
+static XftDraw*
+xftdrawable(Image *img) {
+ if(img->xft == nil)
+ img->xft = XftDrawCreate(display, img->xid, img->visual, img->colormap);
+ return img->xft;
+}
+
+/* Windows */
+Window*
+createwindow_visual(Window *parent, Rectangle r,
+ int depth, Visual *vis, uint class,
+ WinAttr *wa, int valmask) {
+ Window *w;
+
+ assert(parent->type == WWindow);
+
+ w = emallocz(sizeof *w);
+ w->visual = vis;
+ w->type = WWindow;
+ w->parent = parent;
+ if(valmask & CWColormap)
+ w->colormap = wa->colormap;
+
+ w->xid = XCreateWindow(display, parent->xid, r.min.x, r.min.y, Dx(r), Dy(r),
+ 0 /* border */, depth, class, vis, valmask, wa);
+#if 0
+ print("createwindow_visual(%W, %R, %d, %p, %ud, %p, %x) = %W\n",
+ parent, r, depth, vis, class, wa, valmask, w);
+#endif
+ if(class != InputOnly)
+ w->gc = XCreateGC(display, w->xid, 0, nil);
+
+ w->r = r;
+ w->depth = depth;
+ return w;
+}
+
+Window*
+createwindow(Window *parent, Rectangle r, int depth, uint class, WinAttr *wa, int valmask) {
+ return createwindow_visual(parent, r, depth, scr.visual, class, wa, valmask);
+}
+
+Window*
+window(XWindow xw) {
+ Window *w;
+
+ w = malloc(sizeof *w);
+ w->type = WWindow;
+ w->xid = xw;
+ return freelater(w);
+}
+
+void
+reparentwindow(Window *w, Window *par, Point p) {
+ assert(w->type == WWindow);
+ XReparentWindow(display, w->xid, par->xid, p.x, p.y);
+ w->parent = par;
+ w->r = rectsubpt(w->r, w->r.min);
+ w->r = rectaddpt(w->r, p);
+}
+
+void
+destroywindow(Window *w) {
+ assert(w->type == WWindow);
+ sethandler(w, nil);
+ if(w->xft)
+ XftDrawDestroy(w->xft);
+ if(w->gc)
+ XFreeGC(display, w->gc);
+ XDestroyWindow(display, w->xid);
+ free(w);
+}
+
+void
+setwinattr(Window *w, WinAttr *wa, int valmask) {
+ assert(w->type == WWindow);
+ XChangeWindowAttributes(display, w->xid, valmask, wa);
+}
+
+void
+selectinput(Window *w, long mask) {
+ XSelectInput(display, w->xid, mask);
+}
+
+static void
+configwin(Window *w, Rectangle r, int border) {
+ XWindowChanges wc;
+
+ if(eqrect(r, w->r) && border == w->border)
+ return;
+
+ wc.x = r.min.x - border;
+ wc.y = r.min.y - border;
+ wc.width = Dx(r);
+ wc.height = Dy(r);
+ wc.border_width = border;
+ XConfigureWindow(display, w->xid, CWX|CWY|CWWidth|CWHeight|CWBorderWidth, &wc);
+
+ w->r = r;
+ w->border = border;
+}
+
+void
+setborder(Window *w, int width, Color col) {
+
+ assert(w->type == WWindow);
+ if(width)
+ XSetWindowBorder(display, w->xid, col.pixel);
+ if(width != w->border)
+ configwin(w, w->r, width);
+}
+
+void
+reshapewin(Window *w, Rectangle r) {
+ assert(w->type == WWindow);
+ assert(Dx(r) > 0 && Dy(r) > 0); /* Rather than an X error. */
+
+ configwin(w, r, w->border);
+}
+
+void
+movewin(Window *w, Point pt) {
+ Rectangle r;
+
+ assert(w->type == WWindow);
+ r = rectsetorigin(w->r, pt);
+ reshapewin(w, r);
+}
+
+int
+mapwin(Window *w) {
+ assert(w->type == WWindow);
+ if(!w->mapped) {
+ XMapWindow(display, w->xid);
+ w->mapped = 1;
+ return 1;
+ }
+ return 0;
+}
+
+int
+unmapwin(Window *w) {
+ assert(w->type == WWindow);
+ if(w->mapped) {
+ XUnmapWindow(display, w->xid);
+ w->mapped = 0;
+ w->unmapped++;
+ return 1;
+ }
+ return 0;
+}
+
+void
+raisewin(Window *w) {
+ assert(w->type == WWindow);
+ XRaiseWindow(display, w->xid);
+}
+
+void
+lowerwin(Window *w) {
+ assert(w->type == WWindow);
+ XLowerWindow(display, w->xid);
+}
+
+Handlers*
+sethandler(Window *w, Handlers *new) {
+ Handlers *old;
+ void **e;
+
+ assert(w->type == WWindow);
+ assert((w->prev != nil && w->next != nil) || w->next == w->prev);
+
+ if(new == nil)
+ map_rm(&windowmap, (ulong)w->xid);
+ else {
+ e = map_get(&windowmap, (ulong)w->xid, true);
+ *e = w;
+ }
+ old = w->handler;
+ w->handler = new;
+ return old;
+}
+
+Window*
+findwin(XWindow w) {
+ void **e;
+
+ e = map_get(&windowmap, (ulong)w, false);
+ if(e)
+ return *e;
+ return nil;
+}
+
+/* Shape */
+void
+setshapemask(Window *dst, Image *src, Point pt) {
+ /* Assumes that we have the shape extension... */
+ XShapeCombineMask (display, dst->xid,
+ ShapeBounding, pt.x, pt.y, src->xid, ShapeSet);
+}
+
+static void
+setgccol(Image *dst, Color col) {
+ XSetForeground(display, dst->gc, col.pixel);
+}
+
+/* Drawing */
+void
+border(Image *dst, Rectangle r, int w, Color col) {
+ if(w == 0)
+ return;
+
+ r = insetrect(r, w/2);
+ r.max.x -= w%2;
+ r.max.y -= w%2;
+
+ XSetLineAttributes(display, dst->gc, w, LineSolid, CapButt, JoinMiter);
+ setgccol(dst, col);
+ XDrawRectangle(display, dst->xid, dst->gc,
+ r.min.x, r.min.y, Dx(r), Dy(r));
+}
+
+void
+fill(Image *dst, Rectangle r, Color col) {
+ setgccol(dst, col);
+ XFillRectangle(display, dst->xid, dst->gc,
+ r.min.x, r.min.y, Dx(r), Dy(r));
+}
+
+static XPoint*
+convpts(Point *pt, int np) {
+ XPoint *rp;
+ int i;
+
+ rp = emalloc(np * sizeof *rp);
+ for(i = 0; i < np; i++) {
+ rp[i].x = pt[i].x;
+ rp[i].y = pt[i].y;
+ }
+ return rp;
+}
+
+void
+drawpoly(Image *dst, Point *pt, int np, int cap, int w, Color col) {
+ XPoint *xp;
+
+ xp = convpts(pt, np);
+ XSetLineAttributes(display, dst->gc, w, LineSolid, cap, JoinMiter);
+ setgccol(dst, col);
+ XDrawLines(display, dst->xid, dst->gc, xp, np, CoordModeOrigin);
+ free(xp);
+}
+
+void
+fillpoly(Image *dst, Point *pt, int np, Color col) {
+ XPoint *xp;
+
+ xp = convpts(pt, np);
+ setgccol(dst, col);
+ XFillPolygon(display, dst->xid, dst->gc, xp, np, Complex, CoordModeOrigin);
+ free(xp);
+}
+
+void
+drawline(Image *dst, Point p1, Point p2, int cap, int w, Color col) {
+ XSetLineAttributes(display, dst->gc, w, LineSolid, cap, JoinMiter);
+ setgccol(dst, col);
+ XDrawLine(display, dst->xid, dst->gc, p1.x, p1.y, p2.x, p2.y);
+}
+
+uint
+drawstring(Image *dst, Font *font,
+ Rectangle r, Align align,
+ char *text, Color col) {
+ Rectangle tr;
+ char *buf;
+ uint x, y, width, height, len;
+ int shortened;
+
+ shortened = 0;
+
+ len = strlen(text);
+ buf = emalloc(len+1);
+ memcpy(buf, text, len+1);
+
+ r.max.y -= font->pad.min.y;
+ r.min.y += font->pad.max.y;
+
+ height = font->ascent + font->descent;
+ y = r.min.y + Dy(r) / 2 - height / 2 + font->ascent;
+
+ width = Dx(r) - font->pad.min.x - font->pad.max.x - (font->height & ~1);
+
+ r.min.x += font->pad.min.x;
+ r.max.x -= font->pad.max.x;
+
+ /* shorten text if necessary */
+ tr = ZR;
+ while(len > 0) {
+ tr = textextents_l(font, buf, len + min(shortened, 3), nil);
+ if(Dx(tr) <= width)
+ break;
+ while(len > 0 && (buf[--len]&0xC0) == 0x80)
+ buf[len] = '.';
+ buf[len] = '.';
+ shortened++;
+ }
+
+ if(len == 0 || Dx(tr) > width)
+ goto done;
+
+ /* mark shortened info in the string */
+ if(shortened)
+ len += min(shortened, 3);
+
+ switch (align) {
+ case East:
+ x = r.max.x - (tr.max.x + (font->height / 2));
+ break;
+ case Center:
+ x = r.min.x + (Dx(r) - Dx(tr)) / 2 - tr.min.x;
+ break;
+ default:
+ x = r.min.x + (font->height / 2) - tr.min.x;
+ break;
+ }
+
+ setgccol(dst, col);
+ switch(font->type) {
+ case FFontSet:
+ Xutf8DrawString(display, dst->xid,
+ font->font.set, dst->gc,
+ x, y,
+ buf, len);
+ break;
+ case FXft:
+ XftDrawStringUtf8(xftdrawable(dst), xftcolor(col),
+ font->font.xft,
+ x, y, (uchar*)buf, len);
+ break;
+ case FX11:
+ XSetFont(display, dst->gc, font->font.x11->fid);
+ XDrawString(display, dst->xid, dst->gc,
+ x, y, buf, len);
+ break;
+ default:
+ die("Invalid font type.");
+ }
+
+done:
+ free(buf);
+ return Dx(tr);
+}
+
+void
+copyimage(Image *dst, Rectangle r, Image *src, Point p) {
+ XCopyArea(display,
+ src->xid, dst->xid,
+ dst->gc,
+ r.min.x, r.min.y, Dx(r), Dy(r),
+ p.x, p.y);
+}
+
+/* Colors */
+bool
+namedcolor(char *name, Color *ret) {
+ XColor c, c2;
+
+ if(XAllocNamedColor(display, scr.colormap, name, &c, &c2)) {
+ *ret = (Color) {
+ c.pixel, {
+ c.red,
+ c.green,
+ c.blue,
+ 0xffff
+ },
+ };
+ return true;
+ }
+ return false;
+}
+
+bool
+loadcolor(CTuple *c, char *str) {
+ char buf[24];
+
+ utflcpy(buf, str, sizeof buf);
+ memcpy(c->colstr, str, sizeof c->colstr);
+
+ buf[7] = buf[15] = buf[23] = '\0';
+ return namedcolor(buf, &c->fg)
+ && namedcolor(buf+8, &c->bg)
+ && namedcolor(buf+16, &c->border);
+}
+
+static XftColor*
+xftcolor(Color col) {
+ XftColor *c;
+
+ c = emallocz(sizeof *c);
+ *c = (XftColor) {
+ ((col.render.alpha&0xff00) << 24)
+ | ((col.render.red&0xff00) << 8)
+ | ((col.render.green&0xff00) << 0)
+ | ((col.render.blue&0xff00) >> 8),
+ col.render
+ };
+ return freelater(c);
+}
+
+/* Fonts */
+Font*
+loadfont(char *name) {
+ XFontStruct **xfonts;
+ char **missing, **font_names;
+ Biobuf *b;
+ Font *f;
+ int n, i;
+
+ missing = nil;
+ f = emallocz(sizeof *f);
+ f->name = estrdup(name);
+ if(!strncmp(f->name, "xft:", 4)) {
+ f->type = FXft;
+
+ f->font.xft = XftFontOpenXlfd(display, scr.screen, f->name + 4);
+ if(!f->font.xft)
+ f->font.xft = XftFontOpenName(display, scr.screen, f->name + 4);
+ if(!f->font.xft)
+ goto error;
+
+ f->ascent = f->font.xft->ascent;
+ f->descent = f->font.xft->descent;
+ }else {
+ f->font.set = XCreateFontSet(display, name, &missing, &n, nil);
+ if(missing) {
+ if(false) {
+ b = Bfdopen(dup(2), O_WRONLY);
+ Bprint(b, "%s: note: missing fontset%s for '%s':", argv0,
+ (n > 1 ? "s" : ""), name);
+ for(i = 0; i < n; i++)
+ Bprint(b, "%s %s", (i ? "," : ""), missing[i]);
+ Bprint(b, "\n");
+ Bterm(b);
+ }
+ freestringlist(missing);
+ }
+
+ if(f->font.set) {
+ f->type = FFontSet;
+ XFontsOfFontSet(f->font.set, &xfonts, &font_names);
+ f->ascent = xfonts[0]->ascent;
+ f->descent = xfonts[0]->descent;
+ }else {
+ f->type = FX11;
+ f->font.x11 = XLoadQueryFont(display, name);
+ if(!f->font.x11)
+ goto error;
+
+ f->ascent = f->font.x11->ascent;
+ f->descent = f->font.x11->descent;
+ }
+ }
+ f->height = f->ascent + f->descent;
+ return f;
+
+error:
+ fprint(2, "%s: cannot load font: %s\n", argv0, name);
+ f->type = 0;
+ freefont(f);
+ return nil;
+}
+
+void
+freefont(Font *f) {
+ switch(f->type) {
+ case FFontSet:
+ XFreeFontSet(display, f->font.set);
+ break;
+ case FXft:
+ XftFontClose(display, f->font.xft);
+ break;
+ case FX11:
+ XFreeFont(display, f->font.x11);
+ break;
+ default:
+ break;
+ }
+ free(f->name);
+ free(f);
+}
+
+Rectangle
+textextents_l(Font *font, char *text, uint len, int *offset) {
+ Rectangle rect;
+ XRectangle r;
+ XGlyphInfo i;
+ int unused;
+
+ if(!offset)
+ offset = &unused;
+
+ switch(font->type) {
+ case FFontSet:
+ *offset = Xutf8TextExtents(font->font.set, text, len, &r, nil);
+ return Rect(r.x, -r.y - r.height, r.x + r.width, -r.y);
+ case FXft:
+ XftTextExtentsUtf8(display, font->font.xft, (uchar*)text, len, &i);
+ *offset = i.xOff;
+ return Rect(-i.x, i.y - i.height, -i.x + i.width, i.y);
+ case FX11:
+ rect = ZR;
+ rect.max.x = XTextWidth(font->font.x11, text, len);
+ rect.max.y = font->ascent;
+ *offset = rect.max.x;
+ return rect;
+ default:
+ die("Invalid font type");
+ return ZR; /* shut up ken */
+ }
+}
+
+uint
+textwidth_l(Font *font, char *text, uint len) {
+ Rectangle r;
+
+ r = textextents_l(font, text, len, nil);
+ return Dx(r);
+}
+
+uint
+textwidth(Font *font, char *text) {
+ return textwidth_l(font, text, strlen(text));
+}
+
+uint
+labelh(Font *font) {
+ return max(font->height + font->descent + font->pad.min.y + font->pad.max.y, 1);
+}
+
+/* Misc */
+Atom
+xatom(char *name) {
+ void **e;
+
+ e = hash_get(&atommap, name, true);
+ if(*e == nil)
+ *e = (void*)XInternAtom(display, name, false);
+ return (Atom)*e;
+}
+
+void
+sendmessage(Window *w, char *name, long l0, long l1, long l2, long l3, long l4) {
+ XClientMessageEvent e;
+
+ e.type = ClientMessage;
+ e.window = w->xid;
+ e.message_type = xatom(name);
+ e.format = 32;
+ e.data.l[0] = l0;
+ e.data.l[1] = l1;
+ e.data.l[2] = l2;
+ e.data.l[3] = l3;
+ e.data.l[4] = l4;
+ sendevent(w, false, NoEventMask, (XEvent*)&e);
+}
+
+void
+sendevent(Window *w, bool propegate, long mask, XEvent *e) {
+ XSendEvent(display, w->xid, propegate, mask, e);
+}
+
+KeyCode
+keycode(char *name) {
+ return XKeysymToKeycode(display, XStringToKeysym(name));
+}
+
+typedef struct KMask KMask;
+
+static struct KMask {
+ int mask;
+ const char* name;
+} masks[] = {
+ {ShiftMask, "Shift"},
+ {ControlMask, "Control"},
+ {Mod1Mask, "Mod1"},
+ {Mod2Mask, "Mod2"},
+ {Mod3Mask, "Mod3"},
+ {Mod4Mask, "Mod4"},
+ {Mod5Mask, "Mod5"},
+ {0,}
+};
+
+bool
+parsekey(char *str, int *mask, char **key) {
+ static char *keys[16];
+ KMask *m;
+ int i, nkeys;
+
+ *mask = 0;
+ nkeys = tokenize(keys, nelem(keys), str, '-');
+ for(i=0; i < nkeys; i++) {
+ for(m=masks; m->mask; m++)
+ if(!strcasecmp(m->name, keys[i])) {
+ *mask |= m->mask;
+ goto next;
+ }
+ break;
+ next: continue;
+ }
+ if(key) {
+ if(nkeys)
+ *key = keys[i];
+ return i == nkeys - 1;
+ }
+ else
+ return i == nkeys;
+}
+
+void
+sync(void) {
+ XSync(display, false);
+}
+
+/* Properties */
+void
+delproperty(Window *w, char *prop) {
+ XDeleteProperty(display, w->xid, xatom(prop));
+}
+
+void
+changeproperty(Window *w, char *prop, char *type,
+ int width, uchar data[], int n) {
+ XChangeProperty(display, w->xid, xatom(prop), xatom(type), width,
+ PropModeReplace, data, n);
+}
+
+void
+changeprop_string(Window *w, char *prop, char *string) {
+ changeprop_char(w, prop, "UTF8_STRING", string, strlen(string));
+}
+
+void
+changeprop_char(Window *w, char *prop, char *type, char data[], int len) {
+ changeproperty(w, prop, type, 8, (uchar*)data, len);
+}
+
+void
+changeprop_short(Window *w, char *prop, char *type, short data[], int len) {
+ changeproperty(w, prop, type, 16, (uchar*)data, len);
+}
+
+void
+changeprop_long(Window *w, char *prop, char *type, long data[], int len) {
+ changeproperty(w, prop, type, 32, (uchar*)data, len);
+}
+
+void
+changeprop_ulong(Window *w, char *prop, char *type, ulong data[], int len) {
+ changeproperty(w, prop, type, 32, (uchar*)data, len);
+}
+
+void
+changeprop_textlist(Window *w, char *prop, char *type, char *data[]) {
+ char **p, *s, *t;
+ int len, n;
+
+ len = 0;
+ for(p=data; *p; p++)
+ len += strlen(*p) + 1;
+ s = emalloc(len);
+ t = s;
+ for(p=data; *p; p++) {
+ n = strlen(*p) + 1;
+ memcpy(t, *p, n);
+ t += n;
+ }
+ changeprop_char(w, prop, type, s, len);
+ free(s);
+}
+
+void
+freestringlist(char *list[]) {
+ XFreeStringList(list);
+}
+
+static ulong
+getprop(Window *w, char *prop, char *type, Atom *actual, int *format,
+ ulong offset, uchar **ret, ulong length) {
+ Atom typea;
+ ulong n, extra;
+ int status;
+
+ typea = (type ? xatom(type) : 0L);
+
+ status = XGetWindowProperty(display, w->xid,
+ xatom(prop), offset, length, false /* delete */,
+ typea, actual, format, &n, &extra, ret);
+
+ if(status != Success) {
+ *ret = nil;
+ return 0;
+ }
+ if(n == 0) {
+ free(*ret);
+ *ret = nil;
+ }
+ return n;
+}
+
+ulong
+getproperty(Window *w, char *prop, char *type, Atom *actual,
+ ulong offset, uchar **ret, ulong length) {
+ int format;
+
+ return getprop(w, prop, type, actual, &format, offset, ret, length);
+}
+
+ulong
+getprop_long(Window *w, char *prop, char *type,
+ ulong offset, long **ret, ulong length) {
+ Atom actual;
+ ulong n;
+ int format;
+
+ n = getprop(w, prop, type, &actual, &format, offset, (uchar**)ret, length);
+ if(n == 0 || format == 32 && xatom(type) == actual)
+ return n;
+ free(*ret);
+ *ret = 0;
+ return 0;
+}
+
+ulong
+getprop_ulong(Window *w, char *prop, char *type,
+ ulong offset, ulong **ret, ulong length) {
+ return getprop_long(w, prop, type, offset, (long**)ret, length);
+}
+
+char**
+strlistdup(char *list[]) {
+ char **p;
+ char *q;
+ int i, m, n;
+
+ n = 0;
+ m = 0;
+ for(p=list; *p; p++, n++)
+ m += strlen(*p) + 1;
+
+ p = malloc((n+1) * sizeof(*p) + m);
+ q = (char*)&p[n+1];
+
+ for(i=0; i < n; i++) {
+ p[i] = q;
+ m = strlen(list[i]) + 1;
+ memcpy(q, list[i], m);
+ q += m;
+ }
+ p[n] = nil;
+ return p;
+}
+
+int
+getprop_textlist(Window *w, char *name, char **ret[]) {
+ XTextProperty prop;
+ char **list;
+ int n;
+
+ *ret = nil;
+ n = 0;
+
+ XGetTextProperty(display, w->xid, &prop, xatom(name));
+ if(prop.nitems > 0) {
+ if(Xutf8TextPropertyToTextList(display, &prop, &list, &n) == Success)
+ *ret = list;
+ XFree(prop.value);
+ }
+ return n;
+}
+
+char*
+getprop_string(Window *w, char *name) {
+ char **list, *str;
+ int n;
+
+ str = nil;
+
+ n = getprop_textlist(w, name, &list);
+ if(n > 0)
+ str = estrdup(*list);
+ freestringlist(list);
+
+ return str;
+}
+
+Rectangle
+getwinrect(Window *w) {
+ XWindowAttributes wa;
+ Point p;
+
+ if(!XGetWindowAttributes(display, w->xid, &wa))
+ return ZR;
+ p = translate(w, &scr.root, ZP);
+ return rectaddpt(Rect(0, 0, wa.width, wa.height), p);
+}
+
+void
+setfocus(Window *w, int mode) {
+ XSetInputFocus(display, w->xid, mode, CurrentTime);
+}
+
+XWindow
+getfocus(void) {
+ XWindow ret;
+ int revert;
+
+ XGetInputFocus(display, &ret, &revert);
+ return ret;
+}
+
+/* Mouse */
+Point
+querypointer(Window *w) {
+ XWindow win;
+ Point pt;
+ uint ui;
+ int i;
+
+ XQueryPointer(display, w->xid, &win, &win, &i, &i, &pt.x, &pt.y, &ui);
+ return pt;
+}
+
+int
+pointerscreen(void) {
+ XWindow win;
+ Point pt;
+ uint ui;
+ int i;
+
+ return XQueryPointer(display, scr.root.xid, &win, &win, &i, &i,
+ &pt.x, &pt.y, &ui);
+}
+
+void
+warppointer(Point pt) {
+ /* Nasty kludge for xephyr, xnest. */
+ static int havereal = -1;
+ static char* real;
+
+ if(havereal == -1) {
+ real = getenv("REALDISPLAY");
+ havereal = real != nil;
+ }
+ if(havereal)
+ system(sxprint("DISPLAY=%s wiwarp %d %d", real, pt.x, pt.y));
+
+ XWarpPointer(display,
+ /* src, dest w */ None, scr.root.xid,
+ /* src_rect */ 0, 0, 0, 0,
+ /* target */ pt.x, pt.y);
+}
+
+Point
+translate(Window *src, Window *dst, Point sp) {
+ Point pt;
+ XWindow w;
+
+ XTranslateCoordinates(display, src->xid, dst->xid, sp.x, sp.y,
+ &pt.x, &pt.y, &w);
+ return pt;
+}
+
+int
+grabpointer(Window *w, Window *confine, Cursor cur, int mask) {
+ XWindow cw;
+
+ cw = None;
+ if(confine)
+ cw = confine->xid;
+ return XGrabPointer(display, w->xid, false /* owner events */, mask,
+ GrabModeAsync, GrabModeAsync, cw, cur, CurrentTime
+ ) == GrabSuccess;
+}
+
+void
+ungrabpointer(void) {
+ XUngrabPointer(display, CurrentTime);
+}
+
+int
+grabkeyboard(Window *w) {
+
+ return XGrabKeyboard(display, w->xid, true /* owner events */,
+ GrabModeAsync, GrabModeAsync, CurrentTime
+ ) == GrabSuccess;
+}
+
+void
+ungrabkeyboard(void) {
+ XUngrabKeyboard(display, CurrentTime);
+}
+
+/* Insanity */
+void
+sethints(Window *w) {
+ XSizeHints xs;
+ XWMHints *wmh;
+ WinHints *h;
+ Point p;
+ long size;
+
+ if(w->hints == nil)
+ w->hints = emalloc(sizeof *h);
+
+ h = w->hints;
+ memset(h, 0, sizeof *h);
+
+ h->max = Pt(INT_MAX, INT_MAX);
+ h->inc = Pt(1,1);
+
+ wmh = XGetWMHints(display, w->xid);
+ if(wmh) {
+ if(wmh->flags & WindowGroupHint)
+ h->group = wmh->window_group;
+ free(wmh);
+ }
+
+ if(!XGetWMNormalHints(display, w->xid, &xs, &size))
+ return;
+
+ if(xs.flags & PMinSize) {
+ h->min.x = xs.min_width;
+ h->min.y = xs.min_height;
+ }
+ if(xs.flags & PMaxSize) {
+ h->max.x = xs.max_width;
+ h->max.y = xs.max_height;
+ }
+
+ /* Goddamn buggy clients. */
+ if(h->max.x < h->min.x)
+ h->max.x = h->min.x;
+ if(h->max.y < h->min.y)
+ h->max.y = h->min.y;
+
+ h->base = h->min;
+ if(xs.flags & PBaseSize) {
+ p.x = xs.base_width;
+ p.y = xs.base_height;
+ h->base = p;
+ h->baspect = p;
+ }
+
+ if(xs.flags & PResizeInc) {
+ h->inc.x = max(xs.width_inc, 1);
+ h->inc.y = max(xs.height_inc, 1);
+ }
+
+ if(xs.flags & PAspect) {
+ h->aspect.min.x = xs.min_aspect.x;
+ h->aspect.min.y = xs.min_aspect.y;
+ h->aspect.max.x = xs.max_aspect.x;
+ h->aspect.max.y = xs.max_aspect.y;
+ }
+
+ h->position = (xs.flags & (USPosition|PPosition)) != 0;
+
+ if(!(xs.flags & PWinGravity))
+ xs.win_gravity = NorthWestGravity;
+ p = ZP;
+ switch (xs.win_gravity) {
+ case EastGravity:
+ case CenterGravity:
+ case WestGravity:
+ p.y = 1;
+ break;
+ case SouthEastGravity:
+ case SouthGravity:
+ case SouthWestGravity:
+ p.y = 2;
+ break;
+ }
+ switch (xs.win_gravity) {
+ case NorthGravity:
+ case CenterGravity:
+ case SouthGravity:
+ p.x = 1;
+ break;
+ case NorthEastGravity:
+ case EastGravity:
+ case SouthEastGravity:
+ p.x = 2;
+ break;
+ }
+ h->grav = p;
+ h->gravstatic = (xs.win_gravity == StaticGravity);
+}
+
+Rectangle
+sizehint(WinHints *h, Rectangle r) {
+ Point p, aspect, origin;
+
+ if(h == nil)
+ return r;
+
+ origin = r.min;
+ r = rectsubpt(r, origin);
+
+ /* Min/max */
+ r.max.x = max(r.max.x, h->min.x);
+ r.max.y = max(r.max.y, h->min.y);
+ r.max.x = min(r.max.x, h->max.x);
+ r.max.y = min(r.max.y, h->max.y);
+
+ /* Increment */
+ p = subpt(r.max, h->base);
+ r.max.x -= p.x % h->inc.x;
+ r.max.y -= p.y % h->inc.y;
+
+ /* Aspect */
+ p = subpt(r.max, h->baspect);
+ p.y = max(p.y, 1);
+
+ aspect = h->aspect.min;
+ if(p.x * aspect.y / p.y < aspect.x)
+ r.max.y = h->baspect.y
+ + p.x * aspect.y / aspect.x;
+
+ aspect = h->aspect.max;
+ if(p.x * aspect.y / p.y > aspect.x)
+ r.max.x = h->baspect.x
+ + p.y * aspect.x / aspect.y;
+
+ return rectaddpt(r, origin);
+}
+
+Rectangle
+gravitate(Rectangle rc, Rectangle rf, Point grav) {
+ Point d;
+
+ /* Get delta between frame and client rectangles */
+ d = subpt(subpt(rf.max, rf.min),
+ subpt(rc.max, rc.min));
+
+ /* Divide by 2 and apply gravity */
+ d = divpt(d, Pt(2, 2));
+ d = mulpt(d, grav);
+
+ return rectsubpt(rc, d);
+}
+
diff --git a/cmd/wmii/xdnd.c b/cmd/wmii/xdnd.c
new file mode 100644
index 0000000..abb3612
--- /dev/null
+++ b/cmd/wmii/xdnd.c
@@ -0,0 +1,88 @@
+/* Copyright ©2008-2010 Kris Maglione <maglione.k at Gmail>
+ * See LICENSE file for license details.
+ */
+#include "dat.h"
+#include "fns.h"
+
+void
+xdnd_initwindow(Window *w) {
+ long l;
+
+ l = 3; /* They are insane. Why is this an ATOM?! */
+ changeprop_long(w, "XdndAware", "ATOM", &l, 1);
+}
+
+typedef struct Dnd Dnd;
+struct Dnd {
+ XWindow source;
+ Rectangle r;
+};
+
+int
+xdnd_clientmessage(XClientMessageEvent *e) {
+ Window *w;
+ Dnd *dnd;
+ long *l;
+ Rectangle r;
+ Point p;
+ long pos, siz;
+ ulong msg;
+
+ dnd = nil;
+ msg = e->message_type;
+ l = e->data.l;
+ Dprint(DDnd, "ClientMessage: %A\n", msg);
+
+ if(msg == xatom("XdndEnter")) {
+ if(e->format != 32)
+ return -1;
+ w = findwin(e->window);
+ if(w) {
+ if(w->dnd == nil)
+ w->dnd = emallocz(sizeof *dnd);
+ dnd = w->dnd;
+ dnd->source = l[0];
+ dnd->r = ZR;
+ }
+ return 1;
+ }else
+ if(msg == xatom("XdndLeave")) {
+ if(e->format != 32)
+ return -1;
+ w = findwin(e->window);
+ if(w && w->dnd) {
+ free(w->dnd);
+ w->dnd = nil;
+ }
+ return 1;
+ }else
+ if(msg == xatom("XdndPosition")) {
+ if(e->format != 32)
+ return -1;
+ r = ZR;
+ w = findwin(e->window);
+ if(w)
+ dnd = w->dnd;
+ if(dnd) {
+ p.x = (ulong)l[2] >> 16;
+ p.y = (ulong)l[2] & 0xffff;
+ p = subpt(p, w->r.min);
+ Dprint(DDnd, "\tw: %W\n", w);
+ Dprint(DDnd, "\tp: %P\n", p);
+ if(eqrect(dnd->r, ZR) || !rect_haspoint_p(p, dnd->r))
+ if(w->handler->dndmotion)
+ dnd->r = w->handler->dndmotion(w, p);
+ r = dnd->r;
+ if(!eqrect(r, ZR))
+ r = rectaddpt(r, w->r.min);
+ Dprint(DDnd, "\tr: %R\n", r);
+ }
+ pos = (r.min.x<<16) | r.min.y;
+ siz = (Dx(r)<<16) | Dy(r);
+ sendmessage(window(l[0]), "XdndStatus", e->window, 0, pos, siz, 0);
+ return 1;
+ }
+
+ return 0;
+}
+
diff --git a/cmd/wmii/xext.c b/cmd/wmii/xext.c
new file mode 100644
index 0000000..ba851e5
--- /dev/null
+++ b/cmd/wmii/xext.c
@@ -0,0 +1,160 @@
+/* Copyright ©2008-2010 Kris Maglione <maglione.k at Gmail>
+ * See LICENSE file for license details.
+ */
+#define _X11_VISIBLE
+#include "dat.h"
+#include <X11/extensions/Xrender.h>
+#include <X11/extensions/Xinerama.h>
+#include "fns.h"
+
+#if RANDR_MAJOR < 1
+# error XRandR versions less than 1.0 are not supported
+#endif
+
+static void randr_screenchange(XRRScreenChangeNotifyEvent*);
+static bool randr_event_p(XEvent *e);
+static void randr_init(void);
+static void render_init(void);
+static void xinerama_init(void);
+
+typedef void (*EvHandler)(XEvent*);
+static EvHandler randr_handlers[RRNumberEvents];
+
+bool have_RandR;
+bool have_render;
+bool have_xinerama;
+int randr_eventbase;
+
+static void
+handle(XEvent *e, EvHandler h[], int base) {
+
+ if(h[e->type-base])
+ h[e->type-base](e);
+}
+
+void
+xext_init(void) {
+ randr_init();
+ render_init();
+ xinerama_init();
+}
+
+void
+xext_event(XEvent *e) {
+
+ if(randr_event_p(e))
+ handle(e, randr_handlers, randr_eventbase);
+}
+
+static void
+randr_init(void) {
+ int errorbase, major, minor;
+
+ have_RandR = XRRQueryExtension(display, &randr_eventbase, &errorbase);
+ if(have_RandR)
+ if(XRRQueryVersion(display, &major, &minor) && major < 1)
+ have_RandR = false;
+ if(have_RandR)
+ XRRSelectInput(display, scr.root.xid, RRScreenChangeNotifyMask);
+}
+
+static bool
+randr_event_p(XEvent *e) {
+ return have_RandR
+ && (uint)e->type - randr_eventbase < RRNumberEvents;
+}
+
+static void
+randr_screenchange(XRRScreenChangeNotifyEvent *ev) {
+
+ XRRUpdateConfiguration((XEvent*)ev);
+ if(ev->rotation*90 % 180)
+ scr.rect = Rect(0, 0, ev->width, ev->height);
+ else
+ scr.rect = Rect(0, 0, ev->height, ev->width);
+ init_screens();
+}
+
+static EvHandler randr_handlers[] = {
+ [RRScreenChangeNotify] = (EvHandler)randr_screenchange,
+};
+
+/* Ripped most graciously from ecore_x. XRender documentation
+ * is sparse.
+ */
+static void
+render_init(void) {
+ XVisualInfo *vip;
+ XVisualInfo vi;
+ int base, i, n;
+
+ have_render = XRenderQueryExtension(display, &base, &base);
+ if(!have_render)
+ return;
+
+ vi.class = TrueColor;
+ vi.depth = 32;
+ vi.screen = scr.screen;
+ vip = XGetVisualInfo(display, VisualClassMask
+ | VisualDepthMask
+ | VisualScreenMask,
+ &vi, &n);
+ for(i=0; i < n; i++)
+ if(render_argb_p(vip[i].visual)) {
+ render_visual = vip[i].visual;
+ scr.visual32 = render_visual;
+ break;
+ }
+ XFree(vip);
+}
+
+bool
+render_argb_p(Visual *v) {
+ XRenderPictFormat *f;
+
+ if(!have_render)
+ return false;
+ f = XRenderFindVisualFormat(display, v);
+ return f
+ && f->type == PictTypeDirect
+ && f->direct.alphaMask;
+}
+
+static void
+xinerama_init(void) {
+ int base;
+
+ have_xinerama = XineramaQueryExtension(display, &base, &base);
+}
+
+static bool
+xinerama_active(void) {
+ return have_xinerama && XineramaIsActive(display);
+}
+
+Rectangle*
+xinerama_screens(int *np) {
+ static Rectangle *rects;
+ XineramaScreenInfo *res;
+ int i, n;
+
+ if(!xinerama_active()) {
+ *np = 1;
+ return &scr.rect;
+ }
+
+ free(rects);
+ res = XineramaQueryScreens(display, &n);
+ rects = emalloc(n * sizeof *rects);
+ for(i=0; i < n; i++) {
+ rects[i].min.x = res[i].x_org;
+ rects[i].min.y = res[i].y_org;
+ rects[i].max.x = res[i].x_org + res[i].width;
+ rects[i].max.y = res[i].y_org + res[i].height;
+ }
+ XFree(res);
+
+ *np = n;
+ return rects;
+}
+
diff --git a/cmd/wmii9menu.c b/cmd/wmii9menu.c
new file mode 100644
index 0000000..02049f3
--- /dev/null
+++ b/cmd/wmii9menu.c
@@ -0,0 +1,341 @@
+/* Licence
+ * =======
+ *
+ * 9menu is free software, and is Copyright (c) 1994 by David Hogan and
+ * Arnold Robbins. Permission is granted to all sentient beings to use
+ * this software, to make copies of it, and to distribute those copies,
+ * provided that:
+ *
+ * (1) the copyright and licence notices are left intact
+ * (2) the recipients are aware that it is free software
+ * (3) any unapproved changes in functionality are either
+ * (i) only distributed as patches
+ * or (ii) distributed as a new program which is not called 9menu
+ * and whose documentation gives credit where it is due
+ * (4) the authors are not held responsible for any defects
+ * or shortcomings in the software, or damages caused by it.
+ *
+ * There is no warranty for this software. Have a nice day.
+ *
+ * --
+ * Arnold Robbins
+ * arnold@skeeve.com
+ *
+ * 9menu.c
+ *
+ * This program puts up a window that is just a menu, and executes
+ * commands that correspond to the items selected.
+ *
+ * Initial idea: Arnold Robbins
+ * Version using libXg: Matty Farrow (some ideas borrowed)
+ * This code by: David Hogan and Arnold Robbins
+ */
+
+/*
+ * Heavily modified by Kris Maglione for use with wmii.
+ */
+
+#define IXP_NO_P9_
+#define IXP_P9_STRUCTS
+#include <fmt.h>
+#include <ixp.h>
+#include <stdarg.h>
+#include <stdbool.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <clientutil.h>
+#include <util.h>
+#include <x11.h>
+
+char version[] = "wmii9menu-" VERSION " ©2010 Kris Maglione, ©1994 David Hogan, Arnold Robbins";
+
+static Window* menuwin;
+
+static CTuple cnorm;
+static CTuple csel;
+static Font* font;
+
+static int wborder;
+
+char buffer[8092];
+char* _buffer;
+
+/* for XSetWMProperties to use */
+int g_argc;
+char **g_argv;
+
+char *initial = "";
+int cur;
+
+static char** labels; /* list of labels and commands */
+static char** commands;
+static int numitems;
+
+void usage(void);
+void run_menu(void);
+void create_window(void);
+void size_window(int, int);
+void redraw(int, int);
+void warpmouse(int, int);
+void memory(void);
+int args(void);
+
+ErrorCode ignored_xerrors[] = {
+ { 0, }
+};
+
+/* xext.c */
+void xext_init(void);
+Rectangle* xinerama_screens(int*);
+/* geom.c */
+bool rect_haspoint_p(Point, Rectangle);
+
+Cursor cursor[1];
+Visual* render_visual;
+
+void init_screens(void);
+void
+init_screens(void) {
+ Rectangle *rects;
+ Point p;
+ int i, n;
+
+ rects = xinerama_screens(&n);
+ p = querypointer(&scr.root);
+ for(i=0; i < n; i++) {
+ if(rect_haspoint_p(p, rects[i]))
+ break;
+ }
+ if(i == n)
+ i = 0;
+ scr.rect = rects[i];
+}
+
+/* main --- crack arguments, set up X stuff, run the main menu loop */
+
+int
+main(int argc, char **argv)
+{
+ static char *address;
+ char *cp;
+ int i;
+
+ g_argc = argc;
+ g_argv = argv;
+
+ ARGBEGIN{
+ case 'v':
+ print("%s\n", version);
+ return 0;
+ case 'a':
+ address = EARGF(usage());
+ break;
+ case 'i':
+ initial = EARGF(usage());
+ break;
+ default:
+ usage();
+ }ARGEND;
+
+ if(argc == 0)
+ usage();
+
+ initdisplay();
+ xext_init();
+ init_screens();
+ create_window();
+
+ numitems = argc;
+
+ labels = emalloc(numitems * sizeof *labels);
+ commands = emalloc(numitems * sizeof *labels);
+
+ for(i = 0; i < numitems; i++) {
+ labels[i] = argv[i];
+ if((cp = strchr(labels[i], ':')) != nil) {
+ *cp++ = '\0';
+ commands[i] = cp;
+ } else
+ commands[i] = labels[i];
+ if(strcmp(labels[i], initial) == 0)
+ cur = i;
+ }
+
+ client_init(address);
+
+ wborder = strtol(readctl("border "), nil, 10);
+ loadcolor(&cnorm, readctl("normcolors "));
+ loadcolor(&csel, readctl("focuscolors "));
+ font = loadfont(readctl("font "));
+ if(!font)
+ fatal("Can't load font");
+
+ run_menu();
+
+ XCloseDisplay(display);
+ return 0;
+}
+
+/* usage --- print a usage message and die */
+
+void
+usage(void)
+{
+ fprintf(stderr, "usage: %s -v\n", argv0);
+ fprintf(stderr, " %s [-a <address>] [-i <arg>] menitem[:command] ...\n", argv0);
+ exit(0);
+}
+
+/* run_menu --- put up the window, execute selected commands */
+
+enum {
+ MouseMask =
+ ButtonPressMask
+ | ButtonReleaseMask
+ | ButtonMotionMask
+ | PointerMotionMask,
+ MenuMask =
+ MouseMask
+ | StructureNotifyMask
+ | ExposureMask
+};
+
+void
+run_menu(void)
+{
+ XEvent ev;
+ int i, old, wide, high;
+
+ wide = 0;
+ high = labelh(font);
+ for(i = 0; i < numitems; i++)
+ wide = max(wide, textwidth(font, labels[i]));
+ wide += font->height & ~1;
+
+ size_window(wide, high);
+ warpmouse(wide, high);
+
+ for(;;) {
+ XNextEvent(display, &ev);
+ switch (ev.type) {
+ default:
+ fprintf(stderr, "%s: unknown ev.type %d\n",
+ argv0, ev.type);
+ break;
+ case ButtonRelease:
+ i = ev.xbutton.y / high;
+ if(ev.xbutton.x < 0 || ev.xbutton.x > wide)
+ return;
+ else if(i < 0 || i >= numitems)
+ return;
+
+ printf("%s\n", commands[i]);
+ return;
+ case ButtonPress:
+ case MotionNotify:
+ old = cur;
+ cur = ev.xbutton.y / high;
+ if(ev.xbutton.x < 0 || ev.xbutton.x > wide)
+ cur = ~0;
+ if(cur == old)
+ break;
+ redraw(high, wide);
+ break;
+ case MapNotify:
+ redraw(high, wide);
+ break;
+ case Expose:
+ redraw(high, wide);
+ break;
+ case ConfigureNotify:
+ case MappingNotify:
+ break;
+ }
+ }
+}
+
+/* set_wm_hints --- set all the window manager hints */
+
+void
+create_window(void)
+{
+ WinAttr wa = { 0 };
+ XEvent e;
+
+ wa.override_redirect = true;
+ menuwin = createwindow(&scr.root, Rect(-1, -1, 0, 0),
+ scr.depth, InputOutput,
+ &wa, CWOverrideRedirect);
+ selectinput(menuwin, MenuMask);
+ mapwin(menuwin);
+ XMaskEvent(display, StructureNotifyMask, &e);
+ if(!grabpointer(menuwin, nil, 0, MouseMask))
+ fatal("Failed to grab the mouse\n");
+ XSetCommand(display, menuwin->xid, g_argv, g_argc);
+}
+
+void
+size_window(int wide, int high)
+{
+ Rectangle r;
+ Point p;
+ int h;
+
+ h = high * numitems;
+ r = Rect(0, 0, wide, h);
+
+ p = querypointer(&scr.root);
+ p.x -= wide / 2;
+ p.x = max(p.x, scr.rect.min.x);
+ p.x = min(p.x, scr.rect.max.x - wide);
+
+ p.y -= cur * high + high / 2;
+ p.y = max(p.y, scr.rect.min.y);
+ p.y = min(p.y, scr.rect.max.y - h);
+
+ reshapewin(menuwin, rectaddpt(r, p));
+
+ //XSetWindowBackground(display, menuwin->xid, cnorm.bg);
+ setborder(menuwin, 1, cnorm.border);
+}
+
+/* redraw --- actually redraw the menu */
+
+void
+redraw(int high, int wide)
+{
+ Rectangle r;
+ CTuple *c;
+ int i;
+
+ r = Rect(0, 0, wide, high);
+ for(i = 0; i < numitems; i++) {
+ if(cur == i)
+ c = &csel;
+ else
+ c = &cnorm;
+ r = rectsetorigin(r, Pt(0, i * high));
+ fill(menuwin, r, c->bg);
+ drawstring(menuwin, font, r, Center, labels[i], c->fg);
+ }
+}
+
+/* warpmouse --- bring the mouse to the menu */
+
+void
+warpmouse(int wide, int high)
+{
+ Point p;
+ int offset;
+
+ /* move tip of pointer into middle of menu item */
+ offset = labelh(font) / 2;
+ offset += 6; /* fudge factor */
+
+ p = Pt(wide / 2, cur*high - high/2 + offset);
+ p = addpt(p, menuwin->r.min);
+
+ warppointer(p);
+}
+
diff --git a/cmd/wmiir.c b/cmd/wmiir.c
new file mode 100644
index 0000000..1801df8
--- /dev/null
+++ b/cmd/wmiir.c
@@ -0,0 +1,421 @@
+/* Copyight ©2007-2010 Kris Maglione <fbsdaemon@gmail.com>
+ * See LICENSE file for license details.
+ */
+#define IXP_NO_P9_
+#define IXP_P9_STRUCTS
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <time.h>
+#include <unistd.h>
+#include <ixp.h>
+#include <util.h>
+#include <fmt.h>
+
+static IxpClient *client;
+
+static void
+usage(void) {
+ fprint(1,
+ "usage: %s [-a <address>] {create | ls [-dlp] | read | remove | write} <file>\n"
+ " %s [-a <address>] xwrite <file> <data>\n"
+ " %s -v\n", argv0, argv0, argv0);
+ exit(1);
+}
+
+static int
+errfmt(Fmt *f) {
+ return fmtstrcpy(f, ixp_errbuf());
+}
+
+/* Utility Functions */
+static void
+write_data(IxpCFid *fid, char *name) {
+ void *buf;
+ int len;
+
+ buf = emalloc(fid->iounit);;
+ for(;;) {
+ len = read(0, buf, fid->iounit);
+ if(len <= 0)
+ break;
+ if(ixp_write(fid, buf, len) != len)
+ fatal("cannot write file %q\n", name);
+ }
+ free(buf);
+}
+
+static int
+comp_stat(const void *s1, const void *s2) {
+ Stat *st1, *st2;
+
+ st1 = (Stat*)s1;
+ st2 = (Stat*)s2;
+ return strcmp(st1->name, st2->name);
+}
+
+static void
+setrwx(long m, char *s) {
+ static char *modes[] = {
+ "---", "--x", "-w-",
+ "-wx", "r--", "r-x",
+ "rw-", "rwx",
+ };
+ strncpy(s, modes[m], 3);
+}
+
+static char *
+modestr(uint mode) {
+ static char buf[16];
+
+ buf[0]='-';
+ if(mode & P9_DMDIR)
+ buf[0]='d';
+ buf[1]='-';
+ setrwx((mode >> 6) & 7, &buf[2]);
+ setrwx((mode >> 3) & 7, &buf[5]);
+ setrwx((mode >> 0) & 7, &buf[8]);
+ buf[11] = 0;
+ return buf;
+}
+
+static char*
+timestr(time_t val) {
+ static char buf[32];
+
+ strftime(buf, sizeof buf, "%Y-%m-%d %H:%M", localtime(&val));
+ return buf;
+}
+
+static void
+print_stat(Stat *s, int lflag, char *file, int pflag) {
+ char *slash;
+
+ slash = "";
+ if(pflag)
+ slash = "/";
+ else
+ file = "";
+
+ if(lflag)
+ print("%s %s %s %5llud %s %s%s%s\n",
+ modestr(s->mode), s->uid, s->gid, s->length,
+ timestr(s->mtime), file, slash, s->name);
+ else {
+ if((s->mode&P9_DMDIR) && strcmp(s->name, "/"))
+ print("%s%s%s/\n", file, slash, s->name);
+ else
+ print("%s%s%s\n", file, slash, s->name);
+ }
+}
+
+/* Service Functions */
+static int
+xwrite(int argc, char *argv[]) {
+ IxpCFid *fid;
+ char *file;
+
+ ARGBEGIN{
+ default:
+ usage();
+ }ARGEND;
+
+ file = EARGF(usage());
+ fid = ixp_open(client, file, P9_OWRITE);
+ if(fid == nil)
+ fatal("Can't open file '%s': %r\n", file);
+
+ write_data(fid, file);
+ ixp_close(fid);
+ return 0;
+}
+
+static int
+xawrite(int argc, char *argv[]) {
+ IxpCFid *fid;
+ char *file, *buf;
+ int nbuf, i;
+
+ ARGBEGIN{
+ default:
+ usage();
+ }ARGEND;
+
+ file = EARGF(usage());
+ fid = ixp_open(client, file, P9_OWRITE);
+ if(fid == nil)
+ fatal("Can't open file '%s': %r\n", file);
+
+ nbuf = 1;
+ for(i=0; i < argc; i++)
+ nbuf += strlen(argv[i]) + (i > 0);
+ buf = emalloc(nbuf);
+ buf[0] = '\0';
+ while(argc) {
+ strcat(buf, ARGF());
+ if(argc)
+ strcat(buf, " ");
+ }
+
+ if(ixp_write(fid, buf, nbuf) == -1)
+ fatal("cannot write file '%s': %r\n", file);
+ ixp_close(fid);
+ free(buf);
+ return 0;
+}
+
+static int
+xcreate(int argc, char *argv[]) {
+ IxpCFid *fid;
+ char *file;
+
+ ARGBEGIN{
+ default:
+ usage();
+ }ARGEND;
+
+ file = EARGF(usage());
+ fid = ixp_create(client, file, 0777, P9_OWRITE);
+ if(fid == nil)
+ fatal("Can't create file '%s': %r\n", file);
+
+ if((fid->qid.type&P9_DMDIR) == 0)
+ write_data(fid, file);
+ ixp_close(fid);
+ return 0;
+}
+
+static int
+xremove(int argc, char *argv[]) {
+ char *file;
+
+ ARGBEGIN{
+ default:
+ usage();
+ }ARGEND;
+
+ file = EARGF(usage());
+ do {
+ if(!ixp_remove(client, file))
+ fprint(2, "%s: Can't remove file '%s': %r\n", argv0, file);
+ }while((file = ARGF()));
+ return 0;
+}
+
+static int
+xread(int argc, char *argv[]) {
+ IxpCFid *fid;
+ char *file, *buf;
+ int count;
+
+ ARGBEGIN{
+ default:
+ usage();
+ }ARGEND;
+
+ if(argc == 0)
+ usage();
+ file = EARGF(usage());
+ do {
+ fid = ixp_open(client, file, P9_OREAD);
+ if(fid == nil)
+ fatal("Can't open file '%s': %r\n", file);
+
+ buf = emalloc(fid->iounit);
+ while((count = ixp_read(fid, buf, fid->iounit)) > 0)
+ write(1, buf, count);
+ ixp_close(fid);
+
+ if(count == -1)
+ fprint(2, "%s: cannot read file '%s': %r\n", argv0, file);
+ } while((file = ARGF()));
+
+ return 0;
+}
+
+static int
+xls(int argc, char *argv[]) {
+ IxpMsg m;
+ Stat *stat;
+ IxpCFid *fid;
+ char *file;
+ char *buf;
+ int lflag, dflag, pflag;
+ int count, nstat, mstat, i;
+
+ lflag = dflag = pflag = 0;
+
+ ARGBEGIN{
+ case 'l':
+ lflag++;
+ break;
+ case 'd':
+ dflag++;
+ break;
+ case 'p':
+ pflag++;
+ break;
+ default:
+ usage();
+ }ARGEND;
+
+ count = 0;
+ file = EARGF(usage());
+ do {
+ stat = ixp_stat(client, file);
+ if(stat == nil)
+ fatal("cannot stat file '%s': %r\n", file);
+
+ i = strlen(file);
+ if(file[i-1] == '/') {
+ file[i-1] = '\0';
+ if(!(stat->mode&P9_DMDIR))
+ fatal("%s: not a directory", file);
+ }
+ if(dflag || (stat->mode&P9_DMDIR) == 0) {
+ print_stat(stat, lflag, file, pflag);
+ ixp_freestat(stat);
+ continue;
+ }
+ ixp_freestat(stat);
+
+ fid = ixp_open(client, file, P9_OREAD);
+ if(fid == nil)
+ fatal("Can't open file '%s': %r\n", file);
+
+ nstat = 0;
+ mstat = 16;
+ stat = emalloc(mstat * sizeof *stat);
+ buf = emalloc(fid->iounit);
+ while((count = ixp_read(fid, buf, fid->iounit)) > 0) {
+ m = ixp_message(buf, count, MsgUnpack);
+ while(m.pos < m.end) {
+ if(nstat == mstat) {
+ mstat <<= 1;
+ stat = erealloc(stat, mstat * sizeof *stat);
+ }
+ ixp_pstat(&m, &stat[nstat++]);
+ }
+ }
+ ixp_close(fid);
+
+ qsort(stat, nstat, sizeof *stat, comp_stat);
+ for(i = 0; i < nstat; i++) {
+ print_stat(&stat[i], lflag, file, pflag);
+ ixp_freestat(&stat[i]);
+ }
+ free(stat);
+ } while((file = ARGF()));
+
+ if(count == -1)
+ fatal("cannot read directory '%s': %r\n", file);
+ return 0;
+}
+
+static int
+xnamespace(int argc, char *argv[]) {
+ char *path;
+
+ ARGBEGIN{
+ default:
+ usage();
+ }ARGEND;
+
+ path = ixp_namespace();
+ if(path == nil)
+ fatal("can't find namespace: %r\n");
+ print("%s\n", path);
+ return 0;
+}
+
+static int
+xsetsid(int argc, char *argv[]) {
+ char *av0;
+
+ av0 = nil;
+ ARGBEGIN{
+ case '0':
+ av0 = EARGF(usage());
+ break;
+ default:
+ usage();
+ }ARGEND;
+ if(av0 == nil)
+ av0 = argv[0];
+ if(av0 == nil)
+ return 1;
+
+ setsid();
+ execvp(av0, argv);
+ fatal("setsid: can't exec: %r");
+ return 1; /* NOTREACHED */
+}
+
+typedef struct exectab exectab;
+struct exectab {
+ char *cmd;
+ int (*fn)(int, char**);
+} fstab[] = {
+ {"cat", xread},
+ {"create", xcreate},
+ {"ls", xls},
+ {"read", xread},
+ {"remove", xremove},
+ {"rm", xremove},
+ {"write", xwrite},
+ {"xwrite", xawrite},
+ {0, }
+}, utiltab[] = {
+ {"namespace", xnamespace},
+ {"ns", xnamespace},
+ {"setsid", xsetsid},
+ {0, }
+};
+
+int
+main(int argc, char *argv[]) {
+ char *address;
+ exectab *tab;
+ int ret;
+
+ quotefmtinstall();
+ fmtinstall('r', errfmt);
+
+ address = getenv("WMII_ADDRESS");
+
+ ARGBEGIN{
+ case 'v':
+ print("%s-" VERSION ", " COPYRIGHT "\n", argv0);
+ exit(0);
+ case 'a':
+ address = EARGF(usage());
+ break;
+ default:
+ usage();
+ }ARGEND;
+
+ if(argc < 1)
+ usage();
+
+ for(tab=utiltab; tab->cmd; tab++)
+ if(!strcmp(*argv, tab->cmd))
+ return tab->fn(argc, argv);
+
+ if(address && *address)
+ client = ixp_mount(address);
+ else
+ client = ixp_nsmount("wmii");
+ if(client == nil)
+ fatal("can't mount: %r\n");
+
+ for(tab=fstab; tab->cmd; tab++)
+ if(strcmp(*argv, tab->cmd) == 0) break;
+ if(tab->cmd == 0)
+ usage();
+
+ ret = tab->fn(argc, argv);
+
+ ixp_unmount(client);
+ return ret;
+}
+