EditKeyboardWindow.cpp
1 #include "pch.h" 2 3 #include <set> 4 5 #include <common/Display/dpi_aware.h> 6 #include <common/interop/shared_constants.h> 7 #include <common/themes/windows_colors.h> 8 #include <common/utils/EventLocker.h> 9 #include <common/utils/winapi_error.h> 10 11 #include <common/Telemetry/EtwTrace/EtwTrace.h> 12 13 #include <keyboardmanager/common/KeyboardManagerConstants.h> 14 #include <keyboardmanager/common/MappingConfiguration.h> 15 16 #include <KeyboardManagerState.h> 17 #include "EditKeyboardWindow.h" 18 #include "SingleKeyRemapControl.h" 19 #include "KeyDropDownControl.h" 20 #include "XamlBridge2.h" 21 #include "Styles.h" 22 #include "Dialog.h" 23 #include "LoadingAndSavingRemappingHelper.h" 24 #include "UIHelpers.h" 25 #include "ShortcutErrorType.h" 26 #include "EditorConstants.h" 27 #include <common/Themes/theme_listener.h> 28 29 static UINT g_currentDPI = DPIAware::DEFAULT_DPI; 30 31 LRESULT CALLBACK EditKeyboardWindowProc(HWND, UINT, WPARAM, LPARAM); 32 33 // This Hwnd will be the window handler for the Xaml Island: A child window that contains Xaml. 34 HWND hWndXamlIslandEditKeyboardWindow = nullptr; 35 36 // This variable is used to check if window registration has been done to avoid repeated registration leading to an error. 37 bool isEditKeyboardWindowRegistrationCompleted = false; 38 39 // Holds the native window handle of EditKeyboard Window. 40 HWND hwndEditKeyboardNativeWindow = nullptr; 41 std::mutex editKeyboardWindowMutex; 42 43 // Stores a pointer to the Xaml Bridge object so that it can be accessed from the window procedure 44 static XamlBridge2* xamlBridgePtr = nullptr; 45 46 // Theming 47 static ThemeListener theme_listener{}; 48 49 static void handleTheme() 50 { 51 auto theme = theme_listener.AppTheme; 52 auto isDark = theme == Theme::Dark; 53 Logger::info(L"Theme is now {}", isDark ? L"Dark" : L"Light"); 54 if (hwndEditKeyboardNativeWindow != nullptr) 55 { 56 ThemeHelpers::SetImmersiveDarkMode(hwndEditKeyboardNativeWindow, isDark); 57 } 58 } 59 60 static winrt::Windows::Foundation::IAsyncOperation<bool> OrphanKeysConfirmationDialog( 61 KBMEditor::KeyboardManagerState& state, 62 const std::vector<DWORD>& keys, 63 XamlRoot root) 64 { 65 ContentDialog confirmationDialog; 66 confirmationDialog.XamlRoot(root); 67 confirmationDialog.Title(box_value(GET_RESOURCE_STRING(IDS_EDITKEYBOARD_ORPHANEDDIALOGTITLE))); 68 confirmationDialog.Content(nullptr); 69 confirmationDialog.IsPrimaryButtonEnabled(true); 70 confirmationDialog.DefaultButton(ContentDialogButton::Primary); 71 confirmationDialog.PrimaryButtonText(winrt::hstring(GET_RESOURCE_STRING(IDS_CONTINUE_BUTTON))); 72 confirmationDialog.IsSecondaryButtonEnabled(true); 73 confirmationDialog.SecondaryButtonText(winrt::hstring(GET_RESOURCE_STRING(IDS_CANCEL_BUTTON))); 74 75 TextBlock orphanKeysBlock; 76 std::wstring orphanKeyString; 77 for (auto k : keys) 78 { 79 orphanKeyString.append(state.keyboardMap.GetKeyName(k)); 80 orphanKeyString.append(L", "); 81 } 82 83 orphanKeyString = orphanKeyString.substr(0, max(0, orphanKeyString.length() - 2)); 84 orphanKeysBlock.Text(winrt::hstring(orphanKeyString)); 85 orphanKeysBlock.TextWrapping(TextWrapping::Wrap); 86 confirmationDialog.Content(orphanKeysBlock); 87 88 ContentDialogResult res = co_await confirmationDialog.ShowAsync(); 89 90 co_return res == ContentDialogResult::Primary; 91 } 92 93 static winrt::Windows::Foundation::IAsyncAction OnClickAccept(KBMEditor::KeyboardManagerState& keyboardManagerState, XamlRoot root, std::function<void()> ApplyRemappings) 94 { 95 ShortcutErrorType isSuccess = LoadingAndSavingRemappingHelper::CheckIfRemappingsAreValid(SingleKeyRemapControl::singleKeyRemapBuffer); 96 97 if (isSuccess != ShortcutErrorType::NoError) 98 { 99 if (!co_await Dialog::PartialRemappingConfirmationDialog(root, GET_RESOURCE_STRING(IDS_EDITKEYBOARD_PARTIALCONFIRMATIONDIALOGTITLE))) 100 { 101 co_return; 102 } 103 } 104 105 // Check for orphaned keys 106 // Draw content Dialog 107 std::vector<DWORD> orphanedKeys = LoadingAndSavingRemappingHelper::GetOrphanedKeys(SingleKeyRemapControl::singleKeyRemapBuffer); 108 if (orphanedKeys.size() > 0) 109 { 110 if (!co_await OrphanKeysConfirmationDialog(keyboardManagerState, orphanedKeys, root)) 111 { 112 co_return; 113 } 114 } 115 116 ApplyRemappings(); 117 } 118 119 // Function to create the Edit Keyboard Window 120 inline void CreateEditKeyboardWindowImpl(HINSTANCE hInst, KBMEditor::KeyboardManagerState& keyboardManagerState, MappingConfiguration& mappingConfiguration) 121 { 122 Logger::trace("CreateEditKeyboardWindowImpl()"); 123 auto locker = EventLocker::Get(KeyboardManagerConstants::EditorWindowEventName.c_str()); 124 if (!locker.has_value()) 125 { 126 Logger::error(L"Failed to lock event {}. {}", KeyboardManagerConstants::EditorWindowEventName, get_last_error_or_default(GetLastError())); 127 } 128 129 Logger::trace(L"Signaled {} event to suspend the KBM engine", KeyboardManagerConstants::EditorWindowEventName); 130 131 // Window Registration 132 const wchar_t szWindowClass[] = L"EditKeyboardWindow"; 133 if (!isEditKeyboardWindowRegistrationCompleted) 134 { 135 WNDCLASSEX windowClass = {}; 136 windowClass.cbSize = sizeof(WNDCLASSEX); 137 windowClass.lpfnWndProc = EditKeyboardWindowProc; 138 windowClass.hInstance = hInst; 139 windowClass.lpszClassName = szWindowClass; 140 windowClass.hbrBackground = CreateSolidBrush((ThemeHelpers::GetAppTheme() == Theme::Dark) ? 0x00000000 : 0x00FFFFFF); 141 windowClass.hIcon = static_cast<HICON>(LoadImageW( 142 windowClass.hInstance, 143 MAKEINTRESOURCE(IDS_KEYBOARDMANAGER_ICON), 144 IMAGE_ICON, 145 48, 146 48, 147 LR_DEFAULTCOLOR)); 148 149 if (RegisterClassEx(&windowClass) == NULL) 150 { 151 MessageBox(NULL, GET_RESOURCE_STRING(IDS_REGISTERCLASSFAILED_ERRORMESSAGE).c_str(), GET_RESOURCE_STRING(IDS_REGISTERCLASSFAILED_ERRORTITLE).c_str(), NULL); 152 return; 153 } 154 155 isEditKeyboardWindowRegistrationCompleted = true; 156 } 157 158 // Find coordinates of the screen where the settings window is placed. 159 RECT desktopRect = UIHelpers::GetForegroundWindowDesktopRect(); 160 161 // Calculate DPI dependent window size 162 float windowWidth = EditorConstants::DefaultEditKeyboardWindowWidth; 163 float windowHeight = EditorConstants::DefaultEditKeyboardWindowHeight; 164 165 DPIAware::ConvertByCursorPosition(windowWidth, windowHeight); 166 DPIAware::GetScreenDPIForCursor(g_currentDPI); 167 168 // Window Creation 169 HWND _hWndEditKeyboardWindow = CreateWindow( 170 szWindowClass, 171 GET_RESOURCE_STRING(IDS_EDITKEYBOARD_WINDOWNAME).c_str(), 172 WS_OVERLAPPED | WS_CAPTION | WS_SYSMENU | WS_THICKFRAME | WS_MAXIMIZEBOX, 173 ((desktopRect.right + desktopRect.left) / 2) - ((int)windowWidth / 2), 174 ((desktopRect.bottom + desktopRect.top) / 2) - ((int)windowHeight / 2), 175 static_cast<int>(windowWidth), 176 static_cast<int>(windowHeight), 177 NULL, 178 NULL, 179 hInst, 180 NULL); 181 182 if (_hWndEditKeyboardWindow == NULL) 183 { 184 MessageBox(NULL, GET_RESOURCE_STRING(IDS_CREATEWINDOWFAILED_ERRORMESSAGE).c_str(), GET_RESOURCE_STRING(IDS_CREATEWINDOWFAILED_ERRORTITLE).c_str(), NULL); 185 return; 186 } 187 188 // Ensures the window is in foreground on first startup. If this is not done, the window appears behind because the thread is not on the foreground. 189 if (_hWndEditKeyboardWindow) 190 { 191 SetForegroundWindow(_hWndEditKeyboardWindow); 192 } 193 194 // Store the newly created Edit Keyboard window's handle. 195 std::unique_lock<std::mutex> hwndLock(editKeyboardWindowMutex); 196 hwndEditKeyboardNativeWindow = _hWndEditKeyboardWindow; 197 hwndLock.unlock(); 198 199 // Hide icon and caption from title bar 200 const DWORD windowThemeOptionsMask = WTNCA_NODRAWCAPTION | WTNCA_NODRAWICON; 201 WTA_OPTIONS windowThemeOptions{ windowThemeOptionsMask, windowThemeOptionsMask }; 202 SetWindowThemeAttribute(_hWndEditKeyboardWindow, WTA_NONCLIENT, &windowThemeOptions, sizeof(windowThemeOptions)); 203 204 handleTheme(); 205 theme_listener.AddChangedHandler(handleTheme); 206 207 // Create the xaml bridge object 208 XamlBridge2 xamlBridge(_hWndEditKeyboardWindow); 209 210 // Create the desktop window xaml source object and set its content 211 hWndXamlIslandEditKeyboardWindow = xamlBridge.InitBridge(); 212 213 // Set the pointer to the xaml bridge object 214 xamlBridgePtr = &xamlBridge; 215 216 // Header for the window 217 Windows::UI::Xaml::Controls::RelativePanel header; 218 header.Margin({ 10, 10, 10, 30 }); 219 220 // Header text 221 TextBlock headerText; 222 headerText.Text(GET_RESOURCE_STRING(IDS_EDITKEYBOARD_WINDOWNAME)); 223 headerText.FontSize(30); 224 headerText.Margin({ 0, 0, 0, 0 }); 225 header.SetAlignLeftWithPanel(headerText, true); 226 227 // Header Cancel button 228 Button cancelButton; 229 cancelButton.Content(winrt::box_value(GET_RESOURCE_STRING(IDS_CANCEL_BUTTON))); 230 cancelButton.Margin({ 10, 0, 0, 0 }); 231 cancelButton.Click([&](winrt::Windows::Foundation::IInspectable const& sender, RoutedEventArgs const&) { 232 // Close the window since settings do not need to be saved 233 PostMessage(_hWndEditKeyboardWindow, WM_CLOSE, 0, 0); 234 }); 235 236 // Text block for information about remap key section. 237 TextBlock keyRemapInfoHeader; 238 keyRemapInfoHeader.Text(GET_RESOURCE_STRING(IDS_EDITKEYBOARD_INFO)); 239 keyRemapInfoHeader.Margin({ 10, 0, 0, 10 }); 240 keyRemapInfoHeader.FontWeight(Text::FontWeights::SemiBold()); 241 keyRemapInfoHeader.TextWrapping(TextWrapping::Wrap); 242 243 TextBlock keyRemapInfoExample; 244 keyRemapInfoExample.Text(GET_RESOURCE_STRING(IDS_EDITKEYBOARD_INFOEXAMPLE)); 245 keyRemapInfoExample.Margin({ 10, 0, 0, 20 }); 246 keyRemapInfoExample.FontStyle(Text::FontStyle::Italic); 247 keyRemapInfoExample.TextWrapping(TextWrapping::Wrap); 248 249 // Table to display the key remaps 250 StackPanel keyRemapTable; 251 252 // First header textblock in the header row of the keys remap table 253 TextBlock originalKeyRemapHeader; 254 originalKeyRemapHeader.Text(GET_RESOURCE_STRING(IDS_EDITKEYBOARD_SOURCEHEADER)); 255 originalKeyRemapHeader.FontWeight(Text::FontWeights::Bold()); 256 StackPanel originalKeyHeaderContainer = UIHelpers::GetWrapped(originalKeyRemapHeader, EditorConstants::RemapTableDropDownWidth + EditorConstants::TableArrowColWidth).as<StackPanel>(); 257 258 // Second header textblock in the header row of the keys remap table 259 TextBlock newKeyRemapHeader; 260 newKeyRemapHeader.Text(GET_RESOURCE_STRING(IDS_EDITKEYBOARD_TARGETHEADER)); 261 newKeyRemapHeader.FontWeight(Text::FontWeights::Bold()); 262 263 StackPanel tableHeader = StackPanel(); 264 tableHeader.Orientation(Orientation::Horizontal); 265 tableHeader.Margin({ 10, 0, 0, 10 }); 266 tableHeader.Children().Append(originalKeyHeaderContainer); 267 tableHeader.Children().Append(newKeyRemapHeader); 268 269 // Store handle of edit keyboard window 270 SingleKeyRemapControl::EditKeyboardWindowHandle = _hWndEditKeyboardWindow; 271 272 // Store keyboard manager state 273 SingleKeyRemapControl::keyboardManagerState = &keyboardManagerState; 274 KeyDropDownControl::keyboardManagerState = &keyboardManagerState; 275 KeyDropDownControl::mappingConfiguration = &mappingConfiguration; 276 277 // Clear the single key remap buffer 278 SingleKeyRemapControl::singleKeyRemapBuffer.clear(); 279 280 // Vector to store dynamically allocated control objects to avoid early destruction 281 std::vector<std::vector<std::unique_ptr<SingleKeyRemapControl>>> keyboardRemapControlObjects; 282 283 // Set keyboard manager UI state so that remaps are not applied while on this window 284 keyboardManagerState.SetUIState(KBMEditor::KeyboardManagerUIState::EditKeyboardWindowActivated, _hWndEditKeyboardWindow); 285 286 // Load existing remaps into UI 287 SingleKeyRemapTable singleKeyRemapCopy = mappingConfiguration.singleKeyReMap; 288 SingleKeyToTextRemapTable singleKeyToTextRemapCopy = mappingConfiguration.singleKeyToTextReMap; 289 290 LoadingAndSavingRemappingHelper::PreProcessRemapTable(singleKeyRemapCopy); 291 LoadingAndSavingRemappingHelper::PreProcessRemapTable(singleKeyToTextRemapCopy); 292 293 for (const auto& it : singleKeyRemapCopy) 294 { 295 SingleKeyRemapControl::AddNewControlKeyRemapRow(keyRemapTable, keyboardRemapControlObjects, it.first, it.second); 296 } 297 298 for (const auto& it : singleKeyToTextRemapCopy) 299 { 300 SingleKeyRemapControl::AddNewControlKeyRemapRow(keyRemapTable, keyboardRemapControlObjects, it.first, it.second); 301 } 302 303 // Main Header Apply button 304 Button applyButton; 305 applyButton.Content(winrt::box_value(GET_RESOURCE_STRING(IDS_OK_BUTTON))); 306 applyButton.Style(AccentButtonStyle()); 307 applyButton.MinWidth(EditorConstants::HeaderButtonWidth); 308 cancelButton.MinWidth(EditorConstants::HeaderButtonWidth); 309 header.SetAlignRightWithPanel(cancelButton, true); 310 header.SetLeftOf(applyButton, cancelButton); 311 312 auto ApplyRemappings = [&mappingConfiguration, _hWndEditKeyboardWindow]() { 313 LoadingAndSavingRemappingHelper::ApplySingleKeyRemappings(mappingConfiguration, SingleKeyRemapControl::singleKeyRemapBuffer, true); 314 bool saveResult = mappingConfiguration.SaveSettingsToFile(); 315 PostMessage(_hWndEditKeyboardWindow, WM_CLOSE, 0, 0); 316 }; 317 318 applyButton.Click([&keyboardManagerState, ApplyRemappings, applyButton](winrt::Windows::Foundation::IInspectable const& sender, RoutedEventArgs const&) { 319 OnClickAccept(keyboardManagerState, applyButton.XamlRoot(), ApplyRemappings); 320 }); 321 322 header.Children().Append(headerText); 323 header.Children().Append(applyButton); 324 header.Children().Append(cancelButton); 325 326 ScrollViewer scrollViewer; 327 scrollViewer.VerticalScrollMode(ScrollMode::Enabled); 328 scrollViewer.HorizontalScrollMode(ScrollMode::Enabled); 329 scrollViewer.VerticalScrollBarVisibility(ScrollBarVisibility::Auto); 330 scrollViewer.HorizontalScrollBarVisibility(ScrollBarVisibility::Auto); 331 332 // Add remap key button 333 Windows::UI::Xaml::Controls::Button addRemapKey; 334 addRemapKey.Margin({ 10, 10, 0, 25 }); 335 addRemapKey.Style(AccentButtonStyle()); 336 addRemapKey.Click([&](winrt::Windows::Foundation::IInspectable const& sender, RoutedEventArgs const&) { 337 SingleKeyRemapControl::AddNewControlKeyRemapRow(keyRemapTable, keyboardRemapControlObjects); 338 339 // Whenever a remap is added move to the bottom of the screen 340 scrollViewer.ChangeView(nullptr, scrollViewer.ScrollableHeight(), nullptr); 341 342 // Set focus to the first "Select" Button in the newly added row 343 UIHelpers::SetFocusOnFirstSelectButtonInLastRowOfEditKeyboardWindow(keyRemapTable, EditorConstants::RemapTableColCount); 344 }); 345 346 // Remap key button content 347 StackPanel addRemapKeyContent; 348 addRemapKeyContent.Orientation(Orientation::Horizontal); 349 addRemapKeyContent.Spacing(10); 350 addRemapKeyContent.Children().Append(SymbolIcon(Symbol::Add)); 351 TextBlock addRemapKeyText; 352 addRemapKeyText.Text(GET_RESOURCE_STRING(IDS_ADD_KEY_REMAP_BUTTON)); 353 addRemapKeyContent.Children().Append(addRemapKeyText); 354 addRemapKey.Content(addRemapKeyContent); 355 356 // Set accessible name for the addRemapKey button 357 addRemapKey.SetValue(Automation::AutomationProperties::NameProperty(), box_value(GET_RESOURCE_STRING(IDS_ADD_KEY_REMAP_BUTTON))); 358 359 // Header and example text at the top of the window 360 StackPanel helperText; 361 helperText.Children().Append(keyRemapInfoHeader); 362 helperText.Children().Append(keyRemapInfoExample); 363 364 // Remapping table 365 StackPanel mappingsPanel; 366 mappingsPanel.Children().Append(tableHeader); 367 mappingsPanel.Children().Append(keyRemapTable); 368 mappingsPanel.Children().Append(addRemapKey); 369 370 // Remapping table should be scrollable 371 scrollViewer.Content(mappingsPanel); 372 373 // Creating the Xaml content. xamlContainer is the parent UI element 374 RelativePanel xamlContainer; 375 xamlContainer.SetBelow(helperText, header); 376 xamlContainer.SetBelow(scrollViewer, helperText); 377 xamlContainer.SetAlignLeftWithPanel(header, true); 378 xamlContainer.SetAlignRightWithPanel(header, true); 379 xamlContainer.SetAlignLeftWithPanel(helperText, true); 380 xamlContainer.SetAlignRightWithPanel(helperText, true); 381 xamlContainer.SetAlignLeftWithPanel(scrollViewer, true); 382 xamlContainer.SetAlignRightWithPanel(scrollViewer, true); 383 xamlContainer.Children().Append(header); 384 xamlContainer.Children().Append(helperText); 385 xamlContainer.Children().Append(scrollViewer); 386 try 387 { 388 // If a layout update has been triggered by other methods (e.g.: adapting to zoom level), this may throw an exception. 389 xamlContainer.UpdateLayout(); 390 } 391 catch (...) 392 { 393 } 394 395 UserControl xamlContent; 396 xamlContent.Content(xamlContainer); 397 if (Windows::Foundation::Metadata::ApiInformation::IsTypePresent(L"Windows.UI.Composition.ICompositionSupportsSystemBackdrop")) 398 { 399 // Apply Mica 400 muxc::BackdropMaterial::SetApplyToRootOrPageBackground(xamlContent, true); 401 } 402 else 403 { 404 // Mica isn't available 405 xamlContainer.Background(Application::Current().Resources().Lookup(box_value(L"ApplicationPageBackgroundThemeBrush")).as<Media::SolidColorBrush>()); 406 } 407 Window::Current().Content(xamlContent); 408 409 ////End XAML Island section 410 if (_hWndEditKeyboardWindow) 411 { 412 ShowWindow(_hWndEditKeyboardWindow, SW_SHOW); 413 UpdateWindow(_hWndEditKeyboardWindow); 414 } 415 416 // Message loop: 417 xamlBridge.MessageLoop(); 418 419 // Reset pointers to nullptr 420 xamlBridgePtr = nullptr; 421 hWndXamlIslandEditKeyboardWindow = nullptr; 422 hwndLock.lock(); 423 theme_listener.DelChangedHandler(handleTheme); 424 hwndEditKeyboardNativeWindow = nullptr; 425 keyboardManagerState.ResetUIState(); 426 keyboardManagerState.ClearRegisteredKeyDelays(); 427 } 428 429 void CreateEditKeyboardWindow(HINSTANCE hInst, KBMEditor::KeyboardManagerState& keyboardManagerState, MappingConfiguration& mappingConfiguration) 430 { 431 Shared::Trace::ETWTrace trace; 432 trace.UpdateState(true); 433 434 // Move implementation into the separate method so resources get destroyed correctly 435 CreateEditKeyboardWindowImpl(hInst, keyboardManagerState, mappingConfiguration); 436 437 // Calling ClearXamlIslands() outside of the message loop is not enough to prevent 438 // Microsoft.UI.XAML.dll from crashing during deinitialization, see https://github.com/microsoft/PowerToys/issues/10906 439 trace.Flush(); 440 Logger::trace("Terminating process {}", GetCurrentProcessId()); 441 Logger::flush(); 442 TerminateProcess(GetCurrentProcess(), 0); 443 } 444 445 LRESULT CALLBACK EditKeyboardWindowProc(HWND hWnd, UINT messageCode, WPARAM wParam, LPARAM lParam) 446 { 447 switch (messageCode) 448 { 449 // Resize the XAML window whenever the parent window is painted or resized 450 case WM_PAINT: 451 case WM_SIZE: 452 { 453 RECT rect = { 0 }; 454 GetClientRect(hWnd, &rect); 455 SetWindowPos(hWndXamlIslandEditKeyboardWindow, 0, rect.left, rect.top, rect.right, rect.bottom, SWP_SHOWWINDOW); 456 } 457 break; 458 // To avoid UI elements overlapping on making the window smaller enforce a minimum window size 459 case WM_GETMINMAXINFO: 460 { 461 LPMINMAXINFO mmi = reinterpret_cast<LPMINMAXINFO>(lParam); 462 float minWidth = EditorConstants::MinimumEditKeyboardWindowWidth; 463 float minHeight = EditorConstants::MinimumEditKeyboardWindowHeight; 464 DPIAware::Convert(MonitorFromWindow(hWnd, MONITOR_DEFAULTTONULL), minWidth, minHeight); 465 mmi->ptMinTrackSize.x = static_cast<LONG>(minWidth); 466 mmi->ptMinTrackSize.y = static_cast<LONG>(minHeight); 467 } 468 break; 469 case WM_GETDPISCALEDSIZE: 470 { 471 UINT newDPI = static_cast<UINT>(wParam); 472 SIZE* size = reinterpret_cast<SIZE*>(lParam); 473 Logger::trace(L"WM_GETDPISCALEDSIZE: DPI {} size X {} Y {}", newDPI, size->cx, size->cy); 474 475 float scalingFactor = static_cast<float>(newDPI) / g_currentDPI; 476 Logger::trace(L"WM_GETDPISCALEDSIZE: scaling factor {}", scalingFactor); 477 478 size->cx = static_cast<LONG>(size->cx * scalingFactor); 479 size->cy = static_cast<LONG>(size->cy * scalingFactor); 480 481 return 1; 482 } 483 break; 484 case WM_DPICHANGED: 485 { 486 UINT newDPI = static_cast<UINT>(LOWORD(wParam)); 487 g_currentDPI = newDPI; 488 489 RECT* rect = reinterpret_cast<RECT*>(lParam); 490 SetWindowPos( 491 hWnd, 492 nullptr, 493 rect->left, 494 rect->top, 495 rect->right - rect->left, 496 rect->bottom - rect->top, 497 SWP_NOZORDER | SWP_NOACTIVATE); 498 499 Logger::trace(L"WM_DPICHANGED: new dpi {} rect {} {} ", newDPI, rect->right - rect->left, rect->bottom - rect->top); 500 } 501 break; 502 default: 503 // If the Xaml Bridge object exists, then use its message handler to handle keyboard focus operations 504 if (xamlBridgePtr != nullptr) 505 { 506 return xamlBridgePtr->MessageHandler(messageCode, wParam, lParam); 507 } 508 else if (messageCode == WM_NCDESTROY) 509 { 510 PostQuitMessage(0); 511 break; 512 } 513 514 return DefWindowProc(hWnd, messageCode, wParam, lParam); 515 break; 516 } 517 518 return 0; 519 } 520 521 // Function to check if there is already a window active if yes bring to foreground 522 bool CheckEditKeyboardWindowActive() 523 { 524 bool result = false; 525 std::unique_lock<std::mutex> hwndLock(editKeyboardWindowMutex); 526 if (hwndEditKeyboardNativeWindow != nullptr) 527 { 528 // Check if the window is minimized if yes then restore the window. 529 if (IsIconic(hwndEditKeyboardNativeWindow)) 530 { 531 ShowWindow(hwndEditKeyboardNativeWindow, SW_RESTORE); 532 } 533 534 // If there is an already existing window no need to create a new open bring it on foreground. 535 SetForegroundWindow(hwndEditKeyboardNativeWindow); 536 result = true; 537 } 538 539 return result; 540 } 541 542 // Function to close any active Edit Keyboard window 543 void CloseActiveEditKeyboardWindow() 544 { 545 std::unique_lock<std::mutex> hwndLock(editKeyboardWindowMutex); 546 if (hwndEditKeyboardNativeWindow != nullptr) 547 { 548 Logger::trace("CloseActiveEditKeyboardWindow()"); 549 PostMessage(hwndEditKeyboardNativeWindow, WM_CLOSE, 0, 0); 550 } 551 }