commit f7173bd79df77a155444663a262dfc5b0f9f0f42 Author: Julien Danjou Date: Wed Sep 5 20:15:00 2007 +0200 first import 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