Compare commits

...

3 commits

Author SHA1 Message Date
2dust
10358064dc up 7.15.5
Some checks are pending
release Linux / build (Release) (push) Waiting to run
release macOS / build (Release) (push) Waiting to run
release Windows desktop (Avalonia UI) / build (Release) (push) Waiting to run
release Windows / build (Release) (push) Waiting to run
2025-10-21 20:52:02 +08:00
2dust
6a19896915 Optimize the desktop version icon 2025-10-21 20:00:02 +08:00
DHR60
07e173eab1
Bootstrap DNS (#8160)
Also fix the handling of IPv6 domains
2025-10-21 17:28:48 +08:00
29 changed files with 346 additions and 162 deletions

View file

@ -1,7 +1,7 @@
<Project> <Project>
<PropertyGroup> <PropertyGroup>
<Version>7.15.4</Version> <Version>7.15.5</Version>
</PropertyGroup> </PropertyGroup>
<PropertyGroup> <PropertyGroup>

View file

@ -355,6 +355,110 @@ public class Utils
return userHostsMap; return userHostsMap;
} }
/// <summary>
/// Parse a possibly non-standard URL into scheme, domain, port, and path.
/// If parsing fails, the entire input is returned as domain, and others are empty or zero.
/// </summary>
/// <param name="url">Input URL or string</param>
/// <returns>(domain, scheme, port, path)</returns>
public static (string domain, string scheme, int port, string path) ParseUrl(string url)
{
if (string.IsNullOrWhiteSpace(url))
{
return ("", "", 0, "");
}
// 1. First, try to parse using the standard Uri class.
if (Uri.TryCreate(url, UriKind.Absolute, out var uri) && !string.IsNullOrEmpty(uri.Host))
{
var scheme = uri.Scheme;
var domain = uri.Host;
var port = uri.IsDefaultPort ? 0 : uri.Port;
var path = uri.PathAndQuery;
return (domain, scheme, port, path);
}
// 2. Try to handle more general cases with a regular expression, including non-standard schemes.
// This regex captures the scheme (optional), authority (host+port), and path (optional).
var match = Regex.Match(url, @"^(?:([a-zA-Z][a-zA-Z0-9+.-]*):/{2,})?([^/?#]+)([^?#]*)?.*$");
if (match.Success)
{
var scheme = match.Groups[1].Value;
var authority = match.Groups[2].Value;
var path = match.Groups[3].Value;
// Remove userinfo from the authority part.
var atIndex = authority.LastIndexOf('@');
if (atIndex > 0)
{
authority = authority.Substring(atIndex + 1);
}
var (domain, port) = ParseAuthority(authority);
// If the parsed domain is empty, it means the authority part is malformed, so trigger the fallback.
if (!string.IsNullOrEmpty(domain))
{
return (domain, scheme, port, path);
}
}
// 3. If all of the above fails, execute the final fallback strategy.
return (url, "", 0, "");
}
/// <summary>
/// Helper function to parse domain and port from the authority part, with correct handling for IPv6.
/// </summary>
private static (string domain, int port) ParseAuthority(string authority)
{
if (string.IsNullOrEmpty(authority))
{
return ("", 0);
}
var port = 0;
var domain = authority;
// Handle IPv6 addresses, e.g., "[2001:db8::1]:443"
if (authority.StartsWith("[") && authority.Contains("]"))
{
int closingBracketIndex = authority.LastIndexOf(']');
if (closingBracketIndex < authority.Length - 1 && authority[closingBracketIndex + 1] == ':')
{
// Port exists
var portStr = authority.Substring(closingBracketIndex + 2);
if (int.TryParse(portStr, out var portNum))
{
port = portNum;
}
domain = authority.Substring(0, closingBracketIndex + 1);
}
else
{
// No port
domain = authority;
}
}
else // Handle IPv4 or domain names
{
var lastColonIndex = authority.LastIndexOf(':');
// Ensure there are digits after the colon and that this colon is not part of an IPv6 address.
if (lastColonIndex > 0 && lastColonIndex < authority.Length - 1 && authority.Substring(lastColonIndex + 1).All(char.IsDigit))
{
var portStr = authority.Substring(lastColonIndex + 1);
if (int.TryParse(portStr, out var portNum))
{
port = portNum;
domain = authority.Substring(0, lastColonIndex);
}
}
}
return (domain, port);
}
#endregion #endregion
#region #region

View file

@ -112,10 +112,8 @@ public static class ConfigHandler
config.ConstItem ??= new ConstItem(); config.ConstItem ??= new ConstItem();
config.SimpleDNSItem ??= InitBuiltinSimpleDNS(); config.SimpleDNSItem ??= InitBuiltinSimpleDNS();
if (config.SimpleDNSItem.GlobalFakeIp is null) config.SimpleDNSItem.GlobalFakeIp ??= true;
{ config.SimpleDNSItem.BootstrapDNS ??= Global.DomainPureIPDNSAddress.FirstOrDefault();
config.SimpleDNSItem.GlobalFakeIp = true;
}
config.SpeedTestItem ??= new(); config.SpeedTestItem ??= new();
if (config.SpeedTestItem.SpeedTestTimeout < 10) if (config.SpeedTestItem.SpeedTestTimeout < 10)
@ -2273,6 +2271,7 @@ public static class ConfigHandler
BlockBindingQuery = true, BlockBindingQuery = true,
DirectDNS = Global.DomainDirectDNSAddress.FirstOrDefault(), DirectDNS = Global.DomainDirectDNSAddress.FirstOrDefault(),
RemoteDNS = Global.DomainRemoteDNSAddress.FirstOrDefault(), RemoteDNS = Global.DomainRemoteDNSAddress.FirstOrDefault(),
BootstrapDNS = Global.DomainPureIPDNSAddress.FirstOrDefault(),
}; };
} }

View file

@ -263,6 +263,7 @@ public class SimpleDNSItem
public bool? BlockBindingQuery { get; set; } public bool? BlockBindingQuery { get; set; }
public string? DirectDNS { get; set; } public string? DirectDNS { get; set; }
public string? RemoteDNS { get; set; } public string? RemoteDNS { get; set; }
public string? BootstrapDNS { get; set; }
public string? RayStrategy4Freedom { get; set; } public string? RayStrategy4Freedom { get; set; }
public string? SingboxStrategy4Direct { get; set; } public string? SingboxStrategy4Direct { get; set; }
public string? SingboxStrategy4Proxy { get; set; } public string? SingboxStrategy4Proxy { get; set; }

View file

@ -2517,6 +2517,24 @@ namespace ServiceLib.Resx {
} }
} }
/// <summary>
/// 查找类似 Bootstrap DNS 的本地化字符串。
/// </summary>
public static string TbBootstrapDNS {
get {
return ResourceManager.GetString("TbBootstrapDNS", resourceCulture);
}
}
/// <summary>
/// 查找类似 Resolve DNS server domains, requires IP 的本地化字符串。
/// </summary>
public static string TbBootstrapDNSTips {
get {
return ResourceManager.GetString("TbBootstrapDNSTips", resourceCulture);
}
}
/// <summary> /// <summary>
/// 查找类似 Browse 的本地化字符串。 /// 查找类似 Browse 的本地化字符串。
/// </summary> /// </summary>

View file

@ -1590,4 +1590,10 @@
<data name="TbRuleTypeTips" xml:space="preserve"> <data name="TbRuleTypeTips" xml:space="preserve">
<value>You can set separate rules for Routing and DNS, or select "ALL" to apply to both</value> <value>You can set separate rules for Routing and DNS, or select "ALL" to apply to both</value>
</data> </data>
<data name="TbBootstrapDNS" xml:space="preserve">
<value>Bootstrap DNS</value>
</data>
<data name="TbBootstrapDNSTips" xml:space="preserve">
<value>Resolve DNS server domains, requires IP</value>
</data>
</root> </root>

View file

@ -1590,4 +1590,10 @@
<data name="TbRuleType" xml:space="preserve"> <data name="TbRuleType" xml:space="preserve">
<value>Rule Type</value> <value>Rule Type</value>
</data> </data>
<data name="TbBootstrapDNS" xml:space="preserve">
<value>Bootstrap DNS</value>
</data>
<data name="TbBootstrapDNSTips" xml:space="preserve">
<value>Resolve DNS server domains, requires IP</value>
</data>
</root> </root>

View file

@ -1590,4 +1590,10 @@
<data name="TbRuleType" xml:space="preserve"> <data name="TbRuleType" xml:space="preserve">
<value>Rule Type</value> <value>Rule Type</value>
</data> </data>
<data name="TbBootstrapDNS" xml:space="preserve">
<value>Bootstrap DNS</value>
</data>
<data name="TbBootstrapDNSTips" xml:space="preserve">
<value>Resolve DNS server domains, requires IP</value>
</data>
</root> </root>

View file

@ -1590,4 +1590,10 @@
<data name="TbRuleType" xml:space="preserve"> <data name="TbRuleType" xml:space="preserve">
<value>Rule Type</value> <value>Rule Type</value>
</data> </data>
<data name="TbBootstrapDNS" xml:space="preserve">
<value>Bootstrap DNS</value>
</data>
<data name="TbBootstrapDNSTips" xml:space="preserve">
<value>Resolve DNS server domains, requires IP</value>
</data>
</root> </root>

View file

@ -1587,4 +1587,10 @@
<data name="TbRuleTypeTips" xml:space="preserve"> <data name="TbRuleTypeTips" xml:space="preserve">
<value>可对 Routing 和 DNS 单独设定规则ALL 则都生效</value> <value>可对 Routing 和 DNS 单独设定规则ALL 则都生效</value>
</data> </data>
<data name="TbBootstrapDNS" xml:space="preserve">
<value>Bootstrap DNS</value>
</data>
<data name="TbBootstrapDNSTips" xml:space="preserve">
<value>解析 DNS 服务器域名,需指定为 IP</value>
</data>
</root> </root>

View file

@ -1587,4 +1587,10 @@
<data name="TbRuleTypeTips" xml:space="preserve"> <data name="TbRuleTypeTips" xml:space="preserve">
<value>可对 Routing 和 DNS 单独设定规则ALL 则都生效</value> <value>可对 Routing 和 DNS 单独设定规则ALL 则都生效</value>
</data> </data>
<data name="TbBootstrapDNS" xml:space="preserve">
<value>Bootstrap DNS</value>
</data>
<data name="TbBootstrapDNSTips" xml:space="preserve">
<value>Resolve DNS server domains, requires IP</value>
</data>
</root> </root>

View file

@ -138,12 +138,7 @@ public partial class CoreConfigSingboxService
private async Task<Server4Sbox> GenDnsDomains(SingboxConfig singboxConfig, SimpleDNSItem? simpleDNSItem) private async Task<Server4Sbox> GenDnsDomains(SingboxConfig singboxConfig, SimpleDNSItem? simpleDNSItem)
{ {
var finalDnsAddress = "local"; var finalDns = ParseDnsAddress(simpleDNSItem.BootstrapDNS);
if (_config.TunModeItem.EnableTun)
{
finalDnsAddress = "dhcp://auto";
}
var finalDns = ParseDnsAddress(finalDnsAddress);
finalDns.tag = Global.SingboxLocalDNSTag; finalDns.tag = Global.SingboxLocalDNSTag;
singboxConfig.dns ??= new Dns4Sbox(); singboxConfig.dns ??= new Dns4Sbox();
singboxConfig.dns.servers ??= new List<Server4Sbox>(); singboxConfig.dns.servers ??= new List<Server4Sbox>();
@ -459,15 +454,19 @@ public partial class CoreConfigSingboxService
return server; return server;
} }
if (addressFirst.StartsWith("dhcp://", StringComparison.OrdinalIgnoreCase)) var (domain, scheme, port, path) = Utils.ParseUrl(addressFirst);
if (scheme.Equals("dhcp", StringComparison.OrdinalIgnoreCase))
{ {
var interface_name = addressFirst.Substring(7);
server.type = "dhcp"; server.type = "dhcp";
server.Interface = interface_name == "auto" ? null : interface_name; if ((!domain.IsNullOrEmpty()) && domain != "auto")
{
server.server = domain;
}
return server; return server;
} }
if (!addressFirst.Contains("://")) if (scheme.IsNullOrEmpty())
{ {
// udp dns // udp dns
server.type = "udp"; server.type = "udp";
@ -475,63 +474,19 @@ public partial class CoreConfigSingboxService
return server; return server;
} }
try //server.type = scheme.ToLower();
// remove "+local" suffix
// TODO: "+local" suffix decide server.detour = "direct" ?
server.type = scheme.Replace("+local", "", StringComparison.OrdinalIgnoreCase).ToLower();
server.server = domain;
if (port != 0)
{ {
var protocolEndIndex = addressFirst.IndexOf("://", StringComparison.Ordinal); server.server_port = port;
server.type = addressFirst.Substring(0, protocolEndIndex).ToLower();
var uri = new Uri(addressFirst);
server.server = uri.Host;
if (!uri.IsDefaultPort)
{
server.server_port = uri.Port;
}
if ((server.type == "https" || server.type == "h3") && !string.IsNullOrEmpty(uri.AbsolutePath) && uri.AbsolutePath != "/")
{
server.path = uri.AbsolutePath;
}
} }
catch (UriFormatException) if ((server.type == "https" || server.type == "h3") && !string.IsNullOrEmpty(path) && path != "/")
{ {
var protocolEndIndex = addressFirst.IndexOf("://", StringComparison.Ordinal); server.path = path;
if (protocolEndIndex > 0)
{
server.type = addressFirst.Substring(0, protocolEndIndex).ToLower();
var remaining = addressFirst.Substring(protocolEndIndex + 3);
var portIndex = remaining.IndexOf(':');
var pathIndex = remaining.IndexOf('/');
if (portIndex > 0)
{
server.server = remaining.Substring(0, portIndex);
var portPart = pathIndex > portIndex
? remaining.Substring(portIndex + 1, pathIndex - portIndex - 1)
: remaining.Substring(portIndex + 1);
if (int.TryParse(portPart, out var parsedPort))
{
server.server_port = parsedPort;
}
}
else if (pathIndex > 0)
{
server.server = remaining.Substring(0, pathIndex);
}
else
{
server.server = remaining;
}
if (pathIndex > 0 && (server.type == "https" || server.type == "h3"))
{
server.path = remaining.Substring(pathIndex);
}
}
} }
return server; return server;
} }
} }

View file

@ -103,6 +103,35 @@ public partial class CoreConfigV2rayService
var expectedIPs = new List<string>(); var expectedIPs = new List<string>();
var regionNames = new HashSet<string>(); var regionNames = new HashSet<string>();
var bootstrapDNSAddress = ParseDnsAddresses(simpleDNSItem?.BootstrapDNS, Global.DomainPureIPDNSAddress.FirstOrDefault());
var dnsServerDomains = new List<string>();
foreach (var dns in directDNSAddress)
{
var (domain, _, _, _) = Utils.ParseUrl(dns);
if (domain == "localhost")
{
continue;
}
if (Utils.IsDomain(domain))
{
dnsServerDomains.Add($"full:{domain}");
}
}
foreach (var dns in remoteDNSAddress)
{
var (domain, _, _, _) = Utils.ParseUrl(dns);
if (domain == "localhost")
{
continue;
}
if (Utils.IsDomain(domain))
{
dnsServerDomains.Add($"full:{domain}");
}
}
dnsServerDomains = dnsServerDomains.Distinct().ToList();
if (!string.IsNullOrEmpty(simpleDNSItem?.DirectExpectedIPs)) if (!string.IsNullOrEmpty(simpleDNSItem?.DirectExpectedIPs))
{ {
expectedIPs = simpleDNSItem.DirectExpectedIPs expectedIPs = simpleDNSItem.DirectExpectedIPs
@ -217,6 +246,10 @@ public partial class CoreConfigV2rayService
AddDnsServers(remoteDNSAddress, proxyGeositeList); AddDnsServers(remoteDNSAddress, proxyGeositeList);
AddDnsServers(directDNSAddress, directGeositeList); AddDnsServers(directDNSAddress, directGeositeList);
AddDnsServers(directDNSAddress, expectedDomainList, expectedIPs); AddDnsServers(directDNSAddress, expectedDomainList, expectedIPs);
if (dnsServerDomains.Count > 0)
{
AddDnsServers(bootstrapDNSAddress, dnsServerDomains);
}
var useDirectDns = rules?.LastOrDefault() is { } lastRule var useDirectDns = rules?.LastOrDefault() is { } lastRule
&& lastRule.OutboundTag == Global.DirectTag && lastRule.OutboundTag == Global.DirectTag

View file

@ -8,6 +8,7 @@ public class DNSSettingViewModel : MyReactiveObject
[Reactive] public bool? BlockBindingQuery { get; set; } [Reactive] public bool? BlockBindingQuery { get; set; }
[Reactive] public string? DirectDNS { get; set; } [Reactive] public string? DirectDNS { get; set; }
[Reactive] public string? RemoteDNS { get; set; } [Reactive] public string? RemoteDNS { get; set; }
[Reactive] public string? BootstrapDNS { get; set; }
[Reactive] public string? RayStrategy4Freedom { get; set; } [Reactive] public string? RayStrategy4Freedom { get; set; }
[Reactive] public string? SingboxStrategy4Direct { get; set; } [Reactive] public string? SingboxStrategy4Direct { get; set; }
[Reactive] public string? SingboxStrategy4Proxy { get; set; } [Reactive] public string? SingboxStrategy4Proxy { get; set; }
@ -68,6 +69,7 @@ public class DNSSettingViewModel : MyReactiveObject
BlockBindingQuery = item.BlockBindingQuery; BlockBindingQuery = item.BlockBindingQuery;
DirectDNS = item.DirectDNS; DirectDNS = item.DirectDNS;
RemoteDNS = item.RemoteDNS; RemoteDNS = item.RemoteDNS;
BootstrapDNS = item.BootstrapDNS;
RayStrategy4Freedom = item.RayStrategy4Freedom; RayStrategy4Freedom = item.RayStrategy4Freedom;
SingboxStrategy4Direct = item.SingboxStrategy4Direct; SingboxStrategy4Direct = item.SingboxStrategy4Direct;
SingboxStrategy4Proxy = item.SingboxStrategy4Proxy; SingboxStrategy4Proxy = item.SingboxStrategy4Proxy;
@ -97,6 +99,7 @@ public class DNSSettingViewModel : MyReactiveObject
_config.SimpleDNSItem.BlockBindingQuery = BlockBindingQuery; _config.SimpleDNSItem.BlockBindingQuery = BlockBindingQuery;
_config.SimpleDNSItem.DirectDNS = DirectDNS; _config.SimpleDNSItem.DirectDNS = DirectDNS;
_config.SimpleDNSItem.RemoteDNS = RemoteDNS; _config.SimpleDNSItem.RemoteDNS = RemoteDNS;
_config.SimpleDNSItem.BootstrapDNS = BootstrapDNS;
_config.SimpleDNSItem.RayStrategy4Freedom = RayStrategy4Freedom; _config.SimpleDNSItem.RayStrategy4Freedom = RayStrategy4Freedom;
_config.SimpleDNSItem.SingboxStrategy4Direct = SingboxStrategy4Direct; _config.SimpleDNSItem.SingboxStrategy4Direct = SingboxStrategy4Direct;
_config.SimpleDNSItem.SingboxStrategy4Proxy = SingboxStrategy4Proxy; _config.SimpleDNSItem.SingboxStrategy4Proxy = SingboxStrategy4Proxy;

View file

@ -9,13 +9,6 @@
Name="v2rayN" Name="v2rayN"
x:DataType="vms:StatusBarViewModel" x:DataType="vms:StatusBarViewModel"
RequestedThemeVariant="Default"> RequestedThemeVariant="Default">
<Application.Styles>
<semi:SemiTheme />
<semi:AvaloniaEditSemiTheme />
<StyleInclude Source="Assets/GlobalStyles.axaml" />
<StyleInclude Source="avares://Semi.Avalonia.DataGrid/Index.axaml" />
<dialogHost:DialogHostStyles />
</Application.Styles>
<Application.Resources> <Application.Resources>
<ResourceDictionary> <ResourceDictionary>
<ResourceDictionary.MergedDictionaries> <ResourceDictionary.MergedDictionaries>
@ -23,6 +16,13 @@
</ResourceDictionary.MergedDictionaries> </ResourceDictionary.MergedDictionaries>
</ResourceDictionary> </ResourceDictionary>
</Application.Resources> </Application.Resources>
<Application.Styles>
<semi:SemiTheme />
<semi:AvaloniaEditSemiTheme />
<StyleInclude Source="Assets/GlobalStyles.axaml" />
<StyleInclude Source="avares://Semi.Avalonia.DataGrid/Index.axaml" />
<dialogHost:DialogHostStyles />
</Application.Styles>
<TrayIcon.Icons> <TrayIcon.Icons>
<TrayIcons> <TrayIcons>

View file

@ -13,6 +13,7 @@
<Style Selector="PathIcon"> <Style Selector="PathIcon">
<Setter Property="Width" Value="16" /> <Setter Property="Width" Value="16" />
<Setter Property="Height" Value="16" /> <Setter Property="Height" Value="16" />
<Setter Property="Foreground" Value="{DynamicResource ButtonDefaultTertiaryForeground}" />
</Style> </Style>
<Style Selector="TextBox"> <Style Selector="TextBox">
@ -26,4 +27,12 @@
<Style Selector="TabControl"> <Style Selector="TabControl">
<Setter Property="Theme" Value="{StaticResource LineTabControl}" /> <Setter Property="Theme" Value="{StaticResource LineTabControl}" />
</Style> </Style>
<Style Selector="Button.IconButton">
<Setter Property="Width" Value="{StaticResource IconButtonWidth}" />
<Setter Property="Height" Value="{StaticResource IconButtonHeight}" />
<Setter Property="MinWidth" Value="{StaticResource IconButtonWidth}" />
<Setter Property="Theme" Value="{DynamicResource BorderlessButton}" />
</Style>
</Styles> </Styles>

View file

@ -607,12 +607,14 @@
<Button <Button
x:Name="btnExtra" x:Name="btnExtra"
Width="{StaticResource IconButtonWidth}" Classes="IconButton"
Height="{StaticResource IconButtonHeight}" Margin="{StaticResource MarginLr8}">
Margin="{StaticResource MarginLr8}"
Theme="{DynamicResource BorderlessButton}">
<Button.Content> <Button.Content>
<PathIcon Data="{StaticResource building_more}" Foreground="{DynamicResource ButtonDefaultTertiaryForeground}" /> <PathIcon Data="{StaticResource SemiIconMore}" >
<PathIcon.RenderTransform>
<RotateTransform Angle="90" />
</PathIcon.RenderTransform>
</PathIcon>
</Button.Content> </Button.Content>
<Button.Flyout> <Button.Flyout>
<Flyout> <Flyout>

View file

@ -88,13 +88,14 @@
<TextBlock Margin="{StaticResource Margin4}" Text="{x:Static resx:ResUI.menuRemoteBackupAndRestore}" /> <TextBlock Margin="{StaticResource Margin4}" Text="{x:Static resx:ResUI.menuRemoteBackupAndRestore}" />
<Button <Button
Width="{StaticResource IconButtonWidth}" Classes="IconButton"
Height="{StaticResource IconButtonHeight}" Margin="{StaticResource MarginLr8}">
MinWidth="{StaticResource IconButtonWidth}"
Margin="{StaticResource MarginLr8}"
Theme="{DynamicResource BorderlessButton}">
<Button.Content> <Button.Content>
<PathIcon Data="{StaticResource building_more}" Foreground="{DynamicResource ButtonDefaultTertiaryForeground}" /> <PathIcon Data="{StaticResource SemiIconMore}" >
<PathIcon.RenderTransform>
<RotateTransform Angle="90" />
</PathIcon.RenderTransform>
</PathIcon>
</Button.Content> </Button.Content>
<Button.Flyout> <Button.Flyout>
<Flyout> <Flyout>

View file

@ -27,27 +27,21 @@
<Button <Button
x:Name="btnConnectionCloseAll" x:Name="btnConnectionCloseAll"
Width="{StaticResource IconButtonWidth}" Classes="IconButton Success"
Height="{StaticResource IconButtonHeight}"
Classes="Success"
Margin="{StaticResource MarginLr8}" Margin="{StaticResource MarginLr8}"
Theme="{DynamicResource BorderlessButton}"
ToolTip.Tip="{x:Static resx:ResUI.menuConnectionCloseAll}"> ToolTip.Tip="{x:Static resx:ResUI.menuConnectionCloseAll}">
<Button.Content> <Button.Content>
<PathIcon Data="{StaticResource building_delete}" Foreground="{DynamicResource ButtonDefaultTertiaryForeground}" /> <PathIcon Data="{StaticResource SemiIconClose}" />
</Button.Content> </Button.Content>
</Button> </Button>
<Button <Button
x:Name="btnAutofitColumnWidth" x:Name="btnAutofitColumnWidth"
Width="{StaticResource IconButtonWidth}" Classes="IconButton Success"
Height="{StaticResource IconButtonHeight}"
Classes="Success"
Margin="{StaticResource MarginLr8}" Margin="{StaticResource MarginLr8}"
Theme="{DynamicResource BorderlessButton}"
ToolTip.Tip="{x:Static resx:ResUI.menuProfileAutofitColumnWidth}"> ToolTip.Tip="{x:Static resx:ResUI.menuProfileAutofitColumnWidth}">
<Button.Content> <Button.Content>
<PathIcon Data="{StaticResource building_fit}" Foreground="{DynamicResource ButtonDefaultTertiaryForeground}" /> <PathIcon Data="{StaticResource SemiIconExpand}" />
</Button.Content> </Button.Content>
</Button> </Button>

View file

@ -50,27 +50,21 @@
<Button <Button
x:Name="menuProxiesReload" x:Name="menuProxiesReload"
Width="{StaticResource IconButtonWidth}"
Height="{StaticResource IconButtonHeight}"
Margin="{StaticResource MarginLr8}" Margin="{StaticResource MarginLr8}"
Classes="Success" Classes="IconButton Success"
Theme="{DynamicResource BorderlessButton}"
ToolTip.Tip="{x:Static resx:ResUI.menuProxiesReload}"> ToolTip.Tip="{x:Static resx:ResUI.menuProxiesReload}">
<Button.Content> <Button.Content>
<PathIcon Data="{StaticResource building_refresh}" Foreground="{DynamicResource ButtonDefaultTertiaryForeground}" /> <PathIcon Data="{StaticResource SemiIconRefresh}" />
</Button.Content> </Button.Content>
</Button> </Button>
<Button <Button
x:Name="menuProxiesDelaytest" x:Name="menuProxiesDelaytest"
Width="{StaticResource IconButtonWidth}"
Height="{StaticResource IconButtonHeight}"
Margin="{StaticResource MarginLr8}" Margin="{StaticResource MarginLr8}"
Classes="Success" Classes="IconButton Success"
Theme="{DynamicResource BorderlessButton}"
ToolTip.Tip="{x:Static resx:ResUI.menuProxiesDelaytest}"> ToolTip.Tip="{x:Static resx:ResUI.menuProxiesDelaytest}">
<Button.Content> <Button.Content>
<PathIcon Data="{StaticResource building_ping}" Foreground="{DynamicResource ButtonDefaultTertiaryForeground}" /> <PathIcon Data="{StaticResource SemiIconBolt}" />
</Button.Content> </Button.Content>
</Button> </Button>

View file

@ -89,52 +89,73 @@
Grid.Column="0" Grid.Column="0"
Margin="{StaticResource Margin4}" Margin="{StaticResource Margin4}"
VerticalAlignment="Center" VerticalAlignment="Center"
Text="{x:Static resx:ResUI.TbBootstrapDNS}" />
<ComboBox
x:Name="cmbBootstrapDNS"
Grid.Row="3"
Grid.Column="1"
Width="300"
Margin="{StaticResource Margin4}"
IsEditable="True" />
<TextBlock
Grid.Row="3"
Grid.Column="2"
Margin="{StaticResource Margin4}"
VerticalAlignment="Center"
Text="{x:Static resx:ResUI.TbBootstrapDNSTips}"
TextWrapping="Wrap" />
<TextBlock
Grid.Row="4"
Grid.Column="0"
Margin="{StaticResource Margin4}"
VerticalAlignment="Center"
Text="{x:Static resx:ResUI.TbXrayFreedomStrategy}" /> Text="{x:Static resx:ResUI.TbXrayFreedomStrategy}" />
<ComboBox <ComboBox
x:Name="cmbRayFreedomDNSStrategy" x:Name="cmbRayFreedomDNSStrategy"
Grid.Row="3" Grid.Row="4"
Grid.Column="1" Grid.Column="1"
Width="200" Width="200"
Margin="{StaticResource Margin4}" Margin="{StaticResource Margin4}"
PlaceholderText="Default" /> PlaceholderText="Default" />
<TextBlock <TextBlock
Grid.Row="4" Grid.Row="5"
Grid.Column="0" Grid.Column="0"
Margin="{StaticResource Margin4}" Margin="{StaticResource Margin4}"
VerticalAlignment="Center" VerticalAlignment="Center"
Text="{x:Static resx:ResUI.TbSBDirectResolveStrategy}" /> Text="{x:Static resx:ResUI.TbSBDirectResolveStrategy}" />
<ComboBox <ComboBox
x:Name="cmbSBDirectDNSStrategy" x:Name="cmbSBDirectDNSStrategy"
Grid.Row="4" Grid.Row="5"
Grid.Column="1" Grid.Column="1"
Width="200" Width="200"
Margin="{StaticResource Margin4}" Margin="{StaticResource Margin4}"
PlaceholderText="Default" /> PlaceholderText="Default" />
<TextBlock <TextBlock
Grid.Row="5" Grid.Row="6"
Grid.Column="0" Grid.Column="0"
Margin="{StaticResource Margin4}" Margin="{StaticResource Margin4}"
VerticalAlignment="Center" VerticalAlignment="Center"
Text="{x:Static resx:ResUI.TbSBRemoteResolveStrategy}" /> Text="{x:Static resx:ResUI.TbSBRemoteResolveStrategy}" />
<ComboBox <ComboBox
x:Name="cmbSBRemoteDNSStrategy" x:Name="cmbSBRemoteDNSStrategy"
Grid.Row="5" Grid.Row="6"
Grid.Column="1" Grid.Column="1"
Width="200" Width="200"
Margin="{StaticResource Margin4}" Margin="{StaticResource Margin4}"
PlaceholderText="Default" /> PlaceholderText="Default" />
<TextBlock <TextBlock
Grid.Row="6" Grid.Row="7"
Grid.Column="0" Grid.Column="0"
Margin="{StaticResource Margin4}" Margin="{StaticResource Margin4}"
VerticalAlignment="Center" VerticalAlignment="Center"
Text="{x:Static resx:ResUI.TbAddCommonDNSHosts}" /> Text="{x:Static resx:ResUI.TbAddCommonDNSHosts}" />
<ToggleSwitch <ToggleSwitch
x:Name="togAddCommonHosts" x:Name="togAddCommonHosts"
Grid.Row="6" Grid.Row="7"
Grid.Column="1" Grid.Column="1"
Margin="{StaticResource Margin4}" Margin="{StaticResource Margin4}"
HorizontalAlignment="Left" /> HorizontalAlignment="Left" />

View file

@ -20,6 +20,7 @@ public partial class DNSSettingWindow : WindowBase<DNSSettingViewModel>
cmbSBRemoteDNSStrategy.ItemsSource = Global.SingboxDomainStrategy4Out; cmbSBRemoteDNSStrategy.ItemsSource = Global.SingboxDomainStrategy4Out;
cmbDirectDNS.ItemsSource = Global.DomainDirectDNSAddress; cmbDirectDNS.ItemsSource = Global.DomainDirectDNSAddress;
cmbRemoteDNS.ItemsSource = Global.DomainRemoteDNSAddress; cmbRemoteDNS.ItemsSource = Global.DomainRemoteDNSAddress;
cmbBootstrapDNS.ItemsSource = Global.DomainPureIPDNSAddress;
cmbDirectExpectedIPs.ItemsSource = Global.ExpectedIPs; cmbDirectExpectedIPs.ItemsSource = Global.ExpectedIPs;
cmbdomainStrategy4FreedomCompatible.ItemsSource = Global.DomainStrategy4Freedoms; cmbdomainStrategy4FreedomCompatible.ItemsSource = Global.DomainStrategy4Freedoms;
@ -35,6 +36,7 @@ public partial class DNSSettingWindow : WindowBase<DNSSettingViewModel>
this.Bind(ViewModel, vm => vm.BlockBindingQuery, v => v.togBlockBindingQuery.IsChecked).DisposeWith(disposables); this.Bind(ViewModel, vm => vm.BlockBindingQuery, v => v.togBlockBindingQuery.IsChecked).DisposeWith(disposables);
this.Bind(ViewModel, vm => vm.DirectDNS, v => v.cmbDirectDNS.Text).DisposeWith(disposables); this.Bind(ViewModel, vm => vm.DirectDNS, v => v.cmbDirectDNS.Text).DisposeWith(disposables);
this.Bind(ViewModel, vm => vm.RemoteDNS, v => v.cmbRemoteDNS.Text).DisposeWith(disposables); this.Bind(ViewModel, vm => vm.RemoteDNS, v => v.cmbRemoteDNS.Text).DisposeWith(disposables);
this.Bind(ViewModel, vm => vm.BootstrapDNS, v => v.cmbBootstrapDNS.Text).DisposeWith(disposables);
this.Bind(ViewModel, vm => vm.RayStrategy4Freedom, v => v.cmbRayFreedomDNSStrategy.SelectedItem).DisposeWith(disposables); this.Bind(ViewModel, vm => vm.RayStrategy4Freedom, v => v.cmbRayFreedomDNSStrategy.SelectedItem).DisposeWith(disposables);
this.Bind(ViewModel, vm => vm.SingboxStrategy4Direct, v => v.cmbSBDirectDNSStrategy.SelectedItem).DisposeWith(disposables); this.Bind(ViewModel, vm => vm.SingboxStrategy4Direct, v => v.cmbSBDirectDNSStrategy.SelectedItem).DisposeWith(disposables);
this.Bind(ViewModel, vm => vm.SingboxStrategy4Proxy, v => v.cmbSBRemoteDNSStrategy.SelectedItem).DisposeWith(disposables); this.Bind(ViewModel, vm => vm.SingboxStrategy4Proxy, v => v.cmbSBRemoteDNSStrategy.SelectedItem).DisposeWith(disposables);

View file

@ -25,28 +25,22 @@
<Button <Button
x:Name="btnCopy" x:Name="btnCopy"
Width="{StaticResource IconButtonWidth}"
Height="{StaticResource IconButtonHeight}"
Margin="{StaticResource MarginLr8}" Margin="{StaticResource MarginLr8}"
Classes="Success" Classes="IconButton Success"
Click="menuMsgViewCopyAll_Click" Click="menuMsgViewCopyAll_Click"
Theme="{DynamicResource BorderlessButton}"
ToolTip.Tip="{x:Static resx:ResUI.menuMsgViewCopyAll}"> ToolTip.Tip="{x:Static resx:ResUI.menuMsgViewCopyAll}">
<Button.Content> <Button.Content>
<PathIcon Data="{StaticResource building_copy}" Foreground="{DynamicResource ButtonDefaultTertiaryForeground}" /> <PathIcon Data="{StaticResource SemiIconCopy}" />
</Button.Content> </Button.Content>
</Button> </Button>
<Button <Button
x:Name="btnClear" x:Name="btnClear"
Width="{StaticResource IconButtonWidth}"
Height="{StaticResource IconButtonHeight}"
Margin="{StaticResource MarginLr8}" Margin="{StaticResource MarginLr8}"
Classes="Success" Classes="IconButton Success"
Click="menuMsgViewClear_Click" Click="menuMsgViewClear_Click"
Theme="{DynamicResource BorderlessButton}"
ToolTip.Tip="{x:Static resx:ResUI.menuMsgViewClear}"> ToolTip.Tip="{x:Static resx:ResUI.menuMsgViewClear}">
<Button.Content> <Button.Content>
<PathIcon Data="{StaticResource building_delete}" Foreground="{DynamicResource ButtonDefaultTertiaryForeground}" /> <PathIcon Data="{StaticResource SemiIconDelete}" />
</Button.Content> </Button.Content>
</Button> </Button>
<TextBlock <TextBlock

View file

@ -51,12 +51,11 @@
<Button <Button
x:Name="btnAutofitColumnWidth" x:Name="btnAutofitColumnWidth"
Width="32" Margin="{StaticResource MarginLr4}"
Height="32" Classes="IconButton Success"
Margin="8,0"
ToolTip.Tip="{x:Static resx:ResUI.menuProfileAutofitColumnWidth}"> ToolTip.Tip="{x:Static resx:ResUI.menuProfileAutofitColumnWidth}">
<Button.Content> <Button.Content>
<PathIcon Data="{StaticResource building_fit}" /> <PathIcon Data="{StaticResource SemiIconExpand}" />
</Button.Content> </Button.Content>
</Button> </Button>

View file

@ -32,39 +32,30 @@
<Button <Button
x:Name="btnEditSub" x:Name="btnEditSub"
Width="{StaticResource IconButtonWidth}"
Height="{StaticResource IconButtonHeight}"
Margin="{StaticResource MarginLr4}" Margin="{StaticResource MarginLr4}"
Classes="Success" Classes="IconButton Success"
Theme="{DynamicResource BorderlessButton}"
ToolTip.Tip="{x:Static resx:ResUI.menuSubEdit}"> ToolTip.Tip="{x:Static resx:ResUI.menuSubEdit}">
<Button.Content> <Button.Content>
<PathIcon Data="{StaticResource building_edit}" Foreground="{DynamicResource ButtonDefaultTertiaryForeground}" /> <PathIcon Data="{StaticResource SemiIconEdit}" />
</Button.Content> </Button.Content>
</Button> </Button>
<Button <Button
x:Name="btnAddSub" x:Name="btnAddSub"
Width="{StaticResource IconButtonWidth}"
Height="{StaticResource IconButtonHeight}"
Margin="{StaticResource MarginLr4}" Margin="{StaticResource MarginLr4}"
Classes="Success" Classes="IconButton Success"
Theme="{DynamicResource BorderlessButton}"
ToolTip.Tip="{x:Static resx:ResUI.menuSubAdd}"> ToolTip.Tip="{x:Static resx:ResUI.menuSubAdd}">
<Button.Content> <Button.Content>
<PathIcon Data="{StaticResource building_add}" Foreground="{DynamicResource ButtonDefaultTertiaryForeground}" /> <PathIcon Data="{StaticResource SemiIconPlus}" />
</Button.Content> </Button.Content>
</Button> </Button>
<Button <Button
x:Name="btnAutofitColumnWidth" x:Name="btnAutofitColumnWidth"
Width="{StaticResource IconButtonWidth}"
Height="{StaticResource IconButtonHeight}"
Margin="{StaticResource MarginLr4}" Margin="{StaticResource MarginLr4}"
Classes="Success" Classes="IconButton Success"
Theme="{DynamicResource BorderlessButton}"
ToolTip.Tip="{x:Static resx:ResUI.menuProfileAutofitColumnWidth}"> ToolTip.Tip="{x:Static resx:ResUI.menuProfileAutofitColumnWidth}">
<Button.Content> <Button.Content>
<PathIcon Data="{StaticResource building_fit}" Foreground="{DynamicResource ButtonDefaultTertiaryForeground}" /> <PathIcon Data="{StaticResource SemiIconExpand}" />
</Button.Content> </Button.Content>
</Button> </Button>

View file

@ -72,13 +72,15 @@
<Button <Button
Grid.Row="2" Grid.Row="2"
Grid.Column="2" Grid.Column="2"
Width="{StaticResource IconButtonWidth}" Classes="IconButton"
Height="{StaticResource IconButtonHeight}"
Margin="{StaticResource MarginLr8}"
HorizontalAlignment="Left" HorizontalAlignment="Left"
Theme="{DynamicResource BorderlessButton}"> Margin="{StaticResource MarginLr8}">
<Button.Content> <Button.Content>
<PathIcon Data="{StaticResource building_more}" Foreground="{DynamicResource ButtonDefaultTertiaryForeground}" /> <PathIcon Data="{StaticResource SemiIconMore}" >
<PathIcon.RenderTransform>
<RotateTransform Angle="90" />
</PathIcon.RenderTransform>
</PathIcon>
</Button.Content> </Button.Content>
<Button.Flyout> <Button.Flyout>
<Flyout> <Flyout>

View file

@ -19,13 +19,13 @@
</Style> </Style>
</UserControl.Styles> </UserControl.Styles>
<Button <Button Margin="{StaticResource MarginLr8}" Classes="IconButton">
Width="{StaticResource IconButtonWidth}"
Height="{StaticResource IconButtonHeight}"
Margin="{StaticResource MarginLr8}"
Theme="{DynamicResource BorderlessButton}">
<Button.Content> <Button.Content>
<PathIcon Data="{StaticResource building_more}" Foreground="{DynamicResource ButtonDefaultTertiaryForeground}" /> <PathIcon Data="{StaticResource SemiIconMore}">
<PathIcon.RenderTransform>
<RotateTransform Angle="90" />
</PathIcon.RenderTransform>
</PathIcon>
</Button.Content> </Button.Content>
<Button.Flyout> <Button.Flyout>
<Flyout> <Flyout>

View file

@ -115,10 +115,34 @@
Margin="{StaticResource Margin8}" Margin="{StaticResource Margin8}"
VerticalAlignment="Center" VerticalAlignment="Center"
Style="{StaticResource ToolbarTextBlock}" Style="{StaticResource ToolbarTextBlock}"
Text="{x:Static resx:ResUI.TbBootstrapDNS}" />
<ComboBox
x:Name="cmbBootstrapDNS"
Grid.Row="3"
Grid.Column="1"
Width="300"
Margin="{StaticResource Margin8}"
IsEditable="True"
Style="{StaticResource DefComboBox}" />
<TextBlock
Grid.Row="3"
Grid.Column="2"
Margin="{StaticResource Margin8}"
VerticalAlignment="Center"
Style="{StaticResource ToolbarTextBlock}"
Text="{x:Static resx:ResUI.TbBootstrapDNSTips}"
TextWrapping="Wrap" />
<TextBlock
Grid.Row="4"
Grid.Column="0"
Margin="{StaticResource Margin8}"
VerticalAlignment="Center"
Style="{StaticResource ToolbarTextBlock}"
Text="{x:Static resx:ResUI.TbXrayFreedomStrategy}" /> Text="{x:Static resx:ResUI.TbXrayFreedomStrategy}" />
<ComboBox <ComboBox
x:Name="cmbRayFreedomDNSStrategy" x:Name="cmbRayFreedomDNSStrategy"
Grid.Row="3" Grid.Row="4"
Grid.Column="1" Grid.Column="1"
Width="200" Width="200"
Margin="{StaticResource Margin8}" Margin="{StaticResource Margin8}"
@ -126,7 +150,7 @@
Style="{StaticResource DefComboBox}" /> Style="{StaticResource DefComboBox}" />
<TextBlock <TextBlock
Grid.Row="4" Grid.Row="5"
Grid.Column="0" Grid.Column="0"
Margin="{StaticResource Margin8}" Margin="{StaticResource Margin8}"
VerticalAlignment="Center" VerticalAlignment="Center"
@ -134,7 +158,7 @@
Text="{x:Static resx:ResUI.TbSBDirectResolveStrategy}" /> Text="{x:Static resx:ResUI.TbSBDirectResolveStrategy}" />
<ComboBox <ComboBox
x:Name="cmbSBDirectDNSStrategy" x:Name="cmbSBDirectDNSStrategy"
Grid.Row="4" Grid.Row="5"
Grid.Column="1" Grid.Column="1"
Width="200" Width="200"
Margin="{StaticResource Margin8}" Margin="{StaticResource Margin8}"
@ -142,7 +166,7 @@
Style="{StaticResource DefComboBox}" /> Style="{StaticResource DefComboBox}" />
<TextBlock <TextBlock
Grid.Row="5" Grid.Row="6"
Grid.Column="0" Grid.Column="0"
Margin="{StaticResource Margin8}" Margin="{StaticResource Margin8}"
VerticalAlignment="Center" VerticalAlignment="Center"
@ -150,7 +174,7 @@
Text="{x:Static resx:ResUI.TbSBRemoteResolveStrategy}" /> Text="{x:Static resx:ResUI.TbSBRemoteResolveStrategy}" />
<ComboBox <ComboBox
x:Name="cmbSBRemoteDNSStrategy" x:Name="cmbSBRemoteDNSStrategy"
Grid.Row="5" Grid.Row="6"
Grid.Column="1" Grid.Column="1"
Width="200" Width="200"
Margin="{StaticResource Margin8}" Margin="{StaticResource Margin8}"
@ -158,7 +182,7 @@
Style="{StaticResource DefComboBox}" /> Style="{StaticResource DefComboBox}" />
<TextBlock <TextBlock
Grid.Row="6" Grid.Row="7"
Grid.Column="0" Grid.Column="0"
Margin="{StaticResource Margin8}" Margin="{StaticResource Margin8}"
VerticalAlignment="Center" VerticalAlignment="Center"
@ -166,7 +190,7 @@
Text="{x:Static resx:ResUI.TbAddCommonDNSHosts}" /> Text="{x:Static resx:ResUI.TbAddCommonDNSHosts}" />
<ToggleButton <ToggleButton
x:Name="togAddCommonHosts" x:Name="togAddCommonHosts"
Grid.Row="6" Grid.Row="7"
Grid.Column="1" Grid.Column="1"
Margin="{StaticResource Margin8}" Margin="{StaticResource Margin8}"
HorizontalAlignment="Left" /> HorizontalAlignment="Left" />

View file

@ -18,6 +18,7 @@ public partial class DNSSettingWindow
cmbSBRemoteDNSStrategy.ItemsSource = Global.SingboxDomainStrategy4Out; cmbSBRemoteDNSStrategy.ItemsSource = Global.SingboxDomainStrategy4Out;
cmbDirectDNS.ItemsSource = Global.DomainDirectDNSAddress; cmbDirectDNS.ItemsSource = Global.DomainDirectDNSAddress;
cmbRemoteDNS.ItemsSource = Global.DomainRemoteDNSAddress; cmbRemoteDNS.ItemsSource = Global.DomainRemoteDNSAddress;
cmbBootstrapDNS.ItemsSource = Global.DomainPureIPDNSAddress;
cmbDirectExpectedIPs.ItemsSource = Global.ExpectedIPs; cmbDirectExpectedIPs.ItemsSource = Global.ExpectedIPs;
cmbdomainStrategy4FreedomCompatible.ItemsSource = Global.DomainStrategy4Freedoms; cmbdomainStrategy4FreedomCompatible.ItemsSource = Global.DomainStrategy4Freedoms;
@ -33,6 +34,7 @@ public partial class DNSSettingWindow
this.Bind(ViewModel, vm => vm.BlockBindingQuery, v => v.togBlockBindingQuery.IsChecked).DisposeWith(disposables); this.Bind(ViewModel, vm => vm.BlockBindingQuery, v => v.togBlockBindingQuery.IsChecked).DisposeWith(disposables);
this.Bind(ViewModel, vm => vm.DirectDNS, v => v.cmbDirectDNS.Text).DisposeWith(disposables); this.Bind(ViewModel, vm => vm.DirectDNS, v => v.cmbDirectDNS.Text).DisposeWith(disposables);
this.Bind(ViewModel, vm => vm.RemoteDNS, v => v.cmbRemoteDNS.Text).DisposeWith(disposables); this.Bind(ViewModel, vm => vm.RemoteDNS, v => v.cmbRemoteDNS.Text).DisposeWith(disposables);
this.Bind(ViewModel, vm => vm.BootstrapDNS, v => v.cmbBootstrapDNS.Text).DisposeWith(disposables);
this.Bind(ViewModel, vm => vm.RayStrategy4Freedom, v => v.cmbRayFreedomDNSStrategy.Text).DisposeWith(disposables); this.Bind(ViewModel, vm => vm.RayStrategy4Freedom, v => v.cmbRayFreedomDNSStrategy.Text).DisposeWith(disposables);
this.Bind(ViewModel, vm => vm.SingboxStrategy4Direct, v => v.cmbSBDirectDNSStrategy.Text).DisposeWith(disposables); this.Bind(ViewModel, vm => vm.SingboxStrategy4Direct, v => v.cmbSBDirectDNSStrategy.Text).DisposeWith(disposables);
this.Bind(ViewModel, vm => vm.SingboxStrategy4Proxy, v => v.cmbSBRemoteDNSStrategy.Text).DisposeWith(disposables); this.Bind(ViewModel, vm => vm.SingboxStrategy4Proxy, v => v.cmbSBRemoteDNSStrategy.Text).DisposeWith(disposables);