diff --git a/.github/actions/spell-check/expect.txt b/.github/actions/spell-check/expect.txt
index d1b2de49dde1..883ff38071f2 100644
--- a/.github/actions/spell-check/expect.txt
+++ b/.github/actions/spell-check/expect.txt
@@ -763,8 +763,10 @@ hglobal
hhk
HHmmss
HHOOK
+hhx
HICON
HIDEWINDOW
+highlighter
HIMAGELIST
himl
hinst
@@ -2084,6 +2086,7 @@ Switchbetweenvirtualdesktops
SWP
swprintf
SWRESTORE
+swscanf
SYMED
SYMOPT
SYNCMFT
diff --git a/.pipelines/pipeline.user.windows.yml b/.pipelines/pipeline.user.windows.yml
index 22f6cf723ae6..b727524f29a8 100644
--- a/.pipelines/pipeline.user.windows.yml
+++ b/.pipelines/pipeline.user.windows.yml
@@ -171,6 +171,7 @@ build:
- 'modules\launcher\Wox.Infrastructure.dll'
- 'modules\launcher\Wox.Plugin.dll'
- 'modules\MouseUtils\FindMyMouse.dll'
+ - 'modules\MouseUtils\MouseHighlighter.dll'
- 'modules\PowerRename\PowerRenameExt.dll'
- 'modules\PowerRename\PowerRenameUILib.dll'
- 'modules\PowerRename\PowerRename.exe'
diff --git a/PowerToys.sln b/PowerToys.sln
index e185f09f71ed..5b023cf4370e 100644
--- a/PowerToys.sln
+++ b/PowerToys.sln
@@ -371,6 +371,8 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "MouseUtils", "MouseUtils",
EndProject
Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "FindMyMouse", "src\modules\MouseUtils\FindMyMouse\FindMyMouse.vcxproj", "{E94FD11C-0591-456F-899F-EFC0CA548336}"
EndProject
+Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "MouseHighlighter", "src\modules\MouseUtils\MouseHighlighter\MouseHighlighter.vcxproj", "{782A61BE-9D85-4081-B35C-1CCC9DCC1E88}"
+EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|x64 = Debug|x64
@@ -986,6 +988,12 @@ Global
{E94FD11C-0591-456F-899F-EFC0CA548336}.Release|x64.ActiveCfg = Release|x64
{E94FD11C-0591-456F-899F-EFC0CA548336}.Release|x64.Build.0 = Release|x64
{E94FD11C-0591-456F-899F-EFC0CA548336}.Release|x86.ActiveCfg = Release|x64
+ {782A61BE-9D85-4081-B35C-1CCC9DCC1E88}.Debug|x64.ActiveCfg = Debug|x64
+ {782A61BE-9D85-4081-B35C-1CCC9DCC1E88}.Debug|x64.Build.0 = Debug|x64
+ {782A61BE-9D85-4081-B35C-1CCC9DCC1E88}.Debug|x86.ActiveCfg = Debug|x64
+ {782A61BE-9D85-4081-B35C-1CCC9DCC1E88}.Release|x64.ActiveCfg = Release|x64
+ {782A61BE-9D85-4081-B35C-1CCC9DCC1E88}.Release|x64.Build.0 = Release|x64
+ {782A61BE-9D85-4081-B35C-1CCC9DCC1E88}.Release|x86.ActiveCfg = Release|x64
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
@@ -1105,6 +1113,7 @@ Global
{4642D596-723F-4BFC-894C-46811219AC4A} = {89E20BCE-EB9C-46C8-8B50-E01A82E6FDC3}
{322566EF-20DC-43A6-B9F8-616AF942579A} = {4574FDD0-F61D-4376-98BF-E5A1262C11EC}
{E94FD11C-0591-456F-899F-EFC0CA548336} = {322566EF-20DC-43A6-B9F8-616AF942579A}
+ {782A61BE-9D85-4081-B35C-1CCC9DCC1E88} = {322566EF-20DC-43A6-B9F8-616AF942579A}
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {C3A2F9D1-7930-4EF4-A6FC-7EE0A99821D0}
diff --git a/doc/images/icons/Find My Mouse.png b/doc/images/icons/Find My Mouse.png
new file mode 100644
index 000000000000..5098e2237abf
Binary files /dev/null and b/doc/images/icons/Find My Mouse.png differ
diff --git a/doc/images/icons/Mouse Highlighter.png b/doc/images/icons/Mouse Highlighter.png
new file mode 100644
index 000000000000..789b0b1c0490
Binary files /dev/null and b/doc/images/icons/Mouse Highlighter.png differ
diff --git a/installer/PowerToysSetup/Product.wxs b/installer/PowerToysSetup/Product.wxs
index 2b11399842a5..0cbf25227fe6 100644
--- a/installer/PowerToysSetup/Product.wxs
+++ b/installer/PowerToysSetup/Product.wxs
@@ -657,6 +657,9 @@
+
+
+
@@ -971,6 +974,7 @@
+
diff --git a/src/common/logger/logger_settings.h b/src/common/logger/logger_settings.h
index 1015ef9f5740..b88af2bc77a1 100644
--- a/src/common/logger/logger_settings.h
+++ b/src/common/logger/logger_settings.h
@@ -26,6 +26,7 @@ struct LogSettings
inline const static std::string keyboardManagerLoggerName = "keyboard-manager";
inline const static std::wstring keyboardManagerLogPath = L"Logs\\keyboard-manager-log.txt";
inline const static std::string findMyMouseLoggerName = "find-my-mouse";
+ inline const static std::string mouseHighlighterLoggerName = "mouse-highlighter";
inline const static std::string powerRenameLoggerName = "powerrename";
inline const static int retention = 30;
std::wstring logLevel;
diff --git a/src/modules/MouseUtils/MouseHighlighter/Directory.Build.targets b/src/modules/MouseUtils/MouseHighlighter/Directory.Build.targets
new file mode 100644
index 000000000000..7e1362ac6066
--- /dev/null
+++ b/src/modules/MouseUtils/MouseHighlighter/Directory.Build.targets
@@ -0,0 +1,5 @@
+
+
+
+
+
\ No newline at end of file
diff --git a/src/modules/MouseUtils/MouseHighlighter/MouseHighlighter.base.rc b/src/modules/MouseUtils/MouseHighlighter/MouseHighlighter.base.rc
new file mode 100644
index 000000000000..0bcdeca2ef58
--- /dev/null
+++ b/src/modules/MouseUtils/MouseHighlighter/MouseHighlighter.base.rc
@@ -0,0 +1,40 @@
+#include
+#include "resource.h"
+#include "../../../../common/version/version.h"
+
+#define APSTUDIO_READONLY_SYMBOLS
+#include "winres.h"
+#undef APSTUDIO_READONLY_SYMBOLS
+
+1 VERSIONINFO
+FILEVERSION FILE_VERSION
+PRODUCTVERSION PRODUCT_VERSION
+FILEFLAGSMASK VS_FFI_FILEFLAGSMASK
+#ifdef _DEBUG
+FILEFLAGS VS_FF_DEBUG
+#else
+FILEFLAGS 0x0L
+#endif
+FILEOS VOS_NT_WINDOWS32
+FILETYPE VFT_DLL
+FILESUBTYPE VFT2_UNKNOWN
+BEGIN
+ BLOCK "StringFileInfo"
+ BEGIN
+ BLOCK "040904b0" // US English (0x0409), Unicode (0x04B0) charset
+ BEGIN
+ VALUE "CompanyName", COMPANY_NAME
+ VALUE "FileDescription", FILE_DESCRIPTION
+ VALUE "FileVersion", FILE_VERSION_STRING
+ VALUE "InternalName", INTERNAL_NAME
+ VALUE "LegalCopyright", COPYRIGHT_NOTE
+ VALUE "OriginalFilename", ORIGINAL_FILENAME
+ VALUE "ProductName", PRODUCT_NAME
+ VALUE "ProductVersion", PRODUCT_VERSION_STRING
+ END
+ END
+ BLOCK "VarFileInfo"
+ BEGIN
+ VALUE "Translation", 0x409, 1200 // US English (0x0409), Unicode (1200) charset
+ END
+END
diff --git a/src/modules/MouseUtils/MouseHighlighter/MouseHighlighter.cpp b/src/modules/MouseUtils/MouseHighlighter/MouseHighlighter.cpp
new file mode 100644
index 000000000000..8b8c83c1fa77
--- /dev/null
+++ b/src/modules/MouseUtils/MouseHighlighter/MouseHighlighter.cpp
@@ -0,0 +1,443 @@
+// MouseHighlighter.cpp : Defines the entry point for the application.
+//
+
+#include "pch.h"
+#include "MouseHighlighter.h"
+#include "trace.h"
+
+#ifdef COMPOSITION
+namespace winrt
+{
+ using namespace winrt::Windows::System;
+ using namespace winrt::Windows::UI::Composition;
+}
+
+namespace ABI
+{
+ using namespace ABI::Windows::System;
+ using namespace ABI::Windows::UI::Composition::Desktop;
+}
+#endif
+
+struct Highlighter
+{
+ bool MyRegisterClass(HINSTANCE hInstance);
+ static Highlighter* instance;
+ void Terminate();
+ void SwitchActivationMode();
+ void ApplySettings(MouseHighlighterSettings settings);
+
+private:
+ enum class MouseButton
+ {
+ Left,
+ Right
+ };
+
+ void DestroyHighlighter();
+ static LRESULT CALLBACK WndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam) noexcept;
+ void StartDrawing();
+ void StopDrawing();
+ bool CreateHighlighter();
+ void AddDrawingPoint(MouseButton button);
+ void UpdateDrawingPointPosition(MouseButton button);
+ void StartDrawingPointFading(MouseButton button);
+ void ClearDrawing();
+ HHOOK m_mouseHook = NULL;
+ static LRESULT CALLBACK MouseHookProc(int nCode, WPARAM wParam, LPARAM lParam) noexcept;
+
+ static constexpr auto m_className = L"MouseHighlighter";
+ static constexpr auto m_windowTitle = L"MouseHighlighter";
+ HWND m_hwndOwner = NULL;
+ HWND m_hwnd = NULL;
+ HINSTANCE m_hinstance = NULL;
+ static constexpr DWORD WM_SWITCH_ACTIVATION_MODE = WM_APP;
+
+ winrt::DispatcherQueueController m_dispatcherQueueController{ nullptr };
+ winrt::Compositor m_compositor{ nullptr };
+ winrt::Desktop::DesktopWindowTarget m_target{ nullptr };
+ winrt::ContainerVisual m_root{ nullptr };
+ winrt::LayerVisual m_layer{ nullptr };
+ winrt::ShapeVisual m_shape{ nullptr };
+
+ winrt::CompositionSpriteShape m_leftPointer{ nullptr };
+ winrt::CompositionSpriteShape m_rightPointer{ nullptr };
+ bool m_leftButtonPressed = false;
+ bool m_rightButtonPressed = false;
+
+ bool m_visible = false;
+
+ // Possible configurable settings
+ float m_radius = MOUSE_HIGHLIGHTER_DEFAULT_RADIUS;
+
+ int m_fadeDelay_ms = MOUSE_HIGHLIGHTER_DEFAULT_DELAY_MS;
+ int m_fadeDuration_ms = MOUSE_HIGHLIGHTER_DEFAULT_DURATION_MS;
+
+ winrt::Windows::UI::Color m_leftClickColor = MOUSE_HIGHLIGHTER_DEFAULT_LEFT_BUTTON_COLOR;
+ winrt::Windows::UI::Color m_rightClickColor = MOUSE_HIGHLIGHTER_DEFAULT_RIGHT_BUTTON_COLOR;
+};
+
+Highlighter* Highlighter::instance = nullptr;
+
+bool Highlighter::CreateHighlighter()
+{
+ try
+ {
+ // We need a dispatcher queue.
+ DispatcherQueueOptions options =
+ {
+ sizeof(options),
+ DQTYPE_THREAD_CURRENT,
+ DQTAT_COM_ASTA,
+ };
+ ABI::IDispatcherQueueController* controller;
+ winrt::check_hresult(CreateDispatcherQueueController(options, &controller));
+ *winrt::put_abi(m_dispatcherQueueController) = controller;
+
+ // Create the compositor for our window.
+ m_compositor = winrt::Compositor();
+ ABI::IDesktopWindowTarget* target;
+ winrt::check_hresult(m_compositor.as()->CreateDesktopWindowTarget(m_hwnd, false, &target));
+ *winrt::put_abi(m_target) = target;
+
+ // Create visual root
+ m_root = m_compositor.CreateContainerVisual();
+ m_root.RelativeSizeAdjustment({ 1.0f, 1.0f });
+ m_target.Root(m_root);
+
+ // Create the shapes container visual and add it to root.
+ m_shape = m_compositor.CreateShapeVisual();
+ m_shape.RelativeSizeAdjustment({ 1.0f, 1.0f });
+ m_root.Children().InsertAtTop(m_shape);
+
+ return true;
+ } catch (...)
+ {
+ return false;
+ }
+}
+
+
+void Highlighter::AddDrawingPoint(MouseButton button)
+{
+ POINT pt;
+
+ // Applies DPIs.
+ GetCursorPos(&pt);
+
+ // Converts to client area of the Windows.
+ ScreenToClient(m_hwnd, &pt);
+
+ // Create circle and add it.
+ auto circleGeometry = m_compositor.CreateEllipseGeometry();
+ circleGeometry.Radius({ m_radius, m_radius });
+ auto circleShape = m_compositor.CreateSpriteShape(circleGeometry);
+ circleShape.Offset({ (float)pt.x, (float)pt.y });
+ if (button == MouseButton::Left)
+ {
+ circleShape.FillBrush(m_compositor.CreateColorBrush(m_leftClickColor));
+ m_leftPointer = circleShape;
+ }
+ else
+ {
+ //right
+ circleShape.FillBrush(m_compositor.CreateColorBrush(m_rightClickColor));
+ m_rightPointer = circleShape;
+ }
+ m_shape.Shapes().Append(circleShape);
+
+ // TODO: We're leaking shapes for long drawing sessions.
+ // Perhaps add a task to the Dispatcher every X circles to clean up.
+
+ // Get back on top in case other Window is now the topmost.
+ SetWindowPos(m_hwnd, HWND_TOPMOST, GetSystemMetrics(SM_XVIRTUALSCREEN), GetSystemMetrics(SM_YVIRTUALSCREEN),
+ GetSystemMetrics(SM_CXVIRTUALSCREEN), GetSystemMetrics(SM_CYVIRTUALSCREEN), 0);
+}
+
+void Highlighter::UpdateDrawingPointPosition(MouseButton button)
+{
+ POINT pt;
+
+ // Applies DPIs.
+ GetCursorPos(&pt);
+
+ // Converts to client area of the Windows.
+ ScreenToClient(m_hwnd, &pt);
+
+ if (button == MouseButton::Left)
+ {
+ m_leftPointer.Offset({ (float)pt.x, (float)pt.y });
+ }
+ else
+ {
+ //right
+ m_rightPointer.Offset({ (float)pt.x, (float)pt.y });
+ }
+}
+void Highlighter::StartDrawingPointFading(MouseButton button)
+{
+ winrt::Windows::UI::Composition::CompositionSpriteShape circleShape{ nullptr };
+ if (button == MouseButton::Left)
+ {
+ circleShape = m_leftPointer;
+ }
+ else
+ {
+ //right
+ circleShape = m_rightPointer;
+ }
+
+ auto brushColor = circleShape.FillBrush().as().Color();
+
+ // Animate opacity to simulate a fade away effect.
+ auto animation = m_compositor.CreateColorKeyFrameAnimation();
+ animation.InsertKeyFrame(1, winrt::Windows::UI::ColorHelper::FromArgb(0, brushColor.R, brushColor.G, brushColor.B));
+ using timeSpan = std::chrono::duration>;
+ std::chrono::milliseconds duration(m_fadeDuration_ms);
+ std::chrono::milliseconds delay(m_fadeDelay_ms);
+ animation.Duration(timeSpan(duration));
+ animation.DelayTime(timeSpan(delay));
+
+ circleShape.FillBrush().StartAnimation(L"Color", animation);
+}
+
+
+void Highlighter::ClearDrawing()
+{
+ m_shape.Shapes().Clear();
+}
+
+LRESULT CALLBACK Highlighter::MouseHookProc(int nCode, WPARAM wParam, LPARAM lParam) noexcept
+{
+ if (nCode >= 0)
+ {
+ MSLLHOOKSTRUCT* hookData = (MSLLHOOKSTRUCT*)lParam;
+ switch (wParam)
+ {
+ case WM_LBUTTONDOWN:
+ instance->AddDrawingPoint(MouseButton::Left);
+ instance->m_leftButtonPressed = true;
+ break;
+ case WM_RBUTTONDOWN:
+ instance->AddDrawingPoint(MouseButton::Right);
+ instance->m_rightButtonPressed = true;
+ break;
+ case WM_MOUSEMOVE:
+ if (instance->m_leftButtonPressed)
+ {
+ instance->UpdateDrawingPointPosition(MouseButton::Left);
+ }
+ if (instance->m_rightButtonPressed)
+ {
+ instance->UpdateDrawingPointPosition(MouseButton::Right);
+ }
+ break;
+ case WM_LBUTTONUP:
+ if (instance->m_leftButtonPressed)
+ {
+ instance->StartDrawingPointFading(MouseButton::Left);
+ instance->m_leftButtonPressed = false;
+ }
+ break;
+ case WM_RBUTTONUP:
+ if (instance->m_rightButtonPressed)
+ {
+ instance->StartDrawingPointFading(MouseButton::Right);
+ instance->m_rightButtonPressed = false;
+ }
+ break;
+ default:
+ break;
+ }
+ }
+ return CallNextHookEx(0, nCode, wParam, lParam);
+}
+
+
+void Highlighter::StartDrawing()
+{
+ Logger::info("Starting draw mode.");
+ Trace::StartHighlightingSession();
+ m_visible = true;
+ SetWindowPos(m_hwnd, HWND_TOPMOST, GetSystemMetrics(SM_XVIRTUALSCREEN), GetSystemMetrics(SM_YVIRTUALSCREEN),
+ GetSystemMetrics(SM_CXVIRTUALSCREEN), GetSystemMetrics(SM_CYVIRTUALSCREEN), 0);
+ ClearDrawing();
+ ShowWindow(m_hwnd, SW_SHOWNOACTIVATE);
+ m_mouseHook = SetWindowsHookEx(WH_MOUSE_LL, MouseHookProc, m_hinstance, 0);
+}
+
+void Highlighter::StopDrawing()
+{
+ Logger::info("Stopping draw mode.");
+ m_visible = false;
+ m_leftButtonPressed = false;
+ m_rightButtonPressed = false;
+ m_leftPointer = nullptr;
+ m_rightPointer = nullptr;
+ ShowWindow(m_hwnd, SW_HIDE);
+ UnhookWindowsHookEx(m_mouseHook);
+ ClearDrawing();
+ m_mouseHook = NULL;
+}
+
+void Highlighter::SwitchActivationMode()
+{
+ PostMessage(m_hwnd, WM_SWITCH_ACTIVATION_MODE, 0, 0);
+}
+
+void Highlighter::ApplySettings(MouseHighlighterSettings settings) {
+ m_radius = (float)settings.radius;
+ m_fadeDelay_ms = settings.fadeDelayMs;
+ m_fadeDuration_ms = settings.fadeDurationMs;
+ m_leftClickColor = settings.leftButtonColor;
+ m_rightClickColor = settings.rightButtonColor;
+}
+
+void Highlighter::DestroyHighlighter()
+{
+ StopDrawing();
+ PostQuitMessage(0);
+}
+
+LRESULT CALLBACK Highlighter::WndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam) noexcept
+{
+ switch (message)
+ {
+ case WM_NCCREATE:
+ instance->m_hwnd = hWnd;
+ return DefWindowProc(hWnd, message, wParam, lParam);
+ case WM_CREATE:
+ return instance->CreateHighlighter() ? 0 : -1;
+ case WM_NCHITTEST:
+ return HTTRANSPARENT;
+ case WM_SWITCH_ACTIVATION_MODE:
+ if (instance->m_visible)
+ {
+ instance->StopDrawing();
+ }
+ else
+ {
+ instance->StartDrawing();
+ }
+ break;
+ case WM_DESTROY:
+ instance->DestroyHighlighter();
+ break;
+ default:
+ return DefWindowProc(hWnd, message, wParam, lParam);
+ }
+ return 0;
+}
+
+bool Highlighter::MyRegisterClass(HINSTANCE hInstance)
+{
+ WNDCLASS wc{};
+
+ m_hinstance = hInstance;
+
+ SetThreadDpiAwarenessContext(DPI_AWARENESS_CONTEXT_PER_MONITOR_AWARE_V2);
+ if (!GetClassInfoW(hInstance, m_className, &wc))
+ {
+ wc.lpfnWndProc = WndProc;
+ wc.hInstance = hInstance;
+ wc.hIcon = LoadIcon(hInstance, IDI_APPLICATION);
+ wc.hCursor = LoadCursor(nullptr, IDC_ARROW);
+ wc.hbrBackground = (HBRUSH)GetStockObject(NULL_BRUSH);
+ wc.lpszClassName = m_className;
+
+ if (!RegisterClassW(&wc))
+ {
+ return false;
+ }
+ }
+
+ m_hwndOwner = CreateWindow(L"static", nullptr, WS_POPUP, 0, 0, 0, 0, nullptr, nullptr, hInstance, nullptr);
+
+ DWORD exStyle = WS_EX_TRANSPARENT | WS_EX_LAYERED | WS_EX_NOREDIRECTIONBITMAP | WS_EX_TOOLWINDOW;
+ return CreateWindowExW(exStyle, m_className, m_windowTitle, WS_POPUP,
+ CW_USEDEFAULT, 0, CW_USEDEFAULT, 0, m_hwndOwner, nullptr, hInstance, nullptr) != nullptr;
+}
+
+void Highlighter::Terminate()
+{
+ auto dispatcherQueue = m_dispatcherQueueController.DispatcherQueue();
+ bool enqueueSucceeded = dispatcherQueue.TryEnqueue([=]() {
+ DestroyWindow(m_hwndOwner);
+ });
+ if (!enqueueSucceeded)
+ {
+ Logger::error("Couldn't enqueue message to destroy the window.");
+ }
+}
+
+#pragma region MouseHighlighter_API
+
+void MouseHighlighterApplySettings(MouseHighlighterSettings settings)
+{
+ if (Highlighter::instance != nullptr)
+ {
+ Logger::info("Applying settings.");
+ Highlighter::instance->ApplySettings(settings);
+ }
+}
+
+void MouseHighlighterSwitch()
+{
+ if (Highlighter::instance != nullptr)
+ {
+ Logger::info("Switching activation mode.");
+ Highlighter::instance->SwitchActivationMode();
+ }
+}
+
+void MouseHighlighterDisable()
+{
+ if (Highlighter::instance != nullptr)
+ {
+ Logger::info("Terminating the highlighter instance.");
+ Highlighter::instance->Terminate();
+ }
+}
+
+bool MouseHighlighterIsEnabled()
+{
+ return (Highlighter::instance != nullptr);
+}
+
+int MouseHighlighterMain(HINSTANCE hInstance, MouseHighlighterSettings settings)
+{
+ Logger::info("Starting a highlighter instance.");
+ if (Highlighter::instance != nullptr)
+ {
+ Logger::error("A highlighter instance was still working when trying to start a new one.");
+ return 0;
+ }
+
+ // Perform application initialization:
+ Highlighter highlighter;
+ Highlighter::instance = &highlighter;
+ highlighter.ApplySettings(settings);
+ if (!highlighter.MyRegisterClass(hInstance))
+ {
+ Logger::error("Couldn't initialize a highlighter instance.");
+ Highlighter::instance = nullptr;
+ return FALSE;
+ }
+ Logger::info("Initialized the highlighter instance.");
+
+ MSG msg;
+
+ // Main message loop:
+ while (GetMessage(&msg, nullptr, 0, 0))
+ {
+ TranslateMessage(&msg);
+ DispatchMessage(&msg);
+ }
+
+ Logger::info("Mouse highlighter message loop ended.");
+ Highlighter::instance = nullptr;
+
+ return (int)msg.wParam;
+}
+
+#pragma endregion MouseHighlighter_API
diff --git a/src/modules/MouseUtils/MouseHighlighter/MouseHighlighter.h b/src/modules/MouseUtils/MouseHighlighter/MouseHighlighter.h
new file mode 100644
index 000000000000..eb1948bd090f
--- /dev/null
+++ b/src/modules/MouseUtils/MouseHighlighter/MouseHighlighter.h
@@ -0,0 +1,24 @@
+#pragma once
+#include "pch.h"
+
+constexpr int MOUSE_HIGHLIGHTER_DEFAULT_OPACITY = 160;
+const winrt::Windows::UI::Color MOUSE_HIGHLIGHTER_DEFAULT_LEFT_BUTTON_COLOR = winrt::Windows::UI::ColorHelper::FromArgb(MOUSE_HIGHLIGHTER_DEFAULT_OPACITY, 255, 255, 0);
+const winrt::Windows::UI::Color MOUSE_HIGHLIGHTER_DEFAULT_RIGHT_BUTTON_COLOR = winrt::Windows::UI::ColorHelper::FromArgb(MOUSE_HIGHLIGHTER_DEFAULT_OPACITY, 0, 0, 255);
+constexpr int MOUSE_HIGHLIGHTER_DEFAULT_RADIUS = 20;
+constexpr int MOUSE_HIGHLIGHTER_DEFAULT_DELAY_MS = 500;
+constexpr int MOUSE_HIGHLIGHTER_DEFAULT_DURATION_MS = 250;
+
+struct MouseHighlighterSettings
+{
+ winrt::Windows::UI::Color leftButtonColor = MOUSE_HIGHLIGHTER_DEFAULT_LEFT_BUTTON_COLOR;
+ winrt::Windows::UI::Color rightButtonColor = MOUSE_HIGHLIGHTER_DEFAULT_RIGHT_BUTTON_COLOR;
+ int radius = MOUSE_HIGHLIGHTER_DEFAULT_RADIUS;
+ int fadeDelayMs = MOUSE_HIGHLIGHTER_DEFAULT_DELAY_MS;
+ int fadeDurationMs = MOUSE_HIGHLIGHTER_DEFAULT_DURATION_MS;
+};
+
+int MouseHighlighterMain(HINSTANCE hinst, MouseHighlighterSettings settings);
+void MouseHighlighterDisable();
+bool MouseHighlighterIsEnabled();
+void MouseHighlighterSwitch();
+void MouseHighlighterApplySettings(MouseHighlighterSettings settings);
diff --git a/src/modules/MouseUtils/MouseHighlighter/MouseHighlighter.vcxproj b/src/modules/MouseUtils/MouseHighlighter/MouseHighlighter.vcxproj
new file mode 100644
index 000000000000..5c127f852519
--- /dev/null
+++ b/src/modules/MouseUtils/MouseHighlighter/MouseHighlighter.vcxproj
@@ -0,0 +1,145 @@
+
+
+
+
+
+ Debug
+ x64
+
+
+ Release
+ x64
+
+
+
+ 15.0
+ {782a61be-9d85-4081-b35c-1ccc9dcc1e88}
+ Win32Proj
+ MouseHighlighter
+ 10.0.18362.0
+ MouseHighlighter
+
+
+
+ DynamicLibrary
+ true
+ v142
+ Unicode
+
+
+ DynamicLibrary
+ false
+ v142
+ true
+ Unicode
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ true
+ $(SolutionDir)$(Platform)\$(Configuration)\modules\MouseUtils\
+
+
+ false
+ $(SolutionDir)$(Platform)\$(Configuration)\modules\MouseUtils\
+
+
+
+ Level3
+ Disabled
+ true
+ _DEBUG;_WINDOWS;_USRDLL;%(PreprocessorDefinitions)
+ true
+ MultiThreadedDebug
+ stdcpplatest
+
+
+ Windows
+ true
+ $(OutDir)$(TargetName)$(TargetExt)
+
+
+
+
+ Level3
+ MaxSpeed
+ true
+ true
+ true
+ NDEBUG;_WINDOWS;_USRDLL;%(PreprocessorDefinitions)
+ true
+ MultiThreaded
+ stdcpplatest
+
+
+ Windows
+ true
+ true
+ true
+ $(OutDir)$(TargetName)$(TargetExt)
+
+
+
+
+ $(SolutionDir)src\;$(SolutionDir)src\modules;$(SolutionDir)src\common\Telemetry;%(AdditionalIncludeDirectories)
+
+
+
+
+ Use
+ pch.h
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Create
+
+
+
+
+
+
+
+
+
+
+
+
+ {d9b8fc84-322a-4f9f-bbb9-20915c47ddfd}
+
+
+ {6955446d-23f7-4023-9bb3-8657f904af99}
+
+
+
+
+
+
+
+
+
+ This project references NuGet package(s) that are missing on this computer. Use NuGet Package Restore to download them. For more information, see http://go.microsoft.com/fwlink/?LinkID=322105. The missing file is {0}.
+
+
+
+
+
\ No newline at end of file
diff --git a/src/modules/MouseUtils/MouseHighlighter/MouseHighlighter.vcxproj.filters b/src/modules/MouseUtils/MouseHighlighter/MouseHighlighter.vcxproj.filters
new file mode 100644
index 000000000000..ab12bcf6d6ef
--- /dev/null
+++ b/src/modules/MouseUtils/MouseHighlighter/MouseHighlighter.vcxproj.filters
@@ -0,0 +1,62 @@
+
+
+
+
+ Source Files
+
+
+ Source Files
+
+
+ Source Files
+
+
+ Source Files
+
+
+
+
+ Header Files
+
+
+ Header Files
+
+
+ Generated Files
+
+
+ Header Files
+
+
+
+
+
+ Resource Files
+
+
+ Resource Files
+
+
+
+
+ {b012a2c8-5ccb-47fc-9429-4ebf877928e2}
+ cpp;c;cc;cxx;c++;def;odl;idl;hpj;bat;asm;asmx
+
+
+ {c8345550-9836-40a0-b473-0f4bf6129568}
+ h;hh;hpp;hxx;h++;hm;inl;inc;ipp;xsd
+
+
+ {7934ee5b-8427-486d-9324-73b6bcf60eed}
+ rc;ico;cur;bmp;dlg;rc2;rct;bin;rgs;gif;jpg;jpeg;jpe;resx;tiff;tif;png;wav;mfcribbon-ms
+
+
+ {e1083d6b-b856-42a6-bd1f-1710e96170ba}
+
+
+
+
+ Generated Files
+
+
+
\ No newline at end of file
diff --git a/src/modules/MouseUtils/MouseHighlighter/dllmain.cpp b/src/modules/MouseUtils/MouseHighlighter/dllmain.cpp
new file mode 100644
index 000000000000..78858cbed2ef
--- /dev/null
+++ b/src/modules/MouseUtils/MouseHighlighter/dllmain.cpp
@@ -0,0 +1,323 @@
+#include "pch.h"
+#include
+#include
+#include "trace.h"
+#include "MouseHighlighter.h"
+
+namespace
+{
+ const wchar_t JSON_KEY_PROPERTIES[] = L"properties";
+ const wchar_t JSON_KEY_VALUE[] = L"value";
+ const wchar_t JSON_KEY_ACTIVATION_SHORTCUT[] = L"activation_shortcut";
+ const wchar_t JSON_KEY_LEFT_BUTTON_CLICK_COLOR[] = L"left_button_click_color";
+ const wchar_t JSON_KEY_RIGHT_BUTTON_CLICK_COLOR[] = L"right_button_click_color";
+ const wchar_t JSON_KEY_HIGHLIGHT_OPACITY[] = L"highlight_opacity";
+ const wchar_t JSON_KEY_HIGHLIGHT_RADIUS[] = L"highlight_radius";
+ const wchar_t JSON_KEY_HIGHLIGHT_FADE_DELAY_MS[] = L"highlight_fade_delay_ms";
+ const wchar_t JSON_KEY_HIGHLIGHT_FADE_DURATION_MS[] = L"highlight_fade_duration_ms";
+}
+
+extern "C" IMAGE_DOS_HEADER __ImageBase;
+
+HMODULE m_hModule;
+
+BOOL APIENTRY DllMain(HMODULE hModule, DWORD ul_reason_for_call, LPVOID lpReserved)
+{
+ m_hModule = hModule;
+ switch (ul_reason_for_call)
+ {
+ case DLL_PROCESS_ATTACH:
+ Trace::RegisterProvider();
+ break;
+ case DLL_THREAD_ATTACH:
+ case DLL_THREAD_DETACH:
+ break;
+ case DLL_PROCESS_DETACH:
+ Trace::UnregisterProvider();
+ break;
+ }
+ return TRUE;
+}
+
+// The PowerToy name that will be shown in the settings.
+const static wchar_t* MODULE_NAME = L"MouseHighlighter";
+// Add a description that will we shown in the module settings page.
+const static wchar_t* MODULE_DESC = L"";
+
+// Implement the PowerToy Module Interface and all the required methods.
+class MouseHighlighter : public PowertoyModuleIface
+{
+private:
+ // The PowerToy state.
+ bool m_enabled = false;
+
+ // Hotkey to invoke the module
+ HotkeyEx m_hotkey;
+
+ // Mouse Highlighter specific settings
+ MouseHighlighterSettings m_highlightSettings;
+
+ // helper function to get the RGB from a #FFFFFF string.
+ bool checkValidRGB(std::wstring_view hex, uint8_t* R, uint8_t* G, uint8_t* B)
+ {
+ if (hex.length() != 7)
+ return false;
+ hex = hex.substr(1, 6); // remove #
+ for (auto& c : hex)
+ {
+ if (!((c >= '0' && c <= '9') || (c >= 'A' && c <= 'F')))
+ {
+ return false;
+ }
+ }
+ if (swscanf_s(hex.data(), L"%2hhx%2hhx%2hhx", R, G, B) != 3)
+ {
+ return false;
+ }
+ return true;
+ }
+
+public:
+ // Constructor
+ MouseHighlighter()
+ {
+ LoggerHelpers::init_logger(MODULE_NAME, L"ModuleInterface", LogSettings::mouseHighlighterLoggerName);
+ init_settings();
+ };
+
+ // Destroy the powertoy and free memory
+ virtual void destroy() override
+ {
+ delete this;
+ }
+
+ // Return the localized display name of the powertoy
+ virtual const wchar_t* get_name() override
+ {
+ return MODULE_NAME;
+ }
+
+ // Return the non localized key of the powertoy, this will be cached by the runner
+ virtual const wchar_t* get_key() override
+ {
+ return MODULE_NAME;
+ }
+
+ // Return JSON with the configuration options.
+ virtual bool get_config(wchar_t* buffer, int* buffer_size) override
+ {
+ HINSTANCE hinstance = reinterpret_cast(&__ImageBase);
+ PowerToysSettings::Settings settings(hinstance, get_name());
+ return settings.serialize_to_buffer(buffer, buffer_size);
+ }
+
+ // Signal from the Settings editor to call a custom action.
+ // This can be used to spawn more complex editors.
+ virtual void call_custom_action(const wchar_t* action) override
+ {
+ }
+
+ // Called by the runner to pass the updated settings values as a serialized JSON.
+ virtual void set_config(const wchar_t* config) override
+ {
+ try
+ {
+ // Parse the input JSON string.
+ PowerToysSettings::PowerToyValues values =
+ PowerToysSettings::PowerToyValues::from_json_string(config, get_key());
+
+ parse_settings(values);
+
+ MouseHighlighterApplySettings(m_highlightSettings);
+ }
+ catch (std::exception&)
+ {
+ Logger::error("Invalid json when trying to parse Mouse Highlighter settings json.");
+ }
+ }
+
+ // Enable the powertoy
+ virtual void enable()
+ {
+ m_enabled = true;
+ Trace::EnableMouseHighlighter(true);
+ std::thread([=]() { MouseHighlighterMain(m_hModule, m_highlightSettings); }).detach();
+ }
+
+ // Disable the powertoy
+ virtual void disable()
+ {
+ m_enabled = false;
+ Trace::EnableMouseHighlighter(false);
+ MouseHighlighterDisable();
+ }
+
+ // Returns if the powertoys is enabled
+ virtual bool is_enabled() override
+ {
+ return m_enabled;
+ }
+
+ virtual std::optional GetHotkeyEx() override
+ {
+ return m_hotkey;
+ }
+
+ virtual void OnHotkeyEx() override
+ {
+ MouseHighlighterSwitch();
+ }
+
+ // Load the settings file.
+ void init_settings()
+ {
+ try
+ {
+ // Load and parse the settings file for this PowerToy.
+ PowerToysSettings::PowerToyValues settings =
+ PowerToysSettings::PowerToyValues::load_from_settings_file(MouseHighlighter::get_key());
+ parse_settings(settings);
+ }
+ catch (std::exception&)
+ {
+ Logger::error("Invalid json when trying to load the Mouse Highlighter settings json from file.");
+ }
+ }
+
+ void parse_settings(PowerToysSettings::PowerToyValues& settings)
+ {
+ // TODO: refactor to use common/utils/json.h instead
+ auto settingsObject = settings.get_raw_json();
+ MouseHighlighterSettings highlightSettings;
+ if (settingsObject.GetView().Size())
+ {
+ try
+ {
+ // Parse HotKey
+ auto jsonPropertiesObject = settingsObject.GetNamedObject(JSON_KEY_PROPERTIES).GetNamedObject(JSON_KEY_ACTIVATION_SHORTCUT);
+ auto hotkey = PowerToysSettings::HotkeyObject::from_json(jsonPropertiesObject);
+ m_hotkey = HotkeyEx();
+ if (hotkey.win_pressed())
+ {
+ m_hotkey.modifiersMask |= MOD_WIN;
+ }
+
+ if (hotkey.ctrl_pressed())
+ {
+ m_hotkey.modifiersMask |= MOD_CONTROL;
+ }
+
+ if (hotkey.shift_pressed())
+ {
+ m_hotkey.modifiersMask |= MOD_SHIFT;
+ }
+
+ if (hotkey.alt_pressed())
+ {
+ m_hotkey.modifiersMask |= MOD_ALT;
+ }
+
+ m_hotkey.vkCode = hotkey.get_code();
+ }
+ catch (...)
+ {
+ Logger::warn("Failed to initialize Mouse Highlighter activation shortcut");
+ }
+ uint8_t opacity = MOUSE_HIGHLIGHTER_DEFAULT_OPACITY;
+ try
+ {
+ // Parse Opacity
+ auto jsonPropertiesObject = settingsObject.GetNamedObject(JSON_KEY_PROPERTIES).GetNamedObject(JSON_KEY_HIGHLIGHT_OPACITY);
+ opacity = (uint8_t)jsonPropertiesObject.GetNamedNumber(JSON_KEY_VALUE);
+ }
+ catch (...)
+ {
+ Logger::warn("Failed to initialize Opacity from settings. Will use default value");
+ }
+ try
+ {
+ // Parse left button click color
+ auto jsonPropertiesObject = settingsObject.GetNamedObject(JSON_KEY_PROPERTIES).GetNamedObject(JSON_KEY_LEFT_BUTTON_CLICK_COLOR);
+ auto leftColor = (std::wstring)jsonPropertiesObject.GetNamedString(JSON_KEY_VALUE);
+ uint8_t r, g, b;
+ if (!checkValidRGB(leftColor,&r,&g,&b))
+ {
+ Logger::error("Left click color RGB value is invalid. Will use default value");
+ }
+ else
+ {
+ highlightSettings.leftButtonColor = winrt::Windows::UI::ColorHelper::FromArgb(opacity, r, g, b);
+ }
+ }
+ catch (...)
+ {
+ Logger::warn("Failed to initialize left click color from settings. Will use default value");
+ }
+ try
+ {
+ // Parse right button click color
+ auto jsonPropertiesObject = settingsObject.GetNamedObject(JSON_KEY_PROPERTIES).GetNamedObject(JSON_KEY_RIGHT_BUTTON_CLICK_COLOR);
+ auto rightColor = (std::wstring)jsonPropertiesObject.GetNamedString(JSON_KEY_VALUE);
+ uint8_t r, g, b;
+ if (!checkValidRGB(rightColor, &r, &g, &b))
+ {
+ Logger::error("Right click color RGB value is invalid. Will use default value");
+ }
+ else
+ {
+ highlightSettings.rightButtonColor = winrt::Windows::UI::ColorHelper::FromArgb(opacity, r, g, b);
+ }
+ }
+ catch (...)
+ {
+ Logger::warn("Failed to initialize right click color from settings. Will use default value");
+ }
+ try
+ {
+ // Parse Radius
+ auto jsonPropertiesObject = settingsObject.GetNamedObject(JSON_KEY_PROPERTIES).GetNamedObject(JSON_KEY_HIGHLIGHT_RADIUS);
+ highlightSettings.radius = (UINT)jsonPropertiesObject.GetNamedNumber(JSON_KEY_VALUE);
+ }
+ catch (...)
+ {
+ Logger::warn("Failed to initialize Radius from settings. Will use default value");
+ }
+ try
+ {
+ // Parse Fade Delay
+ auto jsonPropertiesObject = settingsObject.GetNamedObject(JSON_KEY_PROPERTIES).GetNamedObject(JSON_KEY_HIGHLIGHT_FADE_DELAY_MS);
+ highlightSettings.fadeDelayMs = (UINT)jsonPropertiesObject.GetNamedNumber(JSON_KEY_VALUE);
+ }
+ catch (...)
+ {
+ Logger::warn("Failed to initialize Fade Delay from settings. Will use default value");
+ }
+ try
+ {
+ // Parse Fade Duration
+ auto jsonPropertiesObject = settingsObject.GetNamedObject(JSON_KEY_PROPERTIES).GetNamedObject(JSON_KEY_HIGHLIGHT_FADE_DURATION_MS);
+ highlightSettings.fadeDurationMs = (UINT)jsonPropertiesObject.GetNamedNumber(JSON_KEY_VALUE);
+ }
+ catch (...)
+ {
+ Logger::warn("Failed to initialize Fade Duration from settings. Will use default value");
+ }
+ }
+ else
+ {
+ Logger::info("Mouse Highlighter settings are empty");
+ }
+ if (!m_hotkey.modifiersMask)
+ {
+ Logger::info("Mouse Highlighter is going to use default shortcut");
+ m_hotkey.modifiersMask = MOD_SHIFT | MOD_WIN;
+ m_hotkey.vkCode = 0x48; // H key
+ }
+ m_highlightSettings = highlightSettings;
+ }
+};
+
+extern "C" __declspec(dllexport) PowertoyModuleIface* __cdecl powertoy_create()
+{
+ return new MouseHighlighter();
+}
\ No newline at end of file
diff --git a/src/modules/MouseUtils/MouseHighlighter/packages.config b/src/modules/MouseUtils/MouseHighlighter/packages.config
new file mode 100644
index 000000000000..81f107b8bcab
--- /dev/null
+++ b/src/modules/MouseUtils/MouseHighlighter/packages.config
@@ -0,0 +1,4 @@
+
+
+
+
\ No newline at end of file
diff --git a/src/modules/MouseUtils/MouseHighlighter/pch.cpp b/src/modules/MouseUtils/MouseHighlighter/pch.cpp
new file mode 100644
index 000000000000..1d9f38c57d63
--- /dev/null
+++ b/src/modules/MouseUtils/MouseHighlighter/pch.cpp
@@ -0,0 +1 @@
+#include "pch.h"
diff --git a/src/modules/MouseUtils/MouseHighlighter/pch.h b/src/modules/MouseUtils/MouseHighlighter/pch.h
new file mode 100644
index 000000000000..bfb4a4776a48
--- /dev/null
+++ b/src/modules/MouseUtils/MouseHighlighter/pch.h
@@ -0,0 +1,22 @@
+#pragma once
+
+#define COMPOSITION
+#define WIN32_LEAN_AND_MEAN
+#include
+#include
+#include
+#include
+
+#ifdef COMPOSITION
+#include
+#include
+#include
+#include
+#include
+#include
+#endif
+
+#include
+#include
+#include
+#include
diff --git a/src/modules/MouseUtils/MouseHighlighter/resource.base.h b/src/modules/MouseUtils/MouseHighlighter/resource.base.h
new file mode 100644
index 000000000000..b49d62acd5de
--- /dev/null
+++ b/src/modules/MouseUtils/MouseHighlighter/resource.base.h
@@ -0,0 +1,14 @@
+//{{NO_DEPENDENCIES}}
+// Microsoft Visual C++ generated include file.
+// Used by MouseHighlighter.rc
+
+//////////////////////////////
+// Non-localizable
+
+#define FILE_DESCRIPTION "PowerToys MouseHighlighter"
+#define INTERNAL_NAME "MouseHighlighter"
+#define ORIGINAL_FILENAME "MouseHighlighter.dll"
+#define IDS_KEYBOARDMANAGER_ICON 1001
+
+// Non-localizable
+//////////////////////////////
diff --git a/src/modules/MouseUtils/MouseHighlighter/trace.cpp b/src/modules/MouseUtils/MouseHighlighter/trace.cpp
new file mode 100644
index 000000000000..feefa17745cc
--- /dev/null
+++ b/src/modules/MouseUtils/MouseHighlighter/trace.cpp
@@ -0,0 +1,40 @@
+#include "pch.h"
+#include "trace.h"
+
+TRACELOGGING_DEFINE_PROVIDER(
+ g_hProvider,
+ "Microsoft.PowerToys",
+ // {38e8889b-9731-53f5-e901-e8a7c1753074}
+ (0x38e8889b, 0x9731, 0x53f5, 0xe9, 0x01, 0xe8, 0xa7, 0xc1, 0x75, 0x30, 0x74),
+ TraceLoggingOptionProjectTelemetry());
+
+void Trace::RegisterProvider() noexcept
+{
+ TraceLoggingRegister(g_hProvider);
+}
+
+void Trace::UnregisterProvider() noexcept
+{
+ TraceLoggingUnregister(g_hProvider);
+}
+
+// Log if the user has MouseHighlighter enabled or disabled
+void Trace::EnableMouseHighlighter(const bool enabled) noexcept
+{
+ TraceLoggingWrite(
+ g_hProvider,
+ "MouseHighlighter_EnableMouseHighlighter",
+ ProjectTelemetryPrivacyDataTag(ProjectTelemetryTag_ProductAndServicePerformance),
+ TraceLoggingKeyword(PROJECT_KEYWORD_MEASURE),
+ TraceLoggingBoolean(enabled, "Enabled"));
+}
+
+// Log that the user activated the module by starting a highlighting session
+void Trace::StartHighlightingSession() noexcept
+{
+ TraceLoggingWrite(
+ g_hProvider,
+ "MouseHighlighter_StartHighlightingSession",
+ ProjectTelemetryPrivacyDataTag(ProjectTelemetryTag_ProductAndServicePerformance),
+ TraceLoggingKeyword(PROJECT_KEYWORD_MEASURE));
+}
diff --git a/src/modules/MouseUtils/MouseHighlighter/trace.h b/src/modules/MouseUtils/MouseHighlighter/trace.h
new file mode 100644
index 000000000000..01d660bbc040
--- /dev/null
+++ b/src/modules/MouseUtils/MouseHighlighter/trace.h
@@ -0,0 +1,14 @@
+#pragma once
+
+class Trace
+{
+public:
+ static void RegisterProvider() noexcept;
+ static void UnregisterProvider() noexcept;
+
+ // Log if the user has MouseHighlighter enabled or disabled
+ static void EnableMouseHighlighter(const bool enabled) noexcept;
+
+ // Log that the user activated the module by starting a highlighting session
+ static void StartHighlightingSession() noexcept;
+};
diff --git a/src/runner/main.cpp b/src/runner/main.cpp
index 7743ae581873..bee782b8ac68 100644
--- a/src/runner/main.cpp
+++ b/src/runner/main.cpp
@@ -148,7 +148,8 @@ int runner(bool isProcessElevated, bool openSettings, std::string settingsWindow
L"modules/ShortcutGuide/ShortcutGuideModuleInterface/ShortcutGuideModuleInterface.dll",
L"modules/ColorPicker/ColorPicker.dll",
L"modules/Awake/AwakeModuleInterface.dll",
- L"modules/MouseUtils/FindMyMouse.dll"
+ L"modules/MouseUtils/FindMyMouse.dll" ,
+ L"modules/MouseUtils/MouseHighlighter.dll"
};
const auto VCM_PATH = L"modules/VideoConference/VideoConferenceModule.dll";
diff --git a/src/settings-ui/Microsoft.PowerToys.Settings.UI.Library/EnabledModules.cs b/src/settings-ui/Microsoft.PowerToys.Settings.UI.Library/EnabledModules.cs
index 0232ff641ba3..910a4dd78ffb 100644
--- a/src/settings-ui/Microsoft.PowerToys.Settings.UI.Library/EnabledModules.cs
+++ b/src/settings-ui/Microsoft.PowerToys.Settings.UI.Library/EnabledModules.cs
@@ -191,6 +191,22 @@ public bool FindMyMouse
}
}
+ private bool mouseHighlighter = true;
+
+ [JsonPropertyName("MouseHighlighter")]
+ public bool MouseHighlighter
+ {
+ get => mouseHighlighter;
+ set
+ {
+ if (mouseHighlighter != value)
+ {
+ LogTelemetryEvent(value);
+ mouseHighlighter = value;
+ }
+ }
+ }
+
public string ToJsonString()
{
return JsonSerializer.Serialize(this);
diff --git a/src/settings-ui/Microsoft.PowerToys.Settings.UI.Library/Helpers/SettingsUtilities.cs b/src/settings-ui/Microsoft.PowerToys.Settings.UI.Library/Helpers/SettingsUtilities.cs
new file mode 100644
index 000000000000..913948921c99
--- /dev/null
+++ b/src/settings-ui/Microsoft.PowerToys.Settings.UI.Library/Helpers/SettingsUtilities.cs
@@ -0,0 +1,39 @@
+// 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.Drawing;
+using System.Globalization;
+
+namespace Microsoft.PowerToys.Settings.UI.Library.Helpers
+{
+ public static class SettingsUtilities
+ {
+ public static string ToRGBHex(string color)
+ {
+ if (color == null)
+ {
+ return "#FFFFFF";
+ }
+
+ // Using InvariantCulture as these are expected to be hex codes.
+ bool success = int.TryParse(
+ color.Replace("#", string.Empty),
+ System.Globalization.NumberStyles.HexNumber,
+ CultureInfo.InvariantCulture,
+ out int argb);
+
+ if (success)
+ {
+ Color clr = Color.FromArgb(argb);
+ return "#" + clr.R.ToString("X2", CultureInfo.InvariantCulture) +
+ clr.G.ToString("X2", CultureInfo.InvariantCulture) +
+ clr.B.ToString("X2", CultureInfo.InvariantCulture);
+ }
+ else
+ {
+ return "#FFFFFF";
+ }
+ }
+ }
+}
diff --git a/src/settings-ui/Microsoft.PowerToys.Settings.UI.Library/MouseHighlighterProperties.cs b/src/settings-ui/Microsoft.PowerToys.Settings.UI.Library/MouseHighlighterProperties.cs
new file mode 100644
index 000000000000..7ccd5534f3d0
--- /dev/null
+++ b/src/settings-ui/Microsoft.PowerToys.Settings.UI.Library/MouseHighlighterProperties.cs
@@ -0,0 +1,43 @@
+// Copyright (c) Microsoft Corporation
+// The Microsoft Corporation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+using System.Text.Json.Serialization;
+
+namespace Microsoft.PowerToys.Settings.UI.Library
+{
+ public class MouseHighlighterProperties
+ {
+ [JsonPropertyName("activation_shortcut")]
+ public HotkeySettings ActivationShortcut { get; set; }
+
+ [JsonPropertyName("left_button_click_color")]
+ public StringProperty LeftButtonClickColor { get; set; }
+
+ [JsonPropertyName("right_button_click_color")]
+ public StringProperty RightButtonClickColor { get; set; }
+
+ [JsonPropertyName("highlight_opacity")]
+ public IntProperty HighlightOpacity { get; set; }
+
+ [JsonPropertyName("highlight_radius")]
+ public IntProperty HighlightRadius { get; set; }
+
+ [JsonPropertyName("highlight_fade_delay_ms")]
+ public IntProperty HighlightFadeDelayMs { get; set; }
+
+ [JsonPropertyName("highlight_fade_duration_ms")]
+ public IntProperty HighlightFadeDurationMs { get; set; }
+
+ public MouseHighlighterProperties()
+ {
+ ActivationShortcut = new HotkeySettings(true, false, false, true, 0x48);
+ LeftButtonClickColor = new StringProperty("#FFFF00");
+ RightButtonClickColor = new StringProperty("#0000FF");
+ HighlightOpacity = new IntProperty(160);
+ HighlightRadius = new IntProperty(20);
+ HighlightFadeDelayMs = new IntProperty(500);
+ HighlightFadeDurationMs = new IntProperty(250);
+ }
+ }
+}
diff --git a/src/settings-ui/Microsoft.PowerToys.Settings.UI.Library/MouseHighlighterSettings.cs b/src/settings-ui/Microsoft.PowerToys.Settings.UI.Library/MouseHighlighterSettings.cs
new file mode 100644
index 000000000000..100e5b60d6fc
--- /dev/null
+++ b/src/settings-ui/Microsoft.PowerToys.Settings.UI.Library/MouseHighlighterSettings.cs
@@ -0,0 +1,35 @@
+// 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.Text.Json.Serialization;
+using Microsoft.PowerToys.Settings.UI.Library.Interfaces;
+
+namespace Microsoft.PowerToys.Settings.UI.Library
+{
+ public class MouseHighlighterSettings : BasePTModuleSettings, ISettingsConfig
+ {
+ public const string ModuleName = "MouseHighlighter";
+
+ [JsonPropertyName("properties")]
+ public MouseHighlighterProperties Properties { get; set; }
+
+ public MouseHighlighterSettings()
+ {
+ Name = ModuleName;
+ Properties = new MouseHighlighterProperties();
+ Version = "1.0";
+ }
+
+ public string GetModuleName()
+ {
+ return Name;
+ }
+
+ // This can be utilized in the future if the settings.json file is to be modified/deleted.
+ public bool UpgradeSettingsConfiguration()
+ {
+ return false;
+ }
+ }
+}
diff --git a/src/settings-ui/Microsoft.PowerToys.Settings.UI.Library/MouseHighlighterSettingsIPCMessage.cs b/src/settings-ui/Microsoft.PowerToys.Settings.UI.Library/MouseHighlighterSettingsIPCMessage.cs
new file mode 100644
index 000000000000..e96669493c0e
--- /dev/null
+++ b/src/settings-ui/Microsoft.PowerToys.Settings.UI.Library/MouseHighlighterSettingsIPCMessage.cs
@@ -0,0 +1,29 @@
+// 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.Text.Json;
+using System.Text.Json.Serialization;
+
+namespace Microsoft.PowerToys.Settings.UI.Library
+{
+ public class MouseHighlighterSettingsIPCMessage
+ {
+ [JsonPropertyName("powertoys")]
+ public SndMouseHighlighterSettings Powertoys { get; set; }
+
+ public MouseHighlighterSettingsIPCMessage()
+ {
+ }
+
+ public MouseHighlighterSettingsIPCMessage(SndMouseHighlighterSettings settings)
+ {
+ this.Powertoys = settings;
+ }
+
+ public string ToJsonString()
+ {
+ return JsonSerializer.Serialize(this);
+ }
+ }
+}
diff --git a/src/settings-ui/Microsoft.PowerToys.Settings.UI.Library/SndMouseHighlighterSettings.cs b/src/settings-ui/Microsoft.PowerToys.Settings.UI.Library/SndMouseHighlighterSettings.cs
new file mode 100644
index 000000000000..ccaf04e0dae1
--- /dev/null
+++ b/src/settings-ui/Microsoft.PowerToys.Settings.UI.Library/SndMouseHighlighterSettings.cs
@@ -0,0 +1,29 @@
+// 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.Text.Json;
+using System.Text.Json.Serialization;
+
+namespace Microsoft.PowerToys.Settings.UI.Library
+{
+ public class SndMouseHighlighterSettings
+ {
+ [JsonPropertyName("MouseHighlighter")]
+ public MouseHighlighterSettings MouseHighlighter { get; set; }
+
+ public SndMouseHighlighterSettings()
+ {
+ }
+
+ public SndMouseHighlighterSettings(MouseHighlighterSettings settings)
+ {
+ MouseHighlighter = settings;
+ }
+
+ public string ToJsonString()
+ {
+ return JsonSerializer.Serialize(this);
+ }
+ }
+}
diff --git a/src/settings-ui/Microsoft.PowerToys.Settings.UI.Library/ViewModels/FancyZonesViewModel.cs b/src/settings-ui/Microsoft.PowerToys.Settings.UI.Library/ViewModels/FancyZonesViewModel.cs
index 29d3650449cb..7c057e024d4f 100644
--- a/src/settings-ui/Microsoft.PowerToys.Settings.UI.Library/ViewModels/FancyZonesViewModel.cs
+++ b/src/settings-ui/Microsoft.PowerToys.Settings.UI.Library/ViewModels/FancyZonesViewModel.cs
@@ -554,7 +554,7 @@ public string ZoneHighlightColor
// The fallback value is based on ToRGBHex's behavior, which returns
// #FFFFFF if any exceptions are encountered, e.g. from passing in a null value.
// This extra handling is added here to deal with FxCop warnings.
- value = (value != null) ? ToRGBHex(value) : "#FFFFFF";
+ value = (value != null) ? SettingsUtilities.ToRGBHex(value) : "#FFFFFF";
if (!value.Equals(_zoneHighlightColor, StringComparison.OrdinalIgnoreCase))
{
_zoneHighlightColor = value;
@@ -576,7 +576,7 @@ public string ZoneBorderColor
// The fallback value is based on ToRGBHex's behavior, which returns
// #FFFFFF if any exceptions are encountered, e.g. from passing in a null value.
// This extra handling is added here to deal with FxCop warnings.
- value = (value != null) ? ToRGBHex(value) : "#FFFFFF";
+ value = (value != null) ? SettingsUtilities.ToRGBHex(value) : "#FFFFFF";
if (!value.Equals(_zoneBorderColor, StringComparison.OrdinalIgnoreCase))
{
_zoneBorderColor = value;
@@ -598,7 +598,7 @@ public string ZoneInActiveColor
// The fallback value is based on ToRGBHex's behavior, which returns
// #FFFFFF if any exceptions are encountered, e.g. from passing in a null value.
// This extra handling is added here to deal with FxCop warnings.
- value = (value != null) ? ToRGBHex(value) : "#FFFFFF";
+ value = (value != null) ? SettingsUtilities.ToRGBHex(value) : "#FFFFFF";
if (!value.Equals(_zoneInActiveColor, StringComparison.OrdinalIgnoreCase))
{
_zoneInActiveColor = value;
@@ -753,27 +753,5 @@ public void NotifyPropertyChanged([CallerMemberName] string propertyName = null)
OnPropertyChanged(propertyName);
SettingsUtils.SaveSettings(Settings.ToJsonString(), GetSettingsSubPath());
}
-
- private static string ToRGBHex(string color)
- {
- // Using InvariantCulture as these are expected to be hex codes.
- bool success = int.TryParse(
- color.Replace("#", string.Empty),
- System.Globalization.NumberStyles.HexNumber,
- CultureInfo.InvariantCulture,
- out int argb);
-
- if (success)
- {
- Color clr = Color.FromArgb(argb);
- return "#" + clr.R.ToString("X2", CultureInfo.InvariantCulture) +
- clr.G.ToString("X2", CultureInfo.InvariantCulture) +
- clr.B.ToString("X2", CultureInfo.InvariantCulture);
- }
- else
- {
- return "#FFFFFF";
- }
- }
}
}
diff --git a/src/settings-ui/Microsoft.PowerToys.Settings.UI.Library/ViewModels/MouseUtilsViewModel.cs b/src/settings-ui/Microsoft.PowerToys.Settings.UI.Library/ViewModels/MouseUtilsViewModel.cs
index 5faec966f79b..1a22ba39d024 100644
--- a/src/settings-ui/Microsoft.PowerToys.Settings.UI.Library/ViewModels/MouseUtilsViewModel.cs
+++ b/src/settings-ui/Microsoft.PowerToys.Settings.UI.Library/ViewModels/MouseUtilsViewModel.cs
@@ -17,7 +17,9 @@ public class MouseUtilsViewModel : Observable
private FindMyMouseSettings FindMyMouseSettingsConfig { get; set; }
- public MouseUtilsViewModel(ISettingsUtils settingsUtils, ISettingsRepository settingsRepository, ISettingsRepository findMyMouseSettingsRepository, Func ipcMSGCallBackFunc)
+ private MouseHighlighterSettings MouseHighlighterSettingsConfig { get; set; }
+
+ public MouseUtilsViewModel(ISettingsUtils settingsUtils, ISettingsRepository settingsRepository, ISettingsRepository findMyMouseSettingsRepository, ISettingsRepository mouseHighlighterSettingsRepository, Func ipcMSGCallBackFunc)
{
SettingsUtils = settingsUtils;
@@ -31,6 +33,8 @@ public MouseUtilsViewModel(ISettingsUtils settingsUtils, ISettingsRepository _isMouseHighlighterEnabled;
+ set
+ {
+ if (_isMouseHighlighterEnabled != value)
+ {
+ _isMouseHighlighterEnabled = value;
+
+ GeneralSettingsConfig.Enabled.MouseHighlighter = value;
+ OnPropertyChanged(nameof(_isMouseHighlighterEnabled));
+
+ OutGoingGeneralSettings outgoing = new OutGoingGeneralSettings(GeneralSettingsConfig);
+ SendConfigMSG(outgoing.ToString());
+
+ NotifyMouseHighlighterPropertyChanged();
+ }
+ }
+ }
+
+ public HotkeySettings MouseHighlighterActivationShortcut
+ {
+ get
+ {
+ return MouseHighlighterSettingsConfig.Properties.ActivationShortcut;
+ }
+
+ set
+ {
+ if (MouseHighlighterSettingsConfig.Properties.ActivationShortcut != value)
+ {
+ MouseHighlighterSettingsConfig.Properties.ActivationShortcut = value;
+ NotifyMouseHighlighterPropertyChanged();
+ }
+ }
+ }
+
+ public string MouseHighlighterLeftButtonClickColor
+ {
+ get
+ {
+ return _highlighterLeftButtonClickColor;
+ }
+
+ set
+ {
+ // The fallback value is based on ToRGBHex's behavior, which returns
+ // #FFFFFF if any exceptions are encountered, e.g. from passing in a null value.
+ // This extra handling is added here to deal with FxCop warnings.
+ value = (value != null) ? SettingsUtilities.ToRGBHex(value) : "#FFFFFF";
+ if (!value.Equals(_highlighterLeftButtonClickColor, StringComparison.OrdinalIgnoreCase))
+ {
+ _highlighterLeftButtonClickColor = value;
+ MouseHighlighterSettingsConfig.Properties.LeftButtonClickColor.Value = value;
+ NotifyMouseHighlighterPropertyChanged();
+ }
+ }
+ }
+
+ public string MouseHighlighterRightButtonClickColor
+ {
+ get
+ {
+ return _highlighterRightButtonClickColor;
+ }
+
+ set
+ {
+ // The fallback value is based on ToRGBHex's behavior, which returns
+ // #FFFFFF if any exceptions are encountered, e.g. from passing in a null value.
+ // This extra handling is added here to deal with FxCop warnings.
+ value = (value != null) ? SettingsUtilities.ToRGBHex(value) : "#FFFFFF";
+ if (!value.Equals(_highlighterRightButtonClickColor, StringComparison.OrdinalIgnoreCase))
+ {
+ _highlighterRightButtonClickColor = value;
+ MouseHighlighterSettingsConfig.Properties.RightButtonClickColor.Value = value;
+ NotifyMouseHighlighterPropertyChanged();
+ }
+ }
+ }
+
+ public int MouseHighlighterOpacity
+ {
+ get
+ {
+ return _highlighterOpacity;
+ }
+
+ set
+ {
+ if (value != _highlighterOpacity)
+ {
+ _highlighterOpacity = value;
+ MouseHighlighterSettingsConfig.Properties.HighlightOpacity.Value = value;
+ NotifyMouseHighlighterPropertyChanged();
+ }
+ }
+ }
+
+ public int MouseHighlighterRadius
+ {
+ get
+ {
+ return _highlighterRadius;
+ }
+
+ set
+ {
+ if (value != _highlighterRadius)
+ {
+ _highlighterRadius = value;
+ MouseHighlighterSettingsConfig.Properties.HighlightRadius.Value = value;
+ NotifyMouseHighlighterPropertyChanged();
+ }
+ }
+ }
+
+ public int MouseHighlighterFadeDelayMs
+ {
+ get
+ {
+ return _highlightFadeDelayMs;
+ }
+
+ set
+ {
+ if (value != _highlightFadeDelayMs)
+ {
+ _highlightFadeDelayMs = value;
+ MouseHighlighterSettingsConfig.Properties.HighlightFadeDelayMs.Value = value;
+ NotifyMouseHighlighterPropertyChanged();
+ }
+ }
+ }
+
+ public int MouseHighlighterFadeDurationMs
+ {
+ get
+ {
+ return _highlightFadeDurationMs;
+ }
+
+ set
+ {
+ if (value != _highlightFadeDurationMs)
+ {
+ _highlightFadeDurationMs = value;
+ MouseHighlighterSettingsConfig.Properties.HighlightFadeDurationMs.Value = value;
+ NotifyMouseHighlighterPropertyChanged();
+ }
+ }
+ }
+
+ public void NotifyMouseHighlighterPropertyChanged([CallerMemberName] string propertyName = null)
+ {
+ OnPropertyChanged(propertyName);
+
+ SndMouseHighlighterSettings outsettings = new SndMouseHighlighterSettings(MouseHighlighterSettingsConfig);
+ SndModuleSettings ipcMessage = new SndModuleSettings(outsettings);
+ SendConfigMSG(ipcMessage.ToJsonString());
+ SettingsUtils.SaveSettings(MouseHighlighterSettingsConfig.ToJsonString(), MouseHighlighterSettings.ModuleName);
+ }
+
private Func SendConfigMSG { get; }
private bool _isFindMyMouseEnabled;
private bool _findMyMouseDoNotActivateOnGameMode;
+
+ private bool _isMouseHighlighterEnabled;
+ private string _highlighterLeftButtonClickColor;
+ private string _highlighterRightButtonClickColor;
+ private int _highlighterOpacity;
+ private int _highlighterRadius;
+ private int _highlightFadeDelayMs;
+ private int _highlightFadeDurationMs;
}
}
diff --git a/src/settings-ui/Microsoft.PowerToys.Settings.UI/Assets/FluentIcons/FluentIconsFindMyMouse.png b/src/settings-ui/Microsoft.PowerToys.Settings.UI/Assets/FluentIcons/FluentIconsFindMyMouse.png
new file mode 100644
index 000000000000..98e7e4cf6809
Binary files /dev/null and b/src/settings-ui/Microsoft.PowerToys.Settings.UI/Assets/FluentIcons/FluentIconsFindMyMouse.png differ
diff --git a/src/settings-ui/Microsoft.PowerToys.Settings.UI/Assets/FluentIcons/FluentIconsMouseHighlighter.png b/src/settings-ui/Microsoft.PowerToys.Settings.UI/Assets/FluentIcons/FluentIconsMouseHighlighter.png
new file mode 100644
index 000000000000..33c05d976059
Binary files /dev/null and b/src/settings-ui/Microsoft.PowerToys.Settings.UI/Assets/FluentIcons/FluentIconsMouseHighlighter.png differ
diff --git a/src/settings-ui/Microsoft.PowerToys.Settings.UI/Microsoft.PowerToys.Settings.UI.csproj b/src/settings-ui/Microsoft.PowerToys.Settings.UI/Microsoft.PowerToys.Settings.UI.csproj
index 33ad5c2dfbb9..1c675efda542 100644
--- a/src/settings-ui/Microsoft.PowerToys.Settings.UI/Microsoft.PowerToys.Settings.UI.csproj
+++ b/src/settings-ui/Microsoft.PowerToys.Settings.UI/Microsoft.PowerToys.Settings.UI.csproj
@@ -237,8 +237,11 @@
+
+
+
diff --git a/src/settings-ui/Microsoft.PowerToys.Settings.UI/OOBE/Views/OobeMouseUtils.xaml b/src/settings-ui/Microsoft.PowerToys.Settings.UI/OOBE/Views/OobeMouseUtils.xaml
index 6fe25099cef1..5ac794509747 100644
--- a/src/settings-ui/Microsoft.PowerToys.Settings.UI/OOBE/Views/OobeMouseUtils.xaml
+++ b/src/settings-ui/Microsoft.PowerToys.Settings.UI/OOBE/Views/OobeMouseUtils.xaml
@@ -18,6 +18,10 @@
Style="{ThemeResource OobeSubtitleStyle}" />
+
+
+
diff --git a/src/settings-ui/Microsoft.PowerToys.Settings.UI/Strings/en-us/Resources.resw b/src/settings-ui/Microsoft.PowerToys.Settings.UI/Strings/en-us/Resources.resw
index 18d0140521cb..c945e465f1f7 100644
--- a/src/settings-ui/Microsoft.PowerToys.Settings.UI/Strings/en-us/Resources.resw
+++ b/src/settings-ui/Microsoft.PowerToys.Settings.UI/Strings/en-us/Resources.resw
@@ -655,11 +655,11 @@
Shows a help overlay with Windows shortcuts.
- Press duration before showing (ms)
+ Press duration before showing
pressing a key in milliseconds
- How long to press the Windows key to activate the module
+ How long to press the Windows key to activate the module (in ms)
Activation method
@@ -1247,7 +1247,7 @@ Made with 💗 by Microsoft and the PowerToys community.
Activation shortcut
- Customize the keyboard shortcut to activate this module
+ Customize the shortcut to activate this module
Let's get started!
@@ -1627,9 +1627,17 @@ From there, simply click on a Markdown file, PDF file or SVG icon in the File Ex
Mouse as in the hardware peripheral
- Click twice on the Left Control key to focus on your mouse.
+ Press the left Ctrl key twice to focus the mouse pointer.
Mouse as in the hardware peripheral. Key as in a keyboard key
+
+ Mouse Highlighter
+ Mouse as in the hardware peripheral.
+
+
+ Use a keyboard shortcut highlight left and right mouse clicks.
+ Mouse as in the hardware peripheral.
+
Launch PowerToys Run
@@ -1709,18 +1717,67 @@ From there, simply click on a Markdown file, PDF file or SVG icon in the File Ex
A collection of mouse utilities.
+
+ Find My Mouse
+ Refers to the utility name
+
Enable Find My Mouse
"Find My Mouse" is the name of the utility.
-
- Press the Left Control key twice to focus the mouse pointer.
- "Left Control" is a keyboard key.
+
+ Find My Mouse highlights the position of the cursor when pressing the left Ctrl key twice.
+ "Ctrl" is a keyboard key. "Find My Mouse" is the name of the utility
Do not activate when Game Mode is on
"Game mode" is the Windows feature to prevent notification when playing a game.
+
+ Mouse Highlighter
+ Refers to the utility name
+
+
+ Enable Mouse Highlighter
+ "Find My Mouse" is the name of the utility.
+
+
+ Mouse Highlighter mode will highlight mouse clicks.
+ "Mouse Highlighter" is the name of the utility. Mouse is the hardware mouse.
+
+
+
+ Activation shortcut
+
+
+ Customize the shortcut to turn on or off this mode
+ "Mouse Highlighter" is the name of the utility. Mouse is the hardware mouse.
+
+
+
+ Left button highlight color
+
+
+ Right button highlight color
+
+
+ Opacity
+
+
+ Radius
+
+
+ Fade delay
+
+
+ How long it takes before a highlight starts to disappear (in ms)
+
+
+ Fade duration
+
+
+ Duration of the disappear animation (in ms)
+
Custom colors
diff --git a/src/settings-ui/Microsoft.PowerToys.Settings.UI/Views/MouseUtilsPage.xaml b/src/settings-ui/Microsoft.PowerToys.Settings.UI/Views/MouseUtilsPage.xaml
index e25ed90096d4..d998207b0ead 100644
--- a/src/settings-ui/Microsoft.PowerToys.Settings.UI/Views/MouseUtilsPage.xaml
+++ b/src/settings-ui/Microsoft.PowerToys.Settings.UI/Views/MouseUtilsPage.xaml
@@ -13,22 +13,117 @@
ModuleImageSource="ms-appx:///Assets/Modules/MouseUtils.png">
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
-
-
-
-
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/settings-ui/Microsoft.PowerToys.Settings.UI/Views/MouseUtilsPage.xaml.cs b/src/settings-ui/Microsoft.PowerToys.Settings.UI/Views/MouseUtilsPage.xaml.cs
index b2e8867dd39f..ac0934002662 100644
--- a/src/settings-ui/Microsoft.PowerToys.Settings.UI/Views/MouseUtilsPage.xaml.cs
+++ b/src/settings-ui/Microsoft.PowerToys.Settings.UI/Views/MouseUtilsPage.xaml.cs
@@ -15,7 +15,7 @@ public sealed partial class MouseUtilsPage : Page
public MouseUtilsPage()
{
var settingsUtils = new SettingsUtils();
- ViewModel = new MouseUtilsViewModel(settingsUtils, SettingsRepository.GetInstance(settingsUtils), SettingsRepository.GetInstance(settingsUtils), ShellPage.SendDefaultIPCMessage);
+ ViewModel = new MouseUtilsViewModel(settingsUtils, SettingsRepository.GetInstance(settingsUtils), SettingsRepository.GetInstance(settingsUtils), SettingsRepository.GetInstance(settingsUtils), ShellPage.SendDefaultIPCMessage);
DataContext = ViewModel;
InitializeComponent();
}