mirror of
https://github.com/MaxiFan/TunnelX.git
synced 2026-05-17 21:14:37 +03:00
Add startup and auto-connect app settings
Persist global app settings and expose startup/auto-connect controls. Adds AppSettings (StartWithWindows, AutoConnectOnStartup, LastActiveProfileId) plus LoadAppSettings/SaveAppSettings to ProfileService (appsettings.json). MainViewModel now loads/saves these settings, exposes StartWithWindows/AutoConnectOnStartup/LastActiveProfileId properties, restores last profile and triggers auto-connect on startup when enabled, and updates the HKCU Run registry entry to enable/disable startup (with a user warning message on enable). Adds corresponding UI controls to SettingsTabView and includes defensive error handling and sensible defaults.
This commit is contained in:
@@ -27,6 +27,7 @@ public class ProfileService
|
||||
private static readonly string ExcludesFile = Path.Combine(ProfileDir, "excludes.json");
|
||||
private static readonly string IncludesFile = Path.Combine(ProfileDir, "includes.json");
|
||||
private static readonly string TunnelAppsFile = Path.Combine(ProfileDir, "tunnelapps.json");
|
||||
private static readonly string AppSettingsFile = Path.Combine(ProfileDir, "appsettings.json");
|
||||
|
||||
private static readonly JsonSerializerOptions JsonOptions = new()
|
||||
{
|
||||
@@ -51,6 +52,45 @@ public class ProfileService
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Load global application settings from disk.
|
||||
/// </summary>
|
||||
public AppSettings LoadAppSettings()
|
||||
{
|
||||
if (!File.Exists(AppSettingsFile))
|
||||
return new AppSettings();
|
||||
|
||||
try
|
||||
{
|
||||
var json = File.ReadAllText(AppSettingsFile, Encoding.UTF8);
|
||||
return JsonSerializer.Deserialize<AppSettings>(json, JsonOptions) ?? new AppSettings();
|
||||
}
|
||||
catch
|
||||
{
|
||||
return new AppSettings();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Save global application settings to disk.
|
||||
/// </summary>
|
||||
public void SaveAppSettings(AppSettings settings)
|
||||
{
|
||||
Directory.CreateDirectory(ProfileDir);
|
||||
var json = JsonSerializer.Serialize(settings, JsonOptions);
|
||||
File.WriteAllText(AppSettingsFile, json, Encoding.UTF8);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Global application settings (startup + auto-connect preferences).
|
||||
/// </summary>
|
||||
public class AppSettings
|
||||
{
|
||||
public bool StartWithWindows { get; set; } = false;
|
||||
public bool AutoConnectOnStartup { get; set; } = false;
|
||||
public string? LastActiveProfileId { get; set; } = null;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Load all saved profiles from disk.
|
||||
/// </summary>
|
||||
|
||||
@@ -113,6 +113,7 @@ public partial class MainViewModel
|
||||
VpnIp = _vpnService.Status.VpnLocalIp;
|
||||
VpnAdapterName = ResolveInterfaceName(_vpnService.Status.VpnInterfaceIndex);
|
||||
_connectionStartTime = DateTime.Now;
|
||||
LastActiveProfileId = _selectedProfile?.Id;
|
||||
RaiseHealthStatusChanged();
|
||||
|
||||
// Start traffic routing for enabled apps
|
||||
|
||||
@@ -6,6 +6,7 @@ using System.Runtime.CompilerServices;
|
||||
using System.Text.Json.Nodes;
|
||||
using System.Windows;
|
||||
using System.Windows.Input;
|
||||
using Microsoft.Win32;
|
||||
using Application = System.Windows.Application;
|
||||
using System.Windows.Threading;
|
||||
using AppTunnel.Models;
|
||||
@@ -23,6 +24,7 @@ public partial class MainViewModel : INotifyPropertyChanged
|
||||
private readonly DispatcherTimer _saveDebounceTimer;
|
||||
private CancellationTokenSource? _connectionCts;
|
||||
private DateTime _connectionStartTime;
|
||||
private ProfileService.AppSettings _appSettings = new();
|
||||
|
||||
public MainViewModel()
|
||||
{
|
||||
@@ -90,7 +92,24 @@ public partial class MainViewModel : INotifyPropertyChanged
|
||||
LoadExcludes();
|
||||
LoadIncludes();
|
||||
LoadHistory();
|
||||
LoadAppSettings();
|
||||
_ = CheckForUpdatesAsync(true);
|
||||
|
||||
// Auto-connect to last active profile if enabled
|
||||
if (_appSettings.AutoConnectOnStartup && !string.IsNullOrEmpty(_appSettings.LastActiveProfileId))
|
||||
{
|
||||
var lastProfile = Profiles.FirstOrDefault(p => p.Id == _appSettings.LastActiveProfileId);
|
||||
if (lastProfile != null)
|
||||
{
|
||||
SelectedProfile = lastProfile;
|
||||
_ = ToggleConnectionAsync().ContinueWith(t =>
|
||||
{
|
||||
if (t.IsFaulted)
|
||||
Application.Current?.Dispatcher.Invoke(() =>
|
||||
StatusText = $"خطای اتصال خودکار: {t.Exception?.InnerException?.Message}");
|
||||
}, TaskScheduler.Default);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#region Properties
|
||||
@@ -220,6 +239,46 @@ public partial class MainViewModel : INotifyPropertyChanged
|
||||
? "Game Mode فعال است: Route نگهداری طولانیتر، DNS سریعتر و DSCP برای بستههای بازی اعمال میشود."
|
||||
: "Game Mode غیرفعال است: حالت متعادل برای مصرف عمومی.";
|
||||
|
||||
private bool _startWithWindows;
|
||||
public bool StartWithWindows
|
||||
{
|
||||
get => _startWithWindows;
|
||||
set
|
||||
{
|
||||
if (_startWithWindows == value) return;
|
||||
_startWithWindows = value;
|
||||
OnPropertyChanged();
|
||||
UpdateStartupRegistry(value);
|
||||
_appSettings.StartWithWindows = value;
|
||||
_profileService.SaveAppSettings(_appSettings);
|
||||
}
|
||||
}
|
||||
|
||||
private bool _autoConnectOnStartup;
|
||||
public bool AutoConnectOnStartup
|
||||
{
|
||||
get => _autoConnectOnStartup;
|
||||
set
|
||||
{
|
||||
if (_autoConnectOnStartup == value) return;
|
||||
_autoConnectOnStartup = value;
|
||||
OnPropertyChanged();
|
||||
_appSettings.AutoConnectOnStartup = value;
|
||||
_profileService.SaveAppSettings(_appSettings);
|
||||
}
|
||||
}
|
||||
|
||||
public string? LastActiveProfileId
|
||||
{
|
||||
get => _appSettings.LastActiveProfileId;
|
||||
set
|
||||
{
|
||||
if (_appSettings.LastActiveProfileId == value) return;
|
||||
_appSettings.LastActiveProfileId = value;
|
||||
_profileService.SaveAppSettings(_appSettings);
|
||||
}
|
||||
}
|
||||
|
||||
private bool _isBusy;
|
||||
public bool IsBusy
|
||||
{
|
||||
@@ -918,6 +977,49 @@ public partial class MainViewModel : INotifyPropertyChanged
|
||||
OnPropertyChanged(nameof(HealthRoutesText));
|
||||
}
|
||||
|
||||
private void LoadAppSettings()
|
||||
{
|
||||
_appSettings = _profileService.LoadAppSettings();
|
||||
_startWithWindows = _appSettings.StartWithWindows;
|
||||
_autoConnectOnStartup = _appSettings.AutoConnectOnStartup;
|
||||
OnPropertyChanged(nameof(StartWithWindows));
|
||||
OnPropertyChanged(nameof(AutoConnectOnStartup));
|
||||
}
|
||||
|
||||
private static void UpdateStartupRegistry(bool enable)
|
||||
{
|
||||
const string runKey = "Software\\Microsoft\\Windows\\CurrentVersion\\Run";
|
||||
const string appName = "TunnelX";
|
||||
try
|
||||
{
|
||||
using var key = Registry.CurrentUser.OpenSubKey(runKey, writable: true);
|
||||
if (key == null) return;
|
||||
|
||||
if (enable)
|
||||
{
|
||||
var exePath = Environment.ProcessPath ??
|
||||
Process.GetCurrentProcess().MainModule?.FileName ??
|
||||
System.IO.Path.Combine(AppContext.BaseDirectory, "TunnelX.exe");
|
||||
key.SetValue(appName, $"\"{exePath}\"");
|
||||
|
||||
System.Windows.MessageBox.Show(
|
||||
"استارتآپ فعال شد.\n\n⚠️ برای کارکرد صحیح، پس از این نباید محل فایل اجرایی TunnelX را تغییر دهید.",
|
||||
"TunnelX — استارتآپ",
|
||||
MessageBoxButton.OK,
|
||||
MessageBoxImage.Information);
|
||||
}
|
||||
else
|
||||
{
|
||||
if (key.GetValue(appName) != null)
|
||||
key.DeleteValue(appName);
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Logger.Warning($"[STARTUP] Registry update failed: {ex.Message}");
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region INotifyPropertyChanged
|
||||
|
||||
@@ -112,6 +112,52 @@
|
||||
</StackPanel>
|
||||
</Border>
|
||||
|
||||
<!-- Startup & Auto-Connect -->
|
||||
<Border Style="{StaticResource CardPanel}">
|
||||
<StackPanel>
|
||||
<TextBlock Style="{StaticResource SectionHeader}" Text="🖥️ استارتآپ و اتصال خودکار"
|
||||
Margin="0,0,0,4"/>
|
||||
|
||||
<Grid Margin="0,2,0,0">
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="*"/>
|
||||
<ColumnDefinition Width="Auto"/>
|
||||
</Grid.ColumnDefinitions>
|
||||
<StackPanel Grid.Column="0">
|
||||
<TextBlock Text="اجرای خودکار هنگام روشن شدن ویندوز" FontSize="11" FontWeight="SemiBold"
|
||||
Foreground="{StaticResource TextPrimaryBrush}"/>
|
||||
<TextBlock Text="⚠️ پس از فعال کردن، نباید محل فایل اجرایی TunnelX را تغییر دهید."
|
||||
FontSize="10" Foreground="{StaticResource WarningBrush}"
|
||||
TextWrapping="Wrap" Margin="0,2,0,0"/>
|
||||
</StackPanel>
|
||||
<CheckBox Grid.Column="1"
|
||||
Style="{StaticResource ToggleSwitch}"
|
||||
IsChecked="{Binding StartWithWindows, Mode=TwoWay}"
|
||||
VerticalAlignment="Center"/>
|
||||
</Grid>
|
||||
|
||||
<Border Background="#18FFFFFF" Height="1" Margin="0,8,0,8"/>
|
||||
|
||||
<Grid>
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="*"/>
|
||||
<ColumnDefinition Width="Auto"/>
|
||||
</Grid.ColumnDefinitions>
|
||||
<StackPanel Grid.Column="0">
|
||||
<TextBlock Text="اتصال خودکار به آخرین کانکشن فعال" FontSize="11" FontWeight="SemiBold"
|
||||
Foreground="{StaticResource TextPrimaryBrush}"/>
|
||||
<TextBlock Text="اگر آخرین بار یک پروفایل متصل بوده، هنگام اجرای برنامه به آن وصل میشود."
|
||||
FontSize="10" Foreground="{StaticResource TextSecondaryBrush}"
|
||||
TextWrapping="Wrap" Margin="0,2,0,0"/>
|
||||
</StackPanel>
|
||||
<CheckBox Grid.Column="1"
|
||||
Style="{StaticResource ToggleSwitch}"
|
||||
IsChecked="{Binding AutoConnectOnStartup, Mode=TwoWay}"
|
||||
VerticalAlignment="Center"/>
|
||||
</Grid>
|
||||
</StackPanel>
|
||||
</Border>
|
||||
|
||||
</StackPanel>
|
||||
</ScrollViewer>
|
||||
</UserControl>
|
||||
|
||||
Reference in New Issue
Block a user