47#include <linux/input-event-codes.h>
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>
54#include <winpr/assert.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>
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>
71#include <security/pam_appl.h>
74#include "mutter_shadow.h"
76#define TAG SERVER_TAG("shadow.mutter")
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"
89#define MUTTER_CURSOR_MODE_EMBEDDED 1u
90#define MUTTER_CURSOR_MODE_METADATA 2u
91#define MUTTER_VIRTUAL_ENV "FREERDP_SHADOW_MUTTER_VIRTUAL"
93#define MUTTER_DAMAGE_CHECK_ENV "FREERDP_SHADOW_MUTTER_DAMAGE_CHECK"
94#define MUTTER_CHECK_TILE 64
98static BOOL mutter_virtual_size(UINT32* width, UINT32* height)
101 WINPR_ASSERT(height);
107 const char* env = getenv(MUTTER_VIRTUAL_ENV);
113 char* next =
nullptr;
114 unsigned long w = strtoul(env, &next, 0);
115 if ((errno != 0) || (w < 64) || (w > 8192))
117 if (!next || (*next !=
'x'))
120 unsigned long h = strtoul(&next[1],
nullptr, 0);
121 if ((errno != 0) || (h < 64) || (h > 8192))
124 *width = WINPR_ASSERTING_INT_CAST(UINT32, w);
125 *height = WINPR_ASSERTING_INT_CAST(UINT32, h);
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
136#define MUTTER_SHADOW_CURSOR_MAX 96u
140#define MUTTER_SHADOW_CURSOR_QUIET_MS 500u
143#define MUTTER_MAX_DAMAGE 16u
147#define MUTTER_NAME_TIMEOUT_MS 10000u
148#define MUTTER_STREAM_TIMEOUT_MS 5000u
149#define MUTTER_FORMAT_TIMEOUT_MS 5000u
151#define MUTTER_STREAM_GONE_GRACE_MS 250u
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
159#define MUTTER_CLIPBOARD_STALL_MS 5000
160#define MUTTER_CLIPBOARD_CALL_USEC (2ull * 1000ull * 1000ull)
162#define MUTTER_CLIPBOARD_MAX_MIMES 64
171#define MUTTER_AUDIO_RATE 48000u
172#define MUTTER_AUDIO_CHANNELS 2u
173#define MUTTER_AUDIO_FRAME_BYTES (MUTTER_AUDIO_CHANNELS * 2ull)
175#define MUTTER_AUDIO_MESSAGE_FRAMES (MUTTER_AUDIO_RATE / 50u)
177#define MUTTER_AUDIO_LOG_MS 5000u
181#define MUTTER_MOD_LOCK (1u << 1)
182#define MUTTER_MOD_NUM (1u << 4)
188 char connector[MUTTER_SHADOW_CONNECTOR_SIZE];
201 char connector[MUTTER_SHADOW_CONNECTOR_SIZE];
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 },
215static BOOL mutter_wait_for_name(sd_bus* bus,
const char* name, UINT64 timeoutMs)
220 const UINT64 start = GetTickCount64();
225 sd_bus_error err = SD_BUS_ERROR_NULL;
226 sd_bus_message* reply =
nullptr;
230 sd_bus_call_method(bus,
"org.freedesktop.DBus",
"/org/freedesktop/DBus",
231 "org.freedesktop.DBus",
"NameHasOwner", &err, &reply,
"s", name);
233 (called >= 0) && (sd_bus_message_read(reply,
"b", &hasOwner) >= 0) && hasOwner;
234 sd_bus_message_unref(reply);
235 sd_bus_error_free(&err);
239 if (GetTickCount64() - start > timeoutMs)
243 WLog_INFO(TAG,
"waiting for %s on the session bus", name);
252static BOOL mutter_call_for_path(sd_bus* bus, sd_bus_message* call,
const char* what,
char** out)
259 sd_bus_error err = SD_BUS_ERROR_NULL;
260 sd_bus_message* reply =
nullptr;
261 const char* path =
nullptr;
263 const int called = sd_bus_call(bus, call, 0, &err, &reply);
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);
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;
282static BOOL mutter_read_dict(sd_bus_message* msg,
const char* key,
char type,
void* value)
288 const char contents[2] = { type,
'\0' };
290 if (sd_bus_message_enter_container(msg,
'a',
"{sv}") < 0)
295 const int entered = sd_bus_message_enter_container(msg,
'e',
"sv");
301 const char* name =
nullptr;
302 if (sd_bus_message_read(msg,
"s", &name) < 0)
304 if (name && (strcmp(name, key) == 0))
306 if (sd_bus_message_read(msg,
"v", contents, value) < 0)
309 else if (sd_bus_message_skip(msg,
"v") < 0)
312 if (sd_bus_message_exit_container(msg) < 0)
316 return sd_bus_message_exit_container(msg) >= 0;
322static BOOL mutter_read_monitor(sd_bus_message* reply, MUTTER_MODE* mode, BOOL* more)
328 const int entered = sd_bus_message_enter_container(reply,
'r',
"(ssss)a(siiddada{sv})a{sv}");
331 *more = (entered > 0);
335 const char* connector =
nullptr;
336 if (sd_bus_message_read(reply,
"(ssss)", &connector,
nullptr,
nullptr,
nullptr) < 0)
338 (void)_snprintf(mode->connector,
sizeof(mode->connector),
"%s", connector ? connector :
"");
340 if (sd_bus_message_enter_container(reply,
'a',
"(siiddada{sv})") < 0)
344 const int modeEntered = sd_bus_message_enter_container(reply,
'r',
"siiddada{sv}");
347 if (modeEntered == 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', ¤t) ||
356 (sd_bus_message_exit_container(reply) < 0))
362 mode->height = height;
366 return (sd_bus_message_exit_container(reply) >= 0) &&
367 (sd_bus_message_skip(reply,
"a{sv}") >= 0) &&
368 (sd_bus_message_exit_container(reply) >= 0);
374static BOOL mutter_read_logical(sd_bus_message* reply, MUTTER_LOGICAL* logical, BOOL* more)
377 WINPR_ASSERT(logical);
380 const int entered = sd_bus_message_enter_container(reply,
'r',
"iiduba(ssss)a{sv}");
383 *more = (entered > 0);
390 uint32_t transform = 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))
396 const char* connector =
nullptr;
399 const char* name =
nullptr;
400 const int read = sd_bus_message_read(reply,
"(ssss)", &name,
nullptr,
nullptr,
nullptr);
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 :
"");
417 return (sd_bus_message_exit_container(reply) >= 0) &&
418 (sd_bus_message_skip(reply,
"a{sv}") >= 0) &&
419 (sd_bus_message_exit_container(reply) >= 0);
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)
430 WINPR_ASSERT(nmodes);
431 WINPR_ASSERT(logical);
432 WINPR_ASSERT(nlogical);
433 WINPR_ASSERT(layoutMode);
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))
440 MUTTER_MODE mode = WINPR_C_ARRAY_INIT;
442 if (!mutter_read_monitor(reply, &mode, &more))
446 if (*nmodes < MUTTER_SHADOW_MAX_MONITORS)
447 modes[(*nmodes)++] = mode;
449 if ((sd_bus_message_exit_container(reply) < 0) ||
450 (sd_bus_message_enter_container(reply,
'a',
"(iiduba(ssss)a{sv})") < 0))
455 MUTTER_LOGICAL entry = WINPR_C_ARRAY_INIT;
457 if (!mutter_read_logical(reply, &entry, &more))
461 if (*nlogical < MUTTER_SHADOW_MAX_MONITORS)
462 logical[(*nlogical)++] = entry;
464 if (sd_bus_message_exit_container(reply) < 0)
467 return mutter_read_dict(reply,
"layout-mode",
'u', layoutMode);
473static UINT32 mutter_list_monitors(sd_bus* bus,
MONITOR_DEF* monitors,
474 char (*connectors)[MUTTER_SHADOW_CONNECTOR_SIZE], UINT32 max)
477 WINPR_ASSERT(monitors);
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;
485 uint32_t layoutMode = 1;
489 sd_bus_call_method(bus, DC_NAME, DC_PATH, DC_NAME,
"GetCurrentState", &err, &reply,
"");
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);
499 const BOOL parsed = mutter_read_state(reply, modes, &nmodes, logical, &nlogical, &layoutMode);
500 sd_bus_message_unref(reply);
502 WLog_WARN(TAG,
"GetCurrentState: unexpected reply");
505 for (
int pass = 0; pass < 2; pass++)
507 for (
size_t x = 0; (x < nlogical) && (count < max); x++)
509 if ((pass == 0) != (logical[x].primary != 0))
514 for (
size_t m = 0; m < nmodes; m++)
516 if (strcmp(modes[m].connector, logical[x].connector) == 0)
518 width = modes[m].width;
519 height = modes[m].height;
522 if ((width <= 0) || (height <= 0))
526 if (logical[x].transform & 1)
528 const INT32 tmp = width;
534 width = (INT32)(width / logical[x].scale + 0.5);
535 height = (INT32)(height / logical[x].scale + 0.5);
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;
545 (void)_snprintf(connectors[count], MUTTER_SHADOW_CONNECTOR_SIZE,
"%s",
546 logical[x].connector);
555static UINT32 mutter_shadow_enum_monitors(
MONITOR_DEF* monitors, UINT32 maxMonitors)
557 WINPR_ASSERT(monitors);
561 if ((maxMonitors > 0) && mutter_virtual_size(&width, &height))
564 monitors[0] = (
MONITOR_DEF){ 0, 0, (INT32)width - 1, (INT32)height - 1, 1 };
568 sd_bus* bus =
nullptr;
569 const int opened = sd_bus_open_user(&bus);
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)));
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);
585static int mutter_on_stream_added(sd_bus_message* msg,
void* data,
586 WINPR_ATTR_UNUSED sd_bus_error* err)
588 mutterShadowSubsystem* mutter = (mutterShadowSubsystem*)data;
590 WINPR_ASSERT(mutter);
593 if (sd_bus_message_read(msg,
"u", &node) >= 0)
595 mutter->nodeId = node;
596 mutter->haveNode = TRUE;
597 WLog_DBG(TAG,
"PipeWire node %" PRIu32, mutter->nodeId);
598 if (mutter->awaitNode)
600 mutter->awaitNode = FALSE;
601 mutter->reconnect = TRUE;
608static int mutter_on_closed(WINPR_ATTR_UNUSED sd_bus_message* msg,
void* data,
609 WINPR_ATTR_UNUSED sd_bus_error* err)
611 mutterShadowSubsystem* mutter = (mutterShadowSubsystem*)data;
612 WINPR_ASSERT(mutter);
614 mutter->closed = TRUE;
620static int mutter_on_name_owner_changed(sd_bus_message* msg,
void* data,
621 WINPR_ATTR_UNUSED sd_bus_error* err)
623 mutterShadowSubsystem* mutter = (mutterShadowSubsystem*)data;
625 WINPR_ASSERT(mutter);
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'))
633 mutter->shellGone = TRUE;
634 mutter->closed = TRUE;
640static void mutter_read_stream_parameters(mutterShadowSubsystem* mutter)
642 WINPR_ASSERT(mutter);
644 sd_bus_error err = SD_BUS_ERROR_NULL;
645 sd_bus_message* reply =
nullptr;
646 const char* mappingId =
nullptr;
648 const int got = sd_bus_get_property(mutter->bus, SC_NAME, mutter->streamPath, SC_STREAM_IFACE,
649 "Parameters", &err, &reply,
"a{sv}");
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);
659 if (mutter_read_dict(reply,
"mapping-id",
's', (
void*)(&mappingId)) && mappingId)
662 char* copy = _strdup(mappingId);
663 EnterCriticalSection(&mutter->eiLock);
664 free(mutter->mappingId);
665 mutter->mappingId = copy;
666 LeaveCriticalSection(&mutter->eiLock);
668 sd_bus_message_unref(reply);
681static BOOL mutter_record_monitor(mutterShadowSubsystem* mutter,
const char* connector)
683 WINPR_ASSERT(mutter);
684 WINPR_ASSERT(connector);
686 sd_bus_message* call =
nullptr;
689 if (mutter->virtualMonitor)
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))
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))
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))
711 WLog_DBG(TAG,
"ScreenCast.Session.RecordMonitor('%s')", connector);
712 if (!mutter_call_for_path(mutter->bus, call,
"ScreenCast.Session.RecordMonitor",
713 &mutter->streamPath))
717 if (sd_bus_match_signal(mutter->bus, &mutter->streamAddedSlot, SC_NAME, mutter->streamPath,
718 SC_STREAM_IFACE,
"PipeWireStreamAdded", mutter_on_stream_added,
721 WLog_ERR(TAG,
"cannot watch the stream's signals");
726 sd_bus_message_unref(call);
729static void mutter_clipboard_enable(mutterShadowSubsystem* mutter);
732static BOOL mutter_setup_sessions(mutterShadowSubsystem* mutter,
const char* connector)
734 WINPR_ASSERT(mutter);
735 WINPR_ASSERT(connector);
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;
744 if (sd_bus_message_new_method_call(mutter->bus, &call, RD_NAME, RD_PATH, RD_NAME,
745 "CreateSession") < 0)
747 WLog_DBG(TAG,
"RemoteDesktop.CreateSession");
748 if (!mutter_call_for_path(mutter->bus, call,
"RemoteDesktop.CreateSession",
749 &mutter->rdSessionPath))
751 call = sd_bus_message_unref(call);
753 const int gotId = sd_bus_get_property_string(mutter->bus, RD_NAME, mutter->rdSessionPath,
754 RD_SESSION_IFACE,
"SessionId", &err, &sessionId);
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)));
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))
768 WLog_DBG(TAG,
"ScreenCast.CreateSession");
769 if (!mutter_call_for_path(mutter->bus, call,
"ScreenCast.CreateSession",
770 &mutter->scSessionPath))
772 call = sd_bus_message_unref(call);
775 if (!mutter_record_monitor(mutter, connector))
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))
786 WLog_ERR(TAG,
"cannot watch the stream's signals");
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,
"");
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)));
801 mutter->started = TRUE;
803 const UINT64 start = GetTickCount64();
804 while (!mutter->haveNode)
806 const int processed = sd_bus_process(mutter->bus,
nullptr);
811 if (GetTickCount64() - start > MUTTER_STREAM_TIMEOUT_MS)
813 WLog_ERR(TAG,
"Mutter announced no PipeWire stream");
816 (void)sd_bus_wait(mutter->bus, 100ull * 1000ull);
818 mutter_read_stream_parameters(mutter);
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))
827 WLog_WARN(TAG,
"ConnectToEIS failed: %s: the clients can only watch",
828 err.message ? err.message :
"bad reply");
833 fd = fcntl(fd, F_DUPFD_CLOEXEC, 3);
834 mutter->ei = ei_new_sender(mutter);
837 ei_configure_name(mutter->ei,
"FreeRDP shadow server");
838 if (ei_setup_backend_fd(mutter->ei, fd) != 0)
840 WLog_WARN(TAG,
"libei setup failed: the clients can only watch");
841 mutter->ei = ei_unref(mutter->ei);
848 mutter_clipboard_enable(mutter);
852 sd_bus_error_free(&err);
853 sd_bus_message_unref(reply);
854 sd_bus_message_unref(call);
863static void mutter_cursor_msg_free(UINT32
id, SHADOW_MSG_OUT* msg)
865 if (msg && (
id == SHADOW_MSG_OUT_POINTER_ALPHA_UPDATE_ID))
868 free(shape->xorMaskData);
869 free(shape->andMaskData);
875static UINT32 mutter_cursor_hash(
const BYTE* pixels, UINT32 width, UINT32 height, UINT32 xHot,
878 WINPR_ASSERT(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++)
887 for (
size_t x = 0; x < sizes[p]; x++)
896static void mutter_cursor_send_shape(mutterShadowSubsystem* mutter,
const BYTE* pixels,
897 UINT32 width, UINT32 height, UINT32 xHot, UINT32 yHot)
899 WINPR_ASSERT(mutter);
900 WINPR_ASSERT(pixels);
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))
916 msg->height = height;
918 if (shadow_subsystem_pointer_convert_alpha_pointer_data_to_format(pixels, PIXEL_FORMAT_RGBA32,
919 TRUE, width, height, msg) < 0)
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,
930 mutter->cursorHash = hash;
931 mutter->cursorKnown = TRUE;
932 mutter->cursorResend = FALSE;
936static void mutter_cursor_send_position(mutterShadowSubsystem* mutter, INT32 x, INT32 y)
938 WINPR_ASSERT(mutter);
940 if ((x == mutter->cursorX) && (y == mutter->cursorY))
946 EnterCriticalSection(&mutter->eiLock);
947 const UINT64 input = mutter->pointerInput;
948 LeaveCriticalSection(&mutter->eiLock);
949 if (GetTickCount64() - input < MUTTER_SHADOW_CURSOR_QUIET_MS)
952 if ((x < 0) || (y < 0) || ((UINT32)x >= mutter->width) || ((UINT32)y >= mutter->height))
955 mutter->common.pointerX = (UINT32)x;
956 mutter->common.pointerY = (UINT32)y;
962 msg->xPos = (UINT32)x;
963 msg->yPos = (UINT32)y;
964 msg->common.Free = mutter_cursor_msg_free;
966 if (shadow_client_boardcast_msg(mutter->common.server,
nullptr,
967 SHADOW_MSG_OUT_POINTER_POSITION_UPDATE_ID, &msg->common,
969 WLog_DBG(TAG,
"the pointer position was not sent");
974static void mutter_cursor_remember(mutterShadowSubsystem* mutter,
const BYTE* pixels, UINT32 width,
975 UINT32 height, UINT32 xHot, UINT32 yHot)
977 WINPR_ASSERT(mutter);
978 WINPR_ASSERT(pixels);
980 const size_t size = 4ull * width * height;
981 BYTE* copy = malloc(size);
984 memcpy(copy, pixels, size);
986 free(mutter->cursorPixels);
987 mutter->cursorPixels = copy;
988 mutter->cursorWidth = width;
989 mutter->cursorHeight = height;
990 mutter->cursorXHot = xHot;
991 mutter->cursorYHot = yHot;
996static void mutter_cursor_send_default(mutterShadowSubsystem* mutter)
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 ",
1006 ARROW_HEIGHT = ARRAYSIZE(arrow)
1008 BYTE pixels[4ull * ARROW_WIDTH * ARROW_HEIGHT] = WINPR_C_ARRAY_INIT;
1009 for (
size_t y = 0; y < ARROW_HEIGHT; y++)
1011 for (
size_t x = 0; x < ARROW_WIDTH; x++)
1013 BYTE* pixel = &pixels[4ull * (y * ARROW_WIDTH + x)];
1014 if (arrow[y][x] ==
' ')
1016 const BYTE value = (arrow[y][x] ==
'.') ? 0xff : 0x00;
1017 pixel[0] = pixel[1] = pixel[2] = value;
1021 mutter_cursor_send_shape(mutter, pixels, ARROW_WIDTH, ARROW_HEIGHT, 0, 0);
1025static void mutter_cursor_update(mutterShadowSubsystem* mutter,
struct spa_buffer* buf)
1027 WINPR_ASSERT(mutter);
1030 struct spa_meta* meta = spa_buffer_find_meta(buf, SPA_META_Cursor);
1031 if (!meta || (meta->size <
sizeof(
struct spa_meta_cursor)))
1034 const struct spa_meta_cursor* mc = (
const struct spa_meta_cursor*)meta->data;
1038 if (mc->bitmap_offset != 0)
1040 if ((
size_t)mc->bitmap_offset +
sizeof(
struct spa_meta_bitmap) > meta->size)
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))
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);
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))
1063 "unusable pointer bitmap (format %" PRIu32
", %" PRIu32
"x%" PRIu32
")",
1064 mb->format, width, height);
1068 const BYTE* src = SPA_PTROFF(mb, mb->offset,
const BYTE);
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);
1078 for (UINT32 y = 0; y < outH; y++)
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)],
1086 MIN((UINT32)MAX(mc->hotspot.x, 0) * scaled / side, outW - 1);
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);
1097 mutter_cursor_send_position(mutter, mc->position.x, mc->position.y);
1104static void mutter_set_monitor_size(mutterShadowSubsystem* mutter)
1106 WINPR_ASSERT(mutter);
1108 rdpShadowSubsystem* subsystem = &mutter->common;
1109 MONITOR_DEF* monitor = &subsystem->monitors[subsystem->selectedMonitor];
1112 monitor->right = monitor->left + (INT32)mutter->width - 1;
1113 monitor->bottom = monitor->top + (INT32)mutter->height - 1;
1115 MONITOR_DEF* virtualScreen = &subsystem->virtualScreen;
1116 *virtualScreen = subsystem->monitors[0];
1117 for (UINT32 x = 1; x < subsystem->numMonitors; 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);
1125 virtualScreen->flags = 1;
1128static void mutter_on_param_changed(
void* data, uint32_t
id,
const struct spa_pod* param)
1130 mutterShadowSubsystem* mutter = (mutterShadowSubsystem*)data;
1131 WINPR_ASSERT(mutter);
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;
1138 if (!param || (
id != SPA_PARAM_Format))
1140 if (spa_format_video_raw_parse(param, &info) < 0)
1142 WLog_ERR(TAG,
"cannot parse the stream format");
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;
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");
1157 if (sized && changed)
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,
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));
1194static void mutter_copy_rect(mutterShadowSubsystem* mutter, rdpShadowSurface* surface,
1195 const BYTE* src, UINT32 stride, INT32 x, INT32 y, INT32 width,
1198 WINPR_ASSERT(mutter);
1199 WINPR_ASSERT(surface);
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))
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))
1216 WLog_ERR(TAG,
"cannot copy the frame to the surface");
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");
1229static UINT32 mutter_repair_missed_damage(mutterShadowSubsystem* mutter, rdpShadowSurface* surface,
1230 const BYTE* src, UINT32 stride)
1232 WINPR_ASSERT(mutter);
1233 WINPR_ASSERT(surface);
1236 const UINT32 width = MIN(mutter->width, surface->width);
1237 const UINT32 height = MIN(mutter->height, surface->height);
1240 const BOOL sameFormat = (mutter->format == surface->format);
1243 for (UINT32 ty = 0; ty < height; ty += MUTTER_CHECK_TILE)
1245 const UINT32 th = MIN(MUTTER_CHECK_TILE, height - ty);
1246 for (UINT32 tx = 0; tx < width; tx += MUTTER_CHECK_TILE)
1248 const UINT32 tw = MIN(MUTTER_CHECK_TILE, width - tx);
1250 for (UINT32 y = ty; (y < ty + th) && !dirty; y++)
1252 const BYTE* a = &src[1ull * y * stride + 4ull * tx];
1253 const BYTE* b = &surface->data[1ull * y * surface->scanline + 4ull * tx];
1255 dirty = (memcmp(a, b, 4ull * tw) != 0);
1258 for (UINT32 x = 0; (x < tw) && !dirty; x++)
1259 dirty = (memcmp(&a[4ull * x], &b[4ull * x], 3) != 0);
1264 mutter_copy_rect(mutter, surface, src, stride, (INT32)tx, (INT32)ty, (INT32)tw,
1274static void mutter_damage_stats(mutterShadowSubsystem* mutter, UINT32 tiles)
1276 WINPR_ASSERT(mutter);
1278 mutter->damageFrames++;
1279 mutter->damageTiles += tiles;
1280 if ((tiles > 0) && !mutter->damageMissSeen)
1282 mutter->damageMissSeen = TRUE;
1284 "Mutter left changes out of its damage: %" PRIu32
1285 " tile(s) copied, the check stays on (" MUTTER_DAMAGE_CHECK_ENV
1289 const UINT64 now = GetTickCount64();
1290 if (now - mutter->damageLogged >= 5000)
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;
1300static void mutter_on_process(
void* data)
1302 mutterShadowSubsystem* mutter = (mutterShadowSubsystem*)data;
1303 WINPR_ASSERT(mutter);
1305 rdpShadowServer* server = mutter->common.server;
1306 if (!server || !server->surface)
1308 rdpShadowSurface* surface = server->surface;
1309 BOOL painted = FALSE;
1310 struct pw_buffer* pb =
nullptr;
1313 while ((pb = pw_stream_dequeue_buffer(mutter->stream)) !=
nullptr)
1315 struct spa_buffer* buf = pb->buffer;
1316 struct spa_data* d = &buf->datas[0];
1318 mutter_cursor_update(mutter, buf);
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))
1326 (void)pw_stream_queue_buffer(mutter->stream, pb);
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;
1333 EnterCriticalSection(&surface->lock);
1334 struct spa_meta* dm = spa_buffer_find_meta(buf, SPA_META_VideoDamage);
1335 if (!dm || mutter->fullPaint)
1337 mutter_copy_rect(mutter, surface, src, stride, 0, 0, (INT32)mutter->width,
1338 (INT32)mutter->height);
1339 mutter->fullPaint = FALSE;
1343 struct spa_meta_region* r =
nullptr;
1344 spa_meta_for_each(r, dm)
1346 if (!spa_meta_region_is_valid(r))
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);
1352 if (mutter->damageCheck)
1353 mutter_damage_stats(mutter,
1354 mutter_repair_missed_damage(mutter, surface, src, stride));
1356 LeaveCriticalSection(&surface->lock);
1358 (void)pw_stream_queue_buffer(mutter->stream, pb);
1368 if (ArrayList_Count(server->clients) > 0)
1369 shadow_subsystem_frame_update(&mutter->common);
1371 EnterCriticalSection(&surface->lock);
1372 region16_clear(&surface->invalidRegion);
1373 LeaveCriticalSection(&surface->lock);
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)
1380 WLog_DBG(TAG,
"stream %s%s%s", pw_stream_state_as_string(state), error ?
": " :
"",
1381 error ? error :
"");
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,
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)
1395 WLog_ERR(TAG,
"PipeWire error on %" PRIu32
": %s",
id, message);
1398static const struct pw_core_events mutter_core_events = {
1399 PW_VERSION_CORE_EVENTS,
1400 .error = mutter_on_core_error,
1406static void mutter_on_global_remove(
void* data, uint32_t
id)
1408 mutterShadowSubsystem* mutter = (mutterShadowSubsystem*)data;
1409 WINPR_ASSERT(mutter);
1411 if (mutter->haveNode && (
id == mutter->nodeId))
1413 mutter->haveNode = FALSE;
1414 mutter->streamGone = TRUE;
1415 mutter->streamGoneAt = GetTickCount64();
1419static const struct pw_registry_events mutter_registry_events = {
1420 PW_VERSION_REGISTRY_EVENTS,
1421 .global_remove = mutter_on_global_remove,
1425static BOOL mutter_stream_connect_node(mutterShadowSubsystem* mutter);
1428static BOOL mutter_connect_stream(mutterShadowSubsystem* mutter)
1430 WINPR_ASSERT(mutter);
1432 pw_init(
nullptr,
nullptr);
1433 mutter->loop = pw_loop_new(
nullptr);
1436 mutter->context = pw_context_new(mutter->loop,
nullptr, 0);
1437 if (!mutter->context)
1440 mutter->core = pw_context_connect(mutter->context,
nullptr, 0);
1443 char buffer[128] = WINPR_C_ARRAY_INIT;
1444 WLog_ERR(TAG,
"cannot connect to PipeWire: %s",
1445 winpr_strerror(errno, buffer,
sizeof(buffer)));
1448 pw_core_add_listener(mutter->core, &mutter->coreListener, &mutter_core_events, mutter);
1450 mutter->registry = pw_core_get_registry(mutter->core, PW_VERSION_REGISTRY, 0);
1451 if (!mutter->registry)
1453 pw_registry_add_listener(mutter->registry, &mutter->registryListener, &mutter_registry_events,
1456 if (!mutter_stream_connect_node(mutter))
1460 const UINT64 start = GetTickCount64();
1461 while (!mutter->haveFormat)
1463 if (GetTickCount64() - start > MUTTER_FORMAT_TIMEOUT_MS)
1465 WLog_ERR(TAG,
"the stream got no format");
1468 pw_loop_enter(mutter->loop);
1469 (void)pw_loop_iterate(mutter->loop, 100);
1470 pw_loop_leave(mutter->loop);
1478static BOOL mutter_stream_connect_node(mutterShadowSubsystem* mutter)
1480 WINPR_ASSERT(mutter);
1481 WINPR_ASSERT(mutter->core);
1484 pw_stream_destroy(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)
1491 pw_stream_add_listener(mutter->stream, &mutter->streamListener, &mutter_stream_events, mutter);
1493 uint8_t podBuffer[1024] = WINPR_C_ARRAY_INIT;
1494 struct spa_pod_builder b = SPA_POD_BUILDER_INIT(podBuffer,
sizeof(podBuffer));
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);
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)) };
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)
1520 WLog_ERR(TAG,
"pw_stream_connect failed");
1530static void mutter_audio_msg_free(WINPR_ATTR_UNUSED UINT32
id, SHADOW_MSG_OUT* msg)
1539static void mutter_audio_post(mutterShadowSubsystem* mutter,
wStream* s)
1541 WINPR_ASSERT(mutter);
1544 const size_t size = Stream_GetPosition(s);
1545 Stream_ResetPosition(s);
1549 rdpShadowServer* server = mutter->common.server;
1550 if (!server || !server->clients || (ArrayList_Count(server->clients) == 0))
1557 msg->buf = malloc(size);
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);
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");
1576static void mutter_audio_stats(mutterShadowSubsystem* mutter,
size_t frames)
1578 WINPR_ASSERT(mutter);
1580 const UINT64 now = GetTickCount64();
1581 if (mutter->audioLogged == 0)
1583 WLog_INFO(TAG,
"sound: capturing what the default sink plays");
1584 mutter->audioLogged = now;
1586 else if (now - mutter->audioLogged >= MUTTER_AUDIO_LOG_MS)
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;
1598 mutter->audioFrames += frames;
1601static void mutter_on_audio_process(
void* data)
1603 mutterShadowSubsystem* mutter = (mutterShadowSubsystem*)data;
1604 WINPR_ASSERT(mutter);
1606 struct pw_buffer* pb =
nullptr;
1608 while ((pb = pw_stream_dequeue_buffer(mutter->audio)) !=
nullptr)
1610 const struct spa_data* d = &pb->buffer->datas[0];
1611 if (d->data && d->chunk && (d->chunk->size > 0) &&
1612 ((
size_t)d->chunk->offset + d->chunk->size <= d->maxsize))
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);
1620 wStream* s = mutter->audioStream;
1621 for (
size_t f = 0; f < frames; f++)
1623 for (
size_t c = 0; c < MUTTER_AUDIO_CHANNELS; c++)
1625 const INT16 value = samples[f * MUTTER_AUDIO_CHANNELS + c];
1627 MAX(mutter->audioPeak, fabsf((
float)value / (
float)INT16_MAX));
1628 Stream_Write_INT16(s, value);
1631 if (Stream_GetPosition(s) >= MUTTER_AUDIO_MESSAGE_FRAMES * MUTTER_AUDIO_FRAME_BYTES)
1632 mutter_audio_post(mutter, s);
1635 (void)pw_stream_queue_buffer(mutter->audio, pb);
1639static const struct pw_stream_events mutter_audio_events = {
1640 PW_VERSION_STREAM_EVENTS,
1641 .process = mutter_on_audio_process,
1648static BOOL mutter_audio_init(mutterShadowSubsystem* mutter)
1650 WINPR_ASSERT(mutter);
1652 uint8_t podBuffer[1024] = WINPR_C_ARRAY_INIT;
1653 struct spa_pod_builder b = SPA_POD_BUILDER_INIT(podBuffer,
sizeof(podBuffer));
1655 AUDIO_FORMAT* formats = audio_formats_new(ARRAYSIZE(mutter_audio_offered_formats));
1660 for (
size_t x = 0; x < ARRAYSIZE(mutter_audio_offered_formats); x++)
1662 const AUDIO_FORMAT* format = &mutter_audio_offered_formats[x];
1663 if (freerdp_dsp_supports_format(format, TRUE))
1664 formats[count++] = *format;
1667 mutter->audioStream =
1668 Stream_New(
nullptr, MUTTER_AUDIO_MESSAGE_FRAMES * MUTTER_AUDIO_FRAME_BYTES);
1669 if (!mutter->audioStream || (count == 0))
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));
1679 pw_stream_add_listener(mutter->audio, &mutter->audioListener, &mutter_audio_events, mutter);
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,
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)
1697 mutter->common.rdpsndFormats = formats;
1698 mutter->common.nRdpsndFormats = count;
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));
1711static void mutter_audio_uninit(mutterShadowSubsystem* mutter)
1713 WINPR_ASSERT(mutter);
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;
1727static void mutter_audio_follow_clients(mutterShadowSubsystem* mutter)
1729 WINPR_ASSERT(mutter);
1734 const BOOL active = InterlockedCompareExchange(&mutter->clients, 0, 0) > 0;
1735 if (active == mutter->audioActive)
1738 if (pw_stream_set_active(mutter->audio, active ?
true : false) < 0)
1740 WLog_WARN(TAG,
"sound: cannot %s the capture", active ?
"start" :
"stop");
1743 mutter->audioActive = active;
1745 Stream_ResetPosition(mutter->audioStream);
1746 WLog_DBG(TAG,
"sound: capture %s", active ?
"started" :
"stopped");
1754static void mutter_process_ei_events(mutterShadowSubsystem* mutter)
1756 WINPR_ASSERT(mutter);
1758 struct ei_event* e =
nullptr;
1760 ei_dispatch(mutter->ei);
1761 while ((e = ei_get_event(mutter->ei)) !=
nullptr)
1763 struct ei_device* dev = ei_event_get_device(e);
1765 switch (ei_event_get_type(e))
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);
1772 case EI_EVENT_DEVICE_ADDED:
1773 if (ei_device_has_capability(dev, EI_DEVICE_CAP_KEYBOARD) && !mutter->keyboard)
1775 mutter->keyboard = ei_device_ref(dev);
1776 WLog_DBG(TAG,
"libei keyboard '%s'", ei_device_get_name(dev));
1778 if (ei_device_has_capability(dev, EI_DEVICE_CAP_POINTER_ABSOLUTE) &&
1781 mutter->pointer = ei_device_ref(dev);
1782 WLog_DBG(TAG,
"libei pointer '%s'", ei_device_get_name(dev));
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;
1792 case EI_EVENT_DEVICE_PAUSED:
1793 if (dev == mutter->keyboard)
1794 mutter->keyboardReady = FALSE;
1795 if (dev == mutter->pointer)
1796 mutter->pointerReady = FALSE;
1798 case EI_EVENT_DEVICE_REMOVED:
1799 if (dev == mutter->keyboard)
1801 mutter->keyboard = ei_device_unref(mutter->keyboard);
1802 mutter->keyboardReady = FALSE;
1804 if (dev == mutter->pointer)
1806 mutter->pointer = ei_device_unref(mutter->pointer);
1807 mutter->pointerReady = FALSE;
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);
1815 case EI_EVENT_DISCONNECT:
1816 WLog_WARN(TAG,
"libei disconnected: the clients can only watch");
1826static void mutter_key(mutterShadowSubsystem* mutter, uint32_t code, BOOL down)
1828 WINPR_ASSERT(mutter);
1830 if (!mutter->keyboard || !mutter->keyboardReady)
1833 ei_device_keyboard_key(mutter->keyboard, code, down ?
true : false);
1834 ei_device_frame(mutter->keyboard, ei_now(mutter->ei));
1839 if (down && (code == KEY_CAPSLOCK))
1840 mutter->lockedMods ^= MUTTER_MOD_LOCK;
1841 if (down && (code == KEY_NUMLOCK))
1842 mutter->lockedMods ^= MUTTER_MOD_NUM;
1846static BOOL mutter_shadow_input_synchronize_event(rdpShadowSubsystem* subsystem,
1847 rdpShadowClient* client, UINT32 flags)
1849 mutterShadowSubsystem* mutter = (mutterShadowSubsystem*)subsystem;
1850 if (!mutter || !client)
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)
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))
1864 mutter_key(mutter, KEY_CAPSLOCK, TRUE);
1865 mutter_key(mutter, KEY_CAPSLOCK, FALSE);
1867 if (num != ((mutter->lockedMods & MUTTER_MOD_NUM) != 0))
1869 mutter_key(mutter, KEY_NUMLOCK, TRUE);
1870 mutter_key(mutter, KEY_NUMLOCK, FALSE);
1873 LeaveCriticalSection(&mutter->eiLock);
1878static BOOL mutter_shadow_input_keyboard_event(rdpShadowSubsystem* subsystem,
1879 rdpShadowClient* client, UINT16 flags, UINT8 code)
1881 mutterShadowSubsystem* mutter = (mutterShadowSubsystem*)subsystem;
1882 if (!mutter || !client)
1885 DWORD scancode = code;
1886 if (flags & KBD_FLAGS_EXTENDED)
1889 DWORD vkcode = GetVirtualKeyCodeFromVirtualScanCode(scancode, WINPR_KBD_TYPE_IBM_ENHANCED);
1890 if (flags & KBD_FLAGS_EXTENDED)
1893 const DWORD keycode = GetKeycodeFromVirtualKeyCode(vkcode, WINPR_KEYCODE_TYPE_EVDEV);
1896 WLog_DBG(TAG,
"no key for scancode 0x%04" PRIx32, scancode);
1901 const BOOL down = (flags & KBD_FLAGS_RELEASE) == 0;
1903 EnterCriticalSection(&mutter->eiLock);
1905 mutter_key(mutter, keycode, down);
1906 LeaveCriticalSection(&mutter->eiLock);
1911static BOOL mutter_shadow_input_unicode_keyboard_event(rdpShadowSubsystem* subsystem,
1912 rdpShadowClient* client, UINT16 flags,
1915 if (!subsystem || !client)
1919 WLog_DBG(TAG,
"unicode key U+%04" PRIX16
" (flags 0x%04" PRIx16
") not supported", code, flags);
1925static void mutter_pointer_move(mutterShadowSubsystem* mutter, UINT16 x, UINT16 y)
1927 WINPR_ASSERT(mutter);
1929 struct ei_region* region =
nullptr;
1930 struct ei_region* first =
nullptr;
1932 for (
size_t index = 0;; index++)
1934 struct ei_region* r = ei_device_get_region(mutter->pointer, index);
1939 const char*
id = ei_region_get_mapping_id(r);
1940 if (mutter->mappingId &&
id && (strcmp(
id, mutter->mappingId) == 0))
1949 mutter->pointerInput = GetTickCount64();
1952 if (region && (mutter->width > 0) && (mutter->height > 0))
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;
1958 "pointer %" PRIu16
",%" PRIu16
" -> %.1f,%.1f (region %s %" PRIu32
",%" PRIu32
1959 " %" PRIu32
"x%" PRIu32
")",
1961 region ? (ei_region_get_mapping_id(region) ? ei_region_get_mapping_id(region) :
"-")
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));
1970static void mutter_pointer_button(mutterShadowSubsystem* mutter, uint32_t button, BOOL down)
1972 WINPR_ASSERT(mutter);
1974 if (!ei_device_has_capability(mutter->pointer, EI_DEVICE_CAP_BUTTON))
1976 ei_device_button_button(mutter->pointer, button, down ?
true : false);
1977 ei_device_frame(mutter->pointer, ei_now(mutter->ei));
1981static void mutter_pointer_wheel(mutterShadowSubsystem* mutter, UINT16 flags, BOOL horizontal)
1983 WINPR_ASSERT(mutter);
1985 if (!ei_device_has_capability(mutter->pointer, EI_DEVICE_CAP_SCROLL))
1988 INT32 value = flags & 0xFF;
1989 if (flags & PTR_FLAGS_WHEEL_NEGATIVE)
1995 ei_device_scroll_discrete(mutter->pointer, value, 0);
1997 ei_device_scroll_discrete(mutter->pointer, 0, -value);
1998 ei_device_frame(mutter->pointer, ei_now(mutter->ei));
2002static BOOL mutter_shadow_input_mouse_event(rdpShadowSubsystem* subsystem, rdpShadowClient* client,
2003 UINT16 flags, UINT16 x, UINT16 y)
2005 mutterShadowSubsystem* mutter = (mutterShadowSubsystem*)subsystem;
2006 if (!mutter || !client)
2009 EnterCriticalSection(&mutter->eiLock);
2010 if (mutter->ei && mutter->pointer && mutter->pointerReady)
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);
2019 mutter_pointer_move(mutter, x, y);
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);
2030 LeaveCriticalSection(&mutter->eiLock);
2035static BOOL mutter_shadow_input_extended_mouse_event(rdpShadowSubsystem* subsystem,
2036 rdpShadowClient* client, UINT16 flags,
2039 mutterShadowSubsystem* mutter = (mutterShadowSubsystem*)subsystem;
2040 if (!mutter || !client)
2043 EnterCriticalSection(&mutter->eiLock);
2044 if (mutter->ei && mutter->pointer && mutter->pointerReady)
2046 mutter_pointer_move(mutter, x, y);
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);
2054 LeaveCriticalSection(&mutter->eiLock);
2066 const char* password;
2067} MUTTER_SHADOW_PAM_DATA;
2070static int mutter_shadow_pam_conv(
int num_msg,
const struct pam_message** msg,
2071 struct pam_response** resp,
void* appdata_ptr)
2073 const MUTTER_SHADOW_PAM_DATA* data = (
const MUTTER_SHADOW_PAM_DATA*)appdata_ptr;
2075 if ((num_msg <= 0) || !msg || !resp || !data)
2076 return PAM_CONV_ERR;
2078 struct pam_response* response = calloc((
size_t)num_msg,
sizeof(
struct pam_response));
2082 for (
int index = 0; index < num_msg; index++)
2084 const char* answer =
nullptr;
2085 switch (msg[index]->msg_style)
2087 case PAM_PROMPT_ECHO_ON:
2088 answer = data->user;
2090 case PAM_PROMPT_ECHO_OFF:
2091 answer = data->password;
2097 response[index].resp = _strdup(answer);
2098 if (!response[index].resp)
2100 for (
int x = 0; x < index; x++)
2101 free(response[x].resp);
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)
2119 if (!user || !password)
2122 struct passwd pbuffer = WINPR_C_ARRAY_INIT;
2123 struct passwd* owner =
nullptr;
2126 long buflen = sysconf(_SC_GETPW_R_SIZE_MAX);
2129 char* buf = calloc((
size_t)buflen,
sizeof(
char));
2132 WLog_ERR(TAG,
"login as '%s' refused: no memory for the session's account", user);
2135 const int rc = getpwuid_r(getuid(), &pbuffer, buf, (
size_t)buflen, &owner);
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)));
2144 if (!owner || !owner->pw_name || (strcmp(owner->pw_name, user) != 0))
2146 WLog_ERR(TAG,
"login as '%s' refused: this session belongs to '%s'", user,
2147 (owner && owner->pw_name) ? owner->pw_name :
"?");
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;
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)
2162 WLog_ERR(TAG,
"pam_start(%s) failure: %s", service, pam_strerror(handle, status));
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));
2173 pam_end(handle, status);
2175 return (status == PAM_SUCCESS) ? 1 : -1;
2183static void mutter_shadow_refresh(mutterShadowSubsystem* mutter)
2185 WINPR_ASSERT(mutter);
2189 mutter->fullPaint = TRUE;
2190 mutter->cursorResend = TRUE;
2193 if (!mutter->virtualMonitor)
2195 static const BYTE clear[4] = { 0 };
2196 mutter_cursor_send_shape(mutter, clear, 1, 1, 0, 0);
2198 else if (mutter->cursorPixels)
2199 mutter_cursor_send_shape(mutter, mutter->cursorPixels, mutter->cursorWidth,
2200 mutter->cursorHeight, mutter->cursorXHot, mutter->cursorYHot);
2202 mutter_cursor_send_default(mutter);
2204 rdpShadowServer* server = mutter->common.server;
2205 rdpShadowSurface* surface = server ? server->surface :
nullptr;
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);
2216 if (ArrayList_Count(server->clients) > 0)
2217 shadow_subsystem_frame_update(&mutter->common);
2219 EnterCriticalSection(&surface->lock);
2220 region16_clear(&surface->invalidRegion);
2221 LeaveCriticalSection(&surface->lock);
2238} mutterClipboardJob;
2241static mutterClipboardJob* mutter_clipboard_job_new(
void)
2243 mutterClipboardJob* job = calloc(1,
sizeof(mutterClipboardJob));
2249static void mutter_clipboard_job_free(mutterClipboardJob* job)
2255 for (
char** mime = job->mimes; *mime; mime++)
2258 free((
void*)job->mimes);
2262 (void)close(job->fd);
2266static void mutter_clipboard_message_free(wMessage* message)
2268 WINPR_ASSERT(message);
2269 mutter_clipboard_job_free(message->context);
2273static BOOL mutter_clipboard_post(wMessageQueue* queue, UINT32
id, mutterClipboardJob* job)
2275 WINPR_ASSERT(queue);
2278 wMessage message = WINPR_C_ARRAY_INIT;
2279 message.context = job;
2281 message.Free = mutter_clipboard_message_free;
2282 if (MessageQueue_Dispatch(queue, &message))
2284 mutter_clipboard_job_free(job);
2290static BOOL mutter_clipboard_offer(rdpShadowSubsystem* subsystem,
const char*
const* mimeTypes,
2293 WINPR_ASSERT(subsystem);
2294 WINPR_ASSERT(subsystem->MsgPipe);
2296 mutterClipboardJob* job = mutter_clipboard_job_new();
2300 job->mimes = (
char**)calloc(count + 1,
sizeof(
char*));
2303 for (
size_t x = 0; x < count; x++)
2305 job->mimes[x] = _strdup(mimeTypes[x]);
2309 return mutter_clipboard_post(subsystem->MsgPipe->In, MUTTER_MSG_CLIPBOARD_OFFER, job);
2312 mutter_clipboard_job_free(job);
2317static BOOL mutter_clipboard_read(rdpShadowSubsystem* subsystem,
const char* mimeType)
2319 WINPR_ASSERT(subsystem);
2320 WINPR_ASSERT(subsystem->MsgPipe);
2321 WINPR_ASSERT(mimeType);
2323 mutterClipboardJob* job = mutter_clipboard_job_new();
2327 job->mime = _strdup(mimeType);
2330 mutter_clipboard_job_free(job);
2333 return mutter_clipboard_post(subsystem->MsgPipe->In, MUTTER_MSG_CLIPBOARD_READ, job);
2337static BOOL mutter_clipboard_provide(rdpShadowSubsystem* subsystem, UINT32 request,
2338 const BYTE* data,
size_t size)
2340 WINPR_ASSERT(subsystem);
2341 WINPR_ASSERT(subsystem->MsgPipe);
2343 mutterClipboardJob* job = mutter_clipboard_job_new();
2347 job->serial = request;
2350 job->data = malloc(size);
2353 mutter_clipboard_job_free(job);
2356 memcpy(job->data, data, size);
2360 return mutter_clipboard_post(subsystem->MsgPipe->In, MUTTER_MSG_CLIPBOARD_PROVIDE, job);
2365static int mutter_clipboard_call_for_fd(mutterShadowSubsystem* mutter,
const char* method,
2366 const char* types, ...)
2368 WINPR_ASSERT(mutter);
2370 sd_bus_error err = SD_BUS_ERROR_NULL;
2371 sd_bus_message* call =
nullptr;
2372 sd_bus_message* reply =
nullptr;
2375 if (sd_bus_message_new_method_call(mutter->bus, &call, RD_NAME, mutter->rdSessionPath,
2376 RD_SESSION_IFACE, method) < 0)
2379 va_list ap = WINPR_C_ARRAY_INIT;
2380 va_start(ap, types);
2381 const int appended = sd_bus_message_appendv(call, types, ap);
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);
2390 WLog_WARN(TAG,
"clipboard: %s failed: %s", method, err.message ? err.message :
"bad reply");
2394 sd_bus_error_free(&err);
2395 sd_bus_message_unref(reply);
2396 sd_bus_message_unref(call);
2400static void mutter_clipboard_write_done(mutterShadowSubsystem* mutter, UINT32 serial, BOOL ok)
2402 WINPR_ASSERT(mutter);
2405 sd_bus_call_method(mutter->bus, RD_NAME, mutter->rdSessionPath, RD_SESSION_IFACE,
2406 "SelectionWriteDone",
nullptr,
nullptr,
"ub", serial, ok ? 1 : 0);
2409 char buffer[128] = WINPR_C_ARRAY_INIT;
2410 WLog_WARN(TAG,
"clipboard: SelectionWriteDone failed: %s",
2411 winpr_strerror(-rc, buffer,
sizeof(buffer)));
2416static void mutter_clipboard_set_selection(mutterShadowSubsystem* mutter,
2417 const mutterClipboardJob* job)
2419 WINPR_ASSERT(mutter);
2421 WINPR_ASSERT(job->mimes);
2423 sd_bus_error err = SD_BUS_ERROR_NULL;
2424 sd_bus_message* call =
nullptr;
2427 int rc = sd_bus_message_new_method_call(mutter->bus, &call, RD_NAME, mutter->rdSessionPath,
2428 RD_SESSION_IFACE,
"SetSelection");
2430 rc = sd_bus_message_open_container(call,
'a',
"{sv}");
2431 if ((rc >= 0) && job->mimes[0])
2433 rc = sd_bus_message_open_container(call,
'e',
"sv");
2435 rc = sd_bus_message_append(call,
"s",
"mime-types");
2437 rc = sd_bus_message_open_container(call,
'v',
"as");
2439 rc = sd_bus_message_append_strv(call, job->mimes);
2441 rc = sd_bus_message_close_container(call);
2443 rc = sd_bus_message_close_container(call);
2446 rc = sd_bus_message_close_container(call);
2448 rc = sd_bus_call(mutter->bus, call, MUTTER_CLIPBOARD_CALL_USEC, &err,
nullptr);
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)));
2456 sd_bus_error_free(&err);
2457 sd_bus_message_unref(call);
2461static void mutter_clipboard_start_read(mutterShadowSubsystem* mutter, wMessage* message)
2463 WINPR_ASSERT(mutter);
2464 WINPR_ASSERT(message);
2466 mutterClipboardJob* job = message->context;
2469 job->fd = mutter_clipboard_call_for_fd(mutter,
"SelectionRead",
"s", job->mime);
2470 if ((job->fd >= 0) && mutter->clipboardJobs)
2472 message->Free =
nullptr;
2473 if (mutter_clipboard_post(mutter->clipboardJobs, MUTTER_MSG_CLIPBOARD_READ, job))
2476 if (!shadow_clipboard_session_data(mutter->common.server,
nullptr, 0))
2477 WLog_DBG(TAG,
"clipboard: no read was waiting");
2481static void mutter_clipboard_start_write(mutterShadowSubsystem* mutter, wMessage* message)
2483 WINPR_ASSERT(mutter);
2484 WINPR_ASSERT(message);
2486 mutterClipboardJob* job = message->context;
2491 job->fd = mutter_clipboard_call_for_fd(mutter,
"SelectionWrite",
"u", job->serial);
2492 if ((job->fd >= 0) && mutter->clipboardJobs)
2494 const UINT32 serial = job->serial;
2495 message->Free =
nullptr;
2496 if (mutter_clipboard_post(mutter->clipboardJobs, MUTTER_MSG_CLIPBOARD_PROVIDE, job))
2498 mutter_clipboard_write_done(mutter, serial, FALSE);
2502 mutter_clipboard_write_done(mutter, job->serial, FALSE);
2507static short mutter_clipboard_wait(mutterShadowSubsystem* mutter,
int fd,
short events)
2509 WINPR_ASSERT(mutter);
2511 struct pollfd fds[2] = { { fd, events, 0 },
2512 { GetEventFileDescriptor(mutter->stopEvent), POLLIN, 0 } };
2516 ready = poll(fds, ARRAYSIZE(fds), MUTTER_CLIPBOARD_STALL_MS);
2517 }
while ((ready < 0) && (errno == EINTR));
2519 if ((ready <= 0) || (fds[1].revents != 0))
2521 return fds[0].revents;
2526static BOOL mutter_clipboard_read_all(mutterShadowSubsystem* mutter, mutterClipboardJob* job)
2528 WINPR_ASSERT(mutter);
2531 if (fcntl(job->fd, F_SETFL, fcntl(job->fd, F_GETFL) | O_NONBLOCK) < 0)
2534 size_t capacity = 0;
2537 if (job->size + 4096 > capacity)
2539 const size_t wanted = capacity ? capacity * 2 : 64ull * 1024ull;
2540 if (wanted > SHADOW_CLIPBOARD_MAX_SIZE)
2542 WLog_WARN(TAG,
"clipboard: the selection is too large");
2545 BYTE* grown = realloc(job->data, wanted);
2552 if (mutter_clipboard_wait(mutter, job->fd, POLLIN) == 0)
2555 const ssize_t got = read(job->fd, &job->data[job->size], capacity - job->size);
2559 job->size += (size_t)got;
2560 else if ((errno != EAGAIN) && (errno != EINTR))
2567static BOOL mutter_clipboard_write_all(mutterShadowSubsystem* mutter, mutterClipboardJob* job)
2569 WINPR_ASSERT(mutter);
2572 if (fcntl(job->fd, F_SETFL, fcntl(job->fd, F_GETFL) | O_NONBLOCK) < 0)
2576 while (done < job->size)
2578 if (mutter_clipboard_wait(mutter, job->fd, POLLOUT) == 0)
2581 const ssize_t put = write(job->fd, &job->data[done], job->size - done);
2583 done += (size_t)put;
2584 else if ((errno != EAGAIN) && (errno != EINTR))
2591static DWORD WINAPI mutter_clipboard_thread(LPVOID arg)
2593 mutterShadowSubsystem* mutter = arg;
2594 WINPR_ASSERT(mutter);
2596 while (MessageQueue_Wait(mutter->clipboardJobs))
2598 wMessage message = WINPR_C_ARRAY_INIT;
2599 if (!MessageQueue_Peek(mutter->clipboardJobs, &message, TRUE))
2601 if (message.id == WMQ_QUIT)
2604 mutterClipboardJob* job = message.context;
2607 if (message.id == MUTTER_MSG_CLIPBOARD_READ)
2609 const BOOL ok = mutter_clipboard_read_all(mutter, job);
2611 WLog_WARN(TAG,
"clipboard: reading %s failed", job->mime);
2612 if (!shadow_clipboard_session_data(mutter->common.server, ok ? job->data : nullptr,
2614 WLog_DBG(TAG,
"clipboard: no read was waiting");
2615 mutter_clipboard_job_free(job);
2619 job->ok = mutter_clipboard_write_all(mutter, job);
2621 WLog_WARN(TAG,
"clipboard: writing to the session failed");
2622 (void)close(job->fd);
2624 if (!mutter_clipboard_post(mutter->common.MsgPipe->In, MUTTER_MSG_CLIPBOARD_WRITE_DONE,
2626 WLog_WARN(TAG,
"clipboard: cannot finish a transfer");
2636static size_t mutter_clipboard_read_mimes(sd_bus_message* msg,
2637 const char* mimes[MUTTER_CLIPBOARD_MAX_MIMES])
2641 const char* contents =
nullptr;
2643 if ((sd_bus_message_peek_type(msg, &type, &contents) <= 0) || !contents)
2646 const BOOL inStruct = contents[0] ==
'(';
2647 if ((strcmp(contents,
"as") != 0) && (strcmp(contents,
"(as)") != 0))
2649 (void)sd_bus_message_skip(msg,
"v");
2654 if (sd_bus_message_enter_container(msg,
'v', contents) <= 0)
2656 if (inStruct && (sd_bus_message_enter_container(msg,
'r',
"as") <= 0))
2658 if (sd_bus_message_enter_container(msg,
'a',
"s") > 0)
2660 const char* mime =
nullptr;
2661 while (sd_bus_message_read(msg,
"s", &mime) > 0)
2663 if (count < MUTTER_CLIPBOARD_MAX_MIMES)
2664 mimes[count++] = mime;
2666 (void)sd_bus_message_exit_container(msg);
2669 (void)sd_bus_message_exit_container(msg);
2670 (void)sd_bus_message_exit_container(msg);
2675static int mutter_on_selection_owner_changed(sd_bus_message* msg,
void* data,
2676 WINPR_ATTR_UNUSED sd_bus_error* err)
2678 mutterShadowSubsystem* mutter = data;
2679 WINPR_ASSERT(mutter);
2681 const char* mimes[MUTTER_CLIPBOARD_MAX_MIMES] = WINPR_C_ARRAY_INIT;
2683 int sessionIsOwner = 0;
2685 if (sd_bus_message_enter_container(msg,
'a',
"{sv}") <= 0)
2687 while (sd_bus_message_enter_container(msg,
'e',
"sv") > 0)
2689 const char* key =
nullptr;
2690 if (sd_bus_message_read(msg,
"s", &key) < 0)
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);
2698 (
void)sd_bus_message_skip(msg,
"v");
2699 (void)sd_bus_message_exit_container(msg);
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");
2713static int mutter_on_selection_transfer(sd_bus_message* msg,
void* data,
2714 WINPR_ATTR_UNUSED sd_bus_error* err)
2716 mutterShadowSubsystem* mutter = data;
2717 WINPR_ASSERT(mutter);
2719 const char* mime =
nullptr;
2720 uint32_t serial = 0;
2721 if (sd_bus_message_read(msg,
"su", &mime, &serial) < 0)
2724 if (!shadow_clipboard_session_request(mutter->common.server, serial, mime))
2725 mutter_clipboard_write_done(mutter, serial, FALSE);
2730static void mutter_clipboard_enable(mutterShadowSubsystem* mutter)
2732 WINPR_ASSERT(mutter);
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");
2746 sd_bus_call_method(mutter->bus, RD_NAME, mutter->rdSessionPath, RD_SESSION_IFACE,
2747 "EnableClipboard", &err,
nullptr,
"a{sv}", 0);
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)));
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;
2762 sd_bus_error_free(&err);
2765static void mutter_shadow_process_message(mutterShadowSubsystem* mutter, wMessage* message)
2767 WINPR_ASSERT(mutter);
2768 WINPR_ASSERT(message);
2770 switch (message->id)
2772 case SHADOW_MSG_IN_REFRESH_REQUEST_ID:
2773 mutter_shadow_refresh(mutter);
2776 case MUTTER_MSG_CLIPBOARD_OFFER:
2777 mutter_clipboard_set_selection(mutter, message->context);
2780 case MUTTER_MSG_CLIPBOARD_READ:
2781 mutter_clipboard_start_read(mutter, message);
2784 case MUTTER_MSG_CLIPBOARD_PROVIDE:
2785 mutter_clipboard_start_write(mutter, message);
2788 case MUTTER_MSG_CLIPBOARD_WRITE_DONE:
2790 const mutterClipboardJob* job = message->context;
2792 mutter_clipboard_write_done(mutter, job->serial, job->ok);
2797 WLog_ERR(TAG,
"Unknown message id: %" PRIu32
"", message->id);
2802 message->Free(message);
2807static void mutter_record_again(mutterShadowSubsystem* mutter)
2809 WINPR_ASSERT(mutter);
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");
2816 mutter->streamAddedSlot = sd_bus_slot_unref(mutter->streamAddedSlot);
2817 free(mutter->streamPath);
2818 mutter->streamPath =
nullptr;
2820 sd_bus_error err = SD_BUS_ERROR_NULL;
2822 if (mutter_record_monitor(mutter, connector))
2824 mutter->awaitNode = TRUE;
2825 started = sd_bus_call_method(mutter->bus, SC_NAME, mutter->streamPath, SC_STREAM_IFACE,
2826 "Start", &err,
nullptr,
"");
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)));
2834 sd_bus_error_free(&err);
2840 mutter->awaitNode = FALSE;
2841 while (sd_bus_process(mutter->bus,
nullptr) > 0)
2844 if (!mutter->closed)
2845 mutter->monitorGone = TRUE;
2846 mutter->closed = TRUE;
2851static void mutter_reconnect(mutterShadowSubsystem* mutter)
2853 WINPR_ASSERT(mutter);
2855 mutter->reconnect = FALSE;
2856 if (!mutter_stream_connect_node(mutter))
2858 mutter->monitorGone = TRUE;
2859 mutter->closed = TRUE;
2862 mutter_read_stream_parameters(mutter);
2863 mutter->fullPaint = TRUE;
2864 WLog_INFO(TAG,
"sharing again from PipeWire node %" PRIu32, mutter->nodeId);
2868static DWORD WINAPI mutter_shadow_subsystem_thread(LPVOID arg)
2870 mutterShadowSubsystem* mutter = (mutterShadowSubsystem*)arg;
2871 WINPR_ASSERT(mutter);
2874 HANDLE events[6] = WINPR_C_ARRAY_INIT;
2877 HANDLE busEvent = CreateFileDescriptorEventA(
nullptr, FALSE, FALSE, sd_bus_get_fd(mutter->bus),
2879 HANDLE pwEvent = CreateFileDescriptorEventA(
nullptr, FALSE, FALSE, pw_loop_get_fd(mutter->loop),
2881 HANDLE eiEvent = mutter->ei ? CreateFileDescriptorEventA(
nullptr, FALSE, FALSE,
2882 ei_get_fd(mutter->ei), WINPR_FD_READ)
2884 if (!busEvent || !pwEvent || (mutter->ei && !eiEvent))
2886 WLog_ERR(TAG,
"cannot wait on the session's connections");
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;
2896 events[count++] = eiEvent;
2902 uint64_t usec = UINT64_MAX;
2903 DWORD timeout = 1000;
2904 if ((sd_bus_get_timeout(mutter->bus, &usec) >= 0) && (usec != UINT64_MAX))
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);
2912 if (mutter->streamGone)
2913 timeout = MIN(timeout, 50);
2915 const DWORD status = WaitForMultipleObjects(count, events, FALSE, timeout);
2916 if ((status == WAIT_FAILED) || (WaitForSingleObject(mutter->stopEvent, 0) == WAIT_OBJECT_0))
2919 while (sd_bus_process(mutter->bus,
nullptr) > 0)
2922 (void)sd_bus_flush(mutter->bus);
2923 if (mutter->closed && !mutter->closedLogged)
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");
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);
2941 if (WaitForSingleObject(mutter->clientsEvent, 0) == WAIT_OBJECT_0)
2944 (void)ResetEvent(mutter->clientsEvent);
2945 mutter_audio_follow_clients(mutter);
2948 pw_loop_enter(mutter->loop);
2949 (void)pw_loop_iterate(mutter->loop, 0);
2950 pw_loop_leave(mutter->loop);
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);
2962 EnterCriticalSection(&mutter->eiLock);
2963 mutter_process_ei_events(mutter);
2964 LeaveCriticalSection(&mutter->eiLock);
2967 wMessage message = WINPR_C_ARRAY_INIT;
2968 while (MessageQueue_Peek(MsgPipe->In, &message, TRUE))
2970 if (message.id == WMQ_QUIT)
2972 mutter_shadow_process_message(mutter, &message);
2978 (void)CloseHandle(busEvent);
2980 (void)CloseHandle(pwEvent);
2982 (void)CloseHandle(eiEvent);
2988static int mutter_shadow_subsystem_init(rdpShadowSubsystem* subsystem)
2990 mutterShadowSubsystem* mutter = (mutterShadowSubsystem*)subsystem;
2991 WINPR_ASSERT(mutter);
2993 const int opened = sd_bus_open_user(&mutter->bus);
2996 char buffer[128] = WINPR_C_ARRAY_INIT;
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)));
3004 if (!mutter_wait_for_name(mutter->bus, RD_NAME, MUTTER_NAME_TIMEOUT_MS))
3006 WLog_ERR(TAG,
"%s is not on the session bus: is this a GNOME session?", RD_NAME);
3011 mutter->virtualMonitor = mutter_virtual_size(&mutter->virtualWidth, &mutter->virtualHeight);
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");
3020 if (mutter->virtualMonitor)
3023 subsystem->monitors[0] = (
MONITOR_DEF){ 0, 0, (INT32)mutter->virtualWidth - 1,
3024 (INT32)mutter->virtualHeight - 1, 1 };
3025 mutter->connectors[0][0] =
'\0';
3030 WLog_DBG(TAG,
"DisplayConfig.GetCurrentState");
3031 count = mutter_list_monitors(mutter->bus, subsystem->monitors, mutter->connectors,
3032 MUTTER_SHADOW_MAX_MONITORS);
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';
3042 subsystem->numMonitors = count;
3043 if (subsystem->selectedMonitor >= count)
3044 subsystem->selectedMonitor = 0;
3046 const char* connector = mutter->connectors[subsystem->selectedMonitor];
3047 if (!mutter_setup_sessions(mutter, connector))
3049 if (!mutter_connect_stream(mutter))
3052 mutter_set_monitor_size(mutter);
3054 if (!mutter_audio_init(mutter))
3055 WLog_WARN(TAG,
"sound: no capture of the default sink: no sound");
3061 if (subsystem->server)
3062 subsystem->server->ShowMouseCursor = TRUE;
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");
3069 "sharing monitor %s (%" PRIu32
" of %" PRIu32
"): %" PRIu32
"x%" PRIu32
3071 connector[0] ? connector :
"(primary)", subsystem->selectedMonitor, count,
3072 mutter->width, mutter->height, mutter->ei ?
"through libei" :
"off");
3077static int mutter_shadow_subsystem_uninit(rdpShadowSubsystem* subsystem)
3079 mutterShadowSubsystem* mutter = (mutterShadowSubsystem*)subsystem;
3083 mutter_audio_uninit(mutter);
3085 pw_stream_destroy(mutter->stream);
3086 mutter->stream =
nullptr;
3087 if (mutter->registry)
3089 spa_hook_remove(&mutter->registryListener);
3090 pw_proxy_destroy((
struct pw_proxy*)mutter->registry);
3092 mutter->registry =
nullptr;
3094 pw_core_disconnect(mutter->core);
3095 mutter->core =
nullptr;
3096 if (mutter->context)
3097 pw_context_destroy(mutter->context);
3098 mutter->context =
nullptr;
3100 pw_loop_destroy(mutter->loop);
3101 mutter->loop =
nullptr;
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);
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);
3123 mutter->started = FALSE;
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;
3137static int mutter_shadow_subsystem_start(rdpShadowSubsystem* subsystem)
3139 mutterShadowSubsystem* mutter = (mutterShadowSubsystem*)subsystem;
3140 if (!mutter || !mutter->bus || !mutter->loop)
3143 (void)ResetEvent(mutter->stopEvent);
3144 if (mutter->clipboardEnabled)
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)
3152 WLog_ERR(TAG,
"Failed to create the clipboard thread");
3157 mutter->thread = CreateThread(
nullptr, 0, mutter_shadow_subsystem_thread, mutter, 0,
nullptr);
3158 if (!mutter->thread)
3160 WLog_ERR(TAG,
"Failed to create thread");
3167static int mutter_shadow_subsystem_stop(rdpShadowSubsystem* subsystem)
3169 mutterShadowSubsystem* mutter = (mutterShadowSubsystem*)subsystem;
3175 (void)SetEvent(mutter->stopEvent);
3176 (void)WaitForSingleObject(mutter->thread, INFINITE);
3177 (void)CloseHandle(mutter->thread);
3178 mutter->thread =
nullptr;
3181 if (mutter->clipboardThread)
3183 (void)SetEvent(mutter->stopEvent);
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;
3190 MessageQueue_Free(mutter->clipboardJobs);
3191 mutter->clipboardJobs =
nullptr;
3196static BOOL mutter_shadow_client_connect(rdpShadowSubsystem* subsystem,
3197 WINPR_ATTR_UNUSED rdpShadowClient* client)
3199 mutterShadowSubsystem* mutter = (mutterShadowSubsystem*)subsystem;
3200 WINPR_ASSERT(mutter);
3203 mutter->cursorResend = TRUE;
3204 (void)InterlockedIncrement(&mutter->clients);
3205 (void)SetEvent(mutter->clientsEvent);
3209static void mutter_shadow_client_disconnect(rdpShadowSubsystem* subsystem,
3210 WINPR_ATTR_UNUSED rdpShadowClient* client)
3212 mutterShadowSubsystem* mutter = (mutterShadowSubsystem*)subsystem;
3213 WINPR_ASSERT(mutter);
3215 (void)InterlockedDecrement(&mutter->clients);
3216 (void)SetEvent(mutter->clientsEvent);
3219static void mutter_shadow_subsystem_free(rdpShadowSubsystem* subsystem)
3221 mutterShadowSubsystem* mutter = (mutterShadowSubsystem*)subsystem;
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);
3239static rdpShadowSubsystem* mutter_shadow_subsystem_new(
void)
3241 mutterShadowSubsystem* mutter = calloc(1,
sizeof(mutterShadowSubsystem));
3245 if (!InitializeCriticalSectionAndSpinCount(&mutter->eiLock, 4000))
3251 mutter->stopEvent = CreateEvent(
nullptr, TRUE, FALSE,
nullptr);
3254 mutter->clientsEvent = CreateEvent(
nullptr, TRUE, FALSE,
nullptr);
3255 if (!mutter->stopEvent || !mutter->clientsEvent)
3257 mutter_shadow_subsystem_free(&mutter->common);
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;
3277 mutter->common.Authenticate = mutter_shadow_pam_authenticate;
3279 return &mutter->common;
3282FREERDP_ENTRY_POINT(WINPR_ATTR_NODISCARD
const char* MutterShadowSubsystemName(
void))
3288 WINPR_ATTR_NODISCARD
int MutterShadowSubsystemEntry(RDP_SHADOW_ENTRY_POINTS* pEntryPoints))
3290 WINPR_ASSERT(pEntryPoints);
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;