diff --git a/src/settings-ui/QuickAccess.UI/Helpers/ModuleHelper.cs b/src/settings-ui/QuickAccess.UI/Helpers/ModuleHelper.cs new file mode 100644 index 0000000000..3e52133324 --- /dev/null +++ b/src/settings-ui/QuickAccess.UI/Helpers/ModuleHelper.cs @@ -0,0 +1,155 @@ +// 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 ModuleHelper +{ + public static string GetModuleLabelResourceName(ModuleType moduleType) + { + return moduleType switch + { + ModuleType.Workspaces => "Workspaces/ModuleTitle", + ModuleType.PowerAccent => "QuickAccent/ModuleTitle", + ModuleType.PowerOCR => "TextExtractor/ModuleTitle", + ModuleType.FindMyMouse => "MouseUtils_FindMyMouse/Header", + ModuleType.MouseHighlighter => "MouseUtils_MouseHighlighter/Header", + ModuleType.MouseJump => "MouseUtils_MouseJump/Header", + ModuleType.MousePointerCrosshairs => "MouseUtils_MousePointerCrosshairs/Header", + ModuleType.CursorWrap => "MouseUtils_CursorWrap/Header", + _ => $"{moduleType}/ModuleTitle", + }; + } + + public static string GetModuleTypeFluentIconName(ModuleType moduleType) + { + return moduleType switch + { + ModuleType.AdvancedPaste => "ms-appx:///Assets/Settings/Icons/AdvancedPaste.png", + ModuleType.Workspaces => "ms-appx:///Assets/Settings/Icons/Workspaces.png", + ModuleType.PowerOCR => "ms-appx:///Assets/Settings/Icons/TextExtractor.png", + ModuleType.PowerAccent => "ms-appx:///Assets/Settings/Icons/QuickAccent.png", + ModuleType.MousePointerCrosshairs => "ms-appx:///Assets/Settings/Icons/MouseCrosshairs.png", + ModuleType.MeasureTool => "ms-appx:///Assets/Settings/Icons/ScreenRuler.png", + ModuleType.PowerLauncher => "ms-appx:///Assets/Settings/Icons/PowerToysRun.png", + _ => $"ms-appx:///Assets/Settings/Icons/{moduleType}.png", + }; + } + + public static bool GetIsModuleEnabled(GeneralSettings generalSettingsConfig, ModuleType moduleType) + { + return moduleType switch + { + ModuleType.AdvancedPaste => generalSettingsConfig.Enabled.AdvancedPaste, + ModuleType.AlwaysOnTop => generalSettingsConfig.Enabled.AlwaysOnTop, + ModuleType.Awake => generalSettingsConfig.Enabled.Awake, + ModuleType.CmdPal => generalSettingsConfig.Enabled.CmdPal, + ModuleType.ColorPicker => generalSettingsConfig.Enabled.ColorPicker, + ModuleType.CropAndLock => generalSettingsConfig.Enabled.CropAndLock, + ModuleType.CursorWrap => generalSettingsConfig.Enabled.CursorWrap, + ModuleType.LightSwitch => generalSettingsConfig.Enabled.LightSwitch, + ModuleType.EnvironmentVariables => generalSettingsConfig.Enabled.EnvironmentVariables, + ModuleType.FancyZones => generalSettingsConfig.Enabled.FancyZones, + ModuleType.FileLocksmith => generalSettingsConfig.Enabled.FileLocksmith, + ModuleType.FindMyMouse => generalSettingsConfig.Enabled.FindMyMouse, + ModuleType.Hosts => generalSettingsConfig.Enabled.Hosts, + ModuleType.ImageResizer => generalSettingsConfig.Enabled.ImageResizer, + ModuleType.KeyboardManager => generalSettingsConfig.Enabled.KeyboardManager, + ModuleType.MouseHighlighter => generalSettingsConfig.Enabled.MouseHighlighter, + ModuleType.MouseJump => generalSettingsConfig.Enabled.MouseJump, + ModuleType.MousePointerCrosshairs => generalSettingsConfig.Enabled.MousePointerCrosshairs, + ModuleType.MouseWithoutBorders => generalSettingsConfig.Enabled.MouseWithoutBorders, + ModuleType.NewPlus => generalSettingsConfig.Enabled.NewPlus, + ModuleType.Peek => generalSettingsConfig.Enabled.Peek, + ModuleType.PowerRename => generalSettingsConfig.Enabled.PowerRename, + ModuleType.PowerLauncher => generalSettingsConfig.Enabled.PowerLauncher, + ModuleType.PowerAccent => generalSettingsConfig.Enabled.PowerAccent, + ModuleType.Workspaces => generalSettingsConfig.Enabled.Workspaces, + ModuleType.RegistryPreview => generalSettingsConfig.Enabled.RegistryPreview, + ModuleType.MeasureTool => generalSettingsConfig.Enabled.MeasureTool, + ModuleType.ShortcutGuide => generalSettingsConfig.Enabled.ShortcutGuide, + ModuleType.PowerOCR => generalSettingsConfig.Enabled.PowerOcr, + ModuleType.ZoomIt => generalSettingsConfig.Enabled.ZoomIt, + _ => false, + }; + } + + public static void SetIsModuleEnabled(GeneralSettings generalSettingsConfig, ModuleType moduleType, bool isEnabled) + { + switch (moduleType) + { + case ModuleType.AdvancedPaste: generalSettingsConfig.Enabled.AdvancedPaste = isEnabled; break; + case ModuleType.AlwaysOnTop: generalSettingsConfig.Enabled.AlwaysOnTop = isEnabled; break; + case ModuleType.Awake: generalSettingsConfig.Enabled.Awake = isEnabled; break; + case ModuleType.CmdPal: generalSettingsConfig.Enabled.CmdPal = isEnabled; break; + case ModuleType.ColorPicker: generalSettingsConfig.Enabled.ColorPicker = isEnabled; break; + case ModuleType.CropAndLock: generalSettingsConfig.Enabled.CropAndLock = isEnabled; break; + case ModuleType.CursorWrap: generalSettingsConfig.Enabled.CursorWrap = isEnabled; break; + case ModuleType.LightSwitch: generalSettingsConfig.Enabled.LightSwitch = isEnabled; break; + case ModuleType.EnvironmentVariables: generalSettingsConfig.Enabled.EnvironmentVariables = isEnabled; break; + case ModuleType.FancyZones: generalSettingsConfig.Enabled.FancyZones = isEnabled; break; + case ModuleType.FileLocksmith: generalSettingsConfig.Enabled.FileLocksmith = isEnabled; break; + case ModuleType.FindMyMouse: generalSettingsConfig.Enabled.FindMyMouse = isEnabled; break; + case ModuleType.Hosts: generalSettingsConfig.Enabled.Hosts = isEnabled; break; + case ModuleType.ImageResizer: generalSettingsConfig.Enabled.ImageResizer = isEnabled; break; + case ModuleType.KeyboardManager: generalSettingsConfig.Enabled.KeyboardManager = isEnabled; break; + case ModuleType.MouseHighlighter: generalSettingsConfig.Enabled.MouseHighlighter = isEnabled; break; + case ModuleType.MouseJump: generalSettingsConfig.Enabled.MouseJump = isEnabled; break; + case ModuleType.MousePointerCrosshairs: generalSettingsConfig.Enabled.MousePointerCrosshairs = isEnabled; break; + case ModuleType.MouseWithoutBorders: generalSettingsConfig.Enabled.MouseWithoutBorders = isEnabled; break; + case ModuleType.NewPlus: generalSettingsConfig.Enabled.NewPlus = isEnabled; break; + case ModuleType.Peek: generalSettingsConfig.Enabled.Peek = isEnabled; break; + case ModuleType.PowerRename: generalSettingsConfig.Enabled.PowerRename = isEnabled; break; + case ModuleType.PowerLauncher: generalSettingsConfig.Enabled.PowerLauncher = isEnabled; break; + case ModuleType.PowerAccent: generalSettingsConfig.Enabled.PowerAccent = isEnabled; break; + case ModuleType.Workspaces: generalSettingsConfig.Enabled.Workspaces = isEnabled; break; + case ModuleType.RegistryPreview: generalSettingsConfig.Enabled.RegistryPreview = isEnabled; break; + case ModuleType.MeasureTool: generalSettingsConfig.Enabled.MeasureTool = isEnabled; break; + case ModuleType.ShortcutGuide: generalSettingsConfig.Enabled.ShortcutGuide = isEnabled; break; + case ModuleType.PowerOCR: generalSettingsConfig.Enabled.PowerOcr = isEnabled; break; + case ModuleType.ZoomIt: generalSettingsConfig.Enabled.ZoomIt = isEnabled; break; + } + } + + 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/QuickAccessXAML/Flyout/AppsListPage.xaml b/src/settings-ui/QuickAccess.UI/QuickAccessXAML/Flyout/AppsListPage.xaml new file mode 100644 index 0000000000..f2a0ece8d5 --- /dev/null +++ b/src/settings-ui/QuickAccess.UI/QuickAccessXAML/Flyout/AppsListPage.xaml @@ -0,0 +1,98 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 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..0834e836ec --- /dev/null +++ b/src/settings-ui/QuickAccess.UI/QuickAccessXAML/Flyout/AppsListPage.xaml.cs @@ -0,0 +1,45 @@ +// 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.ViewModels; +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; + } + } + + private void BackButton_Click(object sender, RoutedEventArgs e) + { + if (_context == null || Frame == null) + { + return; + } + + Frame.Navigate(typeof(LaunchPage), _context, new SlideNavigationTransitionInfo { Effect = SlideNavigationTransitionEffect.FromLeft }); + } +} 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/QuickAccess.UI/QuickAccessXAML/Flyout/LaunchPage.xaml b/src/settings-ui/QuickAccess.UI/QuickAccessXAML/Flyout/LaunchPage.xaml new file mode 100644 index 0000000000..073558245c --- /dev/null +++ b/src/settings-ui/QuickAccess.UI/QuickAccessXAML/Flyout/LaunchPage.xaml @@ -0,0 +1,165 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/settings-ui/QuickAccess.UI/QuickAccessXAML/Flyout/LaunchPage.xaml.cs b/src/settings-ui/QuickAccess.UI/QuickAccessXAML/Flyout/LaunchPage.xaml.cs new file mode 100644 index 0000000000..5aea293cca --- /dev/null +++ b/src/settings-ui/QuickAccess.UI/QuickAccessXAML/Flyout/LaunchPage.xaml.cs @@ -0,0 +1,201 @@ +// 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.Threading; +using ManagedCommon; +using Microsoft.PowerToys.QuickAccess.Services; +using Microsoft.PowerToys.QuickAccess.ViewModels; +using Microsoft.PowerToys.Settings.UI.Controls; +using Microsoft.PowerToys.Settings.UI.Library; +using Microsoft.UI.Xaml; +using Microsoft.UI.Xaml.Controls; +using Microsoft.UI.Xaml.Input; +using Microsoft.UI.Xaml.Media.Animation; +using Microsoft.UI.Xaml.Navigation; +using PowerToys.Interop; +using Windows.System; + +namespace Microsoft.PowerToys.QuickAccess.Flyout; + +public sealed partial class LaunchPage : Page +{ + private AllAppsViewModel? _allAppsViewModel; + private IQuickAccessCoordinator? _coordinator; + + public LaunchPage() + { + InitializeComponent(); + } + + public LauncherViewModel ViewModel { get; private set; } = default!; + + protected override void OnNavigatedTo(NavigationEventArgs e) + { + base.OnNavigatedTo(e); + + if (e.Parameter is FlyoutNavigationContext context) + { + ViewModel = context.LauncherViewModel; + _allAppsViewModel = context.AllAppsViewModel; + _coordinator = context.Coordinator; + DataContext = ViewModel; + } + } + + private void ModuleButton_Click(object sender, RoutedEventArgs e) + { + if (sender is not FlyoutMenuButton selectedModuleBtn) + { + return; + } + + if (selectedModuleBtn.Tag is not ModuleType moduleType) + { + return; + } + + bool moduleRun = true; + + switch (moduleType) + { + case ModuleType.ColorPicker: + using (var eventHandle = new EventWaitHandle(false, EventResetMode.AutoReset, Constants.ShowColorPickerSharedEvent())) + { + eventHandle.Set(); + } + + break; + case ModuleType.EnvironmentVariables: + { + bool launchAdmin = SettingsRepository.GetInstance(new SettingsUtils()).SettingsConfig.Properties.LaunchAdministrator; + bool isElevated = _coordinator?.IsRunnerElevated ?? false; + string eventName = !isElevated && launchAdmin + ? Constants.ShowEnvironmentVariablesAdminSharedEvent() + : Constants.ShowEnvironmentVariablesSharedEvent(); + + using (var eventHandle = new EventWaitHandle(false, EventResetMode.AutoReset, eventName)) + { + eventHandle.Set(); + } + } + + break; + case ModuleType.FancyZones: + using (var eventHandle = new EventWaitHandle(false, EventResetMode.AutoReset, Constants.FZEToggleEvent())) + { + eventHandle.Set(); + } + + break; + case ModuleType.Hosts: + { + bool launchAdmin = SettingsRepository.GetInstance(new SettingsUtils()).SettingsConfig.Properties.LaunchAdministrator; + bool isElevated = _coordinator?.IsRunnerElevated ?? false; + string eventName = !isElevated && launchAdmin + ? Constants.ShowHostsAdminSharedEvent() + : Constants.ShowHostsSharedEvent(); + + using (var eventHandle = new EventWaitHandle(false, EventResetMode.AutoReset, eventName)) + { + eventHandle.Set(); + } + } + + break; + case ModuleType.PowerLauncher: + using (var eventHandle = new EventWaitHandle(false, EventResetMode.AutoReset, Constants.PowerLauncherSharedEvent())) + { + eventHandle.Set(); + } + + break; + case ModuleType.PowerOCR: + using (var eventHandle = new EventWaitHandle(false, EventResetMode.AutoReset, Constants.ShowPowerOCRSharedEvent())) + { + eventHandle.Set(); + } + + break; + case ModuleType.RegistryPreview: + using (var eventHandle = new EventWaitHandle(false, EventResetMode.AutoReset, Constants.RegistryPreviewTriggerEvent())) + { + eventHandle.Set(); + } + + break; + case ModuleType.MeasureTool: + using (var eventHandle = new EventWaitHandle(false, EventResetMode.AutoReset, Constants.MeasureToolTriggerEvent())) + { + eventHandle.Set(); + } + + break; + case ModuleType.ShortcutGuide: + using (var eventHandle = new EventWaitHandle(false, EventResetMode.AutoReset, Constants.ShortcutGuideTriggerEvent())) + { + eventHandle.Set(); + } + + break; + case ModuleType.CmdPal: + using (var eventHandle = new EventWaitHandle(false, EventResetMode.AutoReset, Constants.ShowCmdPalEvent())) + { + eventHandle.Set(); + } + + break; + case ModuleType.Workspaces: + using (var eventHandle = new EventWaitHandle(false, EventResetMode.AutoReset, Constants.WorkspacesLaunchEditorEvent())) + { + eventHandle.Set(); + } + + break; + default: + moduleRun = false; + break; + } + + if (moduleRun) + { + _coordinator?.OnModuleLaunched(moduleType); + } + + _coordinator?.HideFlyout(); + } + + private void SettingsBtn_Click(object sender, RoutedEventArgs e) + { + _coordinator?.OpenSettings(); + } + + private async void DocsBtn_Click(object sender, RoutedEventArgs e) + { + if (_coordinator == null || !await _coordinator.ShowDocumentationAsync()) + { + await Launcher.LaunchUriAsync(new Uri("https://aka.ms/PowerToysOverview")); + } + } + + private void AllAppButton_Click(object sender, RoutedEventArgs e) + { + if (Frame == null || _allAppsViewModel == null || ViewModel == null || _coordinator == null) + { + return; + } + + var context = new FlyoutNavigationContext(ViewModel, _allAppsViewModel, _coordinator); + Frame.Navigate(typeof(AppsListPage), context, new SlideNavigationTransitionInfo { Effect = SlideNavigationTransitionEffect.FromRight }); + } + + private void ReportBugBtn_Click(object sender, RoutedEventArgs e) + { + } + + private void UpdateInfoBar_Tapped(object sender, TappedRoutedEventArgs e) + { + _coordinator?.OpenGeneralSettingsForUpdates(); + } +} diff --git a/src/settings-ui/QuickAccess.UI/QuickAccessXAML/Flyout/ShellPage.xaml b/src/settings-ui/QuickAccess.UI/QuickAccessXAML/Flyout/ShellPage.xaml new file mode 100644 index 0000000000..5f19844d69 --- /dev/null +++ b/src/settings-ui/QuickAccess.UI/QuickAccessXAML/Flyout/ShellPage.xaml @@ -0,0 +1,13 @@ + + + + + + diff --git a/src/settings-ui/QuickAccess.UI/QuickAccessXAML/Flyout/ShellPage.xaml.cs b/src/settings-ui/QuickAccess.UI/QuickAccessXAML/Flyout/ShellPage.xaml.cs new file mode 100644 index 0000000000..e5b4d23cb5 --- /dev/null +++ b/src/settings-ui/QuickAccess.UI/QuickAccessXAML/Flyout/ShellPage.xaml.cs @@ -0,0 +1,60 @@ +// 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; +using Microsoft.UI.Xaml; +using Microsoft.UI.Xaml.Controls; +using Microsoft.UI.Xaml.Media.Animation; + +namespace Microsoft.PowerToys.QuickAccess.Flyout; + +/// +/// Hosts the flyout navigation frame. +/// +public sealed partial class ShellPage : Page +{ + private LauncherViewModel? _launcherViewModel; + private AllAppsViewModel? _allAppsViewModel; + private IQuickAccessCoordinator? _coordinator; + + public ShellPage() + { + InitializeComponent(); + } + + public void Initialize(IQuickAccessCoordinator coordinator, LauncherViewModel launcherViewModel, AllAppsViewModel allAppsViewModel) + { + _coordinator = coordinator; + _launcherViewModel = launcherViewModel; + _allAppsViewModel = allAppsViewModel; + } + + private void Page_Loaded(object sender, RoutedEventArgs e) + { + if (_launcherViewModel == null || _allAppsViewModel == null || _coordinator == null) + { + return; + } + + if (ContentFrame.Content is LaunchPage) + { + return; + } + + var context = new FlyoutNavigationContext(_launcherViewModel, _allAppsViewModel, _coordinator); + ContentFrame.Navigate(typeof(LaunchPage), context, new SuppressNavigationTransitionInfo()); + } + + internal void NavigateToLaunch() + { + if (_launcherViewModel == null || _allAppsViewModel == null || _coordinator == null) + { + return; + } + + var context = new FlyoutNavigationContext(_launcherViewModel, _allAppsViewModel, _coordinator); + ContentFrame.Navigate(typeof(LaunchPage), context, new SlideNavigationTransitionInfo { Effect = SlideNavigationTransitionEffect.FromLeft }); + } +} diff --git a/src/settings-ui/QuickAccess.UI/Services/IQuickAccessCoordinator.cs b/src/settings-ui/QuickAccess.UI/Services/IQuickAccessCoordinator.cs new file mode 100644 index 0000000000..e739f03b42 --- /dev/null +++ b/src/settings-ui/QuickAccess.UI/Services/IQuickAccessCoordinator.cs @@ -0,0 +1,27 @@ +// 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.Tasks; +using ManagedCommon; + +namespace Microsoft.PowerToys.QuickAccess.Services; + +public interface IQuickAccessCoordinator +{ + bool IsRunnerElevated { get; } + + void HideFlyout(); + + void OpenSettings(); + + void OpenGeneralSettingsForUpdates(); + + Task ShowDocumentationAsync(); + + void NotifyUserSettingsInteraction(); + + bool UpdateModuleEnabled(ModuleType moduleType, bool isEnabled); + + void OnModuleLaunched(ModuleType moduleType); +} diff --git a/src/settings-ui/QuickAccess.UI/Services/QuickAccessCoordinator.cs b/src/settings-ui/QuickAccess.UI/Services/QuickAccessCoordinator.cs new file mode 100644 index 0000000000..c0be5252d1 --- /dev/null +++ b/src/settings-ui/QuickAccess.UI/Services/QuickAccessCoordinator.cs @@ -0,0 +1,186 @@ +// 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.Threading.Tasks; +using Common.UI; +using ManagedCommon; +using Microsoft.PowerToys.QuickAccess.Helpers; +using Microsoft.PowerToys.Settings.UI.Library; +using Microsoft.PowerToys.Settings.UI.Library.Helpers; +using PowerToys.Interop; + +namespace Microsoft.PowerToys.QuickAccess.Services; + +internal sealed class QuickAccessCoordinator : IQuickAccessCoordinator, IDisposable +{ + private readonly MainWindow _window; + private readonly QuickAccessLaunchContext _launchContext; + private readonly SettingsUtils _settingsUtils = new(); + private readonly object _generalSettingsLock = new(); + private readonly object _ipcLock = new(); + private TwoWayPipeMessageIPCManaged? _ipcManager; + private bool _ipcUnavailableLogged; + + public QuickAccessCoordinator(MainWindow window, QuickAccessLaunchContext launchContext) + { + _window = window; + _launchContext = launchContext; + InitializeIpc(); + } + + public bool IsRunnerElevated => false; // TODO: wire up real elevation state. + + public void HideFlyout() + { + _window.RequestHide(); + } + + public void OpenSettings() + { + SettingsDeepLink.OpenSettings(SettingsDeepLink.SettingsWindow.Dashboard, true); + _window.RequestHide(); + } + + public void OpenGeneralSettingsForUpdates() + { + SettingsDeepLink.OpenSettings(SettingsDeepLink.SettingsWindow.Overview, true); + _window.RequestHide(); + } + + public Task ShowDocumentationAsync() + { + Logger.LogInfo("QuickAccessCoordinator.ShowDocumentationAsync is not yet connected."); + return Task.FromResult(false); + } + + public void NotifyUserSettingsInteraction() + { + Logger.LogDebug("QuickAccessCoordinator.NotifyUserSettingsInteraction invoked."); + } + + public bool UpdateModuleEnabled(ModuleType moduleType, bool isEnabled) + { + GeneralSettings? updatedSettings = null; + lock (_generalSettingsLock) + { + var repository = SettingsRepository.GetInstance(_settingsUtils); + var generalSettings = repository.SettingsConfig; + var current = ModuleHelper.GetIsModuleEnabled(generalSettings, moduleType); + if (current == isEnabled) + { + return false; + } + + ModuleHelper.SetIsModuleEnabled(generalSettings, moduleType, isEnabled); + _settingsUtils.SaveSettings(generalSettings.ToJsonString()); + Logger.LogInfo($"QuickAccess updated module '{moduleType}' enabled state to {isEnabled}."); + updatedSettings = generalSettings; + } + + if (updatedSettings != null) + { + SendGeneralSettingsUpdate(updatedSettings); + } + + return true; + } + + public void OnModuleLaunched(ModuleType moduleType) + { + Logger.LogInfo($"QuickAccessLauncher invoked module {moduleType}."); + } + + public void Dispose() + { + DisposeIpc(); + } + + private void InitializeIpc() + { + if (string.IsNullOrEmpty(_launchContext.RunnerPipeName) || string.IsNullOrEmpty(_launchContext.AppPipeName)) + { + Logger.LogWarning("QuickAccessCoordinator: IPC pipe names not provided. Runner will not receive updates."); + return; + } + + try + { + _ipcManager = new TwoWayPipeMessageIPCManaged(_launchContext.AppPipeName, _launchContext.RunnerPipeName, OnIpcMessageReceived); + _ipcManager.Start(); + _ipcUnavailableLogged = false; + } + catch (Exception ex) + { + Logger.LogError("QuickAccessCoordinator: failed to start IPC channel to runner.", ex); + DisposeIpc(); + } + } + + private void OnIpcMessageReceived(string message) + { + Logger.LogDebug($"QuickAccessCoordinator received IPC payload: {message}"); + } + + private void SendGeneralSettingsUpdate(GeneralSettings updatedSettings) + { + string payload; + try + { + payload = new OutGoingGeneralSettings(updatedSettings).ToString(); + } + catch (Exception ex) + { + Logger.LogError("QuickAccessCoordinator: failed to serialize general settings payload.", ex); + return; + } + + lock (_ipcLock) + { + if (_ipcManager == null) + { + if (!_ipcUnavailableLogged) + { + _ipcUnavailableLogged = true; + Logger.LogWarning("QuickAccessCoordinator: unable to send settings update because IPC is not available."); + } + + return; + } + + try + { + _ipcManager.Send(payload); + } + catch (Exception ex) + { + Logger.LogError("QuickAccessCoordinator: failed to send general settings update to runner.", ex); + } + } + } + + private void DisposeIpc() + { + lock (_ipcLock) + { + if (_ipcManager == null) + { + return; + } + + try + { + _ipcManager.End(); + } + catch (Exception ex) + { + Logger.LogWarning($"QuickAccessCoordinator: exception while shutting down IPC. {ex.Message}"); + } + + _ipcManager.Dispose(); + _ipcManager = null; + _ipcUnavailableLogged = false; + } + } +} diff --git a/src/settings-ui/QuickAccess.UI/ViewModels/AllAppsViewModel.cs b/src/settings-ui/QuickAccess.UI/ViewModels/AllAppsViewModel.cs new file mode 100644 index 0000000000..7db99a50e7 --- /dev/null +++ b/src/settings-ui/QuickAccess.UI/ViewModels/AllAppsViewModel.cs @@ -0,0 +1,82 @@ +// 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.QuickAccess.Helpers; +using Microsoft.PowerToys.QuickAccess.Services; +using Microsoft.PowerToys.Settings.UI.Library; +using Microsoft.PowerToys.Settings.UI.Library.Helpers; +using Microsoft.PowerToys.Settings.UI.Library.Interfaces; +using Microsoft.Windows.ApplicationModel.Resources; + +namespace Microsoft.PowerToys.QuickAccess.ViewModels; + +public sealed class AllAppsViewModel : Observable +{ + private readonly IQuickAccessCoordinator _coordinator; + private readonly ISettingsRepository _settingsRepository; + private readonly ResourceLoader _resourceLoader; + private GeneralSettings _generalSettings; + + public ObservableCollection FlyoutMenuItems { get; } + + public AllAppsViewModel(IQuickAccessCoordinator coordinator) + { + _coordinator = coordinator; + var settingsUtils = new SettingsUtils(); + _settingsRepository = SettingsRepository.GetInstance(settingsUtils); + _generalSettings = _settingsRepository.SettingsConfig; + _generalSettings.AddEnabledModuleChangeNotification(ModuleEnabledChangedOnSettingsPage); + + _resourceLoader = Helpers.ResourceLoaderInstance.ResourceLoader; + FlyoutMenuItems = new ObservableCollection(); + + foreach (ModuleType moduleType in Enum.GetValues()) + { + AddFlyoutMenuItem(moduleType); + } + } + + private void AddFlyoutMenuItem(ModuleType moduleType) + { + var gpo = ModuleHelper.GetModuleGpoConfiguration(moduleType); + var isLocked = gpo is GpoRuleConfigured.Enabled or GpoRuleConfigured.Disabled; + var isEnabled = gpo == GpoRuleConfigured.Enabled || (!isLocked && ModuleHelper.GetIsModuleEnabled(_generalSettings, moduleType)); + + FlyoutMenuItems.Add(new FlyoutMenuItem + { + Label = _resourceLoader.GetString(ModuleHelper.GetModuleLabelResourceName(moduleType)), + IsEnabled = isEnabled, + IsLocked = isLocked, + Tag = moduleType, + Icon = ModuleHelper.GetModuleTypeFluentIconName(moduleType), + EnabledChangedCallback = EnabledChangedOnUI, + }); + } + + private void EnabledChangedOnUI(FlyoutMenuItem item) + { + if (_coordinator.UpdateModuleEnabled(item.Tag, item.IsEnabled)) + { + _coordinator.NotifyUserSettingsInteraction(); + } + } + + private void ModuleEnabledChangedOnSettingsPage() + { + _generalSettings = _settingsRepository.SettingsConfig; + _generalSettings.AddEnabledModuleChangeNotification(ModuleEnabledChangedOnSettingsPage); + + foreach (var item in FlyoutMenuItems) + { + if (!item.IsLocked) + { + item.IsEnabled = ModuleHelper.GetIsModuleEnabled(_generalSettings, item.Tag); + } + } + } +} diff --git a/src/settings-ui/QuickAccess.UI/ViewModels/FlyoutMenuItem.cs b/src/settings-ui/QuickAccess.UI/ViewModels/FlyoutMenuItem.cs new file mode 100644 index 0000000000..91de44f137 --- /dev/null +++ b/src/settings-ui/QuickAccess.UI/ViewModels/FlyoutMenuItem.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.ComponentModel; +using System.Runtime.CompilerServices; +using ManagedCommon; + +namespace Microsoft.PowerToys.QuickAccess.ViewModels; + +public sealed class FlyoutMenuItem : INotifyPropertyChanged +{ + private bool _visible; + private bool _isEnabled; + + public string Label { get; set; } = string.Empty; + + public string Icon { get; set; } = string.Empty; + + public string ToolTip { get; set; } = string.Empty; + + public ModuleType Tag { get; set; } + + public bool IsLocked { get; set; } + + public bool IsEnabled + { + get => _isEnabled; + set + { + if (_isEnabled != value) + { + _isEnabled = value; + OnPropertyChanged(); + EnabledChangedCallback?.Invoke(this); + } + } + } + + public Action? EnabledChangedCallback { get; set; } + + public bool Visible + { + get => _visible; + set + { + if (_visible != value) + { + _visible = value; + OnPropertyChanged(); + } + } + } + + public event PropertyChangedEventHandler? PropertyChanged; + + private void OnPropertyChanged([CallerMemberName] string? propertyName = null) + { + PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName)); + } +} diff --git a/src/settings-ui/QuickAccess.UI/ViewModels/LauncherViewModel.cs b/src/settings-ui/QuickAccess.UI/ViewModels/LauncherViewModel.cs new file mode 100644 index 0000000000..8d34312c3d --- /dev/null +++ b/src/settings-ui/QuickAccess.UI/ViewModels/LauncherViewModel.cs @@ -0,0 +1,105 @@ +// 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.QuickAccess.Helpers; +using Microsoft.PowerToys.QuickAccess.Services; +using Microsoft.PowerToys.Settings.UI.Library; +using Microsoft.PowerToys.Settings.UI.Library.Helpers; +using Microsoft.PowerToys.Settings.UI.Library.Interfaces; +using Microsoft.Windows.ApplicationModel.Resources; + +namespace Microsoft.PowerToys.QuickAccess.ViewModels; + +public sealed class LauncherViewModel : Observable +{ + private readonly IQuickAccessCoordinator _coordinator; + private readonly ISettingsRepository _settingsRepository; + private readonly ResourceLoader _resourceLoader; + private GeneralSettings _generalSettings; + + public ObservableCollection FlyoutMenuItems { get; } + + public bool IsUpdateAvailable { get; private set; } + + public LauncherViewModel(IQuickAccessCoordinator coordinator) + { + _coordinator = coordinator; + var settingsUtils = new SettingsUtils(); + _settingsRepository = SettingsRepository.GetInstance(settingsUtils); + _generalSettings = _settingsRepository.SettingsConfig; + _generalSettings.AddEnabledModuleChangeNotification(ModuleEnabledChanged); + + _resourceLoader = Helpers.ResourceLoaderInstance.ResourceLoader; + FlyoutMenuItems = new ObservableCollection(); + + AddFlyoutMenuItem(ModuleType.ColorPicker); + AddFlyoutMenuItem(ModuleType.CmdPal); + AddFlyoutMenuItem(ModuleType.EnvironmentVariables); + AddFlyoutMenuItem(ModuleType.FancyZones); + AddFlyoutMenuItem(ModuleType.Hosts); + AddFlyoutMenuItem(ModuleType.PowerLauncher); + AddFlyoutMenuItem(ModuleType.PowerOCR); + AddFlyoutMenuItem(ModuleType.RegistryPreview); + AddFlyoutMenuItem(ModuleType.MeasureTool); + AddFlyoutMenuItem(ModuleType.ShortcutGuide); + AddFlyoutMenuItem(ModuleType.Workspaces); + + var updatingSettings = UpdatingSettings.LoadSettings() ?? new UpdatingSettings(); + IsUpdateAvailable = updatingSettings.State is UpdatingSettings.UpdatingState.ReadyToInstall or UpdatingSettings.UpdatingState.ReadyToDownload; + } + + private void AddFlyoutMenuItem(ModuleType moduleType) + { + if (ModuleHelper.GetModuleGpoConfiguration(moduleType) == GpoRuleConfigured.Disabled) + { + return; + } + + FlyoutMenuItems.Add(new FlyoutMenuItem + { + Label = _resourceLoader.GetString(ModuleHelper.GetModuleLabelResourceName(moduleType)), + Tag = moduleType, + Visible = ModuleHelper.GetIsModuleEnabled(_generalSettings, moduleType), + ToolTip = GetModuleToolTip(moduleType), + Icon = ModuleHelper.GetModuleTypeFluentIconName(moduleType), + }); + } + + private string GetModuleToolTip(ModuleType moduleType) + { + return moduleType switch + { + ModuleType.ColorPicker => SettingsRepository.GetInstance(new SettingsUtils()).SettingsConfig.Properties.ActivationShortcut.ToString(), + ModuleType.FancyZones => SettingsRepository.GetInstance(new SettingsUtils()).SettingsConfig.Properties.FancyzonesEditorHotkey.Value.ToString(), + ModuleType.PowerLauncher => SettingsRepository.GetInstance(new SettingsUtils()).SettingsConfig.Properties.OpenPowerLauncher.ToString(), + ModuleType.PowerOCR => SettingsRepository.GetInstance(new SettingsUtils()).SettingsConfig.Properties.ActivationShortcut.ToString(), + ModuleType.Workspaces => SettingsRepository.GetInstance(new SettingsUtils()).SettingsConfig.Properties.Hotkey.Value.ToString(), + ModuleType.MeasureTool => SettingsRepository.GetInstance(new SettingsUtils()).SettingsConfig.Properties.ActivationShortcut.ToString(), + ModuleType.ShortcutGuide => GetShortcutGuideToolTip(), + _ => string.Empty, + }; + } + + private string GetShortcutGuideToolTip() + { + var shortcutGuideSettings = SettingsRepository.GetInstance(new SettingsUtils()).SettingsConfig; + return shortcutGuideSettings.Properties.UseLegacyPressWinKeyBehavior.Value + ? "Win" + : shortcutGuideSettings.Properties.OpenShortcutGuide.ToString(); + } + + private void ModuleEnabledChanged() + { + _generalSettings = _settingsRepository.SettingsConfig; + _generalSettings.AddEnabledModuleChangeNotification(ModuleEnabledChanged); + foreach (var item in FlyoutMenuItems) + { + item.Visible = ModuleHelper.GetIsModuleEnabled(_generalSettings, item.Tag); + } + } +}