main neuswc / libswc / xwm.c
  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}