/*
* Copyright 2010 Johan Veenhuizen
*/
#include <assert.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <X11/Xatom.h>
#include <X11/Xlib.h>
#include <X11/Xutil.h>
#include "list.h"
#include "wind.h"
struct client {
struct eventlistener 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 isstalker;
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 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 eventlistener(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 randpos(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->isstalker && c->desk != DESK_ALL)
csetdesk(c, curdesk);
else if (!cisvisible(c))
cunmap(c);
}
ewmh_notifycurdesk(curdesk);
}
void csetdesk(struct client *c, Desk i)
{
if (i != DESK_ALL && i >= ndesk)
i = ndesk - 1;
c->desk = i;
ewmh_notifyclientdesktop(c->window, i);
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 csetstalker(struct client *c, Bool enabled)
{
c->isstalker = enabled;
}
/*
* 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)
{
/*
* FIXME: Should we only push on c's desk?
*/
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);
/*
* FIXME: Should we only pop on c's desk?
*/
for (int i = 0; i < n; i++)
if (v[i]->group == c->group)
cpop(v[i]);
/*
* Take care of transient windows.
*/
if (c->wmtransientfor != None) {
// Raise the main window
for (int i = 0; i < n; i++)
if (v[i]->window == c->wmtransientfor) {
cpop(v[i]);
break;
}
// Raise all transients
for (int i = 0; i < n; i++)
if (v[i]->wmtransientfor == c->wmtransientfor)
cpop(v[i]);
// Raise ourself
cpop(c);
} else {
cpop(c);
// Put our transients above
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)
{
LIST_REMOVE(&c->winstack);
LIST_INSERT_TAIL(&winstack, &c->winstack);
needrestack = True;
}
static void cpush(struct client *c)
{
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);
}
/*
* Use main window's desk.
*/
struct client **v;
int n;
getclientstack(&v, &n);
for (int i = n - 1; i >= 0; i--)
if (v[i]->window == c->wmtransientfor) {
csetdesk(c, v[i]->desk);
break;
}
free(v);
}
}
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;
}
}
}
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 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)
csetdesk(c, curdesk);
else {
csetdesk(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) {
/*
* We don't do icons, but we get your point.
*/
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 eventlistener(void *self, XEvent *e)
{
struct client *c = self;
switch (e->type) {
case ButtonPress:
buttonpress(c, &e->xbutton);
break;
case KeyPress:
keypress(c, &e->xkey);
break;
case FocusIn:
focusin(c, &e->xfocus);
break;
case FocusOut:
focusout(c, &e->xfocus);
break;
case ConfigureRequest:
configurerequest(c, &e->xconfigurerequest);
break;
case PropertyNotify:
propertynotify(c, &e->xproperty);
break;
case MapRequest:
// We get this redirected to us from the root listener
// and from the frame listener.
maprequest(c, &e->xmaprequest);
break;
case UnmapNotify:
unmapnotify(c, &e->xunmap);
break;
case DestroyNotify:
destroynotify(c, &e->xdestroywindow);
break;
case ClientMessage:
clientmessage(c, &e->xclient);
break;
case ColormapNotify:
colormapnotify(c, &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->isstalker = 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 = eventlistener;
c->listener.pointer = c;
seteventlistener(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)) {
struct geometry g = (c->frame == NULL) ?
cgetgeom(c) : fgetgeom(c->frame);
randpos(&g);
move(c, g.x, g.y);
}
/*
* 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, 1, 1, 0, CopyFromParent,
InputOnly, CopyFromParent, CWOverrideRedirect,
&(XSetWindowAttributes){ .override_redirect = True });
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);
seteventlistener(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;
}
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;
}
/*
* 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);
}