/* 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; }