Files
chen08209 cb080885b0 feat(network)!: set desktop TUN key by key over each profile's own
BREAKING CHANGE: Desktop TUN keys follow the profile unless picked; changed values carry over
Changelog: Per-key desktop TUN settings; system proxy and tray fixes
2026-10-17 17:21:43 +08:00

831 lines
22 KiB
Dart

// ignore_for_file: constant_identifier_names
import 'dart:io';
import 'package:fl_clash/common/context.dart';
import 'package:fl_clash/common/system.dart';
import 'package:fl_clash/ui/color.dart';
import 'package:fl_clash/ui/icons/icons.dart';
import 'package:fl_clash/ui/surface.dart';
import 'package:material_ui/material_ui.dart';
import 'package:flutter/services.dart';
import 'package:freezed_annotation/freezed_annotation.dart';
enum SupportPlatform {
Windows,
MacOS,
Linux,
Android;
static SupportPlatform get currentPlatform {
if (system.isWindows) {
return SupportPlatform.Windows;
} else if (system.isMacOS) {
return SupportPlatform.MacOS;
} else if (Platform.isLinux) {
return SupportPlatform.Linux;
} else if (system.isAndroid) {
return SupportPlatform.Android;
}
throw UnsupportedError('Unsupported platform: ${Platform.operatingSystem}');
}
}
const desktopPlatforms = [
SupportPlatform.Linux,
SupportPlatform.MacOS,
SupportPlatform.Windows,
];
enum GroupName { GLOBAL }
enum GroupType {
@JsonValue('select')
Selector('select'),
@JsonValue('url-test')
URLTest('url-test'),
@JsonValue('fallback')
Fallback('fallback'),
@JsonValue('load-balance')
LoadBalance('load-balance'),
@JsonValue('relay')
Relay('relay');
final String value;
const GroupType(this.value);
static GroupType parse(String type) {
return switch (type.toLowerCase()) {
'url-test' || 'urltest' => URLTest,
'select' || 'selector' => Selector,
'fallback' => Fallback,
'load-balance' || 'loadbalance' => LoadBalance,
'relay' => Relay,
String() => throw UnimplementedError(),
};
}
/// The core refuses a relay group, so it stays parseable but unofferable.
static List<GroupType> get selectableValues =>
values.where((item) => item != Relay).toList();
}
enum LoadBalanceStrategy {
@JsonValue('consistent-hashing')
consistentHashing('consistent-hashing'),
@JsonValue('round-robin')
roundRobin('round-robin'),
@JsonValue('sticky-sessions')
stickySessions('sticky-sessions');
final String value;
const LoadBalanceStrategy(this.value);
}
enum IpVersion {
dual('dual'),
ipv4('ipv4'),
ipv6('ipv6'),
@JsonValue('ipv4-prefer')
ipv4Prefer('ipv4-prefer'),
@JsonValue('ipv6-prefer')
ipv6Prefer('ipv6-prefer');
final String value;
const IpVersion(this.value);
}
extension GroupTypeExtension on GroupType {
static List<String> get valueList =>
GroupType.values.map((e) => e.toString().split('.').last).toList();
bool get isComputedSelected {
return [GroupType.URLTest, GroupType.Fallback].contains(this);
}
static GroupType? getGroupType(String value) {
final index = GroupTypeExtension.valueList.indexOf(value);
if (index == -1) return null;
return GroupType.values[index];
}
}
enum DelayTestPhase { queued, running }
enum Mode { rule, global, direct }
enum ViewMode { mobile, laptop, desktop }
enum LogLevel { debug, info, warning, error, silent }
enum RecordTone { neutral, warning, error }
extension RecordToneExt on RecordTone {
Color? accentColor(BuildContext context) {
final colorScheme = Theme.of(context).colorScheme;
return switch (this) {
RecordTone.warning => colorScheme.warning,
RecordTone.error => colorScheme.error,
RecordTone.neutral => null,
};
}
Color labelColor(BuildContext context) {
final colorScheme = Theme.of(context).colorScheme;
return switch (this) {
RecordTone.neutral => colorScheme.onSurfaceVariant,
RecordTone.warning => colorScheme.onWarningContainer,
RecordTone.error => colorScheme.onErrorContainer,
};
}
Color labelContainerColor(BuildContext context) {
final colorScheme = Theme.of(context).colorScheme;
return switch (this) {
RecordTone.neutral => context.raisedSurfaceLevel.colorOf(colorScheme),
RecordTone.warning => colorScheme.warningContainer,
RecordTone.error => colorScheme.errorContainer,
};
}
}
enum MessageLevel { info, success, warning, error }
extension MessageLevelExt on MessageLevel {
Glyph? get glyph {
return switch (this) {
MessageLevel.info => null,
MessageLevel.success => AppGlyphs.checkCircle,
MessageLevel.warning => AppGlyphs.warning,
MessageLevel.error => AppGlyphs.error,
};
}
Color containerColor(BuildContext context) {
final colorScheme = Theme.of(context).colorScheme;
return switch (this) {
MessageLevel.error => colorScheme.errorContainer,
_ => SurfaceLevel.floatingOf(colorScheme).colorOf(colorScheme),
};
}
Color contentColor(BuildContext context) {
final colorScheme = Theme.of(context).colorScheme;
return switch (this) {
MessageLevel.error => colorScheme.onErrorContainer,
_ => colorScheme.onSurfaceVariant,
};
}
Color iconColor(BuildContext context) {
final colorScheme = Theme.of(context).colorScheme;
return switch (this) {
MessageLevel.info => colorScheme.onSurfaceVariant,
MessageLevel.success => colorScheme.success,
MessageLevel.warning => colorScheme.warning,
MessageLevel.error => colorScheme.onErrorContainer,
};
}
Duration get duration {
return switch (this) {
MessageLevel.info || MessageLevel.success => const Duration(seconds: 3),
MessageLevel.warning => const Duration(seconds: 5),
MessageLevel.error => const Duration(seconds: 6),
};
}
}
enum TrafficUnit { B, KB, MB, GB, TB }
enum NavigationItemMode { mobile, desktop, more, moreFull }
enum ProxiesSortType { none, delay, name }
enum TunStack { gvisor, system, mixed, mips }
enum AccessControlMode { acceptSelected, rejectSelected }
enum AccessSortType { none, name, time }
enum ProfileType { file, url, custom }
enum ResultType {
@JsonValue(0)
success,
@JsonValue(-1)
error,
}
enum CoreEventType {
log,
delay,
request,
requestTraffic,
dns,
loaded,
crash,
geoUpdate,
routeChanged,
dialerLoop,
}
enum FindProcessMode { always, off }
enum InterfaceNameMode { clear, follow, custom }
enum RestoreOption { all, onlyProfiles }
enum ProxiesType { tab, list }
enum ProxiesLayout { loose, standard, tight }
enum ProxyCardType { loose, standard, compact }
enum DnsMode {
normal,
@JsonValue('fake-ip')
fakeIp,
@JsonValue('redir-host')
redirHost,
hosts,
}
enum DnsCacheAlgorithm { lru, arc }
enum FakeIpFilterMode { blacklist, whitelist, rule }
mixin OverrideKey on Enum {
String get path;
String? get parent {
final dot = path.indexOf('.');
return dot < 0 ? null : path.substring(0, dot);
}
String get field => path.substring(path.indexOf('.') + 1);
}
@JsonEnum(valueField: 'path')
enum DnsOverrideKey with OverrideKey {
enable('enable'),
listen('listen'),
listenRoutingMark('listen-routing-mark'),
useHosts('use-hosts'),
useSystemHosts('use-system-hosts'),
ipv6('ipv6'),
ipv6Timeout('ipv6-timeout'),
respectRules('respect-rules'),
preferH3('prefer-h3'),
cacheAlgorithm('cache-algorithm'),
cacheMaxSize('cache-max-size'),
enhancedMode('enhanced-mode'),
fakeIpRange('fake-ip-range'),
fakeIpRange6('fake-ip-range6'),
fakeIpFilter('fake-ip-filter'),
fakeIpFilterMode('fake-ip-filter-mode'),
fakeIpTtl('fake-ip-ttl'),
defaultNameserver('default-nameserver'),
nameserverPolicy('nameserver-policy'),
nameserver('nameserver'),
fallback('fallback'),
fallbackLazyQuery('fallback-lazy-query'),
proxyServerNameserver('proxy-server-nameserver'),
proxyServerNameserverPolicy('proxy-server-nameserver-policy'),
directNameserver('direct-nameserver'),
directNameserverFollowPolicy('direct-nameserver-follow-policy'),
fallbackFilterGeoip('fallback-filter.geoip'),
fallbackFilterGeoipCode('fallback-filter.geoip-code'),
fallbackFilterGeosite('fallback-filter.geosite'),
fallbackFilterIpcidr('fallback-filter.ipcidr'),
fallbackFilterDomain('fallback-filter.domain');
const DnsOverrideKey(this.path);
@override
final String path;
static const fallbackFilterSection = 'fallback-filter';
/// What a subscription or file profile may override: nothing that names its
/// groups or rule sets, which change with every update.
static const normalProfileKeys = {
listen,
ipv6,
enhancedMode,
fakeIpFilter,
fakeIpFilterMode,
defaultNameserver,
nameserver,
fallback,
proxyServerNameserver,
directNameserver,
};
}
@JsonEnum(valueField: 'path')
enum NtpOverrideKey with OverrideKey {
enable('enable'),
server('server'),
port('port'),
interval('interval'),
dialerProxy('dialer-proxy'),
writeToSystem('write-to-system');
const NtpOverrideKey(this.path);
@override
final String path;
}
@JsonEnum(valueField: 'path')
enum SnifferOverrideKey with OverrideKey {
enable('enable'),
overrideDest('override-destination'),
forceDnsMapping('force-dns-mapping'),
parsePureIp('parse-pure-ip'),
forceDomain('force-domain'),
skipDomain('skip-domain'),
skipSrcAddress('skip-src-address'),
skipDstAddress('skip-dst-address'),
sniffHttp('sniff.HTTP'),
sniffTls('sniff.TLS'),
sniffQuic('sniff.QUIC');
const SnifferOverrideKey(this.path);
@override
final String path;
static const sniffSection = 'sniff';
}
/// Written over the profile's `tun` section, which Android's VPN never reads,
/// so these apply on desktop only, beside the keys of `Tun.coreJson`.
@JsonEnum(valueField: 'path')
enum TunOverrideKey with OverrideKey {
mtu('mtu'),
congestionController('congestion-controller'),
strictRoute('strict-route'),
routeAddress('route-address'),
routeExcludeAddress('route-exclude-address'),
dnsHijack('dns-hijack'),
udpTimeout('udp-timeout'),
disableIcmpForwarding('disable-icmp-forwarding'),
gso('gso'),
includeInterface('include-interface'),
excludeInterface('exclude-interface'),
includeUid('include-uid'),
excludeUid('exclude-uid'),
excludeSrcPort('exclude-src-port'),
excludeDstPort('exclude-dst-port');
const TunOverrideKey(this.path);
@override
final String path;
}
enum ExternalControllerStatus {
@JsonValue('')
close(''),
@JsonValue('127.0.0.1:9090')
open('127.0.0.1:9090');
final String value;
const ExternalControllerStatus(this.value);
}
enum KeyboardModifier {
alt([PhysicalKeyboardKey.altLeft, PhysicalKeyboardKey.altRight]),
capsLock([PhysicalKeyboardKey.capsLock]),
control([PhysicalKeyboardKey.controlLeft, PhysicalKeyboardKey.controlRight]),
fn([PhysicalKeyboardKey.fn]),
meta([PhysicalKeyboardKey.metaLeft, PhysicalKeyboardKey.metaRight]),
shift([PhysicalKeyboardKey.shiftLeft, PhysicalKeyboardKey.shiftRight]);
final List<PhysicalKeyboardKey> physicalKeys;
const KeyboardModifier(this.physicalKeys);
}
enum HotAction {
start,
view,
mode,
proxy,
tun,
ruleMode,
globalMode,
directMode,
delayTest,
updateProfiles,
copyEnv,
exit,
}
enum ProxiesIconStyle { filled, plain, hidden }
enum EditorFontSize {
standard(16),
large(18),
extraLarge(20);
final double value;
const EditorFontSize(this.value);
}
enum AuthorizeCode { none, success, error }
enum TunAuthorizationState { none, authorized, unauthorized }
enum FunctionTag {
updateConfig,
updateGroups,
applyProfile,
savePreferences,
changeProxy,
updateDelay,
autoLaunch,
logs,
requests,
dnsQueries,
loadedProvider,
saveSharedFile,
suspend,
coreErrorNotifier,
dialerLoopNotifier,
reloadPackages,
}
enum DashboardWidget {
currentOutbound,
networkSpeed,
outboundMode,
vpnButton(platforms: [SupportPlatform.Android]),
networkTakeover(platforms: desktopPlatforms),
serviceStatus,
profiles,
proxyGroups,
connectionStats,
intranetIp,
memoryInfo;
final List<SupportPlatform> platforms;
const DashboardWidget({this.platforms = SupportPlatform.values});
}
enum DnsQueryInitiator { app, rule, direct, proxy, other }
enum IpType { residential, mobile, business, hosting }
enum IpQualityLevel { good, normal, risky }
enum IpQualitySource {
identMe('ident.me'),
ipApiCom('ip-api.com'),
ipQuery('ipquery.io'),
ipLocate('iplocate.io'),
proxyCheck('proxycheck.io'),
ipApiIs('ipapi.is');
const IpQualitySource(this.label);
final String label;
}
enum IpQualitySourceStatus { noType, timeout, rateLimited, failed, ipMismatch }
enum GeodataLoader { standard, memconservative }
enum GeoResource {
@JsonValue('mmdb')
MMDB,
@JsonValue('asn')
ASN,
@JsonValue('geoip')
GEOIP,
@JsonValue('geosite')
GEOSITE;
static GeoResource fromJson(String value) {
return switch (value) {
'mmdb' => GeoResource.MMDB,
'asn' => GeoResource.ASN,
'geo-ip' || 'geoip' => GeoResource.GEOIP,
'geo-site' || 'geosite' => GeoResource.GEOSITE,
_ => throw ArgumentError.value(value, 'value', 'Invalid geo resource'),
};
}
}
extension GeoResourceExt on GeoResource {
String get configKey {
return switch (this) {
GeoResource.MMDB => 'mmdb',
GeoResource.ASN => 'asn',
GeoResource.GEOIP => 'geoip',
GeoResource.GEOSITE => 'geosite',
};
}
String get updatingKey => 'geo_resource_$name';
}
enum PageLabel {
dashboard,
proxies,
profiles,
tools,
logs,
requests,
resources,
connections,
dns,
}
enum RuleAction {
DOMAIN('DOMAIN'),
DOMAIN_SUFFIX('DOMAIN-SUFFIX'),
DOMAIN_KEYWORD('DOMAIN-KEYWORD'),
DOMAIN_REGEX('DOMAIN-REGEX'),
DOMAIN_WILDCARD('DOMAIN-WILDCARD'),
GEOSITE('GEOSITE'),
IP_CIDR('IP-CIDR'),
IP_CIDR6('IP-CIDR6'),
IP_SUFFIX('IP-SUFFIX'),
IP_ASN('IP-ASN'),
GEOIP('GEOIP'),
SRC_GEOIP('SRC-GEOIP'),
SRC_IP_ASN('SRC-IP-ASN'),
SRC_IP_CIDR('SRC-IP-CIDR'),
SRC_IP_SUFFIX('SRC-IP-SUFFIX'),
DST_PORT('DST-PORT'),
SRC_PORT('SRC-PORT'),
IN_PORT('IN-PORT'),
IN_TYPE('IN-TYPE'),
IN_USER('IN-USER'),
IN_NAME('IN-NAME'),
REMATCH_NAME('REMATCH-NAME'),
PROCESS_PATH('PROCESS-PATH'),
PROCESS_PATH_REGEX('PROCESS-PATH-REGEX'),
PROCESS_PATH_WILDCARD('PROCESS-PATH-WILDCARD'),
PROCESS_NAME('PROCESS-NAME'),
PROCESS_NAME_REGEX('PROCESS-NAME-REGEX'),
PROCESS_NAME_WILDCARD('PROCESS-NAME-WILDCARD'),
UID('UID'),
NETWORK('NETWORK'),
DSCP('DSCP'),
RULE_SET('RULE-SET'),
AND('AND'),
OR('OR'),
NOT('NOT'),
SUB_RULE('SUB-RULE'),
MATCH('MATCH');
final String value;
const RuleAction(this.value);
static List<RuleAction> get addedRuleActions {
return RuleAction.values
.where(
(item) => ![
RuleAction.MATCH,
RuleAction.RULE_SET,
RuleAction.SUB_RULE,
].contains(item),
)
.toList();
}
}
extension RuleActionExt on RuleAction {
bool get hasParams => [
RuleAction.GEOIP,
RuleAction.IP_ASN,
RuleAction.IP_CIDR,
RuleAction.IP_CIDR6,
RuleAction.IP_SUFFIX,
RuleAction.RULE_SET,
].contains(this);
bool get nestsRules => [
RuleAction.AND,
RuleAction.OR,
RuleAction.NOT,
RuleAction.SUB_RULE,
].contains(this);
bool get hasCommaPayload => [
RuleAction.AND,
RuleAction.OR,
RuleAction.NOT,
RuleAction.SUB_RULE,
RuleAction.DOMAIN_REGEX,
RuleAction.PROCESS_NAME_REGEX,
RuleAction.PROCESS_PATH_REGEX,
].contains(this);
String getDesc(BuildContext context) {
final appLocalizations = context.appLocalizations;
return switch (this) {
RuleAction.DOMAIN => appLocalizations.ruleActionDomainDesc,
RuleAction.DOMAIN_SUFFIX => appLocalizations.ruleActionDomainSuffixDesc,
RuleAction.DOMAIN_KEYWORD => appLocalizations.ruleActionDomainKeywordDesc,
RuleAction.DOMAIN_REGEX => appLocalizations.ruleActionDomainRegexDesc,
RuleAction.DOMAIN_WILDCARD =>
appLocalizations.ruleActionDomainWildcardDesc,
RuleAction.GEOSITE => appLocalizations.ruleActionGeositeDesc,
RuleAction.IP_CIDR => appLocalizations.ruleActionIpCidrDesc,
RuleAction.IP_CIDR6 => appLocalizations.ruleActionIpCidr6Desc,
RuleAction.IP_SUFFIX => appLocalizations.ruleActionIpSuffixDesc,
RuleAction.IP_ASN => appLocalizations.ruleActionIpAsnDesc,
RuleAction.GEOIP => appLocalizations.ruleActionGeoipDesc,
RuleAction.SRC_GEOIP => appLocalizations.ruleActionSrcGeoipDesc,
RuleAction.SRC_IP_ASN => appLocalizations.ruleActionSrcIpAsnDesc,
RuleAction.SRC_IP_CIDR => appLocalizations.ruleActionSrcIpCidrDesc,
RuleAction.SRC_IP_SUFFIX => appLocalizations.ruleActionSrcIpSuffixDesc,
RuleAction.DST_PORT => appLocalizations.ruleActionDstPortDesc,
RuleAction.SRC_PORT => appLocalizations.ruleActionSrcPortDesc,
RuleAction.IN_PORT => appLocalizations.ruleActionInPortDesc,
RuleAction.IN_TYPE => appLocalizations.ruleActionInTypeDesc,
RuleAction.IN_USER => appLocalizations.ruleActionInUserDesc,
RuleAction.IN_NAME => appLocalizations.ruleActionInNameDesc,
RuleAction.REMATCH_NAME => appLocalizations.ruleActionRematchNameDesc,
RuleAction.PROCESS_PATH => appLocalizations.ruleActionProcessPathDesc,
RuleAction.PROCESS_PATH_REGEX =>
appLocalizations.ruleActionProcessPathRegexDesc,
RuleAction.PROCESS_PATH_WILDCARD =>
appLocalizations.ruleActionProcessPathWildcardDesc,
RuleAction.PROCESS_NAME => appLocalizations.ruleActionProcessNameDesc,
RuleAction.PROCESS_NAME_REGEX =>
appLocalizations.ruleActionProcessNameRegexDesc,
RuleAction.PROCESS_NAME_WILDCARD =>
appLocalizations.ruleActionProcessNameWildcardDesc,
RuleAction.UID => appLocalizations.ruleActionUidDesc,
RuleAction.NETWORK => appLocalizations.ruleActionNetworkDesc,
RuleAction.DSCP => appLocalizations.ruleActionDscpDesc,
RuleAction.RULE_SET => appLocalizations.ruleActionRuleSetDesc,
RuleAction.AND => appLocalizations.ruleActionAndDesc,
RuleAction.OR => appLocalizations.ruleActionOrDesc,
RuleAction.NOT => appLocalizations.ruleActionNotDesc,
RuleAction.SUB_RULE => appLocalizations.ruleActionSubRuleDesc,
RuleAction.MATCH => appLocalizations.ruleActionMatchDesc,
};
}
}
enum ProxyFieldKind { text, secret, number, toggle, choice, list }
/// A key of a proxy definition the form edits, at [path] since the core reads
/// transport, TLS and plugin settings from maps of their own.
enum ProxyField {
username(['username']),
password(['password'], ProxyFieldKind.secret),
uuid(['uuid']),
psk(['psk'], ProxyFieldKind.secret),
key(['key'], ProxyFieldKind.secret),
privateKey(['private-key'], ProxyFieldKind.secret),
publicKey(['public-key']),
privateKeyPassphrase(['private-key-passphrase'], ProxyFieldKind.secret),
preSharedKey(['pre-shared-key'], ProxyFieldKind.secret),
authStr(['auth-str'], ProxyFieldKind.secret),
cipher(['cipher'], ProxyFieldKind.choice),
alterId(['alterId'], ProxyFieldKind.number),
protocol(['protocol'], ProxyFieldKind.choice),
obfs(['obfs'], ProxyFieldKind.choice),
transport(['transport'], ProxyFieldKind.choice),
up(['up']),
down(['down']),
network(['network'], ProxyFieldKind.choice),
flow(['flow'], ProxyFieldKind.choice),
wsPath(['ws-opts', 'path']),
wsHost(['ws-opts', 'headers', 'Host']),
grpcServiceName(['grpc-opts', 'grpc-service-name']),
xhttpPath(['xhttp-opts', 'path']),
xhttpHost(['xhttp-opts', 'host']),
tls(['tls'], ProxyFieldKind.toggle),
sni(['sni']),
servername(['servername']),
alpn(['alpn'], ProxyFieldKind.list),
skipCertVerify(['skip-cert-verify'], ProxyFieldKind.toggle),
clientFingerprint(['client-fingerprint'], ProxyFieldKind.choice),
realityPublicKey(['reality-opts', 'public-key']),
realityShortId(['reality-opts', 'short-id']),
plugin(['plugin'], ProxyFieldKind.choice),
pluginMode(['plugin-opts', 'mode']),
pluginHost(['plugin-opts', 'host']),
udpOverTcp(['udp-over-tcp'], ProxyFieldKind.toggle),
protocolParam(['protocol-param']),
obfsParam(['obfs-param']),
obfsPassword(['obfs-password'], ProxyFieldKind.secret),
obfsSecret(['obfs'], ProxyFieldKind.secret),
obfsMode(['obfs-opts', 'mode'], ProxyFieldKind.choice),
obfsHost(['obfs-opts', 'host']),
version(['version'], ProxyFieldKind.number),
ports(['ports']),
hopInterval(['hop-interval']),
congestionController(['congestion-controller'], ProxyFieldKind.choice),
udpRelayMode(['udp-relay-mode'], ProxyFieldKind.choice),
reduceRtt(['reduce-rtt'], ProxyFieldKind.toggle),
ip(['ip']),
ipv6(['ipv6']),
allowedIps(['allowed-ips'], ProxyFieldKind.list),
dns(['dns'], ProxyFieldKind.list),
remoteDnsResolve(['remote-dns-resolve'], ProxyFieldKind.toggle),
mtu(['mtu'], ProxyFieldKind.number),
tfo(['tfo'], ProxyFieldKind.toggle),
mptcp(['mptcp'], ProxyFieldKind.toggle),
interfaceName(['interface-name']),
routingMark(['routing-mark'], ProxyFieldKind.number),
ipVersion(['ip-version'], ProxyFieldKind.choice);
final List<String> path;
final ProxyFieldKind kind;
const ProxyField(this.path, [this.kind = ProxyFieldKind.text]);
}
enum RulePayloadError { network, numberRange, dscpRange }
extension RulePayloadErrorExt on RulePayloadError {
String getMessage(BuildContext context) {
final appLocalizations = context.appLocalizations;
return switch (this) {
RulePayloadError.network => appLocalizations.invalidNetworkContent,
RulePayloadError.numberRange => appLocalizations.invalidRangeContent,
RulePayloadError.dscpRange => appLocalizations.invalidDscpContent,
};
}
}
enum ExtendType { standard, script }
enum RuleTarget {
DIRECT('DIRECT'),
REJECT('REJECT'),
REJECT_DROP('REJECT-DROP'),
PASS('PASS'),
PASS_RULE('PASS-RULE'),
COMPATIBLE('COMPATIBLE');
final String value;
const RuleTarget(this.value);
static final List<String> baseTargetNames = List.unmodifiable(
RuleTarget.values.map((item) => item.value),
);
static final Set<String> baseTargets = Set.unmodifiable(baseTargetNames);
/// The core adds GLOBAL only after it parses the groups, so a rule, a dialer
/// or a DNS server may name it while a group may not.
static final List<String> ruleTargetNames = List.unmodifiable([
...baseTargetNames,
GroupName.GLOBAL.name,
]);
static final Set<String> ruleTargets = Set.unmodifiable(ruleTargetNames);
}
enum ProviderKind { proxy, rule }
enum RuleProviderBehavior { domain, ipcidr, classical }
enum RuleProviderFormat { yaml, text, mrs }
enum RestoreStrategy { compatible, override }
enum TabAnimation { slide, fade }
enum LiquidGlassMode { none, standard, ultra }
enum Language { yaml, javaScript, json }
enum ScrollPositionCacheKey { proxiesTabList }
enum QueryTag { proxies, access }
enum LoadingTag {
profiles,
scripts,
ruleProviders,
iconSets,
backup_restore,
access,
proxies,
checkUpdate,
}
enum CoreStatus { connecting, connected, disconnected }
enum UpdatingScope { core, local }