mirror of
https://github.com/microsoft/PowerToys.git
synced 2026-02-23 19:49:43 +01:00
- 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
152 lines
5.6 KiB
C#
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");
|
|
}
|
|
}
|
|
}
|
|
}
|