diff --git a/.github/actions/spell-check/expect.txt b/.github/actions/spell-check/expect.txt
index d4a0a391bb..9b08f419a3 100644
--- a/.github/actions/spell-check/expect.txt
+++ b/.github/actions/spell-check/expect.txt
@@ -388,7 +388,9 @@ DEFAULTFLAGS
DEFAULTICON
defaultlib
DEFAULTONLY
+DEFAULTSIZE
DEFAULTTONEAREST
+Defaulttonearest
DEFAULTTONULL
DEFAULTTOPRIMARY
DEFERERASE
@@ -884,9 +886,11 @@ IUI
IVO
IUWP
IWIC
+jeli
jfif
jgeosdfsdsgmkedfgdfgdfgbkmhcgcflmi
jjw
+JOBOBJECT
jobject
jpe
jpnime
@@ -922,6 +926,7 @@ lastcodeanalysissucceeded
LASTEXITCODE
LAYOUTRTL
lbl
+Lbuttondown
LCh
lcid
LCIDTo
@@ -1047,6 +1052,7 @@ maxversiontested
mber
MBM
MBR
+Mbuttondown
MDICHILD
MDL
mdtext
@@ -1522,6 +1528,7 @@ RAWINPUTHEADER
RAWMODE
RAWPATH
rbhid
+Rbuttondown
rclsid
RCZOOMIT
rdp
@@ -1558,6 +1565,7 @@ remoteip
Removelnk
renamable
RENAMEONCOLLISION
+RENDERFULLCONTENT
Reparent
reparented
reparenting
@@ -1827,6 +1835,8 @@ SVGIO
svgz
SVSI
SWFO
+SWP
+Swp
swp
SWPNOSIZE
SWPNOZORDER
@@ -1895,7 +1905,6 @@ TILEDWINDOW
TILLSON
timedate
timediff
-timeunion
timeutil
TITLEBARINFO
Titlecase
@@ -2185,6 +2194,7 @@ Wwanpp
xap
XAxis
XButton
+Xbuttondown
xclip
xcopy
XDeployment
diff --git a/.github/actions/spell-check/patterns.txt b/.github/actions/spell-check/patterns.txt
index 8c59d38c47..34b2ad9fe9 100644
--- a/.github/actions/spell-check/patterns.txt
+++ b/.github/actions/spell-check/patterns.txt
@@ -286,3 +286,6 @@ St&yle
# 2D Region struct names
\bDisplayConfig2?D?Region\b
+
+# Microsoft Store URLs and product IDs
+ms-windows-store://\S+
diff --git a/.pipelines/ESRPSigning_core.json b/.pipelines/ESRPSigning_core.json
index b0c2766e84..dfb259fd8f 100644
--- a/.pipelines/ESRPSigning_core.json
+++ b/.pipelines/ESRPSigning_core.json
@@ -117,6 +117,7 @@
"WinUI3Apps\\PowerToys.FileLocksmithUI.dll",
"WinUI3Apps\\PowerToys.FileLocksmithContextMenu.dll",
"FileLocksmithContextMenuPackage.msix",
+ "FileLocksmithCLI.exe",
"WinUI3Apps\\Peek.Common.dll",
"WinUI3Apps\\Peek.FilePreviewer.dll",
diff --git a/.pipelines/v2/templates/job-build-ui-tests.yml b/.pipelines/v2/templates/job-build-ui-tests.yml
index 342750d51c..346248b80a 100644
--- a/.pipelines/v2/templates/job-build-ui-tests.yml
+++ b/.pipelines/v2/templates/job-build-ui-tests.yml
@@ -68,14 +68,13 @@ jobs:
- template: .\steps-restore-nuget.yml
- - task: NuGetCommand@2
+ - task: MSBuild@1
displayName: Restore solution-level NuGet packages
inputs:
- command: restore
- feedsToUse: config
- configPath: nuget.config
- restoreSolution: PowerToys.slnx
- restoreDirectory: '$(Build.SourcesDirectory)\packages'
+ solution: PowerToys.slnx
+ msbuildArguments: '/t:restore /p:RestorePackagesConfig=true'
+ platform: $(BuildPlatform)
+ configuration: $(BuildConfiguration)
# Build all UI test projects if no specific modules are specified
- ${{ if eq(length(parameters.uiTestModules), 0) }}:
diff --git a/COMMUNITY.md b/COMMUNITY.md
index c18bacc8c9..dbbc413f68 100644
--- a/COMMUNITY.md
+++ b/COMMUNITY.md
@@ -6,9 +6,6 @@ Names are in alphabetical order based on first name.
## High impact community members
-### [@Noraa-Junker](https://github.com/Noraa-Junker) - [Noraa Junker](https://noraajunker.ch)
-Noraa has helped triaging, discussing, and creating a substantial number of issues and contributed features/fixes. Noraa was the primary person for helping build the File Explorer preview pane handler for developer files.
-
### [@cgaarden](https://github.com/cgaarden) - [Christian Gaarden Gaardmark](https://www.onegreatworld.com)
Christian contributed New+ utility
@@ -42,6 +39,12 @@ Jay has helped triaging, discussing, creating a substantial number of issues and
### [@jefflord](https://github.com/Jjefflord) - Jeff Lord
Jeff added in multiple new features into Keyboard manager, such as key chord support and launching apps. He also contributed multiple features/fixes to PowerToys.
+### [@snickler](https://github.com/snickler) - [Jeremy Sinclair](http://sinclairinat0r.com)
+Jeremy has helped drive large sums of the ARM64 support inside PowerToys
+
+### [@jiripolasek](https://github.com/jiripolasek) - [Jiřà Polášek](https://github.com/jiripolasek)
+Jiřà has contributed a massive number of features and improvements to Command Palette, including drag & drop support, custom themes, Web Search enhancements, Remote Desktop extension fixes, and many UX improvements.
+
### [@TheJoeFin](https://github.com/TheJoeFin) - [Joe Finney](https://joefinapps.com)
Joe has helped triaging, discussing, issues as well as fixing bugs and building features for Text Extractor.
@@ -57,6 +60,9 @@ Color Picker is from Martin.
### [@mikeclayton](https://github.com/mikeclayton) - [Michael Clayton](https://michael-clayton.com)
Michael contributed the [initial version](https://github.com/microsoft/PowerToys/issues/23216) of the Mouse Jump tool and [a number of updates](https://github.com/microsoft/PowerToys/pulls?q=is%3Apr+author%3Amikeclayton) based on his FancyMouse utility.
+### [@Noraa-Junker](https://github.com/Noraa-Junker) - [Noraa Junker](https://noraajunker.ch)
+Noraa has helped triaging, discussing, and creating a substantial number of issues and contributed features/fixes. Noraa was the primary person for helping build the File Explorer preview pane handler for developer files.
+
### [@pedrolamas](https://github.com/pedrolamas/) - Pedro Lamas
Pedro helped create the thumbnail and File Explorer previewers for 3D files like STL and GCode. If you like 3D printing, these are very helpful.
@@ -69,15 +75,12 @@ Rafael has helped do the [upgrade from CppWinRT 1.x to 2.0](https://github.com/m
### [@royvou](https://github.com/royvou)
Roy has helped out contributing multiple features to PowerToys Run
-### [@snickler](https://github.com/snickler) - [Jeremy Sinclair](http://sinclairinat0r.com)
-Jeremy has helped drive large sums of the ARM64 support inside PowerToys
+### [@ThiefZero](https://github.com/ThiefZero)
+ThiefZero has helped out contributing a features to PowerToys Run such as the unit converter plugin
### [@TobiasSekan](https://github.com/TobiasSekan) - Tobias Sekan
Tobias Sekan has helped out contributing features to PowerToys Run such as Settings plugin, Registry plugin
-### [@ThiefZero](https://github.com/ThiefZero)
-ThiefZero has helped out contributing a features to PowerToys Run such as the unit converter plugin
-
## Open source projects
As PowerToys creates new utilities, some will be based off existing technology. We'll continue to do our best to contribute back to these projects but their efforts were the base of some of our projects. We want to be sure their work is directly recognized.
@@ -187,18 +190,10 @@ ZoomIt source code was originally implemented by [Sysinternals](https://sysinter
- [@niels9001](https://github.com/niels9001/) - Niels Laute - Product Manager
- [@dhowett](https://github.com/dhowett) - Dustin Howett - Dev Lead
- [@yeelam-gordon](https://github.com/yeelam-gordon) - Gordon Lam - Dev Lead
-- [@jamrobot](https://github.com/jamrobot) - Jerry Xu - Dev Lead
- [@lei9444](https://github.com/lei9444) - Leilei Zhang - Dev
- [@shuaiyuanxx](https://github.com/shuaiyuanxx) - Shawn Yuan - Dev
- [@moooyo](https://github.com/moooyo) - Yu Leng - Dev
- [@haoliuu](https://github.com/haoliuu) - Hao Liu - Dev
-- [@chenmy77](https://github.com/chenmy77) - Mengyuan Chen - Dev
-- [@chemwolf6922](https://github.com/chemwolf6922) - Feng Wang - Dev
-- [@yaqingmi](https://github.com/yaqingmi) - Yaqing Mi - Dev
-- [@zhaoqpcn](https://github.com/zhaoqpcn) - Qingpeng Zhao - Dev
-- [@urnotdfs](https://github.com/urnotdfs) - Xiaofeng Wang - Dev
-- [@zhaopy536](https://github.com/zhaopy536) - Peiyao Zhao - Dev
-- [@wang563681252](https://github.com/wang563681252) - Zhaopeng Wang - Dev
- [@vanzue](https://github.com/vanzue) - Kai Tao - Dev
- [@zadjii-msft](https://github.com/zadjii-msft) - Mike Griese - Dev
- [@khmyznikov](https://github.com/khmyznikov) - Gleb Khmyznikov - Dev
@@ -229,3 +224,12 @@ ZoomIt source code was originally implemented by [Sysinternals](https://sysinter
- [@SeraphimaZykova](https://github.com/SeraphimaZykova) - Seraphima Zykova - Dev
- [@stefansjfw](https://github.com/stefansjfw) - Stefan Markovic - Dev
- [@jaimecbernardo](https://github.com/jaimecbernardo) - Jaime Bernardo - Dev Lead
+- [@haoliuu](https://github.com/haoliuu) - Hao Liu - Dev
+- [@chenmy77](https://github.com/chenmy77) - Mengyuan Chen - Dev
+- [@chemwolf6922](https://github.com/chemwolf6922) - Feng Wang - Dev
+- [@yaqingmi](https://github.com/yaqingmi) - Yaqing Mi - Dev
+- [@zhaoqpcn](https://github.com/zhaoqpcn) - Qingpeng Zhao - Dev
+- [@urnotdfs](https://github.com/urnotdfs) - Xiaofeng Wang - Dev
+- [@zhaopy536](https://github.com/zhaopy536) - Peiyao Zhao - Dev
+- [@wang563681252](https://github.com/wang563681252) - Zhaopeng Wang - Dev
+- [@jamrobot](https://github.com/jamrobot) - Jerry Xu - Dev Lead
diff --git a/DATA_AND_PRIVACY.md b/DATA_AND_PRIVACY.md
index 66a0daa1d8..07089e44c3 100644
--- a/DATA_AND_PRIVACY.md
+++ b/DATA_AND_PRIVACY.md
@@ -694,6 +694,30 @@ _If you want to find diagnostic data events in the source code, these two links
+### Light Switch
+
+
+ Event Name
+ Description
+
+
+ Microsoft.PowerToys.LightSwitch_EnableLightSwitch
+ Triggered when Light Switch is enabled or disabled.
+
+
+ Microsoft.PowerToys.LightSwitch_ShortcutInvoked
+ Occurs when the shortcut for Light Switch is invoked.
+
+
+ Microsoft.PowerToys.LightSwitch_ScheduleModeToggled
+ Occurs when a new schedule mode is selected for Light Switch.
+
+
+ Microsoft.PowerToys.LightSwitch_ThemeTargetChanged
+ Occurs when the options for targeting the system or apps is updated.
+
+
+
### Mouse Highlighter
diff --git a/PowerToys.slnx b/PowerToys.slnx
index 0b3480b34a..565a5c1e4a 100644
--- a/PowerToys.slnx
+++ b/PowerToys.slnx
@@ -420,6 +420,7 @@
+
@@ -429,6 +430,9 @@
+
+
+
@@ -1018,6 +1022,14 @@
+
+
+
+
+
+
+
+
diff --git a/doc/devdocs/guidance.md b/doc/devdocs/guidance.md
index 6a5050d89d..c4500f5543 100644
--- a/doc/devdocs/guidance.md
+++ b/doc/devdocs/guidance.md
@@ -58,8 +58,8 @@ string validUIDisplayString = Resources.ValidUIDisplayString;
## More On Coding Guidance
Please review these brief docs below relating to our coding standards, etc.
-* [Coding Style](./style.md)
-* [Code Organization](./readme.md)
+* [Coding Style](development/style.md)
+* [Code Organization](readme.md)
[VS Resource Editor]: https://learn.microsoft.com/cpp/windows/resource-editors?view=vs-2019
diff --git a/doc/devdocs/modules/cropandlock.md b/doc/devdocs/modules/cropandlock.md
index 91f020e3e6..db5e9402cf 100644
--- a/doc/devdocs/modules/cropandlock.md
+++ b/doc/devdocs/modules/cropandlock.md
@@ -20,6 +20,9 @@ Creates a window showing the selected area of the original window. Changes in th
### Reparent Mode
Creates a window that replaces the original window, showing only the selected area. The application is controlled through the cropped window.
+### Screenshot Mode
+Creates a window showing a freezed snapshot of the original window.
+
## Code Structure
### Project Layout
@@ -30,6 +33,7 @@ The Crop and Lock module is part of the PowerToys solution. All the logic-relate
- **OverlayWindow.cpp**: Thumbnail module type's window concrete implementation.
- **ReparentCropAndLockWindow.cpp**: Defines the UI for the reparent mode.
- **ChildWindow.cpp**: Reparent module type's window concrete implementation.
+- **ScreenshotCropAndLockWindow.cpp**: Defines the UI for the screenshot mode.
## Known Issues
diff --git a/doc/devdocs/readme.md b/doc/devdocs/readme.md
index 38df894d1a..a6ac800be9 100644
--- a/doc/devdocs/readme.md
+++ b/doc/devdocs/readme.md
@@ -57,7 +57,7 @@ Once you've discussed your proposed feature/fix/etc. with a team member, and an
## Rules
- **Follow the pattern of what you already see in the code.**
-- [Coding style](style.md).
+- [Coding style](development/style.md).
- Try to package new functionality/components into libraries that have nicely defined interfaces.
- Package new functionality into classes or refactor existing functionality into a class as you extend the code.
- When adding new classes/methods/changing existing code, add new unit tests or update the existing tests.
diff --git a/doc/thirdPartyRunPlugins.md b/doc/thirdPartyRunPlugins.md
index 9cfdc505ff..80e1c16cc1 100644
--- a/doc/thirdPartyRunPlugins.md
+++ b/doc/thirdPartyRunPlugins.md
@@ -50,6 +50,8 @@ Contact the developers of a plugin directly for assistance with a specific plugi
| [Hotkeys](https://github.com/ruslanlap/PowerToysRun-Hotkeys) | [ruslanlap](https://github.com/ruslanlap) | Create, manage, and trigger custom keyboard shortcuts directly from PowerToys Run. |
| [RandomGen](https://github.com/ruslanlap/PowerToysRun-RandomGen) | [ruslanlap](https://github.com/ruslanlap) | 🎲 Generate random data instantly with a single keystroke. Perfect for developers, testers, designers, and anyone who needs quick access to random data. Features include secure passwords, PINs, names, business data, dates, numbers, GUIDs, color codes, and more. Especially useful for designers who need random color codes and placeholder content. |
| [Open With Cursor](https://github.com/VictorNoxx/PowerToys-Run-Cursor/) | [VictorNoxx](https://github.com/VictorNoxx) | Open Visual Studio, VS Code recents with Cursor AI |
+| [Open With Antigravity](https://github.com/artickc/PowerToys-Run-Antygravity) | [artickc](https://github.com/artickc) | Open Visual Studio, VS Code recents with Antigravity AI |
+| [Project Launcher Plugin](https://github.com/artickc/ProjectLauncherPowerToysPlugin) | [artickc](https://github.com/artickc) | Access your projects using Project Launcher and PowerToys Run |
| [CheatSheets](https://github.com/ruslanlap/PowerToysRun-CheatSheets) | [ruslanlap](https://github.com/ruslanlap) | 📚 Find cheat sheets and command examples instantly from tldr pages, cheat.sh, and devhints.io. Features include favorites system, categories, offline mode, and smart caching. |
| [QuickAI](https://github.com/ruslanlap/PowerToysRun-QuickAi) | [ruslanlap](https://github.com/ruslanlap) | AI-powered assistance with instant, smart responses from multiple providers (Groq, Together, Fireworks, OpenRouter, Cohere) |
diff --git a/src/common/ManagedCommon/ModuleType.cs b/src/common/ManagedCommon/ModuleType.cs
index aab99beec8..548276f725 100644
--- a/src/common/ManagedCommon/ModuleType.cs
+++ b/src/common/ManagedCommon/ModuleType.cs
@@ -37,5 +37,6 @@ namespace ManagedCommon
PowerOCR,
Workspaces,
ZoomIt,
+ GeneralSettings,
}
}
diff --git a/src/common/SettingsAPI/settings_objects.h b/src/common/SettingsAPI/settings_objects.h
index 84b064d5af..8927ba5657 100644
--- a/src/common/SettingsAPI/settings_objects.h
+++ b/src/common/SettingsAPI/settings_objects.h
@@ -119,6 +119,16 @@ namespace PowerToysSettings
class HotkeyObject
{
public:
+ HotkeyObject() :
+ m_json(json::JsonObject())
+ {
+ m_json.SetNamedValue(L"win", json::value(false));
+ m_json.SetNamedValue(L"ctrl", json::value(false));
+ m_json.SetNamedValue(L"alt", json::value(false));
+ m_json.SetNamedValue(L"shift", json::value(false));
+ m_json.SetNamedValue(L"code", json::value(0));
+ m_json.SetNamedValue(L"key", json::value(L""));
+ }
static HotkeyObject from_json(json::JsonObject json)
{
return HotkeyObject(std::move(json));
diff --git a/src/common/interop/Constants.cpp b/src/common/interop/Constants.cpp
index 86e5b16262..67b4da51f2 100644
--- a/src/common/interop/Constants.cpp
+++ b/src/common/interop/Constants.cpp
@@ -223,6 +223,10 @@ namespace winrt::PowerToys::Interop::implementation
{
return CommonSharedConstants::CROP_AND_LOCK_REPARENT_EVENT;
}
+ hstring Constants::CropAndLockScreenshotEvent()
+ {
+ return CommonSharedConstants::CROP_AND_LOCK_SCREENSHOT_EVENT;
+ }
hstring Constants::ShowEnvironmentVariablesSharedEvent()
{
return CommonSharedConstants::SHOW_ENVIRONMENT_VARIABLES_EVENT;
diff --git a/src/common/interop/Constants.h b/src/common/interop/Constants.h
index 5ac57d6f11..faa2a97379 100644
--- a/src/common/interop/Constants.h
+++ b/src/common/interop/Constants.h
@@ -59,6 +59,7 @@ namespace winrt::PowerToys::Interop::implementation
static hstring TerminateHostsSharedEvent();
static hstring CropAndLockThumbnailEvent();
static hstring CropAndLockReparentEvent();
+ static hstring CropAndLockScreenshotEvent();
static hstring ShowEnvironmentVariablesSharedEvent();
static hstring ShowEnvironmentVariablesAdminSharedEvent();
static hstring WorkspacesLaunchEditorEvent();
diff --git a/src/common/interop/Constants.idl b/src/common/interop/Constants.idl
index 38cc1fdbe2..042d790699 100644
--- a/src/common/interop/Constants.idl
+++ b/src/common/interop/Constants.idl
@@ -56,6 +56,7 @@ namespace PowerToys
static String TerminateHostsSharedEvent();
static String CropAndLockThumbnailEvent();
static String CropAndLockReparentEvent();
+ static String CropAndLockScreenshotEvent();
static String ShowEnvironmentVariablesSharedEvent();
static String ShowEnvironmentVariablesAdminSharedEvent();
static String WorkspacesLaunchEditorEvent();
diff --git a/src/common/interop/shared_constants.h b/src/common/interop/shared_constants.h
index 2df428f116..a007d50928 100644
--- a/src/common/interop/shared_constants.h
+++ b/src/common/interop/shared_constants.h
@@ -132,6 +132,7 @@ namespace CommonSharedConstants
// Path to the events used by CropAndLock
const wchar_t CROP_AND_LOCK_REPARENT_EVENT[] = L"Local\\PowerToysCropAndLockReparentEvent-6060860a-76a1-44e8-8d0e-6355785e9c36";
const wchar_t CROP_AND_LOCK_THUMBNAIL_EVENT[] = L"Local\\PowerToysCropAndLockThumbnailEvent-1637be50-da72-46b2-9220-b32b206b2434";
+ const wchar_t CROP_AND_LOCK_SCREENSHOT_EVENT[] = L"Local\\PowerToysCropAndLockScreenshotEvent-ff077ab2-8360-4bd1-864a-637389d35593";
const wchar_t CROP_AND_LOCK_EXIT_EVENT[] = L"Local\\PowerToysCropAndLockExitEvent-d995d409-7b70-482b-bad6-e7c8666f375a";
// Path to the events used by EnvironmentVariables
diff --git a/src/modules/CropAndLock/CropAndLock/CropAndLock.vcxproj b/src/modules/CropAndLock/CropAndLock/CropAndLock.vcxproj
index c3e9e4f3f1..dfe9f11b2e 100644
--- a/src/modules/CropAndLock/CropAndLock/CropAndLock.vcxproj
+++ b/src/modules/CropAndLock/CropAndLock/CropAndLock.vcxproj
@@ -112,6 +112,7 @@
+
@@ -126,6 +127,7 @@
+
diff --git a/src/modules/CropAndLock/CropAndLock/CropAndLock.vcxproj.filters b/src/modules/CropAndLock/CropAndLock/CropAndLock.vcxproj.filters
index bea68db119..e906ed2a02 100644
--- a/src/modules/CropAndLock/CropAndLock/CropAndLock.vcxproj.filters
+++ b/src/modules/CropAndLock/CropAndLock/CropAndLock.vcxproj.filters
@@ -12,6 +12,7 @@
+
@@ -28,6 +29,7 @@
+
diff --git a/src/modules/CropAndLock/CropAndLock/ScreenshotCropAndLockWindow.cpp b/src/modules/CropAndLock/CropAndLock/ScreenshotCropAndLockWindow.cpp
new file mode 100644
index 0000000000..11afbd0a26
--- /dev/null
+++ b/src/modules/CropAndLock/CropAndLock/ScreenshotCropAndLockWindow.cpp
@@ -0,0 +1,178 @@
+#include "pch.h"
+#include "ScreenshotCropAndLockWindow.h"
+
+const std::wstring ScreenshotCropAndLockWindow::ClassName = L"CropAndLock.ScreenshotCropAndLockWindow";
+std::once_flag ScreenshotCropAndLockWindowClassRegistration;
+
+void ScreenshotCropAndLockWindow::RegisterWindowClass()
+{
+ auto instance = winrt::check_pointer(GetModuleHandleW(nullptr));
+ WNDCLASSEXW wcex = {};
+ wcex.cbSize = sizeof(wcex);
+ wcex.style = CS_HREDRAW | CS_VREDRAW;
+ wcex.lpfnWndProc = WndProc;
+ wcex.hInstance = instance;
+ wcex.hIcon = LoadIconW(instance, IDI_APPLICATION);
+ wcex.hCursor = LoadCursorW(nullptr, IDC_ARROW);
+ wcex.hbrBackground = static_cast(GetStockObject(BLACK_BRUSH));
+ wcex.lpszClassName = ClassName.c_str();
+ wcex.hIconSm = LoadIconW(wcex.hInstance, IDI_APPLICATION);
+ winrt::check_bool(RegisterClassExW(&wcex));
+}
+
+ScreenshotCropAndLockWindow::ScreenshotCropAndLockWindow(std::wstring const& titleString, int width, int height)
+{
+ auto instance = winrt::check_pointer(GetModuleHandleW(nullptr));
+
+ std::call_once(ScreenshotCropAndLockWindowClassRegistration, []() { RegisterWindowClass(); });
+
+ auto exStyle = 0;
+ auto style = WS_OVERLAPPEDWINDOW | WS_CLIPCHILDREN;
+
+ RECT rect = { 0, 0, width, height };
+ winrt::check_bool(AdjustWindowRectEx(&rect, style, false, exStyle));
+ auto adjustedWidth = rect.right - rect.left;
+ auto adjustedHeight = rect.bottom - rect.top;
+
+ winrt::check_bool(CreateWindowExW(exStyle, ClassName.c_str(), titleString.c_str(), style, CW_USEDEFAULT, CW_USEDEFAULT, adjustedWidth, adjustedHeight, nullptr, nullptr, instance, this));
+ WINRT_ASSERT(m_window);
+}
+
+ScreenshotCropAndLockWindow::~ScreenshotCropAndLockWindow()
+{
+ DestroyWindow(m_window);
+}
+
+LRESULT ScreenshotCropAndLockWindow::MessageHandler(UINT const message, WPARAM const wparam, LPARAM const lparam)
+{
+ switch (message)
+ {
+ case WM_DESTROY:
+ if (m_closedCallback != nullptr && !m_destroyed)
+ {
+ m_destroyed = true;
+ m_closedCallback(m_window);
+ }
+ break;
+ case WM_PAINT:
+ if (m_captured && m_bitmap)
+ {
+ PAINTSTRUCT ps;
+ HDC hdc = BeginPaint(m_window, &ps);
+ HDC memDC = CreateCompatibleDC(hdc);
+ SelectObject(memDC, m_bitmap.get());
+
+ RECT clientRect = {};
+ GetClientRect(m_window, &clientRect);
+ int clientWidth = clientRect.right - clientRect.left;
+ int clientHeight = clientRect.bottom - clientRect.top;
+
+ int srcWidth = m_destRect.right - m_destRect.left;
+ int srcHeight = m_destRect.bottom - m_destRect.top;
+
+ float srcAspect = static_cast(srcWidth) / srcHeight;
+ float dstAspect = static_cast(clientWidth) / clientHeight;
+
+ int drawWidth = clientWidth;
+ int drawHeight = static_cast(clientWidth / srcAspect);
+ if (dstAspect > srcAspect)
+ {
+ drawHeight = clientHeight;
+ drawWidth = static_cast(clientHeight * srcAspect);
+ }
+
+ int offsetX = (clientWidth - drawWidth) / 2;
+ int offsetY = (clientHeight - drawHeight) / 2;
+
+ SetStretchBltMode(hdc, HALFTONE);
+ StretchBlt(hdc, offsetX, offsetY, drawWidth, drawHeight, memDC, 0, 0, srcWidth, srcHeight, SRCCOPY);
+ DeleteDC(memDC);
+ EndPaint(m_window, &ps);
+ }
+ break;
+ default:
+ return base_type::MessageHandler(message, wparam, lparam);
+ }
+ return 0;
+}
+
+void ScreenshotCropAndLockWindow::CropAndLock(HWND windowToCrop, RECT cropRect)
+{
+ if (m_captured)
+ {
+ return;
+ }
+
+ // Get full window bounds
+ RECT windowRect{};
+ winrt::check_hresult(DwmGetWindowAttribute(
+ windowToCrop,
+ DWMWA_EXTENDED_FRAME_BOUNDS,
+ &windowRect,
+ sizeof(windowRect)));
+
+ RECT clientRect = ClientAreaInScreenSpace(windowToCrop);
+ auto offsetX = clientRect.left - windowRect.left;
+ auto offsetY = clientRect.top - windowRect.top;
+
+ m_sourceRect = {
+ cropRect.left + offsetX,
+ cropRect.top + offsetY,
+ cropRect.right + offsetX,
+ cropRect.bottom + offsetY
+ };
+
+ int fullWidth = windowRect.right - windowRect.left;
+ int fullHeight = windowRect.bottom - windowRect.top;
+
+ HDC fullDC = CreateCompatibleDC(nullptr);
+ HDC screenDC = GetDC(nullptr);
+ HBITMAP fullBitmap = CreateCompatibleBitmap(screenDC, fullWidth, fullHeight);
+ HGDIOBJ oldFullBitmap = SelectObject(fullDC, fullBitmap);
+
+ // Capture full window
+ winrt::check_bool(PrintWindow(windowToCrop, fullDC, PW_RENDERFULLCONTENT));
+
+
+ // Crop
+ int cropWidth = m_sourceRect.right - m_sourceRect.left;
+ int cropHeight = m_sourceRect.bottom - m_sourceRect.top;
+
+ HDC cropDC = CreateCompatibleDC(nullptr);
+ HBITMAP cropBitmap = CreateCompatibleBitmap(screenDC, cropWidth, cropHeight);
+ HGDIOBJ oldCropBitmap = SelectObject(cropDC, cropBitmap);
+ ReleaseDC(nullptr, screenDC);
+
+ BitBlt(
+ cropDC,
+ 0,
+ 0,
+ cropWidth,
+ cropHeight,
+ fullDC,
+ m_sourceRect.left,
+ m_sourceRect.top,
+ SRCCOPY);
+
+ SelectObject(fullDC, oldFullBitmap);
+ DeleteObject(fullBitmap);
+ DeleteDC(fullDC);
+
+ SelectObject(cropDC, oldCropBitmap);
+ DeleteDC(cropDC);
+ m_bitmap.reset(cropBitmap);
+
+ // Resize our window
+ RECT dest{ 0, 0, cropWidth, cropHeight };
+ LONG_PTR exStyle = GetWindowLongPtrW(m_window, GWL_EXSTYLE);
+ LONG_PTR style = GetWindowLongPtrW(m_window, GWL_STYLE);
+
+ winrt::check_bool(AdjustWindowRectEx(&dest, static_cast(style), FALSE, static_cast(exStyle)));
+
+ winrt::check_bool(SetWindowPos(
+ m_window, HWND_TOPMOST, 0, 0, dest.right - dest.left, dest.bottom - dest.top, SWP_NOMOVE | SWP_SHOWWINDOW));
+
+ m_destRect = { 0, 0, cropWidth, cropHeight };
+ m_captured = true;
+ InvalidateRect(m_window, nullptr, FALSE);
+}
\ No newline at end of file
diff --git a/src/modules/CropAndLock/CropAndLock/ScreenshotCropAndLockWindow.h b/src/modules/CropAndLock/CropAndLock/ScreenshotCropAndLockWindow.h
new file mode 100644
index 0000000000..149e4c740a
--- /dev/null
+++ b/src/modules/CropAndLock/CropAndLock/ScreenshotCropAndLockWindow.h
@@ -0,0 +1,27 @@
+#pragma once
+#include
+#include "CropAndLockWindow.h"
+
+struct ScreenshotCropAndLockWindow : robmikh::common::desktop::DesktopWindow, CropAndLockWindow
+{
+ static const std::wstring ClassName;
+ ScreenshotCropAndLockWindow(std::wstring const& titleString, int width, int height);
+ ~ScreenshotCropAndLockWindow() override;
+ LRESULT MessageHandler(UINT const message, WPARAM const wparam, LPARAM const lparam);
+
+ HWND Handle() override { return m_window; }
+ void CropAndLock(HWND windowToCrop, RECT cropRect) override;
+ void OnClosed(std::function callback) override { m_closedCallback = callback; }
+
+private:
+ static void RegisterWindowClass();
+
+private:
+ std::unique_ptr m_bitmap{ nullptr, &DeleteObject };
+ RECT m_destRect = {};
+ RECT m_sourceRect = {};
+
+ bool m_captured = false;
+ bool m_destroyed = false;
+ std::function m_closedCallback;
+};
\ No newline at end of file
diff --git a/src/modules/CropAndLock/CropAndLock/SettingsWindow.h b/src/modules/CropAndLock/CropAndLock/SettingsWindow.h
index 88489601ee..f51e4636f0 100644
--- a/src/modules/CropAndLock/CropAndLock/SettingsWindow.h
+++ b/src/modules/CropAndLock/CropAndLock/SettingsWindow.h
@@ -4,4 +4,5 @@ enum class CropAndLockType
{
Reparent,
Thumbnail,
+ Screenshot,
};
diff --git a/src/modules/CropAndLock/CropAndLock/main.cpp b/src/modules/CropAndLock/CropAndLock/main.cpp
index 5aeea262a4..8f3ac89569 100644
--- a/src/modules/CropAndLock/CropAndLock/main.cpp
+++ b/src/modules/CropAndLock/CropAndLock/main.cpp
@@ -2,6 +2,7 @@
#include "SettingsWindow.h"
#include "OverlayWindow.h"
#include "CropAndLockWindow.h"
+#include "ScreenshotCropAndLockWindow.h"
#include "ThumbnailCropAndLockWindow.h"
#include "ReparentCropAndLockWindow.h"
#include "ModuleConstants.h"
@@ -133,6 +134,7 @@ int WINAPI wWinMain(_In_ HINSTANCE, _In_opt_ HINSTANCE, _In_ PWSTR lpCmdLine, _I
// Handles and thread for the events sent from runner
HANDLE m_reparent_event_handle;
HANDLE m_thumbnail_event_handle;
+ HANDLE m_screenshot_event_handle;
HANDLE m_exit_event_handle;
std::thread m_event_triggers_thread;
@@ -181,6 +183,11 @@ int WINAPI wWinMain(_In_ HINSTANCE, _In_opt_ HINSTANCE, _In_ PWSTR lpCmdLine, _I
Logger::trace(L"Creating a thumbnail window");
Trace::CropAndLock::CreateThumbnailWindow();
break;
+ case CropAndLockType::Screenshot:
+ croppedWindow = std::make_shared(title, 800, 600);
+ Logger::trace(L"Creating a screenshot window");
+ Trace::CropAndLock::CreateScreenshotWindow();
+ break;
default:
return;
}
@@ -215,8 +222,9 @@ int WINAPI wWinMain(_In_ HINSTANCE, _In_opt_ HINSTANCE, _In_ PWSTR lpCmdLine, _I
// Start a thread to listen on the events.
m_reparent_event_handle = CreateEventW(nullptr, false, false, CommonSharedConstants::CROP_AND_LOCK_REPARENT_EVENT);
m_thumbnail_event_handle = CreateEventW(nullptr, false, false, CommonSharedConstants::CROP_AND_LOCK_THUMBNAIL_EVENT);
+ m_screenshot_event_handle = CreateEventW(nullptr, false, false, CommonSharedConstants::CROP_AND_LOCK_SCREENSHOT_EVENT);
m_exit_event_handle = CreateEventW(nullptr, false, false, CommonSharedConstants::CROP_AND_LOCK_EXIT_EVENT);
- if (!m_reparent_event_handle || !m_thumbnail_event_handle || !m_exit_event_handle)
+ if (!m_reparent_event_handle || !m_thumbnail_event_handle || !m_screenshot_event_handle || !m_exit_event_handle)
{
Logger::warn(L"Failed to create events. {}", get_last_error_or_default(GetLastError()));
return 1;
@@ -224,10 +232,10 @@ int WINAPI wWinMain(_In_ HINSTANCE, _In_opt_ HINSTANCE, _In_ PWSTR lpCmdLine, _I
m_event_triggers_thread = std::thread([&]() {
MSG msg;
- HANDLE event_handles[3] = { m_reparent_event_handle, m_thumbnail_event_handle, m_exit_event_handle };
+ HANDLE event_handles[4] = { m_reparent_event_handle, m_thumbnail_event_handle, m_screenshot_event_handle, m_exit_event_handle };
while (m_running)
{
- DWORD dwEvt = MsgWaitForMultipleObjects(3, event_handles, false, INFINITE, QS_ALLINPUT);
+ DWORD dwEvt = MsgWaitForMultipleObjects(4, event_handles, false, INFINITE, QS_ALLINPUT);
if (!m_running)
{
break;
@@ -259,13 +267,25 @@ int WINAPI wWinMain(_In_ HINSTANCE, _In_opt_ HINSTANCE, _In_ PWSTR lpCmdLine, _I
break;
}
case WAIT_OBJECT_0 + 2:
+ {
+ // Screenshot Event
+ bool enqueueSucceeded = controller.DispatcherQueue().TryEnqueue([&]() {
+ ProcessCommand(CropAndLockType::Screenshot);
+ });
+ if (!enqueueSucceeded)
+ {
+ Logger::error("Couldn't enqueue message to screenshot a window.");
+ }
+ break;
+ }
+ case WAIT_OBJECT_0 + 3:
{
// Exit Event
Logger::trace(L"Received an exit event.");
PostThreadMessage(mainThreadId, WM_QUIT, 0, 0);
break;
}
- case WAIT_OBJECT_0 + 3:
+ case WAIT_OBJECT_0 + 4:
if (PeekMessageW(&msg, nullptr, 0, 0, PM_REMOVE))
{
TranslateMessage(&msg);
@@ -295,6 +315,7 @@ int WINAPI wWinMain(_In_ HINSTANCE, _In_opt_ HINSTANCE, _In_ PWSTR lpCmdLine, _I
SetEvent(m_reparent_event_handle);
CloseHandle(m_reparent_event_handle);
CloseHandle(m_thumbnail_event_handle);
+ CloseHandle(m_screenshot_event_handle);
CloseHandle(m_exit_event_handle);
m_event_triggers_thread.join();
diff --git a/src/modules/CropAndLock/CropAndLock/trace.cpp b/src/modules/CropAndLock/CropAndLock/trace.cpp
index 42674ec624..3a08fb9683 100644
--- a/src/modules/CropAndLock/CropAndLock/trace.cpp
+++ b/src/modules/CropAndLock/CropAndLock/trace.cpp
@@ -41,6 +41,15 @@ void Trace::CropAndLock::ActivateThumbnail() noexcept
TraceLoggingKeyword(PROJECT_KEYWORD_MEASURE));
}
+void Trace::CropAndLock::ActivateScreenshot() noexcept
+{
+ TraceLoggingWriteWrapper(
+ g_hProvider,
+ "CropAndLock_ActivateScreenshot",
+ ProjectTelemetryPrivacyDataTag(ProjectTelemetryTag_ProductAndServicePerformance),
+ TraceLoggingKeyword(PROJECT_KEYWORD_MEASURE));
+}
+
void Trace::CropAndLock::CreateReparentWindow() noexcept
{
TraceLoggingWriteWrapper(
@@ -59,8 +68,17 @@ void Trace::CropAndLock::CreateThumbnailWindow() noexcept
TraceLoggingKeyword(PROJECT_KEYWORD_MEASURE));
}
+void Trace::CropAndLock::CreateScreenshotWindow() noexcept
+{
+ TraceLoggingWriteWrapper(
+ g_hProvider,
+ "CropAndLock_CreateScreenshotWindow",
+ ProjectTelemetryPrivacyDataTag(ProjectTelemetryTag_ProductAndServicePerformance),
+ TraceLoggingKeyword(PROJECT_KEYWORD_MEASURE));
+}
+
// Event to send settings telemetry.
-void Trace::CropAndLock::SettingsTelemetry(PowertoyModuleIface::Hotkey& reparentHotkey, PowertoyModuleIface::Hotkey& thumbnailHotkey) noexcept
+void Trace::CropAndLock::SettingsTelemetry(PowertoyModuleIface::Hotkey& reparentHotkey, PowertoyModuleIface::Hotkey& thumbnailHotkey, PowertoyModuleIface::Hotkey& screenshotHotkey) noexcept
{
std::wstring hotKeyStrReparent =
std::wstring(reparentHotkey.win ? L"Win + " : L"") +
@@ -76,11 +94,19 @@ void Trace::CropAndLock::SettingsTelemetry(PowertoyModuleIface::Hotkey& reparent
std::wstring(thumbnailHotkey.alt ? L"Alt + " : L"") +
std::wstring(L"VK ") + std::to_wstring(thumbnailHotkey.key);
+ std::wstring hotKeyStrScreenshot =
+ std::wstring(screenshotHotkey.win ? L"Win + " : L"") +
+ std::wstring(screenshotHotkey.ctrl ? L"Ctrl + " : L"") +
+ std::wstring(screenshotHotkey.shift ? L"Shift + " : L"") +
+ std::wstring(screenshotHotkey.alt ? L"Alt + " : L"") +
+ std::wstring(L"VK ") + std::to_wstring(screenshotHotkey.key);
+
TraceLoggingWriteWrapper(
g_hProvider,
"CropAndLock_Settings",
ProjectTelemetryPrivacyDataTag(ProjectTelemetryTag_ProductAndServicePerformance),
TraceLoggingKeyword(PROJECT_KEYWORD_MEASURE),
TraceLoggingWideString(hotKeyStrReparent.c_str(), "ReparentHotKey"),
- TraceLoggingWideString(hotKeyStrThumbnail.c_str(), "ThumbnailHotkey"));
+ TraceLoggingWideString(hotKeyStrThumbnail.c_str(), "ThumbnailHotkey"),
+ TraceLoggingWideString(hotKeyStrScreenshot.c_str(), "ScreenshotHotkey"));
}
diff --git a/src/modules/CropAndLock/CropAndLock/trace.h b/src/modules/CropAndLock/CropAndLock/trace.h
index 5a9aaa95ca..bd9a3431a2 100644
--- a/src/modules/CropAndLock/CropAndLock/trace.h
+++ b/src/modules/CropAndLock/CropAndLock/trace.h
@@ -12,8 +12,10 @@ public:
static void Enable(bool enabled) noexcept;
static void ActivateReparent() noexcept;
static void ActivateThumbnail() noexcept;
+ static void ActivateScreenshot() noexcept;
static void CreateReparentWindow() noexcept;
static void CreateThumbnailWindow() noexcept;
- static void SettingsTelemetry(PowertoyModuleIface::Hotkey&, PowertoyModuleIface::Hotkey&) noexcept;
+ static void CreateScreenshotWindow() noexcept;
+ static void SettingsTelemetry(PowertoyModuleIface::Hotkey&, PowertoyModuleIface::Hotkey&, PowertoyModuleIface::Hotkey&) noexcept;
};
};
diff --git a/src/modules/CropAndLock/CropAndLockModuleInterface/dllmain.cpp b/src/modules/CropAndLock/CropAndLockModuleInterface/dllmain.cpp
index 42c7c6da7e..9821b786f1 100644
--- a/src/modules/CropAndLock/CropAndLockModuleInterface/dllmain.cpp
+++ b/src/modules/CropAndLock/CropAndLockModuleInterface/dllmain.cpp
@@ -29,6 +29,7 @@ namespace
const wchar_t JSON_KEY_CODE[] = L"code";
const wchar_t JSON_KEY_REPARENT_HOTKEY[] = L"reparent-hotkey";
const wchar_t JSON_KEY_THUMBNAIL_HOTKEY[] = L"thumbnail-hotkey";
+ const wchar_t JSON_KEY_SCREENSHOT_HOTKEY[] = L"screenshot-hotkey";
const wchar_t JSON_KEY_VALUE[] = L"value";
}
@@ -124,6 +125,10 @@ public:
SetEvent(m_thumbnail_event_handle);
Trace::CropAndLock::ActivateThumbnail();
}
+ if (hotkeyId == 2) { // Same order as set by get_hotkeys
+ SetEvent(m_screenshot_event_handle);
+ Trace::CropAndLock::ActivateScreenshot();
+ }
return true;
}
@@ -133,12 +138,13 @@ public:
virtual size_t get_hotkeys(Hotkey* hotkeys, size_t buffer_size) override
{
- if (hotkeys && buffer_size >= 2)
+ if (hotkeys && buffer_size >= 3)
{
hotkeys[0] = m_reparent_hotkey;
hotkeys[1] = m_thumbnail_hotkey;
+ hotkeys[2] = m_screenshot_hotkey;
}
- return 2;
+ return 3;
}
// Enable the powertoy
@@ -171,7 +177,7 @@ public:
virtual void send_settings_telemetry() override
{
Logger::info("Send settings telemetry");
- Trace::CropAndLock::SettingsTelemetry(m_reparent_hotkey, m_thumbnail_hotkey);
+ Trace::CropAndLock::SettingsTelemetry(m_reparent_hotkey, m_thumbnail_hotkey, m_screenshot_hotkey);
}
CropAndLockModuleInterface()
@@ -182,6 +188,7 @@ public:
m_reparent_event_handle = CreateDefaultEvent(CommonSharedConstants::CROP_AND_LOCK_REPARENT_EVENT);
m_thumbnail_event_handle = CreateDefaultEvent(CommonSharedConstants::CROP_AND_LOCK_THUMBNAIL_EVENT);
+ m_screenshot_event_handle = CreateDefaultEvent(CommonSharedConstants::CROP_AND_LOCK_SCREENSHOT_EVENT);
m_exit_event_handle = CreateDefaultEvent(CommonSharedConstants::CROP_AND_LOCK_EXIT_EVENT);
init_settings();
@@ -202,6 +209,7 @@ private:
ResetEvent(m_reparent_event_handle);
ResetEvent(m_thumbnail_event_handle);
+ ResetEvent(m_screenshot_event_handle);
ResetEvent(m_exit_event_handle);
SHELLEXECUTEINFOW sei{ sizeof(sei) };
@@ -234,6 +242,7 @@ private:
ResetEvent(m_reparent_event_handle);
ResetEvent(m_thumbnail_event_handle);
+ ResetEvent(m_screenshot_event_handle);
// Log telemetry
if (traceEvent)
@@ -283,6 +292,21 @@ private:
{
Logger::error("Failed to initialize CropAndLock thumbnail shortcut from settings. Value will keep unchanged.");
}
+ try
+ {
+ Hotkey _temp_screenshot;
+ auto jsonHotkeyObject = settingsObject.GetNamedObject(JSON_KEY_PROPERTIES).GetNamedObject(JSON_KEY_SCREENSHOT_HOTKEY).GetNamedObject(JSON_KEY_VALUE);
+ _temp_screenshot.win = jsonHotkeyObject.GetNamedBoolean(JSON_KEY_WIN);
+ _temp_screenshot.alt = jsonHotkeyObject.GetNamedBoolean(JSON_KEY_ALT);
+ _temp_screenshot.shift = jsonHotkeyObject.GetNamedBoolean(JSON_KEY_SHIFT);
+ _temp_screenshot.ctrl = jsonHotkeyObject.GetNamedBoolean(JSON_KEY_CTRL);
+ _temp_screenshot.key = static_cast(jsonHotkeyObject.GetNamedNumber(JSON_KEY_CODE));
+ m_screenshot_hotkey = _temp_screenshot;
+ }
+ catch (...)
+ {
+ Logger::error("Failed to initialize CropAndLock screenshot shortcut from settings. Value will keep unchanged.");
+ }
}
else
{
@@ -321,9 +345,11 @@ private:
// TODO: actual default hotkey setting in line with other PowerToys.
Hotkey m_reparent_hotkey = { .win = true, .ctrl = true, .shift = true, .alt = false, .key = 'R' };
Hotkey m_thumbnail_hotkey = { .win = true, .ctrl = true, .shift = true, .alt = false, .key = 'T' };
+ Hotkey m_screenshot_hotkey = { .win = true, .ctrl = true, .shift = true, .alt = false, .key = 'S' };
HANDLE m_reparent_event_handle;
HANDLE m_thumbnail_event_handle;
+ HANDLE m_screenshot_event_handle;
HANDLE m_exit_event_handle;
};
diff --git a/src/modules/EnvironmentVariables/EnvironmentVariablesUILib/EnvironmentVariablesMainPage.xaml b/src/modules/EnvironmentVariables/EnvironmentVariablesUILib/EnvironmentVariablesMainPage.xaml
index f2628cf375..baa2447cd1 100644
--- a/src/modules/EnvironmentVariables/EnvironmentVariablesUILib/EnvironmentVariablesMainPage.xaml
+++ b/src/modules/EnvironmentVariables/EnvironmentVariablesUILib/EnvironmentVariablesMainPage.xaml
@@ -466,27 +466,39 @@
TextChanged="EditVariableDialogValueTxtBox_TextChanged"
TextWrapping="Wrap" />
-
-
+
+
+
-
-
+
+
diff --git a/src/modules/EnvironmentVariables/EnvironmentVariablesUILib/EnvironmentVariablesMainPage.xaml.cs b/src/modules/EnvironmentVariables/EnvironmentVariablesUILib/EnvironmentVariablesMainPage.xaml.cs
index a70eeeec36..339c13ce6f 100644
--- a/src/modules/EnvironmentVariables/EnvironmentVariablesUILib/EnvironmentVariablesMainPage.xaml.cs
+++ b/src/modules/EnvironmentVariables/EnvironmentVariablesUILib/EnvironmentVariablesMainPage.xaml.cs
@@ -16,6 +16,8 @@ namespace EnvironmentVariablesUILib
{
public sealed partial class EnvironmentVariablesMainPage : Page
{
+ private const string ValueListSeparator = ";";
+
private sealed class RelayCommandParameter
{
public RelayCommandParameter(Variable variable, VariablesSet set)
@@ -440,7 +442,7 @@ namespace EnvironmentVariablesUILib
variable.ValuesList.Move(index, index - 1);
}
- var newValues = string.Join(";", variable.ValuesList?.Select(x => x.Text).ToArray());
+ var newValues = string.Join(ValueListSeparator, variable.ValuesList?.Select(x => x.Text).ToArray());
EditVariableDialogValueTxtBox.Text = newValues;
}
@@ -461,7 +463,7 @@ namespace EnvironmentVariablesUILib
variable.ValuesList.Move(index, index + 1);
}
- var newValues = string.Join(";", variable.ValuesList?.Select(x => x.Text).ToArray());
+ var newValues = string.Join(ValueListSeparator, variable.ValuesList?.Select(x => x.Text).ToArray());
EditVariableDialogValueTxtBox.Text = newValues;
}
@@ -476,7 +478,7 @@ namespace EnvironmentVariablesUILib
var variable = EditVariableDialog.DataContext as Variable;
variable.ValuesList.Remove(listItem);
- var newValues = string.Join(";", variable.ValuesList?.Select(x => x.Text).ToArray());
+ var newValues = string.Join(ValueListSeparator, variable.ValuesList?.Select(x => x.Text).ToArray());
EditVariableDialogValueTxtBox.Text = newValues;
}
@@ -492,7 +494,7 @@ namespace EnvironmentVariablesUILib
var index = variable.ValuesList.IndexOf(listItem);
variable.ValuesList.Insert(index, new Variable.ValuesListItem { Text = string.Empty });
- var newValues = string.Join(";", variable.ValuesList?.Select(x => x.Text).ToArray());
+ var newValues = string.Join(ValueListSeparator, variable.ValuesList?.Select(x => x.Text).ToArray());
EditVariableDialogValueTxtBox.TextChanged -= EditVariableDialogValueTxtBox_TextChanged;
EditVariableDialogValueTxtBox.Text = newValues;
EditVariableDialogValueTxtBox.TextChanged += EditVariableDialogValueTxtBox_TextChanged;
@@ -510,7 +512,7 @@ namespace EnvironmentVariablesUILib
var index = variable.ValuesList.IndexOf(listItem);
variable.ValuesList.Insert(index + 1, new Variable.ValuesListItem { Text = string.Empty });
- var newValues = string.Join(";", variable.ValuesList?.Select(x => x.Text).ToArray());
+ var newValues = string.Join(ValueListSeparator, variable.ValuesList?.Select(x => x.Text).ToArray());
EditVariableDialogValueTxtBox.TextChanged -= EditVariableDialogValueTxtBox_TextChanged;
EditVariableDialogValueTxtBox.Text = newValues;
EditVariableDialogValueTxtBox.TextChanged += EditVariableDialogValueTxtBox_TextChanged;
@@ -532,7 +534,7 @@ namespace EnvironmentVariablesUILib
listItem.Text = (sender as TextBox)?.Text;
var variable = EditVariableDialog.DataContext as Variable;
- var newValues = string.Join(";", variable.ValuesList?.Select(x => x.Text).ToArray());
+ var newValues = string.Join(ValueListSeparator, variable.ValuesList?.Select(x => x.Text).ToArray());
EditVariableDialogValueTxtBox.TextChanged -= EditVariableDialogValueTxtBox_TextChanged;
EditVariableDialogValueTxtBox.Text = newValues;
EditVariableDialogValueTxtBox.TextChanged += EditVariableDialogValueTxtBox_TextChanged;
@@ -548,5 +550,16 @@ namespace EnvironmentVariablesUILib
CancelAddVariable();
ConfirmAddVariableBtn.IsEnabled = false;
}
+
+ private void EditVariableValuesList_DragItemsCompleted(ListViewBase sender, DragItemsCompletedEventArgs args)
+ {
+ if (EditVariableDialog.DataContext is Variable variable && variable.ValuesList != null)
+ {
+ var newValues = string.Join(ValueListSeparator, variable.ValuesList.Select(x => x.Text));
+ EditVariableDialogValueTxtBox.TextChanged -= EditVariableDialogValueTxtBox_TextChanged;
+ EditVariableDialogValueTxtBox.Text = newValues;
+ EditVariableDialogValueTxtBox.TextChanged += EditVariableDialogValueTxtBox_TextChanged;
+ }
+ }
}
}
diff --git a/src/modules/FileLocksmith/FileLocksmithCLI/CLILogic.cpp b/src/modules/FileLocksmith/FileLocksmithCLI/CLILogic.cpp
new file mode 100644
index 0000000000..17015e1ea3
--- /dev/null
+++ b/src/modules/FileLocksmith/FileLocksmithCLI/CLILogic.cpp
@@ -0,0 +1,248 @@
+#include "pch.h"
+#include "CLILogic.h"
+#include
+#include
+#include
+#include
+#include "resource.h"
+#include
+#include
+#include
+
+template
+DWORD_PTR ToDwordPtr(T val)
+{
+ if constexpr (std::is_pointer_v)
+ {
+ return reinterpret_cast(val);
+ }
+ else
+ {
+ return static_cast(val);
+ }
+}
+
+template
+std::wstring FormatString(IStringProvider& strings, UINT id, Args... args)
+{
+ std::wstring format = strings.GetString(id);
+ if (format.empty()) return L"";
+
+ DWORD_PTR arguments[] = { ToDwordPtr(args)..., 0 };
+
+ LPWSTR buffer = nullptr;
+ FormatMessageW(FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_STRING | FORMAT_MESSAGE_ARGUMENT_ARRAY,
+ format.c_str(),
+ 0,
+ 0,
+ reinterpret_cast(&buffer),
+ 0,
+ reinterpret_cast(arguments));
+
+ if (buffer)
+ {
+ std::wstring result(buffer);
+ LocalFree(buffer);
+ return result;
+ }
+ return L"";
+}
+
+std::wstring get_usage(IStringProvider& strings)
+{
+ return strings.GetString(IDS_USAGE);
+}
+
+std::wstring get_json(const std::vector& results)
+{
+ json::JsonObject root;
+ json::JsonArray processes;
+
+ for (const auto& result : results)
+ {
+ json::JsonObject process;
+ process.SetNamedValue(L"pid", json::JsonValue::CreateNumberValue(result.pid));
+ process.SetNamedValue(L"name", json::JsonValue::CreateStringValue(result.name));
+ process.SetNamedValue(L"user", json::JsonValue::CreateStringValue(result.user));
+
+ json::JsonArray files;
+ for (const auto& file : result.files)
+ {
+ files.Append(json::JsonValue::CreateStringValue(file));
+ }
+ process.SetNamedValue(L"files", files);
+
+ processes.Append(process);
+ }
+
+ root.SetNamedValue(L"processes", processes);
+ return root.Stringify().c_str();
+}
+
+std::wstring get_text(const std::vector& results, IStringProvider& strings)
+{
+ std::wstringstream ss;
+ if (results.empty())
+ {
+ ss << strings.GetString(IDS_NO_PROCESSES);
+ return ss.str();
+ }
+
+ ss << strings.GetString(IDS_HEADER);
+ for (const auto& result : results)
+ {
+ ss << result.pid << L"\t"
+ << result.user << L"\t"
+ << result.name << std::endl;
+ }
+ return ss.str();
+}
+
+std::wstring kill_processes(const std::vector& results, IProcessTerminator& terminator, IStringProvider& strings)
+{
+ std::wstringstream ss;
+ for (const auto& result : results)
+ {
+ if (terminator.terminate(result.pid))
+ {
+ ss << FormatString(strings, IDS_TERMINATED, result.pid, result.name.c_str());
+ }
+ else
+ {
+ ss << FormatString(strings, IDS_FAILED_TERMINATE, result.pid, result.name.c_str());
+ }
+ }
+ return ss.str();
+}
+
+CommandResult run_command(int argc, wchar_t* argv[], IProcessFinder& finder, IProcessTerminator& terminator, IStringProvider& strings)
+{
+ Logger::info("Parsing arguments");
+ if (argc < 2)
+ {
+ Logger::warn("No arguments provided");
+ return { 1, get_usage(strings) };
+ }
+
+ bool json_output = false;
+ bool kill = false;
+ bool wait = false;
+ int timeout_ms = -1;
+ std::vector paths;
+
+ for (int i = 1; i < argc; ++i)
+ {
+ std::wstring arg = argv[i];
+ if (arg == L"--json")
+ {
+ json_output = true;
+ }
+ else if (arg == L"--kill")
+ {
+ kill = true;
+ }
+ else if (arg == L"--wait")
+ {
+ wait = true;
+ }
+ else if (arg == L"--timeout")
+ {
+ if (i + 1 < argc)
+ {
+ try
+ {
+ timeout_ms = std::stoi(argv[++i]);
+ }
+ catch (...)
+ {
+ Logger::error("Invalid timeout value");
+ return { 1, strings.GetString(IDS_ERROR_INVALID_TIMEOUT) };
+ }
+ }
+ else
+ {
+ Logger::error("Timeout argument missing");
+ return { 1, strings.GetString(IDS_ERROR_TIMEOUT_ARG) };
+ }
+ }
+ else if (arg == L"--help")
+ {
+ return { 0, get_usage(strings) };
+ }
+ else
+ {
+ paths.push_back(arg);
+ }
+ }
+
+ if (paths.empty())
+ {
+ Logger::error("No paths specified");
+ return { 1, strings.GetString(IDS_ERROR_NO_PATHS) };
+ }
+
+ Logger::info("Processing {} paths", paths.size());
+
+ if (wait)
+ {
+ std::wstringstream ss;
+ if (json_output)
+ {
+ Logger::warn("Wait is incompatible with JSON output");
+ ss << strings.GetString(IDS_WARN_JSON_WAIT);
+ json_output = false;
+ }
+
+ ss << strings.GetString(IDS_WAITING);
+ auto start_time = std::chrono::steady_clock::now();
+ while (true)
+ {
+ auto results = finder.find(paths);
+ if (results.empty())
+ {
+ Logger::info("Files unlocked");
+ ss << strings.GetString(IDS_UNLOCKED);
+ break;
+ }
+
+ if (timeout_ms >= 0)
+ {
+ auto current_time = std::chrono::steady_clock::now();
+ auto elapsed = std::chrono::duration_cast(current_time - start_time).count();
+ if (elapsed > timeout_ms)
+ {
+ Logger::warn("Timeout waiting for files to be unlocked");
+ ss << strings.GetString(IDS_TIMEOUT);
+ return { 1, ss.str() };
+ }
+ }
+
+ Sleep(200);
+ }
+ return { 0, ss.str() };
+ }
+
+ auto results = finder.find(paths);
+ Logger::info("Found {} processes locking the files", results.size());
+ std::wstringstream output_ss;
+
+ if (kill)
+ {
+ Logger::info("Killing processes");
+ output_ss << kill_processes(results, terminator, strings);
+ // Re-check after killing
+ results = finder.find(paths);
+ Logger::info("Remaining processes: {}", results.size());
+ }
+
+ if (json_output)
+ {
+ output_ss << get_json(results) << std::endl;
+ }
+ else
+ {
+ output_ss << get_text(results, strings);
+ }
+
+ return { 0, output_ss.str() };
+}
diff --git a/src/modules/FileLocksmith/FileLocksmithCLI/CLILogic.h b/src/modules/FileLocksmith/FileLocksmithCLI/CLILogic.h
new file mode 100644
index 0000000000..c8f519592f
--- /dev/null
+++ b/src/modules/FileLocksmith/FileLocksmithCLI/CLILogic.h
@@ -0,0 +1,31 @@
+#pragma once
+#include
+#include
+#include "FileLocksmithLib/FileLocksmith.h"
+#include
+
+struct CommandResult
+{
+ int exit_code;
+ std::wstring output;
+};
+
+struct IProcessFinder
+{
+ virtual std::vector find(const std::vector& paths) = 0;
+ virtual ~IProcessFinder() = default;
+};
+
+struct IProcessTerminator
+{
+ virtual bool terminate(DWORD pid) = 0;
+ virtual ~IProcessTerminator() = default;
+};
+
+struct IStringProvider
+{
+ virtual std::wstring GetString(UINT id) = 0;
+ virtual ~IStringProvider() = default;
+};
+
+CommandResult run_command(int argc, wchar_t* argv[], IProcessFinder& finder, IProcessTerminator& terminator, IStringProvider& strings);
diff --git a/src/modules/FileLocksmith/FileLocksmithCLI/FileLocksmithCLI.rc b/src/modules/FileLocksmith/FileLocksmithCLI/FileLocksmithCLI.rc
new file mode 100644
index 0000000000..641c19fb49
--- /dev/null
+++ b/src/modules/FileLocksmith/FileLocksmithCLI/FileLocksmithCLI.rc
@@ -0,0 +1,19 @@
+#include "resource.h"
+#include
+
+STRINGTABLE
+BEGIN
+ IDS_USAGE "Usage: FileLocksmithCLI.exe [options] [path2] ...\nOptions:\n --kill Kill processes locking the files\n --json Output results in JSON format\n --wait Wait for files to be unlocked\n --timeout Timeout in milliseconds for --wait\n --help Show this help message\n"
+ IDS_NO_PROCESSES "No processes found locking the file(s).\n"
+ IDS_HEADER "PID\tUser\tProcess\n"
+ IDS_TERMINATED "Terminated process %1!d! (%2)\n"
+ IDS_FAILED_TERMINATE "Failed to terminate process %1!d! (%2)\n"
+ IDS_FAILED_OPEN "Failed to open process %1!d! (%2)\n"
+ IDS_ERROR_NO_PATHS "Error: No paths specified.\n"
+ IDS_WARN_JSON_WAIT "Warning: --wait is incompatible with --json. Ignoring --json.\n"
+ IDS_WAITING "Waiting for files to be unlocked...\n"
+ IDS_UNLOCKED "Files unlocked.\n"
+ IDS_TIMEOUT "Timeout waiting for files to be unlocked.\n"
+ IDS_ERROR_INVALID_TIMEOUT "Error: Invalid timeout value.\n"
+ IDS_ERROR_TIMEOUT_ARG "Error: --timeout requires an argument.\n"
+END
diff --git a/src/modules/FileLocksmith/FileLocksmithCLI/FileLocksmithCLI.vcxproj b/src/modules/FileLocksmith/FileLocksmithCLI/FileLocksmithCLI.vcxproj
new file mode 100644
index 0000000000..ca85b58d28
--- /dev/null
+++ b/src/modules/FileLocksmith/FileLocksmithCLI/FileLocksmithCLI.vcxproj
@@ -0,0 +1,119 @@
+
+
+
+
+ 17.0
+ Win32Proj
+ {49D456D3-F485-45AF-8875-45B44F193DDC}
+ FileLocksmithCLI
+ 10.0
+ FileLocksmithCLI
+
+
+
+
+ Application
+ true
+ v143
+ Unicode
+
+
+ Application
+ false
+ v143
+ Unicode
+
+
+
+
+
+
+
+
+
+
+
+ ..\..\..\..\$(Platform)\$(Configuration)
+
+
+
+ Level3
+ true
+ WIN32;_DEBUG;_CONSOLE;%(PreprocessorDefinitions)
+ false
+ $(ProjectDir)..;$(ProjectDir)..\..\..;$(ProjectDir)..\..;%(AdditionalIncludeDirectories)
+ Use
+ pch.h
+ false
+
+
+ Console
+ true
+ shlwapi.lib;ntdll.lib;%(AdditionalDependencies)
+
+
+
+
+ Level3
+ true
+ true
+ true
+ WIN32;NDEBUG;_CONSOLE;%(PreprocessorDefinitions)
+ false
+ $(ProjectDir)..;$(ProjectDir)..\..\..;$(ProjectDir)..\..;%(AdditionalIncludeDirectories)
+ Use
+ pch.h
+ false
+
+
+ Console
+ true
+ true
+ true
+ shlwapi.lib;ntdll.lib;%(AdditionalDependencies)
+
+
+
+
+
+
+ Create
+
+
+
+
+
+
+
+
+
+
+
+
+ {9d52fd25-ef90-4f9a-a015-91efc5daf54f}
+
+
+ {d9b8fc84-322a-4f9f-bbb9-20915c47ddfd}
+
+
+ {6955446d-23f7-4023-9bb3-8657f904af99}
+
+
+ {1248566c-272a-43c0-88d6-e6675d569a09}
+
+
+
+
+
+
+
+
+
+
+
+ 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}.
+
+
+
+
+
diff --git a/src/modules/FileLocksmith/FileLocksmithCLI/FileLocksmithCLI.vcxproj.filters b/src/modules/FileLocksmith/FileLocksmithCLI/FileLocksmithCLI.vcxproj.filters
new file mode 100644
index 0000000000..e8a641d95d
--- /dev/null
+++ b/src/modules/FileLocksmith/FileLocksmithCLI/FileLocksmithCLI.vcxproj.filters
@@ -0,0 +1,42 @@
+
+
+
+
+ {4FC737F1-C7A5-4376-A066-2A32D752A2FF}
+ cpp;c;cc;cxx;c++;cppm;ixx;def;odl;idl;hpj;bat;asm;asmx
+
+
+ {93995380-89BD-4b04-88EB-625FBE52EBFB}
+ h;hh;hpp;hxx;h++;hm;inl;inc;ipp;xsd
+
+
+ {67DA6AB6-F800-4c08-8B7A-83BB121AAD01}
+ rc;ico;cur;bmp;dlg;rc2;rct;bin;rgs;gif;jpg;jpeg;jpe;resx;tiff;tif;png;wav;mfcribbon-ms
+
+
+
+
+ Source Files
+
+
+ Source Files
+
+
+ Source Files
+
+
+
+
+ Header Files
+
+
+ Header Files
+
+
+ Header Files
+
+
+
+
+
+
diff --git a/src/modules/FileLocksmith/FileLocksmithCLI/main.cpp b/src/modules/FileLocksmith/FileLocksmithCLI/main.cpp
new file mode 100644
index 0000000000..67a4304b4e
--- /dev/null
+++ b/src/modules/FileLocksmith/FileLocksmithCLI/main.cpp
@@ -0,0 +1,71 @@
+#include "pch.h"
+#include "CLILogic.h"
+#include "FileLocksmithLib/FileLocksmith.h"
+#include
+#include "resource.h"
+#include
+#include
+
+struct RealProcessFinder : IProcessFinder
+{
+ std::vector find(const std::vector& paths) override
+ {
+ return find_processes_recursive(paths);
+ }
+};
+
+struct RealProcessTerminator : IProcessTerminator
+{
+ bool terminate(DWORD pid) override
+ {
+ HANDLE hProcess = OpenProcess(PROCESS_TERMINATE, FALSE, pid);
+ if (hProcess)
+ {
+ bool result = TerminateProcess(hProcess, 0);
+ CloseHandle(hProcess);
+ return result;
+ }
+ return false;
+ }
+};
+
+struct RealStringProvider : IStringProvider
+{
+ std::wstring GetString(UINT id) override
+ {
+ wchar_t buffer[4096];
+ int len = LoadStringW(GetModuleHandle(NULL), id, buffer, ARRAYSIZE(buffer));
+ if (len > 0)
+ {
+ return std::wstring(buffer, len);
+ }
+ return L"";
+ }
+};
+
+#ifndef UNIT_TEST
+int wmain(int argc, wchar_t* argv[])
+{
+ winrt::init_apartment();
+ LoggerHelpers::init_logger(L"FileLocksmithCLI", L"", LogSettings::fileLocksmithLoggerName);
+ Logger::info("FileLocksmithCLI started");
+
+ RealProcessFinder finder;
+ RealProcessTerminator terminator;
+ RealStringProvider strings;
+
+ auto result = run_command(argc, argv, finder, terminator, strings);
+
+ if (result.exit_code != 0)
+ {
+ Logger::error("Command failed with exit code {}", result.exit_code);
+ }
+ else
+ {
+ Logger::info("Command succeeded");
+ }
+
+ std::wcout << result.output;
+ return result.exit_code;
+}
+#endif
diff --git a/src/modules/FileLocksmith/FileLocksmithCLI/packages.config b/src/modules/FileLocksmith/FileLocksmithCLI/packages.config
new file mode 100644
index 0000000000..2e5039eb82
--- /dev/null
+++ b/src/modules/FileLocksmith/FileLocksmithCLI/packages.config
@@ -0,0 +1,4 @@
+
+
+
+
diff --git a/src/modules/FileLocksmith/FileLocksmithCLI/pch.cpp b/src/modules/FileLocksmith/FileLocksmithCLI/pch.cpp
new file mode 100644
index 0000000000..1d9f38c57d
--- /dev/null
+++ b/src/modules/FileLocksmith/FileLocksmithCLI/pch.cpp
@@ -0,0 +1 @@
+#include "pch.h"
diff --git a/src/modules/FileLocksmith/FileLocksmithCLI/pch.h b/src/modules/FileLocksmith/FileLocksmithCLI/pch.h
new file mode 100644
index 0000000000..6099342b41
--- /dev/null
+++ b/src/modules/FileLocksmith/FileLocksmithCLI/pch.h
@@ -0,0 +1,22 @@
+#pragma once
+
+#ifndef PCH_H
+#define PCH_H
+
+#define NOMINMAX
+#define WIN32_LEAN_AND_MEAN
+#include
+#include
+#include
+#include
+
+#include
+#include
+#include
+#include
+#include
+#include
+
+#include
+
+#endif // PCH_H
diff --git a/src/modules/FileLocksmith/FileLocksmithCLI/resource.h b/src/modules/FileLocksmith/FileLocksmithCLI/resource.h
new file mode 100644
index 0000000000..be12cac3ac
--- /dev/null
+++ b/src/modules/FileLocksmith/FileLocksmithCLI/resource.h
@@ -0,0 +1,16 @@
+// resource.h
+#pragma once
+
+#define IDS_USAGE 101
+#define IDS_NO_PROCESSES 102
+#define IDS_HEADER 103
+#define IDS_TERMINATED 104
+#define IDS_FAILED_TERMINATE 105
+#define IDS_FAILED_OPEN 106
+#define IDS_ERROR_NO_PATHS 107
+#define IDS_WARN_JSON_WAIT 108
+#define IDS_WAITING 109
+#define IDS_UNLOCKED 110
+#define IDS_TIMEOUT 111
+#define IDS_ERROR_INVALID_TIMEOUT 112
+#define IDS_ERROR_TIMEOUT_ARG 113
diff --git a/src/modules/FileLocksmith/FileLocksmithCLI/tests/FileLocksmithCLITests.cpp b/src/modules/FileLocksmith/FileLocksmithCLI/tests/FileLocksmithCLITests.cpp
new file mode 100644
index 0000000000..a67e42badf
--- /dev/null
+++ b/src/modules/FileLocksmith/FileLocksmithCLI/tests/FileLocksmithCLITests.cpp
@@ -0,0 +1,130 @@
+#include "pch.h"
+#include "CppUnitTest.h"
+#include "../CLILogic.h"
+#include
+
+using namespace Microsoft::VisualStudio::CppUnitTestFramework;
+
+namespace FileLocksmithCLIUnitTests
+{
+ struct MockProcessFinder : IProcessFinder
+ {
+ std::vector results;
+ std::vector find(const std::vector& paths) override
+ {
+ (void)paths;
+ return results;
+ }
+ };
+
+ struct MockProcessTerminator : IProcessTerminator
+ {
+ bool shouldSucceed = true;
+ std::vector terminatedPids;
+ bool terminate(DWORD pid) override
+ {
+ terminatedPids.push_back(pid);
+ return shouldSucceed;
+ }
+ };
+
+ struct MockStringProvider : IStringProvider
+ {
+ std::map strings;
+ std::wstring GetString(UINT id) override
+ {
+ if (strings.count(id)) return strings[id];
+ return L"String_" + std::to_wstring(id);
+ }
+ };
+
+ TEST_CLASS(CLITests)
+ {
+ public:
+
+ TEST_METHOD(TestNoArgs)
+ {
+ MockProcessFinder finder;
+ MockProcessTerminator terminator;
+ MockStringProvider strings;
+
+ wchar_t* argv[] = { (wchar_t*)L"exe" };
+ auto result = run_command(1, argv, finder, terminator, strings);
+
+ Assert::AreEqual(1, result.exit_code);
+ }
+
+ TEST_METHOD(TestHelp)
+ {
+ MockProcessFinder finder;
+ MockProcessTerminator terminator;
+ MockStringProvider strings;
+
+ wchar_t* argv[] = { (wchar_t*)L"exe", (wchar_t*)L"--help" };
+ auto result = run_command(2, argv, finder, terminator, strings);
+
+ Assert::AreEqual(0, result.exit_code);
+ }
+
+ TEST_METHOD(TestFindProcesses)
+ {
+ MockProcessFinder finder;
+ finder.results = { { L"process", 123, L"user", { L"file1" } } };
+ MockProcessTerminator terminator;
+ MockStringProvider strings;
+
+ wchar_t* argv[] = { (wchar_t*)L"exe", (wchar_t*)L"file1" };
+ auto result = run_command(2, argv, finder, terminator, strings);
+
+ Assert::AreEqual(0, result.exit_code);
+ Assert::IsTrue(result.output.find(L"123") != std::wstring::npos);
+ Assert::IsTrue(result.output.find(L"process") != std::wstring::npos);
+ }
+
+ TEST_METHOD(TestJsonOutput)
+ {
+ MockProcessFinder finder;
+ finder.results = { { L"process", 123, L"user", { L"file1" } } };
+ MockProcessTerminator terminator;
+ MockStringProvider strings;
+
+ wchar_t* argv[] = { (wchar_t*)L"exe", (wchar_t*)L"file1", (wchar_t*)L"--json" };
+ auto result = run_command(3, argv, finder, terminator, strings);
+
+ Microsoft::VisualStudio::CppUnitTestFramework::Logger::WriteMessage(result.output.c_str());
+
+ Assert::AreEqual(0, result.exit_code);
+ Assert::IsTrue(result.output.find(L"\"pid\"") != std::wstring::npos);
+ Assert::IsTrue(result.output.find(L"123") != std::wstring::npos);
+ }
+
+ TEST_METHOD(TestKill)
+ {
+ MockProcessFinder finder;
+ finder.results = { { L"process", 123, L"user", { L"file1" } } };
+ MockProcessTerminator terminator;
+ MockStringProvider strings;
+
+ wchar_t* argv[] = { (wchar_t*)L"exe", (wchar_t*)L"file1", (wchar_t*)L"--kill" };
+ auto result = run_command(3, argv, finder, terminator, strings);
+
+ Assert::AreEqual(0, result.exit_code);
+ Assert::AreEqual((size_t)1, terminator.terminatedPids.size());
+ Assert::AreEqual((DWORD)123, terminator.terminatedPids[0]);
+ }
+
+ TEST_METHOD(TestTimeout)
+ {
+ MockProcessFinder finder;
+ // Always return results so it waits
+ finder.results = { { L"process", 123, L"user", { L"file1" } } };
+ MockProcessTerminator terminator;
+ MockStringProvider strings;
+
+ wchar_t* argv[] = { (wchar_t*)L"exe", (wchar_t*)L"file1", (wchar_t*)L"--wait", (wchar_t*)L"--timeout", (wchar_t*)L"100" };
+ auto result = run_command(5, argv, finder, terminator, strings);
+
+ Assert::AreEqual(1, result.exit_code);
+ }
+ };
+}
diff --git a/src/modules/FileLocksmith/FileLocksmithCLI/tests/FileLocksmithCLIUnitTests.vcxproj b/src/modules/FileLocksmith/FileLocksmithCLI/tests/FileLocksmithCLIUnitTests.vcxproj
new file mode 100644
index 0000000000..4b423c0183
--- /dev/null
+++ b/src/modules/FileLocksmith/FileLocksmithCLI/tests/FileLocksmithCLIUnitTests.vcxproj
@@ -0,0 +1,84 @@
+
+
+
+
+ {A1B2C3D4-E5F6-7890-1234-567890ABCDEF}
+ Win32Proj
+ FileLocksmithCLIUnitTests
+ FileLocksmithCLI.UnitTests
+ 10.0
+
+
+
+
+ v143
+ DynamicLibrary
+ Unicode
+
+
+
+
+
+
+
+
+
+
+
+ ..\..\..\..\..\$(Platform)\$(Configuration)\tests\FileLocksmithCLI\
+
+
+
+ ..\;..\..\;..\..\..\..\;$(VCInstallDir)UnitTest\include;%(AdditionalIncludeDirectories)
+ WIN32;UNIT_TEST;%(PreprocessorDefinitions)
+ true
+ Use
+ pch.h
+ 26466;%(DisableSpecificWarnings)
+
+
+ $(VCInstallDir)UnitTest\lib;%(AdditionalLibraryDirectories)
+ shlwapi.lib;ntdll.lib;%(AdditionalDependencies)
+
+
+
+
+
+
+
+ Create
+
+
+
+ NotUsing
+
+
+
+
+ {9d52fd25-ef90-4f9a-a015-91efc5daf54f}
+
+
+ {d9b8fc84-322a-4f9f-bbb9-20915c47ddfd}
+
+
+ {6955446d-23f7-4023-9bb3-8657f904af99}
+
+
+ {1248566c-272a-43c0-88d6-e6675d569a09}
+
+
+
+
+
+
+
+
+
+
+
+ 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}.
+
+
+
+
+
diff --git a/src/modules/FileLocksmith/FileLocksmithCLI/tests/packages.config b/src/modules/FileLocksmith/FileLocksmithCLI/tests/packages.config
new file mode 100644
index 0000000000..2e5039eb82
--- /dev/null
+++ b/src/modules/FileLocksmith/FileLocksmithCLI/tests/packages.config
@@ -0,0 +1,4 @@
+
+
+
+
diff --git a/src/modules/FileLocksmith/FileLocksmithCLI/tests/pch.cpp b/src/modules/FileLocksmith/FileLocksmithCLI/tests/pch.cpp
new file mode 100644
index 0000000000..1d9f38c57d
--- /dev/null
+++ b/src/modules/FileLocksmith/FileLocksmithCLI/tests/pch.cpp
@@ -0,0 +1 @@
+#include "pch.h"
diff --git a/src/modules/FileLocksmith/FileLocksmithCLI/tests/pch.h b/src/modules/FileLocksmith/FileLocksmithCLI/tests/pch.h
new file mode 100644
index 0000000000..7041294e28
--- /dev/null
+++ b/src/modules/FileLocksmith/FileLocksmithCLI/tests/pch.h
@@ -0,0 +1,9 @@
+#pragma once
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include "CppUnitTest.h"
diff --git a/src/modules/FileLocksmith/FileLocksmithLib/FileLocksmith.h b/src/modules/FileLocksmith/FileLocksmithLib/FileLocksmith.h
new file mode 100644
index 0000000000..087df84a68
--- /dev/null
+++ b/src/modules/FileLocksmith/FileLocksmithLib/FileLocksmith.h
@@ -0,0 +1,9 @@
+#pragma once
+
+#include "ProcessResult.h"
+
+// Second version, checks handles towards files and all subfiles and folders of given dirs, if any.
+std::vector find_processes_recursive(const std::vector& paths);
+
+// Gives the full path of the executable, given the process id
+std::wstring pid_to_full_path(DWORD pid);
diff --git a/src/modules/FileLocksmith/FileLocksmithLib/FileLocksmithLib.vcxproj b/src/modules/FileLocksmith/FileLocksmithLib/FileLocksmithLib.vcxproj
index ebbeb20895..fdfb0c666f 100644
--- a/src/modules/FileLocksmith/FileLocksmithLib/FileLocksmithLib.vcxproj
+++ b/src/modules/FileLocksmith/FileLocksmithLib/FileLocksmithLib.vcxproj
@@ -34,9 +34,9 @@
Level3
true
- WIN32;_DEBUG;_LIB;%(PreprocessorDefinitions)
+ WIN32;_DEBUG;_LIB;FILELOCKSMITH_LIB_STATIC;%(PreprocessorDefinitions)
true
- ../../..;../..;
+ ..\FileLocksmithLibInterop;../../..;../..;
@@ -50,9 +50,9 @@
true
true
true
- WIN32;NDEBUG;_LIB;%(PreprocessorDefinitions)
+ WIN32;NDEBUG;_LIB;FILELOCKSMITH_LIB_STATIC;%(PreprocessorDefinitions)
true
- ../../..;../..;
+ ..\FileLocksmithLibInterop;../../..;../..;
@@ -68,13 +68,15 @@
-
+
+
+
Create
diff --git a/src/modules/FileLocksmith/FileLocksmithLib/FileLocksmithLib.vcxproj.filters b/src/modules/FileLocksmith/FileLocksmithLib/FileLocksmithLib.vcxproj.filters
index ed6b2674fc..c38c9b2d2e 100644
--- a/src/modules/FileLocksmith/FileLocksmithLib/FileLocksmithLib.vcxproj.filters
+++ b/src/modules/FileLocksmith/FileLocksmithLib/FileLocksmithLib.vcxproj.filters
@@ -38,6 +38,15 @@
Source Files
+
+ Source Files
+
+
+ Source Files
+
+
+ Source Files
+
Source Files
diff --git a/src/modules/FileLocksmith/FileLocksmithLib/ProcessResult.h b/src/modules/FileLocksmith/FileLocksmithLib/ProcessResult.h
new file mode 100644
index 0000000000..614e38a209
--- /dev/null
+++ b/src/modules/FileLocksmith/FileLocksmithLib/ProcessResult.h
@@ -0,0 +1,12 @@
+#pragma once
+#include
+#include
+#include
+
+struct ProcessResult
+{
+ std::wstring name;
+ DWORD pid;
+ std::wstring user;
+ std::vector files;
+};
diff --git a/src/modules/FileLocksmith/FileLocksmithLib/Settings.cpp b/src/modules/FileLocksmith/FileLocksmithLib/Settings.cpp
index de997144ca..c30387df5d 100644
--- a/src/modules/FileLocksmith/FileLocksmithLib/Settings.cpp
+++ b/src/modules/FileLocksmith/FileLocksmithLib/Settings.cpp
@@ -2,6 +2,7 @@
#include "Settings.h"
#include "Constants.h"
+#include
#include
#include
diff --git a/src/modules/FileLocksmith/FileLocksmithLib/pch.h b/src/modules/FileLocksmith/FileLocksmithLib/pch.h
deleted file mode 100644
index 885d5d62e4..0000000000
--- a/src/modules/FileLocksmith/FileLocksmithLib/pch.h
+++ /dev/null
@@ -1,13 +0,0 @@
-// pch.h: This is a precompiled header file.
-// Files listed below are compiled only once, improving build performance for future builds.
-// This also affects IntelliSense performance, including code completion and many code browsing features.
-// However, files listed here are ALL re-compiled if any one of them is updated between builds.
-// Do not add files here that you will be updating frequently as this negates the performance advantage.
-
-#ifndef PCH_H
-#define PCH_H
-
-// add headers that you want to pre-compile here
-#include "framework.h"
-
-#endif //PCH_H
diff --git a/src/modules/FileLocksmith/FileLocksmithLibInterop/pch.h b/src/modules/FileLocksmith/FileLocksmithLibInterop/pch.h
index f2449b1578..4f47976891 100644
--- a/src/modules/FileLocksmith/FileLocksmithLibInterop/pch.h
+++ b/src/modules/FileLocksmith/FileLocksmithLibInterop/pch.h
@@ -18,4 +18,6 @@
#include
#include
+#ifndef FILELOCKSMITH_LIB_STATIC
#include
+#endif
diff --git a/src/modules/LightSwitch/LightSwitchModuleInterface/dllmain.cpp b/src/modules/LightSwitch/LightSwitchModuleInterface/dllmain.cpp
index 11cfd412b0..bab4e30797 100644
--- a/src/modules/LightSwitch/LightSwitchModuleInterface/dllmain.cpp
+++ b/src/modules/LightSwitch/LightSwitchModuleInterface/dllmain.cpp
@@ -405,6 +405,7 @@ public:
{
m_enabled = true;
Logger::info(L"Enabling Light Switch module...");
+ Trace::Enable(true);
unsigned long powertoys_pid = GetCurrentProcessId();
std::wstring args = L"--pid " + std::to_wstring(powertoys_pid);
@@ -482,7 +483,8 @@ public:
CloseHandle(m_process);
m_process = nullptr;
}
-
+
+ Trace::Enable(false);
StopToggleListener();
}
@@ -539,6 +541,8 @@ public:
if (m_enabled)
{
Logger::trace(L"Light Switch hotkey pressed");
+ Trace::ShortcutInvoked();
+
if (!is_process_running())
{
enable();
diff --git a/src/modules/LightSwitch/LightSwitchModuleInterface/trace.cpp b/src/modules/LightSwitch/LightSwitchModuleInterface/trace.cpp
index 57fa1921f7..40fc67e679 100644
--- a/src/modules/LightSwitch/LightSwitchModuleInterface/trace.cpp
+++ b/src/modules/LightSwitch/LightSwitchModuleInterface/trace.cpp
@@ -19,12 +19,21 @@ void Trace::UnregisterProvider()
TraceLoggingUnregister(g_hProvider);
}
-void Trace::MyEvent()
+void Trace::Enable(bool enabled) noexcept
{
TraceLoggingWrite(
g_hProvider,
- "PowerToyName_MyEvent",
+ "LightSwitch_EnableLightSwitch",
+ ProjectTelemetryPrivacyDataTag(ProjectTelemetryTag_ProductAndServicePerformance),
+ TraceLoggingKeyword(PROJECT_KEYWORD_MEASURE),
+ TraceLoggingBoolean(enabled, "Enabled"));
+}
+
+void Trace::ShortcutInvoked() noexcept
+{
+ TraceLoggingWrite(
+ g_hProvider,
+ "LightSwitch_ShortcutInvoked",
ProjectTelemetryPrivacyDataTag(ProjectTelemetryTag_ProductAndServicePerformance),
- TraceLoggingBoolean(TRUE, "UTCReplace_AppSessionGuid"),
TraceLoggingKeyword(PROJECT_KEYWORD_MEASURE));
}
diff --git a/src/modules/LightSwitch/LightSwitchModuleInterface/trace.h b/src/modules/LightSwitch/LightSwitchModuleInterface/trace.h
index 55cdedb2ee..bfa32062b9 100644
--- a/src/modules/LightSwitch/LightSwitchModuleInterface/trace.h
+++ b/src/modules/LightSwitch/LightSwitchModuleInterface/trace.h
@@ -11,5 +11,6 @@ class Trace
public:
static void RegisterProvider();
static void UnregisterProvider();
- static void MyEvent();
+ static void Enable(bool enabled) noexcept;
+ static void ShortcutInvoked() noexcept;
};
diff --git a/src/modules/LightSwitch/LightSwitchService/LightSwitchService.cpp b/src/modules/LightSwitch/LightSwitchService/LightSwitchService.cpp
index b6684da54e..8919f4274b 100644
--- a/src/modules/LightSwitch/LightSwitchService/LightSwitchService.cpp
+++ b/src/modules/LightSwitch/LightSwitchService/LightSwitchService.cpp
@@ -14,6 +14,7 @@
#include "LightSwitchStateManager.h"
#include
#include
+#include
SERVICE_STATUS g_ServiceStatus = {};
SERVICE_STATUS_HANDLE g_StatusHandle = nullptr;
@@ -357,6 +358,8 @@ DWORD WINAPI ServiceWorkerThread(LPVOID lpParam)
int APIENTRY wWinMain(HINSTANCE, HINSTANCE, PWSTR, int)
{
+ Trace::LightSwitch::RegisterProvider();
+
if (powertoys_gpo::getConfiguredLightSwitchEnabledValue() == powertoys_gpo::gpo_rule_configured_disabled)
{
wchar_t msg[160];
@@ -364,12 +367,14 @@ int APIENTRY wWinMain(HINSTANCE, HINSTANCE, PWSTR, int)
msg,
L"Tried to start with a GPO policy setting the utility to always be disabled. Please contact your systems administrator.");
Logger::info(msg);
+ Trace::LightSwitch::UnregisterProvider();
return 0;
}
-
int argc = 0;
LPWSTR* argv = CommandLineToArgvW(GetCommandLineW(), &argc);
int rc = _tmain(argc, argv); // reuse your existing logic
LocalFree(argv);
+
+ Trace::LightSwitch::UnregisterProvider();
return rc;
}
\ No newline at end of file
diff --git a/src/modules/LightSwitch/LightSwitchService/LightSwitchService.vcxproj b/src/modules/LightSwitch/LightSwitchService/LightSwitchService.vcxproj
index e1c8052de6..b8e51ee489 100644
--- a/src/modules/LightSwitch/LightSwitchService/LightSwitchService.vcxproj
+++ b/src/modules/LightSwitch/LightSwitchService/LightSwitchService.vcxproj
@@ -80,6 +80,7 @@
+
@@ -94,6 +95,7 @@
+
diff --git a/src/modules/LightSwitch/LightSwitchService/LightSwitchService.vcxproj.filters b/src/modules/LightSwitch/LightSwitchService/LightSwitchService.vcxproj.filters
index 55c7bde39b..a704e87073 100644
--- a/src/modules/LightSwitch/LightSwitchService/LightSwitchService.vcxproj.filters
+++ b/src/modules/LightSwitch/LightSwitchService/LightSwitchService.vcxproj.filters
@@ -39,6 +39,9 @@
Source Files
+
+ Source Files
+
@@ -68,6 +71,9 @@
Header Files
+
+ Header Files
+
diff --git a/src/modules/LightSwitch/LightSwitchService/LightSwitchSettings.cpp b/src/modules/LightSwitch/LightSwitchService/LightSwitchSettings.cpp
index 46bf2add81..15e9f7c915 100644
--- a/src/modules/LightSwitch/LightSwitchService/LightSwitchSettings.cpp
+++ b/src/modules/LightSwitch/LightSwitchService/LightSwitchSettings.cpp
@@ -5,6 +5,7 @@
#include
#include
#include
+#include
using namespace std;
@@ -151,6 +152,7 @@ void LightSwitchSettings::LoadSettings()
if (m_settings.scheduleMode != newMode)
{
m_settings.scheduleMode = newMode;
+ Trace::LightSwitch::ScheduleModeToggled(val);
NotifyObservers(SettingId::ScheduleMode);
}
}
@@ -220,6 +222,8 @@ void LightSwitchSettings::LoadSettings()
}
}
+ bool themeTargetChanged = false;
+
// ChangeSystem
if (const auto jsonVal = values.get_bool_value(L"changeSystem"))
{
@@ -227,6 +231,7 @@ void LightSwitchSettings::LoadSettings()
if (m_settings.changeSystem != val)
{
m_settings.changeSystem = val;
+ themeTargetChanged = true;
NotifyObservers(SettingId::ChangeSystem);
}
}
@@ -238,6 +243,7 @@ void LightSwitchSettings::LoadSettings()
if (m_settings.changeApps != val)
{
m_settings.changeApps = val;
+ themeTargetChanged = true;
NotifyObservers(SettingId::ChangeApps);
}
}
@@ -281,6 +287,12 @@ void LightSwitchSettings::LoadSettings()
m_settings.lightModeProfile = val;
}
}
+
+ // For ChangeSystem/ChangeApps changes, log telemetry
+ if (themeTargetChanged)
+ {
+ Trace::LightSwitch::ThemeTargetChanged(m_settings.changeApps, m_settings.changeSystem);
+ }
}
catch (...)
{
diff --git a/src/modules/LightSwitch/LightSwitchService/trace.cpp b/src/modules/LightSwitch/LightSwitchService/trace.cpp
new file mode 100644
index 0000000000..99afe7a95d
--- /dev/null
+++ b/src/modules/LightSwitch/LightSwitchService/trace.cpp
@@ -0,0 +1,43 @@
+#include "pch.h"
+#include "trace.h"
+
+// Telemetry strings should not be localized.
+#define LoggingProviderKey "Microsoft.PowerToys"
+
+TRACELOGGING_DEFINE_PROVIDER(
+ g_hProvider,
+ LoggingProviderKey,
+ // {38e8889b-9731-53f5-e901-e8a7c1753074}
+ (0x38e8889b, 0x9731, 0x53f5, 0xe9, 0x01, 0xe8, 0xa7, 0xc1, 0x75, 0x30, 0x74),
+ TraceLoggingOptionProjectTelemetry());
+
+void Trace::LightSwitch::RegisterProvider()
+{
+ TraceLoggingRegister(g_hProvider);
+}
+
+void Trace::LightSwitch::UnregisterProvider()
+{
+ TraceLoggingUnregister(g_hProvider);
+}
+
+void Trace::LightSwitch::ScheduleModeToggled(const std::wstring& newMode) noexcept
+{
+ TraceLoggingWriteWrapper(
+ g_hProvider,
+ "LightSwitch_ScheduleModeToggled",
+ ProjectTelemetryPrivacyDataTag(ProjectTelemetryTag_ProductAndServicePerformance),
+ TraceLoggingKeyword(PROJECT_KEYWORD_MEASURE),
+ TraceLoggingWideString(newMode.c_str(), "NewMode"));
+}
+
+void Trace::LightSwitch::ThemeTargetChanged(bool changeApps, bool changeSystem) noexcept
+{
+ TraceLoggingWriteWrapper(
+ g_hProvider,
+ "LightSwitch_ThemeTargetChanged",
+ ProjectTelemetryPrivacyDataTag(ProjectTelemetryTag_ProductAndServicePerformance),
+ TraceLoggingKeyword(PROJECT_KEYWORD_MEASURE),
+ TraceLoggingBoolean(changeApps, "ChangeApps"),
+ TraceLoggingBoolean(changeSystem, "ChangeSystem"));
+}
\ No newline at end of file
diff --git a/src/modules/LightSwitch/LightSwitchService/trace.h b/src/modules/LightSwitch/LightSwitchService/trace.h
new file mode 100644
index 0000000000..a06177075b
--- /dev/null
+++ b/src/modules/LightSwitch/LightSwitchService/trace.h
@@ -0,0 +1,17 @@
+#pragma once
+
+#include
+#include
+
+class Trace
+{
+public:
+ class LightSwitch : public telemetry::TraceBase
+ {
+ public:
+ static void RegisterProvider();
+ static void UnregisterProvider();
+ static void ScheduleModeToggled(const std::wstring& newMode) noexcept;
+ static void ThemeTargetChanged(bool changeApps, bool changeSystem) noexcept;
+ };
+};
diff --git a/src/modules/MouseUtils/FindMyMouse/FindMyMouse.cpp b/src/modules/MouseUtils/FindMyMouse/FindMyMouse.cpp
index f953af0fdd..1c408175a0 100644
--- a/src/modules/MouseUtils/FindMyMouse/FindMyMouse.cpp
+++ b/src/modules/MouseUtils/FindMyMouse/FindMyMouse.cpp
@@ -77,10 +77,8 @@ protected:
int m_sonarRadius = FIND_MY_MOUSE_DEFAULT_SPOTLIGHT_RADIUS;
int m_sonarZoomFactor = FIND_MY_MOUSE_DEFAULT_SPOTLIGHT_INITIAL_ZOOM;
DWORD m_fadeDuration = FIND_MY_MOUSE_DEFAULT_ANIMATION_DURATION_MS;
- int m_finalAlphaNumerator = 100; // legacy (root now always animates to 1.0; kept for GDI fallback compatibility)
std::vector m_excludedApps;
int m_shakeMinimumDistance = FIND_MY_MOUSE_DEFAULT_SHAKE_MINIMUM_DISTANCE;
- static constexpr int FinalAlphaDenominator = 100;
winrt::Microsoft::UI::Dispatching::DispatcherQueueController m_dispatcherQueueController{ nullptr };
// Don't consider movements started past these milliseconds to detect shaking.
@@ -155,7 +153,7 @@ private:
void DetectShake();
bool KeyboardInputCanActivate();
- void StartSonar();
+ void StartSonar(FindMyMouseActivationMethod activationMethod);
void StopSonar();
};
@@ -275,7 +273,7 @@ LRESULT SuperSonar::BaseWndProc(UINT message, WPARAM wParam, LPARAM lParam) n
{
if (m_sonarStart == NoSonar)
{
- StartSonar();
+ StartSonar(FindMyMouseActivationMethod::Shortcut);
}
else
{
@@ -384,7 +382,7 @@ void SuperSonar::OnSonarKeyboardInput(RAWINPUT const& input)
IsEqual(m_lastKeyPos, ptCursor))
{
m_sonarState = SonarState::ControlDown2;
- StartSonar();
+ StartSonar(m_activationMethod);
}
else
{
@@ -451,7 +449,7 @@ void SuperSonar::DetectShake()
if (diagonal > 0 && distanceTravelled / diagonal > (m_shakeFactor / 100.f))
{
m_movementHistory.clear();
- StartSonar();
+ StartSonar(m_activationMethod);
}
}
@@ -519,7 +517,7 @@ void SuperSonar::OnSonarMouseInput(RAWINPUT const& input)
}
template
-void SuperSonar::StartSonar()
+void SuperSonar::StartSonar(FindMyMouseActivationMethod activationMethod)
{
// Don't activate if game mode is on.
if (m_doNotActivateOnGameMode && detect_game_mode())
@@ -532,7 +530,7 @@ void SuperSonar::StartSonar()
return;
}
- Trace::MousePointerFocused();
+ Trace::MousePointerFocused(static_cast(activationMethod));
// Cover the entire virtual screen.
// HACK: Draw with 1 pixel off. Otherwise, Windows glitches the task bar transparency when a transparent window fill the whole screen.
SetWindowPos(m_hwnd, HWND_TOPMOST, GetSystemMetrics(SM_XVIRTUALSCREEN) + 1, GetSystemMetrics(SM_YVIRTUALSCREEN) + 1, GetSystemMetrics(SM_CXVIRTUALSCREEN) - 2, GetSystemMetrics(SM_CYVIRTUALSCREEN) - 2, 0);
@@ -816,13 +814,16 @@ private:
// Dim color (source)
m_dimColorBrush = m_compositor.CreateColorBrush(m_backgroundColor);
// Radial gradient mask (center transparent, outer opaque)
+ // Fixed feather width: 1px for radius < 300, 2px for radius >= 300
+ const float featherPixels = (m_sonarRadius >= 300) ? 2.0f : 1.0f;
+ const float featherOffset = 1.0f - featherPixels / (rDip * zoom);
m_spotlightMaskGradient = m_compositor.CreateRadialGradientBrush();
m_spotlightMaskGradient.MappingMode(muxc::CompositionMappingMode::Absolute);
m_maskStopCenter = m_compositor.CreateColorGradientStop();
m_maskStopCenter.Offset(0.0f);
m_maskStopCenter.Color(winrt::Windows::UI::ColorHelper::FromArgb(0, 0, 0, 0));
m_maskStopInner = m_compositor.CreateColorGradientStop();
- m_maskStopInner.Offset(0.995f);
+ m_maskStopInner.Offset(featherOffset);
m_maskStopInner.Color(winrt::Windows::UI::ColorHelper::FromArgb(0, 0, 0, 0));
m_maskStopOuter = m_compositor.CreateColorGradientStop();
m_maskStopOuter.Offset(1.0f);
@@ -852,23 +853,7 @@ private:
m_root.ImplicitAnimations(collection);
// 6) Spotlight radius shrinks as opacity increases (expression animation)
- auto radiusExpression = m_compositor.CreateExpressionAnimation();
- radiusExpression.SetReferenceParameter(L"Root", m_root);
-
- wchar_t expressionText[256];
- winrt::check_hresult(StringCchPrintfW(
- expressionText, ARRAYSIZE(expressionText), L"Lerp(Vector2(%d, %d), Vector2(%d, %d), Root.Opacity)", m_sonarRadius * m_sonarZoomFactor, m_sonarRadius * m_sonarZoomFactor, m_sonarRadius, m_sonarRadius));
-
- radiusExpression.Expression(expressionText);
- m_spotlightMaskGradient.StartAnimation(L"EllipseRadius", radiusExpression);
- // Also animate spotlight geometry radius for visual consistency
- if (m_circleGeometry)
- {
- auto radiusExpression2 = m_compositor.CreateExpressionAnimation();
- radiusExpression2.SetReferenceParameter(L"Root", m_root);
- radiusExpression2.Expression(expressionText);
- m_circleGeometry.StartAnimation(L"Radius", radiusExpression2);
- }
+ SetupRadiusAnimations(rDip * zoom, rDip, featherPixels);
// Composition created successfully
return true;
@@ -887,6 +872,41 @@ private:
}
}
+ // Helper to setup radius and feather expression animations
+ void SetupRadiusAnimations(float startRadiusDip, float endRadiusDip, float featherPixels)
+ {
+ // Radius expression: shrinks from startRadiusDip to endRadiusDip as opacity goes 0->1
+ auto radiusExpression = m_compositor.CreateExpressionAnimation();
+ radiusExpression.SetReferenceParameter(L"Root", m_root);
+ wchar_t expressionText[256];
+ winrt::check_hresult(StringCchPrintfW(
+ expressionText, ARRAYSIZE(expressionText),
+ L"Lerp(Vector2(%.1f, %.1f), Vector2(%.1f, %.1f), Root.Opacity)",
+ startRadiusDip, startRadiusDip, endRadiusDip, endRadiusDip));
+ radiusExpression.Expression(expressionText);
+ m_spotlightMaskGradient.StartAnimation(L"EllipseRadius", radiusExpression);
+
+ // Feather expression: maintains fixed pixel width as radius changes
+ auto featherExpression = m_compositor.CreateExpressionAnimation();
+ featherExpression.SetReferenceParameter(L"Root", m_root);
+ wchar_t featherExpressionText[256];
+ winrt::check_hresult(StringCchPrintfW(
+ featherExpressionText, ARRAYSIZE(featherExpressionText),
+ L"1.0f - %.1ff / Lerp(%.1ff, %.1ff, Root.Opacity)",
+ featherPixels, startRadiusDip, endRadiusDip));
+ featherExpression.Expression(featherExpressionText);
+ m_maskStopInner.StartAnimation(L"Offset", featherExpression);
+
+ // Circle geometry radius for visual consistency
+ if (m_circleGeometry)
+ {
+ auto radiusExpression2 = m_compositor.CreateExpressionAnimation();
+ radiusExpression2.SetReferenceParameter(L"Root", m_root);
+ radiusExpression2.Expression(expressionText);
+ m_circleGeometry.StartAnimation(L"Radius", radiusExpression2);
+ }
+ }
+
void UpdateIslandSize()
{
if (!m_island)
@@ -964,27 +984,21 @@ public:
const float scale = static_cast(m_surface.XamlRoot().RasterizationScale());
const float rDip = m_sonarRadiusFloat / scale;
const float zoom = static_cast(m_sonarZoomFactor);
+ const float featherPixels = (m_sonarRadius >= 300) ? 2.0f : 1.0f;
+ const float startRadiusDip = rDip * zoom;
m_spotlightMaskGradient.StopAnimation(L"EllipseRadius");
- m_spotlightMaskGradient.EllipseCenter({ rDip * zoom, rDip * zoom });
- if (m_spotlight)
- {
- m_spotlight.Size({ rDip * 2 * zoom, rDip * 2 * zoom });
- m_circleShape.Offset({ rDip * zoom, rDip * zoom });
- }
- auto radiusExpression = m_compositor.CreateExpressionAnimation();
- radiusExpression.SetReferenceParameter(L"Root", m_root);
- wchar_t expressionText[256];
- winrt::check_hresult(StringCchPrintfW(expressionText, ARRAYSIZE(expressionText), L"Lerp(Vector2(%d, %d), Vector2(%d, %d), Root.Opacity)", m_sonarRadius * m_sonarZoomFactor, m_sonarRadius * m_sonarZoomFactor, m_sonarRadius, m_sonarRadius));
- radiusExpression.Expression(expressionText);
- m_spotlightMaskGradient.StartAnimation(L"EllipseRadius", radiusExpression);
+ m_maskStopInner.StopAnimation(L"Offset");
if (m_circleGeometry)
{
m_circleGeometry.StopAnimation(L"Radius");
- auto radiusExpression2 = m_compositor.CreateExpressionAnimation();
- radiusExpression2.SetReferenceParameter(L"Root", m_root);
- radiusExpression2.Expression(expressionText);
- m_circleGeometry.StartAnimation(L"Radius", radiusExpression2);
}
+ m_spotlightMaskGradient.EllipseCenter({ startRadiusDip, startRadiusDip });
+ if (m_spotlight)
+ {
+ m_spotlight.Size({ rDip * 2 * zoom, rDip * 2 * zoom });
+ m_circleShape.Offset({ startRadiusDip, startRadiusDip });
+ }
+ SetupRadiusAnimations(startRadiusDip, rDip, featherPixels);
}
});
if (!enqueueSucceeded)
@@ -1018,202 +1032,6 @@ private:
muxc::ScalarKeyFrameAnimation m_animation{ nullptr };
};
-template
-struct GdiSonar : SuperSonar
-{
- LRESULT WndProc(UINT message, WPARAM wParam, LPARAM lParam) noexcept
- {
- switch (message)
- {
- case WM_CREATE:
- SetLayeredWindowAttributes(this->m_hwnd, 0, 0, LWA_ALPHA);
- break;
-
- case WM_TIMER:
- switch (wParam)
- {
- case TIMER_ID_FADE:
- OnFadeTimer();
- break;
- }
- break;
-
- case WM_PAINT:
- this->Shim()->OnPaint();
- break;
- }
- return this->BaseWndProc(message, wParam, lParam);
- }
-
- void BeforeMoveSonar() { this->Shim()->InvalidateSonar(); }
- void AfterMoveSonar() { this->Shim()->InvalidateSonar(); }
-
- void SetSonarVisibility(bool visible)
- {
- m_alphaTarget = visible ? MaxAlpha : 0;
- m_fadeStart = GetTickCount() - FadeFramePeriod;
- SetTimer(this->m_hwnd, TIMER_ID_FADE, FadeFramePeriod, nullptr);
- OnFadeTimer();
- }
-
- void OnFadeTimer()
- {
- auto now = GetTickCount();
- auto step = (int)((now - m_fadeStart) * MaxAlpha / this->m_fadeDuration);
-
- this->Shim()->InvalidateSonar();
- if (m_alpha < m_alphaTarget)
- {
- m_alpha += step;
- if (m_alpha > m_alphaTarget)
- m_alpha = m_alphaTarget;
- }
- else if (m_alpha > m_alphaTarget)
- {
- m_alpha -= step;
- if (m_alpha < m_alphaTarget)
- m_alpha = m_alphaTarget;
- }
- SetLayeredWindowAttributes(this->m_hwnd, 0, (BYTE)m_alpha, LWA_ALPHA);
- this->Shim()->InvalidateSonar();
- if (m_alpha == m_alphaTarget)
- {
- KillTimer(this->m_hwnd, TIMER_ID_FADE);
- if (m_alpha == 0)
- {
- ShowWindow(this->m_hwnd, SW_HIDE);
- }
- }
- else
- {
- ShowWindow(this->m_hwnd, SW_SHOWNOACTIVATE);
- }
- }
-
-protected:
- int CurrentSonarRadius()
- {
- int range = MaxAlpha - m_alpha;
- int radius = this->m_sonarRadius + this->m_sonarRadius * range * (this->m_sonarZoomFactor - 1) / MaxAlpha;
- return radius;
- }
-
-private:
- static constexpr DWORD FadeFramePeriod = 10;
- int MaxAlpha = SuperSonar::m_finalAlphaNumerator * 255 / SuperSonar::FinalAlphaDenominator;
- static constexpr DWORD TIMER_ID_FADE = 101;
-
-private:
- int m_alpha = 0;
- int m_alphaTarget = 0;
- DWORD m_fadeStart = 0;
-};
-
-struct GdiSpotlight : GdiSonar
-{
- void InvalidateSonar()
- {
- RECT rc;
- auto radius = CurrentSonarRadius();
- rc.left = this->m_sonarPos.x - radius;
- rc.top = this->m_sonarPos.y - radius;
- rc.right = this->m_sonarPos.x + radius;
- rc.bottom = this->m_sonarPos.y + radius;
- InvalidateRect(this->m_hwnd, &rc, FALSE);
- }
-
- void OnPaint()
- {
- PAINTSTRUCT ps;
- BeginPaint(this->m_hwnd, &ps);
-
- auto radius = CurrentSonarRadius();
- auto spotlight = CreateRoundRectRgn(
- this->m_sonarPos.x - radius, this->m_sonarPos.y - radius, this->m_sonarPos.x + radius, this->m_sonarPos.y + radius, radius * 2, radius * 2);
-
- FillRgn(ps.hdc, spotlight, static_cast(GetStockObject(WHITE_BRUSH)));
- Sleep(1000 / 60);
- ExtSelectClipRgn(ps.hdc, spotlight, RGN_DIFF);
- FillRect(ps.hdc, &ps.rcPaint, static_cast(GetStockObject(BLACK_BRUSH)));
- DeleteObject(spotlight);
-
- EndPaint(this->m_hwnd, &ps);
- }
-};
-
-struct GdiCrosshairs : GdiSonar
-{
- void InvalidateSonar()
- {
- RECT rc;
- auto radius = CurrentSonarRadius();
- GetClientRect(m_hwnd, &rc);
- rc.left = m_sonarPos.x - radius;
- rc.right = m_sonarPos.x + radius;
- InvalidateRect(m_hwnd, &rc, FALSE);
-
- GetClientRect(m_hwnd, &rc);
- rc.top = m_sonarPos.y - radius;
- rc.bottom = m_sonarPos.y + radius;
- InvalidateRect(m_hwnd, &rc, FALSE);
- }
-
- void OnPaint()
- {
- PAINTSTRUCT ps;
- BeginPaint(this->m_hwnd, &ps);
-
- auto radius = CurrentSonarRadius();
- RECT rc;
-
- HBRUSH white = static_cast(GetStockObject(WHITE_BRUSH));
-
- rc.left = m_sonarPos.x - radius;
- rc.top = ps.rcPaint.top;
- rc.right = m_sonarPos.x + radius;
- rc.bottom = ps.rcPaint.bottom;
- FillRect(ps.hdc, &rc, white);
-
- rc.left = ps.rcPaint.left;
- rc.top = m_sonarPos.y - radius;
- rc.right = ps.rcPaint.right;
- rc.bottom = m_sonarPos.y + radius;
- FillRect(ps.hdc, &rc, white);
-
- HBRUSH black = static_cast(GetStockObject(BLACK_BRUSH));
-
- // Top left
- rc.left = ps.rcPaint.left;
- rc.top = ps.rcPaint.top;
- rc.right = m_sonarPos.x - radius;
- rc.bottom = m_sonarPos.y - radius;
- FillRect(ps.hdc, &rc, black);
-
- // Top right
- rc.left = m_sonarPos.x + radius;
- rc.top = ps.rcPaint.top;
- rc.right = ps.rcPaint.right;
- rc.bottom = m_sonarPos.y - radius;
- FillRect(ps.hdc, &rc, black);
-
- // Bottom left
- rc.left = ps.rcPaint.left;
- rc.top = m_sonarPos.y + radius;
- rc.right = m_sonarPos.x - radius;
- rc.bottom = ps.rcPaint.bottom;
- FillRect(ps.hdc, &rc, black);
-
- // Bottom right
- rc.left = m_sonarPos.x + radius;
- rc.top = m_sonarPos.y + radius;
- rc.right = ps.rcPaint.right;
- rc.bottom = ps.rcPaint.bottom;
- FillRect(ps.hdc, &rc, black);
-
- EndPaint(this->m_hwnd, &ps);
- }
-};
-
#pragma endregion Super_Sonar_Base_Code
#pragma region Super_Sonar_API
@@ -1284,4 +1102,4 @@ HWND GetSonarHwnd() noexcept
return nullptr;
}
-#pragma endregion Super_Sonar_API
+#pragma endregion Super_Sonar_API
\ No newline at end of file
diff --git a/src/modules/MouseUtils/FindMyMouse/FindMyMouse.vcxproj b/src/modules/MouseUtils/FindMyMouse/FindMyMouse.vcxproj
index bfed4af15d..8e606b88ad 100644
--- a/src/modules/MouseUtils/FindMyMouse/FindMyMouse.vcxproj
+++ b/src/modules/MouseUtils/FindMyMouse/FindMyMouse.vcxproj
@@ -154,4 +154,4 @@
-
\ No newline at end of file
+
diff --git a/src/modules/MouseUtils/FindMyMouse/trace.cpp b/src/modules/MouseUtils/FindMyMouse/trace.cpp
index bf79461e9a..6309596cf8 100644
--- a/src/modules/MouseUtils/FindMyMouse/trace.cpp
+++ b/src/modules/MouseUtils/FindMyMouse/trace.cpp
@@ -22,11 +22,12 @@ void Trace::EnableFindMyMouse(const bool enabled) noexcept
}
// Log that the user activated the module by focusing the mouse pointer
-void Trace::MousePointerFocused() noexcept
+void Trace::MousePointerFocused(const int activationMethod) noexcept
{
TraceLoggingWriteWrapper(
g_hProvider,
"FindMyMouse_MousePointerFocused",
ProjectTelemetryPrivacyDataTag(ProjectTelemetryTag_ProductAndServicePerformance),
- TraceLoggingKeyword(PROJECT_KEYWORD_MEASURE));
+ TraceLoggingKeyword(PROJECT_KEYWORD_MEASURE),
+ TraceLoggingInt32(activationMethod, "ActivationMethod"));
}
diff --git a/src/modules/MouseUtils/FindMyMouse/trace.h b/src/modules/MouseUtils/FindMyMouse/trace.h
index 59d3183b5b..90933e9403 100644
--- a/src/modules/MouseUtils/FindMyMouse/trace.h
+++ b/src/modules/MouseUtils/FindMyMouse/trace.h
@@ -9,5 +9,6 @@ public:
static void EnableFindMyMouse(const bool enabled) noexcept;
// Log that the user activated the module by focusing the mouse pointer
- static void MousePointerFocused() noexcept;
+ // activationMethod: 0 = DoubleLeftControlKey, 1 = DoubleRightControlKey, 2 = ShakeMouse, 3 = Shortcut
+ static void MousePointerFocused(const int activationMethod) noexcept;
};
diff --git a/src/modules/MouseWithoutBorders/App/Class/Common.cs b/src/modules/MouseWithoutBorders/App/Class/Common.cs
deleted file mode 100644
index 9a34500b52..0000000000
--- a/src/modules/MouseWithoutBorders/App/Class/Common.cs
+++ /dev/null
@@ -1,1661 +0,0 @@
-// 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.Diagnostics.CodeAnalysis;
-using System.Drawing;
-using System.Drawing.Imaging;
-using System.Globalization;
-using System.IO;
-using System.Linq;
-using System.Net;
-using System.Net.NetworkInformation;
-using System.Net.Sockets;
-using System.Runtime.InteropServices;
-using System.Security.Cryptography;
-using System.Text;
-using System.Threading;
-using System.Windows.Forms;
-
-using Microsoft.PowerToys.Settings.UI.Library;
-
-//
-// Most of the helper methods.
-//
-//
-// 2008 created by Truong Do (ductdo).
-// 2009-... modified by Truong Do (TruongDo).
-// 2023- Included in PowerToys.
-//
-using MouseWithoutBorders.Class;
-using MouseWithoutBorders.Core;
-using MouseWithoutBorders.Exceptions;
-
-using Clipboard = MouseWithoutBorders.Core.Clipboard;
-using Thread = MouseWithoutBorders.Core.Thread;
-
-// Log is enough
-[module: SuppressMessage("Microsoft.Design", "CA1031:DoNotCatchGeneralExceptionTypes", Scope = "member", Target = "MouseWithoutBorders.Common.#CheckClipboard()", Justification = "Dotnet port with style preservation")]
-[module: SuppressMessage("Microsoft.Design", "CA1031:DoNotCatchGeneralExceptionTypes", Scope = "member", Target = "MouseWithoutBorders.Common.#CheckForDesktopSwitchEvent(System.Boolean)", Justification = "Dotnet port with style preservation")]
-[module: SuppressMessage("Microsoft.Design", "CA1031:DoNotCatchGeneralExceptionTypes", Scope = "member", Target = "MouseWithoutBorders.Common.#SetAsStartupItem(System.Boolean)", Justification = "Dotnet port with style preservation")]
-[module: SuppressMessage("Microsoft.Design", "CA1031:DoNotCatchGeneralExceptionTypes", Scope = "member", Target = "MouseWithoutBorders.Common.#HelperThread()", Justification = "Dotnet port with style preservation")]
-[module: SuppressMessage("Microsoft.Design", "CA1031:DoNotCatchGeneralExceptionTypes", Scope = "member", Target = "MouseWithoutBorders.Common.#GetMyStorageDir()", Justification = "Dotnet port with style preservation")]
-[module: SuppressMessage("Microsoft.Design", "CA1031:DoNotCatchGeneralExceptionTypes", Scope = "member", Target = "MouseWithoutBorders.Common.#MouseEvent(MouseWithoutBorders.MOUSEDATA)", Justification = "Dotnet port with style preservation")]
-[module: SuppressMessage("Microsoft.Design", "CA1031:DoNotCatchGeneralExceptionTypes", Scope = "member", Target = "MouseWithoutBorders.Common.#KeybdEvent(MouseWithoutBorders.KEYBDDATA)", Justification = "Dotnet port with style preservation")]
-[module: SuppressMessage("Microsoft.Design", "CA1031:DoNotCatchGeneralExceptionTypes", Scope = "member", Target = "MouseWithoutBorders.Common.#ImpersonateLoggedOnUserAndDoSomething(System.Threading.ThreadStart)", Justification = "Dotnet port with style preservation")]
-[module: SuppressMessage("Microsoft.Design", "CA1031:DoNotCatchGeneralExceptionTypes", Scope = "member", Target = "MouseWithoutBorders.Common.#StartMouseWithoutBordersService()", Justification = "Dotnet port with style preservation")]
-[module: SuppressMessage("Microsoft.Design", "CA1031:DoNotCatchGeneralExceptionTypes", Scope = "member", Target = "MouseWithoutBorders.Common.#HookClipboard()", Justification = "Dotnet port with style preservation")]
-[module: SuppressMessage("Microsoft.Design", "CA1031:DoNotCatchGeneralExceptionTypes", Scope = "member", Target = "MouseWithoutBorders.Common.#ReceiveClipboardData(MouseWithoutBorders.DATA,System.Boolean)", Justification = "Dotnet port with style preservation")]
-[module: SuppressMessage("Microsoft.Design", "CA1031:DoNotCatchGeneralExceptionTypes", Scope = "member", Target = "MouseWithoutBorders.Common.#ReceiverCallback(System.Object)", Justification = "Dotnet port with style preservation")]
-[module: SuppressMessage("Microsoft.Design", "CA1031:DoNotCatchGeneralExceptionTypes", Scope = "member", Target = "MouseWithoutBorders.Common.#ConnectAndGetData(System.Object)", Justification = "Dotnet port with style preservation")]
-[module: SuppressMessage("Microsoft.Design", "CA1031:DoNotCatchGeneralExceptionTypes", Scope = "member", Target = "MouseWithoutBorders.Common.#CheckNewVersion()", Justification = "Dotnet port with style preservation")]
-[module: SuppressMessage("Microsoft.Design", "CA1031:DoNotCatchGeneralExceptionTypes", Scope = "member", Target = "MouseWithoutBorders.Common.#StartServiceAndSendLogoffSignal()", Justification = "Dotnet port with style preservation")]
-[module: SuppressMessage("Microsoft.Design", "CA1031:DoNotCatchGeneralExceptionTypes", Scope = "member", Target = "MouseWithoutBorders.Common.#GetScreenConfig()", Justification = "Dotnet port with style preservation")]
-[module: SuppressMessage("Microsoft.Design", "CA1031:DoNotCatchGeneralExceptionTypes", Scope = "member", Target = "MouseWithoutBorders.Common.#CaptureScreen()", Justification = "Dotnet port with style preservation")]
-[module: SuppressMessage("Microsoft.Design", "CA1031:DoNotCatchGeneralExceptionTypes", Scope = "member", Target = "MouseWithoutBorders.Common.#InitEncryption()", Justification = "Dotnet port with style preservation")]
-[module: SuppressMessage("Microsoft.Design", "CA1031:DoNotCatchGeneralExceptionTypes", Scope = "member", Target = "MouseWithoutBorders.Common.#ToggleIcon()", Justification = "Dotnet port with style preservation")]
-[module: SuppressMessage("Microsoft.Design", "CA1031:DoNotCatchGeneralExceptionTypes", Scope = "member", Target = "MouseWithoutBorders.Common.#GetNameAndIPAddresses()", Justification = "Dotnet port with style preservation")]
-[module: SuppressMessage("Microsoft.Design", "CA1031:DoNotCatchGeneralExceptionTypes", Scope = "member", Target = "MouseWithoutBorders.Common.#Cleanup()", Justification = "Dotnet port with style preservation")]
-[module: SuppressMessage("Microsoft.Maintainability", "CA1506:AvoidExcessiveClassCoupling", Scope = "type", Target = "MouseWithoutBorders.Common", Justification = "Dotnet port with style preservation")]
-[module: SuppressMessage("Microsoft.Maintainability", "CA1502:AvoidExcessiveComplexity", Scope = "member", Target = "MouseWithoutBorders.Common.#ConnectAndGetData(System.Object)", Justification = "Dotnet port with style preservation")]
-[module: SuppressMessage("Microsoft.Maintainability", "CA1502:AvoidExcessiveComplexity", Scope = "member", Target = "MouseWithoutBorders.Common.#ProcessPackage(MouseWithoutBorders.DATA)", Justification = "Dotnet port with style preservation")]
-[module: SuppressMessage("Microsoft.Design", "CA1031:DoNotCatchGeneralExceptionTypes", Scope = "member", Target = "MouseWithoutBorders.Common.#SetOEMBackground(System.Boolean)", Justification = "Dotnet port with style preservation")]
-[module: SuppressMessage("Microsoft.Design", "CA1031:DoNotCatchGeneralExceptionTypes", Scope = "member", Target = "MouseWithoutBorders.Common.#get_Machine_Pool()", Justification = "Dotnet port with style preservation")]
-[module: SuppressMessage("Microsoft.Design", "CA1031:DoNotCatchGeneralExceptionTypes", Scope = "member", Target = "MouseWithoutBorders.Common.#SetOEMBackground(System.Boolean,System.String)", Justification = "Dotnet port with style preservation")]
-[module: SuppressMessage("Microsoft.Design", "CA1031:DoNotCatchGeneralExceptionTypes", Scope = "member", Target = "MouseWithoutBorders.Common.#GetNewImageAndSaveTo(System.String,System.String)", Justification = "Dotnet port with style preservation")]
-[module: SuppressMessage("Microsoft.Design", "CA1031:DoNotCatchGeneralExceptionTypes", Scope = "member", Target = "MouseWithoutBorders.Common.#CreateLowIntegrityProcess(System.String,System.String,System.Int32)", Justification = "Dotnet port with style preservation")]
-[module: SuppressMessage("Microsoft.Usage", "CA1806:DoNotIgnoreMethodResults", Scope = "member", Target = "MouseWithoutBorders.Common.#LogAll()", MessageId = "System.String.Format(System.IFormatProvider,System.String,System.Object[])", Justification = "Dotnet port with style preservation")]
-[module: SuppressMessage("Microsoft.Usage", "CA1806:DoNotIgnoreMethodResults", Scope = "member", Target = "MouseWithoutBorders.Common.#CheckForDesktopSwitchEvent(System.Boolean)", MessageId = "MouseWithoutBorders.NativeMethods.SendMessage(System.IntPtr,System.Int32,System.IntPtr,System.IntPtr)", Justification = "Dotnet port with style preservation")]
-[module: SuppressMessage("Microsoft.Usage", "CA1806:DoNotIgnoreMethodResults", Scope = "member", Target = "MouseWithoutBorders.Common.#DragDropStep04()", MessageId = "MouseWithoutBorders.NativeMethods.SendMessage(System.IntPtr,System.Int32,System.IntPtr,System.IntPtr)", Justification = "Dotnet port with style preservation")]
-[module: SuppressMessage("Microsoft.Usage", "CA1806:DoNotIgnoreMethodResults", Scope = "member", Target = "MouseWithoutBorders.Common.#CreateLowIntegrityProcess(System.String,System.String,System.Int32)", MessageId = "MouseWithoutBorders.NativeMethods.WaitForSingleObject(System.IntPtr,System.Int32)", Justification = "Dotnet port with style preservation")]
-[module: SuppressMessage("Microsoft.Usage", "CA1806:DoNotIgnoreMethodResults", Scope = "member", Target = "MouseWithoutBorders.Common.#GetText(System.IntPtr)", MessageId = "MouseWithoutBorders.NativeMethods.GetWindowText(System.IntPtr,System.Text.StringBuilder,System.Int32)", Justification = "Dotnet port with style preservation")]
-[module: SuppressMessage("Microsoft.Usage", "CA1806:DoNotIgnoreMethodResults", Scope = "member", Target = "MouseWithoutBorders.Common.#ImpersonateLoggedOnUserAndDoSomething(System.Threading.ThreadStart)", MessageId = "MouseWithoutBorders.NativeMethods.WTSQueryUserToken(System.UInt32,System.IntPtr@)", Justification = "Dotnet port with style preservation")]
-[module: SuppressMessage("Microsoft.Design", "CA1031:DoNotCatchGeneralExceptionTypes", Scope = "member", Target = "MouseWithoutBorders.Common.#CreateLowIntegrityProcess(System.String,System.String,System.Int32,System.Boolean,System.Int64)", Justification = "Dotnet port with style preservation")]
-[module: SuppressMessage("Microsoft.Design", "CA1031:DoNotCatchGeneralExceptionTypes", Scope = "member", Target = "MouseWithoutBorders.Common.#CreateProcessInInputDesktopSession(System.String,System.String,System.String,System.Boolean,System.Int16)", Justification = "Dotnet port with style preservation")]
-[module: SuppressMessage("Microsoft.Design", "CA1031:DoNotCatchGeneralExceptionTypes", Scope = "member", Target = "MouseWithoutBorders.Common.#SkSend(MouseWithoutBorders.DATA,System.Boolean,System.Int32)", Justification = "Dotnet port with style preservation")]
-[module: SuppressMessage("Microsoft.Design", "CA1031:DoNotCatchGeneralExceptionTypes", Scope = "member", Target = "MouseWithoutBorders.Common.#ReceiveClipboardDataUsingTCP(MouseWithoutBorders.DATA,System.Boolean,System.Net.Sockets.Socket)", Justification = "Dotnet port with style preservation")]
-[module: SuppressMessage("Microsoft.Design", "CA1031:DoNotCatchGeneralExceptionTypes", Scope = "member", Target = "MouseWithoutBorders.Common.#UpdateMachineMatrix(MouseWithoutBorders.DATA)", Justification = "Dotnet port with style preservation")]
-[module: SuppressMessage("Microsoft.Design", "CA1031:DoNotCatchGeneralExceptionTypes", Scope = "member", Target = "MouseWithoutBorders.Common.#ReopenSockets(System.Boolean)", Justification = "Dotnet port with style preservation")]
-
-namespace MouseWithoutBorders
-{
- internal partial class Common
- {
- internal Common()
- {
- }
-
- private static InputHook hook;
- private static FrmMatrix matrixForm;
- private static FrmInputCallback inputCallbackForm;
- private static FrmAbout aboutForm;
-#pragma warning disable SA1307 // Accessible fields should begin with upper-case letter
- internal static Thread helper;
- internal static int screenWidth;
- internal static int screenHeight;
-#pragma warning restore SA1307
- private static int lastX;
- private static int lastY;
-
- private static bool mainFormVisible = true;
- private static bool runOnLogonDesktop;
- private static bool runOnScrSaverDesktop;
-
-#pragma warning disable SA1307 // Accessible fields should begin with upper-case letter
- internal static int[] toggleIcons;
- internal static int toggleIconsIndex;
-#pragma warning restore SA1307
- internal const int TOGGLE_ICONS_SIZE = 4;
- internal const int ICON_ONE = 0;
- internal const int ICON_ALL = 1;
- internal const int ICON_SMALL_CLIPBOARD = 2;
- internal const int ICON_BIG_CLIPBOARD = 3;
- internal const int ICON_ERROR = 4;
- internal const int JUST_GOT_BACK_FROM_SCREEN_SAVER = 9999;
-
- internal const int NETWORK_STREAM_BUF_SIZE = 1024 * 1024;
- internal static readonly EventWaitHandle EvSwitch = new(false, EventResetMode.AutoReset);
- private static Point lastPos;
-#pragma warning disable SA1307 // Accessible fields should begin with upper-case names
- internal static int switchCount;
-#pragma warning restore SA1307
- private static long lastReconnectByHotKeyTime;
-#pragma warning disable SA1307 // Accessible fields should begin with upper-case names
- internal static int tcpPort;
-#pragma warning restore SA1307
- private static bool secondOpenSocketTry;
- private static string binaryName;
-
- internal static Process CurrentProcess { get; set; }
-
- internal static bool HotkeyMatched(int vkCode, bool winDown, bool ctrlDown, bool altDown, bool shiftDown, HotkeySettings hotkey)
- {
- return !hotkey.IsEmpty() && (vkCode == hotkey.Code) && (!hotkey.Win || winDown) && (!hotkey.Alt || altDown) && (!hotkey.Shift || shiftDown) && (!hotkey.Ctrl || ctrlDown);
- }
-
- public static string BinaryName
- {
- get => Common.binaryName;
- set => Common.binaryName = value;
- }
-
- public static bool SecondOpenSocketTry
- {
- get => Common.secondOpenSocketTry;
- set => Common.secondOpenSocketTry = value;
- }
-
- public static long LastReconnectByHotKeyTime
- {
- get => Common.lastReconnectByHotKeyTime;
- set => Common.lastReconnectByHotKeyTime = value;
- }
-
- public static int SwitchCount
- {
- get => Common.switchCount;
- set => Common.switchCount = value;
- }
-
- public static Point LastPos
- {
- get => Common.lastPos;
- set => Common.lastPos = value;
- }
-
- internal static FrmAbout AboutForm
- {
- get => Common.aboutForm;
- set => Common.aboutForm = value;
- }
-
- internal static FrmInputCallback InputCallbackForm
- {
- get => Common.inputCallbackForm;
- set => Common.inputCallbackForm = value;
- }
-
- public static int PaintCount { get; set; }
-
- internal static bool RunOnScrSaverDesktop
- {
- get => Common.runOnScrSaverDesktop;
- set => Common.runOnScrSaverDesktop = value;
- }
-
- internal static bool RunOnLogonDesktop
- {
- get => Common.runOnLogonDesktop;
- set => Common.runOnLogonDesktop = value;
- }
-
- internal static bool RunWithNoAdminRight { get; set; }
-
- internal static int LastX
- {
- get => Common.lastX;
- set => Common.lastX = value;
- }
-
- internal static int LastY
- {
- get => Common.lastY;
- set => Common.lastY = value;
- }
-
- internal static int[] ToggleIcons => Common.toggleIcons;
-
- internal static int ScreenHeight => Common.screenHeight;
-
- internal static int ScreenWidth => Common.screenWidth;
-
- internal static bool Is64bitOS
- {
- get; set;
-
- // set { Common.is64bitOS = value; }
- }
-
- internal static int ToggleIconsIndex
- {
- // get { return Common.toggleIconsIndex; }
- set => Common.toggleIconsIndex = value;
- }
-
- internal static InputHook Hook
- {
- get => Common.hook;
- set => Common.hook = value;
- }
-
- internal static SocketStuff Sk { get; set; }
-
- internal static FrmScreen MainForm { get; set; }
-
- internal static FrmMouseCursor MouseCursorForm { get; set; }
-
- internal static FrmMatrix MatrixForm
- {
- get => Common.matrixForm;
- set => Common.matrixForm = value;
- }
-
- internal static ID DesMachineID
- {
- get => MachineStuff.desMachineID;
-
- set
- {
- MachineStuff.desMachineID = value;
- MachineStuff.DesMachineName = MachineStuff.NameFromID(MachineStuff.desMachineID);
- }
- }
-
- internal static ID MachineID => (ID)Setting.Values.MachineId;
-
- internal static string MachineName { get; set; }
-
- internal static bool MainFormVisible
- {
- get => Common.mainFormVisible;
- set => Common.mainFormVisible = value;
- }
-
- internal static Mutex SocketMutex { get; set; } // Synchronization between MouseWithoutBorders running in different desktops
-
- // TODO: For telemetry only, to be removed.
- private static int socketMutexBalance;
-
- internal static void ReleaseSocketMutex()
- {
- if (SocketMutex != null)
- {
- Logger.LogDebug("SOCKET MUTEX BEGIN RELEASE.");
-
- try
- {
- _ = Interlocked.Decrement(ref socketMutexBalance);
- SocketMutex.ReleaseMutex();
- }
- catch (ApplicationException e)
- {
- // The current thread does not own the mutex, the thread acquired it will own it.
- Logger.TelemetryLogTrace($"{nameof(ReleaseSocketMutex)}: {e.Message}. {Thread.CurrentThread.ManagedThreadId}/{UIThreadID}.", SeverityLevel.Warning);
- }
-
- Logger.LogDebug("SOCKET MUTEX RELEASED.");
- }
- else
- {
- Logger.LogDebug("SOCKET MUTEX NULL.");
- }
- }
-
- internal static void AcquireSocketMutex()
- {
- if (SocketMutex != null)
- {
- Logger.LogDebug("SOCKET MUTEX BEGIN WAIT.");
- int waitTimeout = 60000; // TcpListener.Stop may take very long to complete for some reason.
-
- int socketMutexBalance = int.MinValue;
-
- bool acquireMutex = ExecuteAndTrace(
- "Waiting for sockets to close",
- () =>
- {
- socketMutexBalance = Interlocked.Increment(ref Common.socketMutexBalance);
- _ = SocketMutex.WaitOne(waitTimeout); // The app now requires .Net 4.0. Note: .Net20RTM does not have the one-parameter version of the API.
- },
- TimeSpan.FromSeconds(5));
-
- // Took longer than expected.
- if (!acquireMutex)
- {
- Process[] ps = Process.GetProcessesByName(Common.BinaryName);
- Logger.TelemetryLogTrace($"Balance: {socketMutexBalance}, Active: {WinAPI.IsMyDesktopActive()}, Sid/Console: {Process.GetCurrentProcess().SessionId}/{NativeMethods.WTSGetActiveConsoleSessionId()}, Desktop/Input: {WinAPI.GetMyDesktop()}/{WinAPI.GetInputDesktop()}, count: {ps?.Length}.", SeverityLevel.Warning);
- }
-
- Logger.LogDebug("SOCKET MUTEX ENDED.");
- }
- else
- {
- Logger.LogDebug("SOCKET MUTEX NULL.");
- }
- }
-
- internal static bool BlockingUI { get; private set; }
-
- internal static bool ExecuteAndTrace(string actionName, Action action, TimeSpan timeout, bool restart = false)
- {
- bool rv = true;
- Logger.LogDebug(actionName);
- bool done = false;
-
- BlockingUI = true;
-
- if (restart)
- {
- Common.MainForm.Text = Setting.Values.MyIdEx;
-
- /* closesocket() rarely gets stuck for some reason inside ntdll!ZwClose ...=>... afd!AfdCleanupCore.
- * There is no good workaround for it so far, still working with [Winsock 2.0 Discussions] to address the issue.
- * */
- new Thread(
- () =>
- {
- for (int i = 0; i < timeout.TotalSeconds; i++)
- {
- Thread.Sleep(1000);
-
- if (done)
- {
- return;
- }
- }
-
- Logger.TelemetryLogTrace($"[{actionName}] took more than {(long)timeout.TotalSeconds}, restarting the process.", SeverityLevel.Warning, true);
-
- string desktop = WinAPI.GetMyDesktop();
- MachineStuff.oneInstanceCheck?.Close();
- _ = Process.Start(Application.ExecutablePath, desktop);
- Logger.LogDebug($"Started on desktop {desktop}");
-
- Process.GetCurrentProcess().KillProcess(true);
- },
- $"{actionName} watchdog").Start();
- }
-
- Stopwatch timer = Stopwatch.StartNew();
-
- try
- {
- action();
- }
- finally
- {
- done = true;
- BlockingUI = false;
-
- if (restart)
- {
- Common.MainForm.Text = Setting.Values.MyID;
- }
-
- timer.Stop();
-
- if (timer.Elapsed > timeout)
- {
- rv = false;
-
- if (!restart)
- {
- Logger.TelemetryLogTrace($"[{actionName}] took more than {(long)timeout.TotalSeconds}: {(long)timer.Elapsed.TotalSeconds}.", SeverityLevel.Warning);
- }
- }
- }
-
- return rv;
- }
-
- internal static byte[] GetBytes(string st)
- {
- return ASCIIEncoding.ASCII.GetBytes(st);
- }
-
- internal static string GetString(byte[] bytes)
- {
- return ASCIIEncoding.ASCII.GetString(bytes);
- }
-
- internal static byte[] GetBytesU(string st)
- {
- return ASCIIEncoding.Unicode.GetBytes(st);
- }
-
- internal static string GetStringU(byte[] bytes)
- {
- return ASCIIEncoding.Unicode.GetString(bytes);
- }
-
- internal static int UIThreadID { get; set; }
-
- internal static void DoSomethingInUIThread(Action action, bool blocking = false)
- {
- InvokeInFormThread(MainForm, UIThreadID, action, blocking);
- }
-
- internal static int InputCallbackThreadID { get; set; }
-
- internal static void DoSomethingInTheInputCallbackThread(Action action, bool blocking = true)
- {
- InvokeInFormThread(InputCallbackForm, InputCallbackThreadID, action, blocking);
- }
-
- private static void InvokeInFormThread(System.Windows.Forms.Form form, int threadId, Action action, bool blocking)
- {
- if (form != null)
- {
- int currentThreadId = Thread.CurrentThread.ManagedThreadId;
-
- if (currentThreadId == threadId)
- {
- action();
- }
- else
- {
- bool done = false;
-
- try
- {
- Action callback = () =>
- {
- try
- {
- action();
- }
- catch (Exception e)
- {
- Logger.Log(e);
- }
- finally
- {
- done = true;
- }
- };
- _ = form.BeginInvoke(callback);
- }
- catch (Exception e)
- {
- done = true;
- Logger.Log(e);
- }
-
- while (blocking && !done)
- {
- Thread.Sleep(16);
-
- if (currentThreadId == UIThreadID || currentThreadId == InputCallbackThreadID)
- {
- Application.DoEvents();
- }
- }
- }
- }
- }
-
- private static readonly Lock InputSimulationLock = new();
-
- internal static void DoSomethingInTheInputSimulationThread(ThreadStart target)
- {
- /*
- * For some reason, SendInput may hit deadlock if it is called in the InputHookProc thread.
- * For now leave it as is in the caller thread which is the socket receiver thread.
- * */
-
- // SendInput is thread-safe but few users seem to hit a deadlock occasionally, probably a Windows bug.
- lock (InputSimulationLock)
- {
- target();
- }
- }
-
- internal static void SendPackage(ID des, PackageType packageType)
- {
- DATA package = new();
- package.Type = packageType;
- package.Des = des;
- package.MachineName = MachineName;
-
- SkSend(package, null, false);
- }
-
- internal static void SendHeartBeat(bool initial = false)
- {
- SendPackage(ID.ALL, initial && Encryption.GeneratedKey ? PackageType.Heartbeat_ex : PackageType.Heartbeat);
- }
-
- private static long lastSendNextMachine;
-
- internal static void SendNextMachine(ID hostMachine, ID nextMachine, Point requestedXY)
- {
- Logger.LogDebug($"SendNextMachine: Host machine: {hostMachine}, Next machine: {nextMachine}, Requested XY: {requestedXY}");
-
- if (GetTick() - lastSendNextMachine < 100)
- {
- Logger.LogDebug("Machine switching in progress."); // "Move Mouse relatively" mode, slow machine/network, quick/busy hand.
- return;
- }
-
- lastSendNextMachine = GetTick();
-
- DATA package = new();
- package.Type = PackageType.NextMachine;
-
- package.Des = hostMachine;
-
- package.Md.X = requestedXY.X;
- package.Md.Y = requestedXY.Y;
- package.Md.WheelDelta = (int)nextMachine;
-
- SkSend(package, null, false);
-
- Logger.LogDebug("SendNextMachine done.");
- }
-
- private static ulong lastInputEventCount;
- private static ulong lastRealInputEventCount;
-
- internal static void SendAwakeBeat()
- {
- if (!Common.RunOnLogonDesktop && !Common.RunOnScrSaverDesktop && WinAPI.IsMyDesktopActive() &&
- Setting.Values.BlockScreenSaver && lastRealInputEventCount != Event.RealInputEventCount)
- {
- SendPackage(ID.ALL, PackageType.Awake);
- }
- else
- {
- SendHeartBeat();
- }
-
- lastInputEventCount = Event.InputEventCount;
- lastRealInputEventCount = Event.RealInputEventCount;
- }
-
- internal static void HumanBeingDetected()
- {
- if (lastInputEventCount == Event.InputEventCount)
- {
- if (!Common.RunOnLogonDesktop && !Common.RunOnScrSaverDesktop && WinAPI.IsMyDesktopActive())
- {
- PokeMyself();
- }
- }
-
- lastInputEventCount = Event.InputEventCount;
- }
-
- internal static void PokeMyself()
- {
- int x, y = 0;
-
- for (int i = 0; i < 10; i++)
- {
- x = Encryption.Ran.Next(-9, 10);
- InputSimulation.MoveMouseRelative(x, y);
- Thread.Sleep(50);
- InputSimulation.MoveMouseRelative(-x, -y);
- Thread.Sleep(50);
-
- if (lastInputEventCount != Event.InputEventCount)
- {
- break;
- }
- }
- }
-
- internal static void InitLastInputEventCount()
- {
- lastInputEventCount = Event.InputEventCount;
- lastRealInputEventCount = Event.RealInputEventCount;
- }
-
- internal static void SendHello()
- {
- SendPackage(ID.ALL, PackageType.Hello);
- }
-
- /*
- internal static void SendHi()
- {
- SendPackage(IP.ALL, PackageType.hi);
- }
- * */
-
- internal static void SendByeBye()
- {
- Logger.LogDebug($"{nameof(SendByeBye)}");
- SendPackage(ID.ALL, PackageType.ByeBye);
- }
-
- internal static void SendClipboardBeat()
- {
- SendPackage(ID.ALL, PackageType.Clipboard);
- }
-
- internal static void ProcessByeByeMessage(DATA package)
- {
- if (package.Src == MachineStuff.desMachineID)
- {
- MachineStuff.SwitchToMachine(MachineName.Trim());
- }
-
- _ = MachineStuff.RemoveDeadMachines(package.Src);
- }
-
- internal static long GetTick() // ms
- {
- return DateTime.Now.Ticks / 10000;
- }
-
- internal static void SetToggleIcon(int[] toggleIcons)
- {
- Logger.LogDebug($"{nameof(SetToggleIcon)}: {toggleIcons?.FirstOrDefault()}");
- Common.toggleIcons = toggleIcons;
- toggleIconsIndex = 0;
- }
-
- internal static string CaptureScreen()
- {
- try
- {
- string fileName = GetMyStorageDir() + @"ScreenCaptureByMouseWithoutBorders.png";
- int w = MachineStuff.desktopBounds.Right - MachineStuff.desktopBounds.Left;
- int h = MachineStuff.desktopBounds.Bottom - MachineStuff.desktopBounds.Top;
- Bitmap bm = new(w, h);
- Graphics g = Graphics.FromImage(bm);
- Size s = new(w, h);
- g.CopyFromScreen(MachineStuff.desktopBounds.Left, MachineStuff.desktopBounds.Top, 0, 0, s);
- bm.Save(fileName, ImageFormat.Png);
- bm.Dispose();
- return fileName;
- }
- catch (Exception e)
- {
- Logger.Log(e);
- return null;
- }
- }
-
- internal static void PrepareScreenCapture()
- {
- Common.DoSomethingInUIThread(() =>
- {
- if (!DragDrop.MouseDown && Helper.SendMessageToHelper(0x401, IntPtr.Zero, IntPtr.Zero) > 0)
- {
- Common.MMSleep(0.2);
- InputSimulation.SendKey(new KEYBDDATA() { wVk = (int)VK.SNAPSHOT });
- InputSimulation.SendKey(new KEYBDDATA() { dwFlags = (int)WM.LLKHF.UP, wVk = (int)VK.SNAPSHOT });
-
- Logger.LogDebug("PrepareScreenCapture: SNAPSHOT simulated.");
-
- _ = NativeMethods.MoveWindow(
- (IntPtr)NativeMethods.FindWindow(null, Helper.HELPER_FORM_TEXT),
- MachineStuff.DesktopBounds.Left,
- MachineStuff.DesktopBounds.Top,
- MachineStuff.DesktopBounds.Right - MachineStuff.DesktopBounds.Left,
- MachineStuff.DesktopBounds.Bottom - MachineStuff.DesktopBounds.Top,
- false);
-
- _ = Helper.SendMessageToHelper(0x406, IntPtr.Zero, IntPtr.Zero, false);
- }
- else
- {
- Logger.Log("PrepareScreenCapture: Validation failed.");
- }
- });
- }
-
- internal static void OpenImage(string file)
- {
- // We want to run mspaint under the user account who ran explorer.exe (who logged in this current input desktop)
-
- // ImpersonateLoggedOnUserAndDoSomething(delegate()
- // {
- // Process.Start("explorer", "\"" + file + "\"");
- // });
- _ = Launch.CreateProcessInInputDesktopSession(
- "\"" + Environment.ExpandEnvironmentVariables(@"%SystemRoot%\System32\Mspaint.exe") +
- "\"",
- "\"" + file + "\"",
- WinAPI.GetInputDesktop(),
- 1);
-
- // CreateNormalIntegrityProcess(Environment.ExpandEnvironmentVariables(@"%SystemRoot%\System32\Mspaint.exe") +
- // " \"" + file + "\"");
-
- // We don't want to run mspaint as local system account
- /*
- ProcessStartInfo s = new ProcessStartInfo(
- Environment.ExpandEnvironmentVariables(@"%SystemRoot%\System32\Mspaint.exe"),
- "\"" + file + "\"");
- s.WindowStyle = ProcessWindowStyle.Maximized;
- Process.Start(s);
- * */
- }
-
- internal static void SendImage(string machine, string file)
- {
- Clipboard.LastDragDropFile = file;
-
- // Send ClipboardCapture
- if (machine.Equals("All", StringComparison.OrdinalIgnoreCase))
- {
- SendPackage(ID.ALL, PackageType.ClipboardCapture);
- }
- else
- {
- ID id = MachineStuff.MachinePool.ResolveID(machine);
- if (id != ID.NONE)
- {
- SendPackage(id, PackageType.ClipboardCapture);
- }
- }
- }
-
- internal static void SendImage(ID src, string file)
- {
- Clipboard.LastDragDropFile = file;
-
- // Send ClipboardCapture
- SendPackage(src, PackageType.ClipboardCapture);
- }
-
- internal static void ShowToolTip(string tip, int timeOutInMilliseconds = 5000, ToolTipIcon icon = ToolTipIcon.Info, bool showBalloonTip = true, bool forceEvenIfHidingOldUI = false)
- {
- if (!Common.RunOnLogonDesktop && !Common.RunOnScrSaverDesktop)
- {
- DoSomethingInUIThread(() =>
- {
- if (Setting.Values.FirstRun)
- {
- MachineStuff.Settings?.ShowTip(icon, tip, timeOutInMilliseconds);
- }
-
- Common.MatrixForm?.ShowTip(icon, tip, timeOutInMilliseconds);
-
- if (showBalloonTip)
- {
- if (MainForm != null)
- {
- MainForm.ShowToolTip(tip, timeOutInMilliseconds, forceEvenIfHidingOldUI: forceEvenIfHidingOldUI);
- }
- else
- {
- Logger.Log(tip);
- }
- }
- });
- }
- }
-
- private static FrmMessage topMostMessageForm;
-
- internal static void ToggleShowTopMostMessage(string text, string bigText, int timeOut)
- {
- DoSomethingInUIThread(() =>
- {
- if (topMostMessageForm == null)
- {
- topMostMessageForm = new FrmMessage(text, bigText, timeOut);
- topMostMessageForm.Show();
- }
- else
- {
- FrmMessage currentMessageForm = topMostMessageForm;
- topMostMessageForm = null;
- currentMessageForm.Close();
- }
- });
- }
-
- internal static void HideTopMostMessage()
- {
- DoSomethingInUIThread(() =>
- {
- topMostMessageForm?.Close();
- });
- }
-
- internal static void NullTopMostMessage()
- {
- DoSomethingInUIThread(() =>
- {
- if (topMostMessageForm != null)
- {
- topMostMessageForm = null;
- }
- });
- }
-
- internal static bool IsTopMostMessageNotNull()
- {
- return topMostMessageForm != null;
- }
-
- private static bool TestSend(TcpSk t)
- {
- ID remoteMachineID;
-
- if (t.Status == SocketStatus.Connected)
- {
- try
- {
- DATA package = new();
- package.Type = PackageType.Hi;
- package.Des = remoteMachineID = (ID)t.MachineId;
- package.MachineName = MachineName;
-
- _ = Sk.TcpSend(t, package);
- t.EncryptedStream?.Flush();
-
- return true;
- }
- catch (ExpectedSocketException)
- {
- t.BackingSocket = null; // To be removed at CloseAnUnusedSocket()
- }
- }
-
- t.Status = SocketStatus.SendError;
- return false;
- }
-
- internal static bool IsConnectedTo(ID remoteMachineID)
- {
- bool updateClientSockets = false;
-
- if (remoteMachineID == MachineID)
- {
- return true;
- }
-
- SocketStuff sk = Common.Sk;
-
- if (sk != null)
- {
- lock (sk.TcpSocketsLock)
- {
- if (sk.TcpSockets != null)
- {
- foreach (TcpSk t in sk.TcpSockets)
- {
- if (t.Status == SocketStatus.Connected && (uint)remoteMachineID == t.MachineId)
- {
- if (TestSend(t))
- {
- return true;
- }
- else
- {
- updateClientSockets = true;
- }
- }
- }
- }
- }
- }
-
- if (updateClientSockets)
- {
- MachineStuff.UpdateClientSockets(nameof(IsConnectedTo));
- }
-
- return false;
- }
-
-#if DEBUG
- private static long minSendTime = long.MaxValue;
- private static long avgSendTime;
- private static long maxSendTime;
- private static long totalSendCount;
- private static long totalSendTime;
-#endif
-
- internal static void SkSend(DATA data, uint? exceptDes, bool includeHandShakingSockets)
- {
- bool connected = false;
-
- SocketStuff sk = Sk;
-
- if (sk != null)
- {
-#if DEBUG
- long startStop = DateTime.Now.Ticks;
- totalSendCount++;
-#endif
-
- try
- {
- data.Id = Interlocked.Increment(ref Package.PackageID);
-
- bool updateClientSockets = false;
-
- lock (sk.TcpSocketsLock)
- {
- foreach (TcpSk t in sk.TcpSockets)
- {
- if (t != null && t.BackingSocket != null && (t.Status == SocketStatus.Connected || (t.Status == SocketStatus.Handshaking && includeHandShakingSockets)))
- {
- if (t.MachineId == (uint)data.Des || (data.Des == ID.ALL && t.MachineId != exceptDes && MachineStuff.InMachineMatrix(t.MachineName)))
- {
- try
- {
- sk.TcpSend(t, data);
-
- if (data.Des != ID.ALL)
- {
- connected = true;
- }
- }
- catch (ExpectedSocketException)
- {
- t.BackingSocket = null; // To be removed at CloseAnUnusedSocket()
- updateClientSockets = true;
- }
- catch (Exception e)
- {
- Logger.Log(e);
- t.BackingSocket = null; // To be removed at CloseAnUnusedSocket()
- updateClientSockets = true;
- }
- }
- }
- }
- }
-
- if (!connected && data.Des != ID.ALL)
- {
- Logger.LogDebug("********** No active connection found for the remote machine! **********" + data.Des.ToString());
-
- if (data.Des == ID.NONE || MachineStuff.RemoveDeadMachines(data.Des))
- {
- // SwitchToMachine(MachineName.Trim());
- MachineStuff.NewDesMachineID = DesMachineID = MachineID;
- MachineStuff.SwitchLocation.X = Event.XY_BY_PIXEL + Event.myLastX;
- MachineStuff.SwitchLocation.Y = Event.XY_BY_PIXEL + Event.myLastY;
- MachineStuff.SwitchLocation.ResetCount();
- EvSwitch.Set();
- }
- }
-
- if (updateClientSockets)
- {
- MachineStuff.UpdateClientSockets("SkSend");
- }
- }
- catch (Exception e)
- {
- Logger.Log(e);
- }
-
-#if DEBUG
- startStop = DateTime.Now.Ticks - startStop;
- totalSendTime += startStop;
- if (startStop < minSendTime)
- {
- minSendTime = startStop;
- }
-
- if (startStop > maxSendTime)
- {
- maxSendTime = startStop;
- }
-
- avgSendTime = totalSendTime / totalSendCount;
-#endif
- }
- else
- {
- Package.PackageSent.Nil++;
- }
- }
-
- internal static void CloseAnUnusedSocket()
- {
- SocketStuff sk = Common.Sk;
-
- if (sk != null)
- {
- lock (sk.TcpSocketsLock)
- {
- if (sk.TcpSockets != null)
- {
- TcpSk tobeRemoved = null;
-
- foreach (TcpSk t in sk.TcpSockets)
- {
- if ((t.Status != SocketStatus.Connected && t.BirthTime < GetTick() - SocketStuff.CONNECT_TIMEOUT) || t.BackingSocket == null)
- {
- Logger.LogDebug("CloseAnUnusedSocket: " + t.MachineName + ":" + t.MachineId + "|" + t.Status.ToString());
- tobeRemoved = t;
-
- if (t.BackingSocket != null)
- {
- try
- {
- t.BackingSocket.Close();
- }
- catch (Exception e)
- {
- Logger.Log(e);
- }
- }
-
- break; // Each time we try to remove one socket only.
- }
- }
-
- if (tobeRemoved != null)
- {
- _ = sk.TcpSockets.Remove(tobeRemoved);
- }
- }
- }
- }
- }
-
- internal static bool AtLeastOneSocketConnected()
- {
- SocketStuff sk = Common.Sk;
-
- if (sk != null)
- {
- lock (sk.TcpSocketsLock)
- {
- if (sk.TcpSockets != null)
- {
- foreach (TcpSk t in sk.TcpSockets)
- {
- if (t.Status == SocketStatus.Connected)
- {
- Logger.LogDebug("AtLeastOneSocketConnected returning true: " + t.MachineName);
- return true;
- }
- }
- }
- }
- }
-
- Logger.LogDebug("AtLeastOneSocketConnected returning false.");
- return false;
- }
-
- internal static Socket AtLeastOneServerSocketConnected()
- {
- SocketStuff sk = Common.Sk;
-
- if (sk != null)
- {
- lock (sk.TcpSocketsLock)
- {
- if (sk.TcpSockets != null)
- {
- foreach (TcpSk t in sk.TcpSockets)
- {
- if (!t.IsClient && t.Status == SocketStatus.Connected)
- {
- Logger.LogDebug("AtLeastOneServerSocketConnected returning true: " + t.MachineName);
- return t.BackingSocket;
- }
- }
- }
- }
- }
-
- Logger.LogDebug("AtLeastOneServerSocketConnected returning false.");
- return null;
- }
-
- internal static TcpSk GetConnectedClientSocket()
- {
- SocketStuff sk = Common.Sk;
-
- if (sk != null)
- {
- lock (sk.TcpSocketsLock)
- {
- return sk.TcpSockets?.FirstOrDefault(item => item.IsClient && item.Status == SocketStatus.Connected);
- }
- }
- else
- {
- return null;
- }
- }
-
- internal static bool AtLeastOneSocketEstablished()
- {
- SocketStuff sk = Common.Sk;
-
- if (sk != null)
- {
- lock (sk.TcpSocketsLock)
- {
- if (sk.TcpSockets != null)
- {
- foreach (TcpSk t in sk.TcpSockets)
- {
- if (t.BackingSocket != null && t.BackingSocket.Connected)
- {
- if (TestSend(t))
- {
- Logger.LogDebug($"{nameof(AtLeastOneSocketEstablished)} returning true: {t.MachineName}");
- return true;
- }
- }
- }
- }
- }
- }
-
- Logger.LogDebug($"{nameof(AtLeastOneSocketEstablished)} returning false.");
- return false;
- }
-
- internal static bool IsConnectedByAClientSocketTo(string machineName)
- {
- SocketStuff sk = Common.Sk;
-
- if (sk != null)
- {
- lock (sk.TcpSocketsLock)
- {
- foreach (TcpSk t in sk.TcpSockets)
- {
- if (t != null && t.IsClient && t.Status == SocketStatus.Connected
- && t.BackingSocket != null && t.MachineName.Equals(machineName, StringComparison.OrdinalIgnoreCase))
- {
- return true;
- }
- }
- }
- }
-
- return false;
- }
-
- internal static IPAddress GetConnectedClientSocketIPAddressFor(string machineName)
- {
- SocketStuff sk = Common.Sk;
-
- if (sk != null)
- {
- lock (sk.TcpSocketsLock)
- {
- return sk.TcpSockets.FirstOrDefault(t => t != null && t.IsClient && t.Status == SocketStatus.Connected
- && t.Address != null && t.MachineName.Equals(machineName, StringComparison.OrdinalIgnoreCase))
- ?.Address;
- }
- }
-
- return null;
- }
-
- internal static bool IsConnectingByAClientSocketTo(string machineName, IPAddress ip)
- {
- SocketStuff sk = Common.Sk;
-
- if (sk != null)
- {
- lock (sk.TcpSocketsLock)
- {
- foreach (TcpSk t in sk.TcpSockets)
- {
- if (t != null && t.IsClient && t.Status == SocketStatus.Connecting
- && t.BackingSocket != null && t.MachineName.Equals(machineName, StringComparison.OrdinalIgnoreCase)
- && t.Address.ToString().Equals(ip.ToString(), StringComparison.OrdinalIgnoreCase))
- {
- return true;
- }
- }
- }
- }
-
- return false;
- }
-
- internal static void UpdateSetupMachineMatrix(string desMachine)
- {
- int machineCt = 0;
-
- foreach (string m in MachineStuff.MachineMatrix)
- {
- if (!string.IsNullOrEmpty(m.Trim()))
- {
- machineCt++;
- }
- }
-
- if (machineCt < 2 && MachineStuff.Settings != null && (MachineStuff.Settings.GetCurrentPage() is SetupPage1 || MachineStuff.Settings.GetCurrentPage() is SetupPage2b))
- {
- MachineStuff.MachineMatrix = new string[MachineStuff.MAX_MACHINE] { Common.MachineName.Trim(), desMachine, string.Empty, string.Empty };
- Logger.LogDebug("UpdateSetupMachineMatrix: " + string.Join(",", MachineStuff.MachineMatrix));
-
- Common.DoSomethingInUIThread(
- () =>
- {
- MachineStuff.Settings.SetControlPage(new SetupPage4());
- },
- true);
- }
- }
-
- internal static void ReopenSockets(bool byUser)
- {
- DoSomethingInUIThread(
- () =>
- {
- try
- {
- SocketStuff tmpSk = Sk;
-
- if (tmpSk != null)
- {
- Sk = null; // TODO: This looks redundant.
- tmpSk.Close(byUser);
- }
-
- Sk = new SocketStuff(tcpPort, byUser);
- }
- catch (Exception e)
- {
- Sk = null;
- Logger.Log(e);
- }
-
- if (Sk != null)
- {
- if (byUser)
- {
- SocketStuff.ClearBadIPs();
- }
-
- MachineStuff.UpdateClientSockets("ReopenSockets");
- }
- },
- true);
-
- if (Sk == null)
- {
- return;
- }
-
- Common.DoSomethingInTheInputCallbackThread(() =>
- {
- if (Common.Hook != null)
- {
- Common.Hook.Stop();
- Common.Hook = null;
- }
-
- if (byUser)
- {
- Common.InputCallbackForm.Close();
- Common.InputCallbackForm = null;
- Program.StartInputCallbackThread();
- }
- else
- {
- Common.InputCallbackForm.InstallKeyboardAndMouseHook();
- }
- });
- }
-
- internal static string GetMyStorageDir()
- {
- string st = string.Empty;
-
- try
- {
- if (RunOnLogonDesktop || RunOnScrSaverDesktop)
- {
- st = Environment.GetFolderPath(Environment.SpecialFolder.ProgramFiles);
- if (!Directory.Exists(st))
- {
- _ = Directory.CreateDirectory(st);
- }
-
- st += @"\" + Common.BinaryName;
- if (!Directory.Exists(st))
- {
- _ = Directory.CreateDirectory(st);
- }
-
- st += @"\ScreenCaptures\";
- if (!Directory.Exists(st))
- {
- _ = Directory.CreateDirectory(st);
- }
- }
- else
- {
- _ = Launch.ImpersonateLoggedOnUserAndDoSomething(() =>
- {
- st = Environment.GetFolderPath(Environment.SpecialFolder.Desktop) + @"\" + Common.BinaryName;
- if (!Directory.Exists(st))
- {
- _ = Directory.CreateDirectory(st);
- }
-
- st += @"\ScreenCaptures\";
- if (!Directory.Exists(st))
- {
- _ = Directory.CreateDirectory(st);
- }
- });
- }
-
- Logger.LogDebug("GetMyStorageDir: " + st);
-
- // Delete old files.
- foreach (FileInfo fi in new DirectoryInfo(st).GetFiles())
- {
- if (fi.CreationTime.AddDays(1) < DateTime.Now)
- {
- fi.Delete();
- }
- }
-
- return st;
- }
- catch (Exception e)
- {
- Logger.Log(e);
-
- if (string.IsNullOrEmpty(st) || !st.Contains(Common.BinaryName))
- {
- st = Path.GetTempPath();
- }
-
- return st;
- }
- }
-
- internal static void GetMachineName()
- {
- string machine_Name = string.Empty;
-
- try
- {
- machine_Name = Dns.GetHostName();
- Logger.LogDebug("GetHostName = " + machine_Name);
- }
- catch (Exception e)
- {
- Logger.Log(e);
-
- if (string.IsNullOrEmpty(machine_Name))
- {
- machine_Name = "RANDOM" + Encryption.Ran.Next().ToString(CultureInfo.CurrentCulture);
- }
- }
-
- if (machine_Name.Length > 32)
- {
- machine_Name = machine_Name[..32];
- }
-
- Common.MachineName = machine_Name.Trim();
-
- Logger.LogDebug($"========== {nameof(GetMachineName)} ended!");
- }
-
- private static string GetNetworkName(NetworkInterface networkInterface)
- {
- return $"{networkInterface.Name} | {networkInterface.Description.Replace(":", "-")}";
- }
-
- internal static string GetRemoteStringIP(Socket s, bool throwException = false)
- {
- if (s == null)
- {
- return string.Empty;
- }
-
- string ip;
-
- try
- {
- ip = (s?.RemoteEndPoint as IPEndPoint)?.Address?.ToString();
-
- if (string.IsNullOrEmpty(ip))
- {
- return string.Empty;
- }
- }
- catch (ObjectDisposedException e)
- {
- Logger.Log($"{nameof(GetRemoteStringIP)}: The socket could have been disposed by other threads, error: {e.Message}");
-
- if (throwException)
- {
- throw;
- }
-
- return string.Empty;
- }
- catch (SocketException e)
- {
- Logger.Log($"{nameof(GetRemoteStringIP)}: {e.Message}");
-
- if (throwException)
- {
- throw;
- }
-
- return string.Empty;
- }
-
- return ip;
- }
-
- internal static void CloseAllFormsAndHooks()
- {
- if (Hook != null)
- {
- Hook.Stop();
- Hook = null;
- if (InputCallbackForm != null)
- {
- DoSomethingInTheInputCallbackThread(() =>
- {
- InputCallbackForm.Close();
- InputCallbackForm = null;
- });
- }
- }
-
- if (MainForm != null)
- {
- MainForm.Destroy();
- MainForm = null;
- }
-
- if (MatrixForm != null)
- {
- MatrixForm.Close();
- MatrixForm = null;
- }
-
- if (AboutForm != null)
- {
- AboutForm.Close();
- AboutForm = null;
- }
- }
-
- internal static void MoveMouseToCenter()
- {
- Logger.LogDebug("+++++ MoveMouseToCenter");
- InputSimulation.MoveMouse(
- MachineStuff.PrimaryScreenBounds.Left + ((MachineStuff.PrimaryScreenBounds.Right - MachineStuff.PrimaryScreenBounds.Left) / 2),
- MachineStuff.PrimaryScreenBounds.Top + ((MachineStuff.PrimaryScreenBounds.Bottom - MachineStuff.PrimaryScreenBounds.Top) / 2));
- }
-
- internal static void HideMouseCursor(bool byHideMouseMessage)
- {
- Common.LastPos = new Point(
- MachineStuff.PrimaryScreenBounds.Left + ((MachineStuff.PrimaryScreenBounds.Right - MachineStuff.PrimaryScreenBounds.Left) / 2),
- Setting.Values.HideMouse ? 4 : MachineStuff.PrimaryScreenBounds.Top + ((MachineStuff.PrimaryScreenBounds.Bottom - MachineStuff.PrimaryScreenBounds.Top) / 2));
-
- if ((MachineStuff.desMachineID != MachineID && MachineStuff.desMachineID != ID.ALL) || byHideMouseMessage)
- {
- _ = NativeMethods.SetCursorPos(Common.LastPos.X, Common.LastPos.Y);
- _ = NativeMethods.GetCursorPos(ref Common.lastPos);
- Logger.LogDebug($"+++++ HideMouseCursor, byHideMouseMessage = {byHideMouseMessage}");
- }
-
- CustomCursor.ShowFakeMouseCursor(int.MinValue, int.MinValue);
- }
-
- internal static string GetText(IntPtr hWnd)
- {
- int length = NativeMethods.GetWindowTextLength(hWnd);
- StringBuilder sb = new(length + 1);
- int rv = NativeMethods.GetWindowText(hWnd, sb, sb.Capacity);
- Logger.LogDebug("GetWindowText returned " + rv.ToString(CultureInfo.CurrentCulture));
- return sb.ToString();
- }
-
- public static string GetWindowClassName(IntPtr hWnd)
- {
- StringBuilder buffer = new(128);
- _ = NativeMethods.GetClassName(hWnd, buffer, buffer.Capacity);
- return buffer.ToString();
- }
-
- internal static void MMSleep(double secs)
- {
- for (int i = 0; i < secs * 10; i++)
- {
- Application.DoEvents();
- Thread.Sleep(100);
- }
- }
-
- internal static void UpdateMultipleModeIconAndMenu()
- {
- MainForm?.UpdateMultipleModeIconAndMenu();
- }
-
- internal static void SendOrReceiveARandomDataBlockPerInitialIV(Stream st, bool send = true)
- {
- byte[] ranData = new byte[Encryption.SymAlBlockSize];
-
- try
- {
- if (send)
- {
- ranData = RandomNumberGenerator.GetBytes(Encryption.SymAlBlockSize);
- st.Write(ranData, 0, ranData.Length);
- }
- else
- {
- int toRead = ranData.Length;
- int read = st.ReadEx(ranData, 0, toRead);
-
- if (read != toRead)
- {
- Logger.LogDebug("Stream has no more data after reading {0} bytes.", read);
- }
- }
- }
- catch (IOException e)
- {
- string log = $"{nameof(SendOrReceiveARandomDataBlockPerInitialIV)}: Exception {(send ? "writing" : "reading")} to the socket stream: {e.InnerException?.GetType()}/{e.Message}. (This is expected when the remote machine closes the connection during desktop switch or reconnection.)";
- Logger.Log(log);
-
- if (e.InnerException is not (SocketException or ObjectDisposedException))
- {
- throw;
- }
- }
- }
-
- private static bool DisableEasyMouseWhenForegroundWindowIsFullscreenSetting()
- {
- return Setting.Values.DisableEasyMouseWhenForegroundWindowIsFullscreen;
- }
-
- private static bool IsAppIgnoredByEasyMouseFullscreenCheck(IntPtr foregroundWindowHandle)
- {
- if (NativeMethods.GetWindowThreadProcessId(foregroundWindowHandle, out var processId) == 0)
- {
- Logger.LogDebug($"GetWindowThreadProcessId failed with error : {Marshal.GetLastWin32Error()}");
- return false;
- }
-
- var processHandle = NativeMethods.OpenProcess(0x1000, false, processId);
- if (processHandle == IntPtr.Zero)
- {
- return false;
- }
-
- uint maxPath = 260;
- var nameBuffer = new char[maxPath];
- if (!NativeMethods.QueryFullProcessImageName(
- processHandle, NativeMethods.QUERY_FULL_PROCESS_NAME_FLAGS.DEFAULT, nameBuffer, ref maxPath))
- {
- Logger.LogDebug($"QueryFullProcessImageName failed with error : {Marshal.GetLastWin32Error()}");
- NativeMethods.CloseHandle(processHandle);
- return false;
- }
-
- NativeMethods.CloseHandle(processHandle);
-
- var name = new string(nameBuffer, 0, (int)maxPath);
-
- var excludedApps = Setting.Values.EasyMouseFullscreenSwitchBlockExcludedApps;
-
- return excludedApps.Contains(Path.GetFileNameWithoutExtension(name), StringComparer.OrdinalIgnoreCase)
- || excludedApps.Contains(Path.GetFileName(name), StringComparer.OrdinalIgnoreCase);
- }
-
- internal static bool IsEasyMouseBlockedByFullscreenWindow()
- {
- var shellHandle = NativeMethods.GetShellWindow();
- var desktopHandle = NativeMethods.GetDesktopWindow();
- var foregroundHandle = NativeMethods.GetForegroundWindow();
-
- // If the foreground window is either the desktop or the Windows shell, we are not in fullscreen mode.
- if (foregroundHandle.Equals(shellHandle) || foregroundHandle.Equals(desktopHandle))
- {
- return false;
- }
-
- if (NativeMethods.SHQueryUserNotificationState(out var userNotificationState) != 0)
- {
- Logger.LogDebug($"SHQueryUserNotificationState failed with error : {Marshal.GetLastWin32Error()}");
- return false;
- }
-
- switch (userNotificationState)
- {
- // An application running in full screen mode, check if the foreground window is
- // listed as ignored in the settings.
- case NativeMethods.USER_NOTIFICATION_STATE.BUSY:
- case NativeMethods.USER_NOTIFICATION_STATE.RUNNING_D3D_FULL_SCREEN:
- case NativeMethods.USER_NOTIFICATION_STATE.PRESENTATION_MODE:
- return !IsAppIgnoredByEasyMouseFullscreenCheck(foregroundHandle);
-
- // No full screen app running.
- case NativeMethods.USER_NOTIFICATION_STATE.NOT_PRESENT:
- case NativeMethods.USER_NOTIFICATION_STATE.ACCEPTS_NOTIFICATIONS:
- case NativeMethods.USER_NOTIFICATION_STATE.QUIET_TIME:
- // Cannot determine
- case NativeMethods.USER_NOTIFICATION_STATE.APP:
- default:
- return false;
- }
- }
-
- ///
- /// Check if a machine switch triggered by EasyMouse would be allowed to proceed due to other settings.
- ///
- /// A boolean that tells us if the switch isn't blocked by any other settings
- internal static bool IsEasyMouseSwitchAllowed()
- {
- // Never prevent a switch if we are not moving out of the host machine.
- if (!DisableEasyMouseWhenForegroundWindowIsFullscreenSetting() || DesMachineID != MachineID)
- {
- return true;
- }
-
- // Check if the switch is blocked by a full-screen window running in the foreground
- return !IsEasyMouseBlockedByFullscreenWindow();
- }
- }
-}
diff --git a/src/modules/MouseWithoutBorders/App/Class/IClipboardHelper.cs b/src/modules/MouseWithoutBorders/App/Class/IClipboardHelper.cs
index 62360b4795..15ac6fb8b8 100644
--- a/src/modules/MouseWithoutBorders/App/Class/IClipboardHelper.cs
+++ b/src/modules/MouseWithoutBorders/App/Class/IClipboardHelper.cs
@@ -18,12 +18,12 @@ using System.Threading.Tasks;
using System.Windows.Forms;
using Microsoft.VisualStudio.Threading;
+using MouseWithoutBorders.Core;
using Newtonsoft.Json;
using StreamJsonRpc;
#if !MM_HELPER
using MouseWithoutBorders.Class;
-using MouseWithoutBorders.Core;
#endif
using SystemClipboard = System.Windows.Forms.Clipboard;
@@ -246,11 +246,11 @@ WellKnownSidType.AuthenticatedUserSid, null);
CancellationToken cancellationToken = _serverTaskCancellationSource.Token;
IpcChannel.StartIpcServer(ChannelName + "/" + RemoteObjectName, cancellationToken);
- Common.IpcChannelCreated = true;
+ IpcChannelHelper.IpcChannelCreated = true;
}
catch (Exception e)
{
- Common.IpcChannelCreated = false;
+ IpcChannelHelper.IpcChannelCreated = false;
Common.ShowToolTip("Error setting up clipboard sharing, clipboard sharing will not work!", 5000, ToolTipIcon.Error);
Logger.Log(e);
}
@@ -405,7 +405,7 @@ WellKnownSidType.AuthenticatedUserSid, null);
try
{
- rv = Common.Retry(nameof(SystemClipboard.ContainsFileDropList), () => { return SystemClipboard.ContainsFileDropList(); }, (log) => Log(log));
+ rv = IpcChannelHelper.Retry(nameof(SystemClipboard.ContainsFileDropList), () => { return SystemClipboard.ContainsFileDropList(); }, (log) => Log(log));
}
catch (ExternalException e)
{
@@ -427,7 +427,7 @@ WellKnownSidType.AuthenticatedUserSid, null);
try
{
- rv = Common.Retry(nameof(SystemClipboard.ContainsImage), () => { return SystemClipboard.ContainsImage(); }, (log) => Log(log));
+ rv = IpcChannelHelper.Retry(nameof(SystemClipboard.ContainsImage), () => { return SystemClipboard.ContainsImage(); }, (log) => Log(log));
}
catch (ExternalException e)
{
@@ -449,7 +449,7 @@ WellKnownSidType.AuthenticatedUserSid, null);
try
{
- rv = Common.Retry(nameof(SystemClipboard.ContainsText), () => { return SystemClipboard.ContainsText(); }, (log) => Log(log));
+ rv = IpcChannelHelper.Retry(nameof(SystemClipboard.ContainsText), () => { return SystemClipboard.ContainsText(); }, (log) => Log(log));
}
catch (ExternalException e)
{
@@ -471,7 +471,7 @@ WellKnownSidType.AuthenticatedUserSid, null);
try
{
- rv = Common.Retry(nameof(SystemClipboard.GetFileDropList), () => { return SystemClipboard.GetFileDropList(); }, (log) => Log(log));
+ rv = IpcChannelHelper.Retry(nameof(SystemClipboard.GetFileDropList), () => { return SystemClipboard.GetFileDropList(); }, (log) => Log(log));
}
catch (ExternalException e)
{
@@ -493,7 +493,7 @@ WellKnownSidType.AuthenticatedUserSid, null);
try
{
- rv = Common.Retry(nameof(SystemClipboard.GetImage), () => { return SystemClipboard.GetImage(); }, (log) => Log(log));
+ rv = IpcChannelHelper.Retry(nameof(SystemClipboard.GetImage), () => { return SystemClipboard.GetImage(); }, (log) => Log(log));
}
catch (ExternalException e)
{
@@ -515,7 +515,7 @@ WellKnownSidType.AuthenticatedUserSid, null);
try
{
- rv = Common.Retry(nameof(SystemClipboard.GetText), () => { return SystemClipboard.GetText(format); }, (log) => Log(log));
+ rv = IpcChannelHelper.Retry(nameof(SystemClipboard.GetText), () => { return SystemClipboard.GetText(format); }, (log) => Log(log));
}
catch (ExternalException e)
{
@@ -539,7 +539,7 @@ WellKnownSidType.AuthenticatedUserSid, null);
{
try
{
- _ = Common.Retry(
+ _ = IpcChannelHelper.Retry(
nameof(SystemClipboard.SetImage),
() =>
{
@@ -568,7 +568,7 @@ WellKnownSidType.AuthenticatedUserSid, null);
{
try
{
- _ = Common.Retry(
+ _ = IpcChannelHelper.Retry(
nameof(SystemClipboard.SetText),
() =>
{
@@ -600,44 +600,4 @@ WellKnownSidType.AuthenticatedUserSid, null);
{
internal const int QUIT_CMD = 0x409;
}
-
- internal sealed partial class Common
- {
- internal static bool IpcChannelCreated { get; set; }
-
- internal static T Retry(string name, Func func, Action log, Action preRetry = null)
- {
- int count = 0;
-
- do
- {
- try
- {
- T rv = func();
-
- if (count > 0)
- {
- log($"Trace: {name} has been successful after {count} retry.");
- }
-
- return rv;
- }
- catch (Exception)
- {
- count++;
-
- preRetry?.Invoke();
-
- if (count > 10)
- {
- throw;
- }
-
- Application.DoEvents();
- Thread.Sleep(200);
- }
- }
- while (true);
- }
- }
}
diff --git a/src/modules/MouseWithoutBorders/App/Core/Clipboard.cs b/src/modules/MouseWithoutBorders/App/Core/Clipboard.cs
index e557ff4a37..db16ac8b4d 100644
--- a/src/modules/MouseWithoutBorders/App/Core/Clipboard.cs
+++ b/src/modules/MouseWithoutBorders/App/Core/Clipboard.cs
@@ -1036,7 +1036,7 @@ internal static class Clipboard
{
try
{
- _ = Common.Retry(
+ _ = IpcChannelHelper.Retry(
nameof(SystemClipboard.SetFileDropList),
() =>
{
@@ -1073,7 +1073,7 @@ internal static class Clipboard
{
try
{
- _ = Common.Retry(
+ _ = IpcChannelHelper.Retry(
nameof(SystemClipboard.SetImage),
() =>
{
@@ -1104,7 +1104,7 @@ internal static class Clipboard
{
try
{
- _ = Common.Retry(
+ _ = IpcChannelHelper.Retry(
nameof(SystemClipboard.SetText),
() =>
{
diff --git a/src/modules/MouseWithoutBorders/App/Core/Common.cs b/src/modules/MouseWithoutBorders/App/Core/Common.cs
new file mode 100644
index 0000000000..3c2206f2ab
--- /dev/null
+++ b/src/modules/MouseWithoutBorders/App/Core/Common.cs
@@ -0,0 +1,1654 @@
+// 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.Diagnostics.CodeAnalysis;
+using System.Drawing;
+using System.Drawing.Imaging;
+using System.Globalization;
+using System.IO;
+using System.Linq;
+using System.Net;
+using System.Net.NetworkInformation;
+using System.Net.Sockets;
+using System.Runtime.InteropServices;
+using System.Security.Cryptography;
+using System.Text;
+using System.Threading;
+using System.Windows.Forms;
+
+using Microsoft.PowerToys.Settings.UI.Library;
+using MouseWithoutBorders.Class;
+using MouseWithoutBorders.Exceptions;
+
+using Clipboard = MouseWithoutBorders.Core.Clipboard;
+using Thread = MouseWithoutBorders.Core.Thread;
+
+// Log is enough
+[module: SuppressMessage("Microsoft.Design", "CA1031:DoNotCatchGeneralExceptionTypes", Scope = "member", Target = "MouseWithoutBorders.Common.#CheckClipboard()", Justification = "Dotnet port with style preservation")]
+[module: SuppressMessage("Microsoft.Design", "CA1031:DoNotCatchGeneralExceptionTypes", Scope = "member", Target = "MouseWithoutBorders.Common.#CheckForDesktopSwitchEvent(System.Boolean)", Justification = "Dotnet port with style preservation")]
+[module: SuppressMessage("Microsoft.Design", "CA1031:DoNotCatchGeneralExceptionTypes", Scope = "member", Target = "MouseWithoutBorders.Common.#SetAsStartupItem(System.Boolean)", Justification = "Dotnet port with style preservation")]
+[module: SuppressMessage("Microsoft.Design", "CA1031:DoNotCatchGeneralExceptionTypes", Scope = "member", Target = "MouseWithoutBorders.Common.#HelperThread()", Justification = "Dotnet port with style preservation")]
+[module: SuppressMessage("Microsoft.Design", "CA1031:DoNotCatchGeneralExceptionTypes", Scope = "member", Target = "MouseWithoutBorders.Common.#GetMyStorageDir()", Justification = "Dotnet port with style preservation")]
+[module: SuppressMessage("Microsoft.Design", "CA1031:DoNotCatchGeneralExceptionTypes", Scope = "member", Target = "MouseWithoutBorders.Common.#MouseEvent(MouseWithoutBorders.MOUSEDATA)", Justification = "Dotnet port with style preservation")]
+[module: SuppressMessage("Microsoft.Design", "CA1031:DoNotCatchGeneralExceptionTypes", Scope = "member", Target = "MouseWithoutBorders.Common.#KeybdEvent(MouseWithoutBorders.KEYBDDATA)", Justification = "Dotnet port with style preservation")]
+[module: SuppressMessage("Microsoft.Design", "CA1031:DoNotCatchGeneralExceptionTypes", Scope = "member", Target = "MouseWithoutBorders.Common.#ImpersonateLoggedOnUserAndDoSomething(System.Threading.ThreadStart)", Justification = "Dotnet port with style preservation")]
+[module: SuppressMessage("Microsoft.Design", "CA1031:DoNotCatchGeneralExceptionTypes", Scope = "member", Target = "MouseWithoutBorders.Common.#StartMouseWithoutBordersService()", Justification = "Dotnet port with style preservation")]
+[module: SuppressMessage("Microsoft.Design", "CA1031:DoNotCatchGeneralExceptionTypes", Scope = "member", Target = "MouseWithoutBorders.Common.#HookClipboard()", Justification = "Dotnet port with style preservation")]
+[module: SuppressMessage("Microsoft.Design", "CA1031:DoNotCatchGeneralExceptionTypes", Scope = "member", Target = "MouseWithoutBorders.Common.#ReceiveClipboardData(MouseWithoutBorders.DATA,System.Boolean)", Justification = "Dotnet port with style preservation")]
+[module: SuppressMessage("Microsoft.Design", "CA1031:DoNotCatchGeneralExceptionTypes", Scope = "member", Target = "MouseWithoutBorders.Common.#ReceiverCallback(System.Object)", Justification = "Dotnet port with style preservation")]
+[module: SuppressMessage("Microsoft.Design", "CA1031:DoNotCatchGeneralExceptionTypes", Scope = "member", Target = "MouseWithoutBorders.Common.#ConnectAndGetData(System.Object)", Justification = "Dotnet port with style preservation")]
+[module: SuppressMessage("Microsoft.Design", "CA1031:DoNotCatchGeneralExceptionTypes", Scope = "member", Target = "MouseWithoutBorders.Common.#CheckNewVersion()", Justification = "Dotnet port with style preservation")]
+[module: SuppressMessage("Microsoft.Design", "CA1031:DoNotCatchGeneralExceptionTypes", Scope = "member", Target = "MouseWithoutBorders.Common.#StartServiceAndSendLogoffSignal()", Justification = "Dotnet port with style preservation")]
+[module: SuppressMessage("Microsoft.Design", "CA1031:DoNotCatchGeneralExceptionTypes", Scope = "member", Target = "MouseWithoutBorders.Common.#GetScreenConfig()", Justification = "Dotnet port with style preservation")]
+[module: SuppressMessage("Microsoft.Design", "CA1031:DoNotCatchGeneralExceptionTypes", Scope = "member", Target = "MouseWithoutBorders.Common.#CaptureScreen()", Justification = "Dotnet port with style preservation")]
+[module: SuppressMessage("Microsoft.Design", "CA1031:DoNotCatchGeneralExceptionTypes", Scope = "member", Target = "MouseWithoutBorders.Common.#InitEncryption()", Justification = "Dotnet port with style preservation")]
+[module: SuppressMessage("Microsoft.Design", "CA1031:DoNotCatchGeneralExceptionTypes", Scope = "member", Target = "MouseWithoutBorders.Common.#ToggleIcon()", Justification = "Dotnet port with style preservation")]
+[module: SuppressMessage("Microsoft.Design", "CA1031:DoNotCatchGeneralExceptionTypes", Scope = "member", Target = "MouseWithoutBorders.Common.#GetNameAndIPAddresses()", Justification = "Dotnet port with style preservation")]
+[module: SuppressMessage("Microsoft.Design", "CA1031:DoNotCatchGeneralExceptionTypes", Scope = "member", Target = "MouseWithoutBorders.Common.#Cleanup()", Justification = "Dotnet port with style preservation")]
+[module: SuppressMessage("Microsoft.Maintainability", "CA1506:AvoidExcessiveClassCoupling", Scope = "type", Target = "MouseWithoutBorders.Common", Justification = "Dotnet port with style preservation")]
+[module: SuppressMessage("Microsoft.Maintainability", "CA1502:AvoidExcessiveComplexity", Scope = "member", Target = "MouseWithoutBorders.Common.#ConnectAndGetData(System.Object)", Justification = "Dotnet port with style preservation")]
+[module: SuppressMessage("Microsoft.Maintainability", "CA1502:AvoidExcessiveComplexity", Scope = "member", Target = "MouseWithoutBorders.Common.#ProcessPackage(MouseWithoutBorders.DATA)", Justification = "Dotnet port with style preservation")]
+[module: SuppressMessage("Microsoft.Design", "CA1031:DoNotCatchGeneralExceptionTypes", Scope = "member", Target = "MouseWithoutBorders.Common.#SetOEMBackground(System.Boolean)", Justification = "Dotnet port with style preservation")]
+[module: SuppressMessage("Microsoft.Design", "CA1031:DoNotCatchGeneralExceptionTypes", Scope = "member", Target = "MouseWithoutBorders.Common.#get_Machine_Pool()", Justification = "Dotnet port with style preservation")]
+[module: SuppressMessage("Microsoft.Design", "CA1031:DoNotCatchGeneralExceptionTypes", Scope = "member", Target = "MouseWithoutBorders.Common.#SetOEMBackground(System.Boolean,System.String)", Justification = "Dotnet port with style preservation")]
+[module: SuppressMessage("Microsoft.Design", "CA1031:DoNotCatchGeneralExceptionTypes", Scope = "member", Target = "MouseWithoutBorders.Common.#GetNewImageAndSaveTo(System.String,System.String)", Justification = "Dotnet port with style preservation")]
+[module: SuppressMessage("Microsoft.Design", "CA1031:DoNotCatchGeneralExceptionTypes", Scope = "member", Target = "MouseWithoutBorders.Common.#CreateLowIntegrityProcess(System.String,System.String,System.Int32)", Justification = "Dotnet port with style preservation")]
+[module: SuppressMessage("Microsoft.Usage", "CA1806:DoNotIgnoreMethodResults", Scope = "member", Target = "MouseWithoutBorders.Common.#LogAll()", MessageId = "System.String.Format(System.IFormatProvider,System.String,System.Object[])", Justification = "Dotnet port with style preservation")]
+[module: SuppressMessage("Microsoft.Usage", "CA1806:DoNotIgnoreMethodResults", Scope = "member", Target = "MouseWithoutBorders.Common.#CheckForDesktopSwitchEvent(System.Boolean)", MessageId = "MouseWithoutBorders.NativeMethods.SendMessage(System.IntPtr,System.Int32,System.IntPtr,System.IntPtr)", Justification = "Dotnet port with style preservation")]
+[module: SuppressMessage("Microsoft.Usage", "CA1806:DoNotIgnoreMethodResults", Scope = "member", Target = "MouseWithoutBorders.Common.#DragDropStep04()", MessageId = "MouseWithoutBorders.NativeMethods.SendMessage(System.IntPtr,System.Int32,System.IntPtr,System.IntPtr)", Justification = "Dotnet port with style preservation")]
+[module: SuppressMessage("Microsoft.Usage", "CA1806:DoNotIgnoreMethodResults", Scope = "member", Target = "MouseWithoutBorders.Common.#CreateLowIntegrityProcess(System.String,System.String,System.Int32)", MessageId = "MouseWithoutBorders.NativeMethods.WaitForSingleObject(System.IntPtr,System.Int32)", Justification = "Dotnet port with style preservation")]
+[module: SuppressMessage("Microsoft.Usage", "CA1806:DoNotIgnoreMethodResults", Scope = "member", Target = "MouseWithoutBorders.Common.#GetText(System.IntPtr)", MessageId = "MouseWithoutBorders.NativeMethods.GetWindowText(System.IntPtr,System.Text.StringBuilder,System.Int32)", Justification = "Dotnet port with style preservation")]
+[module: SuppressMessage("Microsoft.Usage", "CA1806:DoNotIgnoreMethodResults", Scope = "member", Target = "MouseWithoutBorders.Common.#ImpersonateLoggedOnUserAndDoSomething(System.Threading.ThreadStart)", MessageId = "MouseWithoutBorders.NativeMethods.WTSQueryUserToken(System.UInt32,System.IntPtr@)", Justification = "Dotnet port with style preservation")]
+[module: SuppressMessage("Microsoft.Design", "CA1031:DoNotCatchGeneralExceptionTypes", Scope = "member", Target = "MouseWithoutBorders.Common.#CreateLowIntegrityProcess(System.String,System.String,System.Int32,System.Boolean,System.Int64)", Justification = "Dotnet port with style preservation")]
+[module: SuppressMessage("Microsoft.Design", "CA1031:DoNotCatchGeneralExceptionTypes", Scope = "member", Target = "MouseWithoutBorders.Common.#CreateProcessInInputDesktopSession(System.String,System.String,System.String,System.Boolean,System.Int16)", Justification = "Dotnet port with style preservation")]
+[module: SuppressMessage("Microsoft.Design", "CA1031:DoNotCatchGeneralExceptionTypes", Scope = "member", Target = "MouseWithoutBorders.Common.#SkSend(MouseWithoutBorders.DATA,System.Boolean,System.Int32)", Justification = "Dotnet port with style preservation")]
+[module: SuppressMessage("Microsoft.Design", "CA1031:DoNotCatchGeneralExceptionTypes", Scope = "member", Target = "MouseWithoutBorders.Common.#ReceiveClipboardDataUsingTCP(MouseWithoutBorders.DATA,System.Boolean,System.Net.Sockets.Socket)", Justification = "Dotnet port with style preservation")]
+[module: SuppressMessage("Microsoft.Design", "CA1031:DoNotCatchGeneralExceptionTypes", Scope = "member", Target = "MouseWithoutBorders.Common.#UpdateMachineMatrix(MouseWithoutBorders.DATA)", Justification = "Dotnet port with style preservation")]
+[module: SuppressMessage("Microsoft.Design", "CA1031:DoNotCatchGeneralExceptionTypes", Scope = "member", Target = "MouseWithoutBorders.Common.#ReopenSockets(System.Boolean)", Justification = "Dotnet port with style preservation")]
+
+//
+// Most of the helper methods.
+//
+//
+// 2008 created by Truong Do (ductdo).
+// 2009-... modified by Truong Do (TruongDo).
+// 2023- Included in PowerToys.
+//
+namespace MouseWithoutBorders.Core;
+
+internal static class Common
+{
+ private static InputHook hook;
+ private static FrmMatrix matrixForm;
+ private static FrmInputCallback inputCallbackForm;
+ private static FrmAbout aboutForm;
+#pragma warning disable SA1307 // Accessible fields should begin with upper-case letter
+ internal static Thread helper;
+ internal static int screenWidth;
+ internal static int screenHeight;
+#pragma warning restore SA1307
+ private static int lastX;
+ private static int lastY;
+
+ private static bool mainFormVisible = true;
+ private static bool runOnLogonDesktop;
+ private static bool runOnScrSaverDesktop;
+
+#pragma warning disable SA1307 // Accessible fields should begin with upper-case letter
+ internal static int[] toggleIcons;
+ internal static int toggleIconsIndex;
+#pragma warning restore SA1307
+ internal const int TOGGLE_ICONS_SIZE = 4;
+ internal const int ICON_ONE = 0;
+ internal const int ICON_ALL = 1;
+ internal const int ICON_SMALL_CLIPBOARD = 2;
+ internal const int ICON_BIG_CLIPBOARD = 3;
+ internal const int ICON_ERROR = 4;
+ internal const int JUST_GOT_BACK_FROM_SCREEN_SAVER = 9999;
+
+ internal const int NETWORK_STREAM_BUF_SIZE = 1024 * 1024;
+ internal static readonly EventWaitHandle EvSwitch = new(false, EventResetMode.AutoReset);
+ private static Point lastPos;
+#pragma warning disable SA1307 // Accessible fields should begin with upper-case names
+ internal static int switchCount;
+#pragma warning restore SA1307
+ private static long lastReconnectByHotKeyTime;
+#pragma warning disable SA1307 // Accessible fields should begin with upper-case names
+ internal static int tcpPort;
+#pragma warning restore SA1307
+ private static bool secondOpenSocketTry;
+ private static string binaryName;
+
+ internal static Process CurrentProcess { get; set; }
+
+ internal static bool HotkeyMatched(int vkCode, bool winDown, bool ctrlDown, bool altDown, bool shiftDown, HotkeySettings hotkey)
+ {
+ return !hotkey.IsEmpty() && (vkCode == hotkey.Code) && (!hotkey.Win || winDown) && (!hotkey.Alt || altDown) && (!hotkey.Shift || shiftDown) && (!hotkey.Ctrl || ctrlDown);
+ }
+
+ internal static string BinaryName
+ {
+ get => Common.binaryName;
+ set => Common.binaryName = value;
+ }
+
+ internal static bool SecondOpenSocketTry
+ {
+ get => Common.secondOpenSocketTry;
+ set => Common.secondOpenSocketTry = value;
+ }
+
+ internal static long LastReconnectByHotKeyTime
+ {
+ get => Common.lastReconnectByHotKeyTime;
+ set => Common.lastReconnectByHotKeyTime = value;
+ }
+
+ internal static int SwitchCount
+ {
+ get => Common.switchCount;
+ set => Common.switchCount = value;
+ }
+
+ internal static Point LastPos
+ {
+ get => Common.lastPos;
+ set => Common.lastPos = value;
+ }
+
+ internal static FrmAbout AboutForm
+ {
+ get => Common.aboutForm;
+ set => Common.aboutForm = value;
+ }
+
+ internal static FrmInputCallback InputCallbackForm
+ {
+ get => Common.inputCallbackForm;
+ set => Common.inputCallbackForm = value;
+ }
+
+ internal static int PaintCount { get; set; }
+
+ internal static bool RunOnScrSaverDesktop
+ {
+ get => Common.runOnScrSaverDesktop;
+ set => Common.runOnScrSaverDesktop = value;
+ }
+
+ internal static bool RunOnLogonDesktop
+ {
+ get => Common.runOnLogonDesktop;
+ set => Common.runOnLogonDesktop = value;
+ }
+
+ internal static bool RunWithNoAdminRight { get; set; }
+
+ internal static int LastX
+ {
+ get => Common.lastX;
+ set => Common.lastX = value;
+ }
+
+ internal static int LastY
+ {
+ get => Common.lastY;
+ set => Common.lastY = value;
+ }
+
+ internal static int[] ToggleIcons => Common.toggleIcons;
+
+ internal static int ScreenHeight => Common.screenHeight;
+
+ internal static int ScreenWidth => Common.screenWidth;
+
+ internal static bool Is64bitOS
+ {
+ get; set;
+
+ // set { Common.is64bitOS = value; }
+ }
+
+ internal static int ToggleIconsIndex
+ {
+ // get { return Common.toggleIconsIndex; }
+ set => Common.toggleIconsIndex = value;
+ }
+
+ internal static InputHook Hook
+ {
+ get => Common.hook;
+ set => Common.hook = value;
+ }
+
+ internal static SocketStuff Sk { get; set; }
+
+ internal static FrmScreen MainForm { get; set; }
+
+ internal static FrmMouseCursor MouseCursorForm { get; set; }
+
+ internal static FrmMatrix MatrixForm
+ {
+ get => Common.matrixForm;
+ set => Common.matrixForm = value;
+ }
+
+ internal static ID DesMachineID
+ {
+ get => MachineStuff.desMachineID;
+
+ set
+ {
+ MachineStuff.desMachineID = value;
+ MachineStuff.DesMachineName = MachineStuff.NameFromID(MachineStuff.desMachineID);
+ }
+ }
+
+ internal static ID MachineID => (ID)Setting.Values.MachineId;
+
+ internal static string MachineName { get; set; }
+
+ internal static bool MainFormVisible
+ {
+ get => Common.mainFormVisible;
+ set => Common.mainFormVisible = value;
+ }
+
+ internal static Mutex SocketMutex { get; set; } // Synchronization between MouseWithoutBorders running in different desktops
+
+ // TODO: For telemetry only, to be removed.
+ private static int socketMutexBalance;
+
+ internal static void ReleaseSocketMutex()
+ {
+ if (SocketMutex != null)
+ {
+ Logger.LogDebug("SOCKET MUTEX BEGIN RELEASE.");
+
+ try
+ {
+ _ = Interlocked.Decrement(ref socketMutexBalance);
+ SocketMutex.ReleaseMutex();
+ }
+ catch (ApplicationException e)
+ {
+ // The current thread does not own the mutex, the thread acquired it will own it.
+ Logger.TelemetryLogTrace($"{nameof(ReleaseSocketMutex)}: {e.Message}. {Thread.CurrentThread.ManagedThreadId}/{UIThreadID}.", SeverityLevel.Warning);
+ }
+
+ Logger.LogDebug("SOCKET MUTEX RELEASED.");
+ }
+ else
+ {
+ Logger.LogDebug("SOCKET MUTEX NULL.");
+ }
+ }
+
+ internal static void AcquireSocketMutex()
+ {
+ if (SocketMutex != null)
+ {
+ Logger.LogDebug("SOCKET MUTEX BEGIN WAIT.");
+ int waitTimeout = 60000; // TcpListener.Stop may take very long to complete for some reason.
+
+ int socketMutexBalance = int.MinValue;
+
+ bool acquireMutex = ExecuteAndTrace(
+ "Waiting for sockets to close",
+ () =>
+ {
+ socketMutexBalance = Interlocked.Increment(ref Common.socketMutexBalance);
+ _ = SocketMutex.WaitOne(waitTimeout); // The app now requires .Net 4.0. Note: .Net20RTM does not have the one-parameter version of the API.
+ },
+ TimeSpan.FromSeconds(5));
+
+ // Took longer than expected.
+ if (!acquireMutex)
+ {
+ Process[] ps = Process.GetProcessesByName(Common.BinaryName);
+ Logger.TelemetryLogTrace($"Balance: {socketMutexBalance}, Active: {WinAPI.IsMyDesktopActive()}, Sid/Console: {Process.GetCurrentProcess().SessionId}/{NativeMethods.WTSGetActiveConsoleSessionId()}, Desktop/Input: {WinAPI.GetMyDesktop()}/{WinAPI.GetInputDesktop()}, count: {ps?.Length}.", SeverityLevel.Warning);
+ }
+
+ Logger.LogDebug("SOCKET MUTEX ENDED.");
+ }
+ else
+ {
+ Logger.LogDebug("SOCKET MUTEX NULL.");
+ }
+ }
+
+ internal static bool BlockingUI { get; private set; }
+
+ internal static bool ExecuteAndTrace(string actionName, Action action, TimeSpan timeout, bool restart = false)
+ {
+ bool rv = true;
+ Logger.LogDebug(actionName);
+ bool done = false;
+
+ BlockingUI = true;
+
+ if (restart)
+ {
+ Common.MainForm.Text = Setting.Values.MyIdEx;
+
+ /* closesocket() rarely gets stuck for some reason inside ntdll!ZwClose ...=>... afd!AfdCleanupCore.
+ * There is no good workaround for it so far, still working with [Winsock 2.0 Discussions] to address the issue.
+ * */
+ new Thread(
+ () =>
+ {
+ for (int i = 0; i < timeout.TotalSeconds; i++)
+ {
+ Thread.Sleep(1000);
+
+ if (done)
+ {
+ return;
+ }
+ }
+
+ Logger.TelemetryLogTrace($"[{actionName}] took more than {(long)timeout.TotalSeconds}, restarting the process.", SeverityLevel.Warning, true);
+
+ string desktop = WinAPI.GetMyDesktop();
+ MachineStuff.oneInstanceCheck?.Close();
+ _ = Process.Start(Application.ExecutablePath, desktop);
+ Logger.LogDebug($"Started on desktop {desktop}");
+
+ Process.GetCurrentProcess().KillProcess(true);
+ },
+ $"{actionName} watchdog").Start();
+ }
+
+ Stopwatch timer = Stopwatch.StartNew();
+
+ try
+ {
+ action();
+ }
+ finally
+ {
+ done = true;
+ BlockingUI = false;
+
+ if (restart)
+ {
+ Common.MainForm.Text = Setting.Values.MyID;
+ }
+
+ timer.Stop();
+
+ if (timer.Elapsed > timeout)
+ {
+ rv = false;
+
+ if (!restart)
+ {
+ Logger.TelemetryLogTrace($"[{actionName}] took more than {(long)timeout.TotalSeconds}: {(long)timer.Elapsed.TotalSeconds}.", SeverityLevel.Warning);
+ }
+ }
+ }
+
+ return rv;
+ }
+
+ internal static byte[] GetBytes(string st)
+ {
+ return ASCIIEncoding.ASCII.GetBytes(st);
+ }
+
+ internal static string GetString(byte[] bytes)
+ {
+ return ASCIIEncoding.ASCII.GetString(bytes);
+ }
+
+ internal static byte[] GetBytesU(string st)
+ {
+ return ASCIIEncoding.Unicode.GetBytes(st);
+ }
+
+ internal static string GetStringU(byte[] bytes)
+ {
+ return ASCIIEncoding.Unicode.GetString(bytes);
+ }
+
+ internal static int UIThreadID { get; set; }
+
+ internal static void DoSomethingInUIThread(Action action, bool blocking = false)
+ {
+ InvokeInFormThread(MainForm, UIThreadID, action, blocking);
+ }
+
+ internal static int InputCallbackThreadID { get; set; }
+
+ internal static void DoSomethingInTheInputCallbackThread(Action action, bool blocking = true)
+ {
+ InvokeInFormThread(InputCallbackForm, InputCallbackThreadID, action, blocking);
+ }
+
+ private static void InvokeInFormThread(System.Windows.Forms.Form form, int threadId, Action action, bool blocking)
+ {
+ if (form != null)
+ {
+ int currentThreadId = Thread.CurrentThread.ManagedThreadId;
+
+ if (currentThreadId == threadId)
+ {
+ action();
+ }
+ else
+ {
+ bool done = false;
+
+ try
+ {
+ Action callback = () =>
+ {
+ try
+ {
+ action();
+ }
+ catch (Exception e)
+ {
+ Logger.Log(e);
+ }
+ finally
+ {
+ done = true;
+ }
+ };
+ _ = form.BeginInvoke(callback);
+ }
+ catch (Exception e)
+ {
+ done = true;
+ Logger.Log(e);
+ }
+
+ while (blocking && !done)
+ {
+ Thread.Sleep(16);
+
+ if (currentThreadId == UIThreadID || currentThreadId == InputCallbackThreadID)
+ {
+ Application.DoEvents();
+ }
+ }
+ }
+ }
+ }
+
+ private static readonly Lock InputSimulationLock = new();
+
+ internal static void DoSomethingInTheInputSimulationThread(ThreadStart target)
+ {
+ /*
+ * For some reason, SendInput may hit deadlock if it is called in the InputHookProc thread.
+ * For now leave it as is in the caller thread which is the socket receiver thread.
+ * */
+
+ // SendInput is thread-safe but few users seem to hit a deadlock occasionally, probably a Windows bug.
+ lock (InputSimulationLock)
+ {
+ target();
+ }
+ }
+
+ internal static void SendPackage(ID des, PackageType packageType)
+ {
+ DATA package = new();
+ package.Type = packageType;
+ package.Des = des;
+ package.MachineName = MachineName;
+
+ SkSend(package, null, false);
+ }
+
+ internal static void SendHeartBeat(bool initial = false)
+ {
+ SendPackage(ID.ALL, initial && Encryption.GeneratedKey ? PackageType.Heartbeat_ex : PackageType.Heartbeat);
+ }
+
+ private static long lastSendNextMachine;
+
+ internal static void SendNextMachine(ID hostMachine, ID nextMachine, Point requestedXY)
+ {
+ Logger.LogDebug($"SendNextMachine: Host machine: {hostMachine}, Next machine: {nextMachine}, Requested XY: {requestedXY}");
+
+ if (GetTick() - lastSendNextMachine < 100)
+ {
+ Logger.LogDebug("Machine switching in progress."); // "Move Mouse relatively" mode, slow machine/network, quick/busy hand.
+ return;
+ }
+
+ lastSendNextMachine = GetTick();
+
+ DATA package = new();
+ package.Type = PackageType.NextMachine;
+
+ package.Des = hostMachine;
+
+ package.Md.X = requestedXY.X;
+ package.Md.Y = requestedXY.Y;
+ package.Md.WheelDelta = (int)nextMachine;
+
+ SkSend(package, null, false);
+
+ Logger.LogDebug("SendNextMachine done.");
+ }
+
+ private static ulong lastInputEventCount;
+ private static ulong lastRealInputEventCount;
+
+ internal static void SendAwakeBeat()
+ {
+ if (!Common.RunOnLogonDesktop && !Common.RunOnScrSaverDesktop && WinAPI.IsMyDesktopActive() &&
+ Setting.Values.BlockScreenSaver && lastRealInputEventCount != Event.RealInputEventCount)
+ {
+ SendPackage(ID.ALL, PackageType.Awake);
+ }
+ else
+ {
+ SendHeartBeat();
+ }
+
+ lastInputEventCount = Event.InputEventCount;
+ lastRealInputEventCount = Event.RealInputEventCount;
+ }
+
+ internal static void HumanBeingDetected()
+ {
+ if (lastInputEventCount == Event.InputEventCount)
+ {
+ if (!Common.RunOnLogonDesktop && !Common.RunOnScrSaverDesktop && WinAPI.IsMyDesktopActive())
+ {
+ PokeMyself();
+ }
+ }
+
+ lastInputEventCount = Event.InputEventCount;
+ }
+
+ private static void PokeMyself()
+ {
+ int x, y = 0;
+
+ for (int i = 0; i < 10; i++)
+ {
+ x = Encryption.Ran.Next(-9, 10);
+ InputSimulation.MoveMouseRelative(x, y);
+ Thread.Sleep(50);
+ InputSimulation.MoveMouseRelative(-x, -y);
+ Thread.Sleep(50);
+
+ if (lastInputEventCount != Event.InputEventCount)
+ {
+ break;
+ }
+ }
+ }
+
+ internal static void InitLastInputEventCount()
+ {
+ lastInputEventCount = Event.InputEventCount;
+ lastRealInputEventCount = Event.RealInputEventCount;
+ }
+
+ internal static void SendHello()
+ {
+ SendPackage(ID.ALL, PackageType.Hello);
+ }
+
+ /*
+ internal static void SendHi()
+ {
+ SendPackage(IP.ALL, PackageType.hi);
+ }
+ * */
+
+ internal static void SendByeBye()
+ {
+ Logger.LogDebug($"{nameof(SendByeBye)}");
+ SendPackage(ID.ALL, PackageType.ByeBye);
+ }
+
+ internal static void SendClipboardBeat()
+ {
+ SendPackage(ID.ALL, PackageType.Clipboard);
+ }
+
+ internal static void ProcessByeByeMessage(DATA package)
+ {
+ if (package.Src == MachineStuff.desMachineID)
+ {
+ MachineStuff.SwitchToMachine(MachineName.Trim());
+ }
+
+ _ = MachineStuff.RemoveDeadMachines(package.Src);
+ }
+
+ internal static long GetTick() // ms
+ {
+ return DateTime.Now.Ticks / 10000;
+ }
+
+ internal static void SetToggleIcon(int[] toggleIcons)
+ {
+ Logger.LogDebug($"{nameof(SetToggleIcon)}: {toggleIcons?.FirstOrDefault()}");
+ Common.toggleIcons = toggleIcons;
+ toggleIconsIndex = 0;
+ }
+
+ internal static string CaptureScreen()
+ {
+ try
+ {
+ string fileName = GetMyStorageDir() + @"ScreenCaptureByMouseWithoutBorders.png";
+ int w = MachineStuff.desktopBounds.Right - MachineStuff.desktopBounds.Left;
+ int h = MachineStuff.desktopBounds.Bottom - MachineStuff.desktopBounds.Top;
+ Bitmap bm = new(w, h);
+ Graphics g = Graphics.FromImage(bm);
+ Size s = new(w, h);
+ g.CopyFromScreen(MachineStuff.desktopBounds.Left, MachineStuff.desktopBounds.Top, 0, 0, s);
+ bm.Save(fileName, ImageFormat.Png);
+ bm.Dispose();
+ return fileName;
+ }
+ catch (Exception e)
+ {
+ Logger.Log(e);
+ return null;
+ }
+ }
+
+ private static void PrepareScreenCapture()
+ {
+ Common.DoSomethingInUIThread(() =>
+ {
+ if (!DragDrop.MouseDown && Helper.SendMessageToHelper(0x401, IntPtr.Zero, IntPtr.Zero) > 0)
+ {
+ Common.MMSleep(0.2);
+ InputSimulation.SendKey(new KEYBDDATA() { wVk = (int)VK.SNAPSHOT });
+ InputSimulation.SendKey(new KEYBDDATA() { dwFlags = (int)WM.LLKHF.UP, wVk = (int)VK.SNAPSHOT });
+
+ Logger.LogDebug("PrepareScreenCapture: SNAPSHOT simulated.");
+
+ _ = NativeMethods.MoveWindow(
+ (IntPtr)NativeMethods.FindWindow(null, Helper.HELPER_FORM_TEXT),
+ MachineStuff.DesktopBounds.Left,
+ MachineStuff.DesktopBounds.Top,
+ MachineStuff.DesktopBounds.Right - MachineStuff.DesktopBounds.Left,
+ MachineStuff.DesktopBounds.Bottom - MachineStuff.DesktopBounds.Top,
+ false);
+
+ _ = Helper.SendMessageToHelper(0x406, IntPtr.Zero, IntPtr.Zero, false);
+ }
+ else
+ {
+ Logger.Log("PrepareScreenCapture: Validation failed.");
+ }
+ });
+ }
+
+ internal static void OpenImage(string file)
+ {
+ // We want to run mspaint under the user account who ran explorer.exe (who logged in this current input desktop)
+
+ // ImpersonateLoggedOnUserAndDoSomething(delegate()
+ // {
+ // Process.Start("explorer", "\"" + file + "\"");
+ // });
+ _ = Launch.CreateProcessInInputDesktopSession(
+ "\"" + Environment.ExpandEnvironmentVariables(@"%SystemRoot%\System32\Mspaint.exe") +
+ "\"",
+ "\"" + file + "\"",
+ WinAPI.GetInputDesktop(),
+ 1);
+
+ // CreateNormalIntegrityProcess(Environment.ExpandEnvironmentVariables(@"%SystemRoot%\System32\Mspaint.exe") +
+ // " \"" + file + "\"");
+
+ // We don't want to run mspaint as local system account
+ /*
+ ProcessStartInfo s = new ProcessStartInfo(
+ Environment.ExpandEnvironmentVariables(@"%SystemRoot%\System32\Mspaint.exe"),
+ "\"" + file + "\"");
+ s.WindowStyle = ProcessWindowStyle.Maximized;
+ Process.Start(s);
+ * */
+ }
+
+ internal static void SendImage(string machine, string file)
+ {
+ Clipboard.LastDragDropFile = file;
+
+ // Send ClipboardCapture
+ if (machine.Equals("All", StringComparison.OrdinalIgnoreCase))
+ {
+ SendPackage(ID.ALL, PackageType.ClipboardCapture);
+ }
+ else
+ {
+ ID id = MachineStuff.MachinePool.ResolveID(machine);
+ if (id != ID.NONE)
+ {
+ SendPackage(id, PackageType.ClipboardCapture);
+ }
+ }
+ }
+
+ internal static void SendImage(ID src, string file)
+ {
+ Clipboard.LastDragDropFile = file;
+
+ // Send ClipboardCapture
+ SendPackage(src, PackageType.ClipboardCapture);
+ }
+
+ internal static void ShowToolTip(string tip, int timeOutInMilliseconds = 5000, ToolTipIcon icon = ToolTipIcon.Info, bool showBalloonTip = true, bool forceEvenIfHidingOldUI = false)
+ {
+ if (!Common.RunOnLogonDesktop && !Common.RunOnScrSaverDesktop)
+ {
+ DoSomethingInUIThread(() =>
+ {
+ if (Setting.Values.FirstRun)
+ {
+ MachineStuff.Settings?.ShowTip(icon, tip, timeOutInMilliseconds);
+ }
+
+ Common.MatrixForm?.ShowTip(icon, tip, timeOutInMilliseconds);
+
+ if (showBalloonTip)
+ {
+ if (MainForm != null)
+ {
+ MainForm.ShowToolTip(tip, timeOutInMilliseconds, forceEvenIfHidingOldUI: forceEvenIfHidingOldUI);
+ }
+ else
+ {
+ Logger.Log(tip);
+ }
+ }
+ });
+ }
+ }
+
+ private static FrmMessage topMostMessageForm;
+
+ internal static void ToggleShowTopMostMessage(string text, string bigText, int timeOut)
+ {
+ DoSomethingInUIThread(() =>
+ {
+ if (topMostMessageForm == null)
+ {
+ topMostMessageForm = new FrmMessage(text, bigText, timeOut);
+ topMostMessageForm.Show();
+ }
+ else
+ {
+ FrmMessage currentMessageForm = topMostMessageForm;
+ topMostMessageForm = null;
+ currentMessageForm.Close();
+ }
+ });
+ }
+
+ internal static void HideTopMostMessage()
+ {
+ DoSomethingInUIThread(() =>
+ {
+ topMostMessageForm?.Close();
+ });
+ }
+
+ internal static void NullTopMostMessage()
+ {
+ DoSomethingInUIThread(() =>
+ {
+ if (topMostMessageForm != null)
+ {
+ topMostMessageForm = null;
+ }
+ });
+ }
+
+ internal static bool IsTopMostMessageNotNull()
+ {
+ return topMostMessageForm != null;
+ }
+
+ private static bool TestSend(TcpSk t)
+ {
+ ID remoteMachineID;
+
+ if (t.Status == SocketStatus.Connected)
+ {
+ try
+ {
+ DATA package = new();
+ package.Type = PackageType.Hi;
+ package.Des = remoteMachineID = (ID)t.MachineId;
+ package.MachineName = MachineName;
+
+ _ = Sk.TcpSend(t, package);
+ t.EncryptedStream?.Flush();
+
+ return true;
+ }
+ catch (ExpectedSocketException)
+ {
+ t.BackingSocket = null; // To be removed at CloseAnUnusedSocket()
+ }
+ }
+
+ t.Status = SocketStatus.SendError;
+ return false;
+ }
+
+ internal static bool IsConnectedTo(ID remoteMachineID)
+ {
+ bool updateClientSockets = false;
+
+ if (remoteMachineID == MachineID)
+ {
+ return true;
+ }
+
+ SocketStuff sk = Common.Sk;
+
+ if (sk != null)
+ {
+ lock (sk.TcpSocketsLock)
+ {
+ if (sk.TcpSockets != null)
+ {
+ foreach (TcpSk t in sk.TcpSockets)
+ {
+ if (t.Status == SocketStatus.Connected && (uint)remoteMachineID == t.MachineId)
+ {
+ if (TestSend(t))
+ {
+ return true;
+ }
+ else
+ {
+ updateClientSockets = true;
+ }
+ }
+ }
+ }
+ }
+ }
+
+ if (updateClientSockets)
+ {
+ MachineStuff.UpdateClientSockets(nameof(IsConnectedTo));
+ }
+
+ return false;
+ }
+
+#if DEBUG
+ private static long minSendTime = long.MaxValue;
+ private static long avgSendTime;
+ private static long maxSendTime;
+ private static long totalSendCount;
+ private static long totalSendTime;
+#endif
+
+ internal static void SkSend(DATA data, uint? exceptDes, bool includeHandShakingSockets)
+ {
+ bool connected = false;
+
+ SocketStuff sk = Sk;
+
+ if (sk != null)
+ {
+#if DEBUG
+ long startStop = DateTime.Now.Ticks;
+ totalSendCount++;
+#endif
+
+ try
+ {
+ data.Id = Interlocked.Increment(ref Package.PackageID);
+
+ bool updateClientSockets = false;
+
+ lock (sk.TcpSocketsLock)
+ {
+ foreach (TcpSk t in sk.TcpSockets)
+ {
+ if (t != null && t.BackingSocket != null && (t.Status == SocketStatus.Connected || (t.Status == SocketStatus.Handshaking && includeHandShakingSockets)))
+ {
+ if (t.MachineId == (uint)data.Des || (data.Des == ID.ALL && t.MachineId != exceptDes && MachineStuff.InMachineMatrix(t.MachineName)))
+ {
+ try
+ {
+ sk.TcpSend(t, data);
+
+ if (data.Des != ID.ALL)
+ {
+ connected = true;
+ }
+ }
+ catch (ExpectedSocketException)
+ {
+ t.BackingSocket = null; // To be removed at CloseAnUnusedSocket()
+ updateClientSockets = true;
+ }
+ catch (Exception e)
+ {
+ Logger.Log(e);
+ t.BackingSocket = null; // To be removed at CloseAnUnusedSocket()
+ updateClientSockets = true;
+ }
+ }
+ }
+ }
+ }
+
+ if (!connected && data.Des != ID.ALL)
+ {
+ Logger.LogDebug("********** No active connection found for the remote machine! **********" + data.Des.ToString());
+
+ if (data.Des == ID.NONE || MachineStuff.RemoveDeadMachines(data.Des))
+ {
+ // SwitchToMachine(MachineName.Trim());
+ MachineStuff.NewDesMachineID = DesMachineID = MachineID;
+ MachineStuff.SwitchLocation.X = Event.XY_BY_PIXEL + Event.myLastX;
+ MachineStuff.SwitchLocation.Y = Event.XY_BY_PIXEL + Event.myLastY;
+ MachineStuff.SwitchLocation.ResetCount();
+ EvSwitch.Set();
+ }
+ }
+
+ if (updateClientSockets)
+ {
+ MachineStuff.UpdateClientSockets("SkSend");
+ }
+ }
+ catch (Exception e)
+ {
+ Logger.Log(e);
+ }
+
+#if DEBUG
+ startStop = DateTime.Now.Ticks - startStop;
+ totalSendTime += startStop;
+ if (startStop < minSendTime)
+ {
+ minSendTime = startStop;
+ }
+
+ if (startStop > maxSendTime)
+ {
+ maxSendTime = startStop;
+ }
+
+ avgSendTime = totalSendTime / totalSendCount;
+#endif
+ }
+ else
+ {
+ Package.PackageSent.Nil++;
+ }
+ }
+
+ internal static void CloseAnUnusedSocket()
+ {
+ SocketStuff sk = Common.Sk;
+
+ if (sk != null)
+ {
+ lock (sk.TcpSocketsLock)
+ {
+ if (sk.TcpSockets != null)
+ {
+ TcpSk tobeRemoved = null;
+
+ foreach (TcpSk t in sk.TcpSockets)
+ {
+ if ((t.Status != SocketStatus.Connected && t.BirthTime < GetTick() - SocketStuff.CONNECT_TIMEOUT) || t.BackingSocket == null)
+ {
+ Logger.LogDebug("CloseAnUnusedSocket: " + t.MachineName + ":" + t.MachineId + "|" + t.Status.ToString());
+ tobeRemoved = t;
+
+ if (t.BackingSocket != null)
+ {
+ try
+ {
+ t.BackingSocket.Close();
+ }
+ catch (Exception e)
+ {
+ Logger.Log(e);
+ }
+ }
+
+ break; // Each time we try to remove one socket only.
+ }
+ }
+
+ if (tobeRemoved != null)
+ {
+ _ = sk.TcpSockets.Remove(tobeRemoved);
+ }
+ }
+ }
+ }
+ }
+
+ internal static bool AtLeastOneSocketConnected()
+ {
+ SocketStuff sk = Common.Sk;
+
+ if (sk != null)
+ {
+ lock (sk.TcpSocketsLock)
+ {
+ if (sk.TcpSockets != null)
+ {
+ foreach (TcpSk t in sk.TcpSockets)
+ {
+ if (t.Status == SocketStatus.Connected)
+ {
+ Logger.LogDebug("AtLeastOneSocketConnected returning true: " + t.MachineName);
+ return true;
+ }
+ }
+ }
+ }
+ }
+
+ Logger.LogDebug("AtLeastOneSocketConnected returning false.");
+ return false;
+ }
+
+ private static Socket AtLeastOneServerSocketConnected()
+ {
+ SocketStuff sk = Common.Sk;
+
+ if (sk != null)
+ {
+ lock (sk.TcpSocketsLock)
+ {
+ if (sk.TcpSockets != null)
+ {
+ foreach (TcpSk t in sk.TcpSockets)
+ {
+ if (!t.IsClient && t.Status == SocketStatus.Connected)
+ {
+ Logger.LogDebug("AtLeastOneServerSocketConnected returning true: " + t.MachineName);
+ return t.BackingSocket;
+ }
+ }
+ }
+ }
+ }
+
+ Logger.LogDebug("AtLeastOneServerSocketConnected returning false.");
+ return null;
+ }
+
+ internal static TcpSk GetConnectedClientSocket()
+ {
+ SocketStuff sk = Common.Sk;
+
+ if (sk != null)
+ {
+ lock (sk.TcpSocketsLock)
+ {
+ return sk.TcpSockets?.FirstOrDefault(item => item.IsClient && item.Status == SocketStatus.Connected);
+ }
+ }
+ else
+ {
+ return null;
+ }
+ }
+
+ internal static bool AtLeastOneSocketEstablished()
+ {
+ SocketStuff sk = Common.Sk;
+
+ if (sk != null)
+ {
+ lock (sk.TcpSocketsLock)
+ {
+ if (sk.TcpSockets != null)
+ {
+ foreach (TcpSk t in sk.TcpSockets)
+ {
+ if (t.BackingSocket != null && t.BackingSocket.Connected)
+ {
+ if (TestSend(t))
+ {
+ Logger.LogDebug($"{nameof(AtLeastOneSocketEstablished)} returning true: {t.MachineName}");
+ return true;
+ }
+ }
+ }
+ }
+ }
+ }
+
+ Logger.LogDebug($"{nameof(AtLeastOneSocketEstablished)} returning false.");
+ return false;
+ }
+
+ internal static bool IsConnectedByAClientSocketTo(string machineName)
+ {
+ SocketStuff sk = Common.Sk;
+
+ if (sk != null)
+ {
+ lock (sk.TcpSocketsLock)
+ {
+ foreach (TcpSk t in sk.TcpSockets)
+ {
+ if (t != null && t.IsClient && t.Status == SocketStatus.Connected
+ && t.BackingSocket != null && t.MachineName.Equals(machineName, StringComparison.OrdinalIgnoreCase))
+ {
+ return true;
+ }
+ }
+ }
+ }
+
+ return false;
+ }
+
+ internal static IPAddress GetConnectedClientSocketIPAddressFor(string machineName)
+ {
+ SocketStuff sk = Common.Sk;
+
+ if (sk != null)
+ {
+ lock (sk.TcpSocketsLock)
+ {
+ return sk.TcpSockets.FirstOrDefault(t => t != null && t.IsClient && t.Status == SocketStatus.Connected
+ && t.Address != null && t.MachineName.Equals(machineName, StringComparison.OrdinalIgnoreCase))
+ ?.Address;
+ }
+ }
+
+ return null;
+ }
+
+ internal static bool IsConnectingByAClientSocketTo(string machineName, IPAddress ip)
+ {
+ SocketStuff sk = Common.Sk;
+
+ if (sk != null)
+ {
+ lock (sk.TcpSocketsLock)
+ {
+ foreach (TcpSk t in sk.TcpSockets)
+ {
+ if (t != null && t.IsClient && t.Status == SocketStatus.Connecting
+ && t.BackingSocket != null && t.MachineName.Equals(machineName, StringComparison.OrdinalIgnoreCase)
+ && t.Address.ToString().Equals(ip.ToString(), StringComparison.OrdinalIgnoreCase))
+ {
+ return true;
+ }
+ }
+ }
+ }
+
+ return false;
+ }
+
+ internal static void UpdateSetupMachineMatrix(string desMachine)
+ {
+ int machineCt = 0;
+
+ foreach (string m in MachineStuff.MachineMatrix)
+ {
+ if (!string.IsNullOrEmpty(m.Trim()))
+ {
+ machineCt++;
+ }
+ }
+
+ if (machineCt < 2 && MachineStuff.Settings != null && (MachineStuff.Settings.GetCurrentPage() is SetupPage1 || MachineStuff.Settings.GetCurrentPage() is SetupPage2b))
+ {
+ MachineStuff.MachineMatrix = new string[MachineStuff.MAX_MACHINE] { Common.MachineName.Trim(), desMachine, string.Empty, string.Empty };
+ Logger.LogDebug("UpdateSetupMachineMatrix: " + string.Join(",", MachineStuff.MachineMatrix));
+
+ Common.DoSomethingInUIThread(
+ () =>
+ {
+ MachineStuff.Settings.SetControlPage(new SetupPage4());
+ },
+ true);
+ }
+ }
+
+ internal static void ReopenSockets(bool byUser)
+ {
+ DoSomethingInUIThread(
+ () =>
+ {
+ try
+ {
+ SocketStuff tmpSk = Sk;
+
+ if (tmpSk != null)
+ {
+ Sk = null; // TODO: This looks redundant.
+ tmpSk.Close(byUser);
+ }
+
+ Sk = new SocketStuff(tcpPort, byUser);
+ }
+ catch (Exception e)
+ {
+ Sk = null;
+ Logger.Log(e);
+ }
+
+ if (Sk != null)
+ {
+ if (byUser)
+ {
+ SocketStuff.ClearBadIPs();
+ }
+
+ MachineStuff.UpdateClientSockets("ReopenSockets");
+ }
+ },
+ true);
+
+ if (Sk == null)
+ {
+ return;
+ }
+
+ Common.DoSomethingInTheInputCallbackThread(() =>
+ {
+ if (Common.Hook != null)
+ {
+ Common.Hook.Stop();
+ Common.Hook = null;
+ }
+
+ if (byUser)
+ {
+ Common.InputCallbackForm.Close();
+ Common.InputCallbackForm = null;
+ Program.StartInputCallbackThread();
+ }
+ else
+ {
+ Common.InputCallbackForm.InstallKeyboardAndMouseHook();
+ }
+ });
+ }
+
+ internal static string GetMyStorageDir()
+ {
+ string st = string.Empty;
+
+ try
+ {
+ if (RunOnLogonDesktop || RunOnScrSaverDesktop)
+ {
+ st = Environment.GetFolderPath(Environment.SpecialFolder.ProgramFiles);
+ if (!Directory.Exists(st))
+ {
+ _ = Directory.CreateDirectory(st);
+ }
+
+ st += @"\" + Common.BinaryName;
+ if (!Directory.Exists(st))
+ {
+ _ = Directory.CreateDirectory(st);
+ }
+
+ st += @"\ScreenCaptures\";
+ if (!Directory.Exists(st))
+ {
+ _ = Directory.CreateDirectory(st);
+ }
+ }
+ else
+ {
+ _ = Launch.ImpersonateLoggedOnUserAndDoSomething(() =>
+ {
+ st = Environment.GetFolderPath(Environment.SpecialFolder.Desktop) + @"\" + Common.BinaryName;
+ if (!Directory.Exists(st))
+ {
+ _ = Directory.CreateDirectory(st);
+ }
+
+ st += @"\ScreenCaptures\";
+ if (!Directory.Exists(st))
+ {
+ _ = Directory.CreateDirectory(st);
+ }
+ });
+ }
+
+ Logger.LogDebug("GetMyStorageDir: " + st);
+
+ // Delete old files.
+ foreach (FileInfo fi in new DirectoryInfo(st).GetFiles())
+ {
+ if (fi.CreationTime.AddDays(1) < DateTime.Now)
+ {
+ fi.Delete();
+ }
+ }
+
+ return st;
+ }
+ catch (Exception e)
+ {
+ Logger.Log(e);
+
+ if (string.IsNullOrEmpty(st) || !st.Contains(Common.BinaryName))
+ {
+ st = Path.GetTempPath();
+ }
+
+ return st;
+ }
+ }
+
+ internal static void GetMachineName()
+ {
+ string machine_Name = string.Empty;
+
+ try
+ {
+ machine_Name = Dns.GetHostName();
+ Logger.LogDebug("GetHostName = " + machine_Name);
+ }
+ catch (Exception e)
+ {
+ Logger.Log(e);
+
+ if (string.IsNullOrEmpty(machine_Name))
+ {
+ machine_Name = "RANDOM" + Encryption.Ran.Next().ToString(CultureInfo.CurrentCulture);
+ }
+ }
+
+ if (machine_Name.Length > 32)
+ {
+ machine_Name = machine_Name[..32];
+ }
+
+ Common.MachineName = machine_Name.Trim();
+
+ Logger.LogDebug($"========== {nameof(GetMachineName)} ended!");
+ }
+
+ private static string GetNetworkName(NetworkInterface networkInterface)
+ {
+ return $"{networkInterface.Name} | {networkInterface.Description.Replace(":", "-")}";
+ }
+
+ internal static string GetRemoteStringIP(Socket s, bool throwException = false)
+ {
+ if (s == null)
+ {
+ return string.Empty;
+ }
+
+ string ip;
+
+ try
+ {
+ ip = (s?.RemoteEndPoint as IPEndPoint)?.Address?.ToString();
+
+ if (string.IsNullOrEmpty(ip))
+ {
+ return string.Empty;
+ }
+ }
+ catch (ObjectDisposedException e)
+ {
+ Logger.Log($"{nameof(GetRemoteStringIP)}: The socket could have been disposed by other threads, error: {e.Message}");
+
+ if (throwException)
+ {
+ throw;
+ }
+
+ return string.Empty;
+ }
+ catch (SocketException e)
+ {
+ Logger.Log($"{nameof(GetRemoteStringIP)}: {e.Message}");
+
+ if (throwException)
+ {
+ throw;
+ }
+
+ return string.Empty;
+ }
+
+ return ip;
+ }
+
+ internal static void CloseAllFormsAndHooks()
+ {
+ if (Hook != null)
+ {
+ Hook.Stop();
+ Hook = null;
+ if (InputCallbackForm != null)
+ {
+ DoSomethingInTheInputCallbackThread(() =>
+ {
+ InputCallbackForm.Close();
+ InputCallbackForm = null;
+ });
+ }
+ }
+
+ if (MainForm != null)
+ {
+ MainForm.Destroy();
+ MainForm = null;
+ }
+
+ if (MatrixForm != null)
+ {
+ MatrixForm.Close();
+ MatrixForm = null;
+ }
+
+ if (AboutForm != null)
+ {
+ AboutForm.Close();
+ AboutForm = null;
+ }
+ }
+
+ internal static void MoveMouseToCenter()
+ {
+ Logger.LogDebug("+++++ MoveMouseToCenter");
+ InputSimulation.MoveMouse(
+ MachineStuff.PrimaryScreenBounds.Left + ((MachineStuff.PrimaryScreenBounds.Right - MachineStuff.PrimaryScreenBounds.Left) / 2),
+ MachineStuff.PrimaryScreenBounds.Top + ((MachineStuff.PrimaryScreenBounds.Bottom - MachineStuff.PrimaryScreenBounds.Top) / 2));
+ }
+
+ internal static void HideMouseCursor(bool byHideMouseMessage)
+ {
+ Common.LastPos = new Point(
+ MachineStuff.PrimaryScreenBounds.Left + ((MachineStuff.PrimaryScreenBounds.Right - MachineStuff.PrimaryScreenBounds.Left) / 2),
+ Setting.Values.HideMouse ? 4 : MachineStuff.PrimaryScreenBounds.Top + ((MachineStuff.PrimaryScreenBounds.Bottom - MachineStuff.PrimaryScreenBounds.Top) / 2));
+
+ if ((MachineStuff.desMachineID != MachineID && MachineStuff.desMachineID != ID.ALL) || byHideMouseMessage)
+ {
+ _ = NativeMethods.SetCursorPos(Common.LastPos.X, Common.LastPos.Y);
+ _ = NativeMethods.GetCursorPos(ref Common.lastPos);
+ Logger.LogDebug($"+++++ HideMouseCursor, byHideMouseMessage = {byHideMouseMessage}");
+ }
+
+ CustomCursor.ShowFakeMouseCursor(int.MinValue, int.MinValue);
+ }
+
+ internal static string GetText(IntPtr hWnd)
+ {
+ int length = NativeMethods.GetWindowTextLength(hWnd);
+ StringBuilder sb = new(length + 1);
+ int rv = NativeMethods.GetWindowText(hWnd, sb, sb.Capacity);
+ Logger.LogDebug("GetWindowText returned " + rv.ToString(CultureInfo.CurrentCulture));
+ return sb.ToString();
+ }
+
+ private static string GetWindowClassName(IntPtr hWnd)
+ {
+ StringBuilder buffer = new(128);
+ _ = NativeMethods.GetClassName(hWnd, buffer, buffer.Capacity);
+ return buffer.ToString();
+ }
+
+ internal static void MMSleep(double secs)
+ {
+ for (int i = 0; i < secs * 10; i++)
+ {
+ Application.DoEvents();
+ Thread.Sleep(100);
+ }
+ }
+
+ internal static void UpdateMultipleModeIconAndMenu()
+ {
+ MainForm?.UpdateMultipleModeIconAndMenu();
+ }
+
+ internal static void SendOrReceiveARandomDataBlockPerInitialIV(Stream st, bool send = true)
+ {
+ byte[] ranData = new byte[Encryption.SymAlBlockSize];
+
+ try
+ {
+ if (send)
+ {
+ ranData = RandomNumberGenerator.GetBytes(Encryption.SymAlBlockSize);
+ st.Write(ranData, 0, ranData.Length);
+ }
+ else
+ {
+ int toRead = ranData.Length;
+ int read = st.ReadEx(ranData, 0, toRead);
+
+ if (read != toRead)
+ {
+ Logger.LogDebug("Stream has no more data after reading {0} bytes.", read);
+ }
+ }
+ }
+ catch (IOException e)
+ {
+ string log = $"{nameof(SendOrReceiveARandomDataBlockPerInitialIV)}: Exception {(send ? "writing" : "reading")} to the socket stream: {e.InnerException?.GetType()}/{e.Message}. (This is expected when the remote machine closes the connection during desktop switch or reconnection.)";
+ Logger.Log(log);
+
+ if (e.InnerException is not (SocketException or ObjectDisposedException))
+ {
+ throw;
+ }
+ }
+ }
+
+ private static bool DisableEasyMouseWhenForegroundWindowIsFullscreenSetting()
+ {
+ return Setting.Values.DisableEasyMouseWhenForegroundWindowIsFullscreen;
+ }
+
+ private static bool IsAppIgnoredByEasyMouseFullscreenCheck(IntPtr foregroundWindowHandle)
+ {
+ if (NativeMethods.GetWindowThreadProcessId(foregroundWindowHandle, out var processId) == 0)
+ {
+ Logger.LogDebug($"GetWindowThreadProcessId failed with error : {Marshal.GetLastWin32Error()}");
+ return false;
+ }
+
+ var processHandle = NativeMethods.OpenProcess(0x1000, false, processId);
+ if (processHandle == IntPtr.Zero)
+ {
+ return false;
+ }
+
+ uint maxPath = 260;
+ var nameBuffer = new char[maxPath];
+ if (!NativeMethods.QueryFullProcessImageName(
+ processHandle, NativeMethods.QUERY_FULL_PROCESS_NAME_FLAGS.DEFAULT, nameBuffer, ref maxPath))
+ {
+ Logger.LogDebug($"QueryFullProcessImageName failed with error : {Marshal.GetLastWin32Error()}");
+ NativeMethods.CloseHandle(processHandle);
+ return false;
+ }
+
+ NativeMethods.CloseHandle(processHandle);
+
+ var name = new string(nameBuffer, 0, (int)maxPath);
+
+ var excludedApps = Setting.Values.EasyMouseFullscreenSwitchBlockExcludedApps;
+
+ return excludedApps.Contains(Path.GetFileNameWithoutExtension(name), StringComparer.OrdinalIgnoreCase)
+ || excludedApps.Contains(Path.GetFileName(name), StringComparer.OrdinalIgnoreCase);
+ }
+
+ private static bool IsEasyMouseBlockedByFullscreenWindow()
+ {
+ var shellHandle = NativeMethods.GetShellWindow();
+ var desktopHandle = NativeMethods.GetDesktopWindow();
+ var foregroundHandle = NativeMethods.GetForegroundWindow();
+
+ // If the foreground window is either the desktop or the Windows shell, we are not in fullscreen mode.
+ if (foregroundHandle.Equals(shellHandle) || foregroundHandle.Equals(desktopHandle))
+ {
+ return false;
+ }
+
+ if (NativeMethods.SHQueryUserNotificationState(out var userNotificationState) != 0)
+ {
+ Logger.LogDebug($"SHQueryUserNotificationState failed with error : {Marshal.GetLastWin32Error()}");
+ return false;
+ }
+
+ switch (userNotificationState)
+ {
+ // An application running in full screen mode, check if the foreground window is
+ // listed as ignored in the settings.
+ case NativeMethods.USER_NOTIFICATION_STATE.BUSY:
+ case NativeMethods.USER_NOTIFICATION_STATE.RUNNING_D3D_FULL_SCREEN:
+ case NativeMethods.USER_NOTIFICATION_STATE.PRESENTATION_MODE:
+ return !IsAppIgnoredByEasyMouseFullscreenCheck(foregroundHandle);
+
+ // No full screen app running.
+ case NativeMethods.USER_NOTIFICATION_STATE.NOT_PRESENT:
+ case NativeMethods.USER_NOTIFICATION_STATE.ACCEPTS_NOTIFICATIONS:
+ case NativeMethods.USER_NOTIFICATION_STATE.QUIET_TIME:
+ // Cannot determine
+ case NativeMethods.USER_NOTIFICATION_STATE.APP:
+ default:
+ return false;
+ }
+ }
+
+ ///
+ /// Check if a machine switch triggered by EasyMouse would be allowed to proceed due to other settings.
+ ///
+ /// A boolean that tells us if the switch isn't blocked by any other settings
+ internal static bool IsEasyMouseSwitchAllowed()
+ {
+ // Never prevent a switch if we are not moving out of the host machine.
+ if (!DisableEasyMouseWhenForegroundWindowIsFullscreenSetting() || DesMachineID != MachineID)
+ {
+ return true;
+ }
+
+ // Check if the switch is blocked by a full-screen window running in the foreground
+ return !IsEasyMouseBlockedByFullscreenWindow();
+ }
+}
diff --git a/src/modules/MouseWithoutBorders/App/Core/Helper.cs b/src/modules/MouseWithoutBorders/App/Core/Helper.cs
index 8c291fb417..2d97d91123 100644
--- a/src/modules/MouseWithoutBorders/App/Core/Helper.cs
+++ b/src/modules/MouseWithoutBorders/App/Core/Helper.cs
@@ -295,9 +295,9 @@ internal static class Helper
return;
}
- if (!Common.IpcChannelCreated)
+ if (!IpcChannelHelper.IpcChannelCreated)
{
- Logger.TelemetryLogTrace($"{nameof(Common.IpcChannelCreated)} = {Common.IpcChannelCreated}. {Logger.GetStackTrace(new StackTrace())}", SeverityLevel.Warning);
+ Logger.TelemetryLogTrace($"{nameof(IpcChannelHelper.IpcChannelCreated)} = {IpcChannelHelper.IpcChannelCreated}. {Logger.GetStackTrace(new StackTrace())}", SeverityLevel.Warning);
return;
}
diff --git a/src/modules/MouseWithoutBorders/App/Core/IpcChannelHelper.cs b/src/modules/MouseWithoutBorders/App/Core/IpcChannelHelper.cs
new file mode 100644
index 0000000000..7e6bfd0217
--- /dev/null
+++ b/src/modules/MouseWithoutBorders/App/Core/IpcChannelHelper.cs
@@ -0,0 +1,53 @@
+// 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.Threading;
+using System.Windows.Forms;
+
+#if !MM_HELPER
+using Thread = MouseWithoutBorders.Core.Thread;
+#endif
+
+namespace MouseWithoutBorders.Core;
+
+internal static class IpcChannelHelper
+{
+ internal static bool IpcChannelCreated { get; set; }
+
+ internal static T Retry(string name, Func func, Action log, Action preRetry = null)
+ {
+ int count = 0;
+
+ do
+ {
+ try
+ {
+ T rv = func();
+
+ if (count > 0)
+ {
+ log($"Trace: {name} has been successful after {count} retry.");
+ }
+
+ return rv;
+ }
+ catch (Exception)
+ {
+ count++;
+
+ preRetry?.Invoke();
+
+ if (count > 10)
+ {
+ throw;
+ }
+
+ Application.DoEvents();
+ Thread.Sleep(200);
+ }
+ }
+ while (true);
+ }
+}
diff --git a/src/modules/MouseWithoutBorders/App/Core/Logger.cs b/src/modules/MouseWithoutBorders/App/Core/Logger.cs
index 4d39983c35..334d269400 100644
--- a/src/modules/MouseWithoutBorders/App/Core/Logger.cs
+++ b/src/modules/MouseWithoutBorders/App/Core/Logger.cs
@@ -198,7 +198,6 @@ internal static class Logger
}
Logger.DumpProgramLogs(sb, level);
- Logger.DumpOtherLogs(sb, level);
Logger.DumpStaticTypes(sb, level);
log = string.Format(
@@ -240,19 +239,16 @@ internal static class Logger
_ = Logger.PrivateDump(sb, AllLogs, "[Program logs]\r\n===============\r\n", 0, level, false);
}
- internal static void DumpOtherLogs(StringBuilder sb, int level)
- {
- _ = Logger.PrivateDump(sb, new Common(), "[Other Logs]\r\n===============\r\n", 0, level, false);
- }
-
internal static void DumpStaticTypes(StringBuilder sb, int level)
{
var staticTypes = new List
{
typeof(Clipboard),
+ typeof(Common),
typeof(DragDrop),
typeof(Encryption),
typeof(Event),
+ typeof(IpcChannelHelper),
typeof(InitAndCleanup),
typeof(Helper),
typeof(Launch),
diff --git a/src/modules/MouseWithoutBorders/App/Form/Settings/SetupPage2b.cs b/src/modules/MouseWithoutBorders/App/Form/Settings/SetupPage2b.cs
index 5649ae8d7f..bb802aa159 100644
--- a/src/modules/MouseWithoutBorders/App/Form/Settings/SetupPage2b.cs
+++ b/src/modules/MouseWithoutBorders/App/Form/Settings/SetupPage2b.cs
@@ -2,6 +2,8 @@
// The Microsoft Corporation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
+using MouseWithoutBorders.Core;
+
namespace MouseWithoutBorders
{
public partial class SetupPage2b : SettingsFormPage
diff --git a/src/modules/MouseWithoutBorders/App/Form/frmAbout.cs b/src/modules/MouseWithoutBorders/App/Form/frmAbout.cs
index 3a7ae9901c..069c428589 100644
--- a/src/modules/MouseWithoutBorders/App/Form/frmAbout.cs
+++ b/src/modules/MouseWithoutBorders/App/Form/frmAbout.cs
@@ -15,6 +15,8 @@ using System.Globalization;
using System.Reflection;
using System.Windows.Forms;
+using MouseWithoutBorders.Core;
+
namespace MouseWithoutBorders
{
internal partial class FrmAbout : System.Windows.Forms.Form, IDisposable
diff --git a/src/modules/MouseWithoutBorders/App/Form/frmMessage.cs b/src/modules/MouseWithoutBorders/App/Form/frmMessage.cs
index 2abb18f932..e258df9709 100644
--- a/src/modules/MouseWithoutBorders/App/Form/frmMessage.cs
+++ b/src/modules/MouseWithoutBorders/App/Form/frmMessage.cs
@@ -6,6 +6,8 @@ using System;
using System.Globalization;
using System.Windows.Forms;
+using MouseWithoutBorders.Core;
+
namespace MouseWithoutBorders
{
public partial class FrmMessage : System.Windows.Forms.Form
diff --git a/src/modules/MouseWithoutBorders/App/Form/frmMouseCursor.cs b/src/modules/MouseWithoutBorders/App/Form/frmMouseCursor.cs
index b4c2f13efd..e5b8095d18 100644
--- a/src/modules/MouseWithoutBorders/App/Form/frmMouseCursor.cs
+++ b/src/modules/MouseWithoutBorders/App/Form/frmMouseCursor.cs
@@ -6,6 +6,7 @@ using System;
using System.Windows.Forms;
using MouseWithoutBorders.Class;
+using MouseWithoutBorders.Core;
namespace MouseWithoutBorders
{
diff --git a/src/modules/MouseWithoutBorders/App/Helper/MouseWithoutBordersHelper.csproj b/src/modules/MouseWithoutBorders/App/Helper/MouseWithoutBordersHelper.csproj
index f97c0c44f3..981266c0cb 100644
--- a/src/modules/MouseWithoutBorders/App/Helper/MouseWithoutBordersHelper.csproj
+++ b/src/modules/MouseWithoutBorders/App/Helper/MouseWithoutBordersHelper.csproj
@@ -49,6 +49,9 @@
IClipboardHelper.cs
+
+ IpcChannelHelper.cs
+
diff --git a/src/modules/MouseWithoutBorders/MouseWithoutBorders.UnitTests/Core/Logger.PrivateDump.expected.txt b/src/modules/MouseWithoutBorders/MouseWithoutBorders.UnitTests/Core/Logger.PrivateDump.expected.txt
index 34a83830cd..3a36e9b3d2 100644
--- a/src/modules/MouseWithoutBorders/MouseWithoutBorders.UnitTests/Core/Logger.PrivateDump.expected.txt
+++ b/src/modules/MouseWithoutBorders/MouseWithoutBorders.UnitTests/Core/Logger.PrivateDump.expected.txt
@@ -1,9 +1,38 @@
[Program logs]
===============
= System.String[]
-[Other Logs]
+[Clipboard]
+===============
+Comma = System.Char[]
+--System.Char[] = System.Char[]: N/A
+Star = System.Char[]
+--System.Char[] = System.Char[]: N/A
+NullSeparator = System.Char[]
+--System.Char[] = System.Char[]: N/A
+lastClipboardEventTime = 0
+clipboardCopiedTime = 0
+k__BackingField = NONE
+k__BackingField = 0
+k__BackingField = False
+lastClipboardObject =
+k__BackingField = False
+ClipboardThreadOldLock = Lock
+--_owningThreadId = 0
+--_state = 0
+--_recursionCount = 0
+--_spinCount = 22
+--_waiterStartTimeMs = 0
+--s_contentionCount = 0
+--s_maxSpinCount = 22
+--s_minSpinCountForAdaptiveSpin = -100
+BIG_CLIPBOARD_DATA_TIMEOUT = 30000
+MAX_CLIPBOARD_DATA_SIZE_CAN_BE_SENT_INSTANTLY_TCP = 1048576
+MAX_CLIPBOARD_FILE_SIZE_CAN_BE_SENT = 104857600
+TEXT_HEADER_SIZE = 12
+DATA_SIZE = 48
+TEXT_TYPE_SEP = {4CFF57F7-BEDD-43d5-AE8F-27A61E886F2F}
+[Common]
===============
- = MouseWithoutBorders.Common
screenWidth = 0
screenHeight = 0
lastX = 0
@@ -46,7 +75,6 @@ avgSendTime = 0
maxSendTime = 0
totalSendCount = 0
totalSendTime = 0
-k__BackingField = False
TOGGLE_ICONS_SIZE = 4
ICON_ONE = 0
ICON_ALL = 1
@@ -55,36 +83,6 @@ ICON_BIG_CLIPBOARD = 3
ICON_ERROR = 4
JUST_GOT_BACK_FROM_SCREEN_SAVER = 9999
NETWORK_STREAM_BUF_SIZE = 1048576
-[Clipboard]
-===============
-Comma = System.Char[]
---System.Char[] = System.Char[]: N/A
-Star = System.Char[]
---System.Char[] = System.Char[]: N/A
-NullSeparator = System.Char[]
---System.Char[] = System.Char[]: N/A
-lastClipboardEventTime = 0
-clipboardCopiedTime = 0
-k__BackingField = NONE
-k__BackingField = 0
-k__BackingField = False
-lastClipboardObject =
-k__BackingField = False
-ClipboardThreadOldLock = Lock
---_owningThreadId = 0
---_state = 0
---_recursionCount = 0
---_spinCount = 22
---_waiterStartTimeMs = 0
---s_contentionCount = 0
---s_maxSpinCount = 22
---s_minSpinCountForAdaptiveSpin = -100
-BIG_CLIPBOARD_DATA_TIMEOUT = 30000
-MAX_CLIPBOARD_DATA_SIZE_CAN_BE_SENT_INSTANTLY_TCP = 1048576
-MAX_CLIPBOARD_FILE_SIZE_CAN_BE_SENT = 104857600
-TEXT_HEADER_SIZE = 12
-DATA_SIZE = 48
-TEXT_TYPE_SEP = {4CFF57F7-BEDD-43d5-AE8F-27A61E886F2F}
[DragDrop]
===============
isDragging = False
@@ -174,6 +172,9 @@ actualLastPos = {X=0,Y=0}
--Empty = {X=0,Y=0}
myLastX = 0
myLastY = 0
+[IpcChannelHelper]
+===============
+k__BackingField = False
[InitAndCleanup]
===============
initDone = False
@@ -440,6 +441,7 @@ WM_LBUTTONDBLCLK = 515
WM_RBUTTONDBLCLK = 518
WM_MBUTTONDBLCLK = 521
WM_MOUSEWHEEL = 522
+WM_MOUSEHWHEEL = 526
WM_KEYDOWN = 256
WM_KEYUP = 257
WM_SYSKEYDOWN = 260
diff --git a/src/modules/MouseWithoutBorders/MouseWithoutBorders.UnitTests/Core/LoggerTests.cs b/src/modules/MouseWithoutBorders/MouseWithoutBorders.UnitTests/Core/LoggerTests.cs
index 6ca983724c..f7cd3b461a 100644
--- a/src/modules/MouseWithoutBorders/MouseWithoutBorders.UnitTests/Core/LoggerTests.cs
+++ b/src/modules/MouseWithoutBorders/MouseWithoutBorders.UnitTests/Core/LoggerTests.cs
@@ -117,7 +117,6 @@ public static class LoggerTests
// copied from DumpObjects in Logger.cs
var sb = new StringBuilder(1000000);
Logger.DumpProgramLogs(sb, settingsDumpObjectsLevel);
- Logger.DumpOtherLogs(sb, settingsDumpObjectsLevel);
Logger.DumpStaticTypes(sb, settingsDumpObjectsLevel);
var actual = sb.ToString();
diff --git a/src/modules/cmdpal/Microsoft.CmdPal.UI/Settings/GeneralPage.xaml b/src/modules/cmdpal/Microsoft.CmdPal.UI/Settings/GeneralPage.xaml
index 65fa536c5b..53994b345f 100644
--- a/src/modules/cmdpal/Microsoft.CmdPal.UI/Settings/GeneralPage.xaml
+++ b/src/modules/cmdpal/Microsoft.CmdPal.UI/Settings/GeneralPage.xaml
@@ -106,7 +106,7 @@
-
+
diff --git a/src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.Indexer/Assets/Peek.png b/src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.Indexer/Assets/Peek.png
new file mode 100644
index 0000000000..3beeccaf17
Binary files /dev/null and b/src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.Indexer/Assets/Peek.png differ
diff --git a/src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.Indexer/Commands/PeekFileCommand.cs b/src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.Indexer/Commands/PeekFileCommand.cs
new file mode 100644
index 0000000000..808daa567f
--- /dev/null
+++ b/src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.Indexer/Commands/PeekFileCommand.cs
@@ -0,0 +1,73 @@
+// 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 ManagedCommon;
+using Microsoft.CmdPal.Ext.Indexer.Properties;
+using Microsoft.CommandPalette.Extensions.Toolkit;
+
+namespace Microsoft.CmdPal.Ext.Indexer.Commands;
+
+///
+/// Command to preview a file using PowerToys Peek.
+///
+public sealed partial class PeekFileCommand : InvokableCommand
+{
+ private const string PeekExecutable = @"WinUI3Apps\PowerToys.Peek.UI.exe";
+
+ private static readonly Lazy _peekPath = new(GetPeekExecutablePath);
+
+ private readonly string _fullPath;
+
+ public PeekFileCommand(string fullPath)
+ {
+ _fullPath = fullPath;
+ Name = Resources.Indexer_Command_Peek;
+ Icon = Icons.PeekIcon;
+ }
+
+ ///
+ /// Gets a value indicating whether Peek is available on this system.
+ ///
+ public static bool IsPeekAvailable => !string.IsNullOrEmpty(_peekPath.Value);
+
+ public override CommandResult Invoke()
+ {
+ var peekExe = _peekPath.Value;
+ if (string.IsNullOrEmpty(peekExe))
+ {
+ return CommandResult.ShowToast(Resources.Indexer_Command_Peek_NotAvailable);
+ }
+
+ try
+ {
+ using var process = new Process();
+ process.StartInfo.FileName = peekExe;
+ process.StartInfo.Arguments = $"\"{_fullPath}\"";
+ process.StartInfo.UseShellExecute = false;
+ process.Start();
+ }
+ catch (Exception ex)
+ {
+ ExtensionHost.LogMessage($"Unable to launch Peek for {_fullPath}\n{ex}");
+ return CommandResult.ShowToast(Resources.Indexer_Command_Peek_Failed);
+ }
+
+ return CommandResult.Dismiss();
+ }
+
+ private static string GetPeekExecutablePath()
+ {
+ var installPath = PowerToysPathResolver.GetPowerToysInstallPath();
+ if (string.IsNullOrEmpty(installPath))
+ {
+ return string.Empty;
+ }
+
+ var peekPath = Path.Combine(installPath, PeekExecutable);
+ return File.Exists(peekPath) ? peekPath : string.Empty;
+ }
+}
diff --git a/src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.Indexer/Data/IndexerListItem.cs b/src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.Indexer/Data/IndexerListItem.cs
index 51a30ddc86..54d2744abd 100644
--- a/src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.Indexer/Data/IndexerListItem.cs
+++ b/src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.Indexer/Data/IndexerListItem.cs
@@ -7,6 +7,7 @@ using System.Collections.Generic;
using System.IO;
using System.Linq;
using Microsoft.CmdPal.Core.Common.Commands;
+using Microsoft.CmdPal.Ext.Indexer.Commands;
using Microsoft.CmdPal.Ext.Indexer.Helpers;
using Microsoft.CmdPal.Ext.Indexer.Pages;
using Microsoft.CmdPal.Ext.Indexer.Properties;
@@ -96,6 +97,13 @@ internal sealed partial class IndexerListItem : ListItem
}
commands.Add(new CommandContextItem(new OpenWithCommand(fullPath)));
+
+ // Add Peek command if available (only for files, not directories)
+ if (!isDir && PeekFileCommand.IsPeekAvailable)
+ {
+ commands.Add(new CommandContextItem(new PeekFileCommand(fullPath)) { RequestedShortcut = KeyChords.Peek });
+ }
+
commands.Add(new CommandContextItem(new ShowFileInFolderCommand(fullPath) { Name = Resources.Indexer_Command_ShowInFolder }) { RequestedShortcut = KeyChords.OpenFileLocation });
commands.Add(new CommandContextItem(new CopyPathCommand(fullPath) { Name = Resources.Indexer_Command_CopyPath }) { RequestedShortcut = KeyChords.CopyFilePath });
commands.Add(new CommandContextItem(new OpenInConsoleCommand(fullPath)) { RequestedShortcut = KeyChords.OpenInConsole });
diff --git a/src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.Indexer/Icons.cs b/src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.Indexer/Icons.cs
index bd9759514c..1e92d9f323 100644
--- a/src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.Indexer/Icons.cs
+++ b/src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.Indexer/Icons.cs
@@ -23,4 +23,6 @@ internal static class Icons
internal static IconInfo FilesIcon { get; } = new("\uF571"); // PrintAllPages
internal static IconInfo FilterIcon { get; } = new("\uE71C"); // Filter
+
+ internal static IconInfo PeekIcon { get; } = IconHelpers.FromRelativePath("Assets\\Peek.png");
}
diff --git a/src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.Indexer/KeyChords.cs b/src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.Indexer/KeyChords.cs
index c0fd69d0f2..aa44696a7b 100644
--- a/src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.Indexer/KeyChords.cs
+++ b/src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.Indexer/KeyChords.cs
@@ -4,6 +4,8 @@
using Microsoft.CmdPal.Core.Common.Helpers;
using Microsoft.CommandPalette.Extensions;
+using Microsoft.CommandPalette.Extensions.Toolkit;
+using Windows.System;
namespace Microsoft.CmdPal.Ext.Indexer;
@@ -14,4 +16,6 @@ internal static class KeyChords
internal static KeyChord CopyFilePath { get; } = WellKnownKeyChords.CopyFilePath;
internal static KeyChord OpenInConsole { get; } = WellKnownKeyChords.OpenInConsole;
+
+ internal static KeyChord Peek { get; } = KeyChordHelpers.FromModifiers(ctrl: true, vkey: (int)VirtualKey.Space);
}
diff --git a/src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.Indexer/Microsoft.CmdPal.Ext.Indexer.csproj b/src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.Indexer/Microsoft.CmdPal.Ext.Indexer.csproj
index 6b3b304825..2c40afd19c 100644
--- a/src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.Indexer/Microsoft.CmdPal.Ext.Indexer.csproj
+++ b/src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.Indexer/Microsoft.CmdPal.Ext.Indexer.csproj
@@ -42,6 +42,9 @@
PreserveNewest
+
+ PreserveNewest
+
diff --git a/src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.Indexer/Properties/Resources.Designer.cs b/src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.Indexer/Properties/Resources.Designer.cs
index 4394cd6697..469c5faf61 100644
--- a/src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.Indexer/Properties/Resources.Designer.cs
+++ b/src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.Indexer/Properties/Resources.Designer.cs
@@ -132,6 +132,33 @@ namespace Microsoft.CmdPal.Ext.Indexer.Properties {
}
}
+ ///
+ /// Looks up a localized string similar to Peek preview.
+ ///
+ internal static string Indexer_Command_Peek {
+ get {
+ return ResourceManager.GetString("Indexer_Command_Peek", resourceCulture);
+ }
+ }
+
+ ///
+ /// Looks up a localized string similar to Failed to launch Peek.
+ ///
+ internal static string Indexer_Command_Peek_Failed {
+ get {
+ return ResourceManager.GetString("Indexer_Command_Peek_Failed", resourceCulture);
+ }
+ }
+
+ ///
+ /// Looks up a localized string similar to PowerToys Peek is not available.
+ ///
+ internal static string Indexer_Command_Peek_NotAvailable {
+ get {
+ return ResourceManager.GetString("Indexer_Command_Peek_NotAvailable", resourceCulture);
+ }
+ }
+
///
/// Looks up a localized string similar to Search all files.
///
diff --git a/src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.Indexer/Properties/Resources.resx b/src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.Indexer/Properties/Resources.resx
index 9ea1c4563e..8f5f760137 100644
--- a/src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.Indexer/Properties/Resources.resx
+++ b/src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.Indexer/Properties/Resources.resx
@@ -202,4 +202,13 @@ You can try searching all files on this PC or adjust your indexing settings.
Files
+
+ Peek preview
+
+
+ PowerToys Peek is not available
+
+
+ Failed to launch Peek
+
\ No newline at end of file
diff --git a/src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.PowerToys/Commands/CropAndLock/CropAndLockReparentCommand.cs b/src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.PowerToys/Commands/CropAndLock/CropAndLockReparentCommand.cs
index 417ab34a5d..2c7bfe6868 100644
--- a/src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.PowerToys/Commands/CropAndLock/CropAndLockReparentCommand.cs
+++ b/src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.PowerToys/Commands/CropAndLock/CropAndLockReparentCommand.cs
@@ -4,6 +4,7 @@
using System;
using System.Threading;
+using System.Threading.Tasks;
using Microsoft.CommandPalette.Extensions.Toolkit;
using PowerToys.Interop;
@@ -21,15 +22,20 @@ internal sealed partial class CropAndLockReparentCommand : InvokableCommand
public override CommandResult Invoke()
{
- try
+ Task.Run(async () =>
{
- using var evt = new EventWaitHandle(false, EventResetMode.AutoReset, Constants.CropAndLockReparentEvent());
- evt.Set();
- return CommandResult.Dismiss();
- }
- catch (Exception ex)
- {
- return CommandResult.ShowToast($"Failed to start Crop and Lock (Reparent): {ex.Message}");
- }
+ await Task.Delay(500);
+ try
+ {
+ using var evt = new EventWaitHandle(false, EventResetMode.AutoReset, Constants.CropAndLockReparentEvent());
+ evt.Set();
+ }
+ catch
+ {
+ // Ignore errors after dismissing
+ }
+ });
+
+ return CommandResult.Dismiss();
}
}
diff --git a/src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.PowerToys/Commands/CropAndLock/CropAndLockScreenshotCommand.cs b/src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.PowerToys/Commands/CropAndLock/CropAndLockScreenshotCommand.cs
new file mode 100644
index 0000000000..1b6e295144
--- /dev/null
+++ b/src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.PowerToys/Commands/CropAndLock/CropAndLockScreenshotCommand.cs
@@ -0,0 +1,41 @@
+// 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.Threading;
+using System.Threading.Tasks;
+using Microsoft.CommandPalette.Extensions.Toolkit;
+using PowerToys.Interop;
+
+namespace PowerToysExtension.Commands;
+
+///
+/// Triggers Crop and Lock screenshot mode via the shared event.
+///
+internal sealed partial class CropAndLockScreenshotCommand : InvokableCommand
+{
+ public CropAndLockScreenshotCommand()
+ {
+ Name = "Crop and Lock (Screenshot)";
+ }
+
+ public override CommandResult Invoke()
+ {
+ Task.Run(async () =>
+ {
+ await Task.Delay(500);
+ try
+ {
+ using var evt = new EventWaitHandle(false, EventResetMode.AutoReset, Constants.CropAndLockScreenshotEvent());
+ evt.Set();
+ }
+ catch
+ {
+ // Ignore errors after dismissing
+ }
+ });
+
+ return CommandResult.Dismiss();
+ }
+}
diff --git a/src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.PowerToys/Commands/CropAndLock/CropAndLockThumbnailCommand.cs b/src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.PowerToys/Commands/CropAndLock/CropAndLockThumbnailCommand.cs
index b9996f7835..7b1ce62e56 100644
--- a/src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.PowerToys/Commands/CropAndLock/CropAndLockThumbnailCommand.cs
+++ b/src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.PowerToys/Commands/CropAndLock/CropAndLockThumbnailCommand.cs
@@ -4,6 +4,7 @@
using System;
using System.Threading;
+using System.Threading.Tasks;
using Microsoft.CommandPalette.Extensions.Toolkit;
using PowerToys.Interop;
@@ -21,15 +22,20 @@ internal sealed partial class CropAndLockThumbnailCommand : InvokableCommand
public override CommandResult Invoke()
{
- try
+ Task.Run(async () =>
{
- using var evt = new EventWaitHandle(false, EventResetMode.AutoReset, Constants.CropAndLockThumbnailEvent());
- evt.Set();
- return CommandResult.Dismiss();
- }
- catch (Exception ex)
- {
- return CommandResult.ShowToast($"Failed to start Crop and Lock (Thumbnail): {ex.Message}");
- }
+ await Task.Delay(500);
+ try
+ {
+ using var evt = new EventWaitHandle(false, EventResetMode.AutoReset, Constants.CropAndLockThumbnailEvent());
+ evt.Set();
+ }
+ catch
+ {
+ // Ignore errors after dismissing
+ }
+ });
+
+ return CommandResult.Dismiss();
}
}
diff --git a/src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.PowerToys/Commands/FancyZones/FancyZonesMonitorListItem.cs b/src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.PowerToys/Commands/FancyZones/FancyZonesMonitorListItem.cs
index c65db779df..3675c45311 100644
--- a/src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.PowerToys/Commands/FancyZones/FancyZonesMonitorListItem.cs
+++ b/src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.PowerToys/Commands/FancyZones/FancyZonesMonitorListItem.cs
@@ -8,6 +8,7 @@ using Microsoft.CommandPalette.Extensions;
using Microsoft.CommandPalette.Extensions.Toolkit;
using PowerToysExtension.Commands;
using PowerToysExtension.Helpers;
+using PowerToysExtension.Properties;
namespace PowerToysExtension.Pages;
@@ -24,15 +25,15 @@ internal sealed partial class FancyZonesMonitorListItem : ListItem
var pickerPage = new FancyZonesMonitorLayoutPickerPage(monitor)
{
- Name = "Set active layout",
+ Name = Resources.FancyZones_SetActiveLayout,
};
MoreCommands =
[
new CommandContextItem(pickerPage)
{
- Title = "Set active layout",
- Subtitle = "Pick a layout for this monitor",
+ Title = Resources.FancyZones_SetActiveLayout,
+ Subtitle = Resources.FancyZones_PickLayoutForMonitor,
},
];
}
@@ -42,14 +43,14 @@ internal sealed partial class FancyZonesMonitorListItem : ListItem
var currentVirtualDesktop = FancyZonesVirtualDesktop.GetCurrentVirtualDesktopIdString();
var tags = new List
{
- DetailTag("Monitor", monitor.Data.Monitor),
- DetailTag("Instance", monitor.Data.MonitorInstanceId),
- DetailTag("Serial", monitor.Data.MonitorSerialNumber),
- DetailTag("Number", monitor.Data.MonitorNumber.ToString(CultureInfo.InvariantCulture)),
- DetailTag("Virtual desktop", currentVirtualDesktop),
- DetailTag("Work area", $"{monitor.Data.LeftCoordinate},{monitor.Data.TopCoordinate} {monitor.Data.WorkAreaWidth}\u00D7{monitor.Data.WorkAreaHeight}"),
- DetailTag("Resolution", $"{monitor.Data.MonitorWidth}\u00D7{monitor.Data.MonitorHeight}"),
- DetailTag("DPI", monitor.Data.Dpi.ToString(CultureInfo.InvariantCulture)),
+ DetailTag(Resources.FancyZones_Monitor, monitor.Data.Monitor),
+ DetailTag(Resources.FancyZones_Instance, monitor.Data.MonitorInstanceId),
+ DetailTag(Resources.FancyZones_Serial, monitor.Data.MonitorSerialNumber),
+ DetailTag(Resources.FancyZones_Number, monitor.Data.MonitorNumber.ToString(CultureInfo.InvariantCulture)),
+ DetailTag(Resources.FancyZones_VirtualDesktop, currentVirtualDesktop),
+ DetailTag(Resources.FancyZones_WorkArea, $"{monitor.Data.LeftCoordinate},{monitor.Data.TopCoordinate} {monitor.Data.WorkAreaWidth}\u00D7{monitor.Data.WorkAreaHeight}"),
+ DetailTag(Resources.FancyZones_Resolution, $"{monitor.Data.MonitorWidth}\u00D7{monitor.Data.MonitorHeight}"),
+ DetailTag(Resources.FancyZones_DPI, monitor.Data.Dpi.ToString(CultureInfo.InvariantCulture)),
};
return new Details
@@ -68,7 +69,7 @@ internal sealed partial class FancyZonesMonitorListItem : ListItem
Key = key,
Data = new DetailsTags
{
- Tags = [new Tag(string.IsNullOrWhiteSpace(value) ? "n/a" : value)],
+ Tags = [new Tag(string.IsNullOrWhiteSpace(value) ? Resources.Common_NotAvailable : value)],
},
};
}
diff --git a/src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.PowerToys/Commands/Workspaces/WorkspaceListItem.cs b/src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.PowerToys/Commands/Workspaces/WorkspaceListItem.cs
index 1b2024e759..de1409e823 100644
--- a/src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.PowerToys/Commands/Workspaces/WorkspaceListItem.cs
+++ b/src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.PowerToys/Commands/Workspaces/WorkspaceListItem.cs
@@ -5,15 +5,24 @@
using System;
using System.Collections.Generic;
using System.Globalization;
+using System.Text;
using Microsoft.CommandPalette.Extensions;
using Microsoft.CommandPalette.Extensions.Toolkit;
using PowerToysExtension.Helpers;
+using PowerToysExtension.Properties;
using WorkspacesCsharpLibrary.Data;
namespace PowerToysExtension.Commands;
internal sealed partial class WorkspaceListItem : ListItem
{
+ private static readonly CompositeFormat ApplicationsFormat = CompositeFormat.Parse(Resources.Workspaces_Applications_Format);
+ private static readonly CompositeFormat LastLaunchedFormat = CompositeFormat.Parse(Resources.Workspaces_LastLaunched_Format);
+ private static readonly CompositeFormat ApplicationsCountFormat = CompositeFormat.Parse(Resources.Workspaces_ApplicationsCount_Format);
+ private static readonly CompositeFormat MinAgoFormat = CompositeFormat.Parse(Resources.Workspaces_MinAgo_Format);
+ private static readonly CompositeFormat HrAgoFormat = CompositeFormat.Parse(Resources.Workspaces_HrAgo_Format);
+ private static readonly CompositeFormat DaysAgoFormat = CompositeFormat.Parse(Resources.Workspaces_DaysAgo_Format);
+
public WorkspaceListItem(ProjectWrapper workspace, IconInfo icon)
: base(new LaunchWorkspaceCommand(workspace.Id))
{
@@ -28,13 +37,13 @@ internal sealed partial class WorkspaceListItem : ListItem
var appCount = workspace.Applications?.Count ?? 0;
var appsText = appCount switch
{
- 0 => "No applications",
- _ => string.Format(CultureInfo.CurrentCulture, "{0} applications", appCount),
+ 0 => Resources.Workspaces_NoApplications,
+ _ => string.Format(CultureInfo.CurrentCulture, ApplicationsFormat, appCount),
};
var lastLaunched = workspace.LastLaunchedTime > 0
- ? $"Last launched {FormatRelativeTime(workspace.LastLaunchedTime)}"
- : "Never launched";
+ ? string.Format(CultureInfo.CurrentCulture, LastLaunchedFormat, FormatRelativeTime(workspace.LastLaunchedTime))
+ : Resources.Workspaces_NeverLaunched;
return $"{appsText} \u2022 {lastLaunched}";
}
@@ -44,15 +53,15 @@ internal sealed partial class WorkspaceListItem : ListItem
var appCount = workspace.Applications?.Count ?? 0;
var body = appCount switch
{
- 0 => "No applications in this workspace",
- 1 => "1 application",
- _ => $"{appCount} applications",
+ 0 => Resources.Workspaces_NoApplicationsInWorkspace,
+ 1 => Resources.Workspaces_OneApplication,
+ _ => string.Format(CultureInfo.CurrentCulture, ApplicationsCountFormat, appCount),
};
return new Details
{
HeroImage = icon,
- Title = workspace.Name ?? "Workspace",
+ Title = workspace.Name ?? Resources.Workspaces_Workspace,
Body = body,
Metadata = BuildAppMetadata(workspace),
};
@@ -68,7 +77,7 @@ internal sealed partial class WorkspaceListItem : ListItem
var elements = new List();
foreach (var app in workspace.Applications)
{
- var appName = string.IsNullOrWhiteSpace(app.Application) ? "App" : app.Application;
+ var appName = string.IsNullOrWhiteSpace(app.Application) ? Resources.Workspaces_App : app.Application;
var title = string.IsNullOrWhiteSpace(app.Title) ? appName : app.Title;
var tags = new List();
@@ -99,19 +108,19 @@ internal sealed partial class WorkspaceListItem : ListItem
if (delta.TotalMinutes < 1)
{
- return "just now";
+ return Resources.Workspaces_JustNow;
}
if (delta.TotalMinutes < 60)
{
- return string.Format(CultureInfo.CurrentCulture, "{0} min ago", (int)delta.TotalMinutes);
+ return string.Format(CultureInfo.CurrentCulture, MinAgoFormat, (int)delta.TotalMinutes);
}
if (delta.TotalHours < 24)
{
- return string.Format(CultureInfo.CurrentCulture, "{0} hr ago", (int)delta.TotalHours);
+ return string.Format(CultureInfo.CurrentCulture, HrAgoFormat, (int)delta.TotalHours);
}
- return string.Format(CultureInfo.CurrentCulture, "{0} days ago", (int)delta.TotalDays);
+ return string.Format(CultureInfo.CurrentCulture, DaysAgoFormat, (int)delta.TotalDays);
}
}
diff --git a/src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.PowerToys/Helpers/FancyZonesDataService.cs b/src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.PowerToys/Helpers/FancyZonesDataService.cs
index 0a55859409..639128d7f7 100644
--- a/src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.PowerToys/Helpers/FancyZonesDataService.cs
+++ b/src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.PowerToys/Helpers/FancyZonesDataService.cs
@@ -4,13 +4,16 @@
using System;
using System.Collections.Generic;
+using System.Globalization;
using System.IO;
using System.Linq;
+using System.Text;
using System.Text.Json;
using FancyZonesEditorCommon.Data;
using FancyZonesEditorCommon.Utils;
using ManagedCommon;
+using PowerToysExtension.Properties;
using FZPaths = FancyZonesEditorCommon.Data.FancyZonesPaths;
@@ -20,6 +23,15 @@ internal static class FancyZonesDataService
{
private const string ZeroUuid = "{00000000-0000-0000-0000-000000000000}";
+ private static readonly CompositeFormat ReadMonitorDataFailedFormat = CompositeFormat.Parse(Resources.FancyZones_ReadMonitorDataFailed_Format);
+ private static readonly CompositeFormat WriteAppliedLayoutsFailedFormat = CompositeFormat.Parse(Resources.FancyZones_WriteAppliedLayoutsFailed_Format);
+ private static readonly CompositeFormat LayoutAppliedNotifyFailedFormat = CompositeFormat.Parse(Resources.FancyZones_LayoutAppliedNotifyFailed_Format);
+ private static readonly CompositeFormat TemplateFormat = CompositeFormat.Parse(Resources.FancyZones_Template_Format);
+ private static readonly CompositeFormat ZonesFormat = CompositeFormat.Parse(Resources.FancyZones_Zones_Format);
+ private static readonly CompositeFormat CustomGridZonesFormat = CompositeFormat.Parse(Resources.FancyZones_CustomGrid_Zones_Format);
+ private static readonly CompositeFormat CustomCanvasZonesFormat = CompositeFormat.Parse(Resources.FancyZones_CustomCanvas_Zones_Format);
+ private static readonly CompositeFormat CustomZonesFormat = CompositeFormat.Parse(Resources.FancyZones_Custom_Zones_Format);
+
public static bool TryGetMonitors(out IReadOnlyList monitors, out string error)
{
monitors = Array.Empty();
@@ -31,7 +43,7 @@ internal static class FancyZonesDataService
{
if (!File.Exists(FZPaths.EditorParameters))
{
- error = "FancyZones monitor data not found. Open FancyZones Editor once to initialize.";
+ error = Resources.FancyZones_MonitorDataNotFound;
Logger.LogWarning($"TryGetMonitors: File not found. Path={FZPaths.EditorParameters}");
return false;
}
@@ -43,7 +55,7 @@ internal static class FancyZonesDataService
var editorMonitors = editorParams.Monitors;
if (editorMonitors is null || editorMonitors.Count == 0)
{
- error = "No FancyZones monitors found.";
+ error = Resources.FancyZones_NoFancyZonesMonitorsFound;
Logger.LogWarning($"TryGetMonitors: No monitors in file.");
return false;
}
@@ -56,7 +68,7 @@ internal static class FancyZonesDataService
}
catch (Exception ex)
{
- error = $"Failed to read FancyZones monitor data: {ex.Message}";
+ error = string.Format(CultureInfo.CurrentCulture, ReadMonitorDataFailedFormat, ex.Message);
Logger.LogError($"TryGetMonitors: Exception. Message={ex.Message} Stack={ex.StackTrace}");
return false;
}
@@ -204,7 +216,7 @@ internal static class FancyZonesDataService
}
catch (Exception ex)
{
- return (false, $"Failed to write applied layouts: {ex.Message}");
+ return (false, string.Format(CultureInfo.CurrentCulture, WriteAppliedLayoutsFailedFormat, ex.Message));
}
try
@@ -213,10 +225,10 @@ internal static class FancyZonesDataService
}
catch (Exception ex)
{
- return (true, $"Layout applied, but FancyZones could not be notified: {ex.Message}");
+ return (true, string.Format(CultureInfo.CurrentCulture, LayoutAppliedNotifyFailedFormat, ex.Message));
}
- return (true, "Layout applied.");
+ return (true, Resources.FancyZones_LayoutApplied);
}
private static AppliedLayouts.AppliedLayoutWrapper? FindAppliedLayoutEntry(AppliedLayouts.AppliedLayoutsListWrapper file, EditorParameters.NativeMonitorDataWrapper monitor, string virtualDesktopId)
@@ -293,8 +305,8 @@ internal static class FancyZonesDataService
var zoneCount = type.Equals("blank", StringComparison.OrdinalIgnoreCase)
? 0
: template.ZoneCount > 0 ? template.ZoneCount : 3;
- var title = $"Template: {type}";
- var subtitle = $"{zoneCount} zones";
+ var title = string.Format(CultureInfo.CurrentCulture, TemplateFormat, type);
+ var subtitle = string.Format(CultureInfo.CurrentCulture, ZonesFormat, zoneCount);
yield return new FancyZonesLayoutDescriptor
{
@@ -357,9 +369,9 @@ internal static class FancyZonesDataService
var title = custom.Name.Trim();
var subtitle = customType switch
{
- "grid" => $"Custom grid \u2022 {applied.ZoneCount} zones",
- "canvas" => $"Custom canvas \u2022 {applied.ZoneCount} zones",
- _ => $"Custom \u2022 {applied.ZoneCount} zones",
+ "grid" => string.Format(CultureInfo.CurrentCulture, CustomGridZonesFormat, applied.ZoneCount),
+ "canvas" => string.Format(CultureInfo.CurrentCulture, CustomCanvasZonesFormat, applied.ZoneCount),
+ _ => string.Format(CultureInfo.CurrentCulture, CustomZonesFormat, applied.ZoneCount),
};
yield return new FancyZonesLayoutDescriptor
diff --git a/src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.PowerToys/Microsoft.CmdPal.Ext.PowerToys.csproj b/src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.PowerToys/Microsoft.CmdPal.Ext.PowerToys.csproj
index 36cfeb93f4..17882fe05d 100644
--- a/src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.PowerToys/Microsoft.CmdPal.Ext.PowerToys.csproj
+++ b/src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.PowerToys/Microsoft.CmdPal.Ext.PowerToys.csproj
@@ -61,6 +61,21 @@
+
+
+ True
+ True
+ Resources.resx
+
+
+
+
+
+ ResXFileCodeGenerator
+ Resources.Designer.cs
+
+
+
true
diff --git a/src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.PowerToys/Modules/AdvancedPasteModuleCommandProvider.cs b/src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.PowerToys/Modules/AdvancedPasteModuleCommandProvider.cs
index 58083919c0..0dd79ba1c3 100644
--- a/src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.PowerToys/Modules/AdvancedPasteModuleCommandProvider.cs
+++ b/src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.PowerToys/Modules/AdvancedPasteModuleCommandProvider.cs
@@ -7,6 +7,7 @@ using Common.UI;
using Microsoft.CommandPalette.Extensions.Toolkit;
using PowerToysExtension.Commands;
using PowerToysExtension.Helpers;
+using PowerToysExtension.Properties;
namespace PowerToysExtension.Modules;
@@ -22,8 +23,8 @@ internal sealed class AdvancedPasteModuleCommandProvider : ModuleCommandProvider
{
yield return new ListItem(new OpenAdvancedPasteCommand())
{
- Title = "Open Advanced Paste",
- Subtitle = "Launch the Advanced Paste UI",
+ Title = Resources.AdvancedPaste_Open_Title,
+ Subtitle = Resources.AdvancedPaste_Open_Subtitle,
Icon = icon,
};
}
@@ -31,7 +32,7 @@ internal sealed class AdvancedPasteModuleCommandProvider : ModuleCommandProvider
yield return new ListItem(new OpenInSettingsCommand(module, title))
{
Title = title,
- Subtitle = "Open Advanced Paste settings",
+ Subtitle = Resources.AdvancedPaste_Settings_Subtitle,
Icon = icon,
};
}
diff --git a/src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.PowerToys/Modules/AlwaysOnTopModuleCommandProvider.cs b/src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.PowerToys/Modules/AlwaysOnTopModuleCommandProvider.cs
index cad8a282da..5974359b5a 100644
--- a/src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.PowerToys/Modules/AlwaysOnTopModuleCommandProvider.cs
+++ b/src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.PowerToys/Modules/AlwaysOnTopModuleCommandProvider.cs
@@ -6,6 +6,7 @@ using System.Collections.Generic;
using Microsoft.CommandPalette.Extensions.Toolkit;
using PowerToysExtension.Commands;
using PowerToysExtension.Helpers;
+using PowerToysExtension.Properties;
using static Common.UI.SettingsDeepLink;
namespace PowerToysExtension.Modules;
@@ -20,7 +21,7 @@ internal sealed class AlwaysOnTopModuleCommandProvider : ModuleCommandProvider
yield return new ListItem(new OpenInSettingsCommand(SettingsWindow.AlwaysOnTop, title))
{
Title = title,
- Subtitle = "Open Always On Top settings",
+ Subtitle = Resources.AlwaysOnTop_Settings_Subtitle,
Icon = icon,
};
}
diff --git a/src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.PowerToys/Modules/AwakeModuleCommandProvider.cs b/src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.PowerToys/Modules/AwakeModuleCommandProvider.cs
index 935371fba4..5d958da38a 100644
--- a/src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.PowerToys/Modules/AwakeModuleCommandProvider.cs
+++ b/src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.PowerToys/Modules/AwakeModuleCommandProvider.cs
@@ -10,6 +10,7 @@ using Microsoft.CommandPalette.Extensions.Toolkit;
using PowerToysExtension.Commands;
using PowerToysExtension.Helpers;
using PowerToysExtension.Pages;
+using PowerToysExtension.Properties;
namespace PowerToysExtension.Modules;
@@ -26,7 +27,7 @@ internal sealed class AwakeModuleCommandProvider : ModuleCommandProvider
items.Add(new ListItem(new OpenInSettingsCommand(module, title))
{
Title = title,
- Subtitle = "Open Awake settings",
+ Subtitle = Resources.Awake_Settings_Subtitle,
Icon = moduleIcon,
});
@@ -49,40 +50,40 @@ internal sealed class AwakeModuleCommandProvider : ModuleCommandProvider
statusItem = new ListItem(new CommandItem(refreshCommand))
{
- Title = "Awake: Current status",
+ Title = Resources.Awake_Status_Title,
Subtitle = AwakeStatusService.GetStatusSubtitle(),
Icon = icon,
};
items.Add(statusItem);
- items.Add(new ListItem(new StartAwakeCommand("Awake: Keep awake indefinitely", () => AwakeService.Instance.SetIndefiniteAsync(), "Awake set to indefinite", refreshStatus))
+ items.Add(new ListItem(new StartAwakeCommand(Resources.Awake_KeepIndefinite_Title, () => AwakeService.Instance.SetIndefiniteAsync(), Resources.Awake_SetIndefinite_Toast, refreshStatus))
{
- Title = "Awake: Keep awake indefinitely",
- Subtitle = "Run Awake in indefinite mode",
+ Title = Resources.Awake_KeepIndefinite_Title,
+ Subtitle = Resources.Awake_KeepIndefinite_Subtitle,
Icon = icon,
});
- items.Add(new ListItem(new StartAwakeCommand("Awake: Keep awake for 30 minutes", () => AwakeService.Instance.SetTimedAsync(30), "Awake set for 30 minutes", refreshStatus))
+ items.Add(new ListItem(new StartAwakeCommand(Resources.Awake_Keep30Min_Title, () => AwakeService.Instance.SetTimedAsync(30), Resources.Awake_Set30Min_Toast, refreshStatus))
{
- Title = "Awake: Keep awake for 30 minutes",
- Subtitle = "Run Awake timed for 30 minutes",
+ Title = Resources.Awake_Keep30Min_Title,
+ Subtitle = Resources.Awake_Keep30Min_Subtitle,
Icon = icon,
});
- items.Add(new ListItem(new StartAwakeCommand("Awake: Keep awake for 1 hour", () => AwakeService.Instance.SetTimedAsync(60), "Awake set for 1 hour", refreshStatus))
+ items.Add(new ListItem(new StartAwakeCommand(Resources.Awake_Keep1Hour_Title, () => AwakeService.Instance.SetTimedAsync(60), Resources.Awake_Set1Hour_Toast, refreshStatus))
{
- Title = "Awake: Keep awake for 1 hour",
- Subtitle = "Run Awake timed for 1 hour",
+ Title = Resources.Awake_Keep1Hour_Title,
+ Subtitle = Resources.Awake_Keep1Hour_Subtitle,
Icon = icon,
});
- items.Add(new ListItem(new StartAwakeCommand("Awake: Keep awake for 2 hours", () => AwakeService.Instance.SetTimedAsync(120), "Awake set for 2 hours", refreshStatus))
+ items.Add(new ListItem(new StartAwakeCommand(Resources.Awake_Keep2Hours_Title, () => AwakeService.Instance.SetTimedAsync(120), Resources.Awake_Set2Hours_Toast, refreshStatus))
{
- Title = "Awake: Keep awake for 2 hours",
- Subtitle = "Run Awake timed for 2 hours",
+ Title = Resources.Awake_Keep2Hours_Title,
+ Subtitle = Resources.Awake_Keep2Hours_Subtitle,
Icon = icon,
});
items.Add(new ListItem(new StopAwakeCommand(refreshStatus))
{
- Title = "Awake: Turn off",
- Subtitle = "Switch Awake back to Off",
+ Title = Resources.Awake_TurnOff_Title,
+ Subtitle = Resources.Awake_TurnOff_Subtitle,
Icon = icon,
});
diff --git a/src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.PowerToys/Modules/ColorPickerModuleCommandProvider.cs b/src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.PowerToys/Modules/ColorPickerModuleCommandProvider.cs
index 27b3be6f05..6c2a593ff2 100644
--- a/src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.PowerToys/Modules/ColorPickerModuleCommandProvider.cs
+++ b/src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.PowerToys/Modules/ColorPickerModuleCommandProvider.cs
@@ -8,6 +8,7 @@ using Microsoft.CommandPalette.Extensions.Toolkit;
using PowerToysExtension.Commands;
using PowerToysExtension.Helpers;
using PowerToysExtension.Pages;
+using PowerToysExtension.Properties;
namespace PowerToysExtension.Modules;
@@ -24,7 +25,7 @@ internal sealed class ColorPickerModuleCommandProvider : ModuleCommandProvider
commands.Add(new ListItem(new OpenInSettingsCommand(module, title))
{
Title = title,
- Subtitle = "Open Color Picker settings",
+ Subtitle = Resources.ColorPicker_Settings_Subtitle,
Icon = icon,
});
@@ -36,15 +37,15 @@ internal sealed class ColorPickerModuleCommandProvider : ModuleCommandProvider
// Direct entries in the module list.
commands.Add(new ListItem(new OpenColorPickerCommand())
{
- Title = "Open Color Picker",
- Subtitle = "Start a color pick session",
+ Title = Resources.ColorPicker_Open_Title,
+ Subtitle = Resources.ColorPicker_Open_Subtitle,
Icon = icon,
});
commands.Add(new ListItem(new CommandItem(new ColorPickerSavedColorsPage()))
{
- Title = "Saved colors",
- Subtitle = "Browse and copy saved colors",
+ Title = Resources.ColorPicker_SavedColors_Title,
+ Subtitle = Resources.ColorPicker_SavedColors_Subtitle,
Icon = icon,
});
diff --git a/src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.PowerToys/Modules/CommandNotFoundModuleCommandProvider.cs b/src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.PowerToys/Modules/CommandNotFoundModuleCommandProvider.cs
index 2ec95172f9..48d6701924 100644
--- a/src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.PowerToys/Modules/CommandNotFoundModuleCommandProvider.cs
+++ b/src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.PowerToys/Modules/CommandNotFoundModuleCommandProvider.cs
@@ -6,6 +6,7 @@ using System.Collections.Generic;
using Microsoft.CommandPalette.Extensions.Toolkit;
using PowerToysExtension.Commands;
using PowerToysExtension.Helpers;
+using PowerToysExtension.Properties;
using static Common.UI.SettingsDeepLink;
namespace PowerToysExtension.Modules;
@@ -20,7 +21,7 @@ internal sealed class CommandNotFoundModuleCommandProvider : ModuleCommandProvid
yield return new ListItem(new OpenInSettingsCommand(SettingsWindow.CmdNotFound, title))
{
Title = title,
- Subtitle = "Open Command Not Found settings",
+ Subtitle = Resources.CommandNotFound_Settings_Subtitle,
Icon = icon,
};
}
diff --git a/src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.PowerToys/Modules/CropAndLockModuleCommandProvider.cs b/src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.PowerToys/Modules/CropAndLockModuleCommandProvider.cs
index c3f6d1ccd4..e39eb8ebef 100644
--- a/src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.PowerToys/Modules/CropAndLockModuleCommandProvider.cs
+++ b/src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.PowerToys/Modules/CropAndLockModuleCommandProvider.cs
@@ -6,6 +6,7 @@ using System.Collections.Generic;
using Microsoft.CommandPalette.Extensions.Toolkit;
using PowerToysExtension.Commands;
using PowerToysExtension.Helpers;
+using PowerToysExtension.Properties;
using static Common.UI.SettingsDeepLink;
namespace PowerToysExtension.Modules;
@@ -22,15 +23,22 @@ internal sealed class CropAndLockModuleCommandProvider : ModuleCommandProvider
{
yield return new ListItem(new CropAndLockReparentCommand())
{
- Title = "Crop and Lock (Reparent)",
- Subtitle = "Create a cropped reparented window",
+ Title = Resources.CropAndLock_Reparent_Title,
+ Subtitle = Resources.CropAndLock_Reparent_Subtitle,
Icon = icon,
};
yield return new ListItem(new CropAndLockThumbnailCommand())
{
- Title = "Crop and Lock (Thumbnail)",
- Subtitle = "Create a cropped thumbnail window",
+ Title = Resources.CropAndLock_Thumbnail_Title,
+ Subtitle = Resources.CropAndLock_Thumbnail_Subtitle,
+ Icon = icon,
+ };
+
+ yield return new ListItem(new CropAndLockScreenshotCommand())
+ {
+ Title = Resources.CropAndLock_Screenshot_Title,
+ Subtitle = Resources.CropAndLock_Screenshot_Subtitle,
Icon = icon,
};
}
@@ -38,7 +46,7 @@ internal sealed class CropAndLockModuleCommandProvider : ModuleCommandProvider
yield return new ListItem(new OpenInSettingsCommand(module, title))
{
Title = title,
- Subtitle = "Open Crop and Lock settings",
+ Subtitle = Resources.CropAndLock_Settings_Subtitle,
Icon = icon,
};
}
diff --git a/src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.PowerToys/Modules/EnvironmentVariablesModuleCommandProvider.cs b/src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.PowerToys/Modules/EnvironmentVariablesModuleCommandProvider.cs
index d72644c1bf..ad18153001 100644
--- a/src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.PowerToys/Modules/EnvironmentVariablesModuleCommandProvider.cs
+++ b/src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.PowerToys/Modules/EnvironmentVariablesModuleCommandProvider.cs
@@ -6,6 +6,7 @@ using System.Collections.Generic;
using Microsoft.CommandPalette.Extensions.Toolkit;
using PowerToysExtension.Commands;
using PowerToysExtension.Helpers;
+using PowerToysExtension.Properties;
using static Common.UI.SettingsDeepLink;
namespace PowerToysExtension.Modules;
@@ -22,15 +23,15 @@ internal sealed class EnvironmentVariablesModuleCommandProvider : ModuleCommandP
{
yield return new ListItem(new OpenEnvironmentVariablesCommand())
{
- Title = "Open Environment Variables",
- Subtitle = "Launch Environment Variables editor",
+ Title = Resources.EnvironmentVariables_Open_Title,
+ Subtitle = Resources.EnvironmentVariables_Open_Subtitle,
Icon = icon,
};
yield return new ListItem(new OpenEnvironmentVariablesAdminCommand())
{
- Title = "Open Environment Variables (Admin)",
- Subtitle = "Launch Environment Variables editor as admin",
+ Title = Resources.EnvironmentVariables_OpenAdmin_Title,
+ Subtitle = Resources.EnvironmentVariables_OpenAdmin_Subtitle,
Icon = icon,
};
}
@@ -38,7 +39,7 @@ internal sealed class EnvironmentVariablesModuleCommandProvider : ModuleCommandP
yield return new ListItem(new OpenInSettingsCommand(module, title))
{
Title = title,
- Subtitle = "Open Environment Variables settings",
+ Subtitle = Resources.EnvironmentVariables_Settings_Subtitle,
Icon = icon,
};
}
diff --git a/src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.PowerToys/Modules/FancyZonesModuleCommandProvider.cs b/src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.PowerToys/Modules/FancyZonesModuleCommandProvider.cs
index 6a4287d60f..f6a6d10524 100644
--- a/src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.PowerToys/Modules/FancyZonesModuleCommandProvider.cs
+++ b/src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.PowerToys/Modules/FancyZonesModuleCommandProvider.cs
@@ -7,6 +7,7 @@ using Microsoft.CommandPalette.Extensions.Toolkit;
using PowerToysExtension.Commands;
using PowerToysExtension.Helpers;
using PowerToysExtension.Pages;
+using PowerToysExtension.Properties;
using static Common.UI.SettingsDeepLink;
namespace PowerToysExtension.Modules;
@@ -23,22 +24,22 @@ internal sealed class FancyZonesModuleCommandProvider : ModuleCommandProvider
{
yield return new ListItem(new CommandItem(new FancyZonesLayoutsPage()))
{
- Title = "FancyZones: Layouts",
- Subtitle = "Apply a layout to all monitors or a specific monitor",
+ Title = Resources.FancyZones_Layouts_Title,
+ Subtitle = Resources.FancyZones_Layouts_Subtitle,
Icon = icon,
};
yield return new ListItem(new CommandItem(new FancyZonesMonitorsPage()))
{
- Title = "FancyZones: Monitors",
- Subtitle = "Identify monitors and apply layouts",
+ Title = Resources.FancyZones_Monitors_Title,
+ Subtitle = Resources.FancyZones_Monitors_Subtitle,
Icon = icon,
};
yield return new ListItem(new OpenFancyZonesEditorCommand())
{
- Title = "Open FancyZones Editor",
- Subtitle = "Launch layout editor",
+ Title = Resources.FancyZones_OpenEditor_Title,
+ Subtitle = Resources.FancyZones_OpenEditor_Subtitle,
Icon = icon,
};
}
@@ -46,7 +47,7 @@ internal sealed class FancyZonesModuleCommandProvider : ModuleCommandProvider
yield return new ListItem(new OpenInSettingsCommand(module, title))
{
Title = title,
- Subtitle = "Open FancyZones settings",
+ Subtitle = Resources.FancyZones_Settings_Subtitle,
Icon = icon,
};
}
diff --git a/src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.PowerToys/Modules/FileExplorerAddonsModuleCommandProvider.cs b/src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.PowerToys/Modules/FileExplorerAddonsModuleCommandProvider.cs
index 5fa5162cf8..e64ab1414a 100644
--- a/src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.PowerToys/Modules/FileExplorerAddonsModuleCommandProvider.cs
+++ b/src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.PowerToys/Modules/FileExplorerAddonsModuleCommandProvider.cs
@@ -6,6 +6,7 @@ using System.Collections.Generic;
using Microsoft.CommandPalette.Extensions.Toolkit;
using PowerToysExtension.Commands;
using PowerToysExtension.Helpers;
+using PowerToysExtension.Properties;
using static Common.UI.SettingsDeepLink;
namespace PowerToysExtension.Modules;
@@ -20,7 +21,7 @@ internal sealed class FileExplorerAddonsModuleCommandProvider : ModuleCommandPro
yield return new ListItem(new OpenInSettingsCommand(SettingsWindow.FileExplorer, title))
{
Title = title,
- Subtitle = "Open File Explorer add-ons settings",
+ Subtitle = Resources.FileExplorerAddons_Settings_Subtitle,
Icon = icon,
};
}
diff --git a/src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.PowerToys/Modules/FileLocksmithModuleCommandProvider.cs b/src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.PowerToys/Modules/FileLocksmithModuleCommandProvider.cs
index 19e5a135e5..5c8f7f367d 100644
--- a/src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.PowerToys/Modules/FileLocksmithModuleCommandProvider.cs
+++ b/src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.PowerToys/Modules/FileLocksmithModuleCommandProvider.cs
@@ -6,6 +6,7 @@ using System.Collections.Generic;
using Microsoft.CommandPalette.Extensions.Toolkit;
using PowerToysExtension.Commands;
using PowerToysExtension.Helpers;
+using PowerToysExtension.Properties;
using static Common.UI.SettingsDeepLink;
namespace PowerToysExtension.Modules;
@@ -20,7 +21,7 @@ internal sealed class FileLocksmithModuleCommandProvider : ModuleCommandProvider
yield return new ListItem(new OpenInSettingsCommand(SettingsWindow.FileLocksmith, title))
{
Title = title,
- Subtitle = "Open File Locksmith settings",
+ Subtitle = Resources.FileLocksmith_Settings_Subtitle,
Icon = icon,
};
}
diff --git a/src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.PowerToys/Modules/HostsModuleCommandProvider.cs b/src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.PowerToys/Modules/HostsModuleCommandProvider.cs
index 839598b428..c9dd6b04ed 100644
--- a/src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.PowerToys/Modules/HostsModuleCommandProvider.cs
+++ b/src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.PowerToys/Modules/HostsModuleCommandProvider.cs
@@ -6,6 +6,7 @@ using System.Collections.Generic;
using Microsoft.CommandPalette.Extensions.Toolkit;
using PowerToysExtension.Commands;
using PowerToysExtension.Helpers;
+using PowerToysExtension.Properties;
using static Common.UI.SettingsDeepLink;
namespace PowerToysExtension.Modules;
@@ -22,15 +23,15 @@ internal sealed class HostsModuleCommandProvider : ModuleCommandProvider
{
yield return new ListItem(new OpenHostsEditorCommand())
{
- Title = "Open Hosts File Editor",
- Subtitle = "Launch Hosts File Editor",
+ Title = Resources.Hosts_Open_Title,
+ Subtitle = Resources.Hosts_Open_Subtitle,
Icon = icon,
};
yield return new ListItem(new OpenHostsEditorAdminCommand())
{
- Title = "Open Hosts File Editor (Admin)",
- Subtitle = "Launch Hosts File Editor as admin",
+ Title = Resources.Hosts_OpenAdmin_Title,
+ Subtitle = Resources.Hosts_OpenAdmin_Subtitle,
Icon = icon,
};
}
@@ -38,7 +39,7 @@ internal sealed class HostsModuleCommandProvider : ModuleCommandProvider
yield return new ListItem(new OpenInSettingsCommand(module, title))
{
Title = title,
- Subtitle = "Open Hosts File Editor settings",
+ Subtitle = Resources.Hosts_Settings_Subtitle,
Icon = icon,
};
}
diff --git a/src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.PowerToys/Modules/ImageResizerModuleCommandProvider.cs b/src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.PowerToys/Modules/ImageResizerModuleCommandProvider.cs
index 6ec2f1b3a6..627cd0f2a7 100644
--- a/src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.PowerToys/Modules/ImageResizerModuleCommandProvider.cs
+++ b/src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.PowerToys/Modules/ImageResizerModuleCommandProvider.cs
@@ -6,6 +6,7 @@ using System.Collections.Generic;
using Microsoft.CommandPalette.Extensions.Toolkit;
using PowerToysExtension.Commands;
using PowerToysExtension.Helpers;
+using PowerToysExtension.Properties;
using static Common.UI.SettingsDeepLink;
namespace PowerToysExtension.Modules;
@@ -20,7 +21,7 @@ internal sealed class ImageResizerModuleCommandProvider : ModuleCommandProvider
yield return new ListItem(new OpenInSettingsCommand(SettingsWindow.ImageResizer, title))
{
Title = title,
- Subtitle = "Open Image Resizer settings",
+ Subtitle = Resources.ImageResizer_Settings_Subtitle,
Icon = icon,
};
}
diff --git a/src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.PowerToys/Modules/KeyboardManagerModuleCommandProvider.cs b/src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.PowerToys/Modules/KeyboardManagerModuleCommandProvider.cs
index bb42f484a7..2742db9904 100644
--- a/src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.PowerToys/Modules/KeyboardManagerModuleCommandProvider.cs
+++ b/src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.PowerToys/Modules/KeyboardManagerModuleCommandProvider.cs
@@ -6,6 +6,7 @@ using System.Collections.Generic;
using Microsoft.CommandPalette.Extensions.Toolkit;
using PowerToysExtension.Commands;
using PowerToysExtension.Helpers;
+using PowerToysExtension.Properties;
using static Common.UI.SettingsDeepLink;
namespace PowerToysExtension.Modules;
@@ -20,7 +21,7 @@ internal sealed class KeyboardManagerModuleCommandProvider : ModuleCommandProvid
yield return new ListItem(new OpenInSettingsCommand(SettingsWindow.KBM, title))
{
Title = title,
- Subtitle = "Open Keyboard Manager settings",
+ Subtitle = Resources.KeyboardManager_Settings_Subtitle,
Icon = icon,
};
}
diff --git a/src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.PowerToys/Modules/LightSwitchModuleCommandProvider.cs b/src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.PowerToys/Modules/LightSwitchModuleCommandProvider.cs
index a07c06fedb..f7a9b33744 100644
--- a/src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.PowerToys/Modules/LightSwitchModuleCommandProvider.cs
+++ b/src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.PowerToys/Modules/LightSwitchModuleCommandProvider.cs
@@ -6,6 +6,7 @@ using System.Collections.Generic;
using Microsoft.CommandPalette.Extensions.Toolkit;
using PowerToysExtension.Commands;
using PowerToysExtension.Helpers;
+using PowerToysExtension.Properties;
using static Common.UI.SettingsDeepLink;
namespace PowerToysExtension.Modules;
@@ -24,8 +25,8 @@ internal sealed class LightSwitchModuleCommandProvider : ModuleCommandProvider
{
items.Add(new ListItem(new ToggleLightSwitchCommand())
{
- Title = "Toggle Light Switch",
- Subtitle = "Toggle system/apps theme immediately",
+ Title = Resources.LightSwitch_Toggle_Title,
+ Subtitle = Resources.LightSwitch_Toggle_Subtitle,
Icon = icon,
});
}
@@ -33,7 +34,7 @@ internal sealed class LightSwitchModuleCommandProvider : ModuleCommandProvider
items.Add(new ListItem(new OpenInSettingsCommand(module, title))
{
Title = title,
- Subtitle = "Open Light Switch settings",
+ Subtitle = Resources.LightSwitch_Settings_Subtitle,
Icon = icon,
});
diff --git a/src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.PowerToys/Modules/MouseUtilsModuleCommandProvider.cs b/src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.PowerToys/Modules/MouseUtilsModuleCommandProvider.cs
index 34c6be193e..bce2c86e5e 100644
--- a/src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.PowerToys/Modules/MouseUtilsModuleCommandProvider.cs
+++ b/src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.PowerToys/Modules/MouseUtilsModuleCommandProvider.cs
@@ -6,6 +6,7 @@ using System.Collections.Generic;
using Microsoft.CommandPalette.Extensions.Toolkit;
using PowerToysExtension.Commands;
using PowerToysExtension.Helpers;
+using PowerToysExtension.Properties;
using static Common.UI.SettingsDeepLink;
namespace PowerToysExtension.Modules;
@@ -22,8 +23,8 @@ internal sealed class MouseUtilsModuleCommandProvider : ModuleCommandProvider
{
yield return new ListItem(new ToggleFindMyMouseCommand())
{
- Title = "Trigger Find My Mouse",
- Subtitle = "Focus the mouse pointer",
+ Title = Resources.MouseUtils_FindMyMouse_Title,
+ Subtitle = Resources.MouseUtils_FindMyMouse_Subtitle,
Icon = icon,
};
}
@@ -32,8 +33,8 @@ internal sealed class MouseUtilsModuleCommandProvider : ModuleCommandProvider
{
yield return new ListItem(new ToggleMouseHighlighterCommand())
{
- Title = "Toggle Mouse Highlighter",
- Subtitle = "Highlight mouse clicks",
+ Title = Resources.MouseUtils_Highlighter_Title,
+ Subtitle = Resources.MouseUtils_Highlighter_Subtitle,
Icon = icon,
};
}
@@ -42,8 +43,8 @@ internal sealed class MouseUtilsModuleCommandProvider : ModuleCommandProvider
{
yield return new ListItem(new ToggleMouseCrosshairsCommand())
{
- Title = "Toggle Mouse Crosshairs",
- Subtitle = "Enable or disable pointer crosshairs",
+ Title = Resources.MouseUtils_Crosshairs_Title,
+ Subtitle = Resources.MouseUtils_Crosshairs_Subtitle,
Icon = icon,
};
}
@@ -52,8 +53,8 @@ internal sealed class MouseUtilsModuleCommandProvider : ModuleCommandProvider
{
yield return new ListItem(new ToggleCursorWrapCommand())
{
- Title = "Toggle Cursor Wrap",
- Subtitle = "Wrap the cursor across monitor edges",
+ Title = Resources.MouseUtils_CursorWrap_Title,
+ Subtitle = Resources.MouseUtils_CursorWrap_Subtitle,
Icon = icon,
};
}
@@ -62,8 +63,8 @@ internal sealed class MouseUtilsModuleCommandProvider : ModuleCommandProvider
{
yield return new ListItem(new ShowMouseJumpPreviewCommand())
{
- Title = "Show Mouse Jump Preview",
- Subtitle = "Jump the pointer to a target",
+ Title = Resources.MouseUtils_MouseJump_Title,
+ Subtitle = Resources.MouseUtils_MouseJump_Subtitle,
Icon = icon,
};
}
@@ -71,7 +72,7 @@ internal sealed class MouseUtilsModuleCommandProvider : ModuleCommandProvider
yield return new ListItem(new OpenInSettingsCommand(module, title))
{
Title = title,
- Subtitle = "Open Mouse Utilities settings",
+ Subtitle = Resources.MouseUtils_Settings_Subtitle,
Icon = icon,
};
}
diff --git a/src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.PowerToys/Modules/MouseWithoutBordersModuleCommandProvider.cs b/src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.PowerToys/Modules/MouseWithoutBordersModuleCommandProvider.cs
index 49a3f3635a..d7e96fbf68 100644
--- a/src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.PowerToys/Modules/MouseWithoutBordersModuleCommandProvider.cs
+++ b/src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.PowerToys/Modules/MouseWithoutBordersModuleCommandProvider.cs
@@ -6,6 +6,7 @@ using System.Collections.Generic;
using Microsoft.CommandPalette.Extensions.Toolkit;
using PowerToysExtension.Commands;
using PowerToysExtension.Helpers;
+using PowerToysExtension.Properties;
using static Common.UI.SettingsDeepLink;
namespace PowerToysExtension.Modules;
@@ -20,7 +21,7 @@ internal sealed class MouseWithoutBordersModuleCommandProvider : ModuleCommandPr
yield return new ListItem(new OpenInSettingsCommand(SettingsWindow.MouseWithoutBorders, title))
{
Title = title,
- Subtitle = "Open Mouse Without Borders settings",
+ Subtitle = Resources.MouseWithoutBorders_Settings_Subtitle,
Icon = icon,
};
}
diff --git a/src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.PowerToys/Modules/NewPlusModuleCommandProvider.cs b/src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.PowerToys/Modules/NewPlusModuleCommandProvider.cs
index f88d104b73..7dec548765 100644
--- a/src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.PowerToys/Modules/NewPlusModuleCommandProvider.cs
+++ b/src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.PowerToys/Modules/NewPlusModuleCommandProvider.cs
@@ -6,6 +6,7 @@ using System.Collections.Generic;
using Microsoft.CommandPalette.Extensions.Toolkit;
using PowerToysExtension.Commands;
using PowerToysExtension.Helpers;
+using PowerToysExtension.Properties;
using static Common.UI.SettingsDeepLink;
namespace PowerToysExtension.Modules;
@@ -20,7 +21,7 @@ internal sealed class NewPlusModuleCommandProvider : ModuleCommandProvider
yield return new ListItem(new OpenInSettingsCommand(SettingsWindow.NewPlus, title))
{
Title = title,
- Subtitle = "Open New+ settings",
+ Subtitle = Resources.NewPlus_Settings_Subtitle,
Icon = icon,
};
}
diff --git a/src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.PowerToys/Modules/PeekModuleCommandProvider.cs b/src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.PowerToys/Modules/PeekModuleCommandProvider.cs
index a55a187206..8c65b95276 100644
--- a/src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.PowerToys/Modules/PeekModuleCommandProvider.cs
+++ b/src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.PowerToys/Modules/PeekModuleCommandProvider.cs
@@ -6,6 +6,7 @@ using System.Collections.Generic;
using Microsoft.CommandPalette.Extensions.Toolkit;
using PowerToysExtension.Commands;
using PowerToysExtension.Helpers;
+using PowerToysExtension.Properties;
using static Common.UI.SettingsDeepLink;
namespace PowerToysExtension.Modules;
@@ -20,7 +21,7 @@ internal sealed class PeekModuleCommandProvider : ModuleCommandProvider
yield return new ListItem(new OpenInSettingsCommand(SettingsWindow.Peek, title))
{
Title = title,
- Subtitle = "Open Peek settings",
+ Subtitle = Resources.Peek_Settings_Subtitle,
Icon = icon,
};
}
diff --git a/src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.PowerToys/Modules/PowerRenameModuleCommandProvider.cs b/src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.PowerToys/Modules/PowerRenameModuleCommandProvider.cs
index 434a1d53cf..2cebee25cd 100644
--- a/src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.PowerToys/Modules/PowerRenameModuleCommandProvider.cs
+++ b/src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.PowerToys/Modules/PowerRenameModuleCommandProvider.cs
@@ -6,6 +6,7 @@ using System.Collections.Generic;
using Microsoft.CommandPalette.Extensions.Toolkit;
using PowerToysExtension.Commands;
using PowerToysExtension.Helpers;
+using PowerToysExtension.Properties;
using static Common.UI.SettingsDeepLink;
namespace PowerToysExtension.Modules;
@@ -20,7 +21,7 @@ internal sealed class PowerRenameModuleCommandProvider : ModuleCommandProvider
yield return new ListItem(new OpenInSettingsCommand(SettingsWindow.PowerRename, title))
{
Title = title,
- Subtitle = "Open PowerRename settings",
+ Subtitle = Resources.PowerRename_Settings_Subtitle,
Icon = icon,
};
}
diff --git a/src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.PowerToys/Modules/PowerToysRunModuleCommandProvider.cs b/src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.PowerToys/Modules/PowerToysRunModuleCommandProvider.cs
index 593bebb3a9..35f75467b7 100644
--- a/src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.PowerToys/Modules/PowerToysRunModuleCommandProvider.cs
+++ b/src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.PowerToys/Modules/PowerToysRunModuleCommandProvider.cs
@@ -6,6 +6,7 @@ using System.Collections.Generic;
using Microsoft.CommandPalette.Extensions.Toolkit;
using PowerToysExtension.Commands;
using PowerToysExtension.Helpers;
+using PowerToysExtension.Properties;
using static Common.UI.SettingsDeepLink;
namespace PowerToysExtension.Modules;
@@ -20,7 +21,7 @@ internal sealed class PowerToysRunModuleCommandProvider : ModuleCommandProvider
yield return new ListItem(new OpenInSettingsCommand(SettingsWindow.PowerLauncher, title))
{
Title = title,
- Subtitle = "Open PowerToys Run settings",
+ Subtitle = Resources.PowerToysRun_Settings_Subtitle,
Icon = icon,
};
}
diff --git a/src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.PowerToys/Modules/QuickAccentModuleCommandProvider.cs b/src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.PowerToys/Modules/QuickAccentModuleCommandProvider.cs
index 9122b3534c..b0c97dfa99 100644
--- a/src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.PowerToys/Modules/QuickAccentModuleCommandProvider.cs
+++ b/src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.PowerToys/Modules/QuickAccentModuleCommandProvider.cs
@@ -6,6 +6,7 @@ using System.Collections.Generic;
using Microsoft.CommandPalette.Extensions.Toolkit;
using PowerToysExtension.Commands;
using PowerToysExtension.Helpers;
+using PowerToysExtension.Properties;
using static Common.UI.SettingsDeepLink;
namespace PowerToysExtension.Modules;
@@ -20,7 +21,7 @@ internal sealed class QuickAccentModuleCommandProvider : ModuleCommandProvider
yield return new ListItem(new OpenInSettingsCommand(SettingsWindow.PowerAccent, title))
{
Title = title,
- Subtitle = "Open Quick Accent settings",
+ Subtitle = Resources.QuickAccent_Settings_Subtitle,
Icon = icon,
};
}
diff --git a/src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.PowerToys/Modules/RegistryPreviewModuleCommandProvider.cs b/src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.PowerToys/Modules/RegistryPreviewModuleCommandProvider.cs
index 7dbe3f841b..9069931a82 100644
--- a/src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.PowerToys/Modules/RegistryPreviewModuleCommandProvider.cs
+++ b/src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.PowerToys/Modules/RegistryPreviewModuleCommandProvider.cs
@@ -6,6 +6,7 @@ using System.Collections.Generic;
using Microsoft.CommandPalette.Extensions.Toolkit;
using PowerToysExtension.Commands;
using PowerToysExtension.Helpers;
+using PowerToysExtension.Properties;
using static Common.UI.SettingsDeepLink;
namespace PowerToysExtension.Modules;
@@ -22,8 +23,8 @@ internal sealed class RegistryPreviewModuleCommandProvider : ModuleCommandProvid
{
yield return new ListItem(new OpenRegistryPreviewCommand())
{
- Title = "Open Registry Preview",
- Subtitle = "Launch Registry Preview",
+ Title = Resources.RegistryPreview_Open_Title,
+ Subtitle = Resources.RegistryPreview_Open_Subtitle,
Icon = icon,
};
}
@@ -31,7 +32,7 @@ internal sealed class RegistryPreviewModuleCommandProvider : ModuleCommandProvid
yield return new ListItem(new OpenInSettingsCommand(module, title))
{
Title = title,
- Subtitle = "Open Registry Preview settings",
+ Subtitle = Resources.RegistryPreview_Settings_Subtitle,
Icon = icon,
};
}
diff --git a/src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.PowerToys/Modules/ScreenRulerModuleCommandProvider.cs b/src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.PowerToys/Modules/ScreenRulerModuleCommandProvider.cs
index 23674c3dfe..62591f542f 100644
--- a/src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.PowerToys/Modules/ScreenRulerModuleCommandProvider.cs
+++ b/src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.PowerToys/Modules/ScreenRulerModuleCommandProvider.cs
@@ -6,6 +6,7 @@ using System.Collections.Generic;
using Microsoft.CommandPalette.Extensions.Toolkit;
using PowerToysExtension.Commands;
using PowerToysExtension.Helpers;
+using PowerToysExtension.Properties;
using static Common.UI.SettingsDeepLink;
namespace PowerToysExtension.Modules;
@@ -22,8 +23,8 @@ internal sealed class ScreenRulerModuleCommandProvider : ModuleCommandProvider
{
yield return new ListItem(new ToggleScreenRulerCommand())
{
- Title = "Toggle Screen Ruler",
- Subtitle = "Start or close Screen Ruler",
+ Title = Resources.ScreenRuler_Toggle_Title,
+ Subtitle = Resources.ScreenRuler_Toggle_Subtitle,
Icon = icon,
};
}
@@ -31,7 +32,7 @@ internal sealed class ScreenRulerModuleCommandProvider : ModuleCommandProvider
yield return new ListItem(new OpenInSettingsCommand(module, title))
{
Title = title,
- Subtitle = "Open Screen Ruler settings",
+ Subtitle = Resources.ScreenRuler_Settings_Subtitle,
Icon = icon,
};
}
diff --git a/src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.PowerToys/Modules/ShortcutGuideModuleCommandProvider.cs b/src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.PowerToys/Modules/ShortcutGuideModuleCommandProvider.cs
index 20f487c1f3..1c194b7d14 100644
--- a/src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.PowerToys/Modules/ShortcutGuideModuleCommandProvider.cs
+++ b/src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.PowerToys/Modules/ShortcutGuideModuleCommandProvider.cs
@@ -6,6 +6,7 @@ using System.Collections.Generic;
using Microsoft.CommandPalette.Extensions.Toolkit;
using PowerToysExtension.Commands;
using PowerToysExtension.Helpers;
+using PowerToysExtension.Properties;
using static Common.UI.SettingsDeepLink;
namespace PowerToysExtension.Modules;
@@ -22,8 +23,8 @@ internal sealed class ShortcutGuideModuleCommandProvider : ModuleCommandProvider
{
yield return new ListItem(new ToggleShortcutGuideCommand())
{
- Title = "Toggle Shortcut Guide",
- Subtitle = "Show or hide Shortcut Guide",
+ Title = Resources.ShortcutGuide_Toggle_Title,
+ Subtitle = Resources.ShortcutGuide_Toggle_Subtitle,
Icon = icon,
};
}
@@ -31,7 +32,7 @@ internal sealed class ShortcutGuideModuleCommandProvider : ModuleCommandProvider
yield return new ListItem(new OpenInSettingsCommand(module, title))
{
Title = title,
- Subtitle = "Open Shortcut Guide settings",
+ Subtitle = Resources.ShortcutGuide_Settings_Subtitle,
Icon = icon,
};
}
diff --git a/src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.PowerToys/Modules/TextExtractorModuleCommandProvider.cs b/src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.PowerToys/Modules/TextExtractorModuleCommandProvider.cs
index a8e816ccc6..ea72d7611b 100644
--- a/src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.PowerToys/Modules/TextExtractorModuleCommandProvider.cs
+++ b/src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.PowerToys/Modules/TextExtractorModuleCommandProvider.cs
@@ -6,6 +6,7 @@ using System.Collections.Generic;
using Microsoft.CommandPalette.Extensions.Toolkit;
using PowerToysExtension.Commands;
using PowerToysExtension.Helpers;
+using PowerToysExtension.Properties;
using static Common.UI.SettingsDeepLink;
namespace PowerToysExtension.Modules;
@@ -22,8 +23,8 @@ internal sealed class TextExtractorModuleCommandProvider : ModuleCommandProvider
{
yield return new ListItem(new ToggleTextExtractorCommand())
{
- Title = "Toggle Text Extractor",
- Subtitle = "Start or close Text Extractor",
+ Title = Resources.TextExtractor_Toggle_Title,
+ Subtitle = Resources.TextExtractor_Toggle_Subtitle,
Icon = icon,
};
}
@@ -31,7 +32,7 @@ internal sealed class TextExtractorModuleCommandProvider : ModuleCommandProvider
yield return new ListItem(new OpenInSettingsCommand(module, title))
{
Title = title,
- Subtitle = "Open Text Extractor settings",
+ Subtitle = Resources.TextExtractor_Settings_Subtitle,
Icon = icon,
};
}
diff --git a/src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.PowerToys/Modules/WorkspacesModuleCommandProvider.cs b/src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.PowerToys/Modules/WorkspacesModuleCommandProvider.cs
index 49d585ba6d..0cc315bbf9 100644
--- a/src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.PowerToys/Modules/WorkspacesModuleCommandProvider.cs
+++ b/src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.PowerToys/Modules/WorkspacesModuleCommandProvider.cs
@@ -7,6 +7,7 @@ using Common.UI;
using Microsoft.CommandPalette.Extensions.Toolkit;
using PowerToysExtension.Commands;
using PowerToysExtension.Helpers;
+using PowerToysExtension.Properties;
using Workspaces.ModuleServices;
using WorkspacesCsharpLibrary.Data;
@@ -25,7 +26,7 @@ internal sealed class WorkspacesModuleCommandProvider : ModuleCommandProvider
items.Add(new ListItem(new OpenInSettingsCommand(module, title))
{
Title = title,
- Subtitle = "Open Workspaces settings",
+ Subtitle = Resources.Workspaces_Settings_Subtitle,
Icon = moduleIcon,
});
@@ -37,8 +38,8 @@ internal sealed class WorkspacesModuleCommandProvider : ModuleCommandProvider
// Settings entry plus common actions.
items.Add(new ListItem(new OpenWorkspaceEditorCommand())
{
- Title = "Workspaces: Open editor",
- Subtitle = "Create or edit workspaces",
+ Title = Resources.Workspaces_OpenEditor_Title,
+ Subtitle = Resources.Workspaces_OpenEditor_Subtitle,
Icon = icon,
});
diff --git a/src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.PowerToys/Modules/ZoomItModuleCommandProvider.cs b/src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.PowerToys/Modules/ZoomItModuleCommandProvider.cs
index a73ccdfbe3..0392e4a759 100644
--- a/src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.PowerToys/Modules/ZoomItModuleCommandProvider.cs
+++ b/src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.PowerToys/Modules/ZoomItModuleCommandProvider.cs
@@ -6,6 +6,7 @@ using System.Collections.Generic;
using Microsoft.CommandPalette.Extensions.Toolkit;
using PowerToysExtension.Commands;
using PowerToysExtension.Helpers;
+using PowerToysExtension.Properties;
using static Common.UI.SettingsDeepLink;
namespace PowerToysExtension.Modules;
@@ -21,40 +22,40 @@ internal sealed class ZoomItModuleCommandProvider : ModuleCommandProvider
if (ModuleEnablementService.IsModuleEnabled(module))
{
// Action commands via ZoomIt IPC
- yield return new ListItem(new ZoomItActionCommand("zoom", "ZoomIt: Zoom"))
+ yield return new ListItem(new ZoomItActionCommand("zoom", Resources.ZoomIt_Zoom_Title))
{
- Title = "ZoomIt: Zoom",
- Subtitle = "Enter zoom mode",
+ Title = Resources.ZoomIt_Zoom_Title,
+ Subtitle = Resources.ZoomIt_Zoom_Subtitle,
Icon = icon,
};
- yield return new ListItem(new ZoomItActionCommand("draw", "ZoomIt: Draw"))
+ yield return new ListItem(new ZoomItActionCommand("draw", Resources.ZoomIt_Draw_Title))
{
- Title = "ZoomIt: Draw",
- Subtitle = "Enter drawing mode",
+ Title = Resources.ZoomIt_Draw_Title,
+ Subtitle = Resources.ZoomIt_Draw_Subtitle,
Icon = icon,
};
- yield return new ListItem(new ZoomItActionCommand("break", "ZoomIt: Break"))
+ yield return new ListItem(new ZoomItActionCommand("break", Resources.ZoomIt_Break_Title))
{
- Title = "ZoomIt: Break",
- Subtitle = "Enter break timer",
+ Title = Resources.ZoomIt_Break_Title,
+ Subtitle = Resources.ZoomIt_Break_Subtitle,
Icon = icon,
};
- yield return new ListItem(new ZoomItActionCommand("liveZoom", "ZoomIt: Live Zoom"))
+ yield return new ListItem(new ZoomItActionCommand("liveZoom", Resources.ZoomIt_LiveZoom_Title))
{
- Title = "ZoomIt: Live Zoom",
- Subtitle = "Toggle live zoom",
+ Title = Resources.ZoomIt_LiveZoom_Title,
+ Subtitle = Resources.ZoomIt_LiveZoom_Subtitle,
Icon = icon,
};
- yield return new ListItem(new ZoomItActionCommand("snip", "ZoomIt: Snip"))
+ yield return new ListItem(new ZoomItActionCommand("snip", Resources.ZoomIt_Snip_Title))
{
- Title = "ZoomIt: Snip",
- Subtitle = "Enter snip mode",
+ Title = Resources.ZoomIt_Snip_Title,
+ Subtitle = Resources.ZoomIt_Snip_Subtitle,
Icon = icon,
};
- yield return new ListItem(new ZoomItActionCommand("record", "ZoomIt: Record"))
+ yield return new ListItem(new ZoomItActionCommand("record", Resources.ZoomIt_Record_Title))
{
- Title = "ZoomIt: Record",
- Subtitle = "Start recording",
+ Title = Resources.ZoomIt_Record_Title,
+ Subtitle = Resources.ZoomIt_Record_Subtitle,
Icon = icon,
};
}
@@ -62,7 +63,7 @@ internal sealed class ZoomItModuleCommandProvider : ModuleCommandProvider
yield return new ListItem(new OpenInSettingsCommand(module, title))
{
Title = title,
- Subtitle = "Open ZoomIt settings",
+ Subtitle = Resources.ZoomIt_Settings_Subtitle,
Icon = icon,
};
}
diff --git a/src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.PowerToys/Pages/ColorPickerSavedColorsPage.cs b/src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.PowerToys/Pages/ColorPickerSavedColorsPage.cs
index 5e06951794..54dc15c0c5 100644
--- a/src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.PowerToys/Pages/ColorPickerSavedColorsPage.cs
+++ b/src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.PowerToys/Pages/ColorPickerSavedColorsPage.cs
@@ -3,6 +3,7 @@
// See the LICENSE file in the project root for more information.
using System;
+using System.Globalization;
using System.Linq;
using System.Text;
using ColorPicker.ModuleServices;
@@ -10,24 +11,27 @@ using Microsoft.CommandPalette.Extensions;
using Microsoft.CommandPalette.Extensions.Toolkit;
using PowerToysExtension.Commands;
using PowerToysExtension.Helpers;
+using PowerToysExtension.Properties;
namespace PowerToysExtension.Pages;
internal sealed partial class ColorPickerSavedColorsPage : DynamicListPage
{
+ private static readonly CompositeFormat NoMatchingSavedColorsFormat = CompositeFormat.Parse(Resources.ColorPicker_NoMatchingSavedColors_Subtitle);
+
private readonly CommandItem _emptyContent;
public ColorPickerSavedColorsPage()
{
Icon = PowerToysResourcesHelper.IconFromSettingsIcon("ColorPicker.png");
- Title = "Saved colors";
+ Title = Resources.ColorPicker_SavedColors_Title;
Name = "ColorPickerSavedColors";
Id = "com.microsoft.powertoys.colorpicker.savedColors";
_emptyContent = new CommandItem()
{
- Title = "No saved colors",
- Subtitle = "Pick a color first, then try again.",
+ Title = Resources.ColorPicker_NoSavedColors_Title,
+ Subtitle = Resources.ColorPicker_NoSavedColors_Subtitle,
Icon = PowerToysResourcesHelper.IconFromSettingsIcon("ColorPicker.png"),
};
@@ -70,8 +74,8 @@ internal sealed partial class ColorPickerSavedColorsPage : DynamicListPage
public override void UpdateSearchText(string oldSearch, string newSearch)
{
_emptyContent.Subtitle = string.IsNullOrWhiteSpace(newSearch)
- ? "Pick a color first, then try again."
- : $"No saved colors matching '{newSearch}'";
+ ? Resources.ColorPicker_NoSavedColors_Subtitle
+ : string.Format(CultureInfo.CurrentCulture, NoMatchingSavedColorsFormat, newSearch);
RaiseItemsChanged(0);
}
diff --git a/src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.PowerToys/Pages/FancyZonesLayoutsPage.cs b/src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.PowerToys/Pages/FancyZonesLayoutsPage.cs
index 1b568da7d6..897b7fa46a 100644
--- a/src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.PowerToys/Pages/FancyZonesLayoutsPage.cs
+++ b/src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.PowerToys/Pages/FancyZonesLayoutsPage.cs
@@ -11,6 +11,7 @@ using Microsoft.CommandPalette.Extensions;
using Microsoft.CommandPalette.Extensions.Toolkit;
using PowerToysExtension.Commands;
using PowerToysExtension.Helpers;
+using PowerToysExtension.Properties;
namespace PowerToysExtension.Pages;
@@ -21,13 +22,13 @@ internal sealed partial class FancyZonesLayoutsPage : DynamicListPage
public FancyZonesLayoutsPage()
{
Icon = PowerToysResourcesHelper.IconFromSettingsIcon("FancyZones.png");
- Name = Title = "FancyZones Layouts";
+ Name = Title = Resources.FancyZones_Layouts_Title;
Id = "com.microsoft.cmdpal.powertoys.fancyzones.layouts";
_emptyMessage = new CommandItem()
{
- Title = "No layouts found",
- Subtitle = "Open FancyZones Editor once to initialize layouts.",
+ Title = Resources.FancyZones_NoLayoutsFound_Title,
+ Subtitle = Resources.FancyZones_NoLayoutsFound_Subtitle,
Icon = PowerToysResourcesHelper.IconFromSettingsIcon("FancyZones.png"),
};
EmptyContent = _emptyMessage;
diff --git a/src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.PowerToys/Pages/FancyZonesMonitorLayoutPickerPage.cs b/src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.PowerToys/Pages/FancyZonesMonitorLayoutPickerPage.cs
index 0269f84ed0..d798bce768 100644
--- a/src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.PowerToys/Pages/FancyZonesMonitorLayoutPickerPage.cs
+++ b/src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.PowerToys/Pages/FancyZonesMonitorLayoutPickerPage.cs
@@ -4,16 +4,21 @@
using System;
using System.Collections.Generic;
+using System.Globalization;
using System.Linq;
+using System.Text;
using Microsoft.CommandPalette.Extensions;
using Microsoft.CommandPalette.Extensions.Toolkit;
using PowerToysExtension.Commands;
using PowerToysExtension.Helpers;
+using PowerToysExtension.Properties;
namespace PowerToysExtension.Pages;
internal sealed partial class FancyZonesMonitorLayoutPickerPage : DynamicListPage
{
+ private static readonly CompositeFormat SetActiveLayoutForFormat = CompositeFormat.Parse(Resources.FancyZones_SetActiveLayoutFor_Format);
+
private readonly FancyZonesMonitorDescriptor _monitor;
private readonly CommandItem _emptyMessage;
@@ -21,13 +26,13 @@ internal sealed partial class FancyZonesMonitorLayoutPickerPage : DynamicListPag
{
_monitor = monitor;
Icon = PowerToysResourcesHelper.IconFromSettingsIcon("FancyZones.png");
- Name = Title = $"Set active layout for {_monitor.Title}";
+ Name = Title = string.Format(CultureInfo.CurrentCulture, SetActiveLayoutForFormat, _monitor.Title);
Id = $"com.microsoft.cmdpal.powertoys.fancyzones.monitor.{_monitor.Index}.layouts";
_emptyMessage = new CommandItem()
{
- Title = "No layouts found",
- Subtitle = "Open FancyZones Editor once to initialize layouts.",
+ Title = Resources.FancyZones_NoLayoutsFound_Title,
+ Subtitle = Resources.FancyZones_NoLayoutsFound_Subtitle,
Icon = PowerToysResourcesHelper.IconFromSettingsIcon("FancyZones.png"),
};
EmptyContent = _emptyMessage;
diff --git a/src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.PowerToys/Pages/FancyZonesMonitorsPage.cs b/src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.PowerToys/Pages/FancyZonesMonitorsPage.cs
index 8422038d3d..1da279422b 100644
--- a/src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.PowerToys/Pages/FancyZonesMonitorsPage.cs
+++ b/src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.PowerToys/Pages/FancyZonesMonitorsPage.cs
@@ -4,26 +4,31 @@
using System;
using System.Collections.Generic;
+using System.Globalization;
+using System.Text;
using Microsoft.CommandPalette.Extensions;
using Microsoft.CommandPalette.Extensions.Toolkit;
using PowerToysExtension.Helpers;
+using PowerToysExtension.Properties;
namespace PowerToysExtension.Pages;
internal sealed partial class FancyZonesMonitorsPage : DynamicListPage
{
+ private static readonly CompositeFormat CurrentLayoutFormat = CompositeFormat.Parse(Resources.FancyZones_CurrentLayout_Format);
+
private readonly CommandItem _emptyMessage;
public FancyZonesMonitorsPage()
{
Icon = PowerToysResourcesHelper.IconFromSettingsIcon("FancyZones.png");
- Name = Title = "FancyZones Monitors";
+ Name = Title = Resources.FancyZones_Monitors_Title;
Id = "com.microsoft.cmdpal.powertoys.fancyzones.monitors";
_emptyMessage = new CommandItem()
{
- Title = "No monitors found",
- Subtitle = "Open FancyZones Editor once to initialize monitor data.",
+ Title = Resources.FancyZones_NoMonitorsFound_Title,
+ Subtitle = Resources.FancyZones_NoMonitorsFound_Subtitle,
Icon = PowerToysResourcesHelper.IconFromSettingsIcon("FancyZones.png"),
};
EmptyContent = _emptyMessage;
@@ -55,8 +60,8 @@ internal sealed partial class FancyZonesMonitorsPage : DynamicListPage
}
var layoutDescription = FancyZonesDataService.TryGetAppliedLayoutForMonitor(monitor.Data, out var applied) && applied is not null
- ? $"Current layout: {applied.Value.Type}"
- : "Current layout: unknown";
+ ? string.Format(CultureInfo.CurrentCulture, CurrentLayoutFormat, applied.Value.Type)
+ : Resources.FancyZones_CurrentLayout_Unknown;
var item = new FancyZonesMonitorListItem(monitor, layoutDescription, monitorIcon);
items.Add(item);
diff --git a/src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.PowerToys/Pages/PowerToysExtensionPage.cs b/src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.PowerToys/Pages/PowerToysExtensionPage.cs
index 0d81573280..7082169629 100644
--- a/src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.PowerToys/Pages/PowerToysExtensionPage.cs
+++ b/src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.PowerToys/Pages/PowerToysExtensionPage.cs
@@ -6,6 +6,7 @@ using Awake.ModuleServices;
using Microsoft.CommandPalette.Extensions;
using Microsoft.CommandPalette.Extensions.Toolkit;
using PowerToysExtension.Commands;
+using PowerToysExtension.Properties;
namespace PowerToysExtension;
@@ -14,32 +15,32 @@ internal sealed partial class PowerToysExtensionPage : ListPage
public PowerToysExtensionPage()
{
Icon = Helpers.PowerToysResourcesHelper.IconFromSettingsIcon("PowerToys.png");
- Title = "PowerToys";
- Name = "PowerToys commands";
+ Title = Resources.PowerToys_DisplayName;
+ Name = Resources.PowerToysExtension_CommandsName;
}
public override IListItem[] GetItems()
{
return [
- new ListItem(new LaunchModuleCommand("PowerToys", executableName: "PowerToys.exe", displayName: "Open PowerToys"))
+ new ListItem(new LaunchModuleCommand("PowerToys", executableName: "PowerToys.exe", displayName: Resources.PowerToysExtension_OpenPowerToys_Title))
{
- Title = "Open PowerToys",
- Subtitle = "Launch the PowerToys shell",
+ Title = Resources.PowerToysExtension_OpenPowerToys_Title,
+ Subtitle = Resources.PowerToysExtension_OpenPowerToys_Subtitle,
},
new ListItem(new OpenPowerToysSettingsCommand("PowerToys", "General"))
{
- Title = "Open PowerToys settings",
- Subtitle = "Open the main PowerToys settings window",
+ Title = Resources.PowerToysExtension_OpenSettings_Title,
+ Subtitle = Resources.PowerToysExtension_OpenSettings_Subtitle,
},
new ListItem(new OpenPowerToysSettingsCommand("Workspaces", "Workspaces"))
{
- Title = "Open Workspaces settings",
- Subtitle = "Jump directly to Workspaces settings",
+ Title = Resources.PowerToysExtension_OpenWorkspacesSettings_Title,
+ Subtitle = Resources.PowerToysExtension_OpenWorkspacesSettings_Subtitle,
},
new ListItem(new OpenWorkspaceEditorCommand())
{
- Title = "Open Workspaces editor",
- Subtitle = "Launch the Workspaces editor",
+ Title = Resources.PowerToysExtension_OpenWorkspacesEditor_Title,
+ Subtitle = Resources.PowerToysExtension_OpenWorkspacesEditor_Subtitle,
},
];
}
diff --git a/src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.PowerToys/Pages/PowerToysListPage.cs b/src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.PowerToys/Pages/PowerToysListPage.cs
index c7eed2594f..b91c8e9a5c 100644
--- a/src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.PowerToys/Pages/PowerToysListPage.cs
+++ b/src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.PowerToys/Pages/PowerToysListPage.cs
@@ -5,6 +5,7 @@
using Microsoft.CommandPalette.Extensions;
using Microsoft.CommandPalette.Extensions.Toolkit;
using PowerToysExtension.Helpers;
+using PowerToysExtension.Properties;
namespace PowerToysExtension.Pages;
@@ -15,13 +16,13 @@ internal sealed partial class PowerToysListPage : ListPage
public PowerToysListPage()
{
Icon = PowerToysResourcesHelper.IconFromSettingsIcon("PowerToys.png");
- Name = Title = "PowerToys";
+ Name = Title = Resources.PowerToys_DisplayName;
Id = "com.microsoft.cmdpal.powertoys";
SettingsChangeNotifier.SettingsChanged += OnSettingsChanged;
_empty = new CommandItem()
{
Icon = PowerToysResourcesHelper.IconFromSettingsIcon("PowerToys.png"),
- Title = "No matching module found",
+ Title = Resources.PowerToys_NoMatchingModule,
Subtitle = SearchText,
};
EmptyContent = _empty;
diff --git a/src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.PowerToys/PowerToysCommandsProvider.cs b/src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.PowerToys/PowerToysCommandsProvider.cs
index d4dde03b46..f3d22c7e5a 100644
--- a/src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.PowerToys/PowerToysCommandsProvider.cs
+++ b/src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.PowerToys/PowerToysCommandsProvider.cs
@@ -7,6 +7,7 @@ using ManagedCommon;
using Microsoft.CommandPalette.Extensions;
using Microsoft.CommandPalette.Extensions.Toolkit;
using PowerToysExtension.Helpers;
+using PowerToysExtension.Properties;
namespace PowerToysExtension;
@@ -14,7 +15,7 @@ public sealed partial class PowerToysCommandsProvider : CommandProvider
{
public PowerToysCommandsProvider()
{
- DisplayName = "PowerToys";
+ DisplayName = Resources.PowerToys_DisplayName;
Icon = PowerToysResourcesHelper.IconFromSettingsIcon("PowerToys.png");
}
@@ -22,8 +23,8 @@ public sealed partial class PowerToysCommandsProvider : CommandProvider
[
new CommandItem(new Pages.PowerToysListPage())
{
- Title = "PowerToys",
- Subtitle = "PowerToys commands and settings",
+ Title = Resources.PowerToys_DisplayName,
+ Subtitle = Resources.PowerToys_Subtitle,
}
];
diff --git a/src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.PowerToys/PowerToysExtensionCommandsProvider.cs b/src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.PowerToys/PowerToysExtensionCommandsProvider.cs
index beba6b484a..a6c3dc727e 100644
--- a/src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.PowerToys/PowerToysExtensionCommandsProvider.cs
+++ b/src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.PowerToys/PowerToysExtensionCommandsProvider.cs
@@ -6,6 +6,7 @@ using System.Collections.Generic;
using Microsoft.CommandPalette.Extensions;
using Microsoft.CommandPalette.Extensions.Toolkit;
using PowerToysExtension.Helpers;
+using PowerToysExtension.Properties;
namespace PowerToysExtension;
@@ -15,13 +16,13 @@ public partial class PowerToysExtensionCommandsProvider : CommandProvider
public PowerToysExtensionCommandsProvider()
{
- DisplayName = "PowerToys";
+ DisplayName = Resources.PowerToys_DisplayName;
Icon = PowerToysResourcesHelper.IconFromSettingsIcon("PowerToys.png");
_commands = [
new CommandItem(new Pages.PowerToysListPage())
{
- Title = "PowerToys",
- Subtitle = "PowerToys commands and settings",
+ Title = Resources.PowerToys_DisplayName,
+ Subtitle = Resources.PowerToys_Subtitle,
},
];
}
diff --git a/src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.PowerToys/Properties/Resources.Designer.cs b/src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.PowerToys/Properties/Resources.Designer.cs
new file mode 100644
index 0000000000..d561fcd7a4
--- /dev/null
+++ b/src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.PowerToys/Properties/Resources.Designer.cs
@@ -0,0 +1,1521 @@
+//------------------------------------------------------------------------------
+//
+// This code was generated by a tool.
+// Runtime Version:4.0.30319.42000
+//
+// Changes to this file may cause incorrect behavior and will be lost if
+// the code is regenerated.
+//
+//------------------------------------------------------------------------------
+
+namespace PowerToysExtension.Properties {
+ using System;
+
+
+ ///
+ /// A strongly-typed resource class, for looking up localized strings, etc.
+ ///
+ // This class was auto-generated by the StronglyTypedResourceBuilder
+ // class via a tool like ResGen or Visual Studio.
+ // To add or remove a member, edit your .ResX file then rerun ResGen
+ // with the /str option, or rebuild your VS project.
+ [global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "17.0.0.0")]
+ [global::System.Diagnostics.DebuggerNonUserCodeAttribute()]
+ [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()]
+ internal class Resources {
+
+ private static global::System.Resources.ResourceManager resourceMan;
+
+ private static global::System.Globalization.CultureInfo resourceCulture;
+
+ [global::System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")]
+ internal Resources() {
+ }
+
+ ///
+ /// Returns the cached ResourceManager instance used by this class.
+ ///
+ [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)]
+ internal static global::System.Resources.ResourceManager ResourceManager {
+ get {
+ if (object.ReferenceEquals(resourceMan, null)) {
+ global::System.Resources.ResourceManager temp = new global::System.Resources.ResourceManager("PowerToysExtension.Properties.Resources", typeof(Resources).Assembly);
+ resourceMan = temp;
+ }
+ return resourceMan;
+ }
+ }
+
+ ///
+ /// Overrides the current thread's CurrentUICulture property for all
+ /// resource lookups using this strongly typed resource class.
+ ///
+ [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)]
+ internal static global::System.Globalization.CultureInfo Culture {
+ get {
+ return resourceCulture;
+ }
+ set {
+ resourceCulture = value;
+ }
+ }
+
+ ///
+ /// Looks up a localized string similar to Launch the Advanced Paste UI.
+ ///
+ internal static string AdvancedPaste_Open_Subtitle {
+ get {
+ return ResourceManager.GetString("AdvancedPaste_Open_Subtitle", resourceCulture);
+ }
+ }
+
+ ///
+ /// Looks up a localized string similar to Open Advanced Paste.
+ ///
+ internal static string AdvancedPaste_Open_Title {
+ get {
+ return ResourceManager.GetString("AdvancedPaste_Open_Title", resourceCulture);
+ }
+ }
+
+ ///
+ /// Looks up a localized string similar to Open Advanced Paste settings.
+ ///
+ internal static string AdvancedPaste_Settings_Subtitle {
+ get {
+ return ResourceManager.GetString("AdvancedPaste_Settings_Subtitle", resourceCulture);
+ }
+ }
+
+ ///
+ /// Looks up a localized string similar to Open Always On Top settings.
+ ///
+ internal static string AlwaysOnTop_Settings_Subtitle {
+ get {
+ return ResourceManager.GetString("AlwaysOnTop_Settings_Subtitle", resourceCulture);
+ }
+ }
+
+ ///
+ /// Looks up a localized string similar to Run Awake timed for 1 hour.
+ ///
+ internal static string Awake_Keep1Hour_Subtitle {
+ get {
+ return ResourceManager.GetString("Awake_Keep1Hour_Subtitle", resourceCulture);
+ }
+ }
+
+ ///
+ /// Looks up a localized string similar to Awake: Keep awake for 1 hour.
+ ///
+ internal static string Awake_Keep1Hour_Title {
+ get {
+ return ResourceManager.GetString("Awake_Keep1Hour_Title", resourceCulture);
+ }
+ }
+
+ ///
+ /// Looks up a localized string similar to Run Awake timed for 2 hours.
+ ///
+ internal static string Awake_Keep2Hours_Subtitle {
+ get {
+ return ResourceManager.GetString("Awake_Keep2Hours_Subtitle", resourceCulture);
+ }
+ }
+
+ ///
+ /// Looks up a localized string similar to Awake: Keep awake for 2 hours.
+ ///
+ internal static string Awake_Keep2Hours_Title {
+ get {
+ return ResourceManager.GetString("Awake_Keep2Hours_Title", resourceCulture);
+ }
+ }
+
+ ///
+ /// Looks up a localized string similar to Run Awake timed for 30 minutes.
+ ///
+ internal static string Awake_Keep30Min_Subtitle {
+ get {
+ return ResourceManager.GetString("Awake_Keep30Min_Subtitle", resourceCulture);
+ }
+ }
+
+ ///
+ /// Looks up a localized string similar to Awake: Keep awake for 30 minutes.
+ ///
+ internal static string Awake_Keep30Min_Title {
+ get {
+ return ResourceManager.GetString("Awake_Keep30Min_Title", resourceCulture);
+ }
+ }
+
+ ///
+ /// Looks up a localized string similar to Run Awake in indefinite mode.
+ ///
+ internal static string Awake_KeepIndefinite_Subtitle {
+ get {
+ return ResourceManager.GetString("Awake_KeepIndefinite_Subtitle", resourceCulture);
+ }
+ }
+
+ ///
+ /// Looks up a localized string similar to Awake: Keep awake indefinitely.
+ ///
+ internal static string Awake_KeepIndefinite_Title {
+ get {
+ return ResourceManager.GetString("Awake_KeepIndefinite_Title", resourceCulture);
+ }
+ }
+
+ ///
+ /// Looks up a localized string similar to Awake set for 1 hour.
+ ///
+ internal static string Awake_Set1Hour_Toast {
+ get {
+ return ResourceManager.GetString("Awake_Set1Hour_Toast", resourceCulture);
+ }
+ }
+
+ ///
+ /// Looks up a localized string similar to Awake set for 2 hours.
+ ///
+ internal static string Awake_Set2Hours_Toast {
+ get {
+ return ResourceManager.GetString("Awake_Set2Hours_Toast", resourceCulture);
+ }
+ }
+
+ ///
+ /// Looks up a localized string similar to Awake set for 30 minutes.
+ ///
+ internal static string Awake_Set30Min_Toast {
+ get {
+ return ResourceManager.GetString("Awake_Set30Min_Toast", resourceCulture);
+ }
+ }
+
+ ///
+ /// Looks up a localized string similar to Awake set to indefinite.
+ ///
+ internal static string Awake_SetIndefinite_Toast {
+ get {
+ return ResourceManager.GetString("Awake_SetIndefinite_Toast", resourceCulture);
+ }
+ }
+
+ ///
+ /// Looks up a localized string similar to Open Awake settings.
+ ///
+ internal static string Awake_Settings_Subtitle {
+ get {
+ return ResourceManager.GetString("Awake_Settings_Subtitle", resourceCulture);
+ }
+ }
+
+ ///
+ /// Looks up a localized string similar to Awake: Current status.
+ ///
+ internal static string Awake_Status_Title {
+ get {
+ return ResourceManager.GetString("Awake_Status_Title", resourceCulture);
+ }
+ }
+
+ ///
+ /// Looks up a localized string similar to Switch Awake back to Off.
+ ///
+ internal static string Awake_TurnOff_Subtitle {
+ get {
+ return ResourceManager.GetString("Awake_TurnOff_Subtitle", resourceCulture);
+ }
+ }
+
+ ///
+ /// Looks up a localized string similar to Awake: Turn off.
+ ///
+ internal static string Awake_TurnOff_Title {
+ get {
+ return ResourceManager.GetString("Awake_TurnOff_Title", resourceCulture);
+ }
+ }
+
+ ///
+ /// Looks up a localized string similar to No saved colors matching '{0}'.
+ ///
+ internal static string ColorPicker_NoMatchingSavedColors_Subtitle {
+ get {
+ return ResourceManager.GetString("ColorPicker_NoMatchingSavedColors_Subtitle", resourceCulture);
+ }
+ }
+
+ ///
+ /// Looks up a localized string similar to Pick a color first, then try again..
+ ///
+ internal static string ColorPicker_NoSavedColors_Subtitle {
+ get {
+ return ResourceManager.GetString("ColorPicker_NoSavedColors_Subtitle", resourceCulture);
+ }
+ }
+
+ ///
+ /// Looks up a localized string similar to No saved colors.
+ ///
+ internal static string ColorPicker_NoSavedColors_Title {
+ get {
+ return ResourceManager.GetString("ColorPicker_NoSavedColors_Title", resourceCulture);
+ }
+ }
+
+ ///
+ /// Looks up a localized string similar to Start a color pick session.
+ ///
+ internal static string ColorPicker_Open_Subtitle {
+ get {
+ return ResourceManager.GetString("ColorPicker_Open_Subtitle", resourceCulture);
+ }
+ }
+
+ ///
+ /// Looks up a localized string similar to Open Color Picker.
+ ///
+ internal static string ColorPicker_Open_Title {
+ get {
+ return ResourceManager.GetString("ColorPicker_Open_Title", resourceCulture);
+ }
+ }
+
+ ///
+ /// Looks up a localized string similar to Browse and copy saved colors.
+ ///
+ internal static string ColorPicker_SavedColors_Subtitle {
+ get {
+ return ResourceManager.GetString("ColorPicker_SavedColors_Subtitle", resourceCulture);
+ }
+ }
+
+ ///
+ /// Looks up a localized string similar to Saved colors.
+ ///
+ internal static string ColorPicker_SavedColors_Title {
+ get {
+ return ResourceManager.GetString("ColorPicker_SavedColors_Title", resourceCulture);
+ }
+ }
+
+ ///
+ /// Looks up a localized string similar to Open Color Picker settings.
+ ///
+ internal static string ColorPicker_Settings_Subtitle {
+ get {
+ return ResourceManager.GetString("ColorPicker_Settings_Subtitle", resourceCulture);
+ }
+ }
+
+ ///
+ /// Looks up a localized string similar to Open Command Not Found settings.
+ ///
+ internal static string CommandNotFound_Settings_Subtitle {
+ get {
+ return ResourceManager.GetString("CommandNotFound_Settings_Subtitle", resourceCulture);
+ }
+ }
+
+ ///
+ /// Looks up a localized string similar to n/a.
+ ///
+ internal static string Common_NotAvailable {
+ get {
+ return ResourceManager.GetString("Common_NotAvailable", resourceCulture);
+ }
+ }
+
+ ///
+ /// Looks up a localized string similar to Create a cropped reparented window.
+ ///
+ internal static string CropAndLock_Reparent_Subtitle {
+ get {
+ return ResourceManager.GetString("CropAndLock_Reparent_Subtitle", resourceCulture);
+ }
+ }
+
+ ///
+ /// Looks up a localized string similar to Crop and Lock (Reparent).
+ ///
+ internal static string CropAndLock_Reparent_Title {
+ get {
+ return ResourceManager.GetString("CropAndLock_Reparent_Title", resourceCulture);
+ }
+ }
+
+ ///
+ /// Looks up a localized string similar to Open Crop and Lock settings.
+ ///
+ internal static string CropAndLock_Settings_Subtitle {
+ get {
+ return ResourceManager.GetString("CropAndLock_Settings_Subtitle", resourceCulture);
+ }
+ }
+
+ ///
+ /// Looks up a localized string similar to Create a cropped thumbnail window.
+ ///
+ internal static string CropAndLock_Thumbnail_Subtitle {
+ get {
+ return ResourceManager.GetString("CropAndLock_Thumbnail_Subtitle", resourceCulture);
+ }
+ }
+
+ ///
+ /// Looks up a localized string similar to Crop and Lock (Thumbnail).
+ ///
+ internal static string CropAndLock_Thumbnail_Title {
+ get {
+ return ResourceManager.GetString("CropAndLock_Thumbnail_Title", resourceCulture);
+ }
+ }
+
+ ///
+ /// Looks up a localized string similar to Crop and Lock (Screenshot).
+ ///
+ internal static string CropAndLock_Screenshot_Title {
+ get {
+ return ResourceManager.GetString("CropAndLock_Screenshot_Title", resourceCulture);
+ }
+ }
+
+ ///
+ /// Looks up a localized string similar to Create a cropped screenshot window.
+ ///
+ internal static string CropAndLock_Screenshot_Subtitle {
+ get {
+ return ResourceManager.GetString("CropAndLock_Screenshot_Subtitle", resourceCulture);
+ }
+ }
+
+ ///
+ /// Looks up a localized string similar to Launch Environment Variables editor.
+ ///
+ internal static string EnvironmentVariables_Open_Subtitle {
+ get {
+ return ResourceManager.GetString("EnvironmentVariables_Open_Subtitle", resourceCulture);
+ }
+ }
+
+ ///
+ /// Looks up a localized string similar to Open Environment Variables.
+ ///
+ internal static string EnvironmentVariables_Open_Title {
+ get {
+ return ResourceManager.GetString("EnvironmentVariables_Open_Title", resourceCulture);
+ }
+ }
+
+ ///
+ /// Looks up a localized string similar to Launch Environment Variables editor as admin.
+ ///
+ internal static string EnvironmentVariables_OpenAdmin_Subtitle {
+ get {
+ return ResourceManager.GetString("EnvironmentVariables_OpenAdmin_Subtitle", resourceCulture);
+ }
+ }
+
+ ///
+ /// Looks up a localized string similar to Open Environment Variables (Admin).
+ ///
+ internal static string EnvironmentVariables_OpenAdmin_Title {
+ get {
+ return ResourceManager.GetString("EnvironmentVariables_OpenAdmin_Title", resourceCulture);
+ }
+ }
+
+ ///
+ /// Looks up a localized string similar to Open Environment Variables settings.
+ ///
+ internal static string EnvironmentVariables_Settings_Subtitle {
+ get {
+ return ResourceManager.GetString("EnvironmentVariables_Settings_Subtitle", resourceCulture);
+ }
+ }
+
+ ///
+ /// Looks up a localized string similar to Apply to {0}.
+ ///
+ internal static string FancyZones_ApplyTo_Format {
+ get {
+ return ResourceManager.GetString("FancyZones_ApplyTo_Format", resourceCulture);
+ }
+ }
+
+ ///
+ /// Looks up a localized string similar to Current layout: {0}.
+ ///
+ internal static string FancyZones_CurrentLayout_Format {
+ get {
+ return ResourceManager.GetString("FancyZones_CurrentLayout_Format", resourceCulture);
+ }
+ }
+
+ ///
+ /// Looks up a localized string similar to Current layout: unknown.
+ ///
+ internal static string FancyZones_CurrentLayout_Unknown {
+ get {
+ return ResourceManager.GetString("FancyZones_CurrentLayout_Unknown", resourceCulture);
+ }
+ }
+
+ ///
+ /// Looks up a localized string similar to Custom • {0} zones.
+ ///
+ internal static string FancyZones_Custom_Zones_Format {
+ get {
+ return ResourceManager.GetString("FancyZones_Custom_Zones_Format", resourceCulture);
+ }
+ }
+
+ ///
+ /// Looks up a localized string similar to Custom canvas • {0} zones.
+ ///
+ internal static string FancyZones_CustomCanvas_Zones_Format {
+ get {
+ return ResourceManager.GetString("FancyZones_CustomCanvas_Zones_Format", resourceCulture);
+ }
+ }
+
+ ///
+ /// Looks up a localized string similar to Custom grid • {0} zones.
+ ///
+ internal static string FancyZones_CustomGrid_Zones_Format {
+ get {
+ return ResourceManager.GetString("FancyZones_CustomGrid_Zones_Format", resourceCulture);
+ }
+ }
+
+ ///
+ /// Looks up a localized string similar to DPI.
+ ///
+ internal static string FancyZones_DPI {
+ get {
+ return ResourceManager.GetString("FancyZones_DPI", resourceCulture);
+ }
+ }
+
+ ///
+ /// Looks up a localized string similar to Instance.
+ ///
+ internal static string FancyZones_Instance {
+ get {
+ return ResourceManager.GetString("FancyZones_Instance", resourceCulture);
+ }
+ }
+
+ ///
+ /// Looks up a localized string similar to Layout applied..
+ ///
+ internal static string FancyZones_LayoutApplied {
+ get {
+ return ResourceManager.GetString("FancyZones_LayoutApplied", resourceCulture);
+ }
+ }
+
+ ///
+ /// Looks up a localized string similar to Layout applied, but FancyZones could not be notified: {0}.
+ ///
+ internal static string FancyZones_LayoutAppliedNotifyFailed_Format {
+ get {
+ return ResourceManager.GetString("FancyZones_LayoutAppliedNotifyFailed_Format", resourceCulture);
+ }
+ }
+
+ ///
+ /// Looks up a localized string similar to Apply a layout to all monitors or a specific monitor.
+ ///
+ internal static string FancyZones_Layouts_Subtitle {
+ get {
+ return ResourceManager.GetString("FancyZones_Layouts_Subtitle", resourceCulture);
+ }
+ }
+
+ ///
+ /// Looks up a localized string similar to FancyZones: Layouts.
+ ///
+ internal static string FancyZones_Layouts_Title {
+ get {
+ return ResourceManager.GetString("FancyZones_Layouts_Title", resourceCulture);
+ }
+ }
+
+ ///
+ /// Looks up a localized string similar to FancyZones Layouts.
+ ///
+ internal static string FancyZones_LayoutsPage_Title {
+ get {
+ return ResourceManager.GetString("FancyZones_LayoutsPage_Title", resourceCulture);
+ }
+ }
+
+ ///
+ /// Looks up a localized string similar to Monitor.
+ ///
+ internal static string FancyZones_Monitor {
+ get {
+ return ResourceManager.GetString("FancyZones_Monitor", resourceCulture);
+ }
+ }
+
+ ///
+ /// Looks up a localized string similar to FancyZones monitor data not found. Open FancyZones Editor once to initialize..
+ ///
+ internal static string FancyZones_MonitorDataNotFound {
+ get {
+ return ResourceManager.GetString("FancyZones_MonitorDataNotFound", resourceCulture);
+ }
+ }
+
+ ///
+ /// Looks up a localized string similar to Identify monitors and apply layouts.
+ ///
+ internal static string FancyZones_Monitors_Subtitle {
+ get {
+ return ResourceManager.GetString("FancyZones_Monitors_Subtitle", resourceCulture);
+ }
+ }
+
+ ///
+ /// Looks up a localized string similar to FancyZones: Monitors.
+ ///
+ internal static string FancyZones_Monitors_Title {
+ get {
+ return ResourceManager.GetString("FancyZones_Monitors_Title", resourceCulture);
+ }
+ }
+
+ ///
+ /// Looks up a localized string similar to FancyZones Monitors.
+ ///
+ internal static string FancyZones_MonitorsPage_Title {
+ get {
+ return ResourceManager.GetString("FancyZones_MonitorsPage_Title", resourceCulture);
+ }
+ }
+
+ ///
+ /// Looks up a localized string similar to No FancyZones monitors found..
+ ///
+ internal static string FancyZones_NoFancyZonesMonitorsFound {
+ get {
+ return ResourceManager.GetString("FancyZones_NoFancyZonesMonitorsFound", resourceCulture);
+ }
+ }
+
+ ///
+ /// Looks up a localized string similar to Open FancyZones Editor once to initialize layouts..
+ ///
+ internal static string FancyZones_NoLayoutsFound_Subtitle {
+ get {
+ return ResourceManager.GetString("FancyZones_NoLayoutsFound_Subtitle", resourceCulture);
+ }
+ }
+
+ ///
+ /// Looks up a localized string similar to No layouts found.
+ ///
+ internal static string FancyZones_NoLayoutsFound_Title {
+ get {
+ return ResourceManager.GetString("FancyZones_NoLayoutsFound_Title", resourceCulture);
+ }
+ }
+
+ ///
+ /// Looks up a localized string similar to Open FancyZones Editor once to initialize monitor data..
+ ///
+ internal static string FancyZones_NoMonitorsFound_Subtitle {
+ get {
+ return ResourceManager.GetString("FancyZones_NoMonitorsFound_Subtitle", resourceCulture);
+ }
+ }
+
+ ///
+ /// Looks up a localized string similar to No monitors found.
+ ///
+ internal static string FancyZones_NoMonitorsFound_Title {
+ get {
+ return ResourceManager.GetString("FancyZones_NoMonitorsFound_Title", resourceCulture);
+ }
+ }
+
+ ///
+ /// Looks up a localized string similar to Number.
+ ///
+ internal static string FancyZones_Number {
+ get {
+ return ResourceManager.GetString("FancyZones_Number", resourceCulture);
+ }
+ }
+
+ ///
+ /// Looks up a localized string similar to Launch layout editor.
+ ///
+ internal static string FancyZones_OpenEditor_Subtitle {
+ get {
+ return ResourceManager.GetString("FancyZones_OpenEditor_Subtitle", resourceCulture);
+ }
+ }
+
+ ///
+ /// Looks up a localized string similar to Open FancyZones Editor.
+ ///
+ internal static string FancyZones_OpenEditor_Title {
+ get {
+ return ResourceManager.GetString("FancyZones_OpenEditor_Title", resourceCulture);
+ }
+ }
+
+ ///
+ /// Looks up a localized string similar to Pick a layout for this monitor.
+ ///
+ internal static string FancyZones_PickLayoutForMonitor {
+ get {
+ return ResourceManager.GetString("FancyZones_PickLayoutForMonitor", resourceCulture);
+ }
+ }
+
+ ///
+ /// Looks up a localized string similar to Failed to read FancyZones monitor data: {0}.
+ ///
+ internal static string FancyZones_ReadMonitorDataFailed_Format {
+ get {
+ return ResourceManager.GetString("FancyZones_ReadMonitorDataFailed_Format", resourceCulture);
+ }
+ }
+
+ ///
+ /// Looks up a localized string similar to Resolution.
+ ///
+ internal static string FancyZones_Resolution {
+ get {
+ return ResourceManager.GetString("FancyZones_Resolution", resourceCulture);
+ }
+ }
+
+ ///
+ /// Looks up a localized string similar to Serial.
+ ///
+ internal static string FancyZones_Serial {
+ get {
+ return ResourceManager.GetString("FancyZones_Serial", resourceCulture);
+ }
+ }
+
+ ///
+ /// Looks up a localized string similar to Set active layout.
+ ///
+ internal static string FancyZones_SetActiveLayout {
+ get {
+ return ResourceManager.GetString("FancyZones_SetActiveLayout", resourceCulture);
+ }
+ }
+
+ ///
+ /// Looks up a localized string similar to Set active layout for {0}.
+ ///
+ internal static string FancyZones_SetActiveLayoutFor_Format {
+ get {
+ return ResourceManager.GetString("FancyZones_SetActiveLayoutFor_Format", resourceCulture);
+ }
+ }
+
+ ///
+ /// Looks up a localized string similar to Open FancyZones settings.
+ ///
+ internal static string FancyZones_Settings_Subtitle {
+ get {
+ return ResourceManager.GetString("FancyZones_Settings_Subtitle", resourceCulture);
+ }
+ }
+
+ ///
+ /// Looks up a localized string similar to Template: {0}.
+ ///
+ internal static string FancyZones_Template_Format {
+ get {
+ return ResourceManager.GetString("FancyZones_Template_Format", resourceCulture);
+ }
+ }
+
+ ///
+ /// Looks up a localized string similar to Virtual desktop.
+ ///
+ internal static string FancyZones_VirtualDesktop {
+ get {
+ return ResourceManager.GetString("FancyZones_VirtualDesktop", resourceCulture);
+ }
+ }
+
+ ///
+ /// Looks up a localized string similar to Work area.
+ ///
+ internal static string FancyZones_WorkArea {
+ get {
+ return ResourceManager.GetString("FancyZones_WorkArea", resourceCulture);
+ }
+ }
+
+ ///
+ /// Looks up a localized string similar to Failed to write applied layouts: {0}.
+ ///
+ internal static string FancyZones_WriteAppliedLayoutsFailed_Format {
+ get {
+ return ResourceManager.GetString("FancyZones_WriteAppliedLayoutsFailed_Format", resourceCulture);
+ }
+ }
+
+ ///
+ /// Looks up a localized string similar to {0} zones.
+ ///
+ internal static string FancyZones_Zones_Format {
+ get {
+ return ResourceManager.GetString("FancyZones_Zones_Format", resourceCulture);
+ }
+ }
+
+ ///
+ /// Looks up a localized string similar to Open File Explorer add-ons settings.
+ ///
+ internal static string FileExplorerAddons_Settings_Subtitle {
+ get {
+ return ResourceManager.GetString("FileExplorerAddons_Settings_Subtitle", resourceCulture);
+ }
+ }
+
+ ///
+ /// Looks up a localized string similar to Open File Locksmith settings.
+ ///
+ internal static string FileLocksmith_Settings_Subtitle {
+ get {
+ return ResourceManager.GetString("FileLocksmith_Settings_Subtitle", resourceCulture);
+ }
+ }
+
+ ///
+ /// Looks up a localized string similar to Launch Hosts File Editor.
+ ///
+ internal static string Hosts_Open_Subtitle {
+ get {
+ return ResourceManager.GetString("Hosts_Open_Subtitle", resourceCulture);
+ }
+ }
+
+ ///
+ /// Looks up a localized string similar to Open Hosts File Editor.
+ ///
+ internal static string Hosts_Open_Title {
+ get {
+ return ResourceManager.GetString("Hosts_Open_Title", resourceCulture);
+ }
+ }
+
+ ///
+ /// Looks up a localized string similar to Launch Hosts File Editor as admin.
+ ///
+ internal static string Hosts_OpenAdmin_Subtitle {
+ get {
+ return ResourceManager.GetString("Hosts_OpenAdmin_Subtitle", resourceCulture);
+ }
+ }
+
+ ///
+ /// Looks up a localized string similar to Open Hosts File Editor (Admin).
+ ///
+ internal static string Hosts_OpenAdmin_Title {
+ get {
+ return ResourceManager.GetString("Hosts_OpenAdmin_Title", resourceCulture);
+ }
+ }
+
+ ///
+ /// Looks up a localized string similar to Open Hosts File Editor settings.
+ ///
+ internal static string Hosts_Settings_Subtitle {
+ get {
+ return ResourceManager.GetString("Hosts_Settings_Subtitle", resourceCulture);
+ }
+ }
+
+ ///
+ /// Looks up a localized string similar to Open Image Resizer settings.
+ ///
+ internal static string ImageResizer_Settings_Subtitle {
+ get {
+ return ResourceManager.GetString("ImageResizer_Settings_Subtitle", resourceCulture);
+ }
+ }
+
+ ///
+ /// Looks up a localized string similar to Open Keyboard Manager settings.
+ ///
+ internal static string KeyboardManager_Settings_Subtitle {
+ get {
+ return ResourceManager.GetString("KeyboardManager_Settings_Subtitle", resourceCulture);
+ }
+ }
+
+ ///
+ /// Looks up a localized string similar to Open Light Switch settings.
+ ///
+ internal static string LightSwitch_Settings_Subtitle {
+ get {
+ return ResourceManager.GetString("LightSwitch_Settings_Subtitle", resourceCulture);
+ }
+ }
+
+ ///
+ /// Looks up a localized string similar to Toggle system/apps theme immediately.
+ ///
+ internal static string LightSwitch_Toggle_Subtitle {
+ get {
+ return ResourceManager.GetString("LightSwitch_Toggle_Subtitle", resourceCulture);
+ }
+ }
+
+ ///
+ /// Looks up a localized string similar to Light Switch: Toggle theme.
+ ///
+ internal static string LightSwitch_Toggle_Title {
+ get {
+ return ResourceManager.GetString("LightSwitch_Toggle_Title", resourceCulture);
+ }
+ }
+
+ ///
+ /// Looks up a localized string similar to Enable or disable pointer crosshairs.
+ ///
+ internal static string MouseUtils_Crosshairs_Subtitle {
+ get {
+ return ResourceManager.GetString("MouseUtils_Crosshairs_Subtitle", resourceCulture);
+ }
+ }
+
+ ///
+ /// Looks up a localized string similar to Toggle Mouse Crosshairs.
+ ///
+ internal static string MouseUtils_Crosshairs_Title {
+ get {
+ return ResourceManager.GetString("MouseUtils_Crosshairs_Title", resourceCulture);
+ }
+ }
+
+ ///
+ /// Looks up a localized string similar to Wrap the cursor across monitor edges.
+ ///
+ internal static string MouseUtils_CursorWrap_Subtitle {
+ get {
+ return ResourceManager.GetString("MouseUtils_CursorWrap_Subtitle", resourceCulture);
+ }
+ }
+
+ ///
+ /// Looks up a localized string similar to Toggle Cursor Wrap.
+ ///
+ internal static string MouseUtils_CursorWrap_Title {
+ get {
+ return ResourceManager.GetString("MouseUtils_CursorWrap_Title", resourceCulture);
+ }
+ }
+
+ ///
+ /// Looks up a localized string similar to Focus the mouse pointer.
+ ///
+ internal static string MouseUtils_FindMyMouse_Subtitle {
+ get {
+ return ResourceManager.GetString("MouseUtils_FindMyMouse_Subtitle", resourceCulture);
+ }
+ }
+
+ ///
+ /// Looks up a localized string similar to Trigger Find My Mouse.
+ ///
+ internal static string MouseUtils_FindMyMouse_Title {
+ get {
+ return ResourceManager.GetString("MouseUtils_FindMyMouse_Title", resourceCulture);
+ }
+ }
+
+ ///
+ /// Looks up a localized string similar to Highlight mouse clicks.
+ ///
+ internal static string MouseUtils_Highlighter_Subtitle {
+ get {
+ return ResourceManager.GetString("MouseUtils_Highlighter_Subtitle", resourceCulture);
+ }
+ }
+
+ ///
+ /// Looks up a localized string similar to Toggle Mouse Highlighter.
+ ///
+ internal static string MouseUtils_Highlighter_Title {
+ get {
+ return ResourceManager.GetString("MouseUtils_Highlighter_Title", resourceCulture);
+ }
+ }
+
+ ///
+ /// Looks up a localized string similar to Jump the pointer to a target.
+ ///
+ internal static string MouseUtils_MouseJump_Subtitle {
+ get {
+ return ResourceManager.GetString("MouseUtils_MouseJump_Subtitle", resourceCulture);
+ }
+ }
+
+ ///
+ /// Looks up a localized string similar to Show Mouse Jump Preview.
+ ///
+ internal static string MouseUtils_MouseJump_Title {
+ get {
+ return ResourceManager.GetString("MouseUtils_MouseJump_Title", resourceCulture);
+ }
+ }
+
+ ///
+ /// Looks up a localized string similar to Open Mouse Utilities settings.
+ ///
+ internal static string MouseUtils_Settings_Subtitle {
+ get {
+ return ResourceManager.GetString("MouseUtils_Settings_Subtitle", resourceCulture);
+ }
+ }
+
+ ///
+ /// Looks up a localized string similar to Open Mouse Without Borders settings.
+ ///
+ internal static string MouseWithoutBorders_Settings_Subtitle {
+ get {
+ return ResourceManager.GetString("MouseWithoutBorders_Settings_Subtitle", resourceCulture);
+ }
+ }
+
+ ///
+ /// Looks up a localized string similar to Open New+ settings.
+ ///
+ internal static string NewPlus_Settings_Subtitle {
+ get {
+ return ResourceManager.GetString("NewPlus_Settings_Subtitle", resourceCulture);
+ }
+ }
+
+ ///
+ /// Looks up a localized string similar to Open Peek settings.
+ ///
+ internal static string Peek_Settings_Subtitle {
+ get {
+ return ResourceManager.GetString("Peek_Settings_Subtitle", resourceCulture);
+ }
+ }
+
+ ///
+ /// Looks up a localized string similar to Open PowerRename settings.
+ ///
+ internal static string PowerRename_Settings_Subtitle {
+ get {
+ return ResourceManager.GetString("PowerRename_Settings_Subtitle", resourceCulture);
+ }
+ }
+
+ ///
+ /// Looks up a localized string similar to PowerToys.
+ ///
+ internal static string PowerToys_DisplayName {
+ get {
+ return ResourceManager.GetString("PowerToys_DisplayName", resourceCulture);
+ }
+ }
+
+ ///
+ /// Looks up a localized string similar to No matching module found.
+ ///
+ internal static string PowerToys_NoMatchingModule {
+ get {
+ return ResourceManager.GetString("PowerToys_NoMatchingModule", resourceCulture);
+ }
+ }
+
+ ///
+ /// Looks up a localized string similar to PowerToys commands and settings.
+ ///
+ internal static string PowerToys_Subtitle {
+ get {
+ return ResourceManager.GetString("PowerToys_Subtitle", resourceCulture);
+ }
+ }
+
+ ///
+ /// Looks up a localized string similar to PowerToys commands.
+ ///
+ internal static string PowerToysExtension_CommandsName {
+ get {
+ return ResourceManager.GetString("PowerToysExtension_CommandsName", resourceCulture);
+ }
+ }
+
+ ///
+ /// Looks up a localized string similar to Launch the PowerToys shell.
+ ///
+ internal static string PowerToysExtension_OpenPowerToys_Subtitle {
+ get {
+ return ResourceManager.GetString("PowerToysExtension_OpenPowerToys_Subtitle", resourceCulture);
+ }
+ }
+
+ ///
+ /// Looks up a localized string similar to Open PowerToys.
+ ///
+ internal static string PowerToysExtension_OpenPowerToys_Title {
+ get {
+ return ResourceManager.GetString("PowerToysExtension_OpenPowerToys_Title", resourceCulture);
+ }
+ }
+
+ ///
+ /// Looks up a localized string similar to Open the main PowerToys settings window.
+ ///
+ internal static string PowerToysExtension_OpenSettings_Subtitle {
+ get {
+ return ResourceManager.GetString("PowerToysExtension_OpenSettings_Subtitle", resourceCulture);
+ }
+ }
+
+ ///
+ /// Looks up a localized string similar to Open PowerToys settings.
+ ///
+ internal static string PowerToysExtension_OpenSettings_Title {
+ get {
+ return ResourceManager.GetString("PowerToysExtension_OpenSettings_Title", resourceCulture);
+ }
+ }
+
+ ///
+ /// Looks up a localized string similar to Launch the Workspaces editor.
+ ///
+ internal static string PowerToysExtension_OpenWorkspacesEditor_Subtitle {
+ get {
+ return ResourceManager.GetString("PowerToysExtension_OpenWorkspacesEditor_Subtitle", resourceCulture);
+ }
+ }
+
+ ///
+ /// Looks up a localized string similar to Open Workspaces editor.
+ ///
+ internal static string PowerToysExtension_OpenWorkspacesEditor_Title {
+ get {
+ return ResourceManager.GetString("PowerToysExtension_OpenWorkspacesEditor_Title", resourceCulture);
+ }
+ }
+
+ ///
+ /// Looks up a localized string similar to Jump directly to Workspaces settings.
+ ///
+ internal static string PowerToysExtension_OpenWorkspacesSettings_Subtitle {
+ get {
+ return ResourceManager.GetString("PowerToysExtension_OpenWorkspacesSettings_Subtitle", resourceCulture);
+ }
+ }
+
+ ///
+ /// Looks up a localized string similar to Open Workspaces settings.
+ ///
+ internal static string PowerToysExtension_OpenWorkspacesSettings_Title {
+ get {
+ return ResourceManager.GetString("PowerToysExtension_OpenWorkspacesSettings_Title", resourceCulture);
+ }
+ }
+
+ ///
+ /// Looks up a localized string similar to Open PowerToys Run settings.
+ ///
+ internal static string PowerToysRun_Settings_Subtitle {
+ get {
+ return ResourceManager.GetString("PowerToysRun_Settings_Subtitle", resourceCulture);
+ }
+ }
+
+ ///
+ /// Looks up a localized string similar to Open Quick Accent settings.
+ ///
+ internal static string QuickAccent_Settings_Subtitle {
+ get {
+ return ResourceManager.GetString("QuickAccent_Settings_Subtitle", resourceCulture);
+ }
+ }
+
+ ///
+ /// Looks up a localized string similar to Launch Registry Preview.
+ ///
+ internal static string RegistryPreview_Open_Subtitle {
+ get {
+ return ResourceManager.GetString("RegistryPreview_Open_Subtitle", resourceCulture);
+ }
+ }
+
+ ///
+ /// Looks up a localized string similar to Open Registry Preview.
+ ///
+ internal static string RegistryPreview_Open_Title {
+ get {
+ return ResourceManager.GetString("RegistryPreview_Open_Title", resourceCulture);
+ }
+ }
+
+ ///
+ /// Looks up a localized string similar to Open Registry Preview settings.
+ ///
+ internal static string RegistryPreview_Settings_Subtitle {
+ get {
+ return ResourceManager.GetString("RegistryPreview_Settings_Subtitle", resourceCulture);
+ }
+ }
+
+ ///
+ /// Looks up a localized string similar to Open Screen Ruler settings.
+ ///
+ internal static string ScreenRuler_Settings_Subtitle {
+ get {
+ return ResourceManager.GetString("ScreenRuler_Settings_Subtitle", resourceCulture);
+ }
+ }
+
+ ///
+ /// Looks up a localized string similar to Start or close Screen Ruler.
+ ///
+ internal static string ScreenRuler_Toggle_Subtitle {
+ get {
+ return ResourceManager.GetString("ScreenRuler_Toggle_Subtitle", resourceCulture);
+ }
+ }
+
+ ///
+ /// Looks up a localized string similar to Toggle Screen Ruler.
+ ///
+ internal static string ScreenRuler_Toggle_Title {
+ get {
+ return ResourceManager.GetString("ScreenRuler_Toggle_Title", resourceCulture);
+ }
+ }
+
+ ///
+ /// Looks up a localized string similar to Open Shortcut Guide settings.
+ ///
+ internal static string ShortcutGuide_Settings_Subtitle {
+ get {
+ return ResourceManager.GetString("ShortcutGuide_Settings_Subtitle", resourceCulture);
+ }
+ }
+
+ ///
+ /// Looks up a localized string similar to Show or hide Shortcut Guide.
+ ///
+ internal static string ShortcutGuide_Toggle_Subtitle {
+ get {
+ return ResourceManager.GetString("ShortcutGuide_Toggle_Subtitle", resourceCulture);
+ }
+ }
+
+ ///
+ /// Looks up a localized string similar to Toggle Shortcut Guide.
+ ///
+ internal static string ShortcutGuide_Toggle_Title {
+ get {
+ return ResourceManager.GetString("ShortcutGuide_Toggle_Title", resourceCulture);
+ }
+ }
+
+ ///
+ /// Looks up a localized string similar to Open Text Extractor settings.
+ ///
+ internal static string TextExtractor_Settings_Subtitle {
+ get {
+ return ResourceManager.GetString("TextExtractor_Settings_Subtitle", resourceCulture);
+ }
+ }
+
+ ///
+ /// Looks up a localized string similar to Start or close Text Extractor.
+ ///
+ internal static string TextExtractor_Toggle_Subtitle {
+ get {
+ return ResourceManager.GetString("TextExtractor_Toggle_Subtitle", resourceCulture);
+ }
+ }
+
+ ///
+ /// Looks up a localized string similar to Toggle Text Extractor.
+ ///
+ internal static string TextExtractor_Toggle_Title {
+ get {
+ return ResourceManager.GetString("TextExtractor_Toggle_Title", resourceCulture);
+ }
+ }
+
+ ///
+ /// Looks up a localized string similar to App.
+ ///
+ internal static string Workspaces_App {
+ get {
+ return ResourceManager.GetString("Workspaces_App", resourceCulture);
+ }
+ }
+
+ ///
+ /// Looks up a localized string similar to {0} applications.
+ ///
+ internal static string Workspaces_Applications_Format {
+ get {
+ return ResourceManager.GetString("Workspaces_Applications_Format", resourceCulture);
+ }
+ }
+
+ ///
+ /// Looks up a localized string similar to {0} applications.
+ ///
+ internal static string Workspaces_ApplicationsCount_Format {
+ get {
+ return ResourceManager.GetString("Workspaces_ApplicationsCount_Format", resourceCulture);
+ }
+ }
+
+ ///
+ /// Looks up a localized string similar to {0} days ago.
+ ///
+ internal static string Workspaces_DaysAgo_Format {
+ get {
+ return ResourceManager.GetString("Workspaces_DaysAgo_Format", resourceCulture);
+ }
+ }
+
+ ///
+ /// Looks up a localized string similar to {0} hr ago.
+ ///
+ internal static string Workspaces_HrAgo_Format {
+ get {
+ return ResourceManager.GetString("Workspaces_HrAgo_Format", resourceCulture);
+ }
+ }
+
+ ///
+ /// Looks up a localized string similar to just now.
+ ///
+ internal static string Workspaces_JustNow {
+ get {
+ return ResourceManager.GetString("Workspaces_JustNow", resourceCulture);
+ }
+ }
+
+ ///
+ /// Looks up a localized string similar to Last launched {0}.
+ ///
+ internal static string Workspaces_LastLaunched_Format {
+ get {
+ return ResourceManager.GetString("Workspaces_LastLaunched_Format", resourceCulture);
+ }
+ }
+
+ ///
+ /// Looks up a localized string similar to {0} min ago.
+ ///
+ internal static string Workspaces_MinAgo_Format {
+ get {
+ return ResourceManager.GetString("Workspaces_MinAgo_Format", resourceCulture);
+ }
+ }
+
+ ///
+ /// Looks up a localized string similar to Never launched.
+ ///
+ internal static string Workspaces_NeverLaunched {
+ get {
+ return ResourceManager.GetString("Workspaces_NeverLaunched", resourceCulture);
+ }
+ }
+
+ ///
+ /// Looks up a localized string similar to No applications.
+ ///
+ internal static string Workspaces_NoApplications {
+ get {
+ return ResourceManager.GetString("Workspaces_NoApplications", resourceCulture);
+ }
+ }
+
+ ///
+ /// Looks up a localized string similar to No applications in this workspace.
+ ///
+ internal static string Workspaces_NoApplicationsInWorkspace {
+ get {
+ return ResourceManager.GetString("Workspaces_NoApplicationsInWorkspace", resourceCulture);
+ }
+ }
+
+ ///
+ /// Looks up a localized string similar to 1 application.
+ ///
+ internal static string Workspaces_OneApplication {
+ get {
+ return ResourceManager.GetString("Workspaces_OneApplication", resourceCulture);
+ }
+ }
+
+ ///
+ /// Looks up a localized string similar to Create or edit workspaces.
+ ///
+ internal static string Workspaces_OpenEditor_Subtitle {
+ get {
+ return ResourceManager.GetString("Workspaces_OpenEditor_Subtitle", resourceCulture);
+ }
+ }
+
+ ///
+ /// Looks up a localized string similar to Workspaces: Open editor.
+ ///
+ internal static string Workspaces_OpenEditor_Title {
+ get {
+ return ResourceManager.GetString("Workspaces_OpenEditor_Title", resourceCulture);
+ }
+ }
+
+ ///
+ /// Looks up a localized string similar to Open Workspaces settings.
+ ///
+ internal static string Workspaces_Settings_Subtitle {
+ get {
+ return ResourceManager.GetString("Workspaces_Settings_Subtitle", resourceCulture);
+ }
+ }
+
+ ///
+ /// Looks up a localized string similar to Workspace.
+ ///
+ internal static string Workspaces_Workspace {
+ get {
+ return ResourceManager.GetString("Workspaces_Workspace", resourceCulture);
+ }
+ }
+
+ ///
+ /// Looks up a localized string similar to Enter break timer.
+ ///
+ internal static string ZoomIt_Break_Subtitle {
+ get {
+ return ResourceManager.GetString("ZoomIt_Break_Subtitle", resourceCulture);
+ }
+ }
+
+ ///
+ /// Looks up a localized string similar to ZoomIt: Break.
+ ///
+ internal static string ZoomIt_Break_Title {
+ get {
+ return ResourceManager.GetString("ZoomIt_Break_Title", resourceCulture);
+ }
+ }
+
+ ///
+ /// Looks up a localized string similar to Enter drawing mode.
+ ///
+ internal static string ZoomIt_Draw_Subtitle {
+ get {
+ return ResourceManager.GetString("ZoomIt_Draw_Subtitle", resourceCulture);
+ }
+ }
+
+ ///
+ /// Looks up a localized string similar to ZoomIt: Draw.
+ ///
+ internal static string ZoomIt_Draw_Title {
+ get {
+ return ResourceManager.GetString("ZoomIt_Draw_Title", resourceCulture);
+ }
+ }
+
+ ///
+ /// Looks up a localized string similar to Toggle live zoom.
+ ///
+ internal static string ZoomIt_LiveZoom_Subtitle {
+ get {
+ return ResourceManager.GetString("ZoomIt_LiveZoom_Subtitle", resourceCulture);
+ }
+ }
+
+ ///
+ /// Looks up a localized string similar to ZoomIt: Live Zoom.
+ ///
+ internal static string ZoomIt_LiveZoom_Title {
+ get {
+ return ResourceManager.GetString("ZoomIt_LiveZoom_Title", resourceCulture);
+ }
+ }
+
+ ///
+ /// Looks up a localized string similar to Start recording.
+ ///
+ internal static string ZoomIt_Record_Subtitle {
+ get {
+ return ResourceManager.GetString("ZoomIt_Record_Subtitle", resourceCulture);
+ }
+ }
+
+ ///
+ /// Looks up a localized string similar to ZoomIt: Record.
+ ///
+ internal static string ZoomIt_Record_Title {
+ get {
+ return ResourceManager.GetString("ZoomIt_Record_Title", resourceCulture);
+ }
+ }
+
+ ///
+ /// Looks up a localized string similar to Open ZoomIt settings.
+ ///
+ internal static string ZoomIt_Settings_Subtitle {
+ get {
+ return ResourceManager.GetString("ZoomIt_Settings_Subtitle", resourceCulture);
+ }
+ }
+
+ ///
+ /// Looks up a localized string similar to Enter snip mode.
+ ///
+ internal static string ZoomIt_Snip_Subtitle {
+ get {
+ return ResourceManager.GetString("ZoomIt_Snip_Subtitle", resourceCulture);
+ }
+ }
+
+ ///
+ /// Looks up a localized string similar to ZoomIt: Snip.
+ ///
+ internal static string ZoomIt_Snip_Title {
+ get {
+ return ResourceManager.GetString("ZoomIt_Snip_Title", resourceCulture);
+ }
+ }
+
+ ///
+ /// Looks up a localized string similar to Enter zoom mode.
+ ///
+ internal static string ZoomIt_Zoom_Subtitle {
+ get {
+ return ResourceManager.GetString("ZoomIt_Zoom_Subtitle", resourceCulture);
+ }
+ }
+
+ ///
+ /// Looks up a localized string similar to ZoomIt: Zoom.
+ ///
+ internal static string ZoomIt_Zoom_Title {
+ get {
+ return ResourceManager.GetString("ZoomIt_Zoom_Title", resourceCulture);
+ }
+ }
+ }
+}
diff --git a/src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.PowerToys/Properties/Resources.resx b/src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.PowerToys/Properties/Resources.resx
new file mode 100644
index 0000000000..acd99a55fc
--- /dev/null
+++ b/src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.PowerToys/Properties/Resources.resx
@@ -0,0 +1,636 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ text/microsoft-resx
+
+
+ 2.0
+
+
+ System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089
+
+
+ System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089
+
+
+
+ PowerToys
+
+
+ PowerToys commands and settings
+
+
+ No matching module found
+
+
+
+ PowerToys commands
+
+
+ Open PowerToys
+
+
+ Launch the PowerToys shell
+
+
+ Open PowerToys settings
+
+
+ Open the main PowerToys settings window
+
+
+ Open Workspaces settings
+
+
+ Jump directly to Workspaces settings
+
+
+ Open Workspaces editor
+
+
+ Launch the Workspaces editor
+
+
+
+ Open Advanced Paste
+
+
+ Launch the Advanced Paste UI
+
+
+ Open Advanced Paste settings
+
+
+
+ Open Always On Top settings
+
+
+
+ Open Awake settings
+
+
+ Awake: Current status
+
+
+ Awake: Keep awake indefinitely
+
+
+ Run Awake in indefinite mode
+
+
+ Awake: Keep awake for 30 minutes
+
+
+ Run Awake timed for 30 minutes
+
+
+ Awake: Keep awake for 1 hour
+
+
+ Run Awake timed for 1 hour
+
+
+ Awake: Keep awake for 2 hours
+
+
+ Run Awake timed for 2 hours
+
+
+ Awake: Turn off
+
+
+ Switch Awake back to Off
+
+
+ Awake set to indefinite
+
+
+ Awake set for 30 minutes
+
+
+ Awake set for 1 hour
+
+
+ Awake set for 2 hours
+
+
+
+ Open Color Picker settings
+
+
+ Open Color Picker
+
+
+ Start a color pick session
+
+
+ Saved colors
+
+
+ Browse and copy saved colors
+
+
+ No saved colors
+
+
+ Pick a color first, then try again.
+
+
+ No saved colors matching '{0}'
+
+
+
+ Open Command Not Found settings
+
+
+
+ Crop and Lock (Reparent)
+
+
+ Create a cropped reparented window
+
+
+ Crop and Lock (Thumbnail)
+
+
+ Create a cropped thumbnail window
+
+
+ Crop and Lock (Screenshot)
+
+
+ Create a cropped screenshot window
+
+
+ Open Crop and Lock settings
+
+
+
+ Open Environment Variables
+
+
+ Launch Environment Variables editor
+
+
+ Open Environment Variables (Admin)
+
+
+ Launch Environment Variables editor as admin
+
+
+ Open Environment Variables settings
+
+
+
+ FancyZones: Layouts
+
+
+ Apply a layout to all monitors or a specific monitor
+
+
+ FancyZones: Monitors
+
+
+ Identify monitors and apply layouts
+
+
+ Open FancyZones Editor
+
+
+ Launch layout editor
+
+
+ Open FancyZones settings
+
+
+ FancyZones Layouts
+
+
+ No layouts found
+
+
+ Open FancyZones Editor once to initialize layouts.
+
+
+ Apply to {0}
+
+
+ FancyZones Monitors
+
+
+ No monitors found
+
+
+ Open FancyZones Editor once to initialize monitor data.
+
+
+ Set active layout
+
+
+ Pick a layout for this monitor
+
+
+ Set active layout for {0}
+
+
+ Current layout: {0}
+
+
+ Current layout: unknown
+
+
+ Template: {0}
+
+
+ {0} zones
+
+
+ Custom grid • {0} zones
+
+
+ Custom canvas • {0} zones
+
+
+ Custom • {0} zones
+
+
+ Layout applied.
+
+
+ Layout applied, but FancyZones could not be notified: {0}
+
+
+ Failed to write applied layouts: {0}
+
+
+ FancyZones monitor data not found. Open FancyZones Editor once to initialize.
+
+
+ No FancyZones monitors found.
+
+
+ Failed to read FancyZones monitor data: {0}
+
+
+
+ Open File Explorer add-ons settings
+
+
+
+ Open File Locksmith settings
+
+
+
+ Open Hosts File Editor
+
+
+ Launch Hosts File Editor
+
+
+ Open Hosts File Editor (Admin)
+
+
+ Launch Hosts File Editor as admin
+
+
+ Open Hosts File Editor settings
+
+
+
+ Open Image Resizer settings
+
+
+
+ Open Keyboard Manager settings
+
+
+
+ Light Switch: Toggle theme
+
+
+ Toggle system/apps theme immediately
+
+
+ Open Light Switch settings
+
+
+
+ Trigger Find My Mouse
+
+
+ Focus the mouse pointer
+
+
+ Toggle Mouse Highlighter
+
+
+ Highlight mouse clicks
+
+
+ Toggle Mouse Crosshairs
+
+
+ Enable or disable pointer crosshairs
+
+
+ Toggle Cursor Wrap
+
+
+ Wrap the cursor across monitor edges
+
+
+ Show Mouse Jump Preview
+
+
+ Jump the pointer to a target
+
+
+ Open Mouse Utilities settings
+
+
+
+ Open Mouse Without Borders settings
+
+
+
+ Open New+ settings
+
+
+
+ Open Peek settings
+
+
+
+ Open PowerRename settings
+
+
+
+ Open PowerToys Run settings
+
+
+
+ Open Quick Accent settings
+
+
+
+ Open Registry Preview
+
+
+ Launch Registry Preview
+
+
+ Open Registry Preview settings
+
+
+
+ Toggle Screen Ruler
+
+
+ Start or close Screen Ruler
+
+
+ Open Screen Ruler settings
+
+
+
+ Toggle Shortcut Guide
+
+
+ Show or hide Shortcut Guide
+
+
+ Open Shortcut Guide settings
+
+
+
+ Toggle Text Extractor
+
+
+ Start or close Text Extractor
+
+
+ Open Text Extractor settings
+
+
+
+ Open Workspaces settings
+
+
+ Workspaces: Open editor
+
+
+ Create or edit workspaces
+
+
+ No applications
+
+
+ {0} applications
+
+
+ Last launched {0}
+
+
+ Never launched
+
+
+ No applications in this workspace
+
+
+ 1 application
+
+
+ {0} applications
+
+
+ Workspace
+
+
+ App
+
+
+ just now
+
+
+ {0} min ago
+
+
+ {0} hr ago
+
+
+ {0} days ago
+
+
+
+ ZoomIt: Zoom
+
+
+ Enter zoom mode
+
+
+ ZoomIt: Draw
+
+
+ Enter drawing mode
+
+
+ ZoomIt: Break
+
+
+ Enter break timer
+
+
+ ZoomIt: Live Zoom
+
+
+ Toggle live zoom
+
+
+ ZoomIt: Snip
+
+
+ Enter snip mode
+
+
+ ZoomIt: Record
+
+
+ Start recording
+
+
+ Open ZoomIt settings
+
+
+
+ Monitor
+
+
+ Instance
+
+
+ Serial
+
+
+ Number
+
+
+ Virtual desktop
+
+
+ Work area
+
+
+ Resolution
+
+
+ DPI
+
+
+ N/A
+
+
diff --git a/src/modules/cmdpal/extensionsdk/README.md b/src/modules/cmdpal/extensionsdk/README.md
index c2e9e1fe59..540bec74bd 100644
--- a/src/modules/cmdpal/extensionsdk/README.md
+++ b/src/modules/cmdpal/extensionsdk/README.md
@@ -7,7 +7,7 @@ Palette, and use the "Create a new extension" command. That will set up a
project for you, with the packaging, dependencies, and basic program structure
ready to go.
-To view the full docs, you can head over to [our docs site](https://go.microsoft.com/fwlink/?linkid=2310639)
+To view the full docs, you can head over to [our docs site](https://aka.ms/cmdpalextensions-devdocs)
There are samples of just about everything you can do in [the samples project].
Head over there to see basic usage of the APIs.
diff --git a/src/modules/powerrename/lib/Helpers.cpp b/src/modules/powerrename/lib/Helpers.cpp
index 03977a3732..a1ae8c9073 100644
--- a/src/modules/powerrename/lib/Helpers.cpp
+++ b/src/modules/powerrename/lib/Helpers.cpp
@@ -347,14 +347,19 @@ bool isMetadataUsed(_In_ PCWSTR source, PowerRenameLib::MetadataType metadataTyp
// According to the metadata support table, only these formats support metadata extraction:
// - JPEG (IFD, Exif, XMP, GPS, IPTC) - supports fast metadata encoding
- // - TIFF (IFD, Exif, XMP, GPS, IPTC) - supports fast metadata encoding
+ // - TIFF (IFD, Exif, XMP, GPS, IPTC) - supports fast metadata encoding
// - PNG (text chunks)
+ // - HEIF/HEIC (IFD, Exif, XMP, GPS) - requires HEIF Image Extensions from Microsoft Store
+ // - AVIF (IFD, Exif, XMP, GPS) - requires AV1 Video Extension from Microsoft Store
static const std::unordered_set supportedExtensions = {
L".jpg",
L".jpeg",
L".png",
L".tif",
- L".tiff"
+ L".tiff",
+ L".heic",
+ L".heif",
+ L".avif"
};
// If file type doesn't support metadata, no need to check patterns
diff --git a/src/modules/powerrename/lib/PowerRenameManager.cpp b/src/modules/powerrename/lib/PowerRenameManager.cpp
index 160d064e09..b650113b25 100644
--- a/src/modules/powerrename/lib/PowerRenameManager.cpp
+++ b/src/modules/powerrename/lib/PowerRenameManager.cpp
@@ -754,8 +754,26 @@ DWORD WINAPI CPowerRenameManager::s_fileOpWorkerThread(_In_ void* pv)
// We add the items to the operation in depth-first order. This allows child items to be
// renamed before parent items.
+ // First pass: find the maximum depth to properly size the matrix
+ UINT maxDepth = 0;
+ for (UINT u = 0; u < itemCount; u++)
+ {
+ CComPtr spItem;
+ if (SUCCEEDED(pwtd->spsrm->GetItemByIndex(u, &spItem)))
+ {
+ UINT depth = 0;
+ spItem->GetDepth(&depth);
+ if (depth > maxDepth)
+ {
+ maxDepth = depth;
+ }
+ }
+ }
+
// Creating a vector of vectors of items of the same depth
- std::vector> matrix(itemCount);
+ // Size by maxDepth+1 (not itemCount) to avoid excessive memory allocation
+ // Cast to size_t before arithmetic to avoid overflow on 32-bit UINT
+ std::vector> matrix(static_cast(maxDepth) + 1);
for (UINT u = 0; u < itemCount; u++)
{
@@ -769,7 +787,7 @@ DWORD WINAPI CPowerRenameManager::s_fileOpWorkerThread(_In_ void* pv)
}
// From the greatest depth first, add all items of that depth to the operation
- for (LONG v = itemCount - 1; v >= 0; v--)
+ for (LONG v = static_cast(maxDepth); v >= 0; v--)
{
for (auto it : matrix[v])
{
diff --git a/src/modules/powerrename/lib/PowerRenameRegEx.cpp b/src/modules/powerrename/lib/PowerRenameRegEx.cpp
index aabf838a7a..e9ce4fa62a 100644
--- a/src/modules/powerrename/lib/PowerRenameRegEx.cpp
+++ b/src/modules/powerrename/lib/PowerRenameRegEx.cpp
@@ -344,19 +344,13 @@ IFACEMETHODIMP CPowerRenameRegEx::PutFlags(_In_ DWORD flags)
IFACEMETHODIMP CPowerRenameRegEx::PutFileTime(_In_ SYSTEMTIME fileTime)
{
- union timeunion
- {
- FILETIME fileTime;
- ULARGE_INTEGER ul;
- };
+ FILETIME ft1;
+ FILETIME ft2;
- timeunion ft1;
- timeunion ft2;
+ SystemTimeToFileTime(&m_fileTime, &ft1);
+ SystemTimeToFileTime(&fileTime, &ft2);
- SystemTimeToFileTime(&m_fileTime, &ft1.fileTime);
- SystemTimeToFileTime(&fileTime, &ft2.fileTime);
-
- if (ft2.ul.QuadPart != ft1.ul.QuadPart)
+ if (ft2.dwLowDateTime != ft1.dwLowDateTime || ft2.dwHighDateTime != ft1.dwHighDateTime)
{
m_fileTime = fileTime;
m_useFileTime = true;
diff --git a/src/modules/powerrename/lib/WICMetadataExtractor.cpp b/src/modules/powerrename/lib/WICMetadataExtractor.cpp
index bd2f9c08dc..eb66679aad 100644
--- a/src/modules/powerrename/lib/WICMetadataExtractor.cpp
+++ b/src/modules/powerrename/lib/WICMetadataExtractor.cpp
@@ -20,7 +20,7 @@ namespace
// WIC metadata property paths
const std::wstring EXIF_DATE_TAKEN = L"/app1/ifd/exif/{ushort=36867}"; // DateTimeOriginal
- const std::wstring EXIF_DATE_DIGITIZED = L"/app1/ifd/exif/{ushort=36868}"; // DateTimeDigitized
+ const std::wstring EXIF_DATE_DIGITIZED = L"/app1/ifd/exif/{ushort=36868}"; // DateTimeDigitized
const std::wstring EXIF_DATE_MODIFIED = L"/app1/ifd/{ushort=306}"; // DateTime
const std::wstring EXIF_CAMERA_MAKE = L"/app1/ifd/{ushort=271}"; // Make
const std::wstring EXIF_CAMERA_MODEL = L"/app1/ifd/{ushort=272}"; // Model
@@ -37,14 +37,43 @@ namespace
const std::wstring EXIF_HEIGHT = L"/app1/ifd/exif/{ushort=40963}"; // PixelYDimension - actual image height
const std::wstring EXIF_ARTIST = L"/app1/ifd/{ushort=315}"; // Artist
const std::wstring EXIF_COPYRIGHT = L"/app1/ifd/{ushort=33432}"; // Copyright
-
- // GPS paths
+
+ // GPS paths for JPEG format
const std::wstring GPS_LATITUDE = L"/app1/ifd/gps/{ushort=2}"; // GPSLatitude
const std::wstring GPS_LATITUDE_REF = L"/app1/ifd/gps/{ushort=1}"; // GPSLatitudeRef
const std::wstring GPS_LONGITUDE = L"/app1/ifd/gps/{ushort=4}"; // GPSLongitude
const std::wstring GPS_LONGITUDE_REF = L"/app1/ifd/gps/{ushort=3}"; // GPSLongitudeRef
const std::wstring GPS_ALTITUDE = L"/app1/ifd/gps/{ushort=6}"; // GPSAltitude
const std::wstring GPS_ALTITUDE_REF = L"/app1/ifd/gps/{ushort=5}"; // GPSAltitudeRef
+
+ // WIC metadata property paths for TIFF/HEIF format (uses /ifd prefix directly)
+ // HEIF/HEIC images use TIFF-style metadata paths
+ const std::wstring HEIF_DATE_TAKEN = L"/ifd/exif/{ushort=36867}"; // DateTimeOriginal
+ const std::wstring HEIF_DATE_DIGITIZED = L"/ifd/exif/{ushort=36868}"; // DateTimeDigitized
+ const std::wstring HEIF_DATE_MODIFIED = L"/ifd/{ushort=306}"; // DateTime
+ const std::wstring HEIF_CAMERA_MAKE = L"/ifd/{ushort=271}"; // Make
+ const std::wstring HEIF_CAMERA_MODEL = L"/ifd/{ushort=272}"; // Model
+ const std::wstring HEIF_LENS_MODEL = L"/ifd/exif/{ushort=42036}"; // LensModel
+ const std::wstring HEIF_ISO = L"/ifd/exif/{ushort=34855}"; // ISOSpeedRatings
+ const std::wstring HEIF_APERTURE = L"/ifd/exif/{ushort=33437}"; // FNumber
+ const std::wstring HEIF_SHUTTER_SPEED = L"/ifd/exif/{ushort=33434}"; // ExposureTime
+ const std::wstring HEIF_FOCAL_LENGTH = L"/ifd/exif/{ushort=37386}"; // FocalLength
+ const std::wstring HEIF_EXPOSURE_BIAS = L"/ifd/exif/{ushort=37380}"; // ExposureBiasValue
+ const std::wstring HEIF_FLASH = L"/ifd/exif/{ushort=37385}"; // Flash
+ const std::wstring HEIF_ORIENTATION = L"/ifd/{ushort=274}"; // Orientation
+ const std::wstring HEIF_COLOR_SPACE = L"/ifd/exif/{ushort=40961}"; // ColorSpace
+ const std::wstring HEIF_WIDTH = L"/ifd/exif/{ushort=40962}"; // PixelXDimension
+ const std::wstring HEIF_HEIGHT = L"/ifd/exif/{ushort=40963}"; // PixelYDimension
+ const std::wstring HEIF_ARTIST = L"/ifd/{ushort=315}"; // Artist
+ const std::wstring HEIF_COPYRIGHT = L"/ifd/{ushort=33432}"; // Copyright
+
+ // GPS paths for TIFF/HEIF format
+ const std::wstring HEIF_GPS_LATITUDE = L"/ifd/gps/{ushort=2}"; // GPSLatitude
+ const std::wstring HEIF_GPS_LATITUDE_REF = L"/ifd/gps/{ushort=1}"; // GPSLatitudeRef
+ const std::wstring HEIF_GPS_LONGITUDE = L"/ifd/gps/{ushort=4}"; // GPSLongitude
+ const std::wstring HEIF_GPS_LONGITUDE_REF = L"/ifd/gps/{ushort=3}"; // GPSLongitudeRef
+ const std::wstring HEIF_GPS_ALTITUDE = L"/ifd/gps/{ushort=6}"; // GPSAltitude
+ const std::wstring HEIF_GPS_ALTITUDE_REF = L"/ifd/gps/{ushort=5}"; // GPSAltitudeRef
// Documentation: https://developer.adobe.com/xmp/docs/XMPNamespaces/xmp/
@@ -465,8 +494,11 @@ bool WICMetadataExtractor::LoadEXIFMetadata(
return false;
}
- ExtractAllEXIFFields(reader, outMetadata);
- ExtractGPSData(reader, outMetadata);
+ // Detect container format to determine correct metadata paths
+ MetadataPathFormat pathFormat = GetMetadataPathFormatFromDecoder(decoder);
+
+ ExtractAllEXIFFields(reader, outMetadata, pathFormat);
+ ExtractGPSData(reader, outMetadata, pathFormat);
return true;
}
@@ -507,64 +539,126 @@ CComPtr WICMetadataExtractor::GetMetadataReader(IWICBit
{
return nullptr;
}
-
+
CComPtr frame;
if (FAILED(decoder->GetFrame(0, &frame)))
{
return nullptr;
}
-
+
CComPtr reader;
frame->GetMetadataQueryReader(&reader);
-
+
return reader;
}
-void WICMetadataExtractor::ExtractAllEXIFFields(IWICMetadataQueryReader* reader, EXIFMetadata& metadata)
+MetadataPathFormat WICMetadataExtractor::GetMetadataPathFormatFromDecoder(IWICBitmapDecoder* decoder)
+{
+ if (!decoder)
+ {
+ return MetadataPathFormat::JPEG;
+ }
+
+ GUID containerFormat;
+ if (SUCCEEDED(decoder->GetContainerFormat(&containerFormat)))
+ {
+ // HEIF and TIFF use /ifd/... paths directly
+ if (containerFormat == GUID_ContainerFormatHeif ||
+ containerFormat == GUID_ContainerFormatTiff)
+ {
+ return MetadataPathFormat::IFD;
+ }
+ }
+
+ // JPEG and other formats use /app1/ifd/... paths
+ return MetadataPathFormat::JPEG;
+}
+
+void WICMetadataExtractor::ExtractAllEXIFFields(IWICMetadataQueryReader* reader, EXIFMetadata& metadata, MetadataPathFormat pathFormat)
{
if (!reader)
return;
-
+
+ // Select the correct paths based on container format
+ const bool useIfdPaths = (pathFormat == MetadataPathFormat::IFD);
+
+ // Date/time paths
+ const auto& dateTakenPath = useIfdPaths ? HEIF_DATE_TAKEN : EXIF_DATE_TAKEN;
+ const auto& dateDigitizedPath = useIfdPaths ? HEIF_DATE_DIGITIZED : EXIF_DATE_DIGITIZED;
+ const auto& dateModifiedPath = useIfdPaths ? HEIF_DATE_MODIFIED : EXIF_DATE_MODIFIED;
+
+ // Camera info paths
+ const auto& cameraMakePath = useIfdPaths ? HEIF_CAMERA_MAKE : EXIF_CAMERA_MAKE;
+ const auto& cameraModelPath = useIfdPaths ? HEIF_CAMERA_MODEL : EXIF_CAMERA_MODEL;
+ const auto& lensModelPath = useIfdPaths ? HEIF_LENS_MODEL : EXIF_LENS_MODEL;
+
+ // Shooting parameter paths
+ const auto& isoPath = useIfdPaths ? HEIF_ISO : EXIF_ISO;
+ const auto& aperturePath = useIfdPaths ? HEIF_APERTURE : EXIF_APERTURE;
+ const auto& shutterSpeedPath = useIfdPaths ? HEIF_SHUTTER_SPEED : EXIF_SHUTTER_SPEED;
+ const auto& focalLengthPath = useIfdPaths ? HEIF_FOCAL_LENGTH : EXIF_FOCAL_LENGTH;
+ const auto& exposureBiasPath = useIfdPaths ? HEIF_EXPOSURE_BIAS : EXIF_EXPOSURE_BIAS;
+ const auto& flashPath = useIfdPaths ? HEIF_FLASH : EXIF_FLASH;
+
+ // Image property paths
+ const auto& widthPath = useIfdPaths ? HEIF_WIDTH : EXIF_WIDTH;
+ const auto& heightPath = useIfdPaths ? HEIF_HEIGHT : EXIF_HEIGHT;
+ const auto& orientationPath = useIfdPaths ? HEIF_ORIENTATION : EXIF_ORIENTATION;
+ const auto& colorSpacePath = useIfdPaths ? HEIF_COLOR_SPACE : EXIF_COLOR_SPACE;
+
+ // Author info paths
+ const auto& artistPath = useIfdPaths ? HEIF_ARTIST : EXIF_ARTIST;
+ const auto& copyrightPath = useIfdPaths ? HEIF_COPYRIGHT : EXIF_COPYRIGHT;
+
// Extract date/time fields
- metadata.dateTaken = ReadDateTime(reader, EXIF_DATE_TAKEN);
- metadata.dateDigitized = ReadDateTime(reader, EXIF_DATE_DIGITIZED);
- metadata.dateModified = ReadDateTime(reader, EXIF_DATE_MODIFIED);
-
+ metadata.dateTaken = ReadDateTime(reader, dateTakenPath);
+ metadata.dateDigitized = ReadDateTime(reader, dateDigitizedPath);
+ metadata.dateModified = ReadDateTime(reader, dateModifiedPath);
+
// Extract camera information
- metadata.cameraMake = ReadString(reader, EXIF_CAMERA_MAKE);
- metadata.cameraModel = ReadString(reader, EXIF_CAMERA_MODEL);
- metadata.lensModel = ReadString(reader, EXIF_LENS_MODEL);
-
+ metadata.cameraMake = ReadString(reader, cameraMakePath);
+ metadata.cameraModel = ReadString(reader, cameraModelPath);
+ metadata.lensModel = ReadString(reader, lensModelPath);
+
// Extract shooting parameters
- metadata.iso = ReadInteger(reader, EXIF_ISO);
- metadata.aperture = ReadDouble(reader, EXIF_APERTURE);
- metadata.shutterSpeed = ReadDouble(reader, EXIF_SHUTTER_SPEED);
- metadata.focalLength = ReadDouble(reader, EXIF_FOCAL_LENGTH);
- metadata.exposureBias = ReadDouble(reader, EXIF_EXPOSURE_BIAS);
- metadata.flash = ReadInteger(reader, EXIF_FLASH);
-
+ metadata.iso = ReadInteger(reader, isoPath);
+ metadata.aperture = ReadDouble(reader, aperturePath);
+ metadata.shutterSpeed = ReadDouble(reader, shutterSpeedPath);
+ metadata.focalLength = ReadDouble(reader, focalLengthPath);
+ metadata.exposureBias = ReadDouble(reader, exposureBiasPath);
+ metadata.flash = ReadInteger(reader, flashPath);
+
// Extract image properties
- metadata.width = ReadInteger(reader, EXIF_WIDTH);
- metadata.height = ReadInteger(reader, EXIF_HEIGHT);
- metadata.orientation = ReadInteger(reader, EXIF_ORIENTATION);
- metadata.colorSpace = ReadInteger(reader, EXIF_COLOR_SPACE);
-
+ metadata.width = ReadInteger(reader, widthPath);
+ metadata.height = ReadInteger(reader, heightPath);
+ metadata.orientation = ReadInteger(reader, orientationPath);
+ metadata.colorSpace = ReadInteger(reader, colorSpacePath);
+
// Extract author information
- metadata.author = ReadString(reader, EXIF_ARTIST);
- metadata.copyright = ReadString(reader, EXIF_COPYRIGHT);
+ metadata.author = ReadString(reader, artistPath);
+ metadata.copyright = ReadString(reader, copyrightPath);
}
-void WICMetadataExtractor::ExtractGPSData(IWICMetadataQueryReader* reader, EXIFMetadata& metadata)
+void WICMetadataExtractor::ExtractGPSData(IWICMetadataQueryReader* reader, EXIFMetadata& metadata, MetadataPathFormat pathFormat)
{
if (!reader)
{
return;
}
- auto lat = ReadMetadata(reader, GPS_LATITUDE);
- auto lon = ReadMetadata(reader, GPS_LONGITUDE);
- auto latRef = ReadMetadata(reader, GPS_LATITUDE_REF);
- auto lonRef = ReadMetadata(reader, GPS_LONGITUDE_REF);
+ // Select the correct paths based on container format
+ const bool useIfdPaths = (pathFormat == MetadataPathFormat::IFD);
+
+ const auto& latitudePath = useIfdPaths ? HEIF_GPS_LATITUDE : GPS_LATITUDE;
+ const auto& longitudePath = useIfdPaths ? HEIF_GPS_LONGITUDE : GPS_LONGITUDE;
+ const auto& latitudeRefPath = useIfdPaths ? HEIF_GPS_LATITUDE_REF : GPS_LATITUDE_REF;
+ const auto& longitudeRefPath = useIfdPaths ? HEIF_GPS_LONGITUDE_REF : GPS_LONGITUDE_REF;
+ const auto& altitudePath = useIfdPaths ? HEIF_GPS_ALTITUDE : GPS_ALTITUDE;
+
+ auto lat = ReadMetadata(reader, latitudePath);
+ auto lon = ReadMetadata(reader, longitudePath);
+ auto latRef = ReadMetadata(reader, latitudeRefPath);
+ auto lonRef = ReadMetadata(reader, longitudeRefPath);
if (lat && lon)
{
@@ -584,7 +678,7 @@ void WICMetadataExtractor::ExtractGPSData(IWICMetadataQueryReader* reader, EXIFM
metadata.longitude = coords.second;
}
- auto alt = ReadMetadata(reader, GPS_ALTITUDE);
+ auto alt = ReadMetadata(reader, altitudePath);
if (alt)
{
metadata.altitude = MetadataFormatHelper::ParseGPSRational(alt->Get());
diff --git a/src/modules/powerrename/lib/WICMetadataExtractor.h b/src/modules/powerrename/lib/WICMetadataExtractor.h
index 868d18aa7c..f2149a40f1 100644
--- a/src/modules/powerrename/lib/WICMetadataExtractor.h
+++ b/src/modules/powerrename/lib/WICMetadataExtractor.h
@@ -9,14 +9,32 @@
#include
#include
+// Forward declarations for unit test friend classes
+namespace WICMetadataExtractorTests
+{
+ class ExtractAVIFMetadataTests;
+}
+
namespace PowerRenameLib
{
+ ///
+ /// Metadata path format based on container type
+ ///
+ enum class MetadataPathFormat
+ {
+ JPEG, // Uses /app1/ifd/... paths (JPEG)
+ IFD // Uses /ifd/... paths (HEIF, TIFF, etc.)
+ };
+
///
/// Windows Imaging Component (WIC) implementation for metadata extraction
/// Provides efficient batch extraction of all metadata types with built-in caching
///
class WICMetadataExtractor
{
+ // Friend declarations for unit testing
+ friend class WICMetadataExtractorTests::ExtractAVIFMetadataTests;
+
public:
WICMetadataExtractor();
~WICMetadataExtractor();
@@ -45,10 +63,13 @@ namespace PowerRenameLib
bool LoadXMPMetadata(const std::wstring& filePath, XMPMetadata& outMetadata);
// Batch extraction methods
- void ExtractAllEXIFFields(IWICMetadataQueryReader* reader, EXIFMetadata& metadata);
- void ExtractGPSData(IWICMetadataQueryReader* reader, EXIFMetadata& metadata);
+ void ExtractAllEXIFFields(IWICMetadataQueryReader* reader, EXIFMetadata& metadata, MetadataPathFormat pathFormat);
+ void ExtractGPSData(IWICMetadataQueryReader* reader, EXIFMetadata& metadata, MetadataPathFormat pathFormat);
void ExtractAllXMPFields(IWICMetadataQueryReader* reader, XMPMetadata& metadata);
+ // Internal container format detection
+ MetadataPathFormat GetMetadataPathFormatFromDecoder(IWICBitmapDecoder* decoder);
+
// Field reading helpers
std::optional ReadDateTime(IWICMetadataQueryReader* reader, const std::wstring& path);
std::optional ReadString(IWICMetadataQueryReader* reader, const std::wstring& path);
diff --git a/src/modules/powerrename/unittests/PowerRenameLibUnitTests.vcxproj b/src/modules/powerrename/unittests/PowerRenameLibUnitTests.vcxproj
index 3a3c5663aa..8e58bb7956 100644
--- a/src/modules/powerrename/unittests/PowerRenameLibUnitTests.vcxproj
+++ b/src/modules/powerrename/unittests/PowerRenameLibUnitTests.vcxproj
@@ -89,6 +89,12 @@
true
+
+ true
+
+
+ true
+
true
diff --git a/src/modules/powerrename/unittests/WICMetadataExtractorTests.cpp b/src/modules/powerrename/unittests/WICMetadataExtractorTests.cpp
index c6d1d9b16c..265abfa7b7 100644
--- a/src/modules/powerrename/unittests/WICMetadataExtractorTests.cpp
+++ b/src/modules/powerrename/unittests/WICMetadataExtractorTests.cpp
@@ -227,18 +227,350 @@ namespace WICMetadataExtractorTests
XMPMetadata metadata;
std::wstring testFile = GetTestDataPath() + L"\\xmp_test.jpg";
-
+
bool result1 = extractor.ExtractXMPMetadata(testFile, metadata);
Assert::IsTrue(result1);
-
+
extractor.ClearCache();
-
+
XMPMetadata metadata2;
bool result2 = extractor.ExtractXMPMetadata(testFile, metadata2);
Assert::IsTrue(result2);
-
+
// Both calls should succeed
Assert::AreEqual(metadata.title.value().c_str(), metadata2.title.value().c_str());
}
};
+
+ TEST_CLASS(ExtractHEIFMetadataTests)
+ {
+ public:
+ TEST_METHOD(ExtractHEIF_EXIF_CameraInfo)
+ {
+ // Test HEIF EXIF extraction - camera information
+ // This test requires HEIF Image Extensions to be installed from Microsoft Store
+ WICMetadataExtractor extractor;
+ EXIFMetadata metadata;
+
+ std::wstring testFile = GetTestDataPath() + L"\\heif_test.heic";
+
+ // Check if file exists first
+ if (!std::filesystem::exists(testFile))
+ {
+ Logger::WriteMessage(L"HEIF test file not found, skipping test");
+ return;
+ }
+
+ bool result = extractor.ExtractEXIFMetadata(testFile, metadata);
+
+ // If HEIF extension is not installed, extraction may fail
+ if (!result)
+ {
+ Logger::WriteMessage(L"HEIF extraction failed - HEIF Image Extensions may not be installed");
+ return;
+ }
+
+ Assert::IsTrue(result, L"HEIF EXIF extraction should succeed");
+
+ // Verify camera information from iPhone
+ Assert::IsTrue(metadata.cameraMake.has_value(), L"Camera make should be present");
+ Assert::AreEqual(L"Apple", metadata.cameraMake.value().c_str(), L"Camera make should be Apple");
+
+ Assert::IsTrue(metadata.cameraModel.has_value(), L"Camera model should be present");
+ // Model should contain "iPhone"
+ Assert::IsTrue(metadata.cameraModel.value().find(L"iPhone") != std::wstring::npos,
+ L"Camera model should contain iPhone");
+ }
+
+ TEST_METHOD(ExtractHEIF_EXIF_DateTaken)
+ {
+ // Test HEIF EXIF extraction - date taken
+ WICMetadataExtractor extractor;
+ EXIFMetadata metadata;
+
+ std::wstring testFile = GetTestDataPath() + L"\\heif_test.heic";
+
+ if (!std::filesystem::exists(testFile))
+ {
+ Logger::WriteMessage(L"HEIF test file not found, skipping test");
+ return;
+ }
+
+ bool result = extractor.ExtractEXIFMetadata(testFile, metadata);
+
+ if (!result)
+ {
+ Logger::WriteMessage(L"HEIF extraction failed - HEIF Image Extensions may not be installed");
+ return;
+ }
+
+ Assert::IsTrue(result, L"HEIF EXIF extraction should succeed");
+
+ // Verify date taken is present
+ Assert::IsTrue(metadata.dateTaken.has_value(), L"Date taken should be present");
+
+ // Verify the date is a reasonable year (2020-2030 range)
+ SYSTEMTIME dt = metadata.dateTaken.value();
+ Assert::IsTrue(dt.wYear >= 2020 && dt.wYear <= 2030, L"Date taken year should be reasonable");
+ }
+
+ TEST_METHOD(ExtractHEIF_EXIF_ShootingParameters)
+ {
+ // Test HEIF EXIF extraction - shooting parameters
+ WICMetadataExtractor extractor;
+ EXIFMetadata metadata;
+
+ std::wstring testFile = GetTestDataPath() + L"\\heif_test.heic";
+
+ if (!std::filesystem::exists(testFile))
+ {
+ Logger::WriteMessage(L"HEIF test file not found, skipping test");
+ return;
+ }
+
+ bool result = extractor.ExtractEXIFMetadata(testFile, metadata);
+
+ if (!result)
+ {
+ Logger::WriteMessage(L"HEIF extraction failed - HEIF Image Extensions may not be installed");
+ return;
+ }
+
+ Assert::IsTrue(result, L"HEIF EXIF extraction should succeed");
+
+ // Verify shooting parameters are present
+ Assert::IsTrue(metadata.iso.has_value(), L"ISO should be present");
+ Assert::IsTrue(metadata.iso.value() > 0, L"ISO should be positive");
+
+ Assert::IsTrue(metadata.aperture.has_value(), L"Aperture should be present");
+ Assert::IsTrue(metadata.aperture.value() > 0, L"Aperture should be positive");
+
+ Assert::IsTrue(metadata.shutterSpeed.has_value(), L"Shutter speed should be present");
+ Assert::IsTrue(metadata.shutterSpeed.value() > 0, L"Shutter speed should be positive");
+
+ Assert::IsTrue(metadata.focalLength.has_value(), L"Focal length should be present");
+ Assert::IsTrue(metadata.focalLength.value() > 0, L"Focal length should be positive");
+ }
+
+ TEST_METHOD(ExtractHEIF_EXIF_GPS)
+ {
+ // Test HEIF EXIF extraction - GPS coordinates
+ WICMetadataExtractor extractor;
+ EXIFMetadata metadata;
+
+ std::wstring testFile = GetTestDataPath() + L"\\heif_test.heic";
+
+ if (!std::filesystem::exists(testFile))
+ {
+ Logger::WriteMessage(L"HEIF test file not found, skipping test");
+ return;
+ }
+
+ bool result = extractor.ExtractEXIFMetadata(testFile, metadata);
+
+ if (!result)
+ {
+ Logger::WriteMessage(L"HEIF extraction failed - HEIF Image Extensions may not be installed");
+ return;
+ }
+
+ Assert::IsTrue(result, L"HEIF EXIF extraction should succeed");
+
+ // Verify GPS coordinates are present (if the test file has GPS data)
+ if (metadata.latitude.has_value() && metadata.longitude.has_value())
+ {
+ // Latitude should be between -90 and 90
+ Assert::IsTrue(metadata.latitude.value() >= -90.0 && metadata.latitude.value() <= 90.0,
+ L"Latitude should be valid");
+
+ // Longitude should be between -180 and 180
+ Assert::IsTrue(metadata.longitude.value() >= -180.0 && metadata.longitude.value() <= 180.0,
+ L"Longitude should be valid");
+ }
+ else
+ {
+ Logger::WriteMessage(L"GPS data not present in test file");
+ }
+ }
+
+ TEST_METHOD(ExtractHEIF_EXIF_ImageDimensions)
+ {
+ // Test HEIF EXIF extraction - image dimensions
+ WICMetadataExtractor extractor;
+ EXIFMetadata metadata;
+
+ std::wstring testFile = GetTestDataPath() + L"\\heif_test.heic";
+
+ if (!std::filesystem::exists(testFile))
+ {
+ Logger::WriteMessage(L"HEIF test file not found, skipping test");
+ return;
+ }
+
+ bool result = extractor.ExtractEXIFMetadata(testFile, metadata);
+
+ if (!result)
+ {
+ Logger::WriteMessage(L"HEIF extraction failed - HEIF Image Extensions may not be installed");
+ return;
+ }
+
+ Assert::IsTrue(result, L"HEIF EXIF extraction should succeed");
+
+ // Verify image dimensions are present
+ Assert::IsTrue(metadata.width.has_value(), L"Width should be present");
+ Assert::IsTrue(metadata.width.value() > 0, L"Width should be positive");
+
+ Assert::IsTrue(metadata.height.has_value(), L"Height should be present");
+ Assert::IsTrue(metadata.height.value() > 0, L"Height should be positive");
+ }
+
+ TEST_METHOD(ExtractHEIF_EXIF_LensModel)
+ {
+ // Test HEIF EXIF extraction - lens model
+ WICMetadataExtractor extractor;
+ EXIFMetadata metadata;
+
+ std::wstring testFile = GetTestDataPath() + L"\\heif_test.heic";
+
+ if (!std::filesystem::exists(testFile))
+ {
+ Logger::WriteMessage(L"HEIF test file not found, skipping test");
+ return;
+ }
+
+ bool result = extractor.ExtractEXIFMetadata(testFile, metadata);
+
+ if (!result)
+ {
+ Logger::WriteMessage(L"HEIF extraction failed - HEIF Image Extensions may not be installed");
+ return;
+ }
+
+ Assert::IsTrue(result, L"HEIF EXIF extraction should succeed");
+
+ // Verify lens model is present (iPhone photos typically have this)
+ if (metadata.lensModel.has_value())
+ {
+ Assert::IsFalse(metadata.lensModel.value().empty(), L"Lens model should not be empty");
+ }
+ else
+ {
+ Logger::WriteMessage(L"Lens model not present in test file");
+ }
+ }
+ };
+
+ TEST_CLASS(ExtractAVIFMetadataTests)
+ {
+ public:
+ TEST_METHOD(ExtractAVIF_EXIF_CameraInfo)
+ {
+ // Test AVIF EXIF extraction - camera information
+ // This test requires AV1 Video Extension to be installed from Microsoft Store
+ WICMetadataExtractor extractor;
+ EXIFMetadata metadata;
+
+ std::wstring testFile = GetTestDataPath() + L"\\avif_test.avif";
+
+ if (!std::filesystem::exists(testFile))
+ {
+ Logger::WriteMessage(L"AVIF test file not found, skipping test");
+ return;
+ }
+
+ bool result = extractor.ExtractEXIFMetadata(testFile, metadata);
+
+ if (!result)
+ {
+ Logger::WriteMessage(L"AVIF extraction failed - AV1 Video Extension may not be installed");
+ return;
+ }
+
+ Assert::IsTrue(result, L"AVIF EXIF extraction should succeed");
+
+ // Verify camera information
+ if (metadata.cameraMake.has_value())
+ {
+ Assert::IsFalse(metadata.cameraMake.value().empty(), L"Camera make should not be empty");
+ }
+
+ if (metadata.cameraModel.has_value())
+ {
+ Assert::IsFalse(metadata.cameraModel.value().empty(), L"Camera model should not be empty");
+ }
+ }
+
+ TEST_METHOD(ExtractAVIF_EXIF_DateTaken)
+ {
+ // Test AVIF EXIF extraction - date taken
+ WICMetadataExtractor extractor;
+ EXIFMetadata metadata;
+
+ std::wstring testFile = GetTestDataPath() + L"\\avif_test.avif";
+
+ if (!std::filesystem::exists(testFile))
+ {
+ Logger::WriteMessage(L"AVIF test file not found, skipping test");
+ return;
+ }
+
+ bool result = extractor.ExtractEXIFMetadata(testFile, metadata);
+
+ if (!result)
+ {
+ Logger::WriteMessage(L"AVIF extraction failed - AV1 Video Extension may not be installed");
+ return;
+ }
+
+ Assert::IsTrue(result, L"AVIF EXIF extraction should succeed");
+
+ // Verify date taken is present
+ if (metadata.dateTaken.has_value())
+ {
+ SYSTEMTIME dt = metadata.dateTaken.value();
+ Assert::IsTrue(dt.wYear >= 2000 && dt.wYear <= 2100, L"Date taken year should be reasonable");
+ }
+ else
+ {
+ Logger::WriteMessage(L"Date taken not present in AVIF test file");
+ }
+ }
+
+ TEST_METHOD(ExtractAVIF_EXIF_ImageDimensions)
+ {
+ // Test AVIF EXIF extraction - image dimensions
+ WICMetadataExtractor extractor;
+ EXIFMetadata metadata;
+
+ std::wstring testFile = GetTestDataPath() + L"\\avif_test.avif";
+
+ if (!std::filesystem::exists(testFile))
+ {
+ Logger::WriteMessage(L"AVIF test file not found, skipping test");
+ return;
+ }
+
+ bool result = extractor.ExtractEXIFMetadata(testFile, metadata);
+
+ if (!result)
+ {
+ Logger::WriteMessage(L"AVIF extraction failed - AV1 Video Extension may not be installed");
+ return;
+ }
+
+ Assert::IsTrue(result, L"AVIF EXIF extraction should succeed");
+
+ // Verify image dimensions are present
+ if (metadata.width.has_value())
+ {
+ Assert::IsTrue(metadata.width.value() > 0, L"Width should be positive");
+ }
+
+ if (metadata.height.has_value())
+ {
+ Assert::IsTrue(metadata.height.value() > 0, L"Height should be positive");
+ }
+ }
+ };
}
diff --git a/src/modules/powerrename/unittests/testdata/avif_test.avif b/src/modules/powerrename/unittests/testdata/avif_test.avif
new file mode 100644
index 0000000000..49fad74288
Binary files /dev/null and b/src/modules/powerrename/unittests/testdata/avif_test.avif differ
diff --git a/src/modules/powerrename/unittests/testdata/heif_test.heic b/src/modules/powerrename/unittests/testdata/heif_test.heic
new file mode 100644
index 0000000000..824e011b9e
Binary files /dev/null and b/src/modules/powerrename/unittests/testdata/heif_test.heic differ
diff --git a/src/runner/general_settings.cpp b/src/runner/general_settings.cpp
index c6770731a6..5024e39753 100644
--- a/src/runner/general_settings.cpp
+++ b/src/runner/general_settings.cpp
@@ -2,6 +2,7 @@
#include "general_settings.h"
#include "auto_start_helper.h"
#include "tray_icon.h"
+#include "quick_access_host.h"
#include "Generated files/resource.h"
#include "hotkey_conflict_detector.h"
@@ -66,12 +67,15 @@ namespace
// TODO: would be nice to get rid of these globals, since they're basically cached json settings
static std::wstring settings_theme = L"system";
static bool show_tray_icon = true;
+static bool show_theme_adaptive_tray_icon = false;
static bool run_as_elevated = false;
static bool show_new_updates_toast_notification = true;
static bool download_updates_automatically = true;
static bool show_whats_new_after_updates = true;
static bool enable_experimentation = true;
static bool enable_warnings_elevated_apps = true;
+static bool enable_quick_access = true;
+static PowerToysSettings::HotkeyObject quick_access_shortcut;
static DashboardSortOrder dashboard_sort_order = DashboardSortOrder::Alphabetical;
static json::JsonObject ignored_conflict_properties = create_default_ignored_conflict_properties();
@@ -96,6 +100,7 @@ json::JsonObject GeneralSettings::to_json()
result.SetNamedValue(L"enabled", std::move(enabled));
result.SetNamedValue(L"show_tray_icon", json::value(showSystemTrayIcon));
+ result.SetNamedValue(L"show_theme_adaptive_tray_icon", json::value(showThemeAdaptiveTrayIcon));
result.SetNamedValue(L"is_elevated", json::value(isElevated));
result.SetNamedValue(L"run_elevated", json::value(isRunElevated));
result.SetNamedValue(L"show_new_updates_toast_notification", json::value(showNewUpdatesToastNotification));
@@ -105,6 +110,8 @@ json::JsonObject GeneralSettings::to_json()
result.SetNamedValue(L"dashboard_sort_order", json::value(static_cast(dashboardSortOrder)));
result.SetNamedValue(L"is_admin", json::value(isAdmin));
result.SetNamedValue(L"enable_warnings_elevated_apps", json::value(enableWarningsElevatedApps));
+ result.SetNamedValue(L"enable_quick_access", json::value(enableQuickAccess));
+ result.SetNamedValue(L"quick_access_shortcut", quickAccessShortcut.get_json());
result.SetNamedValue(L"theme", json::value(theme));
result.SetNamedValue(L"system_theme", json::value(systemTheme));
result.SetNamedValue(L"powertoys_version", json::value(powerToysVersion));
@@ -121,12 +128,19 @@ json::JsonObject load_general_settings()
{
settings_theme = L"system";
}
+ show_tray_icon = loaded.GetNamedBoolean(L"show_tray_icon", true);
+ show_theme_adaptive_tray_icon = loaded.GetNamedBoolean(L"show_theme_adaptive_tray_icon", false);
run_as_elevated = loaded.GetNamedBoolean(L"run_elevated", false);
show_new_updates_toast_notification = loaded.GetNamedBoolean(L"show_new_updates_toast_notification", true);
download_updates_automatically = loaded.GetNamedBoolean(L"download_updates_automatically", true) && check_user_is_admin();
show_whats_new_after_updates = loaded.GetNamedBoolean(L"show_whats_new_after_updates", true);
enable_experimentation = loaded.GetNamedBoolean(L"enable_experimentation", true);
enable_warnings_elevated_apps = loaded.GetNamedBoolean(L"enable_warnings_elevated_apps", true);
+ enable_quick_access = loaded.GetNamedBoolean(L"enable_quick_access", true);
+ if (json::has(loaded, L"quick_access_shortcut", json::JsonValueType::Object))
+ {
+ quick_access_shortcut = PowerToysSettings::HotkeyObject::from_json(loaded.GetNamedObject(L"quick_access_shortcut"));
+ }
dashboard_sort_order = parse_dashboard_sort_order(loaded, dashboard_sort_order);
if (json::has(loaded, L"ignored_conflict_properties", json::JsonValueType::Object))
@@ -149,10 +163,13 @@ GeneralSettings get_general_settings()
GeneralSettings settings
{
.showSystemTrayIcon = show_tray_icon,
+ .showThemeAdaptiveTrayIcon = show_theme_adaptive_tray_icon,
.isElevated = is_process_elevated(),
.isRunElevated = run_as_elevated,
.isAdmin = is_user_admin,
.enableWarningsElevatedApps = enable_warnings_elevated_apps,
+ .enableQuickAccess = enable_quick_access,
+ .quickAccessShortcut = quick_access_shortcut,
.showNewUpdatesToastNotification = show_new_updates_toast_notification,
.downloadUpdatesAutomatically = download_updates_automatically && is_user_admin,
.showWhatsNewAfterUpdates = show_whats_new_after_updates,
@@ -178,11 +195,47 @@ GeneralSettings get_general_settings()
void apply_general_settings(const json::JsonObject& general_configs, bool save)
{
+ std::wstring old_settings_json_string;
+ if (save)
+ {
+ old_settings_json_string = get_general_settings().to_json().Stringify().c_str();
+ }
+
Logger::info(L"apply_general_settings: {}", std::wstring{ general_configs.ToString() });
run_as_elevated = general_configs.GetNamedBoolean(L"run_elevated", false);
enable_warnings_elevated_apps = general_configs.GetNamedBoolean(L"enable_warnings_elevated_apps", true);
+ bool new_enable_quick_access = general_configs.GetNamedBoolean(L"enable_quick_access", true);
+ Logger::info(L"apply_general_settings: enable_quick_access={}, new_enable_quick_access={}", enable_quick_access, new_enable_quick_access);
+
+ PowerToysSettings::HotkeyObject new_quick_access_shortcut;
+ if (json::has(general_configs, L"quick_access_shortcut", json::JsonValueType::Object))
+ {
+ new_quick_access_shortcut = PowerToysSettings::HotkeyObject::from_json(general_configs.GetNamedObject(L"quick_access_shortcut"));
+ }
+
+ auto hotkey_equals = [](const PowerToysSettings::HotkeyObject& a, const PowerToysSettings::HotkeyObject& b) {
+ return a.get_code() == b.get_code() &&
+ a.get_modifiers() == b.get_modifiers();
+ };
+
+ if (enable_quick_access != new_enable_quick_access || !hotkey_equals(quick_access_shortcut, new_quick_access_shortcut))
+ {
+ enable_quick_access = new_enable_quick_access;
+ quick_access_shortcut = new_quick_access_shortcut;
+
+ if (enable_quick_access)
+ {
+ QuickAccessHost::start();
+ }
+ else
+ {
+ QuickAccessHost::stop();
+ }
+ update_quick_access_hotkey(enable_quick_access, quick_access_shortcut);
+ }
+
show_new_updates_toast_notification = general_configs.GetNamedBoolean(L"show_new_updates_toast_notification", true);
download_updates_automatically = general_configs.GetNamedBoolean(L"download_updates_automatically", true);
@@ -308,10 +361,19 @@ void apply_general_settings(const json::JsonObject& general_configs, bool save)
if (json::has(general_configs, L"show_tray_icon", json::JsonValueType::Boolean))
{
show_tray_icon = general_configs.GetNamedBoolean(L"show_tray_icon");
- // Update tray icon visibility when setting is toggled
set_tray_icon_visible(show_tray_icon);
}
+ if (json::has(general_configs, L"show_theme_adaptive_tray_icon", json::JsonValueType::Boolean))
+ {
+ bool new_theme_adaptive = general_configs.GetNamedBoolean(L"show_theme_adaptive_tray_icon");
+ if (show_theme_adaptive_tray_icon != new_theme_adaptive)
+ {
+ show_theme_adaptive_tray_icon = new_theme_adaptive;
+ set_tray_icon_theme_adaptive(show_theme_adaptive_tray_icon);
+ }
+ }
+
if (json::has(general_configs, L"ignored_conflict_properties", json::JsonValueType::Object))
{
ignored_conflict_properties = general_configs.GetNamedObject(L"ignored_conflict_properties");
@@ -321,8 +383,12 @@ void apply_general_settings(const json::JsonObject& general_configs, bool save)
if (save)
{
GeneralSettings save_settings = get_general_settings();
- PTSettingsHelper::save_general_settings(save_settings.to_json());
- Trace::SettingsChanged(save_settings);
+ std::wstring new_settings_json_string = save_settings.to_json().Stringify().c_str();
+ if (old_settings_json_string != new_settings_json_string)
+ {
+ PTSettingsHelper::save_general_settings(save_settings.to_json());
+ Trace::SettingsChanged(save_settings);
+ }
}
}
@@ -412,3 +478,5 @@ void start_enabled_powertoys()
}
}
}
+
+
diff --git a/src/runner/general_settings.h b/src/runner/general_settings.h
index b4f7638846..ac93a1fdfd 100644
--- a/src/runner/general_settings.h
+++ b/src/runner/general_settings.h
@@ -1,6 +1,7 @@
#pragma once
#include
+#include
enum class DashboardSortOrder
{
@@ -12,12 +13,15 @@ struct GeneralSettings
{
bool isStartupEnabled;
bool showSystemTrayIcon;
+ bool showThemeAdaptiveTrayIcon;
std::wstring startupDisabledReason;
std::map isModulesEnabledMap;
bool isElevated;
bool isRunElevated;
bool isAdmin;
bool enableWarningsElevatedApps;
+ bool enableQuickAccess;
+ PowerToysSettings::HotkeyObject quickAccessShortcut;
bool showNewUpdatesToastNotification;
bool downloadUpdatesAutomatically;
bool showWhatsNewAfterUpdates;
diff --git a/src/runner/main.cpp b/src/runner/main.cpp
index f60445277d..973cee4ba5 100644
--- a/src/runner/main.cpp
+++ b/src/runner/main.cpp
@@ -37,6 +37,7 @@
#include
#include "centralized_kb_hook.h"
#include "centralized_hotkeys.h"
+#include "quick_access_host.h"
#include "ai_detection.h"
#include
@@ -188,8 +189,18 @@ int runner(bool isProcessElevated, bool openSettings, std::string settingsWindow
//init_global_error_handlers();
#endif
Trace::RegisterProvider();
- start_tray_icon(isProcessElevated);
- set_tray_icon_visible(get_general_settings().showSystemTrayIcon);
+
+ // Load settings from file before reading them
+ load_general_settings();
+ auto const settings = get_general_settings();
+ start_tray_icon(isProcessElevated, settings.showThemeAdaptiveTrayIcon);
+
+ if (settings.enableQuickAccess)
+ {
+ QuickAccessHost::start();
+ }
+ update_quick_access_hotkey(settings.enableQuickAccess, settings.quickAccessShortcut);
+ set_tray_icon_visible(settings.showSystemTrayIcon);
CentralizedKeyboardHook::Start();
int result = -1;
@@ -317,7 +328,7 @@ int runner(bool isProcessElevated, bool openSettings, std::string settingsWindow
{
window = winrt::to_hstring(settingsWindow);
}
- open_settings_window(window, false);
+ open_settings_window(window);
}
if (openOobe)
@@ -340,6 +351,7 @@ int runner(bool isProcessElevated, bool openSettings, std::string settingsWindow
result = -1;
}
Trace::UnregisterProvider();
+ QuickAccessHost::stop();
return result;
}
diff --git a/src/runner/quick_access_host.cpp b/src/runner/quick_access_host.cpp
new file mode 100644
index 0000000000..b546ee244e
--- /dev/null
+++ b/src/runner/quick_access_host.cpp
@@ -0,0 +1,296 @@
+#include "pch.h"
+#include "quick_access_host.h"
+
+#include
+#include
+#include
+#include
+#include
+#include
+
+#include
+#include
+#include
+#include
+
+extern void receive_json_send_to_main_thread(const std::wstring& msg);
+
+namespace
+{
+ wil::unique_handle quick_access_process;
+ wil::unique_handle quick_access_job;
+ wil::unique_handle show_event;
+ wil::unique_handle exit_event;
+ std::wstring show_event_name;
+ std::wstring exit_event_name;
+ std::wstring runner_pipe_name;
+ std::wstring app_pipe_name;
+ std::unique_ptr quick_access_ipc;
+ std::mutex quick_access_mutex;
+
+ bool is_process_active_locked()
+ {
+ if (!quick_access_process)
+ {
+ return false;
+ }
+
+ DWORD exit_code = 0;
+ if (!GetExitCodeProcess(quick_access_process.get(), &exit_code))
+ {
+ Logger::warn(L"QuickAccessHost: failed to read Quick Access process exit code. error={}.", GetLastError());
+ return false;
+ }
+
+ return exit_code == STILL_ACTIVE;
+ }
+
+ void reset_state_locked()
+ {
+ if (quick_access_ipc)
+ {
+ quick_access_ipc->end();
+ quick_access_ipc.reset();
+ }
+
+ quick_access_process.reset();
+ quick_access_job.reset();
+ show_event.reset();
+ exit_event.reset();
+ show_event_name.clear();
+ exit_event_name.clear();
+ runner_pipe_name.clear();
+ app_pipe_name.clear();
+ }
+
+ std::wstring build_event_name(const wchar_t* suffix)
+ {
+ std::wstring name = L"Local\\PowerToysQuickAccess_";
+ name += std::to_wstring(GetCurrentProcessId());
+ if (suffix)
+ {
+ name += suffix;
+ }
+ return name;
+ }
+
+ std::wstring build_command_line(const std::wstring& exe_path)
+ {
+ std::wstring command_line = L"\"";
+ command_line += exe_path;
+ command_line += L"\" --show-event=\"";
+ command_line += show_event_name;
+ command_line += L"\" --exit-event=\"";
+ command_line += exit_event_name;
+ command_line += L"\"";
+ if (!runner_pipe_name.empty())
+ {
+ command_line.append(L" --runner-pipe=\"");
+ command_line += runner_pipe_name;
+ command_line += L"\"";
+ }
+ if (!app_pipe_name.empty())
+ {
+ command_line.append(L" --app-pipe=\"");
+ command_line += app_pipe_name;
+ command_line += L"\"";
+ }
+ return command_line;
+ }
+}
+
+namespace QuickAccessHost
+{
+ bool is_running()
+ {
+ std::scoped_lock lock(quick_access_mutex);
+ return is_process_active_locked();
+ }
+
+ void start()
+ {
+ Logger::info(L"QuickAccessHost::start() called");
+ std::scoped_lock lock(quick_access_mutex);
+ if (is_process_active_locked())
+ {
+ Logger::info(L"QuickAccessHost::start: process already active");
+ return;
+ }
+
+ reset_state_locked();
+
+ show_event_name = build_event_name(L"_Show");
+ exit_event_name = build_event_name(L"_Exit");
+
+ show_event.reset(CreateEventW(nullptr, FALSE, FALSE, show_event_name.c_str()));
+ if (!show_event)
+ {
+ Logger::error(L"QuickAccessHost: failed to create show event. error={}.", GetLastError());
+ reset_state_locked();
+ return;
+ }
+
+ exit_event.reset(CreateEventW(nullptr, FALSE, FALSE, exit_event_name.c_str()));
+ if (!exit_event)
+ {
+ Logger::error(L"QuickAccessHost: failed to create exit event. error={}.", GetLastError());
+ reset_state_locked();
+ return;
+ }
+
+ runner_pipe_name = L"\\\\.\\pipe\\powertoys_quick_access_runner_";
+ app_pipe_name = L"\\\\.\\pipe\\powertoys_quick_access_ui_";
+ UUID temp_uuid;
+ wchar_t* uuid_chars = nullptr;
+ if (UuidCreate(&temp_uuid) == RPC_S_UUID_NO_ADDRESS)
+ {
+ Logger::warn(L"QuickAccessHost: failed to create UUID for pipe names. error={}.", GetLastError());
+ }
+ else if (UuidToString(&temp_uuid, reinterpret_cast(&uuid_chars)) != RPC_S_OK)
+ {
+ Logger::warn(L"QuickAccessHost: failed to convert UUID to string. error={}.", GetLastError());
+ }
+
+ if (uuid_chars != nullptr)
+ {
+ runner_pipe_name += std::wstring(uuid_chars);
+ app_pipe_name += std::wstring(uuid_chars);
+ RpcStringFree(reinterpret_cast(&uuid_chars));
+ uuid_chars = nullptr;
+ }
+ else
+ {
+ const std::wstring fallback_suffix = std::to_wstring(GetTickCount64());
+ runner_pipe_name += fallback_suffix;
+ app_pipe_name += fallback_suffix;
+ }
+
+ HANDLE token_handle = nullptr;
+ if (!OpenProcessToken(GetCurrentProcess(), TOKEN_QUERY, &token_handle))
+ {
+ Logger::error(L"QuickAccessHost: failed to open process token. error={}.", GetLastError());
+ reset_state_locked();
+ return;
+ }
+
+ wil::unique_handle token(token_handle);
+ quick_access_ipc.reset(new (std::nothrow) TwoWayPipeMessageIPC(runner_pipe_name, app_pipe_name, receive_json_send_to_main_thread));
+ if (!quick_access_ipc)
+ {
+ Logger::error(L"QuickAccessHost: failed to allocate IPC instance.");
+ reset_state_locked();
+ return;
+ }
+
+ try
+ {
+ quick_access_ipc->start(token.get());
+ }
+ catch (...)
+ {
+ Logger::error(L"QuickAccessHost: failed to start IPC server for Quick Access.");
+ reset_state_locked();
+ return;
+ }
+
+ const std::wstring exe_path = get_module_folderpath() + L"\\WinUI3Apps\\PowerToys.QuickAccess.exe";
+ if (GetFileAttributesW(exe_path.c_str()) == INVALID_FILE_ATTRIBUTES)
+ {
+ Logger::warn(L"QuickAccessHost: missing Quick Access executable at {}", exe_path);
+ reset_state_locked();
+ return;
+ }
+
+ const std::wstring command_line = build_command_line(exe_path);
+ std::vector command_line_buffer(command_line.begin(), command_line.end());
+ command_line_buffer.push_back(L'\0');
+ STARTUPINFOW startup_info{};
+ startup_info.cb = sizeof(startup_info);
+ PROCESS_INFORMATION process_info{};
+
+ BOOL created = CreateProcessW(exe_path.c_str(), command_line_buffer.data(), nullptr, nullptr, FALSE, CREATE_SUSPENDED, nullptr, nullptr, &startup_info, &process_info);
+ if (!created)
+ {
+ Logger::error(L"QuickAccessHost: failed to launch Quick Access host. error={}.", GetLastError());
+ reset_state_locked();
+ return;
+ }
+
+ quick_access_process.reset(process_info.hProcess);
+
+ // Assign to job object to ensure the process is killed if the runner exits unexpectedly (e.g. debugging stop)
+ quick_access_job.reset(CreateJobObjectW(nullptr, nullptr));
+ if (quick_access_job)
+ {
+ JOBOBJECT_EXTENDED_LIMIT_INFORMATION jeli = { 0 };
+ jeli.BasicLimitInformation.LimitFlags = JOB_OBJECT_LIMIT_KILL_ON_JOB_CLOSE;
+ if (!SetInformationJobObject(quick_access_job.get(), JobObjectExtendedLimitInformation, &jeli, sizeof(jeli)))
+ {
+ Logger::warn(L"QuickAccessHost: failed to set job object information. error={}", GetLastError());
+ }
+ else
+ {
+ if (!AssignProcessToJobObject(quick_access_job.get(), quick_access_process.get()))
+ {
+ Logger::warn(L"QuickAccessHost: failed to assign process to job object. error={}", GetLastError());
+ }
+ }
+ }
+ else
+ {
+ Logger::warn(L"QuickAccessHost: failed to create job object. error={}", GetLastError());
+ }
+
+ ResumeThread(process_info.hThread);
+ CloseHandle(process_info.hThread);
+ }
+
+ void show()
+ {
+ start();
+ std::scoped_lock lock(quick_access_mutex);
+
+ if (show_event)
+ {
+ if (!SetEvent(show_event.get()))
+ {
+ Logger::warn(L"QuickAccessHost: failed to signal show event. error={}.", GetLastError());
+ }
+ }
+ }
+
+ void stop()
+ {
+ Logger::info(L"QuickAccessHost::stop() called");
+ std::unique_lock lock(quick_access_mutex);
+ if (exit_event)
+ {
+ SetEvent(exit_event.get());
+ }
+
+ if (quick_access_process)
+ {
+ const DWORD wait_result = WaitForSingleObject(quick_access_process.get(), 2000);
+ Logger::info(L"QuickAccessHost::stop: WaitForSingleObject result={}", wait_result);
+ if (wait_result == WAIT_TIMEOUT)
+ {
+ Logger::warn(L"QuickAccessHost: Quick Access process did not exit in time, terminating.");
+ if (!TerminateProcess(quick_access_process.get(), 0))
+ {
+ Logger::error(L"QuickAccessHost: failed to terminate Quick Access process. error={}.", GetLastError());
+ }
+ else
+ {
+ Logger::info(L"QuickAccessHost: TerminateProcess succeeded.");
+ WaitForSingleObject(quick_access_process.get(), 5000);
+ }
+ }
+ else if (wait_result == WAIT_FAILED)
+ {
+ Logger::error(L"QuickAccessHost: failed while waiting for Quick Access process. error={}.", GetLastError());
+ }
+ }
+
+ reset_state_locked();
+ }
+}
diff --git a/src/runner/quick_access_host.h b/src/runner/quick_access_host.h
new file mode 100644
index 0000000000..22a65a9c26
--- /dev/null
+++ b/src/runner/quick_access_host.h
@@ -0,0 +1,12 @@
+#pragma once
+
+#include
+#include
+
+namespace QuickAccessHost
+{
+ void start();
+ void show();
+ void stop();
+ bool is_running();
+}
diff --git a/src/runner/runner.vcxproj b/src/runner/runner.vcxproj
index 57cb55b6bd..23cc1c9d9f 100644
--- a/src/runner/runner.vcxproj
+++ b/src/runner/runner.vcxproj
@@ -70,6 +70,7 @@
+
@@ -85,6 +86,7 @@
+
@@ -139,6 +141,28 @@
+
+
+ true
+ true
+ true
+ true
+ $(OutDir)\svgs
+ $(OutDir)\svgs
+ $(OutDir)\svgs
+ $(OutDir)\svgs
+
+
+ true
+ true
+ true
+ true
+ $(OutDir)\svgs
+ $(OutDir)\svgs
+ $(OutDir)\svgs
+ $(OutDir)\svgs
+
+
diff --git a/src/runner/runner.vcxproj.filters b/src/runner/runner.vcxproj.filters
index 904e213405..4d1e82ff0d 100644
--- a/src/runner/runner.vcxproj.filters
+++ b/src/runner/runner.vcxproj.filters
@@ -48,6 +48,9 @@
Utils
+
+ Utils
+
@@ -102,6 +105,9 @@
Utils
+
+ Utils
+
@@ -116,6 +122,8 @@
+
+
diff --git a/src/runner/settings_window.cpp b/src/runner/settings_window.cpp
index a33b11dbdd..098a039d2c 100644
--- a/src/runner/settings_window.cpp
+++ b/src/runner/settings_window.cpp
@@ -148,16 +148,19 @@ std::optional dispatch_json_action_to_module(const json::JsonObjec
return result;
}
-void send_json_config_to_module(const std::wstring& module_key, const std::wstring& settings)
+void send_json_config_to_module(const std::wstring& module_key, const std::wstring& settings, bool hotkeyUpdated)
{
auto moduleIt = modules().find(module_key);
if (moduleIt != modules().end())
{
moduleIt->second->set_config(settings.c_str());
- moduleIt->second.remove_hotkey_records();
- moduleIt->second.update_hotkeys();
- moduleIt->second.UpdateHotkeyEx();
+ if (hotkeyUpdated)
+ {
+ moduleIt->second.remove_hotkey_records();
+ moduleIt->second.update_hotkeys();
+ moduleIt->second.UpdateHotkeyEx();
+ }
}
}
@@ -166,7 +169,22 @@ void dispatch_json_config_to_modules(const json::JsonObject& powertoys_configs)
for (const auto& powertoy_element : powertoys_configs)
{
const auto element = powertoy_element.Value().Stringify();
- send_json_config_to_module(powertoy_element.Key().c_str(), element.c_str());
+
+ /* As PowerToys Run hotkeys are not registered by the runner, hotkey updates are
+ * triggered only when hotkey properties change to avoid incorrect conflict detection;
+ * otherwise, the existing logic remains.
+ */
+ auto settings = powertoy_element.Value().GetObjectW();
+ bool hotkeyUpdated = true;
+ if (settings.HasKey(L"properties"))
+ {
+ const auto properties = settings.GetNamedObject(L"properties");
+
+ // Currently, only PowerToys Run settings use the 'hotkey_changed' property.
+ json::get(properties, L"hotkey_changed", hotkeyUpdated, true);
+ }
+
+ send_json_config_to_module(powertoy_element.Key().c_str(), element.c_str(), hotkeyUpdated);
}
};
@@ -180,6 +198,8 @@ void dispatch_received_json(const std::wstring& json_to_parse)
return;
}
+ Logger::info(L"dispatch_received_json: {}", json_to_parse);
+
for (const auto& base_element : j)
{
const auto name = base_element.Key();
@@ -188,12 +208,12 @@ void dispatch_received_json(const std::wstring& json_to_parse)
if (name == L"general")
{
apply_general_settings(value.GetObjectW());
- const std::wstring settings_string{ get_all_settings().Stringify().c_str() };
- {
- std::unique_lock lock{ ipc_mutex };
- if (current_settings_ipc)
- current_settings_ipc->send(settings_string);
- }
+ // const std::wstring settings_string{ get_all_settings().Stringify().c_str() };
+ // {
+ // std::unique_lock lock{ ipc_mutex };
+ // if (current_settings_ipc)
+ // current_settings_ipc->send(settings_string);
+ // }
}
else if (name == L"powertoys")
{
@@ -403,7 +423,7 @@ BOOL run_settings_non_elevated(LPCWSTR executable_path, LPWSTR executable_args,
DWORD g_settings_process_id = 0;
-void run_settings_window(bool show_oobe_window, bool show_scoobe_window, std::optional settings_window, bool show_flyout = false, const std::optional& flyout_position = std::nullopt)
+void run_settings_window(bool show_oobe_window, bool show_scoobe_window, std::optional settings_window)
{
g_isLaunchInProgress = true;
@@ -473,22 +493,16 @@ void run_settings_window(bool show_oobe_window, bool show_scoobe_window, std::op
// Arg 9: should scoobe window be shown
std::wstring settings_showScoobe = show_scoobe_window ? L"true" : L"false";
- // Arg 10: should flyout be shown
- std::wstring settings_showFlyout = show_flyout ? L"true" : L"false";
-
- // Arg 11: contains if there's a settings window argument. If true, will add one extra argument with the value to the call.
+ // Arg 10: contains if there's a settings window argument. If true, will add one extra argument with the value to the call.
std::wstring settings_containsSettingsWindow = settings_window.has_value() ? L"true" : L"false";
- // Arg 12: contains if there's flyout coordinates. If true, will add two extra arguments to the call containing the x and y coordinates.
- std::wstring settings_containsFlyoutPosition = flyout_position.has_value() ? L"true" : L"false";
-
- // Args 13, .... : Optional arguments depending on the options presented before. All by the same value.
+ // Args 11, .... : Optional arguments depending on the options presented before. All by the same value.
// create general settings file to initialize the settings file with installation configurations like :
// 1. Run on start up.
PTSettingsHelper::save_general_settings(save_settings.to_json());
- std::wstring executable_args = fmt::format(L"\"{}\" {} {} {} {} {} {} {} {} {} {} {}",
+ std::wstring executable_args = fmt::format(L"\"{}\" {} {} {} {} {} {} {} {} {}",
executable_path,
powertoys_pipe_name,
settings_pipe_name,
@@ -498,9 +512,7 @@ void run_settings_window(bool show_oobe_window, bool show_scoobe_window, std::op
settings_isUserAnAdmin,
settings_showOobe,
settings_showScoobe,
- settings_showFlyout,
- settings_containsSettingsWindow,
- settings_containsFlyoutPosition);
+ settings_containsSettingsWindow);
if (settings_window.has_value())
{
@@ -508,14 +520,6 @@ void run_settings_window(bool show_oobe_window, bool show_scoobe_window, std::op
executable_args.append(settings_window.value());
}
- if (flyout_position)
- {
- executable_args.append(L" ");
- executable_args.append(std::to_wstring(flyout_position.value().x));
- executable_args.append(L" ");
- executable_args.append(std::to_wstring(flyout_position.value().y));
- }
-
BOOL process_created = false;
// Commented out to fix #22659
@@ -666,39 +670,22 @@ void bring_settings_to_front()
EnumWindows(callback, 0);
}
-void open_settings_window(std::optional settings_window, bool show_flyout = false, const std::optional& flyout_position)
+void open_settings_window(std::optional settings_window)
{
if (g_settings_process_id != 0)
{
- if (show_flyout)
+ // nl instead of showing the window, send message to it (flyout might need to be hidden, main setting window activated)
+ // bring_settings_to_front();
+ if (current_settings_ipc)
{
- if (current_settings_ipc)
+ if (settings_window.has_value())
{
- if (!flyout_position.has_value())
- {
- current_settings_ipc->send(L"{\"ShowYourself\":\"flyout\"}");
- }
- else
- {
- current_settings_ipc->send(fmt::format(L"{{\"ShowYourself\":\"flyout\", \"x_position\":{}, \"y_position\":{} }}", std::to_wstring(flyout_position.value().x), std::to_wstring(flyout_position.value().y)));
- }
+ std::wstring msg = L"{\"ShowYourself\":\"" + settings_window.value() + L"\"}";
+ current_settings_ipc->send(msg);
}
- }
- else
- {
- // nl instead of showing the window, send message to it (flyout might need to be hidden, main setting window activated)
- // bring_settings_to_front();
- if (current_settings_ipc)
+ else
{
- if (settings_window.has_value())
- {
- std::wstring msg = L"{\"ShowYourself\":\"" + settings_window.value() + L"\"}";
- current_settings_ipc->send(msg);
- }
- else
- {
- current_settings_ipc->send(L"{\"ShowYourself\":\"Dashboard\"}");
- }
+ current_settings_ipc->send(L"{\"ShowYourself\":\"Dashboard\"}");
}
}
}
@@ -706,8 +693,8 @@ void open_settings_window(std::optional settings_window, bool show
{
if (!g_isLaunchInProgress)
{
- std::thread([settings_window, show_flyout, flyout_position]() {
- run_settings_window(false, false, settings_window, show_flyout, flyout_position);
+ std::thread([settings_window]() {
+ run_settings_window(false, false, settings_window);
}).detach();
}
}
diff --git a/src/runner/settings_window.h b/src/runner/settings_window.h
index 4535367e6d..4da4d70a7a 100644
--- a/src/runner/settings_window.h
+++ b/src/runner/settings_window.h
@@ -42,9 +42,8 @@ enum class ESettingsWindowNames
std::string ESettingsWindowNames_to_string(ESettingsWindowNames value);
ESettingsWindowNames ESettingsWindowNames_from_string(std::string value);
-void open_settings_window(std::optional settings_window, bool show_flyout, const std::optional& flyout_position);
+void open_settings_window(std::optional settings_window);
void close_settings_window();
void open_oobe_window();
void open_scoobe_window();
-void open_flyout();
diff --git a/src/runner/svgs/PowerToysDark.ico b/src/runner/svgs/PowerToysDark.ico
new file mode 100644
index 0000000000..313a3d01ec
Binary files /dev/null and b/src/runner/svgs/PowerToysDark.ico differ
diff --git a/src/runner/svgs/PowerToysWhite.ico b/src/runner/svgs/PowerToysWhite.ico
new file mode 100644
index 0000000000..9e55ac8794
Binary files /dev/null and b/src/runner/svgs/PowerToysWhite.ico differ
diff --git a/src/runner/tray_icon.cpp b/src/runner/tray_icon.cpp
index 749c921659..9cff0c36ff 100644
--- a/src/runner/tray_icon.cpp
+++ b/src/runner/tray_icon.cpp
@@ -5,12 +5,15 @@
#include "general_settings.h"
#include "centralized_hotkeys.h"
#include "centralized_kb_hook.h"
+#include "quick_access_host.h"
+#include "hotkey_conflict_detector.h"
#include
#include
#include
#include
#include
+#include
#include "bug_report.h"
namespace
@@ -37,6 +40,8 @@ namespace
bool double_clicked = false;
POINT tray_icon_click_point;
+ static ThemeListener theme_listener;
+ static bool theme_adaptive_enabled = false;
}
// Struct to fill with callback and the data. The window_proc is responsible for cleaning it.
@@ -69,9 +74,9 @@ void change_menu_item_text(const UINT item_id, wchar_t* new_text)
SetMenuItemInfoW(h_menu, item_id, false, &menuitem);
}
-void open_quick_access_flyout_window(const POINT flyout_position)
+void open_quick_access_flyout_window()
{
- open_settings_window(std::nullopt, true, flyout_position);
+ QuickAccessHost::show();
}
void handle_tray_command(HWND window, const WPARAM command_id, LPARAM lparam)
@@ -81,7 +86,7 @@ void handle_tray_command(HWND window, const WPARAM command_id, LPARAM lparam)
case ID_SETTINGS_MENU_COMMAND:
{
std::wstring settings_window{ winrt::to_hstring(ESettingsWindowNames_to_string(static_cast(lparam))) };
- open_settings_window(settings_window, false);
+ open_settings_window(settings_window);
}
break;
case ID_CLOSE_MENU_COMMAND:
@@ -113,9 +118,7 @@ void handle_tray_command(HWND window, const WPARAM command_id, LPARAM lparam)
}
case ID_QUICK_ACCESS_MENU_COMMAND:
{
- POINT mouse_pointer;
- GetCursorPos(&mouse_pointer);
- open_quick_access_flyout_window(mouse_pointer);
+ open_quick_access_flyout_window();
break;
}
}
@@ -126,7 +129,14 @@ void click_timer_elapsed()
double_click_timer_running = false;
if (!double_clicked)
{
- open_quick_access_flyout_window(tray_icon_click_point);
+ if (get_general_settings().enableQuickAccess)
+ {
+ open_quick_access_flyout_window();
+ }
+ else
+ {
+ open_settings_window(std::nullopt);
+ }
}
}
@@ -218,9 +228,6 @@ LRESULT __stdcall tray_icon_window_proc(HWND window, UINT message, WPARAM wparam
// ignore event if this is the second click of a double click
if (!double_click_timer_running)
{
- // save the cursor position for sending where to show the popup.
- GetCursorPos(&tray_icon_click_point);
-
// start timer for detecting single or double click
double_click_timer_running = true;
double_clicked = false;
@@ -236,7 +243,7 @@ LRESULT __stdcall tray_icon_window_proc(HWND window, UINT message, WPARAM wparam
case WM_LBUTTONDBLCLK:
{
double_clicked = true;
- open_settings_window(std::nullopt, false);
+ open_settings_window(std::nullopt);
break;
}
break;
@@ -262,6 +269,28 @@ LRESULT __stdcall tray_icon_window_proc(HWND window, UINT message, WPARAM wparam
return DefWindowProc(window, message, wparam, lparam);
}
+static HICON get_icon(Theme theme)
+{
+ std::wstring icon_path = get_module_folderpath();
+ icon_path += theme == Theme::Dark ? L"\\svgs\\PowerToysWhite.ico" : L"\\svgs\\PowerToysDark.ico";
+ return static_cast(LoadImage(NULL,
+ icon_path.c_str(),
+ IMAGE_ICON,
+ 0,
+ 0,
+ LR_LOADFROMFILE | LR_DEFAULTSIZE | LR_SHARED));
+}
+
+
+static void handle_theme_change()
+{
+ if (theme_adaptive_enabled)
+ {
+ tray_icon_data.hIcon = get_icon(theme_listener.AppTheme);
+ Shell_NotifyIcon(NIM_MODIFY, &tray_icon_data);
+ }
+}
+
void update_bug_report_menu_status(bool isRunning)
{
if (h_sub_menu != nullptr)
@@ -270,10 +299,11 @@ void update_bug_report_menu_status(bool isRunning)
}
}
-void start_tray_icon(bool isProcessElevated)
+void start_tray_icon(bool isProcessElevated, bool theme_adaptive)
{
+ theme_adaptive_enabled = theme_adaptive;
auto h_instance = reinterpret_cast(&__ImageBase);
- auto icon = LoadIcon(h_instance, MAKEINTRESOURCE(APPICON));
+ HICON const icon = theme_adaptive ? get_icon(theme_listener.AppTheme) : LoadIcon(h_instance, MAKEINTRESOURCE(APPICON));
if (icon)
{
UINT id_tray_icon = 1;
@@ -320,6 +350,7 @@ void start_tray_icon(bool isProcessElevated)
ChangeWindowMessageFilterEx(hwnd, WM_COMMAND, MSGFLT_ALLOW, nullptr);
tray_icon_created = Shell_NotifyIcon(NIM_ADD, &tray_icon_data) == TRUE;
+ theme_listener.AddChangedHandler(&handle_theme_change);
// Register callback to update bug report menu item status
BugReportManager::instance().register_callback([](bool isRunning) {
@@ -341,6 +372,18 @@ void set_tray_icon_visible(bool shouldIconBeVisible)
Shell_NotifyIcon(NIM_MODIFY, &tray_icon_data);
}
+void set_tray_icon_theme_adaptive(bool theme_adaptive)
+{
+ theme_adaptive_enabled = theme_adaptive;
+ auto h_instance = reinterpret_cast(&__ImageBase);
+ HICON const icon = theme_adaptive ? get_icon(theme_listener.AppTheme) : LoadIcon(h_instance, MAKEINTRESOURCE(APPICON));
+ if (icon)
+ {
+ tray_icon_data.hIcon = icon;
+ Shell_NotifyIcon(NIM_MODIFY, &tray_icon_data);
+ }
+}
+
void stop_tray_icon()
{
if (tray_icon_created)
@@ -349,4 +392,37 @@ void stop_tray_icon()
BugReportManager::instance().clear_callbacks();
SendMessage(tray_icon_hwnd, WM_CLOSE, 0, 0);
}
-}
\ No newline at end of file
+}
+void update_quick_access_hotkey(bool enabled, PowerToysSettings::HotkeyObject hotkey)
+{
+ static PowerToysSettings::HotkeyObject current_hotkey;
+ static bool is_registered = false;
+ auto& hkmng = HotkeyConflictDetector::HotkeyConflictManager::GetInstance();
+
+ if (is_registered)
+ {
+ CentralizedKeyboardHook::ClearModuleHotkeys(L"QuickAccess");
+ hkmng.RemoveHotkeyByModule(L"GeneralSettings");
+ is_registered = false;
+ }
+
+ if (enabled && hotkey.get_code() != 0)
+ {
+ HotkeyConflictDetector::Hotkey hk = {
+ hotkey.win_pressed(),
+ hotkey.ctrl_pressed(),
+ hotkey.shift_pressed(),
+ hotkey.alt_pressed(),
+ static_cast(hotkey.get_code())
+ };
+
+ hkmng.AddHotkey(hk, L"GeneralSettings", 0, true);
+ CentralizedKeyboardHook::SetHotkeyAction(L"QuickAccess", hk, []() {
+ open_quick_access_flyout_window();
+ return true;
+ });
+
+ current_hotkey = hotkey;
+ is_registered = true;
+ }
+}
diff --git a/src/runner/tray_icon.h b/src/runner/tray_icon.h
index 4fa7ebfe5a..5ef4c3a75b 100644
--- a/src/runner/tray_icon.h
+++ b/src/runner/tray_icon.h
@@ -1,15 +1,20 @@
#pragma once
#include
#include
+#include
// Start the Tray Icon
-void start_tray_icon(bool isProcessElevated);
+void start_tray_icon(bool isProcessElevated, bool theme_adaptive);
// Change the Tray Icon visibility
void set_tray_icon_visible(bool shouldIconBeVisible);
+// Enable or disable theme adaptive tray icon at runtime
+void set_tray_icon_theme_adaptive(bool theme_adaptive);
// Stop the Tray Icon
void stop_tray_icon();
// Open the Settings Window
-void open_settings_window(std::optional settings_window, bool show_flyout, const std::optional& flyout_position = std::nullopt);
+void open_settings_window(std::optional settings_window);
+// Update Quick Access Hotkey
+void update_quick_access_hotkey(bool enabled, PowerToysSettings::HotkeyObject hotkey);
// Callback type to be called by the tray icon loop
typedef void (*main_loop_callback_function)(PVOID);
// Calls a callback in _callback
diff --git a/src/settings-ui/QuickAccess.UI/Helpers/ModuleGpoHelper.cs b/src/settings-ui/QuickAccess.UI/Helpers/ModuleGpoHelper.cs
new file mode 100644
index 0000000000..25f32e191b
--- /dev/null
+++ b/src/settings-ui/QuickAccess.UI/Helpers/ModuleGpoHelper.cs
@@ -0,0 +1,49 @@
+// 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 global::PowerToys.GPOWrapper;
+using ManagedCommon;
+using Microsoft.PowerToys.Settings.UI.Library;
+
+namespace Microsoft.PowerToys.QuickAccess.Helpers;
+
+internal static class ModuleGpoHelper
+{
+ public static GpoRuleConfigured GetModuleGpoConfiguration(ModuleType moduleType)
+ {
+ return moduleType switch
+ {
+ ModuleType.AdvancedPaste => GPOWrapper.GetConfiguredAdvancedPasteEnabledValue(),
+ ModuleType.AlwaysOnTop => GPOWrapper.GetConfiguredAlwaysOnTopEnabledValue(),
+ ModuleType.Awake => GPOWrapper.GetConfiguredAwakeEnabledValue(),
+ ModuleType.CmdPal => GPOWrapper.GetConfiguredCmdPalEnabledValue(),
+ ModuleType.ColorPicker => GPOWrapper.GetConfiguredColorPickerEnabledValue(),
+ ModuleType.CropAndLock => GPOWrapper.GetConfiguredCropAndLockEnabledValue(),
+ ModuleType.CursorWrap => GPOWrapper.GetConfiguredCursorWrapEnabledValue(),
+ ModuleType.EnvironmentVariables => GPOWrapper.GetConfiguredEnvironmentVariablesEnabledValue(),
+ ModuleType.FancyZones => GPOWrapper.GetConfiguredFancyZonesEnabledValue(),
+ ModuleType.FileLocksmith => GPOWrapper.GetConfiguredFileLocksmithEnabledValue(),
+ ModuleType.FindMyMouse => GPOWrapper.GetConfiguredFindMyMouseEnabledValue(),
+ ModuleType.Hosts => GPOWrapper.GetConfiguredHostsFileEditorEnabledValue(),
+ ModuleType.ImageResizer => GPOWrapper.GetConfiguredImageResizerEnabledValue(),
+ ModuleType.KeyboardManager => GPOWrapper.GetConfiguredKeyboardManagerEnabledValue(),
+ ModuleType.MouseHighlighter => GPOWrapper.GetConfiguredMouseHighlighterEnabledValue(),
+ ModuleType.MouseJump => GPOWrapper.GetConfiguredMouseJumpEnabledValue(),
+ ModuleType.MousePointerCrosshairs => GPOWrapper.GetConfiguredMousePointerCrosshairsEnabledValue(),
+ ModuleType.MouseWithoutBorders => GPOWrapper.GetConfiguredMouseWithoutBordersEnabledValue(),
+ ModuleType.NewPlus => GPOWrapper.GetConfiguredNewPlusEnabledValue(),
+ ModuleType.Peek => GPOWrapper.GetConfiguredPeekEnabledValue(),
+ ModuleType.PowerRename => GPOWrapper.GetConfiguredPowerRenameEnabledValue(),
+ ModuleType.PowerLauncher => GPOWrapper.GetConfiguredPowerLauncherEnabledValue(),
+ ModuleType.PowerAccent => GPOWrapper.GetConfiguredQuickAccentEnabledValue(),
+ ModuleType.Workspaces => GPOWrapper.GetConfiguredWorkspacesEnabledValue(),
+ ModuleType.RegistryPreview => GPOWrapper.GetConfiguredRegistryPreviewEnabledValue(),
+ ModuleType.MeasureTool => GPOWrapper.GetConfiguredScreenRulerEnabledValue(),
+ ModuleType.ShortcutGuide => GPOWrapper.GetConfiguredShortcutGuideEnabledValue(),
+ ModuleType.PowerOCR => GPOWrapper.GetConfiguredTextExtractorEnabledValue(),
+ ModuleType.ZoomIt => GPOWrapper.GetConfiguredZoomItEnabledValue(),
+ _ => GpoRuleConfigured.Unavailable,
+ };
+ }
+}
diff --git a/src/settings-ui/QuickAccess.UI/Helpers/ResourceLoaderInstance.cs b/src/settings-ui/QuickAccess.UI/Helpers/ResourceLoaderInstance.cs
new file mode 100644
index 0000000000..b57d73015b
--- /dev/null
+++ b/src/settings-ui/QuickAccess.UI/Helpers/ResourceLoaderInstance.cs
@@ -0,0 +1,12 @@
+// 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 Microsoft.PowerToys.QuickAccess.Helpers;
+
+internal static class ResourceLoaderInstance
+{
+ internal static ResourceLoader ResourceLoader { get; } = new("PowerToys.QuickAccess.pri");
+}
diff --git a/src/settings-ui/QuickAccess.UI/PowerToys.QuickAccess.csproj b/src/settings-ui/QuickAccess.UI/PowerToys.QuickAccess.csproj
new file mode 100644
index 0000000000..0c20d4d234
--- /dev/null
+++ b/src/settings-ui/QuickAccess.UI/PowerToys.QuickAccess.csproj
@@ -0,0 +1,89 @@
+
+
+
+
+
+ WinExe
+ net9.0-windows10.0.26100.0
+ Microsoft.PowerToys.QuickAccess
+ PowerToys.QuickAccess
+ true
+ None
+ true
+ true
+ false
+ false
+ app.manifest
+ ..\..\..\$(Platform)\$(Configuration)\WinUI3Apps
+ false
+ false
+ enable
+ PowerToys.QuickAccess.pri
+
+
+
+ PowerToys.GPOWrapper
+ $(OutDir)
+
+
+
+
+
+
+
+
+
+
+
+
+ Resources\Styles\Button.xaml
+
+
+ Resources\Styles\TextBlock.xaml
+
+
+ Resources\Themes\Colors.xaml
+
+
+ Resources\Themes\Generic.xaml
+
+
+
+
+
+ Strings\en-us\Resources.resw
+
+
+
+
+
+ Assets\Settings\Icons\%(RecursiveDir)%(Filename)%(Extension)
+ PreserveNewest
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/settings-ui/QuickAccess.UI/QuickAccessLaunchContext.cs b/src/settings-ui/QuickAccess.UI/QuickAccessLaunchContext.cs
new file mode 100644
index 0000000000..2b01947728
--- /dev/null
+++ b/src/settings-ui/QuickAccess.UI/QuickAccessLaunchContext.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;
+using System.Diagnostics.CodeAnalysis;
+
+namespace Microsoft.PowerToys.QuickAccess;
+
+public sealed record QuickAccessLaunchContext(string? ShowEventName, string? ExitEventName, string? RunnerPipeName, string? AppPipeName)
+{
+ public static QuickAccessLaunchContext Parse(string[] args)
+ {
+ string? showEvent = null;
+ string? exitEvent = null;
+ string? runnerPipe = null;
+ string? appPipe = null;
+
+ foreach (var arg in args)
+ {
+ if (TryReadValue(arg, "--show-event", out var value))
+ {
+ showEvent = value;
+ }
+ else if (TryReadValue(arg, "--exit-event", out value))
+ {
+ exitEvent = value;
+ }
+ else if (TryReadValue(arg, "--runner-pipe", out value))
+ {
+ runnerPipe = value;
+ }
+ else if (TryReadValue(arg, "--app-pipe", out value))
+ {
+ appPipe = value;
+ }
+ }
+
+ return new QuickAccessLaunchContext(showEvent, exitEvent, runnerPipe, appPipe);
+ }
+
+ private static bool TryReadValue(string candidate, string key, [NotNullWhen(true)] out string? value)
+ {
+ if (candidate.StartsWith(key, StringComparison.OrdinalIgnoreCase))
+ {
+ if (candidate.Length == key.Length)
+ {
+ value = null;
+ return false;
+ }
+
+ if (candidate[key.Length] == '=')
+ {
+ value = candidate[(key.Length + 1)..].Trim('"');
+ return true;
+ }
+ }
+
+ value = null;
+ return false;
+ }
+}
diff --git a/src/settings-ui/QuickAccess.UI/QuickAccessXAML/App.xaml b/src/settings-ui/QuickAccess.UI/QuickAccessXAML/App.xaml
new file mode 100644
index 0000000000..ab7b3c250a
--- /dev/null
+++ b/src/settings-ui/QuickAccess.UI/QuickAccessXAML/App.xaml
@@ -0,0 +1,60 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/settings-ui/QuickAccess.UI/QuickAccessXAML/App.xaml.cs b/src/settings-ui/QuickAccess.UI/QuickAccessXAML/App.xaml.cs
new file mode 100644
index 0000000000..b21fb04b24
--- /dev/null
+++ b/src/settings-ui/QuickAccess.UI/QuickAccessXAML/App.xaml.cs
@@ -0,0 +1,36 @@
+// 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 Microsoft.UI.Xaml;
+
+namespace Microsoft.PowerToys.QuickAccess;
+
+public partial class App : Application
+{
+ private static MainWindow? _window;
+
+ public App()
+ {
+ InitializeComponent();
+ }
+
+ protected override void OnLaunched(LaunchActivatedEventArgs args)
+ {
+ var launchContext = QuickAccessLaunchContext.Parse(Environment.GetCommandLineArgs());
+ _window = new MainWindow(launchContext);
+ _window.Closed += OnWindowClosed;
+ _window.Activate();
+ }
+
+ private static void OnWindowClosed(object sender, WindowEventArgs args)
+ {
+ if (sender is MainWindow window)
+ {
+ window.Closed -= OnWindowClosed;
+ }
+
+ _window = null;
+ }
+}
diff --git a/src/settings-ui/QuickAccess.UI/QuickAccessXAML/Flyout/AppsListPage.xaml b/src/settings-ui/QuickAccess.UI/QuickAccessXAML/Flyout/AppsListPage.xaml
new file mode 100644
index 0000000000..c4d010560d
--- /dev/null
+++ b/src/settings-ui/QuickAccess.UI/QuickAccessXAML/Flyout/AppsListPage.xaml
@@ -0,0 +1,83 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/settings-ui/QuickAccess.UI/QuickAccessXAML/Flyout/AppsListPage.xaml.cs b/src/settings-ui/QuickAccess.UI/QuickAccessXAML/Flyout/AppsListPage.xaml.cs
new file mode 100644
index 0000000000..1212855fe4
--- /dev/null
+++ b/src/settings-ui/QuickAccess.UI/QuickAccessXAML/Flyout/AppsListPage.xaml.cs
@@ -0,0 +1,64 @@
+// 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 Microsoft.PowerToys.QuickAccess.ViewModels;
+using Microsoft.PowerToys.Settings.UI.Library;
+using Microsoft.UI.Xaml;
+using Microsoft.UI.Xaml.Controls;
+using Microsoft.UI.Xaml.Media.Animation;
+using Microsoft.UI.Xaml.Navigation;
+
+namespace Microsoft.PowerToys.QuickAccess.Flyout;
+
+public sealed partial class AppsListPage : Page
+{
+ private FlyoutNavigationContext? _context;
+
+ public AppsListPage()
+ {
+ InitializeComponent();
+ }
+
+ public AllAppsViewModel ViewModel { get; private set; } = default!;
+
+ protected override void OnNavigatedTo(NavigationEventArgs e)
+ {
+ base.OnNavigatedTo(e);
+
+ if (e.Parameter is FlyoutNavigationContext context)
+ {
+ _context = context;
+ ViewModel = context.AllAppsViewModel;
+ DataContext = ViewModel;
+ ViewModel.RefreshSettings();
+ }
+ }
+
+ private void BackButton_Click(object sender, RoutedEventArgs e)
+ {
+ if (_context == null || Frame == null)
+ {
+ return;
+ }
+
+ Frame.Navigate(typeof(LaunchPage), _context, new SlideNavigationTransitionInfo { Effect = SlideNavigationTransitionEffect.FromLeft });
+ }
+
+ private void SortAlphabetical_Click(object sender, RoutedEventArgs e)
+ {
+ if (ViewModel != null)
+ {
+ ViewModel.DashboardSortOrder = DashboardSortOrder.Alphabetical;
+ }
+ }
+
+ private void SortByStatus_Click(object sender, RoutedEventArgs e)
+ {
+ if (ViewModel != null)
+ {
+ ViewModel.DashboardSortOrder = DashboardSortOrder.ByStatus;
+ }
+ }
+}
diff --git a/src/settings-ui/QuickAccess.UI/QuickAccessXAML/Flyout/FlyoutNavigationContext.cs b/src/settings-ui/QuickAccess.UI/QuickAccessXAML/Flyout/FlyoutNavigationContext.cs
new file mode 100644
index 0000000000..3aab3ca334
--- /dev/null
+++ b/src/settings-ui/QuickAccess.UI/QuickAccessXAML/Flyout/FlyoutNavigationContext.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.
+
+using Microsoft.PowerToys.QuickAccess.Services;
+using Microsoft.PowerToys.QuickAccess.ViewModels;
+
+namespace Microsoft.PowerToys.QuickAccess.Flyout;
+
+internal sealed record FlyoutNavigationContext(
+ LauncherViewModel LauncherViewModel,
+ AllAppsViewModel AllAppsViewModel,
+ IQuickAccessCoordinator Coordinator);
diff --git a/src/settings-ui/Settings.UI/SettingsXAML/Flyout/LaunchPage.xaml b/src/settings-ui/QuickAccess.UI/QuickAccessXAML/Flyout/LaunchPage.xaml
similarity index 64%
rename from src/settings-ui/Settings.UI/SettingsXAML/Flyout/LaunchPage.xaml
rename to src/settings-ui/QuickAccess.UI/QuickAccessXAML/Flyout/LaunchPage.xaml
index 8dea006b08..f2f53b57d4 100644
--- a/src/settings-ui/Settings.UI/SettingsXAML/Flyout/LaunchPage.xaml
+++ b/src/settings-ui/QuickAccess.UI/QuickAccessXAML/Flyout/LaunchPage.xaml
@@ -1,5 +1,5 @@
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/settings-ui/Settings.UI.Controls/ModuleList/ModuleList.xaml.cs b/src/settings-ui/Settings.UI.Controls/ModuleList/ModuleList.xaml.cs
new file mode 100644
index 0000000000..f9d36a7e69
--- /dev/null
+++ b/src/settings-ui/Settings.UI.Controls/ModuleList/ModuleList.xaml.cs
@@ -0,0 +1,57 @@
+// 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;
+
+namespace Microsoft.PowerToys.Settings.UI.Controls
+{
+ public sealed partial class ModuleList : UserControl
+ {
+ public ModuleList()
+ {
+ this.InitializeComponent();
+ }
+
+ public Thickness DividerThickness
+ {
+ get => (Thickness)GetValue(DividerThicknessProperty);
+ set => SetValue(DividerThicknessProperty, value);
+ }
+
+ public static readonly DependencyProperty DividerThicknessProperty = DependencyProperty.Register(nameof(DividerThickness), typeof(Thickness), typeof(ModuleList), new PropertyMetadata(new Thickness(0, 1, 0, 0)));
+
+ public bool IsItemClickable
+ {
+ get => (bool)GetValue(IsItemClickableProperty);
+ set => SetValue(IsItemClickableProperty, value);
+ }
+
+ public static readonly DependencyProperty IsItemClickableProperty = DependencyProperty.Register(nameof(IsItemClickable), typeof(bool), typeof(ModuleList), new PropertyMetadata(true));
+
+ public object ItemsSource
+ {
+ get => (object)GetValue(ItemsSourceProperty);
+ set => SetValue(ItemsSourceProperty, value);
+ }
+
+ public static readonly DependencyProperty ItemsSourceProperty = DependencyProperty.Register(nameof(ItemsSource), typeof(object), typeof(ModuleList), new PropertyMetadata(null));
+
+ public ModuleListSortOption SortOption
+ {
+ get => (ModuleListSortOption)GetValue(SortOptionProperty);
+ set => SetValue(SortOptionProperty, value);
+ }
+
+ public static readonly DependencyProperty SortOptionProperty = DependencyProperty.Register(nameof(SortOption), typeof(ModuleListSortOption), typeof(ModuleList), new PropertyMetadata(ModuleListSortOption.Alphabetical));
+
+ private void OnSettingsCardClick(object sender, RoutedEventArgs e)
+ {
+ if (sender is FrameworkElement element && element.Tag is ModuleListItem item)
+ {
+ item.ClickCommand?.Execute(item.Tag);
+ }
+ }
+ }
+}
diff --git a/src/settings-ui/Settings.UI.Controls/ModuleList/ModuleListItem.cs b/src/settings-ui/Settings.UI.Controls/ModuleList/ModuleListItem.cs
new file mode 100644
index 0000000000..ef92e3e592
--- /dev/null
+++ b/src/settings-ui/Settings.UI.Controls/ModuleList/ModuleListItem.cs
@@ -0,0 +1,119 @@
+// 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.ComponentModel;
+using System.Runtime.CompilerServices;
+using System.Windows.Input;
+
+namespace Microsoft.PowerToys.Settings.UI.Controls
+{
+ public class ModuleListItem : INotifyPropertyChanged
+ {
+ private bool _isEnabled;
+ private string _label = string.Empty;
+ private string _icon = string.Empty;
+ private bool _isNew;
+ private bool _isLocked;
+ private object? _tag;
+ private ICommand? _clickCommand;
+
+ public virtual string Label
+ {
+ get => _label;
+ set
+ {
+ if (_label != value)
+ {
+ _label = value;
+ OnPropertyChanged();
+ }
+ }
+ }
+
+ public virtual string Icon
+ {
+ get => _icon;
+ set
+ {
+ if (_icon != value)
+ {
+ _icon = value;
+ OnPropertyChanged();
+ }
+ }
+ }
+
+ public virtual bool IsNew
+ {
+ get => _isNew;
+ set
+ {
+ if (_isNew != value)
+ {
+ _isNew = value;
+ OnPropertyChanged();
+ }
+ }
+ }
+
+ public virtual bool IsLocked
+ {
+ get => _isLocked;
+ set
+ {
+ if (_isLocked != value)
+ {
+ _isLocked = value;
+ OnPropertyChanged();
+ }
+ }
+ }
+
+ public virtual bool IsEnabled
+ {
+ get => _isEnabled;
+ set
+ {
+ if (_isEnabled != value)
+ {
+ _isEnabled = value;
+ OnPropertyChanged();
+ }
+ }
+ }
+
+ public virtual object? Tag
+ {
+ get => _tag;
+ set
+ {
+ if (_tag != value)
+ {
+ _tag = value;
+ OnPropertyChanged();
+ }
+ }
+ }
+
+ public virtual ICommand? ClickCommand
+ {
+ get => _clickCommand;
+ set
+ {
+ if (_clickCommand != value)
+ {
+ _clickCommand = value;
+ OnPropertyChanged();
+ }
+ }
+ }
+
+ public event PropertyChangedEventHandler? PropertyChanged;
+
+ protected void OnPropertyChanged([CallerMemberName] string? propertyName = null)
+ {
+ PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
+ }
+ }
+}
diff --git a/src/settings-ui/Settings.UI.Controls/ModuleList/ModuleListSortOption.cs b/src/settings-ui/Settings.UI.Controls/ModuleList/ModuleListSortOption.cs
new file mode 100644
index 0000000000..233c891d6b
--- /dev/null
+++ b/src/settings-ui/Settings.UI.Controls/ModuleList/ModuleListSortOption.cs
@@ -0,0 +1,12 @@
+// 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 Microsoft.PowerToys.Settings.UI.Controls
+{
+ public enum ModuleListSortOption
+ {
+ Alphabetical,
+ ByStatus,
+ }
+}
diff --git a/src/settings-ui/Settings.UI/SettingsXAML/Controls/Dashboard/Card.xaml b/src/settings-ui/Settings.UI.Controls/Primitives/Card.xaml
similarity index 97%
rename from src/settings-ui/Settings.UI/SettingsXAML/Controls/Dashboard/Card.xaml
rename to src/settings-ui/Settings.UI.Controls/Primitives/Card.xaml
index 9563bfceb3..508d94b7ca 100644
--- a/src/settings-ui/Settings.UI/SettingsXAML/Controls/Dashboard/Card.xaml
+++ b/src/settings-ui/Settings.UI.Controls/Primitives/Card.xaml
@@ -21,11 +21,11 @@
BorderThickness="{x:Bind BorderThickness, Mode=OneWay}"
CornerRadius="{x:Bind CornerRadius, Mode=OneWay}">
-
+
-
+
diff --git a/src/settings-ui/Settings.UI/SettingsXAML/Controls/Dashboard/Card.xaml.cs b/src/settings-ui/Settings.UI.Controls/Primitives/Card.xaml.cs
similarity index 94%
rename from src/settings-ui/Settings.UI/SettingsXAML/Controls/Dashboard/Card.xaml.cs
rename to src/settings-ui/Settings.UI.Controls/Primitives/Card.xaml.cs
index 1959a70445..ebf04c4e89 100644
--- a/src/settings-ui/Settings.UI/SettingsXAML/Controls/Dashboard/Card.xaml.cs
+++ b/src/settings-ui/Settings.UI.Controls/Primitives/Card.xaml.cs
@@ -11,9 +11,9 @@ namespace Microsoft.PowerToys.Settings.UI.Controls
{
public static readonly DependencyProperty TitleContentProperty = DependencyProperty.Register(nameof(TitleContent), typeof(object), typeof(Card), new PropertyMetadata(defaultValue: null, OnVisualPropertyChanged));
- public object TitleContent
+ public object? TitleContent
{
- get => (object)GetValue(TitleContentProperty);
+ get => (object?)GetValue(TitleContentProperty);
set => SetValue(TitleContentProperty, value);
}
@@ -34,7 +34,7 @@ namespace Microsoft.PowerToys.Settings.UI.Controls
set => SetValue(ContentProperty, value);
}
- public static readonly DependencyProperty DividerVisibilityProperty = DependencyProperty.Register(nameof(DividerVisibility), typeof(Visibility), typeof(Card), new PropertyMetadata(defaultValue: null));
+ public static readonly DependencyProperty DividerVisibilityProperty = DependencyProperty.Register(nameof(DividerVisibility), typeof(Visibility), typeof(Card), new PropertyMetadata(defaultValue: Visibility.Visible));
public Visibility DividerVisibility
{
@@ -66,7 +66,6 @@ namespace Microsoft.PowerToys.Settings.UI.Controls
else
{
VisualStateManager.GoToState(this, "TitleGridVisible", true);
- DividerVisibility = Visibility.Visible;
}
}
}
diff --git a/src/settings-ui/Settings.UI/SettingsXAML/Controls/FlyoutMenuButton.xaml.cs b/src/settings-ui/Settings.UI.Controls/Primitives/FlyoutMenuButton.cs
similarity index 96%
rename from src/settings-ui/Settings.UI/SettingsXAML/Controls/FlyoutMenuButton.xaml.cs
rename to src/settings-ui/Settings.UI.Controls/Primitives/FlyoutMenuButton.cs
index bb1767e01e..f313506f23 100644
--- a/src/settings-ui/Settings.UI/SettingsXAML/Controls/FlyoutMenuButton.xaml.cs
+++ b/src/settings-ui/Settings.UI.Controls/Primitives/FlyoutMenuButton.cs
@@ -1,4 +1,4 @@
-// Copyright (c) Microsoft Corporation
+// 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.
diff --git a/src/settings-ui/Settings.UI.Controls/QuickAccess/IQuickAccessLauncher.cs b/src/settings-ui/Settings.UI.Controls/QuickAccess/IQuickAccessLauncher.cs
new file mode 100644
index 0000000000..8c35889c94
--- /dev/null
+++ b/src/settings-ui/Settings.UI.Controls/QuickAccess/IQuickAccessLauncher.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.
+
+using ManagedCommon;
+
+namespace Microsoft.PowerToys.Settings.UI.Controls
+{
+ public interface IQuickAccessLauncher
+ {
+ bool Launch(ModuleType moduleType);
+ }
+}
diff --git a/src/settings-ui/Settings.UI.Controls/QuickAccess/QuickAccessItem.cs b/src/settings-ui/Settings.UI.Controls/QuickAccess/QuickAccessItem.cs
new file mode 100644
index 0000000000..b639d87802
--- /dev/null
+++ b/src/settings-ui/Settings.UI.Controls/QuickAccess/QuickAccessItem.cs
@@ -0,0 +1,69 @@
+// 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.Windows.Input;
+using Microsoft.PowerToys.Settings.UI.Library.Helpers;
+using Microsoft.UI.Xaml;
+
+namespace Microsoft.PowerToys.Settings.UI.Controls
+{
+ public sealed class QuickAccessItem : Observable
+ {
+ private string _title = string.Empty;
+
+ public string Title
+ {
+ get => _title;
+ set => Set(ref _title, value);
+ }
+
+ private string _description = string.Empty;
+
+ public string Description
+ {
+ get => _description;
+ set => Set(ref _description, value);
+ }
+
+ private string _icon = string.Empty;
+
+ public string Icon
+ {
+ get => _icon;
+ set => Set(ref _icon, value);
+ }
+
+ private ICommand? _command;
+
+ public ICommand? Command
+ {
+ get => _command;
+ set => Set(ref _command, value);
+ }
+
+ private object? _commandParameter;
+
+ public object? CommandParameter
+ {
+ get => _commandParameter;
+ set => Set(ref _commandParameter, value);
+ }
+
+ private bool _visible = true;
+
+ public bool Visible
+ {
+ get => _visible;
+ set => Set(ref _visible, value);
+ }
+
+ private object? _tag;
+
+ public object? Tag
+ {
+ get => _tag;
+ set => Set(ref _tag, value);
+ }
+ }
+}
diff --git a/src/settings-ui/Settings.UI.Controls/QuickAccess/QuickAccessLauncher.cs b/src/settings-ui/Settings.UI.Controls/QuickAccess/QuickAccessLauncher.cs
new file mode 100644
index 0000000000..4799ff624f
--- /dev/null
+++ b/src/settings-ui/Settings.UI.Controls/QuickAccess/QuickAccessLauncher.cs
@@ -0,0 +1,121 @@
+// 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.Threading;
+using ManagedCommon;
+using Microsoft.PowerToys.Settings.UI.Library;
+using PowerToys.Interop;
+
+namespace Microsoft.PowerToys.Settings.UI.Controls
+{
+ public class QuickAccessLauncher : IQuickAccessLauncher
+ {
+ private readonly bool _isElevated;
+
+ public QuickAccessLauncher(bool isElevated)
+ {
+ _isElevated = isElevated;
+ }
+
+ public virtual bool Launch(ModuleType moduleType)
+ {
+ switch (moduleType)
+ {
+ case ModuleType.ColorPicker:
+ using (var eventHandle = new EventWaitHandle(false, EventResetMode.AutoReset, Constants.ShowColorPickerSharedEvent()))
+ {
+ eventHandle.Set();
+ }
+
+ return true;
+ case ModuleType.EnvironmentVariables:
+ {
+ bool launchAdmin = SettingsRepository.GetInstance(SettingsUtils.Default).SettingsConfig.Properties.LaunchAdministrator;
+ string eventName = !_isElevated && launchAdmin
+ ? Constants.ShowEnvironmentVariablesAdminSharedEvent()
+ : Constants.ShowEnvironmentVariablesSharedEvent();
+
+ using (var eventHandle = new EventWaitHandle(false, EventResetMode.AutoReset, eventName))
+ {
+ eventHandle.Set();
+ }
+ }
+
+ return true;
+ case ModuleType.FancyZones:
+ using (var eventHandle = new EventWaitHandle(false, EventResetMode.AutoReset, Constants.FZEToggleEvent()))
+ {
+ eventHandle.Set();
+ }
+
+ return true;
+ case ModuleType.Hosts:
+ {
+ bool launchAdmin = SettingsRepository.GetInstance(SettingsUtils.Default).SettingsConfig.Properties.LaunchAdministrator;
+ string eventName = !_isElevated && launchAdmin
+ ? Constants.ShowHostsAdminSharedEvent()
+ : Constants.ShowHostsSharedEvent();
+
+ using (var eventHandle = new EventWaitHandle(false, EventResetMode.AutoReset, eventName))
+ {
+ eventHandle.Set();
+ }
+ }
+
+ return true;
+ case ModuleType.PowerLauncher:
+ using (var eventHandle = new EventWaitHandle(false, EventResetMode.AutoReset, Constants.PowerLauncherSharedEvent()))
+ {
+ eventHandle.Set();
+ }
+
+ return true;
+ case ModuleType.PowerOCR:
+ using (var eventHandle = new EventWaitHandle(false, EventResetMode.AutoReset, Constants.ShowPowerOCRSharedEvent()))
+ {
+ eventHandle.Set();
+ }
+
+ return true;
+ case ModuleType.RegistryPreview:
+ using (var eventHandle = new EventWaitHandle(false, EventResetMode.AutoReset, Constants.RegistryPreviewTriggerEvent()))
+ {
+ eventHandle.Set();
+ }
+
+ return true;
+ case ModuleType.MeasureTool:
+ using (var eventHandle = new EventWaitHandle(false, EventResetMode.AutoReset, Constants.MeasureToolTriggerEvent()))
+ {
+ eventHandle.Set();
+ }
+
+ return true;
+ case ModuleType.ShortcutGuide:
+ using (var eventHandle = new EventWaitHandle(false, EventResetMode.AutoReset, Constants.ShortcutGuideTriggerEvent()))
+ {
+ eventHandle.Set();
+ }
+
+ return true;
+ case ModuleType.CmdPal:
+ using (var eventHandle = new EventWaitHandle(false, EventResetMode.AutoReset, Constants.ShowCmdPalEvent()))
+ {
+ eventHandle.Set();
+ }
+
+ return true;
+ case ModuleType.Workspaces:
+ using (var eventHandle = new EventWaitHandle(false, EventResetMode.AutoReset, Constants.WorkspacesLaunchEditorEvent()))
+ {
+ eventHandle.Set();
+ }
+
+ return true;
+ default:
+ return false;
+ }
+ }
+ }
+}
diff --git a/src/settings-ui/Settings.UI.Controls/QuickAccess/QuickAccessList.xaml b/src/settings-ui/Settings.UI.Controls/QuickAccess/QuickAccessList.xaml
new file mode 100644
index 0000000000..415ae22684
--- /dev/null
+++ b/src/settings-ui/Settings.UI.Controls/QuickAccess/QuickAccessList.xaml
@@ -0,0 +1,51 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/settings-ui/Settings.UI.Controls/QuickAccess/QuickAccessList.xaml.cs b/src/settings-ui/Settings.UI.Controls/QuickAccess/QuickAccessList.xaml.cs
new file mode 100644
index 0000000000..34c4bad013
--- /dev/null
+++ b/src/settings-ui/Settings.UI.Controls/QuickAccess/QuickAccessList.xaml.cs
@@ -0,0 +1,26 @@
+// 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 Microsoft.UI.Xaml;
+using Microsoft.UI.Xaml.Controls;
+
+namespace Microsoft.PowerToys.Settings.UI.Controls
+{
+ public sealed partial class QuickAccessList : UserControl
+ {
+ public QuickAccessList()
+ {
+ this.InitializeComponent();
+ }
+
+ public object ItemsSource
+ {
+ get => (object)GetValue(ItemsSourceProperty);
+ set => SetValue(ItemsSourceProperty, value);
+ }
+
+ public static readonly DependencyProperty ItemsSourceProperty = DependencyProperty.Register(nameof(ItemsSource), typeof(object), typeof(QuickAccessList), new PropertyMetadata(null));
+ }
+}
diff --git a/src/settings-ui/Settings.UI/ViewModels/Flyout/LauncherViewModel.cs b/src/settings-ui/Settings.UI.Controls/QuickAccess/QuickAccessViewModel.cs
similarity index 52%
rename from src/settings-ui/Settings.UI/ViewModels/Flyout/LauncherViewModel.cs
rename to src/settings-ui/Settings.UI.Controls/QuickAccess/QuickAccessViewModel.cs
index 931600d4f0..06d2dd9d07 100644
--- a/src/settings-ui/Settings.UI/ViewModels/Flyout/LauncherViewModel.cs
+++ b/src/settings-ui/Settings.UI.Controls/QuickAccess/QuickAccessViewModel.cs
@@ -4,41 +4,61 @@
using System;
using System.Collections.ObjectModel;
-
-using global::PowerToys.GPOWrapper;
using ManagedCommon;
-using Microsoft.PowerToys.Settings.UI.Helpers;
using Microsoft.PowerToys.Settings.UI.Library;
using Microsoft.PowerToys.Settings.UI.Library.Helpers;
using Microsoft.PowerToys.Settings.UI.Library.Interfaces;
+using Microsoft.PowerToys.Settings.UI.Library.ViewModels.Commands;
+using Microsoft.UI.Dispatching;
using Microsoft.Windows.ApplicationModel.Resources;
-namespace Microsoft.PowerToys.Settings.UI.ViewModels
+namespace Microsoft.PowerToys.Settings.UI.Controls
{
- public partial class LauncherViewModel : Observable
+ public partial class QuickAccessViewModel : Observable
{
- public bool IsUpdateAvailable { get; set; }
+ private readonly ISettingsRepository _settingsRepository;
+ private readonly IQuickAccessLauncher _launcher;
+ private readonly Func _isModuleGpoDisabled;
+ private readonly ResourceLoader _resourceLoader;
+ private readonly DispatcherQueue _dispatcherQueue;
+ private GeneralSettings _generalSettings;
- public ObservableCollection FlyoutMenuItems { get; set; }
+ public ObservableCollection Items { get; } = new();
- private GeneralSettings generalSettingsConfig;
- private UpdatingSettings updatingSettingsConfig;
- private ISettingsRepository _settingsRepository;
- private ResourceLoader resourceLoader;
-
- private Func SendIPCMessage { get; }
-
- public LauncherViewModel(ISettingsRepository settingsRepository, Func ipcMSGCallBackFunc)
+ public QuickAccessViewModel(
+ ISettingsRepository settingsRepository,
+ IQuickAccessLauncher launcher,
+ Func isModuleGpoDisabled,
+ ResourceLoader resourceLoader)
{
_settingsRepository = settingsRepository;
- generalSettingsConfig = settingsRepository.SettingsConfig;
- generalSettingsConfig.AddEnabledModuleChangeNotification(ModuleEnabledChanged);
+ _launcher = launcher;
+ _isModuleGpoDisabled = isModuleGpoDisabled;
+ _resourceLoader = resourceLoader;
+ _dispatcherQueue = DispatcherQueue.GetForCurrentThread();
- // set the callback functions value to handle outgoing IPC message.
- SendIPCMessage = ipcMSGCallBackFunc;
- resourceLoader = ResourceLoaderInstance.ResourceLoader;
- FlyoutMenuItems = new ObservableCollection();
+ _generalSettings = _settingsRepository.SettingsConfig;
+ _generalSettings.AddEnabledModuleChangeNotification(ModuleEnabledChanged);
+ _settingsRepository.SettingsChanged += OnSettingsChanged;
+ InitializeItems();
+ }
+
+ private void OnSettingsChanged(GeneralSettings newSettings)
+ {
+ if (_dispatcherQueue != null)
+ {
+ _dispatcherQueue.TryEnqueue(() =>
+ {
+ _generalSettings = newSettings;
+ _generalSettings.AddEnabledModuleChangeNotification(ModuleEnabledChanged);
+ RefreshItemsVisibility();
+ });
+ }
+ }
+
+ private void InitializeItems()
+ {
AddFlyoutMenuItem(ModuleType.ColorPicker);
AddFlyoutMenuItem(ModuleType.CmdPal);
AddFlyoutMenuItem(ModuleType.EnvironmentVariables);
@@ -51,40 +71,50 @@ namespace Microsoft.PowerToys.Settings.UI.ViewModels
AddFlyoutMenuItem(ModuleType.MeasureTool);
AddFlyoutMenuItem(ModuleType.ShortcutGuide);
AddFlyoutMenuItem(ModuleType.Workspaces);
-
- updatingSettingsConfig = UpdatingSettings.LoadSettings();
- if (updatingSettingsConfig == null)
- {
- updatingSettingsConfig = new UpdatingSettings();
- }
-
- if (updatingSettingsConfig.State == UpdatingSettings.UpdatingState.ReadyToInstall || updatingSettingsConfig.State == UpdatingSettings.UpdatingState.ReadyToDownload)
- {
- IsUpdateAvailable = true;
- }
- else
- {
- IsUpdateAvailable = false;
- }
}
private void AddFlyoutMenuItem(ModuleType moduleType)
{
- if (ModuleHelper.GetModuleGpoConfiguration(moduleType) == GpoRuleConfigured.Disabled)
+ if (_isModuleGpoDisabled(moduleType))
{
return;
}
- FlyoutMenuItems.Add(new FlyoutMenuItem()
+ Items.Add(new QuickAccessItem
{
- Label = resourceLoader.GetString(ModuleHelper.GetModuleLabelResourceName(moduleType)),
+ Title = _resourceLoader.GetString(Microsoft.PowerToys.Settings.UI.Library.Helpers.ModuleHelper.GetModuleLabelResourceName(moduleType)),
Tag = moduleType,
- Visible = ModuleHelper.GetIsModuleEnabled(generalSettingsConfig, moduleType),
- ToolTip = GetModuleToolTip(moduleType),
- Icon = ModuleHelper.GetModuleTypeFluentIconName(moduleType),
+ Visible = Microsoft.PowerToys.Settings.UI.Library.Helpers.ModuleHelper.GetIsModuleEnabled(_generalSettings, moduleType),
+ Description = GetModuleToolTip(moduleType),
+ Icon = Microsoft.PowerToys.Settings.UI.Library.Helpers.ModuleHelper.GetModuleTypeFluentIconName(moduleType),
+ Command = new RelayCommand(() => _launcher.Launch(moduleType)),
});
}
+ private void ModuleEnabledChanged()
+ {
+ if (_dispatcherQueue != null)
+ {
+ _dispatcherQueue.TryEnqueue(() =>
+ {
+ _generalSettings = _settingsRepository.SettingsConfig;
+ _generalSettings.AddEnabledModuleChangeNotification(ModuleEnabledChanged);
+ RefreshItemsVisibility();
+ });
+ }
+ }
+
+ private void RefreshItemsVisibility()
+ {
+ foreach (var item in Items)
+ {
+ if (item.Tag is ModuleType moduleType)
+ {
+ item.Visible = Microsoft.PowerToys.Settings.UI.Library.Helpers.ModuleHelper.GetIsModuleEnabled(_generalSettings, moduleType);
+ }
+ }
+ }
+
private string GetModuleToolTip(ModuleType moduleType)
{
return moduleType switch
@@ -101,16 +131,6 @@ namespace Microsoft.PowerToys.Settings.UI.ViewModels
};
}
- private void ModuleEnabledChanged()
- {
- generalSettingsConfig = _settingsRepository.SettingsConfig;
- generalSettingsConfig.AddEnabledModuleChangeNotification(ModuleEnabledChanged);
- foreach (FlyoutMenuItem item in FlyoutMenuItems)
- {
- item.Visible = ModuleHelper.GetIsModuleEnabled(generalSettingsConfig, item.Tag);
- }
- }
-
private string GetShortcutGuideToolTip()
{
var shortcutGuideSettings = SettingsRepository.GetInstance(SettingsUtils.Default).SettingsConfig;
@@ -118,15 +138,5 @@ namespace Microsoft.PowerToys.Settings.UI.ViewModels
? "Win"
: shortcutGuideSettings.Properties.OpenShortcutGuide.ToString();
}
-
- internal void StartBugReport()
- {
- SendIPCMessage("{\"bugreport\": 0 }");
- }
-
- internal void KillRunner()
- {
- SendIPCMessage("{\"killrunner\": 0 }");
- }
}
}
diff --git a/src/settings-ui/Settings.UI.Controls/Settings.UI.Controls.csproj b/src/settings-ui/Settings.UI.Controls/Settings.UI.Controls.csproj
new file mode 100644
index 0000000000..fac43d56a4
--- /dev/null
+++ b/src/settings-ui/Settings.UI.Controls/Settings.UI.Controls.csproj
@@ -0,0 +1,32 @@
+
+
+
+
+
+ Library
+ net9.0-windows10.0.26100.0
+ Microsoft.PowerToys.Settings.UI.Controls
+ PowerToys.Settings.UI.Controls
+ true
+ true
+ true
+ PowerToys.Settings.UI.Controls.pri
+ enable
+ x64;ARM64
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/settings-ui/Settings.UI.Controls/Strings/en-us/Resources.resw b/src/settings-ui/Settings.UI.Controls/Strings/en-us/Resources.resw
new file mode 100644
index 0000000000..6664b14936
--- /dev/null
+++ b/src/settings-ui/Settings.UI.Controls/Strings/en-us/Resources.resw
@@ -0,0 +1,138 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ text/microsoft-resx
+
+
+ 2.0
+
+
+ System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089
+
+
+ System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089
+
+
+ Sort utilities
+
+
+ Alphabetically
+
+
+ By status
+
+
+ Sort utilities
+
+
+ NEW
+
+
+ This setting is managed by your organization
+
+
\ No newline at end of file
diff --git a/src/settings-ui/Settings.UI/SettingsXAML/Controls/FlyoutMenuButton.xaml b/src/settings-ui/Settings.UI.Controls/Themes/Generic.xaml
similarity index 95%
rename from src/settings-ui/Settings.UI/SettingsXAML/Controls/FlyoutMenuButton.xaml
rename to src/settings-ui/Settings.UI.Controls/Themes/Generic.xaml
index 50c0e4007c..466ad4475b 100644
--- a/src/settings-ui/Settings.UI/SettingsXAML/Controls/FlyoutMenuButton.xaml
+++ b/src/settings-ui/Settings.UI.Controls/Themes/Generic.xaml
@@ -1,14 +1,9 @@
-
-
-
-
-
-