24#include <winpr/assert.h>
25#include <winpr/cast.h>
27#include <winpr/synch.h>
28#include <winpr/input.h>
29#include <winpr/sysinfo.h>
31#include <freerdp/server/server-common.h>
32#include <freerdp/codec/audio.h>
33#include <freerdp/codec/color.h>
34#include <freerdp/codec/dsp.h>
35#include <freerdp/codec/region.h>
36#include <freerdp/log.h>
38#include <ApplicationServices/ApplicationServices.h>
41#include <security/pam_appl.h>
44#include "mac_shadow.h"
46#define TAG SERVER_TAG("shadow.mac")
49#define MAC_SHADOW_FPS 30
51#define MAC_SHADOW_RESTART_MS 1000
55#define MAC_SHADOW_DISPLAYS_MS 1000
59#define MAC_SHADOW_CURSOR_MS 30
60#define MAC_SHADOW_CURSOR_QUIET_MS 500
62#define MAC_SHADOW_CURSOR_MAX 96
64#define MAC_SHADOW_AUDIO_LOG_MS 5000
67#define MAC_SHADOW_CLICK_MS 500
68#define MAC_SHADOW_CLICK_DISTANCE 4.0
78#define MAC_AUDIO_RATE 48000
79#define MAC_AUDIO_CHANNELS 2
80#define MAC_AUDIO_FRAME_BYTES (MAC_AUDIO_CHANNELS * 2)
82#define MAC_AUDIO_MESSAGE_FRAMES (MAC_AUDIO_RATE / 50)
85 { WAVE_FORMAT_OPUS, MAC_AUDIO_CHANNELS, MAC_AUDIO_RATE, 12000, 1, 16, 0,
nullptr },
86 { WAVE_FORMAT_PCM, MAC_AUDIO_CHANNELS, MAC_AUDIO_RATE, (MAC_AUDIO_RATE * MAC_AUDIO_FRAME_BYTES),
87 MAC_AUDIO_FRAME_BYTES, 16, 0,
nullptr },
92static AUDIO_FORMAT mac_audio_source_format = { WAVE_FORMAT_PCM,
95 (MAC_AUDIO_RATE * MAC_AUDIO_FRAME_BYTES),
96 MAC_AUDIO_FRAME_BYTES,
104 MAC_VK_COMMAND = 0x37,
106 MAC_VK_CAPSLOCK = 0x39,
107 MAC_VK_OPTION = 0x3A,
108 MAC_VK_CONTROL = 0x3B,
109 MAC_VK_RIGHT_COMMAND = 0x36,
110 MAC_VK_RIGHT_SHIFT = 0x3C,
111 MAC_VK_RIGHT_OPTION = 0x3D,
112 MAC_VK_RIGHT_CONTROL = 0x3E,
113 MAC_VK_FUNCTION = 0x3F
116static CGEventFlags mac_shadow_modifier_flag(CGKeyCode keycode)
121 case MAC_VK_RIGHT_SHIFT:
122 return kCGEventFlagMaskShift;
124 case MAC_VK_RIGHT_CONTROL:
125 return kCGEventFlagMaskControl;
127 case MAC_VK_RIGHT_OPTION:
128 return kCGEventFlagMaskAlternate;
130 case MAC_VK_RIGHT_COMMAND:
131 return kCGEventFlagMaskCommand;
132 case MAC_VK_FUNCTION:
133 return kCGEventFlagMaskSecondaryFn;
142static UINT32 mac_shadow_list_displays(
MONITOR_DEF* monitors, CGDirectDisplayID* ids,
143 CGRect* bounds, UINT32 maxMonitors)
145 CGDirectDisplayID active[MAC_SHADOW_MAX_DISPLAYS] = WINPR_C_ARRAY_INIT;
148 if (CGGetActiveDisplayList(ARRAYSIZE(active), active, &count) != kCGErrorSuccess)
151 count = MIN(count, maxMonitors);
155 for (uint32_t x = 0; x < count; x++)
157 const CGRect rect = CGDisplayBounds(active[x]);
158 minX = MIN(minX, rect.origin.x);
159 minY = MIN(minY, rect.origin.y);
162 const CGDirectDisplayID mainDisplay = CGMainDisplayID();
163 for (uint32_t x = 0; x < count; x++)
165 const CGRect rect = CGDisplayBounds(active[x]);
167 monitor->left = (INT32)(rect.origin.x - minX);
168 monitor->top = (INT32)(rect.origin.y - minY);
169 monitor->right = monitor->left + (INT32)rect.size.width - 1;
170 monitor->bottom = monitor->top + (INT32)rect.size.height - 1;
171 monitor->flags = (active[x] == mainDisplay) ? 1 : 0;
182static UINT32 mac_shadow_enum_monitors(
MONITOR_DEF* monitors, UINT32 maxMonitors)
184 return mac_shadow_list_displays(monitors,
nullptr,
nullptr, maxMonitors);
187static BOOL mac_shadow_post(CGEventRef event)
192 CGEventPost(kCGHIDEventTap, event);
198static BOOL mac_shadow_input_synchronize_event(rdpShadowSubsystem* subsystem,
199 rdpShadowClient* client, UINT32 flags)
201 macShadowSubsystem* mac = (macShadowSubsystem*)subsystem;
203 if (!subsystem || !client)
212 if (flags & KBD_SYNC_CAPS_LOCK)
213 mac->modifiers |= kCGEventFlagMaskAlphaShift;
215 mac->modifiers &= ~kCGEventFlagMaskAlphaShift;
217 WLog_DBG(TAG,
"synchronize 0x%08" PRIx32
": caps lock %s", flags,
218 (flags & KBD_SYNC_CAPS_LOCK) ?
"on" :
"off");
223static BOOL mac_shadow_input_keyboard_event(rdpShadowSubsystem* subsystem, rdpShadowClient* client,
224 UINT16 flags, UINT8 code)
226 macShadowSubsystem* mac = (macShadowSubsystem*)subsystem;
228 if (!subsystem || !client)
231 DWORD scancode = code;
232 if (flags & KBD_FLAGS_EXTENDED)
238 DWORD vkcode = GetVirtualKeyCodeFromVirtualScanCode(scancode, WINPR_KBD_TYPE_IBM_ENHANCED);
239 if (flags & KBD_FLAGS_EXTENDED)
241 const DWORD keycode = GetKeycodeFromVirtualKeyCode(vkcode, WINPR_KEYCODE_TYPE_APPLE);
244 if ((keycode == 0) && (vkcode != VK_KEY_A))
246 WLog_DBG(TAG,
"no key for scancode 0x%04" PRIx32, scancode);
252 const BOOL down = (flags & KBD_FLAGS_RELEASE) ? FALSE : TRUE;
254 if ((keycode == MAC_VK_CAPSLOCK) && !down)
257 CGEventRef
event = CGEventCreateKeyboardEvent(
nullptr, (CGKeyCode)keycode, down);
264 const CGEventFlags modifier = mac_shadow_modifier_flag((CGKeyCode)keycode);
268 mac->modifiers |= modifier;
270 mac->modifiers &= ~modifier;
271 CGEventSetType(event, kCGEventFlagsChanged);
273 else if (keycode == MAC_VK_CAPSLOCK)
275 mac->modifiers ^= kCGEventFlagMaskAlphaShift;
276 CGEventSetType(event, kCGEventFlagsChanged);
279 CGEventSetFlags(event, mac->modifiers);
280 return mac_shadow_post(event);
284static BOOL mac_shadow_input_unicode_keyboard_event(rdpShadowSubsystem* subsystem,
285 rdpShadowClient* client, UINT16 flags,
288 if (!subsystem || !client)
292 const BOOL down = (flags & KBD_FLAGS_RELEASE) ? FALSE : TRUE;
293 CGEventRef
event = CGEventCreateKeyboardEvent(
nullptr, 0, down);
297 const UniChar character = code;
298 CGEventKeyboardSetUnicodeString(event, 1, &character);
299 return mac_shadow_post(event);
302static CGPoint mac_shadow_point(
const macShadowSubsystem* mac, UINT16 x, UINT16 y)
304 const CGFloat scaleX = mac->bounds.size.width / (CGFloat)mac->width;
305 const CGFloat scaleY = mac->bounds.size.height / (CGFloat)mac->height;
307 return CGPointMake(mac->bounds.origin.x + (CGFloat)x * scaleX,
308 mac->bounds.origin.y + (CGFloat)y * scaleY);
311static BOOL mac_shadow_mouse_event(macShadowSubsystem* mac, CGEventType type, CGPoint point,
312 CGMouseButton button, int64_t clicks)
314 CGEventRef
event = CGEventCreateMouseEvent(
nullptr, type, point, button);
319 CGEventSetIntegerValueField(event, kCGMouseEventClickState, clicks);
320 CGEventSetFlags(event, mac->modifiers);
321 return mac_shadow_post(event);
324static BOOL mac_shadow_mouse_move(macShadowSubsystem* mac, CGPoint point)
326 EnterCriticalSection(&mac->clientLock);
327 mac->pointerInput = GetTickCount64();
328 LeaveCriticalSection(&mac->clientLock);
331 if (mac->buttonsDown & (1u << kCGMouseButtonLeft))
332 return mac_shadow_mouse_event(mac, kCGEventLeftMouseDragged, point, kCGMouseButtonLeft, 0);
333 if (mac->buttonsDown & (1u << kCGMouseButtonRight))
334 return mac_shadow_mouse_event(mac, kCGEventRightMouseDragged, point, kCGMouseButtonRight,
336 for (UINT32 button = kCGMouseButtonCenter; button < 32; button++)
338 if (mac->buttonsDown & (1u << button))
339 return mac_shadow_mouse_event(mac, kCGEventOtherMouseDragged, point,
340 (CGMouseButton)button, 0);
342 return mac_shadow_mouse_event(mac, kCGEventMouseMoved, point, kCGMouseButtonLeft, 0);
345static BOOL mac_shadow_mouse_button(macShadowSubsystem* mac, CGMouseButton button, BOOL down,
348 CGEventType type = kCGEventNull;
352 case kCGMouseButtonLeft:
353 type = down ? kCGEventLeftMouseDown : kCGEventLeftMouseUp;
355 case kCGMouseButtonRight:
356 type = down ? kCGEventRightMouseDown : kCGEventRightMouseUp;
359 type = down ? kCGEventOtherMouseDown : kCGEventOtherMouseUp;
367 const UINT64 now = GetTickCount64();
368 if ((button == mac->lastClickButton) && (now - mac->lastClickTime <= MAC_SHADOW_CLICK_MS) &&
369 (fabs(point.x - mac->lastClickPoint.x) <= MAC_SHADOW_CLICK_DISTANCE) &&
370 (fabs(point.y - mac->lastClickPoint.y) <= MAC_SHADOW_CLICK_DISTANCE))
375 mac->lastClickTime = now;
376 mac->lastClickButton = button;
377 mac->lastClickPoint = point;
378 mac->buttonsDown |= (1u << button);
381 mac->buttonsDown &= ~(1u << button);
383 const int64_t clicks = (button == mac->lastClickButton) ? mac->clickCount : 1;
384 return mac_shadow_mouse_event(mac, type, point, button, clicks);
387static BOOL mac_shadow_mouse_wheel(macShadowSubsystem* mac, UINT16 flags, BOOL horizontal)
390 INT32 delta = flags & WheelRotationMask;
391 if (flags & PTR_FLAGS_WHEEL_NEGATIVE)
394 INT32* remainder = &mac->wheelRemainder[horizontal ? 1 : 0];
396 const INT32 lines = *remainder / 120;
399 *remainder -= lines * 120;
402 const int32_t vertical = horizontal ? 0 : lines;
403 const int32_t sideways = horizontal ? -lines : 0;
405 CGEventCreateScrollWheelEvent(
nullptr, kCGScrollEventUnitLine, 2, vertical, sideways);
409 CGEventSetFlags(event, mac->modifiers);
410 return mac_shadow_post(event);
414static BOOL mac_shadow_input_mouse_event(rdpShadowSubsystem* subsystem, rdpShadowClient* client,
415 UINT16 flags, UINT16 x, UINT16 y)
417 macShadowSubsystem* mac = (macShadowSubsystem*)subsystem;
419 if (!subsystem || !client)
422 if (flags & PTR_FLAGS_WHEEL)
423 return mac_shadow_mouse_wheel(mac, flags, FALSE);
424 if (flags & PTR_FLAGS_HWHEEL)
425 return mac_shadow_mouse_wheel(mac, flags, TRUE);
427 const CGPoint point = mac_shadow_point(mac, x, y);
429 if (flags & PTR_FLAGS_MOVE)
431 if (!mac_shadow_mouse_move(mac, point))
435 const BOOL down = (flags & PTR_FLAGS_DOWN) ? TRUE : FALSE;
436 if (flags & PTR_FLAGS_BUTTON1)
437 return mac_shadow_mouse_button(mac, kCGMouseButtonLeft, down, point);
438 if (flags & PTR_FLAGS_BUTTON2)
439 return mac_shadow_mouse_button(mac, kCGMouseButtonRight, down, point);
440 if (flags & PTR_FLAGS_BUTTON3)
441 return mac_shadow_mouse_button(mac, kCGMouseButtonCenter, down, point);
447static BOOL mac_shadow_input_extended_mouse_event(rdpShadowSubsystem* subsystem,
448 rdpShadowClient* client, UINT16 flags, UINT16 x,
451 macShadowSubsystem* mac = (macShadowSubsystem*)subsystem;
453 if (!subsystem || !client)
456 const CGPoint point = mac_shadow_point(mac, x, y);
458 if (flags & PTR_FLAGS_MOVE)
460 if (!mac_shadow_mouse_move(mac, point))
465 const BOOL down = (flags & PTR_XFLAGS_DOWN) ? TRUE : FALSE;
466 if (flags & PTR_XFLAGS_BUTTON1)
467 return mac_shadow_mouse_button(mac, (CGMouseButton)3, down, point);
468 if (flags & PTR_XFLAGS_BUTTON2)
469 return mac_shadow_mouse_button(mac, (CGMouseButton)4, down, point);
478 const char* password;
479} MAC_SHADOW_PAM_DATA;
481static int mac_shadow_pam_conv(
int num_msg,
const struct pam_message** msg,
482 struct pam_response** resp,
void* appdata_ptr)
484 const MAC_SHADOW_PAM_DATA* data = (
const MAC_SHADOW_PAM_DATA*)appdata_ptr;
486 if ((num_msg <= 0) || !msg || !resp || !data)
489 struct pam_response* response = calloc((
size_t)num_msg,
sizeof(
struct pam_response));
493 for (
int index = 0; index < num_msg; index++)
495 const char* answer =
nullptr;
496 switch (msg[index]->msg_style)
498 case PAM_PROMPT_ECHO_ON:
501 case PAM_PROMPT_ECHO_OFF:
502 answer = data->password;
508 response[index].resp = _strdup(answer);
509 if (!response[index].resp)
511 for (
int x = 0; x < index; x++)
512 free(response[x].resp);
526static int mac_shadow_pam_authenticate(WINPR_ATTR_UNUSED rdpShadowSubsystem* subsystem,
527 WINPR_ATTR_UNUSED rdpShadowClient* client,
const char* user,
528 WINPR_ATTR_UNUSED
const char* domain,
const char* password)
530 if (!user || !password)
533 const struct passwd* owner = getpwuid(getuid());
534 if (!owner || !owner->pw_name || (strcmp(owner->pw_name, user) != 0))
536 WLog_ERR(TAG,
"login as '%s' refused: this screen belongs to '%s'", user,
537 (owner && owner->pw_name) ? owner->pw_name :
"?");
541 MAC_SHADOW_PAM_DATA data = { .user = user, .password = password };
542 const struct pam_conv conv = { .conv = mac_shadow_pam_conv, .appdata_ptr = &data };
543 pam_handle_t* handle =
nullptr;
545 int status = pam_start(
"checkpw", user, &conv, &handle);
546 if (status != PAM_SUCCESS)
548 WLog_ERR(TAG,
"pam_start failure: %s", pam_strerror(handle, status));
553 status = pam_set_item(handle, PAM_AUTHTOK, password);
554 if (status == PAM_SUCCESS)
555 status = pam_authenticate(handle, 0);
556 if (status == PAM_SUCCESS)
557 status = pam_acct_mgmt(handle, 0);
559 if (status != PAM_SUCCESS)
560 WLog_ERR(TAG,
"login as '%s' failed: %s", user, pam_strerror(handle, status));
562 pam_end(handle, status);
563 return (status == PAM_SUCCESS) ? 1 : -1;
567static void mac_shadow_on_frame(
void* context, CVPixelBufferRef frame,
const CGRect* dirty,
570 macShadowSubsystem* mac = (macShadowSubsystem*)context;
573 const CGRect frameRect = CGRectMake(0, 0, (CGFloat)CVPixelBufferGetWidth(frame),
574 (CGFloat)CVPixelBufferGetHeight(frame));
576 EnterCriticalSection(&mac->frameLock);
580 CVPixelBufferRetain(frame);
582 CVPixelBufferRelease(mac->frame);
585 for (
size_t x = 0; x < count; x++)
587 const CGRect rect = CGRectIntegral(CGRectIntersection(dirty[x], frameRect));
588 if (CGRectIsEmpty(rect))
591 const RECTANGLE_16 invalid = { .left = (UINT16)rect.origin.x,
592 .top = (UINT16)rect.origin.y,
593 .right = (UINT16)(rect.origin.x + rect.size.width),
594 .bottom = (UINT16)(rect.origin.y + rect.size.height) };
595 (void)region16_union_rect(&mac->pending, &mac->pending, &invalid);
598 LeaveCriticalSection(&mac->frameLock);
599 (void)SetEvent(mac->frameEvent);
602static void mac_shadow_on_stopped(
void* context)
604 macShadowSubsystem* mac = (macShadowSubsystem*)context;
608 (void)SetEvent(mac->lostEvent);
611static void mac_shadow_audio_msg_free(WINPR_ATTR_UNUSED UINT32
id, SHADOW_MSG_OUT* msg)
620static void mac_shadow_audio_post(macShadowSubsystem* mac,
wStream* s)
622 const size_t size = Stream_GetPosition(s);
623 Stream_ResetPosition(s);
627 rdpShadowServer* server = mac->common.server;
628 if (!server || !server->clients || (ArrayList_Count(server->clients) == 0))
635 msg->buf = malloc(size);
642 memcpy(msg->buf, Stream_Buffer(s), size);
643 mac->audioMessages++;
644 msg->common.Free = mac_shadow_audio_msg_free;
645 msg->audio_format = &mac_audio_source_format;
646 msg->nFrames = size / MAC_AUDIO_FRAME_BYTES;
647 msg->wTimestamp = (UINT16)(GetTickCount64() & 0xFFFF);
649 (void)shadow_client_boardcast_msg(server,
nullptr, SHADOW_MSG_OUT_AUDIO_OUT_SAMPLES_ID,
650 &msg->common,
nullptr);
653static INT16 mac_shadow_audio_sample(
float value)
659 return (INT16)lrintf(value * (
float)INT16_MAX);
662static void mac_shadow_on_audio(
void* context,
const float* samples,
size_t frames, UINT32 channels,
665 macShadowSubsystem* mac = (macShadowSubsystem*)context;
668 if ((channels != MAC_AUDIO_CHANNELS) || (rate != MAC_AUDIO_RATE))
670 WLog_DBG(TAG,
"sound in %" PRIu32
" Hz, %" PRIu32
" channels, not what was asked: dropped",
675 const UINT64 now = GetTickCount64();
676 if (mac->audioLogged == 0)
678 WLog_INFO(TAG,
"sound: capturing %" PRIu32
" Hz, %" PRIu32
" channels", rate, channels);
679 mac->audioLogged = now;
681 else if (now - mac->audioLogged >= MAC_SHADOW_AUDIO_LOG_MS)
684 "sound: %" PRIu64
" frames captured, %" PRIu64
685 " messages sent, peak %.3f in the last %" PRIu64
" ms",
686 mac->audioFrames, mac->audioMessages, (
double)mac->audioPeak,
687 now - mac->audioLogged);
688 mac->audioFrames = 0;
689 mac->audioMessages = 0;
690 mac->audioPeak = 0.0f;
691 mac->audioLogged = now;
693 mac->audioFrames += frames;
696 for (
size_t f = 0; f < frames; f++)
698 for (UINT32 c = 0; c < channels; c++)
700 const float value = samples[f * channels + c];
701 mac->audioPeak = MAX(mac->audioPeak, fabsf(value));
702 Stream_Write_INT16(s, mac_shadow_audio_sample(value));
705 if (Stream_GetPosition(s) >= MAC_AUDIO_MESSAGE_FRAMES * MAC_AUDIO_FRAME_BYTES)
706 mac_shadow_audio_post(mac, s);
710static BOOL mac_shadow_audio_init(macShadowSubsystem* mac)
712 AUDIO_FORMAT* formats = audio_formats_new(ARRAYSIZE(mac_audio_offered_formats));
717 for (
size_t x = 0; x < ARRAYSIZE(mac_audio_offered_formats); x++)
719 const AUDIO_FORMAT* format = &mac_audio_offered_formats[x];
720 if (freerdp_dsp_supports_format(format, TRUE))
721 formats[count++] = *format;
724 mac->audioStream = Stream_New(
nullptr, MAC_AUDIO_MESSAGE_FRAMES * MAC_AUDIO_FRAME_BYTES);
725 if (!mac->audioStream || (count == 0))
727 Stream_Free(mac->audioStream, TRUE);
728 mac->audioStream =
nullptr;
729 audio_formats_free(formats, ARRAYSIZE(mac_audio_offered_formats));
733 mac->common.rdpsndFormats = formats;
734 mac->common.nRdpsndFormats = count;
738static void mac_shadow_audio_uninit(macShadowSubsystem* mac)
740 audio_formats_free(mac->common.rdpsndFormats, mac->common.nRdpsndFormats);
741 mac->common.rdpsndFormats =
nullptr;
742 mac->common.nRdpsndFormats = 0;
743 Stream_Free(mac->audioStream, TRUE);
744 mac->audioStream =
nullptr;
749static void mac_shadow_send_frame(macShadowSubsystem* mac)
751 rdpShadowServer* server = mac->common.server;
752 rdpShadowSurface* surface = server->surface;
754 region16_init(®ion);
756 EnterCriticalSection(&mac->frameLock);
757 CVPixelBufferRef frame = mac->frame;
759 CVPixelBufferRetain(frame);
760 (void)region16_copy(®ion, &mac->pending);
761 region16_clear(&mac->pending);
762 LeaveCriticalSection(&mac->frameLock);
767 if ((CVPixelBufferGetWidth(frame) != surface->width) ||
768 (CVPixelBufferGetHeight(frame) != surface->height))
771 if (!mac->sizeWarned)
772 WLog_DBG(TAG,
"frames of %zux%zu for a surface of %" PRIu32
"x%" PRIu32
": dropped",
773 CVPixelBufferGetWidth(frame), CVPixelBufferGetHeight(frame),
774 (UINT32)surface->width, (UINT32)surface->height);
775 mac->sizeWarned = TRUE;
780 const RECTANGLE_16 surfaceRect = { 0, 0, (UINT16)surface->width, (UINT16)surface->height };
781 (void)region16_intersect_rect(®ion, ®ion, &surfaceRect);
784 if (region16_is_empty(®ion))
787 if (CVPixelBufferLockBaseAddress(frame, kCVPixelBufferLock_ReadOnly) != kCVReturnSuccess)
791 const BYTE* data = CVPixelBufferGetBaseAddress(frame);
792 const size_t stride = CVPixelBufferGetBytesPerRow(frame);
794 const RECTANGLE_16* rects = region16_rects(®ion, &count);
796 EnterCriticalSection(&surface->lock);
797 for (UINT32 x = 0; x < count; x++)
800 (void)freerdp_image_copy_no_overlap(
801 surface->data, surface->format, surface->scanline, rect->left, rect->top,
802 rect->right - rect->left, rect->bottom - rect->top, data, PIXEL_FORMAT_BGRX32,
803 (UINT32)stride, rect->left, rect->top,
nullptr, FREERDP_FLIP_NONE);
804 (void)region16_union_rect(&surface->invalidRegion, &surface->invalidRegion, rect);
806 LeaveCriticalSection(&surface->lock);
809 (void)CVPixelBufferUnlockBaseAddress(frame, kCVPixelBufferLock_ReadOnly);
811 ArrayList_Lock(server->clients);
812 if (ArrayList_Count(server->clients) > 0)
813 shadow_subsystem_frame_update(&mac->common);
814 ArrayList_Unlock(server->clients);
816 EnterCriticalSection(&surface->lock);
817 region16_clear(&surface->invalidRegion);
818 LeaveCriticalSection(&surface->lock);
822 CVPixelBufferRelease(frame);
823 region16_uninit(®ion);
826static void mac_shadow_refresh(macShadowSubsystem* mac)
828 const RECTANGLE_16 all = { 0, 0, (UINT16)mac->width, (UINT16)mac->height };
830 EnterCriticalSection(&mac->frameLock);
831 (void)region16_union_rect(&mac->pending, &mac->pending, &all);
832 LeaveCriticalSection(&mac->frameLock);
834 EnterCriticalSection(&mac->clientLock);
835 mac->cursorResend = TRUE;
836 LeaveCriticalSection(&mac->clientLock);
838 (void)SetEvent(mac->frameEvent);
841static void mac_shadow_cursor_msg_free(UINT32
id, SHADOW_MSG_OUT* msg)
843 if (msg && (
id == SHADOW_MSG_OUT_POINTER_ALPHA_UPDATE_ID))
846 free(shape->xorMaskData);
847 free(shape->andMaskData);
855 UINT32 hash = 2166136261u;
856 const UINT32 header[] = { image->width, image->height, image->xHot, image->yHot };
857 const BYTE* parts[] = { (
const BYTE*)header, image->pixels };
858 const size_t sizes[] = {
sizeof(header), 4ull * image->width * image->height };
859 for (
size_t p = 0; p < ARRAYSIZE(parts); p++)
861 for (
size_t x = 0; x < sizes[p]; x++)
870static BOOL mac_shadow_cursor_send_shape(macShadowSubsystem* mac,
const macCursorImage* image)
877 msg->xHot = image->xHot;
878 msg->yHot = image->yHot;
879 msg->width = image->width;
880 msg->height = image->height;
881 if (shadow_subsystem_pointer_convert_alpha_pointer_data_to_format(
882 image->pixels, PIXEL_FORMAT_BGRA32, TRUE, image->width, image->height, msg) < 0)
888 msg->common.Free = mac_shadow_cursor_msg_free;
889 return shadow_client_boardcast_msg(mac->common.server,
nullptr,
890 SHADOW_MSG_OUT_POINTER_ALPHA_UPDATE_ID, &msg->common,
894static void mac_shadow_cursor_send_position(macShadowSubsystem* mac, UINT64 now)
896 EnterCriticalSection(&mac->clientLock);
897 const UINT64 input = mac->pointerInput;
898 LeaveCriticalSection(&mac->clientLock);
899 if (now - input < MAC_SHADOW_CURSOR_QUIET_MS)
902 CGEventRef
event = CGEventCreate(
nullptr);
905 const CGPoint where = CGEventGetLocation(event);
910 (where.x - mac->bounds.origin.x) * (CGFloat)mac->width / mac->bounds.size.width;
912 (where.y - mac->bounds.origin.y) * (CGFloat)mac->height / mac->bounds.size.height;
913 const UINT32 xPos = (UINT32)MIN(MAX(x, 0.0), (CGFloat)mac->width - 1.0);
914 const UINT32 yPos = (UINT32)MIN(MAX(y, 0.0), (CGFloat)mac->height - 1.0);
915 if ((xPos == mac->common.pointerX) && (yPos == mac->common.pointerY))
918 mac->common.pointerX = xPos;
919 mac->common.pointerY = yPos;
927 msg->common.Free = mac_shadow_cursor_msg_free;
928 (void)shadow_client_boardcast_msg(mac->common.server,
nullptr,
929 SHADOW_MSG_OUT_POINTER_POSITION_UPDATE_ID, &msg->common,
935static void mac_shadow_cursor_update(macShadowSubsystem* mac, UINT64 now)
937 if (now < mac->cursorNext)
939 mac->cursorNext = now + MAC_SHADOW_CURSOR_MS;
942 if (mac_cursor_get(&image, MAC_SHADOW_CURSOR_MAX))
944 EnterCriticalSection(&mac->clientLock);
945 const BOOL resend = mac->cursorResend;
946 mac->cursorResend = FALSE;
947 LeaveCriticalSection(&mac->clientLock);
949 const UINT32 hash = mac_shadow_cursor_hash(&image);
950 if ((resend || !mac->cursorKnown || (hash != mac->cursorHash)) &&
951 mac_shadow_cursor_send_shape(mac, &image))
953 mac->cursorHash = hash;
954 mac->cursorKnown = TRUE;
956 mac_cursor_image_free(&image);
959 mac_shadow_cursor_send_position(mac, now);
962static BOOL mac_shadow_has_clients(macShadowSubsystem* mac)
964 EnterCriticalSection(&mac->clientLock);
965 const BOOL clients = mac->clients > 0;
966 LeaveCriticalSection(&mac->clientLock);
970static void mac_shadow_subsystem_process_message(macShadowSubsystem* mac, wMessage* message)
974 case SHADOW_MSG_IN_REFRESH_REQUEST_ID:
975 mac_shadow_refresh(mac);
979 WLog_ERR(TAG,
"Unknown message id: %" PRIu32
"", message->id);
984 message->Free(message);
989static BOOL mac_shadow_capture_restart(macShadowSubsystem* mac)
992 if (!CGDisplayIsActive(mac->display) || CGDisplayIsAsleep(mac->display))
995 mac_capture_free(mac->capture);
996 mac->capture = mac_capture_new(&mac->config);
1000 if (!mac_capture_start(mac->capture))
1002 mac_capture_free(mac->capture);
1003 mac->capture =
nullptr;
1008 mac_shadow_refresh(mac);
1013static void mac_shadow_set_virtual_screen(rdpShadowSubsystem* subsystem, UINT32 count)
1015 WINPR_ASSERT(subsystem);
1017 MONITOR_DEF* virtualScreen = &subsystem->virtualScreen;
1018 virtualScreen->left = 0;
1019 virtualScreen->top = 0;
1020 virtualScreen->right = 0;
1021 virtualScreen->bottom = 0;
1022 for (UINT32 x = 0; x < count; x++)
1024 virtualScreen->right = MAX(virtualScreen->right, subsystem->monitors[x].right);
1025 virtualScreen->bottom = MAX(virtualScreen->bottom, subsystem->monitors[x].bottom);
1027 virtualScreen->flags = 1;
1033static void mac_shadow_displays_changed(macShadowSubsystem* mac, BOOL* lost)
1038 rdpShadowSubsystem* subsystem = &mac->common;
1039 MONITOR_DEF monitors[MAC_SHADOW_MAX_DISPLAYS] = WINPR_C_ARRAY_INIT;
1040 CGDirectDisplayID ids[MAC_SHADOW_MAX_DISPLAYS] = WINPR_C_ARRAY_INIT;
1041 CGRect bounds[MAC_SHADOW_MAX_DISPLAYS] = WINPR_C_ARRAY_INIT;
1043 const UINT32 count = mac_shadow_list_displays(monitors, ids, bounds, MAC_SHADOW_MAX_DISPLAYS);
1047 UINT32 selected = 0;
1048 for (UINT32 x = 0; x < count; x++)
1050 if (ids[x] == mac->display)
1054 const UINT32 width = WINPR_ASSERTING_INT_CAST(UINT32, monitor->right - monitor->left + 1);
1055 const UINT32 height = WINPR_ASSERTING_INT_CAST(UINT32, monitor->bottom - monitor->top + 1);
1058 "the displays changed: sharing display %" PRIu32
" (%" PRIu32
" of %" PRIu32
1059 "), %" PRIu32
"x%" PRIu32
" (was %" PRIu32
"x%" PRIu32
")",
1060 ids[selected], selected, count, width, height, mac->width, mac->height);
1062 mac_capture_stop(mac->capture);
1063 mac_capture_free(mac->capture);
1064 mac->capture =
nullptr;
1066 memcpy(subsystem->monitors, monitors,
sizeof(
MONITOR_DEF) * count);
1067 subsystem->numMonitors = count;
1068 subsystem->selectedMonitor = selected;
1069 mac_shadow_set_virtual_screen(subsystem, count);
1070 mac->display = ids[selected];
1071 mac->bounds = bounds[selected];
1073 mac->height = height;
1074 mac->config.display = mac->display;
1075 mac->config.width = width;
1076 mac->config.height = height;
1079 EnterCriticalSection(&mac->frameLock);
1081 CVPixelBufferRelease(mac->frame);
1082 mac->frame =
nullptr;
1083 region16_clear(&mac->pending);
1084 LeaveCriticalSection(&mac->frameLock);
1085 mac->sizeWarned = FALSE;
1087 if (!shadow_screen_resize(subsystem->server->screen))
1088 WLog_ERR(TAG,
"the surface could not take the new size");
1091 *lost = !mac_shadow_capture_restart(mac);
1094static void mac_shadow_check_displays(macShadowSubsystem* mac, UINT64 now, BOOL* lost)
1099 if (now < mac->displaysChecked + MAC_SHADOW_DISPLAYS_MS)
1101 mac->displaysChecked = now;
1104 const CGRect bounds = CGDisplayBounds(mac->display);
1105 if ((CGGetActiveDisplayList(0,
nullptr, &count) != kCGErrorSuccess) ||
1106 ((count == mac->common.numMonitors) && CGRectEqualToRect(bounds, mac->bounds)))
1109 mac_shadow_displays_changed(mac, lost);
1112static DWORD WINAPI mac_shadow_subsystem_thread(LPVOID arg)
1114 macShadowSubsystem* mac = (macShadowSubsystem*)arg;
1116 HANDLE events[] = { mac->stopEvent, MessageQueue_Event(MsgPipe->In), mac->frameEvent,
1125 const BOOL clients = mac_shadow_has_clients(mac);
1126 DWORD timeout = clients ? MAC_SHADOW_CURSOR_MS : MAC_SHADOW_DISPLAYS_MS;
1128 timeout = MIN(timeout, MAC_SHADOW_RESTART_MS);
1130 const DWORD status = WaitForMultipleObjects(ARRAYSIZE(events), events, FALSE, timeout);
1131 if ((status == WAIT_FAILED) || (WaitForSingleObject(mac->stopEvent, 0) == WAIT_OBJECT_0))
1134 const UINT64 now = GetTickCount64();
1135 if (WaitForSingleObject(mac->lostEvent, 0) == WAIT_OBJECT_0)
1137 (void)ResetEvent(mac->lostEvent);
1139 WLog_WARN(TAG,
"the screen is no longer captured (the display is off?): the "
1140 "clients see the last picture until it is back");
1142 restart = now + MAC_SHADOW_RESTART_MS;
1144 else if (lost && (now >= restart))
1146 restart = now + MAC_SHADOW_RESTART_MS;
1147 if (mac_shadow_capture_restart(mac))
1149 WLog_INFO(TAG,
"the display is back: capturing the screen again");
1154 mac_shadow_check_displays(mac, now, &lost);
1156 mac_shadow_cursor_update(mac, now);
1158 wMessage message = WINPR_C_ARRAY_INIT;
1159 while (MessageQueue_Peek(MsgPipe->In, &message, TRUE))
1161 if (message.id == WMQ_QUIT)
1163 mac_shadow_subsystem_process_message(mac, &message);
1166 if (WaitForSingleObject(mac->frameEvent, 0) == WAIT_OBJECT_0)
1168 (void)ResetEvent(mac->frameEvent);
1169 mac_shadow_send_frame(mac);
1178static BOOL mac_shadow_check_input_access(
void)
1182 const void* keys[] = { kAXTrustedCheckOptionPrompt };
1183 const void* values[] = { kCFBooleanTrue };
1184 CFDictionaryRef options =
1185 CFDictionaryCreate(kCFAllocatorDefault, keys, values, ARRAYSIZE(keys),
1186 &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks);
1187 const Boolean trusted = AXIsProcessTrustedWithOptions(options);
1192 WLog_WARN(TAG,
"no permission to control the computer: the clients can only watch. "
1193 "Allow this program in System Settings > Privacy & Security > "
1194 "Accessibility, then start it again");
1195 return trusted ? TRUE : FALSE;
1199static int mac_shadow_subsystem_init(rdpShadowSubsystem* subsystem)
1201 macShadowSubsystem* mac = (macShadowSubsystem*)subsystem;
1202 CGDirectDisplayID ids[MAC_SHADOW_MAX_DISPLAYS] = WINPR_C_ARRAY_INIT;
1203 CGRect bounds[MAC_SHADOW_MAX_DISPLAYS] = WINPR_C_ARRAY_INIT;
1205 const UINT32 count = mac_shadow_list_displays(subsystem->monitors, ids, bounds, ARRAYSIZE(ids));
1208 WLog_ERR(TAG,
"no display found");
1212 subsystem->numMonitors = count;
1213 if (subsystem->selectedMonitor >= count)
1214 subsystem->selectedMonitor = 0;
1216 const MONITOR_DEF* monitor = &subsystem->monitors[subsystem->selectedMonitor];
1217 mac->display = ids[subsystem->selectedMonitor];
1218 mac->bounds = bounds[subsystem->selectedMonitor];
1221 mac->width = (UINT32)(monitor->right - monitor->left + 1);
1222 mac->height = (UINT32)(monitor->bottom - monitor->top + 1);
1224 mac_shadow_set_virtual_screen(subsystem, count);
1230 if (subsystem->server)
1231 subsystem->server->ShowMouseCursor = TRUE;
1234 "sharing display %" PRIu32
" (%" PRIu32
" of %" PRIu32
"): %" PRIu32
"x%" PRIu32
1236 mac->display, subsystem->selectedMonitor, count, mac->width, mac->height,
1237 mac->bounds.origin.x, mac->bounds.origin.y);
1239 if (!mac_capture_check_access())
1242 (void)mac_shadow_check_input_access();
1244 const BOOL audio = mac_shadow_audio_init(mac);
1246 WLog_WARN(TAG,
"sound: no format to offer: no sound");
1249 .width = mac->width,
1250 .height = mac->height,
1251 .fps = MAC_SHADOW_FPS,
1253 .showCursor = FALSE,
1255 .audioRate = MAC_AUDIO_RATE,
1256 .audioChannels = MAC_AUDIO_CHANNELS,
1257 .onFrame = mac_shadow_on_frame,
1258 .onAudio = audio ? mac_shadow_on_audio :
nullptr,
1259 .onStopped = mac_shadow_on_stopped,
1261 mac->config = config;
1263 mac->capture = mac_capture_new(&mac->config);
1270static int mac_shadow_subsystem_uninit(rdpShadowSubsystem* subsystem)
1272 macShadowSubsystem* mac = (macShadowSubsystem*)subsystem;
1276 mac_capture_free(mac->capture);
1277 mac->capture =
nullptr;
1279 EnterCriticalSection(&mac->frameLock);
1281 CVPixelBufferRelease(mac->frame);
1282 mac->frame =
nullptr;
1283 region16_clear(&mac->pending);
1284 LeaveCriticalSection(&mac->frameLock);
1286 mac_shadow_audio_uninit(mac);
1290static int mac_shadow_subsystem_stop(rdpShadowSubsystem* subsystem)
1292 macShadowSubsystem* mac = (macShadowSubsystem*)subsystem;
1300 (void)SetEvent(mac->stopEvent);
1301 (void)WaitForSingleObject(mac->thread, INFINITE);
1302 (void)CloseHandle(mac->thread);
1303 mac->thread =
nullptr;
1306 mac_capture_stop(mac->capture);
1313static BOOL mac_shadow_client_connect(rdpShadowSubsystem* subsystem,
1314 WINPR_ATTR_UNUSED rdpShadowClient* client)
1316 macShadowSubsystem* mac = (macShadowSubsystem*)subsystem;
1319 EnterCriticalSection(&mac->clientLock);
1320 if (mac->clients++ == 0)
1322 IOPMAssertionID activity = kIOPMNullAssertionID;
1323 if (IOPMAssertionDeclareUserActivity(CFSTR(
"FreeRDP shadow server: a client connected"),
1324 kIOPMUserActiveRemote, &activity) == kIOReturnSuccess)
1325 (void)IOPMAssertionRelease(activity);
1327 if (IOPMAssertionCreateWithName(kIOPMAssertionTypePreventUserIdleDisplaySleep,
1328 kIOPMAssertionLevelOn,
1329 CFSTR(
"FreeRDP shadow server: clients are connected"),
1330 &mac->awake) != kIOReturnSuccess)
1332 WLog_WARN(TAG,
"could not keep the display awake: when it turns off, the clients "
1333 "see the last picture");
1334 mac->awake = kIOPMNullAssertionID;
1337 LeaveCriticalSection(&mac->clientLock);
1341static void mac_shadow_client_disconnect(rdpShadowSubsystem* subsystem,
1342 WINPR_ATTR_UNUSED rdpShadowClient* client)
1344 macShadowSubsystem* mac = (macShadowSubsystem*)subsystem;
1347 EnterCriticalSection(&mac->clientLock);
1348 if ((mac->clients > 0) && (--mac->clients == 0) && (mac->awake != kIOPMNullAssertionID))
1350 (void)IOPMAssertionRelease(mac->awake);
1351 mac->awake = kIOPMNullAssertionID;
1353 LeaveCriticalSection(&mac->clientLock);
1357static int mac_shadow_subsystem_start(rdpShadowSubsystem* subsystem)
1359 macShadowSubsystem* mac = (macShadowSubsystem*)subsystem;
1364 (void)ResetEvent(mac->stopEvent);
1365 mac->thread = CreateThread(
nullptr, 0, mac_shadow_subsystem_thread, mac, 0,
nullptr);
1368 WLog_ERR(TAG,
"Failed to create thread");
1372 if (!mac_capture_start(mac->capture))
1374 (void)mac_shadow_subsystem_stop(subsystem);
1381static void mac_shadow_subsystem_free(rdpShadowSubsystem* subsystem)
1383 macShadowSubsystem* mac = (macShadowSubsystem*)subsystem;
1388 (void)mac_shadow_subsystem_uninit(subsystem);
1389 if (mac->awake != kIOPMNullAssertionID)
1390 (void)IOPMAssertionRelease(mac->awake);
1391 region16_uninit(&mac->pending);
1392 DeleteCriticalSection(&mac->clientLock);
1393 DeleteCriticalSection(&mac->frameLock);
1394 if (mac->frameEvent)
1395 (void)CloseHandle(mac->frameEvent);
1397 (void)CloseHandle(mac->stopEvent);
1399 (void)CloseHandle(mac->lostEvent);
1404static rdpShadowSubsystem* mac_shadow_subsystem_new(
void)
1406 macShadowSubsystem* mac = calloc(1,
sizeof(macShadowSubsystem));
1411 region16_init(&mac->pending);
1412 if (!InitializeCriticalSectionAndSpinCount(&mac->frameLock, 4000))
1418 if (!InitializeCriticalSectionAndSpinCount(&mac->clientLock, 4000))
1420 DeleteCriticalSection(&mac->frameLock);
1425 mac->frameEvent = CreateEvent(
nullptr, TRUE, FALSE,
nullptr);
1426 mac->stopEvent = CreateEvent(
nullptr, TRUE, FALSE,
nullptr);
1427 mac->lostEvent = CreateEvent(
nullptr, TRUE, FALSE,
nullptr);
1428 if (!mac->frameEvent || !mac->stopEvent || !mac->lostEvent)
1430 mac_shadow_subsystem_free(&mac->common);
1434 mac->common.SynchronizeEvent = mac_shadow_input_synchronize_event;
1435 mac->common.KeyboardEvent = mac_shadow_input_keyboard_event;
1436 mac->common.UnicodeKeyboardEvent = mac_shadow_input_unicode_keyboard_event;
1437 mac->common.MouseEvent = mac_shadow_input_mouse_event;
1438 mac->common.ExtendedMouseEvent = mac_shadow_input_extended_mouse_event;
1439 mac->common.ClientConnect = mac_shadow_client_connect;
1440 mac->common.ClientDisconnect = mac_shadow_client_disconnect;
1442 mac->common.Authenticate = mac_shadow_pam_authenticate;
1444 return &mac->common;
1447FREERDP_API
const char* ShadowSubsystemName(
void)
1452FREERDP_API
int ShadowSubsystemEntry(RDP_SHADOW_ENTRY_POINTS* pEntryPoints)
1454 pEntryPoints->New = mac_shadow_subsystem_new;
1455 pEntryPoints->Free = mac_shadow_subsystem_free;
1456 pEntryPoints->Init = mac_shadow_subsystem_init;
1457 pEntryPoints->Uninit = mac_shadow_subsystem_uninit;
1458 pEntryPoints->Start = mac_shadow_subsystem_start;
1459 pEntryPoints->Stop = mac_shadow_subsystem_stop;
1460 pEntryPoints->EnumMonitors = mac_shadow_enum_monitors;