Signed-off-by: Shuai Yuan <shuai.yuan.zju@gmail.com>
This commit is contained in:
Shuai Yuan
2025-11-20 15:32:32 +08:00
parent ea0a13be3a
commit ed254b60cc
9 changed files with 304 additions and 54 deletions

View File

@@ -4,11 +4,17 @@
#include <mutex>
#include <string>
#include <vector>
#include <rpc.h>
#include <new>
#include <memory>
#include <common/logger/logger.h>
#include <common/utils/process_path.h>
#include <common/interop/two_way_pipe_message_ipc.h>
#include <wil/resource.h>
extern void receive_json_send_to_main_thread(const std::wstring& msg);
namespace
{
struct PositionPayload
@@ -25,7 +31,10 @@ namespace
std::wstring show_event_name;
std::wstring exit_event_name;
std::wstring position_mapping_name;
std::wstring runner_pipe_name;
std::wstring app_pipe_name;
PositionPayload* position_payload = nullptr;
std::unique_ptr<TwoWayPipeMessageIPC> quick_access_ipc;
std::mutex quick_access_mutex;
bool is_process_active_locked()
@@ -47,6 +56,12 @@ namespace
void reset_state_locked()
{
if (quick_access_ipc)
{
quick_access_ipc->end();
quick_access_ipc.reset();
}
if (position_payload)
{
UnmapViewOfFile(position_payload);
@@ -60,11 +75,19 @@ namespace
show_event_name.clear();
exit_event_name.clear();
position_mapping_name.clear();
runner_pipe_name.clear();
app_pipe_name.clear();
}
std::wstring build_event_name(const wchar_t* suffix)
{
return L"Local\\PowerToysQuickAccess_" + std::to_wstring(GetCurrentProcessId()) + suffix;
std::wstring name = L"Local\\PowerToysQuickAccess_";
name += std::to_wstring(GetCurrentProcessId());
if (suffix)
{
name += suffix;
}
return name;
}
std::wstring build_command_line(const std::wstring& exe_path)
@@ -78,6 +101,18 @@ namespace
command_line += L"\" --position-map=\"";
command_line += position_mapping_name;
command_line += L"\"";
if (!runner_pipe_name.empty())
{
command_line.append(L" --runner-pipe=\"");
command_line += runner_pipe_name;
command_line += L"\"";
}
if (!app_pipe_name.empty())
{
command_line.append(L" --app-pipe=\"");
command_line += app_pipe_name;
command_line += L"\"";
}
return command_line;
}
}
@@ -141,6 +176,61 @@ namespace QuickAccessHost
position_payload->y = 0;
position_payload->sequence = 0;
runner_pipe_name = L"\\\\.\\pipe\\powertoys_quick_access_runner_";
app_pipe_name = L"\\\\.\\pipe\\powertoys_quick_access_ui_";
UUID temp_uuid;
wchar_t* uuid_chars = nullptr;
if (UuidCreate(&temp_uuid) == RPC_S_UUID_NO_ADDRESS)
{
Logger::warn(L"QuickAccessHost: failed to create UUID for pipe names. error={}.", GetLastError());
}
else if (UuidToString(&temp_uuid, reinterpret_cast<RPC_WSTR*>(&uuid_chars)) != RPC_S_OK)
{
Logger::warn(L"QuickAccessHost: failed to convert UUID to string. error={}.", GetLastError());
}
if (uuid_chars != nullptr)
{
runner_pipe_name += std::wstring(uuid_chars);
app_pipe_name += std::wstring(uuid_chars);
RpcStringFree(reinterpret_cast<RPC_WSTR*>(&uuid_chars));
uuid_chars = nullptr;
}
else
{
const std::wstring fallback_suffix = std::to_wstring(GetTickCount64());
runner_pipe_name += fallback_suffix;
app_pipe_name += fallback_suffix;
}
HANDLE token_handle = nullptr;
if (!OpenProcessToken(GetCurrentProcess(), TOKEN_QUERY, &token_handle))
{
Logger::error(L"QuickAccessHost: failed to open process token. error={}.", GetLastError());
reset_state_locked();
return;
}
wil::unique_handle token(token_handle);
quick_access_ipc.reset(new (std::nothrow) TwoWayPipeMessageIPC(runner_pipe_name, app_pipe_name, receive_json_send_to_main_thread));
if (!quick_access_ipc)
{
Logger::error(L"QuickAccessHost: failed to allocate IPC instance.");
reset_state_locked();
return;
}
try
{
quick_access_ipc->start(token.get());
}
catch (...)
{
Logger::error(L"QuickAccessHost: failed to start IPC server for Quick Access.");
reset_state_locked();
return;
}
const std::wstring exe_path = get_module_folderpath() + L"\\WinUI3Apps\\PowerToys.QuickAccess.exe";
if (GetFileAttributesW(exe_path.c_str()) == INVALID_FILE_ATTRIBUTES)
{

View File

@@ -4,7 +4,7 @@
<PropertyGroup>
<OutputType>WinExe</OutputType>
<TargetFramework>net9.0-windows10.0.19041.0</TargetFramework>
<TargetFramework>net9.0-windows10.0.26100.0</TargetFramework>
<RootNamespace>Microsoft.PowerToys.QuickAccess</RootNamespace>
<AssemblyName>PowerToys.QuickAccess</AssemblyName>
<UseWinUI>true</UseWinUI>
@@ -18,19 +18,79 @@
<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\Pages\MainPage.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>
<Page Include="..\Settings.UI\SettingsXAML\Controls\FlyoutMenuButton.xaml">
<Link>Controls\FlyoutMenuButton.xaml</Link>
</Page>
</ItemGroup>
<ItemGroup>
<Compile Include="..\Settings.UI\SettingsXAML\Controls\FlyoutMenuButton.xaml.cs">
<Link>Controls\FlyoutMenuButton.xaml.cs</Link>
</Compile>
</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" />
</ItemGroup>
<ItemGroup>
<Manifest Include="$(ApplicationManifest)" />
</ItemGroup>

View File

@@ -7,13 +7,15 @@ using System.Diagnostics.CodeAnalysis;
namespace Microsoft.PowerToys.QuickAccess;
public sealed record QuickAccessLaunchContext(string? ShowEventName, string? ExitEventName, string? PositionMapName)
public sealed record QuickAccessLaunchContext(string? ShowEventName, string? ExitEventName, string? PositionMapName, string? RunnerPipeName, string? AppPipeName)
{
public static QuickAccessLaunchContext Parse(string[] args)
{
string? showEvent = null;
string? exitEvent = null;
string? positionMap = null;
string? runnerPipe = null;
string? appPipe = null;
foreach (var arg in args)
{
@@ -29,9 +31,17 @@ public sealed record QuickAccessLaunchContext(string? ShowEventName, string? Exi
{
positionMap = 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, positionMap);
return new QuickAccessLaunchContext(showEvent, exitEvent, positionMap, runnerPipe, appPipe);
}
private static bool TryReadValue(string candidate, string key, [NotNullWhen(true)] out string? value)

View File

@@ -3,7 +3,54 @@
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:local="using:Microsoft.PowerToys.QuickAccess">
xmlns:tkconverters="using:CommunityToolkit.WinUI.Converters">
<Application.Resources>
<ResourceDictionary>
<ResourceDictionary.MergedDictionaries>
<XamlControlsResources xmlns="using:Microsoft.UI.Xaml.Controls" />
<ResourceDictionary Source="ms-appx:///Resources/Styles/Button.xaml" />
<ResourceDictionary Source="ms-appx:///Resources/Styles/TextBlock.xaml" />
<ResourceDictionary Source="ms-appx:///Resources/Themes/Colors.xaml" />
<ResourceDictionary Source="ms-appx:///Resources/Themes/Generic.xaml" />
<ResourceDictionary Source="ms-appx:///Controls/FlyoutMenuButton.xaml" />
</ResourceDictionary.MergedDictionaries>
<ResourceDictionary.ThemeDictionaries>
<ResourceDictionary x:Key="Default">
<SolidColorBrush
x:Key="LayerOnAcrylicFillColorDefaultBrush"
Opacity="0.7"
Color="#FFFFFFFF" />
</ResourceDictionary>
<ResourceDictionary x:Key="Light">
<SolidColorBrush
x:Key="LayerOnAcrylicFillColorDefaultBrush"
Opacity="0.7"
Color="#FFFFFFFF" />
</ResourceDictionary>
<ResourceDictionary x:Key="Dark">
<SolidColorBrush
x:Key="LayerOnAcrylicFillColorDefaultBrush"
Opacity="0.6"
Color="#FF000000" />
</ResourceDictionary>
<ResourceDictionary x:Key="HighContrast">
<SolidColorBrush
x:Key="LayerOnAcrylicFillColorDefaultBrush"
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>

View File

@@ -9,12 +9,28 @@ namespace Microsoft.PowerToys.QuickAccess;
public partial class App : Application
{
private MainWindow? _window;
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;
}
}

View File

@@ -5,7 +5,7 @@
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:winuiEx="using:WinUIEx"
xmlns:pages="using:Microsoft.PowerToys.QuickAccess.Pages"
xmlns:flyout="using:Microsoft.PowerToys.QuickAccess.Flyout"
Width="420"
Height="188"
MinWidth="420"
@@ -17,5 +17,5 @@
mc:Ignorable="d"
Title="PowerToys Quick Access (Preview)">
<pages:MainPage x:Name="MainPage"/>
<flyout:ShellPage x:Name="ShellHost" />
</winuiEx:WindowEx>

View File

@@ -7,6 +7,9 @@ using System.IO;
using System.IO.MemoryMappedFiles;
using System.Runtime.InteropServices;
using System.Threading;
using Microsoft.PowerToys.QuickAccess.Flyout;
using Microsoft.PowerToys.QuickAccess.Services;
using Microsoft.PowerToys.QuickAccess.ViewModels;
using Microsoft.UI.Dispatching;
using Microsoft.UI.Windowing;
using Microsoft.UI.Xaml;
@@ -16,12 +19,16 @@ using WinUIEx;
namespace Microsoft.PowerToys.QuickAccess;
public sealed partial class MainWindow : WindowEx
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;
@@ -30,6 +37,9 @@ public sealed partial class MainWindow : WindowEx
private bool _isWindowCloaked;
private bool _initialActivationHandled;
private bool _isPrimed;
// Prevent auto-hide until the window actually gained focus once.
private bool _hasSeenInteractiveActivation;
private MemoryMappedFile? _positionMap;
private MemoryMappedViewAccessor? _positionView;
private PointInt32? _lastRequestedPosition;
@@ -65,6 +75,11 @@ public sealed partial class MainWindow : WindowEx
_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();
@@ -105,6 +120,18 @@ public sealed partial class MainWindow : WindowEx
}
}
internal void RequestHide()
{
if (_dispatcherQueue.HasThreadAccess)
{
HideWindow();
}
else
{
_dispatcherQueue.TryEnqueue(HideWindow);
}
}
private void InitializeEventListeners()
{
InitializePositionMapping();
@@ -208,10 +235,17 @@ public sealed partial class MainWindow : WindowEx
{
if (args.WindowActivationState == WindowActivationState.Deactivated)
{
if (!_hasSeenInteractiveActivation)
{
return;
}
HideWindow();
return;
}
_hasSeenInteractiveActivation = true;
if (_initialActivationHandled)
{
return;
@@ -224,16 +258,7 @@ public sealed partial class MainWindow : WindowEx
private void OnClosed(object sender, WindowEventArgs e)
{
StopEventListeners();
_showEvent?.Dispose();
_showEvent = null;
_exitEvent?.Dispose();
_exitEvent = null;
DisposePositionResources();
if (_hwnd != IntPtr.Zero)
{
UncloakWindow();
}
Dispose();
}
private void PrimeWindow()
@@ -308,6 +333,42 @@ public sealed partial class MainWindow : WindowEx
}
}
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)
{
UncloakWindow();
}
DisposePositionResources();
_coordinator.Dispose();
}
_disposed = true;
}
[DllImport("user32.dll", EntryPoint = "ShowWindow", SetLastError = true)]
private static extern bool ShowWindowNative(IntPtr hWnd, int nCmdShow);

View File

@@ -1,18 +0,0 @@
<Page
x:Class="Microsoft.PowerToys.QuickAccess.Pages.MainPage"
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:library="using:Microsoft.PowerToys.Settings.UI.Library"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:tkconverters="using:CommunityToolkit.WinUI.Converters"
xmlns:ui="using:CommunityToolkit.WinUI"
mc:Ignorable="d">
<Grid>
<StackPanel HorizontalAlignment="Center" VerticalAlignment="Center" Spacing="8">
<TextBlock Text="Quick Access content placeholder" HorizontalAlignment="Center" />
<TextBlock Text="Replace this with the flyout UI." HorizontalAlignment="Center" />
</StackPanel>
</Grid>
</Page>

View File

@@ -1,16 +0,0 @@
// Copyright (c) Microsoft Corporation
// The Microsoft Corporation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
using Microsoft.UI.Xaml.Controls;
namespace Microsoft.PowerToys.QuickAccess.Pages
{
public sealed partial class MainPage : Page
{
public MainPage()
{
InitializeComponent();
}
}
}