1616using Microsoft . Extensions . DependencyInjection ;
1717using Microsoft . Extensions . Hosting ;
1818using Microsoft . Extensions . Logging ;
19+ using Microsoft . UI . Dispatching ;
1920using Microsoft . UI . Xaml ;
2021using Microsoft . Win32 ;
2122using Microsoft . Windows . AppLifecycle ;
2223using Microsoft . Windows . AppNotifications ;
24+ using NetSparkleUpdater . Interfaces ;
2325using Serilog ;
2426using LaunchActivatedEventArgs = Microsoft . UI . Xaml . LaunchActivatedEventArgs ;
2527
2628namespace Coder . Desktop . App ;
2729
28- public partial class App : Application
30+ public partial class App : Application , IDispatcherQueueManager
2931{
30- private readonly IServiceProvider _services ;
31-
32- private bool _handleWindowClosed = true ;
3332 private const string MutagenControllerConfigSection = "MutagenController" ;
33+ private const string UpdaterConfigSection = "Updater" ;
3434
3535#if ! DEBUG
3636 private const string ConfigSubKey = @"SOFTWARE\Coder Desktop\App" ;
37- private const string logFilename = "app.log" ;
37+ private const string LogFilename = "app.log" ;
38+ private const string DefaultLogLevel = "Information" ;
3839#else
3940 private const string ConfigSubKey = @"SOFTWARE\Coder Desktop\DebugApp" ;
40- private const string logFilename = "debug-app.log" ;
41+ private const string LogFilename = "debug-app.log" ;
42+ private const string DefaultLogLevel = "Debug" ;
4143#endif
4244
45+ // HACK: This is exposed for dispatcher queue access. The notifier uses
46+ // this to ensure action callbacks run in the UI thread (as
47+ // activation events aren't in the main thread).
48+ public TrayWindow ? TrayWindow ;
49+
50+ private readonly IServiceProvider _services ;
4351 private readonly ILogger < App > _logger ;
4452 private readonly IUriHandler _uriHandler ;
45-
53+ private readonly IUserNotifier _userNotifier ;
4654 private readonly ISettingsManager < CoderConnectSettings > _settingsManager ;
47-
4855 private readonly IHostApplicationLifetime _appLifetime ;
4956
57+ private bool _handleWindowClosed = true ;
58+
5059 public App ( )
5160 {
5261 var builder = Host . CreateApplicationBuilder ( ) ;
@@ -58,7 +67,17 @@ public App()
5867 configBuilder . Add (
5968 new RegistryConfigurationSource ( Registry . LocalMachine , ConfigSubKey ) ) ;
6069 configBuilder . Add (
61- new RegistryConfigurationSource ( Registry . CurrentUser , ConfigSubKey ) ) ;
70+ new RegistryConfigurationSource (
71+ Registry . CurrentUser ,
72+ ConfigSubKey ,
73+ // Block "Updater:" configuration from HKCU, so that updater
74+ // settings can only be set at the HKLM level.
75+ //
76+ // HACK: This isn't super robust, but the security risk is
77+ // minor anyway. Malicious apps running as the user could
78+ // likely override this setting by altering the memory of
79+ // this app.
80+ UpdaterConfigSection + ":" ) ) ;
6281
6382 var services = builder . Services ;
6483
@@ -71,6 +90,7 @@ public App()
7190 services . AddSingleton < ICoderApiClientFactory , CoderApiClientFactory > ( ) ;
7291 services . AddSingleton < IAgentApiClientFactory , AgentApiClientFactory > ( ) ;
7392
93+ services . AddSingleton < IDispatcherQueueManager > ( _ => this ) ;
7494 services . AddSingleton < ICredentialBackend > ( _ =>
7595 new WindowsCredentialBackend ( WindowsCredentialBackend . CoderCredentialsTargetName ) ) ;
7696 services . AddSingleton < ICredentialManager , CredentialManager > ( ) ;
@@ -84,6 +104,12 @@ public App()
84104 services . AddSingleton < IRdpConnector , RdpConnector > ( ) ;
85105 services . AddSingleton < IUriHandler , UriHandler > ( ) ;
86106
107+ services . AddOptions < UpdaterConfig > ( )
108+ . Bind ( builder . Configuration . GetSection ( UpdaterConfigSection ) ) ;
109+ services . AddSingleton < IUpdaterUpdateAvailableViewModelFactory , UpdaterUpdateAvailableViewModelFactory > ( ) ;
110+ services . AddSingleton < IUIFactory , CoderSparkleUIFactory > ( ) ;
111+ services . AddSingleton < IUpdateController , SparkleUpdateController > ( ) ;
112+
87113 // SignInWindow views and view models
88114 services . AddTransient < SignInViewModel > ( ) ;
89115 services . AddTransient < SignInWindow > ( ) ;
@@ -119,6 +145,7 @@ public App()
119145 _services = services . BuildServiceProvider ( ) ;
120146 _logger = _services . GetRequiredService < ILogger < App > > ( ) ;
121147 _uriHandler = _services . GetRequiredService < IUriHandler > ( ) ;
148+ _userNotifier = _services . GetRequiredService < IUserNotifier > ( ) ;
122149 _settingsManager = _services . GetRequiredService < ISettingsManager < CoderConnectSettings > > ( ) ;
123150 _appLifetime = _services . GetRequiredService < IHostApplicationLifetime > ( ) ;
124151
@@ -142,16 +169,18 @@ protected override void OnLaunched(LaunchActivatedEventArgs args)
142169 {
143170 _logger . LogInformation ( "new instance launched" ) ;
144171
145- _ = InitializeServicesAsync ( _appLifetime . ApplicationStopping ) ;
146-
147172 // Prevent the TrayWindow from closing, just hide it.
148- var trayWindow = _services . GetRequiredService < TrayWindow > ( ) ;
149- trayWindow . Closed += ( _ , closedArgs ) =>
173+ if ( TrayWindow != null )
174+ throw new InvalidOperationException ( "OnLaunched was called multiple times? TrayWindow is already set" ) ;
175+ TrayWindow = _services . GetRequiredService < TrayWindow > ( ) ;
176+ TrayWindow . Closed += ( _ , closedArgs ) =>
150177 {
151178 if ( ! _handleWindowClosed ) return ;
152179 closedArgs . Handled = true ;
153- trayWindow . AppWindow . Hide ( ) ;
180+ TrayWindow . AppWindow . Hide ( ) ;
154181 } ;
182+
183+ _ = InitializeServicesAsync ( _appLifetime . ApplicationStopping ) ;
155184 }
156185
157186 /// <summary>
@@ -261,27 +290,49 @@ public void OnActivated(object? sender, AppActivationArguments args)
261290
262291 public void HandleNotification ( AppNotificationManager ? sender , AppNotificationActivatedEventArgs args )
263292 {
264- // right now, we don't do anything other than log
265- _logger . LogInformation ( "handled notification activation" ) ;
293+ _logger . LogInformation ( "handled notification activation: {Argument}" , args . Argument ) ;
294+ _userNotifier . HandleNotificationActivation ( args . Arguments ) ;
266295 }
267296
268297 private static void AddDefaultConfig ( IConfigurationBuilder builder )
269298 {
270299 var logPath = Path . Combine (
271300 Environment . GetFolderPath ( Environment . SpecialFolder . LocalApplicationData ) ,
272301 "CoderDesktop" ,
273- logFilename ) ;
302+ LogFilename ) ;
274303 builder . AddInMemoryCollection ( new Dictionary < string , string ? >
275304 {
276305 [ MutagenControllerConfigSection + ":MutagenExecutablePath" ] = @"C:\mutagen.exe" ,
306+
277307 [ "Serilog:Using:0" ] = "Serilog.Sinks.File" ,
278- [ "Serilog:MinimumLevel" ] = "Information" ,
308+ [ "Serilog:MinimumLevel" ] = DefaultLogLevel ,
279309 [ "Serilog:Enrich:0" ] = "FromLogContext" ,
280310 [ "Serilog:WriteTo:0:Name" ] = "File" ,
281311 [ "Serilog:WriteTo:0:Args:path" ] = logPath ,
282312 [ "Serilog:WriteTo:0:Args:outputTemplate" ] =
283313 "{Timestamp:yyyy-MM-dd HH:mm:ss.fff zzz} [{Level:u3}] {SourceContext} - {Message:lj}{NewLine}{Exception}" ,
284314 [ "Serilog:WriteTo:0:Args:rollingInterval" ] = "Day" ,
315+
316+ #if DEBUG
317+ [ "Serilog:Using:1" ] = "Serilog.Sinks.Debug" ,
318+ [ "Serilog:Enrich:1" ] = "FromLogContext" ,
319+ [ "Serilog:WriteTo:1:Name" ] = "Debug" ,
320+ [ "Serilog:WriteTo:1:Args:outputTemplate" ] =
321+ "{Timestamp:yyyy-MM-dd HH:mm:ss.fff zzz} [{Level:u3}] {SourceContext} - {Message:lj}{NewLine}{Exception}" ,
322+ #endif
285323 } ) ;
286324 }
325+
326+ public void RunInUiThread ( DispatcherQueueHandler action )
327+ {
328+ var dispatcherQueue = TrayWindow ? . DispatcherQueue ;
329+ if ( dispatcherQueue is null )
330+ throw new InvalidOperationException ( "DispatcherQueue is not available" ) ;
331+ if ( dispatcherQueue . HasThreadAccess )
332+ {
333+ action ( ) ;
334+ return ;
335+ }
336+ dispatcherQueue . TryEnqueue ( action ) ;
337+ }
287338}
0 commit comments