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 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+