From 607c6f0ed76caa817d66939095c888e55e31abf4 Mon Sep 17 00:00:00 2001 From: Jeff Lord Date: Wed, 18 Oct 2023 23:15:47 -0400 Subject: [PATCH] progress, can't change or make new run-program items. --- .../KeyboardManagerEditor.cpp | 5 + .../KeyboardManagerEditor.h | 1 + .../KeyboardManagerEditor.vcxproj | 4 +- .../KeyboardManagerEditor/Resources.resx | 3 + .../EditKeyboardWindow.cpp | 2 +- .../EditRunProgramsWindow.cpp | 482 +++++++++++++++++ .../EditRunProgramsWindow.h | 17 + .../EditShortcutsWindow.cpp | 2 +- .../EditorConstants.h | 20 + .../KeyboardManagerEditorLibrary.vcxproj | 5 + ...yboardManagerEditorLibrary.vcxproj.filters | 15 + .../KeyboardManagerState.cpp | 9 + .../KeyboardManagerState.h | 14 +- .../LoadingAndSavingRemappingHelper.cpp | 98 +++- .../LoadingAndSavingRemappingHelper.h | 4 + .../RunProgramControl.cpp | 507 ++++++++++++++++++ .../RunProgramControl.h | 65 +++ .../RunProgramErrorType.h | 24 + .../common/KeyboardManagerConstants.h | 3 + .../common/MappingConfiguration.cpp | 100 +++- .../common/MappingConfiguration.h | 18 + src/modules/keyboardmanager/common/Shortcut.h | 2 +- src/runner/powertoy_module.cpp | 2 +- .../ShortcutsKeyDataModel.cs | 4 + .../Views/KeyboardManagerPage.xaml | 65 +++ .../ViewModels/KeyboardManagerViewModel.cs | 26 + 26 files changed, 1489 insertions(+), 8 deletions(-) create mode 100644 src/modules/keyboardmanager/KeyboardManagerEditorLibrary/EditRunProgramsWindow.cpp create mode 100644 src/modules/keyboardmanager/KeyboardManagerEditorLibrary/EditRunProgramsWindow.h create mode 100644 src/modules/keyboardmanager/KeyboardManagerEditorLibrary/RunProgramControl.cpp create mode 100644 src/modules/keyboardmanager/KeyboardManagerEditorLibrary/RunProgramControl.h create mode 100644 src/modules/keyboardmanager/KeyboardManagerEditorLibrary/RunProgramErrorType.h diff --git a/src/modules/keyboardmanager/KeyboardManagerEditor/KeyboardManagerEditor.cpp b/src/modules/keyboardmanager/KeyboardManagerEditor/KeyboardManagerEditor.cpp index 54e2c7087d05..3b9643311769 100644 --- a/src/modules/keyboardmanager/KeyboardManagerEditor/KeyboardManagerEditor.cpp +++ b/src/modules/keyboardmanager/KeyboardManagerEditor/KeyboardManagerEditor.cpp @@ -16,6 +16,7 @@ #include #include +#include #include std::unique_ptr editor = nullptr; @@ -158,6 +159,10 @@ void KeyboardManagerEditor::OpenEditorWindow(KeyboardManagerEditorType type) break; case KeyboardManagerEditorType::ShortcutEditor: CreateEditShortcutsWindow(hInstance, keyboardManagerState, mappingConfiguration); + break; + case KeyboardManagerEditorType::RunProgramEditor: + CreateEditRunProgramsWindow(hInstance, keyboardManagerState, mappingConfiguration); + break; } } diff --git a/src/modules/keyboardmanager/KeyboardManagerEditor/KeyboardManagerEditor.h b/src/modules/keyboardmanager/KeyboardManagerEditor/KeyboardManagerEditor.h index e6c64df3fd82..87037cb4d5d9 100644 --- a/src/modules/keyboardmanager/KeyboardManagerEditor/KeyboardManagerEditor.h +++ b/src/modules/keyboardmanager/KeyboardManagerEditor/KeyboardManagerEditor.h @@ -9,6 +9,7 @@ enum class KeyboardManagerEditorType { KeyEditor = 0, ShortcutEditor, + RunProgramEditor, }; class KeyboardManagerEditor diff --git a/src/modules/keyboardmanager/KeyboardManagerEditor/KeyboardManagerEditor.vcxproj b/src/modules/keyboardmanager/KeyboardManagerEditor/KeyboardManagerEditor.vcxproj index 3af18021f0a7..48626f4b5d6f 100644 --- a/src/modules/keyboardmanager/KeyboardManagerEditor/KeyboardManagerEditor.vcxproj +++ b/src/modules/keyboardmanager/KeyboardManagerEditor/KeyboardManagerEditor.vcxproj @@ -152,7 +152,9 @@ - + + Designer + diff --git a/src/modules/keyboardmanager/KeyboardManagerEditor/Resources.resx b/src/modules/keyboardmanager/KeyboardManagerEditor/Resources.resx index 1239cc778682..c5bf6569ad42 100644 --- a/src/modules/keyboardmanager/KeyboardManagerEditor/Resources.resx +++ b/src/modules/keyboardmanager/KeyboardManagerEditor/Resources.resx @@ -144,6 +144,9 @@ Remap shortcuts + + Run program shortcuts + OK diff --git a/src/modules/keyboardmanager/KeyboardManagerEditorLibrary/EditKeyboardWindow.cpp b/src/modules/keyboardmanager/KeyboardManagerEditorLibrary/EditKeyboardWindow.cpp index 2c4f2c681cfc..d11a55de4cf5 100644 --- a/src/modules/keyboardmanager/KeyboardManagerEditorLibrary/EditKeyboardWindow.cpp +++ b/src/modules/keyboardmanager/KeyboardManagerEditorLibrary/EditKeyboardWindow.cpp @@ -285,7 +285,7 @@ inline void CreateEditKeyboardWindowImpl(HINSTANCE hInst, KBMEditor::KeyboardMan SingleKeyRemapTable singleKeyRemapCopy = mappingConfiguration.singleKeyReMap; LoadingAndSavingRemappingHelper::PreProcessRemapTable(singleKeyRemapCopy); - + for (const auto& it : singleKeyRemapCopy) { SingleKeyRemapControl::AddNewControlKeyRemapRow(keyRemapTable, keyboardRemapControlObjects, it.first, it.second); diff --git a/src/modules/keyboardmanager/KeyboardManagerEditorLibrary/EditRunProgramsWindow.cpp b/src/modules/keyboardmanager/KeyboardManagerEditorLibrary/EditRunProgramsWindow.cpp new file mode 100644 index 000000000000..307068e111e5 --- /dev/null +++ b/src/modules/keyboardmanager/KeyboardManagerEditorLibrary/EditRunProgramsWindow.cpp @@ -0,0 +1,482 @@ +#include "pch.h" +#include "EditRunProgramsWindow.h" + +#include +#include +#include +#include + +#include "KeyboardManagerState.h" +#include "Dialog.h" +#include "KeyDropDownControl.h" +#include "LoadingAndSavingRemappingHelper.h" +#include "RunProgramControl.h" +#include "Styles.h" +#include "UIHelpers.h" +#include "XamlBridge.h" +#include "RunProgramErrorType.h" +#include "EditorConstants.h" + +using namespace winrt::Windows::Foundation; + +static UINT g_currentDPI = DPIAware::DEFAULT_DPI; + +LRESULT CALLBACK EditRunProgramsWindowProc(HWND, UINT, WPARAM, LPARAM); + +// This Hwnd will be the window handler for the Xaml Island: A child window that contains Xaml. +HWND hWndXamlIslandEditRunProgramsWindow = nullptr; + +// This variable is used to check if window registration has been done to avoid repeated registration leading to an error. +bool isEditRunProgramsWindowRegistrationCompleted = false; + +// Holds the native window handle of EditRunPrograms Window. +HWND hwndEditRunProgramsNativeWindow = nullptr; +std::mutex editRunProgramsWindowMutex; + +// Stores a pointer to the Xaml Bridge object so that it can be accessed from the window procedure +static XamlBridge* xamlBridgePtr = nullptr; + +static IAsyncAction OnClickAccept( + KBMEditor::KeyboardManagerState& keyboardManagerState, + XamlRoot root, + std::function ApplyRemappings) +{ + RunProgramErrorType isSuccess = LoadingAndSavingRemappingHelper::CheckIfRunProgramsAreValid(RunProgramControl::runProgramRemapBuffer); + + if (isSuccess != RunProgramErrorType::NoError) + { + if (!co_await Dialog::PartialRemappingConfirmationDialog(root, GET_RESOURCE_STRING(IDS_EDITSHORTCUTS_PARTIALCONFIRMATIONDIALOGTITLE))) + { + co_return; + } + } + ApplyRemappings(); +} + +// Function to create the Edit RunPrograms Window +inline void CreateEditRunProgramsWindowImpl(HINSTANCE hInst, KBMEditor::KeyboardManagerState& keyboardManagerState, MappingConfiguration& mappingConfiguration) +{ + Logger::trace("CreateEditRunProgramsWindowImpl()"); + auto locker = EventLocker::Get(KeyboardManagerConstants::EditorWindowEventName.c_str()); + if (!locker.has_value()) + { + Logger::error(L"Failed to lock event {}. {}", KeyboardManagerConstants::EditorWindowEventName, get_last_error_or_default(GetLastError())); + } + + Logger::trace(L"Signaled {} event to suspend the KBM engine", KeyboardManagerConstants::EditorWindowEventName); + + // Window Registration + const wchar_t szWindowClass[] = L"EditRunProgramsWindow"; + + if (!isEditRunProgramsWindowRegistrationCompleted) + { + WNDCLASSEX windowClass = {}; + windowClass.cbSize = sizeof(WNDCLASSEX); + windowClass.lpfnWndProc = EditRunProgramsWindowProc; + windowClass.hInstance = hInst; + windowClass.lpszClassName = szWindowClass; + windowClass.hbrBackground = reinterpret_cast(COLOR_WINDOW); + windowClass.hIcon = static_cast(LoadImageW( + windowClass.hInstance, + MAKEINTRESOURCE(IDS_KEYBOARDMANAGER_ICON), + IMAGE_ICON, + 48, + 48, + LR_DEFAULTCOLOR)); + if (RegisterClassEx(&windowClass) == NULL) + { + MessageBox(NULL, GET_RESOURCE_STRING(IDS_REGISTERCLASSFAILED_ERRORMESSAGE).c_str(), GET_RESOURCE_STRING(IDS_REGISTERCLASSFAILED_ERRORTITLE).c_str(), NULL); + return; + } + + isEditRunProgramsWindowRegistrationCompleted = true; + } + + // Find coordinates of the screen where the settings window is placed. + RECT desktopRect = UIHelpers::GetForegroundWindowDesktopRect(); + + // Calculate DPI dependent window size + float windowWidth = EditorConstants::DefaultEditRunProgramsWindowWidth; + float windowHeight = EditorConstants::DefaultEditRunProgramsWindowHeight; + DPIAware::ConvertByCursorPosition(windowWidth, windowHeight); + DPIAware::GetScreenDPIForCursor(g_currentDPI); + + // Window Creation + HWND _hWndEditRunProgramsWindow = CreateWindow( + szWindowClass, + GET_RESOURCE_STRING(IDS_EDITRUNPROGRAMS_WINDOWNAME).c_str(), + WS_OVERLAPPED | WS_CAPTION | WS_SYSMENU | WS_THICKFRAME | WS_MAXIMIZEBOX, + ((desktopRect.right + desktopRect.left) / 2) - ((int)windowWidth / 2), + ((desktopRect.bottom + desktopRect.top) / 2) - ((int)windowHeight / 2), + static_cast(windowWidth), + static_cast(windowHeight), + NULL, + NULL, + hInst, + NULL); + + if (_hWndEditRunProgramsWindow == NULL) + { + MessageBox(NULL, GET_RESOURCE_STRING(IDS_CREATEWINDOWFAILED_ERRORMESSAGE).c_str(), GET_RESOURCE_STRING(IDS_CREATEWINDOWFAILED_ERRORTITLE).c_str(), NULL); + return; + } + + // 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. + if (_hWndEditRunProgramsWindow) + { + SetForegroundWindow(_hWndEditRunProgramsWindow); + } + + // Store the newly created Edit RunPrograms window's handle. + std::unique_lock hwndLock(editRunProgramsWindowMutex); + hwndEditRunProgramsNativeWindow = _hWndEditRunProgramsWindow; + hwndLock.unlock(); + + // Create the xaml bridge object + XamlBridge xamlBridge(_hWndEditRunProgramsWindow); + + // DesktopSource needs to be declared before the RelativePanel xamlContainer object to avoid errors + winrt::Windows::UI::Xaml::Hosting::DesktopWindowXamlSource desktopSource; + + // Create the desktop window xaml source object and set its content + hWndXamlIslandEditRunProgramsWindow = xamlBridge.InitDesktopWindowsXamlSource(desktopSource); + + // Set the pointer to the xaml bridge object + xamlBridgePtr = &xamlBridge; + + // Header for the window + Windows::UI::Xaml::Controls::RelativePanel header; + header.Margin({ 10, 10, 10, 30 }); + + // Header text + TextBlock headerText; + headerText.Text(GET_RESOURCE_STRING(IDS_EDITRUNPROGRAMS_WINDOWNAME)); + headerText.FontSize(30); + headerText.Margin({ 0, 0, 0, 0 }); + header.SetAlignLeftWithPanel(headerText, true); + + // Cancel button + Button cancelButton; + cancelButton.Content(winrt::box_value(GET_RESOURCE_STRING(IDS_CANCEL_BUTTON))); + cancelButton.Margin({ 10, 0, 0, 0 }); + cancelButton.Click([&](winrt::Windows::Foundation::IInspectable const& sender, RoutedEventArgs const&) { + // Close the window since settings do not need to be saved + PostMessage(_hWndEditRunProgramsWindow, WM_CLOSE, 0, 0); + }); + + // Text block for information about remap key section. + TextBlock runProgramRemapInfoHeader; + runProgramRemapInfoHeader.Text(GET_RESOURCE_STRING(IDS_EDITSHORTCUTS_INFO)); + runProgramRemapInfoHeader.Margin({ 10, 0, 0, 10 }); + runProgramRemapInfoHeader.FontWeight(Text::FontWeights::SemiBold()); + runProgramRemapInfoHeader.TextWrapping(TextWrapping::Wrap); + + TextBlock runProgramRemapInfoExample; + runProgramRemapInfoExample.Text(GET_RESOURCE_STRING(IDS_EDITSHORTCUTS_INFOEXAMPLE)); + runProgramRemapInfoExample.Margin({ 10, 0, 0, 20 }); + runProgramRemapInfoExample.FontStyle(Text::FontStyle::Italic); + runProgramRemapInfoExample.TextWrapping(TextWrapping::Wrap); + + // Table to display the runPrograms + StackPanel runProgramTable; + + // First header textblock in the header row of the runProgram table + TextBlock originalRunProgramHeader; + originalRunProgramHeader.Text(GET_RESOURCE_STRING(IDS_EDITSHORTCUTS_SOURCEHEADER)); + originalRunProgramHeader.FontWeight(Text::FontWeights::Bold()); + + // Second header textblock in the header row of the runProgram table + TextBlock newRunProgramHeader; + newRunProgramHeader.Text(GET_RESOURCE_STRING(IDS_EDITSHORTCUTS_TARGETHEADER)); + newRunProgramHeader.FontWeight(Text::FontWeights::Bold()); + + // Third header textblock in the header row of the runProgram table + TextBlock targetAppHeader; + targetAppHeader.Text(GET_RESOURCE_STRING(IDS_EDITSHORTCUTS_TARGETAPPHEADER)); + targetAppHeader.FontWeight(Text::FontWeights::Bold()); + targetAppHeader.HorizontalAlignment(HorizontalAlignment::Center); + + StackPanel tableHeader = StackPanel(); + tableHeader.Orientation(Orientation::Horizontal); + tableHeader.Margin({ 10, 0, 0, 10 }); + auto originalRunProgramContainer = UIHelpers::GetWrapped(originalRunProgramHeader, EditorConstants::RunProgramOriginColumnWidth + static_cast(EditorConstants::RunProgramArrowColumnWidth)); + tableHeader.Children().Append(originalRunProgramContainer.as()); + auto newRunProgramHeaderContainer = UIHelpers::GetWrapped(newRunProgramHeader, EditorConstants::RunProgramTargetColumnWidth); + tableHeader.Children().Append(newRunProgramHeaderContainer.as()); + tableHeader.Children().Append(targetAppHeader); + + // Store handle of edit runPrograms window + RunProgramControl::editRunProgramsWindowHandle = _hWndEditRunProgramsWindow; + + // Store keyboard manager state + RunProgramControl::keyboardManagerState = &keyboardManagerState; + KeyDropDownControl::keyboardManagerState = &keyboardManagerState; + KeyDropDownControl::mappingConfiguration = &mappingConfiguration; + + // Clear the runProgram remap buffer + RunProgramControl::runProgramRemapBuffer.clear(); + + // Vector to store dynamically allocated control objects to avoid early destruction + std::vector>> keyboardRemapControlObjects; + + // Set keyboard manager UI state so that runProgram remaps are not applied while on this window + keyboardManagerState.SetUIState(KBMEditor::KeyboardManagerUIState::RunProgramsWindowActivated, _hWndEditRunProgramsWindow); + + // Load existing os level runPrograms into UI + // Create copy of the remaps to avoid concurrent access + // RunProgramRemapTable osLevelRunProgramReMapCopy = mappingConfiguration.osLevelRunProgramReMap; + + //// Add all + //for (const auto& it : osLevelRunProgramReMapCopy) + //{ + // // TODO: JLO FIX targetShortcut + // RunProgramControl::AddNewRunProgramControlRow(runProgramTable, keyboardRemapControlObjects, it.first, it.second.targetShortcut); + //} + + // Load existing app-specific runPrograms into UI + // Create copy of the remaps to avoid concurrent access + //AppSpecificShortcutRemapTable appSpecificRunProgramReMapCopy = mappingConfiguration.appSpecificShortcutReMap; + AppSpecificShortcutRemapTable appSpecificRunProgramReMapCopy = mappingConfiguration.appSpecificRunProgramReMap; + + // Iterate through all the apps + for (const auto& itApp : appSpecificRunProgramReMapCopy) + { + // Iterate through runPrograms for each app + for (const auto& itRunProgram : itApp.second) + { + RunProgramControl::AddNewRunProgramControlRow(runProgramTable, keyboardRemapControlObjects, itRunProgram.first, itRunProgram.second.targetShortcut, itApp.first); + } + } + + // Apply button + Button applyButton; + applyButton.Content(winrt::box_value(GET_RESOURCE_STRING(IDS_OK_BUTTON))); + applyButton.Style(AccentButtonStyle()); + applyButton.MinWidth(EditorConstants::HeaderButtonWidth); + cancelButton.MinWidth(EditorConstants::HeaderButtonWidth); + header.SetAlignRightWithPanel(cancelButton, true); + header.SetLeftOf(applyButton, cancelButton); + + auto ApplyRemappings = [&mappingConfiguration, _hWndEditRunProgramsWindow]() { + //LoadingAndSavingRemappingHelper::ApplyRunProgramRemappings(mappingConfiguration, RunProgramControl::runProgramRemapBuffer, true); + LoadingAndSavingRemappingHelper::ApplyShortcutRemappings(mappingConfiguration, RunProgramControl::runProgramRemapBuffer, true); + + bool saveResult = mappingConfiguration.SaveSettingsToFile(); + PostMessage(_hWndEditRunProgramsWindow, WM_CLOSE, 0, 0); + }; + + applyButton.Click([&keyboardManagerState, applyButton, ApplyRemappings](winrt::Windows::Foundation::IInspectable const& sender, RoutedEventArgs const&) { + OnClickAccept(keyboardManagerState, applyButton.XamlRoot(), ApplyRemappings); + }); + + header.Children().Append(headerText); + header.Children().Append(applyButton); + header.Children().Append(cancelButton); + + ScrollViewer scrollViewer; + scrollViewer.VerticalScrollMode(ScrollMode::Enabled); + scrollViewer.HorizontalScrollMode(ScrollMode::Enabled); + scrollViewer.VerticalScrollBarVisibility(ScrollBarVisibility::Auto); + scrollViewer.HorizontalScrollBarVisibility(ScrollBarVisibility::Auto); + + // Add runProgram button + Windows::UI::Xaml::Controls::Button addRunProgram; + FontIcon plusSymbol; + plusSymbol.FontFamily(Xaml::Media::FontFamily(L"Segoe MDL2 Assets")); + plusSymbol.Glyph(L"\xE710"); + addRunProgram.Content(plusSymbol); + addRunProgram.Margin({ 10, 10, 0, 25 }); + addRunProgram.Click([&](winrt::Windows::Foundation::IInspectable const& sender, RoutedEventArgs const&) { + RunProgramControl::AddNewRunProgramControlRow(runProgramTable, keyboardRemapControlObjects); + + // Whenever a remap is added move to the bottom of the screen + scrollViewer.ChangeView(nullptr, scrollViewer.ScrollableHeight(), nullptr); + + // Set focus to the first Type Button in the newly added row + UIHelpers::SetFocusOnTypeButtonInLastRow(runProgramTable, EditorConstants::RunProgramTableColCount); + }); + + // Set accessible name for the add runProgram button + addRunProgram.SetValue(Automation::AutomationProperties::NameProperty(), box_value(GET_RESOURCE_STRING(IDS_ADD_SHORTCUT_BUTTON))); + + // Add tooltip for add button which would appear on hover + ToolTip addRunProgramtoolTip; + addRunProgramtoolTip.Content(box_value(GET_RESOURCE_STRING(IDS_ADD_SHORTCUT_BUTTON))); + ToolTipService::SetToolTip(addRunProgram, addRunProgramtoolTip); + + // Header and example text at the top of the window + StackPanel helperText; + helperText.Children().Append(runProgramRemapInfoHeader); + helperText.Children().Append(runProgramRemapInfoExample); + + // Remapping table + StackPanel mappingsPanel; + mappingsPanel.Children().Append(tableHeader); + mappingsPanel.Children().Append(runProgramTable); + mappingsPanel.Children().Append(addRunProgram); + + // Remapping table should be scrollable + scrollViewer.Content(mappingsPanel); + + RelativePanel xamlContainer; + xamlContainer.SetBelow(helperText, header); + xamlContainer.SetBelow(scrollViewer, helperText); + xamlContainer.SetAlignLeftWithPanel(header, true); + xamlContainer.SetAlignRightWithPanel(header, true); + xamlContainer.SetAlignLeftWithPanel(helperText, true); + xamlContainer.SetAlignRightWithPanel(helperText, true); + xamlContainer.SetAlignLeftWithPanel(scrollViewer, true); + xamlContainer.SetAlignRightWithPanel(scrollViewer, true); + xamlContainer.Children().Append(header); + xamlContainer.Children().Append(helperText); + xamlContainer.Children().Append(scrollViewer); + try + { + // If a layout update has been triggered by other methods (e.g.: adapting to zoom level), this may throw an exception. + xamlContainer.UpdateLayout(); + } + catch (...) + { + } + + desktopSource.Content(xamlContainer); + + ////End XAML Island section + if (_hWndEditRunProgramsWindow) + { + ShowWindow(_hWndEditRunProgramsWindow, SW_SHOW); + UpdateWindow(_hWndEditRunProgramsWindow); + } + + // Message loop: + xamlBridge.MessageLoop(); + + // Reset pointers to nullptr + xamlBridgePtr = nullptr; + hWndXamlIslandEditRunProgramsWindow = nullptr; + hwndLock.lock(); + hwndEditRunProgramsNativeWindow = nullptr; + keyboardManagerState.ResetUIState(); + keyboardManagerState.ClearRegisteredKeyDelays(); + + // Cannot be done in WM_DESTROY because that causes crashes due to fatal app exit + xamlBridge.ClearXamlIslands(); +} + +void CreateEditRunProgramsWindow(HINSTANCE hInst, KBMEditor::KeyboardManagerState& keyboardManagerState, MappingConfiguration& mappingConfiguration) +{ + // Move implementation into the separate method so resources get destroyed correctly + CreateEditRunProgramsWindowImpl(hInst, keyboardManagerState, mappingConfiguration); + + // Calling ClearXamlIslands() outside of the message loop is not enough to prevent + // Microsoft.UI.XAML.dll from crashing during deinitialization, see https://github.com/microsoft/PowerToys/issues/10906 + Logger::trace("Terminating process {}", GetCurrentProcessId()); + Logger::flush(); + TerminateProcess(GetCurrentProcess(), 0); +} + +LRESULT CALLBACK EditRunProgramsWindowProc(HWND hWnd, UINT messageCode, WPARAM wParam, LPARAM lParam) +{ + switch (messageCode) + { + // Resize the XAML window whenever the parent window is painted or resized + case WM_PAINT: + case WM_SIZE: + { + RECT rect = { 0 }; + GetClientRect(hWnd, &rect); + SetWindowPos(hWndXamlIslandEditRunProgramsWindow, 0, rect.left, rect.top, rect.right, rect.bottom, SWP_SHOWWINDOW); + } + break; + // To avoid UI elements overlapping on making the window smaller enforce a minimum window size + case WM_GETMINMAXINFO: + { + LPMINMAXINFO mmi = reinterpret_cast(lParam); + float minWidth = EditorConstants::MinimumEditRunProgramsWindowWidth; + float minHeight = EditorConstants::MinimumEditRunProgramsWindowHeight; + DPIAware::Convert(MonitorFromWindow(hWnd, MONITOR_DEFAULTTONULL), minWidth, minHeight); + mmi->ptMinTrackSize.x = static_cast(minWidth); + mmi->ptMinTrackSize.y = static_cast(minHeight); + } + break; + case WM_GETDPISCALEDSIZE: + { + UINT newDPI = static_cast(wParam); + SIZE* size = reinterpret_cast(lParam); + Logger::trace(L"WM_GETDPISCALEDSIZE: DPI {} size X {} Y {}", newDPI, size->cx, size->cy); + + float scalingFactor = static_cast(newDPI) / g_currentDPI; + Logger::trace(L"WM_GETDPISCALEDSIZE: scaling factor {}", scalingFactor); + + size->cx = static_cast(size->cx * scalingFactor); + size->cy = static_cast(size->cy * scalingFactor); + + return 1; + } + break; + case WM_DPICHANGED: + { + UINT newDPI = static_cast(LOWORD(wParam)); + g_currentDPI = newDPI; + + RECT* rect = reinterpret_cast(lParam); + SetWindowPos( + hWnd, + nullptr, + rect->left, + rect->top, + rect->right - rect->left, + rect->bottom - rect->top, + SWP_NOZORDER | SWP_NOACTIVATE); + + Logger::trace(L"WM_DPICHANGED: new dpi {} rect {} {} ", newDPI, rect->right - rect->left, rect->bottom - rect->top); + } + break; + default: + // If the Xaml Bridge object exists, then use it's message handler to handle keyboard focus operations + if (xamlBridgePtr != nullptr) + { + return xamlBridgePtr->MessageHandler(messageCode, wParam, lParam); + } + else if (messageCode == WM_NCDESTROY) + { + PostQuitMessage(0); + break; + } + return DefWindowProc(hWnd, messageCode, wParam, lParam); + break; + } + + return 0; +} + +// Function to check if there is already a window active if yes bring to foreground +bool CheckEditRunProgramsWindowActive() +{ + bool result = false; + std::unique_lock hwndLock(editRunProgramsWindowMutex); + if (hwndEditRunProgramsNativeWindow != nullptr) + { + // Check if the window is minimized if yes then restore the window. + if (IsIconic(hwndEditRunProgramsNativeWindow)) + { + ShowWindow(hwndEditRunProgramsNativeWindow, SW_RESTORE); + } + + // If there is an already existing window no need to create a new open bring it on foreground. + SetForegroundWindow(hwndEditRunProgramsNativeWindow); + result = true; + } + + return result; +} + +// Function to close any active Edit RunPrograms window +void CloseActiveEditRunProgramsWindow() +{ + std::unique_lock hwndLock(editRunProgramsWindowMutex); + if (hwndEditRunProgramsNativeWindow != nullptr) + { + PostMessage(hwndEditRunProgramsNativeWindow, WM_CLOSE, 0, 0); + } +} diff --git a/src/modules/keyboardmanager/KeyboardManagerEditorLibrary/EditRunProgramsWindow.h b/src/modules/keyboardmanager/KeyboardManagerEditorLibrary/EditRunProgramsWindow.h new file mode 100644 index 000000000000..8eab9111e79a --- /dev/null +++ b/src/modules/keyboardmanager/KeyboardManagerEditorLibrary/EditRunProgramsWindow.h @@ -0,0 +1,17 @@ +#pragma once + +namespace KBMEditor +{ + class KeyboardManagerState; +} + +class MappingConfiguration; + +// Function to create the Edit Keyboard Window +void CreateEditRunProgramsWindow(HINSTANCE hInst, KBMEditor::KeyboardManagerState& keyboardManagerState, MappingConfiguration& mappingConfiguration); + +// Function to check if there is already a window active if yes bring to foreground +bool CheckEditRunProgramsWindowActive(); + +// Function to close any active Edit Keyboard window +void CloseActiveEditRunProgramsWindow(); \ No newline at end of file diff --git a/src/modules/keyboardmanager/KeyboardManagerEditorLibrary/EditShortcutsWindow.cpp b/src/modules/keyboardmanager/KeyboardManagerEditorLibrary/EditShortcutsWindow.cpp index d312e0091abb..a41853d7aa77 100644 --- a/src/modules/keyboardmanager/KeyboardManagerEditorLibrary/EditShortcutsWindow.cpp +++ b/src/modules/keyboardmanager/KeyboardManagerEditorLibrary/EditShortcutsWindow.cpp @@ -225,7 +225,7 @@ inline void CreateEditShortcutsWindowImpl(HINSTANCE hInst, KBMEditor::KeyboardMa // Load existing os level shortcuts into UI // Create copy of the remaps to avoid concurrent access ShortcutRemapTable osLevelShortcutReMapCopy = mappingConfiguration.osLevelShortcutReMap; - + for (const auto& it : osLevelShortcutReMapCopy) { ShortcutControl::AddNewShortcutControlRow(shortcutTable, keyboardRemapControlObjects, it.first, it.second.targetShortcut); diff --git a/src/modules/keyboardmanager/KeyboardManagerEditorLibrary/EditorConstants.h b/src/modules/keyboardmanager/KeyboardManagerEditorLibrary/EditorConstants.h index ca36c7aa3364..5d36db22a15d 100644 --- a/src/modules/keyboardmanager/KeyboardManagerEditorLibrary/EditorConstants.h +++ b/src/modules/keyboardmanager/KeyboardManagerEditorLibrary/EditorConstants.h @@ -14,6 +14,12 @@ namespace EditorConstants inline const int MinimumEditShortcutsWindowHeight = 500; inline const int EditShortcutsTableMinWidth = 1000; + inline const int DefaultEditRunProgramsWindowWidth = 1050; + inline const int DefaultEditRunProgramsWindowHeight = 600; + inline const int MinimumEditRunProgramsWindowWidth = 500; + inline const int MinimumEditRunProgramsWindowHeight = 500; + inline const int EditRunProgramsTableMinWidth = 1000; + // Key Remap table constants inline const long RemapTableColCount = 4; inline const long RemapTableHeaderCount = 2; @@ -37,6 +43,20 @@ namespace EditorConstants inline const long ShortcutOriginColumnWidth = 3 * ShortcutTableDropDownWidth + 3 * ShortcutTableDropDownSpacing; inline const long ShortcutTargetColumnWidth = 3 * ShortcutTableDropDownWidth + 3 * ShortcutTableDropDownSpacing + 15; + // RunProgram table constants + inline const long RunProgramTableColCount = 5; + inline const long RunProgramTableHeaderCount = 3; + inline const long RunProgramTableOriginalColIndex = 0; + inline const long RunProgramTableArrowColIndex = 1; + inline const long RunProgramTableNewColIndex = 2; + inline const long RunProgramTableTargetAppColIndex = 3; + inline const long RunProgramTableRemoveColIndex = 4; + inline const long RunProgramArrowColumnWidth = 90; + inline const DWORD64 RunProgramTableDropDownWidth = 110; + inline const DWORD64 RunProgramTableDropDownSpacing = 10; + inline const long RunProgramOriginColumnWidth = 3 * RunProgramTableDropDownWidth + 3 * RunProgramTableDropDownSpacing; + inline const long RunProgramTargetColumnWidth = 3 * RunProgramTableDropDownWidth + 3 * RunProgramTableDropDownSpacing + 15; + // Drop down height used for both Edit Keyboard and Edit Shortcuts inline const DWORD64 TableDropDownHeight = 200; inline const DWORD64 TableArrowColWidth = 230; diff --git a/src/modules/keyboardmanager/KeyboardManagerEditorLibrary/KeyboardManagerEditorLibrary.vcxproj b/src/modules/keyboardmanager/KeyboardManagerEditorLibrary/KeyboardManagerEditorLibrary.vcxproj index 1ded6395b159..ad64be7ae8b7 100644 --- a/src/modules/keyboardmanager/KeyboardManagerEditorLibrary/KeyboardManagerEditorLibrary.vcxproj +++ b/src/modules/keyboardmanager/KeyboardManagerEditorLibrary/KeyboardManagerEditorLibrary.vcxproj @@ -38,6 +38,7 @@ + @@ -45,6 +46,8 @@ + + @@ -59,6 +62,7 @@ + @@ -68,6 +72,7 @@ Create + diff --git a/src/modules/keyboardmanager/KeyboardManagerEditorLibrary/KeyboardManagerEditorLibrary.vcxproj.filters b/src/modules/keyboardmanager/KeyboardManagerEditorLibrary/KeyboardManagerEditorLibrary.vcxproj.filters index ec2a3e2bd918..da18efc42c05 100644 --- a/src/modules/keyboardmanager/KeyboardManagerEditorLibrary/KeyboardManagerEditorLibrary.vcxproj.filters +++ b/src/modules/keyboardmanager/KeyboardManagerEditorLibrary/KeyboardManagerEditorLibrary.vcxproj.filters @@ -75,6 +75,15 @@ Header Files + + Header Files + + + Header Files + + + Header Files + @@ -128,6 +137,12 @@ Source Files + + Source Files + + + Source Files + diff --git a/src/modules/keyboardmanager/KeyboardManagerEditorLibrary/KeyboardManagerState.cpp b/src/modules/keyboardmanager/KeyboardManagerEditorLibrary/KeyboardManagerState.cpp index c3a92f4e9486..18db0aacb595 100644 --- a/src/modules/keyboardmanager/KeyboardManagerEditorLibrary/KeyboardManagerState.cpp +++ b/src/modules/keyboardmanager/KeyboardManagerEditorLibrary/KeyboardManagerState.cpp @@ -103,6 +103,15 @@ void KeyboardManagerState::ConfigureDetectShortcutUI(const StackPanel& textBlock currentShortcutUI2 = textBlock2.as(); } +// Function to set the textblock of the detect run program UI so that it can be accessed by the hook +void KeyboardManagerState::ConfigureDetectRunProgramUI(const StackPanel& textBlock1, const StackPanel& textBlock2) +{ + std::lock_guard lock(currentShortcutUI_mutex); + currentShortcutUI1 = textBlock1.as(); + currentShortcutUI2 = textBlock2.as(); +} + + // Function to set the textblock of the detect remap key UI so that it can be accessed by the hook void KeyboardManagerState::ConfigureDetectSingleKeyRemapUI(const StackPanel& textBlock) { diff --git a/src/modules/keyboardmanager/KeyboardManagerEditorLibrary/KeyboardManagerState.h b/src/modules/keyboardmanager/KeyboardManagerEditorLibrary/KeyboardManagerState.h index 906440f1e473..57ccdbf11060 100644 --- a/src/modules/keyboardmanager/KeyboardManagerEditorLibrary/KeyboardManagerState.h +++ b/src/modules/keyboardmanager/KeyboardManagerEditorLibrary/KeyboardManagerState.h @@ -40,7 +40,16 @@ namespace KBMEditor // If set to this value then the detect shortcut window is currently active and it requires a hook DetectShortcutWindowActivated, // If set to this value then the edit shortcuts window is currently active and remaps should not be applied - EditShortcutsWindowActivated + EditShortcutsWindowActivated, + // If set to this value then the edit run-programs window is currently active and this should not be applied + RunProgramsWindowActivated, + + // If set to this value then the detect shortcut window in edit keyboard window is currently active and it requires a hook + DetectRunProgramWindowInEditKeyboardWindowActivated, + // If set to this value then the detect shortcut window is currently active and it requires a hook + DetectRunProgramWindowActivated, + // If set to this value then the edit shortcuts window is currently active and remaps should not be applied + EditRunProgramsWindowActivated, }; // Class to store the shared state of the keyboard manager between the UI and the hook @@ -108,6 +117,9 @@ namespace KBMEditor // Function to set the textblock of the detect shortcut UI so that it can be accessed by the hook void ConfigureDetectShortcutUI(const winrt::Windows::UI::Xaml::Controls::StackPanel& textBlock1, const winrt::Windows::UI::Xaml::Controls::StackPanel& textBlock2); + // Function to set the textblock of the detect run program UI so that it can be accessed by the hook + void ConfigureDetectRunProgramUI(const winrt::Windows::UI::Xaml::Controls::StackPanel& textBlock1, const winrt::Windows::UI::Xaml::Controls::StackPanel& textBlock2); + // Function to set the textblock of the detect remap key UI so that it can be accessed by the hook void ConfigureDetectSingleKeyRemapUI(const winrt::Windows::UI::Xaml::Controls::StackPanel& textBlock); diff --git a/src/modules/keyboardmanager/KeyboardManagerEditorLibrary/LoadingAndSavingRemappingHelper.cpp b/src/modules/keyboardmanager/KeyboardManagerEditorLibrary/LoadingAndSavingRemappingHelper.cpp index c817d328d605..87f8dc2143aa 100644 --- a/src/modules/keyboardmanager/KeyboardManagerEditorLibrary/LoadingAndSavingRemappingHelper.cpp +++ b/src/modules/keyboardmanager/KeyboardManagerEditorLibrary/LoadingAndSavingRemappingHelper.cpp @@ -10,9 +10,47 @@ #include "keyboardmanager/KeyboardManagerEditorLibrary/trace.h" #include "EditorHelpers.h" #include "ShortcutErrorType.h" +#include "RunProgramErrorType.h" namespace LoadingAndSavingRemappingHelper { + // Function to check if the set of run-programs in the buffer are valid + RunProgramErrorType CheckIfRunProgramsAreValid(const RemapBuffer& remappings) + { + RunProgramErrorType isSuccess = RunProgramErrorType::NoError; + std::map> ogKeys; + for (int i = 0; i < remappings.size(); i++) + { + KeyShortcutUnion ogKey = remappings[i].first[0]; + KeyShortcutUnion newKey = remappings[i].first[1]; + std::wstring appName = remappings[i].second; + + bool ogKeyValidity = (ogKey.index() == 0 && std::get(ogKey) != NULL) || (ogKey.index() == 1 && EditorHelpers::IsValidShortcut(std::get(ogKey))); + bool newKeyValidity = (newKey.index() == 0 && std::get(newKey) != NULL) || (newKey.index() == 1 && EditorHelpers::IsValidShortcut(std::get(newKey))); + + // Add new set for a new target app name + if (ogKeys.find(appName) == ogKeys.end()) + { + ogKeys[appName] = std::set(); + } + + if (ogKeyValidity && newKeyValidity && ogKeys[appName].find(ogKey) == ogKeys[appName].end()) + { + ogKeys[appName].insert(ogKey); + } + else if (ogKeyValidity && newKeyValidity && ogKeys[appName].find(ogKey) != ogKeys[appName].end()) + { + isSuccess = RunProgramErrorType::RemapUnsuccessful; + } + else + { + isSuccess = RunProgramErrorType::RemapUnsuccessful; + } + } + + return isSuccess; + } + // Function to check if the set of remappings in the buffer are valid ShortcutErrorType CheckIfRemappingsAreValid(const RemapBuffer& remappings) { @@ -175,6 +213,64 @@ namespace LoadingAndSavingRemappingHelper } } + // Function to apply the RunProgram remappings from the buffer to the KeyboardManagerState variable + void ApplyRunProgramRemappings(MappingConfiguration& mappingConfiguration, const RemapBuffer& remappings, bool isTelemetryRequired) + { + // Clear existing RunPrograms + mappingConfiguration.ClearOSLevelShortcuts(); + DWORD successfulOSLevelRunProgramToRunProgramRemapCount = 0; + DWORD successfulOSLevelRunProgramToKeyRemapCount = 0; + DWORD successfulAppSpecificRunProgramToRunProgramRemapCount = 0; + DWORD successfulAppSpecificRunProgramToKeyRemapCount = 0; + + // Save the RunPrograms that are valid and report if any of them were invalid + for (int i = 0; i < remappings.size(); i++) + { + Shortcut originalRunProgram = std::get(remappings[i].first[0]); + KeyShortcutUnion newRunProgram = remappings[i].first[1]; + + if (EditorHelpers::IsValidShortcut(originalRunProgram) && ((newRunProgram.index() == 0 && std::get(newRunProgram) != NULL) || (newRunProgram.index() == 1 && EditorHelpers::IsValidShortcut(std::get(newRunProgram))))) + { + if (remappings[i].second == L"") + { + bool result = mappingConfiguration.AddOSLevelShortcut(originalRunProgram, newRunProgram); + if (result) + { + if (newRunProgram.index() == 0) + { + successfulOSLevelRunProgramToKeyRemapCount += 1; + } + else + { + successfulOSLevelRunProgramToRunProgramRemapCount += 1; + } + } + } + else + { + bool result = mappingConfiguration.AddAppSpecificShortcut(remappings[i].second, originalRunProgram, newRunProgram); + if (result) + { + if (newRunProgram.index() == 0) + { + successfulAppSpecificRunProgramToKeyRemapCount += 1; + } + else + { + successfulAppSpecificRunProgramToRunProgramRemapCount += 1; + } + } + } + } + } + + // If telemetry is to be logged, log the RunProgram remap counts + if (isTelemetryRequired) + { + Trace::OSLevelShortcutRemapCount(successfulOSLevelRunProgramToRunProgramRemapCount, successfulOSLevelRunProgramToKeyRemapCount); + } + } + // Function to apply the shortcut remappings from the buffer to the KeyboardManagerState variable void ApplyShortcutRemappings(MappingConfiguration& mappingConfiguration, const RemapBuffer& remappings, bool isTelemetryRequired) { @@ -185,7 +281,7 @@ namespace LoadingAndSavingRemappingHelper DWORD successfulOSLevelShortcutToKeyRemapCount = 0; DWORD successfulAppSpecificShortcutToShortcutRemapCount = 0; DWORD successfulAppSpecificShortcutToKeyRemapCount = 0; - + // Save the shortcuts that are valid and report if any of them were invalid for (int i = 0; i < remappings.size(); i++) { diff --git a/src/modules/keyboardmanager/KeyboardManagerEditorLibrary/LoadingAndSavingRemappingHelper.h b/src/modules/keyboardmanager/KeyboardManagerEditorLibrary/LoadingAndSavingRemappingHelper.h index db7b47b74457..cc51fedccc4e 100644 --- a/src/modules/keyboardmanager/KeyboardManagerEditorLibrary/LoadingAndSavingRemappingHelper.h +++ b/src/modules/keyboardmanager/KeyboardManagerEditorLibrary/LoadingAndSavingRemappingHelper.h @@ -3,6 +3,7 @@ #include #include "ShortcutErrorType.h" +#include "RunProgramErrorType.h" class MappingConfiguration; @@ -11,6 +12,9 @@ namespace LoadingAndSavingRemappingHelper // Function to check if the set of remappings in the buffer are valid ShortcutErrorType CheckIfRemappingsAreValid(const RemapBuffer& remappings); + // Function to check if the set of remappings in the buffer are valid + RunProgramErrorType CheckIfRunProgramsAreValid(const RemapBuffer& remappings); + // Function to return the set of keys that have been orphaned from the remap buffer std::vector GetOrphanedKeys(const RemapBuffer& remappings); diff --git a/src/modules/keyboardmanager/KeyboardManagerEditorLibrary/RunProgramControl.cpp b/src/modules/keyboardmanager/KeyboardManagerEditorLibrary/RunProgramControl.cpp new file mode 100644 index 000000000000..7f74d037ef63 --- /dev/null +++ b/src/modules/keyboardmanager/KeyboardManagerEditorLibrary/RunProgramControl.cpp @@ -0,0 +1,507 @@ +#include "pch.h" +#include "RunProgramControl.h" + +#include + +#include "KeyboardManagerState.h" +#include "KeyboardManagerEditorStrings.h" +#include "KeyDropDownControl.h" +#include "UIHelpers.h" +#include "EditorHelpers.h" +#include "EditorConstants.h" + +//Both static members are initialized to null +HWND RunProgramControl::editRunProgramsWindowHandle = nullptr; +KBMEditor::KeyboardManagerState* RunProgramControl::keyboardManagerState = nullptr; +// Initialized as new vector +RemapBuffer RunProgramControl::runProgramRemapBuffer; + +RunProgramControl::RunProgramControl(StackPanel table, StackPanel row, const int colIndex, TextBox targetApp) +{ + runProgramDropDownVariableSizedWrapGrid = VariableSizedWrapGrid(); + typeRunProgram = Button(); + runProgramControlLayout = StackPanel(); + bool isHybridControl = colIndex == 1 ? true : false; + + // TODO: Check if there is a VariableSizedWrapGrid equivalent. + // runProgramDropDownVariableSizedWrapGrid.as().Spacing(EditorConstants::RunProgramTableDropDownSpacing); + runProgramDropDownVariableSizedWrapGrid.as().Orientation(Windows::UI::Xaml::Controls::Orientation::Horizontal); + runProgramDropDownVariableSizedWrapGrid.as().MaximumRowsOrColumns(3); + + typeRunProgram.as