FreeRDP
Loading...
Searching...
No Matches
wf_client.c
1
22#include <freerdp/config.h>
23
24#include <winpr/windows.h>
25#include <winpr/library.h>
26
27#include <winpr/crt.h>
28#include <winpr/assert.h>
29
30#include <errno.h>
31#include <stdio.h>
32#include <stdlib.h>
33#include <string.h>
34#include <tchar.h>
35#include <winpr/assert.h>
36#include <sys/types.h>
37#include <io.h>
38
39#ifdef WITH_PROGRESS_BAR
40#include <shobjidl.h>
41#endif
42
43#ifdef WITH_WINDOWS_CERT_STORE
44#include <wincrypt.h>
45#endif
46
47#include <freerdp/log.h>
48#include <freerdp/freerdp.h>
49#include <freerdp/constants.h>
50#include <freerdp/settings.h>
51
52#include <freerdp/locale/locale.h>
53#include <freerdp/locale/keyboard.h>
54#include <freerdp/codec/region.h>
55#include <freerdp/client.h>
56#include <freerdp/client/cmdline.h>
57#include <freerdp/client/channels.h>
58#include <freerdp/client/aad_helper.h>
59#include <freerdp/channels/channels.h>
60#include <freerdp/utils/signal.h>
61
62#include "wf_gdi.h"
63#include "wf_rail.h"
64#include "wf_channels.h"
65#include "wf_graphics.h"
66
67#include "resource/resource.h"
68
69#define TAG CLIENT_TAG("windows")
70
71#define WM_FREERDP_SHOWWINDOW (WM_USER + 100)
72
73static BOOL wf_has_console(void)
74{
75#ifdef WITH_WIN_CONSOLE
76 int file = _fileno(stdin);
77 int tty = _isatty(file);
78 DWORD processes[2] = WINPR_C_ARRAY_INIT;
79 const DWORD count = GetConsoleProcessList(processes, ARRAYSIZE(processes));
80 const BOOL inherited = (count > 1);
81#else
82 int file = -1;
83 int tty = 0;
84 BOOL inherited = FALSE;
85#endif
86
87 WLog_INFO(TAG, "Detected stdin=%d -> %s mode", file, (tty && inherited) ? "console" : "gui");
88 return tty && inherited;
89}
90
91static BOOL wf_end_paint(rdpContext* context)
92{
93 RECT updateRect = WINPR_C_ARRAY_INIT;
94 REGION16 invalidRegion = WINPR_C_ARRAY_INIT;
95 RECTANGLE_16 invalidRect = WINPR_C_ARRAY_INIT;
96 const RECTANGLE_16* extents = nullptr;
97
98 WINPR_ASSERT(context);
99
100 wfContext* wfc = (wfContext*)context;
101 rdpGdi* gdi = context->gdi;
102 WINPR_ASSERT(gdi);
103
104 HGDI_DC hdc = gdi->primary->hdc;
105 WINPR_ASSERT(hdc);
106 if (!hdc->hwnd)
107 return TRUE;
108
109 HGDI_WND hwnd = hdc->hwnd;
110 WINPR_ASSERT(hwnd->invalid || (hwnd->ninvalid == 0));
111
112 if (hwnd->invalid->null)
113 return TRUE;
114
115 const int ninvalid = hwnd->ninvalid;
116 const HGDI_RGN cinvalid = hwnd->cinvalid;
117
118 if (ninvalid < 1)
119 return TRUE;
120
121 region16_init(&invalidRegion);
122
123 for (int i = 0; i < ninvalid; i++)
124 {
125 invalidRect.left = cinvalid[i].x;
126 invalidRect.top = cinvalid[i].y;
127 invalidRect.right = cinvalid[i].x + cinvalid[i].w;
128 invalidRect.bottom = cinvalid[i].y + cinvalid[i].h;
129 region16_union_rect(&invalidRegion, &invalidRegion, &invalidRect);
130 }
131
132 if (!region16_is_empty(&invalidRegion))
133 {
134 extents = region16_extents(&invalidRegion);
135 updateRect.left = extents->left;
136 updateRect.top = extents->top;
137 updateRect.right = extents->right;
138 updateRect.bottom = extents->bottom;
139
140 wf_scale_rect(wfc, &updateRect);
141
142 InvalidateRect(wfc->hwnd, &updateRect, FALSE);
143
144 if (wfc->rail)
145 wf_rail_invalidate_region(wfc, &invalidRegion);
146 }
147
148 region16_uninit(&invalidRegion);
149
150 if (!wfc->is_shown)
151 {
152 wfc->is_shown = TRUE;
153
154#ifdef WITH_PROGRESS_BAR
155 if (wfc->taskBarList)
156 {
157 wfc->taskBarList->lpVtbl->SetProgressState(wfc->taskBarList, wfc->hwnd,
158 TBPF_NOPROGRESS);
159 }
160#endif
161
162 PostMessage(wfc->hwnd, WM_FREERDP_SHOWWINDOW, 0, 0);
163 WLog_INFO(TAG, "Window is shown!");
164 }
165 return TRUE;
166}
167
168static BOOL wf_begin_paint(rdpContext* context)
169{
170 HGDI_DC hdc;
171
172 if (!context || !context->gdi || !context->gdi->primary || !context->gdi->primary->hdc)
173 return FALSE;
174
175 hdc = context->gdi->primary->hdc;
176
177 if (!hdc || !hdc->hwnd || !hdc->hwnd->invalid)
178 return FALSE;
179
180 hdc->hwnd->invalid->null = TRUE;
181 hdc->hwnd->ninvalid = 0;
182 return TRUE;
183}
184
185static BOOL wf_desktop_resize(rdpContext* context)
186{
187 BOOL same;
188 RECT rect;
189 rdpSettings* settings;
190 wfContext* wfc = (wfContext*)context;
191
192 if (!context || !context->settings)
193 return FALSE;
194
195 settings = context->settings;
196
197 if (wfc->primary)
198 {
199 same = (wfc->primary == wfc->drawing) ? TRUE : FALSE;
200 wf_image_free(wfc->primary);
201 wfc->primary =
202 wf_image_new(wfc, freerdp_settings_get_uint32(settings, FreeRDP_DesktopWidth),
203 freerdp_settings_get_uint32(settings, FreeRDP_DesktopHeight),
204 context->gdi->dstFormat, nullptr);
205 }
206
207 if (!gdi_resize_ex(context->gdi, freerdp_settings_get_uint32(settings, FreeRDP_DesktopWidth),
208 freerdp_settings_get_uint32(settings, FreeRDP_DesktopHeight), 0,
209 context->gdi->dstFormat, wfc->primary->pdata, nullptr))
210 return FALSE;
211
212 if (same)
213 wfc->drawing = wfc->primary;
214
215 if (wfc->fullscreen != TRUE)
216 {
217 if (wfc->hwnd && !freerdp_settings_get_bool(settings, FreeRDP_SmartSizing))
218 SetWindowPos(wfc->hwnd, HWND_TOP, -1, -1,
219 freerdp_settings_get_uint32(settings, FreeRDP_DesktopWidth) + wfc->diff.x,
220 freerdp_settings_get_uint32(settings, FreeRDP_DesktopHeight) + wfc->diff.y,
221 SWP_NOMOVE);
222 }
223 else
224 {
225 wf_update_offset(wfc);
226 GetWindowRect(wfc->hwnd, &rect);
227 InvalidateRect(wfc->hwnd, &rect, TRUE);
228 }
229
230 return TRUE;
231}
232
233static BOOL wf_pre_connect(freerdp* instance)
234{
235 WINPR_ASSERT(instance);
236 WINPR_ASSERT(instance->context);
237 WINPR_ASSERT(instance->context->settings);
238
239 rdpContext* context = instance->context;
240 wfContext* wfc = (wfContext*)instance->context;
241 rdpSettings* settings = context->settings;
242 if (!freerdp_settings_set_uint32(settings, FreeRDP_OsMajorType, OSMAJORTYPE_WINDOWS))
243 return FALSE;
244 if (!freerdp_settings_set_uint32(settings, FreeRDP_OsMinorType, OSMINORTYPE_WINDOWS_NT))
245 return FALSE;
246 wfc->fullscreen = freerdp_settings_get_bool(settings, FreeRDP_Fullscreen);
247 wfc->fullscreen_toggle = freerdp_settings_get_bool(settings, FreeRDP_ToggleFullscreen);
248 UINT32 desktopWidth = freerdp_settings_get_uint32(settings, FreeRDP_DesktopWidth);
249 UINT32 desktopHeight = freerdp_settings_get_uint32(settings, FreeRDP_DesktopHeight);
250
251 if (wfc->percentscreen > 0)
252 {
253 desktopWidth = (GetSystemMetrics(SM_CXSCREEN) * wfc->percentscreen) / 100;
254 if (!freerdp_settings_set_uint32(settings, FreeRDP_DesktopWidth, desktopWidth))
255 return FALSE;
256 desktopHeight = (GetSystemMetrics(SM_CYSCREEN) * wfc->percentscreen) / 100;
257 if (!freerdp_settings_set_uint32(settings, FreeRDP_DesktopHeight, desktopHeight))
258 return FALSE;
259 }
260
261 if (wfc->fullscreen)
262 {
263 if (freerdp_settings_get_bool(settings, FreeRDP_UseMultimon))
264 {
265 desktopWidth = GetSystemMetrics(SM_CXVIRTUALSCREEN);
266 desktopHeight = GetSystemMetrics(SM_CYVIRTUALSCREEN);
267 }
268 else
269 {
270 desktopWidth = GetSystemMetrics(SM_CXSCREEN);
271 desktopHeight = GetSystemMetrics(SM_CYSCREEN);
272 }
273 }
274
275 /* FIXME: desktopWidth has a limitation that it should be divisible by 4,
276 * otherwise the screen will crash when connecting to an XP desktop.*/
277 desktopWidth = (desktopWidth + 3) & (~3);
278
279 if (desktopWidth != freerdp_settings_get_uint32(settings, FreeRDP_DesktopWidth))
280 {
281 if (!freerdp_settings_set_uint32(settings, FreeRDP_DesktopWidth, desktopWidth))
282 return FALSE;
283 }
284
285 if (desktopHeight != freerdp_settings_get_uint32(settings, FreeRDP_DesktopHeight))
286 {
287 if (!freerdp_settings_set_uint32(settings, FreeRDP_DesktopHeight, desktopHeight))
288 return FALSE;
289 }
290
291 DWORD keyboardLayoutId = freerdp_settings_get_uint32(settings, FreeRDP_KeyboardLayout);
292
293 {
294 CHAR name[KL_NAMELENGTH + 1] = WINPR_C_ARRAY_INIT;
295 if (GetKeyboardLayoutNameA(name))
296 {
297 ULONG rc = 0;
298
299 errno = 0;
300 rc = strtoul(name, nullptr, 16);
301 if (errno == 0)
302 keyboardLayoutId = rc;
303 }
304
305 if (keyboardLayoutId == 0)
306 {
307 const HKL layout = GetKeyboardLayout(0);
308 const uint32_t masked = (uint32_t)(((uintptr_t)layout >> 16) & 0xFFFF);
309 keyboardLayoutId = masked;
310 }
311 }
312
313 if (keyboardLayoutId == 0)
314 freerdp_detect_keyboard_layout_from_system_locale(&keyboardLayoutId);
315 if (keyboardLayoutId == 0)
316 keyboardLayoutId = ENGLISH_UNITED_STATES;
317 if (!freerdp_settings_set_uint32(settings, FreeRDP_KeyboardLayout, keyboardLayoutId))
318 return FALSE;
319 PubSub_SubscribeChannelConnected(instance->context->pubSub, wf_OnChannelConnectedEventHandler);
320 PubSub_SubscribeChannelDisconnected(instance->context->pubSub,
321 wf_OnChannelDisconnectedEventHandler);
322 return TRUE;
323}
324
325static void wf_append_item_to_system_menu(HMENU hMenu, UINT fMask, UINT wID, const wchar_t* text,
326 wfContext* wfc)
327{
328 MENUITEMINFO item_info = WINPR_C_ARRAY_INIT;
329 item_info.fMask = fMask;
330 item_info.cbSize = sizeof(MENUITEMINFO);
331 item_info.wID = wID;
332 item_info.fType = MFT_STRING;
333 item_info.dwTypeData = _wcsdup(text);
334 item_info.cch = (UINT)_wcslen(text);
335 if (wfc)
336 item_info.dwItemData = (ULONG_PTR)wfc;
337 InsertMenuItem(hMenu, wfc->systemMenuInsertPosition++, TRUE, &item_info);
338}
339
340static void wf_add_system_menu(wfContext* wfc)
341{
342 HMENU hMenu;
343
344 if (wfc->fullscreen && !wfc->fullscreen_toggle)
345 {
346 return;
347 }
348
349 if (freerdp_settings_get_bool(wfc->common.context.settings, FreeRDP_DynamicResolutionUpdate))
350 {
351 return;
352 }
353
354 hMenu = GetSystemMenu(wfc->hwnd, FALSE);
355
356 wf_append_item_to_system_menu(hMenu,
357 MIIM_CHECKMARKS | MIIM_FTYPE | MIIM_ID | MIIM_STRING | MIIM_DATA,
358 SYSCOMMAND_ID_SMARTSIZING, L"Smart sizing", wfc);
359
360 if (freerdp_settings_get_bool(wfc->common.context.settings, FreeRDP_SmartSizing))
361 {
362 CheckMenuItem(hMenu, SYSCOMMAND_ID_SMARTSIZING, MF_CHECKED);
363 }
364
365 if (freerdp_settings_get_bool(wfc->common.context.settings, FreeRDP_RemoteAssistanceMode))
366 wf_append_item_to_system_menu(hMenu, MIIM_FTYPE | MIIM_ID | MIIM_STRING,
367 SYSCOMMAND_ID_REQUEST_CONTROL, L"Request control", wfc);
368}
369
370static WCHAR* wf_window_get_title(rdpSettings* settings)
371{
372 BOOL port;
373 WCHAR* windowTitle = nullptr;
374 size_t size;
375 WCHAR prefix[] = L"FreeRDP:";
376
377 if (!settings)
378 return nullptr;
379
380 const char* name = freerdp_settings_get_string(settings, FreeRDP_ServerHostname);
381
382 if (freerdp_settings_get_string(settings, FreeRDP_WindowTitle))
383 return ConvertUtf8ToWCharAlloc(freerdp_settings_get_string(settings, FreeRDP_WindowTitle),
384 nullptr);
385
386 port = (freerdp_settings_get_uint32(settings, FreeRDP_ServerPort) != 3389);
387 size = strlen(name) + 16 + wcslen(prefix);
388 windowTitle = calloc(size, sizeof(WCHAR));
389
390 if (!windowTitle)
391 return nullptr;
392
393 if (!port)
394 _snwprintf_s(windowTitle, size, _TRUNCATE, L"%s %S", prefix, name);
395 else
396 _snwprintf_s(windowTitle, size, _TRUNCATE, L"%s %S:%u", prefix, name,
397 freerdp_settings_get_uint32(settings, FreeRDP_ServerPort));
398
399 return windowTitle;
400}
401
402static BOOL wf_post_connect(freerdp* instance)
403{
404 rdpGdi* gdi;
405 DWORD dwStyle;
406 rdpCache* cache;
407 wfContext* wfc;
408 rdpContext* context;
409 rdpSettings* settings;
410 EmbedWindowEventArgs e;
411 const UINT32 format = PIXEL_FORMAT_BGRX32;
412
413 WINPR_ASSERT(instance);
414
415 context = instance->context;
416 WINPR_ASSERT(context);
417
418 settings = context->settings;
419 WINPR_ASSERT(settings);
420
421 wfc = (wfContext*)instance->context;
422 WINPR_ASSERT(wfc);
423
424 wfc->primary =
425 wf_image_new(wfc, freerdp_settings_get_uint32(settings, FreeRDP_DesktopWidth),
426 freerdp_settings_get_uint32(settings, FreeRDP_DesktopHeight), format, nullptr);
427
428 if (!wfc->primary)
429 {
430 WLog_ERR(TAG, "Failed to allocate primary surface");
431 return FALSE;
432 }
433
434 if (!gdi_init_ex(instance, format, 0, wfc->primary->pdata, nullptr))
435 return FALSE;
436
437 cache = instance->context->cache;
438 WINPR_ASSERT(cache);
439
440 gdi = instance->context->gdi;
441
442 if (!freerdp_settings_get_bool(settings, FreeRDP_SoftwareGdi))
443 {
444 wf_gdi_register_update_callbacks(context->update);
445 }
446
447 wfc->window_title = wf_window_get_title(settings);
448
449 if (!wfc->window_title)
450 return FALSE;
451
452 if (freerdp_settings_get_bool(settings, FreeRDP_EmbeddedWindow))
453 {
454 if (!freerdp_settings_set_bool(settings, FreeRDP_Decorations, FALSE))
455 return FALSE;
456 }
457
458 if (wfc->fullscreen)
459 dwStyle = WS_POPUP;
460 else if (!freerdp_settings_get_bool(settings, FreeRDP_Decorations))
461 dwStyle = WS_CHILD | WS_BORDER;
462 else
463 dwStyle =
464 WS_CAPTION | WS_OVERLAPPED | WS_SYSMENU | WS_MINIMIZEBOX | WS_SIZEBOX | WS_MAXIMIZEBOX;
465
466 if (!wfc->hwnd)
467 {
468 wfc->hwnd = CreateWindowEx(0, wfc->wndClassName, wfc->window_title, dwStyle, 0, 0, 0, 0,
469 wfc->hWndParent, nullptr, wfc->hInstance, nullptr);
470 if (!wfc->hwnd)
471 {
472 WLog_ERR(TAG, "CreateWindowEx failed with error: %lu", GetLastError());
473 return FALSE;
474 }
475 SetWindowLongPtr(wfc->hwnd, GWLP_USERDATA, (LONG_PTR)wfc);
476 }
477
478 wf_resize_window(wfc);
479 wf_add_system_menu(wfc);
480 BitBlt(wfc->primary->hdc, 0, 0, freerdp_settings_get_uint32(settings, FreeRDP_DesktopWidth),
481 freerdp_settings_get_uint32(settings, FreeRDP_DesktopHeight), nullptr, 0, 0, BLACKNESS);
482 wfc->drawing = wfc->primary;
483 EventArgsInit(&e, "wfreerdp");
484 e.embed = FALSE;
485 e.handle = (void*)wfc->hwnd;
486 if (PubSub_OnEmbedWindow(context->pubSub, context, &e) < 0)
487 return FALSE;
488
489#ifdef WITH_PROGRESS_BAR
490 if (wfc->taskBarList)
491 {
492 ShowWindow(wfc->hwnd, SW_SHOWMINIMIZED);
493 wfc->taskBarList->lpVtbl->SetProgressState(wfc->taskBarList, wfc->hwnd, TBPF_INDETERMINATE);
494 }
495#endif
496 UpdateWindow(wfc->hwnd);
497 context->update->BeginPaint = wf_begin_paint;
498 context->update->DesktopResize = wf_desktop_resize;
499 context->update->EndPaint = wf_end_paint;
500 context->update->SetKeyboardIndicators = wf_keyboard_set_indicators;
501 wf_register_pointer(context->graphics);
502
503 wfc->floatbar = wf_floatbar_new(wfc, wfc->hInstance,
504 freerdp_settings_get_uint32(settings, FreeRDP_Floatbar));
505
506 wf_event_focus_in(wfc);
507
508 return TRUE;
509}
510
511static void wf_post_disconnect(freerdp* instance)
512{
513 wfContext* wfc;
514
515 if (!instance || !instance->context)
516 return;
517
518 wfc = (wfContext*)instance->context;
519 free(wfc->window_title);
520}
521
522static CREDUI_INFOW wfUiInfo = { sizeof(CREDUI_INFOW), nullptr, L"Enter your credentials",
523 L"Remote Desktop Security", nullptr };
524
525static BOOL wf_authenticate_ex(freerdp* instance, char** username, char** password, char** domain,
526 rdp_auth_reason reason)
527{
528 wfContext* wfc;
529 BOOL fSave;
530 DWORD status;
531 DWORD dwFlags;
532 WCHAR UserNameW[CREDUI_MAX_USERNAME_LENGTH + 1] = WINPR_C_ARRAY_INIT;
533 WCHAR UserW[CREDUI_MAX_USERNAME_LENGTH + 1] = WINPR_C_ARRAY_INIT;
534 WCHAR DomainW[CREDUI_MAX_DOMAIN_TARGET_LENGTH + 1] = WINPR_C_ARRAY_INIT;
535 WCHAR PasswordW[CREDUI_MAX_PASSWORD_LENGTH + 1] = WINPR_C_ARRAY_INIT;
536
537 WINPR_ASSERT(instance);
538 WINPR_ASSERT(instance->context);
539 WINPR_ASSERT(instance->context->settings);
540
541 wfc = (wfContext*)instance->context;
542 WINPR_ASSERT(wfc);
543
544 WINPR_ASSERT(username);
545 WINPR_ASSERT(domain);
546 WINPR_ASSERT(password);
547
548 /* /from-stdin: delegate to the common CLI handler instead of prompting via GUI.
549 * Settings are fully parsed by the time auth callbacks fire, so this branch
550 * reliably observes the user's choice. */
551 if (freerdp_settings_get_bool(instance->context->settings, FreeRDP_CredentialsFromStdin))
552 return client_cli_authenticate_ex(instance, username, password, domain, reason);
553
554 const WCHAR auth[] = L"Target credentials requested";
555 const WCHAR authPin[] = L"PIN requested";
556 const WCHAR gwAuth[] = L"Gateway credentials requested";
557 const WCHAR* titleW = auth;
558
559 fSave = FALSE;
560 dwFlags = CREDUI_FLAGS_DO_NOT_PERSIST | CREDUI_FLAGS_EXCLUDE_CERTIFICATES |
561 CREDUI_FLAGS_USERNAME_TARGET_CREDENTIALS;
562 switch (reason)
563 {
564 case AUTH_NLA:
565 break;
566 case AUTH_TLS:
567 case AUTH_RDP:
568 if ((*username) && (*password))
569 return TRUE;
570 break;
571 case AUTH_SMARTCARD_PIN:
572 dwFlags &= ~CREDUI_FLAGS_USERNAME_TARGET_CREDENTIALS;
573 dwFlags |= CREDUI_FLAGS_PASSWORD_ONLY_OK | CREDUI_FLAGS_KEEP_USERNAME;
574 titleW = authPin;
575 if (*password)
576 return TRUE;
577 if (!(*username))
578 *username = _strdup("PIN");
579 break;
580 case GW_AUTH_HTTP:
581 case GW_AUTH_RDG:
582 case GW_AUTH_RPC:
583 titleW = gwAuth;
584 break;
585 default:
586 return FALSE;
587 }
588
589 if (*username)
590 {
591 (void)ConvertUtf8ToWChar(*username, UserNameW, ARRAYSIZE(UserNameW));
592 (void)ConvertUtf8ToWChar(*username, UserW, ARRAYSIZE(UserW));
593 }
594
595 if (*password)
596 (void)ConvertUtf8ToWChar(*password, PasswordW, ARRAYSIZE(PasswordW));
597
598 if (*domain)
599 (void)ConvertUtf8ToWChar(*domain, DomainW, ARRAYSIZE(DomainW));
600
601 if (_wcsnlen(PasswordW, ARRAYSIZE(PasswordW)) == 0)
602 {
603 if (!wfc->isConsole &&
604 freerdp_settings_get_bool(wfc->common.context.settings, FreeRDP_CredentialsFromStdin))
605 WLog_ERR(TAG, "Flag for stdin read present but stdin is redirected; using GUI");
606 if (wfc->isConsole &&
607 freerdp_settings_get_bool(wfc->common.context.settings, FreeRDP_CredentialsFromStdin))
608 status = CredUICmdLinePromptForCredentialsW(titleW, nullptr, 0, UserNameW,
609 ARRAYSIZE(UserNameW), PasswordW,
610 ARRAYSIZE(PasswordW), &fSave, dwFlags);
611 else
612 status = CredUIPromptForCredentialsW(&wfUiInfo, titleW, nullptr, 0, UserNameW,
613 ARRAYSIZE(UserNameW), PasswordW,
614 ARRAYSIZE(PasswordW), &fSave, dwFlags);
615 if (status != NO_ERROR)
616 {
617 WLog_ERR(TAG, "CredUIPromptForCredentials unexpected status: 0x%08lX", status);
618 return FALSE;
619 }
620
621 if ((dwFlags & CREDUI_FLAGS_KEEP_USERNAME) == 0)
622 {
623 status = CredUIParseUserNameW(UserNameW, UserW, ARRAYSIZE(UserW), DomainW,
624 ARRAYSIZE(DomainW));
625 if (status != NO_ERROR)
626 {
627 CHAR User[CREDUI_MAX_USERNAME_LENGTH + 1] = WINPR_C_ARRAY_INIT;
628 CHAR UserName[CREDUI_MAX_USERNAME_LENGTH + 1] = WINPR_C_ARRAY_INIT;
629 CHAR Domain[CREDUI_MAX_DOMAIN_TARGET_LENGTH + 1] = WINPR_C_ARRAY_INIT;
630
631 (void)ConvertWCharNToUtf8(UserNameW, ARRAYSIZE(UserNameW), UserName,
632 ARRAYSIZE(UserName));
633 (void)ConvertWCharNToUtf8(UserW, ARRAYSIZE(UserW), User, ARRAYSIZE(User));
634 (void)ConvertWCharNToUtf8(DomainW, ARRAYSIZE(DomainW), Domain, ARRAYSIZE(Domain));
635 WLog_ERR(TAG, "Failed to parse UserName: %s into User: %s Domain: %s", UserName,
636 User, Domain);
637 return FALSE;
638 }
639 }
640 }
641
642 *username = ConvertWCharNToUtf8Alloc(UserW, ARRAYSIZE(UserW), nullptr);
643 if (!(*username))
644 {
645 WLog_ERR(TAG, "ConvertWCharNToUtf8Alloc failed", status);
646 return FALSE;
647 }
648
649 if (_wcsnlen(DomainW, ARRAYSIZE(DomainW)) > 0)
650 *domain = ConvertWCharNToUtf8Alloc(DomainW, ARRAYSIZE(DomainW), nullptr);
651 else
652 *domain = _strdup("\0");
653
654 if (!(*domain))
655 {
656 free(*username);
657 WLog_ERR(TAG, "strdup failed", status);
658 return FALSE;
659 }
660
661 *password = ConvertWCharNToUtf8Alloc(PasswordW, ARRAYSIZE(PasswordW), nullptr);
662 if (!(*password))
663 {
664 free(*username);
665 free(*domain);
666 return FALSE;
667 }
668
669 return TRUE;
670}
671
672static WCHAR* wf_format_text(const WCHAR* fmt, ...)
673{
674 int rc;
675 size_t size = 0;
676 WCHAR* buffer = nullptr;
677
678 do
679 {
680 WCHAR* tmp = nullptr;
681 va_list ap = WINPR_C_ARRAY_INIT;
682 va_start(ap, fmt);
683 rc = _vsnwprintf(buffer, size, fmt, ap);
684 va_end(ap);
685 if (rc <= 0)
686 goto fail;
687
688 if ((size_t)rc < size)
689 return buffer;
690
691 size = (size_t)rc + 1;
692 tmp = realloc(buffer, size * sizeof(WCHAR));
693 if (!tmp)
694 goto fail;
695
696 buffer = tmp;
697 } while (TRUE);
698
699fail:
700 free(buffer);
701 return nullptr;
702}
703
704#ifdef WITH_WINDOWS_CERT_STORE
705/* https://stackoverflow.com/questions/1231178/load-an-pem-encoded-x-509-certificate-into-windows-cryptoapi/3803333#3803333
706 */
707/* https://github.com/microsoft/Windows-classic-samples/blob/main/Samples/Win7Samples/security/cryptoapi/peertrust/cpp/peertrust.cpp
708 */
709/* https://stackoverflow.com/questions/7340504/whats-the-correct-way-to-verify-an-ssl-certificate-in-win32
710 */
711
712static void wf_report_error(char* wszMessage, DWORD dwErrCode)
713{
714 LPSTR pwszMsgBuf = nullptr;
715
716 if (nullptr != wszMessage && 0 != *wszMessage)
717 {
718 WLog_ERR(TAG, "%s", wszMessage);
719 }
720
721 FormatMessageA(FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM,
722 nullptr, // Location of message
723 // definition ignored
724 dwErrCode, // Message identifier for
725 // the requested message
726 MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), // Language identifier for
727 // the requested message
728 (LPSTR)&pwszMsgBuf, // Buffer that receives
729 // the formatted message
730 0, // Size of output buffer
731 // not needed as allocate
732 // buffer flag is set
733 nullptr // Array of insert values
734 );
735
736 if (nullptr != pwszMsgBuf)
737 {
738 WLog_ERR(TAG, "Error: 0x%08x (%d) %s", dwErrCode, dwErrCode, pwszMsgBuf);
739 LocalFree(pwszMsgBuf);
740 }
741 else
742 {
743 WLog_ERR(TAG, "Error: 0x%08x (%d)", dwErrCode, dwErrCode);
744 }
745}
746
747static DWORD wf_is_x509_certificate_trusted(const char* common_name, const char* subject,
748 const char* issuer, const char* fingerprint)
749{
750 HRESULT hr = CRYPT_E_NOT_FOUND;
751
752 DWORD dwChainFlags = CERT_CHAIN_REVOCATION_CHECK_CHAIN_EXCLUDE_ROOT;
753 PCCERT_CONTEXT pCert = nullptr;
754 HCERTCHAINENGINE hChainEngine = nullptr;
755 PCCERT_CHAIN_CONTEXT pChainContext = nullptr;
756
757 CERT_ENHKEY_USAGE EnhkeyUsage = WINPR_C_ARRAY_INIT;
758 CERT_USAGE_MATCH CertUsage = WINPR_C_ARRAY_INIT;
759 CERT_CHAIN_PARA ChainPara = WINPR_C_ARRAY_INIT;
760 CERT_CHAIN_POLICY_PARA ChainPolicy = WINPR_C_ARRAY_INIT;
761 CERT_CHAIN_POLICY_STATUS PolicyStatus = WINPR_C_ARRAY_INIT;
762 CERT_CHAIN_ENGINE_CONFIG EngineConfig = WINPR_C_ARRAY_INIT;
763
764 DWORD derPubKeyLen = WINPR_ASSERTING_INT_CAST(uint32_t, strlen(fingerprint));
765 char* derPubKey = calloc(derPubKeyLen, sizeof(char));
766 if (nullptr == derPubKey)
767 {
768 WLog_ERR(TAG, "Could not allocate derPubKey");
769 goto CleanUp;
770 }
771
772 /*
773 * Convert from PEM format to DER format - removes header and footer and decodes from base64
774 */
775 if (!CryptStringToBinaryA(fingerprint, 0, CRYPT_STRING_BASE64HEADER, derPubKey, &derPubKeyLen,
776 nullptr, nullptr))
777 {
778 WLog_ERR(TAG, "CryptStringToBinary failed. Err: %d", GetLastError());
779 goto CleanUp;
780 }
781
782 //---------------------------------------------------------
783 // Initialize data structures for chain building.
784
785 EnhkeyUsage.cUsageIdentifier = 0;
786 EnhkeyUsage.rgpszUsageIdentifier = nullptr;
787
788 CertUsage.dwType = USAGE_MATCH_TYPE_AND;
789 CertUsage.Usage = EnhkeyUsage;
790
791 ChainPara.cbSize = sizeof(ChainPara);
792 ChainPara.RequestedUsage = CertUsage;
793
794 ChainPolicy.cbSize = sizeof(ChainPolicy);
795
796 PolicyStatus.cbSize = sizeof(PolicyStatus);
797
798 EngineConfig.cbSize = sizeof(EngineConfig);
799 EngineConfig.dwUrlRetrievalTimeout = 0;
800
801 pCert = CertCreateCertificateContext(X509_ASN_ENCODING, derPubKey, derPubKeyLen);
802 if (nullptr == pCert)
803 {
804 WLog_ERR(TAG, "FAILED: Certificate could not be parsed.");
805 goto CleanUp;
806 }
807
808 dwChainFlags |= CERT_CHAIN_ENABLE_PEER_TRUST;
809
810 // When this flag is set, end entity certificates in the
811 // Trusted People store are trusted without doing any chain building
812 // This optimizes the chain building process.
813
814 //---------------------------------------------------------
815 // Create chain engine.
816
817 if (!CertCreateCertificateChainEngine(&EngineConfig, &hChainEngine))
818 {
819 hr = HRESULT_FROM_WIN32(GetLastError());
820 goto CleanUp;
821 }
822
823 //-------------------------------------------------------------------
824 // Build a chain using CertGetCertificateChain
825
826 if (!CertGetCertificateChain(hChainEngine, // use the default chain engine
827 pCert, // pointer to the end certificate
828 nullptr, // use the default time
829 nullptr, // search no additional stores
830 &ChainPara, // use AND logic and enhanced key usage
831 // as indicated in the ChainPara
832 // data structure
833 dwChainFlags,
834 nullptr, // currently reserved
835 &pChainContext)) // return a pointer to the chain created
836 {
837 hr = HRESULT_FROM_WIN32(GetLastError());
838 goto CleanUp;
839 }
840
841 //---------------------------------------------------------------
842 // Verify that the chain complies with policy
843
844 if (!CertVerifyCertificateChainPolicy(CERT_CHAIN_POLICY_BASE, // use the base policy
845 pChainContext, // pointer to the chain
846 &ChainPolicy,
847 &PolicyStatus)) // return a pointer to the policy status
848 {
849 hr = HRESULT_FROM_WIN32(GetLastError());
850 goto CleanUp;
851 }
852
853 if (PolicyStatus.dwError != S_OK)
854 {
855 wf_report_error("CertVerifyCertificateChainPolicy: Chain Status", PolicyStatus.dwError);
856 hr = PolicyStatus.dwError;
857 // Instruction: If the PolicyStatus.dwError is CRYPT_E_NO_REVOCATION_CHECK or
858 // CRYPT_E_REVOCATION_OFFLINE, it indicates errors in obtaining
859 // revocation information. These can be ignored since the retrieval of
860 // revocation information depends on network availability
861
862 if (PolicyStatus.dwError == CRYPT_E_NO_REVOCATION_CHECK ||
863 PolicyStatus.dwError == CRYPT_E_REVOCATION_OFFLINE)
864 {
865 hr = S_OK;
866 }
867
868 goto CleanUp;
869 }
870
871 WLog_INFO(TAG, "CertVerifyCertificateChainPolicy succeeded for %s (%s) issued by %s",
872 common_name, subject, issuer);
873
874 hr = S_OK;
875CleanUp:
876
877 if (FAILED(hr))
878 {
879 WLog_INFO(TAG, "CertVerifyCertificateChainPolicy failed for %s (%s) issued by %s",
880 common_name, subject, issuer);
881 wf_report_error(nullptr, hr);
882 }
883
884 free(derPubKey);
885
886 if (nullptr != pChainContext)
887 {
888 CertFreeCertificateChain(pChainContext);
889 }
890
891 if (nullptr != hChainEngine)
892 {
893 CertFreeCertificateChainEngine(hChainEngine);
894 }
895
896 if (nullptr != pCert)
897 {
898 CertFreeCertificateContext(pCert);
899 }
900
901 return (DWORD)hr;
902}
903#endif
904
905static DWORD wf_cli_verify_certificate_ex(freerdp* instance, const char* host, UINT16 port,
906 const char* common_name, const char* subject,
907 const char* issuer, const char* fingerprint, DWORD flags)
908{
909#ifdef WITH_WINDOWS_CERT_STORE
910 if (flags & VERIFY_CERT_FLAG_FP_IS_PEM && !(flags & VERIFY_CERT_FLAG_MISMATCH))
911 {
912 if (wf_is_x509_certificate_trusted(common_name, subject, issuer, fingerprint) == S_OK)
913 {
914 return 2;
915 }
916 }
917#endif
918
919 return client_cli_verify_certificate_ex(instance, host, port, common_name, subject, issuer,
920 fingerprint, flags);
921}
922
923static DWORD wf_verify_certificate_ex(freerdp* instance, const char* host, UINT16 port,
924 const char* common_name, const char* subject,
925 const char* issuer, const char* fingerprint, DWORD flags)
926{
927 WCHAR* buffer;
928 WCHAR* caption;
929 int what = IDCANCEL;
930
931#ifdef WITH_WINDOWS_CERT_STORE
932 if (flags & VERIFY_CERT_FLAG_FP_IS_PEM && !(flags & VERIFY_CERT_FLAG_MISMATCH))
933 {
934 if (wf_is_x509_certificate_trusted(common_name, subject, issuer, fingerprint) == S_OK)
935 {
936 return 2;
937 }
938 }
939#endif
940
941 buffer = wf_format_text(
942 L"Certificate details:\n"
943 L"\tCommonName: %S\n"
944 L"\tSubject: %S\n"
945 L"\tIssuer: %S\n"
946 L"\tThumbprint: %S\n"
947 L"\tHostMismatch: %S\n"
948 L"\n"
949 L"The above X.509 certificate could not be verified, possibly because you do not have "
950 L"the CA certificate in your certificate store, or the certificate has expired. "
951 L"Please look at the OpenSSL documentation on how to add a private CA to the store.\n"
952 L"\n"
953 L"YES\tAccept permanently\n"
954 L"NO\tAccept for this session only\n"
955 L"CANCEL\tAbort connection\n",
956 common_name, subject, issuer, fingerprint,
957 flags & VERIFY_CERT_FLAG_MISMATCH ? "Yes" : "No");
958 caption = wf_format_text(L"Verify certificate for %S:%hu", host, port);
959
960 WINPR_UNUSED(instance);
961
962 if (!buffer || !caption)
963 goto fail;
964
965 what = MessageBoxW(nullptr, buffer, caption, MB_YESNOCANCEL);
966fail:
967 free(buffer);
968 free(caption);
969
970 /* return 1 to accept and store a certificate, 2 to accept
971 * a certificate only for this session, 0 otherwise */
972 switch (what)
973 {
974 case IDYES:
975 return 1;
976 case IDNO:
977 return 2;
978 default:
979 return 0;
980 }
981}
982
983static DWORD wf_verify_changed_certificate_ex(freerdp* instance, const char* host, UINT16 port,
984 const char* common_name, const char* subject,
985 const char* issuer, const char* new_fingerprint,
986 const char* old_subject, const char* old_issuer,
987 const char* old_fingerprint, DWORD flags)
988{
989 WCHAR* buffer;
990 WCHAR* caption;
991 int what = IDCANCEL;
992
993 buffer = wf_format_text(
994 L"New Certificate details:\n"
995 L"\tCommonName: %S\n"
996 L"\tSubject: %S\n"
997 L"\tIssuer: %S\n"
998 L"\tThumbprint: %S\n"
999 L"\tHostMismatch: %S\n"
1000 L"\n"
1001 L"Old Certificate details:\n"
1002 L"\tSubject: %S\n"
1003 L"\tIssuer: %S\n"
1004 L"\tThumbprint: %S"
1005 L"The above X.509 certificate could not be verified, possibly because you do not have "
1006 L"the CA certificate in your certificate store, or the certificate has expired. "
1007 L"Please look at the OpenSSL documentation on how to add a private CA to the store.\n"
1008 L"\n"
1009 L"YES\tAccept permanently\n"
1010 L"NO\tAccept for this session only\n"
1011 L"CANCEL\tAbort connection\n",
1012 common_name, subject, issuer, new_fingerprint,
1013 flags & VERIFY_CERT_FLAG_MISMATCH ? "Yes" : "No", old_subject, old_issuer, old_fingerprint);
1014 caption = wf_format_text(L"Verify certificate change for %S:%hu", host, port);
1015
1016 WINPR_UNUSED(instance);
1017 if (!buffer || !caption)
1018 goto fail;
1019
1020 what = MessageBoxW(nullptr, buffer, caption, MB_YESNOCANCEL);
1021fail:
1022 free(buffer);
1023 free(caption);
1024
1025 /* return 1 to accept and store a certificate, 2 to accept
1026 * a certificate only for this session, 0 otherwise */
1027 switch (what)
1028 {
1029 case IDYES:
1030 return 1;
1031 case IDNO:
1032 return 2;
1033 default:
1034 return 0;
1035 }
1036}
1037
1038static BOOL wf_present_gateway_message(freerdp* instance, UINT32 type, BOOL isDisplayMandatory,
1039 BOOL isConsentMandatory, size_t length, const WCHAR* message)
1040{
1041 if (!isDisplayMandatory && !isConsentMandatory)
1042 return TRUE;
1043
1044 /* special handling for consent messages (show modal dialog) */
1045 if (type == GATEWAY_MESSAGE_CONSENT && isConsentMandatory)
1046 {
1047 int mbRes;
1048 WCHAR* msg;
1049
1050 msg = wf_format_text(L"%.*s\n\nI understand and agree to the terms of this policy", length,
1051 message);
1052 mbRes = MessageBoxW(nullptr, msg, L"Consent Message", MB_YESNO);
1053 free(msg);
1054
1055 if (mbRes != IDYES)
1056 return FALSE;
1057 }
1058 else
1059 return client_cli_present_gateway_message(instance, type, isDisplayMandatory,
1060 isConsentMandatory, length, message);
1061
1062 return TRUE;
1063}
1064
1065static DWORD WINAPI wf_client_thread(LPVOID lpParam)
1066{
1067 MSG msg = WINPR_C_ARRAY_INIT;
1068 int width = 0;
1069 int height = 0;
1070 BOOL msg_ret = FALSE;
1071 int quit_msg = 0;
1072 DWORD error = 0;
1073
1074 freerdp* instance = (freerdp*)lpParam;
1075 WINPR_ASSERT(instance);
1076
1077 if (!freerdp_connect(instance))
1078 goto end;
1079
1080 rdpContext* context = instance->context;
1081 WINPR_ASSERT(context);
1082
1083 wfContext* wfc = (wfContext*)instance->context;
1084 WINPR_ASSERT(wfc);
1085
1086 rdpChannels* channels = context->channels;
1087 WINPR_ASSERT(channels);
1088
1089 rdpSettings* settings = context->settings;
1090 WINPR_ASSERT(settings);
1091
1092 while (!freerdp_shall_disconnect_context(instance->context))
1093 {
1094 HANDLE handles[MAXIMUM_WAIT_OBJECTS] = WINPR_C_ARRAY_INIT;
1095 DWORD nCount = 0;
1096
1097 if (freerdp_focus_required(instance))
1098 {
1099 wf_event_focus_in(wfc);
1100 wf_event_focus_in(wfc);
1101 }
1102
1103 {
1104 DWORD tmp = freerdp_get_event_handles(context, &handles[nCount], 64 - nCount);
1105
1106 if (tmp == 0)
1107 {
1108 WLog_ERR(TAG, "freerdp_get_event_handles failed");
1109 break;
1110 }
1111
1112 nCount += tmp;
1113 }
1114
1115 DWORD status = MsgWaitForMultipleObjectsEx(nCount, handles, 5 * 1000, QS_ALLINPUT,
1116 MWMO_ALERTABLE | MWMO_INPUTAVAILABLE);
1117 if (status == WAIT_FAILED)
1118 {
1119 WLog_ERR(TAG, "wfreerdp_run: WaitForMultipleObjects failed: 0x%08lX", GetLastError());
1120 break;
1121 }
1122
1123 {
1124 if (!freerdp_check_event_handles(context))
1125 {
1126 if (client_auto_reconnect(instance))
1127 continue;
1128
1129 WLog_ERR(TAG, "Failed to check FreeRDP file descriptor");
1130 break;
1131 }
1132 }
1133
1134 if (freerdp_shall_disconnect_context(instance->context))
1135 break;
1136
1137 quit_msg = FALSE;
1138
1139 while (PeekMessage(&msg, nullptr, 0, 0, PM_NOREMOVE))
1140 {
1141 msg_ret = GetMessage(&msg, nullptr, 0, 0);
1142
1143 if (freerdp_settings_get_bool(settings, FreeRDP_EmbeddedWindow))
1144 {
1145 if ((msg.message == WM_SETFOCUS) && (msg.lParam == 1))
1146 {
1147 PostMessage(wfc->hwnd, WM_SETFOCUS, 0, 0);
1148 }
1149 else if ((msg.message == WM_KILLFOCUS) && (msg.lParam == 1))
1150 {
1151 PostMessage(wfc->hwnd, WM_KILLFOCUS, 0, 0);
1152 }
1153 }
1154
1155 switch (msg.message)
1156 {
1157 case WM_SIZE:
1158 {
1159 width = LOWORD(msg.lParam);
1160 height = HIWORD(msg.lParam);
1161 SetWindowPos(wfc->hwnd, HWND_TOP, 0, 0, width, height, SWP_FRAMECHANGED);
1162 break;
1163 }
1164 case WM_FREERDP_SHOWWINDOW:
1165 {
1166 ShowWindow(wfc->hwnd, SW_NORMAL);
1167 if (wfc->floatbar)
1168 {
1169 wf_floatbar_reset_position(wfc->floatbar);
1170 }
1171 break;
1172 }
1173 default:
1174 break;
1175 }
1176
1177 if ((msg_ret == 0) || (msg_ret == -1))
1178 {
1179 quit_msg = TRUE;
1180 break;
1181 }
1182
1183 TranslateMessage(&msg);
1184 DispatchMessage(&msg);
1185 }
1186
1187 if (quit_msg)
1188 break;
1189 }
1190
1191 /* cleanup */
1192 freerdp_disconnect(instance);
1193
1194end:
1195 error = freerdp_get_last_error(instance->context);
1196 WLog_DBG(TAG, "Main thread exited with %" PRIu32, error);
1197 ExitThread(error);
1198 return error;
1199}
1200
1201static DWORD WINAPI wf_keyboard_thread(LPVOID lpParam)
1202{
1203 MSG msg;
1204 BOOL status;
1205 wfContext* wfc;
1206 HHOOK hook_handle;
1207 wfc = (wfContext*)lpParam;
1208 WINPR_ASSERT(nullptr != wfc);
1209 hook_handle = SetWindowsHookEx(WH_KEYBOARD_LL, wf_ll_kbd_proc, wfc->hInstance, 0);
1210
1211 if (hook_handle)
1212 {
1213 while ((status = GetMessage(&msg, nullptr, 0, 0)) != 0)
1214 {
1215 if (status == -1)
1216 {
1217 WLog_ERR(TAG, "keyboard thread error getting message");
1218 break;
1219 }
1220 else
1221 {
1222 TranslateMessage(&msg);
1223 DispatchMessage(&msg);
1224 }
1225 }
1226
1227 UnhookWindowsHookEx(hook_handle);
1228 }
1229 else
1230 {
1231 WLog_ERR(TAG, "failed to install keyboard hook");
1232 }
1233
1234 WLog_DBG(TAG, "Keyboard thread exited.");
1235 ExitThread(0);
1236 return 0;
1237}
1238
1239int freerdp_client_set_window_size(wfContext* wfc, int width, int height)
1240{
1241 WLog_DBG(TAG, "freerdp_client_set_window_size %d, %d", width, height);
1242
1243 if ((width != wfc->client_width) || (height != wfc->client_height))
1244 {
1245 PostThreadMessage(wfc->mainThreadId, WM_SIZE, SIZE_RESTORED,
1246 ((UINT)height << 16) | (UINT)width);
1247 }
1248
1249 return 0;
1250}
1251
1252void wf_size_scrollbars(wfContext* wfc, UINT32 client_width, UINT32 client_height)
1253{
1254 const rdpSettings* settings;
1255 WINPR_ASSERT(wfc);
1256
1257 settings = wfc->common.context.settings;
1258 WINPR_ASSERT(settings);
1259
1260 if (wfc->disablewindowtracking)
1261 return;
1262
1263 // prevent infinite message loop
1264 wfc->disablewindowtracking = TRUE;
1265
1266 if (freerdp_settings_get_bool(settings, FreeRDP_SmartSizing) ||
1267 freerdp_settings_get_bool(settings, FreeRDP_DynamicResolutionUpdate))
1268 {
1269 wfc->xCurrentScroll = 0;
1270 wfc->yCurrentScroll = 0;
1271
1272 if (wfc->xScrollVisible || wfc->yScrollVisible)
1273 {
1274 if (ShowScrollBar(wfc->hwnd, SB_BOTH, FALSE))
1275 {
1276 wfc->xScrollVisible = FALSE;
1277 wfc->yScrollVisible = FALSE;
1278 }
1279 }
1280 }
1281 else
1282 {
1283 SCROLLINFO si;
1284 BOOL horiz = wfc->xScrollVisible;
1285 BOOL vert = wfc->yScrollVisible;
1286 BOOL redraw = FALSE;
1287
1288 if (!horiz && client_width < freerdp_settings_get_uint32(settings, FreeRDP_DesktopWidth))
1289 {
1290 horiz = TRUE;
1291 }
1292 else if (horiz &&
1293 client_width >=
1295 settings, FreeRDP_DesktopWidth) /* - GetSystemMetrics(SM_CXVSCROLL)*/)
1296 {
1297 horiz = FALSE;
1298 }
1299
1300 if (!vert && client_height < freerdp_settings_get_uint32(settings, FreeRDP_DesktopHeight))
1301 {
1302 vert = TRUE;
1303 }
1304 else if (vert &&
1305 client_height >=
1307 settings, FreeRDP_DesktopHeight) /* - GetSystemMetrics(SM_CYHSCROLL)*/)
1308 {
1309 vert = FALSE;
1310 }
1311
1312 if (horiz == vert && (horiz != wfc->xScrollVisible && vert != wfc->yScrollVisible))
1313 {
1314 if (ShowScrollBar(wfc->hwnd, SB_BOTH, horiz))
1315 {
1316 wfc->xScrollVisible = horiz;
1317 wfc->yScrollVisible = vert;
1318 }
1319 }
1320
1321 if (horiz != wfc->xScrollVisible)
1322 {
1323 if (ShowScrollBar(wfc->hwnd, SB_HORZ, horiz))
1324 {
1325 wfc->xScrollVisible = horiz;
1326 }
1327 }
1328
1329 if (vert != wfc->yScrollVisible)
1330 {
1331 if (ShowScrollBar(wfc->hwnd, SB_VERT, vert))
1332 {
1333 wfc->yScrollVisible = vert;
1334 }
1335 }
1336
1337 if (horiz)
1338 {
1339 // The horizontal scrolling range is defined by
1340 // (bitmap_width) - (client_width). The current horizontal
1341 // scroll value remains within the horizontal scrolling range.
1342 wfc->xMaxScroll =
1343 MAX(freerdp_settings_get_uint32(settings, FreeRDP_DesktopWidth) - client_width, 0);
1344 wfc->xCurrentScroll = MIN(wfc->xCurrentScroll, wfc->xMaxScroll);
1345 si.cbSize = sizeof(si);
1346 si.fMask = SIF_RANGE | SIF_PAGE | SIF_POS;
1347 si.nMin = wfc->xMinScroll;
1348 si.nMax = freerdp_settings_get_uint32(settings, FreeRDP_DesktopWidth);
1349 si.nPage = client_width;
1350 si.nPos = wfc->xCurrentScroll;
1351 SetScrollInfo(wfc->hwnd, SB_HORZ, &si, TRUE);
1352 }
1353 else
1354 {
1355 if (wfc->xCurrentScroll != 0)
1356 {
1357 wfc->xCurrentScroll = 0;
1358 redraw = TRUE;
1359 }
1360 }
1361
1362 if (vert)
1363 {
1364 // The vertical scrolling range is defined by
1365 // (bitmap_height) - (client_height). The current vertical
1366 // scroll value remains within the vertical scrolling range.
1367 wfc->yMaxScroll = MAX(
1368 freerdp_settings_get_uint32(settings, FreeRDP_DesktopHeight) - client_height, 0);
1369 wfc->yCurrentScroll = MIN(wfc->yCurrentScroll, wfc->yMaxScroll);
1370 si.cbSize = sizeof(si);
1371 si.fMask = SIF_RANGE | SIF_PAGE | SIF_POS;
1372 si.nMin = wfc->yMinScroll;
1373 si.nMax = freerdp_settings_get_uint32(settings, FreeRDP_DesktopHeight);
1374 si.nPage = client_height;
1375 si.nPos = wfc->yCurrentScroll;
1376 SetScrollInfo(wfc->hwnd, SB_VERT, &si, TRUE);
1377 }
1378 else
1379 {
1380 if (wfc->yCurrentScroll != 0)
1381 {
1382 wfc->yCurrentScroll = 0;
1383 redraw = TRUE;
1384 }
1385 }
1386
1387 if (redraw)
1388 {
1389 InvalidateRect(wfc->hwnd, nullptr, FALSE);
1390 UpdateWindow(wfc->hwnd);
1391 }
1392 }
1393
1394 wfc->disablewindowtracking = FALSE;
1395 wf_update_canvas_diff(wfc);
1396}
1397
1398static BOOL wfreerdp_client_global_init(void)
1399{
1400 WSADATA wsaData;
1401
1402 WSAStartup(0x101, &wsaData);
1403 if (freerdp_handle_signals() != 0)
1404 return FALSE;
1405
1406 if (freerdp_register_addin_provider(freerdp_channels_load_static_addin_entry, 0) !=
1407 CHANNEL_RC_OK)
1408 return FALSE;
1409
1410 return TRUE;
1411}
1412
1413static void wfreerdp_client_global_uninit(void)
1414{
1415 WSACleanup();
1416}
1417
1418static BOOL wfreerdp_client_new(freerdp* instance, rdpContext* context)
1419{
1420 wfContext* wfc = (wfContext*)context;
1421 if (!wfc)
1422 return FALSE;
1423
1424 // AttachConsole and stdin do not work well.
1425 // Use GUI input dialogs instead of command line ones.
1426 wfc->isConsole = wf_has_console();
1427
1428#ifdef WITH_WIN_CONSOLE
1429 if (!wfc->isConsole)
1430 {
1431 HWND hwndConsole = GetConsoleWindow();
1432 if (hwndConsole)
1433 ShowWindow(hwndConsole, SW_HIDE);
1434 (void)FreeConsole();
1435 }
1436#endif
1437
1438 if (!(wfreerdp_client_global_init()))
1439 return FALSE;
1440
1441 WINPR_ASSERT(instance);
1442 instance->PreConnect = wf_pre_connect;
1443 instance->PostConnect = wf_post_connect;
1444 instance->PostDisconnect = wf_post_disconnect;
1445 instance->AuthenticateEx = wf_authenticate_ex;
1446 instance->GetAccessToken = client_failsafe_get_access_token;
1447
1448#ifdef WITH_WINDOWS_CERT_STORE
1449 if (!freerdp_settings_set_bool(context->settings, FreeRDP_CertificateCallbackPreferPEM, TRUE))
1450 return FALSE;
1451#endif
1452
1453 if (!wfc->isConsole)
1454 {
1455 instance->VerifyCertificateEx = wf_verify_certificate_ex;
1456 instance->VerifyChangedCertificateEx = wf_verify_changed_certificate_ex;
1457 instance->PresentGatewayMessage = wf_present_gateway_message;
1458 }
1459
1460#ifdef WITH_PROGRESS_BAR
1461 CoInitializeEx(nullptr, COINIT_APARTMENTTHREADED);
1462 CoCreateInstance(&CLSID_TaskbarList, nullptr, CLSCTX_ALL, &IID_ITaskbarList3,
1463 (void**)&wfc->taskBarList);
1464#endif
1465
1466 return TRUE;
1467}
1468
1469static void wfreerdp_client_free(freerdp* instance, rdpContext* context)
1470{
1471 WINPR_UNUSED(instance);
1472 if (!context)
1473 return;
1474
1475#ifdef WITH_PROGRESS_BAR
1476 CoUninitialize();
1477#endif
1478}
1479
1480static int wfreerdp_client_start(rdpContext* context)
1481{
1482 wfContext* wfc = (wfContext*)context;
1483
1484 WINPR_ASSERT(context);
1485 WINPR_ASSERT(context->settings);
1486
1487 freerdp* instance = context->instance;
1488 WINPR_ASSERT(instance);
1489
1490 rdpSettings* settings = context->settings;
1491 WINPR_ASSERT(settings);
1492
1493 HINSTANCE hInstance = GetModuleHandle(nullptr);
1494 HWND hWndParent = (HWND)freerdp_settings_get_uint64(settings, FreeRDP_ParentWindowId);
1495 if (!freerdp_settings_set_bool(settings, FreeRDP_EmbeddedWindow, (hWndParent) ? TRUE : FALSE))
1496 return -1;
1497
1498 wfc->hWndParent = hWndParent;
1499
1500 if (freerdp_settings_get_bool(settings, FreeRDP_EmbeddedWindow))
1501 {
1502 typedef UINT(WINAPI * GetDpiForWindow_t)(HWND hwnd);
1503 typedef BOOL(WINAPI * SetProcessDPIAware_t)(void);
1504
1505 HMODULE module = GetModuleHandle(_T("User32"));
1506 if (module)
1507 {
1508 GetDpiForWindow_t pGetDpiForWindow =
1509 GetProcAddressAs(module, "GetDpiForWindow", GetDpiForWindow_t);
1510 SetProcessDPIAware_t pSetProcessDPIAware =
1511 GetProcAddressAs(module, "SetProcessDPIAware", SetProcessDPIAware_t);
1512 if (pGetDpiForWindow && pSetProcessDPIAware)
1513 {
1514 const UINT dpiAwareness = pGetDpiForWindow(hWndParent);
1515 if (dpiAwareness != USER_DEFAULT_SCREEN_DPI)
1516 pSetProcessDPIAware();
1517 }
1518 FreeLibrary(module);
1519 }
1520 }
1521
1522 /* initial windows system item position where we will insert new menu item
1523 * after default 5 items (restore, move, size, minimize, maximize)
1524 * gets incremented each time wf_append_item_to_system_menu is called
1525 * or maybe could use GetMenuItemCount() to get initial item count ? */
1526 wfc->systemMenuInsertPosition = 6;
1527
1528 wfc->hInstance = hInstance;
1529 wfc->cursor = LoadCursor(nullptr, IDC_ARROW);
1530 wfc->icon = LoadIcon(GetModuleHandle(nullptr), MAKEINTRESOURCE(IDI_ICON1));
1531 wfc->wndClassName = _tcsdup(_T("FreeRDP"));
1532 wfc->wndClass.cbSize = sizeof(WNDCLASSEX);
1533 wfc->wndClass.style = CS_HREDRAW | CS_VREDRAW;
1534 wfc->wndClass.lpfnWndProc = wf_event_proc;
1535 wfc->wndClass.cbClsExtra = 0;
1536 wfc->wndClass.cbWndExtra = 0;
1537 wfc->wndClass.hCursor = nullptr;
1538 wfc->wndClass.hbrBackground = (HBRUSH)GetStockObject(BLACK_BRUSH);
1539 wfc->wndClass.lpszMenuName = nullptr;
1540 wfc->wndClass.lpszClassName = wfc->wndClassName;
1541 wfc->wndClass.hInstance = hInstance;
1542 wfc->wndClass.hIcon = wfc->icon;
1543 wfc->wndClass.hIconSm = wfc->icon;
1544 RegisterClassEx(&(wfc->wndClass));
1545 wfc->keyboardThread =
1546 CreateThread(nullptr, 0, wf_keyboard_thread, (void*)wfc, 0, &wfc->keyboardThreadId);
1547
1548 if (!wfc->keyboardThread)
1549 return -1;
1550
1551 wfc->common.thread =
1552 CreateThread(nullptr, 0, wf_client_thread, (void*)instance, 0, &wfc->mainThreadId);
1553
1554 if (!wfc->common.thread)
1555 return -1;
1556
1557 return 0;
1558}
1559
1560static int wfreerdp_client_stop(rdpContext* context)
1561{
1562 int rc;
1563 wfContext* wfc = (wfContext*)context;
1564
1565 WINPR_ASSERT(wfc);
1566 PostThreadMessage(wfc->mainThreadId, WM_QUIT, 0, 0);
1567 rc = freerdp_client_common_stop(context);
1568 wfc->mainThreadId = 0;
1569
1570 if (wfc->keyboardThread)
1571 {
1572 PostThreadMessage(wfc->keyboardThreadId, WM_QUIT, 0, 0);
1573 (void)WaitForSingleObject(wfc->keyboardThread, INFINITE);
1574 (void)CloseHandle(wfc->keyboardThread);
1575 wfc->keyboardThread = nullptr;
1576 wfc->keyboardThreadId = 0;
1577 }
1578
1579 return 0;
1580}
1581
1582int RdpClientEntry(RDP_CLIENT_ENTRY_POINTS* pEntryPoints)
1583{
1584 pEntryPoints->Version = 1;
1585 pEntryPoints->Size = sizeof(RDP_CLIENT_ENTRY_POINTS_V1);
1586 pEntryPoints->GlobalInit = wfreerdp_client_global_init;
1587 pEntryPoints->GlobalUninit = wfreerdp_client_global_uninit;
1588 pEntryPoints->ContextSize = sizeof(wfContext);
1589 pEntryPoints->ClientNew = wfreerdp_client_new;
1590 pEntryPoints->ClientFree = wfreerdp_client_free;
1591 pEntryPoints->ClientStart = wfreerdp_client_start;
1592 pEntryPoints->ClientStop = wfreerdp_client_stop;
1593 return 0;
1594}
WINPR_ATTR_NODISCARD FREERDP_API const char * freerdp_settings_get_string(const rdpSettings *settings, FreeRDP_Settings_Keys_String id)
Returns a immutable string settings value.
WINPR_ATTR_NODISCARD FREERDP_API BOOL freerdp_settings_set_bool(rdpSettings *settings, FreeRDP_Settings_Keys_Bool id, BOOL val)
Sets a BOOL settings value.
WINPR_ATTR_NODISCARD FREERDP_API UINT64 freerdp_settings_get_uint64(const rdpSettings *settings, FreeRDP_Settings_Keys_UInt64 id)
Returns a UINT64 settings value.
WINPR_ATTR_NODISCARD FREERDP_API BOOL freerdp_settings_set_uint32(rdpSettings *settings, FreeRDP_Settings_Keys_UInt32 id, UINT32 val)
Sets a UINT32 settings value.
WINPR_ATTR_NODISCARD FREERDP_API UINT32 freerdp_settings_get_uint32(const rdpSettings *settings, FreeRDP_Settings_Keys_UInt32 id)
Returns a UINT32 settings value.
WINPR_ATTR_NODISCARD FREERDP_API BOOL freerdp_settings_get_bool(const rdpSettings *settings, FreeRDP_Settings_Keys_Bool id)
Returns a boolean settings value.