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