25#include <winpr/synch.h>
26#include <winpr/input.h>
27#include <winpr/sysinfo.h>
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>
36#include <ApplicationServices/ApplicationServices.h>
39#include <security/pam_appl.h>
42#include "mac_shadow.h"
44#define TAG SERVER_TAG("shadow.mac")
47#define MAC_SHADOW_FPS 30
49#define MAC_SHADOW_RESTART_MS 1000
53#define MAC_SHADOW_CURSOR_MS 30
54#define MAC_SHADOW_CURSOR_QUIET_MS 500
56#define MAC_SHADOW_CURSOR_MAX 96
58#define MAC_SHADOW_AUDIO_LOG_MS 5000
61#define MAC_SHADOW_CLICK_MS 500
62#define MAC_SHADOW_CLICK_DISTANCE 4.0
72#define MAC_AUDIO_RATE 48000
73#define MAC_AUDIO_CHANNELS 2
74#define MAC_AUDIO_FRAME_BYTES (MAC_AUDIO_CHANNELS * 2)
76#define MAC_AUDIO_MESSAGE_FRAMES (MAC_AUDIO_RATE / 50)
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 },
86static AUDIO_FORMAT mac_audio_source_format = { WAVE_FORMAT_PCM,
89 (MAC_AUDIO_RATE * MAC_AUDIO_FRAME_BYTES),
90 MAC_AUDIO_FRAME_BYTES,
98 MAC_VK_COMMAND = 0x37,
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
110static CGEventFlags mac_shadow_modifier_flag(CGKeyCode keycode)
115 case MAC_VK_RIGHT_SHIFT:
116 return kCGEventFlagMaskShift;
118 case MAC_VK_RIGHT_CONTROL:
119 return kCGEventFlagMaskControl;
121 case MAC_VK_RIGHT_OPTION:
122 return kCGEventFlagMaskAlternate;
124 case MAC_VK_RIGHT_COMMAND:
125 return kCGEventFlagMaskCommand;
126 case MAC_VK_FUNCTION:
127 return kCGEventFlagMaskSecondaryFn;
136static UINT32 mac_shadow_list_displays(
MONITOR_DEF* monitors, CGDirectDisplayID* ids,
137 CGRect* bounds, UINT32 maxMonitors)
139 CGDirectDisplayID active[MAC_SHADOW_MAX_DISPLAYS] = WINPR_C_ARRAY_INIT;
142 if (CGGetActiveDisplayList(ARRAYSIZE(active), active, &count) != kCGErrorSuccess)
145 count = MIN(count, maxMonitors);
149 for (uint32_t x = 0; x < count; x++)
151 const CGRect rect = CGDisplayBounds(active[x]);
152 minX = MIN(minX, rect.origin.x);
153 minY = MIN(minY, rect.origin.y);
156 const CGDirectDisplayID mainDisplay = CGMainDisplayID();
157 for (uint32_t x = 0; x < count; x++)
159 const CGRect rect = CGDisplayBounds(active[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;
176static UINT32 mac_shadow_enum_monitors(
MONITOR_DEF* monitors, UINT32 maxMonitors)
178 return mac_shadow_list_displays(monitors,
nullptr,
nullptr, maxMonitors);
181static BOOL mac_shadow_post(CGEventRef event)
186 CGEventPost(kCGHIDEventTap, event);
192static BOOL mac_shadow_input_synchronize_event(rdpShadowSubsystem* subsystem,
193 rdpShadowClient* client,
194 WINPR_ATTR_UNUSED UINT32 flags)
196 if (!subsystem || !client)
203static BOOL mac_shadow_input_keyboard_event(rdpShadowSubsystem* subsystem, rdpShadowClient* client,
204 UINT16 flags, UINT8 code)
206 macShadowSubsystem* mac = (macShadowSubsystem*)subsystem;
208 if (!subsystem || !client)
211 DWORD scancode = code;
212 if (flags & KBD_FLAGS_EXTENDED)
218 DWORD vkcode = GetVirtualKeyCodeFromVirtualScanCode(scancode, WINPR_KBD_TYPE_IBM_ENHANCED);
219 if (flags & KBD_FLAGS_EXTENDED)
221 const DWORD keycode = GetKeycodeFromVirtualKeyCode(vkcode, WINPR_KEYCODE_TYPE_APPLE);
224 if ((keycode == 0) && (vkcode != VK_KEY_A))
226 WLog_DBG(TAG,
"no key for scancode 0x%04" PRIx32, scancode);
232 const BOOL down = (flags & KBD_FLAGS_RELEASE) ? FALSE : TRUE;
234 if ((keycode == MAC_VK_CAPSLOCK) && !down)
237 CGEventRef
event = CGEventCreateKeyboardEvent(
nullptr, (CGKeyCode)keycode, down);
244 const CGEventFlags modifier = mac_shadow_modifier_flag((CGKeyCode)keycode);
248 mac->modifiers |= modifier;
250 mac->modifiers &= ~modifier;
251 CGEventSetType(event, kCGEventFlagsChanged);
253 else if (keycode == MAC_VK_CAPSLOCK)
255 mac->modifiers ^= kCGEventFlagMaskAlphaShift;
256 CGEventSetType(event, kCGEventFlagsChanged);
259 CGEventSetFlags(event, mac->modifiers);
260 return mac_shadow_post(event);
264static BOOL mac_shadow_input_unicode_keyboard_event(rdpShadowSubsystem* subsystem,
265 rdpShadowClient* client, UINT16 flags,
268 if (!subsystem || !client)
272 const BOOL down = (flags & KBD_FLAGS_RELEASE) ? FALSE : TRUE;
273 CGEventRef
event = CGEventCreateKeyboardEvent(
nullptr, 0, down);
277 const UniChar character = code;
278 CGEventKeyboardSetUnicodeString(event, 1, &character);
279 return mac_shadow_post(event);
282static CGPoint mac_shadow_point(
const macShadowSubsystem* mac, UINT16 x, UINT16 y)
284 const CGFloat scaleX = mac->bounds.size.width / (CGFloat)mac->width;
285 const CGFloat scaleY = mac->bounds.size.height / (CGFloat)mac->height;
287 return CGPointMake(mac->bounds.origin.x + (CGFloat)x * scaleX,
288 mac->bounds.origin.y + (CGFloat)y * scaleY);
291static BOOL mac_shadow_mouse_event(macShadowSubsystem* mac, CGEventType type, CGPoint point,
292 CGMouseButton button, int64_t clicks)
294 CGEventRef
event = CGEventCreateMouseEvent(
nullptr, type, point, button);
299 CGEventSetIntegerValueField(event, kCGMouseEventClickState, clicks);
300 CGEventSetFlags(event, mac->modifiers);
301 return mac_shadow_post(event);
304static BOOL mac_shadow_mouse_move(macShadowSubsystem* mac, CGPoint point)
306 EnterCriticalSection(&mac->clientLock);
307 mac->pointerInput = GetTickCount64();
308 LeaveCriticalSection(&mac->clientLock);
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,
316 for (UINT32 button = kCGMouseButtonCenter; button < 32; button++)
318 if (mac->buttonsDown & (1u << button))
319 return mac_shadow_mouse_event(mac, kCGEventOtherMouseDragged, point,
320 (CGMouseButton)button, 0);
322 return mac_shadow_mouse_event(mac, kCGEventMouseMoved, point, kCGMouseButtonLeft, 0);
325static BOOL mac_shadow_mouse_button(macShadowSubsystem* mac, CGMouseButton button, BOOL down,
328 CGEventType type = kCGEventNull;
332 case kCGMouseButtonLeft:
333 type = down ? kCGEventLeftMouseDown : kCGEventLeftMouseUp;
335 case kCGMouseButtonRight:
336 type = down ? kCGEventRightMouseDown : kCGEventRightMouseUp;
339 type = down ? kCGEventOtherMouseDown : kCGEventOtherMouseUp;
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))
355 mac->lastClickTime = now;
356 mac->lastClickButton = button;
357 mac->lastClickPoint = point;
358 mac->buttonsDown |= (1u << button);
361 mac->buttonsDown &= ~(1u << button);
363 const int64_t clicks = (button == mac->lastClickButton) ? mac->clickCount : 1;
364 return mac_shadow_mouse_event(mac, type, point, button, clicks);
367static BOOL mac_shadow_mouse_wheel(macShadowSubsystem* mac, UINT16 flags, BOOL horizontal)
370 INT32 delta = flags & WheelRotationMask;
371 if (flags & PTR_FLAGS_WHEEL_NEGATIVE)
374 INT32* remainder = &mac->wheelRemainder[horizontal ? 1 : 0];
376 const INT32 lines = *remainder / 120;
379 *remainder -= lines * 120;
382 const int32_t vertical = horizontal ? 0 : lines;
383 const int32_t sideways = horizontal ? -lines : 0;
385 CGEventCreateScrollWheelEvent(
nullptr, kCGScrollEventUnitLine, 2, vertical, sideways);
389 CGEventSetFlags(event, mac->modifiers);
390 return mac_shadow_post(event);
394static BOOL mac_shadow_input_mouse_event(rdpShadowSubsystem* subsystem, rdpShadowClient* client,
395 UINT16 flags, UINT16 x, UINT16 y)
397 macShadowSubsystem* mac = (macShadowSubsystem*)subsystem;
399 if (!subsystem || !client)
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);
407 const CGPoint point = mac_shadow_point(mac, x, y);
409 if (flags & PTR_FLAGS_MOVE)
411 if (!mac_shadow_mouse_move(mac, point))
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);
427static BOOL mac_shadow_input_extended_mouse_event(rdpShadowSubsystem* subsystem,
428 rdpShadowClient* client, UINT16 flags, UINT16 x,
431 macShadowSubsystem* mac = (macShadowSubsystem*)subsystem;
433 if (!subsystem || !client)
436 const CGPoint point = mac_shadow_point(mac, x, y);
438 if (flags & PTR_FLAGS_MOVE)
440 if (!mac_shadow_mouse_move(mac, point))
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);
458 const char* password;
459} MAC_SHADOW_PAM_DATA;
461static int mac_shadow_pam_conv(
int num_msg,
const struct pam_message** msg,
462 struct pam_response** resp,
void* appdata_ptr)
464 const MAC_SHADOW_PAM_DATA* data = (
const MAC_SHADOW_PAM_DATA*)appdata_ptr;
466 if ((num_msg <= 0) || !msg || !resp || !data)
469 struct pam_response* response = calloc((
size_t)num_msg,
sizeof(
struct pam_response));
473 for (
int index = 0; index < num_msg; index++)
475 const char* answer =
nullptr;
476 switch (msg[index]->msg_style)
478 case PAM_PROMPT_ECHO_ON:
481 case PAM_PROMPT_ECHO_OFF:
482 answer = data->password;
488 response[index].resp = _strdup(answer);
489 if (!response[index].resp)
491 for (
int x = 0; x < index; x++)
492 free(response[x].resp);
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)
510 if (!user || !password)
513 const struct passwd* owner = getpwuid(getuid());
514 if (!owner || !owner->pw_name || (strcmp(owner->pw_name, user) != 0))
516 WLog_ERR(TAG,
"login as '%s' refused: this screen belongs to '%s'", user,
517 (owner && owner->pw_name) ? owner->pw_name :
"?");
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;
525 int status = pam_start(
"checkpw", user, &conv, &handle);
526 if (status != PAM_SUCCESS)
528 WLog_ERR(TAG,
"pam_start failure: %s", pam_strerror(handle, status));
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);
539 if (status != PAM_SUCCESS)
540 WLog_ERR(TAG,
"login as '%s' failed: %s", user, pam_strerror(handle, status));
542 pam_end(handle, status);
543 return (status == PAM_SUCCESS) ? 1 : -1;
547static void mac_shadow_on_frame(
void* context, CVPixelBufferRef frame,
const CGRect* dirty,
550 macShadowSubsystem* mac = (macShadowSubsystem*)context;
553 const CGRect frameRect = CGRectMake(0, 0, (CGFloat)CVPixelBufferGetWidth(frame),
554 (CGFloat)CVPixelBufferGetHeight(frame));
556 EnterCriticalSection(&mac->frameLock);
560 CVPixelBufferRetain(frame);
562 CVPixelBufferRelease(mac->frame);
565 for (
size_t x = 0; x < count; x++)
567 const CGRect rect = CGRectIntegral(CGRectIntersection(dirty[x], frameRect));
568 if (CGRectIsEmpty(rect))
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);
578 LeaveCriticalSection(&mac->frameLock);
579 (void)SetEvent(mac->frameEvent);
582static void mac_shadow_on_stopped(
void* context)
584 macShadowSubsystem* mac = (macShadowSubsystem*)context;
588 (void)SetEvent(mac->lostEvent);
591static void mac_shadow_audio_msg_free(WINPR_ATTR_UNUSED UINT32
id, SHADOW_MSG_OUT* msg)
600static void mac_shadow_audio_post(macShadowSubsystem* mac,
wStream* s)
602 const size_t size = Stream_GetPosition(s);
603 Stream_ResetPosition(s);
607 rdpShadowServer* server = mac->common.server;
608 if (!server || !server->clients || (ArrayList_Count(server->clients) == 0))
615 msg->buf = malloc(size);
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);
629 (void)shadow_client_boardcast_msg(server,
nullptr, SHADOW_MSG_OUT_AUDIO_OUT_SAMPLES_ID,
630 &msg->common,
nullptr);
633static INT16 mac_shadow_audio_sample(
float value)
639 return (INT16)lrintf(value * (
float)INT16_MAX);
642static void mac_shadow_on_audio(
void* context,
const float* samples,
size_t frames, UINT32 channels,
645 macShadowSubsystem* mac = (macShadowSubsystem*)context;
648 if ((channels != MAC_AUDIO_CHANNELS) || (rate != MAC_AUDIO_RATE))
650 WLog_DBG(TAG,
"sound in %" PRIu32
" Hz, %" PRIu32
" channels, not what was asked: dropped",
655 const UINT64 now = GetTickCount64();
656 if (mac->audioLogged == 0)
658 WLog_INFO(TAG,
"sound: capturing %" PRIu32
" Hz, %" PRIu32
" channels", rate, channels);
659 mac->audioLogged = now;
661 else if (now - mac->audioLogged >= MAC_SHADOW_AUDIO_LOG_MS)
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;
673 mac->audioFrames += frames;
676 for (
size_t f = 0; f < frames; f++)
678 for (UINT32 c = 0; c < channels; c++)
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));
685 if (Stream_GetPosition(s) >= MAC_AUDIO_MESSAGE_FRAMES * MAC_AUDIO_FRAME_BYTES)
686 mac_shadow_audio_post(mac, s);
690static BOOL mac_shadow_audio_init(macShadowSubsystem* mac)
692 AUDIO_FORMAT* formats = audio_formats_new(ARRAYSIZE(mac_audio_offered_formats));
697 for (
size_t x = 0; x < ARRAYSIZE(mac_audio_offered_formats); x++)
699 const AUDIO_FORMAT* format = &mac_audio_offered_formats[x];
700 if (freerdp_dsp_supports_format(format, TRUE))
701 formats[count++] = *format;
704 mac->audioStream = Stream_New(
nullptr, MAC_AUDIO_MESSAGE_FRAMES * MAC_AUDIO_FRAME_BYTES);
705 if (!mac->audioStream || (count == 0))
707 Stream_Free(mac->audioStream, TRUE);
708 mac->audioStream =
nullptr;
709 audio_formats_free(formats, ARRAYSIZE(mac_audio_offered_formats));
713 mac->common.rdpsndFormats = formats;
714 mac->common.nRdpsndFormats = count;
718static void mac_shadow_audio_uninit(macShadowSubsystem* mac)
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;
729static void mac_shadow_send_frame(macShadowSubsystem* mac)
731 rdpShadowServer* server = mac->common.server;
732 rdpShadowSurface* surface = server->surface;
734 region16_init(®ion);
736 EnterCriticalSection(&mac->frameLock);
737 CVPixelBufferRef frame = mac->frame;
739 CVPixelBufferRetain(frame);
740 (void)region16_copy(®ion, &mac->pending);
741 region16_clear(&mac->pending);
742 LeaveCriticalSection(&mac->frameLock);
747 if ((CVPixelBufferGetWidth(frame) != surface->width) ||
748 (CVPixelBufferGetHeight(frame) != surface->height))
751 if (!mac->sizeWarned)
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;
762 const RECTANGLE_16 surfaceRect = { 0, 0, (UINT16)surface->width, (UINT16)surface->height };
763 (void)region16_intersect_rect(®ion, ®ion, &surfaceRect);
766 if (region16_is_empty(®ion))
769 if (CVPixelBufferLockBaseAddress(frame, kCVPixelBufferLock_ReadOnly) != kCVReturnSuccess)
773 const BYTE* data = CVPixelBufferGetBaseAddress(frame);
774 const size_t stride = CVPixelBufferGetBytesPerRow(frame);
776 const RECTANGLE_16* rects = region16_rects(®ion, &count);
778 EnterCriticalSection(&surface->lock);
779 for (UINT32 x = 0; x < count; 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);
788 LeaveCriticalSection(&surface->lock);
791 (void)CVPixelBufferUnlockBaseAddress(frame, kCVPixelBufferLock_ReadOnly);
793 ArrayList_Lock(server->clients);
794 if (ArrayList_Count(server->clients) > 0)
795 shadow_subsystem_frame_update(&mac->common);
796 ArrayList_Unlock(server->clients);
798 EnterCriticalSection(&surface->lock);
799 region16_clear(&surface->invalidRegion);
800 LeaveCriticalSection(&surface->lock);
804 CVPixelBufferRelease(frame);
805 region16_uninit(®ion);
808static void mac_shadow_refresh(macShadowSubsystem* mac)
810 const RECTANGLE_16 all = { 0, 0, (UINT16)mac->width, (UINT16)mac->height };
812 EnterCriticalSection(&mac->frameLock);
813 (void)region16_union_rect(&mac->pending, &mac->pending, &all);
814 LeaveCriticalSection(&mac->frameLock);
816 EnterCriticalSection(&mac->clientLock);
817 mac->cursorResend = TRUE;
818 LeaveCriticalSection(&mac->clientLock);
820 (void)SetEvent(mac->frameEvent);
823static void mac_shadow_cursor_msg_free(UINT32
id, SHADOW_MSG_OUT* msg)
825 if (msg && (
id == SHADOW_MSG_OUT_POINTER_ALPHA_UPDATE_ID))
828 free(shape->xorMaskData);
829 free(shape->andMaskData);
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++)
843 for (
size_t x = 0; x < sizes[p]; x++)
852static BOOL mac_shadow_cursor_send_shape(macShadowSubsystem* mac,
const macCursorImage* image)
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)
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,
876static void mac_shadow_cursor_send_position(macShadowSubsystem* mac, UINT64 now)
878 EnterCriticalSection(&mac->clientLock);
879 const UINT64 input = mac->pointerInput;
880 LeaveCriticalSection(&mac->clientLock);
881 if (now - input < MAC_SHADOW_CURSOR_QUIET_MS)
884 CGEventRef
event = CGEventCreate(
nullptr);
887 const CGPoint where = CGEventGetLocation(event);
892 (where.x - mac->bounds.origin.x) * (CGFloat)mac->width / mac->bounds.size.width;
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))
900 mac->common.pointerX = xPos;
901 mac->common.pointerY = 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,
917static void mac_shadow_cursor_update(macShadowSubsystem* mac, UINT64 now)
919 if (now < mac->cursorNext)
921 mac->cursorNext = now + MAC_SHADOW_CURSOR_MS;
924 if (mac_cursor_get(&image, MAC_SHADOW_CURSOR_MAX))
926 EnterCriticalSection(&mac->clientLock);
927 const BOOL resend = mac->cursorResend;
928 mac->cursorResend = FALSE;
929 LeaveCriticalSection(&mac->clientLock);
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))
935 mac->cursorHash = hash;
936 mac->cursorKnown = TRUE;
938 mac_cursor_image_free(&image);
941 mac_shadow_cursor_send_position(mac, now);
944static BOOL mac_shadow_has_clients(macShadowSubsystem* mac)
946 EnterCriticalSection(&mac->clientLock);
947 const BOOL clients = mac->clients > 0;
948 LeaveCriticalSection(&mac->clientLock);
952static void mac_shadow_subsystem_process_message(macShadowSubsystem* mac, wMessage* message)
956 case SHADOW_MSG_IN_REFRESH_REQUEST_ID:
957 mac_shadow_refresh(mac);
961 WLog_ERR(TAG,
"Unknown message id: %" PRIu32
"", message->id);
966 message->Free(message);
971static BOOL mac_shadow_capture_restart(macShadowSubsystem* mac)
974 if (!CGDisplayIsActive(mac->display) || CGDisplayIsAsleep(mac->display))
977 mac_capture_free(mac->capture);
978 mac->capture = mac_capture_new(&mac->config);
982 if (!mac_capture_start(mac->capture))
984 mac_capture_free(mac->capture);
985 mac->capture =
nullptr;
990 mac_shadow_refresh(mac);
994static DWORD WINAPI mac_shadow_subsystem_thread(LPVOID arg)
996 macShadowSubsystem* mac = (macShadowSubsystem*)arg;
998 HANDLE events[] = { mac->stopEvent, MessageQueue_Event(MsgPipe->In), mac->frameEvent,
1007 const BOOL clients = mac_shadow_has_clients(mac);
1008 DWORD timeout = clients ? MAC_SHADOW_CURSOR_MS : INFINITE;
1010 timeout = MIN(timeout, MAC_SHADOW_RESTART_MS);
1012 const DWORD status = WaitForMultipleObjects(ARRAYSIZE(events), events, FALSE, timeout);
1013 if ((status == WAIT_FAILED) || (WaitForSingleObject(mac->stopEvent, 0) == WAIT_OBJECT_0))
1016 const UINT64 now = GetTickCount64();
1017 if (WaitForSingleObject(mac->lostEvent, 0) == WAIT_OBJECT_0)
1019 (void)ResetEvent(mac->lostEvent);
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");
1024 restart = now + MAC_SHADOW_RESTART_MS;
1026 else if (lost && (now >= restart))
1028 restart = now + MAC_SHADOW_RESTART_MS;
1029 if (mac_shadow_capture_restart(mac))
1031 WLog_INFO(TAG,
"the display is back: capturing the screen again");
1037 mac_shadow_cursor_update(mac, now);
1039 wMessage message = WINPR_C_ARRAY_INIT;
1040 while (MessageQueue_Peek(MsgPipe->In, &message, TRUE))
1042 if (message.id == WMQ_QUIT)
1044 mac_shadow_subsystem_process_message(mac, &message);
1047 if (WaitForSingleObject(mac->frameEvent, 0) == WAIT_OBJECT_0)
1049 (void)ResetEvent(mac->frameEvent);
1050 mac_shadow_send_frame(mac);
1059static BOOL mac_shadow_check_input_access(
void)
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);
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;
1080static int mac_shadow_subsystem_init(rdpShadowSubsystem* subsystem)
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;
1086 const UINT32 count = mac_shadow_list_displays(subsystem->monitors, ids, bounds, ARRAYSIZE(ids));
1089 WLog_ERR(TAG,
"no display found");
1093 subsystem->numMonitors = count;
1094 if (subsystem->selectedMonitor >= count)
1095 subsystem->selectedMonitor = 0;
1097 const MONITOR_DEF* monitor = &subsystem->monitors[subsystem->selectedMonitor];
1098 mac->display = ids[subsystem->selectedMonitor];
1099 mac->bounds = bounds[subsystem->selectedMonitor];
1102 mac->width = (UINT32)(monitor->right - monitor->left + 1);
1103 mac->height = (UINT32)(monitor->bottom - monitor->top + 1);
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++)
1112 virtualScreen->right = MAX(virtualScreen->right, subsystem->monitors[x].right);
1113 virtualScreen->bottom = MAX(virtualScreen->bottom, subsystem->monitors[x].bottom);
1115 virtualScreen->flags = 1;
1118 "sharing display %" PRIu32
" (%" PRIu32
" of %" PRIu32
"): %" PRIu32
"x%" PRIu32
1120 mac->display, subsystem->selectedMonitor, count, mac->width, mac->height,
1121 mac->bounds.origin.x, mac->bounds.origin.y);
1123 if (!mac_capture_check_access())
1126 (void)mac_shadow_check_input_access();
1128 const BOOL audio = mac_shadow_audio_init(mac);
1130 WLog_WARN(TAG,
"sound: no format to offer: no sound");
1133 .width = mac->width,
1134 .height = mac->height,
1135 .fps = MAC_SHADOW_FPS,
1137 .showCursor = FALSE,
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,
1145 mac->config = config;
1147 mac->capture = mac_capture_new(&mac->config);
1154static int mac_shadow_subsystem_uninit(rdpShadowSubsystem* subsystem)
1156 macShadowSubsystem* mac = (macShadowSubsystem*)subsystem;
1160 mac_capture_free(mac->capture);
1161 mac->capture =
nullptr;
1163 EnterCriticalSection(&mac->frameLock);
1165 CVPixelBufferRelease(mac->frame);
1166 mac->frame =
nullptr;
1167 region16_clear(&mac->pending);
1168 LeaveCriticalSection(&mac->frameLock);
1170 mac_shadow_audio_uninit(mac);
1174static int mac_shadow_subsystem_stop(rdpShadowSubsystem* subsystem)
1176 macShadowSubsystem* mac = (macShadowSubsystem*)subsystem;
1184 (void)SetEvent(mac->stopEvent);
1185 (void)WaitForSingleObject(mac->thread, INFINITE);
1186 (void)CloseHandle(mac->thread);
1187 mac->thread =
nullptr;
1190 mac_capture_stop(mac->capture);
1197static BOOL mac_shadow_client_connect(rdpShadowSubsystem* subsystem,
1198 WINPR_ATTR_UNUSED rdpShadowClient* client)
1200 macShadowSubsystem* mac = (macShadowSubsystem*)subsystem;
1203 EnterCriticalSection(&mac->clientLock);
1204 if (mac->clients++ == 0)
1206 IOPMAssertionID activity = kIOPMNullAssertionID;
1207 if (IOPMAssertionDeclareUserActivity(CFSTR(
"FreeRDP shadow server: a client connected"),
1208 kIOPMUserActiveRemote, &activity) == kIOReturnSuccess)
1209 (void)IOPMAssertionRelease(activity);
1211 if (IOPMAssertionCreateWithName(kIOPMAssertionTypePreventUserIdleDisplaySleep,
1212 kIOPMAssertionLevelOn,
1213 CFSTR(
"FreeRDP shadow server: clients are connected"),
1214 &mac->awake) != kIOReturnSuccess)
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;
1221 LeaveCriticalSection(&mac->clientLock);
1225static void mac_shadow_client_disconnect(rdpShadowSubsystem* subsystem,
1226 WINPR_ATTR_UNUSED rdpShadowClient* client)
1228 macShadowSubsystem* mac = (macShadowSubsystem*)subsystem;
1231 EnterCriticalSection(&mac->clientLock);
1232 if ((mac->clients > 0) && (--mac->clients == 0) && (mac->awake != kIOPMNullAssertionID))
1234 (void)IOPMAssertionRelease(mac->awake);
1235 mac->awake = kIOPMNullAssertionID;
1237 LeaveCriticalSection(&mac->clientLock);
1241static int mac_shadow_subsystem_start(rdpShadowSubsystem* subsystem)
1243 macShadowSubsystem* mac = (macShadowSubsystem*)subsystem;
1248 (void)ResetEvent(mac->stopEvent);
1249 mac->thread = CreateThread(
nullptr, 0, mac_shadow_subsystem_thread, mac, 0,
nullptr);
1252 WLog_ERR(TAG,
"Failed to create thread");
1256 if (!mac_capture_start(mac->capture))
1258 (void)mac_shadow_subsystem_stop(subsystem);
1265static void mac_shadow_subsystem_free(rdpShadowSubsystem* subsystem)
1267 macShadowSubsystem* mac = (macShadowSubsystem*)subsystem;
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);
1281 (void)CloseHandle(mac->stopEvent);
1283 (void)CloseHandle(mac->lostEvent);
1288static rdpShadowSubsystem* mac_shadow_subsystem_new(
void)
1290 macShadowSubsystem* mac = calloc(1,
sizeof(macShadowSubsystem));
1295 region16_init(&mac->pending);
1296 if (!InitializeCriticalSectionAndSpinCount(&mac->frameLock, 4000))
1302 if (!InitializeCriticalSectionAndSpinCount(&mac->clientLock, 4000))
1304 DeleteCriticalSection(&mac->frameLock);
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)
1314 mac_shadow_subsystem_free(&mac->common);
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;
1326 mac->common.Authenticate = mac_shadow_pam_authenticate;
1328 return &mac->common;
1331FREERDP_API
const char* ShadowSubsystemName(
void)
1336FREERDP_API
int ShadowSubsystemEntry(RDP_SHADOW_ENTRY_POINTS* pEntryPoints)
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;