Refresh code base

Signed-off-by: Loren Eteval <loren.eteval@proton.me>
This commit is contained in:
Loren Eteval
2025-11-27 10:38:13 +08:00
parent caf841b87a
commit c87cf9267d
92 changed files with 70491 additions and 76085 deletions
+1 -1
View File
@@ -17,7 +17,7 @@
from __future__ import annotations
from Furious.Utility import *
from Furious.Frozenlib import *
import os
import re
+245 -233
View File
@@ -17,13 +17,19 @@
from __future__ import annotations
from Furious.Frozenlib import *
from Furious.Interface import *
from Furious.PyFramework import *
from Furious.QtFramework import *
from Furious.QtFramework import gettext as _
from Furious.Library import *
from Furious.Qt import *
from Furious.Qt import gettext as _
from Furious.Utility import *
from Furious.Core import *
from Furious.Core.CoreProcessWorker import *
from Furious.Core.XrayCore import *
from Furious.Core.Hysteria1 import *
from Furious.Core.Hysteria2 import *
from Furious.Core.Tun2socks import *
from typing import Tuple, Union
import uuid
import logging
@@ -35,7 +41,7 @@ __all__ = ['CoreManager']
logger = logging.getLogger(__name__)
def fixLogObjectPath(config: ConfigurationFactory, attr: str, value: str, log=True):
def fixLogObjectPath(config: ConfigFactory, attr: str, value: str, log=True):
try:
path = config['log'][attr]
except Exception:
@@ -47,15 +53,15 @@ def fixLogObjectPath(config: ConfigurationFactory, attr: str, value: str, log=Tr
config['log'][attr] = path = ''
if path == '':
if isPythonw() and StdoutRedirectHelper.TemporaryDir.isValid():
if SystemRuntime.isPythonw() and ProcessOutputRedirector.TemporaryDir.isValid():
# Redirect implementation for pythonw environment
config['log'][attr] = StdoutRedirectHelper.TemporaryDir.filePath(value)
config['log'][attr] = ProcessOutputRedirector.TemporaryDir.filePath(value)
else:
# Relative path fails if booting on start up
# on Windows, when packed using nuitka...
# Fix relative path if needed. User cannot feel this operation.
config['log'][attr] = getAbsolutePath(path)
config['log'][attr] = absolutePath(path)
result = config['log'][attr]
@@ -79,44 +85,222 @@ def fixLogObjectPath(config: ConfigurationFactory, attr: str, value: str, log=Tr
def getUserTUNSettings(*args, **kwargs):
return AS_UserTUNSettings().get(*args, **kwargs)
return Storage.UserTUNSettings().get(*args, **kwargs)
userPrimaryAdapterInterfaceName = functools.partial(
getUserTUNSettings, 'primaryAdapterInterfaceName', ''
)
userPrimaryAdapterInterfaceIP = functools.partial(
getUserTUNSettings, 'primaryAdapterInterfaceIP', ''
)
userDefaultPrimaryGatewayIP = functools.partial(
getUserTUNSettings, 'defaultPrimaryGatewayIP', ''
)
userTunAdapterInterfaceDNS = functools.partial(
getUserTUNSettings, 'tunAdapterInterfaceDNS', ''
)
userBypassTUNAdapterInterfaceIP = functools.partial(
getUserTUNSettings, 'bypassTUNAdapterInterfaceIP', ''
)
userDisablePrimaryAdapterInterfaceDNS = functools.partial(
getUserTUNSettings, 'disablePrimaryAdapterInterfaceDNS', 'True'
(
userPrimaryAdapterInterfaceName,
userPrimaryAdapterInterfaceIP,
userDefaultPrimaryGatewayIP,
userTunAdapterInterfaceDNS,
userBypassTUNAdapterInterfaceIP,
userDisablePrimaryAdapterInterfaceDNS,
) = (
functools.partial(getUserTUNSettings, 'primaryAdapterInterfaceName', ''),
functools.partial(getUserTUNSettings, 'primaryAdapterInterfaceIP', ''),
functools.partial(getUserTUNSettings, 'defaultPrimaryGatewayIP', ''),
functools.partial(getUserTUNSettings, 'tunAdapterInterfaceDNS', ''),
functools.partial(getUserTUNSettings, 'bypassTUNAdapterInterfaceIP', ''),
functools.partial(getUserTUNSettings, 'disablePrimaryAdapterInterfaceDNS', 'True'),
)
class CoreManager(SupportExitCleanup):
class CoreManager(Mixins.CleanupOnExit):
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self.uniqueCleanup = False
self.processesPool = list()
self.coresPool = []
@functools.singledispatchmethod
def _startCore(
self,
config,
routing,
exitCallback=None,
msgCallback=None,
proxyModeOnly=False,
log=True,
**kwargs,
) -> Tuple[Union[CoreProcessWorker, None], bool]:
return None, False
@_startCore.register(ConfigXray)
def _(
self,
config,
routing,
exitCallback=None,
msgCallback=None,
proxyModeOnly=False,
log=True,
**kwargs,
):
if config.get('log') is None or not isinstance(config['log'], dict):
config['log'] = {
'access': '',
'error': '',
'loglevel': 'warning',
}
logRedirectValue = str(uuid.uuid4())
# Fix logObject
for attr in ['access', 'error']:
fixLogObjectPath(config, attr, logRedirectValue, log)
if routing == 'Bypass Mainland China':
# TUN Mode handling
if not proxyModeOnly and SystemRuntime.isTUNMode():
mbox = AppQMessageBox(icon=AppQMessageBox.Icon.Critical)
mbox.setWindowTitle(_('Unable to connect'))
mbox.setText(
_('Routing option with direct rules is not allowed in TUN mode')
)
# Show the MessageBox asynchronously
mbox.open()
return False
routingObject = {
'domainStrategy': 'IPIfNonMatch',
'domainMatcher': 'hybrid',
'rules': [
{
'type': 'field',
'domain': [
'geosite:category-ads-all',
],
'outboundTag': 'block',
},
{
'type': 'field',
'domain': [
'geosite:cn',
],
'outboundTag': 'direct',
},
{
'type': 'field',
'ip': [
'geoip:private',
'geoip:cn',
],
'outboundTag': 'direct',
},
{
'type': 'field',
'port': '0-65535',
'outboundTag': 'proxy',
},
],
}
elif routing == 'Global':
routingObject = {}
elif routing == 'Custom':
routingObject = config.get('routing', {})
else:
routingObject = {}
if log:
logger.info(f'core {XrayCore.name()} configured')
logger.info(f'routing is {routing}')
logger.info(f'RoutingObject: {routingObject}')
config['routing'] = routingObject
process = XrayCore(exitCallback=exitCallback, msgCallback=msgCallback)
success = process.start(config, **kwargs)
return process, success
@_startCore.register(ConfigHysteria1)
def _(
self,
config,
routing,
exitCallback=None,
msgCallback=None,
proxyModeOnly=False,
log=True,
**kwargs,
):
if routing == 'Bypass Mainland China':
# TUN Mode handling
if not proxyModeOnly and SystemRuntime.isTUNMode():
mbox = AppQMessageBox(icon=AppQMessageBox.Icon.Critical)
mbox.setWindowTitle(_('Unable to connect'))
mbox.setText(
_('Routing option with direct rules is not allowed in TUN mode')
)
# Show the MessageBox asynchronously
mbox.open()
return False
routingObject = {
'rule': DATA_DIR / 'hysteria' / 'bypass-mainland-China.acl',
'mmdb': DATA_DIR / 'hysteria' / 'country.mmdb',
}
elif routing == 'Global':
routingObject = {
'rule': '',
'mmdb': '',
}
elif routing == 'Custom':
routingObject = {
'rule': config.get('acl', ''),
'mmdb': config.get('mmdb', ''),
}
else:
routingObject = {
'rule': '',
'mmdb': '',
}
if log:
logger.info(f'core {Hysteria1.name()} configured')
logger.info(f'routing is {routing}')
logger.info(f'RoutingObject: {routingObject}')
process = Hysteria1(exitCallback=exitCallback, msgCallback=msgCallback)
success = process.start(
config,
Hysteria1.rule(routingObject.get('rule', '')),
Hysteria1.mmdb(routingObject.get('mmdb', '')),
**kwargs,
)
return process, success
@_startCore.register(ConfigHysteria2)
def _(
self,
config,
routing,
exitCallback=None,
msgCallback=None,
proxyModeOnly=False,
log=True,
**kwargs,
):
if log:
logger.info(f'core {Hysteria2.name()} configured')
process = Hysteria2(exitCallback=exitCallback, msgCallback=msgCallback)
success = process.start(config, **kwargs)
return process, success
def start(
self,
config: ConfigurationFactory,
config: ConfigFactory,
routing: str,
exitCallback=None,
msgCallbackCore=None,
msgCallbackTun_=None,
msgCallbackTUN_=None,
deepcopy=True,
proxyModeOnly=False,
log=True,
@@ -127,197 +311,27 @@ class CoreManager(SupportExitCleanup):
else:
configcopy = config
if isinstance(configcopy, ConfigurationXray):
if configcopy.get('log') is None or not isinstance(configcopy['log'], dict):
configcopy['log'] = {
'access': '',
'error': '',
'loglevel': 'warning',
}
process, success = self._startCore(
configcopy,
routing,
exitCallback,
msgCallbackCore,
proxyModeOnly,
log,
**kwargs,
)
logRedirectValue = str(uuid.uuid4())
# Fix logObject
for attr in ['access', 'error']:
fixLogObjectPath(configcopy, attr, logRedirectValue, log)
if routing == 'Bypass Mainland China':
# TUN Mode handling
if not proxyModeOnly and isTUNMode():
mbox = AppQMessageBox(icon=AppQMessageBox.Icon.Critical)
mbox.setWindowTitle(_('Unable to connect'))
mbox.setText(
_('Routing option with direct rules is not allowed in TUN mode')
)
# Show the MessageBox asynchronously
mbox.open()
return False
routingObject = {
'domainStrategy': 'IPIfNonMatch',
'domainMatcher': 'hybrid',
'rules': [
{
'type': 'field',
'domain': [
'geosite:category-ads-all',
],
'outboundTag': 'block',
},
{
'type': 'field',
'domain': [
'geosite:cn',
],
'outboundTag': 'direct',
},
{
'type': 'field',
'ip': [
'geoip:private',
'geoip:cn',
],
'outboundTag': 'direct',
},
{
'type': 'field',
'port': '0-65535',
'outboundTag': 'proxy',
},
],
}
# elif routing == 'Bypass Iran':
# routingObject = {
# 'domainStrategy': 'IPIfNonMatch',
# 'domainMatcher': 'hybrid',
# 'rules': [
# {
# 'type': 'field',
# 'domain': [
# 'geosite:category-ads-all',
# 'iran:ads',
# ],
# 'outboundTag': 'block',
# },
# {
# 'type': 'field',
# 'domain': [
# 'iran:ir',
# 'iran:other',
# ],
# 'outboundTag': 'direct',
# },
# {
# 'type': 'field',
# 'ip': [
# 'geoip:private',
# 'geoip:ir',
# ],
# 'outboundTag': 'direct',
# },
# {
# 'type': 'field',
# 'port': '0-65535',
# 'outboundTag': 'proxy',
# },
# ],
# }
elif routing == 'Global':
routingObject = {}
elif routing == 'Custom':
routingObject = configcopy.get('routing', {})
else:
routingObject = {}
if log:
logger.info(f'core {XrayCore.name()} configured')
logger.info(f'routing is {routing}')
logger.info(f'RoutingObject: {routingObject}')
configcopy['routing'] = routingObject
coreProcess = XrayCore(
exitCallback=exitCallback, msgCallback=msgCallbackCore
)
success = coreProcess.start(configcopy, **kwargs)
elif isinstance(configcopy, ConfigurationHysteria1):
if routing == 'Bypass Mainland China':
# TUN Mode handling
if not proxyModeOnly and isTUNMode():
mbox = AppQMessageBox(icon=AppQMessageBox.Icon.Critical)
mbox.setWindowTitle(_('Unable to connect'))
mbox.setText(
_('Routing option with direct rules is not allowed in TUN mode')
)
# Show the MessageBox asynchronously
mbox.open()
return False
routingObject = {
'rule': DATA_DIR / 'hysteria' / 'bypass-mainland-China.acl',
'mmdb': DATA_DIR / 'hysteria' / 'country.mmdb',
}
# elif routing == 'Bypass Iran':
# routingObject = {
# 'rule': DATA_DIR / 'hysteria' / 'bypass-Iran.acl',
# 'mmdb': DATA_DIR / 'hysteria' / 'country.mmdb',
# }
elif routing == 'Global':
routingObject = {
'rule': '',
'mmdb': '',
}
elif routing == 'Custom':
routingObject = {
'rule': configcopy.get('acl', ''),
'mmdb': configcopy.get('mmdb', ''),
}
else:
routingObject = {
'rule': '',
'mmdb': '',
}
if log:
logger.info(f'core {Hysteria1.name()} configured')
logger.info(f'routing is {routing}')
logger.info(f'RoutingObject: {routingObject}')
coreProcess = Hysteria1(
exitCallback=exitCallback, msgCallback=msgCallbackCore
)
success = coreProcess.start(
configcopy,
Hysteria1.rule(routingObject.get('rule', '')),
Hysteria1.mmdb(routingObject.get('mmdb', '')),
**kwargs,
)
elif isinstance(configcopy, ConfigurationHysteria2):
if log:
logger.info(f'core {Hysteria2.name()} configured')
coreProcess = Hysteria2(
exitCallback=exitCallback, msgCallback=msgCallbackCore
)
success = coreProcess.start(configcopy, **kwargs)
else:
coreProcess = None
success = False
if coreProcess is not None:
self.coresPool.append(coreProcess)
if process is not None:
self.processesPool.append(process)
if not success:
logger.error(f'core {coreProcess.name()} start failed')
if isinstance(process, CoreProcessWorker):
logger.error(f'core {process.name()} start failed')
return success
# TUN Mode handling
if not proxyModeOnly and isTUNMode():
if not proxyModeOnly and SystemRuntime.isTUNMode():
# Currently TUN Mode is only supported on Windows and macOS
if PLATFORM == 'Windows' or PLATFORM == 'Darwin':
if PLATFORM == 'Windows':
@@ -368,16 +382,14 @@ class CoreManager(SupportExitCleanup):
else:
gateway, interfaceIP = defaultGateway[0], None
tunProcess = Tun2socks(
exitCallback=exitCallback, msgCallback=msgCallbackTun_
)
self.coresPool.append(tunProcess)
tun = Tun2socks(exitCallback=exitCallback, msgCallback=msgCallbackTUN_)
self.processesPool.append(tun)
if not tunProcess.start(
if not tun.start(
APPLICATION_TUN_DEVICE_NAME,
APPLICATION_TUN_NETWORK_INTERFACE_NAME,
'error',
f'socks5://{configcopy.socksProxyEndpoint()}',
f'socks5://{configcopy.socksProxy()}',
'',
):
return False
@@ -392,7 +404,7 @@ class CoreManager(SupportExitCleanup):
# Any non-exit exceptions
logger.error(
f'error when processing ' f'user TUN bypass Settings: {ex}'
f'error when processing user TUN bypass settings: {ex}'
)
SystemRoutingTable.Relations.clear()
@@ -407,7 +419,7 @@ class CoreManager(SupportExitCleanup):
else:
logger.error(
f'invalid IP address when processing '
f'user TUN bypass Settings: {bypass}'
f'user TUN bypass settings: {bypass}'
)
SystemRoutingTable.Relations.clear()
@@ -422,7 +434,7 @@ class CoreManager(SupportExitCleanup):
address = configcopy.itemAddress
if not isValidIPAddress(address):
DNSResolver.configureHttpProxy(configcopy.httpProxyEndpoint())
DNSResolver.configureHttpProxy(configcopy.httpProxy())
error, resolved = DNSResolver.resolve(address)
@@ -466,7 +478,7 @@ class CoreManager(SupportExitCleanup):
break
PySide6LegacyEventLoopWait(100)
PySide6Legacy.eventLoopWait(100)
if not foundDevice:
logger.error(
@@ -501,7 +513,7 @@ class CoreManager(SupportExitCleanup):
SystemRoutingTable.WIN32SetInterfaceDNS(_alias)
SystemRoutingTable.WIN32FlushDNSCache()
tunProcess.cleanup = functools.partial(_windowsCleanup, alias)
tun.cleanup = functools.partial(_windowsCleanup, alias)
# Handle user defined settings
userDisableInterfaceDNS = (
@@ -556,7 +568,7 @@ class CoreManager(SupportExitCleanup):
for _service, _dnsserver in _servers:
SystemRoutingTable.DarwinSetDNSServers(_service, _dnsserver)
tunProcess.cleanup = functools.partial(_darwinCleanup, servers)
tun.cleanup = functools.partial(_darwinCleanup, servers)
# Handle user defined settings
userTunInterfaceDNS = userTunAdapterInterfaceDNS()
@@ -585,18 +597,18 @@ class CoreManager(SupportExitCleanup):
return True
def allRunning(self) -> bool:
return all(coreProcess.isAlive() for coreProcess in self.coresPool)
return all(process.isAlive() for process in self.processesPool)
def anyRunning(self) -> bool:
return any(coreProcess.isAlive() for coreProcess in self.coresPool)
return any(process.isAlive() for process in self.processesPool)
def stopAll(self):
if self.coresPool:
for coreProcess in self.coresPool:
if isinstance(coreProcess, CoreFactory):
coreProcess.stop()
if self.processesPool:
for process in self.processesPool:
if isinstance(process, CoreProcessFactory):
process.stop()
self.coresPool.clear()
self.processesPool.clear()
def cleanup(self):
self.stopAll()
@@ -17,6 +17,7 @@
from __future__ import annotations
from Furious.Frozenlib import *
from Furious.Interface import *
from Furious.Utility import *
@@ -34,55 +35,11 @@ import threading
import multiprocessing
import multiprocessing.queues
__all__ = ['CoreProcess', 'MsgQueue', 'StdoutRedirectHelper']
__all__ = ['MsgQueue', 'CoreProcessWorker', 'ProcessOutputRedirector']
logger = logging.getLogger(__name__)
class CoreProcessDaemon(CoreFactory, ABC):
def __init__(self, **kwargs):
exitCallback = kwargs.pop('exitCallback', None)
super().__init__(exitCallback)
self._process = None
self._daemon = QtCore.QTimer()
self._daemon.timeout.connect(self.queryIsAlive)
@property
def process(self) -> Union[multiprocessing.Process, None]:
return self._process
@process.setter
def process(self, process: Union[multiprocessing.Process, None]):
self._process = process
@property
def daemon(self) -> QtCore.QTimer:
return self._daemon
def isAlive(self) -> bool:
if isinstance(self.process, multiprocessing.Process):
return self.process.is_alive()
else:
return False
def queryIsAlive(self):
if isinstance(self.process, multiprocessing.Process):
if self.process.is_alive():
return True
else:
self.handleInteralProcessStopped()
return False
else:
return False
def handleInteralProcessStopped(self, *args, **kwargs):
raise NotImplementedError
class MsgQueue(multiprocessing.queues.Queue):
MSG_PRODUCE_THRESHOLD = 1024
OPTIMIZER_MIN_FREQ = 2
@@ -165,11 +122,55 @@ class MsgQueue(multiprocessing.queues.Queue):
self.startTimer()
class CoreProcess(CoreProcessDaemon):
class CoreProcessMonitor(CoreProcessFactory, ABC):
def __init__(self, **kwargs):
exitCallback = kwargs.pop('exitCallback', None)
super().__init__(exitCallback)
self._process = None
self._daemon = QtCore.QTimer()
self._daemon.timeout.connect(self.queryIsAlive)
@property
def process(self) -> Union[multiprocessing.Process, None]:
return self._process
@process.setter
def process(self, process: Union[multiprocessing.Process, None]):
self._process = process
@property
def daemon(self) -> QtCore.QTimer:
return self._daemon
def isAlive(self) -> bool:
if isinstance(self.process, multiprocessing.Process):
return self.process.is_alive()
else:
return False
def queryIsAlive(self):
if isinstance(self.process, multiprocessing.Process):
if self.process.is_alive():
return True
else:
self.handleInteralProcessStopped()
return False
else:
return False
def handleInteralProcessStopped(self, *args, **kwargs):
raise NotImplementedError
class CoreProcessWorker(CoreProcessMonitor, ABC):
def __init__(self, **kwargs):
msgCallback = kwargs.pop('msgCallback', None)
# Optimizer is loggerCore window
backgroundOptimizer = kwargs.pop('backgroundOptimizer', loggerCore)
# Optimizer is AppLoggerWindow.Core window
backgroundOptimizer = kwargs.pop('backgroundOptimizer', AppLoggerWindow.Core)
super().__init__(**kwargs)
@@ -205,7 +206,7 @@ class CoreProcess(CoreProcessDaemon):
if waitCore:
# Wait for the core to start up completely
PySide6LegacyEventLoopWait(waitTime)
PySide6Legacy.eventLoopWait(waitTime)
if self.queryIsAlive():
# Start core daemon
@@ -231,7 +232,7 @@ class CoreProcess(CoreProcessDaemon):
pass
class StdoutRedirectHelper:
class ProcessOutputRedirector:
TemporaryDir = QtCore.QTemporaryDir()
@staticmethod
@@ -242,17 +243,17 @@ class StdoutRedirectHelper:
return
if (
not StdoutRedirectHelper.TemporaryDir.isValid()
not ProcessOutputRedirector.TemporaryDir.isValid()
or not redirect
# pythonw.exe
or isPythonw()
or SystemRuntime.isPythonw()
):
# Call entrypoint directly
entrypoint()
return
temporaryFile = StdoutRedirectHelper.TemporaryDir.filePath(str(uuid.uuid4()))
temporaryFile = ProcessOutputRedirector.TemporaryDir.filePath(str(uuid.uuid4()))
tmpFileStream = open(temporaryFile, 'w+b')
stdoutFileno_ = sys.stdout.fileno()
stderrFileno_ = sys.stderr.fileno()
+21 -31
View File
@@ -17,11 +17,10 @@
from __future__ import annotations
from Furious.Interface import *
from Furious.QtFramework import *
from Furious.Library import *
from Furious.Utility import *
from Furious.Frozenlib import *
from Furious.Core.CoreProcessWorker import *
from enum import Enum
from typing import Union
import time
@@ -40,22 +39,22 @@ def startHysteria1(jsonString, rule, mmdb, msgQueue: multiprocessing.Queue):
except ImportError:
# Fake running process
while True:
time.sleep(1)
time.sleep(3600)
else:
if versionToValue(hysteria.__version__) <= versionToValue('1.3.5'):
redirect = False
else:
redirect = True
StdoutRedirectHelper.launch(
ProcessOutputRedirector.launch(
msgQueue,
functools.partial(hysteria.startFromJSON, jsonString, rule, mmdb),
redirect,
)
class Hysteria1(CoreProcess):
class ExitCode:
class Hysteria1(CoreProcessWorker):
class ExitCode(Enum):
ConfigurationError = 23
RemoteNetworkError = 3
# Windows shutting down
@@ -70,7 +69,7 @@ class Hysteria1(CoreProcess):
return ''
try:
path = getAbsolutePath(str(rulePath))
path = absolutePath(str(rulePath))
except Exception:
# Any non-exit exceptions
@@ -100,7 +99,7 @@ class Hysteria1(CoreProcess):
return ''
try:
path = getAbsolutePath(str(mmdbPath))
path = absolutePath(str(mmdbPath))
except Exception:
# Any non-exit exceptions
@@ -139,28 +138,19 @@ class Hysteria1(CoreProcess):
return '0.0.0'
def startFromArgs(self, jsonString: str, rule, mmdb, **kwargs) -> bool:
self.registerCurrentJSONConfig(jsonString)
return super().start(
target=startHysteria1,
args=(jsonString, rule, mmdb, self.msgQueue),
**kwargs,
)
def start(self, config: Union[str, dict], rule, mmdb, **kwargs) -> bool:
if isinstance(config, str):
return self.startFromArgs(config, rule, mmdb, **kwargs)
elif isinstance(config, ConfigurationFactory):
return self.startFromArgs(config.toJSONString(), rule, mmdb, **kwargs)
elif isinstance(config, dict):
try:
jsonString = UJSONEncoder.encode(config)
except Exception:
# Any non-exit exceptions
param = self.toJSONString(config)
return False
else:
return self.startFromArgs(jsonString, rule, mmdb, **kwargs)
if param:
return super().start(
target=startHysteria1,
args=(
param,
rule,
mmdb,
self.msgQueue,
),
**kwargs,
)
else:
return False
+17 -27
View File
@@ -17,11 +17,10 @@
from __future__ import annotations
from Furious.Interface import *
from Furious.QtFramework import *
from Furious.Library import *
from Furious.Utility import *
from Furious.Frozenlib import *
from Furious.Core.CoreProcessWorker import *
from enum import Enum
from typing import Union
import time
@@ -40,22 +39,22 @@ def startHysteria2(jsonString: str, msgQueue: multiprocessing.Queue):
except ImportError:
# Fake running process
while True:
time.sleep(1)
time.sleep(3600)
else:
if versionToValue(hysteria2.__version__) <= versionToValue('2.0.0.1'):
redirect = False
else:
redirect = True
StdoutRedirectHelper.launch(
ProcessOutputRedirector.launch(
msgQueue,
functools.partial(hysteria2.startFromJSON, jsonString),
redirect,
)
class Hysteria2(CoreProcess):
class ExitCode:
class Hysteria2(CoreProcessWorker):
class ExitCode(Enum):
ConfigurationError = 23
# Windows: 4294967295. Darwin, Linux: 255 (-1)
ServerStartFailure = 4294967295 if PLATFORM == 'Windows' else 255
@@ -80,26 +79,17 @@ class Hysteria2(CoreProcess):
return '0.0.0'
def startFromArgs(self, jsonString: str, **kwargs) -> bool:
self.registerCurrentJSONConfig(jsonString)
return super().start(
target=startHysteria2, args=(jsonString, self.msgQueue), **kwargs
)
def start(self, config: Union[str, dict], **kwargs) -> bool:
if isinstance(config, str):
return self.startFromArgs(config, **kwargs)
elif isinstance(config, ConfigurationFactory):
return self.startFromArgs(config.toJSONString(), **kwargs)
elif isinstance(config, dict):
try:
jsonString = UJSONEncoder.encode(config)
except Exception:
# Any non-exit exceptions
param = self.toJSONString(config)
return False
else:
return self.startFromArgs(jsonString, **kwargs)
if param:
return super().start(
target=startHysteria2,
args=(
param,
self.msgQueue,
),
**kwargs,
)
else:
return False
+15 -10
View File
@@ -17,10 +17,8 @@
from __future__ import annotations
from Furious.Interface import *
from Furious.QtFramework import *
from Furious.Library import *
from Furious.Utility import *
from Furious.Frozenlib import *
from Furious.Core.CoreProcessWorker import *
import time
import functools
@@ -35,28 +33,28 @@ def startTun2socks(msgQueue: multiprocessing.Queue, *args):
except ImportError:
# Fake running process
while True:
time.sleep(1)
time.sleep(3600)
else:
if versionToValue(tun2socks.__version__) <= versionToValue('2.5.1.1'):
redirect = False
else:
redirect = True
StdoutRedirectHelper.launch(
ProcessOutputRedirector.launch(
msgQueue,
functools.partial(tun2socks.startFromArgs, *args),
redirect,
)
class Tun2socks(CoreProcess):
class Tun2socks(CoreProcessWorker):
class ExitCode:
# Windows shutting down
SystemShuttingDown = 0x40010004
def __init__(self, **kwargs):
# Optimizer is loggerTun_ window
backgroundOptimizer = kwargs.pop('backgroundOptimizer', loggerTun_)
# Optimizer is AppLoggerWindow.TUN_ window
backgroundOptimizer = kwargs.pop('backgroundOptimizer', AppLoggerWindow.TUN_)
super().__init__(**kwargs, backgroundOptimizer=backgroundOptimizer)
@@ -88,7 +86,14 @@ class Tun2socks(CoreProcess):
) -> bool:
return super().start(
target=startTun2socks,
args=(self.msgQueue, device, networkInterface, logLevel, proxy, restAPI),
args=(
self.msgQueue,
device,
networkInterface,
logLevel,
proxy,
restAPI,
),
**kwargs,
)
+62 -74
View File
@@ -17,11 +17,13 @@
from __future__ import annotations
from Furious.Frozenlib import *
from Furious.Interface import *
from Furious.QtFramework import *
from Furious.Library import *
from Furious.Utility import *
from Furious.Core.CoreProcessWorker import *
from enum import Enum
from typing import Union
import io
@@ -39,7 +41,7 @@ def startXrayCore(jsonString: str, msgQueue: multiprocessing.Queue):
except ImportError:
# Fake running process
while True:
time.sleep(1)
time.sleep(3600)
else:
if versionToValue(xray.__version__) <= versionToValue('1.8.4'):
redirect = False
@@ -47,8 +49,8 @@ def startXrayCore(jsonString: str, msgQueue: multiprocessing.Queue):
# Can be redirected
redirect = True
if not isPythonw():
return StdoutRedirectHelper.launch(
if not SystemRuntime.isPythonw():
return ProcessOutputRedirector.launch(
msgQueue,
functools.partial(xray.startFromJSON, jsonString),
redirect,
@@ -57,70 +59,65 @@ def startXrayCore(jsonString: str, msgQueue: multiprocessing.Queue):
if not redirect:
return xray.startFromJSON(jsonString)
def xrayPythonwProduceMsg():
try:
jsonObject = UJSONEncoder.decode(jsonString)
except Exception:
# Any non-exit exceptions
try:
jsonObject = UJSONEncoder.decode(jsonString)
except Exception:
# Any non-exit exceptions
xray.startFromJSON(jsonString)
else:
loggingPath = []
fileStreams = []
xray.startFromJSON(jsonString)
else:
loggingPath = list()
fileStreams = list()
for loggingAttr in ['access', 'error']:
try:
path = jsonObject['log'][loggingAttr]
except Exception:
# Any non-exit exceptions
continue
if path not in loggingPath:
loggingPath.append(path)
for loggingAttr in ['access', 'error']:
try:
path = jsonObject['log'][loggingAttr]
stream = open(path, 'rb')
except Exception:
# Any non-exit exceptions
continue
pass
else:
stream.seek(0, io.SEEK_END)
if path not in loggingPath:
loggingPath.append(path)
fileStreams.append(stream)
try:
stream = open(path, 'rb')
except Exception:
# Any non-exit exceptions
def produceMsg():
while True:
for file in fileStreams:
for line in iter(file.readline, b''):
if line and not line.isspace():
try:
msgQueue.put_nowait(line.decode('utf-8', 'replace'))
except Exception:
# Any non-exit exceptions
pass
else:
stream.seek(0, io.SEEK_END)
pass
fileStreams.append(stream)
time.sleep(MsgQueue.MSG_PRODUCE_THRESHOLD / 1000)
def produceMsg():
while True:
for file in fileStreams:
for line in iter(file.readline, b''):
if line and not line.isspace():
try:
msgQueue.put_nowait(
line.decode('utf-8', 'replace')
)
except Exception:
# Any non-exit exceptions
try:
if fileStreams:
msgThread = threading.Thread(target=produceMsg, daemon=True)
msgThread.start()
pass
time.sleep(MsgQueue.MSG_PRODUCE_THRESHOLD / 1000)
try:
if fileStreams:
msgThread = threading.Thread(target=produceMsg, daemon=True)
msgThread.start()
xray.startFromJSON(jsonString)
finally:
for stream in fileStreams:
stream.close()
xrayPythonwProduceMsg()
xray.startFromJSON(jsonString)
finally:
for stream in fileStreams:
stream.close()
class XrayCore(CoreProcess):
class ExitCode:
class XrayCore(CoreProcessWorker):
class ExitCode(Enum):
ConfigurationError = 23
# Windows: 4294967295. Darwin, Linux: 255 (-1)
ServerStartFailure = 4294967295 if PLATFORM == 'Windows' else 255
@@ -145,26 +142,17 @@ class XrayCore(CoreProcess):
return '0.0.0'
def startFromArgs(self, jsonString: str, **kwargs) -> bool:
self.registerCurrentJSONConfig(jsonString)
def start(self, config: Union[str, ConfigFactory, dict], **kwargs) -> bool:
param = self.toJSONString(config)
return super().start(
target=startXrayCore, args=(jsonString, self.msgQueue), **kwargs
)
def start(self, config: Union[str, dict], **kwargs) -> bool:
if isinstance(config, str):
return self.startFromArgs(config, **kwargs)
elif isinstance(config, ConfigurationFactory):
return self.startFromArgs(config.toJSONString(), **kwargs)
elif isinstance(config, dict):
try:
jsonString = UJSONEncoder.encode(config)
except Exception:
# Any non-exit exceptions
return False
else:
return self.startFromArgs(jsonString, **kwargs)
if param:
return super().start(
target=startXrayCore,
args=(
param,
self.msgQueue,
),
**kwargs,
)
else:
return False
Binary file not shown.
+68073 -73426
View File
File diff suppressed because one or more lines are too long
+14 -11
View File
@@ -1,7 +1,7 @@
TRANSLATION = {
"Delete": {
"source": [
"Furious.QtFramework.QtWidgets",
"Furious.Qt.QtWidgets",
"Furious.Widget.UserServersQTableWidget",
"Furious.Widget.UserSubsQTableWidget",
"Furious.Widget.XrayAssetViewerQListWidget",
@@ -13,7 +13,7 @@ TRANSLATION = {
},
"Delete these items?": {
"source": [
"Furious.QtFramework.QtWidgets"
"Furious.Qt.QtWidgets"
],
"RU": "Удалить эти элементы?",
"ZH": "删除这些项目?",
@@ -21,7 +21,7 @@ TRANSLATION = {
},
"Delete this item?": {
"source": [
"Furious.QtFramework.QtWidgets"
"Furious.Qt.QtWidgets"
],
"RU": "Удалить этот элемент?",
"ZH": "删除此项目?",
@@ -29,7 +29,7 @@ TRANSLATION = {
},
"New changes will take effect next time": {
"source": [
"Furious.QtFramework.QtWidgets"
"Furious.Qt.QtWidgets"
],
"RU": "Новые изменения вступят в силу при следующем подключении",
"ZH": "新的更改将于下次生效",
@@ -37,7 +37,7 @@ TRANSLATION = {
},
"Unrecognized Configuration. Please modify it in the editor": {
"source": [
"Furious.QtFramework.QtWidgets"
"Furious.Qt.QtWidgets"
],
"RU": "Нераспознанная конфигурация. Пожалуйста, измените его в редакторе",
"ZH": "无法识别的配置。请在编辑器中修改",
@@ -45,7 +45,7 @@ TRANSLATION = {
},
"New version available": {
"source": [
"Furious.QtFramework.UpdatesManager"
"Furious.Qt.UpdatesManager"
],
"RU": "Доступна новая версия",
"ZH": "新版本可用",
@@ -53,7 +53,7 @@ TRANSLATION = {
},
"Check for updates failed": {
"source": [
"Furious.QtFramework.UpdatesManager"
"Furious.Qt.UpdatesManager"
],
"RU": "Не удалось проверить наличие обновлений",
"ZH": "检查更新失败",
@@ -61,7 +61,7 @@ TRANSLATION = {
},
"Go to download page?": {
"source": [
"Furious.QtFramework.UpdatesManager"
"Furious.Qt.UpdatesManager"
],
"RU": "Перейти на страницу загрузки?",
"ZH": "前往下载页面?",
@@ -69,7 +69,7 @@ TRANSLATION = {
},
"Furious is already the latest version": {
"source": [
"Furious.QtFramework.UpdatesManager"
"Furious.Qt.UpdatesManager"
],
"RU": "Используется последняя версия Furious",
"ZH": "Furious已是最新版本",
@@ -201,7 +201,7 @@ TRANSLATION = {
},
"Cancel": {
"source": [
"Furious.QtFramework.GuiEditorXXX",
"Furious.Qt.GuiEditorXXX",
"Furious.Widget.IndentSpinBox",
"Furious.Window.TextEditorWindow",
"Furious.Window.UserSubsWindow"
@@ -801,6 +801,7 @@ TRANSLATION = {
},
"Show Furious Log...": {
"source": [
"Furious.Widget.UserServersQTableWidget",
"Furious.Window.AppMainWindow"
],
"RU": "Показать журнал Furious...",
@@ -809,6 +810,7 @@ TRANSLATION = {
},
"Show Core Log...": {
"source": [
"Furious.Widget.UserServersQTableWidget",
"Furious.Window.AppMainWindow"
],
"RU": "Показать журнал ядра...",
@@ -817,6 +819,7 @@ TRANSLATION = {
},
"Show Tun2socks Log...": {
"source": [
"Furious.Widget.UserServersQTableWidget",
"Furious.Window.AppMainWindow"
],
"RU": "Показать журнал Tun2socks...",
@@ -1386,7 +1389,7 @@ TRANSLATION = {
"OK": {
"source": [
"Furious.__main__",
"Furious.QtFramework.GuiEditorXXX",
"Furious.Qt.GuiEditorXXX",
"Furious.Widget.IndentSpinBox",
"Furious.Window.UserSubsWindow"
],
@@ -17,17 +17,26 @@
from __future__ import annotations
from Furious.Utility.Utility import BinarySettings
from PySide6 import QtCore
import logging
__all__ = ['AppSettings', 'registerAppSettings']
__all__ = [
'AppBinarySettings',
'AppSettings',
'registerAppSettings',
]
logger = logging.getLogger(__name__)
class AppBinarySettings:
OFF = '0'
ON_ = '1'
RANGE = [OFF, ON_]
class AppSettings:
SettingsPool: dict[str, AppSettings] = dict()
@@ -42,8 +51,8 @@ class AppSettings:
self.isBinary = isBinary
if isBinary:
self.validRange = BinarySettings.RANGE
self.default = BinarySettings.OFF if default is None else default
self.validRange = AppBinarySettings.RANGE
self.default = AppBinarySettings.OFF if default is None else default
else:
self.validRange = validRange
@@ -90,7 +99,7 @@ class AppSettings:
def isStateON_(key: str) -> bool:
value = AppSettings.get(key)
if value == BinarySettings.ON_:
if value == AppBinarySettings.ON_:
return True
else:
return False
@@ -99,7 +108,7 @@ class AppSettings:
def isStateOFF(key: str) -> bool:
value = AppSettings.get(key)
if value == BinarySettings.OFF:
if value == AppBinarySettings.OFF:
return True
else:
return False
@@ -121,11 +130,11 @@ class AppSettings:
@staticmethod
def turnON_(key: str):
AppSettings.set(key, BinarySettings.ON_)
AppSettings.set(key, AppBinarySettings.ON_)
@staticmethod
def turnOFF(key: str):
AppSettings.set(key, BinarySettings.OFF)
AppSettings.set(key, AppBinarySettings.OFF)
def registerAppSettings(name: str, *args, **kwargs):
@@ -18,15 +18,11 @@
from Furious.Version import __version__
from PySide6 import QtCore, __version__ as _pyside6_version
from PySide6.QtWidgets import QApplication
import os
import math
import pathlib
import platform
import functools
APP = functools.partial(QApplication.instance)
APPLICATION_NAME = 'Furious'
APPLICATION_VERSION = __version__
@@ -46,6 +42,7 @@ PYSIDE6_VERSION = _pyside6_version
OS_CPU_COUNT = os.cpu_count()
PLATFORM = platform.system()
PLATFORM_PYTHON_VERSION = platform.python_version()
PLATFORM_RELEASE = platform.release()
PLATFORM_MACHINE = platform.machine()
@@ -15,4 +15,36 @@
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <https://www.gnu.org/licenses/>.
from .Ancestors import *
from __future__ import annotations
from PySide6.QtWidgets import QApplication
import functools
__all__ = [
'APP',
'AppFontName',
'AppThreadPool',
'AppLoggerWindow',
]
APP = functools.partial(QApplication.instance)
def getAppAttributes(name: str):
return getattr(APP(), name)
(
AppFontName,
AppThreadPool,
) = (
functools.partial(getAppAttributes, 'customFontName'),
functools.partial(getAppAttributes, 'threadPool'),
)
class AppLoggerWindow:
Self = functools.partial(getAppAttributes, 'logViewerWindowSelf')
Core = functools.partial(getAppAttributes, 'logViewerWindowCore')
TUN_ = functools.partial(getAppAttributes, 'logViewerWindowTun_')
+178
View File
@@ -0,0 +1,178 @@
# Copyright (C) 2024–present Loren Eteval & contributors <loren.eteval@proton.me>
#
# This file is part of Furious.
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <https://www.gnu.org/licenses/>.
from __future__ import annotations
from PySide6 import QtCore
from PySide6.QtWidgets import QApplication
import logging
logger = logging.getLogger(__name__)
__all__ = ['Mixins']
class Mixins:
class ConnectionAware:
ObjectsPool = list()
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
Mixins.ConnectionAware.ObjectsPool.append(self)
def disconnectedCallback(self):
raise NotImplementedError
def connectedCallback(self):
raise NotImplementedError
@staticmethod
def callConnectedCallback():
for ob in Mixins.ConnectionAware.ObjectsPool:
assert isinstance(ob, Mixins.ConnectionAware)
ob.connectedCallback()
@staticmethod
def callDisconnectedCallback():
for ob in Mixins.ConnectionAware.ObjectsPool:
assert isinstance(ob, Mixins.ConnectionAware)
ob.disconnectedCallback()
class ThemeAware:
ObjectsPool = list()
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
Mixins.ThemeAware.ObjectsPool.append(self)
def themeChangedCallback(self, theme: str):
raise NotImplementedError
@staticmethod
def callThemeChangedCallbackUnchecked(theme: str):
for ob in Mixins.ThemeAware.ObjectsPool:
assert isinstance(ob, Mixins.ThemeAware)
ob.themeChangedCallback(theme)
@staticmethod
def callThemeChangedCallback(theme: str):
try:
app = QApplication.instance()
if app is not None and app.isDarkModeEnabled():
# Ignore application dark detect system
logger.info(f'ignore system theme \'{theme}\' changes in dark mode')
return
except Exception:
# Any non-exit exceptions
pass
logger.info(f'system theme changed to \'{theme}\'')
Mixins.ThemeAware.callThemeChangedCallbackUnchecked(theme)
class CleanupOnExit:
ObjectsPool = list()
VisitedType = dict()
def __init__(self, *args, **kwargs):
self.uniqueCleanup = kwargs.pop('uniqueCleanup', True)
super().__init__(*args, **kwargs)
Mixins.CleanupOnExit.ObjectsPool.append(self)
def cleanup(self):
raise NotImplementedError
@staticmethod
def cleanupAll():
for ob in Mixins.CleanupOnExit.ObjectsPool:
assert isinstance(ob, Mixins.CleanupOnExit)
if ob.uniqueCleanup:
obtype = str(type(ob))
if not Mixins.CleanupOnExit.VisitedType.get(obtype, False):
ob.cleanup()
Mixins.CleanupOnExit.VisitedType[obtype] = True
else:
pass
else:
ob.cleanup()
Mixins.CleanupOnExit.ObjectsPool.clear()
Mixins.CleanupOnExit.VisitedType.clear()
class QSetDisabledContext:
def __init__(self, qobject: QtCore.QObject):
self.qobject = qobject
def __enter__(self):
if hasattr(self.qobject, 'setDisabled'):
self.qobject.setDisabled(True)
def __exit__(self, exceptionType, exceptionValue, tb):
if hasattr(self.qobject, 'setDisabled'):
self.qobject.setDisabled(False)
class QBlockSignalContext:
def __init__(self, qobject: QtCore.QObject):
self.qobject = qobject
def __enter__(self):
if hasattr(self.qobject, 'blockSignals'):
self.qobject.blockSignals(True)
def __exit__(self, exceptionType, exceptionValue, tb):
if hasattr(self.qobject, 'blockSignals'):
self.qobject.blockSignals(False)
class QTranslatable:
ObjectsPool = list()
def __init__(self, *args, **kwargs):
self.translatable = kwargs.pop('translatable', True)
self.useQSetDisabled = kwargs.pop('useQSetDisabled', True)
super().__init__(*args, **kwargs)
def retranslate(self):
raise NotImplementedError
@staticmethod
def retranslateAll():
for ob in Mixins.QTranslatable.ObjectsPool:
assert isinstance(ob, Mixins.QTranslatable)
if ob.translatable:
if ob.useQSetDisabled:
assert isinstance(ob, QtCore.QObject)
with Mixins.QSetDisabledContext(ob):
ob.retranslate()
else:
ob.retranslate()
+50
View File
@@ -0,0 +1,50 @@
# Copyright (C) 2024–present Loren Eteval & contributors <loren.eteval@proton.me>
#
# This file is part of Furious.
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <https://www.gnu.org/licenses/>.
from Furious.Frozenlib.Constants import *
from Furious.Frozenlib.Globals import *
from Furious.Frozenlib.Utility import *
import time
__all__ = ['PySide6Legacy']
class PySide6Legacy:
@staticmethod
def enumValueWrapper(enum):
# Protect PySide6 enum wrapper behavior changes
if versionToValue(PYSIDE6_VERSION) < versionToValue('6.2.2'):
return enum
else:
return enum.value
@staticmethod
def eventLoopWait(ms):
# Protect qWait method does not exist in some
# old PySide6 version
if versionToValue(PYSIDE6_VERSION) < versionToValue('6.3.1'):
for counter in range(0, ms, 10):
time.sleep(10 / 1000)
APP().processEvents()
else:
from PySide6.QtTest import QTest
QTest.qWait(ms)
APP().processEvents()
@@ -17,8 +17,8 @@
from __future__ import annotations
from Furious.Utility.Constants import *
from Furious.Utility.Utility import runExternalCommand
from Furious.Frozenlib.Constants import *
from Furious.Frozenlib.Utility import *
import re
import logging
@@ -17,12 +17,14 @@
from __future__ import annotations
from Furious.Utility.Constants import *
from Furious.Frozenlib.Constants import *
from typing import AnyStr, Tuple
import os
import ujson
import enum
import time
import pathlib
import operator
import functools
import ipaddress
@@ -30,32 +32,19 @@ import subprocess
import urllib.parse
__all__ = [
'AppThreadPool',
'Protocol',
'BinarySettings',
'callRateLimited',
'callOnceOnly',
'protocolRepr',
'isValidIPAddress',
'parseHostPort',
'runExternalCommand',
'getAbsolutePath',
'absolutePath',
'versionToValue',
'getXrayProxyOutboundObject',
'getXrayProxyOutboundStream',
'loggerApp_',
'loggerCore',
'loggerTun_',
]
def _appThreadPool() -> QtCore.QThreadPool:
return APP().threadPool
AppThreadPool = functools.partial(_appThreadPool)
class Protocol:
class Protocol(enum.Enum):
Unknown = 'Unknown'
VMess = 'VMess'
VLESS = 'VLESS'
Shadowsocks = 'Shadowsocks'
@@ -63,12 +52,53 @@ class Protocol:
Hysteria1 = 'hysteria1'
Hysteria2 = 'hysteria2'
@staticmethod
@functools.lru_cache(None)
def toEnum(protocol: str):
if not isinstance(protocol, str):
return Protocol.Unknown
class BinarySettings:
OFF = '0'
ON_ = '1'
if protocol.lower() == 'vmess':
return Protocol.VMess
RANGE = [OFF, ON_]
if protocol.lower() == 'vless':
return Protocol.VLESS
if protocol.lower() == 'shadowsocks':
return Protocol.Shadowsocks
if protocol.lower() == 'trojan':
return Protocol.Trojan
return Protocol.Unknown
def callRateLimited(maxCallPerSecond):
"""
Decorator function that limits the rate at which a function can be called.
"""
interval = 1.0 / float(maxCallPerSecond)
called = time.monotonic()
def decorator(func):
def wrapper(*args, **kwargs):
# Previously called
nonlocal called
elapsed = time.monotonic() - called
waitsec = interval - elapsed
if waitsec > 0:
time.sleep(waitsec)
result = func(*args, **kwargs)
called = time.monotonic()
return result
return wrapper
return decorator
def callOnceOnly(func):
@@ -91,26 +121,6 @@ def callOnceOnly(func):
return wrapper
@functools.lru_cache(None)
def protocolRepr(protocol: str) -> str:
if not isinstance(protocol, str):
return ''
if protocol.lower() == 'vmess':
return Protocol.VMess
if protocol.lower() == 'vless':
return Protocol.VLESS
if protocol.lower() == 'shadowsocks':
return Protocol.Shadowsocks
if protocol.lower() == 'trojan':
return Protocol.Trojan
return protocol
@functools.lru_cache(None)
def isValidIPAddress(address) -> bool:
try:
@@ -124,13 +134,24 @@ def isValidIPAddress(address) -> bool:
# Can throw exceptions
def parseHostPort(address) -> Tuple[AnyStr | None, str]:
if address.count('//') == 0:
@functools.lru_cache(None)
def parseHostPort(address: str) -> Tuple[AnyStr | None, str | None]:
if address.find('//') == -1:
result = urllib.parse.urlsplit('//' + address)
else:
result = urllib.parse.urlsplit(address)
return result.hostname, str(result.port)
if result.hostname is None:
hostname = None
else:
hostname = result.hostname
if result.port is None:
port = None
else:
port = str(result.port)
return hostname, port
def runExternalCommand(*args, **kwargs):
@@ -142,19 +163,18 @@ def runExternalCommand(*args, **kwargs):
return subprocess.run(*args, **kwargs)
def getAbsolutePath(path):
return path if os.path.isabs(path) else str(ROOT_DIR / path)
@functools.lru_cache(None)
def absolutePath(path) -> pathlib.Path:
return pathlib.Path(path) if os.path.isabs(path) else ROOT_DIR / path
@functools.lru_cache(None)
def versionToValue(version: str) -> int:
def _split():
# x.y or x.y.z or x.y.z.u
def split():
# x or x.y or x.y.z or x.y.z.u
result = version.split('.')
if len(result) == 2:
result.append('0')
if len(result) == 3:
while len(result) < 4:
result.append('0')
return result
@@ -165,7 +185,7 @@ def versionToValue(version: str) -> int:
u_weight = 1
try:
x, y, z, u = _split()
x, y, z, u = split()
return functools.reduce(
operator.add,
@@ -180,53 +200,3 @@ def versionToValue(version: str) -> int:
# Any non-exit exceptions
return 0
def getXrayProxyOutboundObject(config: dict) -> dict:
if not isinstance(config.get('outbounds'), list):
config['outbounds'] = []
for outbound in config['outbounds']:
if isinstance(outbound, dict):
tag = outbound.get('tag')
if isinstance(tag, str) and tag == 'proxy':
return outbound
return {}
def getXrayProxyOutboundStream(config: dict) -> dict:
proxyOutboundObject = getXrayProxyOutboundObject(config)
if not isinstance(proxyOutboundObject.get('streamSettings'), dict):
proxyOutboundObject['streamSettings'] = {}
return proxyOutboundObject['streamSettings']
def loggerApp_():
try:
return APP().logViewerWindowApp_
except Exception:
# Any non-exit exceptions
return None
def loggerCore():
try:
return APP().logViewerWindowCore
except Exception:
# Any non-exit exceptions
return None
def loggerTun_():
try:
return APP().logViewerWindowTun_
except Exception:
# Any non-exit exceptions
return None
@@ -17,7 +17,7 @@
from __future__ import annotations
from Furious.Utility.Constants import *
from Furious.Frozenlib.Constants import *
import logging
import threading
@@ -15,6 +15,12 @@
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <https://www.gnu.org/licenses/>.
from .UserServers import *
from .UserSubs import *
from .UserTUNSettings import *
from .AppSettings import *
from .Constants import *
from .Globals import *
from .Mixins import *
from .PySide6Legacy import *
from .SystemRoutingTable import *
from .Tcping import *
from .Utility import *
from .Win32Session import *
@@ -17,11 +17,13 @@
from __future__ import annotations
from enum import Enum
__all__ = ['ApplicationFactory']
class ApplicationFactory:
class ExitCode:
class ExitCode(Enum):
ExitSuccess = 0
UnknownException = 61
PlatformNotSupported = 62
@@ -19,19 +19,16 @@ from __future__ import annotations
from Furious.Interface.UserServersTableItem import *
from PySide6 import QtCore
from PySide6.QtWidgets import QApplication
from abc import ABC
from typing import Union
import copy
import ujson
import functools
__all__ = ['ConfigurationFactory']
__all__ = ['ConfigFactory']
class ConfigurationFactory(UserServersTableItem, dict, ABC):
class ConfigFactory(UserServersTableItem, dict):
"""
ConfigurationFactory is how Furious sees the core config.
@@ -54,24 +51,31 @@ class ConfigurationFactory(UserServersTableItem, dict, ABC):
# Extra attributes
self.kwargs = kwargs
if isinstance(config, str):
self._init_dispatch(config)
@functools.singledispatchmethod
def _init_dispatch(self, config):
super().__init__()
@_init_dispatch.register(str)
def _(self, config):
try:
jsonObject = ujson.loads(config)
except Exception:
# Any non-exit exceptions
try:
jsonObject = ujson.loads(config)
self.fromURI(config)
except Exception:
# Any non-exit exceptions
try:
self.fromURI(config)
except Exception:
# Any non-exit exceptions
super().__init__()
else:
super().__init__(**jsonObject)
elif isinstance(config, dict):
super().__init__(**config)
super().__init__()
else:
super().__init__()
super().__init__(**jsonObject)
@_init_dispatch.register(dict)
def _(self, config):
super().__init__(**config)
def __getitem__(self, item: str):
if not isinstance(item, str):
@@ -85,7 +89,7 @@ class ConfigurationFactory(UserServersTableItem, dict, ABC):
return super().__setitem__(item, value)
def deepcopy(self) -> ConfigurationFactory:
def deepcopy(self) -> ConfigFactory:
return copy.deepcopy(self)
def coreName(self) -> str:
@@ -126,20 +130,7 @@ class ConfigurationFactory(UserServersTableItem, dict, ABC):
@property
def itemSubscription(self) -> str:
try:
app = QApplication.instance()
if app is None:
return ''
else:
subsId = self.getExtras('subsId')
subsOb = app.userSubs.data().get(subsId, {})
return subsOb.get('remark', '')
except Exception:
# Any non-exit exceptions
return ''
return ''
@property
def itemLatency(self) -> str:
@@ -203,7 +194,7 @@ class ConfigurationFactory(UserServersTableItem, dict, ABC):
return False
def httpProxyEndpoint(self) -> str:
def httpProxy(self) -> str:
"""
Get current http proxy endpoint
@@ -212,7 +203,7 @@ class ConfigurationFactory(UserServersTableItem, dict, ABC):
return ''
def socksProxyEndpoint(self) -> str:
def socksProxy(self) -> str:
"""
Get current socks proxy endpoint
@@ -221,7 +212,7 @@ class ConfigurationFactory(UserServersTableItem, dict, ABC):
return ''
def setHttpProxyEndpoint(self, endpoint: str) -> bool:
def setHttpProxy(self, endpoint: str) -> bool:
"""
Set current http proxy endpoint
@@ -230,7 +221,7 @@ class ConfigurationFactory(UserServersTableItem, dict, ABC):
return False
def setSocksProxyEndpoint(self, endpoint: str) -> bool:
def setSocksProxy(self, endpoint: str) -> bool:
"""
Set current socks proxy endpoint
@@ -17,18 +17,21 @@
from __future__ import annotations
from Furious.Utility.Constants import PLATFORM
from Furious.Frozenlib import *
from Furious.Interface.ConfigFactory import *
from abc import ABC
from abc import ABC, abstractmethod
from enum import Enum
from typing import Callable, Union
import ujson
import functools
__all__ = ['CoreFactory']
__all__ = ['CoreProcessFactory']
class CoreFactory(ABC):
class ExitCode:
class CoreProcessFactory(ABC):
class ExitCode(Enum):
ConfigurationError = 23
# Windows: 4294967295. Darwin, Linux: 255 (-1)
ServerStartFailure = 4294967295 if PLATFORM == 'Windows' else 255
@@ -36,62 +39,60 @@ class CoreFactory(ABC):
SystemShuttingDown = 0x40010004
def __init__(
self, exitCallback: Union[Callable[[CoreFactory, int], None], None] = None
self,
exitCallback: Union[Callable[[CoreProcessFactory, int], None], None] = None,
):
super().__init__()
self._exitCallback = exitCallback
self._jsonConfig = ''
def callExitCallback(self, exitcode: int):
if callable(self._exitCallback):
self._exitCallback(self, exitcode)
def registerCurrentJSONConfig(self, config: Union[str, dict]) -> CoreFactory:
self._jsonConfig = config
return self
def configJSONString(self) -> str:
if isinstance(self._jsonConfig, str):
return self._jsonConfig
if isinstance(self._jsonConfig, dict):
try:
return ujson.dumps(
self._jsonConfig,
ensure_ascii=False,
escape_forward_slashes=False,
)
except Exception:
# Any non-exit exceptions
return ''
@functools.singledispatchmethod
def toJSONString(self, config, **kwargs) -> str:
return ''
def configJSONObject(self) -> dict:
if isinstance(self._jsonConfig, str):
try:
return ujson.loads(self._jsonConfig)
except Exception:
# Any non-exit exceptions
@toJSONString.register(str)
def _(self, config, **kwargs) -> str:
return config
return {}
if isinstance(self._jsonConfig, dict):
return self._jsonConfig
@toJSONString.register(ConfigFactory)
def _(self, config, **kwargs) -> str:
return config.toJSONString(**kwargs)
return {}
@toJSONString.register(dict)
def _(self, config, **kwargs) -> str:
try:
ensure_ascii = kwargs.pop('ensure_ascii', False)
escape_forward_slashes = kwargs.pop('escape_forward_slashes', False)
return ujson.dumps(
config,
ensure_ascii=ensure_ascii,
escape_forward_slashes=escape_forward_slashes,
**kwargs,
)
except Exception:
# Any non-exit exceptions
return ''
@staticmethod
@abstractmethod
def name() -> str:
raise NotImplementedError
@staticmethod
@abstractmethod
def version() -> str:
raise NotImplementedError
@abstractmethod
def start(self, *args, **kwargs) -> bool:
raise NotImplementedError
@abstractmethod
def stop(self):
raise NotImplementedError
@@ -17,15 +17,17 @@
from __future__ import annotations
from abc import ABC
from abc import ABC, abstractmethod
from typing import Any
__all__ = ['Encoder']
__all__ = ['EncoderFactory']
class Encoder(ABC):
class EncoderFactory(ABC):
@abstractmethod
def encode(self, data: Any, **kwargs) -> Any:
raise NotImplementedError
@abstractmethod
def decode(self, data: Any, **kwargs) -> Any:
raise NotImplementedError
@@ -17,15 +17,17 @@
from __future__ import annotations
from abc import ABC
from abc import ABC, abstractmethod
from typing import Any
__all__ = ['StorageFactory']
class StorageFactory(ABC):
@abstractmethod
def sync(self):
raise NotImplementedError
@abstractmethod
def data(self) -> Any:
raise NotImplementedError
+1 -3
View File
@@ -17,12 +17,10 @@
from __future__ import annotations
from abc import ABC
__all__ = ['UserServersTableItem']
class UserServersTableItem(ABC):
class UserServersTableItem:
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
+5 -5
View File
@@ -15,10 +15,10 @@
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <https://www.gnu.org/licenses/>.
from .Encoder import *
from .ApplicationFactory import *
from .EncoderFactory import *
from .GuiEditorItemFactory import *
from .StorageFactory import *
from .UserServersTableItem import *
from .Storage import *
from .Application import *
from .ConfigurationFactory import *
from .CoreFactory import *
from .ConfigFactory import *
from .CoreProcessFactory import *
+431 -149
View File
@@ -17,12 +17,14 @@
from __future__ import annotations
from Furious.Frozenlib import *
from Furious.Interface import *
from Furious.Utility import *
from Furious.Library.Encoder import *
from Furious.Library.Storage import *
from typing import Union, Tuple
import copy
import functools
import urllib.parse
@@ -32,17 +34,21 @@ parse_qsl = functools.partial(urllib.parse.parse_qsl)
urlparse = functools.partial(urllib.parse.urlparse)
urlunparse = functools.partial(urllib.parse.urlunparse)
__all__ = [
'ConfigurationXray',
'ConfigurationHysteria1',
'ConfigurationHysteria2',
'constructFromDict',
'constructFromAny',
'BLANK_CONFIG_XRAY',
'BLANK_CONFIG_HYSTERIA1',
'BLANK_CONFIG_HYSTERIA2',
'ConfigXray',
'ConfigHysteria1',
'ConfigHysteria2',
'configXrayEmptyProxyOutboundObject',
'configFactoryFromDict',
'configFactoryFromAny',
'configFactoryBlank',
]
class ConfigurationXrayProxyOutboundObjectV(dict):
class ConfigXrayProxyOutboundObjectV(dict):
def __init__(
self,
protocol,
@@ -62,7 +68,7 @@ class ConfigurationXrayProxyOutboundObjectV(dict):
networkKey = f'{type_}Settings'
networkObjectArgs[networkKey] = (
ConfigurationXray.kwargs2ProxyStreamSettingsNetworkObject(
ConfigXray.kwargs2ProxyStreamSettingsNetworkObject(
type_, remote_host, kwargs
)
)
@@ -74,7 +80,7 @@ class ConfigurationXrayProxyOutboundObjectV(dict):
TLSObjectArgs[
# tlsSettings, realitySettings
f'{security}Settings'
] = ConfigurationXray.kwargs2ProxyStreamSettingsTLSObject(
] = ConfigXray.kwargs2ProxyStreamSettingsTLSObject(
protocol, remote_host, security, kwargs
)
@@ -88,7 +94,7 @@ class ConfigurationXrayProxyOutboundObjectV(dict):
'address': remote_host,
'port': int(remote_port),
'users': [
ConfigurationXray.kwargs2ProxyUserObject(
ConfigXray.kwargs2ProxyUserObject(
protocol, uuid_, encryption, kwargs
),
],
@@ -109,7 +115,7 @@ class ConfigurationXrayProxyOutboundObjectV(dict):
)
class ConfigurationXrayProxyOutboundObjectSS(dict):
class ConfigXrayProxyOutboundObjectSS(dict):
def __init__(self, method, password, address, port):
super().__init__(
**{
@@ -138,7 +144,7 @@ class ConfigurationXrayProxyOutboundObjectSS(dict):
)
class ConfigurationXrayProxyOutboundObjectTrojan(dict):
class ConfigXrayProxyOutboundObjectTrojan(dict):
def __init__(self, password, address, port, type_, security, **kwargs):
networkObjectArgs, securityArgs, TLSObjectArgs = {}, {}, {}
@@ -148,9 +154,7 @@ class ConfigurationXrayProxyOutboundObjectTrojan(dict):
networkKey = f'{type_}Settings'
networkObjectArgs[networkKey] = (
ConfigurationXray.kwargs2ProxyStreamSettingsNetworkObject(
type_, address, kwargs
)
ConfigXray.kwargs2ProxyStreamSettingsNetworkObject(type_, address, kwargs)
)
if security:
@@ -160,7 +164,7 @@ class ConfigurationXrayProxyOutboundObjectTrojan(dict):
TLSObjectArgs[
# tlsSettings, realitySettings
f'{security}Settings'
] = ConfigurationXray.kwargs2ProxyStreamSettingsTLSObject(
] = ConfigXray.kwargs2ProxyStreamSettingsTLSObject(
'trojan', address, security, kwargs
)
@@ -192,21 +196,169 @@ class ConfigurationXrayProxyOutboundObjectTrojan(dict):
)
class ConfigurationXray(ConfigurationFactory):
BLANK_CONFIG_XRAY = {
# log
'log': {
'access': '',
'error': '',
'loglevel': 'warning',
},
# inbounds
'inbounds': [
{
'tag': 'socks',
'port': 10808,
'listen': '127.0.0.1',
'protocol': 'socks',
'sniffing': {
'enabled': True,
'destOverride': [
'http',
'tls',
],
},
'settings': {
'auth': 'noauth',
'udp': True,
'allowTransparent': False,
},
},
{
'tag': 'http',
'port': 10809,
'listen': '127.0.0.1',
'protocol': 'http',
'sniffing': {
'enabled': True,
'destOverride': [
'http',
'tls',
],
},
'settings': {
'auth': 'noauth',
'udp': True,
'allowTransparent': False,
},
},
],
# outbounds
'outbounds': [
# proxy
{
'tag': 'proxy',
'protocol': '',
'settings': {},
'streamSettings': {
'network': 'tcp',
},
'mux': {
'enabled': False,
'concurrency': -1,
},
},
# direct
{
'tag': 'direct',
'protocol': 'freedom',
'settings': {},
},
# block
{
'tag': 'block',
'protocol': 'blackhole',
'settings': {
'response': {
'type': 'http',
}
},
},
],
# routing
'routing': {},
}
class ConfigXray(ConfigFactory):
def __init__(self, config: Union[str, dict] = '', **kwargs):
super().__init__(config, **kwargs)
def coreName(self):
return 'Xray-core'
@staticmethod
def getProxyOutboundObject(config: dict, default=None) -> dict:
if not isinstance(config.get('outbounds'), list):
config['outbounds'] = []
for outbound in config['outbounds']:
if isinstance(outbound, dict):
tag = outbound.get('tag')
if isinstance(tag, str) and tag == 'proxy':
return outbound
if isinstance(default, dict):
# Proxy outbound not found. Add it
config['outbounds'].append(default)
return {}
@staticmethod
def getProxyOutboundStream(config: dict, **kwargs) -> dict:
proxyOutbound = ConfigXray.getProxyOutboundObject(config, **kwargs)
if not isinstance(proxyOutbound.get('streamSettings'), dict):
proxyOutbound['streamSettings'] = {}
return proxyOutbound['streamSettings']
@staticmethod
def getProxyOutboundServer(config: dict, protocol: Protocol, **kwargs) -> dict:
value, proxyOutbound = (
protocol.value.lower(),
ConfigXray.getProxyOutboundObject(config, **kwargs),
)
try:
if value == 'vless' or value == 'vmess':
server = proxyOutbound['settings']['vnext'][0]
elif value == 'shadowsocks' or value == 'trojan':
server = proxyOutbound['settings']['servers'][0]
else:
server = {}
except Exception:
# Any non-exit exceptions
server = {}
if not isinstance(server, dict):
server = {}
return server
@staticmethod
def getProxyOutboundUser(config: dict, protocol: Protocol, **kwargs) -> dict:
proxyOutboundServer = ConfigXray.getProxyOutboundServer(
config, protocol, **kwargs
)
try:
user = proxyOutboundServer['users'][0]
except Exception:
# Any non-exit exceptions
user = {}
if not isinstance(user, dict):
user = {}
return user
@property
def proxyOutboundObject(self) -> dict:
try:
for outboundObject in self['outbounds']:
if outboundObject['tag'] == 'proxy':
return outboundObject
return {}
return ConfigXray.getProxyOutboundObject(self)
except Exception:
# Any non-exit exceptions
@@ -224,16 +376,12 @@ class ConfigurationXray(ConfigurationFactory):
@property
def proxyServerObject(self) -> dict:
try:
if (
self.proxyProtocol.lower() == 'vmess'
or self.proxyProtocol.lower() == 'vless'
):
proxyProtocol = self.proxyProtocol.lower()
if proxyProtocol == 'vmess' or proxyProtocol == 'vless':
return self.proxyOutboundObject['settings']['vnext'][0]
if (
self.proxyProtocol.lower() == 'shadowsocks'
or self.proxyProtocol.lower() == 'trojan'
):
if proxyProtocol == 'shadowsocks' or proxyProtocol == 'trojan':
return self.proxyOutboundObject['settings']['servers'][0]
return {}
@@ -254,7 +402,7 @@ class ConfigurationXray(ConfigurationFactory):
@property
def proxyStreamSettingsObject(self) -> dict:
try:
return self.proxyOutboundObject['streamSettings']
return ConfigXray.getProxyOutboundStream(self)
except Exception:
# Any non-exit exceptions
@@ -732,6 +880,9 @@ class ConfigurationXray(ConfigurationFactory):
return xhttpObject
else:
return {}
@property
def kwargsFromProxyStreamSettingsTLSObject(self) -> dict:
kwargs = {}
@@ -864,7 +1015,7 @@ class ConfigurationXray(ConfigurationFactory):
except Exception:
# Any non-exit exceptions
return ConfigurationXray.URI2ProxyOutboundObjectVLESS(URI, protocol='vmess')
return ConfigXray.URI2ProxyOutboundObjectVLESS(URI, protocol='vmess')
else:
def getOrDefault(key, default=''):
@@ -875,7 +1026,7 @@ class ConfigurationXray(ConfigurationFactory):
return (
remark,
# Ignore: v
ConfigurationXrayProxyOutboundObjectV(
ConfigXrayProxyOutboundObjectV(
'vmess',
getOrDefault('add'),
int(getOrDefault('port', '0')),
@@ -916,7 +1067,7 @@ class ConfigurationXray(ConfigurationFactory):
return (
remark,
ConfigurationXrayProxyOutboundObjectV(
ConfigXrayProxyOutboundObjectV(
kwargs.pop('protocol', 'vless'),
remote_host,
int(remote_port),
@@ -982,7 +1133,7 @@ class ConfigurationXray(ConfigurationFactory):
return (
remark,
ConfigurationXrayProxyOutboundObjectSS(*getSSParams()),
ConfigXrayProxyOutboundObjectSS(*getSSParams()),
)
@staticmethod
@@ -1001,7 +1152,7 @@ class ConfigurationXray(ConfigurationFactory):
return (
remark,
ConfigurationXrayProxyOutboundObjectTrojan(
ConfigXrayProxyOutboundObjectTrojan(
password, address, port, type_, security, **queryObject
),
)
@@ -1009,16 +1160,16 @@ class ConfigurationXray(ConfigurationFactory):
@staticmethod
def URI2ProxyOutboundObject(URI: str) -> Tuple[str, dict]:
if URI.startswith('vmess://'):
return ConfigurationXray.URI2ProxyOutboundObjectVMess(URI)
return ConfigXray.URI2ProxyOutboundObjectVMess(URI)
if URI.startswith('vless://'):
return ConfigurationXray.URI2ProxyOutboundObjectVLESS(URI)
return ConfigXray.URI2ProxyOutboundObjectVLESS(URI)
if URI.startswith('ss://'):
return ConfigurationXray.URI2ProxyOutboundObjectSS(URI)
return ConfigXray.URI2ProxyOutboundObjectSS(URI)
if URI.startswith('trojan://'):
return ConfigurationXray.URI2ProxyOutboundObjectTrojan(URI)
return ConfigXray.URI2ProxyOutboundObjectTrojan(URI)
raise ValueError(f'Unrecognized URI scheme {URI}')
@@ -1026,9 +1177,21 @@ class ConfigurationXray(ConfigurationFactory):
def itemRemark(self) -> str:
return self.getExtras('remark')
@property
def itemSubscription(self) -> str:
try:
subsId = self.getExtras('subsId')
subsOb = Storage.UserSubs().get(subsId, {})
return subsOb.get('remark', '')
except Exception:
# Any non-exit exceptions
return ''
@property
def itemProtocol(self) -> str:
return protocolRepr(self.proxyProtocol)
return Protocol.toEnum(self.proxyProtocol).value
@property
def itemAddress(self) -> str:
@@ -1071,7 +1234,9 @@ class ConfigurationXray(ConfigurationFactory):
else:
override = remark
if self.proxyProtocol.lower() == 'vmess':
proxyProtocol = self.proxyProtocol.lower()
if proxyProtocol == 'vmess':
netloc = PyBase64Encoder.encode(
UJSONEncoder.encode(
{
@@ -1103,7 +1268,7 @@ class ConfigurationXray(ConfigurationFactory):
return urlunparse(['vmess', netloc, '', '', {}, ''])
if self.proxyProtocol.lower() == 'vless':
if proxyProtocol == 'vless':
flowArg = {}
if self.proxyUserObject.get('flow'):
@@ -1132,7 +1297,7 @@ class ConfigurationXray(ConfigurationFactory):
return urlunparse(['vless', netloc, '', '', query, quote(override)])
if self.proxyProtocol.lower() == 'shadowsocks':
if proxyProtocol == 'shadowsocks':
method, password, address, port = list(
self.proxyServerObject[value]
for value in ['method', 'password', 'address', 'port']
@@ -1142,7 +1307,7 @@ class ConfigurationXray(ConfigurationFactory):
return urlunparse(['ss', netloc, '', '', '', quote(override)])
if self.proxyProtocol.lower() == 'trojan':
if proxyProtocol == 'trojan':
password, address, port = list(
self.proxyServerObject[value]
for value in ['password', 'address', 'port']
@@ -1168,81 +1333,31 @@ class ConfigurationXray(ConfigurationFactory):
def fromURI(self, URI: str) -> bool:
try:
remark, proxyOutboundObject = ConfigurationXray.URI2ProxyOutboundObject(URI)
remark, proxyOutboundObject = ConfigXray.URI2ProxyOutboundObject(URI)
dict.__init__(
self,
**{
# log
'log': {
'access': '',
'error': '',
'loglevel': 'warning',
},
# inbounds
'inbounds': [
{
'tag': 'socks',
'port': 10808,
'listen': '127.0.0.1',
'protocol': 'socks',
'sniffing': {
'enabled': True,
'destOverride': [
'http',
'tls',
],
},
'settings': {
'auth': 'noauth',
'udp': True,
'allowTransparent': False,
},
},
{
'tag': 'http',
'port': 10809,
'listen': '127.0.0.1',
'protocol': 'http',
'sniffing': {
'enabled': True,
'destOverride': [
'http',
'tls',
],
},
'settings': {
'auth': 'noauth',
'udp': True,
'allowTransparent': False,
},
},
],
# outbounds
'outbounds': [
# proxy
proxyOutboundObject,
# direct
{
'tag': 'direct',
'protocol': 'freedom',
'settings': {},
},
# block
{
'tag': 'block',
'protocol': 'blackhole',
'settings': {
'response': {
'type': 'http',
}
},
},
],
# routing
'routing': {},
factory = copy.deepcopy(BLANK_CONFIG_XRAY)
factory['outbounds'] = [
# proxy
proxyOutboundObject,
# direct
{
'tag': 'direct',
'protocol': 'freedom',
'settings': {},
},
)
# block
{
'tag': 'block',
'protocol': 'blackhole',
'settings': {
'response': {
'type': 'http',
}
},
},
]
dict.__init__(self, **factory)
self.setExtras('remark', remark)
@@ -1252,7 +1367,7 @@ class ConfigurationXray(ConfigurationFactory):
return False
def httpProxyEndpoint(self) -> str:
def httpProxy(self) -> str:
try:
for inbound in self['inbounds']:
if inbound['protocol'] == 'http':
@@ -1266,7 +1381,7 @@ class ConfigurationXray(ConfigurationFactory):
return ''
def socksProxyEndpoint(self) -> str:
def socksProxy(self) -> str:
try:
for inbound in self['inbounds']:
if inbound['protocol'] == 'socks':
@@ -1280,7 +1395,7 @@ class ConfigurationXray(ConfigurationFactory):
return ''
def setHttpProxyEndpoint(self, endpoint: str) -> bool:
def setHttpProxy(self, endpoint: str) -> bool:
try:
if self.get('inbounds') is None:
self['inbounds'] = []
@@ -1337,7 +1452,7 @@ class ConfigurationXray(ConfigurationFactory):
return False
def setSocksProxyEndpoint(self, endpoint: str) -> bool:
def setSocksProxy(self, endpoint: str) -> bool:
try:
if self.get('inbounds') is None:
self['inbounds'] = []
@@ -1395,7 +1510,19 @@ class ConfigurationXray(ConfigurationFactory):
return False
class ConfigurationHysteria1(ConfigurationFactory):
BLANK_CONFIG_HYSTERIA1 = {
'server': '',
'protocol': 'udp',
'socks5': {
'listen': '127.0.0.1:10808',
},
'http': {
'listen': '127.0.0.1:10809',
},
}
class ConfigHysteria1(ConfigFactory):
def __init__(self, config: Union[str, dict] = '', **kwargs):
super().__init__(config, **kwargs)
@@ -1406,9 +1533,21 @@ class ConfigurationHysteria1(ConfigurationFactory):
def itemRemark(self) -> str:
return self.getExtras('remark')
@property
def itemSubscription(self) -> str:
try:
subsId = self.getExtras('subsId')
subsOb = Storage.UserSubs().get(subsId, {})
return subsOb.get('remark', '')
except Exception:
# Any non-exit exceptions
return ''
@property
def itemProtocol(self) -> str:
return Protocol.Hysteria1
return Protocol.Hysteria1.value
@property
def itemAddress(self) -> str:
@@ -1601,7 +1740,7 @@ class ConfigurationHysteria1(ConfigurationFactory):
return False
def httpProxyEndpoint(self) -> str:
def httpProxy(self) -> str:
try:
return self['http']['listen']
except Exception:
@@ -1609,7 +1748,7 @@ class ConfigurationHysteria1(ConfigurationFactory):
return ''
def socksProxyEndpoint(self) -> str:
def socksProxy(self) -> str:
try:
return self['socks5']['listen']
except Exception:
@@ -1617,7 +1756,7 @@ class ConfigurationHysteria1(ConfigurationFactory):
return ''
def setHttpProxyEndpoint(self, endpoint: str) -> bool:
def setHttpProxy(self, endpoint: str) -> bool:
try:
if endpoint == '':
self.pop('http', None)
@@ -1635,7 +1774,7 @@ class ConfigurationHysteria1(ConfigurationFactory):
return False
def setSocksProxyEndpoint(self, endpoint: str) -> bool:
def setSocksProxy(self, endpoint: str) -> bool:
try:
if endpoint == '':
self.pop('socks5', None)
@@ -1654,7 +1793,21 @@ class ConfigurationHysteria1(ConfigurationFactory):
return False
class ConfigurationHysteria2(ConfigurationFactory):
BLANK_CONFIG_HYSTERIA2 = {
'server': '',
'tls': {
'insecure': False,
},
'socks5': {
'listen': '127.0.0.1:10808',
},
'http': {
'listen': '127.0.0.1:10809',
},
}
class ConfigHysteria2(ConfigFactory):
def __init__(self, config: Union[str, dict] = '', **kwargs):
super().__init__(config, **kwargs)
@@ -1665,9 +1818,21 @@ class ConfigurationHysteria2(ConfigurationFactory):
def itemRemark(self) -> str:
return self.getExtras('remark')
@property
def itemSubscription(self) -> str:
try:
subsId = self.getExtras('subsId')
subsOb = Storage.UserSubs().get(subsId, {})
return subsOb.get('remark', '')
except Exception:
# Any non-exit exceptions
return ''
@property
def itemProtocol(self) -> str:
return Protocol.Hysteria2
return Protocol.Hysteria2.value
@property
def itemAddress(self) -> str:
@@ -1818,7 +1983,7 @@ class ConfigurationHysteria2(ConfigurationFactory):
return False
def httpProxyEndpoint(self) -> str:
def httpProxy(self) -> str:
try:
return self['http']['listen']
except Exception:
@@ -1826,7 +1991,7 @@ class ConfigurationHysteria2(ConfigurationFactory):
return ''
def socksProxyEndpoint(self) -> str:
def socksProxy(self) -> str:
try:
return self['socks5']['listen']
except Exception:
@@ -1834,7 +1999,7 @@ class ConfigurationHysteria2(ConfigurationFactory):
return ''
def setHttpProxyEndpoint(self, endpoint: str) -> bool:
def setHttpProxy(self, endpoint: str) -> bool:
try:
if endpoint == '':
self.pop('http', None)
@@ -1852,7 +2017,7 @@ class ConfigurationHysteria2(ConfigurationFactory):
return False
def setSocksProxyEndpoint(self, endpoint: str) -> bool:
def setSocksProxy(self, endpoint: str) -> bool:
try:
if endpoint == '':
self.pop('socks5', None)
@@ -1871,16 +2036,94 @@ class ConfigurationHysteria2(ConfigurationFactory):
return False
def constructFromDict(config: dict, **kwargs) -> ConfigurationFactory:
def configXrayEmptyProxyOutboundObject(protocol: Protocol) -> dict:
value = protocol.value.lower()
if value == 'vless' or value == 'vmess':
return {
'tag': 'proxy',
'protocol': protocol,
'settings': {
'vnext': [
{
'address': '',
'port': 0,
'users': [
{
'email': PROXY_OUTBOUND_USER_EMAIL,
},
],
},
]
},
'streamSettings': {
'network': 'tcp',
},
'mux': {
'enabled': False,
'concurrency': -1,
},
}
elif value == 'shadowsocks':
return {
'tag': 'proxy',
'protocol': 'shadowsocks',
'settings': {
'servers': [
{
'address': '',
'port': 0,
'method': '',
'password': '',
'email': PROXY_OUTBOUND_USER_EMAIL,
'ota': False,
}
]
},
'streamSettings': {
'network': 'tcp',
},
'mux': {
'enabled': False,
'concurrency': -1,
},
}
elif value == 'trojan':
return {
'tag': 'proxy',
'protocol': 'trojan',
'settings': {
'servers': [
{
'address': '',
'port': 0,
'password': '',
'email': PROXY_OUTBOUND_USER_EMAIL,
}
]
},
'streamSettings': {
'network': 'tcp',
},
'mux': {
'enabled': False,
'concurrency': -1,
},
}
else:
return {}
def configFactoryFromDict(config: dict, **kwargs) -> ConfigFactory:
if not isinstance(config, dict):
return ConfigurationFactory()
return ConfigFactory()
def hasField(field):
return config.get(field) is not None
if hasField('inbounds') or hasField('outbounds'):
# Assuming is Xray-Core
return ConfigurationXray(config, **kwargs)
return ConfigXray(config, **kwargs)
if hasField('server'):
if (
@@ -1897,7 +2140,7 @@ def constructFromDict(config: dict, **kwargs) -> ConfigurationFactory:
or hasField('fast_open')
or hasField('lazy_start')
):
return ConfigurationHysteria1(config, **kwargs)
return ConfigHysteria1(config, **kwargs)
if (
hasField('tls')
or hasField('transport')
@@ -1911,13 +2154,13 @@ def constructFromDict(config: dict, **kwargs) -> ConfigurationFactory:
or hasField('fastOpen')
or hasField('lazy')
):
return ConfigurationHysteria2(config, **kwargs)
return ConfigHysteria2(config, **kwargs)
# Copied to factory unrecognized
return ConfigurationFactory(config, **kwargs)
return ConfigFactory(config, **kwargs)
def constructFromAny(config: Union[str, dict], **kwargs) -> ConfigurationFactory:
def configFactoryFromAny(config: Union[str, dict], **kwargs) -> ConfigFactory:
if isinstance(config, str):
if (
config.startswith('vmess://')
@@ -1925,21 +2168,60 @@ def constructFromAny(config: Union[str, dict], **kwargs) -> ConfigurationFactory
or config.startswith('ss://')
or config.startswith('trojan://')
):
return ConfigurationXray(config, **kwargs)
return ConfigXray(config, **kwargs)
if config.startswith('hysteria://'):
return ConfigurationHysteria1(config, **kwargs)
return ConfigHysteria1(config, **kwargs)
if config.startswith('hy2://') or config.startswith('hysteria2://'):
return ConfigurationHysteria2(config, **kwargs)
return ConfigHysteria2(config, **kwargs)
try:
# Try to construct from JSON string
return constructFromDict(UJSONEncoder.decode(config), **kwargs)
return configFactoryFromDict(UJSONEncoder.decode(config), **kwargs)
except Exception:
# Any non-exit exceptions
return ConfigurationFactory(**kwargs)
return ConfigFactory(**kwargs)
if isinstance(config, dict):
return constructFromDict(config, **kwargs)
return configFactoryFromDict(config, **kwargs)
return ConfigurationFactory(**kwargs)
return ConfigFactory(**kwargs)
def configFactoryBlank(protocol: Protocol) -> ConfigFactory:
if protocol == Protocol.VMess or protocol == Protocol.VLESS:
factory = configFactoryFromDict(BLANK_CONFIG_XRAY)
factory['outbounds'][0]['protocol'] = protocol.value.lower()
factory['outbounds'][0]['settings']['vnext'] = [
{
'address': '',
'port': 0,
'users': [{'email': PROXY_OUTBOUND_USER_EMAIL}],
},
]
return factory
if protocol == Protocol.Shadowsocks or protocol == Protocol.Trojan:
factory = configFactoryFromDict(BLANK_CONFIG_XRAY)
factory['outbounds'][0]['protocol'] = protocol.value.lower()
factory['outbounds'][0]['settings']['servers'] = [
{
'address': '',
'port': 0,
'email': PROXY_OUTBOUND_USER_EMAIL,
},
]
return factory
if protocol == Protocol.Hysteria1:
factory = configFactoryFromDict(BLANK_CONFIG_HYSTERIA1)
return factory
if protocol == Protocol.Hysteria2:
factory = configFactoryFromDict(BLANK_CONFIG_HYSTERIA2)
return factory
return ConfigFactory()
-174
View File
@@ -1,174 +0,0 @@
# Copyright (C) 2024–present Loren Eteval & contributors <loren.eteval@proton.me>
#
# This file is part of Furious.
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <https://www.gnu.org/licenses/>.
from __future__ import annotations
from Furious.Interface import *
from Furious.Utility import *
from Furious.Library.Configuration import *
__all__ = ['getEmptyFactory']
RAY_TEMPLATE = constructFromDict(
{
'log': {
'access': '',
'error': '',
'loglevel': 'warning',
},
'inbounds': [
{
'tag': 'socks',
'port': 10808,
'listen': '127.0.0.1',
'protocol': 'socks',
'sniffing': {
'enabled': True,
'destOverride': [
'http',
'tls',
],
},
'settings': {
'auth': 'noauth',
'udp': True,
'allowTransparent': False,
},
},
{
'tag': 'http',
'port': 10809,
'listen': '127.0.0.1',
'protocol': 'http',
'sniffing': {
'enabled': True,
'destOverride': [
'http',
'tls',
],
},
'settings': {
'auth': 'noauth',
'udp': True,
'allowTransparent': False,
},
},
],
'outbounds': [
{
'tag': 'proxy',
'protocol': '',
'settings': {},
'streamSettings': {
'network': 'tcp',
},
'mux': {
'enabled': False,
'concurrency': -1,
},
},
{
'tag': 'direct',
'protocol': 'freedom',
'settings': {},
},
{
'tag': 'block',
'protocol': 'blackhole',
'settings': {
'response': {
'type': 'http',
}
},
},
],
'routing': {},
}
)
HY1_TEMPLATE = constructFromDict(
{
'server': '',
'protocol': 'udp',
'socks5': {
'listen': '127.0.0.1:10808',
},
'http': {
'listen': '127.0.0.1:10809',
},
}
)
HY2_TEMPLATE = constructFromDict(
{
'server': '',
'tls': {
'insecure': False,
},
'socks5': {
'listen': '127.0.0.1:10808',
},
'http': {
'listen': '127.0.0.1:10809',
},
}
)
def getEmptyFactory(protocol: str) -> ConfigurationFactory:
if (
protocolRepr(protocol) == Protocol.VMess
or protocolRepr(protocol) == Protocol.VLESS
):
factory = RAY_TEMPLATE.deepcopy()
factory['outbounds'][0]['protocol'] = protocol.lower()
factory['outbounds'][0]['settings']['vnext'] = [
{
'address': '',
'port': 0,
'users': [{'email': PROXY_OUTBOUND_USER_EMAIL}],
},
]
return factory
if (
protocolRepr(protocol) == Protocol.Shadowsocks
or protocolRepr(protocol) == Protocol.Trojan
):
factory = RAY_TEMPLATE.deepcopy()
factory['outbounds'][0]['protocol'] = protocol.lower()
factory['outbounds'][0]['settings']['servers'] = [
{
'address': '',
'port': 0,
'email': PROXY_OUTBOUND_USER_EMAIL,
},
]
return factory
if protocolRepr(protocol) == Protocol.Hysteria1:
factory = HY1_TEMPLATE.deepcopy()
return factory
if protocolRepr(protocol) == Protocol.Hysteria2:
factory = HY2_TEMPLATE.deepcopy()
return factory
return ConfigurationFactory()
+26 -19
View File
@@ -26,21 +26,29 @@ import ujson
import base64
import pybase64
__all__ = ['JSONEncoder', 'UJSONEncoder', 'Base64Encoder', 'PyBase64Encoder']
__all__ = [
'JSONEncoder',
'UJSONEncoder',
'Base64Encoder',
'PyBase64Encoder',
]
class _JSONEncoder(Encoder):
def encode(self, data: Any, **kwargs) -> str:
class JSONEncoder(EncoderFactory):
@staticmethod
def encode(data: Any, **kwargs) -> str:
ensure_ascii = kwargs.pop('ensure_ascii', False)
return json.dumps(data, ensure_ascii=ensure_ascii, **kwargs)
def decode(self, data: AnyStr, **kwargs) -> Any:
@staticmethod
def decode(data: AnyStr, **kwargs) -> Any:
return json.loads(data, **kwargs)
class _UJSONEncoder(Encoder):
def encode(self, data: Any, **kwargs) -> str:
class UJSONEncoder(EncoderFactory):
@staticmethod
def encode(data: Any, **kwargs) -> str:
ensure_ascii = kwargs.pop('ensure_ascii', False)
escape_forward_slashes = kwargs.pop('escape_forward_slashes', False)
@@ -51,31 +59,30 @@ class _UJSONEncoder(Encoder):
**kwargs,
)
def decode(self, data: AnyStr, **kwargs) -> Any:
@staticmethod
def decode(data: AnyStr, **kwargs) -> Any:
return ujson.loads(data, **kwargs)
class _Base64Encoder(Encoder):
def encode(self, data: Any, **kwargs) -> bytes:
class Base64Encoder(EncoderFactory):
@staticmethod
def encode(data: Any, **kwargs) -> bytes:
return base64.b64encode(data, **kwargs)
def decode(self, data: Any, **kwargs) -> bytes:
@staticmethod
def decode(data: Any, **kwargs) -> bytes:
validate = kwargs.pop('validate', False)
return base64.b64decode(data, validate=validate, **kwargs)
class _PyBase64Encoder(Encoder):
def encode(self, data: Any, **kwargs) -> bytes:
class PyBase64Encoder(EncoderFactory):
@staticmethod
def encode(data: Any, **kwargs) -> bytes:
return pybase64.b64encode(data, **kwargs)
def decode(self, data: Any, **kwargs) -> bytes:
@staticmethod
def decode(data: Any, **kwargs) -> bytes:
validate = kwargs.pop('validate', False)
return pybase64.b64decode(data, validate=validate, **kwargs)
JSONEncoder = _JSONEncoder()
UJSONEncoder = _UJSONEncoder()
Base64Encoder = _Base64Encoder()
PyBase64Encoder = _PyBase64Encoder()
+105
View File
@@ -0,0 +1,105 @@
# Copyright (C) 2024–present Loren Eteval & contributors <loren.eteval@proton.me>
#
# This file is part of Furious.
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <https://www.gnu.org/licenses/>.
from __future__ import annotations
from Furious.Frozenlib import *
from Furious.Interface import *
from Furious.Library.UserServers import UserServers
from Furious.Library.UserSubs import UserSubs
from Furious.Library.UserTUNSettings import UserTUNSettings
from typing import Union
import functools
__all__ = ['Storage']
class Storage:
@staticmethod
def UserActivatedItemIndex() -> int:
try:
return int(AppSettings.get('ActivatedItemIndex'))
except Exception:
# Any non-exit exceptions
return -1
@staticmethod
@functools.lru_cache(None)
def UserServers() -> list[ConfigFactory]:
assert APP() is not None
return UserServers().data()
@staticmethod
@functools.lru_cache(None)
def UserSubs() -> dict[str, dict]:
assert APP() is not None
return UserSubs().data()
@staticmethod
@functools.lru_cache(None)
def UserTUNSettings() -> dict[str, str]:
assert APP() is not None
return UserTUNSettings().data()
class Extras:
@staticmethod
def UserHttpProxy() -> Union[str, None]:
try:
if APP().isSystemTrayConnected():
index, servers = (
Storage.UserActivatedItemIndex(),
Storage.UserServers(),
)
if index >= 0:
return servers[index].httpProxy()
else:
# Should not reach here
return None
else:
return None
except Exception:
# Any non-exit exceptions
return None
@staticmethod
def UserServerRemark() -> Union[str, None]:
try:
if APP().isSystemTrayConnected():
index, servers = (
Storage.UserActivatedItemIndex(),
Storage.UserServers(),
)
if index >= 0:
return f'{index + 1} - ' + servers[index].getExtras('remark')
else:
# Should not reach here
return ''
else:
return ''
except Exception:
# Any non-exit exceptions
return ''
@@ -17,10 +17,10 @@
from __future__ import annotations
from Furious.Frozenlib import *
from Furious.Interface import *
from Furious.PyFramework import *
from Furious.Utility import *
from Furious.Library import *
from Furious.Library.Encoder import *
from Furious.Library.Configuration import configFactoryFromAny
__all__ = ['UserServers']
@@ -33,7 +33,7 @@ class UserServer:
subsId: str
class UserServers(SupportExitCleanup, StorageFactory):
class UserServers(Mixins.CleanupOnExit, StorageFactory):
# remark, config, subsId. (subsId corresponds to unique in user subscription)
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
@@ -50,7 +50,7 @@ class UserServers(SupportExitCleanup, StorageFactory):
self._data = restore()
self._list = list(
constructFromAny(model.pop('config', ''), index=index, **model)
configFactoryFromAny(model.pop('config', ''), index=index, **model)
for index, model in enumerate(self._data['model'])
)
@@ -64,7 +64,7 @@ class UserServers(SupportExitCleanup, StorageFactory):
),
)
def data(self) -> list[ConfigurationFactory]:
def data(self) -> list[ConfigFactory]:
# Shallow copy
return self._list
@@ -17,10 +17,9 @@
from __future__ import annotations
from Furious.Frozenlib import *
from Furious.Interface import *
from Furious.PyFramework import *
from Furious.Utility import *
from Furious.Library import *
from Furious.Library.Encoder import *
__all__ = ['UserSubs']
@@ -38,7 +37,7 @@ class UserSub:
unique: dict[str, dict]
class UserSubs(SupportExitCleanup, StorageFactory):
class UserSubs(Mixins.CleanupOnExit, StorageFactory):
# unique: { remark, webURL }
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
@@ -17,17 +17,16 @@
from __future__ import annotations
from Furious.Frozenlib import *
from Furious.Interface import *
from Furious.PyFramework import *
from Furious.Utility import *
from Furious.Library import *
from Furious.Library.Encoder import *
__all__ = ['UserTUNSettings']
registerAppSettings('CustomTUNSettings')
class UserTUNSettings(SupportExitCleanup, StorageFactory):
class UserTUNSettings(Mixins.CleanupOnExit, StorageFactory):
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
+2 -3
View File
@@ -15,7 +15,6 @@
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <https://www.gnu.org/licenses/>.
from .Configuration import *
from .EmptyFactoryHelper import *
from .Encoder import *
from .Tcping import *
from .Storage import *
from .Configuration import *
-157
View File
@@ -1,157 +0,0 @@
# Copyright (C) 2024–present Loren Eteval & contributors <loren.eteval@proton.me>
#
# This file is part of Furious.
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <https://www.gnu.org/licenses/>.
from __future__ import annotations
from PySide6.QtWidgets import QApplication
from typing import Any
import logging
logger = logging.getLogger(__name__)
__all__ = [
'Translatable',
'SupportConnectedCallback',
'SupportThemeChangedCallback',
'SupportExitCleanup',
]
class Translatable:
ObjectsPool = list()
def __init__(self, *args, **kwargs):
self.translatable = kwargs.pop('translatable', True)
super().__init__(*args, **kwargs)
Translatable.ObjectsPool.append(self)
def retranslate(self):
raise NotImplementedError
@staticmethod
def retranslateAll():
for object_ in Translatable.ObjectsPool:
assert isinstance(object_, Translatable)
if object_.translatable:
object_.retranslate()
class SupportConnectedCallback:
ObjectsPool = list()
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
SupportConnectedCallback.ObjectsPool.append(self)
def disconnectedCallback(self):
raise NotImplementedError
def connectedCallback(self):
raise NotImplementedError
@staticmethod
def callConnectedCallback():
for object_ in SupportConnectedCallback.ObjectsPool:
assert isinstance(object_, SupportConnectedCallback)
object_.connectedCallback()
@staticmethod
def callDisconnectedCallback():
for object_ in SupportConnectedCallback.ObjectsPool:
assert isinstance(object_, SupportConnectedCallback)
object_.disconnectedCallback()
class SupportThemeChangedCallback:
ObjectsPool = list()
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
SupportThemeChangedCallback.ObjectsPool.append(self)
def themeChangedCallback(self, theme: str):
raise NotImplementedError
@staticmethod
def callThemeChangedCallbackUnchecked(theme: str):
for object_ in SupportThemeChangedCallback.ObjectsPool:
assert isinstance(object_, SupportThemeChangedCallback)
object_.themeChangedCallback(theme)
@staticmethod
def callThemeChangedCallback(theme: str):
try:
app = QApplication.instance()
if app is not None and app.isDarkModeEnabled():
# Ignore application dark detect system
logger.info(f'ignore system theme \'{theme}\' changes in dark mode')
return
except Exception:
# Any non-exit exceptions
pass
logger.info(f'system theme changed to \'{theme}\'')
SupportThemeChangedCallback.callThemeChangedCallbackUnchecked(theme)
class SupportExitCleanup:
ObjectsPool = list()
VisitedType = dict()
def __init__(self, *args, **kwargs):
self.uniqueCleanup = kwargs.pop('uniqueCleanup', True)
super().__init__(*args, **kwargs)
SupportExitCleanup.ObjectsPool.append(self)
def cleanup(self):
raise NotImplementedError
@staticmethod
def cleanupAll():
for object_ in SupportExitCleanup.ObjectsPool:
assert isinstance(object_, SupportExitCleanup)
if object_.uniqueCleanup:
obtype = str(type(object_))
if not SupportExitCleanup.VisitedType.get(obtype, False):
object_.cleanup()
SupportExitCleanup.VisitedType[obtype] = True
else:
pass
else:
object_.cleanup()
SupportExitCleanup.ObjectsPool.clear()
SupportExitCleanup.VisitedType.clear()
@@ -17,9 +17,9 @@
from __future__ import annotations
from Furious.QtFramework.WebGETManager import *
from Furious.Utility import *
from Furious.Frozenlib import *
from Furious.Library import *
from Furious.Qt.WebGETManager import *
from PySide6 import QtCore
from PySide6.QtNetwork import *
@@ -134,7 +134,7 @@ class _DNSResolver(WebGETManager):
return
while resultMap['depth'] != 0 and startCounter < timeout:
PySide6LegacyEventLoopWait(step)
PySide6Legacy.eventLoopWait(step)
startCounter += step
@@ -17,41 +17,37 @@
from __future__ import annotations
from Furious.QtFramework.QtGui import bootstrapIcon, AppQIcon
from Furious.Frozenlib import *
from Furious.Qt.QtGui import *
from Furious.Utility import *
import functools
__all__ = ['ColorRGB', 'AppHue']
class ColorRGB:
LIGHT_BLUE = '#43ACED'
LIGHT_RED = '#FF7276'
LIGHT_PURPLE = '#DA70D6'
__all__ = ['AppHue']
class AppHue:
class ColorRGB:
LIGHT_BLUE = '#43ACED'
LIGHT_RED = '#FF7276'
LIGHT_PURPLE = '#DA70D6'
@staticmethod
def disconnectedColor() -> str:
return ColorRGB.LIGHT_BLUE
return AppHue.ColorRGB.LIGHT_BLUE
@staticmethod
def disconnectedWindowIcon() -> AppQIcon:
return bootstrapIcon('rocket-takeoff-window.svg')
@staticmethod
@functools.lru_cache(None)
def connectedColor() -> str:
if not isAdministrator():
return ColorRGB.LIGHT_RED
if not SystemRuntime.isAdmin():
return AppHue.ColorRGB.LIGHT_RED
else:
return ColorRGB.LIGHT_PURPLE
return AppHue.ColorRGB.LIGHT_PURPLE
@staticmethod
@functools.lru_cache(None)
def connectedWindowIcon() -> AppQIcon:
if not isAdministrator():
if not SystemRuntime.isAdmin():
return bootstrapIcon('rocket-takeoff-connected-dark.svg')
else:
return bootstrapIcon('rocket-takeoff-admin-connected.svg')
@@ -17,12 +17,9 @@
from __future__ import annotations
from Furious.Utility import *
from Furious.Library import *
from Furious.Frozenlib import *
from Furious.Externals import *
import functools
__all__ = [
'gettext',
'LANGUAGE_TO_ABBR',
@@ -17,10 +17,10 @@
from __future__ import annotations
from Furious.Frozenlib import *
from Furious.Interface import *
from Furious.QtFramework.DynamicTranslate import gettext as _
from Furious.QtFramework.QtWidgets import *
from Furious.Utility import *
from Furious.Qt.DynamicTranslate import gettext as _
from Furious.Qt.QtWidgets import *
from PySide6 import QtCore
from PySide6.QtWidgets import *
@@ -137,7 +137,7 @@ class GuiEditorItemBasicRemark(GuiEditorItemTextInput):
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
def inputToFactory(self, config: ConfigurationFactory) -> bool:
def inputToFactory(self, config: ConfigFactory) -> bool:
oldRemark = config.getExtras('remark')
newRemark = self.text()
@@ -150,7 +150,7 @@ class GuiEditorItemBasicRemark(GuiEditorItemTextInput):
# Not modified
return False
def factoryToInput(self, config: ConfigurationFactory):
def factoryToInput(self, config: ConfigFactory):
self.setText(config.getExtras('remark'))
@@ -158,33 +158,33 @@ class GuiEditorItemProxyHttp(GuiEditorItemTextInput):
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
def inputToFactory(self, config: ConfigurationFactory) -> bool:
oldHttp = config.httpProxyEndpoint()
def inputToFactory(self, config: ConfigFactory) -> bool:
oldHttp = config.httpProxy()
newHttp = self.text()
if newHttp == '':
if oldHttp == '':
return False
else:
config.setHttpProxyEndpoint(newHttp)
config.setHttpProxy(newHttp)
return True
else:
if isinstance(oldHttp, str):
if newHttp != oldHttp:
config.setHttpProxyEndpoint(newHttp)
config.setHttpProxy(newHttp)
return True
else:
return False
else:
config.setHttpProxyEndpoint(newHttp)
config.setHttpProxy(newHttp)
return True
def factoryToInput(self, config: ConfigurationFactory):
def factoryToInput(self, config: ConfigFactory):
try:
self.setText(config.httpProxyEndpoint())
self.setText(config.httpProxy())
except Exception:
# Any non-exit exceptions
@@ -195,33 +195,33 @@ class GuiEditorItemProxySocks(GuiEditorItemTextInput):
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
def inputToFactory(self, config: ConfigurationFactory) -> bool:
oldSocks = config.socksProxyEndpoint()
def inputToFactory(self, config: ConfigFactory) -> bool:
oldSocks = config.socksProxy()
newSocks = self.text()
if newSocks == '':
if oldSocks == '':
return False
else:
config.setSocksProxyEndpoint(newSocks)
config.setSocksProxy(newSocks)
return True
else:
if isinstance(oldSocks, str):
if newSocks != oldSocks:
config.setSocksProxyEndpoint(newSocks)
config.setSocksProxy(newSocks)
return True
else:
return False
else:
config.setSocksProxyEndpoint(newSocks)
config.setSocksProxy(newSocks)
return True
def factoryToInput(self, config: ConfigurationFactory):
def factoryToInput(self, config: ConfigFactory):
try:
self.setText(config.socksProxyEndpoint())
self.setText(config.socksProxy())
except Exception:
# Any non-exit exceptions
@@ -237,7 +237,7 @@ class GuiEditorWidget(GuiEditorItemFactory):
def containerSequence(self) -> Sequence[GuiEditorItemWidgetContainer]:
raise NotImplementedError
def inputToFactory(self, config: ConfigurationFactory) -> bool:
def inputToFactory(self, config: ConfigFactory) -> bool:
modified = False
for container in self._containers:
@@ -250,7 +250,7 @@ class GuiEditorWidget(GuiEditorItemFactory):
return modified
def factoryToInput(self, config: ConfigurationFactory):
def factoryToInput(self, config: ConfigFactory):
for container in self._containers:
try:
container.factoryToInput(config)
@@ -358,7 +358,7 @@ class GuiEditorWidgetQDialog(GuiEditorItemFactory, AppQDialog):
self.accepted.disconnect()
self.rejected.disconnect()
def inputToFactory(self, config: ConfigurationFactory) -> bool:
def inputToFactory(self, config: ConfigFactory) -> bool:
modified = False
for groupBox in self.groupBoxes:
@@ -366,6 +366,6 @@ class GuiEditorWidgetQDialog(GuiEditorItemFactory, AppQDialog):
return modified
def factoryToInput(self, config: ConfigurationFactory):
def factoryToInput(self, config: ConfigFactory):
for groupBox in self.groupBoxes:
groupBox.factoryToInput(config)
@@ -17,9 +17,8 @@
from __future__ import annotations
from Furious.PyFramework import *
from Furious.QtFramework.WebGETManager import *
from Furious.Utility import *
from Furious.Frozenlib import *
from Furious.Qt.WebGETManager import *
from PySide6 import QtCore
from PySide6.QtNetwork import *
@@ -32,7 +31,7 @@ __all__ = ['NetworkStateManager']
logger = logging.getLogger(__name__)
class NetworkStateManager(SupportConnectedCallback, WebGETManager):
class NetworkStateManager(Mixins.ConnectionAware, WebGETManager):
MIN_JOB_INTERVAL = 2500
MAX_JOB_INTERVAL = 2000000000
@@ -15,9 +15,8 @@
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <https://www.gnu.org/licenses/>.
from Furious.QtFramework.Ancestors import QTranslatable
from Furious.QtFramework.DynamicTranslate import gettext as _
from Furious.PyFramework.Ancestors import *
from Furious.Frozenlib import *
from Furious.Qt.DynamicTranslate import gettext as _
from Furious.Utility import *
from PySide6 import QtCore
@@ -58,7 +57,7 @@ bootstrapIcon = functools.partial(iconFn, 'bootstrap')
bootstrapIconWhite = functools.partial(iconFn, 'bootstrap/white')
class AppQAction(QTranslatable, SupportThemeChangedCallback, QAction):
class AppQAction(Mixins.QTranslatable, Mixins.ThemeAware, QAction):
def __init__(
self,
text,
@@ -184,7 +183,7 @@ class AppQAction(QTranslatable, SupportThemeChangedCallback, QAction):
# PySide6 has system theme handling.
super().setIcon(bootstrapIconWhite(self.iconFileName))
else:
if getUbuntuRelease() == '20.04' and self.isTrayAction:
if SystemRuntime.ubuntuRelease() == '20.04' and self.isTrayAction:
# Ubuntu 20.04 system dark theme does not change tray menu color.
# Make it go black always
super().setIcon(bootstrapIcon(self.iconFileName))
@@ -17,7 +17,7 @@
from __future__ import annotations
from Furious.Utility import *
from Furious.Frozenlib import *
from PySide6.QtNetwork import *
@@ -17,20 +17,16 @@
from __future__ import annotations
from Furious.QtFramework.Ancestors import *
from Furious.QtFramework.DynamicTheme import AppHue
from Furious.QtFramework.DynamicTranslate import gettext as _
from Furious.QtFramework.QtGui import AppQAction, AppQSeperator
from Furious.PyFramework import *
from Furious.Utility import *
from Furious.Frozenlib import *
from Furious.Library import *
from Furious.Qt.DynamicTheme import *
from Furious.Qt.DynamicTranslate import gettext as _
from Furious.Qt.QtGui import *
from PySide6 import QtCore
from PySide6.QtGui import *
from PySide6.QtWidgets import *
import functools
from typing import Union
__all__ = [
@@ -72,7 +68,7 @@ def moveToCenter(widget, parent=None):
widget.move(geometry.topLeft())
class AppQCheckBox(QTranslatable, QCheckBox):
class AppQCheckBox(Mixins.QTranslatable, QCheckBox):
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
@@ -80,7 +76,7 @@ class AppQCheckBox(QTranslatable, QCheckBox):
self.setText(_(self.text()))
class AppQComboBox(QTranslatable, QComboBox):
class AppQComboBox(Mixins.QTranslatable, QComboBox):
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
@@ -89,7 +85,7 @@ class AppQComboBox(QTranslatable, QComboBox):
self.setItemText(index, _(self.itemText(index)))
class AppQDialog(QTranslatable, SupportConnectedCallback, QDialog):
class AppQDialog(Mixins.QTranslatable, Mixins.ConnectionAware, QDialog):
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
@@ -134,7 +130,7 @@ class AppQDialog(QTranslatable, SupportConnectedCallback, QDialog):
self.setWindowIcon(AppHue.connectedWindowIcon())
class AppQDialogButtonBox(QTranslatable, QDialogButtonBox):
class AppQDialogButtonBox(Mixins.QTranslatable, QDialogButtonBox):
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
@@ -143,7 +139,7 @@ class AppQDialogButtonBox(QTranslatable, QDialogButtonBox):
button.setText(_(button.text()))
class AppQGroupBox(QTranslatable, QGroupBox):
class AppQGroupBox(Mixins.QTranslatable, QGroupBox):
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
@@ -151,7 +147,7 @@ class AppQGroupBox(QTranslatable, QGroupBox):
self.setTitle(_(self.title()))
class AppQHeaderView(SupportExitCleanup, SupportConnectedCallback, QHeaderView):
class AppQHeaderView(Mixins.CleanupOnExit, Mixins.ConnectionAware, QHeaderView):
def sectionSizeSettingsEmpty(self):
return (
self.legacySectionSizeSettingsName == ''
@@ -175,7 +171,7 @@ class AppQHeaderView(SupportExitCleanup, SupportConnectedCallback, QHeaderView):
self.setSectionsClickable(True)
self.setStyleSheet(self.getStyleSheet(AppHue.currentColor()))
self.setFont(QFont(APP().customFontName))
self.setFont(QFont(AppFontName()))
# self.sectionResized.connect(self.handleSectionResized)
@@ -209,7 +205,7 @@ class AppQHeaderView(SupportExitCleanup, SupportConnectedCallback, QHeaderView):
if self.sectionSizeTable.get(str(column)) is None:
self.sectionSizeTable[str(column)] = self.defaultSectionSize()
with QBlockSignals(self):
with Mixins.QBlockSignalContext(self):
for key, value in reversed(self.sectionSizeTable.items()):
self.resizeSection(int(key), value)
except Exception:
@@ -261,7 +257,7 @@ class AppQHeaderView(SupportExitCleanup, SupportConnectedCallback, QHeaderView):
AppSettings.set(self.sectionSizeSettingsName, self.saveState())
class AppQLabel(QTranslatable, QLabel):
class AppQLabel(Mixins.QTranslatable, QLabel):
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
@@ -269,7 +265,7 @@ class AppQLabel(QTranslatable, QLabel):
self.setText(_(self.text()))
class AppQListWidget(SupportConnectedCallback, QListWidget):
class AppQListWidget(Mixins.ConnectionAware, QListWidget):
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
@@ -298,9 +294,9 @@ class AppQListWidget(SupportConnectedCallback, QListWidget):
class AppQMainWindow(
QTranslatable,
SupportConnectedCallback,
SupportExitCleanup,
Mixins.QTranslatable,
Mixins.ConnectionAware,
Mixins.CleanupOnExit,
QMainWindow,
):
def __init__(self, *args, **kwargs):
@@ -349,7 +345,7 @@ class AppQMainWindow(
pass
class AppQMenu(QTranslatable, SupportConnectedCallback, QMenu):
class AppQMenu(Mixins.QTranslatable, Mixins.ConnectionAware, QMenu):
def __init__(self, *actions, **kwargs):
super().__init__(**kwargs)
@@ -398,7 +394,7 @@ class AppQMenu(QTranslatable, SupportConnectedCallback, QMenu):
self.setStyleSheet(self.getStyleSheet(AppHue.connectedColor()))
class AppQMenuBar(SupportConnectedCallback, QMenuBar):
class AppQMenuBar(Mixins.ConnectionAware, QMenuBar):
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
@@ -426,7 +422,7 @@ class AppQMenuBar(SupportConnectedCallback, QMenuBar):
self.setSelectionColor(AppHue.connectedColor())
class AppQMessageBox(QTranslatable, SupportConnectedCallback, QMessageBox):
class AppQMessageBox(Mixins.QTranslatable, Mixins.ConnectionAware, QMessageBox):
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
@@ -475,7 +471,7 @@ class AppQMessageBox(QTranslatable, SupportConnectedCallback, QMessageBox):
self.setWindowIcon(AppHue.connectedWindowIcon())
class AppQPushButton(QTranslatable, QPushButton):
class AppQPushButton(Mixins.QTranslatable, QPushButton):
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
@@ -483,7 +479,7 @@ class AppQPushButton(QTranslatable, QPushButton):
self.setText(_(self.text()))
class AppQTableWidget(SupportConnectedCallback, QTableWidget):
class AppQTableWidget(Mixins.ConnectionAware, QTableWidget):
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
@@ -552,7 +548,7 @@ class AppQTableWidget(SupportConnectedCallback, QTableWidget):
self.setSelectionColor(AppHue.connectedColor())
class AppQTabWidget(QTranslatable, QTabWidget):
class AppQTabWidget(Mixins.QTranslatable, QTabWidget):
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
@@ -561,7 +557,7 @@ class AppQTabWidget(QTranslatable, QTabWidget):
self.setTabText(index, _(self.tabText(index)))
class AppQToolBar(QTranslatable, QToolBar):
class AppQToolBar(Mixins.QTranslatable, QToolBar):
def __init__(self, *actions, **kwargs):
super().__init__(**kwargs)
@@ -17,9 +17,8 @@
from __future__ import annotations
from Furious.PyFramework import *
from Furious.QtFramework.TextEditorTheme import *
from Furious.Utility import *
from Furious.Frozenlib import *
from Furious.Qt.TextEditorTheme import *
from PySide6 import QtCore
from PySide6.QtGui import *
@@ -38,7 +37,7 @@ __all__ = [
]
class SupportPointSizeSettings(SupportExitCleanup):
class SupportPointSizeSettings(Mixins.CleanupOnExit):
def pointSizeSettingsEmpty(self):
return self.pointSizeSettingsName == ''
@@ -17,18 +17,16 @@
from __future__ import annotations
from Furious.QtFramework.QtNetwork import *
from Furious.QtFramework.QtWidgets import *
from Furious.QtFramework.DynamicTranslate import gettext as _
from Furious.QtFramework.WebGETManager import *
from Furious.Utility import *
from Furious.Frozenlib import *
from Furious.Library import *
from Furious.Qt.QtWidgets import *
from Furious.Qt.DynamicTranslate import gettext as _
from Furious.Qt.WebGETManager import *
from PySide6 import QtCore
from PySide6.QtGui import *
from PySide6.QtNetwork import *
from typing import Union, Callable
from typing import Callable
import logging
import functools
@@ -103,7 +101,7 @@ class UpdatesManager(WebGETManager):
if versionToValue(newVersion) > versionToValue(APPLICATION_VERSION):
def handleResultCode(code):
if code == PySide6LegacyEnumValueWrapper(
if code == PySide6Legacy.enumValueWrapper(
AppQMessageBox.StandardButton.Yes
):
if QDesktopServices.openUrl(QtCore.QUrl(info['html_url'])):
@@ -17,17 +17,13 @@
from __future__ import annotations
from Furious.QtFramework.QtNetwork import *
from Furious.QtFramework.QtWidgets import *
from Furious.QtFramework.DynamicTranslate import gettext as _
from Furious.Utility import *
from Furious.Library import *
from Furious.Frozenlib import *
from Furious.Qt.QtNetwork import *
from PySide6 import QtCore
from PySide6.QtGui import *
from PySide6.QtNetwork import *
from typing import AnyStr, Union, Callable
from typing import Union
import logging
import functools
@@ -71,9 +67,8 @@ class WebGETManager(AppQNetworkAccessManager):
if not self.mustCallOnce:
call()
else:
if not self.mustCalled:
call()
elif not self.mustCalled:
call()
def handleReadyReadByNetworkReply(self, networkReply: QNetworkReply, **kwargs):
self.hasDataCallback(networkReply, **kwargs)
@@ -17,16 +17,10 @@
from __future__ import annotations
from Furious.QtFramework.QtNetwork import *
from Furious.QtFramework.QtWidgets import *
from Furious.QtFramework.DynamicTranslate import gettext as _
from Furious.QtFramework.WebGETManager import *
from Furious.Utility import *
from Furious.Library import *
from Furious.Frozenlib import *
from Furious.Qt.WebGETManager import *
from PySide6 import QtCore
from PySide6.QtGui import *
from PySide6.QtNetwork import *
from typing import AnyStr, Union, Callable
@@ -15,18 +15,16 @@
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <https://www.gnu.org/licenses/>.
from .Ancestors import *
from .DNSResolver import *
from .CoreProcess import *
from .DynamicTheme import *
from .DynamicTranslate import *
from .QtGui import *
from .QtWidgets import *
from .QtNetwork import *
from .UpdatesManager import *
from .GuiEditorXXX import *
from .NetworkStateManager import *
from .QtGui import *
from .QtNetwork import *
from .QtWidgets import *
from .TextEditor import *
from .TextEditorTheme import *
from .GuiEditorXXX import *
from .UpdatesManager import *
from .WebGETManager import *
from .XrayAssetDownloadManager import *
-74
View File
@@ -1,74 +0,0 @@
# Copyright (C) 2024–present Loren Eteval & contributors <loren.eteval@proton.me>
#
# This file is part of Furious.
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <https://www.gnu.org/licenses/>.
from __future__ import annotations
from Furious.PyFramework import *
from PySide6 import QtCore
__all__ = ['QBlockSignals', 'QTranslatable']
class QProtection:
def __init__(self, qobject: QtCore.QObject):
self.qobject = qobject
def __enter__(self):
if hasattr(self.qobject, 'setDisabled'):
self.qobject.setDisabled(True)
def __exit__(self, exceptionType, exceptionValue, tb):
if hasattr(self.qobject, 'setDisabled'):
self.qobject.setDisabled(False)
class QBlockSignals:
def __init__(self, qobject: QtCore.QObject):
self.qobject = qobject
def __enter__(self):
if hasattr(self.qobject, 'blockSignals'):
self.qobject.blockSignals(True)
def __exit__(self, exceptionType, exceptionValue, tb):
if hasattr(self.qobject, 'blockSignals'):
self.qobject.blockSignals(False)
class QTranslatable(Translatable):
def __init__(self, *args, **kwargs):
self.useQProtection = kwargs.pop('useQProtection', True)
super().__init__(*args, **kwargs)
def retranslate(self):
raise NotImplementedError
@staticmethod
def retranslateAll():
for ob in QTranslatable.ObjectsPool:
assert isinstance(ob, QTranslatable)
if ob.translatable:
if ob.useQProtection:
assert isinstance(ob, QtCore.QObject)
with QProtection(ob):
ob.retranslate()
else:
ob.retranslate()
+23 -23
View File
@@ -17,12 +17,12 @@
from __future__ import annotations
from Furious.Frozenlib import *
from Furious.Interface import *
from Furious.PyFramework import *
from Furious.QtFramework import *
from Furious.QtFramework import gettext as _
from Furious.Utility import *
from Furious.Library import *
from Furious.Qt import *
from Furious.Qt import gettext as _
from Furious.Utility import *
from Furious.Core import *
from Furious.Widget.ConnectProgressBar import ConnectProgressBar
@@ -107,7 +107,7 @@ class ConnectAction(AppQAction):
APP().systemTray.RoutingAction.setDisabled(value)
APP().systemTray.SystemProxyAction.setDisabled(value)
if isAdministrator():
if SystemRuntime.isAdmin():
TUNModeAction = APP().systemTray.SettingsAction.getTUNModeAction()
if TUNModeAction is not None:
@@ -133,7 +133,7 @@ class ConnectAction(AppQAction):
AppSettings.turnON_('Connect')
SupportConnectedCallback.callConnectedCallback()
Mixins.ConnectionAware.callConnectedCallback()
# Accept new action
self.setDisabledAction(False)
@@ -154,7 +154,7 @@ class ConnectAction(AppQAction):
pass
SupportConnectedCallback.callDisconnectedCallback()
Mixins.ConnectionAware.callDisconnectedCallback()
def doDisconnectWithTrayMessage(self, message: str):
self.doDisconnect()
@@ -165,7 +165,7 @@ class ConnectAction(AppQAction):
# Connect action
assert self.textCompare('Connect')
if not AS_UserServers():
if not Storage.UserServers():
AppSettings.turnOFF('Connect')
self.setChecked(False)
@@ -181,7 +181,7 @@ class ConnectAction(AppQAction):
return
if AS_UserActivatedItemIndex() < 0:
if Storage.UserActivatedItemIndex() < 0:
AppSettings.turnOFF('Connect')
self.setChecked(False)
@@ -198,7 +198,7 @@ class ConnectAction(AppQAction):
return
try:
config = AS_UserServers()[AS_UserActivatedItemIndex()]
config = Storage.UserServers()[Storage.UserActivatedItemIndex()]
except Exception:
# Any non-exit exceptions
@@ -206,9 +206,9 @@ class ConnectAction(AppQAction):
self.setChecked(False)
else:
assert isinstance(config, ConfigurationFactory)
assert isinstance(config, ConfigFactory)
if not validateProxyServer(config.httpProxyEndpoint()):
if not validateProxyServer(config.httpProxy()):
# Proxy server is not valid. Do not connect
AppSettings.turnOFF('Connect')
@@ -232,20 +232,20 @@ class ConnectAction(AppQAction):
self.doConnecting()
# Clear previous log
loggerCore().clear()
loggerTun_().clear()
AppLoggerWindow.Core().clear()
AppLoggerWindow.TUN_().clear()
success = self.coreManager.start(
config,
routing=AppSettings.get('Routing'),
exitCallback=self.coreExitCallback,
msgCallbackCore=lambda line: loggerCore().appendLine(line),
msgCallbackTun_=lambda line: loggerTun_().appendLine(line),
msgCallbackCore=lambda line: AppLoggerWindow.Core().appendLine(line),
msgCallbackTUN_=lambda line: AppLoggerWindow.TUN_().appendLine(line),
)
if self.actionQueue.empty():
if success:
SystemProxy.set(config.httpProxyEndpoint(), PROXY_SERVER_BYPASS)
SystemProxy.set(config.httpProxy(), PROXY_SERVER_BYPASS)
self.doConnected()
@@ -280,7 +280,7 @@ class ConnectAction(AppQAction):
f'{APPLICATION_NAME} {newVersion} ' + _('is available to download')
)
connectedHttpProxy = connectedHttpProxyEndpoint()
connectedHttpProxy = Storage.Extras.UserHttpProxy()
# Check for updates
self.updatesManager.configureHttpProxy(connectedHttpProxy)
@@ -308,7 +308,7 @@ class ConnectAction(AppQAction):
if callable(action):
action()
def coreExitCallback(self, core: CoreFactory, exitcode: int):
def coreExitCallback(self, core: CoreProcessFactory, exitcode: int):
def putItem(item):
try:
self.actionQueue.put_nowait(item)
@@ -317,11 +317,11 @@ class ConnectAction(AppQAction):
pass
if exitcode == CoreFactory.ExitCode.SystemShuttingDown:
if exitcode == CoreProcessFactory.ExitCode.SystemShuttingDown.value:
# System shutting down. Do nothing
return
if exitcode == CoreFactory.ExitCode.ConfigurationError:
if exitcode == CoreProcessFactory.ExitCode.ConfigurationError.value:
return putItem(
functools.partial(
self.doDisconnectWithTrayMessage,
@@ -329,7 +329,7 @@ class ConnectAction(AppQAction):
)
)
if exitcode == CoreFactory.ExitCode.ServerStartFailure:
if exitcode == CoreProcessFactory.ExitCode.ServerStartFailure.value:
return putItem(
functools.partial(
self.doDisconnectWithTrayMessage,
@@ -338,7 +338,7 @@ class ConnectAction(AppQAction):
)
if isinstance(core, Hysteria1):
if exitcode == Hysteria1.ExitCode.RemoteNetworkError:
if exitcode == Hysteria1.ExitCode.RemoteNetworkError.value:
return putItem(
functools.partial(
self.doDisconnectWithTrayMessage,
+3 -5
View File
@@ -15,11 +15,9 @@
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <https://www.gnu.org/licenses/>.
from Furious.QtFramework import *
from Furious.QtFramework import gettext as _
from Furious.Utility import *
import functools
from Furious.Frozenlib import *
from Furious.Qt import *
from Furious.Qt import gettext as _
__all__ = ['EditConfigurationAction']
+3 -5
View File
@@ -15,11 +15,9 @@
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <https://www.gnu.org/licenses/>.
from Furious.QtFramework import *
from Furious.QtFramework import gettext as _
from Furious.Utility import *
import functools
from Furious.Frozenlib import *
from Furious.Qt import *
from Furious.Qt import gettext as _
__all__ = ['ExitAction']
+9 -9
View File
@@ -15,17 +15,15 @@
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <https://www.gnu.org/licenses/>.
from Furious.Interface import *
from Furious.QtFramework import *
from Furious.QtFramework import gettext as _
from Furious.Frozenlib import *
from Furious.Library import *
from Furious.Utility import *
from Furious.Qt import *
from Furious.Qt import gettext as _
from PySide6.QtWidgets import QApplication, QFileDialog
import os
import logging
import functools
__all__ = [
'ImportFromFileAction',
@@ -53,7 +51,7 @@ def showImportErrorMBox(clipboard: str):
def importItemFromClipboard(clipboard: str):
factory = constructFromAny(clipboard)
factory = configFactoryFromAny(clipboard)
if not factory.isValid():
showImportErrorMBox(clipboard)
@@ -174,7 +172,9 @@ class ImportFromFileAction(AppQAction):
# Show the MessageBox asynchronously
mbox.open()
else:
factory = constructFromAny(plainText, remark=os.path.basename(filename))
factory = configFactoryFromAny(
plainText, remark=os.path.basename(filename)
)
if factory.isValid():
APP().mainWindow.appendNewItemByFactory(factory)
@@ -209,10 +209,10 @@ class ImportURIFromClipboardAction(AppQAction):
importItemFromClipboard(clipboard)
else:
imported = list()
rowCount = len(AS_UserServers())
rowCount = len(Storage.UserServers())
for uri in split:
factory = constructFromAny(uri)
factory = configFactoryFromAny(uri)
if factory.isValid():
APP().mainWindow.appendNewItemByFactory(factory)
+4 -5
View File
@@ -15,12 +15,11 @@
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <https://www.gnu.org/licenses/>.
from Furious.QtFramework import *
from Furious.QtFramework import gettext as _
from Furious.Utility import *
from Furious.Frozenlib import *
from Furious.Qt import *
from Furious.Qt import gettext as _
import logging
import functools
__all__ = ['LanguageAction']
@@ -48,7 +47,7 @@ class LanguageChildAction(AppQAction):
AppSettings.set('Language', abbr)
QTranslatable.retranslateAll()
Mixins.QTranslatable.retranslateAll()
def retranslate(self):
# Nothing to do
+3 -5
View File
@@ -15,11 +15,9 @@
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <https://www.gnu.org/licenses/>.
from Furious.QtFramework import *
from Furious.QtFramework import gettext as _
from Furious.Utility import *
import functools
from Furious.Frozenlib import *
from Furious.Qt import *
from Furious.Qt import gettext as _
__all__ = ['RoutingAction']
+8 -9
View File
@@ -15,14 +15,13 @@
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <https://www.gnu.org/licenses/>.
from Furious.QtFramework import *
from Furious.QtFramework import gettext as _
from Furious.Frozenlib import *
from Furious.Qt import *
from Furious.Qt import gettext as _
from Furious.Utility import *
from typing import Union
import functools
__all__ = ['SettingsAction']
registerAppSettings('VPNMode', isBinary=True)
@@ -32,10 +31,10 @@ registerAppSettings('UseMonochromeTrayIcon', isBinary=True)
if PLATFORM == 'Darwin':
registerAppSettings('HideDockIcon', isBinary=True)
registerAppSettings('StartupOnBoot', isBinary=True, default=BinarySettings.ON_)
registerAppSettings('PowerSaveMode', isBinary=True, default=BinarySettings.ON_)
registerAppSettings('StartupOnBoot', isBinary=True, default=AppBinarySettings.ON_)
registerAppSettings('PowerSaveMode', isBinary=True, default=AppBinarySettings.ON_)
registerAppSettings(
'ShowProgressBarWhenConnecting', isBinary=True, default=BinarySettings.ON_
'ShowProgressBarWhenConnecting', isBinary=True, default=AppBinarySettings.ON_
)
registerAppSettings('ShowTabAndSpacesInEditor', isBinary=True)
@@ -48,7 +47,7 @@ TRANSLATABLE_TUN_MODE = [
class TUNModeAction(AppQAction):
def __init__(self, **kwargs):
if isAdministrator():
if SystemRuntime.isAdmin():
super().__init__(_('TUN Mode'), **kwargs)
else:
super().__init__(_(f'TUN Mode Disabled ({ADMINISTRATOR_NAME})'), **kwargs)
@@ -56,7 +55,7 @@ class TUNModeAction(AppQAction):
self.setDisabled(True)
def triggeredCallback(self, checked):
assert isAdministrator()
assert SystemRuntime.isAdmin()
if checked:
AppSettings.turnON_('VPNMode')
+3 -5
View File
@@ -15,11 +15,9 @@
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <https://www.gnu.org/licenses/>.
from Furious.QtFramework import *
from Furious.QtFramework import gettext as _
from Furious.Utility import *
import functools
from Furious.Frozenlib import *
from Furious.Qt import *
from Furious.Qt import gettext as _
__all__ = ['SystemProxyAction']
+7 -7
View File
@@ -17,8 +17,8 @@
from __future__ import annotations
from Furious.Frozenlib import *
from Furious.Interface import *
from Furious.Utility.Constants import *
from typing import Callable
@@ -43,14 +43,14 @@ else:
class AppMainProcess(ProcessContext.Process):
def __init__(self, loader: Callable[[], ApplicationFactory], **kwargs):
def __init__(self, func: Callable[[], ApplicationFactory], **kwargs):
super().__init__(**kwargs)
self.startupTime = str(datetime.datetime.now()).replace(':', '')
self.logFileName = f'{self.startupTime}.log'
self.fileWritten = multiprocessing.Manager().Value('b', False)
self.loader = loader
self.func = func
self.application = None
def exceptHook(self, exceptionType, exceptionValue, tb):
@@ -58,9 +58,9 @@ class AppMainProcess(ProcessContext.Process):
traceback.print_exception(exceptionType, exceptionValue, tb)
if isinstance(exceptionValue, AssertionError):
exitcode = ApplicationFactory.ExitCode.AssertionError
exitcode = ApplicationFactory.ExitCode.AssertionError.value
else:
exitcode = ApplicationFactory.ExitCode.UnknownException
exitcode = ApplicationFactory.ExitCode.UnknownException.value
logger.error(f'stopped with exitcode {exitcode}')
@@ -92,7 +92,7 @@ class AppMainProcess(ProcessContext.Process):
if APP() is None:
crashLog = f'{stackLog}'
else:
crashLog = f'{APP().log()}\n{stackLog}'
crashLog = f'{AppLoggerWindow.Self().plainText()}\n{stackLog}'
with open(CRASH_LOG_DIR / self.logFileName, 'w', encoding='utf-8') as file:
file.write(crashLog)
@@ -111,7 +111,7 @@ class AppMainProcess(ProcessContext.Process):
def run(self):
sys.excepthook = self.exceptHook
self.application = self.loader()
self.application = self.func()
for sig in [signal.SIGTERM, signal.SIGINT]:
signal.signal(sig, self.handler)
-112
View File
@@ -1,112 +0,0 @@
# Copyright (C) 2024–present Loren Eteval & contributors <loren.eteval@proton.me>
#
# This file is part of Furious.
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <https://www.gnu.org/licenses/>.
from __future__ import annotations
from Furious.Utility.Constants import *
from Furious.Utility.AppSettings import *
from typing import Union
import functools
__all___ = [
'AS_UserActivatedItemIndex',
'AS_UserServers',
'AS_UserSubscription',
'AS_UserTUNSettings',
'connectedHttpProxyEndpoint',
'connectedServerRemark',
]
def _activatedItemIndex() -> int:
try:
return int(AppSettings.get('ActivatedItemIndex'))
except Exception:
# Any non-exit exceptions
return -1
def _userServersData() -> list:
try:
return APP().userServers.data()
except Exception:
# Any non-exit exceptions
return []
def _userSubscriptionData() -> dict[str, dict]:
try:
return APP().userSubs.data()
except Exception:
# Any non-exit exceptions
return {}
def _userTUNSettingsData() -> dict[str, str]:
try:
return APP().userTUNSettings.data()
except Exception:
# Any non-exit exceptions
return {}
AS_UserActivatedItemIndex = functools.partial(_activatedItemIndex)
AS_UserServers = functools.partial(_userServersData)
AS_UserSubscription = functools.partial(_userSubscriptionData)
AS_UserTUNSettings = functools.partial(_userTUNSettingsData)
def connectedHttpProxyEndpoint() -> Union[str, None]:
try:
if APP().isSystemTrayConnected():
index = AS_UserActivatedItemIndex()
if index >= 0:
return AS_UserServers()[index].httpProxyEndpoint()
else:
# Should not reach here
return None
else:
return None
except Exception:
# Any non-exit exceptions
return None
def connectedServerRemark() -> str:
try:
if APP().isSystemTrayConnected():
index = AS_UserActivatedItemIndex()
if index >= 0:
return f'{index + 1} - ' + AS_UserServers()[index].getExtras('remark')
else:
# Should not reach here
return ''
else:
return ''
except Exception:
# Any non-exit exceptions
return ''
-47
View File
@@ -1,47 +0,0 @@
# Copyright (C) 2024–present Loren Eteval & contributors <loren.eteval@proton.me>
#
# This file is part of Furious.
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <https://www.gnu.org/licenses/>.
from Furious.Utility.Constants import *
from Furious.Utility.Utility import *
import time
__all__ = ['PySide6LegacyEnumValueWrapper', 'PySide6LegacyEventLoopWait']
def PySide6LegacyEnumValueWrapper(enum):
# Protect PySide6 enum wrapper behavior changes
if versionToValue(PYSIDE6_VERSION) < versionToValue('6.2.2'):
return enum
else:
return enum.value
def PySide6LegacyEventLoopWait(ms):
# Protect qWait method does not exist in some
# old PySide6 version
if versionToValue(PYSIDE6_VERSION) < versionToValue('6.3.1'):
for counter in range(0, ms, 10):
time.sleep(10 / 1000)
APP().processEvents()
else:
from PySide6.QtTest import QTest
QTest.qWait(ms)
APP().processEvents()
+12 -18
View File
@@ -15,9 +15,8 @@
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <https://www.gnu.org/licenses/>.
from Furious.Utility.Constants import *
from Furious.Utility.AppSettings import *
from Furious.Utility.SystemRuntime import getAppImagePath, isScriptMode
from Furious.Frozenlib import *
from Furious.Utility.SystemRuntime import *
from PySide6 import QtCore
@@ -35,7 +34,7 @@ class StartupOnBoot:
@staticmethod
def on_():
def _on_():
if isScriptMode():
if SystemRuntime.isScriptMode():
# Script mode
logger.info('ignore turn on StartupOnBoot in script mode')
@@ -67,7 +66,7 @@ class StartupOnBoot:
return False
appImagePath = getAppImagePath()
appImagePath = SystemRuntime.appImagePath()
if appImagePath:
execPath = appImagePath
@@ -122,13 +121,11 @@ class StartupOnBoot:
'RunAtLoad': True,
}
with open(
os.path.join(
autostartDir,
f'{APPLICATION_MACOS_SIGNATURE}.{APPLICATION_NAME}.plist',
),
'wb',
) as file:
plistFile = (
f'{APPLICATION_MACOS_SIGNATURE}.{APPLICATION_NAME}.plist'
)
with open(os.path.join(autostartDir, plistFile), 'wb') as file:
plistlib.dump(plistData, file)
except Exception:
# Any non-exit exceptions
@@ -176,13 +173,10 @@ class StartupOnBoot:
os.path.expanduser('~'), 'Library', 'LaunchAgents'
)
plistFile = f'{APPLICATION_MACOS_SIGNATURE}.{APPLICATION_NAME}.plist'
try:
os.remove(
os.path.join(
autostartDir,
f'{APPLICATION_MACOS_SIGNATURE}.{APPLICATION_NAME}.plist',
)
)
os.remove(os.path.join(autostartDir, plistFile))
except FileNotFoundError:
return True
except Exception:
+4 -3
View File
@@ -17,9 +17,7 @@
from __future__ import annotations
from Furious.Utility.Constants import *
from Furious.Utility.Utility import parseHostPort, runExternalCommand
from Furious.Utility.AppSettings import *
from Furious.Frozenlib import *
import logging
import subprocess
@@ -149,6 +147,9 @@ class _SystemProxy:
try:
host, port = parseHostPort(server)
if host is None or port is None:
raise
linuxProxyConfig('proxy.http', 'host', host)
linuxProxyConfig('proxy.http', 'port', port)
linuxProxyConfig('proxy.https', 'host', host)
+76 -88
View File
@@ -17,9 +17,7 @@
from __future__ import annotations
from Furious.Utility.Constants import *
from Furious.Utility.AppSettings import AppSettings
from Furious.Utility.Utility import runExternalCommand
from Furious.Frozenlib import *
import os
import sys
@@ -27,99 +25,89 @@ import ctypes
import functools
import subprocess
__all__ = [
'getPythonVersion',
'getUbuntuRelease',
'getAppImagePath',
'isAdministrator',
'isTUNMode',
'isScriptMode',
'isPythonw',
'isWindows7',
]
__all__ = ['SystemRuntime']
@functools.lru_cache(None)
def getPythonVersion():
return '.'.join(str(info) for info in sys.version_info)
@functools.lru_cache(None)
def getUbuntuRelease() -> str:
try:
if PLATFORM != 'Linux':
return ''
result = runExternalCommand(
['cat', '/etc/lsb-release'],
stdout=subprocess.PIPE,
stderr=subprocess.PIPE,
check=True,
)
values = dict(
list(line.split('='))
for line in filter(lambda x: x != '', result.stdout.decode().split('\n'))
)
if values['DISTRIB_ID'] == 'Ubuntu':
return values['DISTRIB_RELEASE']
else:
return ''
except Exception:
# Any non-exit exceptions
return ''
def getAppImagePath() -> str:
if PLATFORM != 'Linux':
return ''
if 'APPIMAGE' in os.environ:
return os.environ['APPIMAGE']
else:
return ''
@functools.lru_cache(None)
def isAdministrator() -> bool:
if PLATFORM == 'Windows':
return ctypes.windll.shell32.IsUserAnAdmin() == 1
else:
return os.geteuid() == 0
def isTUNMode() -> bool:
return isAdministrator() and AppSettings.isStateON_('VPNMode')
@functools.lru_cache(None)
def isScriptMode() -> bool:
return sys.argv[0].endswith('.py')
@functools.lru_cache(None)
def isPythonw() -> bool:
def isRealFile(file):
if not hasattr(file, 'fileno'):
return False
class SystemRuntime:
@staticmethod
@functools.lru_cache(None)
def ubuntuRelease() -> str:
try:
tmp = os.dup(file.fileno())
if PLATFORM != 'Linux':
return ''
result = runExternalCommand(
['cat', '/etc/lsb-release'],
stdout=subprocess.PIPE,
stderr=subprocess.PIPE,
check=True,
)
values = dict(
list(line.split('='))
for line in filter(
lambda x: x != '', result.stdout.decode().split('\n')
)
)
if values['DISTRIB_ID'] == 'Ubuntu':
return values['DISTRIB_RELEASE']
else:
return ''
except Exception:
# Any non-exit exceptions
return False
return ''
@staticmethod
def appImagePath() -> str:
if PLATFORM != 'Linux':
return ''
if 'APPIMAGE' in os.environ:
return os.environ['APPIMAGE']
else:
os.close(tmp)
return ''
return True
@staticmethod
@functools.lru_cache(None)
def isAdmin() -> bool:
if PLATFORM == 'Windows':
return ctypes.windll.shell32.IsUserAnAdmin() == 1
else:
return os.geteuid() == 0
# pythonw.exe. Also applies to packed GUI application on Windows
return not isRealFile(sys.__stdout__) or not isRealFile(sys.__stderr__)
@staticmethod
def isTUNMode() -> bool:
return SystemRuntime.isAdmin() and AppSettings.isStateON_('VPNMode')
@staticmethod
@functools.lru_cache(None)
def isScriptMode() -> bool:
return sys.argv[0].endswith('.py')
@functools.lru_cache(None)
def isWindows7() -> bool:
return PLATFORM == 'Windows' and PLATFORM_RELEASE == '7'
@staticmethod
@functools.lru_cache(None)
def isPythonw() -> bool:
def isRealFile(file):
if not hasattr(file, 'fileno'):
return False
try:
tmp = os.dup(file.fileno())
except Exception:
# Any non-exit exceptions
return False
else:
os.close(tmp)
return True
# pythonw.exe. Also applies to packed GUI application on Windows
return not isRealFile(sys.__stdout__) or not isRealFile(sys.__stderr__)
@staticmethod
@functools.lru_cache(None)
def isWindows7() -> bool:
return PLATFORM == 'Windows' and PLATFORM_RELEASE == '7'
-8
View File
@@ -15,15 +15,7 @@
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <https://www.gnu.org/licenses/>.
from .Constants import *
from .PySide6Legacy import *
from .StartupOnBoot import *
from .SystemProxy import *
from .SystemRoutingTable import *
from .SystemRuntime import *
from .AppMainProcess import *
from .AppSettings import *
from .AppSettingsFn import *
from .Utility import *
from .Win32Session import *
+17 -61
View File
@@ -15,12 +15,10 @@
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <https://www.gnu.org/licenses/>.
from Furious.Frozenlib import *
from Furious.Interface import *
from Furious.PyFramework import *
from Furious.QtFramework import *
from Furious.QtFramework import gettext as _
from Furious.Qt import gettext as _
from Furious.Utility import *
from Furious.Storage import *
from Furious.Widget.SystemTrayIcon import *
from Furious.Window.AppMainWindow import *
from Furious.Window.LogViewerWindow import *
@@ -32,12 +30,10 @@ from PySide6.QtWidgets import *
import os
import sys
import time
import logging
import platform
import threading
import traceback
import functools
import qdarkstyle
import darkdetect
@@ -48,34 +44,6 @@ registerAppSettings('CoreLogViewerWidgetPointSize')
registerAppSettings('TunLogViewerWidgetPointSize')
def rateLimited(maxCallPerSecond):
"""
Decorator function that limits the rate at which a function can be called.
"""
interval = 1.0 / float(maxCallPerSecond)
called = time.monotonic()
def decorator(func):
def wrapper(*args, **kwargs):
# Previously called
nonlocal called
elapsed = time.monotonic() - called
waitsec = interval - elapsed
if waitsec > 0:
time.sleep(waitsec)
result = func(*args, **kwargs)
called = time.monotonic()
return result
return wrapper
return decorator
class SystemTrayUnavailable(Exception):
pass
@@ -178,11 +146,6 @@ class Application(ApplicationFactory, SingletonApplication):
self.themeDetector = None
self.themeListenerThread = None
# Initialize storage
self.userServers = UserServers()
self.userSubs = UserSubs()
self.userTUNSettings = UserTUNSettings()
self.mainWindow = None
self.systemTray = None
@@ -190,7 +153,7 @@ class Application(ApplicationFactory, SingletonApplication):
self.threadPool = QtCore.QThreadPool()
self.threadPool.setMaxThreadCount(max(OS_CPU_COUNT // 2, 1))
@rateLimited(maxCallPerSecond=2)
@callRateLimited(maxCallPerSecond=2)
@QtCore.Slot()
def showExistingApp(self):
if isinstance(self.systemTray, SystemTrayIcon):
@@ -202,7 +165,7 @@ class Application(ApplicationFactory, SingletonApplication):
pass
def configureLogging(self):
self.logViewerWindowApp_ = LogViewerWindow(
self.logViewerWindowSelf = LogViewerWindow(
tabTitle=_('Furious Log'),
fontFamily=self.customFontName,
pointSizeSettingsName='AppLogViewerWidgetPointSize',
@@ -223,16 +186,13 @@ class Application(ApplicationFactory, SingletonApplication):
level=logging.INFO,
handlers=(
AppLogHandler(
lambda record: self.logViewerWindowApp_.appendLine(record)
lambda record: self.logViewerWindowSelf.appendLine(record)
),
logging.StreamHandler(),
),
)
logging.raiseExceptions = False
def log(self):
return self.logViewerWindowApp_.plainText()
def addCustomFont(self):
fontFile = str(DATA_DIR / 'font' / 'CascadiaMono')
fontName = 'Cascadia Mono'
@@ -269,20 +229,18 @@ class Application(ApplicationFactory, SingletonApplication):
def switchToDarkMode(self):
self.setStyleSheet(qdarkstyle.load_stylesheet_pyside6())
SupportThemeChangedCallback.callThemeChangedCallbackUnchecked('Dark')
Mixins.ThemeAware.callThemeChangedCallbackUnchecked('Dark')
def switchToAutoMode(self):
self.setStyleSheet('')
SupportThemeChangedCallback.callThemeChangedCallbackUnchecked(
darkdetect.theme()
)
Mixins.ThemeAware.callThemeChangedCallbackUnchecked(darkdetect.theme())
@staticmethod
@callOnceOnly
@QtCore.Slot()
def cleanup():
SupportExitCleanup.cleanupAll()
Mixins.CleanupOnExit.cleanupAll()
if AppSettings.get('SystemProxyMode') == 'Auto':
# Automatically configure
@@ -345,7 +303,7 @@ class Application(ApplicationFactory, SingletonApplication):
# sys.exit(None) in multiprocessing will produce
# exitcode 1 in some Python version, which is
# not what we want.
return ApplicationFactory.ExitCode.ExitSuccess
return ApplicationFactory.ExitCode.ExitSuccess.value
if not SystemTrayIcon.isSystemTrayAvailable():
raise SystemTrayUnavailable(
@@ -368,24 +326,24 @@ class Application(ApplicationFactory, SingletonApplication):
if PLATFORM == 'Darwin':
logger.info(f'mac_ver: {platform.mac_ver()}')
appImagePath = getAppImagePath()
appImagePath = SystemRuntime.appImagePath()
if appImagePath:
logger.info(f'running from Linux AppImage: \'{appImagePath}\'')
else:
logger.info('not running from Linux AppImage')
logger.info(f'python version: {getPythonVersion()}')
logger.info(f'python version: {PLATFORM_PYTHON_VERSION}')
logger.info(f'system version: {sys.version}')
logger.info(f'sys.executable: \'{sys.executable}\'')
logger.info(f'sys.argv: {sys.argv}')
logger.info(f'appFilePath: \'{self.applicationFilePath()}\'')
logger.info(f'isPythonw: {isPythonw()}')
logger.info(f'isPythonw: {SystemRuntime.isPythonw()}')
logger.info(f'system language is {SYSTEM_LANGUAGE}')
logger.info(self.customFontLoadMsg)
logger.info(f'current theme is {darkdetect.theme()}')
if PLATFORM != 'Windows' and not isScriptMode():
if PLATFORM != 'Windows' and not SystemRuntime.isScriptMode():
logger.info('theme detect method uses timer implementation')
@QtCore.Slot()
@@ -395,9 +353,7 @@ class Application(ApplicationFactory, SingletonApplication):
if self.currentTheme != currentTheme:
self.currentTheme = currentTheme
SupportThemeChangedCallback.callThemeChangedCallback(
currentTheme
)
Mixins.ThemeAware.callThemeChangedCallback(currentTheme)
self.currentTheme = darkdetect.theme()
self.themeDetectTimer = QtCore.QTimer()
@@ -418,7 +374,7 @@ class Application(ApplicationFactory, SingletonApplication):
self.themeDetector = ApplicationThemeDetector()
self.themeDetector.themeChanged.connect(
SupportThemeChangedCallback.callThemeChangedCallback
Mixins.ThemeAware.callThemeChangedCallback
)
self.themeListenerThread = threading.Thread(
@@ -471,10 +427,10 @@ class Application(ApplicationFactory, SingletonApplication):
return self.exec()
except SystemTrayUnavailable:
return ApplicationFactory.ExitCode.PlatformNotSupported
return ApplicationFactory.ExitCode.PlatformNotSupported.value
except Exception:
# Any non-exit exceptions
traceback.print_exc()
return ApplicationFactory.ExitCode.UnknownException
return ApplicationFactory.ExitCode.UnknownException.value
+51 -38
View File
@@ -15,60 +15,42 @@
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <https://www.gnu.org/licenses/>.
from Furious.PyFramework import *
from Furious.QtFramework import *
from Furious.QtFramework import gettext as _
from Furious.Utility import *
from Furious.Frozenlib import *
from Furious.Qt import *
from Furious.Qt import gettext as _
from PySide6 import QtCore
from PySide6.QtWidgets import QHBoxLayout, QProgressBar, QWidget
from PySide6.QtWidgets import *
__all__ = ['ConnectProgressBar']
class ConnectProgressBar(QTranslatable, SupportConnectedCallback, QWidget):
def __init__(self, parent=None):
super().__init__(parent)
class AutoUpdateProgressBar(Mixins.ConnectionAware, QProgressBar):
def __init__(self, **kwargs):
super().__init__(**kwargs)
self.setWindowTitle(_(APPLICATION_NAME))
self.setWindowIcon(AppHue.currentWindowIcon())
self.setAttribute(QtCore.Qt.WidgetAttribute.WA_ShowWithoutActivating)
self.setWindowFlags(QtCore.Qt.WindowType.WindowStaysOnTopHint)
self.setFocusPolicy(QtCore.Qt.FocusPolicy.NoFocus)
self.setFixedSize(280, 61)
self.setRange(0, 100)
@QtCore.Slot()
def updateProgressBar():
def update():
# Update the progress bar value
if self._progressBar.value() < 90:
self._progressBar.setValue(self._progressBar.value() + 1)
if self.value() < 90:
self.setValue(self.value() + 1)
# Stop the timer when the progress bar reaches 100%
if self._progressBar.value() >= 100:
self._timer.stop()
if self.value() > 99:
self.timer.stop()
# Create a progress bar widget
self._progressBar = QProgressBar(self)
self._progressBar.setRange(0, 100)
self._progressBar.setStyleSheet(self.getStyleSheet(AppHue.disconnectedColor()))
self.timer = QtCore.QTimer()
self.timer.timeout.connect(update)
# Create a timer to update the progress bar
self._timer = QtCore.QTimer(self)
self._timer.timeout.connect(updateProgressBar)
self._layout = QHBoxLayout()
self._layout.addWidget(self._progressBar)
self.setLayout(self._layout)
def setValue(self, value: int):
self._progressBar.setValue(value)
self.setStyleSheet(self.getStyleSheet(AppHue.currentColor()))
def start(self, msec: int):
self._timer.start(msec)
self.timer.start(msec)
def stop(self):
self._timer.stop()
self.timer.stop()
@staticmethod
def getStyleSheet(color):
@@ -85,13 +67,44 @@ class ConnectProgressBar(QTranslatable, SupportConnectedCallback, QWidget):
f'}}'
)
def disconnectedCallback(self):
self.setStyleSheet(self.getStyleSheet(AppHue.disconnectedColor()))
def connectedCallback(self):
self.setStyleSheet(self.getStyleSheet(AppHue.connectedColor()))
class ConnectProgressBar(Mixins.QTranslatable, Mixins.ConnectionAware, QWidget):
def __init__(self, parent=None):
super().__init__(parent)
self.setWindowTitle(_(APPLICATION_NAME))
self.setWindowIcon(AppHue.currentWindowIcon())
self.setFixedSize(280, 61)
# Create a progress bar widget
self._widget = AutoUpdateProgressBar(parent=self)
self._widget.setRange(0, 100)
self._layout = QVBoxLayout()
self._layout.addWidget(self._widget)
self.setLayout(self._layout)
def setValue(self, value: int):
self._widget.setValue(value)
def start(self, msec: int):
self._widget.start(msec)
def stop(self):
self._widget.stop()
def disconnectedCallback(self):
self.setWindowIcon(AppHue.disconnectedWindowIcon())
self._progressBar.setStyleSheet(self.getStyleSheet(AppHue.disconnectedColor()))
def connectedCallback(self):
self.setWindowIcon(AppHue.connectedWindowIcon())
self._progressBar.setStyleSheet(self.getStyleSheet(AppHue.connectedColor()))
def retranslate(self):
self.setWindowTitle(_(self.windowTitle()))
+16 -17
View File
@@ -17,17 +17,16 @@
from __future__ import annotations
from Furious.Frozenlib import *
from Furious.Interface import *
from Furious.QtFramework import *
from Furious.QtFramework import gettext as _
from Furious.Utility import *
from Furious.Qt import *
from Furious.Qt import gettext as _
from PySide6 import QtCore
from PySide6.QtGui import *
from PySide6.QtWidgets import *
import logging
import functools
__all__ = ['GuiHysteria1']
@@ -42,7 +41,7 @@ class GuiHy1ItemTextInput(GuiEditorItemTextInput):
self.key = key
def inputToFactory(self, config: ConfigurationFactory) -> bool:
def inputToFactory(self, config: ConfigFactory) -> bool:
oldValue = config.get(self.key, '')
newValue = self.text()
@@ -62,7 +61,7 @@ class GuiHy1ItemTextInput(GuiEditorItemTextInput):
return True
def factoryToInput(self, config: ConfigurationFactory):
def factoryToInput(self, config: ConfigFactory):
try:
self.setText(config.get(self.key, ''))
except Exception:
@@ -77,7 +76,7 @@ class GuiHy1ItemBasicProtocol(GuiEditorItemTextComboBox):
self.addItems(['', 'udp', 'wechat-video', 'faketcp'])
def inputToFactory(self, config: ConfigurationFactory) -> bool:
def inputToFactory(self, config: ConfigFactory) -> bool:
oldProtocol = config.get('protocol', '')
newProtocol = self.text()
@@ -93,7 +92,7 @@ class GuiHy1ItemBasicProtocol(GuiEditorItemTextComboBox):
return True
def factoryToInput(self, config: ConfigurationFactory):
def factoryToInput(self, config: ConfigFactory):
try:
self.setText(config.get('protocol', 'udp'))
except Exception:
@@ -109,7 +108,7 @@ class GuiHy1ItemSpeedUpMbps(GuiEditorItemTextSpinBox):
# Range
self.setRange(0, 1048576)
def inputToFactory(self, config: ConfigurationFactory) -> bool:
def inputToFactory(self, config: ConfigFactory) -> bool:
oldUpMbps = config.get('up_mbps')
newUpMbps = self.value()
@@ -125,7 +124,7 @@ class GuiHy1ItemSpeedUpMbps(GuiEditorItemTextSpinBox):
return True
def factoryToInput(self, config: ConfigurationFactory):
def factoryToInput(self, config: ConfigFactory):
try:
self.setValue(config.get('up_mbps'))
except Exception:
@@ -141,7 +140,7 @@ class GuiHy1ItemSpeedDownMbps(GuiEditorItemTextSpinBox):
# Range
self.setRange(0, 1048576)
def inputToFactory(self, config: ConfigurationFactory) -> bool:
def inputToFactory(self, config: ConfigFactory) -> bool:
oldDownMbps = config.get('down_mbps')
newDownMbps = self.value()
@@ -157,7 +156,7 @@ class GuiHy1ItemSpeedDownMbps(GuiEditorItemTextSpinBox):
return True
def factoryToInput(self, config: ConfigurationFactory):
def factoryToInput(self, config: ConfigFactory):
try:
self.setValue(config.get('down_mbps'))
except Exception:
@@ -170,7 +169,7 @@ class GuiHy1ItemTLSInsecure(GuiEditorItemTextCheckBox):
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
def inputToFactory(self, config: ConfigurationFactory) -> bool:
def inputToFactory(self, config: ConfigFactory) -> bool:
try:
oldChecked = config.get('insecure')
except Exception:
@@ -201,7 +200,7 @@ class GuiHy1ItemTLSInsecure(GuiEditorItemTextCheckBox):
# Modified silently
return False
def factoryToInput(self, config: ConfigurationFactory):
def factoryToInput(self, config: ConfigFactory):
try:
checked = config['insecure']
except Exception:
@@ -304,10 +303,10 @@ class GuiHy1GroupBoxOther(GuiEditorItemFactory, AppQGroupBox):
self.setLayout(layout)
def inputToFactory(self, config: ConfigurationFactory) -> bool:
def inputToFactory(self, config: ConfigFactory) -> bool:
return False
def factoryToInput(self, config: ConfigurationFactory):
def factoryToInput(self, config: ConfigFactory):
pass
@@ -315,7 +314,7 @@ class GuiHysteria1(GuiEditorWidgetQDialog):
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self.setTabText(Protocol.Hysteria1)
self.setTabText(Protocol.Hysteria1.value)
def groupBoxSequence(self):
return [
+18 -19
View File
@@ -17,17 +17,16 @@
from __future__ import annotations
from Furious.Frozenlib import *
from Furious.Interface import *
from Furious.QtFramework import *
from Furious.QtFramework import gettext as _
from Furious.Utility import *
from Furious.Qt import *
from Furious.Qt import gettext as _
from PySide6 import QtCore
from PySide6.QtGui import *
from PySide6.QtWidgets import *
import logging
import functools
__all__ = ['GuiHysteria2']
@@ -38,7 +37,7 @@ class GuiHy2ItemBasicServer(GuiEditorItemTextInput):
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
def inputToFactory(self, config: ConfigurationFactory) -> bool:
def inputToFactory(self, config: ConfigFactory) -> bool:
oldServer = config.get('server', '')
newServer = self.text()
@@ -54,7 +53,7 @@ class GuiHy2ItemBasicServer(GuiEditorItemTextInput):
return True
def factoryToInput(self, config: ConfigurationFactory):
def factoryToInput(self, config: ConfigFactory):
try:
self.setText(config.get('server', ''))
except Exception:
@@ -67,7 +66,7 @@ class GuiHy2ItemBasicAuth(GuiEditorItemTextInput):
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
def inputToFactory(self, config: ConfigurationFactory) -> bool:
def inputToFactory(self, config: ConfigFactory) -> bool:
oldAuth = config.get('auth')
newAuth = self.text()
@@ -91,7 +90,7 @@ class GuiHy2ItemBasicAuth(GuiEditorItemTextInput):
return True
def factoryToInput(self, config: ConfigurationFactory):
def factoryToInput(self, config: ConfigFactory):
try:
self.setText(config.get('auth', ''))
except Exception:
@@ -107,7 +106,7 @@ class GuiHy2ItemObfsType(GuiEditorItemTextComboBox):
# TODO: Only 'salamander' is currently supported by hy2
self.addItems(['', 'salamander'])
def inputToFactory(self, config: ConfigurationFactory) -> bool:
def inputToFactory(self, config: ConfigFactory) -> bool:
try:
oldObfsType = config['obfs']['type']
except Exception:
@@ -140,7 +139,7 @@ class GuiHy2ItemObfsType(GuiEditorItemTextComboBox):
else:
return False
def factoryToInput(self, config: ConfigurationFactory):
def factoryToInput(self, config: ConfigFactory):
try:
obfsType = config['obfs']['type']
except Exception:
@@ -159,7 +158,7 @@ class GuiHy2ItemObfsPassword(GuiEditorItemTextInput):
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
def inputToFactory(self, config: ConfigurationFactory) -> bool:
def inputToFactory(self, config: ConfigFactory) -> bool:
newObfsPassword = self.text()
try:
@@ -205,7 +204,7 @@ class GuiHy2ItemObfsPassword(GuiEditorItemTextInput):
else:
return modified
def factoryToInput(self, config: ConfigurationFactory):
def factoryToInput(self, config: ConfigFactory):
try:
obfsType = config['obfs']['type']
except Exception:
@@ -237,7 +236,7 @@ class GuiHy2ItemTLSTextInput(GuiEditorItemTextInput):
self.key = key
def inputToFactory(self, config: ConfigurationFactory) -> bool:
def inputToFactory(self, config: ConfigFactory) -> bool:
newValue = self.text()
if newValue == '':
@@ -293,7 +292,7 @@ class GuiHy2ItemTLSTextInput(GuiEditorItemTextInput):
else:
return False
def factoryToInput(self, config: ConfigurationFactory):
def factoryToInput(self, config: ConfigFactory):
try:
value = config['tls'][self.key]
except Exception:
@@ -308,7 +307,7 @@ class GuiHy2ItemTLSInsecure(GuiEditorItemTextCheckBox):
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
def inputToFactory(self, config: ConfigurationFactory) -> bool:
def inputToFactory(self, config: ConfigFactory) -> bool:
try:
oldChecked = config['tls']['insecure']
except Exception:
@@ -339,7 +338,7 @@ class GuiHy2ItemTLSInsecure(GuiEditorItemTextCheckBox):
else:
return False
def factoryToInput(self, config: ConfigurationFactory):
def factoryToInput(self, config: ConfigFactory):
try:
checked = config['tls']['insecure']
except Exception:
@@ -444,10 +443,10 @@ class GuiHy2GroupBoxOther(GuiEditorItemFactory, AppQGroupBox):
self.setLayout(layout)
def inputToFactory(self, config: ConfigurationFactory) -> bool:
def inputToFactory(self, config: ConfigFactory) -> bool:
return False
def factoryToInput(self, config: ConfigurationFactory):
def factoryToInput(self, config: ConfigFactory):
pass
@@ -455,7 +454,7 @@ class GuiHysteria2(GuiEditorWidgetQDialog):
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self.setTabText(Protocol.Hysteria2)
self.setTabText(Protocol.Hysteria2.value)
def groupBoxSequence(self):
return [
+18 -66
View File
@@ -17,71 +17,23 @@
from __future__ import annotations
from Furious.Frozenlib import *
from Furious.Interface import *
from Furious.QtFramework import *
from Furious.QtFramework import gettext as _
from Furious.Utility import *
from Furious.Library import *
from Furious.Qt import *
from Furious.Qt import gettext as _
from Furious.Widget.GuiVTransport import *
from Furious.Widget.GuiVTLS import *
import functools
__all__ = ['GuiShadowsocks']
def getProxyOutboundObject(config: ConfigurationFactory) -> dict:
if not isinstance(config.get('outbounds'), list):
config['outbounds'] = []
for outbound in config['outbounds']:
if isinstance(outbound, dict):
tag = outbound.get('tag')
if isinstance(tag, str) and tag == 'proxy':
return outbound
outboundObject = {
'tag': 'proxy',
'protocol': 'shadowsocks',
'settings': {
'servers': [
{
'address': '',
'port': 0,
'method': '',
'password': '',
'email': PROXY_OUTBOUND_USER_EMAIL,
'ota': False,
}
]
},
'streamSettings': {
'network': 'tcp',
},
'mux': {
'enabled': False,
'concurrency': -1,
},
}
# Proxy outbound not found. Add it
config['outbounds'].append(outboundObject)
return outboundObject
def getProxyOutboundServer(config: ConfigurationFactory) -> dict:
proxyOutbound = getProxyOutboundObject(config)
try:
server = proxyOutbound['settings']['servers'][0]
except Exception:
# Any non-exit exceptions
server = {}
if not isinstance(server, dict):
server = {}
return server
getProxyOutboundServer = functools.partial(
ConfigXray.getProxyOutboundServer,
protocol=Protocol.Shadowsocks,
default=configXrayEmptyProxyOutboundObject(Protocol.Shadowsocks),
)
class GuiSSItemTextInput(GuiEditorItemTextInput):
@@ -92,7 +44,7 @@ class GuiSSItemTextInput(GuiEditorItemTextInput):
self.key = key
def inputToFactory(self, config: ConfigurationFactory) -> bool:
def inputToFactory(self, config: ConfigFactory) -> bool:
proxyOutboundServer = getProxyOutboundServer(config)
oldValue = proxyOutboundServer.get(self.key, '')
@@ -114,7 +66,7 @@ class GuiSSItemTextInput(GuiEditorItemTextInput):
return True
def factoryToInput(self, config: ConfigurationFactory):
def factoryToInput(self, config: ConfigFactory):
try:
proxyOutboundServer = getProxyOutboundServer(config)
@@ -132,7 +84,7 @@ class GuiSSItemBasicPort(GuiEditorItemTextSpinBox):
# Range
self.setRange(0, 65535)
def inputToFactory(self, config: ConfigurationFactory) -> bool:
def inputToFactory(self, config: ConfigFactory) -> bool:
proxyOutboundServer = getProxyOutboundServer(config)
oldPort = proxyOutboundServer.get('port')
@@ -150,7 +102,7 @@ class GuiSSItemBasicPort(GuiEditorItemTextSpinBox):
return True
def factoryToInput(self, config: ConfigurationFactory):
def factoryToInput(self, config: ConfigFactory):
try:
proxyOutboundServer = getProxyOutboundServer(config)
@@ -182,7 +134,7 @@ class GuiSSItemBasicMethod(GuiEditorItemTextComboBox):
]
)
def inputToFactory(self, config: ConfigurationFactory) -> bool:
def inputToFactory(self, config: ConfigFactory) -> bool:
proxyOutboundServer = getProxyOutboundServer(config)
oldMethod = proxyOutboundServer.get('method', '')
@@ -200,7 +152,7 @@ class GuiSSItemBasicMethod(GuiEditorItemTextComboBox):
return True
def factoryToInput(self, config: ConfigurationFactory):
def factoryToInput(self, config: ConfigFactory):
try:
proxyOutboundServer = getProxyOutboundServer(config)
@@ -240,7 +192,7 @@ class GuiShadowsocks(GuiEditorWidgetQDialog):
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self.setTabText(Protocol.Shadowsocks)
self.setTabText(Protocol.Shadowsocks.value)
def groupBoxSequence(self):
return [
+7 -4
View File
@@ -17,14 +17,17 @@
from __future__ import annotations
from Furious.Frozenlib import *
from Furious.Interface import *
from Furious.QtFramework import *
from Furious.QtFramework import gettext as _
from Furious.Library import *
from Furious.Qt import *
from Furious.Qt import gettext as _
from Furious.Utility import *
from PySide6.QtGui import *
import logging
import functools
__all__ = ['GuiTUNSettings']
@@ -164,7 +167,7 @@ class GuiTUNSettings(GuiEditorWidgetQDialog):
self.setFixedSize(int(460 * GOLDEN_RATIO), int(460))
# Shallow copy
config = AS_UserTUNSettings()
config = Storage.UserTUNSettings()
try:
self.factoryToInput(config)
@@ -179,7 +182,7 @@ class GuiTUNSettings(GuiEditorWidgetQDialog):
def handleAccepted(self, config: dict):
modified = self.inputToFactory(config)
if modified and isTUNMode():
if modified and SystemRuntime.isTUNMode():
showNewChangesNextTimeMBox()
self.accepted.disconnect()
+16 -62
View File
@@ -17,69 +17,23 @@
from __future__ import annotations
from Furious.Frozenlib import *
from Furious.Interface import *
from Furious.QtFramework import *
from Furious.QtFramework import gettext as _
from Furious.Utility import *
from Furious.Library import *
from Furious.Qt import *
from Furious.Qt import gettext as _
from Furious.Widget.GuiVTransport import *
from Furious.Widget.GuiVTLS import *
import functools
__all__ = ['GuiTrojan']
def getProxyOutboundObject(config: ConfigurationFactory) -> dict:
if not isinstance(config.get('outbounds'), list):
config['outbounds'] = []
for outbound in config['outbounds']:
if isinstance(outbound, dict):
tag = outbound.get('tag')
if isinstance(tag, str) and tag == 'proxy':
return outbound
outboundObject = {
'tag': 'proxy',
'protocol': 'trojan',
'settings': {
'servers': [
{
'address': '',
'port': 0,
'password': '',
'email': PROXY_OUTBOUND_USER_EMAIL,
}
]
},
'streamSettings': {
'network': 'tcp',
},
'mux': {
'enabled': False,
'concurrency': -1,
},
}
# Proxy outbound not found. Add it
config['outbounds'].append(outboundObject)
return outboundObject
def getProxyOutboundServer(config: ConfigurationFactory) -> dict:
proxyOutbound = getProxyOutboundObject(config)
try:
server = proxyOutbound['settings']['servers'][0]
except Exception:
# Any non-exit exceptions
server = {}
if not isinstance(server, dict):
server = {}
return server
getProxyOutboundServer = functools.partial(
ConfigXray.getProxyOutboundServer,
protocol=Protocol.Trojan,
default=configXrayEmptyProxyOutboundObject(Protocol.Trojan),
)
class GuiTrojanItemTextInput(GuiEditorItemTextInput):
@@ -90,7 +44,7 @@ class GuiTrojanItemTextInput(GuiEditorItemTextInput):
self.key = key
def inputToFactory(self, config: ConfigurationFactory) -> bool:
def inputToFactory(self, config: ConfigFactory) -> bool:
proxyOutboundServer = getProxyOutboundServer(config)
oldValue = proxyOutboundServer.get(self.key, '')
@@ -112,7 +66,7 @@ class GuiTrojanItemTextInput(GuiEditorItemTextInput):
return True
def factoryToInput(self, config: ConfigurationFactory):
def factoryToInput(self, config: ConfigFactory):
try:
proxyOutboundServer = getProxyOutboundServer(config)
@@ -130,7 +84,7 @@ class GuiTrojanItemBasicPort(GuiEditorItemTextSpinBox):
# Range
self.setRange(0, 65535)
def inputToFactory(self, config: ConfigurationFactory) -> bool:
def inputToFactory(self, config: ConfigFactory) -> bool:
proxyOutboundServer = getProxyOutboundServer(config)
oldPort = proxyOutboundServer.get('port')
@@ -148,7 +102,7 @@ class GuiTrojanItemBasicPort(GuiEditorItemTextSpinBox):
return True
def factoryToInput(self, config: ConfigurationFactory):
def factoryToInput(self, config: ConfigFactory):
try:
proxyOutboundServer = getProxyOutboundServer(config)
@@ -187,7 +141,7 @@ class GuiTrojan(GuiEditorWidgetQDialog):
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self.setTabText(Protocol.Trojan)
self.setTabText(Protocol.Trojan.value)
def groupBoxSequence(self):
return [
+27 -86
View File
@@ -17,95 +17,36 @@
from __future__ import annotations
from Furious.Frozenlib import *
from Furious.Interface import *
from Furious.QtFramework import *
from Furious.QtFramework import gettext as _
from Furious.Utility import *
from Furious.Library import *
from Furious.Qt import *
from Furious.Qt import gettext as _
from Furious.Widget.GuiVTransport import *
from Furious.Widget.GuiVTLS import *
import functools
__all__ = ['GuiVLESS']
getProxyOutboundServer = functools.partial(
ConfigXray.getProxyOutboundServer,
protocol=Protocol.VLESS,
default=configXrayEmptyProxyOutboundObject(Protocol.VLESS),
)
def getProxyOutboundObject(config: ConfigurationFactory) -> dict:
if not isinstance(config.get('outbounds'), list):
config['outbounds'] = []
for outbound in config['outbounds']:
if isinstance(outbound, dict):
tag = outbound.get('tag')
if isinstance(tag, str) and tag == 'proxy':
return outbound
outboundObject = {
'tag': 'proxy',
'protocol': 'vless',
'settings': {
'vnext': [
{
'address': '',
'port': 0,
'users': [
{
'email': PROXY_OUTBOUND_USER_EMAIL,
},
],
},
]
},
'streamSettings': {
'network': 'tcp',
},
'mux': {
'enabled': False,
'concurrency': -1,
},
}
# Proxy outbound not found. Add it
config['outbounds'].append(outboundObject)
return outboundObject
def getProxyOutboundServer(config: ConfigurationFactory) -> dict:
proxyOutbound = getProxyOutboundObject(config)
try:
server = proxyOutbound['settings']['vnext'][0]
except Exception:
# Any non-exit exceptions
server = {}
if not isinstance(server, dict):
server = {}
return server
def getProxyOutboundUser(config: ConfigurationFactory) -> dict:
proxyOutboundServer = getProxyOutboundServer(config)
try:
user = proxyOutboundServer['users'][0]
except Exception:
# Any non-exit exceptions
user = {}
if not isinstance(user, dict):
user = {}
return user
getProxyOutboundUser = functools.partial(
ConfigXray.getProxyOutboundUser,
protocol=Protocol.VLESS,
default=configXrayEmptyProxyOutboundObject(Protocol.VLESS),
)
class GuiVLESSItemBasicAddress(GuiEditorItemTextInput):
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
def inputToFactory(self, config: ConfigurationFactory) -> bool:
def inputToFactory(self, config: ConfigFactory) -> bool:
proxyOutboundServer = getProxyOutboundServer(config)
oldAddress = proxyOutboundServer.get('address', '')
@@ -127,7 +68,7 @@ class GuiVLESSItemBasicAddress(GuiEditorItemTextInput):
return True
def factoryToInput(self, config: ConfigurationFactory):
def factoryToInput(self, config: ConfigFactory):
try:
proxyOutboundServer = getProxyOutboundServer(config)
@@ -145,7 +86,7 @@ class GuiVLESSItemBasicPort(GuiEditorItemTextSpinBox):
# Range
self.setRange(0, 65535)
def inputToFactory(self, config: ConfigurationFactory) -> bool:
def inputToFactory(self, config: ConfigFactory) -> bool:
proxyOutboundServer = getProxyOutboundServer(config)
oldPort = proxyOutboundServer.get('port')
@@ -163,7 +104,7 @@ class GuiVLESSItemBasicPort(GuiEditorItemTextSpinBox):
return True
def factoryToInput(self, config: ConfigurationFactory):
def factoryToInput(self, config: ConfigFactory):
try:
proxyOutboundServer = getProxyOutboundServer(config)
@@ -178,7 +119,7 @@ class GuiVLESSItemBasicId(GuiEditorItemTextInput):
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
def inputToFactory(self, config: ConfigurationFactory) -> bool:
def inputToFactory(self, config: ConfigFactory) -> bool:
proxyOutboundUser = getProxyOutboundUser(config)
oldId = proxyOutboundUser.get('id', '')
@@ -200,7 +141,7 @@ class GuiVLESSItemBasicId(GuiEditorItemTextInput):
return True
def factoryToInput(self, config: ConfigurationFactory):
def factoryToInput(self, config: ConfigFactory):
try:
proxyOutboundUser = getProxyOutboundUser(config)
@@ -215,7 +156,7 @@ class GuiVLESSItemBasicEncryption(GuiEditorItemTextInput):
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
def inputToFactory(self, config: ConfigurationFactory) -> bool:
def inputToFactory(self, config: ConfigFactory) -> bool:
proxyOutboundUser = getProxyOutboundUser(config)
oldEncryption = proxyOutboundUser.get('encryption', '')
@@ -237,7 +178,7 @@ class GuiVLESSItemBasicEncryption(GuiEditorItemTextInput):
return True
def factoryToInput(self, config: ConfigurationFactory):
def factoryToInput(self, config: ConfigFactory):
try:
proxyOutboundUser = getProxyOutboundUser(config)
@@ -261,7 +202,7 @@ class GuiVLESSItemBasicFlow(GuiEditorItemTextComboBox):
]
)
def inputToFactory(self, config: ConfigurationFactory) -> bool:
def inputToFactory(self, config: ConfigFactory) -> bool:
proxyOutboundUser = getProxyOutboundUser(config)
oldFlow = proxyOutboundUser.get('flow', '')
@@ -279,7 +220,7 @@ class GuiVLESSItemBasicFlow(GuiEditorItemTextComboBox):
return True
def factoryToInput(self, config: ConfigurationFactory):
def factoryToInput(self, config: ConfigFactory):
try:
proxyOutboundUser = getProxyOutboundUser(config)
@@ -320,7 +261,7 @@ class GuiVLESS(GuiEditorWidgetQDialog):
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self.setTabText(Protocol.VLESS)
self.setTabText(Protocol.VLESS.value)
def groupBoxSequence(self):
return [
+27 -86
View File
@@ -17,95 +17,36 @@
from __future__ import annotations
from Furious.Frozenlib import *
from Furious.Interface import *
from Furious.QtFramework import *
from Furious.QtFramework import gettext as _
from Furious.Utility import *
from Furious.Library import *
from Furious.Qt import *
from Furious.Qt import gettext as _
from Furious.Widget.GuiVTransport import *
from Furious.Widget.GuiVTLS import *
import functools
__all__ = ['GuiVMess']
getProxyOutboundServer = functools.partial(
ConfigXray.getProxyOutboundServer,
protocol=Protocol.VMess,
default=configXrayEmptyProxyOutboundObject(Protocol.VMess),
)
def getProxyOutboundObject(config: ConfigurationFactory) -> dict:
if not isinstance(config.get('outbounds'), list):
config['outbounds'] = []
for outbound in config['outbounds']:
if isinstance(outbound, dict):
tag = outbound.get('tag')
if isinstance(tag, str) and tag == 'proxy':
return outbound
outboundObject = {
'tag': 'proxy',
'protocol': 'vmess',
'settings': {
'vnext': [
{
'address': '',
'port': 0,
'users': [
{
'email': PROXY_OUTBOUND_USER_EMAIL,
},
],
},
]
},
'streamSettings': {
'network': 'tcp',
},
'mux': {
'enabled': False,
'concurrency': -1,
},
}
# Proxy outbound not found. Add it
config['outbounds'].append(outboundObject)
return outboundObject
def getProxyOutboundServer(config: ConfigurationFactory) -> dict:
proxyOutbound = getProxyOutboundObject(config)
try:
server = proxyOutbound['settings']['vnext'][0]
except Exception:
# Any non-exit exceptions
server = {}
if not isinstance(server, dict):
server = {}
return server
def getProxyOutboundUser(config: ConfigurationFactory) -> dict:
proxyOutboundServer = getProxyOutboundServer(config)
try:
user = proxyOutboundServer['users'][0]
except Exception:
# Any non-exit exceptions
user = {}
if not isinstance(user, dict):
user = {}
return user
getProxyOutboundUser = functools.partial(
ConfigXray.getProxyOutboundUser,
protocol=Protocol.VMess,
default=configXrayEmptyProxyOutboundObject(Protocol.VMess),
)
class GuiVMessItemBasicAddress(GuiEditorItemTextInput):
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
def inputToFactory(self, config: ConfigurationFactory) -> bool:
def inputToFactory(self, config: ConfigFactory) -> bool:
proxyOutboundServer = getProxyOutboundServer(config)
oldAddress = proxyOutboundServer.get('address', '')
@@ -127,7 +68,7 @@ class GuiVMessItemBasicAddress(GuiEditorItemTextInput):
return True
def factoryToInput(self, config: ConfigurationFactory):
def factoryToInput(self, config: ConfigFactory):
try:
proxyOutboundServer = getProxyOutboundServer(config)
@@ -145,7 +86,7 @@ class GuiVMessItemBasicPort(GuiEditorItemTextSpinBox):
# Range
self.setRange(0, 65535)
def inputToFactory(self, config: ConfigurationFactory) -> bool:
def inputToFactory(self, config: ConfigFactory) -> bool:
proxyOutboundServer = getProxyOutboundServer(config)
oldPort = proxyOutboundServer.get('port')
@@ -163,7 +104,7 @@ class GuiVMessItemBasicPort(GuiEditorItemTextSpinBox):
return True
def factoryToInput(self, config: ConfigurationFactory):
def factoryToInput(self, config: ConfigFactory):
try:
proxyOutboundServer = getProxyOutboundServer(config)
@@ -178,7 +119,7 @@ class GuiVMessItemBasicId(GuiEditorItemTextInput):
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
def inputToFactory(self, config: ConfigurationFactory) -> bool:
def inputToFactory(self, config: ConfigFactory) -> bool:
proxyOutboundUser = getProxyOutboundUser(config)
oldId = proxyOutboundUser.get('id', '')
@@ -200,7 +141,7 @@ class GuiVMessItemBasicId(GuiEditorItemTextInput):
return True
def factoryToInput(self, config: ConfigurationFactory):
def factoryToInput(self, config: ConfigFactory):
try:
proxyOutboundUser = getProxyOutboundUser(config)
@@ -218,7 +159,7 @@ class GuiVMessItemBasicAlterId(GuiEditorItemTextSpinBox):
# Range
self.setRange(0, 65535)
def inputToFactory(self, config: ConfigurationFactory) -> bool:
def inputToFactory(self, config: ConfigFactory) -> bool:
proxyOutboundUser = getProxyOutboundUser(config)
oldAlterId = proxyOutboundUser.get('alterId')
@@ -236,7 +177,7 @@ class GuiVMessItemBasicAlterId(GuiEditorItemTextSpinBox):
return True
def factoryToInput(self, config: ConfigurationFactory):
def factoryToInput(self, config: ConfigFactory):
try:
proxyOutboundUser = getProxyOutboundUser(config)
@@ -262,7 +203,7 @@ class GuiVMessItemBasicSecurity(GuiEditorItemTextComboBox):
]
)
def inputToFactory(self, config: ConfigurationFactory) -> bool:
def inputToFactory(self, config: ConfigFactory) -> bool:
proxyOutboundUser = getProxyOutboundUser(config)
oldSecurity = proxyOutboundUser.get('security', '')
@@ -280,7 +221,7 @@ class GuiVMessItemBasicSecurity(GuiEditorItemTextComboBox):
return True
def factoryToInput(self, config: ConfigurationFactory):
def factoryToInput(self, config: ConfigFactory):
try:
proxyOutboundUser = getProxyOutboundUser(config)
@@ -321,7 +262,7 @@ class GuiVMess(GuiEditorWidgetQDialog):
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self.setTabText(Protocol.VMess)
self.setTabText(Protocol.VMess.value)
def groupBoxSequence(self):
return [
+30 -31
View File
@@ -18,9 +18,8 @@
from __future__ import annotations
from Furious.Interface import *
from Furious.QtFramework import *
from Furious.QtFramework import gettext as _
from Furious.Utility import *
from Furious.Library import *
from Furious.Qt import *
from PySide6.QtWidgets import *
@@ -42,8 +41,8 @@ class GuiVTLSItemSecurity(GuiEditorItemTextComboBox):
self.addItems(STREAM_SECURITY)
def inputToFactory(self, config: ConfigurationFactory) -> bool:
streamSettings = getXrayProxyOutboundStream(config)
def inputToFactory(self, config: ConfigFactory) -> bool:
streamSettings = ConfigXray.getProxyOutboundStream(config)
try:
oldSecurity = streamSettings['security']
@@ -81,9 +80,9 @@ class GuiVTLSItemSecurity(GuiEditorItemTextComboBox):
return True
def factoryToInput(self, config: ConfigurationFactory):
def factoryToInput(self, config: ConfigFactory):
try:
streamSettings = getXrayProxyOutboundStream(config)
streamSettings = ConfigXray.getProxyOutboundStream(config)
self.setText(streamSettings.get('security', ''))
except Exception:
@@ -101,8 +100,8 @@ class GuiVTLSItemXXXServerName(GuiEditorItemTextInput):
self.securityKey = securityKey
def inputToFactory(self, config: ConfigurationFactory) -> bool:
streamSettings = getXrayProxyOutboundStream(config)
def inputToFactory(self, config: ConfigFactory) -> bool:
streamSettings = ConfigXray.getProxyOutboundStream(config)
if not isinstance(streamSettings.get(self.securityKey), dict):
streamSettings[self.securityKey] = {}
@@ -136,9 +135,9 @@ class GuiVTLSItemXXXServerName(GuiEditorItemTextInput):
return True
def factoryToInput(self, config: ConfigurationFactory):
def factoryToInput(self, config: ConfigFactory):
try:
xxxObject = getXrayProxyOutboundStream(config)[self.securityKey]
xxxObject = ConfigXray.getProxyOutboundStream(config)[self.securityKey]
self.setText(xxxObject.get('serverName', ''))
except Exception:
@@ -170,8 +169,8 @@ class GuiVTLSItemXXXFingerprint(GuiEditorItemTextInput):
self.securityKey = securityKey
def inputToFactory(self, config: ConfigurationFactory) -> bool:
streamSettings = getXrayProxyOutboundStream(config)
def inputToFactory(self, config: ConfigFactory) -> bool:
streamSettings = ConfigXray.getProxyOutboundStream(config)
if not isinstance(streamSettings.get(self.securityKey), dict):
streamSettings[self.securityKey] = {}
@@ -205,9 +204,9 @@ class GuiVTLSItemXXXFingerprint(GuiEditorItemTextInput):
return True
def factoryToInput(self, config: ConfigurationFactory):
def factoryToInput(self, config: ConfigFactory):
try:
xxxObject = getXrayProxyOutboundStream(config)[self.securityKey]
xxxObject = ConfigXray.getProxyOutboundStream(config)[self.securityKey]
self.setText(xxxObject.get('fingerprint', ''))
except Exception:
@@ -234,8 +233,8 @@ class GuiVTLSItemTLSAlpn(GuiEditorItemTextInput):
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
def inputToFactory(self, config: ConfigurationFactory) -> bool:
streamSettings = getXrayProxyOutboundStream(config)
def inputToFactory(self, config: ConfigFactory) -> bool:
streamSettings = ConfigXray.getProxyOutboundStream(config)
if not isinstance(streamSettings.get('tlsSettings'), dict):
streamSettings['tlsSettings'] = {}
@@ -269,9 +268,9 @@ class GuiVTLSItemTLSAlpn(GuiEditorItemTextInput):
return True
def factoryToInput(self, config: ConfigurationFactory):
def factoryToInput(self, config: ConfigFactory):
try:
tlsObject = getXrayProxyOutboundStream(config)['tlsSettings']
tlsObject = ConfigXray.getProxyOutboundStream(config)['tlsSettings']
self.setText(','.join(tlsObject.get('alpn', [])))
except Exception:
@@ -284,8 +283,8 @@ class GuiVTLSItemTLSAllowInsecure(GuiEditorItemTextCheckBox):
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
def inputToFactory(self, config: ConfigurationFactory) -> bool:
streamSettings = getXrayProxyOutboundStream(config)
def inputToFactory(self, config: ConfigFactory) -> bool:
streamSettings = ConfigXray.getProxyOutboundStream(config)
if not isinstance(streamSettings.get('tlsSettings'), dict):
streamSettings['tlsSettings'] = {}
@@ -316,9 +315,9 @@ class GuiVTLSItemTLSAllowInsecure(GuiEditorItemTextCheckBox):
else:
return False
def factoryToInput(self, config: ConfigurationFactory):
def factoryToInput(self, config: ConfigFactory):
try:
tlsObject = getXrayProxyOutboundStream(config)['tlsSettings']
tlsObject = ConfigXray.getProxyOutboundStream(config)['tlsSettings']
self.setChecked(tlsObject.get('allowInsecure', False))
except Exception:
@@ -336,8 +335,8 @@ class GuiVTLSItemRealityXXX(GuiEditorItemTextInput):
self.realityKey = realityKey
def inputToFactory(self, config: ConfigurationFactory) -> bool:
streamSettings = getXrayProxyOutboundStream(config)
def inputToFactory(self, config: ConfigFactory) -> bool:
streamSettings = ConfigXray.getProxyOutboundStream(config)
if not isinstance(streamSettings.get('realitySettings'), dict):
streamSettings['realitySettings'] = {}
@@ -371,9 +370,9 @@ class GuiVTLSItemRealityXXX(GuiEditorItemTextInput):
return True
def factoryToInput(self, config: ConfigurationFactory):
def factoryToInput(self, config: ConfigFactory):
try:
realityObject = getXrayProxyOutboundStream(config)['realitySettings']
realityObject = ConfigXray.getProxyOutboundStream(config)['realitySettings']
self.setText(realityObject.get(self.realityKey, ''))
except Exception:
@@ -498,7 +497,7 @@ class GuiVTLSQGroupBox(GuiEditorItemFactory, AppQGroupBox):
super().__init__('TLS', **kwargs, translatable=translatable)
self._config = ConfigurationFactory()
self._config = ConfigFactory()
self._widget = GuiVTLSPageStackedWidget()
self._widget.connectActivated(self.handleActivated)
@@ -524,14 +523,14 @@ class GuiVTLSQGroupBox(GuiEditorItemFactory, AppQGroupBox):
self.setCurrentIndex(index)
def inputToFactory(self, config: ConfigurationFactory) -> bool:
def inputToFactory(self, config: ConfigFactory) -> bool:
return self.page(self.currentIndex()).inputToFactory(config)
def factoryToInput(self, config: ConfigurationFactory):
def factoryToInput(self, config: ConfigFactory):
# Shallow copy
self._config = config
streamSettings = getXrayProxyOutboundStream(config)
streamSettings = ConfigXray.getProxyOutboundStream(config)
security = streamSettings.get('security', '')
if not isinstance(security, str):
+98 -96
View File
@@ -19,10 +19,8 @@ from __future__ import annotations
from Furious.Interface import *
from Furious.Library import *
from Furious.QtFramework import *
from Furious.QtFramework import gettext as _
from Furious.Utility import *
from Furious.Widget.GuiVTLS import *
from Furious.Qt import *
from Furious.Qt import gettext as _
from PySide6.QtWidgets import *
@@ -49,8 +47,8 @@ class GuiVTransportItemNetwork(GuiEditorItemTextComboBox):
self.addItems(STREAM_NETWORK)
def inputToFactory(self, config: ConfigurationFactory) -> bool:
streamSettings = getXrayProxyOutboundStream(config)
def inputToFactory(self, config: ConfigFactory) -> bool:
streamSettings = ConfigXray.getProxyOutboundStream(config)
try:
oldNetwork = streamSettings['network']
@@ -88,9 +86,9 @@ class GuiVTransportItemNetwork(GuiEditorItemTextComboBox):
return True
def factoryToInput(self, config: ConfigurationFactory):
def factoryToInput(self, config: ConfigFactory):
try:
streamSettings = getXrayProxyOutboundStream(config)
streamSettings = ConfigXray.getProxyOutboundStream(config)
self.setText(streamSettings.get('network', ''))
except Exception:
@@ -107,8 +105,8 @@ class GuiVTransportItemTypeXXX(GuiEditorItemTextComboBox):
self.networkKey = networkKey
def inputToFactory(self, config: ConfigurationFactory) -> bool:
streamSettings = getXrayProxyOutboundStream(config)
def inputToFactory(self, config: ConfigFactory) -> bool:
streamSettings = ConfigXray.getProxyOutboundStream(config)
if not isinstance(streamSettings.get(self.networkKey), dict):
streamSettings[self.networkKey] = {}
@@ -145,9 +143,9 @@ class GuiVTransportItemTypeXXX(GuiEditorItemTextComboBox):
return True
def factoryToInput(self, config: ConfigurationFactory):
def factoryToInput(self, config: ConfigFactory):
try:
xxxObject = getXrayProxyOutboundStream(config)[self.networkKey]
xxxObject = ConfigXray.getProxyOutboundStream(config)[self.networkKey]
self.setText(xxxObject['header']['type'])
except Exception:
@@ -175,8 +173,8 @@ class GuiVTransportItemHostTcp(GuiEditorItemTextInput):
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
def inputToFactory(self, config: ConfigurationFactory) -> bool:
streamSettings = getXrayProxyOutboundStream(config)
def inputToFactory(self, config: ConfigFactory) -> bool:
streamSettings = ConfigXray.getProxyOutboundStream(config)
if not isinstance(streamSettings.get('tcpSettings'), dict):
streamSettings['tcpSettings'] = {}
@@ -219,9 +217,9 @@ class GuiVTransportItemHostTcp(GuiEditorItemTextInput):
return True
def factoryToInput(self, config: ConfigurationFactory):
def factoryToInput(self, config: ConfigFactory):
try:
tcpObject = getXrayProxyOutboundStream(config)['tcpSettings']
tcpObject = ConfigXray.getProxyOutboundStream(config)['tcpSettings']
self.setText(','.join(tcpObject['header']['request']['headers']['Host']))
except Exception:
@@ -234,8 +232,8 @@ class GuiVTransportItemPathTcp(GuiEditorItemTextInput):
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
def inputToFactory(self, config: ConfigurationFactory) -> bool:
streamSettings = getXrayProxyOutboundStream(config)
def inputToFactory(self, config: ConfigFactory) -> bool:
streamSettings = ConfigXray.getProxyOutboundStream(config)
if not isinstance(streamSettings.get('tcpSettings'), dict):
streamSettings['tcpSettings'] = {}
@@ -275,9 +273,9 @@ class GuiVTransportItemPathTcp(GuiEditorItemTextInput):
return True
def factoryToInput(self, config: ConfigurationFactory):
def factoryToInput(self, config: ConfigFactory):
try:
tcpObject = getXrayProxyOutboundStream(config)['tcpSettings']
tcpObject = ConfigXray.getProxyOutboundStream(config)['tcpSettings']
self.setText(','.join(tcpObject['header']['request']['path']))
except Exception:
@@ -310,8 +308,8 @@ class GuiVTransportItemSeedKcp(GuiEditorItemTextInput):
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
def inputToFactory(self, config: ConfigurationFactory) -> bool:
streamSettings = getXrayProxyOutboundStream(config)
def inputToFactory(self, config: ConfigFactory) -> bool:
streamSettings = ConfigXray.getProxyOutboundStream(config)
if not isinstance(streamSettings.get('kcpSettings'), dict):
streamSettings['kcpSettings'] = {}
@@ -345,9 +343,9 @@ class GuiVTransportItemSeedKcp(GuiEditorItemTextInput):
return True
def factoryToInput(self, config: ConfigurationFactory):
def factoryToInput(self, config: ConfigFactory):
try:
kcpObject = getXrayProxyOutboundStream(config)['kcpSettings']
kcpObject = ConfigXray.getProxyOutboundStream(config)['kcpSettings']
self.setText(kcpObject.get('seed', ''))
except Exception:
@@ -360,8 +358,8 @@ class GuiVTransportItemHostWs(GuiEditorItemTextInput):
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
def inputToFactory(self, config: ConfigurationFactory) -> bool:
streamSettings = getXrayProxyOutboundStream(config)
def inputToFactory(self, config: ConfigFactory) -> bool:
streamSettings = ConfigXray.getProxyOutboundStream(config)
if not isinstance(streamSettings.get('wsSettings'), dict):
streamSettings['wsSettings'] = {}
@@ -398,9 +396,9 @@ class GuiVTransportItemHostWs(GuiEditorItemTextInput):
return True
def factoryToInput(self, config: ConfigurationFactory):
def factoryToInput(self, config: ConfigFactory):
try:
wsObject = getXrayProxyOutboundStream(config)['wsSettings']
wsObject = ConfigXray.getProxyOutboundStream(config)['wsSettings']
self.setText(wsObject['headers']['Host'])
except Exception:
@@ -413,8 +411,8 @@ class GuiVTransportItemPathWs(GuiEditorItemTextInput):
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
def inputToFactory(self, config: ConfigurationFactory) -> bool:
streamSettings = getXrayProxyOutboundStream(config)
def inputToFactory(self, config: ConfigFactory) -> bool:
streamSettings = ConfigXray.getProxyOutboundStream(config)
if not isinstance(streamSettings.get('wsSettings'), dict):
streamSettings['wsSettings'] = {}
@@ -448,9 +446,9 @@ class GuiVTransportItemPathWs(GuiEditorItemTextInput):
return True
def factoryToInput(self, config: ConfigurationFactory):
def factoryToInput(self, config: ConfigFactory):
try:
wsObject = getXrayProxyOutboundStream(config)['wsSettings']
wsObject = ConfigXray.getProxyOutboundStream(config)['wsSettings']
self.setText(wsObject.get('path', ''))
except Exception:
@@ -463,8 +461,8 @@ class GuiVTransportItemHostHttpUpgrade(GuiEditorItemTextInput):
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
def inputToFactory(self, config: ConfigurationFactory) -> bool:
streamSettings = getXrayProxyOutboundStream(config)
def inputToFactory(self, config: ConfigFactory) -> bool:
streamSettings = ConfigXray.getProxyOutboundStream(config)
if not isinstance(streamSettings.get('httpupgradeSettings'), dict):
streamSettings['httpupgradeSettings'] = {}
@@ -498,9 +496,9 @@ class GuiVTransportItemHostHttpUpgrade(GuiEditorItemTextInput):
return True
def factoryToInput(self, config: ConfigurationFactory):
def factoryToInput(self, config: ConfigFactory):
try:
httpUpgradeObject = getXrayProxyOutboundStream(config)[
httpUpgradeObject = ConfigXray.getProxyOutboundStream(config)[
'httpupgradeSettings'
]
@@ -515,8 +513,8 @@ class GuiVTransportItemPathHttpUpgrade(GuiEditorItemTextInput):
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
def inputToFactory(self, config: ConfigurationFactory) -> bool:
streamSettings = getXrayProxyOutboundStream(config)
def inputToFactory(self, config: ConfigFactory) -> bool:
streamSettings = ConfigXray.getProxyOutboundStream(config)
if not isinstance(streamSettings.get('httpupgradeSettings'), dict):
streamSettings['httpupgradeSettings'] = {}
@@ -550,9 +548,9 @@ class GuiVTransportItemPathHttpUpgrade(GuiEditorItemTextInput):
return True
def factoryToInput(self, config: ConfigurationFactory):
def factoryToInput(self, config: ConfigFactory):
try:
httpUpgradeObject = getXrayProxyOutboundStream(config)[
httpUpgradeObject = ConfigXray.getProxyOutboundStream(config)[
'httpupgradeSettings'
]
@@ -567,8 +565,8 @@ class GuiVTransportItemHostSplitHttp(GuiEditorItemTextInput):
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
def inputToFactory(self, config: ConfigurationFactory) -> bool:
streamSettings = getXrayProxyOutboundStream(config)
def inputToFactory(self, config: ConfigFactory) -> bool:
streamSettings = ConfigXray.getProxyOutboundStream(config)
if not isinstance(streamSettings.get('splithttpSettings'), dict):
streamSettings['splithttpSettings'] = {}
@@ -602,9 +600,11 @@ class GuiVTransportItemHostSplitHttp(GuiEditorItemTextInput):
return True
def factoryToInput(self, config: ConfigurationFactory):
def factoryToInput(self, config: ConfigFactory):
try:
splitHttpObject = getXrayProxyOutboundStream(config)['splithttpSettings']
splitHttpObject = ConfigXray.getProxyOutboundStream(config)[
'splithttpSettings'
]
self.setText(splitHttpObject.get('host', ''))
except Exception:
@@ -617,8 +617,8 @@ class GuiVTransportItemPathSplitHttp(GuiEditorItemTextInput):
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
def inputToFactory(self, config: ConfigurationFactory) -> bool:
streamSettings = getXrayProxyOutboundStream(config)
def inputToFactory(self, config: ConfigFactory) -> bool:
streamSettings = ConfigXray.getProxyOutboundStream(config)
if not isinstance(streamSettings.get('splithttpSettings'), dict):
streamSettings['splithttpSettings'] = {}
@@ -652,9 +652,11 @@ class GuiVTransportItemPathSplitHttp(GuiEditorItemTextInput):
return True
def factoryToInput(self, config: ConfigurationFactory):
def factoryToInput(self, config: ConfigFactory):
try:
splitHttpObject = getXrayProxyOutboundStream(config)['splithttpSettings']
splitHttpObject = ConfigXray.getProxyOutboundStream(config)[
'splithttpSettings'
]
self.setText(splitHttpObject.get('path', ''))
except Exception:
@@ -667,8 +669,8 @@ class GuiVTransportItemHostXHttp(GuiEditorItemTextInput):
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
def inputToFactory(self, config: ConfigurationFactory) -> bool:
streamSettings = getXrayProxyOutboundStream(config)
def inputToFactory(self, config: ConfigFactory) -> bool:
streamSettings = ConfigXray.getProxyOutboundStream(config)
if not isinstance(streamSettings.get('xhttpSettings'), dict):
streamSettings['xhttpSettings'] = {}
@@ -702,9 +704,9 @@ class GuiVTransportItemHostXHttp(GuiEditorItemTextInput):
return True
def factoryToInput(self, config: ConfigurationFactory):
def factoryToInput(self, config: ConfigFactory):
try:
xhttpObject = getXrayProxyOutboundStream(config)['xhttpSettings']
xhttpObject = ConfigXray.getProxyOutboundStream(config)['xhttpSettings']
self.setText(xhttpObject.get('host', ''))
except Exception:
@@ -717,8 +719,8 @@ class GuiVTransportItemPathXHttp(GuiEditorItemTextInput):
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
def inputToFactory(self, config: ConfigurationFactory) -> bool:
streamSettings = getXrayProxyOutboundStream(config)
def inputToFactory(self, config: ConfigFactory) -> bool:
streamSettings = ConfigXray.getProxyOutboundStream(config)
if not isinstance(streamSettings.get('xhttpSettings'), dict):
streamSettings['xhttpSettings'] = {}
@@ -752,9 +754,9 @@ class GuiVTransportItemPathXHttp(GuiEditorItemTextInput):
return True
def factoryToInput(self, config: ConfigurationFactory):
def factoryToInput(self, config: ConfigFactory):
try:
xhttpObject = getXrayProxyOutboundStream(config)['xhttpSettings']
xhttpObject = ConfigXray.getProxyOutboundStream(config)['xhttpSettings']
self.setText(xhttpObject.get('path', ''))
except Exception:
@@ -776,8 +778,8 @@ class GuiVTransportItemModeXHttp(GuiEditorItemTextComboBox):
]
)
def inputToFactory(self, config: ConfigurationFactory) -> bool:
streamSettings = getXrayProxyOutboundStream(config)
def inputToFactory(self, config: ConfigFactory) -> bool:
streamSettings = ConfigXray.getProxyOutboundStream(config)
if not isinstance(streamSettings.get('xhttpSettings'), dict):
streamSettings['xhttpSettings'] = {}
@@ -811,9 +813,9 @@ class GuiVTransportItemModeXHttp(GuiEditorItemTextComboBox):
return True
def factoryToInput(self, config: ConfigurationFactory):
def factoryToInput(self, config: ConfigFactory):
try:
xhttpObject = getXrayProxyOutboundStream(config)['xhttpSettings']
xhttpObject = ConfigXray.getProxyOutboundStream(config)['xhttpSettings']
self.setText(xhttpObject.get('mode', ''))
except Exception:
@@ -826,8 +828,8 @@ class GuiVTransportItemExtraXHttp(GuiEditorItemTextInput):
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
def inputToFactory(self, config: ConfigurationFactory) -> bool:
streamSettings = getXrayProxyOutboundStream(config)
def inputToFactory(self, config: ConfigFactory) -> bool:
streamSettings = ConfigXray.getProxyOutboundStream(config)
if not isinstance(streamSettings.get('xhttpSettings'), dict):
streamSettings['xhttpSettings'] = {}
@@ -869,9 +871,9 @@ class GuiVTransportItemExtraXHttp(GuiEditorItemTextInput):
return True
def factoryToInput(self, config: ConfigurationFactory):
def factoryToInput(self, config: ConfigFactory):
try:
xhttpObject = getXrayProxyOutboundStream(config)['xhttpSettings']
xhttpObject = ConfigXray.getProxyOutboundStream(config)['xhttpSettings']
if xhttpObject.get('extra', ''):
self.setText(UJSONEncoder.encode(xhttpObject.get('extra', '')))
@@ -887,8 +889,8 @@ class GuiVTransportItemHostH2(GuiEditorItemTextInput):
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
def inputToFactory(self, config: ConfigurationFactory) -> bool:
streamSettings = getXrayProxyOutboundStream(config)
def inputToFactory(self, config: ConfigFactory) -> bool:
streamSettings = ConfigXray.getProxyOutboundStream(config)
if not isinstance(streamSettings.get('httpSettings'), dict):
streamSettings['httpSettings'] = {}
@@ -922,9 +924,9 @@ class GuiVTransportItemHostH2(GuiEditorItemTextInput):
return True
def factoryToInput(self, config: ConfigurationFactory):
def factoryToInput(self, config: ConfigFactory):
try:
httpObject = getXrayProxyOutboundStream(config)['httpSettings']
httpObject = ConfigXray.getProxyOutboundStream(config)['httpSettings']
self.setText(','.join(httpObject.get('host', [])))
except Exception:
@@ -937,8 +939,8 @@ class GuiVTransportItemPathH2(GuiEditorItemTextInput):
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
def inputToFactory(self, config: ConfigurationFactory) -> bool:
streamSettings = getXrayProxyOutboundStream(config)
def inputToFactory(self, config: ConfigFactory) -> bool:
streamSettings = ConfigXray.getProxyOutboundStream(config)
if not isinstance(streamSettings.get('httpSettings'), dict):
streamSettings['httpSettings'] = {}
@@ -972,9 +974,9 @@ class GuiVTransportItemPathH2(GuiEditorItemTextInput):
return True
def factoryToInput(self, config: ConfigurationFactory):
def factoryToInput(self, config: ConfigFactory):
try:
httpObject = getXrayProxyOutboundStream(config)['httpSettings']
httpObject = ConfigXray.getProxyOutboundStream(config)['httpSettings']
self.setText(httpObject.get('path', ''))
except Exception:
@@ -1015,8 +1017,8 @@ class GuiVTransportItemSecurityQuic(GuiEditorItemTextComboBox):
]
)
def inputToFactory(self, config: ConfigurationFactory) -> bool:
streamSettings = getXrayProxyOutboundStream(config)
def inputToFactory(self, config: ConfigFactory) -> bool:
streamSettings = ConfigXray.getProxyOutboundStream(config)
if not isinstance(streamSettings.get('quicSettings'), dict):
streamSettings['quicSettings'] = {}
@@ -1047,9 +1049,9 @@ class GuiVTransportItemSecurityQuic(GuiEditorItemTextComboBox):
return True
def factoryToInput(self, config: ConfigurationFactory):
def factoryToInput(self, config: ConfigFactory):
try:
quicObject = getXrayProxyOutboundStream(config)['quicSettings']
quicObject = ConfigXray.getProxyOutboundStream(config)['quicSettings']
self.setText(quicObject.get('security', ''))
except Exception:
@@ -1062,8 +1064,8 @@ class GuiVTransportItemKeyQuic(GuiEditorItemTextInput):
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
def inputToFactory(self, config: ConfigurationFactory) -> bool:
streamSettings = getXrayProxyOutboundStream(config)
def inputToFactory(self, config: ConfigFactory) -> bool:
streamSettings = ConfigXray.getProxyOutboundStream(config)
if not isinstance(streamSettings.get('quicSettings'), dict):
streamSettings['quicSettings'] = {}
@@ -1097,9 +1099,9 @@ class GuiVTransportItemKeyQuic(GuiEditorItemTextInput):
return True
def factoryToInput(self, config: ConfigurationFactory):
def factoryToInput(self, config: ConfigFactory):
try:
quicObject = getXrayProxyOutboundStream(config)['quicSettings']
quicObject = ConfigXray.getProxyOutboundStream(config)['quicSettings']
self.setText(quicObject.get('key', ''))
except Exception:
@@ -1120,8 +1122,8 @@ class GuiVTransportItemModeGRPC(GuiEditorItemTextComboBox):
]
)
def inputToFactory(self, config: ConfigurationFactory) -> bool:
streamSettings = getXrayProxyOutboundStream(config)
def inputToFactory(self, config: ConfigFactory) -> bool:
streamSettings = ConfigXray.getProxyOutboundStream(config)
if not isinstance(streamSettings.get('grpcSettings'), dict):
streamSettings['grpcSettings'] = {}
@@ -1160,9 +1162,9 @@ class GuiVTransportItemModeGRPC(GuiEditorItemTextComboBox):
return True
def factoryToInput(self, config: ConfigurationFactory):
def factoryToInput(self, config: ConfigFactory):
try:
grpcObject = getXrayProxyOutboundStream(config)['grpcSettings']
grpcObject = ConfigXray.getProxyOutboundStream(config)['grpcSettings']
multi = grpcObject.get('multiMode', False)
@@ -1182,8 +1184,8 @@ class GuiVTransportItemAuthorityGRPC(GuiEditorItemTextInput):
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
def inputToFactory(self, config: ConfigurationFactory) -> bool:
streamSettings = getXrayProxyOutboundStream(config)
def inputToFactory(self, config: ConfigFactory) -> bool:
streamSettings = ConfigXray.getProxyOutboundStream(config)
if not isinstance(streamSettings.get('grpcSettings'), dict):
streamSettings['grpcSettings'] = {}
@@ -1217,9 +1219,9 @@ class GuiVTransportItemAuthorityGRPC(GuiEditorItemTextInput):
return True
def factoryToInput(self, config: ConfigurationFactory):
def factoryToInput(self, config: ConfigFactory):
try:
grpcObject = getXrayProxyOutboundStream(config)['grpcSettings']
grpcObject = ConfigXray.getProxyOutboundStream(config)['grpcSettings']
self.setText(grpcObject.get('authority', ''))
except Exception:
@@ -1232,8 +1234,8 @@ class GuiVTransportItemServiceNameGRPC(GuiEditorItemTextInput):
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
def inputToFactory(self, config: ConfigurationFactory) -> bool:
streamSettings = getXrayProxyOutboundStream(config)
def inputToFactory(self, config: ConfigFactory) -> bool:
streamSettings = ConfigXray.getProxyOutboundStream(config)
if not isinstance(streamSettings.get('grpcSettings'), dict):
streamSettings['grpcSettings'] = {}
@@ -1267,9 +1269,9 @@ class GuiVTransportItemServiceNameGRPC(GuiEditorItemTextInput):
return True
def factoryToInput(self, config: ConfigurationFactory):
def factoryToInput(self, config: ConfigFactory):
try:
grpcObject = getXrayProxyOutboundStream(config)['grpcSettings']
grpcObject = ConfigXray.getProxyOutboundStream(config)['grpcSettings']
self.setText(grpcObject.get('serviceName', ''))
except Exception:
@@ -1442,7 +1444,7 @@ class GuiVTransportQGroupBox(GuiEditorItemFactory, AppQGroupBox):
def __init__(self, **kwargs):
super().__init__(_('Transport'), **kwargs)
self._config = ConfigurationFactory()
self._config = ConfigFactory()
self._widget = GuiVTransportPageStackedWidget()
self._widget.connectActivated(self.handleActivated)
@@ -1468,14 +1470,14 @@ class GuiVTransportQGroupBox(GuiEditorItemFactory, AppQGroupBox):
self.setCurrentIndex(index)
def inputToFactory(self, config: ConfigurationFactory) -> bool:
def inputToFactory(self, config: ConfigFactory) -> bool:
return self.page(self.currentIndex()).inputToFactory(config)
def factoryToInput(self, config: ConfigurationFactory):
def factoryToInput(self, config: ConfigFactory):
# Shallow copy
self._config = config
streamSettings = getXrayProxyOutboundStream(config)
streamSettings = ConfigXray.getProxyOutboundStream(config)
network = streamSettings.get('network', '')
if not isinstance(network, str):
+3 -5
View File
@@ -17,14 +17,12 @@
from __future__ import annotations
from Furious.QtFramework import *
from Furious.QtFramework import gettext as _
from Furious.Utility import *
from Furious.Qt import *
from Furious.Qt import gettext as _
from PySide6 import QtCore
from PySide6.QtWidgets import *
import functools
__all__ = ['IndentSpinBox']
+9 -9
View File
@@ -15,9 +15,9 @@
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <https://www.gnu.org/licenses/>.
from Furious.PyFramework import *
from Furious.QtFramework import *
from Furious.QtFramework import gettext as _
from Furious.Frozenlib import *
from Furious.Qt import *
from Furious.Qt import gettext as _
from Furious.Utility import *
from Furious.TrayActions import *
@@ -34,9 +34,9 @@ __all__ = ['SystemTrayIcon']
class SystemTrayIcon(
QTranslatable,
SupportConnectedCallback,
SupportThemeChangedCallback,
Mixins.QTranslatable,
Mixins.ConnectionAware,
Mixins.ThemeAware,
QSystemTrayIcon,
):
def __init__(self, *args, **kwargs):
@@ -150,7 +150,7 @@ class SystemTrayIcon(
if (
PLATFORM == 'Darwin'
or isWindows7()
or SystemRuntime.isWindows7()
or (PLATFORM == 'Windows' and darkdetect.theme() == 'Light')
):
# Darker
@@ -164,12 +164,12 @@ class SystemTrayIcon(
return
if isAdministrator():
if SystemRuntime.isAdmin():
self.setIcon(bootstrapIcon('rocket-takeoff-admin-connected.svg'))
else:
if (
PLATFORM == 'Darwin'
or isWindows7()
or SystemRuntime.isWindows7()
or (PLATFORM == 'Windows' and darkdetect.theme() == 'Light')
):
# Darker
File diff suppressed because it is too large Load Diff
+27 -27
View File
@@ -17,12 +17,10 @@
from __future__ import annotations
from Furious.Interface import *
from Furious.PyFramework import *
from Furious.QtFramework import *
from Furious.QtFramework import gettext as _
from Furious.Utility import *
from Furious.Frozenlib import *
from Furious.Library import *
from Furious.Qt import *
from Furious.Qt import gettext as _
from PySide6 import QtCore
from PySide6.QtGui import *
@@ -76,7 +74,7 @@ class UserSubsAppQComboBox(AppQComboBox):
def retranslate(self):
# Do not emit 'currentTextChanged' when retranslate
with QBlockSignals(self):
with Mixins.QBlockSignalContext(self):
super().retranslate()
@@ -105,7 +103,7 @@ TRANSLATABLE_HEADERS = [
]
class UserSubsQTableWidget(QTranslatable, AppQTableWidget):
class UserSubsQTableWidget(Mixins.QTranslatable, AppQTableWidget):
AutoUpdateOptions = {
'': None,
'Never': None,
@@ -126,7 +124,7 @@ class UserSubsQTableWidget(QTranslatable, AppQTableWidget):
ProxyOptions = {
'': lambda: None,
'Use current proxy': lambda: connectedHttpProxyEndpoint(),
'Use current proxy': lambda: Storage.Extras.UserHttpProxy(),
'Force proxy': lambda: '127.0.0.1:10809',
'No proxy': lambda: None,
}
@@ -150,7 +148,7 @@ class UserSubsQTableWidget(QTranslatable, AppQTableWidget):
super().__init__(*args, **kwargs)
self.timers = list(QtCore.QTimer() for i in range(len(AS_UserSubscription())))
self.timers = list(QtCore.QTimer() for i in range(len(Storage.UserSubs())))
# Must set before flush all
self.setColumnCount(len(self.Headers))
@@ -200,7 +198,7 @@ class UserSubsQTableWidget(QTranslatable, AppQTableWidget):
@QtCore.Slot(QTableWidgetItem)
def handleItemChanged(self, item: QTableWidgetItem):
unique = list(AS_UserSubscription().keys())[item.row()]
unique = list(Storage.UserSubs().keys())[item.row()]
mapped = UserSubsQTableWidget.ItemKey[item.column()]
# 'autoupdate', 'proxy' is not triggered here
@@ -208,7 +206,7 @@ class UserSubsQTableWidget(QTranslatable, AppQTableWidget):
item.setToolTip(item.text())
AS_UserSubscription()[unique][mapped] = item.text()
Storage.UserSubs()[unique][mapped] = item.text()
@QtCore.Slot(QtCore.QPoint)
def handleCustomContextMenuRequested(self, point):
@@ -222,14 +220,16 @@ class UserSubsQTableWidget(QTranslatable, AppQTableWidget):
return
def handleResultCode(_indexes, code):
if code == PySide6LegacyEnumValueWrapper(AppQMessageBox.StandardButton.Yes):
if code == PySide6Legacy.enumValueWrapper(
AppQMessageBox.StandardButton.Yes
):
for i in range(len(_indexes)):
deleteIndex = _indexes[i] - i
deleteUnique = list(AS_UserSubscription().keys())[deleteIndex]
deleteUnique = list(Storage.UserSubs().keys())[deleteIndex]
self.removeRow(deleteIndex)
AS_UserSubscription().pop(deleteUnique)
Storage.UserSubs().pop(deleteUnique)
# Begin timer cleanup
qtimer = self.timers[deleteIndex]
@@ -277,8 +277,8 @@ class UserSubsQTableWidget(QTranslatable, AppQTableWidget):
def handleAutoUpdateComboBoxCurrentTextChanged(self, text: str, row: int):
textEnglish = _(text, 'EN')
unique = list(AS_UserSubscription().keys())[row]
subsob = AS_UserSubscription()[unique]
unique = list(Storage.UserSubs().keys())[row]
subsob = Storage.UserSubs()[unique]
remark, webURL = subsob['remark'], subsob['webURL']
@@ -346,8 +346,8 @@ class UserSubsQTableWidget(QTranslatable, AppQTableWidget):
def handleProxyComboBoxCurrentTextChanged(self, text: str, row: int):
textEnglish = _(text, 'EN')
unique = list(AS_UserSubscription().keys())[row]
subsob = AS_UserSubscription()[unique]
unique = list(Storage.UserSubs().keys())[row]
subsob = Storage.UserSubs()[unique]
try:
proxyFn = self.ProxyOptions[textEnglish]
@@ -380,7 +380,7 @@ class UserSubsQTableWidget(QTranslatable, AppQTableWidget):
if oldItem is None:
# Item does not exist
newItem.setFont(QFont(APP().customFontName))
newItem.setFont(QFont(AppFontName()))
else:
# Use existing
newItem.setFont(oldItem.font())
@@ -409,7 +409,7 @@ class UserSubsQTableWidget(QTranslatable, AppQTableWidget):
if oldItem is None:
# Item does not exist
newItem.setFont(QFont(APP().customFontName))
newItem.setFont(QFont(AppFontName()))
else:
# Use existing
newItem.setFont(oldItem.font())
@@ -439,7 +439,7 @@ class UserSubsQTableWidget(QTranslatable, AppQTableWidget):
if oldItem is None:
# Item does not exist
newItem.setFont(QFont(APP().customFontName))
newItem.setFont(QFont(AppFontName()))
else:
# Use existing
newItem.setFont(oldItem.font())
@@ -464,12 +464,12 @@ class UserSubsQTableWidget(QTranslatable, AppQTableWidget):
def flushAll(self):
if self.rowCount() == 0:
# Should insert row
for index, key in enumerate(AS_UserSubscription()):
for index, key in enumerate(Storage.UserSubs()):
self.insertRow(index)
self.flushRow(index, AS_UserSubscription()[key])
self.flushRow(index, Storage.UserSubs()[key])
else:
for index, key in enumerate(AS_UserSubscription()):
self.flushRow(index, AS_UserSubscription()[key])
for index, key in enumerate(Storage.UserSubs()):
self.flushRow(index, Storage.UserSubs()[key])
def appendNewItem(self, **kwargs):
unique, remark, webURL, autoupdate, proxy = (
@@ -493,11 +493,11 @@ class UserSubsQTableWidget(QTranslatable, AppQTableWidget):
# 'unique' should be unique in subscription storage,
# but protect it anyway.
if unique not in AS_UserSubscription():
if unique not in Storage.UserSubs():
# Add timer
self.timers.append(QtCore.QTimer())
AS_UserSubscription().update(subsob)
Storage.UserSubs().update(subsob)
row = self.rowCount()
+12 -10
View File
@@ -17,9 +17,9 @@
from __future__ import annotations
from Furious.PyFramework import *
from Furious.QtFramework import *
from Furious.QtFramework import gettext as _
from Furious.Frozenlib import *
from Furious.Qt import *
from Furious.Qt import gettext as _
from Furious.Utility import *
from PySide6 import QtCore
@@ -55,7 +55,7 @@ class AssetExistsMBox(AppQMessageBox):
self.moveToCenter()
class XrayAssetViewerQListWidget(SupportThemeChangedCallback, AppQListWidget):
class XrayAssetViewerQListWidget(Mixins.ThemeAware, AppQListWidget):
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
@@ -65,7 +65,7 @@ class XrayAssetViewerQListWidget(SupportThemeChangedCallback, AppQListWidget):
self.setSelectionMode(AppQListWidget.SelectionMode.ExtendedSelection)
self.setIconSize(QtCore.QSize(64, 64))
if PLATFORM == 'Linux' and getUbuntuRelease() == '20.04':
if PLATFORM == 'Linux' and SystemRuntime.ubuntuRelease() == '20.04':
self.initialTheme = darkdetect.theme()
else:
self.initialTheme = None
@@ -117,7 +117,7 @@ class XrayAssetViewerQListWidget(SupportThemeChangedCallback, AppQListWidget):
)
item = QListWidgetItem(f'{filename:{maxlen + 6}}{mdate}')
item.setFont(QFont(APP().customFontName))
item.setFont(QFont(AppFontName()))
if AppSettings.isStateON_('DarkMode'):
# Custom dark mode
@@ -143,7 +143,7 @@ class XrayAssetViewerQListWidget(SupportThemeChangedCallback, AppQListWidget):
self.addItem(item)
def flushItem(self):
if PLATFORM == 'Linux' and getUbuntuRelease() == '20.04':
if PLATFORM == 'Linux' and SystemRuntime.ubuntuRelease() == '20.04':
assert self.initialTheme is not None
# Ubuntu 20.04. Flush by initial theme(Ubuntu 20.04 theme changes bug)
@@ -183,7 +183,7 @@ class XrayAssetViewerQListWidget(SupportThemeChangedCallback, AppQListWidget):
if os.path.isfile(XRAY_ASSET_DIR / basename):
def handleResultCode(_filename, code):
if code == PySide6LegacyEnumValueWrapper(
if code == PySide6Legacy.enumValueWrapper(
AppQMessageBox.StandardButton.Yes
):
append(_filename)
@@ -210,7 +210,9 @@ class XrayAssetViewerQListWidget(SupportThemeChangedCallback, AppQListWidget):
return
def handleResultCode(_indexes, code):
if code == PySide6LegacyEnumValueWrapper(AppQMessageBox.StandardButton.Yes):
if code == PySide6Legacy.enumValueWrapper(
AppQMessageBox.StandardButton.Yes
):
for index in _indexes:
os.remove(XRAY_ASSET_DIR / self.item(index).text())
@@ -244,7 +246,7 @@ class XrayAssetViewerQListWidget(SupportThemeChangedCallback, AppQListWidget):
super().keyPressEvent(event)
def themeChangedCallback(self, theme):
if PLATFORM == 'Linux' and getUbuntuRelease() == '20.04':
if PLATFORM == 'Linux' and SystemRuntime.ubuntuRelease() == '20.04':
# Ubuntu 20.04 system dark theme does not
# change menu color. Do nothing
pass
+14 -16
View File
@@ -17,21 +17,19 @@
from __future__ import annotations
from Furious.Frozenlib import *
from Furious.Interface import *
from Furious.QtFramework import *
from Furious.QtFramework import gettext as _
from Furious.Library import *
from Furious.Utility import *
from Furious.Qt import *
from Furious.Qt import gettext as _
from Furious.Widget.UserServersQTableWidget import *
from Furious.Widget.GuiTUNSettings import *
from Furious.Window.UserSubsWindow import *
from Furious.Window.LogViewerWindow import *
from Furious.Window.XrayAssetViewerWindow import *
from PySide6 import QtCore
from PySide6.QtGui import *
from PySide6.QtWidgets import *
from PySide6.QtNetwork import *
from typing import Union
@@ -78,7 +76,7 @@ class AppNetworkStateManager(NetworkStateManager):
self.stopTest()
connectedHttpProxy = connectedHttpProxyEndpoint()
connectedHttpProxy = Storage.Extras.UserHttpProxy()
if connectedHttpProxy is None:
parent = self.parent()
@@ -121,7 +119,7 @@ class AppMainWindow(AppQMainWindow):
deleteUniqueCallback=lambda unique: self.userServersQTableWidget.deleteItemByIndex(
list(
index
for index, server in enumerate(AS_UserServers())
for index, server in enumerate(Storage.UserServers())
if server.getExtras('subsId') == unique
)
),
@@ -134,7 +132,7 @@ class AppMainWindow(AppQMainWindow):
logActions = [
AppQAction(
_('Show Furious Log...'),
callback=lambda: loggerApp_().showMaximized(),
callback=lambda: AppLoggerWindow.Self().showMaximized(),
shortcut=QtCore.QKeyCombination(
QtCore.Qt.KeyboardModifier.ControlModifier
| QtCore.Qt.KeyboardModifier.ShiftModifier,
@@ -143,7 +141,7 @@ class AppMainWindow(AppQMainWindow):
),
AppQAction(
_('Show Core Log...'),
callback=lambda: loggerCore().showMaximized(),
callback=lambda: AppLoggerWindow.Core().showMaximized(),
shortcut=QtCore.QKeyCombination(
QtCore.Qt.KeyboardModifier.ControlModifier
| QtCore.Qt.KeyboardModifier.ShiftModifier,
@@ -152,7 +150,7 @@ class AppMainWindow(AppQMainWindow):
),
AppQAction(
_('Show Tun2socks Log...'),
callback=lambda: loggerTun_().showMaximized(),
callback=lambda: AppLoggerWindow.TUN_().showMaximized(),
shortcut=QtCore.QKeyCombination(
QtCore.Qt.KeyboardModifier.ControlModifier
| QtCore.Qt.KeyboardModifier.ShiftModifier,
@@ -210,7 +208,7 @@ class AppMainWindow(AppQMainWindow):
AppQAction(
_('Update Subscription (Use Current Proxy)'),
callback=lambda: self.userServersQTableWidget.updateSubs(
connectedHttpProxyEndpoint(),
Storage.Extras.UserHttpProxy(),
parent=self,
),
),
@@ -408,10 +406,10 @@ class AppMainWindow(AppQMainWindow):
def updateSubsByUnique(self, unique: str, httpProxy: Union[str, None], **kwargs):
self.userServersQTableWidget.updateSubsByUnique(unique, httpProxy, **kwargs)
def appendNewItemByFactory(self, factory: ConfigurationFactory):
def appendNewItemByFactory(self, factory: ConfigFactory):
self.userServersQTableWidget.appendNewItemByFactory(factory)
def flushRow(self, row: int, item: ConfigurationFactory):
def flushRow(self, row: int, item: ConfigFactory):
self.userServersQTableWidget.flushRow(row, item)
def showTabAndSpaces(self):
@@ -428,19 +426,19 @@ class AppMainWindow(AppQMainWindow):
return GuiTUNSettings(parent=parent, **kwargs)
guiTUNSettings = cachedGuiTUNSettings()
guiTUNSettings.factoryToInput(AS_UserTUNSettings())
guiTUNSettings.factoryToInput(Storage.UserTUNSettings())
return guiTUNSettings
def checkForUpdates(self, **kwargs):
self.updatesManager.configureHttpProxy(connectedHttpProxyEndpoint())
self.updatesManager.configureHttpProxy(Storage.Extras.UserHttpProxy())
self.updatesManager.checkForUpdates(**kwargs)
def resetNetworkState(self):
self.networkState.setText('')
def setNetworkState(self, success: bool, **kwargs):
remark = connectedServerRemark()
remark = Storage.Extras.UserServerRemark()
if success:
if remark:
+2 -4
View File
@@ -17,14 +17,12 @@
from __future__ import annotations
from Furious.QtFramework import *
from Furious.QtFramework import gettext as _
from Furious.Qt import *
from Furious.Qt import gettext as _
from PySide6 import QtCore
from PySide6.QtWidgets import *
import functools
__all__ = ['LogViewerWindow']
+5 -6
View File
@@ -17,11 +17,10 @@
from __future__ import annotations
from Furious.Interface import *
from Furious.PyFramework import *
from Furious.QtFramework import *
from Furious.QtFramework import gettext as _
from Furious.Utility import *
from Furious.Frozenlib import *
from Furious.Library import *
from Furious.Qt import *
from Furious.Qt import gettext as _
from PySide6 import QtCore
from PySide6.QtGui import *
@@ -55,7 +54,7 @@ class QRCodeWindow(AppQMainWindow):
for index in indexes:
qrdata = io.BytesIO()
config = AS_UserServers()[index]
config = Storage.UserServers()[index]
try:
uri = config.toURI()
+10 -11
View File
@@ -17,11 +17,10 @@
from __future__ import annotations
from Furious.PyFramework import *
from Furious.QtFramework import *
from Furious.QtFramework import gettext as _
from Furious.Utility import *
from Furious.Frozenlib import *
from Furious.Library import *
from Furious.Qt import *
from Furious.Qt import gettext as _
from Furious.Widget.IndentSpinBox import *
from PySide6 import QtCore
@@ -98,7 +97,7 @@ class TextEditorWindow(AppQMainWindow):
)
self.jsonEditor = DraculaJSONTextEditor(
fontFamily=APP().customFontName,
fontFamily=AppFontName(),
pointSizeSettingsName='ServerWidgetPointSize',
)
self.jsonEditor.setLineWrapMode(DraculaJSONTextEditor.LineWrapMode.NoWrap)
@@ -230,7 +229,7 @@ class TextEditorWindow(AppQMainWindow):
def setPlainText(self, text: str, blockSignals: bool):
if blockSignals:
with QBlockSignals(self.jsonEditor):
with Mixins.QBlockSignalContext(self.jsonEditor):
self.jsonEditor.setPlainText(text)
else:
self.jsonEditor.setPlainText(text)
@@ -262,12 +261,12 @@ class TextEditorWindow(AppQMainWindow):
return False
else:
old = AS_UserServers()[index]
new = constructFromDict(jsonObject, **old.kwargs)
old = Storage.UserServers()[index]
new = configFactoryFromDict(jsonObject, **old.kwargs)
old.deleted = True
AS_UserServers()[index] = new
Storage.UserServers()[index] = new
try:
APP().mainWindow.flushRow(index, new)
@@ -276,7 +275,7 @@ class TextEditorWindow(AppQMainWindow):
pass
if index == AS_UserActivatedItemIndex():
if index == Storage.UserActivatedItemIndex():
showNewChangesNextTimeMBox(parent=self, method=showChangesMethod)
self.markAsSaved()
@@ -306,7 +305,7 @@ class TextEditorWindow(AppQMainWindow):
def setIndent(self):
def handleResultCode(_indentSpinBox, code):
if code == PySide6LegacyEnumValueWrapper(AppQDialog.DialogCode.Accepted):
if code == PySide6Legacy.enumValueWrapper(AppQDialog.DialogCode.Accepted):
plain = self.jsonEditor.toPlainText()
try:
+7 -8
View File
@@ -15,16 +15,15 @@
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <https://www.gnu.org/licenses/>.
from Furious.QtFramework import *
from Furious.QtFramework import gettext as _
from Furious.Library import *
from Furious.Utility import *
from __future__ import annotations
from Furious.Frozenlib import *
from Furious.Qt import *
from Furious.Qt import gettext as _
from Furious.Widget.UserSubsQTableWidget import *
from PySide6 import QtWidgets
from PySide6.QtGui import *
from PySide6 import QtCore
from PySide6.QtWidgets import *
from PySide6.QtNetwork import *
import uuid
import functools
@@ -115,7 +114,7 @@ class UserSubsWindow(AppQMainWindow):
def addSubs(self):
def handleResultCode(_addSubsDialog, code):
if code == PySide6LegacyEnumValueWrapper(AppQDialog.DialogCode.Accepted):
if code == PySide6Legacy.enumValueWrapper(AppQDialog.DialogCode.Accepted):
remark = _addSubsDialog.subsRemark()
webURL = _addSubsDialog.subsWebURL()
+3 -6
View File
@@ -17,18 +17,15 @@
from __future__ import annotations
from Furious.Interface import *
from Furious.QtFramework import *
from Furious.QtFramework import gettext as _
from Furious.Library import *
from Furious.Utility import *
from Furious.Frozenlib import *
from Furious.Qt import *
from Furious.Qt import gettext as _
from Furious.Widget.XrayAssetViewerQListWidget import *
from PySide6.QtGui import *
from PySide6.QtWidgets import *
import logging
import functools
__all__ = ['XrayAssetViewerWindow']
+1 -1
View File
@@ -15,4 +15,4 @@
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <https://www.gnu.org/licenses/>.
from .Utility import AppResources
from .Frozenlib import AppResources
+21 -22
View File
@@ -15,9 +15,10 @@
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <https://www.gnu.org/licenses/>.
from Furious.Frozenlib import *
from Furious.Interface import *
from Furious.QtFramework import *
from Furious.QtFramework import gettext as _
from Furious.Qt import *
from Furious.Qt import gettext as _
from Furious.Utility import *
from Furious.Widget.Application import Application
@@ -49,12 +50,12 @@ def runAppMain():
# For Qt runtime. Not used
_app = Application(sys.argv)
if exitcode == ApplicationFactory.ExitCode.PlatformNotSupported:
messageBox = AppQMessageBox(icon=AppQMessageBox.Icon.Critical)
if exitcode == ApplicationFactory.ExitCode.PlatformNotSupported.value:
mbox = AppQMessageBox(icon=AppQMessageBox.Icon.Critical)
messageBox.setWindowIcon(bootstrapIcon('rocket-takeoff-window.svg'))
messageBox.setWindowTitle(_(APPLICATION_NAME))
messageBox.setText(
mbox.setWindowIcon(bootstrapIcon('rocket-takeoff-window.svg'))
mbox.setWindowTitle(_(APPLICATION_NAME))
mbox.setText(
_(f'{APPLICATION_NAME} is not able to run on this operating system')
)
@@ -68,7 +69,7 @@ def runAppMain():
'machine',
)
messageBox.setInformativeText(
mbox.setInformativeText(
_('Operating system information')
+ '\n'
+ '\n'.join(
@@ -77,9 +78,9 @@ def runAppMain():
)
# Show the MessageBox and wait for the user to close it
messageBox.exec()
mbox.exec()
else:
if exitcode == ApplicationFactory.ExitCode.AssertionError:
if exitcode == ApplicationFactory.ExitCode.AssertionError.value:
# Assertion error
text = _(
f'{APPLICATION_NAME} encountered an internal error and needs to be stopped'
@@ -90,11 +91,11 @@ def runAppMain():
f'{APPLICATION_NAME} stopped unexpectedly due to an unknown exception'
)
messageBox = AppQMessageBox(icon=AppQMessageBox.Icon.Critical)
mbox = AppQMessageBox(icon=AppQMessageBox.Icon.Critical)
messageBox.setWindowIcon(bootstrapIcon('rocket-takeoff-window.svg'))
messageBox.setWindowTitle(_(APPLICATION_NAME))
messageBox.setText(text)
mbox.setWindowIcon(bootstrapIcon('rocket-takeoff-window.svg'))
mbox.setWindowTitle(_(APPLICATION_NAME))
mbox.setText(text)
if process.fileWritten.value:
# Crash log saved
@@ -102,15 +103,13 @@ def runAppMain():
logger.info(f'crash log has been saved to {crashLogFile}')
messageBox.setInformativeText(
mbox.setInformativeText(
_('Crash log has been saved to') + f' {crashLogFile}'
)
button0 = messageBox.addButton(
button0 = mbox.addButton(
_('Open crash log'), AppQMessageBox.ButtonRole.AcceptRole
)
button1 = messageBox.addButton(
_('OK'), AppQMessageBox.ButtonRole.RejectRole
)
button1 = mbox.addButton(_('OK'), AppQMessageBox.ButtonRole.RejectRole)
def handleButtonClicked(button):
if button == button0:
@@ -125,16 +124,16 @@ def runAppMain():
# OK. Do nothing
pass
messageBox.buttonClicked.connect(handleButtonClicked)
mbox.buttonClicked.connect(handleButtonClicked)
# Show the MessageBox and wait for the user to close it
messageBox.exec()
mbox.exec()
else:
# Crash log wasn't saved
logger.info(f'crash log was not saved')
# Show the MessageBox and wait for the user to close it
messageBox.exec()
mbox.exec()
sys.exit(exitcode)
+3 -3
View File
@@ -17,9 +17,9 @@
from __future__ import annotations
from Furious.Library import *
from Furious.Utility import *
from Furious.Frozenlib import *
from Furious.Externals import *
from Furious.Library import *
import os
import re
@@ -58,7 +58,7 @@ def getMagicNameFromPath(path):
@functools.lru_cache(None)
def getAppConstantsByName(name):
return getattr(Furious.Utility.Constants, name)
return getattr(Furious.Frozenlib.Constants, name)
APPLICATION_SOURCE_CODE_PATH = getAppSourceCodePath(PACKAGE_DIR)