diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..7e3d272 --- /dev/null +++ b/Makefile @@ -0,0 +1,13 @@ +CFLAGS?=-Wall -O2 +LDADD?=$(shell pkg-config --cflags --libs x11) + +normal: + $(CC) -o cerberus cerberus.c $(CFLAGS) $(LDADD) $(LDFLAGS) + +proto: + cat *.c | egrep '^(void|int|char|unsigned|Window|client)' | sed -r 's/\)/);/' > proto.h + +clean: + rm -f cerberus + +all: proto normal \ No newline at end of file diff --git a/README.md b/README.md index 54dc8d8..8bfb7b3 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,20 @@ cerberus ======== -static tiling wm \ No newline at end of file +* Designed for wide screens. +* Static tiling; you get just three fixed asymmetric tiles and windows never move automatically. +* Bare minimum EWMH to support panels and (simpleswitcher)[https://github.com/seanpringle/simpleswitcher] +* A few keyboard controls for moving, focusing, cycling, closing. +* config.h for customization of borders and keys. + +### The Layout + + ------------------------ + | | | + | | | + | | | + | | | + | |-------| + | | | + | | | + ------------------------ \ No newline at end of file diff --git a/cerberus.c b/cerberus.c new file mode 100644 index 0000000..c9f2327 --- /dev/null +++ b/cerberus.c @@ -0,0 +1,824 @@ +/* + +MIT/X11 License +Copyright (c) 2012 Sean Plistle + +Permission is hereby granted, free of charge, to any person obtaining +a copy of this software and associated documentation files (the +"Software"), to deal in the Software without restriction, including +without limitation the rights to use, copy, modify, merge, publish, +distribute, sublicense, and/or sell copies of the Software, and to +permit persons to whom the Software is furnished to do so, subject to +the following conditions: + +The above copyright notice and this permission notice shall be +included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS +OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. +IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY +CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, +TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE +SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +*/ + +#define _GNU_SOURCE + +#include "cerberus.h" +#include "proto.h" +#include "config.h" + +// X error handler +int oops(Display *d, XErrorEvent *ee) +{ + if (ee->error_code == BadWindow + || (ee->request_code == X_SetInputFocus && ee->error_code == BadMatch) + || (ee->request_code == X_ConfigureWindow && ee->error_code == BadMatch) + || (ee->request_code == X_GrabButton && ee->error_code == BadAccess) + || (ee->request_code == X_GrabKey && ee->error_code == BadAccess) + ) return 0; + fprintf(stderr, "error: request code=%d, error code=%d\n", ee->request_code, ee->error_code); + return xerror(display, ee); +} + +// get pixel value for X named color +unsigned int color_get(const char *name) +{ + XColor color; + Colormap map = DefaultColormap(display, DefaultScreen(display)); + return XAllocNamedColor(display, map, name, &color, &color) ? color.pixel: None; +} + +// retrieve a property of any type from a window +int window_get_prop(Window w, Atom prop, Atom *type, int *items, void *buffer, int bytes) +{ + Atom _type; if (!type) type = &_type; + int _items; if (!items) items = &_items; + int format; unsigned long nitems, nbytes; unsigned char *ret = NULL; + memset(buffer, 0, bytes); + + if (XGetWindowProperty(display, w, prop, 0, bytes/4, False, AnyPropertyType, type, + &format, &nitems, &nbytes, &ret) == Success && ret && *type != None && format) + { + if (format == 8) memmove(buffer, ret, MIN(bytes, nitems)); + if (format == 16) memmove(buffer, ret, MIN(bytes, nitems * sizeof(short))); + if (format == 32) memmove(buffer, ret, MIN(bytes, nitems * sizeof(long))); + *items = (int)nitems; XFree(ret); + return 1; + } + return 0; +} + +int window_get_atom_prop(Window w, Atom atom, Atom *list, int count) +{ + Atom type; int items; + return window_get_prop(w, atom, &type, &items, list, count*sizeof(Atom)) && type == XA_ATOM ? items:0; +} + +void window_set_atom_prop(Window w, Atom prop, Atom *atoms, int count) +{ + XChangeProperty(display, w, prop, XA_ATOM, 32, PropModeReplace, (unsigned char*)atoms, count); +} + +int window_get_cardinal_prop(Window w, Atom atom, unsigned long *list, int count) +{ + Atom type; int items; + return window_get_prop(w, atom, &type, &items, list, count*sizeof(unsigned long)) && type == XA_CARDINAL ? items:0; +} + +void window_set_cardinal_prop(Window w, Atom prop, unsigned long *values, int count) +{ + XChangeProperty(display, w, prop, XA_CARDINAL, 32, PropModeReplace, (unsigned char*)values, count); +} + +// update _NET_CLIENT_LIST_STACKING +void ewmh_client_list() +{ + int i; stack all, wins; + memset(&all, 0, sizeof(stack)); + memset(&wins, 0, sizeof(stack)); + windows_visible(&all); + + for (i = all.depth-1; i > -1; i--) + { + client *c = all.clients[i]; + if (c && c->manage) + { + wins.clients[wins.depth] = c; + wins.windows[wins.depth++] = c->window; + } + } + XChangeProperty(display, root, atoms[_NET_CLIENT_LIST_STACKING], XA_WINDOW, 32, + PropModeReplace, (unsigned char*)&wins.windows, wins.depth); +} + +// update _NET_ACTIVE_WINDOW +void ewmh_active_window(Window w) +{ + XChangeProperty(display, root, atoms[_NET_ACTIVE_WINDOW], XA_WINDOW, 32, + PropModeReplace, (unsigned char*)&w, 1); +} + +// build a client struct +client* window_client(Window win) +{ + if (win == None) return NULL; + + client *c = calloc(1, sizeof(client)); + c->window = win; + + if (XGetWindowAttributes(display, c->window, &c->attr)) + { + c->visible = c->attr.map_state == IsViewable ? 1:0; + XGetTransientForHint(display, c->window, &c->transient_for); + window_get_atom_prop(win, atoms[_NET_WM_WINDOW_TYPE], &c->type, 1); + + c->manage = !c->attr.override_redirect + && c->type != atoms[_NET_WM_WINDOW_TYPE_DESKTOP] + && c->type != atoms[_NET_WM_WINDOW_TYPE_NOTIFICATION] + && c->type != atoms[_NET_WM_WINDOW_TYPE_DOCK] + && c->type != atoms[_NET_WM_WINDOW_TYPE_SPLASH] + ? 1:0; + + c->trans = c->transient_for ? 1:0; + + c->spot = c->attr.x > screen_x+screen_w/2 + ? (c->attr.y > screen_y+screen_h/2 ? SPOT3: SPOT2) + : SPOT1; + + if (c->visible) + { + XWMHints *hints = XGetWMHints(display, c->window); + if (hints) + { + memmove(&c->hints, hints, sizeof(XWMHints)); + XFree(hints); + } + long sr; + XGetWMNormalHints(display, c->window, &c->size, &sr); + c->input = c->hints.flags & InputHint && c->hints.input ? 1:0; + c->urgent = c->hints.flags & XUrgencyHint ? 1: 0; + } + return c; + } + + free(c); + return NULL; +} + +// build a list of visible windows +void windows_visible(stack *s) +{ + // check if the window list is cached + if (inplay.depth) + { + memmove(s, &inplay, sizeof(stack)); + return; + } + unsigned int nwins; int i; Window w1, w2, *wins; + if (XQueryTree(display, root, &w1, &w2, &wins, &nwins) && wins) + { + for (i = nwins-1; i > -1; i--) + { + client *c = window_client(wins[i]); + if (c && c->visible && s->depth < STACK) + { + s->clients[s->depth] = c; + s->windows[s->depth++] = wins[i]; + } + else + client_free(c); + } + } + if (wins) XFree(wins); + memmove(&inplay, s, sizeof(stack)); +} + +// send a ClientMessage (for WM_PROTOCOLS) +int window_message(Window target, Window subject, Atom atom, unsigned long protocol, unsigned long mask) +{ + XEvent e; memset(&e, 0, sizeof(XEvent)); + e.xclient.type = ClientMessage; + e.xclient.message_type = atom; + e.xclient.window = subject; + e.xclient.data.l[0] = protocol; + e.xclient.data.l[1] = latest; + e.xclient.send_event = True; + e.xclient.format = 32; + int r = XSendEvent(display, target, False, mask, &e) ?1:0; + XFlush(display); + return r; +} + +// release a client struct +void client_free(client *c) +{ + free(c); +} + +// WM_PROTOCOLS +int client_protocol(client *c, Atom protocol) +{ + Atom *protocols = NULL; + int i, found = 0, num_pro = 0; + if (XGetWMProtocols(display, c->window, &protocols, &num_pro)) + for (i = 0; i < num_pro && !found; i++) + if (protocols[i] == protocol) found = 1; + if (found) + window_message(c->window, c->window, atoms[WM_PROTOCOLS], protocol, NoEventMask); + if (protocols) XFree(protocols); + return found; +} + +// friendly close followed by stab in the back +void client_close(client *c) +{ + if (!client_protocol(c, atoms[WM_DELETE_WINDOW])) + XKillClient(display, c->window); +} + +// move/resize a window +void client_position(client *c, int x, int y, int w, int h) +{ + if (!c) return; + + w -= BORDER*2; h -= BORDER*2; + + int basew = 0, baseh = 0; + int sw = w, sh = h; + + if (c->size.flags & PBaseSize) + { + basew = c->size.base_width; + baseh = c->size.base_height; + } + if (c->size.flags & PMinSize) + { + w = MAX(w, c->size.min_width); + h = MAX(h, c->size.min_height); + } + if (c->size.flags & PMaxSize) + { + w = MIN(w, c->size.max_width); + h = MIN(h, c->size.max_height); + } + if (c->size.flags & PResizeInc) + { + w -= basew; h -= baseh; + w -= w % c->size.width_inc; + h -= h % c->size.height_inc; + w += basew; h += baseh; + } + if (c->size.flags & PAspect) + { + double ratio = (double) w / h; + double minr = (double) c->size.min_aspect.x / c->size.min_aspect.y; + double maxr = (double) c->size.max_aspect.x / c->size.max_aspect.y; + if (ratio < minr) h = (int)(w / minr); + else if (ratio > maxr) w = (int)(h * maxr); + } + + // center if smaller than supplied size + if (w < sw) x += (sw-w)/2; + if (h < sh) y += (sh-h)/2; + + // bump onto screen + x = MAX(0, MIN(x, screen_x + screen_w - w - BORDER*2)); + y = MAX(0, MIN(y, screen_y + screen_h - h - BORDER*2)); + + XMoveResizeWindow(display, c->window, x, y, w, h); +} + +// return co-ords for a screen "spot" +void spot_xywh(int spot, int *x, int *y, int *w, int *h) +{ + spot = MAX(SPOT1, MIN(SPOT3, spot)); + + int width_3rd = screen_w/3; + int height_3rd = screen_h/3; + + // default, left 2/3 of screen + *x = screen_x, *y = screen_y, *w = screen_w - width_3rd, *h = screen_h; + + switch (spot) + { + // right top 2/9 of screen + case SPOT2: + *x = screen_x + screen_w - width_3rd; + *y = screen_y; + *w = width_3rd; + *h = screen_h - height_3rd; + break; + + // right bottom 1/9 of screen + case SPOT3: + *x = screen_x + screen_w - width_3rd; + *y = screen_y + screen_h - height_3rd; + *w = width_3rd; + *h = height_3rd; + break; + } +} + +// move a window to a screen "spot" +void client_spot(client *c, int spot) +{ + if (!c) return; + int x, y, w, h; + spot = MAX(SPOT1, MIN(SPOT3, spot)); + spot_xywh(spot, &x, &y, &w, &h); + + c->spot = spot; + client_position(c, x, y, w, h); +} + +// cycle through windows in a screen "spot" +void client_cycle(client *c) +{ + spot_active(c->spot, c->window); + client_lower(c); +} + +// find a window within a screen "spot" and raise/activate +Window spot_active(int spot, Window except) +{ + int x, y, w, h; + spot_xywh(spot, &x, &y, &w, &h); + + int i; stack wins; + memset(&wins, 0, sizeof(stack)); + windows_visible(&wins); + + for (i = 0; i < wins.depth; i++) + { + client *o = wins.clients[i]; + if (o->window != except && o->manage + && INTERSECT(x + w/2, y + h/2, 1, 1, + o->attr.x, o->attr.y, o->attr.width, o->attr.height)) + { + client_raise(o); + client_active(o); + return o->window; + } + } + return None; +} + +// build a stack of a window's transients and itself +void client_stack(client *c, stack *all, stack *raise) +{ + int i; client *self = NULL; + for (i = 0; i < all->depth; i++) + { + client *o = all->clients[i]; + if (o->manage && o->visible && o->transient_for == c->window) + client_stack(o, all, raise); + else + if (o->visible && o->window == c->window) + self = o; + } + if (self) + { + raise->clients[raise->depth] = self; + raise->windows[raise->depth++] = self->window; + } +} + +// raise a window and all its transients +void client_raise(client *c) +{ + int i; stack raise, all, family; + memset(&all, 0, sizeof(stack)); + memset(&raise, 0, sizeof(stack)); + memset(&family, 0, sizeof(stack)); + + windows_visible(&all); + + for (i = 0; i < all.depth; i++) + { + client *o = all.clients[i]; + // docks stay on top + if (o && o->type == atoms[_NET_WM_WINDOW_TYPE_DOCK]) + { + raise.clients[raise.depth] = o; + raise.windows[raise.depth++] = o->window; + } + } + while (c->trans) + { + client *t = window_client(c->transient_for); + if (t) + { + family.clients[family.depth++] = t; + c = t; + } + } + client_stack(c, &all, &raise); + + XRaiseWindow(display, raise.windows[0]); + XRestackWindows(display, raise.windows, raise.depth); + + while (family.depth) + client_free(family.clients[--family.depth]); +} + +// lower a window and all its transients +void client_lower(client *c) +{ + stack lower, all; + memset(&all, 0, sizeof(stack)); + memset(&lower, 0, sizeof(stack)); + + windows_visible(&all); + + client_stack(c, &all, &lower); + + XLowerWindow(display, lower.windows[0]); + XRestackWindows(display, lower.windows, lower.depth); +} + +// focus a window +void client_active(client *c) +{ + client *o; + if (!c || !c->visible) return; + + Window old = current; + current = c->window; + current_spot = c->spot; + + if (old && (o = window_client(old))) + { + client_review(o); + client_free(o); + } + + client_protocol(c, atoms[WM_TAKE_FOCUS]); + XSetInputFocus(display, c->input ? c->window: PointerRoot, RevertToPointerRoot, CurrentTime); + client_review(c); + + ewmh_active_window(c->window); +} + +// client events we care about +void window_listen(Window win) +{ + XSelectInput(display, win, EnterWindowMask | LeaveWindowMask | FocusChangeMask | PropertyChangeMask); +} + +// set border color based on focus +void client_review(client *c) +{ + XSetWindowBorder(display, c->window, color_get(c->window == current ? BORDER_FOCUS: BORDER_BLUR)); + XSetWindowBorderWidth(display, c->window, BORDER); +} + +// ------- event handlers -------- + +void create_notify(XCreateWindowEvent *e) +{ + client *c = window_client(e->window); + + if (c && c->manage) + window_listen(c->window); + + client_free(c); +} + +void configure_request(XConfigureRequestEvent *e) +{ + client *c = window_client(e->window); + + if (c) + { + if (c->manage && c->visible && !c->trans) + { + client_review(c); + client_spot(c, c->spot); + } + else + { + XWindowChanges wc; + if (e->value_mask & CWX) wc.x = e->x; + if (e->value_mask & CWY) wc.y = e->y; + if (e->value_mask & CWWidth) wc.width = e->width; + if (e->value_mask & CWHeight) wc.height = e->height; + if (e->value_mask & CWStackMode) wc.stack_mode = e->detail; + if (e->value_mask & CWBorderWidth) wc.border_width = BORDER; + XConfigureWindow(display, c->window, e->value_mask, &wc); + } + } + client_free(c); +} + +void map_request(XMapEvent *e) +{ + client *c = window_client(e->window); + + if (c && c->manage) + { + client_review(c); + + int x, w, y, h; + int spot = SPOT_START == SPOT_CURRENT ? current_spot: SPOT_START; + spot_xywh(spot, &x, &y, &w, &h); + + if (c->trans) + { + // locate a transient's parent's spot + client *t = window_client(c->transient_for); + spot = t->spot; + client_free(t); + } + if (!c->trans && c->type != atoms[_NET_WM_WINDOW_TYPE_DIALOG]) + { + // fill a spot + client_spot(c, spot); + } + else + { + // center in spot at requested size + client_position(c, + x + (w - c->attr.width)/2, y + (h - c->attr.height)/2, + c->attr.width + BORDER*2, c->attr.height + BORDER*2); + c->spot = spot; + } + } + client_free(c); + XMapWindow(display, e->window); +} + +void map_notify(XMapEvent *e) +{ + client *a = NULL, *c = window_client(e->window); + + if (c && c->manage) + { + client_raise(c); + + if (current) + a = window_client(current); + + // if no current window, or new window has opened in the + // current spot, activate it + if (!a || (a && a->spot == c->spot)) + client_active(c); + else + client_review(c); + + client_free(a); + } + client_free(c); + ewmh_client_list(); +} + +void unmap_notify(XUnmapEvent *e) +{ + if (e->window == current && !spot_active(current_spot, current)) + { + // find something to activate + if (!spot_active(SPOT1, current) + && !spot_active(SPOT2, current) + && !spot_active(SPOT3, current)) + current = None; + } + + ewmh_client_list(); +} + +void key_press(XKeyEvent *e) +{ + int i; XEvent ev; + while (XCheckTypedEvent(display, KeyPress, &ev)); + + latest = e->time; + client *c = window_client(current); + + short act = ACTION_NONE; + KeySym key = XkbKeycodeToKeysym(display, e->keycode, 0, 0); + unsigned int state = e->state & ~(LockMask|NumlockMask); + + for (i = 0; i < sizeof(keys)/sizeof(binding); i++) + { + if (keys[i].key == key && keys[i].mod == state) + { + act = keys[i].act; + break; + } + } + if (c && c->visible) + { + switch (act) + { + case ACTION_MOVE_SPOT1: + client_raise(c); + client_spot(c, SPOT1); + break; + case ACTION_MOVE_SPOT2: + client_raise(c); + client_spot(c, SPOT2); + break; + case ACTION_MOVE_SPOT3: + client_raise(c); + client_spot(c, SPOT3); + break; + case ACTION_CYCLE: + client_cycle(c); + break; + case ACTION_OTHER: + spot_active(c->spot, c->window); + break; + case ACTION_CLOSE: + client_close(c); + break; + } + } + switch (act) + { + case ACTION_FOCUS_SPOT1: + spot_active(SPOT1, None); + break; + case ACTION_FOCUS_SPOT2: + spot_active(SPOT2, None); + break; + case ACTION_FOCUS_SPOT3: + spot_active(SPOT3, None); + break; + } + client_free(c); + ewmh_client_list(); +} + +void button_press(XButtonEvent *e) +{ + latest = e->time; + client *c = window_client(e->subwindow); + + if (c) + { + if (c->manage) + { + client_raise(c); + client_active(c); + } + } + client_free(c); + + XAllowEvents(display, ReplayPointer, CurrentTime); + ewmh_client_list(); +} + +void client_message(XClientMessageEvent *e) +{ + client *c = window_client(e->window); + + if (c && c->manage) + { + if (e->message_type == atoms[_NET_ACTIVE_WINDOW]) + { + client_raise(c); + client_active(c); + } + else + if (e->message_type == atoms[_NET_CLOSE_WINDOW]) + { + client_close(c); + } + } + client_free(c); +} + +void focus_change(XFocusChangeEvent *e) +{ + client *c = window_client(e->window); + + if (c && c->manage) + client_review(c); + + client_free(c); +} + +int main(int argc, char *argv[]) +{ + int i, j; + + if(!(display = XOpenDisplay(0))) return 1; + + screen = DefaultScreenOfDisplay(display); + scr_id = DefaultScreen(display); + root = DefaultRootWindow(display); + xerror = XSetErrorHandler(oops); + screen_x = 0; + screen_y = 0; + screen_w = WidthOfScreen(screen); + screen_h = HeightOfScreen(screen); + + XSelectInput(display, root, StructureNotifyMask | SubstructureRedirectMask | SubstructureNotifyMask); + + XModifierKeymap *modmap = XGetModifierMapping(display); + for (i = 0; i < 8; i++) + for (j = 0; j < (int)modmap->max_keypermod; j++) + if (modmap->modifiermap[i*modmap->max_keypermod+j] == XKeysymToKeycode(display, XK_Num_Lock)) + { NumlockMask = (1<window, atoms[_NET_WM_STRUT_PARTIAL], strut, 12) + || window_get_cardinal_prop(c->window, atoms[_NET_WM_STRUT], strut, 4)) + { + struts[LEFT] = MAX(struts[LEFT], strut[LEFT]); + struts[RIGHT] = MAX(struts[RIGHT], strut[RIGHT]); + struts[TOP] = MAX(struts[TOP], strut[TOP]); + struts[BOTTOM] = MAX(struts[BOTTOM], strut[BOTTOM]); + } + } + if (c && c->manage) + { + window_listen(c->window); + if (!current && c->visible) + { + client_active(c); + client_raise(c); + } + if (c->visible) + client_review(c); + } + } + screen_x += struts[LEFT]; + screen_y += struts[TOP]; + screen_w -= struts[LEFT] + struts[RIGHT]; + screen_h -= struts[TOP] + struts[BOTTOM]; + + for (;;) + { + while (inplay.depth) + client_free(inplay.clients[--inplay.depth]); + + XEvent ev; + XNextEvent(display, &ev); + + switch (ev.type) + { + case CreateNotify: + create_notify(&ev.xcreatewindow); + break; + case ConfigureRequest: + configure_request(&ev.xconfigurerequest); + break; + case MapRequest: + map_request(&ev.xmap); + break; + case MapNotify: + map_notify(&ev.xmap); + break; + case UnmapNotify: + unmap_notify(&ev.xunmap); + break; + case KeyPress: + key_press(&ev.xkey); + break; + case ButtonPress: + button_press(&ev.xbutton); + break; + case ClientMessage: + client_message(&ev.xclient); + break; + case FocusIn: + case FocusOut: + focus_change(&ev.xfocus); + break; + } + } + return 0; +} diff --git a/cerberus.h b/cerberus.h new file mode 100644 index 0000000..b1c9625 --- /dev/null +++ b/cerberus.h @@ -0,0 +1,136 @@ +/* + +MIT/X11 License +Copyright (c) 2012 Sean Plistle + +Permission is hereby granted, free of charge, to any person obtaining +a copy of this software and associated documentation files (the +"Software"), to deal in the Software without restriction, including +without limitation the rights to use, copy, modify, merge, publish, +distribute, sublicense, and/or sell copies of the Software, and to +permit persons to whom the Software is furnished to do so, subject to +the following conditions: + +The above copyright notice and this permission notice shall be +included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS +OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. +IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY +CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, +TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE +SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +*/ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define MAX(a, b) ((a) > (b) ? (a) : (b)) +#define MIN(a, b) ((a) < (b) ? (a) : (b)) + +#define NEAR(a,o,b) ((b) > (a)-(o) && (b) < (a)+(o)) +#define OVERLAP(a,b,c,d) (((a)==(c) && (b)==(d)) || MIN((a)+(b), (c)+(d)) - MAX((a), (c)) > 0) +#define INTERSECT(x,y,w,h,x1,y1,w1,h1) (OVERLAP((x),(w),(x1),(w1)) && OVERLAP((y),(h),(y1),(h1))) + +Display *display; +Screen *screen; +int scr_id; +Window root; +Time latest; + +typedef struct { + Window window; + XWindowAttributes attr; + XWMHints hints; + XSizeHints size; + Window transient_for; + Atom type; + short spot, visible, trans, manage, input, urgent; +} client; + +#define STACK 64 + +typedef struct { + short depth; + client *clients[STACK]; + Window windows[STACK]; +} stack; + +short current_spot = 0; +Window current; +stack inplay; + +static int (*xerror)(Display *, XErrorEvent *); + +enum { LEFT, RIGHT, TOP, BOTTOM }; +int struts[4] = { 0, 0, 0, 0 }; +short screen_x, screen_y, screen_w, screen_h; + +#define ATOM_ENUM(x) x +#define ATOM_CHAR(x) #x + +#define GENERAL_ATOMS(X) \ + X(_MOTIF_WM_HINTS),\ + X(WM_DELETE_WINDOW),\ + X(WM_TAKE_FOCUS),\ + X(_NET_ACTIVE_WINDOW),\ + X(_NET_CLOSE_WINDOW),\ + X(_NET_WM_STRUT_PARTIAL),\ + X(_NET_WM_STRUT),\ + X(_NET_WM_WINDOW_TYPE),\ + X(_NET_WM_WINDOW_TYPE_DESKTOP),\ + X(_NET_WM_WINDOW_TYPE_DOCK),\ + X(_NET_WM_WINDOW_TYPE_SPLASH),\ + X(_NET_WM_WINDOW_TYPE_NOTIFICATION),\ + X(_NET_WM_WINDOW_TYPE_DIALOG),\ + X(_NET_CLIENT_LIST_STACKING),\ + X(_NET_WM_STATE),\ + X(WM_PROTOCOLS) + +enum { GENERAL_ATOMS(ATOM_ENUM), ATOMS }; +const char *atom_names[] = { GENERAL_ATOMS(ATOM_CHAR) }; +Atom atoms[ATOMS]; + +unsigned int NumlockMask = 0; + +enum { + ACTION_NONE, + ACTION_MOVE_SPOT1, + ACTION_MOVE_SPOT2, + ACTION_MOVE_SPOT3, + ACTION_FOCUS_SPOT1, + ACTION_FOCUS_SPOT2, + ACTION_FOCUS_SPOT3, + ACTION_CYCLE, + ACTION_CLOSE, + ACTION_OTHER, + ACTION_STATE, + ACTIONS +}; + +typedef struct { + unsigned int mod; + KeySym key; + short act; +} binding; + +enum { + SPOT_CURRENT, + SPOT1, // large left pane + SPOT2, // medium top right pane + SPOT3 // small bottom right pane +}; diff --git a/config.h b/config.h new file mode 100644 index 0000000..c3a66d6 --- /dev/null +++ b/config.h @@ -0,0 +1,19 @@ + +#define BORDER 2 +#define BORDER_BLUR "Dark Gray" +#define BORDER_FOCUS "Royal Blue" +#define SPOT_START SPOT1 + +binding keys[] = { + { .mod = Mod4Mask, .key = XK_1, .act = ACTION_FOCUS_SPOT1 }, + { .mod = Mod4Mask, .key = XK_2, .act = ACTION_FOCUS_SPOT2 }, + { .mod = Mod4Mask, .key = XK_3, .act = ACTION_FOCUS_SPOT3 }, + + { .mod = ShiftMask|Mod4Mask, .key = XK_1, .act = ACTION_MOVE_SPOT1 }, + { .mod = ShiftMask|Mod4Mask, .key = XK_2, .act = ACTION_MOVE_SPOT2 }, + { .mod = ShiftMask|Mod4Mask, .key = XK_3, .act = ACTION_MOVE_SPOT3 }, + + { .mod = Mod4Mask, .key = XK_Tab, .act = ACTION_OTHER }, + { .mod = Mod4Mask, .key = XK_grave, .act = ACTION_CYCLE }, + { .mod = Mod4Mask, .key = XK_Escape, .act = ACTION_CLOSE }, +}; diff --git a/proto.h b/proto.h new file mode 100644 index 0000000..a60d8c5 --- /dev/null +++ b/proto.h @@ -0,0 +1,36 @@ +int oops(Display *d, XErrorEvent *ee); +unsigned int color_get(const char *name); +int window_get_prop(Window w, Atom prop, Atom *type, int *items, void *buffer, int bytes); +int window_get_atom_prop(Window w, Atom atom, Atom *list, int count); +void window_set_atom_prop(Window w, Atom prop, Atom *atoms, int count); +int window_get_cardinal_prop(Window w, Atom atom, unsigned long *list, int count); +void window_set_cardinal_prop(Window w, Atom prop, unsigned long *values, int count); +void ewmh_client_list(); +void ewmh_active_window(Window w); +client* window_client(Window win); +void windows_visible(stack *s); +int window_message(Window target, Window subject, Atom atom, unsigned long protocol, unsigned long mask); +void client_free(client *c); +int client_protocol(client *c, Atom protocol); +void client_close(client *c); +void client_position(client *c, int x, int y, int w, int h); +void spot_xywh(int spot, int *x, int *y, int *w, int *h); +void client_spot(client *c, int spot); +void client_cycle(client *c); +Window spot_active(int spot, Window except); +void client_stack(client *c, stack *all, stack *raise); +void client_raise(client *c); +void client_lower(client *c); +void client_active(client *c); +void window_listen(Window win); +void client_review(client *c); +void create_notify(XCreateWindowEvent *e); +void configure_request(XConfigureRequestEvent *e); +void map_request(XMapEvent *e); +void map_notify(XMapEvent *e); +void unmap_notify(XUnmapEvent *e); +void key_press(XKeyEvent *e); +void button_press(XButtonEvent *e); +void client_message(XClientMessageEvent *e); +void focus_change(XFocusChangeEvent *e); +int main(int argc, char *argv[]);