FreeRDP
Loading...
Searching...
No Matches
mac_shadow.c
1
20#include <math.h>
21#include <pwd.h>
22#include <unistd.h>
23
24#include <winpr/crt.h>
25#include <winpr/synch.h>
26#include <winpr/input.h>
27#include <winpr/sysinfo.h>
28
29#include <freerdp/server/server-common.h>
30#include <freerdp/codec/audio.h>
31#include <freerdp/codec/color.h>
32#include <freerdp/codec/dsp.h>
33#include <freerdp/codec/region.h>
34#include <freerdp/log.h>
35
36#include <ApplicationServices/ApplicationServices.h>
37
38#ifdef WITH_PAM
39#include <security/pam_appl.h>
40#endif
41
42#include "mac_shadow.h"
43
44#define TAG SERVER_TAG("shadow.mac")
45
46/* Frames per second asked from ScreenCaptureKit at most */
47#define MAC_SHADOW_FPS 30
48/* how often to look for the display again after the stream ended */
49#define MAC_SHADOW_RESTART_MS 1000
50/* how often to look at the cursor, and how long after a client moved the
51 * pointer not to tell it where the pointer is: its own cursor is ahead of
52 * what the server sees by a round trip, and would jump back */
53#define MAC_SHADOW_CURSOR_MS 30
54#define MAC_SHADOW_CURSOR_QUIET_MS 500
55/* the largest cursor sent, in points */
56#define MAC_SHADOW_CURSOR_MAX 96
57/* how often the sound statistics are logged (debug level) */
58#define MAC_SHADOW_AUDIO_LOG_MS 5000
59
60/* Two clicks closer than this, in time and in points, make a double click */
61#define MAC_SHADOW_CLICK_MS 500
62#define MAC_SHADOW_CLICK_DISTANCE 4.0
63
64/* What the clients hear: 48 kHz, 2 channels, 16 bits, the format
65 * ScreenCaptureKit is asked for. The build may have no resampler, so every
66 * format offered has that rate and channel count, and 16 bits a sample:
67 * rdpsnd sizes its buffers from the bits and channels of the first one. Opus
68 * first, so that a client that has it takes it (96 kbit/s instead of
69 * 1.5 Mbit/s for PCM). Its block alignment is 1: rdpsnd pads every wave to
70 * a multiple of it with zeros, and an Opus packet with zeros after it no
71 * longer decodes. */
72#define MAC_AUDIO_RATE 48000
73#define MAC_AUDIO_CHANNELS 2
74#define MAC_AUDIO_FRAME_BYTES (MAC_AUDIO_CHANNELS * 2)
75/* samples are sent in messages of 20 ms */
76#define MAC_AUDIO_MESSAGE_FRAMES (MAC_AUDIO_RATE / 50)
77
78static const AUDIO_FORMAT mac_audio_offered_formats[] = {
79 { WAVE_FORMAT_OPUS, MAC_AUDIO_CHANNELS, MAC_AUDIO_RATE, 12000, 1, 16, 0, nullptr },
80 { WAVE_FORMAT_PCM, MAC_AUDIO_CHANNELS, MAC_AUDIO_RATE, (MAC_AUDIO_RATE * MAC_AUDIO_FRAME_BYTES),
81 MAC_AUDIO_FRAME_BYTES, 16, 0, nullptr },
82};
83
84/* The format of the samples in each message: it outlives every client, since
85 * rdpsnd keeps the pointer as its source format */
86static AUDIO_FORMAT mac_audio_source_format = { WAVE_FORMAT_PCM,
87 MAC_AUDIO_CHANNELS,
88 MAC_AUDIO_RATE,
89 (MAC_AUDIO_RATE * MAC_AUDIO_FRAME_BYTES),
90 MAC_AUDIO_FRAME_BYTES,
91 16,
92 0,
93 nullptr };
94
95/* Apple virtual key codes of the modifier keys (HIToolbox/Events.h) */
96enum
97{
98 MAC_VK_COMMAND = 0x37,
99 MAC_VK_SHIFT = 0x38,
100 MAC_VK_CAPSLOCK = 0x39,
101 MAC_VK_OPTION = 0x3A,
102 MAC_VK_CONTROL = 0x3B,
103 MAC_VK_RIGHT_COMMAND = 0x36,
104 MAC_VK_RIGHT_SHIFT = 0x3C,
105 MAC_VK_RIGHT_OPTION = 0x3D,
106 MAC_VK_RIGHT_CONTROL = 0x3E,
107 MAC_VK_FUNCTION = 0x3F
108};
109
110static CGEventFlags mac_shadow_modifier_flag(CGKeyCode keycode)
111{
112 switch (keycode)
113 {
114 case MAC_VK_SHIFT:
115 case MAC_VK_RIGHT_SHIFT:
116 return kCGEventFlagMaskShift;
117 case MAC_VK_CONTROL:
118 case MAC_VK_RIGHT_CONTROL:
119 return kCGEventFlagMaskControl;
120 case MAC_VK_OPTION:
121 case MAC_VK_RIGHT_OPTION:
122 return kCGEventFlagMaskAlternate;
123 case MAC_VK_COMMAND:
124 case MAC_VK_RIGHT_COMMAND:
125 return kCGEventFlagMaskCommand;
126 case MAC_VK_FUNCTION:
127 return kCGEventFlagMaskSecondaryFn;
128 default:
129 return 0;
130 }
131}
132
133/* The active displays, with their monitor rectangles moved so that the
134 * leftmost and topmost one start at 0 (the shadow surface cannot start
135 * below 0); bounds are left in global display coordinates. */
136static UINT32 mac_shadow_list_displays(MONITOR_DEF* monitors, CGDirectDisplayID* ids,
137 CGRect* bounds, UINT32 maxMonitors)
138{
139 CGDirectDisplayID active[MAC_SHADOW_MAX_DISPLAYS] = WINPR_C_ARRAY_INIT;
140 uint32_t count = 0;
141
142 if (CGGetActiveDisplayList(ARRAYSIZE(active), active, &count) != kCGErrorSuccess)
143 return 0;
144
145 count = MIN(count, maxMonitors);
146 CGFloat minX = 0;
147 CGFloat minY = 0;
148
149 for (uint32_t x = 0; x < count; x++)
150 {
151 const CGRect rect = CGDisplayBounds(active[x]);
152 minX = MIN(minX, rect.origin.x);
153 minY = MIN(minY, rect.origin.y);
154 }
155
156 const CGDirectDisplayID mainDisplay = CGMainDisplayID();
157 for (uint32_t x = 0; x < count; x++)
158 {
159 const CGRect rect = CGDisplayBounds(active[x]);
160 MONITOR_DEF* monitor = &monitors[x];
161 monitor->left = (INT32)(rect.origin.x - minX);
162 monitor->top = (INT32)(rect.origin.y - minY);
163 monitor->right = monitor->left + (INT32)rect.size.width - 1;
164 monitor->bottom = monitor->top + (INT32)rect.size.height - 1;
165 monitor->flags = (active[x] == mainDisplay) ? 1 : 0;
166
167 if (ids)
168 ids[x] = active[x];
169 if (bounds)
170 bounds[x] = rect;
171 }
172
173 return count;
174}
175
176static UINT32 mac_shadow_enum_monitors(MONITOR_DEF* monitors, UINT32 maxMonitors)
177{
178 return mac_shadow_list_displays(monitors, nullptr, nullptr, maxMonitors);
179}
180
181static BOOL mac_shadow_post(CGEventRef event)
182{
183 if (!event)
184 return FALSE;
185
186 CGEventPost(kCGHIDEventTap, event);
187 CFRelease(event);
188 return TRUE;
189}
190
191WINPR_ATTR_NODISCARD
192static BOOL mac_shadow_input_synchronize_event(rdpShadowSubsystem* subsystem,
193 rdpShadowClient* client,
194 WINPR_ATTR_UNUSED UINT32 flags)
195{
196 if (!subsystem || !client)
197 return FALSE;
198
199 return TRUE;
200}
201
202WINPR_ATTR_NODISCARD
203static BOOL mac_shadow_input_keyboard_event(rdpShadowSubsystem* subsystem, rdpShadowClient* client,
204 UINT16 flags, UINT8 code)
205{
206 macShadowSubsystem* mac = (macShadowSubsystem*)subsystem;
207
208 if (!subsystem || !client)
209 return FALSE;
210
211 DWORD scancode = code;
212 if (flags & KBD_FLAGS_EXTENDED)
213 scancode |= KBDEXT;
214
215 /* WinPR's Apple table lists the extended keys (both Command keys, right
216 * Option, the arrows, Home, End, Page Up/Down, Forward Delete, keypad
217 * Enter, ...) with KBDEXT set, so the flag has to come along */
218 DWORD vkcode = GetVirtualKeyCodeFromVirtualScanCode(scancode, WINPR_KBD_TYPE_IBM_ENHANCED);
219 if (flags & KBD_FLAGS_EXTENDED)
220 vkcode |= KBDEXT;
221 const DWORD keycode = GetKeycodeFromVirtualKeyCode(vkcode, WINPR_KEYCODE_TYPE_APPLE);
222
223 /* Apple's key code 0 is A: any other key that maps to 0 has no Mac key */
224 if ((keycode == 0) && (vkcode != VK_KEY_A))
225 {
226 WLog_DBG(TAG, "no key for scancode 0x%04" PRIx32, scancode);
227 return TRUE;
228 }
229
230 /* A key is pressed unless the release flag is set; KBD_FLAGS_DOWN only
231 * says it was down already (a repeat). */
232 const BOOL down = (flags & KBD_FLAGS_RELEASE) ? FALSE : TRUE;
233
234 if ((keycode == MAC_VK_CAPSLOCK) && !down)
235 return TRUE;
236
237 CGEventRef event = CGEventCreateKeyboardEvent(nullptr, (CGKeyCode)keycode, down);
238 if (!event)
239 return FALSE;
240
241 /* Events posted from here do not move the system's modifier state, so the
242 * modifiers held are tracked and set on every event, and a modifier key
243 * itself becomes a flags-changed event, as from a real keyboard. */
244 const CGEventFlags modifier = mac_shadow_modifier_flag((CGKeyCode)keycode);
245 if (modifier)
246 {
247 if (down)
248 mac->modifiers |= modifier;
249 else
250 mac->modifiers &= ~modifier;
251 CGEventSetType(event, kCGEventFlagsChanged);
252 }
253 else if (keycode == MAC_VK_CAPSLOCK)
254 {
255 mac->modifiers ^= kCGEventFlagMaskAlphaShift;
256 CGEventSetType(event, kCGEventFlagsChanged);
257 }
258
259 CGEventSetFlags(event, mac->modifiers);
260 return mac_shadow_post(event);
261}
262
263WINPR_ATTR_NODISCARD
264static BOOL mac_shadow_input_unicode_keyboard_event(rdpShadowSubsystem* subsystem,
265 rdpShadowClient* client, UINT16 flags,
266 UINT16 code)
267{
268 if (!subsystem || !client)
269 return FALSE;
270
271 /* a key event with no key: the text it types is the character */
272 const BOOL down = (flags & KBD_FLAGS_RELEASE) ? FALSE : TRUE;
273 CGEventRef event = CGEventCreateKeyboardEvent(nullptr, 0, down);
274 if (!event)
275 return FALSE;
276
277 const UniChar character = code;
278 CGEventKeyboardSetUnicodeString(event, 1, &character);
279 return mac_shadow_post(event);
280}
281
282static CGPoint mac_shadow_point(const macShadowSubsystem* mac, UINT16 x, UINT16 y)
283{
284 const CGFloat scaleX = mac->bounds.size.width / (CGFloat)mac->width;
285 const CGFloat scaleY = mac->bounds.size.height / (CGFloat)mac->height;
286
287 return CGPointMake(mac->bounds.origin.x + (CGFloat)x * scaleX,
288 mac->bounds.origin.y + (CGFloat)y * scaleY);
289}
290
291static BOOL mac_shadow_mouse_event(macShadowSubsystem* mac, CGEventType type, CGPoint point,
292 CGMouseButton button, int64_t clicks)
293{
294 CGEventRef event = CGEventCreateMouseEvent(nullptr, type, point, button);
295 if (!event)
296 return FALSE;
297
298 if (clicks > 0)
299 CGEventSetIntegerValueField(event, kCGMouseEventClickState, clicks);
300 CGEventSetFlags(event, mac->modifiers);
301 return mac_shadow_post(event);
302}
303
304static BOOL mac_shadow_mouse_move(macShadowSubsystem* mac, CGPoint point)
305{
306 EnterCriticalSection(&mac->clientLock);
307 mac->pointerInput = GetTickCount64();
308 LeaveCriticalSection(&mac->clientLock);
309
310 /* with a button held the move is a drag, as macOS expects it */
311 if (mac->buttonsDown & (1u << kCGMouseButtonLeft))
312 return mac_shadow_mouse_event(mac, kCGEventLeftMouseDragged, point, kCGMouseButtonLeft, 0);
313 if (mac->buttonsDown & (1u << kCGMouseButtonRight))
314 return mac_shadow_mouse_event(mac, kCGEventRightMouseDragged, point, kCGMouseButtonRight,
315 0);
316 for (UINT32 button = kCGMouseButtonCenter; button < 32; button++)
317 {
318 if (mac->buttonsDown & (1u << button))
319 return mac_shadow_mouse_event(mac, kCGEventOtherMouseDragged, point,
320 (CGMouseButton)button, 0);
321 }
322 return mac_shadow_mouse_event(mac, kCGEventMouseMoved, point, kCGMouseButtonLeft, 0);
323}
324
325static BOOL mac_shadow_mouse_button(macShadowSubsystem* mac, CGMouseButton button, BOOL down,
326 CGPoint point)
327{
328 CGEventType type = kCGEventNull;
329
330 switch (button)
331 {
332 case kCGMouseButtonLeft:
333 type = down ? kCGEventLeftMouseDown : kCGEventLeftMouseUp;
334 break;
335 case kCGMouseButtonRight:
336 type = down ? kCGEventRightMouseDown : kCGEventRightMouseUp;
337 break;
338 default:
339 type = down ? kCGEventOtherMouseDown : kCGEventOtherMouseUp;
340 break;
341 }
342
343 /* macOS counts clicks from the click state of the event itself: without
344 * it a double click is two single clicks. */
345 if (down)
346 {
347 const UINT64 now = GetTickCount64();
348 if ((button == mac->lastClickButton) && (now - mac->lastClickTime <= MAC_SHADOW_CLICK_MS) &&
349 (fabs(point.x - mac->lastClickPoint.x) <= MAC_SHADOW_CLICK_DISTANCE) &&
350 (fabs(point.y - mac->lastClickPoint.y) <= MAC_SHADOW_CLICK_DISTANCE))
351 mac->clickCount++;
352 else
353 mac->clickCount = 1;
354
355 mac->lastClickTime = now;
356 mac->lastClickButton = button;
357 mac->lastClickPoint = point;
358 mac->buttonsDown |= (1u << button);
359 }
360 else
361 mac->buttonsDown &= ~(1u << button);
362
363 const int64_t clicks = (button == mac->lastClickButton) ? mac->clickCount : 1;
364 return mac_shadow_mouse_event(mac, type, point, button, clicks);
365}
366
367static BOOL mac_shadow_mouse_wheel(macShadowSubsystem* mac, UINT16 flags, BOOL horizontal)
368{
369 /* 9-bit two's complement, 120 for one notch */
370 INT32 delta = flags & WheelRotationMask;
371 if (flags & PTR_FLAGS_WHEEL_NEGATIVE)
372 delta -= 0x200;
373
374 INT32* remainder = &mac->wheelRemainder[horizontal ? 1 : 0];
375 *remainder += delta;
376 const INT32 lines = *remainder / 120;
377 if (lines == 0)
378 return TRUE;
379 *remainder -= lines * 120;
380
381 /* RDP: positive is up, and right; Quartz: positive is up, and left */
382 const int32_t vertical = horizontal ? 0 : lines;
383 const int32_t sideways = horizontal ? -lines : 0;
384 CGEventRef event =
385 CGEventCreateScrollWheelEvent(nullptr, kCGScrollEventUnitLine, 2, vertical, sideways);
386 if (!event)
387 return FALSE;
388
389 CGEventSetFlags(event, mac->modifiers);
390 return mac_shadow_post(event);
391}
392
393WINPR_ATTR_NODISCARD
394static BOOL mac_shadow_input_mouse_event(rdpShadowSubsystem* subsystem, rdpShadowClient* client,
395 UINT16 flags, UINT16 x, UINT16 y)
396{
397 macShadowSubsystem* mac = (macShadowSubsystem*)subsystem;
398
399 if (!subsystem || !client)
400 return FALSE;
401
402 if (flags & PTR_FLAGS_WHEEL)
403 return mac_shadow_mouse_wheel(mac, flags, FALSE);
404 if (flags & PTR_FLAGS_HWHEEL)
405 return mac_shadow_mouse_wheel(mac, flags, TRUE);
406
407 const CGPoint point = mac_shadow_point(mac, x, y);
408
409 if (flags & PTR_FLAGS_MOVE)
410 {
411 if (!mac_shadow_mouse_move(mac, point))
412 return FALSE;
413 }
414
415 const BOOL down = (flags & PTR_FLAGS_DOWN) ? TRUE : FALSE;
416 if (flags & PTR_FLAGS_BUTTON1)
417 return mac_shadow_mouse_button(mac, kCGMouseButtonLeft, down, point);
418 if (flags & PTR_FLAGS_BUTTON2)
419 return mac_shadow_mouse_button(mac, kCGMouseButtonRight, down, point);
420 if (flags & PTR_FLAGS_BUTTON3)
421 return mac_shadow_mouse_button(mac, kCGMouseButtonCenter, down, point);
422
423 return TRUE;
424}
425
426WINPR_ATTR_NODISCARD
427static BOOL mac_shadow_input_extended_mouse_event(rdpShadowSubsystem* subsystem,
428 rdpShadowClient* client, UINT16 flags, UINT16 x,
429 UINT16 y)
430{
431 macShadowSubsystem* mac = (macShadowSubsystem*)subsystem;
432
433 if (!subsystem || !client)
434 return FALSE;
435
436 const CGPoint point = mac_shadow_point(mac, x, y);
437
438 if (flags & PTR_FLAGS_MOVE)
439 {
440 if (!mac_shadow_mouse_move(mac, point))
441 return FALSE;
442 }
443
444 /* X1 and X2 are the fourth and fifth buttons, 3 and 4 counted from 0 */
445 const BOOL down = (flags & PTR_XFLAGS_DOWN) ? TRUE : FALSE;
446 if (flags & PTR_XFLAGS_BUTTON1)
447 return mac_shadow_mouse_button(mac, (CGMouseButton)3, down, point);
448 if (flags & PTR_XFLAGS_BUTTON2)
449 return mac_shadow_mouse_button(mac, (CGMouseButton)4, down, point);
450
451 return TRUE;
452}
453
454#ifdef WITH_PAM
455typedef struct
456{
457 const char* user;
458 const char* password;
459} MAC_SHADOW_PAM_DATA;
460
461static int mac_shadow_pam_conv(int num_msg, const struct pam_message** msg,
462 struct pam_response** resp, void* appdata_ptr)
463{
464 const MAC_SHADOW_PAM_DATA* data = (const MAC_SHADOW_PAM_DATA*)appdata_ptr;
465
466 if ((num_msg <= 0) || !msg || !resp || !data)
467 return PAM_CONV_ERR;
468
469 struct pam_response* response = calloc((size_t)num_msg, sizeof(struct pam_response));
470 if (!response)
471 return PAM_BUF_ERR;
472
473 for (int index = 0; index < num_msg; index++)
474 {
475 const char* answer = nullptr;
476 switch (msg[index]->msg_style)
477 {
478 case PAM_PROMPT_ECHO_ON:
479 answer = data->user;
480 break;
481 case PAM_PROMPT_ECHO_OFF:
482 answer = data->password;
483 break;
484 default:
485 continue;
486 }
487
488 response[index].resp = _strdup(answer);
489 if (!response[index].resp)
490 {
491 for (int x = 0; x < index; x++)
492 free(response[x].resp);
493 free(response);
494 return PAM_BUF_ERR;
495 }
496 }
497
498 *resp = response;
499 return PAM_SUCCESS;
500}
501
502/* The screen shared is the one of the user running the server: only that
503 * user may log in, with the password of their account (checked by Open
504 * Directory, through PAM's "checkpw" service). */
505WINPR_ATTR_NODISCARD
506static int mac_shadow_pam_authenticate(WINPR_ATTR_UNUSED rdpShadowSubsystem* subsystem,
507 WINPR_ATTR_UNUSED rdpShadowClient* client, const char* user,
508 WINPR_ATTR_UNUSED const char* domain, const char* password)
509{
510 if (!user || !password)
511 return -1;
512
513 const struct passwd* owner = getpwuid(getuid());
514 if (!owner || !owner->pw_name || (strcmp(owner->pw_name, user) != 0))
515 {
516 WLog_ERR(TAG, "login as '%s' refused: this screen belongs to '%s'", user,
517 (owner && owner->pw_name) ? owner->pw_name : "?");
518 return -1;
519 }
520
521 MAC_SHADOW_PAM_DATA data = { .user = user, .password = password };
522 const struct pam_conv conv = { .conv = mac_shadow_pam_conv, .appdata_ptr = &data };
523 pam_handle_t* handle = nullptr;
524
525 int status = pam_start("checkpw", user, &conv, &handle);
526 if (status != PAM_SUCCESS)
527 {
528 WLog_ERR(TAG, "pam_start failure: %s", pam_strerror(handle, status));
529 return -1;
530 }
531
532 /* checkpw takes the password as given (use_first_pass), it does not ask */
533 status = pam_set_item(handle, PAM_AUTHTOK, password);
534 if (status == PAM_SUCCESS)
535 status = pam_authenticate(handle, 0);
536 if (status == PAM_SUCCESS)
537 status = pam_acct_mgmt(handle, 0);
538
539 if (status != PAM_SUCCESS)
540 WLog_ERR(TAG, "login as '%s' failed: %s", user, pam_strerror(handle, status));
541
542 pam_end(handle, status);
543 return (status == PAM_SUCCESS) ? 1 : -1;
544}
545#endif
546
547static void mac_shadow_on_frame(void* context, CVPixelBufferRef frame, const CGRect* dirty,
548 size_t count)
549{
550 macShadowSubsystem* mac = (macShadowSubsystem*)context;
551 WINPR_ASSERT(mac);
552
553 const CGRect frameRect = CGRectMake(0, 0, (CGFloat)CVPixelBufferGetWidth(frame),
554 (CGFloat)CVPixelBufferGetHeight(frame));
555
556 EnterCriticalSection(&mac->frameLock);
557
558 /* keep the newest frame only: what changed in the ones skipped is still
559 * in the pending region, and the newest frame has it */
560 CVPixelBufferRetain(frame);
561 if (mac->frame)
562 CVPixelBufferRelease(mac->frame);
563 mac->frame = frame;
564
565 for (size_t x = 0; x < count; x++)
566 {
567 const CGRect rect = CGRectIntegral(CGRectIntersection(dirty[x], frameRect));
568 if (CGRectIsEmpty(rect))
569 continue;
570
571 const RECTANGLE_16 invalid = { .left = (UINT16)rect.origin.x,
572 .top = (UINT16)rect.origin.y,
573 .right = (UINT16)(rect.origin.x + rect.size.width),
574 .bottom = (UINT16)(rect.origin.y + rect.size.height) };
575 (void)region16_union_rect(&mac->pending, &mac->pending, &invalid);
576 }
577
578 LeaveCriticalSection(&mac->frameLock);
579 (void)SetEvent(mac->frameEvent);
580}
581
582static void mac_shadow_on_stopped(void* context)
583{
584 macShadowSubsystem* mac = (macShadowSubsystem*)context;
585 WINPR_ASSERT(mac);
586
587 /* the subsystem thread makes a new stream once the display is back */
588 (void)SetEvent(mac->lostEvent);
589}
590
591static void mac_shadow_audio_msg_free(WINPR_ATTR_UNUSED UINT32 id, SHADOW_MSG_OUT* msg)
592{
594 if (samples)
595 free(samples->buf);
596 free(samples);
597}
598
599/* Sends what s holds to every client, as one message */
600static void mac_shadow_audio_post(macShadowSubsystem* mac, wStream* s)
601{
602 const size_t size = Stream_GetPosition(s);
603 Stream_ResetPosition(s);
604 if (size == 0)
605 return;
606
607 rdpShadowServer* server = mac->common.server;
608 if (!server || !server->clients || (ArrayList_Count(server->clients) == 0))
609 return;
610
612 if (!msg)
613 return;
614
615 msg->buf = malloc(size);
616 if (!msg->buf)
617 {
618 free(msg);
619 return;
620 }
621
622 memcpy(msg->buf, Stream_Buffer(s), size);
623 mac->audioMessages++;
624 msg->common.Free = mac_shadow_audio_msg_free;
625 msg->audio_format = &mac_audio_source_format;
626 msg->nFrames = size / MAC_AUDIO_FRAME_BYTES;
627 msg->wTimestamp = (UINT16)(GetTickCount64() & 0xFFFF);
628 /* the message is freed with its last reference, also when no client took it */
629 (void)shadow_client_boardcast_msg(server, nullptr, SHADOW_MSG_OUT_AUDIO_OUT_SAMPLES_ID,
630 &msg->common, nullptr);
631}
632
633static INT16 mac_shadow_audio_sample(float value)
634{
635 if (value >= 1.0f)
636 return INT16_MAX;
637 if (value <= -1.0f)
638 return -INT16_MAX;
639 return (INT16)lrintf(value * (float)INT16_MAX);
640}
641
642static void mac_shadow_on_audio(void* context, const float* samples, size_t frames, UINT32 channels,
643 UINT32 rate)
644{
645 macShadowSubsystem* mac = (macShadowSubsystem*)context;
646 WINPR_ASSERT(mac);
647
648 if ((channels != MAC_AUDIO_CHANNELS) || (rate != MAC_AUDIO_RATE))
649 {
650 WLog_DBG(TAG, "sound in %" PRIu32 " Hz, %" PRIu32 " channels, not what was asked: dropped",
651 rate, channels);
652 return;
653 }
654
655 const UINT64 now = GetTickCount64();
656 if (mac->audioLogged == 0)
657 {
658 WLog_INFO(TAG, "sound: capturing %" PRIu32 " Hz, %" PRIu32 " channels", rate, channels);
659 mac->audioLogged = now;
660 }
661 else if (now - mac->audioLogged >= MAC_SHADOW_AUDIO_LOG_MS)
662 {
663 WLog_DBG(TAG,
664 "sound: %" PRIu64 " frames captured, %" PRIu64
665 " messages sent, peak %.3f in the last %" PRIu64 " ms",
666 mac->audioFrames, mac->audioMessages, (double)mac->audioPeak,
667 now - mac->audioLogged);
668 mac->audioFrames = 0;
669 mac->audioMessages = 0;
670 mac->audioPeak = 0.0f;
671 mac->audioLogged = now;
672 }
673 mac->audioFrames += frames;
674
675 wStream* s = mac->audioStream;
676 for (size_t f = 0; f < frames; f++)
677 {
678 for (UINT32 c = 0; c < channels; c++)
679 {
680 const float value = samples[f * channels + c];
681 mac->audioPeak = MAX(mac->audioPeak, fabsf(value));
682 Stream_Write_INT16(s, mac_shadow_audio_sample(value));
683 }
684
685 if (Stream_GetPosition(s) >= MAC_AUDIO_MESSAGE_FRAMES * MAC_AUDIO_FRAME_BYTES)
686 mac_shadow_audio_post(mac, s);
687 }
688}
689
690static BOOL mac_shadow_audio_init(macShadowSubsystem* mac)
691{
692 AUDIO_FORMAT* formats = audio_formats_new(ARRAYSIZE(mac_audio_offered_formats));
693 if (!formats)
694 return FALSE;
695
696 size_t count = 0;
697 for (size_t x = 0; x < ARRAYSIZE(mac_audio_offered_formats); x++)
698 {
699 const AUDIO_FORMAT* format = &mac_audio_offered_formats[x];
700 if (freerdp_dsp_supports_format(format, TRUE))
701 formats[count++] = *format;
702 }
703
704 mac->audioStream = Stream_New(nullptr, MAC_AUDIO_MESSAGE_FRAMES * MAC_AUDIO_FRAME_BYTES);
705 if (!mac->audioStream || (count == 0))
706 {
707 Stream_Free(mac->audioStream, TRUE);
708 mac->audioStream = nullptr;
709 audio_formats_free(formats, ARRAYSIZE(mac_audio_offered_formats));
710 return FALSE;
711 }
712
713 mac->common.rdpsndFormats = formats;
714 mac->common.nRdpsndFormats = count;
715 return TRUE;
716}
717
718static void mac_shadow_audio_uninit(macShadowSubsystem* mac)
719{
720 audio_formats_free(mac->common.rdpsndFormats, mac->common.nRdpsndFormats);
721 mac->common.rdpsndFormats = nullptr;
722 mac->common.nRdpsndFormats = 0;
723 Stream_Free(mac->audioStream, TRUE);
724 mac->audioStream = nullptr;
725}
726
727/* Copies what changed from the newest frame to the surface and lets the
728 * clients send it */
729static void mac_shadow_send_frame(macShadowSubsystem* mac)
730{
731 rdpShadowServer* server = mac->common.server;
732 rdpShadowSurface* surface = server->surface;
733 REGION16 region;
734 region16_init(&region);
735
736 EnterCriticalSection(&mac->frameLock);
737 CVPixelBufferRef frame = mac->frame;
738 if (frame)
739 CVPixelBufferRetain(frame);
740 (void)region16_copy(&region, &mac->pending);
741 region16_clear(&mac->pending);
742 LeaveCriticalSection(&mac->frameLock);
743
744 if (!frame)
745 goto out;
746
747 if ((CVPixelBufferGetWidth(frame) != surface->width) ||
748 (CVPixelBufferGetHeight(frame) != surface->height))
749 {
750 /* the display mode changed under us: the surface keeps its size */
751 if (!mac->sizeWarned)
752 WLog_WARN(TAG,
753 "frames of %zux%zu for a surface of %" PRIu32 "x%" PRIu32
754 ": restart the server after changing the display mode",
755 CVPixelBufferGetWidth(frame), CVPixelBufferGetHeight(frame),
756 (UINT32)surface->width, (UINT32)surface->height);
757 mac->sizeWarned = TRUE;
758 goto out;
759 }
760
761 {
762 const RECTANGLE_16 surfaceRect = { 0, 0, (UINT16)surface->width, (UINT16)surface->height };
763 (void)region16_intersect_rect(&region, &region, &surfaceRect);
764 }
765
766 if (region16_is_empty(&region))
767 goto out;
768
769 if (CVPixelBufferLockBaseAddress(frame, kCVPixelBufferLock_ReadOnly) != kCVReturnSuccess)
770 goto out;
771
772 {
773 const BYTE* data = CVPixelBufferGetBaseAddress(frame);
774 const size_t stride = CVPixelBufferGetBytesPerRow(frame);
775 UINT32 count = 0;
776 const RECTANGLE_16* rects = region16_rects(&region, &count);
777
778 EnterCriticalSection(&surface->lock);
779 for (UINT32 x = 0; x < count; x++)
780 {
781 const RECTANGLE_16* rect = &rects[x];
782 (void)freerdp_image_copy_no_overlap(
783 surface->data, surface->format, surface->scanline, rect->left, rect->top,
784 rect->right - rect->left, rect->bottom - rect->top, data, PIXEL_FORMAT_BGRX32,
785 (UINT32)stride, rect->left, rect->top, nullptr, FREERDP_FLIP_NONE);
786 (void)region16_union_rect(&surface->invalidRegion, &surface->invalidRegion, rect);
787 }
788 LeaveCriticalSection(&surface->lock);
789 }
790
791 (void)CVPixelBufferUnlockBaseAddress(frame, kCVPixelBufferLock_ReadOnly);
792
793 ArrayList_Lock(server->clients);
794 if (ArrayList_Count(server->clients) > 0)
795 shadow_subsystem_frame_update(&mac->common);
796 ArrayList_Unlock(server->clients);
797
798 EnterCriticalSection(&surface->lock);
799 region16_clear(&surface->invalidRegion);
800 LeaveCriticalSection(&surface->lock);
801
802out:
803 if (frame)
804 CVPixelBufferRelease(frame);
805 region16_uninit(&region);
806}
807
808static void mac_shadow_refresh(macShadowSubsystem* mac)
809{
810 const RECTANGLE_16 all = { 0, 0, (UINT16)mac->width, (UINT16)mac->height };
811
812 EnterCriticalSection(&mac->frameLock);
813 (void)region16_union_rect(&mac->pending, &mac->pending, &all);
814 LeaveCriticalSection(&mac->frameLock);
815
816 EnterCriticalSection(&mac->clientLock);
817 mac->cursorResend = TRUE;
818 LeaveCriticalSection(&mac->clientLock);
819
820 (void)SetEvent(mac->frameEvent);
821}
822
823static void mac_shadow_cursor_msg_free(UINT32 id, SHADOW_MSG_OUT* msg)
824{
825 if (msg && (id == SHADOW_MSG_OUT_POINTER_ALPHA_UPDATE_ID))
826 {
828 free(shape->xorMaskData);
829 free(shape->andMaskData);
830 }
831 free(msg);
832}
833
834static UINT32 mac_shadow_cursor_hash(const macCursorImage* image)
835{
836 /* FNV-1a over the size, the hot spot and the pixels */
837 UINT32 hash = 2166136261u;
838 const UINT32 header[] = { image->width, image->height, image->xHot, image->yHot };
839 const BYTE* parts[] = { (const BYTE*)header, image->pixels };
840 const size_t sizes[] = { sizeof(header), 4ull * image->width * image->height };
841 for (size_t p = 0; p < ARRAYSIZE(parts); p++)
842 {
843 for (size_t x = 0; x < sizes[p]; x++)
844 {
845 hash ^= parts[p][x];
846 hash *= 16777619u;
847 }
848 }
849 return hash;
850}
851
852static BOOL mac_shadow_cursor_send_shape(macShadowSubsystem* mac, const macCursorImage* image)
853{
855 calloc(1, sizeof(SHADOW_MSG_OUT_POINTER_ALPHA_UPDATE));
856 if (!msg)
857 return FALSE;
858
859 msg->xHot = image->xHot;
860 msg->yHot = image->yHot;
861 msg->width = image->width;
862 msg->height = image->height;
863 if (shadow_subsystem_pointer_convert_alpha_pointer_data_to_format(
864 image->pixels, PIXEL_FORMAT_BGRA32, TRUE, image->width, image->height, msg) < 0)
865 {
866 free(msg);
867 return FALSE;
868 }
869
870 msg->common.Free = mac_shadow_cursor_msg_free;
871 return shadow_client_boardcast_msg(mac->common.server, nullptr,
872 SHADOW_MSG_OUT_POINTER_ALPHA_UPDATE_ID, &msg->common,
873 nullptr) >= 0;
874}
875
876static void mac_shadow_cursor_send_position(macShadowSubsystem* mac, UINT64 now)
877{
878 EnterCriticalSection(&mac->clientLock);
879 const UINT64 input = mac->pointerInput;
880 LeaveCriticalSection(&mac->clientLock);
881 if (now - input < MAC_SHADOW_CURSOR_QUIET_MS)
882 return;
883
884 CGEventRef event = CGEventCreate(nullptr);
885 if (!event)
886 return;
887 const CGPoint where = CGEventGetLocation(event);
888 CFRelease(event);
889
890 /* from global points to the surface, as mac_shadow_point the other way */
891 const CGFloat x =
892 (where.x - mac->bounds.origin.x) * (CGFloat)mac->width / mac->bounds.size.width;
893 const CGFloat y =
894 (where.y - mac->bounds.origin.y) * (CGFloat)mac->height / mac->bounds.size.height;
895 const UINT32 xPos = (UINT32)MIN(MAX(x, 0.0), (CGFloat)mac->width - 1.0);
896 const UINT32 yPos = (UINT32)MIN(MAX(y, 0.0), (CGFloat)mac->height - 1.0);
897 if ((xPos == mac->common.pointerX) && (yPos == mac->common.pointerY))
898 return;
899
900 mac->common.pointerX = xPos;
901 mac->common.pointerY = yPos;
902
905 if (!msg)
906 return;
907 msg->xPos = xPos;
908 msg->yPos = yPos;
909 msg->common.Free = mac_shadow_cursor_msg_free;
910 (void)shadow_client_boardcast_msg(mac->common.server, nullptr,
911 SHADOW_MSG_OUT_POINTER_POSITION_UPDATE_ID, &msg->common,
912 nullptr);
913}
914
915/* The clients draw the cursor themselves (the frames have none): its shape
916 * when it changed, and where it is when something else than a client moved it */
917static void mac_shadow_cursor_update(macShadowSubsystem* mac, UINT64 now)
918{
919 if (now < mac->cursorNext)
920 return;
921 mac->cursorNext = now + MAC_SHADOW_CURSOR_MS;
922
923 macCursorImage image = WINPR_C_ARRAY_INIT;
924 if (mac_cursor_get(&image, MAC_SHADOW_CURSOR_MAX))
925 {
926 EnterCriticalSection(&mac->clientLock);
927 const BOOL resend = mac->cursorResend;
928 mac->cursorResend = FALSE;
929 LeaveCriticalSection(&mac->clientLock);
930
931 const UINT32 hash = mac_shadow_cursor_hash(&image);
932 if ((resend || !mac->cursorKnown || (hash != mac->cursorHash)) &&
933 mac_shadow_cursor_send_shape(mac, &image))
934 {
935 mac->cursorHash = hash;
936 mac->cursorKnown = TRUE;
937 }
938 mac_cursor_image_free(&image);
939 }
940
941 mac_shadow_cursor_send_position(mac, now);
942}
943
944static BOOL mac_shadow_has_clients(macShadowSubsystem* mac)
945{
946 EnterCriticalSection(&mac->clientLock);
947 const BOOL clients = mac->clients > 0;
948 LeaveCriticalSection(&mac->clientLock);
949 return clients;
950}
951
952static void mac_shadow_subsystem_process_message(macShadowSubsystem* mac, wMessage* message)
953{
954 switch (message->id)
955 {
956 case SHADOW_MSG_IN_REFRESH_REQUEST_ID:
957 mac_shadow_refresh(mac);
958 break;
959
960 default:
961 WLog_ERR(TAG, "Unknown message id: %" PRIu32 "", message->id);
962 break;
963 }
964
965 if (message->Free)
966 message->Free(message);
967}
968
969/* Replaces the stream that ended with a new one. It fails while there is no
970 * display to capture (the display is off, the lid is closed). */
971static BOOL mac_shadow_capture_restart(macShadowSubsystem* mac)
972{
973 /* ask Quartz first: ScreenCaptureKit would log an error on every try */
974 if (!CGDisplayIsActive(mac->display) || CGDisplayIsAsleep(mac->display))
975 return FALSE;
976
977 mac_capture_free(mac->capture);
978 mac->capture = mac_capture_new(&mac->config);
979 if (!mac->capture)
980 return FALSE;
981
982 if (!mac_capture_start(mac->capture))
983 {
984 mac_capture_free(mac->capture);
985 mac->capture = nullptr;
986 return FALSE;
987 }
988
989 /* the clients get the whole screen again with the first frame */
990 mac_shadow_refresh(mac);
991 return TRUE;
992}
993
994static DWORD WINAPI mac_shadow_subsystem_thread(LPVOID arg)
995{
996 macShadowSubsystem* mac = (macShadowSubsystem*)arg;
997 wMessagePipe* MsgPipe = mac->common.MsgPipe;
998 HANDLE events[] = { mac->stopEvent, MessageQueue_Event(MsgPipe->In), mac->frameEvent,
999 mac->lostEvent };
1000 BOOL lost = FALSE;
1001 UINT64 restart = 0;
1002
1003 while (1)
1004 {
1005 /* with clients, look at the cursor; without a stream, try to make one
1006 * again every second */
1007 const BOOL clients = mac_shadow_has_clients(mac);
1008 DWORD timeout = clients ? MAC_SHADOW_CURSOR_MS : INFINITE;
1009 if (lost)
1010 timeout = MIN(timeout, MAC_SHADOW_RESTART_MS);
1011
1012 const DWORD status = WaitForMultipleObjects(ARRAYSIZE(events), events, FALSE, timeout);
1013 if ((status == WAIT_FAILED) || (WaitForSingleObject(mac->stopEvent, 0) == WAIT_OBJECT_0))
1014 break;
1015
1016 const UINT64 now = GetTickCount64();
1017 if (WaitForSingleObject(mac->lostEvent, 0) == WAIT_OBJECT_0)
1018 {
1019 (void)ResetEvent(mac->lostEvent);
1020 if (!lost)
1021 WLog_WARN(TAG, "the screen is no longer captured (the display is off?): the "
1022 "clients see the last picture until it is back");
1023 lost = TRUE;
1024 restart = now + MAC_SHADOW_RESTART_MS;
1025 }
1026 else if (lost && (now >= restart))
1027 {
1028 restart = now + MAC_SHADOW_RESTART_MS;
1029 if (mac_shadow_capture_restart(mac))
1030 {
1031 WLog_INFO(TAG, "the display is back: capturing the screen again");
1032 lost = FALSE;
1033 }
1034 }
1035
1036 if (clients)
1037 mac_shadow_cursor_update(mac, now);
1038
1039 wMessage message = WINPR_C_ARRAY_INIT;
1040 while (MessageQueue_Peek(MsgPipe->In, &message, TRUE))
1041 {
1042 if (message.id == WMQ_QUIT)
1043 goto out;
1044 mac_shadow_subsystem_process_message(mac, &message);
1045 }
1046
1047 if (WaitForSingleObject(mac->frameEvent, 0) == WAIT_OBJECT_0)
1048 {
1049 (void)ResetEvent(mac->frameEvent);
1050 mac_shadow_send_frame(mac);
1051 }
1052 }
1053
1054out:
1055 ExitThread(0);
1056 return 0;
1057}
1058
1059static BOOL mac_shadow_check_input_access(void)
1060{
1061 /* posting input needs the Accessibility permission; without it the
1062 * clients only watch */
1063 const void* keys[] = { kAXTrustedCheckOptionPrompt };
1064 const void* values[] = { kCFBooleanTrue };
1065 CFDictionaryRef options =
1066 CFDictionaryCreate(kCFAllocatorDefault, keys, values, ARRAYSIZE(keys),
1067 &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks);
1068 const Boolean trusted = AXIsProcessTrustedWithOptions(options);
1069 if (options)
1070 CFRelease(options);
1071
1072 if (!trusted)
1073 WLog_WARN(TAG, "no permission to control the computer: the clients can only watch. "
1074 "Allow this program in System Settings > Privacy & Security > "
1075 "Accessibility, then start it again");
1076 return trusted ? TRUE : FALSE;
1077}
1078
1079WINPR_ATTR_NODISCARD
1080static int mac_shadow_subsystem_init(rdpShadowSubsystem* subsystem)
1081{
1082 macShadowSubsystem* mac = (macShadowSubsystem*)subsystem;
1083 CGDirectDisplayID ids[MAC_SHADOW_MAX_DISPLAYS] = WINPR_C_ARRAY_INIT;
1084 CGRect bounds[MAC_SHADOW_MAX_DISPLAYS] = WINPR_C_ARRAY_INIT;
1085
1086 const UINT32 count = mac_shadow_list_displays(subsystem->monitors, ids, bounds, ARRAYSIZE(ids));
1087 if (count == 0)
1088 {
1089 WLog_ERR(TAG, "no display found");
1090 return -1;
1091 }
1092
1093 subsystem->numMonitors = count;
1094 if (subsystem->selectedMonitor >= count)
1095 subsystem->selectedMonitor = 0;
1096
1097 const MONITOR_DEF* monitor = &subsystem->monitors[subsystem->selectedMonitor];
1098 mac->display = ids[subsystem->selectedMonitor];
1099 mac->bounds = bounds[subsystem->selectedMonitor];
1100 /* the surface has the size of the display in points: on a Retina display
1101 * ScreenCaptureKit scales the picture down to it */
1102 mac->width = (UINT32)(monitor->right - monitor->left + 1);
1103 mac->height = (UINT32)(monitor->bottom - monitor->top + 1);
1104
1105 MONITOR_DEF* virtualScreen = &subsystem->virtualScreen;
1106 virtualScreen->left = 0;
1107 virtualScreen->top = 0;
1108 virtualScreen->right = 0;
1109 virtualScreen->bottom = 0;
1110 for (UINT32 x = 0; x < count; x++)
1111 {
1112 virtualScreen->right = MAX(virtualScreen->right, subsystem->monitors[x].right);
1113 virtualScreen->bottom = MAX(virtualScreen->bottom, subsystem->monitors[x].bottom);
1114 }
1115 virtualScreen->flags = 1;
1116
1117 WLog_INFO(TAG,
1118 "sharing display %" PRIu32 " (%" PRIu32 " of %" PRIu32 "): %" PRIu32 "x%" PRIu32
1119 " at %.0f,%.0f",
1120 mac->display, subsystem->selectedMonitor, count, mac->width, mac->height,
1121 mac->bounds.origin.x, mac->bounds.origin.y);
1122
1123 if (!mac_capture_check_access())
1124 return -1;
1125
1126 (void)mac_shadow_check_input_access();
1127
1128 const BOOL audio = mac_shadow_audio_init(mac);
1129 if (!audio)
1130 WLog_WARN(TAG, "sound: no format to offer: no sound");
1131
1132 const macCaptureConfig config = { .display = mac->display,
1133 .width = mac->width,
1134 .height = mac->height,
1135 .fps = MAC_SHADOW_FPS,
1136 /* the clients draw the cursor */
1137 .showCursor = FALSE,
1138 .audio = audio,
1139 .audioRate = MAC_AUDIO_RATE,
1140 .audioChannels = MAC_AUDIO_CHANNELS,
1141 .onFrame = mac_shadow_on_frame,
1142 .onAudio = audio ? mac_shadow_on_audio : nullptr,
1143 .onStopped = mac_shadow_on_stopped,
1144 .context = mac };
1145 mac->config = config;
1146
1147 mac->capture = mac_capture_new(&mac->config);
1148 if (!mac->capture)
1149 return -1;
1150
1151 return 1;
1152}
1153
1154static int mac_shadow_subsystem_uninit(rdpShadowSubsystem* subsystem)
1155{
1156 macShadowSubsystem* mac = (macShadowSubsystem*)subsystem;
1157 if (!mac)
1158 return -1;
1159
1160 mac_capture_free(mac->capture);
1161 mac->capture = nullptr;
1162
1163 EnterCriticalSection(&mac->frameLock);
1164 if (mac->frame)
1165 CVPixelBufferRelease(mac->frame);
1166 mac->frame = nullptr;
1167 region16_clear(&mac->pending);
1168 LeaveCriticalSection(&mac->frameLock);
1169
1170 mac_shadow_audio_uninit(mac);
1171 return 1;
1172}
1173
1174static int mac_shadow_subsystem_stop(rdpShadowSubsystem* subsystem)
1175{
1176 macShadowSubsystem* mac = (macShadowSubsystem*)subsystem;
1177
1178 if (!mac)
1179 return -1;
1180
1181 /* the thread first: it may be replacing the stream */
1182 if (mac->thread)
1183 {
1184 (void)SetEvent(mac->stopEvent);
1185 (void)WaitForSingleObject(mac->thread, INFINITE);
1186 (void)CloseHandle(mac->thread);
1187 mac->thread = nullptr;
1188 }
1189
1190 mac_capture_stop(mac->capture);
1191 return 1;
1192}
1193
1194/* A connected client keeps the display awake: when it turns off there is
1195 * nothing left to capture. The first client also wakes it, if it slept. */
1196WINPR_ATTR_NODISCARD
1197static BOOL mac_shadow_client_connect(rdpShadowSubsystem* subsystem,
1198 WINPR_ATTR_UNUSED rdpShadowClient* client)
1199{
1200 macShadowSubsystem* mac = (macShadowSubsystem*)subsystem;
1201 WINPR_ASSERT(mac);
1202
1203 EnterCriticalSection(&mac->clientLock);
1204 if (mac->clients++ == 0)
1205 {
1206 IOPMAssertionID activity = kIOPMNullAssertionID;
1207 if (IOPMAssertionDeclareUserActivity(CFSTR("FreeRDP shadow server: a client connected"),
1208 kIOPMUserActiveRemote, &activity) == kIOReturnSuccess)
1209 (void)IOPMAssertionRelease(activity);
1210
1211 if (IOPMAssertionCreateWithName(kIOPMAssertionTypePreventUserIdleDisplaySleep,
1212 kIOPMAssertionLevelOn,
1213 CFSTR("FreeRDP shadow server: clients are connected"),
1214 &mac->awake) != kIOReturnSuccess)
1215 {
1216 WLog_WARN(TAG, "could not keep the display awake: when it turns off, the clients "
1217 "see the last picture");
1218 mac->awake = kIOPMNullAssertionID;
1219 }
1220 }
1221 LeaveCriticalSection(&mac->clientLock);
1222 return TRUE;
1223}
1224
1225static void mac_shadow_client_disconnect(rdpShadowSubsystem* subsystem,
1226 WINPR_ATTR_UNUSED rdpShadowClient* client)
1227{
1228 macShadowSubsystem* mac = (macShadowSubsystem*)subsystem;
1229 WINPR_ASSERT(mac);
1230
1231 EnterCriticalSection(&mac->clientLock);
1232 if ((mac->clients > 0) && (--mac->clients == 0) && (mac->awake != kIOPMNullAssertionID))
1233 {
1234 (void)IOPMAssertionRelease(mac->awake);
1235 mac->awake = kIOPMNullAssertionID;
1236 }
1237 LeaveCriticalSection(&mac->clientLock);
1238}
1239
1240WINPR_ATTR_NODISCARD
1241static int mac_shadow_subsystem_start(rdpShadowSubsystem* subsystem)
1242{
1243 macShadowSubsystem* mac = (macShadowSubsystem*)subsystem;
1244
1245 if (!mac)
1246 return -1;
1247
1248 (void)ResetEvent(mac->stopEvent);
1249 mac->thread = CreateThread(nullptr, 0, mac_shadow_subsystem_thread, mac, 0, nullptr);
1250 if (!mac->thread)
1251 {
1252 WLog_ERR(TAG, "Failed to create thread");
1253 return -1;
1254 }
1255
1256 if (!mac_capture_start(mac->capture))
1257 {
1258 (void)mac_shadow_subsystem_stop(subsystem);
1259 return -1;
1260 }
1261
1262 return 1;
1263}
1264
1265static void mac_shadow_subsystem_free(rdpShadowSubsystem* subsystem)
1266{
1267 macShadowSubsystem* mac = (macShadowSubsystem*)subsystem;
1268
1269 if (!mac)
1270 return;
1271
1272 (void)mac_shadow_subsystem_uninit(subsystem);
1273 if (mac->awake != kIOPMNullAssertionID)
1274 (void)IOPMAssertionRelease(mac->awake);
1275 region16_uninit(&mac->pending);
1276 DeleteCriticalSection(&mac->clientLock);
1277 DeleteCriticalSection(&mac->frameLock);
1278 if (mac->frameEvent)
1279 (void)CloseHandle(mac->frameEvent);
1280 if (mac->stopEvent)
1281 (void)CloseHandle(mac->stopEvent);
1282 if (mac->lostEvent)
1283 (void)CloseHandle(mac->lostEvent);
1284 free(mac);
1285}
1286
1287WINPR_ATTR_NODISCARD
1288static rdpShadowSubsystem* mac_shadow_subsystem_new(void)
1289{
1290 macShadowSubsystem* mac = calloc(1, sizeof(macShadowSubsystem));
1291
1292 if (!mac)
1293 return nullptr;
1294
1295 region16_init(&mac->pending);
1296 if (!InitializeCriticalSectionAndSpinCount(&mac->frameLock, 4000))
1297 {
1298 free(mac);
1299 return nullptr;
1300 }
1301
1302 if (!InitializeCriticalSectionAndSpinCount(&mac->clientLock, 4000))
1303 {
1304 DeleteCriticalSection(&mac->frameLock);
1305 free(mac);
1306 return nullptr;
1307 }
1308
1309 mac->frameEvent = CreateEvent(nullptr, TRUE, FALSE, nullptr);
1310 mac->stopEvent = CreateEvent(nullptr, TRUE, FALSE, nullptr);
1311 mac->lostEvent = CreateEvent(nullptr, TRUE, FALSE, nullptr);
1312 if (!mac->frameEvent || !mac->stopEvent || !mac->lostEvent)
1313 {
1314 mac_shadow_subsystem_free(&mac->common);
1315 return nullptr;
1316 }
1317
1318 mac->common.SynchronizeEvent = mac_shadow_input_synchronize_event;
1319 mac->common.KeyboardEvent = mac_shadow_input_keyboard_event;
1320 mac->common.UnicodeKeyboardEvent = mac_shadow_input_unicode_keyboard_event;
1321 mac->common.MouseEvent = mac_shadow_input_mouse_event;
1322 mac->common.ExtendedMouseEvent = mac_shadow_input_extended_mouse_event;
1323 mac->common.ClientConnect = mac_shadow_client_connect;
1324 mac->common.ClientDisconnect = mac_shadow_client_disconnect;
1325#ifdef WITH_PAM
1326 mac->common.Authenticate = mac_shadow_pam_authenticate;
1327#endif
1328 return &mac->common;
1329}
1330
1331FREERDP_API const char* ShadowSubsystemName(void)
1332{
1333 return "Mac";
1334}
1335
1336FREERDP_API int ShadowSubsystemEntry(RDP_SHADOW_ENTRY_POINTS* pEntryPoints)
1337{
1338 pEntryPoints->New = mac_shadow_subsystem_new;
1339 pEntryPoints->Free = mac_shadow_subsystem_free;
1340 pEntryPoints->Init = mac_shadow_subsystem_init;
1341 pEntryPoints->Uninit = mac_shadow_subsystem_uninit;
1342 pEntryPoints->Start = mac_shadow_subsystem_start;
1343 pEntryPoints->Stop = mac_shadow_subsystem_stop;
1344 pEntryPoints->EnumMonitors = mac_shadow_enum_monitors;
1345 return 1;
1346}