From 46244e8e84f95a327b87695f3868ed1022147dd3 Mon Sep 17 00:00:00 2001 From: Jaime Bernardo Date: Tue, 23 Nov 2021 16:52:17 +0000 Subject: [PATCH] [FindMyMouse]Add additional settings (#14590) * [FindMyMouse]Add additional settings * Add setting for Spotlight Initial Zoom * PR feedback: lowercase settings names * PR feedback: settings descriptions * PR feedback: change opacity to percentage * PR feedback: increase maximum zoom factor * PR feedback: group spotlight settings * PR feedback: Expanders start collapsed initially * Remove extra settings file save in dllmain * PR feedback: change initial zoom description * PR feedback: Add warning for photo sensitive users * PR feedback: remove warning and add description instead * PR feedback: size->factor in initial zoom description * Feedback PR: remove opacity description * PR feedback: remove photo sensitivity warning --- PowerToys.sln | 1 + src/common/utils/color.h | 21 +++ .../MouseUtils/FindMyMouse/FindMyMouse.cpp | 136 +++++++++++++----- .../MouseUtils/FindMyMouse/FindMyMouse.h | 24 +++- .../MouseUtils/FindMyMouse/dllmain.cpp | 99 ++++++++++++- .../MouseUtils/MouseHighlighter/dllmain.cpp | 21 +-- .../FindMyMouseProperties.cs | 24 ++++ .../ViewModels/MouseUtilsViewModel.cs | 127 ++++++++++++++++ .../Strings/en-us/Resources.resw | 26 +++- .../Views/MouseUtilsPage.xaml | 82 +++++++++-- 10 files changed, 482 insertions(+), 79 deletions(-) create mode 100644 src/common/utils/color.h diff --git a/PowerToys.sln b/PowerToys.sln index 5b023cf43..43ebe00f8 100644 --- a/PowerToys.sln +++ b/PowerToys.sln @@ -262,6 +262,7 @@ EndProject Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "utils", "utils", "{B39DC643-4663-475E-B329-03F0C9918D48}" ProjectSection(SolutionItems) = preProject src\common\utils\appMutex.h = src\common\utils\appMutex.h + src\common\utils\color.h = src\common\utils\color.h src\common\utils\com_object_factory.h = src\common\utils\com_object_factory.h src\common\utils\elevation.h = src\common\utils\elevation.h src\common\utils\EventLocker.h = src\common\utils\EventLocker.h diff --git a/src/common/utils/color.h b/src/common/utils/color.h new file mode 100644 index 000000000..3996ecd61 --- /dev/null +++ b/src/common/utils/color.h @@ -0,0 +1,21 @@ +#pragma once + +// helper function to get the RGB from a #FFFFFF string. +inline 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; +} diff --git a/src/modules/MouseUtils/FindMyMouse/FindMyMouse.cpp b/src/modules/MouseUtils/FindMyMouse/FindMyMouse.cpp index fe4ec0282..fd5037cb5 100644 --- a/src/modules/MouseUtils/FindMyMouse/FindMyMouse.cpp +++ b/src/modules/MouseUtils/FindMyMouse/FindMyMouse.cpp @@ -19,8 +19,6 @@ namespace ABI } #endif -bool m_doNotActivateOnGameMode = true; - #pragma region Super_Sonar_Base_Code template @@ -58,11 +56,13 @@ protected: // At actual check, time a fifth of the current double click setting might be used instead to take into account users who might have low values. static const int MIN_DOUBLE_CLICK_TIME = 100; - static constexpr int SonarRadius = 100; - static constexpr int SonarZoomFactor = 9; - static constexpr DWORD FadeDuration = 500; - static constexpr int FinalAlphaNumerator = 1; - static constexpr int FinalAlphaDenominator = 2; + bool m_destroyed = false; + bool m_doNotActivateOnGameMode = true; + 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 = FIND_MY_MOUSE_DEFAULT_OVERLAY_OPACITY; + static constexpr int FinalAlphaDenominator = 100; winrt::DispatcherQueueController m_dispatcherQueueController{ nullptr }; private: @@ -150,6 +150,7 @@ void SuperSonar::Terminate() { auto dispatcherQueue = m_dispatcherQueueController.DispatcherQueue(); bool enqueueSucceeded = dispatcherQueue.TryEnqueue([=]() { + m_destroyed = true; DestroyWindow(m_hwndOwner); }); if (!enqueueSucceeded) @@ -457,7 +458,7 @@ void SuperSonar::UpdateMouseSnooping() struct CompositionSpotlight : SuperSonar { static constexpr UINT WM_OPACITY_ANIMATION_COMPLETED = WM_APP; - static constexpr float SonarRadiusFloat = static_cast(SonarRadius); + float m_sonarRadiusFloat = static_cast(m_sonarRadius); DWORD GetExtendedStyle() { @@ -489,7 +490,7 @@ struct CompositionSpotlight : SuperSonar m_batch.Completed([hwnd = m_hwnd](auto&&, auto&&) { PostMessage(hwnd, WM_OPACITY_ANIMATION_COMPLETED, 0, 0); }); - m_root.Opacity(visible ? static_cast(FinalAlphaNumerator) / FinalAlphaDenominator : 0.0f); + m_root.Opacity(visible ? static_cast(m_finalAlphaNumerator) / FinalAlphaDenominator : 0.0f); if (visible) { ShowWindow(m_hwnd, SW_SHOWNOACTIVATE); @@ -531,38 +532,38 @@ private: layer.RelativeSizeAdjustment({ 1.0f, 1.0f }); // fill the parent m_root.Children().InsertAtTop(layer); - auto backdrop = m_compositor.CreateSpriteVisual(); - backdrop.RelativeSizeAdjustment({ 1.0f, 1.0f }); // fill the parent - backdrop.Brush(m_compositor.CreateColorBrush({ 255, 0, 0, 0 })); - layer.Children().InsertAtTop(backdrop); + m_backdrop = m_compositor.CreateSpriteVisual(); + m_backdrop.RelativeSizeAdjustment({ 1.0f, 1.0f }); // fill the parent + m_backdrop.Brush(m_compositor.CreateColorBrush(m_backgroundColor)); + layer.Children().InsertAtTop(m_backdrop); m_circleGeometry = m_compositor.CreateEllipseGeometry(); // radius set via expression animation - auto circleShape = m_compositor.CreateSpriteShape(m_circleGeometry); - circleShape.FillBrush(m_compositor.CreateColorBrush({ 255, 255, 255, 255 })); - circleShape.Offset({ SonarRadiusFloat * SonarZoomFactor, SonarRadiusFloat * SonarZoomFactor }); + m_circleShape = m_compositor.CreateSpriteShape(m_circleGeometry); + m_circleShape.FillBrush(m_compositor.CreateColorBrush(m_spotlightColor)); + m_circleShape.Offset({ m_sonarRadiusFloat * m_sonarZoomFactor, m_sonarRadiusFloat * m_sonarZoomFactor }); m_spotlight = m_compositor.CreateShapeVisual(); - m_spotlight.Size({ SonarRadiusFloat * 2 * SonarZoomFactor, SonarRadiusFloat * 2 * SonarZoomFactor }); + m_spotlight.Size({ m_sonarRadiusFloat * 2 * m_sonarZoomFactor, m_sonarRadiusFloat * 2 * m_sonarZoomFactor }); m_spotlight.AnchorPoint({ 0.5f, 0.5f }); - m_spotlight.Shapes().Append(circleShape); + m_spotlight.Shapes().Append(m_circleShape); layer.Children().InsertAtTop(m_spotlight); // Implicitly animate the alpha. - auto animation = m_compositor.CreateScalarKeyFrameAnimation(); - animation.Target(L"Opacity"); - animation.InsertExpressionKeyFrame(1.0f, L"this.FinalValue"); - animation.Duration(std::chrono::milliseconds{ FadeDuration }); + m_animation = m_compositor.CreateScalarKeyFrameAnimation(); + m_animation.Target(L"Opacity"); + m_animation.InsertExpressionKeyFrame(1.0f, L"this.FinalValue"); + m_animation.Duration(std::chrono::milliseconds{ m_fadeDuration }); auto collection = m_compositor.CreateImplicitAnimationCollection(); - collection.Insert(L"Opacity", animation); + collection.Insert(L"Opacity", m_animation); m_root.ImplicitAnimations(collection); // Radius of spotlight shrinks as opacity increases. - // At opacity zero, it is SonarRadius * SonarZoomFactor. - // At maximum opacity, it is SonarRadius. + // At opacity zero, it is m_sonarRadius * SonarZoomFactor. + // At maximum opacity, it is m_sonarRadius. 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 * %d / %d)", SonarRadius * SonarZoomFactor, SonarRadius * SonarZoomFactor, SonarRadius, SonarRadius, FinalAlphaDenominator, FinalAlphaNumerator)); + winrt::check_hresult(StringCchPrintfW(expressionText, ARRAYSIZE(expressionText), L"Lerp(Vector2(%d, %d), Vector2(%d, %d), Root.Opacity * %d / %d)", m_sonarRadius * m_sonarZoomFactor, m_sonarRadius * m_sonarZoomFactor, m_sonarRadius, m_sonarRadius, FinalAlphaDenominator, m_finalAlphaNumerator)); radiusExpression.Expression(expressionText); m_circleGeometry.StartAnimation(L"Radius", radiusExpression); @@ -581,6 +582,62 @@ private: } } +public: + void ApplySettings(const FindMyMouseSettings& settings, bool applyToRuntimeObjects) { + if (!applyToRuntimeObjects) + { + // Runtime objects not created yet. Just update fields. + m_sonarRadius = settings.spotlightRadius; + m_sonarRadiusFloat = static_cast(m_sonarRadius); + m_backgroundColor = settings.backgroundColor; + m_spotlightColor = settings.spotlightColor; + m_doNotActivateOnGameMode = settings.doNotActivateOnGameMode; + m_fadeDuration = settings.animationDurationMs > 0 ? settings.animationDurationMs : 1; + m_finalAlphaNumerator = settings.overlayOpacity; + m_sonarZoomFactor = settings.spotlightInitialZoom; + } + else + { + // Runtime objects already created. Should update in the owner thread. + auto dispatcherQueue = m_dispatcherQueueController.DispatcherQueue(); + FindMyMouseSettings localSettings = settings; + bool enqueueSucceeded = dispatcherQueue.TryEnqueue([=]() { + if (!m_destroyed) + { + // Runtime objects not created yet. Just update fields. + m_sonarRadius = localSettings.spotlightRadius; + m_sonarRadiusFloat = static_cast(m_sonarRadius); + m_backgroundColor = localSettings.backgroundColor; + m_spotlightColor = localSettings.spotlightColor; + m_doNotActivateOnGameMode = localSettings.doNotActivateOnGameMode; + m_fadeDuration = localSettings.animationDurationMs > 0 ? localSettings.animationDurationMs : 1; + m_finalAlphaNumerator = localSettings.overlayOpacity; + m_sonarZoomFactor = localSettings.spotlightInitialZoom; + + // Apply new settings to runtime composition objects. + m_backdrop.Brush().as().Color(m_backgroundColor); + m_circleShape.FillBrush().as().Color(m_spotlightColor); + m_circleShape.Offset({ m_sonarRadiusFloat * m_sonarZoomFactor, m_sonarRadiusFloat * m_sonarZoomFactor }); + m_spotlight.Size({ m_sonarRadiusFloat * 2 * m_sonarZoomFactor, m_sonarRadiusFloat * 2 * m_sonarZoomFactor }); + m_animation.Duration(std::chrono::milliseconds{ m_fadeDuration }); + m_circleGeometry.StopAnimation(L"Radius"); + + // Update 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 * %d / %d)", m_sonarRadius * m_sonarZoomFactor, m_sonarRadius * m_sonarZoomFactor, m_sonarRadius, m_sonarRadius, FinalAlphaDenominator, m_finalAlphaNumerator)); + radiusExpression.Expression(expressionText); + m_circleGeometry.StartAnimation(L"Radius", radiusExpression); + } + }); + if (!enqueueSucceeded) + { + Logger::error("Couldn't enqueue message to update the sonar settings."); + } + } + } + private: winrt::Compositor m_compositor{ nullptr }; winrt::Desktop::DesktopWindowTarget m_target{ nullptr }; @@ -588,6 +645,11 @@ private: winrt::CompositionEllipseGeometry m_circleGeometry{ nullptr }; winrt::ShapeVisual m_spotlight{ nullptr }; winrt::CompositionCommitBatch m_batch{ nullptr }; + winrt::SpriteVisual m_backdrop{ nullptr }; + winrt::CompositionSpriteShape m_circleShape{ nullptr }; + winrt::Windows::UI::Color m_backgroundColor = FIND_MY_MOUSE_DEFAULT_BACKGROUND_COLOR; + winrt::Windows::UI::Color m_spotlightColor = FIND_MY_MOUSE_DEFAULT_SPOTLIGHT_COLOR; + winrt::ScalarKeyFrameAnimation m_animation{ nullptr }; }; template @@ -631,7 +693,7 @@ struct GdiSonar : SuperSonar void OnFadeTimer() { auto now = GetTickCount(); - auto step = (int)((now - m_fadeStart) * MaxAlpha / this->FadeDuration); + auto step = (int)((now - m_fadeStart) * MaxAlpha / this->m_fadeDuration); this->Shim()->InvalidateSonar(); if (m_alpha < m_alphaTarget) @@ -666,13 +728,13 @@ protected: int CurrentSonarRadius() { int range = MaxAlpha - m_alpha; - int radius = this->SonarRadius + this->SonarRadius * range * (this->SonarZoomFactor - 1) / MaxAlpha; + int radius = this->m_sonarRadius + this->m_sonarRadius * range * (this->m_sonarZoomFactor - 1) / MaxAlpha; return radius; } private: static constexpr DWORD FadeFramePeriod = 10; - static constexpr int MaxAlpha = SuperSonar::FinalAlphaNumerator * 255 / SuperSonar::FinalAlphaDenominator; + int MaxAlpha = SuperSonar::m_finalAlphaNumerator * 255 / SuperSonar::FinalAlphaDenominator; static constexpr DWORD TIMER_ID_FADE = 101; private: @@ -792,6 +854,14 @@ struct GdiCrosshairs : GdiSonar #pragma region Super_Sonar_API CompositionSpotlight* m_sonar = nullptr; +void FindMyMouseApplySettings(const FindMyMouseSettings& settings) +{ + if (m_sonar != nullptr) + { + Logger::info("Applying settings."); + m_sonar->ApplySettings(settings, true); + } +} void FindMyMouseDisable() { @@ -807,13 +877,8 @@ bool FindMyMouseIsEnabled() return (m_sonar != nullptr); } -void FindMyMouseSetDoNotActivateOnGameMode(bool doNotActivate) -{ - m_doNotActivateOnGameMode = doNotActivate; -} - // Based on SuperSonar's original wWinMain. -int FindMyMouseMain(HINSTANCE hinst) +int FindMyMouseMain(HINSTANCE hinst, const FindMyMouseSettings& settings) { Logger::info("Starting a sonar instance."); if (m_sonar != nullptr) @@ -823,6 +888,7 @@ int FindMyMouseMain(HINSTANCE hinst) } CompositionSpotlight sonar; + sonar.ApplySettings(settings, false); if (!sonar.Initialize(hinst)) { Logger::error("Couldn't initialize a sonar instance."); diff --git a/src/modules/MouseUtils/FindMyMouse/FindMyMouse.h b/src/modules/MouseUtils/FindMyMouse/FindMyMouse.h index d5c4653d1..f5a81b019 100644 --- a/src/modules/MouseUtils/FindMyMouse/FindMyMouse.h +++ b/src/modules/MouseUtils/FindMyMouse/FindMyMouse.h @@ -1,6 +1,26 @@ #pragma once #include "pch.h" -int FindMyMouseMain(HINSTANCE hinst); + +constexpr bool FIND_MY_MOUSE_DEFAULT_DO_NOT_ACTIVATE_ON_GAME_MODE = true; +const winrt::Windows::UI::Color FIND_MY_MOUSE_DEFAULT_BACKGROUND_COLOR = winrt::Windows::UI::ColorHelper::FromArgb(255, 0, 0, 0); +const winrt::Windows::UI::Color FIND_MY_MOUSE_DEFAULT_SPOTLIGHT_COLOR = winrt::Windows::UI::ColorHelper::FromArgb(255, 255, 255, 255); +constexpr int FIND_MY_MOUSE_DEFAULT_OVERLAY_OPACITY = 50; +constexpr int FIND_MY_MOUSE_DEFAULT_SPOTLIGHT_RADIUS = 100; +constexpr int FIND_MY_MOUSE_DEFAULT_ANIMATION_DURATION_MS = 500; +constexpr int FIND_MY_MOUSE_DEFAULT_SPOTLIGHT_INITIAL_ZOOM = 9; + +struct FindMyMouseSettings +{ + bool doNotActivateOnGameMode = FIND_MY_MOUSE_DEFAULT_DO_NOT_ACTIVATE_ON_GAME_MODE; + winrt::Windows::UI::Color backgroundColor = FIND_MY_MOUSE_DEFAULT_BACKGROUND_COLOR; + winrt::Windows::UI::Color spotlightColor = FIND_MY_MOUSE_DEFAULT_SPOTLIGHT_COLOR; + int overlayOpacity = FIND_MY_MOUSE_DEFAULT_OVERLAY_OPACITY; + int spotlightRadius = FIND_MY_MOUSE_DEFAULT_SPOTLIGHT_RADIUS; + int animationDurationMs = FIND_MY_MOUSE_DEFAULT_ANIMATION_DURATION_MS; + int spotlightInitialZoom = FIND_MY_MOUSE_DEFAULT_SPOTLIGHT_INITIAL_ZOOM; +}; + +int FindMyMouseMain(HINSTANCE hinst, const FindMyMouseSettings& settings); void FindMyMouseDisable(); bool FindMyMouseIsEnabled(); -void FindMyMouseSetDoNotActivateOnGameMode(bool doNotActivate); +void FindMyMouseApplySettings(const FindMyMouseSettings& settings); diff --git a/src/modules/MouseUtils/FindMyMouse/dllmain.cpp b/src/modules/MouseUtils/FindMyMouse/dllmain.cpp index 495d74a4b..f771764a5 100644 --- a/src/modules/MouseUtils/FindMyMouse/dllmain.cpp +++ b/src/modules/MouseUtils/FindMyMouse/dllmain.cpp @@ -5,13 +5,19 @@ #include "FindMyMouse.h" #include #include - +#include namespace { const wchar_t JSON_KEY_PROPERTIES[] = L"properties"; const wchar_t JSON_KEY_VALUE[] = L"value"; const wchar_t JSON_KEY_DO_NOT_ACTIVATE_ON_GAME_MODE[] = L"do_not_activate_on_game_mode"; + const wchar_t JSON_KEY_BACKGROUND_COLOR[] = L"background_color"; + const wchar_t JSON_KEY_SPOTLIGHT_COLOR[] = L"spotlight_color"; + const wchar_t JSON_KEY_OVERLAY_OPACITY[] = L"overlay_opacity"; + const wchar_t JSON_KEY_SPOTLIGHT_RADIUS[] = L"spotlight_radius"; + const wchar_t JSON_KEY_ANIMATION_DURATION_MS[] = L"animation_duration_ms"; + const wchar_t JSON_KEY_SPOTLIGHT_INITIAL_ZOOM[] = L"spotlight_initial_zoom"; } extern "C" IMAGE_DOS_HEADER __ImageBase; @@ -48,6 +54,9 @@ private: // The PowerToy state. bool m_enabled = false; + // Find My Mouse specific settings + FindMyMouseSettings m_findMyMouseSettings; + // Load initial settings from the persisted values. void init_settings(); @@ -109,7 +118,7 @@ public: parse_settings(values); - values.save_to_settings_file(); + FindMyMouseApplySettings(m_findMyMouseSettings); } catch (std::exception&) { @@ -122,7 +131,7 @@ public: { m_enabled = true; Trace::EnableFindMyMouse(true); - std::thread([]() { FindMyMouseMain(m_hModule); }).detach(); + std::thread([=]() { FindMyMouseMain(m_hModule, m_findMyMouseSettings); }).detach(); } // Disable the powertoy @@ -158,25 +167,103 @@ void FindMyMouse::init_settings() void FindMyMouse::parse_settings(PowerToysSettings::PowerToyValues& settings) { - FindMyMouseSetDoNotActivateOnGameMode(true); - auto settingsObject = settings.get_raw_json(); + FindMyMouseSettings findMyMouseSettings; if (settingsObject.GetView().Size()) { try { auto jsonPropertiesObject = settingsObject.GetNamedObject(JSON_KEY_PROPERTIES).GetNamedObject(JSON_KEY_DO_NOT_ACTIVATE_ON_GAME_MODE); - FindMyMouseSetDoNotActivateOnGameMode((bool)jsonPropertiesObject.GetNamedBoolean(JSON_KEY_VALUE)); + findMyMouseSettings.doNotActivateOnGameMode = (bool)jsonPropertiesObject.GetNamedBoolean(JSON_KEY_VALUE); } catch (...) { Logger::warn("Failed to get 'do not activate on game mode' setting"); } + try + { + // Parse background color + auto jsonPropertiesObject = settingsObject.GetNamedObject(JSON_KEY_PROPERTIES).GetNamedObject(JSON_KEY_BACKGROUND_COLOR); + auto backgroundColor = (std::wstring)jsonPropertiesObject.GetNamedString(JSON_KEY_VALUE); + uint8_t r, g, b; + if (!checkValidRGB(backgroundColor, &r, &g, &b)) + { + Logger::error("Background color RGB value is invalid. Will use default value"); + } + else + { + findMyMouseSettings.backgroundColor = winrt::Windows::UI::ColorHelper::FromArgb(255, r, g, b); + } + } + catch (...) + { + Logger::warn("Failed to initialize background color from settings. Will use default value"); + } + try + { + // Parse spotlight color + auto jsonPropertiesObject = settingsObject.GetNamedObject(JSON_KEY_PROPERTIES).GetNamedObject(JSON_KEY_SPOTLIGHT_COLOR); + auto spotlightColor = (std::wstring)jsonPropertiesObject.GetNamedString(JSON_KEY_VALUE); + uint8_t r, g, b; + if (!checkValidRGB(spotlightColor, &r, &g, &b)) + { + Logger::error("Spotlight color RGB value is invalid. Will use default value"); + } + else + { + findMyMouseSettings.spotlightColor = winrt::Windows::UI::ColorHelper::FromArgb(255, r, g, b); + } + } + catch (...) + { + Logger::warn("Failed to initialize spotlight color from settings. Will use default value"); + } + try + { + // Parse Overlay Opacity + auto jsonPropertiesObject = settingsObject.GetNamedObject(JSON_KEY_PROPERTIES).GetNamedObject(JSON_KEY_OVERLAY_OPACITY); + findMyMouseSettings.overlayOpacity = (UINT)jsonPropertiesObject.GetNamedNumber(JSON_KEY_VALUE); + } + catch (...) + { + Logger::warn("Failed to initialize Overlay Opacity from settings. Will use default value"); + } + try + { + // Parse Spotlight Radius + auto jsonPropertiesObject = settingsObject.GetNamedObject(JSON_KEY_PROPERTIES).GetNamedObject(JSON_KEY_SPOTLIGHT_RADIUS); + findMyMouseSettings.spotlightRadius = (UINT)jsonPropertiesObject.GetNamedNumber(JSON_KEY_VALUE); + } + catch (...) + { + Logger::warn("Failed to initialize Spotlight Radius from settings. Will use default value"); + } + try + { + // Parse Animation Duration + auto jsonPropertiesObject = settingsObject.GetNamedObject(JSON_KEY_PROPERTIES).GetNamedObject(JSON_KEY_ANIMATION_DURATION_MS); + findMyMouseSettings.animationDurationMs = (UINT)jsonPropertiesObject.GetNamedNumber(JSON_KEY_VALUE); + } + catch (...) + { + Logger::warn("Failed to initialize Animation Duration from settings. Will use default value"); + } + try + { + // Parse Spotlight Initial Zoom + auto jsonPropertiesObject = settingsObject.GetNamedObject(JSON_KEY_PROPERTIES).GetNamedObject(JSON_KEY_SPOTLIGHT_INITIAL_ZOOM); + findMyMouseSettings.spotlightInitialZoom = (UINT)jsonPropertiesObject.GetNamedNumber(JSON_KEY_VALUE); + } + catch (...) + { + Logger::warn("Failed to initialize Spotlight Initial Zoom from settings. Will use default value"); + } } else { Logger::info("Find My Mouse settings are empty"); } + m_findMyMouseSettings = findMyMouseSettings; } diff --git a/src/modules/MouseUtils/MouseHighlighter/dllmain.cpp b/src/modules/MouseUtils/MouseHighlighter/dllmain.cpp index 78858cbed..d14ffd4f6 100644 --- a/src/modules/MouseUtils/MouseHighlighter/dllmain.cpp +++ b/src/modules/MouseUtils/MouseHighlighter/dllmain.cpp @@ -3,6 +3,7 @@ #include #include "trace.h" #include "MouseHighlighter.h" +#include "common/utils/color.h" namespace { @@ -57,26 +58,6 @@ private: // 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() diff --git a/src/settings-ui/Microsoft.PowerToys.Settings.UI.Library/FindMyMouseProperties.cs b/src/settings-ui/Microsoft.PowerToys.Settings.UI.Library/FindMyMouseProperties.cs index 65cf20cb8..fb7eb158f 100644 --- a/src/settings-ui/Microsoft.PowerToys.Settings.UI.Library/FindMyMouseProperties.cs +++ b/src/settings-ui/Microsoft.PowerToys.Settings.UI.Library/FindMyMouseProperties.cs @@ -11,9 +11,33 @@ namespace Microsoft.PowerToys.Settings.UI.Library [JsonPropertyName("do_not_activate_on_game_mode")] public BoolProperty DoNotActivateOnGameMode { get; set; } + [JsonPropertyName("background_color")] + public StringProperty BackgroundColor { get; set; } + + [JsonPropertyName("spotlight_color")] + public StringProperty SpotlightColor { get; set; } + + [JsonPropertyName("overlay_opacity")] + public IntProperty OverlayOpacity { get; set; } + + [JsonPropertyName("spotlight_radius")] + public IntProperty SpotlightRadius { get; set; } + + [JsonPropertyName("animation_duration_ms")] + public IntProperty AnimationDurationMs { get; set; } + + [JsonPropertyName("spotlight_initial_zoom")] + public IntProperty SpotlightInitialZoom { get; set; } + public FindMyMouseProperties() { DoNotActivateOnGameMode = new BoolProperty(true); + BackgroundColor = new StringProperty("#000000"); + SpotlightColor = new StringProperty("#FFFFFF"); + OverlayOpacity = new IntProperty(50); + SpotlightRadius = new IntProperty(100); + AnimationDurationMs = new IntProperty(500); + SpotlightInitialZoom = new IntProperty(9); } } } 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 1a22ba39d..d2c9a6a86 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 @@ -45,6 +45,17 @@ namespace Microsoft.PowerToys.Settings.UI.Library.ViewModels FindMyMouseSettingsConfig = findMyMouseSettingsRepository.SettingsConfig; _findMyMouseDoNotActivateOnGameMode = FindMyMouseSettingsConfig.Properties.DoNotActivateOnGameMode.Value; + string backgroundColor = FindMyMouseSettingsConfig.Properties.BackgroundColor.Value; + _findMyMouseBackgroundColor = !string.IsNullOrEmpty(backgroundColor) ? backgroundColor : "#000000"; + + string spotlightColor = FindMyMouseSettingsConfig.Properties.SpotlightColor.Value; + _findMyMouseSpotlightColor = !string.IsNullOrEmpty(spotlightColor) ? spotlightColor : "#FFFFFF"; + + _findMyMouseOverlayOpacity = FindMyMouseSettingsConfig.Properties.OverlayOpacity.Value; + _findMyMouseSpotlightRadius = FindMyMouseSettingsConfig.Properties.SpotlightRadius.Value; + _findMyMouseAnimationDurationMs = FindMyMouseSettingsConfig.Properties.AnimationDurationMs.Value; + _findMyMouseSpotlightInitialZoom = FindMyMouseSettingsConfig.Properties.SpotlightInitialZoom.Value; + if (mouseHighlighterSettingsRepository == null) { throw new ArgumentNullException(nameof(mouseHighlighterSettingsRepository)); @@ -104,6 +115,116 @@ namespace Microsoft.PowerToys.Settings.UI.Library.ViewModels } } + public string FindMyMouseBackgroundColor + { + get + { + return _findMyMouseBackgroundColor; + } + + set + { + value = (value != null) ? SettingsUtilities.ToRGBHex(value) : "#000000"; + if (!value.Equals(_findMyMouseBackgroundColor, StringComparison.OrdinalIgnoreCase)) + { + _findMyMouseBackgroundColor = value; + FindMyMouseSettingsConfig.Properties.BackgroundColor.Value = value; + NotifyFindMyMousePropertyChanged(); + } + } + } + + public string FindMyMouseSpotlightColor + { + get + { + return _findMyMouseSpotlightColor; + } + + set + { + value = (value != null) ? SettingsUtilities.ToRGBHex(value) : "#FFFFFF"; + if (!value.Equals(_findMyMouseSpotlightColor, StringComparison.OrdinalIgnoreCase)) + { + _findMyMouseSpotlightColor = value; + FindMyMouseSettingsConfig.Properties.SpotlightColor.Value = value; + NotifyFindMyMousePropertyChanged(); + } + } + } + + public int FindMyMouseOverlayOpacity + { + get + { + return _findMyMouseOverlayOpacity; + } + + set + { + if (value != _findMyMouseOverlayOpacity) + { + _findMyMouseOverlayOpacity = value; + FindMyMouseSettingsConfig.Properties.OverlayOpacity.Value = value; + NotifyFindMyMousePropertyChanged(); + } + } + } + + public int FindMyMouseSpotlightRadius + { + get + { + return _findMyMouseSpotlightRadius; + } + + set + { + if (value != _findMyMouseSpotlightRadius) + { + _findMyMouseSpotlightRadius = value; + FindMyMouseSettingsConfig.Properties.SpotlightRadius.Value = value; + NotifyFindMyMousePropertyChanged(); + } + } + } + + public int FindMyMouseAnimationDurationMs + { + get + { + return _findMyMouseAnimationDurationMs; + } + + set + { + if (value != _findMyMouseAnimationDurationMs) + { + _findMyMouseAnimationDurationMs = value; + FindMyMouseSettingsConfig.Properties.AnimationDurationMs.Value = value; + NotifyFindMyMousePropertyChanged(); + } + } + } + + public int FindMyMouseSpotlightInitialZoom + { + get + { + return _findMyMouseSpotlightInitialZoom; + } + + set + { + if (value != _findMyMouseSpotlightInitialZoom) + { + _findMyMouseSpotlightInitialZoom = value; + FindMyMouseSettingsConfig.Properties.SpotlightInitialZoom.Value = value; + NotifyFindMyMousePropertyChanged(); + } + } + } + public void NotifyFindMyMousePropertyChanged([CallerMemberName] string propertyName = null) { OnPropertyChanged(propertyName); @@ -281,6 +402,12 @@ namespace Microsoft.PowerToys.Settings.UI.Library.ViewModels private bool _isFindMyMouseEnabled; private bool _findMyMouseDoNotActivateOnGameMode; + private string _findMyMouseBackgroundColor; + private string _findMyMouseSpotlightColor; + private int _findMyMouseOverlayOpacity; + private int _findMyMouseSpotlightRadius; + private int _findMyMouseAnimationDurationMs; + private int _findMyMouseSpotlightInitialZoom; private bool _isMouseHighlighterEnabled; private string _highlighterLeftButtonClickColor; 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 c945e465f..19068a951 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 @@ -1733,6 +1733,30 @@ From there, simply click on a Markdown file, PDF file or SVG icon in the File Ex Do not activate when Game Mode is on "Game mode" is the Windows feature to prevent notification when playing a game. + + Background color + + + Spotlight color + + + Overlay opacity + + + Spotlight radius + + + Spotlight initial zoom + + + Spotlight zoom factor at animation start + + + Animation duration + + + How long it takes for the spotlight to appear/disappear (in ms) + Mouse Highlighter Refers to the utility name @@ -1745,7 +1769,6 @@ From there, simply click on a Markdown file, PDF file or SVG icon in the File Ex Mouse Highlighter mode will highlight mouse clicks. "Mouse Highlighter" is the name of the utility. Mouse is the hardware mouse. - Activation shortcut @@ -1753,7 +1776,6 @@ From there, simply click on a Markdown file, PDF file or SVG icon in the File Ex 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 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 d998207b0..f1e5f59d3 100644 --- a/src/settings-ui/Microsoft.PowerToys.Settings.UI/Views/MouseUtilsPage.xaml +++ b/src/settings-ui/Microsoft.PowerToys.Settings.UI/Views/MouseUtilsPage.xaml @@ -15,25 +15,79 @@ - + + + + + + + + + - - - - - - - - + - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + @@ -53,7 +107,7 @@ - +