From 9086995eebf8c6f81c00c2689efbaec4e2fdf83c Mon Sep 17 00:00:00 2001
From: Shawn Yuan <128874481+shuaiyuanxx@users.noreply.github.com>
Date: Wed, 7 Jan 2026 16:38:09 +0800
Subject: [PATCH] Settings Flyout improvement (#43840)
## Summary of the Pull Request
This pull request introduces the new Quick Access feature to PowerToys
by integrating its host process management into the runner and system
tray. The changes add the Quick Access host implementation, update
project and build files to include it, and modify the runner and tray
icon logic to launch and interact with the Quick Access UI.
## PR Checklist
- [x] Closes: #43694
- [x] **Communication:** I've discussed this with core contributors
already. If the work hasn't been agreed, this work might be rejected
- [x] **Tests:** Added/updated and all pass
- [ ] **Localization:** All end-user-facing strings can be localized
- [ ] **Dev docs:** Added/updated
- [ ] **New binaries:** Added on the required places
- [ ] [JSON for
signing](https://github.com/microsoft/PowerToys/blob/main/.pipelines/ESRPSigning_core.json)
for new binaries
- [ ] [WXS for
installer](https://github.com/microsoft/PowerToys/blob/main/installer/PowerToysSetup/Product.wxs)
for new binaries and localization folder
- [ ] [YML for CI
pipeline](https://github.com/microsoft/PowerToys/blob/main/.pipelines/ci/templates/build-powertoys-steps.yml)
for new test projects
- [ ] [YML for signed
pipeline](https://github.com/microsoft/PowerToys/blob/main/.pipelines/release.yml)
- [ ] **Documentation updated:** If checked, please file a pull request
on [our docs
repo](https://github.com/MicrosoftDocs/windows-uwp/tree/docs/hub/powertoys)
and link it here: #xxx
## Detailed Description of the Pull Request / Additional comments
## Validation Steps Performed
---------
Signed-off-by: Shawn Yuan (from Dev Box)
Signed-off-by: Shuai Yuan
Co-authored-by: Niels Laute
---
.github/actions/spell-check/expect.txt | 7 +
PowerToys.slnx | 8 +
src/common/ManagedCommon/ModuleType.cs | 1 +
src/common/SettingsAPI/settings_objects.h | 10 +
src/runner/general_settings.cpp | 58 +-
src/runner/general_settings.h | 3 +
src/runner/main.cpp | 9 +-
src/runner/quick_access_host.cpp | 269 +++++++
src/runner/quick_access_host.h | 12 +
src/runner/runner.vcxproj | 2 +
src/runner/runner.vcxproj.filters | 6 +
src/runner/settings_window.cpp | 79 +-
src/runner/settings_window.h | 3 +-
src/runner/tray_icon.cpp | 61 +-
src/runner/tray_icon.h | 5 +-
.../QuickAccess.UI/Helpers/ModuleGpoHelper.cs | 49 ++
.../Helpers/ResourceLoaderInstance.cs | 12 +
.../PowerToys.QuickAccess.csproj | 89 +++
.../QuickAccessLaunchContext.cs | 62 ++
.../QuickAccess.UI/QuickAccessXAML/App.xaml | 60 ++
.../QuickAccessXAML/App.xaml.cs | 36 +
.../QuickAccessXAML/Flyout/AppsListPage.xaml | 83 ++
.../Flyout/AppsListPage.xaml.cs | 64 ++
.../Flyout/FlyoutNavigationContext.cs | 13 +
.../QuickAccessXAML}/Flyout/LaunchPage.xaml | 54 +-
.../QuickAccessXAML/Flyout/LaunchPage.xaml.cs | 80 ++
.../QuickAccessXAML}/Flyout/ShellPage.xaml | 4 +-
.../QuickAccessXAML/Flyout/ShellPage.xaml.cs | 68 ++
.../QuickAccessXAML/MainWindow.xaml} | 26 +-
.../QuickAccessXAML/MainWindow.xaml.cs | 732 ++++++++++++++++++
.../Services/IQuickAccessCoordinator.cs | 29 +
.../Services/QuickAccessCoordinator.cs | 201 +++++
.../Services/QuickAccessLauncher.cs | 37 +
.../ViewModels/AllAppsViewModel.cs | 172 ++++
.../ViewModels/FlyoutMenuItem.cs | 52 ++
.../ViewModels/LauncherViewModel.cs | 47 ++
src/settings-ui/QuickAccess.UI/app.manifest | 16 +
.../Converters/EnumToBooleanConverter.cs | 6 +-
.../ModuleListSortOptionToBooleanConverter.cs | 38 +
.../ModuleList/ModuleList.xaml | 107 +++
.../ModuleList/ModuleList.xaml.cs | 57 ++
.../ModuleList/ModuleListItem.cs | 119 +++
.../ModuleList/ModuleListSortOption.cs | 12 +
.../Primitives}/Card.xaml | 4 +-
.../Primitives}/Card.xaml.cs | 7 +-
.../Primitives/FlyoutMenuButton.cs} | 2 +-
.../QuickAccess/IQuickAccessLauncher.cs | 13 +
.../QuickAccess/QuickAccessItem.cs | 69 ++
.../QuickAccess/QuickAccessLauncher.cs | 121 +++
.../QuickAccess/QuickAccessList.xaml | 51 ++
.../QuickAccess/QuickAccessList.xaml.cs | 26 +
.../QuickAccess/QuickAccessViewModel.cs} | 136 ++--
.../Settings.UI.Controls.csproj | 32 +
.../Strings/en-us/Resources.resw | 138 ++++
.../Themes/Generic.xaml} | 7 +-
.../Settings.UI.Library/GeneralSettings.cs | 29 +-
.../Helpers/ModuleHelper.cs | 121 +++
.../Interfaces/ISettingsRepository`1.cs | 4 +
.../Settings.UI.Library/SettingsFactory.cs | 13 +-
.../SettingsRepository`1.cs | 72 +-
.../SettingsSerializationContext.cs | 1 +
.../BackCompatTestProperties.cs | 5 +
.../ViewModelTests/General.cs | 36 +-
.../EnumToModuleListSortOptionConverter.cs | 44 ++
.../Settings.UI/Helpers/ModuleGpoHelper.cs | 91 +++
.../Settings.UI/Helpers/ModuleHelper.cs | 196 -----
.../Settings.UI/PowerToys.Settings.csproj | 1 +
.../Settings.UI/SettingsXAML/App.xaml.cs | 45 +-
.../Dashboard/ShortcutConflictWindow.xaml.cs | 2 +-
.../SettingsXAML/Flyout/AppsListPage.xaml | 103 ---
.../SettingsXAML/Flyout/AppsListPage.xaml.cs | 37 -
.../SettingsXAML/Flyout/LaunchPage.xaml.cs | 197 -----
.../SettingsXAML/Flyout/ShellPage.xaml.cs | 30 -
.../SettingsXAML/FlyoutWindow.xaml.cs | 103 ---
.../SettingsXAML/MainWindow.xaml.cs | 46 +-
.../SettingsXAML/Themes/Generic.xaml | 1 -
.../SettingsXAML/Views/DashboardPage.xaml | 125 +--
.../SettingsXAML/Views/DashboardPage.xaml.cs | 5 -
.../SettingsXAML/Views/GeneralPage.xaml | 22 +
.../SettingsXAML/Views/GeneralPage.xaml.cs | 6 +-
.../SettingsXAML/Views/ShellPage.xaml.cs | 58 +-
.../Settings.UI/Strings/en-us/Resources.resw | 11 +-
.../ViewModels/DashboardListItem.cs | 40 +-
.../ViewModels/DashboardViewModel.cs | 64 +-
.../ViewModels/Flyout/AllAppsViewModel.cs | 78 --
.../ViewModels/Flyout/FlyoutMenuItem.cs | 64 --
.../ViewModels/Flyout/FlyoutViewModel.cs | 57 --
.../ViewModels/GeneralViewModel.cs | 108 ++-
.../ViewModels/ShortcutConflictViewModel.cs | 25 +-
89 files changed, 3916 insertions(+), 1388 deletions(-)
create mode 100644 src/runner/quick_access_host.cpp
create mode 100644 src/runner/quick_access_host.h
create mode 100644 src/settings-ui/QuickAccess.UI/Helpers/ModuleGpoHelper.cs
create mode 100644 src/settings-ui/QuickAccess.UI/Helpers/ResourceLoaderInstance.cs
create mode 100644 src/settings-ui/QuickAccess.UI/PowerToys.QuickAccess.csproj
create mode 100644 src/settings-ui/QuickAccess.UI/QuickAccessLaunchContext.cs
create mode 100644 src/settings-ui/QuickAccess.UI/QuickAccessXAML/App.xaml
create mode 100644 src/settings-ui/QuickAccess.UI/QuickAccessXAML/App.xaml.cs
create mode 100644 src/settings-ui/QuickAccess.UI/QuickAccessXAML/Flyout/AppsListPage.xaml
create mode 100644 src/settings-ui/QuickAccess.UI/QuickAccessXAML/Flyout/AppsListPage.xaml.cs
create mode 100644 src/settings-ui/QuickAccess.UI/QuickAccessXAML/Flyout/FlyoutNavigationContext.cs
rename src/settings-ui/{Settings.UI/SettingsXAML => QuickAccess.UI/QuickAccessXAML}/Flyout/LaunchPage.xaml (64%)
create mode 100644 src/settings-ui/QuickAccess.UI/QuickAccessXAML/Flyout/LaunchPage.xaml.cs
rename src/settings-ui/{Settings.UI/SettingsXAML => QuickAccess.UI/QuickAccessXAML}/Flyout/ShellPage.xaml (84%)
create mode 100644 src/settings-ui/QuickAccess.UI/QuickAccessXAML/Flyout/ShellPage.xaml.cs
rename src/settings-ui/{Settings.UI/SettingsXAML/FlyoutWindow.xaml => QuickAccess.UI/QuickAccessXAML/MainWindow.xaml} (62%)
create mode 100644 src/settings-ui/QuickAccess.UI/QuickAccessXAML/MainWindow.xaml.cs
create mode 100644 src/settings-ui/QuickAccess.UI/Services/IQuickAccessCoordinator.cs
create mode 100644 src/settings-ui/QuickAccess.UI/Services/QuickAccessCoordinator.cs
create mode 100644 src/settings-ui/QuickAccess.UI/Services/QuickAccessLauncher.cs
create mode 100644 src/settings-ui/QuickAccess.UI/ViewModels/AllAppsViewModel.cs
create mode 100644 src/settings-ui/QuickAccess.UI/ViewModels/FlyoutMenuItem.cs
create mode 100644 src/settings-ui/QuickAccess.UI/ViewModels/LauncherViewModel.cs
create mode 100644 src/settings-ui/QuickAccess.UI/app.manifest
rename src/settings-ui/{Settings.UI => Settings.UI.Controls}/Converters/EnumToBooleanConverter.cs (80%)
create mode 100644 src/settings-ui/Settings.UI.Controls/Converters/ModuleListSortOptionToBooleanConverter.cs
create mode 100644 src/settings-ui/Settings.UI.Controls/ModuleList/ModuleList.xaml
create mode 100644 src/settings-ui/Settings.UI.Controls/ModuleList/ModuleList.xaml.cs
create mode 100644 src/settings-ui/Settings.UI.Controls/ModuleList/ModuleListItem.cs
create mode 100644 src/settings-ui/Settings.UI.Controls/ModuleList/ModuleListSortOption.cs
rename src/settings-ui/{Settings.UI/SettingsXAML/Controls/Dashboard => Settings.UI.Controls/Primitives}/Card.xaml (97%)
rename src/settings-ui/{Settings.UI/SettingsXAML/Controls/Dashboard => Settings.UI.Controls/Primitives}/Card.xaml.cs (94%)
rename src/settings-ui/{Settings.UI/SettingsXAML/Controls/FlyoutMenuButton.xaml.cs => Settings.UI.Controls/Primitives/FlyoutMenuButton.cs} (96%)
create mode 100644 src/settings-ui/Settings.UI.Controls/QuickAccess/IQuickAccessLauncher.cs
create mode 100644 src/settings-ui/Settings.UI.Controls/QuickAccess/QuickAccessItem.cs
create mode 100644 src/settings-ui/Settings.UI.Controls/QuickAccess/QuickAccessLauncher.cs
create mode 100644 src/settings-ui/Settings.UI.Controls/QuickAccess/QuickAccessList.xaml
create mode 100644 src/settings-ui/Settings.UI.Controls/QuickAccess/QuickAccessList.xaml.cs
rename src/settings-ui/{Settings.UI/ViewModels/Flyout/LauncherViewModel.cs => Settings.UI.Controls/QuickAccess/QuickAccessViewModel.cs} (50%)
create mode 100644 src/settings-ui/Settings.UI.Controls/Settings.UI.Controls.csproj
create mode 100644 src/settings-ui/Settings.UI.Controls/Strings/en-us/Resources.resw
rename src/settings-ui/{Settings.UI/SettingsXAML/Controls/FlyoutMenuButton.xaml => Settings.UI.Controls/Themes/Generic.xaml} (95%)
create mode 100644 src/settings-ui/Settings.UI.Library/Helpers/ModuleHelper.cs
create mode 100644 src/settings-ui/Settings.UI/Converters/EnumToModuleListSortOptionConverter.cs
create mode 100644 src/settings-ui/Settings.UI/Helpers/ModuleGpoHelper.cs
delete mode 100644 src/settings-ui/Settings.UI/Helpers/ModuleHelper.cs
delete mode 100644 src/settings-ui/Settings.UI/SettingsXAML/Flyout/AppsListPage.xaml
delete mode 100644 src/settings-ui/Settings.UI/SettingsXAML/Flyout/AppsListPage.xaml.cs
delete mode 100644 src/settings-ui/Settings.UI/SettingsXAML/Flyout/LaunchPage.xaml.cs
delete mode 100644 src/settings-ui/Settings.UI/SettingsXAML/Flyout/ShellPage.xaml.cs
delete mode 100644 src/settings-ui/Settings.UI/SettingsXAML/FlyoutWindow.xaml.cs
delete mode 100644 src/settings-ui/Settings.UI/ViewModels/Flyout/AllAppsViewModel.cs
delete mode 100644 src/settings-ui/Settings.UI/ViewModels/Flyout/FlyoutMenuItem.cs
delete mode 100644 src/settings-ui/Settings.UI/ViewModels/Flyout/FlyoutViewModel.cs
diff --git a/.github/actions/spell-check/expect.txt b/.github/actions/spell-check/expect.txt
index 91df199e2c..94e32fc8ee 100644
--- a/.github/actions/spell-check/expect.txt
+++ b/.github/actions/spell-check/expect.txt
@@ -365,6 +365,7 @@ DEFAULTICON
defaultlib
DEFAULTONLY
DEFAULTTONEAREST
+Defaulttonearest
DEFAULTTONULL
DEFAULTTOPRIMARY
DEFERERASE
@@ -868,6 +869,7 @@ lastcodeanalysissucceeded
LASTEXITCODE
LAYOUTRTL
lbl
+Lbuttondown
LCh
lcid
LCIDTo
@@ -990,6 +992,7 @@ maxversiontested
mber
MBM
MBR
+Mbuttondown
MDICHILD
MDL
mdtext
@@ -1448,6 +1451,7 @@ RAWINPUTHEADER
RAWMODE
RAWPATH
rbhid
+Rbuttondown
rclsid
RCZOOMIT
remotedesktop
@@ -1753,6 +1757,7 @@ svgz
SVSI
SWFO
SWP
+Swp
SWPNOSIZE
SWPNOZORDER
SWRESTORE
@@ -1772,6 +1777,7 @@ syskeydown
SYSKEYUP
SYSLIB
SYSMENU
+Sysmenu
systemai
SYSTEMAPPS
SYSTEMMODAL
@@ -2097,6 +2103,7 @@ Wwanpp
xap
XAxis
XButton
+Xbuttondown
xclip
xcopy
XDeployment
diff --git a/PowerToys.slnx b/PowerToys.slnx
index ac006fdbf4..70786b58fb 100644
--- a/PowerToys.slnx
+++ b/PowerToys.slnx
@@ -1005,6 +1005,14 @@
+
+
+
+
+
+
+
+
diff --git a/src/common/ManagedCommon/ModuleType.cs b/src/common/ManagedCommon/ModuleType.cs
index d7ae386191..8461b4a6d8 100644
--- a/src/common/ManagedCommon/ModuleType.cs
+++ b/src/common/ManagedCommon/ModuleType.cs
@@ -36,5 +36,6 @@ namespace ManagedCommon
PowerOCR,
Workspaces,
ZoomIt,
+ GeneralSettings,
}
}
diff --git a/src/common/SettingsAPI/settings_objects.h b/src/common/SettingsAPI/settings_objects.h
index 84b064d5af..8927ba5657 100644
--- a/src/common/SettingsAPI/settings_objects.h
+++ b/src/common/SettingsAPI/settings_objects.h
@@ -119,6 +119,16 @@ namespace PowerToysSettings
class HotkeyObject
{
public:
+ HotkeyObject() :
+ m_json(json::JsonObject())
+ {
+ m_json.SetNamedValue(L"win", json::value(false));
+ m_json.SetNamedValue(L"ctrl", json::value(false));
+ m_json.SetNamedValue(L"alt", json::value(false));
+ m_json.SetNamedValue(L"shift", json::value(false));
+ m_json.SetNamedValue(L"code", json::value(0));
+ m_json.SetNamedValue(L"key", json::value(L""));
+ }
static HotkeyObject from_json(json::JsonObject json)
{
return HotkeyObject(std::move(json));
diff --git a/src/runner/general_settings.cpp b/src/runner/general_settings.cpp
index c6770731a6..2ae775a0fc 100644
--- a/src/runner/general_settings.cpp
+++ b/src/runner/general_settings.cpp
@@ -2,6 +2,7 @@
#include "general_settings.h"
#include "auto_start_helper.h"
#include "tray_icon.h"
+#include "quick_access_host.h"
#include "Generated files/resource.h"
#include "hotkey_conflict_detector.h"
@@ -72,6 +73,8 @@ static bool download_updates_automatically = true;
static bool show_whats_new_after_updates = true;
static bool enable_experimentation = true;
static bool enable_warnings_elevated_apps = true;
+static bool enable_quick_access = true;
+static PowerToysSettings::HotkeyObject quick_access_shortcut;
static DashboardSortOrder dashboard_sort_order = DashboardSortOrder::Alphabetical;
static json::JsonObject ignored_conflict_properties = create_default_ignored_conflict_properties();
@@ -105,6 +108,8 @@ json::JsonObject GeneralSettings::to_json()
result.SetNamedValue(L"dashboard_sort_order", json::value(static_cast(dashboardSortOrder)));
result.SetNamedValue(L"is_admin", json::value(isAdmin));
result.SetNamedValue(L"enable_warnings_elevated_apps", json::value(enableWarningsElevatedApps));
+ result.SetNamedValue(L"enable_quick_access", json::value(enableQuickAccess));
+ result.SetNamedValue(L"quick_access_shortcut", quickAccessShortcut.get_json());
result.SetNamedValue(L"theme", json::value(theme));
result.SetNamedValue(L"system_theme", json::value(systemTheme));
result.SetNamedValue(L"powertoys_version", json::value(powerToysVersion));
@@ -127,6 +132,11 @@ json::JsonObject load_general_settings()
show_whats_new_after_updates = loaded.GetNamedBoolean(L"show_whats_new_after_updates", true);
enable_experimentation = loaded.GetNamedBoolean(L"enable_experimentation", true);
enable_warnings_elevated_apps = loaded.GetNamedBoolean(L"enable_warnings_elevated_apps", true);
+ enable_quick_access = loaded.GetNamedBoolean(L"enable_quick_access", true);
+ if (json::has(loaded, L"quick_access_shortcut", json::JsonValueType::Object))
+ {
+ quick_access_shortcut = PowerToysSettings::HotkeyObject::from_json(loaded.GetNamedObject(L"quick_access_shortcut"));
+ }
dashboard_sort_order = parse_dashboard_sort_order(loaded, dashboard_sort_order);
if (json::has(loaded, L"ignored_conflict_properties", json::JsonValueType::Object))
@@ -153,6 +163,8 @@ GeneralSettings get_general_settings()
.isRunElevated = run_as_elevated,
.isAdmin = is_user_admin,
.enableWarningsElevatedApps = enable_warnings_elevated_apps,
+ .enableQuickAccess = enable_quick_access,
+ .quickAccessShortcut = quick_access_shortcut,
.showNewUpdatesToastNotification = show_new_updates_toast_notification,
.downloadUpdatesAutomatically = download_updates_automatically && is_user_admin,
.showWhatsNewAfterUpdates = show_whats_new_after_updates,
@@ -178,11 +190,47 @@ GeneralSettings get_general_settings()
void apply_general_settings(const json::JsonObject& general_configs, bool save)
{
+ std::wstring old_settings_json_string;
+ if (save)
+ {
+ old_settings_json_string = get_general_settings().to_json().Stringify().c_str();
+ }
+
Logger::info(L"apply_general_settings: {}", std::wstring{ general_configs.ToString() });
run_as_elevated = general_configs.GetNamedBoolean(L"run_elevated", false);
enable_warnings_elevated_apps = general_configs.GetNamedBoolean(L"enable_warnings_elevated_apps", true);
+ bool new_enable_quick_access = general_configs.GetNamedBoolean(L"enable_quick_access", true);
+ Logger::info(L"apply_general_settings: enable_quick_access={}, new_enable_quick_access={}", enable_quick_access, new_enable_quick_access);
+
+ PowerToysSettings::HotkeyObject new_quick_access_shortcut;
+ if (json::has(general_configs, L"quick_access_shortcut", json::JsonValueType::Object))
+ {
+ new_quick_access_shortcut = PowerToysSettings::HotkeyObject::from_json(general_configs.GetNamedObject(L"quick_access_shortcut"));
+ }
+
+ auto hotkey_equals = [](const PowerToysSettings::HotkeyObject& a, const PowerToysSettings::HotkeyObject& b) {
+ return a.get_code() == b.get_code() &&
+ a.get_modifiers() == b.get_modifiers();
+ };
+
+ if (enable_quick_access != new_enable_quick_access || !hotkey_equals(quick_access_shortcut, new_quick_access_shortcut))
+ {
+ enable_quick_access = new_enable_quick_access;
+ quick_access_shortcut = new_quick_access_shortcut;
+
+ if (enable_quick_access)
+ {
+ QuickAccessHost::start();
+ }
+ else
+ {
+ QuickAccessHost::stop();
+ }
+ update_quick_access_hotkey(enable_quick_access, quick_access_shortcut);
+ }
+
show_new_updates_toast_notification = general_configs.GetNamedBoolean(L"show_new_updates_toast_notification", true);
download_updates_automatically = general_configs.GetNamedBoolean(L"download_updates_automatically", true);
@@ -321,8 +369,12 @@ void apply_general_settings(const json::JsonObject& general_configs, bool save)
if (save)
{
GeneralSettings save_settings = get_general_settings();
- PTSettingsHelper::save_general_settings(save_settings.to_json());
- Trace::SettingsChanged(save_settings);
+ std::wstring new_settings_json_string = save_settings.to_json().Stringify().c_str();
+ if (old_settings_json_string != new_settings_json_string)
+ {
+ PTSettingsHelper::save_general_settings(save_settings.to_json());
+ Trace::SettingsChanged(save_settings);
+ }
}
}
@@ -412,3 +464,5 @@ void start_enabled_powertoys()
}
}
}
+
+
diff --git a/src/runner/general_settings.h b/src/runner/general_settings.h
index b4f7638846..033f75b087 100644
--- a/src/runner/general_settings.h
+++ b/src/runner/general_settings.h
@@ -1,6 +1,7 @@
#pragma once
#include
+#include
enum class DashboardSortOrder
{
@@ -18,6 +19,8 @@ struct GeneralSettings
bool isRunElevated;
bool isAdmin;
bool enableWarningsElevatedApps;
+ bool enableQuickAccess;
+ PowerToysSettings::HotkeyObject quickAccessShortcut;
bool showNewUpdatesToastNotification;
bool downloadUpdatesAutomatically;
bool showWhatsNewAfterUpdates;
diff --git a/src/runner/main.cpp b/src/runner/main.cpp
index 2ceba89161..d9da20d93c 100644
--- a/src/runner/main.cpp
+++ b/src/runner/main.cpp
@@ -37,6 +37,7 @@
#include
#include "centralized_kb_hook.h"
#include "centralized_hotkeys.h"
+#include "quick_access_host.h"
#include "ai_detection.h"
#include
@@ -189,6 +190,11 @@ int runner(bool isProcessElevated, bool openSettings, std::string settingsWindow
#endif
Trace::RegisterProvider();
start_tray_icon(isProcessElevated);
+ if (get_general_settings().enableQuickAccess)
+ {
+ QuickAccessHost::start();
+ }
+ update_quick_access_hotkey(get_general_settings().enableQuickAccess, get_general_settings().quickAccessShortcut);
set_tray_icon_visible(get_general_settings().showSystemTrayIcon);
CentralizedKeyboardHook::Start();
@@ -316,7 +322,7 @@ int runner(bool isProcessElevated, bool openSettings, std::string settingsWindow
{
window = winrt::to_hstring(settingsWindow);
}
- open_settings_window(window, false);
+ open_settings_window(window);
}
if (openOobe)
@@ -339,6 +345,7 @@ int runner(bool isProcessElevated, bool openSettings, std::string settingsWindow
result = -1;
}
Trace::UnregisterProvider();
+ QuickAccessHost::stop();
return result;
}
diff --git a/src/runner/quick_access_host.cpp b/src/runner/quick_access_host.cpp
new file mode 100644
index 0000000000..609b8f2f36
--- /dev/null
+++ b/src/runner/quick_access_host.cpp
@@ -0,0 +1,269 @@
+#include "pch.h"
+#include "quick_access_host.h"
+
+#include
+#include
+#include
+#include
+#include
+#include
+
+#include
+#include
+#include
+#include
+
+extern void receive_json_send_to_main_thread(const std::wstring& msg);
+
+namespace
+{
+ wil::unique_handle quick_access_process;
+ wil::unique_handle show_event;
+ wil::unique_handle exit_event;
+ std::wstring show_event_name;
+ std::wstring exit_event_name;
+ std::wstring runner_pipe_name;
+ std::wstring app_pipe_name;
+ std::unique_ptr quick_access_ipc;
+ std::mutex quick_access_mutex;
+
+ bool is_process_active_locked()
+ {
+ if (!quick_access_process)
+ {
+ return false;
+ }
+
+ DWORD exit_code = 0;
+ if (!GetExitCodeProcess(quick_access_process.get(), &exit_code))
+ {
+ Logger::warn(L"QuickAccessHost: failed to read Quick Access process exit code. error={}.", GetLastError());
+ return false;
+ }
+
+ return exit_code == STILL_ACTIVE;
+ }
+
+ void reset_state_locked()
+ {
+ if (quick_access_ipc)
+ {
+ quick_access_ipc->end();
+ quick_access_ipc.reset();
+ }
+
+ quick_access_process.reset();
+ show_event.reset();
+ exit_event.reset();
+ show_event_name.clear();
+ exit_event_name.clear();
+ runner_pipe_name.clear();
+ app_pipe_name.clear();
+ }
+
+ std::wstring build_event_name(const wchar_t* suffix)
+ {
+ std::wstring name = L"Local\\PowerToysQuickAccess_";
+ name += std::to_wstring(GetCurrentProcessId());
+ if (suffix)
+ {
+ name += suffix;
+ }
+ return name;
+ }
+
+ std::wstring build_command_line(const std::wstring& exe_path)
+ {
+ std::wstring command_line = L"\"";
+ command_line += exe_path;
+ command_line += L"\" --show-event=\"";
+ command_line += show_event_name;
+ command_line += L"\" --exit-event=\"";
+ command_line += exit_event_name;
+ command_line += L"\"";
+ if (!runner_pipe_name.empty())
+ {
+ command_line.append(L" --runner-pipe=\"");
+ command_line += runner_pipe_name;
+ command_line += L"\"";
+ }
+ if (!app_pipe_name.empty())
+ {
+ command_line.append(L" --app-pipe=\"");
+ command_line += app_pipe_name;
+ command_line += L"\"";
+ }
+ return command_line;
+ }
+}
+
+namespace QuickAccessHost
+{
+ bool is_running()
+ {
+ std::scoped_lock lock(quick_access_mutex);
+ return is_process_active_locked();
+ }
+
+ void start()
+ {
+ Logger::info(L"QuickAccessHost::start() called");
+ std::scoped_lock lock(quick_access_mutex);
+ if (is_process_active_locked())
+ {
+ Logger::info(L"QuickAccessHost::start: process already active");
+ return;
+ }
+
+ reset_state_locked();
+
+ show_event_name = build_event_name(L"_Show");
+ exit_event_name = build_event_name(L"_Exit");
+
+ show_event.reset(CreateEventW(nullptr, FALSE, FALSE, show_event_name.c_str()));
+ if (!show_event)
+ {
+ Logger::error(L"QuickAccessHost: failed to create show event. error={}.", GetLastError());
+ reset_state_locked();
+ return;
+ }
+
+ exit_event.reset(CreateEventW(nullptr, FALSE, FALSE, exit_event_name.c_str()));
+ if (!exit_event)
+ {
+ Logger::error(L"QuickAccessHost: failed to create exit event. error={}.", GetLastError());
+ reset_state_locked();
+ return;
+ }
+
+ runner_pipe_name = L"\\\\.\\pipe\\powertoys_quick_access_runner_";
+ app_pipe_name = L"\\\\.\\pipe\\powertoys_quick_access_ui_";
+ UUID temp_uuid;
+ wchar_t* uuid_chars = nullptr;
+ if (UuidCreate(&temp_uuid) == RPC_S_UUID_NO_ADDRESS)
+ {
+ Logger::warn(L"QuickAccessHost: failed to create UUID for pipe names. error={}.", GetLastError());
+ }
+ else if (UuidToString(&temp_uuid, reinterpret_cast(&uuid_chars)) != RPC_S_OK)
+ {
+ Logger::warn(L"QuickAccessHost: failed to convert UUID to string. error={}.", GetLastError());
+ }
+
+ if (uuid_chars != nullptr)
+ {
+ runner_pipe_name += std::wstring(uuid_chars);
+ app_pipe_name += std::wstring(uuid_chars);
+ RpcStringFree(reinterpret_cast(&uuid_chars));
+ uuid_chars = nullptr;
+ }
+ else
+ {
+ const std::wstring fallback_suffix = std::to_wstring(GetTickCount64());
+ runner_pipe_name += fallback_suffix;
+ app_pipe_name += fallback_suffix;
+ }
+
+ HANDLE token_handle = nullptr;
+ if (!OpenProcessToken(GetCurrentProcess(), TOKEN_QUERY, &token_handle))
+ {
+ Logger::error(L"QuickAccessHost: failed to open process token. error={}.", GetLastError());
+ reset_state_locked();
+ return;
+ }
+
+ wil::unique_handle token(token_handle);
+ quick_access_ipc.reset(new (std::nothrow) TwoWayPipeMessageIPC(runner_pipe_name, app_pipe_name, receive_json_send_to_main_thread));
+ if (!quick_access_ipc)
+ {
+ Logger::error(L"QuickAccessHost: failed to allocate IPC instance.");
+ reset_state_locked();
+ return;
+ }
+
+ try
+ {
+ quick_access_ipc->start(token.get());
+ }
+ catch (...)
+ {
+ Logger::error(L"QuickAccessHost: failed to start IPC server for Quick Access.");
+ reset_state_locked();
+ return;
+ }
+
+ const std::wstring exe_path = get_module_folderpath() + L"\\WinUI3Apps\\PowerToys.QuickAccess.exe";
+ if (GetFileAttributesW(exe_path.c_str()) == INVALID_FILE_ATTRIBUTES)
+ {
+ Logger::warn(L"QuickAccessHost: missing Quick Access executable at {}", exe_path);
+ reset_state_locked();
+ return;
+ }
+
+ const std::wstring command_line = build_command_line(exe_path);
+ std::vector command_line_buffer(command_line.begin(), command_line.end());
+ command_line_buffer.push_back(L'\0');
+ STARTUPINFOW startup_info{};
+ startup_info.cb = sizeof(startup_info);
+ PROCESS_INFORMATION process_info{};
+
+ BOOL created = CreateProcessW(exe_path.c_str(), command_line_buffer.data(), nullptr, nullptr, FALSE, 0, nullptr, nullptr, &startup_info, &process_info);
+ if (!created)
+ {
+ Logger::error(L"QuickAccessHost: failed to launch Quick Access host. error={}.", GetLastError());
+ reset_state_locked();
+ return;
+ }
+
+ quick_access_process.reset(process_info.hProcess);
+ CloseHandle(process_info.hThread);
+ }
+
+ void show()
+ {
+ start();
+ std::scoped_lock lock(quick_access_mutex);
+
+ if (show_event)
+ {
+ if (!SetEvent(show_event.get()))
+ {
+ Logger::warn(L"QuickAccessHost: failed to signal show event. error={}.", GetLastError());
+ }
+ }
+ }
+
+ void stop()
+ {
+ Logger::info(L"QuickAccessHost::stop() called");
+ std::unique_lock lock(quick_access_mutex);
+ if (exit_event)
+ {
+ SetEvent(exit_event.get());
+ }
+
+ if (quick_access_process)
+ {
+ const DWORD wait_result = WaitForSingleObject(quick_access_process.get(), 2000);
+ Logger::info(L"QuickAccessHost::stop: WaitForSingleObject result={}", wait_result);
+ if (wait_result == WAIT_TIMEOUT)
+ {
+ Logger::warn(L"QuickAccessHost: Quick Access process did not exit in time, terminating.");
+ if (!TerminateProcess(quick_access_process.get(), 0))
+ {
+ Logger::error(L"QuickAccessHost: failed to terminate Quick Access process. error={}.", GetLastError());
+ }
+ else
+ {
+ Logger::info(L"QuickAccessHost: TerminateProcess succeeded.");
+ WaitForSingleObject(quick_access_process.get(), 5000);
+ }
+ }
+ else if (wait_result == WAIT_FAILED)
+ {
+ Logger::error(L"QuickAccessHost: failed while waiting for Quick Access process. error={}.", GetLastError());
+ }
+ }
+
+ reset_state_locked();
+ }
+}
diff --git a/src/runner/quick_access_host.h b/src/runner/quick_access_host.h
new file mode 100644
index 0000000000..22a65a9c26
--- /dev/null
+++ b/src/runner/quick_access_host.h
@@ -0,0 +1,12 @@
+#pragma once
+
+#include
+#include
+
+namespace QuickAccessHost
+{
+ void start();
+ void show();
+ void stop();
+ bool is_running();
+}
diff --git a/src/runner/runner.vcxproj b/src/runner/runner.vcxproj
index 57cb55b6bd..1bfd036290 100644
--- a/src/runner/runner.vcxproj
+++ b/src/runner/runner.vcxproj
@@ -70,6 +70,7 @@
+
@@ -85,6 +86,7 @@
+
diff --git a/src/runner/runner.vcxproj.filters b/src/runner/runner.vcxproj.filters
index 904e213405..ac5fe3ec36 100644
--- a/src/runner/runner.vcxproj.filters
+++ b/src/runner/runner.vcxproj.filters
@@ -48,6 +48,9 @@
Utils
+
+ Utils
+
@@ -102,6 +105,9 @@
Utils
+
+ Utils
+
diff --git a/src/runner/settings_window.cpp b/src/runner/settings_window.cpp
index e31b934d40..a1ec3f81b9 100644
--- a/src/runner/settings_window.cpp
+++ b/src/runner/settings_window.cpp
@@ -198,6 +198,8 @@ void dispatch_received_json(const std::wstring& json_to_parse)
return;
}
+ Logger::info(L"dispatch_received_json: {}", json_to_parse);
+
for (const auto& base_element : j)
{
const auto name = base_element.Key();
@@ -206,12 +208,12 @@ void dispatch_received_json(const std::wstring& json_to_parse)
if (name == L"general")
{
apply_general_settings(value.GetObjectW());
- const std::wstring settings_string{ get_all_settings().Stringify().c_str() };
- {
- std::unique_lock lock{ ipc_mutex };
- if (current_settings_ipc)
- current_settings_ipc->send(settings_string);
- }
+ // const std::wstring settings_string{ get_all_settings().Stringify().c_str() };
+ // {
+ // std::unique_lock lock{ ipc_mutex };
+ // if (current_settings_ipc)
+ // current_settings_ipc->send(settings_string);
+ // }
}
else if (name == L"powertoys")
{
@@ -421,7 +423,7 @@ BOOL run_settings_non_elevated(LPCWSTR executable_path, LPWSTR executable_args,
DWORD g_settings_process_id = 0;
-void run_settings_window(bool show_oobe_window, bool show_scoobe_window, std::optional settings_window, bool show_flyout = false, const std::optional& flyout_position = std::nullopt)
+void run_settings_window(bool show_oobe_window, bool show_scoobe_window, std::optional settings_window)
{
g_isLaunchInProgress = true;
@@ -491,22 +493,16 @@ void run_settings_window(bool show_oobe_window, bool show_scoobe_window, std::op
// Arg 9: should scoobe window be shown
std::wstring settings_showScoobe = show_scoobe_window ? L"true" : L"false";
- // Arg 10: should flyout be shown
- std::wstring settings_showFlyout = show_flyout ? L"true" : L"false";
-
- // Arg 11: contains if there's a settings window argument. If true, will add one extra argument with the value to the call.
+ // Arg 10: contains if there's a settings window argument. If true, will add one extra argument with the value to the call.
std::wstring settings_containsSettingsWindow = settings_window.has_value() ? L"true" : L"false";
- // Arg 12: contains if there's flyout coordinates. If true, will add two extra arguments to the call containing the x and y coordinates.
- std::wstring settings_containsFlyoutPosition = flyout_position.has_value() ? L"true" : L"false";
-
- // Args 13, .... : Optional arguments depending on the options presented before. All by the same value.
+ // Args 11, .... : Optional arguments depending on the options presented before. All by the same value.
// create general settings file to initialize the settings file with installation configurations like :
// 1. Run on start up.
PTSettingsHelper::save_general_settings(save_settings.to_json());
- std::wstring executable_args = fmt::format(L"\"{}\" {} {} {} {} {} {} {} {} {} {} {}",
+ std::wstring executable_args = fmt::format(L"\"{}\" {} {} {} {} {} {} {} {} {}",
executable_path,
powertoys_pipe_name,
settings_pipe_name,
@@ -516,9 +512,7 @@ void run_settings_window(bool show_oobe_window, bool show_scoobe_window, std::op
settings_isUserAnAdmin,
settings_showOobe,
settings_showScoobe,
- settings_showFlyout,
- settings_containsSettingsWindow,
- settings_containsFlyoutPosition);
+ settings_containsSettingsWindow);
if (settings_window.has_value())
{
@@ -526,14 +520,6 @@ void run_settings_window(bool show_oobe_window, bool show_scoobe_window, std::op
executable_args.append(settings_window.value());
}
- if (flyout_position)
- {
- executable_args.append(L" ");
- executable_args.append(std::to_wstring(flyout_position.value().x));
- executable_args.append(L" ");
- executable_args.append(std::to_wstring(flyout_position.value().y));
- }
-
BOOL process_created = false;
// Commented out to fix #22659
@@ -684,39 +670,22 @@ void bring_settings_to_front()
EnumWindows(callback, 0);
}
-void open_settings_window(std::optional settings_window, bool show_flyout = false, const std::optional& flyout_position)
+void open_settings_window(std::optional settings_window)
{
if (g_settings_process_id != 0)
{
- if (show_flyout)
+ // nl instead of showing the window, send message to it (flyout might need to be hidden, main setting window activated)
+ // bring_settings_to_front();
+ if (current_settings_ipc)
{
- if (current_settings_ipc)
+ if (settings_window.has_value())
{
- if (!flyout_position.has_value())
- {
- current_settings_ipc->send(L"{\"ShowYourself\":\"flyout\"}");
- }
- else
- {
- current_settings_ipc->send(fmt::format(L"{{\"ShowYourself\":\"flyout\", \"x_position\":{}, \"y_position\":{} }}", std::to_wstring(flyout_position.value().x), std::to_wstring(flyout_position.value().y)));
- }
+ std::wstring msg = L"{\"ShowYourself\":\"" + settings_window.value() + L"\"}";
+ current_settings_ipc->send(msg);
}
- }
- else
- {
- // nl instead of showing the window, send message to it (flyout might need to be hidden, main setting window activated)
- // bring_settings_to_front();
- if (current_settings_ipc)
+ else
{
- if (settings_window.has_value())
- {
- std::wstring msg = L"{\"ShowYourself\":\"" + settings_window.value() + L"\"}";
- current_settings_ipc->send(msg);
- }
- else
- {
- current_settings_ipc->send(L"{\"ShowYourself\":\"Dashboard\"}");
- }
+ current_settings_ipc->send(L"{\"ShowYourself\":\"Dashboard\"}");
}
}
}
@@ -724,8 +693,8 @@ void open_settings_window(std::optional settings_window, bool show
{
if (!g_isLaunchInProgress)
{
- std::thread([settings_window, show_flyout, flyout_position]() {
- run_settings_window(false, false, settings_window, show_flyout, flyout_position);
+ std::thread([settings_window]() {
+ run_settings_window(false, false, settings_window);
}).detach();
}
}
diff --git a/src/runner/settings_window.h b/src/runner/settings_window.h
index e15108059f..507d1c65b4 100644
--- a/src/runner/settings_window.h
+++ b/src/runner/settings_window.h
@@ -41,9 +41,8 @@ enum class ESettingsWindowNames
std::string ESettingsWindowNames_to_string(ESettingsWindowNames value);
ESettingsWindowNames ESettingsWindowNames_from_string(std::string value);
-void open_settings_window(std::optional settings_window, bool show_flyout, const std::optional& flyout_position);
+void open_settings_window(std::optional settings_window);
void close_settings_window();
void open_oobe_window();
void open_scoobe_window();
-void open_flyout();
diff --git a/src/runner/tray_icon.cpp b/src/runner/tray_icon.cpp
index 749c921659..92b723c9cb 100644
--- a/src/runner/tray_icon.cpp
+++ b/src/runner/tray_icon.cpp
@@ -5,6 +5,8 @@
#include "general_settings.h"
#include "centralized_hotkeys.h"
#include "centralized_kb_hook.h"
+#include "quick_access_host.h"
+#include "hotkey_conflict_detector.h"
#include
#include
@@ -69,9 +71,9 @@ void change_menu_item_text(const UINT item_id, wchar_t* new_text)
SetMenuItemInfoW(h_menu, item_id, false, &menuitem);
}
-void open_quick_access_flyout_window(const POINT flyout_position)
+void open_quick_access_flyout_window()
{
- open_settings_window(std::nullopt, true, flyout_position);
+ QuickAccessHost::show();
}
void handle_tray_command(HWND window, const WPARAM command_id, LPARAM lparam)
@@ -81,7 +83,7 @@ void handle_tray_command(HWND window, const WPARAM command_id, LPARAM lparam)
case ID_SETTINGS_MENU_COMMAND:
{
std::wstring settings_window{ winrt::to_hstring(ESettingsWindowNames_to_string(static_cast(lparam))) };
- open_settings_window(settings_window, false);
+ open_settings_window(settings_window);
}
break;
case ID_CLOSE_MENU_COMMAND:
@@ -113,9 +115,7 @@ void handle_tray_command(HWND window, const WPARAM command_id, LPARAM lparam)
}
case ID_QUICK_ACCESS_MENU_COMMAND:
{
- POINT mouse_pointer;
- GetCursorPos(&mouse_pointer);
- open_quick_access_flyout_window(mouse_pointer);
+ open_quick_access_flyout_window();
break;
}
}
@@ -126,7 +126,14 @@ void click_timer_elapsed()
double_click_timer_running = false;
if (!double_clicked)
{
- open_quick_access_flyout_window(tray_icon_click_point);
+ if (get_general_settings().enableQuickAccess)
+ {
+ open_quick_access_flyout_window();
+ }
+ else
+ {
+ open_settings_window(std::nullopt);
+ }
}
}
@@ -218,9 +225,6 @@ LRESULT __stdcall tray_icon_window_proc(HWND window, UINT message, WPARAM wparam
// ignore event if this is the second click of a double click
if (!double_click_timer_running)
{
- // save the cursor position for sending where to show the popup.
- GetCursorPos(&tray_icon_click_point);
-
// start timer for detecting single or double click
double_click_timer_running = true;
double_clicked = false;
@@ -236,7 +240,7 @@ LRESULT __stdcall tray_icon_window_proc(HWND window, UINT message, WPARAM wparam
case WM_LBUTTONDBLCLK:
{
double_clicked = true;
- open_settings_window(std::nullopt, false);
+ open_settings_window(std::nullopt);
break;
}
break;
@@ -349,4 +353,37 @@ void stop_tray_icon()
BugReportManager::instance().clear_callbacks();
SendMessage(tray_icon_hwnd, WM_CLOSE, 0, 0);
}
-}
\ No newline at end of file
+}
+void update_quick_access_hotkey(bool enabled, PowerToysSettings::HotkeyObject hotkey)
+{
+ static PowerToysSettings::HotkeyObject current_hotkey;
+ static bool is_registered = false;
+ auto& hkmng = HotkeyConflictDetector::HotkeyConflictManager::GetInstance();
+
+ if (is_registered)
+ {
+ CentralizedKeyboardHook::ClearModuleHotkeys(L"QuickAccess");
+ hkmng.RemoveHotkeyByModule(L"GeneralSettings");
+ is_registered = false;
+ }
+
+ if (enabled && hotkey.get_code() != 0)
+ {
+ HotkeyConflictDetector::Hotkey hk = {
+ hotkey.win_pressed(),
+ hotkey.ctrl_pressed(),
+ hotkey.shift_pressed(),
+ hotkey.alt_pressed(),
+ static_cast(hotkey.get_code())
+ };
+
+ hkmng.AddHotkey(hk, L"GeneralSettings", 0, true);
+ CentralizedKeyboardHook::SetHotkeyAction(L"QuickAccess", hk, []() {
+ open_quick_access_flyout_window();
+ return true;
+ });
+
+ current_hotkey = hotkey;
+ is_registered = true;
+ }
+}
diff --git a/src/runner/tray_icon.h b/src/runner/tray_icon.h
index 4fa7ebfe5a..e94b7630f4 100644
--- a/src/runner/tray_icon.h
+++ b/src/runner/tray_icon.h
@@ -1,6 +1,7 @@
#pragma once
#include
#include
+#include
// Start the Tray Icon
void start_tray_icon(bool isProcessElevated);
@@ -9,7 +10,9 @@ void set_tray_icon_visible(bool shouldIconBeVisible);
// Stop the Tray Icon
void stop_tray_icon();
// Open the Settings Window
-void open_settings_window(std::optional settings_window, bool show_flyout, const std::optional& flyout_position = std::nullopt);
+void open_settings_window(std::optional settings_window);
+// Update Quick Access Hotkey
+void update_quick_access_hotkey(bool enabled, PowerToysSettings::HotkeyObject hotkey);
// Callback type to be called by the tray icon loop
typedef void (*main_loop_callback_function)(PVOID);
// Calls a callback in _callback
diff --git a/src/settings-ui/QuickAccess.UI/Helpers/ModuleGpoHelper.cs b/src/settings-ui/QuickAccess.UI/Helpers/ModuleGpoHelper.cs
new file mode 100644
index 0000000000..25f32e191b
--- /dev/null
+++ b/src/settings-ui/QuickAccess.UI/Helpers/ModuleGpoHelper.cs
@@ -0,0 +1,49 @@
+// Copyright (c) Microsoft Corporation
+// The Microsoft Corporation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+using global::PowerToys.GPOWrapper;
+using ManagedCommon;
+using Microsoft.PowerToys.Settings.UI.Library;
+
+namespace Microsoft.PowerToys.QuickAccess.Helpers;
+
+internal static class ModuleGpoHelper
+{
+ public static GpoRuleConfigured GetModuleGpoConfiguration(ModuleType moduleType)
+ {
+ return moduleType switch
+ {
+ ModuleType.AdvancedPaste => GPOWrapper.GetConfiguredAdvancedPasteEnabledValue(),
+ ModuleType.AlwaysOnTop => GPOWrapper.GetConfiguredAlwaysOnTopEnabledValue(),
+ ModuleType.Awake => GPOWrapper.GetConfiguredAwakeEnabledValue(),
+ ModuleType.CmdPal => GPOWrapper.GetConfiguredCmdPalEnabledValue(),
+ ModuleType.ColorPicker => GPOWrapper.GetConfiguredColorPickerEnabledValue(),
+ ModuleType.CropAndLock => GPOWrapper.GetConfiguredCropAndLockEnabledValue(),
+ ModuleType.CursorWrap => GPOWrapper.GetConfiguredCursorWrapEnabledValue(),
+ ModuleType.EnvironmentVariables => GPOWrapper.GetConfiguredEnvironmentVariablesEnabledValue(),
+ ModuleType.FancyZones => GPOWrapper.GetConfiguredFancyZonesEnabledValue(),
+ ModuleType.FileLocksmith => GPOWrapper.GetConfiguredFileLocksmithEnabledValue(),
+ ModuleType.FindMyMouse => GPOWrapper.GetConfiguredFindMyMouseEnabledValue(),
+ ModuleType.Hosts => GPOWrapper.GetConfiguredHostsFileEditorEnabledValue(),
+ ModuleType.ImageResizer => GPOWrapper.GetConfiguredImageResizerEnabledValue(),
+ ModuleType.KeyboardManager => GPOWrapper.GetConfiguredKeyboardManagerEnabledValue(),
+ ModuleType.MouseHighlighter => GPOWrapper.GetConfiguredMouseHighlighterEnabledValue(),
+ ModuleType.MouseJump => GPOWrapper.GetConfiguredMouseJumpEnabledValue(),
+ ModuleType.MousePointerCrosshairs => GPOWrapper.GetConfiguredMousePointerCrosshairsEnabledValue(),
+ ModuleType.MouseWithoutBorders => GPOWrapper.GetConfiguredMouseWithoutBordersEnabledValue(),
+ ModuleType.NewPlus => GPOWrapper.GetConfiguredNewPlusEnabledValue(),
+ ModuleType.Peek => GPOWrapper.GetConfiguredPeekEnabledValue(),
+ ModuleType.PowerRename => GPOWrapper.GetConfiguredPowerRenameEnabledValue(),
+ ModuleType.PowerLauncher => GPOWrapper.GetConfiguredPowerLauncherEnabledValue(),
+ ModuleType.PowerAccent => GPOWrapper.GetConfiguredQuickAccentEnabledValue(),
+ ModuleType.Workspaces => GPOWrapper.GetConfiguredWorkspacesEnabledValue(),
+ ModuleType.RegistryPreview => GPOWrapper.GetConfiguredRegistryPreviewEnabledValue(),
+ ModuleType.MeasureTool => GPOWrapper.GetConfiguredScreenRulerEnabledValue(),
+ ModuleType.ShortcutGuide => GPOWrapper.GetConfiguredShortcutGuideEnabledValue(),
+ ModuleType.PowerOCR => GPOWrapper.GetConfiguredTextExtractorEnabledValue(),
+ ModuleType.ZoomIt => GPOWrapper.GetConfiguredZoomItEnabledValue(),
+ _ => GpoRuleConfigured.Unavailable,
+ };
+ }
+}
diff --git a/src/settings-ui/QuickAccess.UI/Helpers/ResourceLoaderInstance.cs b/src/settings-ui/QuickAccess.UI/Helpers/ResourceLoaderInstance.cs
new file mode 100644
index 0000000000..b57d73015b
--- /dev/null
+++ b/src/settings-ui/QuickAccess.UI/Helpers/ResourceLoaderInstance.cs
@@ -0,0 +1,12 @@
+// Copyright (c) Microsoft Corporation
+// The Microsoft Corporation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+using Microsoft.Windows.ApplicationModel.Resources;
+
+namespace Microsoft.PowerToys.QuickAccess.Helpers;
+
+internal static class ResourceLoaderInstance
+{
+ internal static ResourceLoader ResourceLoader { get; } = new("PowerToys.QuickAccess.pri");
+}
diff --git a/src/settings-ui/QuickAccess.UI/PowerToys.QuickAccess.csproj b/src/settings-ui/QuickAccess.UI/PowerToys.QuickAccess.csproj
new file mode 100644
index 0000000000..0c20d4d234
--- /dev/null
+++ b/src/settings-ui/QuickAccess.UI/PowerToys.QuickAccess.csproj
@@ -0,0 +1,89 @@
+
+
+
+
+
+ WinExe
+ net9.0-windows10.0.26100.0
+ Microsoft.PowerToys.QuickAccess
+ PowerToys.QuickAccess
+ true
+ None
+ true
+ true
+ false
+ false
+ app.manifest
+ ..\..\..\$(Platform)\$(Configuration)\WinUI3Apps
+ false
+ false
+ enable
+ PowerToys.QuickAccess.pri
+
+
+
+ PowerToys.GPOWrapper
+ $(OutDir)
+
+
+
+
+
+
+
+
+
+
+
+
+ Resources\Styles\Button.xaml
+
+
+ Resources\Styles\TextBlock.xaml
+
+
+ Resources\Themes\Colors.xaml
+
+
+ Resources\Themes\Generic.xaml
+
+
+
+
+
+ Strings\en-us\Resources.resw
+
+
+
+
+
+ Assets\Settings\Icons\%(RecursiveDir)%(Filename)%(Extension)
+ PreserveNewest
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/settings-ui/QuickAccess.UI/QuickAccessLaunchContext.cs b/src/settings-ui/QuickAccess.UI/QuickAccessLaunchContext.cs
new file mode 100644
index 0000000000..2b01947728
--- /dev/null
+++ b/src/settings-ui/QuickAccess.UI/QuickAccessLaunchContext.cs
@@ -0,0 +1,62 @@
+// Copyright (c) Microsoft Corporation
+// The Microsoft Corporation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+using System;
+using System.Diagnostics.CodeAnalysis;
+
+namespace Microsoft.PowerToys.QuickAccess;
+
+public sealed record QuickAccessLaunchContext(string? ShowEventName, string? ExitEventName, string? RunnerPipeName, string? AppPipeName)
+{
+ public static QuickAccessLaunchContext Parse(string[] args)
+ {
+ string? showEvent = null;
+ string? exitEvent = null;
+ string? runnerPipe = null;
+ string? appPipe = null;
+
+ foreach (var arg in args)
+ {
+ if (TryReadValue(arg, "--show-event", out var value))
+ {
+ showEvent = value;
+ }
+ else if (TryReadValue(arg, "--exit-event", out value))
+ {
+ exitEvent = value;
+ }
+ else if (TryReadValue(arg, "--runner-pipe", out value))
+ {
+ runnerPipe = value;
+ }
+ else if (TryReadValue(arg, "--app-pipe", out value))
+ {
+ appPipe = value;
+ }
+ }
+
+ return new QuickAccessLaunchContext(showEvent, exitEvent, runnerPipe, appPipe);
+ }
+
+ private static bool TryReadValue(string candidate, string key, [NotNullWhen(true)] out string? value)
+ {
+ if (candidate.StartsWith(key, StringComparison.OrdinalIgnoreCase))
+ {
+ if (candidate.Length == key.Length)
+ {
+ value = null;
+ return false;
+ }
+
+ if (candidate[key.Length] == '=')
+ {
+ value = candidate[(key.Length + 1)..].Trim('"');
+ return true;
+ }
+ }
+
+ value = null;
+ return false;
+ }
+}
diff --git a/src/settings-ui/QuickAccess.UI/QuickAccessXAML/App.xaml b/src/settings-ui/QuickAccess.UI/QuickAccessXAML/App.xaml
new file mode 100644
index 0000000000..ab7b3c250a
--- /dev/null
+++ b/src/settings-ui/QuickAccess.UI/QuickAccessXAML/App.xaml
@@ -0,0 +1,60 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/settings-ui/QuickAccess.UI/QuickAccessXAML/App.xaml.cs b/src/settings-ui/QuickAccess.UI/QuickAccessXAML/App.xaml.cs
new file mode 100644
index 0000000000..b21fb04b24
--- /dev/null
+++ b/src/settings-ui/QuickAccess.UI/QuickAccessXAML/App.xaml.cs
@@ -0,0 +1,36 @@
+// Copyright (c) Microsoft Corporation
+// The Microsoft Corporation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+using System;
+using Microsoft.UI.Xaml;
+
+namespace Microsoft.PowerToys.QuickAccess;
+
+public partial class App : Application
+{
+ private static MainWindow? _window;
+
+ public App()
+ {
+ InitializeComponent();
+ }
+
+ protected override void OnLaunched(LaunchActivatedEventArgs args)
+ {
+ var launchContext = QuickAccessLaunchContext.Parse(Environment.GetCommandLineArgs());
+ _window = new MainWindow(launchContext);
+ _window.Closed += OnWindowClosed;
+ _window.Activate();
+ }
+
+ private static void OnWindowClosed(object sender, WindowEventArgs args)
+ {
+ if (sender is MainWindow window)
+ {
+ window.Closed -= OnWindowClosed;
+ }
+
+ _window = null;
+ }
+}
diff --git a/src/settings-ui/QuickAccess.UI/QuickAccessXAML/Flyout/AppsListPage.xaml b/src/settings-ui/QuickAccess.UI/QuickAccessXAML/Flyout/AppsListPage.xaml
new file mode 100644
index 0000000000..c4d010560d
--- /dev/null
+++ b/src/settings-ui/QuickAccess.UI/QuickAccessXAML/Flyout/AppsListPage.xaml
@@ -0,0 +1,83 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/settings-ui/QuickAccess.UI/QuickAccessXAML/Flyout/AppsListPage.xaml.cs b/src/settings-ui/QuickAccess.UI/QuickAccessXAML/Flyout/AppsListPage.xaml.cs
new file mode 100644
index 0000000000..1212855fe4
--- /dev/null
+++ b/src/settings-ui/QuickAccess.UI/QuickAccessXAML/Flyout/AppsListPage.xaml.cs
@@ -0,0 +1,64 @@
+// Copyright (c) Microsoft Corporation
+// The Microsoft Corporation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+using System;
+using Microsoft.PowerToys.QuickAccess.ViewModels;
+using Microsoft.PowerToys.Settings.UI.Library;
+using Microsoft.UI.Xaml;
+using Microsoft.UI.Xaml.Controls;
+using Microsoft.UI.Xaml.Media.Animation;
+using Microsoft.UI.Xaml.Navigation;
+
+namespace Microsoft.PowerToys.QuickAccess.Flyout;
+
+public sealed partial class AppsListPage : Page
+{
+ private FlyoutNavigationContext? _context;
+
+ public AppsListPage()
+ {
+ InitializeComponent();
+ }
+
+ public AllAppsViewModel ViewModel { get; private set; } = default!;
+
+ protected override void OnNavigatedTo(NavigationEventArgs e)
+ {
+ base.OnNavigatedTo(e);
+
+ if (e.Parameter is FlyoutNavigationContext context)
+ {
+ _context = context;
+ ViewModel = context.AllAppsViewModel;
+ DataContext = ViewModel;
+ ViewModel.RefreshSettings();
+ }
+ }
+
+ private void BackButton_Click(object sender, RoutedEventArgs e)
+ {
+ if (_context == null || Frame == null)
+ {
+ return;
+ }
+
+ Frame.Navigate(typeof(LaunchPage), _context, new SlideNavigationTransitionInfo { Effect = SlideNavigationTransitionEffect.FromLeft });
+ }
+
+ private void SortAlphabetical_Click(object sender, RoutedEventArgs e)
+ {
+ if (ViewModel != null)
+ {
+ ViewModel.DashboardSortOrder = DashboardSortOrder.Alphabetical;
+ }
+ }
+
+ private void SortByStatus_Click(object sender, RoutedEventArgs e)
+ {
+ if (ViewModel != null)
+ {
+ ViewModel.DashboardSortOrder = DashboardSortOrder.ByStatus;
+ }
+ }
+}
diff --git a/src/settings-ui/QuickAccess.UI/QuickAccessXAML/Flyout/FlyoutNavigationContext.cs b/src/settings-ui/QuickAccess.UI/QuickAccessXAML/Flyout/FlyoutNavigationContext.cs
new file mode 100644
index 0000000000..3aab3ca334
--- /dev/null
+++ b/src/settings-ui/QuickAccess.UI/QuickAccessXAML/Flyout/FlyoutNavigationContext.cs
@@ -0,0 +1,13 @@
+// Copyright (c) Microsoft Corporation
+// The Microsoft Corporation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+using Microsoft.PowerToys.QuickAccess.Services;
+using Microsoft.PowerToys.QuickAccess.ViewModels;
+
+namespace Microsoft.PowerToys.QuickAccess.Flyout;
+
+internal sealed record FlyoutNavigationContext(
+ LauncherViewModel LauncherViewModel,
+ AllAppsViewModel AllAppsViewModel,
+ IQuickAccessCoordinator Coordinator);
diff --git a/src/settings-ui/Settings.UI/SettingsXAML/Flyout/LaunchPage.xaml b/src/settings-ui/QuickAccess.UI/QuickAccessXAML/Flyout/LaunchPage.xaml
similarity index 64%
rename from src/settings-ui/Settings.UI/SettingsXAML/Flyout/LaunchPage.xaml
rename to src/settings-ui/QuickAccess.UI/QuickAccessXAML/Flyout/LaunchPage.xaml
index 8dea006b08..f2f53b57d4 100644
--- a/src/settings-ui/Settings.UI/SettingsXAML/Flyout/LaunchPage.xaml
+++ b/src/settings-ui/QuickAccess.UI/QuickAccessXAML/Flyout/LaunchPage.xaml
@@ -1,5 +1,5 @@
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/settings-ui/Settings.UI.Controls/ModuleList/ModuleList.xaml.cs b/src/settings-ui/Settings.UI.Controls/ModuleList/ModuleList.xaml.cs
new file mode 100644
index 0000000000..f9d36a7e69
--- /dev/null
+++ b/src/settings-ui/Settings.UI.Controls/ModuleList/ModuleList.xaml.cs
@@ -0,0 +1,57 @@
+// Copyright (c) Microsoft Corporation
+// The Microsoft Corporation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+using Microsoft.UI.Xaml;
+using Microsoft.UI.Xaml.Controls;
+
+namespace Microsoft.PowerToys.Settings.UI.Controls
+{
+ public sealed partial class ModuleList : UserControl
+ {
+ public ModuleList()
+ {
+ this.InitializeComponent();
+ }
+
+ public Thickness DividerThickness
+ {
+ get => (Thickness)GetValue(DividerThicknessProperty);
+ set => SetValue(DividerThicknessProperty, value);
+ }
+
+ public static readonly DependencyProperty DividerThicknessProperty = DependencyProperty.Register(nameof(DividerThickness), typeof(Thickness), typeof(ModuleList), new PropertyMetadata(new Thickness(0, 1, 0, 0)));
+
+ public bool IsItemClickable
+ {
+ get => (bool)GetValue(IsItemClickableProperty);
+ set => SetValue(IsItemClickableProperty, value);
+ }
+
+ public static readonly DependencyProperty IsItemClickableProperty = DependencyProperty.Register(nameof(IsItemClickable), typeof(bool), typeof(ModuleList), new PropertyMetadata(true));
+
+ public object ItemsSource
+ {
+ get => (object)GetValue(ItemsSourceProperty);
+ set => SetValue(ItemsSourceProperty, value);
+ }
+
+ public static readonly DependencyProperty ItemsSourceProperty = DependencyProperty.Register(nameof(ItemsSource), typeof(object), typeof(ModuleList), new PropertyMetadata(null));
+
+ public ModuleListSortOption SortOption
+ {
+ get => (ModuleListSortOption)GetValue(SortOptionProperty);
+ set => SetValue(SortOptionProperty, value);
+ }
+
+ public static readonly DependencyProperty SortOptionProperty = DependencyProperty.Register(nameof(SortOption), typeof(ModuleListSortOption), typeof(ModuleList), new PropertyMetadata(ModuleListSortOption.Alphabetical));
+
+ private void OnSettingsCardClick(object sender, RoutedEventArgs e)
+ {
+ if (sender is FrameworkElement element && element.Tag is ModuleListItem item)
+ {
+ item.ClickCommand?.Execute(item.Tag);
+ }
+ }
+ }
+}
diff --git a/src/settings-ui/Settings.UI.Controls/ModuleList/ModuleListItem.cs b/src/settings-ui/Settings.UI.Controls/ModuleList/ModuleListItem.cs
new file mode 100644
index 0000000000..ef92e3e592
--- /dev/null
+++ b/src/settings-ui/Settings.UI.Controls/ModuleList/ModuleListItem.cs
@@ -0,0 +1,119 @@
+// Copyright (c) Microsoft Corporation
+// The Microsoft Corporation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+using System.ComponentModel;
+using System.Runtime.CompilerServices;
+using System.Windows.Input;
+
+namespace Microsoft.PowerToys.Settings.UI.Controls
+{
+ public class ModuleListItem : INotifyPropertyChanged
+ {
+ private bool _isEnabled;
+ private string _label = string.Empty;
+ private string _icon = string.Empty;
+ private bool _isNew;
+ private bool _isLocked;
+ private object? _tag;
+ private ICommand? _clickCommand;
+
+ public virtual string Label
+ {
+ get => _label;
+ set
+ {
+ if (_label != value)
+ {
+ _label = value;
+ OnPropertyChanged();
+ }
+ }
+ }
+
+ public virtual string Icon
+ {
+ get => _icon;
+ set
+ {
+ if (_icon != value)
+ {
+ _icon = value;
+ OnPropertyChanged();
+ }
+ }
+ }
+
+ public virtual bool IsNew
+ {
+ get => _isNew;
+ set
+ {
+ if (_isNew != value)
+ {
+ _isNew = value;
+ OnPropertyChanged();
+ }
+ }
+ }
+
+ public virtual bool IsLocked
+ {
+ get => _isLocked;
+ set
+ {
+ if (_isLocked != value)
+ {
+ _isLocked = value;
+ OnPropertyChanged();
+ }
+ }
+ }
+
+ public virtual bool IsEnabled
+ {
+ get => _isEnabled;
+ set
+ {
+ if (_isEnabled != value)
+ {
+ _isEnabled = value;
+ OnPropertyChanged();
+ }
+ }
+ }
+
+ public virtual object? Tag
+ {
+ get => _tag;
+ set
+ {
+ if (_tag != value)
+ {
+ _tag = value;
+ OnPropertyChanged();
+ }
+ }
+ }
+
+ public virtual ICommand? ClickCommand
+ {
+ get => _clickCommand;
+ set
+ {
+ if (_clickCommand != value)
+ {
+ _clickCommand = value;
+ OnPropertyChanged();
+ }
+ }
+ }
+
+ public event PropertyChangedEventHandler? PropertyChanged;
+
+ protected void OnPropertyChanged([CallerMemberName] string? propertyName = null)
+ {
+ PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
+ }
+ }
+}
diff --git a/src/settings-ui/Settings.UI.Controls/ModuleList/ModuleListSortOption.cs b/src/settings-ui/Settings.UI.Controls/ModuleList/ModuleListSortOption.cs
new file mode 100644
index 0000000000..233c891d6b
--- /dev/null
+++ b/src/settings-ui/Settings.UI.Controls/ModuleList/ModuleListSortOption.cs
@@ -0,0 +1,12 @@
+// Copyright (c) Microsoft Corporation
+// The Microsoft Corporation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+namespace Microsoft.PowerToys.Settings.UI.Controls
+{
+ public enum ModuleListSortOption
+ {
+ Alphabetical,
+ ByStatus,
+ }
+}
diff --git a/src/settings-ui/Settings.UI/SettingsXAML/Controls/Dashboard/Card.xaml b/src/settings-ui/Settings.UI.Controls/Primitives/Card.xaml
similarity index 97%
rename from src/settings-ui/Settings.UI/SettingsXAML/Controls/Dashboard/Card.xaml
rename to src/settings-ui/Settings.UI.Controls/Primitives/Card.xaml
index 9563bfceb3..508d94b7ca 100644
--- a/src/settings-ui/Settings.UI/SettingsXAML/Controls/Dashboard/Card.xaml
+++ b/src/settings-ui/Settings.UI.Controls/Primitives/Card.xaml
@@ -21,11 +21,11 @@
BorderThickness="{x:Bind BorderThickness, Mode=OneWay}"
CornerRadius="{x:Bind CornerRadius, Mode=OneWay}">
-
+
-
+
diff --git a/src/settings-ui/Settings.UI/SettingsXAML/Controls/Dashboard/Card.xaml.cs b/src/settings-ui/Settings.UI.Controls/Primitives/Card.xaml.cs
similarity index 94%
rename from src/settings-ui/Settings.UI/SettingsXAML/Controls/Dashboard/Card.xaml.cs
rename to src/settings-ui/Settings.UI.Controls/Primitives/Card.xaml.cs
index 1959a70445..ebf04c4e89 100644
--- a/src/settings-ui/Settings.UI/SettingsXAML/Controls/Dashboard/Card.xaml.cs
+++ b/src/settings-ui/Settings.UI.Controls/Primitives/Card.xaml.cs
@@ -11,9 +11,9 @@ namespace Microsoft.PowerToys.Settings.UI.Controls
{
public static readonly DependencyProperty TitleContentProperty = DependencyProperty.Register(nameof(TitleContent), typeof(object), typeof(Card), new PropertyMetadata(defaultValue: null, OnVisualPropertyChanged));
- public object TitleContent
+ public object? TitleContent
{
- get => (object)GetValue(TitleContentProperty);
+ get => (object?)GetValue(TitleContentProperty);
set => SetValue(TitleContentProperty, value);
}
@@ -34,7 +34,7 @@ namespace Microsoft.PowerToys.Settings.UI.Controls
set => SetValue(ContentProperty, value);
}
- public static readonly DependencyProperty DividerVisibilityProperty = DependencyProperty.Register(nameof(DividerVisibility), typeof(Visibility), typeof(Card), new PropertyMetadata(defaultValue: null));
+ public static readonly DependencyProperty DividerVisibilityProperty = DependencyProperty.Register(nameof(DividerVisibility), typeof(Visibility), typeof(Card), new PropertyMetadata(defaultValue: Visibility.Visible));
public Visibility DividerVisibility
{
@@ -66,7 +66,6 @@ namespace Microsoft.PowerToys.Settings.UI.Controls
else
{
VisualStateManager.GoToState(this, "TitleGridVisible", true);
- DividerVisibility = Visibility.Visible;
}
}
}
diff --git a/src/settings-ui/Settings.UI/SettingsXAML/Controls/FlyoutMenuButton.xaml.cs b/src/settings-ui/Settings.UI.Controls/Primitives/FlyoutMenuButton.cs
similarity index 96%
rename from src/settings-ui/Settings.UI/SettingsXAML/Controls/FlyoutMenuButton.xaml.cs
rename to src/settings-ui/Settings.UI.Controls/Primitives/FlyoutMenuButton.cs
index bb1767e01e..f313506f23 100644
--- a/src/settings-ui/Settings.UI/SettingsXAML/Controls/FlyoutMenuButton.xaml.cs
+++ b/src/settings-ui/Settings.UI.Controls/Primitives/FlyoutMenuButton.cs
@@ -1,4 +1,4 @@
-// Copyright (c) Microsoft Corporation
+// Copyright (c) Microsoft Corporation
// The Microsoft Corporation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
diff --git a/src/settings-ui/Settings.UI.Controls/QuickAccess/IQuickAccessLauncher.cs b/src/settings-ui/Settings.UI.Controls/QuickAccess/IQuickAccessLauncher.cs
new file mode 100644
index 0000000000..8c35889c94
--- /dev/null
+++ b/src/settings-ui/Settings.UI.Controls/QuickAccess/IQuickAccessLauncher.cs
@@ -0,0 +1,13 @@
+// Copyright (c) Microsoft Corporation
+// The Microsoft Corporation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+using ManagedCommon;
+
+namespace Microsoft.PowerToys.Settings.UI.Controls
+{
+ public interface IQuickAccessLauncher
+ {
+ bool Launch(ModuleType moduleType);
+ }
+}
diff --git a/src/settings-ui/Settings.UI.Controls/QuickAccess/QuickAccessItem.cs b/src/settings-ui/Settings.UI.Controls/QuickAccess/QuickAccessItem.cs
new file mode 100644
index 0000000000..b639d87802
--- /dev/null
+++ b/src/settings-ui/Settings.UI.Controls/QuickAccess/QuickAccessItem.cs
@@ -0,0 +1,69 @@
+// Copyright (c) Microsoft Corporation
+// The Microsoft Corporation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+using System.Windows.Input;
+using Microsoft.PowerToys.Settings.UI.Library.Helpers;
+using Microsoft.UI.Xaml;
+
+namespace Microsoft.PowerToys.Settings.UI.Controls
+{
+ public sealed class QuickAccessItem : Observable
+ {
+ private string _title = string.Empty;
+
+ public string Title
+ {
+ get => _title;
+ set => Set(ref _title, value);
+ }
+
+ private string _description = string.Empty;
+
+ public string Description
+ {
+ get => _description;
+ set => Set(ref _description, value);
+ }
+
+ private string _icon = string.Empty;
+
+ public string Icon
+ {
+ get => _icon;
+ set => Set(ref _icon, value);
+ }
+
+ private ICommand? _command;
+
+ public ICommand? Command
+ {
+ get => _command;
+ set => Set(ref _command, value);
+ }
+
+ private object? _commandParameter;
+
+ public object? CommandParameter
+ {
+ get => _commandParameter;
+ set => Set(ref _commandParameter, value);
+ }
+
+ private bool _visible = true;
+
+ public bool Visible
+ {
+ get => _visible;
+ set => Set(ref _visible, value);
+ }
+
+ private object? _tag;
+
+ public object? Tag
+ {
+ get => _tag;
+ set => Set(ref _tag, value);
+ }
+ }
+}
diff --git a/src/settings-ui/Settings.UI.Controls/QuickAccess/QuickAccessLauncher.cs b/src/settings-ui/Settings.UI.Controls/QuickAccess/QuickAccessLauncher.cs
new file mode 100644
index 0000000000..4799ff624f
--- /dev/null
+++ b/src/settings-ui/Settings.UI.Controls/QuickAccess/QuickAccessLauncher.cs
@@ -0,0 +1,121 @@
+// Copyright (c) Microsoft Corporation
+// The Microsoft Corporation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+using System.Threading;
+using ManagedCommon;
+using Microsoft.PowerToys.Settings.UI.Library;
+using PowerToys.Interop;
+
+namespace Microsoft.PowerToys.Settings.UI.Controls
+{
+ public class QuickAccessLauncher : IQuickAccessLauncher
+ {
+ private readonly bool _isElevated;
+
+ public QuickAccessLauncher(bool isElevated)
+ {
+ _isElevated = isElevated;
+ }
+
+ public virtual bool Launch(ModuleType moduleType)
+ {
+ switch (moduleType)
+ {
+ case ModuleType.ColorPicker:
+ using (var eventHandle = new EventWaitHandle(false, EventResetMode.AutoReset, Constants.ShowColorPickerSharedEvent()))
+ {
+ eventHandle.Set();
+ }
+
+ return true;
+ case ModuleType.EnvironmentVariables:
+ {
+ bool launchAdmin = SettingsRepository.GetInstance(SettingsUtils.Default).SettingsConfig.Properties.LaunchAdministrator;
+ string eventName = !_isElevated && launchAdmin
+ ? Constants.ShowEnvironmentVariablesAdminSharedEvent()
+ : Constants.ShowEnvironmentVariablesSharedEvent();
+
+ using (var eventHandle = new EventWaitHandle(false, EventResetMode.AutoReset, eventName))
+ {
+ eventHandle.Set();
+ }
+ }
+
+ return true;
+ case ModuleType.FancyZones:
+ using (var eventHandle = new EventWaitHandle(false, EventResetMode.AutoReset, Constants.FZEToggleEvent()))
+ {
+ eventHandle.Set();
+ }
+
+ return true;
+ case ModuleType.Hosts:
+ {
+ bool launchAdmin = SettingsRepository.GetInstance(SettingsUtils.Default).SettingsConfig.Properties.LaunchAdministrator;
+ string eventName = !_isElevated && launchAdmin
+ ? Constants.ShowHostsAdminSharedEvent()
+ : Constants.ShowHostsSharedEvent();
+
+ using (var eventHandle = new EventWaitHandle(false, EventResetMode.AutoReset, eventName))
+ {
+ eventHandle.Set();
+ }
+ }
+
+ return true;
+ case ModuleType.PowerLauncher:
+ using (var eventHandle = new EventWaitHandle(false, EventResetMode.AutoReset, Constants.PowerLauncherSharedEvent()))
+ {
+ eventHandle.Set();
+ }
+
+ return true;
+ case ModuleType.PowerOCR:
+ using (var eventHandle = new EventWaitHandle(false, EventResetMode.AutoReset, Constants.ShowPowerOCRSharedEvent()))
+ {
+ eventHandle.Set();
+ }
+
+ return true;
+ case ModuleType.RegistryPreview:
+ using (var eventHandle = new EventWaitHandle(false, EventResetMode.AutoReset, Constants.RegistryPreviewTriggerEvent()))
+ {
+ eventHandle.Set();
+ }
+
+ return true;
+ case ModuleType.MeasureTool:
+ using (var eventHandle = new EventWaitHandle(false, EventResetMode.AutoReset, Constants.MeasureToolTriggerEvent()))
+ {
+ eventHandle.Set();
+ }
+
+ return true;
+ case ModuleType.ShortcutGuide:
+ using (var eventHandle = new EventWaitHandle(false, EventResetMode.AutoReset, Constants.ShortcutGuideTriggerEvent()))
+ {
+ eventHandle.Set();
+ }
+
+ return true;
+ case ModuleType.CmdPal:
+ using (var eventHandle = new EventWaitHandle(false, EventResetMode.AutoReset, Constants.ShowCmdPalEvent()))
+ {
+ eventHandle.Set();
+ }
+
+ return true;
+ case ModuleType.Workspaces:
+ using (var eventHandle = new EventWaitHandle(false, EventResetMode.AutoReset, Constants.WorkspacesLaunchEditorEvent()))
+ {
+ eventHandle.Set();
+ }
+
+ return true;
+ default:
+ return false;
+ }
+ }
+ }
+}
diff --git a/src/settings-ui/Settings.UI.Controls/QuickAccess/QuickAccessList.xaml b/src/settings-ui/Settings.UI.Controls/QuickAccess/QuickAccessList.xaml
new file mode 100644
index 0000000000..415ae22684
--- /dev/null
+++ b/src/settings-ui/Settings.UI.Controls/QuickAccess/QuickAccessList.xaml
@@ -0,0 +1,51 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/settings-ui/Settings.UI.Controls/QuickAccess/QuickAccessList.xaml.cs b/src/settings-ui/Settings.UI.Controls/QuickAccess/QuickAccessList.xaml.cs
new file mode 100644
index 0000000000..34c4bad013
--- /dev/null
+++ b/src/settings-ui/Settings.UI.Controls/QuickAccess/QuickAccessList.xaml.cs
@@ -0,0 +1,26 @@
+// Copyright (c) Microsoft Corporation
+// The Microsoft Corporation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+using System.Collections.Generic;
+using Microsoft.UI.Xaml;
+using Microsoft.UI.Xaml.Controls;
+
+namespace Microsoft.PowerToys.Settings.UI.Controls
+{
+ public sealed partial class QuickAccessList : UserControl
+ {
+ public QuickAccessList()
+ {
+ this.InitializeComponent();
+ }
+
+ public object ItemsSource
+ {
+ get => (object)GetValue(ItemsSourceProperty);
+ set => SetValue(ItemsSourceProperty, value);
+ }
+
+ public static readonly DependencyProperty ItemsSourceProperty = DependencyProperty.Register(nameof(ItemsSource), typeof(object), typeof(QuickAccessList), new PropertyMetadata(null));
+ }
+}
diff --git a/src/settings-ui/Settings.UI/ViewModels/Flyout/LauncherViewModel.cs b/src/settings-ui/Settings.UI.Controls/QuickAccess/QuickAccessViewModel.cs
similarity index 50%
rename from src/settings-ui/Settings.UI/ViewModels/Flyout/LauncherViewModel.cs
rename to src/settings-ui/Settings.UI.Controls/QuickAccess/QuickAccessViewModel.cs
index 7adc4bb933..5531df1ee5 100644
--- a/src/settings-ui/Settings.UI/ViewModels/Flyout/LauncherViewModel.cs
+++ b/src/settings-ui/Settings.UI.Controls/QuickAccess/QuickAccessViewModel.cs
@@ -1,44 +1,64 @@
-// Copyright (c) Microsoft Corporation
+// Copyright (c) Microsoft Corporation
// The Microsoft Corporation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
using System;
using System.Collections.ObjectModel;
-
-using global::PowerToys.GPOWrapper;
using ManagedCommon;
-using Microsoft.PowerToys.Settings.UI.Helpers;
using Microsoft.PowerToys.Settings.UI.Library;
using Microsoft.PowerToys.Settings.UI.Library.Helpers;
using Microsoft.PowerToys.Settings.UI.Library.Interfaces;
+using Microsoft.PowerToys.Settings.UI.Library.ViewModels.Commands;
+using Microsoft.UI.Dispatching;
using Microsoft.Windows.ApplicationModel.Resources;
-namespace Microsoft.PowerToys.Settings.UI.ViewModels
+namespace Microsoft.PowerToys.Settings.UI.Controls
{
- public partial class LauncherViewModel : Observable
+ public partial class QuickAccessViewModel : Observable
{
- public bool IsUpdateAvailable { get; set; }
+ private readonly ISettingsRepository _settingsRepository;
+ private readonly IQuickAccessLauncher _launcher;
+ private readonly Func _isModuleGpoDisabled;
+ private readonly ResourceLoader _resourceLoader;
+ private readonly DispatcherQueue _dispatcherQueue;
+ private GeneralSettings _generalSettings;
- public ObservableCollection FlyoutMenuItems { get; set; }
+ public ObservableCollection Items { get; } = new();
- private GeneralSettings generalSettingsConfig;
- private UpdatingSettings updatingSettingsConfig;
- private ISettingsRepository _settingsRepository;
- private ResourceLoader resourceLoader;
-
- private Func SendIPCMessage { get; }
-
- public LauncherViewModel(ISettingsRepository settingsRepository, Func ipcMSGCallBackFunc)
+ public QuickAccessViewModel(
+ ISettingsRepository settingsRepository,
+ IQuickAccessLauncher launcher,
+ Func isModuleGpoDisabled,
+ ResourceLoader resourceLoader)
{
_settingsRepository = settingsRepository;
- generalSettingsConfig = settingsRepository.SettingsConfig;
- generalSettingsConfig.AddEnabledModuleChangeNotification(ModuleEnabledChanged);
+ _launcher = launcher;
+ _isModuleGpoDisabled = isModuleGpoDisabled;
+ _resourceLoader = resourceLoader;
+ _dispatcherQueue = DispatcherQueue.GetForCurrentThread();
- // set the callback functions value to handle outgoing IPC message.
- SendIPCMessage = ipcMSGCallBackFunc;
- resourceLoader = ResourceLoaderInstance.ResourceLoader;
- FlyoutMenuItems = new ObservableCollection();
+ _generalSettings = _settingsRepository.SettingsConfig;
+ _generalSettings.AddEnabledModuleChangeNotification(ModuleEnabledChanged);
+ _settingsRepository.SettingsChanged += OnSettingsChanged;
+ InitializeItems();
+ }
+
+ private void OnSettingsChanged(GeneralSettings newSettings)
+ {
+ if (_dispatcherQueue != null)
+ {
+ _dispatcherQueue.TryEnqueue(() =>
+ {
+ _generalSettings = newSettings;
+ _generalSettings.AddEnabledModuleChangeNotification(ModuleEnabledChanged);
+ RefreshItemsVisibility();
+ });
+ }
+ }
+
+ private void InitializeItems()
+ {
AddFlyoutMenuItem(ModuleType.ColorPicker);
AddFlyoutMenuItem(ModuleType.CmdPal);
AddFlyoutMenuItem(ModuleType.EnvironmentVariables);
@@ -50,40 +70,50 @@ namespace Microsoft.PowerToys.Settings.UI.ViewModels
AddFlyoutMenuItem(ModuleType.MeasureTool);
AddFlyoutMenuItem(ModuleType.ShortcutGuide);
AddFlyoutMenuItem(ModuleType.Workspaces);
-
- updatingSettingsConfig = UpdatingSettings.LoadSettings();
- if (updatingSettingsConfig == null)
- {
- updatingSettingsConfig = new UpdatingSettings();
- }
-
- if (updatingSettingsConfig.State == UpdatingSettings.UpdatingState.ReadyToInstall || updatingSettingsConfig.State == UpdatingSettings.UpdatingState.ReadyToDownload)
- {
- IsUpdateAvailable = true;
- }
- else
- {
- IsUpdateAvailable = false;
- }
}
private void AddFlyoutMenuItem(ModuleType moduleType)
{
- if (ModuleHelper.GetModuleGpoConfiguration(moduleType) == GpoRuleConfigured.Disabled)
+ if (_isModuleGpoDisabled(moduleType))
{
return;
}
- FlyoutMenuItems.Add(new FlyoutMenuItem()
+ Items.Add(new QuickAccessItem
{
- Label = resourceLoader.GetString(ModuleHelper.GetModuleLabelResourceName(moduleType)),
+ Title = _resourceLoader.GetString(Microsoft.PowerToys.Settings.UI.Library.Helpers.ModuleHelper.GetModuleLabelResourceName(moduleType)),
Tag = moduleType,
- Visible = ModuleHelper.GetIsModuleEnabled(generalSettingsConfig, moduleType),
- ToolTip = GetModuleToolTip(moduleType),
- Icon = ModuleHelper.GetModuleTypeFluentIconName(moduleType),
+ Visible = Microsoft.PowerToys.Settings.UI.Library.Helpers.ModuleHelper.GetIsModuleEnabled(_generalSettings, moduleType),
+ Description = GetModuleToolTip(moduleType),
+ Icon = Microsoft.PowerToys.Settings.UI.Library.Helpers.ModuleHelper.GetModuleTypeFluentIconName(moduleType),
+ Command = new RelayCommand(() => _launcher.Launch(moduleType)),
});
}
+ private void ModuleEnabledChanged()
+ {
+ if (_dispatcherQueue != null)
+ {
+ _dispatcherQueue.TryEnqueue(() =>
+ {
+ _generalSettings = _settingsRepository.SettingsConfig;
+ _generalSettings.AddEnabledModuleChangeNotification(ModuleEnabledChanged);
+ RefreshItemsVisibility();
+ });
+ }
+ }
+
+ private void RefreshItemsVisibility()
+ {
+ foreach (var item in Items)
+ {
+ if (item.Tag is ModuleType moduleType)
+ {
+ item.Visible = Microsoft.PowerToys.Settings.UI.Library.Helpers.ModuleHelper.GetIsModuleEnabled(_generalSettings, moduleType);
+ }
+ }
+ }
+
private string GetModuleToolTip(ModuleType moduleType)
{
return moduleType switch
@@ -99,16 +129,6 @@ namespace Microsoft.PowerToys.Settings.UI.ViewModels
};
}
- private void ModuleEnabledChanged()
- {
- generalSettingsConfig = _settingsRepository.SettingsConfig;
- generalSettingsConfig.AddEnabledModuleChangeNotification(ModuleEnabledChanged);
- foreach (FlyoutMenuItem item in FlyoutMenuItems)
- {
- item.Visible = ModuleHelper.GetIsModuleEnabled(generalSettingsConfig, item.Tag);
- }
- }
-
private string GetShortcutGuideToolTip()
{
var shortcutGuideSettings = SettingsRepository.GetInstance(SettingsUtils.Default).SettingsConfig;
@@ -116,15 +136,5 @@ namespace Microsoft.PowerToys.Settings.UI.ViewModels
? "Win"
: shortcutGuideSettings.Properties.OpenShortcutGuide.ToString();
}
-
- internal void StartBugReport()
- {
- SendIPCMessage("{\"bugreport\": 0 }");
- }
-
- internal void KillRunner()
- {
- SendIPCMessage("{\"killrunner\": 0 }");
- }
}
}
diff --git a/src/settings-ui/Settings.UI.Controls/Settings.UI.Controls.csproj b/src/settings-ui/Settings.UI.Controls/Settings.UI.Controls.csproj
new file mode 100644
index 0000000000..fac43d56a4
--- /dev/null
+++ b/src/settings-ui/Settings.UI.Controls/Settings.UI.Controls.csproj
@@ -0,0 +1,32 @@
+
+
+
+
+
+ Library
+ net9.0-windows10.0.26100.0
+ Microsoft.PowerToys.Settings.UI.Controls
+ PowerToys.Settings.UI.Controls
+ true
+ true
+ true
+ PowerToys.Settings.UI.Controls.pri
+ enable
+ x64;ARM64
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/settings-ui/Settings.UI.Controls/Strings/en-us/Resources.resw b/src/settings-ui/Settings.UI.Controls/Strings/en-us/Resources.resw
new file mode 100644
index 0000000000..6664b14936
--- /dev/null
+++ b/src/settings-ui/Settings.UI.Controls/Strings/en-us/Resources.resw
@@ -0,0 +1,138 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ text/microsoft-resx
+
+
+ 2.0
+
+
+ System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089
+
+
+ System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089
+
+
+ Sort utilities
+
+
+ Alphabetically
+
+
+ By status
+
+
+ Sort utilities
+
+
+ NEW
+
+
+ This setting is managed by your organization
+
+
\ No newline at end of file
diff --git a/src/settings-ui/Settings.UI/SettingsXAML/Controls/FlyoutMenuButton.xaml b/src/settings-ui/Settings.UI.Controls/Themes/Generic.xaml
similarity index 95%
rename from src/settings-ui/Settings.UI/SettingsXAML/Controls/FlyoutMenuButton.xaml
rename to src/settings-ui/Settings.UI.Controls/Themes/Generic.xaml
index 50c0e4007c..466ad4475b 100644
--- a/src/settings-ui/Settings.UI/SettingsXAML/Controls/FlyoutMenuButton.xaml
+++ b/src/settings-ui/Settings.UI.Controls/Themes/Generic.xaml
@@ -1,14 +1,9 @@
-
-
-
-
-
-