Signed-off-by: Shuai Yuan <shuai.yuan.zju@gmail.com>
This commit is contained in:
Shuai Yuan
2025-11-20 15:33:02 +08:00
parent ed254b60cc
commit 8de7110918
14 changed files with 1224 additions and 0 deletions

View File

@@ -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,
};
}
}

View File

@@ -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");
}

View File

@@ -0,0 +1,98 @@
<Page
x:Class="Microsoft.PowerToys.QuickAccess.Flyout.AppsListPage"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:viewModels="using:Microsoft.PowerToys.QuickAccess.ViewModels"
mc:Ignorable="d">
<Grid Background="{ThemeResource LayerOnAcrylicFillColorDefaultBrush}">
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
<RowDefinition Height="*" />
</Grid.RowDefinitions>
<Grid Padding="24,32,24,0">
<TextBlock
x:Uid="AllAppsTxt"
VerticalAlignment="Center"
Style="{StaticResource BodyStrongTextBlockStyle}" />
<Button
x:Uid="BackBtn"
Padding="8,4,8,4"
HorizontalAlignment="Right"
VerticalAlignment="Center"
Click="BackButton_Click">
<Button.Content>
<StackPanel
VerticalAlignment="Center"
Orientation="Horizontal"
Spacing="12">
<FontIcon
Margin="0,2,0,0"
FontSize="12"
Glyph="&#xe76b;" />
<TextBlock x:Uid="BackLabel" Style="{StaticResource CaptionTextBlockStyle}" />
</StackPanel>
</Button.Content>
</Button>
</Grid>
<ListView
Grid.Row="1"
Margin="0,16,0,0"
ItemsSource="{x:Bind ViewModel.FlyoutMenuItems}"
SelectionMode="None">
<ListView.ItemsPanel>
<ItemsPanelTemplate>
<StackPanel Padding="0,0,0,16" Orientation="Vertical" />
</ItemsPanelTemplate>
</ListView.ItemsPanel>
<ListView.ItemTemplate>
<DataTemplate x:DataType="viewModels:FlyoutMenuItem">
<Grid Height="40" Padding="24,0,24,0">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto" />
<ColumnDefinition Width="*" />
<ColumnDefinition Width="Auto" />
<ColumnDefinition Width="Auto" />
</Grid.ColumnDefinitions>
<Image
Width="20"
Margin="0,0,16,0"
VerticalAlignment="Center">
<Image.Source>
<BitmapImage UriSource="{x:Bind Icon, Mode=OneWay}" />
</Image.Source>
</Image>
<TextBlock
Grid.Column="1"
VerticalAlignment="Center"
Text="{x:Bind Label, Mode=OneWay}"
TextTrimming="CharacterEllipsis" />
<FontIcon
Grid.Column="2"
Width="20"
VerticalAlignment="Center"
FontSize="16"
Glyph="&#xE72E;"
Visibility="{x:Bind IsLocked, Converter={StaticResource ReverseBoolToVisibilityConverter}, ConverterParameter=True, Mode=OneWay}">
<ToolTipService.ToolTip>
<TextBlock x:Uid="GPO_SettingIsManaged_ToolTip" TextWrapping="WrapWholeWords" />
</ToolTipService.ToolTip>
</FontIcon>
<ToggleSwitch
Grid.Column="3"
HorizontalAlignment="Right"
VerticalAlignment="Center"
AutomationProperties.Name="{x:Bind Label, Mode=OneWay}"
IsEnabled="{x:Bind IsLocked, Converter={StaticResource BoolNegationConverter}, ConverterParameter=True, Mode=OneWay}"
IsOn="{x:Bind IsEnabled, Mode=TwoWay}"
OffContent=""
OnContent=""
Style="{StaticResource RightAlignedCompactToggleSwitchStyle}" />
</Grid>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
</Grid>
</Page>

View File

@@ -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 });
}
}

View File

@@ -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);

View File

@@ -0,0 +1,165 @@
<Page
x:Class="Microsoft.PowerToys.QuickAccess.Flyout.LaunchPage"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:animatedVisuals="using:Microsoft.UI.Xaml.Controls.AnimatedVisuals"
xmlns:controls="using:Microsoft.PowerToys.Settings.UI.Controls"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:tkcontrols="using:CommunityToolkit.WinUI.Controls"
xmlns:tkconverters="using:CommunityToolkit.WinUI.Converters"
xmlns:ui="using:CommunityToolkit.WinUI"
xmlns:viewModels="using:Microsoft.PowerToys.QuickAccess.ViewModels"
mc:Ignorable="d">
<Page.Resources>
<Style
x:Key="FlyoutButtonStyle"
BasedOn="{StaticResource SubtleButtonStyle}"
TargetType="Button">
<Setter Property="Padding" Value="6" />
<Setter Property="Width" Value="32" />
<Setter Property="Height" Value="32" />
<Setter Property="FontFamily" Value="{ThemeResource SymbolThemeFontFamily}" />
</Style>
</Page.Resources>
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="*" />
<RowDefinition Height="48" />
</Grid.RowDefinitions>
<Grid
Background="{ThemeResource LayerOnAcrylicFillColorDefaultBrush}"
BorderBrush="{ThemeResource CardStrokeColorDefaultBrush}"
BorderThickness="0,0,0,1">
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
<RowDefinition Height="*" />
</Grid.RowDefinitions>
<Grid Padding="36,32,36,0">
<TextBlock
x:Uid="QuickAccessTxt"
VerticalAlignment="Center"
Style="{StaticResource BodyStrongTextBlockStyle}" />
<Button
x:Uid="MoreBtn"
Padding="8,4,8,4"
HorizontalAlignment="Right"
VerticalAlignment="Center"
Click="AllAppButton_Click">
<Button.Content>
<StackPanel
VerticalAlignment="Center"
Orientation="Horizontal"
Spacing="12">
<TextBlock x:Uid="MoreLabel" Style="{StaticResource CaptionTextBlockStyle}" />
<FontIcon
Margin="0,2,0,0"
FontSize="12"
Glyph="&#xe76c;" />
</StackPanel>
</Button.Content>
</Button>
</Grid>
<Grid Grid.Row="1">
<ScrollViewer>
<ItemsControl
Margin="12,26,12,24"
HorizontalAlignment="Stretch"
VerticalAlignment="Top"
ItemsSource="{x:Bind ViewModel.FlyoutMenuItems}"
TabNavigation="Local">
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<tkcontrols:WrapPanel HorizontalAlignment="Stretch" VerticalSpacing="12" />
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
<ItemsControl.ItemTemplate>
<DataTemplate x:DataType="viewModels:FlyoutMenuItem">
<controls:FlyoutMenuButton
AutomationProperties.Name="{x:Bind Label}"
Click="ModuleButton_Click"
Tag="{x:Bind Tag}"
Visibility="{x:Bind Visible, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged, Converter={StaticResource BoolToVisibilityConverter}}">
<controls:FlyoutMenuButton.Content>
<TextBlock
Style="{StaticResource CaptionTextBlockStyle}"
Text="{x:Bind Label}"
TextAlignment="Center"
TextWrapping="Wrap" />
</controls:FlyoutMenuButton.Content>
<controls:FlyoutMenuButton.Icon>
<Image>
<Image.Source>
<BitmapImage UriSource="{x:Bind Icon, Mode=OneWay}" />
</Image.Source>
</Image>
</controls:FlyoutMenuButton.Icon>
<ToolTipService.ToolTip>
<ToolTip Content="{x:Bind ToolTip}" Visibility="{x:Bind ToolTip, Converter={StaticResource StringVisibilityConverter}}" />
</ToolTipService.ToolTip>
</controls:FlyoutMenuButton>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
</ScrollViewer>
</Grid>
</Grid>
<Grid Grid.Row="2">
<InfoBar
x:Uid="UpdateAvailableInfoBar"
IsClosable="False"
IsOpen="{x:Bind ViewModel.IsUpdateAvailable, Mode=OneWay}"
Severity="Success"
Tapped="UpdateInfoBar_Tapped" />
<StackPanel
Grid.Row="1"
Margin="0,0,12,0"
HorizontalAlignment="Right"
Orientation="Horizontal"
Spacing="8">
<Button
x:Name="DocsBtn"
x:Uid="DocsBtn"
Click="DocsBtn_Click"
Content="{ui:FontIcon Glyph=&#xE8A5;,
FontSize=16}"
Style="{StaticResource FlyoutButtonStyle}">
<ToolTipService.ToolTip>
<TextBlock x:Uid="DocsTooltip" />
</ToolTipService.ToolTip>
</Button>
<Button
x:Name="ReportBugBtn"
x:Uid="BugReportBtn"
Click="ReportBugBtn_Click"
Content="{ui:FontIcon Glyph=&#xebe8;,
FontSize=16}"
Style="{StaticResource FlyoutButtonStyle}">
<ToolTipService.ToolTip>
<TextBlock x:Uid="BugReportTooltip" />
</ToolTipService.ToolTip>
</Button>
<Button
x:Name="SettingsBtn"
x:Uid="SettingsBtn"
Click="SettingsBtn_Click"
Style="{StaticResource FlyoutButtonStyle}">
<ToolTipService.ToolTip>
<TextBlock x:Uid="SettingsTooltip" />
</ToolTipService.ToolTip>
<AnimatedIcon x:Name="SettingsAnimatedIcon">
<AnimatedIcon.Source>
<animatedVisuals:AnimatedSettingsVisualSource />
</AnimatedIcon.Source>
<AnimatedIcon.FallbackIconSource>
<SymbolIconSource Symbol="Setting" />
</AnimatedIcon.FallbackIconSource>
</AnimatedIcon>
</Button>
</StackPanel>
</Grid>
</Grid>
</Page>

View File

@@ -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<EnvironmentVariablesSettings>.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<HostsSettings>.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();
}
}

View File

@@ -0,0 +1,13 @@
<Page
x:Class="Microsoft.PowerToys.QuickAccess.Flyout.ShellPage"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
Loaded="Page_Loaded"
mc:Ignorable="d">
<Grid>
<Frame x:Name="ContentFrame" />
</Grid>
</Page>

View File

@@ -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;
/// <summary>
/// Hosts the flyout navigation frame.
/// </summary>
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 });
}
}

View File

@@ -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<bool> ShowDocumentationAsync();
void NotifyUserSettingsInteraction();
bool UpdateModuleEnabled(ModuleType moduleType, bool isEnabled);
void OnModuleLaunched(ModuleType moduleType);
}

View File

@@ -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<bool> 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<GeneralSettings>.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;
}
}
}

View File

@@ -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<GeneralSettings> _settingsRepository;
private readonly ResourceLoader _resourceLoader;
private GeneralSettings _generalSettings;
public ObservableCollection<FlyoutMenuItem> FlyoutMenuItems { get; }
public AllAppsViewModel(IQuickAccessCoordinator coordinator)
{
_coordinator = coordinator;
var settingsUtils = new SettingsUtils();
_settingsRepository = SettingsRepository<GeneralSettings>.GetInstance(settingsUtils);
_generalSettings = _settingsRepository.SettingsConfig;
_generalSettings.AddEnabledModuleChangeNotification(ModuleEnabledChangedOnSettingsPage);
_resourceLoader = Helpers.ResourceLoaderInstance.ResourceLoader;
FlyoutMenuItems = new ObservableCollection<FlyoutMenuItem>();
foreach (ModuleType moduleType in Enum.GetValues<ModuleType>())
{
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);
}
}
}
}

View File

@@ -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<FlyoutMenuItem>? 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));
}
}

View File

@@ -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<GeneralSettings> _settingsRepository;
private readonly ResourceLoader _resourceLoader;
private GeneralSettings _generalSettings;
public ObservableCollection<FlyoutMenuItem> FlyoutMenuItems { get; }
public bool IsUpdateAvailable { get; private set; }
public LauncherViewModel(IQuickAccessCoordinator coordinator)
{
_coordinator = coordinator;
var settingsUtils = new SettingsUtils();
_settingsRepository = SettingsRepository<GeneralSettings>.GetInstance(settingsUtils);
_generalSettings = _settingsRepository.SettingsConfig;
_generalSettings.AddEnabledModuleChangeNotification(ModuleEnabledChanged);
_resourceLoader = Helpers.ResourceLoaderInstance.ResourceLoader;
FlyoutMenuItems = new ObservableCollection<FlyoutMenuItem>();
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<ColorPickerSettings>.GetInstance(new SettingsUtils()).SettingsConfig.Properties.ActivationShortcut.ToString(),
ModuleType.FancyZones => SettingsRepository<FancyZonesSettings>.GetInstance(new SettingsUtils()).SettingsConfig.Properties.FancyzonesEditorHotkey.Value.ToString(),
ModuleType.PowerLauncher => SettingsRepository<PowerLauncherSettings>.GetInstance(new SettingsUtils()).SettingsConfig.Properties.OpenPowerLauncher.ToString(),
ModuleType.PowerOCR => SettingsRepository<PowerOcrSettings>.GetInstance(new SettingsUtils()).SettingsConfig.Properties.ActivationShortcut.ToString(),
ModuleType.Workspaces => SettingsRepository<WorkspacesSettings>.GetInstance(new SettingsUtils()).SettingsConfig.Properties.Hotkey.Value.ToString(),
ModuleType.MeasureTool => SettingsRepository<MeasureToolSettings>.GetInstance(new SettingsUtils()).SettingsConfig.Properties.ActivationShortcut.ToString(),
ModuleType.ShortcutGuide => GetShortcutGuideToolTip(),
_ => string.Empty,
};
}
private string GetShortcutGuideToolTip()
{
var shortcutGuideSettings = SettingsRepository<ShortcutGuideSettings>.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);
}
}
}