diff --git a/AppTunnel/Services/ProfileService.cs b/AppTunnel/Services/ProfileService.cs index 339b425..1c146ce 100644 --- a/AppTunnel/Services/ProfileService.cs +++ b/AppTunnel/Services/ProfileService.cs @@ -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 } } + /// + /// Load global application settings from disk. + /// + public AppSettings LoadAppSettings() + { + if (!File.Exists(AppSettingsFile)) + return new AppSettings(); + + try + { + var json = File.ReadAllText(AppSettingsFile, Encoding.UTF8); + return JsonSerializer.Deserialize(json, JsonOptions) ?? new AppSettings(); + } + catch + { + return new AppSettings(); + } + } + + /// + /// Save global application settings to disk. + /// + public void SaveAppSettings(AppSettings settings) + { + Directory.CreateDirectory(ProfileDir); + var json = JsonSerializer.Serialize(settings, JsonOptions); + File.WriteAllText(AppSettingsFile, json, Encoding.UTF8); + } + + /// + /// Global application settings (startup + auto-connect preferences). + /// + public class AppSettings + { + public bool StartWithWindows { get; set; } = false; + public bool AutoConnectOnStartup { get; set; } = false; + public string? LastActiveProfileId { get; set; } = null; + } + /// /// Load all saved profiles from disk. /// diff --git a/AppTunnel/ViewModels/MainViewModel.Connection.cs b/AppTunnel/ViewModels/MainViewModel.Connection.cs index da27099..b5b2686 100644 --- a/AppTunnel/ViewModels/MainViewModel.Connection.cs +++ b/AppTunnel/ViewModels/MainViewModel.Connection.cs @@ -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 diff --git a/AppTunnel/ViewModels/MainViewModel.Core.cs b/AppTunnel/ViewModels/MainViewModel.Core.cs index 8c6e6fe..961e621 100644 --- a/AppTunnel/ViewModels/MainViewModel.Core.cs +++ b/AppTunnel/ViewModels/MainViewModel.Core.cs @@ -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 diff --git a/AppTunnel/Views/SettingsTabView.xaml b/AppTunnel/Views/SettingsTabView.xaml index 8a3fc0d..bcd1dee 100644 --- a/AppTunnel/Views/SettingsTabView.xaml +++ b/AppTunnel/Views/SettingsTabView.xaml @@ -112,6 +112,52 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +