Files
PowerToys/src/modules/powerdisplay/PowerDisplay/Program.cs
Yu Leng 95a4f0f01b Add Quick Access launcher support for PowerDisplay
- Add TOGGLE_POWER_DISPLAY_EVENT and listener thread to PowerDisplay module for Quick Access integration
- Implement TogglePowerDisplay() to launch or toggle window as needed
- Update QuickAccessLauncher to signal new event for PowerDisplay
- Ensure process starts on message send in PowerDisplayProcessManager
- Change OnActivated to toggle window on redirect activation
- Only show monitor controls if both enabled and hardware-supported
- Add proper cleanup for new event and thread resources
2026-02-03 16:31:22 +08:00

152 lines
5.6 KiB
C#

// 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.UI.Dispatching;
using Microsoft.Windows.AppLifecycle;
namespace PowerDisplay
{
public static partial class Program
{
private static App? _app;
// LibraryImport for AOT compatibility - COM wait constants
private const uint CowaitDefault = 0;
private const uint InfiniteTimeout = 0xFFFFFFFF;
[LibraryImport("ole32.dll")]
private static partial int CoWaitForMultipleObjects(
uint dwFlags,
uint dwTimeout,
int cHandles,
nint[] pHandles,
out uint lpdwIndex);
[STAThread]
public static int Main(string[] args)
{
// Initialize COM wrappers first (needed for AppInstance)
WinRT.ComWrappersSupport.InitializeComWrappers();
// Single instance check BEFORE logger initialization to avoid creating extra log files
// Command Palette pattern: check for existing instance first
var activationArgs = AppInstance.GetCurrent().GetActivatedEventArgs();
var keyInstance = AppInstance.FindOrRegisterForKey("PowerToys_PowerDisplay_Instance");
if (!keyInstance.IsCurrent)
{
// Another instance exists - redirect and exit WITHOUT initializing logger
// This prevents creation of extra log files for short-lived redirect processes
RedirectActivationTo(activationArgs, keyInstance);
return 0;
}
// This is the primary instance - now initialize logger
Logger.InitializeLogger("\\PowerDisplay\\Logs");
Logger.LogInfo("PowerDisplay starting");
// Register activation handler for future redirects
keyInstance.Activated += OnActivated;
// Parse command line arguments:
// args[0] = runner_pid (Awake pattern)
// args[1] = pipe_name (Named Pipe for IPC with module DLL)
int runnerPid = -1;
string? pipeName = null;
if (args.Length >= 1)
{
if (int.TryParse(args[0], out int parsedPid))
{
runnerPid = parsedPid;
}
}
if (args.Length >= 2)
{
pipeName = args[1];
}
Microsoft.UI.Xaml.Application.Start((p) =>
{
var context = new DispatcherQueueSynchronizationContext(DispatcherQueue.GetForCurrentThread());
SynchronizationContext.SetSynchronizationContext(context);
_app = new App(runnerPid, pipeName);
});
return 0;
}
/// <summary>
/// Redirect activation to existing instance (Command Palette pattern)
/// Called BEFORE logger is initialized, so no logging here
/// </summary>
private static void RedirectActivationTo(AppActivationArguments args, AppInstance keyInstance)
{
// Do the redirection on another thread, and use a non-blocking
// wait method to wait for the redirection to complete.
using var redirectSemaphore = new Semaphore(0, 1);
var redirectTimeout = TimeSpan.FromSeconds(10);
_ = Task.Run(() =>
{
using var cts = new CancellationTokenSource(redirectTimeout);
try
{
keyInstance.RedirectActivationToAsync(args)
.AsTask(cts.Token)
.GetAwaiter()
.GetResult();
}
catch
{
// Silently ignore errors - logger not initialized yet
}
finally
{
redirectSemaphore.Release();
}
});
// Use CoWaitForMultipleObjects to pump COM messages while waiting
nint[] handles = [redirectSemaphore.SafeWaitHandle.DangerousGetHandle()];
_ = CoWaitForMultipleObjects(
CowaitDefault,
InfiniteTimeout,
1,
handles,
out _);
}
/// <summary>
/// Called when an existing instance is activated by another process.
/// This happens when Quick Access or other launchers start the process while one is already running.
/// We toggle the window to show it - this allows Quick Access launch to work properly.
/// </summary>
private static void OnActivated(object? sender, AppActivationArguments args)
{
Logger.LogInfo("OnActivated: Redirect activation received - toggling window");
// Toggle the main window on redirect activation
if (_app?.MainWindow is MainWindow mainWindow)
{
// Dispatch to UI thread since OnActivated may be called from a different thread
mainWindow.DispatcherQueue.TryEnqueue(() =>
{
Logger.LogTrace("OnActivated: Toggling window from redirect activation");
mainWindow.ToggleWindow();
});
}
else
{
Logger.LogWarning("OnActivated: MainWindow not available for toggle");
}
}
}
}