From f7173bd79df77a155444663a262dfc5b0f9f0f42 Mon Sep 17 00:00:00 2001 From: Julien Danjou Date: Wed, 5 Sep 2007 20:15:00 +0200 Subject: [PATCH] first import --- LICENSE | 26 ++ Makefile | 62 +++++ README | 47 ++++ client.c | 657 +++++++++++++++++++++++++++++++++++++++++++++ client.h | 49 ++++ config.c | 405 ++++++++++++++++++++++++++++ config.h | 101 +++++++ config.mk | 25 ++ draw.c | 160 +++++++++++ draw.h | 11 + event.c | 412 ++++++++++++++++++++++++++++ event.h | 10 + jdwm.1 | 153 +++++++++++ jdwm.c | 379 ++++++++++++++++++++++++++ jdwm.doxygen | 276 +++++++++++++++++++ jdwm.h | 49 ++++ jdwmrc | 111 ++++++++ layout.c | 280 +++++++++++++++++++ layout.h | 25 ++ layouts/floating.c | 25 ++ layouts/floating.h | 8 + layouts/grid.c | 50 ++++ layouts/grid.h | 11 + layouts/spiral.c | 77 ++++++ layouts/spiral.h | 9 + layouts/tile.c | 201 ++++++++++++++ layouts/tile.h | 13 + tag.c | 313 +++++++++++++++++++++ tag.h | 27 ++ util.c | 50 ++++ util.h | 66 +++++ 31 files changed, 4088 insertions(+) create mode 100644 LICENSE create mode 100644 Makefile create mode 100644 README create mode 100644 client.c create mode 100644 client.h create mode 100644 config.c create mode 100644 config.h create mode 100644 config.mk create mode 100644 draw.c create mode 100644 draw.h create mode 100644 event.c create mode 100644 event.h create mode 100644 jdwm.1 create mode 100644 jdwm.c create mode 100644 jdwm.doxygen create mode 100644 jdwm.h create mode 100644 jdwmrc create mode 100644 layout.c create mode 100644 layout.h create mode 100644 layouts/floating.c create mode 100644 layouts/floating.h create mode 100644 layouts/grid.c create mode 100644 layouts/grid.h create mode 100644 layouts/spiral.c create mode 100644 layouts/spiral.h create mode 100644 layouts/tile.c create mode 100644 layouts/tile.h create mode 100644 tag.c create mode 100644 tag.h create mode 100644 util.c create mode 100644 util.h diff --git a/LICENSE b/LICENSE new file mode 100644 index 00000000..1600fb46 --- /dev/null +++ b/LICENSE @@ -0,0 +1,26 @@ +MIT/X Consortium License + +© 2006-2007 Anselm R. Garbe +© 2006-2007 Sander van Dijk +© 2006-2007 Jukka Salmi +© 2007 Premysl Hruby +© 2007 Szabolcs Nagy +© 2007 Julien Danjou + +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 new file mode 100644 index 00000000..30ca741f --- /dev/null +++ b/Makefile @@ -0,0 +1,62 @@ +# jdwm - Julien's dynamic window manager +# © 2007 Julien Danjou +# © 2006-2007 Anselm R. Garbe, Sander van Dijk + +include config.mk + +SRC = client.c draw.c event.c layout.c jdwm.c tag.c util.c config.c +OBJ = ${SRC:.c=.o} ${LAYOUTS:.c=.o} + +all: options jdwm + +options: + @echo jdwm build options: + @echo "LAYOUTS = ${LAYOUTS}" + @echo "CFLAGS = ${CFLAGS}" + @echo "LDFLAGS = ${LDFLAGS}" + @echo "CC = ${CC}" + +.c.o: + @echo -e \\t\(CC\) $< + @${CC} -c ${CFLAGS} $< -o $@ + +${OBJ}: jdwm.h config.mk + +jdwm: ${OBJ} + @echo -e \\t\(CC\) ${OBJ} -o $@ + @${CC} -o $@ ${OBJ} ${LDFLAGS} + +clean: + @echo cleaning + @rm -f jdwm ${OBJ} jdwm-${VERSION}.tar.gz + +dist: clean + @echo creating dist tarball + @mkdir -p jdwm-${VERSION} + @cp -R LICENSE Makefile README config.*.h config.mk \ + jdwm.1 jdwm.h grid.h tile.h mem.h ${SRC} ${LAYOUTS} jdwm-${VERSION} + @tar -cf jdwm-${VERSION}.tar jdwm-${VERSION} + @gzip jdwm-${VERSION}.tar + @rm -rf jdwm-${VERSION} + +install: all + @echo installing executable file to ${DESTDIR}${PREFIX}/bin + @mkdir -p ${DESTDIR}${PREFIX}/bin + @cp -f jdwm ${DESTDIR}${PREFIX}/bin + @chmod 755 ${DESTDIR}${PREFIX}/bin/jdwm + @echo installing manual page to ${DESTDIR}${MANPREFIX}/man1 + @mkdir -p ${DESTDIR}${MANPREFIX}/man1 + @sed "s/VERSION/${VERSION}/g" < jdwm.1 > ${DESTDIR}${MANPREFIX}/man1/jdwm.1 + @chmod 644 ${DESTDIR}${MANPREFIX}/man1/jdwm.1 + +uninstall: + @echo removing executable file from ${DESTDIR}${PREFIX}/bin + @rm -f ${DESTDIR}${PREFIX}/bin/jdwm + @echo removing manual page from ${DESTDIR}${MANPREFIX}/man1 + @rm -f ${DESTDIR}${MANPREFIX}/man1/jdwm.1 + +doc: + @echo generating documentation + @doxygen jdwm.doxygen + +.PHONY: all options clean dist install uninstall doc diff --git a/README b/README new file mode 100644 index 00000000..e698234d --- /dev/null +++ b/README @@ -0,0 +1,47 @@ +jdwm - Julien's dynamic window manager +============================ +jdwm is an extremely fast, small, and dynamic window manager for X. +It's based on dwm. + +Requirements +------------ +In order to build jdwm you need the Xlib header files. + +Installation +------------ +Edit config.mk to match your local setup (jdwm is installed into +the /usr/local namespace by default). + +Afterwards enter the following command to build and install jdwm (if +necessary as root): + + make clean install + +Running jdwm +----------- +Add the following line to your .xinitrc to start jdwm using startx +or to .xsession to start jdwm using gdm/kdm/xdm...: + + exec jdwm + +In order to connect jdwm to a specific display, make sure that +the DISPLAY environment variable is set correctly, e.g.: + + DISPLAY=foo.bar:1 exec jdwm + +(This will start jdwm on display :1 of the host foo.bar.) + +In order to display status info in the bar, you can do something +like this in your .xinitrc: + + while true + do + echo `date` `uptime | sed 's/.*,//'` + sleep 1 + done | jdwm + + +Configuration +------------- +The configuration of jdwm is done by creating a custom config.h +and (re)compiling the source code. diff --git a/client.c b/client.c new file mode 100644 index 00000000..28fbc402 --- /dev/null +++ b/client.c @@ -0,0 +1,657 @@ +/* See LICENSE file for copyright and license details. */ + +#include +#include +#include +#include +#include + +#include "jdwm.h" +#include "util.h" +#include "layout.h" +#include "tag.h" + +#include "layouts/floating.h" + +/* extern */ +extern int sx, sy, sw, sh; /* screen geometry */ +extern int wax, way, wah, waw; /* windowarea geometry */ +extern Client *clients, *sel, *stack; /* global client list and stack */ +extern Bool selscreen; +extern Atom jdwmprops, wmatom[WMLast], netatom[NetLast]; + +/* static */ + +static char prop[128]; + +/** Attach client stack to clients stacks + * \param c the client + */ +static inline void +attachstack(Client * c) +{ + c->snext = stack; + stack = c; +} + +/** Detach client from stack + * \param c the client + */ +static inline void +detachstack(Client * c) +{ + Client **tc; + + for(tc = &stack; *tc && *tc != c; tc = &(*tc)->snext); + *tc = c->snext; +} + +/** Grab or ungrab buttons when a client is focused + * \param c client + * \param focused True if client is focused + * \param modkey Mod key mask + * \param numlockmask Numlock mask + */ +static void +grabbuttons(Client * c, Bool focused, KeySym modkey, unsigned int numlockmask) +{ + XUngrabButton(c->display, AnyButton, AnyModifier, c->win); + + if(focused) + { + XGrabButton(c->display, Button1, modkey, c->win, False, BUTTONMASK, + GrabModeAsync, GrabModeSync, None, None); + XGrabButton(c->display, Button1, modkey | LockMask, c->win, False, + BUTTONMASK, GrabModeAsync, GrabModeSync, None, None); + XGrabButton(c->display, Button1, modkey | numlockmask, c->win, False, + BUTTONMASK, GrabModeAsync, GrabModeSync, None, None); + XGrabButton(c->display, Button1, modkey | numlockmask | LockMask, + c->win, False, BUTTONMASK, GrabModeAsync, GrabModeSync, None, None); + + XGrabButton(c->display, Button2, modkey, c->win, False, BUTTONMASK, + GrabModeAsync, GrabModeSync, None, None); + XGrabButton(c->display, Button2, modkey | LockMask, c->win, False, + BUTTONMASK, GrabModeAsync, GrabModeSync, None, None); + XGrabButton(c->display, Button2, modkey | numlockmask, c->win, False, + BUTTONMASK, GrabModeAsync, GrabModeSync, None, None); + XGrabButton(c->display, Button2, modkey | numlockmask | LockMask, + c->win, False, BUTTONMASK, GrabModeAsync, GrabModeSync, None, None); + + XGrabButton(c->display, Button3, modkey, c->win, False, BUTTONMASK, + GrabModeAsync, GrabModeSync, None, None); + XGrabButton(c->display, Button3, modkey | LockMask, c->win, False, + BUTTONMASK, GrabModeAsync, GrabModeSync, None, None); + XGrabButton(c->display, Button3, modkey | numlockmask, c->win, False, + BUTTONMASK, GrabModeAsync, GrabModeSync, None, None); + XGrabButton(c->display, Button3, modkey | numlockmask | LockMask, + c->win, False, BUTTONMASK, GrabModeAsync, GrabModeSync, None, None); + + XUngrabButton(c->display, AnyButton, AnyModifier, DefaultRootWindow(c->display)); + } + else + { + XGrabButton(c->display, AnyButton, AnyModifier, c->win, False, BUTTONMASK, + GrabModeAsync, GrabModeSync, None, None); + XGrabButton(c->display, Button4, NoSymbol, DefaultRootWindow(c->display), False, BUTTONMASK, + GrabModeAsync, GrabModeSync, None, None); + XGrabButton(c->display, Button4, LockMask, DefaultRootWindow(c->display), False, BUTTONMASK, + GrabModeAsync, GrabModeSync, None, None); + XGrabButton(c->display, Button4, numlockmask, DefaultRootWindow(c->display), False, BUTTONMASK, + GrabModeAsync, GrabModeSync, None, None); + XGrabButton(c->display, Button4, numlockmask | LockMask, DefaultRootWindow(c->display), False, BUTTONMASK, + GrabModeAsync, GrabModeSync, None, None); + + XGrabButton(c->display, Button5, NoSymbol, DefaultRootWindow(c->display), False, BUTTONMASK, + GrabModeAsync, GrabModeSync, None, None); + XGrabButton(c->display, Button5, LockMask, DefaultRootWindow(c->display), False, BUTTONMASK, + GrabModeAsync, GrabModeSync, None, None); + XGrabButton(c->display, Button5, numlockmask, DefaultRootWindow(c->display), False, BUTTONMASK, + GrabModeAsync, GrabModeSync, None, None); + XGrabButton(c->display, Button5, numlockmask | LockMask, DefaultRootWindow(c->display), False, BUTTONMASK, + GrabModeAsync, GrabModeSync, None, None); + + } +} + +/** XXX: No idea + * \param c the client + * \return True if atom has WMDelete + */ +static Bool +isprotodel(Client * c) +{ + int i, n; + Atom *protocols; + Bool ret = False; + + if(XGetWMProtocols(c->display, c->win, &protocols, &n)) + { + for(i = 0; !ret && i < n; i++) + if(protocols[i] == wmatom[WMDelete]) + ret = True; + XFree(protocols); + } + return ret; +} + +/** XXX: No idea + * \param c the client + * \param state no idea + */ +static void +setclientstate(Client * c, long state) +{ + long data[] = { state, None }; + + XChangeProperty(c->display, c->win, wmatom[WMState], wmatom[WMState], 32, + PropModeReplace, (unsigned char *) data, 2); +} + +/* extern */ + +/** Attach client to the beginning of the clients stack + * \param c the client + */ +inline void +attach(Client * c) +{ + if(clients) + clients->prev = c; + c->next = clients; + clients = c; +} + +inline void +updatetitle(Client * c) +{ + if(!gettextprop(c->display, c->win, netatom[NetWMName], c->name, sizeof c->name)) + gettextprop(c->display, c->win, wmatom[WMName], c->name, sizeof c->name); +} + +/** Ban client + * \param c the client + */ +void +ban(Client * c) +{ + if(c->isbanned) + return; + XUnmapWindow(c->display, c->win); + setclientstate(c, IconicState); + c->isbanned = True; + c->unmapped++; +} + + +/** Configure client + * \param c the client + */ +void +configure(Client * c) +{ + XConfigureEvent ce; + + ce.type = ConfigureNotify; + ce.display = c->display; + ce.event = c->win; + ce.window = c->win; + ce.x = c->x; + ce.y = c->y; + ce.width = c->w; + ce.height = c->h; + ce.border_width = c->border; + ce.above = None; + ce.override_redirect = False; + XSendEvent(c->display, c->win, False, StructureNotifyMask, (XEvent *) & ce); +} + +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; +} + +/** Give focus to client, or to first client if c is NULL + * \param disp Display ref + * \param drawcontext drawcontext ref + * \param c client + * \param jdwmconf jdwm config + */ +void +focus(Display *disp, DC *drawcontext, Client * c, jdwm_config *jdwmconf) +{ + /* if c is NULL or invisible, take next client in the stack */ + if((!c && selscreen) || (c && !isvisible(c, jdwmconf->ntags))) + for(c = stack; c && !isvisible(c, jdwmconf->ntags); c = c->snext); + + /* if a client was selected but it's not the current client, unfocus it */ + if(sel && sel != c) + { + grabbuttons(sel, False, jdwmconf->modkey, jdwmconf->numlockmask); + XSetWindowBorder(sel->display, sel->win, drawcontext->norm[ColBorder]); + setclienttrans(sel, jdwmconf->opacity_unfocused, 0); + } + if(c) + { + detachstack(c); + attachstack(c); + grabbuttons(c, True, jdwmconf->modkey, jdwmconf->numlockmask); + } + if(!selscreen) + return; + sel = c; + drawstatus(disp, jdwmconf); + if(sel) + { + XSetWindowBorder(sel->display, sel->win, drawcontext->sel[ColBorder]); + XSetInputFocus(sel->display, sel->win, RevertToPointerRoot, CurrentTime); + for(c = stack; c; c = c->snext) + if(c != sel) + setclienttrans(c, jdwmconf->opacity_unfocused, 0); + setclienttrans(sel, -1, 0); + } + else + XSetInputFocus(disp, DefaultRootWindow(disp), RevertToPointerRoot, CurrentTime); +} + +/** Kill selected client + * \param disp Display ref + * \param jdwmconf jdwm config + * \param arg unused + * \ingroup ui_callback + */ +void +uicb_killclient(Display *disp __attribute__ ((unused)), + jdwm_config *jdwmconf __attribute__ ((unused)), + const char *arg __attribute__ ((unused))) +{ + XEvent ev; + + if(!sel) + return; + if(isprotodel(sel)) + { + ev.type = ClientMessage; + ev.xclient.window = sel->win; + ev.xclient.message_type = wmatom[WMProtocols]; + ev.xclient.format = 32; + ev.xclient.data.l[0] = wmatom[WMDelete]; + ev.xclient.data.l[1] = CurrentTime; + XSendEvent(sel->display, sel->win, False, NoEventMask, &ev); + } + else + XKillClient(sel->display, sel->win); +} + +static Bool +loadprops(Client * c, int ntags) +{ + int i; + Bool result = False; + + if(gettextprop(c->display, c->win, jdwmprops, prop, sizeof(prop))) + { + for(i = 0; i < ntags && i < ssizeof(prop) - 1 && prop[i] != '\0'; i++) + if((c->tags[i] = prop[i] == '1')) + result = True; + if(i < ssizeof(prop) - 1 && prop[i] != '\0') + c->isfloating = prop[i] == '1'; + } + return result; +} + +void +manage(Display * disp, DC *drawcontext, Window w, XWindowAttributes * wa, jdwm_config *jdwmconf) +{ + int i; + Client *c, *t = NULL; + Window trans; + Status rettrans; + XWindowChanges wc; + + c = p_new(Client, 1); + c->tags = p_new(Bool, jdwmconf->ntags); + c->win = w; + c->ftview = True; + c->x = c->rw = wa->x; + c->y = c->ry = wa->y; + c->w = c->rw = wa->width; + c->h = c->rh = wa->height; + c->oldborder = wa->border_width; + c->display = disp; + if(c->w == sw && c->h == sh) + { + c->x = sx; + c->y = sy; + c->border = wa->border_width; + } + else + { + if(c->x + c->w + 2 * c->border > wax + waw) + c->x = c->rx = wax + waw - c->w - 2 * c->border; + if(c->y + c->h + 2 * c->border > way + wah) + c->y = c->ry = way + wah - c->h - 2 * c->border; + if(c->x < wax) + c->x = c->rx = wax; + if(c->y < way) + c->y = c->ry = way; + c->border = jdwmconf->borderpx; + } + wc.border_width = c->border; + XConfigureWindow(disp, w, CWBorderWidth, &wc); + XSetWindowBorder(disp, w, drawcontext->norm[ColBorder]); + configure(c); /* propagates border_width, if size doesn't change */ + updatesizehints(c); + XSelectInput(disp, w, StructureNotifyMask | PropertyChangeMask | EnterWindowMask); + grabbuttons(c, False, jdwmconf->modkey, jdwmconf->numlockmask); + updatetitle(c); + if((rettrans = XGetTransientForHint(disp, w, &trans) == Success)) + for(t = clients; t && t->win != trans; t = t->next); + if(t) + for(i = 0; i < jdwmconf->ntags; i++) + c->tags[i] = t->tags[i]; + if(!loadprops(c, jdwmconf->ntags)) + applyrules(c, jdwmconf); + if(!c->isfloating) + c->isfloating = (rettrans == Success) || c->isfixed; + saveprops(c, jdwmconf->ntags); + attach(c); + attachstack(c); + XMoveResizeWindow(disp, c->win, c->x, c->y, c->w, c->h); /* some windows require this */ + ban(c); + arrange(disp, jdwmconf); +} + +void +resize(Client * c, int x, int y, int w, int h, Bool sizehints) +{ + double dx, dy, max, min, ratio; + XWindowChanges wc; + + if(sizehints) + { + if(c->minay > 0 && c->maxay > 0 && (h - c->baseh) > 0 && (w - c->basew) > 0) + { + dx = (double) (w - c->basew); + dy = (double) (h - c->baseh); + min = (double) (c->minax) / (double) (c->minay); + max = (double) (c->maxax) / (double) (c->maxay); + ratio = dx / dy; + if(max > 0 && min > 0 && ratio > 0) + { + if(ratio < min) + { + dy = (dx * min + dy) / (min * min + 1); + dx = dy * min; + w = (int) dx + c->basew; + h = (int) dy + c->baseh; + } + else if(ratio > max) + { + dy = (dx * min + dy) / (max * max + 1); + dx = dy * min; + w = (int) dx + c->basew; + h = (int) dy + c->baseh; + } + } + } + if(c->minw && w < c->minw) + w = c->minw; + if(c->minh && h < c->minh) + h = c->minh; + if(c->maxw && w > c->maxw) + w = c->maxw; + if(c->maxh && h > c->maxh) + h = c->maxh; + if(c->incw) + w -= (w - c->basew) % c->incw; + if(c->inch) + h -= (h - c->baseh) % c->inch; + } + if(w <= 0 || h <= 0) + return; + /* offscreen appearance fixes */ + if(x > sw) + x = sw - w - 2 * c->border; + if(y > sh) + y = sh - h - 2 * c->border; + if(x + w + 2 * c->border < sx) + x = sx; + if(y + h + 2 * c->border < sy) + y = sy; + if(c->x != x || c->y != y || c->w != w || c->h != h) + { + c->x = wc.x = x; + c->y = wc.y = y; + c->w = wc.width = w; + c->h = wc.height = h; + wc.border_width = c->border; + XConfigureWindow(c->display, c->win, CWX | CWY | CWWidth | CWHeight | CWBorderWidth, &wc); + configure(c); + XSync(c->display, False); + } +} + +void +uicb_moveresize(Display *disp __attribute__ ((unused)), + jdwm_config *jdwmconf, + const char *arg) +{ + int x, y, w, h, nx, ny, nw, nh, ox, oy, ow, oh; + char xabs, yabs, wabs, habs; + int mx, my, dx, dy, nmx, nmy; + unsigned int dui; + Window dummy; + + if(!IS_ARRANGE(floating)) + if(!sel || !sel->isfloating || sel->isfixed || !arg) + return; + if(sscanf(arg, "%d%c %d%c %d%c %d%c", &x, &xabs, &y, &yabs, &w, &wabs, &h, &habs) != 8) + return; + nx = xabs == 'x' ? sel->x + x : x; + ny = yabs == 'y' ? sel->y + y : y; + nw = wabs == 'w' ? sel->w + w : w; + nh = habs == 'h' ? sel->h + h : h; + + ox = sel->x; + oy = sel->y; + ow = sel->w; + oh = sel->h; + + Bool xqp = XQueryPointer(sel->display, DefaultRootWindow(sel->display), &dummy, &dummy, &mx, &my, &dx, &dy, &dui); + resize(sel, nx, ny, nw, nh, True); + if (xqp && ox <= mx && (ox + ow) >= mx && oy <= my && (oy + oh) >= my) + { + nmx = mx-ox+sel->w-ow-1 < 0 ? 0 : mx-ox+sel->w-ow-1; + nmy = my-oy+sel->h-oh-1 < 0 ? 0 : my-oy+sel->h-oh-1; + XWarpPointer(sel->display, None, sel->win, 0, 0, 0, 0, nmx, nmy); + } +} + +void +saveprops(Client * c, int ntags) +{ + int i; + for(i = 0; i < ntags && i < ssizeof(prop) - 1; i++) + prop[i] = c->tags[i] ? '1' : '0'; + if(i < ssizeof(prop) - 1) + prop[i++] = c->isfloating ? '1' : '0'; + + prop[i] = '\0'; + XChangeProperty(c->display, c->win, jdwmprops, XA_STRING, 8, + PropModeReplace, (unsigned char *) prop, i); +} + +void +unban(Client * c) +{ + if(!c->isbanned) + return; + XMapWindow(c->display, c->win); + setclientstate(c, NormalState); + c->isbanned = False; +} + +void +unmanage(Client * c, DC *drawcontext, long state, jdwm_config *jdwmconf) +{ + XWindowChanges wc; + + wc.border_width = c->oldborder; + /* The server grab construct avoids race conditions. */ + XGrabServer(c->display); + XConfigureWindow(c->display, c->win, CWBorderWidth, &wc); /* restore border */ + detach(c); + detachstack(c); + if(sel == c) + focus(c->display, drawcontext, NULL, jdwmconf); + XUngrabButton(c->display, AnyButton, AnyModifier, c->win); + setclientstate(c, state); + XSync(c->display, False); + XSetErrorHandler(xerror); + XUngrabServer(c->display); + if(state != NormalState) + arrange(c->display, jdwmconf); + p_delete(&c->tags); + p_delete(&c); +} + +void +updatesizehints(Client * c) +{ + long msize; + XSizeHints size; + + if(!XGetWMNormalHints(c->display, 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 if(c->flags & PMinSize) + { + c->basew = size.min_width; + c->baseh = size.min_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 if(c->flags & PBaseSize) + { + c->minw = size.base_width; + c->minh = size.base_height; + } + else + c->minw = c->minh = 0; + + if(c->flags & PAspect) + { + c->minax = size.min_aspect.x; + c->maxax = size.max_aspect.x; + c->minay = size.min_aspect.y; + c->maxay = size.max_aspect.y; + } + else + c->minax = c->maxax = c->minay = c->maxay = 0; + + c->isfixed = (c->maxw && c->minw && c->maxh && c->minh + && c->maxw == c->minw && c->maxh == c->minh); +} + +void +setclienttrans(Client *c, double opacity, unsigned int current_opacity) +{ + unsigned int real_opacity = 0xffffffff; + + if(opacity >= 0 && opacity <= 100) + { + real_opacity = ((opacity / 100.0) * 0xffffffff) + current_opacity; + XChangeProperty(c->display, c->win, + XInternAtom(c->display, "_NET_WM_WINDOW_OPACITY", False), + XA_CARDINAL, 32, PropModeReplace, (unsigned char *) &real_opacity, 1L); + } + else + XDeleteProperty(c->display, c->win, XInternAtom(c->display, "_NET_WM_WINDOW_OPACITY", False)); + + XSync(c->display, False); +} + +void +uicb_settrans(Display *disp __attribute__ ((unused)), + jdwm_config *jdwmconf __attribute__ ((unused)), + const char *arg) +{ + unsigned int current_opacity = 0; + double delta = 100.0; + unsigned char *data; + Atom actual; + int format; + unsigned long n, left; + int set_prop = 0; + + if(!sel) + return; + + if(arg && sscanf(arg, "%lf", &delta)) + { + if(arg[0] == '+' || arg[0] == '-') + { + XGetWindowProperty(sel->display, sel->win, XInternAtom(sel->display, "_NET_WM_WINDOW_OPACITY", False), + 0L, 1L, False, XA_CARDINAL, &actual, &format, &n, &left, + (unsigned char **) &data); + if(data) + { + memcpy(¤t_opacity, data, sizeof(double)); + XFree((void *) data); + delta += ((current_opacity * 100.0) / 0xffffffff); + } + else + { + delta += 100.0; + set_prop = 1; + } + + } + } + + if(delta <= 0.0) + delta = 0.0; + else if(delta > 100.0) + { + delta = 100.0; + set_prop = 1; + } + + if(delta == 100.0 && !set_prop) + setclienttrans(sel, -1, 0); + else + setclienttrans(sel, delta, current_opacity); +} diff --git a/client.h b/client.h new file mode 100644 index 00000000..6d930ee3 --- /dev/null +++ b/client.h @@ -0,0 +1,49 @@ +/* See LICENSE file for copyright and license details. */ + +#ifndef JDWM_CLIENT_H +#define JDWM_CLIENT_H + +/* mask shorthands, used in event.c and client.c */ +#define BUTTONMASK (ButtonPressMask | ButtonReleaseMask) + +#include "draw.h" + +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, maxax, minay, maxay; + int unmapped; + long flags; + int border, oldborder; + Bool isbanned, isfixed, ismax, isfloating, wasfloating; + Bool *tags; + Client *next; + Client *prev; + Client *snext; + Window win; + Display * display; + Bool ftview; /* first time viewed on new layout */ +}; + +void attach(Client *); /* attaches c to global client list */ +void ban(Client *); /* bans c */ +void configure(Client *); /* send synthetic configure event */ +void detach(Client *); /* detaches c from global client list */ +void focus(Display *, DC *, Client *, jdwm_config *); /* focus c if visible && !NULL, or focus top visible */ +void manage(Display *, DC *, Window, XWindowAttributes *, jdwm_config *); /* manage new client */ +void resize(Client *, int, int, int, int, Bool); /* resize with given coordinates c */ +void unban(Client *); /* unbans c */ +void unmanage(Client *, DC *, long, jdwm_config *); /* unmanage c */ +void updatesizehints(Client *); /* update the size hint variables of c */ +void updatetitle(Client *); /* update the name of c */ +void saveprops(Client * c, int); /* saves client properties */ +void uicb_killclient(Display *, jdwm_config *, const char *); /* kill client */ +void uicb_moveresize(Display *, jdwm_config *, const char *); /* move and resize window */ +void uicb_settrans(Display *, jdwm_config *, const char *); +void setclienttrans(Client *c, double, unsigned int); + +#endif diff --git a/config.c b/config.c new file mode 100644 index 00000000..e8f675c2 --- /dev/null +++ b/config.c @@ -0,0 +1,405 @@ +/* See LICENSE file for copyright and license details. */ + +/** + * \defgroup ui_callback + */ + +#include +#include +#include +#include + +#include "jdwm.h" +#include "util.h" +#include "layout.h" +#include "tag.h" +#include "layouts/tile.h" +#include "layouts/grid.h" +#include "layouts/spiral.h" +#include "layouts/floating.h" + +/* static */ +static void initfont(const char *, Display *, DC *); +static unsigned long initcolor(const char *colstr, Display *, int); +static unsigned int get_numlockmask(Display *); + +/** Main configuration object for parsing*/ +config_t jdwmlibconf; + +/** Current bar position */ +int bpos; + +/** Link a name to a function */ +typedef struct +{ + const char *name; + void *func; +} NameFuncLink; + +/** Link a name to a key symbol */ +typedef struct +{ + const char *name; + KeySym keysym; +} KeyMod; + +/** List of keyname and corresponding X11 mask codes */ +static const KeyMod KeyModList[] = { {"Shift", ShiftMask}, +{"Lock", LockMask}, +{"Control", ControlMask}, +{"Mod1", Mod1Mask}, +{"Mod2", Mod2Mask}, +{"Mod3", Mod3Mask}, +{"Mod4", Mod4Mask}, +{"Mod5", Mod5Mask}, +{"None", 0} +}; + +/** List of available layouts and link between name and functions */ +static const NameFuncLink LayoutsList[] = { {"tile", tile}, +{"tileleft", tileleft}, +{"floating", floating}, +{"grid", grid}, +{"spiral", spiral}, +{"dwindle", dwindle}, +{"bstack", bstack}, +{"bstackportrait", bstackportrait}, +{NULL, NULL} +}; + +/** List of available UI bindable callbacks and functions */ +static const NameFuncLink KeyfuncList[] = { + /* util.c */ + {"spawn", spawn}, + /* client.c */ + {"killclient", uicb_killclient}, + {"moveresize", uicb_moveresize}, + {"settrans", uicb_settrans}, + /* config.c */ + {"reload", uicb_reload}, + /* tag.c */ + {"tag", uicb_tag}, + {"togglefloating", uicb_togglefloating}, + {"toggleview", uicb_toggleview}, + {"toggletag", uicb_toggletag}, + {"view", uicb_view}, + {"viewprevtags", uicb_viewprevtags}, + /* layout.c */ + {"setlayout", uicb_setlayout}, + {"togglebar", uicb_togglebar}, + {"focusnext", uicb_focusnext}, + {"focusprev", uicb_focusprev}, + {"togglemax", uicb_togglemax}, + {"toggleverticalmax", uicb_toggleverticalmax}, + {"togglehorizontalmax", uicb_togglehorizontalmax}, + {"zoom", uicb_zoom}, + /* layouts/tile.c */ + {"setmwfact", uicb_setmwfact}, + {"incnmaster", uicb_incnmaster}, + /* jdwm.c */ + {"quit", uicb_quit}, + {NULL, NULL} +}; + +/** Lookup for a key mask from its name + * \param keyname Key name + * \return Key mask or 0 if not found + */ +static KeySym +key_mask_lookup(const char *keyname) +{ + int i; + + if(keyname) + for(i = 0; KeyModList[i].name; i++) + if(!strcmp(keyname, KeyModList[i].name)) + return KeyModList[i].keysym; + + return 0; +}; + +/** Lookup for a function pointer from its name + * in the given NameFuncLink list + * \param funcname Function name + * \param list Function and name link list + * \return function pointer + */ +static void * +name_func_lookup(const char *funcname, const NameFuncLink * list) +{ + int i; + + if(funcname && list) + for(i = 0; list[i].name; i++) + if(!strcmp(funcname, list[i].name)) + return list[i].func; + + return NULL; +} + +/** \todo remove screen */ +extern int screen; +/** \todo remove dc */ +extern DC dc; + +/** Reload configuration file + * \param disp Display ref + * \param arg unused + * \ingroup ui_callback + * \todo not really working nor safe I guess + */ +void +uicb_reload(Display *disp, jdwm_config *jdwmconf, const char *arg __attribute__ ((unused))) +{ + config_destroy(&jdwmlibconf); + p_delete(&jdwmconf->rules); + p_delete(&jdwmconf->tags); + p_delete(&jdwmconf->layouts); + parse_config(disp, screen, &dc, jdwmconf); +} + +static void +set_default_config(jdwm_config *jdwmconf) +{ + strcpy(jdwmconf->statustext, "jdwm-" VERSION); +} + +/** Parse configuration file and initialize some stuff + * \param disp Display ref + * \param scr Screen number + * \param drawcontext Draw context + */ +void +parse_config(Display * disp, int scr, DC * drawcontext, jdwm_config *jdwmconf) +{ + config_setting_t *conftags; + config_setting_t *conflayouts, *confsublayouts; + config_setting_t *confrules, *confsubrules; + config_setting_t *confkeys, *confsubkeys, *confkeysmasks, *confkeymaskelem; + int i, j; + const char *tmp, *homedir; + char *confpath; + + set_default_config(jdwmconf); + + homedir = getenv("HOME"); + confpath = p_new(char, strlen(homedir) + strlen(JDWM_CONFIG_FILE) + 2); + strcpy(confpath, homedir); + strcat(confpath, "/"); + strcat(confpath, JDWM_CONFIG_FILE); + + config_init(&jdwmlibconf); + + if(config_read_file(&jdwmlibconf, confpath) == CONFIG_FALSE) + eprint("error parsing configuration file at line %d: %s\n", + config_error_line(&jdwmlibconf), config_error_text(&jdwmlibconf)); + + /* tags */ + conftags = config_lookup(&jdwmlibconf, "jdwm.tags"); + + if(!conftags) + eprint("tags not found in configuration file\n"); + + jdwmconf->ntags = config_setting_length(conftags); + jdwmconf->tags = p_new(const char *, jdwmconf->ntags); + for(i = 0; (tmp = config_setting_get_string_elem(conftags, i)); i++) + jdwmconf->tags[i] = tmp; + + /* layouts */ + conflayouts = config_lookup(&jdwmlibconf, "jdwm.layouts"); + + if(!conflayouts) + eprint("layouts not found in configuration file\n"); + + jdwmconf->nlayouts = config_setting_length(conflayouts); + jdwmconf->layouts = p_new(Layout, jdwmconf->nlayouts + 1); + for(i = 0; (confsublayouts = config_setting_get_elem(conflayouts, i)); i++) + { + jdwmconf->layouts[i].symbol = config_setting_get_string_elem(confsublayouts, 0); + jdwmconf->layouts[i].arrange = + name_func_lookup(config_setting_get_string_elem(confsublayouts, 1), LayoutsList); + if(!jdwmconf->layouts[i].arrange) + eprint("unknown layout in configuration file: %s", tmp); + } + jdwmconf->layouts[i].symbol = NULL; + jdwmconf->layouts[i].arrange = NULL; + + /** \todo put this in set_default_layout */ + jdwmconf->current_layout = jdwmconf->layouts; + + /* rules */ + confrules = config_lookup(&jdwmlibconf, "jdwm.rules"); + + if(!confrules) + eprint("rules not found in configuration file\n"); + + jdwmconf->nrules = config_setting_length(confrules); + jdwmconf->rules = p_new(Rule, jdwmconf->nrules); + for(i = 0; (confsubrules = config_setting_get_elem(confrules, i)); i++) + { + jdwmconf->rules[i].prop = config_setting_get_string(config_setting_get_member(confsubrules, "name")); + jdwmconf->rules[i].tags = config_setting_get_string(config_setting_get_member(confsubrules, "tags")); + if(jdwmconf->rules[i].tags && !strlen(jdwmconf->rules[i].tags)) + jdwmconf->rules[i].tags = NULL; + jdwmconf->rules[i].isfloating = + config_setting_get_bool(config_setting_get_member(confsubrules, "float")); + } + + /* modkey */ + jdwmconf->modkey = key_mask_lookup(config_lookup_string(&jdwmlibconf, "jdwm.modkey")); + + /* find numlock mask */ + jdwmconf->numlockmask = get_numlockmask(disp); + + /* keys */ + confkeys = config_lookup(&jdwmlibconf, "jdwm.keys"); + + if(!confkeys) + eprint("keys not found in configuration file\n"); + + jdwmconf->nkeys = config_setting_length(confkeys); + jdwmconf->keys = p_new(Key, jdwmconf->nkeys); + + for(i = 0; (confsubkeys = config_setting_get_elem(confkeys, i)); i++) + { + confkeysmasks = config_setting_get_elem(confsubkeys, 0); + for(j = 0; (confkeymaskelem = config_setting_get_elem(confkeysmasks, j)); j++) + jdwmconf->keys[i].mod |= key_mask_lookup(config_setting_get_string(confkeymaskelem)); + jdwmconf->keys[i].keysym = XStringToKeysym(config_setting_get_string_elem(confsubkeys, 1)); + jdwmconf->keys[i].func = + name_func_lookup(config_setting_get_string_elem(confsubkeys, 2), KeyfuncList); + jdwmconf->keys[i].arg = config_setting_get_string_elem(confsubkeys, 3); + } + + /* barpos */ + tmp = config_lookup_string(&jdwmlibconf, "jdwm.barpos"); + + if(!strncmp(tmp, "BarTop", 6)) + jdwmconf->bpos = BarTop; + else if(!strncmp(tmp, "BarBot", 6)) + jdwmconf->bpos = BarBot; + else if(!strncmp(tmp, "BarOff", 6)) + jdwmconf->bpos = BarOff; + + bpos = jdwmconf->bpos; + + /* borderpx */ + jdwmconf->borderpx = config_lookup_int(&jdwmlibconf, "jdwm.borderpx"); + + /* opacity */ + jdwmconf->opacity_unfocused = config_lookup_int(&jdwmlibconf, "jdwm.opacity_unfocused"); + if(jdwmconf->opacity_unfocused >= 100) + jdwmconf->opacity_unfocused = -1; + + /* snap */ + jdwmconf->snap = config_lookup_int(&jdwmlibconf, "jdwm.snap"); + + /* nmaster */ + jdwmconf->nmaster = config_lookup_int(&jdwmlibconf, "jdwm.nmaster"); + + /* mwfact */ + jdwmconf->mwfact = config_lookup_float(&jdwmlibconf, "jdwm.mwfact"); + + /* font */ + initfont(config_lookup_string(&jdwmlibconf, "jdwm.font"), disp, drawcontext); + + /* colors */ + dc.norm[ColBorder] = initcolor(config_lookup_string(&jdwmlibconf, "jdwm.normal_border_color"), + disp, scr); + dc.norm[ColBG] = initcolor(config_lookup_string(&jdwmlibconf, "jdwm.normal_bg_color"), disp, scr); + dc.norm[ColFG] = initcolor(config_lookup_string(&jdwmlibconf, "jdwm.normal_fg_color"), disp, scr); + dc.sel[ColBorder] = initcolor(config_lookup_string(&jdwmlibconf, "jdwm.focus_border_color"), + disp, scr); + dc.sel[ColBG] = initcolor(config_lookup_string(&jdwmlibconf, "jdwm.focus_bg_color"), disp, scr); + dc.sel[ColFG] = initcolor(config_lookup_string(&jdwmlibconf, "jdwm.focus_fg_color"), disp, scr); + + p_delete(&confpath); +} + +/** Initialize font from X side and store in draw context + * \param fontstr Font name + * \param disp Display ref + * \param drawcontext Draw context + */ +static void +initfont(const char *fontstr, Display * disp, DC * drawcontext) +{ + char *def, **missing; + int i, n; + + missing = NULL; + if(drawcontext->font.set) + XFreeFontSet(disp, drawcontext->font.set); + drawcontext->font.set = XCreateFontSet(disp, fontstr, &missing, &n, &def); + if(missing) + { + while(n--) + fprintf(stderr, "jdwm: missing fontset: %s\n", missing[n]); + XFreeStringList(missing); + } + if(drawcontext->font.set) + { + XFontSetExtents *font_extents; + XFontStruct **xfonts; + char **font_names; + drawcontext->font.ascent = drawcontext->font.descent = 0; + font_extents = XExtentsOfFontSet(drawcontext->font.set); + n = XFontsOfFontSet(drawcontext->font.set, &xfonts, &font_names); + for(i = 0, drawcontext->font.ascent = 0, drawcontext->font.descent = 0; i < n; i++) + { + if(drawcontext->font.ascent < (*xfonts)->ascent) + drawcontext->font.ascent = (*xfonts)->ascent; + if(drawcontext->font.descent < (*xfonts)->descent) + drawcontext->font.descent = (*xfonts)->descent; + xfonts++; + } + } + else + { + if(drawcontext->font.xfont) + XFreeFont(disp, drawcontext->font.xfont); + drawcontext->font.xfont = NULL; + if(!(drawcontext->font.xfont = XLoadQueryFont(disp, fontstr))) + eprint("error, cannot load font: '%s'\n", fontstr); + drawcontext->font.ascent = drawcontext->font.xfont->ascent; + drawcontext->font.descent = drawcontext->font.xfont->descent; + } + drawcontext->font.height = drawcontext->font.ascent + drawcontext->font.descent; +} + +static unsigned int +get_numlockmask(Display *disp) +{ + XModifierKeymap *modmap; + unsigned int mask = 0; + int i, j; + + modmap = XGetModifierMapping(disp); + for(i = 0; i < 8; i++) + for(j = 0; j < modmap->max_keypermod; j++) + { + if(modmap->modifiermap[i * modmap->max_keypermod + j] + == XKeysymToKeycode(disp, XK_Num_Lock)) + mask = (1 << i); + } + + XFreeModifiermap(modmap); + + return mask; +} + +/** Initialize color from X side + * \param colorstr Color code + * \param disp Display ref + * \param scr Screen number + * \return XColor pixel + */ +static unsigned long +initcolor(const char *colstr, Display * disp, int scr) +{ + Colormap cmap = DefaultColormap(disp, scr); + XColor color; + if(!XAllocNamedColor(disp, cmap, colstr, &color, &color)) + eprint("error, cannot allocate color '%s'\n", colstr); + return color.pixel; +} diff --git a/config.h b/config.h new file mode 100644 index 00000000..1d4efbb8 --- /dev/null +++ b/config.h @@ -0,0 +1,101 @@ +/* See LICENSE file for copyright and license details. */ + +#ifndef JDWM_CONFIG_H +#define JDWM_CONFIG_H + +#define JDWM_CONFIG_FILE ".jdwmrc" + +#include + +/** Bar possible position */ +enum +{ BarTop, BarBot, BarOff }; + +enum +{ ColBorder, ColFG, ColBG, ColLast }; /* color */ + +typedef struct +{ + int x, y, w, h; + unsigned long norm[ColLast]; + unsigned long sel[ColLast]; + Drawable drawable; + GC gc; + struct + { + int ascent; + int descent; + int height; + XFontSet set; + XFontStruct *xfont; + } font; +} DC; + +typedef struct +{ + const char *prop; + const char *tags; + Bool isfloating; +} Rule; + +typedef struct jdwm_config jdwm_config; + +typedef struct +{ + const char *symbol; + void (*arrange) (Display *, jdwm_config *); +} Layout; + +typedef struct +{ + unsigned long mod; + KeySym keysym; + void (*func) (Display *, jdwm_config *, const char *); + const char *arg; +} Key; + +/** Main configuration structure */ +struct jdwm_config +{ + /** Tag list */ + const char **tags; + /** Number of tags in **tags */ + int ntags; + /** Layout list */ + Layout *layouts; + /** Number of layouts in *layouts */ + int nlayouts; + /** Rules list */ + Rule *rules; + /** Number of rules in *rules */ + int nrules; + /** Keys binding list */ + Key *keys; + /** Number of keys binding in *keys */ + int nkeys; + /** Default modkey */ + KeySym modkey; + /** Numlock mask */ + unsigned int numlockmask; + /** Bar position */ + int bpos; + /** Border size */ + int borderpx; + /** Master width factor */ + double mwfact; + /** Number of pixels to snap windows */ + int snap; + /** Number of master windows */ + int nmaster; + /** Transparency of unfocused clients */ + int opacity_unfocused; + /** Text displayed in bar */ + char statustext[256]; + /** Current layout */ + Layout * current_layout; +}; + +void parse_config(Display *, int, DC *, jdwm_config *); /* parse configuration file */ +void uicb_reload(Display *, jdwm_config *, const char *); /* reload configuration file */ + +#endif diff --git a/config.mk b/config.mk new file mode 100644 index 00000000..a057c386 --- /dev/null +++ b/config.mk @@ -0,0 +1,25 @@ +# jdwm version +VERSION = 0.0 + +# Customize below to fit your system + +# additional layouts beside floating +LAYOUTS = layouts/tile.c layouts/grid.c layouts/spiral.c layouts/floating.c + +# paths +PREFIX = /usr/local +MANPREFIX = ${PREFIX}/share/man + +X11INC = /usr/include/X11 +X11LIB = /usr/lib/X11 + +# includes and libs +INCS = -I. -I/usr/include -I${X11INC} `pkg-config --cflags libconfig` +LIBS = -L/usr/lib -lc -L${X11LIB} -lX11 `pkg-config --libs libconfig` + +# flags +CFLAGS = -fgnu89-inline -std=gnu99 -ggdb3 -pipe -Wall -Wextra -W -Wchar-subscripts -Wundef -Wshadow -Wcast-align -Wwrite-strings -Wsign-compare -Wunused -Wuninitialized -Winit-self -Wpointer-arith -Wredundant-decls -Wno-format-zero-length -Wmissing-prototypes -Wmissing-format-attribute -Wmissing-noreturn -O2 ${INCS} -DVERSION=\"${VERSION}\" +LDFLAGS = -ggdb3 ${LIBS} + +# compiler and linker +CC = cc diff --git a/draw.c b/draw.c new file mode 100644 index 00000000..b41cee67 --- /dev/null +++ b/draw.c @@ -0,0 +1,160 @@ +/* See LICENSE file for copyright and license details. */ + +#include + +#include "layout.h" + +extern int sw; /* screen geometry */ +extern int bh, blw; /* bar height, bar layout label width */ +extern Window barwin; +extern DC dc; /* global draw context */ +extern Client *clients, *sel, *stack; /* global client list and stack */ +extern Bool *seltags; + +/* static */ + +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(Display *disp, const char *text, unsigned long col[ColLast]) +{ + int x, y, w, h; + static char buf[256]; + unsigned int len, olen; + XRectangle r = { dc.x, dc.y, dc.w, dc.h }; + + XSetForeground(disp, dc.gc, col[ColBG]); + XFillRectangles(disp, 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 */ + XSetForeground(disp, dc.gc, col[ColFG]); + if(dc.font.set) + XmbDrawString(disp, dc.drawable, dc.font.set, dc.gc, x, y, buf, len); + else + XDrawString(disp, dc.drawable, dc.gc, x, y, buf, len); +} + +static void +drawsquare(Bool filled, Bool empty, unsigned long col[ColLast], Display *disp) +{ + int x; + XGCValues gcv; + XRectangle r = { dc.x, dc.y, dc.w, dc.h }; + + gcv.foreground = col[ColFG]; + XChangeGC(disp, dc.gc, GCForeground, &gcv); + x = (dc.font.ascent + dc.font.descent + 2) / 4; + r.x = dc.x + 1; + r.y = dc.y + 1; + if(filled) + { + r.width = r.height = x + 1; + XFillRectangles(disp, dc.drawable, dc.gc, &r, 1); + } + else if(empty) + { + r.width = r.height = x; + XDrawRectangles(disp, dc.drawable, dc.gc, &r, 1); + } +} + +static Bool +isoccupied(unsigned int t) +{ + Client *c; + + for(c = clients; c; c = c->next) + if(c->tags[t]) + return True; + return False; +} + + +/* extern */ + +void +drawstatus(Display *disp, jdwm_config * jdwmconf) +{ + int x, i; + dc.x = dc.y = 0; + for(i = 0; i < jdwmconf->ntags; i++) + { + dc.w = textw(jdwmconf->tags[i]); + if(seltags[i]) + { + drawtext(disp, jdwmconf->tags[i], dc.sel); + drawsquare(sel && sel->tags[i], isoccupied(i), dc.sel, disp); + } + else + { + drawtext(disp, jdwmconf->tags[i], dc.norm); + drawsquare(sel && sel->tags[i], isoccupied(i), dc.norm, disp); + } + dc.x += dc.w; + } + dc.w = blw; + drawtext(disp, jdwmconf->current_layout->symbol, dc.norm); + x = dc.x + dc.w; + dc.w = textw(jdwmconf->statustext); + dc.x = sw - dc.w; + if(dc.x < x) + { + dc.x = x; + dc.w = sw - x; + } + drawtext(disp, jdwmconf->statustext, dc.norm); + if((dc.w = dc.x - x) > bh) + { + dc.x = x; + if(sel) + { + drawtext(disp, sel->name, dc.sel); + drawsquare(sel->ismax, sel->isfloating, dc.sel, disp); + } + else + drawtext(disp, NULL, dc.norm); + } + XCopyArea(disp, dc.drawable, barwin, dc.gc, 0, 0, sw, bh, 0, 0); + XSync(disp, False); +} + + +inline unsigned int +textw(const char *text) +{ + return textnw(text, strlen(text)) + dc.font.height; +} diff --git a/draw.h b/draw.h new file mode 100644 index 00000000..25abcab4 --- /dev/null +++ b/draw.h @@ -0,0 +1,11 @@ +/* See LICENSE file for copyright and license details. */ + +#ifndef JDWM_DRAW_H +#define JDWM_DRAW_H + +#include "config.h" + +void drawstatus(Display *, jdwm_config *); /* draw the bar */ +inline unsigned int textw(const char *text); /* return the width of text in px */ + +#endif diff --git a/event.c b/event.c new file mode 100644 index 00000000..7c6cf457 --- /dev/null +++ b/event.c @@ -0,0 +1,412 @@ +/* See LICENSE file for copyright and license details. */ +#include +#include +#include +#include + +#include "jdwm.h" +#include "util.h" +#include "event.h" +#include "layout.h" +#include "tag.h" +#include "layouts/tile.h" +#include "layouts/floating.h" + +/* extern */ +extern int screen, sw, sh; /* screen geometry */ +extern int wax, way, wah, waw; /* windowarea geometry */ +extern int bh, blw; /* bar height, bar layout label width */ +extern Window barwin; +extern DC dc; /* global draw context */ +extern Cursor cursor[CurLast]; +extern Client *clients, *sel; /* global client list */ +extern Bool selscreen; +extern Atom netatom[NetLast]; +void (*handler[LASTEvent]) (XEvent *, jdwm_config *); /* event handler */ + +#define CLEANMASK(mask) (mask & ~(jdwmconf->numlockmask | LockMask)) +#define MOUSEMASK (BUTTONMASK | PointerMotionMask) + +static Client * +getclient(Window w) +{ + Client *c; + + for(c = clients; c && c->win != w; c = c->next); + return c; +} + +static void +movemouse(Client * c, jdwm_config *jdwmconf) +{ + int x1, y1, ocx, ocy, di, nx, ny; + unsigned int dui; + Window dummy; + XEvent ev; + + ocx = nx = c->x; + ocy = ny = c->y; + if(XGrabPointer(c->display, DefaultRootWindow(c->display), False, MOUSEMASK, GrabModeAsync, GrabModeAsync, + None, cursor[CurMove], CurrentTime) != GrabSuccess) + return; + XQueryPointer(c->display, DefaultRootWindow(c->display), &dummy, &dummy, &x1, &y1, &di, &di, &dui); + for(;;) + { + XMaskEvent(c->display, MOUSEMASK | ExposureMask | SubstructureRedirectMask, &ev); + switch (ev.type) + { + case ButtonRelease: + XUngrabPointer(c->display, CurrentTime); + return; + case ConfigureRequest: + case Expose: + case MapRequest: + handler[ev.type] (&ev, jdwmconf); + break; + case MotionNotify: + XSync(c->display, False); + nx = ocx + (ev.xmotion.x - x1); + ny = ocy + (ev.xmotion.y - y1); + if(abs(wax + nx) < jdwmconf->snap) + nx = wax; + else if(abs((wax + waw) - (nx + c->w + 2 * c->border)) < jdwmconf->snap) + nx = wax + waw - c->w - 2 * c->border; + if(abs(way - ny) < jdwmconf->snap) + ny = way; + else if(abs((way + wah) - (ny + c->h + 2 * c->border)) < jdwmconf->snap) + ny = way + wah - c->h - 2 * c->border; + resize(c, nx, ny, c->w, c->h, False); + break; + } + } +} + +static void +resizemouse(Client * c, jdwm_config *jdwmconf) +{ + int ocx, ocy; + int nw, nh; + XEvent ev; + + ocx = c->x; + ocy = c->y; + if(XGrabPointer(c->display, DefaultRootWindow(c->display), False, MOUSEMASK, GrabModeAsync, GrabModeAsync, + None, cursor[CurResize], CurrentTime) != GrabSuccess) + return; + c->ismax = False; + XWarpPointer(c->display, None, c->win, 0, 0, 0, 0, c->w + c->border - 1, c->h + c->border - 1); + for(;;) + { + XMaskEvent(c->display, MOUSEMASK | ExposureMask | SubstructureRedirectMask, &ev); + switch (ev.type) + { + case ButtonRelease: + XWarpPointer(c->display, None, c->win, 0, 0, 0, 0, c->w + c->border - 1, c->h + c->border - 1); + XUngrabPointer(c->display, CurrentTime); + while(XCheckMaskEvent(c->display, EnterWindowMask, &ev)); + return; + case ConfigureRequest: + case Expose: + case MapRequest: + handler[ev.type] (&ev, jdwmconf); + break; + case MotionNotify: + XSync(c->display, False); + if((nw = ev.xmotion.x - ocx - 2 * c->border + 1) <= 0) + nw = 1; + if((nh = ev.xmotion.y - ocy - 2 * c->border + 1) <= 0) + nh = 1; + resize(c, c->x, c->y, nw, nh, True); + break; + } + } +} + +static void +buttonpress(XEvent * e, jdwm_config *jdwmconf) +{ + int i, x; + Client *c; + XButtonPressedEvent *ev = &e->xbutton; + + if(barwin == ev->window) + { + x = 0; + for(i = 0; i < jdwmconf->ntags; i++) + { + x += textw(jdwmconf->tags[i]); + if(ev->x < x) + { + if(ev->button == Button1) + { + if(ev->state & jdwmconf->modkey) + uicb_tag(e->xany.display, jdwmconf, jdwmconf->tags[i]); + else + uicb_view(e->xany.display, jdwmconf, jdwmconf->tags[i]); + } + else if(ev->button == Button3) + { + if(ev->state & jdwmconf->modkey) + uicb_toggletag(e->xany.display, jdwmconf, jdwmconf->tags[i]); + else + uicb_toggleview(e->xany.display, jdwmconf, jdwmconf->tags[i]); + } + return; + } + } + if((ev->x < x + blw) && ev->button == Button1) + uicb_setlayout(e->xany.display, jdwmconf, NULL); + } + else if((c = getclient(ev->window))) + { + focus(c->display, &dc, c, jdwmconf); + if(CLEANMASK(ev->state) != jdwmconf->modkey) + return; + if(ev->button == Button1 && (IS_ARRANGE(floating) || c->isfloating)) + { + restack(e->xany.display, jdwmconf); + movemouse(c, jdwmconf); + } + else if(ev->button == Button2) + uicb_zoom(e->xany.display, jdwmconf, NULL); + else if(ev->button == Button3 && (IS_ARRANGE(floating) || c->isfloating) && !c->isfixed) + { + restack(e->xany.display, jdwmconf); + resizemouse(c, jdwmconf); + } + } + else if(DefaultRootWindow(e->xany.display) == ev->window && !sel) + { + if(ev->button == Button4) + uicb_tag_viewnext(e->xany.display, jdwmconf, NULL); + else if(ev->button == Button5) + uicb_tag_viewprev(e->xany.display, jdwmconf, NULL); + } +} + +static void +configurerequest(XEvent * e, jdwm_config *jdwmconf __attribute__ ((unused))) +{ + Client *c; + XConfigureRequestEvent *ev = &e->xconfigurerequest; + XWindowChanges wc; + + if((c = getclient(ev->window))) + { + c->ismax = False; + if(ev->value_mask & CWBorderWidth) + c->border = ev->border_width; + if(c->isfixed || c->isfloating || IS_ARRANGE(floating)) + { + 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((c->x + c->w) > sw && c->isfloating) + c->x = sw / 2 - c->w / 2; /* center in x direction */ + if((c->y + c->h) > sh && c->isfloating) + c->y = sh / 2 - c->h / 2; /* center in y direction */ + if((ev->value_mask & (CWX | CWY)) && !(ev->value_mask & (CWWidth | CWHeight))) + configure(c); + if(isvisible(c, jdwmconf->ntags)) + XMoveResizeWindow(e->xany.display, c->win, c->x, c->y, c->w, c->h); + } + else + configure(c); + } + 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(e->xany.display, ev->window, ev->value_mask, &wc); + } + XSync(e->xany.display, False); +} + +static void +configurenotify(XEvent * e, jdwm_config *jdwmconf) +{ + XConfigureEvent *ev = &e->xconfigure; + + if(ev->window == DefaultRootWindow(e->xany.display) && (ev->width != sw || ev->height != sh)) + { + sw = ev->width; + sh = ev->height; + XFreePixmap(e->xany.display, dc.drawable); + dc.drawable = XCreatePixmap(e->xany.display, DefaultRootWindow(e->xany.display), sw, bh, DefaultDepth(e->xany.display, screen)); + XResizeWindow(e->xany.display, barwin, sw, bh); + updatebarpos(e->xany.display); + arrange(e->xany.display, jdwmconf); + } +} + +static void +destroynotify(XEvent * e, jdwm_config *jdwmconf) +{ + Client *c; + XDestroyWindowEvent *ev = &e->xdestroywindow; + + if((c = getclient(ev->window))) + unmanage(c, &dc, WithdrawnState, jdwmconf); +} + +static void +enternotify(XEvent * e, jdwm_config *jdwmconf) +{ + Client *c; + XCrossingEvent *ev = &e->xcrossing; + + if(ev->mode != NotifyNormal || ev->detail == NotifyInferior) + return; + if((c = getclient(ev->window))) + focus(c->display, &dc, c, jdwmconf); + else if(ev->window == DefaultRootWindow(e->xany.display)) + { + selscreen = True; + focus(e->xany.display, &dc, NULL, jdwmconf); + } +} + +static void +expose(XEvent * e, jdwm_config *jdwmconf) +{ + XExposeEvent *ev = &e->xexpose; + + if(!ev->count && barwin == ev->window) + drawstatus(e->xany.display, jdwmconf); +} + +static void +keypress(XEvent * e, jdwm_config *jdwmconf) +{ + int i; + KeySym keysym; + XKeyEvent *ev = &e->xkey; + + keysym = XKeycodeToKeysym(e->xany.display, (KeyCode) ev->keycode, 0); + for(i = 0; i < jdwmconf->nkeys; i++) + if(keysym == jdwmconf->keys[i].keysym + && CLEANMASK(jdwmconf->keys[i].mod) == CLEANMASK(ev->state) && jdwmconf->keys[i].func) + jdwmconf->keys[i].func(e->xany.display, jdwmconf, jdwmconf->keys[i].arg); +} + +static void +leavenotify(XEvent * e, jdwm_config *jdwmconf) +{ + XCrossingEvent *ev = &e->xcrossing; + + if((ev->window == DefaultRootWindow(e->xany.display)) && !ev->same_screen) + { + selscreen = False; + focus(e->xany.display, &dc, NULL, jdwmconf); + } +} + +static void +mappingnotify(XEvent * e, jdwm_config *jdwmconf) +{ + XMappingEvent *ev = &e->xmapping; + + XRefreshKeyboardMapping(ev); + if(ev->request == MappingKeyboard) + grabkeys(e->xany.display, jdwmconf); +} + +static void +maprequest(XEvent * e, jdwm_config *jdwmconf) +{ + static XWindowAttributes wa; + XMapRequestEvent *ev = &e->xmaprequest; + + if(!XGetWindowAttributes(e->xany.display, ev->window, &wa)) + return; + if(wa.override_redirect) + return; + if(!getclient(ev->window)) + manage(e->xany.display, &dc, ev->window, &wa, jdwmconf); +} + +static void +propertynotify(XEvent * e, jdwm_config *jdwmconf) +{ + 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(e->xany.display, c->win, &trans); + if(!c->isfloating && (c->isfloating = (getclient(trans) != NULL))) + arrange(e->xany.display, jdwmconf); + 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(e->xany.display, jdwmconf); + } + } +} + +static void +unmapnotify(XEvent * e, jdwm_config *jdwmconf) +{ + Client *c; + XUnmapEvent *ev = &e->xunmap; + + if((c = getclient(ev->window)) && ev->event == DefaultRootWindow(e->xany.display) && (ev->send_event || !c->unmapped--)) + unmanage(c, &dc, WithdrawnState, jdwmconf); +} + +/* extern */ + +void (*handler[LASTEvent]) (XEvent *, jdwm_config *) = +{ +[ButtonPress] = buttonpress, + [ConfigureRequest] = configurerequest, + [ConfigureNotify] = configurenotify, + [DestroyNotify] = destroynotify, + [EnterNotify] = enternotify, + [LeaveNotify] = leavenotify, + [Expose] = expose, + [KeyPress] = keypress, + [MappingNotify] = mappingnotify, + [MapRequest] = maprequest,[PropertyNotify] = propertynotify,[UnmapNotify] = unmapnotify}; + +void +grabkeys(Display *disp, jdwm_config *jdwmconf) +{ + int i; + KeyCode code; + + XUngrabKey(disp, AnyKey, AnyModifier, DefaultRootWindow(disp)); + for(i = 0; i < jdwmconf->nkeys; i++) + { + code = XKeysymToKeycode(disp, jdwmconf->keys[i].keysym); + XGrabKey(disp, code, jdwmconf->keys[i].mod, DefaultRootWindow(disp), True, GrabModeAsync, GrabModeAsync); + XGrabKey(disp, code, jdwmconf->keys[i].mod | LockMask, DefaultRootWindow(disp), True, GrabModeAsync, GrabModeAsync); + XGrabKey(disp, code, jdwmconf->keys[i].mod | jdwmconf->numlockmask, DefaultRootWindow(disp), True, GrabModeAsync, GrabModeAsync); + XGrabKey(disp, code, jdwmconf->keys[i].mod | jdwmconf->numlockmask | LockMask, DefaultRootWindow(disp), True, + GrabModeAsync, GrabModeAsync); + } +} diff --git a/event.h b/event.h new file mode 100644 index 00000000..b5df0198 --- /dev/null +++ b/event.h @@ -0,0 +1,10 @@ +/* See LICENSE file for copyright and license details. */ + +#ifndef JDWM_EVENT_H +#define JDWM_EVENT_H + +#include "config.h" + +void grabkeys(Display *, jdwm_config *); /* grab all keys defined in config */ + +#endif diff --git a/jdwm.1 b/jdwm.1 new file mode 100644 index 00000000..885405ae --- /dev/null +++ b/jdwm.1 @@ -0,0 +1,153 @@ +.TH DWM 1 dwm\-VERSION +.SH NAME +dwm \- dynamic window manager +.SH SYNOPSIS +.B dwm +.RB [ \-v ] +.SH DESCRIPTION +dwm is a dynamic window manager for X. It manages windows in tiled and +floating layouts. Either layout can be applied dynamically, optimizing the +environment for the application in use and the task performed. +.P +In tiled layout windows are managed in a master and stacking area. The master +area contains the window which currently needs most attention, whereas the +stacking area contains all other windows. In floating layout windows can be +resized and moved freely. Dialog windows are always managed floating, +regardless of the layout applied. +.P +Windows are grouped by tags. Each window can be tagged with one or multiple +tags. Selecting certain tags displays all windows with these tags. +.P +dwm contains a small status bar which displays all available tags, the layout, +the title of the focused window, and the text read from standard input. A +floating window is indicated with an empty square and a maximized +floating window is indicated with a filled square before the windows +title. The selected tags are indicated with a different color. The tags of +the focused window are indicated with a filled square in the top left +corner. The tags which are applied to one or more windows are indicated +with an empty square in the top left corner. +.P +dwm 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 Status bar +.TP +.B Standard input +is read and displayed in the status text area. +.TP +.B Button1 +click on a tag label to display all windows with that tag, click on the layout +label toggles between tiled and floating layout. +.TP +.B Button3 +click on a tag label adds/removes all windows with that tag to/from the view. +.TP +.B Mod1\-Button1 +click on a tag label applies that tag to the focused window. +.TP +.B Mod1\-Button3 +click on a tag label adds/removes that tag to/from the focused window. +.SS Keyboard commands +.TP +.B Mod1\-Shift\-Return +Start +.BR xterm. +.TP +.B Mod1\-Return +Zooms/cycles current window to/from master area (tiled layout only). +.TP +.B Mod1\-b +Shows/hides the status bar. +.TP +.B Mod1\-h +Decreases the master area width about 5% (tiled layout only). +.TP +.B Mod1\-j +Focus next window. +.TP +.B Mod1\-k +Focus previous window. +.TP +.B Mod1\-l +Increases the master area width about 5% (tiled layout only). +.TP +.B Mod1\-m +Toggles maximization of current window (floating layout only). +.TP +.B Mod1\-Shift\-[1..n] +Apply +.RB nth +tag to current window. +.TP +.B Mod1\-Shift\-0 +Apply all tags to current window. +.TP +.B Mod1\-Control\-Shift\-[1..n] +Add/remove +.B nth +tag to/from current window. +.TP +.B Mod1\-Shift\-c +Close focused window. +.TP +.B Mod1\-space +Toggle between tiled and floating layout (affects all windows). +.TP +.B Mod1\-Shift\-space +Toggle focused window between tiled and floating state (tiled layout only). +.TP +.B Mod1\-[1..n] +View all windows with +.BR nth +tag. +.TP +.B Mod1\-0 +View all windows with any tag. +.TP +.B Mod1\-Control\-[1..n] +Add/remove all windows with +.BR nth +tag to/from the view. +.TP +.B Mod1\-Shift\-q +Quit dwm. +.SS Mouse commands +.TP +.B Mod1\-Button1 +Move current window while dragging (floating layout only). +.TP +.B Mod1\-Button2 +Zooms/cycles current window to/from master area (tiled layout only). +.TP +.B Mod1\-Button3 +Resize current window while dragging (floating layout only). +.SH CUSTOMIZATION +dwm 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) +.SH BUGS +The status bar may display +.BR "EOF" +when dwm has been started by an X session manager like +.BR xdm (1), +because those close standard output before executing dwm. +.P +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 . +.P +Recent GTK 2.10.9+ versions contain a broken +.BR Save\-As +file dialog implementation, +which requests to reconfigure its window size in an endless loop. However, its +window is still respondable during this state, so you can simply ignore the flicker +until a new GTK version appears, which will fix this bug, approximately +GTK 2.10.12+ versions. diff --git a/jdwm.c b/jdwm.c new file mode 100644 index 00000000..6bff4ca6 --- /dev/null +++ b/jdwm.c @@ -0,0 +1,379 @@ +/* See LICENSE file for copyright and license details. */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "jdwm.h" +#include "util.h" +#include "event.h" +#include "layout.h" +#include "tag.h" + +/* extern */ +extern int bpos; /* bar position */ +extern void (*handler[LASTEvent]) (XEvent *, jdwm_config *); /* event handler */ + +int screen, sx, sy, sw, sh, wax, way, waw, wah; +int bh; +Atom jdwmprops, wmatom[WMLast], netatom[NetLast]; +Bool *seltags, *prevtags;; +Bool selscreen = True; +Client *clients = NULL; +Client *sel = NULL; +Client *stack = NULL; +Cursor cursor[CurLast]; +DC dc; +Window barwin; +Layout ** taglayouts; + +/* static */ + +static int (*xerrorxlib) (Display *, XErrorEvent *); +static Bool otherwm = False, readin = True; +static Bool running = True; + + +Bool +gettextprop(Display *disp, Window w, Atom atom, char *text, unsigned int size) +{ + char **list = NULL; + int n; + + XTextProperty name; + + if(!text || size == 0) + return False; + + text[0] = '\0'; + XGetTextProperty(disp, w, &name, atom); + + if(!name.nitems) + return False; + + if(name.encoding == XA_STRING) + strncpy(text, (char *) name.value, size - 1); + else if(XmbTextPropertyToTextList(disp, &name, &list, &n) >= Success && n > 0 && *list) + { + strncpy(text, *list, size - 1); + XFreeStringList(list); + } + + text[size - 1] = '\0'; + XFree(name.value); + + return True; +} + + + + +static void +cleanup(Display *disp, jdwm_config *jdwmconf) +{ + close(STDIN_FILENO); + while(stack) + { + unban(stack); + unmanage(stack, &dc, NormalState, jdwmconf); + } + if(dc.font.set) + XFreeFontSet(disp, dc.font.set); + else + XFreeFont(disp, dc.font.xfont); + XUngrabKey(disp, AnyKey, AnyModifier, DefaultRootWindow(disp)); + XFreePixmap(disp, dc.drawable); + XFreeGC(disp, dc.gc); + XDestroyWindow(disp, barwin); + XFreeCursor(disp, cursor[CurNormal]); + XFreeCursor(disp, cursor[CurResize]); + XFreeCursor(disp, cursor[CurMove]); + XSetInputFocus(disp, PointerRoot, RevertToPointerRoot, CurrentTime); + XSync(disp, False); + p_delete(&seltags); + p_delete(&prevtags); + p_delete(&taglayouts); +} + +static long +getstate(Display *disp, Window w) +{ + int format, status; + long result = -1; + unsigned char *p = NULL; + unsigned long n, extra; + Atom real; + status = XGetWindowProperty(disp, w, wmatom[WMState], 0L, 2L, False, wmatom[WMState], + &real, &format, &n, &extra, (unsigned char **) &p); + if(status != Success) + return -1; + if(n != 0) + result = *p; + XFree(p); + return result; +} + +static void +scan(Display *disp, jdwm_config *jdwmconf) +{ + unsigned int i, num; + Window *wins, d1, d2; + XWindowAttributes wa; + + wins = NULL; + if(XQueryTree(disp, DefaultRootWindow(disp), &d1, &d2, &wins, &num)) + for(i = 0; i < num; i++) + { + if(!XGetWindowAttributes(disp, wins[i], &wa) + || wa.override_redirect || XGetTransientForHint(disp, wins[i], &d1)) + continue; + if(wa.map_state == IsViewable || getstate(disp, wins[i]) == IconicState) + manage(disp, &dc, wins[i], &wa, jdwmconf); + } + /* now the transients */ + for(i = 0; i < num; i++) + { + if(!XGetWindowAttributes(disp, wins[i], &wa)) + continue; + if(XGetTransientForHint(disp, wins[i], &d1) + && (wa.map_state == IsViewable || getstate(disp, wins[i]) == IconicState)) + manage(disp, &dc, wins[i], &wa, jdwmconf); + } + if(wins) + XFree(wins); +} + +static void +setup(Display *disp, jdwm_config *jdwmconf) +{ + int i; + unsigned int mask; + Window w; + XSetWindowAttributes wa; + + /* init atoms */ + jdwmprops = XInternAtom(disp, "_JDWM_PROPERTIES", False); + wmatom[WMProtocols] = XInternAtom(disp, "WM_PROTOCOLS", False); + wmatom[WMDelete] = XInternAtom(disp, "WM_DELETE_WINDOW", False); + wmatom[WMName] = XInternAtom(disp, "WM_NAME", False); + wmatom[WMState] = XInternAtom(disp, "WM_STATE", False); + netatom[NetSupported] = XInternAtom(disp, "_NET_SUPPORTED", False); + netatom[NetWMName] = XInternAtom(disp, "_NET_WM_NAME", False); + XChangeProperty(disp, DefaultRootWindow(disp), netatom[NetSupported], XA_ATOM, 32, + PropModeReplace, (unsigned char *) netatom, NetLast); + /* init cursors */ + cursor[CurNormal] = XCreateFontCursor(disp, XC_left_ptr); + cursor[CurResize] = XCreateFontCursor(disp, XC_sizing); + cursor[CurMove] = XCreateFontCursor(disp, XC_fleur); + /* select for events */ + wa.event_mask = SubstructureRedirectMask | SubstructureNotifyMask + | EnterWindowMask | LeaveWindowMask | StructureNotifyMask; + wa.cursor = cursor[CurNormal]; + XChangeWindowAttributes(disp, DefaultRootWindow(disp), CWEventMask | CWCursor, &wa); + XSelectInput(disp, DefaultRootWindow(disp), wa.event_mask); + grabkeys(disp, jdwmconf); + compileregs(jdwmconf); + seltags = p_new(Bool, jdwmconf->ntags); + seltags[0] = True; + prevtags = p_new(Bool, jdwmconf->ntags); + prevtags[0] = True; + /* geometry */ + sx = sy = 0; + sw = DisplayWidth(disp, screen); + sh = DisplayHeight(disp, screen); + initlayouts(jdwmconf); + /* bar */ + dc.h = bh = dc.font.height + 2; + wa.override_redirect = 1; + wa.background_pixmap = ParentRelative; + wa.event_mask = ButtonPressMask | ExposureMask; + barwin = XCreateWindow(disp, DefaultRootWindow(disp), sx, sy, sw, bh, 0, + DefaultDepth(disp, screen), CopyFromParent, + DefaultVisual(disp, screen), + CWOverrideRedirect | CWBackPixmap | CWEventMask, &wa); + XDefineCursor(disp, barwin, cursor[CurNormal]); + updatebarpos(disp); + XMapRaised(disp, barwin); + /* pixmap for everything */ + dc.drawable = XCreatePixmap(disp, DefaultRootWindow(disp), sw, bh, DefaultDepth(disp, screen)); + dc.gc = XCreateGC(disp, DefaultRootWindow(disp), 0, 0); + XSetLineAttributes(disp, dc.gc, 1, LineSolid, CapButt, JoinMiter); + if(!dc.font.set) + XSetFont(disp, dc.gc, dc.font.xfont->fid); + /* multihead support */ + selscreen = XQueryPointer(disp, DefaultRootWindow(disp), &w, &w, &i, &i, &i, &i, &mask); + loadjdwmprops(disp, jdwmconf); +} + +/* + * Startup Error handler to check if another window manager + * is already running. + */ +static int +xerrorstart(Display * dsply __attribute__ ((unused)), XErrorEvent * ee __attribute__ ((unused))) +{ + otherwm = True; + return -1; +} + +/* extern */ + +void +uicb_quit(Display *disp __attribute__ ((unused)), + jdwm_config *jdwmconf __attribute__((unused)), + const char *arg __attribute__ ((unused))) +{ + readin = running = False; +} + +void +updatebarpos(Display *disp) +{ + XEvent ev; + + wax = sx; + way = sy; + wah = sh; + waw = sw; + switch (bpos) + { + default: + wah -= bh; + way += bh; + XMoveWindow(disp, barwin, sx, sy); + break; + case BarBot: + wah -= bh; + XMoveWindow(disp, barwin, sx, sy + wah); + break; + case BarOff: + XMoveWindow(disp, barwin, sx, sy - bh); + break; + } + XSync(disp, False); + while(XCheckMaskEvent(disp, EnterWindowMask, &ev)); +} + +/* 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 * edpy, 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, "jdwm: fatal error: request code=%d, error code=%d\n", + ee->request_code, ee->error_code); + + return xerrorxlib(edpy, ee); /* may call exit */ +} + +int +main(int argc, char *argv[]) +{ + char *p; + int r, xfd; + fd_set rd; + XEvent ev; + Display * dpy; + Window root; + jdwm_config jdwmconf; + + if(argc == 2 && !strcmp("-v", argv[1])) + eprint("jdwm-" VERSION " © 2007 Julien Danjou\n"); + else if(argc != 1) + eprint("usage: jdwm [-v]\n"); + setlocale(LC_CTYPE, ""); + + + if(!(dpy = XOpenDisplay(NULL))) + eprint("jdwm: cannot open display\n"); + xfd = ConnectionNumber(dpy); + screen = DefaultScreen(dpy); + root = RootWindow(dpy, screen); + XSetErrorHandler(xerrorstart); + + /* this causes an error if some other window manager is running */ + XSelectInput(dpy, root, SubstructureRedirectMask); + XSync(dpy, False); + + if(otherwm) + eprint("jdwm: another window manager is already running\n"); + + XSync(dpy, False); + XSetErrorHandler(NULL); + xerrorxlib = XSetErrorHandler(xerror); + XSync(dpy, False); + parse_config(dpy, screen, &dc, &jdwmconf); + setup(dpy, &jdwmconf); + drawstatus(dpy, &jdwmconf); + scan(dpy, &jdwmconf); + XSync(dpy, False); + + /* main event loop, also reads status text from stdin */ + 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, jdwmconf.statustext, sizeof(jdwmconf.statustext) - 1)) + { + case -1: + strncpy(jdwmconf.statustext, strerror(errno), sizeof(jdwmconf.statustext) - 1); + jdwmconf.statustext[sizeof(jdwmconf.statustext) - 1] = '\0'; + readin = False; + break; + case 0: + strncpy(jdwmconf.statustext, "EOF", 4); + readin = False; + break; + default: + for(jdwmconf.statustext[r] = '\0', p = jdwmconf.statustext + strlen(jdwmconf.statustext) - 1; + p >= jdwmconf.statustext && *p == '\n'; *p-- = '\0'); + for(; p >= jdwmconf.statustext && *p != '\n'; --p); + if(p > jdwmconf.statustext) + strncpy(jdwmconf.statustext, p + 1, sizeof(jdwmconf.statustext)); + } + drawstatus(dpy, &jdwmconf); + } + + while(XPending(dpy)) + { + XNextEvent(dpy, &ev); + if(handler[ev.type]) + (handler[ev.type]) (&ev, &jdwmconf); /* call handler */ + } + } + cleanup(dpy, &jdwmconf); + XCloseDisplay(dpy); + + return 0; +} diff --git a/jdwm.doxygen b/jdwm.doxygen new file mode 100644 index 00000000..5770b534 --- /dev/null +++ b/jdwm.doxygen @@ -0,0 +1,276 @@ +DOXYFILE_ENCODING = UTF-8 +PROJECT_NAME = jdwm +PROJECT_NUMBER = 0.0 +OUTPUT_DIRECTORY = doc +CREATE_SUBDIRS = NO +OUTPUT_LANGUAGE = English +BRIEF_MEMBER_DESC = YES +REPEAT_BRIEF = YES +ABBREVIATE_BRIEF = "The $name class" \ + "The $name widget" \ + "The $name file" \ + is \ + provides \ + specifies \ + contains \ + represents \ + a \ + an \ + the +ALWAYS_DETAILED_SEC = NO +INLINE_INHERITED_MEMB = NO +FULL_PATH_NAMES = YES +STRIP_FROM_PATH = +STRIP_FROM_INC_PATH = +SHORT_NAMES = NO +JAVADOC_AUTOBRIEF = NO +QT_AUTOBRIEF = NO +MULTILINE_CPP_IS_BRIEF = NO +DETAILS_AT_TOP = NO +INHERIT_DOCS = YES +SEPARATE_MEMBER_PAGES = NO +TAB_SIZE = 8 +ALIASES = +OPTIMIZE_OUTPUT_FOR_C = YES +OPTIMIZE_OUTPUT_JAVA = NO +BUILTIN_STL_SUPPORT = NO +CPP_CLI_SUPPORT = NO +DISTRIBUTE_GROUP_DOC = NO +SUBGROUPING = YES +#--------------------------------------------------------------------------- +# Build related configuration options +#--------------------------------------------------------------------------- +EXTRACT_ALL = YES +EXTRACT_PRIVATE = YES +EXTRACT_STATIC = YES +EXTRACT_LOCAL_CLASSES = YES +EXTRACT_LOCAL_METHODS = YES +EXTRACT_ANON_NSPACES = YES +HIDE_UNDOC_MEMBERS = NO +HIDE_UNDOC_CLASSES = NO +HIDE_FRIEND_COMPOUNDS = NO +HIDE_IN_BODY_DOCS = NO +INTERNAL_DOCS = NO +CASE_SENSE_NAMES = YES +HIDE_SCOPE_NAMES = NO +SHOW_INCLUDE_FILES = YES +INLINE_INFO = YES +SORT_MEMBER_DOCS = YES +SORT_BRIEF_DOCS = NO +SORT_BY_SCOPE_NAME = NO +GENERATE_TODOLIST = YES +GENERATE_TESTLIST = YES +GENERATE_BUGLIST = YES +GENERATE_DEPRECATEDLIST= YES +ENABLED_SECTIONS = +MAX_INITIALIZER_LINES = 30 +SHOW_USED_FILES = YES +SHOW_DIRECTORIES = NO +FILE_VERSION_FILTER = +#--------------------------------------------------------------------------- +# configuration options related to warning and progress messages +#--------------------------------------------------------------------------- +QUIET = NO +WARNINGS = YES +WARN_IF_UNDOCUMENTED = YES +WARN_IF_DOC_ERROR = YES +WARN_NO_PARAMDOC = NO +WARN_FORMAT = "$file:$line: $text" +WARN_LOGFILE = +#--------------------------------------------------------------------------- +# configuration options related to the input files +#--------------------------------------------------------------------------- +INPUT = . +INPUT_ENCODING = UTF-8 +FILE_PATTERNS = *.c \ + *.cc \ + *.cxx \ + *.cpp \ + *.c++ \ + *.d \ + *.java \ + *.ii \ + *.ixx \ + *.ipp \ + *.i++ \ + *.inl \ + *.h \ + *.hh \ + *.hxx \ + *.hpp \ + *.h++ \ + *.idl \ + *.odl \ + *.cs \ + *.php \ + *.php3 \ + *.inc \ + *.m \ + *.mm \ + *.dox \ + *.py \ + *.C \ + *.CC \ + *.C++ \ + *.II \ + *.I++ \ + *.H \ + *.HH \ + *.H++ \ + *.CS \ + *.PHP \ + *.PHP3 \ + *.M \ + *.MM \ + *.PY +RECURSIVE = YES +EXCLUDE = +EXCLUDE_SYMLINKS = NO +EXCLUDE_PATTERNS = +EXCLUDE_SYMBOLS = +EXAMPLE_PATH = +EXAMPLE_PATTERNS = * +EXAMPLE_RECURSIVE = NO +IMAGE_PATH = +INPUT_FILTER = +FILTER_PATTERNS = +FILTER_SOURCE_FILES = NO +#--------------------------------------------------------------------------- +# configuration options related to source browsing +#--------------------------------------------------------------------------- +SOURCE_BROWSER = YES +INLINE_SOURCES = NO +STRIP_CODE_COMMENTS = YES +REFERENCED_BY_RELATION = YES +REFERENCES_RELATION = YES +REFERENCES_LINK_SOURCE = YES +USE_HTAGS = NO +VERBATIM_HEADERS = YES +#--------------------------------------------------------------------------- +# configuration options related to the alphabetical class index +#--------------------------------------------------------------------------- +ALPHABETICAL_INDEX = NO +COLS_IN_ALPHA_INDEX = 5 +IGNORE_PREFIX = +#--------------------------------------------------------------------------- +# configuration options related to the HTML output +#--------------------------------------------------------------------------- +GENERATE_HTML = YES +HTML_OUTPUT = html +HTML_FILE_EXTENSION = .html +HTML_HEADER = +HTML_FOOTER = +HTML_STYLESHEET = +HTML_ALIGN_MEMBERS = YES +GENERATE_HTMLHELP = NO +HTML_DYNAMIC_SECTIONS = NO +CHM_FILE = +HHC_LOCATION = +GENERATE_CHI = NO +BINARY_TOC = NO +TOC_EXPAND = NO +DISABLE_INDEX = NO +ENUM_VALUES_PER_LINE = 4 +GENERATE_TREEVIEW = YES +TREEVIEW_WIDTH = 250 +#--------------------------------------------------------------------------- +# configuration options related to the LaTeX output +#--------------------------------------------------------------------------- +GENERATE_LATEX = NO +LATEX_OUTPUT = latex +LATEX_CMD_NAME = latex +MAKEINDEX_CMD_NAME = makeindex +COMPACT_LATEX = NO +PAPER_TYPE = a4wide +EXTRA_PACKAGES = +LATEX_HEADER = +PDF_HYPERLINKS = NO +USE_PDFLATEX = NO +LATEX_BATCHMODE = NO +LATEX_HIDE_INDICES = NO +#--------------------------------------------------------------------------- +# configuration options related to the RTF output +#--------------------------------------------------------------------------- +GENERATE_RTF = NO +RTF_OUTPUT = rtf +COMPACT_RTF = NO +RTF_HYPERLINKS = NO +RTF_STYLESHEET_FILE = +RTF_EXTENSIONS_FILE = +#--------------------------------------------------------------------------- +# configuration options related to the man page output +#--------------------------------------------------------------------------- +GENERATE_MAN = NO +MAN_OUTPUT = man +MAN_EXTENSION = .3 +MAN_LINKS = NO +#--------------------------------------------------------------------------- +# configuration options related to the XML output +#--------------------------------------------------------------------------- +GENERATE_XML = NO +XML_OUTPUT = xml +XML_SCHEMA = +XML_DTD = +XML_PROGRAMLISTING = YES +#--------------------------------------------------------------------------- +# configuration options for the AutoGen Definitions output +#--------------------------------------------------------------------------- +GENERATE_AUTOGEN_DEF = NO +#--------------------------------------------------------------------------- +# configuration options related to the Perl module output +#--------------------------------------------------------------------------- +GENERATE_PERLMOD = NO +PERLMOD_LATEX = NO +PERLMOD_PRETTY = YES +PERLMOD_MAKEVAR_PREFIX = +#--------------------------------------------------------------------------- +# Configuration options related to the preprocessor +#--------------------------------------------------------------------------- +ENABLE_PREPROCESSING = YES +MACRO_EXPANSION = NO +EXPAND_ONLY_PREDEF = NO +SEARCH_INCLUDES = YES +INCLUDE_PATH = +INCLUDE_FILE_PATTERNS = +PREDEFINED = +EXPAND_AS_DEFINED = +SKIP_FUNCTION_MACROS = YES +#--------------------------------------------------------------------------- +# Configuration::additions related to external references +#--------------------------------------------------------------------------- +TAGFILES = +GENERATE_TAGFILE = +ALLEXTERNALS = NO +EXTERNAL_GROUPS = YES +PERL_PATH = /usr/bin/perl +#--------------------------------------------------------------------------- +# Configuration options related to the dot tool +#--------------------------------------------------------------------------- +CLASS_DIAGRAMS = NO +MSCGEN_PATH = +HIDE_UNDOC_RELATIONS = YES +HAVE_DOT = YES +CLASS_GRAPH = YES +COLLABORATION_GRAPH = YES +GROUP_GRAPHS = YES +UML_LOOK = NO +TEMPLATE_RELATIONS = NO +INCLUDE_GRAPH = YES +INCLUDED_BY_GRAPH = YES +CALL_GRAPH = YES +CALLER_GRAPH = NO +GRAPHICAL_HIERARCHY = YES +DIRECTORY_GRAPH = YES +DOT_IMAGE_FORMAT = png +DOT_PATH = +DOTFILE_DIRS = +DOT_GRAPH_MAX_NODES = 50 +MAX_DOT_GRAPH_DEPTH = 1000 +DOT_TRANSPARENT = NO +DOT_MULTI_TARGETS = NO +GENERATE_LEGEND = YES +DOT_CLEANUP = YES +#--------------------------------------------------------------------------- +# Configuration::additions related to the search engine +#--------------------------------------------------------------------------- +SEARCHENGINE = NO diff --git a/jdwm.h b/jdwm.h new file mode 100644 index 00000000..401b783d --- /dev/null +++ b/jdwm.h @@ -0,0 +1,49 @@ +/* See LICENSE file for copyright and license details. + * + * Julien's dynamic window manager is designed like any other X client as well. + * It is driven through handling X events. In contrast to other X clients, a + * window manager selects for SubstructureRedirectMask on the root window, to + * receive events about window (dis-)appearance. Only one X connection at a + * time is allowed to select for this event mask. + * + * Calls to fetch an X event from the event queue are blocking. Due reading + * status text from standard input, a select()-driven main loop has been + * implemented which selects for reads on the X connection and STDIN_FILENO to + * handle all data smoothly. The event handlers of jdwm are organized in an + * array which is accessed whenever a new event has been fetched. This allows + * event dispatching in O(1) time. + * + * Each child of the root window is called a client, except windows which have + * set the override_redirect flag. Clients are organized in a global + * doubly-linked client list, the focus history is remembered through a global + * stack list. Each client contains an array of Bools of the same size as the + * global tags array to indicate the tags of a client. For each client jdwm + * creates a small title window, which is resized whenever the (_NET_)WM_NAME + * properties are updated or the client is moved/resized. + * + * Keys and tagging rules are organized as arrays and defined in the config.h + * file. These arrays are kept static in event.o and tag.o respectively, + * because no other part of jdwm needs access to them. The current layout is + * represented by the lt pointer. + * + * To understand everything else, start reading main.c:main(). + */ + +#ifndef JDWM_JDWM_H +#define JDWM_JDWM_H + +#include "config.h" + +enum +{ CurNormal, CurResize, CurMove, CurLast }; /* cursor */ +enum +{ NetSupported, NetWMName, NetLast }; /* EWMH atoms */ +enum +{ WMProtocols, WMDelete, WMName, WMState, WMLast }; /* default atoms */ + +Bool gettextprop(Display *, Window, Atom, char *, unsigned int); /* return text property, UTF-8 compliant */ +void updatebarpos(Display *disp); /* updates the bar position */ +void uicb_quit(Display *, jdwm_config *, const char *); /* quit jdwm nicely */ +int xerror(Display *, XErrorEvent *); /* jdwm's X error handler */ + +#endif diff --git a/jdwmrc b/jdwmrc new file mode 100644 index 00000000..3ca6d036 --- /dev/null +++ b/jdwmrc @@ -0,0 +1,111 @@ +# Configuration file for jdwm + +jdwm: +{ + barpos = "BarTop"; + borderpx = 1; + snap = 8; + mwfact = 0.6; + font = "-*-fixed-medium-r-normal-*-13-*-*-*-*-*-*-*"; + tags = ( "1", "2", "3", "4", "5", "6", "7", "8", "9" ); + normal_border_color = "#dddddd"; + normal_bg_color = "#000000"; + normal_fg_color = "#ffffff"; + focus_border_color = "#008b8b"; + focus_bg_color = "#008b8b"; + focus_fg_color = "#ffffff"; + + layouts = (("[]=", "tile"), + ("=[]", "tileleft"), + ("+++", "grid"), +# ("(@)", "spiral"), + ("[\]", "dwindle"), + ("><>", "floating"), + ("TTT", "bstack"), + ("===", "bstackportrait") + ); + + nmaster = 2; + + rules = ({ name = "Gimp"; + tags = ""; + float = true; + }, + { name = "MPlayer"; + tags = ""; + float = true; + }, + { name = "Acroread"; + tags = ""; + float = true; + }, + { name = "VLC"; + tags = ""; + float = true; + }, + { name = "pinentry"; + tags = ""; + float = true; + }); + + modkey = "Mod4"; + + keys = ((("Mod4"), "Return", "spawn", "exec urxvt"), + (("Mod4"), "space", "setlayout"), + (("Mod4"), "b", "togglebar"), + (("Mod4"), "j", "focusnext"), + (("Mod4"), "k", "focusprev"), + (("Mod4"), "h", "setmwfact", "-0.05"), + (("Mod4"), "l", "setmwfact", "+0.05"), + (("Mod4", "Shift"), "h", "incnmaster", "1"), + (("Mod4", "Shift"), "l", "incnmaster", "-1"), + (("Mod4"), "m", "togglemax"), + (("Mod4"), "Escape", "viewprevtags"), + (("Mod4", "Control"), "Return", "zoom"), + (("Mod4", "Control"), "space", "togglefloating"), + (("Mod4", "Shift"), "c", "killclient"), + (("Mod4", "Shift"), "q", "quit"), + (("Mod4", "Shift"), "r", "reload"), + (("Mod4"), "0", "view"), + (("Mod4"), "1", "view", "1"), + (("Mod4"), "2", "view", "2"), + (("Mod4"), "3", "view", "3"), + (("Mod4"), "4", "view", "4"), + (("Mod4"), "5", "view", "5"), + (("Mod4"), "6", "view", "6"), + (("Mod4"), "7", "view", "7"), + (("Mod4"), "8", "view", "8"), + (("Mod4"), "9", "view", "9"), + (("Mod4", "Control"), "0", "toggleview"), + (("Mod4", "Control"), "1", "toggleview", "1"), + (("Mod4", "Control"), "2", "toggleview", "2"), + (("Mod4", "Control"), "3", "toggleview", "3"), + (("Mod4", "Control"), "4", "toggleview", "4"), + (("Mod4", "Control"), "5", "toggleview", "5"), + (("Mod4", "Control"), "6", "toggleview", "6"), + (("Mod4", "Control"), "7", "toggleview", "7"), + (("Mod4", "Control"), "8", "toggleview", "8"), + (("Mod4", "Control"), "9", "toggleview", "9"), + (("Mod4", "Control"), "0", "toggleview"), + (("Mod4", "Shift"), "0", "tag"), + (("Mod4", "Shift"), "1", "tag", "1"), + (("Mod4", "Shift"), "2", "tag", "2"), + (("Mod4", "Shift"), "3", "tag", "3"), + (("Mod4", "Shift"), "4", "tag", "4"), + (("Mod4", "Shift"), "5", "tag", "5"), + (("Mod4", "Shift"), "6", "tag", "6"), + (("Mod4", "Shift"), "7", "tag", "7"), + (("Mod4", "Shift"), "8", "tag", "8"), + (("Mod4", "Shift"), "9", "tag", "9"), + (("Mod4", "Shift", "Control"), "0", "toggletag"), + (("Mod4", "Shift", "Control"), "1", "toggletag", "1"), + (("Mod4", "Shift", "Control"), "2", "toggletag", "2"), + (("Mod4", "Shift", "Control"), "3", "toggletag", "3"), + (("Mod4", "Shift", "Control"), "4", "toggletag", "4"), + (("Mod4", "Shift", "Control"), "5", "toggletag", "5"), + (("Mod4", "Shift", "Control"), "6", "toggletag", "6"), + (("Mod4", "Shift", "Control"), "7", "toggletag", "7"), + (("Mod4", "Shift", "Control"), "8", "toggletag", "8"), + (("Mod4", "Shift", "Control"), "9", "toggletag", "9") + ); +}; diff --git a/layout.c b/layout.c new file mode 100644 index 00000000..1a033c62 --- /dev/null +++ b/layout.c @@ -0,0 +1,280 @@ +/* See LICENSE file for copyright and license details. */ + +#include +#include +#include +#include + +#include "jdwm.h" +#include "layout.h" +#include "tag.h" +#include "layouts/floating.h" +#include "util.h" + +int blw = 0; + +/* static */ + +static char prop[128]; + +/* extern */ +extern Layout ** taglayouts; +extern int wax, way, wah, waw; /* windowarea geometry */ +extern int bpos; /* bar position */ +extern Window barwin; +extern Client *clients, *sel; /* global client list */ +extern Bool *seltags; +extern Atom jdwmprops; +extern DC dc; + +void +arrange(Display * disp, jdwm_config *jdwmconf) +{ + Client *c; + + for(c = clients; c; c = c->next) + if(isvisible(c, jdwmconf->ntags)) + unban(c); + else + ban(c); + jdwmconf->current_layout->arrange(disp, jdwmconf); + focus(disp, &dc, NULL, jdwmconf); + restack(disp, jdwmconf); +} + +void +uicb_focusnext(Display *disp __attribute__ ((unused)), + jdwm_config * jdwmconf, + const char *arg __attribute__ ((unused))) +{ + Client *c; + + if(!sel) + return; + for(c = sel->next; c && !isvisible(c, jdwmconf->ntags); c = c->next); + if(!c) + for(c = clients; c && !isvisible(c, jdwmconf->ntags); c = c->next); + if(c) + { + focus(c->display, &dc, c, jdwmconf); + restack(c->display, jdwmconf); + } +} + +void +uicb_focusprev(Display *disp __attribute__ ((unused)), + jdwm_config *jdwmconf, + const char *arg __attribute__ ((unused))) +{ + Client *c; + + if(!sel) + return; + for(c = sel->prev; c && !isvisible(c, jdwmconf->ntags); c = c->prev); + if(!c) + { + for(c = clients; c && c->next; c = c->next); + for(; c && !isvisible(c, jdwmconf->ntags); c = c->prev); + } + if(c) + { + focus(c->display, &dc, c, jdwmconf); + restack(c->display, jdwmconf); + } +} + +void +initlayouts(jdwm_config * jdwmconf) +{ + int w, i; + + for(blw = i = 0; i < jdwmconf->nlayouts; i++) + { + w = textw(jdwmconf->layouts[i].symbol); + if(w > blw) + blw = w; + } + + taglayouts = p_new(Layout *, jdwmconf->ntags); + for(i = 0; i < jdwmconf->ntags; i++) + taglayouts[i] = jdwmconf->layouts; +} + +void +loadjdwmprops(Display *disp, jdwm_config * jdwmconf) +{ + int i; + + if(gettextprop(disp, DefaultRootWindow(disp), jdwmprops, prop, sizeof(prop))) + { + for(i = 0; i < jdwmconf->ntags && i < ssizeof(prop) - 1 && prop[i] != '\0'; i++) + seltags[i] = prop[i] == '1'; + } +} + +inline Client * +nexttiled(Client * c, int ntags) +{ + for(; c && (c->isfloating || !isvisible(c, ntags)); c = c->next); + return c; +} + +void +restack(Display * disp, jdwm_config *jdwmconf) +{ + Client *c; + XEvent ev; + XWindowChanges wc; + + drawstatus(disp, jdwmconf); + if(!sel) + return; + if(sel->isfloating || IS_ARRANGE(floating)) + XRaiseWindow(disp, sel->win); + if(!IS_ARRANGE(floating)) + { + wc.stack_mode = Below; + wc.sibling = barwin; + if(!sel->isfloating) + { + XConfigureWindow(disp, sel->win, CWSibling | CWStackMode, &wc); + wc.sibling = sel->win; + } + for(c = nexttiled(clients, jdwmconf->ntags); c; c = nexttiled(c->next, jdwmconf->ntags)) + { + if(c == sel) + continue; + XConfigureWindow(disp, c->win, CWSibling | CWStackMode, &wc); + wc.sibling = c->win; + } + } + XSync(disp, False); + while(XCheckMaskEvent(disp, EnterWindowMask, &ev)); +} + +void +savejdwmprops(Display *disp, jdwm_config *jdwmconf) +{ + int i; + for(i = 0; i < jdwmconf->ntags && i < ssizeof(prop) - 1; i++) + prop[i] = seltags[i] ? '1' : '0'; + prop[i] = '\0'; + XChangeProperty(disp, DefaultRootWindow(disp), jdwmprops, XA_STRING, 8, PropModeReplace, (unsigned char *) prop, i); +} + +void +uicb_setlayout(Display *disp, jdwm_config * jdwmconf, const char *arg) +{ + int i, j; + Client *c; + + if(!arg) + { + if(!(++jdwmconf->current_layout)->symbol) + jdwmconf->current_layout = &jdwmconf->layouts[0]; + } + else + { + i = strtol(arg, NULL, 10); + if(i < 0 || i >= jdwmconf->nlayouts) + return; + jdwmconf->current_layout = &jdwmconf->layouts[i]; + } + + for(c = clients; c; c = c->next) + c->ftview = True; + + if(sel) + arrange(disp, jdwmconf); + else + drawstatus(disp, jdwmconf); + + savejdwmprops(disp, jdwmconf); + + for(j = 0; j < jdwmconf->ntags; j++) + if (seltags[j]) + taglayouts[j] = jdwmconf->current_layout; +} + +void +uicb_togglebar(Display *disp, + jdwm_config *jdwmconf, + const char *arg __attribute__ ((unused))) +{ + if(bpos == BarOff) + bpos = (jdwmconf->bpos == BarOff) ? BarTop : jdwmconf->bpos; + else + bpos = BarOff; + updatebarpos(disp); + arrange(disp, jdwmconf); +} + +static void +maximize(int x, int y, int w, int h, jdwm_config *jdwmconf) +{ + XEvent ev; + + if(!sel) + return; + + if((sel->ismax = !sel->ismax)) + { + sel->wasfloating = sel->isfloating; + sel->isfloating = True; + sel->rx = sel->x; + sel->ry = sel->y; + sel->rw = sel->w; + sel->rh = sel->h; + resize(sel, x, y, w, h, True); + } + else if(sel->isfloating) + resize(sel, sel->rx, sel->ry, sel->rw, sel->rh, True); + else + sel->isfloating = False; + + drawstatus(sel->display, jdwmconf); + + while(XCheckMaskEvent(sel->display, EnterWindowMask, &ev)); +} + +void +uicb_togglemax(Display *disp __attribute__ ((unused)), + jdwm_config *jdwmconf, + const char *arg __attribute__ ((unused))) +{ + maximize(wax, way, waw - 2 * jdwmconf->borderpx, wah - 2 * jdwmconf->borderpx, jdwmconf); +} + +void +uicb_toggleverticalmax(Display *disp __attribute__ ((unused)), + jdwm_config *jdwmconf, + const char *arg __attribute__ ((unused))) +{ + if(sel) + maximize(sel->x, way, sel->w, wah - 2 * jdwmconf->borderpx, jdwmconf); +} + + +void +uicb_togglehorizontalmax(Display *disp __attribute__ ((unused)), + jdwm_config *jdwmconf, + const char *arg __attribute__ ((unused))) +{ + if(sel) + maximize(wax, sel->y, waw - 2 * jdwmconf->borderpx, sel->h, jdwmconf); +} + +void +uicb_zoom(Display *disp __attribute__ ((unused)), + jdwm_config *jdwmconf, + const char *arg __attribute__ ((unused))) +{ + Client *c; + if(!sel || ((c = sel) == nexttiled(clients, jdwmconf->ntags) && !(c = nexttiled(c->next, jdwmconf->ntags)))) + return; + detach(c); + attach(c); + focus(c->display, &dc, c, jdwmconf); + arrange(c->display, jdwmconf); +} + diff --git a/layout.h b/layout.h new file mode 100644 index 00000000..223677d4 --- /dev/null +++ b/layout.h @@ -0,0 +1,25 @@ +/* See LICENSE file for copyright and license details. */ + +#ifndef JDWM_LAYOUT_H +#define JDWM_LAYOUT_H + +#include "client.h" + +#define IS_ARRANGE(layout) (layout == jdwmconf->current_layout->arrange) + +void arrange(Display *, jdwm_config *); /* arranges all windows depending on the layout in use */ +void initlayouts(jdwm_config *); /* initialize layout array */ +Client *nexttiled(Client *, int); /* returns tiled successor of c */ +void restack(Display *, jdwm_config *); /* restores z layers of all clients */ +void uicb_focusnext(Display *, jdwm_config *, const char *); /* focuses next visible client */ +void uicb_focusprev(Display *, jdwm_config *, const char *); /* focuses prev visible client */ +void uicb_setlayout(Display *, jdwm_config *, const char *); /* sets layout, NULL means next layout */ +void uicb_togglebar(Display *, jdwm_config *, const char *); /* shows/hides the bar */ +void uicb_togglemax(Display *, jdwm_config *, const char *); /* toggles maximization of floating client */ +void uicb_toggleverticalmax(Display *, jdwm_config *, const char *); +void uicb_togglehorizontalmax(Display *, jdwm_config *, const char *); +void uicb_zoom(Display *, jdwm_config *, const char *); /* set current window first in stack */ +void loadjdwmprops(Display *, jdwm_config *); +void savejdwmprops(Display *disp, jdwm_config *); + +#endif diff --git a/layouts/floating.c b/layouts/floating.c new file mode 100644 index 00000000..4183a598 --- /dev/null +++ b/layouts/floating.c @@ -0,0 +1,25 @@ +/* See LICENSE file for copyright and license details. */ + +#include "tag.h" +#include "layouts/floating.h" + +/* extern */ +extern Client *clients; /* global client */ + +void +floating(Display *disp __attribute__ ((unused)), jdwm_config *jdwmconf) +{ /* default floating layout */ + Client *c; + + for(c = clients; c; c = c->next) + if(isvisible(c, jdwmconf->ntags)) + { + if(c->ftview) + { + resize(c, c->rx, c->ry, c->rw, c->rh, True); + c->ftview = False; + } + else + resize(c, c->x, c->y, c->w, c->h, True); + } +} diff --git a/layouts/floating.h b/layouts/floating.h new file mode 100644 index 00000000..9e28ed8b --- /dev/null +++ b/layouts/floating.h @@ -0,0 +1,8 @@ +/* See LICENSE file for copyright and license details. */ + +#ifndef JDWM_FLOATING_H +#define JDWM_FLOATING_H + +void floating(Display *, jdwm_config *); /* floating layout */ + +#endif diff --git a/layouts/grid.c b/layouts/grid.c new file mode 100644 index 00000000..967e2cbf --- /dev/null +++ b/layouts/grid.c @@ -0,0 +1,50 @@ +/* See LICENSE file for copyright and license details. */ + +#include "grid.h" +#include "layout.h" +#include "tag.h" + +extern int wah, waw; /* windowarea geometry */ +extern int bh, bpos; /* bar height, bar position */ +extern Client *clients; /* global client list and stack */ +extern DC dc; + +void +grid(Display *disp, jdwm_config *jdwmconf) +{ + unsigned int i, n, cx, cy, cw, ch, aw, ah, cols, rows; + Client *c; + + for(n = 0, c = nexttiled(clients, jdwmconf->ntags); c; c = nexttiled(c->next, jdwmconf->ntags)) + n++; + + /* grid dimensions */ + for(rows = 0; rows <= n / 2; rows++) + if(rows * rows >= n) + break; + cols = (rows && (rows - 1) * rows >= n) ? rows - 1 : rows; + + /* window geoms (cell height/width) */ + ch = wah / (rows ? rows : 1); + cw = waw / (cols ? cols : 1); + + for(i = 0, c = clients; c; c = c->next) + if(isvisible(c, jdwmconf->ntags)) + { + unban(c); + if(c->isfloating) + continue; + c->ismax = False; + cx = (i / rows) * cw; + cy = (i % rows) * ch + (bpos == BarTop ? bh : 0); // bh? adjust + /* adjust height/width of last row/column's windows */ + ah = ((i + 1) % rows == 0) ? wah - ch * rows : 0; + aw = (i >= rows * (cols - 1)) ? waw - cw * cols : 0; + resize(c, cx, cy, cw - 2 * c->border + aw, ch - 2 * c->border + ah, False); + i++; + } + else + ban(c); + focus(disp, &dc, NULL, jdwmconf); + restack(disp, jdwmconf); +} diff --git a/layouts/grid.h b/layouts/grid.h new file mode 100644 index 00000000..acbeae7f --- /dev/null +++ b/layouts/grid.h @@ -0,0 +1,11 @@ +/* See LICENSE file for copyright and license details. */ + +#ifndef JDWM_GRID_H +#define JDWM_GRID_H + +#include "config.h" + +/* grid.c */ +void grid(Display *, jdwm_config *); + +#endif diff --git a/layouts/spiral.c b/layouts/spiral.c new file mode 100644 index 00000000..0bb13233 --- /dev/null +++ b/layouts/spiral.c @@ -0,0 +1,77 @@ +/* See LICENSE file for copyright and license details. */ + +#include "layout.h" +#include "tag.h" +#include "spiral.h" + +extern int wax, way, wah, waw; /* windowarea geometry */ +extern Client *clients; /* global client list */ +extern DC dc; + +static void +fibonacci(Display *disp, jdwm_config *jdwmconf, int shape) +{ + int n, nx, ny, nh, nw, i; + Client *c; + + nx = wax; + ny = 0; + nw = waw; + nh = wah; + for(n = 0, c = nexttiled(clients, jdwmconf->ntags); c; c = nexttiled(c->next, jdwmconf->ntags)) + n++; + for(i = 0, c = clients; c; c = c->next) + { + c->ismax = False; + if((i % 2 && nh / 2 > 2 * c->border) + || (!(i % 2) && nw / 2 > 2 * c->border)) + { + if(i < n - 1) + { + if(i % 2) + nh /= 2; + else + nw /= 2; + if((i % 4) == 2 && !shape) + ny += nh; + } + if((i % 4) == 0) + { + if(shape) + ny += nh; + else + ny -= nh; + } + else if((i % 4) == 1) + nx += nw; + else if((i % 4) == 2) + ny += nh; + else if((i % 4) == 3) + { + if(shape) + nx += nw; + else + nx -= nw; + } + if(i == 0) + ny = way; + i++; + } + resize(c, nx, ny, nw - 2 * c->border, nh - 2 * c->border, False); + } + focus(disp, &dc, NULL, jdwmconf); + restack(disp, jdwmconf); +} + + +void +dwindle(Display *disp, jdwm_config *jdwmconf) +{ + fibonacci(disp, jdwmconf, 1); +} + +void +spiral(Display *disp, jdwm_config *jdwmconf) +{ + fibonacci(disp, jdwmconf, 0); +} diff --git a/layouts/spiral.h b/layouts/spiral.h new file mode 100644 index 00000000..698a7dea --- /dev/null +++ b/layouts/spiral.h @@ -0,0 +1,9 @@ +/* See LICENSE file for copyright and license details. */ + +#ifndef JDWM_SPIRAL_H +#define JDWM_SPIRAL_H + +void dwindle(Display *, jdwm_config *); /* dwindle windows */ +void spiral(Display *, jdwm_config *); /* spiral windows */ + +#endif diff --git a/layouts/tile.c b/layouts/tile.c new file mode 100644 index 00000000..4cd3902d --- /dev/null +++ b/layouts/tile.c @@ -0,0 +1,201 @@ +/* See LICENSE file for copyright and license details. */ + +#include +#include + +#include "layout.h" +#include "layouts/tile.h" + +/* extern */ +extern int wax, way, wah, waw; /* windowarea geometry */ +extern int bh; /* bar height */ +extern Client *sel, *clients; + +/* static */ + +static double mwfact = 0.6; +static void _tile(jdwm_config *, const Bool); /* arranges all windows tiled */ + +static int nmaster = 2; + +void +uicb_incnmaster(Display *disp, + jdwm_config *jdwmconf, + const char * arg) +{ + int i; + + if(!IS_ARRANGE(tile) && !IS_ARRANGE(tileleft) && !IS_ARRANGE(bstack) && !IS_ARRANGE(bstackportrait)) + return; + if(!arg) + nmaster = jdwmconf->nmaster; + else + { + i = strtol(arg, (char **) NULL, 10); + if((nmaster + i) < 1 || wah / (nmaster + i) <= 2 * jdwmconf->borderpx) + return; + nmaster += i; + } + if(sel) + arrange(disp, jdwmconf); + else + drawstatus(disp, jdwmconf); +} + +void +uicb_setmwfact(Display *disp, + jdwm_config * jdwmconf, + const char *arg) +{ + double delta; + + if(!IS_ARRANGE(tile) && !IS_ARRANGE(tileleft) && !IS_ARRANGE(bstack) && !IS_ARRANGE(bstackportrait)) + return; + + /* arg handling, manipulate mwfact */ + if(!arg) + mwfact = jdwmconf->mwfact; + else if(1 == sscanf(arg, "%lf", &delta)) + { + if(arg[0] != '+' && arg[0] != '-') + mwfact = delta; + else + mwfact += delta; + if(mwfact < 0.1) + mwfact = 0.1; + else if(mwfact > 0.9) + mwfact = 0.9; + } + arrange(disp, jdwmconf); +} + +static void +_tile(jdwm_config *jdwmconf, const Bool right) +{ + unsigned int nx, ny, nw, nh, mw; + int n, th, i, mh; + Client *c; + + for(n = 0, c = nexttiled(clients, jdwmconf->ntags); c; c = nexttiled(c->next, jdwmconf->ntags)) + n++; + + /* window geoms */ + mh = (n <= nmaster) ? wah / (n > 0 ? n : 1) : wah / nmaster; + mw = (n <= nmaster) ? waw : mwfact * waw; + th = (n > nmaster) ? wah / (n - nmaster) : 0; + if(n > nmaster && th < bh) + th = wah; + + nx = wax; + ny = way; + for(i = 0, c = nexttiled(clients, jdwmconf->ntags); c; c = nexttiled(c->next, jdwmconf->ntags), i++) + { + c->ismax = False; + if(i < nmaster) + { /* master */ + ny = way + i * mh; + if(!right && i == 0) + nx += (waw - mw); + nw = mw - 2 * c->border; + nh = mh; + if(i + 1 == (n < nmaster ? n : nmaster)) /* remainder */ + nh = wah - mh * i; + nh -= 2 * c->border; + } + else + { /* tile window */ + if(i == nmaster) + { + ny = way; + if(right) + nx += mw; + else + nx = 0; + } + nw = waw - mw - 2 * c->border; + if(i + 1 == n) /* remainder */ + nh = (way + wah) - ny - 2 * c->border; + else + nh = th - 2 * c->border; + } + resize(c, nx, ny, nw, nh, False); + if(n > nmaster && th != wah) + ny += nh + 2 * c->border; + } +} + +void +tile(Display *disp __attribute__ ((unused)), jdwm_config *jdwmconf) +{ + _tile(jdwmconf, True); +} + +void +tileleft(Display *disp __attribute__ ((unused)), jdwm_config *jdwmconf) +{ + _tile(jdwmconf, False); +} + + +static void +_bstack(jdwm_config *jdwmconf, Bool portrait) +{ + int i, n, nx, ny, nw, nh, mw, mh, tw, th; + Client *c; + + for(n = 0, c = nexttiled(clients, jdwmconf->ntags); c; c = nexttiled(c->next, jdwmconf->ntags)) + n++; + + /* window geoms */ + mh = (n > nmaster) ? (wah * mwfact) / nmaster : wah / (n > 0 ? n : 1); + mw = waw; + th = (n > nmaster) ? (wah * (1 - mwfact)) / (portrait ? 1 : n - nmaster) : 0; + tw = (n > nmaster) ? waw / (portrait ? n - nmaster : 1) : 0; + + for(i = 0, c = nexttiled(clients, jdwmconf->ntags); c; c = nexttiled(c->next, jdwmconf->ntags), i++) + { + c->ismax = False; + nx = wax; + ny = way; + if(i < nmaster) + { + ny += i * mh; + nw = mw - 2 * c->border; + nh = mh - 2 * c->border; + } + else if(portrait) + { + nx += (i - nmaster) * tw; + ny += mh * nmaster; + nw = tw - 2 * c->border; + nh = th - 2 * c->border + 1; + } + else + { + ny += mh * nmaster; + nw = tw - 2 * c->border; + if(th > 2 * c->border) + { + ny += (i - nmaster) * th; + nh = th - 2 * c->border; + if (i == n - 1) + nh += (n > nmaster) ? wah - mh - th * (n - nmaster) : 0; + } + else + nh = wah - 2 * c->border; + } + resize(c, nx, ny, nw, nh, False); + } +} + +void +bstack(Display *disp __attribute__ ((unused)), jdwm_config *jdwmconf) +{ + _bstack(jdwmconf, False); +} + +void +bstackportrait(Display *disp __attribute__ ((unused)), jdwm_config *jdwmconf) +{ + _bstack(jdwmconf, True); +} diff --git a/layouts/tile.h b/layouts/tile.h new file mode 100644 index 00000000..78c55ba2 --- /dev/null +++ b/layouts/tile.h @@ -0,0 +1,13 @@ +/* See LICENSE file for copyright and license details. */ + +#ifndef JDWM_TILE_H +#define JDWM_TILE_H + +void uicb_incnmaster(Display *, jdwm_config *, const char *); /* change number of master windows */ +void uicb_setmwfact(Display *, jdwm_config *, const char *); /* sets master width factor */ +void tile(Display *, jdwm_config *); +void tileleft(Display *, jdwm_config *); +void bstack(Display *, jdwm_config *); +void bstackportrait(Display *, jdwm_config *); + +#endif diff --git a/tag.c b/tag.c new file mode 100644 index 00000000..cf299c83 --- /dev/null +++ b/tag.c @@ -0,0 +1,313 @@ +/* See LICENSE file for copyright and license details. */ + +#include +#include +#include +#include + +#include "util.h" +#include "layout.h" +#include "tag.h" + +extern Client *sel; /* global client list */ +extern Bool *seltags, *prevtags; +extern Layout ** taglayouts; + +static Regs *regs = NULL; +static char prop[512]; + +/** This function returns the index of + * the tag given un argument in *tags + * \param tag_to_find tag name + * \return index of tag + */ +static int +idxoftag(const char *tag_to_find, const char **tags, int ntags) +{ + int i; + + if(!tag_to_find) + return 0; + + for(i = 0; i < ntags; i++) + if(!strcmp(tags[i], tag_to_find)) + return i; + + return 0; +} + +void +applyrules(Client * c, jdwm_config *jdwmconf) +{ + int i, j; + regmatch_t tmp; + Bool matched = False; + XClassHint ch = { 0, 0 }; + + /* rule matching */ + XGetClassHint(c->display, 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 < jdwmconf->nrules; i++) + if(regs[i].propregex && !regexec(regs[i].propregex, prop, 1, &tmp, 0)) + { + c->isfloating = jdwmconf->rules[i].isfloating; + for(j = 0; regs[i].tagregex && j < jdwmconf->ntags; j++) + if(!regexec(regs[i].tagregex, jdwmconf->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 < jdwmconf->ntags; i++) + c->tags[i] = seltags[i]; +} + +void +compileregs(jdwm_config * jdwmconf) +{ + int i; + regex_t *reg; + + if(regs) + return; + regs = p_new(Regs, jdwmconf->nrules); + for(i = 0; i < jdwmconf->nrules; i++) + { + if(jdwmconf->rules[i].prop) + { + reg = p_new(regex_t, 1); + if(regcomp(reg, jdwmconf->rules[i].prop, REG_EXTENDED)) + p_delete(®); + else + regs[i].propregex = reg; + } + if(jdwmconf->rules[i].tags) + { + reg = p_new(regex_t, 1); + if(regcomp(reg, jdwmconf->rules[i].tags, REG_EXTENDED)) + p_delete(®); + else + regs[i].tagregex = reg; + } + } +} + + +/** Returns True if a client is visible on + * one of the currently selected tag, false otherwise. + * \param c Client + * \return True or False + */ +Bool +isvisible(Client * c, int ntags) +{ + int i; + + for(i = 0; i < ntags; i++) + if(c->tags[i] && seltags[i]) + return True; + return False; +} + + +/** Tag selected window with tag + * \param disp Display ref + * \param arg Tag name + * \ingroup ui_callback + */ +void +uicb_tag(Display *disp, jdwm_config *jdwmconf, const char *arg) +{ + int i; + + if(!sel) + return; + for(i = 0; i < jdwmconf->ntags; i++) + sel->tags[i] = arg == NULL; + i = idxoftag(arg, jdwmconf->tags, jdwmconf->ntags); + if(i >= 0 && i < jdwmconf->ntags) + sel->tags[i] = True; + saveprops(sel, jdwmconf->ntags); + arrange(disp, jdwmconf); +} + +/** Toggle floating state of a client + * \param disp Display ref + * \param arg unused + * \ingroup ui_callback + */ +void +uicb_togglefloating(Display *disp, + jdwm_config * jdwmconf, + const char *arg __attribute__ ((unused))) +{ + if(!sel) + return; + sel->isfloating = !sel->isfloating; + if(sel->isfloating) + /*restore last known float dimensions*/ + resize(sel, sel->rx, sel->ry, sel->rw, sel->rh, True); + else + { + /*save last known float dimensions*/ + sel->rx = sel->x; + sel->ry = sel->y; + sel->rw = sel->w; + sel->rh = sel->h; + } + saveprops(sel, jdwmconf->ntags); + arrange(disp, jdwmconf); +} + +/** Toggle tag view + * \param disp Display ref + * \param arg Tag name + * \ingroup ui_callback + */ +void +uicb_toggletag(Display *disp, + jdwm_config *jdwmconf, + const char *arg) +{ + unsigned int i; + int j; + + if(!sel) + return; + i = idxoftag(arg, jdwmconf->tags, jdwmconf->ntags); + sel->tags[i] = !sel->tags[i]; + for(j = 0; j < jdwmconf->ntags && !sel->tags[j]; j++); + if(j == jdwmconf->ntags) + sel->tags[i] = True; + saveprops(sel, jdwmconf->ntags); + arrange(disp, jdwmconf); +} + +/** Add a tag to viewed tags + * \param disp Display ref + * \param arg Tag name + * \ingroup ui_callback + */ +void +uicb_toggleview(Display *disp, + jdwm_config *jdwmconf, + const char *arg) +{ + unsigned int i; + int j; + + i = idxoftag(arg, jdwmconf->tags, jdwmconf->ntags); + seltags[i] = !seltags[i]; + for(j = 0; j < jdwmconf->ntags && !seltags[j]; j++); + if(j == jdwmconf->ntags) + seltags[i] = True; /* cannot toggle last view */ + savejdwmprops(disp, jdwmconf); + arrange(disp, jdwmconf); +} + +/** + * \param disp Display ref + * \param arg + * \ingroup ui_callback + */ +void +uicb_view(Display *disp, + jdwm_config *jdwmconf, + const char *arg) +{ + int i; + + for(i = 0; i < jdwmconf->ntags; i++) + { + prevtags[i] = seltags[i]; + seltags[i] = arg == NULL; + } + i = idxoftag(arg, jdwmconf->tags, jdwmconf->ntags); + if(i >= 0 && i < jdwmconf->ntags) + { + seltags[i] = True; + jdwmconf->current_layout = taglayouts[i]; + } + savejdwmprops(disp, jdwmconf); + arrange(disp, jdwmconf); +} + +/** View previously selected tags + * \param disp Display ref + * \param arg unused + * \ingroup ui_callback + */ +void +uicb_viewprevtags(Display * disp, + jdwm_config *jdwmconf, + const char *arg __attribute__ ((unused))) +{ + int i; + Bool t; + + for(i = 0; i < jdwmconf->ntags; i++) + { + t = seltags[i]; + seltags[i] = prevtags[i]; + prevtags[i] = t; + } + arrange(disp, jdwmconf); +} + +/** View next tag + * \param disp Display ref + * \param arg unused + * \ingroup ui_callback + */ +void +uicb_tag_viewnext(Display *disp, + jdwm_config *jdwmconf, + const char *arg __attribute__ ((unused))) +{ + int i; + int firsttag = -1; + + for(i = 0; i < jdwmconf->ntags; i++) + { + if(firsttag < 0 && seltags[i]) + firsttag = i; + seltags[i] = False; + } + if(++firsttag >= jdwmconf->ntags) + firsttag = 0; + seltags[firsttag] = True; + savejdwmprops(disp, jdwmconf); + arrange(disp, jdwmconf); +} + +/** View previous tag + * \param disp Display ref + * \param arg unused + * \ingroup ui_callback + */ +void +uicb_tag_viewprev(Display *disp, + jdwm_config *jdwmconf, + const char *arg __attribute__ ((unused))) +{ + int i; + int firsttag = -1; + + for(i = jdwmconf->ntags - 1; i >= 0; i--) + { + if(firsttag < 0 && seltags[i]) + firsttag = i; + seltags[i] = False; + } + if(--firsttag < 0) + firsttag = jdwmconf->ntags - 1; + seltags[firsttag] = True; + savejdwmprops(disp, jdwmconf); + arrange(disp, jdwmconf); +} diff --git a/tag.h b/tag.h new file mode 100644 index 00000000..b451b612 --- /dev/null +++ b/tag.h @@ -0,0 +1,27 @@ +/* See LICENSE file for copyright and license details. */ + +#ifndef JDWM_TAG_H +#define JDWM_TAG_H + +#include +#include "client.h" + +void compileregs(jdwm_config *); /* initialize regexps of rules defined in config.h */ +Bool isvisible(Client *, int); /* returns True if client is visible */ +void applyrules(Client * c, jdwm_config *); /* applies rules to c */ +void uicb_tag(Display *, jdwm_config *, const char *); /* tags sel with arg's index */ +void uicb_togglefloating(Display *, jdwm_config *, const char *); /* toggles sel between floating/tiled state */ +void uicb_toggletag(Display *, jdwm_config *, const char *); /* toggles sel tags with arg's index */ +void uicb_toggleview(Display *, jdwm_config *, const char *); /* toggles the tag with arg's index (in)visible */ +void uicb_view(Display *, jdwm_config *, const char *); /* views the tag with arg's index */ +void uicb_viewprevtags(Display *, jdwm_config *, const char *); +void uicb_tag_viewnext(Display *, jdwm_config *, const char *); /* view only tag just after the first selected */ +void uicb_tag_viewprev(Display *, jdwm_config *, const char *); /* view only tag just before the first selected */ + +typedef struct +{ + regex_t *propregex; + regex_t *tagregex; +} Regs; + +#endif diff --git a/util.c b/util.c new file mode 100644 index 00000000..35fab355 --- /dev/null +++ b/util.c @@ -0,0 +1,50 @@ +/* See LICENSE file for copyright and license details. */ + +#include +#include +#include +#include +#include +#include + +#include "util.h" + +void +eprint(const char *fmt, ...) +{ + va_list ap; + + va_start(ap, fmt); + vfprintf(stderr, fmt, ap); + va_end(ap); + exit(EXIT_FAILURE); +} + +void +spawn(Display * disp, + jdwm_config * jdwmconf __attribute__ ((unused)), + const char *arg) +{ + static char *shell = NULL; + + if(!shell && !(shell = getenv("SHELL"))) + shell = strdup("/bin/sh"); + if(!arg) + return; + /* The double-fork construct avoids zombie processes and keeps the code + * * clean from stupid signal handlers. */ + if(fork() == 0) + { + if(fork() == 0) + { + if(disp) + close(ConnectionNumber(disp)); + setsid(); + execl(shell, shell, "-c", arg, (char *) NULL); + fprintf(stderr, "jdwm: execl '%s -c %s'", shell, arg); + perror(" failed"); + } + exit(EXIT_SUCCESS); + } + wait(0); +} diff --git a/util.h b/util.h new file mode 100644 index 00000000..fec7a340 --- /dev/null +++ b/util.h @@ -0,0 +1,66 @@ +/* See LICENSE file for copyright and license details. */ + +#ifndef JDWM_MEM_H +#define JDWM_MEM_H + +#include "config.h" + +#define ssizeof(foo) (ssize_t)sizeof(foo) +#define countof(foo) (ssizeof(foo) / ssizeof(foo[0])) + +#define p_new(type, count) ((type *)xmalloc(sizeof(type) * (count))) +#define p_clear(p, count) ((void)memset((p), 0, sizeof(*(p)) * (count))) +#define p_realloc(pp, count) xrealloc((void*)(pp) sizeof(**(pp) * (count))) + +#ifdef __GNUC__ + +#define p_delete(mem_pp) \ + do { \ + typeof(**(mem_pp)) **__ptr = (mem_pp); \ + free(*__ptr); \ + *__ptr = NULL; \ + } while(0) + +#else + +#define p_delete(mem_p) \ + do { \ + void *__ptr = (mem_p); \ + free(*__ptr); \ + *(void **)__ptr = NULL; \ + } while (0) + +#endif + +static inline void * __attribute__ ((malloc)) xmalloc(ssize_t size) +{ + void *ptr; + + if(size <= 0) + return NULL; + + ptr = calloc(1, size); + + if(!ptr) + abort(); + + return ptr; +} + +static inline void +xrealloc(void **ptr, ssize_t newsize) +{ + if(newsize <= 0) + p_delete(ptr); + else + { + *ptr = realloc(*ptr, newsize); + if(!*ptr) + abort(); + } +} + +void eprint(const char *, ...) __attribute__ ((noreturn)) __attribute__ ((format(printf, 1, 2))); +void spawn(Display *, jdwm_config *, const char *); + +#endif