mirror of
https://github.com/microsoft/PowerToys.git
synced 2026-04-04 18:26:39 +02:00
Settings Flyout improvement (#43840)
<!-- Enter a brief description/summary of your PR here. What does it fix/what does it change/how was it tested (even manually, if necessary)? --> ## 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. <!-- Please review the items on the PR checklist before submitting--> ## PR Checklist - [x] Closes: #43694 <!-- - [ ] Closes: #yyy (add separate lines for additional resolved issues) --> - [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 <!-- Provide a more detailed description of the PR, other things fixed, or any additional comments/features here --> ## Detailed Description of the Pull Request / Additional comments <img width="290" height="420" alt="image" src="https://github.com/user-attachments/assets/7390a706-171c-479f-a4a2-999b18cfc65f" /> <img width="290" height="420" alt="image" src="https://github.com/user-attachments/assets/99e99bc9-b1a3-46c6-b648-81e3048dec1b" /> <img width="490" height="350" alt="image" src="https://github.com/user-attachments/assets/2cce4ad6-a54e-4587-87b7-fdc7fba1f54f" /> <!-- Describe how you validated the behavior. Add automated tests wherever possible, but list manual validation steps taken as well --> ## Validation Steps Performed --------- Signed-off-by: Shawn Yuan (from Dev Box) <shuaiyuan@microsoft.com> Signed-off-by: Shuai Yuan <shuai.yuan.zju@gmail.com> Co-authored-by: Niels Laute <niels.laute@live.nl>
This commit is contained in:
49
src/settings-ui/QuickAccess.UI/Helpers/ModuleGpoHelper.cs
Normal file
49
src/settings-ui/QuickAccess.UI/Helpers/ModuleGpoHelper.cs
Normal file
@@ -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,
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -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");
|
||||
}
|
||||
89
src/settings-ui/QuickAccess.UI/PowerToys.QuickAccess.csproj
Normal file
89
src/settings-ui/QuickAccess.UI/PowerToys.QuickAccess.csproj
Normal file
@@ -0,0 +1,89 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
<Import Project="..\..\Common.Dotnet.CsWinRT.props" />
|
||||
<Import Project="..\..\Common.SelfContained.props" />
|
||||
|
||||
<PropertyGroup>
|
||||
<OutputType>WinExe</OutputType>
|
||||
<TargetFramework>net9.0-windows10.0.26100.0</TargetFramework>
|
||||
<RootNamespace>Microsoft.PowerToys.QuickAccess</RootNamespace>
|
||||
<AssemblyName>PowerToys.QuickAccess</AssemblyName>
|
||||
<UseWinUI>true</UseWinUI>
|
||||
<WindowsPackageType>None</WindowsPackageType>
|
||||
<WindowsAppSDKSelfContained>true</WindowsAppSDKSelfContained>
|
||||
<EnablePreviewMsixTooling>true</EnablePreviewMsixTooling>
|
||||
<AppendTargetFrameworkToOutputPath>false</AppendTargetFrameworkToOutputPath>
|
||||
<AppendRuntimeIdentifierToOutputPath>false</AppendRuntimeIdentifierToOutputPath>
|
||||
<ApplicationManifest>app.manifest</ApplicationManifest>
|
||||
<OutputPath>..\..\..\$(Platform)\$(Configuration)\WinUI3Apps</OutputPath>
|
||||
<EnableDefaultPageItems>false</EnableDefaultPageItems>
|
||||
<EnableDefaultApplicationDefinition>false</EnableDefaultApplicationDefinition>
|
||||
<Nullable>enable</Nullable>
|
||||
<ProjectPriFileName>PowerToys.QuickAccess.pri</ProjectPriFileName>
|
||||
</PropertyGroup>
|
||||
|
||||
<PropertyGroup>
|
||||
<CsWinRTIncludes>PowerToys.GPOWrapper</CsWinRTIncludes>
|
||||
<CsWinRTGeneratedFilesDir>$(OutDir)</CsWinRTGeneratedFilesDir>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ApplicationDefinition Include="QuickAccessXaml\App.xaml" />
|
||||
<Page Include="QuickAccessXaml\MainWindow.xaml" />
|
||||
<Page Include="QuickAccessXaml\Flyout\ShellPage.xaml" />
|
||||
<Page Include="QuickAccessXaml\Flyout\LaunchPage.xaml" />
|
||||
<Page Include="QuickAccessXaml\Flyout\AppsListPage.xaml" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<Page Include="..\Settings.UI\SettingsXAML\Styles\Button.xaml">
|
||||
<Link>Resources\Styles\Button.xaml</Link>
|
||||
</Page>
|
||||
<Page Include="..\Settings.UI\SettingsXAML\Styles\TextBlock.xaml">
|
||||
<Link>Resources\Styles\TextBlock.xaml</Link>
|
||||
</Page>
|
||||
<Page Include="..\Settings.UI\SettingsXAML\Themes\Colors.xaml">
|
||||
<Link>Resources\Themes\Colors.xaml</Link>
|
||||
</Page>
|
||||
<Page Include="..\Settings.UI\SettingsXAML\Themes\Generic.xaml">
|
||||
<Link>Resources\Themes\Generic.xaml</Link>
|
||||
</Page>
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PRIResource Include="..\Settings.UI\Strings\en-us\Resources.resw">
|
||||
<Link>Strings\en-us\Resources.resw</Link>
|
||||
</PRIResource>
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<Content Include="..\Settings.UI\Assets\Settings\Icons\**\*">
|
||||
<Link>Assets\Settings\Icons\%(RecursiveDir)%(Filename)%(Extension)</Link>
|
||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||
</Content>
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="CommunityToolkit.WinUI.Animations" />
|
||||
<PackageReference Include="CommunityToolkit.WinUI.Controls.Primitives" />
|
||||
<PackageReference Include="CommunityToolkit.WinUI.Controls.SettingsControls" />
|
||||
<PackageReference Include="CommunityToolkit.WinUI.Controls.Segmented" />
|
||||
<PackageReference Include="CommunityToolkit.WinUI.Converters" />
|
||||
<PackageReference Include="CommunityToolkit.WinUI.Extensions" />
|
||||
<PackageReference Include="Microsoft.WindowsAppSDK" />
|
||||
<PackageReference Include="WinUIEx" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\..\common\GPOWrapper\GPOWrapper.vcxproj" />
|
||||
<ProjectReference Include="..\..\common\ManagedCommon\ManagedCommon.csproj" />
|
||||
<ProjectReference Include="..\..\common\ManagedTelemetry\Telemetry\ManagedTelemetry.csproj" />
|
||||
<ProjectReference Include="..\..\common\interop\PowerToys.Interop.vcxproj" />
|
||||
<ProjectReference Include="..\..\common\Common.UI\Common.UI.csproj" />
|
||||
<ProjectReference Include="..\Settings.UI.Library\Settings.UI.Library.csproj" />
|
||||
<ProjectReference Include="..\Settings.UI.Controls\Settings.UI.Controls.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<Manifest Include="$(ApplicationManifest)" />
|
||||
</ItemGroup>
|
||||
</Project>
|
||||
62
src/settings-ui/QuickAccess.UI/QuickAccessLaunchContext.cs
Normal file
62
src/settings-ui/QuickAccess.UI/QuickAccessLaunchContext.cs
Normal 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.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;
|
||||
}
|
||||
}
|
||||
60
src/settings-ui/QuickAccess.UI/QuickAccessXAML/App.xaml
Normal file
60
src/settings-ui/QuickAccess.UI/QuickAccessXAML/App.xaml
Normal file
@@ -0,0 +1,60 @@
|
||||
<?xml version="1.0" encoding="utf-8" ?>
|
||||
<Application
|
||||
x:Class="Microsoft.PowerToys.QuickAccess.App"
|
||||
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:tkconverters="using:CommunityToolkit.WinUI.Converters">
|
||||
<Application.Resources>
|
||||
<ResourceDictionary>
|
||||
<ResourceDictionary.MergedDictionaries>
|
||||
<XamlControlsResources xmlns="using:Microsoft.UI.Xaml.Controls" />
|
||||
<ResourceDictionary Source="/Resources/Styles/Button.xaml" />
|
||||
<ResourceDictionary Source="/Resources/Styles/TextBlock.xaml" />
|
||||
<ResourceDictionary Source="/Resources/Themes/Colors.xaml" />
|
||||
</ResourceDictionary.MergedDictionaries>
|
||||
|
||||
<ResourceDictionary.ThemeDictionaries>
|
||||
<ResourceDictionary x:Key="Default">
|
||||
<SolidColorBrush
|
||||
x:Key="LayerOnAcrylicFillColorDefaultBrush"
|
||||
Opacity="0.7"
|
||||
Color="#FFFFFFFF" />
|
||||
<SolidColorBrush x:Key="CardStrokeColorDefaultBrush" Color="#0F000000" />
|
||||
<SolidColorBrush x:Key="CardBackgroundFillColorDefaultBrush" Color="#B3FFFFFF" />
|
||||
</ResourceDictionary>
|
||||
<ResourceDictionary x:Key="Light">
|
||||
<SolidColorBrush
|
||||
x:Key="LayerOnAcrylicFillColorDefaultBrush"
|
||||
Opacity="0.7"
|
||||
Color="#FFFFFFFF" />
|
||||
<SolidColorBrush x:Key="CardStrokeColorDefaultBrush" Color="#0F000000" />
|
||||
<SolidColorBrush x:Key="CardBackgroundFillColorDefaultBrush" Color="#B3FFFFFF" />
|
||||
</ResourceDictionary>
|
||||
<ResourceDictionary x:Key="Dark">
|
||||
<SolidColorBrush
|
||||
x:Key="LayerOnAcrylicFillColorDefaultBrush"
|
||||
Opacity="0.6"
|
||||
Color="#FF000000" />
|
||||
<SolidColorBrush x:Key="CardStrokeColorDefaultBrush" Color="#0FFFFFFF" />
|
||||
<SolidColorBrush x:Key="CardBackgroundFillColorDefaultBrush" Color="#0DFFFFFF" />
|
||||
</ResourceDictionary>
|
||||
<ResourceDictionary x:Key="HighContrast">
|
||||
<SolidColorBrush x:Key="LayerOnAcrylicFillColorDefaultBrush" Color="{ThemeResource SystemColorWindowColor}" />
|
||||
<SolidColorBrush x:Key="CardStrokeColorDefaultBrush" Color="{ThemeResource SystemColorWindowTextColor}" />
|
||||
<SolidColorBrush x:Key="CardBackgroundFillColorDefaultBrush" Color="{ThemeResource SystemColorWindowColor}" />
|
||||
</ResourceDictionary>
|
||||
</ResourceDictionary.ThemeDictionaries>
|
||||
|
||||
<tkconverters:BoolToVisibilityConverter
|
||||
x:Key="ReverseBoolToVisibilityConverter"
|
||||
FalseValue="Visible"
|
||||
TrueValue="Collapsed" />
|
||||
<tkconverters:BoolToVisibilityConverter
|
||||
x:Key="BoolToVisibilityConverter"
|
||||
FalseValue="Collapsed"
|
||||
TrueValue="Visible" />
|
||||
<tkconverters:BoolNegationConverter x:Key="BoolNegationConverter" />
|
||||
<tkconverters:StringVisibilityConverter x:Key="StringVisibilityConverter" />
|
||||
</ResourceDictionary>
|
||||
</Application.Resources>
|
||||
</Application>
|
||||
36
src/settings-ui/QuickAccess.UI/QuickAccessXAML/App.xaml.cs
Normal file
36
src/settings-ui/QuickAccess.UI/QuickAccessXAML/App.xaml.cs
Normal file
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,83 @@
|
||||
<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:controls="using:Microsoft.PowerToys.Settings.UI.Controls"
|
||||
xmlns:converters="using:Microsoft.PowerToys.Settings.UI.Controls.Converters"
|
||||
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||
xmlns:local="using:Microsoft.PowerToys.QuickAccess.Flyout"
|
||||
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||
xmlns:viewModels="using:Microsoft.PowerToys.QuickAccess.ViewModels"
|
||||
mc:Ignorable="d">
|
||||
<Page.Resources>
|
||||
<converters:EnumToBooleanConverter x:Key="EnumToBooleanConverter" />
|
||||
</Page.Resources>
|
||||
<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}" />
|
||||
<StackPanel
|
||||
HorizontalAlignment="Right"
|
||||
Orientation="Horizontal"
|
||||
Spacing="8">
|
||||
<Button
|
||||
x:Uid="Dashboard_SortBy"
|
||||
VerticalAlignment="Center"
|
||||
Foreground="{ThemeResource TextFillColorSecondaryBrush}"
|
||||
Style="{StaticResource SubtleButtonStyle}">
|
||||
<ToolTipService.ToolTip>
|
||||
<TextBlock x:Uid="Dashboard_SortBy_ToolTip" />
|
||||
</ToolTipService.ToolTip>
|
||||
<Button.Content>
|
||||
<FontIcon FontSize="14" Glyph="" />
|
||||
</Button.Content>
|
||||
<Button.Flyout>
|
||||
<MenuFlyout Placement="BottomEdgeAlignedRight">
|
||||
<ToggleMenuFlyoutItem
|
||||
x:Uid="Dashboard_SortAlphabetical"
|
||||
Click="SortAlphabetical_Click"
|
||||
IsChecked="{x:Bind ViewModel.DashboardSortOrder, Mode=OneWay, Converter={StaticResource EnumToBooleanConverter}, ConverterParameter=Alphabetical}" />
|
||||
<ToggleMenuFlyoutItem
|
||||
x:Uid="Dashboard_SortByStatus"
|
||||
Click="SortByStatus_Click"
|
||||
IsChecked="{x:Bind ViewModel.DashboardSortOrder, Mode=OneWay, Converter={StaticResource EnumToBooleanConverter}, ConverterParameter=ByStatus}" />
|
||||
</MenuFlyout>
|
||||
</Button.Flyout>
|
||||
</Button>
|
||||
<Button
|
||||
x:Uid="BackBtn"
|
||||
Padding="8,4,8,4"
|
||||
VerticalAlignment="Center"
|
||||
Click="BackButton_Click">
|
||||
<Button.Content>
|
||||
<StackPanel
|
||||
VerticalAlignment="Center"
|
||||
Orientation="Horizontal"
|
||||
Spacing="12">
|
||||
<FontIcon
|
||||
Margin="0,2,0,0"
|
||||
FontSize="12"
|
||||
Glyph="" />
|
||||
<TextBlock x:Uid="BackLabel" Style="{StaticResource CaptionTextBlockStyle}" />
|
||||
</StackPanel>
|
||||
</Button.Content>
|
||||
</Button>
|
||||
</StackPanel>
|
||||
</Grid>
|
||||
<Grid Grid.Row="1">
|
||||
<ScrollViewer HorizontalScrollBarVisibility="Disabled" VerticalScrollBarVisibility="Auto">
|
||||
<controls:ModuleList
|
||||
Margin="8,12,12,12"
|
||||
DividerThickness="0,0,0,0"
|
||||
IsItemClickable="False"
|
||||
ItemsSource="{x:Bind ViewModel.FlyoutMenuItems, Mode=OneWay}" />
|
||||
</ScrollViewer>
|
||||
</Grid>
|
||||
</Grid>
|
||||
</Page>
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
@@ -0,0 +1,131 @@
|
||||
<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="" />
|
||||
</StackPanel>
|
||||
</Button.Content>
|
||||
</Button>
|
||||
</Grid>
|
||||
<Grid Grid.Row="1">
|
||||
<ScrollViewer>
|
||||
<controls:QuickAccessList
|
||||
Margin="12,26,12,24"
|
||||
HorizontalAlignment="Stretch"
|
||||
VerticalAlignment="Top"
|
||||
ItemsSource="{x:Bind ViewModel.FlyoutMenuItems}"
|
||||
TabNavigation="Local" />
|
||||
</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=,
|
||||
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=,
|
||||
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>
|
||||
@@ -0,0 +1,80 @@
|
||||
// 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 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 });
|
||||
}
|
||||
|
||||
public void ReportBugBtn_Click(object sender, RoutedEventArgs e)
|
||||
{
|
||||
_coordinator?.ReportBug();
|
||||
}
|
||||
|
||||
private void UpdateInfoBar_Tapped(object sender, TappedRoutedEventArgs e)
|
||||
{
|
||||
_coordinator?.OpenGeneralSettingsForUpdates();
|
||||
}
|
||||
}
|
||||
@@ -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>
|
||||
@@ -0,0 +1,68 @@
|
||||
// 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 });
|
||||
}
|
||||
|
||||
internal void RefreshIfAppsList()
|
||||
{
|
||||
if (ContentFrame.Content is AppsListPage appsListPage)
|
||||
{
|
||||
appsListPage.ViewModel?.RefreshSettings();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,36 @@
|
||||
<winuiEx:WindowEx
|
||||
x:Class="Microsoft.PowerToys.QuickAccess.MainWindow"
|
||||
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:flyout="using:Microsoft.PowerToys.QuickAccess.Flyout"
|
||||
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||
xmlns:winuiEx="using:WinUIEx"
|
||||
Title="PowerToys Quick Access (Preview)"
|
||||
Width="400"
|
||||
Height="516"
|
||||
MinWidth="400"
|
||||
MinHeight="516"
|
||||
IsAlwaysOnTop="True"
|
||||
IsMaximizable="False"
|
||||
IsMinimizable="False"
|
||||
IsResizable="False"
|
||||
IsShownInSwitchers="False"
|
||||
IsTitleBarVisible="False"
|
||||
mc:Ignorable="d">
|
||||
<winuiEx:WindowEx.Backdrop>
|
||||
<winuiEx:AcrylicSystemBackdrop
|
||||
DarkFallbackColor="#1c1c1c"
|
||||
DarkLuminosityOpacity="0.96"
|
||||
DarkTintColor="#202020"
|
||||
DarkTintOpacity="0.5"
|
||||
LightFallbackColor="#EEEEEE"
|
||||
LightLuminosityOpacity="0.90"
|
||||
LightTintColor="#F3F3F3"
|
||||
LightTintOpacity="0" />
|
||||
</winuiEx:WindowEx.Backdrop>
|
||||
|
||||
<Grid>
|
||||
<flyout:ShellPage x:Name="ShellHost" />
|
||||
</Grid>
|
||||
</winuiEx:WindowEx>
|
||||
@@ -0,0 +1,732 @@
|
||||
// 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.Runtime.InteropServices;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using ManagedCommon;
|
||||
using Microsoft.PowerToys.QuickAccess.Services;
|
||||
using Microsoft.PowerToys.QuickAccess.ViewModels;
|
||||
using Microsoft.UI.Dispatching;
|
||||
using Microsoft.UI.Windowing;
|
||||
using Microsoft.UI.Xaml;
|
||||
using Windows.Graphics;
|
||||
using WinRT.Interop;
|
||||
using WinUIEx;
|
||||
|
||||
namespace Microsoft.PowerToys.QuickAccess;
|
||||
|
||||
public sealed partial class MainWindow : WindowEx, IDisposable
|
||||
{
|
||||
private readonly QuickAccessLaunchContext _launchContext;
|
||||
private readonly DispatcherQueue _dispatcherQueue;
|
||||
private readonly IntPtr _hwnd;
|
||||
private readonly AppWindow? _appWindow;
|
||||
private readonly LauncherViewModel _launcherViewModel;
|
||||
private readonly AllAppsViewModel _allAppsViewModel;
|
||||
private readonly QuickAccessCoordinator _coordinator;
|
||||
private bool _disposed;
|
||||
private EventWaitHandle? _showEvent;
|
||||
private EventWaitHandle? _exitEvent;
|
||||
private ManualResetEventSlim? _listenerShutdownEvent;
|
||||
private Thread? _showListenerThread;
|
||||
private Thread? _exitListenerThread;
|
||||
private bool _isWindowCloaked;
|
||||
private bool _initialActivationHandled;
|
||||
private bool _isPrimed;
|
||||
|
||||
// Prevent auto-hide until the window actually gained focus once.
|
||||
private bool _hasSeenInteractiveActivation;
|
||||
private bool _isVisible;
|
||||
private IntPtr _mouseHook;
|
||||
private LowLevelMouseProc? _mouseHookDelegate;
|
||||
private CancellationTokenSource? _trimCts;
|
||||
|
||||
private const int DefaultWidth = 320;
|
||||
private const int DefaultHeight = 480;
|
||||
private const int DwmWaCloak = 13;
|
||||
private const int GwlStyle = -16;
|
||||
private const int GwlExStyle = -20;
|
||||
private const int SwHide = 0;
|
||||
private const int SwShow = 5;
|
||||
private const int SwShowNoActivate = 8;
|
||||
private const uint SwpShowWindow = 0x0040;
|
||||
private const uint SwpNoZorder = 0x0004;
|
||||
private const uint SwpNoSize = 0x0001;
|
||||
private const uint SwpNoMove = 0x0002;
|
||||
private const uint SwpNoActivate = 0x0010;
|
||||
private const uint SwpFrameChanged = 0x0020;
|
||||
private const long WsSysmenu = 0x00080000L;
|
||||
private const long WsMinimizeBox = 0x00020000L;
|
||||
private const long WsMaximizeBox = 0x00010000L;
|
||||
private const long WsExToolWindow = 0x00000080L;
|
||||
private const uint MonitorDefaulttonearest = 0x00000002;
|
||||
private static readonly IntPtr HwndTopmost = new(-1);
|
||||
private static readonly IntPtr HwndBottom = new(1);
|
||||
|
||||
public MainWindow(QuickAccessLaunchContext launchContext)
|
||||
{
|
||||
InitializeComponent();
|
||||
_launchContext = launchContext;
|
||||
_dispatcherQueue = DispatcherQueue.GetForCurrentThread();
|
||||
_hwnd = WindowNative.GetWindowHandle(this);
|
||||
_appWindow = InitializeAppWindow(_hwnd);
|
||||
Title = "PowerToys Quick Access (Preview)";
|
||||
|
||||
_coordinator = new QuickAccessCoordinator(this, _launchContext);
|
||||
_launcherViewModel = new LauncherViewModel(_coordinator);
|
||||
_allAppsViewModel = new AllAppsViewModel(_coordinator);
|
||||
ShellHost.Initialize(_coordinator, _launcherViewModel, _allAppsViewModel);
|
||||
|
||||
CustomizeWindowChrome();
|
||||
HideFromTaskbar();
|
||||
HideWindow();
|
||||
InitializeEventListeners();
|
||||
Closed += OnClosed;
|
||||
Activated += OnActivated;
|
||||
}
|
||||
|
||||
private AppWindow? InitializeAppWindow(IntPtr hwnd)
|
||||
{
|
||||
var windowId = Microsoft.UI.Win32Interop.GetWindowIdFromWindow(hwnd);
|
||||
return AppWindow.GetFromWindowId(windowId);
|
||||
}
|
||||
|
||||
private void HideWindow()
|
||||
{
|
||||
if (_hwnd != IntPtr.Zero)
|
||||
{
|
||||
var cloaked = CloakWindow();
|
||||
|
||||
if (!ShowWindowNative(_hwnd, SwHide) && _appWindow != null)
|
||||
{
|
||||
_appWindow.Hide();
|
||||
}
|
||||
|
||||
if (cloaked)
|
||||
{
|
||||
ShowWindowNative(_hwnd, SwShowNoActivate);
|
||||
}
|
||||
else
|
||||
{
|
||||
SetWindowPosNative(_hwnd, HwndBottom, 0, 0, 0, 0, SwpNoMove | SwpNoSize | SwpNoActivate);
|
||||
}
|
||||
}
|
||||
else if (_appWindow != null)
|
||||
{
|
||||
_appWindow.Hide();
|
||||
}
|
||||
|
||||
_isVisible = false;
|
||||
RemoveGlobalMouseHook();
|
||||
|
||||
ScheduleMemoryTrim();
|
||||
}
|
||||
|
||||
internal void RequestHide()
|
||||
{
|
||||
if (_dispatcherQueue.HasThreadAccess)
|
||||
{
|
||||
HideWindow();
|
||||
}
|
||||
else
|
||||
{
|
||||
_dispatcherQueue.TryEnqueue(HideWindow);
|
||||
}
|
||||
}
|
||||
|
||||
private void ScheduleMemoryTrim()
|
||||
{
|
||||
CancelMemoryTrim();
|
||||
_trimCts = new CancellationTokenSource();
|
||||
var token = _trimCts.Token;
|
||||
|
||||
// Delay the trim to avoid aggressive GC during quick toggles
|
||||
Task.Delay(2000, token).ContinueWith(
|
||||
_ =>
|
||||
{
|
||||
if (token.IsCancellationRequested)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
TrimMemory();
|
||||
},
|
||||
token,
|
||||
TaskContinuationOptions.None,
|
||||
TaskScheduler.Default);
|
||||
}
|
||||
|
||||
private void CancelMemoryTrim()
|
||||
{
|
||||
_trimCts?.Cancel();
|
||||
_trimCts?.Dispose();
|
||||
_trimCts = null;
|
||||
}
|
||||
|
||||
private void TrimMemory()
|
||||
{
|
||||
GC.Collect();
|
||||
GC.WaitForPendingFinalizers();
|
||||
GC.Collect();
|
||||
SetProcessWorkingSetSize(System.Diagnostics.Process.GetCurrentProcess().Handle, -1, -1);
|
||||
}
|
||||
|
||||
private void InitializeEventListeners()
|
||||
{
|
||||
if (!string.IsNullOrEmpty(_launchContext.ShowEventName))
|
||||
{
|
||||
try
|
||||
{
|
||||
_showEvent = EventWaitHandle.OpenExisting(_launchContext.ShowEventName!);
|
||||
EnsureListenerInfrastructure();
|
||||
StartShowListenerThread();
|
||||
}
|
||||
catch (WaitHandleCannotBeOpenedException)
|
||||
{
|
||||
}
|
||||
}
|
||||
|
||||
if (!string.IsNullOrEmpty(_launchContext.ExitEventName))
|
||||
{
|
||||
try
|
||||
{
|
||||
_exitEvent = EventWaitHandle.OpenExisting(_launchContext.ExitEventName!);
|
||||
EnsureListenerInfrastructure();
|
||||
StartExitListenerThread();
|
||||
}
|
||||
catch (WaitHandleCannotBeOpenedException)
|
||||
{
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void ShowWindow()
|
||||
{
|
||||
CancelMemoryTrim();
|
||||
|
||||
if (_hwnd != IntPtr.Zero)
|
||||
{
|
||||
UncloakWindow();
|
||||
|
||||
ShowWindowNative(_hwnd, SwShow);
|
||||
|
||||
var flags = SwpNoSize | SwpShowWindow;
|
||||
var targetX = 0;
|
||||
var targetY = 0;
|
||||
|
||||
var windowSize = _appWindow?.Size;
|
||||
var windowWidth = windowSize?.Width ?? DefaultWidth;
|
||||
var windowHeight = windowSize?.Height ?? DefaultHeight;
|
||||
|
||||
GetCursorPos(out var cursorPosition);
|
||||
var monitorHandle = MonitorFromPointNative(cursorPosition, MonitorDefaulttonearest);
|
||||
if (monitorHandle != IntPtr.Zero)
|
||||
{
|
||||
var monitorInfo = new MonitorInfo { CbSize = Marshal.SizeOf<MonitorInfo>() };
|
||||
if (GetMonitorInfoNative(monitorHandle, ref monitorInfo))
|
||||
{
|
||||
targetX = monitorInfo.RcWork.Right - windowWidth;
|
||||
targetY = monitorInfo.RcWork.Bottom - windowHeight;
|
||||
}
|
||||
}
|
||||
|
||||
SetWindowPosNative(_hwnd, HwndTopmost, targetX, targetY, 0, 0, flags);
|
||||
WindowHelpers.BringToForeground(_hwnd);
|
||||
}
|
||||
|
||||
_hasSeenInteractiveActivation = true;
|
||||
_initialActivationHandled = true;
|
||||
Activate();
|
||||
_isVisible = true;
|
||||
EnsureGlobalMouseHook();
|
||||
ShellHost.RefreshIfAppsList();
|
||||
}
|
||||
|
||||
private void OnActivated(object sender, WindowActivatedEventArgs args)
|
||||
{
|
||||
if (args.WindowActivationState == WindowActivationState.Deactivated)
|
||||
{
|
||||
if (!_hasSeenInteractiveActivation)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
HideWindow();
|
||||
return;
|
||||
}
|
||||
|
||||
_hasSeenInteractiveActivation = true;
|
||||
|
||||
if (_initialActivationHandled)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
_initialActivationHandled = true;
|
||||
PrimeWindow();
|
||||
HideWindow();
|
||||
}
|
||||
|
||||
private void OnClosed(object sender, WindowEventArgs e)
|
||||
{
|
||||
Dispose();
|
||||
}
|
||||
|
||||
private void PrimeWindow()
|
||||
{
|
||||
if (_isPrimed || _hwnd == IntPtr.Zero)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
_isPrimed = true;
|
||||
|
||||
if (_appWindow != null)
|
||||
{
|
||||
var currentPosition = _appWindow.Position;
|
||||
_appWindow.MoveAndResize(new RectInt32(currentPosition.X, currentPosition.Y, DefaultWidth, DefaultHeight));
|
||||
}
|
||||
|
||||
// Warm up the window while cloaked so the first summon does not pay XAML initialization cost.
|
||||
var cloaked = CloakWindow();
|
||||
if (cloaked)
|
||||
{
|
||||
ShowWindowNative(_hwnd, SwShowNoActivate);
|
||||
}
|
||||
}
|
||||
|
||||
private void HideFromTaskbar()
|
||||
{
|
||||
if (_appWindow == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
_appWindow.IsShownInSwitchers = false;
|
||||
}
|
||||
|
||||
private bool CloakWindow()
|
||||
{
|
||||
if (_hwnd == IntPtr.Zero)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
if (_isWindowCloaked)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
int cloak = 1;
|
||||
var result = DwmSetWindowAttribute(_hwnd, DwmWaCloak, ref cloak, sizeof(int));
|
||||
if (result == 0)
|
||||
{
|
||||
_isWindowCloaked = true;
|
||||
SetWindowPosNative(_hwnd, HwndBottom, 0, 0, 0, 0, SwpNoMove | SwpNoSize | SwpNoActivate);
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
private void UncloakWindow()
|
||||
{
|
||||
if (_hwnd == IntPtr.Zero || !_isWindowCloaked)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
int cloak = 0;
|
||||
var result = DwmSetWindowAttribute(_hwnd, DwmWaCloak, ref cloak, sizeof(int));
|
||||
if (result == 0)
|
||||
{
|
||||
_isWindowCloaked = false;
|
||||
}
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
Dispose(true);
|
||||
GC.SuppressFinalize(this);
|
||||
}
|
||||
|
||||
private void Dispose(bool disposing)
|
||||
{
|
||||
if (_disposed)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (disposing)
|
||||
{
|
||||
StopEventListeners();
|
||||
|
||||
_showEvent?.Dispose();
|
||||
_showEvent = null;
|
||||
|
||||
_exitEvent?.Dispose();
|
||||
_exitEvent = null;
|
||||
|
||||
if (_hwnd != IntPtr.Zero && IsWindow(_hwnd))
|
||||
{
|
||||
UncloakWindow();
|
||||
}
|
||||
|
||||
RemoveGlobalMouseHook();
|
||||
|
||||
_coordinator.Dispose();
|
||||
}
|
||||
|
||||
_disposed = true;
|
||||
}
|
||||
|
||||
[DllImport("user32.dll", SetLastError = true)]
|
||||
[return: MarshalAs(UnmanagedType.Bool)]
|
||||
private static extern bool IsWindow(IntPtr hWnd);
|
||||
|
||||
[DllImport("user32.dll", EntryPoint = "ShowWindow", SetLastError = true)]
|
||||
private static extern bool ShowWindowNative(IntPtr hWnd, int nCmdShow);
|
||||
|
||||
[DllImport("user32.dll", EntryPoint = "GetWindowLongPtr", SetLastError = true)]
|
||||
private static extern nint GetWindowLongPtrNative(IntPtr hWnd, int nIndex);
|
||||
|
||||
[DllImport("user32.dll", EntryPoint = "SetWindowLongPtr", SetLastError = true)]
|
||||
private static extern nint SetWindowLongPtrNative(IntPtr hWnd, int nIndex, nint dwNewLong);
|
||||
|
||||
[DllImport("user32.dll", EntryPoint = "SetWindowPos", SetLastError = true)]
|
||||
private static extern bool SetWindowPosNative(IntPtr hWnd, IntPtr hWndInsertAfter, int x, int y, int cx, int cy, uint uFlags);
|
||||
|
||||
[DllImport("user32.dll", EntryPoint = "SetForegroundWindow", SetLastError = true)]
|
||||
private static extern bool SetForegroundWindowNative(IntPtr hWnd);
|
||||
|
||||
[DllImport("user32.dll", EntryPoint = "GetForegroundWindow", SetLastError = true)]
|
||||
private static extern IntPtr GetForegroundWindowNative();
|
||||
|
||||
[DllImport("user32.dll", EntryPoint = "GetWindowThreadProcessId", SetLastError = true)]
|
||||
private static extern uint GetWindowThreadProcessIdNative(IntPtr hWnd, IntPtr lpdwProcessId);
|
||||
|
||||
[DllImport("user32.dll", EntryPoint = "AttachThreadInput", SetLastError = true)]
|
||||
private static extern bool AttachThreadInputNative(uint idAttach, uint idAttachTo, bool fAttach);
|
||||
|
||||
[DllImport("dwmapi.dll", EntryPoint = "DwmSetWindowAttribute", SetLastError = true)]
|
||||
private static extern int DwmSetWindowAttribute(IntPtr hwnd, int attr, ref int attrValue, int attrSize);
|
||||
|
||||
[DllImport("user32.dll", EntryPoint = "MonitorFromPoint", SetLastError = true)]
|
||||
private static extern IntPtr MonitorFromPointNative(NativePoint pt, uint dwFlags);
|
||||
|
||||
[DllImport("user32.dll", EntryPoint = "GetMonitorInfoW", SetLastError = true)]
|
||||
private static extern bool GetMonitorInfoNative(IntPtr hMonitor, ref MonitorInfo lpmi);
|
||||
|
||||
[DllImport("user32.dll", EntryPoint = "SetWindowsHookExW", SetLastError = true)]
|
||||
private static extern IntPtr SetWindowsHookExNative(int idHook, LowLevelMouseProc lpfn, IntPtr hMod, uint dwThreadId);
|
||||
|
||||
[DllImport("user32.dll", EntryPoint = "UnhookWindowsHookEx", SetLastError = true)]
|
||||
private static extern bool UnhookWindowsHookExNative(IntPtr hhk);
|
||||
|
||||
[DllImport("user32.dll", EntryPoint = "CallNextHookEx", SetLastError = true)]
|
||||
private static extern IntPtr CallNextHookExNative(IntPtr hhk, int nCode, IntPtr wParam, IntPtr lParam);
|
||||
|
||||
[DllImport("kernel32.dll", EntryPoint = "GetModuleHandleW", SetLastError = true, CharSet = CharSet.Unicode)]
|
||||
private static extern IntPtr GetModuleHandleNative([MarshalAs(UnmanagedType.LPWStr)] string? lpModuleName);
|
||||
|
||||
[DllImport("user32.dll", EntryPoint = "GetWindowRect", SetLastError = true)]
|
||||
private static extern bool GetWindowRectNative(IntPtr hWnd, out Rect rect);
|
||||
|
||||
private void EnsureGlobalMouseHook()
|
||||
{
|
||||
if (_mouseHook != IntPtr.Zero)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
_mouseHookDelegate ??= LowLevelMouseHookCallback;
|
||||
var moduleHandle = GetModuleHandleNative(null);
|
||||
_mouseHook = SetWindowsHookExNative(WhMouseLl, _mouseHookDelegate, moduleHandle, 0);
|
||||
}
|
||||
|
||||
private void RemoveGlobalMouseHook()
|
||||
{
|
||||
if (_mouseHook == IntPtr.Zero)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
UnhookWindowsHookExNative(_mouseHook);
|
||||
_mouseHook = IntPtr.Zero;
|
||||
}
|
||||
|
||||
private IntPtr LowLevelMouseHookCallback(int nCode, IntPtr wParam, IntPtr lParam)
|
||||
{
|
||||
if (nCode >= 0 && _isVisible && lParam != IntPtr.Zero && IsMouseButtonDownMessage(wParam))
|
||||
{
|
||||
var data = Marshal.PtrToStructure<LowLevelMouseInput>(lParam);
|
||||
if (!IsPointInsideWindow(data.Point))
|
||||
{
|
||||
_dispatcherQueue.TryEnqueue(() =>
|
||||
{
|
||||
if (_isVisible)
|
||||
{
|
||||
HideWindow();
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
return CallNextHookExNative(_mouseHook, nCode, wParam, lParam);
|
||||
}
|
||||
|
||||
private static bool IsMouseButtonDownMessage(IntPtr wParam)
|
||||
{
|
||||
var message = wParam.ToInt32();
|
||||
return message == WmLbuttondown || message == WmRbuttondown || message == WmMbuttondown || message == WmXbuttondown;
|
||||
}
|
||||
|
||||
private bool IsPointInsideWindow(NativePoint point)
|
||||
{
|
||||
if (_hwnd == IntPtr.Zero)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!GetWindowRectNative(_hwnd, out var rect))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
return point.X >= rect.Left && point.X <= rect.Right && point.Y >= rect.Top && point.Y <= rect.Bottom;
|
||||
}
|
||||
|
||||
private void EnsureListenerInfrastructure()
|
||||
{
|
||||
_listenerShutdownEvent ??= new ManualResetEventSlim(false);
|
||||
}
|
||||
|
||||
private void StartShowListenerThread()
|
||||
{
|
||||
if (_showEvent == null || _listenerShutdownEvent == null || _showListenerThread != null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
_showListenerThread = new Thread(ListenForShowEvents)
|
||||
{
|
||||
IsBackground = true,
|
||||
Name = "QuickAccess-ShowEventListener",
|
||||
};
|
||||
_showListenerThread.Start();
|
||||
}
|
||||
|
||||
private void StartExitListenerThread()
|
||||
{
|
||||
if (_exitEvent == null || _listenerShutdownEvent == null || _exitListenerThread != null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
_exitListenerThread = new Thread(ListenForExitEvents)
|
||||
{
|
||||
IsBackground = true,
|
||||
Name = "QuickAccess-ExitEventListener",
|
||||
};
|
||||
_exitListenerThread.Start();
|
||||
}
|
||||
|
||||
private void ListenForShowEvents()
|
||||
{
|
||||
if (_showEvent == null || _listenerShutdownEvent == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
var handles = new WaitHandle[] { _showEvent, _listenerShutdownEvent.WaitHandle };
|
||||
try
|
||||
{
|
||||
while (true)
|
||||
{
|
||||
var index = WaitHandle.WaitAny(handles);
|
||||
if (index == 0)
|
||||
{
|
||||
_dispatcherQueue.TryEnqueue(ShowWindow);
|
||||
}
|
||||
else
|
||||
{
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (ObjectDisposedException)
|
||||
{
|
||||
}
|
||||
catch (ThreadInterruptedException)
|
||||
{
|
||||
}
|
||||
}
|
||||
|
||||
private void ListenForExitEvents()
|
||||
{
|
||||
if (_exitEvent == null || _listenerShutdownEvent == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
var handles = new WaitHandle[] { _exitEvent, _listenerShutdownEvent.WaitHandle };
|
||||
try
|
||||
{
|
||||
while (true)
|
||||
{
|
||||
var index = WaitHandle.WaitAny(handles);
|
||||
if (index == 0)
|
||||
{
|
||||
_dispatcherQueue.TryEnqueue(Close);
|
||||
break;
|
||||
}
|
||||
|
||||
if (index == 1)
|
||||
{
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (ObjectDisposedException)
|
||||
{
|
||||
}
|
||||
catch (ThreadInterruptedException)
|
||||
{
|
||||
}
|
||||
}
|
||||
|
||||
private void StopEventListeners()
|
||||
{
|
||||
if (_listenerShutdownEvent == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
_listenerShutdownEvent.Set();
|
||||
|
||||
JoinListenerThread(ref _showListenerThread);
|
||||
JoinListenerThread(ref _exitListenerThread);
|
||||
|
||||
_listenerShutdownEvent.Dispose();
|
||||
_listenerShutdownEvent = null;
|
||||
}
|
||||
|
||||
private static void JoinListenerThread(ref Thread? thread)
|
||||
{
|
||||
if (thread == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
if (!thread.Join(TimeSpan.FromMilliseconds(250)))
|
||||
{
|
||||
thread.Interrupt();
|
||||
thread.Join(TimeSpan.FromMilliseconds(250));
|
||||
}
|
||||
}
|
||||
catch (ThreadInterruptedException)
|
||||
{
|
||||
}
|
||||
catch (ThreadStateException)
|
||||
{
|
||||
}
|
||||
|
||||
thread = null;
|
||||
}
|
||||
|
||||
private void CustomizeWindowChrome()
|
||||
{
|
||||
if (_hwnd == IntPtr.Zero)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
var windowAttributesChanged = false;
|
||||
|
||||
var stylePtr = GetWindowLongPtrNative(_hwnd, GwlStyle);
|
||||
var styleError = Marshal.GetLastWin32Error();
|
||||
if (!(stylePtr == nint.Zero && styleError != 0))
|
||||
{
|
||||
var styleValue = (long)stylePtr;
|
||||
var newStyleValue = styleValue & ~(WsSysmenu | WsMinimizeBox | WsMaximizeBox);
|
||||
|
||||
if (newStyleValue != styleValue)
|
||||
{
|
||||
SetWindowLongPtrNative(_hwnd, GwlStyle, (nint)newStyleValue);
|
||||
windowAttributesChanged = true;
|
||||
}
|
||||
}
|
||||
|
||||
var exStylePtr = GetWindowLongPtrNative(_hwnd, GwlExStyle);
|
||||
var exStyleError = Marshal.GetLastWin32Error();
|
||||
if (!(exStylePtr == nint.Zero && exStyleError != 0))
|
||||
{
|
||||
var exStyleValue = (long)exStylePtr;
|
||||
var newExStyleValue = exStyleValue | WsExToolWindow;
|
||||
if (newExStyleValue != exStyleValue)
|
||||
{
|
||||
SetWindowLongPtrNative(_hwnd, GwlExStyle, (nint)newExStyleValue);
|
||||
windowAttributesChanged = true;
|
||||
}
|
||||
}
|
||||
|
||||
if (windowAttributesChanged)
|
||||
{
|
||||
// Apply the new chrome immediately so caption buttons disappear right away and the tool-window flag takes effect.
|
||||
SetWindowPosNative(_hwnd, IntPtr.Zero, 0, 0, 0, 0, SwpNoMove | SwpNoSize | SwpNoZorder | SwpNoActivate | SwpFrameChanged);
|
||||
}
|
||||
}
|
||||
|
||||
private const int WhMouseLl = 14;
|
||||
private const int WmLbuttondown = 0x0201;
|
||||
private const int WmRbuttondown = 0x0204;
|
||||
private const int WmMbuttondown = 0x0207;
|
||||
|
||||
[DllImport("user32.dll")]
|
||||
private static extern bool GetCursorPos(out NativePoint lpPoint);
|
||||
|
||||
[DllImport("kernel32.dll")]
|
||||
private static extern bool SetProcessWorkingSetSize(IntPtr hProcess, int dwMinimumWorkingSetSize, int dwMaximumWorkingSetSize);
|
||||
|
||||
private const int WmXbuttondown = 0x020B;
|
||||
|
||||
private delegate IntPtr LowLevelMouseProc(int nCode, IntPtr wParam, IntPtr lParam);
|
||||
|
||||
private struct Rect
|
||||
{
|
||||
public int Left;
|
||||
public int Top;
|
||||
public int Right;
|
||||
public int Bottom;
|
||||
}
|
||||
|
||||
[StructLayout(LayoutKind.Sequential)]
|
||||
private struct LowLevelMouseInput
|
||||
{
|
||||
public NativePoint Point;
|
||||
public int MouseData;
|
||||
public int Flags;
|
||||
public int Time;
|
||||
public IntPtr DwExtraInfo;
|
||||
}
|
||||
|
||||
private struct NativePoint
|
||||
{
|
||||
public int X;
|
||||
public int Y;
|
||||
}
|
||||
|
||||
[StructLayout(LayoutKind.Sequential)]
|
||||
private struct MonitorInfo
|
||||
{
|
||||
public int CbSize;
|
||||
public Rect RcMonitor;
|
||||
public Rect RcWork;
|
||||
public uint DwFlags;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,29 @@
|
||||
// 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 ReportBug();
|
||||
|
||||
void OnModuleLaunched(ModuleType moduleType);
|
||||
}
|
||||
@@ -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.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 = SettingsUtils.Default;
|
||||
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()
|
||||
{
|
||||
Common.UI.SettingsDeepLink.OpenSettings(Common.UI.SettingsDeepLink.SettingsWindow.Dashboard);
|
||||
_window.RequestHide();
|
||||
}
|
||||
|
||||
public void OpenGeneralSettingsForUpdates()
|
||||
{
|
||||
Common.UI.SettingsDeepLink.OpenSettings(Common.UI.SettingsDeepLink.SettingsWindow.Overview);
|
||||
_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 ReportBug()
|
||||
{
|
||||
if (!TrySendIpcMessage("{\"bugreport\": 0 }", "bug report request"))
|
||||
{
|
||||
Logger.LogWarning("QuickAccessCoordinator: failed to dispatch bug report request; IPC unavailable.");
|
||||
}
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
TrySendIpcMessage(payload, "general settings update");
|
||||
}
|
||||
|
||||
private bool TrySendIpcMessage(string payload, string operationDescription)
|
||||
{
|
||||
lock (_ipcLock)
|
||||
{
|
||||
if (_ipcManager == null)
|
||||
{
|
||||
if (!_ipcUnavailableLogged)
|
||||
{
|
||||
_ipcUnavailableLogged = true;
|
||||
Logger.LogWarning($"QuickAccessCoordinator: unable to send {operationDescription} because IPC is not available.");
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
_ipcManager.Send(payload);
|
||||
return true;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Logger.LogError($"QuickAccessCoordinator: failed to send {operationDescription}.", ex);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,37 @@
|
||||
// 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.Controls;
|
||||
using Microsoft.PowerToys.Settings.UI.Library;
|
||||
using PowerToys.Interop;
|
||||
|
||||
namespace Microsoft.PowerToys.QuickAccess.Services
|
||||
{
|
||||
public class QuickAccessLauncher : Microsoft.PowerToys.Settings.UI.Controls.QuickAccessLauncher
|
||||
{
|
||||
private readonly IQuickAccessCoordinator? _coordinator;
|
||||
|
||||
public QuickAccessLauncher(IQuickAccessCoordinator? coordinator)
|
||||
: base(coordinator?.IsRunnerElevated ?? false)
|
||||
{
|
||||
_coordinator = coordinator;
|
||||
}
|
||||
|
||||
public override bool Launch(ModuleType moduleType)
|
||||
{
|
||||
bool moduleRun = base.Launch(moduleType);
|
||||
|
||||
if (moduleRun)
|
||||
{
|
||||
_coordinator?.OnModuleLaunched(moduleType);
|
||||
}
|
||||
|
||||
_coordinator?.HideFlyout();
|
||||
|
||||
return moduleRun;
|
||||
}
|
||||
}
|
||||
}
|
||||
172
src/settings-ui/QuickAccess.UI/ViewModels/AllAppsViewModel.cs
Normal file
172
src/settings-ui/QuickAccess.UI/ViewModels/AllAppsViewModel.cs
Normal file
@@ -0,0 +1,172 @@
|
||||
// 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.Generic;
|
||||
using System.Collections.ObjectModel;
|
||||
using System.Linq;
|
||||
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.PowerToys.Settings.UI.Library.ViewModels.Commands;
|
||||
using Microsoft.UI.Dispatching;
|
||||
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 SettingsUtils _settingsUtils;
|
||||
private readonly ResourceLoader _resourceLoader;
|
||||
private readonly DispatcherQueue _dispatcherQueue;
|
||||
private GeneralSettings _generalSettings;
|
||||
|
||||
public ObservableCollection<FlyoutMenuItem> FlyoutMenuItems { get; }
|
||||
|
||||
public DashboardSortOrder DashboardSortOrder
|
||||
{
|
||||
get => _generalSettings.DashboardSortOrder;
|
||||
set
|
||||
{
|
||||
if (_generalSettings.DashboardSortOrder != value)
|
||||
{
|
||||
_generalSettings.DashboardSortOrder = value;
|
||||
_settingsUtils.SaveSettings(_generalSettings.ToJsonString(), _generalSettings.GetModuleName());
|
||||
OnPropertyChanged();
|
||||
RefreshFlyoutMenuItems();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public AllAppsViewModel(IQuickAccessCoordinator coordinator)
|
||||
{
|
||||
_coordinator = coordinator;
|
||||
_dispatcherQueue = DispatcherQueue.GetForCurrentThread();
|
||||
_settingsUtils = SettingsUtils.Default;
|
||||
_settingsRepository = SettingsRepository<GeneralSettings>.GetInstance(_settingsUtils);
|
||||
_generalSettings = _settingsRepository.SettingsConfig;
|
||||
_generalSettings.AddEnabledModuleChangeNotification(ModuleEnabledChangedOnSettingsPage);
|
||||
_settingsRepository.SettingsChanged += OnSettingsChanged;
|
||||
|
||||
_resourceLoader = Helpers.ResourceLoaderInstance.ResourceLoader;
|
||||
FlyoutMenuItems = new ObservableCollection<FlyoutMenuItem>();
|
||||
|
||||
RefreshFlyoutMenuItems();
|
||||
}
|
||||
|
||||
private void OnSettingsChanged(GeneralSettings newSettings)
|
||||
{
|
||||
_dispatcherQueue.TryEnqueue(() =>
|
||||
{
|
||||
_generalSettings = newSettings;
|
||||
_generalSettings.AddEnabledModuleChangeNotification(ModuleEnabledChangedOnSettingsPage);
|
||||
OnPropertyChanged(nameof(DashboardSortOrder));
|
||||
RefreshFlyoutMenuItems();
|
||||
});
|
||||
}
|
||||
|
||||
public void RefreshSettings()
|
||||
{
|
||||
if (_settingsRepository.ReloadSettings())
|
||||
{
|
||||
OnSettingsChanged(_settingsRepository.SettingsConfig);
|
||||
}
|
||||
}
|
||||
|
||||
private void RefreshFlyoutMenuItems()
|
||||
{
|
||||
var desiredItems = new List<FlyoutMenuItem>();
|
||||
|
||||
foreach (ModuleType moduleType in Enum.GetValues<ModuleType>())
|
||||
{
|
||||
if (moduleType == ModuleType.GeneralSettings)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
var gpo = Helpers.ModuleGpoHelper.GetModuleGpoConfiguration(moduleType);
|
||||
var isLocked = gpo is GpoRuleConfigured.Enabled or GpoRuleConfigured.Disabled;
|
||||
var isEnabled = gpo == GpoRuleConfigured.Enabled || (!isLocked && Microsoft.PowerToys.Settings.UI.Library.Helpers.ModuleHelper.GetIsModuleEnabled(_generalSettings, moduleType));
|
||||
|
||||
var existingItem = FlyoutMenuItems.FirstOrDefault(x => x.Tag == moduleType);
|
||||
|
||||
if (existingItem != null)
|
||||
{
|
||||
existingItem.Label = _resourceLoader.GetString(Microsoft.PowerToys.Settings.UI.Library.Helpers.ModuleHelper.GetModuleLabelResourceName(moduleType));
|
||||
existingItem.IsLocked = isLocked;
|
||||
existingItem.Icon = Microsoft.PowerToys.Settings.UI.Library.Helpers.ModuleHelper.GetModuleTypeFluentIconName(moduleType);
|
||||
|
||||
if (existingItem.IsEnabled != isEnabled)
|
||||
{
|
||||
var callback = existingItem.EnabledChangedCallback;
|
||||
existingItem.EnabledChangedCallback = null;
|
||||
existingItem.IsEnabled = isEnabled;
|
||||
existingItem.EnabledChangedCallback = callback;
|
||||
}
|
||||
|
||||
desiredItems.Add(existingItem);
|
||||
}
|
||||
else
|
||||
{
|
||||
desiredItems.Add(new FlyoutMenuItem
|
||||
{
|
||||
Label = _resourceLoader.GetString(Microsoft.PowerToys.Settings.UI.Library.Helpers.ModuleHelper.GetModuleLabelResourceName(moduleType)),
|
||||
IsEnabled = isEnabled,
|
||||
IsLocked = isLocked,
|
||||
Tag = moduleType,
|
||||
Icon = Microsoft.PowerToys.Settings.UI.Library.Helpers.ModuleHelper.GetModuleTypeFluentIconName(moduleType),
|
||||
EnabledChangedCallback = EnabledChangedOnUI,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
var sortedItems = DashboardSortOrder switch
|
||||
{
|
||||
DashboardSortOrder.ByStatus => desiredItems.OrderByDescending(x => x.IsEnabled).ThenBy(x => x.Label).ToList(),
|
||||
_ => desiredItems.OrderBy(x => x.Label).ToList(),
|
||||
};
|
||||
|
||||
for (int i = FlyoutMenuItems.Count - 1; i >= 0; i--)
|
||||
{
|
||||
if (!sortedItems.Contains(FlyoutMenuItems[i]))
|
||||
{
|
||||
FlyoutMenuItems.RemoveAt(i);
|
||||
}
|
||||
}
|
||||
|
||||
for (int i = 0; i < sortedItems.Count; i++)
|
||||
{
|
||||
var item = sortedItems[i];
|
||||
var oldIndex = FlyoutMenuItems.IndexOf(item);
|
||||
|
||||
if (oldIndex < 0)
|
||||
{
|
||||
FlyoutMenuItems.Insert(i, item);
|
||||
}
|
||||
else if (oldIndex != i)
|
||||
{
|
||||
FlyoutMenuItems.Move(oldIndex, i);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void EnabledChangedOnUI(FlyoutMenuItem item)
|
||||
{
|
||||
if (_coordinator.UpdateModuleEnabled(item.Tag, item.IsEnabled))
|
||||
{
|
||||
_coordinator.NotifyUserSettingsInteraction();
|
||||
}
|
||||
}
|
||||
|
||||
private void ModuleEnabledChangedOnSettingsPage()
|
||||
{
|
||||
RefreshFlyoutMenuItems();
|
||||
}
|
||||
}
|
||||
52
src/settings-ui/QuickAccess.UI/ViewModels/FlyoutMenuItem.cs
Normal file
52
src/settings-ui/QuickAccess.UI/ViewModels/FlyoutMenuItem.cs
Normal file
@@ -0,0 +1,52 @@
|
||||
// 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;
|
||||
using Microsoft.PowerToys.Settings.UI.Controls;
|
||||
|
||||
namespace Microsoft.PowerToys.QuickAccess.ViewModels;
|
||||
|
||||
public sealed class FlyoutMenuItem : ModuleListItem
|
||||
{
|
||||
private bool _visible;
|
||||
|
||||
public string ToolTip { get; set; } = string.Empty;
|
||||
|
||||
public new ModuleType Tag
|
||||
{
|
||||
get => (ModuleType)(base.Tag ?? ModuleType.PowerLauncher);
|
||||
set => base.Tag = value;
|
||||
}
|
||||
|
||||
public override bool IsEnabled
|
||||
{
|
||||
get => base.IsEnabled;
|
||||
set
|
||||
{
|
||||
if (base.IsEnabled != value)
|
||||
{
|
||||
base.IsEnabled = value;
|
||||
EnabledChangedCallback?.Invoke(this);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public Action<FlyoutMenuItem>? EnabledChangedCallback { get; set; }
|
||||
|
||||
public bool Visible
|
||||
{
|
||||
get => _visible;
|
||||
set
|
||||
{
|
||||
if (_visible != value)
|
||||
{
|
||||
_visible = value;
|
||||
OnPropertyChanged();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,47 @@
|
||||
// Copyright (c) Microsoft Corporation
|
||||
// The Microsoft Corporation licenses this file to you under the MIT license.
|
||||
// See the LICENSE file in the project root for more information.
|
||||
|
||||
using System.Collections.ObjectModel;
|
||||
using global::PowerToys.GPOWrapper;
|
||||
using ManagedCommon;
|
||||
using Microsoft.PowerToys.QuickAccess.Services;
|
||||
using Microsoft.PowerToys.Settings.UI.Controls;
|
||||
using Microsoft.PowerToys.Settings.UI.Library;
|
||||
using Microsoft.PowerToys.Settings.UI.Library.Helpers;
|
||||
using Microsoft.PowerToys.Settings.UI.Library.Interfaces;
|
||||
using Microsoft.UI.Dispatching;
|
||||
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 readonly DispatcherQueue _dispatcherQueue;
|
||||
private readonly QuickAccessViewModel _quickAccessViewModel;
|
||||
|
||||
public ObservableCollection<QuickAccessItem> FlyoutMenuItems => _quickAccessViewModel.Items;
|
||||
|
||||
public bool IsUpdateAvailable { get; private set; }
|
||||
|
||||
public LauncherViewModel(IQuickAccessCoordinator coordinator)
|
||||
{
|
||||
_coordinator = coordinator;
|
||||
_dispatcherQueue = DispatcherQueue.GetForCurrentThread();
|
||||
var settingsUtils = SettingsUtils.Default;
|
||||
_settingsRepository = SettingsRepository<GeneralSettings>.GetInstance(settingsUtils);
|
||||
|
||||
_resourceLoader = Helpers.ResourceLoaderInstance.ResourceLoader;
|
||||
|
||||
_quickAccessViewModel = new QuickAccessViewModel(
|
||||
_settingsRepository,
|
||||
new Microsoft.PowerToys.QuickAccess.Services.QuickAccessLauncher(_coordinator),
|
||||
moduleType => Helpers.ModuleGpoHelper.GetModuleGpoConfiguration(moduleType) == GpoRuleConfigured.Disabled,
|
||||
_resourceLoader);
|
||||
var updatingSettings = UpdatingSettings.LoadSettings() ?? new UpdatingSettings();
|
||||
IsUpdateAvailable = updatingSettings.State is UpdatingSettings.UpdatingState.ReadyToInstall or UpdatingSettings.UpdatingState.ReadyToDownload;
|
||||
}
|
||||
}
|
||||
16
src/settings-ui/QuickAccess.UI/app.manifest
Normal file
16
src/settings-ui/QuickAccess.UI/app.manifest
Normal file
@@ -0,0 +1,16 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<assembly manifestVersion="1.0" xmlns="urn:schemas-microsoft-com:asm.v1">
|
||||
<assemblyIdentity version="1.0.0.0" name="PowerToys.QuickAccess.app" />
|
||||
|
||||
<application xmlns="urn:schemas-microsoft-com:asm.v3">
|
||||
<windowsSettings>
|
||||
<dpiAware xmlns="http://schemas.microsoft.com/SMI/2005/WindowsSettings">true/PM</dpiAware>
|
||||
<dpiAwareness xmlns="http://schemas.microsoft.com/SMI/2016/WindowsSettings">PerMonitorV2</dpiAwareness>
|
||||
</windowsSettings>
|
||||
</application>
|
||||
<compatibility xmlns="urn:schemas-microsoft-com:compatibility.v1">
|
||||
<application>
|
||||
<supportedOS Id="{8e0f7a12-bfb3-4fe8-b9a5-48fd50a15a9a}" />
|
||||
</application>
|
||||
</compatibility>
|
||||
</assembly>
|
||||
Reference in New Issue
Block a user