This commit is contained in:
Reza Bakhshi Laktasaraei 2025-04-27 19:54:45 +03:30
commit 3f8bbb6074
31 changed files with 475 additions and 265 deletions

View file

@ -5,21 +5,83 @@ internal static class Program
[STAThread]
private static void Main(string[] args)
{
if (args.Length == 0)
try
{
Console.WriteLine(Resx.Resource.Guidelines);
Thread.Sleep(5000);
return;
}
// If no arguments are provided, display usage guidelines and exit
if (args.Length == 0)
{
ShowHelp();
return;
}
var argData = Uri.UnescapeDataString(string.Join(" ", args));
if (argData.Equals("rebootas"))
// Log all arguments for debugging purposes
foreach (var arg in args)
{
Console.WriteLine(arg);
}
// Parse command based on first argument
switch (args[0].ToLowerInvariant())
{
case "rebootas":
// Handle application restart
HandleRebootAsync();
break;
case "help":
case "--help":
case "-h":
case "/?":
// Display help information
ShowHelp();
break;
default:
// Default behavior: handle as upgrade data
// Maintain backward compatibility with existing usage pattern
var argData = Uri.UnescapeDataString(string.Join(" ", args));
HandleUpgrade(argData);
break;
}
}
catch (Exception ex)
{
Thread.Sleep(1000);
Utils.StartV2RayN();
return;
// Global exception handling
Console.WriteLine($"An error occurred: {ex.Message}");
Console.WriteLine("Press any key to exit...");
Console.ReadKey();
}
}
UpgradeApp.Upgrade(argData);
/// <summary>
/// Display help information and usage guidelines
/// </summary>
private static void ShowHelp()
{
Console.WriteLine(Resx.Resource.Guidelines);
Console.WriteLine("Available commands:");
Console.WriteLine(" rebootas - Restart the application");
Console.WriteLine(" help - Display this help information");
Thread.Sleep(5000);
}
/// <summary>
/// Handle application restart
/// </summary>
private static void HandleRebootAsync()
{
Console.WriteLine("Restarting application...");
Thread.Sleep(1000);
Utils.StartV2RayN();
}
/// <summary>
/// Handle application upgrade with the provided data
/// </summary>
/// <param name="upgradeData">Data for the upgrade process</param>
private static void HandleUpgrade(string upgradeData)
{
Console.WriteLine("Upgrading application...");
UpgradeApp.Upgrade(upgradeData);
}
}

View file

@ -20,6 +20,7 @@ public enum EViewAction
BrowseServer,
ImportRulesFromFile,
InitSettingFont,
PasswordInput,
SubEditWindow,
RoutingRuleSettingWindow,
RoutingRuleDetailsWindow,

View file

@ -31,14 +31,7 @@ public sealed class AppHandler
}
}
public bool IsAdministrator
{
get
{
_isAdministrator ??= Utils.IsAdministrator();
return _isAdministrator.Value;
}
}
public string LinuxSudoPwd { get; set; }
#endregion Property

View file

@ -0,0 +1,144 @@
using System.Diagnostics;
using System.Text;
using CliWrap;
namespace ServiceLib.Handler;
public class CoreAdminHandler
{
private static readonly Lazy<CoreAdminHandler> _instance = new(() => new());
public static CoreAdminHandler Instance => _instance.Value;
private Config _config;
private Action<bool, string>? _updateFunc;
private const string _tag = "CoreAdminHandler";
private int _linuxSudoPid = -1;
public async Task Init(Config config, Action<bool, string> updateFunc)
{
if (_config != null)
{
return;
}
_config = config;
_updateFunc = updateFunc;
}
private void UpdateFunc(bool notify, string msg)
{
_updateFunc?.Invoke(notify, msg);
}
public async Task<Process?> RunProcessAsLinuxSudo(string fileName, CoreInfo coreInfo, string configPath)
{
try
{
var cmdLine = $"{fileName.AppendQuotes()} {string.Format(coreInfo.Arguments, Utils.GetBinConfigPath(configPath).AppendQuotes())}";
var shFilePath = await CreateLinuxShellFile(cmdLine, "run_as_sudo.sh");
Process proc = new()
{
StartInfo = new()
{
FileName = shFilePath,
Arguments = "",
WorkingDirectory = Utils.GetBinConfigPath(),
UseShellExecute = false,
RedirectStandardInput = true,
RedirectStandardOutput = true,
RedirectStandardError = true,
CreateNoWindow = true,
StandardInputEncoding = Encoding.UTF8,
StandardOutputEncoding = Encoding.UTF8,
StandardErrorEncoding = Encoding.UTF8,
}
};
proc.OutputDataReceived += (sender, e) =>
{
if (e.Data.IsNotEmpty())
{
UpdateFunc(false, e.Data + Environment.NewLine);
}
};
proc.ErrorDataReceived += (sender, e) =>
{
if (e.Data.IsNotEmpty())
{
UpdateFunc(false, e.Data + Environment.NewLine);
}
};
proc.Start();
proc.BeginOutputReadLine();
proc.BeginErrorReadLine();
await Task.Delay(10);
await proc.StandardInput.WriteLineAsync();
await Task.Delay(10);
await proc.StandardInput.WriteLineAsync(AppHandler.Instance.LinuxSudoPwd);
await Task.Delay(100);
if (proc is null or { HasExited: true })
{
throw new Exception(ResUI.FailedToRunCore);
}
_linuxSudoPid = proc.Id;
return proc;
}
catch (Exception ex)
{
Logging.SaveLog(_tag, ex);
UpdateFunc(false, ex.Message);
return null;
}
}
public async Task KillProcessAsLinuxSudo()
{
if (_linuxSudoPid < 0)
{
return;
}
try
{
var cmdLine = $"pkill -P {_linuxSudoPid} ; kill {_linuxSudoPid}";
var shFilePath = await CreateLinuxShellFile(cmdLine, "kill_as_sudo.sh");
var result = await Cli.Wrap(shFilePath)
.WithStandardInputPipe(PipeSource.FromString(AppHandler.Instance.LinuxSudoPwd))
.ExecuteAsync();
_linuxSudoPid = -1;
}
catch (Exception ex)
{
Logging.SaveLog(_tag, ex);
UpdateFunc(false, ex.Message);
}
}
private async Task<string> CreateLinuxShellFile(string cmdLine, string fileName)
{
var shFilePath = Utils.GetBinConfigPath(fileName);
File.Delete(shFilePath);
var sb = new StringBuilder();
sb.AppendLine("#!/bin/sh");
if (Utils.IsAdministrator())
{
sb.AppendLine($"{cmdLine}");
}
else
{
sb.AppendLine($"sudo -S {cmdLine}");
}
await File.WriteAllTextAsync(shFilePath, sb.ToString());
await Utils.SetLinuxChmod(shFilePath);
return shFilePath;
}
}

View file

@ -13,7 +13,7 @@ public class CoreHandler
private Config _config;
private Process? _process;
private Process? _processPre;
private int _linuxSudoPid = -1;
private bool _linuxSudo = false;
private Action<bool, string>? _updateFunc;
private const string _tag = "CoreHandler";
@ -155,23 +155,23 @@ public class CoreHandler
{
try
{
if (_linuxSudo)
{
await CoreAdminHandler.Instance.KillProcessAsLinuxSudo();
_linuxSudo = false;
}
if (_process != null)
{
await ProcUtils.ProcessKill(_process, true);
await ProcUtils.ProcessKill(_process, Utils.IsWindows());
_process = null;
}
if (_processPre != null)
{
await ProcUtils.ProcessKill(_processPre, true);
await ProcUtils.ProcessKill(_processPre, Utils.IsWindows());
_processPre = null;
}
if (_linuxSudoPid > 0)
{
await KillProcessAsLinuxSudo();
}
_linuxSudoPid = -1;
}
catch (Exception ex)
{
@ -225,15 +225,6 @@ public class CoreHandler
_updateFunc?.Invoke(notify, msg);
}
private bool IsNeedSudo(ECoreType eCoreType)
{
return _config.TunModeItem.EnableTun
&& eCoreType == ECoreType.sing_box
&& (Utils.IsNonWindows())
//&& _config.TunModeItem.LinuxSudoPwd.IsNotEmpty()
;
}
#endregion Private
#region Process
@ -247,6 +238,16 @@ public class CoreHandler
return null;
}
if (mayNeedSudo
&& _config.TunModeItem.EnableTun
&& coreInfo.CoreType == ECoreType.sing_box
&& Utils.IsNonWindows())
{
_linuxSudo = true;
await CoreAdminHandler.Instance.Init(_config, _updateFunc);
return await CoreAdminHandler.Instance.RunProcessAsLinuxSudo(fileName, coreInfo, configPath);
}
try
{
Process proc = new()
@ -265,40 +266,25 @@ public class CoreHandler
}
};
var isNeedSudo = mayNeedSudo && IsNeedSudo(coreInfo.CoreType);
if (isNeedSudo)
{
await RunProcessAsLinuxSudo(proc, fileName, coreInfo, configPath);
}
if (displayLog)
{
proc.OutputDataReceived += (sender, e) =>
{
if (e.Data.IsNullOrEmpty())
return;
UpdateFunc(false, e.Data + Environment.NewLine);
if (e.Data.IsNotEmpty())
{
UpdateFunc(false, e.Data + Environment.NewLine);
}
};
proc.ErrorDataReceived += (sender, e) =>
{
if (e.Data.IsNullOrEmpty())
return;
UpdateFunc(false, e.Data + Environment.NewLine);
if (e.Data.IsNotEmpty())
{
UpdateFunc(false, e.Data + Environment.NewLine);
}
};
}
proc.Start();
if (isNeedSudo && _config.TunModeItem.LinuxSudoPwd.IsNotEmpty())
{
var pwd = DesUtils.Decrypt(_config.TunModeItem.LinuxSudoPwd);
await Task.Delay(10);
await proc.StandardInput.WriteLineAsync(pwd);
await Task.Delay(10);
await proc.StandardInput.WriteLineAsync(pwd);
}
if (isNeedSudo)
_linuxSudoPid = proc.Id;
if (displayLog)
{
proc.BeginOutputReadLine();
@ -322,88 +308,4 @@ public class CoreHandler
}
#endregion Process
#region Linux
private async Task RunProcessAsLinuxSudo(Process proc, string fileName, CoreInfo coreInfo, string configPath)
{
var cmdLine = $"{fileName.AppendQuotes()} {string.Format(coreInfo.Arguments, Utils.GetBinConfigPath(configPath).AppendQuotes())}";
var shFilePath = await CreateLinuxShellFile(cmdLine, "run_as_sudo.sh");
proc.StartInfo.FileName = shFilePath;
proc.StartInfo.Arguments = "";
proc.StartInfo.WorkingDirectory = "";
if (_config.TunModeItem.LinuxSudoPwd.IsNotEmpty())
{
proc.StartInfo.StandardInputEncoding = Encoding.UTF8;
proc.StartInfo.RedirectStandardInput = true;
}
}
private async Task KillProcessAsLinuxSudo()
{
var cmdLine = $"kill {_linuxSudoPid}";
var shFilePath = await CreateLinuxShellFile(cmdLine, "kill_as_sudo.sh");
Process proc = new()
{
StartInfo = new()
{
FileName = shFilePath,
UseShellExecute = false,
CreateNoWindow = true,
StandardInputEncoding = Encoding.UTF8,
RedirectStandardInput = true
}
};
proc.Start();
if (_config.TunModeItem.LinuxSudoPwd.IsNotEmpty())
{
try
{
var pwd = DesUtils.Decrypt(_config.TunModeItem.LinuxSudoPwd);
await Task.Delay(10);
await proc.StandardInput.WriteLineAsync(pwd);
await Task.Delay(10);
await proc.StandardInput.WriteLineAsync(pwd);
}
catch (Exception)
{
// ignored
}
}
var timeout = new CancellationTokenSource(TimeSpan.FromSeconds(10));
await proc.WaitForExitAsync(timeout.Token);
await Task.Delay(3000);
}
private async Task<string> CreateLinuxShellFile(string cmdLine, string fileName)
{
//Shell scripts
var shFilePath = Utils.GetBinConfigPath(AppHandler.Instance.IsAdministrator ? "root_" + fileName : fileName);
File.Delete(shFilePath);
var sb = new StringBuilder();
sb.AppendLine("#!/bin/sh");
if (AppHandler.Instance.IsAdministrator)
{
sb.AppendLine($"{cmdLine}");
}
else if (_config.TunModeItem.LinuxSudoPwd.IsNullOrEmpty())
{
sb.AppendLine($"pkexec {cmdLine}");
}
else
{
sb.AppendLine($"sudo -S {cmdLine}");
}
await File.WriteAllTextAsync(shFilePath, sb.ToString());
await Utils.SetLinuxChmod(shFilePath);
Logging.SaveLog(shFilePath);
return shFilePath;
}
#endregion Linux
}

View file

@ -147,7 +147,6 @@ public class TunModeItem
public int Mtu { get; set; }
public bool EnableExInbound { get; set; }
public bool EnableIPv6Address { get; set; }
public string? LinuxSudoPwd { get; set; }
}
[Serializable]

View file

@ -3247,7 +3247,7 @@ namespace ServiceLib.Resx {
}
/// <summary>
/// 查找类似 The password is encrypted and stored only in local files 的本地化字符串。
/// 查找类似 The password you entered cannot be verified, so make sure you enter it correctly. If the application does not work properly due to an incorrect input, please restart the application. The password will not be stored and you will need to enter it again after each restart. 的本地化字符串。
/// </summary>
public static string TbSettingsLinuxSudoPasswordTip {
get {

View file

@ -1339,7 +1339,7 @@
<value>رمز عبور sudo سیستم</value>
</data>
<data name="TbSettingsLinuxSudoPasswordTip" xml:space="preserve">
<value>رمز عبور رمزگذاری شده و فقط در فایل های محلی ذخیره می شود.</value>
<value>رمز عبوری که وارد کرده اید تایید نمی شود، بنابراین مطمئن شوید که آن را به درستی وارد کرده اید. اگر برنامه به دلیل ورودی نادرست به درستی کار نمی کند، لطفاً برنامه را مجدداً راه اندازی کنید. رمز عبور ذخیره نمی شود و پس از هر بار راه اندازی مجدد باید آن را دوباره وارد کنید.</value>
</data>
<data name="TbSettingsLinuxSudoPasswordIsEmpty" xml:space="preserve">
<value>لطفاً ابتدا رمز عبور sudo را در تنظیمات حالت Tun تنظیم کنید</value>

View file

@ -1339,7 +1339,7 @@
<value>Rendszer sudo jelszó</value>
</data>
<data name="TbSettingsLinuxSudoPasswordTip" xml:space="preserve">
<value>A jelszó titkosítva és csak a helyi fájlokban tárolva.</value>
<value>The password you entered cannot be verified, so make sure you enter it correctly. If the application does not work properly due to an incorrect input, please restart the application. The password will not be stored and you will need to enter it again after each restart.</value>
</data>
<data name="TbSettingsLinuxSudoPasswordIsEmpty" xml:space="preserve">
<value>Kérlek, először állítsd be a sudo jelszót a Tun módban</value>

View file

@ -1339,7 +1339,7 @@
<value>System sudo password</value>
</data>
<data name="TbSettingsLinuxSudoPasswordTip" xml:space="preserve">
<value>The password is encrypted and stored only in local files</value>
<value>The password you entered cannot be verified, so make sure you enter it correctly. If the application does not work properly due to an incorrect input, please restart the application. The password will not be stored and you will need to enter it again after each restart.</value>
</data>
<data name="TbSettingsLinuxSudoPasswordIsEmpty" xml:space="preserve">
<value>Please set the sudo password in Tun mode settings first</value>

View file

@ -1339,7 +1339,7 @@
<value>System sudo password</value>
</data>
<data name="TbSettingsLinuxSudoPasswordTip" xml:space="preserve">
<value>Пароль зашифрован и хранится только в локальных файлах..</value>
<value>The password you entered cannot be verified, so make sure you enter it correctly. If the application does not work properly due to an incorrect input, please restart the application. The password will not be stored and you will need to enter it again after each restart.</value>
</data>
<data name="TbSettingsLinuxSudoPasswordIsEmpty" xml:space="preserve">
<value>Please set the sudo password in Tun mode settings first</value>

View file

@ -1071,13 +1071,13 @@
<data name="TbHeaderType8" xml:space="preserve">
<value>拥塞控制算法</value>
</data>
<data name="LvPrevProfile" xml:space="preserve">
<data name="LvPrevProfile" xml:space="preserve">
<value>前置代理配置文件别名</value>
</data>
<data name="LvNextProfile" xml:space="preserve">
<data name="LvNextProfile" xml:space="preserve">
<value>落地代理配置文件別名</value>
</data>
<data name="LvPrevProfileTip" xml:space="preserve">
<data name="LvPrevProfileTip" xml:space="preserve">
<value>请确保配置文件别名存在并唯一</value>
</data>
<data name="TbSettingsEnableExInbound" xml:space="preserve">
@ -1336,7 +1336,7 @@
<value>系统的 sudo 密码</value>
</data>
<data name="TbSettingsLinuxSudoPasswordTip" xml:space="preserve">
<value>密码已加密且只存储在本地文件中,无密码则每次都要输入</value>
<value>输入的密码无法校验,所以请确保输入正确。如果因为输入错误导致无法正常运行时,请重启本应用。 密码不会存储,每次重启后都需要再次输入。</value>
</data>
<data name="TbSettingsLinuxSudoPasswordIsEmpty" xml:space="preserve">
<value>请先在 Tun 模式设置中设置 sudo 密码</value>
@ -1413,4 +1413,4 @@
<data name="menuExportConfig" xml:space="preserve">
<value>导出配置文件</value>
</data>
</root>
</root>

View file

@ -1071,13 +1071,13 @@
<data name="TbHeaderType8" xml:space="preserve">
<value>擁塞控制算法</value>
</data>
<data name="LvPrevProfile" xml:space="preserve">
<data name="LvPrevProfile" xml:space="preserve">
<value>前置代理設定檔別名</value>
</data>
<data name="LvNextProfile" xml:space="preserve">
<data name="LvNextProfile" xml:space="preserve">
<value>落地代理設定檔別名</value>
</data>
<data name="LvPrevProfileTip" xml:space="preserve">
<data name="LvPrevProfileTip" xml:space="preserve">
<value>請確保設定檔別名存在並且唯一</value>
</data>
<data name="TbSettingsEnableExInbound" xml:space="preserve">
@ -1336,7 +1336,7 @@
<value>系統的 sudo 密碼</value>
</data>
<data name="TbSettingsLinuxSudoPasswordTip" xml:space="preserve">
<value>密碼已加密且只儲存在本機檔案中,無密碼則每次都要輸入</value>
<value>輸入的密碼無法校驗,所以請確保輸入正確。如果因為輸入錯誤導致無法正常運作時,請重新啟動本應用。 密碼不會存儲,每次重啟後都需要再次輸入。</value>
</data>
<data name="TbSettingsLinuxSudoPasswordIsEmpty" xml:space="preserve">
<value>請先在 Tun 模式設定中設定 sudo 密碼</value>
@ -1413,4 +1413,4 @@
<data name="menuExportConfig" xml:space="preserve">
<value>匯出設定檔</value>
</data>
</root>
</root>

View file

@ -88,7 +88,6 @@ public class OptionSettingViewModel : MyReactiveObject
[Reactive] public int TunMtu { get; set; }
[Reactive] public bool TunEnableExInbound { get; set; }
[Reactive] public bool TunEnableIPv6Address { get; set; }
[Reactive] public string TunLinuxSudoPassword { get; set; }
#endregion Tun mode
@ -205,7 +204,6 @@ public class OptionSettingViewModel : MyReactiveObject
TunMtu = _config.TunModeItem.Mtu;
TunEnableExInbound = _config.TunModeItem.EnableExInbound;
TunEnableIPv6Address = _config.TunModeItem.EnableIPv6Address;
TunLinuxSudoPassword = _config.TunModeItem.LinuxSudoPwd;
#endregion Tun mode
@ -358,11 +356,7 @@ public class OptionSettingViewModel : MyReactiveObject
_config.TunModeItem.Mtu = TunMtu;
_config.TunModeItem.EnableExInbound = TunEnableExInbound;
_config.TunModeItem.EnableIPv6Address = TunEnableIPv6Address;
if (TunLinuxSudoPassword != _config.TunModeItem.LinuxSudoPwd)
{
_config.TunModeItem.LinuxSudoPwd = DesUtils.Encrypt(TunLinuxSudoPassword);
}
//coreType
await SaveCoreType();

View file

@ -436,11 +436,13 @@ public class StatusBarViewModel : MyReactiveObject
Locator.Current.GetService<MainWindowViewModel>()?.RebootAsAdmin();
return;
}
else if (Utils.IsOSX())
else
{
_config.TunModeItem.EnableTun = false;
NoticeHandler.Instance.SendMessageAndEnqueue(ResUI.TbSettingsLinuxSudoPasswordIsEmpty);
return;
if (await _updateView?.Invoke(EViewAction.PasswordInput, null) == false)
{
_config.TunModeItem.EnableTun = false;
return;
}
}
}
await ConfigHandler.SaveConfig(_config);
@ -452,15 +454,15 @@ public class StatusBarViewModel : MyReactiveObject
{
if (Utils.IsWindows())
{
return AppHandler.Instance.IsAdministrator;
return Utils.IsAdministrator();
}
else if (Utils.IsLinux())
{
return _config.TunModeItem.LinuxSudoPwd.IsNotEmpty();
return AppHandler.Instance.LinuxSudoPwd.IsNotEmpty();
}
else if (Utils.IsOSX())
{
return _config.TunModeItem.LinuxSudoPwd.IsNotEmpty();
return AppHandler.Instance.LinuxSudoPwd.IsNotEmpty();
}
return false;
}

View file

@ -143,7 +143,7 @@ public partial class MainWindow : ReactiveWindow<MainWindowViewModel>
}
else
{
if (AppHandler.Instance.IsAdministrator)
if (Utils.IsAdministrator())
{
this.Title = $"{Utils.GetVersion()} - {ResUI.TbSettingsLinuxSudoPasswordNotSudoRunApp}";
NoticeHandler.Instance.SendMessageAndEnqueue(ResUI.TbSettingsLinuxSudoPasswordNotSudoRunApp);

View file

@ -784,28 +784,6 @@
Margin="{StaticResource Margin4}"
HorizontalAlignment="Left" />
<TextBlock
x:Name="labLinuxSudoPassword"
Grid.Row="7"
Grid.Column="0"
Margin="{StaticResource Margin4}"
VerticalAlignment="Center"
Text="{x:Static resx:ResUI.TbSettingsLinuxSudoPassword}" />
<TextBox
x:Name="txtLinuxSudoPassword"
Grid.Row="7"
Grid.Column="1"
Width="200"
Margin="{StaticResource Margin4}"
HorizontalAlignment="Left" />
<TextBlock
x:Name="labLinuxSudoPasswordTip"
Grid.Row="7"
Grid.Column="2"
Margin="{StaticResource Margin4}"
VerticalAlignment="Center"
Text="{x:Static resx:ResUI.TbSettingsLinuxSudoPasswordTip}"
TextWrapping="Wrap" />
</Grid>
</TabItem>

View file

@ -153,7 +153,6 @@ public partial class OptionSettingWindow : ReactiveWindow<OptionSettingViewModel
this.Bind(ViewModel, vm => vm.TunMtu, v => v.cmbMtu.SelectedValue).DisposeWith(disposables);
this.Bind(ViewModel, vm => vm.TunEnableExInbound, v => v.togEnableExInbound.IsChecked).DisposeWith(disposables);
this.Bind(ViewModel, vm => vm.TunEnableIPv6Address, v => v.togEnableIPv6Address.IsChecked).DisposeWith(disposables);
this.Bind(ViewModel, vm => vm.TunLinuxSudoPassword, v => v.txtLinuxSudoPassword.Text).DisposeWith(disposables);
this.Bind(ViewModel, vm => vm.CoreType1, v => v.cmbCoreType1.SelectedValue).DisposeWith(disposables);
this.Bind(ViewModel, vm => vm.CoreType2, v => v.cmbCoreType2.SelectedValue).DisposeWith(disposables);
@ -170,10 +169,6 @@ public partial class OptionSettingWindow : ReactiveWindow<OptionSettingViewModel
{
txbSettingsExceptionTip2.IsVisible = false;
txtLinuxSudoPassword.IsVisible = false;
labLinuxSudoPassword.IsVisible = false;
labLinuxSudoPasswordTip.IsVisible = false;
labHide2TrayWhenClose.IsVisible = false;
togHide2TrayWhenClose.IsVisible = false;
}

View file

@ -4,6 +4,7 @@ using Avalonia.Controls;
using Avalonia.Controls.ApplicationLifetimes;
using Avalonia.ReactiveUI;
using Avalonia.Threading;
using DialogHostAvalonia;
using ReactiveUI;
using Splat;
using v2rayN.Desktop.Common;
@ -81,6 +82,10 @@ public partial class StatusBarView : ReactiveUserControl<StatusBarViewModel>
return false;
await AvaUtils.SetClipboardData(this, (string)obj);
break;
case EViewAction.PasswordInput:
return await PasswordInputAsync();
break;
}
return await Task.FromResult(true);
}
@ -96,6 +101,20 @@ public partial class StatusBarView : ReactiveUserControl<StatusBarViewModel>
}
}
private async Task<bool> PasswordInputAsync()
{
var dialog = new SudoPasswordInputView();
var obj = await DialogHost.Show(dialog);
if (obj == null)
{
togEnableTun.IsChecked = false;
return false;
}
AppHandler.Instance.LinuxSudoPwd = obj.ToString() ?? string.Empty;
return true;
}
private void TxtRunningServerDisplay_Tapped(object? sender, Avalonia.Input.TappedEventArgs e)
{
ViewModel?.TestServerAvailability();

View file

@ -0,0 +1,70 @@
<UserControl
x:Class="v2rayN.Desktop.Views.SudoPasswordInputView"
xmlns="https://github.com/avaloniaui"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:resx="clr-namespace:ServiceLib.Resx;assembly=ServiceLib"
d:DesignHeight="200"
d:DesignWidth="400"
mc:Ignorable="d">
<DockPanel Margin="{StaticResource Margin8}">
<Border
Margin="{StaticResource Margin4}"
VerticalAlignment="Center"
DockPanel.Dock="Bottom"
Theme="{DynamicResource CardBorder}">
<StackPanel
Margin="{StaticResource Margin4}"
HorizontalAlignment="Center"
Orientation="Horizontal">
<Button
x:Name="btnSave"
Width="100"
Content="{x:Static resx:ResUI.TbConfirm}"
Cursor="Hand"
IsDefault="True" />
<Button
x:Name="btnCancel"
Width="100"
Margin="{StaticResource MarginLr8}"
Content="{x:Static resx:ResUI.TbCancel}"
Cursor="Hand"
IsCancel="True" />
</StackPanel>
</Border>
<Border
Margin="{StaticResource Margin4}"
VerticalAlignment="Center"
Theme="{DynamicResource CardBorder}">
<Grid ColumnDefinitions="Auto,400" RowDefinitions="Auto,Auto,Auto">
<TextBlock
Grid.Row="1"
Grid.Column="0"
Margin="{StaticResource Margin4}"
VerticalAlignment="Center"
Text="{x:Static resx:ResUI.TbSettingsLinuxSudoPassword}" />
<TextBox
x:Name="txtPassword"
Grid.Row="1"
Grid.Column="1"
Margin="{StaticResource Margin4}"
Classes="revealPasswordButton"
Focusable="True" />
<TextBlock
Grid.Row="2"
Grid.Column="1"
Margin="{StaticResource Margin4}"
VerticalAlignment="Center"
Text="{x:Static resx:ResUI.TbSettingsLinuxSudoPasswordTip}"
TextWrapping="Wrap" />
</Grid>
</Border>
</DockPanel>
</UserControl>

View file

@ -0,0 +1,33 @@
using Avalonia.Controls;
using Avalonia.Threading;
using DialogHostAvalonia;
namespace v2rayN.Desktop.Views;
public partial class SudoPasswordInputView : UserControl
{
public SudoPasswordInputView()
{
InitializeComponent();
this.Loaded += (s, e) => txtPassword.Focus();
btnSave.Click += (_, _) =>
{
if (string.IsNullOrEmpty(txtPassword.Text))
{
txtPassword.Focus();
return;
}
Dispatcher.UIThread.Post(() =>
{
DialogHost.Close(null, txtPassword.Text);
});
};
btnCancel.Click += (_, _) =>
{
DialogHost.Close(null);
};
}
}

View file

@ -1,9 +1,9 @@
<Application
x:Class="v2rayN.App"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:materialDesign="http://materialdesigninxaml.net/winfx/xaml/themes"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:conv="clr-namespace:v2rayN.Converters"
xmlns:materialDesign="http://materialdesigninxaml.net/winfx/xaml/themes"
ShutdownMode="OnExplicitShutdown"
StartupUri="Views/MainWindow.xaml">
<Application.Resources>

View file

@ -1,14 +1,14 @@
<reactiveui:ReactiveWindow
x:Class="v2rayN.Views.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:materialDesign="http://materialdesigninxaml.net/winfx/xaml/themes"
xmlns:reactiveui="http://reactiveui.net"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:materialDesign="http://materialdesigninxaml.net/winfx/xaml/themes"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:reactiveui="http://reactiveui.net"
xmlns:resx="clr-namespace:ServiceLib.Resx;assembly=ServiceLib"
xmlns:vms="clr-namespace:ServiceLib.ViewModels;assembly=ServiceLib"
xmlns:view="clr-namespace:v2rayN.Views"
xmlns:vms="clr-namespace:ServiceLib.ViewModels;assembly=ServiceLib"
Title="v2rayN"
Width="900"
Height="700"
@ -43,8 +43,7 @@
Style="{StaticResource MaterialDesignToolBar}"
KeyboardNavigation.TabNavigation="Continue">
<Menu Margin="0,1" Style="{StaticResource ToolbarMenu}">
<MenuItem Padding="8,0"
AutomationProperties.Name="{x:Static resx:ResUI.menuServers}">
<MenuItem Padding="8,0" AutomationProperties.Name="{x:Static resx:ResUI.menuServers}">
<MenuItem.Header>
<StackPanel Orientation="Horizontal">
<materialDesign:PackIcon
@ -112,8 +111,7 @@
</Menu>
<Separator />
<Menu Margin="0,1" Style="{StaticResource ToolbarMenu}">
<MenuItem Padding="8,0"
AutomationProperties.Name="{x:Static resx:ResUI.menuSubscription}">
<MenuItem Padding="8,0" AutomationProperties.Name="{x:Static resx:ResUI.menuSubscription}">
<MenuItem.Header>
<StackPanel Orientation="Horizontal">
<materialDesign:PackIcon
@ -148,8 +146,7 @@
</Menu>
<Separator />
<Menu Margin="0,1" Style="{StaticResource ToolbarMenu}">
<MenuItem Padding="8,0"
AutomationProperties.Name="{x:Static resx:ResUI.menuSetting}">
<MenuItem Padding="8,0" AutomationProperties.Name="{x:Static resx:ResUI.menuSetting}">
<MenuItem.Header>
<StackPanel Orientation="Horizontal">
<materialDesign:PackIcon
@ -215,8 +212,10 @@
</Menu>
<Separator />
<Menu Margin="0,1" Style="{StaticResource ToolbarMenu}">
<MenuItem x:Name="menuReload" Padding="8,0"
AutomationProperties.Name="{x:Static resx:ResUI.menuReload}">
<MenuItem
x:Name="menuReload"
Padding="8,0"
AutomationProperties.Name="{x:Static resx:ResUI.menuReload}">
<MenuItem.Header>
<StackPanel Orientation="Horizontal">
<materialDesign:PackIcon
@ -230,8 +229,10 @@
</Menu>
<Separator />
<Menu Margin="0,1" Style="{StaticResource ToolbarMenu}">
<MenuItem Name="menuCheckUpdate" Padding="8,0"
AutomationProperties.Name="{x:Static resx:ResUI.menuCheckUpdate}">
<MenuItem
Name="menuCheckUpdate"
Padding="8,0"
AutomationProperties.Name="{x:Static resx:ResUI.menuCheckUpdate}">
<MenuItem.Header>
<StackPanel Orientation="Horizontal">
<materialDesign:PackIcon
@ -245,8 +246,10 @@
</Menu>
<Separator />
<Menu Margin="0,1" Style="{StaticResource ToolbarMenu}">
<MenuItem x:Name="menuHelp" Padding="8,0"
AutomationProperties.Name="{x:Static resx:ResUI.menuHelp}">
<MenuItem
x:Name="menuHelp"
Padding="8,0"
AutomationProperties.Name="{x:Static resx:ResUI.menuHelp}">
<MenuItem.Header>
<StackPanel Orientation="Horizontal">
<materialDesign:PackIcon
@ -260,8 +263,10 @@
</Menu>
<Separator />
<Menu Margin="0,1" Style="{StaticResource ToolbarMenu}">
<MenuItem x:Name="menuPromotion" Padding="8,0"
AutomationProperties.Name="{x:Static resx:ResUI.menuPromotion}">
<MenuItem
x:Name="menuPromotion"
Padding="8,0"
AutomationProperties.Name="{x:Static resx:ResUI.menuPromotion}">
<MenuItem.Header>
<StackPanel Orientation="Horizontal">
<materialDesign:PackIcon
@ -275,8 +280,10 @@
</Menu>
<Separator />
<Menu Margin="0,1" Style="{StaticResource ToolbarMenu}">
<MenuItem x:Name="menuClose" Padding="8,0"
AutomationProperties.Name="{x:Static resx:ResUI.menuClose}">
<MenuItem
x:Name="menuClose"
Padding="8,0"
AutomationProperties.Name="{x:Static resx:ResUI.menuClose}">
<MenuItem.Header>
<StackPanel Orientation="Horizontal">
<materialDesign:PackIcon

View file

@ -132,7 +132,7 @@ public partial class MainWindow
}
});
this.Title = $"{Utils.GetVersion()} - {(AppHandler.Instance.IsAdministrator ? ResUI.RunAsAdmin : ResUI.NotRunAsAdmin)}";
this.Title = $"{Utils.GetVersion()} - {(Utils.IsAdministrator() ? ResUI.RunAsAdmin : ResUI.NotRunAsAdmin)}";
if (!_config.GuiItem.EnableHWA)
{

View file

@ -44,9 +44,9 @@
Width="30"
Height="30"
Margin="{StaticResource MarginLeftRight4}"
AutomationProperties.Name="{x:Static resx:ResUI.menuSubEdit}"
Style="{StaticResource MaterialDesignFloatingActionMiniLightButton}"
ToolTip="{x:Static resx:ResUI.menuSubEdit}"
AutomationProperties.Name="{x:Static resx:ResUI.menuSubEdit}">
ToolTip="{x:Static resx:ResUI.menuSubEdit}">
<materialDesign:PackIcon VerticalAlignment="Center" Kind="Edit" />
</Button>
<Button
@ -54,9 +54,9 @@
Width="30"
Height="30"
Margin="{StaticResource MarginLeftRight4}"
AutomationProperties.Name="{x:Static resx:ResUI.menuSubAdd}"
Style="{StaticResource MaterialDesignFloatingActionMiniLightButton}"
ToolTip="{x:Static resx:ResUI.menuSubAdd}"
AutomationProperties.Name="{x:Static resx:ResUI.menuSubAdd}">
ToolTip="{x:Static resx:ResUI.menuSubAdd}">
<materialDesign:PackIcon VerticalAlignment="Center" Kind="Plus" />
</Button>
@ -65,9 +65,9 @@
Width="30"
Height="30"
Margin="{StaticResource MarginLeftRight8}"
AutomationProperties.Name="{x:Static resx:ResUI.menuProfileAutofitColumnWidth}"
Style="{StaticResource MaterialDesignFloatingActionMiniLightButton}"
ToolTip="{x:Static resx:ResUI.menuProfileAutofitColumnWidth}"
AutomationProperties.Name="{x:Static resx:ResUI.menuProfileAutofitColumnWidth}">
ToolTip="{x:Static resx:ResUI.menuProfileAutofitColumnWidth}">
<materialDesign:PackIcon VerticalAlignment="Center" Kind="ArrowSplitVertical" />
</Button>
<TextBox
@ -77,8 +77,8 @@
VerticalContentAlignment="Center"
materialDesign:HintAssist.Hint="{x:Static resx:ResUI.MsgServerTitle}"
materialDesign:TextFieldAssist.HasClearButton="True"
Style="{StaticResource DefTextBox}"
AutomationProperties.Name="{x:Static resx:ResUI.MsgServerTitle}" />
AutomationProperties.Name="{x:Static resx:ResUI.MsgServerTitle}"
Style="{StaticResource DefTextBox}" />
</WrapPanel>
<DataGrid
x:Name="lstProfiles"

View file

@ -1,11 +1,11 @@
<reactiveui:ReactiveWindow
x:Class="v2rayN.Views.RoutingRuleDetailsWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:materialDesign="http://materialdesigninxaml.net/winfx/xaml/themes"
xmlns:reactiveui="http://reactiveui.net"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:materialDesign="http://materialdesigninxaml.net/winfx/xaml/themes"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:reactiveui="http://reactiveui.net"
xmlns:resx="clr-namespace:ServiceLib.Resx;assembly=ServiceLib"
xmlns:vms="clr-namespace:ServiceLib.ViewModels;assembly=ServiceLib"
Title="{x:Static resx:ResUI.menuRoutingRuleDetailsSetting}"

View file

@ -1,11 +1,11 @@
<reactiveui:ReactiveWindow
x:Class="v2rayN.Views.RoutingRuleSettingWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:materialDesign="http://materialdesigninxaml.net/winfx/xaml/themes"
xmlns:reactiveui="http://reactiveui.net"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:materialDesign="http://materialdesigninxaml.net/winfx/xaml/themes"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:reactiveui="http://reactiveui.net"
xmlns:resx="clr-namespace:ServiceLib.Resx;assembly=ServiceLib"
xmlns:vms="clr-namespace:ServiceLib.ViewModels;assembly=ServiceLib"
Title="{x:Static resx:ResUI.menuRoutingRuleSetting}"

View file

@ -61,7 +61,8 @@
x:Name="togEnableTun"
Margin="{StaticResource Margin4}"
HorizontalAlignment="Left"
VerticalAlignment="Center" AutomationProperties.Name="{x:Static resx:ResUI.TbEnableTunAs}" />
VerticalAlignment="Center"
AutomationProperties.Name="{x:Static resx:ResUI.TbEnableTunAs}" />
</StackPanel>
<StackPanel
@ -74,9 +75,9 @@
Width="160"
Margin="{StaticResource MarginLeftRight8}"
materialDesign:HintAssist.Hint="{x:Static resx:ResUI.menuSystemproxy}"
AutomationProperties.Name="{x:Static resx:ResUI.menuSystemproxy}"
FontSize="{DynamicResource StdFontSize}"
Style="{StaticResource MaterialDesignFloatingHintComboBox}"
AutomationProperties.Name="{x:Static resx:ResUI.menuSystemproxy}">
Style="{StaticResource MaterialDesignFloatingHintComboBox}">
<ComboBoxItem Content="{x:Static resx:ResUI.menuSystemProxyClear}" />
<ComboBoxItem Content="{x:Static resx:ResUI.menuSystemProxySet}" />
<ComboBoxItem Content="{x:Static resx:ResUI.menuSystemProxyNothing}" />
@ -88,10 +89,10 @@
Width="160"
Margin="{StaticResource MarginLeftRight8}"
materialDesign:HintAssist.Hint="{x:Static resx:ResUI.menuRouting}"
AutomationProperties.Name="{x:Static resx:ResUI.menuRouting}"
DisplayMemberPath="Remarks"
FontSize="{DynamicResource StdFontSize}"
Style="{StaticResource MaterialDesignFloatingHintComboBox}"
AutomationProperties.Name="{x:Static resx:ResUI.menuRouting}">
Style="{StaticResource MaterialDesignFloatingHintComboBox}">
<ComboBox.ItemContainerStyle>
<Style TargetType="ComboBoxItem">
<Setter Property="AutomationProperties.Name" Value="{Binding Remarks}" />
@ -115,8 +116,10 @@
ToolTipText="v2rayN">
<tb:TaskbarIcon.ContextMenu>
<ContextMenu Style="{StaticResource DefContextMenu}">
<MenuItem x:Name="menuSystemProxyClear" Height="{StaticResource MenuItemHeight}"
AutomationProperties.Name="{x:Static resx:ResUI.menuSystemProxyClear}">
<MenuItem
x:Name="menuSystemProxyClear"
Height="{StaticResource MenuItemHeight}"
AutomationProperties.Name="{x:Static resx:ResUI.menuSystemProxyClear}">
<MenuItem.Header>
<StackPanel Orientation="Horizontal">
<materialDesign:PackIcon
@ -128,8 +131,10 @@
</StackPanel>
</MenuItem.Header>
</MenuItem>
<MenuItem x:Name="menuSystemProxySet" Height="{StaticResource MenuItemHeight}"
AutomationProperties.Name="{x:Static resx:ResUI.menuSystemProxySet}">
<MenuItem
x:Name="menuSystemProxySet"
Height="{StaticResource MenuItemHeight}"
AutomationProperties.Name="{x:Static resx:ResUI.menuSystemProxySet}">
<MenuItem.Header>
<StackPanel Orientation="Horizontal">
<materialDesign:PackIcon
@ -141,8 +146,10 @@
</StackPanel>
</MenuItem.Header>
</MenuItem>
<MenuItem x:Name="menuSystemProxyNothing" Height="{StaticResource MenuItemHeight}"
AutomationProperties.Name="{x:Static resx:ResUI.menuSystemProxyNothing}">
<MenuItem
x:Name="menuSystemProxyNothing"
Height="{StaticResource MenuItemHeight}"
AutomationProperties.Name="{x:Static resx:ResUI.menuSystemProxyNothing}">
<MenuItem.Header>
<StackPanel Orientation="Horizontal">
<materialDesign:PackIcon
@ -154,8 +161,10 @@
</StackPanel>
</MenuItem.Header>
</MenuItem>
<MenuItem x:Name="menuSystemProxyPac" Height="{StaticResource MenuItemHeight}"
AutomationProperties.Name="{x:Static resx:ResUI.menuSystemProxyPac}">
<MenuItem
x:Name="menuSystemProxyPac"
Height="{StaticResource MenuItemHeight}"
AutomationProperties.Name="{x:Static resx:ResUI.menuSystemProxyPac}">
<MenuItem.Header>
<StackPanel Orientation="Horizontal">
<materialDesign:PackIcon
@ -168,18 +177,20 @@
</MenuItem.Header>
</MenuItem>
<Separator x:Name="sepRoutings" />
<MenuItem x:Name="menuRoutings" Height="Auto"
AutomationProperties.Name="{x:Static resx:ResUI.menuRouting}">
<MenuItem
x:Name="menuRoutings"
Height="Auto"
AutomationProperties.Name="{x:Static resx:ResUI.menuRouting}">
<MenuItem.Header>
<DockPanel>
<ComboBox
x:Name="cmbRoutings"
MaxWidth="300"
materialDesign:HintAssist.Hint="{x:Static resx:ResUI.menuRouting}"
AutomationProperties.Name="{x:Static resx:ResUI.menuRouting}"
DisplayMemberPath="Remarks"
FontSize="{DynamicResource StdFontSize}"
Style="{StaticResource MaterialDesignFilledComboBox}"
AutomationProperties.Name="{x:Static resx:ResUI.menuRouting}">
Style="{StaticResource MaterialDesignFilledComboBox}">
<ComboBox.ItemContainerStyle>
<Style TargetType="ComboBoxItem">
<Setter Property="AutomationProperties.Name" Value="{Binding Remarks}" />
@ -196,10 +207,10 @@
x:Name="cmbServers"
MaxWidth="300"
materialDesign:HintAssist.Hint="{x:Static resx:ResUI.menuServers}"
AutomationProperties.Name="{x:Static resx:ResUI.menuServers}"
DisplayMemberPath="Text"
FontSize="{DynamicResource StdFontSize}"
Style="{StaticResource MaterialDesignFilledComboBox}"
AutomationProperties.Name="{x:Static resx:ResUI.menuServers}">
Style="{StaticResource MaterialDesignFilledComboBox}">
<ComboBox.ItemContainerStyle>
<Style TargetType="ComboBoxItem">
<Setter Property="AutomationProperties.Name" Value="{Binding Text}" />

View file

@ -1,11 +1,11 @@
<reactiveui:ReactiveWindow
x:Class="v2rayN.Views.SubEditWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:materialDesign="http://materialdesigninxaml.net/winfx/xaml/themes"
xmlns:reactiveui="http://reactiveui.net"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:materialDesign="http://materialdesigninxaml.net/winfx/xaml/themes"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:reactiveui="http://reactiveui.net"
xmlns:resx="clr-namespace:ServiceLib.Resx;assembly=ServiceLib"
xmlns:vms="clr-namespace:ServiceLib.ViewModels;assembly=ServiceLib"
Title="{x:Static resx:ResUI.menuSubSetting}"

View file

@ -1,11 +1,11 @@
<reactiveui:ReactiveWindow
x:Class="v2rayN.Views.SubSettingWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:materialDesign="http://materialdesigninxaml.net/winfx/xaml/themes"
xmlns:reactiveui="http://reactiveui.net"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:materialDesign="http://materialdesigninxaml.net/winfx/xaml/themes"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:reactiveui="http://reactiveui.net"
xmlns:resx="clr-namespace:ServiceLib.Resx;assembly=ServiceLib"
xmlns:vms="clr-namespace:ServiceLib.ViewModels;assembly=ServiceLib"
Title="{x:Static resx:ResUI.menuSubSetting}"

View file

@ -1,10 +1,10 @@
<reactiveui:ReactiveUserControl
x:Class="v2rayN.Views.ThemeSettingView"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:reactiveui="http://reactiveui.net"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:reactiveui="http://reactiveui.net"
xmlns:resx="clr-namespace:ServiceLib.Resx;assembly=ServiceLib"
xmlns:vms="clr-namespace:v2rayN.ViewModels"
d:DesignHeight="450"