/* * Copyright 2010 Johan Veenhuizen */ #include #include #include #include #include #include #include #include "list.h" #include "wind.h" struct client { struct listener listener; List winstack; Window window; Colormap colormap; struct geometry geometry; XWMHints *wmhints; XSizeHints *wmnormalhints; Atom *wmprotocols; int wmprotocolscount; Window wmtransientfor; // WM_NAME property in current locale encoding char *wmname; // _NET_WM_NAME property in UTF-8 encoding char *netwmname; /* * This is a combination of the window group and * some logic for transient windows. Use wmhints * to figure out the ICCCM window group. */ XID group; struct frame *frame; Desk desk; /* * If this counter is zero when an UnmapNotify event * is received, the client is considered withdrawn. */ int ignoreunmapcount; Bool ismapped; Bool hasfocus; Bool pendingfocus; Bool isfull; Bool isdock; Bool skiptaskbar; Bool isundecorated; Bool followdesk; Bool initialized; }; static void cmap(struct client *); static void cunmap(struct client *); static void cpop(struct client *); static void cpush(struct client *); static void cfocusgrp(struct client *, Time); static void reloadwmtransientfor(struct client *); static void reloadwmhints(struct client *); static void reloadwmnormalhints(struct client *); static void reloadwmname(struct client *); static void reloadwmprotocols(struct client *); static void cupdatedesk(struct client *); static void buttonpress(struct client *, XButtonEvent *); static void keypress(struct client *, XKeyEvent *); static void keypress_delete(struct client *, unsigned, Time); static void keypress_pushgrp(struct client *, unsigned, Time); static void keypress_fullscreen(struct client *, unsigned, Time); static void keypress_sticky(struct client *, unsigned, Time); static void focusin(struct client *, XFocusChangeEvent *); static void focusout(struct client *, XFocusChangeEvent *); static void configurerequest(struct client *, XConfigureRequestEvent *); static void propertynotify(struct client *, XPropertyEvent *); static void maprequest(struct client *, XMapRequestEvent *); static void unmapnotify(struct client *, XUnmapEvent *); static void destroynotify(struct client *, XDestroyWindowEvent *); static void clientmessage(struct client *, XClientMessageEvent *); static void colormapnotify(struct client *, XColormapEvent *); static void event(void *, XEvent *); static void cinstallcolormaps(struct client *); static void crelease(struct client *, int); static void getclientstack(struct client ***, int *); static void csendwmproto(struct client *, Atom, Time); static Bool cisframed(struct client *); static struct client *getfronttask(void); static Bool expectsfocus(struct client *); static void cunmanage(struct client *); static void cwithdraw(struct client *); static void smartpos(struct client *); static void randpos(struct geometry *); static Bool samedesk(struct client *, struct client *); static Bool overlap(struct geometry, struct geometry); static void move(struct client *, int, int); static LIST_DEFINE(winstack); // Current desk static Desk curdesk = 0; // Number of desks static Desk ndesk = 1; // True if restacking needed static Bool needrestack = False; // Dummy window for window stacking static Window stacktop = None; static struct { KeySym keysym; unsigned modifiers; void (*function)(struct client *, unsigned, Time); } keymap[] = { { XK_BackSpace, Mod1Mask, keypress_delete }, { XK_Escape, Mod1Mask, keypress_pushgrp }, { XK_Return, Mod1Mask, keypress_fullscreen }, { XK_space, Mod1Mask, keypress_sticky }, }; void setndesk(Desk n) { if (n == 0 || n >= 0xffffffffUL) return; Desk oldn = ndesk; ndesk = n; if (n >= oldn) ewmh_notifyndesk(n); if (curdesk >= n) gotodesk(n - 1); List *lp; LIST_FOREACH_REV(lp, &winstack) { struct client *c = LIST_ITEM(lp, struct client, winstack); if (c->desk != DESK_ALL && c->desk >= n) csetdesk(c, n - 1); } if (n < oldn) ewmh_notifyndesk(n); } void gotodesk(Desk i) { if (i == curdesk || i >= ndesk || i == DESK_ALL) return; curdesk = i; // Try to make the switch visually pleasing by first mapping // windows from the new desk top-down, and then unmapping // the windows from the old desk bottom-up. List *lp; LIST_FOREACH_REV(lp, &winstack) { struct client *c = LIST_ITEM(lp, struct client, winstack); if (cisvisible(c)) cmap(c); } LIST_FOREACH(lp, &winstack) { struct client *c = LIST_ITEM(lp, struct client, winstack); if (c->followdesk && c->desk != DESK_ALL) csetdesk(c, curdesk); else if (!cisvisible(c)) cunmap(c); } ewmh_notifycurdesk(curdesk); } void csetgrpdesk(struct client *c, Desk d) { struct client **v; int n; getclientstack(&v, &n); for (int i = 0; i < n; i++) if (v[i]->group == c->group) csetdesk(v[i], d); free(v); } void csetdesk(struct client *c, Desk d) { if (d >= ndesk && d != DESK_ALL) d = ndesk - 1; c->desk = d; ewmh_notifyclientdesktop(c->window, d); if (cisvisible(c)) cmap(c); else cunmap(c); // May have become sticky if (c->frame != NULL) fupdate(c->frame); } Desk cgetdesk(struct client *c) { return c->desk; } void csetdock(struct client *c, Bool isdock) { c->isdock = isdock; if (cisframed(c)) { if (c->frame == NULL) c->frame = fcreate(c); } else if (c->frame != NULL) { fdestroy(c->frame); c->frame = NULL; } } void csetfull(struct client *c, Bool enabled) { if (enabled && !c->isfull) { if (c->frame != NULL) { fdestroy(c->frame); c->frame = NULL; } c->isfull = True; XMoveResizeWindow(dpy, c->window, -c->geometry.borderwidth, -c->geometry.borderwidth, DisplayWidth(dpy, scr), DisplayHeight(dpy, scr)); } else if (!enabled && c->isfull) { assert(c->frame == NULL); c->isfull = False; XMoveResizeWindow(dpy, c->window, c->geometry.x, c->geometry.y, c->geometry.width, c->geometry.height); if (cisframed(c)) c->frame = fcreate(c); } ewmh_notifyfull(c->window, enabled); } void csetundecorated(struct client *c, Bool enabled) { c->isundecorated = enabled; if (cisframed(c)) { if (c->frame == NULL) c->frame = fcreate(c); } else if (c->frame != NULL) { fdestroy(c->frame); c->frame = NULL; } } void csetgrpfollowdesk(struct client *c, Bool enabled) { struct client **v; int n; getclientstack(&v, &n); for (int i = 0; i < n; i++) if (v[i]->group == c->group) v[i]->followdesk = enabled; free(v); } /* * Return client window stack, from bottom (first) to top (last). * Caller deallocates using free(3). */ void getwindowstack(Window **vp, size_t *np) { List *lp; size_t n = 0; LIST_FOREACH(lp, &winstack) n++; Window *v = xmalloc(n * sizeof v[0]); size_t i = 0; LIST_FOREACH(lp, &winstack) { struct client *c = LIST_ITEM(lp, struct client, winstack); v[i++] = c->window; } *vp = v; *np = n; } void cpushgrp(struct client *c) { struct client **v; int n; getclientstack(&v, &n); for (int i = n - 1; i >= 0; i--) if (v[i]->group == c->group) cpush(v[i]); free(v); } void cpopgrp(struct client *c) { struct client **v; int n; getclientstack(&v, &n); for (int i = 0; i < n; i++) if (v[i]->group == c->group) cpop(v[i]); if (c->wmtransientfor != None) { for (int i = 0; i < n; i++) if (v[i]->window == c->wmtransientfor) { cpop(v[i]); break; } for (int i = 0; i < n; i++) if (v[i]->wmtransientfor == c->wmtransientfor) cpop(v[i]); cpop(c); } else { cpop(c); for (int i = 0; i < n; i++) if (v[i]->wmtransientfor == c->window) cpop(v[i]); } free(v); } /* * Return client stack, from bottom (first) to top (last). * Caller deallocates using free(3). */ static void getclientstack(struct client ***vp, int *np) { int n = 0; List *lp; LIST_FOREACH(lp, &winstack) n++; struct client **v = xmalloc(n * sizeof *v); struct client **p = v; LIST_FOREACH(lp, &winstack) *p++ = LIST_ITEM(lp, struct client, winstack); *vp = v; *np = n; } static void cpop(struct client *c) { if (LIST_TAIL(&winstack) != &c->winstack) { LIST_REMOVE(&c->winstack); LIST_INSERT_TAIL(&winstack, &c->winstack); needrestack = True; } } static void cpush(struct client *c) { if (LIST_HEAD(&winstack) != &c->winstack) { LIST_REMOVE(&c->winstack); LIST_INSERT_HEAD(&winstack, &c->winstack); needrestack = True; } } void restack(void) { if (!needrestack) return; int n = 1; List *lp; LIST_FOREACH(lp, &winstack) n++; Window *v = xmalloc(n * sizeof *v); int i = 0; assert(stacktop != None); v[i++] = stacktop; LIST_FOREACH_REV(lp, &winstack) { struct client *c = LIST_ITEM(lp, struct client, winstack); v[i++] = c->frame == NULL ? c->window : fgetwin(c->frame); } assert(i == n); XRestackWindows(dpy, v, n); free(v); needrestack = False; ewmh_notifyrestack(); } static void reloadwmtransientfor(struct client *c) { c->wmtransientfor = None; XGetTransientForHint(dpy, c->window, &c->wmtransientfor); if (c->wmtransientfor != None) { /* * Inherit the window group of the main window, * or use the main window ID if it has no group. */ c->group = c->wmtransientfor; XWMHints *h = XGetWMHints(dpy, c->wmtransientfor); if (h != NULL) { if (h->flags & WindowGroupHint) c->group = h->window_group; XFree(h); } cupdatedesk(c); } } static void reloadwmhints(struct client *c) { if (c->wmhints != NULL) XFree(c->wmhints); c->wmhints = XGetWMHints(dpy, c->window); if (c->wmtransientfor == None) { c->group = c->window; if (c->wmhints != NULL) { if (c->wmhints->flags & WindowGroupHint) c->group = c->wmhints->window_group; } cupdatedesk(c); } } static void reloadwmnormalhints(struct client *c) { if (c->wmnormalhints == NULL) c->wmnormalhints = XAllocSizeHints(); if (c->wmnormalhints != NULL) { c->wmnormalhints->flags = 0; long dummy; XGetWMNormalHints(dpy, c->window, c->wmnormalhints, &dummy); } } static void reloadwmname(struct client *c) { free(c->wmname); c->wmname = NULL; XTextProperty p; if (XGetWMName(dpy, c->window, &p) != 0) { c->wmname = decodetextproperty(&p); if (p.value != NULL) XFree(p.value); } if (c->frame != NULL) fupdate(c->frame); } static void reloadwmprotocols(struct client *c) { if (c->wmprotocols != NULL) { XFree(c->wmprotocols); c->wmprotocols = NULL; } c->wmprotocolscount = 0; XGetWMProtocols(dpy, c->window, &c->wmprotocols, &c->wmprotocolscount); } static void cupdatedesk(struct client *c) { Desk d = c->desk; struct client **v; int n; getclientstack(&v, &n); if (c->wmtransientfor != None) { for (int i = n - 1; i >= 0; i--) if (v[i]->window == c->wmtransientfor) { d = v[i]->desk; break; } } else if (c->group != c->window) { for (int i = n - 1; i >= 0; i--) if (v[i]->group == c->group && v[i] != c) { d = v[i]->desk; break; } } free(v); if (d != c->desk) csetdesk(c, d); } static void buttonpress(struct client *c, XButtonEvent *e) { cpopgrp(c); cfocus(c, e->time); ungrabbutton(AnyButton, AnyModifier, c->window); XAllowEvents(dpy, ReplayPointer, CurrentTime); } static void keypress(struct client *c, XKeyEvent *e) { for (int i = 0; i < NELEM(keymap); i++) if (XKeysymToKeycode(dpy, keymap[i].keysym) == e->keycode) keymap[i].function(c, e->state, e->time); } static void keypress_delete(struct client *c, unsigned state, Time time) { if (!c->isdock) cdelete(c, time); } static void keypress_pushgrp(struct client *c, unsigned state, Time time) { cpushgrp(c); refocus(time); } static void keypress_fullscreen(struct client *c, unsigned state, Time time) { if (!c->isdock) csetfull(c, !c->isfull); } static void keypress_sticky(struct client *c, unsigned state, Time time) { if (c->isdock) return; if (cgetdesk(c) == DESK_ALL) csetgrpdesk(c, curdesk); else { csetgrpdesk(c, DESK_ALL); // Make sure we are still on top when switching desks. cpopgrp(c); } } static void focusin(struct client *c, XFocusChangeEvent *e) { // Ignore ungrab and pointer events to prevent confusion. if (e->mode == NotifyUngrab || e->detail == NotifyPointerRoot || e->detail == NotifyPointer) return; // This should not happen. if (c->hasfocus || !c->ismapped) return; cinstallcolormaps(c); c->pendingfocus = False; c->hasfocus = True; if (c->frame != NULL) fupdate(c->frame); ewmh_notifyfocus(None, c->window); } static void focusout(struct client *c, XFocusChangeEvent *e) { if (e->mode == NotifyGrab) return; if (e->detail == NotifyPointerRoot || e->detail == NotifyPointer || e->detail == NotifyInferior) return; if (!c->hasfocus) return; c->hasfocus = False; grabbutton(AnyButton, AnyModifier, c->window, True, ButtonPressMask, GrabModeSync, GrabModeAsync, None, None); if (c->frame != NULL) fupdate(c->frame); ewmh_notifyfocus(c->window, None); } static void configurerequest(struct client *c, XConfigureRequestEvent *e) { if (c->frame != NULL) { /* * If this happens, we are processing an event that * was sent before we created the frame. We need to * redirect the event manually. Note that this should * only happen immediately after creating a frame. * * XMMS is one program that triggers this particularly * often, and so is the "Save As" dialog of Firefox. */ redirect((XEvent *)e, fgetwin(c->frame)); return; } if (c->isfull) { // Deny fullscreen windows to reconfigure themselves. csendconf(c); return; } unsigned long mask = e->value_mask & (CWX | CWY | CWWidth | CWHeight | CWBorderWidth); if (mask & CWX) c->geometry.x = e->x; if (mask & CWY) c->geometry.y = e->y; if (mask & CWWidth) c->geometry.width = e->width; if (mask & CWHeight) c->geometry.height = e->height; if (mask & CWBorderWidth) c->geometry.borderwidth = e->border_width; XConfigureWindow(dpy, c->window, mask, &(XWindowChanges){ .x = c->geometry.x, .y = c->geometry.y, .width = c->geometry.width, .height = c->geometry.height, .border_width = c->geometry.borderwidth }); } static void propertynotify(struct client *c, XPropertyEvent *e) { switch (e->atom) { case XA_WM_NAME: reloadwmname(c); break; case XA_WM_HINTS: reloadwmhints(c); break; case XA_WM_NORMAL_HINTS: reloadwmnormalhints(c); break; case XA_WM_TRANSIENT_FOR: reloadwmtransientfor(c); break; default: if (e->atom == WM_PROTOCOLS) reloadwmprotocols(c); break; } ewmh_propertynotify(c, e); mwm_propertynotify(c, e); } /* * We don't listen to this event ourselves, but get it redirected to * us from the root listener and from the frame listener. */ static void maprequest(struct client *c, XMapRequestEvent *e) { ewmh_maprequest(c); cpopgrp(c); if (cisvisible(c)) { cmap(c); cfocus(c, CurrentTime); } } static void unmapnotify(struct client *c, XUnmapEvent *e) { if (c->ignoreunmapcount > 0) c->ignoreunmapcount--; else cwithdraw(c); } static void destroynotify(struct client *c, XDestroyWindowEvent *e) { cwithdraw(c); } static void clientmessage(struct client *c, XClientMessageEvent *e) { if (e->message_type == WM_CHANGE_STATE && e->format == 32 && e->data.l[0] == IconicState) { /* * Wind doesn't allow hidden windows, so just push it. */ cpushgrp(c); if (chasfocus(c)) refocus(CurrentTime); } ewmh_clientmessage(c, e); } static void colormapnotify(struct client *c, XColormapEvent *e) { if (e->new) { c->colormap = e->colormap; if (c->hasfocus) cinstallcolormaps(c); } } static void event(void *self, XEvent *e) { switch (e->type) { case ButtonPress: buttonpress(self, &e->xbutton); break; case KeyPress: keypress(self, &e->xkey); break; case FocusIn: focusin(self, &e->xfocus); break; case FocusOut: focusout(self, &e->xfocus); break; case ConfigureRequest: configurerequest(self, &e->xconfigurerequest); break; case PropertyNotify: propertynotify(self, &e->xproperty); break; case MapRequest: // We get this redirected to us from the root listener // and from the frame listener. maprequest(self, &e->xmaprequest); break; case UnmapNotify: unmapnotify(self, &e->xunmap); break; case DestroyNotify: destroynotify(self, &e->xdestroywindow); break; case ClientMessage: clientmessage(self, &e->xclient); break; case ColormapNotify: colormapnotify(self, &e->xcolormap); break; } } static void cinstallcolormaps(struct client *c) { XInstallColormap(dpy, c->colormap == None ? DefaultColormap(dpy, scr) : c->colormap); } /* * Returns true if the window is, or should be, visible. */ Bool cisvisible(struct client *c) { return c->desk == curdesk || c->desk == DESK_ALL; } /* * Returns true if the window expects keyboard input focus. * A window that does not specify an input hint is considered * to expect focus. */ static Bool expectsfocus(struct client *c) { return c->wmhints == NULL || !(c->wmhints->flags & InputHint) || c->wmhints->input; } /* * Returns true if the window is a task, i.e. if it appears in taskbars. */ Bool cistask(struct client *c) { return !c->skiptaskbar && c->wmtransientfor == None; } struct client *manage(Window window) { XWindowAttributes attr; if (!XGetWindowAttributes(dpy, window, &attr)) return NULL; if (attr.override_redirect) return NULL; long wmstate = getwmstate(window); if (wmstate == WithdrawnState) { XWMHints *h = XGetWMHints(dpy, window); if (h == NULL) wmstate = NormalState; else { if (h->flags & StateHint) wmstate = h->initial_state; else wmstate = NormalState; XFree(h); } } if (wmstate == WithdrawnState) return NULL; struct client *c = xmalloc(sizeof *c); LIST_INIT(&c->winstack); LIST_INSERT_TAIL(&winstack, &c->winstack); needrestack = True; c->desk = curdesk; c->frame = NULL; c->wmname = NULL; c->netwmname = NULL; c->wmhints = NULL; c->wmnormalhints = NULL; c->wmprotocols = NULL; c->wmprotocolscount = 0; c->wmtransientfor = None; c->window = window; c->group = window; c->colormap = attr.colormap; c->ignoreunmapcount = 0; c->hasfocus = False; c->pendingfocus = False; c->isfull = False; c->isdock = False; c->skiptaskbar = False; c->isundecorated = False; c->followdesk = False; c->initialized = False; csetgeom(c, (struct geometry){ attr.x, attr.y, attr.width, attr.height, attr.border_width }); XAddToSaveSet(dpy, c->window); /* * Everything initialized to default values. Register * for events and THEN (re)load all attributes and * properties. This avoids losing update events. */ c->listener.function = event; c->listener.pointer = c; setlistener(c->window, &c->listener); XSelectInput(dpy, c->window, StructureNotifyMask | PropertyChangeMask | ColormapChangeMask | FocusChangeMask); XSync(dpy, False); /* * Done registering for events. What we read now is * safe to use, since any updates will be notified * to our event listener. */ c->ismapped = ismapped(c->window); if (!XGetWindowAttributes(dpy, c->window, &attr)) { // The window disappeared. crelease(c, True); return NULL; } csetgeom(c, (struct geometry){ attr.x, attr.y, attr.width, attr.height, attr.border_width }); c->colormap = attr.colormap; reloadwmname(c); reloadwmhints(c); reloadwmnormalhints(c); reloadwmprotocols(c); reloadwmtransientfor(c); /* * Let the hints create the frame, if there should be one. */ ewmh_manage(c); mwm_manage(c); grabbutton(AnyButton, AnyModifier, c->window, True, ButtonPressMask, GrabModeSync, GrabModeAsync, None, None); for (int i = 0; i < NELEM(keymap); i++) grabkey(XKeysymToKeycode(dpy, keymap[i].keysym), keymap[i].modifiers, c->window, True, GrabModeAsync, GrabModeAsync); if (c->geometry.width == DisplayWidth(dpy, scr) && c->geometry.height == DisplayHeight(dpy, scr)) csetfull(c, True); if (!cisframed(c)) ewmh_notifyframeextents(c->window, (struct extents){ .top = 0, .bottom = 0, .left = 0, .right = 0 }); XSizeHints *h = c->wmnormalhints; if (runlevel != RL_STARTUP && (h == NULL || (h->flags & (USPosition | PPosition)) == 0)) smartpos(c); /* * Make sure WM_STATE is always initiated. We can't trust * the first call to cmap/cunmap. */ setwmstate(c->window, cisvisible(c) ? NormalState : IconicState); c->initialized = True; if (wmstate == IconicState && runlevel == RL_NORMAL) { // Closest thing to iconic state cpush(c); if (cisvisible(c)) cmap(c); } else { cpopgrp(c); if (cisvisible(c)) { cmap(c); if (runlevel == RL_NORMAL) cfocus(c, CurrentTime); } } return c; } void manageall(void) { assert(stacktop == None); stacktop = XCreateWindow(dpy, root, 0, 0, 100, 100, 0, CopyFromParent, InputOnly, CopyFromParent, 0, NULL); Window r, p, *stack; unsigned n; if (XQueryTree(dpy, root, &r, &p, &stack, &n) != 0) { for (int i = 0; i < n; i++) { if (ismapped(stack[i])) manage(stack[i]); } if (stack != NULL) XFree(stack); } restack(); } static void cmap(struct client *c) { assert(c->desk == curdesk || c->desk == DESK_ALL); // Prevent premature mapping if (!c->initialized) return; if (!c->ismapped) { /* * Make sure stacking order is correct before * mapping the window. */ restack(); if (c->frame != NULL) { Window f = fgetwin(c->frame); XMapSubwindows(dpy, f); XMapWindow(dpy, f); } else XMapWindow(dpy, c->window); c->ismapped = True; setwmstate(c->window, NormalState); } } static void cunmap(struct client *c) { if (c->ismapped) { c->ismapped = False; c->ignoreunmapcount++; if (c->frame != NULL) { Window f = fgetwin(c->frame); XUnmapWindow(dpy, f); XUnmapSubwindows(dpy, f); } else XUnmapWindow(dpy, c->window); setwmstate(c->window, IconicState); } } static void crelease(struct client *c, Bool clientrequested) { // Unset this or fdestroy() will refocus the window. c->hasfocus = False; if (c->frame != NULL) { fdestroy(c->frame); c->frame = NULL; } LIST_REMOVE(&c->winstack); needrestack = True; ungrabkey(AnyKey, AnyModifier, c->window); XSelectInput(dpy, c->window, 0); setlistener(c->window, NULL); if (!clientrequested) XMapWindow(dpy, c->window); XRemoveFromSaveSet(dpy, c->window); /* * If we recently tried to focus this window (pendingfocus) * and no other window currently has the focus, we need to * find a new focus window. */ if (c->pendingfocus && getfocus() == NULL) refocus(CurrentTime); if (c->wmnormalhints != NULL) XFree(c->wmnormalhints); if (c->wmhints != NULL) XFree(c->wmhints); if (c->wmprotocols != NULL) XFree(c->wmprotocols); free(c->wmname); free(c->netwmname); free(c); } static void cwithdraw(struct client *c) { ewmh_withdraw(c); Window w = c->window; crelease(c, True); setwmstate(w, WithdrawnState); } static void cunmanage(struct client *c) { ewmh_unmanage(c); Window w = c->window; crelease(c, False); setwmstate(w, NormalState); } void cdelete(struct client *c, Time time) { /* * Don't check if it supports delete. Most windows do, * and half of the ones that don't announce support * just forgot to announce it. */ csendwmproto(c, WM_DELETE_WINDOW, time); } void unmanageall(void) { while (!LIST_EMPTY(&winstack)) { List *lp = LIST_TAIL(&winstack); struct client *c = LIST_ITEM(lp, struct client, winstack); cunmanage(c); } if (stacktop != None) { XDestroyWindow(dpy, stacktop); stacktop = None; } } struct client *getfocus(void) { List *lp; LIST_FOREACH_REV(lp, &winstack) { struct client *c = LIST_ITEM(lp, struct client, winstack); if (c->hasfocus) { assert(c->desk == curdesk || c->desk == DESK_ALL); assert(c->ismapped); return c; } } return NULL; } static struct client *getfronttask(void) { List *lp; LIST_FOREACH_REV(lp, &winstack) { struct client *c = LIST_ITEM(lp, struct client, winstack); if (cisvisible(c) && cistask(c)) return c; } return NULL; } static void cfocusgrp(struct client *c, Time time) { struct client *topmost = NULL; struct client *focus = NULL; // Find a window in the group that expects focus. List *lp; LIST_FOREACH_REV(lp, &winstack) { struct client *x = LIST_ITEM( lp, struct client, winstack); if (x->group == c->group && cisvisible(x)) { if (topmost == NULL) topmost = x; if (focus == NULL && expectsfocus(x)) focus = x; } } if (focus == NULL) focus = topmost; assert(focus != NULL); cfocus(focus, time); } void cfocus(struct client *c, Time time) { if (c->hasfocus || !cisvisible(c)) return; /* * ICCCM says we MUST NOT send CurrentTime in WM_TAKE_FOCUS * messages, so if we are given CurrentTime we set focus * explicitly. */ if (time != CurrentTime && !expectsfocus(c) && chaswmproto(c, WM_TAKE_FOCUS)) csendwmproto(c, WM_TAKE_FOCUS, time); else XSetInputFocus(dpy, c->window, RevertToPointerRoot, time); c->pendingfocus = True; } /* * Focus the front window and return it. */ struct client *refocus(Time time) { struct client *c = getfronttask(); if (c != NULL) cfocusgrp(c, time); return c; } int namewidth(struct font *font, struct client *c) { if (c->netwmname != NULL) return fttextwidth_utf8(font, c->netwmname); else if (c->wmname != NULL) return fttextwidth(font, c->wmname); else return 0; } void drawname(Drawable d, struct font *font, struct fontcolor *color, int x, int y, struct client *c) { if (c->netwmname != NULL) ftdrawstring_utf8(d, font, color, x, y, c->netwmname); else if (c->wmname != NULL) ftdrawstring(d, font, color, x, y, c->wmname); } Bool chaswmproto(struct client *c, Atom protocol) { for (int i = 0; i < c->wmprotocolscount; i++) if (c->wmprotocols[i] == protocol) return True; return False; } static void csendwmproto(struct client *c, Atom protocol, Time time) { XEvent e; memset(&e, 0, sizeof e); e.xclient.type = ClientMessage; e.xclient.window = c->window; e.xclient.message_type = WM_PROTOCOLS; e.xclient.format = 32; e.xclient.data.l[0] = protocol; e.xclient.data.l[1] = time; XSendEvent(dpy, c->window, False, 0L, &e); } struct geometry cgetgeom(struct client *c) { if (c->isfull) return (struct geometry){ .x = -c->geometry.borderwidth, .y = -c->geometry.borderwidth, .width = DisplayWidth(dpy, scr), .height = DisplayHeight(dpy, scr), .borderwidth = c->geometry.borderwidth }; else return c->geometry; } void csetgeom(struct client *c, struct geometry g) { c->geometry = g; } void adjustclientsize(struct client *c, int width, int height, int *rwidth, int *rheight) { width = MAX(1, width); height = MAX(1, height); XSizeHints *h = c->wmnormalhints; if (h == NULL) { *rwidth = width; *rheight = height; return; } int basewidth; int baseheight; if (h->flags & PBaseSize) { basewidth = h->base_width; baseheight = h->base_height; } else if (h->flags & PMinSize) { basewidth = h->min_width; baseheight = h->min_height; } else { basewidth = 0; baseheight = 0; } // Cannot be less than the base (or minimum) width = MAX(basewidth, width); height = MAX(baseheight, height); if (h->flags & PResizeInc) { if (h->width_inc != 0) width -= (width - basewidth) % h->width_inc; if (h->height_inc != 0) height -= (height - baseheight) % h->height_inc; } if (h->flags & PMaxSize) { width = MIN(h->max_width, width); height = MIN(h->max_height, height); } width = MAX(1, width); height = MAX(1, height); *rwidth = width; *rheight = height; } void csendconf(struct client *c) { struct geometry g = cgetgeom(c); XSendEvent(dpy, c->window, False, StructureNotifyMask, (XEvent *)&(XConfigureEvent){ .type = ConfigureNotify, .event = c->window, .window = c->window, .x = g.x, .y = g.y, .width = g.width, .height = g.height, .border_width = g.borderwidth, .above = None, .override_redirect = False }); } Bool chasfocus(struct client *c) { return c->hasfocus; } Window cgetwin(struct client *c) { return c->window; } void csetnetwmname(struct client *c, const char *name) { free(c->netwmname); c->netwmname = (name == NULL) ? NULL : xstrdup(name); if (c->frame != NULL) fupdate(c->frame); } Bool cgetwmnormalhints(struct client *c, XSizeHints *hints) { if (c->wmnormalhints == NULL) return False; else { *hints = *c->wmnormalhints; return True; } } void cignoreunmap(struct client *c) { assert(c->ismapped); c->ignoreunmapcount++; } Bool cismapped(struct client *c) { return c->ismapped; } static Bool cisframed(struct client *c) { return !c->isfull && !c->isdock && !c->isundecorated; } void csetskiptaskbar(struct client *c, Bool skiptaskbar) { c->skiptaskbar = skiptaskbar; } /* * Find a good location for the specified client and move it there. */ static void smartpos(struct client *c) { struct geometry g = c->frame == NULL ? cgetgeom(c) : fgetgeom(c->frame); struct client **v; int n; getclientstack(&v, &n); // Exclude the window itself, and clients on other desks for (int i = 0; i < n; i++) if (v[i] == c || !samedesk(v[i], c)) v[i--] = v[--n]; /* * Examine a number of random locations and pick the least bad. * A "bad" location is defined as a location that makes the * window overlap other windows, the more the worse. This is * in no way optimal, but it is reasonably fast and avoids * really bad locations. */ unsigned min = ~0; struct geometry best = g; for (int k = 0; min != 0 && k < 100; k++) { randpos(&g); unsigned badness = 0; for (int i = 0; i < n; i++) { struct geometry g2 = v[i]->frame == NULL ? cgetgeom(v[i]) : fgetgeom(v[i]->frame); if (overlap(g, g2)) badness++; } if (badness < min) { min = badness; best = g; } } move(c, best.x, best.y); free(v); } /* * Find a random location for the specified geometry. */ static void randpos(struct geometry *g) { int maxx = DisplayWidth(dpy, scr) - (g->width + 2 * g->borderwidth); int maxy = DisplayHeight(dpy, scr) - (g->height + 2 * g->borderwidth); g->x = maxx > 0 ? rand() % maxx : 0; g->y = maxy > 0 ? rand() % maxy : 0; } /* * Return true if, and only if, the two clients are visible on the same desk. */ static Bool samedesk(struct client *c1, struct client *c2) { return c1->desk == c2->desk || c1->desk == DESK_ALL || c2->desk == DESK_ALL; } /* * Return true if, and only if, the two geometries overlap. */ static Bool overlap(struct geometry g1, struct geometry g2) { int x1 = g1.x; int x2 = g2.x; int x3 = g1.x + g1.width + 2 * g1.borderwidth; int x4 = g2.x + g2.width + 2 * g2.borderwidth; int y1 = g1.y; int y2 = g2.y; int y3 = g1.y + g1.height + 2 * g1.borderwidth; int y4 = g2.y + g2.height + 2 * g2.borderwidth; return x1 < x4 && x2 < x3 && y1 < y4 && y2 < y3; } /* * XXX: We move a window by simulating a ConfigureRequest from * the client. */ static void move(struct client *c, int x, int y) { Window parent = c->frame == NULL ? root : fgetwin(c->frame); XEvent e; e.xconfigurerequest = (XConfigureRequestEvent){ .type = ConfigureRequest, .serial = 0, .send_event = True, .display = dpy, .parent = parent, .window = c->window, .x = x, .y = y, .value_mask = CWX | CWY }; redirect(&e, parent); }