tewmh.c - glazier - window management experiments
(DIR) Log
(DIR) Files
(DIR) Refs
(DIR) Submodules
(DIR) README
(DIR) LICENSE
---
tewmh.c (10775B)
---
1 #include <signal.h>
2 #include <stdio.h>
3 #include <stdlib.h>
4 #include <string.h>
5 #include <xcb/xcb.h>
6 #include <xcb/xcb_cursor.h>
7 #include <xcb/randr.h>
8
9 #include "arg.h"
10 #include "wm.h"
11
12 #define LEN(x) (sizeof(x)/sizeof(x[0]))
13
14 struct xatom {
15 char *name;
16 xcb_atom_t atom;
17 };
18
19 struct xgeom {
20 int x, y, w, h, b;
21 };
22
23 enum EWMH_TYPES {
24 IGNORE,
25 NORMAL,
26 POPUP,
27 };
28
29 enum {
30 _NET_SUPPORTED,
31 _NET_CLIENT_LIST,
32 _NET_CLIENT_LIST_STACKING,
33 _NET_SUPPORTING_WM_CHECK,
34 _NET_ACTIVE_WINDOW,
35 _NET_WM_STATE,
36 _NET_WM_STATE_FULLSCREEN,
37 _NET_WM_WINDOW_TYPE,
38 _NET_WM_WINDOW_TYPE_DESKTOP,
39 _NET_WM_WINDOW_TYPE_DOCK,
40 _NET_WM_WINDOW_TYPE_TOOLBAR,
41 _NET_WM_WINDOW_TYPE_MENU,
42 _NET_WM_WINDOW_TYPE_UTILITY,
43 _NET_WM_WINDOW_TYPE_SPLASH,
44 _NET_WM_WINDOW_TYPE_DIALOG,
45 _NET_WM_WINDOW_TYPE_DROPDOWN_MENU,
46 _NET_WM_WINDOW_TYPE_POPUP_MENU,
47 _NET_WM_WINDOW_TYPE_TOOLTIP,
48 _NET_WM_WINDOW_TYPE_NOTIFICATION,
49 _NET_WM_WINDOW_TYPE_COMBO,
50 _NET_WM_WINDOW_TYPE_DND,
51 _NET_WM_WINDOW_TYPE_NORMAL,
52 };
53
54 static void usage(const char *);
55 static int ewmh_init();
56 static int ewmh_wipe();
57 static int ewmh_supported();
58 static int ewmh_supportingwmcheck();
59 static int ewmh_activewindow(xcb_window_t);
60 static int ewmh_clientlist();
61 static int ewmh_type(xcb_window_t);
62 static int ewmh_message(xcb_client_message_event_t *);
63 static int ewmh_fullscreen(xcb_window_t, int);
64
65 xcb_connection_t *conn;
66 xcb_screen_t *scrn;
67 xcb_window_t ewmhwid; /* _NET_SUPPORTING_WM_CHECK target window */
68
69 struct xatom ewmh[] = {
70 [_NET_SUPPORTED] = { .name = "_NET_SUPPORTED" },
71 [_NET_CLIENT_LIST] = { .name = "_NET_CLIENT_LIST" },
72 [_NET_CLIENT_LIST_STACKING] = { .name = "_NET_CLIENT_LIST_STACKING" },
73 [_NET_SUPPORTING_WM_CHECK] = { .name = "_NET_SUPPORTING_WM_CHECK" },
74 [_NET_ACTIVE_WINDOW] = { .name = "_NET_ACTIVE_WINDOW" },
75 [_NET_WM_STATE] = { .name = "_NET_WM_STATE" },
76 [_NET_WM_STATE_FULLSCREEN] = { .name = "_NET_WM_STATE_FULLSCREEN" },
77 [_NET_WM_WINDOW_TYPE] = { .name = "_NET_WM_WINDOW_TYPE" },
78 [_NET_WM_WINDOW_TYPE_DESKTOP] = { .name = "_NET_WM_WINDOW_TYPE_DESKTOP" },
79 [_NET_WM_WINDOW_TYPE_DOCK] = { .name = "_NET_WM_WINDOW_TYPE_DOCK" },
80 [_NET_WM_WINDOW_TYPE_TOOLBAR] = { .name = "_NET_WM_WINDOW_TYPE_TOOLBAR" },
81 [_NET_WM_WINDOW_TYPE_MENU] = { .name = "_NET_WM_WINDOW_TYPE_MENU" },
82 [_NET_WM_WINDOW_TYPE_UTILITY] = { .name = "_NET_WM_WINDOW_TYPE_UTILITY" },
83 [_NET_WM_WINDOW_TYPE_SPLASH] = { .name = "_NET_WM_WINDOW_TYPE_SPLASH" },
84 [_NET_WM_WINDOW_TYPE_DIALOG] = { .name = "_NET_WM_WINDOW_TYPE_DIALOG" },
85 [_NET_WM_WINDOW_TYPE_DROPDOWN_MENU] = { .name = "_NET_WM_WINDOW_TYPE_DROPDOWN_MENU" },
86 [_NET_WM_WINDOW_TYPE_POPUP_MENU] = { .name = "_NET_WM_WINDOW_TYPE_POPUP_MENU" },
87 [_NET_WM_WINDOW_TYPE_TOOLTIP] = { .name = "_NET_WM_WINDOW_TYPE_TOOLTIP" },
88 [_NET_WM_WINDOW_TYPE_NOTIFICATION] = { .name = "_NET_WM_WINDOW_TYPE_NOTIFICATION" },
89 [_NET_WM_WINDOW_TYPE_COMBO] = { .name = "_NET_WM_WINDOW_TYPE_COMBO" },
90 [_NET_WM_WINDOW_TYPE_DND] = { .name = "_NET_WM_WINDOW_TYPE_DND" },
91 [_NET_WM_WINDOW_TYPE_NORMAL] = { .name = "_NET_WM_WINDOW_TYPE_NORMAL" },
92 };
93
94 void
95 usage(const char *name)
96 {
97 fprintf(stderr, "usage: %s [-h]\n", name);
98 }
99
100 void
101 cleanup()
102 {
103 printf("cleaning up\n");
104 ewmh_wipe();
105 wm_kill_xcb();
106 }
107
108 int
109 ewmh_init()
110 {
111 uint32_t i, n;
112 xcb_window_t *w;
113
114 for (i = 0; i < LEN(ewmh); i++)
115 ewmh[i].atom = wm_add_atom(ewmh[i].name, strlen(ewmh[i].name));
116
117 /* monitor focus events on existing windows */
118 n = wm_get_windows(scrn->root, &w);
119 for (i = 0; i < n; i++)
120 wm_reg_window_event(w[i], XCB_EVENT_MASK_FOCUS_CHANGE);
121
122 ewmh_supported();
123 ewmh_supportingwmcheck();
124 ewmh_clientlist();
125
126 return 0;
127 }
128
129 int
130 ewmh_wipe()
131 {
132 xcb_delete_property(conn, scrn->root, ewmh[_NET_SUPPORTED].atom);
133 xcb_delete_property(conn, scrn->root, ewmh[_NET_CLIENT_LIST].atom);
134 xcb_delete_property(conn, scrn->root, ewmh[_NET_CLIENT_LIST_STACKING].atom);
135 xcb_delete_property(conn, scrn->root, ewmh[_NET_ACTIVE_WINDOW].atom);
136 xcb_delete_property(conn, scrn->root, ewmh[_NET_SUPPORTING_WM_CHECK].atom);
137 xcb_destroy_window(conn, ewmhwid);
138
139 xcb_flush(conn);
140
141 return 0;
142 }
143
144 int
145 ewmh_supported()
146 {
147 uint32_t i;
148 xcb_atom_t supported[LEN(ewmh)];
149
150 for (i = 0; i < LEN(ewmh); i++)
151 supported[i] = ewmh[i].atom;
152
153 return wm_set_atom(scrn->root, ewmh[_NET_SUPPORTED].atom, XCB_ATOM_ATOM, i, &supported);
154 }
155
156 int
157 ewmh_supportingwmcheck()
158 {
159 int val = 1;
160
161 ewmhwid = xcb_generate_id(conn);
162
163 /* dummyest window ever. */
164 xcb_create_window(conn,
165 XCB_COPY_FROM_PARENT, ewmhwid, scrn->root,
166 0, 0, 1, 1, 0, /* x, y, w, h, border */
167 XCB_WINDOW_CLASS_INPUT_ONLY, /* no need for output */
168 scrn->root_visual, /* visual */
169 XCB_CW_OVERRIDE_REDIRECT, &val); /* have the WM ignore us */
170
171 wm_set_atom(scrn->root, ewmh[_NET_SUPPORTING_WM_CHECK].atom, XCB_ATOM_WINDOW, 1, &ewmhwid);
172 wm_set_atom(ewmhwid, ewmh[_NET_SUPPORTING_WM_CHECK].atom, XCB_ATOM_WINDOW, 1, &ewmhwid);
173
174 return 0;
175 }
176
177 int
178 ewmh_activewindow(xcb_window_t wid)
179 {
180 wm_set_atom(scrn->root, ewmh[_NET_ACTIVE_WINDOW].atom, XCB_ATOM_WINDOW, 1, &wid);
181 return 0;
182 }
183
184 int
185 ewmh_clientlist()
186 {
187 uint32_t i, c, n;
188 xcb_window_t *w, *l;
189
190 n = wm_get_windows(scrn->root, &w);
191
192 l = calloc(n, sizeof(*w));
193
194 for (i=0, c=0; i<n; i++) {
195 if (ewmh_type(w[i]) != NORMAL)
196 xcb_change_window_attributes(conn, w[i], XCB_CW_OVERRIDE_REDIRECT, &(int){1});
197
198 if (wm_is_listable(w[i], 0))
199 l[c++] = w[i];
200 }
201
202 free(w);
203
204 wm_set_atom(scrn->root, ewmh[_NET_CLIENT_LIST].atom, XCB_ATOM_WINDOW, c, l);
205 wm_set_atom(scrn->root, ewmh[_NET_CLIENT_LIST_STACKING].atom, XCB_ATOM_WINDOW, c, l);
206
207 free(l);
208
209 return 0;
210 }
211
212 int
213 ewmh_type(xcb_window_t window)
214 {
215 unsigned long n;
216 int type = NORMAL; /* treat non-ewmh as normal windows */
217 xcb_atom_t *atoms;
218
219 atoms = wm_get_atom(window, ewmh[_NET_WM_WINDOW_TYPE].atom, XCB_ATOM_ATOM, &n);
220
221 if (!atoms)
222 return NORMAL;
223
224 /*
225 * as per the EWMH spec, when multiple types are
226 * applicable, they must be listed from the most to least
227 * important.
228 * To do so, we cycle through them in reverse order, changing
229 * the window type everytime a known type is encountered.
230 * Some toolkits like to use toolkit-specific atoms as their
231 * first value for more appropriate categorization. This function
232 * only deals with standard EWMH atoms.
233 */
234 while (n --> 0) {
235 if (atoms[n] == ewmh[_NET_WM_WINDOW_TYPE_DESKTOP].atom
236 || atoms[n] == ewmh[_NET_WM_WINDOW_TYPE_DOCK].atom
237 || atoms[n] == ewmh[_NET_WM_WINDOW_TYPE_TOOLBAR].atom
238 || atoms[n] == ewmh[_NET_WM_WINDOW_TYPE_POPUP_MENU].atom
239 || atoms[n] == ewmh[_NET_WM_WINDOW_TYPE_COMBO].atom
240 || atoms[n] == ewmh[_NET_WM_WINDOW_TYPE_DND].atom)
241 type = IGNORE;
242
243 if (atoms[n] == ewmh[_NET_WM_WINDOW_TYPE_MENU].atom
244 || atoms[n] == ewmh[_NET_WM_WINDOW_TYPE_SPLASH].atom
245 || atoms[n] == ewmh[_NET_WM_WINDOW_TYPE_DROPDOWN_MENU].atom
246 || atoms[n] == ewmh[_NET_WM_WINDOW_TYPE_TOOLTIP].atom
247 || atoms[n] == ewmh[_NET_WM_WINDOW_TYPE_NOTIFICATION].atom)
248 type = POPUP;
249
250 if (atoms[n] == ewmh[_NET_WM_WINDOW_TYPE_DIALOG].atom
251 || atoms[n] == ewmh[_NET_WM_WINDOW_TYPE_UTILITY].atom
252 || atoms[n] == ewmh[_NET_WM_WINDOW_TYPE_NORMAL].atom)
253 type = NORMAL;
254 }
255
256 return type;
257 }
258
259 int
260 ewmh_message(xcb_client_message_event_t *ev)
261 {
262 /* ignore all other messages */
263 if (ev->type != ewmh[_NET_WM_STATE].atom)
264 return -1;
265
266 if (ev->data.data32[1] == ewmh[_NET_WM_STATE_FULLSCREEN].atom
267 || ev->data.data32[2] == ewmh[_NET_WM_STATE_FULLSCREEN].atom) {
268 ewmh_fullscreen(ev->window, ev->data.data32[0]);
269 return 0;
270 }
271
272 return 1;
273 }
274
275 int
276 ewmh_fullscreen(xcb_window_t wid, int state)
277 {
278 size_t n;
279 int isfullscreen;
280 xcb_atom_t *atom, original_size;
281 xcb_randr_monitor_info_t *m;
282 struct xgeom g, *origin;
283
284 atom = wm_get_atom(wid, ewmh[_NET_WM_STATE].atom, XCB_ATOM_ATOM, &n);
285 original_size = wm_add_atom("ORIGINAL_SIZE", strlen("ORIGINAL_SIZE"));
286
287 isfullscreen = (atom && *atom == ewmh[_NET_WM_STATE_FULLSCREEN].atom);
288
289 switch (state) {
290 case -1:
291 return isfullscreen;
292 break; /* NOTREACHED */
293
294 case 0: /* _NET_WM_STATE_REMOVE */
295 wm_set_atom(wid, ewmh[_NET_WM_STATE].atom, XCB_ATOM_ATOM, 0, NULL);
296 origin = wm_get_atom(wid, original_size, XCB_ATOM_CARDINAL, &n);
297 if (!origin || n < 5)
298 return -1;
299
300 wm_set_border(origin->b, -1, wid);
301 wm_teleport(wid, origin->x, origin->y, origin->w, origin->h);
302 xcb_delete_property(conn, wid, original_size);
303 break;
304
305 case 1: /* _NET_WM_STATE_ADD */
306 /* save current window geometry */
307 g.x = wm_get_attribute(wid, ATTR_X);
308 g.y = wm_get_attribute(wid, ATTR_Y);
309 g.w = wm_get_attribute(wid, ATTR_W);
310 g.h = wm_get_attribute(wid, ATTR_H);
311 g.b = wm_get_attribute(wid, ATTR_B);
312 wm_set_atom(wid, original_size, XCB_ATOM_CARDINAL, 5, &g);
313
314 m = wm_get_monitor(wm_find_monitor(g.x, g.y));
315 if (!m)
316 return -1;
317
318 /* move window fullscreen */
319 wm_set_border(0, -1, wid);
320 wm_teleport(wid, m->x, m->y, m->width, m->height);
321 wm_restack(wid, XCB_STACK_MODE_ABOVE);
322 wm_set_atom(wid, ewmh[_NET_WM_STATE].atom, XCB_ATOM_ATOM, 1, &ewmh[_NET_WM_STATE_FULLSCREEN].atom);
323 free(m);
324 break;
325
326 case 2: /* _NET_WM_STATE_TOGGLE */
327 printf("0x%08x !fullscreen\n", wid);
328 ewmh_fullscreen(wid, !isfullscreen);
329 break;
330 }
331
332 return 0;
333 }
334
335 int
336 main (int argc, char *argv[])
337 {
338 int mask;
339 char *argv0;
340 xcb_generic_event_t *ev = NULL;
341
342 ARGBEGIN {
343 case 'h':
344 usage(argv0);
345 return 0;
346 break; /* NOTREACHED */
347 default:
348 usage(argv0);
349 return -1;
350 break; /* NOTREACHED */
351 } ARGEND;
352
353 wm_init_xcb();
354 wm_get_screen();
355 ewmh_init();
356
357 signal(SIGINT, cleanup);
358 signal(SIGTERM, cleanup);
359
360 /* needed to get notified of windows creation */
361 mask = XCB_EVENT_MASK_STRUCTURE_NOTIFY | XCB_EVENT_MASK_SUBSTRUCTURE_NOTIFY;
362
363 if (!wm_reg_window_event(scrn->root, mask))
364 return -1;
365
366 for (;;) {
367 xcb_flush(conn);
368 ev = xcb_wait_for_event(conn);
369 if (!ev)
370 break;
371
372 switch (ev->response_type & ~0x80) {
373
374 case XCB_CREATE_NOTIFY:
375 wm_reg_window_event(((xcb_create_notify_event_t *)ev)->window, XCB_EVENT_MASK_FOCUS_CHANGE);
376 /* FALLTHROUGH */
377 case XCB_DESTROY_NOTIFY:
378 ewmh_clientlist();
379 break;
380
381 case XCB_CLIENT_MESSAGE:
382 ewmh_message((xcb_client_message_event_t *)ev);
383 break;
384
385 case XCB_FOCUS_IN:
386 ewmh_activewindow(((xcb_focus_in_event_t *)ev)->event);
387 break;
388 case XCB_MAP_NOTIFY:
389 if (ewmh_type(((xcb_map_notify_event_t *)ev)->window) == POPUP)
390 wm_restack(((xcb_map_notify_event_t *)ev)->window, XCB_STACK_MODE_ABOVE);
391 }
392 free(ev);
393 }
394
395 return -1;
396