FreeRDP
Loading...
Searching...
No Matches
win_shadow.c
1
19#include <windows.h>
20
21#include <winpr/crt.h>
22#include <winpr/synch.h>
23#include <winpr/sysinfo.h>
24
25#include <freerdp/log.h>
26#include <freerdp/codec/color.h>
27#include <freerdp/codec/region.h>
28#include <freerdp/server/server-common.h>
29
30#include "win_shadow.h"
31#include "win_sas.h"
32#include "win_audio.h"
33
34#if defined(CHANNEL_CLIPRDR_SERVER)
35#include "win_shadow_cliprdr.h"
36#endif
37
38#define TAG SERVER_TAG("shadow.win")
39
40/* https://docs.microsoft.com/en-us/windows/win32/api/winuser/nf-winuser-mouse_event
41 * does not mention this flag is only supported if building for _WIN32_WINNT >= 0x0600
42 */
43#ifndef MOUSEEVENTF_HWHEEL
44#define MOUSEEVENTF_HWHEEL 0x1000
45#endif
46
47/* The desktop this thread opened to follow the input desktop, if any */
48static WINPR_TLS HDESK win_shadow_thread_desktop = nullptr;
49
50/* Moves the calling thread to the input desktop when that changed: SendInput
51 * and DXGI desktop duplication only work there. The input desktop is the
52 * user's (Default) or, as SYSTEM on the console session (the service mode),
53 * the secure one (Winlogon: the logon and lock screens, UAC prompts). A user
54 * process cannot open the secure desktop and stays where it is. Returns TRUE
55 * when the thread moved. */
56WINPR_ATTR_NODISCARD
57static BOOL win_shadow_follow_input_desktop(void)
58{
59 WCHAR inputName[64] = WINPR_C_ARRAY_INIT;
60 WCHAR threadName[64] = WINPR_C_ARRAY_INIT;
61 HDESK input = OpenInputDesktop(0, FALSE, GENERIC_ALL);
62
63 if (!input)
64 return FALSE;
65
66 if (!GetUserObjectInformationW(input, UOI_NAME, inputName, sizeof(inputName), nullptr))
67 WLog_WARN(TAG, "GetUserObjectInformationW(input desktop, UOI_NAME) failed: 0x%08lX",
68 GetLastError());
69 if (!GetUserObjectInformationW(GetThreadDesktop(GetCurrentThreadId()), UOI_NAME, threadName,
70 sizeof(threadName), nullptr))
71 WLog_WARN(TAG, "GetUserObjectInformationW(thread desktop, UOI_NAME) failed: 0x%08lX",
72 GetLastError());
73
74 if (_wcsicmp(inputName, threadName) == 0)
75 {
76 (void)CloseDesktop(input);
77 return FALSE;
78 }
79
80 if (!SetThreadDesktop(input))
81 {
82 WLog_WARN(TAG, "SetThreadDesktop(%ls) failed: 0x%08lX", inputName, GetLastError());
83 (void)CloseDesktop(input);
84 return FALSE;
85 }
86
87 /* the desktop GetThreadDesktop() gave first is not ours to close */
88 if (win_shadow_thread_desktop)
89 (void)CloseDesktop(win_shadow_thread_desktop);
90 win_shadow_thread_desktop = input;
91 WLog_INFO(TAG, "thread %lu follows the input desktop from %ls to %ls", GetCurrentThreadId(),
92 threadName, inputName);
93 return TRUE;
94}
95
96/* SendInput, without ending the session when Windows refuses the event: it
97 * does while the input desktop switches (the secure desktop comes or goes)
98 * and for a window above our integrity level, and FreeRDP drops the client
99 * when an input handler fails. The failure log names only the input kind:
100 * the key codes themselves must never reach the log. */
101static void win_shadow_send_input(const char* what, INPUT* event)
102{
103 WINPR_ASSERT(what);
104 WINPR_ASSERT(event);
105
106 if (SendInput(1, event, sizeof(INPUT)) == 0)
107 WLog_WARN(TAG, "%s: SendInput refused the event (0x%08lX), it is dropped", what,
108 GetLastError());
109}
110
111/* UIPI: a process below High integrity (a scheduled task defaults to Medium)
112 * may not send input to windows of a higher integrity level (an elevated
113 * foreground application), and SendInput does not report the refusal: it
114 * returns success and does not set the last error. The screen capture is
115 * unaffected, so such a shadow serves a perfectly fine picture while every
116 * keystroke and mouse click into an elevated window is silently dropped.
117 * Say so at startup: this turns a "keyboard and mouse dead while picture is
118 * fine" report into a log line readable in seconds.
119 * https://learn.microsoft.com/en-us/windows/win32/secauthz/how-uipi-works */
120static void win_shadow_check_input_integrity(void)
121{
122 HANDLE token = nullptr;
123 DWORD length = 0;
124
125 if (!OpenProcessToken(GetCurrentProcess(), TOKEN_QUERY, &token))
126 {
127 WLog_WARN(TAG, "OpenProcessToken failed: cannot read the process integrity level (0x%08lX)",
128 GetLastError());
129 return;
130 }
131
132 /* the integrity level RID is the last sub-authority of the mandatory
133 * label SID (S-1-16-<rid>) */
134 if (!GetTokenInformation(token, TokenIntegrityLevel, nullptr, 0, &length) &&
135 (GetLastError() == ERROR_INSUFFICIENT_BUFFER))
136 {
137 PTOKEN_MANDATORY_LABEL label = (PTOKEN_MANDATORY_LABEL)malloc(length);
138
139 if (!label)
140 WLog_WARN(TAG, "out of memory: cannot read the process integrity level");
141 else
142 {
143 if (GetTokenInformation(token, TokenIntegrityLevel, label, length, &length) &&
144 label->Label.Sid)
145 {
146 const PSID sid = label->Label.Sid;
147 const DWORD rid = *GetSidSubAuthority(sid, *GetSidSubAuthorityCount(sid) - 1);
148
149 if (rid < SECURITY_MANDATORY_HIGH_RID)
150 WLog_WARN(TAG,
151 "process is NOT elevated (integrity level 0x%04lX, Medium or lower): "
152 "injected input to elevated windows will be silently dropped by UIPI "
153 "- run the shadow elevated (e.g. scheduled task RunLevel Highest) or "
154 "expect \"keyboard and mouse dead while picture is fine\" reports",
155 rid);
156 else
157 WLog_DBG(TAG,
158 "process integrity level 0x%04lX: injected input reaches elevated "
159 "windows",
160 rid);
161 }
162 else
163 WLog_WARN(
164 TAG,
165 "GetTokenInformation(TokenIntegrityLevel) failed: cannot read the process "
166 "integrity level (0x%08lX)",
167 GetLastError());
168
169 free(label);
170 }
171 }
172
173 (void)CloseHandle(token);
174}
175
176/* Ctrl+Alt+Del: asks freerdp-shadow-service for the secure attention
177 * sequence (win_sas.h) */
178static void win_shadow_send_sas(void)
179{
180 HANDLE event = OpenEventW(EVENT_MODIFY_STATE, FALSE, WIN_SHADOW_SAS_EVENT);
181
182 if (!event)
183 {
184 WLog_WARN(TAG, "Ctrl+Alt+Del: no freerdp-shadow-service to send it (0x%08lX)",
185 GetLastError());
186 return;
187 }
188
189 if (SetEvent(event))
190 WLog_INFO(TAG,
191 "Ctrl+Alt+Del: asked freerdp-shadow-service for the secure attention sequence");
192 else
193 WLog_WARN(TAG, "Ctrl+Alt+Del: SetEvent failed (0x%08lX)", GetLastError());
194 (void)CloseHandle(event);
195}
196
197static BOOL win_shadow_input_synchronize_event(rdpShadowSubsystem* subsystem,
198 rdpShadowClient* client, UINT32 flags)
199{
200 WLog_WARN(TAG, "TODO: Implement!");
201 return TRUE;
202}
203
204static BOOL win_shadow_input_keyboard_event(rdpShadowSubsystem* subsystem, rdpShadowClient* client,
205 UINT16 flags, UINT8 code)
206{
207 winShadowSubsystem* win = (winShadowSubsystem*)subsystem;
208 const BOOL down = (flags & KBD_FLAGS_RELEASE) == 0;
209
210 WINPR_ASSERT(win);
211 (void)win_shadow_follow_input_desktop();
212
213 /* scan codes: Ctrl 0x1D, Alt 0x38 (the right ones extended), Delete
214 * 0x53 (the keypad's not extended); Windows takes either Delete */
215 if (code == 0x1D)
216 win->ctrlDown = down;
217 else if (code == 0x38)
218 win->altDown = down;
219 else if ((code == 0x53) && win->ctrlDown && win->altDown)
220 {
221 if (down)
222 win_shadow_send_sas();
223 return TRUE; /* never typed into the desktop */
224 }
225 INPUT event = WINPR_C_ARRAY_INIT;
226 event.type = INPUT_KEYBOARD;
227 KEYBDINPUT* ki =
228#if defined(NONAMELESSUNION)
229 &event.u.ki;
230#else
231 &event.ki;
232#endif
233
234 ki->wVk = 0;
235 ki->wScan = code;
236 ki->dwFlags = KEYEVENTF_SCANCODE;
237 ki->dwExtraInfo = 0;
238 ki->time = 0;
239
240 if (flags & KBD_FLAGS_RELEASE)
241 ki->dwFlags |= KEYEVENTF_KEYUP;
242
243 if (flags & KBD_FLAGS_EXTENDED)
244 ki->dwFlags |= KEYEVENTF_EXTENDEDKEY;
245
246 win_shadow_send_input("keyboard", &event);
247 return TRUE;
248}
249
250static BOOL win_shadow_input_unicode_keyboard_event(rdpShadowSubsystem* subsystem,
251 rdpShadowClient* client, UINT16 flags,
252 UINT16 code)
253{
254 (void)win_shadow_follow_input_desktop();
255 INPUT event = WINPR_C_ARRAY_INIT;
256 event.type = INPUT_KEYBOARD;
257 KEYBDINPUT* ki =
258#if defined(NONAMELESSUNION)
259 &event.u.ki;
260#else
261 &event.ki;
262#endif
263 ki->wVk = 0;
264 ki->wScan = code;
265 ki->dwFlags = KEYEVENTF_UNICODE;
266 ki->dwExtraInfo = 0;
267 ki->time = 0;
268
269 if (flags & KBD_FLAGS_RELEASE)
270 ki->dwFlags |= KEYEVENTF_KEYUP;
271
272 win_shadow_send_input("unicode keyboard", &event);
273 return TRUE;
274}
275
276static BOOL win_shadow_input_mouse_event(rdpShadowSubsystem* subsystem, rdpShadowClient* client,
277 UINT16 flags, UINT16 x, UINT16 y)
278{
279 (void)win_shadow_follow_input_desktop();
280 INPUT event = WINPR_C_ARRAY_INIT;
281 float width;
282 float height;
283 MOUSEINPUT* mi =
284#if defined(NONAMELESSUNION)
285 &event.u.mi;
286#else
287 &event.mi;
288#endif
289
290 event.type = INPUT_MOUSE;
291
292 if (flags & (PTR_FLAGS_WHEEL | PTR_FLAGS_HWHEEL))
293 {
294 if (flags & PTR_FLAGS_WHEEL)
295 mi->dwFlags = MOUSEEVENTF_WHEEL;
296 else
297 mi->dwFlags = MOUSEEVENTF_HWHEEL;
298 mi->mouseData = flags & WheelRotationMask;
299
300 if (flags & PTR_FLAGS_WHEEL_NEGATIVE)
301 mi->mouseData *= -1;
302
303 win_shadow_send_input("mouse", &event);
304 }
305 else
306 {
307 width = (float)GetSystemMetrics(SM_CXSCREEN);
308 height = (float)GetSystemMetrics(SM_CYSCREEN);
309 mi->dx = (LONG)((float)x * (65535.0f / width));
310 mi->dy = (LONG)((float)y * (65535.0f / height));
311 mi->dwFlags = MOUSEEVENTF_ABSOLUTE;
312
313 if (flags & PTR_FLAGS_MOVE)
314 {
315 mi->dwFlags |= MOUSEEVENTF_MOVE;
316 win_shadow_send_input("mouse", &event);
317 }
318
319 mi->dwFlags = MOUSEEVENTF_ABSOLUTE;
320
321 if (flags & PTR_FLAGS_BUTTON1)
322 {
323 if (flags & PTR_FLAGS_DOWN)
324 mi->dwFlags |= MOUSEEVENTF_LEFTDOWN;
325 else
326 mi->dwFlags |= MOUSEEVENTF_LEFTUP;
327
328 win_shadow_send_input("mouse", &event);
329 }
330 else if (flags & PTR_FLAGS_BUTTON2)
331 {
332 if (flags & PTR_FLAGS_DOWN)
333 mi->dwFlags |= MOUSEEVENTF_RIGHTDOWN;
334 else
335 mi->dwFlags |= MOUSEEVENTF_RIGHTUP;
336
337 win_shadow_send_input("mouse", &event);
338 }
339 else if (flags & PTR_FLAGS_BUTTON3)
340 {
341 if (flags & PTR_FLAGS_DOWN)
342 mi->dwFlags |= MOUSEEVENTF_MIDDLEDOWN;
343 else
344 mi->dwFlags |= MOUSEEVENTF_MIDDLEUP;
345
346 win_shadow_send_input("mouse", &event);
347 }
348 }
349
350 return TRUE;
351}
352
353static BOOL win_shadow_input_extended_mouse_event(rdpShadowSubsystem* subsystem,
354 rdpShadowClient* client, UINT16 flags, UINT16 x,
355 UINT16 y)
356{
357 (void)win_shadow_follow_input_desktop();
358 INPUT event = WINPR_C_ARRAY_INIT;
359 float width;
360 float height;
361 MOUSEINPUT* mi =
362#if defined(NONAMELESSUNION)
363 &event.u.mi;
364#else
365 &event.mi;
366#endif
367
368 if ((flags & PTR_XFLAGS_BUTTON1) || (flags & PTR_XFLAGS_BUTTON2))
369 {
370 event.type = INPUT_MOUSE;
371
372 if (flags & PTR_FLAGS_MOVE)
373 {
374 width = (float)GetSystemMetrics(SM_CXSCREEN);
375 height = (float)GetSystemMetrics(SM_CYSCREEN);
376 mi->dx = (LONG)((float)x * (65535.0f / width));
377 mi->dy = (LONG)((float)y * (65535.0f / height));
378 mi->dwFlags = MOUSEEVENTF_ABSOLUTE | MOUSEEVENTF_MOVE;
379 win_shadow_send_input("extended mouse", &event);
380 }
381
382 mi->dx = mi->dy = mi->dwFlags = 0;
383
384 if (flags & PTR_XFLAGS_DOWN)
385 mi->dwFlags |= MOUSEEVENTF_XDOWN;
386 else
387 mi->dwFlags |= MOUSEEVENTF_XUP;
388
389 if (flags & PTR_XFLAGS_BUTTON1)
390 mi->mouseData = XBUTTON1;
391 else if (flags & PTR_XFLAGS_BUTTON2)
392 mi->mouseData = XBUTTON2;
393
394 win_shadow_send_input("extended mouse", &event);
395 }
396
397 return TRUE;
398}
399
400static int win_shadow_invalidate_region(winShadowSubsystem* subsystem, int x, int y, int width,
401 int height)
402{
403 rdpShadowServer* server = subsystem->base.server;
404 rdpShadowSurface* surface = server->surface;
405 RECTANGLE_16 invalidRect = { .left = x, .top = y, .right = x + width, .bottom = y + height };
406 EnterCriticalSection(&(surface->lock));
407 region16_union_rect(&(surface->invalidRegion), &(surface->invalidRegion), &invalidRect);
408 LeaveCriticalSection(&(surface->lock));
409 return 1;
410}
411
412static int win_shadow_surface_copy(winShadowSubsystem* subsystem)
413{
414 int x, y;
415 int width;
416 int height;
417 int status = 1;
418 int nDstStep = 0;
419 DWORD DstFormat;
420 BYTE* pDstData = nullptr;
421 rdpShadowServer* server;
422 rdpShadowSurface* surface;
423 RECTANGLE_16 surfaceRect;
424 RECTANGLE_16 invalidRect;
425 const RECTANGLE_16* extents;
426 server = subsystem->base.server;
427 surface = server->surface;
428
429 if (ArrayList_Count(server->clients) < 1)
430 return 1;
431
432 surfaceRect.left = surface->x;
433 surfaceRect.top = surface->y;
434 surfaceRect.right = surface->x + surface->width;
435 surfaceRect.bottom = surface->y + surface->height;
436 region16_intersect_rect(&(surface->invalidRegion), &(surface->invalidRegion), &surfaceRect);
437
438 if (region16_is_empty(&(surface->invalidRegion)))
439 return 1;
440
441 extents = region16_extents(&(surface->invalidRegion));
442 CopyMemory(&invalidRect, extents, sizeof(RECTANGLE_16));
443 shadow_capture_align_clip_rect(&invalidRect, &surfaceRect);
444 x = invalidRect.left;
445 y = invalidRect.top;
446 width = invalidRect.right - invalidRect.left;
447 height = invalidRect.bottom - invalidRect.top;
448
449 /* The alignment to 16 pixels may reach past the captured screen when its
450 * size is not a multiple of 16 (1600x900): copy what was captured only */
451 if (x + width > subsystem->width)
452 width = subsystem->width - x;
453 if (y + height > subsystem->height)
454 height = subsystem->height - y;
455 if ((width <= 0) || (height <= 0))
456 {
457 region16_clear(&(surface->invalidRegion));
458 return 1;
459 }
460
461 if (0)
462 {
463 x = 0;
464 y = 0;
465 width = surface->width;
466 height = surface->height;
467 }
468
469 WLog_INFO(TAG, "SurfaceCopy x: %d y: %d width: %d height: %d right: %d bottom: %d", x, y, width,
470 height, x + width, y + height);
471#if defined(WITH_WDS_API)
472 {
473 rdpGdi* gdi;
474 shwContext* shw;
475 rdpContext* context;
476
477 WINPR_ASSERT(subsystem);
478 shw = subsystem->shw;
479 WINPR_ASSERT(shw);
480
481 context = &shw->common.context;
482 WINPR_ASSERT(context);
483
484 gdi = context->gdi;
485 WINPR_ASSERT(gdi);
486
487 pDstData = gdi->primary_buffer;
488 nDstStep = gdi->width * 4;
489 DstFormat = gdi->dstFormat;
490 }
491#elif defined(WITH_DXGI_1_2)
492 DstFormat = PIXEL_FORMAT_BGRX32;
493 status = win_shadow_dxgi_fetch_frame_data(subsystem, &pDstData, &nDstStep, x, y, width, height);
494#endif
495
496 if (status <= 0)
497 return status;
498
499#if defined(WITH_WDS_API)
500 /* WDS: the GDI primary buffer spans the whole desktop */
501 const int srcX = x;
502 const int srcY = y;
503#elif defined(WITH_DXGI_1_2)
504 /* DXGI: fetch_frame_data() copies the region to the staging texture
505 * origin, so the source offset is always the origin */
506 const int srcX = 0;
507 const int srcY = 0;
508#else
509 /* no capture backend compiled in: keep the previous behavior */
510 const int srcX = x;
511 const int srcY = y;
512#endif
513 if (!freerdp_image_copy_no_overlap(surface->data, surface->format, surface->scanline, x, y,
514 width, height, pDstData, DstFormat, nDstStep, srcX, srcY,
515 nullptr, FREERDP_FLIP_NONE))
516 return ERROR_INTERNAL_ERROR;
517
518 /* shadow_subsystem_frame_update() publishes the frame to all subscriber
519 * clients and waits until every one of them consumed it. That wait must
520 * not happen while the server clients list is locked: the listener
521 * thread needs this lock in shadow_client_accepted() (ArrayList_Append)
522 * and shadow_server_check_peer_restrictions() (ArrayList Count), so
523 * holding it across an unresponsive subscriber would stop accepting new
524 * connections entirely. The frame publication iterates the update
525 * event's own subscriber list, not the server clients list, so the lock
526 * is not needed here at all. */
527 shadow_subsystem_frame_update(&subsystem->base);
528 region16_clear(&(surface->invalidRegion));
529 return 1;
530}
531
532#if defined(WITH_WDS_API)
533
534static DWORD WINAPI win_shadow_subsystem_thread(LPVOID arg)
535{
536 winShadowSubsystem* subsystem = (winShadowSubsystem*)arg;
537 DWORD status;
538 DWORD nCount;
539 HANDLE events[32];
540 HANDLE StopEvent;
541 StopEvent = subsystem->base.server->StopEvent;
542 nCount = 0;
543 events[nCount++] = StopEvent;
544 events[nCount++] = subsystem->RdpUpdateEnterEvent;
545
546 while (1)
547 {
548 status = WaitForMultipleObjects(nCount, events, FALSE, INFINITE);
549
550 if (WaitForSingleObject(StopEvent, 0) == WAIT_OBJECT_0)
551 {
552 break;
553 }
554
555 if (WaitForSingleObject(subsystem->RdpUpdateEnterEvent, 0) == WAIT_OBJECT_0)
556 {
557 win_shadow_surface_copy(subsystem);
558 (void)ResetEvent(subsystem->RdpUpdateEnterEvent);
559 (void)SetEvent(subsystem->RdpUpdateLeaveEvent);
560 }
561 }
562
563 ExitThread(0);
564 return 0;
565}
566
567#elif defined(WITH_DXGI_1_2)
568
569static DWORD WINAPI win_shadow_subsystem_thread(LPVOID arg)
570{
571 winShadowSubsystem* subsystem = (winShadowSubsystem*)arg;
572 DWORD status;
573 DWORD nCount;
574 UINT64 cTime;
575 DWORD dwTimeout;
576 DWORD dwInterval;
577 UINT64 frameTime;
578 HANDLE events[32];
579 HANDLE StopEvent;
580 StopEvent = subsystem->base.server->StopEvent;
581 wMessagePipe* MsgPipe = subsystem->base.MsgPipe;
582 WINPR_ASSERT(MsgPipe);
583 nCount = 0;
584 events[nCount++] = StopEvent;
585 events[nCount++] = MessageQueue_Event(MsgPipe->In);
586 subsystem->base.captureFrameRate = 120;
587 dwInterval = 1000 / subsystem->base.captureFrameRate;
588 frameTime = GetTickCount64() + dwInterval;
589 BOOL refresh = FALSE;
590
591 while (1)
592 {
593 dwTimeout = INFINITE;
594 cTime = GetTickCount64();
595 dwTimeout = (DWORD)((cTime > frameTime) ? 0 : frameTime - cTime);
596 status = WaitForMultipleObjects(nCount, events, FALSE, dwTimeout);
597
598 if (WaitForSingleObject(StopEvent, 0) == WAIT_OBJECT_0)
599 {
600 break;
601 }
602
603 /* a client asks for the whole screen (its graphics channel is
604 * ready, or it asked to refresh), as the X11 subsystem answers */
605 wMessage message = WINPR_C_ARRAY_INIT;
606 BOOL quit = FALSE;
607 while (!quit && MessageQueue_Peek(MsgPipe->In, &message, TRUE))
608 {
609 quit = (message.id == WMQ_QUIT);
610 if (message.id == SHADOW_MSG_IN_REFRESH_REQUEST_ID)
611 refresh = TRUE;
612 if (message.Free)
613 message.Free(&message);
614 }
615 if (quit)
616 break;
617
618 if ((status == WAIT_TIMEOUT) || (GetTickCount64() > frameTime))
619 {
620 int dxgi_status = win_shadow_check_resize(subsystem);
621
622 /* apply a pending desktop display mode change before the
623 * restart/acquire below; on fatal resize error (<0) skip this
624 * round, the next tick retries */
625 if (dxgi_status >= 0)
626 {
627 /* a desktop switch (logon, lock screen, UAC), a refresh request,
628 * or no duplication yet: duplicate the output again on the input
629 * desktop, and send all of it. DXGI reports changes only: on a
630 * desktop that does not change, a new client would otherwise wait
631 * for its first picture until something moves; a new duplication
632 * starts with the whole desktop image. A missing duplication is
633 * retried from dxgiRetryTime on, not on every pass: every
634 * WIN_SHADOW_DXGI_DENIED_RETRY_MS while a desktop this process
635 * may not capture is active, every WIN_SHADOW_DXGI_INIT_RETRY_MS
636 * after a failure which does not recover by itself (no D3D11
637 * device, no duplicatable output) */
638 const BOOL followed = win_shadow_follow_input_desktop();
639 const BOOL retry = !subsystem->dxgiOutputDuplication &&
640 (GetTickCount64() >= subsystem->dxgiRetryTime);
641 if (followed || refresh || retry)
642 {
643 refresh = FALSE;
644 if (win_shadow_dxgi_restart_duplication(subsystem) > 0)
645 (void)win_shadow_invalidate_region(subsystem, 0, 0, subsystem->width,
646 subsystem->height);
647 }
648
649 /* the resize status only gates this block; without a duplication
650 * (capture paused, or a failed restart) there is no frame */
651 dxgi_status = subsystem->dxgiOutputDuplication
652 ? win_shadow_dxgi_get_next_frame(subsystem)
653 : 0;
654
655 /* a frame without dirty rectangles of its own (the first of a
656 * new duplication) still holds the whole desktop image: copy
657 * what is marked invalid already */
658 if (dxgi_status > 0)
659 {
660 const int changed = win_shadow_dxgi_get_invalid_region(subsystem);
661 rdpShadowSurface* surface = subsystem->base.server->surface;
662 WINPR_ASSERT(surface);
663 EnterCriticalSection(&(surface->lock));
664 const BOOL pending = !region16_is_empty(&(surface->invalidRegion));
665 LeaveCriticalSection(&(surface->lock));
666 dxgi_status = ((changed > 0) || ((changed == 0) && pending)) ? 1 : changed;
667 }
668 }
669
670 if (dxgi_status > 0)
671 win_shadow_surface_copy(subsystem);
672
673 /* Follow the encoder capture rate suggestion, like the X11 and Mac
674 * subsystems do: shadow_encoder_create_frame_id() lowers the
675 * preferred fps while frames are still in flight (not yet
676 * acknowledged by the client). This subsystem hardcoded the
677 * capture rate and never read the suggestion, so a slow client
678 * could not throttle the capture and in-flight frames (latency)
679 * accumulated without bound. Use the lowest suggestion of all
680 * connected clients. */
681 UINT32 captureFrameRate = UINT32_MAX;
682 ArrayList_Lock(subsystem->base.server->clients);
683 const UINT32 clientCount = ArrayList_Count(subsystem->base.server->clients);
684 for (UINT32 index = 0; index < clientCount; index++)
685 {
686 const rdpShadowClient* client =
687 (rdpShadowClient*)ArrayList_GetItem(subsystem->base.server->clients, index);
688
689 if (client && client->encoder)
690 {
691 const UINT32 suggestion = shadow_encoder_preferred_fps(client->encoder);
692 if (suggestion < captureFrameRate)
693 captureFrameRate = suggestion;
694 }
695 }
696 ArrayList_Unlock(subsystem->base.server->clients);
697
698 if ((captureFrameRate == UINT32_MAX) || (captureFrameRate < 1))
699 captureFrameRate = subsystem->base.captureFrameRate;
700 subsystem->base.captureFrameRate = captureFrameRate;
701
702 dwInterval = 1000 / subsystem->base.captureFrameRate;
703 frameTime += dwInterval;
704 }
705 }
706
707 ExitThread(0);
708 return 0;
709}
710
711#endif
712
713static UINT32 win_shadow_enum_monitors(MONITOR_DEF* monitors, UINT32 maxMonitors)
714{
715 HDC hdc;
716 int index;
717 int desktopWidth;
718 int desktopHeight;
719 DWORD iDevNum = 0;
720 int numMonitors = 0;
721 MONITOR_DEF* monitor;
722 DISPLAY_DEVICE displayDevice = WINPR_C_ARRAY_INIT;
723
724 displayDevice.cb = sizeof(DISPLAY_DEVICE);
725
726 /* Skip adapters which are not attached to the desktop: on machines with
727 * a BMC/management GPU (e.g. ASPEED) listed as device 0 while the real GPU
728 * is attached later in the list, CreateDC for the unattached device fails
729 * and the monitor rectangle stays zeroed, which results in a 1x1 surface. */
730 {
731 DWORD devNum = 0;
732 BOOL attached = FALSE;
733 DISPLAY_DEVICE probe = WINPR_C_ARRAY_INIT;
734 probe.cb = sizeof(DISPLAY_DEVICE);
735 while (EnumDisplayDevices(nullptr, devNum, &probe, 0))
736 {
737 if (probe.StateFlags & DISPLAY_DEVICE_ATTACHED_TO_DESKTOP)
738 {
739 attached = TRUE;
740 break;
741 }
742 devNum++;
743 probe.cb = sizeof(DISPLAY_DEVICE);
744 }
745 if (attached && (devNum != 0))
746 {
747 displayDevice.cb = sizeof(DISPLAY_DEVICE);
748 if (EnumDisplayDevices(nullptr, devNum, &displayDevice, 0))
749 iDevNum = devNum;
750 }
751 }
752
753 if (EnumDisplayDevices(nullptr, iDevNum, &displayDevice, 0))
754 {
755 hdc = CreateDC(displayDevice.DeviceName, nullptr, nullptr, nullptr);
756 desktopWidth = GetDeviceCaps(hdc, HORZRES);
757 desktopHeight = GetDeviceCaps(hdc, VERTRES);
758 index = 0;
759 numMonitors = 1;
760 monitor = &monitors[index];
761 monitor->left = 0;
762 monitor->top = 0;
763 /* right and bottom are inclusive, as the shadow server reads them */
764 monitor->right = desktopWidth - 1;
765 monitor->bottom = desktopHeight - 1;
766 monitor->flags = 1;
767 DeleteDC(hdc);
768 }
769
770 /* Fallback to the primary screen metrics if enumeration did not
771 * yield a usable rectangle, otherwise shadow_screen_new computes a
772 * 1x1 surface and clients are resized to 1x1. */
773 if ((numMonitors == 0) || (monitors[0].right <= 0) || (monitors[0].bottom <= 0))
774 {
775 monitor = &monitors[0];
776 monitor->left = 0;
777 monitor->top = 0;
778 monitor->right = GetSystemMetrics(SM_CXSCREEN) - 1;
779 monitor->bottom = GetSystemMetrics(SM_CYSCREEN) - 1;
780 monitor->flags = 1;
781 numMonitors = 1;
782 }
783
784 return numMonitors;
785}
786
787/* Win counterpart of x11_shadow_check_resize: track desktop display mode
788 * changes while the server is running. Returns 1 when the geometry
789 * changed, 0 when it did not and <0 on fatal error. */
790int win_shadow_check_resize(winShadowSubsystem* subsystem)
791{
792 WINPR_ASSERT(subsystem);
793
794 DEVMODEW dm = WINPR_C_ARRAY_INIT;
795 dm.dmSize = (WORD)sizeof(dm);
796
797 if (!EnumDisplaySettingsW(nullptr, ENUM_CURRENT_SETTINGS, &dm))
798 return 0;
799
800 /* compare unsigned: an int cast would wrap resolutions above INT_MAX */
801 if (((UINT32)dm.dmPelsWidth == (UINT32)subsystem->width) &&
802 ((UINT32)dm.dmPelsHeight == (UINT32)subsystem->height))
803 return 0;
804
805 MONITOR_DEF* virtualScreen = &(subsystem->base.virtualScreen);
806
807 /* refresh the monitor definition; a false alarm from a non-primary
808 * device bails out below to avoid per-tick flapping */
809 subsystem->base.numMonitors = win_shadow_enum_monitors(subsystem->base.monitors, 16);
810
811 if (subsystem->base.numMonitors < 1)
812 return 0;
813
814 {
815 const MONITOR_DEF* primary = &subsystem->base.monitors[0];
816 const int newWidth = primary->right - primary->left + 1;
817 const int newHeight = primary->bottom - primary->top + 1;
818
819 if ((newWidth == subsystem->width) && (newHeight == subsystem->height))
820 return 0;
821
822 /* The screen does not exist yet while the subsystem is initialized;
823 * in that case updating the geometry below is all that is needed. */
824 if (subsystem->base.server && subsystem->base.server->screen &&
825 !shadow_screen_resize(subsystem->base.server->screen))
826 return -1;
827
828 subsystem->width = newWidth;
829 subsystem->height = newHeight;
830 }
831
832 virtualScreen->left = 0;
833 virtualScreen->top = 0;
834 virtualScreen->right = subsystem->width - 1;
835 virtualScreen->bottom = subsystem->height - 1;
836 virtualScreen->flags = 1;
837
838 WLog_INFO(TAG, "desktop resized to %dx%d", subsystem->width, subsystem->height);
839 return 1;
840}
841
842static BOOL win_shadow_client_connect(rdpShadowSubsystem* subsystem, rdpShadowClient* client)
843{
844 WINPR_ASSERT(subsystem);
845 WINPR_ASSERT(client);
846
847#if defined(CHANNEL_CLIPRDR_SERVER)
848 /* cliprdr is a static channel: the join decision is final by the time
849 * ClientConnect runs (shadow_client_channels_post_connect precedes it). */
850 if (WTSVirtualChannelManagerIsChannelJoined(client->vcm, CLIPRDR_SVC_CHANNEL_NAME))
851 {
852 if (win_shadow_cliprdr_init(client) < 0)
853 return FALSE;
854 }
855#endif
856 return TRUE;
857}
858
859static void win_shadow_client_disconnect(rdpShadowSubsystem* subsystem, rdpShadowClient* client)
860{
861 WINPR_ASSERT(subsystem);
862 WINPR_ASSERT(client);
863
864#if defined(CHANNEL_CLIPRDR_SERVER)
865 win_shadow_cliprdr_uninit(client);
866#endif
867}
868
869static int win_shadow_subsystem_init(rdpShadowSubsystem* arg)
870{
871 winShadowSubsystem* subsystem = (winShadowSubsystem*)arg;
872 int status;
873 MONITOR_DEF* virtualScreen;
874 win_shadow_check_input_integrity();
875 subsystem->base.numMonitors = win_shadow_enum_monitors(subsystem->base.monitors, 16);
876 if (subsystem->base.numMonitors > 0)
877 {
878 const MONITOR_DEF* primary = &subsystem->base.monitors[0];
879 subsystem->width = primary->right - primary->left + 1;
880 subsystem->height = primary->bottom - primary->top + 1;
881 }
882
883 if ((subsystem->width <= 0) || (subsystem->height <= 0))
884 {
885 subsystem->width = GetSystemMetrics(SM_CXSCREEN);
886 subsystem->height = GetSystemMetrics(SM_CYSCREEN);
887 }
888#if defined(WITH_WDS_API)
889 /* The Windows Desktop Sharing API is the low performance capture path:
890 * its throughput degrades with multiple clients and high frame rates.
891 * Which capture backend a binary carries is decided at build time (the
892 * alternative, DXGI desktop duplication, is WITH_SHADOW_WIN_DXGI=ON),
893 * so a binary holding only the slow path would degrade silently: say
894 * what was built in. */
895 WLog_WARN(TAG,
896 "capture uses the Windows Desktop Sharing API: the low performance capture path, "
897 "throughput drops with multiple clients or high frame rates - build with "
898 "WITH_SHADOW_WIN_DXGI=ON for DXGI desktop duplication");
899 status = win_shadow_wds_init(subsystem);
900#elif defined(WITH_DXGI_1_2)
901 status = win_shadow_dxgi_init(subsystem);
902 if (status < 0)
903 WLog_WARN(TAG, "DXGI init failed, the capture thread retries at a low rate");
904#endif
905 virtualScreen = &(subsystem->base.virtualScreen);
906 virtualScreen->left = 0;
907 virtualScreen->top = 0;
908 virtualScreen->right = subsystem->width - 1;
909 virtualScreen->bottom = subsystem->height - 1;
910 virtualScreen->flags = 1;
911 WLog_INFO(TAG, "width: %d height: %d", subsystem->width, subsystem->height);
912
913 if (!win_shadow_audio_init(subsystem))
914 WLog_WARN(TAG, "audio: no formats to offer: no sound");
915 return 1;
916}
917
918static int win_shadow_subsystem_uninit(rdpShadowSubsystem* arg)
919{
920 winShadowSubsystem* subsystem = (winShadowSubsystem*)arg;
921
922 if (!subsystem)
923 return -1;
924
925 win_shadow_audio_uninit(subsystem);
926#if defined(WITH_WDS_API)
927 win_shadow_wds_uninit(subsystem);
928#elif defined(WITH_DXGI_1_2)
929 win_shadow_dxgi_uninit(subsystem);
930#endif
931 return 1;
932}
933
934static int win_shadow_subsystem_start(rdpShadowSubsystem* arg)
935{
936 winShadowSubsystem* subsystem = (winShadowSubsystem*)arg;
937 HANDLE thread;
938
939 if (!subsystem)
940 return -1;
941
942 if (!(thread =
943 CreateThread(nullptr, 0, win_shadow_subsystem_thread, (void*)subsystem, 0, nullptr)))
944 {
945 WLog_ERR(TAG, "Failed to create thread");
946 return -1;
947 }
948
949 /* the picture works without the sound */
950 if (!win_shadow_audio_start(subsystem))
951 WLog_WARN(TAG, "audio: no capture thread: no sound");
952 return 1;
953}
954
955static int win_shadow_subsystem_stop(rdpShadowSubsystem* arg)
956{
957 winShadowSubsystem* subsystem = (winShadowSubsystem*)arg;
958
959 if (!subsystem)
960 return -1;
961
962 win_shadow_audio_stop(subsystem);
963 return 1;
964}
965
966static void win_shadow_subsystem_free(rdpShadowSubsystem* arg)
967{
968 winShadowSubsystem* subsystem = (winShadowSubsystem*)arg;
969
970 if (!subsystem)
971 return;
972
973 win_shadow_subsystem_uninit(arg);
974 free(subsystem);
975}
976
977static rdpShadowSubsystem* win_shadow_subsystem_new(void)
978{
979 winShadowSubsystem* subsystem;
980 subsystem = (winShadowSubsystem*)calloc(1, sizeof(winShadowSubsystem));
981
982 if (!subsystem)
983 return nullptr;
984
985 subsystem->base.SynchronizeEvent = win_shadow_input_synchronize_event;
986 subsystem->base.KeyboardEvent = win_shadow_input_keyboard_event;
987 subsystem->base.UnicodeKeyboardEvent = win_shadow_input_unicode_keyboard_event;
988 subsystem->base.MouseEvent = win_shadow_input_mouse_event;
989 subsystem->base.ExtendedMouseEvent = win_shadow_input_extended_mouse_event;
990 subsystem->base.ClientConnect = win_shadow_client_connect;
991 subsystem->base.ClientDisconnect = win_shadow_client_disconnect;
992 return &subsystem->base;
993}
994
995FREERDP_API const char* ShadowSubsystemName(void)
996{
997 return "Win";
998}
999
1000FREERDP_API int ShadowSubsystemEntry(RDP_SHADOW_ENTRY_POINTS* pEntryPoints)
1001{
1002 char name[] = "windows shadow subsystem";
1003 char* arg[] = { name };
1004
1005 freerdp_server_warn_unmaintained(ARRAYSIZE(arg), arg);
1006 pEntryPoints->New = win_shadow_subsystem_new;
1007 pEntryPoints->Free = win_shadow_subsystem_free;
1008 pEntryPoints->Init = win_shadow_subsystem_init;
1009 pEntryPoints->Uninit = win_shadow_subsystem_uninit;
1010 pEntryPoints->Start = win_shadow_subsystem_start;
1011 pEntryPoints->Stop = win_shadow_subsystem_stop;
1012 pEntryPoints->EnumMonitors = win_shadow_enum_monitors;
1013 return 1;
1014}