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.Globalization; 7 using System.Threading; 8 using System.Windows; 9 using Common.UI; 10 using ManagedCommon; 11 using Microsoft.PowerToys.Telemetry; 12 using WorkspacesEditor.Telemetry; 13 using WorkspacesEditor.Utils; 14 using WorkspacesEditor.ViewModels; 15 16 namespace WorkspacesEditor 17 { 18 /// <summary> 19 /// Interaction logic for App.xaml 20 /// </summary> 21 public partial class App : Application, IDisposable 22 { 23 private static Mutex _instanceMutex; 24 25 public static WorkspacesEditorIO WorkspacesEditorIO { get; private set; } 26 27 private MainWindow _mainWindow; 28 29 private MainViewModel _mainViewModel; 30 31 public static ThemeManager ThemeManager { get; set; } 32 33 private bool _isDisposed; 34 35 private ETWTrace etwTrace = new ETWTrace(); 36 37 public App() 38 { 39 PowerToysTelemetry.Log.WriteEvent(new WorkspacesEditorStartEvent() { TimeStamp = DateTimeOffset.UtcNow.ToUnixTimeMilliseconds() }); 40 41 WorkspacesEditorIO = new WorkspacesEditorIO(); 42 } 43 44 private void OnStartup(object sender, StartupEventArgs e) 45 { 46 Logger.InitializeLogger("\\Workspaces\\Logs"); 47 AppDomain.CurrentDomain.UnhandledException += OnUnhandledException; 48 49 var languageTag = LanguageHelper.LoadLanguage(); 50 51 if (!string.IsNullOrEmpty(languageTag)) 52 { 53 try 54 { 55 System.Threading.Thread.CurrentThread.CurrentUICulture = new CultureInfo(languageTag); 56 } 57 catch (CultureNotFoundException ex) 58 { 59 Logger.LogError("CultureNotFoundException: " + ex.Message); 60 } 61 } 62 63 const string appName = "Local\\PowerToys_Workspaces_Editor_InstanceMutex"; 64 bool createdNew; 65 _instanceMutex = new Mutex(true, appName, out createdNew); 66 if (!createdNew) 67 { 68 Logger.LogWarning("Another instance of Workspaces Editor is already running. Exiting this instance."); 69 _instanceMutex = null; 70 Shutdown(0); 71 return; 72 } 73 74 if (PowerToys.GPOWrapperProjection.GPOWrapper.GetConfiguredWorkspacesEnabledValue() == PowerToys.GPOWrapperProjection.GpoRuleConfigured.Disabled) 75 { 76 Logger.LogWarning("Tried to start with a GPO policy setting the utility to always be disabled. Please contact your systems administrator."); 77 Shutdown(0); 78 return; 79 } 80 81 var args = e?.Args; 82 int powerToysRunnerPid; 83 if (args?.Length > 0) 84 { 85 _ = int.TryParse(args[0], out powerToysRunnerPid); 86 87 Logger.LogInfo($"WorkspacesEditor started from the PowerToys Runner. Runner pid={powerToysRunnerPid}"); 88 RunnerHelper.WaitForPowerToysRunner(powerToysRunnerPid, () => 89 { 90 Logger.LogInfo("PowerToys Runner exited. Exiting WorkspacesEditor"); 91 Dispatcher.Invoke(Shutdown); 92 }); 93 } 94 95 ThemeManager = new ThemeManager(this); 96 97 if (_mainViewModel == null) 98 { 99 _mainViewModel = new MainViewModel(WorkspacesEditorIO); 100 } 101 102 var parseResult = WorkspacesEditorIO.ParseWorkspaces(_mainViewModel); 103 104 // normal start of editor 105 if (_mainWindow == null) 106 { 107 _mainWindow = new MainWindow(_mainViewModel); 108 } 109 110 // reset main window owner to keep it on the top 111 _mainWindow.ShowActivated = true; 112 _mainWindow.Topmost = true; 113 _mainWindow.Show(); 114 115 // we can reset topmost flag after it's opened 116 _mainWindow.Topmost = false; 117 } 118 119 private void OnExit(object sender, ExitEventArgs e) 120 { 121 if (_instanceMutex != null) 122 { 123 _instanceMutex.ReleaseMutex(); 124 } 125 126 Dispose(); 127 Environment.Exit(0); 128 } 129 130 private void OnUnhandledException(object sender, UnhandledExceptionEventArgs args) 131 { 132 Logger.LogError("Unhandled exception occurred", args.ExceptionObject as Exception); 133 } 134 135 protected virtual void Dispose(bool disposing) 136 { 137 if (!_isDisposed) 138 { 139 if (disposing) 140 { 141 ThemeManager?.Dispose(); 142 _instanceMutex?.Dispose(); 143 etwTrace?.Dispose(); 144 } 145 146 _isDisposed = true; 147 } 148 } 149 150 public void Dispose() 151 { 152 // Do not change this code. Put cleanup code in 'Dispose(bool disposing)' method 153 Dispose(disposing: true); 154 GC.SuppressFinalize(this); 155 } 156 } 157 }