diff --git a/PowerToys.sln b/PowerToys.sln index c78c08794c..4754e51676 100644 --- a/PowerToys.sln +++ b/PowerToys.sln @@ -265,6 +265,7 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "utils", "utils", "{B39DC643 src\common\utils\elevation.h = src\common\utils\elevation.h src\common\utils\EventLocker.h = src\common\utils\EventLocker.h src\common\utils\EventWaiter.h = src\common\utils\EventWaiter.h + src\common\utils\excluded_apps.h = src\common\utils\excluded_apps.h src\common\utils\exec.h = src\common\utils\exec.h src\common\utils\game_mode.h = src\common\utils\game_mode.h src\common\utils\HDropIterator.h = src\common\utils\HDropIterator.h diff --git a/src/common/utils/excluded_apps.h b/src/common/utils/excluded_apps.h new file mode 100644 index 0000000000..5ea29a136a --- /dev/null +++ b/src/common/utils/excluded_apps.h @@ -0,0 +1,19 @@ +#pragma once +#include +#include + +// Checks if a process path is included in a list of strings. +bool find_app_name_in_path(const std::wstring& where, const std::vector& what) +{ + for (const auto& row : what) + { + const auto pos = where.rfind(row); + const auto last_slash = where.rfind('\\'); + //Check that row occurs in where, and its last occurrence contains in itself the first character after the last backslash. + if (pos != std::wstring::npos && pos <= last_slash + 1 && pos + row.length() > last_slash) + { + return true; + } + } + return false; +} diff --git a/src/modules/MouseUtils/FindMyMouse/FindMyMouse.cpp b/src/modules/MouseUtils/FindMyMouse/FindMyMouse.cpp index 204d7e482d..7eb5698fa5 100644 --- a/src/modules/MouseUtils/FindMyMouse/FindMyMouse.cpp +++ b/src/modules/MouseUtils/FindMyMouse/FindMyMouse.cpp @@ -4,6 +4,8 @@ #include "FindMyMouse.h" #include "trace.h" #include "common/utils/game_mode.h" +#include "common/utils/process_path.h" +#include "common/utils/excluded_apps.h" #include #ifdef COMPOSITION @@ -45,6 +47,7 @@ protected: void AfterMoveSonar() {} void SetSonarVisibility(bool visible) = delete; void UpdateMouseSnooping(); + bool IsForegroundAppExcluded(); protected: // Base class members you can access. @@ -65,6 +68,7 @@ protected: 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; + std::vector m_excludedApps; static constexpr int FinalAlphaDenominator = 100; winrt::DispatcherQueueController m_dispatcherQueueController{ nullptr }; @@ -479,6 +483,11 @@ void SuperSonar::StartSonar() return; } + if (IsForegroundAppExcluded()) + { + return; + } + Logger::info("Focusing the sonar on the mouse cursor."); Trace::MousePointerFocused(); // Cover the entire virtual screen. @@ -566,6 +575,25 @@ void SuperSonar::UpdateMouseSnooping() } } +template +bool SuperSonar::IsForegroundAppExcluded() +{ + if (m_excludedApps.size() < 1) + { + return false; + } + if (HWND foregroundApp{ GetForegroundWindow() }) + { + auto processPath = get_process_path(foregroundApp); + CharUpperBuffW(processPath.data(), (DWORD)processPath.length()); + return find_app_name_in_path(processPath, m_excludedApps); + } + else + { + return false; + } +} + struct CompositionSpotlight : SuperSonar { static constexpr UINT WM_OPACITY_ANIMATION_COMPLETED = WM_APP; @@ -707,6 +735,7 @@ public: m_fadeDuration = settings.animationDurationMs > 0 ? settings.animationDurationMs : 1; m_finalAlphaNumerator = settings.overlayOpacity; m_sonarZoomFactor = settings.spotlightInitialZoom; + m_excludedApps = settings.excludedApps; } else { @@ -727,11 +756,12 @@ public: m_sonarRadiusFloat = static_cast(m_sonarRadius); m_backgroundColor = localSettings.backgroundColor; m_spotlightColor = localSettings.spotlightColor; - m_activationMethod = settings.activationMethod; + m_activationMethod = localSettings.activationMethod; m_doNotActivateOnGameMode = localSettings.doNotActivateOnGameMode; m_fadeDuration = localSettings.animationDurationMs > 0 ? localSettings.animationDurationMs : 1; m_finalAlphaNumerator = localSettings.overlayOpacity; m_sonarZoomFactor = localSettings.spotlightInitialZoom; + m_excludedApps = localSettings.excludedApps; UpdateMouseSnooping(); // For the shake mouse activation method // Apply new settings to runtime composition objects. diff --git a/src/modules/MouseUtils/FindMyMouse/FindMyMouse.h b/src/modules/MouseUtils/FindMyMouse/FindMyMouse.h index d6e121ef77..cb9650b585 100644 --- a/src/modules/MouseUtils/FindMyMouse/FindMyMouse.h +++ b/src/modules/MouseUtils/FindMyMouse/FindMyMouse.h @@ -27,6 +27,7 @@ struct FindMyMouseSettings 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; + std::vector excludedApps; }; int FindMyMouseMain(HINSTANCE hinst, const FindMyMouseSettings& settings); diff --git a/src/modules/MouseUtils/FindMyMouse/dllmain.cpp b/src/modules/MouseUtils/FindMyMouse/dllmain.cpp index 1e497a00b8..c46f3797bd 100644 --- a/src/modules/MouseUtils/FindMyMouse/dllmain.cpp +++ b/src/modules/MouseUtils/FindMyMouse/dllmain.cpp @@ -6,6 +6,7 @@ #include #include #include +#include namespace { @@ -19,6 +20,7 @@ namespace 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"; + const wchar_t JSON_KEY_EXCLUDED_APPS[] = L"excluded_apps"; } extern "C" IMAGE_DOS_HEADER __ImageBase; @@ -273,6 +275,31 @@ void FindMyMouse::parse_settings(PowerToysSettings::PowerToyValues& settings) { Logger::warn("Failed to initialize Spotlight Initial Zoom from settings. Will use default value"); } + try + { + // Parse Excluded Apps + auto jsonPropertiesObject = settingsObject.GetNamedObject(JSON_KEY_PROPERTIES).GetNamedObject(JSON_KEY_EXCLUDED_APPS); + std::wstring apps = jsonPropertiesObject.GetNamedString(JSON_KEY_VALUE).c_str(); + std::vector excludedApps; + auto excludedUppercase = apps; + CharUpperBuffW(excludedUppercase.data(), (DWORD)excludedUppercase.length()); + std::wstring_view view(excludedUppercase); + view = left_trim(trim(view)); + + while (!view.empty()) + { + auto pos = (std::min)(view.find_first_of(L"\r\n"), view.length()); + excludedApps.emplace_back(view.substr(0, pos)); + view.remove_prefix(pos); + view = left_trim(trim(view)); + } + + findMyMouseSettings.excludedApps = excludedApps; + } + catch (...) + { + Logger::warn("Failed to initialize Excluded Apps from settings. Will use default value"); + } } else { diff --git a/src/modules/alwaysontop/AlwaysOnTop/AlwaysOnTop.cpp b/src/modules/alwaysontop/AlwaysOnTop/AlwaysOnTop.cpp index c2dbe6e955..c6f711358f 100644 --- a/src/modules/alwaysontop/AlwaysOnTop/AlwaysOnTop.cpp +++ b/src/modules/alwaysontop/AlwaysOnTop/AlwaysOnTop.cpp @@ -3,6 +3,7 @@ #include #include +#include #include #include #include @@ -15,22 +16,6 @@ namespace NonLocalizable const static wchar_t* WINDOW_IS_PINNED_PROP = L"AlwaysOnTop_Pinned"; } -// TODO: move to common utils -bool find_app_name_in_path(const std::wstring& where, const std::vector& what) -{ - for (const auto& row : what) - { - const auto pos = where.rfind(row); - const auto last_slash = where.rfind('\\'); - //Check that row occurs in where, and its last occurrence contains in itself the first character after the last backslash. - if (pos != std::wstring::npos && pos <= last_slash + 1 && pos + row.length() > last_slash) - { - return true; - } - } - return false; -} - bool isExcluded(HWND window) { auto processPath = get_process_path(window); diff --git a/src/modules/fancyzones/FancyZonesLib/util.cpp b/src/modules/fancyzones/FancyZonesLib/util.cpp index 3e57aa71fe..afaf2ea45d 100644 --- a/src/modules/fancyzones/FancyZonesLib/util.cpp +++ b/src/modules/fancyzones/FancyZonesLib/util.cpp @@ -5,6 +5,7 @@ #include #include #include +#include #include #include @@ -20,20 +21,6 @@ namespace NonLocalizable const wchar_t SplashClassName[] = L"MsoSplash"; } -bool find_app_name_in_path(const std::wstring& where, const std::vector& what) -{ - for (const auto& row : what) - { - const auto pos = where.rfind(row); - const auto last_slash = where.rfind('\\'); - //Check that row occurs in where, and its last occurrence contains in itself the first character after the last backslash. - if (pos != std::wstring::npos && pos <= last_slash + 1 && pos + row.length() > last_slash) - { - return true; - } - } - return false; -} namespace { bool IsZonableByProcessPath(const std::wstring& processPath, const std::vector& excludedApps) diff --git a/src/settings-ui/Settings.UI.Library/FindMyMouseProperties.cs b/src/settings-ui/Settings.UI.Library/FindMyMouseProperties.cs index c5cb5a7909..4c0a1d142c 100644 --- a/src/settings-ui/Settings.UI.Library/FindMyMouseProperties.cs +++ b/src/settings-ui/Settings.UI.Library/FindMyMouseProperties.cs @@ -32,6 +32,9 @@ namespace Microsoft.PowerToys.Settings.UI.Library [JsonPropertyName("spotlight_initial_zoom")] public IntProperty SpotlightInitialZoom { get; set; } + [JsonPropertyName("excluded_apps")] + public StringProperty ExcludedApps { get; set; } + public FindMyMouseProperties() { ActivationMethod = new IntProperty(0); @@ -42,6 +45,7 @@ namespace Microsoft.PowerToys.Settings.UI.Library SpotlightRadius = new IntProperty(100); AnimationDurationMs = new IntProperty(500); SpotlightInitialZoom = new IntProperty(9); + ExcludedApps = new StringProperty(); } } } diff --git a/src/settings-ui/Settings.UI.Library/ViewModels/MouseUtilsViewModel.cs b/src/settings-ui/Settings.UI.Library/ViewModels/MouseUtilsViewModel.cs index 3572ff7925..a8325667fb 100644 --- a/src/settings-ui/Settings.UI.Library/ViewModels/MouseUtilsViewModel.cs +++ b/src/settings-ui/Settings.UI.Library/ViewModels/MouseUtilsViewModel.cs @@ -60,6 +60,7 @@ namespace Microsoft.PowerToys.Settings.UI.Library.ViewModels _findMyMouseSpotlightRadius = FindMyMouseSettingsConfig.Properties.SpotlightRadius.Value; _findMyMouseAnimationDurationMs = FindMyMouseSettingsConfig.Properties.AnimationDurationMs.Value; _findMyMouseSpotlightInitialZoom = FindMyMouseSettingsConfig.Properties.SpotlightInitialZoom.Value; + _findMyMouseExcludedApps = FindMyMouseSettingsConfig.Properties.ExcludedApps.Value; if (mouseHighlighterSettingsRepository == null) { @@ -266,6 +267,24 @@ namespace Microsoft.PowerToys.Settings.UI.Library.ViewModels } } + public string FindMyMouseExcludedApps + { + get + { + return _findMyMouseExcludedApps; + } + + set + { + if (value != _findMyMouseExcludedApps) + { + _findMyMouseExcludedApps = value; + FindMyMouseSettingsConfig.Properties.ExcludedApps.Value = value; + NotifyFindMyMousePropertyChanged(); + } + } + } + public void NotifyFindMyMousePropertyChanged([CallerMemberName] string propertyName = null) { OnPropertyChanged(propertyName); @@ -613,6 +632,7 @@ namespace Microsoft.PowerToys.Settings.UI.Library.ViewModels private int _findMyMouseSpotlightRadius; private int _findMyMouseAnimationDurationMs; private int _findMyMouseSpotlightInitialZoom; + private string _findMyMouseExcludedApps; private bool _isMouseHighlighterEnabled; private string _highlighterLeftButtonClickColor; diff --git a/src/settings-ui/Settings.UI/Strings/en-us/Resources.resw b/src/settings-ui/Settings.UI/Strings/en-us/Resources.resw index e4d6e509f9..dd2eb3fb4f 100644 --- a/src/settings-ui/Settings.UI/Strings/en-us/Resources.resw +++ b/src/settings-ui/Settings.UI/Strings/en-us/Resources.resw @@ -1775,6 +1775,15 @@ From there, simply click on a Markdown file, PDF file or SVG icon in the File Ex Shake mouse Mouse is the hardware peripheral. + + Prevents module activation when an excluded application is the foreground application + + + Excluded apps + + + Example: outlook.exe + Do not activate when Game Mode is on "Game mode" is the Windows feature to prevent notification when playing a game. diff --git a/src/settings-ui/Settings.UI/Views/MouseUtilsPage.xaml b/src/settings-ui/Settings.UI/Views/MouseUtilsPage.xaml index 11c813a61c..1f1d9aa6e6 100644 --- a/src/settings-ui/Settings.UI/Views/MouseUtilsPage.xaml +++ b/src/settings-ui/Settings.UI/Views/MouseUtilsPage.xaml @@ -93,6 +93,23 @@ + + + + + + + +