Skip to content

Commit e7a9d70

Browse files
committed
added StartupManager to handle auto-start
changed order of CredentialManager loading
1 parent c2c3d51 commit e7a9d70

File tree

7 files changed

+120
-44
lines changed

7 files changed

+120
-44
lines changed

App/App.xaml.cs

Lines changed: 20 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -138,6 +138,25 @@ public async Task ExitApplication()
138138
protected override void OnLaunched(LaunchActivatedEventArgs args)
139139
{
140140
_logger.LogInformation("new instance launched");
141+
142+
// Load the credentials in the background.
143+
var credentialManagerCts = new CancellationTokenSource(TimeSpan.FromSeconds(15));
144+
var credentialManager = _services.GetRequiredService<ICredentialManager>();
145+
credentialManager.LoadCredentials(credentialManagerCts.Token).ContinueWith(t =>
146+
{
147+
if (t.Exception != null)
148+
{
149+
_logger.LogError(t.Exception, "failed to load credentials");
150+
#if DEBUG
151+
Debug.WriteLine(t.Exception);
152+
Debugger.Break();
153+
#endif
154+
}
155+
156+
credentialManagerCts.Dispose();
157+
});
158+
159+
141160
// Start connecting to the manager in the background.
142161
var rpcController = _services.GetRequiredService<IRpcController>();
143162
if (rpcController.GetState().RpcLifecycle == RpcLifecycle.Disconnected)
@@ -155,7 +174,7 @@ protected override void OnLaunched(LaunchActivatedEventArgs args)
155174
#endif
156175
} else
157176
{
158-
if (rpcController.GetState().RpcLifecycle == RpcLifecycle.Disconnected)
177+
if (rpcController.GetState().VpnLifecycle == VpnLifecycle.Stopped)
159178
{
160179
if (_settingsManager.Read(SettingsManager.ConnectOnLaunchKey, false))
161180
{
@@ -172,23 +191,6 @@ protected override void OnLaunched(LaunchActivatedEventArgs args)
172191
}
173192
});
174193

175-
// Load the credentials in the background.
176-
var credentialManagerCts = new CancellationTokenSource(TimeSpan.FromSeconds(15));
177-
var credentialManager = _services.GetRequiredService<ICredentialManager>();
178-
_ = credentialManager.LoadCredentials(credentialManagerCts.Token).ContinueWith(t =>
179-
{
180-
if (t.Exception != null)
181-
{
182-
_logger.LogError(t.Exception, "failed to load credentials");
183-
#if DEBUG
184-
Debug.WriteLine(t.Exception);
185-
Debugger.Break();
186-
#endif
187-
}
188-
189-
credentialManagerCts.Dispose();
190-
}, CancellationToken.None);
191-
192194
// Initialize file sync.
193195
var syncSessionCts = new CancellationTokenSource(TimeSpan.FromSeconds(10));
194196
var syncSessionController = _services.GetRequiredService<ISyncSessionController>();

App/Services/SettingsManager.cs

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,7 @@
11
using System;
22
using System.Collections.Generic;
33
using System.IO;
4-
using System.Linq;
5-
using System.Text;
64
using System.Text.Json;
7-
using System.Threading.Tasks;
85

96
namespace Coder.Desktop.App.Services;
107
/// <summary>

App/Services/StartupManager.cs

Lines changed: 73 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,73 @@
1+
using Microsoft.Win32;
2+
using System;
3+
using System.Diagnostics;
4+
using System.Security;
5+
6+
namespace Coder.Desktop.App.Services;
7+
public static class StartupManager
8+
{
9+
private const string RunKey = @"Software\\Microsoft\\Windows\\CurrentVersion\\Run";
10+
private const string PoliciesExplorerUser = @"Software\\Microsoft\\Windows\\CurrentVersion\\Policies\\Explorer";
11+
private const string PoliciesExplorerMachine = @"SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\Policies\\Explorer";
12+
private const string DisableCurrentUserRun = "DisableCurrentUserRun";
13+
private const string DisableLocalMachineRun = "DisableLocalMachineRun";
14+
15+
private const string _defaultValueName = "CoderDesktopApp";
16+
17+
/// <summary>
18+
/// Adds the current executable to the per‑user Run key. Returns <c>true</c> if successful.
19+
/// Fails (returns <c>false</c>) when blocked by policy or lack of permissions.
20+
/// </summary>
21+
public static bool Enable()
22+
{
23+
if (IsDisabledByPolicy())
24+
return false;
25+
26+
string exe = Process.GetCurrentProcess().MainModule!.FileName;
27+
try
28+
{
29+
using var key = Registry.CurrentUser.OpenSubKey(RunKey, writable: true)
30+
?? Registry.CurrentUser.CreateSubKey(RunKey)!;
31+
key.SetValue(_defaultValueName, $"\"{exe}\"");
32+
return true;
33+
}
34+
catch (UnauthorizedAccessException) { return false; }
35+
catch (SecurityException) { return false; }
36+
}
37+
38+
/// <summary>Removes the value from the Run key (no-op if missing).</summary>
39+
public static void Disable()
40+
{
41+
using var key = Registry.CurrentUser.OpenSubKey(RunKey, writable: true);
42+
key?.DeleteValue(_defaultValueName, throwOnMissingValue: false);
43+
}
44+
45+
/// <summary>Checks whether the value exists in the Run key.</summary>
46+
public static bool IsEnabled()
47+
{
48+
using var key = Registry.CurrentUser.OpenSubKey(RunKey);
49+
return key?.GetValue(_defaultValueName) != null;
50+
}
51+
52+
/// <summary>
53+
/// Detects whether group policy disables per‑user startup programs.
54+
/// Mirrors <see cref="Windows.ApplicationModel.StartupTaskState.DisabledByPolicy"/>.
55+
/// </summary>
56+
public static bool IsDisabledByPolicy()
57+
{
58+
// User policy – HKCU
59+
using (var keyUser = Registry.CurrentUser.OpenSubKey(PoliciesExplorerUser))
60+
{
61+
if ((int?)keyUser?.GetValue(DisableCurrentUserRun) == 1) return true;
62+
}
63+
// Machine policy – HKLM
64+
using (var keyMachine = Registry.LocalMachine.OpenSubKey(PoliciesExplorerMachine))
65+
{
66+
if ((int?)keyMachine?.GetValue(DisableLocalMachineRun) == 1) return true;
67+
}
68+
69+
// Some non‑desktop SKUs report DisabledByPolicy implicitly
70+
return false;
71+
}
72+
}
73+

App/ViewModels/SettingsViewModel.cs

Lines changed: 22 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1,24 +1,22 @@
11
using Coder.Desktop.App.Services;
22
using CommunityToolkit.Mvvm.ComponentModel;
3-
using CommunityToolkit.Mvvm.Input;
43
using Microsoft.Extensions.Logging;
54
using Microsoft.UI.Dispatching;
65
using Microsoft.UI.Xaml;
7-
using Microsoft.UI.Xaml.Controls;
86
using System;
97

108
namespace Coder.Desktop.App.ViewModels;
119

1210
public partial class SettingsViewModel : ObservableObject
1311
{
14-
private Window? _window;
15-
private DispatcherQueue? _dispatcherQueue;
16-
1712
private readonly ILogger<SettingsViewModel> _logger;
1813

1914
[ObservableProperty]
2015
public partial bool ConnectOnLaunch { get; set; } = false;
2116

17+
[ObservableProperty]
18+
public partial bool StartOnLoginDisabled { get; set; } = false;
19+
2220
[ObservableProperty]
2321
public partial bool StartOnLogin { get; set; } = false;
2422

@@ -31,6 +29,10 @@ public SettingsViewModel(ILogger<SettingsViewModel> logger, ISettingsManager set
3129
ConnectOnLaunch = _settingsManager.Read(SettingsManager.ConnectOnLaunchKey, false);
3230
StartOnLogin = _settingsManager.Read(SettingsManager.StartOnLoginKey, false);
3331

32+
// Various policies can disable the "Start on login" option.
33+
// We disable the option in the UI if the policy is set.
34+
StartOnLoginDisabled = StartupManager.IsDisabledByPolicy();
35+
3436
this.PropertyChanged += (_, args) =>
3537
{
3638
if (args.PropertyName == nameof(ConnectOnLaunch))
@@ -41,28 +43,34 @@ public SettingsViewModel(ILogger<SettingsViewModel> logger, ISettingsManager set
4143
}
4244
catch (Exception ex)
4345
{
44-
Console.WriteLine($"Error saving {SettingsManager.ConnectOnLaunchKey} setting: {ex.Message}");
46+
_logger.LogError($"Error saving {SettingsManager.ConnectOnLaunchKey} setting: {ex.Message}");
4547
}
4648
}
4749
else if (args.PropertyName == nameof(StartOnLogin))
4850
{
4951
try
5052
{
5153
_settingsManager.Save(SettingsManager.StartOnLoginKey, StartOnLogin);
54+
if (StartOnLogin)
55+
{
56+
StartupManager.Enable();
57+
}
58+
else
59+
{
60+
StartupManager.Disable();
61+
}
5262
}
5363
catch (Exception ex)
5464
{
55-
Console.WriteLine($"Error saving {SettingsManager.StartOnLoginKey} setting: {ex.Message}");
65+
_logger.LogError($"Error saving {SettingsManager.StartOnLoginKey} setting: {ex.Message}");
5666
}
5767
}
5868
};
59-
}
6069

61-
public void Initialize(Window window, DispatcherQueue dispatcherQueue)
62-
{
63-
_window = window;
64-
_dispatcherQueue = dispatcherQueue;
65-
if (!_dispatcherQueue.HasThreadAccess)
66-
throw new InvalidOperationException("Initialize must be called from the UI thread");
70+
// Ensure the StartOnLogin property matches the current startup state.
71+
if (StartOnLogin != StartupManager.IsEnabled())
72+
{
73+
StartOnLogin = StartupManager.IsEnabled();
74+
}
6775
}
6876
}

App/Views/Pages/SettingsMainPage.xaml

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -39,14 +39,16 @@
3939

4040
<controls:SettingsCard Description="This setting controls whether the Coder Desktop app starts on Windows startup."
4141
Header="Start on login"
42-
HeaderIcon="{ui:FontIcon Glyph=&#xE819;}">
42+
HeaderIcon="{ui:FontIcon Glyph=&#xE819;}"
43+
IsEnabled="{x:Bind ViewModel.StartOnLoginDisabled, Converter={StaticResource InverseBoolConverter}, Mode=OneWay}">
4344
<ToggleSwitch IsOn="{x:Bind ViewModel.StartOnLogin, Mode=TwoWay}" />
4445
</controls:SettingsCard>
4546

4647
<TextBlock Style="{StaticResource SettingsSectionHeaderTextBlockStyle}" Text="Coder Connect" />
47-
<controls:SettingsCard Description="This setting controls whether Coder Connect automatically starts with Coder Desktop "
48+
<controls:SettingsCard Description="This setting controls whether Coder Connect automatically starts with Coder Desktop. "
4849
Header="Connect on launch"
49-
HeaderIcon="{ui:FontIcon Glyph=&#xE8AF;}">
50+
HeaderIcon="{ui:FontIcon Glyph=&#xE8AF;}"
51+
>
5052
<ToggleSwitch IsOn="{x:Bind ViewModel.ConnectOnLaunch, Mode=TwoWay}" />
5153
</controls:SettingsCard>
5254
</StackPanel>

App/Views/SettingsWindow.xaml.cs

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,6 @@ public SettingsWindow(SettingsViewModel viewModel)
1818

1919
SystemBackdrop = new DesktopAcrylicBackdrop();
2020

21-
ViewModel.Initialize(this, DispatcherQueue);
2221
RootFrame.Content = new SettingsMainPage(ViewModel);
2322

2423
this.CenterOnScreen();

Tests.App/Services/SettingsManagerTest.cs

Lines changed: 0 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,4 @@
11
using Coder.Desktop.App.Services;
2-
using System;
3-
using System.Collections.Generic;
4-
using System.Linq;
5-
using System.Text;
6-
using System.Threading.Tasks;
72

83
namespace Coder.Desktop.Tests.App.Services
94
{

0 commit comments

Comments
 (0)
pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy