/* * Copyright 2010 Johan Veenhuizen */ #include #include #include #include #include #include "wind.h" #define xatom(name) XInternAtom(dpy, (name), False) #define NET_WM_STATE_REMOVE 0 #define NET_WM_STATE_ADD 1 #define NET_WM_STATE_TOGGLE 2 static void addclient(Window); static void delclient(Window); static unsigned long ewmh_getndesktops(void); static void setcurrentdesktop(unsigned long); static void reloadwindowname(struct client *); static void reloadwindowstate(struct client *); static void reloadwindowtype(struct client *); static void reloadwindowdesktop(struct client *); static Bool hasstate(Window, Atom); static void removestate(Window, Atom); static void addstate(Window, Atom); static void changestate(Window, int, Atom); /* * The list of supported properties. Note that we need to * include some properties that we actually never use in * the _NET_SUPPORTED list in order to show applications * that we do indeed know about them. For example, pagers * act strangely if we don't mention _NET_WM_STATE_HIDDEN. * * NB: Keep this list sorted. */ static Atom NET_ACTIVE_WINDOW; static Atom NET_CLIENT_LIST; static Atom NET_CLIENT_LIST_STACKING; static Atom NET_CLOSE_WINDOW; static Atom NET_CURRENT_DESKTOP; static Atom NET_DESKTOP_GEOMETRY; static Atom NET_DESKTOP_VIEWPORT; static Atom NET_FRAME_EXTENTS; static Atom NET_NUMBER_OF_DESKTOPS; static Atom NET_REQUEST_FRAME_EXTENTS; static Atom NET_SUPPORTED; static Atom NET_SUPPORTING_WM_CHECK; static Atom NET_WM_ACTION_CHANGE_DESKTOP; static Atom NET_WM_ACTION_CLOSE; static Atom NET_WM_ACTION_FULLSCREEN; static Atom NET_WM_ACTION_MINIMIZE; static Atom NET_WM_ALLOWED_ACTIONS; static Atom NET_WM_DESKTOP; static Atom NET_WM_ICON_NAME; static Atom NET_WM_NAME; static Atom NET_WM_STATE; static Atom NET_WM_STATE_ABOVE; static Atom NET_WM_STATE_BELOW; static Atom NET_WM_STATE_FULLSCREEN; static Atom NET_WM_STATE_HIDDEN; static Atom NET_WM_STATE_SKIP_TASKBAR; static Atom NET_WM_VISIBLE_ICON_NAME; static Atom NET_WM_VISIBLE_NAME; static Atom NET_WM_WINDOW_TYPE; static Atom NET_WM_WINDOW_TYPE_DOCK; static Atom NET_WORKAREA; static Atom UTF8_STRING; static Window wmcheckwin = None; static struct { Window *v; size_t n; size_t lim; } clientlist = { NULL, 0, 0 }; void ewmh_notifyclientdesktop(Window w, unsigned long i) { setprop(w, NET_WM_DESKTOP, XA_CARDINAL, 32, &i, 1); } void ewmh_notifycurdesk(unsigned long n) { setprop(root, NET_CURRENT_DESKTOP, XA_CARDINAL, 32, &n, 1); } void ewmh_notifyframeextents(Window w, struct extents e) { unsigned long v[4] = { e.left, e.right, e.top, e.bottom }; setprop(w, NET_FRAME_EXTENTS, XA_CARDINAL, 32, v, NELEM(v)); } static void addclient(Window w) { if (clientlist.n == clientlist.lim) { clientlist.lim += 32; clientlist.v = xrealloc(clientlist.v, clientlist.lim * sizeof clientlist.v[0]); } clientlist.v[clientlist.n++] = w; setprop(root, NET_CLIENT_LIST, XA_WINDOW, 32, clientlist.v, clientlist.n); } static void delclient(Window w) { int i; for (i = 0; i < clientlist.n && clientlist.v[i] != w; i++) ; if (i < clientlist.n) { for (; i < clientlist.n - 1; i++) clientlist.v[i] = clientlist.v[i + 1]; clientlist.n--; } setprop(root, NET_CLIENT_LIST, XA_WINDOW, 32, clientlist.v, clientlist.n); if (clientlist.n == 0) { free(clientlist.v); clientlist.v = NULL; clientlist.lim = 0; } } static unsigned long ewmh_getndesktops(void) { unsigned long ndesk = 1; unsigned long n; unsigned long *p = getprop(root, NET_NUMBER_OF_DESKTOPS, XA_CARDINAL, 32, &n); if (p != NULL) { if (n == 1) ndesk = *p & 0xffffffffUL; XFree(p); } return ndesk; } void ewmh_notifyndesk(unsigned long n) { long *viewport = xmalloc(n * 2 * sizeof (long)); long *workarea = xmalloc(n * 4 * sizeof (long)); for (unsigned long i = 0; i < n; i++) { viewport[2 * i + 0] = 0; viewport[2 * i + 1] = 0; workarea[4 * i + 0] = 0; workarea[4 * i + 1] = 0; workarea[4 * i + 2] = DisplayWidth(dpy, scr); workarea[4 * i + 3] = DisplayHeight(dpy, scr); } setprop(root, NET_DESKTOP_VIEWPORT, XA_CARDINAL, 32, viewport, n * 2); setprop(root, NET_WORKAREA, XA_CARDINAL, 32, workarea, n * 4); free(workarea); free(viewport); setprop(root, NET_NUMBER_OF_DESKTOPS, XA_CARDINAL, 32, &n, 1); } static void setcurrentdesktop(unsigned long i) { setprop(root, NET_CURRENT_DESKTOP, XA_CARDINAL, 32, &i, 1); } void ewmh_startwm(void) { UTF8_STRING = xatom("UTF8_STRING"); Atom v[] = { // Keep sorted NET_ACTIVE_WINDOW = xatom("_NET_ACTIVE_WINDOW"), NET_CLIENT_LIST = xatom("_NET_CLIENT_LIST"), NET_CLIENT_LIST_STACKING = xatom("_NET_CLIENT_LIST_STACKING"), NET_CLOSE_WINDOW = xatom("_NET_CLOSE_WINDOW"), NET_CURRENT_DESKTOP = xatom("_NET_CURRENT_DESKTOP"), NET_DESKTOP_GEOMETRY = xatom("_NET_DESKTOP_GEOMETRY"), NET_DESKTOP_VIEWPORT = xatom("_NET_DESKTOP_VIEWPORT"), NET_FRAME_EXTENTS = xatom("_NET_FRAME_EXTENTS"), NET_NUMBER_OF_DESKTOPS = xatom("_NET_NUMBER_OF_DESKTOPS"), NET_REQUEST_FRAME_EXTENTS = xatom("_NET_REQUEST_FRAME_EXTENTS"), NET_SUPPORTED = xatom("_NET_SUPPORTED"), NET_SUPPORTING_WM_CHECK = xatom("_NET_SUPPORTING_WM_CHECK"), NET_WM_ACTION_CHANGE_DESKTOP = xatom("_NET_WM_ACTION_CHANGE_DESKTOP"), NET_WM_ACTION_CLOSE = xatom("_NET_WM_ACTION_CLOSE"), NET_WM_ACTION_FULLSCREEN = xatom("_NET_WM_ACTION_FULLSCREEN"), NET_WM_ACTION_MINIMIZE = xatom("_NET_WM_ACTION_MINIMIZE"), NET_WM_ALLOWED_ACTIONS = xatom("_NET_WM_ALLOWED_ACTIONS"), NET_WM_DESKTOP = xatom("_NET_WM_DESKTOP"), NET_WM_ICON_NAME = xatom("_NET_WM_ICON_NAME"), NET_WM_NAME = xatom("_NET_WM_NAME"), NET_WM_STATE = xatom("_NET_WM_STATE"), NET_WM_STATE_ABOVE = xatom("_NET_WM_STATE_ABOVE"), NET_WM_STATE_BELOW = xatom("_NET_WM_STATE_BELOW"), NET_WM_STATE_FULLSCREEN = xatom("_NET_WM_STATE_FULLSCREEN"), NET_WM_STATE_HIDDEN = xatom("_NET_WM_STATE_HIDDEN"), NET_WM_STATE_SKIP_TASKBAR = xatom("_NET_WM_STATE_SKIP_TASKBAR"), NET_WM_VISIBLE_ICON_NAME = xatom("_NET_WM_VISIBLE_ICON_NAME"), NET_WM_VISIBLE_NAME = xatom("_NET_WM_VISIBLE_NAME"), NET_WM_WINDOW_TYPE = xatom("_NET_WM_WINDOW_TYPE"), NET_WM_WINDOW_TYPE_DOCK = xatom("_NET_WM_WINDOW_TYPE_DOCK"), NET_WORKAREA = xatom("_NET_WORKAREA"), }; setprop(root, NET_SUPPORTED, XA_ATOM, 32, v, NELEM(v)); long geometry[2] = { DisplayWidth(dpy, scr), DisplayHeight(dpy, scr) }; setprop(root, NET_DESKTOP_GEOMETRY, XA_CARDINAL, 32, geometry, 2); setndesk(ewmh_getndesktops()); unsigned long n = 0; unsigned long *deskp = getprop(root, NET_CURRENT_DESKTOP, XA_CARDINAL, 32, &n); setcurrentdesktop(0); if (deskp != NULL) { if (n == 1) { gotodesk(*deskp & 0xffffffffUL); refocus(CurrentTime); } XFree(deskp); } Window none = None; setprop(root, NET_ACTIVE_WINDOW, XA_WINDOW, 32, &none, 1); // Finally create the WM_CHECK window to announce our EWMH support. wmcheckwin = XCreateWindow(dpy, root, 0, 0, 1, 1, 0, CopyFromParent, InputOnly, CopyFromParent, CWOverrideRedirect, &(XSetWindowAttributes){ .override_redirect = True }); setprop(wmcheckwin, NET_SUPPORTING_WM_CHECK, XA_WINDOW, 32, &wmcheckwin, 1); setprop(wmcheckwin, NET_WM_NAME, UTF8_STRING, 8, WM_OFFICIAL_NAME, strlen(WM_OFFICIAL_NAME)); setprop(root, NET_SUPPORTING_WM_CHECK, XA_WINDOW, 32, &wmcheckwin, 1); } void ewmh_stopwm(void) { XDestroyWindow(dpy, wmcheckwin); } static void reloadwindowname(struct client *c) { unsigned long n = 0; char *name = getprop(cgetwin(c), NET_WM_NAME, UTF8_STRING, 8, &n); csetnetwmname(c, name); if (name != NULL) XFree(name); } static void reloadwindowstate(struct client *c) { Window w = cgetwin(c); Bool skiptaskbar = False; Bool isfullscreen = False; unsigned long n = 0; Atom *states = getprop(w, NET_WM_STATE, XA_ATOM, 32, &n); for (int i = 0; i < n; i++) if (states[i] == NET_WM_STATE_SKIP_TASKBAR) skiptaskbar = True; else if (states[i] == NET_WM_STATE_FULLSCREEN) isfullscreen = True; else removestate(w, states[i]); if (states != NULL) XFree(states); csetskiptaskbar(c, skiptaskbar); csetfull(c, isfullscreen); } static void reloadwindowtype(struct client *c) { Bool isdock = False; unsigned long n = 0; Atom *types = getprop(cgetwin(c), NET_WM_WINDOW_TYPE, XA_ATOM, 32, &n); if (types != NULL) { for (unsigned long i = 0; i < n; i++) if (types[i] == NET_WM_WINDOW_TYPE_DOCK) isdock = True; XFree(types); } csetdock(c, isdock); } static void reloadwindowdesktop(struct client *c) { Window w = cgetwin(c); unsigned long n = 0; long *deskp = getprop(w, NET_WM_DESKTOP, XA_CARDINAL, 32, &n); if (deskp != NULL) { if (n == 1) csetdesk(c, *deskp & 0xffffffffUL); XFree(deskp); } else ewmh_notifyclientdesktop(w, cgetdesk(c)); } void ewmh_maprequest(struct client *c) { /* * The order of the following calls is optimized * for visual appearance. */ reloadwindowdesktop(c); reloadwindowstate(c); reloadwindowtype(c); } void ewmh_manage(struct client *c) { Window w = cgetwin(c); addclient(w); // Remove properties that other window managers may have set. XDeleteProperty(dpy, w, NET_WM_VISIBLE_NAME); XDeleteProperty(dpy, w, NET_WM_VISIBLE_ICON_NAME); Atom v[] = { NET_WM_ACTION_CHANGE_DESKTOP, NET_WM_ACTION_CLOSE, NET_WM_ACTION_FULLSCREEN, }; setprop(w, NET_WM_ALLOWED_ACTIONS, XA_ATOM, 32, v, NELEM(v)); /* * The order of the following calls is optimized * for visual appearance. */ reloadwindowdesktop(c); reloadwindowstate(c); reloadwindowname(c); reloadwindowtype(c); } void ewmh_unmanage(struct client *c) { Window w = cgetwin(c); ewmh_notifyfocus(w, None); delclient(w); XDeleteProperty(dpy, w, NET_WM_ALLOWED_ACTIONS); } void ewmh_withdraw(struct client *c) { Window w = cgetwin(c); ewmh_notifyfocus(w, None); delclient(w); /* * EWMH says we should remove _NET_WM_STATE when a window is * withdrawn, but not when we're shutting down (unmanage). */ XDeleteProperty(dpy, w, NET_WM_STATE); } /* * Notify change in focus. The focus change is only * accepted if 'old' matches the last recorded focus * window, or if it is None. * * The reason this function takes two arguments is to * avoid race conditions between FocusIn and FocusOut * events. * * A FocusIn handler should use None as 'old' and the * event window as 'new', while a FocusOut handler * should use the event window as 'old' and None as * 'new'. This way, it doesn't matter in which order * the events are reported. */ void ewmh_notifyfocus(Window old, Window new) { // The last recorded focus window static Window current = None; if (old == None || old == current) { setprop(root, NET_ACTIVE_WINDOW, XA_WINDOW, 32, &new, 1); current = new; } } void ewmh_notifyrestack(void) { Window *v; size_t n; getwindowstack(&v, &n); setprop(root, NET_CLIENT_LIST_STACKING, XA_WINDOW, 32, v, n); free(v); } void ewmh_propertynotify(struct client *c, XPropertyEvent *e) { if (e->atom == NET_WM_NAME) reloadwindowname(c); } static Bool hasstate(Window w, Atom state) { unsigned long n = 0; Atom *v = getprop(w, NET_WM_STATE, XA_ATOM, 32, &n); Bool rval = False; for (unsigned long i = 0; i < n; i++) if (v[i] == state) rval = True; if (v != NULL) XFree(v); return rval; } static void removestate(Window w, Atom state) { unsigned long n = 0; Atom *v = getprop(w, NET_WM_STATE, XA_ATOM, 32, &n); for (unsigned long i = 0; i < n; i++) if (v[i] == state) v[i] = v[--n]; setprop(w, NET_WM_STATE, XA_ATOM, 32, v, n); if (v != NULL) XFree(v); } static void addstate(Window w, Atom state) { unsigned long n = 0; Atom *old = getprop(w, NET_WM_STATE, XA_ATOM, 32, &n); Atom *new = xmalloc((n + 1) * sizeof (Atom)); for (unsigned long i = 0; i < n; i++) new[i] = old[i]; new[n] = state; setprop(w, NET_WM_STATE, XA_ATOM, 32, new, n + 1); free(new); if (old != NULL) XFree(old); } static void changestate(Window w, int how, Atom state) { switch (how) { case NET_WM_STATE_REMOVE: removestate(w, state); break; case NET_WM_STATE_ADD: addstate(w, state); break; case NET_WM_STATE_TOGGLE: if (hasstate(w, state)) removestate(w, state); else addstate(w, state); break; } } void ewmh_notifyfull(Window w, Bool full) { if (full) { if (!hasstate(w, NET_WM_STATE_FULLSCREEN)) addstate(w, NET_WM_STATE_FULLSCREEN); } else removestate(w, NET_WM_STATE_FULLSCREEN); } void ewmh_clientmessage(struct client *c, XClientMessageEvent *e) { if (e->message_type == NET_ACTIVE_WINDOW && e->format == 32) { cpopgrp(c); gotodesk(cgetdesk(c)); cfocus(c, (Time)e->data.l[1]); } else if (e->message_type == NET_CLOSE_WINDOW && e->format == 32) { cdelete(c, (Time)e->data.l[0]); } else if (e->message_type == NET_WM_DESKTOP && e->format == 32) { csetdesk(c, e->data.l[0] & 0xffffffff); } else if (e->message_type == NET_WM_STATE && e->format == 32) { int how = e->data.l[0]; for (int i = 1; i <= 2; i++) if (e->data.l[i] != 0) changestate(cgetwin(c), how, e->data.l[i]); reloadwindowstate(c); } } void ewmh_rootclientmessage(XClientMessageEvent *e) { if (e->message_type == NET_CURRENT_DESKTOP && e->format == 32) { gotodesk(e->data.l[0]); refocus(e->data.l[1]); } else if (e->message_type == NET_REQUEST_FRAME_EXTENTS) { struct extents ext = estimateframeextents(e->window); ewmh_notifyframeextents(e->window, ext); } else if (e->message_type == NET_NUMBER_OF_DESKTOPS && e->format == 32) { setndesk(e->data.l[0]); } }