FreeRDP
Loading...
Searching...
No Matches
mutter_shadow.c
1
19/* The shadow server for a GNOME session, through Mutter's own D-Bus APIs on
20 * the session bus (no portal, so no permission dialog: only the session's own
21 * user reaches its bus):
22 *
23 * 1. org.gnome.Mutter.DisplayConfig.GetCurrentState the monitors
24 * 2. RemoteDesktop.CreateSession a remote desktop session
25 * 3. ScreenCast.CreateSession, tied to it a screen cast session
26 * 4. ScreenCast.Session.RecordMonitor(connector) a stream of one monitor,
27 * the cursor as metadata (the clients draw it)
28 * 5. RemoteDesktop.Session.Start PipeWireStreamAdded(node)
29 * 6. RemoteDesktop.Session.ConnectToEIS input through libei
30 *
31 * The frames arrive as a PipeWire stream in shared memory, with the damaged
32 * regions and the cursor as buffer metadata. One thread waits on the session
33 * bus, the PipeWire loop and libei; the clients' threads post input to libei
34 * under a lock.
35 */
36
37#include <errno.h>
38#include <fcntl.h>
39#include <math.h>
40#include <poll.h>
41#include <pwd.h>
42#include <stdarg.h>
43#include <string.h>
44#include <time.h>
45#include <unistd.h>
46
47#include <linux/input-event-codes.h>
48
49#include <spa/param/audio/format-utils.h>
50#include <spa/param/video/format-utils.h>
51#include <spa/param/buffers.h>
52#include <spa/buffer/meta.h>
53
54#include <winpr/assert.h>
55#include <winpr/crt.h>
56#include <winpr/collections.h>
57#include <winpr/input.h>
58#include <winpr/interlocked.h>
59#include <winpr/path.h>
60#include <winpr/synch.h>
61#include <winpr/sysinfo.h>
62
63#include <freerdp/server/server-common.h>
64#include <freerdp/codec/audio.h>
65#include <freerdp/codec/color.h>
66#include <freerdp/codec/dsp.h>
67#include <freerdp/codec/region.h>
68#include <freerdp/log.h>
69
70#ifdef WITH_PAM
71#include <security/pam_appl.h>
72#endif
73
74#include "mutter_shadow.h"
75
76#define TAG SERVER_TAG("shadow.mutter")
77
78#define DC_NAME "org.gnome.Mutter.DisplayConfig"
79#define DC_PATH "/org/gnome/Mutter/DisplayConfig"
80#define RD_NAME "org.gnome.Mutter.RemoteDesktop"
81#define RD_PATH "/org/gnome/Mutter/RemoteDesktop"
82#define RD_SESSION_IFACE "org.gnome.Mutter.RemoteDesktop.Session"
83#define SC_NAME "org.gnome.Mutter.ScreenCast"
84#define SC_PATH "/org/gnome/Mutter/ScreenCast"
85#define SC_SESSION_IFACE "org.gnome.Mutter.ScreenCast.Session"
86#define SC_STREAM_IFACE "org.gnome.Mutter.ScreenCast.Stream"
87
88/* ScreenCast cursor mode: the cursor comes as buffer metadata, not painted in */
89#define MUTTER_CURSOR_MODE_EMBEDDED 1u
90#define MUTTER_CURSOR_MODE_METADATA 2u
91#define MUTTER_VIRTUAL_ENV "FREERDP_SHADOW_MUTTER_VIRTUAL"
92/* "0" turns off the check for changes Mutter left out of its damage */
93#define MUTTER_DAMAGE_CHECK_ENV "FREERDP_SHADOW_MUTTER_DAMAGE_CHECK"
94#define MUTTER_CHECK_TILE 64
95
96/* FREERDP_SHADOW_MUTTER_VIRTUAL=<w>x<h>: TRUE and the size when set and valid */
97WINPR_ATTR_NODISCARD
98static BOOL mutter_virtual_size(UINT32* width, UINT32* height)
99{
100 WINPR_ASSERT(width);
101 WINPR_ASSERT(height);
102
103 *width = 0;
104 *height = 0;
105
106 // NOLINTNEXTLINE(concurrency-mt-unsafe)
107 const char* env = getenv(MUTTER_VIRTUAL_ENV);
108 if (!env)
109 return FALSE;
110
111 errno = 0;
112
113 char* next = nullptr;
114 unsigned long w = strtoul(env, &next, 0);
115 if ((errno != 0) || (w < 64) || (w > 8192))
116 return FALSE;
117 if (!next || (*next != 'x'))
118 return FALSE;
119
120 unsigned long h = strtoul(&next[1], nullptr, 0);
121 if ((errno != 0) || (h < 64) || (h > 8192))
122 return FALSE;
123
124 *width = WINPR_ASSERTING_INT_CAST(UINT32, w);
125 *height = WINPR_ASSERTING_INT_CAST(UINT32, h);
126 return TRUE;
127}
128
129/* cursor metadata: position, hot spot and, when the shape changes, a bitmap;
130 * Mutter's bitmaps are up to 384x384 */
131#define MUTTER_CURSOR_META_SIZE(width, height) \
132 (sizeof(struct spa_meta_cursor) + sizeof(struct spa_meta_bitmap) + 4ull * (width) * (height))
133#define MUTTER_CURSOR_META_MAX 384u
134
135/* the largest pointer sent to the clients */
136#define MUTTER_SHADOW_CURSOR_MAX 96u
137
138/* how long after a client's own pointer motion the server leaves the pointer
139 * position to that client */
140#define MUTTER_SHADOW_CURSOR_QUIET_MS 500u
141
142/* damage rectangles taken from one buuffer */
143#define MUTTER_MAX_DAMAGE 16u
144
145/* how long Mutter may take to offer its API, announce the stream, and give
146 * the stream a format */
147#define MUTTER_NAME_TIMEOUT_MS 10000u
148#define MUTTER_STREAM_TIMEOUT_MS 5000u
149#define MUTTER_FORMAT_TIMEOUT_MS 5000u
150/* after Mutter's node goes away, how long a Closed may take to follow it */
151#define MUTTER_STREAM_GONE_GRACE_MS 250u
152
153/* the messages of the clipboard, to the subsystem thread and its data thread */
154#define MUTTER_MSG_CLIPBOARD_OFFER 0x10001
155#define MUTTER_MSG_CLIPBOARD_READ 0x10002
156#define MUTTER_MSG_CLIPBOARD_PROVIDE 0x10003
157#define MUTTER_MSG_CLIPBOARD_WRITE_DONE 0x10004
158/* a transfer gives up when its application stays silent for this long */
159#define MUTTER_CLIPBOARD_STALL_MS 5000
160#define MUTTER_CLIPBOARD_CALL_USEC (2ull * 1000ull * 1000ull)
161/* the mime types of a selection that the session offers */
162#define MUTTER_CLIPBOARD_MAX_MIMES 64
163
164/* What the clients hear: 48 kHz, 2 channels, 16 bits; PipeWire converts what
165 * the sink plays to it. The build may have no resampler, so every format
166 * offered has that rate and channel count, and 16 bits a sample: rdpsnd sizes
167 * its buffers from the bits and channels of the first one. Opus first, so that
168 * a client that has it takes it (96 kbit/s instead of 1.5 Mbit/s for PCM). Its
169 * block alignment is 1: rdpsnd pads every wave to a multiple of it with zeros,
170 * and an Opus packet with zeros after it no longer decodes. */
171#define MUTTER_AUDIO_RATE 48000u
172#define MUTTER_AUDIO_CHANNELS 2u
173#define MUTTER_AUDIO_FRAME_BYTES (MUTTER_AUDIO_CHANNELS * 2ull)
174/* samples are sent in messages of 20 ms */
175#define MUTTER_AUDIO_MESSAGE_FRAMES (MUTTER_AUDIO_RATE / 50u)
176/* how often the sound statistics are logged (debug level) */
177#define MUTTER_AUDIO_LOG_MS 5000u
178
179/* xkbcommon's core modifiers: Lock is Caps Lock, Mod2 is Num Lock in every
180 * stock keymap */
181#define MUTTER_MOD_LOCK (1u << 1)
182#define MUTTER_MOD_NUM (1u << 4)
183
184/* DisplayConfig.GetCurrentState: a physical monitor's connector and the size of
185 * its current mode */
186typedef struct
187{
188 char connector[MUTTER_SHADOW_CONNECTOR_SIZE];
189 INT32 width;
190 INT32 height;
191} MUTTER_MODE;
192
193/* DisplayConfig.GetCurrentState: a logical monitor, where it is in the layout */
194typedef struct
195{
196 INT32 x;
197 INT32 y;
198 double scale;
199 uint32_t transform;
200 int primary;
201 char connector[MUTTER_SHADOW_CONNECTOR_SIZE];
202} MUTTER_LOGICAL;
203
204static const AUDIO_FORMAT mutter_audio_offered_formats[] = {
205 { WAVE_FORMAT_OPUS, MUTTER_AUDIO_CHANNELS, MUTTER_AUDIO_RATE, 12000, 1, 16, 0, nullptr },
206 { WAVE_FORMAT_PCM, MUTTER_AUDIO_CHANNELS, MUTTER_AUDIO_RATE,
207 (MUTTER_AUDIO_RATE * MUTTER_AUDIO_FRAME_BYTES), MUTTER_AUDIO_FRAME_BYTES, 16, 0, nullptr },
208};
209
210/* ------------------------------------------------------------------------ */
211/* D-Bus */
212/* ------------------------------------------------------------------------ */
213
214WINPR_ATTR_NODISCARD
215static BOOL mutter_wait_for_name(sd_bus* bus, const char* name, UINT64 timeoutMs)
216{
217 WINPR_ASSERT(bus);
218 WINPR_ASSERT(name);
219
220 const UINT64 start = GetTickCount64();
221 BOOL logged = FALSE;
222
223 for (;;)
224 {
225 sd_bus_error err = SD_BUS_ERROR_NULL;
226 sd_bus_message* reply = nullptr;
227 int hasOwner = 0;
228
229 const int called =
230 sd_bus_call_method(bus, "org.freedesktop.DBus", "/org/freedesktop/DBus",
231 "org.freedesktop.DBus", "NameHasOwner", &err, &reply, "s", name);
232 const BOOL owned =
233 (called >= 0) && (sd_bus_message_read(reply, "b", &hasOwner) >= 0) && hasOwner;
234 sd_bus_message_unref(reply);
235 sd_bus_error_free(&err);
236 if (owned)
237 return TRUE;
238
239 if (GetTickCount64() - start > timeoutMs)
240 return FALSE;
241 if (!logged)
242 {
243 WLog_INFO(TAG, "waiting for %s on the session bus", name);
244 logged = TRUE;
245 }
246 Sleep(200);
247 }
248}
249
250/* Calls a method that returns one object path; *out gets a copy */
251WINPR_ATTR_NODISCARD
252static BOOL mutter_call_for_path(sd_bus* bus, sd_bus_message* call, const char* what, char** out)
253{
254 WINPR_ASSERT(bus);
255 WINPR_ASSERT(call);
256 WINPR_ASSERT(what);
257 WINPR_ASSERT(out);
258
259 sd_bus_error err = SD_BUS_ERROR_NULL;
260 sd_bus_message* reply = nullptr;
261 const char* path = nullptr;
262
263 const int called = sd_bus_call(bus, call, 0, &err, &reply);
264 if (called < 0)
265 {
266 char buffer[128] = WINPR_C_ARRAY_INIT;
267 WLog_ERR(TAG, "%s failed: %s", what,
268 err.message ? err.message : winpr_strerror(-called, buffer, sizeof(buffer)));
269 sd_bus_error_free(&err);
270 return FALSE;
271 }
272
273 if ((sd_bus_message_read(reply, "o", &path) >= 0) && path)
274 *out = _strdup(path);
275 sd_bus_message_unref(reply);
276 return *out != nullptr;
277}
278
279/* Reads a dictionary, a{sv}: the value of key (of type 'b', 'u' or 's') goes to value
280 * when the key is there, the other entries are skipped */
281WINPR_ATTR_NODISCARD
282static BOOL mutter_read_dict(sd_bus_message* msg, const char* key, char type, void* value)
283{
284 WINPR_ASSERT(msg);
285 WINPR_ASSERT(key);
286 WINPR_ASSERT(value);
287
288 const char contents[2] = { type, '\0' };
289
290 if (sd_bus_message_enter_container(msg, 'a', "{sv}") < 0)
291 return FALSE;
292
293 for (;;)
294 {
295 const int entered = sd_bus_message_enter_container(msg, 'e', "sv");
296 if (entered < 0)
297 return FALSE;
298 if (entered == 0)
299 break;
300
301 const char* name = nullptr;
302 if (sd_bus_message_read(msg, "s", &name) < 0)
303 return FALSE;
304 if (name && (strcmp(name, key) == 0))
305 {
306 if (sd_bus_message_read(msg, "v", contents, value) < 0)
307 return FALSE;
308 }
309 else if (sd_bus_message_skip(msg, "v") < 0)
310 return FALSE;
311
312 if (sd_bus_message_exit_container(msg) < 0)
313 return FALSE;
314 }
315
316 return sd_bus_message_exit_container(msg) >= 0;
317}
318
319/* Reads one physical monitor, ((ssss)a(siiddada{sv})a{sv}): its connector and the
320 * size of its current mode. *more is FALSE after the last one. */
321WINPR_ATTR_NODISCARD
322static BOOL mutter_read_monitor(sd_bus_message* reply, MUTTER_MODE* mode, BOOL* more)
323{
324 WINPR_ASSERT(reply);
325 WINPR_ASSERT(mode);
326 WINPR_ASSERT(more);
327
328 const int entered = sd_bus_message_enter_container(reply, 'r', "(ssss)a(siiddada{sv})a{sv}");
329 if (entered < 0)
330 return FALSE;
331 *more = (entered > 0);
332 if (!*more)
333 return TRUE;
334
335 const char* connector = nullptr;
336 if (sd_bus_message_read(reply, "(ssss)", &connector, nullptr, nullptr, nullptr) < 0)
337 return FALSE;
338 (void)_snprintf(mode->connector, sizeof(mode->connector), "%s", connector ? connector : "");
339
340 if (sd_bus_message_enter_container(reply, 'a', "(siiddada{sv})") < 0)
341 return FALSE;
342 for (;;)
343 {
344 const int modeEntered = sd_bus_message_enter_container(reply, 'r', "siiddada{sv}");
345 if (modeEntered < 0)
346 return FALSE;
347 if (modeEntered == 0)
348 break;
349
350 int32_t width = 0;
351 int32_t height = 0;
352 int current = 0;
353 if ((sd_bus_message_read(reply, "siidd", nullptr, &width, &height, nullptr, nullptr) < 0) ||
354 (sd_bus_message_skip(reply, "ad") < 0) ||
355 !mutter_read_dict(reply, "is-current", 'b', &current) ||
356 (sd_bus_message_exit_container(reply) < 0))
357 return FALSE;
358
359 if (current)
360 {
361 mode->width = width;
362 mode->height = height;
363 }
364 }
365
366 return (sd_bus_message_exit_container(reply) >= 0) && /* the modes */
367 (sd_bus_message_skip(reply, "a{sv}") >= 0) &&
368 (sd_bus_message_exit_container(reply) >= 0);
369}
370
371/* Reads one logical monitor, (iiduba(ssss)a{sv}): where it is and its (first)
372 * connector. *more is FALSE after the last one. */
373WINPR_ATTR_NODISCARD
374static BOOL mutter_read_logical(sd_bus_message* reply, MUTTER_LOGICAL* logical, BOOL* more)
375{
376 WINPR_ASSERT(reply);
377 WINPR_ASSERT(logical);
378 WINPR_ASSERT(more);
379
380 const int entered = sd_bus_message_enter_container(reply, 'r', "iiduba(ssss)a{sv}");
381 if (entered < 0)
382 return FALSE;
383 *more = (entered > 0);
384 if (!*more)
385 return TRUE;
386
387 int32_t x = 0;
388 int32_t y = 0;
389 double scale = 1.0;
390 uint32_t transform = 0;
391 int primary = 0;
392 if ((sd_bus_message_read(reply, "iidub", &x, &y, &scale, &transform, &primary) < 0) ||
393 (sd_bus_message_enter_container(reply, 'a', "(ssss)") < 0))
394 return FALSE;
395
396 const char* connector = nullptr;
397 for (;;)
398 {
399 const char* name = nullptr;
400 const int read = sd_bus_message_read(reply, "(ssss)", &name, nullptr, nullptr, nullptr);
401 if (read < 0)
402 return FALSE;
403 if (read == 0)
404 break;
405 if (!connector)
406 connector = name;
407 }
408
409 logical->x = x;
410 logical->y = y;
411 logical->scale = (scale > 0.0) ? scale : 1.0;
412 logical->transform = transform;
413 logical->primary = primary;
414 (void)_snprintf(logical->connector, sizeof(logical->connector), "%s",
415 connector ? connector : "");
416
417 return (sd_bus_message_exit_container(reply) >= 0) && /* the connectors */
418 (sd_bus_message_skip(reply, "a{sv}") >= 0) &&
419 (sd_bus_message_exit_container(reply) >= 0);
420}
421
422/* Reads GetCurrentState's reply: the physical monitors' modes, the logical
423 * monitors, and the layout mode */
424WINPR_ATTR_NODISCARD
425static BOOL mutter_read_state(sd_bus_message* reply, MUTTER_MODE* modes, size_t* nmodes,
426 MUTTER_LOGICAL* logical, size_t* nlogical, uint32_t* layoutMode)
427{
428 WINPR_ASSERT(reply);
429 WINPR_ASSERT(modes);
430 WINPR_ASSERT(nmodes);
431 WINPR_ASSERT(logical);
432 WINPR_ASSERT(nlogical);
433 WINPR_ASSERT(layoutMode);
434
435 if ((sd_bus_message_read(reply, "u", nullptr) < 0) ||
436 (sd_bus_message_enter_container(reply, 'a', "((ssss)a(siiddada{sv})a{sv})") < 0))
437 return FALSE;
438 for (;;)
439 {
440 MUTTER_MODE mode = WINPR_C_ARRAY_INIT;
441 BOOL more = FALSE;
442 if (!mutter_read_monitor(reply, &mode, &more))
443 return FALSE;
444 if (!more)
445 break;
446 if (*nmodes < MUTTER_SHADOW_MAX_MONITORS)
447 modes[(*nmodes)++] = mode;
448 }
449 if ((sd_bus_message_exit_container(reply) < 0) ||
450 (sd_bus_message_enter_container(reply, 'a', "(iiduba(ssss)a{sv})") < 0))
451 return FALSE;
452
453 for (;;)
454 {
455 MUTTER_LOGICAL entry = WINPR_C_ARRAY_INIT;
456 BOOL more = FALSE;
457 if (!mutter_read_logical(reply, &entry, &more))
458 return FALSE;
459 if (!more)
460 break;
461 if (*nlogical < MUTTER_SHADOW_MAX_MONITORS)
462 logical[(*nlogical)++] = entry;
463 }
464 if (sd_bus_message_exit_container(reply) < 0)
465 return FALSE;
466
467 return mutter_read_dict(reply, "layout-mode", 'u', layoutMode);
468}
469
470/* The logical monitors, in Mutter's layout, primary first. connectors gets
471 * each one's (first) connector. Returns the count. */
472WINPR_ATTR_NODISCARD
473static UINT32 mutter_list_monitors(sd_bus* bus, MONITOR_DEF* monitors,
474 char (*connectors)[MUTTER_SHADOW_CONNECTOR_SIZE], UINT32 max)
475{
476 WINPR_ASSERT(bus);
477 WINPR_ASSERT(monitors);
478
479 sd_bus_error err = SD_BUS_ERROR_NULL;
480 sd_bus_message* reply = nullptr;
481 MUTTER_MODE modes[MUTTER_SHADOW_MAX_MONITORS] = WINPR_C_ARRAY_INIT;
482 MUTTER_LOGICAL logical[MUTTER_SHADOW_MAX_MONITORS] = WINPR_C_ARRAY_INIT;
483 size_t nmodes = 0;
484 size_t nlogical = 0;
485 uint32_t layoutMode = 1; /* logical: sizes are divided by the scale */
486 UINT32 count = 0;
487
488 const int called =
489 sd_bus_call_method(bus, DC_NAME, DC_PATH, DC_NAME, "GetCurrentState", &err, &reply, "");
490 if (called < 0)
491 {
492 char buffer[128] = WINPR_C_ARRAY_INIT;
493 WLog_WARN(TAG, "GetCurrentState failed: %s",
494 err.message ? err.message : winpr_strerror(-called, buffer, sizeof(buffer)));
495 sd_bus_error_free(&err);
496 return 0;
497 }
498
499 const BOOL parsed = mutter_read_state(reply, modes, &nmodes, logical, &nlogical, &layoutMode);
500 sd_bus_message_unref(reply);
501 if (!parsed)
502 WLog_WARN(TAG, "GetCurrentState: unexpected reply");
503
504 /* primary first, then Mutter's order */
505 for (int pass = 0; pass < 2; pass++)
506 {
507 for (size_t x = 0; (x < nlogical) && (count < max); x++)
508 {
509 if ((pass == 0) != (logical[x].primary != 0))
510 continue;
511
512 INT32 width = 0;
513 INT32 height = 0;
514 for (size_t m = 0; m < nmodes; m++)
515 {
516 if (strcmp(modes[m].connector, logical[x].connector) == 0)
517 {
518 width = modes[m].width;
519 height = modes[m].height;
520 }
521 }
522 if ((width <= 0) || (height <= 0))
523 continue;
524
525 /* 90 and 270 degrees, flipped or not */
526 if (logical[x].transform & 1)
527 {
528 const INT32 tmp = width;
529 width = height;
530 height = tmp;
531 }
532 if (layoutMode == 1)
533 {
534 width = (INT32)(width / logical[x].scale + 0.5);
535 height = (INT32)(height / logical[x].scale + 0.5);
536 }
537
538 MONITOR_DEF* monitor = &monitors[count];
539 monitor->left = logical[x].x;
540 monitor->top = logical[x].y;
541 monitor->right = logical[x].x + width - 1;
542 monitor->bottom = logical[x].y + height - 1;
543 monitor->flags = logical[x].primary ? 1 : 0;
544 if (connectors)
545 (void)_snprintf(connectors[count], MUTTER_SHADOW_CONNECTOR_SIZE, "%s",
546 logical[x].connector);
547 count++;
548 }
549 }
550
551 return count;
552}
553
554WINPR_ATTR_NODISCARD
555static UINT32 mutter_shadow_enum_monitors(MONITOR_DEF* monitors, UINT32 maxMonitors)
556{
557 WINPR_ASSERT(monitors);
558
559 UINT32 width = 0;
560 UINT32 height = 0;
561 if ((maxMonitors > 0) && mutter_virtual_size(&width, &height))
562 {
563 /* the monitor this server will add: a GNOME session may have none yet */
564 monitors[0] = (MONITOR_DEF){ 0, 0, (INT32)width - 1, (INT32)height - 1, 1 };
565 return 1;
566 }
567
568 sd_bus* bus = nullptr;
569 const int opened = sd_bus_open_user(&bus);
570 if (opened < 0)
571 {
572 char buffer[128] = WINPR_C_ARRAY_INIT;
573 WLog_WARN(TAG, "cannot connect to the session bus: %s",
574 winpr_strerror(-opened, buffer, sizeof(buffer)));
575 return 0;
576 }
577
578 const UINT32 count = mutter_list_monitors(bus, monitors, nullptr, maxMonitors);
579 WLog_DBG(TAG, "%" PRIu32 " monitors", count);
580 sd_bus_flush_close_unref(bus);
581 return count;
582}
583
584WINPR_ATTR_NODISCARD
585static int mutter_on_stream_added(sd_bus_message* msg, void* data,
586 WINPR_ATTR_UNUSED sd_bus_error* err)
587{
588 mutterShadowSubsystem* mutter = (mutterShadowSubsystem*)data;
589 WINPR_ASSERT(msg);
590 WINPR_ASSERT(mutter);
591
592 uint32_t node = 0;
593 if (sd_bus_message_read(msg, "u", &node) >= 0)
594 {
595 mutter->nodeId = node;
596 mutter->haveNode = TRUE;
597 WLog_DBG(TAG, "PipeWire node %" PRIu32, mutter->nodeId);
598 if (mutter->awaitNode)
599 {
600 mutter->awaitNode = FALSE;
601 mutter->reconnect = TRUE;
602 }
603 }
604 return 0;
605}
606
607WINPR_ATTR_NODISCARD
608static int mutter_on_closed(WINPR_ATTR_UNUSED sd_bus_message* msg, void* data,
609 WINPR_ATTR_UNUSED sd_bus_error* err)
610{
611 mutterShadowSubsystem* mutter = (mutterShadowSubsystem*)data;
612 WINPR_ASSERT(mutter);
613
614 mutter->closed = TRUE;
615 return 0;
616}
617
618/* GNOME Shell left the session bus (it ended, or crashed): the sessions went with it */
619WINPR_ATTR_NODISCARD
620static int mutter_on_name_owner_changed(sd_bus_message* msg, void* data,
621 WINPR_ATTR_UNUSED sd_bus_error* err)
622{
623 mutterShadowSubsystem* mutter = (mutterShadowSubsystem*)data;
624 WINPR_ASSERT(msg);
625 WINPR_ASSERT(mutter);
626
627 const char* name = nullptr;
628 const char* oldOwner = nullptr;
629 const char* newOwner = nullptr;
630 if ((sd_bus_message_read(msg, "sss", &name, &oldOwner, &newOwner) >= 0) && newOwner &&
631 (newOwner[0] == '\0'))
632 {
633 mutter->shellGone = TRUE;
634 mutter->closed = TRUE;
635 }
636 return 0;
637}
638
639/* The stream's "mapping-id" parameter names the libei region of its monitor */
640static void mutter_read_stream_parameters(mutterShadowSubsystem* mutter)
641{
642 WINPR_ASSERT(mutter);
643
644 sd_bus_error err = SD_BUS_ERROR_NULL;
645 sd_bus_message* reply = nullptr;
646 const char* mappingId = nullptr;
647
648 const int got = sd_bus_get_property(mutter->bus, SC_NAME, mutter->streamPath, SC_STREAM_IFACE,
649 "Parameters", &err, &reply, "a{sv}");
650 if (got < 0)
651 {
652 char buffer[128] = WINPR_C_ARRAY_INIT;
653 WLog_DBG(TAG, "no stream parameters: %s",
654 err.message ? err.message : winpr_strerror(-got, buffer, sizeof(buffer)));
655 sd_bus_error_free(&err);
656 return;
657 }
658
659 if (mutter_read_dict(reply, "mapping-id", 's', (void*)(&mappingId)) && mappingId)
660 {
661 /* the client threads look it up when they move the pointer */
662 char* copy = _strdup(mappingId);
663 EnterCriticalSection(&mutter->eiLock);
664 free(mutter->mappingId);
665 mutter->mappingId = copy;
666 LeaveCriticalSection(&mutter->eiLock);
667 }
668 sd_bus_message_unref(reply);
669}
670
671/* A stream of the monitor, in the screen cast session, with the cursor drawn
672 * into the picture.
673 *
674 * With the cursor as metadata instead (the clients would draw it, with no lag),
675 * Mutter 50 records a cursor frame at each stage update and skips the picture
676 * that follows it as "too early": on a stream that has just started, the
677 * stage may keep updating and no picture comes at all, neither the first one
678 * nor later changes (MUTTER_DEBUG=screen-cast: no full frame and about a
679 * thousand skipped in 10 s). Drawn in, there are no cursor frames to skip. */
680WINPR_ATTR_NODISCARD
681static BOOL mutter_record_monitor(mutterShadowSubsystem* mutter, const char* connector)
682{
683 WINPR_ASSERT(mutter);
684 WINPR_ASSERT(connector);
685
686 sd_bus_message* call = nullptr;
687 BOOL rc = FALSE;
688
689 if (mutter->virtualMonitor)
690 {
691 /* a monitor of its own: Mutter records it directly, with no "too early"
692 * skips, so the clients draw the cursor (metadata, no lag) */
693 if ((sd_bus_message_new_method_call(mutter->bus, &call, SC_NAME, mutter->scSessionPath,
694 SC_SESSION_IFACE, "RecordVirtual") < 0) ||
695 (sd_bus_message_append(call, "a{sv}", 1, "cursor-mode", "u",
696 (uint32_t)MUTTER_CURSOR_MODE_METADATA) < 0))
697 goto out;
698 WLog_DBG(TAG, "ScreenCast.Session.RecordVirtual(%" PRIu32 "x%" PRIu32 ")",
699 mutter->virtualWidth, mutter->virtualHeight);
700 if (!mutter_call_for_path(mutter->bus, call, "ScreenCast.Session.RecordVirtual",
701 &mutter->streamPath))
702 goto out;
703 }
704 else
705 {
706 if ((sd_bus_message_new_method_call(mutter->bus, &call, SC_NAME, mutter->scSessionPath,
707 SC_SESSION_IFACE, "RecordMonitor") < 0) ||
708 (sd_bus_message_append(call, "sa{sv}", connector, 1, "cursor-mode", "u",
709 (uint32_t)MUTTER_CURSOR_MODE_EMBEDDED) < 0))
710 goto out;
711 WLog_DBG(TAG, "ScreenCast.Session.RecordMonitor('%s')", connector);
712 if (!mutter_call_for_path(mutter->bus, call, "ScreenCast.Session.RecordMonitor",
713 &mutter->streamPath))
714 goto out;
715 }
716
717 if (sd_bus_match_signal(mutter->bus, &mutter->streamAddedSlot, SC_NAME, mutter->streamPath,
718 SC_STREAM_IFACE, "PipeWireStreamAdded", mutter_on_stream_added,
719 mutter) < 0)
720 {
721 WLog_ERR(TAG, "cannot watch the stream's signals");
722 goto out;
723 }
724 rc = TRUE;
725out:
726 sd_bus_message_unref(call);
727 return rc;
728}
729static void mutter_clipboard_enable(mutterShadowSubsystem* mutter);
730
731WINPR_ATTR_NODISCARD
732static BOOL mutter_setup_sessions(mutterShadowSubsystem* mutter, const char* connector)
733{
734 WINPR_ASSERT(mutter);
735 WINPR_ASSERT(connector);
736
737 sd_bus_error err = SD_BUS_ERROR_NULL;
738 sd_bus_message* call = nullptr;
739 sd_bus_message* reply = nullptr;
740 char* sessionId = nullptr;
741 BOOL rc = FALSE;
742
743 /* 1. the remote desktop session */
744 if (sd_bus_message_new_method_call(mutter->bus, &call, RD_NAME, RD_PATH, RD_NAME,
745 "CreateSession") < 0)
746 goto out;
747 WLog_DBG(TAG, "RemoteDesktop.CreateSession");
748 if (!mutter_call_for_path(mutter->bus, call, "RemoteDesktop.CreateSession",
749 &mutter->rdSessionPath))
750 goto out;
751 call = sd_bus_message_unref(call);
752
753 const int gotId = sd_bus_get_property_string(mutter->bus, RD_NAME, mutter->rdSessionPath,
754 RD_SESSION_IFACE, "SessionId", &err, &sessionId);
755 if (gotId < 0)
756 {
757 char buffer[128] = WINPR_C_ARRAY_INIT;
758 WLog_ERR(TAG, "no SessionId: %s",
759 err.message ? err.message : winpr_strerror(-gotId, buffer, sizeof(buffer)));
760 goto out;
761 }
762
763 /* 2. the screen cast session, tied to it */
764 if ((sd_bus_message_new_method_call(mutter->bus, &call, SC_NAME, SC_PATH, SC_NAME,
765 "CreateSession") < 0) ||
766 (sd_bus_message_append(call, "a{sv}", 1, "remote-desktop-session-id", "s", sessionId) < 0))
767 goto out;
768 WLog_DBG(TAG, "ScreenCast.CreateSession");
769 if (!mutter_call_for_path(mutter->bus, call, "ScreenCast.CreateSession",
770 &mutter->scSessionPath))
771 goto out;
772 call = sd_bus_message_unref(call);
773
774 /* 3. the monitor */
775 if (!mutter_record_monitor(mutter, connector))
776 goto out;
777
778 if ((sd_bus_match_signal(mutter->bus, &mutter->closedSlot, RD_NAME, mutter->rdSessionPath,
779 RD_SESSION_IFACE, "Closed", mutter_on_closed, mutter) < 0) ||
780 (sd_bus_add_match(mutter->bus, &mutter->ownerSlot,
781 "type='signal',sender='org.freedesktop.DBus',"
782 "path='/org/freedesktop/DBus',interface='org.freedesktop.DBus',"
783 "member='NameOwnerChanged',arg0='" RD_NAME "'",
784 mutter_on_name_owner_changed, mutter) < 0))
785 {
786 WLog_ERR(TAG, "cannot watch the stream's signals");
787 goto out;
788 }
789
790 /* 4. start, then wait for the PipeWire node */
791 WLog_DBG(TAG, "RemoteDesktop.Session.Start");
792 const int started = sd_bus_call_method(mutter->bus, RD_NAME, mutter->rdSessionPath,
793 RD_SESSION_IFACE, "Start", &err, nullptr, "");
794 if (started < 0)
795 {
796 char buffer[128] = WINPR_C_ARRAY_INIT;
797 WLog_ERR(TAG, "RemoteDesktop.Session.Start failed: %s",
798 err.message ? err.message : winpr_strerror(-started, buffer, sizeof(buffer)));
799 goto out;
800 }
801 mutter->started = TRUE;
802
803 const UINT64 start = GetTickCount64();
804 while (!mutter->haveNode)
805 {
806 const int processed = sd_bus_process(mutter->bus, nullptr);
807 if (processed < 0)
808 goto out;
809 if (processed > 0)
810 continue;
811 if (GetTickCount64() - start > MUTTER_STREAM_TIMEOUT_MS)
812 {
813 WLog_ERR(TAG, "Mutter announced no PipeWire stream");
814 goto out;
815 }
816 (void)sd_bus_wait(mutter->bus, 100ull * 1000ull);
817 }
818 mutter_read_stream_parameters(mutter);
819
820 /* 5. input */
821 int fd = -1;
822 const int connected =
823 sd_bus_call_method(mutter->bus, RD_NAME, mutter->rdSessionPath, RD_SESSION_IFACE,
824 "ConnectToEIS", &err, &reply, "a{sv}", 0);
825 if ((connected < 0) || (sd_bus_message_read(reply, "h", &fd) < 0))
826 {
827 WLog_WARN(TAG, "ConnectToEIS failed: %s: the clients can only watch",
828 err.message ? err.message : "bad reply");
829 }
830 else
831 {
832 /* the reply owns the fd */
833 fd = fcntl(fd, F_DUPFD_CLOEXEC, 3);
834 mutter->ei = ei_new_sender(mutter);
835 if (mutter->ei)
836 {
837 ei_configure_name(mutter->ei, "FreeRDP shadow server");
838 if (ei_setup_backend_fd(mutter->ei, fd) != 0)
839 {
840 WLog_WARN(TAG, "libei setup failed: the clients can only watch");
841 mutter->ei = ei_unref(mutter->ei);
842 }
843 }
844 else
845 close(fd);
846 }
847
848 mutter_clipboard_enable(mutter);
849
850 rc = TRUE;
851out:
852 sd_bus_error_free(&err);
853 sd_bus_message_unref(reply);
854 sd_bus_message_unref(call);
855 free(sessionId);
856 return rc;
857}
858
859/* ------------------------------------------------------------------------ */
860/* the cursor */
861/* ------------------------------------------------------------------------ */
862
863static void mutter_cursor_msg_free(UINT32 id, SHADOW_MSG_OUT* msg)
864{
865 if (msg && (id == SHADOW_MSG_OUT_POINTER_ALPHA_UPDATE_ID))
866 {
868 free(shape->xorMaskData);
869 free(shape->andMaskData);
870 }
871 free(msg);
872}
873
874WINPR_ATTR_NODISCARD
875static UINT32 mutter_cursor_hash(const BYTE* pixels, UINT32 width, UINT32 height, UINT32 xHot,
876 UINT32 yHot)
877{
878 WINPR_ASSERT(pixels);
879
880 /* FNV-1a over the size, the hot spot and the pixels */
881 UINT32 hash = 2166136261u;
882 const UINT32 header[] = { width, height, xHot, yHot };
883 const BYTE* parts[] = { (const BYTE*)header, pixels };
884 const size_t sizes[] = { sizeof(header), 4ull * width * height };
885 for (size_t p = 0; p < ARRAYSIZE(parts); p++)
886 {
887 for (size_t x = 0; x < sizes[p]; x++)
888 {
889 hash ^= parts[p][x];
890 hash *= 16777619u;
891 }
892 }
893 return hash;
894}
895
896static void mutter_cursor_send_shape(mutterShadowSubsystem* mutter, const BYTE* pixels,
897 UINT32 width, UINT32 height, UINT32 xHot, UINT32 yHot)
898{
899 WINPR_ASSERT(mutter);
900 WINPR_ASSERT(pixels);
901
902 const UINT32 hash = mutter_cursor_hash(pixels, width, height, xHot, yHot);
903 WLog_DBG(TAG, "pointer shape %" PRIu32 "x%" PRIu32 ", resend %d", width, height,
904 mutter->cursorResend);
905 if (mutter->cursorKnown && !mutter->cursorResend && (hash == mutter->cursorHash))
906 return;
907
909 calloc(1, sizeof(SHADOW_MSG_OUT_POINTER_ALPHA_UPDATE));
910 if (!msg)
911 return;
912
913 msg->xHot = xHot;
914 msg->yHot = yHot;
915 msg->width = width;
916 msg->height = height;
917 /* Mutter's bitmaps are premultiplied RGBA */
918 if (shadow_subsystem_pointer_convert_alpha_pointer_data_to_format(pixels, PIXEL_FORMAT_RGBA32,
919 TRUE, width, height, msg) < 0)
920 {
921 free(msg);
922 return;
923 }
924
925 msg->common.Free = mutter_cursor_msg_free;
926 if (shadow_client_boardcast_msg(mutter->common.server, nullptr,
927 SHADOW_MSG_OUT_POINTER_ALPHA_UPDATE_ID, &msg->common,
928 nullptr) >= 0)
929 {
930 mutter->cursorHash = hash;
931 mutter->cursorKnown = TRUE;
932 mutter->cursorResend = FALSE;
933 }
934}
935
936static void mutter_cursor_send_position(mutterShadowSubsystem* mutter, INT32 x, INT32 y)
937{
938 WINPR_ASSERT(mutter);
939
940 if ((x == mutter->cursorX) && (y == mutter->cursorY))
941 return;
942 mutter->cursorX = x;
943 mutter->cursorY = y;
944
945 /* while a client moves the pointer, it already knows where it is */
946 EnterCriticalSection(&mutter->eiLock);
947 const UINT64 input = mutter->pointerInput;
948 LeaveCriticalSection(&mutter->eiLock);
949 if (GetTickCount64() - input < MUTTER_SHADOW_CURSOR_QUIET_MS)
950 return;
951
952 if ((x < 0) || (y < 0) || ((UINT32)x >= mutter->width) || ((UINT32)y >= mutter->height))
953 return;
954
955 mutter->common.pointerX = (UINT32)x;
956 mutter->common.pointerY = (UINT32)y;
957
960 if (!msg)
961 return;
962 msg->xPos = (UINT32)x;
963 msg->yPos = (UINT32)y;
964 msg->common.Free = mutter_cursor_msg_free;
965 /* the message is freed with its last reference, also when no client took it */
966 if (shadow_client_boardcast_msg(mutter->common.server, nullptr,
967 SHADOW_MSG_OUT_POINTER_POSITION_UPDATE_ID, &msg->common,
968 nullptr) < 0)
969 WLog_DBG(TAG, "the pointer position was not sent");
970}
971
972/* Mutter sends a shape only when it changes, so a client that connects later would
973 * have no pointer until something else changed it */
974static void mutter_cursor_remember(mutterShadowSubsystem* mutter, const BYTE* pixels, UINT32 width,
975 UINT32 height, UINT32 xHot, UINT32 yHot)
976{
977 WINPR_ASSERT(mutter);
978 WINPR_ASSERT(pixels);
979
980 const size_t size = 4ull * width * height;
981 BYTE* copy = malloc(size);
982 if (!copy)
983 return;
984 memcpy(copy, pixels, size);
985
986 free(mutter->cursorPixels);
987 mutter->cursorPixels = copy;
988 mutter->cursorWidth = width;
989 mutter->cursorHeight = height;
990 mutter->cursorXHot = xHot;
991 mutter->cursorYHot = yHot;
992}
993
994/* Until Mutter has sent a shape, which it may not do for long: an arrow, so a client is
995 * never left with no pointer */
996static void mutter_cursor_send_default(mutterShadowSubsystem* mutter)
997{
998 static const char* const arrow[] = { "X ", "XX ", "X.X ", "X..X ",
999 "X...X ", "X....X ", "X.....X ", "X......X ",
1000 "X.......X ", "X........X ", "X.....XXXXX", "X..X..X ",
1001 "X.X X..X ", "XX X..X ", "X X..X ", " X..X ",
1002 " XX " };
1003 enum
1004 {
1005 ARROW_WIDTH = 11,
1006 ARROW_HEIGHT = ARRAYSIZE(arrow)
1007 };
1008 BYTE pixels[4ull * ARROW_WIDTH * ARROW_HEIGHT] = WINPR_C_ARRAY_INIT;
1009 for (size_t y = 0; y < ARROW_HEIGHT; y++)
1010 {
1011 for (size_t x = 0; x < ARROW_WIDTH; x++)
1012 {
1013 BYTE* pixel = &pixels[4ull * (y * ARROW_WIDTH + x)];
1014 if (arrow[y][x] == ' ')
1015 continue;
1016 const BYTE value = (arrow[y][x] == '.') ? 0xff : 0x00;
1017 pixel[0] = pixel[1] = pixel[2] = value;
1018 pixel[3] = 0xff;
1019 }
1020 }
1021 mutter_cursor_send_shape(mutter, pixels, ARROW_WIDTH, ARROW_HEIGHT, 0, 0);
1022}
1023
1024/* The cursor in a buffer's metadata: its position, and its shape when it changed */
1025static void mutter_cursor_update(mutterShadowSubsystem* mutter, struct spa_buffer* buf)
1026{
1027 WINPR_ASSERT(mutter);
1028 WINPR_ASSERT(buf);
1029
1030 struct spa_meta* meta = spa_buffer_find_meta(buf, SPA_META_Cursor);
1031 if (!meta || (meta->size < sizeof(struct spa_meta_cursor)))
1032 return;
1033
1034 const struct spa_meta_cursor* mc = (const struct spa_meta_cursor*)meta->data;
1035 if (mc->id == 0)
1036 return; /* no cursor in this buffer */
1037
1038 if (mc->bitmap_offset != 0)
1039 {
1040 if ((size_t)mc->bitmap_offset + sizeof(struct spa_meta_bitmap) > meta->size)
1041 return;
1042
1043 const struct spa_meta_bitmap* mb =
1044 SPA_PTROFF(mc, mc->bitmap_offset, const struct spa_meta_bitmap);
1045 if ((mb->format == 0) || (mb->offset == 0) || (mb->size.width == 0) ||
1046 (mb->size.height == 0))
1047 {
1048 /* Mutter sends an empty bitmap for a hidden pointer */
1049 const BYTE transparent[4] = WINPR_C_ARRAY_INIT;
1050 mutter_cursor_remember(mutter, transparent, 1, 1, 0, 0);
1051 mutter_cursor_send_shape(mutter, transparent, 1, 1, 0, 0);
1052 }
1053 else
1054 {
1055 const UINT32 width = mb->size.width;
1056 const UINT32 height = mb->size.height;
1057 const UINT32 stride = (mb->stride > 0) ? (UINT32)mb->stride : width * 4;
1058 if ((mb->format != SPA_VIDEO_FORMAT_RGBA) || (width > MUTTER_CURSOR_META_MAX) ||
1059 (height > MUTTER_CURSOR_META_MAX) || (stride < width * 4) ||
1060 ((size_t)mc->bitmap_offset + mb->offset + (size_t)stride * height > meta->size))
1061 {
1062 WLog_DBG(TAG,
1063 "unusable pointer bitmap (format %" PRIu32 ", %" PRIu32 "x%" PRIu32 ")",
1064 mb->format, width, height);
1065 }
1066 else
1067 {
1068 const BYTE* src = SPA_PTROFF(mb, mb->offset, const BYTE);
1069
1070 /* a pointer larger than the clients take is shrunk, nearest pixel */
1071 const UINT32 side = MAX(width, height);
1072 const UINT32 scaled = MIN(side, MUTTER_SHADOW_CURSOR_MAX);
1073 const UINT32 outW = MAX(1, width * scaled / side);
1074 const UINT32 outH = MAX(1, height * scaled / side);
1075 BYTE* pixels = calloc(4ull * outW, outH);
1076 if (pixels)
1077 {
1078 for (UINT32 y = 0; y < outH; y++)
1079 {
1080 const BYTE* row = src + (size_t)(y * side / scaled) * stride;
1081 for (UINT32 x = 0; x < outW; x++)
1082 memcpy(&pixels[4ull * (y * outW + x)], &row[4ull * (x * side / scaled)],
1083 4);
1084 }
1085 const UINT32 xHot =
1086 MIN((UINT32)MAX(mc->hotspot.x, 0) * scaled / side, outW - 1);
1087 const UINT32 yHot =
1088 MIN((UINT32)MAX(mc->hotspot.y, 0) * scaled / side, outH - 1);
1089 mutter_cursor_remember(mutter, pixels, outW, outH, xHot, yHot);
1090 mutter_cursor_send_shape(mutter, pixels, outW, outH, xHot, yHot);
1091 free(pixels);
1092 }
1093 }
1094 }
1095 }
1096
1097 mutter_cursor_send_position(mutter, mc->position.x, mc->position.y);
1098}
1099
1100/* ------------------------------------------------------------------------ */
1101/* PipeWire */
1102/* ------------------------------------------------------------------------ */
1103
1104static void mutter_set_monitor_size(mutterShadowSubsystem* mutter)
1105{
1106 WINPR_ASSERT(mutter);
1107
1108 rdpShadowSubsystem* subsystem = &mutter->common;
1109 MONITOR_DEF* monitor = &subsystem->monitors[subsystem->selectedMonitor];
1110
1111 /* the surface has the size of the frames */
1112 monitor->right = monitor->left + (INT32)mutter->width - 1;
1113 monitor->bottom = monitor->top + (INT32)mutter->height - 1;
1114
1115 MONITOR_DEF* virtualScreen = &subsystem->virtualScreen;
1116 *virtualScreen = subsystem->monitors[0];
1117 for (UINT32 x = 1; x < subsystem->numMonitors; x++)
1118 {
1119 const MONITOR_DEF* m = &subsystem->monitors[x];
1120 virtualScreen->left = MIN(virtualScreen->left, m->left);
1121 virtualScreen->top = MIN(virtualScreen->top, m->top);
1122 virtualScreen->right = MAX(virtualScreen->right, m->right);
1123 virtualScreen->bottom = MAX(virtualScreen->bottom, m->bottom);
1124 }
1125 virtualScreen->flags = 1;
1126}
1127
1128static void mutter_on_param_changed(void* data, uint32_t id, const struct spa_pod* param)
1129{
1130 mutterShadowSubsystem* mutter = (mutterShadowSubsystem*)data;
1131 WINPR_ASSERT(mutter);
1132
1133 struct spa_video_info_raw info = WINPR_C_ARRAY_INIT;
1134 uint8_t podBuffer[1024] = WINPR_C_ARRAY_INIT;
1135 struct spa_pod_builder b = SPA_POD_BUILDER_INIT(podBuffer, sizeof(podBuffer));
1136 const struct spa_pod* params[4] = WINPR_C_ARRAY_INIT;
1137
1138 if (!param || (id != SPA_PARAM_Format))
1139 return;
1140 if (spa_format_video_raw_parse(param, &info) < 0)
1141 {
1142 WLog_ERR(TAG, "cannot parse the stream format");
1143 return;
1144 }
1145
1146 const BOOL sized = mutter->haveFormat;
1147 const BOOL changed = (info.size.width != mutter->width) || (info.size.height != mutter->height);
1148 mutter->width = info.size.width;
1149 mutter->height = info.size.height;
1150 mutter->format =
1151 (info.format == SPA_VIDEO_FORMAT_BGRA) ? PIXEL_FORMAT_BGRA32 : PIXEL_FORMAT_BGRX32;
1152 mutter->haveFormat = TRUE;
1153 mutter->fullPaint = TRUE;
1154 WLog_INFO(TAG, "stream: %" PRIu32 "x%" PRIu32 " %s", mutter->width, mutter->height,
1155 (info.format == SPA_VIDEO_FORMAT_BGRA) ? "BGRA" : "BGRx");
1156
1157 if (sized && changed)
1158 {
1159 /* the monitor changed its mode: the clients get the new size */
1160 mutter_set_monitor_size(mutter);
1161 if (!shadow_screen_resize(mutter->common.server->screen))
1162 WLog_ERR(TAG, "cannot resize the screen to %" PRIu32 "x%" PRIu32, mutter->width,
1163 mutter->height);
1164 }
1165
1166 /* shared memory only (no dma-buf), with the damage and the cursor */
1167 const int32_t stride = (int32_t)mutter->width * 4;
1168 params[0] = spa_pod_builder_add_object(
1169 &b, SPA_TYPE_OBJECT_ParamBuffers, SPA_PARAM_Buffers, SPA_PARAM_BUFFERS_buffers,
1170 SPA_POD_CHOICE_RANGE_Int(4, 2, 16), SPA_PARAM_BUFFERS_blocks, SPA_POD_Int(1),
1171 SPA_PARAM_BUFFERS_size, SPA_POD_Int(stride * (int32_t)mutter->height),
1172 SPA_PARAM_BUFFERS_stride, SPA_POD_Int(stride), SPA_PARAM_BUFFERS_dataType,
1173 SPA_POD_CHOICE_FLAGS_Int((1 << SPA_DATA_MemFd) | (1 << SPA_DATA_MemPtr)));
1174 params[1] = spa_pod_builder_add_object(&b, SPA_TYPE_OBJECT_ParamMeta, SPA_PARAM_Meta,
1175 SPA_PARAM_META_type, SPA_POD_Id(SPA_META_Header),
1176 SPA_PARAM_META_size,
1177 SPA_POD_Int(sizeof(struct spa_meta_header)));
1178 params[2] = spa_pod_builder_add_object(
1179 &b, SPA_TYPE_OBJECT_ParamMeta, SPA_PARAM_Meta, SPA_PARAM_META_type,
1180 SPA_POD_Id(SPA_META_VideoDamage), SPA_PARAM_META_size,
1181 SPA_POD_CHOICE_RANGE_Int(sizeof(struct spa_meta_region) * MUTTER_MAX_DAMAGE,
1182 sizeof(struct spa_meta_region),
1183 sizeof(struct spa_meta_region) * MUTTER_MAX_DAMAGE));
1184 params[3] = spa_pod_builder_add_object(
1185 &b, SPA_TYPE_OBJECT_ParamMeta, SPA_PARAM_Meta, SPA_PARAM_META_type,
1186 SPA_POD_Id(SPA_META_Cursor), SPA_PARAM_META_size,
1187 SPA_POD_CHOICE_RANGE_Int(
1188 MUTTER_CURSOR_META_SIZE(64, 64), MUTTER_CURSOR_META_SIZE(1, 1),
1189 MUTTER_CURSOR_META_SIZE(MUTTER_CURSOR_META_MAX, MUTTER_CURSOR_META_MAX)));
1190 (void)pw_stream_update_params(mutter->stream, params, ARRAYSIZE(params));
1191}
1192
1193/* Copies one rectangle of a frame to the surface and marks it changed */
1194static void mutter_copy_rect(mutterShadowSubsystem* mutter, rdpShadowSurface* surface,
1195 const BYTE* src, UINT32 stride, INT32 x, INT32 y, INT32 width,
1196 INT32 height)
1197{
1198 WINPR_ASSERT(mutter);
1199 WINPR_ASSERT(surface);
1200 WINPR_ASSERT(src);
1201
1202 const INT32 maxW = (INT32)MIN(mutter->width, surface->width);
1203 const INT32 maxH = (INT32)MIN(mutter->height, surface->height);
1204 const INT32 left = MAX(x, 0);
1205 const INT32 top = MAX(y, 0);
1206 const INT32 right = MIN(x + width, maxW);
1207 const INT32 bottom = MIN(y + height, maxH);
1208 if ((right <= left) || (bottom <= top))
1209 return;
1210
1211 if (!freerdp_image_copy_no_overlap(surface->data, surface->format, surface->scanline,
1212 (UINT32)left, (UINT32)top, (UINT32)(right - left),
1213 (UINT32)(bottom - top), src, mutter->format, stride,
1214 (UINT32)left, (UINT32)top, nullptr, FREERDP_FLIP_NONE))
1215 {
1216 WLog_ERR(TAG, "cannot copy the frame to the surface");
1217 return;
1218 }
1219 const RECTANGLE_16 rect = { (UINT16)left, (UINT16)top, (UINT16)right, (UINT16)bottom };
1220 if (!region16_union_rect(&surface->invalidRegion, &surface->invalidRegion, &rect))
1221 WLog_ERR(TAG, "cannot mark the changed region");
1222}
1223
1224/* Mutter's damage metadata now and then leaves out a change: a window that opens or closes, the
1225 * clock in the top bar. Nothing repaints it later, so the client kept the old picture there.
1226 * Compare the buffer with the surface in tiles after the damage was copied, and copy the tiles that
1227 * still differ. When the damage was right, every tile is equal and nothing is copied. Returns the
1228 * tiles copied. */
1229static UINT32 mutter_repair_missed_damage(mutterShadowSubsystem* mutter, rdpShadowSurface* surface,
1230 const BYTE* src, UINT32 stride)
1231{
1232 WINPR_ASSERT(mutter);
1233 WINPR_ASSERT(surface);
1234 WINPR_ASSERT(src);
1235
1236 const UINT32 width = MIN(mutter->width, surface->width);
1237 const UINT32 height = MIN(mutter->height, surface->height);
1238 /* the same format is copied byte for byte; otherwise compare the colour (alpha is undefined
1239 * in BGRx) */
1240 const BOOL sameFormat = (mutter->format == surface->format);
1241 UINT32 tiles = 0;
1242
1243 for (UINT32 ty = 0; ty < height; ty += MUTTER_CHECK_TILE)
1244 {
1245 const UINT32 th = MIN(MUTTER_CHECK_TILE, height - ty);
1246 for (UINT32 tx = 0; tx < width; tx += MUTTER_CHECK_TILE)
1247 {
1248 const UINT32 tw = MIN(MUTTER_CHECK_TILE, width - tx);
1249 BOOL dirty = FALSE;
1250 for (UINT32 y = ty; (y < ty + th) && !dirty; y++)
1251 {
1252 const BYTE* a = &src[1ull * y * stride + 4ull * tx];
1253 const BYTE* b = &surface->data[1ull * y * surface->scanline + 4ull * tx];
1254 if (sameFormat)
1255 dirty = (memcmp(a, b, 4ull * tw) != 0);
1256 else
1257 {
1258 for (UINT32 x = 0; (x < tw) && !dirty; x++)
1259 dirty = (memcmp(&a[4ull * x], &b[4ull * x], 3) != 0);
1260 }
1261 }
1262 if (dirty)
1263 {
1264 mutter_copy_rect(mutter, surface, src, stride, (INT32)tx, (INT32)ty, (INT32)tw,
1265 (INT32)th);
1266 tiles++;
1267 }
1268 }
1269 }
1270 return tiles;
1271}
1272
1273/* counts and logs what mutter_repair_missed_damage found */
1274static void mutter_damage_stats(mutterShadowSubsystem* mutter, UINT32 tiles)
1275{
1276 WINPR_ASSERT(mutter);
1277
1278 mutter->damageFrames++;
1279 mutter->damageTiles += tiles;
1280 if ((tiles > 0) && !mutter->damageMissSeen)
1281 {
1282 mutter->damageMissSeen = TRUE;
1283 WLog_INFO(TAG,
1284 "Mutter left changes out of its damage: %" PRIu32
1285 " tile(s) copied, the check stays on (" MUTTER_DAMAGE_CHECK_ENV
1286 "=0 turns it off)",
1287 tiles);
1288 }
1289 const UINT64 now = GetTickCount64();
1290 if (now - mutter->damageLogged >= 5000)
1291 {
1292 WLog_DBG(TAG, "damage check: %" PRIu64 " tile(s) Mutter left out, in %" PRIu64 " frame(s)",
1293 mutter->damageTiles, mutter->damageFrames);
1294 mutter->damageTiles = 0;
1295 mutter->damageFrames = 0;
1296 mutter->damageLogged = now;
1297 }
1298}
1299
1300static void mutter_on_process(void* data)
1301{
1302 mutterShadowSubsystem* mutter = (mutterShadowSubsystem*)data;
1303 WINPR_ASSERT(mutter);
1304
1305 rdpShadowServer* server = mutter->common.server;
1306 if (!server || !server->surface)
1307 return;
1308 rdpShadowSurface* surface = server->surface;
1309 BOOL painted = FALSE;
1310 struct pw_buffer* pb = nullptr;
1311
1312 /* every queued buffer: no damage is lost when frames come faster */
1313 while ((pb = pw_stream_dequeue_buffer(mutter->stream)) != nullptr)
1314 {
1315 struct spa_buffer* buf = pb->buffer;
1316 struct spa_data* d = &buf->datas[0]; // codespell:ignore datas
1317
1318 mutter_cursor_update(mutter, buf);
1319
1320 const struct spa_meta_header* h =
1321 spa_buffer_find_meta_data(buf, SPA_META_Header, sizeof(struct spa_meta_header));
1322 if ((h && (h->flags & SPA_META_HEADER_FLAG_CORRUPTED)) || !d->data || !d->chunk ||
1323 (d->chunk->size == 0))
1324 {
1325 /* no picture in this buffer (only the cursor moved) */
1326 (void)pw_stream_queue_buffer(mutter->stream, pb);
1327 continue;
1328 }
1329
1330 const BYTE* src = (const BYTE*)d->data + d->chunk->offset;
1331 const UINT32 stride = (d->chunk->stride > 0) ? (UINT32)d->chunk->stride : mutter->width * 4;
1332
1333 EnterCriticalSection(&surface->lock);
1334 struct spa_meta* dm = spa_buffer_find_meta(buf, SPA_META_VideoDamage);
1335 if (!dm || mutter->fullPaint)
1336 {
1337 mutter_copy_rect(mutter, surface, src, stride, 0, 0, (INT32)mutter->width,
1338 (INT32)mutter->height);
1339 mutter->fullPaint = FALSE;
1340 }
1341 else
1342 {
1343 struct spa_meta_region* r = nullptr;
1344 spa_meta_for_each(r, dm)
1345 {
1346 if (!spa_meta_region_is_valid(r))
1347 break;
1348 mutter_copy_rect(mutter, surface, src, stride, r->region.position.x,
1349 r->region.position.y, (INT32)r->region.size.width,
1350 (INT32)r->region.size.height);
1351 }
1352 if (mutter->damageCheck)
1353 mutter_damage_stats(mutter,
1354 mutter_repair_missed_damage(mutter, surface, src, stride));
1355 }
1356 LeaveCriticalSection(&surface->lock);
1357 painted = TRUE;
1358 (void)pw_stream_queue_buffer(mutter->stream, pb);
1359 }
1360
1361 if (!painted)
1362 return;
1363
1364 /* Not under the clients list lock: the frame update waits until every client has encoded
1365 * the frame (an AV2 frame takes 40 to 120 ms, a key frame up to 800 ms), and the sound
1366 * thread needs the lock for each chunk it hands to the clients; it stalled, and PipeWire
1367 * dropped the sound. The update has a subscriber list of its own (see win_shadow.c). */
1368 if (ArrayList_Count(server->clients) > 0)
1369 shadow_subsystem_frame_update(&mutter->common);
1370
1371 EnterCriticalSection(&surface->lock);
1372 region16_clear(&surface->invalidRegion);
1373 LeaveCriticalSection(&surface->lock);
1374}
1375
1376static void mutter_on_state_changed(WINPR_ATTR_UNUSED void* data,
1377 WINPR_ATTR_UNUSED enum pw_stream_state old,
1378 enum pw_stream_state state, const char* error)
1379{
1380 WLog_DBG(TAG, "stream %s%s%s", pw_stream_state_as_string(state), error ? ": " : "",
1381 error ? error : "");
1382}
1383
1384static const struct pw_stream_events mutter_stream_events = {
1385 PW_VERSION_STREAM_EVENTS,
1386 .state_changed = mutter_on_state_changed,
1387 .param_changed = mutter_on_param_changed,
1388 .process = mutter_on_process,
1389};
1390
1391static void mutter_on_core_error(WINPR_ATTR_UNUSED void* data, uint32_t id,
1392 WINPR_ATTR_UNUSED int seq, WINPR_ATTR_UNUSED int res,
1393 const char* message)
1394{
1395 WLog_ERR(TAG, "PipeWire error on %" PRIu32 ": %s", id, message);
1396}
1397
1398static const struct pw_core_events mutter_core_events = {
1399 PW_VERSION_CORE_EVENTS,
1400 .error = mutter_on_core_error,
1401};
1402
1403/* Mutter ends a monitor's stream when the monitor's layout changes (its scale,
1404 * its rotation, its position): it removes the node, and the session goes on
1405 * without a picture. No D-Bus signal says so; PipeWire's registry does. */
1406static void mutter_on_global_remove(void* data, uint32_t id)
1407{
1408 mutterShadowSubsystem* mutter = (mutterShadowSubsystem*)data;
1409 WINPR_ASSERT(mutter);
1410
1411 if (mutter->haveNode && (id == mutter->nodeId))
1412 {
1413 mutter->haveNode = FALSE;
1414 mutter->streamGone = TRUE;
1415 mutter->streamGoneAt = GetTickCount64();
1416 }
1417}
1418
1419static const struct pw_registry_events mutter_registry_events = {
1420 PW_VERSION_REGISTRY_EVENTS,
1421 .global_remove = mutter_on_global_remove,
1422};
1423
1424WINPR_ATTR_NODISCARD
1425static BOOL mutter_stream_connect_node(mutterShadowSubsystem* mutter);
1426
1427WINPR_ATTR_NODISCARD
1428static BOOL mutter_connect_stream(mutterShadowSubsystem* mutter)
1429{
1430 WINPR_ASSERT(mutter);
1431
1432 pw_init(nullptr, nullptr);
1433 mutter->loop = pw_loop_new(nullptr);
1434 if (!mutter->loop)
1435 return FALSE;
1436 mutter->context = pw_context_new(mutter->loop, nullptr, 0);
1437 if (!mutter->context)
1438 return FALSE;
1439 /* the session's PipeWire daemon, through XDG_RUNTIME_DIR */
1440 mutter->core = pw_context_connect(mutter->context, nullptr, 0);
1441 if (!mutter->core)
1442 {
1443 char buffer[128] = WINPR_C_ARRAY_INIT;
1444 WLog_ERR(TAG, "cannot connect to PipeWire: %s",
1445 winpr_strerror(errno, buffer, sizeof(buffer)));
1446 return FALSE;
1447 }
1448 pw_core_add_listener(mutter->core, &mutter->coreListener, &mutter_core_events, mutter);
1449
1450 mutter->registry = pw_core_get_registry(mutter->core, PW_VERSION_REGISTRY, 0);
1451 if (!mutter->registry)
1452 return FALSE;
1453 pw_registry_add_listener(mutter->registry, &mutter->registryListener, &mutter_registry_events,
1454 mutter);
1455
1456 if (!mutter_stream_connect_node(mutter))
1457 return FALSE;
1458
1459 /* the surface needs the stream's size before the server starts */
1460 const UINT64 start = GetTickCount64();
1461 while (!mutter->haveFormat)
1462 {
1463 if (GetTickCount64() - start > MUTTER_FORMAT_TIMEOUT_MS)
1464 {
1465 WLog_ERR(TAG, "the stream got no format");
1466 return FALSE;
1467 }
1468 pw_loop_enter(mutter->loop);
1469 (void)pw_loop_iterate(mutter->loop, 100);
1470 pw_loop_leave(mutter->loop);
1471 }
1472 return TRUE;
1473}
1474
1475/* A new stream, connected to Mutter's node mutter->nodeId. Each node gets a
1476 * stream of its own: a stream connected again keeps its first target. */
1477WINPR_ATTR_NODISCARD
1478static BOOL mutter_stream_connect_node(mutterShadowSubsystem* mutter)
1479{
1480 WINPR_ASSERT(mutter);
1481 WINPR_ASSERT(mutter->core);
1482
1483 if (mutter->stream)
1484 pw_stream_destroy(mutter->stream);
1485 mutter->stream =
1486 pw_stream_new(mutter->core, "FreeRDP shadow server",
1487 pw_properties_new(PW_KEY_MEDIA_TYPE, "Video", PW_KEY_MEDIA_CATEGORY,
1488 "Capture", PW_KEY_MEDIA_ROLE, "Screen", nullptr));
1489 if (!mutter->stream)
1490 return FALSE;
1491 pw_stream_add_listener(mutter->stream, &mutter->streamListener, &mutter_stream_events, mutter);
1492
1493 uint8_t podBuffer[1024] = WINPR_C_ARRAY_INIT;
1494 struct spa_pod_builder b = SPA_POD_BUILDER_INIT(podBuffer, sizeof(podBuffer));
1495 /* a virtual monitor takes the size the stream asks for */
1496 const struct spa_rectangle size =
1497 mutter->virtualMonitor ? SPA_RECTANGLE(mutter->virtualWidth, mutter->virtualHeight)
1498 : SPA_RECTANGLE(1920, 1080);
1499 const struct spa_rectangle minSize = SPA_RECTANGLE(1, 1);
1500 const struct spa_rectangle maxSize = SPA_RECTANGLE(8192, 8192);
1501 const struct spa_fraction rate = SPA_FRACTION(0, 1);
1502 const struct spa_fraction maxRate = SPA_FRACTION(60, 1);
1503 const struct spa_fraction minRate = SPA_FRACTION(1, 1);
1504
1505 /* no modifiers offered: Mutter sends frames in shared memory */
1506 const struct spa_pod* params[] = { spa_pod_builder_add_object(
1507 &b, SPA_TYPE_OBJECT_Format, SPA_PARAM_EnumFormat, SPA_FORMAT_mediaType,
1508 SPA_POD_Id(SPA_MEDIA_TYPE_video), SPA_FORMAT_mediaSubtype,
1509 SPA_POD_Id(SPA_MEDIA_SUBTYPE_raw), SPA_FORMAT_VIDEO_format,
1510 SPA_POD_CHOICE_ENUM_Id(3, SPA_VIDEO_FORMAT_BGRx, SPA_VIDEO_FORMAT_BGRx,
1511 SPA_VIDEO_FORMAT_BGRA),
1512 SPA_FORMAT_VIDEO_size, SPA_POD_CHOICE_RANGE_Rectangle(&size, &minSize, &maxSize),
1513 SPA_FORMAT_VIDEO_framerate, SPA_POD_Fraction(&rate), SPA_FORMAT_VIDEO_maxFramerate,
1514 SPA_POD_CHOICE_RANGE_Fraction(&maxRate, &minRate, &maxRate)) };
1515
1516 if (pw_stream_connect(mutter->stream, PW_DIRECTION_INPUT, mutter->nodeId,
1517 PW_STREAM_FLAG_AUTOCONNECT | PW_STREAM_FLAG_MAP_BUFFERS, params,
1518 ARRAYSIZE(params)) < 0)
1519 {
1520 WLog_ERR(TAG, "pw_stream_connect failed");
1521 return FALSE;
1522 }
1523 return TRUE;
1524}
1525
1526/* ------------------------------------------------------------------------ */
1527/* sound */
1528/* ------------------------------------------------------------------------ */
1529
1530static void mutter_audio_msg_free(WINPR_ATTR_UNUSED UINT32 id, SHADOW_MSG_OUT* msg)
1531{
1533 if (samples)
1534 free(samples->buf);
1535 free(samples);
1536}
1537
1538/* Sends what s holds to every client, as one message */
1539static void mutter_audio_post(mutterShadowSubsystem* mutter, wStream* s)
1540{
1541 WINPR_ASSERT(mutter);
1542 WINPR_ASSERT(s);
1543
1544 const size_t size = Stream_GetPosition(s);
1545 Stream_ResetPosition(s);
1546 if (size == 0)
1547 return;
1548
1549 rdpShadowServer* server = mutter->common.server;
1550 if (!server || !server->clients || (ArrayList_Count(server->clients) == 0))
1551 return;
1552
1554 if (!msg)
1555 return;
1556
1557 msg->buf = malloc(size);
1558 if (!msg->buf)
1559 {
1560 free(msg);
1561 return;
1562 }
1563
1564 memcpy(msg->buf, Stream_Buffer(s), size);
1565 mutter->audioMessages++;
1566 msg->common.Free = mutter_audio_msg_free;
1567 msg->audio_format = &mutter->audioSourceFormat;
1568 msg->nFrames = size / MUTTER_AUDIO_FRAME_BYTES;
1569 msg->wTimestamp = (UINT16)(GetTickCount64() & 0xFFFF);
1570 /* the message is freed with its last reference, also when no client took it */
1571 if (shadow_client_boardcast_msg(server, nullptr, SHADOW_MSG_OUT_AUDIO_OUT_SAMPLES_ID,
1572 &msg->common, nullptr) < 0)
1573 WLog_DBG(TAG, "sound: a message was not sent");
1574}
1575
1576static void mutter_audio_stats(mutterShadowSubsystem* mutter, size_t frames)
1577{
1578 WINPR_ASSERT(mutter);
1579
1580 const UINT64 now = GetTickCount64();
1581 if (mutter->audioLogged == 0)
1582 {
1583 WLog_INFO(TAG, "sound: capturing what the default sink plays");
1584 mutter->audioLogged = now;
1585 }
1586 else if (now - mutter->audioLogged >= MUTTER_AUDIO_LOG_MS)
1587 {
1588 WLog_DBG(TAG,
1589 "sound: %" PRIu64 " frames captured, %" PRIu64
1590 " messages sent, peak %.3f in the last %" PRIu64 " ms",
1591 mutter->audioFrames, mutter->audioMessages, (double)mutter->audioPeak,
1592 now - mutter->audioLogged);
1593 mutter->audioFrames = 0;
1594 mutter->audioMessages = 0;
1595 mutter->audioPeak = 0.0f;
1596 mutter->audioLogged = now;
1597 }
1598 mutter->audioFrames += frames;
1599}
1600
1601static void mutter_on_audio_process(void* data)
1602{
1603 mutterShadowSubsystem* mutter = (mutterShadowSubsystem*)data;
1604 WINPR_ASSERT(mutter);
1605
1606 struct pw_buffer* pb = nullptr;
1607
1608 while ((pb = pw_stream_dequeue_buffer(mutter->audio)) != nullptr)
1609 {
1610 const struct spa_data* d = &pb->buffer->datas[0]; // codespell:ignore datas
1611 if (d->data && d->chunk && (d->chunk->size > 0) &&
1612 ((size_t)d->chunk->offset + d->chunk->size <= d->maxsize))
1613 {
1614 const BYTE* bdata = d->data;
1615 const BYTE* offset = &bdata[d->chunk->offset];
1616 const INT16* samples = WINPR_PACKED_ALIGN_CAST(const INT16*, offset);
1617 const size_t frames = d->chunk->size / MUTTER_AUDIO_FRAME_BYTES;
1618 mutter_audio_stats(mutter, frames);
1619
1620 wStream* s = mutter->audioStream;
1621 for (size_t f = 0; f < frames; f++)
1622 {
1623 for (size_t c = 0; c < MUTTER_AUDIO_CHANNELS; c++)
1624 {
1625 const INT16 value = samples[f * MUTTER_AUDIO_CHANNELS + c];
1626 mutter->audioPeak =
1627 MAX(mutter->audioPeak, fabsf((float)value / (float)INT16_MAX));
1628 Stream_Write_INT16(s, value);
1629 }
1630
1631 if (Stream_GetPosition(s) >= MUTTER_AUDIO_MESSAGE_FRAMES * MUTTER_AUDIO_FRAME_BYTES)
1632 mutter_audio_post(mutter, s);
1633 }
1634 }
1635 (void)pw_stream_queue_buffer(mutter->audio, pb);
1636 }
1637}
1638
1639static const struct pw_stream_events mutter_audio_events = {
1640 PW_VERSION_STREAM_EVENTS,
1641 .process = mutter_on_audio_process,
1642};
1643
1644/* The formats offered to the clients, and a capture of the default sink's
1645 * monitor, inactive until a client connects. Without it there is no sound,
1646 * and the rest works. */
1647WINPR_ATTR_NODISCARD
1648static BOOL mutter_audio_init(mutterShadowSubsystem* mutter)
1649{
1650 WINPR_ASSERT(mutter);
1651
1652 uint8_t podBuffer[1024] = WINPR_C_ARRAY_INIT;
1653 struct spa_pod_builder b = SPA_POD_BUILDER_INIT(podBuffer, sizeof(podBuffer));
1654
1655 AUDIO_FORMAT* formats = audio_formats_new(ARRAYSIZE(mutter_audio_offered_formats));
1656 if (!formats)
1657 return FALSE;
1658
1659 size_t count = 0;
1660 for (size_t x = 0; x < ARRAYSIZE(mutter_audio_offered_formats); x++)
1661 {
1662 const AUDIO_FORMAT* format = &mutter_audio_offered_formats[x];
1663 if (freerdp_dsp_supports_format(format, TRUE))
1664 formats[count++] = *format;
1665 }
1666
1667 mutter->audioStream =
1668 Stream_New(nullptr, MUTTER_AUDIO_MESSAGE_FRAMES * MUTTER_AUDIO_FRAME_BYTES);
1669 if (!mutter->audioStream || (count == 0))
1670 goto fail;
1671
1672 mutter->audio =
1673 pw_stream_new(mutter->core, "FreeRDP shadow server sound",
1674 pw_properties_new(PW_KEY_MEDIA_TYPE, "Audio", PW_KEY_MEDIA_CATEGORY,
1675 "Capture", PW_KEY_MEDIA_ROLE, "Screen",
1676 PW_KEY_STREAM_CAPTURE_SINK, "true", nullptr));
1677 if (!mutter->audio)
1678 goto fail;
1679 pw_stream_add_listener(mutter->audio, &mutter->audioListener, &mutter_audio_events, mutter);
1680
1681 struct spa_audio_info_raw info = WINPR_C_ARRAY_INIT;
1682 info.format = SPA_AUDIO_FORMAT_S16_LE;
1683 info.rate = MUTTER_AUDIO_RATE;
1684 info.channels = MUTTER_AUDIO_CHANNELS;
1685 info.position[0] = SPA_AUDIO_CHANNEL_FL;
1686 info.position[1] = SPA_AUDIO_CHANNEL_FR;
1687 const struct spa_pod* params[] = { spa_format_audio_raw_build(&b, SPA_PARAM_EnumFormat,
1688 &info) };
1689
1690 /* PipeWire links it to the default sink's monitor and converts to our format */
1691 if (pw_stream_connect(mutter->audio, PW_DIRECTION_INPUT, PW_ID_ANY,
1692 PW_STREAM_FLAG_AUTOCONNECT | PW_STREAM_FLAG_MAP_BUFFERS |
1693 PW_STREAM_FLAG_INACTIVE,
1694 params, ARRAYSIZE(params)) < 0)
1695 goto fail;
1696
1697 mutter->common.rdpsndFormats = formats;
1698 mutter->common.nRdpsndFormats = count;
1699 return TRUE;
1700
1701fail:
1702 if (mutter->audio)
1703 pw_stream_destroy(mutter->audio);
1704 mutter->audio = nullptr;
1705 Stream_Free(mutter->audioStream, TRUE);
1706 mutter->audioStream = nullptr;
1707 audio_formats_free(formats, ARRAYSIZE(mutter_audio_offered_formats));
1708 return FALSE;
1709}
1710
1711static void mutter_audio_uninit(mutterShadowSubsystem* mutter)
1712{
1713 WINPR_ASSERT(mutter);
1714
1715 if (mutter->audio)
1716 pw_stream_destroy(mutter->audio);
1717 mutter->audio = nullptr;
1718 mutter->audioActive = FALSE;
1719 audio_formats_free(mutter->common.rdpsndFormats, mutter->common.nRdpsndFormats);
1720 mutter->common.rdpsndFormats = nullptr;
1721 mutter->common.nRdpsndFormats = 0;
1722 Stream_Free(mutter->audioStream, TRUE);
1723 mutter->audioStream = nullptr;
1724}
1725
1726/* in the subsystem thread: capture only while clients are connected */
1727static void mutter_audio_follow_clients(mutterShadowSubsystem* mutter)
1728{
1729 WINPR_ASSERT(mutter);
1730
1731 if (!mutter->audio)
1732 return;
1733
1734 const BOOL active = InterlockedCompareExchange(&mutter->clients, 0, 0) > 0;
1735 if (active == mutter->audioActive)
1736 return;
1737
1738 if (pw_stream_set_active(mutter->audio, active ? true : false) < 0)
1739 {
1740 WLog_WARN(TAG, "sound: cannot %s the capture", active ? "start" : "stop");
1741 return;
1742 }
1743 mutter->audioActive = active;
1744 if (!active)
1745 Stream_ResetPosition(mutter->audioStream);
1746 WLog_DBG(TAG, "sound: capture %s", active ? "started" : "stopped");
1747}
1748
1749/* ------------------------------------------------------------------------ */
1750/* libei */
1751/* ------------------------------------------------------------------------ */
1752
1753/* under eiLock */
1754static void mutter_process_ei_events(mutterShadowSubsystem* mutter)
1755{
1756 WINPR_ASSERT(mutter);
1757
1758 struct ei_event* e = nullptr;
1759
1760 ei_dispatch(mutter->ei);
1761 while ((e = ei_get_event(mutter->ei)) != nullptr)
1762 {
1763 struct ei_device* dev = ei_event_get_device(e);
1764
1765 switch (ei_event_get_type(e))
1766 {
1767 case EI_EVENT_SEAT_ADDED:
1768 ei_seat_bind_capabilities(ei_event_get_seat(e), EI_DEVICE_CAP_POINTER_ABSOLUTE,
1769 EI_DEVICE_CAP_KEYBOARD, EI_DEVICE_CAP_BUTTON,
1770 EI_DEVICE_CAP_SCROLL, nullptr);
1771 break;
1772 case EI_EVENT_DEVICE_ADDED:
1773 if (ei_device_has_capability(dev, EI_DEVICE_CAP_KEYBOARD) && !mutter->keyboard)
1774 {
1775 mutter->keyboard = ei_device_ref(dev);
1776 WLog_DBG(TAG, "libei keyboard '%s'", ei_device_get_name(dev));
1777 }
1778 if (ei_device_has_capability(dev, EI_DEVICE_CAP_POINTER_ABSOLUTE) &&
1779 !mutter->pointer)
1780 {
1781 mutter->pointer = ei_device_ref(dev);
1782 WLog_DBG(TAG, "libei pointer '%s'", ei_device_get_name(dev));
1783 }
1784 break;
1785 case EI_EVENT_DEVICE_RESUMED:
1786 ei_device_start_emulating(dev, ++mutter->sequence);
1787 if (dev == mutter->keyboard)
1788 mutter->keyboardReady = TRUE;
1789 if (dev == mutter->pointer)
1790 mutter->pointerReady = TRUE;
1791 break;
1792 case EI_EVENT_DEVICE_PAUSED:
1793 if (dev == mutter->keyboard)
1794 mutter->keyboardReady = FALSE;
1795 if (dev == mutter->pointer)
1796 mutter->pointerReady = FALSE;
1797 break;
1798 case EI_EVENT_DEVICE_REMOVED:
1799 if (dev == mutter->keyboard)
1800 {
1801 mutter->keyboard = ei_device_unref(mutter->keyboard);
1802 mutter->keyboardReady = FALSE;
1803 }
1804 if (dev == mutter->pointer)
1805 {
1806 mutter->pointer = ei_device_unref(mutter->pointer);
1807 mutter->pointerReady = FALSE;
1808 }
1809 break;
1810 case EI_EVENT_KEYBOARD_MODIFIERS:
1811 mutter->lockedMods = ei_event_keyboard_get_xkb_mods_locked(e);
1812 mutter->lockedModsKnown = TRUE;
1813 WLog_DBG(TAG, "keyboard: locked modifiers 0x%08" PRIx32, mutter->lockedMods);
1814 break;
1815 case EI_EVENT_DISCONNECT:
1816 WLog_WARN(TAG, "libei disconnected: the clients can only watch");
1817 break;
1818 default:
1819 break;
1820 }
1821 ei_event_unref(e);
1822 }
1823}
1824
1825/* under eiLock */
1826static void mutter_key(mutterShadowSubsystem* mutter, uint32_t code, BOOL down)
1827{
1828 WINPR_ASSERT(mutter);
1829
1830 if (!mutter->keyboard || !mutter->keyboardReady)
1831 return;
1832 /* Mutter keeps the modifier state itself, as for a real keyboard */
1833 ei_device_keyboard_key(mutter->keyboard, code, down ? true : false);
1834 ei_device_frame(mutter->keyboard, ei_now(mutter->ei));
1835
1836 /* Mutter reports the new locks later, through libei: until then, follow the
1837 * lock keys pressed here, or a Synchronize that comes first (clients send one
1838 * on each focus) would tap them again and undo them */
1839 if (down && (code == KEY_CAPSLOCK))
1840 mutter->lockedMods ^= MUTTER_MOD_LOCK;
1841 if (down && (code == KEY_NUMLOCK))
1842 mutter->lockedMods ^= MUTTER_MOD_NUM;
1843}
1844
1845WINPR_ATTR_NODISCARD
1846static BOOL mutter_shadow_input_synchronize_event(rdpShadowSubsystem* subsystem,
1847 rdpShadowClient* client, UINT32 flags)
1848{
1849 mutterShadowSubsystem* mutter = (mutterShadowSubsystem*)subsystem;
1850 if (!mutter || !client)
1851 return FALSE;
1852
1853 EnterCriticalSection(&mutter->eiLock);
1854 WLog_DBG(TAG, "synchronize: client caps %d num %d, session %s 0x%08" PRIx32,
1855 (flags & KBD_SYNC_CAPS_LOCK) ? 1 : 0, (flags & KBD_SYNC_NUM_LOCK) ? 1 : 0,
1856 mutter->lockedModsKnown ? "locked" : "unknown", mutter->lockedMods);
1857 if (mutter->ei && mutter->lockedModsKnown)
1858 {
1859 /* the client's lock keys win: tap each one that differs */
1860 const BOOL caps = (flags & KBD_SYNC_CAPS_LOCK) != 0;
1861 const BOOL num = (flags & KBD_SYNC_NUM_LOCK) != 0;
1862 if (caps != ((mutter->lockedMods & MUTTER_MOD_LOCK) != 0))
1863 {
1864 mutter_key(mutter, KEY_CAPSLOCK, TRUE);
1865 mutter_key(mutter, KEY_CAPSLOCK, FALSE);
1866 }
1867 if (num != ((mutter->lockedMods & MUTTER_MOD_NUM) != 0))
1868 {
1869 mutter_key(mutter, KEY_NUMLOCK, TRUE);
1870 mutter_key(mutter, KEY_NUMLOCK, FALSE);
1871 }
1872 }
1873 LeaveCriticalSection(&mutter->eiLock);
1874 return TRUE;
1875}
1876
1877WINPR_ATTR_NODISCARD
1878static BOOL mutter_shadow_input_keyboard_event(rdpShadowSubsystem* subsystem,
1879 rdpShadowClient* client, UINT16 flags, UINT8 code)
1880{
1881 mutterShadowSubsystem* mutter = (mutterShadowSubsystem*)subsystem;
1882 if (!mutter || !client)
1883 return FALSE;
1884
1885 DWORD scancode = code;
1886 if (flags & KBD_FLAGS_EXTENDED)
1887 scancode |= KBDEXT;
1888
1889 DWORD vkcode = GetVirtualKeyCodeFromVirtualScanCode(scancode, WINPR_KBD_TYPE_IBM_ENHANCED);
1890 if (flags & KBD_FLAGS_EXTENDED)
1891 vkcode |= KBDEXT;
1892 /* WinPR's evdev table is indexed by the Linux input codes libei takes */
1893 const DWORD keycode = GetKeycodeFromVirtualKeyCode(vkcode, WINPR_KEYCODE_TYPE_EVDEV);
1894 if (keycode == 0)
1895 {
1896 WLog_DBG(TAG, "no key for scancode 0x%04" PRIx32, scancode);
1897 return TRUE;
1898 }
1899
1900 /* a key is pressed unless the release flag is set (KBD_FLAGS_DOWN marks a repeat) */
1901 const BOOL down = (flags & KBD_FLAGS_RELEASE) == 0;
1902
1903 EnterCriticalSection(&mutter->eiLock);
1904 if (mutter->ei)
1905 mutter_key(mutter, keycode, down);
1906 LeaveCriticalSection(&mutter->eiLock);
1907 return TRUE;
1908}
1909
1910WINPR_ATTR_NODISCARD
1911static BOOL mutter_shadow_input_unicode_keyboard_event(rdpShadowSubsystem* subsystem,
1912 rdpShadowClient* client, UINT16 flags,
1913 UINT16 code)
1914{
1915 if (!subsystem || !client)
1916 return FALSE;
1917
1918 /* libei sends key codes only; typing a character needs a key in the keymap */
1919 WLog_DBG(TAG, "unicode key U+%04" PRIX16 " (flags 0x%04" PRIx16 ") not supported", code, flags);
1920 return TRUE;
1921}
1922
1923/* under eiLock: the client's surface position into Mutter's layout, in the
1924 * libei region of the shared monitor */
1925static void mutter_pointer_move(mutterShadowSubsystem* mutter, UINT16 x, UINT16 y)
1926{
1927 WINPR_ASSERT(mutter);
1928
1929 struct ei_region* region = nullptr;
1930 struct ei_region* first = nullptr;
1931
1932 for (size_t index = 0;; index++)
1933 {
1934 struct ei_region* r = ei_device_get_region(mutter->pointer, index);
1935 if (!r)
1936 break;
1937 if (!first)
1938 first = r;
1939 const char* id = ei_region_get_mapping_id(r);
1940 if (mutter->mappingId && id && (strcmp(id, mutter->mappingId) == 0))
1941 {
1942 region = r;
1943 break;
1944 }
1945 }
1946 if (!region)
1947 region = first;
1948
1949 mutter->pointerInput = GetTickCount64();
1950 double px = x;
1951 double py = y;
1952 if (region && (mutter->width > 0) && (mutter->height > 0))
1953 {
1954 px = ei_region_get_x(region) + (double)x * ei_region_get_width(region) / mutter->width;
1955 py = ei_region_get_y(region) + (double)y * ei_region_get_height(region) / mutter->height;
1956 }
1957 WLog_VRB(TAG,
1958 "pointer %" PRIu16 ",%" PRIu16 " -> %.1f,%.1f (region %s %" PRIu32 ",%" PRIu32
1959 " %" PRIu32 "x%" PRIu32 ")",
1960 x, y, px, py,
1961 region ? (ei_region_get_mapping_id(region) ? ei_region_get_mapping_id(region) : "-")
1962 : "none",
1963 region ? ei_region_get_x(region) : 0, region ? ei_region_get_y(region) : 0,
1964 region ? ei_region_get_width(region) : 0, region ? ei_region_get_height(region) : 0);
1965 ei_device_pointer_motion_absolute(mutter->pointer, px, py);
1966 ei_device_frame(mutter->pointer, ei_now(mutter->ei));
1967}
1968
1969/* under eiLock */
1970static void mutter_pointer_button(mutterShadowSubsystem* mutter, uint32_t button, BOOL down)
1971{
1972 WINPR_ASSERT(mutter);
1973
1974 if (!ei_device_has_capability(mutter->pointer, EI_DEVICE_CAP_BUTTON))
1975 return;
1976 ei_device_button_button(mutter->pointer, button, down ? true : false);
1977 ei_device_frame(mutter->pointer, ei_now(mutter->ei));
1978}
1979
1980/* under eiLock; 120 is one notch, as in RDP */
1981static void mutter_pointer_wheel(mutterShadowSubsystem* mutter, UINT16 flags, BOOL horizontal)
1982{
1983 WINPR_ASSERT(mutter);
1984
1985 if (!ei_device_has_capability(mutter->pointer, EI_DEVICE_CAP_SCROLL))
1986 return;
1987
1988 INT32 value = flags & 0xFF;
1989 if (flags & PTR_FLAGS_WHEEL_NEGATIVE)
1990 value -= 0x100;
1991
1992 /* RDP: positive turns away from the user (up) or to the right; libei:
1993 * positive scrolls down or to the right */
1994 if (horizontal)
1995 ei_device_scroll_discrete(mutter->pointer, value, 0);
1996 else
1997 ei_device_scroll_discrete(mutter->pointer, 0, -value);
1998 ei_device_frame(mutter->pointer, ei_now(mutter->ei));
1999}
2000
2001WINPR_ATTR_NODISCARD
2002static BOOL mutter_shadow_input_mouse_event(rdpShadowSubsystem* subsystem, rdpShadowClient* client,
2003 UINT16 flags, UINT16 x, UINT16 y)
2004{
2005 mutterShadowSubsystem* mutter = (mutterShadowSubsystem*)subsystem;
2006 if (!mutter || !client)
2007 return FALSE;
2008
2009 EnterCriticalSection(&mutter->eiLock);
2010 if (mutter->ei && mutter->pointer && mutter->pointerReady)
2011 {
2012 if (flags & PTR_FLAGS_WHEEL)
2013 mutter_pointer_wheel(mutter, flags, FALSE);
2014 else if (flags & PTR_FLAGS_HWHEEL)
2015 mutter_pointer_wheel(mutter, flags, TRUE);
2016 else
2017 {
2018 /* button events carry the position too */
2019 mutter_pointer_move(mutter, x, y);
2020
2021 const BOOL down = (flags & PTR_FLAGS_DOWN) != 0;
2022 if (flags & PTR_FLAGS_BUTTON1)
2023 mutter_pointer_button(mutter, BTN_LEFT, down);
2024 else if (flags & PTR_FLAGS_BUTTON2)
2025 mutter_pointer_button(mutter, BTN_RIGHT, down);
2026 else if (flags & PTR_FLAGS_BUTTON3)
2027 mutter_pointer_button(mutter, BTN_MIDDLE, down);
2028 }
2029 }
2030 LeaveCriticalSection(&mutter->eiLock);
2031 return TRUE;
2032}
2033
2034WINPR_ATTR_NODISCARD
2035static BOOL mutter_shadow_input_extended_mouse_event(rdpShadowSubsystem* subsystem,
2036 rdpShadowClient* client, UINT16 flags,
2037 UINT16 x, UINT16 y)
2038{
2039 mutterShadowSubsystem* mutter = (mutterShadowSubsystem*)subsystem;
2040 if (!mutter || !client)
2041 return FALSE;
2042
2043 EnterCriticalSection(&mutter->eiLock);
2044 if (mutter->ei && mutter->pointer && mutter->pointerReady)
2045 {
2046 mutter_pointer_move(mutter, x, y);
2047
2048 const BOOL down = (flags & PTR_XFLAGS_DOWN) != 0;
2049 if ((flags & PTR_XFLAGS_BUTTON1) != 0)
2050 mutter_pointer_button(mutter, BTN_SIDE, down);
2051 else if ((flags & PTR_XFLAGS_BUTTON2) != 0)
2052 mutter_pointer_button(mutter, BTN_EXTRA, down);
2053 }
2054 LeaveCriticalSection(&mutter->eiLock);
2055 return TRUE;
2056}
2057
2058/* ------------------------------------------------------------------------ */
2059/* login */
2060/* ------------------------------------------------------------------------ */
2061
2062#ifdef WITH_PAM
2063typedef struct
2064{
2065 const char* user;
2066 const char* password;
2067} MUTTER_SHADOW_PAM_DATA;
2068
2069WINPR_ATTR_NODISCARD
2070static int mutter_shadow_pam_conv(int num_msg, const struct pam_message** msg,
2071 struct pam_response** resp, void* appdata_ptr)
2072{
2073 const MUTTER_SHADOW_PAM_DATA* data = (const MUTTER_SHADOW_PAM_DATA*)appdata_ptr;
2074
2075 if ((num_msg <= 0) || !msg || !resp || !data)
2076 return PAM_CONV_ERR;
2077
2078 struct pam_response* response = calloc((size_t)num_msg, sizeof(struct pam_response));
2079 if (!response)
2080 return PAM_BUF_ERR;
2081
2082 for (int index = 0; index < num_msg; index++)
2083 {
2084 const char* answer = nullptr;
2085 switch (msg[index]->msg_style)
2086 {
2087 case PAM_PROMPT_ECHO_ON:
2088 answer = data->user;
2089 break;
2090 case PAM_PROMPT_ECHO_OFF:
2091 answer = data->password;
2092 break;
2093 default:
2094 continue;
2095 }
2096
2097 response[index].resp = _strdup(answer);
2098 if (!response[index].resp)
2099 {
2100 for (int x = 0; x < index; x++)
2101 free(response[x].resp);
2102 free(response);
2103 return PAM_BUF_ERR;
2104 }
2105 }
2106
2107 *resp = response;
2108 return PAM_SUCCESS;
2109}
2110
2111/* The session shared belongs to the user running the server: only that user
2112 * may log in, with the password of their account. */
2113WINPR_ATTR_NODISCARD
2114static int mutter_shadow_pam_authenticate(WINPR_ATTR_UNUSED rdpShadowSubsystem* subsystem,
2115 WINPR_ATTR_UNUSED rdpShadowClient* client,
2116 const char* user, WINPR_ATTR_UNUSED const char* domain,
2117 const char* password)
2118{
2119 if (!user || !password)
2120 return -1;
2121
2122 struct passwd pbuffer = WINPR_C_ARRAY_INIT;
2123 struct passwd* owner = nullptr;
2124
2125 /* -1 means no fixed limit: the usual size */
2126 long buflen = sysconf(_SC_GETPW_R_SIZE_MAX);
2127 if (buflen <= 0)
2128 buflen = 16384;
2129 char* buf = calloc((size_t)buflen, sizeof(char));
2130 if (!buf)
2131 {
2132 WLog_ERR(TAG, "login as '%s' refused: no memory for the session's account", user);
2133 return -1;
2134 }
2135 const int rc = getpwuid_r(getuid(), &pbuffer, buf, (size_t)buflen, &owner);
2136 if (rc != 0)
2137 {
2138 char msg[128] = WINPR_C_ARRAY_INIT;
2139 WLog_ERR(TAG, "login as '%s' refused: the session's account: %s", user,
2140 winpr_strerror(rc, msg, sizeof(msg)));
2141 free(buf);
2142 return -1;
2143 }
2144 if (!owner || !owner->pw_name || (strcmp(owner->pw_name, user) != 0))
2145 {
2146 WLog_ERR(TAG, "login as '%s' refused: this session belongs to '%s'", user,
2147 (owner && owner->pw_name) ? owner->pw_name : "?");
2148 free(buf);
2149 return -1;
2150 }
2151
2152 MUTTER_SHADOW_PAM_DATA data = { .user = user, .password = password };
2153 const struct pam_conv conv = { .conv = mutter_shadow_pam_conv, .appdata_ptr = &data };
2154 pam_handle_t* handle = nullptr;
2155
2156 /* a service of our own if the system has one, else the console login's */
2157 const char* service =
2158 winpr_PathFileExists("/etc/pam.d/freerdp-shadow") ? "freerdp-shadow" : "login";
2159 int status = pam_start(service, user, &conv, &handle);
2160 if (status != PAM_SUCCESS)
2161 {
2162 WLog_ERR(TAG, "pam_start(%s) failure: %s", service, pam_strerror(handle, status));
2163 free(buf);
2164 return -1;
2165 }
2166
2167 status = pam_authenticate(handle, 0);
2168 if (status == PAM_SUCCESS)
2169 status = pam_acct_mgmt(handle, 0);
2170 if (status != PAM_SUCCESS)
2171 WLog_ERR(TAG, "login as '%s' failed: %s", user, pam_strerror(handle, status));
2172
2173 pam_end(handle, status);
2174 free(buf);
2175 return (status == PAM_SUCCESS) ? 1 : -1;
2176}
2177#endif
2178
2179/* ------------------------------------------------------------------------ */
2180/* the subsystem */
2181/* ------------------------------------------------------------------------ */
2182
2183static void mutter_shadow_refresh(mutterShadowSubsystem* mutter)
2184{
2185 WINPR_ASSERT(mutter);
2186
2187 /* the next frame goes out whole; Mutter sends one when asked for nothing
2188 * else only once something changes, so ask the clients' surface again */
2189 mutter->fullPaint = TRUE;
2190 mutter->cursorResend = TRUE;
2191
2192 /* the picture carries the cursor: the clients hide their own pointer */
2193 if (!mutter->virtualMonitor)
2194 {
2195 static const BYTE clear[4] = { 0 };
2196 mutter_cursor_send_shape(mutter, clear, 1, 1, 0, 0);
2197 }
2198 else if (mutter->cursorPixels)
2199 mutter_cursor_send_shape(mutter, mutter->cursorPixels, mutter->cursorWidth,
2200 mutter->cursorHeight, mutter->cursorXHot, mutter->cursorYHot);
2201 else
2202 mutter_cursor_send_default(mutter);
2203
2204 rdpShadowServer* server = mutter->common.server;
2205 rdpShadowSurface* surface = server ? server->surface : nullptr;
2206 if (!surface)
2207 return;
2208
2209 EnterCriticalSection(&surface->lock);
2210 const RECTANGLE_16 all = { 0, 0, (UINT16)surface->width, (UINT16)surface->height };
2211 if (!region16_union_rect(&surface->invalidRegion, &surface->invalidRegion, &all))
2212 WLog_ERR(TAG, "cannot mark the screen changed");
2213 LeaveCriticalSection(&surface->lock);
2214
2215 /* not under the clients list lock, as in mutter_on_process */
2216 if (ArrayList_Count(server->clients) > 0)
2217 shadow_subsystem_frame_update(&mutter->common);
2218
2219 EnterCriticalSection(&surface->lock);
2220 region16_clear(&surface->invalidRegion);
2221 LeaveCriticalSection(&surface->lock);
2222}
2223
2224/* ------------------------------------------------------------------------- */
2225/* Clipboard: the session's side of shadow_cliprdr.c, through the selection */
2226/* calls of RemoteDesktop.Session. Mutter hands out a pipe for each transfer. */
2227/* ------------------------------------------------------------------------- */
2228
2229typedef struct
2230{
2231 char** mimes; /* nullptr terminated */
2232 char* mime;
2233 UINT32 serial;
2234 BYTE* data;
2235 size_t size;
2236 BOOL ok;
2237 int fd;
2238} mutterClipboardJob;
2239
2240WINPR_ATTR_NODISCARD
2241static mutterClipboardJob* mutter_clipboard_job_new(void)
2242{
2243 mutterClipboardJob* job = calloc(1, sizeof(mutterClipboardJob));
2244 if (job)
2245 job->fd = -1;
2246 return job;
2247}
2248
2249static void mutter_clipboard_job_free(mutterClipboardJob* job)
2250{
2251 if (!job)
2252 return;
2253 if (job->mimes)
2254 {
2255 for (char** mime = job->mimes; *mime; mime++)
2256 free(*mime);
2257 }
2258 free((void*)job->mimes);
2259 free(job->mime);
2260 free(job->data);
2261 if (job->fd >= 0)
2262 (void)close(job->fd);
2263 free(job);
2264}
2265
2266static void mutter_clipboard_message_free(wMessage* message)
2267{
2268 WINPR_ASSERT(message);
2269 mutter_clipboard_job_free(message->context);
2270}
2271
2272WINPR_ATTR_NODISCARD
2273static BOOL mutter_clipboard_post(wMessageQueue* queue, UINT32 id, mutterClipboardJob* job)
2274{
2275 WINPR_ASSERT(queue);
2276 WINPR_ASSERT(job);
2277
2278 wMessage message = WINPR_C_ARRAY_INIT;
2279 message.context = job;
2280 message.id = id;
2281 message.Free = mutter_clipboard_message_free;
2282 if (MessageQueue_Dispatch(queue, &message))
2283 return TRUE;
2284 mutter_clipboard_job_free(job);
2285 return FALSE;
2286}
2287
2288/* The hooks of shadow_cliprdr.c: any thread, so the work goes to the subsystem's */
2289WINPR_ATTR_NODISCARD
2290static BOOL mutter_clipboard_offer(rdpShadowSubsystem* subsystem, const char* const* mimeTypes,
2291 size_t count)
2292{
2293 WINPR_ASSERT(subsystem);
2294 WINPR_ASSERT(subsystem->MsgPipe);
2295
2296 mutterClipboardJob* job = mutter_clipboard_job_new();
2297 if (!job)
2298 return FALSE;
2299
2300 job->mimes = (char**)calloc(count + 1, sizeof(char*));
2301 if (!job->mimes)
2302 goto fail;
2303 for (size_t x = 0; x < count; x++)
2304 {
2305 job->mimes[x] = _strdup(mimeTypes[x]);
2306 if (!job->mimes[x])
2307 goto fail;
2308 }
2309 return mutter_clipboard_post(subsystem->MsgPipe->In, MUTTER_MSG_CLIPBOARD_OFFER, job);
2310
2311fail:
2312 mutter_clipboard_job_free(job);
2313 return FALSE;
2314}
2315
2316WINPR_ATTR_NODISCARD
2317static BOOL mutter_clipboard_read(rdpShadowSubsystem* subsystem, const char* mimeType)
2318{
2319 WINPR_ASSERT(subsystem);
2320 WINPR_ASSERT(subsystem->MsgPipe);
2321 WINPR_ASSERT(mimeType);
2322
2323 mutterClipboardJob* job = mutter_clipboard_job_new();
2324 if (!job)
2325 return FALSE;
2326
2327 job->mime = _strdup(mimeType);
2328 if (!job->mime)
2329 {
2330 mutter_clipboard_job_free(job);
2331 return FALSE;
2332 }
2333 return mutter_clipboard_post(subsystem->MsgPipe->In, MUTTER_MSG_CLIPBOARD_READ, job);
2334}
2335
2336WINPR_ATTR_NODISCARD
2337static BOOL mutter_clipboard_provide(rdpShadowSubsystem* subsystem, UINT32 request,
2338 const BYTE* data, size_t size)
2339{
2340 WINPR_ASSERT(subsystem);
2341 WINPR_ASSERT(subsystem->MsgPipe);
2342
2343 mutterClipboardJob* job = mutter_clipboard_job_new();
2344 if (!job)
2345 return FALSE;
2346
2347 job->serial = request;
2348 if (data)
2349 {
2350 job->data = malloc(size);
2351 if (!job->data)
2352 {
2353 mutter_clipboard_job_free(job);
2354 return FALSE;
2355 }
2356 memcpy(job->data, data, size);
2357 job->size = size;
2358 job->ok = TRUE;
2359 }
2360 return mutter_clipboard_post(subsystem->MsgPipe->In, MUTTER_MSG_CLIPBOARD_PROVIDE, job);
2361}
2362
2363/* @return the pipe Mutter answers with, ours to close, or -1 */
2364WINPR_ATTR_NODISCARD
2365static int mutter_clipboard_call_for_fd(mutterShadowSubsystem* mutter, const char* method,
2366 const char* types, ...)
2367{
2368 WINPR_ASSERT(mutter);
2369
2370 sd_bus_error err = SD_BUS_ERROR_NULL;
2371 sd_bus_message* call = nullptr;
2372 sd_bus_message* reply = nullptr;
2373 int fd = -1;
2374
2375 if (sd_bus_message_new_method_call(mutter->bus, &call, RD_NAME, mutter->rdSessionPath,
2376 RD_SESSION_IFACE, method) < 0)
2377 return -1;
2378
2379 va_list ap = WINPR_C_ARRAY_INIT;
2380 va_start(ap, types);
2381 const int appended = sd_bus_message_appendv(call, types, ap);
2382 va_end(ap);
2383
2384 if ((appended >= 0) &&
2385 (sd_bus_call(mutter->bus, call, MUTTER_CLIPBOARD_CALL_USEC, &err, &reply) >= 0) &&
2386 (sd_bus_message_read(reply, "h", &fd) >= 0))
2387 fd = fcntl(fd, F_DUPFD_CLOEXEC, 3); /* the reply owns the fd */
2388 else
2389 {
2390 WLog_WARN(TAG, "clipboard: %s failed: %s", method, err.message ? err.message : "bad reply");
2391 fd = -1;
2392 }
2393
2394 sd_bus_error_free(&err);
2395 sd_bus_message_unref(reply);
2396 sd_bus_message_unref(call);
2397 return fd;
2398}
2399
2400static void mutter_clipboard_write_done(mutterShadowSubsystem* mutter, UINT32 serial, BOOL ok)
2401{
2402 WINPR_ASSERT(mutter);
2403
2404 const int rc =
2405 sd_bus_call_method(mutter->bus, RD_NAME, mutter->rdSessionPath, RD_SESSION_IFACE,
2406 "SelectionWriteDone", nullptr, nullptr, "ub", serial, ok ? 1 : 0);
2407 if (rc < 0)
2408 {
2409 char buffer[128] = WINPR_C_ARRAY_INIT;
2410 WLog_WARN(TAG, "clipboard: SelectionWriteDone failed: %s",
2411 winpr_strerror(-rc, buffer, sizeof(buffer)));
2412 }
2413}
2414
2415/* subsystem thread: a client's clipboard becomes the session's selection */
2416static void mutter_clipboard_set_selection(mutterShadowSubsystem* mutter,
2417 const mutterClipboardJob* job)
2418{
2419 WINPR_ASSERT(mutter);
2420 WINPR_ASSERT(job);
2421 WINPR_ASSERT(job->mimes);
2422
2423 sd_bus_error err = SD_BUS_ERROR_NULL;
2424 sd_bus_message* call = nullptr;
2425
2426 /* without mime types, Mutter ends the session's ownership */
2427 int rc = sd_bus_message_new_method_call(mutter->bus, &call, RD_NAME, mutter->rdSessionPath,
2428 RD_SESSION_IFACE, "SetSelection");
2429 if (rc >= 0)
2430 rc = sd_bus_message_open_container(call, 'a', "{sv}");
2431 if ((rc >= 0) && job->mimes[0])
2432 {
2433 rc = sd_bus_message_open_container(call, 'e', "sv");
2434 if (rc >= 0)
2435 rc = sd_bus_message_append(call, "s", "mime-types");
2436 if (rc >= 0)
2437 rc = sd_bus_message_open_container(call, 'v', "as");
2438 if (rc >= 0)
2439 rc = sd_bus_message_append_strv(call, job->mimes);
2440 if (rc >= 0)
2441 rc = sd_bus_message_close_container(call);
2442 if (rc >= 0)
2443 rc = sd_bus_message_close_container(call);
2444 }
2445 if (rc >= 0)
2446 rc = sd_bus_message_close_container(call);
2447 if (rc >= 0)
2448 rc = sd_bus_call(mutter->bus, call, MUTTER_CLIPBOARD_CALL_USEC, &err, nullptr);
2449 if (rc < 0)
2450 {
2451 char buffer[128] = WINPR_C_ARRAY_INIT;
2452 WLog_WARN(TAG, "clipboard: SetSelection failed: %s",
2453 err.message ? err.message : winpr_strerror(-rc, buffer, sizeof(buffer)));
2454 }
2455
2456 sd_bus_error_free(&err);
2457 sd_bus_message_unref(call);
2458}
2459
2460/* subsystem thread: a client pastes */
2461static void mutter_clipboard_start_read(mutterShadowSubsystem* mutter, wMessage* message)
2462{
2463 WINPR_ASSERT(mutter);
2464 WINPR_ASSERT(message);
2465
2466 mutterClipboardJob* job = message->context;
2467 WINPR_ASSERT(job);
2468
2469 job->fd = mutter_clipboard_call_for_fd(mutter, "SelectionRead", "s", job->mime);
2470 if ((job->fd >= 0) && mutter->clipboardJobs)
2471 {
2472 message->Free = nullptr; /* the data thread has it now */
2473 if (mutter_clipboard_post(mutter->clipboardJobs, MUTTER_MSG_CLIPBOARD_READ, job))
2474 return;
2475 }
2476 if (!shadow_clipboard_session_data(mutter->common.server, nullptr, 0))
2477 WLog_DBG(TAG, "clipboard: no read was waiting");
2478}
2479
2480/* subsystem thread: the session pastes, here is the client's data */
2481static void mutter_clipboard_start_write(mutterShadowSubsystem* mutter, wMessage* message)
2482{
2483 WINPR_ASSERT(mutter);
2484 WINPR_ASSERT(message);
2485
2486 mutterClipboardJob* job = message->context;
2487 WINPR_ASSERT(job);
2488
2489 if (job->ok)
2490 {
2491 job->fd = mutter_clipboard_call_for_fd(mutter, "SelectionWrite", "u", job->serial);
2492 if ((job->fd >= 0) && mutter->clipboardJobs)
2493 {
2494 const UINT32 serial = job->serial;
2495 message->Free = nullptr; /* the data thread has it now */
2496 if (mutter_clipboard_post(mutter->clipboardJobs, MUTTER_MSG_CLIPBOARD_PROVIDE, job))
2497 return;
2498 mutter_clipboard_write_done(mutter, serial, FALSE);
2499 return;
2500 }
2501 }
2502 mutter_clipboard_write_done(mutter, job->serial, FALSE);
2503}
2504
2505/* @return the revents of fd, 0 on a stall or a stop */
2506WINPR_ATTR_NODISCARD
2507static short mutter_clipboard_wait(mutterShadowSubsystem* mutter, int fd, short events)
2508{
2509 WINPR_ASSERT(mutter);
2510
2511 struct pollfd fds[2] = { { fd, events, 0 },
2512 { GetEventFileDescriptor(mutter->stopEvent), POLLIN, 0 } };
2513 int ready = 0;
2514 do
2515 {
2516 ready = poll(fds, ARRAYSIZE(fds), MUTTER_CLIPBOARD_STALL_MS);
2517 } while ((ready < 0) && (errno == EINTR));
2518
2519 if ((ready <= 0) || (fds[1].revents != 0))
2520 return 0;
2521 return fds[0].revents;
2522}
2523
2524/* data thread: everything the session's application writes to the pipe */
2525WINPR_ATTR_NODISCARD
2526static BOOL mutter_clipboard_read_all(mutterShadowSubsystem* mutter, mutterClipboardJob* job)
2527{
2528 WINPR_ASSERT(mutter);
2529 WINPR_ASSERT(job);
2530
2531 if (fcntl(job->fd, F_SETFL, fcntl(job->fd, F_GETFL) | O_NONBLOCK) < 0)
2532 return FALSE;
2533
2534 size_t capacity = 0;
2535 while (1)
2536 {
2537 if (job->size + 4096 > capacity)
2538 {
2539 const size_t wanted = capacity ? capacity * 2 : 64ull * 1024ull;
2540 if (wanted > SHADOW_CLIPBOARD_MAX_SIZE)
2541 {
2542 WLog_WARN(TAG, "clipboard: the selection is too large");
2543 return FALSE;
2544 }
2545 BYTE* grown = realloc(job->data, wanted);
2546 if (!grown)
2547 return FALSE;
2548 job->data = grown;
2549 capacity = wanted;
2550 }
2551
2552 if (mutter_clipboard_wait(mutter, job->fd, POLLIN) == 0)
2553 return FALSE;
2554
2555 const ssize_t got = read(job->fd, &job->data[job->size], capacity - job->size);
2556 if (got == 0)
2557 return TRUE;
2558 if (got > 0)
2559 job->size += (size_t)got;
2560 else if ((errno != EAGAIN) && (errno != EINTR))
2561 return FALSE;
2562 }
2563}
2564
2565/* data thread: all of the client's data into the pipe to the session's application */
2566WINPR_ATTR_NODISCARD
2567static BOOL mutter_clipboard_write_all(mutterShadowSubsystem* mutter, mutterClipboardJob* job)
2568{
2569 WINPR_ASSERT(mutter);
2570 WINPR_ASSERT(job);
2571
2572 if (fcntl(job->fd, F_SETFL, fcntl(job->fd, F_GETFL) | O_NONBLOCK) < 0)
2573 return FALSE;
2574
2575 size_t done = 0;
2576 while (done < job->size)
2577 {
2578 if (mutter_clipboard_wait(mutter, job->fd, POLLOUT) == 0)
2579 return FALSE;
2580
2581 const ssize_t put = write(job->fd, &job->data[done], job->size - done); /* SIGPIPE: off */
2582 if (put > 0)
2583 done += (size_t)put;
2584 else if ((errno != EAGAIN) && (errno != EINTR))
2585 return FALSE;
2586 }
2587 return TRUE;
2588}
2589
2590WINPR_ATTR_NODISCARD
2591static DWORD WINAPI mutter_clipboard_thread(LPVOID arg)
2592{
2593 mutterShadowSubsystem* mutter = arg;
2594 WINPR_ASSERT(mutter);
2595
2596 while (MessageQueue_Wait(mutter->clipboardJobs))
2597 {
2598 wMessage message = WINPR_C_ARRAY_INIT;
2599 if (!MessageQueue_Peek(mutter->clipboardJobs, &message, TRUE))
2600 continue;
2601 if (message.id == WMQ_QUIT)
2602 break;
2603
2604 mutterClipboardJob* job = message.context;
2605 WINPR_ASSERT(job);
2606
2607 if (message.id == MUTTER_MSG_CLIPBOARD_READ)
2608 {
2609 const BOOL ok = mutter_clipboard_read_all(mutter, job);
2610 if (!ok)
2611 WLog_WARN(TAG, "clipboard: reading %s failed", job->mime);
2612 if (!shadow_clipboard_session_data(mutter->common.server, ok ? job->data : nullptr,
2613 job->size))
2614 WLog_DBG(TAG, "clipboard: no read was waiting");
2615 mutter_clipboard_job_free(job);
2616 }
2617 else
2618 {
2619 job->ok = mutter_clipboard_write_all(mutter, job);
2620 if (!job->ok)
2621 WLog_WARN(TAG, "clipboard: writing to the session failed");
2622 (void)close(job->fd); /* the end of the data to the application */
2623 job->fd = -1;
2624 if (!mutter_clipboard_post(mutter->common.MsgPipe->In, MUTTER_MSG_CLIPBOARD_WRITE_DONE,
2625 job))
2626 WLog_WARN(TAG, "clipboard: cannot finish a transfer");
2627 }
2628 }
2629
2630 ExitThread(0);
2631 return 0;
2632}
2633
2634/* The mime types are documented as "as"; GNOME Shell 50 sends "(as)". */
2635WINPR_ATTR_NODISCARD
2636static size_t mutter_clipboard_read_mimes(sd_bus_message* msg,
2637 const char* mimes[MUTTER_CLIPBOARD_MAX_MIMES])
2638{
2639 WINPR_ASSERT(msg);
2640
2641 const char* contents = nullptr;
2642 char type = 0;
2643 if ((sd_bus_message_peek_type(msg, &type, &contents) <= 0) || !contents)
2644 return 0;
2645
2646 const BOOL inStruct = contents[0] == '(';
2647 if ((strcmp(contents, "as") != 0) && (strcmp(contents, "(as)") != 0))
2648 {
2649 (void)sd_bus_message_skip(msg, "v");
2650 return 0;
2651 }
2652
2653 size_t count = 0;
2654 if (sd_bus_message_enter_container(msg, 'v', contents) <= 0)
2655 return 0;
2656 if (inStruct && (sd_bus_message_enter_container(msg, 'r', "as") <= 0))
2657 return 0;
2658 if (sd_bus_message_enter_container(msg, 'a', "s") > 0)
2659 {
2660 const char* mime = nullptr;
2661 while (sd_bus_message_read(msg, "s", &mime) > 0)
2662 {
2663 if (count < MUTTER_CLIPBOARD_MAX_MIMES)
2664 mimes[count++] = mime;
2665 }
2666 (void)sd_bus_message_exit_container(msg);
2667 }
2668 if (inStruct)
2669 (void)sd_bus_message_exit_container(msg);
2670 (void)sd_bus_message_exit_container(msg);
2671 return count;
2672}
2673
2674WINPR_ATTR_NODISCARD
2675static int mutter_on_selection_owner_changed(sd_bus_message* msg, void* data,
2676 WINPR_ATTR_UNUSED sd_bus_error* err)
2677{
2678 mutterShadowSubsystem* mutter = data;
2679 WINPR_ASSERT(mutter);
2680
2681 const char* mimes[MUTTER_CLIPBOARD_MAX_MIMES] = WINPR_C_ARRAY_INIT;
2682 size_t count = 0;
2683 int sessionIsOwner = 0;
2684
2685 if (sd_bus_message_enter_container(msg, 'a', "{sv}") <= 0)
2686 return 0;
2687 while (sd_bus_message_enter_container(msg, 'e', "sv") > 0)
2688 {
2689 const char* key = nullptr;
2690 if (sd_bus_message_read(msg, "s", &key) < 0)
2691 break;
2692
2693 if (strcmp(key, "mime-types") == 0)
2694 count = mutter_clipboard_read_mimes(msg, mimes);
2695 else if (strcmp(key, "session-is-owner") == 0)
2696 (void)sd_bus_message_read(msg, "v", "b", &sessionIsOwner);
2697 else
2698 (void)sd_bus_message_skip(msg, "v");
2699 (void)sd_bus_message_exit_container(msg);
2700 }
2701
2702 /* that is what a client copied, coming back */
2703 if (sessionIsOwner)
2704 return 0;
2705
2706 WLog_DBG(TAG, "clipboard: the session offers %" PRIuz " mime types", count);
2707 if (!shadow_clipboard_session_changed(mutter->common.server, mimes, count))
2708 WLog_WARN(TAG, "clipboard: cannot announce the session's clipboard");
2709 return 0;
2710}
2711
2712WINPR_ATTR_NODISCARD
2713static int mutter_on_selection_transfer(sd_bus_message* msg, void* data,
2714 WINPR_ATTR_UNUSED sd_bus_error* err)
2715{
2716 mutterShadowSubsystem* mutter = data;
2717 WINPR_ASSERT(mutter);
2718
2719 const char* mime = nullptr;
2720 uint32_t serial = 0;
2721 if (sd_bus_message_read(msg, "su", &mime, &serial) < 0)
2722 return 0;
2723
2724 if (!shadow_clipboard_session_request(mutter->common.server, serial, mime))
2725 mutter_clipboard_write_done(mutter, serial, FALSE);
2726 return 0;
2727}
2728
2729/* Without it the rest works: the clients simply get no clipboard channel. */
2730static void mutter_clipboard_enable(mutterShadowSubsystem* mutter)
2731{
2732 WINPR_ASSERT(mutter);
2733
2734 sd_bus_error err = SD_BUS_ERROR_NULL;
2735 if ((sd_bus_match_signal(mutter->bus, &mutter->selectionOwnerSlot, RD_NAME,
2736 mutter->rdSessionPath, RD_SESSION_IFACE, "SelectionOwnerChanged",
2737 mutter_on_selection_owner_changed, mutter) < 0) ||
2738 (sd_bus_match_signal(mutter->bus, &mutter->selectionTransferSlot, RD_NAME,
2739 mutter->rdSessionPath, RD_SESSION_IFACE, "SelectionTransfer",
2740 mutter_on_selection_transfer, mutter) < 0))
2741 WLog_WARN(TAG, "cannot watch the session's clipboard: no clipboard");
2742 else
2743 {
2744 /* Mutter answers with a SelectionOwnerChanged for what the session holds now */
2745 const int enabled =
2746 sd_bus_call_method(mutter->bus, RD_NAME, mutter->rdSessionPath, RD_SESSION_IFACE,
2747 "EnableClipboard", &err, nullptr, "a{sv}", 0);
2748 if (enabled < 0)
2749 {
2750 char buffer[128] = WINPR_C_ARRAY_INIT;
2751 WLog_WARN(TAG, "EnableClipboard failed: %s: no clipboard",
2752 err.message ? err.message : winpr_strerror(-enabled, buffer, sizeof(buffer)));
2753 }
2754 else
2755 {
2756 mutter->clipboardEnabled = TRUE;
2757 mutter->common.ClipboardOffer = mutter_clipboard_offer;
2758 mutter->common.ClipboardRead = mutter_clipboard_read;
2759 mutter->common.ClipboardProvide = mutter_clipboard_provide;
2760 }
2761 }
2762 sd_bus_error_free(&err);
2763}
2764
2765static void mutter_shadow_process_message(mutterShadowSubsystem* mutter, wMessage* message)
2766{
2767 WINPR_ASSERT(mutter);
2768 WINPR_ASSERT(message);
2769
2770 switch (message->id)
2771 {
2772 case SHADOW_MSG_IN_REFRESH_REQUEST_ID:
2773 mutter_shadow_refresh(mutter);
2774 break;
2775
2776 case MUTTER_MSG_CLIPBOARD_OFFER:
2777 mutter_clipboard_set_selection(mutter, message->context);
2778 break;
2779
2780 case MUTTER_MSG_CLIPBOARD_READ:
2781 mutter_clipboard_start_read(mutter, message);
2782 break;
2783
2784 case MUTTER_MSG_CLIPBOARD_PROVIDE:
2785 mutter_clipboard_start_write(mutter, message);
2786 break;
2787
2788 case MUTTER_MSG_CLIPBOARD_WRITE_DONE:
2789 {
2790 const mutterClipboardJob* job = message->context;
2791 WINPR_ASSERT(job);
2792 mutter_clipboard_write_done(mutter, job->serial, job->ok);
2793 }
2794 break;
2795
2796 default:
2797 WLog_ERR(TAG, "Unknown message id: %" PRIu32 "", message->id);
2798 break;
2799 }
2800
2801 if (message->Free)
2802 message->Free(message);
2803}
2804
2805/* Mutter ended the stream: record the same monitor again in the running
2806 * session. Its node comes with PipeWireStreamAdded (see mutter_reconnect). */
2807static void mutter_record_again(mutterShadowSubsystem* mutter)
2808{
2809 WINPR_ASSERT(mutter);
2810
2811 mutter->streamGone = FALSE;
2812 const char* connector = mutter->connectors[mutter->common.selectedMonitor];
2813 WLog_INFO(TAG, "Mutter ended the stream of %s (its layout changed): recording it again",
2814 connector[0] ? connector : "the primary monitor");
2815
2816 mutter->streamAddedSlot = sd_bus_slot_unref(mutter->streamAddedSlot);
2817 free(mutter->streamPath);
2818 mutter->streamPath = nullptr;
2819
2820 sd_bus_error err = SD_BUS_ERROR_NULL;
2821 int started = -1;
2822 if (mutter_record_monitor(mutter, connector))
2823 {
2824 mutter->awaitNode = TRUE;
2825 started = sd_bus_call_method(mutter->bus, SC_NAME, mutter->streamPath, SC_STREAM_IFACE,
2826 "Start", &err, nullptr, "");
2827 if (started < 0)
2828 {
2829 char buffer[128] = WINPR_C_ARRAY_INIT;
2830 WLog_ERR(TAG, "ScreenCast.Stream.Start failed: %s",
2831 err.message ? err.message : winpr_strerror(-started, buffer, sizeof(buffer)));
2832 }
2833 }
2834 sd_bus_error_free(&err);
2835
2836 if (started < 0)
2837 {
2838 /* the session closed meanwhile, or the monitor is gone (unplugged, or
2839 * disabled): nothing left to share either way */
2840 mutter->awaitNode = FALSE;
2841 while (sd_bus_process(mutter->bus, nullptr) > 0)
2842 {
2843 }
2844 if (!mutter->closed)
2845 mutter->monitorGone = TRUE;
2846 mutter->closed = TRUE;
2847 }
2848}
2849
2850/* The new node is there: the same PipeWire stream connects to it */
2851static void mutter_reconnect(mutterShadowSubsystem* mutter)
2852{
2853 WINPR_ASSERT(mutter);
2854
2855 mutter->reconnect = FALSE;
2856 if (!mutter_stream_connect_node(mutter))
2857 {
2858 mutter->monitorGone = TRUE;
2859 mutter->closed = TRUE;
2860 return;
2861 }
2862 mutter_read_stream_parameters(mutter);
2863 mutter->fullPaint = TRUE;
2864 WLog_INFO(TAG, "sharing again from PipeWire node %" PRIu32, mutter->nodeId);
2865}
2866
2867WINPR_ATTR_NODISCARD
2868static DWORD WINAPI mutter_shadow_subsystem_thread(LPVOID arg)
2869{
2870 mutterShadowSubsystem* mutter = (mutterShadowSubsystem*)arg;
2871 WINPR_ASSERT(mutter);
2872
2873 wMessagePipe* MsgPipe = mutter->common.MsgPipe;
2874 HANDLE events[6] = WINPR_C_ARRAY_INIT;
2875 DWORD count = 0;
2876
2877 HANDLE busEvent = CreateFileDescriptorEventA(nullptr, FALSE, FALSE, sd_bus_get_fd(mutter->bus),
2878 WINPR_FD_READ);
2879 HANDLE pwEvent = CreateFileDescriptorEventA(nullptr, FALSE, FALSE, pw_loop_get_fd(mutter->loop),
2880 WINPR_FD_READ);
2881 HANDLE eiEvent = mutter->ei ? CreateFileDescriptorEventA(nullptr, FALSE, FALSE,
2882 ei_get_fd(mutter->ei), WINPR_FD_READ)
2883 : nullptr;
2884 if (!busEvent || !pwEvent || (mutter->ei && !eiEvent))
2885 {
2886 WLog_ERR(TAG, "cannot wait on the session's connections");
2887 goto out;
2888 }
2889
2890 events[count++] = mutter->stopEvent;
2891 events[count++] = MessageQueue_Event(MsgPipe->In);
2892 events[count++] = mutter->clientsEvent;
2893 events[count++] = busEvent;
2894 events[count++] = pwEvent;
2895 if (eiEvent)
2896 events[count++] = eiEvent;
2897
2898 while (1)
2899 {
2900 /* sd-bus may want to run before its fd is readable (a queued message, a
2901 * call's deadline): its timeout is an absolute CLOCK_MONOTONIC time */
2902 uint64_t usec = UINT64_MAX;
2903 DWORD timeout = 1000;
2904 if ((sd_bus_get_timeout(mutter->bus, &usec) >= 0) && (usec != UINT64_MAX))
2905 {
2906 struct timespec ts = WINPR_C_ARRAY_INIT;
2907 (void)clock_gettime(CLOCK_MONOTONIC, &ts);
2908 const uint64_t now = (uint64_t)ts.tv_sec * 1000000ull + (uint64_t)ts.tv_nsec / 1000ull;
2909 const uint64_t waitMs = (usec > now) ? (usec - now + 999) / 1000 : 0;
2910 timeout = (DWORD)MIN(waitMs, 1000);
2911 }
2912 if (mutter->streamGone)
2913 timeout = MIN(timeout, 50);
2914
2915 const DWORD status = WaitForMultipleObjects(count, events, FALSE, timeout);
2916 if ((status == WAIT_FAILED) || (WaitForSingleObject(mutter->stopEvent, 0) == WAIT_OBJECT_0))
2917 break;
2918
2919 while (sd_bus_process(mutter->bus, nullptr) > 0)
2920 {
2921 }
2922 (void)sd_bus_flush(mutter->bus);
2923 if (mutter->closed && !mutter->closedLogged)
2924 {
2925 /* The user stopped the sharing (GNOME's screen sharing indicator, or anything
2926 * that inhibits remote access), or GNOME Shell is gone. The server ends with
2927 * success, so a service restarted on failure does not share the screen again
2928 * against the user's choice. */
2929 if (mutter->shellGone)
2930 WLog_WARN(TAG, "GNOME Shell left the session: the server ends");
2931 else if (mutter->monitorGone)
2932 WLog_WARN(TAG, "the shared monitor is gone: the server ends");
2933 else
2934 WLog_WARN(TAG, "the GNOME session stopped the sharing (its user did, or GNOME "
2935 "Shell is ending): the server ends");
2936 mutter->closedLogged = TRUE;
2937 if (mutter->common.server)
2938 (void)SetEvent(mutter->common.server->StopEvent);
2939 }
2940
2941 if (WaitForSingleObject(mutter->clientsEvent, 0) == WAIT_OBJECT_0)
2942 {
2943 /* reset before reading the count: a change after this sets it again */
2944 (void)ResetEvent(mutter->clientsEvent);
2945 mutter_audio_follow_clients(mutter);
2946 }
2947
2948 pw_loop_enter(mutter->loop);
2949 (void)pw_loop_iterate(mutter->loop, 0);
2950 pw_loop_leave(mutter->loop);
2951
2952 /* a Closed that follows the node's end (the user stopped the sharing)
2953 * wins over recording the monitor again */
2954 if (mutter->streamGone && !mutter->closed &&
2955 (GetTickCount64() - mutter->streamGoneAt >= MUTTER_STREAM_GONE_GRACE_MS))
2956 mutter_record_again(mutter);
2957 if (mutter->reconnect && !mutter->closed)
2958 mutter_reconnect(mutter);
2959
2960 if (mutter->ei)
2961 {
2962 EnterCriticalSection(&mutter->eiLock);
2963 mutter_process_ei_events(mutter);
2964 LeaveCriticalSection(&mutter->eiLock);
2965 }
2966
2967 wMessage message = WINPR_C_ARRAY_INIT;
2968 while (MessageQueue_Peek(MsgPipe->In, &message, TRUE))
2969 {
2970 if (message.id == WMQ_QUIT)
2971 goto out;
2972 mutter_shadow_process_message(mutter, &message);
2973 }
2974 }
2975
2976out:
2977 if (busEvent)
2978 (void)CloseHandle(busEvent);
2979 if (pwEvent)
2980 (void)CloseHandle(pwEvent);
2981 if (eiEvent)
2982 (void)CloseHandle(eiEvent);
2983 ExitThread(0);
2984 return 0;
2985}
2986
2987WINPR_ATTR_NODISCARD
2988static int mutter_shadow_subsystem_init(rdpShadowSubsystem* subsystem)
2989{
2990 mutterShadowSubsystem* mutter = (mutterShadowSubsystem*)subsystem;
2991 WINPR_ASSERT(mutter);
2992
2993 const int opened = sd_bus_open_user(&mutter->bus);
2994 if (opened < 0)
2995 {
2996 char buffer[128] = WINPR_C_ARRAY_INIT;
2997 WLog_ERR(TAG,
2998 "cannot connect to the session bus: %s (start the server inside the GNOME "
2999 "session, as its user)",
3000 winpr_strerror(-opened, buffer, sizeof(buffer)));
3001 return -1;
3002 }
3003
3004 if (!mutter_wait_for_name(mutter->bus, RD_NAME, MUTTER_NAME_TIMEOUT_MS))
3005 {
3006 WLog_ERR(TAG, "%s is not on the session bus: is this a GNOME session?", RD_NAME);
3007 return -1;
3008 }
3009
3010 UINT32 count = 0;
3011 mutter->virtualMonitor = mutter_virtual_size(&mutter->virtualWidth, &mutter->virtualHeight);
3012 {
3013 /* on unless "0" */
3014 // NOLINTNEXTLINE(concurrency-mt-unsafe)
3015 const char* check = getenv(MUTTER_DAMAGE_CHECK_ENV);
3016 mutter->damageCheck = !check || (strcmp(check, "0") != 0);
3017 if (!mutter->damageCheck)
3018 WLog_INFO(TAG, "the check for changes Mutter left out of its damage is off");
3019 }
3020 if (mutter->virtualMonitor)
3021 {
3022 /* one monitor, this server's own */
3023 subsystem->monitors[0] = (MONITOR_DEF){ 0, 0, (INT32)mutter->virtualWidth - 1,
3024 (INT32)mutter->virtualHeight - 1, 1 };
3025 mutter->connectors[0][0] = '\0';
3026 count = 1;
3027 }
3028 else
3029 {
3030 WLog_DBG(TAG, "DisplayConfig.GetCurrentState");
3031 count = mutter_list_monitors(mutter->bus, subsystem->monitors, mutter->connectors,
3032 MUTTER_SHADOW_MAX_MONITORS);
3033 }
3034 if (count == 0)
3035 {
3036 /* no layout: the primary monitor, sized by its stream */
3037 WLog_WARN(TAG, "no monitor layout from Mutter: sharing the primary monitor");
3038 subsystem->monitors[0] = (MONITOR_DEF){ 0, 0, 0, 0, 1 };
3039 mutter->connectors[0][0] = '\0';
3040 count = 1;
3041 }
3042 subsystem->numMonitors = count;
3043 if (subsystem->selectedMonitor >= count)
3044 subsystem->selectedMonitor = 0;
3045
3046 const char* connector = mutter->connectors[subsystem->selectedMonitor];
3047 if (!mutter_setup_sessions(mutter, connector))
3048 return -1;
3049 if (!mutter_connect_stream(mutter))
3050 return -1;
3051
3052 mutter_set_monitor_size(mutter);
3053
3054 if (!mutter_audio_init(mutter))
3055 WLog_WARN(TAG, "sound: no capture of the default sink: no sound");
3056
3057 /* mirrored monitors carry the cursor in their frames and the clients get a
3058 * transparent shape; a virtual monitor sends the cursor's shapes. Either way
3059 * shapes go out, which needs ShowMouseCursor (otherwise only set by
3060 * /server-side-cursor) */
3061 if (subsystem->server)
3062 subsystem->server->ShowMouseCursor = TRUE;
3063
3064 if (mutter->virtualMonitor)
3065 WLog_INFO(TAG, "sharing a virtual monitor of its own: %" PRIu32 "x%" PRIu32 ", input %s",
3066 mutter->width, mutter->height, mutter->ei ? "through libei" : "off");
3067 else
3068 WLog_INFO(TAG,
3069 "sharing monitor %s (%" PRIu32 " of %" PRIu32 "): %" PRIu32 "x%" PRIu32
3070 ", input %s",
3071 connector[0] ? connector : "(primary)", subsystem->selectedMonitor, count,
3072 mutter->width, mutter->height, mutter->ei ? "through libei" : "off");
3073 return 1;
3074}
3075
3076WINPR_ATTR_NODISCARD
3077static int mutter_shadow_subsystem_uninit(rdpShadowSubsystem* subsystem)
3078{
3079 mutterShadowSubsystem* mutter = (mutterShadowSubsystem*)subsystem;
3080 if (!mutter)
3081 return -1;
3082
3083 mutter_audio_uninit(mutter);
3084 if (mutter->stream)
3085 pw_stream_destroy(mutter->stream);
3086 mutter->stream = nullptr;
3087 if (mutter->registry)
3088 {
3089 spa_hook_remove(&mutter->registryListener);
3090 pw_proxy_destroy((struct pw_proxy*)mutter->registry);
3091 }
3092 mutter->registry = nullptr;
3093 if (mutter->core)
3094 pw_core_disconnect(mutter->core);
3095 mutter->core = nullptr;
3096 if (mutter->context)
3097 pw_context_destroy(mutter->context);
3098 mutter->context = nullptr;
3099 if (mutter->loop)
3100 pw_loop_destroy(mutter->loop);
3101 mutter->loop = nullptr;
3102
3103 EnterCriticalSection(&mutter->eiLock);
3104 mutter->keyboard = ei_device_unref(mutter->keyboard);
3105 mutter->pointer = ei_device_unref(mutter->pointer);
3106 mutter->ei = ei_unref(mutter->ei);
3107 mutter->keyboardReady = FALSE;
3108 mutter->pointerReady = FALSE;
3109 LeaveCriticalSection(&mutter->eiLock);
3110
3111 if (mutter->bus)
3112 {
3113 if (mutter->started && !mutter->closed && mutter->rdSessionPath)
3114 (void)sd_bus_call_method(mutter->bus, RD_NAME, mutter->rdSessionPath, RD_SESSION_IFACE,
3115 "Stop", nullptr, nullptr, "");
3116 mutter->streamAddedSlot = sd_bus_slot_unref(mutter->streamAddedSlot);
3117 mutter->closedSlot = sd_bus_slot_unref(mutter->closedSlot);
3118 mutter->ownerSlot = sd_bus_slot_unref(mutter->ownerSlot);
3119 mutter->selectionOwnerSlot = sd_bus_slot_unref(mutter->selectionOwnerSlot);
3120 mutter->selectionTransferSlot = sd_bus_slot_unref(mutter->selectionTransferSlot);
3121 mutter->bus = sd_bus_flush_close_unref(mutter->bus);
3122 }
3123 mutter->started = FALSE;
3124
3125 free(mutter->rdSessionPath);
3126 mutter->rdSessionPath = nullptr;
3127 free(mutter->scSessionPath);
3128 mutter->scSessionPath = nullptr;
3129 free(mutter->streamPath);
3130 mutter->streamPath = nullptr;
3131 free(mutter->mappingId);
3132 mutter->mappingId = nullptr;
3133 return 1;
3134}
3135
3136WINPR_ATTR_NODISCARD
3137static int mutter_shadow_subsystem_start(rdpShadowSubsystem* subsystem)
3138{
3139 mutterShadowSubsystem* mutter = (mutterShadowSubsystem*)subsystem;
3140 if (!mutter || !mutter->bus || !mutter->loop)
3141 return -1;
3142
3143 (void)ResetEvent(mutter->stopEvent);
3144 if (mutter->clipboardEnabled)
3145 {
3146 mutter->clipboardJobs = MessageQueue_New(nullptr);
3147 if (mutter->clipboardJobs)
3148 mutter->clipboardThread =
3149 CreateThread(nullptr, 0, mutter_clipboard_thread, mutter, 0, nullptr);
3150 if (!mutter->clipboardThread)
3151 {
3152 WLog_ERR(TAG, "Failed to create the clipboard thread");
3153 return -1;
3154 }
3155 }
3156
3157 mutter->thread = CreateThread(nullptr, 0, mutter_shadow_subsystem_thread, mutter, 0, nullptr);
3158 if (!mutter->thread)
3159 {
3160 WLog_ERR(TAG, "Failed to create thread");
3161 return -1;
3162 }
3163 return 1;
3164}
3165
3166WINPR_ATTR_NODISCARD
3167static int mutter_shadow_subsystem_stop(rdpShadowSubsystem* subsystem)
3168{
3169 mutterShadowSubsystem* mutter = (mutterShadowSubsystem*)subsystem;
3170 if (!mutter)
3171 return -1;
3172
3173 if (mutter->thread)
3174 {
3175 (void)SetEvent(mutter->stopEvent);
3176 (void)WaitForSingleObject(mutter->thread, INFINITE);
3177 (void)CloseHandle(mutter->thread);
3178 mutter->thread = nullptr;
3179 }
3180
3181 if (mutter->clipboardThread)
3182 {
3183 (void)SetEvent(mutter->stopEvent); /* a transfer ends at once */
3184 if (!MessageQueue_PostQuit(mutter->clipboardJobs, 0))
3185 WLog_WARN(TAG, "cannot stop the clipboard thread");
3186 (void)WaitForSingleObject(mutter->clipboardThread, INFINITE);
3187 (void)CloseHandle(mutter->clipboardThread);
3188 mutter->clipboardThread = nullptr;
3189 }
3190 MessageQueue_Free(mutter->clipboardJobs);
3191 mutter->clipboardJobs = nullptr;
3192 return 1;
3193}
3194
3195WINPR_ATTR_NODISCARD
3196static BOOL mutter_shadow_client_connect(rdpShadowSubsystem* subsystem,
3197 WINPR_ATTR_UNUSED rdpShadowClient* client)
3198{
3199 mutterShadowSubsystem* mutter = (mutterShadowSubsystem*)subsystem;
3200 WINPR_ASSERT(mutter);
3201
3202 /* a new client gets the cursor shape with the next one seen */
3203 mutter->cursorResend = TRUE;
3204 (void)InterlockedIncrement(&mutter->clients);
3205 (void)SetEvent(mutter->clientsEvent);
3206 return TRUE;
3207}
3208
3209static void mutter_shadow_client_disconnect(rdpShadowSubsystem* subsystem,
3210 WINPR_ATTR_UNUSED rdpShadowClient* client)
3211{
3212 mutterShadowSubsystem* mutter = (mutterShadowSubsystem*)subsystem;
3213 WINPR_ASSERT(mutter);
3214
3215 (void)InterlockedDecrement(&mutter->clients);
3216 (void)SetEvent(mutter->clientsEvent);
3217}
3218
3219static void mutter_shadow_subsystem_free(rdpShadowSubsystem* subsystem)
3220{
3221 mutterShadowSubsystem* mutter = (mutterShadowSubsystem*)subsystem;
3222 if (!mutter)
3223 return;
3224
3225 if (mutter_shadow_subsystem_stop(subsystem) < 0)
3226 WLog_WARN(TAG, "the subsystem thread did not stop");
3227 if (mutter_shadow_subsystem_uninit(subsystem) < 0)
3228 WLog_WARN(TAG, "the subsystem was not uninitialized");
3229 DeleteCriticalSection(&mutter->eiLock);
3230 if (mutter->stopEvent)
3231 (void)CloseHandle(mutter->stopEvent);
3232 if (mutter->clientsEvent)
3233 (void)CloseHandle(mutter->clientsEvent);
3234 free(mutter->cursorPixels);
3235 free(mutter);
3236}
3237
3238WINPR_ATTR_NODISCARD
3239static rdpShadowSubsystem* mutter_shadow_subsystem_new(void)
3240{
3241 mutterShadowSubsystem* mutter = calloc(1, sizeof(mutterShadowSubsystem));
3242 if (!mutter)
3243 return nullptr;
3244
3245 if (!InitializeCriticalSectionAndSpinCount(&mutter->eiLock, 4000))
3246 {
3247 free(mutter);
3248 return nullptr;
3249 }
3250
3251 mutter->stopEvent = CreateEvent(nullptr, TRUE, FALSE, nullptr);
3252 /* manual reset: WinPR has no auto-reset events (it creates a manual-reset one and
3253 * logs an error), so the thread resets it */
3254 mutter->clientsEvent = CreateEvent(nullptr, TRUE, FALSE, nullptr);
3255 if (!mutter->stopEvent || !mutter->clientsEvent)
3256 {
3257 mutter_shadow_subsystem_free(&mutter->common);
3258 return nullptr;
3259 }
3260
3261 mutter->cursorX = -1;
3262 mutter->cursorY = -1;
3263 mutter->audioSourceFormat.wFormatTag = WAVE_FORMAT_PCM;
3264 mutter->audioSourceFormat.nChannels = MUTTER_AUDIO_CHANNELS;
3265 mutter->audioSourceFormat.nSamplesPerSec = MUTTER_AUDIO_RATE;
3266 mutter->audioSourceFormat.nAvgBytesPerSec = MUTTER_AUDIO_RATE * MUTTER_AUDIO_FRAME_BYTES;
3267 mutter->audioSourceFormat.nBlockAlign = MUTTER_AUDIO_FRAME_BYTES;
3268 mutter->audioSourceFormat.wBitsPerSample = 16;
3269 mutter->common.SynchronizeEvent = mutter_shadow_input_synchronize_event;
3270 mutter->common.KeyboardEvent = mutter_shadow_input_keyboard_event;
3271 mutter->common.UnicodeKeyboardEvent = mutter_shadow_input_unicode_keyboard_event;
3272 mutter->common.MouseEvent = mutter_shadow_input_mouse_event;
3273 mutter->common.ExtendedMouseEvent = mutter_shadow_input_extended_mouse_event;
3274 mutter->common.ClientConnect = mutter_shadow_client_connect;
3275 mutter->common.ClientDisconnect = mutter_shadow_client_disconnect;
3276#ifdef WITH_PAM
3277 mutter->common.Authenticate = mutter_shadow_pam_authenticate;
3278#endif
3279 return &mutter->common;
3280}
3281
3282FREERDP_ENTRY_POINT(WINPR_ATTR_NODISCARD const char* MutterShadowSubsystemName(void))
3283{
3284 return "Mutter";
3285}
3286
3287FREERDP_ENTRY_POINT(
3288 WINPR_ATTR_NODISCARD int MutterShadowSubsystemEntry(RDP_SHADOW_ENTRY_POINTS* pEntryPoints))
3289{
3290 WINPR_ASSERT(pEntryPoints);
3291
3292 pEntryPoints->New = mutter_shadow_subsystem_new;
3293 pEntryPoints->Free = mutter_shadow_subsystem_free;
3294 pEntryPoints->Init = mutter_shadow_subsystem_init;
3295 pEntryPoints->Uninit = mutter_shadow_subsystem_uninit;
3296 pEntryPoints->Start = mutter_shadow_subsystem_start;
3297 pEntryPoints->Stop = mutter_shadow_subsystem_stop;
3298 pEntryPoints->EnumMonitors = mutter_shadow_enum_monitors;
3299 return 1;
3300}