mirror of
https://github.com/microsoft/PowerToys.git
synced 2025-12-16 03:37:59 +01:00
Refactor PowerDisplay to use Windows Named Events
Replaced IPC-based communication with Windows Named Events for simpler and more reliable process interaction. Introduced the `NativeEventWaiter` helper class to handle event signaling and callbacks. Removed the `PowerDisplayProcessManager` class and refactored process lifecycle management to use direct process launching and event signaling. Simplified `App.xaml.cs` by removing IPC logic and adding event- based handling for window visibility, monitor refresh, settings updates, and termination. Enhanced `MainWindow` initialization and show logic with detailed logging and error handling. Updated `dllmain.cpp` to manage persistent event handles and refactored the `enable` and `disable` methods to use event-based communication. Improved process termination logic with additional checks and logging. Performed general cleanup, including removing unused code, improving readability, and enhancing error handling throughout the codebase.
This commit is contained in:
@@ -203,4 +203,12 @@ namespace winrt::PowerToys::Interop::implementation
|
||||
{
|
||||
return CommonSharedConstants::TERMINATE_POWER_DISPLAY_EVENT;
|
||||
}
|
||||
hstring Constants::RefreshPowerDisplayMonitorsEvent()
|
||||
{
|
||||
return CommonSharedConstants::REFRESH_POWER_DISPLAY_MONITORS_EVENT;
|
||||
}
|
||||
hstring Constants::SettingsUpdatedPowerDisplayEvent()
|
||||
{
|
||||
return CommonSharedConstants::SETTINGS_UPDATED_POWER_DISPLAY_EVENT;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -54,6 +54,8 @@ namespace winrt::PowerToys::Interop::implementation
|
||||
static hstring ShowCmdPalEvent();
|
||||
static hstring ShowPowerDisplayEvent();
|
||||
static hstring TerminatePowerDisplayEvent();
|
||||
static hstring RefreshPowerDisplayMonitorsEvent();
|
||||
static hstring SettingsUpdatedPowerDisplayEvent();
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
@@ -134,6 +134,8 @@ namespace CommonSharedConstants
|
||||
// Path to the events used by PowerDisplay
|
||||
const wchar_t SHOW_POWER_DISPLAY_EVENT[] = L"Local\\PowerToysPowerDisplay-ShowEvent-d8a4e0e3-2c5b-4a1c-9e7f-8b3d6c1a2f4e";
|
||||
const wchar_t TERMINATE_POWER_DISPLAY_EVENT[] = L"Local\\PowerToysPowerDisplay-TerminateEvent-7b9c2e1f-8a5d-4c3e-9f6b-2a1d8c5e3b7a";
|
||||
const wchar_t REFRESH_POWER_DISPLAY_MONITORS_EVENT[] = L"Local\\PowerToysPowerDisplay-RefreshMonitorsEvent-a3f5c8e7-9d1b-4e2f-8c6a-3b5d7e9f1a2c";
|
||||
const wchar_t SETTINGS_UPDATED_POWER_DISPLAY_EVENT[] = L"Local\\PowerToysPowerDisplay-SettingsUpdatedEvent-2e4d6f8a-1c3b-5e7f-9a1d-4c6e8f0b2d3e";
|
||||
|
||||
// used from quick access window
|
||||
const wchar_t CMDPAL_SHOW_EVENT[] = L"Local\\PowerToysCmdPal-ShowEvent-62336fcd-8611-4023-9b30-091a6af4cc5a";
|
||||
|
||||
@@ -0,0 +1,41 @@
|
||||
// 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 Microsoft.UI.Dispatching;
|
||||
|
||||
namespace PowerDisplay.Helpers
|
||||
{
|
||||
/// <summary>
|
||||
/// Helper class for waiting on Windows Named Events (Awake pattern)
|
||||
/// Based on Peek.UI implementation
|
||||
/// </summary>
|
||||
public static class NativeEventWaiter
|
||||
{
|
||||
/// <summary>
|
||||
/// Wait for a Windows Event in a background thread and invoke callback on UI thread when signaled
|
||||
/// </summary>
|
||||
/// <param name="eventName">Name of the Windows Event to wait for</param>
|
||||
/// <param name="callback">Callback to invoke when event is signaled</param>
|
||||
public static void WaitForEventLoop(string eventName, Action callback)
|
||||
{
|
||||
var dispatcherQueue = DispatcherQueue.GetForCurrentThread();
|
||||
var t = new Thread(() =>
|
||||
{
|
||||
var eventHandle = new EventWaitHandle(false, EventResetMode.AutoReset, eventName);
|
||||
while (true)
|
||||
{
|
||||
if (eventHandle.WaitOne())
|
||||
{
|
||||
dispatcherQueue.TryEnqueue(() => callback());
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
t.IsBackground = true;
|
||||
t.Start();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -67,6 +67,7 @@
|
||||
<!-- Removed Common.UI dependency - SettingsDeepLink is now implemented locally -->
|
||||
<ProjectReference Include="..\..\..\common\ManagedCommon\ManagedCommon.csproj" />
|
||||
<ProjectReference Include="..\..\..\common\ManagedCsWin32\ManagedCsWin32.csproj" />
|
||||
<ProjectReference Include="..\..\..\common\interop\PowerToys.Interop.vcxproj" />
|
||||
<ProjectReference Include="..\..\..\settings-ui\Settings.UI.Library\Settings.UI.Library.csproj" />
|
||||
</ItemGroup>
|
||||
<!-- Copy Assets folder to output directory -->
|
||||
|
||||
@@ -14,6 +14,7 @@ using Microsoft.UI.Xaml.Media;
|
||||
using Microsoft.Windows.AppLifecycle;
|
||||
using PowerDisplay.Helpers;
|
||||
using PowerDisplay.Serialization;
|
||||
using PowerToys.Interop;
|
||||
|
||||
namespace PowerDisplay
|
||||
{
|
||||
@@ -24,15 +25,18 @@ namespace PowerDisplay
|
||||
public partial class App : Application
|
||||
#pragma warning restore CA1001
|
||||
{
|
||||
// Windows Event names (from shared_constants.h)
|
||||
private const string ShowPowerDisplayEvent = "Local\\PowerToysPowerDisplay-ShowEvent-d8a4e0e3-2c5b-4a1c-9e7f-8b3d6c1a2f4e";
|
||||
private const string TerminatePowerDisplayEvent = "Local\\PowerToysPowerDisplay-TerminateEvent-7b9c2e1f-8a5d-4c3e-9f6b-2a1d8c5e3b7a";
|
||||
private const string RefreshMonitorsEvent = "Local\\PowerToysPowerDisplay-RefreshMonitorsEvent-a3f5c8e7-9d1b-4e2f-8c6a-3b5d7e9f1a2c";
|
||||
private const string SettingsUpdatedEvent = "Local\\PowerToysPowerDisplay-SettingsUpdatedEvent-2e4d6f8a-1c3b-5e7f-9a1d-4c6e8f0b2d3e";
|
||||
|
||||
private Window? _mainWindow;
|
||||
private int _powerToysRunnerPid;
|
||||
private string _pipeUuid = string.Empty;
|
||||
private CancellationTokenSource? _ipcCancellationTokenSource;
|
||||
|
||||
public App(int runnerPid, string pipeUuid)
|
||||
public App(int runnerPid)
|
||||
{
|
||||
_powerToysRunnerPid = runnerPid;
|
||||
_pipeUuid = pipeUuid;
|
||||
|
||||
this.InitializeComponent();
|
||||
|
||||
@@ -84,84 +88,100 @@ namespace PowerDisplay
|
||||
try
|
||||
{
|
||||
// Single instance is already ensured by AppInstance.FindOrRegisterForKey() in Program.cs
|
||||
// No need for additional Mutex check here
|
||||
// PID is already parsed in Program.cs and passed to constructor
|
||||
|
||||
// Parse command line arguments
|
||||
var cmdArgs = Environment.GetCommandLineArgs();
|
||||
if (cmdArgs?.Length > 1)
|
||||
{
|
||||
// Support two formats: direct PID or --pid PID
|
||||
int pidValue = -1;
|
||||
|
||||
// Check if using --pid format
|
||||
for (int i = 1; i < cmdArgs.Length - 1; i++)
|
||||
// Set up Windows Events monitoring (Awake pattern)
|
||||
NativeEventWaiter.WaitForEventLoop(
|
||||
ShowPowerDisplayEvent,
|
||||
() =>
|
||||
{
|
||||
if (cmdArgs[i] == "--pid" && int.TryParse(cmdArgs[i + 1], out pidValue))
|
||||
Logger.LogInfo("[EVENT] Show event received");
|
||||
Logger.LogInfo($"[EVENT] _mainWindow is null: {_mainWindow == null}");
|
||||
Logger.LogInfo($"[EVENT] _mainWindow type: {_mainWindow?.GetType().Name}");
|
||||
Logger.LogInfo($"[EVENT] Current thread ID: {Environment.CurrentManagedThreadId}");
|
||||
|
||||
// Direct call - NativeEventWaiter already marshalled to UI thread
|
||||
// No need for double DispatcherQueue.TryEnqueue
|
||||
if (_mainWindow is MainWindow mainWindow)
|
||||
{
|
||||
break;
|
||||
Logger.LogInfo("[EVENT] Calling ShowWindow directly");
|
||||
mainWindow.ShowWindow();
|
||||
Logger.LogInfo("[EVENT] ShowWindow returned");
|
||||
}
|
||||
}
|
||||
|
||||
// If not --pid format, try parsing last argument (compatible with old format)
|
||||
if (pidValue == -1 && cmdArgs.Length > 1)
|
||||
{
|
||||
_ = int.TryParse(cmdArgs[cmdArgs.Length - 1], out pidValue);
|
||||
}
|
||||
|
||||
if (pidValue > 0)
|
||||
{
|
||||
_powerToysRunnerPid = pidValue;
|
||||
|
||||
// Started from PowerToys Runner
|
||||
Logger.LogInfo($"PowerDisplay started from PowerToys Runner. Runner pid={_powerToysRunnerPid}");
|
||||
|
||||
// Monitor parent process
|
||||
RunnerHelper.WaitForPowerToysRunner(_powerToysRunnerPid, () =>
|
||||
else
|
||||
{
|
||||
Logger.LogInfo("PowerToys Runner exited. Exiting PowerDisplay");
|
||||
ForceExit();
|
||||
Logger.LogError($"[EVENT] _mainWindow type mismatch, actual type: {_mainWindow?.GetType().Name}");
|
||||
}
|
||||
});
|
||||
|
||||
NativeEventWaiter.WaitForEventLoop(
|
||||
TerminatePowerDisplayEvent,
|
||||
() =>
|
||||
{
|
||||
Logger.LogInfo("Received terminate event - exiting immediately");
|
||||
Environment.Exit(0);
|
||||
});
|
||||
|
||||
NativeEventWaiter.WaitForEventLoop(
|
||||
RefreshMonitorsEvent,
|
||||
() =>
|
||||
{
|
||||
Logger.LogInfo("Received refresh monitors event");
|
||||
_mainWindow?.DispatcherQueue.TryEnqueue(() =>
|
||||
{
|
||||
if (_mainWindow is MainWindow mainWindow && mainWindow.ViewModel != null)
|
||||
{
|
||||
mainWindow.ViewModel.RefreshCommand?.Execute(null);
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
NativeEventWaiter.WaitForEventLoop(
|
||||
SettingsUpdatedEvent,
|
||||
() =>
|
||||
{
|
||||
Logger.LogInfo("Received settings updated event");
|
||||
_mainWindow?.DispatcherQueue.TryEnqueue(() =>
|
||||
{
|
||||
if (_mainWindow is MainWindow mainWindow && mainWindow.ViewModel != null)
|
||||
{
|
||||
_ = mainWindow.ViewModel.ReloadMonitorSettingsAsync();
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
// Monitor Runner process (backup exit mechanism)
|
||||
if (_powerToysRunnerPid > 0)
|
||||
{
|
||||
Logger.LogInfo($"PowerDisplay started from PowerToys Runner. Runner pid={_powerToysRunnerPid}");
|
||||
|
||||
RunnerHelper.WaitForPowerToysRunner(_powerToysRunnerPid, () =>
|
||||
{
|
||||
Logger.LogInfo("PowerToys Runner exited. Exiting PowerDisplay");
|
||||
Environment.Exit(0);
|
||||
});
|
||||
}
|
||||
else
|
||||
{
|
||||
// Standalone mode
|
||||
Logger.LogInfo("PowerDisplay started detached from PowerToys Runner.");
|
||||
_powerToysRunnerPid = -1;
|
||||
}
|
||||
|
||||
// Initialize IPC in background (non-blocking)
|
||||
// Only connect pipes when launched from PowerToys (not standalone)
|
||||
bool isIPCMode = !string.IsNullOrEmpty(_pipeUuid) && _powerToysRunnerPid != -1;
|
||||
|
||||
if (isIPCMode)
|
||||
{
|
||||
// Start IPC message listener in background
|
||||
_ipcCancellationTokenSource = new CancellationTokenSource();
|
||||
_ = Task.Run(() => StartIPCListener(_pipeUuid, _ipcCancellationTokenSource.Token));
|
||||
Logger.LogInfo("Starting IPC pipe listener in background");
|
||||
}
|
||||
else
|
||||
{
|
||||
Logger.LogInfo("Running in standalone mode, IPC disabled");
|
||||
Logger.LogInfo("PowerDisplay started in standalone mode");
|
||||
}
|
||||
|
||||
// Create main window
|
||||
_mainWindow = new MainWindow();
|
||||
|
||||
// Window visibility depends on launch mode
|
||||
// - IPC mode (launched by PowerToys Runner): Start hidden, wait for show_window IPC command
|
||||
// - Standalone mode (no command-line args): Show window immediately
|
||||
if (!isIPCMode)
|
||||
bool isStandaloneMode = _powerToysRunnerPid <= 0;
|
||||
|
||||
if (isStandaloneMode)
|
||||
{
|
||||
// Standalone mode - activate and show window
|
||||
// Standalone mode - activate and show window immediately
|
||||
_mainWindow.Activate();
|
||||
Logger.LogInfo("Window activated (standalone mode)");
|
||||
}
|
||||
else
|
||||
{
|
||||
// IPC mode - window remains inactive (hidden) until show_window command received
|
||||
Logger.LogInfo("Window created but not activated (IPC mode - waiting for show_window command)");
|
||||
// PowerToys mode - window remains hidden until show event received
|
||||
Logger.LogInfo("Window created, waiting for show event (PowerToys mode)");
|
||||
|
||||
// Start background initialization to scan monitors even when hidden
|
||||
_ = Task.Run(async () =>
|
||||
@@ -175,7 +195,7 @@ namespace PowerDisplay
|
||||
if (_mainWindow is MainWindow mainWindow)
|
||||
{
|
||||
await mainWindow.EnsureInitializedAsync();
|
||||
Logger.LogInfo("Background initialization completed (IPC mode)");
|
||||
Logger.LogInfo("Background initialization completed");
|
||||
}
|
||||
});
|
||||
});
|
||||
@@ -187,122 +207,6 @@ namespace PowerDisplay
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Start IPC listener to receive commands from ModuleInterface
|
||||
/// </summary>
|
||||
private async Task StartIPCListener(string pipeUuid, CancellationToken cancellationToken)
|
||||
{
|
||||
try
|
||||
{
|
||||
string pipeName = $"powertoys_powerdisplay_{pipeUuid}";
|
||||
Logger.LogInfo($"Connecting to pipe: {pipeName}");
|
||||
|
||||
await NamedPipeProcessor.ProcessNamedPipeAsync(
|
||||
pipeName,
|
||||
TimeSpan.FromSeconds(5),
|
||||
OnIPCMessageReceived,
|
||||
cancellationToken);
|
||||
}
|
||||
catch (OperationCanceledException)
|
||||
{
|
||||
Logger.LogInfo("IPC listener cancelled");
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Logger.LogError($"Error in IPC listener: {ex.Message}");
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Handle IPC messages received from ModuleInterface
|
||||
/// </summary>
|
||||
private void OnIPCMessageReceived(string message)
|
||||
{
|
||||
try
|
||||
{
|
||||
Logger.LogInfo($"Received IPC message: {message}");
|
||||
|
||||
// Parse JSON command
|
||||
var json = System.Text.Json.JsonDocument.Parse(message);
|
||||
var root = json.RootElement;
|
||||
|
||||
if (root.TryGetProperty("action", out var actionElement))
|
||||
{
|
||||
string action = actionElement.GetString() ?? string.Empty;
|
||||
|
||||
switch (action)
|
||||
{
|
||||
case "show_window":
|
||||
Logger.LogInfo("Received show_window command");
|
||||
_mainWindow?.DispatcherQueue.TryEnqueue(() =>
|
||||
{
|
||||
if (_mainWindow is MainWindow mainWindow)
|
||||
{
|
||||
mainWindow.ShowWindow();
|
||||
}
|
||||
});
|
||||
break;
|
||||
|
||||
case "toggle_window":
|
||||
Logger.LogInfo("Received toggle_window command");
|
||||
_mainWindow?.DispatcherQueue.TryEnqueue(() =>
|
||||
{
|
||||
if (_mainWindow is MainWindow mainWindow)
|
||||
{
|
||||
if (mainWindow.IsWindowVisible())
|
||||
{
|
||||
mainWindow.HideWindow();
|
||||
}
|
||||
else
|
||||
{
|
||||
mainWindow.ShowWindow();
|
||||
}
|
||||
}
|
||||
});
|
||||
break;
|
||||
|
||||
case "refresh_monitors":
|
||||
Logger.LogInfo("Received refresh_monitors command");
|
||||
_mainWindow?.DispatcherQueue.TryEnqueue(() =>
|
||||
{
|
||||
if (_mainWindow is MainWindow mainWindow && mainWindow.ViewModel != null)
|
||||
{
|
||||
mainWindow.ViewModel.RefreshCommand?.Execute(null);
|
||||
}
|
||||
});
|
||||
break;
|
||||
|
||||
case "settings_updated":
|
||||
Logger.LogInfo("Received settings_updated command");
|
||||
_mainWindow?.DispatcherQueue.TryEnqueue(() =>
|
||||
{
|
||||
if (_mainWindow is MainWindow mainWindow && mainWindow.ViewModel != null)
|
||||
{
|
||||
_ = mainWindow.ViewModel.ReloadMonitorSettingsAsync();
|
||||
}
|
||||
});
|
||||
break;
|
||||
|
||||
case "terminate":
|
||||
Logger.LogInfo("Received terminate command");
|
||||
_mainWindow?.DispatcherQueue.TryEnqueue(() =>
|
||||
{
|
||||
Shutdown();
|
||||
});
|
||||
break;
|
||||
|
||||
default:
|
||||
Logger.LogWarning($"Unknown action: {action}");
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Logger.LogError($"Error processing IPC message: {ex.Message}");
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Show startup error
|
||||
/// </summary>
|
||||
@@ -377,32 +281,11 @@ namespace PowerDisplay
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Shutdown application (simplified version following other PowerToys modules pattern)
|
||||
/// Shutdown application (Awake pattern - simple and clean)
|
||||
/// </summary>
|
||||
public void Shutdown()
|
||||
{
|
||||
Logger.LogInfo("PowerDisplay shutting down");
|
||||
|
||||
// Cancel IPC listener
|
||||
_ipcCancellationTokenSource?.Cancel();
|
||||
_ipcCancellationTokenSource?.Dispose();
|
||||
|
||||
// Exit immediately - OS will clean up all resources (pipes, threads, windows, etc.)
|
||||
// Single instance is managed by AppInstance in Program.cs, no manual cleanup needed
|
||||
Environment.Exit(0);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Force exit when PowerToys Runner exits
|
||||
/// </summary>
|
||||
private void ForceExit()
|
||||
{
|
||||
Logger.LogInfo("PowerToys Runner exited, forcing shutdown");
|
||||
|
||||
// Cancel IPC listener
|
||||
_ipcCancellationTokenSource?.Cancel();
|
||||
_ipcCancellationTokenSource?.Dispose();
|
||||
|
||||
Environment.Exit(0);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -240,39 +240,83 @@ namespace PowerDisplay
|
||||
}
|
||||
}
|
||||
|
||||
public async void ShowWindow()
|
||||
public void ShowWindow()
|
||||
{
|
||||
// Ensure window is initialized before showing
|
||||
if (!_hasInitialized)
|
||||
Logger.LogInfo("[SHOWWINDOW] Method entry");
|
||||
Logger.LogInfo($"[SHOWWINDOW] _hasInitialized: {_hasInitialized}");
|
||||
Logger.LogInfo($"[SHOWWINDOW] Current thread ID: {Environment.CurrentManagedThreadId}");
|
||||
|
||||
try
|
||||
{
|
||||
await EnsureInitializedAsync();
|
||||
// If not initialized, log warning but continue showing
|
||||
if (!_hasInitialized)
|
||||
{
|
||||
Logger.LogWarning("[SHOWWINDOW] Window not fully initialized yet, showing anyway");
|
||||
}
|
||||
|
||||
Logger.LogInfo("[SHOWWINDOW] Getting window handle");
|
||||
var hWnd = WinRT.Interop.WindowNative.GetWindowHandle(this);
|
||||
Logger.LogInfo($"[SHOWWINDOW] Window handle: 0x{hWnd:X}");
|
||||
|
||||
Logger.LogInfo("[SHOWWINDOW] Adjusting window size");
|
||||
AdjustWindowSizeToContent();
|
||||
|
||||
Logger.LogInfo("[SHOWWINDOW] Repositioning window");
|
||||
if (_appWindow != null)
|
||||
{
|
||||
PositionWindowAtBottomRight(_appWindow);
|
||||
Logger.LogInfo("[SHOWWINDOW] Window repositioned");
|
||||
}
|
||||
else
|
||||
{
|
||||
Logger.LogWarning("[SHOWWINDOW] _appWindow is null, skipping reposition");
|
||||
}
|
||||
|
||||
Logger.LogInfo("[SHOWWINDOW] Setting opacity to 0 for animation");
|
||||
RootGrid.Opacity = 0;
|
||||
|
||||
Logger.LogInfo("[SHOWWINDOW] Calling this.Activate()");
|
||||
this.Activate();
|
||||
|
||||
Logger.LogInfo("[SHOWWINDOW] Calling WindowHelper.ShowWindow");
|
||||
WindowHelper.ShowWindow(hWnd, true);
|
||||
|
||||
Logger.LogInfo("[SHOWWINDOW] Calling WindowHelpers.BringToForeground");
|
||||
WindowHelpers.BringToForeground(hWnd);
|
||||
|
||||
Logger.LogInfo("[SHOWWINDOW] Checking for animation storyboard");
|
||||
if (RootGrid.Resources.ContainsKey("SlideInStoryboard"))
|
||||
{
|
||||
Logger.LogInfo("[SHOWWINDOW] Starting SlideInStoryboard animation");
|
||||
var slideInStoryboard = RootGrid.Resources["SlideInStoryboard"] as Storyboard;
|
||||
slideInStoryboard?.Begin();
|
||||
}
|
||||
else
|
||||
{
|
||||
Logger.LogWarning("[SHOWWINDOW] SlideInStoryboard not found, setting opacity=1");
|
||||
RootGrid.Opacity = 1;
|
||||
}
|
||||
|
||||
Logger.LogInfo("[SHOWWINDOW] Verifying window visibility");
|
||||
bool isVisible = IsWindowVisible();
|
||||
Logger.LogInfo($"[SHOWWINDOW] IsWindowVisible result: {isVisible}");
|
||||
|
||||
if (!isVisible)
|
||||
{
|
||||
Logger.LogError("[SHOWWINDOW] Window not visible after show, forcing visibility");
|
||||
RootGrid.Opacity = 1;
|
||||
this.Activate();
|
||||
WindowHelpers.BringToForeground(hWnd);
|
||||
}
|
||||
|
||||
Logger.LogInfo("[SHOWWINDOW] Method completed successfully");
|
||||
}
|
||||
|
||||
var hWnd = WinRT.Interop.WindowNative.GetWindowHandle(this);
|
||||
|
||||
// Adjust window size before showing
|
||||
AdjustWindowSizeToContent();
|
||||
|
||||
// Reposition to bottom right (set position before showing)
|
||||
if (_appWindow != null)
|
||||
catch (Exception ex)
|
||||
{
|
||||
PositionWindowAtBottomRight(_appWindow);
|
||||
}
|
||||
|
||||
// Set initial state for animation
|
||||
RootGrid.Opacity = 0;
|
||||
|
||||
// Show window
|
||||
WindowHelper.ShowWindow(hWnd, true);
|
||||
|
||||
// Bring window to foreground
|
||||
PInvoke.SetForegroundWindow(hWnd);
|
||||
|
||||
// Use storyboard animation for window entrance
|
||||
if (RootGrid.Resources.ContainsKey("SlideInStoryboard"))
|
||||
{
|
||||
var slideInStoryboard = RootGrid.Resources["SlideInStoryboard"] as Storyboard;
|
||||
slideInStoryboard?.Begin();
|
||||
Logger.LogError($"[SHOWWINDOW] Exception: {ex.GetType().Name}");
|
||||
Logger.LogError($"[SHOWWINDOW] Exception message: {ex.Message}");
|
||||
Logger.LogError($"[SHOWWINDOW] Stack trace: {ex.StackTrace}");
|
||||
throw;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -19,24 +19,24 @@ namespace PowerDisplay
|
||||
|
||||
WinRT.ComWrappersSupport.InitializeComWrappers();
|
||||
|
||||
// Parse command line arguments: args[0] = runner_pid, args[1] = pipe_uuid
|
||||
// Parse command line arguments: args[0] = runner_pid (Awake pattern)
|
||||
int runnerPid = -1;
|
||||
string pipeUuid = string.Empty;
|
||||
|
||||
if (args.Length >= 2)
|
||||
if (args.Length >= 1)
|
||||
{
|
||||
if (int.TryParse(args[0], out int parsedPid))
|
||||
{
|
||||
runnerPid = parsedPid;
|
||||
Logger.LogInfo($"PowerDisplay started with runner_pid={runnerPid}");
|
||||
}
|
||||
else
|
||||
{
|
||||
Logger.LogWarning($"Failed to parse PID from args[0]: {args[0]}");
|
||||
}
|
||||
|
||||
pipeUuid = args[1];
|
||||
Logger.LogInfo($"PowerDisplay started with runner_pid={runnerPid}, pipe_uuid={pipeUuid}");
|
||||
}
|
||||
else
|
||||
{
|
||||
Logger.LogWarning("PowerDisplay started without command line arguments");
|
||||
Logger.LogWarning($"PowerDisplay started with insufficient arguments (expected 2, got {args.Length}). Running in standalone mode.");
|
||||
Logger.LogWarning("PowerDisplay started without runner PID. Running in standalone mode.");
|
||||
}
|
||||
|
||||
var instanceKey = AppInstance.FindOrRegisterForKey("PowerToys_PowerDisplay_Instance");
|
||||
@@ -47,7 +47,7 @@ namespace PowerDisplay
|
||||
{
|
||||
var context = new DispatcherQueueSynchronizationContext(DispatcherQueue.GetForCurrentThread());
|
||||
SynchronizationContext.SetSynchronizationContext(context);
|
||||
_ = new App(runnerPid, pipeUuid);
|
||||
_ = new App(runnerPid);
|
||||
});
|
||||
}
|
||||
else
|
||||
|
||||
@@ -90,7 +90,6 @@
|
||||
<ItemGroup>
|
||||
<ClInclude Include="Constants.h" />
|
||||
<ClInclude Include="pch.h" />
|
||||
<ClInclude Include="PowerDisplayProcessManager.h" />
|
||||
<ClInclude Include="resource.h" />
|
||||
<ClInclude Include="trace.h" />
|
||||
</ItemGroup>
|
||||
@@ -99,7 +98,6 @@
|
||||
<ClCompile Include="pch.cpp">
|
||||
<PrecompiledHeader Condition="'$(UsePrecompiledHeaders)' != 'false'">Create</PrecompiledHeader>
|
||||
</ClCompile>
|
||||
<ClCompile Include="PowerDisplayProcessManager.cpp" />
|
||||
<ClCompile Include="trace.cpp" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
|
||||
@@ -105,18 +105,46 @@ bool PowerDisplayProcessManager::is_process_running() const
|
||||
|
||||
void PowerDisplayProcessManager::terminate_process()
|
||||
{
|
||||
// Close pipe
|
||||
m_write_pipe.reset();
|
||||
|
||||
// Terminate process
|
||||
// Terminate process if still running
|
||||
if (m_hProcess != nullptr)
|
||||
{
|
||||
TerminateProcess(m_hProcess, 1);
|
||||
// Check if process is still running
|
||||
if (WaitForSingleObject(m_hProcess, 0) == WAIT_TIMEOUT)
|
||||
{
|
||||
Logger::trace(L"Process still running, calling TerminateProcess");
|
||||
|
||||
// Force terminate the process
|
||||
if (TerminateProcess(m_hProcess, 1))
|
||||
{
|
||||
// Wait a bit to ensure process is terminated
|
||||
DWORD wait_result = WaitForSingleObject(m_hProcess, 1000);
|
||||
if (wait_result == WAIT_OBJECT_0)
|
||||
{
|
||||
Logger::trace(L"PowerDisplay process successfully terminated");
|
||||
}
|
||||
else
|
||||
{
|
||||
Logger::error(L"TerminateProcess succeeded but process did not exit within timeout");
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
Logger::error(L"TerminateProcess failed: {}", get_last_error_or_default(GetLastError()));
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
Logger::trace(L"PowerDisplay process already exited gracefully");
|
||||
}
|
||||
|
||||
// Clean up process handle
|
||||
CloseHandle(m_hProcess);
|
||||
m_hProcess = nullptr;
|
||||
}
|
||||
|
||||
Logger::trace(L"PowerDisplay process terminated");
|
||||
// Close pipe after process is terminated
|
||||
m_write_pipe.reset();
|
||||
Logger::trace(L"PowerDisplay process cleanup complete");
|
||||
}
|
||||
|
||||
HRESULT PowerDisplayProcessManager::start_process(const std::wstring& pipe_uuid)
|
||||
@@ -256,24 +284,60 @@ void PowerDisplayProcessManager::refresh()
|
||||
// Stop PowerDisplay process
|
||||
Logger::trace(L"Stopping PowerDisplay process");
|
||||
|
||||
// Send terminate message
|
||||
send_message_to_powerdisplay(L"{\"action\":\"terminate\"}");
|
||||
|
||||
// Wait for graceful exit
|
||||
if (m_hProcess != nullptr)
|
||||
// Send terminate message synchronously (not through thread executor)
|
||||
// This ensures the message is sent before we wait for process exit
|
||||
if (m_write_pipe)
|
||||
{
|
||||
WaitForSingleObject(m_hProcess, 2000);
|
||||
}
|
||||
try
|
||||
{
|
||||
const auto message = L"{\"action\":\"terminate\"}";
|
||||
const auto formatted = std::format(L"{}\r\n", message);
|
||||
|
||||
if (is_process_running())
|
||||
{
|
||||
Logger::warn(L"PowerDisplay process failed to gracefully exit, terminating");
|
||||
// Match WinUI side which reads the pipe using UTF-16 (Encoding.Unicode)
|
||||
const CString payload(formatted.c_str());
|
||||
const DWORD bytes_to_write = static_cast<DWORD>(payload.GetLength() * sizeof(wchar_t));
|
||||
DWORD bytes_written = 0;
|
||||
|
||||
if (SUCCEEDED(m_write_pipe->Write(payload, bytes_to_write, &bytes_written)))
|
||||
{
|
||||
Logger::trace(L"Sent terminate message to PowerDisplay");
|
||||
}
|
||||
else
|
||||
{
|
||||
Logger::warn(L"Failed to send terminate message to PowerDisplay");
|
||||
}
|
||||
}
|
||||
catch (...)
|
||||
{
|
||||
Logger::warn(L"Exception while sending terminate message to PowerDisplay");
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
Logger::trace(L"PowerDisplay process successfully exited");
|
||||
Logger::warn(L"Cannot send terminate message: pipe not connected");
|
||||
}
|
||||
|
||||
// Wait for graceful exit (use longer timeout like AdvancedPaste)
|
||||
if (m_hProcess != nullptr)
|
||||
{
|
||||
Logger::trace(L"Waiting for PowerDisplay process to exit gracefully");
|
||||
DWORD wait_result = WaitForSingleObject(m_hProcess, 5000);
|
||||
|
||||
if (wait_result == WAIT_OBJECT_0)
|
||||
{
|
||||
Logger::trace(L"PowerDisplay process exited gracefully");
|
||||
}
|
||||
else if (wait_result == WAIT_TIMEOUT)
|
||||
{
|
||||
Logger::warn(L"PowerDisplay process failed to exit within timeout, will force terminate");
|
||||
}
|
||||
else
|
||||
{
|
||||
Logger::error(L"WaitForSingleObject failed with error: {}", get_last_error_or_default(GetLastError()));
|
||||
}
|
||||
}
|
||||
|
||||
// Clean up (will force terminate if still running)
|
||||
terminate_process();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -11,7 +11,6 @@
|
||||
|
||||
#include "resource.h"
|
||||
#include "Constants.h"
|
||||
#include "PowerDisplayProcessManager.h"
|
||||
|
||||
extern "C" IMAGE_DOS_HEADER __ImageBase;
|
||||
|
||||
@@ -55,8 +54,12 @@ private:
|
||||
bool m_hotkey_enabled = false;
|
||||
Hotkey m_activation_hotkey = { .win = true, .ctrl = false, .shift = false, .alt = true, .key = 'M' };
|
||||
|
||||
// Process manager for handling PowerDisplay.exe lifecycle and IPC
|
||||
PowerDisplayProcessManager m_process_manager;
|
||||
// Windows Events for IPC (persistent handles - ColorPicker pattern)
|
||||
HANDLE m_hProcess = nullptr;
|
||||
HANDLE m_hInvokeEvent = nullptr;
|
||||
HANDLE m_hTerminateEvent = nullptr;
|
||||
HANDLE m_hRefreshEvent = nullptr;
|
||||
HANDLE m_hSettingsUpdatedEvent = nullptr;
|
||||
|
||||
void parse_hotkey_settings(PowerToysSettings::PowerToyValues settings)
|
||||
{
|
||||
@@ -143,6 +146,42 @@ private:
|
||||
}
|
||||
}
|
||||
|
||||
// Helper method to check if PowerDisplay.exe process is still running
|
||||
bool is_process_running()
|
||||
{
|
||||
if (m_hProcess == nullptr)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
return WaitForSingleObject(m_hProcess, 0) == WAIT_TIMEOUT;
|
||||
}
|
||||
|
||||
// Helper method to launch PowerDisplay.exe process
|
||||
void launch_process()
|
||||
{
|
||||
Logger::trace(L"Starting PowerDisplay process");
|
||||
unsigned long powertoys_pid = GetCurrentProcessId();
|
||||
|
||||
std::wstring executable_args = std::to_wstring(powertoys_pid);
|
||||
|
||||
SHELLEXECUTEINFOW sei{ sizeof(sei) };
|
||||
sei.fMask = SEE_MASK_NOCLOSEPROCESS | SEE_MASK_FLAG_NO_UI;
|
||||
sei.lpFile = L"WinUI3Apps\\PowerToys.PowerDisplay.exe";
|
||||
sei.nShow = SW_SHOWNORMAL;
|
||||
sei.lpParameters = executable_args.data();
|
||||
|
||||
if (ShellExecuteExW(&sei))
|
||||
{
|
||||
Logger::trace(L"Successfully started PowerDisplay process");
|
||||
m_hProcess = sei.hProcess;
|
||||
}
|
||||
else
|
||||
{
|
||||
Logger::error(L"PowerDisplay process failed to start. {}",
|
||||
get_last_error_or_default(GetLastError()));
|
||||
}
|
||||
}
|
||||
|
||||
public:
|
||||
PowerDisplayModule()
|
||||
{
|
||||
@@ -151,27 +190,51 @@ public:
|
||||
|
||||
init_settings();
|
||||
|
||||
// Note: PowerDisplay.exe will send messages directly to runner via named pipes
|
||||
// The runner's message_receiver_thread will handle routing to Settings UI
|
||||
// No need to set a callback here - the process manager just manages lifecycle
|
||||
// Create all Windows Events (persistent handles - ColorPicker pattern)
|
||||
m_hInvokeEvent = CreateDefaultEvent(CommonSharedConstants::SHOW_POWER_DISPLAY_EVENT);
|
||||
m_hTerminateEvent = CreateDefaultEvent(CommonSharedConstants::TERMINATE_POWER_DISPLAY_EVENT);
|
||||
m_hRefreshEvent = CreateDefaultEvent(CommonSharedConstants::REFRESH_POWER_DISPLAY_MONITORS_EVENT);
|
||||
m_hSettingsUpdatedEvent = CreateDefaultEvent(CommonSharedConstants::SETTINGS_UPDATED_POWER_DISPLAY_EVENT);
|
||||
|
||||
if (!m_hInvokeEvent || !m_hTerminateEvent || !m_hRefreshEvent || !m_hSettingsUpdatedEvent)
|
||||
{
|
||||
Logger::error(L"Failed to create one or more event handles");
|
||||
}
|
||||
}
|
||||
|
||||
~PowerDisplayModule()
|
||||
{
|
||||
if (m_enabled)
|
||||
{
|
||||
m_process_manager.stop();
|
||||
disable();
|
||||
}
|
||||
|
||||
// Clean up all event handles
|
||||
if (m_hInvokeEvent)
|
||||
{
|
||||
CloseHandle(m_hInvokeEvent);
|
||||
m_hInvokeEvent = nullptr;
|
||||
}
|
||||
if (m_hTerminateEvent)
|
||||
{
|
||||
CloseHandle(m_hTerminateEvent);
|
||||
m_hTerminateEvent = nullptr;
|
||||
}
|
||||
if (m_hRefreshEvent)
|
||||
{
|
||||
CloseHandle(m_hRefreshEvent);
|
||||
m_hRefreshEvent = nullptr;
|
||||
}
|
||||
if (m_hSettingsUpdatedEvent)
|
||||
{
|
||||
CloseHandle(m_hSettingsUpdatedEvent);
|
||||
m_hSettingsUpdatedEvent = nullptr;
|
||||
}
|
||||
m_enabled = false;
|
||||
}
|
||||
|
||||
virtual void destroy() override
|
||||
{
|
||||
Logger::trace("PowerDisplay::destroy()");
|
||||
if (m_enabled)
|
||||
{
|
||||
m_process_manager.stop();
|
||||
}
|
||||
delete this;
|
||||
}
|
||||
|
||||
@@ -209,14 +272,33 @@ public:
|
||||
|
||||
if (action_object.get_name() == L"Launch")
|
||||
{
|
||||
Logger::trace(L"Launch action received, sending show_window command");
|
||||
m_process_manager.send_message_to_powerdisplay(L"{\"action\":\"show_window\"}");
|
||||
Logger::trace(L"Launch action received");
|
||||
|
||||
// ColorPicker pattern: check if process is running, re-launch if needed
|
||||
if (!is_process_running())
|
||||
{
|
||||
Logger::trace(L"PowerDisplay process not running, re-launching");
|
||||
launch_process();
|
||||
}
|
||||
|
||||
if (m_hInvokeEvent)
|
||||
{
|
||||
Logger::trace(L"Signaling show event");
|
||||
SetEvent(m_hInvokeEvent);
|
||||
}
|
||||
Trace::ActivatePowerDisplay();
|
||||
}
|
||||
else if (action_object.get_name() == L"RefreshMonitors")
|
||||
{
|
||||
Logger::trace(L"RefreshMonitors action received");
|
||||
m_process_manager.send_message_to_powerdisplay(L"{\"action\":\"refresh_monitors\"}");
|
||||
Logger::trace(L"RefreshMonitors action received, signaling refresh event");
|
||||
if (m_hRefreshEvent)
|
||||
{
|
||||
SetEvent(m_hRefreshEvent);
|
||||
}
|
||||
else
|
||||
{
|
||||
Logger::warn(L"Refresh event handle is null");
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (std::exception&)
|
||||
@@ -236,9 +318,16 @@ public:
|
||||
parse_activation_hotkey(values);
|
||||
values.save_to_settings_file();
|
||||
|
||||
// Notify PowerDisplay.exe that settings have been updated (signal only, no config data)
|
||||
// PowerDisplay will read the updated settings.json file itself
|
||||
m_process_manager.send_message_to_powerdisplay(L"{\"action\":\"settings_updated\"}");
|
||||
// Signal settings updated event
|
||||
if (m_hSettingsUpdatedEvent)
|
||||
{
|
||||
Logger::trace(L"Signaling settings updated event");
|
||||
SetEvent(m_hSettingsUpdatedEvent);
|
||||
}
|
||||
else
|
||||
{
|
||||
Logger::warn(L"Settings updated event handle is null");
|
||||
}
|
||||
}
|
||||
catch (std::exception&)
|
||||
{
|
||||
@@ -248,19 +337,43 @@ public:
|
||||
|
||||
virtual void enable() override
|
||||
{
|
||||
Logger::trace(L"PowerDisplay::enable()");
|
||||
m_enabled = true;
|
||||
Trace::EnablePowerDisplay(true);
|
||||
|
||||
Logger::trace(L"PowerDisplay enabled, starting process manager");
|
||||
m_process_manager.start();
|
||||
// Launch PowerDisplay.exe with PID only (Awake pattern)
|
||||
launch_process();
|
||||
}
|
||||
|
||||
virtual void disable() override
|
||||
{
|
||||
Logger::trace(L"PowerDisplay::disable()");
|
||||
|
||||
if (m_enabled)
|
||||
{
|
||||
Logger::trace(L"Disabling Power Display...");
|
||||
m_process_manager.stop();
|
||||
// Reset invoke event to prevent accidental activation during shutdown
|
||||
if (m_hInvokeEvent)
|
||||
{
|
||||
ResetEvent(m_hInvokeEvent);
|
||||
}
|
||||
|
||||
// Signal terminate event
|
||||
if (m_hTerminateEvent)
|
||||
{
|
||||
Logger::trace(L"Signaling PowerDisplay to exit");
|
||||
SetEvent(m_hTerminateEvent);
|
||||
}
|
||||
else
|
||||
{
|
||||
Logger::warn(L"Terminate event handle is null");
|
||||
}
|
||||
|
||||
// Close process handle (don't wait, don't force terminate - Awake pattern)
|
||||
if (m_hProcess)
|
||||
{
|
||||
CloseHandle(m_hProcess);
|
||||
m_hProcess = nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
m_enabled = false;
|
||||
@@ -274,11 +387,19 @@ public:
|
||||
|
||||
virtual bool on_hotkey(size_t /*hotkeyId*/) override
|
||||
{
|
||||
if (m_enabled)
|
||||
if (m_enabled && m_hInvokeEvent)
|
||||
{
|
||||
Logger::trace(L"Power Display hotkey pressed");
|
||||
// Send toggle window command
|
||||
m_process_manager.send_message_to_powerdisplay(L"{\"action\":\"toggle_window\"}");
|
||||
|
||||
// ColorPicker pattern: check if process is running, re-launch if needed
|
||||
if (!is_process_running())
|
||||
{
|
||||
Logger::trace(L"PowerDisplay process not running, re-launching");
|
||||
launch_process();
|
||||
}
|
||||
|
||||
Logger::trace(L"Signaling show event");
|
||||
SetEvent(m_hInvokeEvent);
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user