16
16
using Microsoft . Extensions . DependencyInjection ;
17
17
using Microsoft . Extensions . Hosting ;
18
18
using Microsoft . Extensions . Logging ;
19
+ using Microsoft . UI . Dispatching ;
19
20
using Microsoft . UI . Xaml ;
20
21
using Microsoft . Win32 ;
21
22
using Microsoft . Windows . AppLifecycle ;
22
23
using Microsoft . Windows . AppNotifications ;
24
+ using NetSparkleUpdater . Interfaces ;
23
25
using Serilog ;
24
26
using LaunchActivatedEventArgs = Microsoft . UI . Xaml . LaunchActivatedEventArgs ;
25
27
26
28
namespace Coder . Desktop . App ;
27
29
28
- public partial class App : Application
30
+ public partial class App : Application , IDispatcherQueueManager
29
31
{
30
- private readonly IServiceProvider _services ;
31
-
32
- private bool _handleWindowClosed = true ;
33
32
private const string MutagenControllerConfigSection = "MutagenController" ;
33
+ private const string UpdaterConfigSection = "Updater" ;
34
34
35
35
#if ! DEBUG
36
36
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" ;
38
39
#else
39
40
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" ;
41
43
#endif
42
44
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 ;
43
51
private readonly ILogger < App > _logger ;
44
52
private readonly IUriHandler _uriHandler ;
45
-
53
+ private readonly IUserNotifier _userNotifier ;
46
54
private readonly ISettingsManager < CoderConnectSettings > _settingsManager ;
47
-
48
55
private readonly IHostApplicationLifetime _appLifetime ;
49
56
57
+ private bool _handleWindowClosed = true ;
58
+
50
59
public App ( )
51
60
{
52
61
var builder = Host . CreateApplicationBuilder ( ) ;
@@ -58,7 +67,17 @@ public App()
58
67
configBuilder . Add (
59
68
new RegistryConfigurationSource ( Registry . LocalMachine , ConfigSubKey ) ) ;
60
69
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 + ":" ) ) ;
62
81
63
82
var services = builder . Services ;
64
83
@@ -71,6 +90,7 @@ public App()
71
90
services . AddSingleton < ICoderApiClientFactory , CoderApiClientFactory > ( ) ;
72
91
services . AddSingleton < IAgentApiClientFactory , AgentApiClientFactory > ( ) ;
73
92
93
+ services . AddSingleton < IDispatcherQueueManager > ( _ => this ) ;
74
94
services . AddSingleton < ICredentialBackend > ( _ =>
75
95
new WindowsCredentialBackend ( WindowsCredentialBackend . CoderCredentialsTargetName ) ) ;
76
96
services . AddSingleton < ICredentialManager , CredentialManager > ( ) ;
@@ -84,6 +104,12 @@ public App()
84
104
services . AddSingleton < IRdpConnector , RdpConnector > ( ) ;
85
105
services . AddSingleton < IUriHandler , UriHandler > ( ) ;
86
106
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
+
87
113
// SignInWindow views and view models
88
114
services . AddTransient < SignInViewModel > ( ) ;
89
115
services . AddTransient < SignInWindow > ( ) ;
@@ -119,6 +145,7 @@ public App()
119
145
_services = services . BuildServiceProvider ( ) ;
120
146
_logger = _services . GetRequiredService < ILogger < App > > ( ) ;
121
147
_uriHandler = _services . GetRequiredService < IUriHandler > ( ) ;
148
+ _userNotifier = _services . GetRequiredService < IUserNotifier > ( ) ;
122
149
_settingsManager = _services . GetRequiredService < ISettingsManager < CoderConnectSettings > > ( ) ;
123
150
_appLifetime = _services . GetRequiredService < IHostApplicationLifetime > ( ) ;
124
151
@@ -142,16 +169,18 @@ protected override void OnLaunched(LaunchActivatedEventArgs args)
142
169
{
143
170
_logger . LogInformation ( "new instance launched" ) ;
144
171
145
- _ = InitializeServicesAsync ( _appLifetime . ApplicationStopping ) ;
146
-
147
172
// 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 ) =>
150
177
{
151
178
if ( ! _handleWindowClosed ) return ;
152
179
closedArgs . Handled = true ;
153
- trayWindow . AppWindow . Hide ( ) ;
180
+ TrayWindow . AppWindow . Hide ( ) ;
154
181
} ;
182
+
183
+ _ = InitializeServicesAsync ( _appLifetime . ApplicationStopping ) ;
155
184
}
156
185
157
186
/// <summary>
@@ -261,27 +290,49 @@ public void OnActivated(object? sender, AppActivationArguments args)
261
290
262
291
public void HandleNotification ( AppNotificationManager ? sender , AppNotificationActivatedEventArgs args )
263
292
{
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 ) ;
266
295
}
267
296
268
297
private static void AddDefaultConfig ( IConfigurationBuilder builder )
269
298
{
270
299
var logPath = Path . Combine (
271
300
Environment . GetFolderPath ( Environment . SpecialFolder . LocalApplicationData ) ,
272
301
"CoderDesktop" ,
273
- logFilename ) ;
302
+ LogFilename ) ;
274
303
builder . AddInMemoryCollection ( new Dictionary < string , string ? >
275
304
{
276
305
[ MutagenControllerConfigSection + ":MutagenExecutablePath" ] = @"C:\mutagen.exe" ,
306
+
277
307
[ "Serilog:Using:0" ] = "Serilog.Sinks.File" ,
278
- [ "Serilog:MinimumLevel" ] = "Information" ,
308
+ [ "Serilog:MinimumLevel" ] = DefaultLogLevel ,
279
309
[ "Serilog:Enrich:0" ] = "FromLogContext" ,
280
310
[ "Serilog:WriteTo:0:Name" ] = "File" ,
281
311
[ "Serilog:WriteTo:0:Args:path" ] = logPath ,
282
312
[ "Serilog:WriteTo:0:Args:outputTemplate" ] =
283
313
"{Timestamp:yyyy-MM-dd HH:mm:ss.fff zzz} [{Level:u3}] {SourceContext} - {Message:lj}{NewLine}{Exception}" ,
284
314
[ "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
285
323
} ) ;
286
324
}
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
+ }
287
338
}
0 commit comments