2wm

dual window manager prototype (minimalist dwm with no tags, just one view)
git clone git://git.suckless.org/2wm
Log | Files | Refs | README | LICENSE

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(); +}