diff options
author | Andrew O. Shadoura <bugzilla@tut.by> | 2011-05-30 20:12:40 +0300 |
---|---|---|
committer | Andrew O. Shadoura <bugzilla@tut.by> | 2011-05-30 20:12:40 +0300 |
commit | 833a761b1ab08b6b88dd902231948fc2a2fd44a7 (patch) | |
tree | fda7fafdd345d4995e290e002065abc331147937 /cmd |
import the initial version wmii-3.9.2+debian
Diffstat (limited to 'cmd')
66 files changed, 18127 insertions, 0 deletions
diff --git a/cmd/Makefile b/cmd/Makefile new file mode 100644 index 0000000..ea8690b --- /dev/null +++ b/cmd/Makefile @@ -0,0 +1,33 @@ +ROOT=.. +include $(ROOT)/mk/hdr.mk +include $(ROOT)/mk/wmii.mk + +wmiir.c: $(ROOT)/mk/wmii.mk + +DIRS = wmii \ + menu +TARG = wihack \ + wmii.rc \ + wmii.sh \ + wmii9menu \ + wmiir + +OFILES = util.o + +LIBS += $(LIBS9) +CFLAGS += $(INCX11) + +include $(ROOT)/mk/many.mk +include $(ROOT)/mk/dir.mk + +OWMIIR=wmiir.o $(OFILES) $(LIBIXP) +wmiir.out: $(OWMIIR) + $(LINK) $@ $(OWMIIR) + +wmii/x11.o wmii/xext.o wmii/geom.o wmii/map.o: dall + true + +O9MENU=wmii9menu.o clientutil.o wmii/x11.o wmii/xext.o wmii/geom.o wmii/map.o $(OFILES) $(LIBIXP) +wmii9menu.out: $(O9MENU) + $(LINK) $@ $(O9MENU) $$(pkg-config --libs $(X11PACKAGES) xrandr xinerama) -lXext + diff --git a/cmd/click/Makefile b/cmd/click/Makefile new file mode 100644 index 0000000..1abf6cb --- /dev/null +++ b/cmd/click/Makefile @@ -0,0 +1,22 @@ +ROOT= ../.. +include ${ROOT}/mk/hdr.mk +include ${ROOT}/mk/wmii.mk + +main.c: ${ROOT}/mk/wmii.mk + +TARG = click +HFILES= dat.h fns.h + +PACKAGES += $(X11PACKAGES) xext xrandr xrender xinerama + +LIB = $(LIBIXP) +LIBS += -lm -lXtst $(LIBS9) +CFLAGS += -DVERSION=\"$(VERSION)\" -DIXP_NEEDAPI=86 +OBJ = main \ + _util \ + ../wmii/map \ + ../wmii/x11 \ + ../util + +include ${ROOT}/mk/one.mk + diff --git a/cmd/click/_util.c b/cmd/click/_util.c new file mode 100644 index 0000000..364ff81 --- /dev/null +++ b/cmd/click/_util.c @@ -0,0 +1,77 @@ +/* Copyright ©2008-2010 Kris Maglione <fbsdaemon@gmail.com> + * See LICENSE file for license details. + */ +#include "dat.h" +#include <ctype.h> +#include <string.h> +#include "fns.h" + +#define strbcmp(str, const) (strncmp((str), (const), sizeof(const)-1)) +static int +getbase(const char **s, long *sign) { + const char *p; + int ret; + + ret = 10; + *sign = 1; + if(**s == '-') { + *sign = -1; + *s += 1; + }else if(**s == '+') + *s += 1; + + p = *s; + if(!strbcmp(p, "0x")) { + *s += 2; + ret = 16; + } + else if(isdigit(p[0])) { + if(p[1] == 'r') { + *s += 2; + ret = p[0] - '0'; + } + else if(isdigit(p[1]) && p[2] == 'r') { + *s += 3; + ret = 10*(p[0]-'0') + (p[1]-'0'); + } + } + else if(p[0] == '0') { + ret = 8; + } + if(ret != 10 && (**s == '-' || **s == '+')) + *sign = 0; + return ret; +} + +bool +getlong(const char *s, long *ret) { + const char *end; + char *rend; + int base; + long sign; + + end = s+strlen(s); + base = getbase(&s, &sign); + if(sign == 0) + return false; + + *ret = sign * strtol(s, &rend, base); + return (end == rend); +} + +bool +getulong(const char *s, ulong *ret) { + const char *end; + char *rend; + int base; + long sign; + + end = s+strlen(s); + base = getbase(&s, &sign); + if(sign < 1) + return false; + + *ret = strtoul(s, &rend, base); + return (end == rend); +} + diff --git a/cmd/click/dat.h b/cmd/click/dat.h new file mode 100644 index 0000000..5096865 --- /dev/null +++ b/cmd/click/dat.h @@ -0,0 +1,27 @@ +#include <fmt.h> +#include <stdarg.h> +#include <stdbool.h> +#include <stdlib.h> +#include <unistd.h> +#include <util.h> +#include <ixp.h> +#include <x11.h> + +#define BLOCK(x) do { x; }while(0) + +#ifndef EXTERN +# define EXTERN extern +#endif + +EXTERN Window win; + +EXTERN char buffer[8092]; +EXTERN char* _buffer; + +static char* const _buf_end = buffer + sizeof buffer; + +#define bufclear() \ + BLOCK( _buffer = buffer; _buffer[0] = '\0' ) +#define bufprint(...) \ + _buffer = seprint(_buffer, _buf_end, __VA_ARGS__) + diff --git a/cmd/click/fns.h b/cmd/click/fns.h new file mode 100644 index 0000000..d41b840 --- /dev/null +++ b/cmd/click/fns.h @@ -0,0 +1,4 @@ + +bool getlong(const char*, long*); +bool getulong(const char*, ulong*); + diff --git a/cmd/click/main.c b/cmd/click/main.c new file mode 100644 index 0000000..3ddc8ef --- /dev/null +++ b/cmd/click/main.c @@ -0,0 +1,62 @@ +/* Copyright ©2006-2010 Kris Maglione <fbsdaemon@gmail.com> + * See LICENSE file for license details. + */ +#define EXTERN +#include "dat.h" +#include <X11/Xproto.h> +#include <X11/extensions/XTest.h> +#include <locale.h> +#include <string.h> +#include "fns.h" + +static const char version[] = "click-"VERSION", ©2010 Kris Maglione\n"; + +static void +usage(void) { + fatal("usage: %s [window]\n", argv0); +} + +static void +click(Window *w, Point p) { + Rectangle r; + Point rp; + + r = getwinrect(w); + rp = subpt(r.max, p); + + XTestFakeMotionEvent(display, 0, rp.x, rp.y, 0); + + XTestFakeButtonEvent(display, 1, true, 0); + XTestFakeButtonEvent(display, 1, false, 0); + + XTestFakeMotionEvent(display, 0, r.max.x, r.max.y, 0); +} + +int +main(int argc, char *argv[]) { + char *s; + + ARGBEGIN{ + default: + usage(); + }ARGEND; + + setlocale(LC_CTYPE, ""); + + initdisplay(); + + s = ARGF(); + if(s && !getulong(s, &win.w)) + usage(); + if (!s) + win.w = getfocus(); + + if(argc) + usage(); + + click(&win, Pt(1, 1)); + + XCloseDisplay(display); + return 0; +} + diff --git a/cmd/clientutil.c b/cmd/clientutil.c new file mode 100644 index 0000000..411fe67 --- /dev/null +++ b/cmd/clientutil.c @@ -0,0 +1,50 @@ +#define IXP_NO_P9_ +#define IXP_P9_STRUCTS +#define CLIENTEXTERN +#include <string.h> +#include <ixp.h> +#include <clientutil.h> +#include <util.h> + +static IxpCFid* ctlfid; +static char ctl[1024]; +static char* ectl; + +char* +readctl(char *key) { + char *s, *p; + int nkey, n; + + if(ctlfid == nil) { + ctlfid = ixp_open(client, "ctl", OREAD); + n = ixp_read(ctlfid, ctl, 1023); + ectl = ctl + n; + ixp_close(ctlfid); + } + + nkey = strlen(key); + p = ctl - 1; + do { + p++; + if(!strncmp(p, key, nkey)) { + p += nkey; + s = strchr(p, '\n'); + n = (s ? s : ectl) - p; + s = freelater(emalloc(n + 1)); + s[n] = '\0'; + return strncpy(s, p, n); + } + } while((p = strchr(p, '\n'))); + return ""; +} + +void +client_init(char* address) { + if(address && *address) + client = ixp_mount(address); + else + client = ixp_nsmount("wmii"); + if(client == nil) + fatal("can't mount: %r\n"); +} + diff --git a/cmd/menu/Makefile b/cmd/menu/Makefile new file mode 100644 index 0000000..a2fc9ad --- /dev/null +++ b/cmd/menu/Makefile @@ -0,0 +1,36 @@ +ROOT= ../.. +include $(ROOT)/mk/hdr.mk +include $(ROOT)/mk/wmii.mk + +main.c: $(ROOT)/mk/wmii.mk + +bindings.c: keys.txt Makefile + ( echo "char binding_spec[] = "; \ + sed 's/.*/ "&\\n"/' keys.txt; \ + echo " ;" ) >bindings.c + +TARG = wimenu +HFILES= dat.h fns.h + +PACKAGES += $(X11PACKAGES) xext xrandr xrender xinerama + +LIB = $(LIBIXP) +LIBS += -lm $(LIBS9) +CFLAGS += -DIXP_NEEDAPI=86 +OBJ = main \ + caret \ + history \ + event \ + menu \ + keys \ + bindings \ + ../wmii/geom \ + ../wmii/map \ + ../wmii/printevent \ + ../wmii/x11 \ + ../wmii/xext \ + ../clientutil \ + ../util + +include $(ROOT)/mk/one.mk + diff --git a/cmd/menu/bindings.c b/cmd/menu/bindings.c new file mode 100644 index 0000000..28aad75 --- /dev/null +++ b/cmd/menu/bindings.c @@ -0,0 +1,51 @@ +char binding_spec[] = + "Control-j Accept\n" + "Control-m Accept\n" + "Return Accept\n" + "Control-Shift-j Accept literal\n" + "Control-Shift-m Accept literal\n" + "Shift-Return Accept literal\n" + "\n" + "Escape Reject\n" + "Control-Bracketleft Reject\n" + "\n" + "Left Backward char\n" + "Control-b Backward char\n" + "Right Forward char\n" + "Control-f Forward char\n" + "\n" + "Mod1-b Backward word\n" + "Mod1-f Forward word\n" + "\n" + "Control-a Backward line\n" + "Control-e Forward line\n" + "\n" + "Control-p History backward\n" + "Up History backward\n" + "Control-n History forward\n" + "Down History forward\n" + "\n" + "Backspace Kill char\n" + "Control-h Kill char\n" + "Control-Backspace Kill word\n" + "Control-w Kill word\n" + "Control-u Kill line\n" + "\n" + "Tab Complete next\n" + "Control-i Complete next\n" + "Mod1-l Complete next\n" + "\n" + "Shift-Tab Complete prev\n" + "Control-Shift-i Complete prev\n" + "Mod1-h Complete prev\n" + "\n" + "Prior Complete prevpage\n" + "Mod1-k Complete prevpage\n" + "Next Complete nextpage\n" + "Mod1-j Complete nextpage\n" + "Home Complete first\n" + "Mod1-g Complete first\n" + "End Complete last\n" + "Mod1-Shift-g Complete last\n" + "\n" + ; diff --git a/cmd/menu/caret.c b/cmd/menu/caret.c new file mode 100644 index 0000000..c0380e3 --- /dev/null +++ b/cmd/menu/caret.c @@ -0,0 +1,164 @@ +#include "dat.h" +#include <ctype.h> +#include <string.h> +#include "fns.h" + +static int +iswordrune(Rune r) { + if(isalpharune(r)) + return 1; + return r < 0x80 && (r == '_' || isdigit(r)); +} + +static char* +prev_rune(char *start, char *p, Rune *r) { + + *r = 0; + if(p == start) + return p; + while(p > start && (*(--p)&0xC0) == 0x80) + ; + chartorune(r, p); + return p; +} + +static char* +next_rune(char *p, Rune *r) { + int i; + + *r = 0; + if(!*p) + return p; + i = chartorune(r, p); + return p + i; +} + +void +caret_set(int start, int end) { + int len; + + len = input.end - input.string; + start = max(0, min(len, start)); + + input.pos = input.string + start; + if(end < 0) + input.pos_end = nil; + else + input.pos_end = input.string + max(start, end); +} + +char* +caret_find(int dir, int type) { + char *end; + char *next, *p; + Rune r; + int res; + + p = input.pos; + if(dir == FORWARD) { + end = input.end; + switch(type) { + case LINE: + return end; + case WORD: + chartorune(&r, p); + res = iswordrune(r); + while(next=next_rune(p, &r), r && iswordrune(r) == res && !isspacerune(r)) + p = next; + while(next=next_rune(p, &r), r && isspacerune(r)) + p = next; + return p; + case CHAR: + if(p < end) + return p+1; + return p; + } + } + else if(dir == BACKWARD) { + end = input.string; + switch(type) { + case LINE: + return end; + case WORD: + while(next=prev_rune(end, p, &r), r && isspacerune(r)) + p = next; + prev_rune(end, p, &r); + res = iswordrune(r); + while(next=prev_rune(end, p, &r), r && iswordrune(r) == res && !isspacerune(r)) + p = next; + return p; + case CHAR: + if(p > end) + return p-1; + return end; + } + } + input.pos_end = nil; + return input.pos; +} + +void +caret_move(int dir, int type) { + input.pos = caret_find(dir, type); + input.pos_end = nil; +} + +void +caret_delete(int dir, int type) { + char *pos, *p; + int n; + + if(input.pos_end) + p = input.pos_end; + else + p = caret_find(dir, type); + pos = input.pos; + if(p == input.end) + input.end = pos; + else { + if(p < pos) { + pos = p; + p = input.pos; + } + n = input.end - p; + memmove(pos, p, n); + input.pos = pos; + input.end = pos + n; + } + *input.end = '\0'; + input.pos_end = nil; +} + +void +caret_insert(char *s, bool clear) { + int pos, end, len, size; + + if(s == nil) + return; + if(clear) { + input.pos = input.string; + input.end = input.string; + }else if(input.pos_end) + caret_delete(0, 0); + + len = strlen(s); + pos = input.pos - input.string; + end = input.end - input.string; + + size = input.size; + if(input.size == 0) + input.size = 1; + while(input.size < end + len + 1) + input.size <<= 2; + if(input.size != size) + input.string = erealloc(input.string, input.size); + + input.pos = input.string + pos; + input.end = input.string + end + len; + *input.end = '\0'; + memmove(input.pos + len, input.pos, end - pos); + memmove(input.pos, s, len); + input.pos += len; + input.pos_end = nil; +} + diff --git a/cmd/menu/dat.h b/cmd/menu/dat.h new file mode 100644 index 0000000..1d805fa --- /dev/null +++ b/cmd/menu/dat.h @@ -0,0 +1,116 @@ +#define _XOPEN_SOURCE 600 +#define IXP_P9_STRUCTS +#define IXP_NO_P9_ +#include <fmt.h> +#include <stdarg.h> +#include <stdbool.h> +#include <stdlib.h> +#include <string.h> +#include <unistd.h> +#include <util.h> +#include <ixp.h> +#include <x11.h> + +#define BLOCK(x) do { x; }while(0) + +#ifndef EXTERN +# define EXTERN extern +#endif + +enum { + FORWARD, + BACKWARD, + LINE, + WORD, + CHAR, + CARET_LAST, +}; + +enum { + LACCEPT, + LBACKWARD, + LCHAR, + LCOMPLETE, + LFIRST, + LFORWARD, + LHISTORY, + LKILL, + LLAST, + LLINE, + LLITERAL, + LNEXT, + LNEXTPAGE, + LPREV, + LPREVPAGE, + LREJECT, + LWORD, +}; + +typedef struct Item Item; + +struct Item { + char* string; + char* retstring; + Item* next_link; + Item* next; + Item* prev; + int len; + int width; +}; + +EXTERN struct { + char* string; + char* end; + char* pos; + char* pos_end; + int size; + + char* filter; + int filter_start; +} input; + +extern char binding_spec[]; + +EXTERN int numlock; + +EXTERN long xtime; +EXTERN Image* ibuf; +EXTERN Font* font; +EXTERN CTuple cnorm, csel; +EXTERN bool ontop; + +EXTERN Cursor cursor[1]; +EXTERN Visual* render_visual; + +EXTERN IxpServer srv; + +EXTERN Window* barwin; + +EXTERN Item* items; +EXTERN Item* matchfirst; +EXTERN Item* matchstart; +EXTERN Item* matchend; +EXTERN Item* matchidx; + +EXTERN Item hist; +EXTERN Item* histidx; + +EXTERN int maxwidth; +EXTERN int result; + +EXTERN char* (*find)(const char*, const char*); +EXTERN int (*compare)(const char*, const char*, size_t); + +EXTERN char* prompt; +EXTERN int promptw; + +EXTERN char buffer[8092]; +EXTERN char* _buffer; + +static char* const _buf_end = buffer + sizeof buffer; + +#define bufclear() \ + BLOCK( _buffer = buffer; _buffer[0] = '\0' ) +#define bufprint(...) \ + _buffer = seprint(_buffer, _buf_end, __VA_ARGS__) + diff --git a/cmd/menu/event.c b/cmd/menu/event.c new file mode 100644 index 0000000..fd524f9 --- /dev/null +++ b/cmd/menu/event.c @@ -0,0 +1,334 @@ +/* Copyright ©2006-2010 Kris Maglione <fbsdaemon@gmail.com> + * See LICENSE file for license details. + */ +#include "dat.h" +#include "fns.h" + +typedef void (*EvHandler)(XEvent*); +static EvHandler handler[LASTEvent]; + +void +dispatch_event(XEvent *e) { + if(e->type < nelem(handler)) { + if(handler[e->type]) + handler[e->type](e); + }else + xext_event(e); +} + +#define handle(w, fn, ev) \ + BLOCK(if((w)->handler->fn) (w)->handler->fn((w), ev)) + +static int +findtime(Display *d, XEvent *e, XPointer v) { + Window *w; + + w = (Window*)v; + if(e->type == PropertyNotify && e->xproperty.window == w->xid) { + xtime = e->xproperty.time; + return true; + } + return false; +} + +void +xtime_kludge(void) { + /* Round trip. */ + static Window *w; + WinAttr wa; + XEvent e; + long l; + + if(w == nil) { + w = createwindow(&scr.root, Rect(0, 0, 1, 1), 0, InputOnly, &wa, 0); + selectinput(w, PropertyChangeMask); + } + changeprop_long(w, "ATOM", "ATOM", &l, 0); + sync(); + XIfEvent(display, &e, findtime, (void*)w); +} + +uint +flushevents(long event_mask, bool dispatch) { + XEvent ev; + uint n = 0; + + while(XCheckMaskEvent(display, event_mask, &ev)) { + if(dispatch) + dispatch_event(&ev); + n++; + } + return n; +} + +static int +findenter(Display *d, XEvent *e, XPointer v) { + long *l; + + USED(d); + l = (long*)v; + if(*l) + return false; + if(e->type == EnterNotify) + return true; + if(e->type == MotionNotify) + (*l)++; + return false; +} + +/* This isn't perfect. If there were motion events in the queue + * before this was called, then it flushes nothing. If we don't + * check for them, we might lose a legitamate enter event. + */ +uint +flushenterevents(void) { + XEvent e; + long l; + int n; + + l = 0; + n = 0; + while(XCheckIfEvent(display, &e, findenter, (void*)&l)) + n++; + return n; +} + +static void +buttonrelease(XButtonPressedEvent *ev) { + Window *w; + + if((w = findwin(ev->window))) + handle(w, bup, ev); +} + +static void +buttonpress(XButtonPressedEvent *ev) { + Window *w; + + if((w = findwin(ev->window))) + handle(w, bdown, ev); + else + XAllowEvents(display, ReplayPointer, ev->time); +} + +static void +configurerequest(XConfigureRequestEvent *ev) { + XWindowChanges wc; + Window *w; + + if((w = findwin(ev->window))) + handle(w, configreq, ev); + else{ + wc.x = ev->x; + wc.y = ev->y; + wc.width = ev->width; + wc.height = ev->height; + wc.border_width = ev->border_width; + wc.sibling = ev->above; + wc.stack_mode = ev->detail; + XConfigureWindow(display, ev->window, ev->value_mask, &wc); + } +} + +static void +configurenotify(XConfigureEvent *ev) { + Window *w; + + USED(ev); + if((w = findwin(ev->window))) + handle(w, config, ev); +} + +static void +clientmessage(XClientMessageEvent *ev) { + + USED(ev); +} + +static void +destroynotify(XDestroyWindowEvent *ev) { + Window *w; + + if((w = findwin(ev->window))) + handle(w, destroy, ev); +} + +static void +enternotify(XCrossingEvent *ev) { + Window *w; + static int sel_screen; + + xtime = ev->time; + if(ev->mode != NotifyNormal) + return; + + if((w = findwin(ev->window))) + handle(w, enter, ev); + else if(ev->window == scr.root.xid) + sel_screen = true; +} + +static void +leavenotify(XCrossingEvent *ev) { + + xtime = ev->time; +#if 0 + if((ev->window == scr.root.xid) && !ev->same_screen) + sel_screen = true; +#endif +} + +static void +focusin(XFocusChangeEvent *ev) { + Window *w; + + /* Yes, we're focusing in on nothing, here. */ + if(ev->detail == NotifyDetailNone) { + /* FIXME: Do something. */ + return; + } + + if(!((ev->detail == NotifyNonlinear) + ||(ev->detail == NotifyNonlinearVirtual) + ||(ev->detail == NotifyVirtual) + ||(ev->detail == NotifyInferior) + ||(ev->detail == NotifyAncestor))) + return; + if((ev->mode == NotifyWhileGrabbed)) /* && (screen->hasgrab != &c_root)) */ + return; + + if((w = findwin(ev->window))) + handle(w, focusin, ev); +#if 0 + else if(ev->mode == NotifyGrab) { + if(ev->window == scr.root.xid) + screen->hasgrab = &c_root; + /* Some unmanaged window has grabbed focus */ + else if((c = screen->focus)) { + print_focus("focusin", &c_magic, "<magic>"); + screen->focus = &c_magic; + if(c->sel) + frame_draw(c->sel); + } + } +#endif +} + +static void +focusout(XFocusChangeEvent *ev) { + Window *w; + + if(!((ev->detail == NotifyNonlinear) + ||(ev->detail == NotifyNonlinearVirtual) + ||(ev->detail == NotifyVirtual) + ||(ev->detail == NotifyInferior) + ||(ev->detail == NotifyAncestor))) + return; +#if 0 + if(ev->mode == NotifyUngrab) + screen->hasgrab = nil; +#endif + + if((w = findwin(ev->window))) + handle(w, focusout, ev); +} + +static void +expose(XExposeEvent *ev) { + Window *w; + + if(ev->count == 0) { + if((w = findwin(ev->window))) + handle(w, expose, ev); + } +} + +static void +keypress(XKeyEvent *ev) { + Window *w; + + xtime = ev->time; + if((w = findwin(ev->window))) + handle(w, kdown, ev); +} + +static void +mappingnotify(XMappingEvent *ev) { + + /* Why do you need me to tell you this? */ + XRefreshKeyboardMapping(ev); +} + +static void +maprequest(XMapRequestEvent *ev) { + + USED(ev); +} + +static void +motionnotify(XMotionEvent *ev) { + Window *w; + + xtime = ev->time; + if((w = findwin(ev->window))) + handle(w, motion, ev); +} + +static void +propertynotify(XPropertyEvent *ev) { + Window *w; + + xtime = ev->time; + if((w = findwin(ev->window))) + handle(w, property, ev); +} + +static void +mapnotify(XMapEvent *ev) { + Window *w; + + if((w = findwin(ev->window))) + handle(w, map, ev); +} + +static void +unmapnotify(XUnmapEvent *ev) { + Window *w; + + if((w = findwin(ev->window)) && (ev->event == w->parent->xid)) { + w->mapped = false; + if(ev->send_event || w->unmapped-- == 0) + handle(w, unmap, ev); + } +} + +static EvHandler handler[LASTEvent] = { + [ButtonPress] = (EvHandler)buttonpress, + [ButtonRelease] = (EvHandler)buttonrelease, + [ConfigureRequest] = (EvHandler)configurerequest, + [ConfigureNotify] = (EvHandler)configurenotify, + [ClientMessage] = (EvHandler)clientmessage, + [DestroyNotify] = (EvHandler)destroynotify, + [EnterNotify] = (EvHandler)enternotify, + [Expose] = (EvHandler)expose, + [FocusIn] = (EvHandler)focusin, + [FocusOut] = (EvHandler)focusout, + [KeyPress] = (EvHandler)keypress, + [LeaveNotify] = (EvHandler)leavenotify, + [MapNotify] = (EvHandler)mapnotify, + [MapRequest] = (EvHandler)maprequest, + [MappingNotify] = (EvHandler)mappingnotify, + [MotionNotify] = (EvHandler)motionnotify, + [PropertyNotify] = (EvHandler)propertynotify, + [UnmapNotify] = (EvHandler)unmapnotify, +}; + +void +check_x_event(IxpConn *c) { + XEvent ev; + + USED(c); + while(XCheckMaskEvent(display, ~0, &ev)) + dispatch_event(&ev); +} + diff --git a/cmd/menu/fns.h b/cmd/menu/fns.h new file mode 100644 index 0000000..d95ab7f --- /dev/null +++ b/cmd/menu/fns.h @@ -0,0 +1,51 @@ + +void check_x_event(IxpConn*); +void dispatch_event(XEvent*); +uint flushenterevents(void); +uint flushevents(long, bool); +void xtime_kludge(void); + +/* caret.c */ +void caret_delete(int, int); +char* caret_find(int, int); +void caret_insert(char*, bool); +void caret_move(int, int); +void caret_set(int, int); + +/* history.c */ +void history_dump(const char*, int); +char* history_search(int, char*, int); + +/* main.c */ +void debug(int, const char*, ...); +Item* filter_list(Item*, char*); +void init_screens(int); +void update_filter(bool); +void update_input(void); + +/* menu.c */ +void menu_draw(void); +void menu_init(void); +void menu_show(void); + +/* keys.c */ +void parse_keys(char*); +char** find_key(char*, long); +int getsym(char*); + +/* geom.c */ +Align get_sticky(Rectangle src, Rectangle dst); +Cursor quad_cursor(Align); +Align quadrant(Rectangle, Point); +bool rect_contains_p(Rectangle, Rectangle); +bool rect_haspoint_p(Point, Rectangle); +bool rect_intersect_p(Rectangle, Rectangle); +Rectangle rect_intersection(Rectangle, Rectangle); + +/* xext.c */ +void randr_event(XEvent*); +bool render_argb_p(Visual*); +void xext_event(XEvent*); +void xext_init(void); +Rectangle* xinerama_screens(int*); + diff --git a/cmd/menu/history.c b/cmd/menu/history.c new file mode 100644 index 0000000..38b0598 --- /dev/null +++ b/cmd/menu/history.c @@ -0,0 +1,90 @@ +#include "dat.h" +#include <assert.h> +#include <bio.h> +#include <stdio.h> +#include <string.h> +#include <unistd.h> +#include "fns.h" + +static void +splice(Item *i) { + if(i->next != nil) + i->next->prev = i->prev; + if(i->prev != nil) + i->prev->next = i->next; +} + +char* +history_search(int dir, char *string, int n) { + Item *i; + + if(dir == FORWARD) { + if(histidx == &hist) + return hist.string; + for(i=histidx->next; i != hist.next; i=i->next) + if(!i->string || !compare(i->string, string, n)) { + histidx = i; + return i->string; + } + return string; + } + assert(dir == BACKWARD); + + if(histidx == &hist) { + free(hist.string); + hist.string = estrdup(input.string); + } + + for(i=histidx->prev; i != &hist; i=i->prev) + if(!compare(i->string, string, n)) { + histidx = i; + return i->string; + } + return string; +} + +void +history_dump(const char *path, int max) { + static char *items[20]; + static char *tmp; + Biobuf b; + Item *h, *first; + int i, n, fd; + + SET(first); + if(fork() != 0) + return; + + tmp = smprint("%s.XXXXXX", path); + fd = mkstemp(tmp); + if(fd < 0) { + fprint(2, "%s: Can't create temporary history file %q: %r\n", argv0, path); + return; + } + + hist.string = input.string; + n = 0; + hist.next->prev = nil; + for(h=&hist; h; h=h->prev) { + for(i=0; i < nelem(items); i++) + if(items[i] && !strcmp(h->string, items[i])) { + splice(h); + goto next; + } + items[n++ % nelem(items)] = h->string; + first = h; + if(!max || n >= max) + break; + next: + continue; + } + + Binit(&b, fd, OWRITE); + hist.next = nil; + for(h=first; h; h=h->next) + Bprint(&b, "%s\n", h->string); + Bterm(&b); + rename(tmp, path); + exit(0); +} + diff --git a/cmd/menu/keys.c b/cmd/menu/keys.c new file mode 100644 index 0000000..e99f061 --- /dev/null +++ b/cmd/menu/keys.c @@ -0,0 +1,142 @@ +#include "dat.h" +#include <ctype.h> +#include <strings.h> +#include <unistd.h> +#include "fns.h" + +typedef struct Key Key; + +struct Key { + Key* next; + long mask; + char* key; + char** action; +}; + +static Key* bindings; + +static void +init_numlock(void) { + static int masks[] = { + ShiftMask, LockMask, ControlMask, Mod1Mask, + Mod2Mask, Mod3Mask, Mod4Mask, Mod5Mask + }; + XModifierKeymap *modmap; + KeyCode kcode; + int i, max; + + modmap = XGetModifierMapping(display); + kcode = keycode("Num_Lock"); + if(kcode) + if(modmap && modmap->max_keypermod > 0) { + max = nelem(masks) * modmap->max_keypermod; + for(i = 0; i < max; i++) + if(modmap->modifiermap[i] == kcode) + numlock = masks[i / modmap->max_keypermod]; + } + XFreeModifiermap(modmap); +} + +/* + * To do: Find my red black tree implementation. + */ +void +parse_keys(char *spec) { + static char *lines[1024]; + static char *words[16]; + Key *k; + char *p, *line; + int mask; + int i, nlines, nwords; + + if(!numlock) + init_numlock(); + + nlines = tokenize(lines, nelem(lines), spec, '\n'); + for(i=0; i < nlines; i++) { + line = lines[i]; + p = strchr(line, '#'); + if(p) + *p = '\0'; + + nwords = stokenize(words, nelem(words) - 1, line, " \t"); + words[nwords] = nil; + if(!words[0]) + continue; + if(parsekey(words[0], &mask, &p)) { + k = emallocz(sizeof *k); + k->key = p; + k->mask = mask; + k->action = strlistdup(words + 1); + k->next = bindings; + bindings = k; + } + } +} + +char** +find_key(char *key, long mask) { + Key *k; + + /* Horrible hack. */ + if(!strcmp(key, "ISO_Left_Tab")) + key = "Tab"; + + mask &= ~(numlock | LockMask) & ((1<<8) - 1); + for(k=bindings; k; k=k->next) + if(!strcasecmp(k->key, key) && k->mask == mask) + return k->action; + return nil; +} + + /* sed 's/"([^"]+)"/L\1/g' | tr 'a-z' 'A-Z' */ + /* awk '{$1=""; print}' keys.txt | perl -e '$_=lc join "", <>; print join "\n", m/(\w+)/g;' | sort -u | sed 's:.*: "&",:' */ +char *symtab[] = { + "accept", + "backward", + "char", + "complete", + "first", + "forward", + "history", + "kill", + "last", + "line", + "literal", + "next", + "nextpage", + "prev", + "prevpage", + "reject", + "word", +}; + +static int +_bsearch(char *s, char **tab, int ntab) { + int i, n, m, cmp; + + if(s == nil) + return -1; + + n = ntab; + i = 0; + while(n) { + m = n/2; + cmp = strcasecmp(s, tab[i+m]); + if(cmp == 0) + return i+m; + if(cmp < 0 || m == 0) + n = m; + else { + i += m; + n = n-m; + } + } + return -1; +} + +int +getsym(char *s) { + return _bsearch(s, symtab, nelem(symtab)); +} + diff --git a/cmd/menu/keys.txt b/cmd/menu/keys.txt new file mode 100644 index 0000000..d16a6c6 --- /dev/null +++ b/cmd/menu/keys.txt @@ -0,0 +1,49 @@ +Control-j Accept +Control-m Accept +Return Accept +Control-Shift-j Accept literal +Control-Shift-m Accept literal +Shift-Return Accept literal + +Escape Reject +Control-Bracketleft Reject + +Left Backward char +Control-b Backward char +Right Forward char +Control-f Forward char + +Mod1-b Backward word +Mod1-f Forward word + +Control-a Backward line +Control-e Forward line + +Control-p History backward +Up History backward +Control-n History forward +Down History forward + +Backspace Kill char +Control-h Kill char +Control-Backspace Kill word +Control-w Kill word +Control-u Kill line + +Tab Complete next +Control-i Complete next +Mod1-l Complete next + +Shift-Tab Complete prev +Control-Shift-i Complete prev +Mod1-h Complete prev + +Prior Complete prevpage +Mod1-k Complete prevpage +Next Complete nextpage +Mod1-j Complete nextpage +Home Complete first +Mod1-g Complete first +End Complete last +Mod1-Shift-g Complete last + diff --git a/cmd/menu/main.c b/cmd/menu/main.c new file mode 100644 index 0000000..aaab27d --- /dev/null +++ b/cmd/menu/main.c @@ -0,0 +1,346 @@ +/* Copyright ©2006-2010 Kris Maglione <fbsdaemon@gmail.com> + * See LICENSE file for license details. + */ +#define IXP_NO_P9_ +#define IXP_P9_STRUCTS +#define EXTERN +#include "dat.h" +#include <X11/Xproto.h> +#include <locale.h> +#include <strings.h> +#include <unistd.h> +#include <bio.h> +#include <clientutil.h> +#include "fns.h" +#define link _link + +static const char version[] = "wimenu-"VERSION", "COPYRIGHT"\n"; +static Biobuf* cmplbuf; +static Biobuf* inbuf; +static bool alwaysprint; +static char* cmdsep; + +static void +usage(void) { + fatal("usage: wimenu -i [-h <history>] [-a <address>] [-p <prompt>] [-s <screen>]\n"); +} + +static int +errfmt(Fmt *f) { + return fmtstrcpy(f, ixp_errbuf()); +} + +/* Stubs. */ +void +debug(int flag, const char *fmt, ...) { + va_list ap; + + USED(flag); + va_start(ap, fmt); + vfprint(2, fmt, ap); + va_end(ap); +} + +void dprint(long, char*, ...); +void dprint(long mask, char *fmt, ...) { + va_list ap; + + USED(mask); + va_start(ap, fmt); + vfprint(2, fmt, ap); + va_end(ap); +} + +static inline void +splice(Item *i) { + i->next->prev = i->prev; + i->prev->next = i->next; +} +static inline void +link(Item *i, Item *j) { + i->next = j; + j->prev = i; +} + +static Item* +populate_list(Biobuf *buf, bool hist) { + Item ret; + Item *i; + char *p; + bool stop; + + stop = !hist && !isatty(buf->fid); + i = &ret; + while((p = Brdstr(buf, '\n', true))) { + if(stop && p[0] == '\0') + break; + link(i, emallocz(sizeof *i)); + i->next_link = i->next; + i = i->next; + i->string = p; + i->retstring = p; + if(cmdsep && (p = strstr(p, cmdsep))) { + *p = '\0'; + i->retstring = p + strlen(cmdsep); + } + if(!hist) { + i->len = strlen(i->string); + i->width = textwidth_l(font, i->string, i->len); + if(i->width > maxwidth) + maxwidth = i->width; + } + } + + link(i, &ret); + splice(&ret); + return ret.next != &ret ? ret.next : nil; +} + +static void +check_competions(IxpConn *c) { + char *s; + + s = Brdstr(cmplbuf, '\n', true); + if(!s) { + ixp_hangup(c); + return; + } + input.filter_start = strtol(s, nil, 10); + items = populate_list(cmplbuf, false); + update_filter(false); + menu_draw(); +} + +Item* +filter_list(Item *i, char *filter) { + static Item exact; + Item start, substr; + Item *exactp, *startp, *substrp; + Item **ip; + char *p; + int len; + + len = strlen(filter); + exactp = &exact; + startp = &start; + substrp = &substr; + for(; i; i=i->next_link) + if((p = find(i->string, filter))) { + ip = &substrp; + if(p == i->string) + if(strlen(p) == len) + ip = &exactp; + else + ip = &startp; + link(*ip, i); + *ip = i; + } + + link(substrp, &exact); + link(startp, &substr); + link(exactp, &start); + splice(&substr); + splice(&start); + splice(&exact); + return exact.next; +} + +void +update_input(void) { + if(alwaysprint) { + write(1, input.string, input.pos - input.string); + write(1, "\n", 1); + write(1, input.pos, input.end - input.pos); + write(1, "\n", 1); + } +} + +void +update_filter(bool print) { + char *filter; + + filter = input.string + min(input.filter_start, input.pos - input.string); + if(input.pos < input.end) + filter = freelater(estrndup(filter, input.pos - filter)); + + matchidx = nil; + matchfirst = matchstart = filter_list(items, filter); + if(print) + update_input(); +} + +ErrorCode ignored_xerrors[] = { + { 0, } +}; + +static void +end(IxpConn *c) { + + USED(c); + srv.running = 0; +} + +static void +preselect(IxpServer *s) { + + USED(s); + check_x_event(nil); +} + +enum { PointerScreen = -1 }; + +void +init_screens(int screen_hint) { + Rectangle *rects; + Point p; + int i, n; + + rects = xinerama_screens(&n); + if (screen_hint >= 0 && screen_hint < n) + /* We were given a valid screen index, use that. */ + i = screen_hint; + else { + /* Pick the screen with the pointer, for now. Later, + * try for the screen with the focused window first. + */ + p = querypointer(&scr.root); + for(i=0; i < n; i++) + if(rect_haspoint_p(p, rects[i])) + break; + if(i == n) + i = 0; + } + scr.rect = rects[i]; + menu_show(); +} + +int +main(int argc, char *argv[]) { + Item *item; + static char *address; + static char *histfile; + static char *keyfile; + static bool nokeys; + int i; + long ndump; + int screen; + + quotefmtinstall(); + fmtinstall('r', errfmt); + address = getenv("WMII_ADDRESS"); + screen = PointerScreen; + + find = strstr; + compare = strncmp; + + ndump = -1; + + ARGBEGIN{ + case 'a': + address = EARGF(usage()); + break; + case 'c': + alwaysprint = true; + break; + case 'h': + histfile = EARGF(usage()); + break; + case 'i': + find = strcasestr; + compare = strncasecmp; + break; + case 'K': + nokeys = true; + case 'k': + keyfile = EARGF(usage()); + break; + case 'n': + ndump = strtol(EARGF(usage()), nil, 10); + break; + case 'p': + prompt = EARGF(usage()); + break; + case 's': + screen = strtol(EARGF(usage()), nil, 10); + break; + case 'S': + cmdsep = EARGF(usage()); + break; + case 'v': + print("%s", version); + return 0; + default: + usage(); + }ARGEND; + + if(argc) + usage(); + + setlocale(LC_CTYPE, ""); + + initdisplay(); + + xext_init(); + if(!isatty(0)) + menu_init(); + + client_init(address); + + srv.preselect = preselect; + ixp_listen(&srv, ConnectionNumber(display), nil, check_x_event, end); + + ontop = !strcmp(readctl("bar on "), "top"); + loadcolor(&cnorm, readctl("normcolors ")); + loadcolor(&csel, readctl("focuscolors ")); + font = loadfont(readctl("font ")); + sscanf(readctl("fontpad "), "%d %d %d %d", &font->pad.min.x, &font->pad.max.x, + &font->pad.min.x, &font->pad.max.y); + if(!font) + fatal("Can't load font %q", readctl("font ")); + + cmplbuf = Bfdopen(0, OREAD); + items = populate_list(cmplbuf, false); + if(!isatty(cmplbuf->fid)) + ixp_listen(&srv, cmplbuf->fid, inbuf, check_competions, nil); + + caret_insert("", true); + update_filter(false); + + if(!nokeys) + parse_keys(binding_spec); + if(keyfile) { + i = open(keyfile, O_RDONLY); + if(read(i, buffer, sizeof(buffer)) > 0) + parse_keys(buffer); + } + + histidx = &hist; + link(&hist, &hist); + if(histfile) { + inbuf = Bopen(histfile, OREAD); + if(inbuf) { + item = populate_list(inbuf, true); + if(item) { + link(item->prev, &hist); + link(&hist, item); + } + Bterm(inbuf); + } + } + + if(barwin == nil) + menu_init(); + + init_screens(screen); + + i = ixp_serverloop(&srv); + if(i) + fprint(2, "%s: error: %r\n", argv0); + XCloseDisplay(display); + + if(ndump >= 0 && histfile && result == 0) + history_dump(histfile, ndump); + + return result; +} + diff --git a/cmd/menu/menu.c b/cmd/menu/menu.c new file mode 100644 index 0000000..c947ec4 --- /dev/null +++ b/cmd/menu/menu.c @@ -0,0 +1,344 @@ +#include "dat.h" +#include <ctype.h> +#include <strings.h> +#include <unistd.h> +#include "fns.h" + +static Handlers handlers; + +static int ltwidth; + +static void _menu_draw(bool); + +enum { + ACCEPT = CARET_LAST, + REJECT, + HIST, + KILL, + CMPL_NEXT, + CMPL_PREV, + CMPL_FIRST, + CMPL_LAST, + CMPL_NEXT_PAGE, + CMPL_PREV_PAGE, +}; + +void +menu_init(void) { + WinAttr wa; + + wa.override_redirect = 1; + wa.background_pixmap = ParentRelative; + wa.event_mask = ExposureMask | KeyPressMask; + barwin = createwindow(&scr.root, Rect(-1, -1, 1, 1), scr.depth, InputOutput, + &wa, CWOverrideRedirect + | CWBackPixmap + | CWEventMask); + sethandler(barwin, &handlers); + mapwin(barwin); + + int i = 0; + while(!grabkeyboard(barwin)) { + if(i++ > 1000) + fatal("can't grab keyboard"); + usleep(1000); + } +} + +static void +menu_unmap(long id, void *p) { + + USED(id, p); + unmapwin(barwin); + XFlush(display); +} + +static void +selectitem(Item *i) { + if(i != matchidx) { + caret_set(input.filter_start, input.pos - input.string); + caret_insert(i->string, 0); + matchidx = i; + } +} + +static void +menu_cmd(int op, int motion) { + int n; + + switch(op) { + case HIST: + n = input.pos - input.string; + caret_insert(history_search(motion, input.string, n), true); + input.pos = input.string + n; + break; + case KILL: + caret_delete(BACKWARD, motion); + break; + default: + goto next; + } + update_filter(true); +next: + switch(op) { + case ACCEPT: + srv.running = false; + if(!matchidx && matchfirst->retstring && !motion) + if(input.filter_start == 0 && input.pos == input.end) + menu_cmd(CMPL_FIRST, 0); + if(!motion && matchidx && !strcmp(input.string, matchidx->string)) + print("%s", matchidx->retstring); + else + print("%s", input.string); + break; + case REJECT: + srv.running = false; + result = 1; + break; + case BACKWARD: + case FORWARD: + caret_move(op, motion); + update_input(); + break; + case CMPL_NEXT: + selectitem(matchidx ? matchidx->next : matchfirst); + break; + case CMPL_PREV: + selectitem((matchidx ? matchidx : matchstart)->prev); + break; + case CMPL_FIRST: + matchstart = matchfirst; + matchend = nil; + selectitem(matchstart); + break; + case CMPL_LAST: + selectitem(matchfirst->prev); + break; + case CMPL_NEXT_PAGE: + if(matchend) + selectitem(matchend->next); + break; + case CMPL_PREV_PAGE: + matchend = matchstart->prev; + matchidx = nil; + _menu_draw(false); + selectitem(matchstart); + break; + } + menu_draw(); +} + +static void +_menu_draw(bool draw) { + Rectangle r, rd, rp, r2, extent; + CTuple *c; + Item *i; + int inputw, itemoff, end, pad, n, offset; + + r = barwin->r; + r = rectsetorigin(r, ZP); + + pad = (font->height & ~1) + font->pad.min.x + font->pad.max.x; + + rd = r; + rp = ZR; // SET(rp) + if (prompt) { + if (!promptw) + promptw = textwidth(font, prompt) + 2 * ltwidth + pad; + rd.min.x += promptw; + + rp = r; + rp.max.x = promptw; + } + + inputw = min(Dx(rd) / 3, maxwidth); + inputw = max(inputw, textwidth(font, input.string)) + pad; + itemoff = inputw + 2 * ltwidth; + end = Dx(rd) - ltwidth; + + fill(ibuf, r, cnorm.bg); + + if(matchend && matchidx == matchend->next) + matchstart = matchidx; + else if(matchidx == matchstart->prev) + matchend = matchidx; + if (matchend == nil) + matchend = matchstart; + + if(matchend == matchstart->prev && matchstart != matchidx) { + n = itemoff; + matchstart = matchend; + for(i=matchend; ; i=i->prev) { + n += i->width + pad; + if(n > end) + break; + matchstart = i; + if(i == matchfirst) + break; + } + } + + if(!draw) + return; + + r2 = rd; + for(i=matchstart; i->string; i=i->next) { + r2.min.x = promptw + itemoff; + itemoff = itemoff + i->width + pad; + r2.max.x = promptw + min(itemoff, end); + if(i != matchstart && itemoff > end) + break; + + c = (i == matchidx) ? &csel : &cnorm; + fill(ibuf, r2, c->bg); + drawstring(ibuf, font, r2, Center, i->string, c->fg); + matchend = i; + if(i->next == matchfirst) + break; + } + + r2 = rd; + r2.min.x = promptw + inputw; + if(matchstart != matchfirst) + drawstring(ibuf, font, r2, West, "<", cnorm.fg); + if(matchend->next != matchfirst) + drawstring(ibuf, font, r2, East, ">", cnorm.fg); + + r2 = rd; + r2.max.x = promptw + inputw; + drawstring(ibuf, font, r2, West, input.string, cnorm.fg); + + extent = textextents_l(font, input.string, input.pos - input.string, &offset); + r2.min.x = promptw + offset + font->pad.min.x - extent.min.x + pad/2 - 1; + r2.max.x = r2.min.x + 2; + r2.min.y++; + r2.max.y--; + border(ibuf, r2, 1, cnorm.border); + + if (prompt) + drawstring(ibuf, font, rp, West, prompt, cnorm.fg); + + border(ibuf, rd, 1, cnorm.border); + copyimage(barwin, r, ibuf, ZP); +} + +void +menu_draw(void) { + _menu_draw(true); +} + +void +menu_show(void) { + Rectangle r; + int height, pad; + + USED(menu_unmap); + + ltwidth = textwidth(font, "<"); + + pad = (font->height & ~1)/2; + height = labelh(font); + + r = scr.rect; + if(ontop) + r.max.y = r.min.y + height; + else + r.min.y = r.max.y - height; + reshapewin(barwin, r); + + freeimage(ibuf); + ibuf = allocimage(Dx(r), Dy(r), scr.depth); + + mapwin(barwin); + raisewin(barwin); + menu_draw(); +} + +static void +kdown_event(Window *w, XKeyEvent *e) { + char **action, **p; + char *key; + char buf[32]; + int num; + KeySym ksym; + + buf[0] = 0; + num = XLookupString(e, buf, sizeof buf, &ksym, 0); + key = XKeysymToString(ksym); + if(IsKeypadKey(ksym)) + if(ksym == XK_KP_Enter) + ksym = XK_Return; + else if(ksym >= XK_KP_0 && ksym <= XK_KP_9) + ksym = (ksym - XK_KP_0) + XK_0; + + if(IsFunctionKey(ksym) + || IsMiscFunctionKey(ksym) + || IsKeypadKey(ksym) + || IsPrivateKeypadKey(ksym) + || IsPFKey(ksym)) + return; + + action = find_key(key, e->state); + if(action == nil || action[0] == nil) { + if(num && !iscntrl(buf[0])) { + caret_insert(buf, false); + update_filter(true); + menu_draw(); + } + } + else { + long mask = 0; +# define have(val) !!(mask & (1 << val)) + for(p=action+1; *p; p++) + mask |= 1 << getsym(*p); + int amount = ( + have(LCHAR) ? CHAR : + have(LWORD) ? WORD : + have(LLINE) ? LINE : + -1); + switch(getsym(action[0])) { + case LACCEPT: + menu_cmd(ACCEPT, have(LLITERAL)); + break; + case LBACKWARD: + menu_cmd(BACKWARD, amount); + break; + case LCOMPLETE: + amount = ( + have(LNEXT) ? CMPL_NEXT : + have(LPREV) ? CMPL_PREV : + have(LNEXTPAGE) ? CMPL_NEXT_PAGE : + have(LPREVPAGE) ? CMPL_PREV_PAGE : + have(LFIRST) ? CMPL_FIRST : + have(LLAST) ? CMPL_LAST : + CMPL_NEXT); + menu_cmd(amount, 0); + break; + case LFORWARD: + menu_cmd(FORWARD, amount); + break; + case LHISTORY: + menu_cmd(HIST, have(LBACKWARD) ? BACKWARD : FORWARD); + break; + case LKILL: + menu_cmd(KILL, amount); + break; + case LREJECT: + menu_cmd(REJECT, 0); + break; + } + } +} + +static void +expose_event(Window *w, XExposeEvent *e) { + + USED(w); + menu_draw(); +} + +static Handlers handlers = { + .expose = expose_event, + .kdown = kdown_event, +}; + diff --git a/cmd/strut/Makefile b/cmd/strut/Makefile new file mode 100644 index 0000000..fca4bd2 --- /dev/null +++ b/cmd/strut/Makefile @@ -0,0 +1,27 @@ +ROOT= ../.. +include ${ROOT}/mk/hdr.mk +include ${ROOT}/mk/wmii.mk + +main.c: ${ROOT}/mk/wmii.mk + +TARG = wistrut +HFILES= dat.h fns.h + +PACKAGES += $(X11PACKAGES) xext xrandr xinerama + +LIB = $(LIBIXP) +LIBS += -lm $(LIBS9) +CFLAGS += -DIXP_NEEDAPI=86 +OBJ = main \ + event \ + ewmh \ + win \ + _util \ + ../wmii/map \ + ../wmii/printevent \ + printevent_kludge \ + ../wmii/x11 \ + ../util + +include ${ROOT}/mk/one.mk + diff --git a/cmd/strut/_util.c b/cmd/strut/_util.c new file mode 100644 index 0000000..364ff81 --- /dev/null +++ b/cmd/strut/_util.c @@ -0,0 +1,77 @@ +/* Copyright ©2008-2010 Kris Maglione <fbsdaemon@gmail.com> + * See LICENSE file for license details. + */ +#include "dat.h" +#include <ctype.h> +#include <string.h> +#include "fns.h" + +#define strbcmp(str, const) (strncmp((str), (const), sizeof(const)-1)) +static int +getbase(const char **s, long *sign) { + const char *p; + int ret; + + ret = 10; + *sign = 1; + if(**s == '-') { + *sign = -1; + *s += 1; + }else if(**s == '+') + *s += 1; + + p = *s; + if(!strbcmp(p, "0x")) { + *s += 2; + ret = 16; + } + else if(isdigit(p[0])) { + if(p[1] == 'r') { + *s += 2; + ret = p[0] - '0'; + } + else if(isdigit(p[1]) && p[2] == 'r') { + *s += 3; + ret = 10*(p[0]-'0') + (p[1]-'0'); + } + } + else if(p[0] == '0') { + ret = 8; + } + if(ret != 10 && (**s == '-' || **s == '+')) + *sign = 0; + return ret; +} + +bool +getlong(const char *s, long *ret) { + const char *end; + char *rend; + int base; + long sign; + + end = s+strlen(s); + base = getbase(&s, &sign); + if(sign == 0) + return false; + + *ret = sign * strtol(s, &rend, base); + return (end == rend); +} + +bool +getulong(const char *s, ulong *ret) { + const char *end; + char *rend; + int base; + long sign; + + end = s+strlen(s); + base = getbase(&s, &sign); + if(sign < 1) + return false; + + *ret = strtoul(s, &rend, base); + return (end == rend); +} + diff --git a/cmd/strut/dat.h b/cmd/strut/dat.h new file mode 100644 index 0000000..810d384 --- /dev/null +++ b/cmd/strut/dat.h @@ -0,0 +1,33 @@ +#include <fmt.h> +#include <stdarg.h> +#include <stdbool.h> +#include <stdlib.h> +#include <unistd.h> +#include <util.h> +#include <ixp.h> +#include <x11.h> + +#define BLOCK(x) do { x; }while(0) + +#ifndef EXTERN +# define EXTERN extern +#endif + +extern Handlers handlers; + +EXTERN bool running; + +EXTERN Window win; +EXTERN Window frame; +EXTERN long xtime; + +EXTERN char buffer[8092]; +EXTERN char* _buffer; + +static char* const _buf_end = buffer + sizeof buffer; + +#define bufclear() \ + BLOCK( _buffer = buffer; _buffer[0] = '\0' ) +#define bufprint(...) \ + _buffer = seprint(_buffer, _buf_end, __VA_ARGS__) + diff --git a/cmd/strut/event.c b/cmd/strut/event.c new file mode 100644 index 0000000..aebd8ba --- /dev/null +++ b/cmd/strut/event.c @@ -0,0 +1,309 @@ +/* Copyright ©2006-2010 Kris Maglione <fbsdaemon@gmail.com> + * See LICENSE file for license details. + */ +#include "dat.h" +#include "fns.h" + +static void (*handler[LASTEvent])(XEvent*); + +void +dispatch_event(XEvent *e) { + /* print("%E\n", e); */ + if(e->type < nelem(handler) && handler[e->type]) + handler[e->type](e); +} + +#define handle(w, fn, ev) \ + BLOCK(if((w)->handler->fn) (w)->handler->fn((w), ev)) + +#ifdef notdef +uint +flushevents(long event_mask, bool dispatch) { + XEvent ev; + uint n = 0; + + while(XCheckMaskEvent(display, event_mask, &ev)) { + if(dispatch) + dispatch_event(&ev); + n++; + } + return n; +} + +static int +findenter(Display *d, XEvent *e, XPointer v) { + long *l; + + USED(d); + l = (long*)v; + if(*l) + return false; + if(e->type == EnterNotify) + return true; + if(e->type == MotionNotify) + (*l)++; + return false; +} + +/* This isn't perfect. If there were motion events in the queue + * before this was called, then it flushes nothing. If we don't + * check for them, we might lose a legitamate enter event. + */ +uint +flushenterevents(void) { + XEvent e; + long l; + int n; + + l = 0; + n = 0; + while(XCheckIfEvent(display, &e, findenter, (void*)&l)) + n++; + return n; +} +#endif + +static int +findtime(Display *d, XEvent *e, XPointer v) { + Window *w; + + w = (Window*)v; + if(e->type == PropertyNotify && e->xproperty.window == w->xid) { + xtime = e->xproperty.time; + return true; + } + return false; +} + +void +xtime_kludge(void) { + Window *w; + WinAttr wa; + XEvent e; + long l; + + w = createwindow(&scr.root, Rect(0, 0, 1, 1), 0, InputOnly, &wa, 0); + + XSelectInput(display, w->xid, PropertyChangeMask); + changeprop_long(w, "ATOM", "ATOM", &l, 0); + XIfEvent(display, &e, findtime, (void*)w); + + destroywindow(w); +} + +static void +buttonrelease(XEvent *e) { + XButtonPressedEvent *ev; + Window *w; + + ev = &e->xbutton; + if((w = findwin(ev->window))) + handle(w, bup, ev); +} + +static void +buttonpress(XEvent *e) { + XButtonPressedEvent *ev; + Window *w; + + ev = &e->xbutton; + if((w = findwin(ev->window))) + handle(w, bdown, ev); + else + XAllowEvents(display, ReplayPointer, ev->time); +} + +static void +clientmessage(XEvent *e) { + XClientMessageEvent *ev; + + ev = &e->xclient; + USED(ev); +} + +static void +configurenotify(XEvent *e) { + XConfigureEvent *ev; + Window *w; + + ev = &e->xconfigure; + if((w = findwin(ev->window))) + handle(w, config, ev); +} + +static void +destroynotify(XEvent *e) { + XDestroyWindowEvent *ev; + Window *w; + + ev = &e->xdestroywindow; + if((w = findwin(ev->window))) + handle(w, destroy, ev); +} + +static void +enternotify(XEvent *e) { + XCrossingEvent *ev; + Window *w; + + ev = &e->xcrossing; + xtime = ev->time; + if(ev->mode != NotifyNormal) + return; + + if((w = findwin(ev->window))) + handle(w, enter, ev); +} + +static void +leavenotify(XEvent *e) { + XCrossingEvent *ev; + + ev = &e->xcrossing; + xtime = ev->time; +} + +static void +focusin(XEvent *e) { + XFocusChangeEvent *ev; + Window *w; + + ev = &e->xfocus; + /* Yes, we're focusing in on nothing, here. */ + if(ev->detail == NotifyDetailNone) { + /* FIXME: Do something. */ + return; + } + + if(!((ev->detail == NotifyNonlinear) + ||(ev->detail == NotifyNonlinearVirtual) + ||(ev->detail == NotifyVirtual) + ||(ev->detail == NotifyInferior) + ||(ev->detail == NotifyAncestor))) + return; + if((ev->mode == NotifyWhileGrabbed)) + return; + + if((w = findwin(ev->window))) + handle(w, focusin, ev); +} + +static void +focusout(XEvent *e) { + XFocusChangeEvent *ev; + Window *w; + + ev = &e->xfocus; + if(!((ev->detail == NotifyNonlinear) + ||(ev->detail == NotifyNonlinearVirtual) + ||(ev->detail == NotifyVirtual) + ||(ev->detail == NotifyInferior) + ||(ev->detail == NotifyAncestor))) + return; + + if((w = findwin(ev->window))) + handle(w, focusout, ev); +} + +static void +expose(XEvent *e) { + XExposeEvent *ev; + Window *w; + + ev = &e->xexpose; + if(ev->count == 0) { + if((w = findwin(ev->window))) + handle(w, expose, ev); + } +} + +static void +keypress(XEvent *e) { + XKeyEvent *ev; + + ev = &e->xkey; + xtime = ev->time; +} + +static void +mappingnotify(XEvent *e) { + XMappingEvent *ev; + + ev = &e->xmapping; + /* Why do you need me to tell you this? */ + XRefreshKeyboardMapping(ev); +} + +static void +motionnotify(XEvent *e) { + XMotionEvent *ev; + Window *w; + + ev = &e->xmotion; + xtime = ev->time; + if((w = findwin(ev->window))) + handle(w, motion, ev); +} + +static void +propertynotify(XEvent *e) { + XPropertyEvent *ev; + Window *w; + + ev = &e->xproperty; + xtime = ev->time; + if((w = findwin(ev->window))) + handle(w, property, ev); +} + +static void +mapnotify(XEvent *e) { + XMapEvent *ev; + Window *w; + + ev = &e->xmap; + if((w = findwin(ev->window))) + handle(w, map, ev); +} + +static void +unmapnotify(XEvent *e) { + XUnmapEvent *ev; + Window *w; + + ev = &e->xunmap; + if((w = findwin(ev->window)) && w->parent && (ev->event == w->parent->xid)) { + if(ev->send_event || w->unmapped-- == 0) + handle(w, unmap, ev); + } +} + +static void (*handler[LASTEvent])(XEvent*) = { + [ButtonPress] = buttonpress, + [ButtonRelease] = buttonrelease, + [ClientMessage] = clientmessage, + [ConfigureNotify] = configurenotify, + [DestroyNotify] = destroynotify, + [EnterNotify] = enternotify, + [Expose] = expose, + [FocusIn] = focusin, + [FocusOut] = focusout, + [KeyPress] = keypress, + [LeaveNotify] = leavenotify, + [MapNotify] = mapnotify, + [MappingNotify] = mappingnotify, + [MotionNotify] = motionnotify, + [PropertyNotify] = propertynotify, + [UnmapNotify] = unmapnotify, +}; + +void +xevent_loop(void) { + XEvent ev; + + while(running) { + XNextEvent(display, &ev); + dispatch_event(&ev); + } +} + diff --git a/cmd/strut/ewmh.c b/cmd/strut/ewmh.c new file mode 100644 index 0000000..b56923c --- /dev/null +++ b/cmd/strut/ewmh.c @@ -0,0 +1,84 @@ +/* Copyright ©2007-2010 Kris Maglione <fbsdaemon@gmail.com> + * See LICENSE file for license details. + */ +#include "dat.h" +#include <limits.h> +#include <string.h> +#include "fns.h" + +#define Net(x) ("_NET_" x) +#define Action(x) ("_NET_WM_ACTION_" x) +#define State(x) ("_NET_WM_STATE_" x) +#define Type(x) ("_NET_WM_WINDOW_TYPE_" x) +#define NET(x) xatom(Net(x)) +#define ACTION(x) xatom(Action(x)) +#define STATE(x) xatom(State(x)) +#define TYPE(x) xatom(Type(x)) + +enum { + Left, Right, Top, Bottom, + LeftMin, LeftMax, + RightMin, RightMax, + TopMin, TopMax, + BottomMin, BottomMax, + Last +}; + +void +ewmh_getstrut(Window *w, Rectangle struts[4]) { + long *strut; + ulong n; + + memset(struts, 0, sizeof struts); + + n = getprop_long(w, Net("WM_STRUT_PARTIAL"), "CARDINAL", + 0L, &strut, Last); + if(n != Last) { + free(strut); + n = getprop_long(w, Net("WM_STRUT"), "CARDINAL", + 0L, &strut, 4L); + if(n != 4) { + free(strut); + return; + } + strut = erealloc(strut, Last * sizeof *strut); + strut[LeftMin] = strut[RightMin] = 0; + strut[LeftMax] = strut[RightMax] = INT_MAX; + strut[TopMin] = strut[BottomMin] = 0; + strut[TopMax] = strut[BottomMax] = INT_MAX; + } + struts[Left] = Rect(0, strut[LeftMin], strut[Left], strut[LeftMax]); + struts[Right] = Rect(-strut[Right], strut[RightMin], 0, strut[RightMax]); + struts[Top] = Rect(strut[TopMin], 0, strut[TopMax], strut[Top]); + struts[Bottom] = Rect(strut[BottomMin], -strut[Bottom], strut[BottomMax], 0); + free(strut); +} + +void +ewmh_setstrut(Window *w, Rectangle struts[4]) { + long strut[Last]; + int i; + + strut[LeftMin] = struts[Left].min.y; + strut[Left] = struts[Left].max.x; + strut[LeftMax] = struts[Left].max.y; + + strut[RightMin] = struts[Right].min.y; + strut[Right] = -struts[Right].min.x; + strut[RightMax] = struts[Right].max.y; + + strut[TopMin] = struts[Top].min.x; + strut[Top] = struts[Top].max.y; + strut[TopMax] = struts[Top].max.x; + + strut[BottomMin] = struts[Bottom].min.x; + strut[Bottom] = -struts[Bottom].min.y; + strut[BottomMax] = struts[Bottom].max.x; + + for(i=0; i<Last; i++) + if(strut[i] < 0) + strut[i] = 0; + + changeprop_long(w, Net("WM_STRUT_PARTIAL"), "CARDINAL", strut, nelem(strut)); +} + diff --git a/cmd/strut/fns.h b/cmd/strut/fns.h new file mode 100644 index 0000000..af6383e --- /dev/null +++ b/cmd/strut/fns.h @@ -0,0 +1,18 @@ + +void debug(int, const char*, ...); +void dispatch_event(XEvent*); +uint flushevents(long, bool); +uint flushenterevents(void); +void xevent_loop(void); +void xtime_kludge(void); + +void restrut(void); + +bool getlong(const char*, long*); +bool getulong(const char*, ulong*); + +void ewmh_getstrut(Window*, Rectangle[4]); +void ewmh_setstrut(Window*, Rectangle[4]); + +void printevent(XEvent *e); + diff --git a/cmd/strut/main.c b/cmd/strut/main.c new file mode 100644 index 0000000..ef9d27f --- /dev/null +++ b/cmd/strut/main.c @@ -0,0 +1,102 @@ +/* Copyright ©2006-2010 Kris Maglione <fbsdaemon@gmail.com> + * See LICENSE file for license details. + */ +#define EXTERN +#include "dat.h" +#include <X11/Xproto.h> +#include <locale.h> +#include <string.h> +#include "fns.h" + +static const char version[] = "witray-"VERSION", ©2007 Kris Maglione\n"; + +static void +usage(void) { + fatal("usage: %s <window>\n", argv0); +} + +static int +errfmt(Fmt *f) { + return fmtstrcpy(f, ixp_errbuf()); +} + +void +debug(int flag, const char *fmt, ...) { + va_list ap; + + USED(flag); + va_start(ap, fmt); + vfprint(2, fmt, ap); + va_end(ap); +} + +ErrorCode ignored_xerrors[] = { {0,} }; + +static Window +findframe(Window *w) { + XWindow *children; + XWindow xw, par, root; + Window ret = {0, }; + uint n; + + for(par=w->xid; par != scr.root.xid; ) { + xw = par; + XQueryTree(display, xw, &root, &par, &children, &n); + XFree(children); + } + ret.xid = xw; + ret.parent = &scr.root; + return ret; +} + +static void +getwinsize(Window *win) { + int x, y; + uint w, h; + XWindow root; + uint border, depth; + + XGetGeometry(display, win->xid, &root, + &x, &y, &w, &h, + &border, &depth); + win->r = rectaddpt(Rect(0, 0, w, h), + Pt(x+border, y+border)); +} + +int +main(int argc, char *argv[]) { + char *s; + + fmtinstall('r', errfmt); +extern int fmtevent(Fmt*); + fmtinstall('E', fmtevent); + + ARGBEGIN{ + default: + usage(); + }ARGEND; + + s = EARGF(usage()); + if(!getulong(s, &win.xid)) + usage(); + + if(argc) + usage(); + + setlocale(LC_CTYPE, ""); + + initdisplay(); + + frame = findframe(&win); + getwinsize(&frame); + restrut(); + sethandler(&frame, &handlers); + selectinput(&frame, StructureNotifyMask); + + running = true; + xevent_loop(); + + XCloseDisplay(display); + return 0; +} + diff --git a/cmd/strut/printevent_kludge.c b/cmd/strut/printevent_kludge.c new file mode 100644 index 0000000..c5e53cb --- /dev/null +++ b/cmd/strut/printevent_kludge.c @@ -0,0 +1,12 @@ +#include "dat.h" + +void dprint(const char *fmt, ...); +void +dprint(const char *fmt, ...) { + va_list ap; + + va_start(ap, fmt); + vfprint(2, fmt, ap); + va_end(ap); +} + diff --git a/cmd/strut/win.c b/cmd/strut/win.c new file mode 100644 index 0000000..ba607cf --- /dev/null +++ b/cmd/strut/win.c @@ -0,0 +1,102 @@ +/* Copyright ©2008-2010 Kris Maglione <fbsdaemon@gmail.com> + * See LICENSE file for license details. + */ +#include "dat.h" +#include <string.h> +#include "fns.h" + +void +restrut(void) { + enum { Left, Right, Top, Bottom }; + Rectangle strut[4]; + Rectangle r; + + r = frame.r; + memset(strut, 0, sizeof strut); + if(Dx(r) < Dx(scr.rect)/2) { + if(r.min.x <= scr.rect.min.x) { + strut[Left] = r; + strut[Left].min.x = 0; + strut[Left].max.x -= scr.rect.min.x; + } + if(r.max.x >= scr.rect.max.x) { + strut[Right] = r; + strut[Right].min.x -= scr.rect.max.x; + strut[Right].max.x = 0; + } + } + if(Dy(r) < Dy(scr.rect)/2) { + if(r.min.y <= scr.rect.min.y) { + strut[Top] = r; + strut[Top].min.y = 0; + strut[Top].max.y -= scr.rect.min.y; + } + if(r.max.y >= scr.rect.max.y) { + strut[Bottom] = r; + strut[Bottom].min.y -= scr.rect.max.y; + strut[Bottom].max.y = 0; + } + } + +#define pstrut(name) \ + if(!eqrect(strut[name], ZR)) \ + fprint(2, "strut["#name"] = %R\n", strut[name]) + /* Choose the struts which take up the least space. + * Not ideal. + */ + if(Dy(strut[Top])) { + if(Dx(strut[Left])) + if(Dy(strut[Top]) < Dx(strut[Left])) + strut[Left] = ZR; + else + strut[Top] = ZR; + if(Dx(strut[Right])) + if(Dy(strut[Top]) < Dx(strut[Right])) + strut[Right] = ZR; + else + strut[Top] = ZR; + } + if(Dy(strut[Bottom])) { + if(Dx(strut[Left])) + if(Dy(strut[Bottom]) < Dx(strut[Left])) + strut[Left] = ZR; + else + strut[Bottom] = ZR; + if(Dx(strut[Right])) + if(Dy(strut[Bottom]) < Dx(strut[Right])) + strut[Right] = ZR; + else + strut[Bottom] = ZR; + } + +#if 0 + pstrut(Left); + pstrut(Right); + pstrut(Top); + pstrut(Bottom); +#endif + + ewmh_setstrut(&win, strut); +} + +static void +config(Window *w, XConfigureEvent *ev) { + + USED(w); + + frame.r = rectaddpt(Rect(0, 0, ev->width, ev->height), + Pt(ev->x+ev->border_width, ev->y+ev->border_width)); + restrut(); +} + +static void +destroy(Window *w, XDestroyWindowEvent *ev) { + USED(w, ev); + running = false; +} + +Handlers handlers = { + .config = config, + .destroy = destroy, +}; + diff --git a/cmd/util.c b/cmd/util.c new file mode 100644 index 0000000..2556d28 --- /dev/null +++ b/cmd/util.c @@ -0,0 +1,272 @@ +/* Written by Kris Maglione <fbsdaemon at gmail dot com> */ +/* Public domain */ +#include <ctype.h> +#include <errno.h> +#include <sys/types.h> +#include <signal.h> +#include <stdarg.h> +#include <stdlib.h> +#include <stdint.h> +#include <stdio.h> +#include <string.h> +#include <strings.h> +#include <unistd.h> +#include <util.h> +#include <fmt.h> + +typedef struct VFmt VFmt; +struct VFmt { + const char *fmt; + va_list args; +}; + +#ifdef VARARGCK +# pragma varargck type "V" VFmt* +#endif + +static int +Vfmt(Fmt *f) { + VFmt *vf; + int i; + + vf = va_arg(f->args, VFmt*); + i = fmtvprint(f, vf->fmt, vf->args); + return i; +} + +void +fatal(const char *fmt, ...) { + VFmt fp; + + fmtinstall('V', Vfmt); + fmtinstall('\001', Vfmt); + + fp.fmt = fmt; + va_start(fp.args, fmt); + fprint(2, "%s: fatal: %V\n", argv0, &fp); + va_end(fp.args); + + exit(1); +} + +void* +freelater(void *p) { + static char* obj[16]; + static long nobj; + int id; + + id = nobj++ % nelem(obj); + free(obj[id]); + obj[id] = p; + return p; +} + +char* +vsxprint(const char *fmt, va_list ap) { + char *s; + + s = vsmprint(fmt, ap); + freelater(s); + return s; +} + +char* +sxprint(const char *fmt, ...) { + va_list ap; + char *ret; + + va_start(ap, fmt); + ret = vsxprint(fmt, ap); + va_end(ap); + return ret; +} + +void +_die(char *file, int line, char *msg, ...) { + va_list ap; + + va_start(ap, msg); + fprint(2, "%s: dieing at %s:%d: %s\n", + argv0, file, line, + vsxprint(msg, ap)); + va_end(ap); + + kill(getpid(), SIGABRT); + abort(); /* Adds too many frames: + * _die() + * abort() + * raise(SIGABRT) + * kill(getpid(), SIGABRT) + */ +} + +/* Can't malloc */ +static void +mfatal(char *name, uint size) { + const char + couldnot[] = ": fatal: Could not ", + paren[] = "() ", + bytes[] = " bytes\n"; + char buf[1024]; + char sizestr[8]; + int i; + + i = sizeof sizestr; + do { + sizestr[--i] = '0' + (size%10); + size /= 10; + } while(size > 0); + + strlcat(buf, argv0, sizeof buf); + strlcat(buf, couldnot, sizeof buf); + strlcat(buf, name, sizeof buf); + strlcat(buf, paren, sizeof buf); + strlcat(buf, sizestr+i, sizeof buf); + strlcat(buf, bytes, sizeof buf); + write(2, buf, strlen(buf)); + + exit(1); +} + +void * +emalloc(uint size) { + void *ret = malloc(size); + if(!ret) + mfatal("malloc", size); + return ret; +} + +void * +emallocz(uint size) { + void *ret = emalloc(size); + memset(ret, 0, size); + return ret; +} + +void * +erealloc(void *ptr, uint size) { + void *ret = realloc(ptr, size); + if(!ret) + mfatal("realloc", size); + return ret; +} + +char* +estrdup(const char *str) { + void *ret = strdup(str); + if(!ret) + mfatal("strdup", strlen(str)); + return ret; +} + +char* +estrndup(const char *str, uint len) { + char *ret; + + len = min(len, strlen(str)); + ret = emalloc(len + 1); + memcpy(ret, str, len); + ret[len] = '\0'; + return ret; +} + + +uint +tokenize(char *res[], uint reslen, char *str, char delim) { + char *s; + uint i; + + i = 0; + s = str; + while(i < reslen && *s) { + while(*s == delim) + *(s++) = '\0'; + if(*s) + res[i++] = s; + while(*s && *s != delim) + s++; + } + return i; +} + +uint +stokenize(char *res[], uint reslen, char *str, char *delim) { + char *s; + uint i; + + i = 0; + s = str; + while(i < reslen && *s) { + while(strchr(delim, *s)) + *(s++) = '\0'; + if(*s) + res[i++] = s; + while(*s && !strchr(delim, *s)) + s++; + } + return i; +} + +int +max(int a, int b) { + if(a > b) + return a; + return b; +} + +int +min(int a, int b) { + if(a < b) + return a; + return b; +} + +int +utflcpy(char *to, const char *from, int l) { + char *p; + + p = utfecpy(to, to+l, from); + return p-to; +} + +uint +strlcat(char *dst, const char *src, uint size) { + const char *s; + char *d; + int n, len; + + d = dst; + s = src; + n = size; + while(n-- > 0 && *d != '\0') + d++; + len = n; + + while(*s != '\0') { + if(n-- > 0) + *d++ = *s; + s++; + } + if(len > 0) + *d = '\0'; + return size - n - 1; +} + +/* TODO: Make this UTF-8 compliant. */ +char* +strcasestr(const char *dst, const char *src) { + int dc, sc; + int len; + + len = strlen(src) - 1; + for(; (sc = *src) && *dst; src++) { + sc = tolower(dc); + for(; (dc = *dst); dst++) { + dc = tolower(dc); + if(sc == dc && !strncasecmp(dst+1, src+1, len)) + return (char*)(uintptr_t)dst; + } + } + return nil; +} + diff --git a/cmd/wihack.sh b/cmd/wihack.sh new file mode 100644 index 0000000..2c401d4 --- /dev/null +++ b/cmd/wihack.sh @@ -0,0 +1,44 @@ +#!/bin/sh -f + +usage() { + echo 1>&2 Usage: \ + "$0 [-transient <window>] [-type <window_type>[,...]] [-tags <tags>] <command> [<arg> ...]" + exit 1 +} + +checkarg='[ ${#@} -gt 0 ] || usage' +export WMII_HACK_TIME=$(date +%s) + +while [ ${#@} -gt 0 ] +do + case $1 in + -transient) + shift; eval $checkarg + export WMII_HACK_TRANSIENT=$1 + shift;; + -type) + shift; eval $checkarg + export WMII_HACK_TYPE=$1 + shift;; + -tags) + shift; eval $checkarg + export WMII_HACK_TAGS=$1 + shift;; + -*) + usage;; + *) + break;; + esac +done + +eval $checkarg + +if [ ! -u "`which $1`" -a ! -g "`which $1`" ] +then + export LD_PRELOAD=libwmii_hack.so + export LD_LIBRARY_PATH="@LIBDIR@${LD_LIBRARY_PATH:+:}${LD_LIBRARY_PATH}" +else + unset WMII_HACK_TRANSIENT WMII_HACK_TYPE WMII_HACK_TAGS +fi +exec "$@" + diff --git a/cmd/wmii.rc.rc b/cmd/wmii.rc.rc new file mode 100755 index 0000000..392a0d7 --- /dev/null +++ b/cmd/wmii.rc.rc @@ -0,0 +1,181 @@ + +# For the time being, this file follows the lisp bracing +# convention. i.e.: +# if(frob this) { +# frob that +# if(frob theother) { +# unfrob this +# unfrob that}} + +wmiiscript=$1 +wmiikeys=() + +wi_newline=' +' + +echo Start $wmiiscript | wmiir write /event >[2]/dev/null \ + || exit write + +if(~ $scriptname '') + scriptname=$wmiiscript + +# Blech. +if(! test -x $PLAN9/bin/read) + fn read { sh -c 'read -r x || exit 1; echo $x' } + +fn wi_atexit {} +fn sigexit { + wi_atexit +} + +fn wi_fatal { + echo $scriptname: Fatal: $* + exit fatal +} + +fn wi_notice { + xmessage $scriptname: Notice: $* +} + +fn wi_readctl { wmiir read /ctl | sed -n 's/^'$1' (.*)/\1/p' } + +wmiifont=`{wi_readctl font} +wmiinormcol=`{wi_readctl normcolors} +wmiifocuscol=`{wi_readctl focuscolors} + +fn wi_fnmenu { + group=$1^Menu-$2 last=$group^_last fns=`{wi_getfuns $group} { + shift 2 + if(! ~ $#fns 0) { + res = `{wmii9menu -i $"($last) $fns} \ + if(! ~ $res '') { + ($last) = $res + $group-$res $*}}} +} + +fn wi_fn-p { + rc -c 'whatis '$1 >[2]/dev/null | grep -s '^fn ' +} + +fn wi_proglist { + # XXX: maxdepth is not POSIX compliant. + ifs=: { find -L `{echo -n $*} -type f -a -maxdepth 1 \ + '(' -perm -0100 -o -perm -0010 -o -perm -0001 ')' >[2]/dev/null \ + | sed 's,.*/,,' | sort | uniq} +} + +fn wi_actions { + { wi_proglist $WMII_CONFPATH + wi_getfuns Action + } | sort | uniq +} + +fn wi_script { + noprog=true prog=() { + if(~ $1 -f) { + shift + noprog=/dev/null + } + prog = `{rc -c 'path=$confpath whatis '$1 >[2]/dev/null \ + | grep -v '^fn |=' || echo $noprog} + shift; echo $prog $*} +} + + +fn wi_initkeys { + ifs=() { + wmiikeys = `{wmiir read /keys} { + mykeys = `{comm -23 \ + <{wi_getfuns Key | sort | uniq} \ + <{echo $wmiikeys | sort | uniq}} + {echo $wmiikeys; wi_getfuns Key} \ + | sort | uniq \ + | wmiir write /keys }} + fn wi_atexit { + wi_cleankeys + } +} + +fn wi_cleankeys { + ifs=() { + wmiikeys = `{wmiir read /keys} { + comm -23 <{echo $wmiikeys | sort | uniq} \ + <{echo $mykeys} \ + | wmiir write /keys }} +} + +fn wi_runcmd { @{ + rfork ns + path=$oldpath + if(~ $1 -t) { + shift + * = (wihack -tags `{wmiir read /tag/sel/ctl | sed 1q} $*) } + fn `{env | 9 sed -n 's/^fn#([^=]+).*/\1/p'} + mykeys=() + if(! ~ $* '') + eval exec $* & } +} + +fn wi_getfuns { + env | sed -n 's/^fn#'^$1^'-([^=]+).*/\1/p' | sort | uniq +} + +for(i in Key Event Action '*Menu') + fns=`{wi_getfuns $i} { + if(! ~ $fns '') + fn $i-^$fns} + +fn wi_tags { + wmiir ls /tag | sed 's,/,,; /^sel$/d' +} + +fn wi_seltag { + wmiir read /tag/sel/ctl | sed 1q +} + +fn wi_selclient { + wmiir read /client/sel/ctl | sed 1q +} + +fn wi_readevent { + wmiir read /event +} + +fn wi_eventloop { + wi_initkeys + + wi_readevent | + while(ifs=$wi_ewlinel{wi_event=`{read}}) { + ifs=$wi_newline{ + wi_arg=`{echo $wi_event | sed 's/^[^ ]+ //'}} + * = `{echo $wi_event} + event = $1; shift + Event-$"event $* + } >[2]/dev/null </dev/null + true +} + +fn Event-Key { + Key-$1 $1 +} + +fn Event-Quit { + exit +} + +fn Event-Start { + if(~ $1 $wmiiscript) + exit +} + +fn Action { + cmd=$1 action=Action-$"cmd { shift + if(! ~ $cmd '') { + if(wi_fn-p $action) + $action $* + if not + wi_runcmd `{wi_script $cmd} $* + } + } +} + diff --git a/cmd/wmii.sh.sh b/cmd/wmii.sh.sh new file mode 100755 index 0000000..d636f26 --- /dev/null +++ b/cmd/wmii.sh.sh @@ -0,0 +1,219 @@ + +[ -z "$scriptname" ] && scriptname="$wmiiscript" +echo Start $wmiiscript | wmiir write /event 2>/dev/null || + exit 1 + +wi_newline=' +' + +_wi_script() { + # Awk script to mangle key/event/action definition spec + # into switch-case functions and lists of the cases. Also + # generates a simple key binding help text based on KeyGroup + # clauses and comments that appear on key lines. + # + # Each clause (Key, Event, Action) generates a function of the + # same name which executes the indented text after the matching + # clause. Clauses are selected based on the first argument passed + # to the mangled function. Additionally, a variable is created named + # for the plouralized version of the clause name (Keys, Events, + # Actions) which lists each case value. These are used for actions + # menus and to write wmii's /keys file. + cat <<'!' + BEGIN { + arg[1] = "Nop" + narg = 1; + body = ""; + keyhelp = "" + } + function quote(s) { + gsub(/'/, "'\\''", s) + return "'" s "'" + } + function addevent() { + var = arg[1] "s" + for(i=2; i <= narg; i++) { + if(body == "") + delete a[arg[1],arg[i]] + else + a[arg[1],arg[i]] = body + if(i == 2) { + # There's a bug here. Can you spot it? + gsub("[^a-zA-Z_0-9]", "_", arg[2]); + body = sprintf("%s %s \"$@\"", arg[1], arg[2]) + } + } + } + /^(Key)Group[ \t]/ { + sub(/^[^ \t]+[ \t]+/, "") + keyhelp = keyhelp "\n " $0 "\n" + } + /^(Event|Key|Action|Menu)[ \t]/ { + addevent() + split($0, tmp, /[ \t]+#[ \t]*/) + narg = split(tmp[1], arg) + if(arg[1] == "Key" && tmp[2]) + for (i=2; i <= narg; i++) + keyhelp = keyhelp sprintf(" %-20s %s\n", + arg[i], tmp[2]) + body = "" + } + /^[ \t]/ { + sub(/^( |\t)/, "") + body = body"\n"$0 + } + + END { + addevent() + for(k in a) { + split(k, b, SUBSEP) + c[b[1]] = c[b[1]] b[2] "\n" + if(body != "") + d[b[1]] = d[b[1]] quote(b[2]) ")" a[k] "\n;;\n" + } + for(k in c) + printf "%ss=%s\n", k, quote(c[k]) + for(k in d) { + printf "%s() {\n", k + printf " %s=$1; shift\n", tolower(k) + printf "case $%s in\n%s\n*) return 1\nesac\n", tolower(k), d[k] + printf "}\n" + } + print "KeysHelp=" quote(keyhelp) + } +! +} + +_wi_text() { + cat <<'!' +Event Start + if [ "$1" = "$wmiiscript" ]; then + exit + fi +Event Key + Key "$@" +! + eval "cat <<! +$( (test ! -t 0 && cat; for a; do eval "$a"; done) | sed '/^[ ]/s/\([$`\\]\)/\\\1/g') +! +" +} + +wi_events() { + #cho "$(_wi_text "$@" | awk "$(_wi_script)")" | cat -n + eval "$(_wi_text "$@" | awk "$(_wi_script)")" +} + +wi_fatal() { + echo $scriptname: Fatal: $* + exit 1 +} + +wi_notice() { + xmessage $scriptname: Notice: $* +} + +wi_readctl() { + wmiir read /ctl | sed -n 's/^'$1' //p' +} + +wmiifont="$(wi_readctl font)" +wmiinormcol="$(wi_readctl normcolors)" +wmiifocuscol="$(wi_readctl focuscolors)" + +wi_fnmenu() { + group="$1-$2"; shift 2 + _last="$(echo $group|tr - _)_last" + eval "last=\"\$$_last\"" + res=$(set -- $(echo "$Menus" | awk -v "s=$group" 'BEGIN{n=length(s)} + substr($1,1,n) == s{print substr($1,n+2)}') + [ $# != 0 ] && wmii9menu -i "$last" "$@") + if [ -n "$res" ]; then + eval "$_last="'"$res"' + Menu $group-$res "$@" + fi +} + +wi_proglist() { + ls -lL $(echo $* | sed 'y/:/ /') 2>/dev/null \ + | awk '$1 ~ /^[^d].*x/ { print $NF }' \ + | sort | uniq +} + +wi_actions() { + { wi_proglist $WMII_CONFPATH + echo -n "$Actions" + } | sort | uniq +} + +wi_runconf() { + sflag=""; if [ "$1" = -s ]; then sflag=1; shift; fi + which="$(which which)" + prog=$(PATH="$WMII_CONFPATH" "$which" -- $1 2>/dev/null); shift + if [ -n "$prog" ]; then + if [ -z "$sflag" ] + then "$prog" "$@" + else . "$prog" + fi + else return 1 + fi +} + +wi_script() { + _noprog=true + if [ "$1" = -f ]; then + shift + _noprog=/dev/null + fi + which=$(which which) + _prog=$(PATH="$WMII_CONFPATH" $which $1 || echo $_noprog); shift + shift; echo "$_prog $*" +} + +wi_runcmd() { + if [ "$1" = -t ]; then + shift + set -- wihack -tags $(wmiir read /tag/sel/ctl | sed 1q) "$*" + fi + eval exec "$*" & +} + +wi_tags() { + wmiir ls /tag | sed 's,/,,; /^sel$/d' +} + +wi_seltag() { + wmiir read /tag/sel/ctl | sed 1q | tr -d '\012' +} + +wi_selclient() { + wmiir read /client/sel/ctl | sed 1q | tr -d '\012' +} + +wi_eventloop() { + echo "$Keys" | wmiir write /keys + + if [ "$1" = -i ] + then cat + else wmiir read /event + fi | + while read wi_event; do + IFS="$wi_newline" + wi_arg=$(echo "$wi_event" | sed 's/^[^ ]* //') + unset IFS + set -- $wi_event + event=$1; shift + ( Event $event "$@" ) + done + true +} + +action() { + action=$1; shift + if [ -n "$action" ]; then + set +x + Action $action "$@" \ + || wi_runconf $action "$@" + fi +} + diff --git a/cmd/wmii/Makefile b/cmd/wmii/Makefile new file mode 100644 index 0000000..de635ca --- /dev/null +++ b/cmd/wmii/Makefile @@ -0,0 +1,47 @@ +ROOT= ../.. +include $(ROOT)/mk/hdr.mk +include $(ROOT)/mk/wmii.mk + +main.c: $(ROOT)/mk/wmii.mk + +TARG = wmii +HFILES= dat.h fns.h + +PACKAGES += $(X11PACKAGES) xext xrandr xrender xinerama + +LIB = $(LIBIXP) +LIBS += -lm $(LIBS9) + +CFLAGS += $(INCICONV) -DIXP_NEEDAPI=97 +OBJ = area \ + bar \ + client \ + column \ + div \ + error \ + event \ + ewmh \ + float \ + frame \ + fs \ + geom \ + key \ + layout \ + main \ + map \ + message \ + mouse \ + print \ + root \ + rule \ + printevent\ + screen \ + _util \ + view \ + xdnd \ + x11 \ + xext \ + ../util + +include $(ROOT)/mk/one.mk + diff --git a/cmd/wmii/_util.c b/cmd/wmii/_util.c new file mode 100644 index 0000000..feafba9 --- /dev/null +++ b/cmd/wmii/_util.c @@ -0,0 +1,378 @@ +/* Copyright ©2008-2010 Kris Maglione <maglione.k at Gmail> + * See LICENSE file for license details. + */ +#include "dat.h" +#include <errno.h> +#include <fcntl.h> +#include <stdarg.h> +#include <sys/signal.h> +#include <sys/wait.h> +#include <unistd.h> +#include <bio.h> +#include "fns.h" + +/* Blech. */ +#define VECTOR(type, nam, c) \ +void \ +vector_##c##init(Vector_##nam *v) { \ + memset(v, 0, sizeof *v); \ +} \ + \ +void \ +vector_##c##free(Vector_##nam *v) { \ + free(v->ary); \ + memset(v, 0, sizeof *v); \ +} \ + \ +void \ +vector_##c##push(Vector_##nam *v, type val) { \ + if(v->n == v->size) { \ + if(v->size == 0) \ + v->size = 2; \ + v->size <<= 2; \ + v->ary = erealloc(v->ary, v->size * sizeof *v->ary); \ + } \ + v->ary[v->n++] = val; \ +} \ + +VECTOR(long, long, l) +VECTOR(Rectangle, rect, r) +VECTOR(void*, ptr, p) + +int +doublefork(void) { + pid_t pid; + int status; + + switch(pid=fork()) { + case -1: + fatal("Can't fork(): %r"); + case 0: + switch(pid=fork()) { + case -1: + fatal("Can't fork(): %r"); + case 0: + return 0; + default: + exit(0); + } + default: + waitpid(pid, &status, 0); + return pid; + } + /* NOTREACHED */ +} + +void +closeexec(int fd) { + if(fcntl(fd, F_SETFD, FD_CLOEXEC) == -1) + fatal("can't set %d close on exec: %r", fd); +} + +int +spawn3(int fd[3], const char *file, char *argv[]) { + /* Some ideas from Russ Cox's libthread port. */ + int p[2]; + int pid; + + if(pipe(p) < 0) + return -1; + closeexec(p[1]); + + switch(pid = doublefork()) { + case 0: + dup2(fd[0], 0); + dup2(fd[1], 1); + dup2(fd[2], 2); + + execvp(file, argv); + write(p[1], &errno, sizeof errno); + exit(1); + break; + default: + close(p[1]); + if(read(p[0], &errno, sizeof errno) == sizeof errno) + pid = -1; + close(p[0]); + break; + case -1: /* can't happen */ + break; + } + + close(fd[0]); + /* These could fail if any of these was also a previous fd. */ + close(fd[1]); + close(fd[2]); + return pid; +} + +int +spawn3l(int fd[3], const char *file, ...) { + va_list ap; + char **argv; + int i, n; + + va_start(ap, file); + for(n=0; va_arg(ap, char*); n++) + ; + va_end(ap); + + argv = emalloc((n+1) * sizeof *argv); + va_start(ap, file); + quotefmtinstall(); + for(i=0; i <= n; i++) + argv[i] = va_arg(ap, char*); + va_end(ap); + + i = spawn3(fd, file, argv); + free(argv); + return i; +} + +#ifdef __linux__ +# define PROGTXT "exe" +#else +# define PROGTXT "file" +#endif + +static void +_backtrace(int pid, char *btarg) { + char *proc, *spid, *gdbcmd; + int fd[3], p[2]; + int status, cmdfd; + + gdbcmd = estrdup("/tmp/gdbcmd.XXXXXX"); + if(pipe(p) < 0) + goto done; + closeexec(p[0]); + + cmdfd = mkstemp(gdbcmd); + if(cmdfd < 0) + goto done; + + fprint(cmdfd, "bt %s\n", btarg); + fprint(cmdfd, "detach\n"); + close(cmdfd); + + fd[0] = open("/dev/null", O_RDONLY); + fd[1] = p[1]; + fd[2] = dup(2); + + proc = sxprint("/proc/%d/" PROGTXT, pid); + spid = sxprint("%d", pid); + if(spawn3l(fd, "gdb", "gdb", "-batch", "-x", gdbcmd, proc, spid, nil) < 0) { + unlink(gdbcmd); + goto done; + } + + Biobuf bp; + char *s; + + Binit(&bp, p[0], OREAD); + while((s = Brdstr(&bp, '\n', 1))) { + Dprint(DStack, "%s\n", s); + free(s); + } + unlink(gdbcmd); + +done: + free(gdbcmd); + kill(pid, SIGKILL); + waitpid(pid, &status, 0); +} + +void +backtrace(char *btarg) { + int pid; + + /* Fork so we can backtrace the child. Keep this stack + * frame minimal, so the trace is fairly clean. + */ + Debug(DStack) + switch(pid = fork()) { + case -1: + return; + case 0: + kill(getpid(), SIGSTOP); + _exit(0); + default: + _backtrace(pid, btarg); + break; + } + +} + +void +reinit(Regex *r, char *regx) { + + refree(r); + + if(regx[0] != '\0') { + r->regex = estrdup(regx); + r->regc = regcomp(regx); + } +} + +void +refree(Regex *r) { + + free(r->regex); + free(r->regc); + r->regex = nil; + r->regc = nil; +} + +void +uniq(char **toks) { + char **p, **q; + + q = toks; + if(*q == nil) + return; + for(p=q+1; *p; p++) + if(strcmp(*q, *p)) + *++q = *p; + *++q = nil; +} + +char** +comm(int cols, char **toka, char **tokb) { + Vector_ptr vec; + char **ret; + int cmp; + + vector_pinit(&vec); + while(*toka || *tokb) { + if(!*toka) + cmp = 1; + else if(!*tokb) + cmp = -1; + else + cmp = strcmp(*toka, *tokb); + if(cmp < 0) { + if(cols & CLeft) + vector_ppush(&vec, *toka); + toka++; + }else if(cmp > 0) { + if(cols & CRight) + vector_ppush(&vec, *tokb); + tokb++; + }else { + if(cols & CCenter) + vector_ppush(&vec, *toka); + toka++; + tokb++; + } + } + vector_ppush(&vec, nil); + ret = strlistdup((char**)vec.ary); + free(vec.ary); + return ret; +} + +void +grep(char **list, Reprog *re, int flags) { + char **p, **q; + int res; + + q = list; + for(p=q; *p; p++) { + res = 0; + if(re) + res = regexec(re, *p, nil, 0); + if(res && !(flags & GInvert) + || !res && (flags & GInvert)) + *q++ = *p; + } + *q = nil; +} + +char* +join(char **list, char *sep) { + Fmt f; + char **p; + + if(fmtstrinit(&f) < 0) + abort(); + + for(p=list; *p; p++) { + if(p != list) + fmtstrcpy(&f, sep); + fmtstrcpy(&f, *p); + } + + return fmtstrflush(&f); +} + +int +strlcatprint(char *buf, int len, const char *fmt, ...) { + va_list ap; + int buflen; + int ret; + + va_start(ap, fmt); + buflen = strlen(buf); + ret = vsnprint(buf+buflen, len-buflen, fmt, ap); + va_end(ap); + return ret; +} + +char* +pathsearch(const char *path, const char *file, bool slashok) { + char *orig, *p, *s; + + if(!slashok && strchr(file, '/') > file) + file = sxprint("%s/%s", getcwd(buffer, sizeof buffer), file); + else if(!strncmp(file, "./", 2)) + file = sxprint("%s/%s", getcwd(buffer, sizeof buffer), file+2); + if(file[0] == '/') { + if(access(file, X_OK)) + return strdup(file); + return nil; + } + + orig = estrdup(path ? path : getenv("PATH")); + for(p=orig; (s=strtok(p, ":")); p=nil) { + s = smprint("%s/%s", s, file); + if(!access(s, X_OK)) + break; + free(s); + } + free(orig); + return s; +} + +int +unquote(char *buf, char *toks[], int ntoks) { + char *s, *t; + bool inquote; + int n; + + n = 0; + s = buf; + while(*s && n < ntoks) { + while(*s && utfrune(" \t\r\n", *s)) + s++; + inquote = false; + toks[n] = s; + t = s; + while(*s && (inquote || !utfrune(" \t\r\n", *s))) { + if(*s == '\'') { + if(inquote && s[1] == '\'') + *t++ = *s++; + else + inquote = !inquote; + } + else + *t++ = *s; + s++; + } + if(*s) + s++; + *t = '\0'; + if(s != toks[n]) + n++; + } + return n; +} + diff --git a/cmd/wmii/area.c b/cmd/wmii/area.c new file mode 100644 index 0000000..0f94e72 --- /dev/null +++ b/cmd/wmii/area.c @@ -0,0 +1,328 @@ +/* Copyright ©2006-2010 Kris Maglione <maglione.k at Gmail> + * See LICENSE file for license details. + */ +#include "dat.h" +#include <math.h> +#include <limits.h> +#include "fns.h" + +Client* +area_selclient(Area *a) { + if(a && a->sel) + return a->sel->client; + return nil; +} + +int +area_idx(Area *a) { + View *v; + Area *ap; + uint i; + + v = a->view; + if(a->floating) + return -1; + i = 1; + for(ap=v->areas[a->screen]; a != ap; ap=ap->next) + i++; + return i; +} + +static Rectangle +area_rect(void *v) { + Area *a; + + a = v; + return a->r; +} + +Area* +area_find(View *v, Rectangle r, int dir, bool wrap) { + static Vector_ptr vec; + Area *a; + int s; + + vec.n = 0; + foreach_column(v, s, a) + vector_ppush(&vec, a); + + return findthing(r, dir, &vec, area_rect, wrap); +} + +int +afmt(Fmt *f) { + Area *a; + + a = va_arg(f->args, Area*); + if(a == nil) + return fmtstrcpy(f, "<nil>"); + if(a->floating) + return fmtstrcpy(f, "~"); + if(a->screen > 0 || (f->flags & FmtSharp)) + return fmtprint(f, "%d:%d", a->screen, area_idx(a)); + return fmtprint(f, "%d", area_idx(a)); +} + +char* +area_name(Area *a) { + + if(a == nil) + return "<nil>"; + if(a->floating) + return "~"; + return sxprint("%d", area_idx(a)); +} + +Area* +area_create(View *v, Area *pos, int scrn, uint width) { + static ushort id = 1; + int i, j; + uint minwidth, index; + int numcols; + Area *a; + + assert(!pos || pos->screen == scrn); + SET(index); + if(v->areas) { /* Creating a column. */ + minwidth = column_minwidth(); + index = pos ? area_idx(pos) : 1; + numcols = 0; + for(a=v->areas[scrn]; a; a=a->next) + numcols++; + + /* TODO: Need a better sizing/placing algorithm. + */ + if(width == 0) { + if(numcols >= 0) { + width = view_newcolwidth(v, index); + if (width == 0) + width = Dx(v->r[scrn]) / (numcols + 1); + } + else + width = Dx(v->r[scrn]); + } + + if(width < minwidth) + width = minwidth; + minwidth = numcols * minwidth + minwidth; + if(minwidth > Dx(v->r[scrn])) + return nil; + + i = minwidth - Dx(v->pad[scrn]) - Dx(v->r[scrn]); + if(i > 0 && Dx(v->pad[scrn])) { + j = min(i/2, v->pad[scrn].min.x); + v->pad[scrn].min.x -= j; + v->pad[scrn].max.x += i - j; + } + + view_scale(v, scrn, Dx(v->r[scrn]) - width); + } + + a = emallocz(sizeof *a); + a->view = v; + a->screen = scrn; + a->id = id++; + a->floating = !v->floating; + if(a->floating) + a->mode = Coldefault; + else + a->mode = def.colmode; + a->frame = nil; + a->sel = nil; + + a->r = v->r[scrn]; + a->r.min.x = 0; + a->r.max.x = width; + + if(a->floating) { + v->floating = a; + a->screen = -1; + } + else if(pos) { + a->next = pos->next; + a->prev = pos; + } + else { + a->next = v->areas[scrn]; + v->areas[scrn] = a; + } + if(a->prev) + a->prev->next = a; + if(a->next) + a->next->prev = a; + + if(v->sel == nil && !a->floating) + area_focus(a); + + if(!a->floating) + event("CreateColumn %ud\n", index); + return a; +} + +void +area_destroy(Area *a) { + Area *newfocus; + View *v; + int idx; + + v = a->view; + + if(a->frame) + die("destroying non-empty area"); + + if(v->revert == a) + v->revert = nil; + if(v->oldsel == a) + v->oldsel = nil; + + idx = area_idx(a); + + if(a->prev && !a->prev->floating) + newfocus = a->prev; + else + newfocus = a->next; + + /* Can only destroy the floating area when destroying a + * view---after destroying all columns. + */ + assert(!a->floating || !v->areas[0]); + if(a->prev) + a->prev->next = a->next; + else if(!a->floating) + v->areas[a->screen] = a->next; + else + v->floating = nil; + if(a->next) + a->next->prev = a->prev; + + if(newfocus && v->sel == a) + area_focus(newfocus); + + view_arrange(v); + event("DestroyArea %d\n", idx); + + free(a); +} + +void +area_moveto(Area *to, Frame *f) { + Area *from; + + assert(to->view == f->view); + + if(f->client->fullscreen >= 0 && !to->floating) + return; + + from = f->area; + if (from == to) + return; + + area_detach(f); + + /* Temporary kludge. */ + if(!to->floating + && to->floating != from->floating + && !eqrect(f->colr, ZR)) + column_attachrect(to, f, f->colr); + else + area_attach(to, f); +} + +void +area_setsel(Area *a, Frame *f) { + View *v; + + v = a->view; + /* XXX: Stack. */ + for(; f && f->collapsed && f->anext; f=f->anext) + ; + for(; f && f->collapsed && f->aprev; f=f->aprev) + ; + + if(a == v->sel && f) + frame_focus(f); + else + a->sel = f; +} + +void +area_attach(Area *a, Frame *f) { + + f->area = a; + if(a->floating) + float_attach(a, f); + else + column_attach(a, f); + + view_arrange(a->view); + + if(btassert("4 full", a->frame && a->sel == nil)) + a->sel = a->frame; +} + +void +area_detach(Frame *f) { + View *v; + Area *a; + + a = f->area; + v = a->view; + + if(a->floating) + float_detach(f); + else + column_detach(f); + + if(v->sel->sel == nil && v->floating->sel) + v->sel = v->floating; + + view_arrange(v); +} + +void +area_focus(Area *a) { + Frame *f; + View *v; + Area *old_a; + + v = a->view; + f = a->sel; + old_a = v->sel; + + if(!a->floating && view_fullscreen_p(v, a->screen)) + return; + + v->sel = a; + if(!a->floating) { + v->selcol = area_idx(a); + v->selscreen = a->screen; + } + if(a != old_a) + v->oldsel = nil; + + if((old_a) && (a->floating != old_a->floating)) { + v->revert = old_a; + if(v->floating->max) + view_update(v); + } + + if(v != selview) + return; + + move_focus(old_a->sel, f); + + if(f) + client_focus(f->client); + else + client_focus(nil); + + if(a != old_a) { + event("AreaFocus %a\n", a); + /* Deprecated */ + if(a->floating) + event("FocusFloating\n"); + else + event("ColumnFocus %d\n", area_idx(a)); + } +} + diff --git a/cmd/wmii/bar.c b/cmd/wmii/bar.c new file mode 100644 index 0000000..fd4ba26 --- /dev/null +++ b/cmd/wmii/bar.c @@ -0,0 +1,300 @@ +/* Copyright ©2006-2010 Kris Maglione <maglione.k at Gmail> + * See LICENSE file for license details. + */ +#include "dat.h" +#include "fns.h" + +static Handlers handlers; + +#define foreach_bar(s, b) \ + for(int __bar_n=0; __bar_n < nelem((s)->bar); __bar_n++) \ + for((b)=(s)->bar[__bar_n]; (b); (b)=(b)->next) + +void +bar_init(WMScreen *s) { + WinAttr wa; + + if(s->barwin) { + bar_resize(s); + return; + } + + s->brect = s->r; + s->brect.min.y = s->brect.max.y - labelh(def.font); + + wa.override_redirect = 1; + wa.background_pixmap = ParentRelative; + wa.event_mask = ExposureMask + | ButtonPressMask + | ButtonReleaseMask + | FocusChangeMask; + s->barwin = createwindow(&scr.root, s->brect, scr.depth, InputOutput, + &wa, CWOverrideRedirect + | CWBackPixmap + | CWEventMask); + s->barwin->aux = s; + xdnd_initwindow(s->barwin); + sethandler(s->barwin, &handlers); + if(s == screens[0]) + mapwin(s->barwin); +} + +void +bar_resize(WMScreen *s) { + + s->brect = s->r; + s->brect.min.y = s->r.max.y - labelh(def.font); + if(s == screens[0]) + reshapewin(s->barwin, s->brect); + else + s->brect.min.y = s->r.max.y; + /* FIXME: view_arrange. */ +} + +void +bar_setbounds(WMScreen *s, int left, int right) { + Rectangle *r; + + if(s != screens[0]) + return; + + r = &s->brect; + r->min.x = left; + r->max.x = right; + if(Dy(*r)) + reshapewin(s->barwin, *r); +} + +void +bar_sety(WMScreen *s, int y) { + Rectangle *r; + int dy; + + r = &s->brect; + + dy = Dy(*r); + r->min.y = y; + r->max.y = y + dy; + if(Dy(*r)) + reshapewin(s->barwin, *r); +} + +Bar* +bar_create(Bar **bp, const char *name) { + static uint id = 1; + WMScreen *s, **sp; + Bar *b; + uint i; + + b = bar_find(*bp, name);; + if(b) + return b; + + b = emallocz(sizeof *b); + b->id = id++; + utflcpy(b->name, name, sizeof b->name); + b->col = def.normcolor; + + strlcat(b->buf, b->col.colstr, sizeof(b->buf)); + strlcat(b->buf, " ", sizeof(b->buf)); + strlcat(b->buf, b->text, sizeof(b->buf)); + + SET(i); + for(sp=screens; (s = *sp); sp++) { + i = bp - s->bar; + if(i < nelem(s->bar)) + break; + } + b->bar = i; + b->screen = s; + + for(; *bp; bp = &bp[0]->next) + if(strcmp(bp[0]->name, name) >= 0) + break; + b->next = *bp; + *bp = b; + + return b; +} + +void +bar_destroy(Bar **bp, Bar *b) { + Bar **p; + + for(p = bp; *p; p = &p[0]->next) + if(*p == b) break; + *p = b->next; + free(b); +} + +void +bar_draw(WMScreen *s) { + Bar *b, *tb, *largest, **pb; + Rectangle r; + Align align; + uint width, tw; + float shrink; + + /* To do: Generalize this. */ + + largest = nil; + width = 0; + foreach_bar(s, b) { + b->r.min = ZP; + b->r.max.y = Dy(s->brect); + b->r.max.x = (def.font->height & ~1) + def.font->pad.min.x + def.font->pad.max.x; + if(b->text && strlen(b->text)) + b->r.max.x += textwidth(def.font, b->text); + width += Dx(b->r); + } + + if(width > Dx(s->brect)) { /* Not enough room. Shrink bars until they all fit. */ + foreach_bar(s, b) { + for(pb=&largest; *pb; pb=&pb[0]->smaller) + if(Dx(pb[0]->r) < Dx(b->r)) + break; + b->smaller = *pb; + *pb = b; + } + SET(shrink); + tw = 0; + for(tb=largest; tb; tb=tb->smaller) { + width -= Dx(tb->r); + tw += Dx(tb->r); + shrink = (Dx(s->brect) - width) / (float)tw; + if(tb->smaller && Dx(tb->r) * shrink < Dx(tb->smaller->r)) + continue; + if(width + (int)(tw * shrink) <= Dx(s->brect)) + break; + } + if(tb) + for(b=largest; b != tb->smaller; b=b->smaller) + b->r.max.x *= shrink; + width += tw * shrink; + } + + tb = nil; + foreach_bar(s, b) { + if(tb) + b->r = rectaddpt(b->r, Pt(tb->r.max.x, 0)); + if(b == s->bar[BRight]) + b->r.max.x += Dx(s->brect) - width; + tb = b; + } + + r = rectsubpt(s->brect, s->brect.min); + fill(disp.ibuf, r, def.normcolor.bg); + border(disp.ibuf, r, 1, def.normcolor.border); + foreach_bar(s, b) { + align = Center; + if(b == s->bar[BRight]) + align = East; + fill(disp.ibuf, b->r, b->col.bg); + drawstring(disp.ibuf, def.font, b->r, align, b->text, b->col.fg); + border(disp.ibuf, b->r, 1, b->col.border); + } + copyimage(s->barwin, r, disp.ibuf, ZP); +} + +void +bar_load(Bar *b) { + IxpMsg m; + char *p, *q; + + p = b->buf; + m = ixp_message(p, strlen(p), 0); + msg_parsecolors(&m, &b->col); + + q = (char*)m.end-1; + while(q >= (char*)m.pos && *q == '\n') + *q-- = '\0'; + + q = b->text; + utflcpy(q, (char*)m.pos, sizeof b->text); + + p[0] = '\0'; + strlcat(p, b->col.colstr, sizeof b->buf); + strlcat(p, " ", sizeof b->buf); + strlcat(p, b->text, sizeof b->buf); + + bar_draw(b->screen); +} + +Bar* +bar_find(Bar *bp, const char *name) { + Bar *b; + + for(b=bp; b; b=b->next) + if(!strcmp(b->name, name)) + break; + return b; +} + +static char *barside[] = { + [BLeft] = "Left", + [BRight] = "Right", +}; + +static Bar* +findbar(WMScreen *s, Point p) { + Bar *b; + + foreach_bar(s, b) + if(rect_haspoint_p(p, b->r)) + return b; + return nil; +} + +static void +bdown_event(Window *w, XButtonPressedEvent *e) { + WMScreen *s; + Bar *b; + + /* Ungrab so a menu can receive events before the button is released */ + XUngrabPointer(display, e->time); + sync(); + + s = w->aux; + b = findbar(s, Pt(e->x, e->y)); + if(b) + event("%sBarMouseDown %d %s\n", barside[b->bar], e->button, b->name); +} + +static void +bup_event(Window *w, XButtonPressedEvent *e) { + WMScreen *s; + Bar *b; + + s = w->aux; + b = findbar(s, Pt(e->x, e->y)); + if(b) + event("%sBarClick %d %s\n", barside[b->bar], e->button, b->name); +} + +static Rectangle +dndmotion_event(Window *w, Point p) { + WMScreen *s; + Bar *b; + + s = w->aux; + b = findbar(s, p); + if(b) { + event("%sBarDND 1 %s\n", barside[b->bar], b->name); + return b->r; + } + return ZR; +} + +static void +expose_event(Window *w, XExposeEvent *e) { + USED(w, e); + bar_draw(w->aux); +} + +static Handlers handlers = { + .bdown = bdown_event, + .bup = bup_event, + .dndmotion = dndmotion_event, + .expose = expose_event, +}; + diff --git a/cmd/wmii/client.c b/cmd/wmii/client.c new file mode 100644 index 0000000..5f82455 --- /dev/null +++ b/cmd/wmii/client.c @@ -0,0 +1,1212 @@ +/* Copyright ©2004-2006 Anselm R. Garbe <garbeam at gmail dot com> + * Copyright ©2006-2010 Kris Maglione <maglione.k at Gmail> + * See LICENSE file for license details. + */ +#include "dat.h" +#include <ctype.h> +#include <strings.h> +#include <X11/Xatom.h> +#include "fns.h" + +#define Mbsearch(k, l, cmp) bsearch(k, l, nelem(l), sizeof(*l), cmp) + +static Handlers handlers; + +enum { + ClientMask = StructureNotifyMask + | PropertyChangeMask + | EnterWindowMask + | FocusChangeMask, + ButtonMask = ButtonPressMask + | ButtonReleaseMask, +}; + +static Group* group; + +static void +group_init(Client *c) { + Group *g; + long *ret; + XWindow w; + long n; + + w = c->w.hints->group; + if(w == 0) { + /* Not quite ICCCM compliant, but it seems to work. */ + n = getprop_long(&c->w, "WM_CLIENT_LEADER", "WINDOW", 0L, &ret, 1L); + if(n == 0) + return; + w = *ret; + free(ret); + } + + for(g=group; g; g=g->next) + if(g->leader == w) + break; + if(g == nil) { + g = emallocz(sizeof *g); + g->leader = w; + g->next = group; + group = g; + } + c->group = g; + g->ref++; +} + +static void +group_remove(Client *c) { + Group **gp; + Group *g; + + g = c->group; + if(g == nil) + return; + if(g->client == c) + g->client = nil; + g->ref--; + if(g->ref == 0) { + for(gp=&group; *gp; gp=&gp[0]->next) + if(*gp == g) break; + assert(*gp == g); + gp[0] = gp[0]->next; + free(g); + } +} + +Client* +group_leader(Group *g) { + Client *c; + + c = win2client(g->leader); + if(c) + return c; + if(g->client) + return g->client; + /* Could do better. */ + for(c=client; c; c=c->next) + if(c->frame && c->group == g) + break; + return c; +} + +Client* +client_create(XWindow w, XWindowAttributes *wa) { + Client **t, *c; + WinAttr fwa; + Point p; + Visual *vis; + int depth; + + c = emallocz(sizeof *c); + c->fullscreen = -1; + c->border = wa->border_width; + + c->r.min = Pt(wa->x, wa->y); + c->r.max = addpt(c->r.min, + Pt(wa->width, wa->height)); + + c->w.type = WWindow; + c->w.xid = w; + c->w.r = c->r; + + depth = scr.depth; + vis = scr.visual; + /* XXX: Multihead. */ + c->ibuf = &ibuf; + if(render_argb_p(wa->visual)) { + depth = 32; + vis = render_visual; + c->ibuf = &ibuf32; + } + + client_prop(c, xatom("WM_PROTOCOLS")); + client_prop(c, xatom("WM_TRANSIENT_FOR")); + client_prop(c, xatom("WM_NORMAL_HINTS")); + client_prop(c, xatom("WM_HINTS")); + client_prop(c, xatom("WM_CLASS")); + client_prop(c, xatom("WM_NAME")); + client_prop(c, xatom("_MOTIF_WM_HINTS")); + + XSetWindowBorderWidth(display, w, 0); + XAddToSaveSet(display, w); + + fwa.background_pixmap = None; + fwa.bit_gravity = NorthWestGravity; + fwa.border_pixel = 0; + fwa.colormap = XCreateColormap(display, scr.root.xid, vis, AllocNone); + fwa.event_mask = SubstructureRedirectMask + | SubstructureNotifyMask + | StructureNotifyMask + | ExposureMask + | EnterWindowMask + | PointerMotionMask + | ButtonPressMask + | ButtonReleaseMask; + fwa.override_redirect = true; + c->framewin = createwindow_visual(&scr.root, c->r, + depth, vis, InputOutput, + &fwa, CWBackPixmap + | CWBitGravity + /* These next two matter for ARGB windows. Donno why. */ + | CWBorderPixel + | CWColormap + | CWEventMask + | CWOverrideRedirect); + XFreeColormap(display, fwa.colormap); + + c->framewin->aux = c; + c->w.aux = c; + sethandler(c->framewin, &framehandler); + sethandler(&c->w, &handlers); + + selectinput(&c->w, ClientMask); + + p.x = def.border; + p.y = labelh(def.font); + + group_init(c); + + grab_button(c->framewin->xid, AnyButton, AnyModifier); + + for(t=&client ;; t=&t[0]->next) + if(!*t) { + c->next = *t; + *t = c; + break; + } + + + /* + * It's actually possible for a window to be destroyed + * before we get a chance to reparent it. Check for that + * now, because otherwise we'll wind up mapping a + * perceptibly empty frame before it's destroyed. + */ + traperrors(true); + reparentwindow(&c->w, c->framewin, p); + if(traperrors(false)) { + client_destroy(c); + return nil; + } + + ewmh_initclient(c); + + event("CreateClient %C\n", c); + client_manage(c); + return c; +} + +static bool +apply_rules(Client *c) { + Rule *r; + + if(def.tagrules.string) + for(r=def.tagrules.rule; r; r=r->next) + if(regexec(r->regex, c->props, nil, 0)) + return client_applytags(c, r->value); + return false; +} + +void +client_manage(Client *c) { + Client *leader; + Frame *f; + char *tags; + bool rules; + + if(Dx(c->r) == Dx(screen->r)) + if(Dy(c->r) == Dy(screen->r)) + if(c->w.ewmh.type == 0) + fullscreen(c, true, -1); + + tags = getprop_string(&c->w, "_WMII_TAGS"); + rules = apply_rules(c); + + leader = win2client(c->trans); + if(leader == nil && c->group) + leader = group_leader(c->group); + + if(tags) // && (!leader || leader == c || starting)) + utflcpy(c->tags, tags, sizeof c->tags); + else if(leader && !rules) + utflcpy(c->tags, leader->tags, sizeof c->tags); + free(tags); + + if(c->tags[0]) + client_applytags(c, c->tags); + else + client_applytags(c, "sel"); + + if(!starting) + view_update_all(); + + bool newgroup = !c->group + || c->group->ref == 1 + || selclient() && (selclient()->group == c->group) + || group_leader(c->group) + && !client_viewframe(group_leader(c->group), + c->sel->view); + + f = c->sel; + if(!(c->w.ewmh.type & TypeSplash)) + if(newgroup) { + /* XXX: Look over this. + if(f->area != f->view->sel) + f->view->oldsel = f->view->sel; + */ + }else { + frame_restack(c->sel, c->sel->area->sel); + view_restack(c->sel->view); + } +} + +void +client_destroy(Client *c) { + Rectangle r; + char *none; + Client **tc; + bool hide; + + unmapwin(c->framewin); + client_seturgent(c, false, UrgClient); + + for(tc=&client; *tc; tc=&tc[0]->next) + if(*tc == c) { + *tc = c->next; + break; + } + + r = client_grav(c, ZR); + + hide = false; + if(!c->sel || c->sel->view != selview) + hide = true; + + XGrabServer(display); + + /* In case the client is already destroyed. */ + traperrors(true); + + sethandler(&c->w, nil); + if(hide) + reparentwindow(&c->w, &scr.root, screen->r.max); + else + reparentwindow(&c->w, &scr.root, r.min); + + if(starting > -1) + XRemoveFromSaveSet(display, c->w.xid); + + traperrors(false); + XUngrabServer(display); + + none = nil; + client_setviews(c, &none); + if(starting > -1) + client_unmap(c, WithdrawnState); + refree(&c->tagre); + refree(&c->tagvre); + free(c->retags); + + destroywindow(c->framewin); + + ewmh_destroyclient(c); + group_remove(c); + if(starting > -1) + event("DestroyClient %C\n", c); + + flushevents(FocusChangeMask, true); + free(c->w.hints); + free(c); +} + +/* Convenience functions */ +Frame* +client_viewframe(Client *c, View *v) { + Frame *f; + + for(f=c->frame; f; f=f->cnext) + if(f->view == v) + break; + return f; +} + +Client* +selclient(void) { + if(selview->sel->sel) + return selview->sel->sel->client; + return nil; +} + +Client* +win2client(XWindow w) { + Client *c; + for(c=client; c; c=c->next) + if(c->w.xid == w) break; + return c; +} + +int +Cfmt(Fmt *f) { + Client *c; + + c = va_arg(f->args, Client*); + if(c) + return fmtprint(f, "%W", &c->w); + return fmtprint(f, "<nil>"); +} + +char* +clientname(Client *c) { + if(c) + return c->name; + return "<nil>"; +} + +Rectangle +client_grav(Client *c, Rectangle rd) { + Rectangle r, cr; + Point sp; + WinHints *h; + + h = c->w.hints; + + if(eqrect(rd, ZR)) { + if(c->sel) { + r = c->sel->floatr; + cr = frame_rect2client(c, r, true); + }else { + cr = c->r; + r = frame_client2rect(c, cr, true); + r = rectsetorigin(r, cr.min); + } + sp = subpt(cr.min, r.min); + r = gravitate(r, cr, h->grav); + if(!h->gravstatic) + r = rectsubpt(r, sp); + return frame_rect2client(c, r, true); + }else { + r = frame_client2rect(c, rd, true); + sp = subpt(rd.min, r.min); + r = gravitate(rd, r, h->grav); + if(!h->gravstatic) + r = rectaddpt(r, sp); + return frame_client2rect(c, r, true); + } +} + +bool +client_floats_p(Client *c) { + return c->trans + || c->floating + || c->fixedsize + || c->titleless + || c->borderless + || c->fullscreen >= 0 + || (c->w.ewmh.type & (TypeDialog|TypeSplash|TypeDock)); +} + +Frame* +client_groupframe(Client *c, View *v) { + if(c->group && c->group->client) + return client_viewframe(c->group->client, v); + return nil; +} + +Rectangle +frame_hints(Frame *f, Rectangle r, Align sticky) { + Rectangle or; + WinHints h; + Point p; + Client *c; + + c = f->client; + if(c->w.hints == nil) + return r; + + or = r; + h = frame_gethints(f); + r = sizehint(&h, r); + + if(!f->area->floating) { + /* Not allowed to grow */ + if(Dx(r) > Dx(or)) + r.max.x = r.min.x+Dx(or); + if(Dy(r) > Dy(or)) + r.max.y = r.min.y+Dy(or); + } + + p = ZP; + if((sticky&(East|West)) == East) + p.x = Dx(or) - Dx(r); + if((sticky&(North|South)) == South) + p.y = Dy(or) - Dy(r); + return rectaddpt(r, p); +} + +static void +client_setstate(Client * c, int state) { + long data[] = { state, None }; + + changeprop_long(&c->w, "WM_STATE", "WM_STATE", data, nelem(data)); +} + +void +client_map(Client *c) { + if(!c->w.mapped) { + mapwin(&c->w); + client_setstate(c, NormalState); + } +} + +void +client_unmap(Client *c, int state) { + if(c->w.mapped) + unmapwin(&c->w); + client_setstate(c, state); +} + +int +map_frame(Client *c) { + return mapwin(c->framewin); +} + +int +unmap_frame(Client *c) { + return unmapwin(c->framewin); +} + +void +focus(Client *c, bool user) { + View *v; + Frame *f; + + USED(user); + f = c->sel; + if(!f) + return; + /* + if(!user && c->noinput) + return; + */ + + v = f->view; + if(v != selview) + view_focus(screen, v); + frame_focus(c->sel); +} + +void +client_focus(Client *c) { + /* Round trip. */ + + if(c && c->group) + c->group->client = c; + + sync(); + flushevents(FocusChangeMask, true); + + Dprint(DFocus, "client_focus([%C]%s)\n", c, clientname(c)); + Dprint(DFocus, "\t[%C]%s\n\t=> [%C]%s\n", + disp.focus, clientname(disp.focus), + c, clientname(c)); + if(disp.focus != c) { + if(c) { + if(!c->noinput) + setfocus(&c->w, RevertToParent); + else if(c->proto & ProtoTakeFocus) { + xtime_kludge(); + client_message(c, "WM_TAKE_FOCUS", 0); + } + }else + setfocus(screen->barwin, RevertToParent); + event("ClientFocus %C\n", c); + + sync(); + flushevents(FocusChangeMask, true); + } +} + +void +client_resize(Client *c, Rectangle r) { + Frame *f; + + f = c->sel; + frame_resize(f, r); + + if(f->view != selview) { + client_unmap(c, IconicState); + unmap_frame(c); + return; + } + + c->r = rectaddpt(f->crect, f->r.min); + + if(f->collapsed) { + if(f->area->max && !resizing) + unmap_frame(c); + else { + reshapewin(c->framewin, f->r); + movewin(&c->w, f->crect.min); + map_frame(c); + } + client_unmap(c, IconicState); + }else { + client_map(c); + reshapewin(c->framewin, f->r); + reshapewin(&c->w, f->crect); + map_frame(c); + client_configure(c); + ewmh_framesize(c); + } +} + +void +client_setcursor(Client *c, Cursor cur) { + WinAttr wa; + + if(c->cursor != cur) { + c->cursor = cur; + wa.cursor = cur; + setwinattr(c->framewin, &wa, CWCursor); + } +} + +void +client_configure(Client *c) { + XConfigureEvent e; + Rectangle r; + + r = rectsubpt(c->r, Pt(c->border, c->border)); + + e.type = ConfigureNotify; + e.event = c->w.xid; + e.window = c->w.xid; + e.above = None; + e.override_redirect = false; + + e.x = r.min.x; + e.y = r.min.y; + e.width = Dx(r); + e.height = Dy(r); + e.border_width = c->border; + + sendevent(&c->w, false, StructureNotifyMask, (XEvent*)&e); +} + +void +client_message(Client *c, char *msg, long l2) { + sendmessage(&c->w, "WM_PROTOCOLS", xatom(msg), xtime, l2, 0, 0); +} + +void +client_kill(Client *c, bool nice) { + if(nice && (c->proto & ProtoDelete)) { + client_message(c, "WM_DELETE_WINDOW", 0); + ewmh_pingclient(c); + }else + XKillClient(display, c->w.xid); +} + +void +fullscreen(Client *c, int fullscreen, long screen) { + Client *leader; + Frame *f; + bool wassel; + + if(fullscreen == Toggle) + fullscreen = (c->fullscreen >= 0) ^ On; + if(fullscreen == (c->fullscreen >= 0)) + return; + + event("Fullscreen %C %s\n", c, (fullscreen ? "on" : "off")); + ewmh_updatestate(c); + + c->fullscreen = -1; + if(!fullscreen) + for(f=c->frame; f; f=f->cnext) { + if(f->oldarea == 0) { + frame_resize(f, f->floatr); + if(f->view == selview) /* FIXME */ + client_resize(f->client, f->r); + + } + else if(f->oldarea > 0) { + wassel = (f == f->area->sel); + area_moveto(view_findarea(f->view, f->oldscreen, f->oldarea, true), + f); + if(wassel) + frame_focus(f); + } + } + else { + c->fullscreen = 0; + if(screen >= 0) + c->fullscreen = screen; + else if(c->sel) + c->fullscreen = ownerscreen(c->r); + else if(c->group && (leader = group_leader(c->group)) && leader->sel) + c->fullscreen = ownerscreen(leader->r); + else if(selclient()) + c->fullscreen = ownerscreen(selclient()->r); + + for(f=c->frame; f; f=f->cnext) + f->oldarea = -1; + if((f = c->sel)) + view_update(f->view); + } +} + +void +client_seturgent(Client *c, int urgent, int from) { + XWMHints *wmh; + char *cfrom, *cnot; + Frame *f, *ff; + Area *a; + int s; + + if(urgent == Toggle) + urgent = c->urgent ^ On; + + cfrom = (from == UrgManager ? "Manager" : "Client"); + cnot = (urgent ? "" : "Not"); + + if(urgent != c->urgent) { + event("%sUrgent %C %s\n", cnot, c, cfrom); + c->urgent = urgent; + ewmh_updatestate(c); + if(c->sel) { + if(c->sel->view == selview) + frame_draw(c->sel); + for(f=c->frame; f; f=f->cnext) { + SET(ff); + if(!urgent) + foreach_frame(f->view, s, a, ff) + if(ff->client->urgent) break; + if(urgent || ff == nil) + event("%sUrgentTag %s %s\n", + cnot, cfrom, f->view->name); + } + } + } + + if(from == UrgManager) { + wmh = XGetWMHints(display, c->w.xid); + if(wmh == nil) + wmh = emallocz(sizeof *wmh); + + wmh->flags &= ~XUrgencyHint; + if(urgent) + wmh->flags |= XUrgencyHint; + XSetWMHints(display, c->w.xid, wmh); + XFree(wmh); + } +} + +/* X11 stuff */ +void +update_class(Client *c) { + char *str; + + str = utfrune(c->props, L':'); + if(str) + str = utfrune(str+1, L':'); + if(str == nil) { + strcpy(c->props, "::"); + str = c->props + 1; + } + utflcpy(str+1, c->name, sizeof c->props); +} + +static void +client_updatename(Client *c) { + char *str; + + c->name[0] = '\0'; + + str = getprop_string(&c->w, "_NET_WM_NAME"); + if(str == nil) + str = getprop_string(&c->w, "WM_NAME"); + if(str) + utflcpy(c->name, str, sizeof c->name); + free(str); + + update_class(c); + if(c->sel) + frame_draw(c->sel); +} + +static void +updatemwm(Client *c) { + enum { + All = 0x1, + Border = 0x2, + Title = 0x8, + FlagDecor = 0x2, + Flags = 0, + Decor = 2, + }; + Rectangle r; + ulong *ret; + int n; + + /* To quote Metacity, or KWin quoting Metacity: + * + * We support MWM hints deemed non-stupid + * + * Our definition of non-stupid is a bit less lenient than + * theirs, though. In fact, we don't really even support the + * idea of supporting the hints that we support, but apps + * like xmms (which no one should use) break if we don't. + */ + + n = getprop_ulong(&c->w, "_MOTIF_WM_HINTS", "_MOTIF_WM_HINTS", + 0L, &ret, 3L); + + /* FIXME: Should somehow handle all frames of a client. */ + if(c->sel) + r = client_grav(c, ZR); + + c->borderless = 0; + c->titleless = 0; + if(n >= 3 && (ret[Flags] & FlagDecor)) { + if(ret[Decor] & All) + ret[Decor] ^= ~0; + c->borderless = !(ret[Decor] & Border); + c->titleless = !(ret[Decor] & Title); + } + free(ret); + + if(c->sel && false) { + c->sel->floatr = client_grav(c, r); + if(c->sel->area->floating) { + client_resize(c, c->sel->floatr); + frame_draw(c->sel); + } + } +} + +void +client_prop(Client *c, Atom a) { + WinHints h; + XWMHints *wmh; + char **class; + int n; + + if(a == xatom("WM_PROTOCOLS")) + c->proto = ewmh_protocols(&c->w); + else + if(a == xatom("_NET_WM_NAME")) + goto wmname; + else + if(a == xatom("_MOTIF_WM_HINTS")) + updatemwm(c); + else + switch (a) { + default: + ewmh_prop(c, a); + break; + case XA_WM_TRANSIENT_FOR: + XGetTransientForHint(display, c->w.xid, &c->trans); + break; + case XA_WM_NORMAL_HINTS: + memset(&h, 0, sizeof h); + if(c->w.hints) + bcopy(c->w.hints, &h, sizeof h); + sethints(&c->w); + if(c->w.hints) + c->fixedsize = eqpt(c->w.hints->min, c->w.hints->max); + if(memcmp(&h, c->w.hints, sizeof h)) + if(c->sel) + view_update(c->sel->view); + break; + case XA_WM_HINTS: + wmh = XGetWMHints(display, c->w.xid); + if(wmh) { + c->noinput = (wmh->flags&InputFocus) && !wmh->input; + client_seturgent(c, (wmh->flags & XUrgencyHint) != 0, UrgClient); + XFree(wmh); + } + break; + case XA_WM_CLASS: + n = getprop_textlist(&c->w, "WM_CLASS", &class); + snprint(c->props, sizeof c->props, "%s:%s:", + (n > 0 ? class[0] : "<nil>"), + (n > 1 ? class[1] : "<nil>")); + freestringlist(class); + update_class(c); + break; + case XA_WM_NAME: + wmname: + client_updatename(c); + break; + } +} + +/* Handlers */ +static void +configreq_event(Window *w, XConfigureRequestEvent *e) { + Rectangle r, cr; + Client *c; + + c = w->aux; + + r = client_grav(c, ZR); + r.max = subpt(r.max, r.min); + + if(e->value_mask & CWX) + r.min.x = e->x; + if(e->value_mask & CWY) + r.min.y = e->y; + if(e->value_mask & CWWidth) + r.max.x = e->width; + if(e->value_mask & CWHeight) + r.max.y = e->height; + + if(e->value_mask & CWBorderWidth) + c->border = e->border_width; + + r.max = addpt(r.min, r.max); + cr = r; + r = client_grav(c, r); + + if(c->sel->area->floating) { + client_resize(c, r); + }else { + c->sel->floatr = r; + client_configure(c); + } +} + +static void +destroy_event(Window *w, XDestroyWindowEvent *e) { + USED(w, e); + + client_destroy(w->aux); +} + +static void +enter_event(Window *w, XCrossingEvent *e) { + Client *c; + + c = w->aux; + if(e->detail != NotifyInferior) { + if(e->detail != NotifyVirtual) + if(e->serial != ignoreenter && disp.focus != c) { + Dprint(DFocus, "enter_notify([%C]%s)\n", c, c->name); + focus(c, false); + } + client_setcursor(c, cursor[CurNormal]); + }else + Dprint(DFocus, "enter_notify(%C[NotifyInferior]%s)\n", c, c->name); +} + +static void +focusin_event(Window *w, XFocusChangeEvent *e) { + Client *c, *old; + + c = w->aux; + + print_focus("focusin_event", c, c->name); + + if(e->mode == NotifyGrab) + disp.hasgrab = c; + + old = disp.focus; + disp.focus = c; + if(c != old) { + if(c->sel) + frame_draw(c->sel); + } +} + +static void +focusout_event(Window *w, XFocusChangeEvent *e) { + Client *c; + + c = w->aux; + if((e->mode == NotifyWhileGrabbed) && (disp.hasgrab != &c_root)) { + if(disp.focus) + disp.hasgrab = disp.focus; + }else if(disp.focus == c) { + print_focus("focusout_event", &c_magic, "<magic>"); + disp.focus = &c_magic; + if(c->sel) + frame_draw(c->sel); + } +} + +static void +unmap_event(Window *w, XUnmapEvent *e) { + Client *c; + + c = w->aux; + if(!e->send_event) + c->unmapped--; + client_destroy(c); +} + +static void +map_event(Window *w, XMapEvent *e) { + Client *c; + + USED(e); + + c = w->aux; + if(c == selclient()) + client_focus(c); +} + +static void +property_event(Window *w, XPropertyEvent *e) { + Client *c; + + if(e->state == PropertyDelete) /* FIXME */ + return; + + c = w->aux; + client_prop(c, e->atom); +} + +static Handlers handlers = { + .configreq = configreq_event, + .destroy = destroy_event, + .enter = enter_event, + .focusin = focusin_event, + .focusout = focusout_event, + .map = map_event, + .unmap = unmap_event, + .property = property_event, +}; + +/* Other */ +void +client_setviews(Client *c, char **tags) { + Frame **fp, *f; + int cmp; + + fp = &c->frame; + while(*fp || *tags) { + SET(cmp); + while(*fp) { + if(*tags) { + cmp = strcmp(fp[0]->view->name, *tags); + if(cmp >= 0) + break; + } + + f = *fp; + view_detach(f); + *fp = f->cnext; + if(c->sel == f) + c->sel = *fp; + free(f); + } + if(*tags) { + if(!*fp || cmp > 0) { + f = frame_create(c, view_create(*tags)); + if(f->view == selview || !c->sel) + c->sel = f; + kludge = c; /* FIXME */ + view_attach(f->view, f); + kludge = nil; + f->cnext = *fp; + *fp = f; + } + if(fp[0]) fp=&fp[0]->cnext; + tags++; + } + } + if(c->sel == nil) + c->sel = c->frame; + if(c->sel) + frame_draw(c->sel); +} + +static int +bsstrcmp(const void *a, const void *b) { + return strcmp((char*)a, *(char**)b); +} + +static int +strpcmp(const void *ap, const void *bp) { + char **a, **b; + + a = (char**)ap; + b = (char**)bp; + return strcmp(*a, *b); +} + +static char *badtags[] = { + ".", + "..", + "sel", +}; + +char* +client_extratags(Client *c) { + Frame *f; + char *toks[32]; + char **tags; + char *s, *s2; + int i; + + i = 0; + toks[i++] = ""; + for(f=c->frame; f && i < nelem(toks)-1; f=f->cnext) + if(f != c->sel) + toks[i++] = f->view->name; + toks[i] = nil; + tags = comm(CLeft, toks, c->retags); + + s = nil; + if(i > 1) + s = join(tags, "+"); + free(tags); + if(!c->tagre.regex && !c->tagvre.regex) + return s; + + if(c->tagre.regex) { + s2 = s; + s = smprint("%s+/%s/", s ? s : "", c->tagre.regex); + free(s2); + } + if(c->tagvre.regex) { + s2 = s; + s = smprint("%s-/%s/", s ? s : "", c->tagvre.regex); + free(s2); + } + return s; +} + +bool +client_applytags(Client *c, const char *tags) { + uint i, j, k, n; + bool add, found; + char buf[512], last; + char *toks[32]; + char **p; + char *cur, *s; + + buf[0] = 0; + + for(n = 0; tags[n]; n++) + if(!isspace(tags[n])) + break; + + if(tags[n] == '+' || tags[n] == '-') + utflcpy(buf, c->tags, sizeof c->tags); + else { + refree(&c->tagre); + refree(&c->tagvre); + } + strlcat(buf, &tags[n], sizeof buf); + + n = 0; + add = true; + if(buf[0] == '+') + n++; + else if(buf[0] == '-') { + n++; + add = false; + } + + found = false; + + j = 0; + while(buf[n] && n < sizeof(buf) && j < 32) { + /* Check for regex. */ + if(buf[n] == '/') { + for(i=n+1; i < sizeof(buf) - 1; i++) + if(buf[i] == '/') break; + if(buf[i] == '/') { + i++; + if(buf[i] == '+' + || buf[i] == '-' + || buf[i] == '\0') { /* Don't be lenient */ + buf[i-1] = '\0'; + if(add) + reinit(&c->tagre, buf+n+1); + else + reinit(&c->tagvre, buf+n+1); + last = buf[i]; + buf[i] = '\0'; + + found = true; + goto next; + } + } + } + + for(i = n; i < sizeof(buf) - 1; i++) + if(buf[i] == '+' + || buf[i] == '-' + || buf[i] == '\0') + break; + last = buf[i]; + buf[i] = '\0'; + + trim(buf+n, " \t/"); + + cur = nil; + if(!strcmp(buf+n, "~")) + c->floating = add; + else + if(!strcmp(buf+n, "!") || !strcmp(buf+n, "sel")) + cur = selview->name; + else + if(!Mbsearch(buf+n, badtags, bsstrcmp)) + cur = buf+n; + + if(cur && j < nelem(toks)-1) { + if(add) { + found = true; + toks[j++] = cur; + }else { + for(i = 0, k = 0; i < j; i++) + if(strcmp(toks[i], cur)) + toks[k++] = toks[i]; + j = k; + } + } + + next: + n = i + 1; + if(last == '+') + add = true; + if(last == '-') + add = false; + if(last == '\0') + break; + } + + toks[j] = nil; + qsort(toks, j, sizeof *toks, strpcmp); + uniq(toks); + + s = join(toks, "+"); + utflcpy(c->tags, s, sizeof c->tags); + if(c->tagre.regex) + strlcatprint(c->tags, sizeof c->tags, "+/%s/", c->tagre.regex); + if(c->tagvre.regex) + strlcatprint(c->tags, sizeof c->tags, "-/%s/", c->tagvre.regex); + changeprop_string(&c->w, "_WMII_TAGS", c->tags); + free(s); + + free(c->retags); + p = view_names(); + grep(p, c->tagre.regc, 0); + grep(p, c->tagvre.regc, GInvert); + c->retags = comm(CRight, toks, p); + free(p); + + if(c->retags[0] == nil && toks[0] == nil) { + toks[0] = "orphans"; + toks[1] = nil; + } + + p = comm(~0, c->retags, toks); + client_setviews(c, p); + free(p); + return found; +} + diff --git a/cmd/wmii/column.c b/cmd/wmii/column.c new file mode 100644 index 0000000..e94f6a9 --- /dev/null +++ b/cmd/wmii/column.c @@ -0,0 +1,738 @@ +/* Copyright ©2004-2006 Anselm R. Garbe <garbeam at gmail dot com> + * Copyright ©2006-2010 Kris Maglione <maglione.k at Gmail> + * See LICENSE file for license details. + */ +#include "dat.h" +#include <math.h> +#include <strings.h> +#include "fns.h" + +static void column_resizeframe_h(Frame*, Rectangle); + +char *modes[] = { + [Coldefault] = "default", + [Colstack] = "stack", + [Colmax] = "max", +}; + +bool +column_setmode(Area *a, const char *mode) { + char *str, *tok, *orig; + char add, old; + + /* + * The mapping between the current internal + * representation and the external interface + * is currently a bit complex. That will probably + * change. + */ + + orig = strdup(mode); + str = orig; + old = '\0'; + while(*(tok = str)) { + add = old; + while((old=*str) && !strchr("+-^", old)) + str++; + *str = '\0'; + if(str > tok) { + print("'%s' %c\n", tok, add); + if(!strcmp(tok, "max")) { + if(add == '\0' || add == '+') + a->max = true; + else if(add == '-') + a->max = false; + else + a->max = !a->max; + }else + if(!strcmp(tok, "stack")) { + if(add == '\0' || add == '+') + a->mode = Colstack; + else if(add == '-') + a->mode = Coldefault; + else + a->mode = a->mode == Colstack ? Coldefault : Colstack; + }else + if(!strcmp(tok, "default")) { + if(add == '\0' || add == '+') { + a->mode = Coldefault; + column_arrange(a, true); + }else if(add == '-') + a->mode = Colstack; + else + a->mode = a->mode == Coldefault ? Colstack : Coldefault; + }else { + free(orig); + return false; + } + } + if(old) + str++; + + } + free(orig); + return true; +} + +char* +column_getmode(Area *a) { + return sxprint("%s%cmax", a->mode == Colstack ? "stack" : "default", + a->max ? '+' : '-'); +} + +int +column_minwidth(void) +{ + return 4 * labelh(def.font); +} + +Area* +column_new(View *v, Area *pos, int scrn, uint w) { + Area *a; + + assert(!pos || !pos->floating && pos->screen == scrn); + a = area_create(v, pos, scrn, w); + return a; +#if 0 + if(!a) + return nil; + + view_arrange(v); + view_update(v); +#endif +} + +void +column_insert(Area *a, Frame *f, Frame *pos) { + + f->area = a; + f->client->floating = false; + f->screen = a->screen; + f->column = area_idx(a); + frame_insert(f, pos); + if(a->sel == nil) + area_setsel(a, f); +} + +/* Temporary. */ +static void +stack_scale(Frame *first, int height) { + Frame *f; + Area *a; + uint dy; + int surplus; + + a = first->area; + + /* + * Will need something like this. + column_fit(a, &ncol, &nuncol); + */ + + dy = 0; + for(f=first; f && !f->collapsed; f=f->anext) + dy += Dy(f->colr); + + /* Distribute the surplus. + */ + surplus = height - dy; + for(f=first; f && !f->collapsed; f=f->anext) + f->colr.max.y += ((float)Dy(f->r) / dy) * surplus; +} + +static void +stack_info(Frame *f, Frame **firstp, Frame **lastp, int *dyp, int *nframep) { + Frame *ft, *first, *last; + int dy, nframe; + + nframe = 0; + dy = 0; + first = f; + last = f; + + for(ft=f; ft && ft->collapsed; ft=ft->anext) + ; + if(ft && ft != f) { + f = ft; + dy += Dy(f->colr); + } + for(ft=f; ft && !ft->collapsed; ft=ft->aprev) { + first = ft; + nframe++; + dy += Dy(ft->colr); + } + for(ft=f->anext; ft && !ft->collapsed; ft=ft->anext) { + if(first == nil) + first = ft; + last = ft; + nframe++; + dy += Dy(ft->colr); + } + if(nframep) *nframep = nframe; + if(firstp) *firstp = first; + if(lastp) *lastp = last; + if(dyp) *dyp = dy; +} + +int +stack_count(Frame *f, int *mp) { + Frame *fp; + int n, m; + + n = 0; + for(fp=f->aprev; fp && fp->collapsed; fp=fp->aprev) + n++; + m = ++n; + for(fp=f->anext; fp && fp->collapsed; fp=fp->anext) + n++; + if(mp) *mp = m; + return n; +} + +Frame* +stack_find(Area *a, Frame *f, int dir, bool stack) { + Frame *fp; + + switch (dir) { + default: + die("not reached"); + case North: + if(f) + for(f=f->aprev; f && f->collapsed && stack; f=f->aprev) + ; + else { + f = nil; + for(fp=a->frame; fp; fp=fp->anext) + if(!fp->collapsed || !stack) + f = fp; + } + break; + case South: + if(f) + for(f=f->anext; f && f->collapsed && stack; f=f->anext) + ; + else + for(f=a->frame; f && f->collapsed && stack; f=f->anext) + ; + break; + } + return f; +} + +/* TODO: Move elsewhere. */ +bool +find(Area **ap, Frame **fp, int dir, bool wrap, bool stack) { + Rectangle r; + Frame *f; + Area *a; + + f = *fp; + a = *ap; + r = f ? f->r : a->r; + + if(dir == North || dir == South) { + *fp = stack_find(a, f, dir, stack); + if(*fp) + return true; + if (!a->floating) + *ap = area_find(a->view, r, dir, wrap); + if(!*ap) + return false; + *fp = stack_find(*ap, *fp, dir, stack); + return true; + } + if(dir != East && dir != West) + die("not reached"); + *ap = area_find(a->view, r, dir, wrap); + if(!*ap) + return false; + *fp = ap[0]->sel; + return true; +} + +void +column_attach(Area *a, Frame *f) { + Frame *first; + int nframe, dy, h; + + f->colr = a->r; + + if(a->sel) { + stack_info(a->sel, &first, nil, &dy, &nframe); + h = dy / (nframe+1); + f->colr.max.y = f->colr.min.y + h; + stack_scale(first, dy - h); + } + + column_insert(a, f, a->sel); + column_arrange(a, false); +} + +void +column_detach(Frame *f) { + Frame *first; + Area *a; + int dy; + + a = f->area; + stack_info(f, &first, nil, &dy, nil); + if(first && first == f) + first = f->anext; + column_remove(f); + if(a->frame) { + if(first) + stack_scale(first, dy); + column_arrange(a, false); + }else if(a->view->areas[a->screen]->next) + area_destroy(a); +} + +static void column_scale(Area*); + +void +column_attachrect(Area *a, Frame *f, Rectangle r) { + Frame *fp, *pos; + int before, after; + + pos = nil; + for(fp=a->frame; fp; pos=fp, fp=fp->anext) { + if(r.max.y < fp->r.min.y || r.min.y > fp->r.max.y) + continue; + before = fp->r.min.y - r.min.y; + after = -fp->r.max.y + r.max.y; + } + column_insert(a, f, pos); + column_resizeframe_h(f, r); + column_scale(a); +} + +void +column_remove(Frame *f) { + Frame *pr; + Area *a; + + a = f->area; + pr = f->aprev; + + frame_remove(f); + + f->area = nil; + if(a->sel == f) { + if(pr == nil) + pr = a->frame; + if(pr && pr->collapsed) + if(pr->anext && !pr->anext->collapsed) + pr = pr->anext; + else + pr->collapsed = false; + a->sel = nil; + area_setsel(a, pr); + } +} + +static int +column_surplus(Area *a) { + Frame *f; + int surplus; + + surplus = Dy(a->r); + for(f=a->frame; f; f=f->anext) + surplus -= Dy(f->r); + return surplus; +} + +static void +column_fit(Area *a, uint *n_colp, uint *n_uncolp) { + Frame *f, **fp; + uint minh, dy; + uint n_col, n_uncol; + uint col_h, uncol_h; + int surplus, i, j; + + /* The minimum heights of collapsed and uncollpsed frames. + */ + minh = labelh(def.font); + col_h = labelh(def.font); + uncol_h = minh + col_h + 1; + if(a->max && !resizing) + col_h = 0; + + /* Count collapsed and uncollapsed frames. */ + n_col = 0; + n_uncol = 0; + for(f=a->frame; f; f=f->anext) { + frame_resize(f, f->colr); + if(f->collapsed) + n_col++; + else + n_uncol++; + } + + if(n_uncol == 0) { + n_uncol++; + n_col--; + (a->sel ? a->sel : a->frame)->collapsed = false; + } + + /* FIXME: Kludge. See frame_attachrect. */ + dy = Dy(a->view->r[a->screen]) - Dy(a->r); + minh = col_h * (n_col + n_uncol - 1) + uncol_h; + if(dy && Dy(a->r) < minh) + a->r.max.y += min(dy, minh - Dy(a->r)); + + surplus = Dy(a->r) + - (n_col * col_h) + - (n_uncol * uncol_h); + + /* Collapse until there is room */ + if(surplus < 0) { + i = ceil(-1.F * surplus / (uncol_h - col_h)); + if(i >= n_uncol) + i = n_uncol - 1; + n_uncol -= i; + n_col += i; + surplus += i * (uncol_h - col_h); + } + /* Push to the floating layer until there is room */ + if(surplus < 0) { + i = ceil(-1.F * surplus / col_h); + if(i > n_col) + i = n_col; + n_col -= i; + surplus += i * col_h; + } + + /* Decide which to collapse and which to float. */ + j = n_uncol - 1; + i = n_col - 1; + for(fp=&a->frame; *fp;) { + f = *fp; + if(f != a->sel) { + if(!f->collapsed) { + if(j < 0) + f->collapsed = true; + j--; + } + if(f->collapsed) { + if(i < 0) { + f->collapsed = false; + area_moveto(f->view->floating, f); + continue; + } + i--; + } + } + /* Doesn't change if we 'continue' */ + fp = &f->anext; + } + + if(n_colp) *n_colp = n_col; + if(n_uncolp) *n_uncolp = n_uncol; +} + +void +column_settle(Area *a) { + Frame *f; + uint yoff, yoffcr; + int surplus, n_uncol, n; + + n_uncol = 0; + surplus = column_surplus(a); + for(f=a->frame; f; f=f->anext) + if(!f->collapsed) n_uncol++; + + if(n_uncol == 0) { + fprint(2, "%s: Badness: No uncollapsed frames, column %d, view %q\n", + argv0, area_idx(a), a->view->name); + return; + } + if(surplus < 0) + fprint(2, "%s: Badness: surplus = %d in column_settle, column %d, view %q\n", + argv0, surplus, area_idx(a), a->view->name); + + yoff = a->r.min.y; + yoffcr = yoff; + n = surplus % n_uncol; + surplus /= n_uncol; + for(f=a->frame; f; f=f->anext) { + f->r = rectsetorigin(f->r, Pt(a->r.min.x, yoff)); + f->colr = rectsetorigin(f->colr, Pt(a->r.min.x, yoffcr)); + f->r.min.x = a->r.min.x; + f->r.max.x = a->r.max.x; + if(def.incmode == ISqueeze && !resizing) + if(!f->collapsed) { + f->r.max.y += surplus; + if(n-- > 0) + f->r.max.y++; + } + yoff = f->r.max.y; + yoffcr = f->colr.max.y; + } +} + +/* + * Returns how much a frame "wants" to grow. + */ +static int +foo(Frame *f) { + WinHints h; + int maxh; + + h = frame_gethints(f); + maxh = 0; + if(h.aspect.max.x) + maxh = h.baspect.y + + (Dx(f->r) - h.baspect.x) * + h.aspect.max.y / h.aspect.max.x; + maxh = max(maxh, h.max.y); + + if(Dy(f->r) >= maxh) + return 0; + return h.inc.y - (Dy(f->r) - h.base.y) % h.inc.y; +} + +static int +comp_frame(const void *a, const void *b) { + int ia, ib; + + ia = foo(*(Frame**)a); + ib = foo(*(Frame**)b); + /* + * I'd like to favor the selected client, but + * it causes windows to jump as focus changes. + */ + return ia < ib ? -1 : + ia > ib ? 1 : + 0; +} + +static void +column_squeeze(Area *a) { + static Vector_ptr fvec; + Frame *f; + int surplus, osurplus, dy, i; + + fvec.n = 0; + for(f=a->frame; f; f=f->anext) + if(!f->collapsed) { + f->r = frame_hints(f, f->r, 0); + vector_ppush(&fvec, f); + } + + surplus = column_surplus(a); + for(osurplus=0; surplus != osurplus;) { + osurplus = surplus; + qsort(fvec.ary, fvec.n, sizeof *fvec.ary, comp_frame); + for(i=0; i < fvec.n; i++) { + f=fvec.ary[i]; + dy = foo(f); + if(dy > surplus) + break; + surplus -= dy; + f->r.max.y += dy; + } + } +} + +/* + * Frobs a column. Which is to say, *temporary* kludge. + * Essentially seddles the column and resizes its clients. + */ +void +column_frob(Area *a) { + Frame *f; + + for(f=a->frame; f; f=f->anext) + f->r = f->colr; + column_settle(a); + if(a->view == selview) + for(f=a->frame; f; f=f->anext) + client_resize(f->client, f->r); +} + +static void +column_scale(Area *a) { + Frame *f; + uint dy; + uint ncol, nuncol; + uint colh; + int surplus; + + if(!a->frame) + return; + + column_fit(a, &ncol, &nuncol); + + colh = labelh(def.font); + if(a->max && !resizing) + colh = 0; + + dy = 0; + surplus = Dy(a->r); + for(f=a->frame; f; f=f->anext) { + if(f->collapsed) + f->colr.max.y = f->colr.min.y + colh; + else if(Dy(f->colr) == 0) + f->colr.max.y++; + surplus -= Dy(f->colr); + if(!f->collapsed) + dy += Dy(f->colr); + } + for(f=a->frame; f; f=f->anext) { + f->dy = Dy(f->colr); + f->colr.min.x = a->r.min.x; + f->colr.max.x = a->r.max.x; + if(!f->collapsed) + f->colr.max.y += ((float)f->dy / dy) * surplus; + if(btassert("6 full", !(f->collapsed ? Dy(f->r) >= 0 : dy > 0))) + warning("Something's fucked: %s:%d:%s()", + __FILE__, __LINE__, __func__); + frame_resize(f, f->colr); + } + + if(def.incmode == ISqueeze && !resizing) + column_squeeze(a); + column_settle(a); +} + +void +column_arrange(Area *a, bool dirty) { + Frame *f; + View *v; + + if(a->floating) + float_arrange(a); + if(a->floating || !a->frame) + return; + + v = a->view; + + switch(a->mode) { + case Coldefault: + if(dirty) + for(f=a->frame; f; f=f->anext) + f->colr = Rect(0, 0, 100, 100); + break; + case Colstack: + /* XXX */ + for(f=a->frame; f; f=f->anext) + f->collapsed = (f != a->sel); + break; + default: + fprint(2, "Dieing: %s: screen: %d a: %p mode: %x floating: %d\n", + v->name, a->screen, a, a->mode, a->floating); + die("not reached"); + break; + } + column_scale(a); + /* XXX */ + if(a->sel->collapsed) + area_setsel(a, a->sel); + if(v == selview) { + //view_restack(v); + client_resize(a->sel->client, a->sel->r); + for(f=a->frame; f; f=f->anext) + client_resize(f->client, f->r); + } +} + +void +column_resize(Area *a, int w) { + Area *an; + int dw; + + an = a->next; + assert(an != nil); + + dw = w - Dx(a->r); + a->r.max.x += dw; + an->r.min.x += dw; + + /* view_arrange(a->view); */ + view_update(a->view); +} + +static void +column_resizeframe_h(Frame *f, Rectangle r) { + Area *a; + Frame *fn, *fp; + uint minh; + + minh = labelh(def.font); + + a = f->area; + fn = f->anext; + fp = f->aprev; + + if(fp) + r.min.y = max(r.min.y, fp->colr.min.y + minh); + else + r.min.y = max(r.min.y, a->r.min.y); + + if(fn) + r.max.y = min(r.max.y, fn->colr.max.y - minh); + else + r.max.y = min(r.max.y, a->r.max.y); + + if(fp) { + fp->colr.max.y = r.min.y; + frame_resize(fp, fp->colr); + } + else + r.min.y = min(r.min.y, r.max.y - minh); + + if(fn) { + fn->colr.min.y = r.max.y; + frame_resize(fn, fn->colr); + } + else + r.max.y = max(r.max.y, r.min.y + minh); + + f->colr = r; + frame_resize(f, r); +} + +void +column_resizeframe(Frame *f, Rectangle r) { + Area *a, *al, *ar; + View *v; + uint minw; + + a = f->area; + v = a->view; + + minw = column_minwidth(); + + al = a->prev; + ar = a->next; + + if(al) + r.min.x = max(r.min.x, al->r.min.x + minw); + else { /* Hm... */ + r.min.x = max(r.min.x, v->r[a->screen].min.x); + r.max.x = max(r.max.x, r.min.x + minw); + } + + if(ar) + r.max.x = min(r.max.x, ar->r.max.x - minw); + else { + r.max.x = min(r.max.x, v->r[a->screen].max.x); + r.min.x = min(r.min.x, r.max.x - minw); + } + + a->r.min.x = r.min.x; + a->r.max.x = r.max.x; + if(al) { + al->r.max.x = a->r.min.x; + column_arrange(al, false); + } + if(ar) { + ar->r.min.x = a->r.max.x; + column_arrange(ar, false); + } + + column_resizeframe_h(f, r); + + view_update(v); +} + diff --git a/cmd/wmii/dat.h b/cmd/wmii/dat.h new file mode 100644 index 0000000..8038026 --- /dev/null +++ b/cmd/wmii/dat.h @@ -0,0 +1,404 @@ +/* Copyright ©2007-2010 Kris Maglione <jg@suckless.org> + * See LICENSE file for license details. + */ + +#define _XOPEN_SOURCE 600 +#define IXP_P9_STRUCTS +#define IXP_NO_P9_ +#include <assert.h> +#include <regexp9.h> +#include <stdbool.h> +#include <stdint.h> +#include <stdlib.h> +#include <string.h> +#include <ixp.h> +#include <util.h> +#include <utf.h> +#include <fmt.h> +#include <x11.h> + +#define FONT "-*-fixed-medium-r-*-*-13-*-*-*-*-*-*-*" +#define FOCUSCOLORS "#ffffff #335577 #447799" +#define NORMCOLORS "#222222 #eeeeee #666666" + +enum { + PingTime = 10000, +}; + +enum { + CLeft = 1<<0, + CCenter = 1<<1, + CRight = 1<<2, +}; + +enum IncMode { + IIgnore, + IShow, + ISqueeze, +}; + +enum { + GInvert = 1<<0, +}; + +enum { + UrgManager, + UrgClient, +}; + +enum EWMHType { + TypeDesktop = 1<<0, + TypeDock = 1<<1, + TypeToolbar = 1<<2, + TypeMenu = 1<<3, + TypeUtility = 1<<4, + TypeSplash = 1<<5, + TypeDialog = 1<<6, + TypeNormal = 1<<7, +}; + +enum { + Coldefault, Colstack, Colmax, Collast +}; + +extern char* modes[]; + +#define TOGGLE(x) \ + (x == On ? "on" : \ + x == Off ? "off" : \ + x == Toggle ? "toggle" : \ + "<toggle>") +enum { + Off, + On, + Toggle, +}; + +enum Barpos { + BBottom, + BTop, +}; + +enum { + CurNormal, + CurNECorner, CurNWCorner, CurSECorner, CurSWCorner, + CurDHArrow, CurDVArrow, CurMove, CurInput, CurSizing, + CurTCross, CurIcon, + CurNone, + CurLast, +}; + +enum { + NCOL = 16, +}; + +enum Protocols { + ProtoDelete = 1<<0, + ProtoTakeFocus = 1<<1, + ProtoPing = 1<<2, +}; + +enum DebugOpt { + D9p = 1<<0, + DDnd = 1<<1, + DEvent = 1<<2, + DEwmh = 1<<3, + DFocus = 1<<4, + DGeneric= 1<<5, + DStack = 1<<6, + NDebugOpt = 7, +}; + +/* Data Structures */ +typedef struct Area Area; +typedef struct Bar Bar; +typedef struct Client Client; +typedef struct Divide Divide; +typedef struct Frame Frame; +typedef struct Group Group; +typedef struct Key Key; +typedef struct Map Map; +typedef struct MapEnt MapEnt; +typedef struct Regex Regex; +typedef struct Rule Rule; +typedef struct Ruleset Ruleset; +typedef struct Strut Strut; +typedef struct View View; +typedef struct WMScreen WMScreen; + +struct Area { + Area* next; + Area* prev; + Frame* frame; + Frame* frame_old; + Frame* stack; + Frame* sel; + View* view; + bool floating; + ushort id; + int mode; + int screen; + bool max; + Rectangle r; + Rectangle r_old; +}; + +struct Bar { + Bar* next; + Bar* smaller; + char buf[280]; + char text[256]; + char name[256]; + int bar; + ushort id; + CTuple col; + Rectangle r; + WMScreen* screen; +}; + +struct Regex { + char* regex; + Reprog* regc; +}; + +struct Client { + Client* next; + Frame* frame; + Frame* sel; + Window w; + Window* framewin; + Image** ibuf; + XWindow trans; + Regex tagre; + Regex tagvre; + Group* group; + Strut* strut; + Cursor cursor; + Rectangle r; + char** retags; + char name[256]; + char tags[256]; + char props[512]; + long proto; + uint border; + int fullscreen; + int unmapped; + bool floating; + bool fixedsize; + bool urgent; + bool borderless; + bool titleless; + bool noinput; +}; + +struct Divide { + Divide* next; + Window* w; + Area* left; + Area* right; + bool mapped; + int x; +}; + +struct Frame { + Frame* cnext; + Frame* anext; + Frame* aprev; + Frame* anext_old; + Frame* snext; + Frame* sprev; + Client* client; + View* view; + Area* area; + int oldscreen; + int oldarea; + int screen; + int column; + ushort id; + bool collapsed; + int dy; + Rectangle r; + Rectangle colr; + Rectangle colr_old; + Rectangle floatr; + Rectangle crect; + Rectangle grabbox; + Rectangle titlebar; +}; + +struct Group { + Group* next; + XWindow leader; + Client *client; + int ref; +}; + +struct Key { + Key* next; + Key* lnext; + Key* tnext; + ushort id; + char name[128]; + ulong mod; + KeyCode key; +}; + +struct Map { + MapEnt**bucket; + uint nhash; +}; + +struct Rule { + Rule* next; + Reprog* regex; + char value[256]; + +}; + +struct Ruleset { + Rule* rule; + char* string; + uint size; +}; + +struct Strut { + Rectangle left; + Rectangle right; + Rectangle top; + Rectangle bottom; +}; + +#define firstarea areas[screen->idx] +#define screenr r[screen->idx] +struct View { + View* next; + char name[256]; + ushort id; + Area* floating; + Area** areas; + Area* sel; + Area* oldsel; + Area* revert; + int selcol; + int selscreen; + bool dead; + Rectangle *r; + Rectangle *pad; +}; + +/* Yuck. */ +#define VECTOR(type, nam, c) \ +typedef struct Vector_##nam Vector_##nam; \ +struct Vector_##nam { \ + type* ary; \ + long n; \ + long size; \ +}; \ +void vector_##c##free(Vector_##nam*); \ +void vector_##c##init(Vector_##nam*); \ +void vector_##c##push(Vector_##nam*, type); \ + +VECTOR(long, long, l) +VECTOR(Rectangle, rect, r) +VECTOR(void*, ptr, p) +#undef VECTOR + +#ifndef EXTERN +# define EXTERN extern +#endif + +/* global variables */ +EXTERN struct { + CTuple focuscolor; + CTuple normcolor; + Font* font; + char* keys; + uint keyssz; + Ruleset tagrules; + Ruleset colrules; + char grabmod[5]; + ulong mod; + uint border; + uint snap; + int colmode; + int incmode; +} def; + +enum { + BLeft, BRight +}; + +#define BLOCK(x) do { x; }while(0) + +EXTERN struct WMScreen { + Bar* bar[2]; + Window* barwin; + bool showing; + int barpos; + int idx; + + Rectangle r; + Rectangle brect; +} **screens, *screen; +EXTERN uint nscreens; + +EXTERN struct { + Client* focus; + Client* hasgrab; + Image* ibuf; + Image* ibuf32; + bool sel; +} disp; + +EXTERN Client* client; +EXTERN View* view; +EXTERN View* selview; +EXTERN Key* key; +EXTERN Divide* divs; +EXTERN Client c_magic; +EXTERN Client c_root; + +EXTERN Handlers framehandler; + +EXTERN char buffer[8092]; +EXTERN char* _buffer; +static char* const _buf_end = buffer + sizeof buffer; + +#define bufclear() \ + BLOCK( _buffer = buffer; _buffer[0] = '\0' ) +#define bufprint(...) \ + _buffer = seprint(_buffer, _buf_end, __VA_ARGS__) + +/* IXP */ +EXTERN IxpServer srv; +EXTERN Ixp9Srv p9srv; + +/* X11 */ +EXTERN uint valid_mask; +EXTERN uint numlock_mask; +EXTERN Image* ibuf; +EXTERN Image* ibuf32; + +EXTERN Cursor cursor[CurLast]; + +typedef void (*XHandler)(XEvent*); +EXTERN XHandler handler[LASTEvent]; + +/* Misc */ +EXTERN int starting; +EXTERN bool resizing; +EXTERN long ignoreenter; +EXTERN char* user; +EXTERN char* execstr; +EXTERN int debugflag; +EXTERN int debugfile; +EXTERN long xtime; +EXTERN Visual* render_visual; + +EXTERN Client* kludge; + +extern char* debugtab[]; + +#define Debug(x) if(((debugflag|debugfile)&(x)) && setdebug(x)) +#define Dprint(x, ...) BLOCK( if((debugflag|debugfile)&(x)) debug(x, __VA_ARGS__) ) + diff --git a/cmd/wmii/div.c b/cmd/wmii/div.c new file mode 100644 index 0000000..986d6a2 --- /dev/null +++ b/cmd/wmii/div.c @@ -0,0 +1,190 @@ +/* Copyright ©2006-2010 Kris Maglione <maglione.k at Gmail> + * See LICENSE file for license details. + */ +#include "dat.h" +#include "fns.h" + +static Image* divimg; +static Image* divmask; +static CTuple divcolor; +static Handlers handlers; + +static Divide* +getdiv(Divide ***dp) { + WinAttr wa; + Divide *d; + + if(**dp) + d = **dp; + else { + d = emallocz(sizeof *d); + + wa.override_redirect = true; + wa.cursor = cursor[CurDHArrow]; + wa.event_mask = + ExposureMask + | EnterWindowMask + | ButtonPressMask + | ButtonReleaseMask; + d->w = createwindow(&scr.root, Rect(0, 0, 1, 1), scr.depth, + InputOutput, &wa, + CWOverrideRedirect + | CWEventMask + | CWCursor); + d->w->aux = d; + sethandler(d->w, &handlers); + **dp = d; + } + *dp = &d->next; + return d; +} + +static void +mapdiv(Divide *d) { + mapwin(d->w); +} + +static void +unmapdiv(Divide *d) { + unmapwin(d->w); +} + +void +div_set(Divide *d, int x) { + Rectangle r; + int scrn; + + scrn = d->left ? d->left->screen : d->right->screen; + + d->x = x; + r = rectaddpt(divimg->r, Pt(x - Dx(divimg->r)/2, 0)); + r.min.y = selview->r[scrn].min.y; + r.max.y = selview->r[scrn].max.y; + + reshapewin(d->w, r); + mapdiv(d); +} + +static void +drawimg(Image *img, Color cbg, Color cborder, Divide *d) { + Point pt[8]; + int n, start, w; + + w = Dx(img->r)/2; + n = 0; + pt[n++] = Pt(w, 0); + pt[n++] = Pt(0, 0); + pt[n++] = Pt(w - 1, w - 1); + + pt[n++] = Pt(w - 1, Dy(img->r)); + pt[n++] = Pt(w, pt[n-1].y); + + pt[n++] = Pt(w, w - 1); + pt[n++] = Pt(2*w - 1, 0); + pt[n++] = Pt(w, 0); + + start = d->left ? 0 : n/2; + n = d->right && d->left ? n : n/2; + + fillpoly(img, pt + start, n, cbg); + drawpoly(img, pt + start, n, CapNotLast, 1, cborder); +} + +static void +drawdiv(Divide *d) { + + fill(divmask, divmask->r, (Color){0}); + drawimg(divmask, (Color){1}, (Color){1}, d); + drawimg(divimg, divcolor.bg, divcolor.border, d); + + copyimage(d->w, divimg->r, divimg, ZP); + setshapemask(d->w, divmask, ZP); +} + +static void +update_imgs(void) { + Divide *d; + int w, h; + + w = 2 * (labelh(def.font) / 3); + w = max(w, 10); + /* XXX: Multihead. */ + h = Dy(scr.rect); + + if(divimg) { + if(w == Dx(divimg->r) && h == Dy(divimg->r) + && !memcmp(&divcolor, &def.normcolor, sizeof divcolor)) + return; + freeimage(divimg); + freeimage(divmask); + } + + divimg = allocimage(w, h, scr.depth); + divmask = allocimage(w, h, 1); + divcolor = def.normcolor; + + for(d = divs; d && d->w->mapped; d = d->next) + drawdiv(d); +} + +void +div_update_all(void) { + Divide **dp, *d; + Area *a, *ap; + View *v; + int s; + + update_imgs(); + + v = selview; + dp = &divs; + ap = nil; + foreach_column(v, s, a) { + if (ap && ap->screen != s) + ap = nil; + + d = getdiv(&dp); + d->left = ap; + d->right = a; + div_set(d, a->r.min.x); + drawdiv(d); + ap = a; + + if(!a->next) { + d = getdiv(&dp); + d->left = a; + d->right = nil; + div_set(d, a->r.max.x); + drawdiv(d); + } + } + for(d = *dp; d; d = d->next) + unmapdiv(d); +} + +/* Div Handlers */ +static void +bdown_event(Window *w, XButtonEvent *e) { + Divide *d; + + USED(e); + + d = w->aux; + mouse_resizecol(d); +} + +static void +expose_event(Window *w, XExposeEvent *e) { + Divide *d; + + USED(e); + + d = w->aux; + drawdiv(d); +} + +static Handlers handlers = { + .bdown = bdown_event, + .expose = expose_event, +}; + diff --git a/cmd/wmii/error.c b/cmd/wmii/error.c new file mode 100644 index 0000000..3f66653 --- /dev/null +++ b/cmd/wmii/error.c @@ -0,0 +1,41 @@ +/* Copyright ©2007-2010 Kris Maglione <jg@suckless.org> + * See LICENSE file for license details. + */ + +#include "dat.h" +#include "fns.h" + +static jmp_buf errjmp[16]; +static long nerror; + +void +error(char *fmt, ...) { + char errbuf[IXP_ERRMAX]; + va_list ap; + + va_start(ap, fmt); + vsnprint(errbuf, IXP_ERRMAX, fmt, ap); + va_end(ap); + ixp_errstr(errbuf, IXP_ERRMAX); + + nexterror(); +} + +void +nexterror(void) { + assert(nerror > 0); + longjmp(errjmp[--nerror], 1); +} + +void +poperror(void) { + assert(nerror > 0); + --nerror; +} + +jmp_buf* +pusherror(void) { + assert(nerror < nelem(errjmp)); + return &errjmp[nerror++]; +} + diff --git a/cmd/wmii/event.c b/cmd/wmii/event.c new file mode 100644 index 0000000..82a51eb --- /dev/null +++ b/cmd/wmii/event.c @@ -0,0 +1,359 @@ +/* Copyright ©2006-2010 Kris Maglione <maglione.k at Gmail> + * See LICENSE file for license details. + */ +#include "dat.h" +#include <X11/keysym.h> +#include "fns.h" + +typedef void (*EvHandler)(XEvent*); + +void +dispatch_event(XEvent *e) { + Dprint(DEvent, "%E\n", e); + if(e->type < nelem(handler)) { + if(handler[e->type]) + handler[e->type](e); + }else + xext_event(e); +} + +#define handle(w, fn, ev) \ + BLOCK(if((w)->handler->fn) (w)->handler->fn((w), ev)) + +static int +findtime(Display *d, XEvent *e, XPointer v) { + Window *w; + + w = (Window*)v; + if(e->type == PropertyNotify && e->xproperty.window == w->xid) { + xtime = e->xproperty.time; + return true; + } + return false; +} + +void +xtime_kludge(void) { + /* Round trip. */ + static Window *w; + WinAttr wa; + XEvent e; + long l; + + if(w == nil) { + w = createwindow(&scr.root, Rect(0, 0, 1, 1), 0, InputOnly, &wa, 0); + selectinput(w, PropertyChangeMask); + } + changeprop_long(w, "ATOM", "ATOM", &l, 0); + sync(); + XIfEvent(display, &e, findtime, (void*)w); +} + +uint +flushevents(long event_mask, bool dispatch) { + XEvent ev; + uint n = 0; + + while(XCheckMaskEvent(display, event_mask, &ev)) { + if(dispatch) + dispatch_event(&ev); + n++; + } + return n; +} + +static Bool +findenter(Display *d, XEvent *e, XPointer v) { + long *l; + + USED(d); + l = (long*)v; + if(*l) + return false; + if(e->type == EnterNotify) + return true; + if(e->type == MotionNotify) + (*l)++; + return false; +} + +/* This isn't perfect. If there were motion events in the queue + * before this was called, then it flushes nothing. If we don't + * check for them, we might lose a legitamate enter event. + */ +uint +flushenterevents(void) { + XEvent e; + long l; + int n; + + l = 0; + n = 0; + while(XCheckIfEvent(display, &e, findenter, (void*)&l)) + n++; + return n; +} + +static void +buttonrelease(XButtonPressedEvent *ev) { + Window *w; + + if((w = findwin(ev->window))) + handle(w, bup, ev); +} + +static void +buttonpress(XButtonPressedEvent *ev) { + Window *w; + + if((w = findwin(ev->window))) + handle(w, bdown, ev); + else + XAllowEvents(display, ReplayPointer, ev->time); +} + +static void +configurerequest(XConfigureRequestEvent *ev) { + XWindowChanges wc; + Window *w; + + if((w = findwin(ev->window))) + handle(w, configreq, ev); + else{ + wc.x = ev->x; + wc.y = ev->y; + wc.width = ev->width; + wc.height = ev->height; + wc.border_width = ev->border_width; + wc.sibling = ev->above; + wc.stack_mode = ev->detail; + XConfigureWindow(display, ev->window, ev->value_mask, &wc); + } +} + +static void +configurenotify(XConfigureEvent *ev) { + Window *w; + + ignoreenter = ev->serial; + if((w = findwin(ev->window))) + handle(w, config, ev); +} + +static void +clientmessage(XClientMessageEvent *ev) { + + if(ewmh_clientmessage(ev)) + return; + if(xdnd_clientmessage(ev)) + return; +} + +static void +destroynotify(XDestroyWindowEvent *ev) { + Window *w; + Client *c; + + if((w = findwin(ev->window))) + handle(w, destroy, ev); + else { + if((c = win2client(ev->window))) + fprint(2, "Badness: Unhandled DestroyNotify: " + "Client: %p, Window: %W, Name: %s\n", + c, &c->w, c->name); + } +} + +static void +enternotify(XCrossingEvent *ev) { + Window *w; + + xtime = ev->time; + if(ev->mode != NotifyNormal) + return; + + if((w = findwin(ev->window))) + handle(w, enter, ev); +} + +static void +leavenotify(XCrossingEvent *ev) { + Window *w; + + xtime = ev->time; + if((w = findwin(ev->window))) + handle(w, leave, ev); +} + +void +print_focus(const char *fn, Client *c, const char *to) { + Dprint(DFocus, "%s() disp.focus:\n", fn); + Dprint(DFocus, "\t%C => %C\n", disp.focus, c); + Dprint(DFocus, "\t%s => %s\n", clientname(disp.focus), to); +} + +static void +focusin(XFocusChangeEvent *ev) { + Window *w; + Client *c; + + /* Yes, we're focusing in on nothing, here. */ + if(ev->detail == NotifyDetailNone) { + print_focus("focusin", &c_magic, "<magic[none]>"); + disp.focus = &c_magic; + setfocus(screen->barwin, RevertToParent); + return; + } + + if(!((ev->detail == NotifyNonlinear) + ||(ev->detail == NotifyNonlinearVirtual) + ||(ev->detail == NotifyVirtual) + ||(ev->detail == NotifyInferior) + ||(ev->detail == NotifyAncestor))) + return; + if((ev->mode == NotifyWhileGrabbed) && (disp.hasgrab != &c_root)) + return; + + if(ev->window == screen->barwin->xid) { + print_focus("focusin", nil, "<nil>"); + disp.focus = nil; + } + else if((w = findwin(ev->window))) + handle(w, focusin, ev); + else if(ev->mode == NotifyGrab) { + /* Some unmanaged window has grabbed focus */ + if((c = disp.focus)) { + print_focus("focusin", &c_magic, "<magic>"); + disp.focus = &c_magic; + if(c->sel) + frame_draw(c->sel); + } + } +} + +static void +focusout(XFocusChangeEvent *ev) { + XEvent me; + Window *w; + + if(!((ev->detail == NotifyNonlinear) + ||(ev->detail == NotifyNonlinearVirtual) + ||(ev->detail == NotifyVirtual) + ||(ev->detail == NotifyInferior) + ||(ev->detail == NotifyAncestor))) + return; + if(ev->mode == NotifyUngrab) + disp.hasgrab = nil; + + if((ev->mode == NotifyGrab) + && XCheckMaskEvent(display, KeyPressMask, &me)) + dispatch_event(&me); + else if((w = findwin(ev->window))) + handle(w, focusout, ev); +} + +static void +expose(XExposeEvent *ev) { + Window *w; + + if(ev->count == 0) + if((w = findwin(ev->window))) + handle(w, expose, ev); +} + +static void +keypress(XKeyEvent *ev) { + Window *w; + + xtime = ev->time; + if((w = findwin(ev->window))) + handle(w, kdown, ev); +} + +static void +mappingnotify(XMappingEvent *ev) { + + XRefreshKeyboardMapping(ev); + if(ev->request == MappingKeyboard) + update_keys(); +} + +static void +maprequest(XMapRequestEvent *ev) { + Window *w; + + if((w = findwin(ev->parent))) + handle(w, mapreq, ev); +} + +static void +motionnotify(XMotionEvent *ev) { + Window *w; + + xtime = ev->time; + if((w = findwin(ev->window))) + handle(w, motion, ev); +} + +static void +propertynotify(XPropertyEvent *ev) { + Window *w; + + xtime = ev->time; + if((w = findwin(ev->window))) + handle(w, property, ev); +} + +static void +mapnotify(XMapEvent *ev) { + Window *w; + + ignoreenter = ev->serial; + if((w = findwin(ev->window))) + handle(w, map, ev); +} + +static void +unmapnotify(XUnmapEvent *ev) { + Window *w; + + ignoreenter = ev->serial; + if((w = findwin(ev->window)) && (ev->event == w->parent->xid)) { + w->mapped = false; + if(ev->send_event || w->unmapped-- == 0) + handle(w, unmap, ev); + } +} + +EvHandler handler[LASTEvent] = { + [ButtonPress] = (EvHandler)buttonpress, + [ButtonRelease] = (EvHandler)buttonrelease, + [ConfigureRequest] = (EvHandler)configurerequest, + [ConfigureNotify] = (EvHandler)configurenotify, + [ClientMessage] = (EvHandler)clientmessage, + [DestroyNotify] = (EvHandler)destroynotify, + [EnterNotify] = (EvHandler)enternotify, + [Expose] = (EvHandler)expose, + [FocusIn] = (EvHandler)focusin, + [FocusOut] = (EvHandler)focusout, + [KeyPress] = (EvHandler)keypress, + [LeaveNotify] = (EvHandler)leavenotify, + [MapNotify] = (EvHandler)mapnotify, + [MapRequest] = (EvHandler)maprequest, + [MappingNotify] = (EvHandler)mappingnotify, + [MotionNotify] = (EvHandler)motionnotify, + [PropertyNotify] = (EvHandler)propertynotify, + [UnmapNotify] = (EvHandler)unmapnotify, +}; + +void +check_x_event(IxpConn *c) { + XEvent ev; + + USED(c); + while(XPending(display)) { + XNextEvent(display, &ev); + dispatch_event(&ev); + } +} + diff --git a/cmd/wmii/ewmh.c b/cmd/wmii/ewmh.c new file mode 100644 index 0000000..6509e5f --- /dev/null +++ b/cmd/wmii/ewmh.c @@ -0,0 +1,544 @@ +/* Copyright ©2007-2010 Kris Maglione <maglione.k at Gmail> + * See LICENSE file for license details. + */ +#include "dat.h" +#include <limits.h> +#include "fns.h" + +Window *ewmhwin; + +static void ewmh_getwinstate(Client*); +static void ewmh_setstate(Client*, Atom, int); + +#define Net(x) ("_NET_" x) +#define Action(x) Net("WM_ACTION_" x) +#define State(x) Net("WM_STATE_" x) +#define Type(x) Net("WM_WINDOW_TYPE_" x) +#define NET(x) xatom(Net(x)) +#define ACTION(x) xatom(Action(x)) +#define STATE(x) xatom(State(x)) +#define TYPE(x) xatom(Type(x)) + +void +ewmh_init(void) { + WinAttr wa; + char myname[] = "wmii"; + long win; + + ewmhwin = createwindow(&scr.root, + Rect(0, 0, 1, 1), 0 /*depth*/, + InputOnly, &wa, 0); + + win = ewmhwin->xid; + changeprop_long(&scr.root, Net("SUPPORTING_WM_CHECK"), "WINDOW", &win, 1); + changeprop_long(ewmhwin, Net("SUPPORTING_WM_CHECK"), "WINDOW", &win, 1); + changeprop_string(ewmhwin, Net("WM_NAME"), myname); + + long zz[] = {0, 0}; + changeprop_long(&scr.root, Net("DESKTOP_VIEWPORT"), "CARDINAL", + zz, 2); + + long supported[] = { + /* Misc */ + NET("SUPPORTED"), + /* Root Properties/Messages */ + NET("ACTIVE_WINDOW"), + NET("CLOSE_WINDOW"), + NET("CURRENT_DESKTOP"), + /* Client Properties */ + NET("FRAME_EXTENTS"), + NET("WM_DESKTOP"), + NET("WM_FULLSCREEN_MONITORS"), + NET("WM_NAME"), + NET("WM_STRUT"), + NET("WM_STRUT_PARTIAL"), + /* States */ + NET("WM_STATE"), + STATE("DEMANDS_ATTENTION"), + STATE("FULLSCREEN"), + STATE("SHADED"), + /* Window Types */ + NET("WM_WINDOW_TYPE"), + TYPE("DIALOG"), + TYPE("DOCK"), + TYPE("NORMAL"), + TYPE("SPLASH"), + /* Actions */ + NET("WM_ALLOWED_ACTIONS"), + ACTION("FULLSCREEN"), + /* Desktops */ + NET("DESKTOP_NAMES"), + NET("NUMBER_OF_DESKTOPS"), + /* Client List */ + NET("CLIENT_LIST"), + NET("CLIENT_LIST_STACKING"), + }; + changeprop_long(&scr.root, Net("SUPPORTED"), "ATOM", supported, nelem(supported)); +} + +void +ewmh_updateclientlist(void) { + Vector_long vec; + Client *c; + + vector_linit(&vec); + for(c=client; c; c=c->next) + vector_lpush(&vec, c->w.xid); + changeprop_long(&scr.root, Net("CLIENT_LIST"), "WINDOW", vec.ary, vec.n); + free(vec.ary); +} + +void +ewmh_updatestacking(void) { + Vector_long vec; + Frame *f; + Area *a; + View *v; + int s; + + vector_linit(&vec); + + for(v=view; v; v=v->next) { + foreach_column(v, s, a) + for(f=a->frame; f; f=f->anext) + if(f->client->sel == f) + vector_lpush(&vec, f->client->w.xid); + } + for(v=view; v; v=v->next) { + for(f=v->floating->stack; f; f=f->snext) + if(!f->snext) break; + for(; f; f=f->sprev) + if(f->client->sel == f) + vector_lpush(&vec, f->client->w.xid); + } + + changeprop_long(&scr.root, Net("CLIENT_LIST_STACKING"), "WINDOW", vec.ary, vec.n); + vector_lfree(&vec); +} + +void +ewmh_initclient(Client *c) { + long allowed[] = { + ACTION("FULLSCREEN"), + }; + + changeprop_long(&c->w, Net("WM_ALLOWED_ACTIONS"), "ATOM", + allowed, nelem(allowed)); + ewmh_getwintype(c); + ewmh_getwinstate(c); + ewmh_getstrut(c); + ewmh_updateclientlist(); +} + +void +ewmh_destroyclient(Client *c) { + Ewmh *e; + + ewmh_updateclientlist(); + + e = &c->w.ewmh; + if(e->timer) + if(!ixp_unsettimer(&srv, e->timer)) + fprint(2, "Badness: %C: Can't unset timer\n", c); + free(c->strut); +} + +static void +pingtimeout(long id, void *v) { + Client *c; + + USED(id); + c = v; + event("Unresponsive %C\n", c); + c->w.ewmh.ping = 0; + c->w.ewmh.timer = 0; +} + +void +ewmh_pingclient(Client *c) { + Ewmh *e; + + if(!(c->proto & ProtoPing)) + return; + + e = &c->w.ewmh; + if(e->ping) + return; + + client_message(c, Net("WM_PING"), c->w.xid); + e->ping = xtime++; + e->timer = ixp_settimer(&srv, PingTime, pingtimeout, c); +} + +int +ewmh_prop(Client *c, Atom a) { + if(a == NET("WM_WINDOW_TYPE")) + ewmh_getwintype(c); + else + if(a == NET("WM_STRUT_PARTIAL")) + ewmh_getstrut(c); + else + return 0; + return 1; +} + +typedef struct Prop Prop; +struct Prop { + char* name; + long mask; + Atom atom; +}; + +static long +getmask(Prop *props, ulong *vals, int n) { + Prop *p; + long ret; + + if(props[0].atom == 0) + for(p=props; p->name; p++) + p->atom = xatom(p->name); + + ret = 0; + while(n--) { + Dprint(DEwmh, "\tvals[%d] = \"%A\"\n", n, vals[n]); + for(p=props; p->name; p++) + if(p->atom == vals[n]) { + ret |= p->mask; + break; + } + } + return ret; +} + +static long +getprop_mask(Window *w, char *prop, Prop *props) { + ulong *vals; + long n, mask; + + n = getprop_ulong(w, prop, "ATOM", + 0L, &vals, 16); + mask = getmask(props, vals, n); + free(vals); + return mask; +} + +void +ewmh_getwintype(Client *c) { + static Prop props[] = { + {Type("DESKTOP"), TypeDesktop}, + {Type("DOCK"), TypeDock}, + {Type("TOOLBAR"), TypeToolbar}, + {Type("MENU"), TypeMenu}, + {Type("UTILITY"), TypeUtility}, + {Type("SPLASH"), TypeSplash}, + {Type("DIALOG"), TypeDialog}, + {Type("NORMAL"), TypeNormal}, + {0, } + }; + long mask; + + mask = getprop_mask(&c->w, Net("WM_WINDOW_TYPE"), props); + + c->w.ewmh.type = mask; + if(mask & TypeDock) { + c->borderless = 1; + c->titleless = 1; + } +} + +static void +ewmh_getwinstate(Client *c) { + ulong *vals; + long n; + + n = getprop_ulong(&c->w, Net("WM_STATE"), "ATOM", + 0L, &vals, 16); + while(--n >= 0) + ewmh_setstate(c, vals[n], On); + free(vals); +} + +long +ewmh_protocols(Window *w) { + static Prop props[] = { + {"WM_DELETE_WINDOW", ProtoDelete}, + {"WM_TAKE_FOCUS", ProtoTakeFocus}, + {Net("WM_PING"), ProtoPing}, + {0, } + }; + + return getprop_mask(w, "WM_PROTOCOLS", props); +} + +void +ewmh_getstrut(Client *c) { + enum { + Left, Right, Top, Bottom, + LeftMin, LeftMax, + RightMin, RightMax, + TopMin, TopMax, + BottomMin, BottomMax, + Last + }; + long *strut; + ulong n; + + if(c->strut == nil) + free(c->strut); + c->strut = nil; + + n = getprop_long(&c->w, Net("WM_STRUT_PARTIAL"), "CARDINAL", + 0L, &strut, Last); + if(n != Last) { + free(strut); + n = getprop_long(&c->w, Net("WM_STRUT"), "CARDINAL", + 0L, &strut, 4L); + if(n != 4) { + free(strut); + return; + } + Dprint(DEwmh, "ewmh_getstrut(%C[%s]) Using WM_STRUT\n", c, clientname(c)); + strut = erealloc(strut, Last * sizeof *strut); + strut[LeftMin] = strut[RightMin] = 0; + strut[LeftMax] = strut[RightMax] = INT_MAX; + strut[TopMin] = strut[BottomMin] = 0; + strut[TopMax] = strut[BottomMax] = INT_MAX; + } + c->strut = emalloc(sizeof *c->strut); + c->strut->left = Rect(0, strut[LeftMin], strut[Left], strut[LeftMax]); + c->strut->right = Rect(-strut[Right], strut[RightMin], 0, strut[RightMax]); + c->strut->top = Rect(strut[TopMin], 0, strut[TopMax], strut[Top]); + c->strut->bottom = Rect(strut[BottomMin], -strut[Bottom], strut[BottomMax], 0); + Dprint(DEwmh, "ewmh_getstrut(%C[%s])\n", c, clientname(c)); + Dprint(DEwmh, "\ttop: %R\n", c->strut->top); + Dprint(DEwmh, "\tleft: %R\n", c->strut->left); + Dprint(DEwmh, "\tright: %R\n", c->strut->right); + Dprint(DEwmh, "\tbottom: %R\n", c->strut->bottom); + free(strut); + view_update(selview); +} + +static void +ewmh_setstate(Client *c, Atom state, int action) { + + Dprint(DEwmh, "\tSTATE = %A\n", state); + if(state == 0) + return; + + if(state == STATE("FULLSCREEN")) + fullscreen(c, action, -1); + else + if(state == STATE("DEMANDS_ATTENTION")) + client_seturgent(c, action, UrgClient); +} + +int +ewmh_clientmessage(XClientMessageEvent *e) { + Client *c; + View *v; + ulong *l; + ulong msg; + int action, i; + + l = (ulong*)e->data.l; + msg = e->message_type; + Dprint(DEwmh, "ClientMessage: %A\n", msg); + + if(msg == NET("WM_STATE")) { + enum { + StateUnset, + StateSet, + StateToggle, + }; + if(e->format != 32) + return -1; + c = win2client(e->window); + if(c == nil) + return 0; + switch(l[0]) { + case StateUnset: action = Off; break; + case StateSet: action = On; break; + case StateToggle: action = Toggle; break; + default: return -1; + } + Dprint(DEwmh, "\tAction: %s\n", TOGGLE(action)); + ewmh_setstate(c, l[1], action); + ewmh_setstate(c, l[2], action); + return 1; + }else + if(msg == NET("ACTIVE_WINDOW")) { + if(e->format != 32) + return -1; + Dprint(DEwmh, "\tsource: %ld\n", l[0]); + Dprint(DEwmh, "\twindow: 0x%lx\n", e->window); + c = win2client(e->window); + if(c == nil) + return 1; + Dprint(DEwmh, "\tclient: %s\n", clientname(c)); + if(l[0] != 2) + return 1; + focus(c, true); + return 1; + }else + if(msg == NET("CLOSE_WINDOW")) { + if(e->format != 32) + return -1; + Dprint(DEwmh, "\tsource: %ld\n", l[0]); + Dprint(DEwmh, "\twindow: 0x%lx\n", e->window); + c = win2client(e->window); + if(c == nil) + return 1; + client_kill(c, true); + return 1; + }else + if(msg == NET("CURRENT_DESKTOP")) { + if(e->format != 32) + return -1; + for(v=view, i=l[0]; v; v=v->next, i--) + if(i == 0) + break; + Dprint(DEwmh, "\t%s\n", v->name); + if(i == 0) + view_select(v->name); + return 1; + }else + if(msg == xatom("WM_PROTOCOLS")) { + if(e->format != 32) + return 0; + Dprint(DEwmh, "\t%A\n", l[0]); + if(l[0] == NET("WM_PING")) { + if(e->window != scr.root.xid) + return -1; + c = win2client(l[2]); + if(c == nil) + return 1; + Dprint(DEwmh, "\tclient = [%C]\"%s\"\n", c, clientname(c)); + Dprint(DEwmh, "\ttimer = %ld, ping = %ld\n", + c->w.ewmh.timer, c->w.ewmh.ping); + if(c->w.ewmh.timer) + ixp_unsettimer(&srv, c->w.ewmh.timer); + c->w.ewmh.timer = 0; + c->w.ewmh.ping = 0; + return 1; + } + } + + return 0; +} + +void +ewmh_framesize(Client *c) { + Rectangle r; + Frame *f; + + f = c->sel; + r.min.x = f->crect.min.x; + r.min.y = f->crect.min.y; + r.max.x = Dx(f->r) - f->crect.max.x; + r.max.y = Dy(f->r) - f->crect.max.y; + + long extents[] = { + r.min.x, r.max.x, + r.min.y, r.max.y, + }; + changeprop_long(&c->w, Net("FRAME_EXTENTS"), "CARDINAL", + extents, nelem(extents)); +} + +void +ewmh_updatestate(Client *c) { + long state[16]; + Frame *f; + int i; + + f = c->sel; + if(f == nil || f->view != selview) + return; + + i = 0; + if(f->collapsed) + state[i++] = STATE("SHADED"); + if(c->fullscreen >= 0) + state[i++] = STATE("FULLSCREEN"); + if(c->urgent) + state[i++] = STATE("DEMANDS_ATTENTION"); + + if(i > 0) + changeprop_long(&c->w, Net("WM_STATE"), "ATOM", state, i); + else + delproperty(&c->w, Net("WM_STATE")); + + if(c->fullscreen >= 0) + changeprop_long(&c->w, Net("WM_FULLSCREEN_MONITORS"), "CARDINAL", + (long[]) { c->fullscreen, c->fullscreen, + c->fullscreen, c->fullscreen }, + 4); + else + delproperty(&c->w, Net("WM_FULLSCREEN_MONITORS")); +} + +/* Views */ +void +ewmh_updateviews(void) { + View *v; + Vector_ptr tags; + long i; + + if(starting) + return; + + vector_pinit(&tags); + for(v=view, i=0; v; v=v->next, i++) + vector_ppush(&tags, v->name); + vector_ppush(&tags, nil); + changeprop_textlist(&scr.root, Net("DESKTOP_NAMES"), "UTF8_STRING", (char**)tags.ary); + changeprop_long(&scr.root, Net("NUMBER_OF_DESKTOPS"), "CARDINAL", &i, 1); + vector_pfree(&tags); + ewmh_updateview(); + ewmh_updateclients(); +} + +static int +viewidx(View *v) { + View *vp; + int i; + + for(vp=view, i=0; vp; vp=vp->next, i++) + if(vp == v) + break; + assert(vp); + return i; +} + +void +ewmh_updateview(void) { + long i; + + if(starting) + return; + + i = viewidx(selview); + changeprop_long(&scr.root, Net("CURRENT_DESKTOP"), "CARDINAL", &i, 1); +} + +void +ewmh_updateclient(Client *c) { + long i; + + i = -1; + if(c->sel) + i = viewidx(c->sel->view); + changeprop_long(&c->w, Net("WM_DESKTOP"), "CARDINAL", &i, 1); +} + +void +ewmh_updateclients(void) { + Client *c; + + if(starting) + return; + + for(c=client; c; c=c->next) + ewmh_updateclient(c); +} + diff --git a/cmd/wmii/float.c b/cmd/wmii/float.c new file mode 100644 index 0000000..23998d5 --- /dev/null +++ b/cmd/wmii/float.c @@ -0,0 +1,245 @@ +/* Copyright ©2006-2010 Kris Maglione <maglione.k at Gmail> + * See LICENSE file for license details. + */ +#include "dat.h" +#include <limits.h> +#include "fns.h" + +static void float_placeframe(Frame*); + +void +float_attach(Area *a, Frame *f) { + + f->client->floating = true; + + f->r = f->floatr; + float_placeframe(f); + assert(a->sel != f); + frame_insert(f, a->sel); + + if(a->sel == nil) + area_setsel(a, f); +} + +void +float_detach(Frame *f) { + Frame *pr; + Area *a, *sel, *oldsel; + View *v; + + v = f->view; + a = f->area; + sel = view_findarea(v, v->selscreen, v->selcol, false); + oldsel = v->oldsel; + pr = f->aprev; + + frame_remove(f); + + if(a->sel == f) { + if(!pr) + pr = a->frame; + a->sel = nil; + area_setsel(a, pr); + } + f->area = nil; + + if(oldsel) + area_focus(oldsel); + else if(!a->frame) + if(sel && sel->frame) + area_focus(sel); +} + +void +float_resizeframe(Frame *f, Rectangle r) { + + if(f->area->view == selview) + client_resize(f->client, r); + else + frame_resize(f, r); +} + +void +float_arrange(Area *a) { + Frame *f; + + assert(a->floating); + + switch(a->mode) { + case Coldefault: + for(f=a->frame; f; f=f->anext) + f->collapsed = false; + break; + case Colstack: + for(f=a->frame; f; f=f->anext) + f->collapsed = (f != a->sel); + break; + default: + die("not reached"); + break; + } + for(f=a->frame; f; f=f->anext) + f->r = f->floatr; + view_update(a->view); +} + +static void +rect_push(Vector_rect *vec, Rectangle r) { + Rectangle *rp; + int i; + + for(i=0; i < vec->n; i++) { + rp = &vec->ary[i]; + if(rect_contains_p(*rp, r)) + return; + if(rect_contains_p(r, *rp)) { + *rp = r; + return; + } + } + vector_rpush(vec, r); +} + +Vector_rect* +unique_rects(Vector_rect *vec, Rectangle orig) { + static Vector_rect vec1, vec2; + Vector_rect *v1, *v2, *v; + Rectangle r1, r2; + int i, j; + + v1 = &vec1; + v2 = &vec2; + v1->n = 0; + vector_rpush(v1, orig); + for(i=0; i < vec->n; i++) { + v2->n = 0; + r1 = vec->ary[i]; + for(j=0; j < v1->n; j++) { + r2 = v1->ary[j]; + if(!rect_intersect_p(r1, r2)) { + rect_push(v2, r2); + continue; + } + if(r2.min.x < r1.min.x) + rect_push(v2, Rect(r2.min.x, r2.min.y, r1.min.x, r2.max.y)); + if(r2.min.y < r1.min.y) + rect_push(v2, Rect(r2.min.x, r2.min.y, r2.max.x, r1.min.y)); + if(r2.max.x > r1.max.x) + rect_push(v2, Rect(r1.max.x, r2.min.y, r2.max.x, r2.max.y)); + if(r2.max.y > r1.max.y) + rect_push(v2, Rect(r2.min.x, r1.max.y, r2.max.x, r2.max.y)); + } + v = v1; + v1 = v2; + v2 = v; + } + return v1; +} + +Rectangle +max_rect(Vector_rect *vec) { + Rectangle *r, *rp; + int i, a, area; + + area = 0; + r = 0; + for(i=0; i < vec->n; i++) { + rp = &vec->ary[i]; + a = Dx(*rp) * Dy(*rp); + if(a > area) { + area = a; + r = rp; + } + } + return r ? *r : ZR; +} + +static void +float_placeframe(Frame *f) { + static Vector_rect vec; + Vector_rect *vp; + Rectangle r; + Point dim, p; + Client *c; + Frame *ff; + Area *a, *sel; + long area, l; + int i, s; + + a = f->area; + c = f->client; + + /* + if(c->trans) + return; + */ + + if(c->fullscreen >= 0 || c->w.hints->position || starting) { + f->r = f->floatr; + return; + } + + /* Find all rectangles on the floating layer into which + * the new frame would fit. + */ + vec.n = 0; + for(ff=a->frame; ff; ff=ff->anext) + /* TODO: Find out why this check is needed. + * The frame hasn't been inserted yet, but somehow, + * its old rectangle winds up in the list. + */ + if(ff->client != f->client) + vector_rpush(&vec, ff->r); + + /* Decide which screen we want to place this on. + * Ideally, it should probably Do the Right Thing + * when a screen fills, but what's the right thing? + * I think usage will show... + */ + s = -1; + ff = client_groupframe(c, f->view); + if (f->screen >= 0) + s = f->screen; + else if (ff) + s = ownerscreen(ff->r); + else if (selclient()) + s = ownerscreen(selclient()->sel->r); + else { + sel = view_findarea(a->view, a->view->selscreen, a->view->selcol, false); + if (sel) + s = sel->screen; + } + + r = s == -1 ? a->r : screens[s]->r; + vp = unique_rects(&vec, r); + + area = LONG_MAX; + dim.x = Dx(f->r); + dim.y = Dy(f->r); + p = ZP; + + for(i=0; i < vp->n; i++) { + r = vp->ary[i]; + if(Dx(r) < dim.x || Dy(r) < dim.y) + continue; + l = Dx(r) * Dy(r); + if(l < area) { + area = l; + p = r.min; + } + } + + if(area == LONG_MAX) { + /* Cascade. */ + s = max(s, 0); + ff = a->sel; + if(ff) + p = addpt(ff->r.min, Pt(Dy(ff->titlebar), Dy(ff->titlebar))); + if(p.x + Dx(f->r) > screens[s]->r.max.x || + p.y + Dy(f->r) > screens[s]->r.max.y) + p = screens[s]->r.min; + } + + f->floatr = rectsetorigin(f->r, p); +} + diff --git a/cmd/wmii/fns.h b/cmd/wmii/fns.h new file mode 100644 index 0000000..9b53ef9 --- /dev/null +++ b/cmd/wmii/fns.h @@ -0,0 +1,328 @@ +/* Copyright ©2007-2010 Kris Maglione <jg@suckless.org> + * See LICENSE file for license details. + */ + +#ifdef VARARGCK +# pragma varargck argpos debug 2 +# pragma varargck argpos dprint 1 +# pragma varargck argpos event 1 +# pragma varargck argpos warning 1 +# +# pragma varargck type "a" Area* +# pragma varargck type "C" Client* +# pragma varargck type "r" void +#endif + +#define _cond(cond, n) (cond) && __alive++ == n +#define _cont(cont) (void)(__alive--, cont) + +#define with(type, var) \ + for(type var=(type)-1; (var == (type)-1) && ((var=0) || true);) + +/* Grotesque, but worth it. */ + +#define foreach_area(v, s, a) \ + with(int, __alive) \ + with(Area*, __anext) \ + for(s=0; _cond(s <= nscreens, 0); _cont(s++)) \ + for((a)=(s < nscreens ? (v)->areas[s] : v->floating), __anext=(a)->next; _cond(a, 1); _cont(((a)=__anext) && (__anext=(a)->next))) + +#define foreach_column(v, s, a) \ + with(int, __alive) \ + with(Area*, __anext) \ + for(s=0; _cond(s < nscreens, 0); _cont(s++)) \ + for((a)=(v)->areas[s], __anext=(a)->next; _cond(a, 1); _cont(((a)=__anext) && (__anext=(a)->next))) + +#define foreach_frame(v, s, a, f) \ + with(Frame*, __fnext) \ + foreach_area(v, s, a) \ + for((void)(((f)=(a)->frame) && (__fnext=(f)->anext)); _cond(f, 2); _cont(((f)=__fnext) && (__fnext=(f)->anext))) + +#define btassert(arg, cond) \ + (cond ? fprint(1, __FILE__":%d: failed assertion: " #cond "\n", __LINE__), backtrace(arg), true : false) + +/* area.c */ +int afmt(Fmt*); +void area_attach(Area*, Frame*); +Area* area_create(View*, Area *pos, int scrn, uint w); +void area_destroy(Area*); +void area_detach(Frame*); +Area* area_find(View*, Rectangle, int, bool); +void area_focus(Area*); +int area_idx(Area*); +void area_moveto(Area*, Frame*); +char* area_name(Area*); +Client* area_selclient(Area*); +void area_setsel(Area*, Frame*); + +/* bar.c */ +Bar* bar_create(Bar**, const char*); +void bar_destroy(Bar**, Bar*); +void bar_draw(WMScreen*); +Bar* bar_find(Bar*, const char*); +void bar_init(WMScreen*); +void bar_load(Bar*); +void bar_resize(WMScreen*); +void bar_sety(WMScreen*, int); +void bar_setbounds(WMScreen*, int, int); + +/* client.c */ +int Cfmt(Fmt *f); +bool client_applytags(Client*, const char*); +void client_configure(Client*); +Client* client_create(XWindow, XWindowAttributes*); +void client_destroy(Client*); +char* client_extratags(Client*); +bool client_floats_p(Client*); +void client_focus(Client*); +Frame* client_groupframe(Client*, View*); +void client_kill(Client*, bool); +void client_manage(Client*); +void client_map(Client*); +void client_message(Client*, char*, long); +void client_prop(Client*, Atom); +void client_reparent(Client*, Window*, Point); +void client_resize(Client*, Rectangle); +void client_setcursor(Client*, Cursor); +void client_seturgent(Client*, int, int); +void client_setviews(Client*, char**); +void client_unmap(Client*, int state); +Frame* client_viewframe(Client *c, View *v); +char* clientname(Client*); +void focus(Client*, bool restack); +void fullscreen(Client*, int, long); +Client* group_leader(Group*); +int map_frame(Client*); +Client* selclient(void); +int unmap_frame(Client*); +void update_class(Client*); +Client* win2client(XWindow); +Rectangle client_grav(Client*, Rectangle); + +/* column.c */ +bool column_setmode(Area*, const char*); +char* column_getmode(Area*); +void column_arrange(Area*, bool dirty); +void column_attach(Area*, Frame*); +void column_attachrect(Area*, Frame*, Rectangle); +void column_detach(Frame*); +void column_frob(Area*); +void column_insert(Area*, Frame*, Frame*); +int column_minwidth(void); +Area* column_new(View*, Area*, int, uint); +void column_remove(Frame*); +void column_resize(Area*, int); +void column_resizeframe(Frame*, Rectangle); +void column_settle(Area*); +void div_draw(Divide*); +void div_set(Divide*, int x); +void div_update_all(void); +bool find(Area**, Frame**, int, bool, bool); +int stack_count(Frame*, int*); +Frame* stack_find(Area*, Frame*, int, bool); + +/* error.c */ +#define waserror() setjmp(pusherror()) +void error(char*, ...); +void nexterror(void); +void poperror(void); +jmp_buf* pusherror(void); + +/* event.c */ +void check_x_event(IxpConn*); +void dispatch_event(XEvent*); +uint flushenterevents(void); +uint flushevents(long, bool dispatch); +void print_focus(const char*, Client*, const char*); +void xtime_kludge(void); + +/* ewmh.c */ +int ewmh_clientmessage(XClientMessageEvent*); +void ewmh_destroyclient(Client*); +void ewmh_framesize(Client*); +void ewmh_getstrut(Client*); +void ewmh_getwintype(Client*); +void ewmh_init(void); +void ewmh_initclient(Client*); +void ewmh_pingclient(Client*); +int ewmh_prop(Client*, Atom); +long ewmh_protocols(Window*); +void ewmh_updateclient(Client*); +void ewmh_updateclientlist(void); +void ewmh_updateclients(void); +void ewmh_updatestacking(void); +void ewmh_updatestate(Client*); +void ewmh_updateview(void); +void ewmh_updateviews(void); + +/* float.c */ +void float_arrange(Area*); +void float_attach(Area*, Frame*); +void float_detach(Frame*); +void float_resizeframe(Frame*, Rectangle); +Vector_rect* unique_rects(Vector_rect*, Rectangle); +Rectangle max_rect(Vector_rect*); + +/* frame.c */ +Frame* frame_create(Client*, View*); +int frame_delta_h(void); +void frame_draw(Frame*); +void frame_draw_all(void); +void frame_focus(Frame*); +uint frame_idx(Frame*); +void frame_insert(Frame*, Frame *pos); +void frame_remove(Frame*); +void frame_resize(Frame*, Rectangle); +bool frame_restack(Frame*, Frame*); +void frame_swap(Frame*, Frame*); +int ingrabbox_p(Frame*, int x, int y); +void move_focus(Frame*, Frame*); +Rectangle constrain(Rectangle, int); +Rectangle frame_client2rect(Client*, Rectangle, bool); +WinHints frame_gethints(Frame*); +Rectangle frame_hints(Frame*, Rectangle, Align); +Rectangle frame_rect2client(Client*, Rectangle, bool); + +/* fs.c */ +void fs_attach(Ixp9Req*); +void fs_clunk(Ixp9Req*); +void fs_create(Ixp9Req*); +void fs_flush(Ixp9Req*); +void fs_freefid(Fid*); +void fs_open(Ixp9Req*); +void fs_read(Ixp9Req*); +void fs_remove(Ixp9Req*); +void fs_stat(Ixp9Req*); +void fs_walk(Ixp9Req*); +void fs_write(Ixp9Req*); +void event(const char*, ...); + +/* geom.c */ +Align get_sticky(Rectangle src, Rectangle dst); +Cursor quad_cursor(Align); +Align quadrant(Rectangle, Point); +bool rect_contains_p(Rectangle, Rectangle); +bool rect_haspoint_p(Point, Rectangle); +bool rect_intersect_p(Rectangle, Rectangle); +Rectangle rect_intersection(Rectangle, Rectangle); + +/* key.c */ +void init_lock_keys(void); +void kpress(XWindow, ulong mod, KeyCode); +void update_keys(void); + +/* main.c */ +void init_screens(void); +void spawn_command(const char*); + +/* map.c */ +void** hash_get(Map*, const char*, bool create); +void* hash_rm(Map*, const char*); +void** map_get(Map*, ulong, bool create); +void* map_rm(Map*, ulong); + +/* message.c */ +bool getlong(const char*, long*); +bool getulong(const char*, ulong*); +char* message_client(Client*, IxpMsg*); +char* message_root(void*, IxpMsg*); +char* message_view(View*, IxpMsg*); +char* msg_debug(IxpMsg*); +char* msg_getword(IxpMsg*); +char* msg_parsecolors(IxpMsg*, CTuple*); +char* msg_selectarea(Area*, IxpMsg*); +char* msg_sendclient(View*, IxpMsg*, bool swap); +char* readctl_client(Client*); +char* readctl_root(void); +char* readctl_view(View*); +Area* strarea(View*, ulong, const char*); +void warning(const char*, ...); +/* debug */ +void debug(int, const char*, ...); +void dprint(const char*, ...); +void dwrite(int, void*, int, bool); +bool setdebug(int); +void vdebug(int, const char*, va_list); + +/* mouse.c */ +Window* constraintwin(Rectangle); +void destroyconstraintwin(Window*); +void grab_button(XWindow, uint button, ulong mod); +void mouse_checkresize(Frame*, Point, bool); +void mouse_movegrabbox(Client*, bool); +void mouse_resize(Client*, Align, bool); +void mouse_resizecol(Divide*); +bool readmotion(Point*); +int readmouse(Point*, uint*); +Align snap_rect(const Rectangle *rects, int num, Rectangle *current, Align *mask, int snapw); + +/* print.c */ +int Ffmt(Fmt*); + +/* printevent.c */ +void printevent(XEvent*); + +/* root.c */ +void root_init(void); + +/* screen.c */ +void* findthing(Rectangle, int, Vector_ptr*, Rectangle(*)(void*), bool); +int ownerscreen(Rectangle); + +/* rule.c */ +void trim(char *str, const char *chars); +void update_rules(Rule**, const char*); + +/* view.c */ +void view_arrange(View*); +void view_attach(View*, Frame*); +View* view_create(const char*); +void view_destroy(View*); +void view_detach(Frame*); +Area* view_findarea(View*, int, int, bool); +void view_focus(WMScreen*, View*); +bool view_fullscreen_p(View*, int); +char* view_index(View*); +void view_init(View*, int iscreen); +char** view_names(void); +uint view_newcolwidth(View*, int i); +void view_restack(View*); +void view_scale(View*, int, int); +Client* view_selclient(View*); +void view_select(const char*); +void view_update(View*); +void view_update_all(void); +void view_update_rect(View*); +Rectangle* view_rects(View*, uint *num, Frame *ignore); + +/* _util.c */ +void backtrace(char*); +void closeexec(int); +char** comm(int, char**, char**); +int doublefork(void); +void grep(char**, Reprog*, int); +char* join(char**, char*); +char* pathsearch(const char*, const char*, bool); +void refree(Regex*); +void reinit(Regex*, char*); +int strlcatprint(char*, int, const char*, ...); +int spawn3(int[3], const char*, char*[]); +int spawn3l(int[3], const char*, ...); +void uniq(char**); +int unquote(char*, char*[], int); + +/* utf.c */ +char* toutf8(const char*); +char* toutf8n(const char*, size_t); + +/* xdnd.c */ +int xdnd_clientmessage(XClientMessageEvent*); +void xdnd_initwindow(Window*); + +/* xext.c */ +void randr_event(XEvent*); +bool render_argb_p(Visual*); +void xext_event(XEvent*); +void xext_init(void); +Rectangle* xinerama_screens(int*); + diff --git a/cmd/wmii/frame.c b/cmd/wmii/frame.c new file mode 100644 index 0000000..6bedd82 --- /dev/null +++ b/cmd/wmii/frame.c @@ -0,0 +1,681 @@ +/* Copyright ©2006-2010 Kris Maglione <maglione.k at Gmail> + * See LICENSE file for license details. + */ +#include "dat.h" +#include <math.h> +#include "fns.h" + +uint +frame_idx(Frame *f) { + Frame *fp; + uint i; + + fp = f->area->frame; + for(i = 1; fp != f; fp = fp->anext) + i++; + return i; +} + +Frame* +frame_create(Client *c, View *v) { + static ushort id = 1; + Frame *f; + + f = emallocz(sizeof *f); + f->id = id++; + f->client = c; + f->view = v; + + if(c->sel) { + f->floatr = c->sel->floatr; + f->r = c->sel->r; + }else { + f->r = client_grav(c, c->r); + f->floatr = f->r; + c->sel = f; + } + f->collapsed = false; + f->screen = -1; + f->oldarea = -1; + f->oldscreen = -1; + + return f; +} + +void +frame_remove(Frame *f) { + Area *a; + + a = f->area; + if(f->aprev) + f->aprev->anext = f->anext; + if(f->anext) + f->anext->aprev = f->aprev; + if(f == a->frame) + a->frame = f->anext; + + if(a->floating) { + if(f->sprev) + f->sprev->snext = f->snext; + if(f->snext) + f->snext->sprev = f->sprev; + if(f == a->stack) + a->stack = f->snext; + } + f->anext = f->aprev = f->snext = f->sprev = nil; +} + +void +frame_insert(Frame *f, Frame *pos) { + Area *a; + + a = f->area; + + if(pos) { + assert(pos != f); + f->aprev = pos; + f->anext = pos->anext; + }else { + assert(f->area->frame != f); + f->anext = f->area->frame; + f->area->frame = f; + } + if(f->aprev) + f->aprev->anext = f; + if(f->anext) + f->anext->aprev = f; + + if(a->floating) { + assert(f->sprev == nil); + frame_restack(f, nil); + } +} + +bool +frame_restack(Frame *f, Frame *above) { + Client *c; + Frame *fp; + Area *a; + + c = f->client; + a = f->area; + if(!a->floating) + return false; + if(f == above) + return false; + + if(above == nil && !(c->w.ewmh.type & TypeDock)) + for(fp=a->stack; fp; fp=fp->snext) + if(fp->client->w.ewmh.type & TypeDock) + above = fp; + else + break; + + if(f->sprev || f == a->stack) + if(f->sprev == above) + return false; + + if(f->sprev) + f->sprev->snext = f->snext; + else if(f->snext) + a->stack = f->snext; + if(f->snext) + f->snext->sprev = f->sprev; + + f->sprev = above; + if(above == nil) { + f->snext = a->stack; + a->stack = f; + } + else { + f->snext = above->snext; + above->snext = f; + } + if(f->snext) + f->snext->sprev = f; + assert(f->snext != f && f->sprev != f); + + return true; +} + +/* Handlers */ +static void +bup_event(Window *w, XButtonEvent *e) { + if((e->state & def.mod) != def.mod) + XAllowEvents(display, ReplayPointer, e->time); + else + XUngrabPointer(display, e->time); + event("ClientClick %C %d\n", w->aux, e->button); +} + +static void +bdown_event(Window *w, XButtonEvent *e) { + Frame *f; + Client *c; + + c = w->aux; + f = c->sel; + + if((e->state & def.mod) == def.mod) { + switch(e->button) { + case Button1: + focus(c, false); + mouse_resize(c, Center, true); + break; + case Button2: + frame_restack(f, nil); + view_restack(f->view); + focus(c, false); + grabpointer(c->framewin, nil, cursor[CurNone], ButtonReleaseMask); + break; + case Button3: + focus(c, false); + mouse_resize(c, quadrant(f->r, Pt(e->x_root, e->y_root)), true); + break; + default: + XAllowEvents(display, ReplayPointer, e->time); + break; + } + }else { + if(e->button == Button1) { + if(!e->subwindow) { + frame_restack(f, nil); + view_restack(f->view); + mouse_checkresize(f, Pt(e->x, e->y), true); + } + + if(f->client != selclient()) + focus(c, false); + } + if(e->subwindow) + XAllowEvents(display, ReplayPointer, e->time); + else { + /* Ungrab so a menu can receive events before the button is released */ + XUngrabPointer(display, e->time); + sync(); + + event("ClientMouseDown %C %d\n", f->client, e->button); + } + } +} + +static void +config_event(Window *w, XConfigureEvent *e) { + + USED(w, e); +} + +static void +enter_event(Window *w, XCrossingEvent *e) { + Client *c; + Frame *f; + + c = w->aux; + f = c->sel; + if(disp.focus != c || selclient() != c) { + Dprint(DFocus, "enter_notify(f) => [%C]%s%s\n", + f->client, f->client->name, + ignoreenter == e->serial ? " (ignored)" : ""); + if(e->detail != NotifyInferior) + if(e->serial != ignoreenter && (f->area->floating || !f->collapsed)) + if(!(c->w.ewmh.type & TypeSplash)) + focus(f->client, false); + } + mouse_checkresize(f, Pt(e->x, e->y), false); +} + +static void +expose_event(Window *w, XExposeEvent *e) { + Client *c; + + USED(e); + + c = w->aux; + if(c->sel) + frame_draw(c->sel); + else + fprint(2, "Badness: Expose event on a client frame which shouldn't be visible: %C\n", + c); +} + +static void +motion_event(Window *w, XMotionEvent *e) { + Client *c; + + c = w->aux; + mouse_checkresize(c->sel, Pt(e->x, e->y), false); +} + +Handlers framehandler = { + .bup = bup_event, + .bdown = bdown_event, + .config = config_event, + .enter = enter_event, + .expose = expose_event, + .motion = motion_event, +}; + +WinHints +frame_gethints(Frame *f) { + WinHints h; + Client *c; + Rectangle r; + Point d; + int minh; + + minh = labelh(def.font); + + c = f->client; + h = *c->w.hints; + + r = frame_client2rect(c, ZR, f->area->floating); + d = subpt(r.max, r.min); + + if(!f->area->floating && def.incmode == IIgnore) + h.inc = Pt(1, 1); + + if(h.min.x < 2*minh) + h.min.x = minh + (2*minh) % h.inc.x; + if(h.min.y < minh) + h.min.y = minh + minh % h.inc.y; + + h.min.x += d.x; + h.min.y += d.y; + /* Guard against overflow. */ + h.max.x = max(h.max.x + d.x, h.max.x); + h.max.y = max(h.max.y + d.y, h.max.y); + + h.base.x += d.x; + h.base.y += d.y; + h.baspect.x += d.x; + h.baspect.y += d.y; + + h.group = 0; + h.grav = ZP; + h.gravstatic = 0; + h.position = 0; + return h; +} + +#define ADJ(PE, ME) \ + if(c->fullscreen >= 0) \ + return r; \ + \ + if(!floating) { \ + r.min.x PE 1; \ + r.min.y PE labelh(def.font); \ + r.max.x ME 1; \ + r.max.y ME 1; \ + }else { \ + if(!c->borderless) { \ + r.min.x PE def.border; \ + r.max.x ME def.border; \ + r.max.y ME def.border; \ + } \ + if(!c->titleless) \ + r.min.y PE labelh(def.font); \ + } \ + +Rectangle +frame_rect2client(Client *c, Rectangle r, bool floating) { + + ADJ(+=, -=) + + /* Force clients to be at least 1x1 */ + r.max.x = max(r.max.x, r.min.x+1); + r.max.y = max(r.max.y, r.min.y+1); + return r; +} + +Rectangle +frame_client2rect(Client *c, Rectangle r, bool floating) { + + ADJ(-=, +=) + + return r; +} + +#undef ADJ + +void +frame_resize(Frame *f, Rectangle r) { + Client *c; + Rectangle fr, cr; + int collapsed, dx; + + if(btassert("8 full", Dx(r) <= 0 || Dy(r) < 0 + || Dy(r) == 0 && (!f->area->max || resizing) + && !f->collapsed)) { + fprint(2, "Frame rect: %R\n", r); + r.max.x = min(r.min.x+1, r.max.x); + r.max.y = min(r.min.y+1, r.max.y); + } + + c = f->client; + if(c->fullscreen >= 0) { + f->r = screens[c->fullscreen]->r; + f->crect = rectsetorigin(f->r, ZP); + return; + } + + /* + if(f->area->floating) + f->collapsed = false; + */ + + fr = frame_hints(f, r, get_sticky(f->r, r)); + if(f->area->floating && !c->strut) + fr = constrain(fr, -1); + + /* Collapse managed frames which are too small */ + /* XXX. */ + collapsed = f->collapsed; + if(!f->area->floating && f->area->mode == Coldefault) { + f->collapsed = false; + if(Dy(r) < 2 * labelh(def.font)) + f->collapsed = true; + } + if(collapsed != f->collapsed) + ewmh_updatestate(c); + + fr.max.x = max(fr.max.x, fr.min.x + 2*labelh(def.font)); + if(f->collapsed && f->area->floating) + fr.max.y = fr.min.y + labelh(def.font); + + cr = frame_rect2client(c, fr, f->area->floating); + if(f->area->floating) + f->r = fr; + else { + f->r = r; + dx = Dx(r) - Dx(cr); + dx -= 2 * (cr.min.x - fr.min.x); + cr.min.x += dx / 2; + cr.max.x += dx / 2; + } + f->crect = rectsubpt(cr, f->r.min); + + if(f->area->floating && !f->collapsed) + f->floatr = f->r; +} + +static void +pushlabel(Image *img, Rectangle *rp, char *s, CTuple *col) { + Rectangle r; + int w; + + w = textwidth(def.font, s) + def.font->height; + w = min(w, Dx(*rp) - 30); /* Magic number. */ + if(w > 0) { + r = *rp; + rp->max.x -= w; + if(0) + drawline(img, Pt(rp->max.x, r.min.y+2), + Pt(rp->max.x, r.max.y-2), + CapButt, 1, col->border); + drawstring(img, def.font, r, East, + s, col->fg); + } +} + +void +frame_draw(Frame *f) { + Rectangle r, fr; + Client *c; + CTuple *col; + Image *img; + char *s; + uint w; + int n, m; + + if(f->view != selview) + return; + if(f->area == nil) /* Blech. */ + return; + + c = f->client; + img = *c->ibuf; + fr = rectsetorigin(c->framewin->r, ZP); + + /* Pick colors. */ + if(c == selclient() || c == disp.focus) + col = &def.focuscolor; + else + col = &def.normcolor; + + /* Background/border */ + r = fr; + fill(img, r, col->bg); + border(img, r, 1, col->border); + + /* Title border */ + r.max.y = r.min.y + labelh(def.font); + border(img, r, 1, col->border); + + f->titlebar = insetrect(r, 3); + f->titlebar.max.y += 3; + + /* Odd focus. Unselected, with keyboard focus. */ + /* Draw a border just inside the titlebar. */ + if(c != selclient() && c == disp.focus) { + border(img, insetrect(r, 1), 1, def.normcolor.bg); + border(img, insetrect(r, 2), 1, def.focuscolor.border); + } + + /* grabbox */ + r.min = Pt(2, 2); + r.max.y -= 2; + r.max.x = r.min.x + Dy(r); + f->grabbox = r; + + if(c->urgent) + fill(img, r, col->fg); + border(img, r, 1, col->border); + + /* Odd focus. Selected, without keyboard focus. */ + /* Draw a border around the grabbox. */ + if(c != disp.focus && col == &def.focuscolor) + border(img, insetrect(r, -1), 1, def.normcolor.bg); + + /* Draw a border on borderless+titleless selected apps. */ + if(f->area->floating && c->borderless && c->titleless && !c->fullscreen && c == selclient()) + setborder(c->framewin, def.border, def.focuscolor.border); + else + setborder(c->framewin, 0, def.focuscolor.border); + + /* Label */ + r.min.x = r.max.x; + r.max.x = fr.max.x; + r.min.y = 0; + r.max.y = labelh(def.font); + /* Draw count on frames in 'max' columns. */ + if(f->area->max && !resizing) { + /* XXX */ + n = stack_count(f, &m); + s = smprint("%d/%d", m, n); + pushlabel(img, &r, s, col); + free(s); + } + /* Label clients with extra tags. */ + if((s = client_extratags(c))) { + pushlabel(img, &r, s, col); + free(s); + }else /* Make sure floating clients have room for their indicators. */ + if(c->floating) + r.max.x -= Dx(f->grabbox); + w = drawstring(img, def.font, r, West, + c->name, col->fg); + + /* Draw inner border on floating clients. */ + if(f->area->floating) { + r.min.x = r.min.x + w + 10; + r.max.x += Dx(f->grabbox) - 2; + r.min.y = f->grabbox.min.y; + r.max.y = f->grabbox.max.y; + border(img, r, 1, col->border); + } + + /* Border increment gaps... */ + r.min.y = f->crect.min.y; + r.min.x = max(1, f->crect.min.x - 1); + r.max.x = min(fr.max.x - 1, f->crect.max.x + 1); + r.max.y = min(fr.max.y - 1, f->crect.max.y + 1); + border(img, r, 1, col->border); + + /* Why? Because some non-ICCCM-compliant apps feel the need to + * change the background properties of all of their ancestor windows + * in order to implement pseudo-transparency. + * What's more, the designers of X11 felt that it would be unfair to + * implementers to make it possible to detect, or forbid, such changes. + */ + XSetWindowBackgroundPixmap(display, c->framewin->xid, None); + + copyimage(c->framewin, fr, img, ZP); +} + +void +frame_draw_all(void) { + Client *c; + + for(c=client; c; c=c->next) + if(c->sel && c->sel->view == selview) + frame_draw(c->sel); +} + +void +frame_swap(Frame *fa, Frame *fb) { + Frame **fp; + Client *c; + + if(fa == fb) return; + + for(fp = &fa->client->frame; *fp; fp = &fp[0]->cnext) + if(*fp == fa) break; + fp[0] = fp[0]->cnext; + + for(fp = &fb->client->frame; *fp; fp = &fp[0]->cnext) + if(*fp == fb) break; + fp[0] = fp[0]->cnext; + + c = fa->client; + fa->client = fb->client; + fb->client = c; + fb->cnext = c->frame; + c->frame = fb; + + c = fa->client; + fa->cnext = c->frame; + c->frame = fa; + + if(c->sel) + view_update(c->sel->view); +} + +void +move_focus(Frame *old_f, Frame *f) { + int noinput; + + noinput = (old_f && old_f->client->noinput) || + (f && f->client->noinput) || + disp.hasgrab != &c_root; + if(noinput) { + if(old_f) + frame_draw(old_f); + if(f) + frame_draw(f); + } +} + +void +frame_focus(Frame *f) { + Frame *old_f, *ff; + View *v; + Area *a, *old_a; + + v = f->view; + a = f->area; + old_a = v->sel; + + if(0 && f->collapsed) { + for(ff=f; ff->collapsed && ff->anext; ff=ff->anext) + ; + for(; ff->collapsed && ff->aprev; ff=ff->aprev) + ; + /* XXX */ + f->colr.max.y = f->colr.min.y + Dy(ff->colr); + ff->colr.max.y = ff->colr.min.y + labelh(def.font); + }else if(f->area->mode == Coldefault) { + for(; f->collapsed && f->anext; f=f->anext) + ; + for(; f->collapsed && f->aprev; f=f->aprev) + ; + } + + old_f = old_a->sel; + a->sel = f; + + if(a != old_a) + area_focus(f->area); + if(old_a != v->oldsel && f != old_f) + v->oldsel = nil; + + if(v != selview || a != v->sel || resizing) + return; + + move_focus(old_f, f); + if(a->floating) + float_arrange(a); + client_focus(f->client); + + /* + if(!a->floating && ((a->mode == Colstack) || (a->mode == Colmax))) + */ + column_arrange(a, false); +} + +int +frame_delta_h(void) { + return def.border + labelh(def.font); +} + +Rectangle +constrain(Rectangle r, int inset) { + WMScreen **sp; + WMScreen *s, *sbest; + Rectangle isect; + Point p; + int best, n; + + if(inset < 0) + inset = Dy(screen->brect); + /* + * FIXME: This will cause problems for windows with + * D(r) < 2 * inset + */ + + SET(best); + sbest = nil; + for(sp=screens; (s = *sp); sp++) { + if (!screen->showing) + continue; + isect = rect_intersection(r, insetrect(s->r, inset)); + if(Dx(isect) >= 0 && Dy(isect) >= 0) + return r; + if(Dx(isect) <= 0 && Dy(isect) <= 0) + n = max(Dx(isect), Dy(isect)); + else + n = min(Dx(isect), Dy(isect)); + if(!sbest || n > best) { + sbest = s; + best = n; + } + } + + isect = insetrect(sbest->r, inset); + p = ZP; + p.x -= min(r.max.x - isect.min.x, 0); + p.x -= max(r.min.x - isect.max.x, 0); + p.y -= min(r.max.y - isect.min.y, 0); + p.y -= max(r.min.y - isect.max.y, 0); + return rectaddpt(r, p); +} + diff --git a/cmd/wmii/fs.c b/cmd/wmii/fs.c new file mode 100644 index 0000000..5fdfca4 --- /dev/null +++ b/cmd/wmii/fs.c @@ -0,0 +1,725 @@ +/* Copyright ©2006-2010 Kris Maglione <maglione.k at Gmail> + * See LICENSE file for license details. + */ +#include "dat.h" +#include <ctype.h> +#include <stdarg.h> +#include <time.h> +#include <unistd.h> +#include "fns.h" + +typedef union IxpFileIdU IxpFileIdU; +union IxpFileIdU { + Bar* bar; + Bar** bar_p; + CTuple* col; + Client* client; + Ruleset* rule; + View* view; + char* buf; + void* ref; +}; + +#include <ixp_srvutil.h> + +static IxpPending events; +static IxpPending pdebug[NDebugOpt]; + +/* Constants */ +enum { /* Dirs */ + FsDBars, + FsDClient, + FsDClients, + FsDDebug, + FsDTag, + FsDTags, + FsRoot, + /* Files */ + FsFBar, + FsFCctl, + FsFClabel, + FsFColRules, + FsFCtags, + FsFDebug, + FsFEvent, + FsFKeys, + FsFRctl, + FsFTagRules, + FsFTctl, + FsFTindex, + FsFprops, +}; + +/* Error messages */ +static char + Enoperm[] = "permission denied", + Enofile[] = "file not found", + Ebadvalue[] = "bad value", + Einterrupted[] = "interrupted"; + +/* Macros */ +#define QID(t, i) (((vlong)((t)&0xFF)<<32)|((i)&0xFFFFFFFF)) + +/* Global Vars */ +/***************/ +Ixp9Srv p9srv = { + .open= fs_open, + .walk= fs_walk, + .read= fs_read, + .stat= fs_stat, + .write= fs_write, + .clunk= fs_clunk, + .flush= fs_flush, + .attach=fs_attach, + .create=fs_create, + .remove=fs_remove, + .freefid=fs_freefid +}; + +/* ad-hoc file tree. Empty names ("") indicate dynamic entries to be filled + * in by lookup_file + */ +static IxpDirtab +dirtab_root[]= {{".", QTDIR, FsRoot, 0500|DMDIR }, + {"rbar", QTDIR, FsDBars, 0700|DMDIR }, + {"lbar", QTDIR, FsDBars, 0700|DMDIR }, + {"debug", QTDIR, FsDDebug, 0500|DMDIR, FLHide }, + {"client", QTDIR, FsDClients, 0500|DMDIR }, + {"tag", QTDIR, FsDTags, 0500|DMDIR }, + {"ctl", QTAPPEND, FsFRctl, 0600|DMAPPEND }, + {"colrules", QTFILE, FsFColRules, 0600 }, + {"event", QTFILE, FsFEvent, 0600 }, + {"keys", QTFILE, FsFKeys, 0600 }, + {"tagrules", QTFILE, FsFTagRules, 0600 }, + {nil}}, +dirtab_clients[]={{".", QTDIR, FsDClients, 0500|DMDIR }, + {"", QTDIR, FsDClient, 0500|DMDIR }, + {nil}}, +dirtab_client[]= {{".", QTDIR, FsDClient, 0500|DMDIR }, + {"ctl", QTAPPEND, FsFCctl, 0600|DMAPPEND }, + {"label", QTFILE, FsFClabel, 0600 }, + {"tags", QTFILE, FsFCtags, 0600 }, + {"props", QTFILE, FsFprops, 0400 }, + {nil}}, +dirtab_debug[]= {{".", QTDIR, FsDDebug, 0500|DMDIR, FLHide }, + {"", QTFILE, FsFDebug, 0400 }, + {nil}}, +dirtab_bars[]= {{".", QTDIR, FsDBars, 0700|DMDIR }, + {"", QTFILE, FsFBar, 0600 }, + {nil}}, +dirtab_tags[]= {{".", QTDIR, FsDTags, 0500|DMDIR }, + {"", QTDIR, FsDTag, 0500|DMDIR }, + {nil}}, +dirtab_tag[]= {{".", QTDIR, FsDTag, 0500|DMDIR }, + {"ctl", QTAPPEND, FsFTctl, 0600|DMAPPEND }, + {"index", QTFILE, FsFTindex, 0400 }, + {nil}}; +static IxpDirtab* dirtab[] = { + [FsRoot] = dirtab_root, + [FsDBars] = dirtab_bars, + [FsDClients] = dirtab_clients, + [FsDClient] = dirtab_client, + [FsDDebug] = dirtab_debug, + [FsDTags] = dirtab_tags, + [FsDTag] = dirtab_tag, +}; +typedef char* (*MsgFunc)(void*, IxpMsg*); + +void +event(const char *format, ...) { + va_list ap; + + va_start(ap, format); + vsnprint(buffer, sizeof buffer, format, ap); + va_end(ap); + + ixp_pending_write(&events, buffer, strlen(buffer)); +} + +static int dflags; + +bool +setdebug(int flag) { + dflags = flag; + return true; +} + +void +vdebug(int flag, const char *fmt, va_list ap) { + char *s; + + if(flag == 0) + flag = dflags; + + if(!((debugflag|debugfile) & flag)) + return; + + s = vsmprint(fmt, ap); + dwrite(flag, s, strlen(s), false); + free(s); +} + +void +debug(int flag, const char *fmt, ...) { + va_list ap; + + va_start(ap, fmt); + vdebug(flag, fmt, ap); + va_end(ap); +} + +void +dprint(const char *fmt, ...) { + va_list ap; + + va_start(ap, fmt); + vdebug(0, fmt, ap); + va_end(ap); +} + +void +dwrite(int flag, void *buf, int n, bool always) { + int i; + + if(flag == 0) + flag = dflags; + + if(always || debugflag&flag) + write(2, buf, n); + + if(debugfile&flag) + for(i=0; i < nelem(pdebug); i++) + if(flag & (1<<i)) + ixp_pending_write(pdebug+i, buf, n); +} + +static uint fs_size(IxpFileId*); + +static void +dostat(Stat *s, IxpFileId *f) { + s->type = 0; + s->dev = 0; + s->qid.path = QID(f->tab.type, f->id); + s->qid.version = 0; + s->qid.type = f->tab.qtype; + s->mode = f->tab.perm; + s->atime = time(nil); + s->mtime = s->atime; + s->length = fs_size(f);; + s->name = f->tab.name; + s->uid = user; + s->gid = user; + s->muid = user; +} + +/* + * All lookups and directory organization should be performed through + * lookup_file, mostly through the dirtab[] tree. + */ +static IxpFileId* +lookup_file(IxpFileId *parent, char *name) +{ + IxpFileId *ret, *file, **last; + IxpDirtab *dir; + Client *c; + View *v; + Bar *b; + uint id; + int i; + + + if(!(parent->tab.perm & DMDIR)) + return nil; + dir = dirtab[parent->tab.type]; + last = &ret; + ret = nil; + for(; dir->name; dir++) { +# define push_file(nam) \ + file = ixp_srv_getfile(); \ + *last = file; \ + last = &file->next; \ + file->tab = *dir; \ + file->tab.name = estrdup(nam) + /* Dynamic dirs */ + if(dir->name[0] == '\0') { + switch(parent->tab.type) { + case FsDClients: + if(!name || !strcmp(name, "sel")) { + if((c = selclient())) { + push_file("sel"); + file->volatil = true; + file->p.client = c; + file->id = c->w.xid; + file->index = c->w.xid; + } + if(name) + goto LastItem; + } + SET(id); + if(name) { + id = (uint)strtol(name, &name, 16); + if(*name) + goto NextItem; + } + for(c=client; c; c=c->next) { + if(!name || c->w.xid == id) { + push_file(sxprint("%C", c)); + file->volatil = true; + file->p.client = c; + file->id = c->w.xid; + file->index = c->w.xid; + assert(file->tab.name); + if(name) + goto LastItem; + } + } + break; + case FsDDebug: + for(i=0; i < nelem(pdebug); i++) + if(!name || !strcmp(name, debugtab[i])) { + push_file(debugtab[i]); + file->id = i; + if(name) + goto LastItem; + } + break; + case FsDTags: + if(!name || !strcmp(name, "sel")) { + if(selview) { + push_file("sel"); + file->volatil = true; + file->p.view = selview; + file->id = selview->id; + } + if(name) + goto LastItem; + } + for(v=view; v; v=v->next) { + if(!name || !strcmp(name, v->name)) { + push_file(v->name); + file->volatil = true; + file->p.view = v; + file->id = v->id; + if(name) + goto LastItem; + } + } + break; + case FsDBars: + for(b=*parent->p.bar_p; b; b=b->next) { + if(!name || !strcmp(name, b->name)) { + push_file(b->name); + file->volatil = true; + file->p.bar = b; + file->id = b->id; + if(name) + goto LastItem; + } + } + break; + } + }else /* Static dirs */ + if(!name && !(dir->flags & FLHide) || name && !strcmp(name, dir->name)) { + push_file(file->tab.name); + file->id = 0; + file->p.ref = parent->p.ref; + file->index = parent->index; + /* Special considerations: */ + switch(file->tab.type) { + case FsDBars: + if(!strcmp(file->tab.name, "lbar")) + file->p.bar_p = &screen[0].bar[BLeft]; + else + file->p.bar_p = &screen[0].bar[BRight]; + file->id = (int)(uintptr_t)file->p.bar_p; + break; + case FsFColRules: + file->p.rule = &def.colrules; + break; + case FsFTagRules: + file->p.rule = &def.tagrules; + break; + } + if(name) + goto LastItem; + } + NextItem: + continue; +# undef push_file + } +LastItem: + *last = nil; + return ret; +} + +/* Service Functions */ +void +fs_attach(Ixp9Req *r) { + IxpFileId *f; + + f = ixp_srv_getfile(); + f->tab = dirtab[FsRoot][0]; + f->tab.name = estrdup("/"); + r->fid->aux = f; + r->fid->qid.type = f->tab.qtype; + r->fid->qid.path = QID(f->tab.type, 0); + r->ofcall.rattach.qid = r->fid->qid; + respond(r, nil); +} + +void +fs_walk(Ixp9Req *r) { + + ixp_srv_walkandclone(r, lookup_file); +} + +static uint +fs_size(IxpFileId *f) { + switch(f->tab.type) { + default: + return 0; + case FsFColRules: + case FsFTagRules: + return f->p.rule->size; + case FsFKeys: + return def.keyssz; + case FsFCtags: + return strlen(f->p.client->tags); + case FsFClabel: + return strlen(f->p.client->name); + case FsFprops: + return strlen(f->p.client->props); + } +} + +void +fs_stat(Ixp9Req *r) { + IxpMsg m; + Stat s; + int size; + char *buf; + IxpFileId *f; + + f = r->fid->aux; + + if(!ixp_srv_verifyfile(f, lookup_file)) { + respond(r, Enofile); + return; + } + + dostat(&s, f); + size = ixp_sizeof_stat(&s); + r->ofcall.rstat.nstat = size; + buf = emallocz(size); + + m = ixp_message(buf, size, MsgPack); + ixp_pstat(&m, &s); + + r->ofcall.rstat.stat = (uchar*)m.data; + respond(r, nil); +} + +void +fs_read(Ixp9Req *r) { + char *buf; + IxpFileId *f; + int n; + + f = r->fid->aux; + + if(!ixp_srv_verifyfile(f, lookup_file)) { + respond(r, Enofile); + return; + } + + if(f->tab.perm & DMDIR && f->tab.perm & 0400) { + ixp_srv_readdir(r, lookup_file, dostat); + return; + } + else{ + if(f->pending) { + ixp_pending_respond(r); + return; + } + switch(f->tab.type) { + case FsFprops: + ixp_srv_readbuf(r, f->p.client->props, strlen(f->p.client->props)); + respond(r, nil); + return; + case FsFColRules: + case FsFTagRules: + ixp_srv_readbuf(r, f->p.rule->string, f->p.rule->size); + respond(r, nil); + return; + case FsFKeys: + ixp_srv_readbuf(r, def.keys, def.keyssz); + respond(r, nil); + return; + case FsFCtags: + ixp_srv_readbuf(r, f->p.client->tags, strlen(f->p.client->tags)); + respond(r, nil); + return; + case FsFClabel: + ixp_srv_readbuf(r, f->p.client->name, strlen(f->p.client->name)); + respond(r, nil); + return; + case FsFBar: + ixp_srv_readbuf(r, f->p.bar->buf, strlen(f->p.bar->buf)); + respond(r, nil); + return; + case FsFRctl: + buf = readctl_root(); + ixp_srv_readbuf(r, buf, strlen(buf)); + respond(r, nil); + return; + case FsFCctl: + buf = readctl_client(f->p.client); + ixp_srv_readbuf(r, buf, strlen(buf)); + respond(r, nil); + return; + case FsFTindex: + buf = view_index(f->p.view); + ixp_srv_readbuf(r, buf, strlen(buf)); + respond(r, nil); + return; + case FsFTctl: + buf = readctl_view(f->p.view); + n = strlen(buf); + ixp_srv_readbuf(r, buf, n); + respond(r, nil); + return; + } + } + /* This should not be called if the file is not open for reading. */ + die("Read called on an unreadable file"); +} + +void +fs_write(Ixp9Req *r) { + MsgFunc mf; + IxpFileId *f; + char *errstr; + char *p; + uint i; + + if(r->ifcall.io.count == 0) { + respond(r, nil); + return; + } + f = r->fid->aux; + + if(!ixp_srv_verifyfile(f, lookup_file)) { + respond(r, Enofile); + return; + } + + switch(f->tab.type) { + case FsFColRules: + case FsFTagRules: + ixp_srv_writebuf(r, &f->p.rule->string, &f->p.rule->size, 0); + respond(r, nil); + return; + case FsFKeys: + ixp_srv_writebuf(r, &def.keys, &def.keyssz, 0); + respond(r, nil); + return; + case FsFClabel: + ixp_srv_data2cstring(r); + utfecpy(f->p.client->name, + f->p.client->name+sizeof(client->name), + r->ifcall.io.data); + frame_draw(f->p.client->sel); + update_class(f->p.client); + r->ofcall.io.count = r->ifcall.io.count; + respond(r, nil); + return; + case FsFCtags: + ixp_srv_data2cstring(r); + client_applytags(f->p.client, r->ifcall.io.data); + r->ofcall.io.count = r->ifcall.io.count; + respond(r, nil); + return; + case FsFBar: + i = strlen(f->p.bar->buf); + p = f->p.bar->buf; + ixp_srv_writebuf(r, &p, &i, 279); + bar_load(f->p.bar); + r->ofcall.io.count = i - r->ifcall.io.offset; + respond(r, nil); + return; + case FsFCctl: + mf = (MsgFunc)message_client; + goto msg; + case FsFTctl: + mf = (MsgFunc)message_view; + goto msg; + case FsFRctl: + mf = (MsgFunc)message_root; + goto msg; + msg: + errstr = ixp_srv_writectl(r, mf); + r->ofcall.io.count = r->ifcall.io.count; + respond(r, errstr); + return; + case FsFEvent: + if(r->ifcall.io.data[r->ifcall.io.count-1] == '\n') + event("%.*s", (int)r->ifcall.io.count, r->ifcall.io.data); + else + event("%.*s\n", (int)r->ifcall.io.count, r->ifcall.io.data); + r->ofcall.io.count = r->ifcall.io.count; + respond(r, nil); + return; + } + /* + /* This should not be called if the file is not open for writing. */ + die("Write called on an unwritable file"); +} + +void +fs_open(Ixp9Req *r) { + IxpFileId *f; + + f = r->fid->aux; + + if(!ixp_srv_verifyfile(f, lookup_file)) { + respond(r, Enofile); + return; + } + + switch(f->tab.type) { + case FsFEvent: + ixp_pending_pushfid(&events, r->fid); + break; + case FsFDebug: + ixp_pending_pushfid(pdebug+f->id, r->fid); + debugfile |= 1<<f->id; + break; + } + + if((r->ifcall.topen.mode&3) == OEXEC + || (r->ifcall.topen.mode&3) != OREAD && !(f->tab.perm & 0200) + || (r->ifcall.topen.mode&3) != OWRITE && !(f->tab.perm & 0400) + || (r->ifcall.topen.mode & ~(3|OAPPEND|OTRUNC))) + respond(r, Enoperm); + else + respond(r, nil); +} + +void +fs_create(Ixp9Req *r) { + IxpFileId *f; + + f = r->fid->aux; + + switch(f->tab.type) { + default: + respond(r, Enoperm); + return; + case FsDBars: + if(!strlen(r->ifcall.tcreate.name)) { + respond(r, Ebadvalue); + return; + } + bar_create(f->p.bar_p, r->ifcall.tcreate.name); + f = lookup_file(f, r->ifcall.tcreate.name); + if(!f) { + respond(r, Enofile); + return; + } + r->ofcall.ropen.qid.type = f->tab.qtype; + r->ofcall.ropen.qid.path = QID(f->tab.type, f->id); + f->next = r->fid->aux; + r->fid->aux = f; + respond(r, nil); + break; + } +} + +void +fs_remove(Ixp9Req *r) { + IxpFileId *f; + WMScreen *s; + + f = r->fid->aux; + if(!ixp_srv_verifyfile(f, lookup_file)) { + respond(r, Enofile); + return; + } + + + switch(f->tab.type) { + default: + respond(r, Enoperm); + return; + case FsFBar: + s = f->p.bar->screen; + bar_destroy(f->next->p.bar_p, f->p.bar); + bar_draw(s); + respond(r, nil); + break; + } +} + +void +fs_clunk(Ixp9Req *r) { + IxpFileId *f; + + f = r->fid->aux; + if(!ixp_srv_verifyfile(f, lookup_file)) { + respond(r, nil); + return; + } + + if(f->pending) { + /* Should probably be in freefid */ + if(ixp_pending_clunk(r)) { + if(f->tab.type == FsFDebug) + debugfile &= ~(1<<f->id); + } + return; + } + + switch(f->tab.type) { + case FsFColRules: + update_rules(&f->p.rule->rule, f->p.rule->string); + break; + case FsFTagRules: + update_rules(&f->p.rule->rule, f->p.rule->string); + /* + for(c=client; c; c=c->next) + apply_rules(c); + view_update_all(); + */ + break; + case FsFKeys: + update_keys(); + break; + } + respond(r, nil); +} + +void +fs_flush(Ixp9Req *r) { + Ixp9Req *or; + IxpFileId *f; + + or = r->oldreq; + f = or->fid->aux; + if(f->pending) + ixp_pending_flush(r); + /* else die() ? */ + respond(r->oldreq, Einterrupted); + respond(r, nil); +} + +void +fs_freefid(Fid *f) { + IxpFileId *id, *tid; + + tid = f->aux; + while((id = tid)) { + tid = id->next; + ixp_srv_freefile(id); + } +} + diff --git a/cmd/wmii/geom.c b/cmd/wmii/geom.c new file mode 100644 index 0000000..464eb67 --- /dev/null +++ b/cmd/wmii/geom.c @@ -0,0 +1,94 @@ +/* Copyright ©2006-2010 Kris Maglione <maglione.k at Gmail> + * See LICENSE file for license details. + */ +#include "dat.h" +#include "fns.h" + +bool +rect_haspoint_p(Point pt, Rectangle r) { + return (pt.x >= r.min.x) && (pt.x < r.max.x) + && (pt.y >= r.min.y) && (pt.y < r.max.y); +} + +bool +rect_intersect_p(Rectangle r, Rectangle r2) { + return r.min.x <= r2.max.x + && r.max.x >= r2.min.x + && r.min.y <= r2.max.y + && r.max.y >= r2.min.y; +} + +Rectangle +rect_intersection(Rectangle r, Rectangle r2) { + Rectangle ret; + + /* ret != canonrect(ret) ≡ no intersection. */ + ret.min.x = max(r.min.x, r2.min.x); + ret.max.x = min(r.max.x, r2.max.x); + ret.min.y = max(r.min.y, r2.min.y); + ret.max.y = min(r.max.y, r2.max.y); + return ret; +} + +bool +rect_contains_p(Rectangle r, Rectangle r2) { + return r2.min.x >= r.min.x + && r2.max.x <= r.max.x + && r2.min.y >= r.min.y + && r2.max.y <= r.max.y; +} + +Align +quadrant(Rectangle r, Point pt) { + Align ret; + + pt = subpt(pt, r.min); + ret = 0; + + if(pt.x >= Dx(r) * .5) + ret |= East; + if(pt.x <= Dx(r) * .5) + ret |= West; + if(pt.y <= Dy(r) * .5) + ret |= North; + if(pt.y >= Dy(r) * .5) + ret |= South; + + return ret; +} + +Cursor +quad_cursor(Align align) { + switch(align) { + case NEast: return cursor[CurNECorner]; + case NWest: return cursor[CurNWCorner]; + case SEast: return cursor[CurSECorner]; + case SWest: return cursor[CurSWCorner]; + case South: + case North: return cursor[CurDVArrow]; + case East: + case West: return cursor[CurDHArrow]; + default: return cursor[CurMove]; + } +} + +Align +get_sticky(Rectangle src, Rectangle dst) { + Align corner; + + corner = 0; + if(src.min.x != dst.min.x + && src.max.x == dst.max.x) + corner |= East; + else + corner |= West; + + if(src.min.y != dst.min.y + && src.max.y == dst.max.y) + corner |= South; + else + corner |= North; + + return corner; +} + diff --git a/cmd/wmii/key.c b/cmd/wmii/key.c new file mode 100644 index 0000000..f2c3471 --- /dev/null +++ b/cmd/wmii/key.c @@ -0,0 +1,244 @@ +/* Copyright ©2006-2010 Kris Maglione <fbsdaemon at Gmail> + * Copyright ©2004-2006 Anselm R. Garbe <garbeam at gmail dot com> + * See LICENSE file for license details. + */ +#include "dat.h" +#include <X11/keysym.h> +#include "fns.h" + +void +init_lock_keys(void) { + static int masks[] = { + ShiftMask, LockMask, ControlMask, Mod1Mask, Mod2Mask, + Mod3Mask, Mod4Mask, Mod5Mask + }; + XModifierKeymap *modmap; + KeyCode numlock; + int i, max; + + numlock_mask = 0; + modmap = XGetModifierMapping(display); + numlock = keycode("Num_Lock"); + if(numlock) + if(modmap && modmap->max_keypermod > 0) { + max = nelem(masks) * modmap->max_keypermod; + for(i = 0; i < max; i++) + if(modmap->modifiermap[i] == numlock) + numlock_mask = masks[i / modmap->max_keypermod]; + } + XFreeModifiermap(modmap); + valid_mask = 255 & ~(numlock_mask | LockMask); +} + +static void +freekey(Key *k) { + Key *n; + + while((n = k)) { + k = k->next; + free(n); + } +} + +static void +_grab(XWindow w, int keycode, uint mod) { + XGrabKey(display, keycode, mod, w, + true, GrabModeAsync, GrabModeAsync); +} + +static void +grabkey(Key *k) { + _grab(scr.root.xid, k->key, k->mod); + _grab(scr.root.xid, k->key, k->mod | LockMask); + if(numlock_mask) { + _grab(scr.root.xid, k->key, k->mod | numlock_mask); + _grab(scr.root.xid, k->key, k->mod | numlock_mask | LockMask); + } +} + +static void +ungrabkey(Key *k) { + XUngrabKey(display, k->key, k->mod, scr.root.xid); + XUngrabKey(display, k->key, k->mod | LockMask, scr.root.xid); + if(numlock_mask) { + XUngrabKey(display, k->key, k->mod | numlock_mask, scr.root.xid); + XUngrabKey(display, k->key, k->mod | numlock_mask | LockMask, scr.root.xid); + } +} + +static Key * +name2key(const char *name) { + Key *k; + + for(k=key; k; k=k->lnext) + if(!strncmp(k->name, name, sizeof k->name)) + return k; + return nil; +} + +static Key* +getkey(const char *name) { + Key *k, *r; + char buf[128]; + char *seq[8]; + char *kstr; + int mask; + uint i, toks; + static ushort id = 1; + + r = nil; + + if((k = name2key(name))) { + ungrabkey(k); + return k; + } + utflcpy(buf, name, sizeof buf); + toks = tokenize(seq, 8, buf, ','); + for(i = 0; i < toks; i++) { + if(!k) + r = k = emallocz(sizeof *k); + else { + k->next = emallocz(sizeof *k); + k = k->next; + } + utflcpy(k->name, name, sizeof k->name); + if(parsekey(seq[i], &mask, &kstr)) { + k->key = keycode(kstr); + k->mod = mask; + } + if(k->key == 0) { + freekey(r); + return nil; + } + } + if(r) { + r->id = id++; + r->lnext = key; + key = r; + } + + return r; +} + +static void +next_keystroke(ulong *mod, KeyCode *code) { + XEvent e; + KeySym sym; + *mod = 0; + + do { + XMaskEvent(display, KeyPressMask, &e); + *mod |= e.xkey.state & valid_mask; + *code = (KeyCode) e.xkey.keycode; + sym = XKeycodeToKeysym(display, e.xkey.keycode, 0); + } while(IsModifierKey(sym)); +} + +static void +fake_keypress(ulong mod, KeyCode key) { + XKeyEvent e; + Client *c; + + c = disp.focus; + if(c == nil || c->w.xid == 0) + return; + + e.time = CurrentTime; + e.window = c->w.xid; + e.display = display; + e.state = mod; + e.keycode = key; + + e.type = KeyPress; + sendevent(&c->w, true, KeyPressMask, (XEvent*)&e); + e.type = KeyRelease; + sendevent(&c->w, true, KeyReleaseMask, (XEvent*)&e); + + sync(); +} + +static Key * +match_keys(Key *k, ulong mod, KeyCode keycode, bool seq) { + Key *ret, *next; + volatile int i; /* shut up ken */ + + ret = nil; + for(next = k->tnext; k; i = (k=next) && (next=k->tnext)) { + if(seq) + k = k->next; + if(k && (k->mod == mod) && (k->key == keycode)) { + k->tnext = ret; + ret = k; + } + } + return ret; +} + +static void +kpress_seq(XWindow w, Key *done) { + ulong mod; + KeyCode key; + Key *found; + + next_keystroke(&mod, &key); + found = match_keys(done, mod, key, true); + if((done->mod == mod) && (done->key == key)) + fake_keypress(mod, key); /* double key */ + else { + if(!found) + XBell(display, 0); + else if(!found->tnext && !found->next) + event("Key %s\n", found->name); + else + kpress_seq(w, found); + } +} + +void +kpress(XWindow w, ulong mod, KeyCode keycode) { + Key *k, *found; + + for(k=key; k; k=k->lnext) + k->tnext = k->lnext; + + found = match_keys(key, mod, keycode, false); + if(!found) /* grabbed but not found */ + XBell(display, 0); + else if(!found->tnext && !found->next) + event("Key %s\n", found->name); + else { + XGrabKeyboard(display, w, true, GrabModeAsync, GrabModeAsync, CurrentTime); + flushevents(FocusChangeMask, true); + kpress_seq(w, found); + XUngrabKeyboard(display, CurrentTime); + } +} + +void +update_keys(void) { + Key *k; + char *l, *p; + + init_lock_keys(); + while((k = key)) { + key = key->lnext; + ungrabkey(k); + freekey(k); + } + for(l = p = def.keys; p && *p;) { + if(*p == '\n') { + *p = 0; + if((k = getkey(l))) + grabkey(k); + *p = '\n'; + l = ++p; + } + else + p++; + } + if(l < p && strlen(l)) { + if((k = getkey(l))) + grabkey(k); + } +} + diff --git a/cmd/wmii/layout.c b/cmd/wmii/layout.c new file mode 100644 index 0000000..eb70302 --- /dev/null +++ b/cmd/wmii/layout.c @@ -0,0 +1,608 @@ +/* Copyright ©2006-2010 Kris Maglione <maglione.k at Gmail> + * See LICENSE file for license details. + */ +#include "dat.h" +#include "fns.h" + +/* Here be dragons. */ +/* Actually, I'm happy to say, the dragons have dissipated. */ + +enum { + ButtonMask = + ButtonPressMask | ButtonReleaseMask, + MouseMask = + ButtonMask | PointerMotionMask +}; + +static Handlers handlers; + +enum { OHoriz, OVert }; +typedef struct Framewin Framewin; +struct Framewin { + /* Todo... give these better names. */ + Window* w; + Rectangle grabbox; + Frame* f; + Area* ra; + Point pt; + int orientation; + int xy; + int screen; +}; + +static Rectangle +framerect(Framewin *f) { + Rectangle r; + Point p; + int scrn; + + r.min = ZP; + if(f->orientation == OHoriz) { + r.max.x = f->xy; + r.max.y = f->grabbox.max.y + f->grabbox.min.y; + }else { + r.max.x = f->grabbox.max.x + f->grabbox.min.x; + r.max.y = f->xy; + r = rectsubpt(r, Pt(Dx(r)/2, 0)); + } + r = rectaddpt(r, f->pt); + + scrn = f->screen; + if (scrn == -1) + scrn = max(ownerscreen(f->f->r), 0); + + /* Keep onscreen */ + p = ZP; + p.x -= min(0, r.min.x); + p.x -= max(0, r.max.x - screens[scrn]->r.max.x); + p.y -= max(0, r.max.y - screens[scrn]->brect.min.y - Dy(r)/2); + return rectaddpt(r, p); +} + +static void +frameadjust(Framewin *f, Point pt, int orientation, int xy) { + f->orientation = orientation; + f->xy = xy; + f->pt = pt; +} + +static Framewin* +framewin(Frame *f, Point pt, int orientation, int n) { + WinAttr wa; + Framewin *fw; + + fw = emallocz(sizeof *fw); + wa.override_redirect = true; + wa.event_mask = ExposureMask; + fw->w = createwindow(&scr.root, Rect(0, 0, 1, 1), + scr.depth, InputOutput, + &wa, CWEventMask); + fw->w->aux = fw; + sethandler(fw->w, &handlers); + + fw->f = f; + fw->screen = f->area->screen; + fw->grabbox = f->grabbox; + frameadjust(fw, pt, orientation, n); + reshapewin(fw->w, framerect(fw)); + + mapwin(fw->w); + raisewin(fw->w); + + return fw; +} + +static void +framedestroy(Framewin *f) { + destroywindow(f->w); + free(f); +} + +static void +expose_event(Window *w, XExposeEvent *e) { + Rectangle r; + Framewin *f; + Image *buf; + CTuple *c; + + USED(e); + + f = w->aux; + c = &def.focuscolor; + buf = disp.ibuf; + + r = rectsubpt(w->r, w->r.min); + fill(buf, r, c->bg); + border(buf, r, 1, c->border); + border(buf, f->grabbox, 1, c->border); + border(buf, insetrect(f->grabbox, -f->grabbox.min.x), 1, c->border); + + copyimage(w, r, buf, ZP); +} + +static Handlers handlers = { + .expose = expose_event, +}; + +static Area* +find_area(Point pt) { + View *v; + Area *a; + int s; + + v = selview; + for(s=0; s < nscreens; s++) { + if(!rect_haspoint_p(pt, screens[s]->r)) + continue; + for(a=v->areas[s]; a; a=a->next) + if(pt.x < a->r.max.x) + return a; + } + return nil; +} + +static void +vplace(Framewin *fw, Point pt) { + Vector_long vec = {0}; + Rectangle r; + Frame *f; + Area *a; + View *v; + long l; + int hr; + + v = selview; + + a = find_area(pt); + if(a == nil) + return; + + fw->ra = a; + fw->screen = a->screen; + + pt.x = a->r.min.x; + frameadjust(fw, pt, OHoriz, Dx(a->r)); + + r = fw->w->r; + hr = Dy(r)/2; + pt.y -= hr; + + if(a->frame == nil) + goto done; + + vector_lpush(&vec, a->frame->r.min.y); + for(f=a->frame; f; f=f->anext) { + if(f == fw->f) + vector_lpush(&vec, f->r.min.y + 0*hr); + else if(f->collapsed) + vector_lpush(&vec, f->r.min.y + 1*hr); + else + vector_lpush(&vec, f->r.min.y + 2*hr); + if(!f->collapsed && f->anext != fw->f) + vector_lpush(&vec, f->r.max.y - 2*hr); + } + + for(int i=0; i < vec.n; i++) { + l = vec.ary[i]; + if(abs(pt.y - l) < hr) { + pt.y = l; + break; + } + } + vector_lfree(&vec); + +done: + pt.x = a->r.min.x; + frameadjust(fw, pt, OHoriz, Dx(a->r)); + reshapewin(fw->w, framerect(fw)); +} + +static void +hplace(Framewin *fw, Point pt) { + Area *a; + View *v; + int minw; + + v = selview; + + a = find_area(pt); + if(a == nil) + return; /* XXX: Multihead. */ + + fw->screen = a->screen; + fw->ra = nil; + minw = column_minwidth(); + if(abs(pt.x - a->r.min.x) < minw/2) { + pt.x = a->r.min.x; + fw->ra = a->prev; + } + else if(abs(pt.x - a->r.max.x) < minw/2) { + pt.x = a->r.max.x; + fw->ra = a; + } + + pt.y = a->r.min.y; + frameadjust(fw, pt, OVert, Dy(a->r)); + reshapewin(fw->w, framerect(fw)); +} + +static Point +grabboxcenter(Frame *f) { + Point p; + + p = addpt(f->r.min, f->grabbox.min); + p.x += Dx(f->grabbox)/2; + p.y += Dy(f->grabbox)/2; + return p; +} + +static int tvcol(Frame*); +static int thcol(Frame*); +static int tfloat(Frame*); + +enum { + TDone, + TVCol, + THCol, + TFloat, +}; + +static int (*tramp[])(Frame*) = { + 0, + tvcol, + thcol, + tfloat, +}; + +/* Trampoline to allow properly tail recursive move/resize routines. + * We could probably get away with plain tail calls, but I don't + * like the idea. + */ +static void +trampoline(int fn, Frame *f, bool grabbox) { + + while(fn > 0) { + resizing = fn != TFloat; + view_update(f->view); + if(grabbox) + warppointer(grabboxcenter(f)); + //f->collapsed = false; + fn = tramp[fn](f); + } + ungrabpointer(); + resizing = false; + view_update(f->view); +} + +void +mouse_movegrabbox(Client *c, bool grabmod) { + Frame *f; + Point p; + float x, y; + + f = c->sel; + + SET(x); + SET(y); + if(grabmod) { + p = querypointer(f->client->framewin); + x = (float)p.x / Dx(f->r); + y = (float)p.y / Dy(f->r); + } + + if(f->area->floating) + trampoline(TFloat, f, !grabmod); + else + trampoline(THCol, f, true); + + if(grabmod) + warppointer(addpt(f->r.min, Pt(x * Dx(f->r), + y * Dy(f->r)))); + else + warppointer(grabboxcenter(f)); +} + +static int +_openstack_down(Frame *f, int h) { + int ret; + int dy; + + if(f == nil) + return 0;; + ret = 0; + if(!f->collapsed) { + dy = Dy(f->colr) - labelh(def.font); + if(dy >= h) { + f->colr.min.y += h; + return h; + }else { + f->collapsed = true; + f->colr.min.y += dy; + ret = dy; + h -= dy; + } + } + dy = _openstack_down(f->anext, h); + f->colr.min.y += dy; + f->colr.max.y += dy; + return ret + dy; +} + +static int +_openstack_up(Frame *f, int h) { + int ret; + int dy; + + if(f == nil) + return 0; + ret = 0; + if(!f->collapsed) { + dy = Dy(f->colr) - labelh(def.font); + if(dy >= h) { + f->colr.max.y -= h; + return h; + }else { + f->collapsed = true; + f->colr.max.y -= dy; + ret = dy; + h -= dy; + } + } + dy = _openstack_up(f->aprev, h); + f->colr.min.y -= dy; + f->colr.max.y -= dy; + return ret + dy; +} + +static void +column_openstack(Area *a, Frame *f, int h) { + + if(f == nil) + _openstack_down(a->frame, h); + else { + h -= _openstack_down(f->anext, h); + if(h) + _openstack_up(f->aprev, h); + } +} + +static void +column_drop(Area *a, Frame *f, int y) { + Frame *ff; + int dy; + + for(ff=a->frame; ff; ff=ff->anext) + assert(ff != f); + + if(a->frame == nil || y <= a->frame->r.min.y) { + f->collapsed = true; + f->colr.min.y = 0; + f->colr.max.y = labelh(def.font); + column_openstack(a, nil, labelh(def.font)); + column_insert(a, f, nil); + return; + } + for(ff=a->frame; ff->anext; ff=ff->anext) + if(y <= ff->colr.max.y) break; + + y = max(y, ff->colr.min.y + labelh(def.font)); + y = min(y, ff->colr.max.y); + dy = ff->colr.max.y - y; + if(dy <= labelh(def.font)) { + f->collapsed = true; + f->colr.min.y = 0; + f->colr.max.y = labelh(def.font); + column_openstack(a, ff, labelh(def.font) - dy); + }else { + f->colr.min.y = y; + f->colr.max.y = ff->colr.max.y; + ff->colr.max.y = y; + } + column_insert(a, f, ff); +} + +static int +thcol(Frame *f) { + Framewin *fw; + Frame *fp, *fn; + Area *a; + Point pt, pt2; + uint button; + int ret, collapsed; + + focus(f->client, false); + + ret = TDone; + if(!grabpointer(&scr.root, nil, cursor[CurIcon], MouseMask)) + return TDone; + + pt = querypointer(&scr.root); + pt2.x = f->area->r.min.x; + pt2.y = pt.y; + fw = framewin(f, pt2, OHoriz, Dx(f->area->r)); + + vplace(fw, pt); + for(;;) + switch (readmouse(&pt, &button)) { + case MotionNotify: + vplace(fw, pt); + break; + case ButtonRelease: + if(button != 1) + continue; + SET(collapsed); + SET(fp); + SET(fn); + a = f->area; + if(a->floating) + area_detach(f); + else { + collapsed = f->collapsed; + fp = f->aprev; + fn = f->anext; + column_remove(f); + if(!f->collapsed) + if(fp) + fp->colr.max.y = f->colr.max.y; + else if(fn && fw->pt.y > fn->r.min.y) + fn->colr.min.y = f->colr.min.y; + } + + column_drop(fw->ra, f, fw->pt.y); + if(!a->floating && collapsed) { + /* XXX */ + for(; fn && fn->collapsed; fn=fn->anext) + ; + if(fn == nil) + for(fn=fp; fn && fn->collapsed; fn=fn->aprev) + ; + if(fp) + fp->colr.max.x += labelh(def.font); + } + + + if(!a->frame && !a->floating && a->view->areas[a->screen]->next) + area_destroy(a); + + frame_focus(f); + goto done; + case ButtonPress: + if(button == 2) + ret = TVCol; + else if(button == 3) + ret = TFloat; + else + continue; + goto done; + } +done: + framedestroy(fw); + return ret; +} + +static int +tvcol(Frame *f) { + Framewin *fw; + Window *cwin; + WinAttr wa; + Rectangle r; + Point pt, pt2; + uint button; + int ret, scrn; + + focus(f->client, false); + + pt = querypointer(&scr.root); + pt2.x = pt.x; + pt2.y = f->area->r.min.y; + + scrn = f->area->screen > -1 ? f->area->screen : find_area(pt) ? find_area(pt)->screen : 0; + r = f->view->r[scrn]; + fw = framewin(f, pt2, OVert, Dy(r)); + + r.min.y += fw->grabbox.min.y + Dy(fw->grabbox)/2; + r.max.y = r.min.y + 1; + cwin = createwindow(&scr.root, r, 0, InputOnly, &wa, 0); + mapwin(cwin); + + ret = TDone; + if(!grabpointer(&scr.root, cwin, cursor[CurIcon], MouseMask)) + goto done; + + hplace(fw, pt); + for(;;) + switch (readmouse(&pt, &button)) { + case MotionNotify: + hplace(fw, pt); + continue; + case ButtonPress: + if(button == 2) + ret = THCol; + else if(button == 3) + ret = TFloat; + else + continue; + goto done; + case ButtonRelease: + if(button != 1) + continue; + if(fw->ra) { + fw->ra = column_new(f->view, fw->ra, screen->idx, 0); + area_moveto(fw->ra, f); + } + goto done; + } + +done: + framedestroy(fw); + destroywindow(cwin); + return ret; +} + +static int +tfloat(Frame *f) { + Rectangle *rects; + Rectangle frect, origin; + Point pt, pt1; + Client *c; + Align align; + uint nrect, button; + int ret; + + c = f->client; + if(!f->area->floating) { + if(f->anext) + f->anext->colr.min.y = f->colr.min.y; + else if(f->aprev) + f->aprev->colr.max.y = f->colr.max.y; + area_moveto(f->view->floating, f); + } + map_frame(f->client); + focus(f->client, false); + + ret = TDone; + if(!grabpointer(c->framewin, nil, cursor[CurMove], MouseMask)) + return TDone; + + rects = view_rects(f->view, &nrect, f); + origin = f->r; + frect = f->r; + + pt = querypointer(&scr.root); + /* pt1 = grabboxcenter(f); */ + pt1 = pt; + goto case_motion; + +shut_up_ken: + for(;;pt1=pt) + switch (readmouse(&pt, &button)) { + default: goto shut_up_ken; + case MotionNotify: + case_motion: + origin = rectaddpt(origin, subpt(pt, pt1)); + origin = constrain(origin, -1); + frect = origin; + + align = Center; + snap_rect(rects, nrect, &frect, &align, def.snap); + + frect = frame_hints(f, frect, Center); + frect = constrain(frect, -1); + client_resize(c, frect); + continue; + case ButtonRelease: + if(button != 1) + continue; + goto done; + case ButtonPress: + if(button != 3) + continue; + unmap_frame(f->client); + ret = THCol; + goto done; + } +done: + free(rects); + return ret; +} + diff --git a/cmd/wmii/main.c b/cmd/wmii/main.c new file mode 100644 index 0000000..fba5d5f --- /dev/null +++ b/cmd/wmii/main.c @@ -0,0 +1,470 @@ +/* Copyright ©2004-2006 Anselm R. Garbe <garbeam at gmail dot com> + * Copyright ©2006-2010 Kris Maglione <maglione.k at Gmail> + * See LICENSE file for license details. + */ +#define EXTERN +#include "dat.h" +#include <X11/Xproto.h> +#include <X11/cursorfont.h> +#include <errno.h> +#include <fcntl.h> +#include <locale.h> +#include <pwd.h> +#include <sys/signal.h> +#include <sys/stat.h> +#include <unistd.h> +#include "fns.h" + +static const char + version[] = "wmii-"VERSION", "COPYRIGHT"\n"; + +static char* address; +static char* ns_path; +static int sleeperfd; +static int sock; +static int exitsignal; + +static struct sigaction sa; +static struct passwd* passwd; + +static void +usage(void) { + fatal("usage: wmii [-a <address>] [-r <wmiirc>] [-v]\n"); +} + +static int +errfmt(Fmt *f) { + return fmtstrcpy(f, ixp_errbuf()); +} + +static void +scan_wins(void) { + int i; + uint num; + XWindow *wins; + XWindowAttributes wa; + XWindow d1, d2; + + if(XQueryTree(display, scr.root.xid, &d1, &d2, &wins, &num)) { + for(i = 0; i < num; i++) { + if(!XGetWindowAttributes(display, wins[i], &wa)) + continue; + /* Skip transients. */ + if(wa.override_redirect || XGetTransientForHint(display, wins[i], &d1)) + continue; + if(wa.map_state == IsViewable) + client_create(wins[i], &wa); + } + /* Manage transients. */ + for(i = 0; i < num; i++) { + if(!XGetWindowAttributes(display, wins[i], &wa)) + continue; + if((XGetTransientForHint(display, wins[i], &d1)) + && (wa.map_state == IsViewable)) + client_create(wins[i], &wa); + } + } + if(wins) + XFree(wins); +} + +static void +init_ns(void) { + char *s; + + if(address && strncmp(address, "unix!", 5) == 0) { + ns_path = estrdup(&address[5]); + s = strrchr(ns_path, '/'); + if(s != nil) + *s = '\0'; + if(ns_path[0] != '/' || ns_path[0] == '\0') + fatal("address %q is not an absolute path", address); + setenv("NAMESPACE", ns_path, true); + }else + ns_path = ixp_namespace(); + + if(ns_path == nil) + fatal("Bad namespace path: %r\n"); +} + +static void +init_environment(void) { + init_ns(); + + if(address) + setenv("WMII_ADDRESS", address, true); + else + address = smprint("unix!%s/wmii", ns_path); + setenv("WMII_CONFPATH", sxprint("%s/.wmii%s:%s/wmii%s", + getenv("HOME"), CONFVERSION, + CONFPREFIX, CONFVERSION), true); +} + +static void +create_cursor(int ident, uint shape) { + cursor[ident] = XCreateFontCursor(display, shape); +} + +static void +init_cursors(void) { + static char zchar[1]; + Pixmap pix; + XColor black, dummy; + + create_cursor(CurNormal, XC_left_ptr); + create_cursor(CurNECorner, XC_top_right_corner); + create_cursor(CurNWCorner, XC_top_left_corner); + create_cursor(CurSECorner, XC_bottom_right_corner); + create_cursor(CurSWCorner, XC_bottom_left_corner); + create_cursor(CurMove, XC_fleur); + create_cursor(CurDHArrow, XC_sb_h_double_arrow); + create_cursor(CurDVArrow, XC_sb_v_double_arrow); + create_cursor(CurInput, XC_xterm); + create_cursor(CurSizing, XC_sizing); + create_cursor(CurIcon, XC_icon); + create_cursor(CurTCross, XC_tcross); + + XAllocNamedColor(display, scr.colormap, + "black", &black, &dummy); + pix = XCreateBitmapFromData( + display, scr.root.xid, + zchar, 1, 1); + + cursor[CurNone] = XCreatePixmapCursor(display, + pix, pix, + &black, &black, + 0, 0); + + XFreePixmap(display, pix); +} + +/* + * There's no way to check accesses to destroyed windows, thus + * those cases are ignored (especially on UnmapNotifies). + * Other types of errors call Xlib's default error handler, which + * calls exit(). + */ +ErrorCode ignored_xerrors[] = { + { 0, BadWindow }, + { X_SetInputFocus, BadMatch }, + { X_PolyText8, BadDrawable }, + { X_PolyFillRectangle, BadDrawable }, + { X_PolySegment, BadDrawable }, + { X_ConfigureWindow, BadMatch }, + { X_GrabKey, BadAccess }, + { X_GetAtomName, BadAtom }, + { 0, } +}; + +void +regerror(char *err) { + fprint(2, "%s: %s\n", argv0, err); +} + +void +init_screens(void) { + Rectangle *rects; + View *v; + int i, n, m; + +#ifdef notdef + d.x = Dx(scr.rect) - Dx(screen->r); + d.y = Dy(scr.rect) - Dy(screen->r); + for(v=view; v; v=v->next) { + v->r.max.x += d.x; + v->r.max.y += d.y; + } +#endif + + /* Reallocate screens, zero any new ones. */ + rects = xinerama_screens(&n); + m = max(n, nscreens); + screens = erealloc(screens, (m + 1) * sizeof *screens); + screens[m] = nil; + for(v=view; v; v=v->next) { + v->areas = erealloc(v->areas, m * sizeof *v->areas); + v->r = erealloc(v->r, m * sizeof *v->r); + v->pad = erealloc(v->pad, m * sizeof *v->pad); + } + + for(i=nscreens; i < m; i++) { + screens[i] = emallocz(sizeof *screens[i]); + for(v=view; v; v=v->next) + view_init(v, i); + } + + nscreens = m; + + /* Reallocate buffers. */ + freeimage(ibuf); + freeimage(ibuf32); + ibuf = allocimage(Dx(scr.rect), Dy(scr.rect), scr.depth); + ibuf32 = nil; /* Probably shouldn't do this until it's needed. */ + if(render_visual) + ibuf32 = allocimage(Dx(scr.rect), Dy(scr.rect), 32); + disp.ibuf = ibuf; + disp.ibuf32 = ibuf32; + + /* Resize and initialize screens. */ + for(i=0; i < nscreens; i++) { + screen = screens[i]; + screen->idx = i; + + screen->showing = i < n; + if(screen->showing) + screen->r = rects[i]; + else + screen->r = rectsetorigin(screen->r, scr.rect.max); + def.snap = Dy(screen->r) / 63; + bar_init(screens[i]); + } + screen = screens[0]; + if(selview) + view_update(selview); +} + +static void +cleanup(void) { + starting = -1; + while(client) + client_destroy(client); + ixp_server_close(&srv); + close(sleeperfd); +} + +static void +cleanup_handler(int signal) { + sa.sa_handler = SIG_DFL; + sigaction(signal, &sa, nil); + + srv.running = false; + + switch(signal) { + case SIGTERM: + sa.sa_handler = cleanup_handler; + sigaction(SIGALRM, &sa, nil); + alarm(1); + default: + exitsignal = signal; + break; + case SIGALRM: + raise(SIGTERM); + case SIGINT: + break; + } +} + +static void +init_traps(void) { + char buf[1]; + int fd[2]; + + if(pipe(fd) != 0) + fatal("Can't pipe(): %r"); + + if(doublefork() == 0) { + close(fd[1]); + close(ConnectionNumber(display)); + setsid(); + + display = XOpenDisplay(nil); + if(!display) + fatal("Can't open display"); + + /* Wait for parent to exit */ + read(fd[0], buf, 1); + + setfocus(pointerwin, RevertToPointerRoot); + XCloseDisplay(display); + exit(0); + } + + close(fd[0]); + sleeperfd = fd[1]; + + sa.sa_flags = 0; + sa.sa_handler = cleanup_handler; + sigaction(SIGINT, &sa, nil); + sigaction(SIGTERM, &sa, nil); + sigaction(SIGQUIT, &sa, nil); + sigaction(SIGHUP, &sa, nil); + sigaction(SIGUSR1, &sa, nil); + sigaction(SIGUSR2, &sa, nil); +} + +void +spawn_command(const char *cmd) { + char *shell, *p; + + + if(doublefork() == 0) { + if((p = pathsearch(getenv("WMII_CONFPATH"), cmd, true))) + cmd = p; + + if(setsid() == -1) + fatal("Can't setsid: %r"); + + /* Run through the user's shell as a login shell */ + shell = passwd->pw_shell; + if(shell[0] != '/') + fatal("Shell is not an absolute path: %s", shell); + p = smprint("-%s", strrchr(shell, '/') + 1); + + close(0); + open("/dev/null", O_RDONLY); + + execl(shell, p, "-c", cmd, nil); + fatal("Can't exec '%s': %r", cmd); + /* NOTREACHED */ + } +} + +static void +check_preselect(IxpServer *s) { + USED(s); + + check_x_event(nil); +} + +static void +closedisplay(IxpConn *c) { + USED(c); + + XCloseDisplay(display); +} + +static void +printfcall(IxpFcall *f) { + Dprint(D9p, "%F\n", f); +} + +int +main(int argc, char *argv[]) { + IxpMsg m; + char **oargv; + char *wmiirc, *s; + int i; + + quotefmtinstall(); + fmtinstall('r', errfmt); + fmtinstall('a', afmt); + fmtinstall('C', Cfmt); +extern int fmtevent(Fmt*); + fmtinstall('E', fmtevent); + + wmiirc = "wmiirc"; + + oargv = argv; + ARGBEGIN{ + case 'a': + address = EARGF(usage()); + break; + case 'r': + wmiirc = EARGF(usage()); + break; + case 'v': + print("%s", version); + exit(0); + case 'D': + s = EARGF(usage()); + m = ixp_message(s, strlen(s), 0); + msg_debug(&m); + break; + default: + usage(); + break; + }ARGEND; + + if(argc) + usage(); + + setlocale(LC_CTYPE, ""); + starting = true; + + initdisplay(); + + traperrors(true); + selectinput(&scr.root, EnterWindowMask + | SubstructureRedirectMask); + if(traperrors(false)) + fatal("another window manager is already running"); + + passwd = getpwuid(getuid()); + user = estrdup(passwd->pw_name); + + init_environment(); + + fmtinstall('F', Ffmt); + ixp_printfcall = printfcall; + + sock = ixp_announce(address); + if(sock < 0) + fatal("Can't create socket '%s': %r", address); + closeexec(ConnectionNumber(display)); + closeexec(sock); + + if(wmiirc[0]) + spawn_command(wmiirc); + + init_traps(); + init_cursors(); + init_lock_keys(); + ewmh_init(); + xext_init(); + + srv.preselect = check_preselect; + ixp_listen(&srv, sock, &p9srv, serve_9pcon, nil); + ixp_listen(&srv, ConnectionNumber(display), nil, check_x_event, closedisplay); + + def.border = 1; + def.colmode = Colstack; + def.font = loadfont(FONT); + def.incmode = ISqueeze; + + def.mod = Mod1Mask; + strcpy(def.grabmod, "Mod1"); + + loadcolor(&def.focuscolor, FOCUSCOLORS); + loadcolor(&def.normcolor, NORMCOLORS); + + disp.sel = pointerscreen(); + + init_screens(); + root_init(); + + disp.focus = nil; + setfocus(screen->barwin, RevertToParent); + view_select("1"); + + scan_wins(); + starting = false; + + view_update_all(); + ewmh_updateviews(); + + event("FocusTag %s\n", selview->name); + + i = ixp_serverloop(&srv); + if(i) + fprint(2, "%s: error: %r\n", argv0); + else + event("Quit"); + + cleanup(); + + if(exitsignal) + raise(exitsignal); + if(execstr) { + char *toks[32]; + int n; + + n = unquote(strdup(execstr), toks, nelem(toks)-1); + toks[n] = nil; + execvp(toks[0], toks); + fprint(2, "%s: failed to exec %q: %r\n", argv0, execstr); + execvp(argv0, oargv); + fatal("failed to exec myself"); + } + return i; +} + diff --git a/cmd/wmii/map.c b/cmd/wmii/map.c new file mode 100644 index 0000000..08b137a --- /dev/null +++ b/cmd/wmii/map.c @@ -0,0 +1,126 @@ +/* Written by Kris Maglione */ +/* Public domain */ +#include "dat.h" +#include "fns.h" + +/* Edit s/^([a-zA-Z].*)\n([a-z].*) {/\1 \2;/g x/^([^a-zA-Z]|static|$)/-+d s/ (\*map|val|*str)//g */ + +struct MapEnt { + ulong hash; + const char* key; + void* val; + MapEnt* next; +}; + +MapEnt *NM; + +/* By Dan Bernstein. Public domain. */ +static ulong +hash(const char *str) { + ulong h; + + h = 5381; + while (*str != '\0') { + h += h << 5; /* h *= 33 */ + h ^= *str++; + } + return h; +} + +static void +insert(MapEnt **e, ulong val, const char *key) { + MapEnt *te; + + te = emallocz(sizeof *te); + te->hash = val; + te->key = key; + te->next = *e; + *e = te; +} + +static MapEnt** +map_getp(Map *map, ulong val, int create) { + MapEnt **e; + + e = &map->bucket[val%map->nhash]; + for(; *e; e = &(*e)->next) + if((*e)->hash >= val) break; + if(*e == nil || (*e)->hash != val) { + if(create) + insert(e, val, nil); + else + e = &NM; + } + return e; +} + +static MapEnt** +hash_getp(Map *map, const char *str, int create) { + MapEnt **e; + ulong h; + int cmp; + + h = hash(str); + e = map_getp(map, h, create); + if(*e && (*e)->key == nil) + (*e)->key = str; + else { + SET(cmp); + for(; *e; e = &(*e)->next) + if((*e)->hash > h || (cmp = strcmp((*e)->key, str)) >= 0) + break; + if(*e == nil || (*e)->hash > h || cmp > 0) + if(create) + insert(e, h, str); + } + return e; +} + +void** +map_get(Map *map, ulong val, bool create) { + MapEnt *e; + + e = *map_getp(map, val, create); + return e ? &e->val : nil; +} + +void** +hash_get(Map *map, const char *str, bool create) { + MapEnt *e; + + e = *hash_getp(map, str, create); + return e ? &e->val : nil; +} + +void* +map_rm(Map *map, ulong val) { + MapEnt **e, *te; + void *ret; + + ret = nil; + e = map_getp(map, val, 0); + if(*e) { + te = *e; + ret = te->val; + *e = te->next; + free(te); + } + return ret; +} + +void* +hash_rm(Map *map, const char *str) { + MapEnt **e, *te; + void *ret; + + ret = nil; + e = hash_getp(map, str, 0); + if(*e) { + te = *e; + ret = te->val; + *e = te->next; + free(te); + } + return ret; +} + diff --git a/cmd/wmii/message.c b/cmd/wmii/message.c new file mode 100644 index 0000000..d1dd4a7 --- /dev/null +++ b/cmd/wmii/message.c @@ -0,0 +1,1177 @@ +/* Copyright ©2006-2010 Kris Maglione <maglione.k at Gmail> + * See LICENSE file for license details. + */ +#include "dat.h" +#include <ctype.h> +#include "fns.h" + +static char* msg_grow(View*, IxpMsg*); +static char* msg_nudge(View*, IxpMsg*); +static char* msg_selectframe(Area*, IxpMsg*, int); +static char* msg_sendframe(Frame*, int, bool); + +#define DIR(s) (\ + s == LUP ? North : \ + s == LDOWN ? South : \ + s == LLEFT ? West : \ + s == LRIGHT ? East : \ + (abort(), 0)) + +static char + Ebadcmd[] = "bad command", + Ebadvalue[] = "bad value", + Ebadusage[] = "bad usage"; + +/* Edit |sort Edit |sed 's/"([^"]+)"/L\1/g' | tr 'a-z' 'A-Z' */ +enum { + LFULLSCREEN, + LURGENT, + LBAR, + LBORDER, + LCLIENT, + LCOLMODE, + LDEBUG, + LDOWN, + LEXEC, + LFOCUSCOLORS, + LFONT, + LFONTPAD, + LGRABMOD, + LGROW, + LINCMODE, + LKILL, + LLEFT, + LNORMCOLORS, + LNUDGE, + LOFF, + LON, + LQUIT, + LRIGHT, + LSELCOLORS, + LSELECT, + LSEND, + LSLAY, + LSPAWN, + LSWAP, + LTOGGLE, + LUP, + LVIEW, + LTILDE, +}; +char *symtab[] = { + "Fullscreen", + "Urgent", + "bar", + "border", + "client", + "colmode", + "debug", + "down", + "exec", + "focuscolors", + "font", + "fontpad", + "grabmod", + "grow", + "incmode", + "kill", + "left", + "normcolors", + "nudge", + "off", + "on", + "quit", + "right", + "selcolors", + "select", + "send", + "slay", + "spawn", + "swap", + "toggle", + "up", + "view", + "~", +}; + +char* debugtab[] = { + "9p", + "dnd", + "event", + "ewmh", + "focus", + "generic", + "stack", +}; + +static char* barpostab[] = { + "bottom", + "top", +}; +static char* incmodetab[] = { + "ignore", + "show", + "squeeze", +}; +static char* toggletab[] = { + "off", + "on", + "toggle", +}; + +/* Edit ,y/^[a-zA-Z].*\n.* {\n/d + * Edit s/^([a-zA-Z].*)\n(.*) {\n/\1 \2;\n/ + * Edit ,x/^static.*\n/d + */ + +static int +_bsearch(char *s, char **tab, int ntab) { + int i, n, m, cmp; + + if(s == nil) + return -1; + + n = ntab; + i = 0; + while(n) { + m = n/2; + cmp = strcmp(s, tab[i+m]); + if(cmp == 0) + return i+m; + if(cmp < 0 || m == 0) + n = m; + else { + i += m; + n = n-m; + } + } + return -1; +} + +static int +getsym(char *s) { + return _bsearch(s, symtab, nelem(symtab)); +} + +static bool +setdef(int *ptr, char *s, char *tab[], int ntab) { + int i; + + i = _bsearch(s, tab, ntab); + if(i >= 0) + *ptr = i; + return i >= 0; +} + +static int +gettoggle(char *s) { + switch(getsym(s)) { + case LON: return On; + case LOFF: return Off; + case LTOGGLE: return Toggle; + default: + return -1; + } +} + +static int +getdirection(IxpMsg *m) { + int i; + + switch(i = getsym(msg_getword(m))) { + case LLEFT: + case LRIGHT: + case LUP: + case LDOWN: + return i; + default: + return -1; + } +} + +static void +eatrunes(IxpMsg *m, int (*p)(Rune), int val) { + Rune r; + int n; + + while(m->pos < m->end) { + n = chartorune(&r, m->pos); + if(p(r) != val) + break; + m->pos += n; + } + if(m->pos > m->end) + m->pos = m->end; +} + +char* +msg_getword(IxpMsg *m) { + char *ret; + Rune r; + int n; + + eatrunes(m, isspacerune, true); + ret = m->pos; + eatrunes(m, isspacerune, false); + n = chartorune(&r, m->pos); + *m->pos = '\0'; + m->pos += n; + eatrunes(m, isspacerune, true); + + /* Filter out comments. */ + if(*ret == '#') { + *ret = '\0'; + m->pos = m->end; + } + if(*ret == '\\') + if(ret[1] == '\\' || ret[1] == '#') + ret++; + if(*ret == '\0') + return nil; + return ret; +} + +#define strbcmp(str, const) (strncmp((str), (const), sizeof(const)-1)) +static int +getbase(const char **s, long *sign) { + const char *p; + int ret; + + ret = 10; + *sign = 1; + if(**s == '-') { + *sign = -1; + *s += 1; + }else if(**s == '+') + *s += 1; + + p = *s; + if(!strbcmp(p, "0x")) { + *s += 2; + ret = 16; + } + else if(isdigit(p[0])) { + if(p[1] == 'r') { + *s += 2; + ret = p[0] - '0'; + } + else if(isdigit(p[1]) && p[2] == 'r') { + *s += 3; + ret = 10*(p[0]-'0') + (p[1]-'0'); + } + } + else if(p[0] == '0') { + ret = 8; + } + if(ret != 10 && (**s == '-' || **s == '+')) + *sign = 0; + return ret; +} + +static bool +getint(const char *s, int *ret) { + long l; + bool res; + + res = getlong(s, &l); + *ret = l; + return res; +} + +bool +getlong(const char *s, long *ret) { + const char *end; + char *rend; + int base; + long sign; + + if(s == nil) + return false; + end = s+strlen(s); + base = getbase(&s, &sign); + if(sign == 0) + return false; + + *ret = sign * strtol(s, &rend, base); + return (end == rend); +} + +bool +getulong(const char *s, ulong *ret) { + const char *end; + char *rend; + int base; + long sign; + + if(s == nil) + return false; + end = s+strlen(s); + base = getbase(&s, &sign); + if(sign < 1) + return false; + + *ret = strtoul(s, &rend, base); + return (end == rend); +} + +static char* +strend(char *s, int n) { + int len; + + len = strlen(s); + return s + max(0, len - n); +} + +static Client* +strclient(View *v, char *s) { + ulong id; + + /* + * sel + * 0x<window xid> + */ + + if(s == nil) + return nil; + if(!strcmp(s, "sel")) + return view_selclient(v); + if(getulong(s, &id)) + return win2client(id); + + return nil; +} + +Area* +strarea(View *v, ulong scrn, const char *s) { + Area *a; + char *p; + long i; + + /* + * sel + * ~ + * <column number> + */ + + if(s == nil) + return nil; + + if((p = strchr(s, ':'))) { + *p++ = '\0'; + if(!strcmp(s, "sel")) + scrn = v->selscreen; + else if(!getulong(s, &scrn)) + return nil; + s = p; + } + else if(!strcmp(s, "sel")) + return v->sel; + + if(!strcmp(s, "sel")) { + if(scrn != v->selscreen) + return nil; + return v->sel; + } + if(!strcmp(s, "~")) + return v->floating; + if(scrn < 0 || !getlong(s, &i) || i == 0) + return nil; + + if(i > 0) { + for(a = v->areas[scrn]; a; a = a->next) + if(i-- == 1) break; + } + else { + /* FIXME: Switch to circularly linked list. */ + for(a = v->areas[scrn]; a->next; a = a->next) + ; + for(; a; a = a->prev) + if(++i == 0) break; + } + return a; +} + +static Frame* +getframe(View *v, int scrn, IxpMsg *m) { + Client *c; + Frame *f; + Area *a; + char *s; + ulong l; + + s = msg_getword(m); + if(!s || !strcmp(s, "client")) { + c = strclient(v, msg_getword(m)); + if(c == nil) + return nil; + return client_viewframe(c, v); + } + + /* XXX: Multihead */ + a = strarea(v, scrn, s); + if(a == nil) { + fprint(2, "a == nil\n"); + return nil; + } + + s = msg_getword(m); + if(!s) + return nil; + if(!strcmp(s, "sel")) + return a->sel; + if(!getulong(s, &l)) + return nil; + for(f=a->frame; f; f=f->anext) + if(--l == 0) break; + return f; +} + +char* +readctl_client(Client *c) { + bufclear(); + bufprint("%C\n", c); + if(c->fullscreen >= 0) + bufprint("Fullscreen %d\n", c->fullscreen); + else + bufprint("Fullscreen off\n"); + bufprint("Urgent %s\n", toggletab[(int)c->urgent]); + return buffer; +} + +char* +message_client(Client *c, IxpMsg *m) { + char *s; + long l; + int i; + + s = msg_getword(m); + + /* + * Toggle ::= on + * | off + * | toggle + * | <screen> + * Fullscreen <toggle> + * Urgent <toggle> + * kill + * slay + */ + + switch(getsym(s)) { + case LFULLSCREEN: + s = msg_getword(m); + if(getlong(s, &l)) + fullscreen(c, On, l); + else { + i = gettoggle(s); + if(i == -1) + return Ebadusage; + fullscreen(c, i, -1); + } + break; + case LKILL: + client_kill(c, true); + break; + case LSLAY: + client_kill(c, false); + break; + case LURGENT: + i = gettoggle(msg_getword(m)); + if(i == -1) + return Ebadusage; + client_seturgent(c, i, UrgManager); + break; + default: + return Ebadcmd; + } + return nil; +} + +char* +message_root(void *p, IxpMsg *m) { + Font *fn; + char *s, *ret; + ulong n; + int i; + + USED(p); + ret = nil; + s = msg_getword(m); + if(s == nil) + return nil; + + if(!strcmp(s, "backtrace")) { + backtrace(m->pos); + return nil; + } + + switch(getsym(s)) { + case LBAR: /* bar on? <"top" | "bottom"> */ + s = msg_getword(m); + if(!strcmp(s, "on")) + s = msg_getword(m); + if(!setdef(&screen->barpos, s, barpostab, nelem(barpostab))) + return Ebadvalue; + view_update(selview); + break; + case LBORDER: + if(!getulong(msg_getword(m), &n)) + return Ebadvalue; + def.border = n; + view_update(selview); + break; + case LCOLMODE: + s = msg_getword(m); + if(!setdef(&def.colmode, s, modes, Collast)) + return Ebadvalue; + break; + case LDEBUG: + ret = msg_debug(m); + break; + case LEXEC: + execstr = strdup(m->pos); + srv.running = 0; + break; + case LSPAWN: + spawn_command(m->pos); + break; + case LFOCUSCOLORS: + ret = msg_parsecolors(m, &def.focuscolor); + view_update(selview); + break; + case LFONT: + fn = loadfont(m->pos); + if(fn) { + freefont(def.font); + def.font = fn; + for(n=0; n < nscreens; n++) + bar_resize(screens[n]); + }else + ret = "can't load font"; + view_update(selview); + break; + case LFONTPAD: + if(!getint(msg_getword(m), &def.font->pad.min.x) || + !getint(msg_getword(m), &def.font->pad.max.x) || + !getint(msg_getword(m), &def.font->pad.max.y) || + !getint(msg_getword(m), &def.font->pad.min.y)) + ret = "invalid rectangle"; + else { + for(n=0; n < nscreens; n++) + bar_resize(screens[n]); + view_update(selview); + } + break; + case LGRABMOD: + s = msg_getword(m); + if(!parsekey(s, &i, nil) || i == 0) + return Ebadvalue; + + utflcpy(def.grabmod, s, sizeof def.grabmod); + def.mod = i; + break; + case LINCMODE: + if(!setdef(&def.incmode, msg_getword(m), incmodetab, nelem(incmodetab))) + return Ebadvalue; + view_update(selview); + break; + case LNORMCOLORS: + ret = msg_parsecolors(m, &def.normcolor); + view_update(selview); + break; + case LSELCOLORS: + warning("selcolors have been removed"); + return Ebadcmd; + case LVIEW: + view_select(m->pos); + break; + case LQUIT: + srv.running = 0; + break; + default: + return Ebadcmd; + } + return ret; +} + +static void +printdebug(int mask) { + int i, j; + + for(i=0, j=0; i < nelem(debugtab); i++) + if(mask & (1<<i)) { + if(j++ > 0) bufprint(" "); + bufprint("%s", debugtab[i]); + } +} + +char* +readctl_root(void) { + bufclear(); + bufprint("bar on %s\n", barpostab[screen->barpos]); + bufprint("border %d\n", def.border); + bufprint("colmode %s\n", modes[def.colmode]); + if(debugflag) { + bufprint("debug "); + printdebug(debugflag); + bufprint("\n"); + } + if(debugfile) { + bufprint("debugfile "); + printdebug(debugfile); + bufprint("\n"); + } + bufprint("focuscolors %s\n", def.focuscolor.colstr); + bufprint("font %s\n", def.font->name); + bufprint("fontpad %d %d %d %d\n", def.font->pad.min.x, def.font->pad.max.x, + def.font->pad.max.y, def.font->pad.min.y); + bufprint("grabmod %s\n", def.grabmod); + bufprint("incmode %s\n", incmodetab[def.incmode]); + bufprint("normcolors %s\n", def.normcolor.colstr); + bufprint("view %s\n", selview->name); + return buffer; +} + +char* +message_view(View *v, IxpMsg *m) { + Area *a; + char *s; + + s = msg_getword(m); + if(s == nil) + return nil; + + /* + * area ::= ~ + * | <column number> + * | sel + * direction ::= left + * | right + * | up + * | down + * # This *should* be identical to <frame> + * place ::= <column number> + * #| ~ # This should be, but isn't. + * | <direction> + * | toggle + * colmode ::= default + * | stack + * | normal + * column ::= sel + * | <column number> + * frame ::= up + * | down + * | left + * | right + * | toggle + * | client <window xid> + * | sel + * | ~ + * | <column> <frame number> + * | <column> + * amount ::= + * | <number> + * | <number>px + * + * colmode <area> <colmode> + * select <area> + * send <frame> <place> + * swap <frame> <place> + * grow <thing> <direction> <amount> + * nudge <thing> <direction> <amount> + * select <ordframe> + */ + + switch(getsym(s)) { + case LCOLMODE: + s = msg_getword(m); + a = strarea(v, screen->idx, s); + if(a == nil) /* || a->floating) */ + return Ebadvalue; + + s = msg_getword(m); + if(s == nil || !column_setmode(a, s)) + return Ebadvalue; + + column_arrange(a, false); + view_restack(v); + + view_update(v); + return nil; + case LGROW: + return msg_grow(v, m); + case LNUDGE: + return msg_nudge(v, m); + case LSELECT: + return msg_selectarea(v->sel, m); + case LSEND: + return msg_sendclient(v, m, false); + case LSWAP: + return msg_sendclient(v, m, true); + default: + return Ebadcmd; + } + /* not reached */ +} + +char* +readctl_view(View *v) { + Area *a; + int s; + + bufclear(); + bufprint("%s\n", v->name); + + /* select <area>[ <frame>] */ + bufprint("select %a", v->sel); + if(v->sel->sel) + bufprint(" %d", frame_idx(v->sel->sel)); + bufprint("\n"); + + /* select client <client> */ + if(v->sel->sel) + bufprint("select client %C\n", v->sel->sel->client); + + foreach_area(v, s, a) + bufprint("colmode %a %s\n", a, column_getmode(a)); + return buffer; +} + +char* +msg_debug(IxpMsg *m) { + char *opt; + int d; + char add; + + bufclear(); + while((opt = msg_getword(m))) { + add = '+'; + if(opt[0] == '+' || opt[0] == '-') + add = *opt++; + d = _bsearch(opt, debugtab, nelem(debugtab)); + if(d == -1) { + bufprint(", %s", opt); + continue; + } + if(add == '+') + debugflag |= 1<<d; + else + debugflag &= ~(1<<d); + } + if(buffer[0] != '\0') + return sxprint("Bad debug options: %s", buffer+2); + return nil; +} + +static bool +getamt(IxpMsg *m, Point *amt) { + char *s, *p; + long l; + + s = msg_getword(m); + if(s) { + p = strend(s, 2); + if(!strcmp(p, "px")) { + *p = '\0'; + amt->x = 1; + amt->y = 1; + } + + if(!getlong(s, &l)) + return false; + amt->x *= l; + amt->y *= l; + } + return true; +} + +static char* +msg_grow(View *v, IxpMsg *m) { + Client *c; + Frame *f; + Rectangle r; + Point amount; + int dir; + + f = getframe(v, screen->idx, m); + if(f == nil) + return "bad frame"; + c = f->client; + + dir = getdirection(m); + if(dir == -1) + return "bad direction"; + + amount.x = Dy(f->titlebar); + amount.y = Dy(f->titlebar); + if(amount.x < c->w.hints->inc.x) + amount.x = c->w.hints->inc.x; + if(amount.y < c->w.hints->inc.y) + amount.y = c->w.hints->inc.y; + + if(!getamt(m, &amount)) + return Ebadvalue; + + if(f->area->floating) + r = f->r; + else + r = f->colr; + switch(dir) { + case LLEFT: r.min.x -= amount.x; break; + case LRIGHT: r.max.x += amount.x; break; + case LUP: r.min.y -= amount.y; break; + case LDOWN: r.max.y += amount.y; break; + default: abort(); + } + + if(f->area->floating) + float_resizeframe(f, r); + else + column_resizeframe(f, r); + + return nil; +} + +static char* +msg_nudge(View *v, IxpMsg *m) { + Frame *f; + Rectangle r; + Point amount; + int dir; + + f = getframe(v, screen->idx, m); + if(f == nil) + return "bad frame"; + + dir = getdirection(m); + if(dir == -1) + return "bad direction"; + + amount.x = Dy(f->titlebar); + amount.y = Dy(f->titlebar); + if(!getamt(m, &amount)) + return Ebadvalue; + + if(f->area->floating) + r = f->r; + else + r = f->colr; + switch(dir) { + case LLEFT: r = rectaddpt(r, Pt(-amount.x, 0)); break; + case LRIGHT: r = rectaddpt(r, Pt( amount.x, 0)); break; + case LUP: r = rectaddpt(r, Pt(0, -amount.y)); break; + case LDOWN: r = rectaddpt(r, Pt(0, amount.y)); break; + default: abort(); + } + + if(f->area->floating) + float_resizeframe(f, r); + else + column_resizeframe(f, r); + + return nil; +} + +char* +msg_parsecolors(IxpMsg *m, CTuple *col) { + static char Ebad[] = "bad color string"; + Rune r; + char c, *p; + int i, j; + + /* '#%6x #%6x #%6x' */ + p = m->pos; + for(i = 0; i < 3 && p < m->end; i++) { + if(*p++ != '#') + return Ebad; + for(j = 0; j < 6; j++) + if(p >= m->end || !isxdigit(*p++)) + return Ebad; + + chartorune(&r, p); + if(i < 2) { + if(r != ' ') + return Ebad; + p++; + }else if(*p != '\0' && !isspacerune(r)) + return Ebad; + } + + c = *p; + *p = '\0'; + loadcolor(col, m->pos); + *p = c; + + m->pos = p; + eatrunes(m, isspacerune, true); + return nil; +} + +char* +msg_selectarea(Area *a, IxpMsg *m) { + Frame *f; + Area *ap; + View *v; + char *s; + ulong i; + int sym; + + v = a->view; + s = msg_getword(m); + sym = getsym(s); + + switch(sym) { + case LTOGGLE: + if(!a->floating) + ap = v->floating; + else if(v->revert && v->revert != a) + ap = v->revert; + else + ap = v->firstarea; + break; + case LLEFT: + case LRIGHT: + case LUP: + case LDOWN: + case LCLIENT: + return msg_selectframe(a, m, sym); + case LTILDE: + ap = v->floating; + break; + default: + /* XXX: Multihead */ + ap = strarea(v, a->screen, s); + if(!ap || ap->floating) + return Ebadvalue; + if((s = msg_getword(m))) { + if(!getulong(s, &i)) + return Ebadvalue; + for(f = ap->frame; f; f = f->anext) + if(--i == 0) break; + if(i != 0) + return Ebadvalue; + frame_focus(f); + return nil; + } + } + + area_focus(ap); + return nil; +} + +static char* +msg_selectframe(Area *a, IxpMsg *m, int sym) { + Client *c; + Frame *f, *fp; + char *s; + bool stack; + ulong i, dy; + + f = a->sel; + fp = f; + + stack = false; + if(sym == LUP || sym == LDOWN) { + s = msg_getword(m); + if(s) + if(!strcmp(s, "stack")) + stack = true; + else + return Ebadvalue; + } + + if(sym == LCLIENT) { + s = msg_getword(m); + if(s == nil || !getulong(s, &i)) + return "usage: select client <client>"; + c = win2client(i); + if(c == nil) + return "unknown client"; + f = client_viewframe(c, a->view); + if(!f) + return Ebadvalue; + } + else { + if(!find(&a, &f, DIR(sym), true, stack)) + return Ebadvalue; + } + + area_focus(a); + + if(!f) + return nil; + + /* XXX */ + if(fp && fp->area == a) + if(f->collapsed && !f->area->floating && f->area->mode == Coldefault) { + dy = Dy(f->colr); + f->colr.max.y = f->colr.min.y + Dy(fp->colr); + fp->colr.max.y = fp->colr.min.y + dy; + column_arrange(a, false); + } + + frame_focus(f); + frame_restack(f, nil); + if(f->view == selview) + view_restack(a->view); + return nil; +} + +static char* +sendarea(Frame *f, Area *to, bool swap) { + Client *c; + + c = f->client; + if(!to) + return Ebadvalue; + + if(!swap) + area_moveto(to, f); + else if(to->sel) + frame_swap(f, to->sel); + else + return Ebadvalue; + + frame_focus(client_viewframe(c, f->view)); + /* view_arrange(v); */ + view_update_all(); + return nil; +} + +char* +msg_sendclient(View *v, IxpMsg *m, bool swap) { + Area *to, *a; + Frame *f, *ff; + Client *c; + char *s; + int sym; + + s = msg_getword(m); + + c = strclient(v, s); + if(c == nil) + return Ebadvalue; + + f = client_viewframe(c, v); + if(f == nil) + return Ebadvalue; + + a = f->area; + to = nil; + + s = msg_getword(m); + sym = getsym(s); + + /* FIXME: Should use a helper function. */ + switch(sym) { + case LUP: + case LDOWN: + return msg_sendframe(f, sym, swap); + case LLEFT: + if(a->floating) + return Ebadvalue; + to = a->prev; + break; + case LRIGHT: + if(a->floating) + return Ebadvalue; + to = a->next; + break; + case LTOGGLE: + if(!a->floating) + to = v->floating; + else if(f->column) + to = view_findarea(v, f->screen, f->column, true); + else + to = view_findarea(v, v->selscreen, v->selcol, true); + break; + case LTILDE: + if(a->floating) + return Ebadvalue; + to = v->floating; + break; + default: + to = strarea(v, v->selscreen, s); + // to = view_findarea(v, scrn, i, true); + break; + } + + + if(!to && !swap) { + /* XXX: Multihead - clean this up, move elsewhere. */ + if(!f->anext && f == f->area->frame) { + ff = f; + to = a; + if(!find(&to, &ff, DIR(sym), false, false)) + return Ebadvalue; + } + else { + to = (sym == LLEFT) ? nil : a; + to = column_new(v, to, a->screen, 0); + } + } + + return sendarea(f, to, swap); +} + +static char* +msg_sendframe(Frame *f, int sym, bool swap) { + Client *c; + Area *a; + Frame *fp; + + SET(fp); + c = f->client; + + a = f->area; + fp = f; + if(!find(&a, &fp, DIR(sym), false, false)) + return Ebadvalue; + if(a != f->area) + return sendarea(f, a, swap); + + switch(sym) { + case LUP: + fp = f->aprev; + if(!fp) + return Ebadvalue; + if(!swap) + fp = fp->aprev; + break; + case LDOWN: + fp = f->anext; + if(!fp) + return Ebadvalue; + break; + default: + die("can't get here"); + } + + if(swap) + frame_swap(f, fp); + else { + frame_remove(f); + frame_insert(f, fp); + } + + /* view_arrange(f->view); */ + + frame_focus(client_viewframe(c, f->view)); + view_update_all(); + return nil; +} + +void +warning(const char *fmt, ...) { + va_list ap; + char *s; + + va_start(ap, fmt); + s = vsmprint(fmt, ap); + va_end(ap); + + event("Warning %s\n", s); + fprint(2, "%s: warning: %s\n", argv0, s); + free(s); +} + diff --git a/cmd/wmii/mouse.c b/cmd/wmii/mouse.c new file mode 100644 index 0000000..fb7e2ff --- /dev/null +++ b/cmd/wmii/mouse.c @@ -0,0 +1,642 @@ +/* Copyright ©2006-2010 Kris Maglione <maglione.k at Gmail> + * See LICENSE file for license details. + */ +#include "dat.h" +#include "fns.h" + +/* Here be dragons. */ + +enum { + ButtonMask = + ButtonPressMask | ButtonReleaseMask, + MouseMask = + ButtonMask | PointerMotionMask +}; + +static void +cwin_expose(Window *w, XExposeEvent *e) { + + fill(w, rectsubpt(w->r, w->r.min), def.focuscolor.bg); + fill(w, w->r, def.focuscolor.bg); +} + +static Handlers chandler = { + .expose = cwin_expose, +}; + +Window* +constraintwin(Rectangle r) { + Window *w; + WinAttr wa; + + w = createwindow(&scr.root, r, 0, InputOnly, &wa, 0); + if(0) { + Window *w2; + + w2 = createwindow(&scr.root, r, 0, InputOutput, &wa, 0); + selectinput(w2, ExposureMask); + w->aux = w2; + + setborder(w2, 1, def.focuscolor.border); + sethandler(w2, &chandler); + mapwin(w2); + raisewin(w2); + } + mapwin(w); + return w; +} + +void +destroyconstraintwin(Window *w) { + Window *w2; + + if(w->aux) { + w2 = w->aux; + sethandler(w2, nil); + destroywindow(w2); + } + destroywindow(w); +} + +static Window* +gethsep(Rectangle r) { + Window *w; + WinAttr wa; + + wa.background_pixel = def.normcolor.border.pixel; + w = createwindow(&scr.root, r, scr.depth, InputOutput, &wa, CWBackPixel); + mapwin(w); + raisewin(w); + return w; +} + +static void +rect_morph(Rectangle *r, Point d, Align *mask) { + int n; + + if(*mask & North) + r->min.y += d.y; + if(*mask & West) + r->min.x += d.x; + if(*mask & South) + r->max.y += d.y; + if(*mask & East) + r->max.x += d.x; + + if(r->min.x > r->max.x) { + n = r->min.x; + r->min.x = r->max.x; + r->max.x = n; + *mask ^= East|West; + } + if(r->min.y > r->max.y) { + n = r->min.y; + r->min.y = r->max.y; + r->max.y = n; + *mask ^= North|South; + } +} + +/* Yes, yes, macros are evil. So are patterns. */ +#define frob(x, y) \ + const Rectangle *rp; \ + int i, tx; \ + \ + for(i=0; i < nrect; i++) { \ + rp = &rects[i]; \ + if((rp->min.y <= r->max.y) && (rp->max.y >= r->min.y)) { \ + tx = rp->min.x; \ + if(abs(tx - x) <= abs(dx)) \ + dx = tx - x; \ + \ + tx = rp->max.x; \ + if(abs(tx - x) <= abs(dx)) \ + dx = tx - x; \ + } \ + } \ + return dx \ + +static int +snap_hline(const Rectangle *rects, int nrect, int dx, const Rectangle *r, int y) { + frob(y, x); +} + +static int +snap_vline(const Rectangle *rects, int nrect, int dx, const Rectangle *r, int x) { + frob(x, y); +} + +#undef frob + +/* Returns a gravity for increment handling. It's normally the + * opposite of the mask (the directions that we're resizing in), + * unless a snap occurs, in which case, it's the direction of the + * snap. + */ +Align +snap_rect(const Rectangle *rects, int num, Rectangle *r, Align *mask, int snap) { + Align ret; + Point d; + + d.x = snap+1; + d.y = snap+1; + + if(*mask&North) + d.y = snap_hline(rects, num, d.y, r, r->min.y); + if(*mask&South) + d.y = snap_hline(rects, num, d.y, r, r->max.y); + + if(*mask&East) + d.x = snap_vline(rects, num, d.x, r, r->max.x); + if(*mask&West) + d.x = snap_vline(rects, num, d.x, r, r->min.x); + + ret = Center; + if(abs(d.x) <= snap) + ret ^= East|West; + else + d.x = 0; + + if(abs(d.y) <= snap) + ret ^= North|South; + else + d.y = 0; + + rect_morph(r, d, mask); + return ret ^ *mask; +} + +int +readmouse(Point *p, uint *button) { + XEvent ev; + + for(;;) { + XMaskEvent(display, MouseMask|ExposureMask|PropertyChangeMask, &ev); + switch(ev.type) { + case Expose: + case NoExpose: + case PropertyNotify: + dispatch_event(&ev); + default: + Dprint(DEvent, "readmouse(): ignored: %E\n", &ev); + continue; + case ButtonPress: + case ButtonRelease: + *button = ev.xbutton.button; + case MotionNotify: + p->x = ev.xmotion.x_root; + p->y = ev.xmotion.y_root; + break; + } + return ev.type; + } +} + +bool +readmotion(Point *p) { + uint button; + + for(;;) + switch(readmouse(p, &button)) { + case MotionNotify: + return true; + case ButtonRelease: + return false; + } +} + +static void +mouse_resizecolframe(Frame *f, Align align) { + Window *cwin, *hwin; + Divide *d; + View *v; + Area *a; + Rectangle r; + Point pt, min; + int s; + + assert((align&(East|West)) != (East|West)); + assert((align&(North|South)) != (North|South)); + + f->collapsed = false; + + v = selview; + d = divs; + SET(a); + foreach_column(v, s, a) { + if(a == f->area) + break; + d = d->next; + } + + if(align&East) + d = d->next; + + min.x = column_minwidth(); + min.y = /*frame_delta_h() +*/ labelh(def.font); + /* Set the limits of where this box may be dragged. */ +#define frob(pred, f, aprev, rmin, rmax, plus, minus, xy) BLOCK( \ + if(pred) { \ + r.rmin.xy = f->aprev->r.rmin.xy plus min.xy; \ + r.rmax.xy = f->r.rmax.xy minus min.xy; \ + }else { \ + r.rmin.xy = a->r.rmin.xy; \ + r.rmax.xy = r.rmin.xy plus 1; \ + }) + if(align&North) + frob(f->aprev, f, aprev, min, max, +, -, y); + else + frob(f->anext, f, anext, max, min, -, +, y); + if(align&West) + frob(a->prev, a, prev, min, max, +, -, x); + else + frob(a->next, a, next, max, min, -, +, x); +#undef frob + + cwin = constraintwin(r); + + r = f->r; + if(align&North) + r.min.y--; + else + r.min.y = r.max.y - 1; + r.max.y = r.min.y + 2; + + hwin = gethsep(r); + + if(!grabpointer(&scr.root, cwin, cursor[CurSizing], MouseMask)) + goto done; + + pt.x = ((align&West) ? f->r.min.x : f->r.max.x); + pt.y = ((align&North) ? f->r.min.y : f->r.max.y); + warppointer(pt); + + while(readmotion(&pt)) { + if(align&West) + r.min.x = pt.x; + else + r.max.x = pt.x; + r.min.y = ((align&South) ? pt.y : pt.y-1); + r.max.y = r.min.y+2; + + div_set(d, pt.x); + reshapewin(hwin, r); + } + + r = f->r; + if(align&West) + r.min.x = pt.x; + else + r.max.x = pt.x; + if(align&North) + r.min.y = pt.y; + else + r.max.y = pt.y; + column_resizeframe(f, r); + + /* XXX: Magic number... */ + if(align&West) + pt.x = f->r.min.x + 4; + else + pt.x = f->r.max.x - 4; + if(align&North) + pt.y = f->r.min.y + 4; + else + pt.y = f->r.max.y - 4; + warppointer(pt); + +done: + ungrabpointer(); + destroyconstraintwin(cwin); + destroywindow(hwin); +} + +void +mouse_resizecol(Divide *d) { + Window *cwin; + View *v; + Rectangle r; + Point pt; + int minw, scrn; + + v = selview; + + scrn = (d->left ? d->left : d->right)->screen; + + pt = querypointer(&scr.root); + + minw = column_minwidth(); + r.min.x = d->left ? d->left->r.min.x + minw : v->r[scrn].min.x; + r.max.x = d->right ? d->right->r.max.x - minw : v->r[scrn].max.x; + r.min.y = pt.y; + r.max.y = pt.y+1; + + cwin = constraintwin(r); + + if(!grabpointer(&scr.root, cwin, cursor[CurNone], MouseMask)) + goto done; + + while(readmotion(&pt)) + div_set(d, pt.x); + + if(d->left) + d->left->r.max.x = pt.x; + else + v->pad[scrn].min.x = pt.x - v->r[scrn].min.x; + + if(d->right) + d->right->r.min.x = pt.x; + else + v->pad[scrn].max.x = pt.x - v->r[scrn].max.x; + + view_arrange(v); + +done: + ungrabpointer(); + destroyconstraintwin(cwin); +} + +void +mouse_resize(Client *c, Align align, bool grabmod) { + Rectangle *rects; + Rectangle frect, origin; + Align grav; + Cursor cur; + Point d, pt, hr; + float rx, ry, hrx, hry; + uint nrect; + Frame *f; + + f = c->sel; + if(f->client->fullscreen >= 0) { + ungrabpointer(); + return; + } + if(!f->area->floating) { + if(align==Center) + mouse_movegrabbox(c, grabmod); + else + mouse_resizecolframe(f, align); + return; + } + + cur = quad_cursor(align); + if(align == Center) + cur = cursor[CurSizing]; + + if(!grabpointer(c->framewin, nil, cur, MouseMask)) + return; + + origin = f->r; + frect = f->r; + rects = view_rects(f->area->view, &nrect, c->frame); + + pt = querypointer(c->framewin); + rx = (float)pt.x / Dx(frect); + ry = (float)pt.y / Dy(frect); + + pt = querypointer(&scr.root); + + SET(hrx); + SET(hry); + if(align != Center) { + hr = subpt(frect.max, frect.min); + hr = divpt(hr, Pt(2, 2)); + d = hr; + if(align&North) d.y -= hr.y; + if(align&South) d.y += hr.y; + if(align&East) d.x += hr.x; + if(align&West) d.x -= hr.x; + + pt = addpt(d, f->r.min); + warppointer(pt); + }else { + hrx = (double)(Dx(scr.rect) + + Dx(frect) + - 2 * labelh(def.font)) + / Dx(scr.rect); + hry = (double)(Dy(scr.rect) + + Dy(frect) + - 3 * labelh(def.font)) + / Dy(scr.rect); + + pt.x = frect.max.x - labelh(def.font); + pt.y = frect.max.y - labelh(def.font); + d.x = pt.x / hrx; + d.y = pt.y / hry; + + warppointer(d); + } + sync(); + flushevents(PointerMotionMask, false); + + while(readmotion(&d)) { + if(align == Center) { + d.x = (d.x * hrx) - pt.x; + d.y = (d.y * hry) - pt.y; + }else + d = subpt(d, pt); + pt = addpt(pt, d); + + rect_morph(&origin, d, &align); + frect = constrain(origin, -1); + + grav = snap_rect(rects, nrect, &frect, &align, def.snap); + + frect = frame_hints(f, frect, grav); + frect = constrain(frect, -1); + + client_resize(c, frect); + } + + pt = addpt(c->framewin->r.min, + Pt(Dx(frect) * rx, + Dy(frect) * ry)); + if(pt.y > scr.rect.max.y) + pt.y = scr.rect.max.y - 1; + warppointer(pt); + + free(rects); + ungrabpointer(); +} + +static int +pushstack_down(Frame *f, int y) { + int ret; + int dh, dy; + + if(f == nil) + return 0;; + ret = 0; + dy = y - f->colr.min.y; + if(dy < 0) + return 0; + if(!f->collapsed) { + dh = Dy(f->colr) - labelh(def.font); + if(dy <= dh) { + f->colr.min.y += dy; + return dy; + }else { + f->collapsed = true; + f->colr.min.y += dh; + ret = dh; + dy -= dh; + } + } + dy = pushstack_down(f->anext, f->colr.max.y + dy); + f->colr.min.y += dy; + f->colr.max.y += dy; + return ret + dy; +} + +static int +pushstack_up(Frame *f, int y) { + int ret; + int dh, dy; + + if(f == nil) + return 0; + ret = 0; + dy = f->colr.max.y - y; + if(dy < 0) + return 0; + if(!f->collapsed) { + dh = Dy(f->colr) - labelh(def.font); + if(dy <= dh) { + f->colr.max.y -= dy; + return dy; + }else { + f->collapsed = true; + f->colr.max.y -= dh; + ret = dh; + dy -= dh; + } + } + dy = pushstack_up(f->aprev, f->colr.min.y - dy); + f->colr.min.y -= dy; + f->colr.max.y -= dy; + return ret + dy; +} + +static void +mouse_tempvertresize(Area *a, Point p) { + Frame *fa, *fb, *f; + Window *cwin; + Rectangle r; + Point pt; + int incmode, nabove, nbelow; + + if(a->mode != Coldefault) + return; + + for(fa=a->frame; fa; fa=fa->anext) + if(p.y < fa->r.max.y + labelh(def.font)/2) + break; + if(!(fa && fa->anext)) + return; + fb = fa->anext; + nabove=0; + nbelow=0; + for(f=fa; f; f=f->aprev) + nabove++; + for(f=fa->anext; f; f=f->anext) + nbelow++; + + incmode = def.incmode; + def.incmode = IIgnore; + resizing = true; + column_arrange(a, false); + + r.min.x = p.x; + r.max.x = p.x + 1; + r.min.y = a->r.min.y + labelh(def.font) * nabove; + r.max.y = a->r.max.y - labelh(def.font) * nbelow; + cwin = constraintwin(r); + + if(!grabpointer(&scr.root, cwin, cursor[CurDVArrow], MouseMask)) + goto done; + + for(f=a->frame; f; f=f->anext) + f->colr_old = f->colr; + + while(readmotion(&pt)) { + for(f=a->frame; f; f=f->anext) { + f->collapsed = false; + f->colr = f->colr_old; + } + if(pt.y > p.y) + pushstack_down(fb, pt.y); + else + pushstack_up(fa, pt.y); + fa->colr.max.y = pt.y; + fb->colr.min.y = pt.y; + column_frob(a); + } + +done: + ungrabpointer(); + destroyconstraintwin(cwin); + def.incmode = incmode; + resizing = false; + column_arrange(a, false); +} + +void +mouse_checkresize(Frame *f, Point p, bool exec) { + Rectangle r; + Cursor cur; + int q; + + cur = cursor[CurNormal]; + if(rect_haspoint_p(p, f->crect)) { + client_setcursor(f->client, cur); + return; + } + + r = rectsubpt(f->r, f->r.min); + q = quadrant(r, p); + if(rect_haspoint_p(p, f->grabbox)) { + cur = cursor[CurTCross]; + if(exec) + mouse_movegrabbox(f->client, false); + } + else if(f->area->floating) { + if(p.x <= 2 + || p.y <= 2 + || r.max.x - p.x <= 2 + || r.max.y - p.y <= 2) { + cur = quad_cursor(q); + if(exec) + mouse_resize(f->client, q, false); + } + else if(exec && rect_haspoint_p(p, f->titlebar)) + mouse_movegrabbox(f->client, true); + }else { + if(f->aprev && p.y <= 2 + || f->anext && r.max.y - p.y <= 2) { + cur = cursor[CurDVArrow]; + if(exec) + mouse_tempvertresize(f->area, addpt(p, f->r.min)); + } + } + + if(!exec) + client_setcursor(f->client, cur); +} + +static void +_grab(XWindow w, uint button, ulong mod) { + XGrabButton(display, button, mod, w, false, ButtonMask, + GrabModeSync, GrabModeAsync, None, None); +} + +/* Doesn't belong here */ +void +grab_button(XWindow w, uint button, ulong mod) { + _grab(w, button, mod); + if((mod != AnyModifier) && numlock_mask) { + _grab(w, button, mod | numlock_mask); + _grab(w, button, mod | numlock_mask | LockMask); + } +} + diff --git a/cmd/wmii/print.c b/cmd/wmii/print.c new file mode 100644 index 0000000..cddf609 --- /dev/null +++ b/cmd/wmii/print.c @@ -0,0 +1,124 @@ +#include "dat.h" +#include <fmt.h> +#include "fns.h" + +static char* fcnames[] = { + "TVersion", + "RVersion", + "TAuth", + "RAuth", + "TAttach", + "RAttach", + "TError", + "RError", + "TFlush", + "RFlush", + "TWalk", + "RWalk", + "TOpen", + "ROpen", + "TCreate", + "RCreate", + "TRead", + "RRead", + "TWrite", + "RWrite", + "TClunk", + "RClunk", + "TRemove", + "RRemove", + "TStat", + "RStat", + "TWStat", + "RWStat", +}; + +static int +qid(Fmt *f, Qid *q) { + return fmtprint(f, "(%uhd,%uld,%ullx)", q->type, q->version, q->path); +} + +int +Ffmt(Fmt *f) { + Fcall *fcall; + + fcall = va_arg(f->args, Fcall*); + fmtprint(f, "% 2d %s\t", fcall->hdr.tag, fcnames[fcall->hdr.type - TVersion]); + switch(fcall->hdr.type) { + case TVersion: + case RVersion: + fmtprint(f, " msize: %uld version: \"%s\"", (ulong)fcall->version.msize, fcall->version.version); + break; + case TAuth: + fmtprint(f, " afid: %uld uname: \"%s\" aname: \"%s\"", (ulong)fcall->tauth.afid, fcall->tauth.uname, fcall->tauth.aname); + break; + case RAuth: + fmtprint(f, " aqid: "); + qid(f, &fcall->rauth.aqid); + break; + case RAttach: + fmtprint(f, " qid: "); + qid(f, &fcall->rattach.qid); + break; + case TAttach: + fmtprint(f, " fid: %uld afid: %uld uname: \"%s\" aname: \"%s\"", (ulong)fcall->hdr.fid, (ulong)fcall->tattach.afid, fcall->tattach.uname, fcall->tattach.aname); + break; + case RError: + fmtprint(f, " \"%s\"", fcall->error.ename); + break; + case TFlush: + fmtprint(f, " oldtag: %uld", (ulong)fcall->tflush.oldtag); + break; + case TWalk: + fmtprint(f, " newfid: %uld wname: {", (ulong)fcall->twalk.newfid); + for(int i=0; i<fcall->twalk.nwname; i++) { + if(i > 0) fmtprint(f, ", "); + fmtprint(f, "\"%s\"", fcall->twalk.wname[i]); + } + fmtprint(f, "}"); + break; + case RWalk: + fmtprint(f, " wqid: {"); + for(int i=0; i<fcall->rwalk.nwqid; i++) { + if(i > 0) fmtprint(f, ", "); + qid(f, &fcall->rwalk.wqid[i]); + } + fmtprint(f, "}"); + break; + case TOpen: + fmtprint(f, " fid: %uld mode: %ulo", (ulong)fcall->hdr.fid, (ulong)fcall->topen.mode); + break; + case ROpen: + case RCreate: + fmtprint(f, " qid: "); + qid(f, &fcall->ropen.qid); + fmtprint(f, " %uld", (ulong)fcall->ropen.iounit); + break; + case TCreate: + fmtprint(f, " fid: %uld name: \"%s\" perm: %ulo mode: %ulo", (ulong)fcall->hdr.fid, fcall->tcreate.name, (ulong)fcall->tcreate.perm, (ulong)fcall->tcreate.mode); + break; + case TRead: + fmtprint(f, " fid: %uld offset: %ulld count: %uld", (ulong)fcall->hdr.fid, fcall->tread.offset, (ulong)fcall->tread.count); + break; + case RRead: + fmtprint(f, " data: {data: %uld}", fcall->rread.count); + break; + case TWrite: + fmtprint(f, " fid: %uld offset: %ulld data: {data: %uld}", (ulong)fcall->hdr.fid, fcall->twrite.offset, fcall->twrite.count); + break; + case RWrite: + fmtprint(f, " count: %uld", (ulong)fcall->rwrite.count); + break; + case TClunk: + case TRemove: + case TStat: + fmtprint(f, " fid: %uld", (ulong)fcall->hdr.fid); + break; + case RStat: + fmtprint(f, " stat: {data: %uld}", fcall->rstat.nstat); + break; + } + + return 0; +} + diff --git a/cmd/wmii/printevent.c b/cmd/wmii/printevent.c new file mode 100644 index 0000000..5b52c43 --- /dev/null +++ b/cmd/wmii/printevent.c @@ -0,0 +1,994 @@ +/* + * Original code posted to comp.sources.x + * Modifications by Russ Cox <rsc@swtch.com>. + * Further modifications by Kris Maglione <maglione.k at Gmail> + */ + +/* + * Path: uunet!wyse!mikew From: mikew@wyse.wyse.com (Mike Wexler) Newsgroups: + * comp.sources.x Subject: v02i056: subroutine to print events in human + * readable form, Part01/01 Message-ID: <1935@wyse.wyse.com> Date: 22 Dec 88 + * 19:28:25 GMT Organization: Wyse Technology, San Jose Lines: 1093 Approved: + * mikew@wyse.com + * + * Submitted-by: richsun!darkstar!ken Posting-number: Volume 2, Issue 56 + * Archive-name: showevent/part01 + * + * + * There are times during debugging when it would be real useful to be able to + * print the fields of an event in a human readable form. Too many times I + * found myself scrounging around in section 8 of the Xlib manual looking for + * the valid fields for the events I wanted to see, then adding printf's to + * display the numeric values of the fields, and then scanning through X.h + * trying to decode the cryptic detail and state fields. After playing with + * xev, I decided to write a couple of standard functions that I could keep + * in a library and call on whenever I needed a little debugging verbosity. + * The first function, GetType(), is useful for returning the string + * representation of the type of an event. The second function, ShowEvent(), + * is used to display all the fields of an event in a readable format. The + * functions are not complicated, in fact, they are mind-numbingly boring - + * but that's just the point nobody wants to spend the time writing functions + * like this, they just want to have them when they need them. + * + * A simple, sample program is included which does little else but to + * demonstrate the use of these two functions. These functions have saved me + * many an hour during debugging and I hope you find some benefit to these. + * If you have any comments, suggestions, improvements, or if you find any + * blithering errors you can get it touch with me at the following location: + * + * ken@richsun.UUCP + */ + +#include "dat.h" +#include <stdarg.h> +#include <bio.h> +//#include "fns.h" +#include "printevent.h" +#define Window XWindow + +#define nil ((void*)0) + +typedef struct Pair Pair; + +struct Pair { + int key; + char *val; +}; + +static char* sep = " "; + +static char * +search(Pair *lst, int key, char *(*def)(int)) { + for(; lst->val; lst++) + if(lst->key == key) + return lst->val; + return def(key); +} + +static char* +unmask(Pair *list, uint val) +{ + Pair *p; + char *s, *end; + int n; + + buffer[0] = '\0'; + end = buffer + sizeof buffer; + s = buffer; + + n = 0; + s = utfecpy(s, end, "("); + for (p = list; p->val; p++) + if (val & p->key) { + if(n++) + s = utfecpy(s, end, "|"); + s = utfecpy(s, end, p->val); + } + utfecpy(s, end, ")"); + + return buffer; +} + +static char * +strhex(int key) { + sprint(buffer, "0x%x", key); + return buffer; +} + +static char * +strdec(int key) { + sprint(buffer, "%d", key); + return buffer; +} + +static char * +strign(int key) { + USED(key); + + return "?"; +} + +/******************************************************************************/ +/**** Miscellaneous routines to convert values to their string equivalents ****/ +/******************************************************************************/ + +static void +TInt(Fmt *b, va_list *ap) { + fmtprint(b, "%d", va_arg(*ap, int)); +} + +static void +TWindow(Fmt *b, va_list *ap) { + Window w; + + w = va_arg(*ap, Window); + fmtprint(b, "0x%ux", (uint)w); +} + +static void +TData(Fmt *b, va_list *ap) { + long *l; + int i; + + l = va_arg(*ap, long*); + fmtprint(b, "{"); + for (i = 0; i < 5; i++) { + if(i > 0) + fmtprint(b, ", "); + fmtprint(b, "0x%08lx", l[i]); + } + fmtprint(b, "}"); +} + +/* Returns the string equivalent of a timestamp */ +static void +TTime(Fmt *b, va_list *ap) { + ldiv_t d; + ulong msec; + ulong sec; + ulong min; + ulong hr; + ulong day; + Time time; + + time = va_arg(*ap, Time); + + msec = time/1000; + d = ldiv(msec, 60); + msec = time-msec*1000; + + sec = d.rem; + d = ldiv(d.quot, 60); + min = d.rem; + d = ldiv(d.quot, 24); + hr = d.rem; + day = d.quot; + +#ifdef notdef + sprintf(buffer, "%lu day%s %02lu:%02lu:%02lu.%03lu", + day, day == 1 ? "" : "(s)", hr, min, sec, msec); +#endif + + fmtprint(b, "%ludd_%ludh_%ludm_%lud.%03luds", day, hr, min, sec, msec); +} + +/* Returns the string equivalent of a boolean parameter */ +static void +TBool(Fmt *b, va_list *ap) { + static Pair list[] = { + {True, "True"}, + {False, "False"}, + {0, nil}, + }; + Bool key; + + key = va_arg(*ap, Bool); + fmtprint(b, "%s", search(list, key, strign)); +} + +/* Returns the string equivalent of a property notify state */ +static void +TPropState(Fmt *b, va_list *ap) { + static Pair list[] = { + {PropertyNewValue, "PropertyNewValue"}, + {PropertyDelete, "PropertyDelete"}, + {0, nil}, + }; + uint key; + + key = va_arg(*ap, uint); + fmtprint(b, "%s", search(list, key, strign)); +} + +/* Returns the string equivalent of a visibility notify state */ +static void +TVis(Fmt *b, va_list *ap) { + static Pair list[] = { + {VisibilityUnobscured, "VisibilityUnobscured"}, + {VisibilityPartiallyObscured, "VisibilityPartiallyObscured"}, + {VisibilityFullyObscured, "VisibilityFullyObscured"}, + {0, nil}, + }; + int key; + + key = va_arg(*ap, int); + fmtprint(b, "%s", search(list, key, strign)); +} + +/* Returns the string equivalent of a mask of buttons and/or modifier keys */ +static void +TModState(Fmt *b, va_list *ap) { + static Pair list[] = { + {Button1Mask, "Button1Mask"}, + {Button2Mask, "Button2Mask"}, + {Button3Mask, "Button3Mask"}, + {Button4Mask, "Button4Mask"}, + {Button5Mask, "Button5Mask"}, + {ShiftMask, "ShiftMask"}, + {LockMask, "LockMask"}, + {ControlMask, "ControlMask"}, + {Mod1Mask, "Mod1Mask"}, + {Mod2Mask, "Mod2Mask"}, + {Mod3Mask, "Mod3Mask"}, + {Mod4Mask, "Mod4Mask"}, + {Mod5Mask, "Mod5Mask"}, + {0, nil}, + }; + uint state; + + state = va_arg(*ap, uint); + fmtprint(b, "%s", unmask(list, state)); +} + +/* Returns the string equivalent of a mask of configure window values */ +static void +TConfMask(Fmt *b, va_list *ap) { + static Pair list[] = { + {CWX, "CWX"}, + {CWY, "CWY"}, + {CWWidth, "CWWidth"}, + {CWHeight, "CWHeight"}, + {CWBorderWidth, "CWBorderWidth"}, + {CWSibling, "CWSibling"}, + {CWStackMode, "CWStackMode"}, + {0, nil}, + }; + uint valuemask; + + valuemask = va_arg(*ap, uint); + fmtprint(b, "%s", unmask(list, valuemask)); +} + +/* Returns the string equivalent of a motion hint */ +#if 0 +static void +IsHint(Fmt *b, va_list *ap) { + static Pair list[] = { + {NotifyNormal, "NotifyNormal"}, + {NotifyHint, "NotifyHint"}, + {0, nil}, + }; + char key; + + key = va_arg(*ap, char); + fmtprint(b, "%s", search(list, key, strign)); +} +#endif + +/* Returns the string equivalent of an id or the value "None" */ +static void +TIntNone(Fmt *b, va_list *ap) { + static Pair list[] = { + {None, "None"}, + {0, nil}, + }; + int key; + + key = va_arg(*ap, int); + fmtprint(b, "%s", search(list, key, strhex)); +} + +/* Returns the string equivalent of a colormap state */ +static void +TColMap(Fmt *b, va_list *ap) { + static Pair list[] = { + {ColormapInstalled, "ColormapInstalled"}, + {ColormapUninstalled, "ColormapUninstalled"}, + {0, nil}, + }; + int key; + + key = va_arg(*ap, int); + fmtprint(b, "%s", search(list, key, strign)); +} + +/* Returns the string equivalent of a crossing detail */ +static void +TXing(Fmt *b, va_list *ap) { + static Pair list[] = { + {NotifyAncestor, "NotifyAncestor"}, + {NotifyInferior, "NotifyInferior"}, + {NotifyVirtual, "NotifyVirtual"}, + {NotifyNonlinear, "NotifyNonlinear"}, + {NotifyNonlinearVirtual, "NotifyNonlinearVirtual"}, + {0, nil}, + }; + int key; + + key = va_arg(*ap, int); + fmtprint(b, "%s", search(list, key, strign)); +} + +/* Returns the string equivalent of a focus change detail */ +static void +TFocus(Fmt *b, va_list *ap) { + static Pair list[] = { + {NotifyAncestor, "NotifyAncestor"}, + {NotifyInferior, "NotifyInferior"}, + {NotifyVirtual, "NotifyVirtual"}, + {NotifyNonlinear, "NotifyNonlinear"}, + {NotifyNonlinearVirtual, "NotifyNonlinearVirtual"}, + {NotifyPointer, "NotifyPointer"}, + {NotifyPointerRoot, "NotifyPointerRoot"}, + {NotifyDetailNone, "NotifyDetailNone"}, + {0, nil}, + }; + int key; + + key = va_arg(*ap, int); + fmtprint(b, "%s", search(list, key, strign)); +} + +/* Returns the string equivalent of a configure detail */ +static void +TConfDetail(Fmt *b, va_list *ap) { + static Pair list[] = { + {Above, "Above"}, + {Below, "Below"}, + {TopIf, "TopIf"}, + {BottomIf, "BottomIf"}, + {Opposite, "Opposite"}, + {0, nil}, + }; + int key; + + key = va_arg(*ap, int); + fmtprint(b, "%s", search(list, key, strign)); +} + +/* Returns the string equivalent of a grab mode */ +static void +TGrabMode(Fmt *b, va_list *ap) { + static Pair list[] = { + {NotifyNormal, "NotifyNormal"}, + {NotifyGrab, "NotifyGrab"}, + {NotifyUngrab, "NotifyUngrab"}, + {NotifyWhileGrabbed, "NotifyWhileGrabbed"}, + {0, nil}, + }; + int key; + + key = va_arg(*ap, int); + fmtprint(b, "%s", search(list, key, strign)); +} + +/* Returns the string equivalent of a mapping request */ +static void +TMapping(Fmt *b, va_list *ap) { + static Pair list[] = { + {MappingModifier, "MappingModifier"}, + {MappingKeyboard, "MappingKeyboard"}, + {MappingPointer, "MappingPointer"}, + {0, nil}, + }; + int key; + + key = va_arg(*ap, int); + fmtprint(b, "%s", search(list, key, strign)); +} + +/* Returns the string equivalent of a stacking order place */ +static void +TPlace(Fmt *b, va_list *ap) { + static Pair list[] = { + {PlaceOnTop, "PlaceOnTop"}, + {PlaceOnBottom, "PlaceOnBottom"}, + {0, nil}, + }; + int key; + + key = va_arg(*ap, int); + fmtprint(b, "%s", search(list, key, strign)); +} + +/* Returns the string equivalent of a major code */ +static void +TMajor(Fmt *b, va_list *ap) { + static char *list[] = { XMajors }; + char *s; + uint key; + + key = va_arg(*ap, uint); + s = "<nil>"; + if(key < nelem(list)) + s = list[key]; + fmtprint(b, "%s", s); +} + +static char* +eventtype(int key) { + static Pair list[] = { + {ButtonPress, "ButtonPress"}, + {ButtonRelease, "ButtonRelease"}, + {CirculateNotify, "CirculateNotify"}, + {CirculateRequest, "CirculateRequest"}, + {ClientMessage, "ClientMessage"}, + {ColormapNotify, "ColormapNotify"}, + {ConfigureNotify, "ConfigureNotify"}, + {ConfigureRequest, "ConfigureRequest"}, + {CreateNotify, "CreateNotify"}, + {DestroyNotify, "DestroyNotify"}, + {EnterNotify, "EnterNotify"}, + {Expose, "Expose"}, + {FocusIn, "FocusIn"}, + {FocusOut, "FocusOut"}, + {GraphicsExpose, "GraphicsExpose"}, + {GravityNotify, "GravityNotify"}, + {KeyPress, "KeyPress"}, + {KeyRelease, "KeyRelease"}, + {KeymapNotify, "KeymapNotify"}, + {LeaveNotify, "LeaveNotify"}, + {MapNotify, "MapNotify"}, + {MapRequest, "MapRequest"}, + {MappingNotify, "MappingNotify"}, + {MotionNotify, "MotionNotify"}, + {NoExpose, "NoExpose"}, + {PropertyNotify, "PropertyNotify"}, + {ReparentNotify, "ReparentNotify"}, + {ResizeRequest, "ResizeRequest"}, + {SelectionClear, "SelectionClear"}, + {SelectionNotify, "SelectionNotify"}, + {SelectionRequest, "SelectionRequest"}, + {UnmapNotify, "UnmapNotify"}, + {VisibilityNotify, "VisibilityNotify"}, + {0, nil}, + }; + return search(list, key, strdec); +} +/* Returns the string equivalent the keycode contained in the key event */ +static void +TKeycode(Fmt *b, va_list *ap) { + KeySym keysym_str; + XKeyEvent *ev; + char *keysym_name; + + ev = va_arg(*ap, XKeyEvent*); + + XLookupString(ev, buffer, sizeof buffer, &keysym_str, nil); + + if (keysym_str == NoSymbol) + keysym_name = "NoSymbol"; + else + keysym_name = XKeysymToString(keysym_str); + if(keysym_name == nil) + keysym_name = "(no name)"; + + fmtprint(b, "%ud (keysym 0x%x \"%s\")", (int)ev->keycode, + (int)keysym_str, keysym_name); +} + +/* Returns the string equivalent of an atom or "None" */ +static void +TAtom(Fmt *b, va_list *ap) { + char *atom_name; + Atom atom; + + atom = va_arg(*ap, Atom); + atom_name = XGetAtomName(display, atom); + fmtprint(b, "%s", atom_name); + XFree(atom_name); +} + +#define _(m) #m, ev->m +#define TEnd nil +typedef void (*Tfn)(Fmt*, va_list*); + +static int +pevent(Fmt *fmt, void *e, ...) { + va_list ap; + Tfn fn; + XAnyEvent *ev; + char *key; + int n; + + ev = e; + fmtprint(fmt, "%3ld %-20s ", ev->serial, eventtype(ev->type)); + if(ev->send_event) + fmtstrcpy(fmt, "(sendevent) "); + + n = 0; + va_start(ap, e); + for(;;) { + fn = va_arg(ap, Tfn); + if(fn == TEnd) + break; + + if(n++ != 0) + fmtprint(fmt, "%s", sep); + + key = va_arg(ap, char*); + fmtprint(fmt, "%s=", key); + fn(fmt, &ap); + } + va_end(ap); + return 0; +} + +/*****************************************************************************/ +/*** Routines to print out readable values for the field of various events ***/ +/*****************************************************************************/ + +static int +VerbMotion(Fmt *fmt, XEvent *e) { + XMotionEvent *ev = &e->xmotion; + + return pevent(fmt, ev, + TWindow, _(window), + TWindow, _(root), + TWindow, _(subwindow), + TTime, _(time), + TInt, _(x), TInt, _(y), + TInt, _(x_root), TInt, _(y_root), + TModState, _(state), + TBool, _(same_screen), + TEnd + ); + //fprintf(stderr, "is_hint=%s%s", IsHint(ev->is_hint), sep); +} + +static int +VerbButton(Fmt *fmt, XEvent *e) { + XButtonEvent *ev = &e->xbutton; + + return pevent(fmt, ev, + TWindow, _(window), + TWindow, _(root), + TWindow, _(subwindow), + TTime, _(time), + TInt, _(x), TInt, _(y), + TInt, _(x_root), TInt, _(y_root), + TModState, _(state), + TModState, _(button), + TBool, _(same_screen), + TEnd + ); +} + +static int +VerbColormap(Fmt *fmt, XEvent *e) { + XColormapEvent *ev = &e->xcolormap; + + return pevent(fmt, ev, + TWindow, _(window), + TIntNone, _(colormap), + TBool, _(new), + TColMap, _(state), + TEnd + ); +} + +static int +VerbCrossing(Fmt *fmt, XEvent *e) { + XCrossingEvent *ev = &e->xcrossing; + + return pevent(fmt, ev, + TWindow, _(window), + TWindow, _(root), + TWindow, _(subwindow), + TTime, _(time), + TInt, _(x), TInt, _(y), + TInt, _(x_root), TInt, _(y_root), + TGrabMode, _(mode), + TXing, _(detail), + TBool, _(same_screen), + TBool, _(focus), + TModState, _(state), + TEnd + ); +} + +static int +VerbExpose(Fmt *fmt, XEvent *e) { + XExposeEvent *ev = &e->xexpose; + + return pevent(fmt, ev, + TWindow, _(window), + TInt, _(x), TInt, _(y), + TInt, _(width), TInt, _(height), + TInt, _(count), + TEnd + ); +} + +static int +VerbGraphicsExpose(Fmt *fmt, XEvent *e) { + XGraphicsExposeEvent *ev = &e->xgraphicsexpose; + + return pevent(fmt, ev, + TWindow, _(drawable), + TInt, _(x), TInt, _(y), + TInt, _(width), TInt, _(height), + TMajor, _(major_code), + TInt, _(minor_code), + TEnd + ); +} + +static int +VerbNoExpose(Fmt *fmt, XEvent *e) { + XNoExposeEvent *ev = &e->xnoexpose; + + return pevent(fmt, ev, + TWindow, _(drawable), + TMajor, _(major_code), + TInt, _(minor_code), + TEnd + ); +} + +static int +VerbFocus(Fmt *fmt, XEvent *e) { + XFocusChangeEvent *ev = &e->xfocus; + + return pevent(fmt, ev, + TWindow, _(window), + TGrabMode, _(mode), + TFocus, _(detail), + TEnd + ); +} + +static int +VerbKeymap(Fmt *fmt, XEvent *e) { + XKeymapEvent *ev = &e->xkeymap; + int i; + + fmtprint(fmt, "window=0x%x%s", (int)ev->window, sep); + fmtprint(fmt, "key_vector="); + for (i = 0; i < 32; i++) + fmtprint(fmt, "%02x", ev->key_vector[i]); + fmtprint(fmt, "\n"); + return 0; +} + +static int +VerbKey(Fmt *fmt, XEvent *e) { + XKeyEvent *ev = &e->xkey; + + return pevent(fmt, ev, + TWindow, _(window), + TWindow, _(root), + TWindow, _(subwindow), + TTime, _(time), + TInt, _(x), TInt, _(y), + TInt, _(x_root), TInt, _(y_root), + TModState, _(state), + TKeycode, "keycode", ev, + TBool, _(same_screen), + TEnd + ); +} + +static int +VerbProperty(Fmt *fmt, XEvent *e) { + XPropertyEvent *ev = &e->xproperty; + + return pevent(fmt, ev, + TWindow, _(window), + TAtom, _(atom), + TTime, _(time), + TPropState, _(state), + TEnd + ); +} + +static int +VerbResizeRequest(Fmt *fmt, XEvent *e) { + XResizeRequestEvent *ev = &e->xresizerequest; + + return pevent(fmt, ev, + TWindow, _(window), + TInt, _(width), TInt, _(height), + TEnd + ); +} + +static int +VerbCirculate(Fmt *fmt, XEvent *e) { + XCirculateEvent *ev = &e->xcirculate; + + return pevent(fmt, ev, + TWindow, _(event), + TWindow, _(window), + TPlace, _(place), + TEnd + ); +} + +static int +VerbConfigure(Fmt *fmt, XEvent *e) { + XConfigureEvent *ev = &e->xconfigure; + + return pevent(fmt, ev, + TWindow, _(event), + TWindow, _(window), + TInt, _(x), TInt, _(y), + TInt, _(width), TInt, _(height), + TInt, _(border_width), + TIntNone, _(above), + TBool, _(override_redirect), + TEnd + ); +} + +static int +VerbCreateWindow(Fmt *fmt, XEvent *e) { + XCreateWindowEvent *ev = &e->xcreatewindow; + + return pevent(fmt, ev, + TWindow, _(parent), + TWindow, _(window), + TInt, _(x), TInt, _(y), + TInt, _(width), TInt, _(height), + TInt, _(border_width), + TBool, _(override_redirect), + TEnd + ); +} + +static int +VerbDestroyWindow(Fmt *fmt, XEvent *e) { + XDestroyWindowEvent *ev = &e->xdestroywindow; + + return pevent(fmt, ev, + TWindow, _(event), + TWindow, _(window), + TEnd + ); +} + +static int +VerbGravity(Fmt *fmt, XEvent *e) { + XGravityEvent *ev = &e->xgravity; + + return pevent(fmt, ev, + TWindow, _(event), + TWindow, _(window), + TInt, _(x), TInt, _(y), + TEnd + ); +} + +static int +VerbMap(Fmt *fmt, XEvent *e) { + XMapEvent *ev = &e->xmap; + + return pevent(fmt, ev, + TWindow, _(event), + TWindow, _(window), + TBool, _(override_redirect), + TEnd + ); +} + +static int +VerbReparent(Fmt *fmt, XEvent *e) { + XReparentEvent *ev = &e->xreparent; + + return pevent(fmt, ev, + TWindow, _(event), + TWindow, _(window), + TWindow, _(parent), + TInt, _(x), TInt, _(y), + TBool, _(override_redirect), + TEnd + ); +} + +static int +VerbUnmap(Fmt *fmt, XEvent *e) { + XUnmapEvent *ev = &e->xunmap; + + return pevent(fmt, ev, + TWindow, _(event), + TWindow, _(window), + TBool, _(from_configure), + TEnd + ); +} + +static int +VerbCirculateRequest(Fmt *fmt, XEvent *e) { + XCirculateRequestEvent *ev = &e->xcirculaterequest; + + return pevent(fmt, ev, + TWindow, _(parent), + TWindow, _(window), + TPlace, _(place), + TEnd + ); +} + +static int +VerbConfigureRequest(Fmt *fmt, XEvent *e) { + XConfigureRequestEvent *ev = &e->xconfigurerequest; + + return pevent(fmt, ev, + TWindow, _(parent), + TWindow, _(window), + TInt, _(x), TInt, _(y), + TInt, _(width), TInt, _(height), + TInt, _(border_width), + TIntNone, _(above), + TConfDetail, _(detail), + TConfMask, _(value_mask), + TEnd + ); +} + +static int +VerbMapRequest(Fmt *fmt, XEvent *e) { + XMapRequestEvent *ev = &e->xmaprequest; + + return pevent(fmt, ev, + TWindow, _(parent), + TWindow, _(window), + TEnd + ); +} + +static int +VerbClient(Fmt *fmt, XEvent *e) { + XClientMessageEvent *ev = &e->xclient; + + return pevent(fmt, ev, + TWindow, _(window), + TAtom, _(message_type), + TInt, _(format), + TData, "data (as longs)", &ev->data, + TEnd + ); +} + +static int +VerbMapping(Fmt *fmt, XEvent *e) { + XMappingEvent *ev = &e->xmapping; + + return pevent(fmt, ev, + TWindow, _(window), + TMapping, _(request), + TWindow, _(first_keycode), + TWindow, _(count), + TEnd + ); +} + +static int +VerbSelectionClear(Fmt *fmt, XEvent *e) { + XSelectionClearEvent *ev = &e->xselectionclear; + + return pevent(fmt, ev, + TWindow, _(window), + TAtom, _(selection), + TTime, _(time), + TEnd + ); +} + +static int +VerbSelection(Fmt *fmt, XEvent *e) { + XSelectionEvent *ev = &e->xselection; + + return pevent(fmt, ev, + TWindow, _(requestor), + TAtom, _(selection), + TAtom, _(target), + TAtom, _(property), + TTime, _(time), + TEnd + ); +} + +static int +VerbSelectionRequest(Fmt *fmt, XEvent *e) { + XSelectionRequestEvent *ev = &e->xselectionrequest; + + return pevent(fmt, ev, + TWindow, _(owner), + TWindow, _(requestor), + TAtom, _(selection), + TAtom, _(target), + TAtom, _(property), + TTime, _(time), + TEnd + ); +} + +static int +VerbVisibility(Fmt *fmt, XEvent *e) { + XVisibilityEvent *ev = &e->xvisibility; + + return pevent(fmt, ev, + TWindow, _(window), + TVis, _(state), + TEnd + ); +} + +/******************************************************************************/ +/**************** Print the values of all fields for any event ****************/ +/******************************************************************************/ + +typedef struct Handler Handler; +struct Handler { + int key; + int (*fn)(Fmt*, XEvent*); +}; + +int +fmtevent(Fmt *fmt) { + XEvent *e; + XAnyEvent *ev; + /* + fprintf(stderr, "type=%s%s", eventtype(e->xany.type), sep); + fprintf(stderr, "serial=%lu%s", ev->serial, sep); + fprintf(stderr, "send_event=%s%s", TorF(ev->send_event), sep); + fprintf(stderr, "display=0x%p%s", ev->display, sep); + */ + static Handler fns[] = { + {MotionNotify, VerbMotion}, + {ButtonPress, VerbButton}, + {ButtonRelease, VerbButton}, + {ColormapNotify, VerbColormap}, + {EnterNotify, VerbCrossing}, + {LeaveNotify, VerbCrossing}, + {Expose, VerbExpose}, + {GraphicsExpose, VerbGraphicsExpose}, + {NoExpose, VerbNoExpose}, + {FocusIn, VerbFocus}, + {FocusOut, VerbFocus}, + {KeymapNotify, VerbKeymap}, + {KeyPress, VerbKey}, + {KeyRelease, VerbKey}, + {PropertyNotify, VerbProperty}, + {ResizeRequest, VerbResizeRequest}, + {CirculateNotify, VerbCirculate}, + {ConfigureNotify, VerbConfigure}, + {CreateNotify, VerbCreateWindow}, + {DestroyNotify, VerbDestroyWindow}, + {GravityNotify, VerbGravity}, + {MapNotify, VerbMap}, + {ReparentNotify, VerbReparent}, + {UnmapNotify, VerbUnmap}, + {CirculateRequest, VerbCirculateRequest}, + {ConfigureRequest, VerbConfigureRequest}, + {MapRequest, VerbMapRequest}, + {ClientMessage, VerbClient}, + {MappingNotify, VerbMapping}, + {SelectionClear, VerbSelectionClear}, + {SelectionNotify, VerbSelection}, + {SelectionRequest, VerbSelectionRequest}, + {VisibilityNotify, VerbVisibility}, + {0, nil}, + }; + Handler *p; + + e = va_arg(fmt->args, XEvent*); + ev = &e->xany; + + for (p = fns; p->fn; p++) + if (p->key == ev->type) + return p->fn(fmt, e); + return 1; +} + diff --git a/cmd/wmii/printevent.h b/cmd/wmii/printevent.h new file mode 100644 index 0000000..22d6b25 --- /dev/null +++ b/cmd/wmii/printevent.h @@ -0,0 +1,248 @@ +int fmtevent(Fmt *fmt); + +enum { + X_CreateWindow = 1, + X_ChangeWindowAttributes, + X_GetWindowAttributes, + X_DestroyWindow, + X_DestroySubwindows, + X_ChangeSaveSet, + X_ReparentWindow, + X_MapWindow, + X_MapSubwindows, + X_UnmapWindow, + X_UnmapSubwindows, + X_ConfigureWindow, + X_CirculateWindow, + X_GetGeometry, + X_QueryTree, + X_InternAtom, + X_GetAtomName, + X_ChangeProperty, + X_DeleteProperty, + X_GetProperty, + X_ListProperties, + X_SetSelectionOwner, + X_GetSelectionOwner, + X_ConvertSelection, + X_SendEvent, + X_GrabPointer, + X_UngrabPointer, + X_GrabButton, + X_UngrabButton, + X_ChangeActivePointerGrab, + X_GrabKeyboard, + X_UngrabKeyboard, + X_GrabKey, + X_UngrabKey, + X_AllowEvents, + X_GrabServer, + X_UngrabServer, + X_QueryPointer, + X_GetMotionEvents, + X_TranslateCoords, + X_WarpPointer, + X_SetInputFocus, + X_GetInputFocus, + X_QueryKeymap, + X_OpenFont, + X_CloseFont, + X_QueryFont, + X_QueryTextExtents, + X_ListFonts, + X_ListFontsWithInfo, + X_SetFontPath, + X_GetFontPath, + X_CreatePixmap, + X_FreePixmap, + X_CreateGC, + X_ChangeGC, + X_CopyGC, + X_SetDashes, + X_SetClipRectangles, + X_FreeGC, + X_ClearArea, + X_CopyArea, + X_CopyPlane, + X_PolyPoint, + X_PolyLine, + X_PolySegment, + X_PolyRectangle, + X_PolyArc, + X_FillPoly, + X_PolyFillRectangle, + X_PolyFillArc, + X_PutImage, + X_GetImage, + X_PolyText8, + X_PolyText16, + X_ImageText8, + X_ImageText16, + X_CreateColormap, + X_FreeColormap, + X_CopyColormapAndFree, + X_InstallColormap, + X_UninstallColormap, + X_ListInstalledColormaps, + X_AllocColor, + X_AllocNamedColor, + X_AllocColorCells, + X_AllocColorPlanes, + X_FreeColors, + X_StoreColors, + X_StoreNamedColor, + X_QueryColors, + X_LookupColor, + X_CreateCursor, + X_CreateGlyphCursor, + X_FreeCursor, + X_RecolorCursor, + X_QueryBestSize, + X_QueryExtension, + X_ListExtensions, + X_ChangeKeyboardMapping, + X_GetKeyboardMapping, + X_ChangeKeyboardControl, + X_GetKeyboardControl, + X_Bell, + X_ChangePointerControl, + X_GetPointerControl, + X_SetScreenSaver, + X_GetScreenSaver, + X_ChangeHosts, + X_ListHosts, + X_SetAccessControl, + X_SetCloseDownMode, + X_KillClient, + X_RotateProperties, + X_ForceScreenSaver, + X_SetPointerMapping, + X_GetPointerMapping, + X_SetModifierMapping, + X_GetModifierMapping, + X_NoOperation, +}; + +#define XMajors \ + "<nil>",\ + "CreateWindow",\ + "ChangeWindowAttributes",\ + "GetWindowAttributes",\ + "DestroyWindow",\ + "DestroySubwindows",\ + "ChangeSaveSet",\ + "ReparentWindow",\ + "MapWindow",\ + "MapSubwindows",\ + "UnmapWindow",\ + "UnmapSubwindows",\ + "ConfigureWindow",\ + "CirculateWindow",\ + "GetGeometry",\ + "QueryTree",\ + "InternAtom",\ + "GetAtomName",\ + "ChangeProperty",\ + "DeleteProperty",\ + "GetProperty",\ + "ListProperties",\ + "SetSelectionOwner",\ + "GetSelectionOwner",\ + "ConvertSelection",\ + "SendEvent",\ + "GrabPointer",\ + "UngrabPointer",\ + "GrabButton",\ + "UngrabButton",\ + "ChangeActivePointerGrab",\ + "GrabKeyboard",\ + "UngrabKeyboard",\ + "GrabKey",\ + "UngrabKey",\ + "AllowEvents",\ + "GrabServer",\ + "UngrabServer",\ + "QueryPointer",\ + "GetMotionEvents",\ + "TranslateCoords",\ + "WarpPointer",\ + "SetInputFocus",\ + "GetInputFocus",\ + "QueryKeymap",\ + "OpenFont",\ + "CloseFont",\ + "QueryFont",\ + "QueryTextExtents",\ + "ListFonts",\ + "ListFontsWithInfo",\ + "SetFontPath",\ + "GetFontPath",\ + "CreatePixmap",\ + "FreePixmap",\ + "CreateGC",\ + "ChangeGC",\ + "CopyGC",\ + "SetDashes",\ + "SetClipRectangles",\ + "FreeGC",\ + "ClearArea",\ + "CopyArea",\ + "CopyPlane",\ + "PolyPoint",\ + "PolyLine",\ + "PolySegment",\ + "PolyRectangle",\ + "PolyArc",\ + "FillPoly",\ + "PolyFillRectangle",\ + "PolyFillArc",\ + "PutImage",\ + "GetImage",\ + "PolyText8",\ + "PolyText16",\ + "ImageText8",\ + "ImageText16",\ + "CreateColormap",\ + "FreeColormap",\ + "CopyColormapAndFree",\ + "InstallColormap",\ + "UninstallColormap",\ + "ListInstalledColormaps",\ + "AllocColor",\ + "AllocNamedColor",\ + "AllocColorCells",\ + "AllocColorPlanes",\ + "FreeColors",\ + "StoreColors",\ + "StoreNamedColor",\ + "QueryColors",\ + "LookupColor",\ + "CreateCursor",\ + "CreateGlyphCursor",\ + "FreeCursor",\ + "RecolorCursor",\ + "QueryBestSize",\ + "QueryExtension",\ + "ListExtensions",\ + "ChangeKeyboardMapping",\ + "GetKeyboardMapping",\ + "ChangeKeyboardControl",\ + "GetKeyboardControl",\ + "Bell",\ + "ChangePointerControl",\ + "GetPointerControl",\ + "SetScreenSaver",\ + "GetScreenSaver",\ + "ChangeHosts",\ + "ListHosts",\ + "SetAccessControl",\ + "SetCloseDownMode",\ + "KillClient",\ + "RotateProperties",\ + "ForceScreenSaver",\ + "SetPointerMapping",\ + "GetPointerMapping",\ + "SetModifierMapping",\ + "GetModifierMapping",\ + "NoOperation",\ + diff --git a/cmd/wmii/root.c b/cmd/wmii/root.c new file mode 100644 index 0000000..e3e53e7 --- /dev/null +++ b/cmd/wmii/root.c @@ -0,0 +1,89 @@ +/* Copyright ©2008-2010 Kris Maglione <maglione.k at Gmail> + * See LICENSE file for license details. + */ +#include "dat.h" +#include "fns.h" + +static Handlers handlers; + +void +root_init(void) { + WinAttr wa; + + wa.event_mask = EnterWindowMask + | FocusChangeMask + | LeaveWindowMask + | PointerMotionMask + | SubstructureNotifyMask + | SubstructureRedirectMask; + wa.cursor = cursor[CurNormal]; + setwinattr(&scr.root, &wa, + CWEventMask + | CWCursor); + sethandler(&scr.root, &handlers); +} + +static void +enter_event(Window *w, XCrossingEvent *e) { + disp.sel = true; + frame_draw_all(); +} + +static void +leave_event(Window *w, XCrossingEvent *e) { + if(!e->same_screen) { + disp.sel = false; + frame_draw_all(); + } +} + +static void +focusin_event(Window *w, XFocusChangeEvent *e) { + if(e->mode == NotifyGrab) + disp.hasgrab = &c_root; +} + +static void +mapreq_event(Window *w, XMapRequestEvent *e) { + XWindowAttributes wa; + + if(!XGetWindowAttributes(display, e->window, &wa)) + return; + if(wa.override_redirect) { + /* Do I really want these? */ + /* Probably not. + XSelectInput(display, e->window, + PropertyChangeMask | StructureNotifyMask); + */ + return; + } + if(!win2client(e->window)) + client_create(e->window, &wa); +} + +static void +motion_event(Window *w, XMotionEvent *e) { + Rectangle r, r2; + + r = rectsetorigin(Rect(0, 0, 1, 1), Pt(e->x_root, e->y_root)); + r2 = constrain(r, 0); + if(!eqrect(r, r2)) + warppointer(r2.min); +} + +static void +kdown_event(Window *w, XKeyEvent *e) { + + e->state &= valid_mask; + kpress(w->xid, e->state, (KeyCode)e->keycode); +} + +static Handlers handlers = { + .enter = enter_event, + .focusin = focusin_event, + .kdown = kdown_event, + .leave = leave_event, + .mapreq = mapreq_event, + .motion = motion_event, +}; + diff --git a/cmd/wmii/rule.c b/cmd/wmii/rule.c new file mode 100644 index 0000000..b2f838f --- /dev/null +++ b/cmd/wmii/rule.c @@ -0,0 +1,107 @@ +/* Copyright ©2006 Anselm R. Garbe <garbeam at gmail dot com> + * See LICENSE file for license details. + */ + +#include "dat.h" +#include "fns.h" + +void +trim(char *str, const char *chars) { + const char *cp; + char *p, *q; + char c; + + q = str; + for(p=str; *p; p++) { + for(cp=chars; (c = *cp); cp++) + if(*p == c) + break; + if(c == '\0') + *q++ = *p; + } + *q = '\0'; +} + +/* XXX: I hate this. --KM */ +void +update_rules(Rule **rule, const char *data) { + /* basic rule matching language /regex/ -> value + * regex might contain POSIX regex syntax defined in regex(3) */ + enum { + IGNORE, + REGEX, + VALUE, + COMMENT, + }; + int state; + Rule *rul; + char regex[256], value[256]; + char *regex_end = regex + sizeof(regex) - 1; + char *value_end = value + sizeof(value) - 1; + char *r, *v; + const char *p; + char c; + + SET(r); + SET(v); + + if(!data || !strlen(data)) + return; + while((rul = *rule)) { + *rule = rul->next; + free(rul->regex); + free(rul); + } + state = IGNORE; + for(p = data; (c = *p); p++) + switch(state) { + case COMMENT: + if(c == '\n') + state = IGNORE; + break; + case IGNORE: + if(c == '#') + state = COMMENT; + else if(c == '/') { + r = regex; + state = REGEX; + } + else if(c == '>') { + value[0] = 0; + v = value; + state = VALUE; + } + break; + case REGEX: + if(c == '\\' && p[1] == '/') + p++; + else if(c == '/') { + *r = 0; + state = IGNORE; + break; + } + if(r < regex_end) + *r++ = c; + break; + case VALUE: + if(c == '\n' || c == '#' || c == 0) { + *v = 0; + trim(value, " \t"); + *rule = emallocz(sizeof **rule); + (*rule)->regex = regcomp(regex); + if((*rule)->regex) { + utflcpy((*rule)->value, value, sizeof rul->value); + rule = &(*rule)->next; + }else + free(*rule); + state = IGNORE; + if(c == '#') + state = COMMENT; + } + else if(v < value_end) + *v++ = c; + break; + default: /* can't happen */ + die("invalid state"); + } +} diff --git a/cmd/wmii/screen.c b/cmd/wmii/screen.c new file mode 100644 index 0000000..76d89eb --- /dev/null +++ b/cmd/wmii/screen.c @@ -0,0 +1,189 @@ +/* Copyright ©2006-2010 Kris Maglione <maglione.k at Gmail> + * See LICENSE file for license details. + */ +#include "dat.h" +#include <math.h> +#include <stdlib.h> +#include "fns.h" + +#ifdef notdef +void +mapscreens(void) { + WMScreen *s, *ss; + Rectangle r; + int i, j; + +#define frob(left, min, max, x, y) \ + if(Dy(r) > 0) /* If they intersect at some point on this axis */ \ + if(ss->r.min.x < s->r.min.x) { \ + if((!s->left) \ + || (abs(Dy(r)) < abs(s->left.max.x - s->min.x))) \ + s->left = ss; \ + } + + /* Variable hell? Certainly. */ + for(i=0; i < nscreens; i++) { + s = screens[i]; + for(j=0; j < nscreens; j++) { + if(i == j) + continue; + ss = screens[j]; + r = rect_intersection(ss->r, s->r); + frob(left, min, max, x, y); + frob(right, max, min, x, y); + frob(atop, min, max, y, x); + frob(below, max, min, y, x); + } + } +#undef frob +} + +int findscreen(Rectangle, int); +int +findscreen(Rectangle rect, int direction) { + Rectangle r; + WMScreen *ss, *s; + int best, i, j; + + best = -1; +#define frob(min, max, x, y) + if(Dy(r) > 0) /* If they intersect at some point on this axis */ + if(ss->r.min.x < rect.min.x) { + if(best == -1 + || (abs(ss->r.max.x - rect.min.x) < abs(screens[best]->r.max.x - rect.min.x))) + best = s->idx; + } + + /* Variable hell? Certainly. */ + for(i=0; i < nscreens; i++) { + ss = screens[j]; + r = rect_intersection(ss->r, rect); + switch(direction) { + default: + return -1; + case West: + frob(min, max, x, y); + break; + case East: + frob(max, min, x, y); + break; + case North: + frob(min, max, y, x); + break; + case South: + frob(max, min, y, x); + break; + } + } +#undef frob +} +#endif + +static Rectangle +leastthing(Rectangle rect, int direction, Vector_ptr *vec, Rectangle (*key)(void*)) { + Rectangle r; + int i, best, d; + + SET(d); + SET(best); + for(i=0; i < vec->n; i++) { + r = key(vec->ary[i]); + switch(direction) { + case South: d = r.min.y; break; + case North: d = -r.max.y; break; + case East: d = r.min.x; break; + case West: d = -r.max.x; break; + } + if(i == 0 || d < best) + best = d; + } + switch(direction) { + case South: rect.min.y = rect.max.y = best; break; + case North: rect.min.y = rect.max.y = -best; break; + case East: rect.min.x = rect.max.x = best; break; + case West: rect.min.x = rect.max.x = -best; break; + } + return rect; +} + +void* +findthing(Rectangle rect, int direction, Vector_ptr *vec, Rectangle (*key)(void*), bool wrap) { + Rectangle isect; + Rectangle r, bestisect = {0,}, bestr = {0,}; + void *best, *p; + int i, n; + + best = nil; + + /* For the record, I really hate these macros. */ +#define frob(min, max, LT, x, y) \ + if(D##y(isect) > 0) /* If they intersect at some point on this axis */ \ + if(r.min.x LT rect.min.x) { \ + n = abs(r.max.x - rect.min.x) - abs(bestr.max.x - rect.min.x); \ + if(best == nil \ + || n == 0 && D##y(isect) > D##y(bestisect) \ + || n < 0 \ + ) { \ + best = p; \ + bestr = r; \ + bestisect = isect; \ + } \ + } + + /* Variable hell? Certainly. */ + for(i=0; i < vec->n; i++) { + p = vec->ary[i]; + r = key(p); + isect = rect_intersection(rect, r); + switch(direction) { + default: + die("not reached"); + /* Not reached */ + case West: + frob(min, max, <, x, y); + break; + case East: + frob(max, min, >, x, y); + break; + case North: + frob(min, max, <, y, x); + break; + case South: + frob(max, min, >, y, x); + break; + } + } +#undef frob + if(!best && wrap) { + r = leastthing(rect, direction, vec, key); + return findthing(r, direction, vec, key, false); + } + return best; +} + +static int +area(Rectangle r) { + return Dx(r) * Dy(r) * + (Dx(r) < 0 && Dy(r) < 0 ? -1 : 1); +} + +int +ownerscreen(Rectangle r) { + Rectangle isect; + int s, a, best, besta; + + SET(besta); + best = -1; + for(s=0; s < nscreens; s++) { + if(!screens[s]->showing) + continue; + isect = rect_intersection(r, screens[s]->r); + a = area(isect); + if(best < 0 || a > besta) { + besta = a; + best = s; + } + } + return best; +} + diff --git a/cmd/wmii/utf.c b/cmd/wmii/utf.c new file mode 100644 index 0000000..48e2a6d --- /dev/null +++ b/cmd/wmii/utf.c @@ -0,0 +1,60 @@ +/* Public Domain --Kris Maglione */ +#include "dat.h" +#include <errno.h> +#include <iconv.h> +#include <langinfo.h> +#include <string.h> +#include "fns.h" + +char* +toutf8n(const char *str, size_t nstr) { + static iconv_t cd; + static bool haveiconv; + char *buf, *pos; + size_t nbuf, bsize; + + if(cd == nil) { + cd = iconv_open("UTF-8", nl_langinfo(CODESET)); + if((long)cd == -1) + warning("Can't convert from local character encoding to UTF-8"); + else + haveiconv = true; + } + if(!haveiconv) { + buf = emalloc(nstr+1); + memcpy(buf, str, nstr); + buf[nstr+1] = '\0'; + return buf; + } + + iconv(cd, nil, nil, nil, nil); + + bsize = (nstr+1) << 1; + buf = emalloc(bsize); + pos = buf; + nbuf = bsize-1; + /* The (void*) cast is because, while the BSDs declare: + * size_t iconv(iconv_t, const char**, size_t*, char**, size_t*), + * GNU/Linux and POSIX declare: + * size_t iconv(iconv_t, char**, size_t*, char**, size_t*). + * This just happens to be safer than declaring our own + * prototype. + */ + while(iconv(cd, (void*)&str, &nstr, &pos, &nbuf) == -1) + if(errno == E2BIG) { + bsize <<= 1; + nbuf = pos - buf; + buf = erealloc(buf, bsize); + pos = buf + nbuf; + nbuf = bsize - nbuf - 1; + }else + break; + *pos++ = '\0'; + return erealloc(buf, pos-buf); +} + +char* +toutf8(const char *str) { + return toutf8n(str, strlen(str)); +} + diff --git a/cmd/wmii/view.c b/cmd/wmii/view.c new file mode 100644 index 0000000..e853003 --- /dev/null +++ b/cmd/wmii/view.c @@ -0,0 +1,630 @@ +/* Copyright ©2004-2006 Anselm R. Garbe <garbeam at gmail dot com> + * Copyright ©2006-2010 Kris Maglione <maglione.k at Gmail> + * See LICENSE file for license details. + */ +#include "dat.h" +#include "fns.h" + +static bool +empty_p(View *v) { + Frame *f; + Area *a; + char **p; + int cmp; + int s; + + foreach_frame(v, s, a, f) { + cmp = 1; + for(p=f->client->retags; *p; p++) { + cmp = strcmp(*p, v->name); + if(cmp >= 0) + break; + } + if(cmp) + return false; + } + return true; +} + +static void +_view_select(View *v) { + if(selview != v) { + if(selview) + event("UnfocusTag %s\n",selview->name); + selview = v; + event("FocusTag %s\n", v->name); + event("AreaFocus %a\n", v->sel); + ewmh_updateview(); + } +} + +Client* +view_selclient(View *v) { + if(v->sel && v->sel->sel) + return v->sel->sel->client; + return nil; +} + +bool +view_fullscreen_p(View *v, int scrn) { + Frame *f; + + for(f=v->floating->frame; f; f=f->anext) + if(f->client->fullscreen == scrn) + return true; + return false; +} + +View* +view_create(const char *name) { + static ushort id = 1; + View **vp; + Client *c; + View *v; + int i; + + for(vp=&view; *vp; vp=&(*vp)->next) { + i = strcmp((*vp)->name, name); + if(i == 0) + return *vp; + if(i > 0) + break; + } + + v = emallocz(sizeof *v); + v->id = id++; + v->r = emallocz(nscreens * sizeof *v->r); + v->pad = emallocz(nscreens * sizeof *v->pad); + + utflcpy(v->name, name, sizeof v->name); + + event("CreateTag %s\n", v->name); + area_create(v, nil, screen->idx, 0); + + v->areas = emallocz(nscreens * sizeof *v->areas); + + for(i=0; i < nscreens; i++) + view_init(v, i); + + + area_focus(v->firstarea); + + v->next = *vp; + *vp = v; + + /* FIXME: Belongs elsewhere */ + /* FIXME: Can do better. */ + for(c=client; c; c=c->next) + if(c != kludge) + client_applytags(c, c->tags); + + view_arrange(v); + if(!selview) + _view_select(v); + ewmh_updateviews(); + return v; +} + +void +view_init(View *v, int iscreen) { + v->r[iscreen] = screens[iscreen]->r; + v->areas[iscreen] = nil; + column_new(v, nil, iscreen, 0); +} + +void +view_destroy(View *v) { + View **vp; + Frame *f; + View *tv; + Area *a; + int s; + + if(v->dead) + return; + v->dead = true; + + for(vp=&view; *vp; vp=&(*vp)->next) + if(*vp == v) break; + *vp = v->next; + assert(v != v->next); + + /* Detach frames held here by regex tags. */ + /* FIXME: Can do better. */ + foreach_frame(v, s, a, f) + client_applytags(f->client, f->client->tags); + + foreach_area(v, s, a) + area_destroy(a); + + event("DestroyTag %s\n", v->name); + + if(v == selview) { + for(tv=view; tv; tv=tv->next) + if(tv->next == *vp) break; + if(tv == nil) + tv = view; + if(tv) + view_focus(screen, tv); + } + free(v->areas); + free(v->r); + free(v); + ewmh_updateviews(); +} + +Area* +view_findarea(View *v, int screen, int idx, bool create) { + Area *a; + + assert(screen >= 0 && screen < nscreens); + + for(a=v->areas[screen]; a && --idx > 0; a=a->next) + if(create && a->next == nil) + return area_create(v, a, screen, 0); + return a; +} + +static void +frames_update_sel(View *v) { + Frame *f; + Area *a; + int s; + + foreach_frame(v, s, a, f) + f->client->sel = f; +} + +/* Don't let increment hints take up more than half + * of the screen, in either direction. + */ +static Rectangle +fix_rect(Rectangle old, Rectangle new) { + double r; + + new = rect_intersection(new, old); + + r = (Dy(old) - Dy(new)) / Dy(old); + if(r > .5) { + r -= .5; + new.min.y -= r * (new.min.y - old.min.y); + new.max.y += r * (old.max.y - new.max.y); + } + r = (Dx(old) - Dx(new)) / Dx(old); + if(r > .5) { + r -= .5; + new.min.x -= r * (new.min.x - old.min.x); + new.max.x += r * (old.max.x - new.max.x); + } + return new; +} + +void +view_update_rect(View *v) { + static Vector_rect vec; + static Vector_rect *vp; + Rectangle r, sr, rr, brect, scrnr; + WMScreen *scrn; + Strut *strut; + Frame *f; + int s, i; + /* These short variable names are hell, eh? */ + + /* XXX: + if(v != selview) + return false; + */ + vec.n = 0; + for(f=v->floating->frame; f; f=f->anext) { + strut = f->client->strut; + if(!strut) + continue; + vector_rpush(&vec, strut->top); + vector_rpush(&vec, strut->left); + vector_rpush(&vec, rectaddpt(strut->right, Pt(scr.rect.max.x, 0))); + vector_rpush(&vec, rectaddpt(strut->bottom, Pt(0, scr.rect.max.y))); + } + /* Find the largest screen space not occupied by struts. */ + vp = unique_rects(&vec, scr.rect); + scrnr = max_rect(vp); + + /* FIXME: Multihead. */ + v->floating->r = scr.rect; + + for(s=0; s < nscreens; s++) { + scrn = screens[s]; + r = fix_rect(scrn->r, scrnr); + + /* Ugly. Very, very ugly. */ + /* + * Try to find some rectangle near the edge of the + * screen where the bar will fit. This way, for + * instance, a system tray can be placed there + * without taking up too much extra screen real + * estate. + */ + rr = r; + brect = scrn->brect; + for(i=0; i < vp->n; i++) { + sr = rect_intersection(vp->ary[i], scrn->r); + if(Dx(sr) < Dx(r)/2 || Dy(sr) < Dy(brect)) + continue; + if(scrn->barpos == BTop && sr.min.y < rr.min.y + || scrn->barpos != BTop && sr.max.y > rr.max.y) + rr = sr; + } + if(scrn->barpos == BTop) { + bar_sety(scrn, rr.min.y); + r.min.y = max(r.min.y, scrn->brect.max.y); + }else { + bar_sety(scrn, rr.max.y - Dy(brect)); + r.max.y = min(r.max.y, scrn->brect.min.y); + } + bar_setbounds(scrn, rr.min.x, rr.max.x); + v->r[s] = r; + } +} + +void +view_update(View *v) { + Client *c; + Frame *f; + Area *a; + int s; + + if(v != selview) + return; + if(starting) + return; + + frames_update_sel(v); + + foreach_frame(v, s, a, f) + if(f->client->fullscreen >= 0) { + f->collapsed = false; + if(!f->area->floating) { + f->oldarea = area_idx(f->area); + f->oldscreen = f->area->screen; + area_moveto(v->floating, f); + area_setsel(v->floating, f); + }else if(f->oldarea == -1) + f->oldarea = 0; + } + + view_arrange(v); + + for(c=client; c; c=c->next) { + f = c->sel; + if((f && f->view == v) + && (f->area == v->sel || !(f->area && f->area->max && f->area->floating))) { + if(f->area) + client_resize(c, f->r); + }else { + unmap_frame(c); + client_unmap(c, IconicState); + } + ewmh_updatestate(c); + ewmh_updateclient(c); + } + + view_restack(v); + if(!v->sel->floating && view_fullscreen_p(v, v->sel->screen)) + area_focus(v->floating); + else + area_focus(v->sel); + frame_draw_all(); +} + +void +view_focus(WMScreen *s, View *v) { + + USED(s); + + _view_select(v); + view_update(v); +} + +void +view_select(const char *arg) { + char buf[256]; + + utflcpy(buf, arg, sizeof buf); + trim(buf, " \t+/"); + + if(buf[0] == '\0') + return; + if(!strcmp(buf, ".") || !strcmp(buf, "..")) + return; + + _view_select(view_create(buf)); + view_update_all(); /* performs view_focus */ +} + +void +view_attach(View *v, Frame *f) { + Client *c; + Frame *ff; + Area *a, *oldsel; + + c = f->client; + + oldsel = v->oldsel; + a = v->sel; + if(client_floats_p(c)) { + if(v->sel != v->floating && c->fullscreen < 0) + oldsel = v->sel; + a = v->floating; + } + else if((ff = client_groupframe(c, v))) + a = ff->area; + else if(v->sel->floating) { + if(v->oldsel) + a = v->oldsel; + /* Don't float a frame when starting or when its + * last focused frame didn't float. Important when + * tagging with +foo. + */ + else if(starting + || c->sel && c->sel->area && !c->sel->area->floating) + a = v->firstarea; + } + if(!a->floating && view_fullscreen_p(v, a->screen)) + a = v->floating; + + area_attach(a, f); + /* TODO: Decide whether to focus this frame */ + bool newgroup = !c->group + || c->group->ref == 1 + || view_selclient(v) + && view_selclient(v)->group == c->group + || group_leader(c->group) + && !client_viewframe(group_leader(c->group), + c->sel->view); + USED(newgroup); + + if(!(c->w.ewmh.type & (TypeSplash|TypeDock))) { + if(!(c->tagre.regex && regexec(c->tagre.regc, v->name, nil, 0))) + frame_focus(f); + else if(c->group && f->area->sel->client->group == c->group) + /* XXX: Stack. */ + area_setsel(f->area, f); + } + + if(oldsel) + v->oldsel = oldsel; + + if(c->sel == nil) + c->sel = f; + view_update(v); +} + +void +view_detach(Frame *f) { + Client *c; + View *v; + + v = f->view; + c = f->client; + + area_detach(f); + if(c->sel == f) + c->sel = f->cnext; + + if(v == selview) + view_update(v); + else if(empty_p(v)) + view_destroy(v); +} + +char** +view_names(void) { + Vector_ptr vec; + View *v; + + vector_pinit(&vec); + for(v=view; v; v=v->next) + vector_ppush(&vec, v->name); + vector_ppush(&vec, nil); + return erealloc(vec.ary, vec.n * sizeof *vec.ary); +} + +void +view_restack(View *v) { + static Vector_long wins; + Divide *d; + Frame *f; + Area *a; + int s; + + if(v != selview) + return; + + wins.n = 0; + + /* *sigh */ + for(f=v->floating->stack; f; f=f->snext) + if(f->client->w.ewmh.type & TypeDock) + vector_lpush(&wins, f->client->framewin->xid); + else + break; + + for(; f; f=f->snext) + vector_lpush(&wins, f->client->framewin->xid); + + for(int s=0; s < nscreens; s++) + vector_lpush(&wins, screens[s]->barwin->xid); + + for(d = divs; d && d->w->mapped; d = d->next) + vector_lpush(&wins, d->w->xid); + + foreach_column(v, s, a) + if(a->frame) { + vector_lpush(&wins, a->sel->client->framewin->xid); + for(f=a->frame; f; f=f->anext) + if(f != a->sel) + vector_lpush(&wins, f->client->framewin->xid); + } + + ewmh_updatestacking(); + if(wins.n) + XRestackWindows(display, (ulong*)wins.ary, wins.n); +} + +void +view_scale(View *v, int scrn, int width) { + uint xoff, numcol; + uint minwidth; + Area *a; + float scale; + int dx, minx; + + minwidth = column_minwidth(); + minx = v->r[scrn].min.x + v->pad[scrn].min.x; + + if(!v->areas[scrn]) + return; + + numcol = 0; + dx = 0; + for(a=v->areas[scrn]; a; a=a->next) { + numcol++; + dx += Dx(a->r); + } + + scale = (float)width / dx; + xoff = minx; + for(a=v->areas[scrn]; a; a=a->next) { + a->r.max.x = xoff + Dx(a->r) * scale; + a->r.min.x = xoff; + if(!a->next) + a->r.max.x = v->r[scrn].min.x + width; + xoff = a->r.max.x; + } + + if(numcol * minwidth > width) + return; + + xoff = minx; + for(a=v->areas[scrn]; a; a=a->next) { + a->r.min.x = xoff; + + if(Dx(a->r) < minwidth) + a->r.max.x = xoff + minwidth; + if(!a->next) + a->r.max.x = minx + width; + xoff = a->r.max.x; + } +} + +/* XXX: Multihead. */ +void +view_arrange(View *v) { + Area *a; + int s; + + if(!v->firstarea) + return; + + view_update_rect(v); + for(s=0; s < nscreens; s++) + view_scale(v, s, Dx(v->r[s]) + Dx(v->pad[s])); + foreach_area(v, s, a) { + if(a->floating) + continue; + /* This is wrong... */ + a->r.min.y = v->r[s].min.y; + a->r.max.y = v->r[s].max.y; + column_arrange(a, false); + } + if(v == selview) + div_update_all(); +} + +Rectangle* +view_rects(View *v, uint *num, Frame *ignore) { + Vector_rect result; + Frame *f; + int i; + + vector_rinit(&result); + + for(f=v->floating->frame; f; f=f->anext) + if(f != ignore) + vector_rpush(&result, f->r); + for(i=0; i < nscreens; i++) { + vector_rpush(&result, v->r[i]); + vector_rpush(&result, screens[i]->r); + } + + *num = result.n; + return result.ary; +} + +void +view_update_all(void) { + View *n, *v, *old; + + old = selview; + for(v=view; v; v=v->next) + frames_update_sel(v); + + for(v=view; v; v=n) { + n=v->next; + if(v != old && empty_p(v)) + view_destroy(v); + } + + view_update(selview); +} + +uint +view_newcolwidth(View *v, int num) { + Rule *r; + char *toks[16]; + char buf[sizeof r->value]; + ulong n; + + for(r=def.colrules.rule; r; r=r->next) + if(regexec(r->regex, v->name, nil, 0)) { + utflcpy(buf, r->value, sizeof buf); + n = tokenize(toks, 16, buf, '+'); + if(num < n) + if(getulong(toks[num], &n)) + return Dx(v->screenr) * (n / 100.0); /* XXX: Multihead. */ + break; + } + return 0; +} + +char* +view_index(View *v) { + Rectangle *r; + Frame *f; + Area *a; + int s; + + bufclear(); + foreach_area(v, s, a) { + if(a->floating) + bufprint("# %a %d %d\n", a, Dx(a->r), Dy(a->r)); + else + bufprint("# %a %d %d\n", a, a->r.min.x, Dx(a->r)); + + for(f=a->frame; f; f=f->anext) { + r = &f->r; + if(a->floating) + bufprint("%a %C %d %d %d %d %s\n", + a, f->client, + r->min.x, r->min.y, + Dx(*r), Dy(*r), + f->client->props); + else + bufprint("%a %C %d %d %s\n", + a, f->client, + r->min.y, Dy(*r), + f->client->props); + } + } + return buffer; +} + diff --git a/cmd/wmii/x11.c b/cmd/wmii/x11.c new file mode 100644 index 0000000..dafe85c --- /dev/null +++ b/cmd/wmii/x11.c @@ -0,0 +1,1317 @@ +/* Copyright ©2007-2010 Kris Maglione <maglione.k at Gmail> + * See LICENSE file for license details. + */ +#define _X11_VISIBLE +#define pointerwin __pointerwin +#include "dat.h" +#include <limits.h> +#include <math.h> +#include <strings.h> +#include <unistd.h> +#include <bio.h> +#include "fns.h" +#undef pointerwin + +const Point ZP = {0, 0}; +const Rectangle ZR = {{0, 0}, {0, 0}}; + +const Window _pointerwin = { .xid = PointerRoot }; +Window* const pointerwin = (Window*)&_pointerwin; + +static Map windowmap; +static Map atommap; +static MapEnt* wbucket[137]; +static MapEnt* abucket[137]; + +static int errorhandler(Display*, XErrorEvent*); +static int (*xlib_errorhandler) (Display*, XErrorEvent*); + +static XftColor* xftcolor(Color); + + +/* Rectangles/Points */ +XRectangle +XRect(Rectangle r) { + XRectangle xr; + + xr.x = r.min.x; + xr.y = r.min.y; + xr.width = Dx(r); + xr.height = Dy(r); + return xr; +} + +int +eqrect(Rectangle a, Rectangle b) { + return a.min.x==b.min.x && a.max.x==b.max.x + && a.min.y==b.min.y && a.max.y==b.max.y; +} + +int +eqpt(Point p, Point q) { + return p.x==q.x && p.y==q.y; +} + +Point +addpt(Point p, Point q) { + p.x += q.x; + p.y += q.y; + return p; +} + +Point +subpt(Point p, Point q) { + p.x -= q.x; + p.y -= q.y; + return p; +} + +Point +mulpt(Point p, Point q) { + p.x *= q.x; + p.y *= q.y; + return p; +} + +Point +divpt(Point p, Point q) { + p.x /= q.x; + p.y /= q.y; + return p; +} + +Rectangle +insetrect(Rectangle r, int n) { + r.min.x += n; + r.min.y += n; + r.max.x -= n; + r.max.y -= n; + return r; +} + +Rectangle +rectaddpt(Rectangle r, Point p) { + r.min.x += p.x; + r.max.x += p.x; + r.min.y += p.y; + r.max.y += p.y; + return r; +} + +Rectangle +rectsubpt(Rectangle r, Point p) { + r.min.x -= p.x; + r.max.x -= p.x; + r.min.y -= p.y; + r.max.y -= p.y; + return r; +} + +Rectangle +rectsetorigin(Rectangle r, Point p) { + Rectangle ret; + + ret.min.x = p.x; + ret.min.y = p.y; + ret.max.x = p.x + Dx(r); + ret.max.y = p.y + Dy(r); + return ret; +} + +/* Formatters */ +static int +Afmt(Fmt *f) { + Atom a; + char *s; + int i; + + a = va_arg(f->args, Atom); + s = XGetAtomName(display, a); + i = fmtprint(f, "%s", s); + free(s); + return i; +} + +static int +Rfmt(Fmt *f) { + Rectangle r; + + r = va_arg(f->args, Rectangle); + return fmtprint(f, "%P+%dx%d", r.min, Dx(r), Dy(r)); +} + +static int +Pfmt(Fmt *f) { + Point p; + + p = va_arg(f->args, Point); + return fmtprint(f, "(%d,%d)", p.x, p.y); +} + +static int +Wfmt(Fmt *f) { + Window *w; + + w = va_arg(f->args, Window*); + return fmtprint(f, "0x%ulx", w->xid); +} + +/* Init */ +void +initdisplay(void) { + display = XOpenDisplay(nil); + if(display == nil) + fatal("Can't open display"); + scr.screen = DefaultScreen(display); + scr.colormap = DefaultColormap(display, scr.screen); + scr.visual = DefaultVisual(display, scr.screen); + scr.visual32 = DefaultVisual(display, scr.screen); + scr.gc = DefaultGC(display, scr.screen); + scr.depth = DefaultDepth(display, scr.screen); + + scr.white = WhitePixel(display, scr.screen); + scr.black = BlackPixel(display, scr.screen); + + scr.root.xid = RootWindow(display, scr.screen); + scr.root.r = Rect(0, 0, + DisplayWidth(display, scr.screen), + DisplayHeight(display, scr.screen)); + scr.rect = scr.root.r; + + scr.root.parent = &scr.root; + + windowmap.bucket = wbucket; + windowmap.nhash = nelem(wbucket); + atommap.bucket = abucket; + atommap.nhash = nelem(abucket); + + fmtinstall('A', Afmt); + fmtinstall('R', Rfmt); + fmtinstall('P', Pfmt); + fmtinstall('W', Wfmt); + + xlib_errorhandler = XSetErrorHandler(errorhandler); +} + +/* Error handling */ + +extern ErrorCode ignored_xerrors[]; +static bool _trap_errors; +static long nerrors; + +static int +errorhandler(Display *dpy, XErrorEvent *error) { + ErrorCode *e; + + USED(dpy); + + if(_trap_errors) + nerrors++; + + e = ignored_xerrors; + if(e) + for(; e->rcode || e->ecode; e++) + if((e->rcode == 0 || e->rcode == error->request_code) + && (e->ecode == 0 || e->ecode == error->error_code)) + return 0; + + fprint(2, "%s: fatal error: Xrequest code=%d, Xerror code=%d\n", + argv0, error->request_code, error->error_code); + return xlib_errorhandler(display, error); /* calls exit() */ +} + +int +traperrors(bool enable) { + + sync(); + _trap_errors = enable; + if (enable) + nerrors = 0; + return nerrors; + +} + +/* Images */ +Image* +allocimage(int w, int h, int depth) { + Image *img; + + img = emallocz(sizeof *img); + img->type = WImage; + img->xid = XCreatePixmap(display, scr.root.xid, w, h, depth); + img->gc = XCreateGC(display, img->xid, 0, nil); + img->colormap = scr.colormap; + img->visual = scr.visual; + if(depth == 32) + img->visual = scr.visual32; + img->depth = depth; + img->r = Rect(0, 0, w, h); + return img; +} + +void +freeimage(Image *img) { + if(img == nil) + return; + + assert(img->type == WImage); + + if(img->xft) + XftDrawDestroy(img->xft); + XFreePixmap(display, img->xid); + XFreeGC(display, img->gc); + free(img); +} + +static XftDraw* +xftdrawable(Image *img) { + if(img->xft == nil) + img->xft = XftDrawCreate(display, img->xid, img->visual, img->colormap); + return img->xft; +} + +/* Windows */ +Window* +createwindow_visual(Window *parent, Rectangle r, + int depth, Visual *vis, uint class, + WinAttr *wa, int valmask) { + Window *w; + + assert(parent->type == WWindow); + + w = emallocz(sizeof *w); + w->visual = vis; + w->type = WWindow; + w->parent = parent; + if(valmask & CWColormap) + w->colormap = wa->colormap; + + w->xid = XCreateWindow(display, parent->xid, r.min.x, r.min.y, Dx(r), Dy(r), + 0 /* border */, depth, class, vis, valmask, wa); +#if 0 + print("createwindow_visual(%W, %R, %d, %p, %ud, %p, %x) = %W\n", + parent, r, depth, vis, class, wa, valmask, w); +#endif + if(class != InputOnly) + w->gc = XCreateGC(display, w->xid, 0, nil); + + w->r = r; + w->depth = depth; + return w; +} + +Window* +createwindow(Window *parent, Rectangle r, int depth, uint class, WinAttr *wa, int valmask) { + return createwindow_visual(parent, r, depth, scr.visual, class, wa, valmask); +} + +Window* +window(XWindow xw) { + Window *w; + + w = malloc(sizeof *w); + w->type = WWindow; + w->xid = xw; + return freelater(w); +} + +void +reparentwindow(Window *w, Window *par, Point p) { + assert(w->type == WWindow); + XReparentWindow(display, w->xid, par->xid, p.x, p.y); + w->parent = par; + w->r = rectsubpt(w->r, w->r.min); + w->r = rectaddpt(w->r, p); +} + +void +destroywindow(Window *w) { + assert(w->type == WWindow); + sethandler(w, nil); + if(w->xft) + XftDrawDestroy(w->xft); + if(w->gc) + XFreeGC(display, w->gc); + XDestroyWindow(display, w->xid); + free(w); +} + +void +setwinattr(Window *w, WinAttr *wa, int valmask) { + assert(w->type == WWindow); + XChangeWindowAttributes(display, w->xid, valmask, wa); +} + +void +selectinput(Window *w, long mask) { + XSelectInput(display, w->xid, mask); +} + +static void +configwin(Window *w, Rectangle r, int border) { + XWindowChanges wc; + + if(eqrect(r, w->r) && border == w->border) + return; + + wc.x = r.min.x - border; + wc.y = r.min.y - border; + wc.width = Dx(r); + wc.height = Dy(r); + wc.border_width = border; + XConfigureWindow(display, w->xid, CWX|CWY|CWWidth|CWHeight|CWBorderWidth, &wc); + + w->r = r; + w->border = border; +} + +void +setborder(Window *w, int width, Color col) { + + assert(w->type == WWindow); + if(width) + XSetWindowBorder(display, w->xid, col.pixel); + if(width != w->border) + configwin(w, w->r, width); +} + +void +reshapewin(Window *w, Rectangle r) { + assert(w->type == WWindow); + assert(Dx(r) > 0 && Dy(r) > 0); /* Rather than an X error. */ + + configwin(w, r, w->border); +} + +void +movewin(Window *w, Point pt) { + Rectangle r; + + assert(w->type == WWindow); + r = rectsetorigin(w->r, pt); + reshapewin(w, r); +} + +int +mapwin(Window *w) { + assert(w->type == WWindow); + if(!w->mapped) { + XMapWindow(display, w->xid); + w->mapped = 1; + return 1; + } + return 0; +} + +int +unmapwin(Window *w) { + assert(w->type == WWindow); + if(w->mapped) { + XUnmapWindow(display, w->xid); + w->mapped = 0; + w->unmapped++; + return 1; + } + return 0; +} + +void +raisewin(Window *w) { + assert(w->type == WWindow); + XRaiseWindow(display, w->xid); +} + +void +lowerwin(Window *w) { + assert(w->type == WWindow); + XLowerWindow(display, w->xid); +} + +Handlers* +sethandler(Window *w, Handlers *new) { + Handlers *old; + void **e; + + assert(w->type == WWindow); + assert((w->prev != nil && w->next != nil) || w->next == w->prev); + + if(new == nil) + map_rm(&windowmap, (ulong)w->xid); + else { + e = map_get(&windowmap, (ulong)w->xid, true); + *e = w; + } + old = w->handler; + w->handler = new; + return old; +} + +Window* +findwin(XWindow w) { + void **e; + + e = map_get(&windowmap, (ulong)w, false); + if(e) + return *e; + return nil; +} + +/* Shape */ +void +setshapemask(Window *dst, Image *src, Point pt) { + /* Assumes that we have the shape extension... */ + XShapeCombineMask (display, dst->xid, + ShapeBounding, pt.x, pt.y, src->xid, ShapeSet); +} + +static void +setgccol(Image *dst, Color col) { + XSetForeground(display, dst->gc, col.pixel); +} + +/* Drawing */ +void +border(Image *dst, Rectangle r, int w, Color col) { + if(w == 0) + return; + + r = insetrect(r, w/2); + r.max.x -= w%2; + r.max.y -= w%2; + + XSetLineAttributes(display, dst->gc, w, LineSolid, CapButt, JoinMiter); + setgccol(dst, col); + XDrawRectangle(display, dst->xid, dst->gc, + r.min.x, r.min.y, Dx(r), Dy(r)); +} + +void +fill(Image *dst, Rectangle r, Color col) { + setgccol(dst, col); + XFillRectangle(display, dst->xid, dst->gc, + r.min.x, r.min.y, Dx(r), Dy(r)); +} + +static XPoint* +convpts(Point *pt, int np) { + XPoint *rp; + int i; + + rp = emalloc(np * sizeof *rp); + for(i = 0; i < np; i++) { + rp[i].x = pt[i].x; + rp[i].y = pt[i].y; + } + return rp; +} + +void +drawpoly(Image *dst, Point *pt, int np, int cap, int w, Color col) { + XPoint *xp; + + xp = convpts(pt, np); + XSetLineAttributes(display, dst->gc, w, LineSolid, cap, JoinMiter); + setgccol(dst, col); + XDrawLines(display, dst->xid, dst->gc, xp, np, CoordModeOrigin); + free(xp); +} + +void +fillpoly(Image *dst, Point *pt, int np, Color col) { + XPoint *xp; + + xp = convpts(pt, np); + setgccol(dst, col); + XFillPolygon(display, dst->xid, dst->gc, xp, np, Complex, CoordModeOrigin); + free(xp); +} + +void +drawline(Image *dst, Point p1, Point p2, int cap, int w, Color col) { + XSetLineAttributes(display, dst->gc, w, LineSolid, cap, JoinMiter); + setgccol(dst, col); + XDrawLine(display, dst->xid, dst->gc, p1.x, p1.y, p2.x, p2.y); +} + +uint +drawstring(Image *dst, Font *font, + Rectangle r, Align align, + char *text, Color col) { + Rectangle tr; + char *buf; + uint x, y, width, height, len; + int shortened; + + shortened = 0; + + len = strlen(text); + buf = emalloc(len+1); + memcpy(buf, text, len+1); + + r.max.y -= font->pad.min.y; + r.min.y += font->pad.max.y; + + height = font->ascent + font->descent; + y = r.min.y + Dy(r) / 2 - height / 2 + font->ascent; + + width = Dx(r) - font->pad.min.x - font->pad.max.x - (font->height & ~1); + + r.min.x += font->pad.min.x; + r.max.x -= font->pad.max.x; + + /* shorten text if necessary */ + tr = ZR; + while(len > 0) { + tr = textextents_l(font, buf, len + min(shortened, 3), nil); + if(Dx(tr) <= width) + break; + while(len > 0 && (buf[--len]&0xC0) == 0x80) + buf[len] = '.'; + buf[len] = '.'; + shortened++; + } + + if(len == 0 || Dx(tr) > width) + goto done; + + /* mark shortened info in the string */ + if(shortened) + len += min(shortened, 3); + + switch (align) { + case East: + x = r.max.x - (tr.max.x + (font->height / 2)); + break; + case Center: + x = r.min.x + (Dx(r) - Dx(tr)) / 2 - tr.min.x; + break; + default: + x = r.min.x + (font->height / 2) - tr.min.x; + break; + } + + setgccol(dst, col); + switch(font->type) { + case FFontSet: + Xutf8DrawString(display, dst->xid, + font->font.set, dst->gc, + x, y, + buf, len); + break; + case FXft: + XftDrawStringUtf8(xftdrawable(dst), xftcolor(col), + font->font.xft, + x, y, (uchar*)buf, len); + break; + case FX11: + XSetFont(display, dst->gc, font->font.x11->fid); + XDrawString(display, dst->xid, dst->gc, + x, y, buf, len); + break; + default: + die("Invalid font type."); + } + +done: + free(buf); + return Dx(tr); +} + +void +copyimage(Image *dst, Rectangle r, Image *src, Point p) { + XCopyArea(display, + src->xid, dst->xid, + dst->gc, + r.min.x, r.min.y, Dx(r), Dy(r), + p.x, p.y); +} + +/* Colors */ +bool +namedcolor(char *name, Color *ret) { + XColor c, c2; + + if(XAllocNamedColor(display, scr.colormap, name, &c, &c2)) { + *ret = (Color) { + c.pixel, { + c.red, + c.green, + c.blue, + 0xffff + }, + }; + return true; + } + return false; +} + +bool +loadcolor(CTuple *c, char *str) { + char buf[24]; + + utflcpy(buf, str, sizeof buf); + memcpy(c->colstr, str, sizeof c->colstr); + + buf[7] = buf[15] = buf[23] = '\0'; + return namedcolor(buf, &c->fg) + && namedcolor(buf+8, &c->bg) + && namedcolor(buf+16, &c->border); +} + +static XftColor* +xftcolor(Color col) { + XftColor *c; + + c = emallocz(sizeof *c); + *c = (XftColor) { + ((col.render.alpha&0xff00) << 24) + | ((col.render.red&0xff00) << 8) + | ((col.render.green&0xff00) << 0) + | ((col.render.blue&0xff00) >> 8), + col.render + }; + return freelater(c); +} + +/* Fonts */ +Font* +loadfont(char *name) { + XFontStruct **xfonts; + char **missing, **font_names; + Biobuf *b; + Font *f; + int n, i; + + missing = nil; + f = emallocz(sizeof *f); + f->name = estrdup(name); + if(!strncmp(f->name, "xft:", 4)) { + f->type = FXft; + + f->font.xft = XftFontOpenXlfd(display, scr.screen, f->name + 4); + if(!f->font.xft) + f->font.xft = XftFontOpenName(display, scr.screen, f->name + 4); + if(!f->font.xft) + goto error; + + f->ascent = f->font.xft->ascent; + f->descent = f->font.xft->descent; + }else { + f->font.set = XCreateFontSet(display, name, &missing, &n, nil); + if(missing) { + if(false) { + b = Bfdopen(dup(2), O_WRONLY); + Bprint(b, "%s: note: missing fontset%s for '%s':", argv0, + (n > 1 ? "s" : ""), name); + for(i = 0; i < n; i++) + Bprint(b, "%s %s", (i ? "," : ""), missing[i]); + Bprint(b, "\n"); + Bterm(b); + } + freestringlist(missing); + } + + if(f->font.set) { + f->type = FFontSet; + XFontsOfFontSet(f->font.set, &xfonts, &font_names); + f->ascent = xfonts[0]->ascent; + f->descent = xfonts[0]->descent; + }else { + f->type = FX11; + f->font.x11 = XLoadQueryFont(display, name); + if(!f->font.x11) + goto error; + + f->ascent = f->font.x11->ascent; + f->descent = f->font.x11->descent; + } + } + f->height = f->ascent + f->descent; + return f; + +error: + fprint(2, "%s: cannot load font: %s\n", argv0, name); + f->type = 0; + freefont(f); + return nil; +} + +void +freefont(Font *f) { + switch(f->type) { + case FFontSet: + XFreeFontSet(display, f->font.set); + break; + case FXft: + XftFontClose(display, f->font.xft); + break; + case FX11: + XFreeFont(display, f->font.x11); + break; + default: + break; + } + free(f->name); + free(f); +} + +Rectangle +textextents_l(Font *font, char *text, uint len, int *offset) { + Rectangle rect; + XRectangle r; + XGlyphInfo i; + int unused; + + if(!offset) + offset = &unused; + + switch(font->type) { + case FFontSet: + *offset = Xutf8TextExtents(font->font.set, text, len, &r, nil); + return Rect(r.x, -r.y - r.height, r.x + r.width, -r.y); + case FXft: + XftTextExtentsUtf8(display, font->font.xft, (uchar*)text, len, &i); + *offset = i.xOff; + return Rect(-i.x, i.y - i.height, -i.x + i.width, i.y); + case FX11: + rect = ZR; + rect.max.x = XTextWidth(font->font.x11, text, len); + rect.max.y = font->ascent; + *offset = rect.max.x; + return rect; + default: + die("Invalid font type"); + return ZR; /* shut up ken */ + } +} + +uint +textwidth_l(Font *font, char *text, uint len) { + Rectangle r; + + r = textextents_l(font, text, len, nil); + return Dx(r); +} + +uint +textwidth(Font *font, char *text) { + return textwidth_l(font, text, strlen(text)); +} + +uint +labelh(Font *font) { + return max(font->height + font->descent + font->pad.min.y + font->pad.max.y, 1); +} + +/* Misc */ +Atom +xatom(char *name) { + void **e; + + e = hash_get(&atommap, name, true); + if(*e == nil) + *e = (void*)XInternAtom(display, name, false); + return (Atom)*e; +} + +void +sendmessage(Window *w, char *name, long l0, long l1, long l2, long l3, long l4) { + XClientMessageEvent e; + + e.type = ClientMessage; + e.window = w->xid; + e.message_type = xatom(name); + e.format = 32; + e.data.l[0] = l0; + e.data.l[1] = l1; + e.data.l[2] = l2; + e.data.l[3] = l3; + e.data.l[4] = l4; + sendevent(w, false, NoEventMask, (XEvent*)&e); +} + +void +sendevent(Window *w, bool propegate, long mask, XEvent *e) { + XSendEvent(display, w->xid, propegate, mask, e); +} + +KeyCode +keycode(char *name) { + return XKeysymToKeycode(display, XStringToKeysym(name)); +} + +typedef struct KMask KMask; + +static struct KMask { + int mask; + const char* name; +} masks[] = { + {ShiftMask, "Shift"}, + {ControlMask, "Control"}, + {Mod1Mask, "Mod1"}, + {Mod2Mask, "Mod2"}, + {Mod3Mask, "Mod3"}, + {Mod4Mask, "Mod4"}, + {Mod5Mask, "Mod5"}, + {0,} +}; + +bool +parsekey(char *str, int *mask, char **key) { + static char *keys[16]; + KMask *m; + int i, nkeys; + + *mask = 0; + nkeys = tokenize(keys, nelem(keys), str, '-'); + for(i=0; i < nkeys; i++) { + for(m=masks; m->mask; m++) + if(!strcasecmp(m->name, keys[i])) { + *mask |= m->mask; + goto next; + } + break; + next: continue; + } + if(key) { + if(nkeys) + *key = keys[i]; + return i == nkeys - 1; + } + else + return i == nkeys; +} + +void +sync(void) { + XSync(display, false); +} + +/* Properties */ +void +delproperty(Window *w, char *prop) { + XDeleteProperty(display, w->xid, xatom(prop)); +} + +void +changeproperty(Window *w, char *prop, char *type, + int width, uchar data[], int n) { + XChangeProperty(display, w->xid, xatom(prop), xatom(type), width, + PropModeReplace, data, n); +} + +void +changeprop_string(Window *w, char *prop, char *string) { + changeprop_char(w, prop, "UTF8_STRING", string, strlen(string)); +} + +void +changeprop_char(Window *w, char *prop, char *type, char data[], int len) { + changeproperty(w, prop, type, 8, (uchar*)data, len); +} + +void +changeprop_short(Window *w, char *prop, char *type, short data[], int len) { + changeproperty(w, prop, type, 16, (uchar*)data, len); +} + +void +changeprop_long(Window *w, char *prop, char *type, long data[], int len) { + changeproperty(w, prop, type, 32, (uchar*)data, len); +} + +void +changeprop_ulong(Window *w, char *prop, char *type, ulong data[], int len) { + changeproperty(w, prop, type, 32, (uchar*)data, len); +} + +void +changeprop_textlist(Window *w, char *prop, char *type, char *data[]) { + char **p, *s, *t; + int len, n; + + len = 0; + for(p=data; *p; p++) + len += strlen(*p) + 1; + s = emalloc(len); + t = s; + for(p=data; *p; p++) { + n = strlen(*p) + 1; + memcpy(t, *p, n); + t += n; + } + changeprop_char(w, prop, type, s, len); + free(s); +} + +void +freestringlist(char *list[]) { + XFreeStringList(list); +} + +static ulong +getprop(Window *w, char *prop, char *type, Atom *actual, int *format, + ulong offset, uchar **ret, ulong length) { + Atom typea; + ulong n, extra; + int status; + + typea = (type ? xatom(type) : 0L); + + status = XGetWindowProperty(display, w->xid, + xatom(prop), offset, length, false /* delete */, + typea, actual, format, &n, &extra, ret); + + if(status != Success) { + *ret = nil; + return 0; + } + if(n == 0) { + free(*ret); + *ret = nil; + } + return n; +} + +ulong +getproperty(Window *w, char *prop, char *type, Atom *actual, + ulong offset, uchar **ret, ulong length) { + int format; + + return getprop(w, prop, type, actual, &format, offset, ret, length); +} + +ulong +getprop_long(Window *w, char *prop, char *type, + ulong offset, long **ret, ulong length) { + Atom actual; + ulong n; + int format; + + n = getprop(w, prop, type, &actual, &format, offset, (uchar**)ret, length); + if(n == 0 || format == 32 && xatom(type) == actual) + return n; + free(*ret); + *ret = 0; + return 0; +} + +ulong +getprop_ulong(Window *w, char *prop, char *type, + ulong offset, ulong **ret, ulong length) { + return getprop_long(w, prop, type, offset, (long**)ret, length); +} + +char** +strlistdup(char *list[]) { + char **p; + char *q; + int i, m, n; + + n = 0; + m = 0; + for(p=list; *p; p++, n++) + m += strlen(*p) + 1; + + p = malloc((n+1) * sizeof(*p) + m); + q = (char*)&p[n+1]; + + for(i=0; i < n; i++) { + p[i] = q; + m = strlen(list[i]) + 1; + memcpy(q, list[i], m); + q += m; + } + p[n] = nil; + return p; +} + +int +getprop_textlist(Window *w, char *name, char **ret[]) { + XTextProperty prop; + char **list; + int n; + + *ret = nil; + n = 0; + + XGetTextProperty(display, w->xid, &prop, xatom(name)); + if(prop.nitems > 0) { + if(Xutf8TextPropertyToTextList(display, &prop, &list, &n) == Success) + *ret = list; + XFree(prop.value); + } + return n; +} + +char* +getprop_string(Window *w, char *name) { + char **list, *str; + int n; + + str = nil; + + n = getprop_textlist(w, name, &list); + if(n > 0) + str = estrdup(*list); + freestringlist(list); + + return str; +} + +Rectangle +getwinrect(Window *w) { + XWindowAttributes wa; + Point p; + + if(!XGetWindowAttributes(display, w->xid, &wa)) + return ZR; + p = translate(w, &scr.root, ZP); + return rectaddpt(Rect(0, 0, wa.width, wa.height), p); +} + +void +setfocus(Window *w, int mode) { + XSetInputFocus(display, w->xid, mode, CurrentTime); +} + +XWindow +getfocus(void) { + XWindow ret; + int revert; + + XGetInputFocus(display, &ret, &revert); + return ret; +} + +/* Mouse */ +Point +querypointer(Window *w) { + XWindow win; + Point pt; + uint ui; + int i; + + XQueryPointer(display, w->xid, &win, &win, &i, &i, &pt.x, &pt.y, &ui); + return pt; +} + +int +pointerscreen(void) { + XWindow win; + Point pt; + uint ui; + int i; + + return XQueryPointer(display, scr.root.xid, &win, &win, &i, &i, + &pt.x, &pt.y, &ui); +} + +void +warppointer(Point pt) { + /* Nasty kludge for xephyr, xnest. */ + static int havereal = -1; + static char* real; + + if(havereal == -1) { + real = getenv("REALDISPLAY"); + havereal = real != nil; + } + if(havereal) + system(sxprint("DISPLAY=%s wiwarp %d %d", real, pt.x, pt.y)); + + XWarpPointer(display, + /* src, dest w */ None, scr.root.xid, + /* src_rect */ 0, 0, 0, 0, + /* target */ pt.x, pt.y); +} + +Point +translate(Window *src, Window *dst, Point sp) { + Point pt; + XWindow w; + + XTranslateCoordinates(display, src->xid, dst->xid, sp.x, sp.y, + &pt.x, &pt.y, &w); + return pt; +} + +int +grabpointer(Window *w, Window *confine, Cursor cur, int mask) { + XWindow cw; + + cw = None; + if(confine) + cw = confine->xid; + return XGrabPointer(display, w->xid, false /* owner events */, mask, + GrabModeAsync, GrabModeAsync, cw, cur, CurrentTime + ) == GrabSuccess; +} + +void +ungrabpointer(void) { + XUngrabPointer(display, CurrentTime); +} + +int +grabkeyboard(Window *w) { + + return XGrabKeyboard(display, w->xid, true /* owner events */, + GrabModeAsync, GrabModeAsync, CurrentTime + ) == GrabSuccess; +} + +void +ungrabkeyboard(void) { + XUngrabKeyboard(display, CurrentTime); +} + +/* Insanity */ +void +sethints(Window *w) { + XSizeHints xs; + XWMHints *wmh; + WinHints *h; + Point p; + long size; + + if(w->hints == nil) + w->hints = emalloc(sizeof *h); + + h = w->hints; + memset(h, 0, sizeof *h); + + h->max = Pt(INT_MAX, INT_MAX); + h->inc = Pt(1,1); + + wmh = XGetWMHints(display, w->xid); + if(wmh) { + if(wmh->flags & WindowGroupHint) + h->group = wmh->window_group; + free(wmh); + } + + if(!XGetWMNormalHints(display, w->xid, &xs, &size)) + return; + + if(xs.flags & PMinSize) { + h->min.x = xs.min_width; + h->min.y = xs.min_height; + } + if(xs.flags & PMaxSize) { + h->max.x = xs.max_width; + h->max.y = xs.max_height; + } + + /* Goddamn buggy clients. */ + if(h->max.x < h->min.x) + h->max.x = h->min.x; + if(h->max.y < h->min.y) + h->max.y = h->min.y; + + h->base = h->min; + if(xs.flags & PBaseSize) { + p.x = xs.base_width; + p.y = xs.base_height; + h->base = p; + h->baspect = p; + } + + if(xs.flags & PResizeInc) { + h->inc.x = max(xs.width_inc, 1); + h->inc.y = max(xs.height_inc, 1); + } + + if(xs.flags & PAspect) { + h->aspect.min.x = xs.min_aspect.x; + h->aspect.min.y = xs.min_aspect.y; + h->aspect.max.x = xs.max_aspect.x; + h->aspect.max.y = xs.max_aspect.y; + } + + h->position = (xs.flags & (USPosition|PPosition)) != 0; + + if(!(xs.flags & PWinGravity)) + xs.win_gravity = NorthWestGravity; + p = ZP; + switch (xs.win_gravity) { + case EastGravity: + case CenterGravity: + case WestGravity: + p.y = 1; + break; + case SouthEastGravity: + case SouthGravity: + case SouthWestGravity: + p.y = 2; + break; + } + switch (xs.win_gravity) { + case NorthGravity: + case CenterGravity: + case SouthGravity: + p.x = 1; + break; + case NorthEastGravity: + case EastGravity: + case SouthEastGravity: + p.x = 2; + break; + } + h->grav = p; + h->gravstatic = (xs.win_gravity == StaticGravity); +} + +Rectangle +sizehint(WinHints *h, Rectangle r) { + Point p, aspect, origin; + + if(h == nil) + return r; + + origin = r.min; + r = rectsubpt(r, origin); + + /* Min/max */ + r.max.x = max(r.max.x, h->min.x); + r.max.y = max(r.max.y, h->min.y); + r.max.x = min(r.max.x, h->max.x); + r.max.y = min(r.max.y, h->max.y); + + /* Increment */ + p = subpt(r.max, h->base); + r.max.x -= p.x % h->inc.x; + r.max.y -= p.y % h->inc.y; + + /* Aspect */ + p = subpt(r.max, h->baspect); + p.y = max(p.y, 1); + + aspect = h->aspect.min; + if(p.x * aspect.y / p.y < aspect.x) + r.max.y = h->baspect.y + + p.x * aspect.y / aspect.x; + + aspect = h->aspect.max; + if(p.x * aspect.y / p.y > aspect.x) + r.max.x = h->baspect.x + + p.y * aspect.x / aspect.y; + + return rectaddpt(r, origin); +} + +Rectangle +gravitate(Rectangle rc, Rectangle rf, Point grav) { + Point d; + + /* Get delta between frame and client rectangles */ + d = subpt(subpt(rf.max, rf.min), + subpt(rc.max, rc.min)); + + /* Divide by 2 and apply gravity */ + d = divpt(d, Pt(2, 2)); + d = mulpt(d, grav); + + return rectsubpt(rc, d); +} + diff --git a/cmd/wmii/xdnd.c b/cmd/wmii/xdnd.c new file mode 100644 index 0000000..abb3612 --- /dev/null +++ b/cmd/wmii/xdnd.c @@ -0,0 +1,88 @@ +/* Copyright ©2008-2010 Kris Maglione <maglione.k at Gmail> + * See LICENSE file for license details. + */ +#include "dat.h" +#include "fns.h" + +void +xdnd_initwindow(Window *w) { + long l; + + l = 3; /* They are insane. Why is this an ATOM?! */ + changeprop_long(w, "XdndAware", "ATOM", &l, 1); +} + +typedef struct Dnd Dnd; +struct Dnd { + XWindow source; + Rectangle r; +}; + +int +xdnd_clientmessage(XClientMessageEvent *e) { + Window *w; + Dnd *dnd; + long *l; + Rectangle r; + Point p; + long pos, siz; + ulong msg; + + dnd = nil; + msg = e->message_type; + l = e->data.l; + Dprint(DDnd, "ClientMessage: %A\n", msg); + + if(msg == xatom("XdndEnter")) { + if(e->format != 32) + return -1; + w = findwin(e->window); + if(w) { + if(w->dnd == nil) + w->dnd = emallocz(sizeof *dnd); + dnd = w->dnd; + dnd->source = l[0]; + dnd->r = ZR; + } + return 1; + }else + if(msg == xatom("XdndLeave")) { + if(e->format != 32) + return -1; + w = findwin(e->window); + if(w && w->dnd) { + free(w->dnd); + w->dnd = nil; + } + return 1; + }else + if(msg == xatom("XdndPosition")) { + if(e->format != 32) + return -1; + r = ZR; + w = findwin(e->window); + if(w) + dnd = w->dnd; + if(dnd) { + p.x = (ulong)l[2] >> 16; + p.y = (ulong)l[2] & 0xffff; + p = subpt(p, w->r.min); + Dprint(DDnd, "\tw: %W\n", w); + Dprint(DDnd, "\tp: %P\n", p); + if(eqrect(dnd->r, ZR) || !rect_haspoint_p(p, dnd->r)) + if(w->handler->dndmotion) + dnd->r = w->handler->dndmotion(w, p); + r = dnd->r; + if(!eqrect(r, ZR)) + r = rectaddpt(r, w->r.min); + Dprint(DDnd, "\tr: %R\n", r); + } + pos = (r.min.x<<16) | r.min.y; + siz = (Dx(r)<<16) | Dy(r); + sendmessage(window(l[0]), "XdndStatus", e->window, 0, pos, siz, 0); + return 1; + } + + return 0; +} + diff --git a/cmd/wmii/xext.c b/cmd/wmii/xext.c new file mode 100644 index 0000000..ba851e5 --- /dev/null +++ b/cmd/wmii/xext.c @@ -0,0 +1,160 @@ +/* Copyright ©2008-2010 Kris Maglione <maglione.k at Gmail> + * See LICENSE file for license details. + */ +#define _X11_VISIBLE +#include "dat.h" +#include <X11/extensions/Xrender.h> +#include <X11/extensions/Xinerama.h> +#include "fns.h" + +#if RANDR_MAJOR < 1 +# error XRandR versions less than 1.0 are not supported +#endif + +static void randr_screenchange(XRRScreenChangeNotifyEvent*); +static bool randr_event_p(XEvent *e); +static void randr_init(void); +static void render_init(void); +static void xinerama_init(void); + +typedef void (*EvHandler)(XEvent*); +static EvHandler randr_handlers[RRNumberEvents]; + +bool have_RandR; +bool have_render; +bool have_xinerama; +int randr_eventbase; + +static void +handle(XEvent *e, EvHandler h[], int base) { + + if(h[e->type-base]) + h[e->type-base](e); +} + +void +xext_init(void) { + randr_init(); + render_init(); + xinerama_init(); +} + +void +xext_event(XEvent *e) { + + if(randr_event_p(e)) + handle(e, randr_handlers, randr_eventbase); +} + +static void +randr_init(void) { + int errorbase, major, minor; + + have_RandR = XRRQueryExtension(display, &randr_eventbase, &errorbase); + if(have_RandR) + if(XRRQueryVersion(display, &major, &minor) && major < 1) + have_RandR = false; + if(have_RandR) + XRRSelectInput(display, scr.root.xid, RRScreenChangeNotifyMask); +} + +static bool +randr_event_p(XEvent *e) { + return have_RandR + && (uint)e->type - randr_eventbase < RRNumberEvents; +} + +static void +randr_screenchange(XRRScreenChangeNotifyEvent *ev) { + + XRRUpdateConfiguration((XEvent*)ev); + if(ev->rotation*90 % 180) + scr.rect = Rect(0, 0, ev->width, ev->height); + else + scr.rect = Rect(0, 0, ev->height, ev->width); + init_screens(); +} + +static EvHandler randr_handlers[] = { + [RRScreenChangeNotify] = (EvHandler)randr_screenchange, +}; + +/* Ripped most graciously from ecore_x. XRender documentation + * is sparse. + */ +static void +render_init(void) { + XVisualInfo *vip; + XVisualInfo vi; + int base, i, n; + + have_render = XRenderQueryExtension(display, &base, &base); + if(!have_render) + return; + + vi.class = TrueColor; + vi.depth = 32; + vi.screen = scr.screen; + vip = XGetVisualInfo(display, VisualClassMask + | VisualDepthMask + | VisualScreenMask, + &vi, &n); + for(i=0; i < n; i++) + if(render_argb_p(vip[i].visual)) { + render_visual = vip[i].visual; + scr.visual32 = render_visual; + break; + } + XFree(vip); +} + +bool +render_argb_p(Visual *v) { + XRenderPictFormat *f; + + if(!have_render) + return false; + f = XRenderFindVisualFormat(display, v); + return f + && f->type == PictTypeDirect + && f->direct.alphaMask; +} + +static void +xinerama_init(void) { + int base; + + have_xinerama = XineramaQueryExtension(display, &base, &base); +} + +static bool +xinerama_active(void) { + return have_xinerama && XineramaIsActive(display); +} + +Rectangle* +xinerama_screens(int *np) { + static Rectangle *rects; + XineramaScreenInfo *res; + int i, n; + + if(!xinerama_active()) { + *np = 1; + return &scr.rect; + } + + free(rects); + res = XineramaQueryScreens(display, &n); + rects = emalloc(n * sizeof *rects); + for(i=0; i < n; i++) { + rects[i].min.x = res[i].x_org; + rects[i].min.y = res[i].y_org; + rects[i].max.x = res[i].x_org + res[i].width; + rects[i].max.y = res[i].y_org + res[i].height; + } + XFree(res); + + *np = n; + return rects; +} + diff --git a/cmd/wmii9menu.c b/cmd/wmii9menu.c new file mode 100644 index 0000000..02049f3 --- /dev/null +++ b/cmd/wmii9menu.c @@ -0,0 +1,341 @@ +/* Licence + * ======= + * + * 9menu is free software, and is Copyright (c) 1994 by David Hogan and + * Arnold Robbins. Permission is granted to all sentient beings to use + * this software, to make copies of it, and to distribute those copies, + * provided that: + * + * (1) the copyright and licence notices are left intact + * (2) the recipients are aware that it is free software + * (3) any unapproved changes in functionality are either + * (i) only distributed as patches + * or (ii) distributed as a new program which is not called 9menu + * and whose documentation gives credit where it is due + * (4) the authors are not held responsible for any defects + * or shortcomings in the software, or damages caused by it. + * + * There is no warranty for this software. Have a nice day. + * + * -- + * Arnold Robbins + * arnold@skeeve.com + * + * 9menu.c + * + * This program puts up a window that is just a menu, and executes + * commands that correspond to the items selected. + * + * Initial idea: Arnold Robbins + * Version using libXg: Matty Farrow (some ideas borrowed) + * This code by: David Hogan and Arnold Robbins + */ + +/* + * Heavily modified by Kris Maglione for use with wmii. + */ + +#define IXP_NO_P9_ +#define IXP_P9_STRUCTS +#include <fmt.h> +#include <ixp.h> +#include <stdarg.h> +#include <stdbool.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <clientutil.h> +#include <util.h> +#include <x11.h> + +char version[] = "wmii9menu-" VERSION " ©2010 Kris Maglione, ©1994 David Hogan, Arnold Robbins"; + +static Window* menuwin; + +static CTuple cnorm; +static CTuple csel; +static Font* font; + +static int wborder; + +char buffer[8092]; +char* _buffer; + +/* for XSetWMProperties to use */ +int g_argc; +char **g_argv; + +char *initial = ""; +int cur; + +static char** labels; /* list of labels and commands */ +static char** commands; +static int numitems; + +void usage(void); +void run_menu(void); +void create_window(void); +void size_window(int, int); +void redraw(int, int); +void warpmouse(int, int); +void memory(void); +int args(void); + +ErrorCode ignored_xerrors[] = { + { 0, } +}; + +/* xext.c */ +void xext_init(void); +Rectangle* xinerama_screens(int*); +/* geom.c */ +bool rect_haspoint_p(Point, Rectangle); + +Cursor cursor[1]; +Visual* render_visual; + +void init_screens(void); +void +init_screens(void) { + Rectangle *rects; + Point p; + int i, n; + + rects = xinerama_screens(&n); + p = querypointer(&scr.root); + for(i=0; i < n; i++) { + if(rect_haspoint_p(p, rects[i])) + break; + } + if(i == n) + i = 0; + scr.rect = rects[i]; +} + +/* main --- crack arguments, set up X stuff, run the main menu loop */ + +int +main(int argc, char **argv) +{ + static char *address; + char *cp; + int i; + + g_argc = argc; + g_argv = argv; + + ARGBEGIN{ + case 'v': + print("%s\n", version); + return 0; + case 'a': + address = EARGF(usage()); + break; + case 'i': + initial = EARGF(usage()); + break; + default: + usage(); + }ARGEND; + + if(argc == 0) + usage(); + + initdisplay(); + xext_init(); + init_screens(); + create_window(); + + numitems = argc; + + labels = emalloc(numitems * sizeof *labels); + commands = emalloc(numitems * sizeof *labels); + + for(i = 0; i < numitems; i++) { + labels[i] = argv[i]; + if((cp = strchr(labels[i], ':')) != nil) { + *cp++ = '\0'; + commands[i] = cp; + } else + commands[i] = labels[i]; + if(strcmp(labels[i], initial) == 0) + cur = i; + } + + client_init(address); + + wborder = strtol(readctl("border "), nil, 10); + loadcolor(&cnorm, readctl("normcolors ")); + loadcolor(&csel, readctl("focuscolors ")); + font = loadfont(readctl("font ")); + if(!font) + fatal("Can't load font"); + + run_menu(); + + XCloseDisplay(display); + return 0; +} + +/* usage --- print a usage message and die */ + +void +usage(void) +{ + fprintf(stderr, "usage: %s -v\n", argv0); + fprintf(stderr, " %s [-a <address>] [-i <arg>] menitem[:command] ...\n", argv0); + exit(0); +} + +/* run_menu --- put up the window, execute selected commands */ + +enum { + MouseMask = + ButtonPressMask + | ButtonReleaseMask + | ButtonMotionMask + | PointerMotionMask, + MenuMask = + MouseMask + | StructureNotifyMask + | ExposureMask +}; + +void +run_menu(void) +{ + XEvent ev; + int i, old, wide, high; + + wide = 0; + high = labelh(font); + for(i = 0; i < numitems; i++) + wide = max(wide, textwidth(font, labels[i])); + wide += font->height & ~1; + + size_window(wide, high); + warpmouse(wide, high); + + for(;;) { + XNextEvent(display, &ev); + switch (ev.type) { + default: + fprintf(stderr, "%s: unknown ev.type %d\n", + argv0, ev.type); + break; + case ButtonRelease: + i = ev.xbutton.y / high; + if(ev.xbutton.x < 0 || ev.xbutton.x > wide) + return; + else if(i < 0 || i >= numitems) + return; + + printf("%s\n", commands[i]); + return; + case ButtonPress: + case MotionNotify: + old = cur; + cur = ev.xbutton.y / high; + if(ev.xbutton.x < 0 || ev.xbutton.x > wide) + cur = ~0; + if(cur == old) + break; + redraw(high, wide); + break; + case MapNotify: + redraw(high, wide); + break; + case Expose: + redraw(high, wide); + break; + case ConfigureNotify: + case MappingNotify: + break; + } + } +} + +/* set_wm_hints --- set all the window manager hints */ + +void +create_window(void) +{ + WinAttr wa = { 0 }; + XEvent e; + + wa.override_redirect = true; + menuwin = createwindow(&scr.root, Rect(-1, -1, 0, 0), + scr.depth, InputOutput, + &wa, CWOverrideRedirect); + selectinput(menuwin, MenuMask); + mapwin(menuwin); + XMaskEvent(display, StructureNotifyMask, &e); + if(!grabpointer(menuwin, nil, 0, MouseMask)) + fatal("Failed to grab the mouse\n"); + XSetCommand(display, menuwin->xid, g_argv, g_argc); +} + +void +size_window(int wide, int high) +{ + Rectangle r; + Point p; + int h; + + h = high * numitems; + r = Rect(0, 0, wide, h); + + p = querypointer(&scr.root); + p.x -= wide / 2; + p.x = max(p.x, scr.rect.min.x); + p.x = min(p.x, scr.rect.max.x - wide); + + p.y -= cur * high + high / 2; + p.y = max(p.y, scr.rect.min.y); + p.y = min(p.y, scr.rect.max.y - h); + + reshapewin(menuwin, rectaddpt(r, p)); + + //XSetWindowBackground(display, menuwin->xid, cnorm.bg); + setborder(menuwin, 1, cnorm.border); +} + +/* redraw --- actually redraw the menu */ + +void +redraw(int high, int wide) +{ + Rectangle r; + CTuple *c; + int i; + + r = Rect(0, 0, wide, high); + for(i = 0; i < numitems; i++) { + if(cur == i) + c = &csel; + else + c = &cnorm; + r = rectsetorigin(r, Pt(0, i * high)); + fill(menuwin, r, c->bg); + drawstring(menuwin, font, r, Center, labels[i], c->fg); + } +} + +/* warpmouse --- bring the mouse to the menu */ + +void +warpmouse(int wide, int high) +{ + Point p; + int offset; + + /* move tip of pointer into middle of menu item */ + offset = labelh(font) / 2; + offset += 6; /* fudge factor */ + + p = Pt(wide / 2, cur*high - high/2 + offset); + p = addpt(p, menuwin->r.min); + + warppointer(p); +} + diff --git a/cmd/wmiir.c b/cmd/wmiir.c new file mode 100644 index 0000000..1801df8 --- /dev/null +++ b/cmd/wmiir.c @@ -0,0 +1,421 @@ +/* Copyight ©2007-2010 Kris Maglione <fbsdaemon@gmail.com> + * See LICENSE file for license details. + */ +#define IXP_NO_P9_ +#define IXP_P9_STRUCTS +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <time.h> +#include <unistd.h> +#include <ixp.h> +#include <util.h> +#include <fmt.h> + +static IxpClient *client; + +static void +usage(void) { + fprint(1, + "usage: %s [-a <address>] {create | ls [-dlp] | read | remove | write} <file>\n" + " %s [-a <address>] xwrite <file> <data>\n" + " %s -v\n", argv0, argv0, argv0); + exit(1); +} + +static int +errfmt(Fmt *f) { + return fmtstrcpy(f, ixp_errbuf()); +} + +/* Utility Functions */ +static void +write_data(IxpCFid *fid, char *name) { + void *buf; + int len; + + buf = emalloc(fid->iounit);; + for(;;) { + len = read(0, buf, fid->iounit); + if(len <= 0) + break; + if(ixp_write(fid, buf, len) != len) + fatal("cannot write file %q\n", name); + } + free(buf); +} + +static int +comp_stat(const void *s1, const void *s2) { + Stat *st1, *st2; + + st1 = (Stat*)s1; + st2 = (Stat*)s2; + return strcmp(st1->name, st2->name); +} + +static void +setrwx(long m, char *s) { + static char *modes[] = { + "---", "--x", "-w-", + "-wx", "r--", "r-x", + "rw-", "rwx", + }; + strncpy(s, modes[m], 3); +} + +static char * +modestr(uint mode) { + static char buf[16]; + + buf[0]='-'; + if(mode & P9_DMDIR) + buf[0]='d'; + buf[1]='-'; + setrwx((mode >> 6) & 7, &buf[2]); + setrwx((mode >> 3) & 7, &buf[5]); + setrwx((mode >> 0) & 7, &buf[8]); + buf[11] = 0; + return buf; +} + +static char* +timestr(time_t val) { + static char buf[32]; + + strftime(buf, sizeof buf, "%Y-%m-%d %H:%M", localtime(&val)); + return buf; +} + +static void +print_stat(Stat *s, int lflag, char *file, int pflag) { + char *slash; + + slash = ""; + if(pflag) + slash = "/"; + else + file = ""; + + if(lflag) + print("%s %s %s %5llud %s %s%s%s\n", + modestr(s->mode), s->uid, s->gid, s->length, + timestr(s->mtime), file, slash, s->name); + else { + if((s->mode&P9_DMDIR) && strcmp(s->name, "/")) + print("%s%s%s/\n", file, slash, s->name); + else + print("%s%s%s\n", file, slash, s->name); + } +} + +/* Service Functions */ +static int +xwrite(int argc, char *argv[]) { + IxpCFid *fid; + char *file; + + ARGBEGIN{ + default: + usage(); + }ARGEND; + + file = EARGF(usage()); + fid = ixp_open(client, file, P9_OWRITE); + if(fid == nil) + fatal("Can't open file '%s': %r\n", file); + + write_data(fid, file); + ixp_close(fid); + return 0; +} + +static int +xawrite(int argc, char *argv[]) { + IxpCFid *fid; + char *file, *buf; + int nbuf, i; + + ARGBEGIN{ + default: + usage(); + }ARGEND; + + file = EARGF(usage()); + fid = ixp_open(client, file, P9_OWRITE); + if(fid == nil) + fatal("Can't open file '%s': %r\n", file); + + nbuf = 1; + for(i=0; i < argc; i++) + nbuf += strlen(argv[i]) + (i > 0); + buf = emalloc(nbuf); + buf[0] = '\0'; + while(argc) { + strcat(buf, ARGF()); + if(argc) + strcat(buf, " "); + } + + if(ixp_write(fid, buf, nbuf) == -1) + fatal("cannot write file '%s': %r\n", file); + ixp_close(fid); + free(buf); + return 0; +} + +static int +xcreate(int argc, char *argv[]) { + IxpCFid *fid; + char *file; + + ARGBEGIN{ + default: + usage(); + }ARGEND; + + file = EARGF(usage()); + fid = ixp_create(client, file, 0777, P9_OWRITE); + if(fid == nil) + fatal("Can't create file '%s': %r\n", file); + + if((fid->qid.type&P9_DMDIR) == 0) + write_data(fid, file); + ixp_close(fid); + return 0; +} + +static int +xremove(int argc, char *argv[]) { + char *file; + + ARGBEGIN{ + default: + usage(); + }ARGEND; + + file = EARGF(usage()); + do { + if(!ixp_remove(client, file)) + fprint(2, "%s: Can't remove file '%s': %r\n", argv0, file); + }while((file = ARGF())); + return 0; +} + +static int +xread(int argc, char *argv[]) { + IxpCFid *fid; + char *file, *buf; + int count; + + ARGBEGIN{ + default: + usage(); + }ARGEND; + + if(argc == 0) + usage(); + file = EARGF(usage()); + do { + fid = ixp_open(client, file, P9_OREAD); + if(fid == nil) + fatal("Can't open file '%s': %r\n", file); + + buf = emalloc(fid->iounit); + while((count = ixp_read(fid, buf, fid->iounit)) > 0) + write(1, buf, count); + ixp_close(fid); + + if(count == -1) + fprint(2, "%s: cannot read file '%s': %r\n", argv0, file); + } while((file = ARGF())); + + return 0; +} + +static int +xls(int argc, char *argv[]) { + IxpMsg m; + Stat *stat; + IxpCFid *fid; + char *file; + char *buf; + int lflag, dflag, pflag; + int count, nstat, mstat, i; + + lflag = dflag = pflag = 0; + + ARGBEGIN{ + case 'l': + lflag++; + break; + case 'd': + dflag++; + break; + case 'p': + pflag++; + break; + default: + usage(); + }ARGEND; + + count = 0; + file = EARGF(usage()); + do { + stat = ixp_stat(client, file); + if(stat == nil) + fatal("cannot stat file '%s': %r\n", file); + + i = strlen(file); + if(file[i-1] == '/') { + file[i-1] = '\0'; + if(!(stat->mode&P9_DMDIR)) + fatal("%s: not a directory", file); + } + if(dflag || (stat->mode&P9_DMDIR) == 0) { + print_stat(stat, lflag, file, pflag); + ixp_freestat(stat); + continue; + } + ixp_freestat(stat); + + fid = ixp_open(client, file, P9_OREAD); + if(fid == nil) + fatal("Can't open file '%s': %r\n", file); + + nstat = 0; + mstat = 16; + stat = emalloc(mstat * sizeof *stat); + buf = emalloc(fid->iounit); + while((count = ixp_read(fid, buf, fid->iounit)) > 0) { + m = ixp_message(buf, count, MsgUnpack); + while(m.pos < m.end) { + if(nstat == mstat) { + mstat <<= 1; + stat = erealloc(stat, mstat * sizeof *stat); + } + ixp_pstat(&m, &stat[nstat++]); + } + } + ixp_close(fid); + + qsort(stat, nstat, sizeof *stat, comp_stat); + for(i = 0; i < nstat; i++) { + print_stat(&stat[i], lflag, file, pflag); + ixp_freestat(&stat[i]); + } + free(stat); + } while((file = ARGF())); + + if(count == -1) + fatal("cannot read directory '%s': %r\n", file); + return 0; +} + +static int +xnamespace(int argc, char *argv[]) { + char *path; + + ARGBEGIN{ + default: + usage(); + }ARGEND; + + path = ixp_namespace(); + if(path == nil) + fatal("can't find namespace: %r\n"); + print("%s\n", path); + return 0; +} + +static int +xsetsid(int argc, char *argv[]) { + char *av0; + + av0 = nil; + ARGBEGIN{ + case '0': + av0 = EARGF(usage()); + break; + default: + usage(); + }ARGEND; + if(av0 == nil) + av0 = argv[0]; + if(av0 == nil) + return 1; + + setsid(); + execvp(av0, argv); + fatal("setsid: can't exec: %r"); + return 1; /* NOTREACHED */ +} + +typedef struct exectab exectab; +struct exectab { + char *cmd; + int (*fn)(int, char**); +} fstab[] = { + {"cat", xread}, + {"create", xcreate}, + {"ls", xls}, + {"read", xread}, + {"remove", xremove}, + {"rm", xremove}, + {"write", xwrite}, + {"xwrite", xawrite}, + {0, } +}, utiltab[] = { + {"namespace", xnamespace}, + {"ns", xnamespace}, + {"setsid", xsetsid}, + {0, } +}; + +int +main(int argc, char *argv[]) { + char *address; + exectab *tab; + int ret; + + quotefmtinstall(); + fmtinstall('r', errfmt); + + address = getenv("WMII_ADDRESS"); + + ARGBEGIN{ + case 'v': + print("%s-" VERSION ", " COPYRIGHT "\n", argv0); + exit(0); + case 'a': + address = EARGF(usage()); + break; + default: + usage(); + }ARGEND; + + if(argc < 1) + usage(); + + for(tab=utiltab; tab->cmd; tab++) + if(!strcmp(*argv, tab->cmd)) + return tab->fn(argc, argv); + + if(address && *address) + client = ixp_mount(address); + else + client = ixp_nsmount("wmii"); + if(client == nil) + fatal("can't mount: %r\n"); + + for(tab=fstab; tab->cmd; tab++) + if(strcmp(*argv, tab->cmd) == 0) break; + if(tab->cmd == 0) + usage(); + + ret = tab->fn(argc, argv); + + ixp_unmount(client); + return ret; +} + |