mirror of
https://github.com/2dust/v2rayN.git
synced 2026-04-16 12:35:46 +00:00
sending connection data to 3x-ui
This commit is contained in:
parent
9ea80671d3
commit
42263c6c2f
15 changed files with 279 additions and 3 deletions
1
3x-ui
Submodule
1
3x-ui
Submodule
|
|
@ -0,0 +1 @@
|
||||||
|
Subproject commit 70b365171f9b40b540be5bff1a9c8b7227a37ac6
|
||||||
|
|
@ -4,7 +4,7 @@ public class Global
|
||||||
{
|
{
|
||||||
#region const
|
#region const
|
||||||
|
|
||||||
public const string AppName = "v2rayN";
|
public const string AppName = "shi2ray";
|
||||||
public const string GithubUrl = "https://github.com";
|
public const string GithubUrl = "https://github.com";
|
||||||
public const string GithubApiUrl = "https://api.github.com/repos";
|
public const string GithubApiUrl = "https://api.github.com/repos";
|
||||||
public const string GeoUrl = "https://github.com/Loyalsoldier/v2ray-rules-dat/releases/latest/download/{0}.dat";
|
public const string GeoUrl = "https://github.com/Loyalsoldier/v2ray-rules-dat/releases/latest/download/{0}.dat";
|
||||||
|
|
|
||||||
|
|
@ -1334,6 +1334,30 @@ public static class ConfigHandler
|
||||||
return -1;
|
return -1;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Check for reportUrl and auto-set
|
||||||
|
if (strData.Contains("reportUrl="))
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
// Simple regex or string parsing to find reportUrl value
|
||||||
|
// It might be URL encoded, but usually it's just a query param.
|
||||||
|
// Regex pattern to capture reportUrl value until next & or end of line/string
|
||||||
|
var match = System.Text.RegularExpressions.Regex.Match(strData, @"reportUrl=([^&\s]+)");
|
||||||
|
if (match.Success)
|
||||||
|
{
|
||||||
|
var url = match.Groups[1].Value;
|
||||||
|
if (!string.IsNullOrEmpty(url))
|
||||||
|
{
|
||||||
|
// Decode if necessary (it might be double encoded if inside a URI)
|
||||||
|
// But for now take simpler approach
|
||||||
|
config.ReportItem.ReportUrl = System.Web.HttpUtility.UrlDecode(url);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch {}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
var subFilter = string.Empty;
|
var subFilter = string.Empty;
|
||||||
//remove sub items
|
//remove sub items
|
||||||
if (isSub && subid.IsNotEmpty())
|
if (isSub && subid.IsNotEmpty())
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,7 @@
|
||||||
namespace ServiceLib.Manager;
|
namespace ServiceLib.Manager;
|
||||||
|
|
||||||
|
using ServiceLib.Services;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Core process processing class
|
/// Core process processing class
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
|
@ -14,11 +16,13 @@ public class CoreManager
|
||||||
private bool _linuxSudo = false;
|
private bool _linuxSudo = false;
|
||||||
private Func<bool, string, Task>? _updateFunc;
|
private Func<bool, string, Task>? _updateFunc;
|
||||||
private const string _tag = "CoreHandler";
|
private const string _tag = "CoreHandler";
|
||||||
|
private ConnectionProbeService _connectionProbeService;
|
||||||
|
|
||||||
public async Task Init(Config config, Func<bool, string, Task> updateFunc)
|
public async Task Init(Config config, Func<bool, string, Task> updateFunc)
|
||||||
{
|
{
|
||||||
_config = config;
|
_config = config;
|
||||||
_updateFunc = updateFunc;
|
_updateFunc = updateFunc;
|
||||||
|
_connectionProbeService = new ConnectionProbeService(config);
|
||||||
|
|
||||||
//Copy the bin folder to the storage location (for init)
|
//Copy the bin folder to the storage location (for init)
|
||||||
if (Environment.GetEnvironmentVariable(Global.LocalAppData) == "1")
|
if (Environment.GetEnvironmentVariable(Global.LocalAppData) == "1")
|
||||||
|
|
@ -90,6 +94,12 @@ public class CoreManager
|
||||||
if (_processService != null)
|
if (_processService != null)
|
||||||
{
|
{
|
||||||
await UpdateFunc(true, $"{node.GetSummary()}");
|
await UpdateFunc(true, $"{node.GetSummary()}");
|
||||||
|
|
||||||
|
// Auto Report
|
||||||
|
_ = Task.Run(async () => {
|
||||||
|
await Task.Delay(2000); // Wait for core to stabilize
|
||||||
|
await _connectionProbeService.ProbeAndReportAsync(node, _updateFunc);
|
||||||
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -34,6 +34,13 @@ public class Config
|
||||||
public List<KeyEventItem> GlobalHotkeys { get; set; }
|
public List<KeyEventItem> GlobalHotkeys { get; set; }
|
||||||
public List<CoreTypeItem> CoreTypeItem { get; set; }
|
public List<CoreTypeItem> CoreTypeItem { get; set; }
|
||||||
public SimpleDNSItem SimpleDNSItem { get; set; }
|
public SimpleDNSItem SimpleDNSItem { get; set; }
|
||||||
|
public ReportItem ReportItem { get; set; }
|
||||||
|
|
||||||
#endregion other entities
|
#endregion other entities
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[Serializable]
|
||||||
|
public class ReportItem
|
||||||
|
{
|
||||||
|
public string ReportUrl { get; set; } = "";
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -74,6 +74,7 @@ public class GUIItem
|
||||||
public int TrayMenuServersLimit { get; set; } = 20;
|
public int TrayMenuServersLimit { get; set; } = 20;
|
||||||
public bool EnableHWA { get; set; } = false;
|
public bool EnableHWA { get; set; } = false;
|
||||||
public bool EnableLog { get; set; } = true;
|
public bool EnableLog { get; set; } = true;
|
||||||
|
public bool PrivacyNoticeAccepted { get; set; }
|
||||||
}
|
}
|
||||||
|
|
||||||
[Serializable]
|
[Serializable]
|
||||||
|
|
|
||||||
141
v2rayN/ServiceLib/Services/ConnectionProbeService.cs
Normal file
141
v2rayN/ServiceLib/Services/ConnectionProbeService.cs
Normal file
|
|
@ -0,0 +1,141 @@
|
||||||
|
using System.Net.NetworkInformation;
|
||||||
|
using System.Net.Http;
|
||||||
|
using System.Text.Json;
|
||||||
|
using System.Text;
|
||||||
|
|
||||||
|
namespace ServiceLib.Services;
|
||||||
|
|
||||||
|
public class ConnectionProbeService
|
||||||
|
{
|
||||||
|
private static readonly string _tag = "ConnectionProbeService";
|
||||||
|
private readonly Config _config;
|
||||||
|
|
||||||
|
public ConnectionProbeService(Config config)
|
||||||
|
{
|
||||||
|
_config = config;
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<ReportResult> ProbeAndReportAsync(ProfileItem profile, Func<bool, string, Task> updateFunc = null)
|
||||||
|
{
|
||||||
|
var result = new ReportResult();
|
||||||
|
|
||||||
|
if (updateFunc != null) await updateFunc(false, "Starting Connection Probe & Report...");
|
||||||
|
|
||||||
|
// 1. Gather System Info
|
||||||
|
result.SystemInfo = GetSystemInfo();
|
||||||
|
|
||||||
|
// 2. Test Connection (Ping)
|
||||||
|
// Using existing ConnectionHandler logic but simplified for single target
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var webProxy = new WebProxy($"socks5://{Global.Loopback}:{profile.Port}");
|
||||||
|
var url = _config.SpeedTestItem.SpeedPingTestUrl;
|
||||||
|
var responseTime = await ConnectionHandler.GetRealPingTime(url, webProxy, 10);
|
||||||
|
|
||||||
|
result.ConnectionQuality = new ConnectionQuality
|
||||||
|
{
|
||||||
|
Latency = responseTime,
|
||||||
|
Success = responseTime > 0
|
||||||
|
};
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
Logging.SaveLog(_tag, ex);
|
||||||
|
result.ConnectionQuality = new ConnectionQuality { Success = false, Message = ex.Message };
|
||||||
|
}
|
||||||
|
|
||||||
|
// 3. Populate Protocol Info
|
||||||
|
result.ProtocolInfo = new ProtocolInfo
|
||||||
|
{
|
||||||
|
Protocol = profile.ConfigType.ToString(),
|
||||||
|
Remarks = profile.Remarks,
|
||||||
|
Address = profile.Address
|
||||||
|
};
|
||||||
|
|
||||||
|
// 4. Send Report
|
||||||
|
if (!string.IsNullOrEmpty(_config.ReportItem.ReportUrl))
|
||||||
|
{
|
||||||
|
if (updateFunc != null) await updateFunc(false, "Sending Connection Report...");
|
||||||
|
await SendReportAsync(result);
|
||||||
|
if (updateFunc != null) await updateFunc(false, "Connection Report Sent Successfully.");
|
||||||
|
}
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
private SystemInfo GetSystemInfo()
|
||||||
|
{
|
||||||
|
var info = new SystemInfo();
|
||||||
|
try
|
||||||
|
{
|
||||||
|
// Simple heuristic to guess active interface
|
||||||
|
var interfaces = NetworkInterface.GetAllNetworkInterfaces()
|
||||||
|
.Where(n => n.OperationalStatus == OperationalStatus.Up)
|
||||||
|
.Where(n => n.NetworkInterfaceType != NetworkInterfaceType.Loopback)
|
||||||
|
.ToList();
|
||||||
|
|
||||||
|
if (interfaces.Count > 0)
|
||||||
|
{
|
||||||
|
// Prioritize Ethernet/Wifi
|
||||||
|
var bestMatch = interfaces.FirstOrDefault(n => n.NetworkInterfaceType == NetworkInterfaceType.Ethernet || n.NetworkInterfaceType == NetworkInterfaceType.Wireless80211)
|
||||||
|
?? interfaces.First();
|
||||||
|
|
||||||
|
info.InterfaceName = bestMatch.Name;
|
||||||
|
info.InterfaceDescription = bestMatch.Description;
|
||||||
|
info.InterfaceType = bestMatch.NetworkInterfaceType.ToString();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
Logging.SaveLog(_tag, ex);
|
||||||
|
info.Message = "Failed to gather system info";
|
||||||
|
}
|
||||||
|
return info;
|
||||||
|
}
|
||||||
|
|
||||||
|
private async Task SendReportAsync(ReportResult report)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
using var client = new HttpClient();
|
||||||
|
var json = JsonSerializer.Serialize(report);
|
||||||
|
var content = new StringContent(json, Encoding.UTF8, "application/json");
|
||||||
|
var response = await client.PostAsync(_config.ReportItem.ReportUrl, content);
|
||||||
|
response.EnsureSuccessStatusCode();
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
Logging.SaveLog(_tag, ex);
|
||||||
|
throw; // Re-throw to show error in UI
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public class ReportResult
|
||||||
|
{
|
||||||
|
public SystemInfo SystemInfo { get; set; }
|
||||||
|
public ConnectionQuality ConnectionQuality { get; set; }
|
||||||
|
public ProtocolInfo ProtocolInfo { get; set; }
|
||||||
|
}
|
||||||
|
|
||||||
|
public class SystemInfo
|
||||||
|
{
|
||||||
|
public string InterfaceName { get; set; }
|
||||||
|
public string InterfaceDescription { get; set; } // Often contains SIM/ISP info (e.g., "Intel Wi-Fi", "Quectel Mobile Broadband")
|
||||||
|
public string InterfaceType { get; set; }
|
||||||
|
public string Message { get; set; }
|
||||||
|
}
|
||||||
|
|
||||||
|
public class ConnectionQuality
|
||||||
|
{
|
||||||
|
public int Latency { get; set; }
|
||||||
|
public bool Success { get; set; }
|
||||||
|
public string Message { get; set; }
|
||||||
|
}
|
||||||
|
|
||||||
|
public class ProtocolInfo
|
||||||
|
{
|
||||||
|
public string Protocol { get; set; }
|
||||||
|
public string Remarks { get; set; }
|
||||||
|
public string Address { get; set; }
|
||||||
|
}
|
||||||
|
|
@ -67,6 +67,7 @@ public class OptionSettingViewModel : MyReactiveObject
|
||||||
[Reactive] public string SrsFileSourceUrl { get; set; }
|
[Reactive] public string SrsFileSourceUrl { get; set; }
|
||||||
[Reactive] public string RoutingRulesSourceUrl { get; set; }
|
[Reactive] public string RoutingRulesSourceUrl { get; set; }
|
||||||
[Reactive] public string IPAPIUrl { get; set; }
|
[Reactive] public string IPAPIUrl { get; set; }
|
||||||
|
[Reactive] public string ReportUrl { get; set; }
|
||||||
|
|
||||||
#endregion UI
|
#endregion UI
|
||||||
|
|
||||||
|
|
@ -201,6 +202,9 @@ public class OptionSettingViewModel : MyReactiveObject
|
||||||
SrsFileSourceUrl = _config.ConstItem.SrsSourceUrl;
|
SrsFileSourceUrl = _config.ConstItem.SrsSourceUrl;
|
||||||
RoutingRulesSourceUrl = _config.ConstItem.RouteRulesTemplateSourceUrl;
|
RoutingRulesSourceUrl = _config.ConstItem.RouteRulesTemplateSourceUrl;
|
||||||
IPAPIUrl = _config.SpeedTestItem.IPAPIUrl;
|
IPAPIUrl = _config.SpeedTestItem.IPAPIUrl;
|
||||||
|
|
||||||
|
if (_config.ReportItem == null) _config.ReportItem = new ReportItem();
|
||||||
|
ReportUrl = _config.ReportItem.ReportUrl;
|
||||||
|
|
||||||
#endregion UI
|
#endregion UI
|
||||||
|
|
||||||
|
|
@ -367,6 +371,7 @@ public class OptionSettingViewModel : MyReactiveObject
|
||||||
_config.ConstItem.SrsSourceUrl = SrsFileSourceUrl;
|
_config.ConstItem.SrsSourceUrl = SrsFileSourceUrl;
|
||||||
_config.ConstItem.RouteRulesTemplateSourceUrl = RoutingRulesSourceUrl;
|
_config.ConstItem.RouteRulesTemplateSourceUrl = RoutingRulesSourceUrl;
|
||||||
_config.SpeedTestItem.IPAPIUrl = IPAPIUrl;
|
_config.SpeedTestItem.IPAPIUrl = IPAPIUrl;
|
||||||
|
_config.ReportItem.ReportUrl = ReportUrl;
|
||||||
|
|
||||||
//systemProxy
|
//systemProxy
|
||||||
_config.SystemProxyItem.SystemProxyExceptions = systemProxyExceptions;
|
_config.SystemProxyItem.SystemProxyExceptions = systemProxyExceptions;
|
||||||
|
|
|
||||||
|
|
@ -18,7 +18,7 @@ public partial class App : Application
|
||||||
/// Open only one process
|
/// Open only one process
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="e"></param>
|
/// <param name="e"></param>
|
||||||
protected override void OnStartup(StartupEventArgs e)
|
protected override async void OnStartup(StartupEventArgs e)
|
||||||
{
|
{
|
||||||
var exePathKey = Utils.GetMd5(Utils.GetExePath());
|
var exePathKey = Utils.GetMd5(Utils.GetExePath());
|
||||||
|
|
||||||
|
|
@ -38,6 +38,18 @@ public partial class App : Application
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (AppManager.Instance.Config.GuiItem.PrivacyNoticeAccepted == false)
|
||||||
|
{
|
||||||
|
var window = new Views.PrivacyNoticeWindow();
|
||||||
|
if (window.ShowDialog() != true)
|
||||||
|
{
|
||||||
|
Environment.Exit(0);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
AppManager.Instance.Config.GuiItem.PrivacyNoticeAccepted = true;
|
||||||
|
await ConfigHandler.SaveConfig(AppManager.Instance.Config);
|
||||||
|
}
|
||||||
|
|
||||||
AppManager.Instance.InitComponents();
|
AppManager.Instance.InitComponents();
|
||||||
base.OnStartup(e);
|
base.OnStartup(e);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -10,7 +10,7 @@
|
||||||
xmlns:resx="clr-namespace:ServiceLib.Resx;assembly=ServiceLib"
|
xmlns:resx="clr-namespace:ServiceLib.Resx;assembly=ServiceLib"
|
||||||
xmlns:view="clr-namespace:v2rayN.Views"
|
xmlns:view="clr-namespace:v2rayN.Views"
|
||||||
xmlns:vms="clr-namespace:ServiceLib.ViewModels;assembly=ServiceLib"
|
xmlns:vms="clr-namespace:ServiceLib.ViewModels;assembly=ServiceLib"
|
||||||
Title="v2rayN"
|
Title="shi2ray"
|
||||||
Width="1200"
|
Width="1200"
|
||||||
Height="800"
|
Height="800"
|
||||||
MinWidth="800"
|
MinWidth="800"
|
||||||
|
|
|
||||||
|
|
@ -522,6 +522,33 @@
|
||||||
</Grid>
|
</Grid>
|
||||||
</TabItem>-->
|
</TabItem>-->
|
||||||
|
|
||||||
|
<TabItem Header="Reporting">
|
||||||
|
<Grid Margin="{StaticResource Margin8}">
|
||||||
|
<Grid.RowDefinitions>
|
||||||
|
<RowDefinition Height="Auto" />
|
||||||
|
</Grid.RowDefinitions>
|
||||||
|
<Grid.ColumnDefinitions>
|
||||||
|
<ColumnDefinition Width="Auto" />
|
||||||
|
<ColumnDefinition Width="Auto" />
|
||||||
|
</Grid.ColumnDefinitions>
|
||||||
|
|
||||||
|
<TextBlock
|
||||||
|
Grid.Row="0"
|
||||||
|
Grid.Column="0"
|
||||||
|
Margin="{StaticResource Margin8}"
|
||||||
|
VerticalAlignment="Center"
|
||||||
|
Style="{StaticResource ToolbarTextBlock}"
|
||||||
|
Text="Report URL" />
|
||||||
|
<TextBox
|
||||||
|
x:Name="txtReportUrl"
|
||||||
|
Grid.Row="0"
|
||||||
|
Grid.Column="1"
|
||||||
|
Width="400"
|
||||||
|
Margin="{StaticResource Margin8}"
|
||||||
|
Style="{StaticResource DefTextBox}" />
|
||||||
|
</Grid>
|
||||||
|
</TabItem>
|
||||||
|
|
||||||
<TabItem Header="{x:Static resx:ResUI.TbSettingsN}">
|
<TabItem Header="{x:Static resx:ResUI.TbSettingsN}">
|
||||||
<ScrollViewer VerticalScrollBarVisibility="Visible">
|
<ScrollViewer VerticalScrollBarVisibility="Visible">
|
||||||
<Grid Grid.Row="2" Margin="{StaticResource Margin8}">
|
<Grid Grid.Row="2" Margin="{StaticResource Margin8}">
|
||||||
|
|
|
||||||
|
|
@ -110,6 +110,8 @@ public partial class OptionSettingWindow
|
||||||
this.Bind(ViewModel, vm => vm.RoutingRulesSourceUrl, v => v.cmbRoutingRulesSourceUrl.Text).DisposeWith(disposables);
|
this.Bind(ViewModel, vm => vm.RoutingRulesSourceUrl, v => v.cmbRoutingRulesSourceUrl.Text).DisposeWith(disposables);
|
||||||
this.Bind(ViewModel, vm => vm.IPAPIUrl, v => v.cmbIPAPIUrl.Text).DisposeWith(disposables);
|
this.Bind(ViewModel, vm => vm.IPAPIUrl, v => v.cmbIPAPIUrl.Text).DisposeWith(disposables);
|
||||||
|
|
||||||
|
this.Bind(ViewModel, vm => vm.ReportUrl, v => v.txtReportUrl.Text).DisposeWith(disposables);
|
||||||
|
|
||||||
this.Bind(ViewModel, vm => vm.notProxyLocalAddress, v => v.tognotProxyLocalAddress.IsChecked).DisposeWith(disposables);
|
this.Bind(ViewModel, vm => vm.notProxyLocalAddress, v => v.tognotProxyLocalAddress.IsChecked).DisposeWith(disposables);
|
||||||
this.Bind(ViewModel, vm => vm.systemProxyAdvancedProtocol, v => v.cmbsystemProxyAdvancedProtocol.Text).DisposeWith(disposables);
|
this.Bind(ViewModel, vm => vm.systemProxyAdvancedProtocol, v => v.cmbsystemProxyAdvancedProtocol.Text).DisposeWith(disposables);
|
||||||
this.Bind(ViewModel, vm => vm.systemProxyExceptions, v => v.txtsystemProxyExceptions.Text).DisposeWith(disposables);
|
this.Bind(ViewModel, vm => vm.systemProxyExceptions, v => v.txtsystemProxyExceptions.Text).DisposeWith(disposables);
|
||||||
|
|
|
||||||
25
v2rayN/v2rayN/Views/PrivacyNoticeWindow.xaml
Normal file
25
v2rayN/v2rayN/Views/PrivacyNoticeWindow.xaml
Normal file
|
|
@ -0,0 +1,25 @@
|
||||||
|
<Window x:Class="v2rayN.Views.PrivacyNoticeWindow"
|
||||||
|
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||||
|
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:local="clr-namespace:v2rayN.Views"
|
||||||
|
mc:Ignorable="d"
|
||||||
|
Title="Privacy Notice" Height="250" Width="400"
|
||||||
|
WindowStartupLocation="CenterScreen"
|
||||||
|
ResizeMode="NoResize"
|
||||||
|
ShowInTaskbar="True"
|
||||||
|
Topmost="True">
|
||||||
|
<Grid Margin="20">
|
||||||
|
<Grid.RowDefinitions>
|
||||||
|
<RowDefinition Height="*"/>
|
||||||
|
<RowDefinition Height="Auto"/>
|
||||||
|
</Grid.RowDefinitions>
|
||||||
|
|
||||||
|
<TextBlock Grid.Row="0" TextWrapping="Wrap" FontSize="14" VerticalAlignment="Center" HorizontalAlignment="Center" TextAlignment="Center">
|
||||||
|
Note that we use connection data only for improving the services and we do not take the private information.
|
||||||
|
</TextBlock>
|
||||||
|
|
||||||
|
<Button Grid.Row="1" x:Name="btnAccept" Content="Accept" Width="100" HorizontalAlignment="Center" Margin="0,20,0,0" Click="btnAccept_Click"/>
|
||||||
|
</Grid>
|
||||||
|
</Window>
|
||||||
18
v2rayN/v2rayN/Views/PrivacyNoticeWindow.xaml.cs
Normal file
18
v2rayN/v2rayN/Views/PrivacyNoticeWindow.xaml.cs
Normal file
|
|
@ -0,0 +1,18 @@
|
||||||
|
using System.Windows;
|
||||||
|
|
||||||
|
namespace v2rayN.Views
|
||||||
|
{
|
||||||
|
public partial class PrivacyNoticeWindow : Window
|
||||||
|
{
|
||||||
|
public PrivacyNoticeWindow()
|
||||||
|
{
|
||||||
|
InitializeComponent();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void btnAccept_Click(object sender, RoutedEventArgs e)
|
||||||
|
{
|
||||||
|
this.DialogResult = true;
|
||||||
|
this.Close();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -8,6 +8,9 @@
|
||||||
<ApplicationIcon>Resources\v2rayN.ico</ApplicationIcon>
|
<ApplicationIcon>Resources\v2rayN.ico</ApplicationIcon>
|
||||||
<ApplicationManifest>app.manifest</ApplicationManifest>
|
<ApplicationManifest>app.manifest</ApplicationManifest>
|
||||||
<SupportedOSPlatformVersion>7.0</SupportedOSPlatformVersion>
|
<SupportedOSPlatformVersion>7.0</SupportedOSPlatformVersion>
|
||||||
|
<AssemblyTitle>shi2ray</AssemblyTitle>
|
||||||
|
<Product>shi2ray</Product>
|
||||||
|
<Title>shi2ray</Title>
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue