commit caaad63c0d40c92b4256cd9d0fc92a2a716e7068 Author: garbeam Date: Fri Nov 18 17:54:58 2005 +0200 added initial files diff --git a/CHANGES b/CHANGES new file mode 100644 index 00000000..35873a81 --- /dev/null +++ b/CHANGES @@ -0,0 +1,92 @@ +3 - (20051101): Tini + +2 - (20050701): Boyd + * new sloppy focus + * new ahwm-alike moving and resizing of frames + * new tabbing through un-/locking frames + * new builtin pager layer + * new builtin icon layer + * new layers per page: + - floating layercontains floating frames + - managed layer contains tiled frames + * new native layout mechanism: C interface implements functions: + init, deinit, manage, unmanage, resize, select + * new default layouts: tiled, float, grid, vsplit + * renamed wmiinput into wmimenu, several cleanups and added + double-buffered rendering + * changed wmikeys to allow grabbing the keyboard for special modes + like resize mode, move mode + * changed wmibar to calculate label sizes dynamically and added + double-buffered rendering + * removed limited generic layout mechanism + * removed nesting from layout mechanism + * fixed following issues: + - http://wmi.modprobe.de/index.php/PITS/00001 + - http://wmi.modprobe.de/index.php/PITS/00002 + - http://wmi.modprobe.de/index.php/PITS/00003 + - http://wmi.modprobe.de/index.php/PITS/00004 + - http://wmi.modprobe.de/index.php/PITS/00005 + - http://wmi.modprobe.de/index.php/PITS/00006 + - http://wmi.modprobe.de/index.php/PITS/00007 + - http://wmi.modprobe.de/index.php/PITS/00008 + - http://wmi.modprobe.de/index.php/PITS/00009 + - http://wmi.modprobe.de/index.php/PITS/00010 + - http://wmi.modprobe.de/index.php/PITS/00011 + - http://wmi.modprobe.de/index.php/PITS/00012 + - http://wmi.modprobe.de/index.php/PITS/00013 + - http://wmi.modprobe.de/index.php/PITS/00014 + - http://wmi.modprobe.de/index.php/PITS/00015 + - http://wmi.modprobe.de/index.php/PITS/00016 + - http://wmi.modprobe.de/index.php/PITS/00017 + - http://wmi.modprobe.de/index.php/PITS/00018 + - http://wmi.modprobe.de/index.php/PITS/00019 + - http://wmi.modprobe.de/index.php/PITS/00020 + - http://wmi.modprobe.de/index.php/PITS/00021 + - http://wmi.modprobe.de/index.php/PITS/00022 + - http://wmi.modprobe.de/index.php/PITS/00023 + - http://wmi.modprobe.de/index.php/PITS/00024 + - http://wmi.modprobe.de/index.php/PITS/00025 + - http://wmi.modprobe.de/index.php/PITS/00026 + - http://wmi.modprobe.de/index.php/PITS/00027 + - http://wmi.modprobe.de/index.php/PITS/00028 + - http://wmi.modprobe.de/index.php/PITS/00029 + - http://wmi.modprobe.de/index.php/PITS/00030 + - http://wmi.modprobe.de/index.php/PITS/00031 + - http://wmi.modprobe.de/index.php/PITS/00032 + - http://wmi.modprobe.de/index.php/PITS/00033 + - http://wmi.modprobe.de/index.php/PITS/00034 + - http://wmi.modprobe.de/index.php/PITS/00035 + - http://wmi.modprobe.de/index.php/PITS/00036 + - http://wmi.modprobe.de/index.php/PITS/00037 + - http://wmi.modprobe.de/index.php/PITS/00038 + - http://wmi.modprobe.de/index.php/PITS/00039 + - http://wmi.modprobe.de/index.php/PITS/00040 + - http://wmi.modprobe.de/index.php/PITS/00041 + - http://wmi.modprobe.de/index.php/PITS/00042 + - http://wmi.modprobe.de/index.php/PITS/00043 + - http://wmi.modprobe.de/index.php/PITS/00044 + - http://wmi.modprobe.de/index.php/PITS/00045 + - http://wmi.modprobe.de/index.php/PITS/00046 + - http://wmi.modprobe.de/index.php/PITS/00047 + - http://wmi.modprobe.de/index.php/PITS/00048 + - http://wmi.modprobe.de/index.php/PITS/00049 + - http://wmi.modprobe.de/index.php/PITS/00050 + - http://wmi.modprobe.de/index.php/PITS/00051 + - http://wmi.modprobe.de/index.php/PITS/00052 + - http://wmi.modprobe.de/index.php/PITS/00053 + +1.1 - (20050613): Zadkiel + * fixed following issues: + - http://wmi.modprobe.de/index.php/PITS/00001 + - http://wmi.modprobe.de/index.php/PITS/00004 + - http://wmi.modprobe.de/index.php/PITS/00007 + - http://wmi.modprobe.de/index.php/PITS/00033 + - http://wmi.modprobe.de/index.php/PITS/00019 + - http://wmi.modprobe.de/index.php/PITS/00028 + - http://wmi.modprobe.de/index.php/PITS/00034 + - http://wmi.modprobe.de/index.php/PITS/00039 + - http://wmi.modprobe.de/index.php/PITS/00027 + - http://wmi.modprobe.de/index.php/PITS/00032 + +1 - (20050601): Uriel + initial release diff --git a/LICENSE b/LICENSE new file mode 100644 index 00000000..3adf98cc --- /dev/null +++ b/LICENSE @@ -0,0 +1,22 @@ +MIT/X Consortium License + +(C)opyright MMIII-MMV Anselm R. Garbe +(C)opyright MMV Georg Neis + +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..56fcc066 --- /dev/null +++ b/Makefile @@ -0,0 +1,70 @@ +# wmii - window manager improved 2 +# (C)opyright MMIV-MMV Anselm R. Garbe + +include config.mk + +SUBDIRS = libcext liblitz libixp libixp2 libwmii cmd + +BIN = cmd/wm/wmii cmd/wm/wmiiwm cmd/wmibar cmd/wmifs \ + cmd/wmikeys cmd/wmimenu cmd/wmiplumb cmd/wmir cmd/wmiwarp + +MAN1 = cmd/wm/wmii.1 cmd/wm/wmiiwm.1 cmd/wmibar.1 cmd/wmifs.1 \ + cmd/wmikeys.1 cmd/wmimenu.1 cmd/wmir.1 + +all: + @echo wmii build options: + @echo "LIBS = ${LIBS}" + @echo "INCLUDES = ${INCLUDES}" + @echo "CFLAGS = ${CFLAGS}" + @echo "LDFLAGS = ${LDFLAGS}" + @echo "CC = ${CC}" + @for i in ${SUBDIRS} cmd/wm; do \ + (cd $$i; ${MAKE}); \ + done + +dist: clean + @mkdir -p wmii-${VERSION} + @cp -R Makefile README LICENSE config.mk rc ${SUBDIRS} extra doc wmii-${VERSION} + @tar -cf wmii-${VERSION}.tar wmii-${VERSION} + @gzip wmii-${VERSION}.tar + @rm -rf wmii-${VERSION} + @echo created distribution wmii-${VERSION}.tar.gz + +clean: + rm -f *.o + @for i in ${SUBDIRS} cmd/wm; do \ + (cd $$i; ${MAKE} clean); \ + done + rm -rf wmii-${VERSION}* + +install: all + @mkdir -p ${DESTDIR}${PREFIX}/bin + @cp -f ${BIN} ${DESTDIR}${PREFIX}/bin + @sed 's|CONFPREFIX|${CONFPREFIX}|; s|9PREFIX|${9PREFIX}|' ${DESTDIR}${PREFIX}/bin/wmii + @for i in ${BIN}; do \ + chmod 755 ${DESTDIR}${PREFIX}/bin/`basename $$i`; \ + done + @echo installed executable files to ${DESTDIR}${PREFIX}/bin + @mkdir -p ${DESTDIR}${CONFPREFIX}/wmii-3 + @cd rc; for i in *; do \ + sed 's|9PREFIX|${9PREFIX}|' <$$i >${DESTDIR}${CONFPREFIX}/wmii-3/$$i; \ + chmod 755 ${DESTDIR}${CONFPREFIX}/wmii-3/$$i; \ + done + @echo installed rc scripts to ${DESTDIR}${CONFPREFIX}/wmii-3 + @mkdir -p ${DESTDIR}${MANPREFIX}/man1 + @cp -f ${MAN1} ${DESTDIR}${MANPREFIX}/man1 + @sed 's|CONFPREFIX|${CONFPREFIX}|' ${DESTDIR}${MANPREFIX}/man1/wmii.1 + @for i in ${MAN1}; do \ + chmod 444 ${DESTDIR}${MANPREFIX}/man1/`basename $$i`; \ + done + @echo installed manual pages to ${DESTDIR}${MANPREFIX}/man1 + +uninstall: + @for i in ${BIN}; do \ + rm -f ${DESTDIR}${PREFIX}/bin/`basename $$i`; \ + done + @for i in ${MAN1}; do \ + rm -f ${DESTDIR}${MANPREFIX}/man1/`basename $$i`; \ + done + @rm -rf ${DESTDIR}${CONFPREFIX}/wmii-3 + @echo "uninstalled wmii" diff --git a/README b/README new file mode 100644 index 00000000..ba4db062 --- /dev/null +++ b/README @@ -0,0 +1,86 @@ +Abstract +-------- +window manager improved 2 (wmii) [1] is an improved, modularized and +lightweight X11 window manager which supports tabbed, tiled and +conventional window management through layouts. +wmii consists of components that are independent processes and +communicate via a socket-based virtual filesystem which is oriented +on the "everything is file" paradigm of the Plan 9 [2] operating system. +The core distribution of wmii contains the window manager itself, +a master file system routing utility (wmifs), a generic bar (wmibar), +a shortcut handler (wmikeys), and a generic interaction menu (wmimenu) +beside several tiny utilities like wmir, wmiplumb, and wmiwarp. + + +Requirements +------------ +In order to build wmii you need the Xlib header files. +Furthermore, the wmii configuration scripts rely on the 9rc package [3] +which contains ports of various Plan 9 standard tools, in particular the +rc shell. + + +Installation +------------ +Edit config.mk to match your local setup. wmii is installed into +the /usr/local hierarchy by default. + +Afterwards enter the following command to build and install wmii (if +necessary as root): + + $ make clean install + + +Running wmii +------------ +Add the following line to your .xinitrc to start wmii using startx: + + exec wmii + +In order to connect wmii or wmir to a specific display, make sure that +DISPLAY environment variable is set correctly, e.g.: + + $ setenv DISPLAY foo.bar:1 + $ wmii + +This will start wmii on display :1 of the host foo.bar if it is present +in your .xinitrc. + + +Configuration +------------- +The configuration of wmii is done by customizing the existing actions. +Customizing an action means copying the appropriate file from the directory +$WMII_CONFDIR (usually /usr/local/etc/wmii-3) to +$HOME/.wmii-3 and editing it to fit your needs. The action of main +interest is called 'wmirc': it is executed on startup. + + +Credits +------- +Beside all thanks to the wmi contributors, the following people have +contributed especially to wmii in various ways: + +- Christoph Wegscheider +- Georg Neis +- Uwe Zeisberger +- Uriel +- Scot Doyle +- Sebastian Hartmann +- Bernhard Leiner +- Jonas Domeij +- Vincent <10 (dot) 50 (at) free (dot) fr> +- Oliver Kopp +- Sebastian Roth +- Steve Hoffman +- Christof Musik +- Steffen Liebergeld + + +References +---------- +[1] http://wmii.de +[2] http://www.cs.bell-labs.com/plan9dist/ +[3] http://wmii.de/repos/9base/ + +--Anselm R. Garbe diff --git a/cmd/Makefile b/cmd/Makefile new file mode 100644 index 00000000..29fb56a2 --- /dev/null +++ b/cmd/Makefile @@ -0,0 +1,86 @@ +# window manager improved 2 utilities +# (C)opyright MMIV-MMV Anselm R. Garbe + +include ../config.mk + +CFLAGS += -I../liblitz -I../libixp -I../libwmii -I../libcext +LDFLAGS += -L../liblitz -llitz -L../libixp -lixp \ + -L../libwmii -lwmii -L../libcext -lcext +LDFLAGS2 += ${LIBS} -L../liblitz -llitz -L../libixp2 -lixp \ + -L../libwmii -lwmii -L../libcext -lcext + +SRC_bar = wmibar.c +OBJ_bar = ${SRC_bar:.c=.o} + +SRC_bar2 = wmibar2.c +OBJ_bar2 = ${SRC_bar2:.c=.o} + +SRC_menu = wmimenu.c +OBJ_menu = ${SRC_menu:.c=.o} + +SRC_r2 = wmir2.c +OBJ_r2 = ${SRC_r2:.c=.o} + +SRC_r = wmir.c +OBJ_r = ${SRC_r:.c=.o} + +SRC_fs = wmifs.c +OBJ_fs = ${SRC_fs:.c=.o} + +SRC_keys = wmikeys.c +OBJ_keys = ${SRC_keys:.c=.o} + +SRC_plumb = wmiplumb.c +OBJ_plumb = ${SRC_plumb:.c=.o} + +SRC_warp = wmiwarp.c +OBJ_warp = ${SRC_warp:.c=.o} + +all: wmibar wmibar2 wmimenu wmir wmir2 wmifs wmikeys wmiplumb wmiwarp + @echo built wmi commands + +.c.o: + @echo CC $< + @${CC} -c ${CFLAGS} $< + +wmibar: ${OBJ_bar} + @echo LD $@ + @${CC} -o $@ ${OBJ_bar} ${LDFLAGS} + +wmibar2: ${OBJ_bar2} + @echo LD $@ + @${CC} -o $@ ${OBJ_bar2} ${LDFLAGS2} + +wmimenu: ${OBJ_menu} + @echo LD $@ + @${CC} -o $@ ${OBJ_menu} ${LDFLAGS} + +wmir2: ${OBJ_r2} + @echo LD $@ + @${CC} -o $@ ${OBJ_r2} -g -static -L${LIBDIR} -L/usr/lib -lc \ + -L../libixp2 -lixp -L../libcext -lcext + +wmir: ${OBJ_r} + @echo LD $@ + @${CC} -o $@ ${OBJ_r} -g -static -L${LIBDIR} -L/usr/lib -lc \ + -L../libixp -lixp -L../libcext -lcext + +wmifs: ${OBJ_fs} + @echo LD $@ + @${CC} -o $@ ${OBJ_fs} ${LDFLAGS} + +wmikeys: ${OBJ_keys} + @echo LD $@ + @${CC} -o $@ ${OBJ_keys} ${LDFLAGS} + +wmiplumb: ${OBJ_plumb} + @echo LD $@ + @${CC} -o $@ ${OBJ_plumb} ${LDFLAGS} + +wmiwarp: ${OBJ_warp} + @echo LD $@ + @${CC} -o $@ ${OBJ_warp} ${LDFLAGS} + +clean: + rm -f wmibar wmibar2 wmimenu wmir wmir2 wmikeys wmiplumb wmifs\ + wmiwarp *.o diff --git a/cmd/wm/Makefile b/cmd/wm/Makefile new file mode 100644 index 00000000..f7bb932e --- /dev/null +++ b/cmd/wm/Makefile @@ -0,0 +1,15 @@ +# wm - window manager improved 2 +# (C)opyright MMIV-MMV Anselm R. Garbe + +include ../../config.mk + +all: layout.c + @${MAKE} -f Makefile.wm + @echo built core wm + +layout.c: + @echo SH mklayout.sh + @sh mklayout.sh + +clean: + rm -f wmiiwm *.o layout.* diff --git a/cmd/wm/Makefile.wm b/cmd/wm/Makefile.wm new file mode 100644 index 00000000..7abd087f --- /dev/null +++ b/cmd/wm/Makefile.wm @@ -0,0 +1,25 @@ +# wm - window manager improved 2 +# (C)opyright MMIV-MMV Anselm R. Garbe + +include ../../config.mk + +CFLAGS += -I../../liblitz -I../../libixp -I../../libwmii \ + -I../../libcext +LDFLAGS += -L../../liblitz -llitz -L../../libixp -lixp \ + -L../../libwmii -lwmii -L../../libcext -lcext + +SRC = wm.c core.c client.c event.c mouse.c frame.c page.c layout.c + +include layout.mk + +OBJ = ${SRC:.c=.o} + +all: wmiiwm + +.c.o: + @echo CC $< + @${CC} -c ${CFLAGS} $< + +wmiiwm: ${OBJ} + @echo LD $@ + @${CC} -o $@ ${OBJ} ${LDFLAGS} diff --git a/cmd/wm/area.c b/cmd/wm/area.c new file mode 100644 index 00000000..2359f3a7 --- /dev/null +++ b/cmd/wm/area.c @@ -0,0 +1,67 @@ +/* + * (C)opyright MMIV-MMV Anselm R. Garbe + * See LICENSE file for license details. + */ + +#include +#include +#include + +#include "wm.h" + +#include + +static Area zero_area = {0}; + +void +free_area(Area* a) +{ + ixp_remove_file(ixps, a->files[A_PREFIX]); + free(f); +} + +void +destroy_area(Area *a) +{ + unsigned int i; + a->layout->deinit(a); + for(i = 0; a->frames && a->frames[i]; i++); + destroy_frame(i); + free_area(a); +} + +void +focus_area(Area *a, int raise, int up, int down) +{ + Page *p = f->page; + Frame *old; + if (!p) + return; + + old = get_selected(p); + if (down && f->clients) + focus_client(f->clients[f->sel], raise, 0); + + if (is_managed_frame(f)) { + p->managed_stack = (Frame **) + attach_item_begin(detach_item + ((void **) p->managed_stack, f, + sizeof(Frame *)), f, sizeof(Frame *)); + p->files[P_MANAGED_SELECTED]->content = + f->files[F_PREFIX]->content; + p->files[P_MODE]->content = p->files[P_MANAGED_PREFIX]->content; + } else { + p->floating_stack = (Frame **) + attach_item_begin(detach_item + ((void **) p->floating_stack, f, + sizeof(Frame *)), f, sizeof(Frame *)); + p->files[P_FLOATING_SELECTED]->content = + f->files[F_PREFIX]->content; + p->files[P_MODE]->content = p->files[P_FLOATING_PREFIX]->content; + if (raise) + XRaiseWindow(dpy, f->win); + } + if (up) + focus_page(p, raise, 0); +} + diff --git a/cmd/wm/client.c b/cmd/wm/client.c new file mode 100644 index 00000000..6143e059 --- /dev/null +++ b/cmd/wm/client.c @@ -0,0 +1,366 @@ +/* + * (C)opyright MMIV-MMV Anselm R. Garbe + * See LICENSE file for license details. + */ + +#include +#include +#include +#include + +#include "wm.h" + +#include + +static Client zero_client = {0}; + +Client * +alloc_client(Window w) +{ + static int id = 0; + char buf[MAX_BUF]; + char buf2[MAX_BUF]; + XClassHint ch; + Client *c = (Client *) emalloc(sizeof(Client)); + + *c = zero_client; + c->win = w; + snprintf(buf, MAX_BUF, "/detached/client/%d", id); + c->files[C_PREFIX] = ixp_create(ixps, buf); + win_prop(dpy, c->win, XA_WM_NAME, buf2, MAX_BUF); + snprintf(buf, MAX_BUF, "/detached/client/%d/name", id); + c->files[C_NAME] = wmii_create_ixpfile(ixps, buf, buf2); + if (XGetClassHint(dpy, c->win, &ch)) { + snprintf(buf, MAX_BUF, "/detached/client/%d/class", id); + c->files[C_CLASS] = wmii_create_ixpfile(ixps, buf, ch.res_class); + snprintf(buf, MAX_BUF, "/detached/client/%d/instance", id); + c->files[C_INSTANCE] = wmii_create_ixpfile(ixps, buf, ch.res_name); + } else { + snprintf(buf, MAX_BUF, "/detached/client/%d/class", id); + c->files[C_CLASS] = ixp_create(ixps, buf); + snprintf(buf, MAX_BUF, "/detached/client/%d/instance", id); + c->files[C_INSTANCE] = ixp_create(ixps, buf); + } + id++; + clients = (Client **) attach_item_end((void **) clients, c, sizeof(Client *)); + XSelectInput(dpy, c->win, CLIENT_MASK); + return c; +} + +void +set_client_state(Client * c, int state) +{ + long data[2]; + + data[0] = (long) state; + data[1] = (long) None; + XChangeProperty(dpy, c->win, wm_state, wm_state, 32, + PropModeReplace, (unsigned char *) data, 2); +} + +void +show_client(Client * c) +{ + XSelectInput(dpy, c->win, CLIENT_MASK & ~StructureNotifyMask); + XMapWindow(dpy, c->win); + XSelectInput(dpy, c->win, CLIENT_MASK); + set_client_state(c, NormalState); + grab_client(c, Mod1Mask, Button1); + grab_client(c, Mod1Mask, Button3); +} + +void +hide_client(Client * c) +{ + ungrab_client(c, AnyModifier, AnyButton); + XSelectInput(dpy, c->win, CLIENT_MASK & ~StructureNotifyMask); + XUnmapWindow(dpy, c->win); + XSelectInput(dpy, c->win, CLIENT_MASK); + set_client_state(c, WithdrawnState); +} + +void +reparent_client(Client * c, Window w, int x, int y) +{ + XSelectInput(dpy, c->win, CLIENT_MASK & ~StructureNotifyMask); + XReparentWindow(dpy, c->win, w, x, y); + XSelectInput(dpy, c->win, CLIENT_MASK); +} + +void +grab_client(Client * c, unsigned long mod, unsigned int button) +{ + XSelectInput(dpy, c->win, CLIENT_MASK & ~StructureNotifyMask); + XGrabButton(dpy, button, mod, c->win, False, + ButtonPressMask, GrabModeAsync, GrabModeAsync, None, None); + if ((mod != AnyModifier) && num_lock_mask) { + XGrabButton(dpy, button, mod | num_lock_mask, c->win, + False, ButtonPressMask, GrabModeAsync, GrabModeAsync, + None, None); + XGrabButton(dpy, button, mod | num_lock_mask | LockMask, + c->win, False, ButtonPressMask, GrabModeAsync, + GrabModeAsync, None, None); + } + XSelectInput(dpy, c->win, CLIENT_MASK); + XSync(dpy, False); +} + +void +ungrab_client(Client * c, unsigned long mod, unsigned int button) +{ + XSelectInput(dpy, c->win, CLIENT_MASK & ~StructureNotifyMask); + XUngrabButton(dpy, button, mod, c->win); + if (mod != AnyModifier && num_lock_mask) { + XUngrabButton(dpy, button, mod | num_lock_mask, c->win); + XUngrabButton(dpy, button, mod | num_lock_mask | LockMask, c->win); + } + XSelectInput(dpy, c->win, CLIENT_MASK); + XSync(dpy, False); +} + +void +configure_client(Client * c) +{ + XConfigureEvent e; + e.type = ConfigureNotify; + e.event = c->win; + e.window = c->win; + e.x = c->rect.x; + e.y = c->rect.y; + if (c->frame) { + XRectangle *frect = rect_of_frame(c->frame); + e.x += frect->x; + e.y += frect->y; + } + e.width = c->rect.width; + e.height = c->rect.height; + e.border_width = c->border; + e.above = None; + e.override_redirect = False; + + XSelectInput(dpy, c->win, CLIENT_MASK & ~StructureNotifyMask); + XSendEvent(dpy, c->win, False, StructureNotifyMask, (XEvent *) & e); + XSelectInput(dpy, c->win, CLIENT_MASK); + XSync(dpy, False); +} + +void +close_client(Client * c) +{ + if (c->proto & PROTO_DEL) + send_message(dpy, c->win, wm_protocols, wm_delete); + else + XKillClient(dpy, c->win); +} + +void +_init_client(Client * c, XWindowAttributes * wa) +{ + long msize; + c->rect.x = wa->x; + c->rect.y = wa->y; + c->border = wa->border_width; + c->rect.width = wa->width + 2 * c->border; + c->rect.height = wa->height + 2 * c->border; + XSetWindowBorderWidth(dpy, c->win, 0); + XSelectInput(dpy, c->win, PropertyChangeMask); + c->proto = win_proto(c->win); + XGetTransientForHint(dpy, c->win, &c->trans); + /* size hints */ + if (!XGetWMNormalHints(dpy, c->win, &c->size, &msize) + || !c->size.flags) + c->size.flags = PSize; + XAddToSaveSet(dpy, c->win); +} + +void +handle_client_property(Client * c, XPropertyEvent * e) +{ + char buf[1024]; + long msize; + + buf[0] = '\0'; + if (e->state == PropertyDelete) + return; /* ignore */ + + if (e->atom == wm_protocols) { + /* update */ + c->proto = win_proto(c->win); + return; + } + switch (e->atom) { + case XA_WM_NAME: + win_prop(dpy, c->win, XA_WM_NAME, buf, sizeof(buf)); + if (strlen(buf)) { + if (c->files[C_NAME]->content) + free(c->files[C_NAME]->content); + c->files[C_NAME]->content = estrdup(buf); + c->files[C_NAME]->size = strlen(buf); + } + if (c->frame) + draw_client(c); + invoke_core_event(defaults[WM_EVENT_CLIENT_UPDATE]); + break; + case XA_WM_TRANSIENT_FOR: + XGetTransientForHint(dpy, c->win, &c->trans); + break; + case XA_WM_NORMAL_HINTS: + if (!XGetWMNormalHints(dpy, c->win, &c->size, &msize) + || !c->size.flags) { + c->size.flags = PSize; + } + break; + } +} + +void +free_client(Client * c) +{ + clients = + (Client **) detach_item((void **) clients, c, sizeof(Client *)); + ixp_remove_file(ixps, c->files[C_PREFIX]); + if (ixps->errstr) + fprintf(stderr, "wmiiwm: free_client(): %s\n", ixps->errstr); + free(c); +} + +/* speed reasoned function for client property change */ +void +draw_client(Client * c) +{ + Frame *f = c->frame; + unsigned int tabh = tab_height(f); + int i, size; + XRectangle *frect; + int tw; + + if (!tabh) + return; + size = count_items((void **) f->clients); + frect = rect_of_frame(f); + tw = frect->width; + if (size) + tw /= size; + for (i = 0; f->clients[i] && f->clients[i] != c; i++); + + if (!f->clients[i + 1]) { + int xoff = i * tw; + draw_tab(f, c->files[C_NAME]->content, xoff, 0, + frect->width - xoff, tabh, ISSELFRAME(f) + && f->clients[f->sel] == c); + } else + draw_tab(f, c->files[C_NAME]->content, i * tw, 0, tw, tabh, + is_selected(f) && f->clients[f->sel] == c); +} + +void +draw_clients(Frame * f) +{ + unsigned int tabh = tab_height(f); + int i, size = count_items((void **) f->clients); + XRectangle *frect = rect_of_frame(f); + int tw = frect->width; + + if (!tabh || !size) + return; + if (size) + tw /= size; + for (i = 0; f->clients[i]; i++) { + if (!f->clients[i + 1]) { + int xoff = i * tw; + draw_tab(f, f->clients[i]->files[C_NAME]->content, + xoff, 0, frect->width - xoff, tabh, is_selected(f) + && f->clients[f->sel] == f->clients[i]); + break; + } else + draw_tab(f, f->clients[i]->files[C_NAME]->content, + i * tw, 0, tw, tabh, is_selected(f) + && f->clients[f->sel] == f->clients[i]); + } + XSync(dpy, False); +} + +int +manage_class_instance(Client * c) +{ + char buf[MAX_BUF]; + File *f; + char *class = (char *) c->files[C_CLASS]->content; + char *inst = (char *) c->files[C_INSTANCE]->content; + + if (!c->files[C_CLASS]->content || !c->files[C_INSTANCE]->content) + return 1; + + snprintf(buf, sizeof(buf), "/default/client/%s:%s/manage", + class ? class : "", inst ? inst : ""); + f = ixp_walk(ixps, buf); + if (!f) { + snprintf(buf, sizeof(buf), "/default/client/%s:%s/manage", + class ? class : "", "*"); + f = ixp_walk(ixps, buf); + } + if (f && f->content) + return _strtonum(f->content, 0, 1); + return 1; +} + +void +gravitate(Client * c, unsigned int tabh, unsigned int bw, int invert) +{ + int dx = 0, dy = 0; + int gravity = NorthWestGravity; + + if (c->size.flags & PWinGravity) { + gravity = c->size.win_gravity; + } + /* y */ + switch (gravity) { + case StaticGravity: + case NorthWestGravity: + case NorthGravity: + case NorthEastGravity: + dy = tabh; + break; + case EastGravity: + case CenterGravity: + case WestGravity: + dy = -(c->rect.height / 2) + tabh; + break; + case SouthEastGravity: + case SouthGravity: + case SouthWestGravity: + dy = -c->rect.height; + break; + default: /* don't care */ + break; + } + + /* x */ + switch (gravity) { + case StaticGravity: + case NorthWestGravity: + case WestGravity: + case SouthWestGravity: + dx = bw; + break; + case NorthGravity: + case CenterGravity: + case SouthGravity: + dx = -(c->rect.width / 2) + bw; + break; + case NorthEastGravity: + case EastGravity: + case SouthEastGravity: + dx = -(c->rect.width + bw); + break; + default: /* don't care */ + break; + } + + if (invert) { + dx = -dx; + dy = -dy; + } + c->rect.x += dx; + c->rect.y += dy; +} diff --git a/cmd/wm/core.c b/cmd/wm/core.c new file mode 100644 index 00000000..38c206bb --- /dev/null +++ b/cmd/wm/core.c @@ -0,0 +1,582 @@ +/* + * (C)opyright MMIV-MMV Anselm R. Garbe + * See LICENSE file for license details. + */ + +#include +#include +#include +#include +#include +#include + +#include "wm.h" + +static void new_page(void *obj, char *cmd); +static void _select_page(void *obj, char *cmd); +static void _destroy_page(void *obj, char *cmd); +static void quit(void *obj, char *cmd); +static void _attach_client(void *obj, char *cmd); +static void _detach_client(void *obj, char *cmd); +static void _close_client(void *obj, char *cmd); +static void pager(void *obj, char *cmd); +static void icons(void *obj, char *cmd); + +/* action table for /ctl namespace */ +Action core_acttbl[] = { + {"new", new_page}, + {"destroy", _destroy_page}, + {"select", _select_page}, + {"attach", _attach_client}, + {"detach", _detach_client}, + {"close", _close_client}, + {"quit", quit}, + {"pager", pager}, + {"icons", icons}, + {0, 0} +}; + +int +comp_obj(void *c1, void *c2) +{ + return c1 == c2; +} + +void +run_action(File * f, void *obj, Action * acttbl) +{ + int i; + size_t len; + + if (!f->content) + return; + for (i = 0; acttbl[i].name; i++) { + len = strlen(acttbl[i].name); + if (!strncmp(acttbl[i].name, (char *) f->content, len)) { + if (f->size > len) + acttbl[i].func(obj, &((char *) f->content)[len + 1]); + else + acttbl[i].func(obj, 0); + return; + break; + } + } + fprintf(stderr, "wmiiwm: unknown action '%s'\n" + " or invalid ctl device\n", (char *) f->content); +} + +static void +quit(void *obj, char *cmd) +{ + ixps->runlevel = SHUTDOWN; +} + +void +invoke_core_event(File * f) +{ + if (!f->content) + return; + spawn(dpy, f->content); +} + +void +focus_page(Page * p, int raise, int down) +{ + if (!pages) + return; + if (p != pages[sel]) { + hide_page(pages[sel]); + sel = index_item((void **) pages, p); + show_page(pages[sel]); + defaults[WM_SEL_PAGE]->content = p->files[P_PREFIX]->content; + invoke_core_event(defaults[WM_EVENT_PAGE_UPDATE]); + } + if (down) + focus_area(p->areas[p->sel], raise, 0, down); +} + +unsigned int +tab_height(Frame *f) +{ + if (_strtonum(f->files[F_TAB]->content, 0, 1)) + return font->ascent + font->descent + 4; + return 0; +} + +unsigned int +border_width(Frame *f) +{ + if(_strtonum(f->files[F_BORDER]->content, 0, 1)) + return BORDER_WIDTH; + return 0; +} + +static void +scale_rect(XRectangle * from_dim, XRectangle * to_dim, + XRectangle * src, XRectangle * tgt) +{ + double wfact = (double) to_dim->width / (double) from_dim->width; + double hfact = (double) to_dim->height / (double) from_dim->height; + + tgt->x = to_dim->x + (src->x * wfact); + tgt->y = to_dim->y + (src->y * hfact); + tgt->width = (src->width * wfact); + tgt->height = (src->height * hfact); + + if (tgt->width < 1) + tgt->width = 1; + if (tgt->height < 1) + tgt->height = 1; +} + +static void +draw_pager_page(Page * p, Draw * d) +{ + unsigned int i, j; + XRectangle r = d->rect; + char name[4]; + if (p == pages[sel]) { + d->bg = blitz_loadcolor(dpy, screen_num, defaults[WM_SEL_BG_COLOR]->content); + d->fg = blitz_loadcolor(dpy, screen_num, defaults[WM_SEL_FG_COLOR]->content); + d->border = blitz_loadcolor(dpy, screen_num, defaults[WM_SEL_BORDER_COLOR]->content); + d->font = font; + } else { + d->bg = blitz_loadcolor(dpy, screen_num, defaults[WM_NORM_BG_COLOR]->content); + d->fg = blitz_loadcolor(dpy, screen_num, defaults[WM_NORM_FG_COLOR]->content); + d->border = blitz_loadcolor(dpy, screen_num, defaults[WM_NORM_BORDER_COLOR]->content); + d->font = font; + } + snprintf(name, sizeof(name), "%d", index_item((void **)pages, p)); + d->data = name; + blitz_drawlabel(dpy, d); + XSync(dpy, False); + + for (i = 0; p->areas[i]; i++) { + for(j = 0; p->areas[i]->frames[j]; j++) { + if (i == p->sel && j == p->areas[i]->sel) { + d->bg = blitz_loadcolor(dpy, screen_num, defaults[WM_SEL_BG_COLOR]->content); + d->fg = blitz_loadcolor(dpy, screen_num, defaults[WM_SEL_FG_COLOR]->content); + d->border = blitz_loadcolor(dpy, screen_num, defaults[WM_SEL_BORDER_COLOR]->content); + d->font = font; + } else { + d->bg = blitz_loadcolor(dpy, screen_num, defaults[WM_NORM_BG_COLOR]->content); + d->fg = blitz_loadcolor(dpy, screen_num, defaults[WM_NORM_FG_COLOR]->content); + d->border = blitz_loadcolor(dpy, screen_num, defaults[WM_NORM_BORDER_COLOR]->content); + d->font = font; + } + d->data = p->areas[i]->frames[j]->clients[p->areas[i]->frames[j]->sel]->files[C_NAME]->content; + scale_rect(&rect, &r, &p->areas[i]->rect, &d->rect); + blitz_drawlabel(dpy, d); + XSync(dpy, False); /* do not clear upwards */ + } + } +} + +static void +draw_pager() +{ + unsigned int ic, ir, tw, th, rows, cols, size; + int i = 0; + int dx; + Draw d = {0}; + + blitz_getbasegeometry((void **) pages, &size, &cols, &rows); + dx = (cols - 1) * GAP; /* GAPpx space */ + tw = (rect.width - dx) / cols; + th = ((double) tw / rect.width) * rect.height; + d.drawable = transient; + d.gc = transient_gc; + for (ir = 0; ir < rows; ir++) { + for (ic = 0; ic < cols; ic++) { + d.rect.x = ic * tw + (ic * GAP); + d.rect.width = tw; + if (rows == 1) + d.rect.y = 0; + else + d.rect.y = ir * (rect.height - th) / (rows - 1); + d.rect.height = th; + if (!pages[i]) + return; + draw_pager_page(pages[i], &d); + i++; + } + } +} + +static Page * +xy_to_pager_page(int x, int y) +{ + unsigned int ic, ir, tw, th, rows, cols, size; + int i = 0; + int dx; + XRectangle r; + + if (!pages) + return 0; + blitz_getbasegeometry((void **) pages, &size, &cols, &rows); + dx = (cols - 1) * GAP; /* GAPpx space */ + tw = (rect.width - dx) / cols; + th = ((double) tw / rect.width) * rect.height; + + for (ir = 0; ir < rows; ir++) { + for (ic = 0; ic < cols; ic++) { + r.x = ic * tw + (ic * GAP); + r.width = tw; + if (rows == 1) + r.y = 0; + else + r.y = ir * (rect.height - th) / (rows - 1); + r.height = th; + if (!pages[i]) + return 0; + if (blitz_ispointinrect(x, y, &r)) + return pages[i]; + i++; + } + } + return 0; +} + +static int +handle_kpress(XKeyEvent * e) +{ + KeySym ksym = XKeycodeToKeysym(dpy, e->keycode, 0); + + if (ksym >= XK_1 && ksym <= XK_9) + return ksym - XK_1; + else if (ksym == XK_0) + return 9; + else if (ksym >= XK_a && ksym <= XK_z) + return 10 + ksym - XK_a; + + return -1; +} + +static void +pager(void *obj, char *cmd) +{ + XEvent ev; + int i; + + if (!pages) + return; + + XClearWindow(dpy, transient); + XMapRaised(dpy, transient); + draw_pager(); + while (XGrabKeyboard(dpy, transient, True, GrabModeAsync, GrabModeAsync, CurrentTime) != GrabSuccess) + usleep(1000); + + for (;;) { + while (!XCheckWindowEvent(dpy, transient, ButtonPressMask | KeyPressMask, &ev)) { + usleep(20000); + continue; + } + + switch (ev.type) { + case KeyPress: + XUnmapWindow(dpy, transient); + if ((i = handle_kpress(&ev.xkey)) != -1) + if (i < count_items((void **) pages)) + focus_page(pages[i], 0, 1); + XUngrabKeyboard(dpy, CurrentTime); + return; + break; + case ButtonPress: + XUnmapWindow(dpy, transient); + if (ev.xbutton.button == Button1) { + Page *p = xy_to_pager_page(ev.xbutton.x, ev.xbutton.y); + if (p) + focus_page(p, 0, 1); + } + return; + break; + } + } +} + +static void +draw_icons() +{ + unsigned int i, ic, ir, tw, th, rows, cols, size; + int dx, dy; + + if (!detached) + return; + blitz_getbasegeometry((void **) detached, &size, &cols, &rows); + dx = (cols - 1) * GAP; /* GAPpx space */ + dy = (rows - 1) * GAP; /* GAPpx space */ + tw = (rect.width - dx) / cols; + th = (rect.height - dy) / rows; + + XClearWindow(dpy, transient); + XMapRaised(dpy, transient); + i = 0; + for (ir = 0; ir < rows; ir++) { + for (ic = 0; ic < cols; ic++) { + Client *c = detached[i++]; + XRectangle cr; + if (!c) + return; + cr.x = ic * tw + (ic * GAP); + cr.y = ir * th + (ir * GAP); + cr.width = tw; + cr.height = th; + XMoveResizeWindow(dpy, c->win, cr.x, cr.y, cr.width, + cr.height); + configure_client(c); + show_client(c); + XRaiseWindow(dpy, c->win); + grab_client(c, AnyModifier, AnyButton); + XSync(dpy, False); + } + } +} + + +static void +icons(void *obj, char *cmd) +{ + XEvent ev; + int i, n; + Client *c; + + if (!detached) + return; + + XClearWindow(dpy, transient); + XMapRaised(dpy, transient); + draw_icons(); + while (XGrabKeyboard(dpy, transient, True, GrabModeAsync, GrabModeAsync, CurrentTime) != GrabSuccess) + usleep(1000); + + for (;;) { + while (!XCheckMaskEvent(dpy, ButtonPressMask | KeyPressMask, &ev)) { + usleep(20000); + continue; + } + switch (ev.type) { + case KeyPress: + XUnmapWindow(dpy, transient); + if ((n = handle_kpress(&ev.xkey)) != -1) { + for (i = 0; detached && detached[i]; i++) + hide_client(detached[i]); + if (n - 1 < i) { + c = detached[n]; + detached = (Client **) detach_item((void **) detached, c, + sizeof(Client *)); + attach_client(c); + } + } else { + for (i = 0; detached && detached[i]; i++) + hide_client(detached[i]); + } + XUngrabKeyboard(dpy, CurrentTime); + return; + break; + case ButtonPress: + if (ev.xbutton.button == Button1) { + XUnmapWindow(dpy, transient); + for (i = 0; detached && detached[i]; i++) + hide_client(detached[i]); + if ((c = win_to_client(ev.xbutton.window))) { + detached = + (Client **) detach_item((void **) detached, c, + sizeof(Client *)); + attach_client(c); + } + XUngrabKeyboard(dpy, CurrentTime); + } + return; + break; + } + } +} + +static void +_close_client(void *obj, char *cmd) +{ + if (clients && clients[sel_client]) + close_client(clients[sel_client]); +} + +static void +_attach_client(void *obj, char *cmd) +{ + if (detached) { + Client *c = detached[0]; + detached = + (Client **) detach_item((void **) detached, c, + sizeof(Client *)); + attach_client(c); + } +} + +static void +_detach_client(void *obj, char *cmd) +{ + Frame *f; + if (!pages) + return; + f = SELFRAME(pages[sel]); + if (!f) + return; + detach_client_from_frame(f->clients[f->sel], 0, 0); +} + +static void +_select_page(void *obj, char *cmd) +{ + if (!pages || !cmd) + return; + if (!strncmp(cmd, "prev", 5)) + sel = index_prev_item((void **) pages, pages[sel]); + else if (!strncmp(cmd, "next", 5)) + sel = index_next_item((void **) pages, pages[sel]); + else + sel = _strtonum(cmd, 0, count_items((void **)pages)); + focus_page(pages[sel], 0, 1); +} + +void +destroy_page(Page * p) +{ + unsigned int i; + for (i = 0; p->areas[i]; i++) + destroy_area(p->areas[i]); + free_page(p); + if (pages) { + show_page(pages[sel]); + defaults[WM_SEL_PAGE]->content = pages[sel]->files[P_PREFIX]->content; + focus_page(pages[sel], 0, 1); + invoke_core_event(defaults[WM_EVENT_PAGE_UPDATE]); + } +} + +static void +_destroy_page(void *obj, char *cmd) +{ + if (!pages) + return; + destroy_page(pages[sel]); +} + +static void +new_page(void *obj, char *cmd) +{ + if (pages) + hide_page(pages[sel]); + alloc_page("0"); +} + +Client * +win_to_client(Window w) +{ + int i; + + for (i = 0; clients && clients[i]; i++) + if (clients[i]->win == w) + return clients[i]; + return 0; +} + +void +scan_wins() +{ + int i; + unsigned int num; + Window *wins; + XWindowAttributes wa; + Window d1, d2; + Client *c; + + if (XQueryTree(dpy, root, &d1, &d2, &wins, &num)) { + for (i = 0; i < num; i++) { + if (!XGetWindowAttributes(dpy, wins[i], &wa)) + continue; + if (wa.override_redirect + || XGetTransientForHint(dpy, wins[i], &d1)) + continue; + if (wa.map_state == IsViewable) { + c = alloc_client(wins[i]); + _init_client(c, &wa); + attach_client(c); + } + } + } + if (wins) + XFree(wins); +} + +void * +get_func(void *acttbl[][2], int rows, char *fname) +{ + int i; + for (i = 0; i < rows; i++) { + if (!strncmp((char *) acttbl[i][0], fname, MAX_BUF)) { + return acttbl[i][1]; + } + } + return 0; +} + +int +win_proto(Window w) +{ + Atom *protocols; + long res; + int protos = 0; + int i; + + res = property(dpy, w, wm_protocols, XA_ATOM, + 20L, ((unsigned char **) &protocols)); + if (res <= 0) { + return protos; + } + for (i = 0; i < res; i++) { + if (protocols[i] == wm_delete) { + protos |= PROTO_DEL; + } + } + free((char *) protocols); + return protos; +} + +int +win_state(Window w) +{ + /* state hints */ + XWMHints *hints = XGetWMHints(dpy, w); + int res; + + long *prop = 0; + if (property(dpy, w, wm_state, wm_state, + 2L, ((unsigned char **) &prop)) > 0) { + res = (int) *prop; + free((long *) prop); + } else { + res = hints ? hints->initial_state : NormalState; + } + + if (hints) { + free(hints); + } + return res; +} + +void +handle_after_write(IXPServer * s, File * f) +{ + if (f == defaults[WM_CTL]) + run_action(f, 0, core_acttbl); + else if (f == defaults[WM_TRANS_COLOR]) { + unsigned long col[1]; + col[0] = xorcolor.pixel; + XFreeColors(dpy, DefaultColormap(dpy, screen_num), col, 1, 0); + XAllocNamedColor(dpy, DefaultColormap(dpy, screen_num), + defaults[WM_TRANS_COLOR]->content, + &xorcolor, &xorcolor); + XSetForeground(dpy, xorgc, xorcolor.pixel); + } + check_event(0); +} diff --git a/cmd/wm/event.c b/cmd/wm/event.c new file mode 100644 index 00000000..2a564869 --- /dev/null +++ b/cmd/wm/event.c @@ -0,0 +1,329 @@ +/* + * (C)opyright MMIV-MMV Anselm R. Garbe + * See LICENSE file for license details. + */ + +#include +#include +#include +#include + +#include "wm.h" + +/* local functions */ +static void handle_buttonpress(XEvent * e); +static void handle_configurerequest(XEvent * e); +static void handle_destroynotify(XEvent * e); +static void handle_expose(XEvent * e); +static void handle_maprequest(XEvent * e); +static void handle_motionnotify(XEvent * e); +static void handle_propertynotify(XEvent * e); +static void handle_unmapnotify(XEvent * e); +static void handle_enternotify(XEvent * e); +static void update_ignore_enternotify_hack(XEvent * e); + +static unsigned int ignore_enternotify_hack = 0; + +void (*handler[LASTEvent]) (XEvent *); + +void +init_event_hander() +{ + int i; + /* init event handler */ + for (i = 0; i < LASTEvent; i++) { + handler[i] = 0; + } + handler[ButtonPress] = handle_buttonpress; + handler[CirculateNotify] = update_ignore_enternotify_hack; + handler[ConfigureRequest] = handle_configurerequest; + handler[DestroyNotify] = handle_destroynotify; + handler[EnterNotify] = handle_enternotify; + handler[Expose] = handle_expose; + handler[GravityNotify] = update_ignore_enternotify_hack; + handler[MapRequest] = handle_maprequest; + handler[MapNotify] = update_ignore_enternotify_hack; + handler[MotionNotify] = handle_motionnotify; + handler[PropertyNotify] = handle_propertynotify; + handler[UnmapNotify] = handle_unmapnotify; +} + +void +check_event(Connection * c) +{ + XEvent ev; + while (XPending(dpy)) { + XNextEvent(dpy, &ev); + /* main evet loop */ + if (handler[ev.type]) { + /* call handler */ + (handler[ev.type]) (&ev); + } + } +} + +static void +handle_buttonpress(XEvent * e) +{ + Client *c; + XButtonPressedEvent *ev = &e->xbutton; + Frame *f = win_to_frame(ev->window); + if (f) { + handle_frame_buttonpress(ev, f); + return; + } + if (ev->window == root) { + XSetInputFocus(dpy, root, RevertToPointerRoot, CurrentTime); + XSync(dpy, False); + } + if ((c = win_to_client(ev->window))) { + if (c->frame) { + ev->state &= valid_mask; + if (ev->state & Mod1Mask) { + if (!is_managed_frame(c->frame)) + XRaiseWindow(dpy, c->frame->win); + switch (ev->button) { + case Button1: + mouse_move(c->frame); + break; + case Button3: + { + Align align = xy_to_align(&c->rect, ev->x, ev->y); + if (align == CENTER) + mouse_move(c->frame); + else + mouse_resize(c->frame, align); + } + break; + default: + break; + } + } + } + } +} + +static void +handle_configurerequest(XEvent * e) +{ + XConfigureRequestEvent *ev = &e->xconfigurerequest; + XWindowChanges wc; + Client *c; + unsigned int bw = 0, tabh = 0; + Frame *f = 0; + + update_ignore_enternotify_hack(e); + /* fprintf(stderr, "%s", "configure request\n"); */ + c = win_to_client(ev->window); + ev->value_mask &= ~CWSibling; + + if (c) { + /* fprintf(stderr, "%s", "configure request client\n"); */ + f = c->frame; + + if (f) { + bw = border_width(f); + tabh = tab_height(f); + } + if (ev->value_mask & CWStackMode) { + if (wc.stack_mode == Above) + XRaiseWindow(dpy, c->win); + else + ev->value_mask &= ~CWStackMode; + } + gravitate(c, tabh ? tabh : bw, bw, 1); + + if (ev->value_mask & CWX) + c->rect.x = ev->x; + if (ev->value_mask & CWY) + c->rect.y = ev->y; + if (ev->value_mask & CWWidth) + c->rect.width = ev->width; + if (ev->value_mask & CWHeight) + c->rect.height = ev->height; + if (ev->value_mask & CWBorderWidth) + c->border = ev->border_width; + + gravitate(c, tabh ? tabh : bw, bw, 0); + + if (f) { + XRectangle *frect = rect_of_frame(f); + frect->x = wc.x = c->rect.x - bw; + frect->y = wc.y = c->rect.y - (tabh ? tabh : bw); + frect->width = wc.width = c->rect.width + 2 * bw; + frect->height = wc.height = + c->rect.height + bw + (tabh ? tabh : bw); + wc.border_width = 1; + wc.sibling = None; + wc.stack_mode = ev->detail; + XConfigureWindow(dpy, f->win, ev->value_mask, &wc); + configure_client(c); + } + } + wc.x = ev->x; + wc.y = ev->y; + + if (f) { + /* if so, then bw and tabh are already initialized */ + wc.x = bw; + wc.y = tabh ? tabh : bw; + } + wc.width = ev->width; + wc.height = ev->height; + wc.border_width = 0; + wc.sibling = None; + wc.stack_mode = Above; + ev->value_mask &= ~CWStackMode; + ev->value_mask |= CWBorderWidth; + XConfigureWindow(dpy, e->xconfigurerequest.window, ev->value_mask, + &wc); + XSync(dpy, False); + + /* + fprintf(stderr, "%d,%d,%d,%d\n", wc.x, wc.y, wc.width, wc.height); + */ +} + +static void +handle_destroynotify(XEvent * e) +{ + XDestroyWindowEvent *ev = &e->xdestroywindow; + Client *c = win_to_client(ev->window); + /* fprintf(stderr, "destroy: client 0x%x\n", (int)ev->window); */ + if (!c) + return; + if (c->frame) + detach_client_from_frame(c, 0, 1); + else if (detached && (index_item((void **) detached, c) >= 0)) + detached = (Client **) detach_item((void **) detached, c, + sizeof(Client *)); + free_client(c); +} + +static void +handle_expose(XEvent * e) +{ + static Frame *f; + if (e->xexpose.count == 0) { + f = win_to_frame(e->xbutton.window); + if (f) + draw_frame(f); + } +} + +static void +handle_maprequest(XEvent * e) +{ + XMapRequestEvent *ev = &e->xmaprequest; + static XWindowAttributes wa; + static Client *c; + + /* fprintf(stderr, "map: window 0x%x\n", (int)ev->window); */ + if (!XGetWindowAttributes(dpy, ev->window, &wa)) + return; + if (wa.override_redirect) + return; + /* there're clients which send map requests twice */ + c = win_to_client(ev->window); + if (!c) + c = alloc_client(ev->window); + if (!c->frame) { + _init_client(c, &wa); + attach_client(c); + } +} + +static void +handle_motionnotify(XEvent * e) +{ + Frame *f = win_to_frame(e->xmotion.window); + Cursor cursor; + if (f) { + Frame *old = SELFRAME(pages[sel]); + if (old != f) { + focus_frame(f, 0, 0, 1); + draw_frame(old); + draw_frame(f); + } else if (f->clients) { + /* multihead assumption */ + XSetInputFocus(dpy, f->clients[f->sel]->win, + RevertToPointerRoot, CurrentTime); + XSync(dpy, False); + } + cursor = cursor_for_motion(f, e->xmotion.x, e->xmotion.y); + if (cursor != f->cursor) { + f->cursor = cursor; + XDefineCursor(dpy, f->win, cursor); + } + } +} + +static void +handle_propertynotify(XEvent * e) +{ + XPropertyEvent *ev = &e->xproperty; + Client *c = win_to_client(ev->window); + + if (c) { + handle_client_property(c, ev); + return; + } +} + +static void +handle_unmapnotify(XEvent * e) +{ + XUnmapEvent *ev = &e->xunmap; + Client *c; + + update_ignore_enternotify_hack(e); + if (ev->event == root) + return; + if ((c = win_to_client(ev->window))) { + if (c->frame) { + detach_client_from_frame(c, 1, 0); + if (pages) + draw_page(pages[sel]); + free_client(c); + } else if (detached) { + if (index_item((void **) detached, c) == -1) + free_client(c); + } + } +} + +static void +handle_enternotify(XEvent * e) +{ + XCrossingEvent *ev = &e->xcrossing; + Client *c; + + if (ev->mode != NotifyNormal) + return; + + /* mouse is not in the focus window */ + if (ev->detail == NotifyInferior) + return; + + c = win_to_client(ev->window); + if (c && c->frame && (ev->serial != ignore_enternotify_hack)) { + Frame *old = SELFRAME(pages[sel]); + XUndefineCursor(dpy, c->frame->win); + if (old != c->frame) { + focus_frame(c->frame, 0, 0, 1); + draw_frame(old); + draw_frame(c->frame); + } else { + /* multihead assumption */ + XSetInputFocus(dpy, c->win, RevertToPointerRoot, CurrentTime); + XSync(dpy, False); + } + } +} + +static void +update_ignore_enternotify_hack(XEvent * e) +{ + ignore_enternotify_hack = e->xany.serial; + XSync(dpy, False); +} diff --git a/cmd/wm/frame.c b/cmd/wm/frame.c new file mode 100644 index 00000000..9ec7f1a7 --- /dev/null +++ b/cmd/wm/frame.c @@ -0,0 +1,659 @@ +/* + * (C)opyright MMIV-MMV Anselm R. Garbe + * See LICENSE file for license details. + */ + +#include +#include +#include +#include + +#include "wm.h" + +#include + +static Frame zero_frame = {0}; + +static void mouse(); +static void select_client(void *obj, char *cmd); +static void handle_after_write_frame(IXPServer * s, File * f); +static void handle_before_read_frame(IXPServer * s, File * f); + +/* action table for /frame/?/ namespace */ +Action frame_acttbl[] = { + {"select", select_client}, + {0, 0} +}; + +Frame * +alloc_frame(XRectangle * r, int add_frame_border, int floating) +{ + XSetWindowAttributes wa; + static int id = 0; + char buf[MAX_BUF]; + Frame *f = (Frame *) emalloc(sizeof(Frame)); + + *f = zero_frame; + f->rect = *r; + f->cursor = normal_cursor; + + snprintf(buf, MAX_BUF, "/detached/frame/%d", id); + f->files[F_PREFIX] = ixp_create(ixps, buf); + snprintf(buf, MAX_BUF, "/detached/frame/%d/client", id); + f->files[F_CLIENT_PREFIX] = ixp_create(ixps, buf); + snprintf(buf, MAX_BUF, "/detached/frame/%d/client/sel", id); + f->files[F_SEL_CLIENT] = ixp_create(ixps, buf); + f->files[F_SEL_CLIENT]->bind = 1; + snprintf(buf, MAX_BUF, "/detached/frame/%d/ctl", id); + f->files[F_CTL] = ixp_create(ixps, buf); + f->files[F_CTL]->after_write = handle_after_write_frame; + snprintf(buf, MAX_BUF, "/detached/frame/%d/size", id); + f->files[F_SIZE] = ixp_create(ixps, buf); + f->files[F_SIZE]->before_read = handle_before_read_frame; + f->files[F_SIZE]->after_write = handle_after_write_frame; + snprintf(buf, MAX_BUF, "/detached/frame/%d/border", id); + f->files[F_BORDER] = wmii_create_ixpfile(ixps, buf, defaults[WM_BORDER]->content); + f->files[F_BORDER]->after_write = handle_after_write_frame; + snprintf(buf, MAX_BUF, "/detached/frame/%d/tab", id); + f->files[F_TAB] = wmii_create_ixpfile(ixps, buf, defaults[WM_TAB]->content); + f->files[F_TAB]->after_write = handle_after_write_frame; + snprintf(buf, MAX_BUF, "/detached/frame/%d/handleinc", id); + f->files[F_HANDLE_INC] = wmii_create_ixpfile(ixps, buf, defaults[WM_HANDLE_INC]->content); + f->files[F_HANDLE_INC]->after_write = handle_after_write_frame; + snprintf(buf, MAX_BUF, "/detached/frame/%d/locked", id); + f->files[F_LOCKED] = wmii_create_ixpfile(ixps, buf, defaults[WM_LOCKED]->content); + snprintf(buf, MAX_BUF, "/detached/frame/%d/selstyle/bgcolor", id); + f->files[F_SEL_BG_COLOR] = wmii_create_ixpfile(ixps, buf, defaults[WM_SEL_BG_COLOR]->content); + snprintf(buf, MAX_BUF, "/detached/frame/%d/selstyle/fgcolor", id); + f->files[F_SEL_FG_COLOR] = wmii_create_ixpfile(ixps, buf, defaults[WM_SEL_FG_COLOR]->content); + snprintf(buf, MAX_BUF, "/detached/frame/%d/selstyle/bordercolor", id); + f->files[F_SEL_BORDER_COLOR] = wmii_create_ixpfile(ixps, buf, defaults[WM_SEL_BORDER_COLOR]->content); + snprintf(buf, MAX_BUF, "/detached/frame/%d/normstyle/bgcolor", id); + f->files[F_NORM_BG_COLOR] = wmii_create_ixpfile(ixps, buf, defaults[WM_NORM_BG_COLOR]->content); + snprintf(buf, MAX_BUF, "/detached/frame/%d/normstyle/fgcolor", id); + f->files[F_NORM_FG_COLOR] = wmii_create_ixpfile(ixps, buf, defaults[WM_NORM_FG_COLOR]->content); + snprintf(buf, MAX_BUF, "/detached/frame/%d/normstyle/bordercolor", id); + f->files[F_NORM_BORDER_COLOR] = wmii_create_ixpfile(ixps, buf, defaults[WM_NORM_BORDER_COLOR]->content); + snprintf(buf, MAX_BUF, "/detached/frame/%d/event/b2press", id); + f->files[F_EVENT_B2PRESS] = wmii_create_ixpfile(ixps, buf, defaults[WM_EVENT_B2PRESS]->content); + snprintf(buf, MAX_BUF, "/detached/frame/%d/event/b3press", id); + f->files[F_EVENT_B3PRESS] = wmii_create_ixpfile(ixps, buf, defaults[WM_EVENT_B3PRESS]->content); + snprintf(buf, MAX_BUF, "/detached/frame/%d/event/b4press", id); + f->files[F_EVENT_B4PRESS] = wmii_create_ixpfile(ixps, buf, defaults[WM_EVENT_B4PRESS]->content); + snprintf(buf, MAX_BUF, "/detached/frame/%d/event/b5press", id); + f->files[F_EVENT_B5PRESS] = wmii_create_ixpfile(ixps, buf, defaults[WM_EVENT_B5PRESS]->content); + id++; + + wa.override_redirect = 1; + wa.background_pixmap = ParentRelative; + wa.event_mask = ExposureMask | ButtonPressMask | ButtonReleaseMask + | PointerMotionMask | SubstructureRedirectMask + | SubstructureNotifyMask; + + if (add_frame_border) { + int bw = border_width(f); + int th = tab_height(f); + f->rect.width += 2 * bw; + f->rect.height += bw + (th ? th : bw); + } + f->win = + XCreateWindow(dpy, root, f->rect.x, f->rect.y, f->rect.width, f->rect.height, 0, + DefaultDepth(dpy, screen_num), CopyFromParent, + DefaultVisual(dpy, screen_num), + CWOverrideRedirect | CWBackPixmap | CWEventMask, + &wa); + + XDefineCursor(dpy, f->win, f->cursor); + f->gc = XCreateGC(dpy, f->win, 0, 0); + XSync(dpy, False); + frames = + (Frame **) attach_item_end((void **) frames, f, sizeof(Frame *)); + return f; +} + +void +focus_frame(Frame * f, int raise, int up, int down) +{ + Area *a = f->area; + if (down && f->clients) + focus_client(f->clients[f->sel], raise, 0); + a->sel = index_item((void **)a->frames, f); + a->files[A_SEL_FRAME]->content = f->files[F_PREFIX]->content; + if (raise && a->page->sel == 0) /* only floating windows are raised */ + XRaiseWindow(dpy, f->win); + if (up) + focus_area(a, raise, up, 0); +} + +Frame * +win_to_frame(Window w) +{ + int i; + + for (i = 0; frames && frames[i]; i++) + if (frames[i]->win == w) + return frames[i]; + return 0; +} + +void +toggle_frame(Frame * f) +{ + Page *p = f->page; + int was_managed = is_managed_frame(f); + detach_frame_from_page(f, 1); + attach_Frameo_page(p, f, !was_managed); + resize_frame(f, rect_of_frame(f), 0, 0); + draw_page(p); +} + +void +free_frame(Frame * f) +{ + frames = (Frame **) detach_item((void **) frames, f, sizeof(Frame *)); + XFreeGC(dpy, f->gc); + XDestroyWindow(dpy, f->win); + ixp_remove_file(ixps, f->files[F_PREFIX]); + if (ixps->errstr) + fprintf(stderr, "wmiiwm: free_frame(): %s\n", ixps->errstr); + free(f); +} + +XRectangle * +rect_of_frame(Frame * f) +{ + return is_managed_frame(f) ? &f->managed_rect : &f->floating_rect; +} + +void +focus_client(Client * c, int raise, int up) +{ + Frame *f = 0; + + sel = c; + + /* focus client */ + if (c) { + f = c->frame; + for (f->sel = 0; f->clients && f->clients[f->sel] != c; f->sel++); + f->files[F_CLIENT_SELECTED]->content = c->files[C_PREFIX]->content; + if (raise) + XRaiseWindow(dpy, c->win); + XSetInputFocus(dpy, c->win, RevertToPointerRoot, CurrentTime); + } else + XSetInputFocus(dpy, PointerRoot, RevertToPointerRoot, CurrentTime); + invoke_core_event(core_files[CORE_EVENT_CLIENT_UPDATE]); + if (up && f) + focus_frame(f, raise, up, 0); +} + +static void +resize_clients(Frame * f, int tabh, int bw) +{ + int i; + XRectangle *frect = rect_of_frame(f); + for (i = 0; f->clients && f->clients[i]; i++) { + Client *c = f->clients[i]; + c->rect.x = bw; + c->rect.y = tabh ? tabh : bw; + c->rect.width = frect->width - 2 * bw; + c->rect.height = frect->height - bw - (tabh ? tabh : bw); + XMoveResizeWindow(dpy, c->win, c->rect.x, c->rect.y, + c->rect.width, c->rect.height); + configure_client(c); + } +} + +int +is_managed_frame(Frame * f) +{ + Page *p = f->page; + if (p->managed) { + int i; + for (i = 0; p->managed[i] && p->managed[i] != f; i++); + if (p->managed[i]) + return 1; + } + return 0; +} + +static void +check_dimensions(Frame * f, unsigned int tabh, unsigned int bw) +{ + XRectangle *frect = rect_of_frame(f); + Client *c = f->clients ? f->clients[f->sel] : 0; + + if (!c) + return; + + if (c->size.flags & PMinSize) { + if (frect->width - 2 * bw < c->size.min_width) { + frect->width = c->size.min_width + 2 * bw; + } + if (frect->height - bw - (tabh ? tabh : bw) < c->size.min_height) { + frect->height = c->size.min_height + bw + (tabh ? tabh : bw); + } + } + if (c->size.flags & PMaxSize) { + if (frect->width - 2 * bw > c->size.max_width) { + frect->width = c->size.max_width + 2 * bw; + } + if (frect->height - bw - (tabh ? tabh : bw) > c->size.max_height) { + frect->height = c->size.max_height + bw + (tabh ? tabh : bw); + } + } +} + +static void +resize_incremental(Frame * f, unsigned int tabh, unsigned int bw) +{ + XRectangle *frect = rect_of_frame(f); + Client *c = f->clients ? f->clients[f->sel] : 0; + + if (!c) + return; + + /* + * increment stuff, see chapter 4.1.2.3 of the Inter-Client + * Communication Conventions Manual + */ + if (c->size.flags & PResizeInc) { + int base_width = 0, base_height = 0; + + if (c->size.flags & PBaseSize) { + base_width = c->size.base_width; + base_height = c->size.base_height; + } else if (c->size.flags & PMinSize) { + /* base_{width,height} defaults to min_{width,height} */ + base_width = c->size.min_width; + base_height = c->size.min_height; + } + /* + * client_width = base_width + i * c->size.width_inc for an + * integer i + */ + frect->width -= + (frect->width - 2 * bw - base_width) % c->size.width_inc; + frect->height -= + (frect->height - bw - (tabh ? tabh : bw) - + base_height) % c->size.height_inc; + } +} + +void +resize_frame(Frame * f, XRectangle * r, XPoint * pt, int ignore_layout) +{ + unsigned int tabh = _strtonum(f->files[F_TAB_H]->content, 0, 30); + unsigned int bw = _strtonum(f->files[F_BORDER_W]->content, 0, 10); + XRectangle *frect = rect_of_frame(f); + + /* do layout stuff if necessary */ + if (!ignore_layout && is_managed_frame(f)) { + Page *p = f->page; + if (p && p->layout) { + p->layout->resize(f, r, pt); + } else + *frect = *r; + } else + *frect = *r; + + /* resize if client requests special size */ + check_dimensions(f, tabh, bw); + + if (f->files[F_HANDLE_INC]->content + && ((char *) f->files[F_HANDLE_INC]->content)[0] == '1') + resize_incremental(f, tabh, bw); + + XMoveResizeWindow(dpy, f->win, frect->x, frect->y, frect->width, + frect->height); + resize_clients(f, (tabh ? tabh : bw), bw); +} + + +void +draw_tab(Frame * f, char *text, int x, int y, int w, int h, int sel) +{ + Draw d = {0}; + XFontStruct *font = blitz_getfont(dpy, f->files[F_SELECTED_TEXT_FONT]->content); + + d.drawable = f->win; + d.gc = f->gc; + d.rect.x = x; + d.rect.y = y; + d.rect.width = w; + d.rect.height = h; + d.data = text; + if (sel) { + d.bg = + blitz_loadcolor(dpy, screen_num, + f->files[F_SELECTED_BG_COLOR]->content); + d.fg = + blitz_loadcolor(dpy, screen_num, + f->files[F_SELECTED_FG_COLOR]->content); + d.border = + blitz_loadcolor(dpy, screen_num, + f->files[F_SELECTED_BORDER_COLOR]->content); + d.font = font; + } else { + d.bg = + blitz_loadcolor(dpy, screen_num, + f->files[F_NORMAL_BG_COLOR]->content); + d.fg = + blitz_loadcolor(dpy, screen_num, + f->files[F_NORMAL_FG_COLOR]->content); + d.border = + blitz_loadcolor(dpy, screen_num, + f->files[F_NORMAL_BORDER_COLOR]->content); + d.font = font; + } + blitz_drawlabel(dpy, &d); + XSync(dpy, False); + XFreeFont(dpy, font); +} + + +/** + * Assumes following files: + * + * ./sel-style/text-font "" + * ./sel-style/text-size "" + * ./sel-style/text-color "#RRGGBBAA" + * ./sel-style/bg-color "#RRGGBBAA" + * ./sel-style/border-color "#RRGGBBAA [#RRGGBBAA [#RRGGBBAA [#RRGGBBAA]]]" + * ./norm-style/text-font "" + * ./norm-style/text-size "" + * ./norm-style/text-color "#RRGGBBAA" + * ./norm-style/bg-color "#RRGGBBAA" + * ./norm-style/border-color "#RRGGBBAA [#RRGGBBAA [#RRGGBBAA [#RRGGBBAA]]]" + */ +void +draw_frame(Frame * f) +{ + Draw d = {0}; + int bw = _strtonum(f->files[F_BORDER_W]->content, 0, 10); + XRectangle notch; + XRectangle *frect = rect_of_frame(f); + + if (bw) { + notch.x = bw; + notch.y = bw; + notch.width = frect->width - 2 * bw; + notch.height = frect->height - 2 * bw; + d.drawable = f->win; + d.gc = f->gc; + + /* define ground plate (i = 0) */ + if (is_selected(f)) { + d.bg = + blitz_loadcolor(dpy, screen_num, + f->files[F_SELECTED_BG_COLOR]->content); + d.fg = + blitz_loadcolor(dpy, screen_num, + f->files[F_SELECTED_FG_COLOR]->content); + d.border = + blitz_loadcolor(dpy, screen_num, + f->files[F_SELECTED_BORDER_COLOR]-> + content); + } else { + d.bg = + blitz_loadcolor(dpy, screen_num, + f->files[F_NORMAL_BG_COLOR]->content); + d.fg = + blitz_loadcolor(dpy, screen_num, + f->files[F_NORMAL_FG_COLOR]->content); + d.border = + blitz_loadcolor(dpy, screen_num, + f->files[F_NORMAL_BORDER_COLOR]->content); + } + d.rect = *frect; + d.rect.x = d.rect.y = 0; + d.notch = ¬ch; + + blitz_drawlabel(dpy, &d); + } + draw_clients(f); + XSync(dpy, False); +} + +void +handle_frame_buttonpress(XButtonEvent * e, Frame * f) +{ + int bindex; + int size = count_items((void **) f->clients); + int cindex = e->x / (rect_of_frame(f)->width / size); + if (!is_managed_frame(f)) + XRaiseWindow(dpy, f->win); + if (cindex != f->sel) { + focus_client(f->clients[cindex], 1, 0); + draw_frame(f); + return; + } + if (e->button == Button1) { + mouse(); + return; + } + bindex = F_EVENT_B2PRESS - 2 + e->button; + /* frame mouse handling */ + if (f->files[bindex]->content) + spawn(dpy, f->files[bindex]->content); + draw_frame(f); +} + +void +attach_Cliento_frame(Frame * f, Client * c) +{ + int size = count_items((void **) f->clients); + wmii_move_ixpfile(c->files[C_PREFIX], f->files[F_CLIENT_PREFIX]); + f->files[F_CLIENT_SELECTED]->content = c->files[C_PREFIX]->content; + f->clients = + (Client **) attach_item_end((void **) f->clients, c, + sizeof(Client *)); + f->sel = size; + c->frame = f; + reparent_client(c, f->win, + _strtonum(f->files[F_BORDER_W]->content, 0, 10), + _strtonum(f->files[F_TAB_H]->content, 0, 30)); + resize_frame(f, rect_of_frame(f), 0, 1); + show_client(c); + focus_client(c, 1, 1); +} + +static void +attach_client_fullscreen(Client * c) +{ + Page *p; + Frame *f; + int bw, th; + + if (pages) + hide_page(pages[sel_page]); + p = alloc_page("1"); + c->rect = rect; + f = alloc_frame(&c->rect, 1, 1); + bw = _strtonum(f->files[F_BORDER_W]->content, 0, 10); + th = _strtonum(f->files[F_TAB_H]->content, 0, 30); + f->floating_rect.x -= bw; + f->floating_rect.y -= (th ? th : bw); + attach_Frameo_page(p, f, 0); + attach_Cliento_frame(f, c); + draw_frame(f); + invoke_core_event(core_files[CORE_EVENT_PAGE_UPDATE]); +} + +void +attach_client(Client * c) +{ + Page *p = 0; + Frame *f = 0; + + /* fullscreen app support */ + if ((c->rect.x == rect.x) && (c->rect.y == rect.y) + && (c->rect.width == rect.width) + && (c->rect.height == rect.height)) { + attach_client_fullscreen(c); + return; + } + if (!pages) + alloc_page("0"); + + /* transient stuff */ + if (c && c->trans && !f) { + Client *t = win_to_client(c->trans); + if (t && t->frame) { + focus_client(t, 1, 1); + f = alloc_frame(&c->rect, 1, 1); + } + } + p = pages[sel_page]; + + if (!f) { + /* check if we shall manage it */ + if (!manage_class_instance(c)) + f = alloc_frame(&c->rect, 1, 1); + } + if (!f) { + /* check for tabbing? */ + f = get_selected(p); + if (f && (f->floating + || (((char *) f->files[F_LOCKED]->content)[0] == '1'))) + f = 0; + } + if (!f) + f = alloc_frame(&c->rect, 1, 0); + + if (!f->page) + attach_Frameo_page(p, f, !f->floating); + attach_Cliento_frame(f, c); + draw_frame(f); + invoke_core_event(core_files[CORE_EVENT_PAGE_UPDATE]); +} + +void +detach_client_from_frame(Client * c, int unmapped, int destroyed) +{ + Frame *f = c->frame; + wmii_move_ixpfile(c->files[C_PREFIX], + core_files[CORE_DETACHED_CLIENT]); + c->frame = 0; + f->clients = + (Client **) detach_item((void **) f->clients, c, sizeof(Client *)); + if (f->sel) + f->sel--; + else + f->sel = 0; + if (!destroyed) { + if (!unmapped) { + hide_client(c); + detached = + (Client **) attach_item_begin((void **) detached, c, + sizeof(Client *)); + } + reparent_client(c, root, + _strtonum(f->files[F_BORDER_W]->content, 0, 10), + _strtonum(f->files[F_TAB_H]->content, 0, 30)); + } + if (f->clients) { + focus_client(f->clients[f->sel], 1, 1); + draw_frame(f); + } else { + detach_frame_from_page(f, 0); + free_frame(f); + if (pages) + focus_page(pages[sel_page], 0, 1); + } + invoke_core_event(core_files[CORE_EVENT_PAGE_UPDATE]); +} + +static void +mouse() +{ + Frame *f; + Align align; + + if (!pages) + return; + if (!(f = SELFRAME(pages[sel]))) + return; + align = cursor_to_align(f->cursor); + if (align == CENTER) + mouse_move(f); + else + mouse_resize(f, align); +} + +static void +select_client(void *obj, char *cmd) +{ + Frame *f = obj; + int size = count_items((void **) f->clients); + if (!f || !cmd || size == 1) + return; + if (!strncmp(cmd, "prev", 5)) { + if (f->sel > 0) + f->sel--; + else + f->sel = size - 1; + } else if (!strncmp(cmd, "next", 5)) { + if (f->sel + 1 == size) + f->sel = 0; + else + f->sel++; + } + focus_client(f->clients[f->sel], 1, 0); + draw_frame(f); +} + +static void +handle_before_read_frame(IXPServer * s, File * f) +{ + int i = 0; + + for (i = 0; frames && frames[i]; i++) { + if (f == frames[i]->files[F_SIZE]) { + XRectangle *frect = rect_of_frame(frames[i]); + char buf[64]; + snprintf(buf, 64, "%d,%d,%d,%d", frect->x, frect->y, + frect->width, frect->height); + if (f->content) + free(f->content); + f->content = estrdup(buf); + f->size = strlen(buf); + return; + } + } +} + +static void +handle_after_write_frame(IXPServer * s, File * f) +{ + int i; + + for (i = 0; frames && frames[i]; i++) { + if (f == frames[i]->files[F_CTL]) { + run_action(f, frames[i], frame_acttbl); + return; + } + if (f == frames[i]->files[F_TAB_H] + || f == frames[i]->files[F_BORDER_W] + || f == frames[i]->files[F_HANDLE_INC]) { + if (frames[i]->page && frames[i]->page->layout) { + frames[i]->page->layout->arrange(frames[i]->page); + draw_page(frames[i]->page); + } else { + resize_frame(frames[i], rect_of_frame(frames[i]), 0, 0); + draw_frame(frames[i]); + } + return; + } else if (f == frames[i]->files[F_SIZE]) { + char *size = frames[i]->files[F_SIZE]->content; + if (size && strrchr(size, ',')) { + XRectangle frect; + frect = *rect_of_frame(frames[i]); + blitz_strtorect(dpy, &rect, &frect, size); + resize_frame(frames[i], &frect, 0, 0); + draw_page(frames[i]->page); + } + return; + } + } +} diff --git a/cmd/wm/layout_column.c b/cmd/wm/layout_column.c new file mode 100644 index 00000000..49696867 --- /dev/null +++ b/cmd/wm/layout_column.c @@ -0,0 +1,393 @@ +/* + * (C)opyright MMIV-MMV Anselm R. Garbe + * See LICENSE file for license details. + */ + +#include +#include +#include + +#include "wm.h" +#include "layout.h" + +#include + +typedef struct Acme Acme; +typedef struct Column Column; + +struct Acme { + int sel; + Column **column; +}; + +struct Column { + int sel; + int refresh; + Frame **frames; + XRectangle rect; +}; + +static void init_col(Page * p, int argc, char **argv); +static void deinit_col(Page * p); +static void arrange_col(Page * p); +static void attach_col(Page * p, Client * c); +static void detach_col(Page *, Client * c, int unmapped, int destroyed); +static void resize_col(Frame * f, XRectangle * new, XPoint * pt); +static void aux_col(Frame * f, char *what); + +static LayoutImpl lcol = {"col", init_col, deinit_col, arrange_col, attach_col, + detach_col, resize_col, aux_col +}; + +static Column zero_column = {0}; +static Acme zero_acme = {0}; + +void +init_layout_col() +{ + layouts = + (Layout **) attach_item_end((void **) layouts, &lcol, + sizeof(Layout *)); +} + + +static void +arrange_column(Page * p, Column * col) +{ + int i; + int n = count_items((void **) col->frames); + unsigned int height = p->managed_rect.height / n; + for (i = 0; col->frames && col->frames[i]; i++) { + if (col->refresh) { + col->frames[i]->rect = col->rect; + col->frames[i]->rect.height = height; + col->frames[i]->rect.y = i * height; + } + resize_frame(col->frames[i], &col->frames[i]->rect, 0, 1); + } +} + +static void +arrange_col(Page * p) +{ + Acme *acme = p->aux; + int i; + + if (!acme) { + fprintf(stderr, "%s", "wmiiwm: fatal, page has no layout\n"); + exit(1); + } + for (i = 0; acme->column && acme->column[i]; i++) + arrange_column(p, acme->column[i]); +} + +static void +init_col(Page * p, int argc, char **argv) +{ + Acme *acme = emalloc(sizeof(Acme)); + int i, j, n, cols = 1; + unsigned int width = 1; + Column *col; + + *acme = zero_acme; + p->aux = acme; + + /* processing argv */ + for (i = 1; (i < argc) && (argv[i][0] == '-'); i++) { + switch (argv[i][1]) { + case 'c': + cols = _strtonum(argv[++i], 0, 32); + if (cols < 1) + cols = 1; + break; + } + } + + width = p->managed_rect.width / cols; + acme->column = emalloc((cols + 1) * sizeof(Column *)); + for (i = 0; i < cols; i++) { + acme->column[i] = emalloc(sizeof(Column)); + *acme->column[i] = zero_column; + acme->column[i]->rect = p->managed_rect; + acme->column[i]->rect.x = i * width; + acme->column[i]->rect.width = width; + acme->column[i]->refresh = 1; + } + acme->column[cols] = 0; /* null termination of array */ + + /* + * Frame attaching strategy works as follows: 1. If more clients than + * column exist, then each column gets one client, except eastmost + * column, which gets all remaining clients. 2. If lesser clients + * than column exist, than filling begins from eastmost to westmost + * column until no more clients exist. + */ + n = count_items((void **) clients); + if (n > cols) { + /* 1st. case */ + j = 0; + for (i = 0; i < (cols - 1); i++) { + col = acme->column[i]; + col->frames = (Frame **) attach_item_end((void **) col->frames, + alloc_frame(&clients[i]->rect, 1, 1), sizeof(Frame *)); + col->frames[0]->aux = col; + attach_Frameo_page(p, col->frames[0], 1); + attach_Cliento_frame(col->frames[0], clients[j]); + j++; + } + col = acme->column[cols - 1]; + col->frames = emalloc((n - j + 1) * sizeof(Frame *)); + for (i = 0; i + j < n; i++) { + col->frames[i] = alloc_frame(&clients[j + i]->rect, 1, 1); + col->frames[i]->aux = col; + attach_Frameo_page(p, col->frames[i], 1); + attach_Cliento_frame(col->frames[i], clients[j + i]); + } + col->frames[i] = 0; + } else { + /* 2nd case */ + j = 0; + for (i = cols - 1; j < n; i--) { + col = acme->column[i]; + col->frames = (Frame **) attach_item_end((void **) col->frames, + alloc_frame(&clients[i]->rect, 1, 1), sizeof(Frame *)); + col->frames[0]->aux = col; + attach_Frameo_page(p, col->frames[0], 1); + attach_Cliento_frame(col->frames[0], clients[j]); + j++; + } + } + arrange_col(p); +} + +static void +deinit_col(Page * p) +{ + Acme *acme = p->aux; + int i; + + for (i = 0; acme->column && acme->column[i]; i++) { + Column *col = acme->column[i]; + int j; + for (j = 0; col->frames && col->frames[j]; j++) { + Frame *f = col->frames[j]; + while (f->clients && f->clients[0]) + detach_client_from_frame(f->clients[0], 0, 0); + detach_frame_from_page(f, 1); + free_frame(f); + } + free(col->frames); + } + free(acme->column); + free(acme); + p->aux = 0; +} + +static void +attach_col(Page * p, Client * c) +{ + Acme *acme = p->aux; + Column *col; + Frame *f; + + col = acme->column[acme->sel]; + f = alloc_frame(&c->rect, 1, 1); + col->frames = (Frame **) attach_item_end((void **) col->frames, f, + sizeof(Frame *)); + f->aux = col; + col->refresh = 1; + attach_Frameo_page(p, f, 1); + attach_Cliento_frame(f, c); + + arrange_col(p); +} + +static void +detach_col(Page * p, Client * c, int unmapped, int destroyed) +{ + Frame *f = c->frame; + Column *col = f->aux; + + if (!col) + return; /* client was not attached, maybe exit(1) in + * such case */ + col->frames = (Frame **) detach_item((void **) col->frames, c->frame, + sizeof(Frame *)); + col->refresh = 1; + detach_client_from_frame(c, unmapped, destroyed); + detach_frame_from_page(f, 1); + free_frame(f); + + arrange_col(p); +} + +static void +drop_resize(Frame * f, XRectangle * new) +{ + Column *col = f->aux; + Acme *acme = f->page->aux; + int i, idx, n = 0; + + if (!col) { + fprintf(stderr, "%s", + "wmii: fatal: frame has no associated column\n"); + exit(1); + } + for (i = 0; acme->column && acme->column[i]; i++) { + if (acme->column[i] == col) + idx = i; + n++; + } + + /* horizontal resizals are */ + if (new->x < f->rect.x) { + if (idx && new->x > acme->column[idx - 1]->rect.x) { + Column *west = acme->column[idx - 1]; + west->rect.width = new->x - west->rect.x; + col->rect.width += f->rect.x - new->x; + col->rect.x = new->x; + + for (i = 0; west->frames && west->frames[i]; i++) { + Frame *f = west->frames[i]; + f->rect.x = west->rect.x; + f->rect.width = west->rect.width; + resize_frame(f, &f->rect, 0, 1); + } + for (i = 0; col->frames && col->frames[i]; i++) { + Frame *f = col->frames[i]; + f->rect.x = col->rect.x; + f->rect.width = col->rect.width; + resize_frame(f, &f->rect, 0, 1); + } + } + } + if (new->x + new->width > f->rect.x + f->rect.width) { + if ((idx + 1 < n) && + (new->x + new->width < acme->column[idx + 1]->rect.x + + acme->column[idx + 1]->rect.width)) { + Column *east = acme->column[idx + 1]; + east->rect.width -= new->x + new->width - east->rect.x; + east->rect.x = new->x + new->width; + col->rect.x = new->x; + col->rect.width = new->width; + + for (i = 0; col->frames && col->frames[i]; i++) { + Frame *f = col->frames[i]; + f->rect.x = col->rect.x; + f->rect.width = col->rect.width; + resize_frame(f, &f->rect, 0, 1); + } + for (i = 0; east->frames && east->frames[i]; i++) { + Frame *f = east->frames[i]; + f->rect.x = east->rect.x; + f->rect.width = east->rect.width; + resize_frame(f, &f->rect, 0, 1); + } + } + } + /* vertical stuff */ + n = 0; + for (i = 0; col->frames && col->frames[i]; i++) { + if (col->frames[i] == f) + idx = i; + n++; + } + + if (new->y < f->rect.y) { + if (idx && new->y > col->frames[idx - 1]->rect.y) { + Frame *north = col->frames[idx - 1]; + north->rect.height = new->y - north->rect.y; + f->rect.width += new->y - north->rect.y; + f->rect.y = new->y; + resize_frame(north, &north->rect, 0, 1); + resize_frame(f, &f->rect, 0, 1); + } + } + if (new->y + new->height > f->rect.y + f->rect.height) { + if ((idx + 1 < n) && + (new->y + new->height < col->frames[idx + 1]->rect.y + + col->frames[idx + 1]->rect.height)) { + Frame *south = col->frames[idx + 1]; + south->rect.width -= new->x + new->width - south->rect.x; + south->rect.x = new->x + new->width; + f->rect.x = new->x; + f->rect.width = new->width; + resize_frame(f, &f->rect, 0, 1); + resize_frame(south, &south->rect, 0, 1); + } + } +} + +static void +_drop_move(Frame * f, XRectangle * new, XPoint * pt) +{ + Column *tgt = 0, *src = f->aux; + Acme *acme = f->page->aux; + int i; + + if (!src) { + fprintf(stderr, "%s", + "wmii: fatal: frame has no associated column\n"); + exit(1); + } + for (i = 0; acme->column && acme->column[i]; i++) { + Column *colp = acme->column[i]; + if (blitz_ispointinrect(pt->x, pt->y, &colp->rect)) { + tgt = colp; + break; + } + } + + /* use pointer as fixpoint */ + if (tgt == src) { + /* only change order within column */ + for (i = 0; tgt->frames && tgt->frames[i]; i++) { + Frame *fp = tgt->frames[i]; + if (blitz_ispointinrect(pt->x, pt->y, &fp->rect)) { + if (fp == f) + return; /* just ignore */ + else { + int idxf = index_item((void **) tgt->frames, f); + int idxfp = index_item((void **) tgt->frames, fp); + Frame *tmpf = f; + XRectangle tmpr = f->rect; + + f->rect = fp->rect; + fp->rect = tmpr; + tgt->frames[idxf] = tgt->frames[idxfp]; + tgt->frames[idxfp] = tmpf; + resize_frame(f, &f->rect, 0, 1); + resize_frame(fp, &fp->rect, 0, 1); + } + return; + } + } + } else { + /* detach, attach and change order in target column */ + src->frames = (Frame **) detach_item((void **) src->frames, + f, sizeof(Frame *)); + tgt->frames = + (Frame **) attach_item_end((void **) tgt->frames, f, + sizeof(Frame *)); + tgt->refresh = 1; + arrange_column(f->page, tgt); + + /* TODO: implement a better target placing strategy */ + } +} + +static void +resize_col(Frame * f, XRectangle * new, XPoint * pt) +{ + if ((f->rect.width == new->width) + && (f->rect.height == new->height)) + _drop_move(f, new, pt); + else + drop_resize(f, new); +} + + +static void +aux_col(Frame * f, char *what) +{ +} diff --git a/cmd/wm/mklayout.sh b/cmd/wm/mklayout.sh new file mode 100644 index 00000000..b7e9ab95 --- /dev/null +++ b/cmd/wm/mklayout.sh @@ -0,0 +1,12 @@ +#!/bin/sh +rm -f layout.h layout.c layout.mk +echo "#include \"layout.h\"" > layout.c +echo "#include \"wm.h\"" >> layout.c +echo "void init_layouts() {" >> layout.c +for i in `ls layout_*.c`; do + FUNC="`echo \`basename $i\` | sed 's/\.c//g'`" + echo "void init_$FUNC();" >> layout.h + echo " init_$FUNC();" >> layout.c; + echo "SRC += $i" >>layout.mk +done +echo "}" >> layout.c diff --git a/cmd/wm/mouse.c b/cmd/wm/mouse.c new file mode 100644 index 00000000..a9548a48 --- /dev/null +++ b/cmd/wm/mouse.c @@ -0,0 +1,620 @@ +/* + * (C)opyright MMIV-MMV Anselm R. Garbe + * See LICENSE file for license details. + */ + +#include + +#include "wm.h" + +Cursor +cursor_for_motion(Frame * f, int x, int y) +{ + int n, e, w, s, tn, te, tw, ts; + int tabh, bw; + + bw = border_width(f); + tabh = tab_height(f); + + if (!f || !bw) + return normal_cursor; + + /* rectangle attributes of client are used */ + w = x < bw; + e = x >= rect_of_frame(f)->width - bw; + n = y < bw; + s = y >= rect_of_frame(f)->height - bw; + + tw = x < (tabh ? tabh : 2 * bw); + te = x > rect_of_frame(f)->width - (tabh ? tabh : 2 * bw); + tn = y < (tabh ? tabh : 2 * bw); + ts = s > rect_of_frame(f)->height - (tabh ? tabh : 2 * bw); + + if ((w && n) || (w && tn) || (n && tw)) + return nw_cursor; + else if ((e && n) || (e && tn) || (n && te)) + return ne_cursor; + else if ((w && s) || (w && ts) || (s && tw)) + return sw_cursor; + else if ((e && s) || (e && ts) || (s && te)) + return se_cursor; + else if (w) + return w_cursor; + else if (e) + return e_cursor; + else if (n) + return n_cursor; + else if (s) + return s_cursor; + + return normal_cursor; +} + +Align +xy_to_align(XRectangle * rect, int x, int y) +{ + + int w = x <= rect->x + rect->width / 2; + int n = y <= rect->y + rect->height / 2; + int e = x > rect->x + rect->width / 2; + int s = y > rect->y + rect->height / 2; + int nw = w && n; + int ne = e && n; + int sw = w && s; + int se = e && s; + + if (nw) + return NWEST; + else if (ne) + return NEAST; + else if (se) + return SEAST; + else if (sw) + return SWEST; + else if (w) + return WEST; + else if (e) + return EAST; + else if (n) + return NORTH; + else if (s) + return SOUTH; + return CENTER; +} + +Align +cursor_to_align(Cursor cursor) +{ + if (cursor == w_cursor) + return WEST; + else if (cursor == nw_cursor) + return NWEST; + else if (cursor == n_cursor) + return NORTH; + else if (cursor == ne_cursor) + return NEAST; + else if (cursor == e_cursor) + return EAST; + else if (cursor == se_cursor) + return SEAST; + else if (cursor == s_cursor) + return SOUTH; + else if (cursor == sw_cursor) + return SWEST; + return CENTER; /* should not happen */ +} + +static int +check_vert_match(XRectangle * r, XRectangle * neighbor) +{ + /* check if neighbor matches edge */ + return (((neighbor->y <= r->y) + && (neighbor->y + neighbor->height >= r->y)) + || ((neighbor->y >= r->y) + && (r->y + r->height >= neighbor->y))); +} + +static int +check_horiz_match(XRectangle * r, XRectangle * neighbor) +{ + /* check if neighbor matches edge */ + return (((neighbor->x <= r->x) + && (neighbor->x + neighbor->width >= r->x)) + || ((neighbor->x >= r->x) + && (r->x + r->width >= neighbor->x))); +} + +static void +snap_move(XRectangle * r, XRectangle * rects, + unsigned int num, int snapw, int snaph) +{ + + int i, j, w = 0, n = 0, e = 0, s = 0; + + /* snap to other windows */ + for (i = 0; i <= snapw && !(w && e); i++) { + + for (j = 0; j < num && !(w && e); j++) { + + /* check west neighbors leftwards */ + if (!w) { + if (r->x - i == (rects[j].x + rects[j].width)) { + /* + * west edge of neighbor found, check + * vert match + */ + w = check_vert_match(r, &rects[j]); + if (w) + r->x = rects[j].x + rects[j].width; + } + } + /* check west neighbors rightwards */ + if (!w) { + if (r->x + i == (rects[j].x + rects[j].width)) { + /* + * west edge of neighbor found, check + * vert match + */ + w = check_vert_match(r, &rects[j]); + if (w) + r->x = rects[j].x + rects[j].width; + } + } + /* check east neighbors leftwards */ + if (!e) { + if (r->x + r->width - i == rects[j].x) { + /* + * east edge of neighbor found, check + * vert match + */ + e = check_vert_match(r, &rects[j]); + if (e) + r->x = rects[j].x - r->width; + } + } + /* check east neighbors rightwards */ + if (!e) { + if (r->x + r->width + i == rects[j].x) { + /* + * east edge of neighbor found, check + * vert match + */ + e = check_vert_match(r, &rects[j]); + if (e) + r->x = rects[j].x - r->width; + } + } + } + + /* snap to west screen border */ + if (!w && (r->x - i == rect.x)) { + w = 1; + r->x = rect.x; + } + /* snap to west screen border */ + if (!w && (r->x + i == rect.x)) { + w = 1; + r->x = rect.x; + } + /* snap to east screen border */ + if (!e && (r->x + r->width - i == rect.width)) { + e = 1; + r->x = rect.x + rect.width - r->width; + } + if (!e && (r->x + r->width + i == rect.width)) { + e = 1; + r->x = rect.x + rect.width - r->width; + } + } + + for (i = 0; i <= snaph && !(n && s); i++) { + + for (j = 0; j < num && !(n && s); j++) { + /* check north neighbors upwards */ + if (!n) { + if (r->y - i == (rects[j].y + rects[j].height)) { + /* + * north edge of neighbor found, + * check horiz match + */ + n = check_horiz_match(r, &rects[j]); + if (n) + r->y = rects[j].y + rects[j].height; + } + } + /* check north neighbors downwards */ + if (!n) { + if (r->y + i == (rects[j].y + rects[j].height)) { + /* + * north edge of neighbor found, + * check horiz match + */ + n = check_horiz_match(r, &rects[j]); + if (n) + r->y = rects[j].y + rects[j].height; + } + } + /* check south neighbors upwards */ + if (!s) { + if (r->y + r->height - i == rects[j].y) { + /* + * south edge of neighbor found, + * check horiz match + */ + s = check_horiz_match(r, &rects[j]); + if (s) + r->y = rects[j].y - r->height; + } + } + /* check south neighbors downwards */ + if (!s) { + if (r->y + r->height + i == rects[j].y) { + /* + * south edge of neighbor found, + * check horiz match + */ + s = check_horiz_match(r, &rects[j]); + if (s) + r->y = rects[j].y - r->height; + } + } + } + + /* snap to north screen border */ + if (!n && (r->y - i == rect.y)) { + n = 1; + r->y = rect.y; + } + if (!n && (r->y + i == rect.y)) { + n = 1; + r->y = rect.y; + } + /* snap to south screen border */ + if (!s && (r->y + r->height - i == rect.height)) { + s = 1; + r->y = rect.y + rect.height - r->height; + } + if (!s && (r->y + r->height + i == rect.height)) { + s = 1; + r->y = rect.y + rect.height - r->height; + } + } +} + +static void +draw_pseudo_border(XRectangle * r) +{ + XRectangle pseudo = *r; + + pseudo.x += 2; + pseudo.y += 2; + pseudo.width -= 4; + pseudo.height -= 4; + XDrawRectangles(dpy, root, xorgc, &pseudo, 1); + XSync(dpy, False); +} + + +void +mouse_move(Frame * f) +{ + int px = 0, py = 0, wex, wey, ex, ey, first = 1, i; + Window dummy; + XEvent ev; + /* borders */ + int snapw = rect.width * _strtonum(defaults[WM_SNAP_VALUE]->content, 0, 1000) / 1000; + int snaph = rect.height * _strtonum(defaults[WM_SNAP_VALUE]->content, 0, 1000) / 1000; + unsigned int num; + unsigned int dmask; + XRectangle *rects = rectangles(&num); + XRectangle frect = *rect_of_frame(f); + XPoint pt; + + XQueryPointer(dpy, f->win, &dummy, &dummy, &i, &i, &wex, &wey, &dmask); + XTranslateCoordinates(dpy, f->win, root, wex, wey, &ex, &ey, &dummy); + pt.x = ex; + pt.y = ey; + XSync(dpy, False); + XGrabServer(dpy); + XGrabPointer(dpy, root, False, ButtonMotionMask | ButtonReleaseMask, + GrabModeAsync, GrabModeAsync, None, move_cursor, + CurrentTime); + for (;;) { + while (!XCheckMaskEvent(dpy, + ButtonReleaseMask | ButtonMotionMask, &ev)) { + usleep(20000); + continue; + } + + switch (ev.type) { + case ButtonRelease: + if (!first) { + draw_pseudo_border(&frect); + resize_frame(f, &frect, &pt, 0); + } + draw_page(pages[sel]); + free(rects); + XUngrabPointer(dpy, CurrentTime /* ev.xbutton.time */ ); + XUngrabServer(dpy); + XSync(dpy, False); + return; + break; + case MotionNotify: + pt.x = ev.xmotion.x; + pt.y = ev.xmotion.y; + XTranslateCoordinates(dpy, f->win, root, ev.xmotion.x, + ev.xmotion.y, &px, &py, &dummy); + if (first) + first = 0; + else + draw_pseudo_border(&frect); + frect.x = px - ex; + frect.y = py - ey; + snap_move(&frect, rects, num, snapw, snaph); + draw_pseudo_border(&frect); + break; + } + } +} + +static void +snap_resize(XRectangle * r, XRectangle * o, Align align, + XRectangle * rects, unsigned int num, int px, int ox, int py, + int oy, int snapw, int snaph) +{ + int i, j, pend = 0; + int w, h; + + /* x */ + switch (align) { + case NEAST: + case EAST: + case SEAST: + w = px - r->x + (o->width - ox); + if (w < 10) + break; + r->width = w; + if (w <= snapw) + break; + /* snap to border */ + for (i = 0; !pend && (i < snapw); i++) { + if (r->x + r->width - i == rect.x + rect.width) { + r->width -= i; + break; + } + if (r->x + r->width + i == rect.x + rect.width) { + r->width += i; + break; + } + for (j = 0; j < num; j++) { + if (r->x + r->width - i == rects[j].x) { + pend = check_vert_match(r, &rects[j]); + if (pend) { + r->width -= i; + break; + } + } + if (r->x + r->width + i == rects[j].x) { + pend = check_vert_match(r, &rects[j]); + if (pend) { + r->width += i; + break; + } + } + } + } + break; + case NWEST: + case WEST: + case SWEST: + w = r->width + r->x - px + ox; + if (w < 10) + break; + r->width = w; + r->x = px - ox; + if (w <= snapw) + break; + /* snap to border */ + for (i = 0; !pend && (i < snapw); i++) { + if (r->x - i == rect.x) { + r->x -= i; + r->width += i; + break; + } + if (r->x + i == rect.x) { + r->x += i; + r->width -= i; + break; + } + for (j = 0; j < num; j++) { + if (r->x - i == rects[j].x + rects[j].width) { + pend = check_vert_match(r, &rects[j]); + if (pend) { + r->x -= i; + r->width += i; + break; + } + } + if (r->x + i == rects[j].x + rects[j].width) { + pend = check_vert_match(r, &rects[j]); + if (pend) { + r->x += i; + r->width -= i; + break; + } + } + } + } + break; + default: + break; + } + + /* y */ + pend = 0; + switch (align) { + case SWEST: + case SOUTH: + case SEAST: + h = py - r->y + (o->height - oy); + if (h < 10) + break; + r->height = h; + if (h <= snaph) + break; + /* snap to border */ + for (i = 0; !pend && (i < snaph); i++) { + if (r->y + r->height - i == rect.y + rect.height) { + r->height -= i; + break; + } + if (r->y + r->height + i == rect.y + rect.height) { + r->height += i; + break; + } + for (j = 0; j < num; j++) { + if (r->y + r->height - i == rects[j].y) { + pend = check_horiz_match(r, &rects[j]); + if (pend) { + r->height -= i; + break; + } + } + if (r->y + r->height + i == rects[j].y) { + pend = check_horiz_match(r, &rects[j]); + if (pend) { + r->height += i; + break; + } + } + } + } + break; + case NWEST: + case NORTH: + case NEAST: + h = r->height + r->y - py + oy; + if (h < 10) + break; + r->height = h; + r->y = py - oy; + if (h <= snaph) + break; + /* snap to border */ + for (i = 0; !pend && (i < snaph); i++) { + if (r->y - i == rect.y) { + r->y -= i; + r->height += i; + break; + } + if (r->y + i == rect.y) { + r->y += i; + r->height -= i; + break; + } + for (j = 0; j < num; j++) { + if (r->y - i == rects[j].y + rects[j].height) { + pend = check_horiz_match(r, &rects[j]); + if (pend) { + r->y -= i; + r->height += i; + break; + } + } + if (r->y + i == rects[j].y + rects[j].height) { + pend = check_horiz_match(r, &rects[j]); + if (pend) { + r->y += i; + r->height -= i; + break; + } + } + } + } + break; + default: + break; + } +} + + +void +mouse_resize(Frame * f, Align align) +{ + int px = 0, py = 0, i, ox, oy, first = 1; + Window dummy; + XEvent ev; + /* borders */ + int snapw = rect.width * _strtonum(defaults[WM_SNAP_VALUE]->content, 0, 1000) / 1000; + int snaph = rect.height * _strtonum(defaults[WM_SNAP_VALUE]->content, 0, 1000) / 1000; + unsigned int dmask; + unsigned int num; + XRectangle *rects = rectangles(&num); + XRectangle frect = *rect_of_frame(f); + XRectangle origin = frect; + + XQueryPointer(dpy, f->win, &dummy, &dummy, &i, &i, &ox, &oy, &dmask); + XSync(dpy, False); + XGrabServer(dpy); + XGrabPointer(dpy, f->win, False, ButtonMotionMask | ButtonReleaseMask, + GrabModeAsync, GrabModeAsync, None, resize_cursor, + CurrentTime); + for (;;) { + while (!XCheckMaskEvent(dpy, + ButtonReleaseMask | ButtonMotionMask, &ev)) { + usleep(20000); + continue; + } + + switch (ev.type) { + case ButtonRelease: + if (!first) { + XPoint pt; + draw_pseudo_border(&frect); + pt.x = px; + pt.y = py; + resize_frame(f, &frect, &pt, 0); + } + XUngrabPointer(dpy, CurrentTime /* ev.xbutton.time */ ); + XUngrabServer(dpy); + XSync(dpy, False); + return; + break; + case MotionNotify: + XTranslateCoordinates(dpy, f->win, root, ev.xmotion.x, + ev.xmotion.y, &px, &py, &dummy); + + if (first) + first = 0; + else + draw_pseudo_border(&frect); + + snap_resize(&frect, &origin, align, rects, num, px, + ox, py, oy, snapw, snaph); + draw_pseudo_border(&frect); + break; + } + } +} + + +void +drop_move(Frame * f, XRectangle * new, XPoint * pt) +{ + Area *a = f->area; + int cx, cy; + unsigned int i, idx = index_item((void **) a->frames, f); + + if ((f->rect.x == new->x) && (f->rect.y == new->y)) + return; + cx = (pt ? pt->x : new->x + new->width / 2); + cy = (pt ? pt->y : new->y + new->height / 2); + for (i = 0; a->frames[i]; i++) { + if ((a->frames[i] != f) && blitz_ispointinrect(cx, cy, &a->frames[i]->rect)) { + swap((void **) &a->frames[i], (void **) &a->frames[idx]); + a->sel = i; + a->layout->arrange(a); + return; + } + } +} diff --git a/cmd/wm/old/layout_grid.c b/cmd/wm/old/layout_grid.c new file mode 100644 index 00000000..083de72d --- /dev/null +++ b/cmd/wm/old/layout_grid.c @@ -0,0 +1,316 @@ +/* + * (C)opyright MMIV-MMV Anselm R. Garbe + * See LICENSE file for license details. + */ + +#include +#include +#include + +#include "wm.h" +#include "layout.h" + +#include + +/* grid layout definition */ +static void arrange_grid(Page * p); +static void init_grid(Page * p); +static void deinit_grid(Page * p); +static void manage_grid(Frame * f); +static void unmanage_grid(Frame * f); +static void resize_grid(Frame * f, XRectangle * new, XPoint * pt); +static Frame *select_grid(Frame * f, char *what); + +static Layout grid = +{"grid", init_grid, deinit_grid, arrange_grid, manage_grid, + unmanage_grid, resize_grid, select_grid +}; + +void +init_layout_grid() +{ + layouts = + (Layout **) attach_item_end((void **) layouts, &grid, + sizeof(Layout *)); +} + +static void +arrange_grid(Page * p) +{ + unsigned int i, ic, ir, tw, th, rows, cols; + + if (!p->managed) + return; + + blitz_getbasegeometry((void **) p->managed, &ic, &cols, &rows); + th = p->managed_rect.height / rows; + tw = p->managed_rect.width / cols; + + i = 0; + for (ir = 0; ir < rows; ir++) { + for (ic = 0; ic < cols; ic++) { + if (p->managed[i]) { + XRectangle *r = (XRectangle *) p->managed[i]->aux; + r->x = p->managed_rect.x + ic * tw; + r->y = p->managed_rect.y + ir * th; + r->width = tw; + r->height = th; + p->managed[i]->managed_rect = *r; + resize_frame(p->managed[i], &p->managed[i]->managed_rect, + 0, 1); + } else + break; + i++; + } + } +} + +static void +init_grid(Page * p) +{ + int i; + for (i = 0; p->managed && p->managed[i]; i++) + p->managed[i]->aux = emalloc(sizeof(XRectangle)); +} + +static void +deinit_grid(Page * p) +{ + int i; + for (i = 0; p->managed && p->managed[i]; i++) { + if (p->managed[i]->aux) { + free(p->managed[i]->aux); + p->managed[i]->aux = 0; + } + } +} + +static void +manage_grid(Frame * f) +{ + f->aux = emalloc(sizeof(XRectangle)); + if (f->page) + arrange_grid(f->page); +} + +static void +unmanage_grid(Frame * f) +{ + if (f->aux) { + free(f->aux); + f->aux = 0; + } + if (f->page) + arrange_grid(f->page); +} + +#define THRESHOLD 30 + +static void +drop_resize(Frame * f, XRectangle * new) +{ + int diff; + unsigned int i, rows, cols, cr, cc, num, idx; + Page *p = f->page; + XRectangle *r; + + if (!p || !p->managed) + return; + blitz_getbasegeometry((void **) p->managed, &i, &cols, &rows); + + num = index_item((void **) p->managed, f); + + cr = num / cols; + cc = num - cr * cols; + + /* horizontal resize */ + if (f->managed_rect.x == new->x && f->managed_rect.width != new->width) { + /* east direction resize */ + if (cc == cols - 1) + return; + if (p->managed[cc + 1] + && (new->x + new->width > p->managed[cc + 1]->managed_rect.x + + p->managed[cc + 1]->managed_rect.width - THRESHOLD)) + return; + diff = new->width - ((XRectangle *) f->aux)->width; + for (i = 0; i < rows; i++) { + idx = (i * cols) + cc; + if (p->managed[idx]) { + r = (XRectangle *) p->managed[idx]->aux; + r->width = new->width; + p->managed[idx]->managed_rect = *r; + resize_frame(p->managed[idx], + &p->managed[idx]->managed_rect, 0, 1); + } + idx = (i * cols) + (cc + 1); + if (p->managed[idx]) { + r = (XRectangle *) p->managed[idx]->aux; + r->x += diff; + r->width -= diff; + p->managed[idx]->managed_rect = *r; + resize_frame(p->managed[idx], + &p->managed[idx]->managed_rect, 0, 1); + } + } + } + if (f->managed_rect.x != new->x) { + /* west direction resize */ + if (!cc) + return; + if (new->x < p->managed[cc - 1]->managed_rect.x + THRESHOLD) + return; + diff = new->width - ((XRectangle *) f->aux)->width; + for (i = 0; i < rows; i++) { + idx = (i * cols) + cc; + if (p->managed[idx]) { + r = (XRectangle *) p->managed[idx]->aux; + r->x -= diff; + r->width += diff; + p->managed[idx]->managed_rect = *r; + resize_frame(p->managed[idx], + &p->managed[idx]->managed_rect, 0, 1); + } + idx = (i * cols) + (cc - 1); + if (p->managed[idx]) { + r = (XRectangle *) p->managed[idx]->aux; + r->width -= diff; + p->managed[idx]->managed_rect = *r; + resize_frame(p->managed[idx], + &p->managed[idx]->managed_rect, 0, 1); + } + } + } + /* vertical resize */ + if (f->managed_rect.y == new->y + && f->managed_rect.height != new->height) { + /* south direction resize */ + if (cr == rows - 1) + return; + if (p->managed[cr + 1] + && (new->y + new->height > + p->managed[(cr + 1) * rows]->managed_rect.y + + p->managed[(cr + 1) * rows]->managed_rect.height - + THRESHOLD)) + return; + diff = new->height - ((XRectangle *) f->aux)->height; + for (i = 0; i < cols; i++) { + idx = (cr * rows) + i; + if (p->managed[idx]) { + r = (XRectangle *) p->managed[idx]->aux; + r->height = new->height; + p->managed[idx]->managed_rect = *r; + resize_frame(p->managed[idx], + &p->managed[idx]->managed_rect, 0, 1); + } + idx = (cr + 1) * rows + i; + if (p->managed[idx]) { + r = (XRectangle *) p->managed[idx]->aux; + r->y += diff; + r->height -= diff; + p->managed[idx]->managed_rect = *r; + resize_frame(p->managed[idx], + &p->managed[idx]->managed_rect, 0, 1); + } + } + } else if (f->managed_rect.y != new->y) { + /* north direction resize */ + if (!cr) + return; + if (new->y < + p->managed[(cr - 1) * rows]->managed_rect.y + THRESHOLD) + return; + diff = new->height - ((XRectangle *) f->aux)->height; + for (i = 0; i < cols; i++) { + idx = (cr * rows) + i; + if (p->managed[idx]) { + r = (XRectangle *) p->managed[idx]->aux; + r->y -= diff; + r->height += diff; + p->managed[idx]->managed_rect = *r; + resize_frame(p->managed[idx], + &p->managed[idx]->managed_rect, 0, 1); + } + idx = (cr - 1) * rows + i; + if (p->managed[idx]) { + r = (XRectangle *) p->managed[idx]->aux; + r->height -= diff; + p->managed[idx]->managed_rect = *r; + resize_frame(p->managed[idx], + &p->managed[idx]->managed_rect, 0, 1); + } + } + } +} + +static void +resize_grid(Frame * f, XRectangle * new, XPoint * pt) +{ + if ((f->managed_rect.width == new->width) + && (f->managed_rect.height == new->height)) + drop_move(f, new, pt); + else + drop_resize(f, new); +} + +static unsigned int +get_current_frame_position(Frame * f) +{ + Page *p = f->page; + unsigned int i; + + for (i = 0; i < count_items((void **) p->managed); ++i) + if (p->managed[i] == f) + return i; + return -1; +} + +static Frame * +select_grid(Frame * f, char *what) +{ + Page *p = f->page; + unsigned int ic, cols, rows, pos; + int idx; + + blitz_getbasegeometry((void **) p->managed, &ic, &cols, &rows); + pos = get_current_frame_position(f); + + if (!strncmp(what, "prev", 5)) { + idx = index_prev_item((void **) p->managed, f); + if (idx >= 0) + return p->managed[idx]; + } else if (!strncmp(what, "next", 5)) { + idx = index_next_item((void **) p->managed, f); + if (idx >= 0) + return p->managed[idx]; + } else if (!strncmp(what, "south", 6)) { + if ((pos + cols) <= ic) + pos += cols; + else + pos %= cols; + return p->managed[pos]; + } else if (!strncmp(what, "north", 6)) { + if (pos < cols) + pos = (rows - 1) * cols + pos; + else + pos -= cols; + return p->managed[pos]; + } else if (!strncmp(what, "west", 5)) { + if (pos % cols == 0) { + pos += (cols - 1); + if (pos >= ic) + pos = ic - 1; + } else + pos--; + return p->managed[pos]; + } else if (!strncmp(what, "east", 5)) { + if (pos % cols == (cols - 1)) + pos -= (cols - 1); + else { + pos++; + if (pos >= ic) + pos = ic - 1; + } + return p->managed[pos]; + } + return 0; +} diff --git a/cmd/wm/old/layout_max.c b/cmd/wm/old/layout_max.c new file mode 100644 index 00000000..195a2064 --- /dev/null +++ b/cmd/wm/old/layout_max.c @@ -0,0 +1,115 @@ +/* + * (C)opyright MMIV-MMV Anselm R. Garbe + * See LICENSE file for license details. + */ + +#include +#include +#include + +#include "wm.h" +#include "layout.h" + +static void arrange_max(Page * p); +static void init_max(Page * p); +static void deinit_max(Page * p); +static void manage_max(Frame * f); +static void unmanage_max(Frame * f); +static void resize_max(Frame * f, XRectangle * new, XPoint * pt); +static Frame *select_max(Frame * f, char *what); + +static Layout max = {"max", init_max, deinit_max, arrange_max, manage_max, + unmanage_max, resize_max, select_max +}; + +void +init_layout_max() +{ + layouts = + (Layout **) attach_item_end((void **) layouts, &max, + sizeof(Layout *)); +} + + +static void +arrange_max(Page * p) +{ + int i = 0; + if (!p->managed) + return; + + for (i = 0; p->managed[i]; i++) { + p->managed[i]->managed_rect = p->managed_rect; + resize_frame(p->managed[i], &p->managed[i]->managed_rect, 0, 1); + } + if (p->managed_stack) + XRaiseWindow(dpy, p->managed_stack[0]->win); + + /* raise floatings */ + for (i = 0; p->floating && p->floating[i]; i++) + XRaiseWindow(dpy, p->floating[i]->win); +} + +static void +init_max(Page * p) +{ + /* max has nothing to init */ + arrange_max(p); +} + +static void +deinit_max(Page * p) +{ + /* max has nothing to free */ +} + +static void +manage_max(Frame * f) +{ + Page *p = f->page; + int idx; + if (!p) + return; + idx = index_next_item((void **) p->managed, f); + if (idx > 0) + swap((void **) &p->managed[0], (void **) &p->managed[idx]); + arrange_max(f->page); +} + +static void +unmanage_max(Frame * f) +{ + /* nothing todo */ +} + +static void +resize_max(Frame * f, XRectangle * new, XPoint * pt) +{ + if (f->page) + f->managed_rect = f->page->managed_rect; +} + +static Frame * +select_max(Frame * f, char *what) +{ + Page *p = f->page; + Frame *res = 0; + int i, idx; + + if (!strncmp(what, "prev", 5)) { + idx = index_prev_item((void **) p->managed, f); + if (idx >= 0) + res = p->managed[idx]; + } else if (!strncmp(what, "next", 5)) { + idx = index_next_item((void **) p->managed, f); + if (idx >= 0) + res = p->managed[idx]; + } + if (res) + XRaiseWindow(dpy, res->win); + + /* raise floatings */ + for (i = 0; p->floating && p->floating[i]; i++) + XRaiseWindow(dpy, p->floating[i]->win); + return res; +} diff --git a/cmd/wm/old/layout_tiled.c b/cmd/wm/old/layout_tiled.c new file mode 100644 index 00000000..978c84db --- /dev/null +++ b/cmd/wm/old/layout_tiled.c @@ -0,0 +1,176 @@ +/* + * (C)opyright MMIV-MMV Anselm R. Garbe + * See LICENSE file for license details. + */ + +#include +#include + +#include "wm.h" +#include "layout.h" + +#include + +/* tiled layout definition */ +static void arrange_tiled(Page * p); +static void init_tiled(Page * p); +static void deinit_tiled(Page * p); +static void manage_tiled(Frame * f); +static void unmanage_tiled(Frame * f); +static void resize_tiled(Frame * f, XRectangle * new, XPoint * pt); +static Frame *select_tiled(Frame * f, char *what); + +static Layout tiled = +{"tiled", init_tiled, deinit_tiled, arrange_tiled, manage_tiled, + unmanage_tiled, resize_tiled, select_tiled +}; + +void +init_layout_tiled() +{ + layouts = + (Layout **) attach_item_end((void **) layouts, &tiled, + sizeof(Layout *)); +} + +static void +arrange_tiled(Page * p) +{ + int tw, th, i, size = 0; + + if (!p->managed) + return; + + /* determing num of frame and size of page */ + size = count_items((void **) p->managed); + + if (size > 1) { + tw = (p->managed_rect.width * *((int *) p->aux)) / 100; + th = p->managed_rect.height / (size - 1); + } else { + tw = p->managed_rect.width; + th = p->managed_rect.height; + } + + /* master tile */ + p->managed[0]->managed_rect.x = p->managed_rect.x; + p->managed[0]->managed_rect.y = p->managed_rect.y; + p->managed[0]->managed_rect.width = tw; + p->managed[0]->managed_rect.height = p->managed_rect.height; + resize_frame(p->managed[0], &p->managed[0]->managed_rect, 0, 1); + + if (size == 1) + return; + for (i = 1; i < size; i++) { + p->managed[i]->managed_rect.x = p->managed_rect.x + tw; + p->managed[i]->managed_rect.y = p->managed_rect.y + (i - 1) * th; + p->managed[i]->managed_rect.width = p->managed_rect.width - tw; + p->managed[i]->managed_rect.height = th; + resize_frame(p->managed[i], &p->managed[i]->managed_rect, 0, 1); + } +} + +static void +init_tiled(Page * p) +{ + p->aux = emalloc(sizeof(int)); + *((int *) p->aux) = + _strtonum(core_files[CORE_PAGE_TILE_WIDTH]->content, 5, 95); +} + +static void +deinit_tiled(Page * p) +{ + p->aux = 0; +} + +static void +manage_tiled(Frame * f) +{ + Page *p = f->page; + if (!p) + return; + arrange_tiled(p); +} + +static void +unmanage_tiled(Frame * f) +{ + manage_tiled(f); +} + +static void +drop_resize(Frame * f, XRectangle * new) +{ + Page *p = f->page; + int num = 0; + int rearrange = 0; + + if (!p) + return; + /* determing num of frame and size of page */ + num = index_item((void **) p->managed, f); + + if (!num) { /* master tile */ + if ((f->managed_rect.x == new->x) + && (f->managed_rect.width != new->width)) { + f->managed_rect = *new; + rearrange = 1; + } + } else if (f->managed_rect.x != new->x) { + int diff = f->managed_rect.width - new->width; + p->managed[0]->managed_rect.width += diff; + rearrange = 1; + } + if (rearrange) { + int tw = + (p->managed[0]->managed_rect.width * 100) / + p->managed_rect.width; + if (tw > 95) + tw = 95; + else if (tw < 5) + tw = 5; + *((int *) p->aux) = tw; + arrange_tiled(p); + } +} + +static void +resize_tiled(Frame * f, XRectangle * new, XPoint * pt) +{ + if ((f->managed_rect.width == new->width) + && (f->managed_rect.height == new->height)) + drop_move(f, new, pt); + else + drop_resize(f, new); +} + +static Frame * +select_tiled(Frame * f, char *what) +{ + Page *p = f->page; + int idx; + if (!strncmp(what, "prev", 5)) { + idx = index_prev_item((void **) p->managed, f); + if (idx >= 0) + return p->managed[idx]; + } else if (!strncmp(what, "next", 5)) { + idx = index_next_item((void **) p->managed, f); + if (idx >= 0) + return p->managed[idx]; + } else if (!strncmp(what, "zoomed", 7)) { + idx = index_item((void **) p->managed, f); + if (idx == 0 && p->managed[1]) + idx = 1; + if (idx > 0) + swap((void **) &p->managed[0], (void **) &p->managed[idx]); + p->managed_stack = (Frame **) + attach_item_begin(detach_item + ((void **) p->managed_stack, p->managed[0], + sizeof(Frame *)), p->managed[0], + sizeof(Frame *)); + arrange_tiled(p); + return p->managed_stack[0]; + } + return 0; +} diff --git a/cmd/wm/old/layout_vsplit.c b/cmd/wm/old/layout_vsplit.c new file mode 100644 index 00000000..09c5d73c --- /dev/null +++ b/cmd/wm/old/layout_vsplit.c @@ -0,0 +1,223 @@ +/* + * (C)opyright 2005 Jonas WUSTRACK + * (C)opyright MMIV-MMV Anselm R. Garbe + * See LICENSE file for license details. + */ + +#include +#include +#include + +#include "wm.h" +#include "layout.h" + +#include + +static void arrange_vsplit(Page * p); +static void init_vsplit(Page * p); +static void deinit_vsplit(Page * p); +static void manage_vsplit(Frame * f); +static void unmanage_vsplit(Frame * f); +static void resize_vsplit(Frame * f, XRectangle * new, XPoint * pt); +static Frame *select_vsplit(Frame * f, char *what); + +static Layout vsplit = +{"vsplit", init_vsplit, deinit_vsplit, arrange_vsplit, manage_vsplit, + unmanage_vsplit, resize_vsplit, select_vsplit +}; + +void +init_layout_vsplit() +{ + layouts = + (Layout **) attach_item_end((void **) layouts, &vsplit, + sizeof(Layout *)); +} + +static void +get_base_geometry_vsplit(void **items, unsigned int *size, + unsigned int *cols, unsigned int *rows) +{ + /* float sq, dummy; */ + + *size = count_items((void **) items); + *cols = 1; + *rows = *size; +} + + +static void +arrange_vsplit(Page * p) +{ + unsigned int i, ic, tw, th, rows, cols; + + if (!p->managed) + return; + + get_base_geometry_vsplit((void **) p->managed, &ic, &cols, &rows); + th = p->managed_rect.height / rows; + tw = p->managed_rect.width; + + for (i = 0; i < rows; i++) { + if (p->managed[i]) { + XRectangle *r = (XRectangle *) p->managed[i]->aux; + r->x = p->managed_rect.x; + r->y = p->managed_rect.y + i * th; + r->width = tw; + r->height = th; + p->managed[i]->managed_rect = *r; + resize_frame(p->managed[i], &p->managed[i]->managed_rect, 0, + 1); + } + } +} + +static void +init_vsplit(Page * p) +{ + int i; + for (i = 0; p->managed && p->managed[i]; i++) + p->managed[i]->aux = emalloc(sizeof(XRectangle)); +} + +static void +deinit_vsplit(Page * p) +{ + int i; + for (i = 0; p->managed && p->managed[i]; i++) { + if (p->managed[i]->aux) { + free(p->managed[i]->aux); + p->managed[i]->aux = 0; + } + } +} + +static void +manage_vsplit(Frame * f) +{ + f->aux = emalloc(sizeof(XRectangle)); + if (f->page) + arrange_vsplit(f->page); +} + +static void +unmanage_vsplit(Frame * f) +{ + if (f->aux) { + free(f->aux); + f->aux = 0; + } + if (f->page) + arrange_vsplit(f->page); +} + +#define THRESHOLD 30 + +static void +drop_resize(Frame * f, XRectangle * new) +{ + int diff; + unsigned int i, rows, cols, num, idx; + Page *p = f->page; + XRectangle *r; + + if (!p || !p->managed) + return; + get_base_geometry_vsplit((void **) p->managed, &i, &cols, &rows); + + num = index_item((void **) p->managed, f); + + /* vertical resize */ + if (f->managed_rect.y == new->y + && f->managed_rect.height != new->height) { + /* south direction resize */ + if (num == rows - 1) + return; + if (p->managed[num + 1] + && (new->y + new->height > p->managed[num + 1]->managed_rect.y + + p->managed[num + 1]->managed_rect.height - THRESHOLD)) + return; + diff = new->height - ((XRectangle *) f->aux)->height; + idx = num; + if (p->managed[idx]) { + r = (XRectangle *) p->managed[idx]->aux; + r->height = new->height; + p->managed[idx]->managed_rect = *r; + resize_frame(p->managed[idx], &p->managed[idx]->managed_rect, + 0, 1); + } + idx = num + 1; + if (p->managed[idx]) { + r = (XRectangle *) p->managed[idx]->aux; + r->y += diff; + r->height -= diff; + p->managed[idx]->managed_rect = *r; + resize_frame(p->managed[idx], &p->managed[idx]->managed_rect, + 0, 1); + } + } else if (f->managed_rect.y != new->y) { + /* north direction resize */ + if (!num) + return; + if (new->y < p->managed[num - 1]->managed_rect.y + THRESHOLD) + return; + diff = new->height - ((XRectangle *) f->aux)->height; + idx = num; + if (p->managed[idx]) { + r = (XRectangle *) p->managed[idx]->aux; + r->y -= diff; + r->height += diff; + p->managed[idx]->managed_rect = *r; + resize_frame(p->managed[idx], &p->managed[idx]->managed_rect, + 0, 1); + } + idx = num - 1; + if (p->managed[idx]) { + r = (XRectangle *) p->managed[idx]->aux; + r->height -= diff; + p->managed[idx]->managed_rect = *r; + resize_frame(p->managed[idx], &p->managed[idx]->managed_rect, + 0, 1); + } + } +} + +static void +resize_vsplit(Frame * f, XRectangle * new, XPoint * pt) +{ + if ((f->managed_rect.width == new->width) + && (f->managed_rect.height == new->height)) + drop_move(f, new, pt); + else + drop_resize(f, new); +} + +static Frame * +select_vsplit(Frame * f, char *what) +{ + Page *p = f->page; + int idx; + if (!strncmp(what, "prev", 5)) { + idx = index_prev_item((void **) p->managed, f); + if (idx >= 0) + return p->managed[idx]; + } else if (!strncmp(what, "next", 5)) { + idx = index_next_item((void **) p->managed, f); + if (idx >= 0) + return p->managed[idx]; + } else if (!strncmp(what, "zoomed", 7)) { + idx = index_item((void **) p->managed, f); + if (idx == 0 && p->managed[1]) + idx = 1; + if (idx > 0) + swap((void **) &p->managed[0], (void **) &p->managed[idx]); + p->managed_stack = (Frame **) + attach_item_begin(detach_item + ((void **) p->managed_stack, p->managed[0], + sizeof(Frame *)), p->managed[0], + sizeof(Frame *)); + arrange_vsplit(p); + return p->managed_stack[0]; + } + return 0; +} diff --git a/cmd/wm/page.c b/cmd/wm/page.c new file mode 100644 index 00000000..a09ae4d4 --- /dev/null +++ b/cmd/wm/page.c @@ -0,0 +1,463 @@ +/* + * (C)opyright MMIV-MMV Anselm R. Garbe + * See LICENSE file for license details. + */ + +#include +#include +#include + +#include "wm.h" + +#include + +static Page zero_page = {0}; + +static void select_frame(void *obj, char *cmd); +static void _toggle_frame(void *obj, char *cmd); +static void handle_after_write_page(IXPServer * s, File * f); +static void handle_before_read_page(IXPServer * s, File * f); + +/* action table for /page/?/ namespace */ +Action page_acttbl[] = { + {"select", select_frame}, + {"toggle", _toggle_frame}, + {0, 0} +}; + +Page * +alloc_page(char *autodestroy) +{ + Page *p = emalloc(sizeof(Page)); + char buf[MAX_BUF], buf2[MAX_BUF]; + int id = count_items((void **) pages) + 1; + + *p = zero_page; + p->managed_rect = rect; + if (core_files[CORE_PAGE_MANAGED_SIZE]->content) + blitz_strtorect(dpy, &rect, &p->managed_rect, + core_files[CORE_PAGE_MANAGED_SIZE]->content); + + snprintf(buf, sizeof(buf), "/page/%d", id); + p->files[P_PREFIX] = ixp_create(ixps, buf); + snprintf(buf, sizeof(buf), "/page/%d/floating", id); + p->files[P_FLOATING_PREFIX] = ixp_create(ixps, buf); + snprintf(buf, sizeof(buf), "/page/%d/floating/sel", id); + p->files[P_FLOATING_SELECTED] = ixp_create(ixps, buf); + p->files[P_FLOATING_SELECTED]->bind = 1; /* mount point */ + snprintf(buf, sizeof(buf), "/page/%d/floating/name", id); + p->files[P_FLOATING_LAYOUT] = wmii_create_ixpfile(ixps, buf, "float"); + snprintf(buf, sizeof(buf), "/page/%d/managed", id); + p->files[P_MANAGED_PREFIX] = ixp_create(ixps, buf); + snprintf(buf, sizeof(buf), "/page/%d/managed/sel", id); + p->files[P_MANAGED_SELECTED] = ixp_create(ixps, buf); + p->files[P_MANAGED_SELECTED]->bind = 1; /* mount point */ + snprintf(buf, sizeof(buf), "/page/%d/ctl", id); + p->files[P_CTL] = ixp_create(ixps, buf); + p->files[P_CTL]->after_write = handle_after_write_page; + snprintf(buf, sizeof(buf), "/page/%d/name", id); + snprintf(buf2, sizeof(buf2), "%d", id); + p->files[P_NAME] = wmii_create_ixpfile(ixps, buf, buf2); + snprintf(buf, sizeof(buf), "/page/%d/managed/name", id); + p->files[P_MANAGED_LAYOUT] = + wmii_create_ixpfile(ixps, buf, + core_files[CORE_PAGE_LAYOUT]->content); + p->files[P_MANAGED_LAYOUT]->after_write = handle_after_write_page; + snprintf(buf, sizeof(buf), "/page/%d/mode", id); + p->files[P_MODE] = ixp_create(ixps, buf); + p->files[P_MODE]->bind = 1; + snprintf(buf, sizeof(buf), "/page/%d/managed/size", id); + p->files[P_MANAGED_SIZE] = wmii_create_ixpfile(ixps, buf, core_files[CORE_PAGE_MANAGED_SIZE]->content); + p->files[P_MANAGED_SIZE]->after_write = handle_after_write_page; + p->files[P_MANAGED_SIZE]->before_read = handle_before_read_page; + snprintf(buf, sizeof(buf), "/page/%d/auto-destroy", id); + p->files[P_AUTO_DESTROY] = wmii_create_ixpfile(ixps, buf, autodestroy); + p->layout = get_layout(p->files[P_MANAGED_LAYOUT]->content); + if (p->layout) { + p->layout->init(p); + p->files[P_MODE]->content = p->files[P_MANAGED_PREFIX]->content; + } else + p->files[P_MODE]->content = p->files[P_FLOATING_PREFIX]->content; + pages = (Page **) attach_item_end((void **) pages, p, sizeof(Page *)); + sel_page = index_item((void **) pages, p); + core_files[CORE_PAGE_SELECTED]->content = p->files[P_PREFIX]->content; + invoke_core_event(core_files[CORE_EVENT_PAGE_UPDATE]); + return p; +} + +void +free_page(Page * p) +{ + pages = (Page **) detach_item((void **) pages, p, sizeof(Page *)); + if (pages) { + int i; + char buf[8]; + if (sel_page - 1 >= 0) + sel_page--; + else + sel_page = 0; + for (i = 0; pages[i]; i++) { + snprintf(buf, sizeof(buf), "%d", i + 1); + free(pages[i]->files[P_PREFIX]->name); + pages[i]->files[P_PREFIX]->name = estrdup(buf); + free(pages[i]->files[P_NAME]->content); + pages[i]->files[P_NAME]->content = estrdup(buf); + pages[i]->files[P_NAME]->size = strlen(buf); + } + } + if (p->layout) + p->layout->deinit(p); + core_files[CORE_PAGE_SELECTED]->content = 0; + ixp_remove_file(ixps, p->files[P_PREFIX]); + if (ixps->errstr) + fprintf(stderr, "wmiiwm: free_page(): %s\n", ixps->errstr); + free(p); +} + +void +draw_page(Page * p) +{ + int i; + if (!p) + return; + for (i = 0; p->floating && p->floating[i]; i++) + draw_frame(p->floating[i]); + for (i = 0; p->managed && p->managed[i]; i++) + draw_frame(p->managed[i]); +} + +XRectangle * +rectangles(unsigned int *num) +{ + XRectangle *result = 0; + int i, j = 0; + Window d1, d2; + Window *wins; + XWindowAttributes wa; + XRectangle r; + + if (XQueryTree(dpy, root, &d1, &d2, &wins, num)) { + result = emalloc(*num * sizeof(XRectangle)); + for (i = 0; i < *num; i++) { + if (!XGetWindowAttributes(dpy, wins[i], &wa)) + continue; + if (wa.override_redirect && (wa.map_state == IsViewable)) { + r.x = wa.x; + r.y = wa.y; + r.width = wa.width; + r.height = wa.height; + result[j++] = r; + } + } + } + if (wins) { + XFree(wins); + } + *num = j; + return result; +} + +static void +_toggle_frame(void *obj, char *cmd) +{ + Page *p = obj; + Frame *f; + if (!p || !p->layout) + return; + f = get_selected(p); + if (!f) + return; + toggle_frame(f); +} + +static Frame * +select_toggled(Page * p, Frame * f) +{ + if (is_managed_frame(f)) + return p->floating_stack ? p->floating_stack[0] : 0; + else + return f = p->managed_stack ? p->managed_stack[0] : 0; + return 0; +} + +Frame * +select_floating(Page * p, Frame * f, char *what) +{ + int idx; + if (!strncmp(what, "prev", 5)) { + idx = index_prev_item((void **) p->floating, f); + if (idx >= 0) + return p->floating[idx]; + } else if (!strncmp(what, "next", 5)) { + idx = index_next_item((void **) p->floating, f); + if (idx >= 0) + return p->floating[idx]; + } + return 0; +} + +static void +center_pointer(Frame * f) +{ + + Window dummy; + int wex, wey, ex, ey, i; + unsigned int dmask; + XRectangle *r; + + if (!f) + return; + r = rect_of_frame(f); + XQueryPointer(dpy, f->win, &dummy, &dummy, &i, &i, &wex, &wey, &dmask); + XTranslateCoordinates(dpy, f->win, root, wex, wey, &ex, &ey, &dummy); + if (blitz_ispointinrect(ex, ey, r)) + return; + /* suppress EnterNotify's while mouse warping */ + XSelectInput(dpy, root, ROOT_MASK & ~StructureNotifyMask); + XWarpPointer(dpy, None, f->win, 0, 0, 0, 0, + r->width / 2, r->height / 2); + XSync(dpy, False); + XSelectInput(dpy, root, ROOT_MASK); + +} + +static void +select_frame(void *obj, char *cmd) +{ + Page *p = (Page *) obj; + Frame *f, *old, *old2; + if (!p || !cmd) + return; + old2 = old = f = get_selected(p); + if (!f) + return; + if (is_managed_frame(f)) + old2 = p->managed[0]; + if (!strncmp(cmd, "toggled", 8)) + f = select_toggled(p, f); + else if (is_managed_frame(f)) + f = p->layout->select(f, cmd); + else + f = select_floating(p, f, cmd); + if (!f) + return; + focus_frame(f, 1, 1, 1); + center_pointer(f); + if (old) + draw_frame(old); + if (old2 != old) + draw_frame(old2); /* on zoom */ + draw_frame(f); +} + +void +hide_page(Page * p) +{ + + int i; + for (i = 0; p->floating && p->floating[i]; i++) + XUnmapWindow(dpy, p->floating[i]->win); + for (i = 0; p->managed && p->managed[i]; i++) + XUnmapWindow(dpy, p->managed[i]->win); + XSync(dpy, False); +} + +void +show_page(Page * p) +{ + int i; + for (i = 0; p->floating && p->floating[i]; i++) + XMapWindow(dpy, p->floating[i]->win); + for (i = 0; p->managed && p->managed[i]; i++) + XMapWindow(dpy, p->managed[i]->win); + XSync(dpy, False); +} + +static void +handle_before_read_page(IXPServer * s, File * f) +{ + int i; + XRectangle *rct = 0; + for (i = 0; pages && pages[i]; i++) { + if (pages[i]->files[P_MANAGED_SIZE] == f) { + rct = &pages[i]->managed_rect; + break; + } + } + + if (rct) { + char buf[64]; + snprintf(buf, 64, "%d,%d,%d,%d", rct->x, rct->y, + rct->width, rct->height); + if (f->content) + free(f->content); + f->content = estrdup(buf); + f->size = strlen(buf); + } +} + +Layout * +get_layout(char *name) +{ + int i = 0; + size_t len; + if (!name) + return 0; + len = strlen(name); + for (i = 0; layouts[i]; i++) { + if (!strncmp(name, layouts[i]->name, len)) + return layouts[i]; + } + return 0; +} + +static void +handle_after_write_page(IXPServer * s, File * f) +{ + int i; + + for (i = 0; pages && pages[i]; i++) { + Page *p = pages[i]; + if (p->files[P_CTL] == f) { + run_action(f, p, page_acttbl); + return; + } else if (p->files[P_MANAGED_SIZE] == f) { + /* resize stuff */ + blitz_strtorect(dpy, &rect, &p->managed_rect, + p->files[P_MANAGED_SIZE]->content); + if (!p->managed_rect.width) + p->managed_rect.width = 10; + if (!p->managed_rect.height) + p->managed_rect.height = 10; + if (p->layout) + p->layout->arrange(p); + draw_page(p); + return; + } else if (p->files[P_MANAGED_LAYOUT] == f) { + int had_valid_layout = p->layout ? 1 : 0; + if (p->layout) + p->layout->deinit(p); + p->layout = get_layout(p->files[P_MANAGED_LAYOUT]->content); + if (p->layout) { + p->layout->init(p); + p->layout->arrange(p); + if (!had_valid_layout) { + int j; + Frame **tmp = 0; + for (j = 0; p->floating && p->floating[j]; j++) { + if (!p->floating[j]->floating) + tmp = + (Frame **) attach_item_begin((void **) tmp, + p-> + floating[j], + sizeof(Frame + *)); + } + for (j = 0; tmp && tmp[j]; j++) + toggle_frame(tmp[j]); + free(tmp); + } + } + if (!p->layout) { + /* make all managed clients floating */ + int j; + Frame **tmp = 0; + while (p->managed) { + tmp = (Frame **) attach_item_begin((void **) tmp, + p->managed[0], + sizeof(Frame *)); + detach_frame_from_page(p->managed[0], 1); + } + for (j = 0; tmp && tmp[j]; j++) { + attach_Frameo_page(p, tmp[j], 0); + resize_frame(tmp[j], rect_of_frame(tmp[j]), 0, 1); + } + free(tmp); + } + draw_page(p); + invoke_core_event(core_files[CORE_EVENT_PAGE_UPDATE]); + return; + } + } +} + +void +attach_Frameo_page(Page * p, Frame * f, int managed) +{ + Frame *old = get_selected(p); + XSelectInput(dpy, root, ROOT_MASK & ~StructureNotifyMask); + XMapRaised(dpy, f->win); + if (!f->floating && managed && p->layout) { + int i; + p->managed = (Frame **) attach_item_end((void **) p->managed, f, + sizeof(Frame *)); + p->managed_stack = + (Frame **) attach_item_begin((void **) p->managed_stack, f, + sizeof(Frame *)); + wmii_move_ixpfile(f->files[F_PREFIX], p->files[P_MANAGED_PREFIX]); + p->files[P_MANAGED_SELECTED]->content = + f->files[F_PREFIX]->content; + if (p == pages[sel_page]) + for (i = 0; p->floating && p->floating[i]; i++) + XRaiseWindow(dpy, p->floating[i]->win); + } else { + p->floating = (Frame **) attach_item_end((void **) p->floating, f, + sizeof(Frame *)); + p->floating_stack = + (Frame **) attach_item_begin((void **) p->floating_stack, f, + sizeof(Frame *)); + wmii_move_ixpfile(f->files[F_PREFIX], p->files[P_FLOATING_PREFIX]); + p->files[P_FLOATING_SELECTED]->content = + f->files[F_PREFIX]->content; + p->files[P_MODE]->content = p->files[P_FLOATING_PREFIX]->content; + } + f->page = p; + focus_frame(f, 1, 0, 1); + if (is_managed_frame(f) && p->layout) + p->layout->manage(f); + center_pointer(f); + if (old) + draw_frame(old); + draw_frame(f); +} + +void +detach_frame_from_page(Frame * f, int ignore_focus_and_destroy) +{ + Page *p = f->page; + wmii_move_ixpfile(f->files[F_PREFIX], core_files[CORE_DETACHED_FRAME]); + if (is_managed_frame(f)) { + p->managed = (Frame **) detach_item((void **) p->managed, f, + sizeof(Frame *)); + p->managed_stack = + (Frame **) detach_item((void **) p->managed_stack, f, + sizeof(Frame *)); + p->files[P_MANAGED_SELECTED]->content = 0; + } else { + p->floating = (Frame **) detach_item((void **) p->floating, f, + sizeof(Frame *)); + p->floating_stack = + (Frame **) detach_item((void **) p->floating_stack, f, + sizeof(Frame *)); + p->files[P_FLOATING_SELECTED]->content = 0; + } + XUnmapWindow(dpy, f->win); + if (is_managed_mode(p) && p->layout) + p->layout->unmanage(f); + f->page = 0; + if (!ignore_focus_and_destroy) { + Frame *fr; + if (!p->managed && !p->floating + && _strtonum(p->files[P_AUTO_DESTROY]->content, 0, 1)) { + destroy_page(p); + return; + } + focus_page(p, 0, 1); + fr = get_selected(p); + if (fr) { + center_pointer(fr); + draw_frame(fr); + } + } +} + diff --git a/cmd/wm/wm.c b/cmd/wm/wm.c new file mode 100644 index 00000000..2d27d0f6 --- /dev/null +++ b/cmd/wm/wm.c @@ -0,0 +1,290 @@ +/* + * (C)opyright MMIV-MMV Anselm R. Garbe + * See LICENSE file for license details. + */ + +#include +#include +#include +#include +#include +#include +#include + +#include "wm.h" + +static int other_wm_running; +static int (*x_error_handler) (Display *, XErrorEvent *); + +char *version[] = { + "wmiiwm - window manager improved 2 - " VERSION "\n" + " (C)opyright MMIV-MMV Anselm R. Garbe\n", 0 +}; + +static void +usage() +{ + fprintf(stderr, "%s", + "usage: wmiiwm -s [-c] [-v]\n" + " -s socket file\n" + " -c checks if another WM is already running\n" + " -v version info\n"); + exit(1); +} + +static void +init_atoms() +{ + wm_state = XInternAtom(dpy, "WM_STATE", False); + wm_change_state = XInternAtom(dpy, "WM_CHANGE_STATE", False); + wm_protocols = XInternAtom(dpy, "WM_PROTOCOLS", False); + wm_delete = XInternAtom(dpy, "WM_DELETE_WINDOW", False); + motif_wm_hints = XInternAtom(dpy, "_MOTIF_WM_HINTS", False); + net_wm_desktop = XInternAtom(dpy, "_NET_WM_DESKTOP", False); +} + +static void +init_cursors() +{ + normal_cursor = XCreateFontCursor(dpy, XC_left_ptr); + resize_cursor = XCreateFontCursor(dpy, XC_sizing); + move_cursor = XCreateFontCursor(dpy, XC_fleur); + drag_cursor = XCreateFontCursor(dpy, XC_cross); + w_cursor = XCreateFontCursor(dpy, XC_left_side); + e_cursor = XCreateFontCursor(dpy, XC_right_side); + n_cursor = XCreateFontCursor(dpy, XC_top_side); + s_cursor = XCreateFontCursor(dpy, XC_bottom_side); + nw_cursor = XCreateFontCursor(dpy, XC_top_left_corner); + ne_cursor = XCreateFontCursor(dpy, XC_top_right_corner); + sw_cursor = XCreateFontCursor(dpy, XC_bottom_left_corner); + se_cursor = XCreateFontCursor(dpy, XC_bottom_right_corner); +} + +static void +init_defaults() +{ + defaults[WM_DETACHED_FRAME] = ixp_create(ixps, "/detached/frame"); + defaults[WM_DETACHED_CLIENT] = ixp_create(ixps, "/detached/client"); + defaults[WM_TRANS_COLOR] = wmii_create_ixpfile(ixps, "/default/transcolor", BLITZ_SEL_FG_COLOR); + defaults[WM_TRANS_COLOR]->after_write = handle_after_write; + defaults[WM_SEL_BG_COLOR] = wmii_create_ixpfile(ixps, "/default/selstyle/bgcolor", BLITZ_SEL_BG_COLOR); + defaults[WM_SEL_FG_COLOR] = wmii_create_ixpfile(ixps, "/default/selstyle/fgcolor", BLITZ_SEL_FG_COLOR); + defaults[WM_SEL_BORDER_COLOR] = wmii_create_ixpfile(ixps, "/default/selstyle/fgcolor", BLITZ_SEL_BORDER_COLOR); + defaults[WM_NORM_BG_COLOR] = wmii_create_ixpfile(ixps, "/default/normstyle/bgcolor", BLITZ_NORM_BG_COLOR); + defaults[WM_NORM_FG_COLOR] = wmii_create_ixpfile(ixps, "/default/normstyle/fgcolor", BLITZ_NORM_FG_COLOR); + defaults[WM_NORM_BORDER_COLOR] = wmii_create_ixpfile(ixps, "/default/normstyle/fgcolor", BLITZ_NORM_BORDER_COLOR); + defaults[WM_FONT] = wmii_create_ixpfile(ixps, "/default/font", BLITZ_FONT); + defaults[WM_FONT]->after_write = handle_after_write; + defaults[WM_PAGE_SIZE] = wmii_create_ixpfile(ixps, "/default/pagesize", "0,0,east,south-16"); + defaults[WM_SNAP_VALUE] = wmii_create_ixpfile(ixps, "/default/snapvalue", "20"); /* 0..1000 */ + defaults[WM_BORDER] = wmii_create_ixpfile(ixps, "/default/border", "1"); + defaults[WM_TAB] = wmii_create_ixpfile(ixps, "/default/tab", "1"); + defaults[WM_HANDLE_INC] = wmii_create_ixpfile(ixps, "/default/handleinc", "1"); + defaults[WM_LOCKED] = wmii_create_ixpfile(ixps, "/default/locked", "1"); + defaults[WM_LAYOUT] = wmii_create_ixpfile(ixps, "/default/layout", LAYOUT); + defaults[WM_SEL_PAGE] = ixp_create(ixps, "/page/sel"); + defaults[WM_EVENT_PAGE_UPDATE] = ixp_create(ixps, "/default/event/pageupdate"); + defaults[WM_EVENT_CLIENT_UPDATE] = ixp_create(ixps, "/default/event/clientupdate"); + defaults[WM_EVENT_B1PRESS] = ixp_create(ixps, "/defaults/event/b1press"); + defaults[WM_EVENT_B2PRESS] = ixp_create(ixps, "/defaults/event/b2press"); + defaults[WM_EVENT_B3PRESS] = ixp_create(ixps, "/defaults/event/b3press"); + defaults[WM_EVENT_B4PRESS] = ixp_create(ixps, "/defaults/event/b4press"); + defaults[WM_EVENT_B5PRESS] = ixp_create(ixps, "/defaults/event/b5press"); +} + +static void +init_screen() +{ + XGCValues gcv; + XSetWindowAttributes wa; + + XAllocNamedColor(dpy, DefaultColormap(dpy, screen_num), + defaults[WM_TRANS_COLOR]->content, + &xorcolor, &xorcolor); + gcv.subwindow_mode = IncludeInferiors; + gcv.function = GXxor; + gcv.foreground = xorcolor.pixel; + gcv.line_width = 4; + gcv.plane_mask = AllPlanes; + gcv.graphics_exposures = False; + xorgc = XCreateGC(dpy, root, GCForeground | GCGraphicsExposures + | GCFunction | GCSubwindowMode | GCLineWidth + | GCPlaneMask, &gcv); + rect.x = rect.y = 0; + rect.width = DisplayWidth(dpy, screen_num); + rect.height = DisplayHeight(dpy, screen_num); + + wa.override_redirect = 1; + wa.background_pixmap = ParentRelative; + wa.event_mask = ExposureMask | ButtonPressMask | PointerMotionMask + | SubstructureRedirectMask | SubstructureNotifyMask; + transient = XCreateWindow(dpy, root, 0, 0, rect.width, rect.height, + 0, DefaultDepth(dpy, screen_num), + CopyFromParent, DefaultVisual(dpy, + screen_num), + CWOverrideRedirect | CWBackPixmap | + CWEventMask, &wa); + + XSync(dpy, False); + transient_gc = XCreateGC(dpy, transient, 0, 0); + XDefineCursor(dpy, transient, normal_cursor); + XDefineCursor(dpy, root, normal_cursor); + XSelectInput(dpy, root, ROOT_MASK); +} + +/* + * There's no way to check accesses to destroyed windows, thus + * those cases are ignored (especially on UnmapNotify's). + * Other types of errors call Xlib's default error handler, which + * calls exit(). + */ +static int +wmii_error_handler(Display * dpy, XErrorEvent * error) +{ + if (error->error_code == BadWindow + || (error->request_code == X_SetInputFocus + && error->error_code == BadMatch) + || (error->request_code == X_PolyText8 + && error->error_code == BadDrawable) + || (error->request_code == X_PolyFillRectangle + && error->error_code == BadDrawable) + || (error->request_code == X_PolySegment + && error->error_code == BadDrawable) + || (error->request_code == X_ConfigureWindow + && error->error_code == BadMatch)) + return 0; + fprintf(stderr, "%s", "wmiiwm: fatal error"); + return x_error_handler(dpy, error); /* calls exit() */ +} + +/* + * Startup Error handler to check if another window manager + * is already running. + */ +static int +startup_error_handler(Display * dpy, XErrorEvent * error) +{ + other_wm_running = 1; + return -1; +} + +static void +cleanup() +{ + int i; + XWindowChanges wc; + + for (i = 0; clients && clients[i]; i++) { + Client *c = clients[i]; + Frame *f = c->frame; + if (f) { + gravitate(c, tab_height(f), border_width(f), 1); + XReparentWindow(dpy, c->win, root, + rect_of_frame(f)->x + c->rect.x, + rect_of_frame(f)->y + c->rect.y); + wc.border_width = c->border; + XConfigureWindow(dpy, c->win, CWBorderWidth, &wc); + } + } + XSetInputFocus(dpy, PointerRoot, RevertToPointerRoot, CurrentTime); +} + +static void +run() +{ + /* init */ + init_event_hander(); + + if (!(defaults[WM_CTL] = ixp_create(ixps, "/ctl"))) { + perror("wmiiwm: cannot connect IXP server"); + exit(1); + } + defaults[WM_CTL]->after_write = handle_after_write; + + clients = 0; + frames = 0; + detached = 0; + pages = 0; + layouts = 0; + sel = 0; + sel_client = 0; + + init_atoms(); + init_cursors(); + init_defaults(); + font = blitz_getfont(dpy, defaults[WM_FONT]->content); + init_lock_modifiers(dpy, &valid_mask, &num_lock_mask); + init_screen(); + init_layouts(); + scan_wins(); + + /* main event loop */ + run_server_with_fd_support(ixps, ConnectionNumber(dpy), check_event, + 0); + cleanup(); + deinit_server(ixps); + XCloseDisplay(dpy); +} + +int +main(int argc, char *argv[]) +{ + int i; + int checkwm = 0; + char *sockfile = 0; + + /* command line args */ + if (argc > 1) { + for (i = 1; (i < argc) && (argv[i][0] == '-'); i++) { + switch (argv[i][1]) { + case 'v': + fprintf(stdout, "%s", version[0]); + exit(0); + break; + case 'c': + checkwm = 1; + break; + case 's': + if (i + 1 < argc) + sockfile = argv[++i]; + else + usage(); + break; + default: + usage(); + break; + } + } + } + dpy = XOpenDisplay(0); + if (!dpy) { + fprintf(stderr, "%s", "wmiiwm: cannot open display\n"); + exit(1); + } + screen_num = DefaultScreen(dpy); + root = RootWindow(dpy, screen_num); + + /* check if another WM is already running */ + other_wm_running = 0; + XSetErrorHandler(startup_error_handler); + /* this causes an error if some other WM is running */ + XSelectInput(dpy, root, ROOT_MASK); + XSync(dpy, False); + if (other_wm_running) { + fprintf(stderr, + "wmiiwm: another window manager is already running\n"); + exit(1); + } + if (checkwm) { + XCloseDisplay(dpy); + exit(0); + } + XSetErrorHandler(0); + x_error_handler = XSetErrorHandler(wmii_error_handler); + + ixps = wmii_setup_server(sockfile); + + run(); + + return 0; +} diff --git a/cmd/wm/wm.h b/cmd/wm/wm.h new file mode 100644 index 00000000..6307f2f0 --- /dev/null +++ b/cmd/wm/wm.h @@ -0,0 +1,291 @@ +/* + * (C)opyright MMIV-MMV Anselm R. Garbe + * See LICENSE file for license details. + */ + +#include +#include + +#include "cext.h" +#include "wmii.h" + +/* array indexes of page file pointers */ +enum { + P_PREFIX, + P_AREA_PREFIX, + P_SEL_AREA, + P_CTL, + P_MODE, + P_AUTO_DESTROY, + P_LAST +}; + +/* array indexes of area file pointers */ +enum { + A_PREFIX, + A_SEL_FRAME, + A_CTL, + A_SIZE, + A_LAYOUT, + A_LAST +}; + +/* array indexes of frame file pointers */ +enum { + F_PREFIX, + F_CLIENT_PREFIX, + F_SEL_CLIENT, + F_CTL, + F_SIZE, + F_BORDER, + F_TAB, + F_HANDLE_INC, + F_LOCKED, + F_SEL_BG_COLOR, + F_SEL_FG_COLOR, + F_SEL_BORDER_COLOR, + F_NORM_BG_COLOR, + F_NORM_FG_COLOR, + F_NORM_BORDER_COLOR, + F_EVENT_B2PRESS, + F_EVENT_B3PRESS, + F_EVENT_B4PRESS, + F_EVENT_B5PRESS, + F_LAST +}; + +/* array indexes of client file pointers */ +enum { + C_PREFIX, + C_NAME, + C_CLASS, + C_INSTANCE, + C_LAST +}; + +/* array indexes of wm file pointers */ +enum { + WM_CTL, + WM_DETACHED_FRAME, + WM_DETACHED_CLIENT, + WM_TRANS_COLOR, + WM_SEL_BG_COLOR, + WM_SEL_BORDER_COLOR, + WM_SEL_FG_COLOR, + WM_NORM_BG_COLOR, + WM_NORM_BORDER_COLOR, + WM_NORM_FG_COLOR, + WM_FONT, + WM_PAGE_SIZE, + WM_BORDER, + WM_TAB, + WM_HANDLE_INC, + WM_LOCKED, + WM_SNAP_VALUE, + WM_SEL_PAGE, + WM_LAYOUT, + WM_EVENT_PAGE_UPDATE, + WM_EVENT_CLIENT_UPDATE, + WM_EVENT_B1PRESS, + WM_EVENT_B2PRESS, + WM_EVENT_B3PRESS, + WM_EVENT_B4PRESS, + WM_EVENT_B5PRESS, + WM_LAST +}; + +#define PROTO_DEL 1 +#define BORDER_WIDTH 3 +#define LAYOUT "column" +#define GAP 5 + +#define ROOT_MASK (SubstructureRedirectMask | SubstructureNotifyMask | ButtonPressMask | ButtonReleaseMask) +#define CLIENT_MASK (SubstructureNotifyMask | PropertyChangeMask | EnterWindowMask) + +typedef struct Page Page; +typedef struct Layout Layout; +typedef struct Area Area; +typedef struct Frame Frame; +typedef struct Client Client; + +/* new layout interface: + * /page/[1..n]/0/ floating space + * /page/[1..n]/[1..n]/ layout space + */ + +struct Page { + Area **areas; + unsigned int sel; + File *files[P_LAST]; +}; + +struct Layout { + char *name; + void (*init) (Area *); /* called when layout is initialized */ + void (*deinit) (Area *); /* called when layout is uninitialized */ + void (*arrange) (Area *); /* called when area is resized */ + void (*attach) (Area *, Client *); /* called on attach */ + void (*detach) (Area *, Client *, int, int); /* called on detach */ + void (*resize) (Frame *, XRectangle *, XPoint * pt); /* called after resize */ +}; + +struct Area { + Layout *layout; + Page *page; + Frame **frames; + unsigned int sel; + XRectangle rect; + void *aux; /* free pointer */ + File *files[A_LAST]; +}; + +struct Frame { + Area *area; + Window win; + GC gc; + XRectangle rect; + Cursor cursor; + Client **clients; + int sel; + void *aux; /* free pointer */ + File *files[F_LAST]; +}; + +struct Client { + int proto; + unsigned int border; + Window win; + Window trans; + XRectangle rect; + XSizeHints size; + Frame *frame; + File *files[C_LAST]; +}; + +#define SELFRAME(x) (x && x->areas[x->sel]->frames ? x->areas[x->sel]->frames[x->areas[x->sel]->sel] : 0) +#define ISSELFRAME(x) (pages && SELFRAME(pages[sel]) == x) + +/* global variables */ +Display *dpy; +IXPServer *ixps; +int screen_num; +Window root; +Window transient; +XRectangle rect; +Client **detached; +Page **pages; +unsigned int sel; +Frame **frames; +Client **clients; +XFontStruct *font; +XColor xorcolor; +GC xorgc; +GC transient_gc; +Layout **layouts; + +Atom wm_state; +Atom wm_change_state; +Atom wm_protocols; +Atom wm_delete; +Atom motif_wm_hints; +Atom net_wm_desktop; + +Cursor normal_cursor; +Cursor resize_cursor; +Cursor move_cursor; +Cursor drag_cursor; +Cursor w_cursor; +Cursor e_cursor; +Cursor n_cursor; +Cursor s_cursor; +Cursor nw_cursor; +Cursor ne_cursor; +Cursor sw_cursor; +Cursor se_cursor; + +/* default file pointers */ +File *defaults[WM_LAST]; + +unsigned int valid_mask, num_lock_mask; + +/* area.c */ +void focus_frame(Frame * f, int raise, int up, int down); +void destroy_area(Area *a); +void free_area(Area *a); + + +/* client.c */ +Client *alloc_client(Window w); +void _init_client(Client * c, XWindowAttributes * wa); +void free_client(Client * c); +void configure_client(Client * c); +void handle_client_property(Client * c, XPropertyEvent * e); +void close_client(Client * c); +void draw_client(Client * c); +void draw_clients(Frame * f); +void gravitate(Client * c, unsigned int tabh, unsigned int bw, int invert); +int manage_class_instance(Client * c); +void grab_client(Client * c, unsigned long mod, unsigned int button); +void ungrab_client(Client * c, unsigned long mod, unsigned int button); +void hide_client(Client * c); +void show_client(Client * c); +void reparent_client(Client * c, Window w, int x, int y); + +/* core.c */ +unsigned int tab_height(Frame *f); +unsigned int border_width(Frame *f); +void invoke_core_event(File * f); +void run_action(File * f, void *obj, Action * acttbl); +void scan_wins(); +Client *win_to_client(Window w); +int win_proto(Window w); +int win_state(Window w); +void handle_after_write(IXPServer * s, File * f); +void focus_page(Page * p, int raise, int down); +void detach(Frame * f, int client_destroyed); +int comp_obj(void *f1, void *f2); +void destroy_page(Page * p); +void set_client_state(Client * c, int state); + +/* frame.c */ +Frame *win_to_frame(Window w); +Frame *alloc_frame(XRectangle * r, int add_frame_border, int floating); +void free_frame(Frame * f); +void resize_frame(Frame * f, XRectangle * r, XPoint * pt, int ignore_layout); +void draw_frame(Frame * f); +void handle_frame_buttonpress(XButtonEvent * e, Frame * f); +void attach_client(Client * c); +void attach_Cliento_frame(Frame * f, Client * c); +void detach_client_from_frame(Client * c, int unmapped, int destroyed); +void draw_tab(Frame * f, char *text, int x, int y, int w, int h, int sel); +void focus_client(Client * c, int raise, int up); +int is_managed_frame(Frame * f); +XRectangle *rect_of_frame(Frame * f); + +/* event.c */ +void init_event_hander(); +void check_event(Connection * c); + +/* mouse.c */ +void mouse_resize(Frame * f, Align align); +void mouse_move(Frame * f); +Cursor cursor_for_motion(Frame * f, int x, int y); +Align cursor_to_align(Cursor cursor); +Align xy_to_align(XRectangle * rect, int x, int y); +void drop_move(Frame * f, XRectangle * new, XPoint * pt); + +/* page.c */ +Page *alloc_page(char *autodestroy); +void free_page(Page * p); +void focus_area(Area *a, int raise, int up, int down); +XRectangle *rectangles(unsigned int *num); +void hide_page(Page * p); +void show_page(Page * p); +void attach_Frameo_page(Page * p, Frame * f, int managed); +void detach_frame_from_page(Frame * f, int ignore_focus_and_destroy); +void draw_page(Page * p); +Layout *get_layout(char *name); +Frame *select_floating(Page * p, Frame * f, char *what); + +/* layout.c */ +void init_layouts(); diff --git a/cmd/wm/wmii b/cmd/wm/wmii new file mode 100644 index 00000000..1ab07277 --- /dev/null +++ b/cmd/wm/wmii @@ -0,0 +1,43 @@ +#!9PREFIX/bin/rc +# window manager improved 2 wrapper + +if(! ~ $#* 0) { + exec wmiiwm $* +} + +if(! wmiiwm -c) { + exit 1 # wmiiwm is already running or $DISPLAY is unset +} + +WMII_CONFDIR=CONFPREFIX/wmii-3 +OLD_PATH=$PATH +PATH=$HOME/.wmii-3:$WMII_CONFDIR:9PREFIX/bin:$PATH +WMII_IDENT=`{date -n}^'-'^$pid +WMIR_SOCKET=/tmp/.ixp-$USER/wmifs-$WMII_IDENT + +# start window manager and utilities: +mkdir -p /tmp/.ixp-$USER +wmiiwm -s /tmp/.ixp-$USER/wmiiwm-$WMII_IDENT & +wmiiwmpid=$apid +wmifs -s /tmp/.ixp-$USER/wmifs-$WMII_IDENT & +wmikeys -s /tmp/.ixp-$USER/wmikeys-$WMII_IDENT & +wmibar -s /tmp/.ixp-$USER/wmibar-$WMII_IDENT & +wmimenu -s /tmp/.ixp-$USER/wmimenu-$WMII_IDENT & +sleep 1 + +# mount ixp file systems: +wmir write /ctl 'bind /wm /tmp/.ixp-'^$USER/wmiiwm-$WMII_IDENT +wmir write /ctl 'bind /bar /tmp/.ixp-'^$USER/wmibar-$WMII_IDENT +wmir write /ctl 'bind /menu /tmp/.ixp-'^$USER/wmimenu-$WMII_IDENT +wmir write /ctl 'bind /keys /tmp/.ixp-'^$USER/wmikeys-$WMII_IDENT + +# display the wmii introduction if necessary: +if(mkdir $HOME/.wmii-3 >[2]/dev/null) { + welcome & +} + +# run configuration: +wmirc & + +# wait for wmiiwm's termination: +wait $wmiiwmpid diff --git a/cmd/wm/wmii.1 b/cmd/wm/wmii.1 new file mode 100644 index 00000000..f509220b --- /dev/null +++ b/cmd/wm/wmii.1 @@ -0,0 +1,95 @@ +.TH WMII 1 wmii-3 +.SH NAME +wmii \- window manager improved 2 +.SH DESCRIPTION +.SS Overview +.BR wmii (1) +is a script that launches the +.B wmii +window manager and its various utilities and takes care that they are +configured for use. +.SS Actions +An action is a script written for the rc shell, but it can actually be +any executable file. It is executed usually by selecting it from the +actions menu. +You can customize an action by copying it from the global action +directory CONFPREFIX/wmii-3 to $HOME/.wmii-3 and then editing the copy to +fit your needs. Of course you can also create your own actions there; make +sure that they are executable. +.P +Here is a list of the default actions: +.TP 2 +extern +clean the environment and execute the given command +.TP 2 +kmode +activate shortcuts of the given mode +.TP 2 +quit +quit wmii +.TP 2 +status +periodically print date and load average to the bar +.TP 2 +welcome +display a welcome message that contains the wmii tutorial +.TP 2 +wmirc +configure wmii +.SS Configuration +If you feel the need to change the default configuration, then customize (as +described above) the +.B wmirc +action. This action is executed at the end of the +.BR wmii (1) +script and does all the work of setting up the window manager, the key +bindings, the bar labels, etc. +.SH FILES +.TP +/tmp/.ixp-$USER +Directory where the socket files are stored. +.TP +CONFPREFIX/wmii-3 +Global action directory. +.TP +$HOME/.wmii-3 +User-specific action directory. Actions are first searched here. +.SH ENVIRONMENT +.TP +HOME, USER +See the section +.B FILES +above. +.P +The following variables are set and exported within +.BR wmii (1) +and thus can be used in actions. +.TP +OLD_PATH +PATH as it was before +.BR wmii (1) +added the local and global action directory and the location where the 9base +tools reside. The +.B wmire +action resets PATH and removes OLD_PATH before executing a command. +.TP +WMII_CONFDIR +Global action directory. +.TP +WMII_IDENT +Unique identifier for the +.BR wmii (1) +session. Part of the socket file names. +.TP +WMIR_SOCKET +Socket file of +.BR wmifs (1). +Used by +.BR wmir (1). +.SH SEE ALSO +.BR wmibar (1), +.BR wmifs (1), +.BR wmiiwm (1), +.BR wmikeys (1), +.BR wmimenu (1), +.BR wmir (1) diff --git a/cmd/wm/wmiiwm.1 b/cmd/wm/wmiiwm.1 new file mode 100644 index 00000000..600e920f --- /dev/null +++ b/cmd/wm/wmiiwm.1 @@ -0,0 +1,171 @@ +.de FN +\fI\|\\$1\|\fP\\$2 +.. +.TH wmii 1 +.SH NAME +wmii \- window manager improved 2 for X11 + +.SH DESCRIPTION +.B wmii +is a dynamic window manager for the X Window System. +It provides a synthesis of conventional, tiled and tabbed window +management based on dynamic layouts. +Several roots of these window management capabilities have been +introduced by the Ion and LarsWM window managers. +Apart from this, it implements a socket-based fileserver, +which is accessed to configure and interoperate with wmii. The idea +behind this file-based approach is derived from the plan9 +operating system and can be found in the Acme programming environment. +.B wmii +consists of the core window manager itself and several utilities, such +as +.BR wmibar (1), +.BR wmifs (1), +.BR wmimenu (1), +.BR wmikeys (1), +.BR wmiplumb (1), +.BR wmir (1) +and +.BR wmiwarp (1). + +.SH SYNOPSIS +.B wmii +.RB [ \-s +.IR socketfile ] +.br +.B wmii +.RB \-v + +.SH OPTIONS +.TP +.BI \-s " socketfile" +lets you override the default socketfile which +.B wmii +should use for connecting fileserver clients. +.TP +.B \-v +prints version information to stderr, then exits. + +.SH FILES +.TP +.FN /tmp/.ixp-$USER/wmii\-$WMII_IDENT +this file is the default socket file used by +.B wmii. +.TP +.FN $HOME/.wmii/rc [start|stop] +this file is executed when +.B wmii +starts up or shuts down. If it is not present, +.B wmii +executes +.FN $WMII_CONFDIR/rc [start|stop] +as fallback. +The rc script is used to customize and setup +.B wmii +beside its components to match the users needs. +It can be a simple shell script, a native binary or whatever executable. +The only condition is, that it understands the command line arguments +.IR start +, which is provided while startup and +.IR stop +, which is provided on shutdown. +For details about the shipped default rc configuration system, see +.BR wmii.rc (5). + +.SH STRUCTURE +The structure of +.B wmii +consists of following objects which are described in more detail. +.TP +.B Display +The display is a running X server which consists of at least one +.B Screen (Monitor), +.B Keyboard, +and the +.B Mouse. +Applications with X11 support can connect to such X server display in +order to be used. +.TP +.B Screen (Monitor) +A screen is the physical device which displays a part or the whole +display driven by the X server. Each screen consists of a root window +which matches the physical conditions the X server is configured to +drive the graphics adaptor and the connected screen, such as color +depth and resolution. +.TP +.B (Root) Window +A window is a rectangular area which is drawn by the X server. A root +window is the complete drawable area of a screen. Root windows are +top-level windows which are always at the most-background position. If +you have set a wallpaper, the root window displays the wallpaper. +All other windows are children of the root window. Thus, windows of X +clients, frames surrounding them and bars are all windows. +.TP +.B Client +A client is the window provided by X applications (clients) without the +surrounding frame. There're X clients supported, such as xmms, which +request not to be surrounded by a frame, those are called borderless. +.TP +.B Frame +A frame is a parent window of a client or of nested frames in a layout. +Mostly a frame consists of a border and a titlebar. The titlebar +provides tabs, if the frame contains nested frames, otherwise it shows the title +of the surrounded client. +.TP +.B Layout +A layout defines how to arrange nested frames of a frame. See +CUSTOMIZATION section for further details about how such definitions look +like. +.TP +.B Page +A page is a container of the size of the root window which contains +nested frames. It can be compared to workspaces in other window +managers, but with the exception, that a page behaves very similiar to a +frame, except that it reuses the root window as frame window. +.TP +.B Action +An action is an internal interface command or an external process call +in order to reach a specific window management result, ie resizing a +frame or launching a terminal. + +.SH CUSTOMIZATION +.B wmii +is customized through the rc script, which manipulates the namespace its +fileserver provides to other processes. This namespace can be accessed +using the +.BR wmir (1) +utility. The default rc script uses this utility to setup wmii and its +components. +.P +A namespace is a filesystem structure consisting of files +and directories, which is specified by its underlying fileserver +process, like +.B wmii. +Such +namespaces can be bound (mounted) to userdefined namespaces using the +.BR wmifs (1) +utility. In the default rc script the namespace provided by wmii, is +bound to +.FN /wmii +.P +There are four actions provided by the +.BR wmir (1) +utility to manipulate your namespace: +.BR create , +.BR remove , +.BR write , +and +.BR read . +.P + +.SH AUTHOR +Copyright \(co 2003 - 2005 by Anselm R. Garbe +.SH SEE ALSO +.BR wmibar (1), +.BR wmifs (1), +.BR wmimenu (1), +.BR wmikeys (1), +.BR wmiplumb (1), +.BR wmir (1), +.BR wmiwarp (1), +.BR wmii.rc (5). diff --git a/cmd/wm/wmiiwm.h.O b/cmd/wm/wmiiwm.h.O new file mode 100644 index 00000000..0de6aa9a --- /dev/null +++ b/cmd/wm/wmiiwm.h.O @@ -0,0 +1,332 @@ +/* + * (C)opyright MMIV-MMV Anselm R. Garbe + * See LICENSE file for license details. + */ + +#include +#include + +#include "cext.h" +#include "wmii.h" + +/* array indexes of page file pointers */ +typedef enum { + P_PREFIX, + P_FLOATING_PREFIX, + P_FLOATING_SELECTED, + P_FLOATING_LAYOUT, + P_MANAGED_PREFIX, + P_MANAGED_SELECTED, + P_MANAGED_LAYOUT, + P_MANAGED_SIZE, + P_CTL, + P_NAME, + P_MODE, + P_AUTO_DESTROY, + P_LAST +} PageIndexes; + +/* array indexes of frame file pointers */ +typedef enum { + F_PREFIX, + F_NAME, + F_CLIENT_PREFIX, + F_CLIENT_SELECTED, + F_CTL, + F_SIZE, + F_BORDER_W, + F_TAB_H, + F_HANDLE_INC, + F_LOCKED, + F_SELECTED_BG_COLOR, + F_SELECTED_TEXT_FONT, + F_SELECTED_FG_COLOR, + F_SELECTED_BORDER_COLOR, + F_NORMAL_BG_COLOR, + F_NORMAL_TEXT_FONT, + F_NORMAL_FG_COLOR, + F_NORMAL_BORDER_COLOR, + F_EVENT_B2PRESS, + F_EVENT_B3PRESS, + F_EVENT_B4PRESS, + F_EVENT_B5PRESS, + F_LAST +} FrameIndexes; + +/* array indexes of client file pointers */ +typedef enum { + C_PREFIX, + C_ID, + C_NAME, + C_CLASS, + C_INSTANCE, + C_LAST +} ClientIndexes; + +/* array indexes of core file pointers */ +typedef enum { + CORE_CTL, + CORE_DETACHED_FRAME, + CORE_DETACHED_CLIENT, + CORE_TRANS_COLOR, + CORE_PAGER_SEL_BG_COLOR, + CORE_PAGER_NORM_BG_COLOR, + CORE_PAGER_SEL_BORDER_COLOR, + CORE_PAGER_NORM_BORDER_COLOR, + CORE_PAGER_SEL_TEXT_COLOR, + CORE_PAGER_SEL_TEXT_FONT, + CORE_PAGER_NORM_TEXT_COLOR, + CORE_PAGER_NORM_TEXT_FONT, + CORE_PAGER_SEL_CLIENT_COLOR, + CORE_PAGER_NORM_CLIENT_COLOR, + CORE_PAGER_SEL_CTEXT_COLOR, + CORE_PAGER_SEL_CTEXT_FONT, + CORE_PAGER_NORM_CTEXT_COLOR, + CORE_PAGER_NORM_CTEXT_FONT, + CORE_PAGER_SEL_CBORDER_COLOR, + CORE_PAGER_NORM_CBORDER_COLOR, + CORE_PAGE_MANAGED_SIZE, + CORE_SNAP_VALUE, + CORE_PAGE_SELECTED, + CORE_PAGE_LAYOUT, + CORE_PAGE_TILE_WIDTH, + CORE_EVENT_PAGE_UPDATE, + CORE_EVENT_CLIENT_UPDATE, + CORE_LAST +} CoreIndexes; + +#define PROTO_DEL 1 +#define DEFAULT_BORDER_W "3" +#define DEFAULT_TAB_H "16" +#define DEFAULT_LAYOUT "tiled" +#define GAP 5 + +#define ROOT_MASK (SubstructureRedirectMask | SubstructureNotifyMask | ButtonPressMask | ButtonReleaseMask) +#define CLIENT_MASK (SubstructureNotifyMask | PropertyChangeMask | EnterWindowMask) + +typedef struct Layout Layout; +typedef struct LayoutImpl LayoutImpl; +typedef struct Frame Frame; +typedef struct Client Client; +typedef struct Page Page; + +/* new layout interface: + * /page/[1..n]/0/ floating space + * /page/[1..n]/[1..n]/ layout space + * + * CORE: + * Page **pages; + * Client **clients; -- for event lookup + * Frames **frames; -- for event lookup + * + * PAGE: + * int sel_layout; + * Layout **layout; -- layout[0] == floating + * + * LAYOUT: + * Frame **stack; + * Frame **frames; + * + * FRAME: + * Client **stack; + * Client **clients; + * + * CLIENT: + * Frame *frame; + * + */ + +struct LayoutImpl { + char *name; + void (*init) (Page *, int argc, char **argv); /* called on new layout */ + void (*deinit) (Page *); /* called when layout gets + * released */ + void (*arrange) (Page *); /* called when area is + * resized */ + void (*attach) (Page *, Client *); /* called on attach */ + void (*detach) (Page *, Client *, int, int); /* called on detach */ + void (*resize) (Frame *, XRectangle *, XPoint * pt); /* called after resize */ + void (*aux) (Frame *, char *); /* auxillary extension + * for layout specific + * stuff */ +}; + +struct Layout { + char *name; + void (*init) (Page *); /* called on new layout */ + void (*deinit) (Page *); /* called when layout gets + * released */ + void (*arrange) (Page *); /* called when area is + * resized */ + void (*manage) (Frame *); /* called on attach */ + void (*unmanage) (Frame *); /* called on detach */ + void (*resize) (Frame *, XRectangle *, XPoint * pt); + /* called after resize/move */ + Frame *(*select) (Frame *, char *); /* called to select a + * frame */ +}; + +struct Page { + Client **clients; + Frame **floating; + Frame **managed; + Frame **floating_stack; + Frame **managed_stack; + XRectangle managed_rect; + File *files[P_LAST]; + Layout *layout; + void *aux; /* free pointer for layout backends */ +}; + +struct Frame { + int id; + int floating; + Window win; + GC gc; + XRectangle managed_rect; + XRectangle floating_rect; + XRectangle rect; + Cursor cursor; + Client **clients; + int sel; + File *files[F_LAST]; + Page *page; + Page *before_max; + void *aux; /* free pointer for layout backends */ +}; + +struct Client { + int id; + int proto; + unsigned int border; + Window win; + Window trans; + XRectangle rect; + XSizeHints size; + Frame *frame; + File *files[C_LAST]; +}; + +/* global variables */ +Display *dpy; +IXPServer *ixps; +int screen_num; +Window root; +Window transient; +XRectangle rect; +Client **detached; +Page **pages; +int sel_page; +Frame **frames; +Client **clients; +XColor xorcolor; +GC xorgc; +GC transient_gc; +Client *sel; +Layout **layouts; + +Atom wm_state; +Atom wm_change_state; +Atom wm_protocols; +Atom wm_delete; +Atom motif_wm_hints; +Atom net_wm_desktop; + +Cursor normal_cursor; +Cursor resize_cursor; +Cursor move_cursor; +Cursor drag_cursor; +Cursor w_cursor; +Cursor e_cursor; +Cursor n_cursor; +Cursor s_cursor; +Cursor nw_cursor; +Cursor ne_cursor; +Cursor sw_cursor; +Cursor se_cursor; + +/* default file pointers */ +File *defaults[F_LAST]; +File *core_files[CORE_LAST]; + +unsigned int valid_mask, num_lock_mask; + +/* client.c */ +Client *alloc_client(Window w); +void _init_client(Client * c, XWindowAttributes * wa); +void free_client(Client * c); +void configure_client(Client * c); +void handle_client_property(Client * c, XPropertyEvent * e); +void close_client(Client * c); +void draw_client(Client * c); +void draw_clients(Frame * f); +void gravitate(Client * c, unsigned int tabh, unsigned int bw, int invert); +int manage_class_instance(Client * c); +void grab_client(Client * c, unsigned long mod, unsigned int button); +void ungrab_client(Client * c, unsigned long mod, unsigned int button); +void hide_client(Client * c); +void show_client(Client * c); +void reparent_client(Client * c, Window w, int x, int y); + +/* core.c */ +void invoke_core_event(File * f); +void run_action(File * f, void *obj, Action * acttbl); +void scan_wins(); +Client *win_to_client(Window w); +Frame *win_to_frame(Window w); +int win_proto(Window w); +int win_state(Window w); +void handle_after_write(IXPServer * s, File * f); +void focus_page(Page * p, int raise, int down); +void detach(Frame * f, int client_destroyed); +int comp_obj(void *f1, void *f2); +void destroy_page(Page * p); +void set_client_state(Client * c, int state); + +/* frame.c */ +Frame *alloc_frame(XRectangle * r, int add_frame_border, int floating); +void free_frame(Frame * f); +void +resize_frame(Frame * f, XRectangle * r, XPoint * pt, + int ignore_layout); +void draw_frame(Frame * f); +void handle_frame_buttonpress(XButtonEvent * e, Frame * f); +void attach_client(Client * c); +void attach_Cliento_frame(Frame * f, Client * c); +void detach_client_from_frame(Client * c, int unmapped, int destroyed); +void draw_tab(Frame * f, char *text, int x, int y, int w, int h, int sel); +void focus_client(Client * c, int raise, int up); +int is_managed_frame(Frame * f); +XRectangle *rect_of_frame(Frame * f); + +/* event.c */ +void init_event_hander(); +void check_event(Connection * c); + +/* mouse.c */ +void mouse_resize(Frame * f, Align align); +void mouse_move(Frame * f); +Cursor cursor_for_motion(Frame * f, int x, int y); +Align cursor_to_align(Cursor cursor); +Align xy_to_align(XRectangle * rect, int x, int y); +void drop_move(Frame * f, XRectangle * new, XPoint * pt); + +/* page.c */ +Page *alloc_page(char *autodestroy); +void free_page(Page * p); +Frame *get_selected(Page * p); +int is_selected(Frame * f); +XRectangle *rectangles(unsigned int *num); +void hide_page(Page * p); +void show_page(Page * p); +void attach_Frameo_page(Page * p, Frame * f, int managed); +void detach_frame_from_page(Frame * f, int ignore_focus_and_destroy); +void draw_page(Page * p); +void focus_frame(Frame * f, int raise, int up, int down); +Layout *get_layout(char *name); +int is_managed_mode(Page * p); +void toggle_frame(Frame * f); +Frame *select_floating(Page * p, Frame * f, char *what); + +/* layout.c */ +void init_layouts(); diff --git a/cmd/wmibar.1 b/cmd/wmibar.1 new file mode 100644 index 00000000..be8ebabe --- /dev/null +++ b/cmd/wmibar.1 @@ -0,0 +1,43 @@ +.TH WMIBAR 1 wmii-3 +.SH NAME +wmibar \- window manager improved 2 bar +.SH SYNOPSIS +.B wmibar +.B \-s +.I socketfile +.SH DESCRIPTION +.SS Overview +.B wmibar +is a generic and highly customizable bar for the X Window System, +originally designed for +.BR wmii (1). +It supports arbitrary sized labels with arbitrary styles on a per label +basis and with button click events. +Like wmii, +.B wmibar +also implements a socket-based fileserver, which is accessed to configure and +interoperate with other components. +.SS Options +.TP +.BI \-s " socketfile" +specifies the socketfile that +.B wmibar +should create. +.TP +.B \-v +prints version information to stderr, then exits. +.SS Customization +.B wmibar +is customized through manipulating its filesystem namespace. +In the default setup of +.BR wmii (1) +the namespace of +.B wmibar +can be found in /bar. +.SH SEE ALSO +.BR wmifs (1), +.BR wmii (1), +.BR wmiiwm (1), +.BR wmikeys (1), +.BR wmimenu (1), +.BR wmir (1) diff --git a/cmd/wmibar.c b/cmd/wmibar.c new file mode 100644 index 00000000..3df0e54a --- /dev/null +++ b/cmd/wmibar.c @@ -0,0 +1,550 @@ +/* + * (C)opyright MMIV-MMV Anselm R. Garbe + * See LICENSE file for license details. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "wmii.h" + +#include + +/* array indexes for file pointers */ +typedef enum { + B_CTL, + B_NEW, + B_EXPANDABLE, + B_GEOMETRY, + B_FONT, + B_FG_COLOR, + B_BORDER_COLOR, + B_BG_COLOR, + B_LAST +} BarIndexes; + +typedef struct { + File *root; + Draw d; +} Item; + +static IXPServer *ixps = 0; +static Display *dpy; +static GC gc; +static Window win; +static XRectangle rect; +static XRectangle brect; +static int screen_num; +static int displayed = 0; +static char *sockfile = 0; +static File *files[B_LAST]; +static Item **items = 0; +static unsigned int id = 0; +static Pixmap pmap; + +static Draw zero_draw = {0}; + +static void draw_bar(void *obj, char *arg); +static void quit(void *obj, char *arg); +static void display(void *obj, char *arg); +static void reset(void *obj, char *arg); +static void _destroy(void *obj, char *arg); +static void handle_after_write(IXPServer * s, File * f); + +static Action acttbl[] = { + {"quit", quit}, + {"display", display}, + {"update", draw_bar}, + {"reset", reset}, + {"destroy", _destroy}, + {0, 0} +}; + +static char *version[] = { + "wmibar - window manager improved bar - " VERSION "\n" + " (C)opyright MMIV-MMV Anselm R. Garbe\n", 0 +}; + +static void +usage() +{ + fprintf(stderr, "%s", + "usage: wmibar -s [-v] [,,,]\n" + " -s socket file\n" " -v version info\n"); + exit(1); +} + +/** + * /data "" + * /fgcolor "#RRGGBBAA" + * /bgcolor "#RRGGBBAA" + * /bordercolor "#RRGGBBAA" + * /b1press "" + * /b2press "" + * /b3press "" + * /b4press "" + * /b5press "" + */ +static void +create_label(char *path) +{ + File *f; + char file[MAX_BUF]; + int i; + + snprintf(file, MAX_BUF, "%s/data", path); + f = ixp_create(ixps, file); + f->after_write = handle_after_write; + snprintf(file, MAX_BUF, "%s/fgcolor", path); + wmii_create_ixpfile(ixps, file, files[B_FG_COLOR]->content); + snprintf(file, MAX_BUF, "%s/bgcolor", path); + wmii_create_ixpfile(ixps, file, files[B_BG_COLOR]->content); + snprintf(file, MAX_BUF, "%s/bordercolor", path); + wmii_create_ixpfile(ixps, file, files[B_BORDER_COLOR]->content); + for (i = 1; i < 6; i++) { /* 5 buttons events */ + snprintf(file, MAX_BUF, "%s/b%dpress", path, i); + ixp_create(ixps, file); + } +} + +static void +_destroy(void *obj, char *arg) +{ + char buf[512]; + if (!arg) + return; + snprintf(buf, sizeof(buf), "/%s", arg); + ixps->remove(ixps, buf); + draw_bar(0, 0); +} + +static void +reset(void *obj, char *arg) +{ + int i; + char buf[512]; + for (i = 0; i < id; i++) { + snprintf(buf, sizeof(buf), "/%d", i + 1); + ixps->remove(ixps, buf); + } + id = 0; + draw_bar(0, 0); +} + +static void +quit(void *obj, char *arg) +{ + ixps->runlevel = SHUTDOWN; +} + +static void +display(void *obj, char *arg) +{ + if (!arg) + return; + displayed = _strtonum(arg, 0, 1); + if (displayed) { + XMapRaised(dpy, win); + draw_bar(0, 0); + } else { + XUnmapWindow(dpy, win); + XSync(dpy, False); + } +} + +static void +init_draw_label(char *path, Draw * d) +{ + char buf[MAX_BUF]; + File *f; + + /* text stuff */ + snprintf(buf, MAX_BUF, "%s/data", path); + f = ixp_walk(ixps, buf); + d->data = f->content; + /* style stuff */ + snprintf(buf, MAX_BUF, "%s/fgcolor", path); + f = ixp_walk(ixps, buf); + d->fg = blitz_loadcolor(dpy, screen_num, f->content); + snprintf(buf, MAX_BUF, "%s/bgcolor", path); + f = ixp_walk(ixps, buf); + d->bg = blitz_loadcolor(dpy, screen_num, f->content); + snprintf(buf, MAX_BUF, "%s/bordercolor", path); + f = ixp_walk(ixps, buf); + d->border = blitz_loadcolor(dpy, screen_num, f->content); +} + +static void +init_item(char *path, Item * i) +{ + i->d = zero_draw; + i->root = ixp_walk(ixps, path); + i->d.gc = gc; + i->d.drawable = pmap; + i->d.rect = brect; + i->d.rect.y = 0; + init_draw_label(path, &i->d); +} + +static int +comp_str(const void *s1, const void *s2) +{ + return strcmp(*(char **) s1, *(char **) s2); +} + +static void +draw() +{ + unsigned int n = 0, i, w, xoff = 0; + XFontStruct *font; + unsigned expandable = 0; + char buf[32]; + + if (!items) + return; + + n = count_items((void **) items); + font = blitz_getfont(dpy, files[B_FONT]->content); + expandable = _strtonum(files[B_EXPANDABLE]->content, 1, id); + snprintf(buf, sizeof(buf), "/%d", expandable); + if (!ixp_walk(ixps, buf)) + expandable = 0; + + w = 0; + /* precalc */ + for (i = 0; expandable && items[i]; i++) + if (i + 1 != expandable) { + items[i]->d.rect.width = brect.height; + if (items[i]->d.data) { + if (!strncmp(items[i]->d.data, "%m:", 3)) + /* meter */ + items[i]->d.rect.width = brect.height / 2; + else + items[i]->d.rect.width += + XTextWidth(font, items[i]->d.data, strlen(items[i]->d.data)); + } + w += items[i]->d.rect.width; + } + if (!expandable || w > brect.width) { + /* failsafe mode, give all labels same width */ + w = brect.width / n; + for (i = 0; items[i]; i++) + items[i]->d.rect.width = w; + items[i - 1]->d.rect.width = brect.width - items[i - 1]->d.rect.x; + } else + items[expandable - 1]->d.rect.width = brect.width - w; + + for (i = 0; items[i]; i++) { + items[i]->d.font = font; + items[i]->d.rect.x = xoff; + xoff += items[i]->d.rect.width; + if (items[i]->d.data && !strncmp(items[i]->d.data, "%m:", 3)) + blitz_drawmeter(dpy, &items[i]->d); + else + blitz_drawlabel(dpy, &items[i]->d); + } + XCopyArea(dpy, pmap, win, gc, 0, 0, brect.width, brect.height, 0, 0); + XSync(dpy, False); + XFreeFont(dpy, font); +} + +static void +draw_bar(void *obj, char *arg) +{ + File *label = 0; + unsigned int i = 0, n = 0; + Item *item; + char buf[512]; + + if (!displayed) + return; + if (items) { + for (i = 0; items[i]; i++) { + free(items[i]); + } + free(items); + } + items = 0; + snprintf(buf, sizeof(buf), "%s", "/1"); + label = ixp_walk(ixps, buf); + if (!label) { + Draw d = {0}; + /* default stuff */ + d.gc = gc; + d.drawable = pmap; + d.rect.width = brect.width; + d.rect.height = brect.height; + d.bg = blitz_loadcolor(dpy, screen_num, files[B_BG_COLOR]->content); + d.fg = blitz_loadcolor(dpy, screen_num, files[B_FG_COLOR]->content); + d.border = blitz_loadcolor(dpy, screen_num, files[B_BORDER_COLOR]->content); + blitz_drawlabelnoborder(dpy, &d); + } else { + File *f; + char **paths = 0; + /* + * take order into account, directory names are used in + * alphabetical order + */ + n = 0; + for (f = label; f; f = f->next) + n++; + paths = emalloc(sizeof(char *) * n); + i = 0; + for (f = label; f; f = f->next) + paths[i++] = f->name; + qsort(paths, n, sizeof(char *), comp_str); + for (i = 0; i < n; i++) { + snprintf(buf, sizeof(buf), "/%s", paths[i]); + item = emalloc(sizeof(Item)); + items = (Item **) attach_item_end((void **) items, item, sizeof(Item *)); + init_item(buf, item); + } + draw(); + free(paths); + } +} + +static Item * +get_item_for_file(File * f) +{ + int i; + for (i = 0; items && items[i]; i++) + if (items[i]->root == f) + return items[i]; + return 0; +} + +static void +handle_buttonpress(XButtonPressedEvent * e) +{ + File *p; + char buf[MAX_BUF]; + char path[512]; + int i; + + for (i = 0; items && items[i]; i++) { + if (blitz_ispointinrect(e->x, e->y, &items[i]->d.rect)) { + path[0] = '\0'; + wmii_get_ixppath(items[i]->root, path, sizeof(path)); + snprintf(buf, MAX_BUF, "%s/b%upress", path, e->button); + if ((p = ixp_walk(ixps, buf))) + if (p->content) + spawn(dpy, p->content); + return; + } + } +} + +static void +check_event(Connection * e) +{ + XEvent ev; + + while (XPending(dpy)) { + XNextEvent(dpy, &ev); + switch (ev.type) { + case ButtonPress: + handle_buttonpress(&ev.xbutton); + break; + case Expose: + if (ev.xexpose.count == 0) { + /* XRaiseWindow(dpy, win); */ + draw_bar(0, 0); + } + break; + default: + break; + } + } +} + +static void +update_geometry(char *size) +{ + blitz_strtorect(dpy, &rect, &brect, size); + if (!brect.width) + brect.width = DisplayWidth(dpy, screen_num); + if (!brect.height) + brect.height = 20; +} + +static void +handle_after_write(IXPServer * s, File * f) +{ + int i; + size_t len; + Item *item; + char buf[512]; + + buf[0] = '\0'; + if (!strncmp(f->name, "data", 5)) { + if ((item = get_item_for_file(f->parent))) { + wmii_get_ixppath(f->parent, buf, sizeof(buf)); + init_draw_label(buf, &item->d); + draw(); + } + } else if (files[B_GEOMETRY] == f) { + char *geom = files[B_GEOMETRY]->content; + if (geom && strrchr(geom, ',')) { + update_geometry(geom); + XMoveResizeWindow(dpy, win, brect.x, brect.y, + brect.width, brect.height); + XSync(dpy, False); + pmap = XCreatePixmap(dpy, win, brect.width, brect.height, + DefaultDepth(dpy, screen_num)); + XSync(dpy, False); + draw_bar(0, 0); + } + } else if (files[B_CTL] == f) { + for (i = 0; acttbl[i].name; i++) { + len = strlen(acttbl[i].name); + if (!strncmp(acttbl[i].name, (char *) f->content, len)) { + if (strlen(f->content) > len) { + acttbl[i].func(0, &((char *) f->content)[len + 1]); + } else { + acttbl[i].func(0, 0); + } + break; + } + } + } + check_event(0); +} + +static void +handle_before_read(IXPServer * s, File * f) +{ + char buf[64]; + if (f == files[B_GEOMETRY]) { + snprintf(buf, sizeof(buf), "%d,%d,%d,%d", brect.x, brect.y, + brect.width, brect.height); + if (f->content) + free(f->content); + f->content = strdup(buf); + f->size = strlen(buf); + } else if (f == files[B_NEW]) { + snprintf(buf, sizeof(buf), "%d", ++id); + if (f->content) + free(f->content); + f->content = strdup(buf); + f->size = strlen(buf); + create_label(buf); + draw_bar(0, 0); + } +} + +static void +run(char *geom) +{ + XSetWindowAttributes wa; + XGCValues gcv; + + /* init */ + if (!(files[B_CTL] = ixp_create(ixps, "/ctl"))) { + perror("wmibar: cannot connect IXP server"); + exit(1); + } + files[B_CTL]->after_write = handle_after_write; + files[B_NEW] = ixp_create(ixps, "/new"); + files[B_NEW]->before_read = handle_before_read; + files[B_FONT] = wmii_create_ixpfile(ixps, "/font", BLITZ_FONT); + files[B_BG_COLOR] = wmii_create_ixpfile(ixps, "/bgcolor", BLITZ_NORM_BG_COLOR); + files[B_FG_COLOR] = wmii_create_ixpfile(ixps, "/fgcolor", BLITZ_NORM_FG_COLOR); + files[B_BORDER_COLOR] = wmii_create_ixpfile(ixps, "/bordercolor", BLITZ_NORM_BORDER_COLOR); + files[B_GEOMETRY] = ixp_create(ixps, "/geometry"); + files[B_GEOMETRY]->before_read = handle_before_read; + files[B_GEOMETRY]->after_write = handle_after_write; + files[B_EXPANDABLE] = ixp_create(ixps, "/expandable"); + + wa.override_redirect = 1; + wa.background_pixmap = ParentRelative; + wa.event_mask = ExposureMask | ButtonPressMask | SubstructureRedirectMask | SubstructureNotifyMask; + + brect.x = brect.y = brect.width = brect.height = 0; + rect.x = rect.y = 0; + rect.width = DisplayWidth(dpy, screen_num); + rect.height = DisplayHeight(dpy, screen_num); + update_geometry(geom); + + win = XCreateWindow(dpy, RootWindow(dpy, screen_num), brect.x, brect.y, + brect.width, brect.height, 0, DefaultDepth(dpy, + screen_num), + CopyFromParent, DefaultVisual(dpy, screen_num), + CWOverrideRedirect | CWBackPixmap | CWEventMask, + &wa); + XDefineCursor(dpy, win, XCreateFontCursor(dpy, XC_left_ptr)); + XSync(dpy, False); + + gcv.function = GXcopy; + gcv.graphics_exposures = False; + gc = XCreateGC(dpy, win, 0, 0); + + pmap = + XCreatePixmap(dpy, win, brect.width, brect.height, + DefaultDepth(dpy, screen_num)); + + /* main event loop */ + run_server_with_fd_support(ixps, ConnectionNumber(dpy), + check_event, 0); + deinit_server(ixps); + XFreePixmap(dpy, pmap); + XFreeGC(dpy, gc); + XCloseDisplay(dpy); +} + +static int +dummy_error_handler(Display * dpy, XErrorEvent * err) +{ + return 0; +} + +int +main(int argc, char *argv[]) +{ + char geom[64]; + int i; + + /* command line args */ + for (i = 1; (i < argc) && (argv[i][0] == '-'); i++) { + switch (argv[i][1]) { + case 'v': + fprintf(stdout, "%s", version[0]); + exit(0); + break; + case 's': + if (i + 1 < argc) + sockfile = argv[++i]; + else + usage(); + break; + default: + usage(); + break; + } + } + + dpy = XOpenDisplay(0); + if (!dpy) { + fprintf(stderr, "%s", "wmibar: cannot open display\n"); + exit(1); + } + XSetErrorHandler(dummy_error_handler); + screen_num = DefaultScreen(dpy); + + geom[0] = '\0'; + if (argc > i) + _strlcpy(geom, argv[i], sizeof(geom)); + + ixps = wmii_setup_server(sockfile); + run(geom); + + return 0; +} diff --git a/cmd/wmibar2.c b/cmd/wmibar2.c new file mode 100644 index 00000000..8030053e --- /dev/null +++ b/cmd/wmibar2.c @@ -0,0 +1,472 @@ +/* + * (C)opyright MMIV-MMV Anselm R. Garbe + * See LICENSE file for license details. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "../libixp2/ixp.h" +#include "blitz.h" +#include "cext.h" + +/* + * filesystem specification + * / Droot + * /display Fdisplay 'top', 'bottom', 'none' + * /font Ffont + * /new Fnew returns id of new item + * /default/ Ditem + * /default/bNpress Fevent + * /default/bgcolor Fcolor <#RRGGBB, #RGB> + * /default/fgcolor Fcolor <#RRGGBB, #RGB> + * /default/bordercolor Fcolor <#RRGGBB, #RGB> + * /1/ Ditem + * /1/data Fdata + * /1/bNpress Fevent + * /1/bgcolor Fcolor <#RRGGBB, #RGB> + * /1/fgcolor Fcolor <#RRGGBB, #RGB> + * /1/bordercolor Fcolor <#RRGGBB, #RGB> ... + */ +enum { /* 8-bit qid.path.type */ + Droot, + Ditem, + Fdisplay, + Fnew, + Fdata, /* data to display */ + Fevent, + Fcolor, + Ffont +}; + +#define NONE (u16)0xffff + +typedef struct { + char *name; + u8 type; +} QFile; + +static QFile qfilelist[] = { + {"display", Fdisplay}, + {"font", Ffont}, + {"new", Fnew}, + {"data", Fdata}, + {"bgcolor", Fcolor}, + {"fgcolor", Fcolor}, + {"bordercolor", Fcolor}, + {"b1press", Fevent}, + {"b2press", Fevent}, + {"b3press", Fevent}, + {"b4press", Fevent}, + {"b5press", Fevent}, + {0, 0}, +}; + +typedef struct { + u32 fid; + Qid qid; +} Map; + +typedef struct { + int id; + char text[256]; + int value; + unsigned long bg; + unsigned long fg; + unsigned long border[4]; + XFontStruct *font; + char event[5][256]; +} Item; + +static Item **items = 0; +static char *sockfile = 0; +static pid_t mypid = 0; +static IXPServer srv = {0}; +static Qid root_qid; +static Display *dpy; +static int screen_num; +static char *align = 0; +static char *font = 0; +/* +static GC gc; +static Window win; +static XRectangle geom; +static int mapped = 0; +static Pixmap pmap; + +static Draw zero_draw = { 0 }; +*/ + +static char *version[] = { + "wmibar - window manager improved bar - " VERSION "\n" + " (C)opyright MMIV-MMV Anselm R. Garbe\n", 0 +}; + +static void +usage() +{ + fprintf(stderr, "%s %d", + "usage: wmibar -s [-v]\n" + " -s socket file\n" " -v version info\n", + NONE); + exit(1); +} + +static int +dummy_error_handler(Display * dpy, XErrorEvent * err) +{ + return 0; +} + +static void +exit_cleanup() +{ + if (mypid == getpid()) + unlink(sockfile); +} + +static u64 +make_qpath(u8 type, u16 item, u16 file) +{ + return ((u64) file << 24) | ((u64) item << 8) | (u64) type; +} + +static u8 +qpath_type(u64 path) +{ + return path & 0xff; +} + +static u16 +qpath_item(u64 path) +{ + return (path >> 8) & 0xffff; +} + +/* +static u16 +qpath_file(u64 path) +{ + return (path >> 24) & 0xffff; +} +*/ + +static Map * +fid_to_map(Map ** maps, u32 fid) +{ + u32 i; + for (i = 0; maps && maps[i]; i++) + if (maps[i]->fid == fid) + return maps[i]; + return nil; +} + +static int +qfile_index(char *name, u16 * index) +{ + int i; + for (i = 0; qfilelist[i].name; i++) + if (!strncmp(name, qfilelist[i].name, strlen(qfilelist[i].name))) { + *index = i; + return TRUE; + } + return FALSE; +} + +static int +make_qid(Qid * dir, char *wname, Qid * new) +{ + u16 idx; + const char *errstr; + if (dir->type != IXP_QTDIR) + return FALSE; + new->version = 0; + if (!qfile_index(wname, &idx)) { + new->type = IXP_QTDIR; + if (!strncmp(wname, "..", 3)) { + *new = root_qid; + return TRUE; + } else if (!strncmp(wname, "default", 8)) { + new->path = make_qpath(Ditem, 0, NONE); + return TRUE; + } + /* check if wname is a number, otherwise file not found */ + idx = (u16) __strtonum(wname, 1, 0xffff, &errstr); + if (errstr || count_items((void **) items) < idx) + return FALSE; + /* found */ + new->path = make_qpath(Ditem, idx, NONE); + } else { + new->type = IXP_QTFILE; + new->path = + make_qpath(qfilelist[idx].type, qpath_item(dir->path), idx); + } + return TRUE; +} + +static int +attach(IXPServer * s, IXPConn * c) +{ + Map *map = emalloc(sizeof(Map)); + fprintf(stderr, "attaching %d %s %s\n", s->fcall.afid, s->fcall.uname, s->fcall.aname); + map->qid = root_qid; + map->fid = s->fcall.fid; + c->aux = (Map **) attach_item_begin((void **) c->aux, map, sizeof(Map *)); + s->fcall.id = RATTACH; + s->fcall.qid = root_qid; + return TRUE; +} + +static int +walk(IXPServer * s, IXPConn * c) +{ + u16 nwqid = 0; + Qid qid; + Map *map; + + fprintf(stderr, "%s", "walking\n"); + if (!(map = fid_to_map(c->aux, s->fcall.fid))) { + s->errstr = "no directory associated with fid"; + return FALSE; + } + if (s->fcall.fid != s->fcall.newfid + && (fid_to_map(c->aux, s->fcall.newfid))) { + s->errstr = "fid alreay in use"; + return FALSE; + } + if (s->fcall.nwname) { + qid = map->qid; + for (nwqid = 0; (nwqid < s->fcall.nwname) + && make_qid(&qid, s->fcall.wname[nwqid], + &s->fcall.wqid[nwqid]); nwqid++) + qid = s->fcall.wqid[nwqid]; + if (!nwqid) { + s->errstr = "file not found"; + return FALSE; + } + } + /* + * following condition is required by 9P, a fid will only be valid if + * the walk was complete + */ + if (nwqid == s->fcall.nwname) { + if (s->fcall.fid == s->fcall.newfid) { + c->aux = + (Map **) detach_item((void **) c->aux, map, sizeof(Map *)); + free(map); + } + map = emalloc(sizeof(Map)); + map->qid = qid; + map->fid = s->fcall.newfid; + c->aux = + (Map **) attach_item_begin((void **) c->aux, map, + sizeof(Map *)); + } + s->fcall.id = RWALK; + s->fcall.nwqid = nwqid; + return TRUE; +} + +static int +_open(IXPServer * s, IXPConn * c) +{ + Map *map = fid_to_map(c->aux, s->fcall.fid); + + fprintf(stderr, "%s", "opening\n"); + if (!map) { + s->errstr = "invalid fid"; + return FALSE; + } + if ((s->fcall.mode != IXP_OREAD) && (s->fcall.mode != IXP_OWRITE)) { + s->errstr = "mode not supported"; + return FALSE; + } + s->fcall.id = ROPEN; + s->fcall.qid = map->qid; + s->fcall.iounit = + s->fcall.maxmsg - (sizeof(u8) + sizeof(u16) + 2 * sizeof(u32)); + return TRUE; +} + +static int +_read(IXPServer * s, IXPConn * c) +{ + Map *map = fid_to_map(c->aux, s->fcall.fid); + Stat stat = {0}; + u8 *p; + + fprintf(stderr, "%s", "reading\n"); + if (!map) { + s->errstr = "invalid fid"; + return FALSE; + } + stat.mode = 0xff; + stat.atime = stat.mtime = time(0); + _strlcpy(stat.uid, getenv("USER"), sizeof(stat.uid)); + _strlcpy(stat.gid, getenv("USER"), sizeof(stat.gid)); + _strlcpy(stat.muid, getenv("USER"), sizeof(stat.muid)); + + fprintf(stderr, "%d\n", qpath_item(map->qid.path)); + switch (qpath_type(map->qid.path)) { + default: + case Droot: + p = s->fcall.data; + _strlcpy(stat.name, "display", sizeof(stat.name)); + stat.length = strlen(align); + make_qid(&root_qid, "display", &stat.qid); + stat.size = ixp_sizeof_stat(&stat); + s->fcall.count = stat.size; + p = ixp_enc_stat(p, &stat); + _strlcpy(stat.name, "font", sizeof(stat.name)); + stat.length = strlen(font); + make_qid(&root_qid, "font", &stat.qid); + stat.size = ixp_sizeof_stat(&stat);; + s->fcall.count += stat.size; + p = ixp_enc_stat(p, &stat); + _strlcpy(stat.name, "new", sizeof(stat.name)); + stat.length = 0; + make_qid(&root_qid, "new", &stat.qid); + stat.size = ixp_sizeof_stat(&stat);; + s->fcall.count += stat.size; + p = ixp_enc_stat(p, &stat); + s->fcall.id = RREAD; + fprintf(stderr, "%d msize\n", s->fcall.count); + break; + case Ditem: + break; + case Fdisplay: + break; + case Fnew: + break; + case Fdata: + break; + case Fevent: + break; + case Fcolor: + break; + case Ffont: + break; + } + + return TRUE; +} + +static int +_write(IXPServer * s, IXPConn * c) +{ + + + return FALSE; +} + +static int +clunk(IXPServer * s, IXPConn * c) +{ + Map *map = fid_to_map(c->aux, s->fcall.fid); + + if (!map) { + s->errstr = "invalid fid"; + return FALSE; + } + c->aux = (Map **) detach_item((void **) c->aux, map, sizeof(Map *)); + free(map); + s->fcall.id = RCLUNK; + return TRUE; +} + +static void +freeconn(IXPServer * s, IXPConn * c) +{ + Map **maps = c->aux; + if (maps) { + int i; + for (i = 0; maps[i]; i++) + free(maps[i]); + free(maps); + } +} + +static IXPTFunc funcs[] = { + {TVERSION, ixp_server_tversion}, + {TATTACH, attach}, + {TWALK, walk}, + {TOPEN, _open}, + {TREAD, _read}, + {TWRITE, _write}, + {TCLUNK, clunk}, + {0, 0} +}; + +int +main(int argc, char *argv[]) +{ + int i; + Item *item; + + /* command line args */ + for (i = 1; (i < argc) && (argv[i][0] == '-'); i++) { + switch (argv[i][1]) { + case 'v': + fprintf(stdout, "%s", version[0]); + exit(0); + break; + case 's': + if (i + 1 < argc) + sockfile = argv[++i]; + else + usage(); + break; + default: + usage(); + break; + } + } + + dpy = XOpenDisplay(0); + if (!dpy) { + fprintf(stderr, "%s", "wmibar: cannot open display\n"); + exit(1); + } + XSetErrorHandler(dummy_error_handler); + screen_num = DefaultScreen(dpy); + + if (!ixp_server_init(&srv, sockfile, funcs, freeconn)) { + fprintf(stderr, "wmibar: fatal: %s\n", srv.errstr); + exit(1); + } + root_qid.type = IXP_QTDIR; + root_qid.version = 0; + root_qid.path = make_qpath(Droot, NONE, NONE); + + mypid = getpid(); + atexit(exit_cleanup); + + /* default item settings */ + item = emalloc(sizeof(Item)); + item->id = 0; + item->text[0] = '\0'; + item->value = 0; + + align = "bottom"; + font = "fixed"; + + ixp_server_loop(&srv); + if (srv.errstr) { + fprintf(stderr, "wmibar: fatal: %s\n", srv.errstr); + ixp_server_deinit(&srv); + exit(1); + } + ixp_server_deinit(&srv); + return 0; +} diff --git a/cmd/wmifs.1 b/cmd/wmifs.1 new file mode 100644 index 00000000..f261734e --- /dev/null +++ b/cmd/wmifs.1 @@ -0,0 +1,38 @@ +.TH WMIFS 1 wmii-3 +.SH NAME +wmifs \- window manager improved 2 filesystem +.SH SYNOPSIS +.B wmifs +.B \-s +.I socketfile +.SH DESCRIPTION +.SS Overview +.B wmifs +is a socket-based fileserver which is used for binding other fileserver +filesystems to a specific namespace. It routes filesystem accesses to +the specific fileserver and behaves to the outside as one master +filesystem. +.SS Options +.TP +.BI \-s " socketfile" +specifies the socketfile that +.B wmifs +should create. +.TP +.B \-v +prints version information to stderr, then exits. +.SS Customization +.B wmifs +is customized through manipulating its filesystem namespace. +In the default setup of +.BR wmii (1) +the namespace of +.B wmifs +can be found in /. +.SH SEE ALSO +.BR wmibar (1), +.BR wmii (1), +.BR wmiiwm (1), +.BR wmikeys (1), +.BR wmimenu (1), +.BR wmir (1) diff --git a/cmd/wmifs.c b/cmd/wmifs.c new file mode 100644 index 00000000..a30a6607 --- /dev/null +++ b/cmd/wmifs.c @@ -0,0 +1,417 @@ +/* + * (C)opyright MMV Anselm R. Garbe + * See LICENSE file for license details. + */ + +#include +#include +#include + +#include "wmii.h" + +#include + +/* array indexes for file pointers */ +typedef enum { + F_CTL, + F_LAST +} FsIndexes; + +typedef struct Route Route; +struct Route { + File dest; + int src; +}; + +typedef struct Bind Bind; +struct Bind { + IXPClient *client; + Route route[MAX_CONN * MAX_OPEN_FILES]; + File *mount; + char *prefix; +}; + +static Bind zero_bind = {0}; +static Display *dpy; +static IXPServer *ixps; +static char *sockfile = 0; +static File *files[F_LAST]; +static Bind **bindings = 0; + +static void quit(void *obj, char *arg); +static void bind(void *obj, char *arg); +static void unbind(void *obj, char *arg); +static Bind *path_to_bind(char *path); + +static Action acttbl[] = { + {"quit", quit}, + {"unbind", unbind}, + {"bind", bind}, + {0, 0} +}; + +static char *version[] = { + "wmifs - window manager improved filesystem - " VERSION "\n" + " (C)opyright MMV Anselm R. Garbe\n", 0 +}; + +static void +usage() +{ + fprintf(stderr, + "usage: wmifs -s [-v]\n" + " -s socket file\n" " -v version info\n"); + exit(1); +} + +static void +quit(void *obj, char *arg) +{ + int i; + for (i = 0; bindings && bindings[i]; i++) { + if (bindings[i]->mount) { + bindings[i]->mount->content = 0; + ixp_remove(ixps, bindings[i]->prefix); + if (ixps->errstr) + fprintf(stderr, "wmifs: error on quit: remove %s: %s\n", + bindings[i]->prefix, ixps->errstr); + } + /* free stuff */ + if (bindings[i]->prefix) + free(bindings[i]->prefix); + free(bindings[i]); + } + free(bindings); + bindings = 0; + ixps->runlevel = SHUTDOWN; +} + +static void +_unbind(Bind * b) +{ + bindings = + (Bind **) detach_item((void **) bindings, b, sizeof(Bind *)); + + if (b->mount) { + b->mount->content = 0; + ixp_remove(ixps, b->prefix); + if (ixps->errstr) + fprintf(stderr, "wmifs: error on _unbind: remove %s: %s\n", + b->prefix, ixps->errstr); + } + /* free stuff */ + deinit_client(b->client); + free(b->prefix); + free(b); +} + +static void +unbind(void *obj, char *arg) +{ + Bind *b = path_to_bind(arg); + + if (!b) { + fprintf(stderr, "wmifs: unbind: '%s' no such path\n", arg); + return; + } + _unbind(b); +} + +static void +bind(void *obj, char *arg) +{ + Bind *b = 0; + char cmd[1024]; + char *sfile; + + if (!arg) + return; + _strlcpy(cmd, arg, sizeof(cmd)); + sfile = strchr(cmd, ' '); + if (!sfile) { + fprintf(stderr, + "wmifs: bind: '%s' without socket argument, ignoring\n", + arg); + return; /* shortcut with empty argument */ + } + *sfile = '\0'; + sfile++; + if (*sfile == '\0') { + fprintf(stderr, + "wmifs: bind: '%s' without socket argument, ignoring\n", + arg); + return; /* shortcut with empty argument */ + } + b = emalloc(sizeof(Bind)); + *b = zero_bind; + + b->client = init_client(sfile); + + if (!b->client) { + fprintf(stderr, + "wmifs: bind: cannot connect to server '%s', ignoring\n", + sfile); + free(b); + return; + } + b->prefix = strdup(cmd); + b->mount = ixp_create(ixps, b->prefix); + b->mount->content = b->mount; /* shall be a directory */ + + bindings = + (Bind **) attach_item_end((void **) bindings, b, sizeof(Bind *)); +} + +static void +handle_after_write(IXPServer * s, File * f) +{ + int i; + size_t len; + + for (i = 0; acttbl[i].name; i++) { + len = strlen(acttbl[i].name); + if (!strncmp(acttbl[i].name, (char *) f->content, len)) { + if (strlen(f->content) > len) { + acttbl[i].func(0, &((char *) f->content)[len + 1]); + } else { + acttbl[i].func(0, 0); + } + break; + } + } +} + +static Bind * +path_to_bind(char *path) +{ + int i; + for (i = 0; bindings && bindings[i]; i++) + if (!strncmp + (bindings[i]->prefix, path, strlen(bindings[i]->prefix))) + return bindings[i]; + return 0; +} + +static Bind * +fd_to_bind(int fd, int *client_fd) +{ + File *f = fd_to_file(ixps, fd); + int i, j; + + if (!f) + return 0; + for (i = 0; bindings && bindings[i]; i++) { + for (j = 0; j < MAX_CONN * MAX_OPEN_FILES; j++) { + if (&bindings[i]->route[j].dest == f) { + *client_fd = bindings[i]->route[j].src; + return bindings[i]; + } + } + } + return 0; +} + +static File * +fixp_create(IXPServer * s, char *path) +{ + Bind *b = path_to_bind(path); + size_t len; + + if (!b) { + File *f = ixp_create(s, path); + return f; + } + /* route to b */ + len = strlen(b->prefix); + b->client->create(b->client, path[len] == '\0' ? "/" : &path[len]); + if (b->client->errstr) { + if (!strcmp(b->client->errstr, DEAD_SERVER)) + _unbind(b); + } + return 0; +} + +static File * +fixp_open(IXPServer * s, char *path) +{ + Bind *b = path_to_bind(path); + int fd; + size_t len; + + if (!b) { + File *f = ixp_open(s, path); + return f; + } + /* route to b */ + len = strlen(b->prefix); + fd = b->client->open(b->client, path[len] == '\0' ? "/" : &path[len]); + if (b->client->errstr) { + set_error(s, b->client->errstr); + if (!strcmp(b->client->errstr, DEAD_SERVER)) + _unbind(b); + return 0; + } + b->route[fd].src = fd; + return &b->route[fd].dest; +} + +static size_t +fixp_read(IXPServer * s, int fd, size_t offset, void *out_buf, + size_t out_buf_len) +{ + int cfd; + Bind *b = fd_to_bind(fd, &cfd); + size_t result; + + if (!b) { + result = ixp_read(s, fd, offset, out_buf, out_buf_len); + return result; + } + /* route to b */ + result = seek_read(b->client, cfd, offset, out_buf, out_buf_len); + if (b->client->errstr) { + set_error(s, b->client->errstr); + if (!strcmp(b->client->errstr, DEAD_SERVER)) + _unbind(b); + } + return result; +} + +static void +fixp_write(IXPServer * s, int fd, size_t offset, void *content, + size_t in_len) +{ + int cfd; + Bind *b = fd_to_bind(fd, &cfd); + + if (!b) { + ixp_write(s, fd, offset, content, in_len); + return; + } + /* route to b */ + seek_write(b->client, cfd, offset, content, in_len); + if (b->client->errstr) { + set_error(s, b->client->errstr); + if (!strcmp(b->client->errstr, DEAD_SERVER)) + _unbind(b); + } +} + +static void +fixp_close(IXPServer * s, int fd) +{ + int cfd; + Bind *b = fd_to_bind(fd, &cfd); + + if (!b) { + ixp_close(s, fd); + return; + } + /* route to b */ + b->client->close(b->client, cfd); + if (b->client->errstr) { + set_error(s, b->client->errstr); + if (!strcmp(b->client->errstr, DEAD_SERVER)) + _unbind(b); + } +} + +static void +fixp_remove(IXPServer * s, char *path) +{ + Bind *b = path_to_bind(path); + size_t len; + + if (!b) { + ixp_remove(s, path); + return; + } + /* route to b */ + len = strlen(b->prefix); + b->client->remove(b->client, path[len] == '\0' ? "/" : &path[len]); + if (b->client->errstr) { + set_error(s, b->client->errstr); + if (!strcmp(b->client->errstr, DEAD_SERVER)) + _unbind(b); + } +} + +static void +check_event(Connection * e) +{ + XEvent ev; + while (XPending(dpy)) { + /* + * wmifs isn't interested in any X events, so just drop them + * all + */ + XNextEvent(dpy, &ev); + } + /* why check them? because X won't kill wmifs when X dies */ +} + +static void +run() +{ + if (!(files[F_CTL] = ixp_create(ixps, "/ctl"))) { + perror("wmifs: cannot connect IXP server"); + exit(1); + } + files[F_CTL]->after_write = handle_after_write; + + /* routing functions */ + ixps->create = fixp_create; + ixps->remove = fixp_remove; + ixps->open = fixp_open; + ixps->close = fixp_close; + ixps->read = fixp_read; + ixps->write = fixp_write; + + /* main event loop */ + run_server_with_fd_support(ixps, ConnectionNumber(dpy), + check_event, 0); + + deinit_server(ixps); +} + +int +main(int argc, char *argv[]) +{ + int i; + + /* command line args */ + for (i = 1; (i < argc) && (argv[i][0] == '-'); i++) { + switch (argv[i][1]) { + case 'v': + fprintf(stdout, "%s", version[0]); + exit(0); + break; + case 's': + if (i + 1 < argc) + sockfile = argv[++i]; + else + usage(); + break; + default: + usage(); + break; + } + } + + if (!getenv("HOME")) { + fprintf(stderr, "%s", + "wmifs: $HOME environment variable is not set\n"); + usage(); + } + /* just for the case X crashes/gets quit */ + dpy = XOpenDisplay(0); + if (!dpy) { + fprintf(stderr, "%s", "wmifs: cannot open display\n"); + exit(1); + } + ixps = wmii_setup_server(sockfile); + + run(); + + return 0; +} diff --git a/cmd/wmikeys.1 b/cmd/wmikeys.1 new file mode 100644 index 00000000..a2002d20 --- /dev/null +++ b/cmd/wmikeys.1 @@ -0,0 +1,45 @@ +.TH WMIKEYS 1 wmii-3 +.SH NAME +wmikeys \- window manager improved 2 keys +.SH SYNOPSIS +.B wmikeys +.B \-s +.I socketfile +.SH DESCRIPTION +.SS Overview +.B wmikeys +is a generic, highly customizable, and efficient key grabbing utility for the +X Window System, originally designed for +.BR wmii (1). +It is similar to xbindkeys, but provides more flexibility and comes +with equivalent features like the ratpoison keyboard control allows. +It supports arbitrary, user defined shortcuts which are bound to +arbitrary actions. +Like wmii, +.B wmikeys +also implements a socket-based fileserver, which is accessed to configure and +interact with other components. +.SS Options +.TP +.BI \-s " socketfile" +specifies the socketfile that +.B wmikeys +should create. +.TP +.B \-v +prints version information to stderr, then exits. +.SS Customization +.B wmikeys +is customized through manipulating its filesystem namespace. +In the default setup of +.BR wmii (1) +the namespace of +.B wmikeys +can be found in /keys. +.SH SEE ALSO +.BR wmibar (1), +.BR wmifs (1), +.BR wmii (1), +.BR wmiiwm (1), +.BR wmimenu (1), +.BR wmir (1) diff --git a/cmd/wmikeys.c b/cmd/wmikeys.c new file mode 100644 index 00000000..984d2429 --- /dev/null +++ b/cmd/wmikeys.c @@ -0,0 +1,535 @@ +/* + * (C)opyright MMIV-MMV Anselm R. Garbe + * See LICENSE file for license details. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "wmii.h" + +#include + +/* array indexes for file pointers */ +typedef enum { + K_CTL, + K_LOOKUP, + K_SIZE, + K_GRAB_KB, + K_TEXT_FONT, + K_TEXT_COLOR, + K_BG_COLOR, + K_BORDER_COLOR, + K_LAST +} KeyIndexes; + +typedef struct Shortcut Shortcut; + +struct Shortcut { + char name[MAX_BUF]; + unsigned long mod; + KeyCode key; + Shortcut *next; + File *cmdfile; +}; + +static IXPServer *ixps = 0; +static Display *dpy; +static GC gc; +static Window win; +static Window root; +static XRectangle krect; +static XRectangle rect; +static int screen_num; +static char *sockfile = 0; +static Shortcut **shortcuts = 0; +static File *files[K_LAST]; +static int grabkb = 0; +static unsigned int num_lock_mask, valid_mask; +static char buf[MAX_BUF]; + +static Shortcut zero_shortcut = {"", 0, 0, 0, 0}; + +static void grab_shortcut(Shortcut * s); +static void ungrab_shortcut(Shortcut * s); +static void draw_shortcut_box(char *text); +static void quit(void *obj, char *arg); + +static Action acttbl[] = { + {"quit", quit}, + {0, 0} +}; + +static char *version[] = { + "wmikeys - window manager improved keys - " VERSION "\n" + " (C)opyright MMIV-MMV Anselm R. Garbe\n", 0 +}; + +static void +center() +{ + krect.x = rect.width / 2 - krect.width / 2; + krect.y = rect.height / 2 - krect.height / 2; +} + +static void +usage() +{ + fprintf(stderr, "%s", + "usage: wmikeys [-s ] [-v] [,,,]\n" + " -s socket file (default: /tmp/.ixp-$USER/wmikeys-$WMII_IDENT)\n" + " -v version info\n"); + exit(1); +} + +/* grabs shortcut on all screens */ +static void +grab_shortcut(Shortcut * s) +{ + XGrabKey(dpy, s->key, s->mod, root, + True, GrabModeAsync, GrabModeAsync); + if (num_lock_mask) { + XGrabKey(dpy, s->key, s->mod | num_lock_mask, root, + True, GrabModeAsync, GrabModeAsync); + XGrabKey(dpy, s->key, s->mod | num_lock_mask | LockMask, root, + True, GrabModeAsync, GrabModeAsync); + } + XSync(dpy, False); +} + +/* + * don't handle evil keys anymore, just define more shortcuts if you cannot + * live without evil key handling + */ +static void +ungrab_shortcut(Shortcut * s) +{ + XUngrabKey(dpy, s->key, s->mod, root); + if (num_lock_mask) { + XUngrabKey(dpy, s->key, s->mod | num_lock_mask, root); + XUngrabKey(dpy, s->key, s->mod | num_lock_mask | + LockMask, root); + } + XSync(dpy, False); +} + +static void +create_shortcut(File * f) +{ + static char *chain[8]; + char *k; + size_t i, toks; + Shortcut *s = 0, *r = 0; + + _strlcpy(buf, f->name, sizeof(buf)); + toks = tokenize(chain, 8, buf, ','); + + for (i = 0; i < toks; i++) { + if (!s) + r = s = emalloc(sizeof(Shortcut)); + else { + s->next = emalloc(sizeof(Shortcut)); + s = s->next; + } + *s = zero_shortcut; + _strlcpy(s->name, chain[i], MAX_BUF); + k = strrchr(chain[i], '-'); + if (k) + k++; + else + k = chain[i]; + s->key = XKeysymToKeycode(dpy, XStringToKeysym(k)); + s->mod = blitz_strtomod(chain[i]); + } + if (r) { + s->cmdfile = f; + shortcuts = (Shortcut **) attach_item_end((void **) shortcuts, r, sizeof(Shortcut *)); + grab_shortcut(r); + } +} + +static void +destroy_shortcut(Shortcut * s, int ungrab) +{ + if (s->next) + destroy_shortcut(s->next, 0); + if (ungrab) + ungrab_shortcut(s); + free(s); +} + +static void +next_keystroke(unsigned long *mod, KeyCode * key) +{ + XEvent e; + KeySym sym; + *mod = 0; + do { + XMaskEvent(dpy, KeyPressMask, &e); + *mod |= e.xkey.state & valid_mask; + *key = (KeyCode) e.xkey.keycode; + sym = XKeycodeToKeysym(dpy, e.xkey.keycode, 0); + } while (IsModifierKey(sym)); +} + +static void +emulate_key_press(unsigned long mod, KeyCode key) +{ + XEvent e; + Window client_win; + int revert; + + XGetInputFocus(dpy, &client_win, &revert); + + e.xkey.type = KeyPress; + e.xkey.time = CurrentTime; + e.xkey.window = client_win; + e.xkey.display = dpy; + e.xkey.state = mod; + e.xkey.keycode = key; + XSendEvent(dpy, client_win, True, KeyPressMask, &e); + e.xkey.type = KeyRelease; + XSendEvent(dpy, client_win, True, KeyReleaseMask, &e); + XSync(dpy, False); +} + +static void +handle_shortcut_chain(Window w, Shortcut * processed, char *prefix, int grab) +{ + unsigned long mod; + KeyCode key; + Shortcut *s = processed->next; + + if (grab) { + XGrabKeyboard(dpy, w, True, GrabModeAsync, + GrabModeAsync, CurrentTime); + XMapRaised(dpy, win); + } + draw_shortcut_box(prefix); + next_keystroke(&mod, &key); + + if ((processed->mod == mod) && (processed->key == key)) { + /* double shortcut */ + emulate_key_press(mod, key); + } else if ((s->mod == mod) && (s->key == key)) { + if (s->cmdfile && s->cmdfile->content) + spawn(dpy, s->cmdfile->content); + else if (s->next) { + snprintf(buf, sizeof(buf), "%s/%s", prefix, s->name); + handle_shortcut_chain(w, s, buf, 0); + } + } + if (grab) { + XUngrabKeyboard(dpy, CurrentTime); + XUnmapWindow(dpy, win); + XSync(dpy, False); + } +} + +static void +handle_shortcut_gkb(Window w, unsigned long mod, KeyCode key) +{ + int i; + Shortcut *s; + if (!files[K_LOOKUP]->content) + return; + for (i = 0; shortcuts && shortcuts[i]; i++) { + s = shortcuts[i]; + if ((s->mod == mod) && (s->key == key)) { + if (s->cmdfile && s->cmdfile->content) + spawn(dpy, s->cmdfile->content); + return; + } + } + XBell(dpy, 0); +} + +static void +handle_shortcut(Window w, unsigned long mod, KeyCode key) +{ + int i; + Shortcut *s; + if (!files[K_LOOKUP]->content) + return; + for (i = 0; shortcuts && shortcuts[i]; i++) { + s = shortcuts[i]; + if ((s->mod == mod) && (s->key == key)) { + if (s->cmdfile && s->cmdfile->content) { + spawn(dpy, s->cmdfile->content); + return; + } + break; + } + } + if (s->next) + handle_shortcut_chain(w, s, s->name, 1); +} + +static void +quit(void *obj, char *arg) +{ + ixps->runlevel = SHUTDOWN; +} + +static void +update() +{ + int i; + File *f, *p; + if (!files[K_LOOKUP]->content) + return; + + f = ixp_walk(ixps, files[K_LOOKUP]->content); + + if (!f || !is_directory(f)) + return; /* cannot update */ + + /* destroy existing shortcuts if any */ + for (i = 0; shortcuts && shortcuts[i]; i++) + destroy_shortcut(shortcuts[i], 1); + free(shortcuts); + shortcuts = 0; + + if (grabkb) { + XGrabKeyboard(dpy, root, True, GrabModeAsync, + GrabModeAsync, CurrentTime); + return; + } + /* create new shortcuts */ + for (p = f->content; p; p = p->next) + create_shortcut(p); +} + +/* + * Function assumes following fs-structure: + * + * /box/style/text-align "" + * /box/style/text-font "" + * /box/style/text-color "#RRGGBBAA" + * /box/style/bg-color "#RRGGBBAA" + */ +static void +draw_shortcut_box(char *text) +{ + Draw d = {0}; + + d.font = blitz_getfont(dpy, files[K_TEXT_FONT]->content); + krect.width = XTextWidth(d.font, text, strlen(text)) + krect.height; + center(); + XMoveResizeWindow(dpy, win, krect.x, krect.y, krect.width, + krect.height); + + /* default stuff */ + d.gc = gc; + d.drawable = win; + d.data = text; + d.rect.y = 0; + d.rect.width = krect.width; + d.rect.height = krect.height; + d.bg = blitz_loadcolor(dpy, screen_num, files[K_BG_COLOR]->content); + d.fg = blitz_loadcolor(dpy, screen_num, files[K_TEXT_COLOR]->content); + d.border = + blitz_loadcolor(dpy, screen_num, files[K_BORDER_COLOR]->content); + blitz_drawlabel(dpy, &d); +} + +static void +check_event(Connection * c) +{ + XEvent ev; + + while (XPending(dpy)) { + XNextEvent(dpy, &ev); + switch (ev.type) { + case KeyPress: + ev.xkey.state &= valid_mask; + if (grabkb) + handle_shortcut_gkb(root, ev.xkey.state, + (KeyCode) ev.xkey.keycode); + else + handle_shortcut(root, ev.xkey.state, + (KeyCode) ev.xkey.keycode); + break; + case KeymapNotify: + update(); + break; + default: + break; + } + } +} + +static void +handle_after_write(IXPServer * s, File * f) +{ + int i; + size_t len; + + if (f == files[K_CTL]) { + for (i = 0; acttbl[i].name; i++) { + len = strlen(acttbl[i].name); + if (!strncmp(acttbl[i].name, (char *) f->content, len)) { + if (strlen(f->content) > len) { + acttbl[i].func(0, &((char *) f->content)[len + 1]); + } else { + acttbl[i].func(0, 0); + } + break; + } + } + } else if (files[K_SIZE] == f) { + char *size = files[K_SIZE]->content; + if (size && strrchr(size, ',')) + blitz_strtorect(dpy, &rect, &krect, size); + } else if (f == files[K_GRAB_KB]) { + grabkb = _strtonum(files[K_GRAB_KB]->content, 0, 1); + if (!grabkb) { + XUngrabKeyboard(dpy, CurrentTime); + XUnmapWindow(dpy, win); + XSync(dpy, False); + } else + update(); + } else if (f == files[K_LOOKUP]) { + update(); + } + check_event(0); +} + +static void +handle_before_read(IXPServer * s, File * f) +{ + if (f != files[K_SIZE]) + return; + snprintf(buf, sizeof(buf), "%d,%d,%d,%d", krect.x, krect.y, + krect.width, krect.height); + if (f->content) + free(f->content); + f->content = strdup(buf); + f->size = strlen(buf); +} + +static void +run(char *size) +{ + XSetWindowAttributes wa; + XGCValues gcv; + + /* init */ + if (!(files[K_CTL] = ixp_create(ixps, "/ctl"))) { + perror("wmikeys: cannot connect IXP server"); + exit(1); + } + files[K_CTL]->after_write = handle_after_write; + files[K_LOOKUP] = ixp_create(ixps, "/lookup"); + files[K_LOOKUP]->after_write = handle_after_write; + files[K_SIZE] = ixp_create(ixps, "/size"); + files[K_SIZE]->before_read = handle_before_read; + files[K_SIZE]->after_write = handle_after_write; + files[K_GRAB_KB] = wmii_create_ixpfile(ixps, "/grab-keyb", "0"); + files[K_GRAB_KB]->after_write = handle_after_write; + files[K_TEXT_FONT] = wmii_create_ixpfile(ixps, "/box/style/text-font", BLITZ_FONT); + files[K_TEXT_COLOR] = wmii_create_ixpfile(ixps, "/box/style/text-color", BLITZ_SEL_FG_COLOR); + files[K_BG_COLOR] = wmii_create_ixpfile(ixps, "/box/style/bg-color", BLITZ_SEL_BG_COLOR); + files[K_BORDER_COLOR] = wmii_create_ixpfile(ixps, "/box/style/border-color", BLITZ_SEL_BORDER_COLOR); + + wa.override_redirect = 1; + wa.background_pixmap = ParentRelative; + wa.event_mask = + ExposureMask | SubstructureRedirectMask | SubstructureNotifyMask; + + root = RootWindow(dpy, screen_num); + rect.x = rect.y = 0; + rect.width = DisplayWidth(dpy, screen_num); + rect.height = DisplayHeight(dpy, screen_num); + krect.x = krect.y = -1; + krect.width = krect.height = 0; + blitz_strtorect(dpy, &rect, &krect, size); + /* default is center position */ + if (!krect.width) { + krect.width = 200; + } + if (!krect.height) { + krect.height = 20; + } + center(); + + init_lock_modifiers(dpy, &valid_mask, &num_lock_mask); + + win = XCreateWindow(dpy, RootWindow(dpy, screen_num), krect.x, krect.y, + krect.width, krect.height, 0, DefaultDepth(dpy, + screen_num), + CopyFromParent, DefaultVisual(dpy, screen_num), + CWOverrideRedirect | CWBackPixmap | CWEventMask, + &wa); + XDefineCursor(dpy, win, XCreateFontCursor(dpy, XC_left_ptr)); + XSync(dpy, False); + + gcv.function = GXcopy; + gcv.graphics_exposures = False; + + gc = XCreateGC(dpy, win, 0, 0); + + /* main event loop */ + run_server_with_fd_support(ixps, ConnectionNumber(dpy), + check_event, 0); + deinit_server(ixps); + XFreeGC(dpy, gc); + XCloseDisplay(dpy); +} + +static int +dummy_error_handler(Display * dpy, XErrorEvent * err) +{ + return 0; +} + +int +main(int argc, char *argv[]) +{ + char size[64]; + int i; + + /* command line args */ + for (i = 1; (i < argc) && (argv[i][0] == '-'); i++) { + switch (argv[i][1]) { + case 'v': + fprintf(stdout, "%s", version[0]); + exit(0); + break; + case 's': + if (i + 1 < argc) + sockfile = argv[++i]; + else + usage(); + break; + default: + usage(); + break; + } + } + + dpy = XOpenDisplay(0); + if (!dpy) { + fprintf(stderr, "%s", "wmikeys: cannot open display\n"); + exit(1); + } + XSetErrorHandler(dummy_error_handler); + screen_num = DefaultScreen(dpy); + size[0] = '\0'; + if (argc > i) + _strlcpy(size, argv[i], sizeof(size)); + + ixps = wmii_setup_server(sockfile); + + run(size); + + return 0; +} diff --git a/cmd/wmimenu.1 b/cmd/wmimenu.1 new file mode 100644 index 00000000..2c7d4eae --- /dev/null +++ b/cmd/wmimenu.1 @@ -0,0 +1,42 @@ +.TH WMIMENU 1 wmii-3 +.SH NAME +wmimenu \- window manager improved 2 menu +.SH SYNOPSIS +.B wmimenu +.B \-s +.I socketfile +.SH DESCRIPTION +.SS Overview +.B wmimenu +is a generic, highly customizable, and efficient menu for the X Window System, +originally designed for +.BR wmii (1). +It supports arbitrary, user defined menu contents, which get executed. +Like wmii, +.B wmimenu +also implements a socket-based fileserver, which is accessed to configure and +interoperate with other components. +.SS Options +.TP +.BI \-s " socketfile" +specifies the socketfile that +.B wmimenu +should create. +.TP +.B \-v +prints version information to stderr, then exits. +.SS Customization +.B wmimenu +is customized through manipulating its filesystem namespace. +In the default setup of +.BR wmii (1) +the namespace of +.B wmimenu +can be found in /menu. +.SH SEE ALSO +.BR wmibar (1), +.BR wmifs (1), +.BR wmii (1), +.BR wmiiwm (1), +.BR wmikeys (1), +.BR wmir (1) diff --git a/cmd/wmimenu.c b/cmd/wmimenu.c new file mode 100644 index 00000000..39f69726 --- /dev/null +++ b/cmd/wmimenu.c @@ -0,0 +1,689 @@ +/* + * (C)opyright MMIV-MMV Anselm R. Garbe + * See LICENSE file for license details. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "wmii.h" + +#include + +/* array indexes for file pointers */ +typedef enum { + M_CTL, + M_SIZE, + M_PRE_COMMAND, + M_COMMAND, + M_HISTORY, + M_LOOKUP, + M_TEXT_FONT, + M_SELECTED_BG_COLOR, + M_SELECTED_TEXT_COLOR, + M_SELECTED_BORDER_COLOR, + M_NORMAL_BG_COLOR, + M_NORMAL_TEXT_COLOR, + M_NORMAL_BORDER_COLOR, + M_RETARDED, + M_LAST +} InputIndexes; + +enum { + OFF_NEXT, OFF_PREV, OFF_CURR, OFF_LAST +}; + +static IXPServer *ixps = 0; +static Display *dpy; +static GC gc; +static Window win; +static XRectangle rect; +static XRectangle mrect; +static int screen_num; +static char *sockfile = 0; +static File *files[M_LAST]; +static File **items = 0; +static size_t item_size = 0; +static int item = 0; +static int offset[OFF_LAST]; +static unsigned int cmdw = 0; +static File **history = 0; +static int sel_history = 0; +static Pixmap pmap; +static const int seek = 30; /* 30px */ + +static void check_event(Connection * c); +static void draw_menu(void); +static void handle_kpress(XKeyEvent * e); +static void set_text(char *text); +static void quit(void *obj, char *arg); +static void display(void *obj, char *arg); +static int update_items(char *prefix); + +static Action acttbl[2] = { + {"quit", quit}, + {"display", display} +}; + +static char *version[] = { + "wmimenu - window manager improved menu - " VERSION "\n" + " (C)opyright MMIV-MMV Anselm R. Garbe\n", 0 +}; + +static void +usage() +{ + fprintf(stderr, "%s", + "usage: wmimenu [-s ] [-r] [-v] [,,,]\n" + " -s socket file (default: /tmp/.ixp-$USER/wmimenu-%s-%s)\n" + " -v version info\n"); + exit(1); +} + +static void +add_history(char *cmd) +{ + char buf[MAX_BUF]; + snprintf(buf, MAX_BUF, "/history/%ld", (long) time(0)); + history = (File **) attach_item_begin((void **) history, + wmii_create_ixpfile(ixps, buf, + cmd), + sizeof(File *)); +} + +static void +_exec(char *cmd) +{ + char *rc = cmd; + + if (!cmd || cmd[0] == '\0') + return; + + if (items && items[0]) { + if ((item >= 0) && items[item] && items[item]->size) + rc = cmd = items[item]->content; + else if ((item == -1) && items[0]->size) /* autolight */ + rc = cmd = items[0]->content; + } + add_history(cmd); + if (files[M_PRE_COMMAND]->content) { + size_t len = strlen(cmd) + files[M_PRE_COMMAND]->size + 2; + rc = emalloc(len); + snprintf(rc, len, "%s %s", (char *) files[M_PRE_COMMAND]->content, + cmd); + } + /* fallback */ + spawn(dpy, rc); + /* cleanup */ + if (files[M_PRE_COMMAND]->content) + free(rc); +} + +static void +quit(void *obj, char *arg) +{ + ixps->runlevel = SHUTDOWN; +} + +static void +show() +{ + set_text(0); + XMapRaised(dpy, win); + XSync(dpy, False); + update_items(files[M_COMMAND]->content); + draw_menu(); + while (XGrabKeyboard + (dpy, RootWindow(dpy, screen_num), True, GrabModeAsync, + GrabModeAsync, CurrentTime) != GrabSuccess) + usleep(1000); +} + +static void +hide() +{ + XUngrabKeyboard(dpy, CurrentTime); + XUnmapWindow(dpy, win); + XSync(dpy, False); +} + +static void +display(void *obj, char *arg) +{ + if (!arg) + return; + if (_strtonum(arg, 0, 1)) + show(); + else + hide(); + check_event(0); +} + +void +set_text(char *text) +{ + if (files[M_COMMAND]->content) { + free(files[M_COMMAND]->content); + files[M_COMMAND]->content = 0; + files[M_COMMAND]->size = 0; + } + if (text && strlen(text)) { + files[M_COMMAND]->content = strdup(text); + files[M_COMMAND]->size = strlen(text); + } +} + +static void +update_offsets() +{ + int i; + XFontStruct *font; + unsigned int w = cmdw + 2 * seek; + + if (!items) + return; + + font = blitz_getfont(dpy, files[M_TEXT_FONT]->content); + + /* calc next offset */ + for (i = offset[OFF_CURR]; items[i]; i++) { + w += XTextWidth(font, items[i]->content, strlen(items[i]->content)) + mrect.height; + if (w > mrect.width) + break; + } + offset[OFF_NEXT] = i; + + + w = cmdw + 2 * seek; + for (i = offset[OFF_CURR] - 1; i >= 0; i--) { + w += XTextWidth(font, items[i]->content, strlen(items[i]->content)) + mrect.height; + if (w > mrect.width) + break; + } + offset[OFF_PREV] = i + 1; + XFreeFont(dpy, font); +} + +static int +update_items(char *pattern) +{ + size_t plen = pattern ? strlen(pattern) : 0, size = 0, max = 0, + len; + int matched = pattern ? plen == 0 : 1; + XFontStruct *font; + File *f, *p, *maxitem = 0; + + cmdw = 0; + item = -1; + offset[OFF_CURR] = offset[OFF_PREV] = offset[OFF_NEXT] = 0; + + if (!files[M_LOOKUP]->content) + return 0; + f = ixp_walk(ixps, files[M_LOOKUP]->content); + if (!f || !is_directory(f)) + return 0; + + font = blitz_getfont(dpy, files[M_TEXT_FONT]->content); + + /* build new items */ + for (p = f->content; p; p = p->next) { + size++; + len = strlen(p->name); + if (max < len) { + maxitem = p; + max = len; + } + } + + if (maxitem) { + if (files[M_RETARDED]->content) + free(files[M_RETARDED]->content); + files[M_RETARDED]->content = strdup(maxitem->name); + files[M_RETARDED]->size = max; + cmdw = XTextWidth(font, maxitem->name, max) + mrect.height; + } + if (size > item_size) { + /* stores always the biggest amount of items in memory */ + if (items) + free((File **) items); + items = 0; + item_size = size; + if (item_size) + items = (File **) emalloc((item_size + 1) * sizeof(File *)); + } + size = 0; + + for (p = f->content; p; p = p->next) { + if (!p->content) + continue; /* ignore bogus files */ + if (matched || !strncmp(pattern, p->name, plen)) { + items[size++] = p; + p->parent = 0; /* HACK to prevent doubled items */ + } + } + + for (p = f->content; p; p = p->next) { + if (!p->content) + continue; /* ignore bogus files */ + if (p->parent && strstr(p->name, pattern)) + items[size++] = p; + else + p->parent = f; /* restore HACK */ + } + items[size] = 0; + update_offsets(); + XFreeFont(dpy, font); + return size; +} + +/* creates draw structs for menu mode drawing */ +static void +draw_menu() +{ + Draw d = {0}; + unsigned int offx = 0; + int i = 0; + XFontStruct *font = blitz_getfont(dpy, files[M_TEXT_FONT]->content); + + d.gc = gc; + d.drawable = pmap; + d.rect = mrect; + d.rect.x = 0; + d.rect.y = 0; + d.bg = blitz_loadcolor(dpy, screen_num, files[M_NORMAL_BG_COLOR]->content); + d.border = blitz_loadcolor(dpy, screen_num, files[M_NORMAL_BORDER_COLOR]->content); + blitz_drawlabelnoborder(dpy, &d); + + /* print command */ + d.align = WEST; + d.font = font; + d.fg = blitz_loadcolor(dpy, screen_num, files[M_NORMAL_TEXT_COLOR]->content); + d.data = files[M_COMMAND]->content; + if (cmdw && items && items[0]) + d.rect.width = cmdw; + offx += d.rect.width; + blitz_drawlabelnoborder(dpy, &d); + + d.align = CENTER; + if (items && items[0]) { + d.bg = blitz_loadcolor(dpy, screen_num, files[M_NORMAL_BG_COLOR]->content); + d.fg = blitz_loadcolor(dpy, screen_num, files[M_NORMAL_TEXT_COLOR]->content); + d.data = offset[OFF_CURR] ? "<" : 0; + d.rect.x = offx; + d.rect.width = seek; + offx += d.rect.width; + blitz_drawlabelnoborder(dpy, &d); + + /* determine maximum items */ + for (i = offset[OFF_CURR]; items[i] && (i < offset[OFF_NEXT]); i++) { + d.data = items[i]->name; + d.rect.x = offx; + d.rect.width = XTextWidth(d.font, d.data, strlen(d.data)) + mrect.height; + offx += d.rect.width; + if (i == item) { + d.bg = blitz_loadcolor(dpy, screen_num, files[M_SELECTED_BG_COLOR]->content); + d.fg = blitz_loadcolor(dpy, screen_num, files[M_SELECTED_TEXT_COLOR]->content); + d.border = blitz_loadcolor(dpy, screen_num, files[M_SELECTED_BORDER_COLOR]->content); + blitz_drawlabel(dpy, &d); + } else if (!i && item == -1) { + /* fg and bg are inverted */ + d.fg = blitz_loadcolor(dpy, screen_num, files[M_SELECTED_BG_COLOR]->content); + d.bg = blitz_loadcolor(dpy, screen_num, files[M_SELECTED_TEXT_COLOR]->content); + d.border = blitz_loadcolor(dpy, screen_num, files[M_SELECTED_BORDER_COLOR]->content); + blitz_drawlabel(dpy, &d); + } else { + d.bg = blitz_loadcolor(dpy, screen_num, files[M_NORMAL_BG_COLOR]->content); + d.fg = blitz_loadcolor(dpy, screen_num, files[M_NORMAL_TEXT_COLOR]->content); + d.border = blitz_loadcolor(dpy, screen_num, files[M_NORMAL_BORDER_COLOR]->content); + blitz_drawlabelnoborder(dpy, &d); + } + } + + d.bg = blitz_loadcolor(dpy, screen_num, files[M_NORMAL_BG_COLOR]->content); + d.fg = blitz_loadcolor(dpy, screen_num, files[M_NORMAL_TEXT_COLOR]->content); + d.data = items[i] ? ">" : 0; + d.rect.x = mrect.width - seek; + d.rect.width = seek; + blitz_drawlabelnoborder(dpy, &d); + } + XCopyArea(dpy, pmap, win, gc, 0, 0, mrect.width, mrect.height, 0, 0); + XSync(dpy, False); + XFreeFont(dpy, font); +} + +static void +handle_kpress(XKeyEvent * e) +{ + KeySym ksym; + char buf[32]; + int idx, num; + static char text[4096]; + size_t len = 0; + + text[0] = '\0'; + if (files[M_COMMAND]->content) { + _strlcpy(text, files[M_COMMAND]->content, sizeof(text)); + len = strlen(text); + } + buf[0] = '\0'; + num = XLookupString(e, buf, sizeof(buf), &ksym, 0); + + if (IsFunctionKey(ksym) || IsKeypadKey(ksym) + || IsMiscFunctionKey(ksym) || IsPFKey(ksym) + || IsPrivateKeypadKey(ksym)) + return; + + /* first check if a control mask is omitted */ + if (e->state & ShiftMask) { + if (ksym == XK_ISO_Left_Tab) + ksym = XK_Left; + } else if (e->state & ControlMask) { + switch (ksym) { + case XK_E: + case XK_e: + ksym = XK_End; + break; + case XK_H: + case XK_h: + ksym = XK_BackSpace; + break; + case XK_J: + case XK_j: + ksym = XK_Return; + break; + case XK_U: + case XK_u: + set_text(0); + update_items(0); + draw_menu(); + return; + break; + default: /* ignore other control sequences */ + return; + break; + } + } + switch (ksym) { + case XK_Left: + if (!items || !items[0]) + return; + if (item > 0) { + item--; + set_text(items[item]->name); + } else + return; + break; + case XK_Right: + case XK_Tab: + if (!items || !items[0]) + return; + if (items[item + 1]) { + item++; + set_text(items[item]->name); + } else + return; + break; + case XK_Down: + if (history) { + set_text(history[sel_history]->content); + idx = index_prev_item((void **) history, history[sel_history]); + if (idx >= 0) + sel_history = idx; + } + update_items(files[M_COMMAND]->content); + break; + case XK_Up: + if (history) { + set_text(history[sel_history]->content); + idx = index_next_item((void **) history, history[sel_history]); + if (idx >= 0) + sel_history = idx; + } + update_items(files[M_COMMAND]->content); + break; + case XK_Return: + if (items && items[0]) { + if (item >= 0) + _exec(items[item]->name); + else + _exec(items[0]->name); + } else if (text) + _exec(text); + case XK_Escape: + hide(); + break; + case XK_BackSpace: + if (len) { + int size = 0; + size_t i = len; + for (size = 0; items && items[size]; size++); + if (i) { + do + text[--i] = '\0'; + while (size && i && size == update_items(text)); + } + set_text(text); + update_items(files[M_COMMAND]->content); + } + break; + default: + if ((num == 1) && !iscntrl((int) buf[0])) { + buf[num] = '\0'; + if (len > 0) + _strlcat(text, buf, sizeof(text)); + else + _strlcpy(text, buf, sizeof(text)); + set_text(text); + update_items(files[M_COMMAND]->content); + } + } + if (items && item > 0) { + if (item < offset[OFF_CURR]) { + offset[OFF_CURR] = offset[OFF_PREV]; + update_offsets(); + } else if (item >= offset[OFF_NEXT]) { + offset[OFF_CURR] = offset[OFF_NEXT]; + update_offsets(); + } + } + draw_menu(); +} + +static void +check_event(Connection * c) +{ + XEvent ev; + + while (XPending(dpy)) { + XNextEvent(dpy, &ev); + switch (ev.type) { + case KeyPress: + handle_kpress(&ev.xkey); + break; + case Expose: + if (ev.xexpose.count == 0) { + draw_menu(); + } + break; + default: + break; + } + } +} + +static void +handle_after_write(IXPServer * s, File * f) +{ + int i; + size_t len; + + if (files[M_CTL] == f) { + for (i = 0; i < 2; i++) { + len = strlen(acttbl[i].name); + if (!strncmp(acttbl[i].name, (char *) f->content, len)) { + if (strlen(f->content) > len) { + acttbl[i].func(0, &((char *) f->content)[len + 1]); + } else { + acttbl[i].func(0, 0); + } + break; + } + } + } else if (files[M_SIZE] == f) { + char *size = files[M_SIZE]->content; + if (size && strrchr(size, ',')) { + blitz_strtorect(dpy, &rect, &mrect, size); + XFreePixmap(dpy, pmap); + XMoveResizeWindow(dpy, win, mrect.x, mrect.y, + mrect.width, mrect.height); + XSync(dpy, False); + pmap = XCreatePixmap(dpy, win, mrect.width, mrect.height, + DefaultDepth(dpy, screen_num)); + XSync(dpy, False); + draw_menu(); + } + } else if (files[M_COMMAND] == f) { + update_items(files[M_COMMAND]->content); + draw_menu(); + } + check_event(0); +} + +static void +handle_before_read(IXPServer * s, File * f) +{ + char buf[64]; + if (f != files[M_SIZE]) + return; + snprintf(buf, sizeof(buf), "%d,%d,%d,%d", mrect.x, mrect.y, + mrect.width, mrect.height); + if (f->content) + free(f->content); + f->content = strdup(buf); + f->size = strlen(buf); +} + +static void +run(char *size) +{ + XSetWindowAttributes wa; + XGCValues gcv; + + /* init */ + if (!(files[M_CTL] = ixp_create(ixps, "/ctl"))) { + perror("wmimenu: cannot connect IXP server"); + exit(1); + } + files[M_CTL]->after_write = handle_after_write; + files[M_SIZE] = ixp_create(ixps, "/size"); + files[M_SIZE]->before_read = handle_before_read; + files[M_SIZE]->after_write = handle_after_write; + files[M_PRE_COMMAND] = ixp_create(ixps, "/precmd"); + files[M_COMMAND] = ixp_create(ixps, "/cmd"); + files[M_COMMAND]->after_write = handle_after_write; + files[M_HISTORY] = ixp_create(ixps, "/history"); + add_history(""); + files[M_LOOKUP] = ixp_create(ixps, "/lookup"); + files[M_TEXT_FONT] = wmii_create_ixpfile(ixps, "/style/text-font", BLITZ_FONT); + files[M_SELECTED_BG_COLOR] = wmii_create_ixpfile(ixps, "/sel-style/bg-color", BLITZ_SEL_BG_COLOR); + files[M_SELECTED_TEXT_COLOR] = wmii_create_ixpfile(ixps, "/sel-style/text-color", BLITZ_SEL_FG_COLOR); + files[M_SELECTED_BORDER_COLOR] = wmii_create_ixpfile(ixps, "/sel-style/border-color", BLITZ_SEL_BORDER_COLOR); + files[M_NORMAL_BG_COLOR] = wmii_create_ixpfile(ixps, "/norm-style/bg-color", BLITZ_NORM_BG_COLOR); + files[M_NORMAL_TEXT_COLOR] = wmii_create_ixpfile(ixps, "/norm-style/text-color", BLITZ_NORM_FG_COLOR); + files[M_NORMAL_BORDER_COLOR] = wmii_create_ixpfile(ixps, "/norm-style/border-color", BLITZ_NORM_BORDER_COLOR); + files[M_RETARDED] = ixp_create(ixps, "/retarded"); + + wa.override_redirect = 1; + wa.background_pixmap = ParentRelative; + wa.event_mask = ExposureMask | ButtonPressMask | KeyPressMask + | SubstructureRedirectMask | SubstructureNotifyMask; + + rect.x = rect.y = 0; + rect.width = DisplayWidth(dpy, screen_num); + rect.height = DisplayHeight(dpy, screen_num); + blitz_strtorect(dpy, &rect, &mrect, size); + if (!mrect.width) + mrect.width = DisplayWidth(dpy, screen_num); + if (!mrect.height) + mrect.height = 40; + + win = XCreateWindow(dpy, RootWindow(dpy, screen_num), mrect.x, mrect.y, + mrect.width, mrect.height, 0, DefaultDepth(dpy, + screen_num), + CopyFromParent, DefaultVisual(dpy, screen_num), + CWOverrideRedirect | CWBackPixmap | CWEventMask, + &wa); + XDefineCursor(dpy, win, XCreateFontCursor(dpy, XC_xterm)); + XSync(dpy, False); + + /* window pixmap */ + gcv.function = GXcopy; + gcv.graphics_exposures = False; + + gc = XCreateGC(dpy, win, 0, 0); + pmap = + XCreatePixmap(dpy, win, mrect.width, mrect.height, + DefaultDepth(dpy, screen_num)); + + /* main event loop */ + run_server_with_fd_support(ixps, ConnectionNumber(dpy), + check_event, 0); + deinit_server(ixps); + XFreePixmap(dpy, pmap); + XFreeGC(dpy, gc); + XCloseDisplay(dpy); +} + +int +main(int argc, char *argv[]) +{ + char size[64]; + int i; + + /* command line args */ + for (i = 1; (i < argc) && (argv[i][0] == '-'); i++) { + switch (argv[i][1]) { + case 'v': + fprintf(stdout, "%s", version[0]); + exit(0); + break; + case 's': + if (i + 1 < argc) + sockfile = argv[++i]; + else + usage(); + break; + default: + usage(); + break; + } + } + + dpy = XOpenDisplay(0); + if (!dpy) { + fprintf(stderr, "%s", "wmimenu: cannot open display\n"); + exit(1); + } + screen_num = DefaultScreen(dpy); + + size[0] = '\0'; + if (argc > i) + _strlcpy(size, argv[i], sizeof(size)); + + ixps = wmii_setup_server(sockfile); + items = 0; + + run(size); + + return 0; +} diff --git a/cmd/wmiplumb.c b/cmd/wmiplumb.c new file mode 100644 index 00000000..0e6a0dfc --- /dev/null +++ b/cmd/wmiplumb.c @@ -0,0 +1,95 @@ +/* + * (C)opyright MMIV-MMV Anselm R. Garbe + * See LICENSE file for license details. + */ + +#include +#include +#include + +#include +#include + +static char *version[] = { + "wmiplumb - window manager improved plumb - " VERSION "\n" + " (C)opyright MMIV-MMV Anselm R. Garbe\n", 0 +}; + +static void +usage() +{ + fprintf(stderr, "%s\n", + "usage: wmiplumb [-v]\n" " -v version info\n"); + exit(1); +} + +static void +print_sel(Display * dpy, Window w, XSelectionEvent * e) +{ + Atom typeret; + int format; + unsigned long nitems, bytesleft; + unsigned char *data; + + XGetWindowProperty(dpy, w, e->property, 0L, 4096L, False, + AnyPropertyType, &typeret, &format, + &nitems, &bytesleft, &data); + if (format == 8) { + int i; + for (i = 0; i < nitems; i++) + putchar(data[i]); + putchar('\n'); + } + XDeleteProperty(dpy, w, e->property); +} + +int +main(int argc, char **argv) +{ + Display *dpy; + Atom xa_clip_string; + Window w; + XEvent ev; + int pdone = 0; + + + /* command line args */ + if (argc > 1) { + if (!strncmp(argv[1], "-v", 3)) { + fprintf(stdout, "%s", version[0]); + exit(0); + } else + usage(); + } + dpy = XOpenDisplay(0); + if (!dpy) { + fprintf(stderr, "%s", "wmiplumb: cannot open display\n"); + exit(1); + } + xa_clip_string = XInternAtom(dpy, "PLUMB_STRING", False); + w = XCreateSimpleWindow(dpy, DefaultRootWindow(dpy), 10, 10, 200, 200, + 1, CopyFromParent, CopyFromParent); + while (1 && !pdone) { + XConvertSelection(dpy, XA_PRIMARY, XA_STRING, xa_clip_string, + w, CurrentTime); + XFlush(dpy); + XNextEvent(dpy, &ev); + switch (ev.type) { + case SelectionNotify: + if (ev.xselection.property != None) + print_sel(dpy, w, &ev.xselection); + else + putchar('\n'); + XDestroyWindow(dpy, w); + XCloseDisplay(dpy); + pdone = 1; + break; + default: + break; + } + } + return 0; + /* + * XDestroyWindow(dpy, w); XCloseDisplay(dpy); return 1; + */ +} diff --git a/cmd/wmir.1 b/cmd/wmir.1 new file mode 100644 index 00000000..d3bd4dc0 --- /dev/null +++ b/cmd/wmir.1 @@ -0,0 +1,71 @@ +.TH WMIR 1 wmii-3 +.SH NAME +wmir \- window manager improved 2 remote +.SH SYNOPSIS +.B wmir +.RB [ \-s +.IR socketfile ] +.I action +.I action_arg +[...] +.br +.B wmir +.RB [ \-s +.IR socketfile ] +.B \-f +.br +.B wmir +.B \-v +.SH DESCRIPTION +.SS Overview +.B wmir +is a client to access wmi* fileservers for the command line and +scripts. It can be used to configure wmii(1). +.SS Options +.TP +.BI \-s " socketfile" +lets you specify the socketfile to which +.B wmir +a connection will be established. If the environment variable +.B WMIR_SOCKET +is set and points to a socket file, wmir will use that file, if this +option is not supplied. +.TP +.B \-f +reads from stdin, useful for interactive wmir sessions or for +scripts that write/read a bunch of data, because this speeds things up +due to missing process creation/destruction, ie., a hack. +.TP +.B \-v +prints version information to stderr, then exits. +.TP +The syntax of the actions is as follows: +.TP +.B create [] +creates file +.TP +.B write +writes value to a file +.TP +.B read +reads file or directory contents +.TP +.B remove +removes file or directory tree +.SH ENVIRONMENT +.TP +WMIR_SOCKET +See above. +.SH EXAMPLES +.TP +$ wmir read / +This gives you an idea about what the wmii filesystem currently looks like. +.TP +$ wmir write /wm/ctl quit +.SH SEE ALSO +.BR wmibar (1), +.BR wmifs (1), +.BR wmii (1), +.BR wmiiwm (1), +.BR wmikeys (1), +.BR wmimenu (1) diff --git a/cmd/wmir.c b/cmd/wmir.c new file mode 100644 index 00000000..68a37fc5 --- /dev/null +++ b/cmd/wmir.c @@ -0,0 +1,228 @@ +/* + * (C)opyright MMIV-MMV Anselm R. Garbe + * See LICENSE file for license details. + */ + +#include +#include +#include + +#include "ixp.h" + +static IXPClient *c; +static int exit_code = 0; + +static char *version[] = { + "wmir - window manager improved remote - " VERSION "\n" + " (C)opyright MMIV-MMV Anselm R. Garbe\n", 0 +}; + +static void +usage() +{ + fprintf(stderr, "%s", + "usage: wmir [-s ] [-v] [...]\n" + " -s socket file (default: $WMIR_SOCKET)\n" + " -f read actions from stdin\n" + " -v version info\n" + "actions:\n" + " create [] -- creates and optionally writes content to a file\n" + " write -- writes content to a file\n" + " read -- reads file or directory contents\n" + " remove -- removes file or directory, use with care!\n"); + exit(1); +} + +static void +perform(char *action, char *file, char *content) +{ + size_t out_len = 0; + char output[2050]; + int crt, fd = -1; + + if (!action) + return; + + crt = !strncmp(action, "create", 7); + if (!strncmp(action, "write", 6) || crt) { + if (!file) + return; + /* create file first */ + if (crt) { + c->create(c, file); + if (c->errstr) { + fprintf(stderr, "wmir: error: create %s: %s\n", file, + c->errstr); + exit_code = 1; + return; + } + } + if (!content) + return; + fd = c->open(c, file); + if (c->errstr) { + fprintf(stderr, "wmir: error: open %s: %s\n", file, c->errstr); + exit_code = 1; + return; + } + c->write(c, fd, content, strlen(content)); + if (c->errstr) { + fprintf(stderr, "wmir: error: write %s: %s\n", file, + c->errstr); + exit_code = 1; + if (!strncmp(c->errstr, DEAD_SERVER, strlen(DEAD_SERVER) + 1)) + return; + } + } else if (!strncmp(action, "read", 5)) { + if (!file) + return; + fd = c->open(c, file); + if (c->errstr) { + fprintf(stderr, "wmir: error: open %s: %s\n", file, c->errstr); + exit_code = 1; + return; + } + do { + out_len = c->read(c, fd, output, 2048); + if (c->errstr) { + fprintf(stderr, "wmir: error: read %s: %s\n", file, + c->errstr); + exit_code = 1; + if (!strncmp + (c->errstr, DEAD_SERVER, strlen(DEAD_SERVER) + 1)) + return; + break; + } + output[out_len] = '\0'; + fprintf(stdout, "%s", output); + } + while (out_len == 2048); + fprintf(stdout, "%s", "\n"); + } else if (!strncmp(action, "remove", 7)) { + if (!file) + return; + c->remove(c, file); + if (c->errstr) { + fprintf(stderr, "wmir: error: remove %s: %s\n", file, + c->errstr); + exit_code = 1; + return; + } + } + if (fd != -1) { + c->close(c, fd); + if (c->errstr) { + fprintf(stderr, "wmir: error: close %s: %s\n", file, + c->errstr); + exit_code = 1; + return; + } + } +} + +int +main(int argc, char *argv[]) +{ + int i = 0, read_stdin = 0; + char line[4096], *p; + char *sockfile = getenv("WMIR_SOCKET"); + + /* command line args */ + if (argc > 1) { + for (i = 1; (i < argc) && (argv[i][0] == '-'); i++) { + switch (argv[i][1]) { + case 'v': + fprintf(stderr, "%s", version[0]); + exit(0); + break; + case 's': + if (i + 1 < argc) { + sockfile = argv[++i]; + } else { + usage(); + } + break; + case 'f': + read_stdin = 1; + break; + default: + usage(); + break; + } + } + } + if ((argc <= 1) || (!read_stdin && (i + 1) >= argc)) { + fprintf(stderr, "%s", "wmir: arguments: "); + for (i = 1; i < argc; i++) + fprintf(stderr, "%s, ", argv[i]); + fprintf(stderr, "%s", "\n"); + usage(); + } + if (!sockfile) { + fprintf(stderr, "%s", + "wmir: error: WMIR_SOCKET environment not set\n"); + usage(); + } + /* open socket */ + if (!(c = init_client(sockfile))) { + fprintf(stderr, "wmir: cannot connect to server '%s'\n", sockfile); + exit(1); + } + if (read_stdin) { + /* simple shell */ + char *action, *file, *content; + while (fgets(line, 4096, stdin)) { + p = line; + while (*p != '\0' && (*p == ' ' || *p == '\t')) + p++; + if (*p == '\0') + continue; /* empty line */ + if (strncmp(p, "create ", 7) && + strncmp(p, "write ", 6) && + strncmp(p, "read ", 5) && strncmp(p, "remove ", 7)) + continue; + + action = p; + while (*p != '\0' && *p != ' ' && *p != '\t' && *p != '\n') + p++; + if (*p == '\0' || *p == '\n') + continue; /* ignore bogus command */ + *p = '\0'; + p++; + while (*p != '\0' && (*p == ' ' || *p == '\t')) + p++; + if (*p == '\0') + continue; /* ignore bogus command */ + file = p; + while (*p != '\0' && *p != ' ' && *p != '\t' && *p != '\n') + p++; + if (*p == '\0' || *p == '\n') { + content = 0; + *p = '\0'; + } else { + *p = '\0'; + p++; + content = p; + } + if (file[0] == '\0') + continue; + if (content) { + static size_t len; + if ((len = strlen(content))) + content[len - 1] = '\0'; + } + perform(action, file, content); + if (c->errstr) + fprintf(stderr, "wmir: error: read %s: %s\n", file, + c->errstr); + } + } else { + perform(argv[i], argv[i + 1], (i + 2) < argc ? argv[i + 2] : 0); + } + + if (c->errstr) { + deinit_client(c); + exit_code = 1; + } + return exit_code; +} diff --git a/cmd/wmir2.c b/cmd/wmir2.c new file mode 100644 index 00000000..103bac7f --- /dev/null +++ b/cmd/wmir2.c @@ -0,0 +1,252 @@ +/* + * (C)opyright MMIV-MMV Anselm R. Garbe + * See LICENSE file for license details. + */ + +#include +#include +#include + +#include "../libixp2/ixp.h" + +#include + +typedef struct { + char *name; + int (*cmd) (char **argv); + int min_argc; +} Command; + +static int xcreate(char **argv); +static int xread(char **argv); +static int xwrite(char **argv); +static int xremove(char **argv); +static Command cmds[] = { + {"create", xcreate, 2}, + {"read", xread, 1}, + {"write", xwrite, 2}, + {"remove", xremove, 1}, + {0, 0} +}; +static IXPClient c = {0}; + +static char *version[] = { + "wmir - window manager improved remote - " VERSION "\n" + " (C)opyright MMIV-MMV Anselm R. Garbe\n", 0 +}; + +static void +usage() +{ + fprintf(stderr, "%s", + "usage: wmir [-s ] [-v] [...]\n" + " -s socket file (default: $WMIR_SOCKET)\n" + " -f read commands from stdin\n" + " -v version info\n" + "commands:\n" + " create -- creates file\n" + " read -- reads file/directory contents\n" + " write -- writes content to a file\n" + " remove -- removes file\n"); + exit(1); +} + +static u32 +write_data(u32 fid, u8 * data, u32 count) +{ + u32 len, i, runs = count / c.fcall.iounit; + + for (i = 0; i <= runs; i++) { + /* write */ + len = count - (i * c.fcall.iounit); + if (len > c.fcall.iounit) + len = c.fcall.iounit; + if (ixp_client_write + (&c, fid, i * c.fcall.iounit, len, + &data[i * c.fcall.iounit]) != count) { + fprintf(stderr, "wmir: cannot write file: %s\n", c.errstr); + return 0; + } + } + return count; +} + +static int +xcreate(char **argv) +{ + u32 fid; + char *p = strrchr(argv[0], '/'); + + fid = c.root_fid << 2; + /* walk to bottom-most directory */ + *p = 0; + if (!ixp_client_walk(&c, fid, argv[0])) { + fprintf(stderr, "wmir: cannot walk to %s: %s\n", argv[0], + c.errstr); + return 1; + } + /* create */ + p++; + if (!ixp_client_create(&c, fid, p, (u32) 0xff, IXP_OWRITE)) { + fprintf(stderr, "wmir: cannot create file: %s\n", c.errstr); + return 1; + } + write_data(fid, (u8 *) argv[1], strlen(argv[1])); + return !ixp_client_close(&c, fid); +} + +static int +xwrite(char **argv) +{ + /* open */ + u32 fid = c.root_fid << 2; + if (!ixp_client_open(&c, fid, argv[0], IXP_OWRITE)) { + fprintf(stderr, "wmir: cannot open file: %s\n", c.errstr); + return 1; + } + write_data(fid, (u8 *) argv[1], strlen(argv[1])); + return !ixp_client_close(&c, fid); +} + +static void +print_directory(void *result, u32 msize) +{ + void *p = result; + static Stat stat, zerostat = {0}; + u32 len = 0; + do { + p = ixp_dec_stat(p, &stat); + len += stat.size + sizeof(u16); + if (stat.qid.type == IXP_QTDIR) + fprintf(stdout, "%s/\n", stat.name); + else + fprintf(stdout, "%s\n", stat.name); + stat = zerostat; + } + while (len < msize); +} + +static int +xread(char **argv) +{ + u32 count, fid = c.root_fid << 2; + int is_directory = FALSE; + static u8 result[IXP_MAX_MSG]; + + /* open */ + if (!ixp_client_open(&c, fid, argv[0], IXP_OREAD)) { + fprintf(stderr, "wmir: cannot open file: %s\n", c.errstr); + return 1; + } + is_directory = !c.fcall.nwqid || (c.fcall.qid.type == IXP_QTDIR); + /* read */ + if (!(count = ixp_client_read(&c, fid, 0, result, IXP_MAX_MSG)) + && c.errstr) { + fprintf(stderr, "wmir: cannot read file: %s\n", c.errstr); + return 1; + } + if (count) { + if (is_directory) + print_directory(result, count); + else { + u32 i; + for (i = 0; i < count; i++) + putchar(result[i]); + } + } + return !ixp_client_close(&c, fid); +} + +static int +xremove(char **argv) +{ + u32 fid; + + /* remove */ + fid = c.root_fid << 2; + if (!ixp_client_remove(&c, fid, argv[0])) { + fprintf(stderr, "wmir: cannot remove file: %s\n", c.errstr); + return 1; + } + return 0; +} + +static int +perform_cmd(int argc, char **argv) +{ + int i; + for (i = 0; cmds[i].name; i++) + if (!strncmp(cmds[i].name, argv[0], strlen(cmds[i].name))) { + if (cmds[i].min_argc <= argc) + return cmds[i].cmd(&argv[1]); + else + usage(); + } + /* bogus command */ + return 1; +} + +int +main(int argc, char *argv[]) +{ + int i = 0, ret, read_stdin = 0; + char line[4096]; + char *sockfile = getenv("WMIR_SOCKET"); + + /* command line args */ + if (argc > 1) { + for (i = 1; (i < argc) && (argv[i][0] == '-'); i++) { + switch (argv[i][1]) { + case 'v': + fprintf(stderr, "%s", version[0]); + exit(0); + break; + case 's': + if (i + 1 < argc) + sockfile = argv[++i]; + else + usage(); + break; + case 'f': + read_stdin = 1; + break; + default: + usage(); + break; + } + } + } + if ((argc <= 1) || (!read_stdin && (i + 1) >= argc)) { + fprintf(stderr, "%s", "wmir: arguments: "); + for (i = 1; i < argc; i++) + fprintf(stderr, "%s, ", argv[i]); + fprintf(stderr, "%s", "\n"); + usage(); + } + if (!sockfile) { + fprintf(stderr, "%s", + "wmir: error: WMIR_SOCKET environment not set\n"); + usage(); + } + /* open socket */ + if (!ixp_client_init(&c, sockfile)) { + fprintf(stderr, "wmir: %s\n", c.errstr); + exit(1); + } + /* wether perform directly or read from stdin */ + if (read_stdin) { + char *_argv[3]; + int _argc; + while (fgets(line, 4096, stdin)) + if ((_argc = tokenize(_argv, 3, line, ' '))) { + if ((ret = perform_cmd(_argc, _argv))) + break; + } + } else + ret = perform_cmd(argc - (i + 1), &argv[i]); + + /* close socket */ + ixp_client_deinit(&c); + + return ret; +} diff --git a/cmd/wmiwarp.c b/cmd/wmiwarp.c new file mode 100644 index 00000000..46acaac0 --- /dev/null +++ b/cmd/wmiwarp.c @@ -0,0 +1,56 @@ +/* + * (C)opyright MMIV-MMV Anselm R. Garbe + * See LICENSE file for license details. + */ + +#include +#include +#include + +#include +#include + +static char *version[] = { + "wmiwarp - window manager improved warp - " VERSION "\n" + " (C)opyright MMIV-MMV Anselm R. Garbe\n", 0 +}; + +static void +usage() +{ + fprintf(stderr, + "usage: wmiwarp [-v] ,\n" " -v version info\n"); + exit(1); +} + +int +main(int argc, char **argv) +{ + Display *dpy; + int x, y; + + /* command line args */ + if (argc != 2) + usage(); + if (!strncmp(argv[1], "-v", 2)) { + fprintf(stdout, "%s", version[0]); + exit(0); + } + dpy = XOpenDisplay(0); + if (!dpy) { + fprintf(stderr, "%s", "wmiwarp: cannot open display\n"); + exit(1); + } + if (!strncmp(argv[1], "center", 7)) { + x = DisplayWidth(dpy, DefaultScreen(dpy)) / 2; + y = DisplayHeight(dpy, DefaultScreen(dpy)) / 2; + } else if (sscanf(argv[1], "%d,%d", &x, &y) != 2) { + XCloseDisplay(dpy); + usage(); + } + XWarpPointer(dpy, None, RootWindow(dpy, DefaultScreen(dpy)), + 0, 0, 0, 0, x, y); + XSetInputFocus(dpy, PointerRoot, RevertToPointerRoot, CurrentTime); + XCloseDisplay(dpy); + return 0; +} diff --git a/config.mk b/config.mk new file mode 100644 index 00000000..e13df509 --- /dev/null +++ b/config.mk @@ -0,0 +1,28 @@ +# Customize to fit your system + +# paths +PREFIX = /usr/local +CONFPREFIX = ${PREFIX}/etc +MANPREFIX = ${PREFIX}/share/man +9PREFIX = ${PREFIX}/9 + +INCDIR = ${PREFIX}/include +LIBDIR = ${PREFIX}/lib +X11INC = /usr/X11R6/include +X11LIB = /usr/X11R6/lib + +# includes and libs +INCLUDES = -I. -I${INCDIR} -I/usr/include -I${X11INC} +LIBS = -L${LIBDIR} -L/usr/lib -lc -lm -L${X11LIB} -lX11 +VERSION = 3-current + +# flags +CFLAGS = -O0 -g -Wall ${INCLUDES} -DVERSION=\"${VERSION}\" +LDFLAGS = -g ${LIBS} + +# compiler +# Note: - under Solaris add -D__EXTENSIONS__ to CFLAGS +MAKE = make +AR = ar cr +CC = cc +RANLIB = ranlib diff --git a/doc/NOTES b/doc/NOTES new file mode 100644 index 00000000..661ea615 --- /dev/null +++ b/doc/NOTES @@ -0,0 +1,198 @@ +Development notes of wmii +=============================================== +Last change: 2005-06-29, garbeam + + +Audience +-------- +This document is intended to describe the architectural decisions and +concepts of the wmii development. It is primary written for developers. + + +Motivation +---------- +There're several reasons for rewriting wmi: + +1. Monolithic object-oriented design +wmi consisted of a single binary which included all components we know, +the window manager itself, three different bars, a shortcut box and the +slot. Monolithic designs provide some advantages (no shared memory +needed between the different components, special optimizations possible), +but they lead to high coupling (code overhead) and reduce maintainability. + +2. The XTextProperty based remote interface. +Originally the idea of wmiremote was derived from larswm and is based on +XTextProperties, which suffer from reliability and correct order under +high system load (maybe due to bugs in X itself). But for the context of +wmi they just work and do their job fine. However, a guy called Uriel +came up one day in #wmi IRC channel and asked to generalize the +interface and change its property-based character to a file input/output +interface similar to the 9p protocol of plan9. +In long discussions we agreed and tried a prototypical implementation in +wmi, which can be found between revisions 640-735 of the wmi Subversion +repository. + +3. The wmi configuration interface lacked on-the-fly changes +If a user wanted to change a setting, he had to restart wmi. As +workaround, which produced much complexity, some convenience actions like +create-workspace or bind-shortcut have been implemented, that the user +was able to change a subset of often needed things on-the-fly. Anyway, this +sucked a lot and made wmi less attractive. Within the above revisions we +also implemented prototypical support for on-the-fly changes in wmi. + +4. Software law +However the prototypical implementation was very time-consuming without +stable and usable results (in wmi Subversion repository they can be +found in trunk/current/wmi). Thus we realized, that wmi itself was +a prototype only around end of September 2004. Originally the +prototypical changes between revisions 640-735 were intended to be used +in wmi-10, but they changed things to deep. Thus wmii was born. + + +Architecture +------------ +To solve the above issues found in wmi are the requirements for wmii: + +1. Modularized design +wmii separates all different components into different binaries. We +evaluated the dlopen() mechanism to load modules dynamically (like ion +does), but skipped that for stability reasons (one single invalid memory +access would crash the whole application in dlopen() world). +To share code between components we separated also two libraries, +which are initially written for wmii, and most wmii components link +statically against them. +The components and libraries of wmii look as follows: + + * libixp (remote interface library) + * liblitz (non-wimp GUI-toolkit library) + * wmii (core WM) + * wmibar (a generic bar) + * wmiinput (input bar) + * wmir (remote interface client) + * wmikeys (shortcut handler) + +The modularization has been chosen by two different point of views: +a) The separation of independent tasks, which means that a pager or +input bar should be independent from a window manager itself. +b) The separation of technically independent stuff, e.g. managing windows +is completely independent from shortcut handling - in X world at least. + +The libixp is described below. + +The liblitz contains primary all drawing code, which is used by +all separated components, and provides some util functions. +If you implement a function within a wmii component, first ask yourself, +if that function would make sense also in other components, if so, +export it to liblitz, the non-wimp GUI toolkit. + +2. Remote interface +The remote interface is implemented with the new libixp library, which +is independent from all other parts of wmii and provides a client/server +API for a userland memory filesystem with a similar approach as 9p +server- and clients of the plan9 operating system. +An IXP server provides and manages a memory file system which is accessed +concurrently by several clients over a UNIX socket file. All above wmii +components, except wmir, are IXP servers. Since the IXP server +dispatches its connections with a select() based loop, it also +represents the X event loop for above components, because the +ConnectionNumber(X11) is the file descriptor of a connection to the X +server. If requested, the IXP server also selects() for file descriptors +provided by the server implementing developer and runs appropriate read() +and write() callbacks. In above components we need only a read() +callback for the ConnectionNumber, because the event handling of X is +single-directional and awakes if X writes data to that file descriptor. +The file system what the server manages, is capable to manage arbitrary +length paths, infinite files (as long as memory is available), and byte +content of files through following functions: + + * create (creates file/path) + * remove (removes file/path) + * open (opens file) + * read (reads opened file) + * write (writes opened file) + * close (closes opened file) + +Processes which implement such a file server can access the memory file +system explicitly, which is done by all above components for +performance reasons. Because the server handles all accesses +sequentially there is no race condition. + +An IXP client, like wmir, accesses an IXP server remotely to perform +above functions. The wmir of wmii is not allowed to create or to remove +files/paths, because all components of wmii handle file creation and +destruction internally, because they are IXP servers and can do so. +But that is only a special case. In general each client is capable to +request the server to create or remove files. +Not allowing all IXP functions to wmir is easy to understand, because +the problematic of accidentally removals of important files, +which guarantees the functionality of wmii components, is prevented. +If for example an IXP client would request to remove / in wmii, wmii +would crash immediately, because it won't be able to read its needed +files. + +As you might notice, libixp provides accessing shared memory through a +decent file input/output based interface between an IXP server and IXP +clients, which is used for configuration and scripting. To get further +details about IXP, start with libixp/libixp.h. + +3. Configuration +The above described remote interface is used for configuration. The idea +is very similar to the procfs of Linux and other Unices which is +accessed to configure the kernel. The above IXP servers provide following +special file systems which are used for configuration and can be +explored with: + +wmir -s $HOME/.wmii/ixp/ read + +e.g. + +wmir -s $HOME/.wmii/ixp/wmii-:0 read / + +Reading a path which is no file, returns the contents of a directory. In +IXP there's no differentiation between files and directories. Whether a +file contains content (byte data) or it contains files (you can compare +the term 'file' in IXP to inodes in UNIX world). + +Note: By default all wmi components use a socket file in +$HOME/.wmii/ixp/-$DISPLAY. That arose some issues with the +$DISPLAY variable, because it is on X startup mostly set to ':0' +(':'), but if wmii runs and you invoke terms it is set +to the screen which is focused (important for multihead configurations), +that means that a ':0.' is appended. Thus reading simply +using $HOME/.wmii/ixp/-$DISPLAY might fail, because of the +appended '.' - keep that in mind! + +Each server developer decides by itself how its file system should look +like to configure it. A very basic configuration interface filesystem +can be found in wmipager, the most complex one can be found in wmii. + +4. Software law +Yes, wmi was implemented with C++, but wmii is implemented with C. Most +people are surprised about that fact. But there're several reasons for +this. Most are rarely rational, but there're arguable ones: + + * C code compiles faster + * all dependencies (Xlib, libc, ...) are implemented with C and + don't provide an object oriented API + * C code produces smaller binaries + +One design goal of wmii is to win the challenge of simplicity, which +means that our upper boundary of code size is 10.000 SLOC (including +above components, libwmii and libixp). + + +Style guide +----------- +We use the traditional Unix-style from Bell Labs in the source code. +We only comment things which are hard to understand or not obvious. + +Technical Details +----------------- +The window management of wmii conforms to the ICCCM specification, +although client supplied icons and iconified window states are not +handled for simplicity reasons and because they don't fit well with the +tiled window management. +Apart from this, wmii conforms partly to the EWMH specification to fit +well with the requirements of more recent applications in the area of +the KDE and Gnome desktop, although to fulfill these hints are no primary +target of wmii's window management capabilities. diff --git a/doc/RELEASE b/doc/RELEASE new file mode 100644 index 00000000..5ff7a13c --- /dev/null +++ b/doc/RELEASE @@ -0,0 +1,45 @@ +Abbadon +Amare +Andariel +Andon +ArchDean +Beth +Bethanel +Boyd * (2) +Camael +Charon +Duma +Djibril +Elerial +Foucault +Frankie +Gabriel +Haniel +Israfiel +Izra'il +Japhkiel +Johab +Loki +Lucifer +Lycurgus +Munkar +Michael +Mikaal +Metathron +Melody +Naarai +Nakir +Raphael +Raziel +Satan +Sandalphon +Sophia +Sunya +Tini * (3) +Thanatos +Tothiel +Ultimus Maximus +Uriel * (1) +Yhprum +Zaphkiel +Zadkiel * (1.1) diff --git a/doc/wmii.svg b/doc/wmii.svg new file mode 100644 index 00000000..5b966903 --- /dev/null +++ b/doc/wmii.svg @@ -0,0 +1,61 @@ + + + + + + + image/svg+xml + + + + + + + + + + diff --git a/doc/wmii.tex b/doc/wmii.tex new file mode 100644 index 00000000..91b60ed4 --- /dev/null +++ b/doc/wmii.tex @@ -0,0 +1,83 @@ +% (C)opyright 2005 by Anselm R. Garbe +\documentclass{article} \usepackage{times} \begin{document} + +\title{Improved GUI concepts for experienced users} + +\author{Anselm R. Garbe\\ \small garbeam at gmail dot com} + +\maketitle \thispagestyle{empty} + +\begin{abstract} +This article presents the motivation and concepts of the dynamic +window manager wmii and the graphical toolkit liblitz for the +\it{X Window System}. +\end{abstract} + + +%------------------------------------------------------------------------- +\section{Motivation} + +Most common graphical user interfaces are designed after the WIMP\cite{wimp} +paradigm, which has dominated the desktop environment +landscape since late 1980s. While research has been done on alternative +user interfaces, often the focus targeted more in ease of use and low +learning curves for new computer users rather than in efficiency and +power of abstraction. + +The main reason has been the economical success of computers +in the normal consumer market, which consists of unexperienced users mainly. +Our motivation is to change this situation and to provide a graphical +user interface for experienced users, though we know that this market is +a niche. + +There has been done rarly research in the non-wimp GUI landscape for years. +Back in 80s and early 90s there has been some research in +this area for the Plan 9\cite{plan9} operating system at Bell Labs. +Most recent research has been done by individuals only, like Tuomo Valkonen with +his Ion\cite{ion} project and Lars Bernhardsson with his +LarsWM\cite{larswm} project. + +The approaches found in the Plan 9 operating system are interesting, because +on the one hand they cancelled the Unix tradition to work in Teletype emulators +and on the other hand, they didn't followed the WIMP paradigm propagated by Apple, +IBM or Microsoft. This makes Plan 9 the most unique approach compared to the +classical WIMP world. + +The main aspects of an improved GUI consists of two things, a powerful +window management approach and a sane and simple widget set which fits well +into this window management approach. + +In the area of improved window management concepts there has been done more +research, thus there appeared several different approaches. But the area +of improved widget sets which form powerful applications with a simple widget +set has been ignored for long time. Instead, the WIMP world introduced many +widgets which seem to focus on eye-candy, like progress bars, but don't +fix the essential problems with WIMP toolkits. + + + +\section{Future} + + +\section*{Acknowlegdements} Following people provided useful feedback or several +grammar fixes to this article: +\begin{itemize} +\item Frank Boehme (1st version of this article) +\item Tuncer M zayamut Ayaz (1st version of this article +\end{itemize} + +\begin{thebibliography}{99} +\bibitem{wimp} Ashley George Taylor, WIMP Interfaces, CS6751 Topic Report: Winter '97 +\bibitem{x} X Window System - http://www.freedesktop.org +\bibitem{plan9} plan9 operating system - http://cm.bell-labs.com/plan9dist/ +\bibitem{acme} Rob Pike, Acme: A User Interface for Programmers - +http://www.cs.bell-labs.com/sys/doc/acme/acme.html +\bibitem{rat} Ratpoison window manager - http://www.nongnu.org/ratpoison/ +\bibitem{evil} evilwm window manager - http://evilwm.sf.net +\bibitem{ion} Ion window manager - http://modeemi.cs.tut.fi/~tuomov/ion/ +\bibitem{larswm} LarsWM window manager - http://www.fnurt.net/larswm/ +\bibitem{vi} Vi Improved (VIM) - http://www.vim.org +\bibitem{9p} 9P protocol - http://www.cs.bell-labs.com/sys/man/5/INDEX.html +\end{thebibliography} + +\end{document} diff --git a/extra/README b/extra/README new file mode 100644 index 00000000..05fe6e8c --- /dev/null +++ b/extra/README @@ -0,0 +1,8 @@ +Scripts in this directory have been contributed by various different +people. For License information look into the source code of the +appropriate script. + + +Name: pkeys +Purpose: Print the current key bindings. +Requirements: 9base diff --git a/extra/pkeys b/extra/pkeys new file mode 100644 index 00000000..d18f6722 --- /dev/null +++ b/extra/pkeys @@ -0,0 +1,6 @@ +#!/usr/local/9/bin/rc + +mdir=/keys/mode/ +for (mode in `{wmir read $mdir}) + for (key in `{wmir read $mdir$mode}) + echo $mode$key '->' `{wmir read $mdir$mode$key} diff --git a/libcext/Makefile b/libcext/Makefile new file mode 100644 index 00000000..80f8c1b3 --- /dev/null +++ b/libcext/Makefile @@ -0,0 +1,25 @@ +# libcext - c extensions for wmii project +# (C)opyright MMIV-MMV Anselm R. Garbe + +include ../config.mk + +SRC = array.c emalloc.c estrdup.c strlcat.c strlcpy.c strtonum.c tokenize.c + +OBJ = ${SRC:.c=.o} + +.PREFIXES = .c .o + +all: libcext + @echo built libcext + +.c.o: + @echo CC $< + @${CC} -c ${CFLAGS} $< + +libcext: ${OBJ} + @echo AR $@.a + @${AR} $@.a ${OBJ} + @${RANLIB} $@.a + +clean: + rm -f libcext.a *.o diff --git a/libcext/array.c b/libcext/array.c new file mode 100644 index 00000000..9ac97e9b --- /dev/null +++ b/libcext/array.c @@ -0,0 +1,98 @@ +/* + * (C)opyright MMIV-MMV Anselm R. Garbe + * See LICENSE file for license details. + */ + +#include + +#include "cext.h" + +void ** +attach_item_begin(void **old, void *item, size_t size_item) +{ + int i, size_old; + void **result = 0; + for (size_old = 0; old && old[size_old]; size_old++); + result = emalloc(size_item * (size_old + 2)); + result[0] = item; + for (i = 0; old && old[i]; i++) + result[i + 1] = old[i]; + result[i + 1] = 0; + if (old) + free(old); + return result; +} + +void ** +attach_item_end(void **old, void *item, size_t size_item) +{ + int i, size_old; + void **result = 0; + for (size_old = 0; old && old[size_old]; size_old++); + result = emalloc(size_item * (size_old + 2)); + for (i = 0; old && old[i]; i++) + result[i] = old[i]; + result[i++] = item; + result[i] = 0; + if (old) + free(old); + return result; +} + +void ** +detach_item(void **old, void *item, size_t size_item) +{ + int size_old, i, j = 0; + void **result = 0; + for (size_old = 0; old && old[size_old]; size_old++); + if (size_old != 1) { + result = emalloc(size_item * size_old); + for (i = 0; old[i]; i++) + if (old[i] != item) + result[j++] = old[i]; + result[j] = 0; + } + if (old) + free(old); + return result; +} + +int +index_item(void **items, void *item) +{ + int i = 0; + for (i = 0; items && items[i] && (items[i] != item); i++); + return items[i] ? i : -1; +} + +int +count_items(void **items) +{ + int i; + for (i = 0; items && items[i]; i++); + return i; +} + +int +index_next_item(void **items, void *item) +{ + int idx = index_item(items, item); + if (idx == -1) + return idx; + if (idx == count_items(items) - 1) + return 0; + else + return idx + 1; +} + +int +index_prev_item(void **items, void *item) +{ + int idx = index_item(items, item); + if (idx == -1) + return idx; + if (idx == 0) + return count_items(items) - 1; + else + return idx - 1; +} diff --git a/libcext/cext.h b/libcext/cext.h new file mode 100644 index 00000000..d05261e7 --- /dev/null +++ b/libcext/cext.h @@ -0,0 +1,45 @@ +/* + * (C)opyright MMIV-MMV Anselm R. Garbe + * See LICENSE file for license details. + */ + +#include + +#ifndef nil +#define nil (void *)0 +#endif +#ifndef FALSE +#define FALSE 0 +#endif +#ifndef TRUE +#define TRUE 1 +#endif + +/* array.c */ +void **attach_item_end(void **old, void *item, size_t size_item); +void **attach_item_begin(void **old, void *item, size_t size_item); +void **detach_item(void **old, void *item, size_t size_item); +int index_item(void **items, void *item); +int index_next_item(void **items, void *item); +int index_prev_item(void **items, void *item); +int count_items(void **items); + +/* emalloc.c */ +void *emalloc(size_t size); + +/* estrdup.c */ +char *estrdup(const char *s); + +/* strlcat.c */ +size_t _strlcat(char *dst, const char *src, size_t siz); + +/* strlcpy.c */ +size_t _strlcpy(char *dst, const char *src, size_t siz); + +/* strtonum.c */ +long long +__strtonum(const char *numstr, long long minval, + long long maxval, const char **errstrp); + +/* tokenize.c */ +size_t tokenize(char **result, size_t reslen, char *str, char delim); diff --git a/libcext/emalloc.c b/libcext/emalloc.c new file mode 100644 index 00000000..860379dd --- /dev/null +++ b/libcext/emalloc.c @@ -0,0 +1,22 @@ +/* + * (C)opyright MMIV-MMV Anselm R. Garbe + * See LICENSE file for license details. + */ + +#include +#include + +#include "cext.h" + +void * +emalloc(size_t size) +{ + void *res = malloc(size); + + if (!res) { + fprintf(stderr, "fatal: could not malloc() %d bytes\n", + (int) size); + exit(1); + } + return res; +} diff --git a/libcext/estrdup.c b/libcext/estrdup.c new file mode 100644 index 00000000..4d1c00d7 --- /dev/null +++ b/libcext/estrdup.c @@ -0,0 +1,21 @@ +/* + * (C)opyright MMIV-MMV Anselm R. Garbe + * See LICENSE file for license details. + */ + +#include +#include +#include + +#include "cext.h" + +char * +estrdup(const char *s) +{ + char *tmp; + + tmp = (char *) emalloc(strlen(s) + 1); + strcpy(tmp, (char *) s); + + return tmp; +} diff --git a/libcext/strlcat.c b/libcext/strlcat.c new file mode 100644 index 00000000..8605dcb8 --- /dev/null +++ b/libcext/strlcat.c @@ -0,0 +1,55 @@ +/* + * Copyright (c) 1998 Todd C. Miller + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#include +#include + +#include "cext.h" + +/* + * Appends src to string dst of size siz (unlike strncat, siz is the + * full size of dst, not space left). At most siz-1 characters + * will be copied. Always NUL terminates (unless siz <= strlen(dst)). + * Returns strlen(src) + MIN(siz, strlen(initial dst)). + * If retval >= siz, truncation occurred. + */ +size_t +_strlcat(char *dst, const char *src, size_t siz) +{ + register char *d = dst; + register const char *s = src; + register size_t n = siz; + size_t dlen; + + /* Find the end of dst and adjust bytes left but don't go past end */ + while (n-- != 0 && *d != '\0') + d++; + dlen = d - dst; + n = siz - dlen; + + if (n == 0) + return (dlen + strlen(s)); + while (*s != '\0') { + if (n != 1) { + *d++ = *s; + n--; + } + s++; + } + *d = '\0'; + + return (dlen + (s - src)); /* count does not include NUL */ +} diff --git a/libcext/strlcpy.c b/libcext/strlcpy.c new file mode 100644 index 00000000..c27417b6 --- /dev/null +++ b/libcext/strlcpy.c @@ -0,0 +1,47 @@ +/* + * Copyright (c) 1998 Todd C. Miller + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#include + +#include "cext.h" + +/* + * Copy src to string dst of size siz. At most siz-1 characters + * will be copied. Always NUL terminates (unless siz == 0). + * Returns strlen(src); if retval >= siz, truncation occurred. + */ +size_t +_strlcpy(char *dst, const char *src, size_t siz) +{ + register char *d = dst; + register const char *s = src; + register size_t n = siz; + + /* Copy as many bytes as will fit */ + if (n != 0 && --n != 0) { + do { + if ((*d++ = *s++) == 0) + break; + } while (--n != 0); + } + /* Not enough room in dst, add NUL and traverse rest of src */ + if (n == 0) { + if (siz != 0) + *d = '\0'; /* NUL-terminate dst */ + while (*s++); + } + return (s - src - 1); /* count does not include NUL */ +} diff --git a/libcext/strtonum.c b/libcext/strtonum.c new file mode 100644 index 00000000..2069edb8 --- /dev/null +++ b/libcext/strtonum.c @@ -0,0 +1,68 @@ +/* + * Copyright (c) 2004 Ted Unangst and Todd Miller + * All rights reserved. + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#include +#include +#include + +#define INVALID 1 +#define TOOSMALL 2 +#define TOOLARGE 3 + +long long +__strtonum(const char *numstr, long long minval, long long maxval, + const char **errstrp) +{ + long long ll = 0; + char *ep; + int error = 0; + struct errval { + const char *errstr; + int err; + } ev[4] = { + { + NULL, 0 + }, { + "invalid", EINVAL + }, { + "too small", ERANGE + }, { + "too large", ERANGE + }, + }; + + ev[0].err = errno; + errno = 0; + if (minval > maxval) + error = INVALID; + else { + ll = strtoll(numstr, &ep, 10); + if (numstr == ep || *ep != '\0') + error = INVALID; + else if (errno == ERANGE || ll < minval) + error = TOOSMALL; + else if (errno == ERANGE || ll > maxval) + error = TOOLARGE; + } + if (errstrp != NULL) + *errstrp = ev[error].errstr; + errno = ev[error].err; + if (error) + ll = 0; + + return (ll); +} diff --git a/libcext/tokenize.c b/libcext/tokenize.c new file mode 100644 index 00000000..80160383 --- /dev/null +++ b/libcext/tokenize.c @@ -0,0 +1,34 @@ +/* + * (C)opyright MMIV-MMV Anselm R. Garbe + * See LICENSE file for license details. + */ + +#include + +#include "cext.h" + +size_t +tokenize(char **result, size_t reslen, char *str, char delim) +{ + char *p, *n; + size_t i = 0; + + if (!str) + return 0; + for (n = str; *n == ' '; n++); + p = n; + for (i = 0; *n != '\0';) { + if (i == reslen) + return i; + if (*n == delim) { + *n = '\0'; + if (strlen(p)) + result[i++] = p; + p = ++n; + } else + n++; + } + if (strlen(p)) + result[i++] = p; + return i; /* number of tokens */ +} diff --git a/libixp/Makefile b/libixp/Makefile new file mode 100644 index 00000000..2c1aca81 --- /dev/null +++ b/libixp/Makefile @@ -0,0 +1,26 @@ +# libixp - lib ixp protocol +# (C)opyright MMIV-MMV Anselm R. Garbe + +include ../config.mk + +CFLAGS += -I ../libcext +LDFLAGS += -L../libcext -lcext + +SRC = client.c message.c ramfs.c server.c + +OBJ = ${SRC:.c=.o} + +all: libixp + @echo built libixp + +.c.o: + @echo CC $< + @${CC} -c ${CFLAGS} $< + +libixp: ${OBJ} + @echo AR $@.a + @${AR} $@.a ${OBJ} + @${RANLIB} $@.a + +clean: + rm -f libixp.a *.o diff --git a/libixp/client.c b/libixp/client.c new file mode 100644 index 00000000..8ccd10a3 --- /dev/null +++ b/libixp/client.c @@ -0,0 +1,302 @@ +/* + * (C)opyright MMIV-MMV Anselm R. Garbe + * See LICENSE file for license details. + */ + +#include +#include +#include +#include +#include +#include +#include +#include + +#include "ixp.h" + +#include + +static IXPClient zero_client = {0}; +static size_t offsets[MAX_CONN * MAX_OPEN_FILES][2]; /* set of possible fd's */ + +static void +check_error(IXPClient * c, void *msg) +{ + ResHeader h; + + if (c->errstr) + free(c->errstr); + c->errstr = 0; + + memcpy(&h, msg, sizeof(ResHeader)); + if (h.res == RERROR) + c->errstr = strdup((char *) msg + sizeof(ResHeader)); +} + +static void +handle_dead_server(IXPClient * c) +{ + if (c->errstr) + free(c->errstr); + c->errstr = strdup(DEAD_SERVER); + if (c->fd) { + shutdown(c->fd, SHUT_RDWR); + close(c->fd); + } + c->fd = -1; +} + +static void * +poll_server(IXPClient * c, void *request, size_t req_len, + size_t * out_len) +{ + size_t num = 0; + void *result = 0; + int r, header = 0; + + if (c->errstr) + free(c->errstr); + c->errstr = 0; + + /* first send request */ + while (num < req_len) { + FD_ZERO(&c->wr); + FD_SET(c->fd, &c->wr); + r = select(c->fd + 1, 0, &c->wr, 0, 0); + if (r == -1 && errno == EINTR) + continue; + if (r < 0) { + perror("ixp: client: select"); + if (result) + free(result); + handle_dead_server(c); + return 0; + } else if (r > 0) { + if (!header) { + /* write header first */ + r = write(c->fd, &req_len, sizeof(size_t)); + if (r != sizeof(size_t)) { + if (result) + free(result); + handle_dead_server(c); + return 0; + } + header = 1; + } + r = write(c->fd, ((char *) request) + num, req_len - num); + if (r < 1) { + perror("ixp: client: write"); + if (result) + free(result); + handle_dead_server(c); + return 0; + } + num += r; + } + } + free(request); /* cleanup */ + + num = 0; + header = 0; + /* now wait for response */ + do { + FD_ZERO(&c->rd); + FD_SET(c->fd, &c->rd); + r = select(c->fd + 1, &c->rd, 0, 0, 0); + if (r == -1 && errno == EINTR) + continue; + if (r < 0) { + perror("ixp: client: select"); + if (result) + free(result); + handle_dead_server(c); + return 0; + } else if (r > 0) { + if (!header) { + r = read(c->fd, out_len, sizeof(size_t)); + if (r != sizeof(size_t)) { + if (result) + free(result); + handle_dead_server(c); + return 0; + } + result = emalloc(*out_len); + header = 1; + } + r = read(c->fd, ((char *) result) + num, *out_len - num); + if (r < 1) { + perror("ixp: client: read"); + if (result) + free(result); + handle_dead_server(c); + return 0; + } + num += r; + } + } + while (num != *out_len); + + /* error checking */ + if (result) + check_error(c, result); + if (c->errstr) { + free(result); + result = 0; + } + return result; +} + +static void +cixp_create(IXPClient * c, char *path) +{ + ResHeader h; + size_t req_len, res_len; + void *req = tcreate_message(path, &req_len); + void *result = poll_server(c, req, req_len, &res_len); + + if (!result) + return; + memcpy(&h, result, sizeof(ResHeader)); + free(result); +} + +static int +cixp_open(IXPClient * c, char *path) +{ + ResHeader h; + size_t req_len, res_len; + void *req = topen_message(path, &req_len); + void *result = poll_server(c, req, req_len, &res_len); + + if (!result) + return -1; + memcpy(&h, result, sizeof(ResHeader)); + free(result); + offsets[h.fd][0] = offsets[h.fd][1] = 0; + return h.fd; +} + +static size_t +cixp_read(IXPClient * c, int fd, void *out_buf, size_t out_buf_len) +{ + size_t len; + + len = seek_read(c, fd, offsets[fd][0], out_buf, out_buf_len); + if (c->errstr) + return 0; + if (len == out_buf_len) + offsets[fd][0] += len; + return len; +} + +size_t +seek_read(IXPClient * c, int fd, size_t offset, + void *out_buf, size_t out_buf_len) +{ + ResHeader h; + size_t req_len, res_len; + void *req = tread_message(fd, offset, out_buf_len, &req_len); + void *result = poll_server(c, req, req_len, &res_len); + + if (!result) + return -1; + memcpy(&h, result, sizeof(ResHeader)); + memcpy(out_buf, ((char *) result) + sizeof(ResHeader), h.buf_len); + free(result); + return h.buf_len; +} + +static void +cixp_write(IXPClient * c, int fd, void *content, size_t in_len) +{ + seek_write(c, fd, offsets[fd][1], content, in_len); + if (!c->errstr) + offsets[fd][1] += in_len; +} + +void +seek_write(IXPClient * c, int fd, size_t offset, void *content, + size_t in_len) +{ + ResHeader h; + size_t req_len, res_len; + void *req = twrite_message(fd, offset, content, in_len, &req_len); + void *result = poll_server(c, req, req_len, &res_len); + + if (!result) + return; + memcpy(&h, result, sizeof(ResHeader)); + free(result); +} + +static void +cixp_close(IXPClient * c, int fd) +{ + ResHeader h; + size_t req_len, res_len; + void *req = tclose_message(fd, &req_len); + void *result = poll_server(c, req, req_len, &res_len); + + if (!result) + return; + memcpy(&h, result, sizeof(ResHeader)); + free(result); + offsets[fd][0] = offsets[fd][1] = 0; +} + +static void +cixp_remove(IXPClient * c, char *path) +{ + ResHeader h; + size_t req_len, res_len; + void *req = tremove_message(path, &req_len); + void *result = poll_server(c, req, req_len, &res_len); + + if (!result) + return; + memcpy(&h, result, sizeof(ResHeader)); + free(result); +} + +IXPClient * +init_client(char *sockfile) +{ + struct sockaddr_un addr = {0}; + socklen_t su_len; + + /* init */ + IXPClient *c = (IXPClient *) emalloc(sizeof(IXPClient)); + *c = zero_client; + c->create = cixp_create; + c->open = cixp_open; + c->read = cixp_read; + c->write = cixp_write; + c->close = cixp_close; + c->remove = cixp_remove; + + addr.sun_family = AF_UNIX; + strncpy(addr.sun_path, sockfile, sizeof(addr.sun_path)); + su_len = sizeof(struct sockaddr) + strlen(addr.sun_path); + + if ((c->fd = socket(AF_UNIX, SOCK_STREAM, 0)) < 0) { + free(c); + return 0; + } + if (connect(c->fd, (struct sockaddr *) & addr, su_len)) { + close(c->fd); + free(c); + return 0; + } + return c; +} + +void +deinit_client(IXPClient * c) +{ + if (c->fd) { + shutdown(c->fd, SHUT_RDWR); + close(c->fd); + } + c->fd = -1; + free(c); +} diff --git a/libixp/ixp.h b/libixp/ixp.h new file mode 100644 index 00000000..9d43dab6 --- /dev/null +++ b/libixp/ixp.h @@ -0,0 +1,188 @@ +/* + * (C)opyright MMIV-MMV Anselm R. Garbe + * See LICENSE file for license details. + */ + +#include +#include +#include + +#define MAX_CONN 32 +#define MAX_OPEN_FILES 16 +#define DEAD_SERVER "server closed connection unexpectedly" +#define MAX_SEEN_SHUTDOWN 3 + +#define _MAX(x,y) ((x) > (y) ? (x) : (y)) + +/* 9P message types */ +enum { + TVERSION = 100, + RVERSION, + TAUTH = 102, + RAUTH, + TATTACH = 104, + RATTACH, + TERROR = 106, + RERROR, + TFLUSH = 108, + RFLUSH, + TWALK = 110, + RWALK, + TOPEN = 112, + ROPEN, + TCREATE = 114, + RCREATE, + TREAD = 116, + RREAD, + TWRITE = 118, + RWRITE, + TCLUNK = 120, + RCLUNK, + TREMOVE = 122, + RREMOVE, + TSTAT = 124, + RSTAT, + TWSTAT = 126, + RWSTAT, +}; + +typedef struct ResHeader ResHeader; +typedef struct ReqHeader ReqHeader; +typedef struct Connection Connection; +typedef struct File File; +typedef struct IXPClient IXPClient; +typedef struct IXPServer IXPServer; +typedef int IXPRequest; +typedef int IXPResponse; +typedef enum { + HALT, SHUTDOWN, RUNNING +} IXPRunlevel; + +struct ReqHeader { + IXPRequest req; + int fd; + size_t offset; + size_t buf_len; +}; + +struct ResHeader { + IXPResponse res; + int fd; + size_t buf_len; +}; + +/** Definition of a connection to IXP */ +struct Connection { + IXPServer *s; /* !< server for this connection */ + int index; /* !< index inside server */ + int fd; /* !< file descriptor */ + int mode; /* 0 for reading, 1 for writing */ + int header; + void *data; + size_t len; + size_t remain; + void (*read) (Connection *); + void (*write) (Connection *); + File *files[MAX_OPEN_FILES]; + int seen[MAX_OPEN_FILES]; +}; + +struct File { + char *name; + void *content; + size_t size; + int lock; + int bind; + File *parent; + File *next; + /* introduced IXPServer in signature for IXPServer->errstr */ + void (*after_write) (IXPServer * s, File *); + void (*before_read) (IXPServer * s, File *); +}; + +struct IXPClient { + int fd; + fd_set rd, wr; + char *errstr; /* 0 if succes, CHECK AFTER EACH of following + * operations */ + /* returns fd if path exists */ + void (*create) (IXPClient *, char *path); + void (*remove) (IXPClient *, char *path); + int (*open) (IXPClient *, char *path); + void (*close) (IXPClient *, int fd); + size_t(*read) (IXPClient *, int fd, void *out_buf, + size_t out_buf_len); + void (*write) (IXPClient *, int fd, void *content, size_t in_len); +}; + +struct IXPServer { + char *sockfile; + IXPRunlevel runlevel; + Connection conn[MAX_CONN]; + fd_set rd, wr; /* socks to wakeup while select() */ + int nfds; /* number of file descriptors */ + File *root; + char *errstr; /* 0 if succes, CHECK AFTER EACH of following + * operations */ + File *(*create) (IXPServer *, char *); + File *(*open) (IXPServer *, char *); + size_t(*read) (IXPServer *, int, size_t, void *, size_t); + void (*write) (IXPServer *, int, size_t, void *, size_t); + void (*close) (IXPServer *, int); + void (*remove) (IXPServer *, char *); +}; + +/* client.c, implements client stub functions for fs access */ +IXPClient *init_client(char *sockfile); +void deinit_client(IXPClient * c); +size_t +seek_read(IXPClient * c, int fd, size_t offset, void *out_buf, + size_t out_buf_len); +void +seek_write(IXPClient * c, int fd, size_t offset, + void *content, size_t in_len); + +/* message.c */ +void *tcreate_message(char *path, size_t * msg_len); +void *topen_message(char *path, size_t * msg_len); +void * +tread_message(int fd, size_t offset, size_t buf_len, + size_t * msg_len); +void * +twrite_message(int fd, size_t offset, void *msg, size_t in_len, + size_t * msg_len); +void *tclose_message(int fd, size_t * msg_len); +void *tremove_message(char *path, size_t * msg_len); +void *rcreate_message(size_t * msg_len); +void *ropen_message(int fd, size_t * msg_len); +void *rread_message(void *content, size_t content_len, size_t * msg_len); +void *rwrite_message(size_t * msg_len); +void *rclose_message(size_t * msg_len); +void *rremove_message(size_t * msg_len); +void *rerror_message(char *errstr, size_t * msg_len); + +/* ramfs.c */ +int is_directory(File * f); +File *ixp_walk(IXPServer * s, char *path); +File *ixp_create(IXPServer * s, char *path); +File *ixp_open(IXPServer * s, char *path); +size_t +ixp_read(IXPServer * s, int fd, size_t offset, void *out_buf, + size_t out_buf_len); +void +ixp_write(IXPServer * s, int fd, size_t offset, + void *content, size_t in_len); +void ixp_close(IXPServer * s, int fd); +void ixp_remove(IXPServer * s, char *path); +void ixp_remove_file(IXPServer * s, File * f); + +/* server.c, uses fs directly for manipulation */ +IXPServer *init_server(char *sockfile, void (*cleanup) (void)); +void deinit_server(IXPServer * s); +File *fd_to_file(IXPServer * s, int fd); +void run_server(IXPServer * s); +void run_server_with_fd_support(IXPServer * s, int fd, void (*fd_read) (Connection *), /* callback for read on + * fd */ + void (*fd_write) (Connection *)); /* callback for write on + * fd */ +void set_error(IXPServer * s, char *errstr); diff --git a/libixp/message.c b/libixp/message.c new file mode 100644 index 00000000..80c6e8c2 --- /dev/null +++ b/libixp/message.c @@ -0,0 +1,189 @@ +/* + * (C)opyright MMIV-MMV Anselm R. Garbe + * See LICENSE file for license details. + */ + +#include +#include + +#include "ixp.h" + +#include + +/* request messages ------------------------------------------------ */ + +void * +tcreate_message(char *path, size_t * msg_len) +{ + char *msg; + ReqHeader h; + *msg_len = sizeof(ReqHeader) + strlen(path) + 1; + msg = emalloc(*msg_len); + h.req = TCREATE; + memcpy(msg, &h, sizeof(ReqHeader)); + memcpy(msg + sizeof(ReqHeader), path, strlen(path) + 1); + return msg; +} + +void * +topen_message(char *path, size_t * msg_len) +{ + char *msg; + ReqHeader h; + *msg_len = sizeof(ReqHeader) + strlen(path) + 1; + msg = emalloc(*msg_len); + h.req = TOPEN; + memcpy(msg, &h, sizeof(ReqHeader)); + memcpy(msg + sizeof(ReqHeader), path, strlen(path) + 1); + return msg; +} + +void * +tread_message(int fd, size_t offset, size_t buf_len, + size_t * msg_len) +{ + char *msg; + ReqHeader h; + *msg_len = sizeof(ReqHeader); + msg = emalloc(*msg_len); + h.req = TREAD; + h.fd = fd; + h.offset = offset; + h.buf_len = buf_len; + memcpy(msg, &h, sizeof(ReqHeader)); + return msg; +} + +void * +twrite_message(int fd, size_t offset, void *content, + size_t content_len, size_t * msg_len) +{ + char *msg; + ReqHeader h; + *msg_len = sizeof(ReqHeader) + content_len; + msg = emalloc(*msg_len); + h.req = TWRITE; + h.fd = fd; + h.offset = offset; + h.buf_len = content_len; + memcpy(msg, &h, sizeof(ReqHeader)); + memcpy(msg + sizeof(ReqHeader), content, content_len); + return msg; +} + +void * +tclose_message(int fd, size_t * msg_len) +{ + char *msg; + ReqHeader h; + *msg_len = sizeof(ReqHeader); + msg = emalloc(*msg_len); + h.req = TCLUNK; + h.fd = fd; + memcpy(msg, &h, sizeof(ReqHeader)); + return msg; +} + +void * +tremove_message(char *path, size_t * msg_len) +{ + char *msg; + ReqHeader h; + *msg_len = sizeof(ReqHeader) + strlen(path) + 1; + msg = emalloc(*msg_len); + h.req = TREMOVE; + memcpy(msg, &h, sizeof(ReqHeader)); + memcpy(msg + sizeof(ReqHeader), path, strlen(path) + 1); + return msg; +} + +/* response messages ----------------------------------------------- */ + +void * +rcreate_message(size_t * msg_len) +{ + char *msg; + ResHeader h; + *msg_len = sizeof(ResHeader); + msg = emalloc(*msg_len); + h.res = RCREATE; + memcpy(msg, &h, sizeof(ResHeader)); + return msg; +} + +void * +ropen_message(int fd, size_t * msg_len) +{ + char *msg; + ResHeader h; + *msg_len = sizeof(ResHeader); + msg = emalloc(*msg_len); + h.res = ROPEN; + h.fd = fd; + memcpy(msg, &h, sizeof(ResHeader)); + return msg; +} + +void * +rread_message(void *content, size_t content_len, size_t * msg_len) +{ + char *msg; + ResHeader h; + *msg_len = sizeof(ResHeader) + content_len; + msg = emalloc(*msg_len); + h.res = RREAD; + h.buf_len = content_len; + memcpy(msg, &h, sizeof(ResHeader)); + memmove(msg + sizeof(ResHeader), content, content_len); + return msg; +} + +void * +rwrite_message(size_t * msg_len) +{ + char *msg; + ResHeader h; + *msg_len = sizeof(ResHeader); + msg = emalloc(*msg_len); + h.res = RWRITE; + memcpy(msg, &h, sizeof(ResHeader)); + return msg; +} + +void * +rclose_message(size_t * msg_len) +{ + char *msg; + ResHeader h; + *msg_len = sizeof(ResHeader); + msg = emalloc(*msg_len); + h.res = RCLUNK; + memcpy(msg, &h, sizeof(ResHeader)); + return msg; +} + +void * +rremove_message(size_t * msg_len) +{ + char *msg; + ResHeader h; + *msg_len = sizeof(ResHeader); + msg = emalloc(*msg_len); + h.res = RREMOVE; + memcpy(msg, &h, sizeof(ResHeader)); + return msg; +} + +void * +rerror_message(char *errstr, size_t * msg_len) +{ + char *msg; + size_t len = strlen(errstr) + 1; + ResHeader h; + *msg_len = sizeof(ResHeader) + len; + msg = emalloc(*msg_len); + h.res = RERROR; + memcpy(msg, &h, sizeof(ResHeader)); + memmove(msg + sizeof(ResHeader), errstr, len); + return msg; +} diff --git a/libixp/ramfs.c b/libixp/ramfs.c new file mode 100644 index 00000000..bdf70c30 --- /dev/null +++ b/libixp/ramfs.c @@ -0,0 +1,322 @@ +/* + * (C)opyright MMIV-MMV Anselm R. Garbe + * See LICENSE file for license details. + */ + +#include +#include +#include + +#include "ixp.h" + +#include + +static File zero_file = {0}; + +int +is_directory(File * f) +{ + return !f->size && f->content; +} + +File * +ixp_create(IXPServer * s, char *path) +{ + File *f, *p; + char *buf = strdup(path); + char *tok, *tok_ptr; + + if (!path) + return 0; + + /* cannot create a directory with empty name */ + if (buf[strlen(buf) - 1] == '/') + return 0; + tok = strtok_r(buf, "/", &tok_ptr); + p = s->root; + f = p->content; + /* first determine the path as it exists already */ + while (f && tok) { + if (!strcmp(f->name, tok)) { + tok = strtok_r(0, "/", &tok_ptr); + p = f; + if (tok && is_directory(f)) + f = f->content; + else + break; + continue; + } + f = f->next; + } + if (p->size && tok) { + free(buf); + return 0; /* cannot create subdirectory, if file has + * content */ + } + /* only create missing parts, if file is directory */ + while (tok) { + f = (File *) emalloc(sizeof(File)); + *f = zero_file; + f->name = strdup(tok); + f->parent = p; + if (p->content) { + p = p->content; + while (p->next) { + p = p->next; + } + p->next = f; + } else { + p->content = f; + } + p = f; + tok = strtok_r(0, "/", &tok_ptr); + } + free(buf); + return f; +} + +static int +comp_file_name(const void *f1, const void *f2) +{ + File *f = (File *) f1; + File *p = (File *) f2; + return strcmp(*(char **) f->name, *(char **) p->name); +} + +static char * +_ls(File * f) +{ + File *p; + char *result = 0; + size_t size = 1; /* for \n */ + int num = 0, i; + File **tmp; + + for (p = f; p; p = p->next) + num++; + tmp = emalloc(sizeof(File *) * num); + i = 0; + for (p = f; p; p = p->next) { + size += strlen(p->name) + 1; + if (is_directory(p)) + size++; + tmp[i++] = p; + } + qsort(tmp, num, sizeof(char *), comp_file_name); + result = emalloc(size); + result[0] = '\0'; + for (i = 0; i < num; i++) { + strncat(result, tmp[i]->name, size); + if (is_directory(tmp[i])) + strncat(result, "/\n", size); + else + strncat(result, "\n", size); + } + free(tmp); + return result; +} + +File * +ixp_open(IXPServer * s, char *path) +{ + File *f; + + f = ixp_walk(s, path); + if (!f) { + set_error(s, "file does not exist"); + return 0; + } + f->lock++; + return f; +} + +void +ixp_close(IXPServer * s, int fd) +{ + File *f = fd_to_file(s, fd); + if (!f) + set_error(s, "invalid file descriptor"); + else if (f->lock > 0) + f->lock--; +} + +size_t +ixp_read(IXPServer * s, int fd, size_t offset, void *out_buf, + size_t out_buf_len) +{ + File *f = fd_to_file(s, fd); + void *result = 0; + size_t len = 0, res_len = 0; + + if (!f) { + set_error(s, "invalid file descriptor"); + return 0; + } + /* callback */ + if (f->before_read) + f->before_read(s, f); + if (is_directory(f)) { + result = _ls(f->content); + res_len = strlen(result); + } else if (f->size) { + result = f->content; + res_len = f->size; + } + if (offset > res_len) { + set_error(s, "invalid offset when reading file"); + if (is_directory(f)) + free(result); + return 0; + } + if (result) { + len = res_len - offset; + if (len > out_buf_len) + len = out_buf_len; + memcpy(out_buf, (char *) result + offset, len); + if (is_directory(f)) + free(result); + } + return len; +} + +void +ixp_write(IXPServer * s, int fd, size_t offset, void *content, + size_t in_len) +{ + File *f = fd_to_file(s, fd); + + if (!f) { + set_error(s, "invalid file descriptor"); + return; + } + if (is_directory(f)) { + /* we cannot write to directories */ + set_error(s, "cannot write to a directory"); + return; + } + if (in_len) { + /* offset 0 flushes the file */ + if (!offset || (offset + in_len > f->size)) { + f->content = realloc(f->content, offset + in_len + 1); + f->size = offset + in_len; + } + memcpy((char *) f->content + offset, content, in_len); + /* internal EOF character */ + ((char *) f->content)[f->size] = '\0'; + } else if (!offset) { + /* blank file */ + if (f->content) + free(f->content); + f->content = 0; + f->size = 0; + } + /* callback */ + if (f->after_write) + f->after_write(s, f); +} + +static void +_ixp_remove(IXPServer * s, File * f) +{ + if (!f) + return; + if (f->next) { + _ixp_remove(s, f->next); + if (s->errstr) + return; + } + if (f->lock) { + set_error(s, "cannot remove opened file"); + return; /* a file is opened, so stop removing tree */ + } + if (!f->bind && is_directory(f)) { + _ixp_remove(s, f->content); + if (s->errstr) + return; + } + if (f->content && f->size) { + free(f->content); + } + if (f != s->root) { + if (f->name) { + free(f->name); + } + free(f); + } +} + +void +ixp_remove_file(IXPServer * s, File * f) +{ + File *p, *n; + set_error(s, 0); + if (!f) { + set_error(s, "file does not exist"); + return; + } + if (f->lock) { + set_error(s, "cannot remove opened file"); + return; + } + /* detach */ + p = f->parent; + n = f->next; + f->next = 0; + if (p) { + if (p->content == f) + p->content = n; + else { + p = p->content; + while (p && (p->next != f)) + p = p->next; + if (p) + p->next = n; + } + } + /* remove now */ + _ixp_remove(s, f); +} + + +void +ixp_remove(IXPServer * s, char *path) +{ + ixp_remove_file(s, ixp_walk(s, path)); +} + +File * +ixp_walk(IXPServer * s, char *path) +{ + File *f = 0; + File *n; + char *buf; + char *tok, *tok_ptr; + + if (!path) { + return 0; + } + buf = strdup(path); + + tok = strtok_r(buf, "/", &tok_ptr); + f = s->root->content; + if (!tok && buf[0] == '/') { + f = s->root; + } + while (f && tok) { + n = f->next; + if (!strcmp(f->name, tok)) { + tok = strtok_r(0, "/", &tok_ptr); + if (tok && f->size) + return 0; + if (!tok) + break; + f = f->content; + continue; + } + f = n; + } + if (f && (path[strlen(path) - 1] == '/') && !is_directory(f)) + f = 0; + free(buf); + return f; +} diff --git a/libixp/server.c b/libixp/server.c new file mode 100644 index 00000000..a2c2e4b6 --- /dev/null +++ b/libixp/server.c @@ -0,0 +1,475 @@ +/* + * (C)opyright MMIV-MMV Anselm R. Garbe + * See LICENSE file for license details. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "ixp.h" + +#include + +static File zero_file = {0}; +static IXPServer zero_server = {0}; +static Connection zero_conn = {0}; +static int user_fd = -1; + +void +set_error(IXPServer * s, char *errstr) +{ + if (s->errstr) + free(s->errstr); + if (errstr) + s->errstr = strdup(errstr); + else + s->errstr = 0; +} + +File * +fd_to_file(IXPServer * s, int fd) +{ + int cidx = fd / MAX_CONN; + int fidx = fd - (cidx * MAX_CONN); + + return s->conn[cidx].files[fidx]; +} + +static void +handle_ixp_create(Connection * c) +{ + c->s->create(c->s, ((char *) c->data) + sizeof(ReqHeader)); + free(c->data); + c->data = c->s->errstr ? + rerror_message(c->s->errstr, &c->len) : rcreate_message(&c->len); + c->remain = c->len; +} + +static void +handle_ixp_open(Connection * c) +{ + int i; + + /* seek next free slot */ + for (i = 0; (i < MAX_OPEN_FILES) && c->files[i]; i++); + if (i == MAX_OPEN_FILES) { + fprintf(stderr, "%s", + "ixp: server: maximum of open files, try again later.\n"); + free(c->data); + c->data = + rerror_message("maximum open files reached, close files first", + &c->len); + c->remain = c->len; + return; + } + c->files[i] = c->s->open(c->s, ((char *) c->data) + sizeof(ReqHeader)); + c->seen[i] = MAX_SEEN_SHUTDOWN; + free(c->data); + c->data = c->s->errstr ? + rerror_message(c->s->errstr, + &c->len) : ropen_message(i + MAX_CONN * c->index, + &c->len); + c->remain = c->len; +} + +static void +handle_ixp_read(Connection * c, ReqHeader * h) +{ + int fidx = h->fd - (c->index * MAX_CONN); + void *data = 0; + size_t out_len; + + data = emalloc(h->buf_len); + out_len = c->s->read(c->s, h->fd, h->offset, data, h->buf_len); + free(c->data); + if (c->s->errstr) { + c->data = rerror_message(c->s->errstr, &c->len); + if (c->files[fidx] && c->files[fidx]->lock > 0) + c->files[fidx]->lock--; + + } else + c->data = rread_message(data, out_len, &c->len); + c->remain = c->len; + free(data); +} + +static void +handle_ixp_write(Connection * c, ReqHeader * h) +{ + int fidx = h->fd - (c->index * MAX_CONN); + c->s->write(c->s, h->fd, h->offset, + ((char *) c->data) + sizeof(ReqHeader), h->buf_len); + free(c->data); + if (c->s->errstr) { + c->data = rerror_message(c->s->errstr, &c->len); + if (c->files[fidx] && c->files[fidx]->lock > 0) + c->files[fidx]->lock--; + + } else + c->data = rwrite_message(&c->len); + c->remain = c->len; +} + +static void +handle_ixp_close(Connection * c, ReqHeader * h) +{ + int fidx = h->fd - (c->index * MAX_CONN); + + c->s->close(c->s, h->fd); + c->files[fidx] = 0; + free(c->data); + if (c->s->errstr) { + c->data = rerror_message(c->s->errstr, &c->len); + if (c->files[fidx] && c->files[fidx]->lock > 0) + c->files[fidx]->lock--; + + } else + c->data = rclose_message(&c->len); + c->remain = c->len; +} + +static void +handle_ixp_remove(Connection * c) +{ + c->s->remove(c->s, ((char *) c->data) + sizeof(ReqHeader)); + free(c->data); + c->data = c->s->errstr ? + rerror_message(c->s->errstr, &c->len) : rremove_message(&c->len); + c->remain = c->len; +} + +static void +check_ixp_request(Connection * c) +{ + ReqHeader h; + /* check pending request */ + if (c->s->errstr) + set_error(c->s, 0); + memcpy(&h, c->data, sizeof(ReqHeader)); + switch (h.req) { + case TCREATE: + handle_ixp_create(c); + break; + case TREMOVE: + handle_ixp_remove(c); + break; + case TOPEN: + handle_ixp_open(c); + break; + case TCLUNK: + handle_ixp_close(c, &h); + break; + case TREAD: + handle_ixp_read(c, &h); + break; + case TWRITE: + handle_ixp_write(c, &h); + break; + default: + fprintf(stderr, "%s", "ixp: server: invalid request\n"); + free(c->data); + c->len = c->remain = 0; + break; + } +} + +static void +update_conns(IXPServer * s) +{ + int i; + + FD_ZERO(&s->rd); + FD_ZERO(&s->wr); + for (i = 0; i < MAX_CONN; i++) { + if (s->conn[i].fd >= 0) { + s->nfds = _MAX(s->nfds, s->conn[i].fd); + if (s->conn[i].read && !s->conn[i].mode + && (!s->conn[i].len || s->conn[i].remain)) { + FD_SET(s->conn[i].fd, &s->rd); + } + if (s->conn[i].write && s->conn[i].mode && s->conn[i].remain) { + FD_SET(s->conn[i].fd, &s->wr); + } + } + } +} + +static void +close_conn(Connection * c) +{ + int i; + /* shutdown connection and cleanup open files */ + shutdown(c->fd, SHUT_RDWR); + close(c->fd); + c->fd = -1; + c->mode = 0; + for (i = 0; i < MAX_OPEN_FILES; i++) { + if (c->files[i]) { + if (c->files[i]->lock > 0) + c->files[i]->lock--; + c->files[i] = 0; + } + } +} + +static void +read_conn(Connection * c) +{ + size_t r; + + if (!c->header) { + r = read(c->fd, &c->len, sizeof(size_t)); + if (r != sizeof(size_t)) { + close_conn(c); + return; + } + c->remain = c->len; + c->data = emalloc(c->len); + c->header = 1; + } + r = read(c->fd, ((char *) c->data) + c->len - c->remain, c->remain); + if (r < 1) { + close_conn(c); + return; + } + c->remain -= r; + + if (c->remain == 0) { + /* check IXP request */ + c->mode = 1; /* next mode is response */ + check_ixp_request(c); + c->header = 0; + } +} + +static void +write_conn(Connection * c) +{ + size_t r; + + if (!c->header) { + r = write(c->fd, &c->len, sizeof(size_t)); + if (r != sizeof(size_t)) { + close_conn(c); + } + c->header = 1; + } + r = write(c->fd, ((char *) c->data) + c->len - c->remain, c->remain); + if (r < 1) { + close_conn(c); + return; + } + c->remain -= r; + + if (c->remain == 0) { + c->len = 0; + c->mode = 0; + c->header = 0; + } +} + +static void +new_conn(Connection * c) +{ + int r, i; + socklen_t l; + struct sockaddr_un name = {0}; + + l = sizeof(name); + if ((r = accept(c->fd, (struct sockaddr *) & name, &l)) < 0) { + perror("ixp: server: cannot accept connection"); + return; + } + if (c->s->runlevel == SHUTDOWN) { + fprintf(stderr, "%s", + "ixp: server: connection refused, server is shutting down.\n"); + close(r); + return; + } + for (i = 0; i < MAX_CONN; i++) { + if (c->s->conn[i].fd == -1) { /* free connection */ + c->s->conn[i] = zero_conn; + c->s->conn[i].s = c->s; + c->s->conn[i].index = i; + c->s->conn[i].fd = r; + c->s->conn[i].read = read_conn; + c->s->conn[i].write = write_conn; + break; + } + } + + if (i == MAX_CONN) { + fprintf(stderr, "%s", + "ixp: server: connection refused, try again later.\n"); + close(r); + } +} + + +static int +check_open_files(Connection * c) +{ + int i; + for (i = 0; i < MAX_OPEN_FILES; i++) { + if (c->files[i] && c->seen[i]) { + c->seen[i]--; + return 1; + } + } + return 0; +} + +static void +handle_socks(IXPServer * s) +{ + int i, now = 1; + for (i = 0; i < MAX_CONN; i++) { + if (s->conn[i].fd >= 0) { + if (FD_ISSET(s->conn[i].fd, &s->rd) && s->conn[i].read) { + /* call back read handler */ + s->conn[i].read(&s->conn[i]); + } else if (FD_ISSET(s->conn[i].fd, &s->wr) && s->conn[i].write) { + /* call back write handler */ + s->conn[i].write(&s->conn[i]); + } + /* + * don't shutdown, if there're remaining bits or if + * still responses are sent or still opened files + */ + if ((s->runlevel == SHUTDOWN) + && (check_open_files(&s->conn[i]) + || (s->conn[i].remain > 0) + || s->conn[i].mode)) + now = 0; + } + } + if ((s->runlevel == SHUTDOWN) && now) + s->runlevel = HALT; /* real stop */ +} + +IXPServer * +init_server(char *sockfile, void (*cleanup) (void)) +{ + int i; + struct sockaddr_un addr = {0}; + int yes = 1; + socklen_t su_len; + IXPServer *s; + + /* init */ + s = (IXPServer *) emalloc(sizeof(IXPServer)); + *s = zero_server; + s->sockfile = sockfile; + s->root = (File *) emalloc(sizeof(File)); + s->runlevel = HALT; /* initially server is not running */ + s->create = ixp_create; + s->remove = ixp_remove; + s->open = ixp_open; + s->close = ixp_close; + s->read = ixp_read; + s->write = ixp_write; + *s->root = zero_file; + s->root->name = strdup(""); + for (i = 0; i < MAX_CONN; i++) { + s->conn[i].s = s; + s->conn[i].fd = -1; + s->conn[i].index = i; + } + + signal(SIGPIPE, SIG_IGN); + if ((s->conn[0].fd = socket(AF_UNIX, SOCK_STREAM, 0)) < 0) { + perror("ixp: server: socket"); + free(s); + return 0; + } + if (setsockopt(s->conn[0].fd, SOL_SOCKET, SO_REUSEADDR, + (char *) &yes, sizeof(yes)) < 0) { + perror("ixp: server: setsockopt"); + close(s->conn[0].fd); + free(s); + return 0; + } + addr.sun_family = AF_UNIX; + strncpy(addr.sun_path, sockfile, sizeof(addr.sun_path)); + su_len = sizeof(struct sockaddr) + strlen(addr.sun_path); + + if (bind(s->conn[0].fd, (struct sockaddr *) & addr, su_len) < 0) { + perror("ixp: server: cannot bind socket"); + close(s->conn[0].fd); + free(s); + return 0; + } + chmod(sockfile, S_IRWXU); + + if (listen(s->conn[0].fd, MAX_CONN) < 0) { + perror("ixp: server: cannot listen on socket"); + close(s->conn[0].fd); + free(s); + return 0; + } + s->conn[0].read = new_conn; + + /* register to cleanup function, to unlink sockfile */ + if (cleanup) + atexit(cleanup); + + return s; +} + +void +run_server_with_fd_support(IXPServer * s, int fd, + void (*fd_read) (Connection *), + void (*fd_write) (Connection *)) +{ + s->conn[1] = zero_conn; + s->conn[1].index = 1; + s->conn[1].s = s; + s->conn[1].fd = user_fd = fd; + s->conn[1].read = fd_read; + s->conn[1].write = fd_write; + run_server(s); +} + +void +run_server(IXPServer * s) +{ + int r, i; + s->runlevel = RUNNING; + + /* main loop */ + while (s->runlevel != HALT) { + + update_conns(s); + + r = select(s->nfds + 1, &s->rd, &s->wr, 0, 0); + if (r == -1 && errno == EINTR) + continue; + if (r < 0) { + perror("ixp: server: select"); + break; /* allow cleanups in IXP using app */ + } else if (r > 0) { + handle_socks(s); + } + } + /* shut down server */ + for (i = MAX_CONN - 1; i >= 0; i--) { + if (s->conn[i].fd >= 0 && s->conn[i].fd != user_fd) { + close(s->conn[i].fd); + } + } +} + +void +deinit_server(IXPServer * s) +{ + unlink(s->sockfile); + ixp_remove(s, "/"); + free(s); +} diff --git a/libixp2/Makefile b/libixp2/Makefile new file mode 100644 index 00000000..199c1e1f --- /dev/null +++ b/libixp2/Makefile @@ -0,0 +1,25 @@ +# libixp - lib ixp protocol +# (C)opyright MMIV-MMV Anselm R. Garbe + +include ../config.mk + +CFLAGS += -I ../libcext + +SRC = client.c convert.c message.c server.c socket.c transport.c + +OBJ = ${SRC:.c=.o} + +all: libixp + @echo built libixp2 + +.c.o: + @echo CC $< + @${CC} -c ${CFLAGS} $< + +libixp: ${OBJ} + @echo AR $@.a + @${AR} $@.a ${OBJ} + @${RANLIB} $@.a + +clean: + rm -f libixp.a *.o diff --git a/libixp2/client.c b/libixp2/client.c new file mode 100644 index 00000000..1086c57c --- /dev/null +++ b/libixp2/client.c @@ -0,0 +1,191 @@ +/* + * (C)opyright MMIV-MMV Anselm R. Garbe + * See LICENSE file for license details. + */ + +#include +#include +#include +#include +#include +#include + +#include "cext.h" +#include "ixp.h" + +static u8 msg[IXP_MAX_MSG]; + +static int +do_fcall(IXPClient * c) +{ + u32 msize = ixp_fcall_to_msg(&c->fcall, msg, IXP_MAX_MSG); + c->errstr = 0; + if (ixp_send_message(c->fd, msg, msize, &c->errstr) != msize) + return FALSE; + if (!ixp_recv_message(c->fd, msg, IXP_MAX_MSG, &c->errstr)) + return FALSE; + if (!(msize = ixp_msg_to_fcall(msg, IXP_MAX_MSG, &c->fcall))) { + c->errstr = "received bad message"; + return FALSE; + } + if (c->fcall.id == RERROR) { + c->errstr = c->fcall.errstr; + return FALSE; + } + return TRUE; +} + +int +ixp_client_init(IXPClient * c, char *sockfile) +{ + if ((c->fd = ixp_connect_sock(sockfile)) < 0) { + c->errstr = "cannot connect server"; + return FALSE; + } + /* version */ + c->fcall.id = TVERSION; + c->fcall.tag = IXP_NOTAG; + c->fcall.maxmsg = IXP_MAX_MSG; + _strlcpy(c->fcall.version, IXP_VERSION, sizeof(c->fcall.version)); + if (!do_fcall(c)) { + ixp_client_deinit(c); + return FALSE; + } + if (strncmp(c->fcall.version, IXP_VERSION, strlen(IXP_VERSION))) { + c->errstr = "9P versions differ"; + ixp_client_deinit(c); + return FALSE; /* we cannot handle this version */ + } + c->root_fid = getpid(); + + /* attach */ + c->fcall.id = TATTACH; + c->fcall.tag = IXP_NOTAG; + c->fcall.fid = c->root_fid; + c->fcall.afid = IXP_NOFID; + _strlcpy(c->fcall.uname, getenv("USER"), sizeof(c->fcall.uname)); + c->fcall.aname[0] = '\0'; + if (!do_fcall(c)) { + ixp_client_deinit(c); + return FALSE; + } + c->root_qid = c->fcall.qid; + return TRUE; +} + +int +ixp_client_remove(IXPClient * c, u32 newfid, char *filepath) +{ + if (!ixp_client_walk(c, newfid, filepath)) + return FALSE; + /* remove */ + c->fcall.id = TREMOVE; + c->fcall.tag = IXP_NOTAG; + c->fcall.fid = newfid; + return do_fcall(c); +} + +int +ixp_client_create(IXPClient * c, u32 dirfid, char *name, u32 perm, + u8 mode) +{ + /* create */ + c->fcall.id = TCREATE; + c->fcall.tag = IXP_NOTAG; + c->fcall.fid = dirfid; + _strlcpy(c->fcall.name, name, sizeof(c->fcall.name)); + c->fcall.perm = perm; + c->fcall.mode = mode; + return do_fcall(c); +} + +int +ixp_client_walk(IXPClient * c, u32 newfid, char *filepath) +{ + /* walk */ + c->fcall.id = TWALK; + c->fcall.fid = c->root_fid; + c->fcall.newfid = newfid; + if (filepath) { + _strlcpy(c->fcall.name, filepath, sizeof(c->fcall.name)); + c->fcall.nwname = + tokenize((char **) c->fcall.wname, IXP_MAX_WELEM, + c->fcall.name, '/'); + } + return do_fcall(c); +} + +int +ixp_client_open(IXPClient * c, u32 newfid, char *filepath, u8 mode) +{ + if (!ixp_client_walk(c, newfid, filepath)) + return FALSE; + + /* open */ + c->fcall.id = TOPEN; + c->fcall.tag = IXP_NOTAG; + c->fcall.fid = newfid; + c->fcall.mode = mode; + return do_fcall(c); +} + +u32 +ixp_client_read(IXPClient * c, u32 fid, u64 offset, void *result, + u32 res_len) +{ + u32 bytes = + c->fcall.maxmsg - (sizeof(u8) + sizeof(u16) + 2 * sizeof(u32) + + sizeof(u64)); + /* read */ + c->fcall.id = TREAD; + c->fcall.tag = IXP_NOTAG; + c->fcall.fid = fid; + c->fcall.offset = offset; + c->fcall.count = res_len < bytes ? res_len : bytes; + if (!do_fcall(c)) + return 0; + memcpy(result, c->fcall.data, c->fcall.count); + return c->fcall.count; +} + +u32 +ixp_client_write(IXPClient * c, u32 fid, u64 offset, u32 count, + u8 * data) +{ + if (count > + c->fcall.maxmsg - (sizeof(u8) + sizeof(u16) + 2 * sizeof(u32) + + sizeof(u64))) { + c->errstr = "message size exceeds buffer size"; + return 0; + } + /* write */ + c->fcall.id = TWRITE; + c->fcall.tag = IXP_NOTAG; + c->fcall.fid = fid; + c->fcall.offset = offset; + c->fcall.count = count; + memcpy(c->fcall.data, data, count); + if (!do_fcall(c)) + return 0; + return c->fcall.count; +} + +int +ixp_client_close(IXPClient * c, u32 fid) +{ + /* clunk */ + c->fcall.id = TCLUNK; + c->fcall.tag = IXP_NOTAG; + c->fcall.fid = fid; + return do_fcall(c); +} + +void +ixp_client_deinit(IXPClient * c) +{ + /* session finished, now shutdown */ + if (c->fd) { + shutdown(c->fd, SHUT_RDWR); + close(c->fd); + } +} diff --git a/libixp2/convert.c b/libixp2/convert.c new file mode 100644 index 00000000..d442dd8d --- /dev/null +++ b/libixp2/convert.c @@ -0,0 +1,187 @@ +/* + * (C)opyright MMIV-MMV Anselm R. Garbe + * See LICENSE file for license details. + */ + +#include +#include +#include "ixp.h" + +/* encode/decode stuff */ + +void * +ixp_enc_u8(u8 * msg, u8 val) +{ + msg[0] = val; + return &msg[1]; +} + +void * +ixp_dec_u8(u8 * msg, u8 * val) +{ + *val = msg[0]; + return &msg[1]; +} + +void * +ixp_enc_u16(u8 * msg, u16 val) +{ + msg[0] = val; + msg[1] = val >> 8; + return &msg[2]; +} + +void * +ixp_dec_u16(u8 * msg, u16 * val) +{ + *val = msg[0] | (msg[1] << 8); + return &msg[2]; +} + +void * +ixp_enc_u32(u8 * msg, u32 val) +{ + msg[0] = val; + msg[1] = val >> 8; + msg[2] = val >> 16; + msg[3] = val >> 24; + return &msg[4]; +} + +void * +ixp_dec_u32(u8 * msg, u32 * val) +{ + *val = msg[0] | (msg[1] << 8) | (msg[2] << 16) | (msg[3] << 24); + return &msg[4]; +} + +void * +ixp_enc_u64(u8 * msg, u64 val) +{ + msg[0] = val; + msg[1] = val >> 8; + msg[2] = val >> 16; + msg[3] = val >> 24; + msg[4] = val >> 32; + msg[5] = val >> 40; + msg[6] = val >> 48; + msg[7] = val >> 56; + return &msg[8]; +} + +void * +ixp_dec_u64(u8 * msg, u64 * val) +{ + *val = (u64) msg[0] | ((u64) msg[1] << 8) | + ((u64) msg[2] << 16) | ((u64) msg[3] << 24) | + ((u64) msg[4] << 32) | ((u64) msg[5] << 40) | + ((u64) msg[6] << 48) | ((u64) msg[7] << 56); + return &msg[8]; +} + +void * +ixp_enc_string(u8 * msg, const char *s) +{ + u16 len = s ? strlen(s) : 0; + msg = ixp_enc_u16(msg, len); + if (s) + memcpy(msg, s, len); + return &msg[len]; +} + +void * +ixp_dec_string(u8 * msg, char *string, u16 stringlen, u16 * len) +{ + msg = ixp_dec_u16(msg, len); + if (!(*len)) + return msg; + if (*len > stringlen - 1) + /* might never happen if stringlen == IXP_MAX_MSG */ + string[0] = '\0'; + else { + memcpy(string, msg, *len); + string[*len] = 0; + } + return &msg[*len]; +} + +void * +ixp_enc_data(u8 * msg, u8 * data, u32 datalen) +{ + memcpy(msg, data, datalen); + return &msg[datalen]; +} + +void * +ixp_dec_data(u8 * msg, u8 * data, u32 datalen) +{ + memcpy(data, msg, datalen); + return &msg[datalen]; +} + +void * +ixp_enc_prefix(u8 * msg, u32 size, u8 id, u16 tag) +{ + msg = ixp_enc_u32(msg, size); + msg = ixp_enc_u8(msg, id); + return ixp_enc_u16(msg, tag); +} + +void * +ixp_dec_prefix(u8 * msg, u32 * size, u8 * id, u16 * tag) +{ + msg = ixp_dec_u32(msg, size); + msg = ixp_dec_u8(msg, id); + return ixp_dec_u16(msg, tag); +} + +void * +ixp_enc_qid(u8 * msg, Qid * qid) +{ + msg = ixp_enc_u8(msg, qid->type); + msg = ixp_enc_u32(msg, qid->version); + return ixp_enc_u64(msg, qid->path); +} + +void * +ixp_dec_qid(u8 * msg, Qid * qid) +{ + msg = ixp_dec_u8(msg, &qid->type); + msg = ixp_dec_u32(msg, &qid->version); + return ixp_dec_u64(msg, &qid->path); +} + +void * +ixp_enc_stat(u8 * msg, Stat * stat) +{ + msg = ixp_enc_u16(msg, stat->size); + msg = ixp_enc_u16(msg, stat->type); + msg = ixp_enc_u32(msg, stat->dev); + msg = ixp_enc_qid(msg, &stat->qid); + msg = ixp_enc_u32(msg, stat->mode); + msg = ixp_enc_u32(msg, stat->atime); + msg = ixp_enc_u32(msg, stat->mtime); + msg = ixp_enc_u64(msg, stat->length); + msg = ixp_enc_string(msg, stat->name); + msg = ixp_enc_string(msg, stat->uid); + msg = ixp_enc_string(msg, stat->gid); + return ixp_enc_string(msg, stat->muid); +} + +void * +ixp_dec_stat(u8 * msg, Stat * stat) +{ + u16 len; + msg = ixp_dec_u16(msg, &stat->size); + msg = ixp_dec_u16(msg, &stat->type); + msg = ixp_dec_u32(msg, &stat->dev); + msg = ixp_dec_qid(msg, &stat->qid); + msg = ixp_dec_u32(msg, &stat->mode); + msg = ixp_dec_u32(msg, &stat->atime); + msg = ixp_dec_u32(msg, &stat->mtime); + msg = ixp_dec_u64(msg, &stat->length); + msg = ixp_dec_string(msg, stat->name, sizeof(stat->name), &len); + msg = ixp_dec_string(msg, stat->uid, sizeof(stat->uid), &len); + msg = ixp_dec_string(msg, stat->gid, sizeof(stat->gid), &len); + return ixp_dec_string(msg, stat->muid, sizeof(stat->muid), &len); +} diff --git a/libixp2/ixp.h b/libixp2/ixp.h new file mode 100644 index 00000000..0dcd0ed8 --- /dev/null +++ b/libixp2/ixp.h @@ -0,0 +1,241 @@ +/* + * (C)opyright MMIV-MMV Anselm R. Garbe + * See LICENSE file for license details. + */ + +#include + +#ifndef FALSE +#define FALSE 0 +#endif +#ifndef TRUE +#define TRUE 1 +#endif +#ifndef nil +#define nil 0 +#endif + +#define IXP_VERSION "9P2000" +#define IXP_MAX_VERSION 32 +#define IXP_MAX_ERROR 128 +#define IXP_MAX_CONN 32 +#define IXP_MAX_MSG 8192 +#define IXP_MAX_FLEN 128 +#define IXP_MAX_ULEN 32 +#define IXP_MAX_STAT 64 +#define IXP_MAX_WELEM 16 /* MAXWELEM */ +#define IXP_MAX_TFUNCS 14 + +/* 9P message types */ +enum { + TVERSION = 100, + RVERSION, + TAUTH = 102, + RAUTH, + TATTACH = 104, + RATTACH, + TERROR = 106, + RERROR, + TFLUSH = 108, + RFLUSH, + TWALK = 110, + RWALK, + TOPEN = 112, + ROPEN, + TCREATE = 114, + RCREATE, + TREAD = 116, + RREAD, + TWRITE = 118, + RWRITE, + TCLUNK = 120, + RCLUNK, + TREMOVE = 122, + RREMOVE, + TSTAT = 124, + RSTAT, + TWSTAT = 126, + RWSTAT, +}; + +/* modes */ +enum { + IXP_OREAD = 0x00, + IXP_OWRITE = 0x01, + IXP_ORDWR = 0x02, + IXP_OEXEC = 0x03, + IXP_OEXCL = 0x04, + IXP_OTRUNC = 0x10, + IXP_OREXEC = 0x20, + IXP_ORCLOSE = 0x40, + IXP_OAPPEND = 0x80, +}; + +/* qid.types */ +enum { + IXP_QTDIR = 0x80, + IXP_QTAPPEND = 0x40, + IXP_QTEXCL = 0x20, + IXP_QTMOUNT = 0x10, + IXP_QTAUTH = 0x08, + IXP_QTTMP = 0x04, + IXP_QTSYMLINK = 0x02, + IXP_QTLINK = 0x01, + IXP_QTFILE = 0x00, +}; + +/* this should work on all architectures */ +typedef unsigned char u8; +typedef unsigned short u16; +typedef unsigned int u32; +typedef unsigned long long u64; +#define IXP_NOTAG (u16)~0U /* Dummy tag */ +#define IXP_NOFID (u32)~0 /* No auth */ + +typedef struct { + u8 type; + u32 version; + u64 path; +} Qid; + +/* stat structure */ +typedef struct { + u16 size; + u16 type; + u32 dev; + Qid qid; + u32 mode; + u32 atime; + u32 mtime; + u64 length; + char name[IXP_MAX_FLEN]; + char uid[IXP_MAX_ULEN]; + char gid[IXP_MAX_ULEN]; + char muid[IXP_MAX_ULEN]; +} Stat; + +typedef struct { + u8 id; + u16 tag; + u32 fid; + u32 maxmsg; /* Tversion, Rversion */ + char version[IXP_MAX_VERSION]; /* Tversion, Rversion */ + u16 oldtag; /* Tflush */ + char errstr[IXP_MAX_ERROR]; /* Rerror */ + Qid qid; /* Rattach, Ropen, Rcreate */ + u32 iounit; /* Ropen, Rcreate */ + Qid aqid; /* Rauth */ + u32 afid; /* Tauth, Tattach */ + char uname[IXP_MAX_ULEN]; /* Tauth, Tattach */ + char aname[IXP_MAX_FLEN]; /* Tauth, Tattach */ + u32 perm; /* Tcreate */ + char name[IXP_MAX_FLEN]; /* Tcreate */ + u8 mode; /* Tcreate, Topen */ + u32 newfid; /* Twalk */ + u16 nwname; /* Twalk */ + char *wname[IXP_MAX_WELEM]; /* Twalk */ + u16 nwqid; /* Rwalk */ + Qid wqid[IXP_MAX_WELEM]; /* Rwalk */ + u64 offset; /* Tread, Twrite */ + u32 count; /* Tread, Twrite, Rread */ + Stat stat; /* Rstat */ + u16 nstat; /* Twstat, Rstat */ + u8 data[IXP_MAX_MSG]; /* Twrite, Rread, Twstat, + * Rstat */ +} Fcall; + +typedef struct IXPServer IXPServer; +typedef struct IXPConn IXPConn; +typedef struct { + u8 id; + int (*tfunc) (IXPServer *, IXPConn *); +} IXPTFunc; + +struct IXPConn { + int fd; + int dont_close; + void (*read) (IXPServer *, IXPConn *); + void *aux; +}; + +struct IXPServer { + int running; + IXPConn conn[IXP_MAX_CONN]; + void (*freeconn) (IXPServer *, IXPConn *); + int maxfd; + fd_set rd; + IXPTFunc *funcs; + Fcall fcall; + char *errstr; +}; + +typedef struct { + int fd; + u32 root_fid; + Qid root_qid; + Fcall fcall; + char *errstr; +} IXPClient; + +/* client.c */ +int ixp_client_init(IXPClient * c, char *sockfile); +void ixp_client_deinit(IXPClient * c); +int ixp_client_remove(IXPClient * c, u32 newfid, char *filepath); +int +ixp_client_create(IXPClient * c, u32 dirfid, char *name, u32 perm, + u8 mode); +int ixp_client_walk(IXPClient * c, u32 newfid, char *filepath); +int ixp_client_open(IXPClient * c, u32 newfid, char *filepath, u8 mode); +u32 +ixp_client_read(IXPClient * c, u32 fid, u64 offset, void *result, + u32 res_len); +u32 +ixp_client_write(IXPClient * c, u32 fid, u64 offset, u32 count, + u8 * data); +int ixp_client_close(IXPClient * c, u32 fid); + + +/* convert.c */ +void *ixp_enc_u8(u8 * msg, u8 val); +void *ixp_dec_u8(u8 * msg, u8 * val); +void *ixp_enc_u16(u8 * msg, u16 val); +void *ixp_dec_u16(u8 * msg, u16 * val); +void *ixp_enc_u32(u8 * msg, u32 val); +void *ixp_dec_u32(u8 * msg, u32 * val); +void *ixp_enc_u64(u8 * msg, u64 val); +void *ixp_dec_u64(u8 * msg, u64 * val); +void *ixp_enc_string(u8 * msg, const char *s); +void *ixp_dec_string(u8 * msg, char *string, u16 stringlen, u16 * len); +void *ixp_enc_data(u8 * msg, u8 * data, u32 datalen); +void *ixp_dec_data(u8 * msg, u8 * data, u32 datalen); +void *ixp_enc_prefix(u8 * msg, u32 size, u8 id, u16 tag); +void *ixp_dec_prefix(u8 * msg, u32 * size, u8 * id, u16 * tag); +void *ixp_enc_qid(u8 * msg, Qid * qid); +void *ixp_dec_qid(u8 * msg, Qid * qid); +void *ixp_enc_stat(u8 * msg, Stat * stat); +void *ixp_dec_stat(u8 * msg, Stat * stat); + +/* message.c */ +u16 ixp_sizeof_stat(Stat * stat); +u32 ixp_fcall_to_msg(Fcall * fcall, void *msg, u32 msglen); +u32 ixp_msg_to_fcall(void *msg, u32 msglen, Fcall * fcall); + +/* server.c */ +IXPConn * +ixp_server_add_conn(IXPServer * s, int fd, int dont_close, + void (*read) (IXPServer *, IXPConn *)); + int ixp_server_tversion(IXPServer *, IXPConn * c); + void ixp_server_rm_conn(IXPServer * s, IXPConn * c); + void ixp_server_loop(IXPServer * s); + int ixp_server_init(IXPServer * s, char *sockfile, IXPTFunc * funcs, + void (*freeconn) (IXPServer *, IXPConn *)); + void ixp_server_deinit(IXPServer * s); + +/* socket.c */ + int ixp_connect_sock(char *sockfile); + int ixp_accept_sock(int fd); + int ixp_create_sock(char *sockfile, char **errstr); + +/* transport.c */ + u32 ixp_send_message(int fd, void *msg, u32 msize, char **errstr); + u32 ixp_recv_message(int fd, void *msg, u32 msglen, char **errstr); diff --git a/libixp2/message.c b/libixp2/message.c new file mode 100644 index 00000000..846c34f3 --- /dev/null +++ b/libixp2/message.c @@ -0,0 +1,293 @@ +/* + * (C)opyright MMIV-MMV Anselm R. Garbe + * See LICENSE file for license details. + */ + +#include +#include +#include "ixp.h" + +static u16 +sizeof_string(const char *s) +{ + return sizeof(u16) + strlen(s); +} + +u16 +ixp_sizeof_stat(Stat * stat) +{ + return sizeof(Qid) + + 2 * sizeof(u16) + + 4 * sizeof(u32) + + sizeof(u64) + + sizeof_string(stat->name) + + sizeof_string(stat->uid) + + sizeof_string(stat->gid) + + sizeof_string(stat->muid); +} + +u32 +ixp_fcall_to_msg(Fcall * fcall, void *msg, u32 msglen) +{ + u32 i, msize = sizeof(u8) + sizeof(u16) + sizeof(u32); + void *p = msg; + + switch (fcall->id) { + case TVERSION: + case RVERSION: + msize += sizeof(u32) + sizeof_string(fcall->version); + break; + case TAUTH: + msize += + sizeof(u32) + sizeof_string(fcall->uname) + + sizeof_string(fcall->aname); + break; + case RAUTH: + case RATTACH: + msize += sizeof(Qid); + break; + case TATTACH: + msize += + 2 * sizeof(u32) + sizeof_string(fcall->uname) + + sizeof_string(fcall->aname); + break; + case RERROR: + msize += sizeof_string(fcall->errstr) + sizeof(u32); + break; + case RWRITE: + case TCLUNK: + case TREMOVE: + case TSTAT: + msize += sizeof(u32); + break; + case TWALK: + msize += sizeof(u16) + 2 * sizeof(u32); + for (i = 0; i < fcall->nwname; i++) + msize += sizeof_string(fcall->wname[i]); + break; + case TFLUSH: + msize += sizeof(u16); + break; + case RWALK: + msize += sizeof(u16) + fcall->nwqid * sizeof(Qid); + break; + case TOPEN: + msize += sizeof(u32) + sizeof(u8); + break; + case ROPEN: + case RCREATE: + msize += sizeof(Qid) + sizeof(u32); + break; + case TCREATE: + msize += sizeof(u8) + 2 * sizeof(u32) + sizeof_string(fcall->name); + break; + case TREAD: + msize += 2 * sizeof(u32) + sizeof(u64); + break; + case RREAD: + msize += sizeof(u32) + fcall->count; + break; + case TWRITE: + msize += 2 * sizeof(u32) + sizeof(u64) + fcall->count; + break; + case RSTAT: + msize += ixp_sizeof_stat(&fcall->stat); + break; + case TWSTAT: + msize += sizeof(u32) + ixp_sizeof_stat(&fcall->stat); + break; + default: + break; + } + + if (msize > msglen) + return 0; + p = ixp_enc_prefix(p, msize, fcall->id, fcall->tag); + + switch (fcall->id) { + case TVERSION: + case RVERSION: + p = ixp_enc_u32(p, fcall->maxmsg); + p = ixp_enc_string(p, fcall->version); + break; + case TAUTH: + p = ixp_enc_u32(p, fcall->afid); + p = ixp_enc_string(p, fcall->uname); + p = ixp_enc_string(p, fcall->aname); + break; + case RAUTH: + case RATTACH: + p = ixp_enc_qid(p, &fcall->qid); + break; + case TATTACH: + p = ixp_enc_u32(p, fcall->fid); + p = ixp_enc_u32(p, fcall->afid); + p = ixp_enc_string(p, fcall->uname); + p = ixp_enc_string(p, fcall->aname); + break; + case RERROR: + p = ixp_enc_string(p, fcall->errstr); + break; + case TFLUSH: + p = ixp_enc_u16(p, fcall->oldtag); + break; + case TWALK: + p = ixp_enc_u32(p, fcall->fid); + p = ixp_enc_u32(p, fcall->newfid); + p = ixp_enc_u16(p, fcall->nwname); + for (i = 0; i < fcall->nwname; i++) + p = ixp_enc_string(p, fcall->wname[i]); + break; + case RWALK: + p = ixp_enc_u16(p, fcall->nwqid); + for (i = 0; i < fcall->nwqid; i++) + p = ixp_enc_qid(p, &fcall->wqid[i]); + break; + case TOPEN: + p = ixp_enc_u32(p, fcall->fid); + p = ixp_enc_u8(p, fcall->mode); + break; + case ROPEN: + case RCREATE: + p = ixp_enc_qid(p, &fcall->qid); + p = ixp_enc_u32(p, fcall->iounit); + break; + case TCREATE: + p = ixp_enc_u32(p, fcall->fid); + p = ixp_enc_string(p, fcall->name); + p = ixp_enc_u32(p, fcall->perm); + p = ixp_enc_u8(p, fcall->mode); + break; + case TREAD: + p = ixp_enc_u32(p, fcall->fid); + p = ixp_enc_u64(p, fcall->offset); + p = ixp_enc_u32(p, fcall->count); + break; + case RREAD: + p = ixp_enc_u32(p, fcall->count); + p = ixp_enc_data(p, fcall->data, fcall->count); + break; + case TWRITE: + p = ixp_enc_u32(p, fcall->fid); + p = ixp_enc_u64(p, fcall->offset); + p = ixp_enc_u32(p, fcall->count); + p = ixp_enc_data(p, fcall->data, fcall->count); + break; + case RWRITE: + p = ixp_enc_u32(p, fcall->count); + break; + case TCLUNK: + case TREMOVE: + case TSTAT: + p = ixp_enc_u32(p, fcall->fid); + break; + case RSTAT: + p = ixp_enc_stat(p, &fcall->stat); + break; + case TWSTAT: + p = ixp_enc_u32(p, fcall->fid); + p = ixp_enc_stat(p, &fcall->stat); + break; + } + return msize; +} + +u32 +ixp_msg_to_fcall(void *msg, u32 msglen, Fcall * fcall) +{ + u32 i, msize; + u16 len; + void *p = ixp_dec_prefix(msg, &msize, &fcall->id, &fcall->tag); + + if (msize > msglen) /* bad message */ + return 0; + + switch (fcall->id) { + case TVERSION: + case RVERSION: + p = ixp_dec_u32(p, &fcall->maxmsg); + p = ixp_dec_string(p, fcall->version, sizeof(fcall->version), + &len); + break; + case TAUTH: + p = ixp_dec_u32(p, &fcall->afid); + p = ixp_dec_string(p, fcall->uname, sizeof(fcall->uname), &len); + p = ixp_dec_string(p, fcall->aname, sizeof(fcall->aname), &len); + break; + case RAUTH: + case RATTACH: + p = ixp_dec_qid(p, &fcall->qid); + break; + case TATTACH: + p = ixp_dec_u32(p, &fcall->fid); + p = ixp_dec_u32(p, &fcall->afid); + p = ixp_dec_string(p, fcall->uname, sizeof(fcall->uname), &len); + p = ixp_dec_string(p, fcall->aname, sizeof(fcall->aname), &len); + break; + case RERROR: + p = ixp_dec_string(p, fcall->errstr, sizeof(fcall->errstr), &len); + break; + case TFLUSH: + p = ixp_dec_u16(p, &fcall->oldtag); + break; + case TWALK: + p = ixp_dec_u32(p, &fcall->fid); + p = ixp_dec_u32(p, &fcall->newfid); + p = ixp_dec_u16(p, &fcall->nwname); + for (i = 0; i < fcall->nwname; i++) + p = ixp_dec_string(p, fcall->wname[i], IXP_MAX_FLEN, &len); + break; + case RWALK: + p = ixp_dec_u16(p, &fcall->nwqid); + for (i = 0; i < fcall->nwqid; i++) + p = ixp_dec_qid(p, &fcall->wqid[i]); + break; + case TOPEN: + p = ixp_dec_u32(p, &fcall->fid); + p = ixp_dec_u8(p, &fcall->mode); + break; + case ROPEN: + case RCREATE: + p = ixp_dec_qid(p, &fcall->qid); + p = ixp_dec_u32(p, &fcall->iounit); + break; + case TCREATE: + p = ixp_dec_u32(p, &fcall->fid); + p = ixp_dec_string(p, fcall->name, sizeof(fcall->name), &len); + p = ixp_dec_u32(p, &fcall->perm); + p = ixp_dec_u8(p, &fcall->mode); + break; + case TREAD: + p = ixp_dec_u32(p, &fcall->fid); + p = ixp_dec_u64(p, &fcall->offset); + p = ixp_dec_u32(p, &fcall->count); + break; + case RREAD: + p = ixp_dec_u32(p, &fcall->count); + p = ixp_dec_data(p, fcall->data, fcall->count); + break; + case TWRITE: + p = ixp_dec_u32(p, &fcall->fid); + p = ixp_dec_u64(p, &fcall->offset); + p = ixp_dec_u32(p, &fcall->count); + p = ixp_dec_data(p, fcall->data, fcall->count); + break; + case RWRITE: + p = ixp_dec_u32(p, &fcall->count); + break; + case TCLUNK: + case TREMOVE: + case TSTAT: + p = ixp_dec_u32(p, &fcall->fid); + break; + case RSTAT: + p = ixp_dec_stat(p, &fcall->stat); + break; + case TWSTAT: + p = ixp_dec_u32(p, &fcall->fid); + p = ixp_dec_stat(p, &fcall->stat); + break; + } + + return msize; +} diff --git a/libixp2/server.c b/libixp2/server.c new file mode 100644 index 00000000..31d69a55 --- /dev/null +++ b/libixp2/server.c @@ -0,0 +1,201 @@ +/* + * (C)opyright MMIV-MMV Anselm R. Garbe + * See LICENSE file for license details. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "ixp.h" +#include "cext.h" + +static IXPConn zero_conn = {-1, 0, 0, 0}; +static u8 msg[IXP_MAX_MSG]; + +static IXPConn * +next_free_conn(IXPServer * s) +{ + int i; + for (i = 0; i < IXP_MAX_CONN; i++) + if (s->conn[i].fd < 0) + return &s->conn[i]; + return nil; +} + +static void +prepare_select(IXPServer * s) +{ + int i; + FD_ZERO(&s->rd); + for (i = 0; i < IXP_MAX_CONN; i++) { + if (s->conn[i].fd >= 0) { + if (s->maxfd < s->conn[i].fd) + s->maxfd = s->conn[i].fd; + if (s->conn[i].read) + FD_SET(s->conn[i].fd, &s->rd); + } + } +} + +void +ixp_server_rm_conn(IXPServer * s, IXPConn * c) +{ + if (!c->dont_close) { + shutdown(c->fd, SHUT_RDWR); + close(c->fd); + } + if (s->freeconn) + s->freeconn(s, c); + *c = zero_conn; +} + +static IXPConn * +init_conn(IXPConn * c, int fd, int dont_close, + void (*read) (IXPServer *, IXPConn *)) +{ + *c = zero_conn; + c->fd = fd; + c->dont_close = dont_close; + c->read = read; + return c; +} + +IXPConn * +ixp_server_add_conn(IXPServer * s, int fd, int dont_close, + void (*read) (IXPServer *, IXPConn *)) +{ + IXPConn *c = next_free_conn(s); + if (!c) + return nil; + return init_conn(c, fd, dont_close, read); +} + +static void +handle_conns(IXPServer * s) +{ + int i; + for (i = 0; i < IXP_MAX_CONN; i++) { + if (s->conn[i].fd >= 0) { + if (FD_ISSET(s->conn[i].fd, &s->rd) && s->conn[i].read) + /* call back read handler */ + s->conn[i].read(s, &s->conn[i]); + } + } +} + +static void +server_client_read(IXPServer * s, IXPConn * c) +{ + u32 i, msize; + s->errstr = 0; + if (!(msize = ixp_recv_message(c->fd, msg, IXP_MAX_MSG, &s->errstr))) { + ixp_server_rm_conn(s, c); + return; + } + fprintf(stderr, "msize=%d\n", msize); + if ((msize = ixp_msg_to_fcall(msg, IXP_MAX_MSG, &s->fcall))) { + for (i = 0; s->funcs && s->funcs[i].id; i++) { + if (s->funcs[i].id == s->fcall.id) { + if (!s->funcs[i].tfunc(s, c)) + break; + msize = ixp_fcall_to_msg(&s->fcall, msg, s->fcall.maxmsg); + fprintf(stderr, "msize=%d\n", msize); + if (ixp_send_message(c->fd, msg, msize, &s->errstr) != msize) + break; + return; + } + } + } + if (!s->errstr) + s->errstr = "function not supported"; + s->fcall.id = RERROR; + _strlcpy(s->fcall.errstr, s->errstr, sizeof(s->fcall.errstr)); + msize = ixp_fcall_to_msg(&s->fcall, msg, IXP_MAX_MSG); + if (ixp_send_message(c->fd, msg, msize, &s->errstr) != msize) + ixp_server_rm_conn(s, c); +} + +static void +server_read(IXPServer * s, IXPConn * c) +{ + int fd; + IXPConn *new = next_free_conn(s); + if (new && ((fd = ixp_accept_sock(c->fd)) >= 0)) + init_conn(new, fd, 0, server_client_read); +} + +void +ixp_server_loop(IXPServer * s) +{ + int r; + s->running = TRUE; + s->errstr = 0; + + /* main loop */ + while (s->running) { + + prepare_select(s); + + r = select(s->maxfd + 1, &s->rd, 0, 0, 0); + if (r == -1 && errno == EINTR) + continue; + if (r < 0) { + s->errstr = "fatal select error"; + break; /* allow cleanups in IXP using app */ + } else if (r > 0) + handle_conns(s); + } +} + +int +ixp_server_tversion(IXPServer * s, IXPConn * c) +{ + fprintf(stderr, "got version %s (%s) %d (%d)\n", s->fcall.version, IXP_VERSION, + s->fcall.maxmsg, IXP_MAX_MSG); + if (strncmp(s->fcall.version, IXP_VERSION, strlen(IXP_VERSION))) { + s->errstr = "9P versions differ"; + return FALSE; + } + else if (s->fcall.maxmsg > IXP_MAX_MSG) + s->fcall.maxmsg = IXP_MAX_MSG; + s->fcall.id = RVERSION; + return TRUE; +} + +int +ixp_server_init(IXPServer * s, char *sockfile, IXPTFunc * funcs, + void (*freeconn) (IXPServer *, IXPConn *)) +{ + int fd, i; + s->funcs = funcs; + s->freeconn = freeconn; + s->errstr = 0; + if (!sockfile) { + s->errstr = "no socket file provided or invalid directory"; + return FALSE; + } + if ((fd = ixp_create_sock(sockfile, &s->errstr)) < 0) + return FALSE; + for (i = 0; i < IXP_MAX_CONN; i++) + s->conn[i] = zero_conn; + ixp_server_add_conn(s, fd, 0, server_read); + return TRUE; +} + +void +ixp_server_deinit(IXPServer * s) +{ + int i; + /* shut down server */ + for (i = 0; i < IXP_MAX_CONN; i++) + if (s->conn[i].fd >= 0) + ixp_server_rm_conn(s, &s->conn[i]); +} diff --git a/libixp2/socket.c b/libixp2/socket.c new file mode 100644 index 00000000..da6aaa8d --- /dev/null +++ b/libixp2/socket.c @@ -0,0 +1,86 @@ +/* + * (C)opyright MMIV-MMV Anselm R. Garbe + * See LICENSE file for license details. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "cext.h" +#include "ixp.h" + +int +ixp_connect_sock(char *sockfile) +{ + int fd = 0; + struct sockaddr_un addr = {0}; + socklen_t su_len; + + /* init */ + addr.sun_family = AF_UNIX; + strncpy(addr.sun_path, sockfile, sizeof(addr.sun_path)); + su_len = sizeof(struct sockaddr) + strlen(addr.sun_path); + + if ((fd = socket(AF_UNIX, SOCK_STREAM, 0)) < 0) + return -1; + if (connect(fd, (struct sockaddr *) & addr, su_len)) { + close(fd); + return -1; + } + return fd; +} + +int +ixp_accept_sock(int fd) +{ + socklen_t su_len; + struct sockaddr_un addr = {0}; + + su_len = sizeof(struct sockaddr); + return accept(fd, (struct sockaddr *) & addr, &su_len); +} + +int +ixp_create_sock(char *sockfile, char **errstr) +{ + int fd; + int yes = 1; + struct sockaddr_un addr = {0}; + socklen_t su_len; + + signal(SIGPIPE, SIG_IGN); + if ((fd = socket(AF_UNIX, SOCK_STREAM, 0)) < 0) { + *errstr = "cannot open socket"; + return -1; + } + if (setsockopt(fd, SOL_SOCKET, SO_REUSEADDR, + (char *) &yes, sizeof(yes)) < 0) { + *errstr = "cannot set socket options"; + close(fd); + return -1; + } + addr.sun_family = AF_UNIX; + strncpy(addr.sun_path, sockfile, sizeof(addr.sun_path)); + su_len = sizeof(struct sockaddr) + strlen(addr.sun_path); + + if (bind(fd, (struct sockaddr *) & addr, su_len) < 0) { + *errstr = "cannot bind socket"; + close(fd); + return -1; + } + chmod(sockfile, S_IRWXU); + + if (listen(fd, IXP_MAX_CONN) < 0) { + *errstr = "cannot listen on socket"; + close(fd); + return -1; + } + return fd; +} diff --git a/libixp2/transport.c b/libixp2/transport.c new file mode 100644 index 00000000..e287ef8b --- /dev/null +++ b/libixp2/transport.c @@ -0,0 +1,78 @@ +/* + * (C)opyright MMIV-MMV Anselm R. Garbe + * See LICENSE file for license details. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "ixp.h" + +#include + +u32 +ixp_send_message(int fd, void *msg, u32 msize, char **errstr) +{ + u32 num = 0; + int r; + + /* send message */ + while (num < msize) { + r = write(fd, msg + num, msize - num); + if (r == -1 && errno == EINTR) + continue; + if (r < 1) { + *errstr = "cannot send message"; + return 0; + } + num += r; + } + return num; +} + +static u32 +ixp_recv_data(int fd, void *msg, u32 msize, char **errstr) +{ + u32 num = 0; + int r = 0; + + /* receive data */ + while (num < msize) { + r = read(fd, msg + num, msize - num); + if (r == -1 && errno == EINTR) + continue; + if (r < 1) { + *errstr = "cannot receive data"; + return 0; + } + num += r; + } + return num; +} + +u32 +ixp_recv_message(int fd, void *msg, u32 msglen, char **errstr) +{ + u32 msize; + + /* receive header */ + if (ixp_recv_data(fd, msg, sizeof(u32), errstr) != sizeof(u32)) + return 0; + ixp_dec_u32(msg, &msize); + if (msize > msglen) { + *errstr = "message size exceeds buffer size"; + return 0; + } + /* receive message */ + if (ixp_recv_data(fd, msg + sizeof(u32), msize - sizeof(u32), errstr) + != msize - sizeof(u32)) + return 0; + return msize; +} diff --git a/liblitz/Makefile b/liblitz/Makefile new file mode 100644 index 00000000..36cfc73f --- /dev/null +++ b/liblitz/Makefile @@ -0,0 +1,25 @@ +# liblitz - non wimp GUI toolkit +# (C)opyright MMIV-MMV Anselm R. Garbe + +include ../config.mk + +CFLAGS += -I../libixp -I../libcext +LDFLAGS += -L../libixp -lixp -L../libcext -lcext + +SRC = draw.c geometry.c kb.c mouse.c util.c +OBJ = ${SRC:.c=.o} + +all: liblitz + @echo built liblitz + +.c.o: + @echo CC $< + @${CC} -c ${CFLAGS} $< + +liblitz: ${OBJ} + @echo AR $@.a + @${AR} $@.a ${OBJ} + @${RANLIB} $@.a + +clean: + rm -f liblitz.a *.o diff --git a/liblitz/blitz.h b/liblitz/blitz.h new file mode 100644 index 00000000..b0620fed --- /dev/null +++ b/liblitz/blitz.h @@ -0,0 +1,64 @@ +/* + * (C)opyright MMIV-MMV Anselm R. Garbe + * See LICENSE file for license details. + */ + +#include + +#define BLITZ_FONT "fixed" +#define BLITZ_SEL_FG_COLOR "#eeeeee" +#define BLITZ_SEL_BG_COLOR "#506070" +#define BLITZ_SEL_BORDER_COLOR "#708090" +#define BLITZ_NORM_FG_COLOR "#bbbbbb" +#define BLITZ_NORM_BG_COLOR "#222222" +#define BLITZ_NORM_BORDER_COLOR "#000000" + +typedef enum { + CENTER, WEST, NWEST, NORTH, NEAST, EAST, + SEAST, SOUTH, SWEST +} Align; + +typedef struct Draw Draw; + +struct Draw { + Drawable drawable; + GC gc; + unsigned long bg; + unsigned long fg; + unsigned long border; + Align align; + XFontStruct *font; + XRectangle rect; /* relative rect */ + XRectangle *notch; /* relative notch rect */ + char *data; +}; + +/* draw.c */ +XFontStruct *blitz_getfont(Display * dpy, char *fontstr); +unsigned long blitz_loadcolor(Display * dpy, int mon, char *colstr); +void blitz_drawlabel(Display * dpy, Draw * r); +void blitz_drawmeter(Display * dpy, Draw * r); +void blitz_drawlabelnoborder(Display * dpy, Draw * r); + +/* geometry.c */ +int +blitz_strtorect(Display * dpy, XRectangle * root, XRectangle * r, + char *val); +int blitz_ispointinrect(int x, int y, XRectangle * r); +int blitz_distance(XRectangle * origin, XRectangle * target); +void +blitz_getbasegeometry(void **items, unsigned int *size, + unsigned int *cols, unsigned int *rows); + +/* mouse.c */ +char *blitz_buttontostr(unsigned int button); +unsigned int blitz_strtobutton(char *val); + +/* kb.c */ +char *blitz_modtostr(unsigned long mod); +unsigned long blitz_strtomod(char *val); + +/* util.c */ +long long +_strtonum(const char *numstr, long long minval, + long long maxval); diff --git a/liblitz/draw.c b/liblitz/draw.c new file mode 100644 index 00000000..aca16738 --- /dev/null +++ b/liblitz/draw.c @@ -0,0 +1,181 @@ +/* + * (C)opyright MMIV-MMV Anselm R. Garbe + * See LICENSE file for license details. + */ + +#include +#include +#include "blitz.h" + +#include + +XFontStruct * +blitz_getfont(Display * dpy, char *fontstr) +{ + XFontStruct *font; + font = XLoadQueryFont(dpy, fontstr); + if (!font) { + font = XLoadQueryFont(dpy, "fixed"); + if (!font) { + fprintf(stderr, "%s", "wmii: error, cannot load fixed font\n"); + return 0; + } + } + return font; +} + +unsigned long +blitz_loadcolor(Display * dpy, int mon, char *colstr) +{ + XColor color; + char col[8]; + + _strlcpy(col, colstr, sizeof(col)); + col[7] = '\0'; + XAllocNamedColor(dpy, DefaultColormap(dpy, mon), col, &color, &color); + return color.pixel; +} + +static void +draw_bg(Display * dpy, Draw * d) +{ + XRectangle rect[4]; + XSetForeground(dpy, d->gc, d->bg); + if (!d->notch) { + XFillRectangles(dpy, d->drawable, d->gc, &d->rect, 1); + return; + } + rect[0] = d->rect; + rect[0].height = d->notch->y; + rect[1] = d->rect; + rect[1].y = d->notch->y; + rect[1].width = d->notch->x; + rect[1].height = d->notch->height; + rect[2].x = d->notch->x + d->notch->width; + rect[2].y = d->notch->y; + rect[2].width = d->rect.width - (d->notch->x + d->notch->width); + rect[2].height = d->notch->height; + rect[3] = d->rect; + rect[3].y = d->notch->y + d->notch->height; + rect[3].height = d->rect.height - (d->notch->y + d->notch->height); + XFillRectangles(dpy, d->drawable, d->gc, rect, 4); +} + +static void +_draw_border(Display * dpy, Draw * d) +{ + XPoint points[5]; + + XSetLineAttributes(dpy, d->gc, 1, LineSolid, CapButt, JoinMiter); + XSetForeground(dpy, d->gc, d->border); + points[0].x = d->rect.x; + points[0].y = d->rect.y; + points[1].x = d->rect.width - 1; + points[1].y = 0; + points[2].x = 0; + points[2].y = d->rect.height - 1; + points[3].x = -(d->rect.width - 1); + points[3].y = 0; + points[4].x = 0; + points[4].y = -(d->rect.height - 1); + XDrawLines(dpy, d->drawable, d->gc, points, 5, CoordModePrevious); +} + +static void +draw_text(Display * dpy, Draw * d) +{ + unsigned int x, y, w, h, shortened = FALSE; + size_t len = 0; + static char text[2048]; + + if (!d->data) + return; + + len = strlen(d->data); + _strlcpy(text, d->data, sizeof(text)); + XSetFont(dpy, d->gc, d->font->fid); + h = d->font->ascent + d->font->descent; + y = d->rect.y + d->rect.height / 2 - h / 2 + d->font->ascent; + + /* shorten text if necessary */ + while (len && (w = XTextWidth(d->font, text, len)) > d->rect.width) { + text[len - 1] = '\0'; + len--; + shortened = TRUE; + } + + if (w > d->rect.width) + return; + + /* mark shortened info in the string */ + if (shortened) { + if (len > 3) + text[len - 3] = '.'; + if (len > 2) + text[len - 2] = '.'; + if (len > 1) + text[len - 1] = '.'; + } + switch (d->align) { + case WEST: + x = d->rect.x + h / 2; + break; + case EAST: + x = d->rect.x + d->rect.width - (h / 2 + w); + break; + default: /* CENTER */ + x = d->rect.x + (d->rect.width - w) / 2; + break; + } + + XSetBackground(dpy, d->gc, d->bg); + /* + * uncomment, if you want get an shadow effect XSetForeground(dpy, + * d->gc, BlackPixel(dpy, DefaultScreen(dpy))); XDrawString(dpy, + * d->drawable, d->gc, x + 1, y + 1, text, len); + */ + XSetForeground(dpy, d->gc, d->fg); + XDrawString(dpy, d->drawable, d->gc, x, y, text, len); +} + +/* draws meter */ +void +blitz_drawmeter(Display * dpy, Draw * d) +{ + unsigned int offy, mh, val, w = d->rect.width - 4; + + if (!d->data || strncmp(d->data, "%m:", 3)) + return; + + val = _strtonum(&d->data[3], 0, 100); + draw_bg(dpy, d); + _draw_border(dpy, d); + + /* draw bg gradient */ + mh = ((d->rect.height - 4) * val) / 100; + offy = d->rect.y + d->rect.height - 2 - mh; + XSetForeground(dpy, d->gc, d->fg); + XFillRectangle(dpy, d->drawable, d->gc, d->rect.x + 2, offy, w, mh); +} + +static void +_draw_label(Display * dpy, Draw * d) +{ + draw_bg(dpy, d); + if (d->data) + draw_text(dpy, d); +} + +/* draws label */ +void +blitz_drawlabel(Display * dpy, Draw * d) +{ + _draw_label(dpy, d); + _draw_border(dpy, d); +} + +void +blitz_drawlabelnoborder(Display * dpy, Draw * d) +{ + _draw_label(dpy, d); +} diff --git a/liblitz/geometry.c b/liblitz/geometry.c new file mode 100644 index 00000000..a52d4231 --- /dev/null +++ b/liblitz/geometry.c @@ -0,0 +1,222 @@ +/* + * (C)opyright MMIV-MMV Anselm R. Garbe + * See LICENSE file for license details. + */ + +#include +#include +#include + +#include "blitz.h" +#include + +static int +strtoalign(Align * result, char *val) +{ + /* + * note, resize allows syntax like "east-20", this we cannot do + * include zero termination in strncmp checking! + */ + + *result = CENTER; + if (!strncmp(val, "west", 4)) + *result = WEST; + else if (!strncmp(val, "nwest", 5)) + *result = NWEST; + else if (!strncmp(val, "north", 5)) + *result = NORTH; + else if (!strncmp(val, "neast", 5)) + *result = NEAST; + else if (!strncmp(val, "east", 4)) + *result = EAST; + else if (!strncmp(val, "seast", 5)) + *result = SEAST; + else if (!strncmp(val, "south", 5)) + *result = SOUTH; + else if (!strncmp(val, "swest", 5)) + *result = SWEST; + else if (!strncmp(val, "center", 6)) + *result = CENTER; + else + return FALSE; + return TRUE; +} + +/** + * Basic Syntax: ,,, + * Each component can be of following format: + * <...> = [+|-]0..n|[[+|-]0..n] + */ +int +blitz_strtorect(Display * dpy, XRectangle * root, XRectangle * r, + char *val) +{ + char buf[64]; + char *x, *y, *w, *h; + char *p; + int sx, sy, sw, sh; + + if (!val) + return FALSE; + sx = sy = sw = sh = 0; + x = y = w = h = 0; + _strlcpy(buf, val, sizeof(buf)); + + x = strtok_r(buf, ",", &p); + if (x) { + y = strtok_r(0, ",", &p); + if (y) { + w = strtok_r(0, ",", &p); + if (w) { + h = strtok_r(0, "", &p); + } + } + } + if (x && (sx = (x[0] >= '0') && (x[0] <= '9'))) + r->x = _strtonum(x, 0, 65535); + if (y && (sy = (y[0] >= '0') && (y[0] <= '9'))) + r->y = _strtonum(y, 0, 65535); + if (w && (sw = (w[0] >= '0') && (w[0] <= '9'))) + r->width = _strtonum(w, 0, 65535); + if (h && (sh = (h[0] >= '0') && (h[0] <= '9'))) + r->height = _strtonum(h, 0, 65535); + + if (!sx && !sw && x && w + && x[0] != '-' && x[0] != '+' && w[0] != '-' && w[0] != '+') { + Align ax, aw; + strtoalign(&ax, x); + strtoalign(&aw, w); + if ((ax == CENTER) && (aw == EAST)) { + r->x = root->x + root->width / 2; + r->width = root->width / 2; + } else { + r->x = root->x; + if (aw == CENTER) { + r->width = root->width / 2; + } else { + r->width = root->width; + } + } + } else if (!sx && x && x[0] != '-' && x[0] != '+') { + Align ax; + strtoalign(&ax, x); + if (ax == CENTER) { + r->x = root->x + (root->width / 2) - (r->width / 2); + } else if (ax == EAST) { + r->x = root->x + root->width - r->width; + } else { + r->x = root->x; + } + } else if (!sw && w && w[0] != '-' && w[0] != '+') { + Align aw; + strtoalign(&aw, w); + if (aw == CENTER) { + r->width = (root->width / 2) - r->x; + } else { + r->width = root->width - r->x; + } + } + if (!sy && !sh && y && h + && y[0] != '-' && y[0] != '+' && h[0] != '-' && h[0] != '+') { + Align ay, ah; + strtoalign(&ay, y); + strtoalign(&ah, h); + if ((ay == CENTER) && (ah == SOUTH)) { + r->y = root->y + root->height / 2; + r->height = root->height / 2; + } else { + r->y = root->y; + if (ah == CENTER) { + r->height = root->height / 2; + } else { + r->height = root->height; + } + } + } else if (!sy && y && y[0] != '-' && y[0] != '+') { + Align ay; + strtoalign(&ay, y); + if (ay == CENTER) { + r->y = root->y + (root->height / 2) - (r->height / 2); + } else if (ay == SOUTH) { + r->y = root->y + root->height - r->height; + } else { + r->y = root->y; + } + } else if (!sh && h && h[0] != '-' && h[0] != '+') { + Align ah; + strtoalign(&ah, h); + if (ah == CENTER) { + r->height = (root->height / 2) - r->y; + } else { + r->height = root->height - r->y; + } + } + /* now do final calculations */ + if (x) { + p = strchr(x, '-'); + if (p) + r->x -= _strtonum(++p, 0, 65535); + p = strchr(x, '+'); + if (p) + r->x += _strtonum(++p, 0, 65535); + } + if (y) { + p = strchr(y, '-'); + if (p) + r->y -= _strtonum(++p, 0, 65535); + p = strchr(y, '+'); + if (p) + r->y += _strtonum(++p, 0, 65535); + } + if (w) { + p = strchr(w, '-'); + if (p) + r->width -= _strtonum(++p, 0, 65535); + p = strchr(w, '+'); + if (p) + r->width += _strtonum(++p, 0, 65535); + } + if (h) { + p = strchr(h, '-'); + if (p) + r->height -= _strtonum(++p, 0, 65535); + p = strchr(h, '+'); + if (p) + r->height += _strtonum(++p, 0, 65535); + } + return TRUE; +} + +int +blitz_ispointinrect(int x, int y, XRectangle * r) +{ + return (x >= r->x) && (x <= r->x + r->width) + && (y >= r->y) && (y <= r->y + r->height); +} + +int +blitz_distance(XRectangle * origin, XRectangle * target) +{ + int ox = origin->x + origin->width / 2; + int oy = origin->y + origin->height / 2; + int tx = target->x + target->width / 2; + int ty = target->y + target->height / 2; + + return (int) sqrt((double) (((ox - tx) * (ox - tx)) + + ((oy - ty) * (oy - ty)))); +} + +void +blitz_getbasegeometry(void **items, unsigned int *size, + unsigned int *cols, unsigned int *rows) +{ + float sq, dummy; + + *size = count_items((void **) items); + sq = sqrt(*size); + if (modff(sq, &dummy) < 0.5) + *rows = floor(sq); + else + *rows = ceil(sq); + *cols = ((*rows) * (*rows)) < (*size) ? *rows + 1 : *rows; +} diff --git a/liblitz/kb.c b/liblitz/kb.c new file mode 100644 index 00000000..4b92da5f --- /dev/null +++ b/liblitz/kb.c @@ -0,0 +1,55 @@ +/* + * (C)opyright MMIV-MMV Anselm R. Garbe + * See LICENSE file for license details. + */ + +#include + +#include "blitz.h" + +#include + +/* free the result manually! */ +char * +blitz_modtostr(unsigned long mod) +{ + char result[60]; + result[0] = '\0'; + + if (mod & ShiftMask) + _strlcat(result, "S-", sizeof(result)); + if (mod & ControlMask) + _strlcat(result, "C-", sizeof(result)); + if (mod & Mod1Mask) + _strlcat(result, "M-", sizeof(result)); + if (mod & Mod2Mask) + _strlcat(result, "M2-", sizeof(result)); + if (mod & Mod3Mask) + _strlcat(result, "M3-", sizeof(result)); + if (mod & Mod4Mask) + _strlcat(result, "WIN-", sizeof(result)); + if (mod & Mod5Mask) + _strlcat(result, "M5-", sizeof(result)); + return estrdup(result); +} + +unsigned long +blitz_strtomod(char *val) +{ + unsigned long mod = 0; + if (strstr(val, "S-")) + mod |= ShiftMask; + if (strstr(val, "C-")) + mod |= ControlMask; + if (strstr(val, "M-")) + mod |= Mod1Mask; + if (strstr(val, "M2-")) + mod |= Mod2Mask; + if (strstr(val, "M3-")) + mod |= Mod3Mask; + if (strstr(val, "WIN-")) + mod |= Mod4Mask; + if (strstr(val, "M5-")) + mod |= Mod5Mask; + return mod; +} diff --git a/liblitz/mouse.c b/liblitz/mouse.c new file mode 100644 index 00000000..3ae3ae9e --- /dev/null +++ b/liblitz/mouse.c @@ -0,0 +1,31 @@ +/* + * (C)opyright MMIV-MMV Anselm R. Garbe + * See LICENSE file for license details. + */ + +#include +#include +#include + +#include "blitz.h" + +#include + +/* free the result manually! */ +char * +blitz_buttontostr(unsigned int button) +{ + char result[8]; + result[0] = '\0'; + snprintf(result, 8, "Button%ud", button - Button1); + return estrdup(result); +} + +unsigned int +blitz_strtobutton(char *val) +{ + unsigned int res = 0; + if (val && strlen(val) > 6 && !strncmp(val, "Button", 6)) + res = _strtonum(&val[6], 1, 5) + Button1; + return res; +} diff --git a/liblitz/util.c b/liblitz/util.c new file mode 100644 index 00000000..90fb9345 --- /dev/null +++ b/liblitz/util.c @@ -0,0 +1,21 @@ +/* + * (C)opyright MMIV-MMV Anselm R. Garbe + * See LICENSE file for license details. + */ + +#include + +#include "cext.h" +#include "blitz.h" + +long long +_strtonum(const char *numstr, long long minval, long long maxval) +{ + const char *errstr; + long long ret = __strtonum(numstr, minval, maxval, &errstr); + if (errstr) + fprintf(stderr, + "liblitz: cannot convert '%s' into integer: %s\n", + numstr, errstr); + return ret; +} diff --git a/libwmii/Makefile b/libwmii/Makefile new file mode 100644 index 00000000..50919996 --- /dev/null +++ b/libwmii/Makefile @@ -0,0 +1,26 @@ +# libwmii - mediator lib for wmii +# (C)opyright MMIV-MMV Anselm R. Garbe + +include ../config.mk + +CFLAGS += -I../liblitz -I../libixp -I../libcext +LDFLAGS += -L../liblitz -llitz -L../libixp -lixp -L../libcext -lcext + +SRC = ixputil.c spawn.c util.c wm.c + +OBJ = ${SRC:.c=.o} + +all: libwmii + @echo built libwmii + +.c.o: + @echo CC $< + @${CC} -c ${CFLAGS} $< + +libwmii: ${OBJ} + @echo AR $@.a + @${AR} $@.a ${OBJ} + @${RANLIB} $@.a + +clean: + rm -f libwmii.a *.o diff --git a/libwmii/ixputil.c b/libwmii/ixputil.c new file mode 100644 index 00000000..b285167a --- /dev/null +++ b/libwmii/ixputil.c @@ -0,0 +1,102 @@ +/* + * (C)opyright MMIV-MMV Anselm R. Garbe + * See LICENSE file for license details. + */ + +#include +#include +#include +#include +#include +#include + +#include "wmii.h" + +#include + +static pid_t mypid; +static char *mysockfile; + +/* convenience stuff ----------------------------------------------- */ + +File * +wmii_create_ixpfile(IXPServer * s, char *key, char *val) +{ + File *f = ixp_create(s, key); + if (f && !is_directory(f)) { + size_t l = val ? strlen(val) : 0; + f->content = l ? strdup(val) : 0; + f->size = l; + return f; + } + /* forbidden, file is directory */ + return 0; +} + +void +wmii_get_ixppath(File * f, char *path, size_t size) +{ + char buf[512]; + + buf[0] = '\0'; + if (path) + _strlcpy(buf, path, sizeof(buf)); + snprintf(path, size, "%s/", f->name); + if (buf[0] != '\0') + _strlcat(path, buf, size); + if (f->parent) + wmii_get_ixppath(f->parent, path, size); +} + +void +wmii_move_ixpfile(File * f, File * to_parent) +{ + File *p = f->parent; + File *fil = p->content; + + /* detach */ + if (p->content == f) + p->content = fil->next; + else { + while (fil->next != f) + fil = fil->next; + fil->next = f->next; + } + f->next = 0; + + + /* attach */ + if (!to_parent->content) + to_parent->content = f; + else { + for (fil = to_parent->content; fil->next; fil = fil->next); + fil->next = f; + } + f->parent = to_parent; +} + +static void +exit_cleanup() +{ + if (mypid == getpid()) + unlink(mysockfile); +} + +IXPServer * +wmii_setup_server(char *sockfile) +{ + IXPServer *s; + + if (!sockfile) { + fprintf(stderr, "%s\n", "libwmii: no socket file provided"); + exit(1); + } + mysockfile = sockfile; + mypid = getpid(); + s = init_server(sockfile, exit_cleanup); + if (!s) { + perror("libwmii: cannot initialize IXP server"); + exit(1); + } + return s; +} diff --git a/libwmii/spawn.c b/libwmii/spawn.c new file mode 100644 index 00000000..36c17c15 --- /dev/null +++ b/libwmii/spawn.c @@ -0,0 +1,31 @@ +/* + * (C)opyright MMIV-MMV Anselm R. Garbe + * See LICENSE file for license details. + */ + +#include +#include +#include +#include +#include + +#include "wmii.h" + +#include + +void +spawn(void *dpy, char *cmd) +{ + /* the questionable double-fork is done to catch all zombies */ + if (fork() == 0) { + if (fork() == 0) { + setsid(); + close(ConnectionNumber(dpy)); + execlp("rc", "rc", "-c", cmd, (char *) 0); + perror("failed"); + exit(1); + } + exit(0); + } + wait(0); +} diff --git a/libwmii/util.c b/libwmii/util.c new file mode 100644 index 00000000..7d1a54c4 --- /dev/null +++ b/libwmii/util.c @@ -0,0 +1,14 @@ +/* + * (C)opyright MMIV-MMV Anselm R. Garbe + * See LICENSE file for license details. + */ + +#include "wmii.h" + +void +swap(void **p1, void **p2) +{ + void *tmp = *p1; + *p1 = *p2; + *p2 = tmp; +} diff --git a/libwmii/wm.c b/libwmii/wm.c new file mode 100644 index 00000000..1d54d58e --- /dev/null +++ b/libwmii/wm.c @@ -0,0 +1,95 @@ +/* + * (C)opyright MMIV-MMV Anselm R. Garbe + * See LICENSE file for license details. + */ + +#include +#include +#include +#include +#include +#include +#include + +#include "blitz.h" + +#include + +int +property(Display * dpy, Window w, Atom a, Atom t, long l, + unsigned char **prop) +{ + Atom real; + int format; + unsigned long res, extra; + int status; + + status = XGetWindowProperty(dpy, w, a, 0L, l, False, t, &real, &format, + &res, &extra, prop); + + if (status != Success || *prop == 0) { + return 0; + } + if (res == 0) { + free((void *) *prop); + } + return res; +} + +void +win_prop(Display * dpy, Window w, Atom a, char *res, int len) +{ + unsigned char *prop; + + if (property(dpy, w, a, XA_STRING, 100L, &prop)) { + _strlcpy(res, (char *) prop, len); + XFree(prop); + } + res[len - 1] = '\0'; + XSync(dpy, False); +} + +void +send_message(Display * dpy, Window w, Atom a, long value) +{ + XEvent e; + e.type = ClientMessage; + e.xclient.window = w; + e.xclient.message_type = a; + e.xclient.format = 32; + e.xclient.data.l[0] = value; + e.xclient.data.l[1] = CurrentTime; + + XSendEvent(dpy, w, False, NoEventMask, &e); + XSync(dpy, False); +} + +#define NUM_MASKS 8 +void +init_lock_modifiers(Display * dpy, unsigned int *valid_mask, + unsigned int *num_lock_mask) +{ + XModifierKeymap *modmap; + KeyCode num_lock; + static int masks[NUM_MASKS] = { + ShiftMask, LockMask, ControlMask, Mod1Mask, + Mod2Mask, Mod3Mask, Mod4Mask, Mod5Mask + }; + int i; + + *num_lock_mask = 0; + modmap = XGetModifierMapping(dpy); + num_lock = XKeysymToKeycode(dpy, XStringToKeysym("Num_Lock")); + + if (modmap && modmap->max_keypermod > 0) { + int max = NUM_MASKS * modmap->max_keypermod; + for (i = 0; i < max; i++) { + if (num_lock && (modmap->modifiermap[i] == num_lock)) { + *num_lock_mask = masks[i / modmap->max_keypermod]; + } + } + } + XFreeModifiermap(modmap); + + *valid_mask = 255 & ~(*num_lock_mask | LockMask); +} diff --git a/libwmii/wmii.h b/libwmii/wmii.h new file mode 100644 index 00000000..d587cc93 --- /dev/null +++ b/libwmii/wmii.h @@ -0,0 +1,39 @@ +/* + * (C)opyright MMIV-MMV Anselm R. Garbe + * See LICENSE file for license details. + */ + +#include "ixp.h" +#include "blitz.h" + +#define MAX_ID 20 +#define MAX_BUF 128 + +typedef struct Action Action; + +struct Action { + char *name; + void (*func) (void *obj, char *); +}; + +/* ixputil.c */ +File *wmii_create_ixpfile(IXPServer * s, char *key, char *val); +void wmii_get_ixppath(File * f, char *path, size_t size); +void wmii_move_ixpfile(File * f, File * to_parent); +IXPServer *wmii_setup_server(char *sockfile); + +/* spawn.c */ +void spawn(void *dpy, char *cmd); + +/* util.c */ +void swap(void **p1, void **p2); + +/* wm.c */ +int +property(Display * dpy, Window w, Atom a, Atom t, long l, + unsigned char **prop); +void win_prop(Display * dpy, Window w, Atom a, char *res, int len); +void send_message(Display * dpy, Window w, Atom a, long value); +void +init_lock_modifiers(Display * dpy, unsigned int *valid_mask, + unsigned int *num_lock_mask); diff --git a/rc/extern b/rc/extern new file mode 100644 index 00000000..1bbaeedb --- /dev/null +++ b/rc/extern @@ -0,0 +1,15 @@ +#!9PREFIX/bin/rc +# clean the environment and execute the given command + +PATH=$OLD_PATH +OLD_PATH=() +wmiiwmpid=() +apid=() +home=() +ifs=() +pid=() +prompt=() +rcname=() +status=() + +exec $* diff --git a/rc/kmode b/rc/kmode new file mode 100644 index 00000000..f961b041 --- /dev/null +++ b/rc/kmode @@ -0,0 +1,11 @@ +#!9PREFIX/bin/rc +# activate shortcuts of the given mode + +wmir write /keys/grab-keyb 0 +wmir write /keys/lookup /mode/$1 + +plab_cmd=`{wmir read /wm/event/page-update | sed -e 's/\\//'} +if (! ~ $#plab_cmd 0) eval $"plab_cmd + +#wmir write /bar/$plab/data \ +#`{wmir read /bar/$plab/data | sed -e 's/.$/'^`{echo $2 | cut -b1}^'/'} diff --git a/rc/quit b/rc/quit new file mode 100644 index 00000000..c1e6722c --- /dev/null +++ b/rc/quit @@ -0,0 +1,4 @@ +#!9PREFIX/bin/rc +# quit wmii + +wmir write /wm/ctl quit diff --git a/rc/status b/rc/status new file mode 100644 index 00000000..eb2f8603 --- /dev/null +++ b/rc/status @@ -0,0 +1,30 @@ +#!9PREFIX/bin/rc +# periodically print date and load average to the bar + +PIDFILE=/tmp/.ixp-$USER/statuspid-$WMII_IDENT + +if(test -r $PIDFILE) { + kill -2 `{cat $PIDFILE} >[2]/dev/null +} +echo $pid >$PIDFILE + +label=`{wmir read /bar/new} +wmir write /bar/$label/b1press 'wmir write /wm/ctl ''select prev''' +wmir write /bar/$label/b3press 'wmir write /wm/ctl ''select next''' +wmir write /bar/$label/b4press 'wmir write /wm/ctl ''select prev''' +wmir write /bar/$label/b5press 'wmir write /wm/ctl ''select next''' + +# install signal handler for artificial sigexit: +fn sigint { + if(test -f $PIDFILE && ~ `{cat $PIDFILE} $pid) + rm -f $PIDFILE + wmir write /bar/ctl 'destroy '^$label + exit +} + +text=fnord +while(wmir write /bar/$label/data $"text >[2]/dev/null) { + # if you need formatted date, use /bin/date instead + text=(`{date} `{uptime | sed 's/.*://; s/,//g'}) + sleep 10 +} diff --git a/rc/welcome b/rc/welcome new file mode 100644 index 00000000..2bdb275b --- /dev/null +++ b/rc/welcome @@ -0,0 +1,65 @@ +#!9PREFIX/bin/rc +# display a welcome message that contains the wmii tutorial + +xmessage -file - <. +END diff --git a/rc/wmirc b/rc/wmirc new file mode 100644 index 00000000..2f8e09a8 --- /dev/null +++ b/rc/wmirc @@ -0,0 +1,218 @@ +#!9PREFIX/bin/rc +# configure wmii + +TAB_HEIGHT=16 +BAR_HEIGHT=16 +BORDER_WIDTH=3 + +SELECTED_BG_COLOR='#307080' +SELECTED_FG_COLOR='#fefefe' +SELECTED_BORDER_COLOR='#5090a0' +NORMAL_BG_COLOR='#004050' +NORMAL_FG_COLOR='#cccccc' +NORMAL_BORDER_COLOR='#206070' +TEXT_FONT='fixed' + +MODKEY=M +NORTHKEY=k +SOUTHKEY=j +WESTKEY=h +EASTKEY=l + +nl=' +' + +fn kbind { + wmir create /keys/mode/$1/$2 $3 +} + +fn selstyle { + wmir write $1/text-font $TEXT_FONT + wmir write $1/text-color $SELECTED_FG_COLOR + wmir write $1/bg-color $SELECTED_BG_COLOR + wmir write $1/border-color $SELECTED_BORDER_COLOR +} + +fn normstyle { + wmir write $1/text-font $TEXT_FONT + wmir write $1/text-color $NORMAL_FG_COLOR + wmir write $1/bg-color $NORMAL_BG_COLOR + wmir write $1/border-color $NORMAL_BORDER_COLOR +} + +fn items { + ifs=:$nl { dirs=`{echo $2} } + { + for(dir in $dirs) { + for(file in $dir/*) { + if(! ~ $file $dir^'/*' && ! test -d $file && test -x $file) { + file=`{basename $file} + echo create /menu/items/$1/$"file $"file + } + } + } + } | sort | wmir -f & +} + +fn frameconf { + wmir write $1/event/b3press 'wmir write /wm/page/sel/ctl toggle' + wmir write $1/event/b2press 'wmir write /wm/ctl close' + normstyle $1/norm-style + selstyle $1/sel-style + wmir write $1/tab-height $TAB_HEIGHT + if(~ $2 refresh) + wmir write $1/size +0,+0,+0,+0 # causes refresh +} + +fn framesconf { + for(frame in `{wmir read $1 | grep '^[0-9]'}) + frameconf $1/$frame $2 +} + + +# WMIBAR CONFIGURATION + +fn barsucks { + wmir write /bar/$1/b1press 'wmir write /wm/ctl ''select prev''' + wmir write /bar/$1/b3press 'wmir write /wm/ctl ''select next''' + wmir write /bar/$1/b4press 'wmir write /wm/ctl ''select prev''' + wmir write /bar/$1/b5press 'wmir write /wm/ctl ''select next''' +} + +wmir write /bar/ctl reset +wmir write /bar/font $TEXT_FONT +wmir write /bar/fgcolor $NORMAL_FG_COLOR +wmir write /bar/bgcolor $NORMAL_BG_COLOR +wmir write /bar/bordercolor $NORMAL_BORDER_COLOR + +plab=`{wmir read /bar/new} +wmir write /bar/$plab/b1press 'wmir write /wm/ctl pager' + +clab=`{wmir read /bar/new} +wmir write /bar/$clab/fgcolor $SELECTED_FG_COLOR +wmir write /bar/$clab/bgcolor $SELECTED_BG_COLOR +wmir write /bar/$clab/bordercolor $SELECTED_BG_COLOR +barsucks $clab + +wmir write /bar/expandable 2 +wmir write /bar/geometry 0,south,east,$BAR_HEIGHT +wmir write /bar/ctl 'display 1' + + +# WMIIWM CONFIGURATION + +# default layout (tiled, max, grid or vsplit): +wmir write /wm/default/page/layout tiled + +# width of the left frame in tiled layout in percent: +wmir write /wm/default/page/tile-width 60 + +wmir write /wm/default/core/trans-color $SELECTED_BG_COLOR +wmir write /wm/default/frame/handle-inc 1 + +# some broken WIMP apps: +wmir create /wm/default/client/'xmms:*'/manage 0 +wmir create /wm/default/client/'Gimp:*'/manage 0 + +wmir write /wm/default/frame/border-width $BORDER_WIDTH +wmir write /wm/default/page/size '0,0,east,south-'^$BAR_HEIGHT +wmir write /wm/event/client-update \ +'text=`{wmir read /wm/page/sel/mode/sel/client/sel/name} \ +wmir write /bar/'^$clab^'/data $"text' +wmir write /wm/event/page-update \ +'text=`{wmir read /wm/page/sel/name} ^ \ +`{wmir read /wm/page/sel/mode/name|awk ''{print substr($0,0,1)}''} ^ \ +`{wmir read /keys/lookup|awk ''{print substr($0,7,1)}''} \ +wmir write /bar/'^$plab^'/data $"text' + +for(page in `{wmir read /wm/page | grep '^[0-9]'}) { + framesconf /wm/page/$page/floating refresh + framesconf /wm/page/$page/managed refresh +} +frameconf /wm/default/frame norefresh +for(i in norm-style norm-style/client) + normstyle /wm/default/core/pager/$i +for(i in sel-style sel-style/client) + selstyle /wm/default/core/pager/$i +wmir write /wm/page/sel/managed/size 0,0,east,south-$BAR_HEIGHT >[2]/dev/null + + +# WMIKEYS CONFIGURATION + +kbind bare $MODKEY-Escape 'kmode normal' + +kbind move Escape 'kmode normal' +kbind move $MODKEY-C-r 'kmode resize' +kbind move $NORTHKEY 'wmir write /wm/page/sel/mode/sel/size -0,-30,-0,-0' +kbind move $SOUTHKEY 'wmir write /wm/page/sel/mode/sel/size +0,+30,+0,+0' +kbind move $WESTKEY 'wmir write /wm/page/sel/mode/sel/size -40,-0,-0,-0' +kbind move $EASTKEY 'wmir write /wm/page/sel/mode/sel/size +40,+0,+0,+0' +kbind move S-$NORTHKEY 'wmir write /wm/page/sel/mode/sel/size -0,north,-0,-0' +kbind move S-$SOUTHKEY 'wmir write /wm/page/sel/mode/sel/size +0,south-'^$BAR_HEIGHT^',+0,+0' +kbind move S-$WESTKEY 'wmir write /wm/page/sel/mode/sel/size west,-0,-0,-0' +kbind move S-$EASTKEY 'wmir write /wm/page/sel/mode/sel/size east,+0,+0,+0' + +kbind resize Escape 'kmode normal' +kbind resize $MODKEY-C-m 'kmode move' +kbind resize $NORTHKEY 'wmir write /wm/page/sel/mode/sel/size +0,+0,+0,-30' +kbind resize $SOUTHKEY 'wmir write /wm/page/sel/mode/sel/size +0,+0,+0,+30' +kbind resize $WESTKEY 'wmir write /wm/page/sel/mode/sel/size +0,+0,-40,+0' +kbind resize $EASTKEY 'wmir write /wm/page/sel/mode/sel/size +0,+0,+40,+0' + +kbind normal $MODKEY-C-b 'kmode bare' +kbind normal $MODKEY-C-m 'kmode move' +kbind normal $MODKEY-C-r 'kmode resize' +kbind normal $MODKEY-C-a 'wmir write /menu/precmd ''''; wmir write /menu/lookup /items/actions; wmir write /menu/ctl ''display 1''' +kbind normal $MODKEY-C-p 'wmir write /menu/precmd extern; wmir write /menu/lookup /items/programs; wmir write /menu/ctl ''display 1''' +kbind normal $MODKEY-C-c 'wmir write /wm/ctl close' +kbind normal $MODKEY-C-q,y quit +kbind normal $MODKEY-C-w,y wmirc +kbind normal $MODKEY-t 'extern xterm ''+sb'' -bg ''#003040'' -fg ''#dddddd'' -cr ''#408090'' -sl 4000' +kbind normal $MODKEY-d 'wmir write /wm/ctl detach' +kbind normal $MODKEY-a 'wmir write /wm/ctl attach' +kbind normal $MODKEY-S-a 'wmir write /wm/ctl icons' +kbind normal $MODKEY-S-space 'wmir write /wm/page/sel/ctl toggle' +kbind normal $MODKEY-Return 'wmir write /wm/page/sel/ctl ''select zoomed''' +kbind normal $MODKEY-C-y 'wmir write /wm/ctl new' +kbind normal $MODKEY-u 'wmir write /wm/page/sel/mode/sel/locked 0' +kbind normal $MODKEY-S-u 'wmir write /wm/page/sel/mode/sel/locked 1' +kbind normal $MODKEY-m 'wmir write /wm/ctl togglemax' +kbind normal $MODKEY-S-t 'wmir write /wm/page/sel/managed/name tiled' +kbind normal $MODKEY-S-g 'wmir write /wm/page/sel/managed/name grid' +kbind normal $MODKEY-S-v 'wmir write /wm/page/sel/managed/name vsplit' +kbind normal $MODKEY-S-f 'wmir write /wm/page/sel/managed/name float' +kbind normal $MODKEY-S-m 'wmir write /wm/page/sel/managed/name max' +kbind normal $MODKEY-$WESTKEY 'wmir write /wm/ctl ''select prev''' +kbind normal $MODKEY-$EASTKEY 'wmir write /wm/ctl ''select next''' +kbind normal $MODKEY-Tab 'wmir write /wm/page/sel/ctl ''select next''' +kbind normal $MODKEY-$SOUTHKEY 'wmir write /wm/page/sel/ctl ''select next''' +kbind normal $MODKEY-$NORTHKEY 'wmir write /wm/page/sel/ctl ''select prev''' +kbind normal $MODKEY-S-Tab 'wmir write /wm/page/sel/mode/sel/ctl ''select next''' +kbind normal $MODKEY-S-$SOUTHKEY 'wmir write /wm/page/sel/mode/sel/ctl ''select next''' +kbind normal $MODKEY-S-$NORTHKEY 'wmir write /wm/page/sel/mode/sel/ctl ''select prev''' +kbind normal $MODKEY-space 'wmir write /wm/page/sel/ctl ''select toggled''' +kbind normal $MODKEY-S-p 'wmir write /wm/ctl pager' +kbind normal $MODKEY-S-0 'wmir write /wm/ctl ''select 10''' +for(i in 1 2 3 4 5 6 7 8 9) { + kbind normal $MODKEY-S-$i 'wmir write /wm/ctl ''select '^$i^'''' +} + +wmir write /keys/size center,center,100,$BAR_HEIGHT +selstyle /keys/box/style +kmode normal + + +# WMIMENU CONFIGURATION + +items actions $WMII_CONFDIR:$HOME/.wmii-3 +wmir create /menu/items/actions/rmpage 'wmir write /wm/ctl destroy' +items programs $OLD_PATH +wmir write /menu/size 0,south,east,$BAR_HEIGHT +normstyle /menu/style >[2]/dev/null +normstyle /menu/norm-style >[2]/dev/null +selstyle /menu/sel-style >[2]/dev/null + + +# MISC +xsetroot -mod 2 2 -fg '#003040' -bg '#004050' +status &