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*)¬ify);
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