2025-10-20 16:22:47 +08:00
|
|
|
// 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 System.Threading.Tasks;
|
|
|
|
|
using ManagedCommon;
|
|
|
|
|
using Microsoft.PowerToys.Telemetry;
|
|
|
|
|
using Microsoft.UI.Dispatching;
|
|
|
|
|
using Microsoft.UI.Xaml;
|
|
|
|
|
using Microsoft.UI.Xaml.Controls;
|
|
|
|
|
using Microsoft.UI.Xaml.Media;
|
|
|
|
|
using Microsoft.Windows.AppLifecycle;
|
|
|
|
|
using PowerDisplay.Helpers;
|
|
|
|
|
using PowerDisplay.Serialization;
|
2025-11-13 12:40:56 +08:00
|
|
|
using PowerToys.Interop;
|
2025-10-20 16:22:47 +08:00
|
|
|
|
|
|
|
|
namespace PowerDisplay
|
|
|
|
|
{
|
|
|
|
|
/// <summary>
|
|
|
|
|
/// PowerDisplay application main class
|
|
|
|
|
/// </summary>
|
2025-11-12 16:36:43 +08:00
|
|
|
#pragma warning disable CA1001 // CancellationTokenSource is disposed in Shutdown/ForceExit methods
|
2025-10-20 16:22:47 +08:00
|
|
|
public partial class App : Application
|
2025-11-12 16:36:43 +08:00
|
|
|
#pragma warning restore CA1001
|
2025-10-20 16:22:47 +08:00
|
|
|
{
|
2025-11-13 12:40:56 +08:00
|
|
|
// 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";
|
|
|
|
|
|
2025-10-20 16:22:47 +08:00
|
|
|
private Window? _mainWindow;
|
|
|
|
|
private int _powerToysRunnerPid;
|
|
|
|
|
|
2025-11-13 12:40:56 +08:00
|
|
|
public App(int runnerPid)
|
2025-10-20 16:22:47 +08:00
|
|
|
{
|
|
|
|
|
_powerToysRunnerPid = runnerPid;
|
|
|
|
|
|
|
|
|
|
this.InitializeComponent();
|
|
|
|
|
|
|
|
|
|
// Ensure types used in XAML are preserved for AOT compilation
|
|
|
|
|
TypePreservation.PreserveTypes();
|
|
|
|
|
|
|
|
|
|
// Initialize Logger
|
|
|
|
|
Logger.InitializeLogger("\\PowerDisplay\\Logs");
|
|
|
|
|
|
|
|
|
|
// Initialize PowerToys telemetry
|
|
|
|
|
try
|
|
|
|
|
{
|
|
|
|
|
PowerToysTelemetry.Log.WriteEvent(new Telemetry.Events.PowerDisplayStartEvent());
|
|
|
|
|
}
|
|
|
|
|
catch
|
|
|
|
|
{
|
|
|
|
|
// Telemetry errors should not crash the app
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Initialize language settings
|
|
|
|
|
string appLanguage = LanguageHelper.LoadLanguage();
|
|
|
|
|
if (!string.IsNullOrEmpty(appLanguage))
|
|
|
|
|
{
|
|
|
|
|
Microsoft.Windows.Globalization.ApplicationLanguages.PrimaryLanguageOverride = appLanguage;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Handle unhandled exceptions
|
|
|
|
|
this.UnhandledException += OnUnhandledException;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
/// Handle unhandled exceptions
|
|
|
|
|
/// </summary>
|
|
|
|
|
private void OnUnhandledException(object sender, Microsoft.UI.Xaml.UnhandledExceptionEventArgs e)
|
|
|
|
|
{
|
|
|
|
|
// Try to display error information
|
|
|
|
|
ShowStartupError(e.Exception);
|
|
|
|
|
|
|
|
|
|
// Mark exception as handled to prevent app crash
|
|
|
|
|
e.Handled = true;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
/// Called when the application is launched
|
|
|
|
|
/// </summary>
|
|
|
|
|
/// <param name="args">Launch arguments</param>
|
|
|
|
|
protected override void OnLaunched(Microsoft.UI.Xaml.LaunchActivatedEventArgs args)
|
|
|
|
|
{
|
|
|
|
|
try
|
|
|
|
|
{
|
2025-11-12 16:36:43 +08:00
|
|
|
// Single instance is already ensured by AppInstance.FindOrRegisterForKey() in Program.cs
|
2025-11-13 12:40:56 +08:00
|
|
|
// PID is already parsed in Program.cs and passed to constructor
|
2025-10-20 16:22:47 +08:00
|
|
|
|
2025-11-13 12:40:56 +08:00
|
|
|
// Set up Windows Events monitoring (Awake pattern)
|
|
|
|
|
NativeEventWaiter.WaitForEventLoop(
|
|
|
|
|
ShowPowerDisplayEvent,
|
|
|
|
|
() =>
|
2025-10-20 16:22:47 +08:00
|
|
|
{
|
2025-11-13 12:40:56 +08:00
|
|
|
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)
|
|
|
|
|
{
|
|
|
|
|
Logger.LogInfo("[EVENT] Calling ShowWindow directly");
|
|
|
|
|
mainWindow.ShowWindow();
|
|
|
|
|
Logger.LogInfo("[EVENT] ShowWindow returned");
|
|
|
|
|
}
|
|
|
|
|
else
|
2025-10-20 16:22:47 +08:00
|
|
|
{
|
2025-11-13 12:40:56 +08:00
|
|
|
Logger.LogError($"[EVENT] _mainWindow type mismatch, actual type: {_mainWindow?.GetType().Name}");
|
2025-10-20 16:22:47 +08:00
|
|
|
}
|
2025-11-13 12:40:56 +08:00
|
|
|
});
|
2025-10-20 16:22:47 +08:00
|
|
|
|
2025-11-13 12:40:56 +08:00
|
|
|
NativeEventWaiter.WaitForEventLoop(
|
|
|
|
|
TerminatePowerDisplayEvent,
|
|
|
|
|
() =>
|
2025-10-20 16:22:47 +08:00
|
|
|
{
|
2025-11-13 12:40:56 +08:00
|
|
|
Logger.LogInfo("Received terminate event - exiting immediately");
|
|
|
|
|
Environment.Exit(0);
|
|
|
|
|
});
|
2025-10-20 16:22:47 +08:00
|
|
|
|
2025-11-13 12:40:56 +08:00
|
|
|
NativeEventWaiter.WaitForEventLoop(
|
|
|
|
|
RefreshMonitorsEvent,
|
|
|
|
|
() =>
|
2025-10-20 16:22:47 +08:00
|
|
|
{
|
2025-11-13 12:40:56 +08:00
|
|
|
Logger.LogInfo("Received refresh monitors event");
|
|
|
|
|
_mainWindow?.DispatcherQueue.TryEnqueue(() =>
|
2025-10-20 16:22:47 +08:00
|
|
|
{
|
2025-11-13 12:40:56 +08:00
|
|
|
if (_mainWindow is MainWindow mainWindow && mainWindow.ViewModel != null)
|
|
|
|
|
{
|
|
|
|
|
mainWindow.ViewModel.RefreshCommand?.Execute(null);
|
|
|
|
|
}
|
2025-10-20 16:22:47 +08:00
|
|
|
});
|
2025-11-13 12:40:56 +08:00
|
|
|
});
|
2025-10-20 16:22:47 +08:00
|
|
|
|
2025-11-13 12:40:56 +08:00
|
|
|
NativeEventWaiter.WaitForEventLoop(
|
|
|
|
|
SettingsUpdatedEvent,
|
|
|
|
|
() =>
|
|
|
|
|
{
|
|
|
|
|
Logger.LogInfo("Received settings updated event");
|
|
|
|
|
_mainWindow?.DispatcherQueue.TryEnqueue(() =>
|
|
|
|
|
{
|
|
|
|
|
if (_mainWindow is MainWindow mainWindow && mainWindow.ViewModel != null)
|
|
|
|
|
{
|
|
|
|
|
_ = mainWindow.ViewModel.ReloadMonitorSettingsAsync();
|
|
|
|
|
}
|
|
|
|
|
});
|
|
|
|
|
});
|
2025-11-12 13:18:36 +08:00
|
|
|
|
2025-11-13 12:40:56 +08:00
|
|
|
// Monitor Runner process (backup exit mechanism)
|
|
|
|
|
if (_powerToysRunnerPid > 0)
|
2025-10-20 16:22:47 +08:00
|
|
|
{
|
2025-11-13 12:40:56 +08:00
|
|
|
Logger.LogInfo($"PowerDisplay started from PowerToys Runner. Runner pid={_powerToysRunnerPid}");
|
|
|
|
|
|
|
|
|
|
RunnerHelper.WaitForPowerToysRunner(_powerToysRunnerPid, () =>
|
|
|
|
|
{
|
|
|
|
|
Logger.LogInfo("PowerToys Runner exited. Exiting PowerDisplay");
|
|
|
|
|
Environment.Exit(0);
|
|
|
|
|
});
|
2025-10-20 16:22:47 +08:00
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
2025-11-13 12:40:56 +08:00
|
|
|
Logger.LogInfo("PowerDisplay started in standalone mode");
|
2025-10-20 16:22:47 +08:00
|
|
|
}
|
|
|
|
|
|
2025-11-12 13:18:36 +08:00
|
|
|
// Create main window
|
2025-10-20 16:22:47 +08:00
|
|
|
_mainWindow = new MainWindow();
|
2025-11-12 13:18:36 +08:00
|
|
|
|
2025-11-12 16:36:43 +08:00
|
|
|
// Window visibility depends on launch mode
|
2025-11-13 12:40:56 +08:00
|
|
|
bool isStandaloneMode = _powerToysRunnerPid <= 0;
|
|
|
|
|
|
|
|
|
|
if (isStandaloneMode)
|
2025-11-12 13:18:36 +08:00
|
|
|
{
|
2025-11-13 12:40:56 +08:00
|
|
|
// Standalone mode - activate and show window immediately
|
2025-11-12 13:18:36 +08:00
|
|
|
_mainWindow.Activate();
|
|
|
|
|
Logger.LogInfo("Window activated (standalone mode)");
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
2025-11-13 12:40:56 +08:00
|
|
|
// PowerToys mode - window remains hidden until show event received
|
|
|
|
|
Logger.LogInfo("Window created, waiting for show event (PowerToys mode)");
|
2025-11-12 16:36:43 +08:00
|
|
|
|
|
|
|
|
// Start background initialization to scan monitors even when hidden
|
|
|
|
|
_ = Task.Run(async () =>
|
|
|
|
|
{
|
|
|
|
|
// Give window a moment to finish construction
|
|
|
|
|
await Task.Delay(500);
|
|
|
|
|
|
|
|
|
|
// Trigger initialization on UI thread
|
|
|
|
|
_mainWindow?.DispatcherQueue.TryEnqueue(async () =>
|
|
|
|
|
{
|
|
|
|
|
if (_mainWindow is MainWindow mainWindow)
|
|
|
|
|
{
|
|
|
|
|
await mainWindow.EnsureInitializedAsync();
|
2025-11-13 12:40:56 +08:00
|
|
|
Logger.LogInfo("Background initialization completed");
|
2025-11-12 16:36:43 +08:00
|
|
|
}
|
|
|
|
|
});
|
|
|
|
|
});
|
2025-11-12 13:18:36 +08:00
|
|
|
}
|
2025-10-20 16:22:47 +08:00
|
|
|
}
|
|
|
|
|
catch (Exception ex)
|
|
|
|
|
{
|
|
|
|
|
ShowStartupError(ex);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
/// Show startup error
|
|
|
|
|
/// </summary>
|
|
|
|
|
private void ShowStartupError(Exception ex)
|
|
|
|
|
{
|
|
|
|
|
try
|
|
|
|
|
{
|
|
|
|
|
Logger.LogError($"PowerDisplay startup failed: {ex.Message}");
|
|
|
|
|
|
|
|
|
|
var errorWindow = new Window { Title = "PowerDisplay - Startup Error" };
|
|
|
|
|
var panel = new StackPanel { Margin = new Thickness(20), Spacing = 16 };
|
|
|
|
|
|
|
|
|
|
panel.Children.Add(new TextBlock
|
|
|
|
|
{
|
|
|
|
|
Text = "PowerDisplay Startup Failed",
|
|
|
|
|
FontSize = 20,
|
|
|
|
|
FontWeight = Microsoft.UI.Text.FontWeights.SemiBold,
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
panel.Children.Add(new TextBlock
|
|
|
|
|
{
|
|
|
|
|
Text = $"Error: {ex.Message}",
|
|
|
|
|
FontSize = 14,
|
|
|
|
|
TextWrapping = TextWrapping.Wrap,
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
panel.Children.Add(new TextBlock
|
|
|
|
|
{
|
|
|
|
|
Text = $"Details:\n{ex}",
|
|
|
|
|
FontSize = 12,
|
|
|
|
|
TextWrapping = TextWrapping.Wrap,
|
|
|
|
|
Foreground = new SolidColorBrush(Microsoft.UI.Colors.Gray),
|
|
|
|
|
Margin = new Thickness(0, 10, 0, 0),
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
var closeButton = new Button
|
|
|
|
|
{
|
|
|
|
|
Content = "Close",
|
|
|
|
|
HorizontalAlignment = HorizontalAlignment.Right,
|
|
|
|
|
Margin = new Thickness(0, 10, 0, 0),
|
|
|
|
|
};
|
|
|
|
|
closeButton.Click += (_, _) => errorWindow.Close();
|
|
|
|
|
panel.Children.Add(closeButton);
|
|
|
|
|
|
|
|
|
|
errorWindow.Content = new ScrollViewer
|
|
|
|
|
{
|
|
|
|
|
Content = panel,
|
|
|
|
|
VerticalScrollBarVisibility = ScrollBarVisibility.Auto,
|
|
|
|
|
MaxHeight = 600,
|
|
|
|
|
MaxWidth = 800,
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
errorWindow.Activate();
|
|
|
|
|
}
|
|
|
|
|
catch
|
|
|
|
|
{
|
|
|
|
|
Environment.Exit(1);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
/// Gets the main window instance
|
|
|
|
|
/// </summary>
|
|
|
|
|
public Window? MainWindow => _mainWindow;
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
/// Check if running standalone (not launched from PowerToys Runner)
|
|
|
|
|
/// </summary>
|
|
|
|
|
public bool IsRunningDetachedFromPowerToys()
|
|
|
|
|
{
|
|
|
|
|
return _powerToysRunnerPid == -1;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
2025-11-13 12:40:56 +08:00
|
|
|
/// Shutdown application (Awake pattern - simple and clean)
|
2025-10-20 16:22:47 +08:00
|
|
|
/// </summary>
|
|
|
|
|
public void Shutdown()
|
|
|
|
|
{
|
2025-11-12 16:36:43 +08:00
|
|
|
Logger.LogInfo("PowerDisplay shutting down");
|
|
|
|
|
Environment.Exit(0);
|
2025-10-20 16:22:47 +08:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|