21
21
using Microsoft . Win32 ;
22
22
using Microsoft . Windows . AppLifecycle ;
23
23
using Microsoft . Windows . AppNotifications ;
24
+ using NetSparkleUpdater . Interfaces ;
24
25
using Serilog ;
25
26
using LaunchActivatedEventArgs = Microsoft . UI . Xaml . LaunchActivatedEventArgs ;
26
27
27
28
namespace Coder . Desktop . App ;
28
29
29
30
public partial class App : Application
30
31
{
31
- private readonly IServiceProvider _services ;
32
-
33
- private bool _handleWindowClosed = true ;
34
32
private const string MutagenControllerConfigSection = "MutagenController" ;
33
+ private const string UpdaterConfigSection = "Updater" ;
35
34
36
35
#if ! DEBUG
37
36
private const string ConfigSubKey = @"SOFTWARE\Coder Desktop\App" ;
38
- private const string logFilename = "app.log" ;
37
+ private const string LogFilename = "app.log" ;
38
+ private const string DefaultLogLevel = "Information" ;
39
39
#else
40
40
private const string ConfigSubKey = @"SOFTWARE\Coder Desktop\DebugApp" ;
41
- private const string logFilename = "debug-app.log" ;
41
+ private const string LogFilename = "debug-app.log" ;
42
+ private const string DefaultLogLevel = "Debug" ;
42
43
#endif
43
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 ;
44
51
private readonly ILogger < App > _logger ;
45
52
private readonly IUriHandler _uriHandler ;
53
+ private readonly IUserNotifier _userNotifier ;
54
+
55
+ private bool _handleWindowClosed = true ;
46
56
47
57
public App ( )
48
58
{
@@ -55,7 +65,17 @@ public App()
55
65
configBuilder . Add (
56
66
new RegistryConfigurationSource ( Registry . LocalMachine , ConfigSubKey ) ) ;
57
67
configBuilder . Add (
58
- new RegistryConfigurationSource ( Registry . CurrentUser , ConfigSubKey ) ) ;
68
+ new RegistryConfigurationSource (
69
+ Registry . CurrentUser ,
70
+ ConfigSubKey ,
71
+ // Block "Updater:" configuration from HKCU, so that updater
72
+ // settings can only be set at the HKLM level.
73
+ //
74
+ // HACK: This isn't super robust, but the security risk is
75
+ // minor anyway. Malicious apps running as the user could
76
+ // likely override this setting by altering the memory of
77
+ // this app.
78
+ UpdaterConfigSection + ":" ) ) ;
59
79
60
80
var services = builder . Services ;
61
81
@@ -81,6 +101,12 @@ public App()
81
101
services . AddSingleton < IRdpConnector , RdpConnector > ( ) ;
82
102
services . AddSingleton < IUriHandler , UriHandler > ( ) ;
83
103
104
+ services . AddOptions < UpdaterConfig > ( )
105
+ . Bind ( builder . Configuration . GetSection ( UpdaterConfigSection ) ) ;
106
+ services . AddSingleton < IUpdaterUpdateAvailableViewModelFactory , UpdaterUpdateAvailableViewModelFactory > ( ) ;
107
+ services . AddSingleton < IUIFactory , CoderSparkleUIFactory > ( ) ;
108
+ services . AddSingleton < IUpdateController , SparkleUpdateController > ( ) ;
109
+
84
110
// SignInWindow views and view models
85
111
services . AddTransient < SignInViewModel > ( ) ;
86
112
services . AddTransient < SignInWindow > ( ) ;
@@ -107,8 +133,9 @@ public App()
107
133
services . AddTransient < TrayWindow > ( ) ;
108
134
109
135
_services = services . BuildServiceProvider ( ) ;
110
- _logger = ( ILogger < App > ) _services . GetService ( typeof ( ILogger < App > ) ) ! ;
111
- _uriHandler = ( IUriHandler ) _services . GetService ( typeof ( IUriHandler ) ) ! ;
136
+ _logger = _services . GetRequiredService < ILogger < App > > ( ) ;
137
+ _uriHandler = _services . GetRequiredService < IUriHandler > ( ) ;
138
+ _userNotifier = _services . GetRequiredService < IUserNotifier > ( ) ;
112
139
113
140
InitializeComponent ( ) ;
114
141
}
@@ -129,6 +156,18 @@ public async Task ExitApplication()
129
156
protected override void OnLaunched ( LaunchActivatedEventArgs args )
130
157
{
131
158
_logger . LogInformation ( "new instance launched" ) ;
159
+
160
+ // Prevent the TrayWindow from closing, just hide it.
161
+ if ( TrayWindow != null )
162
+ throw new InvalidOperationException ( "OnLaunched was called multiple times? TrayWindow is already set" ) ;
163
+ TrayWindow = _services . GetRequiredService < TrayWindow > ( ) ;
164
+ TrayWindow . Closed += ( _ , closedArgs ) =>
165
+ {
166
+ if ( ! _handleWindowClosed ) return ;
167
+ closedArgs . Handled = true ;
168
+ TrayWindow . AppWindow . Hide ( ) ;
169
+ } ;
170
+
132
171
// Start connecting to the manager in the background.
133
172
var rpcController = _services . GetRequiredService < IRpcController > ( ) ;
134
173
if ( rpcController . GetState ( ) . RpcLifecycle == RpcLifecycle . Disconnected )
@@ -179,15 +218,6 @@ protected override void OnLaunched(LaunchActivatedEventArgs args)
179
218
180
219
syncSessionCts . Dispose ( ) ;
181
220
} , CancellationToken . None ) ;
182
-
183
- // Prevent the TrayWindow from closing, just hide it.
184
- var trayWindow = _services . GetRequiredService < TrayWindow > ( ) ;
185
- trayWindow . Closed += ( _ , closedArgs ) =>
186
- {
187
- if ( ! _handleWindowClosed ) return ;
188
- closedArgs . Handled = true ;
189
- trayWindow . AppWindow . Hide ( ) ;
190
- } ;
191
221
}
192
222
193
223
public void OnActivated ( object ? sender , AppActivationArguments args )
@@ -229,27 +259,36 @@ public void OnActivated(object? sender, AppActivationArguments args)
229
259
230
260
public void HandleNotification ( AppNotificationManager ? sender , AppNotificationActivatedEventArgs args )
231
261
{
232
- // right now, we don't do anything other than log
233
- _logger . LogInformation ( "handled notification activation" ) ;
262
+ _logger . LogInformation ( "handled notification activation: {Argument}" , args . Argument ) ;
263
+ _userNotifier . HandleActivation ( args ) ;
234
264
}
235
265
236
266
private static void AddDefaultConfig ( IConfigurationBuilder builder )
237
267
{
238
268
var logPath = Path . Combine (
239
269
Environment . GetFolderPath ( Environment . SpecialFolder . LocalApplicationData ) ,
240
270
"CoderDesktop" ,
241
- logFilename ) ;
271
+ LogFilename ) ;
242
272
builder . AddInMemoryCollection ( new Dictionary < string , string ? >
243
273
{
244
274
[ MutagenControllerConfigSection + ":MutagenExecutablePath" ] = @"C:\mutagen.exe" ,
275
+
245
276
[ "Serilog:Using:0" ] = "Serilog.Sinks.File" ,
246
- [ "Serilog:MinimumLevel" ] = "Information" ,
277
+ [ "Serilog:MinimumLevel" ] = DefaultLogLevel ,
247
278
[ "Serilog:Enrich:0" ] = "FromLogContext" ,
248
279
[ "Serilog:WriteTo:0:Name" ] = "File" ,
249
280
[ "Serilog:WriteTo:0:Args:path" ] = logPath ,
250
281
[ "Serilog:WriteTo:0:Args:outputTemplate" ] =
251
282
"{Timestamp:yyyy-MM-dd HH:mm:ss.fff zzz} [{Level:u3}] {SourceContext} - {Message:lj}{NewLine}{Exception}" ,
252
283
[ "Serilog:WriteTo:0:Args:rollingInterval" ] = "Day" ,
284
+
285
+ #if DEBUG
286
+ [ "Serilog:Using:1" ] = "Serilog.Sinks.Debug" ,
287
+ [ "Serilog:Enrich:1" ] = "FromLogContext" ,
288
+ [ "Serilog:WriteTo:1:Name" ] = "Debug" ,
289
+ [ "Serilog:WriteTo:1:Args:outputTemplate" ] =
290
+ "{Timestamp:yyyy-MM-dd HH:mm:ss.fff zzz} [{Level:u3}] {SourceContext} - {Message:lj}{NewLine}{Exception}" ,
291
+ #endif
253
292
} ) ;
254
293
}
255
294
}
0 commit comments