1 /*
2  * There are a number of oddities with dealing with XWayland, and
3  * its behavior change depending on if you are using rootless mode
4  * or not.
5  *
6  * With 'normal' mode it behaves as a dumb (and buggy) wl_shell
7  * client that basically ignored everything.
8  *
9  * With 'rootless' mode, it creates compositor surfaces and uses
10  * them directly - being basically the only client to do so. The
11  * job then is to pair these surfaces based on a window property
12  * and just treat them as something altogether special by adding
13  * a custom window-manager.
14  */
15 #include <sys/wait.h>
16 #include <sys/types.h>
17 #include <sys/ipc.h>
18 #include <sys/mman.h>
19 #include <sys/time.h>
20 #include <sys/resource.h>
21 #include <fcntl.h>
22 #include <unistd.h>
23 #include <stdlib.h>
24 #include <stdio.h>
25 #include <stdarg.h>
26 
27 static FILE* wmfd_output = NULL;
28 static pid_t xwl_wm_pid = -1;
29 static char* xwl_wm_display;
30 
31 static int wmfd_input = -1;
32 static char wmfd_inbuf[1024];
33 static size_t wmfd_ofs = 0;
34 
35 /* "known" mapped windows, we trigger the search etc. when a buffer
36  * is commited without a known backing on the compositor, and try
37  * to 'pair' it with ones that we have been told about */
38 struct xwl_window {
39 
40 /* Xid for Window */
41 	uint32_t id;
42 
43 /* Wayland client-local reference ID */
44 	uint32_t surface_id;
45 
46 /* Parent Xid for Window */
47 	uint32_t parent_id;
48 
49 /* Track viewport separate from comp_surf viewport as it can be
50  * populated when there is still no surf to pair it to */
51 	struct arcan_event viewport;
52 
53 /* a window mapping that is PAIRED means that we know both the local
54  * compositor surface and the wmed X surface */
55 	bool paired;
56 
57 /* a window mapping that is PENDING means that we only know the local
58  * compositor surface and there is a pending commit on that surface */
59 	struct wl_resource* pending_res;
60 	struct wl_client* pending_client;
61 
62 /* when a surface goes pending, we flush a queue of pending wm state
63  * messages, if the size does not suffice here, we should really
64  * just override the ones that have been redefined */
65 	size_t queue_count;
66 	struct arcan_event queued_message[8];
67 
68 	char* xtype;
69 	struct comp_surf* surf;
70 };
71 
72 /* just linear search it for the time being, can copy the UT_HASH
73  * use from the xwlwm side if need be */
74 
75 static struct xwl_window xwl_windows[256];
xwl_find(uint32_t id)76 static struct xwl_window* xwl_find(uint32_t id)
77 {
78 	for (size_t i = 0; i < COUNT_OF(xwl_windows); i++)
79 		if (xwl_windows[i].id == id)
80 			return &xwl_windows[i];
81 
82 	return NULL;
83 }
84 
dump_unpaired()85 static void dump_unpaired()
86 {
87 	for (size_t i = 0; i < COUNT_OF(xwl_windows); i++){
88 		struct xwl_window* wnd = &xwl_windows[i];
89 
90 		if (!wnd->paired && (wnd->id || wnd->surface_id)){
91 			trace(TRACE_XWL, "unpaired_index=%zu:xid=%"PRIu32":wlid=%"PRIu32":type=%s:queue=%zu",
92 				i, wnd->id, wnd->surface_id, wnd->xtype ? wnd->xtype : "unknown", wnd->queue_count);
93 		}
94 	}
95 }
96 
wnd_queue(struct xwl_window * wnd,struct arcan_event * ev)97 static int wnd_queue(struct xwl_window* wnd, struct arcan_event* ev)
98 {
99 	bool res = false;
100 
101 /* messages can come while unpaired, buffer them for the time being */
102 	if (!wnd->surf){
103 		if (wnd->queue_count < COUNT_OF(wnd->queued_message)){
104 			wnd->queued_message[wnd->queue_count++] = *ev;
105 			return -1;
106 		}
107 		return -2;
108 	}
109 
110 	arcan_shmif_enqueue(&wnd->surf->acon, ev);
111 	return 0;
112 }
113 
wnd_message(struct xwl_window * wnd,const char * fmt,...)114 static void wnd_message(struct xwl_window* wnd, const char* fmt, ...)
115 {
116 	struct arcan_event ev = {
117 		.category = EVENT_EXTERNAL,
118 		.ext.kind = ARCAN_EVENT(MESSAGE)
119 	};
120 
121 	va_list args;
122 	va_start(args, fmt);
123 		vsnprintf((char*)ev.ext.message.data,
124 			COUNT_OF(ev.ext.message.data), fmt, args);
125 	va_end(args);
126 
127 	switch(wnd_queue(wnd, &ev)){
128 	case -1:
129 		trace(TRACE_XWL,
130 			"queue(%zu:message) unpaired: %s", wnd->queue_count, ev.ext.message.data);
131 	break;
132 	case -2:
133 		trace(TRACE_XWL, "queue-full:broken_wm");
134 		break;
135 	case 0:
136 		trace(TRACE_XWL, "message:%s", ev.ext.message.data);
137 	break;
138 	}
139 }
140 
wnd_title(struct xwl_window * wnd,const char * title)141 static void wnd_title(struct xwl_window* wnd, const char* title)
142 {
143 	arcan_event ev = {
144 		.category = EVENT_EXTERNAL,
145 		.ext.kind = ARCAN_EVENT(IDENT)
146 	};
147 
148 	size_t lim = sizeof(ev.ext.message.data)/sizeof(ev.ext.message.data[1]);
149 	snprintf((char*)ev.ext.message.data, lim, "%s", title);
150 	switch (wnd_queue(wnd, &ev)){
151 	case -1:
152 		trace(TRACE_XWL,
153 			"queue(%zu:title) unpaired: %s", wnd->queue_count, ev.ext.message.data);
154 	case -2:
155 	case 0:
156 	break;
157 	}
158 }
159 
xwl_find_surface(uint32_t id)160 static struct xwl_window* xwl_find_surface(uint32_t id)
161 {
162 	for (size_t i = 0; i < COUNT_OF(xwl_windows); i++)
163 		if (xwl_windows[i].surface_id == id)
164 			return &xwl_windows[i];
165 
166 	return NULL;
167 }
168 
xwl_find_alloc(uint32_t id)169 static struct xwl_window* xwl_find_alloc(uint32_t id)
170 {
171 	struct xwl_window* wnd = xwl_find(id);
172 	if (wnd)
173 		return wnd;
174 
175 /* default to 'toplevel' like behavior */
176 	wnd = xwl_find(0);
177 	return wnd;
178 }
179 
180 static void surf_commit(struct wl_client*, struct wl_resource*);
181 static void wnd_viewport(struct xwl_window* wnd);
182 
xwl_wnd_paired(struct xwl_window * wnd)183 static void xwl_wnd_paired(struct xwl_window* wnd)
184 {
185 /*
186  * we will be sent here if there existed a surface id before the id mapping was
187  * known, and there might be an id before the surface id. Find the 'orphan'
188  * and copy / merge
189  */
190 	ssize_t match = -1;
191 
192 	for (size_t i = 0; i < COUNT_OF(xwl_windows); i++){
193 		if (&xwl_windows[i] == wnd){
194 			continue;
195 		}
196 		if (xwl_windows[i].id == wnd->id || xwl_windows[i].id == wnd->surface_id){
197 			match = i;
198 			break;
199 		}
200 	}
201 
202 /* merge orphan states */
203 	if (-1 != match){
204 		struct xwl_window tmp = *wnd;
205 		struct xwl_window* xwnd = &xwl_windows[match];
206 
207 		*wnd = *xwnd;
208 		wnd->id = tmp.id;
209 		wnd->surface_id = tmp.surface_id;
210 		wnd->pending_res = tmp.pending_res;
211 		wnd->pending_client = tmp.pending_client;
212 		*xwnd = (struct xwl_window){};
213 	}
214 
215 	wnd->paired = true;
216 	wnd_message(wnd, "pair:%d:%d", wnd->surface_id, wnd->id);
217 	surf_commit(wnd->pending_client, wnd->pending_res);
218 
219 	if (!wnd->surf){
220 		trace(TRACE_XWL, "couldn't pair, surface allocation failed");
221 		return;
222 	}
223 
224 	wnd->pending_res = NULL;
225 	wnd->pending_client = NULL;
226 }
227 
wnd_viewport(struct xwl_window * wnd)228 static void wnd_viewport(struct xwl_window* wnd)
229 {
230 /* always re-resolve parent token */
231 	wnd->viewport.ext.viewport.parent = 0;
232 
233 	if (wnd->parent_id > 0){
234 		struct xwl_window* pwnd = xwl_find(wnd->parent_id);
235 		if (!pwnd || !pwnd->surf){
236 			trace(TRACE_XWL, "bad parent id:%"PRIu32, wnd->parent_id);
237 		}
238 		else{
239 		trace(TRACE_XWL, "parent set (%"PRIu32") => (%"PRIu32")",
240 			wnd->parent_id, pwnd->surf->acon.segment_token);
241 			wnd->viewport.ext.viewport.parent = pwnd->surf->acon.segment_token;
242 		}
243 	}
244 
245 	if (!wnd->surf)
246 		return;
247 
248 	arcan_shmif_enqueue(&wnd->surf->acon, &wnd->viewport);
249 
250 	trace(TRACE_XWL, "viewport id:%"PRIu32",parent:%"PRIu32"@%"PRId32",%"PRId32,
251 		wnd->id, wnd->parent_id,
252 		wnd->viewport.ext.viewport.x, wnd->viewport.ext.viewport.y
253 	);
254 }
255 
256 /*
257  * Take an input- line from the window manager, unpack it, and interpret the
258  * command inside. A noticable part here is that the resolved window may be
259  * in an unallocated, unpaired or paired state here and the input itself is
260  * not necessarily trusted.
261  *
262  * Thus any extracted field or update that should propagate as an event to
263  * a backing shmif connection need to be support being deferred until
264  * pairing / allocation - and resist UAF/spoofing. Luckily, there is not
265  * many events that need to be forwarded.
266  */
process_input(const char * msg)267 static int process_input(const char* msg)
268 {
269 /* special handling for multiplexing trace messages */
270 	trace(TRACE_XWL, "wm->%s", msg);
271 	if (strncmp(msg, "kind=trace", 10) == 0){
272 		return 0;
273 	}
274 
275 	struct arg_arr* cmd = arg_unpack(msg);
276 	if (!cmd){
277 		trace(TRACE_XWL, "malformed message: %s", msg);
278 		return 0;
279 	}
280 
281 /* all commands should have a 'kind' field */
282 	const char* arg;
283 	if (!arg_lookup(cmd, "kind", 0, &arg) && arg){
284 		trace(TRACE_XWL, "malformed argument: %s, missing kind", msg);
285 		goto cleanup;
286 	}
287 /* pair surface */
288 	else if (strcmp(arg, "surface") == 0){
289 		if (!arg_lookup(cmd, "id", 0, &arg) && arg){
290 			trace(TRACE_XWL, "malformed surface argument: missing id");
291 			goto cleanup;
292 		}
293 		uint32_t id = strtoul(arg, NULL, 10);
294 		if (!arg_lookup(cmd, "surface_id", 0, &arg) && arg){
295 			trace(TRACE_XWL, "malformed surface argument: missing surface id");
296 			goto cleanup;
297 		}
298 		uint32_t surface_id = strtoul(arg, NULL, 10);
299 		trace(TRACE_XWL, "surface id:%"PRIu32"-%"PRIu32, id, surface_id);
300 		struct xwl_window* wnd = xwl_find_surface(surface_id);
301 		if (!wnd){
302 			wnd = xwl_find_alloc(id);
303 			dump_unpaired();
304 		}
305 		if (!wnd)
306 			goto cleanup;
307 
308 		wnd->surface_id = surface_id;
309 		wnd->id = id;
310 
311 /* if we already know about the compositor-surface, activate it */
312 		if (wnd->pending_res){
313 			trace(TRACE_XWL, "paired-pending %"PRIu32, id);
314 			xwl_wnd_paired(wnd);
315 		}
316 		wnd->paired = true;
317 	}
318 /* window goes from invisible to visible state */
319 	else if (strcmp(arg, "create") == 0){
320 		if (!arg_lookup(cmd, "id", 0, &arg) && arg){
321 			trace(TRACE_XWL, "malformed map argument: missing id");
322 			goto cleanup;
323 		}
324 		uint32_t id = strtoul(arg, NULL, 10);
325 		trace(TRACE_XWL, "map id:%"PRIu32, id);
326 		struct xwl_window* wnd = xwl_find_alloc(id);
327 		if (!wnd)
328 			goto cleanup;
329 
330 		wnd->id = id;
331 
332 /* should come with some kind of type information */
333 		if (arg_lookup(cmd, "type", 0, &arg) && arg){
334 			trace(TRACE_XWL, "created with type %s", arg);
335 			wnd->xtype = strdup(arg);
336 			wnd_message(wnd, "type:%s", arg);
337 		}
338 		else{
339 			trace(TRACE_XWL, "malformed create argument: missing type");
340 			goto cleanup;
341 		}
342 
343 /* we only viewport when we have a grab or hierarchy relationship change */
344 		if (arg_lookup(cmd, "parent", 0, &arg) && arg){
345 			uint32_t parent_id = strtoul(arg, NULL, 0);
346 			struct xwl_window* wnd = xwl_find(parent_id);
347 			if (wnd){
348 				trace(TRACE_XWL, "found parent surface: %"PRIu32, parent_id);
349 				wnd->parent_id = parent_id;
350 				wnd_viewport(wnd);
351 			}
352 			else
353 				trace(TRACE_XWL, "bad parent-id: "PRIu32, parent_id);
354 		}
355 	}
356 	else if (strcmp(arg, "title") == 0 && arg){
357 		if (!arg_lookup(cmd, "id", 0, &arg) && arg)
358 			goto cleanup;
359 
360 		uint32_t id = strtoul(arg, NULL, 10);
361 		struct xwl_window* wnd = xwl_find(id);
362 
363 		if (!wnd)
364 			goto cleanup;
365 
366 		const char* msg = NULL;
367 		if (!arg_lookup(cmd, "msg", 0, &msg) || !msg){
368 			msg = "";
369 		}
370 		wnd_title(wnd, msg);
371 	}
372 /* reparent */
373 	else if (strcmp(arg, "parent") == 0){
374 		if (!arg_lookup(cmd, "id", 0, &arg) && arg)
375 			goto cleanup;
376 		uint32_t id = strtoul(arg, NULL, 10);
377 
378 		if (!arg_lookup(cmd, "parent_id", 0, &arg) && arg)
379 			goto cleanup;
380 
381 		uint32_t parent_id = strtoul(arg, NULL, 10);
382 		struct xwl_window* wnd = xwl_find(id);
383 
384 		if (!wnd)
385 			goto cleanup;
386 
387 		wnd->parent_id = parent_id;
388 		trace(TRACE_XWL, "reparent id:%"PRIu32" to %"PRIu32, id, parent_id);
389 		wnd_viewport(wnd);
390 	}
391 /* invisible -> visible */
392 	else if (strcmp(arg, "map") == 0){
393 		if (!arg_lookup(cmd, "id", 0, &arg) && arg){
394 			trace(TRACE_XWL, "map:status=no_id");
395 			goto cleanup;
396 		}
397 		uint32_t id = strtoul(arg, NULL, 10);
398 		struct xwl_window* wnd = xwl_find(id);
399 
400 		if (!wnd){
401 			trace(TRACE_XWL, "map:id=%"PRIu32":status=no_wnd", id);
402 			goto cleanup;
403 		}
404 
405 		wnd->viewport.ext.viewport.invisible = false;
406 		if (arg_lookup(cmd, "parent_id", 0, &arg)){
407 			wnd->parent_id = strtoul(arg, NULL, 10);
408 		}
409 
410 		if (arg_lookup(cmd, "x", 0, &arg)){
411 			wnd->viewport.ext.viewport.x = strtol(arg, NULL, 10);
412 		}
413 		if (arg_lookup(cmd, "y", 0, &arg)){
414 			wnd->viewport.ext.viewport.y = strtol(arg, NULL, 10);
415 		}
416 
417 		if (!arg_lookup(cmd, "type", 0, &arg)){
418 			trace(TRACE_XWL, "remap id:%"PRIu32", failed, no type information", id);
419 			goto cleanup;
420 		}
421 
422 		if (wnd->xtype)
423 			free(wnd->xtype);
424 		wnd->xtype = strdup(arg);
425 		wnd_message(wnd, "type:%s", arg);
426 		wnd_viewport(wnd);
427 	}
428 /* window goes from visible to invisible state, but resources remain */
429 	else if (strcmp(arg, "unmap") == 0){
430 		trace(TRACE_XWL, "unmap");
431 		uint32_t id = strtoul(arg, NULL, 10);
432 		struct xwl_window* wnd = xwl_find(id);
433 		if (!wnd)
434 			goto cleanup;
435 
436 		wnd->viewport.ext.viewport.invisible = true;
437 		wnd_viewport(wnd);
438 	}
439 	else if (strcmp(arg, "terminated") == 0){
440 		trace(TRACE_XWL, "xwayland died");
441 		if (wl.exec_mode)
442 			wl.alive = false;
443 	}
444 /* window changes position or hierarchy, the size part is tied to the
445  * buffer in shmif- parlerance so we don't really care to match that
446  * here */
447 	else if (strcmp(arg, "configure") == 0){
448 		if (!arg_lookup(cmd, "id", 0, &arg)){
449 			trace(TRACE_XWL, "malformed surface argument: missing surface id");
450 			goto cleanup;
451 		}
452 		uint32_t id = strtoul(arg, NULL, 10);
453 		struct xwl_window* wnd = xwl_find(id);
454 		if (!wnd){
455 			trace(TRACE_XWL, "configure on unknown id %"PRIu32, id);
456 			goto cleanup;
457 		}
458 
459 /* we cache the viewport event for the window as well as for the surface
460  * due to the possibility of the unpaired state */
461 		if (arg_lookup(cmd, "x", 0, &arg)){
462 			wnd->viewport.ext.viewport.x = strtol(arg, NULL, 10);
463 			trace(TRACE_XWL, "x reconfigured %d", wnd->viewport.ext.viewport.x);
464 		}
465 
466 		if (arg_lookup(cmd, "y", 0, &arg)){
467 			wnd->viewport.ext.viewport.y = strtol(arg, NULL, 10);
468 			trace(TRACE_XWL, "y reconfigured %d", wnd->viewport.ext.viewport.y);
469 		}
470 
471 /* and either reflect now or later */
472 		wnd_viewport(wnd);
473 	}
474 	else if (strcmp(arg, "destroy") == 0){
475 		if (!arg_lookup(cmd, "id", 0, &arg)){
476 			trace(TRACE_XWL, "malformed surface argument: missing surface id");
477 			goto cleanup;
478 		}
479 		uint32_t id = strtoul(arg, NULL, 10);
480 		struct xwl_window* wnd = xwl_find(id);
481 		if (!wnd){
482 			trace(TRACE_XWL, "destroy on unknown id %"PRIu32, id);
483 			goto cleanup;
484 		}
485 		*wnd = (struct xwl_window){};
486 	}
487 /* just forward */
488 	else if (arg_lookup(cmd, "fullscreen", 0, &arg) && arg){
489 		if (!arg_lookup(cmd, "id", 0, &arg)){
490 			trace(TRACE_XWL, "malformed surface argument: missing surface id");
491 			goto cleanup;
492 		}
493 		uint32_t id = strtoul(arg, NULL, 10);
494 		struct xwl_window* wnd = xwl_find(id);
495 		if (!wnd){
496 			trace(TRACE_XWL, "configure on unknown id %"PRIu32, id);
497 			goto cleanup;
498 		}
499 
500 		wnd_message(wnd, "fullscreen=%s", arg);
501 	}
502 
503 cleanup:
504 	arg_cleanup(cmd);
505 	return 0;
506 }
507 
xwl_spawn_check(const char * msg)508 static int xwl_spawn_check(const char* msg)
509 {
510 	struct arg_arr* cmd = arg_unpack(msg);
511 	int rv = -1;
512 	if (cmd){
513 		const char* arg;
514 		if (arg_lookup(cmd, "xwayland_fail", 0, &arg)){
515 			trace(TRACE_XWL, "couldn't spawn xwayland: %s", arg);
516 			rv = -3;
517 		}
518 		if (arg_lookup(cmd, "xwayland_ok", 0, &arg)){
519 			trace(TRACE_XWL, "xwayland on: %s", arg);
520 			xwl_wm_display = strdup(arg);
521 		}
522 		arg_cleanup(cmd);
523 		rv = 0;
524 	}
525 	return rv;
526 }
527 
close_xwl()528 static void close_xwl()
529 {
530 	fclose(wmfd_output);
531 	close(wmfd_input);
532 	wmfd_ofs = 0;
533 	xwl_wm_pid = -1;
534 	wmfd_input = -1;
535 	trace(TRACE_XWL, "arcan_xwm died");
536 }
537 
xwl_read_wm(int (* callback)(const char * str))538 static int xwl_read_wm(int (*callback)(const char* str))
539 {
540 	if (xwl_wm_pid == -1)
541 		return -1;
542 
543 /* track so we don't accidentally call into ourself */
544 	static bool in_wm_input;
545 	if (in_wm_input)
546 		return 0;
547 	in_wm_input = true;
548 
549 /* populate inbuffer, look for linefeed */
550 	char inbuf[256];
551 	int status;
552 	ssize_t nr = read(wmfd_input, inbuf, 256);
553 	if (-1 == nr){
554 		if (errno != EAGAIN && errno != EINTR){
555 			trace(TRACE_XWL, "kind=error:code=%d:message=%s", errno, strerror(errno));
556 			kill(xwl_wm_pid, SIGKILL);
557 			waitpid(xwl_wm_pid, NULL, 0);
558 			close_xwl();
559 		}
560 		else {
561 			int res = waitpid(xwl_wm_pid, &status, WNOHANG);
562 			if (res == xwl_wm_pid && WIFEXITED(status)){
563 				trace(TRACE_XWL, "kind=status:message=exited");
564 				close_xwl();
565 			}
566 		}
567 
568 		in_wm_input = false;
569 		return -1;
570 	}
571 
572 /* check the new input for a linefeed character, or flush to the buffer */
573 	for (size_t i = 0; i < nr; i++){
574 		if (inbuf[i] == '\n'){
575 			wmfd_inbuf[wmfd_ofs] = '\0';
576 			wmfd_ofs = 0;
577 
578 /* leave the rest in buffer */
579 			if (callback(wmfd_inbuf) != 0){
580 				trace(TRACE_XWL, "kind=error:callback_fail:message=%s", wmfd_inbuf);
581 				break;
582 			}
583 		}
584 /* accept crop on overflow (though no command should be this long) */
585 		else {
586 			wmfd_inbuf[wmfd_ofs] = inbuf[i];
587 			wmfd_ofs ++;
588 			if (wmfd_ofs == sizeof(wmfd_inbuf)){
589 				trace(TRACE_XWL, "kind=error:overflow");
590 				wmfd_ofs = 0;
591 			}
592 		}
593 	}
594 
595 /* possibly more to flush */
596 	in_wm_input = false;
597 	if (nr == 256)
598 		return xwl_read_wm(callback);
599 
600 	return 0;
601 }
602 
xwl_request_handover()603 static void xwl_request_handover()
604 {
605 /* use the waybridge to request a handover surface that will be used for xwl
606  * debugging bootstrap and for clipboard / selection - it is a bit trickier if
607  * this happens on a wm-crash where we run in service mode as the control
608  * connection is lost, option is to re-open one or just ignore the clipboard
609  * part, start with the later */
610 	if (!wl.control.addr){
611 		return;
612 	}
613 
614 	arcan_shmif_enqueue(&wl.control,
615 	&(struct arcan_event){
616 		.ext.kind = ARCAN_EVENT(SEGREQ),
617 		.ext.segreq.kind = SEGID_HANDOVER
618 	});
619 
620 /* incomplete - we need to wait for the handover to arrive before spawning
621  * the WM, then use that as our 'clipboard' / dnd manager - other option is
622  * to switch from transfer pipes to a shared socket and push descriptors
623  * over that as well */
624 }
625 
626 /*
627  * Launch the arcan-xwayland-wm (that in turn launches Xwayland) if [block] is
628  * set, wait until arcan-xwayland-wm acknowledges that Xwayland could be
629  * launched or not.
630  *
631  * The other detail is how to handle the clipboard in a way that covers both
632  * cut/paste and drag/drop. Best is probably to request a clipboard/handover
633  * and then exec that into arcan-xwayland-wm.
634  *
635  * Error codes:
636  * -1 : arcan-xwayland-wm connection died
637  * -2 : arcan-xwayland-wm couldn't spawn Xwayland
638  * -3 : resource errors spawning arcan-xwayland-wm
639  *  0 : ok
640  */
xwl_spawn_wm(bool block,char ** argv)641 static int xwl_spawn_wm(bool block, char** argv)
642 {
643 /* already running */
644 	if (xwl_wm_pid != -1)
645 		return 0;
646 
647 	trace(TRACE_XWL, "spawning 'arcan-xwayland-wm'");
648 	wl.groups[0].xwm->fd = -1;
649 	int p2c_pipe[2];
650 	int c2p_pipe[2];
651 	if (-1 == pipe(p2c_pipe))
652 		return -3;
653 
654 	if (-1 == pipe(c2p_pipe)){
655 		close(p2c_pipe[0]);
656 		close(p2c_pipe[1]);
657 		return -3;
658 	}
659 
660 	wmfd_input = c2p_pipe[0];
661 	wmfd_output = fdopen(p2c_pipe[1], "w");
662 	setlinebuf(wmfd_output);
663 	wl.groups[0].xwm->fd = wmfd_input;
664 
665 	xwl_wm_pid = fork();
666 
667 	if (-1 == xwl_wm_pid){
668 		fprintf(stderr, "Couldn't spawn wm- process (fork failed)\n");
669 		exit(EXIT_FAILURE);
670 	}
671 
672 /* child, close, dup spawn */
673 	if (!xwl_wm_pid){
674 		close(p2c_pipe[1]);
675 		close(c2p_pipe[0]);
676 		dup2(p2c_pipe[0], STDIN_FILENO);
677 		dup2(c2p_pipe[1], STDOUT_FILENO);
678 		close(p2c_pipe[1]);
679 		close(c2p_pipe[0]);
680 
681 		setsid();
682 		int ndev = open("/dev/null", O_WRONLY);
683 		dup2(ndev, STDERR_FILENO);
684 		close(ndev);
685 
686 		size_t nargs = 0;
687 		while (argv[nargs])
688 			nargs++;
689 
690 /* need to prepend the binary name so top makes sense */
691 		char* newargv[nargs+2];
692 		newargv[0] = "arcan_xwm";
693 		newargv[nargs+1] = NULL;
694 		for (size_t i = 0; i < nargs+1; i++){
695 			newargv[i+1] = argv[i];
696 		}
697 
698 /* want to avoid the situation when building / working from a build dir, the
699  * execvp approach would still the usr/bin one */
700 #ifdef _DEBUG
701 		execv("./arcan_xwm", newargv);
702 #endif
703 		execvp("arcan_xwm", newargv);
704 		exit(EXIT_FAILURE);
705 	}
706 
707 /* in this case we care about the status of the ability to launch Xwayland,
708  * this happens with -xwl -exec */
709 	if (block){
710 		int rv = xwl_read_wm(xwl_spawn_check);
711 		if (rv < 0)
712 			return rv;
713 	}
714 
715 /* want the input-pipe to work non-blocking here */
716 	int flags = fcntl(wmfd_input, F_GETFL);
717 		if (-1 != flags)
718 			fcntl(wmfd_input, F_SETFL, flags | O_NONBLOCK);
719 
720 /* drop child write end, parent read end as the wm process owns these now */
721 	close(c2p_pipe[1]);
722 	close(p2c_pipe[0]);
723 
724 	return 0;
725 }
726 
xwl_check_wm()727 static void xwl_check_wm()
728 {
729 	xwl_read_wm(process_input);
730 }
731 
xwlsurf_shmifev_handler(struct comp_surf * surf,struct arcan_event * ev)732 static bool xwlsurf_shmifev_handler(
733 	struct comp_surf* surf, struct arcan_event* ev)
734 {
735 	if (ev->category != EVENT_TARGET || !wmfd_output)
736 		return false;
737 
738 /* translate relevant non-input shmif events to the text- based
739  * format used with the wm- process */
740 
741 	struct xwl_window* wnd =
742 		xwl_find_surface(wl_resource_get_id(surf->shell_res));
743 	if (!wnd)
744 		return false;
745 
746 	switch (ev->tgt.kind){
747 	case TARGET_COMMAND_DISPLAYHINT:{
748 		struct surf_state states = surf->states;
749 		bool changed = displayhint_handler(surf, &ev->tgt);
750 		int rw = ev->tgt.ioevs[0].iv;
751 		int rh = ev->tgt.ioevs[1].iv;
752 		int dw = rw - (int)surf->acon.w;
753 		int dh = rh - (int)surf->acon.h;
754 
755 /* split up into resize requests and focus/input changes */
756 		if (rw > 0 && rh > 0 && (dw != 0 || dh != 0)){
757 			trace(TRACE_XWL, "displayhint: %"PRIu32",%"PRIu32,
758 				ev->tgt.ioevs[0].iv, ev->tgt.ioevs[1].iv);
759 
760 			fprintf(wmfd_output,
761 				"id=%"PRIu32":kind=resize:width=%"PRIu32":height=%"PRIu32":ts=%"PRIu64"%s\n",
762 				(uint32_t) wnd->id,
763 				(uint32_t) abs(ev->tgt.ioevs[0].iv),
764 				(uint32_t) abs(ev->tgt.ioevs[1].iv),
765 				(uint64_t) ev->tgt.timestamp,
766 				(surf->states.drag_resize) ? ":drag" : ""
767 			);
768 		}
769 
770 		if (changed){
771 			if (states.unfocused != surf->states.unfocused){
772 				fprintf(wmfd_output, "id=%"PRIu32":ts=%"PRIu64"%s\n",
773 					wnd->id,
774 					(uint64_t) ev->tgt.timestamp,
775 					surf->states.unfocused ? ":kind=unfocus" : ":kind=focus"
776 				);
777 				if (surf->states.unfocused){
778 					release_all_keys(surf->client);
779 					leave_all(surf);
780 				}
781 				else{
782 					enter_all(surf);
783 /*
784 					send_pointer_button(surf->client, STEP_SERIAL(),
785 						arcan_timemillis(), 0x10f, WL_POINTER_BUTTON_STATE_RELEASED);
786 */
787 				}
788 			}
789 		}
790 
791 		return true;
792 	}
793 /* raw-forward from appl to xwm, doesn't respect multipart */
794 	case TARGET_COMMAND_MESSAGE:
795 		fprintf(wmfd_output, "id=%"PRIu32":%s\n", (uint32_t) wnd->id, ev->tgt.message);
796 	break;
797 	case TARGET_COMMAND_EXIT:
798 		fprintf(wmfd_output, "kind=destroy:id=%"PRIu32"\n", wnd->id);
799 		*wnd = (struct xwl_window){};
800 		return true;
801 	default:
802 	break;
803 	}
804 
805 	return false;
806 }
807 
xwl_defer_handler(struct surface_request * req,struct arcan_shmif_cont * con)808 static bool xwl_defer_handler(
809 	struct surface_request* req, struct arcan_shmif_cont* con)
810 {
811 	if (!req || !con){
812 		return false;
813 	}
814 
815 	struct comp_surf* surf = wl_resource_get_user_data(req->target);
816 	surf->acon = *con;
817 	surf->cookie = 0xfeedface;
818 	surf->shell_res = req->target;
819 	surf->dispatch = xwlsurf_shmifev_handler;
820 	surf->id = wl_resource_get_id(surf->shell_res);
821 
822 	struct xwl_window* wnd = (req->tag);
823 	wnd->surf = surf;
824 	wnd->viewport.ext.kind = ARCAN_EVENT(VIEWPORT);
825 	trace(TRACE_ALLOC, "kind=X:fd=%d:queue=%zu", con->epipe, wnd->queue_count);
826 
827 /* the normal path is:
828  * surface_commit -> check_paired -> request surface -> request ok ->
829  * commit and flush */
830 	if (wnd->queue_count > 0){
831 		trace(TRACE_XWL, "flush (%zu) queued messages", wnd->queue_count);
832 		for (size_t i = 0; i < wnd->queue_count; i++){
833 			arcan_shmif_enqueue(con, &wnd->queued_message[i]);
834 		}
835 		wnd->queue_count = 0;
836 	}
837 	surf->viewport = wnd->viewport;
838 	wnd_viewport(wnd);
839 
840 	return true;
841 }
842 
843 static struct xwl_window*
lookup_surface(struct comp_surf * surf,struct wl_resource * res)844 	lookup_surface(struct comp_surf* surf, struct wl_resource* res)
845 {
846 	if (!wl.use_xwayland)
847 		return NULL;
848 
849 /* always start by synching against pending from wm as the surface + atom
850  * mapping might be done there before we actually get to this stage */
851 	xwl_check_wm();
852 	uint32_t id = wl_resource_get_id(res);
853 	struct xwl_window* wnd = xwl_find_surface(id);
854 	if (!wnd){
855 		wnd = xwl_find(0);
856 		if (!wnd){
857 			trace(TRACE_XWL, "out-of-memory");
858 			return NULL;
859 		}
860 		wnd->surface_id = id;
861 	}
862 	return wnd;
863 }
864 
865 /* called from the surface being commited when it is not marked as paired */
xwl_pair_surface(struct wl_client * cl,struct comp_surf * surf,struct wl_resource * res)866 static bool xwl_pair_surface(
867 	struct wl_client* cl, struct comp_surf* surf, struct wl_resource* res)
868 {
869 /* do we know of a matching xwayland- provided surface? */
870 	struct xwl_window* wnd = lookup_surface(surf, res);
871 	if (!wnd){
872 		trace(TRACE_XWL,
873 			"no known X surface for: %"PRIu32, wl_resource_get_id(res));
874 		return false;
875 	}
876 
877 /* are we waiting for the surface-id part? then set as pending so that we can
878  * activate when it arrives - allocation behavior is a bit suspicious here -
879  * other option is also to release the buffer and trigger frame callbacks */
880 	if (!wnd->paired){
881 		trace(TRACE_XWL,
882 			"unpaired surface-ID: %"PRIu32, wl_resource_get_id(res));
883 		wnd->pending_res = res;
884 		wnd->pending_client = cl;
885 		dump_unpaired();
886 
887 		return false;
888 	}
889 
890 	if (surf->acon.addr){
891 		return true;
892 	}
893 
894 /* if so, allocate the corresponding arcan- side resource */
895 	return request_surface(surf->client, &(struct surface_request){
896 			.segid = SEGID_BRIDGE_X11,
897 			.target = res,
898 			.trace = "xwl",
899 			.dispatch = xwl_defer_handler,
900 			.client = surf->client,
901 			.source = surf,
902 			.tag = wnd
903 	}, 'X');
904 }
905