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  }