1/* swc: libswc/xwm.c
2 *
3 * Copyright (c) 2013, 2014 Michael Forney
4 *
5 * Permission is hereby granted, free of charge, to any person obtaining a copy
6 * of this software and associated documentation files (the "Software"), to deal
7 * in the Software without restriction, including without limitation the rights
8 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 * copies of the Software, and to permit persons to whom the Software is
10 * furnished to do so, subject to the following conditions:
11 *
12 * The above copyright notice and this permission notice shall be included in
13 * all copies or substantial portions of the Software.
14 *
15 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 * SOFTWARE.
22 */
23
24#include "xwm.h"
25#include "compositor.h"
26#include "internal.h"
27#include "surface.h"
28#include "swc.h"
29#include "util.h"
30#include "view.h"
31#include "window.h"
32#include "xserver.h"
33
34#include <stdio.h>
35#include <xcb/composite.h>
36#include <xcb/xcb_ewmh.h>
37#include <xcb/xcb_icccm.h>
38
39struct xwl_window {
40 xcb_window_t id;
41 uint32_t surface_id;
42 bool override_redirect, supports_delete;
43 struct wl_list link;
44
45 /* Only used for paired windows. */
46 struct {
47 struct window window;
48 struct wl_listener surface_destroy_listener;
49 };
50};
51
52enum atom {
53 ATOM_WL_SURFACE_ID,
54 ATOM_WM_DELETE_WINDOW,
55 ATOM_WM_PROTOCOLS,
56 ATOM_WM_S0,
57};
58
59static struct {
60 xcb_connection_t *connection;
61 xcb_ewmh_connection_t ewmh;
62 xcb_screen_t *screen;
63 xcb_window_t window;
64 struct xwl_window *focus;
65 struct wl_event_source *source;
66 struct wl_list windows, unpaired_windows;
67 union {
68 const char *name;
69 xcb_intern_atom_cookie_t cookie;
70 xcb_atom_t value;
71 } atoms[4];
72} xwm = {.atoms = {
73 [ATOM_WL_SURFACE_ID] = {"WL_SURFACE_ID"},
74 [ATOM_WM_DELETE_WINDOW] = {"WM_DELETE_WINDOW"},
75 [ATOM_WM_PROTOCOLS] = {"WM_PROTOCOLS"},
76 [ATOM_WM_S0] = {"WM_S0"},
77 }};
78
79static void
80update_name(struct xwl_window *xwl_window)
81{
82 xcb_get_property_cookie_t wm_name_cookie;
83 xcb_ewmh_get_utf8_strings_reply_t wm_name_reply;
84
85 wm_name_cookie = xcb_ewmh_get_wm_name(&xwm.ewmh, xwl_window->id);
86
87 if (xcb_ewmh_get_wm_name_reply(&xwm.ewmh, wm_name_cookie, &wm_name_reply,
88 NULL)) {
89 window_set_title(&xwl_window->window, wm_name_reply.strings,
90 wm_name_reply.strings_len);
91 xcb_ewmh_get_utf8_strings_reply_wipe(&wm_name_reply);
92 } else {
93 window_set_title(&xwl_window->window, NULL, 0);
94 }
95}
96
97static void
98update_protocols(struct xwl_window *xwl_window)
99{
100 xcb_get_property_cookie_t cookie;
101 xcb_icccm_get_wm_protocols_reply_t reply;
102 unsigned index;
103
104 cookie = xcb_icccm_get_wm_protocols(xwm.connection, xwl_window->id,
105 xwm.atoms[ATOM_WM_PROTOCOLS].value);
106 xwl_window->supports_delete = true;
107
108 if (!xcb_icccm_get_wm_protocols_reply(xwm.connection, cookie, &reply,
109 NULL)) {
110 return;
111 }
112
113 for (index = 0; index < reply.atoms_len; ++index) {
114 if (reply.atoms[index] == xwm.atoms[ATOM_WM_DELETE_WINDOW].value) {
115 xwl_window->supports_delete = true;
116 }
117 }
118
119 xcb_icccm_get_wm_protocols_reply_wipe(&reply);
120}
121
122static struct xwl_window *
123find_window(struct wl_list *list, xcb_window_t id)
124{
125 struct xwl_window *window;
126
127 wl_list_for_each(window, list, link)
128 {
129 if (window->id == id) {
130 return window;
131 }
132 }
133
134 return NULL;
135}
136
137static struct xwl_window *
138find_window_by_surface_id(struct wl_list *list, uint32_t id)
139{
140 struct xwl_window *window;
141
142 wl_list_for_each(window, list, link)
143 {
144 if (window->surface_id == id) {
145 return window;
146 }
147 }
148
149 return NULL;
150}
151
152static void
153move(struct window *window, int32_t x, int32_t y)
154{
155 uint32_t mask, values[2];
156 struct xwl_window *xwl_window = wl_container_of(window, xwl_window, window);
157
158 mask = XCB_CONFIG_WINDOW_X | XCB_CONFIG_WINDOW_Y;
159 values[0] = x;
160 values[1] = y;
161
162 xcb_configure_window(xwm.connection, xwl_window->id, mask, values);
163 xcb_flush(xwm.connection);
164}
165
166static void
167configure(struct window *window, uint32_t width, uint32_t height)
168{
169 uint32_t mask, values[2];
170 struct xwl_window *xwl_window = wl_container_of(window, xwl_window, window);
171
172 mask = XCB_CONFIG_WINDOW_WIDTH | XCB_CONFIG_WINDOW_HEIGHT;
173 values[0] = width;
174 values[1] = height;
175
176 window->configure.acknowledged = true;
177 xcb_configure_window(xwm.connection, xwl_window->id, mask, values);
178 xcb_flush(xwm.connection);
179}
180
181static void
182focus(struct window *window)
183{
184 struct xwl_window *xwl_window = wl_container_of(window, xwl_window, window);
185
186 xcb_set_input_focus(xwm.connection, XCB_INPUT_FOCUS_NONE, xwl_window->id,
187 XCB_CURRENT_TIME);
188 xcb_flush(xwm.connection);
189 xwm.focus = xwl_window;
190}
191
192static void
193unfocus(struct window *window)
194{
195 struct xwl_window *xwl_window = wl_container_of(window, xwl_window, window);
196
197 /* If the window we are unfocusing is the latest xwl_window to be focused,
198 * we know we have transitioned to some other window type, so the X11 focus
199 * can be set to XCB_NONE. Otherwise, we have transitioned to another X11
200 * window, and the X11 focus has already been updated. */
201 if (xwl_window == xwm.focus) {
202 xcb_set_input_focus(xwm.connection, XCB_INPUT_FOCUS_NONE, XCB_NONE,
203 XCB_CURRENT_TIME);
204 xcb_flush(xwm.connection);
205 }
206}
207
208static void
209close_(struct window *window)
210{
211 struct xwl_window *xwl_window = wl_container_of(window, xwl_window, window);
212
213 if (xwl_window->supports_delete) {
214 xcb_client_message_event_t event = {
215 .response_type = XCB_CLIENT_MESSAGE,
216 .format = 32,
217 .window = xwl_window->id,
218 .type = xwm.atoms[ATOM_WM_PROTOCOLS].value,
219 .data.data32 =
220 {
221 xwm.atoms[ATOM_WM_DELETE_WINDOW].value,
222 XCB_CURRENT_TIME,
223 },
224 };
225
226 xcb_send_event(xwm.connection, false, xwl_window->id,
227 XCB_EVENT_MASK_NO_EVENT, (const char *)&event);
228 } else {
229 xcb_kill_client(xwm.connection, xwl_window->id);
230 }
231
232 xcb_flush(xwm.connection);
233}
234
235static const struct window_impl xwl_window_handler = {
236 .move = move,
237 .configure = configure,
238 .focus = focus,
239 .unfocus = unfocus,
240 .close = close_,
241};
242
243static void
244handle_surface_destroy(struct wl_listener *listener, void *data)
245{
246 struct xwl_window *xwl_window =
247 wl_container_of(listener, xwl_window, surface_destroy_listener);
248
249 if (xwm.focus == xwl_window) {
250 xwm.focus = NULL;
251 }
252
253 window_finalize(&xwl_window->window);
254 wl_list_remove(&xwl_window->link);
255 wl_list_insert(&xwm.unpaired_windows, &xwl_window->link);
256 xwl_window->surface_id = 0;
257}
258
259static bool
260manage_window(struct xwl_window *xwl_window)
261{
262 struct wl_resource *resource;
263 struct surface *surface;
264 xcb_get_geometry_cookie_t geometry_cookie;
265 xcb_get_geometry_reply_t *geometry_reply;
266
267 resource =
268 wl_client_get_object(swc.xserver->client, xwl_window->surface_id);
269
270 if (!resource) {
271 return false;
272 }
273
274 surface = wl_resource_get_user_data(resource);
275 geometry_cookie = xcb_get_geometry(xwm.connection, xwl_window->id);
276
277 window_initialize(&xwl_window->window, &xwl_window_handler, surface);
278 xwl_window->surface_destroy_listener.notify = &handle_surface_destroy;
279 wl_resource_add_destroy_listener(surface->resource,
280 &xwl_window->surface_destroy_listener);
281
282 if ((geometry_reply =
283 xcb_get_geometry_reply(xwm.connection, geometry_cookie, NULL))) {
284 view_move(surface->view, geometry_reply->x, geometry_reply->y);
285 free(geometry_reply);
286 }
287
288 if (xwl_window->override_redirect) {
289 compositor_view_show(xwl_window->window.view);
290 } else {
291 uint32_t mask, values[1];
292
293 mask = XCB_CW_EVENT_MASK;
294 values[0] = XCB_EVENT_MASK_PROPERTY_CHANGE;
295 xcb_change_window_attributes(xwm.connection, xwl_window->id, mask,
296 values);
297 mask = XCB_CONFIG_WINDOW_BORDER_WIDTH;
298 values[0] = 0;
299 xcb_configure_window(xwm.connection, xwl_window->id, mask, values);
300 update_name(xwl_window);
301 update_protocols(xwl_window);
302 window_manage(&xwl_window->window);
303 }
304
305 wl_list_remove(&xwl_window->link);
306 wl_list_insert(&xwm.windows, &xwl_window->link);
307
308 return true;
309}
310
311static void
312handle_new_surface(struct wl_listener *listener, void *data)
313{
314 struct surface *surface = data;
315 struct xwl_window *window;
316
317 window = find_window_by_surface_id(&xwm.unpaired_windows,
318 wl_resource_get_id(surface->resource));
319
320 if (!window) {
321 return;
322 }
323
324 manage_window(window);
325}
326
327static struct wl_listener new_surface_listener = {.notify =
328 &handle_new_surface};
329
330/* X event handlers */
331static void
332create_notify(xcb_create_notify_event_t *event)
333{
334 struct xwl_window *xwl_window;
335
336 if (!(xwl_window = malloc(sizeof *xwl_window))) {
337 return;
338 }
339
340 xwl_window->id = event->window;
341 xwl_window->surface_id = 0;
342 xwl_window->override_redirect = event->override_redirect;
343 wl_list_insert(&xwm.unpaired_windows, &xwl_window->link);
344}
345
346static void
347destroy_notify(xcb_destroy_notify_event_t *event)
348{
349 struct xwl_window *xwl_window;
350
351 if ((xwl_window = find_window(&xwm.windows, event->window))) {
352 wl_list_remove(&xwl_window->surface_destroy_listener.link);
353 window_finalize(&xwl_window->window);
354 } else if (!(xwl_window =
355 find_window(&xwm.unpaired_windows, event->window))) {
356 return;
357 }
358
359 wl_list_remove(&xwl_window->link);
360 free(xwl_window);
361}
362
363static void
364map_request(xcb_map_request_event_t *event)
365{
366 xcb_map_window(xwm.connection, event->window);
367}
368
369static void
370configure_request(xcb_configure_request_event_t *event)
371{
372}
373
374static void
375property_notify(xcb_property_notify_event_t *event)
376{
377 struct xwl_window *xwl_window;
378
379 if (!(xwl_window = find_window(&xwm.windows, event->window))) {
380 return;
381 }
382
383 if (event->atom == xwm.ewmh._NET_WM_NAME &&
384 event->state == XCB_PROPERTY_NEW_VALUE) {
385 update_name(xwl_window);
386 } else if (event->atom == xwm.atoms[ATOM_WM_PROTOCOLS].value) {
387 update_protocols(xwl_window);
388 }
389}
390
391static void
392client_message(xcb_client_message_event_t *event)
393{
394 if (event->type == xwm.atoms[ATOM_WL_SURFACE_ID].value) {
395 struct xwl_window *xwl_window;
396
397 if (!(xwl_window = find_window(&xwm.unpaired_windows, event->window))) {
398 return;
399 }
400
401 xwl_window->surface_id = event->data.data32[0];
402 manage_window(xwl_window);
403 }
404}
405
406static int
407connection_data(int fd, uint32_t mask, void *data)
408{
409 xcb_generic_event_t *event;
410 uint32_t count = 0;
411
412 while ((event = xcb_poll_for_event(xwm.connection))) {
413 switch (event->response_type & ~0x80) {
414 case XCB_CREATE_NOTIFY:
415 create_notify((xcb_create_notify_event_t *)event);
416 break;
417 case XCB_DESTROY_NOTIFY:
418 destroy_notify((xcb_destroy_notify_event_t *)event);
419 break;
420 case XCB_MAP_REQUEST:
421 map_request((xcb_map_request_event_t *)event);
422 break;
423 case XCB_CONFIGURE_REQUEST:
424 configure_request((xcb_configure_request_event_t *)event);
425 break;
426 case XCB_PROPERTY_NOTIFY:
427 property_notify((xcb_property_notify_event_t *)event);
428 break;
429 case XCB_CLIENT_MESSAGE:
430 client_message((xcb_client_message_event_t *)event);
431 break;
432 }
433
434 free(event);
435 ++count;
436 }
437
438 xcb_flush(xwm.connection);
439
440 return count;
441}
442
443bool
444xwm_initialize(int fd)
445{
446 const xcb_setup_t *setup;
447 xcb_screen_iterator_t screen_iterator;
448 uint32_t mask;
449 uint32_t values[1];
450 xcb_void_cookie_t change_attributes_cookie, redirect_subwindows_cookie;
451 xcb_generic_error_t *error;
452 xcb_intern_atom_cookie_t *ewmh_cookies;
453 xcb_intern_atom_reply_t *atom_reply;
454 unsigned index;
455 const char *name;
456 const xcb_query_extension_reply_t *composite_extension;
457
458 xwm.connection = xcb_connect_to_fd(fd, NULL);
459
460 if (xcb_connection_has_error(xwm.connection)) {
461 ERROR("xwm: Could not connect to X server\n");
462 goto error0;
463 }
464
465 xcb_prefetch_extension_data(xwm.connection, &xcb_composite_id);
466 ewmh_cookies = xcb_ewmh_init_atoms(xwm.connection, &xwm.ewmh);
467
468 if (!ewmh_cookies) {
469 ERROR("xwm: Failed to initialize EWMH atoms\n");
470 goto error1;
471 }
472
473 for (index = 0; index < ARRAY_LENGTH(xwm.atoms); ++index) {
474 name = xwm.atoms[index].name;
475 xwm.atoms[index].cookie =
476 xcb_intern_atom(xwm.connection, 0, strlen(name), name);
477 }
478
479 setup = xcb_get_setup(xwm.connection);
480 screen_iterator = xcb_setup_roots_iterator(setup);
481 xwm.screen = screen_iterator.data;
482
483 /* Try to select for substructure redirect. */
484 mask = XCB_CW_EVENT_MASK;
485 values[0] = XCB_EVENT_MASK_SUBSTRUCTURE_NOTIFY |
486 XCB_EVENT_MASK_SUBSTRUCTURE_REDIRECT;
487 change_attributes_cookie = xcb_change_window_attributes(
488 xwm.connection, xwm.screen->root, mask, values);
489
490 xwm.source = wl_event_loop_add_fd(swc.event_loop, fd, WL_EVENT_READABLE,
491 &connection_data, NULL);
492 wl_list_init(&xwm.windows);
493 wl_list_init(&xwm.unpaired_windows);
494
495 if (!xwm.source) {
496 ERROR("xwm: Failed to create X connection event source\n");
497 goto error2;
498 }
499
500 composite_extension =
501 xcb_get_extension_data(xwm.connection, &xcb_composite_id);
502
503 if (!composite_extension->present) {
504 ERROR("xwm: X server does not have composite extension\n");
505 goto error3;
506 }
507
508 redirect_subwindows_cookie = xcb_composite_redirect_subwindows_checked(
509 xwm.connection, xwm.screen->root, XCB_COMPOSITE_REDIRECT_MANUAL);
510
511 if ((error = xcb_request_check(xwm.connection, change_attributes_cookie))) {
512 ERROR("xwm: Another window manager is running\n");
513 free(error);
514 goto error3;
515 }
516
517 if ((error =
518 xcb_request_check(xwm.connection, redirect_subwindows_cookie))) {
519 ERROR("xwm: Could not redirect subwindows of root for compositing\n");
520 free(error);
521 goto error3;
522 }
523
524 xwm.window = xcb_generate_id(xwm.connection);
525 xcb_create_window(xwm.connection, 0, xwm.window, xwm.screen->root, 0, 0, 1,
526 1, 0, XCB_WINDOW_CLASS_INPUT_ONLY, XCB_COPY_FROM_PARENT,
527 0, NULL);
528
529 xcb_ewmh_init_atoms_replies(&xwm.ewmh, ewmh_cookies, &error);
530
531 if (error) {
532 ERROR("xwm: Failed to get EWMH atom replies: %u\n", error->error_code);
533 goto error3;
534 }
535
536 for (index = 0; index < ARRAY_LENGTH(xwm.atoms); ++index) {
537 atom_reply = xcb_intern_atom_reply(xwm.connection,
538 xwm.atoms[index].cookie, &error);
539
540 if (error) {
541 ERROR("xwm: Failed to get atom reply: %u\n", error->error_code);
542 return false;
543 }
544
545 xwm.atoms[index].value = atom_reply->atom;
546 free(atom_reply);
547 }
548
549 xcb_set_selection_owner(xwm.connection, xwm.window,
550 xwm.atoms[ATOM_WM_S0].value, XCB_CURRENT_TIME);
551 xcb_flush(xwm.connection);
552
553 wl_signal_add(&swc.compositor->signal.new_surface, &new_surface_listener);
554
555 return true;
556
557error3:
558 wl_event_source_remove(xwm.source);
559error2:
560 xcb_ewmh_connection_wipe(&xwm.ewmh);
561error1:
562 xcb_disconnect(xwm.connection);
563error0:
564 return false;
565}
566
567void
568xwm_finalize(void)
569{
570 wl_event_source_remove(xwm.source);
571 xcb_ewmh_connection_wipe(&xwm.ewmh);
572 xcb_disconnect(xwm.connection);
573}