diff --git a/.github/actions/spell-check/allow/code.txt b/.github/actions/spell-check/allow/code.txt index b9be3f82a47d..c344b0edca70 100644 --- a/.github/actions/spell-check/allow/code.txt +++ b/.github/actions/spell-check/allow/code.txt @@ -51,6 +51,7 @@ resx runtimeconfig srt Stereolithography +taskmgr terabyte UYVY xbf @@ -328,6 +329,10 @@ MRUCMPPROC MRUINFO REGSTR +#Xaml +NVI +Storyboards + # Misc Win32 APIs and PInvokes INVOKEIDLIST MEMORYSTATUSEX diff --git a/.github/actions/spell-check/expect.txt b/.github/actions/spell-check/expect.txt index 779a848e6bee..76ec06dd4876 100644 --- a/.github/actions/spell-check/expect.txt +++ b/.github/actions/spell-check/expect.txt @@ -722,6 +722,8 @@ jpnime Jsons jsonval jxr +KBSC +kdc keybd KEYBDDATA KEYBDINPUT @@ -1085,6 +1087,7 @@ osvi OUTOFCONTEXT Outptr outsettings +outsourced OVERLAPPEDWINDOW Oversampling OVERWRITEPROMPT @@ -1147,6 +1150,8 @@ phwnd pici pidl PIDLIST +pii +pinboard pinfo pinvoke pipename @@ -1221,6 +1226,7 @@ programdata projectname PROPERTYKEY PROPVARIANT +Prt PRTL prvpane psapi @@ -1813,6 +1819,8 @@ WNDCLASSEX WNDCLASSEXW WNDCLASSW wnode +wom +workerw WORKSPACESEDITOR WORKSPACESLAUNCHER WORKSPACESSNAPSHOTTOOL diff --git a/.pipelines/ESRPSigning_core.json b/.pipelines/ESRPSigning_core.json index 8851a7ffacb0..f40bd8f4c0b6 100644 --- a/.pipelines/ESRPSigning_core.json +++ b/.pipelines/ESRPSigning_core.json @@ -239,8 +239,12 @@ "WinUI3Apps\\PowerToys.RegistryPreview.dll", "WinUI3Apps\\PowerToys.RegistryPreview.exe", - "PowerToys.ShortcutGuide.exe", - "PowerToys.ShortcutGuideModuleInterface.dll", + "WinUI3Apps\\PowerToys.ShortcutGuide.exe", + "WinUI3Apps\\PowerToys.ShortcutGuide.dll", + "WinUI3Apps\\PowerToys.ShortcutGuideModuleInterface.dll", + "WinUI3Apps\\PowerToys.ShortcutGuide.IndexYmlGenerator.dll", + "WinUI3Apps\\PowerToys.ShortcutGuide.IndexYmlGenerator.exe", + "WinUI3Apps\\ShortcutGuide.CPPProject.dll", "PowerToys.ZoomIt.exe", "PowerToys.ZoomItModuleInterface.dll", diff --git a/Directory.Packages.props b/Directory.Packages.props index 4a488adfdb29..582faa64180e 100644 --- a/Directory.Packages.props +++ b/Directory.Packages.props @@ -139,6 +139,7 @@ + diff --git a/NOTICE.md b/NOTICE.md index 73a353209661..4f56378da8af 100644 --- a/NOTICE.md +++ b/NOTICE.md @@ -1604,3 +1604,4 @@ SOFTWARE. - WmiLight - WPF-UI - WyHash +- YamlDotNet \ No newline at end of file diff --git a/PowerToys.slnx b/PowerToys.slnx index 9e14ce1a6c41..dd8ee08f9dcc 100644 --- a/PowerToys.slnx +++ b/PowerToys.slnx @@ -979,9 +979,16 @@ - - - + + + + + + + + + + diff --git a/doc/devdocs/images/shortcutguide/diagram.png b/doc/devdocs/images/shortcutguide/diagram.png deleted file mode 100644 index 12d7256828a8..000000000000 Binary files a/doc/devdocs/images/shortcutguide/diagram.png and /dev/null differ diff --git a/doc/devdocs/modules/shortcut_guide.md b/doc/devdocs/modules/shortcut_guide.md index f150a4456c76..9a7a0230dba7 100644 --- a/doc/devdocs/modules/shortcut_guide.md +++ b/doc/devdocs/modules/shortcut_guide.md @@ -9,12 +9,14 @@ [Pull Requests](https://github.com/microsoft/PowerToys/pulls?q=is%3Apr+is%3Aopen+label%3A%22Product-Shortcut+Guide%22+) ## Overview -Shortcut Guide is a PowerToy that displays an overlay of available keyboard shortcuts when the Windows key is pressed and held. It provides a visual reference for Windows key combinations, helping users discover and utilize built-in Windows shortcuts. +Shortcut Guide is a PowerToy that displays an overlay of available keyboard shortcuts when a user-set keyboard shortcut is pressed. It helps users discover and remember keyboard shortcuts for Windows and apps. + +> [!NOTE] +> The spec for the manifest files is in development and will be linked here once available. ## Usage -- Press and hold the Windows key to display the overlay of available shortcuts -- Press the hotkey again to dismiss the overlay -- The overlay displays Windows shortcuts with their corresponding actions +- Press the user-defined hotkey to display the overlay +- Press the hotkey again or press ESC to dismiss the overlay ## Build and Debug Instructions @@ -25,67 +27,89 @@ Shortcut Guide is a PowerToy that displays an overlay of available keyboard shor 4. The executable is named PowerToys.ShortcutGuide.exe ### Debug -1. Right-click the ShortcutGuide project and select 'Set as Startup Project' +1. Right-click the ShortcutGuide.Ui project and select 'Set as Startup Project' 2. Right-click the project again and select 'Debug' -## Code Structure +> [!NOTE] +> When run in debug mode, the window behaves differently than in release mode. It will not automatically close when loosing focus, it will be displayed on top of all other windows, and it is not hidden from the taskbar. + +## Project Structure + +The Shortcut Guide module consists of the following 4 projects: + +### [`ShortcutGuide.Ui`](/src/modules/ShortcutGuide/ShortcutGuide.Ui/ShortcutGuide.Ui.csproj + +This is the main UI project for the Shortcut Guide module. Upon startup it does the following tasks: + +1. Copies the built-in manifest files to the users manifest directory (overwriting existing files). +2. Generate the `index.yml` manifest file. +3. Populate the PowerToys shortcut manifest with the user-defined shortcuts. +4. Starts the UI. + +### Related files in PowerToys.Interop + +#### [`excluded_app.cpp`](/src/modules/ShortcutGuide/ShortcutGuide.CPPProject/excluded_app.cpp) + +This file contains one function with the following signature: + +```cpp +__declspec(dllexport) bool IsCurrentWindowExcludedFromShortcutGuide() +``` + +This function checks if the current window is excluded from the Shortcut Guide overlay. It returns `true` if the current window is excluded otherwise it returns `false`. -![Diagram](../images/shortcutguide/diagram.png) +#### [`tasklist_positions.cpp`](/src/modules/ShortcutGuide/ShortcutGuide.CPPProject/tasklist_positions.cpp) -### Core Files +This file contains helper functions to retrieve the positions of the taskbar buttons. It exports the following function: -#### [`dllmain.cpp`](/src/modules/shortcut_guide/dllmain.cpp) -Contains DLL boilerplate code. Implements the PowertoyModuleIface, including enable/disable functionality and GPO policy handling. Captures hotkey events and starts the PowerToys.ShortcutGuide.exe process to display the shortcut guide window. +```cpp +__declspec(dllexport) TasklistButton* get_buttons(HMONITOR monitor, int* size) +``` -#### [`shortcut_guide.cpp`](/src/modules/shortcut_guide/shortcut_guide.cpp) -Contains the module interface code. It initializes the settings values and the keyboard event listener. Defines the OverlayWindow class, which manages the overall logic and event handling for the PowerToys Shortcut Guide. +This function retrieves the positions of the taskbar buttons for a given monitor. It returns an array of `TasklistButton` structures (max 10), which contain the position and size of each button. -#### [`overlay_window.cpp`](/src/modules/shortcut_guide/overlay_window.cpp) -Contains the code for loading the SVGs, creating and rendering of the overlay window. Manages and displays overlay windows with SVG graphics through two main classes: -- D2DOverlaySVG: Handles loading, resizing, and manipulation of SVG graphics -- D2DOverlayWindow: Manages the display and behavior of the overlay window +`monitor` must be the monitor handle of the monitor containing the taskbar instance of which the buttons should be retrieved. -#### [`keyboard_state.cpp`](/src/modules/shortcut_guide/keyboard_state.cpp) -Contains helper methods for checking the current state of the keyboard. +`size` will contain the resulting array size. -#### [`target_state.cpp`](/src/modules/shortcut_guide/target_state.cpp) -State machine that handles the keyboard events. It's responsible for deciding when to show the overlay, when to suppress the Start menu (if the overlay is displayed long enough), etc. Handles state transitions and synchronization to ensure the overlay is shown or hidden appropriately based on user interactions. +It determines the positions through Windows `FindWindowEx` function. +For the primary taskbar it searches for: +* A window called "Shell_TrayWnd" +* that contains a window called "ReBarWindow32" +* that contains a window called "MSTaskSwWClass" +* that contains a window called "MSTaskListWClass" -#### [`trace.cpp`](/src/modules/shortcut_guide/trace.cpp) -Contains code for telemetry. +For any secondary taskbar it searches for: +* A window called "Shell_SecondaryTrayWnd" +* that contains a window called "WorkerW" +* that contains a window called "MSTaskListWClass" -### Supporting Files +It then enumerates all the button elements inside "MSTaskListWClass" while skipping such with a same name (which implies the user does not use combining taskbar buttons) -#### [`animation.cpp`](/src/modules/shortcut_guide/animation.cpp) -Handles the timing and interpolation of animations. Calculates the current value of an animation based on elapsed time and a specified easing function. +If this method fails, which it will for newer versions of Windows, it falls back to searching for: +* A window called "Shell_TrayWnd" or "Shell_SecondaryTrayWnd" +* that contains a window called "Windows.UI.Composition.DesktopWindowContentBridge" +* that contains a window called "Windows.UI.Input.InputSite.WindowClass" +* the first child element -#### [`d2d_svg.cpp`](/src/modules/shortcut_guide/d2d_svg.cpp) -Provides functionality for loading, resizing, recoloring, rendering, and manipulating SVG images using Direct2D. +It then enumerates all the button elements inside the selected while skipping such with a same name (which implies the user does not use combining taskbar buttons) and such that do not start with "Appid:" (which are not actual taskbar buttons related to apps, but others like the widgets or the search button). -#### [`d2d_text.cpp`](/src/modules/shortcut_guide/d2d_text.cpp) -Handles creation, resizing, alignment, and rendering of text using Direct2D and DirectWrite. +### [`ShortcutGuide.IndexYmlGenerator`](/src/modules/ShortcutGuide/ShortcutGuide.IndexYmlGenerator/) -#### [`d2d_window.cpp`](/src/modules/shortcut_guide/d2d_window.cpp) -Manages a window using Direct2D and Direct3D for rendering. Handles window creation, resizing, rendering, and destruction. +This application generates the `index.yml` manifest file. -#### [`native_event_waiter.cpp`](/src/modules/shortcut_guide/native_event_waiter.cpp) -Waits for a named event and executes a specified action when the event is triggered. Uses a separate thread to handle event waiting and action execution. +It is a separate project so that its code can be easier ported to WinGet in the future. -#### [`tasklist_positions.cpp`](/src/modules/shortcut_guide/tasklist_positions.cpp) -Handles retrieving and updating the positions and information of taskbar buttons in Windows. +### [`ShortcutGuideModuleInterface`](/src/modules/ShortcutGuide/ShortcutGuideModuleInterface/ShortcutGuideModuleInterface.vcxproj) -#### [`main.cpp`](/src/modules/shortcut_guide/main.cpp) -The entry point for the PowerToys Shortcut Guide application. Handles initialization, ensures single instance execution, manages parent process termination, creates and displays the overlay window, and runs the main event loop. +The module interface that handles opening and closing the user interface. ## Features and Limitations -- The overlay displays Windows shortcuts (Windows key combinations) -- The module supports localization, but only for the Windows controls on the left side of the overlay +- Currently the displayed shortcuts (Except the ones from PowerToys) are not localized. - It's currently rated as a P3 (lower priority) module ## Future Development -A community-contributed version 2 is in development that will support: -- Application-specific shortcuts based on the active application -- Additional shortcuts beyond Windows key combinations -- PowerToys shortcuts +- Implementing with WinGet to get new shortcut manifest files +- Adding localization support for the built-in manifest files \ No newline at end of file diff --git a/doc/specs/WinGet Manifest Keyboard Shortcuts schema.md b/doc/specs/WinGet Manifest Keyboard Shortcuts schema.md new file mode 100644 index 000000000000..5dddbad27cf3 --- /dev/null +++ b/doc/specs/WinGet Manifest Keyboard Shortcuts schema.md @@ -0,0 +1,318 @@ +# WinGet Manifest Keyboard Shortcuts schema + +## 1 What this spec is about + +This spec provides an extension to the existing [WinGet manifest schema](https://github.com/microsoft/winget-pkgs/blob/master/doc/manifest/README.md) in form of an additional yaml file, that describes keyboard shortcuts the application provides. + +These yaml files are saved on a per-user base and so called manifest interpreters can then display these manifests in a human-friendly version. + +### 1.1 What this spec is not about + +This spec does not provide a way to back up or save user-defined keyboard shortcuts. + +## 2 Save location of manifests + +### 2.1 WinGet + +These files are saved online along with the other manifest files in the [WinGet Package repository](https://github.com/microsoft/winget-pkgs). + +### 2.2 Locally + +All manifests and one index file are saved locally under `%LocalAppData%/Microsoft/WinGet/KeyboardShortcuts`. All apps are allowed to add their manifest files there. In addition Package Managers (like WinGet) and manifest interpreters (like PowerToys Shortcut Guide) can control and add other manifests themselves. + +#### 2.2.1 Downloading manifests + +When WinGet or other package managers download a package, they should also download the corresponding keyboard shortcuts manifest file and save it in the local directory, given such a file exists in the WinGet repository. + +The downloader is also responsible for updating the local `index.yaml` file, which contains all the information about the different manifest files that are saved in the same directory. + +#### 2.2.2 Updating manifests + +When a manifest interpreter starts, it should download the latest version of the manifests from the WinGet repository and save them in the local directory. If a manifest interpreter is not able to download the manifests or they do not exist, it should use the locally saved manifests. + +The updater is also responsible for updating the local `index.yaml` file, which contains all the information about the different manifest files that are saved in the same directory. + +> Note: WinGet must provide a way to update the keyboard shortcuts manifests given a package id. + +### 2.3 File names + +The file name of a keyboard shortcuts file is the WinGet package identifier, plus the locale of the strings of the file and at last the `.KBSC.yaml` file extension. + +For example the package "test.bar" saves its manifest with `en-US` strings in `test.bar.en-US.KBSC.yaml`. + +#### 2.3.1 No winget package available + +If an application has no corresponding WinGet package its name starts with a plus (`+`) symbol. + +### 2.4 Reserved namespaces + +Every name starting with `+WindowsNT` is reserved for the Windows OS and its components. + +## 3 File syntax + +All relevant files are written in [YAML](https://yaml.org/spec). + +> Note: A JSON schema will be provided as soon as the spec reaches a further step + +### 3.1 Manifest Schema vNext Keyboard Shortcuts File + +``` +PackageName: # The package unique identifier +WindowFilter: # The filter of window processes to which the shortcuts apply to +BackgroundProcess: # Optionally allows applying WindowFilter to background processes +Shortcuts: # List of sections with keyboard shortcuts + - SectionName: # Name of the category of shortcuts + Properties: # List of shortcuts in the category + - Name: # Name of the shortcut + Description: # Optional description of the shortcut + AdditionalInfo: # Optional additional information about the shortcut + Recommended: # Optionally determines if the shortcut is displayed in a designated recommended area + Shortcut: # An array of shortcuts that need to be pressed + - Win: # Determines if the Windows Key is part of the shortcut + Ctrl: # Determines if the Ctrl Key is part of the shortcut + Shift: # Determines if the Shift Key is part of the shortcut + Alt: # Determines if the Alt Key is part of the shortcut + Keys: # Array of keys that need to be pressed +``` + +Per Application/Package one or more Keyboard manifests can be declared. Every manifest must have a different locale and the same `PackageName`, `WindowFilter` and `BackgroundProcess` fields. + +
+ PackageName - The package unique identifier + + Package identifier (see 2.1 for more information on the package identifier). + +
+ +
+ WindowFilter - The filter of window processes to which the shortcuts apply to + + This field declares for which process name the shortcuts should be showed (To rephrase: For which processes the shortcut will have an effect if pressed). You can use an asterisk to leave out a certain part. For example `*.PowerToys.*.exe` targets all PowerToys processes and `*` apply to any process. + +
+ +
+ BackgroundProcess - Optionally allows applying WindowFilter to background processes. + + **Optional field** + + Defaults to `False`. Determines if WindowFilter should apply to background processes as well (Rephrased: When the process is running, the shortcuts will apply). + +
+ +
+ Shortcuts - List of sections with keyboard shortcuts + + List of different section (also called categories) of shortcuts. +
+ +
+ SectionName - Name of the category of shortcuts + + Name of the section of shortcuts. + +**Special sections**: + +Special sections start with an identifier enclosed between `<` and `>`. This declares the category as a special display. If the interpreter of the manifest file can't understand the content this section should be left out. + +
+ +
+ Properties - List of shortcuts in the category +
+ +
+ Name - Name of the shortcut + + Name of the shortcut. This is the name that will be displayed in the interpreter. + +
+ + +
+ Description - Optional description of the shortcut + + Optional description of the shortcut. This is the description that will be displayed by the interpreter. +
+ +
+ AdditionalInfo - Optional additional information about the shortcut + + Array of additional information about the shortcut. This is the additional information that will be displayed by the interpreter and are not part of this manifest. + + **Example**: + + For example, if the shortcut is only available on a certain Windows version, this information could be added here. + ```yaml + AdditionalInfo: + - MinWindowsVersion: "10.0.19041.0" + ``` +
+ +
+ Shortcut - An array of shortcuts that need to be pressed + + An array of shortcuts that need to be pressed. This allows defining sequential shortcuts that need to be pressed in order to trigger the action. + +
+ +
+ Win - Determines if the Windows Key is part of the shortcut + + Refers to the left Windows Key on the keyboard. +
+ +
+ Ctrl - Determines if the Ctrl Key is part of the shortcut + + Refers to the left Ctrl Key on the keyboard. +
+ +
+ Shift - Determines if the Shift Key is part of the shortcut + + Refers to the left Shift Key on the keyboard. +
+ +
+ Alt - Determines if the Alt Key is part of the shortcut + + Refers to the left Alt Key on the keyboard. +
+ + +
+ Recommended - Optionally determines if the shortcut is displayed in a designated recommended area + + **Optional field** + + Defaults to `False`. Determines if the shortcut should be displayed in a designated recommended area. This is a visual hint for the user that this shortcut is important. + +
+ +
+ Keys - Array of keys that need to be pressed + + A string array of all the keys that need to be pressed. If a number is supplied, it should be read as a [KeyCode](https://learn.microsoft.com/windows/win32/inputdev/virtual-key-codes) and displayed accordingly (based on the Keyboard Layout of the user). + +**Special keys**: + +Special keys are enclosed between `<` and `>` and correspond to a key that should be displayed in a certain way. If the interpreter of the manifest file can't understand the content, the brackets should be left out. + +|Name|Description| +|----|-----------| +|``| Corresponds to the Office key on some Windows keyboards | +|``| Corresponds to the Copilot key on some Windows keyboards | +|``| Corresponds to the left arrow key | +|``| Corresponds to the right arrow key | +|``| Corresponds to the up arrow key | +|``| Corresponds to the down arrow key | +|``| Corresponds to the Enter key | +|``| Corresponds to the Space key | +|``| Corresponds to the Tab key | +|``| Corresponds to the Backspace key | +|``| Corresponds to the Delete key | +|``| Corresponds to the Insert key | +|``| Corresponds to the Home key | +|``| Corresponds to the End key | +|``| Corresponds to the Print Screen key | +|``| Corresponds to the pause key | +|``| Corresponds to the Page Up key | +|``| Corresponds to the Page Down key | +|``| Corresponds to the Escape key | +|``| Corresponds to either the left, right, up or down arrow key | +|``| Corresponds to either the left or right arrow key | +|``| Corresponds to either the up or down arrow key | +|``| Corresponds to any letter that is _underlined_ in the UI | + +
+ +#### 3.2.2 Example + +```yaml +PackageName: Microsoft.PowerToys +WindowFilter: "*" +BackgroundProcess: True +Shortcuts: + - SectionName: General + Properties: + - Name: Advanced Paste + Shortcut: + - Win: True + Ctrl: False + Alt: False + Shift: False + Keys: + - 86 + Description: Open Advanced Paste window + - Name: Advanced Paste + Shortcut: + - Win: True + Ctrl: True + Alt: True + Shift: False + Keys: + - 86 + Description: Paste as plain text directly + +``` + + +### 3.2 `index.yaml` file + +The `index.yaml` file is a file that contains all the information about the different manifest files that are saved in the same directory. This file is only available locally and is not saved in the WinGet repository as it is specific to the user. + +```yaml +DefaultShellName: # The package identifier of the default shell used in Windows +Index: # List of all manifest files + - WindowFilter: # The filter of window processes to which the shortcuts apply to + BackgroundProcess: # Optionally allows applying WindowFilter to background processes + Apps: # List of all manifest files for the filter +``` + +
+ DefaultShellName - The package identifier of the default shell used in Windows + + This declares the package identifier of the default shell used in Windows. Most commonly it is `+WindowsNT.Shell`. Although not enforced, only the shell declared in the registry key `HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows NT\CurrentVersion\Winlogon\Shell` should be used here. + +
+ +
+ Index - List of all manifest files +
+ +
+ WindowFilter - The filter of window processes to which the shortcuts apply to + + See the `WindowFilter` field in the manifest file for more information. + +
+ +
+ BackgroundProcess - Optionally allows applying WindowFilter to background processes + + **Optional field** + + See the `BackgroundProcess` field in the manifest file for more information. + +
+ +
+ Apps - List of all the package identifiers applying for the filter +
+ +#### 3.2.1 Example + +```yaml +DefaultShellName: "+WindowsNT.Shell" +Index: + - Filter: "*" + BackgroundProcess: True + Apps: ["+WindowsNT.Shell", "Microsoft.PowerToys"] + - Filter: "explorer.exe" + Apps: ["+WindowsNT.WindowsExplorer"] + - Filter: "taskmgr.exe" + Apps: ["+WindowsNT.TaskManager"] + - Filter: "msedge.exe" + Apps: ["+WindowsNT.Edge"] +``` diff --git a/installer/PowerToysSetupVNext/ShortcutGuide.wxs b/installer/PowerToysSetupVNext/ShortcutGuide.wxs index 37b1c7800b01..262ddd267c3c 100644 --- a/installer/PowerToysSetupVNext/ShortcutGuide.wxs +++ b/installer/PowerToysSetupVNext/ShortcutGuide.wxs @@ -2,26 +2,25 @@ - - + + - - - + + - + - + - - + + - + - + diff --git a/installer/PowerToysSetupVNext/generateAllFileComponents.ps1 b/installer/PowerToysSetupVNext/generateAllFileComponents.ps1 index 81445753690c..483048ee2e5f 100644 --- a/installer/PowerToysSetupVNext/generateAllFileComponents.ps1 +++ b/installer/PowerToysSetupVNext/generateAllFileComponents.ps1 @@ -323,8 +323,8 @@ Generate-FileComponents -fileListName "ValueGeneratorImagesCmpFiles" -wxsFilePat ## Plugins #ShortcutGuide -Generate-FileList -fileDepsJson "" -fileListName ShortcutGuideSvgFiles -wxsFilePath $PSScriptRoot\ShortcutGuide.wxs -depsPath "$PSScriptRoot..\..\..\$platform\Release\Assets\ShortcutGuide\" -Generate-FileComponents -fileListName "ShortcutGuideSvgFiles" -wxsFilePath $PSScriptRoot\ShortcutGuide.wxs +Generate-FileList -fileDepsJson "" -fileListName ShortcutGuideAssetsFiles -wxsFilePath $PSScriptRoot\ShortcutGuide.wxs -depsPath "$PSScriptRoot..\..\..\$platform\Release\WinUI3Apps\Assets\ShortcutGuide\" +Generate-FileComponents -fileListName "ShortcutGuideAssetsFiles" -wxsFilePath $PSScriptRoot\ShortcutGuide.wxs -regroot $registryroot #Settings Generate-FileList -fileDepsJson "" -fileListName SettingsV2AssetsFiles -wxsFilePath $PSScriptRoot\Settings.wxs -depsPath "$PSScriptRoot..\..\..\$platform\Release\WinUI3Apps\Assets\Settings\" diff --git a/src/common/interop/PowerToys.Interop.vcxproj b/src/common/interop/PowerToys.Interop.vcxproj index 0c0727afa216..f152b44899e4 100644 --- a/src/common/interop/PowerToys.Interop.vcxproj +++ b/src/common/interop/PowerToys.Interop.vcxproj @@ -41,7 +41,6 @@ DynamicLibrary - Unicode false @@ -100,6 +99,7 @@ KeyboardListener.idl + HotkeyManager.idl @@ -114,6 +114,7 @@ + TwoWayPipeMessageIPCManaged.idl @@ -127,6 +128,7 @@ KeyboardListener.idl + HotkeyManager.idl @@ -140,6 +142,7 @@ Create + TwoWayPipeMessageIPCManaged.idl @@ -165,6 +168,9 @@ + + {6955446d-23f7-4023-9bb3-8657f904af99} + {cc6e41ac-8174-4e8a-8d22-85dd7f4851df} diff --git a/src/common/interop/PowerToys.Interop.vcxproj.filters b/src/common/interop/PowerToys.Interop.vcxproj.filters index 6fa51a327571..9a2b3edb6fe6 100644 --- a/src/common/interop/PowerToys.Interop.vcxproj.filters +++ b/src/common/interop/PowerToys.Interop.vcxproj.filters @@ -54,6 +54,12 @@ Header Files + + Header Files + + + Header Files + @@ -83,6 +89,12 @@ Source Files + + Source Files + + + Source Files + diff --git a/src/common/interop/excluded_app.cpp b/src/common/interop/excluded_app.cpp new file mode 100644 index 000000000000..bdffe5fed29f --- /dev/null +++ b/src/common/interop/excluded_app.cpp @@ -0,0 +1,39 @@ +#include "pch.h" +#include "excluded_app.h" +#include <../utils/string_utils.h> + +extern "C" +{ + __declspec(dllexport) bool IsCurrentWindowExcludedFromShortcutGuide() + { + PowerToysSettings::PowerToyValues settings = PowerToysSettings::PowerToyValues::load_from_settings_file(L"Shortcut Guide"); + auto settingsObject = settings.get_raw_json(); + std::wstring apps = settingsObject.GetNamedObject(L"properties").GetNamedObject(L"disabled_apps").GetNamedString(L"value").c_str(); + auto excludedUppercase = apps; + CharUpperBuffW(excludedUppercase.data(), static_cast(excludedUppercase.length())); + std::wstring_view view(excludedUppercase); + view = left_trim(trim(view)); + + while (!view.empty()) + { + auto pos = (std::min)(view.find_first_of(L"\r\n"), view.length()); + m_excludedApps.emplace_back(view.substr(0, pos)); + view.remove_prefix(pos); + view = left_trim(trim(view)); + } + + if (m_excludedApps.empty()) + { + return false; + } + + if (HWND foregroundApp{ GetForegroundWindow() }) + { + auto processPath = get_process_path(foregroundApp); + CharUpperBuffW(processPath.data(), static_cast(processPath.length())); + + return check_excluded_app(foregroundApp, processPath, m_excludedApps); + } + return false; + } +} diff --git a/src/common/interop/excluded_app.h b/src/common/interop/excluded_app.h new file mode 100644 index 000000000000..652449a138a1 --- /dev/null +++ b/src/common/interop/excluded_app.h @@ -0,0 +1,7 @@ +#pragma once + +extern "C" +{ + std::vector m_excludedApps; + __declspec(dllexport) bool IsCurrentWindowExcludedFromShortcutGuide(); +} diff --git a/src/common/interop/pch.h b/src/common/interop/pch.h index c4835a98e1ca..7b06662237e8 100644 --- a/src/common/interop/pch.h +++ b/src/common/interop/pch.h @@ -12,3 +12,29 @@ #include #include #include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include <../SettingsAPI/settings_objects.h> diff --git a/src/common/interop/tasklist_positions.cpp b/src/common/interop/tasklist_positions.cpp new file mode 100644 index 000000000000..b650535e950a --- /dev/null +++ b/src/common/interop/tasklist_positions.cpp @@ -0,0 +1,242 @@ +#include "pch.h" +#include "tasklist_positions.h" + +// Tried my hardest adapting this to C#, but FindWindowW didn't work properly in C#. ~Noraa Junker + +extern "C" +{ + HWND GetTaskbarHwndForCursorMonitor(HMONITOR monitor) + { + POINT pt; + if (!GetCursorPos(&pt)) + return nullptr; + + // Find the primary taskbar + HWND primaryTaskbar = FindWindowW(L"Shell_TrayWnd", nullptr); + if (primaryTaskbar) + { + MONITORINFO mi = { sizeof(mi) }; + if (GetWindowRect(primaryTaskbar, &mi.rcMonitor)) + { + HMONITOR primaryMonitor = MonitorFromRect(&mi.rcMonitor, MONITOR_DEFAULTTONEAREST); + if (primaryMonitor == monitor) + return primaryTaskbar; + } + } + + // Find the secondary taskbar(s) + HWND secondaryTaskbar = nullptr; + while ((secondaryTaskbar = FindWindowExW(nullptr, secondaryTaskbar, L"Shell_SecondaryTrayWnd", nullptr)) != nullptr) + { + MONITORINFO mi = { sizeof(mi) }; + RECT rc; + if (GetWindowRect(secondaryTaskbar, &rc)) + { + HMONITOR taskbarMonitor = MonitorFromRect(&rc, MONITOR_DEFAULTTONEAREST); + if (monitor == taskbarMonitor) + return secondaryTaskbar; + } + } + + return nullptr; + } + + void update(HMONITOR monitor) + { + // Get HWND of the tasklist for the monitor under the cursor + auto taskbar_hwnd = GetTaskbarHwndForCursorMonitor(monitor); + if (!taskbar_hwnd) + return; + + wchar_t class_name[64] = {}; + GetClassNameW(taskbar_hwnd, class_name, 64); + + HWND tasklist_hwnd = nullptr; + + if (wcscmp(class_name, L"Shell_TrayWnd") == 0) + { + // Primary taskbar structure + tasklist_hwnd = FindWindowExW(taskbar_hwnd, 0, L"ReBarWindow32", nullptr); + if (!tasklist_hwnd) + return; + tasklist_hwnd = FindWindowExW(tasklist_hwnd, 0, L"MSTaskSwWClass", nullptr); + if (!tasklist_hwnd) + return; + tasklist_hwnd = FindWindowExW(tasklist_hwnd, 0, L"MSTaskListWClass", nullptr); + if (!tasklist_hwnd) + return; + } + else if (wcscmp(class_name, L"Shell_SecondaryTrayWnd") == 0) + { + // Secondary taskbar structure + HWND worker_hwnd = FindWindowExW(taskbar_hwnd, 0, L"WorkerW", nullptr); + if (!worker_hwnd) + return; + tasklist_hwnd = FindWindowExW(worker_hwnd, 0, L"MSTaskListWClass", nullptr); + if (!tasklist_hwnd) + return; + } + else + { + // Unknown taskbar type + return; + } + + if (!automation) + { + winrt::check_hresult(CoCreateInstance(CLSID_CUIAutomation, + nullptr, + CLSCTX_INPROC_SERVER, + IID_IUIAutomation, + automation.put_void())); + winrt::check_hresult(automation->CreateTrueCondition(true_condition.put())); + } + element = nullptr; + winrt::check_hresult(automation->ElementFromHandle(tasklist_hwnd, element.put())); + } + + void update_new(HMONITOR monitor) + { + // Get HWND of the tasklist for the monitor under the cursor + auto taskbar_hwnd = GetTaskbarHwndForCursorMonitor(monitor); + if (!taskbar_hwnd) + return; + + wchar_t class_name[64] = {}; + GetClassNameW(taskbar_hwnd, class_name, 64); + + HWND tasklist_hwnd = nullptr; + + if (wcscmp(class_name, L"Shell_TrayWnd") == 0 || wcscmp(class_name, L"Shell_SecondaryTrayWnd") == 0) + { + // Primary taskbar structure + tasklist_hwnd = FindWindowExW(taskbar_hwnd, 0, L"Windows.UI.Composition.DesktopWindowContentBridge", nullptr); + if (!tasklist_hwnd) + return; + tasklist_hwnd = FindWindowExW(tasklist_hwnd, 0, L"Windows.UI.Input.InputSite.WindowClass", nullptr); + if (!tasklist_hwnd) + return; + } + else + { + // Unknown taskbar type + return; + } + + if (!automation) + { + winrt::check_hresult(CoCreateInstance(CLSID_CUIAutomation, + nullptr, + CLSCTX_INPROC_SERVER, + IID_IUIAutomation, + automation.put_void())); + winrt::check_hresult(automation->CreateTrueCondition(true_condition.put())); + } + + winrt::com_ptr tempElement; + element = nullptr; + winrt::check_hresult(automation->ElementFromHandle(tasklist_hwnd, tempElement.put())); + + winrt::check_hresult( + tempElement->FindFirst(TreeScope_Children, true_condition.get(), element.put())); + } + + bool update_buttons(std::vector& buttons) + { + if (!automation || !element) + { + return false; + } + winrt::com_ptr elements; + if (element->FindAll(TreeScope_Children, true_condition.get(), elements.put()) < 0) + return false; + if (!elements) + return false; + int count; + if (elements->get_Length(&count) < 0) + return false; + winrt::com_ptr child; + std::vector found_buttons; + found_buttons.reserve(count); + for (int i = 0; i < count; ++i) + { + child = nullptr; + if (elements->GetElement(i, child.put()) < 0) + return false; + TasklistButton button = {}; + if (VARIANT var_rect; child->GetCurrentPropertyValue(UIA_BoundingRectanglePropertyId, &var_rect) >= 0) + { + if (var_rect.vt == (VT_R8 | VT_ARRAY)) + { + LONG pos; + double value; + pos = 0; + SafeArrayGetElement(var_rect.parray, &pos, &value); + button.x = static_cast(value); + pos = 1; + SafeArrayGetElement(var_rect.parray, &pos, &value); + button.y = static_cast(value); + pos = 2; + SafeArrayGetElement(var_rect.parray, &pos, &value); + button.width = static_cast(value); + pos = 3; + SafeArrayGetElement(var_rect.parray, &pos, &value); + button.height = static_cast(value); + } + VariantClear(&var_rect); + } + else + { + return false; + } + if (BSTR automation_id; child->get_CurrentAutomationId(&automation_id) >= 0) + { + wcsncpy_s(button.name, automation_id, _countof(button.name)); + SysFreeString(automation_id); + if (wcsncmp(button.name, L"Appid:", wcslen(L"Appid:")) != 0) + { + continue; + } + } + found_buttons.push_back(button); + } + // assign keynums + buttons.clear(); + for (auto& button : found_buttons) + { + if (buttons.empty()) + { + button.keynum = 1; + buttons.push_back(std::move(button)); + } + else + { + if (button.x < buttons.back().x || button.y < buttons.back().y) // skip 2nd row + break; + if (wcsncmp(button.name, buttons.back().name, _countof(button.name)) == 0) + continue; // skip buttons from the same app + button.keynum = buttons.back().keynum + 1; + buttons.push_back(std::move(button)); + if (buttons.back().keynum == 10) + break; // no more than 10 buttons + } + } + return true; + } + + __declspec(dllexport) TasklistButton* get_buttons(HMONITOR monitor, int* size) + { + update(monitor); + static std::vector buttons; + update_buttons(buttons); + *size = static_cast(buttons.size()); + if (*size == 0) + { + // After a certain Windows update, the old method stopped working, try the new one + update_new(monitor); + update_buttons(buttons); + *size = static_cast(buttons.size()); + } + return buttons.data(); + } +} \ No newline at end of file diff --git a/src/common/interop/tasklist_positions.h b/src/common/interop/tasklist_positions.h new file mode 100644 index 000000000000..0f8c4ca35aa4 --- /dev/null +++ b/src/common/interop/tasklist_positions.h @@ -0,0 +1,23 @@ +#pragma once + +struct TasklistButton +{ + wchar_t name[256]; + int x; + int y; + int width; + int height; + int keynum; +}; + +extern "C" +{ + winrt::com_ptr automation; + winrt::com_ptr element; + winrt::com_ptr true_condition; + + // Helper to get the taskbar HWND for the monitor under the cursor + HWND GetTaskbarHwndForCursorMonitor(HMONITOR monitor); + bool update_buttons(std::vector& buttons); + __declspec(dllexport) TasklistButton* get_buttons(HMONITOR monitor, int* size); +} diff --git a/src/modules/ShortcutGuide/ShortcutGuide.IndexYmlGenerator/IndexYmlGenerator.cs b/src/modules/ShortcutGuide/ShortcutGuide.IndexYmlGenerator/IndexYmlGenerator.cs new file mode 100644 index 000000000000..597ead2ba2ca --- /dev/null +++ b/src/modules/ShortcutGuide/ShortcutGuide.IndexYmlGenerator/IndexYmlGenerator.cs @@ -0,0 +1,76 @@ +// Copyright (c) Microsoft Corporation +// The Microsoft Corporation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System.Collections.Generic; +using System.IO; +using ShortcutGuide.Helpers; +using ShortcutGuide.Models; +using YamlDotNet.Serialization; + +// This class should be moved to WinGet in the future +namespace ShortcutGuide.IndexYmlGenerator +{ + public class IndexYmlGenerator + { + public static void Main() + { + CreateIndexYmlFile(); + } + + // Todo: Exception handling + public static void CreateIndexYmlFile() + { + string path = ManifestInterpreter.PathOfManifestFiles; + if (File.Exists(Path.Combine(path, "index.yml"))) + { + File.Delete(Path.Combine(path, "index.yml")); + } + + IndexFile indexFile = new() { }; + Dictionary<(string WindowFilter, bool BackgroundProcess), List> processes = []; + + foreach (string file in Directory.EnumerateFiles(path, "*.yml")) + { + string content = File.ReadAllText(file); + Deserializer deserializer = new(); + ShortcutFile shortcutFile = deserializer.Deserialize(content); + if (processes.TryGetValue((shortcutFile.WindowFilter, shortcutFile.BackgroundProcess), out List? apps)) + { + if (apps.Contains(shortcutFile.PackageName)) + { + continue; + } + + apps.Add(shortcutFile.PackageName); + continue; + } + + processes[(shortcutFile.WindowFilter, shortcutFile.BackgroundProcess)] = [shortcutFile.PackageName]; + } + + indexFile.Index = []; + + foreach (var item in processes) + { + indexFile.Index = + [ + .. indexFile.Index, + new IndexFile.IndexItem + { + WindowFilter = item.Key.WindowFilter, + BackgroundProcess = item.Key.BackgroundProcess, + Apps = [.. item.Value], + }, + ]; + } + + // Todo: Take the default shell name from the settings or environment variable, default to "+WindowsNT.Shell" + indexFile.DefaultShellName = "+WindowsNT.Shell"; + + Serializer serializer = new(); + string yamlContent = serializer.Serialize(indexFile); + File.WriteAllText(Path.Combine(path, "index.yml"), yamlContent); + } + } +} diff --git a/src/modules/ShortcutGuide/ShortcutGuide.IndexYmlGenerator/ShortcutGuide.IndexYmlGenerator.csproj b/src/modules/ShortcutGuide/ShortcutGuide.IndexYmlGenerator/ShortcutGuide.IndexYmlGenerator.csproj new file mode 100644 index 000000000000..571ca9de0363 --- /dev/null +++ b/src/modules/ShortcutGuide/ShortcutGuide.IndexYmlGenerator/ShortcutGuide.IndexYmlGenerator.csproj @@ -0,0 +1,24 @@ + + + + + + + WinExe + ShortcutGuide.IndexYmlGenerator + enable + false + false + ..\..\..\..\$(Platform)\$(Configuration)\WinUI3Apps + PowerToys.ShortcutGuide.IndexYmlGenerator + + + + + + + + + + + diff --git a/src/modules/ShortcutGuide/ShortcutGuide.Ui/Assets/ShortcutGuide/+WindowsNT.Notepad.en-US.yml b/src/modules/ShortcutGuide/ShortcutGuide.Ui/Assets/ShortcutGuide/+WindowsNT.Notepad.en-US.yml new file mode 100644 index 000000000000..d0d607253c36 --- /dev/null +++ b/src/modules/ShortcutGuide/ShortcutGuide.Ui/Assets/ShortcutGuide/+WindowsNT.Notepad.en-US.yml @@ -0,0 +1,247 @@ +PackageName: +WindowsNT.Notepad +Name: Notepad +WindowFilter: "Notepad.exe" +BackgroundProcess: false +Shortcuts: + - SectionName: File + Properties: + - Name: New tab + Recommended: true + Shortcut: + - Win: false + Ctrl: true + Shift: false + Alt: false + Keys: + - N + - Name: New window + Shortcut: + - Win: false + Ctrl: true + Shift: true + Alt: false + Keys: + - N + - Name: Open + Shortcut: + - Win: false + Ctrl: true + Shift: false + Alt: false + Keys: + - O + - Name: Save + Shortcut: + - Win: false + Ctrl: true + Shift: false + Alt: false + Keys: + - S + - Name: Save As + Shortcut: + - Win: false + Ctrl: true + Shift: true + Alt: false + Keys: + - S + - Name: Save all + Shortcut: + - Win: false + Ctrl: true + Shift: false + Alt: true + Keys: + - S + - Name: Print + Shortcut: + - Win: false + Ctrl: true + Shift: false + Alt: false + Keys: + - P + - Name: Close tab + Shortcut: + - Win: false + Ctrl: true + Shift: false + Alt: false + Keys: + - W + - Name: Close window + Shortcut: + - Win: false + Ctrl: true + Shift: true + Alt: false + Keys: + - W + - SectionName: Edit + Properties: + - Name: Undo + Shortcut: + - Win: false + Ctrl: true + Shift: false + Alt: false + Keys: + - Z + - Name: Redo + Shortcut: + - Win: false + Ctrl: true + Shift: false + Alt: true + Keys: + - Z + - Name: Cut + Shortcut: + - Win: false + Ctrl: true + Shift: false + Alt: false + Keys: + - X + - Name: Copy + Shortcut: + - Win: false + Ctrl: true + Shift: false + Alt: false + Keys: + - C + - Name: Paste + Shortcut: + - Win: false + Ctrl: true + Shift: false + Alt: false + Keys: + - V + - Name: Search with Bing + Shortcut: + - Win: false + Ctrl: true + Shift: false + Alt: false + Keys: + - E + - Name: Find + Recommended: true + Shortcut: + - Win: false + Ctrl: true + Shift: false + Alt: false + Keys: + - F + - Name: Find next + Shortcut: + - Win: false + Ctrl: false + Shift: false + Alt: false + Keys: + - F3 + - Name: Find previous + Shortcut: + - Win: false + Ctrl: false + Shift: true + Alt: false + Keys: + - F3 + - Name: Replace + Shortcut: + - Win: false + Ctrl: true + Shift: false + Alt: false + Keys: + - H + - Name: Go to + Shortcut: + - Win: false + Ctrl: true + Shift: false + Alt: false + Keys: + - G + - Name: Select all + Shortcut: + - Win: false + Ctrl: true + Shift: false + Alt: false + Keys: + - A + - Name: Time/Date + Shortcut: + - Win: false + Ctrl: true + Shift: false + Alt: false + Keys: + - F5 + - SectionName: View + Properties: + - Name: Zoom in + Shortcut: + - Win: false + Ctrl: true + Shift: false + Alt: false + Keys: + - Plus + - Name: Zoom out + Shortcut: + - Win: false + Ctrl: true + Shift: false + Alt: false + Keys: + - Minus + - Name: Reset zoom + Shortcut: + - Win: false + Ctrl: true + Shift: false + Alt: false + Keys: + - 0 + - SectionName: Formatting + Properties: + - Name: Bold + Shortcut: + - Win: false + Ctrl: true + Shift: false + Alt: false + Keys: + - B + - Name: Italic + Shortcut: + - Win: false + Ctrl: true + Shift: false + Alt: false + Keys: + - I + - Name: Insert link + Shortcut: + - Win: false + Ctrl: true + Shift: false + Alt: false + Keys: + - K + - Name: Clear formatting + Shortcut: + - Win: false + Ctrl: true + Shift: false + Alt: false + Keys: + - Space \ No newline at end of file diff --git a/src/modules/ShortcutGuide/ShortcutGuide.Ui/Assets/ShortcutGuide/+WindowsNT.Shell.en-US.yml b/src/modules/ShortcutGuide/ShortcutGuide.Ui/Assets/ShortcutGuide/+WindowsNT.Shell.en-US.yml new file mode 100644 index 000000000000..de9eb03ab10c --- /dev/null +++ b/src/modules/ShortcutGuide/ShortcutGuide.Ui/Assets/ShortcutGuide/+WindowsNT.Shell.en-US.yml @@ -0,0 +1,773 @@ +PackageName: +WindowsNT.Shell +WindowFilter: "*" +BackgroundProcess: true +Shortcuts: + - SectionName: Desktop Shortcuts + Properties: + - Name: Close active window + Shortcut: + - Win: false + Ctrl: false + Shift: false + Alt: true + Keys: + - F4 + - Name: Open shutdown box + Description: When no windows are open + Shortcut: + - Win: false + Ctrl: false + Shift: false + Alt: true + Keys: + - F4 + - Name: Cycle through open Windows + Shortcut: + - Win: false + Ctrl: false + Shift: false + Alt: true + Keys: + - Esc + - Name: Reveal typed password + Description: On sign-in screen + Shortcut: + - Win: false + Ctrl: false + Shift: false + Alt: true + Keys: + - F8 + - Name: Go back + Shortcut: + - Win: false + Ctrl: false + Shift: false + Alt: true + Keys: + - "" + - Name: Go forward + Shortcut: + - Win: false + Ctrl: false + Shift: false + Alt: true + Keys: + - "" + - Name: Move up one screen + Shortcut: + - Win: false + Ctrl: false + Shift: false + Alt: true + Keys: + - "" + - Name: Move down one screen + Shortcut: + - Win: false + Ctrl: false + Shift: false + Alt: true + Keys: + - "" + - Name: Window context menu + Shortcut: + - Win: false + Ctrl: false + Shift: false + Alt: true + Keys: + - Space + - Name: Switch between open apps + Shortcut: + - Win: false + Ctrl: false + Shift: false + Alt: true + Keys: + - Tab + Description: While pressing Tab multiple times + - Name: Run command + Shortcut: + - Win: false + Ctrl: false + Shift: false + Alt: true + Keys: + - "" + Description: for the underlined letter in the app + - Name: View open apps + Shortcut: + - Win: false + Ctrl: true + Shift: false + Alt: true + Keys: + - Tab + - Name: Change start menu size + Shortcut: + - Win: false + Ctrl: true + Shift: false + Alt: false + Keys: + - "" + - Name: Move cursor + Shortcut: + - Win: false + Ctrl: true + Shift: false + Alt: false + Keys: + - "" + Description: To the beginning or end of a word + - Name: Switch keyboard layout + Shortcut: + - Win: false + Ctrl: true + Shift: true + Alt: false + Keys: + - "" + - Name: Select block of text + Shortcut: + - Win: false + Ctrl: true + Shift: true + Alt: false + Keys: + - "" + - Name: Open Task Manager + Shortcut: + - Win: false + Ctrl: true + Shift: true + Alt: false + Keys: + - Esc + - Name: Enable/Disable Chinese IME + Shortcut: + - Win: false + Ctrl: true + Shift: false + Alt: false + Keys: + - Space + - Name: Open context menu + Shortcut: + - Win: false + Ctrl: false + Shift: true + Alt: false + Keys: + - F10 + Description: For the selected item + - SectionName: Virtual desktop + Properties: + - Name: Open task view + Shortcut: + - Win: true + Ctrl: false + Shift: false + Alt: false + Keys: + - Tab + - Name: Add a virtual desktop + Shortcut: + - Win: true + Ctrl: true + Shift: false + Alt: false + Keys: + - D + - Name: Close current desktop + Shortcut: + - Win: true + Ctrl: true + Shift: false + Alt: false + Keys: + - F4 + - Name: Switch desktop + Shortcut: + - Win: true + Ctrl: true + Shift: false + Alt: false + Keys: + - "" + Recommended: true + - SectionName: "Windows key" + Properties: + - Name: Open start menu + Shortcut: + - Win: true + Ctrl: false + Shift: false + Alt: false + Keys: + - "" + - Name: Open Action Center + Shortcut: + - Win: true + Ctrl: false + Shift: false + Alt: false + Keys: + - "A" + - Name: Open Date and Time + Shortcut: + - Win: true + Ctrl: false + Shift: false + Alt: true + Keys: + - "D" + - Name: Focus on the notification area + Shortcut: + - Win: true + Ctrl: false + Shift: false + Alt: false + Keys: + - "B" + - Name: Open narrator + Shortcut: + - Win: true + Ctrl: true + Shift: false + Alt: false + Keys: + - "Enter" + - Name: Open domain search + Shortcut: + - Win: true + Ctrl: true + Shift: false + Alt: false + Keys: + - "F" + - Name: Open Quick Assist + Shortcut: + - Win: true + Ctrl: true + Shift: false + Alt: false + Keys: + - "Q" + - Name: Wake up device + Shortcut: + - Win: true + Ctrl: true + Shift: true + Alt: false + Keys: + - "B" + Description: When black or a blank screen. + - Name: Change input option + Shortcut: + - Win: true + Ctrl: false + Shift: false + Alt: false + Keys: + - "Space" + Description: To next option + - Name: Change input option + Shortcut: + - Win: true + Ctrl: true + Shift: false + Alt: false + Keys: + - "Space" + Description: To previous option + - Name: Display/Hide desktop + Shortcut: + - Win: true + Ctrl: false + Shift: false + Alt: false + Keys: + - "D" + - Name: Minimize the active window + Shortcut: + - Win: true + Ctrl: false + Shift: false + Alt: false + Keys: + - "Down" + Recommended: true + - Name: Open file Explorer + Shortcut: + - Win: true + Ctrl: false + Shift: false + Alt: false + Keys: + - "E" + - Name: Close Magnifier + Shortcut: + - Win: true + Ctrl: false + Shift: false + Alt: false + Keys: + - "Esc" + - Name: Open Feedback Hub + Shortcut: + - Win: true + Ctrl: false + Shift: false + Alt: false + Keys: + - "F" + Recommended: true + - Name: Start IME reconversion + Shortcut: + - Win: true + Ctrl: false + Shift: false + Alt: false + Keys: + - "/" + - Name: Open Game Bar + Shortcut: + - Win: true + Ctrl: false + Shift: false + Alt: false + Keys: + - "G" + - Name: Open voice dictation + Shortcut: + - Win: true + Ctrl: false + Shift: false + Alt: false + Keys: + - "H" + - Name: Minimize or restore all other windows + Shortcut: + - Win: true + Ctrl: false + Shift: false + Alt: false + Keys: + - "" + - Name: Open Settings + Shortcut: + - Win: true + Ctrl: false + Shift: false + Alt: false + Keys: + - "I" + - Name: Set focus to a Windows tip + Shortcut: + - Win: true + Ctrl: false + Shift: false + Alt: false + Keys: + - "J" + - Name: Open Cast + Shortcut: + - Win: true + Ctrl: false + Shift: false + Alt: false + Keys: + - "K" + - Name: Lock the device + Shortcut: + - Win: true + Ctrl: false + Shift: false + Alt: false + Keys: + - "L" + - Name: Snap the window + Shortcut: + - Win: true + Ctrl: false + Shift: false + Alt: false + Keys: + - "" + - Name: Minimize all windows + Shortcut: + - Win: true + Ctrl: false + Shift: false + Alt: false + Keys: + - "M" + - Name: Zoom out Magnifier + Shortcut: + - Win: true + Ctrl: false + Shift: false + Alt: false + Keys: + - "-" + - Name: Zoom in Magnifier + Shortcut: + - Win: true + Ctrl: false + Shift: false + Alt: false + Keys: + - "=" + - Name: Open notification center + Shortcut: + - Win: true + Ctrl: false + Shift: false + Alt: false + Keys: + - "N" + - Name: Lock the device orientation + Shortcut: + - Win: true + Ctrl: false + Shift: false + Alt: false + Keys: + - "O" + - Name: Open project Settings + Shortcut: + - Win: true + Ctrl: false + Shift: false + Alt: false + Keys: + - "P" + - Name: Open Settings about Page + Shortcut: + - Win: true + Ctrl: false + Shift: false + Alt: false + Keys: + - "" + - Name: Open the emoji panel + Shortcut: + - Win: true + Ctrl: false + Shift: false + Alt: false + Keys: + - "." + - Name: Open the emoji panel + Shortcut: + - Win: true + Ctrl: false + Shift: false + Alt: false + Keys: + - ";" + - Name: Capture a screenshot + Shortcut: + - Win: true + Ctrl: false + Shift: false + Alt: false + Keys: + - "" + Description: Save to the pictures folder + - Name: Open search + Shortcut: + - Win: true + Ctrl: false + Shift: false + Alt: false + Keys: + - "Q" + - Name: Open search + Shortcut: + - Win: true + Ctrl: false + Shift: false + Alt: false + Keys: + - "S" + - Name: Open Run dialog + Shortcut: + - Win: true + Ctrl: false + Shift: false + Alt: false + Keys: + - "R" + - Name: Restore window + Description: If a window is snapped or maximized + Shortcut: + - Win: true + Ctrl: false + Shift: true + Alt: false + Keys: + - "" + - Name: Make UWP app full screen + Shortcut: + - Win: true + Ctrl: false + Shift: true + Alt: false + Keys: + - "" + - Name: Move window to monitor + Shortcut: + - Win: true + Ctrl: false + Shift: true + Alt: false + Keys: + - "" + - Name: Open Snipping Tool + Shortcut: + - Win: true + Ctrl: false + Shift: true + Alt: false + Keys: + - "S" + - Name: Stretch window + Description: To the top and bottom of the screen + Shortcut: + - Win: true + Ctrl: false + Shift: true + Alt: false + Keys: + - "" + - Name: Open task view + Shortcut: + - Win: true + Ctrl: false + Shift: false + Alt: false + Keys: + - "Tab" + - Name: Open Accessibility Settings + Shortcut: + - Win: true + Ctrl: false + Shift: false + Alt: false + Keys: + - "U" + - Name: Maximize the active window + Shortcut: + - Win: true + Ctrl: false + Shift: false + Alt: false + Keys: + - "" + - Name: Open the clipboard history + Shortcut: + - Win: true + Ctrl: false + Shift: false + Alt: false + Keys: + - "V" + - Name: Open widgets + Shortcut: + - Win: true + Ctrl: false + Shift: false + Alt: false + Keys: + - "W" + - Name: Open Quick Link menu + Shortcut: + - Win: true + Ctrl: false + Shift: false + Alt: false + Keys: + - "X" + - Name: Open snap layouts + Shortcut: + - Win: true + Ctrl: false + Shift: false + Alt: false + Keys: + - "Z" + - SectionName: Clipboard + Properties: + - Name: Copy + Shortcut: + - Win: false + Ctrl: true + Shift: false + Alt: false + Keys: + - "C" + - Name: Cut + Shortcut: + - Win: false + Ctrl: true + Shift: false + Alt: false + Keys: + - "X" + - Name: Paste + Shortcut: + - Win: false + Ctrl: true + Shift: false + Alt: false + Keys: + - "V" + - Name: Paste + Shortcut: + - Win: false + Ctrl: false + Shift: true + Alt: false + Keys: + - "" + Description: Paste as plain text + - SectionName: Taskbar Shortcuts + Properties: + - Name: Open app in Taskbar + Shortcut: + - Win: true + Ctrl: false + Shift: false + Alt: false + Keys: + - "" + - Name: Open jump list + Shortcut: + - Win: true + Ctrl: false + Shift: false + Alt: true + Keys: + - "" + - Name: Switch to last active window + Shortcut: + - Win: true + Ctrl: true + Shift: false + Alt: false + Keys: + - "" + - Name: Open as administrator + Shortcut: + - Win: true + Ctrl: true + Shift: true + Alt: false + Keys: + - "" + - SectionName: Copilot key + Properties: + - Name: Open Copilot + Shortcut: + - Win: false + Ctrl: false + Shift: false + Alt: false + Keys: + - '' + Description: When copilot is available + - Name: Open Windows search + Shortcut: + - Win: false + Ctrl: false + Shift: false + Alt: false + Keys: + - '' + Description: When copilot is not available + - SectionName: Office key + Properties: + - Name: Open Word + Shortcut: + - Win: false + Ctrl: false + Shift: false + Alt: false + Keys: + - "" + - "W" + - Name: Open Excel + Shortcut: + - Win: false + Ctrl: false + Shift: false + Alt: false + Keys: + - "" + - "X" + - Name: Open PowerPoint + Shortcut: + - Win: false + Ctrl: false + Shift: false + Alt: false + Keys: + - "" + - "P" + - Name: Open Outlook + Shortcut: + - Win: false + Ctrl: false + Shift: false + Alt: false + Keys: + - "" + - "O" + - Name: Open Microsoft Teams + Shortcut: + - Win: false + Ctrl: false + Shift: false + Alt: false + Keys: + - "" + - "T" + - Name: Open OneNote + Shortcut: + - Win: false + Ctrl: false + Shift: false + Alt: false + Keys: + - "" + - "N" + - Name: Open OneDrive + Shortcut: + - Win: false + Ctrl: false + Shift: false + Alt: false + Keys: + - "" + - "D" + - Name: Open Yammer + Shortcut: + - Win: false + Ctrl: false + Shift: false + Alt: false + Keys: + - "" + - "Y" + - Name: Open LinkedIn + Shortcut: + - Win: false + Ctrl: false + Shift: false + Alt: false + Keys: + - "" + - "L" \ No newline at end of file diff --git a/src/modules/ShortcutGuide/ShortcutGuide.Ui/Assets/ShortcutGuide/+WindowsNT.WindowsExplorer.en-US.yml b/src/modules/ShortcutGuide/ShortcutGuide.Ui/Assets/ShortcutGuide/+WindowsNT.WindowsExplorer.en-US.yml new file mode 100644 index 000000000000..ca134ea7926d --- /dev/null +++ b/src/modules/ShortcutGuide/ShortcutGuide.Ui/Assets/ShortcutGuide/+WindowsNT.WindowsExplorer.en-US.yml @@ -0,0 +1,266 @@ +PackageName: +WindowsNT.WindowsExplorer +WindowFilter: "explorer.exe" +Name: File Explorer +Shortcuts: + - SectionName: General + Properties: + - Name: Open File Explorer + Shortcut: + - Win: true + Ctrl: false + Shift: false + Alt: false + Keys: + - "E" + Recommended: true + - Name: Select the address bar + Shortcut: + - Win: false + Ctrl: false + Shift: false + Alt: true + Keys: + - "D" + - Name: Select the address bar + Shortcut: + - Win: false + Ctrl: true + Shift: false + Alt: false + Keys: + - "L" + - Name: Select the address bar + Shortcut: + - Win: false + Ctrl: false + Shift: false + Alt: false + Keys: + - "F4" + - Name: Select the search box + Shortcut: + - Win: false + Ctrl: true + Shift: false + Alt: false + Keys: + - "E" + - Name: Select the search box + Shortcut: + - Win: false + Ctrl: false + Shift: false + Alt: false + Keys: + - "F3" + - Name: Select the search box + Shortcut: + - Win: false + Ctrl: true + Shift: false + Alt: false + Keys: + - "F" + - Name: Refresh the window + Shortcut: + - Win: false + Ctrl: false + Shift: false + Alt: false + Keys: + - "F5" + - Name: Cycle through elements in the active window + Shortcut: + - Win: false + Ctrl: false + Shift: false + Alt: false + Keys: + - "F6" + - Name: Maximize or restore the active window + Shortcut: + - Win: false + Ctrl: false + Shift: false + Alt: false + Keys: + - "F11" + - SectionName: Navigation + Properties: + - Name: Navigate to the previous folder + Shortcut: + - Win: false + Ctrl: false + Shift: false + Alt: true + Keys: + - "" + - Name: Navigate to the previous folder + Shortcut: + - Win: false + Ctrl: false + Shift: false + Alt: false + Keys: + - "" + - Name: Navigate to the next folder + Shortcut: + - Win: false + Ctrl: false + Shift: false + Alt: true + Keys: + - "" + - Name: Move up a level in the folder path + Shortcut: + - Win: false + Ctrl: false + Shift: false + Alt: true + Keys: + - "" + - SectionName: "Window management" + Properties: + - Name: Open a new window + Shortcut: + - Win: false + Ctrl: true + Shift: false + Alt: false + Keys: + - "N" + - Name: Open a new tab + Shortcut: + - Win: false + Ctrl: true + Shift: false + Alt: false + Keys: + - "T" + Recommended: true + - Name: Close the current active tab + Shortcut: + - Win: false + Ctrl: true + Shift: false + Alt: false + Keys: + - "W" + - Name: Move to the next tab + Shortcut: + - Win: false + Ctrl: true + Shift: false + Alt: false + Keys: + - "Tab" + - Name: Move to the previous tab + Shortcut: + - Win: false + Ctrl: true + Shift: true + Alt: false + Keys: + - "Tab" + - Name: Move to that tab number + Shortcut: + - Win: false + Ctrl: true + Shift: false + Alt: false + Keys: + - "Number (1-9)" + - Name: Show/Hide the preview pane + Shortcut: + - Win: false + Ctrl: false + Shift: false + Alt: true + Keys: + - "P" + - Name: Show/Hide the details pane + Shortcut: + - Win: false + Ctrl: false + Shift: true + Alt: true + Keys: + - "P" + - Name: Resize all columns to fit text + Shortcut: + - Win: false + Ctrl: true + Shift: false + Alt: false + Keys: + - "+" + - Name: Expand all folders + Description: In the navigation pane + Shortcut: + - Win: false + Ctrl: true + Shift: true + Alt: false + Keys: + - "E" + - SectionName: "File management" + Properties: + - Name: Display properties for the selected item + Shortcut: + - Win: false + Ctrl: false + Shift: false + Alt: true + Keys: + - "Enter" + - Name: Delete the selected item + Shortcut: + - Win: false + Ctrl: false + Shift: false + Alt: false + Keys: + - "" + - Name: Delete the selected item + Shortcut: + - Win: false + Ctrl: true + Shift: false + Alt: false + Keys: + - "D" + - Name: Delete the selected item permanently + Description: "This removes the item without sending it to the Recycle Bin" + Shortcut: + - Win: false + Ctrl: true + Shift: false + Alt: false + Keys: + - "D" + - Name: Create a new folder + Shortcut: + - Win: false + Ctrl: true + Shift: true + Alt: false + Keys: + - "N" + Recommended: true + - Name: Rename the selected item + Shortcut: + - Win: false + Ctrl: false + Shift: false + Alt: false + Keys: + - "F2" + - Name: Select multiple items + Shortcut: + - Win: false + Ctrl: true + Shift: false + Alt: false + Keys: + - "" + \ No newline at end of file diff --git a/src/modules/ShortcutGuide/ShortcutGuide.Ui/Assets/ShortcutGuide/CopilotKey.png b/src/modules/ShortcutGuide/ShortcutGuide.Ui/Assets/ShortcutGuide/CopilotKey.png new file mode 100644 index 000000000000..ccb59a6075af Binary files /dev/null and b/src/modules/ShortcutGuide/ShortcutGuide.Ui/Assets/ShortcutGuide/CopilotKey.png differ diff --git a/src/modules/ShortcutGuide/ShortcutGuide.Ui/Assets/ShortcutGuide/HeroImage-dark.png b/src/modules/ShortcutGuide/ShortcutGuide.Ui/Assets/ShortcutGuide/HeroImage-dark.png new file mode 100644 index 000000000000..aa1a055744b5 Binary files /dev/null and b/src/modules/ShortcutGuide/ShortcutGuide.Ui/Assets/ShortcutGuide/HeroImage-dark.png differ diff --git a/src/modules/ShortcutGuide/ShortcutGuide.Ui/Assets/ShortcutGuide/HeroImage.png b/src/modules/ShortcutGuide/ShortcutGuide.Ui/Assets/ShortcutGuide/HeroImage.png new file mode 100644 index 000000000000..54c028ec5467 Binary files /dev/null and b/src/modules/ShortcutGuide/ShortcutGuide.Ui/Assets/ShortcutGuide/HeroImage.png differ diff --git a/src/modules/ShortcutGuide/ShortcutGuide.Ui/Assets/ShortcutGuide/Microsoft.PowerToys.en-US.yml b/src/modules/ShortcutGuide/ShortcutGuide.Ui/Assets/ShortcutGuide/Microsoft.PowerToys.en-US.yml new file mode 100644 index 000000000000..c768b4bb5ebe --- /dev/null +++ b/src/modules/ShortcutGuide/ShortcutGuide.Ui/Assets/ShortcutGuide/Microsoft.PowerToys.en-US.yml @@ -0,0 +1,9 @@ +PackageName: Microsoft.PowerToys +Name: PowerToys +BackgroundProcess: True +WindowFilter: "powertoys.exe" +Shortcuts: + - SectionName: General + Properties: +# +# \ No newline at end of file diff --git a/src/modules/ShortcutGuide/ShortcutGuide.Ui/Assets/ShortcutGuide/OfficeKey.png b/src/modules/ShortcutGuide/ShortcutGuide.Ui/Assets/ShortcutGuide/OfficeKey.png new file mode 100644 index 000000000000..ab524b1793be Binary files /dev/null and b/src/modules/ShortcutGuide/ShortcutGuide.Ui/Assets/ShortcutGuide/OfficeKey.png differ diff --git a/src/modules/ShortcutGuide/ShortcutGuide/Shortcut-Guide.ico b/src/modules/ShortcutGuide/ShortcutGuide.Ui/Assets/ShortcutGuide/ShortcutGuide.ico similarity index 100% rename from src/modules/ShortcutGuide/ShortcutGuide/Shortcut-Guide.ico rename to src/modules/ShortcutGuide/ShortcutGuide.Ui/Assets/ShortcutGuide/ShortcutGuide.ico diff --git a/src/modules/ShortcutGuide/ShortcutGuide.Ui/Converters/ShortcutDescriptionToKeysConverter.cs b/src/modules/ShortcutGuide/ShortcutGuide.Ui/Converters/ShortcutDescriptionToKeysConverter.cs new file mode 100644 index 000000000000..4ed93a88efc9 --- /dev/null +++ b/src/modules/ShortcutGuide/ShortcutGuide.Ui/Converters/ShortcutDescriptionToKeysConverter.cs @@ -0,0 +1,100 @@ +// Copyright (c) Microsoft Corporation +// The Microsoft Corporation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System; +using System.Collections.Generic; +using System.Windows.Documents; +using ManagedCommon; +using Microsoft.UI.Xaml.Data; +using ShortcutGuide.Models; +using Windows.System; + +namespace ShortcutGuide.Converters +{ + public sealed partial class ShortcutDescriptionToKeysConverter : IValueConverter + { + public object Convert(object value, Type targetType, object parameter, string language) + { + if (value is ShortcutDescription description) + { + // Populate keysList with the keys from the ShortcutDescription + return this.GetKeysList(description); + } + else + { + List keysList = [string.Empty]; + return keysList; + } + } + + public object ConvertBack(object value, Type targetType, object parameter, string language) + { + return value; + } + + public List GetKeysList(ShortcutDescription description) + { + List shortcutList = []; + + if (description.Win) + { + shortcutList.Add(92); // The Windows key or button. + } + + if (description.Ctrl) + { + shortcutList.Add("Ctrl"); + } + + if (description.Alt) + { + shortcutList.Add("Alt"); + } + + if (description.Shift) + { + shortcutList.Add(16); // The Shift key or button. + } + + foreach (var key in description.Keys) + { + // Try to parse a string key number to a VirtualKey + if (int.TryParse(key, out int keyCode)) + { + shortcutList.Add(keyCode); + } + else + { + switch (key) + { + // https://learn.microsoft.com/uwp/api/windows.system.virtualkey?view=winrt-20348 + case "Up": + shortcutList.Add(38); // The Up Arrow key or button. + break; + case "Down": + shortcutList.Add(40); // The Down Arrow key or button. + break; + case "Left": + shortcutList.Add(37); // The Left Arrow key or button. + break; + case "Right": + shortcutList.Add(39); // The Right Arrow key or button. + break; + case "Back": + shortcutList.Add(8); // The Back key or button. + break; + case "": + shortcutList.Add("Num"); + break; + default: + shortcutList.Add(key); // Add other keys as strings. + break; + } + } + } + + return shortcutList; + } + } +} diff --git a/src/modules/ShortcutGuide/ShortcutGuide.Ui/Helpers/DisplayHelper.cs b/src/modules/ShortcutGuide/ShortcutGuide.Ui/Helpers/DisplayHelper.cs new file mode 100644 index 000000000000..e638ff1c7e2d --- /dev/null +++ b/src/modules/ShortcutGuide/ShortcutGuide.Ui/Helpers/DisplayHelper.cs @@ -0,0 +1,50 @@ +// Copyright (c) Microsoft Corporation +// The Microsoft Corporation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using Windows.Foundation; +using WinUIEx; + +namespace ShortcutGuide.Helpers +{ + public static class DisplayHelper + { + /// + /// Returns the display work area for the monitor that contains the specified window. + /// + /// The window handle + /// A element containing the display area + public static Rect GetWorkAreaForDisplayWithWindow(nint hwnd) + { + _foundMonitorIndex = -1; + _monitorIndex = 0; + var monitor = NativeMethods.MonitorFromWindow(hwnd, (int)NativeMethods.MonitorFromWindowDwFlags.MONITOR_DEFAULTTONEAREST); + NativeMethods.EnumDisplayMonitors(nint.Zero, nint.Zero, MonitorEnumProc, new NativeMethods.LPARAM(monitor)); + return MonitorInfo.GetDisplayMonitors()[_foundMonitorIndex].RectWork; + } + + /// + /// The index of the monitor that contains the specified window. -1 indicates that no monitor was found (yet). + /// + private static int _foundMonitorIndex = -1; + + /// + /// The index of the monitor in the enumeration. This is used to find the correct monitor in the list of monitors. + /// + private static int _monitorIndex; + + private static bool MonitorEnumProc(nint hMonitor, nint hdcMonitor, ref NativeMethods.RECT lprcMonitor, nint dwData) + { + nint targetMonitor = dwData; + + if (hMonitor == targetMonitor) + { + _foundMonitorIndex = _monitorIndex; + return false; + } + + _monitorIndex++; + return true; + } + } +} diff --git a/src/modules/ShortcutGuide/ShortcutGuide.Ui/Helpers/DpiHelper.cs b/src/modules/ShortcutGuide/ShortcutGuide.Ui/Helpers/DpiHelper.cs new file mode 100644 index 000000000000..78f5fa8b12b7 --- /dev/null +++ b/src/modules/ShortcutGuide/ShortcutGuide.Ui/Helpers/DpiHelper.cs @@ -0,0 +1,43 @@ +// Copyright (c) Microsoft Corporation +// The Microsoft Corporation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +namespace ShortcutGuide.Helpers +{ + // This class is rewritten from C++ to C# from the measure tool project + internal static class DpiHelper + { +#pragma warning disable SA1310 // Field names should not contain underscore + private const int DEFAULT_DPI = 96; + private const int MONITOR_DEFAULTTONEAREST = 2; + private const int MDT_EFFECTIVE_DPI = 0; +#pragma warning restore SA1310 // Field names should not contain underscore + + public static float GetDPIScaleForWindow(int hwnd) + { + int dpi = DEFAULT_DPI; + GetScreenDPIForWindow(hwnd, ref dpi); + return (float)dpi / DEFAULT_DPI; + } + + private static long GetScreenDPIForWindow(int hwnd, ref int dpi) + { + var targetMonitor = NativeMethods.MonitorFromWindow(hwnd, MONITOR_DEFAULTTONEAREST); + return GetScreenDPIForMonitor(targetMonitor.ToInt32(), ref dpi); + } + + private static long GetScreenDPIForMonitor(int targetMonitor, ref int dpi) + { + if (targetMonitor != 0) + { + int dummy = 0; + return NativeMethods.GetDpiForMonitor(targetMonitor, MDT_EFFECTIVE_DPI, ref dpi, ref dummy); + } + else + { + dpi = DEFAULT_DPI; + return 0x80004005L; + } + } + } +} diff --git a/src/modules/ShortcutGuide/ShortcutGuide.Ui/Helpers/ManifestInterpreter.cs b/src/modules/ShortcutGuide/ShortcutGuide.Ui/Helpers/ManifestInterpreter.cs new file mode 100644 index 000000000000..e45ccb58d1be --- /dev/null +++ b/src/modules/ShortcutGuide/ShortcutGuide.Ui/Helpers/ManifestInterpreter.cs @@ -0,0 +1,150 @@ +// Copyright (c) Microsoft Corporation +// The Microsoft Corporation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System; +using System.Collections.Generic; +using System.ComponentModel; +using System.Diagnostics; +using System.Globalization; +using System.IO; +using System.Linq; +using System.Text.RegularExpressions; +using ShortcutGuide.Models; +using YamlDotNet.Serialization; + +namespace ShortcutGuide.Helpers +{ + /// + /// Helps to interpret the manifest files for the Shortcut Guide. + /// + public class ManifestInterpreter + { + // Todo: Get language from settings or environment variable, default to "en-US" + + /// + /// Gets the language used for the manifest files. + /// + public static string Language => "en-US"; + + /// + /// Returns the shortcuts for a specific application. + /// + /// + /// The method should only be called if the application is known to have a shortcuts file. + /// + /// The manifest id. + /// The deserialized shortcuts file. + /// The requested file was not found. + public static ShortcutFile GetShortcutsOfApplication(string applicationName) + { + string path = PathOfManifestFiles; + IEnumerable files = Directory.EnumerateFiles(path, applicationName + ".*.yml") ?? + throw new FileNotFoundException($"The file for the application '{applicationName}' was not found in '{path}'."); + + IEnumerable filesEnumerable = files as string[] ?? [.. files]; + return filesEnumerable.Any(f => f.EndsWith($".{Language}.yml", StringComparison.InvariantCulture)) + ? YamlToShortcutList(File.ReadAllText(Path.Combine(path, applicationName + $".{Language}.yml"))) + : filesEnumerable.Any(f => f.EndsWith(".en-US.yml", StringComparison.InvariantCulture)) + ? YamlToShortcutList(File.ReadAllText(filesEnumerable.First(f => f.EndsWith(".en-US.yml", StringComparison.InvariantCulture)))) + : throw new FileNotFoundException($"The file for the application '{applicationName}' was not found in '{path}' with the language '{Language}' or 'en-US'."); + } + + /// + /// Deserializes the content of a YAML file to a . + /// + /// The content of the YAML file. + /// A deserialized object. + private static ShortcutFile YamlToShortcutList(string content) + { + Deserializer deserializer = new(); + return deserializer.Deserialize(content); + } + + /// + /// Gets the path to the directory where the manifest files are stored. + /// + public static string PathOfManifestFiles => Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData), "Microsoft", "WinGet", "KeyboardShortcuts"); + + /// + /// Retrieves the index YAML file that contains the list of all applications and their shortcuts. + /// + /// A deserialized object. + public static IndexFile GetIndexYamlFile() + { + string path = PathOfManifestFiles; + string content = File.ReadAllText(Path.Combine(path, "index.yml")); + Deserializer deserializer = new(); + return deserializer.Deserialize(content); + } + + /// + /// Retrieves all application IDs that should be displayed, based on the foreground window and background processes. + /// + /// An array of all application IDs. + public static string[] GetAllCurrentApplicationIds() + { + nint handle = NativeMethods.GetForegroundWindow(); + + List applicationIds = []; + + Process[] processes = Process.GetProcesses(); + + if (NativeMethods.GetWindowThreadProcessId(handle, out uint processId) > 0) + { + string? name = Process.GetProcessById((int)processId).MainModule?.ModuleName; + + if (name is not null) + { + try + { + foreach (var item in GetIndexYamlFile().Index.First((s) => !s.BackgroundProcess && IsMatch(name, s.WindowFilter)).Apps) + { + applicationIds.Add(item); + } + } + catch (InvalidOperationException) + { + } + } + } + + foreach (var item in GetIndexYamlFile().Index.Where((s) => s.BackgroundProcess)) + { + try + { + if (processes.Any((p) => + { + try + { + return IsMatch(p.MainModule!.ModuleName, item.WindowFilter); + } + catch (Win32Exception) + { + return false; + } + })) + { + foreach (var app in item.Apps) + { + applicationIds.Add(app); + } + } + } + catch (InvalidOperationException) + { + } + } + + return [.. applicationIds]; + + static bool IsMatch(string input, string filter) + { + input = input.ToLower(CultureInfo.InvariantCulture); + filter = filter.ToLower(CultureInfo.InvariantCulture); + string regexPattern = "^" + Regex.Escape(filter).Replace("\\*", ".*") + "$"; + return Regex.IsMatch(input, regexPattern); + } + } + } +} diff --git a/src/modules/ShortcutGuide/ShortcutGuide.Ui/Helpers/NavItemIconHelper.cs b/src/modules/ShortcutGuide/ShortcutGuide.Ui/Helpers/NavItemIconHelper.cs new file mode 100644 index 000000000000..7f54365e8c98 --- /dev/null +++ b/src/modules/ShortcutGuide/ShortcutGuide.Ui/Helpers/NavItemIconHelper.cs @@ -0,0 +1,85 @@ +// Copyright (c) Microsoft Corporation +// The Microsoft Corporation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using Microsoft.UI.Xaml; + +namespace ShortcutGuide.Helpers; + +internal static class NavItemIconHelper +{ + public static object GetSelectedIcon(DependencyObject obj) + { + return obj.GetValue(SelectedIconProperty); + } + + public static void SetSelectedIcon(DependencyObject obj, object value) + { + obj.SetValue(SelectedIconProperty, value); + } + + public static readonly DependencyProperty SelectedIconProperty = + DependencyProperty.RegisterAttached("SelectedIcon", typeof(object), typeof(NavItemIconHelper), new PropertyMetadata(null)); + + /// + /// Gets the value of for a + /// + /// Returns a boolean indicating whether the notification dot should be shown. + public static bool GetShowNotificationDot(DependencyObject obj) + { + return (bool)obj.GetValue(ShowNotificationDotProperty); + } + + /// + /// Sets on a + /// + public static void SetShowNotificationDot(DependencyObject obj, bool value) + { + obj.SetValue(ShowNotificationDotProperty, value); + } + + /// + /// An attached property that sets whether or not a notification dot should be shown on an associated + /// + public static readonly DependencyProperty ShowNotificationDotProperty = + DependencyProperty.RegisterAttached("ShowNotificationDot", typeof(bool), typeof(NavItemIconHelper), new PropertyMetadata(false)); + + /// + /// Gets the value of for a + /// + /// Returns the unselected icon as an object. + public static object GetUnselectedIcon(DependencyObject obj) + { + return (object)obj.GetValue(UnselectedIconProperty); + } + + /// + /// Sets the value of for a + /// + public static void SetUnselectedIcon(DependencyObject obj, object value) + { + obj.SetValue(UnselectedIconProperty, value); + } + + /// + /// An attached property that sets the unselected icon on an associated + /// + public static readonly DependencyProperty UnselectedIconProperty = + DependencyProperty.RegisterAttached("UnselectedIcon", typeof(object), typeof(NavItemIconHelper), new PropertyMetadata(null)); + + public static Visibility GetStaticIconVisibility(DependencyObject obj) + { + return (Visibility)obj.GetValue(StaticIconVisibilityProperty); + } + + public static void SetStaticIconVisibility(DependencyObject obj, Visibility value) + { + obj.SetValue(StaticIconVisibilityProperty, value); + } + + /// + /// An attached property that sets the visibility of the static icon in the associated . + /// + public static readonly DependencyProperty StaticIconVisibilityProperty = + DependencyProperty.RegisterAttached("StaticIconVisibility", typeof(Visibility), typeof(NavItemIconHelper), new PropertyMetadata(Visibility.Collapsed)); +} diff --git a/src/modules/ShortcutGuide/ShortcutGuide.Ui/Helpers/PinnedShortcutsHelper.cs b/src/modules/ShortcutGuide/ShortcutGuide.Ui/Helpers/PinnedShortcutsHelper.cs new file mode 100644 index 000000000000..ea84893ec220 --- /dev/null +++ b/src/modules/ShortcutGuide/ShortcutGuide.Ui/Helpers/PinnedShortcutsHelper.cs @@ -0,0 +1,37 @@ +// Copyright (c) Microsoft Corporation +// The Microsoft Corporation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Text; +using System.Text.Json; +using System.Threading.Tasks; +using Microsoft.PowerToys.Settings.UI.Library; +using ShortcutGuide.Models; + +namespace ShortcutGuide.Helpers +{ + public static class PinnedShortcutsHelper + { + public static void UpdatePinnedShortcuts(string appName, ShortcutEntry shortcutEntry) + { + if (!App.PinnedShortcuts[appName].Remove(shortcutEntry)) + { + App.PinnedShortcuts[appName].Add(shortcutEntry); + } + + Save(); + } + + public static void Save() + { + string serialized = JsonSerializer.Serialize(App.PinnedShortcuts); + + string pinnedPath = SettingsUtils.Default.GetSettingsFilePath(ShortcutGuideSettings.ModuleName, "Pinned.json"); + File.WriteAllText(pinnedPath, serialized); + } + } +} diff --git a/src/modules/ShortcutGuide/ShortcutGuide.Ui/Helpers/PowerToysShortcutsPopulator.cs b/src/modules/ShortcutGuide/ShortcutGuide.Ui/Helpers/PowerToysShortcutsPopulator.cs new file mode 100644 index 000000000000..22e88db819bc --- /dev/null +++ b/src/modules/ShortcutGuide/ShortcutGuide.Ui/Helpers/PowerToysShortcutsPopulator.cs @@ -0,0 +1,197 @@ +// Copyright (c) Microsoft Corporation +// The Microsoft Corporation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System; +using System.Globalization; +using System.IO; +using System.Text; +using System.Text.RegularExpressions; +using Microsoft.PowerToys.Settings.UI.Library; +using static ShortcutGuide.Helpers.ResourceLoaderInstance; + +namespace ShortcutGuide.Helpers +{ + /// + /// Populates the PowerToys shortcuts in the manifest files. + /// + internal sealed partial class PowerToysShortcutsPopulator + { + /// + /// Populates the PowerToys shortcuts in the manifest files. + /// + public static void Populate() + { + string path = Path.Combine(ManifestInterpreter.PathOfManifestFiles, $"Microsoft.PowerToys.{ManifestInterpreter.Language}.yml"); + + StringBuilder content = new(File.ReadAllText(path)); + + const string populateStartString = "# "; + const string populateEndString = "# "; + + content = new(PopulateRegex().Replace(content.ToString(), populateStartString + Environment.NewLine)); + + SettingsUtils settingsUtils = SettingsUtils.Default; + EnabledModules enabledModules = SettingsRepository.GetInstance(settingsUtils).SettingsConfig.Enabled; + if (enabledModules.AdvancedPaste) + { + AdvancedPasteProperties advancedPasteProperties = SettingsRepository.GetInstance(settingsUtils).SettingsConfig.Properties; + content.Append(HotkeySettingsToYaml(advancedPasteProperties.AdvancedPasteUIShortcut, SettingsResourceLoader.GetString("AdvancedPaste/ModuleTitle"), SettingsResourceLoader.GetString("AdvancedPasteUI_Shortcut/Header"))); + content.Append(HotkeySettingsToYaml(advancedPasteProperties.PasteAsPlainTextShortcut, SettingsResourceLoader.GetString("AdvancedPaste/ModuleTitle"), SettingsResourceLoader.GetString("PasteAsPlainText_Shortcut/Header"))); + content.Append(HotkeySettingsToYaml(advancedPasteProperties.PasteAsMarkdownShortcut, SettingsResourceLoader.GetString("AdvancedPaste/ModuleTitle"), SettingsResourceLoader.GetString("PasteAsMarkdown_Shortcut/Header"))); + content.Append(HotkeySettingsToYaml(advancedPasteProperties.PasteAsJsonShortcut, SettingsResourceLoader.GetString("AdvancedPaste/ModuleTitle"), SettingsResourceLoader.GetString("PasteAsJson_Shortcut/Header"))); + if (advancedPasteProperties.AdditionalActions.ImageToText.IsShown) + { + content.Append(HotkeySettingsToYaml(advancedPasteProperties.AdditionalActions.ImageToText.Shortcut, SettingsResourceLoader.GetString("AdvancedPaste/ModuleTitle"), SettingsResourceLoader.GetString("ImageToText/Header"))); + } + + if (advancedPasteProperties.AdditionalActions.PasteAsFile.IsShown) + { + content.Append(HotkeySettingsToYaml(advancedPasteProperties.AdditionalActions.PasteAsFile.PasteAsTxtFile.Shortcut, SettingsResourceLoader.GetString("AdvancedPaste/ModuleTitle"), SettingsResourceLoader.GetString("PasteAsTxtFile/Header"))); + content.Append(HotkeySettingsToYaml(advancedPasteProperties.AdditionalActions.PasteAsFile.PasteAsPngFile.Shortcut, SettingsResourceLoader.GetString("AdvancedPaste/ModuleTitle"), SettingsResourceLoader.GetString("PasteAsPngFile/Header"))); + content.Append(HotkeySettingsToYaml(advancedPasteProperties.AdditionalActions.PasteAsFile.PasteAsHtmlFile.Shortcut, SettingsResourceLoader.GetString("AdvancedPaste/ModuleTitle"), SettingsResourceLoader.GetString("PasteAsHtmlFile/Header"))); + } + + if (advancedPasteProperties.AdditionalActions.Transcode.IsShown) + { + content.Append(HotkeySettingsToYaml(advancedPasteProperties.AdditionalActions.Transcode.TranscodeToMp3.Shortcut, SettingsResourceLoader.GetString("AdvancedPaste/ModuleTitle"), SettingsResourceLoader.GetString("TranscodeToMp3/Header"))); + content.Append(HotkeySettingsToYaml(advancedPasteProperties.AdditionalActions.Transcode.TranscodeToMp4.Shortcut, SettingsResourceLoader.GetString("AdvancedPaste/ModuleTitle"), SettingsResourceLoader.GetString("TranscodeToMp4/Header"))); + } + } + + if (enabledModules.AlwaysOnTop) + { + content.Append(HotkeySettingsToYaml(SettingsRepository.GetInstance(settingsUtils).SettingsConfig.Properties.Hotkey, SettingsResourceLoader.GetString("AlwaysOnTop/ModuleTitle"), SettingsResourceLoader.GetString("AlwaysOnTop_ShortDescription"))); + } + + if (enabledModules.ColorPicker) + { + content.Append(HotkeySettingsToYaml(SettingsRepository.GetInstance(settingsUtils).SettingsConfig.Properties.ActivationShortcut, SettingsResourceLoader.GetString("ColorPicker/ModuleTitle"), SettingsResourceLoader.GetString("ColorPicker_ShortDescription"))); + } + + if (enabledModules.CmdPal) + { + content.Append(HotkeySettingsToYaml(new CmdPalProperties().Hotkey, SettingsResourceLoader.GetString("CmdPal/ModuleTitle"))); + } + + if (enabledModules.CropAndLock) + { + CropAndLockProperties cropAndLockProperties = SettingsRepository.GetInstance(settingsUtils).SettingsConfig.Properties; + content.Append(HotkeySettingsToYaml(cropAndLockProperties.ThumbnailHotkey, SettingsResourceLoader.GetString("CropAndLock/ModuleTitle"), SettingsResourceLoader.GetString("CropAndLock_Thumbnail"))); + content.Append(HotkeySettingsToYaml(cropAndLockProperties.ReparentHotkey, SettingsResourceLoader.GetString("CropAndLock/ModuleTitle"), SettingsResourceLoader.GetString("CropAndLock_Reparent"))); + } + + if (enabledModules.CursorWrap) + { + content.Append(HotkeySettingsToYaml(SettingsRepository.GetInstance(settingsUtils).SettingsConfig.Properties.ActivationShortcut, SettingsResourceLoader.GetString("MouseUtils_CursorWrap/Header"), SettingsResourceLoader.GetString("MouseUtils_CursorWrap/Description"))); + } + + if (enabledModules.FancyZones) + { + content.Append(HotkeySettingsToYaml(SettingsRepository.GetInstance(settingsUtils).SettingsConfig.Properties.FancyzonesEditorHotkey, SettingsResourceLoader.GetString("FancyZones/ModuleTitle"), SettingsResourceLoader.GetString("FancyZones_OpenEditor"))); + } + + if (enabledModules.LightSwitch) + { + content.Append(HotkeySettingsToYaml(SettingsRepository.GetInstance(settingsUtils).SettingsConfig.Properties.ToggleThemeHotkey, SettingsResourceLoader.GetString("LightSwitch/ModuleTitle"), SettingsResourceLoader.GetString("LightSwitch_ForceDarkMode"))); + } + + if (enabledModules.MouseHighlighter) + { + content.Append(HotkeySettingsToYaml(SettingsRepository.GetInstance(settingsUtils).SettingsConfig.Properties.ActivationShortcut, SettingsResourceLoader.GetString("MouseUtils_MouseHighlighter/Header"), SettingsResourceLoader.GetString("MouseHighlighter_ShortDescription"))); + } + + if (enabledModules.MouseJump) + { + content.Append(HotkeySettingsToYaml(SettingsRepository.GetInstance(settingsUtils).SettingsConfig.Properties.ActivationShortcut, SettingsResourceLoader.GetString("MouseUtils_MouseJump/Header"), SettingsResourceLoader.GetString("MouseJump_ShortDescription"))); + } + + if (enabledModules.MousePointerCrosshairs) + { + content.Append(HotkeySettingsToYaml(SettingsRepository.GetInstance(settingsUtils).SettingsConfig.Properties.ActivationShortcut, SettingsResourceLoader.GetString("MouseUtils_MousePointerCrosshairs/Header"), SettingsResourceLoader.GetString("MouseCrosshairs_ShortDescription"))); + } + + if (enabledModules.Peek) + { + content.Append(HotkeySettingsToYaml(SettingsRepository.GetInstance(settingsUtils).SettingsConfig.Properties.ActivationShortcut, SettingsResourceLoader.GetString("Peek/ModuleTitle"))); + } + + if (enabledModules.PowerLauncher) + { + content.Append(HotkeySettingsToYaml(SettingsRepository.GetInstance(settingsUtils).SettingsConfig.Properties.OpenPowerLauncher, SettingsResourceLoader.GetString("PowerLauncher/ModuleTitle"))); + } + + if (enabledModules.MeasureTool) + { + content.Append(HotkeySettingsToYaml(SettingsRepository.GetInstance(settingsUtils).SettingsConfig.Properties.ActivationShortcut, SettingsResourceLoader.GetString("MeasureTool/ModuleTitle"), SettingsResourceLoader.GetString("ScreenRuler_ShortDescription"))); + } + + if (enabledModules.ShortcutGuide) + { + content.Append(HotkeySettingsToYaml(SettingsRepository.GetInstance(settingsUtils).SettingsConfig.Properties.DefaultOpenShortcutGuide, SettingsResourceLoader.GetString("ShortcutGuide/ModuleTitle"), SettingsResourceLoader.GetString("ShortcutGuide_ShortDescription"))); + } + + if (enabledModules.PowerOcr) + { + content.Append(HotkeySettingsToYaml(SettingsRepository.GetInstance(settingsUtils).SettingsConfig.Properties.ActivationShortcut, SettingsResourceLoader.GetString("TextExtractor/ModuleTitle"), SettingsResourceLoader.GetString("PowerOcr_ShortDescription"))); + } + + if (enabledModules.Workspaces) + { + content.Append(HotkeySettingsToYaml(SettingsRepository.GetInstance(settingsUtils).SettingsConfig.Properties.Hotkey, SettingsResourceLoader.GetString("Workspaces/ModuleTitle"), SettingsResourceLoader.GetString("Workspaces_ShortDescription"))); + } + + // Todo: ZoomIt hotkeys currently not supported, because ZoomIt does save their settings in the view model instead of the settings properties, which is weird. + /* + if (enabledModules.ZoomIt) + { + content.Append(HotkeySettingsToYaml(SettingsRepository.GetInstance(settingsUtils).SettingsConfig.Properties.ToggleKey, SettingsResourceLoader.GetString("ZoomIt/ModuleTitle"), SettingsResourceLoader.GetString("ZoomIt_ZoomGroup/Header"))); + content.Append(HotkeySettingsToYaml(SettingsRepository.GetInstance(settingsUtils).SettingsConfig.Properties.LiveZoomToggleKey, SettingsResourceLoader.GetString("ZoomIt/ModuleTitle"), SettingsResourceLoader.GetString("ZoomIt_LiveZoomGroup/Header"))); + content.Append(HotkeySettingsToYaml(SettingsRepository.GetInstance(settingsUtils).SettingsConfig.Properties.DrawToggleKey, SettingsResourceLoader.GetString("ZoomIt/ModuleTitle"), SettingsResourceLoader.GetString("ZoomIt_DrawGroup/Header"))); + content.Append(HotkeySettingsToYaml(SettingsRepository.GetInstance(settingsUtils).SettingsConfig.Properties.DemoTypeToggleKey, SettingsResourceLoader.GetString("ZoomIt/ModuleTitle"), SettingsResourceLoader.GetString("ZoomIt_DemoTypeGroup/Header"))); + content.Append(HotkeySettingsToYaml(SettingsRepository.GetInstance(settingsUtils).SettingsConfig.Properties.BreakTimerKey, SettingsResourceLoader.GetString("ZoomIt/ModuleTitle"), SettingsResourceLoader.GetString("ZoomIt_BreakGroup/Header"))); + content.Append(HotkeySettingsToYaml(SettingsRepository.GetInstance(settingsUtils).SettingsConfig.Properties.RecordToggleKey, SettingsResourceLoader.GetString("ZoomIt/ModuleTitle"), SettingsResourceLoader.GetString("ZoomIt_RecordGroup/Header"))); + content.Append(HotkeySettingsToYaml(SettingsRepository.GetInstance(settingsUtils).SettingsConfig.Properties.SnipToggleKey, SettingsResourceLoader.GetString("ZoomIt/ModuleTitle"), SettingsResourceLoader.GetString("ZoomIt_SnipGroup/Header"))); + }*/ + + content.Append(populateEndString); + + File.WriteAllText(path, content.ToString()); + } + + /// + /// Converts the hotkey settings to a YAML format string for the manifest file. + /// + /// Object containing a hotkey from the settings. + /// The name of the PowerToys module. + /// Description of the action. + /// Yaml code for the manifest file. + private static string HotkeySettingsToYaml(HotkeySettings hotkeySettings, string moduleName, string? description = null) + { + string content = string.Empty; + content += " - Name: " + moduleName + Environment.NewLine; + content += " Shortcut: " + Environment.NewLine; + content += " - Win: " + hotkeySettings.Win.ToString() + Environment.NewLine; + content += " Ctrl: " + hotkeySettings.Ctrl.ToString() + Environment.NewLine; + content += " Alt: " + hotkeySettings.Alt.ToString() + Environment.NewLine; + content += " Shift: " + hotkeySettings.Shift.ToString() + Environment.NewLine; + content += " Keys:" + Environment.NewLine; + content += " - " + hotkeySettings.Code.ToString(CultureInfo.InvariantCulture) + Environment.NewLine; + if (description != null) + { + content += " Description: " + description + Environment.NewLine; + } + + return content; + } + + /// + private static string HotkeySettingsToYaml(KeyboardKeysProperty hotkeySettings, string moduleName, string? description = null) + { + return HotkeySettingsToYaml(hotkeySettings.Value, moduleName, description); + } + + [GeneratedRegex(@"# [\s\S\n\r]*# ")] + private static partial Regex PopulateRegex(); + } +} diff --git a/src/modules/ShortcutGuide/ShortcutGuide.Ui/Helpers/ResourceLoaderInstance.cs b/src/modules/ShortcutGuide/ShortcutGuide.Ui/Helpers/ResourceLoaderInstance.cs new file mode 100644 index 000000000000..f4e18161e9db --- /dev/null +++ b/src/modules/ShortcutGuide/ShortcutGuide.Ui/Helpers/ResourceLoaderInstance.cs @@ -0,0 +1,27 @@ +// Copyright (c) Microsoft Corporation +// The Microsoft Corporation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using Microsoft.Windows.ApplicationModel.Resources; + +namespace ShortcutGuide.Helpers +{ + internal static class ResourceLoaderInstance + { + /// + /// Gets the resource loader for the Shortcut Guide module. + /// + internal static ResourceLoader ResourceLoader { get; private set; } + + /// + /// Gets the resource loader for the Settings module. + /// + internal static ResourceLoader SettingsResourceLoader { get; private set; } + + static ResourceLoaderInstance() + { + ResourceLoader = new ResourceLoader("PowerToys.ShortcutGuide.pri"); + SettingsResourceLoader = new ResourceLoader("PowerToys.Settings.pri"); + } + } +} diff --git a/src/modules/ShortcutGuide/ShortcutGuide.Ui/Helpers/StringResourceExtension.cs b/src/modules/ShortcutGuide/ShortcutGuide.Ui/Helpers/StringResourceExtension.cs new file mode 100644 index 000000000000..f647aef4141a --- /dev/null +++ b/src/modules/ShortcutGuide/ShortcutGuide.Ui/Helpers/StringResourceExtension.cs @@ -0,0 +1,30 @@ +// Copyright (c) Microsoft Corporation +// The Microsoft Corporation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using Microsoft.UI.Xaml.Markup; + +namespace ShortcutGuide.Helpers +{ + [MarkupExtensionReturnType(ReturnType = typeof(string))] + public partial class StringResourceExtension : MarkupExtension + { + public enum SpecialTreatment + { + None, + FirstCharOnly, + EverythingExceptFirstChar, + } + + public string Key { get; set; } = string.Empty; + + public SpecialTreatment Treatment { get; set; } = SpecialTreatment.None; + + protected override object ProvideValue() => this.Treatment switch + { + SpecialTreatment.FirstCharOnly => ResourceLoaderInstance.ResourceLoader.GetString(this.Key)[0].ToString(), + SpecialTreatment.EverythingExceptFirstChar => ResourceLoaderInstance.ResourceLoader.GetString(this.Key)[1..], + _ => ResourceLoaderInstance.ResourceLoader.GetString(this.Key), + }; + } +} diff --git a/src/modules/ShortcutGuide/ShortcutGuide.Ui/Helpers/TasklistPositions.cs b/src/modules/ShortcutGuide/ShortcutGuide.Ui/Helpers/TasklistPositions.cs new file mode 100644 index 000000000000..ba5427f35b13 --- /dev/null +++ b/src/modules/ShortcutGuide/ShortcutGuide.Ui/Helpers/TasklistPositions.cs @@ -0,0 +1,45 @@ +// Copyright (c) Microsoft Corporation +// The Microsoft Corporation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System.Runtime.InteropServices; +using WinRT.Interop; +using TasklistButton = ShortcutGuide.NativeMethods.TasklistButton; + +namespace ShortcutGuide.Helpers +{ + /// + /// Provides methods to retrieve the positions of taskbar buttons on the current monitor. + /// + internal static class TasklistPositions + { + /// + /// Retrieves the taskbar buttons for the current monitor. + /// + /// An array of the taskbar buttons. + public static TasklistButton[] GetButtons() + { + var monitor = NativeMethods.MonitorFromWindow(WindowNative.GetWindowHandle(App.MainWindow), 0); + nint ptr = NativeMethods.GetTasklistButtons(monitor, out int size); + if (ptr == nint.Zero) + { + return []; + } + + if (size <= 0) + { + return []; + } + + TasklistButton[] buttons = new TasklistButton[size]; + nint currentPtr = ptr; + for (int i = 0; i < size; i++) + { + buttons[i] = Marshal.PtrToStructure(currentPtr); + currentPtr += Marshal.SizeOf(); + } + + return buttons; + } + } +} diff --git a/src/modules/ShortcutGuide/ShortcutGuide.Ui/Models/IndexFile.cs b/src/modules/ShortcutGuide/ShortcutGuide.Ui/Models/IndexFile.cs new file mode 100644 index 000000000000..e8c64d813b82 --- /dev/null +++ b/src/modules/ShortcutGuide/ShortcutGuide.Ui/Models/IndexFile.cs @@ -0,0 +1,22 @@ +// Copyright (c) Microsoft Corporation +// The Microsoft Corporation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +namespace ShortcutGuide.Models +{ + public struct IndexFile + { + public struct IndexItem + { + public string WindowFilter { get; set; } + + public bool BackgroundProcess { get; set; } + + public string[] Apps { get; set; } + } + + public string DefaultShellName { get; set; } + + public IndexItem[] Index { get; set; } + } +} diff --git a/src/modules/ShortcutGuide/ShortcutGuide.Ui/Models/ShortcutCategory.cs b/src/modules/ShortcutGuide/ShortcutGuide.Ui/Models/ShortcutCategory.cs new file mode 100644 index 000000000000..0dcd3d1323af --- /dev/null +++ b/src/modules/ShortcutGuide/ShortcutGuide.Ui/Models/ShortcutCategory.cs @@ -0,0 +1,13 @@ +// Copyright (c) Microsoft Corporation +// The Microsoft Corporation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +namespace ShortcutGuide.Models +{ + public struct ShortcutCategory + { + public string SectionName { get; set; } + + public ShortcutEntry[] Properties { get; set; } + } +} diff --git a/src/modules/ShortcutGuide/ShortcutGuide.Ui/Models/ShortcutDescription.cs b/src/modules/ShortcutGuide/ShortcutGuide.Ui/Models/ShortcutDescription.cs new file mode 100644 index 000000000000..583571cb050f --- /dev/null +++ b/src/modules/ShortcutGuide/ShortcutGuide.Ui/Models/ShortcutDescription.cs @@ -0,0 +1,60 @@ +// Copyright (c) Microsoft Corporation +// The Microsoft Corporation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Text.Json.Serialization; +using System.Threading.Tasks; + +namespace ShortcutGuide.Models +{ + public class ShortcutDescription(bool ctrl, bool shift, bool alt, bool win, string[] keys) + { + public ShortcutDescription() + : this(false, false, false, false, []) + { + } + + [JsonPropertyName(nameof(Ctrl))] + public bool Ctrl { get; set; } = ctrl; + + [JsonPropertyName(nameof(Shift))] + public bool Shift { get; set; } = shift; + + [JsonPropertyName(nameof(Alt))] + public bool Alt { get; set; } = alt; + + [JsonPropertyName(nameof(Win))] + public bool Win { get; set; } = win; + + [JsonPropertyName(nameof(Keys))] + public string[] Keys { get; set; } = keys; + + public override bool Equals(object? obj) + { + return obj is ShortcutDescription other && this.Ctrl == other.Ctrl && + this.Shift == other.Shift && + this.Alt == other.Alt && + this.Win == other.Win && + this.Keys.SequenceEqual(other.Keys); + } + + public override int GetHashCode() + { + return base.GetHashCode(); + } + + public static bool operator ==(ShortcutDescription? left, ShortcutDescription? right) + { + return (left is null && right is null) || (left is not null && right is not null && left.Equals(right)); + } + + public static bool operator !=(ShortcutDescription? left, ShortcutDescription? right) + { + return !(left == right); + } + } +} diff --git a/src/modules/ShortcutGuide/ShortcutGuide.Ui/Models/ShortcutEntry.cs b/src/modules/ShortcutGuide/ShortcutGuide.Ui/Models/ShortcutEntry.cs new file mode 100644 index 000000000000..4e4b0a050f24 --- /dev/null +++ b/src/modules/ShortcutGuide/ShortcutGuide.Ui/Models/ShortcutEntry.cs @@ -0,0 +1,63 @@ +// Copyright (c) Microsoft Corporation +// The Microsoft Corporation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text.Json.Serialization; +using System.Threading; +using System.Threading.Tasks; +using Microsoft.PowerToys.Settings.UI.Library.Utilities; +using Microsoft.UI.Xaml; +using Microsoft.UI.Xaml.Controls; +using Microsoft.UI.Xaml.Markup; +using Windows.UI.Text; +using static ShortcutGuide.Models.ShortcutEntry; +using Orientation = Microsoft.UI.Xaml.Controls.Orientation; + +namespace ShortcutGuide.Models +{ + public class ShortcutEntry(string name, string? description, bool recommended, ShortcutDescription[] shortcutDescriptions) + { + public override bool Equals(object? obj) + { + return obj is ShortcutEntry other && this.Name == other.Name && + this.Description == other.Description && + this.Shortcut.Length == other.Shortcut.Length && + this.Shortcut.SequenceEqual(other.Shortcut); + } + + public override int GetHashCode() + { + return base.GetHashCode(); + } + + public static bool operator ==(ShortcutEntry? left, ShortcutEntry? right) + { + return (left is null && right is null) || (left is not null && right is not null && left.Equals(right)); + } + + public static bool operator !=(ShortcutEntry? left, ShortcutEntry? right) + { + return !(left == right); + } + + public ShortcutEntry() + : this(string.Empty, string.Empty, false, []) + { + } + + [JsonPropertyName(nameof(Name))] + public string Name { get; set; } = name; + + [JsonPropertyName(nameof(Description))] + public string? Description { get; set; } = description; + + [JsonPropertyName(nameof(Recommended))] + public bool Recommended { get; set; } = recommended; + + [JsonPropertyName(nameof(Shortcut))] + public ShortcutDescription[] Shortcut { get; set; } = shortcutDescriptions; + } +} diff --git a/src/modules/ShortcutGuide/ShortcutGuide.Ui/Models/ShortcutFile.cs b/src/modules/ShortcutGuide/ShortcutGuide.Ui/Models/ShortcutFile.cs new file mode 100644 index 000000000000..46c114d6453c --- /dev/null +++ b/src/modules/ShortcutGuide/ShortcutGuide.Ui/Models/ShortcutFile.cs @@ -0,0 +1,19 @@ +// Copyright (c) Microsoft Corporation +// The Microsoft Corporation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +namespace ShortcutGuide.Models +{ + public struct ShortcutFile + { + public string PackageName { get; set; } + + public ShortcutCategory[] Shortcuts { get; set; } + + public string WindowFilter { get; set; } + + public bool BackgroundProcess { get; set; } + + public string Name { get; set; } + } +} diff --git a/src/modules/ShortcutGuide/ShortcutGuide.Ui/Models/ShortcutPageNavParam.cs b/src/modules/ShortcutGuide/ShortcutGuide.Ui/Models/ShortcutPageNavParam.cs new file mode 100644 index 000000000000..cb68fa46e928 --- /dev/null +++ b/src/modules/ShortcutGuide/ShortcutGuide.Ui/Models/ShortcutPageNavParam.cs @@ -0,0 +1,21 @@ +// Copyright (c) Microsoft Corporation +// The Microsoft Corporation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace ShortcutGuide.Models +{ + internal sealed class ShortcutPageNavParam + { + public string AppName { get; set; } = string.Empty; + + public ShortcutFile ShortcutFile { get; set; } + + public int PageIndex { get; set; } + } +} diff --git a/src/modules/ShortcutGuide/ShortcutGuide.Ui/NativeMethods.cs b/src/modules/ShortcutGuide/ShortcutGuide.Ui/NativeMethods.cs new file mode 100644 index 000000000000..12f4cca64cb2 --- /dev/null +++ b/src/modules/ShortcutGuide/ShortcutGuide.Ui/NativeMethods.cs @@ -0,0 +1,110 @@ +// Copyright (c) Microsoft Corporation +// The Microsoft Corporation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System; +using System.Runtime.InteropServices; +using Windows.Graphics; + +namespace ShortcutGuide; + +internal static partial class NativeMethods +{ + internal const int GWL_STYLE = -16; + internal const int WS_CAPTION = 0x00C00000; + + [LibraryImport("user32.dll")] + [return: MarshalAs(UnmanagedType.Bool)] + internal static partial bool SetWindowPos(IntPtr hWnd, IntPtr hWndInsertAfter, int x, int y, int cx, int cy, uint uFlags); + + [LibraryImport("user32.dll", SetLastError = true)] + internal static partial int GetWindowLongW(IntPtr hWnd, int nIndex); + + [LibraryImport("user32.dll")] + internal static partial int SetWindowLongW(IntPtr hWnd, int nIndex, int dwNewLong); + + [LibraryImport("user32.dll", StringMarshalling = StringMarshalling.Utf16)] + internal static partial IntPtr FindWindowA(in string lpClassName, in string? lpWindowName); + + [LibraryImport("User32.dll")] + internal static partial IntPtr MonitorFromWindow(IntPtr hwnd, int dwFlags); + + [LibraryImport("Shcore.dll")] + internal static partial long GetDpiForMonitor(IntPtr hmonitor, int dpiType, ref int dpiX, ref int dpiY); + + [LibraryImport("user32.dll")] + [return: MarshalAs(UnmanagedType.Bool)] + internal static partial bool GetCursorPos(out POINT lpPoint); + + [LibraryImport("user32.dll")] + internal static partial IntPtr GetForegroundWindow(); + + [LibraryImport("user32.dll", SetLastError = true)] + internal static partial uint GetWindowThreadProcessId(IntPtr hWnd, out uint processId); + + [LibraryImport("user32.dll")] + internal static partial short GetAsyncKeyState(int vKey); + + [DllImport("../PowerToys.Interop.dll", EntryPoint = "get_buttons")] + internal static extern IntPtr GetTasklistButtons(IntPtr monitor, out int size); + + [LibraryImport("../PowerToys.Interop.dll", EntryPoint = "IsCurrentWindowExcludedFromShortcutGuide")] + [return: MarshalAs(UnmanagedType.Bool)] + internal static partial bool IsCurrentWindowExcludedFromShortcutGuide(); + + [LibraryImport("User32.dll")] + [return: MarshalAs(UnmanagedType.Bool)] + internal static partial bool EnumDisplayMonitors(IntPtr hdc, IntPtr lprcClip, MonitorEnumDelegate lpfnEnum, IntPtr dwData); + + internal delegate bool MonitorEnumDelegate(IntPtr hMonitor, IntPtr hdcMonitor, ref RECT lprcMonitor, IntPtr dwData); + + internal struct LPARAM(IntPtr value) + { + internal IntPtr Value = value; + + public static implicit operator IntPtr(LPARAM lParam) + { + return lParam.Value; + } + + public static implicit operator LPARAM(IntPtr value) + { + return new LPARAM(value); + } + + public static implicit operator LPARAM(int value) + { + return new LPARAM(new IntPtr(value)); + } + + public static implicit operator int(LPARAM lParam) + { + return lParam.Value.ToInt32(); + } + } + + [StructLayout(LayoutKind.Sequential)] + internal struct RECT + { + public int Left; + public int Top; + public int Right; + public int Bottom; + } + + internal struct POINT + { + internal int X; + internal int Y; + + public static implicit operator PointInt32(POINT point) + { + return new PointInt32(point.X, point.Y); + } + } + + public enum MonitorFromWindowDwFlags + { + MONITOR_DEFAULTTONEAREST = 2, + } +} diff --git a/src/modules/ShortcutGuide/ShortcutGuide.Ui/Program.cs b/src/modules/ShortcutGuide/ShortcutGuide.Ui/Program.cs new file mode 100644 index 000000000000..61a61883b9d2 --- /dev/null +++ b/src/modules/ShortcutGuide/ShortcutGuide.Ui/Program.cs @@ -0,0 +1,84 @@ +// Copyright (c) Microsoft Corporation +// The Microsoft Corporation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System; +using System.Diagnostics; +using System.IO; +using System.Threading; +using System.Windows; +using ManagedCommon; +using Microsoft.UI.Dispatching; +using Microsoft.Windows.AppLifecycle; +using ShortcutGuide.Helpers; +using Application = Microsoft.UI.Xaml.Application; + +namespace ShortcutGuide +{ + public sealed class Program + { + private static readonly string[] InbuiltManifestFiles = [ + "+WindowsNT.Shell.en-US.yml", + "+WindowsNT.WindowsExplorer.en-US.yml", + "+WindowsNT.Notepad.en-US.yml", + "Microsoft.PowerToys.en-US.yml", + ]; + + [STAThread] + public static void Main() + { + if (PowerToys.GPOWrapper.GPOWrapper.GetConfiguredShortcutGuideEnabledValue() == PowerToys.GPOWrapper.GpoRuleConfigured.Disabled) + { + Logger.LogWarning("Tried to start with a GPO policy setting the utility to always be disabled. Please contact your systems administrator."); + return; + } + + Directory.CreateDirectory(ManifestInterpreter.PathOfManifestFiles); + + if (NativeMethods.IsCurrentWindowExcludedFromShortcutGuide()) + { + return; + } + + // Todo: Only copy files after an update. + // Todo: Handle error + foreach (var file in InbuiltManifestFiles) + { + File.Copy(Path.GetDirectoryName(Environment.ProcessPath) + "\\Assets\\ShortcutGuide\\" + file, ManifestInterpreter.PathOfManifestFiles + "\\" + file, true); + } + + Process indexGeneration = Process.Start(Path.GetDirectoryName(Environment.ProcessPath) + "\\PowerToys.ShortcutGuide.IndexYmlGenerator.exe"); + indexGeneration.WaitForExit(); + if (indexGeneration.ExitCode != 0) + { + Logger.LogError("Index generation failed with exit code: " + indexGeneration.ExitCode); + MessageBox.Show($"Shortcut Guide encountered an error while generating the index file. There is likely a corrupt shortcuts file in \"{ManifestInterpreter.PathOfManifestFiles}\". Try deleting this directory.", "Error displaying shortcuts", MessageBoxButton.OK, MessageBoxImage.Error); + return; + } + + PowerToysShortcutsPopulator.Populate(); + + Logger.InitializeLogger("\\ShortcutGuide\\Logs"); + WinRT.ComWrappersSupport.InitializeComWrappers(); + + var instanceKey = AppInstance.FindOrRegisterForKey("PowerToys_ShortcutGuide_Instance"); + + if (instanceKey.IsCurrent) + { + Application.Start((p) => + { + var context = new DispatcherQueueSynchronizationContext(DispatcherQueue.GetForCurrentThread()); + SynchronizationContext.SetSynchronizationContext(context); + _ = new App(); + }); + } + else + { + Logger.LogWarning("Another instance of ShortcutGuide is running. Exiting ShortcutGuide"); + } + + // Something prevents the process from exiting, so we need to kill it manually. + Process.GetCurrentProcess().Kill(); + } + } +} diff --git a/src/modules/ShortcutGuide/ShortcutGuide.Ui/ShortcutGuide.Ui.csproj b/src/modules/ShortcutGuide/ShortcutGuide.Ui/ShortcutGuide.Ui.csproj new file mode 100644 index 000000000000..70abd619463b --- /dev/null +++ b/src/modules/ShortcutGuide/ShortcutGuide.Ui/ShortcutGuide.Ui.csproj @@ -0,0 +1,145 @@ + + + + + + + WinExe + ShortcutGuide + app.manifest + true + true + None + enable + false + false + true + ..\..\..\..\$(Platform)\$(Configuration)\WinUI3Apps + PowerToys.ShortcutGuide + DISABLE_XAML_GENERATED_MAIN,TRACE + Assets\ShortcutGuide\ShortcutGuide.ico + + PowerToys.ShortcutGuide.pri + + + + + + + + + + + + + + + + + + + PreserveNewest + + + + + + + + + + + + + + + + + + + + + + + + tlbimp + 0 + 1 + 944de083-8fb8-45cf-bcb7-c477acb2f897 + 0 + false + true + + + + + + PowerToys.GPOWrapper + $(OutDir) + false + + + + + + + + + + + + + + + + + + + + + + + + + + + + MSBuild:Compile + + + + + MSBuild:Compile + + + + + MSBuild:Compile + + + + + MSBuild:Compile + + + + + MSBuild:Compile + + + + + + + + true + + diff --git a/src/modules/ShortcutGuide/ShortcutGuide.Ui/ShortcutGuideXAML/App.xaml b/src/modules/ShortcutGuide/ShortcutGuide.Ui/ShortcutGuideXAML/App.xaml new file mode 100644 index 000000000000..f21fcf9ad22d --- /dev/null +++ b/src/modules/ShortcutGuide/ShortcutGuide.Ui/ShortcutGuideXAML/App.xaml @@ -0,0 +1,33 @@ + + + + + + + + + + + + + + + + + + + + diff --git a/src/modules/ShortcutGuide/ShortcutGuide.Ui/ShortcutGuideXAML/App.xaml.cs b/src/modules/ShortcutGuide/ShortcutGuide.Ui/ShortcutGuideXAML/App.xaml.cs new file mode 100644 index 000000000000..ee1124e0538c --- /dev/null +++ b/src/modules/ShortcutGuide/ShortcutGuide.Ui/ShortcutGuideXAML/App.xaml.cs @@ -0,0 +1,62 @@ +// Copyright (c) Microsoft Corporation +// The Microsoft Corporation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System.Collections.Generic; +using System.IO; +using System.Text.Json; +using Microsoft.PowerToys.Settings.UI.Library; +using Microsoft.UI.Xaml; +using ShortcutGuide.Models; +using ShortcutGuide.ShortcutGuideXAML; + +namespace ShortcutGuide +{ + public partial class App + { + internal static Dictionary> PinnedShortcuts { get; private set; } = null!; + + internal static ShortcutGuideSettings ShortcutGuideSettings { get; private set; } = null!; + + internal static ShortcutGuideProperties ShortcutGuideProperties { get; private set; } = null!; + + internal static MainWindow MainWindow { get; private set; } = null!; + + internal static TaskbarWindow TaskBarWindow { get; private set; } = null!; + + public App() + { + this.InitializeComponent(); + } + + protected override void OnLaunched(LaunchActivatedEventArgs args) + { + this.LoadData(); + MainWindow = new MainWindow(); + TaskBarWindow = new TaskbarWindow(); + MainWindow.Activate(); + MainWindow.Closed += (_, _) => + { + Current.Exit(); + }; + } + + private void LoadData() + { + SettingsUtils settingsUtils = SettingsUtils.Default; + + if (settingsUtils.SettingsExists(ShortcutGuideSettings.ModuleName, "Pinned.json")) + { + string pinnedPath = settingsUtils.GetSettingsFilePath(ShortcutGuideSettings.ModuleName, "Pinned.json"); + PinnedShortcuts = JsonSerializer.Deserialize>>(File.ReadAllText(pinnedPath))!; + } + + ShortcutGuideSettings = SettingsRepository.GetInstance(settingsUtils).SettingsConfig; + ShortcutGuideProperties = ShortcutGuideSettings.Properties; + +#pragma warning disable CA1869 // Cache and reuse 'JsonSerializerOptions' instances + settingsUtils.SaveSettings(JsonSerializer.Serialize(App.ShortcutGuideSettings, new JsonSerializerOptions { WriteIndented = true }), "Shortcut Guide"); +#pragma warning restore CA1869 // Cache and reuse 'JsonSerializerOptions' instances + } + } +} diff --git a/src/modules/ShortcutGuide/ShortcutGuide.Ui/ShortcutGuideXAML/Controls/KeyCharPresenter.xaml b/src/modules/ShortcutGuide/ShortcutGuide.Ui/ShortcutGuideXAML/Controls/KeyCharPresenter.xaml new file mode 100644 index 000000000000..bde2ebd55fe6 --- /dev/null +++ b/src/modules/ShortcutGuide/ShortcutGuide.Ui/ShortcutGuideXAML/Controls/KeyCharPresenter.xaml @@ -0,0 +1,131 @@ + + + + + + + + + + diff --git a/src/modules/ShortcutGuide/ShortcutGuide.Ui/ShortcutGuideXAML/Controls/KeyCharPresenter.xaml.cs b/src/modules/ShortcutGuide/ShortcutGuide.Ui/ShortcutGuideXAML/Controls/KeyCharPresenter.xaml.cs new file mode 100644 index 000000000000..f6f2be3b6b67 --- /dev/null +++ b/src/modules/ShortcutGuide/ShortcutGuide.Ui/ShortcutGuideXAML/Controls/KeyCharPresenter.xaml.cs @@ -0,0 +1,34 @@ +// Copyright (c) Microsoft Corporation +// The Microsoft Corporation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System; +using System.Collections.Generic; +using System.Linq; +using System.Runtime.InteropServices.WindowsRuntime; +using System.Windows.Markup; +using Microsoft.UI.Xaml; +using Microsoft.UI.Xaml.Controls; +using Microsoft.UI.Xaml.Data; +using Microsoft.UI.Xaml.Documents; +using Microsoft.UI.Xaml.Input; +using Microsoft.UI.Xaml.Media; +using ShortcutGuide.Helpers; + +namespace ShortcutGuide.Controls; + +public sealed partial class KeyCharPresenter : Control +{ + public KeyCharPresenter() + { + DefaultStyleKey = typeof(KeyCharPresenter); + } + + public object Content + { + get => (object)GetValue(ContentProperty); + set => SetValue(ContentProperty, value); + } + + public static readonly DependencyProperty ContentProperty = DependencyProperty.Register(nameof(Content), typeof(object), typeof(KeyCharPresenter), new PropertyMetadata(default(string))); +} diff --git a/src/modules/ShortcutGuide/ShortcutGuide.Ui/ShortcutGuideXAML/Controls/KeyVisual.xaml b/src/modules/ShortcutGuide/ShortcutGuide.Ui/ShortcutGuideXAML/Controls/KeyVisual.xaml new file mode 100644 index 000000000000..05ba7e1c6164 --- /dev/null +++ b/src/modules/ShortcutGuide/ShortcutGuide.Ui/ShortcutGuideXAML/Controls/KeyVisual.xaml @@ -0,0 +1,191 @@ + + + + + + + + diff --git a/src/modules/ShortcutGuide/ShortcutGuide.Ui/ShortcutGuideXAML/Controls/KeyVisual.xaml.cs b/src/modules/ShortcutGuide/ShortcutGuide.Ui/ShortcutGuideXAML/Controls/KeyVisual.xaml.cs new file mode 100644 index 000000000000..130304932612 --- /dev/null +++ b/src/modules/ShortcutGuide/ShortcutGuide.Ui/ShortcutGuideXAML/Controls/KeyVisual.xaml.cs @@ -0,0 +1,194 @@ +// Copyright (c) Microsoft Corporation +// The Microsoft Corporation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using Microsoft.UI.Xaml; +using Microsoft.UI.Xaml.Controls; +using Windows.System; + +namespace ShortcutGuide.Controls +{ + [TemplatePart(Name = KeyPresenter, Type = typeof(KeyCharPresenter))] + [TemplateVisualState(Name = NormalState, GroupName = "CommonStates")] + [TemplateVisualState(Name = DisabledState, GroupName = "CommonStates")] + [TemplateVisualState(Name = InvalidState, GroupName = "CommonStates")] + public sealed partial class KeyVisual : Control + { + private const string KeyPresenter = "KeyPresenter"; + private const string NormalState = "Normal"; + private const string DisabledState = "Disabled"; + private const string InvalidState = "Invalid"; + private KeyCharPresenter _keyPresenter = null!; + + public object Content + { + get => (object)GetValue(ContentProperty); + set => SetValue(ContentProperty, value); + } + + public static readonly DependencyProperty ContentProperty = DependencyProperty.Register(nameof(Content), typeof(object), typeof(KeyVisual), new PropertyMetadata(default(string), OnContentChanged)); + + public bool IsInvalid + { + get => (bool)GetValue(IsInvalidProperty); + set => SetValue(IsInvalidProperty, value); + } + + public static readonly DependencyProperty IsInvalidProperty = DependencyProperty.Register(nameof(IsInvalid), typeof(bool), typeof(KeyVisual), new PropertyMetadata(false, OnIsInvalidChanged)); + + public bool RenderKeyAsGlyph + { + get => (bool)GetValue(RenderKeyAsGlyphProperty); + set => SetValue(RenderKeyAsGlyphProperty, value); + } + + public static readonly DependencyProperty RenderKeyAsGlyphProperty = DependencyProperty.Register(nameof(RenderKeyAsGlyph), typeof(bool), typeof(KeyVisual), new PropertyMetadata(false, OnContentChanged)); + + public KeyVisual() + { + this.DefaultStyleKey = typeof(KeyVisual); + } + + protected override void OnApplyTemplate() + { + IsEnabledChanged -= KeyVisual_IsEnabledChanged; + this._keyPresenter = (KeyCharPresenter)this.GetTemplateChild(KeyPresenter); + this.Update(); + this.SetVisualStates(); + IsEnabledChanged += KeyVisual_IsEnabledChanged; + base.OnApplyTemplate(); + } + + private static void OnContentChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) + { + ((KeyVisual)d).SetVisualStates(); + } + + private static void OnIsInvalidChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) + { + ((KeyVisual)d).SetVisualStates(); + } + + private void SetVisualStates() + { + if (this != null) + { + if (this.IsInvalid) + { + VisualStateManager.GoToState(this, InvalidState, true); + } + else if (!IsEnabled) + { + VisualStateManager.GoToState(this, DisabledState, true); + } + else + { + VisualStateManager.GoToState(this, NormalState, true); + } + } + } + + private void Update() + { + if (this.Content == null) + { + Visibility = Visibility.Collapsed; + return; + } + + if (this.Content is string key) + { + SetGlyphOrText(key switch + { + "" => "Num", + "" => "\uE0E2", + "" => "\uE0E3", + "" => "\uE0E4", + "" => "\uE0E5", + "" => "\uE0E4\uE0E5", + "" => "\uE0E2\uE0E3", + "" => "\uE0E2\uE0E3\uE0E4\uE0E5", + "" => "\uE751", + "" => "\uE750", + "" => "Esc", + string s when s.StartsWith('<') => s.Trim('<', '>'), + _ => key, + }); + + this._keyPresenter.Style = key switch + { + "" => (Style)Application.Current.Resources["CopilotKeyCharPresenterStyle"], + "" => (Style)Application.Current.Resources["OfficeKeyCharPresenterStyle"], + "" => (Style)Application.Current.Resources["UnderlinedLetterKeyCharPresenterStyle"], + _ => this._keyPresenter.Style, + }; + + return; + } + + if (this.Content is int keyCode) + { + VirtualKey virtualKey = (VirtualKey)keyCode; + switch (virtualKey) + { + case VirtualKey.Enter: + this.SetGlyphOrText("\uE751"); + break; + + case VirtualKey.Back: + this.SetGlyphOrText("\uE750"); + break; + + case VirtualKey.Shift: + case (VirtualKey)160: // Left Shift + case (VirtualKey)161: // Right Shift + this.SetGlyphOrText("\uE752"); + break; + + case VirtualKey.Up: + this.SetGlyphOrText("\uE0E4"); + break; + + case VirtualKey.Down: + this.SetGlyphOrText("\uE0E5"); + break; + + case VirtualKey.Left: + this.SetGlyphOrText("\uE0E2"); + break; + + case VirtualKey.Right: + this.SetGlyphOrText("\uE0E3"); + break; + + case VirtualKey.LeftWindows: + case VirtualKey.RightWindows: + this._keyPresenter.Style = (Style)Application.Current.Resources["WindowsKeyCharPresenterStyle"]; + break; + default: // For all other keys, we will use the key name. + SetGlyphOrText(virtualKey.ToString()); + break; + } + + return; + } + + Visibility = Visibility.Collapsed; + } + + private void SetGlyphOrText(string glyphOrText) + { + this.RenderKeyAsGlyph = ((glyphOrText[0] >> 12) & 0xF) is 0xE or 0xF; + this._keyPresenter.Content = glyphOrText; + + this._keyPresenter.Style = this.RenderKeyAsGlyph + ? (Style)Application.Current.Resources["GlyphKeyCharPresenterStyle"] + : (Style)Application.Current.Resources["DefaultKeyCharPresenterStyle"]; + } + + private void KeyVisual_IsEnabledChanged(object sender, DependencyPropertyChangedEventArgs e) + { + this.SetVisualStates(); + } + } +} diff --git a/src/modules/ShortcutGuide/ShortcutGuide.Ui/ShortcutGuideXAML/Controls/TaskbarIndicator.xaml b/src/modules/ShortcutGuide/ShortcutGuide.Ui/ShortcutGuideXAML/Controls/TaskbarIndicator.xaml new file mode 100644 index 000000000000..87c95c946209 --- /dev/null +++ b/src/modules/ShortcutGuide/ShortcutGuide.Ui/ShortcutGuideXAML/Controls/TaskbarIndicator.xaml @@ -0,0 +1,30 @@ + + + + + + + + + diff --git a/src/modules/ShortcutGuide/ShortcutGuide.Ui/ShortcutGuideXAML/Controls/TaskbarIndicator.xaml.cs b/src/modules/ShortcutGuide/ShortcutGuide.Ui/ShortcutGuideXAML/Controls/TaskbarIndicator.xaml.cs new file mode 100644 index 000000000000..3325b18d302c --- /dev/null +++ b/src/modules/ShortcutGuide/ShortcutGuide.Ui/ShortcutGuideXAML/Controls/TaskbarIndicator.xaml.cs @@ -0,0 +1,25 @@ +// Copyright (c) Microsoft Corporation +// The Microsoft Corporation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System.Globalization; +using Microsoft.UI.Xaml; +using Microsoft.UI.Xaml.Controls; + +namespace ShortcutGuide.Controls; + +public sealed partial class TaskbarIndicator : UserControl +{ + public string Label + { + get => (string)GetValue(LabelProperty); + set => SetValue(LabelProperty, value); + } + + public static readonly DependencyProperty LabelProperty = DependencyProperty.Register(nameof(Label), typeof(string), typeof(TaskbarIndicator), new PropertyMetadata(default(string))); + + public TaskbarIndicator() + { + this.InitializeComponent(); + } +} diff --git a/src/modules/ShortcutGuide/ShortcutGuide.Ui/ShortcutGuideXAML/MainWindow.xaml b/src/modules/ShortcutGuide/ShortcutGuide.Ui/ShortcutGuideXAML/MainWindow.xaml new file mode 100644 index 000000000000..06c782018f1c --- /dev/null +++ b/src/modules/ShortcutGuide/ShortcutGuide.Ui/ShortcutGuideXAML/MainWindow.xaml @@ -0,0 +1,100 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/modules/ShortcutGuide/ShortcutGuide.Ui/ShortcutGuideXAML/MainWindow.xaml.cs b/src/modules/ShortcutGuide/ShortcutGuide.Ui/ShortcutGuideXAML/MainWindow.xaml.cs new file mode 100644 index 000000000000..3dcc13f23495 --- /dev/null +++ b/src/modules/ShortcutGuide/ShortcutGuide.Ui/ShortcutGuideXAML/MainWindow.xaml.cs @@ -0,0 +1,307 @@ +// Copyright (c) Microsoft Corporation +// The Microsoft Corporation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System; +using System.IO; +using System.Linq; +using Common.UI; +using ManagedCommon; +using Microsoft.UI.Windowing; +using Microsoft.UI.Xaml; +using Microsoft.UI.Xaml.Controls; +using Microsoft.UI.Xaml.Input; +using ShortcutGuide.Helpers; +using ShortcutGuide.Models; +using ShortcutGuide.Pages; +using Windows.Foundation; +using Windows.Graphics; +using Windows.System; +using Windows.UI.WindowManagement; +using WinRT.Interop; +using WinUIEx; +using WinUIEx.Messaging; + +namespace ShortcutGuide +{ + public sealed partial class MainWindow : WindowEx + { + private readonly string[] _currentApplicationIds; + private ShortcutFile? _shortcutFile; + private string _selectedAppName = null!; + + private bool _setPosition; + + public MainWindow() + { + this._currentApplicationIds = ManifestInterpreter.GetAllCurrentApplicationIds(); + + this.InitializeComponent(); + + Title = ResourceLoaderInstance.ResourceLoader.GetString("Title")!; + ExtendsContentIntoTitleBar = true; + +#if !DEBUG + this.SetIsAlwaysOnTop(true); + this.SetIsShownInSwitchers(false); +#endif + WindowMessageMonitor msgMonitor = new(this); + msgMonitor.WindowMessageReceived += (_, e) => + { + const int WM_NCLBUTTONDBLCLK = 0x00A3; + if (e.Message.MessageId == WM_NCLBUTTONDBLCLK) + { + // Disable double click on title bar to maximize window + e.Result = 0; + e.Handled = true; + } + }; + + Activated += Window_Activated; + + Content.KeyUp += (_, e) => + { + if (e.Key == VirtualKey.Escape) + { + Close(); + } + }; + + switch (App.ShortcutGuideProperties.Theme.Value) + { + case "dark": + ((FrameworkElement)Content).RequestedTheme = ElementTheme.Dark; + this.MainPage.RequestedTheme = ElementTheme.Dark; + break; + case "light": + ((FrameworkElement)Content).RequestedTheme = ElementTheme.Light; + this.MainPage.RequestedTheme = ElementTheme.Light; + break; + case "system": + // Ignore, as the theme will be set by the system. + break; + default: + Logger.LogError("Invalid theme value in settings: " + App.ShortcutGuideProperties.Theme.Value); + break; + } + } + + protected override void OnStateChanged(WindowState state) + { + if (state == WindowState.Maximized) + { + this.SetWindowPosition(); + } + } + + protected override void OnPositionChanged(PointInt32 position) + { + this.SetWindowPosition(); + } + + private void Window_Activated(object sender, WindowActivatedEventArgs e) + { + if (e.WindowActivationState == WindowActivationState.Deactivated && !this._taskBarWindowActivated) + { +#if !DEBUG + Close(); +#endif + } + + if (this._taskBarWindowActivated) + { + this._taskBarWindowActivated = false; + this.BringToFront(); + } + + // The code below sets the position of the window to the center of the monitor, but only if it hasn't been set before. + if (!this._setPosition) + { + Content.GettingFocus += (_, _) => + { + this.FakeSettingsButton.Height = 10; + this.FakeSettingsButton.Height = 0; + }; + + this.SetWindowPosition(); + this._setPosition = true; + + AppWindow.Changed += (_, a) => + { + if (!a.DidPresenterChange) + { + return; + } + + this.SetWindowPosition(); + }; + } + + this.SetNavItems(); + } + + private void SetNavItems() + { + // Populate the window selector with the current application IDs if it is empty. + // TO DO: Check if Settings button is considered an item too. + if (this.WindowSelector.MenuItems.Count == 0) + { + foreach (var item in this._currentApplicationIds) + { + if (item == ManifestInterpreter.GetIndexYamlFile().DefaultShellName) + { + this.WindowSelector.MenuItems.Add(new NavigationViewItem { Name = item, Content = "Windows", Icon = new FontIcon() { Glyph = "\xE770" } }); + } + else + { + try + { + this.WindowSelector.MenuItems.Add(new NavigationViewItem { Name = item, Content = ManifestInterpreter.GetShortcutsOfApplication(item).Name, Icon = new FontIcon { Glyph = "\uEB91" } }); + } + catch (IOException) + { + } + } + } + + this.WindowSelector.SelectedItem = this.WindowSelector.MenuItems[0]; + } + } + + private bool _hasMovedToRightMonitor; + + private void SetWindowPosition() + { + if (!this._hasMovedToRightMonitor) + { + NativeMethods.GetCursorPos(out NativeMethods.POINT lpPoint); + AppWindow.Move(new NativeMethods.POINT { Y = lpPoint.Y - ((int)Height / 2), X = lpPoint.X - ((int)Width / 2) }); + this._hasMovedToRightMonitor = true; + } + + var hwnd = WindowNative.GetWindowHandle(this); + float dpi = DpiHelper.GetDPIScaleForWindow(hwnd.ToInt32()); + Rect monitorRect = DisplayHelper.GetWorkAreaForDisplayWithWindow(hwnd); + if (App.TaskBarWindow.AppWindow.IsVisible && App.TaskBarWindow.AppWindow.Position.X < AppWindow.Position.X + Width) + { + MaxHeight = (monitorRect.Height / dpi) - App.TaskBarWindow.AppWindow.Size.Height; + MinHeight = MaxHeight; + Height = MaxHeight; + } + else + { + MaxHeight = monitorRect.Height / DpiHelper.GetDPIScaleForWindow(hwnd.ToInt32()); + MinHeight = MaxHeight; + Height = MaxHeight; + } + + this.MoveAndResize((int)monitorRect.X, (int)monitorRect.Y, Width, Height); + } + + /*private void SearchBox_TextChanged(AutoSuggestBox sender, AutoSuggestBoxTextChangedEventArgs args) + { + // TO DO: should the results of this be shown on a separate results page? Or as part of the suggested items of the search box? + // The current UX is a bit weird as search is about the content that is selected on the page, vs. global search (which a search box in the title bar communicates). + // Also, the results indicate that they can be clicked - but they cannot, so this needs more UX thinking on having the right model. + if (args.Reason == AutoSuggestionBoxTextChangeReason.UserInput && !string.IsNullOrWhiteSpace(SearchBox.Text)) + { + ObservableCollection searchResults = new ObservableCollection(); + + if (_shortcutFile is ShortcutFile file) + { + foreach (var shortcut in file.Shortcuts.SelectMany(list => list.Properties.Where(s => s.Name.Contains(SearchBox.Text, StringComparison.InvariantCultureIgnoreCase)))) + { + searchResults.Add(shortcut); + } + + SearchBox.ItemsSource = searchResults; + } + } + } + + private void SearchBox_KeyboardAcceleratorInvoked(KeyboardAccelerator sender, KeyboardAcceleratorInvokedEventArgs args) + { + SearchBox.Focus(FocusState.Programmatic); + }*/ + + private void WindowSelector_SelectionChanged(NavigationView sender, NavigationViewSelectionChangedEventArgs args) + { + if (args.SelectedItem is NavigationViewItem selectedItem) + { + this._selectedAppName = selectedItem.Name; + this._shortcutFile = ManifestInterpreter.GetShortcutsOfApplication(this._selectedAppName); + this.PopulateCategorySelector(); + } + } + + private void PopulateCategorySelector() + { + this.SubNav.MenuItems.Clear(); + this.SubNav.MenuItems.Add(new NavigationViewItem() + { + Content = ResourceLoaderInstance.ResourceLoader.GetString("Overview"), + Tag = -1, + }); + + int i = 0; + + if (this._shortcutFile is ShortcutFile file) + { + foreach (var category in file.Shortcuts) + { + switch (category.SectionName) + { + case { } name when name.StartsWith("", StringComparison.Ordinal): + break; + case { } name when name.StartsWith('<') && name.EndsWith('>'): + break; + default: + this.SubNav.MenuItems.Add(new NavigationViewItem() { Content = category.SectionName, Tag = i }); + break; + } + + i++; + } + + if (this.SubNav.MenuItems.Count > 0) + { + this.SubNav.SelectedItem = this.SubNav.MenuItems[0]; + } + } + } + + /// + /// Tracks whether the taskbar window was activated. So that the main window does not close. + /// + private bool _taskBarWindowActivated; + + private void SubNav_SelectionChanged(NavigationView sender, NavigationViewSelectionChangedEventArgs args) + { + if (args.SelectedItem is NavigationViewItem selectedItem && selectedItem.Tag is int param && this._shortcutFile is ShortcutFile file) + { + Type selectedPage = typeof(ShortcutsPage); + App.TaskBarWindow.Hide(); + if (param == -1) + { + selectedPage = typeof(OverviewPage); + + // We only show the taskbar button window when the overview page of Windows is selected. + if (this._shortcutFile is not null && this._shortcutFile.Value.Shortcuts.Any(c => c.SectionName.Contains(""))) + { + this._taskBarWindowActivated = true; + App.TaskBarWindow.Activate(); + } + } + + // Set window position so that the taskbar window does not potentially clip into the main window + this.SetWindowPosition(); + this.ContentFrame.Navigate(selectedPage, new ShortcutPageNavParam() { ShortcutFile = file, PageIndex = param, AppName = this._selectedAppName }); + } + } + + private void Settings_Tapped(object sender, TappedRoutedEventArgs e) + { + SettingsDeepLink.OpenSettings(SettingsDeepLink.SettingsWindow.ShortcutGuide); + } + } +} diff --git a/src/modules/ShortcutGuide/ShortcutGuide.Ui/ShortcutGuideXAML/Pages/OverviewPage.xaml b/src/modules/ShortcutGuide/ShortcutGuide.Ui/ShortcutGuideXAML/Pages/OverviewPage.xaml new file mode 100644 index 000000000000..8c82b98ef580 --- /dev/null +++ b/src/modules/ShortcutGuide/ShortcutGuide.Ui/ShortcutGuideXAML/Pages/OverviewPage.xaml @@ -0,0 +1,143 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/modules/ShortcutGuide/ShortcutGuide.Ui/ShortcutGuideXAML/Pages/OverviewPage.xaml.cs b/src/modules/ShortcutGuide/ShortcutGuide.Ui/ShortcutGuideXAML/Pages/OverviewPage.xaml.cs new file mode 100644 index 000000000000..66f59ae581aa --- /dev/null +++ b/src/modules/ShortcutGuide/ShortcutGuide.Ui/ShortcutGuideXAML/Pages/OverviewPage.xaml.cs @@ -0,0 +1,86 @@ +// Copyright (c) Microsoft Corporation +// The Microsoft Corporation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System; +using System.Collections.ObjectModel; +using System.ComponentModel; +using System.Linq; +using Microsoft.UI.Xaml; +using Microsoft.UI.Xaml.Controls; +using Microsoft.UI.Xaml.Navigation; +using ShortcutGuide.Helpers; +using ShortcutGuide.Models; + +namespace ShortcutGuide.Pages +{ + public sealed partial class OverviewPage : Page, INotifyPropertyChanged + { + private ObservableCollection? _recommendedShortcuts; + private ObservableCollection? _pinnedShortcuts; + private ObservableCollection? _taskbarShortcuts; + + private int PinnedShortcutsCount => this._pinnedShortcuts?.Count ?? 0; + + private string _appName = string.Empty; + private ShortcutFile _shortcutFile; + + public OverviewPage() + { + this.InitializeComponent(); + } + + private void OnPropertyChanged(string propertyName) + { + this.PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName)); + } + + public event PropertyChangedEventHandler? PropertyChanged; + + protected override void OnNavigatedTo(NavigationEventArgs e) + { + if (e.Parameter is ShortcutPageNavParam param) + { + this._appName = param.AppName; + this._shortcutFile = param.ShortcutFile; + this._recommendedShortcuts = [.. this._shortcutFile.Shortcuts.SelectMany(list => list.Properties.Where(s => s.Recommended))]; + if (App.PinnedShortcuts.TryGetValue(this._appName, out var shortcuts)) + { + this._pinnedShortcuts = [.. shortcuts]; + } + + if (this._appName == ManifestInterpreter.GetIndexYamlFile().DefaultShellName) + { + this.TaskbarShortcutsPanel.Visibility = Visibility.Visible; + this._taskbarShortcuts = + [ + .. this._shortcutFile.Shortcuts.First(x => x.SectionName.StartsWith("", StringComparison.InvariantCulture)).Properties, + ]; + } + } + } + + private void PinFlyout_Opening(object sender, object e) + { + if (sender is MenuFlyout fl && fl.Target is Grid g && g.Tag is ShortcutEntry dataObject && fl.Items[0] is MenuFlyoutItem pinItem) + { + bool isItemPinned = App.PinnedShortcuts[this._appName].Any(x => x.Equals(dataObject)); + pinItem.Text = isItemPinned ? ResourceLoaderInstance.ResourceLoader.GetString("UnpinShortcut") : ResourceLoaderInstance.ResourceLoader.GetString("PinShortcut"); + pinItem.Icon = new SymbolIcon(isItemPinned ? Symbol.UnPin : Symbol.Pin); + } + } + + private void Pin_Click(object sender, RoutedEventArgs e) + { + if (sender is MenuFlyoutItem { CommandParameter: ShortcutEntry shortcutEntry }) + { + PinnedShortcutsHelper.UpdatePinnedShortcuts(this._appName, shortcutEntry); + + // Update ListView to reflect changes + this._pinnedShortcuts = [.. App.PinnedShortcuts[this._appName]]; + this.PinnedShortcutsListView.ItemsSource = this._pinnedShortcuts; + this.OnPropertyChanged(nameof(this.PinnedShortcutsCount)); + } + } + } +} diff --git a/src/modules/ShortcutGuide/ShortcutGuide.Ui/ShortcutGuideXAML/Pages/ShortcutsPage.xaml b/src/modules/ShortcutGuide/ShortcutGuide.Ui/ShortcutGuideXAML/Pages/ShortcutsPage.xaml new file mode 100644 index 000000000000..b6b99cff85cc --- /dev/null +++ b/src/modules/ShortcutGuide/ShortcutGuide.Ui/ShortcutGuideXAML/Pages/ShortcutsPage.xaml @@ -0,0 +1,101 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/modules/ShortcutGuide/ShortcutGuide.Ui/ShortcutGuideXAML/Pages/ShortcutsPage.xaml.cs b/src/modules/ShortcutGuide/ShortcutGuide.Ui/ShortcutGuideXAML/Pages/ShortcutsPage.xaml.cs new file mode 100644 index 000000000000..8bf471665dca --- /dev/null +++ b/src/modules/ShortcutGuide/ShortcutGuide.Ui/ShortcutGuideXAML/Pages/ShortcutsPage.xaml.cs @@ -0,0 +1,61 @@ +// Copyright (c) Microsoft Corporation +// The Microsoft Corporation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System.Collections.ObjectModel; +using System.Globalization; +using System.IO; +using System.Linq; +using System.Text.Json; +using Microsoft.PowerToys.Settings.UI.Library; +using Microsoft.UI.Xaml; +using Microsoft.UI.Xaml.Controls; +using Microsoft.UI.Xaml.Navigation; +using ShortcutGuide.Helpers; +using ShortcutGuide.Models; +using Windows.ApplicationModel.VoiceCommands; + +namespace ShortcutGuide.Pages +{ + public sealed partial class ShortcutsPage : Page + { + private ObservableCollection? _shortcuts; + private string _appName = string.Empty; + private ShortcutFile _shortcutFile; + private int _pageIndex; + + public ShortcutsPage() + { + this.InitializeComponent(); + } + + protected override void OnNavigatedTo(NavigationEventArgs e) + { + if (e.Parameter is ShortcutPageNavParam param) + { + this._appName = param.AppName; + this._shortcutFile = param.ShortcutFile; + this._pageIndex = param.PageIndex; + this._shortcuts = [.. this._shortcutFile.Shortcuts[this._pageIndex].Properties ?? Enumerable.Empty()]; + } + } + + private void PinFlyout_Opening(object sender, object e) + { + if (sender is MenuFlyout fl && fl.Target is Grid g && g.Tag is ShortcutEntry dataObject && fl.Items[0] is MenuFlyoutItem pinItem) + { + bool isItemPinned = App.PinnedShortcuts[this._appName].Any(x => x.Equals(dataObject)); + pinItem.Text = isItemPinned ? ResourceLoaderInstance.ResourceLoader.GetString("UnpinShortcut") : ResourceLoaderInstance.ResourceLoader.GetString("PinShortcut"); + pinItem.Icon = new SymbolIcon(isItemPinned ? Symbol.UnPin : Symbol.Pin); + } + } + + private void Pin_Click(object sender, RoutedEventArgs e) + { + if (sender is MenuFlyoutItem { CommandParameter: ShortcutEntry shortcutEntry }) + { + PinnedShortcutsHelper.UpdatePinnedShortcuts(this._appName, shortcutEntry); + } + } + } +} diff --git a/src/modules/ShortcutGuide/ShortcutGuide.Ui/ShortcutGuideXAML/Styles/CustomNavigationViewStyle.xaml b/src/modules/ShortcutGuide/ShortcutGuide.Ui/ShortcutGuideXAML/Styles/CustomNavigationViewStyle.xaml new file mode 100644 index 000000000000..e22448a45f1a --- /dev/null +++ b/src/modules/ShortcutGuide/ShortcutGuide.Ui/ShortcutGuideXAML/Styles/CustomNavigationViewStyle.xaml @@ -0,0 +1,1696 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 72 + 72 + 0 + 58 + 0 + + + + + + + + + + + + + + + + + + + + + --> + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/src/modules/ShortcutGuide/ShortcutGuide.Ui/ShortcutGuideXAML/TaskbarWindow.xaml b/src/modules/ShortcutGuide/ShortcutGuide.Ui/ShortcutGuideXAML/TaskbarWindow.xaml new file mode 100644 index 000000000000..0c8e9eabdc7a --- /dev/null +++ b/src/modules/ShortcutGuide/ShortcutGuide.Ui/ShortcutGuideXAML/TaskbarWindow.xaml @@ -0,0 +1,57 @@ + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/modules/ShortcutGuide/ShortcutGuide.Ui/ShortcutGuideXAML/TaskbarWindow.xaml.cs b/src/modules/ShortcutGuide/ShortcutGuide.Ui/ShortcutGuideXAML/TaskbarWindow.xaml.cs new file mode 100644 index 000000000000..135c0eda4ce5 --- /dev/null +++ b/src/modules/ShortcutGuide/ShortcutGuide.Ui/ShortcutGuideXAML/TaskbarWindow.xaml.cs @@ -0,0 +1,82 @@ +// Copyright (c) Microsoft Corporation +// The Microsoft Corporation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System.Globalization; +using Microsoft.UI; +using Microsoft.UI.Xaml.Controls; +using Microsoft.UI.Xaml.Media; +using Microsoft.UI.Xaml.Shapes; +using ShortcutGuide.Controls; +using ShortcutGuide.Helpers; +using Windows.Foundation; +using WinRT.Interop; +using WinUIEx; +using static ShortcutGuide.NativeMethods; + +namespace ShortcutGuide.ShortcutGuideXAML +{ + public sealed partial class TaskbarWindow : WindowEx + { + private float DPI => DpiHelper.GetDPIScaleForWindow(WindowNative.GetWindowHandle(this).ToInt32()); + + private Rect WorkArea => DisplayHelper.GetWorkAreaForDisplayWithWindow(WindowNative.GetWindowHandle(this)); + + public TaskbarWindow() + { + this.InitializeComponent(); + this.UpdateTasklistButtons(); + this.Activated += (_, _) => this.UpdateTasklistButtons(); + } + + public void UpdateTasklistButtons() + { + // This move ensures the window spawns on the same monitor as the main window + AppWindow.MoveInZOrderAtBottom(); + AppWindow.Move(App.MainWindow.AppWindow.Position); + TasklistButton[] buttons = []; + try + { + buttons = TasklistPositions.GetButtons(); + } + catch + { + } + + if (buttons.Length == 0) + { + AppWindow.Hide(); + return; + } + + double windowsLogoColumnWidth = this.WindowsLogoColumnWidth.Width.Value; + double windowHeight = 58; + double windowMargin = 8 * this.DPI; + double windowWidth = windowsLogoColumnWidth; + double xPosition = buttons[0].X - (windowsLogoColumnWidth * this.DPI); + double yPosition = this.WorkArea.Bottom - (windowHeight * this.DPI); + + this.KeyHolder.Children.Clear(); + + foreach (TasklistButton b in buttons) + { + TaskbarIndicator indicator = new() + { + Label = b.Keynum >= 10 ? "0" : b.Keynum.ToString(CultureInfo.InvariantCulture), + Height = b.Height / this.DPI, + Width = b.Width / this.DPI, + }; + + windowWidth += indicator.Width; + + this.KeyHolder.Children.Add(indicator); + + double indicatorPos = (b.X - xPosition) / this.DPI; + Canvas.SetLeft(indicator, indicatorPos - windowsLogoColumnWidth); + } + + this.MoveAndResize(xPosition - windowMargin, yPosition, windowWidth + (2 * windowMargin), windowHeight); + AppWindow.MoveInZOrderAtTop(); + } + } +} diff --git a/src/modules/ShortcutGuide/ShortcutGuide/Resources.resx b/src/modules/ShortcutGuide/ShortcutGuide.Ui/Strings/en-us/Resources.resw similarity index 61% rename from src/modules/ShortcutGuide/ShortcutGuide/Resources.resx rename to src/modules/ShortcutGuide/ShortcutGuide.Ui/Strings/en-us/Resources.resw index 34942e7a5295..266a4835143d 100644 --- a/src/modules/ShortcutGuide/ShortcutGuide/Resources.resx +++ b/src/modules/ShortcutGuide/ShortcutGuide.Ui/Strings/en-us/Resources.resw @@ -1,4 +1,4 @@ - + + true/PM + PerMonitorV2, PerMonitor + + + + + + + + + diff --git a/src/modules/ShortcutGuide/ShortcutGuide/Assets/ShortcutGuide/0.svg b/src/modules/ShortcutGuide/ShortcutGuide/Assets/ShortcutGuide/0.svg deleted file mode 100644 index b797f8b7ef73..000000000000 --- a/src/modules/ShortcutGuide/ShortcutGuide/Assets/ShortcutGuide/0.svg +++ /dev/null @@ -1,21 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/src/modules/ShortcutGuide/ShortcutGuide/Assets/ShortcutGuide/1.svg b/src/modules/ShortcutGuide/ShortcutGuide/Assets/ShortcutGuide/1.svg deleted file mode 100644 index 6e1e3d28f146..000000000000 --- a/src/modules/ShortcutGuide/ShortcutGuide/Assets/ShortcutGuide/1.svg +++ /dev/null @@ -1,21 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - diff --git a/src/modules/ShortcutGuide/ShortcutGuide/Assets/ShortcutGuide/2.svg b/src/modules/ShortcutGuide/ShortcutGuide/Assets/ShortcutGuide/2.svg deleted file mode 100644 index 5183242c1b2e..000000000000 --- a/src/modules/ShortcutGuide/ShortcutGuide/Assets/ShortcutGuide/2.svg +++ /dev/null @@ -1,21 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/src/modules/ShortcutGuide/ShortcutGuide/Assets/ShortcutGuide/3.svg b/src/modules/ShortcutGuide/ShortcutGuide/Assets/ShortcutGuide/3.svg deleted file mode 100644 index 63ad68f061df..000000000000 --- a/src/modules/ShortcutGuide/ShortcutGuide/Assets/ShortcutGuide/3.svg +++ /dev/null @@ -1,21 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/src/modules/ShortcutGuide/ShortcutGuide/Assets/ShortcutGuide/4.svg b/src/modules/ShortcutGuide/ShortcutGuide/Assets/ShortcutGuide/4.svg deleted file mode 100644 index 4f7e36f8bd7b..000000000000 --- a/src/modules/ShortcutGuide/ShortcutGuide/Assets/ShortcutGuide/4.svg +++ /dev/null @@ -1,21 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - diff --git a/src/modules/ShortcutGuide/ShortcutGuide/Assets/ShortcutGuide/5.svg b/src/modules/ShortcutGuide/ShortcutGuide/Assets/ShortcutGuide/5.svg deleted file mode 100644 index 0fd52f658589..000000000000 --- a/src/modules/ShortcutGuide/ShortcutGuide/Assets/ShortcutGuide/5.svg +++ /dev/null @@ -1,21 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/src/modules/ShortcutGuide/ShortcutGuide/Assets/ShortcutGuide/6.svg b/src/modules/ShortcutGuide/ShortcutGuide/Assets/ShortcutGuide/6.svg deleted file mode 100644 index 00303cdb3208..000000000000 --- a/src/modules/ShortcutGuide/ShortcutGuide/Assets/ShortcutGuide/6.svg +++ /dev/null @@ -1,21 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/src/modules/ShortcutGuide/ShortcutGuide/Assets/ShortcutGuide/7.svg b/src/modules/ShortcutGuide/ShortcutGuide/Assets/ShortcutGuide/7.svg deleted file mode 100644 index cce8aedbaa61..000000000000 --- a/src/modules/ShortcutGuide/ShortcutGuide/Assets/ShortcutGuide/7.svg +++ /dev/null @@ -1,21 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/src/modules/ShortcutGuide/ShortcutGuide/Assets/ShortcutGuide/8.svg b/src/modules/ShortcutGuide/ShortcutGuide/Assets/ShortcutGuide/8.svg deleted file mode 100644 index 57f4856b6b90..000000000000 --- a/src/modules/ShortcutGuide/ShortcutGuide/Assets/ShortcutGuide/8.svg +++ /dev/null @@ -1,21 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/src/modules/ShortcutGuide/ShortcutGuide/Assets/ShortcutGuide/9.svg b/src/modules/ShortcutGuide/ShortcutGuide/Assets/ShortcutGuide/9.svg deleted file mode 100644 index 2e0f33c493ff..000000000000 --- a/src/modules/ShortcutGuide/ShortcutGuide/Assets/ShortcutGuide/9.svg +++ /dev/null @@ -1,21 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/src/modules/ShortcutGuide/ShortcutGuide/Assets/ShortcutGuide/no_active_window.svg b/src/modules/ShortcutGuide/ShortcutGuide/Assets/ShortcutGuide/no_active_window.svg deleted file mode 100644 index 69d390bd081b..000000000000 --- a/src/modules/ShortcutGuide/ShortcutGuide/Assets/ShortcutGuide/no_active_window.svg +++ /dev/null @@ -1,6 +0,0 @@ - - - - - - \ No newline at end of file diff --git a/src/modules/ShortcutGuide/ShortcutGuide/Assets/ShortcutGuide/overlay.svg b/src/modules/ShortcutGuide/ShortcutGuide/Assets/ShortcutGuide/overlay.svg deleted file mode 100644 index fff83ab395ee..000000000000 --- a/src/modules/ShortcutGuide/ShortcutGuide/Assets/ShortcutGuide/overlay.svg +++ /dev/null @@ -1,205 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/src/modules/ShortcutGuide/ShortcutGuide/Assets/ShortcutGuide/overlay_portrait.svg b/src/modules/ShortcutGuide/ShortcutGuide/Assets/ShortcutGuide/overlay_portrait.svg deleted file mode 100644 index 82c03149733b..000000000000 --- a/src/modules/ShortcutGuide/ShortcutGuide/Assets/ShortcutGuide/overlay_portrait.svg +++ /dev/null @@ -1,208 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/src/modules/ShortcutGuide/ShortcutGuide/PropertySheet.props b/src/modules/ShortcutGuide/ShortcutGuide/PropertySheet.props deleted file mode 100644 index b0c622690fed..000000000000 --- a/src/modules/ShortcutGuide/ShortcutGuide/PropertySheet.props +++ /dev/null @@ -1,16 +0,0 @@ - - - - - - - - \ No newline at end of file diff --git a/src/modules/ShortcutGuide/ShortcutGuide/ShortcutGuide.base.rc b/src/modules/ShortcutGuide/ShortcutGuide/ShortcutGuide.base.rc deleted file mode 100644 index 1f88309dd2dc..000000000000 --- a/src/modules/ShortcutGuide/ShortcutGuide/ShortcutGuide.base.rc +++ /dev/null @@ -1,54 +0,0 @@ -#include -#include "Generated Files/resource.h" -#include "../../../../common/version/version.h" - -#define APSTUDIO_READONLY_SYMBOLS -#include "winres.h" -#undef APSTUDIO_READONLY_SYMBOLS - -///////////////////////////////////////////////////////////////////////////// -// -// Icon -// - -// Icon with lowest ID value placed first to ensure application icon -// remains consistent on all systems. -IDI_ICON1 ICON "Shortcut-Guide.ico" - - ///////////////////////////////////////////////////////////////////////////// -// -// Version -// - -VS_VERSION_INFO VERSIONINFO - FILEVERSION FILE_VERSION - PRODUCTVERSION PRODUCT_VERSION - FILEFLAGSMASK VS_FFI_FILEFLAGSMASK -#ifdef _DEBUG - FILEFLAGS VS_FF_DEBUG -#else - FILEFLAGS 0x0L -#endif - FILEOS VOS_NT_WINDOWS32 - FILETYPE VFT_DLL - FILESUBTYPE VFT2_UNKNOWN -BEGIN - BLOCK "StringFileInfo" - BEGIN - BLOCK "040904b0" - BEGIN - VALUE "CompanyName", COMPANY_NAME - VALUE "FileDescription", FILE_DESCRIPTION - VALUE "FileVersion", FILE_VERSION_STRING - VALUE "InternalName", INTERNAL_NAME - VALUE "LegalCopyright", COPYRIGHT_NOTE - VALUE "OriginalFilename", ORIGINAL_FILENAME - VALUE "ProductName", PRODUCT_NAME - VALUE "ProductVersion", PRODUCT_VERSION_STRING - END - END - BLOCK "VarFileInfo" - BEGIN - VALUE "Translation", 0x409, 1200 - END -END \ No newline at end of file diff --git a/src/modules/ShortcutGuide/ShortcutGuide/ShortcutGuide.exe.manifest b/src/modules/ShortcutGuide/ShortcutGuide/ShortcutGuide.exe.manifest deleted file mode 100644 index 4747d3bd23dd..000000000000 --- a/src/modules/ShortcutGuide/ShortcutGuide/ShortcutGuide.exe.manifest +++ /dev/null @@ -1,9 +0,0 @@ - - - - - true/PM - PerMonitorV2 - - - \ No newline at end of file diff --git a/src/modules/ShortcutGuide/ShortcutGuide/ShortcutGuide.vcxproj b/src/modules/ShortcutGuide/ShortcutGuide/ShortcutGuide.vcxproj deleted file mode 100644 index 374dc951ded5..000000000000 --- a/src/modules/ShortcutGuide/ShortcutGuide/ShortcutGuide.vcxproj +++ /dev/null @@ -1,193 +0,0 @@ - - - - - - - - - true - true - true - true - 15.0 - {2edb3eb4-fa92-4bff-b2d8-566584837231} - Win32Proj - ShortcutGuide - - - Application - - v141 - v142 - Unicode - - - true - true - - - false - true - false - - - - - - - - - - - - - - - PowerToys.$(MSBuildProjectName) - - - $(RepoRoot)$(Platform)\$(Configuration)\ - - - - ;$(RepoRoot)src\common\inc;$(RepoRoot)src\common\Telemetry;$(RepoRoot)src\;..\;%(AdditionalIncludeDirectories) - - - ole32.lib;Shell32.lib;OleAut32.lib;Dbghelp.lib;Dwmapi.lib;Dcomp.lib;Shlwapi.lib;%(AdditionalDependencies) - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - Create - - - - - - - - - - - - Document - $(OutDir)\Assets\ShortcutGuide - - - Document - $(OutDir)\Assets\ShortcutGuide - - - Document - $(OutDir)\Assets\ShortcutGuide - - - Document - $(OutDir)\Assets\ShortcutGuide - - - Document - $(OutDir)\Assets\ShortcutGuide - - - Document - $(OutDir)\Assets\ShortcutGuide - - - Document - $(OutDir)\Assets\ShortcutGuide - - - Document - $(OutDir)\Assets\ShortcutGuide - - - Document - $(OutDir)\Assets\ShortcutGuide - - - Document - $(OutDir)\Assets\ShortcutGuide - - - Document - $(OutDir)\Assets\ShortcutGuide - - - Document - $(OutDir)\Assets\ShortcutGuide - - - Document - $(OutDir)\Assets\ShortcutGuide - - - - - {caba8dfb-823b-4bf2-93ac-3f31984150d9} - - - {d9b8fc84-322a-4f9f-bbb9-20915c47ddfd} - - - {6955446d-23f7-4023-9bb3-8657f904af99} - - - {8f021b46-362b-485c-bfba-ccf83e820cbd} - - - {98537082-0fdb-40de-abd8-0dc5a4269bab} - - - - - - - - - - - - - - - - - - - - - - - This project references NuGet package(s) that are missing on this computer. Use NuGet Package Restore to download them. For more information, see http://go.microsoft.com/fwlink/?LinkID=322105. The missing file is {0}. - - - - - \ No newline at end of file diff --git a/src/modules/ShortcutGuide/ShortcutGuide/ShortcutGuide.vcxproj.filters b/src/modules/ShortcutGuide/ShortcutGuide/ShortcutGuide.vcxproj.filters deleted file mode 100644 index 006a6196ef35..000000000000 --- a/src/modules/ShortcutGuide/ShortcutGuide/ShortcutGuide.vcxproj.filters +++ /dev/null @@ -1,141 +0,0 @@ - - - - - {4FC737F1-C7A5-4376-A066-2A32D752A2FF} - cpp;c;cc;cxx;def;odl;idl;hpj;bat;asm;asmx - - - {93995380-89BD-4b04-88EB-625FBE52EBFB} - h;hh;hpp;hxx;hm;inl;inc;xsd - - - {67DA6AB6-F800-4c08-8B7A-83BB121AAD01} - rc;ico;cur;bmp;dlg;rc2;rct;bin;rgs;gif;jpg;jpeg;jpe;resx;svg;tiff;tif;png;wav;mfcribbon-ms - - - {cb917ac7-30da-494b-81f1-cbe4415e91f4} - - - - - Header Files - - - Header Files - - - Header Files - - - Header Files - - - Header Files - - - Header Files - - - Header Files - - - Header Files - - - Header Files - - - Header Files - - - Header Files - - - Header Files - - - Header Files - - - Resource Files - - - Generated Files - - - Header Files - - - - - Source Files - - - Source Files - - - Source Files - - - Source Files - - - Source Files - - - Source Files - - - Source Files - - - Source Files - - - Source Files - - - Source Files - - - Source Files - - - Source Files - - - Source Files - - - - - - Resource Files - - - - - - Resource Files - - - - - Resource Files - - - - - Generated Files - - - - - Resource Files - - - - - - \ No newline at end of file diff --git a/src/modules/ShortcutGuide/ShortcutGuide/ShortcutGuideConstants.h b/src/modules/ShortcutGuide/ShortcutGuide/ShortcutGuideConstants.h deleted file mode 100644 index 3d0e434abfb2..000000000000 --- a/src/modules/ShortcutGuide/ShortcutGuide/ShortcutGuideConstants.h +++ /dev/null @@ -1,8 +0,0 @@ -#pragma once -#include - -namespace ShortcutGuideConstants -{ - // Name of the powertoy module. - inline const std::wstring ModuleKey = L"Shortcut Guide"; -} \ No newline at end of file diff --git a/src/modules/ShortcutGuide/ShortcutGuide/ShortcutGuideSettings.h b/src/modules/ShortcutGuide/ShortcutGuide/ShortcutGuideSettings.h deleted file mode 100644 index a39f2f25114e..000000000000 --- a/src/modules/ShortcutGuide/ShortcutGuide/ShortcutGuideSettings.h +++ /dev/null @@ -1,13 +0,0 @@ -#pragma once -#include - -struct ShortcutGuideSettings -{ - std::wstring hotkey = L"shift+win+/"; - int overlayOpacity = 90; - std::wstring theme = L"system"; - std::wstring disabledApps = L""; - bool shouldReactToPressedWinKey = false; - int windowsKeyPressTimeForGlobalWindowsShortcuts = 900; - int windowsKeyPressTimeForTaskbarIconShortcuts = 900; -}; diff --git a/src/modules/ShortcutGuide/ShortcutGuide/animation.cpp b/src/modules/ShortcutGuide/ShortcutGuide/animation.cpp deleted file mode 100644 index 4bc039a3e279..000000000000 --- a/src/modules/ShortcutGuide/ShortcutGuide/animation.cpp +++ /dev/null @@ -1,51 +0,0 @@ -#include "pch.h" -#include "animation.h" - -Animation::Animation(double duration, double start, double stop) : - duration(duration), start_value(start), end_value(stop), start(std::chrono::high_resolution_clock::now()) {} - -void Animation::reset() -{ - start = std::chrono::high_resolution_clock::now(); -} -void Animation::reset(double animation_duration) -{ - duration = animation_duration; - reset(); -} -void Animation::reset(double animation_duration, double animation_start, double animation_stop) -{ - start_value = animation_start; - end_value = animation_stop; - reset(animation_duration); -} - -static double ease_out_expo(double t) -{ - return 1 - pow(2, -8 * t); -} - -double Animation::apply_animation_function(double t, AnimFunctions apply_function) -{ - switch (apply_function) - { - case EASE_OUT_EXPO: - return ease_out_expo(t); - case LINEAR: - default: - return t; - } -} - -double Animation::value(AnimFunctions apply_function) const -{ - auto anim_duration = std::chrono::high_resolution_clock::now() - start; - double t = std::chrono::duration(anim_duration).count() / duration; - if (t >= 1) - return end_value; - return start_value + (end_value - start_value) * apply_animation_function(t, apply_function); -} -bool Animation::done() const -{ - return std::chrono::high_resolution_clock::now() - start >= std::chrono::duration(duration); -} diff --git a/src/modules/ShortcutGuide/ShortcutGuide/animation.h b/src/modules/ShortcutGuide/ShortcutGuide/animation.h deleted file mode 100644 index 61ae59b661ad..000000000000 --- a/src/modules/ShortcutGuide/ShortcutGuide/animation.h +++ /dev/null @@ -1,34 +0,0 @@ -#pragma once -#include - -/* - Usage: - When creating animation constructor takes one parameter - how long - should the animation take in seconds. - - Call reset() when starting animation. - - When rendering, call value() to get value from 0 to 1 - depending on animation - progress. -*/ -class Animation -{ -public: - enum AnimFunctions - { - LINEAR = 0, - EASE_OUT_EXPO - }; - - Animation(double duration = 1, double start = 0, double stop = 1); - void reset(); - void reset(double animation_duration); - void reset(double animation_duration, double animation_start, double animation_stop); - double value(AnimFunctions apply_function) const; - bool done() const; - -private: - static double apply_animation_function(double t, AnimFunctions apply_function); - std::chrono::high_resolution_clock::time_point start; - double start_value, end_value, duration; -}; diff --git a/src/modules/ShortcutGuide/ShortcutGuide/d2d_svg.cpp b/src/modules/ShortcutGuide/ShortcutGuide/d2d_svg.cpp deleted file mode 100644 index 0f01aaf403d3..000000000000 --- a/src/modules/ShortcutGuide/ShortcutGuide/d2d_svg.cpp +++ /dev/null @@ -1,120 +0,0 @@ -#include "pch.h" -#include "d2d_svg.h" - -D2DSVG& D2DSVG::load(const std::wstring& filename, ID2D1DeviceContext5* d2d_dc) -{ - svg = nullptr; - winrt::com_ptr svg_stream; - auto h = SHCreateStreamOnFileEx(filename.c_str(), - STGM_READ, - FILE_ATTRIBUTE_NORMAL, - FALSE, - nullptr, - svg_stream.put()); - winrt::check_hresult(h); - - auto h1 = d2d_dc->CreateSvgDocument( - svg_stream.get(), - D2D1::SizeF(1, 1), - svg.put()); - - winrt::check_hresult(h1); - - winrt::com_ptr root; - svg->GetRoot(root.put()); - float tmp; - winrt::check_hresult(root->GetAttributeValue(L"width", &tmp)); - svg_width = static_cast(tmp); - winrt::check_hresult(root->GetAttributeValue(L"height", &tmp)); - svg_height = static_cast(tmp); - return *this; -} - -D2DSVG& D2DSVG::resize(int x, int y, int width, int height, float fill, float max_scale) -{ - // Center - transform = D2D1::Matrix3x2F::Identity(); - transform = transform * D2D1::Matrix3x2F::Translation((width - svg_width) / 2.0f, (height - svg_height) / 2.0f); - float h_scale = fill * height / svg_height; - float v_scale = fill * width / svg_width; - used_scale = std::min(h_scale, v_scale); - if (max_scale > 0) - { - used_scale = std::min(used_scale, max_scale); - } - transform = transform * D2D1::Matrix3x2F::Scale(used_scale, used_scale, D2D1::Point2F(width / 2.0f, height / 2.0f)); - transform = transform * D2D1::Matrix3x2F::Translation(static_cast(x), static_cast(y)); - return *this; -} - -D2DSVG& D2DSVG::recolor(uint32_t oldcolor, uint32_t newcolor) -{ - auto new_color = D2D1::ColorF(newcolor & 0xFFFFFF, 1); - auto old_color = D2D1::ColorF(oldcolor & 0xFFFFFF, 1); - std::function recurse = [&](ID2D1SvgElement* element) { - if (!element) - return; - if (element->IsAttributeSpecified(L"fill")) - { - D2D1_COLOR_F elem_fill; - winrt::com_ptr paint; - element->GetAttributeValue(L"fill", paint.put()); - paint->GetColor(&elem_fill); - if (elem_fill.r == old_color.r && elem_fill.g == old_color.g && elem_fill.b == old_color.b) - { - winrt::check_hresult(element->SetAttributeValue(L"fill", new_color)); - } - } - winrt::com_ptr sub; - element->GetFirstChild(sub.put()); - while (sub) - { - recurse(sub.get()); - winrt::com_ptr next; - element->GetNextChild(sub.get(), next.put()); - sub = next; - } - }; - winrt::com_ptr root; - svg->GetRoot(root.put()); - recurse(root.get()); - return *this; -} - -D2DSVG& D2DSVG::render(ID2D1DeviceContext5* d2d_dc) -{ - D2D1_MATRIX_3X2_F current; - d2d_dc->GetTransform(¤t); - d2d_dc->SetTransform(transform * current); - d2d_dc->DrawSvgDocument(svg.get()); - d2d_dc->SetTransform(current); - return *this; -} - -D2DSVG& D2DSVG::toggle_element(const wchar_t* id, bool visible) -{ - winrt::com_ptr element; - if (svg->FindElementById(id, element.put()) != S_OK) - return *this; - if (!element) - return *this; - element->SetAttributeValue(L"display", visible ? D2D1_SVG_DISPLAY::D2D1_SVG_DISPLAY_INLINE : D2D1_SVG_DISPLAY::D2D1_SVG_DISPLAY_NONE); - return *this; -} - -winrt::com_ptr D2DSVG::find_element(const std::wstring& id) -{ - winrt::com_ptr element; - winrt::check_hresult(svg->FindElementById(id.c_str(), element.put())); - return element; -} - -D2D1_RECT_F D2DSVG::rescale(D2D1_RECT_F rect) -{ - D2D1_RECT_F result; - auto src = reinterpret_cast(&rect); - auto dst = reinterpret_cast(&result); - dst[0] = src[0] * transform; - dst[1] = src[1] * transform; - return result; -} diff --git a/src/modules/ShortcutGuide/ShortcutGuide/d2d_svg.h b/src/modules/ShortcutGuide/ShortcutGuide/d2d_svg.h deleted file mode 100644 index 08869b2de193..000000000000 --- a/src/modules/ShortcutGuide/ShortcutGuide/d2d_svg.h +++ /dev/null @@ -1,26 +0,0 @@ -#pragma once -#include -#include -#include -#include - -class D2DSVG -{ -public: - D2DSVG& load(const std::wstring& filename, ID2D1DeviceContext5* d2d_dc); - D2DSVG& resize(int x, int y, int width, int height, float fill, float max_scale = -1.0f); - D2DSVG& render(ID2D1DeviceContext5* d2d_dc); - D2DSVG& recolor(uint32_t oldcolor, uint32_t newcolor); - float get_scale() const { return used_scale; } - int width() const { return svg_width; } - int height() const { return svg_height; } - D2DSVG& toggle_element(const wchar_t* id, bool visible); - winrt::com_ptr find_element(const std::wstring& id); - D2D1_RECT_F rescale(D2D1_RECT_F rect); - -protected: - float used_scale = 1.0f; - winrt::com_ptr svg; - int svg_width = -1, svg_height = -1; - D2D1::Matrix3x2F transform; -}; diff --git a/src/modules/ShortcutGuide/ShortcutGuide/d2d_text.cpp b/src/modules/ShortcutGuide/ShortcutGuide/d2d_text.cpp deleted file mode 100644 index 7f25c4e32c13..000000000000 --- a/src/modules/ShortcutGuide/ShortcutGuide/d2d_text.cpp +++ /dev/null @@ -1,54 +0,0 @@ -#include "pch.h" -#include "d2d_text.h" - -D2DText::D2DText(float text_size, float scale) -{ - winrt::check_hresult(DWriteCreateFactory(DWRITE_FACTORY_TYPE_SHARED, __uuidof(factory), reinterpret_cast(factory.put_void()))); - resize(text_size, scale); - winrt::check_hresult(format->SetTextAlignment(DWRITE_TEXT_ALIGNMENT_CENTER)); - winrt::check_hresult(format->SetParagraphAlignment(DWRITE_PARAGRAPH_ALIGNMENT_CENTER)); -} - -D2DText& D2DText::resize(float text_size, float scale) -{ - format = nullptr; - winrt::check_hresult(factory->CreateTextFormat(L"Segoe UI", - nullptr, - DWRITE_FONT_WEIGHT_NORMAL, - DWRITE_FONT_STYLE_NORMAL, - DWRITE_FONT_STRETCH_NORMAL, - text_size * scale, - L"en-us", - format.put())); - winrt::check_hresult(format->SetParagraphAlignment(DWRITE_PARAGRAPH_ALIGNMENT_CENTER)); - return *this; -} - -D2DText& D2DText::set_alignment_left() -{ - winrt::check_hresult(format->SetTextAlignment(DWRITE_TEXT_ALIGNMENT_LEADING)); - return *this; -} - -D2DText& D2DText::set_alignment_center() -{ - winrt::check_hresult(format->SetTextAlignment(DWRITE_TEXT_ALIGNMENT_CENTER)); - return *this; -} - -D2DText& D2DText::set_alignment_right() -{ - winrt::check_hresult(format->SetTextAlignment(DWRITE_TEXT_ALIGNMENT_TRAILING)); - return *this; -} - -void D2DText::write(ID2D1DeviceContext5* d2d_dc, D2D1_COLOR_F color, D2D1_RECT_F rect, std::wstring text) -{ - winrt::com_ptr brush; - d2d_dc->CreateSolidColorBrush(color, brush.put()); - d2d_dc->DrawText(text.c_str(), - static_cast(text.length()), - format.get(), - rect, - brush.get()); -} diff --git a/src/modules/ShortcutGuide/ShortcutGuide/d2d_text.h b/src/modules/ShortcutGuide/ShortcutGuide/d2d_text.h deleted file mode 100644 index 513dc120e58b..000000000000 --- a/src/modules/ShortcutGuide/ShortcutGuide/d2d_text.h +++ /dev/null @@ -1,18 +0,0 @@ -#pragma once -#include -#include - -class D2DText -{ -public: - D2DText(float text_size = 15.0f, float scale = 1.0f); - D2DText& resize(float text_size, float scale); - D2DText& set_alignment_left(); - D2DText& set_alignment_center(); - D2DText& set_alignment_right(); - void write(ID2D1DeviceContext5* d2d_dc, D2D1_COLOR_F color, D2D1_RECT_F rect, std::wstring text); - -private: - winrt::com_ptr factory; - winrt::com_ptr format; -}; diff --git a/src/modules/ShortcutGuide/ShortcutGuide/d2d_window.cpp b/src/modules/ShortcutGuide/ShortcutGuide/d2d_window.cpp deleted file mode 100644 index f4bf7e9cf607..000000000000 --- a/src/modules/ShortcutGuide/ShortcutGuide/d2d_window.cpp +++ /dev/null @@ -1,211 +0,0 @@ -#include "pch.h" -#include "d2d_window.h" - -#include - -D2DWindow::D2DWindow() -{ - static const WCHAR* class_name = L"PToyD2DPopup"; - WNDCLASS wc = {}; - wc.hCursor = LoadCursor(nullptr, IDC_ARROW); - wc.hInstance = reinterpret_cast(&__ImageBase); - wc.lpszClassName = class_name; - wc.style = CS_HREDRAW | CS_VREDRAW; - wc.lpfnWndProc = d2d_window_proc; - RegisterClass(&wc); - hwnd = CreateWindowExW(WS_EX_TOOLWINDOW | WS_EX_TOPMOST | WS_EX_NOREDIRECTIONBITMAP | WS_EX_LAYERED, - wc.lpszClassName, - L"PToyD2DPopup", - WS_POPUP | WS_VISIBLE, - CW_USEDEFAULT, - CW_USEDEFAULT, - CW_USEDEFAULT, - CW_USEDEFAULT, - nullptr, - nullptr, - wc.hInstance, - this); - WINRT_VERIFY(hwnd); -} - -void D2DWindow::show(UINT x, UINT y, UINT width, UINT height) -{ - if (!initialized) - { - base_init(); - } - base_resize(width, height); - render_empty(); - hidden = false; - on_show(); - SetWindowPos(hwnd, HWND_TOPMOST, x, y, width, height, 0); - ShowWindow(hwnd, SW_SHOWNORMAL); - SetForegroundWindow(hwnd); - UpdateWindow(hwnd); -} - -void D2DWindow::hide() -{ - hidden = true; - ShowWindow(hwnd, SW_HIDE); - on_hide(); -} - -void D2DWindow::initialize() -{ - base_init(); -} - -void D2DWindow::base_init() -{ - std::unique_lock lock(mutex); - // D2D1Factory is independent from the device, no need to recreate it if we need to recreate the device. - if (!d2d_factory) - { -#ifdef _DEBUG - D2D1_FACTORY_OPTIONS options = { D2D1_DEBUG_LEVEL_INFORMATION }; -#else - D2D1_FACTORY_OPTIONS options = {}; -#endif - winrt::check_hresult(D2D1CreateFactory(D2D1_FACTORY_TYPE_MULTI_THREADED, - __uuidof(d2d_factory), - &options, - d2d_factory.put_void())); - } - // For all other stuff - assign nullptr first to release the object, to reset the com_ptr. - d2d_dc = nullptr; - d2d_device = nullptr; - dxgi_factory = nullptr; - dxgi_device = nullptr; - d3d_device = nullptr; - winrt::check_hresult(D3D11CreateDevice(nullptr, - D3D_DRIVER_TYPE_HARDWARE, - nullptr, - D3D11_CREATE_DEVICE_BGRA_SUPPORT, - nullptr, - 0, - D3D11_SDK_VERSION, - d3d_device.put(), - nullptr, - nullptr)); - winrt::check_hresult(d3d_device->QueryInterface(__uuidof(dxgi_device), dxgi_device.put_void())); - winrt::check_hresult(CreateDXGIFactory2(0, __uuidof(dxgi_factory), dxgi_factory.put_void())); - winrt::check_hresult(d2d_factory->CreateDevice(dxgi_device.get(), d2d_device.put())); - winrt::check_hresult(d2d_device->CreateDeviceContext(D2D1_DEVICE_CONTEXT_OPTIONS_NONE, d2d_dc.put())); - init(); - initialized = true; -} - -void D2DWindow::base_resize(UINT width, UINT height) -{ - std::unique_lock lock(mutex); - if (!initialized) - { - return; - } - window_width = width; - window_height = height; - if (window_width == 0 || window_height == 0) - { - return; - } - DXGI_SWAP_CHAIN_DESC1 sc_description = {}; - sc_description.Format = DXGI_FORMAT_B8G8R8A8_UNORM; - sc_description.BufferUsage = DXGI_USAGE_RENDER_TARGET_OUTPUT; - sc_description.SwapEffect = DXGI_SWAP_EFFECT_FLIP_SEQUENTIAL; - sc_description.BufferCount = 2; - sc_description.SampleDesc.Count = 1; - sc_description.AlphaMode = DXGI_ALPHA_MODE_PREMULTIPLIED; - sc_description.Width = window_width; - sc_description.Height = window_height; - dxgi_swap_chain = nullptr; - winrt::check_hresult(dxgi_factory->CreateSwapChainForComposition(dxgi_device.get(), - &sc_description, - nullptr, - dxgi_swap_chain.put())); - composition_device = nullptr; - winrt::check_hresult(DCompositionCreateDevice(dxgi_device.get(), - __uuidof(composition_device), - composition_device.put_void())); - - composition_target = nullptr; - winrt::check_hresult(composition_device->CreateTargetForHwnd(hwnd, true, composition_target.put())); - - composition_visual = nullptr; - winrt::check_hresult(composition_device->CreateVisual(composition_visual.put())); - winrt::check_hresult(composition_visual->SetContent(dxgi_swap_chain.get())); - winrt::check_hresult(composition_target->SetRoot(composition_visual.get())); - - dxgi_surface = nullptr; - winrt::check_hresult(dxgi_swap_chain->GetBuffer(0, __uuidof(dxgi_surface), dxgi_surface.put_void())); - D2D1_BITMAP_PROPERTIES1 properties = {}; - properties.pixelFormat.alphaMode = D2D1_ALPHA_MODE_PREMULTIPLIED; - properties.pixelFormat.format = DXGI_FORMAT_B8G8R8A8_UNORM; - properties.bitmapOptions = D2D1_BITMAP_OPTIONS_TARGET | D2D1_BITMAP_OPTIONS_CANNOT_DRAW; - - d2d_bitmap = nullptr; - winrt::check_hresult(d2d_dc->CreateBitmapFromDxgiSurface(dxgi_surface.get(), - properties, - d2d_bitmap.put())); - d2d_dc->SetTarget(d2d_bitmap.get()); - resize(); -} - -void D2DWindow::base_render() -{ - std::unique_lock lock(mutex); - if (!initialized || !d2d_dc || !d2d_bitmap) - return; - d2d_dc->BeginDraw(); - render(d2d_dc.get()); - winrt::check_hresult(d2d_dc->EndDraw()); - winrt::check_hresult(dxgi_swap_chain->Present(1, 0)); - winrt::check_hresult(composition_device->Commit()); -} - -void D2DWindow::render_empty() -{ - std::unique_lock lock(mutex); - if (!initialized || !d2d_dc || !d2d_bitmap) - return; - d2d_dc->BeginDraw(); - d2d_dc->Clear(); - winrt::check_hresult(d2d_dc->EndDraw()); - winrt::check_hresult(dxgi_swap_chain->Present(1, 0)); - winrt::check_hresult(composition_device->Commit()); -} - -D2DWindow::~D2DWindow() -{ - ShowWindow(hwnd, SW_HIDE); - DestroyWindow(hwnd); -} - -D2DWindow* D2DWindow::this_from_hwnd(HWND window) -{ - return reinterpret_cast(GetWindowLongPtr(window, GWLP_USERDATA)); -} - -LRESULT __stdcall D2DWindow::d2d_window_proc(HWND window, UINT message, WPARAM wparam, LPARAM lparam) -{ - auto self = this_from_hwnd(window); - switch (message) - { - case WM_NCCREATE: - { - auto create_struct = reinterpret_cast(lparam); - SetWindowLongPtr(window, GWLP_USERDATA, reinterpret_cast(create_struct->lpCreateParams)); - return TRUE; - } - case WM_MOVE: - case WM_SIZE: - self->base_resize(static_cast(lparam) & 0xFFFF, static_cast(lparam) >> 16); - [[fallthrough]]; - case WM_PAINT: - self->base_render(); - return 0; - - default: - return DefWindowProc(window, message, wparam, lparam); - } -} diff --git a/src/modules/ShortcutGuide/ShortcutGuide/d2d_window.h b/src/modules/ShortcutGuide/ShortcutGuide/d2d_window.h deleted file mode 100644 index b062962d281f..000000000000 --- a/src/modules/ShortcutGuide/ShortcutGuide/d2d_window.h +++ /dev/null @@ -1,66 +0,0 @@ -#pragma once -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include "d2d_svg.h" - -#include -#include - -class D2DWindow -{ -public: - D2DWindow(); - void show(UINT x, UINT y, UINT width, UINT height); - void hide(); - void initialize(); - virtual ~D2DWindow(); - -protected: - // Implement this: - - // Initialization - called when D2D device needs to be created. - // When called all D2DWindow members will be initialized, including d2d_dc - virtual void init() = 0; - // resize - when called, window_width and window_height will have current window size - virtual void resize() = 0; - // render - called on WM_PAINT, BeginPaint/EndPaint is handled by D2DWindow - virtual void render(ID2D1DeviceContext5* d2d_dc) = 0; - // on_show, on_hide - called when the window is about to be shown or about to be hidden - virtual void on_show() = 0; - virtual void on_hide() = 0; - - static LRESULT __stdcall d2d_window_proc(HWND window, UINT message, WPARAM wparam, LPARAM lparam); - static D2DWindow* this_from_hwnd(HWND window); - - void base_init(); - void base_resize(UINT width, UINT height); - void base_render(); - void render_empty(); - - std::recursive_mutex mutex; - bool hidden = true; - bool initialized = false; - HWND hwnd; - UINT window_width{}; - UINT window_height{}; - winrt::com_ptr d3d_device; - winrt::com_ptr dxgi_device; - winrt::com_ptr dxgi_factory; - winrt::com_ptr dxgi_swap_chain; - winrt::com_ptr composition_device; - winrt::com_ptr composition_target; - winrt::com_ptr composition_visual; - winrt::com_ptr dxgi_surface; - winrt::com_ptr d2d_bitmap; - winrt::com_ptr d2d_factory; - winrt::com_ptr d2d_device; - winrt::com_ptr d2d_dc; -}; diff --git a/src/modules/ShortcutGuide/ShortcutGuide/main.cpp b/src/modules/ShortcutGuide/ShortcutGuide/main.cpp deleted file mode 100644 index 57a4491d4129..000000000000 --- a/src/modules/ShortcutGuide/ShortcutGuide/main.cpp +++ /dev/null @@ -1,144 +0,0 @@ -#include "pch.h" -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#include - -#include "shortcut_guide.h" -#include "target_state.h" -#include "ShortcutGuideConstants.h" -#include "trace.h" - -const std::wstring instanceMutexName = L"Local\\PowerToys_ShortcutGuide_InstanceMutex"; - -// set current path to the executable path -bool SetCurrentPath() -{ - TCHAR buffer[MAX_PATH] = { 0 }; - if (!GetModuleFileName(NULL, buffer, MAX_PATH)) - { - Logger::error(L"Failed to get module path. {}", get_last_error_or_default(GetLastError())); - return false; - } - - if (!PathRemoveFileSpec(buffer)) - { - Logger::error(L"Failed to remove file from module path. {}", get_last_error_or_default(GetLastError())); - return false; - } - - std::error_code err; - std::filesystem::current_path(buffer, err); - if (err.value()) - { - Logger::error("Failed to set current path. {}", err.message()); - return false; - } - - return true; -} - -int WINAPI wWinMain(_In_ HINSTANCE /*hInstance*/, _In_opt_ HINSTANCE /*hPrevInstance*/, _In_ PWSTR lpCmdLine, _In_ int /*nCmdShow*/) -{ - winrt::init_apartment(); - LoggerHelpers::init_logger(ShortcutGuideConstants::ModuleKey, L"ShortcutGuide", LogSettings::shortcutGuideLoggerName); - - Shared::Trace::ETWTrace trace; - trace.UpdateState(true); - - if (powertoys_gpo::getConfiguredShortcutGuideEnabledValue() == powertoys_gpo::gpo_rule_configured_disabled) - { - Logger::warn(L"Tried to start with a GPO policy setting the utility to always be disabled. Please contact your systems administrator."); - return 0; - } - - InitUnhandledExceptionHandler(); - Logger::trace("Starting Shortcut Guide"); - - if (!SetCurrentPath()) - { - return false; - } - - Trace::RegisterProvider(); - if (std::wstring(lpCmdLine).find(L' ') != std::wstring::npos) - { - Logger::trace("Sending settings telemetry"); - auto settings = OverlayWindow::GetSettings(); - Trace::SendSettings(settings); - Trace::UnregisterProvider(); - return 0; - } - - auto mutex = CreateMutex(nullptr, true, instanceMutexName.c_str()); - if (mutex == nullptr) - { - Logger::error(L"Failed to create mutex. {}", get_last_error_or_default(GetLastError())); - } - - if (GetLastError() == ERROR_ALREADY_EXISTS) - { - Logger::warn(L"Shortcut Guide instance is already running"); - Trace::UnregisterProvider(); - return 0; - } - - std::wstring pid = std::wstring(lpCmdLine); - if (!pid.empty()) - { - auto mainThreadId = GetCurrentThreadId(); - ProcessWaiter::OnProcessTerminate(pid, [mainThreadId](int err) { - if (err != ERROR_SUCCESS) - { - Logger::error(L"Failed to wait for parent process exit. {}", get_last_error_or_default(err)); - } - else - { - Logger::trace(L"PowerToys runner exited."); - } - - Logger::trace(L"Exiting Shortcut Guide"); - PostThreadMessage(mainThreadId, WM_QUIT, 0, 0); - }); - } - - auto hwnd = GetForegroundWindow(); - auto window = OverlayWindow(hwnd); - EventWaiter exitEventWaiter; - if (window.IsDisabled()) - { - Logger::trace("SG is disabled for the current foreground app. Exiting SG"); - Trace::UnregisterProvider(); - return 0; - } - else - { - auto mainThreadId = GetCurrentThreadId(); - exitEventWaiter.start(CommonSharedConstants::SHORTCUT_GUIDE_EXIT_EVENT, [mainThreadId, &window](DWORD err) { - if (err != ERROR_SUCCESS) - { - Logger::error(L"Failed to wait for {} event. {}", CommonSharedConstants::SHORTCUT_GUIDE_EXIT_EVENT, get_last_error_or_default(err)); - } - else - { - Logger::trace(L"{} event was signaled", CommonSharedConstants::SHORTCUT_GUIDE_EXIT_EVENT); - } - - window.CloseWindow(HideWindowType::THE_SHORTCUT_PRESSED, mainThreadId); - }); - } - - window.ShowWindow(); - run_message_loop(); - - trace.Flush(); - Trace::UnregisterProvider(); - return 0; -} diff --git a/src/modules/ShortcutGuide/ShortcutGuide/native_event_waiter.cpp b/src/modules/ShortcutGuide/ShortcutGuide/native_event_waiter.cpp deleted file mode 100644 index b038ea4ffda8..000000000000 --- a/src/modules/ShortcutGuide/ShortcutGuide/native_event_waiter.cpp +++ /dev/null @@ -1,29 +0,0 @@ -#include "pch.h" -#include "native_event_waiter.h" - -void NativeEventWaiter::run() -{ - while (!aborting) - { - auto result = WaitForSingleObject(event_handle, timeout); - if (!aborting && result == WAIT_OBJECT_0) - { - action(); - } - } -} - -NativeEventWaiter::NativeEventWaiter(const std::wstring& event_name, std::function action) -{ - event_handle = CreateEventW(NULL, FALSE, FALSE, event_name.c_str()); - this->action = action; - running_thread = std::thread([&]() { run(); }); -} - -NativeEventWaiter::~NativeEventWaiter() -{ - aborting = true; - SetEvent(event_handle); - running_thread.join(); - CloseHandle(event_handle); -} diff --git a/src/modules/ShortcutGuide/ShortcutGuide/native_event_waiter.h b/src/modules/ShortcutGuide/ShortcutGuide/native_event_waiter.h deleted file mode 100644 index d7dcaa603eca..000000000000 --- a/src/modules/ShortcutGuide/ShortcutGuide/native_event_waiter.h +++ /dev/null @@ -1,19 +0,0 @@ -#pragma once -#include "pch.h" -#include "common/interop/shared_constants.h" - -class NativeEventWaiter -{ - static const int timeout = 1000; - - HANDLE event_handle = nullptr; - std::function action = nullptr; - std::atomic aborting = false; - - void run(); - std::thread running_thread; - -public: - NativeEventWaiter(const std::wstring& event_name, std::function action); - ~NativeEventWaiter(); -}; diff --git a/src/modules/ShortcutGuide/ShortcutGuide/overlay_window.cpp b/src/modules/ShortcutGuide/ShortcutGuide/overlay_window.cpp deleted file mode 100644 index 825bd10a1f7a..000000000000 --- a/src/modules/ShortcutGuide/ShortcutGuide/overlay_window.cpp +++ /dev/null @@ -1,947 +0,0 @@ -#include "pch.h" -#include "overlay_window.h" -#include -#include "tasklist_positions.h" -#include "start_visible.h" -#include -#include -#include - -#include "shortcut_guide.h" -#include "trace.h" -#include "Generated Files/resource.h" - -namespace -{ - // Gets position of given window. - std::optional get_window_pos(HWND hwnd) - { - RECT window; - if (DwmGetWindowAttribute(hwnd, DWMWA_EXTENDED_FRAME_BOUNDS, &window, sizeof(window)) == S_OK) - { - return window; - } - else - { - return {}; - } - } - - enum WindowState - { - UNKNOWN, - MINIMIZED, - MAXIMIZED, - SNAPPED_TOP_LEFT, - SNAPPED_LEFT, - SNAPPED_BOTTOM_LEFT, - SNAPPED_TOP_RIGHT, - SNAPPED_RIGHT, - SNAPPED_BOTTOM_RIGHT, - RESTORED - }; - - inline WindowState get_window_state(HWND hwnd) - { - WINDOWPLACEMENT placement; - placement.length = sizeof(WINDOWPLACEMENT); - - if (GetWindowPlacement(hwnd, &placement) == 0) - { - return UNKNOWN; - } - - if (placement.showCmd == SW_MINIMIZE || placement.showCmd == SW_SHOWMINIMIZED || IsIconic(hwnd)) - { - return MINIMIZED; - } - - if (placement.showCmd == SW_MAXIMIZE || placement.showCmd == SW_SHOWMAXIMIZED) - { - return MAXIMIZED; - } - - auto rectp = get_window_pos(hwnd); - if (!rectp) - { - return UNKNOWN; - } - - auto rect = *rectp; - MONITORINFO monitor; - monitor.cbSize = sizeof(MONITORINFO); - auto h_monitor = MonitorFromWindow(hwnd, MONITOR_DEFAULTTONEAREST); - GetMonitorInfo(h_monitor, &monitor); - bool top_left = monitor.rcWork.top == rect.top && monitor.rcWork.left == rect.left; - bool bottom_left = monitor.rcWork.bottom == rect.bottom && monitor.rcWork.left == rect.left; - bool top_right = monitor.rcWork.top == rect.top && monitor.rcWork.right == rect.right; - bool bottom_right = monitor.rcWork.bottom == rect.bottom && monitor.rcWork.right == rect.right; - - if (top_left && bottom_left) - return SNAPPED_LEFT; - if (top_left) - return SNAPPED_TOP_LEFT; - if (bottom_left) - return SNAPPED_BOTTOM_LEFT; - if (top_right && bottom_right) - return SNAPPED_RIGHT; - if (top_right) - return SNAPPED_TOP_RIGHT; - if (bottom_right) - return SNAPPED_BOTTOM_RIGHT; - - return RESTORED; - } - -} - -D2DOverlaySVG& D2DOverlaySVG::load(const std::wstring& filename, ID2D1DeviceContext5* d2d_dc) -{ - D2DSVG::load(filename, d2d_dc); - window_group = nullptr; - thumbnail_top_left = {}; - thumbnail_bottom_right = {}; - thumbnail_scaled_rect = {}; - return *this; -} - -D2DOverlaySVG& D2DOverlaySVG::resize(int x, int y, int width, int height, float fill, float max_scale) -{ - D2DSVG::resize(x, y, width, height, fill, max_scale); - if (thumbnail_bottom_right.x != 0 && thumbnail_bottom_right.y != 0) - { - auto scaled_top_left = transform.TransformPoint(thumbnail_top_left); - auto scanled_bottom_right = transform.TransformPoint(thumbnail_bottom_right); - thumbnail_scaled_rect.left = static_cast(scaled_top_left.x); - thumbnail_scaled_rect.top = static_cast(scaled_top_left.y); - thumbnail_scaled_rect.right = static_cast(scanled_bottom_right.x); - thumbnail_scaled_rect.bottom = static_cast(scanled_bottom_right.y); - } - return *this; -} - -D2DOverlaySVG& D2DOverlaySVG::find_thumbnail(const std::wstring& id) -{ - winrt::com_ptr thumbnail_box; - winrt::check_hresult(svg->FindElementById(id.c_str(), thumbnail_box.put())); - winrt::check_hresult(thumbnail_box->GetAttributeValue(L"x", &thumbnail_top_left.x)); - winrt::check_hresult(thumbnail_box->GetAttributeValue(L"y", &thumbnail_top_left.y)); - winrt::check_hresult(thumbnail_box->GetAttributeValue(L"width", &thumbnail_bottom_right.x)); - thumbnail_bottom_right.x += thumbnail_top_left.x; - winrt::check_hresult(thumbnail_box->GetAttributeValue(L"height", &thumbnail_bottom_right.y)); - thumbnail_bottom_right.y += thumbnail_top_left.y; - return *this; -} - -D2DOverlaySVG& D2DOverlaySVG::find_window_group(const std::wstring& id) -{ - window_group = nullptr; - winrt::check_hresult(svg->FindElementById(id.c_str(), window_group.put())); - return *this; -} - -ScaleResult D2DOverlaySVG::get_thumbnail_rect_and_scale(int x_offset, int y_offset, int window_cx, int window_cy, float fill) -{ - if (thumbnail_bottom_right.x == 0 && thumbnail_bottom_right.y == 0) - { - return {}; - } - int thumbnail_scaled_rect_width = thumbnail_scaled_rect.right - thumbnail_scaled_rect.left; - int thumbnail_scaled_rect_heigh = thumbnail_scaled_rect.bottom - thumbnail_scaled_rect.top; - if (thumbnail_scaled_rect_heigh == 0 || thumbnail_scaled_rect_width == 0 || - window_cx == 0 || window_cy == 0) - { - return {}; - } - float scale_h = fill * thumbnail_scaled_rect_width / window_cx; - float scale_v = fill * thumbnail_scaled_rect_heigh / window_cy; - float use_scale = std::min(scale_h, scale_v); - RECT thumb_rect; - thumb_rect.left = thumbnail_scaled_rect.left + static_cast(thumbnail_scaled_rect_width - use_scale * window_cx) / 2 + x_offset; - thumb_rect.right = thumbnail_scaled_rect.right - static_cast(thumbnail_scaled_rect_width - use_scale * window_cx) / 2 + x_offset; - thumb_rect.top = thumbnail_scaled_rect.top + static_cast(thumbnail_scaled_rect_heigh - use_scale * window_cy) / 2 + y_offset; - thumb_rect.bottom = thumbnail_scaled_rect.bottom - static_cast(thumbnail_scaled_rect_heigh - use_scale * window_cy) / 2 + y_offset; - ScaleResult result; - result.scale = use_scale; - result.rect = thumb_rect; - return result; -} - -winrt::com_ptr D2DOverlaySVG::find_element(const std::wstring& id) -{ - winrt::com_ptr element; - winrt::check_hresult(svg->FindElementById(id.c_str(), element.put())); - return element; -} - -D2DOverlaySVG& D2DOverlaySVG::toggle_window_group(bool active) -{ - if (window_group) - { - window_group->SetAttributeValue(L"fill-opacity", active ? 1.0f : 0.3f); - } - return *this; -} - -D2D1_RECT_F D2DOverlaySVG::get_maximize_label() const -{ - D2D1_RECT_F result; - auto height = thumbnail_scaled_rect.bottom - thumbnail_scaled_rect.top; - auto width = thumbnail_scaled_rect.right - thumbnail_scaled_rect.left; - if (width >= height) - { - result.top = thumbnail_scaled_rect.bottom + height * 0.210f; - result.bottom = thumbnail_scaled_rect.bottom + height * 0.310f; - result.left = thumbnail_scaled_rect.left + width * 0.009f; - result.right = thumbnail_scaled_rect.right + width * 0.009f; - } - else - { - result.top = thumbnail_scaled_rect.top + height * 0.323f; - result.bottom = thumbnail_scaled_rect.top + height * 0.398f; - result.left = static_cast(thumbnail_scaled_rect.right); - result.right = thumbnail_scaled_rect.right + width * 1.45f; - } - return result; -} -D2D1_RECT_F D2DOverlaySVG::get_minimize_label() const -{ - D2D1_RECT_F result; - auto height = thumbnail_scaled_rect.bottom - thumbnail_scaled_rect.top; - auto width = thumbnail_scaled_rect.right - thumbnail_scaled_rect.left; - if (width >= height) - { - result.top = thumbnail_scaled_rect.bottom + height * 0.8f; - result.bottom = thumbnail_scaled_rect.bottom + height * 0.9f; - result.left = thumbnail_scaled_rect.left + width * 0.009f; - result.right = thumbnail_scaled_rect.right + width * 0.009f; - } - else - { - result.top = thumbnail_scaled_rect.top + height * 0.725f; - result.bottom = thumbnail_scaled_rect.top + height * 0.800f; - result.left =static_cast(thumbnail_scaled_rect.right); - result.right = thumbnail_scaled_rect.right + width * 1.45f; - } - return result; -} -D2D1_RECT_F D2DOverlaySVG::get_snap_left() const -{ - D2D1_RECT_F result; - auto height = thumbnail_scaled_rect.bottom - thumbnail_scaled_rect.top; - auto width = thumbnail_scaled_rect.right - thumbnail_scaled_rect.left; - if (width >= height) - { - result.top = thumbnail_scaled_rect.bottom + height * 0.5f; - result.bottom = thumbnail_scaled_rect.bottom + height * 0.6f; - result.left = thumbnail_scaled_rect.left + width * 0.009f; - result.right = thumbnail_scaled_rect.left + width * 0.339f; - } - else - { - result.top = thumbnail_scaled_rect.top + height * 0.523f; - result.bottom = thumbnail_scaled_rect.top + height * 0.598f; - result.left = static_cast(thumbnail_scaled_rect.right); - result.right = thumbnail_scaled_rect.right + width * 0.450f; - } - return result; -} -D2D1_RECT_F D2DOverlaySVG::get_snap_right() const -{ - D2D1_RECT_F result; - auto height = thumbnail_scaled_rect.bottom - thumbnail_scaled_rect.top; - auto width = thumbnail_scaled_rect.right - thumbnail_scaled_rect.left; - if (width >= height) - { - result.top = thumbnail_scaled_rect.bottom + height * 0.5f; - result.bottom = thumbnail_scaled_rect.bottom + height * 0.6f; - result.left = thumbnail_scaled_rect.left + width * 0.679f; - result.right = thumbnail_scaled_rect.right + width * 1.009f; - } - else - { - result.top = thumbnail_scaled_rect.top + height * 0.523f; - result.bottom = thumbnail_scaled_rect.top + height * 0.598f; - result.left = static_cast(thumbnail_scaled_rect.right + width); - result.right = thumbnail_scaled_rect.right + width * 1.45f; - } - return result; -} - -D2DOverlayWindow::D2DOverlayWindow() : - total_screen({}), - D2DWindow() -{ - BOOL isEnabledAnimations = GetAnimationsEnabled(); - background_animation = isEnabledAnimations? 0.3f : 0.f; - global_windows_shortcuts_animation = isEnabledAnimations ? 0.3f : 0.f; - taskbar_icon_shortcuts_animation = isEnabledAnimations ? 0.3f : 0.f; - tasklist_thread = std::thread([&] { - while (running) - { - // Removing causes C3538 on std::unique_lock lock(mutex); in show(..) - std::unique_lock task_list_lock(tasklist_cv_mutex); - tasklist_cv.wait(task_list_lock, [&] { return !running || tasklist_update; }); - if (!running) - return; - task_list_lock.unlock(); - while (running && tasklist_update) - { - std::vector buttons; - if (tasklist.update_buttons(buttons)) - { - std::unique_lock lock(mutex); - tasklist_buttons.swap(buttons); - } - std::this_thread::sleep_for(std::chrono::milliseconds(500)); - } - } - }); -} - -void D2DOverlayWindow::show(HWND window, bool snappable) -{ - std::unique_lock lock(mutex); - hidden = false; - tasklist_buttons.clear(); - active_window = window; - active_window_snappable = snappable; - auto old_bck = colors.start_color_menu; - auto colors_updated = colors.update(); - auto new_light_mode = (theme_setting == Light) || (theme_setting == System && colors.light_mode); - if (initialized && (colors_updated || light_mode != new_light_mode)) - { - // update background colors - landscape.recolor(old_bck, colors.start_color_menu); - portrait.recolor(old_bck, colors.start_color_menu); - for (auto& arrow : arrows) - { - arrow.recolor(old_bck, colors.start_color_menu); - } - light_mode = new_light_mode; - if (light_mode) - { - landscape.recolor(0xDDDDDD, 0x222222); - portrait.recolor(0xDDDDDD, 0x222222); - for (auto& arrow : arrows) - { - arrow.recolor(0xDDDDDD, 0x222222); - } - } - else - { - landscape.recolor(0x222222, 0xDDDDDD); - portrait.recolor(0x222222, 0xDDDDDD); - for (auto& arrow : arrows) - { - arrow.recolor(0x222222, 0xDDDDDD); - } - } - } - monitors = MonitorInfo::GetMonitors(true); - // calculate the rect covering all the screens - total_screen = monitors[0].GetScreenSize(true); - for (auto& monitor : monitors) - { - const auto monitorSize = monitor.GetScreenSize(true); - total_screen.rect.left = std::min(total_screen.left(), monitorSize.left()); - total_screen.rect.top = std::min(total_screen.top(), monitorSize.top()); - total_screen.rect.right = std::max(total_screen.right(), monitorSize.right()); - total_screen.rect.bottom = std::max(total_screen.bottom(), monitorSize.bottom()); - } - // make sure top-right corner of all the monitor rects is (0,0) - monitor_dx = -total_screen.left(); - monitor_dy = -total_screen.top(); - total_screen.rect.left += monitor_dx; - total_screen.rect.right += monitor_dx; - total_screen.rect.top += monitor_dy; - total_screen.rect.bottom += monitor_dy; - tasklist.update(); - if (window) - { - // Ignore errors, if this fails we will just not show the thumbnail - DwmRegisterThumbnail(hwnd, window, &thumbnail); - } - - background_animation.reset(); - - if (milliseconds_press_time_for_global_windows_shortcuts < milliseconds_press_time_for_taskbar_icon_shortcuts) - { - global_windows_shortcuts_shown = true; - taskbar_icon_shortcuts_shown = false; - global_windows_shortcuts_animation.reset(); - } - else if (milliseconds_press_time_for_global_windows_shortcuts > milliseconds_press_time_for_taskbar_icon_shortcuts) - { - global_windows_shortcuts_shown = false; - taskbar_icon_shortcuts_shown = true; - taskbar_icon_shortcuts_animation.reset(); - } - else - { - global_windows_shortcuts_shown = true; - taskbar_icon_shortcuts_shown = true; - global_windows_shortcuts_animation.reset(); - taskbar_icon_shortcuts_animation.reset(); - } - - auto primary_size = MonitorInfo::GetPrimaryMonitor().GetScreenSize(false); - shown_start_time = std::chrono::steady_clock::now(); - lock.unlock(); - D2DWindow::show(primary_size.left(), primary_size.top(), primary_size.width(), primary_size.height()); - // Check if taskbar is auto-hidden. If so, don't display the number arrows - APPBARDATA param = {}; - param.cbSize = sizeof(APPBARDATA); - if (static_cast(SHAppBarMessage(ABM_GETSTATE, ¶m)) != ABS_AUTOHIDE) - { - tasklist_cv_mutex.lock(); - tasklist_update = true; - tasklist_cv_mutex.unlock(); - tasklist_cv.notify_one(); - } -} - -void D2DOverlayWindow::on_show() -{ - // show override does everything -} - -void D2DOverlayWindow::on_hide() -{ - Logger::trace("D2DOverlayWindow::on_hide()"); - tasklist_cv_mutex.lock(); - tasklist_update = false; - tasklist_cv_mutex.unlock(); - tasklist_cv.notify_one(); - if (thumbnail) - { - DwmUnregisterThumbnail(thumbnail); - } - std::chrono::steady_clock::time_point shown_end_time = std::chrono::steady_clock::now(); - // Trace the event only if the overlay window was visible. - if (shown_start_time.time_since_epoch().count() > 0) - { - auto duration = std::chrono::duration_cast(shown_end_time - shown_start_time).count(); - Logger::trace(L"Duration: {}. Close Type: {}", duration, windowCloseType); - Trace::SendGuideSession(duration, windowCloseType.c_str()); - shown_start_time = {}; - } -} - -D2DOverlayWindow::~D2DOverlayWindow() -{ - tasklist_cv_mutex.lock(); - running = false; - tasklist_cv_mutex.unlock(); - tasklist_cv.notify_one(); - tasklist_thread.join(); -} - -void D2DOverlayWindow::apply_overlay_opacity(float opacity) -{ - if (opacity <= 0.0f) - { - opacity = 0.0f; - } - if (opacity >= 1.0f) - { - opacity = 1.0f; - } - overlay_opacity = opacity; -} - -void D2DOverlayWindow::apply_press_time_for_global_windows_shortcuts(int press_time) -{ - milliseconds_press_time_for_global_windows_shortcuts = std::max(press_time, 0); -} - -void D2DOverlayWindow::apply_press_time_for_taskbar_icon_shortcuts(int press_time) -{ - milliseconds_press_time_for_taskbar_icon_shortcuts = std::max(press_time, 0); -} - -void D2DOverlayWindow::set_theme(const std::wstring& theme) -{ - if (theme == L"light") - { - theme_setting = Light; - } - else if (theme == L"dark") - { - theme_setting = Dark; - } - else - { - theme_setting = System; - } -} - -/* Hide the window but do not call on_hide(). Use this to quickly hide the window when needed. - Note, that a proper hide should be made after this before showing the window again. -*/ -void D2DOverlayWindow::quick_hide() -{ - ShowWindow(hwnd, SW_HIDE); - if (thumbnail) - { - DwmUnregisterThumbnail(thumbnail); - } -} - -HWND D2DOverlayWindow::get_window_handle() -{ - return hwnd; -} - -float D2DOverlayWindow::get_overlay_opacity() -{ - return overlay_opacity; -} - -void D2DOverlayWindow::init() -{ - colors.update(); - landscape.load(L"Assets\\ShortcutGuide\\overlay.svg", d2d_dc.get()) - .find_thumbnail(L"monitorRect") - .find_window_group(L"WindowControlsGroup") - .recolor(0x2582FB, colors.start_color_menu); - portrait.load(L"Assets\\ShortcutGuide\\overlay_portrait.svg", d2d_dc.get()) - .find_thumbnail(L"monitorRect") - .find_window_group(L"WindowControlsGroup") - .recolor(0x2582FB, colors.start_color_menu); - no_active.load(L"Assets\\ShortcutGuide\\no_active_window.svg", d2d_dc.get()); - arrows.resize(10); - for (unsigned i = 0; i < arrows.size(); ++i) - { - arrows[i].load(L"Assets\\ShortcutGuide\\" + std::to_wstring((i + 1) % 10) + L".svg", d2d_dc.get()).recolor(0x2582FB, colors.start_color_menu); - } - light_mode = (theme_setting == Light) || (theme_setting == System && colors.light_mode); - if (light_mode) - { - landscape.recolor(0x2E17FC, 0x000000); - portrait.recolor(0x2E17FC, 0x000000); - for (auto& arrow : arrows) - { - arrow.recolor(0x222222, 0x000000); - } - } - else - { - landscape.recolor(0x2E17FC, 0xFFFFFF); - portrait.recolor(0x2E17FC, 0xFFFFFF); - for (auto& arrow : arrows) - { - arrow.recolor(0x222222, 0xFFFFFF); - } - } -} - -void D2DOverlayWindow::resize() -{ - window_rect = *get_window_pos(hwnd); - float no_active_scale, font; - if (window_width >= window_height) - { // portrait is broke right now - use_overlay = &landscape; - no_active_scale = 0.3f; - font = 12.0f; - } - else - { - use_overlay = &portrait; - no_active_scale = 0.5f; - font = 13.0f; - } - use_overlay->resize(0, 0, window_width, window_height, 0.8f); - auto thumb_no_active_rect = use_overlay->get_thumbnail_rect_and_scale(0, 0, no_active.width(), no_active.height(), no_active_scale).rect; - no_active.resize(thumb_no_active_rect.left, - thumb_no_active_rect.top, - thumb_no_active_rect.right - thumb_no_active_rect.left, - thumb_no_active_rect.bottom - thumb_no_active_rect.top, - 1.0f); - text.resize(font, use_overlay->get_scale()); -} - -void render_arrow(D2DSVG& arrow, TasklistButton& button, RECT window, float max_scale, ID2D1DeviceContext5* d2d_dc, int x_offset, int y_offset) -{ - int dx = 0, dy = 0; - // Calculate taskbar orientation - arrow.toggle_element(L"left", false); - arrow.toggle_element(L"right", false); - arrow.toggle_element(L"top", false); - arrow.toggle_element(L"bottom", false); - if (button.x <= window.left) - { // taskbar on left - dx = 1; - arrow.toggle_element(L"left", true); - } - if (button.x >= window.right) - { // taskbar on right - dx = -1; - arrow.toggle_element(L"right", true); - } - if (button.y <= window.top) - { // taskbar on top - dy = 1; - arrow.toggle_element(L"top", true); - } - if (button.y >= window.bottom) - { // taskbar on bottom - dy = -1; - arrow.toggle_element(L"bottom", true); - } - double arrow_ratio = static_cast(arrow.height()) / arrow.width(); - if (dy != 0) - { - // assume button is 25% wider than taller, +10% to make room for each of the arrows that are hidden - auto render_arrow_width = static_cast(button.height * 1.25f * 1.2f); - auto render_arrow_height = static_cast(render_arrow_width * arrow_ratio); - arrow.resize((button.x + (button.width - render_arrow_width) / 2) + x_offset, - (dy == -1 ? button.y - render_arrow_height : 0) + y_offset, - render_arrow_width, - render_arrow_height, - 0.95f, - max_scale) - .render(d2d_dc); - } - else - { - // same as above - make room for the hidden arrow - auto render_arrow_height = static_cast(button.height * 1.2f); - auto render_arrow_width = static_cast(render_arrow_height / arrow_ratio); - arrow.resize((dx == -1 ? button.x - render_arrow_width : 0) + x_offset, - (button.y + (button.height - render_arrow_height) / 2) + y_offset, - render_arrow_width, - render_arrow_height, - 0.95f, - max_scale) - .render(d2d_dc); - } -} - -bool D2DOverlayWindow::show_thumbnail(const RECT& rect, double alpha) -{ - if (!thumbnail) - { - return false; - } - DWM_THUMBNAIL_PROPERTIES thumb_properties; - thumb_properties.dwFlags = DWM_TNP_SOURCECLIENTAREAONLY | DWM_TNP_VISIBLE | DWM_TNP_RECTDESTINATION | DWM_TNP_OPACITY; - thumb_properties.fSourceClientAreaOnly = FALSE; - thumb_properties.fVisible = TRUE; - thumb_properties.opacity = static_cast(255 * alpha); - thumb_properties.rcDestination = rect; - if (DwmUpdateThumbnailProperties(thumbnail, &thumb_properties) != S_OK) - { - return false; - } - return true; -} - -void D2DOverlayWindow::hide_thumbnail() -{ - DWM_THUMBNAIL_PROPERTIES thumb_properties; - thumb_properties.dwFlags = DWM_TNP_VISIBLE; - thumb_properties.fVisible = FALSE; - DwmUpdateThumbnailProperties(thumbnail, &thumb_properties); -} - -void D2DOverlayWindow::render(ID2D1DeviceContext5* d2d_device_context) -{ - if (!hidden && !overlay_window_instance->overlay_visible()) - { - hide(); - return; - } - - d2d_device_context->Clear(); - int taskbar_icon_shortcuts_x_offset = 0, taskbar_icon_shortcuts_y_offset = 0; - - double current_background_anim_value = background_animation.value(Animation::AnimFunctions::LINEAR); - double current_global_windows_shortcuts_anim_value = global_windows_shortcuts_animation.value(Animation::AnimFunctions::LINEAR); - double pos_global_windows_shortcuts_anim_value = 1 - global_windows_shortcuts_animation.value(Animation::AnimFunctions::EASE_OUT_EXPO); - double pos_taskbar_icon_shortcuts_anim_value = 1 - taskbar_icon_shortcuts_animation.value(Animation::AnimFunctions::EASE_OUT_EXPO); - - // Draw background - SetLayeredWindowAttributes(hwnd, 0, static_cast(255 * current_background_anim_value), LWA_ALPHA); - winrt::com_ptr brush; - float brush_opacity = get_overlay_opacity(); - D2D1_COLOR_F brushColor = light_mode ? D2D1::ColorF(1.0f, 1.0f, 1.0f, brush_opacity) : D2D1::ColorF(0, 0, 0, brush_opacity); - winrt::check_hresult(d2d_device_context->CreateSolidColorBrush(brushColor, brush.put())); - D2D1_RECT_F background_rect = {}; - background_rect.bottom = static_cast(window_height); - background_rect.right = static_cast(window_width); - d2d_device_context->SetTransform(D2D1::Matrix3x2F::Identity()); - d2d_device_context->FillRectangle(background_rect, brush.get()); - - // Draw the taskbar shortcuts (the arrows with numbers) - if (taskbar_icon_shortcuts_shown) - { - if (!tasklist_buttons.empty()) - { - if (tasklist_buttons[0].x <= window_rect.left) - { - // taskbar on left - taskbar_icon_shortcuts_x_offset = static_cast(-pos_taskbar_icon_shortcuts_anim_value * use_overlay->width() * use_overlay->get_scale()); - } - if (tasklist_buttons[0].x >= window_rect.right) - { - // taskbar on right - taskbar_icon_shortcuts_x_offset = static_cast(pos_taskbar_icon_shortcuts_anim_value * use_overlay->width() * use_overlay->get_scale()); - } - if (tasklist_buttons[0].y <= window_rect.top) - { - // taskbar on top - taskbar_icon_shortcuts_y_offset = static_cast(-pos_taskbar_icon_shortcuts_anim_value * use_overlay->height() * use_overlay->get_scale()); - } - if (tasklist_buttons[0].y >= window_rect.bottom) - { - // taskbar on bottom - taskbar_icon_shortcuts_y_offset = static_cast(pos_taskbar_icon_shortcuts_anim_value * use_overlay->height() * use_overlay->get_scale()); - } - for (auto&& button : tasklist_buttons) - { - if (static_cast(button.keynum) - 1 >= arrows.size()) - { - continue; - } - render_arrow(arrows[static_cast(button.keynum) - 1], button, window_rect, use_overlay->get_scale(), d2d_device_context, taskbar_icon_shortcuts_x_offset, taskbar_icon_shortcuts_y_offset); - } - } - } - else - { - auto time_since_start = std::chrono::high_resolution_clock::now() - shown_start_time; - if (time_since_start.count() / 1000000 > milliseconds_press_time_for_taskbar_icon_shortcuts - milliseconds_press_time_for_global_windows_shortcuts) - { - taskbar_icon_shortcuts_shown = true; - taskbar_icon_shortcuts_animation.reset(); - } - } - - if (global_windows_shortcuts_shown) - { - // Thumbnail logic: - auto window_state = get_window_state(active_window); - auto thumb_window = get_window_pos(active_window); - if (!thumb_window.has_value()) - { - thumb_window = RECT(); - } - - bool miniature_shown = active_window != nullptr && thumbnail != nullptr && thumb_window && window_state != MINIMIZED; - RECT client_rect; - if (thumb_window && GetClientRect(active_window, &client_rect)) - { - int dx = ((thumb_window->right - thumb_window->left) - (client_rect.right - client_rect.left)) / 2; - int dy = ((thumb_window->bottom - thumb_window->top) - (client_rect.bottom - client_rect.top)) / 2; - thumb_window->left += dx; - thumb_window->right -= dx; - thumb_window->top += dy; - thumb_window->bottom -= dy; - } - if (miniature_shown && thumb_window->right - thumb_window->left <= 0 || thumb_window->bottom - thumb_window->top <= 0) - { - miniature_shown = false; - } - bool render_monitors = true; - auto total_monitor_with_screen = total_screen; - if (thumb_window) - { - total_monitor_with_screen.rect.left = std::min(total_monitor_with_screen.rect.left, thumb_window->left + monitor_dx); - total_monitor_with_screen.rect.top = std::min(total_monitor_with_screen.rect.top, thumb_window->top + monitor_dy); - total_monitor_with_screen.rect.right = std::max(total_monitor_with_screen.rect.right, thumb_window->right + monitor_dx); - total_monitor_with_screen.rect.bottom = std::max(total_monitor_with_screen.rect.bottom, thumb_window->bottom + monitor_dy); - } - // Only allow the new rect being slight bigger. - if (total_monitor_with_screen.width() - total_screen.width() > (thumb_window->right - thumb_window->left) / 2 || - total_monitor_with_screen.height() - total_screen.height() > (thumb_window->bottom - thumb_window->top) / 2) - { - render_monitors = false; - } - if (window_state == MINIMIZED) - { - total_monitor_with_screen = total_screen; - } - auto rect_and_scale = use_overlay->get_thumbnail_rect_and_scale(0, 0, total_monitor_with_screen.width(), total_monitor_with_screen.height(), 1); - if (miniature_shown) - { - RECT thumbnail_pos; - if (render_monitors) - { - thumbnail_pos.left = static_cast((thumb_window->left + monitor_dx) * rect_and_scale.scale + rect_and_scale.rect.left); - thumbnail_pos.top = static_cast((thumb_window->top + monitor_dy) * rect_and_scale.scale + rect_and_scale.rect.top); - thumbnail_pos.right = static_cast((thumb_window->right + monitor_dx) * rect_and_scale.scale + rect_and_scale.rect.left); - thumbnail_pos.bottom = static_cast((thumb_window->bottom + monitor_dy) * rect_and_scale.scale + rect_and_scale.rect.top); - } - else - { - thumbnail_pos = use_overlay->get_thumbnail_rect_and_scale(0, 0, thumb_window->right - thumb_window->left, thumb_window->bottom - thumb_window->top, 1).rect; - } - // If the animation is done show the thumbnail - // we cannot animate the thumbnail, the animation lags behind - miniature_shown = show_thumbnail(thumbnail_pos, current_global_windows_shortcuts_anim_value); - } - else - { - hide_thumbnail(); - } - if (window_state == MINIMIZED) - { - render_monitors = true; - } - // render the monitors - if (render_monitors) - { - brushColor = D2D1::ColorF(colors.start_color_menu, miniature_shown ? static_cast(current_global_windows_shortcuts_anim_value * 0.9) : static_cast(current_global_windows_shortcuts_anim_value * 0.3)); - brush = nullptr; - winrt::check_hresult(d2d_device_context->CreateSolidColorBrush(brushColor, brush.put())); - for (auto& monitor : monitors) - { - D2D1_RECT_F monitor_rect; - const auto monitor_size = monitor.GetScreenSize(true); - monitor_rect.left = static_cast((monitor_size.left() + monitor_dx) * rect_and_scale.scale + rect_and_scale.rect.left); - monitor_rect.top = static_cast((monitor_size.top() + monitor_dy) * rect_and_scale.scale + rect_and_scale.rect.top); - monitor_rect.right = static_cast((monitor_size.right() + monitor_dx) * rect_and_scale.scale + rect_and_scale.rect.left); - monitor_rect.bottom = static_cast((monitor_size.bottom() + monitor_dy) * rect_and_scale.scale + rect_and_scale.rect.top); - d2d_device_context->SetTransform(D2D1::Matrix3x2F::Identity()); - d2d_device_context->FillRectangle(monitor_rect, brush.get()); - } - } - // Finalize the overlay - dim the buttons if no thumbnail is present and show "No active window" - use_overlay->toggle_window_group(miniature_shown || window_state == MINIMIZED); - if (!miniature_shown && window_state != MINIMIZED) - { - no_active.render(d2d_device_context); - window_state = UNKNOWN; - } - - // Set the animation - move the draw window according to animation step - int global_windows_shortcuts_y_offset = static_cast(pos_global_windows_shortcuts_anim_value * use_overlay->height() * use_overlay->get_scale()); - auto popIn = D2D1::Matrix3x2F::Translation(0, static_cast(global_windows_shortcuts_y_offset)); - d2d_device_context->SetTransform(popIn); - - // Animate keys - for (unsigned id = 0; id < key_animations.size();) - { - auto& animation = key_animations[id]; - D2D1_COLOR_F color; - auto value = static_cast(animation.animation.value(Animation::AnimFunctions::EASE_OUT_EXPO)); - color.a = 1.0f; - color.r = animation.original.r + (1.0f - animation.original.r) * value; - color.g = animation.original.g + (1.0f - animation.original.g) * value; - color.b = animation.original.b + (1.0f - animation.original.b) * value; - animation.button->SetAttributeValue(L"fill", color); - if (animation.animation.done()) - { - if (value == 1) - { - animation.animation.reset(0.05, 1, 0); - animation.animation.value(Animation::AnimFunctions::EASE_OUT_EXPO); - } - else - { - key_animations.erase(key_animations.begin() + id); - continue; - } - } - ++id; - } - // Finally: render the overlay... - use_overlay->render(d2d_device_context); - // ... window arrows texts ... - std::wstring left, right, up, down; - bool left_disabled = false; - bool right_disabled = false; - bool up_disabled = false; - bool down_disabled = false; - switch (window_state) - { - case MINIMIZED: - left = GET_RESOURCE_STRING(IDS_NO_ACTION); - left_disabled = true; - right = GET_RESOURCE_STRING(IDS_NO_ACTION); - right_disabled = true; - up = GET_RESOURCE_STRING(IDS_RESTORE); - down = GET_RESOURCE_STRING(IDS_NO_ACTION); - down_disabled = true; - break; - case MAXIMIZED: - left = GET_RESOURCE_STRING(IDS_SNAP_LEFT); - right = GET_RESOURCE_STRING(IDS_SNAP_RIGHT); - up = GET_RESOURCE_STRING(IDS_NO_ACTION); - up_disabled = true; - down = GET_RESOURCE_STRING(IDS_RESTORE); - break; - case SNAPPED_TOP_LEFT: - left = GET_RESOURCE_STRING(IDS_SNAP_UPPER_RIGHT); - right = GET_RESOURCE_STRING(IDS_SNAP_UPPER_RIGHT); - up = GET_RESOURCE_STRING(IDS_MAXIMIZE); - down = GET_RESOURCE_STRING(IDS_SNAP_LEFT); - break; - case SNAPPED_LEFT: - left = GET_RESOURCE_STRING(IDS_SNAP_RIGHT); - right = GET_RESOURCE_STRING(IDS_RESTORE); - up = GET_RESOURCE_STRING(IDS_SNAP_UPPER_LEFT); - down = GET_RESOURCE_STRING(IDS_SNAP_LOWER_LEFT); - break; - case SNAPPED_BOTTOM_LEFT: - left = GET_RESOURCE_STRING(IDS_SNAP_LOWER_RIGHT); - right = GET_RESOURCE_STRING(IDS_SNAP_LOWER_RIGHT); - up = GET_RESOURCE_STRING(IDS_SNAP_LEFT); - down = GET_RESOURCE_STRING(IDS_MINIMIZE); - break; - case SNAPPED_TOP_RIGHT: - left = GET_RESOURCE_STRING(IDS_SNAP_UPPER_LEFT); - right = GET_RESOURCE_STRING(IDS_SNAP_UPPER_LEFT); - up = GET_RESOURCE_STRING(IDS_MAXIMIZE); - down = GET_RESOURCE_STRING(IDS_SNAP_RIGHT); - break; - case SNAPPED_RIGHT: - left = GET_RESOURCE_STRING(IDS_RESTORE); - right = GET_RESOURCE_STRING(IDS_SNAP_LEFT); - up = GET_RESOURCE_STRING(IDS_SNAP_UPPER_RIGHT); - down = GET_RESOURCE_STRING(IDS_SNAP_LOWER_RIGHT); - break; - case SNAPPED_BOTTOM_RIGHT: - left = GET_RESOURCE_STRING(IDS_SNAP_LOWER_LEFT); - right = GET_RESOURCE_STRING(IDS_SNAP_LOWER_LEFT); - up = GET_RESOURCE_STRING(IDS_SNAP_RIGHT); - down = GET_RESOURCE_STRING(IDS_MINIMIZE); - break; - case RESTORED: - left = GET_RESOURCE_STRING(IDS_SNAP_LEFT); - right = GET_RESOURCE_STRING(IDS_SNAP_RIGHT); - up = GET_RESOURCE_STRING(IDS_MAXIMIZE); - down = GET_RESOURCE_STRING(IDS_MINIMIZE); - break; - default: - left = GET_RESOURCE_STRING(IDS_NO_ACTION); - left_disabled = true; - right = GET_RESOURCE_STRING(IDS_NO_ACTION); - right_disabled = true; - up = GET_RESOURCE_STRING(IDS_NO_ACTION); - up_disabled = true; - down = GET_RESOURCE_STRING(IDS_NO_ACTION); - down_disabled = true; - } - auto text_color = D2D1::ColorF(light_mode ? 0x222222 : 0xDDDDDD, active_window_snappable && (miniature_shown || window_state == MINIMIZED) ? 1.0f : 0.3f); - use_overlay->find_element(L"KeyUpGroup")->SetAttributeValue(L"fill-opacity", up_disabled ? 0.3f : 1.0f); - text.set_alignment_center().write(d2d_device_context, text_color, use_overlay->get_maximize_label(), up); - use_overlay->find_element(L"KeyDownGroup")->SetAttributeValue(L"fill-opacity", down_disabled ? 0.3f : 1.0f); - text.write(d2d_device_context, text_color, use_overlay->get_minimize_label(), down); - use_overlay->find_element(L"KeyLeftGroup")->SetAttributeValue(L"fill-opacity", left_disabled ? 0.3f : 1.0f); - text.set_alignment_right().write(d2d_device_context, text_color, use_overlay->get_snap_left(), left); - use_overlay->find_element(L"KeyRightGroup")->SetAttributeValue(L"fill-opacity", right_disabled ? 0.3f : 1.0f); - text.set_alignment_left().write(d2d_device_context, text_color, use_overlay->get_snap_right(), right); - } - else - { - auto time_since_start = std::chrono::high_resolution_clock::now() - shown_start_time; - if (time_since_start.count() / 1000000 > milliseconds_press_time_for_global_windows_shortcuts - milliseconds_press_time_for_taskbar_icon_shortcuts) - { - global_windows_shortcuts_shown = true; - global_windows_shortcuts_animation.reset(); - } - } -} diff --git a/src/modules/ShortcutGuide/ShortcutGuide/overlay_window.h b/src/modules/ShortcutGuide/ShortcutGuide/overlay_window.h deleted file mode 100644 index 00f05db56526..000000000000 --- a/src/modules/ShortcutGuide/ShortcutGuide/overlay_window.h +++ /dev/null @@ -1,114 +0,0 @@ -#pragma once -#include "animation.h" -#include "d2d_svg.h" -#include "d2d_window.h" -#include "d2d_text.h" - -#include -#include -#include "tasklist_positions.h" - -struct ScaleResult -{ - double scale; - RECT rect; -}; - -class D2DOverlaySVG : public D2DSVG -{ -public: - D2DOverlaySVG& load(const std::wstring& filename, ID2D1DeviceContext5* d2d_dc); - D2DOverlaySVG& resize(int x, int y, int width, int height, float fill, float max_scale = -1.0f); - D2DOverlaySVG& find_thumbnail(const std::wstring& id); - D2DOverlaySVG& find_window_group(const std::wstring& id); - ScaleResult get_thumbnail_rect_and_scale(int x_offset, int y_offset, int window_cx, int window_cy, float fill); - D2DOverlaySVG& toggle_window_group(bool active); - winrt::com_ptr find_element(const std::wstring& id); - D2D1_RECT_F get_maximize_label() const; - D2D1_RECT_F get_minimize_label() const; - D2D1_RECT_F get_snap_left() const; - D2D1_RECT_F get_snap_right() const; - -private: - D2D1_POINT_2F thumbnail_top_left = {}; - D2D1_POINT_2F thumbnail_bottom_right = {}; - RECT thumbnail_scaled_rect = {}; - winrt::com_ptr window_group; -}; - -struct AnimateKeys -{ - Animation animation; - D2D1_COLOR_F original; - winrt::com_ptr button; - int vk_code; -}; - -class D2DOverlayWindow : public D2DWindow -{ -public: - D2DOverlayWindow(); - void show(HWND window, bool snappable); - ~D2DOverlayWindow(); - void apply_overlay_opacity(float opacity); - void apply_press_time_for_global_windows_shortcuts(int press_time); - void apply_press_time_for_taskbar_icon_shortcuts(int press_time); - void set_theme(const std::wstring& theme); - void quick_hide(); - - HWND get_window_handle(); - void SetWindowCloseType(std::wstring wCloseType) - { - windowCloseType = wCloseType; - } - -private: - std::wstring windowCloseType; - bool show_thumbnail(const RECT& rect, double alpha); - void hide_thumbnail(); - virtual void init() override; - virtual void resize() override; - virtual void render(ID2D1DeviceContext5* d2dd2d_device_context_dc) override; - virtual void on_show() override; - virtual void on_hide() override; - float get_overlay_opacity(); - - bool running = true; - std::vector key_animations; - std::vector monitors; - Box total_screen; - int monitor_dx = 0, monitor_dy = 0; - D2DText text; - WindowsColors colors; - Animation background_animation; - Animation global_windows_shortcuts_animation; - Animation taskbar_icon_shortcuts_animation; - bool global_windows_shortcuts_shown = false; - bool taskbar_icon_shortcuts_shown = false; - RECT window_rect = {}; - Tasklist tasklist; - std::vector tasklist_buttons; - std::thread tasklist_thread; - bool tasklist_update = false; - std::mutex tasklist_cv_mutex; - std::condition_variable tasklist_cv; - - HTHUMBNAIL thumbnail = nullptr; - HWND active_window = nullptr; - bool active_window_snappable = false; - D2DOverlaySVG landscape, portrait; - D2DOverlaySVG* use_overlay = nullptr; - D2DSVG no_active; - std::vector arrows; - std::chrono::steady_clock::time_point shown_start_time; - float overlay_opacity = 0.9f; - enum - { - Light, - Dark, - System - } theme_setting = System; - bool light_mode = true; - UINT milliseconds_press_time_for_global_windows_shortcuts = 900; - UINT milliseconds_press_time_for_taskbar_icon_shortcuts = 900; -}; diff --git a/src/modules/ShortcutGuide/ShortcutGuide/packages.config b/src/modules/ShortcutGuide/ShortcutGuide/packages.config deleted file mode 100644 index f32f48b009c8..000000000000 --- a/src/modules/ShortcutGuide/ShortcutGuide/packages.config +++ /dev/null @@ -1,4 +0,0 @@ - - - - \ No newline at end of file diff --git a/src/modules/ShortcutGuide/ShortcutGuide/pch.cpp b/src/modules/ShortcutGuide/ShortcutGuide/pch.cpp deleted file mode 100644 index 1d9f38c57d63..000000000000 --- a/src/modules/ShortcutGuide/ShortcutGuide/pch.cpp +++ /dev/null @@ -1 +0,0 @@ -#include "pch.h" diff --git a/src/modules/ShortcutGuide/ShortcutGuide/pch.h b/src/modules/ShortcutGuide/ShortcutGuide/pch.h deleted file mode 100644 index 9ec2bbcb4184..000000000000 --- a/src/modules/ShortcutGuide/ShortcutGuide/pch.h +++ /dev/null @@ -1,29 +0,0 @@ -#pragma once -#define NOMINMAX -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include \ No newline at end of file diff --git a/src/modules/ShortcutGuide/ShortcutGuide/resource.base.h b/src/modules/ShortcutGuide/ShortcutGuide/resource.base.h deleted file mode 100644 index 75f509067cb7..000000000000 --- a/src/modules/ShortcutGuide/ShortcutGuide/resource.base.h +++ /dev/null @@ -1,13 +0,0 @@ -//{{NO_DEPENDENCIES}} -// Microsoft Visual C++ generated include file. -// Used by ShortcutGuide.rc - -////////////////////////////// -// Non-localizable - -#define FILE_DESCRIPTION "PowerToys ShortcutGuide" -#define INTERNAL_NAME "ShortcutGuide" -#define ORIGINAL_FILENAME "PowerToys.ShortcutGuide.exe" - -// Non-localizable -////////////////////////////// diff --git a/src/modules/ShortcutGuide/ShortcutGuide/shortcut_guide.cpp b/src/modules/ShortcutGuide/ShortcutGuide/shortcut_guide.cpp deleted file mode 100644 index 719f713e7933..000000000000 --- a/src/modules/ShortcutGuide/ShortcutGuide/shortcut_guide.cpp +++ /dev/null @@ -1,505 +0,0 @@ -#include "pch.h" -#include "shortcut_guide.h" -#include "target_state.h" -#include "trace.h" - -#include -#include -#include -#include - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -// TODO: refactor singleton -OverlayWindow* overlay_window_instance = nullptr; - -namespace -{ - // Window properties relevant to ShortcutGuide - struct ShortcutGuideWindowInfo - { - HWND hwnd = nullptr; // Handle to the top-level foreground window or nullptr if there is no such window - bool snappable = false; // True, if the window can react to Windows Snap keys - bool disabled = false; - }; - - ShortcutGuideWindowInfo GetShortcutGuideWindowInfo(HWND active_window) - { - ShortcutGuideWindowInfo result; - active_window = GetAncestor(active_window, GA_ROOT); - if (!IsWindowVisible(active_window)) - { - return result; - } - - auto style = GetWindowLong(active_window, GWL_STYLE); - auto exStyle = GetWindowLong(active_window, GWL_EXSTYLE); - if ((style & WS_CHILD) == WS_CHILD || - (style & WS_DISABLED) == WS_DISABLED || - (exStyle & WS_EX_TOOLWINDOW) == WS_EX_TOOLWINDOW || - (exStyle & WS_EX_NOACTIVATE) == WS_EX_NOACTIVATE) - { - return result; - } - std::array class_name; - GetClassNameA(active_window, class_name.data(), static_cast(class_name.size())); - if (is_system_window(active_window, class_name.data())) - { - return result; - } - static HWND cortana_hwnd = nullptr; - if (cortana_hwnd == nullptr) - { - if (strcmp(class_name.data(), "Windows.UI.Core.CoreWindow") == 0 && - get_process_path(active_window).ends_with(L"SearchUI.exe")) - { - cortana_hwnd = active_window; - return result; - } - } - else if (cortana_hwnd == active_window) - { - return result; - } - result.hwnd = active_window; - // In reality, Windows Snap works if even one of those styles is set - // for a window, it is just limited. If there is no WS_MAXIMIZEBOX using - // WinKey + Up just won't maximize the window. Similarly, without - // WS_MINIMIZEBOX the window will not get minimized. A "Save As..." dialog - // is a example of such window - it can be snapped to both sides and to - // all screen corners, but will not get maximized nor minimized. - // For now, since ShortcutGuide can only disable entire "Windows Controls" - // group, we require that the window supports all the options. - result.snappable = ((style & WS_MAXIMIZEBOX) == WS_MAXIMIZEBOX) && - ((style & WS_MINIMIZEBOX) == WS_MINIMIZEBOX) && - ((style & WS_THICKFRAME) == WS_THICKFRAME); - return result; - } - - const LPARAM eventActivateWindow = 1; - - bool wasWinPressed = false; - bool isWinPressed() - { - return (GetAsyncKeyState(VK_LWIN) & 0x8000) || (GetAsyncKeyState(VK_RWIN) & 0x8000); - } - - // all modifiers without win key - std::vector modifierKeys = { VK_SHIFT, VK_LSHIFT, VK_RSHIFT, VK_CONTROL, VK_LCONTROL, VK_RCONTROL, VK_MENU, VK_LMENU, VK_RMENU }; - - // returns false if there are other modifiers pressed or win key isn' pressed - bool onlyWinPressed() - { - if (!isWinPressed()) - { - return false; - } - - for (auto key : modifierKeys) - { - if (GetAsyncKeyState(key) & 0x8000) - { - return false; - } - } - - return true; - } - - constexpr bool isWin(int key) - { - return key == VK_LWIN || key == VK_RWIN; - } - - constexpr bool isKeyDown(LowlevelKeyboardEvent event) - { - return event.wParam == WM_KEYDOWN || event.wParam == WM_SYSKEYDOWN; - } - - LRESULT CALLBACK LowLevelKeyboardProc(int nCode, WPARAM wParam, LPARAM lParam) - { - LowlevelKeyboardEvent event; - if (nCode == HC_ACTION) - { - event.lParam = reinterpret_cast(lParam); - event.wParam = wParam; - - if (event.lParam->vkCode == VK_ESCAPE) - { - Logger::trace(L"ESC key was pressed"); - overlay_window_instance->CloseWindow(HideWindowType::ESC_PRESSED); - } - - if (wasWinPressed && !isKeyDown(event) && isWin(event.lParam->vkCode)) - { - Logger::trace(L"Win key was released"); - overlay_window_instance->CloseWindow(HideWindowType::WIN_RELEASED); - } - - if (isKeyDown(event) && isWin(event.lParam->vkCode)) - { - wasWinPressed = true; - } - - if (onlyWinPressed() && isKeyDown(event) && !isWin(event.lParam->vkCode)) - { - Logger::trace(L"Shortcut with win key was pressed"); - overlay_window_instance->CloseWindow(HideWindowType::WIN_SHORTCUT_PRESSED); - } - } - - return CallNextHookEx(NULL, nCode, wParam, lParam); - } - - LRESULT CALLBACK LowLevelMouseProc(int nCode, WPARAM wParam, LPARAM lParam) - { - if (nCode >= 0) - { - switch (wParam) - { - case WM_LBUTTONUP: - case WM_RBUTTONUP: - case WM_MBUTTONUP: - case WM_XBUTTONUP: - // Don't close with mouse click if activation is windows key and the key is pressed - if (!overlay_window_instance->win_key_activation() || !isWinPressed()) - { - overlay_window_instance->CloseWindow(HideWindowType::MOUSE_BUTTONUP); - } - break; - default: - break; - } - } - - return CallNextHookEx(0, nCode, wParam, lParam); - } - - std::wstring ToWstring(HideWindowType type) - { - switch (type) - { - case HideWindowType::ESC_PRESSED: - return L"ESC_PRESSED"; - case HideWindowType::WIN_RELEASED: - return L"WIN_RELEASED"; - case HideWindowType::WIN_SHORTCUT_PRESSED: - return L"WIN_SHORTCUT_PRESSED"; - case HideWindowType::THE_SHORTCUT_PRESSED: - return L"THE_SHORTCUT_PRESSED"; - case HideWindowType::MOUSE_BUTTONUP: - return L"MOUSE_BUTTONUP"; - } - - return L""; - } -} - -OverlayWindow::OverlayWindow(HWND activeWindow) -{ - overlay_window_instance = this; - this->activeWindow = activeWindow; - app_name = GET_RESOURCE_STRING(IDS_SHORTCUT_GUIDE); - - Logger::info("Overlay Window is creating"); - init_settings(); - keyboardHook = SetWindowsHookEx(WH_KEYBOARD_LL, LowLevelKeyboardProc, GetModuleHandle(NULL), NULL); - if (!keyboardHook) - { - Logger::warn(L"Failed to create low level keyboard hook. {}", get_last_error_or_default(GetLastError())); - } - - mouseHook = SetWindowsHookEx(WH_MOUSE_LL, LowLevelMouseProc, GetModuleHandle(NULL), NULL); - if (!mouseHook) - { - Logger::warn(L"Failed to create low level mouse hook. {}", get_last_error_or_default(GetLastError())); - } -} - -void OverlayWindow::ShowWindow() -{ - winkey_popup = std::make_unique(); - winkey_popup->apply_overlay_opacity(overlayOpacity.value / 100.0f); - winkey_popup->set_theme(theme.value); - - // The press time only takes effect when the shortcut guide is activated by pressing the win key. - if (shouldReactToPressedWinKey.value) - { - winkey_popup->apply_press_time_for_global_windows_shortcuts(windowsKeyPressTimeForGlobalWindowsShortcuts.value); - winkey_popup->apply_press_time_for_taskbar_icon_shortcuts(windowsKeyPressTimeForTaskbarIconShortcuts.value); - } - else - { - winkey_popup->apply_press_time_for_global_windows_shortcuts(0); - winkey_popup->apply_press_time_for_taskbar_icon_shortcuts(0); - } - - target_state = std::make_unique(); - try - { - winkey_popup->initialize(); - } - catch (...) - { - Logger::critical("Winkey popup failed to initialize"); - return; - } - - target_state->toggle_force_shown(); -} - -void OverlayWindow::CloseWindow(HideWindowType type, int mainThreadId) -{ - if (mainThreadId == 0) - { - mainThreadId = GetCurrentThreadId(); - } - - if (this->winkey_popup) - { - if (shouldReactToPressedWinKey.value) - { - // Send a dummy key to prevent Start Menu from activating - INPUT dummyEvent[1] = {}; - dummyEvent[0].type = INPUT_KEYBOARD; - dummyEvent[0].ki.wVk = 0xFF; - dummyEvent[0].ki.dwFlags = KEYEVENTF_KEYUP; - SendInput(1, dummyEvent, sizeof(INPUT)); - } - this->winkey_popup->SetWindowCloseType(ToWstring(type)); - Logger::trace(L"Terminating process"); - PostThreadMessage(mainThreadId, WM_QUIT, 0, 0); - } -} - -bool OverlayWindow::IsDisabled() -{ - WCHAR exePath[MAX_PATH] = L""; - overlay_window_instance->get_exe_path(activeWindow, exePath); - if (wcslen(exePath) > 0) - { - return is_disabled_app(exePath); - } - - return false; -} - -OverlayWindow::~OverlayWindow() -{ - if (event_waiter) - { - event_waiter.reset(); - } - - if (winkey_popup) - { - winkey_popup->hide(); - } - - if (target_state) - { - target_state->exit(); - target_state.reset(); - } - - if (winkey_popup) - { - winkey_popup.reset(); - } - - if (keyboardHook) - { - UnhookWindowsHookEx(keyboardHook); - } -} - -void OverlayWindow::on_held() -{ - auto windowInfo = GetShortcutGuideWindowInfo(activeWindow); - if (windowInfo.disabled) - { - target_state->was_hidden(); - return; - } - winkey_popup->show(windowInfo.hwnd, windowInfo.snappable); -} - -void OverlayWindow::quick_hide() -{ - winkey_popup->quick_hide(); -} - -void OverlayWindow::was_hidden() -{ - target_state->was_hidden(); -} - -bool OverlayWindow::overlay_visible() const -{ - return target_state->active(); -} - -bool OverlayWindow::win_key_activation() const -{ - return shouldReactToPressedWinKey.value; -} - -void OverlayWindow::init_settings() -{ - auto settings = GetSettings(); - overlayOpacity.value = settings.overlayOpacity; - theme.value = settings.theme; - disabledApps.value = settings.disabledApps; - shouldReactToPressedWinKey.value = settings.shouldReactToPressedWinKey; - windowsKeyPressTimeForGlobalWindowsShortcuts.value = settings.windowsKeyPressTimeForGlobalWindowsShortcuts; - windowsKeyPressTimeForTaskbarIconShortcuts.value = settings.windowsKeyPressTimeForTaskbarIconShortcuts; - update_disabled_apps(); -} - -bool OverlayWindow::is_disabled_app(wchar_t* exePath) -{ - if (exePath == nullptr) - { - return false; - } - - auto exePathUpper = std::wstring(exePath); - CharUpperBuffW(exePathUpper.data(), static_cast(exePathUpper.length())); - for (const auto& row : disabled_apps_array) - { - const auto pos = exePathUpper.rfind(row); - const auto last_slash = exePathUpper.rfind('\\'); - // Check that row occurs in disabled_apps_array, and its last occurrence contains in itself the first character after the last backslash. - if (pos != std::wstring::npos && pos <= last_slash + 1 && pos + row.length() > last_slash) - { - return true; - } - } - return false; -} - -void OverlayWindow::update_disabled_apps() -{ - disabled_apps_array.clear(); - auto disabledUppercase = disabledApps.value; - CharUpperBuffW(disabledUppercase.data(), static_cast(disabledUppercase.length())); - std::wstring_view view(disabledUppercase); - view = trim(view); - while (!view.empty()) - { - auto pos = (std::min)(view.find_first_of(L"\r\n"), view.length()); - disabled_apps_array.emplace_back(view.substr(0, pos)); - view.remove_prefix(pos); - view = trim(view); - } -} - -void OverlayWindow::get_exe_path(HWND window, wchar_t* path) -{ - if (disabled_apps_array.empty()) - { - return; - } - - DWORD pid = 0; - GetWindowThreadProcessId(window, &pid); - if (pid != 0) - { - HANDLE processHandle = OpenProcess(PROCESS_QUERY_INFORMATION, FALSE, pid); - if (processHandle && GetProcessImageFileName(processHandle, path, MAX_PATH) > 0) - { - CloseHandle(processHandle); - } - } -} - -ShortcutGuideSettings OverlayWindow::GetSettings() noexcept -{ - ShortcutGuideSettings settings; - json::JsonObject properties; - try - { - PowerToysSettings::PowerToyValues settingsValues = - PowerToysSettings::PowerToyValues::load_from_settings_file(app_key); - - auto settingsObject = settingsValues.get_raw_json(); - if (!settingsObject.GetView().Size()) - { - return settings; - } - - properties = settingsObject.GetNamedObject(L"properties"); - } - catch (...) - { - Logger::warn("Failed to read settings. Use default settings"); - return settings; - } - - try - { - settings.hotkey = PowerToysSettings::HotkeyObject::from_json(properties.GetNamedObject(OpenShortcut::name)).to_string(); - } - catch (...) - { - } - - try - { - settings.overlayOpacity = static_cast(properties.GetNamedObject(OverlayOpacity::name).GetNamedNumber(L"value")); - } - catch (...) - { - } - - try - { - settings.shouldReactToPressedWinKey = properties.GetNamedObject(ShouldReactToPressedWinKey::name).GetNamedBoolean(L"value"); - } - catch (...) - { - } - - try - { - settings.windowsKeyPressTimeForGlobalWindowsShortcuts = static_cast(properties.GetNamedObject(WindowsKeyPressTimeForGlobalWindowsShortcuts::name).GetNamedNumber(L"value")); - } - catch (...) - { - } - - try - { - settings.windowsKeyPressTimeForTaskbarIconShortcuts = static_cast(properties.GetNamedObject(WindowsKeyPressTimeForTaskbarIconShortcuts::name).GetNamedNumber(L"value")); - } - catch (...) - { - } - - try - { - settings.theme = (std::wstring)properties.GetNamedObject(Theme::name).GetNamedString(L"value"); - } - catch (...) - { - } - - try - { - settings.disabledApps = (std::wstring)properties.GetNamedObject(DisabledApps::name).GetNamedString(L"value"); - } - catch (...) - { - } - - return settings; -} diff --git a/src/modules/ShortcutGuide/ShortcutGuide/shortcut_guide.h b/src/modules/ShortcutGuide/ShortcutGuide/shortcut_guide.h deleted file mode 100644 index 57f9bf326729..000000000000 --- a/src/modules/ShortcutGuide/ShortcutGuide/shortcut_guide.h +++ /dev/null @@ -1,106 +0,0 @@ -#pragma once -#include "../interface/powertoy_module_interface.h" -//#include -#include "overlay_window.h" -#include "native_event_waiter.h" -#include "ShortcutGuideSettings.h" -#include "ShortcutGuideConstants.h" - -#include "Generated Files/resource.h" - -// We support only one instance of the overlay -extern class OverlayWindow* overlay_window_instance; - -class TargetState; - -enum class HideWindowType -{ - ESC_PRESSED, - WIN_RELEASED, - WIN_SHORTCUT_PRESSED, - THE_SHORTCUT_PRESSED, - MOUSE_BUTTONUP -}; - -class OverlayWindow -{ -public: - OverlayWindow(HWND activeWindow); - void ShowWindow(); - void CloseWindow(HideWindowType type, int mainThreadId = 0); - bool IsDisabled(); - - void on_held(); - void quick_hide(); - void was_hidden(); - - bool overlay_visible() const; - bool win_key_activation() const; - - bool is_disabled_app(wchar_t* exePath); - - void get_exe_path(HWND window, wchar_t* exePath); - ~OverlayWindow(); - static ShortcutGuideSettings GetSettings() noexcept; -private: - std::wstring app_name; - //contains the non localized key of the powertoy - static inline std::wstring app_key = ShortcutGuideConstants::ModuleKey; - std::unique_ptr target_state; - std::unique_ptr winkey_popup; - std::unique_ptr event_waiter; - std::vector disabled_apps_array; - void init_settings(); - void update_disabled_apps(); - HWND activeWindow; - HHOOK keyboardHook; - HHOOK mouseHook; - - struct OverlayOpacity - { - static inline PCWSTR name = L"overlay_opacity"; - int value; - int resourceId = IDS_SETTING_DESCRIPTION_OVERLAY_OPACITY; - } overlayOpacity{}; - - struct Theme - { - static inline PCWSTR name = L"theme"; - std::wstring value; - int resourceId = IDS_SETTING_DESCRIPTION_THEME; - std::vector> keys_and_texts = { - { L"system", IDS_SETTING_DESCRIPTION_THEME_SYSTEM }, - { L"light", IDS_SETTING_DESCRIPTION_THEME_LIGHT }, - { L"dark", IDS_SETTING_DESCRIPTION_THEME_DARK } - }; - } theme; - - struct DisabledApps - { - static inline PCWSTR name = L"disabled_apps"; - std::wstring value; - } disabledApps; - - struct ShouldReactToPressedWinKey - { - static inline PCWSTR name = L"use_legacy_press_win_key_behavior"; - bool value; - } shouldReactToPressedWinKey; - - struct WindowsKeyPressTimeForGlobalWindowsShortcuts - { - static inline PCWSTR name = L"press_time"; - int value; - } windowsKeyPressTimeForGlobalWindowsShortcuts; - - struct WindowsKeyPressTimeForTaskbarIconShortcuts - { - static inline PCWSTR name = L"press_time_for_taskbar_icon_shortcuts"; - int value; - } windowsKeyPressTimeForTaskbarIconShortcuts; - - struct OpenShortcut - { - static inline PCWSTR name = L"open_shortcutguide"; - } openShortcut; -}; diff --git a/src/modules/ShortcutGuide/ShortcutGuide/start_visible.cpp b/src/modules/ShortcutGuide/ShortcutGuide/start_visible.cpp deleted file mode 100644 index 17efa76b7d68..000000000000 --- a/src/modules/ShortcutGuide/ShortcutGuide/start_visible.cpp +++ /dev/null @@ -1,24 +0,0 @@ -#include "pch.h" -#include "start_visible.h" - -bool is_start_visible() -{ - static const auto app_visibility = []() { - winrt::com_ptr result; - CoCreateInstance(CLSID_AppVisibility, - nullptr, - CLSCTX_INPROC_SERVER, - __uuidof(result), - result.put_void()); - return result; - }(); - - if (!app_visibility) - { - return false; - } - - BOOL visible; - auto result = app_visibility->IsLauncherVisible(&visible); - return SUCCEEDED(result) && visible; -} diff --git a/src/modules/ShortcutGuide/ShortcutGuide/start_visible.h b/src/modules/ShortcutGuide/ShortcutGuide/start_visible.h deleted file mode 100644 index a1431086a232..000000000000 --- a/src/modules/ShortcutGuide/ShortcutGuide/start_visible.h +++ /dev/null @@ -1,3 +0,0 @@ -#pragma once - -bool is_start_visible(); diff --git a/src/modules/ShortcutGuide/ShortcutGuide/target_state.cpp b/src/modules/ShortcutGuide/ShortcutGuide/target_state.cpp deleted file mode 100644 index 98fd9bb722b7..000000000000 --- a/src/modules/ShortcutGuide/ShortcutGuide/target_state.cpp +++ /dev/null @@ -1,47 +0,0 @@ -#include "pch.h" -#include "target_state.h" -#include "start_visible.h" -#include -#include - -constexpr unsigned VK_S = 0x53; - -void TargetState::was_hidden() -{ - std::unique_lock lock(mutex); - // Ignore callbacks from the D2DOverlayWindow - if (state == ForceShown) - { - return; - } - state = Hidden; - lock.unlock(); - cv.notify_one(); -} - -void TargetState::exit() -{ - std::unique_lock lock(mutex); - state = Exiting; - lock.unlock(); - cv.notify_one(); -} - -void TargetState::toggle_force_shown() -{ - std::unique_lock lock(mutex); - if (state != ForceShown) - { - state = ForceShown; - overlay_window_instance->on_held(); - } - else - { - state = Hidden; - } -} - -bool TargetState::active() const -{ - return state == ForceShown || state == Shown; -} diff --git a/src/modules/ShortcutGuide/ShortcutGuide/target_state.h b/src/modules/ShortcutGuide/ShortcutGuide/target_state.h deleted file mode 100644 index c1f51e3f604d..000000000000 --- a/src/modules/ShortcutGuide/ShortcutGuide/target_state.h +++ /dev/null @@ -1,33 +0,0 @@ -#pragma once -#include -#include -#include "shortcut_guide.h" - -struct KeyEvent -{ - bool key_down; - unsigned vk_code; -}; - -class TargetState -{ -public: - TargetState() = default; - void was_hidden(); - void exit(); - - void toggle_force_shown(); - bool active() const; - -private: - std::recursive_mutex mutex; - std::condition_variable_any cv; - enum State - { - Hidden, - Shown, - ForceShown, - Exiting - }; - std::atomic state = Hidden; -}; diff --git a/src/modules/ShortcutGuide/ShortcutGuide/tasklist_positions.cpp b/src/modules/ShortcutGuide/ShortcutGuide/tasklist_positions.cpp deleted file mode 100644 index 687fc86566e2..000000000000 --- a/src/modules/ShortcutGuide/ShortcutGuide/tasklist_positions.cpp +++ /dev/null @@ -1,116 +0,0 @@ -#include "pch.h" -#include "tasklist_positions.h" - -void Tasklist::update() -{ - // Get HWND of the tasklist - auto tasklist_hwnd = FindWindowA("Shell_TrayWnd", nullptr); - if (!tasklist_hwnd) - return; - tasklist_hwnd = FindWindowExA(tasklist_hwnd, 0, "ReBarWindow32", nullptr); - if (!tasklist_hwnd) - return; - tasklist_hwnd = FindWindowExA(tasklist_hwnd, 0, "MSTaskSwWClass", nullptr); - if (!tasklist_hwnd) - return; - tasklist_hwnd = FindWindowExA(tasklist_hwnd, 0, "MSTaskListWClass", nullptr); - if (!tasklist_hwnd) - return; - if (!automation) - { - winrt::check_hresult(CoCreateInstance(CLSID_CUIAutomation, - nullptr, - CLSCTX_INPROC_SERVER, - IID_IUIAutomation, - automation.put_void())); - winrt::check_hresult(automation->CreateTrueCondition(true_condition.put())); - } - element = nullptr; - winrt::check_hresult(automation->ElementFromHandle(tasklist_hwnd, element.put())); -} - -bool Tasklist::update_buttons(std::vector& buttons) -{ - if (!automation || !element) - { - return false; - } - winrt::com_ptr elements; - if (element->FindAll(TreeScope_Children, true_condition.get(), elements.put()) < 0) - return false; - if (!elements) - return false; - int count; - if (elements->get_Length(&count) < 0) - return false; - winrt::com_ptr child; - std::vector found_buttons; - found_buttons.reserve(count); - for (int i = 0; i < count; ++i) - { - child = nullptr; - if (elements->GetElement(i, child.put()) < 0) - return false; - TasklistButton button; - if (VARIANT var_rect; child->GetCurrentPropertyValue(UIA_BoundingRectanglePropertyId, &var_rect) >= 0) - { - if (var_rect.vt == (VT_R8 | VT_ARRAY)) - { - LONG pos; - double value; - pos = 0; - SafeArrayGetElement(var_rect.parray, &pos, &value); - button.x = static_cast(value); - pos = 1; - SafeArrayGetElement(var_rect.parray, &pos, &value); - button.y = static_cast(value); - pos = 2; - SafeArrayGetElement(var_rect.parray, &pos, &value); - button.width = static_cast(value); - pos = 3; - SafeArrayGetElement(var_rect.parray, &pos, &value); - button.height = static_cast(value); - } - VariantClear(&var_rect); - } - else - { - return false; - } - if (BSTR automation_id; child->get_CurrentAutomationId(&automation_id) >= 0) - { - button.name = automation_id; - SysFreeString(automation_id); - } - found_buttons.push_back(button); - } - // assign keynums - buttons.clear(); - for (auto& button : found_buttons) - { - if (buttons.empty()) - { - button.keynum = 1; - buttons.push_back(std::move(button)); - } - else - { - if (button.x < buttons.back().x || button.y < buttons.back().y) // skip 2nd row - break; - if (button.name == buttons.back().name) - continue; // skip buttons from the same app - button.keynum = buttons.back().keynum + 1; - buttons.push_back(std::move(button)); - if (buttons.back().keynum == 10) - break; // no more than 10 buttons - } - } - return true; -} - -std::vector Tasklist::get_buttons() -{ - std::vector buttons; - update_buttons(buttons); - return buttons; -} \ No newline at end of file diff --git a/src/modules/ShortcutGuide/ShortcutGuide/tasklist_positions.h b/src/modules/ShortcutGuide/ShortcutGuide/tasklist_positions.h deleted file mode 100644 index 4ac5bed8a93b..000000000000 --- a/src/modules/ShortcutGuide/ShortcutGuide/tasklist_positions.h +++ /dev/null @@ -1,29 +0,0 @@ -#pragma once -#include -#include -#include -#include -#include - -struct TasklistButton -{ - std::wstring name; - long x{}; - long y{}; - long width{}; - long height{}; - long keynum{}; -}; - -class Tasklist -{ -public: - void update(); - std::vector get_buttons(); - bool update_buttons(std::vector& buttons); - -private: - winrt::com_ptr automation; - winrt::com_ptr element; - winrt::com_ptr true_condition; -}; diff --git a/src/modules/ShortcutGuide/ShortcutGuide/trace.cpp b/src/modules/ShortcutGuide/ShortcutGuide/trace.cpp deleted file mode 100644 index f2a414d6ac5b..000000000000 --- a/src/modules/ShortcutGuide/ShortcutGuide/trace.cpp +++ /dev/null @@ -1,40 +0,0 @@ -#include "pch.h" -#include "trace.h" - -#include - -TRACELOGGING_DEFINE_PROVIDER( - g_hProvider, - "Microsoft.PowerToys", - // {38e8889b-9731-53f5-e901-e8a7c1753074} - (0x38e8889b, 0x9731, 0x53f5, 0xe9, 0x01, 0xe8, 0xa7, 0xc1, 0x75, 0x30, 0x74), - TraceLoggingOptionProjectTelemetry()); - -void Trace::SendGuideSession(const __int64 duration_ms, const wchar_t* close_type) noexcept -{ - TraceLoggingWriteWrapper( - g_hProvider, - "ShortcutGuide_GuideSession", - TraceLoggingInt64(duration_ms, "DurationInMs"), - TraceLoggingWideString(close_type, "CloseType"), - ProjectTelemetryPrivacyDataTag(ProjectTelemetryTag_ProductAndServicePerformance), - TraceLoggingBoolean(TRUE, "UTCReplace_AppSessionGuid"), - TraceLoggingKeyword(PROJECT_KEYWORD_MEASURE)); -} - -void Trace::SendSettings(ShortcutGuideSettings settings) noexcept -{ - TraceLoggingWriteWrapper( - g_hProvider, - "ShortcutGuide_Settings", - TraceLoggingWideString(settings.hotkey.c_str(), "Hotkey"), - TraceLoggingInt32(settings.overlayOpacity, "OverlayOpacity"), - TraceLoggingWideString(settings.theme.c_str(), "Theme"), - TraceLoggingWideString(settings.disabledApps.c_str(), "DisabledApps"), - TraceLoggingBoolean(settings.shouldReactToPressedWinKey, "ShouldReactToPressedWinKey"), - TraceLoggingInt32(settings.windowsKeyPressTimeForGlobalWindowsShortcuts, "WindowsKeyPressTimeForGlobalWindowsShortcuts"), - TraceLoggingInt32(settings.windowsKeyPressTimeForTaskbarIconShortcuts, "WindowsKeyPressTimeForTaskbarIconShortcuts"), - ProjectTelemetryPrivacyDataTag(ProjectTelemetryTag_ProductAndServicePerformance), - TraceLoggingBoolean(TRUE, "UTCReplace_AppSessionGuid"), - TraceLoggingKeyword(PROJECT_KEYWORD_MEASURE)); -} diff --git a/src/modules/ShortcutGuide/ShortcutGuide/trace.h b/src/modules/ShortcutGuide/ShortcutGuide/trace.h deleted file mode 100644 index a3446a857078..000000000000 --- a/src/modules/ShortcutGuide/ShortcutGuide/trace.h +++ /dev/null @@ -1,11 +0,0 @@ -#pragma once -#include "ShortcutGuideSettings.h" - -#include - -class Trace : public telemetry::TraceBase -{ -public: - static void SendGuideSession(const __int64 duration_ms, const wchar_t* close_type) noexcept; - static void SendSettings(ShortcutGuideSettings settings) noexcept; -}; diff --git a/src/modules/ShortcutGuide/ShortcutGuideModuleInterface/ShortcutGuideModuleInterface.vcxproj b/src/modules/ShortcutGuide/ShortcutGuideModuleInterface/ShortcutGuideModuleInterface.vcxproj index e24c0db76d78..76a3e640a2a2 100644 --- a/src/modules/ShortcutGuide/ShortcutGuideModuleInterface/ShortcutGuideModuleInterface.vcxproj +++ b/src/modules/ShortcutGuide/ShortcutGuideModuleInterface/ShortcutGuideModuleInterface.vcxproj @@ -8,9 +8,10 @@ 15.0 Win32Proj - {2d604c07-51fc-46bb-9eb7-75aecc7f5e81} + {E487304A-B1FB-4E6B-8E70-014051AF5B99} ShortcutGuideModuleInterface ShortcutGuideModuleInterface + 10.0.26100.0 DynamicLibrary @@ -37,7 +38,7 @@ - $(RepoRoot)$(Platform)\$(Configuration)\ + $(RepoRoot)$(Platform)\$(Configuration)\WinUI3Apps\ PowerToys.ShortcutGuideModuleInterface diff --git a/src/modules/ShortcutGuide/ShortcutGuideModuleInterface/dllmain.cpp b/src/modules/ShortcutGuide/ShortcutGuideModuleInterface/dllmain.cpp index a870fb9ad876..1d35f91cd9b5 100644 --- a/src/modules/ShortcutGuide/ShortcutGuideModuleInterface/dllmain.cpp +++ b/src/modules/ShortcutGuide/ShortcutGuideModuleInterface/dllmain.cpp @@ -104,7 +104,10 @@ class ShortcutGuideModule : public PowertoyModuleIface if (_enabled) { _enabled = false; - TerminateProcess(); + if (IsProcessActive()) + { + TerminateProcess(m_hProcess, 0); + } } else { @@ -131,10 +134,6 @@ class ShortcutGuideModule : public PowertoyModuleIface virtual std::optional GetHotkeyEx() override { Logger::trace("GetHotkeyEx()"); - if (m_shouldReactToPressedWinKey) - { - return std::nullopt; - } return m_hotkey; } @@ -148,7 +147,7 @@ class ShortcutGuideModule : public PowertoyModuleIface if (IsProcessActive()) { - TerminateProcess(); + TerminateProcess(m_hProcess, 0); return; } @@ -170,16 +169,6 @@ class ShortcutGuideModule : public PowertoyModuleIface } } - virtual bool keep_track_of_pressed_win_key() override - { - return m_shouldReactToPressedWinKey; - } - - virtual UINT milliseconds_win_key_must_be_pressed() override - { - return std::min(m_millisecondsWinKeyPressTimeForGlobalWindowsShortcuts, m_millisecondsWinKeyPressTimeForTaskbarIconShortcuts); - } - private: std::wstring app_name; //contains the non localized key of the powertoy @@ -193,7 +182,6 @@ class ShortcutGuideModule : public PowertoyModuleIface // If the module should be activated through the legacy pressing windows key behavior. const UINT DEFAULT_MILLISECONDS_WIN_KEY_PRESS_TIME_FOR_GLOBAL_WINDOWS_SHORTCUTS = 900; const UINT DEFAULT_MILLISECONDS_WIN_KEY_PRESS_TIME_FOR_TASKBAR_ICON_SHORTCUTS = 900; - bool m_shouldReactToPressedWinKey = false; UINT m_millisecondsWinKeyPressTimeForGlobalWindowsShortcuts = DEFAULT_MILLISECONDS_WIN_KEY_PRESS_TIME_FOR_GLOBAL_WINDOWS_SHORTCUTS; UINT m_millisecondsWinKeyPressTimeForTaskbarIconShortcuts = DEFAULT_MILLISECONDS_WIN_KEY_PRESS_TIME_FOR_TASKBAR_ICON_SHORTCUTS; @@ -219,7 +207,7 @@ class ShortcutGuideModule : public PowertoyModuleIface SHELLEXECUTEINFOW sei{ sizeof(sei) }; sei.fMask = { SEE_MASK_NOCLOSEPROCESS | SEE_MASK_FLAG_NO_UI }; - sei.lpFile = L"PowerToys.ShortcutGuide.exe"; + sei.lpFile = L"WinUI3Apps\\PowerToys.ShortcutGuide.exe"; sei.nShow = SW_SHOWNORMAL; sei.lpParameters = executable_args.data(); if (ShellExecuteExW(&sei) == false) @@ -239,33 +227,18 @@ class ShortcutGuideModule : public PowertoyModuleIface return true; } - void TerminateProcess() + bool IsProcessActive() { - if (m_hProcess) + if (!m_hProcess) { - if (WaitForSingleObject(m_hProcess, 0) != WAIT_OBJECT_0) - { - if (exitEvent && SetEvent(exitEvent)) - { - Logger::trace(L"Signaled {}", CommonSharedConstants::SHORTCUT_GUIDE_EXIT_EVENT); - } - else - { - Logger::warn(L"Failed to signal {}", CommonSharedConstants::SHORTCUT_GUIDE_EXIT_EVENT); - } - } - else - { - CloseHandle(m_hProcess); - m_hProcess = nullptr; - Logger::trace("SG process was already terminated"); - } + return false; } - } - - bool IsProcessActive() - { - return m_hProcess && WaitForSingleObject(m_hProcess, 0) != WAIT_OBJECT_0; + auto result = WaitForSingleObject(m_hProcess, 0); + if (result == WAIT_FAILED) + { + Logger::error("Failed to wait for SG process."); + } + return result == WAIT_TIMEOUT; } void InitSettings() @@ -289,10 +262,6 @@ class ShortcutGuideModule : public PowertoyModuleIface void ParseSettings(PowerToysSettings::PowerToyValues& settings) { - m_shouldReactToPressedWinKey = false; - m_millisecondsWinKeyPressTimeForGlobalWindowsShortcuts = DEFAULT_MILLISECONDS_WIN_KEY_PRESS_TIME_FOR_GLOBAL_WINDOWS_SHORTCUTS; - m_millisecondsWinKeyPressTimeForTaskbarIconShortcuts = DEFAULT_MILLISECONDS_WIN_KEY_PRESS_TIME_FOR_TASKBAR_ICON_SHORTCUTS; - auto settingsObject = settings.get_raw_json(); if (settingsObject.GetView().Size()) { @@ -328,36 +297,6 @@ class ShortcutGuideModule : public PowertoyModuleIface { Logger::warn("Failed to initialize Shortcut Guide start shortcut"); } - try - { - // Parse Legacy windows key press behavior settings - auto jsonUseLegacyWinKeyBehaviorObject = settingsObject.GetNamedObject(L"properties").GetNamedObject(L"use_legacy_press_win_key_behavior"); - m_shouldReactToPressedWinKey = jsonUseLegacyWinKeyBehaviorObject.GetNamedBoolean(L"value"); - auto jsonPressTimeForGlobalWindowsShortcutsObject = settingsObject.GetNamedObject(L"properties").GetNamedObject(L"press_time"); - auto jsonPressTimeForTaskbarIconShortcutsObject = settingsObject.GetNamedObject(L"properties").GetNamedObject(L"press_time_for_taskbar_icon_shortcuts"); - int value = static_cast(jsonPressTimeForGlobalWindowsShortcutsObject.GetNamedNumber(L"value")); - if (value >= 0) - { - m_millisecondsWinKeyPressTimeForGlobalWindowsShortcuts = value; - } - else - { - throw std::runtime_error("Invalid Press Time Windows Shortcuts value"); - } - value = static_cast(jsonPressTimeForTaskbarIconShortcutsObject.GetNamedNumber(L"value")); - if (value >= 0) - { - m_millisecondsWinKeyPressTimeForTaskbarIconShortcuts = value; - } - else - { - throw std::runtime_error("Invalid Press Time Taskbar Shortcuts value"); - } - } - catch (...) - { - Logger::warn("Failed to get legacy win key behavior settings"); - } } else { diff --git a/src/runner/main.cpp b/src/runner/main.cpp index 626bddc47f51..28129849119e 100644 --- a/src/runner/main.cpp +++ b/src/runner/main.cpp @@ -260,7 +260,7 @@ int runner(bool isProcessElevated, bool openSettings, std::string settingsWindow L"PowerToys.KeyboardManager.dll", L"PowerToys.Launcher.dll", L"WinUI3Apps/PowerToys.PowerRenameExt.dll", - L"PowerToys.ShortcutGuideModuleInterface.dll", + L"WinUI3Apps/PowerToys.ShortcutGuideModuleInterface.dll", L"PowerToys.ColorPicker.dll", L"PowerToys.AwakeModuleInterface.dll", L"PowerToys.FindMyMouse.dll", diff --git a/src/settings-ui/Settings.UI.Controls/QuickAccess/QuickAccessViewModel.cs b/src/settings-ui/Settings.UI.Controls/QuickAccess/QuickAccessViewModel.cs index e32ae0d4647e..bcf0a3c13648 100644 --- a/src/settings-ui/Settings.UI.Controls/QuickAccess/QuickAccessViewModel.cs +++ b/src/settings-ui/Settings.UI.Controls/QuickAccess/QuickAccessViewModel.cs @@ -168,17 +168,9 @@ private string GetModuleToolTip(ModuleType moduleType) ModuleType.PowerOCR => SettingsRepository.GetInstance(SettingsUtils.Default).SettingsConfig.Properties.ActivationShortcut.ToString(), ModuleType.Workspaces => SettingsRepository.GetInstance(SettingsUtils.Default).SettingsConfig.Properties.Hotkey.Value.ToString(), ModuleType.MeasureTool => SettingsRepository.GetInstance(SettingsUtils.Default).SettingsConfig.Properties.ActivationShortcut.ToString(), - ModuleType.ShortcutGuide => GetShortcutGuideToolTip(), + ModuleType.ShortcutGuide => SettingsRepository.GetInstance(SettingsUtils.Default).SettingsConfig.Properties.OpenShortcutGuide.ToString(), _ => string.Empty, }; } - - private string GetShortcutGuideToolTip() - { - var shortcutGuideSettings = SettingsRepository.GetInstance(SettingsUtils.Default).SettingsConfig; - return shortcutGuideSettings.Properties.UseLegacyPressWinKeyBehavior.Value - ? "Win" - : shortcutGuideSettings.Properties.OpenShortcutGuide.ToString(); - } } } diff --git a/src/settings-ui/Settings.UI.Library/ShortcutGuideProperties.cs b/src/settings-ui/Settings.UI.Library/ShortcutGuideProperties.cs index d34a2f748a09..1895b47a9a17 100644 --- a/src/settings-ui/Settings.UI.Library/ShortcutGuideProperties.cs +++ b/src/settings-ui/Settings.UI.Library/ShortcutGuideProperties.cs @@ -15,34 +15,22 @@ public class ShortcutGuideProperties public ShortcutGuideProperties() { - OverlayOpacity = new IntProperty(90); - UseLegacyPressWinKeyBehavior = new BoolProperty(false); - PressTimeForGlobalWindowsShortcuts = new IntProperty(900); - PressTimeForTaskbarIconShortcuts = new IntProperty(900); Theme = new StringProperty("system"); DisabledApps = new StringProperty(); OpenShortcutGuide = DefaultOpenShortcutGuide; + FirstRun = new BoolProperty(true); } [JsonPropertyName("open_shortcutguide")] public HotkeySettings OpenShortcutGuide { get; set; } - [JsonPropertyName("overlay_opacity")] - public IntProperty OverlayOpacity { get; set; } - - [JsonPropertyName("use_legacy_press_win_key_behavior")] - public BoolProperty UseLegacyPressWinKeyBehavior { get; set; } - - [JsonPropertyName("press_time")] - public IntProperty PressTimeForGlobalWindowsShortcuts { get; set; } - - [JsonPropertyName("press_time_for_taskbar_icon_shortcuts")] - public IntProperty PressTimeForTaskbarIconShortcuts { get; set; } - [JsonPropertyName("theme")] public StringProperty Theme { get; set; } [JsonPropertyName("disabled_apps")] public StringProperty DisabledApps { get; set; } + + [JsonPropertyName("first_run")] + public BoolProperty FirstRun { get; set; } } } diff --git a/src/settings-ui/Settings.UI.Library/ShortcutGuideSettings.cs b/src/settings-ui/Settings.UI.Library/ShortcutGuideSettings.cs index 40174aeb81d8..89a2c26f9b2b 100644 --- a/src/settings-ui/Settings.UI.Library/ShortcutGuideSettings.cs +++ b/src/settings-ui/Settings.UI.Library/ShortcutGuideSettings.cs @@ -21,7 +21,7 @@ public ShortcutGuideSettings() { Name = ModuleName; Properties = new ShortcutGuideProperties(); - Version = "1.0"; + Version = "2.0"; } public string GetModuleName() diff --git a/src/settings-ui/Settings.UI.UnitTests/ViewModelTests/ShortcutGuide.cs b/src/settings-ui/Settings.UI.UnitTests/ViewModelTests/ShortcutGuide.cs index 6ebab902e7d9..11f7a266eebd 100644 --- a/src/settings-ui/Settings.UI.UnitTests/ViewModelTests/ShortcutGuide.cs +++ b/src/settings-ui/Settings.UI.UnitTests/ViewModelTests/ShortcutGuide.cs @@ -48,7 +48,6 @@ public void OriginalFilesModificationTest(string version, string fileName) // Verify that the old settings persisted Assert.AreEqual(originalGeneralSettings.Enabled.ShortcutGuide, viewModel.IsEnabled); - Assert.AreEqual(originalSettings.Properties.OverlayOpacity.Value, viewModel.OverlayOpacity); // Verify that the stub file was used var expectedCallCount = 2; // once via the view model, and once by the test (GetSettings) @@ -105,21 +104,5 @@ public void ThemeIndexShouldSetThemeToDarkWhenSuccessful() Func isDark = s => JsonSerializer.Deserialize(s).Properties.Theme.Value == "dark"; settingsUtilsMock.Verify(x => x.SaveSettings(It.Is(y => isDark(y)), It.IsAny(), It.IsAny()), Times.Once); } - - [TestMethod] - public void OverlayOpacityShouldSeOverlayOpacityToOneHundredWhenSuccessful() - { - // Arrange - var settingsUtilsMock = new Mock(new FileSystem(), null); - ShortcutGuideViewModel viewModel = new ShortcutGuideViewModel(settingsUtilsMock.Object, SettingsRepository.GetInstance(mockGeneralSettingsUtils.Object), SettingsRepository.GetInstance(mockShortcutGuideSettingsUtils.Object), msg => { return 0; }, ShortCutGuideTestFolderName); - Assert.AreEqual(90, viewModel.OverlayOpacity); - - // Act - viewModel.OverlayOpacity = 100; - - // Assert - Func equal100 = s => JsonSerializer.Deserialize(s).Properties.OverlayOpacity.Value == 100; - settingsUtilsMock.Verify(x => x.SaveSettings(It.Is(y => equal100(y)), It.IsAny(), It.IsAny()), Times.Once); - } } } diff --git a/src/settings-ui/Settings.UI/Assets/Settings/Modules/OOBE/ShortcutGuide.png b/src/settings-ui/Settings.UI/Assets/Settings/Modules/OOBE/ShortcutGuide.png index ccef1f4c312e..0af878b776b5 100644 Binary files a/src/settings-ui/Settings.UI/Assets/Settings/Modules/OOBE/ShortcutGuide.png and b/src/settings-ui/Settings.UI/Assets/Settings/Modules/OOBE/ShortcutGuide.png differ diff --git a/src/settings-ui/Settings.UI/Assets/Settings/Modules/ShortcutGuide.png b/src/settings-ui/Settings.UI/Assets/Settings/Modules/ShortcutGuide.png index 85610c419044..0af878b776b5 100644 Binary files a/src/settings-ui/Settings.UI/Assets/Settings/Modules/ShortcutGuide.png and b/src/settings-ui/Settings.UI/Assets/Settings/Modules/ShortcutGuide.png differ diff --git a/src/settings-ui/Settings.UI/PowerToys.Settings.csproj b/src/settings-ui/Settings.UI/PowerToys.Settings.csproj index e726063b819e..177e2b15e43a 100644 --- a/src/settings-ui/Settings.UI/PowerToys.Settings.csproj +++ b/src/settings-ui/Settings.UI/PowerToys.Settings.csproj @@ -19,6 +19,7 @@ PowerToys.Settings.pri + diff --git a/src/settings-ui/Settings.UI/SettingsXAML/OOBE/Views/OobeShortcutGuide.xaml.cs b/src/settings-ui/Settings.UI/SettingsXAML/OOBE/Views/OobeShortcutGuide.xaml.cs index f979ddb6a48d..0f3dcce35f9c 100644 --- a/src/settings-ui/Settings.UI/SettingsXAML/OOBE/Views/OobeShortcutGuide.xaml.cs +++ b/src/settings-ui/Settings.UI/SettingsXAML/OOBE/Views/OobeShortcutGuide.xaml.cs @@ -29,7 +29,7 @@ public OobeShortcutGuide() private void Start_ShortcutGuide_Click(object sender, Microsoft.UI.Xaml.RoutedEventArgs e) { - var executablePath = Path.Combine(System.AppDomain.CurrentDomain.BaseDirectory, "..", @"PowerToys.ShortcutGuide.exe"); + var executablePath = Path.Combine(System.AppDomain.CurrentDomain.BaseDirectory, "..", @"WinUI3Apps//PowerToys.ShortcutGuide.exe"); var id = System.Environment.ProcessId.ToString(CultureInfo.InvariantCulture); var p = Process.Start(executablePath, id); if (p != null) @@ -55,14 +55,7 @@ protected override void OnNavigatedTo(NavigationEventArgs e) ViewModel.LogOpeningModuleEvent(); var settingsProperties = SettingsRepository.GetInstance(SettingsUtils.Default).SettingsConfig.Properties; - if ((bool)settingsProperties.UseLegacyPressWinKeyBehavior.Value) - { - HotkeyControl.Keys = new List { 92 }; - } - else - { - HotkeyControl.Keys = settingsProperties.OpenShortcutGuide.GetKeysList(); - } + HotkeyControl.Keys = settingsProperties.OpenShortcutGuide.GetKeysList(); // Disable the Launch button if the module is disabled var generalSettings = SettingsRepository.GetInstance(SettingsUtils.Default).SettingsConfig; diff --git a/src/settings-ui/Settings.UI/SettingsXAML/Views/ShellPage.xaml b/src/settings-ui/Settings.UI/SettingsXAML/Views/ShellPage.xaml index 0cfb826fcf51..3f01f5960d71 100644 --- a/src/settings-ui/Settings.UI/SettingsXAML/Views/ShellPage.xaml +++ b/src/settings-ui/Settings.UI/SettingsXAML/Views/ShellPage.xaml @@ -238,7 +238,11 @@ x:Uid="Shell_ShortcutGuide" helpers:NavHelper.NavigateTo="views:ShortcutGuidePage" AutomationProperties.AutomationId="ShortcutGuideNavItem" - Icon="{ui:BitmapIcon Source=/Assets/Settings/Icons/ShortcutGuide.png}" /> + Icon="{ui:BitmapIcon Source=/Assets/Settings/Icons/ShortcutGuide.png}"> + + + + + + + diff --git a/src/settings-ui/Settings.UI/SettingsXAML/Views/ShortcutGuidePage.xaml b/src/settings-ui/Settings.UI/SettingsXAML/Views/ShortcutGuidePage.xaml index 488c80823047..0664da3a87a4 100644 --- a/src/settings-ui/Settings.UI/SettingsXAML/Views/ShortcutGuidePage.xaml +++ b/src/settings-ui/Settings.UI/SettingsXAML/Views/ShortcutGuidePage.xaml @@ -23,63 +23,13 @@ - - - - - - - - - - - - - + + - - - - - - - + @@ -89,14 +39,6 @@ - - - - @@ -126,5 +68,8 @@ + + + \ No newline at end of file diff --git a/src/settings-ui/Settings.UI/Strings/en-us/Resources.resw b/src/settings-ui/Settings.UI/Strings/en-us/Resources.resw index 72d31fa4cd75..6396496c8a0c 100644 --- a/src/settings-ui/Settings.UI/Strings/en-us/Resources.resw +++ b/src/settings-ui/Settings.UI/Strings/en-us/Resources.resw @@ -503,7 +503,7 @@ opera.exe Product name: Navigation view item name for PowerRename - Shortcut Guide + Shortcut Guide V2 Product name: Navigation view item name for Shortcut Guide @@ -1125,17 +1125,13 @@ opera.exe Inactive color - Shows a help overlay with Windows shortcuts. - - - Press duration before showing global Windows shortcuts (ms) - ms = milliseconds + Shows a help overlay with shortcuts for your apps and Windows. Activation method - Use a shortcut or press the Windows key for some time to activate + Use a shortcut to activate Shortcut Guide Custom shortcut @@ -1162,9 +1158,6 @@ opera.exe Shortcut Guide do not loc the Product name. Do you want this feature on / off - - Background opacity (%) - Exclude apps @@ -1366,7 +1359,7 @@ opera.exe do not loc the product name - Shortcut Guide + Shortcut Guide V2 GitHub repository @@ -2034,7 +2027,13 @@ Made with 💗 by Microsoft and the PowerToys community. Screen Ruler is a quick and easy way to measure pixels on your screen. - Shortcut Guide presents the user with a listing of available shortcuts for the current state of the desktop. + The new Shortcut Guide displays keyboard shortcuts for your apps and for the Windows environment. The following apps are included by default (with more to come in the future): +The Microsoft Windows Operating System +Windows Explorer +Notepad +Microsoft PowerToys + +The shortcuts always correspond to the most current application/Windows version. A collection of utilities to enhance your mouse. @@ -2196,7 +2195,7 @@ From there, simply click on one of the supported files in the File Explorer and Do not localize this string - Shortcut Guide + Shortcut Guide V2 Do not localize this string @@ -3762,10 +3761,6 @@ Activate by holding the key for the character you want to add an accent to, then Open as administrator - - Press duration before showing taskbar icon shortcuts (ms) - ms = milliseconds - A Windows shell extension to find out which processes are using the selected files and directories. {Locked="Windows"} diff --git a/src/settings-ui/Settings.UI/ViewModels/DashboardViewModel.cs b/src/settings-ui/Settings.UI/ViewModels/DashboardViewModel.cs index a64e25c25d9e..16c60ccd4856 100644 --- a/src/settings-ui/Settings.UI/ViewModels/DashboardViewModel.cs +++ b/src/settings-ui/Settings.UI/ViewModels/DashboardViewModel.cs @@ -227,7 +227,7 @@ private void BuildModuleList() IsEnabled = gpo == GpoRuleConfigured.Enabled || (gpo != GpoRuleConfigured.Disabled && ModuleHelper.GetIsModuleEnabled(generalSettingsConfig, moduleType)), IsLocked = gpo == GpoRuleConfigured.Enabled || gpo == GpoRuleConfigured.Disabled, Icon = ModuleHelper.GetModuleTypeFluentIconName(moduleType), - IsNew = false, + IsNew = moduleType == ModuleType.ShortcutGuide, DashboardModuleItems = GetModuleItems(moduleType), ClickCommand = new RelayCommand(DashboardListItemClick), }; @@ -779,9 +779,7 @@ private ObservableCollection GetModuleItemsShortcutGuide() { ISettingsRepository moduleSettingsRepository = SettingsRepository.GetInstance(SettingsUtils.Default); - var shortcut = moduleSettingsRepository.SettingsConfig.Properties.UseLegacyPressWinKeyBehavior.Value - ? new List { 92 } // Right Windows key code - : moduleSettingsRepository.SettingsConfig.Properties.OpenShortcutGuide.GetKeysList(); + var shortcut = moduleSettingsRepository.SettingsConfig.Properties.OpenShortcutGuide.GetKeysList(); var list = new List { diff --git a/src/settings-ui/Settings.UI/ViewModels/ShortcutGuideViewModel.cs b/src/settings-ui/Settings.UI/ViewModels/ShortcutGuideViewModel.cs index 8c91cb477959..460cc7784b9a 100644 --- a/src/settings-ui/Settings.UI/ViewModels/ShortcutGuideViewModel.cs +++ b/src/settings-ui/Settings.UI/ViewModels/ShortcutGuideViewModel.cs @@ -54,10 +54,6 @@ public ShortcutGuideViewModel(SettingsUtils settingsUtils, ISettingsRepository GetAllHotkeySettings() private bool _enabledStateIsGPOConfigured; private bool _isEnabled; private int _themeIndex; - private bool _useLegacyPressWinKeyBehavior; - private int _pressTimeForGlobalWindowsShortcuts; - private int _pressTimeForTaskbarIconShortcuts; - private int _opacity; public bool IsEnabled { @@ -177,78 +169,6 @@ public int ThemeIndex } } - public int OverlayOpacity - { - get - { - return _opacity; - } - - set - { - if (_opacity != value) - { - _opacity = value; - Settings.Properties.OverlayOpacity.Value = value; - NotifyPropertyChanged(); - } - } - } - - public bool UseLegacyPressWinKeyBehavior - { - get - { - return _useLegacyPressWinKeyBehavior; - } - - set - { - if (_useLegacyPressWinKeyBehavior != value) - { - _useLegacyPressWinKeyBehavior = value; - Settings.Properties.UseLegacyPressWinKeyBehavior.Value = value; - NotifyPropertyChanged(); - } - } - } - - public int PressTime - { - get - { - return _pressTimeForGlobalWindowsShortcuts; - } - - set - { - if (_pressTimeForGlobalWindowsShortcuts != value) - { - _pressTimeForGlobalWindowsShortcuts = value; - Settings.Properties.PressTimeForGlobalWindowsShortcuts.Value = value; - NotifyPropertyChanged(); - } - } - } - - public int DelayTime - { - get - { - return _pressTimeForTaskbarIconShortcuts; - } - - set - { - if (_pressTimeForTaskbarIconShortcuts != value) - { - _pressTimeForTaskbarIconShortcuts = value; - Settings.Properties.PressTimeForTaskbarIconShortcuts.Value = value; - NotifyPropertyChanged(); - } - } - } - public string DisabledApps { get