App.xaml.cs
1 // Copyright (c) Microsoft Corporation 2 // The Microsoft Corporation licenses this file to you under the MIT license. 3 // See the LICENSE file in the project root for more information. 4 5 using System; 6 using System.Collections.Generic; 7 using System.Diagnostics; 8 using System.IO; 9 using System.Linq; 10 using System.Text.Json; 11 using System.Threading.Tasks; 12 using ManagedCommon; 13 using Microsoft.PowerToys.Settings.UI.Helpers; 14 using Microsoft.PowerToys.Settings.UI.Library; 15 using Microsoft.PowerToys.Settings.UI.Library.Telemetry.Events; 16 using Microsoft.PowerToys.Settings.UI.SerializationContext; 17 using Microsoft.PowerToys.Settings.UI.Services; 18 using Microsoft.PowerToys.Settings.UI.Views; 19 using Microsoft.PowerToys.Telemetry; 20 using Microsoft.UI.Xaml; 21 using PowerToys.Interop; 22 using Windows.UI.Popups; 23 using WinRT.Interop; 24 using WinUIEx; 25 26 namespace Microsoft.PowerToys.Settings.UI 27 { 28 /// <summary> 29 /// Provides application-specific behavior to supplement the default Application class. 30 /// </summary> 31 public partial class App : Application 32 { 33 private enum Arguments 34 { 35 PTPipeName = 1, 36 SettingsPipeName, 37 PTPid, 38 Theme, // used in the old settings 39 ElevatedStatus, 40 IsUserAdmin, 41 ShowOobeWindow, 42 ShowScoobeWindow, 43 ContainsSettingsWindow, 44 } 45 46 private const int RequiredArgumentsSetSettingQty = 4; 47 private const int RequiredArgumentsSetAdditionalSettingsQty = 4; 48 private const int RequiredArgumentsGetSettingQty = 3; 49 50 private const int RequiredArgumentsLaunchedFromRunnerQty = 10; 51 52 // Create an instance of the IPC wrapper. 53 private static TwoWayPipeMessageIPCManaged ipcmanager; 54 55 public static bool IsElevated { get; set; } 56 57 public static bool IsUserAnAdmin { get; set; } 58 59 public static int PowerToysPID { get; set; } 60 61 public bool ShowOobe { get; set; } 62 63 public bool ShowScoobe { get; set; } 64 65 public Type StartupPage { get; set; } = typeof(Views.DashboardPage); 66 67 public static Action<string> IPCMessageReceivedCallback { get; set; } 68 69 public ETWTrace EtwTrace { get; private set; } = new ETWTrace(); 70 71 /// <summary> 72 /// Initializes a new instance of the <see cref="App"/> class. 73 /// Initializes the singleton application object. This is the first line of authored code 74 /// executed, and as such is the logical equivalent of main() or WinMain(). 75 /// </summary> 76 public App() 77 { 78 Logger.InitializeLogger(@"\Settings\Logs"); 79 80 string appLanguage = LanguageHelper.LoadLanguage(); 81 if (!string.IsNullOrEmpty(appLanguage)) 82 { 83 Microsoft.Windows.Globalization.ApplicationLanguages.PrimaryLanguageOverride = appLanguage; 84 } 85 86 InitializeComponent(); 87 88 UnhandledException += App_UnhandledException; 89 90 NativeEventWaiter.WaitForEventLoop( 91 Constants.PowerToysRunnerTerminateSettingsEvent(), () => 92 { 93 EtwTrace?.Dispose(); 94 Environment.Exit(0); 95 }); 96 } 97 98 private void App_UnhandledException(object sender, Microsoft.UI.Xaml.UnhandledExceptionEventArgs e) 99 { 100 Logger.LogError("Unhandled exception", e.Exception); 101 } 102 103 public static void OpenSettingsWindow(Type type = null, bool ensurePageIsSelected = false) 104 { 105 if (settingsWindow == null) 106 { 107 settingsWindow = new MainWindow(); 108 } 109 110 settingsWindow.Activate(); 111 112 if (type != null) 113 { 114 settingsWindow.NavigateToSection(type); 115 116 WindowHelpers.BringToForeground(settingsWindow.GetWindowHandle()); 117 } 118 119 if (ensurePageIsSelected) 120 { 121 settingsWindow.EnsurePageIsSelected(); 122 } 123 } 124 125 private void OnLaunchedToSetSetting(string[] cmdArgs) 126 { 127 var settingName = cmdArgs[2]; 128 var settingValue = cmdArgs[3]; 129 try 130 { 131 SetSettingCommandLineCommand.Execute(settingName, settingValue, SettingsUtils.Default); 132 } 133 catch (Exception ex) 134 { 135 Logger.LogError($"SetSettingCommandLineCommand exception: '{settingName}' setting couldn't be set to {settingValue}", ex); 136 } 137 138 Exit(); 139 } 140 141 private void OnLaunchedToSetAdditionalSetting(string[] cmdArgs) 142 { 143 var moduleName = cmdArgs[2]; 144 var ipcFileName = cmdArgs[3]; 145 try 146 { 147 using (var settings = JsonDocument.Parse(File.ReadAllText(ipcFileName))) 148 { 149 SetAdditionalSettingsCommandLineCommand.Execute(moduleName, settings, SettingsUtils.Default); 150 } 151 } 152 catch (Exception ex) 153 { 154 Logger.LogError($"SetAdditionalSettingsCommandLineCommand exception: couldn't set additional settings for '{moduleName}'", ex); 155 } 156 157 Exit(); 158 } 159 160 private void OnLaunchedToGetSetting(string[] cmdArgs) 161 { 162 var ipcFileName = cmdArgs[2]; 163 164 try 165 { 166 var requestedSettings = JsonSerializer.Deserialize<Dictionary<string, List<string>>>(File.ReadAllText(ipcFileName), SourceGenerationContextContext.Default.DictionaryStringListString); 167 File.WriteAllText(ipcFileName, GetSettingCommandLineCommand.Execute(requestedSettings)); 168 } 169 catch (Exception ex) 170 { 171 Logger.LogError($"GetSettingCommandLineCommand exception", ex); 172 } 173 174 Exit(); 175 } 176 177 private void OnLaunchedFromRunner(string[] cmdArgs) 178 { 179 // Skip the first argument which is prepended when launched by explorer 180 if (cmdArgs[0].EndsWith(".dll", StringComparison.InvariantCultureIgnoreCase) && cmdArgs[1].EndsWith(".exe", StringComparison.InvariantCultureIgnoreCase) && (cmdArgs.Length >= RequiredArgumentsLaunchedFromRunnerQty + 1)) 181 { 182 cmdArgs = cmdArgs.Skip(1).ToArray(); 183 } 184 185 _ = int.TryParse(cmdArgs[(int)Arguments.PTPid], out int powerToysPID); 186 PowerToysPID = powerToysPID; 187 188 IsElevated = cmdArgs[(int)Arguments.ElevatedStatus] == "true"; 189 IsUserAnAdmin = cmdArgs[(int)Arguments.IsUserAdmin] == "true"; 190 ShowOobe = cmdArgs[(int)Arguments.ShowOobeWindow] == "true"; 191 ShowScoobe = cmdArgs[(int)Arguments.ShowScoobeWindow] == "true"; 192 bool containsSettingsWindow = cmdArgs[(int)Arguments.ContainsSettingsWindow] == "true"; 193 194 // To keep track of variable arguments 195 int currentArgumentIndex = RequiredArgumentsLaunchedFromRunnerQty; 196 197 if (containsSettingsWindow) 198 { 199 // Open specific window 200 StartupPage = GetPage(cmdArgs[currentArgumentIndex]); 201 202 currentArgumentIndex++; 203 } 204 205 RunnerHelper.WaitForPowerToysRunner(PowerToysPID, () => 206 { 207 Environment.Exit(0); 208 }); 209 210 ipcmanager = new TwoWayPipeMessageIPCManaged(cmdArgs[(int)Arguments.SettingsPipeName], cmdArgs[(int)Arguments.PTPipeName], (string message) => 211 { 212 if (IPCMessageReceivedCallback != null && message.Length > 0) 213 { 214 IPCMessageReceivedCallback(message); 215 } 216 }); 217 ipcmanager.Start(); 218 219 GlobalHotkeyConflictManager.Initialize(message => 220 { 221 ipcmanager.Send(message); 222 return 0; 223 }); 224 225 if (!ShowOobe && !ShowScoobe) 226 { 227 settingsWindow = new MainWindow(); 228 settingsWindow.Activate(); 229 settingsWindow.NavigateToSection(StartupPage); 230 231 // https://github.com/microsoft/microsoft-ui-xaml/issues/7595 - Activate doesn't bring window to the foreground 232 // Need to call SetForegroundWindow to actually gain focus. 233 WindowHelpers.BringToForeground(settingsWindow.GetWindowHandle()); 234 235 // https://github.com/microsoft/microsoft-ui-xaml/issues/8948 - A window's top border incorrectly 236 // renders as black on Windows 10. 237 WindowHelpers.ForceTopBorder1PixelInsetOnWindows10(WindowNative.GetWindowHandle(settingsWindow)); 238 } 239 else 240 { 241 // Create the Settings window hidden so that it's fully initialized and 242 // it will be ready to receive the notification if the user opens 243 // the Settings from the tray icon. 244 settingsWindow = new MainWindow(true); 245 246 if (ShowOobe) 247 { 248 PowerToysTelemetry.Log.WriteEvent(new OobeStartedEvent()); 249 OobeWindow oobeWindow = new OobeWindow(OOBE.Enums.PowerToysModules.Overview); 250 oobeWindow.Activate(); 251 oobeWindow.ExtendsContentIntoTitleBar = true; 252 WindowHelpers.ForceTopBorder1PixelInsetOnWindows10(WindowNative.GetWindowHandle(settingsWindow)); 253 SetOobeWindow(oobeWindow); 254 } 255 else if (ShowScoobe) 256 { 257 PowerToysTelemetry.Log.WriteEvent(new ScoobeStartedEvent()); 258 ScoobeWindow newScoobeWindow = new ScoobeWindow(); 259 newScoobeWindow.Activate(); 260 WindowHelpers.ForceTopBorder1PixelInsetOnWindows10(WindowNative.GetWindowHandle(settingsWindow)); 261 SetScoobeWindow(newScoobeWindow); 262 } 263 } 264 } 265 266 /// <summary> 267 /// Invoked when the application is launched normally by the end user. Other entry points 268 /// will be used such as when the application is launched to open a specific file. 269 /// </summary> 270 /// <param name="args">Details about the launch request and process.</param> 271 protected override void OnLaunched(Microsoft.UI.Xaml.LaunchActivatedEventArgs args) 272 { 273 var cmdArgs = Environment.GetCommandLineArgs(); 274 275 if (cmdArgs?.Length >= RequiredArgumentsLaunchedFromRunnerQty) 276 { 277 OnLaunchedFromRunner(cmdArgs); 278 } 279 else if (cmdArgs?.Length == RequiredArgumentsSetSettingQty && cmdArgs[1] == "set") 280 { 281 OnLaunchedToSetSetting(cmdArgs); 282 } 283 else if (cmdArgs?.Length == RequiredArgumentsSetAdditionalSettingsQty && cmdArgs[1] == "setAdditional") 284 { 285 OnLaunchedToSetAdditionalSetting(cmdArgs); 286 } 287 else if (cmdArgs?.Length == RequiredArgumentsGetSettingQty && cmdArgs[1] == "get") 288 { 289 OnLaunchedToGetSetting(cmdArgs); 290 } 291 else 292 { 293 #if DEBUG 294 // For debugging purposes 295 // Window is also needed to show MessageDialog 296 settingsWindow = new MainWindow(); 297 settingsWindow.ExtendsContentIntoTitleBar = true; 298 WindowHelpers.ForceTopBorder1PixelInsetOnWindows10(WindowNative.GetWindowHandle(settingsWindow)); 299 settingsWindow.Activate(); 300 settingsWindow.NavigateToSection(StartupPage); 301 302 // In DEBUG mode, we might not have IPC set up, so provide a dummy implementation 303 GlobalHotkeyConflictManager.Initialize(message => 304 { 305 // In debug mode, just log or do nothing 306 System.Diagnostics.Debug.WriteLine($"IPC Message: {message}"); 307 return 0; 308 }); 309 #else 310 /* If we try to run Settings as a standalone app, it will start PowerToys.exe if not running and open Settings again through it in the Dashboard page. */ 311 Common.UI.SettingsDeepLink.OpenSettings(Common.UI.SettingsDeepLink.SettingsWindow.Dashboard); 312 Exit(); 313 #endif 314 } 315 } 316 317 public static TwoWayPipeMessageIPCManaged GetTwoWayIPCManager() 318 { 319 return ipcmanager; 320 } 321 322 public static bool IsDarkTheme() 323 { 324 return ThemeService.Theme == ElementTheme.Dark || (ThemeService.Theme == ElementTheme.Default && ThemeHelpers.GetAppTheme() == AppTheme.Dark); 325 } 326 327 public static int UpdateUIThemeMethod(string themeName) 328 { 329 return 0; 330 } 331 332 private static SettingsUtils settingsUtils = SettingsUtils.Default; 333 private static ThemeService themeService = new ThemeService(SettingsRepository<GeneralSettings>.GetInstance(settingsUtils)); 334 335 public static ThemeService ThemeService => themeService; 336 337 private static MainWindow settingsWindow; 338 private static OobeWindow oobeWindow; 339 private static ScoobeWindow scoobeWindow; 340 341 public static void ClearSettingsWindow() 342 { 343 settingsWindow = null; 344 } 345 346 public static MainWindow GetSettingsWindow() 347 { 348 return settingsWindow; 349 } 350 351 public static OobeWindow GetOobeWindow() 352 { 353 return oobeWindow; 354 } 355 356 public static void SetOobeWindow(OobeWindow window) 357 { 358 oobeWindow = window; 359 } 360 361 public static void ClearOobeWindow() 362 { 363 oobeWindow = null; 364 } 365 366 public static ScoobeWindow GetScoobeWindow() 367 { 368 return scoobeWindow; 369 } 370 371 public static void SetScoobeWindow(ScoobeWindow window) 372 { 373 scoobeWindow = window; 374 } 375 376 public static void ClearScoobeWindow() 377 { 378 scoobeWindow = null; 379 } 380 381 public static Type GetPage(string settingWindow) 382 { 383 switch (settingWindow) 384 { 385 case "Dashboard": return typeof(DashboardPage); 386 case "Overview": return typeof(GeneralPage); 387 case "AdvancedPaste": return typeof(AdvancedPastePage); 388 case "AlwaysOnTop": return typeof(AlwaysOnTopPage); 389 case "Awake": return typeof(AwakePage); 390 case "CmdNotFound": return typeof(CmdNotFoundPage); 391 case "ColorPicker": return typeof(ColorPickerPage); 392 case "LightSwitch": return typeof(LightSwitchPage); 393 case "FancyZones": return typeof(FancyZonesPage); 394 case "FileLocksmith": return typeof(FileLocksmithPage); 395 case "Run": return typeof(PowerLauncherPage); 396 case "ImageResizer": return typeof(ImageResizerPage); 397 case "KBM": return typeof(KeyboardManagerPage); 398 case "MouseUtils": return typeof(MouseUtilsPage); 399 case "MouseWithoutBorders": return typeof(MouseWithoutBordersPage); 400 case "Peek": return typeof(PeekPage); 401 case "PowerAccent": return typeof(PowerAccentPage); 402 case "PowerLauncher": return typeof(PowerLauncherPage); 403 case "PowerPreview": return typeof(PowerPreviewPage); 404 case "PowerRename": return typeof(PowerRenamePage); 405 case "QuickAccent": return typeof(PowerAccentPage); 406 case "FileExplorer": return typeof(PowerPreviewPage); 407 case "ShortcutGuide": return typeof(ShortcutGuidePage); 408 case "PowerOcr": return typeof(PowerOcrPage); 409 case "MeasureTool": return typeof(MeasureToolPage); 410 case "Hosts": return typeof(HostsPage); 411 case "RegistryPreview": return typeof(RegistryPreviewPage); 412 case "CropAndLock": return typeof(CropAndLockPage); 413 case "EnvironmentVariables": return typeof(EnvironmentVariablesPage); 414 case "NewPlus": return typeof(NewPlusPage); 415 case "Workspaces": return typeof(WorkspacesPage); 416 case "CmdPal": return typeof(CmdPalPage); 417 case "ZoomIt": return typeof(ZoomItPage); 418 case "PowerDisplay": return typeof(PowerDisplayPage); 419 default: 420 // Fallback to Dashboard 421 Debug.Assert(false, "Unexpected SettingsWindow argument value"); 422 return typeof(DashboardPage); 423 } 424 } 425 } 426 }