1 /*
2  * Copyright 2018-2020, Björn Ståhl
3  * License: 3-Clause BSD, see COPYING file in arcan source repository.
4  * Reference: https://github.com/letoram/arcan/wiki/wayland.md
5  * Description: XWayland specific 'Window Manager' that deals with the special
6  * considerations needed for pairing XWayland redirected windows with wayland
7  * surfaces etc. Decoupled from the normal XWayland so that both sides can be
8  * sandboxed better and possibly used for a similar -rootless mode in Xarcan.
9  */
10 #include "../../shmif/arcan_shmif.h"
11 #include "../../shmif/arcan_shmif_debugif.h"
12 #include <inttypes.h>
13 #include <errno.h>
14 #include <signal.h>
15 #include <stdarg.h>
16 #include <fcntl.h>
17 #include <sys/socket.h>
18 #include <sys/wait.h>
19 #include <sys/types.h>
20 #include <poll.h>
21 
22 /* #include <X11/XCursor/XCursor.h> */
23 #include <xcb/xcb.h>
24 #include <xcb/composite.h>
25 #include <xcb/xfixes.h>
26 #include <xcb/xcb_event.h>
27 #include <xcb/xcb_icccm.h>
28 #include <pthread.h>
29 
30 #include "uthash.h"
31 
32 static pthread_mutex_t logout_synch = PTHREAD_MUTEX_INITIALIZER;
33 static pthread_mutex_t wm_synch = PTHREAD_MUTEX_INITIALIZER;
34 static pid_t exec_child = -1;
35 
36 /* retries before ICCCM destroy requests are ignored and we just kill
37  * the client outright */
38 static int default_kill_count = 5;
39 struct timed_request {
40 	uint64_t ts_ms;
41 	uint64_t serial;
42 	int w, h;
43 	int x, y;
44 };
45 
46 struct xwnd_state {
47 	uint64_t mapped;
48 	uint64_t managed;
49 
50 	struct timed_request cl_configure;
51 	struct timed_request wm_configure;
52 	int pending_mask;
53 	int pending_sibling;
54 	int pending_stack;
55 
56 	bool paired;
57 	bool configured;
58 	bool override_redirect;
59 	bool fullscreen;
60 	int x, y;
61 	int w, h;
62 	int kill_count;
63 	int id;
64 	char* title;
65 	UT_hash_handle hh;
66 };
67 static struct xwnd_state* windows;
68 
69 struct selection {
70 	uint8_t* buf;
71 	size_t buf_sz;
72 	const char* buf_type;
73 };
74 
75 static int signal_fd = -1;
76 static xcb_connection_t* dpy;
77 static xcb_screen_t* screen;
78 
79 static xcb_drawable_t wnd_root;
80 static xcb_drawable_t wnd_wm;
81 static xcb_drawable_t wnd_sel;
82 
83 static xcb_colormap_t colormap;
84 static xcb_visualid_t visual;
85 struct selection selection_primary;
86 struct selection selection_secondary;
87 
88 static int64_t input_grab = XCB_WINDOW_NONE;
89 static int64_t input_focus = XCB_WINDOW_NONE;
90 static bool xwm_standalone = false;
91 static int clipboard_fd = -1;
92 
93 #include "atoms.h"
94 
95 #define WM_FLUSH true
96 #define WM_APPEND false
97 
on_chld(int num)98 static void on_chld(int num)
99 {
100 	uint8_t ch = 'x';
101 	write(signal_fd, &ch, 1);
102 }
103 
on_dbgreq(int num)104 static void on_dbgreq(int num)
105 {
106 	uint8_t ch = 'd';
107 	write(signal_fd, &ch, 1);
108 }
109 
trace(const char * msg,...)110 static inline void trace(const char* msg, ...)
111 {
112 	FILE* dst = stdout;
113 
114 	va_list args;
115 	va_start( args, msg );
116 		vfprintf(dst,  msg, args );
117 		fputc((int) '\n', dst);
118 	va_end( args);
119 }
120 
121 #ifdef _DEBUG
122 #define TRACE_PREFIX "kind=trace:"
123 #define trace(Y, ...) do { \
124 	pthread_mutex_lock(&logout_synch); \
125 	trace("%sts=%lld:" Y, TRACE_PREFIX, arcan_timemillis(), ##__VA_ARGS__);\
126 	pthread_mutex_unlock(&logout_synch); \
127 } while (0)
128 #else
129 #define TRACE_PREFIX ""
130 #define trace(Y, ...) do { } while (0)
131 #endif
132 
is_wm_window(xcb_drawable_t id)133 static bool is_wm_window(xcb_drawable_t id)
134 {
135 	return id == wnd_wm || id == wnd_root;
136 }
137 
wm_command(bool flush,const char * msg,...)138 static inline void wm_command(bool flush, const char* msg, ...)
139 {
140 	va_list args;
141 	va_start(args, msg);
142 	static bool in_lock;
143 
144 	if (!in_lock){
145 		pthread_mutex_lock(&logout_synch);
146 		in_lock = true;
147 	}
148 
149 	vfprintf(stdout, msg, args);
150 	va_end(args);
151 
152 	if (!msg || flush){
153 		fputc((int) '\n', stdout);
154 	}
155 
156 	if (flush){
157 		in_lock = false;
158 		pthread_mutex_unlock(&logout_synch);
159 	}
160 }
161 
scan_atoms()162 static void scan_atoms()
163 {
164 	for (size_t i = 0; i < ATOM_LAST; i++){
165 		xcb_intern_atom_cookie_t cookie =
166 			xcb_intern_atom(dpy, 0, strlen(atom_map[i]), atom_map[i]);
167 
168 		xcb_generic_error_t* error;
169 		xcb_intern_atom_reply_t* reply =
170 			xcb_intern_atom_reply(dpy, cookie, &error);
171 		if (reply && !error){
172 			atoms[i] = reply->atom;
173 		}
174 		if (error){
175 			trace("atom (%s) failed with code (%d)", atom_map[i], error->error_code);
176 			free(error);
177 		}
178 		free(reply);
179 	}
180 
181 /* do we need to add xfixes here? */
182 }
183 
setup_visuals()184 static bool setup_visuals()
185 {
186 	xcb_depth_iterator_t depth =
187 		xcb_screen_allowed_depths_iterator(screen);
188 
189 	while(depth.rem > 0){
190 		if (depth.data->depth == 32){
191 			visual = (xcb_depth_visuals_iterator(depth.data).data)->visual_id;
192 			colormap = xcb_generate_id(dpy);
193 			xcb_create_colormap(dpy, XCB_COLORMAP_ALLOC_NONE, colormap, wnd_root, visual);
194 			return true;
195 		}
196 		xcb_depth_next(&depth);
197 	}
198 
199 	return false;
200 }
201 
update_title(struct xwnd_state * state)202 static void update_title(struct xwnd_state* state)
203 {
204 	xcb_get_property_cookie_t cookie = xcb_get_property(
205 		dpy, 0, state->id, atoms[NET_WM_NAME], XCB_ATOM_ANY, 0, 2048);
206 	xcb_get_property_reply_t* reply = xcb_get_property_reply(dpy, cookie, NULL);
207 
208 	if (!reply)
209 		return;
210 
211 	if (reply->type != atoms[UTF8_STRING]){
212 		trace("title:unsupported_type:%d", (int)reply->type);
213 		goto out;
214 	}
215 
216 	size_t len = xcb_get_property_value_length(reply);
217 	char* title = xcb_get_property_value(reply);
218 	if (!title || !len)
219 		goto out;
220 
221 	char* scratch = strndup(title, len);
222 	if (!scratch)
223 		goto out;
224 
225 /* treat as non-0 terminated */
226 	if (!state->title || strcmp(state->title, scratch) != 0){
227 		free(state->title);
228 		state->title = scratch;
229 		trace("title=%s", scratch);
230 		char* pos = state->title;
231 		while(*pos){
232 			if (*pos == ':')
233 				*pos = ' ';
234 			pos++;
235 		}
236 
237 		wm_command(WM_FLUSH, "kind=title:id=%"PRIu32":msg=%s", state->id, state->title);
238 	}
239 	else
240 		free(scratch);
241 
242 out:
243 	free(reply);
244 }
245 
xcb_property_notify(xcb_property_notify_event_t * ev)246 static void xcb_property_notify(xcb_property_notify_event_t* ev)
247 {
248 	struct xwnd_state* state = NULL;
249 
250 #ifdef _DEBUG
251 	xcb_get_atom_name_cookie_t cookie = xcb_get_atom_name(dpy, ev->atom);
252 	xcb_get_atom_name_reply_t* name = xcb_get_atom_name_reply(dpy, cookie, NULL);
253 	trace("xcb=property-notify:property=%s", xcb_get_atom_name_name(name));
254 	xcb_get_atom_name_name_end(name);
255 #endif
256 
257 /* intended for our clipboard? */
258 	if (ev->window == wnd_wm){
259 
260 /* simplified utf8- for the time only - other is to enumerate the TARGETS */
261 	}
262 
263 	HASH_FIND_INT(windows,&ev->window,state);
264 	if (!state)
265 		return;
266 
267 	if (ev->atom != atoms[NET_WM_NAME])
268 		return;
269 
270 	update_title(state);
271 }
272 
update_focus(int64_t id)273 static void update_focus(int64_t id)
274 {
275 	struct xwnd_state* state = NULL;
276 
277 	if (id != XCB_WINDOW_NONE)
278 		HASH_FIND_INT(windows,&id,state);
279 
280 	input_focus = id;
281 	if (!state){
282 		xcb_set_input_focus_checked(dpy,
283 			XCB_INPUT_FOCUS_NONE, XCB_NONE, XCB_TIME_CURRENT_TIME);
284 		trace("focus-none");
285 	}
286 	else {
287 		if (state->override_redirect){
288 			trace("ignore-redirect");
289 			return;
290 		}
291 
292 		trace("focus-to:id=%"PRId64, id);
293 		xcb_client_message_event_t msg = (xcb_client_message_event_t){
294 			.response_type = XCB_CLIENT_MESSAGE,
295 			.format = 32,
296 			.window = id,
297 			.type = atoms[WM_PROTOCOLS],
298 			.data.data32[0] = atoms[WM_TAKE_FOCUS],
299 			.data.data32[1] = XCB_TIME_CURRENT_TIME
300 		};
301 
302 		xcb_send_event(dpy, 0, id, XCB_EVENT_MASK_SUBSTRUCTURE_REDIRECT, (char*)&msg);
303 		xcb_set_input_focus(dpy,
304 			XCB_INPUT_FOCUS_POINTER_ROOT, id, XCB_TIME_CURRENT_TIME);
305 		xcb_configure_window(dpy, id,
306 			XCB_CONFIG_WINDOW_STACK_MODE, (uint32_t[]){XCB_STACK_MODE_ABOVE, 0});
307 	}
308 
309 	xcb_change_property(dpy, XCB_PROP_MODE_REPLACE,
310 		wnd_root, atoms[NET_ACTIVE_WINDOW], atoms[WINDOW], 32, 1, &input_focus);
311 }
312 
create_window()313 static void create_window()
314 {
315 	wnd_wm = xcb_generate_id(dpy);
316 	xcb_create_window(dpy,
317 	XCB_COPY_FROM_PARENT, wnd_wm, wnd_root,
318 		0, 0, 10, 10, 0, XCB_WINDOW_CLASS_INPUT_OUTPUT,
319 		visual, 0, NULL
320 	);
321 /* should visual be here? */
322 
323 	xcb_change_property(dpy,
324 		XCB_PROP_MODE_REPLACE, wnd_wm,
325 		atoms[NET_SUPPORTING_WM_CHECK], XCB_ATOM_WINDOW, 32, 1, &wnd_wm);
326 
327 	static const char wmname[] = "Arcan XWM";
328 	xcb_change_property(dpy,
329 		XCB_PROP_MODE_REPLACE, wnd_wm,
330 		atoms[NET_WM_NAME], atoms[UTF8_STRING], 8, strlen(wmname), wmname);
331 
332 	xcb_change_property(dpy,
333 		XCB_PROP_MODE_REPLACE, wnd_wm,
334 		atoms[NET_SUPPORTING_WM_CHECK], XCB_ATOM_WINDOW, 32, 1, &wnd_root);
335 
336 /* for clipboard forwarding */
337 	xcb_set_selection_owner_checked(dpy, wnd_wm, atoms[CLIPBOARD_MANAGER], XCB_TIME_CURRENT_TIME);
338 
339 	xcb_convert_selection(dpy, wnd_wm,
340 		XCB_ATOM_PRIMARY, atoms[UTF8_STRING], atoms[XSEL_DATA], XCB_CURRENT_TIME);
341 
342 	int mask =
343 		XCB_XFIXES_SELECTION_EVENT_MASK_SET_SELECTION_OWNER |
344 		XCB_XFIXES_SELECTION_EVENT_MASK_SELECTION_WINDOW_DESTROY |
345 		XCB_XFIXES_SELECTION_EVENT_MASK_SELECTION_CLIENT_CLOSE;
346 
347 	xcb_xfixes_select_selection_input_checked(dpy, wnd_wm, atoms[PRIMARY], mask);
348   xcb_xfixes_select_selection_input_checked(dpy, wnd_wm, atoms[CLIPBOARD], mask);
349 }
350 
has_atom(xcb_get_property_reply_t * prop,enum atom_names atom)351 static bool has_atom(
352 	xcb_get_property_reply_t* prop, enum atom_names atom)
353 {
354 	if (prop == NULL || xcb_get_property_value_length(prop) == 0)
355 	return false;
356 
357 	xcb_atom_t* atom_query = xcb_get_property_value(prop);
358 	if (!atom_query){
359 		return false;
360 	}
361 
362 	size_t count = xcb_get_property_value_length(prop) / (prop->format / 8);
363 	for (size_t i = 0; i < count; i++){
364 		if (atom_query[i] == atoms[atom]){
365 			return true;
366 		}
367 	}
368 
369 	return false;
370 }
371 
check_window_support(xcb_window_t wnd,xcb_atom_t atom)372 static bool check_window_support(xcb_window_t wnd, xcb_atom_t atom)
373 {
374 	xcb_get_property_cookie_t cookie =
375 		xcb_icccm_get_wm_protocols(dpy, wnd, atoms[WM_PROTOCOLS]);
376 	xcb_icccm_get_wm_protocols_reply_t protocols;
377 
378 	if (xcb_icccm_get_wm_protocols_reply(dpy, cookie, &protocols, NULL) == 1){
379 		for (size_t i = 0; i < protocols.atoms_len; i++){
380 			if (protocols.atoms[i] == atom){
381 				xcb_icccm_get_wm_protocols_reply_wipe(&protocols);
382 				return true;
383 			}
384 		}
385 	}
386 
387 	return false;
388 }
389 
send_net_wm_state(struct xwnd_state * wnd)390 static void send_net_wm_state(struct xwnd_state* wnd)
391 {
392 	uint32_t property[6] = {0};
393 	size_t ind = 0;
394 
395 	if (wnd->fullscreen)
396 		property[ind++] = atoms[NET_WM_STATE_FULLSCREEN];
397 
398 	if (input_focus == wnd->id)
399 		property[ind++] = atoms[NET_WM_STATE_FOCUSED];
400 
401 	xcb_change_property(dpy, XCB_PROP_MODE_REPLACE,
402 		wnd->id, atoms[NET_WM_STATE], XCB_ATOM_ATOM, 32, ind, property);
403 }
404 
send_configured_window(struct xwnd_state * wnd)405 static void send_configured_window(struct xwnd_state* wnd)
406 {
407 	struct timed_request req;
408 
409 /* there is the race between us forwarding a configure request from the client,
410  * then acknowledging that request while there is also a wm initiated resize in
411  * flight - so pick the one we last saw, to make this less racy we should also
412  * pair with ev->serial */
413 	if (!wnd->override_redirect && wnd->wm_configure.ts_ms > wnd->cl_configure.ts_ms){
414 		req = wnd->wm_configure;
415 		trace("configure-wnd(srv) %d*%d@%d+%d", req.x, req.y, req.w, req.h);
416 	}
417 	else{
418 		req = wnd->cl_configure;
419 		trace("configure-wnd(cl) %d*%d@%d+%d", req.x, req.y, req.w, req.h);
420 	}
421 
422 	uint32_t values[8] = {
423 		req.x, req.y, req.w, req.h
424 	};
425 	int pos = 5;
426 
427 	int mask =
428 		XCB_CONFIG_WINDOW_X     | XCB_CONFIG_WINDOW_Y      |
429 		XCB_CONFIG_WINDOW_WIDTH | XCB_CONFIG_WINDOW_HEIGHT |
430 		XCB_CONFIG_WINDOW_BORDER_WIDTH;
431 
432 	if (wnd->pending_mask & XCB_CONFIG_WINDOW_SIBLING){
433 		values[pos++] = wnd->pending_sibling;
434 		wnd->pending_sibling = 0;
435 		mask |= XCB_CONFIG_WINDOW_SIBLING;
436 	}
437 
438 	if (wnd->pending_mask & XCB_CONFIG_WINDOW_STACK_MODE){
439 		values[pos++] = wnd->pending_stack;
440 		wnd->pending_stack = 0;
441 		mask |= XCB_CONFIG_WINDOW_STACK_MODE;
442 	}
443 
444 	send_net_wm_state(wnd);
445 	wnd->pending_mask = 0;
446 
447 	xcb_configure_window(dpy, wnd->id, mask, values);
448 }
449 
send_configure_notify(uint32_t id)450 static void send_configure_notify(uint32_t id)
451 {
452 	struct xwnd_state* state;
453 	HASH_FIND_INT(windows,&id,state);
454 	if (!state)
455 		return;
456 
457 /* so a number of games have different behavior for 'fullscreen', where
458  * an older form of this is using the x/y of the output and the dimensions
459  * of the window */
460 	xcb_configure_notify_event_t notify = (xcb_configure_notify_event_t){
461 		.response_type = XCB_CONFIGURE_NOTIFY,
462 		.event = id,
463 		.window = id,
464 		.above_sibling = XCB_WINDOW_NONE,
465 		.x = state->x,
466 		.y = state->y,
467 		.width = state->w,
468 		.height = state->h
469 	};
470 
471 	xcb_send_event(dpy, 0, id, XCB_EVENT_MASK_STRUCTURE_NOTIFY, (char*)&notify);
472 }
473 
check_window_state(uint32_t id)474 static const char* check_window_state(uint32_t id)
475 {
476 	xcb_get_property_cookie_t cookie = xcb_get_property(
477 		dpy, 0, id, atoms[NET_WM_WINDOW_TYPE], XCB_ATOM_ANY, 0, 2048);
478 	xcb_get_property_reply_t* reply = xcb_get_property_reply(dpy, cookie, NULL);
479 
480 /* couldn't find out more, just map it and hope */
481 	bool popup = false, dnd = false, menu = false, notification = false;
482 	bool splash = false, tooltip = false, utility = false, dropdown = false;
483 
484 	if (!reply){
485 		trace("no reply on window type atom");
486 		return "unknown";
487 	}
488 
489 	popup = has_atom(reply, NET_WM_WINDOW_TYPE_POPUP_MENU);
490 	dnd = has_atom(reply, NET_WM_WINDOW_TYPE_DND);
491 	dropdown = has_atom(reply, NET_WM_WINDOW_TYPE_DROPDOWN_MENU);
492 	menu  = has_atom(reply, NET_WM_WINDOW_TYPE_MENU);
493 	notification = has_atom(reply, NET_WM_WINDOW_TYPE_NOTIFICATION);
494 	splash = has_atom(reply, NET_WM_WINDOW_TYPE_SPLASH);
495 	tooltip = has_atom(reply, NET_WM_WINDOW_TYPE_TOOLTIP);
496 	utility = has_atom(reply, NET_WM_WINDOW_TYPE_UTILITY);
497 	free(reply);
498 
499 /*
500  * trace("wnd-state:%"PRIu32",popup=%d,menu=%d,dnd=%d,dropdown=%d,"
501 		"notification=%d,splash=%d,tooltip=%d,utility=%d:fullscreen=%d",
502 		id, popup, menu, dnd, dropdown, notification, splash,
503 		tooltip, utility, fullscreen
504 	);
505 */
506 
507 /* just string- translate and leave for higher layers to deal with */
508 	if (popup)
509 		return "popup";
510 	else if (dnd)
511 		return "dnd";
512 	else if (dropdown)
513 		return "dropdown";
514 	else if (menu)
515 		return "menu";
516 	else if (notification)
517 		return "notification";
518 	else if (splash)
519 		return "splash";
520 	else if (tooltip)
521 		return "tooltip";
522 	else if (utility)
523 		return "utility";
524 
525 	return "default";
526 }
527 
send_updated_window(struct xwnd_state * wnd,const char * kind)528 static void send_updated_window(struct xwnd_state* wnd, const char* kind)
529 {
530 /* defer update information until we have something mapped, otherwise we can
531  * get one update where the type is generic, then immediately one that is popup
532  * etc. making life more difficult for the arcan wm side */
533 /*
534  * metainformation about the window to better select a type and behavior.
535  *
536  * _NET_WM_WINDOW_TYPE replaces MOTIF_wm_HINTS so we much prefer that as it
537  * maps to the segment type.
538  */
539 	trace("update_window=%s:x=%"PRId32":y=%"PRId32":w=%"PRId32":y=%"PRId32,
540 		kind, wnd->x, wnd->y, wnd->w, wnd->h);
541 
542 	xcb_get_property_cookie_t cookie = xcb_get_property(dpy,
543 		0, wnd->id, XCB_ATOM_WM_TRANSIENT_FOR, XCB_ATOM_WINDOW, 0, 2048);
544 	xcb_get_property_reply_t* reply = xcb_get_property_reply(dpy, cookie, NULL);
545 
546 	if (reply){
547 		xcb_window_t* pwd = xcb_get_property_value(reply);
548 		wm_command(WM_FLUSH,
549 			"kind=%s:id=%"PRIu32":type=%s:x=%"PRId32":y=%"PRId32":parent_id=%"PRIu32,
550 			kind, wnd->id, check_window_state(wnd->id), wnd->x, wnd->y, *pwd
551 		);
552 		free(reply);
553 	}
554 	else
555 		wm_command(WM_FLUSH,
556 			"kind=%s:id=%"PRIu32":type=%s:x=%"PRId32":y=%"PRId32":w=%"PRId32":h=%"PRId32,
557 			kind, wnd->id, check_window_state(wnd->id), wnd->x, wnd->y, wnd->w, wnd->h
558 		);
559 
560 /*
561  * a bunch of translation heuristics here:
562  *  transient_for ? convert to parent- relative coordinates unless input
563  *  if input, set toplevel and viewport parent-
564  *
565  * do we have a map request coordinate?
566  */
567 
568 /*
569  * WM_HINTS :
570  *  flags as feature bitmap
571  *  input, initial_state, pixmap, window, position, mask, group,
572  *  message, urgency
573  */
574 }
575 
xcb_create_notify(xcb_create_notify_event_t * ev)576 static void xcb_create_notify(xcb_create_notify_event_t* ev)
577 {
578 	trace("xcb=create-notify:%"PRIu32, ev->window);
579 
580 /* if we add other wm- managed windows (selection, dnd),
581  * these should be filtered out here as well */
582 	if (is_wm_window(ev->window))
583 		return;
584 
585 	struct xwnd_state* state = malloc(sizeof(struct xwnd_state));
586 	*state = (struct xwnd_state){
587 		.id = ev->window,
588 		.x = ev->x,
589 		.y = ev->y,
590 		.w = ev->width,
591 		.h = ev->height,
592 		.kill_count = default_kill_count,
593 		.override_redirect = ev->override_redirect
594 	};
595 
596 	HASH_ADD_INT(windows, id, state);
597 	send_updated_window(state, "create");
598 }
599 
xcb_map_notify(xcb_map_notify_event_t * ev)600 static void xcb_map_notify(xcb_map_notify_event_t* ev)
601 {
602 	trace("xcb=map-notify:%"PRIu32, ev->window);
603 	struct xwnd_state* state;
604 	HASH_FIND_INT(windows,&ev->window,state);
605 	if (!state)
606 		return;
607 
608 	state->mapped = arcan_timemillis();
609 	update_title(state);
610 	send_updated_window(state, "map");
611 }
612 
xcb_map_request(xcb_map_request_event_t * ev)613 static void xcb_map_request(xcb_map_request_event_t* ev)
614 {
615 /* while the above could've made a round-trip to make sure we don't
616  * race with the wayland channel, the approach of detecting surface-
617  * type and checking seems to work ok (xwl.c) */
618 	trace("xcb=map-request:%"PRIu32, ev->window);
619 
620 	struct xwnd_state* state;
621 	HASH_FIND_INT(windows,&ev->window,state);
622 
623 /* ICCCM_NORMAL_STATE */
624 	xcb_change_property(dpy,
625 		XCB_PROP_MODE_REPLACE, ev->window, atoms[WM_STATE],
626 		atoms[WM_STATE], 32, 2, (uint32_t[]){1, XCB_WINDOW_NONE});
627 
628 /* this was the cause of a noteworthy issue - if we don't acknowledge the map
629  * Xwayland won't send the client message that would give us the ID to pair the
630  * wayland surface with that of the client, causing the window to be deadlocked
631  */
632 	state->managed = arcan_timemillis();
633 	xcb_map_window(dpy, ev->window);
634 }
635 
xcb_reparent_notify(xcb_reparent_notify_event_t * ev)636 static void xcb_reparent_notify(xcb_reparent_notify_event_t* ev)
637 {
638 	trace("xcb=reparent:=id%"PRIu32":parent=%"PRIu32":mode=%s",
639 		ev->window, ev->parent, ev->override_redirect ? " override" : "normal");
640 	if (ev->parent == wnd_root){
641 		wm_command(WM_FLUSH,
642 			"kind=reparent:parent=root:override=%d", ev->override_redirect ? 1 : 0);
643 	}
644 	else
645 		wm_command(WM_FLUSH,
646 			"kind=reparent:id=%"PRIu32":parent_id=%"PRIu32"%s",
647 			ev->window, ev->parent, ev->override_redirect ? ":override=1" : "");
648 }
649 
xcb_unmap_notify(xcb_unmap_notify_event_t * ev)650 static void xcb_unmap_notify(xcb_unmap_notify_event_t* ev)
651 {
652 	trace("xcb=unmap:id=%"PRIu32, ev->window);
653 	if (ev->window == input_focus)
654 		input_focus = XCB_WINDOW_NONE;
655 	if (ev->window == input_grab)
656 		input_grab = XCB_WINDOW_NONE;
657 
658 	struct xwnd_state* state = NULL;
659 	HASH_FIND_INT(windows,&ev->window,state);
660 
661 	wm_command(WM_FLUSH, "kind=unmap:id=%"PRIu32, ev->window);
662 }
663 
xcb_client_message(xcb_client_message_event_t * ev)664 static void xcb_client_message(xcb_client_message_event_t* ev)
665 {
666 	trace("xcb=client-message:id=%"PRIu32":type=%d", ev->window, ev->type);
667 /*
668  * switch type against resolved atoms:
669  *  NET_WM_STATE : (format field == 32), gives:
670  *                 modal, fullscreen, maximized_vert, maximized_horiz
671  * NET_ACTIVE_WINDOW: set active window on root
672  * NET_WM_MOVERESIZE: set edges for move-resize window
673  * PROTOCOLS: set ping-pong
674  */
675 	struct xwnd_state* state;
676 	HASH_FIND_INT(windows,&ev->window,state);
677 
678 /* WL_SURFACE_ID : gives wayland surface id */
679 	if (ev->type == atoms[WL_SURFACE_ID]){
680 		trace("wl-surface:%"PRIu32" to %"PRIu32, ev->data.data32[0], ev->window);
681 		wm_command(WM_FLUSH,
682 			"kind=surface:id=%"PRIu32":surface_id=%"PRIu32,
683 			ev->window, ev->data.data32[0]
684 		);
685 
686 		if (state){
687 			state->paired = true;
688 			send_updated_window(state, "map");
689 		}
690 	}
691 /* NET_WM_STATE:
692  * data32[0] : action (remove:0, add:1, toggle:2)
693  * [1,2] property (NET_WM_STATE_ MODAL, FULLSCREEN, MAXIMIZED_VERT, MAXIMIZED_HORIZ) */
694 	else if (ev->type == atoms[NET_WM_STATE] && state){
695 		if (ev->data.data32[1] == atoms[NET_WM_STATE_FULLSCREEN] ||
696 			ev->data.data32[2] == atoms[NET_WM_STATE_FULLSCREEN]){
697 			if (ev->data.data32[0] == 0){
698 				state->fullscreen = false;
699 			}
700 			else if (ev->data.data32[0] == 1){
701 				state->fullscreen = true;
702 			}
703 			else {
704 				state->fullscreen = !state->fullscreen;
705 			}
706 			wm_command(WM_FLUSH,
707 				"kind=fullscreen:state=%s:id=%"PRIu32,
708 				state->fullscreen ? "on" : "off", ev->window);
709 
710 		}
711 	}
712 	else {
713 		trace("client-message(unhandled) %"PRIu32"->%d", ev->window, ev->type);
714 	}
715 }
716 
xcb_destroy_notify(xcb_destroy_notify_event_t * ev)717 static void xcb_destroy_notify(xcb_destroy_notify_event_t* ev)
718 {
719 	trace("xcb=destroy-notify:id=%"PRIu32, ev->window);
720 	if (ev->window == input_focus){
721 		input_focus = -1;
722 	}
723 
724 	struct xwnd_state* state;
725 	HASH_FIND_INT(windows,&ev->window,state);
726 
727 	if (state){
728 		HASH_DEL(windows, state);
729 		free(state->title);
730 	}
731 
732 	size_t count = HASH_COUNT(windows);
733 	wm_command(WM_FLUSH, "kind=destroy:left=%zu:id=%"PRIu32,
734 		count, ((xcb_destroy_notify_event_t*) ev)->window);
735 }
736 
737 /*
738  * ConfigureNotify :
739  */
xcb_configure_notify(xcb_configure_notify_event_t * ev)740 static void xcb_configure_notify(xcb_configure_notify_event_t* ev)
741 {
742 	trace("xcb=configure-notify:id=%"PRIu32":x=%d:y=%d:w=%d:h=%d",
743 		ev->window, ev->x, ev->y, ev->width, ev->height);
744 
745 	struct xwnd_state* state;
746 	HASH_FIND_INT(windows,&ev->window,state);
747 	if (!state)
748 		return;
749 
750 /* is this one older than any of our pending requests? */
751 	state->configured = arcan_timemillis();
752 	state->x = ev->x;
753 	state->y = ev->y;
754 	state->w = ev->width;
755 	state->h = ev->height;
756 	state->override_redirect = ev->override_redirect;
757 
758 /* override redirect? use width / height */
759 
760 	if (state->mapped && state->paired){
761 		wm_command(WM_FLUSH,
762 		"kind=configure:id=%"PRIu32":x=%d:y=%d:w=%d:h=%d",
763 		ev->window, ev->x, ev->y, ev->width, ev->height);
764 	}
765 }
766 
767 /*
768  * ConfigureRequest : different client initiated a configure request
769  * (i.e. could practically be the result of ourselves saying 'configure'
770  */
xcb_configure_request(xcb_configure_request_event_t * ev)771 static void xcb_configure_request(xcb_configure_request_event_t* ev)
772 {
773 	trace("xcb=configure-request:id=%"PRIu32":x=%d:y=%d:w=%d:d=%d",
774 			ev->window, ev->x, ev->y, ev->width, ev->height);
775 
776 	struct xwnd_state* state;
777 	HASH_FIND_INT(windows,&ev->window,state);
778 	if (!state){
779 		trace("status=error:unknown_window:id=%d", ev->window);
780 		return;
781 	}
782 
783 /* this needs to translate to _resize calls and to VIEWPORT hint events */
784 	wm_command(WM_FLUSH,
785 		"kind=configure:id=%"PRIu32":x=%d:y=%d:w=%d:h=%d",
786 		ev->window, ev->x, ev->y, ev->width, ev->height
787 	);
788 
789 	struct timed_request req =
790 		(struct timed_request){
791 		.ts_ms = arcan_timemillis(),
792 		.x = state->x,
793 		.y = state->y,
794 		.w = state->w,
795 		.h = state->h
796 	};
797 
798 	if (ev->width)
799 		req.w = ev->width;
800 
801 	if (ev->height)
802 		req.h = ev->height;
803 
804 	if (state->fullscreen)
805 		send_configure_notify(ev->window);
806 
807 /* client might request more information than is in the normal arcan-wm
808  * response, so recall the mask and provide the values on the proper cfg */
809 	state->pending_mask = (ev->value_mask &
810 		(XCB_CONFIG_WINDOW_SIBLING | XCB_CONFIG_WINDOW_STACK_MODE));
811 
812 	state->pending_stack = ev->stack_mode;
813 	state->pending_sibling = ev->sibling;
814 	state->cl_configure = req;
815 
816 /* So if the server does not yet know about this window > at all < it might not
817  * try to map unless it gets a configure, some clients do, some have a timeout.
818  * Send the configuration immediately for the time being, and consider adding a
819  * timeout ourselves (e.g. a tick- event handler from parent) */
820 	if (!state->mapped || state->override_redirect)
821 		send_configured_window(state);
822 }
823 
xcb_focus_in(xcb_focus_in_event_t * ev)824 static void xcb_focus_in(xcb_focus_in_event_t* ev)
825 {
826 	if (ev->mode == XCB_NOTIFY_MODE_GRAB ||
827 		ev->mode == XCB_NOTIFY_MODE_UNGRAB){
828 		trace("kind=focus-in:status=grab-ignore:id=%"PRIu32, ev->event);
829 		return;
830 	}
831 	trace("kind=focus-in:id=%"PRIu32, ev->event);
832 
833 /* this is more troublesome than it seems, so this is a notification that
834  * something got focus. This might not reflect the focus status in the real WM,
835  * so right now we just say nope, you don't get to chose focus and force-back
836  * the old one - otoh some applications do like to hand over focus between its
837  * windows as possible keybindings, ... and there it might be highly desired */
838 	if (XCB_WINDOW_NONE == input_focus){ /* || ev->event != input_focus){ */
839 		update_focus(input_focus);
840 	}
841 }
842 
843 /* use stdin/popen/line based format to make debugging easier */
process_wm_command(const char * arg)844 static void process_wm_command(const char* arg)
845 {
846 	trace("wm_command=%s", arg);
847 	struct arg_arr* args = arg_unpack(arg);
848 	if (!args)
849 		return;
850 
851 /* all commands have kind/id */
852 	const char* idstr;
853 	if (!arg_lookup(args, "id", 0, &idstr) || !idstr){
854 		trace("wm_error=bad_argument:message=missing/empty id");
855 		goto cleanup;
856 	}
857 
858 /* and they should be present in the wnd table */
859 	struct xwnd_state* state = NULL;
860 	uint32_t id = strtoul(idstr, NULL, 10);
861 	HASH_FIND_INT(windows,&id,state);
862 	if (!state){
863 		trace("wm_error=bad_wnd_id=%s", idstr);
864 		goto cleanup;
865 	}
866 
867 	const char* dst;
868 	if (!arg_lookup(args, "kind", 0, &dst) || !dst){
869 		trace("wm_error=bad_argument:message=missing/empty kind");
870 		goto cleanup;
871 	}
872 
873 	if (strcmp(dst, "maximized") == 0){
874 		trace("wm=srv-maximize:id=%s", idstr);
875 	}
876 	else if (strcmp(dst, "fullscreen") == 0){
877 		trace("wm=srv-fullscreen:id=%s", idstr);
878 		state->fullscreen = !state->fullscreen;
879 	}
880 	else if (strcmp(dst, "resize") == 0){
881 		if (!arg_lookup(args, "width", 0, &dst) || !dst){
882 			trace("wm_error=bad_argument:message=resize missing width");
883 			goto cleanup;
884 		}
885 		size_t w = strtoul(dst, NULL, 10);
886 
887 		if (!arg_lookup(args, "height", 0, &dst) || !dst){
888 			trace("wm_error=bad_argument:message=resize missing height");
889 			goto cleanup;
890 		}
891 		size_t h = strtoul(dst, NULL, 10);
892 		trace("wm=srv-resize:id=%s:width=%zu:height=%zu", idstr, w, h);
893 		const char* wtype = check_window_state(id);
894 
895 /* just don't configure popups etc. */
896 		if (strcmp(wtype, "default") == 0){
897 			state->wm_configure.ts_ms = arcan_timemillis();
898 			state->wm_configure.w = w;
899 			state->wm_configure.h = h;
900 			send_configured_window(state);
901 		}
902 		else
903 			trace("wm=srv-resize:id=%s:wtype=%s:ignored=true", idstr, wtype);
904 	}
905 /* absolute positioned window position need to be synched, the cleaner bit is
906  * that this hould really merge into the resize- handling but we are somewhat
907  * more adamant about server mandated position */
908 	else if (strcmp(dst, "move") == 0){
909 		arg_lookup(args, "x", 0, &dst);
910 		ssize_t x = strtol(dst, NULL, 10);
911 		arg_lookup(args, "y", 0, &dst);
912 		ssize_t y = strtol(dst, NULL, 10);
913 		trace("srv-move(%d)(%zd, %zd)", id, x, y);
914 		state->wm_configure.w = state->x = x;
915 		state->wm_configure.h = state->y = y;
916 		xcb_configure_window(dpy, id,
917 			XCB_CONFIG_WINDOW_X | XCB_CONFIG_WINDOW_Y,
918 			(uint32_t[]){x, y, 0, 0, 0}
919 		);
920 	}
921 	else if (strcmp(dst, "destroy") == 0){
922 /* check if window support WM_DELETE_WINDOW, and if so: */
923 		if (check_window_support(id, atoms[WM_DELETE_WINDOW]) && state->kill_count){
924 			trace("srv-destroy, delete_window(%d)", id);
925 			xcb_client_message_event_t ev = {
926 				.response_type = XCB_CLIENT_MESSAGE,
927 				.window = id,
928 				.type = atoms[WM_PROTOCOLS],
929 				.format = 32,
930 				.data = {
931 					.data32 = {atoms[WM_DELETE_WINDOW], XCB_CURRENT_TIME}
932 				}
933 			};
934 			xcb_send_event(dpy, 0, id, XCB_EVENT_MASK_NO_EVENT, (char*)&ev);
935 			state->kill_count--;
936 		}
937 		else {
938 /* if it is the last one currently active, disconnect the client */
939 			trace("srv-destroy, delete_kill(%d)", id);
940 			if (HASH_COUNT(windows) == 1){
941 				xcb_kill_client(dpy, id);
942 			}
943 			else
944 				xcb_destroy_window(dpy, id);
945 		}
946 	}
947 	else if (strcmp(dst, "unfocus") == 0){
948 		if (input_focus == id){
949 			trace("srv-unfocus(%d)", id);
950 			update_focus(-1);
951 		}
952 	}
953 	else if (strcmp(dst, "focus") == 0){
954 		struct xwnd_state* state = NULL;
955 		HASH_FIND_INT(windows,&id,state);
956 
957 		if (state && state->override_redirect)
958 			goto cleanup;
959 
960 		trace("srv-focus(%d)", id);
961 		update_focus(id);
962 	}
963 
964 /*
965  * paste is problematic -
966  * we can set the selection owner for clipboard to ourselves and unpack/get the
967  * buffer or file from our parent but the actual 'paste' is not initiated
968  * automatically, so the ordering becomes important
969  */
970 
971 cleanup:
972 	arg_cleanup(args);
973 }
974 
supported_selection(xcb_atom_t type)975 static bool supported_selection(xcb_atom_t type)
976 {
977 	return false;
978 }
979 
xcb_selection_request(struct xcb_selection_request_event_t * ev)980 static void xcb_selection_request(struct xcb_selection_request_event_t* ev)
981 {
982 	trace("kind=selection_request");
983 }
984 
xcb_selection_notify(struct xcb_selection_notify_event_t * ev)985 static void xcb_selection_notify(struct xcb_selection_notify_event_t* ev)
986 {
987 	if (ev->property == XCB_ATOM_NONE){
988 		trace("kind=error:message=couldn't convert selection");
989 		return;
990 	}
991 
992 	if (ev->selection == atoms[XCB_ATOM_PRIMARY]){
993 		xcb_icccm_get_text_property_reply_t prop;
994 		xcb_get_property_cookie_t cookie =
995 			xcb_icccm_get_text_property(dpy, ev->requestor, ev->property);
996 
997 		if (xcb_icccm_get_text_property_reply(dpy, cookie, &prop, NULL)){
998 			if (prop.name_len){
999 				char* buf = malloc(prop.name_len+1);
1000 				buf[prop.name_len] = 0;
1001 				memcpy(buf, prop.name, prop.name_len);
1002 				trace("text_property=%s", buf);
1003 				free(buf);
1004 			}
1005 			xcb_icccm_get_text_property_reply_wipe(&prop);
1006 			xcb_delete_property(dpy, ev->requestor, ev->property);
1007 		}
1008 	}
1009 
1010 	if (supported_selection(ev->target)){
1011 	}
1012 /* push the descriptor or data on the socket, then send the corresponding
1013  * message on the output queue */
1014 
1015 /* ev->target matches the ATOM from the requested selection target,
1016  * e.g. timestamp, utf8_string, text, ...) */
1017 	trace("kind=error:message=unsupported selection target");
1018 }
1019 
spawn_debug()1020 static void spawn_debug()
1021 {
1022 	trace("kind=status:message=debug requested");
1023 	struct arcan_shmif_cont ct = arcan_shmif_open(SEGID_TUI, 0, NULL);
1024 	if (ct.addr){
1025 		arcan_shmif_debugint_spawn(&ct, NULL, NULL);
1026 /* &(struct debugint_ext_resolver){
1027  * .handler, .label and .tag to attach to the menu structure, can expose the
1028  * known WM states there
1029  * } */
1030 	}
1031 }
1032 
process_thread(void * arg)1033 static void* process_thread(void* arg)
1034 {
1035 	while (!ferror(stdin) && !feof(stdin)){
1036 		char inbuf[1024];
1037 		if (fgets(inbuf, 1024, stdin)){
1038 /* trim */
1039 			size_t len = strlen(inbuf);
1040 			if (len && inbuf[len-1] == '\n')
1041 				inbuf[len-1] = '\0';
1042 
1043 /* shouldn't be needed but doesn't trust xcb */
1044 			pthread_mutex_lock(&wm_synch);
1045 			process_wm_command(inbuf);
1046 			xcb_flush(dpy);
1047 			pthread_mutex_unlock(&wm_synch);
1048 		}
1049 	}
1050 	wm_command(WM_FLUSH, "kind=terminated");
1051 	uint8_t ch = 'x';
1052 	trace("shutdown:source=process_thread");
1053 	write(signal_fd, &ch, 1);
1054 	return NULL;
1055 }
1056 
run_event()1057 static void run_event()
1058 {
1059 	xcb_generic_event_t* ev = xcb_wait_for_event(dpy);
1060 	if (ev->response_type == 0){
1061 		return;
1062 	}
1063 
1064 	pthread_mutex_lock(&wm_synch);
1065 	switch (ev->response_type & ~0x80) {
1066 /* the following are mostly relevant for "UI" events if the decorations are
1067 * implemented in the context of X rather than at a higher level. Since this
1068 * doesn't really apply to us, these can be ignored */
1069 	case XCB_BUTTON_PRESS:
1070 		trace("xcb=button-press");
1071 	break;
1072 	case XCB_MOTION_NOTIFY:
1073 		trace("xcb=motion-notify");
1074 	break;
1075 	case XCB_BUTTON_RELEASE:
1076 		trace("xcb=button-release");
1077 	break;
1078 	case XCB_ENTER_NOTIFY:
1079 		trace("xcb=enter-notify");
1080 	break;
1081 	case XCB_LEAVE_NOTIFY:
1082 		trace("xcb=leave-notify");
1083 	break;
1084 /*
1085 * end of 'UI notifications'
1086 */
1087 	case XCB_CREATE_NOTIFY:
1088 		xcb_create_notify((xcb_create_notify_event_t*) ev);
1089 	break;
1090 	case XCB_MAP_REQUEST:
1091 		xcb_map_request((xcb_map_request_event_t*) ev);
1092 	break;
1093 	case XCB_MAP_NOTIFY:
1094 		xcb_map_notify((xcb_map_notify_event_t*) ev);
1095 	break;
1096 	case XCB_UNMAP_NOTIFY:
1097 		xcb_unmap_notify((xcb_unmap_notify_event_t*) ev);
1098 	break;
1099 	case XCB_REPARENT_NOTIFY:
1100 		xcb_reparent_notify((xcb_reparent_notify_event_t*) ev);
1101 	break;
1102 	case XCB_CONFIGURE_REQUEST:
1103 		xcb_configure_request((xcb_configure_request_event_t*) ev);
1104 	break;
1105 	case XCB_CONFIGURE_NOTIFY:
1106 		xcb_configure_notify((xcb_configure_notify_event_t*) ev);
1107 	break;
1108 	case XCB_DESTROY_NOTIFY:
1109 		xcb_destroy_notify((xcb_destroy_notify_event_t*) ev);
1110 	break;
1111 /* keyboards / pointer / notifications, not interesting here
1112  * unless going for some hotkey etc. kind of a thing */
1113 	case XCB_MAPPING_NOTIFY:
1114 		trace("xcb=mapping-notify");
1115 	break;
1116 	case XCB_PROPERTY_NOTIFY:
1117 		xcb_property_notify((xcb_property_notify_event_t*) ev);
1118 	break;
1119 	case XCB_CLIENT_MESSAGE:
1120 		xcb_client_message((xcb_client_message_event_t*) ev);
1121 	break;
1122 	case XCB_FOCUS_IN:
1123 		xcb_focus_in((xcb_focus_in_event_t*) ev);
1124 	break;
1125 	case XCB_SELECTION_NOTIFY:
1126 		xcb_selection_notify((xcb_selection_notify_event_t *) ev);
1127 	break;
1128 	case XCB_SELECTION_REQUEST:
1129 		xcb_selection_request((xcb_selection_request_event_t *) ev);
1130 	break;
1131 	default:
1132 		trace("xcb-unhandled:type=%"PRIu8, ev->response_type);
1133 	break;
1134 	}
1135 	xcb_flush(dpy);
1136 	pthread_mutex_unlock(&wm_synch);
1137 }
1138 
1139 /*
1140  * before we have our WM 'window' and before the first client has been launched
1141  */
setup_init_state(bool standalone)1142 static void setup_init_state(bool standalone)
1143 {
1144 	xcb_change_window_attributes(dpy, wnd_root, XCB_CW_EVENT_MASK,
1145 	(uint32_t[]){
1146 		XCB_EVENT_MASK_SUBSTRUCTURE_NOTIFY |
1147 		XCB_EVENT_MASK_SUBSTRUCTURE_REDIRECT |
1148 		XCB_EVENT_MASK_PROPERTY_CHANGE, 0, 0
1149 	});
1150 
1151 	if (!standalone){
1152 		xcb_composite_redirect_subwindows(
1153 			dpy, wnd_root, XCB_COMPOSITE_REDIRECT_MANUAL);
1154 	}
1155 
1156 	xcb_atom_t atom_supp[] = {
1157 		atoms[NET_WM_STATE],
1158 		atoms[NET_ACTIVE_WINDOW],
1159 		atoms[NET_WM_MOVERESIZE],
1160 		atoms[NET_WM_STATE_MODAL],
1161 		atoms[NET_WM_STATE_FULLSCREEN],
1162 		atoms[NET_WM_STATE_MAXIMIZED_VERT],
1163 		atoms[NET_WM_STATE_MAXIMIZED_HORZ],
1164 		atoms[NET_WM_STATE_FOCUSED]
1165 	};
1166 
1167 	xcb_change_property(dpy,
1168 		XCB_PROP_MODE_REPLACE, wnd_root, atoms[NET_SUPPORTED],
1169 		XCB_ATOM_ATOM, 32, 6, atom_supp
1170 	);
1171 }
1172 
xcb_msg_thread(void * arg)1173 static void* xcb_msg_thread(void* arg)
1174 {
1175 	for (;;)
1176 		run_event();
1177 	return NULL;
1178 }
1179 
launch_child(bool unlink_display,char ** argv)1180 static int launch_child(bool unlink_display, char** argv)
1181 {
1182 /*
1183  * Now it should be safe to chain-execute any single client we'd want, and
1184  * unlink on connect should we want to avoid more clients connecting to the
1185  * display, this will block / stop clients that proxy/spawn multiples etc.
1186  *
1187  * To retain that kind of isolation the other option is to create a
1188  * new namespace for this tree and keep the references around in there.
1189  */
1190 	exec_child = fork();
1191 	if (-1 == exec_child){
1192 		trace("setup=fail:message=fork-fail");
1193 		return EXIT_FAILURE;
1194 	}
1195 	else if (0 == exec_child){
1196 /* remove the display variable, but also unlink the parent socket for the
1197  * normal 'default' display as some toolkits also fallback and check for it,
1198  * might be 'safer' to rename it but we are already quite far out in wtfsville */
1199 		const char* disp = getenv("WAYLAND_DISPLAY");
1200 		if (!disp)
1201 			disp = "wayland-0";
1202 
1203 /* should be guaranteed here but just to be certain, length is at sun_path (108) */
1204 		if (getenv("XDG_RUNTIME_DIR") && unlink_display){
1205 			char path[128];
1206 			snprintf(path, 128, "%s/%s", getenv("XDG_RUNTIME_DIR"), disp);
1207 			unlink(path);
1208 		}
1209 
1210 		unsetenv("WAYLAND_DISPLAY");
1211 
1212 		int ndev = open("/dev/null", O_RDWR);
1213 		dup2(ndev, STDIN_FILENO);
1214 		dup2(ndev, STDOUT_FILENO);
1215 		dup2(ndev, STDERR_FILENO);
1216 		close(ndev);
1217 		setsid();
1218 
1219 		execvp(argv[0], argv);
1220 		return EXIT_FAILURE;
1221 	}
1222 	else {
1223 		trace("client_exec=%s", argv[1]);
1224 	}
1225 	return EXIT_SUCCESS;
1226 }
1227 
main(int argc,char ** argv)1228 int main (int argc, char **argv)
1229 {
1230 	int code;
1231 	int exec_ind = 1;
1232 
1233 	sigaction(SIGCHLD, &(struct sigaction){
1234 		.sa_handler = on_chld, .sa_flags = 0}, 0);
1235 
1236 	sigaction(SIGPIPE, &(struct sigaction){
1237 		.sa_handler = on_chld, .sa_flags = 0}, 0);
1238 
1239 /* standalone mode is to test/debug the WM against an externally managed X,
1240  * this runs without the normal inherited/rootless setup */
1241 	xwm_standalone = argc > 1 && strcmp(argv[1], "-standalone") == 0;
1242 	bool single_exec = !xwm_standalone && argc > exec_ind;
1243 	bool use_notification = !xwm_standalone;
1244 
1245 	char* binary = "Xwayland";
1246 	char* binary_arg = "-rootless";
1247 
1248 /* Allow substituting Xwayland for Xarcan, normally this mode should not be
1249  * needed as we can run the WM passing logic through Xarcan itself and skip the
1250  * middle man, but for testing / development it works fine. Ideally we should
1251  * also preload the xcb open and get rid of the /tmp/X11 hardcoded path in
1252  * favor of XDG_RUNTIME */
1253 	if (argc > 1 && strcmp(argv[1], "-xarcan") == 0){
1254 		binary = "Xarcan";
1255 		binary_arg = "-noreset";
1256 		xwm_standalone = true;
1257 		use_notification = true;
1258 		exec_ind = 2;
1259 		single_exec = argc > exec_ind;
1260 	}
1261 
1262 /*
1263  * Now we spawn the XWayland instance with a pipe- pair so that we can
1264  * read when the server is ready
1265  */
1266 	int notification[2];
1267 	if (-1 == pipe(notification)){
1268 		trace("setup=fail:message=couldn't create status pipe");
1269 		return EXIT_FAILURE;
1270 	}
1271 
1272 	int wmfd[2] = {-1, -1};
1273 	if (-1 == socketpair(AF_UNIX, SOCK_STREAM, 0, wmfd)){
1274 		trace("setup=fail:message=couldn't create wm socket");
1275 		return EXIT_FAILURE;
1276 	}
1277 	int flags = fcntl(wmfd[0], F_GETFD);
1278 	fcntl(wmfd[0], F_SETFD, flags | O_CLOEXEC);
1279 
1280 	pid_t xwayland = fork();
1281 	if (0 == xwayland){
1282 		close(notification[0]);
1283 		char* argv[] = {
1284 			binary, binary_arg,
1285 			"-displayfd", NULL,
1286 			"-wm", NULL,
1287 			NULL, NULL};
1288 
1289 		asprintf(&argv[3], "%d", notification[1]);
1290 		if (single_exec)
1291 			argv[6] = "-terminate";
1292 
1293 		asprintf(&argv[5], "%d", wmfd[1]);
1294 
1295 /* noreset will not really hit unless the wm itself exists, which we don't
1296  * expose real controls for in the single-exec mode. If we map to Xarcan stdio
1297  * can be restored through preroll bonding the descriptors */
1298 		int ndev = open("/dev/null", O_RDWR);
1299 		dup2(ndev, STDIN_FILENO);
1300 		dup2(ndev, STDOUT_FILENO);
1301 		dup2(ndev, STDERR_FILENO);
1302 		close(ndev);
1303 		setsid();
1304 
1305 		execvp(binary, argv);
1306 		exit(EXIT_FAILURE);
1307 	}
1308 	else if (-1 == xwayland){
1309 		trace("setup=fail:message=failed to fork xwayland");
1310 		return EXIT_FAILURE;
1311 	}
1312 	trace("%s:pid=%d", binary, xwayland);
1313 /*
1314  * wait for a reply from the Xwayland setup, we can also get that as a SIGUSR1
1315  */
1316 	if (use_notification){
1317 		trace("xwayland:status=initializing");
1318 		char inbuf[64] = {0};
1319 		close(notification[1]);
1320 		int rv = read(notification[0], inbuf, 63);
1321 		if (-1 == rv){
1322 			trace("xwayland:message=%s", strerror(errno));
1323 			return EXIT_FAILURE;
1324 		}
1325 
1326 		char* err;
1327 		unsigned long num = strtoul(inbuf, &err, 10);
1328 		if (err == inbuf){
1329 			trace("xwayland:status=error:message=couldn't spawn");
1330 			return EXIT_FAILURE;
1331 		}
1332 
1333 		char dispnum[8];
1334 		snprintf(dispnum, 8, ":%lu", num);
1335 		setenv("DISPLAY", dispnum, 1);
1336 		close(notification[0]);
1337 		trace("xwayland:display=%lu", num);
1338 /*
1339  * since we have gotten a reply, the display should be ready, just connect
1340  */
1341 		dpy = xcb_connect_to_fd(wmfd[0], NULL);
1342 	}
1343 	else{
1344 		dpy = xcb_connect(NULL, NULL);
1345 	}
1346 
1347 	if ((code = xcb_connection_has_error(dpy))){
1348 		trace("setup=fail:code=%d:message=no display", code);
1349 		return EXIT_FAILURE;
1350 	}
1351 
1352 	screen = xcb_setup_roots_iterator(xcb_get_setup(dpy)).data;
1353 	wnd_root = screen->root;
1354 	if (!setup_visuals()){
1355 		trace("setup=fail:message=no 32bit visual");
1356 		return EXIT_FAILURE;
1357 	}
1358 
1359 	scan_atoms();
1360 
1361 /* pipe pair to 'wake' event thread with */
1362 	int eventsig[2];
1363 	if (-1 == pipe(eventsig)){
1364 		trace("setup=fail:message=pipe-pair fail:reason=%s", strerror(errno));
1365 		return EXIT_FAILURE;
1366 	}
1367 	signal_fd = eventsig[1];
1368 
1369 	sigaction(SIGUSR2, &(struct sigaction){.sa_handler = on_dbgreq}, 0);
1370 
1371 	setup_init_state(xwm_standalone);
1372 
1373 	create_window();
1374 
1375 /* used to pass descriptors in/out when there are clipboard transfers */
1376 	if (getenv("XWM_CLIPBOARD_SOCKET"))
1377 		clipboard_fd = strtoul(getenv("XWM_CLIPBOARD_SOCKET"), NULL, 10);
1378 
1379 /*
1380  * xcb is thread-safe, so we can have one thread for incoming
1381  * dispatch and another thread for outgoing dispatch
1382  */
1383 	if (xwm_standalone){
1384 		if (single_exec)
1385 			launch_child(false, &argv[exec_ind]);
1386 		for (;;){
1387 			run_event();
1388 		}
1389 		return EXIT_SUCCESS;
1390 	}
1391 
1392 	setlinebuf(stdin);
1393 	setlinebuf(stdout);
1394 
1395 /* one thread for the WM, one thread for the arcan-wayland connection */
1396 	pthread_t pth;
1397 	pthread_attr_t pthattr;
1398 	pthread_attr_init(&pthattr);
1399 	pthread_attr_setdetachstate(&pthattr, PTHREAD_CREATE_DETACHED);
1400 	pthread_create(&pth, &pthattr, process_thread, NULL);
1401 	pthread_attr_setdetachstate(&pthattr, PTHREAD_CREATE_DETACHED);
1402 	pthread_create(&pth, &pthattr, xcb_msg_thread, NULL);
1403 
1404 	if (single_exec)
1405 		launch_child(true, &argv[exec_ind]);
1406 
1407 	for(;;){
1408 		uint8_t ch;
1409 		if (1 == read(eventsig[0], &ch, 1)){
1410 			if (ch == 'x'){
1411 				trace("shutdown:source=kill_thread_msg");
1412 				break;
1413 			}
1414 			if (ch == 'd')
1415 				spawn_debug();
1416 		}
1417 	}
1418 
1419 	if (exec_child != -1){
1420 		trace("shutdown:kill_child=%d", (int)exec_child);
1421 		kill(SIGHUP, exec_child);
1422 	}
1423 
1424 	if (xwayland != -1){
1425 		trace("shutdown:kill_xwayland=%d", (int)xwayland);
1426 		kill(SIGHUP, xwayland);
1427 	}
1428 
1429 	return 0;
1430 }
1431