commit 8b0680e70836aa4a2cba7877cc40d46c1f7f8a47
Author: arg@suckless.org <unknown>
Date: Mon, 12 Feb 2007 11:52:41 +0100
initial commit of stereo wm
Diffstat:
2wm.1 | | | 92 | +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ |
2wm.h | | | 126 | +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ |
LICENSE | | | 22 | ++++++++++++++++++++++ |
Makefile | | | 61 | +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ |
README | | | 39 | +++++++++++++++++++++++++++++++++++++++ |
client.c | | | 356 | +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ |
config.arg.h | | | 94 | +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ |
config.default.h | | | 90 | +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ |
config.h | | | 94 | +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ |
config.mk | | | 29 | +++++++++++++++++++++++++++++ |
draw.c | | | 175 | +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ |
event.c | | | 387 | +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ |
main.c | | | 288 | +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ |
tag.c | | | 134 | +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ |
util.c | | | 54 | ++++++++++++++++++++++++++++++++++++++++++++++++++++++ |
view.c | | | 271 | +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ |
16 files changed, 2312 insertions(+), 0 deletions(-)
diff --git a/2wm.1 b/2wm.1
@@ -0,0 +1,92 @@
+.TH 2WM 1 2wm-VERSION
+.SH NAME
+2wm \- stereo window manager
+.SH SYNOPSIS
+.B 2wm
+.RB [ \-v ]
+.SH DESCRIPTION
+2wm is a dynamic window manager for X. It manages windows in a master and
+stacking area. The master area contains the windows which currently need most
+attention, whereas the stacking area contains all other windows. Dialog windows
+are always managed floating, regardless of the mode applied. Each window can be
+made floating on demand.
+.P
+There are no workspaces and no tags. Instead there are two sets of windows, the
+currently attached windows and the currently detached windows. Each window can
+be detached and reattached, and you can invert the view to attach all detached
+windows and to detach all attached windows.
+.P
+2wm draws a small border around windows to indicate the focus state.
+.SH OPTIONS
+.TP
+.B \-v
+prints version information to standard output, then exits.
+.SH USAGE
+.SS Keyboard commands
+.TP
+.B Mod1-Shift-Return
+Start
+.BR xterm (1).
+.TP
+.B Mod1-j
+Focus next window.
+.TP
+.B Mod1-k
+Focus previous window.
+.TP
+.B Mod1-Return
+Zooms/cycles current window to/from master area, toggles maximization of current floating window.
+.TP
+.B Mod1-g
+Grow master area.
+.TP
+.B Mod1-s
+Shrink master area.
+.TP
+.B Mod1-a
+Attach most recently detached window.
+.TP
+.B Mod1-d
+Detach focused window.
+.TP
+.B Mod1-i
+Increase the number of windows in the master area.
+.TP
+.B Mod1-r
+Decrease the number of windows in the master area.
+.TP
+.B Mod1-Shift-c
+Close focused window.
+.TP
+.B Mod1-space
+Invert view, toggles detached with attached windows.
+.TP
+.B Mod1-Shift-space
+Toggle focused window between floating and non-floating state.
+.TP
+.B Mod1-Shift-q
+Quit 2wm.
+.SS Mouse commands
+.TP
+.B Mod1-Button1
+Move current floating window while dragging.
+.TP
+.B Mod1-Button2
+Zoom current window to the master area, toggles maximization of current floating window.
+.TP
+.B Mod1-Button3
+Resize current floating window while dragging.
+.SH CUSTOMIZATION
+2wm is customized by creating a custom config.h and (re)compiling the source
+code. This keeps it fast, secure and simple.
+.SH SEE ALSO
+.BR dmenu (1) ,
+.BR sbar (1)
+.SH BUGS
+Java applications which use the XToolkit/XAWT backend may draw grey windows
+only. The XToolkit/XAWT backend breaks ICCCM-compliance in recent JDK 1.5 and early
+JDK 1.6 versions, because it assumes a reparenting window manager. As a workaround
+you can use JDK 1.4 (which doesn't contain the XToolkit/XAWT backend) or you
+can set the following environment variable (to use the older Motif
+backend instead):
+.BR AWT_TOOLKIT=MToolkit .
diff --git a/2wm.h b/2wm.h
@@ -0,0 +1,126 @@
+/* (C)opyright MMVII Anselm R. Garbe <garbeam at gmail dot com>
+ * See LICENSE file for license details.
+ */
+
+#include "config.h"
+#include <X11/Xlib.h>
+
+/* mask shorthands, used in event.c and client.c */
+#define BUTTONMASK (ButtonPressMask | ButtonReleaseMask)
+
+enum { NetSupported, NetWMName, NetLast }; /* EWMH atoms */
+enum { WMProtocols, WMDelete, WMState, WMLast }; /* default atoms */
+enum { CurNormal, CurResize, CurMove, CurLast }; /* cursor */
+enum { ColBorder, ColFG, ColBG, ColLast }; /* color */
+
+typedef union {
+ const char *cmd;
+ int i;
+} Arg; /* argument type */
+
+typedef struct {
+ int ascent;
+ int descent;
+ int height;
+ XFontSet set;
+ XFontStruct *xfont;
+} Fnt;
+
+typedef struct {
+ int x, y, w, h;
+ unsigned long norm[ColLast];
+ unsigned long sel[ColLast];
+ Drawable drawable;
+ Fnt font;
+ GC gc;
+} DC; /* draw context */
+
+typedef struct Client Client;
+struct Client {
+ char name[256];
+ int x, y, w, h;
+ int rx, ry, rw, rh; /* revert geometry */
+ int basew, baseh, incw, inch, maxw, maxh, minw, minh;
+ int minax, minay, maxax, maxay;
+ long flags;
+ unsigned int border;
+ Bool isfixed, isfloat, ismax;
+ Bool *tags;
+ Client *next;
+ Client *prev;
+ Client *snext;
+ Window win;
+};
+
+extern const char *tags[]; /* all tags */
+extern char stext[256]; /* status text */
+extern int bh, bmw; /* bar height, bar mode label width */
+extern int screen, sx, sy, sw, sh; /* screen geometry */
+extern int wax, way, wah, waw; /* windowarea geometry */
+extern unsigned int master, nmaster; /* master percent, number of master clients */
+extern unsigned int ntags, numlockmask; /* number of tags, dynamic lock mask */
+extern void (*handler[LASTEvent])(XEvent *); /* event handler */
+extern void (*arrange)(void); /* arrange function, indicates mode */
+extern Atom wmatom[WMLast], netatom[NetLast];
+extern Bool running, selscreen, *seltag; /* seltag is array of Bool */
+extern Client *clients, *sel, *stack; /* global client list and stack */
+extern Cursor cursor[CurLast];
+extern DC dc; /* global draw context */
+extern Display *dpy;
+extern Window root, barwin;
+
+/* client.c */
+extern void configure(Client *c); /* send synthetic configure event */
+extern void focus(Client *c); /* focus c, c may be NULL */
+extern Client *getclient(Window w); /* return client of w */
+extern Bool isprotodel(Client *c); /* returns True if c->win supports wmatom[WMDelete] */
+extern void killclient(Arg *arg); /* kill c nicely */
+extern void manage(Window w, XWindowAttributes *wa); /* manage new client */
+extern void resize(Client *c, Bool sizehints); /* resize c*/
+extern void updatesizehints(Client *c); /* update the size hint variables of c */
+extern void updatetitle(Client *c); /* update the name of c */
+extern void unmanage(Client *c); /* destroy c */
+
+/* draw.c */
+extern void drawstatus(void); /* draw the bar */
+extern unsigned long getcolor(const char *colstr); /* return color of colstr */
+extern void setfont(const char *fontstr); /* set the font for DC */
+extern unsigned int textw(const char *text); /* return the width of text in px*/
+
+/* event.c */
+extern void grabkeys(void); /* grab all keys defined in config.h */
+extern void procevent(void); /* process pending X events */
+
+/* main.c */
+extern void quit(Arg *arg); /* quit 2wm nicely */
+extern void sendevent(Window w, Atom a, long value); /* send synthetic event to w */
+extern int xerror(Display *dsply, XErrorEvent *ee); /* 2wm's X error handler */
+
+/* tag.c */
+extern void initrregs(void); /* initialize regexps of rules defined in config.h */
+extern Client *getnext(Client *c); /* returns next visible client */
+extern Client *getprev(Client *c); /* returns previous visible client */
+extern void settags(Client *c, Client *trans); /* sets tags of c */
+extern void tag(Arg *arg); /* tags c with arg's index */
+extern void toggletag(Arg *arg); /* toggles c tags with arg's index */
+
+/* util.c */
+extern void *emallocz(unsigned int size); /* allocates zero-initialized memory, exits on error */
+extern void eprint(const char *errstr, ...); /* prints errstr and exits with 1 */
+extern void spawn(Arg *arg); /* forks a new subprocess with to arg's cmd */
+
+/* view.c */
+extern void detach(Client *c); /* detaches c from global client list */
+extern void dofloat(void); /* arranges all windows floating */
+extern void dotile(void); /* arranges all windows tiled */
+extern void focusnext(Arg *arg); /* focuses next visible client, arg is ignored */
+extern void focusprev(Arg *arg); /* focuses previous visible client, arg is ignored */
+extern void incnmaster(Arg *arg); /* increments nmaster with arg's index value */
+extern Bool isvisible(Client *c); /* returns True if client is visible */
+extern void resizemaster(Arg *arg); /* resizes the master percent with arg's index value */
+extern void restack(void); /* restores z layers of all clients */
+extern void togglefloat(Arg *arg); /* toggles focusesd client between floating/non-floating state */
+extern void togglemode(Arg *arg); /* toggles global arrange function (dotile/dofloat) */
+extern void toggleview(Arg *arg); /* toggles the tag with arg's index (in)visible */
+extern void view(Arg *arg); /* views the tag with arg's index */
+extern void zoom(Arg *arg); /* zooms the focused client to master area, arg is ignored */
diff --git a/LICENSE b/LICENSE
@@ -0,0 +1,22 @@
+MIT/X Consortium License
+
+(C)opyright MMVI-MMVII Anselm R. Garbe <garbeam at gmail dot com>
+(C)opyright MMVI-MMVII Sander van Dijk <a dot h dot vandijk at gmail dot com>
+
+Permission is hereby granted, free of charge, to any person obtaining a
+copy of this software and associated documentation files (the "Software"),
+to deal in the Software without restriction, including without limitation
+the rights to use, copy, modify, merge, publish, distribute, sublicense,
+and/or sell copies of the Software, and to permit persons to whom the
+Software is furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in
+all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
+THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+DEALINGS IN THE SOFTWARE.
diff --git a/Makefile b/Makefile
@@ -0,0 +1,61 @@
+# 2wm - stereo window manager
+# (C)opyright MMVII Anselm R. Garbe
+
+include config.mk
+
+SRC = client.c draw.c event.c main.c tag.c util.c view.c
+OBJ = ${SRC:.c=.o}
+
+all: options 2wm
+
+options:
+ @echo 2wm build options:
+ @echo "CFLAGS = ${CFLAGS}"
+ @echo "LDFLAGS = ${LDFLAGS}"
+ @echo "CC = ${CC}"
+
+.c.o:
+ @echo CC $<
+ @${CC} -c ${CFLAGS} $<
+
+${OBJ}: 2wm.h config.h config.mk
+
+config.h:
+ @echo creating $@ from config.default.h
+ @cp config.default.h $@
+
+2wm: ${OBJ}
+ @echo CC -o $@
+ @${CC} -o $@ ${OBJ} ${LDFLAGS}
+ @strip $@
+
+clean:
+ @echo cleaning
+ @rm -f 2wm ${OBJ} 2wm-${VERSION}.tar.gz
+
+dist: clean
+ @echo creating dist tarball
+ @mkdir -p 2wm-${VERSION}
+ @cp -R LICENSE Makefile README config.*.h config.mk \
+ 2wm.1 2wm.h ${SRC} 2wm-${VERSION}
+ @tar -cf 2wm-${VERSION}.tar 2wm-${VERSION}
+ @gzip 2wm-${VERSION}.tar
+ @rm -rf 2wm-${VERSION}
+
+install: all
+ @echo installing executable file to ${DESTDIR}${PREFIX}/bin
+ @mkdir -p ${DESTDIR}${PREFIX}/bin
+ @cp -f 2wm ${DESTDIR}${PREFIX}/bin
+ @chmod 755 ${DESTDIR}${PREFIX}/bin/2wm
+ @echo installing manual page to ${DESTDIR}${MANPREFIX}/man1
+ @mkdir -p ${DESTDIR}${MANPREFIX}/man1
+ @sed 's/VERSION/${VERSION}/g' < 2wm.1 > ${DESTDIR}${MANPREFIX}/man1/2wm.1
+ @chmod 644 ${DESTDIR}${MANPREFIX}/man1/2wm.1
+
+uninstall:
+ @echo removing executable file from ${DESTDIR}${PREFIX}/bin
+ @rm -f ${DESTDIR}${PREFIX}/bin/2wm
+ @echo removing manual page from ${DESTDIR}${MANPREFIX}/man1
+ @rm -f ${DESTDIR}${MANPREFIX}/man1/2wm.1
+
+.PHONY: all options clean dist install uninstall
diff --git a/README b/README
@@ -0,0 +1,39 @@
+2wm - stereo window manager
+===========================
+2wm is an extremely fast, small, and dynamic window manager for X. It is the
+little brother of dwm.
+
+
+Requirements
+------------
+In order to build 2wm you need the Xlib header files.
+
+
+Installation
+------------
+Edit config.mk to match your local setup (2wm is installed into
+the /usr/local namespace by default).
+
+Afterwards enter the following command to build and install 2wm (if
+necessary as root):
+
+ make clean install
+
+
+Running 2wm
+-----------
+Add the following line to your .xinitrc to start 2wm using startx:
+
+ exec 2wm
+
+In order to connect 2wm to a specific display, make sure that
+the DISPLAY environment variable is set correctly, e.g.:
+
+ DISPLAY=foo.bar:1 exec 2wm
+
+(This will start 2wm on display :1 of the host foo.bar.)
+
+Configuration
+-------------
+The configuration of 2wm is done by creating a custom config.h
+and (re)compiling the source code.
diff --git a/client.c b/client.c
@@ -0,0 +1,356 @@
+/* (C)opyright MMVI-MMVII Anselm R. Garbe <garbeam at gmail dot com>
+ * See LICENSE file for license details.
+ */
+#include "2wm.h"
+#include <stdlib.h>
+#include <string.h>
+#include <X11/Xatom.h>
+#include <X11/Xutil.h>
+
+/* static */
+
+static void
+detachstack(Client *c) {
+ Client **tc;
+ for(tc=&stack; *tc && *tc != c; tc=&(*tc)->snext);
+ *tc = c->snext;
+}
+
+static void
+grabbuttons(Client *c, Bool focused) {
+ XUngrabButton(dpy, AnyButton, AnyModifier, c->win);
+
+ if(focused) {
+ XGrabButton(dpy, Button1, MODKEY, c->win, False, BUTTONMASK,
+ GrabModeAsync, GrabModeSync, None, None);
+ XGrabButton(dpy, Button1, MODKEY | LockMask, c->win, False, BUTTONMASK,
+ GrabModeAsync, GrabModeSync, None, None);
+ XGrabButton(dpy, Button1, MODKEY | numlockmask, c->win, False, BUTTONMASK,
+ GrabModeAsync, GrabModeSync, None, None);
+ XGrabButton(dpy, Button1, MODKEY | numlockmask | LockMask, c->win, False, BUTTONMASK,
+ GrabModeAsync, GrabModeSync, None, None);
+
+ XGrabButton(dpy, Button2, MODKEY, c->win, False, BUTTONMASK,
+ GrabModeAsync, GrabModeSync, None, None);
+ XGrabButton(dpy, Button2, MODKEY | LockMask, c->win, False, BUTTONMASK,
+ GrabModeAsync, GrabModeSync, None, None);
+ XGrabButton(dpy, Button2, MODKEY | numlockmask, c->win, False, BUTTONMASK,
+ GrabModeAsync, GrabModeSync, None, None);
+ XGrabButton(dpy, Button2, MODKEY | numlockmask | LockMask, c->win, False, BUTTONMASK,
+ GrabModeAsync, GrabModeSync, None, None);
+
+ XGrabButton(dpy, Button3, MODKEY, c->win, False, BUTTONMASK,
+ GrabModeAsync, GrabModeSync, None, None);
+ XGrabButton(dpy, Button3, MODKEY | LockMask, c->win, False, BUTTONMASK,
+ GrabModeAsync, GrabModeSync, None, None);
+ XGrabButton(dpy, Button3, MODKEY | numlockmask, c->win, False, BUTTONMASK,
+ GrabModeAsync, GrabModeSync, None, None);
+ XGrabButton(dpy, Button3, MODKEY | numlockmask | LockMask, c->win, False, BUTTONMASK,
+ GrabModeAsync, GrabModeSync, None, None);
+ }
+ else
+ XGrabButton(dpy, AnyButton, AnyModifier, c->win, False, BUTTONMASK,
+ GrabModeAsync, GrabModeSync, None, None);
+}
+
+static void
+setclientstate(Client *c, long state) {
+ long data[] = {state, None};
+ XChangeProperty(dpy, c->win, wmatom[WMState], wmatom[WMState], 32,
+ PropModeReplace, (unsigned char *)data, 2);
+}
+
+static int
+xerrordummy(Display *dsply, XErrorEvent *ee) {
+ return 0;
+}
+
+/* extern */
+
+void
+configure(Client *c) {
+ XEvent synev;
+
+ synev.type = ConfigureNotify;
+ synev.xconfigure.display = dpy;
+ synev.xconfigure.event = c->win;
+ synev.xconfigure.window = c->win;
+ synev.xconfigure.x = c->x;
+ synev.xconfigure.y = c->y;
+ synev.xconfigure.width = c->w;
+ synev.xconfigure.height = c->h;
+ synev.xconfigure.border_width = c->border;
+ synev.xconfigure.above = None;
+ XSendEvent(dpy, c->win, True, NoEventMask, &synev);
+}
+
+void
+focus(Client *c) {
+ if(c && !isvisible(c))
+ return;
+ if(sel && sel != c) {
+ grabbuttons(sel, False);
+ XSetWindowBorder(dpy, sel->win, dc.norm[ColBorder]);
+ }
+ if(c) {
+ detachstack(c);
+ c->snext = stack;
+ stack = c;
+ grabbuttons(c, True);
+ }
+ sel = c;
+ drawstatus();
+ if(!selscreen)
+ return;
+ if(c) {
+ XSetWindowBorder(dpy, c->win, dc.sel[ColBorder]);
+ XSetInputFocus(dpy, c->win, RevertToPointerRoot, CurrentTime);
+ }
+ else
+ XSetInputFocus(dpy, root, RevertToPointerRoot, CurrentTime);
+}
+
+Client *
+getclient(Window w) {
+ Client *c;
+
+ for(c = clients; c; c = c->next)
+ if(c->win == w)
+ return c;
+ return NULL;
+}
+
+Bool
+isprotodel(Client *c) {
+ int i, n;
+ Atom *protocols;
+ Bool ret = False;
+
+ if(XGetWMProtocols(dpy, c->win, &protocols, &n)) {
+ for(i = 0; !ret && i < n; i++)
+ if(protocols[i] == wmatom[WMDelete])
+ ret = True;
+ XFree(protocols);
+ }
+ return ret;
+}
+
+void
+killclient(Arg *arg) {
+ if(!sel)
+ return;
+ if(isprotodel(sel))
+ sendevent(sel->win, wmatom[WMProtocols], wmatom[WMDelete]);
+ else
+ XKillClient(dpy, sel->win);
+}
+
+void
+manage(Window w, XWindowAttributes *wa) {
+ Client *c;
+ Window trans;
+
+ c = emallocz(sizeof(Client));
+ c->tags = emallocz(ntags * sizeof(Bool));
+ c->win = w;
+ c->x = wa->x;
+ c->y = wa->y;
+ c->w = wa->width;
+ c->h = wa->height;
+ if(c->w == sw && c->h == sh) {
+ c->border = 0;
+ c->x = sx;
+ c->y = sy;
+ }
+ else {
+ c->border = BORDERPX;
+ if(c->x + c->w + 2 * c->border > wax + waw)
+ c->x = wax + waw - c->w - 2 * c->border;
+ if(c->y + c->h + 2 * c->border > way + wah)
+ c->y = way + wah - c->h - 2 * c->border;
+ if(c->x < wax)
+ c->x = wax;
+ if(c->y < way)
+ c->y = way;
+ }
+ updatesizehints(c);
+ XSelectInput(dpy, c->win,
+ StructureNotifyMask | PropertyChangeMask | EnterWindowMask);
+ XGetTransientForHint(dpy, c->win, &trans);
+ grabbuttons(c, False);
+ XSetWindowBorder(dpy, c->win, dc.norm[ColBorder]);
+ updatetitle(c);
+ settags(c, getclient(trans));
+ if(!c->isfloat)
+ c->isfloat = trans || c->isfixed;
+ if(clients)
+ clients->prev = c;
+ c->next = clients;
+ c->snext = stack;
+ stack = clients = c;
+ XMoveWindow(dpy, c->win, c->x + 2 * sw, c->y);
+ XMapWindow(dpy, c->win);
+ setclientstate(c, NormalState);
+ if(isvisible(c))
+ focus(c);
+ arrange();
+}
+
+void
+resize(Client *c, Bool sizehints) {
+ float actual, dx, dy, max, min;
+ XWindowChanges wc;
+
+ if(c->w <= 0 || c->h <= 0)
+ return;
+ if(sizehints) {
+ if(c->minw && c->w < c->minw)
+ c->w = c->minw;
+ if(c->minh && c->h < c->minh)
+ c->h = c->minh;
+ if(c->maxw && c->w > c->maxw)
+ c->w = c->maxw;
+ if(c->maxh && c->h > c->maxh)
+ c->h = c->maxh;
+ /* inspired by algorithm from fluxbox */
+ if(c->minay > 0 && c->maxay && (c->h - c->baseh) > 0) {
+ dx = (float)(c->w - c->basew);
+ dy = (float)(c->h - c->baseh);
+ min = (float)(c->minax) / (float)(c->minay);
+ max = (float)(c->maxax) / (float)(c->maxay);
+ actual = dx / dy;
+ if(max > 0 && min > 0 && actual > 0) {
+ if(actual < min) {
+ dy = (dx * min + dy) / (min * min + 1);
+ dx = dy * min;
+ c->w = (int)dx + c->basew;
+ c->h = (int)dy + c->baseh;
+ }
+ else if(actual > max) {
+ dy = (dx * min + dy) / (max * max + 1);
+ dx = dy * min;
+ c->w = (int)dx + c->basew;
+ c->h = (int)dy + c->baseh;
+ }
+ }
+ }
+ if(c->incw)
+ c->w -= (c->w - c->basew) % c->incw;
+ if(c->inch)
+ c->h -= (c->h - c->baseh) % c->inch;
+ }
+ if(c->w == sw && c->h == sh)
+ c->border = 0;
+ else
+ c->border = BORDERPX;
+ /* offscreen appearance fixes */
+ if(c->x > sw)
+ c->x = sw - c->w - 2 * c->border;
+ if(c->y > sh)
+ c->y = sh - c->h - 2 * c->border;
+ if(c->x + c->w + 2 * c->border < sx)
+ c->x = sx;
+ if(c->y + c->h + 2 * c->border < sy)
+ c->y = sy;
+ wc.x = c->x;
+ wc.y = c->y;
+ wc.width = c->w;
+ wc.height = c->h;
+ wc.border_width = c->border;
+ XConfigureWindow(dpy, c->win, CWX | CWY | CWWidth | CWHeight | CWBorderWidth, &wc);
+ configure(c);
+ XSync(dpy, False);
+}
+
+void
+updatesizehints(Client *c) {
+ long msize;
+ XSizeHints size;
+
+ if(!XGetWMNormalHints(dpy, c->win, &size, &msize) || !size.flags)
+ size.flags = PSize;
+ c->flags = size.flags;
+ if(c->flags & PBaseSize) {
+ c->basew = size.base_width;
+ c->baseh = size.base_height;
+ }
+ else
+ c->basew = c->baseh = 0;
+ if(c->flags & PResizeInc) {
+ c->incw = size.width_inc;
+ c->inch = size.height_inc;
+ }
+ else
+ c->incw = c->inch = 0;
+ if(c->flags & PMaxSize) {
+ c->maxw = size.max_width;
+ c->maxh = size.max_height;
+ }
+ else
+ c->maxw = c->maxh = 0;
+ if(c->flags & PMinSize) {
+ c->minw = size.min_width;
+ c->minh = size.min_height;
+ }
+ else
+ c->minw = c->minh = 0;
+ if(c->flags & PAspect) {
+ c->minax = size.min_aspect.x;
+ c->minay = size.min_aspect.y;
+ c->maxax = size.max_aspect.x;
+ c->maxay = size.max_aspect.y;
+ }
+ else
+ c->minax = c->minay = c->maxax = c->maxay = 0;
+ c->isfixed = (c->maxw && c->minw && c->maxh && c->minh &&
+ c->maxw == c->minw && c->maxh == c->minh);
+}
+
+void
+updatetitle(Client *c) {
+ char **list = NULL;
+ int n;
+ XTextProperty name;
+
+ name.nitems = 0;
+ c->name[0] = 0;
+ XGetTextProperty(dpy, c->win, &name, netatom[NetWMName]);
+ if(!name.nitems)
+ XGetWMName(dpy, c->win, &name);
+ if(!name.nitems)
+ return;
+ if(name.encoding == XA_STRING)
+ strncpy(c->name, (char *)name.value, sizeof c->name);
+ else {
+ if(XmbTextPropertyToTextList(dpy, &name, &list, &n) >= Success
+ && n > 0 && *list)
+ {
+ strncpy(c->name, *list, sizeof c->name);
+ XFreeStringList(list);
+ }
+ }
+ XFree(name.value);
+}
+
+void
+unmanage(Client *c) {
+ Client *nc;
+
+ /* The server grab construct avoids race conditions. */
+ XGrabServer(dpy);
+ XSetErrorHandler(xerrordummy);
+ detach(c);
+ detachstack(c);
+ if(sel == c) {
+ for(nc = stack; nc && !isvisible(nc); nc = nc->snext);
+ focus(nc);
+ }
+ XUngrabButton(dpy, AnyButton, AnyModifier, c->win);
+ setclientstate(c, WithdrawnState);
+ free(c->tags);
+ free(c);
+ XSync(dpy, False);
+ XSetErrorHandler(xerror);
+ XUngrabServer(dpy);
+ arrange();
+}
diff --git a/config.arg.h b/config.arg.h
@@ -0,0 +1,94 @@
+/* (C)opyright MMVI-MMVII Anselm R. Garbe <garbeam at gmail dot com>
+ * See LICENSE file for license details.
+ */
+
+#define TAGS \
+const char *tags[] = { "1", "2", "3", "4", "5", "6", "7", "8", "9", NULL };
+
+#define BORDERPX 1
+#define DEFMODE dotile /* dofloat */
+#define FLOATSYMBOL "><>"
+#define TILESYMBOL "[]="
+
+#define FONT "-*-terminus-medium-r-*-*-14-*-*-*-*-*-*-*"
+#define NORMBORDERCOLOR "#333"
+#define NORMBGCOLOR "#222"
+#define NORMFGCOLOR "#ccc"
+#define SELBORDERCOLOR "#69c"
+#define SELBGCOLOR "#555"
+#define SELFGCOLOR "#fff"
+
+#define MASTER 600 /* per thousand */
+#define MODKEY Mod1Mask
+#define NMASTER 1 /* clients in master area */
+#define SNAP 40 /* pixel */
+#define TOPBAR True /* False */
+
+#define KEYS \
+static Key key[] = { \
+ /* modifier key function argument */ \
+ { MODKEY|ShiftMask, XK_Return, spawn, \
+ { .cmd = "exec uxterm -bg '#222' -fg '#eee' -cr '#eee' +sb -fn '"FONT"'" } }, \
+ { MODKEY, XK_p, spawn, \
+ { .cmd = "exe=\"$(lsx `echo $PATH | sed 's/:/ /g'` | sort -u " \
+ " | dmenu -fn '"FONT"' -nb '"NORMBGCOLOR"' -nf '"NORMFGCOLOR"' " \
+ "-sb '"SELBGCOLOR"' -sf '"SELFGCOLOR"')\" && exec $exe" } }, \
+ { MODKEY, XK_j, focusnext, { 0 } }, \
+ { MODKEY, XK_k, focusprev, { 0 } }, \
+ { MODKEY, XK_Return, zoom, { 0 } }, \
+ { MODKEY, XK_g, resizemaster, { .i = 15 } }, \
+ { MODKEY, XK_s, resizemaster, { .i = -15 } }, \
+ { MODKEY, XK_i, incnmaster, { .i = 1 } }, \
+ { MODKEY, XK_d, incnmaster, { .i = -1 } }, \
+ { MODKEY|ShiftMask, XK_0, tag, { .i = -1 } }, \
+ { MODKEY|ShiftMask, XK_1, tag, { .i = 0 } }, \
+ { MODKEY|ShiftMask, XK_2, tag, { .i = 1 } }, \
+ { MODKEY|ShiftMask, XK_3, tag, { .i = 2 } }, \
+ { MODKEY|ShiftMask, XK_4, tag, { .i = 3 } }, \
+ { MODKEY|ShiftMask, XK_5, tag, { .i = 4 } }, \
+ { MODKEY|ShiftMask, XK_6, tag, { .i = 5 } }, \
+ { MODKEY|ShiftMask, XK_7, tag, { .i = 6 } }, \
+ { MODKEY|ShiftMask, XK_8, tag, { .i = 7 } }, \
+ { MODKEY|ShiftMask, XK_9, tag, { .i = 8 } }, \
+ { MODKEY|ControlMask|ShiftMask, XK_1, toggletag, { .i = 0 } }, \
+ { MODKEY|ControlMask|ShiftMask, XK_2, toggletag, { .i = 1 } }, \
+ { MODKEY|ControlMask|ShiftMask, XK_3, toggletag, { .i = 2 } }, \
+ { MODKEY|ControlMask|ShiftMask, XK_4, toggletag, { .i = 3 } }, \
+ { MODKEY|ControlMask|ShiftMask, XK_5, toggletag, { .i = 4 } }, \
+ { MODKEY|ControlMask|ShiftMask, XK_6, toggletag, { .i = 5 } }, \
+ { MODKEY|ControlMask|ShiftMask, XK_7, toggletag, { .i = 6 } }, \
+ { MODKEY|ControlMask|ShiftMask, XK_8, toggletag, { .i = 7 } }, \
+ { MODKEY|ControlMask|ShiftMask, XK_9, toggletag, { .i = 8 } }, \
+ { MODKEY|ShiftMask, XK_c, killclient, { 0 } }, \
+ { MODKEY, XK_space, togglemode, { 0 } }, \
+ { MODKEY|ShiftMask, XK_space, togglefloat, { 0 } }, \
+ { MODKEY, XK_0, view, { .i = -1 } }, \
+ { MODKEY, XK_1, view, { .i = 0 } }, \
+ { MODKEY, XK_2, view, { .i = 1 } }, \
+ { MODKEY, XK_3, view, { .i = 2 } }, \
+ { MODKEY, XK_4, view, { .i = 3 } }, \
+ { MODKEY, XK_5, view, { .i = 4 } }, \
+ { MODKEY, XK_6, view, { .i = 5 } }, \
+ { MODKEY, XK_7, view, { .i = 6 } }, \
+ { MODKEY, XK_8, view, { .i = 7 } }, \
+ { MODKEY, XK_9, view, { .i = 8 } }, \
+ { MODKEY|ControlMask, XK_1, toggleview, { .i = 0 } }, \
+ { MODKEY|ControlMask, XK_2, toggleview, { .i = 1 } }, \
+ { MODKEY|ControlMask, XK_3, toggleview, { .i = 2 } }, \
+ { MODKEY|ControlMask, XK_4, toggleview, { .i = 3 } }, \
+ { MODKEY|ControlMask, XK_5, toggleview, { .i = 4 } }, \
+ { MODKEY|ControlMask, XK_6, toggleview, { .i = 5 } }, \
+ { MODKEY|ControlMask, XK_7, toggleview, { .i = 6 } }, \
+ { MODKEY|ControlMask, XK_8, toggleview, { .i = 7 } }, \
+ { MODKEY|ControlMask, XK_9, toggleview, { .i = 8 } }, \
+ { MODKEY|ShiftMask, XK_q, quit, { 0 } }, \
+};
+
+#define RULES \
+static Rule rule[] = { \
+ /* class:instance:title regex tags regex isfloat */ \
+ { "Firefox.*", "3", False }, \
+ { "Gimp.*", NULL, True }, \
+ { "MPlayer.*", NULL, True }, \
+ { "Acroread.*", NULL, True }, \
+};
diff --git a/config.default.h b/config.default.h
@@ -0,0 +1,90 @@
+/* (C)opyright MMVI-MMVII Anselm R. Garbe <garbeam at gmail dot com>
+ * See LICENSE file for license details.
+ */
+
+#define TAGS \
+const char *tags[] = { "1", "2", "3", "4", "5", "6", "7", "8", "9", NULL };
+
+#define BORDERPX 1
+#define DEFMODE dotile /* dofloat */
+#define FLOATSYMBOL "><>"
+#define TILESYMBOL "[]="
+
+#define FONT "-*-fixed-medium-r-normal-*-13-*-*-*-*-*-*-*"
+#define NORMBORDERCOLOR "#dddddd"
+#define NORMBGCOLOR "#eeeeee"
+#define NORMFGCOLOR "#222222"
+#define SELBORDERCOLOR "#ff0000"
+#define SELBGCOLOR "#006699"
+#define SELFGCOLOR "#ffffff"
+
+#define MASTER 600 /* per thousand */
+#define MODKEY Mod1Mask
+#define NMASTER 1 /* clients in master area */
+#define SNAP 20 /* pixel */
+#define TOPBAR True /* False */
+
+#define KEYS \
+static Key key[] = { \
+ /* modifier key function argument */ \
+ { MODKEY|ShiftMask, XK_Return, spawn, { .cmd = "exec xterm" } }, \
+ { MODKEY, XK_Tab, focusnext, { 0 } }, \
+ { MODKEY|ShiftMask, XK_Tab, focusprev, { 0 } }, \
+ { MODKEY, XK_Return, zoom, { 0 } }, \
+ { MODKEY, XK_g, resizemaster, { .i = 15 } }, \
+ { MODKEY, XK_s, resizemaster, { .i = -15 } }, \
+ { MODKEY, XK_i, incnmaster, { .i = 1 } }, \
+ { MODKEY, XK_d, incnmaster, { .i = -1 } }, \
+ { MODKEY|ShiftMask, XK_0, tag, { .i = -1 } }, \
+ { MODKEY|ShiftMask, XK_1, tag, { .i = 0 } }, \
+ { MODKEY|ShiftMask, XK_2, tag, { .i = 1 } }, \
+ { MODKEY|ShiftMask, XK_3, tag, { .i = 2 } }, \
+ { MODKEY|ShiftMask, XK_4, tag, { .i = 3 } }, \
+ { MODKEY|ShiftMask, XK_5, tag, { .i = 4 } }, \
+ { MODKEY|ShiftMask, XK_6, tag, { .i = 5 } }, \
+ { MODKEY|ShiftMask, XK_7, tag, { .i = 6 } }, \
+ { MODKEY|ShiftMask, XK_8, tag, { .i = 7 } }, \
+ { MODKEY|ShiftMask, XK_9, tag, { .i = 8 } }, \
+ { MODKEY|ControlMask|ShiftMask, XK_1, toggletag, { .i = 0 } }, \
+ { MODKEY|ControlMask|ShiftMask, XK_2, toggletag, { .i = 1 } }, \
+ { MODKEY|ControlMask|ShiftMask, XK_3, toggletag, { .i = 2 } }, \
+ { MODKEY|ControlMask|ShiftMask, XK_4, toggletag, { .i = 3 } }, \
+ { MODKEY|ControlMask|ShiftMask, XK_5, toggletag, { .i = 4 } }, \
+ { MODKEY|ControlMask|ShiftMask, XK_6, toggletag, { .i = 5 } }, \
+ { MODKEY|ControlMask|ShiftMask, XK_7, toggletag, { .i = 6 } }, \
+ { MODKEY|ControlMask|ShiftMask, XK_8, toggletag, { .i = 7 } }, \
+ { MODKEY|ControlMask|ShiftMask, XK_9, toggletag, { .i = 8 } }, \
+ { MODKEY|ShiftMask, XK_c, killclient, { 0 } }, \
+ { MODKEY, XK_space, togglemode, { 0 } }, \
+ { MODKEY|ShiftMask, XK_space, togglefloat, { 0 } }, \
+ { MODKEY, XK_0, view, { .i = -1 } }, \
+ { MODKEY, XK_1, view, { .i = 0 } }, \
+ { MODKEY, XK_2, view, { .i = 1 } }, \
+ { MODKEY, XK_3, view, { .i = 2 } }, \
+ { MODKEY, XK_4, view, { .i = 3 } }, \
+ { MODKEY, XK_5, view, { .i = 4 } }, \
+ { MODKEY, XK_6, view, { .i = 5 } }, \
+ { MODKEY, XK_7, view, { .i = 6 } }, \
+ { MODKEY, XK_8, view, { .i = 7 } }, \
+ { MODKEY, XK_9, view, { .i = 8 } }, \
+ { MODKEY|ControlMask, XK_1, toggleview, { .i = 0 } }, \
+ { MODKEY|ControlMask, XK_2, toggleview, { .i = 1 } }, \
+ { MODKEY|ControlMask, XK_3, toggleview, { .i = 2 } }, \
+ { MODKEY|ControlMask, XK_4, toggleview, { .i = 3 } }, \
+ { MODKEY|ControlMask, XK_5, toggleview, { .i = 4 } }, \
+ { MODKEY|ControlMask, XK_6, toggleview, { .i = 5 } }, \
+ { MODKEY|ControlMask, XK_7, toggleview, { .i = 6 } }, \
+ { MODKEY|ControlMask, XK_8, toggleview, { .i = 7 } }, \
+ { MODKEY|ControlMask, XK_9, toggleview, { .i = 8 } }, \
+ { MODKEY|ShiftMask, XK_q, quit, { 0 } }, \
+};
+
+/* Query class:instance:title for regex matching info with following command:
+ * xprop | awk -F '"' '/^WM_CLASS/ { printf("%s:%s:",$4,$2) }; /^WM_NAME/ { printf("%s\n",$2) }' */
+#define RULES \
+static Rule rule[] = { \
+ /* class:instance:title regex tags regex isfloat */ \
+ { "Gimp.*", NULL, True }, \
+ { "MPlayer.*", NULL, True }, \
+ { "Acroread.*", NULL, True }, \
+};
diff --git a/config.h b/config.h
@@ -0,0 +1,94 @@
+/* (C)opyright MMVI-MMVII Anselm R. Garbe <garbeam at gmail dot com>
+ * See LICENSE file for license details.
+ */
+
+#define TAGS \
+const char *tags[] = { "1", "2", "3", "4", "5", "6", "7", "8", "9", NULL };
+
+#define BORDERPX 1
+#define DEFMODE dotile /* dofloat */
+#define FLOATSYMBOL "><>"
+#define TILESYMBOL "[]="
+
+#define FONT "-*-terminus-medium-r-*-*-14-*-*-*-*-*-*-*"
+#define NORMBORDERCOLOR "#333"
+#define NORMBGCOLOR "#222"
+#define NORMFGCOLOR "#ccc"
+#define SELBORDERCOLOR "#69c"
+#define SELBGCOLOR "#555"
+#define SELFGCOLOR "#fff"
+
+#define MASTER 600 /* per thousand */
+#define MODKEY Mod1Mask
+#define NMASTER 1 /* clients in master area */
+#define SNAP 40 /* pixel */
+#define TOPBAR True /* False */
+
+#define KEYS \
+static Key key[] = { \
+ /* modifier key function argument */ \
+ { MODKEY|ShiftMask, XK_Return, spawn, \
+ { .cmd = "exec uxterm -bg '#222' -fg '#eee' -cr '#eee' +sb -fn '"FONT"'" } }, \
+ { MODKEY, XK_p, spawn, \
+ { .cmd = "exe=\"$(lsx `echo $PATH | sed 's/:/ /g'` | sort -u " \
+ " | dmenu -fn '"FONT"' -nb '"NORMBGCOLOR"' -nf '"NORMFGCOLOR"' " \
+ "-sb '"SELBGCOLOR"' -sf '"SELFGCOLOR"')\" && exec $exe" } }, \
+ { MODKEY, XK_j, focusnext, { 0 } }, \
+ { MODKEY, XK_k, focusprev, { 0 } }, \
+ { MODKEY, XK_Return, zoom, { 0 } }, \
+ { MODKEY, XK_g, resizemaster, { .i = 15 } }, \
+ { MODKEY, XK_s, resizemaster, { .i = -15 } }, \
+ { MODKEY, XK_i, incnmaster, { .i = 1 } }, \
+ { MODKEY, XK_d, incnmaster, { .i = -1 } }, \
+ { MODKEY|ShiftMask, XK_0, tag, { .i = -1 } }, \
+ { MODKEY|ShiftMask, XK_1, tag, { .i = 0 } }, \
+ { MODKEY|ShiftMask, XK_2, tag, { .i = 1 } }, \
+ { MODKEY|ShiftMask, XK_3, tag, { .i = 2 } }, \
+ { MODKEY|ShiftMask, XK_4, tag, { .i = 3 } }, \
+ { MODKEY|ShiftMask, XK_5, tag, { .i = 4 } }, \
+ { MODKEY|ShiftMask, XK_6, tag, { .i = 5 } }, \
+ { MODKEY|ShiftMask, XK_7, tag, { .i = 6 } }, \
+ { MODKEY|ShiftMask, XK_8, tag, { .i = 7 } }, \
+ { MODKEY|ShiftMask, XK_9, tag, { .i = 8 } }, \
+ { MODKEY|ControlMask|ShiftMask, XK_1, toggletag, { .i = 0 } }, \
+ { MODKEY|ControlMask|ShiftMask, XK_2, toggletag, { .i = 1 } }, \
+ { MODKEY|ControlMask|ShiftMask, XK_3, toggletag, { .i = 2 } }, \
+ { MODKEY|ControlMask|ShiftMask, XK_4, toggletag, { .i = 3 } }, \
+ { MODKEY|ControlMask|ShiftMask, XK_5, toggletag, { .i = 4 } }, \
+ { MODKEY|ControlMask|ShiftMask, XK_6, toggletag, { .i = 5 } }, \
+ { MODKEY|ControlMask|ShiftMask, XK_7, toggletag, { .i = 6 } }, \
+ { MODKEY|ControlMask|ShiftMask, XK_8, toggletag, { .i = 7 } }, \
+ { MODKEY|ControlMask|ShiftMask, XK_9, toggletag, { .i = 8 } }, \
+ { MODKEY|ShiftMask, XK_c, killclient, { 0 } }, \
+ { MODKEY, XK_space, togglemode, { 0 } }, \
+ { MODKEY|ShiftMask, XK_space, togglefloat, { 0 } }, \
+ { MODKEY, XK_0, view, { .i = -1 } }, \
+ { MODKEY, XK_1, view, { .i = 0 } }, \
+ { MODKEY, XK_2, view, { .i = 1 } }, \
+ { MODKEY, XK_3, view, { .i = 2 } }, \
+ { MODKEY, XK_4, view, { .i = 3 } }, \
+ { MODKEY, XK_5, view, { .i = 4 } }, \
+ { MODKEY, XK_6, view, { .i = 5 } }, \
+ { MODKEY, XK_7, view, { .i = 6 } }, \
+ { MODKEY, XK_8, view, { .i = 7 } }, \
+ { MODKEY, XK_9, view, { .i = 8 } }, \
+ { MODKEY|ControlMask, XK_1, toggleview, { .i = 0 } }, \
+ { MODKEY|ControlMask, XK_2, toggleview, { .i = 1 } }, \
+ { MODKEY|ControlMask, XK_3, toggleview, { .i = 2 } }, \
+ { MODKEY|ControlMask, XK_4, toggleview, { .i = 3 } }, \
+ { MODKEY|ControlMask, XK_5, toggleview, { .i = 4 } }, \
+ { MODKEY|ControlMask, XK_6, toggleview, { .i = 5 } }, \
+ { MODKEY|ControlMask, XK_7, toggleview, { .i = 6 } }, \
+ { MODKEY|ControlMask, XK_8, toggleview, { .i = 7 } }, \
+ { MODKEY|ControlMask, XK_9, toggleview, { .i = 8 } }, \
+ { MODKEY|ShiftMask, XK_q, quit, { 0 } }, \
+};
+
+#define RULES \
+static Rule rule[] = { \
+ /* class:instance:title regex tags regex isfloat */ \
+ { "Firefox.*", "3", False }, \
+ { "Gimp.*", NULL, True }, \
+ { "MPlayer.*", NULL, True }, \
+ { "Acroread.*", NULL, True }, \
+};
diff --git a/config.mk b/config.mk
@@ -0,0 +1,29 @@
+# 2wm version
+VERSION = 0.0
+
+# Customize below to fit your system
+
+# paths
+PREFIX = /usr/local
+MANPREFIX = ${PREFIX}/share/man
+
+X11INC = /usr/X11R6/include
+X11LIB = /usr/X11R6/lib
+
+# includes and libs
+INCS = -I. -I/usr/include -I${X11INC}
+LIBS = -L/usr/lib -lc -L${X11LIB} -lX11
+
+# flags
+CFLAGS = -Os ${INCS} -DVERSION=\"${VERSION}\"
+LDFLAGS = ${LIBS}
+#CFLAGS = -g -Wall -O2 ${INCS} -DVERSION=\"${VERSION}\"
+#LDFLAGS = -g ${LIBS}
+
+# Solaris
+#CFLAGS = -fast ${INCS} -DVERSION=\"${VERSION}\"
+#LDFLAGS = ${LIBS}
+#CFLAGS += -xtarget=ultra
+
+# compiler and linker
+CC = cc
diff --git a/draw.c b/draw.c
@@ -0,0 +1,175 @@
+/* (C)opyright MMIV-MMVII Anselm R. Garbe <garbeam at gmail dot com>
+ * See LICENSE file for license details.
+ */
+#include "2wm.h"
+#include <stdio.h>
+#include <string.h>
+
+/* static */
+
+static Bool
+isoccupied(unsigned int t)
+{
+ Client *c;
+ for(c = clients; c; c = c->next)
+ if(c->tags[t])
+ return True;
+ return False;
+}
+
+static unsigned int
+textnw(const char *text, unsigned int len) {
+ XRectangle r;
+
+ if(dc.font.set) {
+ XmbTextExtents(dc.font.set, text, len, NULL, &r);
+ return r.width;
+ }
+ return XTextWidth(dc.font.xfont, text, len);
+}
+
+static void
+drawtext(const char *text, unsigned long col[ColLast], Bool filledsquare, Bool emptysquare) {
+ int x, y, w, h;
+ static char buf[256];
+ unsigned int len, olen;
+ XGCValues gcv;
+ XRectangle r = { dc.x, dc.y, dc.w, dc.h };
+
+ XSetForeground(dpy, dc.gc, col[ColBG]);
+ XFillRectangles(dpy, dc.drawable, dc.gc, &r, 1);
+ if(!text)
+ return;
+ w = 0;
+ olen = len = strlen(text);
+ if(len >= sizeof buf)
+ len = sizeof buf - 1;
+ memcpy(buf, text, len);
+ buf[len] = 0;
+ h = dc.font.ascent + dc.font.descent;
+ y = dc.y + (dc.h / 2) - (h / 2) + dc.font.ascent;
+ x = dc.x + (h / 2);
+ /* shorten text if necessary */
+ while(len && (w = textnw(buf, len)) > dc.w - h)
+ buf[--len] = 0;
+ if(len < olen) {
+ if(len > 1)
+ buf[len - 1] = '.';
+ if(len > 2)
+ buf[len - 2] = '.';
+ if(len > 3)
+ buf[len - 3] = '.';
+ }
+ if(w > dc.w)
+ return; /* too long */
+ gcv.foreground = col[ColFG];
+ if(dc.font.set) {
+ XChangeGC(dpy, dc.gc, GCForeground, &gcv);
+ XmbDrawString(dpy, dc.drawable, dc.font.set, dc.gc, x, y, buf, len);
+ }
+ else {
+ gcv.font = dc.font.xfont->fid;
+ XChangeGC(dpy, dc.gc, GCForeground | GCFont, &gcv);
+ XDrawString(dpy, dc.drawable, dc.gc, x, y, buf, len);
+ }
+ x = (h + 2) / 4;
+ r.x = dc.x + 1;
+ r.y = dc.y + 1;
+ if(filledsquare) {
+ r.width = r.height = x + 1;
+ XFillRectangles(dpy, dc.drawable, dc.gc, &r, 1);
+ }
+ else if(emptysquare) {
+ r.width = r.height = x;
+ XDrawRectangles(dpy, dc.drawable, dc.gc, &r, 1);
+ }
+}
+
+/* extern */
+
+void
+drawstatus(void) {
+ int i, x;
+
+ dc.x = dc.y = 0;
+ for(i = 0; i < ntags; i++) {
+ dc.w = textw(tags[i]);
+ if(seltag[i])
+ drawtext(tags[i], dc.sel, sel && sel->tags[i], isoccupied(i));
+ else
+ drawtext(tags[i], dc.norm, sel && sel->tags[i], isoccupied(i));
+ dc.x += dc.w;
+ }
+ dc.w = bmw;
+ drawtext(arrange == dofloat ? FLOATSYMBOL : TILESYMBOL, dc.norm, False, False);
+ x = dc.x + dc.w;
+ dc.w = textw(stext);
+ dc.x = sw - dc.w;
+ if(dc.x < x) {
+ dc.x = x;
+ dc.w = sw - x;
+ }
+ drawtext(stext, dc.norm, False, False);
+ if((dc.w = dc.x - x) > bh) {
+ dc.x = x;
+ drawtext(sel ? sel->name : NULL, sel ? dc.sel : dc.norm, False, False);
+ }
+ XCopyArea(dpy, dc.drawable, barwin, dc.gc, 0, 0, sw, bh, 0, 0);
+ XSync(dpy, False);
+}
+
+unsigned long
+getcolor(const char *colstr) {
+ Colormap cmap = DefaultColormap(dpy, screen);
+ XColor color;
+
+ if(!XAllocNamedColor(dpy, cmap, colstr, &color, &color))
+ eprint("error, cannot allocate color '%s'\n", colstr);
+ return color.pixel;
+}
+
+void
+setfont(const char *fontstr) {
+ char *def, **missing;
+ int i, n;
+
+ missing = NULL;
+ if(dc.font.set)
+ XFreeFontSet(dpy, dc.font.set);
+ dc.font.set = XCreateFontSet(dpy, fontstr, &missing, &n, &def);
+ if(missing) {
+ while(n--)
+ fprintf(stderr, "missing fontset: %s\n", missing[n]);
+ XFreeStringList(missing);
+ }
+ if(dc.font.set) {
+ XFontSetExtents *font_extents;
+ XFontStruct **xfonts;
+ char **font_names;
+ dc.font.ascent = dc.font.descent = 0;
+ font_extents = XExtentsOfFontSet(dc.font.set);
+ n = XFontsOfFontSet(dc.font.set, &xfonts, &font_names);
+ for(i = 0, dc.font.ascent = 0, dc.font.descent = 0; i < n; i++) {
+ if(dc.font.ascent < (*xfonts)->ascent)
+ dc.font.ascent = (*xfonts)->ascent;
+ if(dc.font.descent < (*xfonts)->descent)
+ dc.font.descent = (*xfonts)->descent;
+ xfonts++;
+ }
+ }
+ else {
+ if(dc.font.xfont)
+ XFreeFont(dpy, dc.font.xfont);
+ dc.font.xfont = NULL;
+ if(!(dc.font.xfont = XLoadQueryFont(dpy, fontstr)))
+ eprint("error, cannot load font: '%s'\n", fontstr);
+ dc.font.ascent = dc.font.xfont->ascent;
+ dc.font.descent = dc.font.xfont->descent;
+ }
+ dc.font.height = dc.font.ascent + dc.font.descent;
+}
+
+unsigned int
+textw(const char *text) {
+ return textnw(text, strlen(text)) + dc.font.height;
+}
diff --git a/event.c b/event.c
@@ -0,0 +1,387 @@
+/* (C)opyright MMVI-MMVII Anselm R. Garbe <garbeam at gmail dot com>
+ * See LICENSE file for license details.
+ */
+#include "2wm.h"
+#include <stdlib.h>
+#include <X11/keysym.h>
+#include <X11/Xatom.h>
+
+/* static */
+
+typedef struct {
+ unsigned long mod;
+ KeySym keysym;
+ void (*func)(Arg *arg);
+ Arg arg;
+} Key;
+
+KEYS
+
+#define CLEANMASK(mask) (mask & ~(numlockmask | LockMask))
+#define MOUSEMASK (BUTTONMASK | PointerMotionMask)
+
+static void
+movemouse(Client *c) {
+ int x1, y1, ocx, ocy, di;
+ unsigned int dui;
+ Window dummy;
+ XEvent ev;
+
+ ocx = c->x;
+ ocy = c->y;
+ if(XGrabPointer(dpy, root, False, MOUSEMASK, GrabModeAsync, GrabModeAsync,
+ None, cursor[CurMove], CurrentTime) != GrabSuccess)
+ return;
+ c->ismax = False;
+ XQueryPointer(dpy, root, &dummy, &dummy, &x1, &y1, &di, &di, &dui);
+ for(;;) {
+ XMaskEvent(dpy, MOUSEMASK | ExposureMask | SubstructureRedirectMask, &ev);
+ switch (ev.type) {
+ case ButtonRelease:
+ resize(c, True);
+ XUngrabPointer(dpy, CurrentTime);
+ return;
+ case ConfigureRequest:
+ case Expose:
+ case MapRequest:
+ handler[ev.type](&ev);
+ break;
+ case MotionNotify:
+ XSync(dpy, False);
+ c->x = ocx + (ev.xmotion.x - x1);
+ c->y = ocy + (ev.xmotion.y - y1);
+ if(abs(wax + c->x) < SNAP)
+ c->x = wax;
+ else if(abs((wax + waw) - (c->x + c->w + 2 * c->border)) < SNAP)
+ c->x = wax + waw - c->w - 2 * c->border;
+ if(abs(way - c->y) < SNAP)
+ c->y = way;
+ else if(abs((way + wah) - (c->y + c->h + 2 * c->border)) < SNAP)
+ c->y = way + wah - c->h - 2 * c->border;
+ resize(c, False);
+ break;
+ }
+ }
+}
+
+static void
+resizemouse(Client *c) {
+ int ocx, ocy;
+ int nw, nh;
+ XEvent ev;
+
+ ocx = c->x;
+ ocy = c->y;
+ if(XGrabPointer(dpy, root, False, MOUSEMASK, GrabModeAsync, GrabModeAsync,
+ None, cursor[CurResize], CurrentTime) != GrabSuccess)
+ return;
+ c->ismax = False;
+ XWarpPointer(dpy, None, c->win, 0, 0, 0, 0, c->w + c->border - 1, c->h + c->border - 1);
+ for(;;) {
+ XMaskEvent(dpy, MOUSEMASK | ExposureMask | SubstructureRedirectMask , &ev);
+ switch(ev.type) {
+ case ButtonRelease:
+ resize(c, True);
+ XWarpPointer(dpy, None, c->win, 0, 0, 0, 0,
+ c->w + c->border - 1, c->h + c->border - 1);
+ XUngrabPointer(dpy, CurrentTime);
+ while(XCheckMaskEvent(dpy, EnterWindowMask, &ev));
+ return;
+ case ConfigureRequest:
+ case Expose:
+ case MapRequest:
+ handler[ev.type](&ev);
+ break;
+ case MotionNotify:
+ XSync(dpy, False);
+ nw = ev.xmotion.x - ocx - 2 * c->border + 1;
+ c->w = nw > 0 ? nw : 1;
+ nh = ev.xmotion.y - ocy - 2 * c->border + 1;
+ c->h = nh > 0 ? nh : 1;
+ resize(c, True);
+ break;
+ }
+ }
+}
+
+static void
+buttonpress(XEvent *e) {
+ int x;
+ Arg a;
+ Client *c;
+ XButtonPressedEvent *ev = &e->xbutton;
+
+ if(barwin == ev->window) {
+ x = 0;
+ for(a.i = 0; a.i < ntags; a.i++) {
+ x += textw(tags[a.i]);
+ if(ev->x < x) {
+ if(ev->button == Button1) {
+ if(ev->state & MODKEY)
+ tag(&a);
+ else
+ view(&a);
+ }
+ else if(ev->button == Button3) {
+ if(ev->state & MODKEY)
+ toggletag(&a);
+ else
+ toggleview(&a);
+ }
+ return;
+ }
+ }
+ if(ev->x < x + bmw)
+ switch(ev->button) {
+ case Button1:
+ togglemode(NULL);
+ break;
+ case Button4:
+ a.i = 1;
+ incnmaster(&a);
+ break;
+ case Button5:
+ a.i = -1;
+ incnmaster(&a);
+ break;
+ }
+ }
+ else if((c = getclient(ev->window))) {
+ focus(c);
+ if(CLEANMASK(ev->state) != MODKEY)
+ return;
+ if(ev->button == Button1 && (arrange == dofloat || c->isfloat)) {
+ restack();
+ movemouse(c);
+ }
+ else if(ev->button == Button2)
+ zoom(NULL);
+ else if(ev->button == Button3 && (arrange == dofloat || c->isfloat) &&
+ !c->isfixed) {
+ restack();
+ resizemouse(c);
+ }
+ }
+}
+
+static void
+configurerequest(XEvent *e) {
+ unsigned long newmask;
+ Client *c;
+ XConfigureRequestEvent *ev = &e->xconfigurerequest;
+ XWindowChanges wc;
+
+ if((c = getclient(ev->window))) {
+ c->ismax = False;
+ if(ev->value_mask & CWX)
+ c->x = ev->x;
+ if(ev->value_mask & CWY)
+ c->y = ev->y;
+ if(ev->value_mask & CWWidth)
+ c->w = ev->width;
+ if(ev->value_mask & CWHeight)
+ c->h = ev->height;
+ if(ev->value_mask & CWBorderWidth)
+ c->border = ev->border_width;
+ wc.x = c->x;
+ wc.y = c->y;
+ wc.width = c->w;
+ wc.height = c->h;
+ newmask = ev->value_mask & (~(CWSibling | CWStackMode | CWBorderWidth));
+ if(newmask)
+ XConfigureWindow(dpy, c->win, newmask, &wc);
+ else
+ configure(c);
+ XSync(dpy, False);
+ if(c->isfloat) {
+ resize(c, False);
+ if(!isvisible(c))
+ XMoveWindow(dpy, c->win, c->x + 2 * sw, c->y);
+ }
+ else
+ arrange();
+ }
+ 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(dpy, ev->window, ev->value_mask, &wc);
+ XSync(dpy, False);
+ }
+}
+
+static void
+destroynotify(XEvent *e) {
+ Client *c;
+ XDestroyWindowEvent *ev = &e->xdestroywindow;
+
+ if((c = getclient(ev->window)))
+ unmanage(c);
+}
+
+static void
+enternotify(XEvent *e) {
+ Client *c;
+ XCrossingEvent *ev = &e->xcrossing;
+
+ if(ev->mode != NotifyNormal || ev->detail == NotifyInferior)
+ return;
+ if((c = getclient(ev->window)) && isvisible(c))
+ focus(c);
+ else if(ev->window == root) {
+ selscreen = True;
+ for(c = stack; c && !isvisible(c); c = c->snext);
+ focus(c);
+ }
+}
+
+static void
+expose(XEvent *e) {
+ XExposeEvent *ev = &e->xexpose;
+
+ if(ev->count == 0) {
+ if(barwin == ev->window)
+ drawstatus();
+ }
+}
+
+static void
+keypress(XEvent *e) {
+ static unsigned int len = sizeof key / sizeof key[0];
+ unsigned int i;
+ KeySym keysym;
+ XKeyEvent *ev = &e->xkey;
+
+ keysym = XKeycodeToKeysym(dpy, (KeyCode)ev->keycode, 0);
+ for(i = 0; i < len; i++) {
+ if(keysym == key[i].keysym
+ && CLEANMASK(key[i].mod) == CLEANMASK(ev->state))
+ {
+ if(key[i].func)
+ key[i].func(&key[i].arg);
+ }
+ }
+}
+
+static void
+leavenotify(XEvent *e) {
+ XCrossingEvent *ev = &e->xcrossing;
+
+ if((ev->window == root) && !ev->same_screen) {
+ selscreen = False;
+ focus(NULL);
+ }
+}
+
+static void
+mappingnotify(XEvent *e) {
+ XMappingEvent *ev = &e->xmapping;
+
+ XRefreshKeyboardMapping(ev);
+ if(ev->request == MappingKeyboard)
+ grabkeys();
+}
+
+static void
+maprequest(XEvent *e) {
+ static XWindowAttributes wa;
+ XMapRequestEvent *ev = &e->xmaprequest;
+
+ if(!XGetWindowAttributes(dpy, ev->window, &wa))
+ return;
+ if(wa.override_redirect) {
+ XSelectInput(dpy, ev->window,
+ (StructureNotifyMask | PropertyChangeMask));
+ return;
+ }
+ if(!getclient(ev->window))
+ manage(ev->window, &wa);
+}
+
+static void
+propertynotify(XEvent *e) {
+ Client *c;
+ Window trans;
+ XPropertyEvent *ev = &e->xproperty;
+
+ if(ev->state == PropertyDelete)
+ return; /* ignore */
+ if((c = getclient(ev->window))) {
+ switch (ev->atom) {
+ default: break;
+ case XA_WM_TRANSIENT_FOR:
+ XGetTransientForHint(dpy, c->win, &trans);
+ if(!c->isfloat && (c->isfloat = (trans != 0)))
+ arrange();
+ break;
+ case XA_WM_NORMAL_HINTS:
+ updatesizehints(c);
+ break;
+ }
+ if(ev->atom == XA_WM_NAME || ev->atom == netatom[NetWMName]) {
+ updatetitle(c);
+ if(c == sel)
+ drawstatus();
+ }
+ }
+}
+
+static void
+unmapnotify(XEvent *e) {
+ Client *c;
+ XUnmapEvent *ev = &e->xunmap;
+
+ if((c = getclient(ev->window)))
+ unmanage(c);
+}
+
+/* extern */
+
+void (*handler[LASTEvent]) (XEvent *) = {
+ [ButtonPress] = buttonpress,
+ [ConfigureRequest] = configurerequest,
+ [DestroyNotify] = destroynotify,
+ [EnterNotify] = enternotify,
+ [LeaveNotify] = leavenotify,
+ [Expose] = expose,
+ [KeyPress] = keypress,
+ [MappingNotify] = mappingnotify,
+ [MapRequest] = maprequest,
+ [PropertyNotify] = propertynotify,
+ [UnmapNotify] = unmapnotify
+};
+
+void
+grabkeys(void) {
+ static unsigned int len = sizeof key / sizeof key[0];
+ unsigned int i;
+ KeyCode code;
+
+ XUngrabKey(dpy, AnyKey, AnyModifier, root);
+ for(i = 0; i < len; i++) {
+ code = XKeysymToKeycode(dpy, key[i].keysym);
+ XGrabKey(dpy, code, key[i].mod, root, True,
+ GrabModeAsync, GrabModeAsync);
+ XGrabKey(dpy, code, key[i].mod | LockMask, root, True,
+ GrabModeAsync, GrabModeAsync);
+ XGrabKey(dpy, code, key[i].mod | numlockmask, root, True,
+ GrabModeAsync, GrabModeAsync);
+ XGrabKey(dpy, code, key[i].mod | numlockmask | LockMask, root, True,
+ GrabModeAsync, GrabModeAsync);
+ }
+}
+
+void
+procevent(void) {
+ XEvent ev;
+
+ while(XPending(dpy)) {
+ XNextEvent(dpy, &ev);
+ if(handler[ev.type])
+ (handler[ev.type])(&ev); /* call handler */
+ }
+}
diff --git a/main.c b/main.c
@@ -0,0 +1,288 @@
+/* (C)opyright MMVI-MMVII Anselm R. Garbe <garbeam at gmail dot com>
+ * See LICENSE file for license details.
+ */
+
+#include "2wm.h"
+#include <errno.h>
+#include <locale.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+#include <sys/select.h>
+#include <X11/cursorfont.h>
+#include <X11/keysym.h>
+#include <X11/Xatom.h>
+#include <X11/Xproto.h>
+
+/* extern */
+
+char stext[256];
+int bh, bmw, screen, sx, sy, sw, sh, wax, way, waw, wah;
+unsigned int master, nmaster, ntags, numlockmask;
+Atom wmatom[WMLast], netatom[NetLast];
+Bool running = True;
+Bool *seltag;
+Bool selscreen = True;
+Client *clients = NULL;
+Client *sel = NULL;
+Client *stack = NULL;
+Cursor cursor[CurLast];
+Display *dpy;
+DC dc = {0};
+Window root, barwin;
+
+/* static */
+
+static int (*xerrorxlib)(Display *, XErrorEvent *);
+static Bool otherwm, readin;
+
+static void
+cleanup(void) {
+ close(STDIN_FILENO);
+ while(stack) {
+ resize(stack, True);
+ unmanage(stack);
+ }
+ if(dc.font.set)
+ XFreeFontSet(dpy, dc.font.set);
+ else
+ XFreeFont(dpy, dc.font.xfont);
+ XUngrabKey(dpy, AnyKey, AnyModifier, root);
+ XFreePixmap(dpy, dc.drawable);
+ XFreeGC(dpy, dc.gc);
+ XDestroyWindow(dpy, barwin);
+ XFreeCursor(dpy, cursor[CurNormal]);
+ XFreeCursor(dpy, cursor[CurResize]);
+ XFreeCursor(dpy, cursor[CurMove]);
+ XSetInputFocus(dpy, PointerRoot, RevertToPointerRoot, CurrentTime);
+ XSync(dpy, False);
+ free(seltag);
+}
+
+static void
+scan(void) {
+ unsigned int i, num;
+ Window *wins, d1, d2;
+ XWindowAttributes wa;
+
+ wins = NULL;
+ if(XQueryTree(dpy, root, &d1, &d2, &wins, &num)) {
+ for(i = 0; i < num; i++) {
+ if(!XGetWindowAttributes(dpy, wins[i], &wa))
+ continue;
+ if(wa.override_redirect || XGetTransientForHint(dpy, wins[i], &d1))
+ continue;
+ if(wa.map_state == IsViewable)
+ manage(wins[i], &wa);
+ }
+ }
+ if(wins)
+ XFree(wins);
+}
+
+static void
+setup(void) {
+ int i, j;
+ unsigned int mask;
+ Window w;
+ XModifierKeymap *modmap;
+ XSetWindowAttributes wa;
+
+ /* init atoms */
+ wmatom[WMProtocols] = XInternAtom(dpy, "WM_PROTOCOLS", False);
+ wmatom[WMDelete] = XInternAtom(dpy, "WM_DELETE_WINDOW", False);
+ wmatom[WMState] = XInternAtom(dpy, "WM_STATE", False);
+ netatom[NetSupported] = XInternAtom(dpy, "_NET_SUPPORTED", False);
+ netatom[NetWMName] = XInternAtom(dpy, "_NET_WM_NAME", False);
+ XChangeProperty(dpy, root, netatom[NetSupported], XA_ATOM, 32,
+ PropModeReplace, (unsigned char *) netatom, NetLast);
+ /* init cursors */
+ cursor[CurNormal] = XCreateFontCursor(dpy, XC_left_ptr);
+ cursor[CurResize] = XCreateFontCursor(dpy, XC_sizing);
+ cursor[CurMove] = XCreateFontCursor(dpy, XC_fleur);
+ /* init modifier map */
+ numlockmask = 0;
+ modmap = XGetModifierMapping(dpy);
+ for (i = 0; i < 8; i++) {
+ for (j = 0; j < modmap->max_keypermod; j++) {
+ if(modmap->modifiermap[i * modmap->max_keypermod + j] == XKeysymToKeycode(dpy, XK_Num_Lock))
+ numlockmask = (1 << i);
+ }
+ }
+ XFreeModifiermap(modmap);
+ /* select for events */
+ wa.event_mask = SubstructureRedirectMask | SubstructureNotifyMask
+ | EnterWindowMask | LeaveWindowMask;
+ wa.cursor = cursor[CurNormal];
+ XChangeWindowAttributes(dpy, root, CWEventMask | CWCursor, &wa);
+ grabkeys();
+ initrregs();
+ for(ntags = 0; tags[ntags]; ntags++);
+ seltag = emallocz(sizeof(Bool) * ntags);
+ seltag[0] = True;
+ /* style */
+ dc.norm[ColBorder] = getcolor(NORMBORDERCOLOR);
+ dc.norm[ColBG] = getcolor(NORMBGCOLOR);
+ dc.norm[ColFG] = getcolor(NORMFGCOLOR);
+ dc.sel[ColBorder] = getcolor(SELBORDERCOLOR);
+ dc.sel[ColBG] = getcolor(SELBGCOLOR);
+ dc.sel[ColFG] = getcolor(SELFGCOLOR);
+ setfont(FONT);
+ /* geometry */
+ sx = sy = 0;
+ sw = DisplayWidth(dpy, screen);
+ sh = DisplayHeight(dpy, screen);
+ master = MASTER;
+ nmaster = NMASTER;
+ bmw = textw(TILESYMBOL) > textw(FLOATSYMBOL) ? textw(TILESYMBOL) : textw(FLOATSYMBOL);
+ /* bar */
+ dc.h = bh = dc.font.height + 2;
+ wa.override_redirect = 1;
+ wa.background_pixmap = ParentRelative;
+ wa.event_mask = ButtonPressMask | ExposureMask;
+ barwin = XCreateWindow(dpy, root, sx, sy + (TOPBAR ? 0 : sh - bh), sw, bh, 0,
+ DefaultDepth(dpy, screen), CopyFromParent, DefaultVisual(dpy, screen),
+ CWOverrideRedirect | CWBackPixmap | CWEventMask, &wa);
+ XDefineCursor(dpy, barwin, cursor[CurNormal]);
+ XMapRaised(dpy, barwin);
+ strcpy(stext, "dwm-"VERSION);
+ /* windowarea */
+ wax = sx;
+ way = sy + (TOPBAR ? bh : 0);
+ wah = sh - bh;
+ waw = sw;
+ /* pixmap for everything */
+ dc.drawable = XCreatePixmap(dpy, root, sw, bh, DefaultDepth(dpy, screen));
+ dc.gc = XCreateGC(dpy, root, 0, 0);
+ XSetLineAttributes(dpy, dc.gc, 1, LineSolid, CapButt, JoinMiter);
+ /* multihead support */
+ selscreen = XQueryPointer(dpy, root, &w, &w, &i, &i, &i, &i, &mask);
+}
+
+/*
+ * Startup Error handler to check if another window manager
+ * is already running.
+ */
+static int
+xerrorstart(Display *dsply, XErrorEvent *ee) {
+ otherwm = True;
+ return -1;
+}
+
+/* extern */
+
+void
+sendevent(Window w, Atom a, long value) {
+ XEvent e;
+
+ e.type = ClientMessage;
+ e.xclient.window = w;
+ e.xclient.message_type = a;
+ e.xclient.format = 32;
+ e.xclient.data.l[0] = value;
+ e.xclient.data.l[1] = CurrentTime;
+ XSendEvent(dpy, w, False, NoEventMask, &e);
+ XSync(dpy, False);
+}
+
+void
+quit(Arg *arg) {
+ readin = running = False;
+}
+
+/* There's no way to check accesses to destroyed windows, thus those cases are
+ * ignored (especially on UnmapNotify's). Other types of errors call Xlibs
+ * default error handler, which may call exit.
+ */
+int
+xerror(Display *dpy, XErrorEvent *ee) {
+ if(ee->error_code == BadWindow
+ || (ee->request_code == X_SetInputFocus && ee->error_code == BadMatch)
+ || (ee->request_code == X_PolyText8 && ee->error_code == BadDrawable)
+ || (ee->request_code == X_PolyFillRectangle && ee->error_code == BadDrawable)
+ || (ee->request_code == X_PolySegment && ee->error_code == BadDrawable)
+ || (ee->request_code == X_ConfigureWindow && ee->error_code == BadMatch)
+ || (ee->request_code == X_GrabKey && ee->error_code == BadAccess)
+ || (ee->request_code == X_CopyArea && ee->error_code == BadDrawable))
+ return 0;
+ fprintf(stderr, "dwm: fatal error: request code=%d, error code=%d\n",
+ ee->request_code, ee->error_code);
+ return xerrorxlib(dpy, ee); /* may call exit */
+}
+
+int
+main(int argc, char *argv[]) {
+ char *p;
+ int r, xfd;
+ fd_set rd;
+
+ if(argc == 2 && !strncmp("-v", argv[1], 3)) {
+ fputs("dwm-"VERSION", (C)opyright MMVI-MMVII Anselm R. Garbe\n", stdout);
+ exit(EXIT_SUCCESS);
+ }
+ else if(argc != 1)
+ eprint("usage: dwm [-v]\n");
+ setlocale(LC_CTYPE, "");
+ dpy = XOpenDisplay(0);
+ if(!dpy)
+ eprint("dwm: cannot open display\n");
+ xfd = ConnectionNumber(dpy);
+ screen = DefaultScreen(dpy);
+ root = RootWindow(dpy, screen);
+ otherwm = False;
+ XSetErrorHandler(xerrorstart);
+ /* this causes an error if some other window manager is running */
+ XSelectInput(dpy, root, SubstructureRedirectMask);
+ XSync(dpy, False);
+ if(otherwm)
+ eprint("dwm: another window manager is already running\n");
+
+ XSync(dpy, False);
+ XSetErrorHandler(NULL);
+ xerrorxlib = XSetErrorHandler(xerror);
+ XSync(dpy, False);
+ setup();
+ drawstatus();
+ scan();
+
+ /* main event loop, also reads status text from stdin */
+ XSync(dpy, False);
+ procevent();
+ readin = True;
+ while(running) {
+ FD_ZERO(&rd);
+ if(readin)
+ FD_SET(STDIN_FILENO, &rd);
+ FD_SET(xfd, &rd);
+ if(select(xfd + 1, &rd, NULL, NULL, NULL) == -1) {
+ if(errno == EINTR)
+ continue;
+ eprint("select failed\n");
+ }
+ if(FD_ISSET(STDIN_FILENO, &rd)) {
+ switch(r = read(STDIN_FILENO, stext, sizeof stext - 1)) {
+ case -1:
+ strncpy(stext, strerror(errno), sizeof stext - 1);
+ stext[sizeof stext - 1] = '\0';
+ readin = False;
+ break;
+ case 0:
+ strncpy(stext, "EOF", 4);
+ readin = False;
+ break;
+ default:
+ for(stext[r] = '\0', p = stext + strlen(stext) - 1; p >= stext && *p == '\n'; *p-- = '\0');
+ for(; p >= stext && *p != '\n'; --p);
+ if(p > stext)
+ strncpy(stext, p + 1, sizeof stext);
+ }
+ drawstatus();
+ }
+ if(FD_ISSET(xfd, &rd))
+ procevent();
+ }
+ cleanup();
+ XCloseDisplay(dpy);
+ return 0;
+}
diff --git a/tag.c b/tag.c
@@ -0,0 +1,134 @@
+/* (C)opyright MMVI-MMVII Anselm R. Garbe <garbeam at gmail dot com>
+ * See LICENSE file for license details.
+ */
+#include "2wm.h"
+#include <regex.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sys/types.h>
+#include <X11/Xutil.h>
+
+
+typedef struct {
+ const char *clpattern;
+ const char *tpattern;
+ Bool isfloat;
+} Rule;
+
+typedef struct {
+ regex_t *clregex;
+ regex_t *tregex;
+} RReg;
+
+/* static */
+
+TAGS
+RULES
+
+static RReg *rreg = NULL;
+static unsigned int len = 0;
+
+/* extern */
+
+Client *
+getnext(Client *c) {
+ for(; c && !isvisible(c); c = c->next);
+ return c;
+}
+
+Client *
+getprev(Client *c) {
+ for(; c && !isvisible(c); c = c->prev);
+ return c;
+}
+
+void
+initrregs(void) {
+ unsigned int i;
+ regex_t *reg;
+
+ if(rreg)
+ return;
+ len = sizeof rule / sizeof rule[0];
+ rreg = emallocz(len * sizeof(RReg));
+ for(i = 0; i < len; i++) {
+ if(rule[i].clpattern) {
+ reg = emallocz(sizeof(regex_t));
+ if(regcomp(reg, rule[i].clpattern, REG_EXTENDED))
+ free(reg);
+ else
+ rreg[i].clregex = reg;
+ }
+ if(rule[i].tpattern) {
+ reg = emallocz(sizeof(regex_t));
+ if(regcomp(reg, rule[i].tpattern, REG_EXTENDED))
+ free(reg);
+ else
+ rreg[i].tregex = reg;
+ }
+ }
+}
+
+void
+settags(Client *c, Client *trans) {
+ char prop[512];
+ unsigned int i, j;
+ regmatch_t tmp;
+ Bool matched = trans != NULL;
+ XClassHint ch = { 0 };
+
+ if(matched) {
+ for(i = 0; i < ntags; i++)
+ c->tags[i] = trans->tags[i];
+ }
+ else {
+ XGetClassHint(dpy, c->win, &ch);
+ snprintf(prop, sizeof prop, "%s:%s:%s",
+ ch.res_class ? ch.res_class : "",
+ ch.res_name ? ch.res_name : "", c->name);
+ for(i = 0; i < len; i++)
+ if(rreg[i].clregex && !regexec(rreg[i].clregex, prop, 1, &tmp, 0)) {
+ c->isfloat = rule[i].isfloat;
+ for(j = 0; rreg[i].tregex && j < ntags; j++) {
+ if(!regexec(rreg[i].tregex, tags[j], 1, &tmp, 0)) {
+ matched = True;
+ c->tags[j] = True;
+ }
+ }
+ }
+ if(ch.res_class)
+ XFree(ch.res_class);
+ if(ch.res_name)
+ XFree(ch.res_name);
+ }
+ if(!matched)
+ for(i = 0; i < ntags; i++)
+ c->tags[i] = seltag[i];
+}
+
+void
+tag(Arg *arg) {
+ unsigned int i;
+
+ if(!sel)
+ return;
+ for(i = 0; i < ntags; i++)
+ sel->tags[i] = (arg->i == -1) ? True : False;
+ if(arg->i >= 0 && arg->i < ntags)
+ sel->tags[arg->i] = True;
+ arrange();
+}
+
+void
+toggletag(Arg *arg) {
+ unsigned int i;
+
+ if(!sel)
+ return;
+ sel->tags[arg->i] = !sel->tags[arg->i];
+ for(i = 0; i < ntags && !sel->tags[i]; i++);
+ if(i == ntags)
+ sel->tags[arg->i] = True;
+ arrange();
+}
diff --git a/util.c b/util.c
@@ -0,0 +1,54 @@
+/* (C)opyright MMVI-MMVII Anselm R. Garbe <garbeam at gmail dot com>
+ * See LICENSE file for license details.
+ */
+#include "2wm.h"
+#include <stdarg.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <sys/wait.h>
+#include <unistd.h>
+
+/* extern */
+
+void *
+emallocz(unsigned int size) {
+ void *res = calloc(1, size);
+
+ if(!res)
+ eprint("fatal: could not malloc() %u bytes\n", size);
+ return res;
+}
+
+void
+eprint(const char *errstr, ...) {
+ va_list ap;
+
+ va_start(ap, errstr);
+ vfprintf(stderr, errstr, ap);
+ va_end(ap);
+ exit(EXIT_FAILURE);
+}
+
+void
+spawn(Arg *arg) {
+ static char *shell = NULL;
+
+ if(!shell && !(shell = getenv("SHELL")))
+ shell = "/bin/sh";
+ if(!arg->cmd)
+ return;
+ /* The double-fork construct avoids zombie processes and keeps the code
+ * clean from stupid signal handlers. */
+ if(fork() == 0) {
+ if(fork() == 0) {
+ if(dpy)
+ close(ConnectionNumber(dpy));
+ setsid();
+ execl(shell, shell, "-c", arg->cmd, (char *)NULL);
+ fprintf(stderr, "dwm: execl '%s -c %s'", shell, arg->cmd);
+ perror(" failed");
+ }
+ exit(0);
+ }
+ wait(0);
+}
diff --git a/view.c b/view.c
@@ -0,0 +1,271 @@
+/* (C)opyright MMVI-MMVII Anselm R. Garbe <garbeam at gmail dot com>
+ * See LICENSE file for license details.
+ */
+#include "2wm.h"
+#include <stdio.h>
+
+/* static */
+
+static Client *
+nexttiled(Client *c) {
+ for(c = getnext(c); c && c->isfloat; c = getnext(c->next));
+ return c;
+}
+
+static void
+togglemax(Client *c) {
+ XEvent ev;
+
+ if(c->isfixed)
+ return;
+
+ if((c->ismax = !c->ismax)) {
+ c->rx = c->x; c->x = wax;
+ c->ry = c->y; c->y = way;
+ c->rw = c->w; c->w = waw - 2 * BORDERPX;
+ c->rh = c->h; c->h = wah - 2 * BORDERPX;
+ }
+ else {
+ c->x = c->rx;
+ c->y = c->ry;
+ c->w = c->rw;
+ c->h = c->rh;
+ }
+ resize(c, True);
+ while(XCheckMaskEvent(dpy, EnterWindowMask, &ev));
+}
+
+/* extern */
+
+void (*arrange)(void) = DEFMODE;
+
+void
+detach(Client *c) {
+ if(c->prev)
+ c->prev->next = c->next;
+ if(c->next)
+ c->next->prev = c->prev;
+ if(c == clients)
+ clients = c->next;
+ c->next = c->prev = NULL;
+}
+
+void
+dofloat(void) {
+ Client *c;
+
+ for(c = clients; c; c = c->next) {
+ if(isvisible(c)) {
+ resize(c, True);
+ }
+ else
+ XMoveWindow(dpy, c->win, c->x + 2 * sw, c->y);
+ }
+ if(!sel || !isvisible(sel)) {
+ for(c = stack; c && !isvisible(c); c = c->snext);
+ focus(c);
+ }
+ restack();
+}
+
+void
+dotile(void) {
+ unsigned int i, n, mw, mh, tw, th;
+ Client *c;
+
+ for(n = 0, c = nexttiled(clients); c; c = nexttiled(c->next))
+ n++;
+ /* window geoms */
+ mh = (n > nmaster) ? wah / nmaster : wah / (n > 0 ? n : 1);
+ mw = (n > nmaster) ? (waw * master) / 1000 : waw;
+ th = (n > nmaster) ? wah / (n - nmaster) : 0;
+ tw = waw - mw;
+
+ for(i = 0, c = clients; c; c = c->next)
+ if(isvisible(c)) {
+ if(c->isfloat) {
+ resize(c, True);
+ continue;
+ }
+ c->ismax = False;
+ c->x = wax;
+ c->y = way;
+ if(i < nmaster) {
+ c->y += i * mh;
+ c->w = mw - 2 * BORDERPX;
+ c->h = mh - 2 * BORDERPX;
+ }
+ else { /* tile window */
+ c->x += mw;
+ c->w = tw - 2 * BORDERPX;
+ if(th > 2 * BORDERPX) {
+ c->y += (i - nmaster) * th;
+ c->h = th - 2 * BORDERPX;
+ }
+ else /* fallback if th <= 2 * BORDERPX */
+ c->h = wah - 2 * BORDERPX;
+ }
+ resize(c, False);
+ i++;
+ }
+ else
+ XMoveWindow(dpy, c->win, c->x + 2 * sw, c->y);
+ if(!sel || !isvisible(sel)) {
+ for(c = stack; c && !isvisible(c); c = c->snext);
+ focus(c);
+ }
+ restack();
+}
+
+void
+focusnext(Arg *arg) {
+ Client *c;
+
+ if(!sel)
+ return;
+ if(!(c = getnext(sel->next)))
+ c = getnext(clients);
+ if(c) {
+ focus(c);
+ restack();
+ }
+}
+
+void
+focusprev(Arg *arg) {
+ Client *c;
+
+ if(!sel)
+ return;
+ if(!(c = getprev(sel->prev))) {
+ for(c = clients; c && c->next; c = c->next);
+ c = getprev(c);
+ }
+ if(c) {
+ focus(c);
+ restack();
+ }
+}
+
+void
+incnmaster(Arg *arg) {
+ if((arrange == dofloat) || (nmaster + arg->i < 1)
+ || (wah / (nmaster + arg->i) <= 2 * BORDERPX))
+ return;
+ nmaster += arg->i;
+ if(sel)
+ arrange();
+ else
+ drawstatus();
+}
+
+Bool
+isvisible(Client *c) {
+ unsigned int i;
+
+ for(i = 0; i < ntags; i++)
+ if(c->tags[i] && seltag[i])
+ return True;
+ return False;
+}
+
+void
+resizemaster(Arg *arg) {
+ if(arg->i == 0)
+ master = MASTER;
+ else {
+ if(waw * (master + arg->i) / 1000 >= waw - 2 * BORDERPX
+ || waw * (master + arg->i) / 1000 <= 2 * BORDERPX)
+ return;
+ master += arg->i;
+ }
+ arrange();
+}
+
+void
+restack(void) {
+ Client *c;
+ XEvent ev;
+
+ drawstatus();
+ if(!sel)
+ return;
+ if(sel->isfloat || arrange == dofloat)
+ XRaiseWindow(dpy, sel->win);
+ if(arrange != dofloat) {
+ if(!sel->isfloat)
+ XLowerWindow(dpy, sel->win);
+ for(c = nexttiled(clients); c; c = nexttiled(c->next)) {
+ if(c == sel)
+ continue;
+ XLowerWindow(dpy, c->win);
+ }
+ }
+ XSync(dpy, False);
+ while(XCheckMaskEvent(dpy, EnterWindowMask, &ev));
+}
+
+void
+togglefloat(Arg *arg) {
+ if (!sel || arrange == dofloat)
+ return;
+ sel->isfloat = !sel->isfloat;
+ arrange();
+}
+
+void
+togglemode(Arg *arg) {
+ arrange = (arrange == dofloat) ? dotile : dofloat;
+ if(sel)
+ arrange();
+ else
+ drawstatus();
+}
+
+void
+toggleview(Arg *arg) {
+ unsigned int i;
+
+ seltag[arg->i] = !seltag[arg->i];
+ for(i = 0; i < ntags && !seltag[i]; i++);
+ if(i == ntags)
+ seltag[arg->i] = True; /* cannot toggle last view */
+ arrange();
+}
+
+void
+view(Arg *arg) {
+ unsigned int i;
+
+ for(i = 0; i < ntags; i++)
+ seltag[i] = (arg->i == -1) ? True : False;
+ if(arg->i >= 0 && arg->i < ntags)
+ seltag[arg->i] = True;
+ arrange();
+}
+
+void
+zoom(Arg *arg) {
+ unsigned int n;
+ Client *c;
+
+ if(!sel)
+ return;
+ if(sel->isfloat || (arrange == dofloat)) {
+ togglemax(sel);
+ return;
+ }
+ for(n = 0, c = nexttiled(clients); c; c = nexttiled(c->next))
+ n++;
+
+ if((c = sel) == nexttiled(clients))
+ if(!(c = nexttiled(c->next)))
+ return;
+ detach(c);
+ if(clients)
+ clients->prev = c;
+ c->next = clients;
+ clients = c;
+ focus(c);
+ arrange();
+}