From 602a3ff090ad2ff41e5c93c637879d1564b99f70 Mon Sep 17 00:00:00 2001 From: Heiko <61519853+htcfreek@users.noreply.github.com> Date: Wed, 11 Oct 2023 16:37:15 +0200 Subject: [PATCH 1/2] [PTRun][Enterprise]GPO for plugin enabled state (#27468) * try code for gpo with pluginID param * fix typo * fixes * update admx * Add second policy to admx * spelling fixes * admx clean up * add gpo code * small fixes * fixes * fix cast * update settings code * bug fixes * fix plugins disabled warning * Info bar in settings * settings ui fixes * code clean up * fix spelling * fix spelling * code optimization * changes * fix code * switch to char* * update comments * validate plugin ID * spell fixes * spell fixes * fix IPlugin interface * Update Directory.Packages.props hopefully fixes unit tests * revert change of nuget pkg * fixes * fix spell check * add todo comment * improve gpo.h * improve gpo.h * update gpo.h * clean up code in gpo.h * fix build * try to fix build * xaml fix * Fix getting string value from the registry * communicate policy state suing settings.json * various changes and gpo docs * spell fixes * PT Run: Policy handling * spell fix * fix logging * fix admx revision * revision fix 2 * review feedback 1 * review feedback 2 * dev docs update * fix typo --- .../modules/launcher/new-plugin-checklist.md | 4 + doc/gpo/README.md | 31 +++++ src/common/GPOWrapper/GPOWrapper.cpp | 4 + src/common/GPOWrapper/GPOWrapper.h | 1 + src/common/GPOWrapper/GPOWrapper.idl | 1 + src/common/GPOWrapperProjection/GPOWrapper.cs | 5 + src/common/utils/gpo.h | 120 ++++++++++++++++++ src/gpo/assets/PowerToys.admx | 20 +++ src/gpo/assets/en-US/PowerToys.adml | 35 +++++ .../Main.cs | 2 + .../Main.cs | 2 + .../Main.cs | 2 + .../Main.cs | 2 + .../Plugins/Microsoft.Plugin.Folder/Main.cs | 2 + .../Plugins/Microsoft.Plugin.Indexer/Main.cs | 2 + .../Plugins/Microsoft.Plugin.Program/Main.cs | 2 + .../Plugins/Microsoft.Plugin.Shell/Main.cs | 2 + .../Plugins/Microsoft.Plugin.Uri/Main.cs | 2 + .../Microsoft.Plugin.WindowWalker/Main.cs | 2 + .../Main.cs | 2 + .../Main.cs | 2 + .../Main.cs | 5 + .../Main.cs | 2 + .../Main.cs | 2 + .../Main.cs | 2 + .../Main.cs | 2 + .../Main.cs | 2 + .../Main.cs | 5 + .../Main.cs | 2 + .../PowerLauncher/Plugin/PluginManager.cs | 21 +++ .../launcher/PowerLauncher/SettingsReader.cs | 4 + src/modules/launcher/Wox.Plugin/IPlugin.cs | 7 + .../launcher/Wox.Plugin/PluginMetadata.cs | 4 + src/modules/launcher/Wox.Plugin/PluginPair.cs | 39 ++++-- .../PowerLauncherPluginSettings.cs | 4 + .../SettingsXAML/Views/PowerLauncherPage.xaml | 10 +- .../Settings.UI/Strings/en-us/Resources.resw | 3 + .../PowerLauncherPluginViewModel.cs | 23 +++- .../ViewModels/PowerLauncherViewModel.cs | 10 +- 39 files changed, 377 insertions(+), 15 deletions(-) diff --git a/doc/devdocs/modules/launcher/new-plugin-checklist.md b/doc/devdocs/modules/launcher/new-plugin-checklist.md index b8b4d83f3b..3554e3af47 100644 --- a/doc/devdocs/modules/launcher/new-plugin-checklist.md +++ b/doc/devdocs/modules/launcher/new-plugin-checklist.md @@ -20,6 +20,10 @@ "IcoPathLight": string // Path to light theme icon. The path is relative to the root plugin folder } ``` +- [ ] Make sure your `Main` class contains a public, static string property for the `PluginID`. The plugin id has to be the same as the one in the `plugin.json`file. +```csharp +public static string PluginID => "xxxxxxx"; // The part xxxxxxx stands for the plugin ID. +``` - [ ] Do not use plugin name or PowerToys as prefixes for entities inside of the plugin project - [ ] The plugin has to have Unit tests. Use MSTest framework - [ ] Plugin's output code and assets have to be included in the installer [`Product.wxs`](/installer/PowerToysSetup/Product.wxs) diff --git a/doc/gpo/README.md b/doc/gpo/README.md index a6bd9b2c03..b40a07e614 100644 --- a/doc/gpo/README.md +++ b/doc/gpo/README.md @@ -94,3 +94,34 @@ If enabled, the automatic update checks are disabled. If disabled or not configured, the automatic update checks are enabled. --> + +### PowerToys Run + +#### Configure enabled state for all plugins + +This policy configures the enabled state for all PowerToys Run plugins. All plugins will have the same state. + +If you enable this setting, the plugins will be always enabled and the user won't be able to disable it. + +If you disable this setting, the plugins will be always disabled and the user won't be able to enable it. + +If you don't configure this setting, users are able to disable or enable the plugins. + +You can override this policy for individual plugins using the policy "Configure enabled state for individual plugins". + +Note: Changes require a restart of PowerToys Run. + +#### Configure enabled state for individual plugins + +With this policy you can configures an individual enabled state for each PowerToys Run plugin that you add to the list. + +If you enable this setting, you can define the list of plugins and their enabled states: + - The value name (first column) is the plugin ID. You will find it in the plugin.json which is located in the plugin folder. + - The value (second column) is a numeric value: 0 for disabled, 1 for enabled and 2 for user takes control. + - Example to disable the Program plugin: `791FC278BA414111B8D1886DFE447410 | 0` + +If you disable or don't configure this policy, either the user or the policy "Configure enabled state for all plugins" takes control over the enabled state of the plugins. + +You can set the enabled state for all plugins not listed here using the policy "Configure enabled state for all plugins". + +Note: Changes require a restart of PowerToys Run. \ No newline at end of file diff --git a/src/common/GPOWrapper/GPOWrapper.cpp b/src/common/GPOWrapper/GPOWrapper.cpp index 46ccf722d3..9fd9d7d2af 100644 --- a/src/common/GPOWrapper/GPOWrapper.cpp +++ b/src/common/GPOWrapper/GPOWrapper.cpp @@ -144,4 +144,8 @@ namespace winrt::PowerToys::GPOWrapper::implementation { return static_cast(powertoys_gpo::getAllowExperimentationValue()); } + GpoRuleConfigured GPOWrapper::GetRunPluginEnabledValue(winrt::hstring const& pluginID) + { + return static_cast(powertoys_gpo::getRunPluginEnabledValue(winrt::to_string(pluginID))); + } } diff --git a/src/common/GPOWrapper/GPOWrapper.h b/src/common/GPOWrapper/GPOWrapper.h index d11feb7c3a..34d3f0f5c2 100644 --- a/src/common/GPOWrapper/GPOWrapper.h +++ b/src/common/GPOWrapper/GPOWrapper.h @@ -42,6 +42,7 @@ namespace winrt::PowerToys::GPOWrapper::implementation static GpoRuleConfigured GetConfiguredPeekEnabledValue(); static GpoRuleConfigured GetDisableAutomaticUpdateDownloadValue(); static GpoRuleConfigured GetAllowExperimentationValue(); + static GpoRuleConfigured GetRunPluginEnabledValue(winrt::hstring const& pluginID); }; } diff --git a/src/common/GPOWrapper/GPOWrapper.idl b/src/common/GPOWrapper/GPOWrapper.idl index 5e26390d7e..5472ad1e87 100644 --- a/src/common/GPOWrapper/GPOWrapper.idl +++ b/src/common/GPOWrapper/GPOWrapper.idl @@ -46,6 +46,7 @@ namespace PowerToys static GpoRuleConfigured GetConfiguredPeekEnabledValue(); static GpoRuleConfigured GetDisableAutomaticUpdateDownloadValue(); static GpoRuleConfigured GetAllowExperimentationValue(); + static GpoRuleConfigured GetRunPluginEnabledValue(String pluginID); } } } diff --git a/src/common/GPOWrapperProjection/GPOWrapper.cs b/src/common/GPOWrapperProjection/GPOWrapper.cs index 1a3d5c4024..92196323e5 100644 --- a/src/common/GPOWrapperProjection/GPOWrapper.cs +++ b/src/common/GPOWrapperProjection/GPOWrapper.cs @@ -51,5 +51,10 @@ namespace PowerToys.GPOWrapperProjection { return (GpoRuleConfigured)PowerToys.GPOWrapper.GPOWrapper.GetConfiguredPeekEnabledValue(); } + + public static GpoRuleConfigured GetRunPluginEnabledValue(string pluginID) + { + return (GpoRuleConfigured)PowerToys.GPOWrapper.GPOWrapper.GetRunPluginEnabledValue(pluginID); + } } } diff --git a/src/common/utils/gpo.h b/src/common/utils/gpo.h index 7eb13495e5..bbb6545f85 100644 --- a/src/common/utils/gpo.h +++ b/src/common/utils/gpo.h @@ -1,6 +1,7 @@ #pragma once #include +#include namespace powertoys_gpo { enum gpo_rule_configured_t { @@ -13,6 +14,7 @@ namespace powertoys_gpo { // Registry path where gpo policy values are stored. const std::wstring POLICIES_PATH = L"SOFTWARE\\Policies\\PowerToys"; + const std::wstring POWER_LAUNCHER_INDIVIDUAL_PLUGIN_ENABLED_LIST_PATH = POLICIES_PATH + L"\\PowerLauncherIndividualPluginEnabledList"; // Registry scope where gpo policy values are stored. const HKEY POLICIES_SCOPE_MACHINE = HKEY_LOCAL_MACHINE; @@ -62,6 +64,39 @@ namespace powertoys_gpo { // The registry value names for other PowerToys policies. const std::wstring POLICY_ALLOW_EXPERIMENTATION = L"AllowExperimentation"; + const std::wstring POLICY_CONFIGURE_ENABLED_POWER_LAUNCHER_ALL_PLUGINS = L"PowerLauncherAllPluginsEnabledState"; + + + inline std::optional readRegistryStringValue(HKEY hRootKey, const std::wstring& subKey, const std::wstring& value_name) + { + DWORD reg_value_type = REG_SZ; + DWORD reg_flags = RRF_RT_REG_SZ; + + DWORD string_buffer_capacity; + // Request required buffer capacity / string length + if (RegGetValueW(hRootKey, subKey.c_str(), value_name.c_str(), reg_flags, ®_value_type, NULL, &string_buffer_capacity) != ERROR_SUCCESS) + { + return std::nullopt; + } + else if (string_buffer_capacity == 0) + { + return std::nullopt; + } + + // RegGetValueW overshoots sometimes. Use a buffer first to not have characters past the string end. + wchar_t* temp_buffer = new wchar_t[string_buffer_capacity / sizeof(wchar_t) + 1]; + // Read string + if (RegGetValueW(hRootKey, subKey.c_str(), value_name.c_str(), reg_flags, ®_value_type, temp_buffer, &string_buffer_capacity) != ERROR_SUCCESS) + { + delete temp_buffer; + return std::nullopt; + } + + // Convert buffer to std::wstring, delete buffer and return REG_SZ value + std::wstring string_value = temp_buffer; + delete temp_buffer; + return string_value; + } inline gpo_rule_configured_t getConfiguredValue(const std::wstring& registry_value_name) { @@ -117,6 +152,51 @@ namespace powertoys_gpo { } } + inline std::optional getPolicyListValue(const std::wstring& registry_list_path, const std::wstring& registry_list_value_name) + { + // This function returns the value of an entry of an policy list. The user scope is only checked, if the list is not enabled for the machine to not mix the lists. + + HKEY key{}; + + // Try to read from the machine list. + bool machine_list_found = false; + if (RegOpenKeyExW(POLICIES_SCOPE_MACHINE, registry_list_path.c_str(), 0, KEY_READ, &key) == ERROR_SUCCESS) + { + machine_list_found = true; + RegCloseKey(key); + + // If the path exists in the machine registry, we try to read the value. + auto regValueData = readRegistryStringValue(POLICIES_SCOPE_MACHINE, registry_list_path, registry_list_value_name); + + if (regValueData.has_value()) + { + // Return the value from the machine list. + return *regValueData; + } + } + + // If no list exists for machine, we try to read from the user list. + if (!machine_list_found) + { + if (RegOpenKeyExW(POLICIES_SCOPE_USER, registry_list_path.c_str(), 0, KEY_READ, &key) == ERROR_SUCCESS) + { + RegCloseKey(key); + + // If the path exists in the user registry, we try to read the value. + auto regValueData = readRegistryStringValue(POLICIES_SCOPE_USER, registry_list_path, registry_list_value_name); + + if (regValueData.has_value()) + { + // Return the value from the user list. + return *regValueData; + } + } + } + + // No list exists for machine and user, or no value was found in the list, or an error ocurred while reading the value. + return std::nullopt; + } + inline gpo_rule_configured_t getUtilityEnabledValue(const std::wstring& utility_name) { auto individual_value = getConfiguredValue(utility_name); @@ -321,4 +401,44 @@ namespace powertoys_gpo { return getConfiguredValue(POLICY_ALLOW_EXPERIMENTATION); } + inline gpo_rule_configured_t getRunPluginEnabledValue(std::string pluginID) + { + if (pluginID == "" || pluginID == " ") + { + // this plugin id can't exist in the registry + return gpo_rule_configured_not_configured; + } + + std::wstring plugin_id(pluginID.begin(), pluginID.end()); + auto individual_plugin_setting = getPolicyListValue(POWER_LAUNCHER_INDIVIDUAL_PLUGIN_ENABLED_LIST_PATH, plugin_id); + + if (individual_plugin_setting.has_value()) + { + if (*individual_plugin_setting == L"0") + { + // force disabled + return gpo_rule_configured_disabled; + } + else if (*individual_plugin_setting == L"1") + { + // force enabled + return gpo_rule_configured_enabled; + } + else if (*individual_plugin_setting == L"2") + { + // user takes control + return gpo_rule_configured_not_configured; + } + else + { + return gpo_rule_configured_wrong_value; + } + } + else + { + // If no individual plugin policy exists, we check the policy with the setting for all plugins. + return getConfiguredValue(POLICY_CONFIGURE_ENABLED_POWER_LAUNCHER_ALL_PLUGINS); + } + } + } diff --git a/src/gpo/assets/PowerToys.admx b/src/gpo/assets/PowerToys.admx index 92a04d15b5..c582ff7021 100644 --- a/src/gpo/assets/PowerToys.admx +++ b/src/gpo/assets/PowerToys.admx @@ -20,6 +20,9 @@ + + + @@ -416,6 +419,23 @@ + + + + + + + + + + + + + + + + + diff --git a/src/gpo/assets/en-US/PowerToys.adml b/src/gpo/assets/en-US/PowerToys.adml index b6a397feb4..fa73306193 100644 --- a/src/gpo/assets/en-US/PowerToys.adml +++ b/src/gpo/assets/en-US/PowerToys.adml @@ -8,6 +8,7 @@ Microsoft PowerToys Installer and Updates + PowerToys Run PowerToys version 0.64.0 or later PowerToys version 0.68.0 or later @@ -80,6 +81,31 @@ If disabled or not configured, the automatic update checks are enabled. If this setting is not configured or enabled, the user can control experimentation in the PowerToys settings menu. If this setting is disabled, experimentation is not allowed. + + This policy configures the enabled state for all PowerToys Run plugins. All plugins will have the same state. + +If you enable this setting, the plugins will be always enabled and the user won't be able to disable it. + +If you disable this setting, the plugins will be always disabled and the user won't be able to enable it. + +If you don't configure this setting, users are able to disable or enable the plugins. + +You can override this policy for individual plugins using the policy "Configure enabled state for individual plugins". + +Note: Changes require a restart of PowerToys Run. + + With this policy you can configures an individual enabled state for each PowerToys Run plugin that you add to the list. + +If you enable this setting, you can define the list of plugins and their enabled states: + - The value name (first column) is the plugin ID. You will find it in the plugin.json which is located in the plugin folder. + - The value (second column) is a numeric value: 0 for disabled, 1 for enabled and 2 for user takes control. + - Example to disable the Program plugin: 791FC278BA414111B8D1886DFE447410 | 0 + +If you disable or don't configure this policy, either the user or the policy "Configure enabled state for all plugins" takes control over the enabled state of the plugins. + +You can set the enabled state for all plugins not listed here using the policy "Configure enabled state for all plugins". + +Note: Changes require a restart of PowerToys Run. Configure global utility enabled state Always On Top: Configure enabled state @@ -120,7 +146,16 @@ If this setting is disabled, experimentation is not allowed. Suspend Action Center notification for new updates Disable automatic update checks Allow Experimentation + Configure enabled state for all plugins + Configure enabled state for individual plugins + + + + List of managed plugins: + + + diff --git a/src/modules/launcher/Plugins/Community.PowerToys.Run.Plugin.UnitConverter/Main.cs b/src/modules/launcher/Plugins/Community.PowerToys.Run.Plugin.UnitConverter/Main.cs index bb72b6fa2c..a8845cff30 100644 --- a/src/modules/launcher/Plugins/Community.PowerToys.Run.Plugin.UnitConverter/Main.cs +++ b/src/modules/launcher/Plugins/Community.PowerToys.Run.Plugin.UnitConverter/Main.cs @@ -21,6 +21,8 @@ namespace Community.PowerToys.Run.Plugin.UnitConverter public string Description => Properties.Resources.plugin_description; + public static string PluginID => "aa0ee9daff654fb7be452c2d77c471b9"; + private PluginInitContext _context; private static string _icon_path; private bool _disposed; diff --git a/src/modules/launcher/Plugins/Community.PowerToys.Run.Plugin.VSCodeWorkspaces/Main.cs b/src/modules/launcher/Plugins/Community.PowerToys.Run.Plugin.VSCodeWorkspaces/Main.cs index 428b20ad23..7a41b008d4 100644 --- a/src/modules/launcher/Plugins/Community.PowerToys.Run.Plugin.VSCodeWorkspaces/Main.cs +++ b/src/modules/launcher/Plugins/Community.PowerToys.Run.Plugin.VSCodeWorkspaces/Main.cs @@ -23,6 +23,8 @@ namespace Community.PowerToys.Run.Plugin.VSCodeWorkspaces public string Description => GetTranslatedPluginDescription(); + public static string PluginID => "525995402BEF4A8CA860D92F6D108092"; + public Main() { VSCodeInstances.LoadVSCodeInstances(); diff --git a/src/modules/launcher/Plugins/Community.PowerToys.Run.Plugin.ValueGenerator/Main.cs b/src/modules/launcher/Plugins/Community.PowerToys.Run.Plugin.ValueGenerator/Main.cs index 50d304c8d6..17004a9c2d 100644 --- a/src/modules/launcher/Plugins/Community.PowerToys.Run.Plugin.ValueGenerator/Main.cs +++ b/src/modules/launcher/Plugins/Community.PowerToys.Run.Plugin.ValueGenerator/Main.cs @@ -20,6 +20,8 @@ namespace Community.PowerToys.Run.Plugin.ValueGenerator public string Description => Resources.plugin_description; + public static string PluginID => "a26b1bb4dbd911edafa10242ac120002"; + private PluginInitContext _context; private static bool _isLightTheme = true; private bool _disposed; diff --git a/src/modules/launcher/Plugins/Community.PowerToys.Run.Plugin.WebSearch/Main.cs b/src/modules/launcher/Plugins/Community.PowerToys.Run.Plugin.WebSearch/Main.cs index b7a33acc38..dd83014518 100644 --- a/src/modules/launcher/Plugins/Community.PowerToys.Run.Plugin.WebSearch/Main.cs +++ b/src/modules/launcher/Plugins/Community.PowerToys.Run.Plugin.WebSearch/Main.cs @@ -36,6 +36,8 @@ namespace Community.PowerToys.Run.Plugin.WebSearch public string Description => Properties.Resources.plugin_description; + public static string PluginID => "9F1B49201C3F4BF781CAAD5CD88EA4DC"; + public IEnumerable AdditionalOptions => new List() { new PluginAdditionalOption() diff --git a/src/modules/launcher/Plugins/Microsoft.Plugin.Folder/Main.cs b/src/modules/launcher/Plugins/Microsoft.Plugin.Folder/Main.cs index 12763596f6..7911cc90c7 100644 --- a/src/modules/launcher/Plugins/Microsoft.Plugin.Folder/Main.cs +++ b/src/modules/launcher/Plugins/Microsoft.Plugin.Folder/Main.cs @@ -44,6 +44,8 @@ namespace Microsoft.Plugin.Folder public string Description => Properties.Resources.wox_plugin_folder_plugin_description; + public static string PluginID => "B4D3B69656E14D44865C8D818EAE47C4"; + public void Save() { _storage.Save(); diff --git a/src/modules/launcher/Plugins/Microsoft.Plugin.Indexer/Main.cs b/src/modules/launcher/Plugins/Microsoft.Plugin.Indexer/Main.cs index dc1b7cd606..38944a836c 100644 --- a/src/modules/launcher/Plugins/Microsoft.Plugin.Indexer/Main.cs +++ b/src/modules/launcher/Plugins/Microsoft.Plugin.Indexer/Main.cs @@ -51,6 +51,8 @@ namespace Microsoft.Plugin.Indexer public string Description => Properties.Resources.Microsoft_plugin_indexer_plugin_description; + public static string PluginID => "2140FC9819AD43A3A616E2735815C27C"; + public IEnumerable AdditionalOptions => new List() { new PluginAdditionalOption() diff --git a/src/modules/launcher/Plugins/Microsoft.Plugin.Program/Main.cs b/src/modules/launcher/Plugins/Microsoft.Plugin.Program/Main.cs index 146ee7556c..94416f32a1 100644 --- a/src/modules/launcher/Plugins/Microsoft.Plugin.Program/Main.cs +++ b/src/modules/launcher/Plugins/Microsoft.Plugin.Program/Main.cs @@ -37,6 +37,8 @@ namespace Microsoft.Plugin.Program public string Description => Properties.Resources.wox_plugin_program_plugin_description; + public static string PluginID => "791FC278BA414111B8D1886DFE447410"; + private static PluginInitContext _context; private readonly PluginJsonStorage _settingsStorage; private bool _disposed; diff --git a/src/modules/launcher/Plugins/Microsoft.Plugin.Shell/Main.cs b/src/modules/launcher/Plugins/Microsoft.Plugin.Shell/Main.cs index 5e97e3f29c..34118b04d0 100644 --- a/src/modules/launcher/Plugins/Microsoft.Plugin.Shell/Main.cs +++ b/src/modules/launcher/Plugins/Microsoft.Plugin.Shell/Main.cs @@ -39,6 +39,8 @@ namespace Microsoft.Plugin.Shell public string Description => Properties.Resources.wox_plugin_cmd_plugin_description; + public static string PluginID => "D409510CD0D2481F853690A07E6DC426"; + public IEnumerable AdditionalOptions => new List() { new PluginAdditionalOption() diff --git a/src/modules/launcher/Plugins/Microsoft.Plugin.Uri/Main.cs b/src/modules/launcher/Plugins/Microsoft.Plugin.Uri/Main.cs index c073520924..9d54eeb766 100644 --- a/src/modules/launcher/Plugins/Microsoft.Plugin.Uri/Main.cs +++ b/src/modules/launcher/Plugins/Microsoft.Plugin.Uri/Main.cs @@ -37,6 +37,8 @@ namespace Microsoft.Plugin.Uri public string Description => Properties.Resources.Microsoft_plugin_uri_plugin_description; + public static string PluginID => "03276A39D4E9417C8FFD200B0EE5E871"; + public List LoadContextMenus(Result selectedResult) { return new List(0); diff --git a/src/modules/launcher/Plugins/Microsoft.Plugin.WindowWalker/Main.cs b/src/modules/launcher/Plugins/Microsoft.Plugin.WindowWalker/Main.cs index 6c865faf39..cb660fe26b 100644 --- a/src/modules/launcher/Plugins/Microsoft.Plugin.WindowWalker/Main.cs +++ b/src/modules/launcher/Plugins/Microsoft.Plugin.WindowWalker/Main.cs @@ -29,6 +29,8 @@ namespace Microsoft.Plugin.WindowWalker public string Description => Properties.Resources.wox_plugin_windowwalker_plugin_description; + public static string PluginID => "F737A9223560B3C6833B5FFB8CDF78E5"; + internal static readonly VirtualDesktopHelper VirtualDesktopHelperInstance = new VirtualDesktopHelper(); public List Query(Query query) diff --git a/src/modules/launcher/Plugins/Microsoft.PowerToys.Run.Plugin.Calculator/Main.cs b/src/modules/launcher/Plugins/Microsoft.PowerToys.Run.Plugin.Calculator/Main.cs index d9b7626efc..d0ae109e39 100644 --- a/src/modules/launcher/Plugins/Microsoft.PowerToys.Run.Plugin.Calculator/Main.cs +++ b/src/modules/launcher/Plugins/Microsoft.PowerToys.Run.Plugin.Calculator/Main.cs @@ -30,6 +30,8 @@ namespace Microsoft.PowerToys.Run.Plugin.Calculator public string Description => Resources.wox_plugin_calculator_plugin_description; + public static string PluginID => "CEA0FDFC6D3B4085823D60DC76F28855"; + private bool _disposed; public IEnumerable AdditionalOptions => new List() diff --git a/src/modules/launcher/Plugins/Microsoft.PowerToys.Run.Plugin.History/Main.cs b/src/modules/launcher/Plugins/Microsoft.PowerToys.Run.Plugin.History/Main.cs index 894b2c463b..04f0efc5d8 100644 --- a/src/modules/launcher/Plugins/Microsoft.PowerToys.Run.Plugin.History/Main.cs +++ b/src/modules/launcher/Plugins/Microsoft.PowerToys.Run.Plugin.History/Main.cs @@ -22,6 +22,8 @@ namespace Microsoft.PowerToys.Run.Plugin.History public string Description => Resources.wox_plugin_history_plugin_description; + public static string PluginID => "C88512156BB74580AADF7252E130BA8D"; + private bool _disposed; public List Query(Query query) diff --git a/src/modules/launcher/Plugins/Microsoft.PowerToys.Run.Plugin.OneNote/Main.cs b/src/modules/launcher/Plugins/Microsoft.PowerToys.Run.Plugin.OneNote/Main.cs index 0e45d6063a..a5eb87d166 100644 --- a/src/modules/launcher/Plugins/Microsoft.PowerToys.Run.Plugin.OneNote/Main.cs +++ b/src/modules/launcher/Plugins/Microsoft.PowerToys.Run.Plugin.OneNote/Main.cs @@ -59,6 +59,11 @@ namespace Microsoft.PowerToys.Run.Plugin.OneNote /// public string Description => Resources.PluginDescription; + /// + /// Gets the plugin ID for validation + /// + public static string PluginID => "0778F0C264114FEC8A3DF59447CF0A74"; + /// /// Initialize the plugin with the given /// diff --git a/src/modules/launcher/Plugins/Microsoft.PowerToys.Run.Plugin.PowerToys/Main.cs b/src/modules/launcher/Plugins/Microsoft.PowerToys.Run.Plugin.PowerToys/Main.cs index 5a61a74b5c..24714012db 100644 --- a/src/modules/launcher/Plugins/Microsoft.PowerToys.Run.Plugin.PowerToys/Main.cs +++ b/src/modules/launcher/Plugins/Microsoft.PowerToys.Run.Plugin.PowerToys/Main.cs @@ -21,6 +21,8 @@ namespace Microsoft.PowerToys.Run.Plugin.PowerToys public string Description => Resources.Plugin_Description; + public static string PluginID => "29DD65DB28C84A37BDEF1D2B43DA368B"; + public string GetTranslatedPluginTitle() => Resources.Plugin_Name; public string GetTranslatedPluginDescription() => Resources.Plugin_Description; diff --git a/src/modules/launcher/Plugins/Microsoft.PowerToys.Run.Plugin.Registry/Main.cs b/src/modules/launcher/Plugins/Microsoft.PowerToys.Run.Plugin.Registry/Main.cs index 312c2b6d30..aa7b8e07c3 100644 --- a/src/modules/launcher/Plugins/Microsoft.PowerToys.Run.Plugin.Registry/Main.cs +++ b/src/modules/launcher/Plugins/Microsoft.PowerToys.Run.Plugin.Registry/Main.cs @@ -46,6 +46,8 @@ namespace Microsoft.PowerToys.Run.Plugin.Registry public string Description => Resources.PluginDescription; + public static string PluginID => "303417D927BF4C97BCFFC78A123BE0C8"; + /// /// Initializes a new instance of the class. /// diff --git a/src/modules/launcher/Plugins/Microsoft.PowerToys.Run.Plugin.Service/Main.cs b/src/modules/launcher/Plugins/Microsoft.PowerToys.Run.Plugin.Service/Main.cs index 28d3db512a..94e117796d 100644 --- a/src/modules/launcher/Plugins/Microsoft.PowerToys.Run.Plugin.Service/Main.cs +++ b/src/modules/launcher/Plugins/Microsoft.PowerToys.Run.Plugin.Service/Main.cs @@ -23,6 +23,8 @@ namespace Microsoft.PowerToys.Run.Plugin.Service public string Description => Resources.wox_plugin_service_plugin_description; + public static string PluginID => "11A6C36E4E91439CA69F702CBD364EF7"; + public void Init(PluginInitContext context) { _context = context; diff --git a/src/modules/launcher/Plugins/Microsoft.PowerToys.Run.Plugin.System/Main.cs b/src/modules/launcher/Plugins/Microsoft.PowerToys.Run.Plugin.System/Main.cs index 0591f4d1a1..09aaecc6e0 100644 --- a/src/modules/launcher/Plugins/Microsoft.PowerToys.Run.Plugin.System/Main.cs +++ b/src/modules/launcher/Plugins/Microsoft.PowerToys.Run.Plugin.System/Main.cs @@ -31,6 +31,8 @@ namespace Microsoft.PowerToys.Run.Plugin.System public string Description => Resources.Microsoft_plugin_sys_plugin_description; + public static string PluginID => "CEA08895D2544B019B2E9C5009600DF4"; + public string IconTheme { get; set; } public bool IsBootedInUefiMode { get; set; } diff --git a/src/modules/launcher/Plugins/Microsoft.PowerToys.Run.Plugin.TimeDate/Main.cs b/src/modules/launcher/Plugins/Microsoft.PowerToys.Run.Plugin.TimeDate/Main.cs index 1df1c78773..ac8824baa9 100644 --- a/src/modules/launcher/Plugins/Microsoft.PowerToys.Run.Plugin.TimeDate/Main.cs +++ b/src/modules/launcher/Plugins/Microsoft.PowerToys.Run.Plugin.TimeDate/Main.cs @@ -25,6 +25,8 @@ namespace Microsoft.PowerToys.Run.Plugin.TimeDate public string Description => GetTranslatedPluginDescription(); + public static string PluginID => "5D69806A5A474115821C3E4C56B9C793"; + public IEnumerable AdditionalOptions { get diff --git a/src/modules/launcher/Plugins/Microsoft.PowerToys.Run.Plugin.WindowsSettings/Main.cs b/src/modules/launcher/Plugins/Microsoft.PowerToys.Run.Plugin.WindowsSettings/Main.cs index 6d7bef456a..5ec5b32045 100644 --- a/src/modules/launcher/Plugins/Microsoft.PowerToys.Run.Plugin.WindowsSettings/Main.cs +++ b/src/modules/launcher/Plugins/Microsoft.PowerToys.Run.Plugin.WindowsSettings/Main.cs @@ -72,6 +72,11 @@ namespace Microsoft.PowerToys.Run.Plugin.WindowsSettings /// public string Description => Resources.PluginDescription; + /// + /// Gets the plugin ID for validation + /// + public static string PluginID => "5043CECEE6A748679CBE02D27D83747A"; + /// /// Initialize the plugin with the given . /// diff --git a/src/modules/launcher/Plugins/Microsoft.PowerToys.Run.Plugin.WindowsTerminal/Main.cs b/src/modules/launcher/Plugins/Microsoft.PowerToys.Run.Plugin.WindowsTerminal/Main.cs index e575ad99cf..c8ece1b5aa 100644 --- a/src/modules/launcher/Plugins/Microsoft.PowerToys.Run.Plugin.WindowsTerminal/Main.cs +++ b/src/modules/launcher/Plugins/Microsoft.PowerToys.Run.Plugin.WindowsTerminal/Main.cs @@ -34,6 +34,8 @@ namespace Microsoft.PowerToys.Run.Plugin.WindowsTerminal public string Description => Resources.plugin_description; + public static string PluginID => "F59BA85006B14389A72A0EA756695F1D"; + public IEnumerable AdditionalOptions => new List() { new PluginAdditionalOption() diff --git a/src/modules/launcher/PowerLauncher/Plugin/PluginManager.cs b/src/modules/launcher/PowerLauncher/Plugin/PluginManager.cs index 1e079840af..1b14ae0a95 100644 --- a/src/modules/launcher/PowerLauncher/Plugin/PluginManager.cs +++ b/src/modules/launcher/PowerLauncher/Plugin/PluginManager.cs @@ -11,6 +11,8 @@ using System.IO.Abstractions; using System.Linq; using System.Reflection; using System.Threading.Tasks; +using global::PowerToys.GPOWrapper; +using ManagedCommon; using PowerLauncher.Properties; using Wox.Infrastructure.Storage; using Wox.Plugin; @@ -140,6 +142,25 @@ namespace PowerLauncher.Plugin var failedPlugins = new ConcurrentQueue(); Parallel.ForEach(AllPlugins, pair => { + // Check policy state for the plugin and update metadata + var enabledPolicyState = GPOWrapper.GetRunPluginEnabledValue(pair.Metadata.ID); + if (enabledPolicyState == GpoRuleConfigured.Enabled) + { + pair.Metadata.Disabled = false; + pair.Metadata.IsEnabledPolicyConfigured = true; + Log.Info($"The plugin <{pair.Metadata.Name}> is enabled by policy.", typeof(PluginManager)); + } + else if (enabledPolicyState == GpoRuleConfigured.Disabled) + { + pair.Metadata.Disabled = true; + pair.Metadata.IsEnabledPolicyConfigured = true; + Log.Info($"The plugin <{pair.Metadata.Name}> is disabled by policy.", typeof(PluginManager)); + } + else if (enabledPolicyState == GpoRuleConfigured.WrongValue) + { + Log.Warn($"Wrong policy value for enabled policy for plugin <{pair.Metadata.Name}>.", typeof(PluginManager)); + } + if (pair.Metadata.Disabled) { return; diff --git a/src/modules/launcher/PowerLauncher/SettingsReader.cs b/src/modules/launcher/PowerLauncher/SettingsReader.cs index 6fba7247da..30ef4bbe75 100644 --- a/src/modules/launcher/PowerLauncher/SettingsReader.cs +++ b/src/modules/launcher/PowerLauncher/SettingsReader.cs @@ -9,6 +9,7 @@ using System.Linq; using System.Threading; using System.Windows.Input; using Common.UI; +using global::PowerToys.GPOWrapper; using Microsoft.PowerToys.Settings.UI.Library; using PowerLauncher.Helper; using PowerLauncher.Plugin; @@ -242,6 +243,7 @@ namespace PowerLauncher IconPathDark = GetIcon(x.Metadata, x.Metadata.IcoPathDark), IconPathLight = GetIcon(x.Metadata, x.Metadata.IcoPathLight), AdditionalOptions = x.Plugin is ISettingProvider ? (x.Plugin as ISettingProvider).AdditionalOptions : new List(), + EnabledPolicyUiState = (int)GpoRuleConfigured.NotConfigured, }); } @@ -256,11 +258,13 @@ namespace PowerLauncher if (defaultPlugins.ContainsKey(plugin.Id)) { var additionalOptions = CombineAdditionalOptions(defaultPlugins[plugin.Id].AdditionalOptions, plugin.AdditionalOptions); + var enabledPolicyState = GPOWrapper.GetRunPluginEnabledValue(plugin.Id); plugin.Name = defaultPlugins[plugin.Id].Name; plugin.Description = defaultPlugins[plugin.Id].Description; plugin.Author = defaultPlugins[plugin.Id].Author; plugin.IconPathDark = defaultPlugins[plugin.Id].IconPathDark; plugin.IconPathLight = defaultPlugins[plugin.Id].IconPathLight; + plugin.EnabledPolicyUiState = (int)enabledPolicyState; defaultPlugins[plugin.Id] = plugin; defaultPlugins[plugin.Id].AdditionalOptions = additionalOptions; } diff --git a/src/modules/launcher/Wox.Plugin/IPlugin.cs b/src/modules/launcher/Wox.Plugin/IPlugin.cs index 464480c065..c41f96538d 100644 --- a/src/modules/launcher/Wox.Plugin/IPlugin.cs +++ b/src/modules/launcher/Wox.Plugin/IPlugin.cs @@ -2,6 +2,7 @@ // The Microsoft Corporation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. +using System; using System.Collections.Generic; namespace Wox.Plugin @@ -17,5 +18,11 @@ namespace Wox.Plugin // Localized description string Description { get; } + + /* The two property lines are commented because they break the unit tests. (The Moq package used in the unit tests doesn't support the .Net 7 feature 'static abstract' properties yet.) - https://github.com/Moq/Moq/issues/1398 + * + * // Plugin ID for validating the plugin.json entry (It must be static for accessing it before loading the plugin.) + * public static abstract string PluginID { get; } + */ } } diff --git a/src/modules/launcher/Wox.Plugin/PluginMetadata.cs b/src/modules/launcher/Wox.Plugin/PluginMetadata.cs index 514bcbcf64..a9461f0181 100644 --- a/src/modules/launcher/Wox.Plugin/PluginMetadata.cs +++ b/src/modules/launcher/Wox.Plugin/PluginMetadata.cs @@ -32,6 +32,10 @@ namespace Wox.Plugin public bool Disabled { get; set; } + // This property is used in PT Run only to decide whether to updated the Disabled property or not. + [JsonIgnore] + public bool IsEnabledPolicyConfigured { get; set; } + [JsonInclude] public string ExecuteFilePath { get; private set; } diff --git a/src/modules/launcher/Wox.Plugin/PluginPair.cs b/src/modules/launcher/Wox.Plugin/PluginPair.cs index 52a08041a1..3d46dfde74 100644 --- a/src/modules/launcher/Wox.Plugin/PluginPair.cs +++ b/src/modules/launcher/Wox.Plugin/PluginPair.cs @@ -63,20 +63,24 @@ namespace Wox.Plugin return; } - if (Metadata.Disabled && !setting.Disabled) + // If the enabled state is policy managed then we skip the update of the disabled state as it must be a manual settings.json manipulation. + if (!Metadata.IsEnabledPolicyConfigured) { - Metadata.Disabled = false; - InitializePlugin(api); - - if (!IsPluginInitialized) + if (Metadata.Disabled && !setting.Disabled) { - string description = $"{Resources.FailedToLoadPluginDescription} {Metadata.Name}\n\n{Resources.FailedToLoadPluginDescriptionPartTwo}"; - api.ShowMsg(Resources.FailedToLoadPluginTitle, description, string.Empty, false); + Metadata.Disabled = false; + InitializePlugin(api); + + if (!IsPluginInitialized) + { + string description = $"{Resources.FailedToLoadPluginDescription} {Metadata.Name}\n\n{Resources.FailedToLoadPluginDescriptionPartTwo}"; + api.ShowMsg(Resources.FailedToLoadPluginTitle, description, string.Empty, false); + } + } + else + { + Metadata.Disabled = setting.Disabled; } - } - else - { - Metadata.Disabled = setting.Disabled; } Metadata.ActionKeyword = setting.ActionKeyword; @@ -167,6 +171,19 @@ namespace Wox.Plugin return false; } + // Validate plugin ID to prevent bypassing the GPO by changing the ID in the plugin.json file. + string pluginID = (string)type.GetProperty("PluginID", BindingFlags.Public | BindingFlags.Static)?.GetValue(null); + if (pluginID == null) + { + Log.Error($"Can't validate plugin ID of plugin <{Metadata.Name}> in {Metadata.ExecuteFilePath}: The static property was not found.", MethodBase.GetCurrentMethod().DeclaringType); + return false; + } + else if (pluginID != Metadata.ID) + { + Log.Error($"Wrong plugin ID found in plugin.json of plugin <{Metadata.Name}>. ('{Metadata.ID}' != '{pluginID}')", MethodBase.GetCurrentMethod().DeclaringType); + return false; + } + try { Plugin = (IPlugin)Activator.CreateInstance(type); diff --git a/src/settings-ui/Settings.UI.Library/PowerLauncherPluginSettings.cs b/src/settings-ui/Settings.UI.Library/PowerLauncherPluginSettings.cs index d1561d6440..2b84fe16f2 100644 --- a/src/settings-ui/Settings.UI.Library/PowerLauncherPluginSettings.cs +++ b/src/settings-ui/Settings.UI.Library/PowerLauncherPluginSettings.cs @@ -18,6 +18,10 @@ namespace Microsoft.PowerToys.Settings.UI.Library public bool Disabled { get; set; } + // Use to communicate the state to settings UI (Using int type because we can't reference GPOWrapper.) + // This property should never be used inside of PT Run to get the policy state as it can be manipulated by changing the settings.json file. + public int EnabledPolicyUiState { get; set; } + public bool IsGlobal { get; set; } public string ActionKeyword { get; set; } diff --git a/src/settings-ui/Settings.UI/SettingsXAML/Views/PowerLauncherPage.xaml b/src/settings-ui/Settings.UI/SettingsXAML/Views/PowerLauncherPage.xaml index 05347debf7..42832199a9 100644 --- a/src/settings-ui/Settings.UI/SettingsXAML/Views/PowerLauncherPage.xaml +++ b/src/settings-ui/Settings.UI/SettingsXAML/Views/PowerLauncherPage.xaml @@ -238,7 +238,12 @@ - + - + + 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 25b09ac683..1a3006007a 100644 --- a/src/settings-ui/Settings.UI/Strings/en-us/Resources.resw +++ b/src/settings-ui/Settings.UI/Strings/en-us/Resources.resw @@ -3680,6 +3680,9 @@ Activate by holding the key for the character you want to add an accent to, then Find more plugins + + The system administrator is managing the enabled state of some plugins. + Opacity (%) diff --git a/src/settings-ui/Settings.UI/ViewModels/PowerLauncherPluginViewModel.cs b/src/settings-ui/Settings.UI/ViewModels/PowerLauncherPluginViewModel.cs index 6988c02034..9d3fbcc39f 100644 --- a/src/settings-ui/Settings.UI/ViewModels/PowerLauncherPluginViewModel.cs +++ b/src/settings-ui/Settings.UI/ViewModels/PowerLauncherPluginViewModel.cs @@ -8,6 +8,7 @@ using System.ComponentModel; using System.IO; using System.Linq; using System.Runtime.CompilerServices; +using global::PowerToys.GPOWrapper; using Microsoft.PowerToys.Settings.UI.Library; namespace Microsoft.PowerToys.Settings.UI.ViewModels @@ -33,6 +34,9 @@ namespace Microsoft.PowerToys.Settings.UI.ViewModels NotifyPropertyChanged(nameof(AdditionalOptions)); }; } + + _enabledGpoRuleConfiguration = (GpoRuleConfigured)settings.EnabledPolicyUiState; + _enabledGpoRuleIsConfigured = _enabledGpoRuleConfiguration == GpoRuleConfigured.Disabled || _enabledGpoRuleConfiguration == GpoRuleConfigured.Enabled; } public string Id { get => settings.Id; } @@ -43,11 +47,25 @@ namespace Microsoft.PowerToys.Settings.UI.ViewModels public string Author { get => settings.Author; } + private GpoRuleConfigured _enabledGpoRuleConfiguration; + private bool _enabledGpoRuleIsConfigured; + public bool Disabled { get { - return settings.Disabled; + if (_enabledGpoRuleConfiguration == GpoRuleConfigured.Disabled) + { + return true; + } + else if (_enabledGpoRuleConfiguration == GpoRuleConfigured.Enabled) + { + return false; + } + else + { + return settings.Disabled; + } } set @@ -55,6 +73,7 @@ namespace Microsoft.PowerToys.Settings.UI.ViewModels if (settings.Disabled != value) { settings.Disabled = value; + NotifyPropertyChanged(); NotifyPropertyChanged(nameof(ShowNotAccessibleWarning)); NotifyPropertyChanged(nameof(Enabled)); @@ -67,6 +86,8 @@ namespace Microsoft.PowerToys.Settings.UI.ViewModels public bool Enabled => !Disabled; + public bool EnabledGpoRuleIsConfigured => _enabledGpoRuleIsConfigured; + public double DisabledOpacity => Disabled ? 0.5 : 1; public bool IsGlobalAndEnabled diff --git a/src/settings-ui/Settings.UI/ViewModels/PowerLauncherViewModel.cs b/src/settings-ui/Settings.UI/ViewModels/PowerLauncherViewModel.cs index 9629316c79..b0556fba1a 100644 --- a/src/settings-ui/Settings.UI/ViewModels/PowerLauncherViewModel.cs +++ b/src/settings-ui/Settings.UI/ViewModels/PowerLauncherViewModel.cs @@ -135,6 +135,7 @@ namespace Microsoft.PowerToys.Settings.UI.ViewModels } OnPropertyChanged(nameof(ShowAllPluginsDisabledWarning)); + OnPropertyChanged(nameof(ShowPluginsAreGpoManagedInfo)); UpdateSettings(); } @@ -174,6 +175,7 @@ namespace Microsoft.PowerToys.Settings.UI.ViewModels OnPropertyChanged(nameof(EnablePowerLauncher)); OnPropertyChanged(nameof(ShowAllPluginsDisabledWarning)); OnPropertyChanged(nameof(ShowPluginsLoadingMessage)); + OnPropertyChanged(nameof(ShowPluginsAreGpoManagedInfo)); OutGoingGeneralSettings outgoing = new OutGoingGeneralSettings(GeneralSettingsConfig); SendConfigMSG(outgoing.ToString()); } @@ -186,6 +188,7 @@ namespace Microsoft.PowerToys.Settings.UI.ViewModels OnPropertyChanged(nameof(EnablePowerLauncher)); OnPropertyChanged(nameof(ShowAllPluginsDisabledWarning)); OnPropertyChanged(nameof(ShowPluginsLoadingMessage)); + OnPropertyChanged(nameof(ShowPluginsAreGpoManagedInfo)); } public bool IsEnabledGpoConfigured @@ -616,9 +619,14 @@ namespace Microsoft.PowerToys.Settings.UI.ViewModels } } + public bool ShowPluginsAreGpoManagedInfo + { + get => EnablePowerLauncher && settings.Plugins.Any() && Plugins.Any(x => x.EnabledGpoRuleIsConfigured); + } + public bool ShowAllPluginsDisabledWarning { - get => EnablePowerLauncher && settings.Plugins.Any() && settings.Plugins.All(x => x.Disabled); + get => EnablePowerLauncher && settings.Plugins.Any() && Plugins.All(x => x.Disabled); } public bool ShowPluginsLoadingMessage From 93d80f542c6c111a318e9f43370f4e01dafd0a4c Mon Sep 17 00:00:00 2001 From: Michael Clayton Date: Wed, 11 Oct 2023 15:58:19 +0100 Subject: [PATCH 2/2] [MouseJump]Long lived background process (#28380) * [MouseJump] Long lived background exe * [MouseJump] Long lived background exe * [MouseJump] Long lived background exe * [MouseJump] Long lived background exe * [MouseJump] Close long lived background exe when parent runner exits * [MouseJump] Close long lived background exe when parent runner exits * [MouseJump] long lived background exe - fixing build * [MouseJump] - add FileSystemWatcher for config (#26703) * Fix telemetry event --- src/common/interop/interop.cpp | 4 + src/common/interop/shared_constants.h | 3 + .../MouseJump/MouseJump.vcxproj.filters | 56 ++++++++++ src/modules/MouseUtils/MouseJump/dllmain.cpp | 101 +++++++++--------- .../MouseJumpUI/Helpers/SettingsHelper.cs | 95 ++++++++++++++++ .../Helpers/ThrottledActionInvoker.cs | 47 ++++++++ .../MouseUtils/MouseJumpUI/MainForm.cs | 61 +++++++---- .../MouseUtils/MouseJumpUI/MouseJumpUI.csproj | 1 + src/modules/MouseUtils/MouseJumpUI/Program.cs | 46 +++++++- .../Settings.UI.Library/MouseJumpSettings.cs | 18 ++++ 10 files changed, 359 insertions(+), 73 deletions(-) create mode 100644 src/modules/MouseUtils/MouseJump/MouseJump.vcxproj.filters create mode 100644 src/modules/MouseUtils/MouseJumpUI/Helpers/SettingsHelper.cs create mode 100644 src/modules/MouseUtils/MouseJumpUI/Helpers/ThrottledActionInvoker.cs diff --git a/src/common/interop/interop.cpp b/src/common/interop/interop.cpp index aebf044f21..46ce729219 100644 --- a/src/common/interop/interop.cpp +++ b/src/common/interop/interop.cpp @@ -203,6 +203,10 @@ public return gcnew String(CommonSharedConstants::SHOW_POWEROCR_SHARED_EVENT); } + static String ^ MouseJumpShowPreviewEvent() { + return gcnew String(CommonSharedConstants::MOUSE_JUMP_SHOW_PREVIEW_EVENT); + } + static String ^ AwakeExitEvent() { return gcnew String(CommonSharedConstants::AWAKE_EXIT_EVENT); } diff --git a/src/common/interop/shared_constants.h b/src/common/interop/shared_constants.h index 1535a3aa87..4761d3c22c 100644 --- a/src/common/interop/shared_constants.h +++ b/src/common/interop/shared_constants.h @@ -50,6 +50,9 @@ namespace CommonSharedConstants // Path to the event used by PowerOCR const wchar_t SHOW_POWEROCR_SHARED_EVENT[] = L"Local\\PowerOCREvent-dc864e06-e1af-4ecc-9078-f98bee745e3a"; + // Path to the events used by Mouse Jump + const wchar_t MOUSE_JUMP_SHOW_PREVIEW_EVENT[] = L"Local\\MouseJumpEvent-aa0be051-3396-4976-b7ba-1a9cc7d236a5"; + // Path to the event used by RegistryPreview const wchar_t REGISTRY_PREVIEW_TRIGGER_EVENT[] = L"Local\\RegistryPreviewEvent-4C559468-F75A-4E7F-BC4F-9C9688316687"; diff --git a/src/modules/MouseUtils/MouseJump/MouseJump.vcxproj.filters b/src/modules/MouseUtils/MouseJump/MouseJump.vcxproj.filters new file mode 100644 index 0000000000..1d00e4c5b3 --- /dev/null +++ b/src/modules/MouseUtils/MouseJump/MouseJump.vcxproj.filters @@ -0,0 +1,56 @@ + + + + + {4FC737F1-C7A5-4376-A066-2A32D752A2FF} + cpp;c;cc;cxx;c++;def;odl;idl;hpj;bat;asm;asmx + + + {93995380-89BD-4b04-88EB-625FBE52EBFB} + h;hh;hpp;hxx;h++;hm;inl;inc;ipp;xsd + + + {67DA6AB6-F800-4c08-8B7A-83BB121AAD01} + rc;ico;cur;bmp;dlg;rc2;rct;bin;rgs;gif;jpg;jpeg;jpe;resx;tiff;tif;png;wav;mfcribbon-ms + + + {875a08c6-f610-4667-bd0f-80171ed96072} + + + + + Header Files + + + Generated Files + + + Header Files + + + + + Source Files + + + Source Files + + + Source Files + + + + + Header Files + + + Resource Files + + + + + + Resource Files + + + \ No newline at end of file diff --git a/src/modules/MouseUtils/MouseJump/dllmain.cpp b/src/modules/MouseUtils/MouseJump/dllmain.cpp index 8699fc4fad..d687caa063 100644 --- a/src/modules/MouseUtils/MouseJump/dllmain.cpp +++ b/src/modules/MouseUtils/MouseJump/dllmain.cpp @@ -1,32 +1,32 @@ +// dllmain.cpp : Defines the entry point for the DLL application. #include "pch.h" #include -//#include -//#include -#include #include "trace.h" -#include +#include +#include +#include #include +#include -extern "C" IMAGE_DOS_HEADER __ImageBase; - -HMODULE m_hModule; - -BOOL APIENTRY DllMain(HMODULE hModule, DWORD ul_reason_for_call, LPVOID /*lpReserved*/) +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: + break; case DLL_THREAD_DETACH: break; case DLL_PROCESS_DETACH: Trace::UnregisterProvider(); break; } + return TRUE; } @@ -54,9 +54,17 @@ private: bool m_enabled = false; // Hotkey to invoke the module - Hotkey m_hotkey; + HANDLE m_hProcess; + // Time to wait for process to close after sending WM_CLOSE signal + static const int MAX_WAIT_MILLISEC = 10000; + + Hotkey m_hotkey; + + // Handle to event used to invoke PowerOCR + HANDLE m_hInvokeEvent; + void parse_hotkey(PowerToysSettings::PowerToyValues& settings) { auto settingsObject = settings.get_raw_json(); @@ -123,11 +131,21 @@ private: } // Load initial settings from the persisted values. - void init_settings(); - - void terminate_process() + void init_settings() { - TerminateProcess(m_hProcess, 1); + try + { + // Load and parse the settings file for this PowerToy. + PowerToysSettings::PowerToyValues settings = + PowerToysSettings::PowerToyValues::load_from_settings_file(get_key()); + + parse_hotkey(settings); + } + catch (std::exception&) + { + Logger::warn(L"An exception occurred while loading the settings file"); + // Error while loading from the settings file. Let default values stay as they are. + } } public: @@ -135,6 +153,7 @@ public: MouseJump() { LoggerHelpers::init_logger(MODULE_NAME, L"ModuleInterface", LogSettings::mouseJumpLoggerName); + m_hInvokeEvent = CreateDefaultEvent(CommonSharedConstants::MOUSE_JUMP_SHOW_PREVIEW_EVENT); init_settings(); }; @@ -142,7 +161,6 @@ public: { if (m_enabled) { - terminate_process(); } m_enabled = false; } @@ -150,6 +168,7 @@ public: // Destroy the powertoy and free memory virtual void destroy() override { + Logger::trace("MouseJump::destroy()"); delete this; } @@ -180,12 +199,14 @@ public: PowerToysSettings::Settings settings(hinstance, get_name()); settings.set_description(MODULE_DESC); + settings.set_overview_link(L"https://aka.ms/PowerToysOverview_MouseUtilities/#mouse-jump"); + 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 + virtual void call_custom_action(const wchar_t* /*action*/) override { } @@ -199,7 +220,6 @@ public: PowerToysSettings::PowerToyValues::from_json_string(config, get_key()); parse_hotkey(values); - values.save_to_settings_file(); } catch (std::exception&) @@ -211,6 +231,9 @@ public: // Enable the powertoy virtual void enable() { + Logger::trace("MouseJump::enable()"); + ResetEvent(m_hInvokeEvent); + launch_process(); m_enabled = true; Trace::EnableJumpTool(true); } @@ -218,33 +241,29 @@ public: // Disable the powertoy virtual void disable() { + Logger::trace("MouseJump::disable()"); if (m_enabled) { - terminate_process(); + ResetEvent(m_hInvokeEvent); + TerminateProcess(m_hProcess, 1); } m_enabled = false; Trace::EnableJumpTool(false); } - // Returns if the powertoys is enabled - virtual bool is_enabled() override - { - return m_enabled; - } - virtual bool on_hotkey(size_t /*hotkeyId*/) override { if (m_enabled) { Logger::trace(L"MouseJump hotkey pressed"); Trace::InvokeJumpTool(); - if (is_process_running()) + if (!is_process_running()) { - terminate_process(); + launch_process(); } - launch_process(); + SetEvent(m_hInvokeEvent); return true; } @@ -268,6 +287,12 @@ public: } } + // Returns if the powertoys is enabled + virtual bool is_enabled() override + { + return m_enabled; + } + // Returns whether the PowerToys should be enabled by default virtual bool is_enabled_by_default() const override { @@ -276,26 +301,6 @@ public: }; -// Load the settings file. -void MouseJump::init_settings() -{ - try - { - // Load and parse the settings file for this PowerToy. - PowerToysSettings::PowerToyValues settings = - PowerToysSettings::PowerToyValues::load_from_settings_file(MouseJump::get_name()); - - parse_hotkey(settings); - - } - catch (std::exception&) - { - Logger::warn(L"An exception occurred while loading the settings file"); - // Error while loading from the settings file. Let default values stay as they are. - } -} - - extern "C" __declspec(dllexport) PowertoyModuleIface* __cdecl powertoy_create() { return new MouseJump(); diff --git a/src/modules/MouseUtils/MouseJumpUI/Helpers/SettingsHelper.cs b/src/modules/MouseUtils/MouseJumpUI/Helpers/SettingsHelper.cs new file mode 100644 index 0000000000..c2602ad700 --- /dev/null +++ b/src/modules/MouseUtils/MouseJumpUI/Helpers/SettingsHelper.cs @@ -0,0 +1,95 @@ +// Copyright (c) Microsoft Corporation +// The Microsoft Corporation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System; +using System.IO; +using System.IO.Abstractions; +using System.Threading; +using ManagedCommon; +using Microsoft.PowerToys.Settings.UI.Library; +using Microsoft.PowerToys.Settings.UI.Library.Utilities; + +namespace MouseJumpUI.Helpers; + +internal class SettingsHelper +{ + public SettingsHelper() + { + this.LockObject = new(); + this.CurrentSettings = this.LoadSettings(); + + // delay loading settings on change by some time to avoid file in use exception + var throttledActionInvoker = new ThrottledActionInvoker(); + this.FileSystemWatcher = Helper.GetFileWatcher( + moduleName: MouseJumpSettings.ModuleName, + fileName: "settings.json", + onChangedCallback: () => throttledActionInvoker.ScheduleAction(this.ReloadSettings, 250)); + } + + private IFileSystemWatcher FileSystemWatcher + { + get; + } + + private object LockObject + { + get; + } + + public MouseJumpSettings CurrentSettings + { + get; + private set; + } + + private MouseJumpSettings LoadSettings() + { + lock (this.LockObject) + { + { + var settingsUtils = new SettingsUtils(); + + // set this to 1 to disable retries + var remainingRetries = 5; + + while (remainingRetries > 0) + { + try + { + if (!settingsUtils.SettingsExists(MouseJumpSettings.ModuleName)) + { + Logger.LogInfo("MouseJump settings.json was missing, creating a new one"); + var defaultSettings = new MouseJumpSettings(); + defaultSettings.Save(settingsUtils); + } + + var settings = settingsUtils.GetSettingsOrDefault(MouseJumpSettings.ModuleName); + return settings; + } + catch (IOException ex) + { + Logger.LogError("Failed to read changed settings", ex); + Thread.Sleep(250); + } + catch (Exception ex) + { + Logger.LogError("Failed to read changed settings", ex); + Thread.Sleep(250); + } + + remainingRetries--; + } + } + } + + const string message = "Failed to read changed settings - ran out of retries"; + Logger.LogError(message); + throw new InvalidOperationException(message); + } + + public void ReloadSettings() + { + this.CurrentSettings = this.LoadSettings(); + } +} diff --git a/src/modules/MouseUtils/MouseJumpUI/Helpers/ThrottledActionInvoker.cs b/src/modules/MouseUtils/MouseJumpUI/Helpers/ThrottledActionInvoker.cs new file mode 100644 index 0000000000..ecd17cfa4c --- /dev/null +++ b/src/modules/MouseUtils/MouseJumpUI/Helpers/ThrottledActionInvoker.cs @@ -0,0 +1,47 @@ +// Copyright (c) Microsoft Corporation +// The Microsoft Corporation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System; +using System.Windows.Threading; + +namespace MouseJumpUI.Helpers; + +internal sealed class ThrottledActionInvoker +{ + private readonly object _invokerLock = new(); + private readonly DispatcherTimer _timer; + + private Action? _actionToRun; + + public ThrottledActionInvoker() + { + _timer = new DispatcherTimer(); + _timer.Tick += Timer_Tick; + } + + public void ScheduleAction(Action action, int milliseconds) + { + lock (_invokerLock) + { + if (_timer.IsEnabled) + { + _timer.Stop(); + } + + _actionToRun = action; + _timer.Interval = new TimeSpan(0, 0, 0, 0, milliseconds); + + _timer.Start(); + } + } + + private void Timer_Tick(object? sender, EventArgs? e) + { + lock (_invokerLock) + { + _timer.Stop(); + _actionToRun?.Invoke(); + } + } +} diff --git a/src/modules/MouseUtils/MouseJumpUI/MainForm.cs b/src/modules/MouseUtils/MouseJumpUI/MainForm.cs index 7dbc1c6dd4..2b055aa862 100644 --- a/src/modules/MouseUtils/MouseJumpUI/MainForm.cs +++ b/src/modules/MouseUtils/MouseJumpUI/MainForm.cs @@ -19,14 +19,13 @@ namespace MouseJumpUI; internal partial class MainForm : Form { - public MainForm(MouseJumpSettings settings) + public MainForm(SettingsHelper settingsHelper) { this.InitializeComponent(); - this.Settings = settings ?? throw new ArgumentNullException(nameof(settings)); - this.ShowThumbnail(); + this.SettingsHelper = settingsHelper ?? throw new ArgumentNullException(nameof(settingsHelper)); } - public MouseJumpSettings Settings + public SettingsHelper SettingsHelper { get; } @@ -104,14 +103,8 @@ internal partial class MainForm : Form private void MainForm_Deactivate(object sender, EventArgs e) { - this.Close(); - - if (this.Thumbnail.Image is not null) - { - var tmp = this.Thumbnail.Image; - this.Thumbnail.Image = null; - tmp.Dispose(); - } + this.Hide(); + this.ClearPreview(); } private void Thumbnail_Click(object sender, EventArgs e) @@ -139,8 +132,11 @@ internal partial class MainForm : Form this.OnDeactivate(EventArgs.Empty); } - public void ShowThumbnail() + public void ShowPreview() { + // hide the form while we redraw it... + this.Visible = false; + var stopwatch = Stopwatch.StartNew(); var layoutInfo = MainForm.GetLayoutInfo(this); LayoutHelper.PositionForm(this, layoutInfo.FormBounds); @@ -148,7 +144,7 @@ internal partial class MainForm : Form stopwatch.Stop(); // we have to activate the form to make sure the deactivate event fires - Microsoft.PowerToys.Telemetry.PowerToysTelemetry.Log.WriteEvent(new Telemetry.MouseJumpTeleportCursorEvent()); + Microsoft.PowerToys.Telemetry.PowerToysTelemetry.Log.WriteEvent(new Telemetry.MouseJumpShowEvent()); this.Activate(); } @@ -175,6 +171,9 @@ internal partial class MainForm : Form .Single(item => item.Screen.Handle == activatedScreenHandle.Value) .Index; + // avoid a race condition - cache the current settings in case they change + var currentSettings = form.SettingsHelper.CurrentSettings; + var layoutConfig = new LayoutConfig( virtualScreenBounds: ScreenHelper.GetVirtualScreen(), screens: screens.Select(item => item.Screen).ToList(), @@ -182,13 +181,13 @@ internal partial class MainForm : Form activatedScreenIndex: activatedScreenIndex, activatedScreenNumber: activatedScreenIndex + 1, maximumFormSize: new( - form.Settings.Properties.ThumbnailSize.Width, - form.Settings.Properties.ThumbnailSize.Height), - formPadding: new( - form.panel1.Padding.Left, - form.panel1.Padding.Top, - form.panel1.Padding.Right, - form.panel1.Padding.Bottom), + currentSettings.Properties.ThumbnailSize.Width, + currentSettings.Properties.ThumbnailSize.Height), + /* + don't read the panel padding values because they are affected by dpi scaling + and can give wrong values when moving between monitors with different dpi scaling + */ + formPadding: new(5, 5, 5, 5), previewPadding: new(0)); Logger.LogInfo(string.Join( '\n', @@ -295,10 +294,30 @@ internal partial class MainForm : Form stopwatch.Stop(); } + private void ClearPreview() + { + if (this.Thumbnail.Image is null) + { + return; + } + + var tmp = this.Thumbnail.Image; + this.Thumbnail.Image = null; + tmp.Dispose(); + + // force preview image memory to be released, otherwise + // all the disposed images can pile up without being GC'ed + GC.Collect(); + } + private static void RefreshPreview(MainForm form) { if (!form.Visible) { + // we seem to need to turn off topmost and then re-enable it again + // when we show the form, otherwise it doesn't get shown topmost... + form.TopMost = false; + form.TopMost = true; form.Show(); } diff --git a/src/modules/MouseUtils/MouseJumpUI/MouseJumpUI.csproj b/src/modules/MouseUtils/MouseJumpUI/MouseJumpUI.csproj index 478e7afe1e..d8cbe38df9 100644 --- a/src/modules/MouseUtils/MouseJumpUI/MouseJumpUI.csproj +++ b/src/modules/MouseUtils/MouseJumpUI/MouseJumpUI.csproj @@ -62,6 +62,7 @@ + diff --git a/src/modules/MouseUtils/MouseJumpUI/Program.cs b/src/modules/MouseUtils/MouseJumpUI/Program.cs index 7d3c385730..d70ebfa454 100644 --- a/src/modules/MouseUtils/MouseJumpUI/Program.cs +++ b/src/modules/MouseUtils/MouseJumpUI/Program.cs @@ -4,10 +4,16 @@ using System; using System.IO; +using System.Reflection; using System.Text.Json; +using System.Threading; using System.Windows.Forms; +using System.Windows.Threading; +using Common.UI; +using interop; using ManagedCommon; using Microsoft.PowerToys.Settings.UI.Library; +using MouseJumpUI.Helpers; namespace MouseJumpUI; @@ -17,7 +23,7 @@ internal static class Program /// The main entry point for the application. /// [STAThread] - private static void Main() + private static void Main(string[] args) { Logger.InitializeLogger("\\MouseJump\\Logs"); @@ -38,10 +44,42 @@ internal static class Program return; } - var settings = Program.ReadSettings(); - var mainForm = new MainForm(settings); + // validate command line arguments - we're expecting + // a single argument containing the runner pid + if ((args.Length != 1) || !int.TryParse(args[0], out var runnerPid)) + { + var message = string.Join("\r\n", new[] + { + "Invalid command line arguments.", + "Expected usage is:", + string.Empty, + $"{Assembly.GetExecutingAssembly().GetName().Name} ", + }); + Logger.LogInfo(message); + throw new InvalidOperationException(message); + } - Application.Run(mainForm); + Logger.LogInfo($"Mouse Jump started from the PowerToys Runner. Runner pid={runnerPid}"); + + var cancellationTokenSource = new CancellationTokenSource(); + + RunnerHelper.WaitForPowerToysRunner(runnerPid, () => + { + Logger.LogInfo("PowerToys Runner exited. Exiting Mouse Jump"); + cancellationTokenSource.Cancel(); + Application.Exit(); + }); + + var settingsHelper = new SettingsHelper(); + var mainForm = new MainForm(settingsHelper); + + NativeEventWaiter.WaitForEventLoop( + Constants.MouseJumpShowPreviewEvent(), + mainForm.ShowPreview, + Dispatcher.CurrentDispatcher, + cancellationTokenSource.Token); + + Application.Run(); } private static MouseJumpSettings ReadSettings() diff --git a/src/settings-ui/Settings.UI.Library/MouseJumpSettings.cs b/src/settings-ui/Settings.UI.Library/MouseJumpSettings.cs index 73b15f5086..2c0cf9eb3b 100644 --- a/src/settings-ui/Settings.UI.Library/MouseJumpSettings.cs +++ b/src/settings-ui/Settings.UI.Library/MouseJumpSettings.cs @@ -2,6 +2,8 @@ // The Microsoft Corporation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. +using System; +using System.Text.Json; using System.Text.Json.Serialization; using Microsoft.PowerToys.Settings.UI.Library.Interfaces; @@ -21,6 +23,22 @@ namespace Microsoft.PowerToys.Settings.UI.Library Version = "1.0"; } + public void Save(ISettingsUtils settingsUtils) + { + // Save settings to file + var options = new JsonSerializerOptions + { + WriteIndented = true, + }; + + if (settingsUtils == null) + { + throw new ArgumentNullException(nameof(settingsUtils)); + } + + settingsUtils.SaveSettings(JsonSerializer.Serialize(this, options), ModuleName); + } + public string GetModuleName() { return Name;