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  }