Compare commits

...

5 commits

Author SHA1 Message Date
DHR60
a87a015c03
Fix some minor UI bugs (#7941)
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-09-12 20:28:24 +08:00
2dust
c559914ff7 Fix
https://github.com/2dust/v2rayN/issues/7938
2025-09-12 17:01:53 +08:00
2dust
436d95576e Optimization and improvement JsonUtils 2025-09-12 16:45:55 +08:00
DHR60
54e83391d0
Pre-resolve to apply hosts (#7937) 2025-09-12 16:28:31 +08:00
JieXu
3e0578f775
Update CheckUpdateViewModel.cs (#7932)
* Update CheckUpdateViewModel.cs

* Update Utils.cs

* Update Utils.cs

* Update Utils.cs

* Update CheckUpdateViewModel.cs

* Update CheckUpdateViewModel.cs

* Update Utils.cs
2025-09-12 16:24:59 +08:00
12 changed files with 152 additions and 37 deletions

View file

@ -9,6 +9,31 @@ public class JsonUtils
{
private static readonly string _tag = "JsonUtils";
private static readonly JsonSerializerOptions _defaultDeserializeOptions = new()
{
PropertyNameCaseInsensitive = true,
ReadCommentHandling = JsonCommentHandling.Skip
};
private static readonly JsonSerializerOptions _defaultSerializeOptions = new()
{
WriteIndented = true,
DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingNull,
Encoder = JavaScriptEncoder.UnsafeRelaxedJsonEscaping
};
private static readonly JsonSerializerOptions _nullValueSerializeOptions = new()
{
WriteIndented = true,
DefaultIgnoreCondition = JsonIgnoreCondition.Never,
Encoder = JavaScriptEncoder.UnsafeRelaxedJsonEscaping
};
private static readonly JsonDocumentOptions _defaultDocumentOptions = new()
{
CommentHandling = JsonCommentHandling.Skip
};
/// <summary>
/// DeepCopy
/// </summary>
@ -34,11 +59,7 @@ public class JsonUtils
{
return default;
}
var options = new JsonSerializerOptions
{
PropertyNameCaseInsensitive = true
};
return JsonSerializer.Deserialize<T>(strJson, options);
return JsonSerializer.Deserialize<T>(strJson, _defaultDeserializeOptions);
}
catch
{
@ -59,7 +80,7 @@ public class JsonUtils
{
return null;
}
return JsonNode.Parse(strJson);
return JsonNode.Parse(strJson, nodeOptions: null, _defaultDocumentOptions);
}
catch
{
@ -84,12 +105,7 @@ public class JsonUtils
{
return result;
}
var options = new JsonSerializerOptions
{
WriteIndented = indented,
DefaultIgnoreCondition = nullValue ? JsonIgnoreCondition.Never : JsonIgnoreCondition.WhenWritingNull,
Encoder = JavaScriptEncoder.UnsafeRelaxedJsonEscaping
};
var options = nullValue ? _nullValueSerializeOptions : _defaultSerializeOptions;
result = JsonSerializer.Serialize(obj, options);
}
catch (Exception ex)

View file

@ -331,6 +331,32 @@ public class Utils
.ToList();
}
public static Dictionary<string, List<string>> ParseHostsToDictionary(string hostsContent)
{
var userHostsMap = hostsContent
.Split(new[] { '\r', '\n' }, StringSplitOptions.RemoveEmptyEntries)
.Select(line => line.Trim())
// skip full-line comments
.Where(line => !string.IsNullOrWhiteSpace(line) && !line.StartsWith("#"))
// strip inline comments (truncate at '#')
.Select(line =>
{
var index = line.IndexOf('#');
return index >= 0 ? line.Substring(0, index).Trim() : line;
})
// ensure line still contains valid parts
.Where(line => !string.IsNullOrWhiteSpace(line) && line.Contains(' '))
.Select(line => line.Split(new[] { ' ', '\t' }, StringSplitOptions.RemoveEmptyEntries))
.Where(parts => parts.Length >= 2)
.GroupBy(parts => parts[0])
.ToDictionary(
group => group.Key,
group => group.SelectMany(parts => parts.Skip(1)).ToList()
);
return userHostsMap;
}
#endregion
#region
@ -857,6 +883,55 @@ public class Utils
return false;
}
public static bool IsPackagedInstall()
{
try
{
if (IsWindows() || IsOSX())
{
return false;
}
if (!string.IsNullOrEmpty(Environment.GetEnvironmentVariable("APPIMAGE")))
{
return true;
}
var exePath = GetExePath();
var baseDir = string.IsNullOrEmpty(exePath) ? StartupPath() : Path.GetDirectoryName(exePath) ?? "";
var p = baseDir.Replace('\\', '/');
if (string.IsNullOrEmpty(p))
{
return false;
}
if (p.Contains("/.mount_", StringComparison.Ordinal))
{
return true;
}
if (p.StartsWith("/opt/v2rayN", StringComparison.OrdinalIgnoreCase))
{
return true;
}
if (p.StartsWith("/usr/lib/v2rayN", StringComparison.OrdinalIgnoreCase))
{
return true;
}
if (p.StartsWith("/usr/share/v2rayN", StringComparison.OrdinalIgnoreCase))
{
return true;
}
}
catch
{
}
return false;
}
private static async Task<string?> GetLinuxUserId()
{
var arg = new List<string>() { "-c", "id -u" };

View file

@ -4,7 +4,7 @@ public class ClashFmt : BaseFmt
{
public static ProfileItem? ResolveFull(string strData, string? subRemarks)
{
if (Contains(strData, "port", "socks-port", "proxies"))
if (Contains(strData, "external-controller", "-port", "proxies"))
{
var fileName = WriteAllText(strData, "yaml");

View file

@ -94,17 +94,7 @@ public partial class CoreConfigSingboxService
if (!simpleDNSItem.Hosts.IsNullOrEmpty())
{
var userHostsMap = simpleDNSItem.Hosts
.Split(new[] { '\r', '\n' }, StringSplitOptions.RemoveEmptyEntries)
.Select(line => line.Trim())
.Where(line => !string.IsNullOrWhiteSpace(line) && line.Contains(' '))
.Select(line => line.Split(new[] { ' ', '\t' }, StringSplitOptions.RemoveEmptyEntries))
.Where(parts => parts.Length >= 2)
.GroupBy(parts => parts[0])
.ToDictionary(
group => group.Key,
group => group.SelectMany(parts => parts.Skip(1)).ToList()
);
var userHostsMap = Utils.ParseHostsToDictionary(simpleDNSItem.Hosts);
foreach (var kvp in userHostsMap)
{

View file

@ -71,6 +71,31 @@ public partial class CoreConfigSingboxService
});
}
var hostsDomains = new List<string>();
var systemHostsMap = Utils.GetSystemHosts();
foreach (var kvp in systemHostsMap)
{
hostsDomains.Add(kvp.Key);
}
var dnsItem = await AppManager.Instance.GetDNSItem(ECoreType.sing_box);
if (dnsItem == null || dnsItem.Enabled == false)
{
var simpleDNSItem = _config.SimpleDNSItem;
if (!simpleDNSItem.Hosts.IsNullOrEmpty())
{
var userHostsMap = Utils.ParseHostsToDictionary(simpleDNSItem.Hosts);
foreach (var kvp in userHostsMap)
{
hostsDomains.Add(kvp.Key);
}
}
}
singboxConfig.route.rules.Add(new()
{
action = "resolve",
domain = hostsDomains,
});
singboxConfig.route.rules.Add(new()
{
outbound = Global.DirectTag,

View file

@ -261,17 +261,7 @@ public partial class CoreConfigV2rayService
if (!simpleDNSItem.Hosts.IsNullOrEmpty())
{
var userHostsMap = simpleDNSItem.Hosts
.Split(new[] { '\r', '\n' }, StringSplitOptions.RemoveEmptyEntries)
.Select(line => line.Trim())
.Where(line => !string.IsNullOrWhiteSpace(line) && line.Contains(' '))
.Select(line => line.Split(new[] { ' ', '\t' }, StringSplitOptions.RemoveEmptyEntries))
.Where(parts => parts.Length >= 2)
.GroupBy(parts => parts[0])
.ToDictionary(
group => group.Key,
group => group.SelectMany(parts => parts.Skip(1)).ToList()
);
var userHostsMap = Utils.ParseHostsToDictionary(simpleDNSItem.Hosts);
foreach (var kvp in userHostsMap)
{

View file

@ -63,6 +63,16 @@ public class CheckUpdateViewModel : MyReactiveObject
private CheckUpdateModel GetCheckUpdateModel(string coreType)
{
if (coreType == _v2rayN && Utils.IsPackagedInstall())
{
return new()
{
IsSelected = false,
CoreType = coreType,
Remarks = ResUI.menuCheckUpdate + " (Not Support)",
};
}
return new()
{
IsSelected = _config.CheckUpdateItem.SelectedCoreTypes?.Contains(coreType) ?? true,
@ -104,6 +114,11 @@ public class CheckUpdateViewModel : MyReactiveObject
}
else if (item.CoreType == _v2rayN)
{
if (Utils.IsPackagedInstall())
{
await UpdateView(_v2rayN, "Not Support");
continue;
}
await CheckUpdateN(EnableCheckPreReleaseUpdate);
}
else if (item.CoreType == ECoreType.Xray.ToString())

View file

@ -400,7 +400,7 @@
Grid.Column="1"
Width="400"
Margin="{StaticResource Margin4}"
Watermark="1000:2000,3000:4000" />
Watermark="1000-2000,3000,4000" />
<TextBlock
Grid.Row="3"
Grid.Column="2"

View file

@ -176,6 +176,7 @@
<DataGrid
x:Name="lstRules"
AutoGenerateColumns="False"
Background="Transparent"
BorderThickness="1"
CanUserResizeColumns="True"
GridLinesVisibility="All"

View file

@ -92,6 +92,7 @@
<DataGrid
x:Name="lstRoutings"
AutoGenerateColumns="False"
Background="Transparent"
BorderThickness="1"
CanUserResizeColumns="True"
GridLinesVisibility="All"

View file

@ -538,7 +538,7 @@
Width="400"
Margin="{StaticResource Margin4}"
HorizontalAlignment="Left"
materialDesign:HintAssist.Hint="1000:2000,3000:4000"
materialDesign:HintAssist.Hint="1000-2000,3000,4000"
Style="{StaticResource DefTextBox}" />
<TextBlock
Grid.Row="3"

View file

@ -122,6 +122,8 @@ public partial class RoutingRuleSettingWindow
private void RoutingRuleSettingWindow_PreviewKeyDown(object sender, KeyEventArgs e)
{
if (!lstRules.IsKeyboardFocusWithin)
return;
if (Keyboard.IsKeyDown(Key.LeftCtrl) || Keyboard.IsKeyDown(Key.RightCtrl))
{
if (e.Key == Key.A)