diff --git a/Furious/Core/CoreManager.py b/Furious/Core/CoreManager.py
index d91e862a..bf44780c 100644
--- a/Furious/Core/CoreManager.py
+++ b/Furious/Core/CoreManager.py
@@ -15,6 +15,8 @@
# You should have received a copy of the GNU General Public License
# along with this program. If not, see .
+"""Coordinate proxy cores, TUN mode, DNS, and platform routing."""
+
from __future__ import annotations
from Furious.Frozenlib import *
@@ -41,6 +43,7 @@ logger = logging.getLogger(__name__)
def fixLogObjectPath(config: ConfigFactory, attr: str, value: str, log=True):
+ """Resolve and normalize one log-file path in a core configuration."""
try:
path = config['log'][attr]
except Exception:
@@ -84,6 +87,7 @@ def fixLogObjectPath(config: ConfigFactory, attr: str, value: str, log=True):
def cleanRoutingRule(rule: dict):
+ """Remove empty match fields from an Xray routing rule."""
if not isinstance(rule, dict):
return None
@@ -131,6 +135,7 @@ def cleanRoutingRule(rule: dict):
def customRoutingObjectFromSettings(routing: str):
+ """Load a named custom routing object from persistent settings."""
prefix = 'Custom:'
if not isinstance(routing, str) or not routing.startswith(prefix):
@@ -165,6 +170,7 @@ def customRoutingObjectFromSettings(routing: str):
def routingObjectHasDirectRule(routingObject: dict) -> bool:
+ """Return whether an Xray routing object contains a direct rule."""
try:
return any(
rule.get('outboundTag') == 'direct'
@@ -178,6 +184,7 @@ def routingObjectHasDirectRule(routingObject: dict) -> bool:
def getUserTUNSettings(*args, **kwargs):
+ """Return one user-defined TUN setting or its fallback value."""
return Storage.UserTUNSettings().get(*args, **kwargs)
@@ -205,7 +212,9 @@ def getUserTUNSettings(*args, **kwargs):
class CoreManager(Mixins.CleanupOnExit):
+ """Coordinate proxy cores, TUN setup, DNS changes, and routing cleanup."""
def __init__(self, *args, **kwargs):
+ """Initialize the CoreManager."""
super().__init__(*args, **kwargs)
self.uniqueCleanup = False
@@ -222,6 +231,7 @@ class CoreManager(Mixins.CleanupOnExit):
log=True,
**kwargs,
) -> Tuple[Union[CoreProcessWorker, None], bool]:
+ """Return the start core value used by the core manager."""
return None, False
@_startCore.register(ConfigXray)
@@ -235,6 +245,7 @@ class CoreManager(Mixins.CleanupOnExit):
log=True,
**kwargs,
):
+ """Handle the registered singledispatch variant."""
if config.get('log') is None or not isinstance(config['log'], dict):
config['log'] = {
'access': '',
@@ -332,6 +343,7 @@ class CoreManager(Mixins.CleanupOnExit):
log=True,
**kwargs,
):
+ """Handle the registered singledispatch variant."""
if routing == AppBuiltinRouting.BypassMainlandChina.value:
# TUN Mode handling
if not proxyModeOnly and SystemRuntime.isTUNMode():
@@ -385,6 +397,7 @@ class CoreManager(Mixins.CleanupOnExit):
log=True,
**kwargs,
):
+ """Handle the registered singledispatch variant."""
if log:
logger.info(f'core {Hysteria2.name()} configured')
@@ -395,6 +408,7 @@ class CoreManager(Mixins.CleanupOnExit):
@staticmethod
def waitForTUNDeviceBroughtUp(func: Callable[[str], bool], deviceName: str) -> bool:
+ """Wait for the platform to expose the configured TUN device."""
for counter in range(0, 10000, 100):
if func(deviceName):
logger.info(
@@ -421,12 +435,14 @@ class CoreManager(Mixins.CleanupOnExit):
log=True,
**kwargs,
) -> bool:
+ """Start the selected proxy core and configure TUN mode when requested."""
if deepcopy:
configcopy = config.deepcopy()
else:
configcopy = config
def abortStart(message: str = ''):
+ """Return the abort start value used by the core manager."""
if message:
logger.error(message)
@@ -639,6 +655,7 @@ class CoreManager(Mixins.CleanupOnExit):
if alias:
def _windowsCleanup(_alias):
+ """Handle windows cleanup for the core manager."""
SystemRoutingTable.WIN32SetInterfaceDNS(_alias)
SystemRoutingTable.WIN32FlushDNSCache()
@@ -693,6 +710,7 @@ class CoreManager(Mixins.CleanupOnExit):
servers = SystemRoutingTable.DarwinGetDNSServers()
def _darwinCleanup(_servers):
+ """Handle darwin cleanup for the core manager."""
for _service, _dnsserver in _servers:
SystemRoutingTable.DarwinSetDNSServers(_service, _dnsserver)
@@ -726,6 +744,7 @@ class CoreManager(Mixins.CleanupOnExit):
if PLATFORM == 'Linux':
def _linuxCleanup():
+ """Handle linux cleanup for the core manager."""
SystemRoutingTable.LinuxDeleteTUNDevice(APPLICATION_TUN_DEVICE_NAME)
tun.cleanup = functools.partial(_linuxCleanup)
@@ -754,6 +773,7 @@ class CoreManager(Mixins.CleanupOnExit):
)
def route(source, destination) -> str:
+ """Configure platform routing for the active proxy connection."""
return f'{source} via {destination} dev {interface}'
iproute = SystemRoutingTable.LinuxGetIpRoute()
@@ -802,12 +822,15 @@ class CoreManager(Mixins.CleanupOnExit):
return True
def allRunning(self) -> bool:
+ """Return whether every managed process is running."""
return all(process.isAlive() for process in self.processesPool)
def anyRunning(self) -> bool:
+ """Return whether any managed process is running."""
return any(process.isAlive() for process in self.processesPool)
def stopAll(self):
+ """Stop every managed proxy and TUN process."""
try:
for process in list(self.processesPool):
if not isinstance(process, CoreProcessFactory):
@@ -821,4 +844,5 @@ class CoreManager(Mixins.CleanupOnExit):
self.processesPool.clear()
def cleanup(self):
+ """Release resources owned by the core manager."""
self.stopAll()
diff --git a/Furious/Core/CoreProcessWorker.py b/Furious/Core/CoreProcessWorker.py
index ac21a322..3af121d9 100644
--- a/Furious/Core/CoreProcessWorker.py
+++ b/Furious/Core/CoreProcessWorker.py
@@ -15,6 +15,8 @@
# You should have received a copy of the GNU General Public License
# along with this program. If not, see .
+"""Manage proxy-core child processes and their output queues."""
+
from __future__ import annotations
from Furious.Frozenlib import *
@@ -49,6 +51,7 @@ logger = logging.getLogger(__name__)
class CoreProcessState(Enum):
+ """Enumerate core process state."""
Idle = 'idle'
Starting = 'starting'
Running = 'running'
@@ -59,6 +62,7 @@ class CoreProcessState(Enum):
@dataclass
class CoreLaunchSpec:
+ """Describe the parameters required by a core launch operation."""
target: Callable
args: Tuple[Any, ...] = field(default_factory=tuple)
processKwargs: Dict[str, Any] = field(default_factory=dict)
@@ -67,6 +71,7 @@ class CoreLaunchSpec:
waitTime: int = 2500
def __post_init__(self):
+ """Normalize the initialized core launch spec values."""
self.args = tuple(self.args)
self.processKwargs = dict(self.processKwargs)
@@ -78,6 +83,7 @@ class CoreLaunchSpec:
@classmethod
def fromProcessKwargs(cls, **kwargs):
+ """Build a launch specification from multiprocessing keyword arguments."""
daemon, waitCore, waitTime, target, args = (
kwargs.pop('daemon', True),
kwargs.pop('waitCore', True),
@@ -96,6 +102,7 @@ class CoreLaunchSpec:
)
def toProcessKwargs(self):
+ """Convert the launch specification to multiprocessing keyword arguments."""
kwargs = dict(self.processKwargs)
kwargs.update(
{
@@ -108,11 +115,13 @@ class CoreLaunchSpec:
class MsgQueue(multiprocessing.queues.Queue):
+ """Deliver child-process log messages to Qt callbacks at an adaptive rate."""
MSG_PRODUCE_THRESHOLD = 1024
OPTIMIZER_MIN_FREQ = 2
OPTIMIZER_MAX_FREQ = 256
def __init__(self, **kwargs):
+ """Initialize the MsgQueue."""
msgCallback = kwargs.pop('msgCallback', None)
backgroundOptimizer = kwargs.pop('backgroundOptimizer', None)
@@ -125,6 +134,7 @@ class MsgQueue(multiprocessing.queues.Queue):
self.backgroundOptimizer = backgroundOptimizer
def getNoWait(self) -> str:
+ """Return no wait."""
try:
return self.get_nowait()
except Exception:
@@ -133,19 +143,24 @@ class MsgQueue(multiprocessing.queues.Queue):
return ''
def getTimeout(self) -> int:
+ """Return timeout."""
return self.timeout
def setTimeout(self, timeout: int):
+ """Set timeout."""
self.timeout = timeout
def startTimer(self):
+ """Start timer."""
self.timer.start(self.getTimeout())
def stopTimer(self):
+ """Stop timer."""
self.timer.stop()
@property
def optimizer(self):
+ """Return the optimizer value."""
try:
return self.backgroundOptimizer()
except Exception:
@@ -154,6 +169,7 @@ class MsgQueue(multiprocessing.queues.Queue):
return None
def processMsg(self):
+ """Process msg."""
msg = self.getNoWait()
if not callable(self.callback):
@@ -190,9 +206,11 @@ class MsgQueue(multiprocessing.queues.Queue):
class CoreProcessMonitor(CoreProcessFactory, ABC):
+ """Track the state and lifetime of a proxy-core child process."""
StopJoinTimeout = 3
def __init__(self, **kwargs):
+ """Initialize the CoreProcessMonitor."""
exitCallback = kwargs.pop('exitCallback', None)
super().__init__(exitCallback)
@@ -206,37 +224,46 @@ class CoreProcessMonitor(CoreProcessFactory, ABC):
@property
def process(self) -> Union[multiprocessing.Process, None]:
+ """Return the process value."""
return self._process
@process.setter
def process(self, process: Union[multiprocessing.Process, None]):
+ """Set the process value."""
self._process = process
@property
def daemon(self) -> QtCore.QTimer:
+ """Return the daemon value."""
return self._daemon
@property
def state(self) -> CoreProcessState:
+ """Return the state value."""
return self._state
def setState(self, state: CoreProcessState):
+ """Set state."""
self._state = state
@property
def lastExitCode(self):
+ """Return the last exit code value."""
return self._lastExitCode
def setLastExitCode(self, exitCode):
+ """Set last exit code."""
self._lastExitCode = exitCode
def isAlive(self) -> bool:
+ """Return whether alive."""
if isinstance(self.process, multiprocessing.Process):
return self.process.is_alive()
else:
return False
def queryIsAlive(self):
+ """Query is alive."""
if isinstance(self.process, multiprocessing.Process):
if self.process.is_alive():
return True
@@ -248,9 +275,11 @@ class CoreProcessMonitor(CoreProcessFactory, ABC):
return False
def handleInternalProcessStopped(self, *args, **kwargs):
+ """Handle internal process stopped."""
raise NotImplementedError
def closeProcess(self):
+ """Close process."""
if isinstance(self.process, multiprocessing.Process):
try:
self.process.close()
@@ -262,7 +291,9 @@ class CoreProcessMonitor(CoreProcessFactory, ABC):
class CoreProcessWorker(CoreProcessMonitor, ABC):
+ """Run and monitor a proxy core in a child process."""
def __init__(self, **kwargs):
+ """Initialize the CoreProcessWorker."""
msgCallback = kwargs.pop('msgCallback', None)
# Optimizer is AppLoggerWindow.Core window
backgroundOptimizer = kwargs.pop('backgroundOptimizer', AppLoggerWindow.Core)
@@ -274,6 +305,7 @@ class CoreProcessWorker(CoreProcessMonitor, ABC):
)
def handleInternalProcessStopped(self):
+ """Handle internal process stopped."""
exitcode = self.process.exitcode
logger.error(f'{self.name()} stopped unexpectedly with exitcode {exitcode}')
@@ -289,9 +321,11 @@ class CoreProcessWorker(CoreProcessMonitor, ABC):
self.closeProcess()
def start(self, **kwargs) -> bool:
+ """Start the core process worker."""
return self.startWithSpec(CoreLaunchSpec.fromProcessKwargs(**kwargs))
def startWithSpec(self, launchSpec: CoreLaunchSpec) -> bool:
+ """Start and validate a child process from a launch specification."""
if not isinstance(launchSpec, CoreLaunchSpec):
logger.error(f'invalid launch spec for {self.name()}: {launchSpec}')
@@ -348,6 +382,7 @@ class CoreProcessWorker(CoreProcessMonitor, ABC):
return False
def stop(self):
+ """Stop the core process worker."""
self.msgQueue.stopTimer()
self.daemon.stop()
@@ -379,12 +414,14 @@ class CoreProcessWorker(CoreProcessMonitor, ABC):
class ProcessOutputRedirector:
+ """Redirect child-process output into the application message queue."""
TemporaryDir = QtCore.QTemporaryDir()
@staticmethod
def launch(
msgQueue: multiprocessing.Queue, entrypoint: Callable[[], None], redirect: bool
):
+ """Run an entry point while forwarding its output to a message queue."""
if not callable(entrypoint):
return
@@ -415,6 +452,7 @@ class ProcessOutputRedirector:
sys.stderr = tmpFileStream
def produceMsg():
+ """Forward one process-output message to the shared queue."""
with open(temporaryFile, 'rb') as file:
while True:
for line in iter(file.readline, b''):
diff --git a/Furious/Core/Hysteria1.py b/Furious/Core/Hysteria1.py
index e2020cbe..60c8084a 100644
--- a/Furious/Core/Hysteria1.py
+++ b/Furious/Core/Hysteria1.py
@@ -15,6 +15,8 @@
# You should have received a copy of the GNU General Public License
# along with this program. If not, see .
+"""Wrap the embedded Hysteria 1 core process."""
+
from __future__ import annotations
from Furious.Frozenlib import *
@@ -34,6 +36,7 @@ __all__ = ['Hysteria1']
def startHysteria1(jsonString, rule, mmdb, msgQueue: multiprocessing.Queue):
+ """Start hysteria1."""
try:
import hysteria
except ImportError:
@@ -54,17 +57,21 @@ def startHysteria1(jsonString, rule, mmdb, msgQueue: multiprocessing.Queue):
class Hysteria1(CoreProcessWorker):
+ """Manage the embedded Hysteria 1 core subprocess."""
class ExitCode(Enum):
+ """Enumerate process exit codes."""
ConfigurationError = 23
RemoteNetworkError = 3
# Windows shutting down
SystemShuttingDown = 0x40010004
def __init__(self, **kwargs):
+ """Initialize the Hysteria1."""
super().__init__(**kwargs)
@staticmethod
def loadOptionalFile(pathLike, fileType: str):
+ """Load optional file."""
if isinstance(pathLike, str) and pathLike == '':
return ''
@@ -96,18 +103,22 @@ class Hysteria1(CoreProcessWorker):
@staticmethod
def rule(rulePath):
+ """Return the rule value."""
return Hysteria1.loadOptionalFile(rulePath, 'rule')
@staticmethod
def mmdb(mmdbPath):
+ """Return the mmdb value."""
return Hysteria1.loadOptionalFile(mmdbPath, 'mmdb')
@staticmethod
def name() -> str:
+ """Return the process implementation name."""
return 'Hysteria1'
@staticmethod
def version() -> str:
+ """Return the bundled core version."""
try:
import hysteria
@@ -120,6 +131,7 @@ class Hysteria1(CoreProcessWorker):
def launchSpec(
self, config: Union[str, dict], rule, mmdb, **kwargs
) -> Union[CoreLaunchSpec, None]:
+ """Build the child-process launch specification."""
param = self.toJSONString(config)
if not param:
@@ -137,6 +149,7 @@ class Hysteria1(CoreProcessWorker):
)
def start(self, config: Union[str, dict], rule, mmdb, **kwargs) -> bool:
+ """Start the hysteria1."""
launchSpec = self.launchSpec(config, rule, mmdb, **kwargs)
if launchSpec is None:
diff --git a/Furious/Core/Hysteria2.py b/Furious/Core/Hysteria2.py
index 61edd914..c4accf57 100644
--- a/Furious/Core/Hysteria2.py
+++ b/Furious/Core/Hysteria2.py
@@ -15,6 +15,8 @@
# You should have received a copy of the GNU General Public License
# along with this program. If not, see .
+"""Wrap the embedded Hysteria 2 core process."""
+
from __future__ import annotations
from Furious.Frozenlib import *
@@ -34,6 +36,7 @@ __all__ = ['Hysteria2']
def startHysteria2(jsonString: str, msgQueue: multiprocessing.Queue):
+ """Start hysteria2."""
try:
import hysteria2
except ImportError:
@@ -54,7 +57,9 @@ def startHysteria2(jsonString: str, msgQueue: multiprocessing.Queue):
class Hysteria2(CoreProcessWorker):
+ """Manage the embedded Hysteria 2 core subprocess."""
class ExitCode(Enum):
+ """Enumerate process exit codes."""
ConfigurationError = 23
# Windows: 4294967295. Darwin, Linux: 255 (-1)
ServerStartFailure = 4294967295 if PLATFORM == 'Windows' else 255
@@ -62,14 +67,17 @@ class Hysteria2(CoreProcessWorker):
SystemShuttingDown = 0x40010004
def __init__(self, **kwargs):
+ """Initialize the Hysteria2."""
super().__init__(**kwargs)
@staticmethod
def name() -> str:
+ """Return the process implementation name."""
return 'Hysteria2'
@staticmethod
def version() -> str:
+ """Return the bundled core version."""
try:
import hysteria2
@@ -82,6 +90,7 @@ class Hysteria2(CoreProcessWorker):
def launchSpec(
self, config: Union[str, dict], **kwargs
) -> Union[CoreLaunchSpec, None]:
+ """Build the child-process launch specification."""
param = self.toJSONString(config)
if not param:
@@ -97,6 +106,7 @@ class Hysteria2(CoreProcessWorker):
)
def start(self, config: Union[str, dict], **kwargs) -> bool:
+ """Start the hysteria2."""
launchSpec = self.launchSpec(config, **kwargs)
if launchSpec is None:
diff --git a/Furious/Core/Tun2socks.py b/Furious/Core/Tun2socks.py
index 0f249a0c..cd9a676f 100644
--- a/Furious/Core/Tun2socks.py
+++ b/Furious/Core/Tun2socks.py
@@ -15,6 +15,8 @@
# You should have received a copy of the GNU General Public License
# along with this program. If not, see .
+"""Wrap the embedded tun2socks process used by TUN mode."""
+
from __future__ import annotations
from Furious.Frozenlib import *
@@ -28,6 +30,7 @@ __all__ = ['Tun2socks']
def startTun2socks(msgQueue: multiprocessing.Queue, *args):
+ """Start tun2socks."""
try:
import tun2socks
except ImportError:
@@ -48,12 +51,15 @@ def startTun2socks(msgQueue: multiprocessing.Queue, *args):
class Tun2socks(CoreProcessWorker):
+ """Manage the tun2socks subprocess used by TUN mode."""
class ExitCode:
# Windows shutting down
+ """Enumerate process exit codes."""
SystemShuttingDown = 0x40010004
def __init__(self, **kwargs):
# Optimizer is AppLoggerWindow.TUN_ window
+ """Initialize the Tun2socks."""
backgroundOptimizer = kwargs.pop('backgroundOptimizer', AppLoggerWindow.TUN_)
super().__init__(**kwargs, backgroundOptimizer=backgroundOptimizer)
@@ -62,10 +68,12 @@ class Tun2socks(CoreProcessWorker):
@staticmethod
def name() -> str:
+ """Return the process implementation name."""
return 'Tun2socks'
@staticmethod
def version() -> str:
+ """Return the bundled core version."""
try:
import tun2socks
@@ -87,6 +95,7 @@ class Tun2socks(CoreProcessWorker):
tcpAutoTuning: bool = False,
**kwargs,
) -> CoreLaunchSpec:
+ """Build the child-process launch specification."""
return CoreLaunchSpec(
target=startTun2socks,
args=(
@@ -115,6 +124,7 @@ class Tun2socks(CoreProcessWorker):
tcpAutoTuning: bool = False,
**kwargs,
) -> bool:
+ """Start the tun2socks."""
launchSpec = self.launchSpec(
device,
networkInterface,
@@ -130,6 +140,7 @@ class Tun2socks(CoreProcessWorker):
return self.startWithSpec(launchSpec)
def stop(self):
+ """Stop the tun2socks."""
if PLATFORM == 'Linux':
# Stop tunnel first
super().stop()
diff --git a/Furious/Core/XrayCore.py b/Furious/Core/XrayCore.py
index 0b8c5074..fa687b76 100644
--- a/Furious/Core/XrayCore.py
+++ b/Furious/Core/XrayCore.py
@@ -15,6 +15,8 @@
# You should have received a copy of the GNU General Public License
# along with this program. If not, see .
+"""Wrap the embedded Xray core process."""
+
from __future__ import annotations
from Furious.Frozenlib import *
@@ -35,6 +37,7 @@ __all__ = ['XrayCore']
def startXrayCore(jsonString: str, msgQueue: multiprocessing.Queue):
+ """Start Xray core."""
try:
import xray
except ImportError:
@@ -91,6 +94,7 @@ def startXrayCore(jsonString: str, msgQueue: multiprocessing.Queue):
fileStreams.append(stream)
def produceMsg():
+ """Forward one process-output message to the shared queue."""
while True:
for file in fileStreams:
for line in iter(file.readline, b''):
@@ -116,7 +120,9 @@ def startXrayCore(jsonString: str, msgQueue: multiprocessing.Queue):
class XrayCore(CoreProcessWorker):
+ """Manage the embedded Xray core subprocess."""
class ExitCode(Enum):
+ """Enumerate process exit codes."""
ConfigurationError = 23
# Windows: 4294967295. Darwin, Linux: 255 (-1)
ServerStartFailure = 4294967295 if PLATFORM == 'Windows' else 255
@@ -124,14 +130,17 @@ class XrayCore(CoreProcessWorker):
SystemShuttingDown = 0x40010004
def __init__(self, **kwargs):
+ """Initialize the XrayCore."""
super().__init__(**kwargs)
@staticmethod
def name() -> str:
+ """Return the process implementation name."""
return 'Xray-core'
@staticmethod
def version() -> str:
+ """Return the bundled core version."""
try:
import xray
@@ -144,6 +153,7 @@ class XrayCore(CoreProcessWorker):
def launchSpec(
self, config: Union[str, ConfigFactory, dict], **kwargs
) -> Union[CoreLaunchSpec, None]:
+ """Build the child-process launch specification."""
param = self.toJSONString(config)
if not param:
@@ -159,6 +169,7 @@ class XrayCore(CoreProcessWorker):
)
def start(self, config: Union[str, ConfigFactory, dict], **kwargs) -> bool:
+ """Start the Xray core."""
launchSpec = self.launchSpec(config, **kwargs)
if launchSpec is None:
diff --git a/Furious/Core/__init__.py b/Furious/Core/__init__.py
index 6ea5f142..7d72a47c 100644
--- a/Furious/Core/__init__.py
+++ b/Furious/Core/__init__.py
@@ -15,6 +15,8 @@
# You should have received a copy of the GNU General Public License
# along with this program. If not, see .
+"""Expose the public core-process integration API."""
+
from __future__ import annotations
from .XrayCore import *
diff --git a/Furious/Externals/GenTranslation.py b/Furious/Externals/GenTranslation.py
index 6ce50154..06596587 100644
--- a/Furious/Externals/GenTranslation.py
+++ b/Furious/Externals/GenTranslation.py
@@ -1,3 +1,5 @@
+"""Define source strings and translations used by the localization workflow."""
+
TRANSLATION = {
"Delete": {
"source": [
diff --git a/Furious/Externals/__init__.py b/Furious/Externals/__init__.py
index f9661d1d..029e0d63 100644
--- a/Furious/Externals/__init__.py
+++ b/Furious/Externals/__init__.py
@@ -15,6 +15,8 @@
# You should have received a copy of the GNU General Public License
# along with this program. If not, see .
+"""Initialize helpers for generated external data."""
+
from __future__ import annotations
from .GenTranslation import *
diff --git a/Furious/Frozenlib/AppResources.py b/Furious/Frozenlib/AppResources.py
index 50d789b7..f62d0609 100644
--- a/Furious/Frozenlib/AppResources.py
+++ b/Furious/Frozenlib/AppResources.py
@@ -3,6 +3,8 @@
# Created by: The Resource Compiler for Qt version 6.8.3
# WARNING! All changes made in this file will be lost!
+"""Provide Qt resources generated from the application resource collection."""
+
from PySide6 import QtCore
qt_resource_data = b"\
@@ -179543,9 +179545,11 @@ qt_resource_struct = b"\
"
def qInitResources():
+ """Register the generated resources with Qt."""
QtCore.qRegisterResourceData(0x03, qt_resource_struct, qt_resource_name, qt_resource_data)
def qCleanupResources():
+ """Unregister the generated resources from Qt."""
QtCore.qUnregisterResourceData(0x03, qt_resource_struct, qt_resource_name, qt_resource_data)
qInitResources()
diff --git a/Furious/Frozenlib/AppSettings.py b/Furious/Frozenlib/AppSettings.py
index ccd028c1..88a9646d 100644
--- a/Furious/Frozenlib/AppSettings.py
+++ b/Furious/Frozenlib/AppSettings.py
@@ -15,6 +15,8 @@
# You should have received a copy of the GNU General Public License
# along with this program. If not, see .
+"""Register, validate, read, and write persistent application settings."""
+
from __future__ import annotations
from PySide6 import QtCore
@@ -31,6 +33,7 @@ logger = logging.getLogger(__name__)
class AppBinarySettings:
+ """Store and validate app binary settings."""
OFF = '0'
ON_ = '1'
@@ -38,6 +41,7 @@ class AppBinarySettings:
class AppSettings:
+ """Store and validate app settings."""
SettingsPool: dict[str, AppSettings] = dict()
def __init__(
@@ -47,6 +51,7 @@ class AppSettings:
validRange: list = None,
default=None,
):
+ """Initialize the AppSettings."""
self.name = name
self.isBinary = isBinary
@@ -65,6 +70,7 @@ class AppSettings:
raise ValueError(f'\'{name}\' already exists in AppSettings')
def validate(self, value) -> bool:
+ """Validate the app settings."""
if self.validRange is None:
return True
else:
@@ -72,6 +78,7 @@ class AppSettings:
@staticmethod
def get(key: str):
+ """Return data managed by the app settings."""
settings = AppSettings.SettingsPool.get(key)
if settings is None:
@@ -110,6 +117,7 @@ class AppSettings:
@staticmethod
def isStateON_(key: str) -> bool:
+ """Return whether state on."""
value = AppSettings.get(key)
if value == AppBinarySettings.ON_:
@@ -119,6 +127,7 @@ class AppSettings:
@staticmethod
def isStateOFF(key: str) -> bool:
+ """Return whether state off."""
value = AppSettings.get(key)
if value == AppBinarySettings.OFF:
@@ -128,6 +137,7 @@ class AppSettings:
@staticmethod
def set(key: str, value):
+ """Set data managed by the app settings."""
settings = AppSettings.SettingsPool.get(key)
if settings is None:
@@ -143,12 +153,15 @@ class AppSettings:
@staticmethod
def turnON_(key: str):
+ """Handle turn on for the app settings."""
AppSettings.set(key, AppBinarySettings.ON_)
@staticmethod
def turnOFF(key: str):
+ """Handle turn off for the app settings."""
AppSettings.set(key, AppBinarySettings.OFF)
def registerAppSettings(name: str, *args, **kwargs):
+ """Handle register app settings for the application."""
AppSettings.SettingsPool[name] = AppSettings(name, *args, **kwargs)
diff --git a/Furious/Frozenlib/Constants.py b/Furious/Frozenlib/Constants.py
index 8ac9c1f9..daa84590 100644
--- a/Furious/Frozenlib/Constants.py
+++ b/Furious/Frozenlib/Constants.py
@@ -15,6 +15,8 @@
# You should have received a copy of the GNU General Public License
# along with this program. If not, see .
+"""Define application, platform, network, and filesystem constants."""
+
from __future__ import annotations
from Furious.Version import __version__
diff --git a/Furious/Frozenlib/Enum.py b/Furious/Frozenlib/Enum.py
index b59dbd05..f61f1900 100644
--- a/Furious/Frozenlib/Enum.py
+++ b/Furious/Frozenlib/Enum.py
@@ -15,6 +15,8 @@
# You should have received a copy of the GNU General Public License
# along with this program. If not, see .
+"""Provide bundled enum."""
+
from __future__ import annotations
from enum import Enum
@@ -27,17 +29,20 @@ __all__ = [
class AppBuiltinCommand(Enum):
+ """Enumerate app builtin command."""
Empty = 'empty'
RunAs = 'runas'
Clear = 'clear'
class AppBuiltinRouting(Enum):
+ """Enumerate app builtin routing."""
BypassMainlandChina = 'Bypass Mainland China'
Global = 'Global'
Custom = 'Custom'
class AppBuiltinProxyMode(Enum):
+ """Enumerate app builtin proxy mode."""
Auto = 'Auto'
NoChanges = 'NoChanges'
diff --git a/Furious/Frozenlib/Globals.py b/Furious/Frozenlib/Globals.py
index f6127232..354ce030 100644
--- a/Furious/Frozenlib/Globals.py
+++ b/Furious/Frozenlib/Globals.py
@@ -15,6 +15,8 @@
# You should have received a copy of the GNU General Public License
# along with this program. If not, see .
+"""Provide bundled globals."""
+
from __future__ import annotations
from PySide6.QtWidgets import QApplication
@@ -32,6 +34,7 @@ APP = functools.partial(QApplication.instance)
def getAppAttributes(name: str):
+ """Return app attributes."""
return getattr(APP(), name)
@@ -45,6 +48,7 @@ def getAppAttributes(name: str):
class AppLoggerWindow:
+ """Present the app logger window."""
Self = functools.partial(getAppAttributes, 'logViewerWindowSelf')
Core = functools.partial(getAppAttributes, 'logViewerWindowCore')
TUN_ = functools.partial(getAppAttributes, 'logViewerWindowTun_')
diff --git a/Furious/Frozenlib/Mixins.py b/Furious/Frozenlib/Mixins.py
index 49a9ed5d..6fcbf97c 100644
--- a/Furious/Frozenlib/Mixins.py
+++ b/Furious/Frozenlib/Mixins.py
@@ -15,6 +15,8 @@
# You should have received a copy of the GNU General Public License
# along with this program. If not, see .
+"""Provide bundled mixins."""
+
from __future__ import annotations
from PySide6 import QtCore
@@ -31,8 +33,10 @@ __all__ = ['Mixins']
class Mixins:
+ """Group reusable lifecycle, translation, theme, and Qt context mixins."""
@staticmethod
def qObjectIsValid(qobject) -> bool:
+ """Return the q object is valid value used by the mixins."""
if not isinstance(qobject, QtCore.QObject):
return True
@@ -42,21 +46,26 @@ class Mixins:
return False
class ConnectionAware:
+ """Represent connection aware."""
ObjectsPool = list()
def __init__(self, *args, **kwargs):
+ """Initialize the ConnectionAware."""
super().__init__(*args, **kwargs)
Mixins.ConnectionAware.ObjectsPool.append(self)
def disconnectedCallback(self):
+ """Update the connection aware for a disconnected state."""
raise NotImplementedError
def connectedCallback(self):
+ """Update the connection aware for a connected state."""
raise NotImplementedError
@staticmethod
def callConnectedCallback():
+ """Call connected callback."""
for ob in Mixins.ConnectionAware.ObjectsPool:
assert isinstance(ob, Mixins.ConnectionAware)
@@ -64,24 +73,29 @@ class Mixins:
@staticmethod
def callDisconnectedCallback():
+ """Call disconnected callback."""
for ob in Mixins.ConnectionAware.ObjectsPool:
assert isinstance(ob, Mixins.ConnectionAware)
ob.disconnectedCallback()
class ThemeAware:
+ """Represent theme aware."""
ObjectsPool = list()
def __init__(self, *args, **kwargs):
+ """Initialize the ThemeAware."""
super().__init__(*args, **kwargs)
Mixins.ThemeAware.ObjectsPool.append(self)
def themeChangedCallback(self, theme: str):
+ """Update the theme aware for a theme change."""
raise NotImplementedError
@staticmethod
def callThemeChangedCallbackUnchecked(theme: str):
+ """Call theme changed callback unchecked."""
for ob in Mixins.ThemeAware.ObjectsPool:
assert isinstance(ob, Mixins.ThemeAware)
@@ -89,6 +103,7 @@ class Mixins:
@staticmethod
def callThemeChangedCallback(theme: str):
+ """Call theme changed callback."""
try:
app = QApplication.instance()
@@ -107,10 +122,12 @@ class Mixins:
Mixins.ThemeAware.callThemeChangedCallbackUnchecked(theme)
class CleanupOnExit:
+ """Represent cleanup on exit."""
ObjectsPool = list()
VisitedType = dict()
def __init__(self, *args, **kwargs):
+ """Initialize the CleanupOnExit."""
self.uniqueCleanup = kwargs.pop('uniqueCleanup', True)
super().__init__(*args, **kwargs)
@@ -118,10 +135,12 @@ class Mixins:
Mixins.CleanupOnExit.ObjectsPool.append(self)
def cleanup(self):
+ """Release resources owned by the cleanup on exit."""
raise NotImplementedError
@staticmethod
def cleanupAll():
+ """Handle cleanup all for the cleanup on exit."""
for ob in Mixins.CleanupOnExit.ObjectsPool:
assert isinstance(ob, Mixins.CleanupOnExit)
@@ -141,41 +160,51 @@ class Mixins:
Mixins.CleanupOnExit.VisitedType.clear()
class QSetDisabledContext:
+ """Manage the q set disabled context."""
def __init__(self, qobject: QtCore.QObject):
+ """Initialize the QSetDisabledContext."""
self.qobject = qobject
def __enter__(self):
+ """Enter the q set disabled context context."""
if Mixins.qObjectIsValid(self.qobject) and hasattr(
self.qobject, 'setDisabled'
):
self.qobject.setDisabled(True)
def __exit__(self, exceptionType, exceptionValue, tb):
+ """Exit the q set disabled context context and restore state."""
if Mixins.qObjectIsValid(self.qobject) and hasattr(
self.qobject, 'setDisabled'
):
self.qobject.setDisabled(False)
class QBlockSignalContext:
+ """Manage the q block signal context."""
def __init__(self, qobject: QtCore.QObject):
+ """Initialize the QBlockSignalContext."""
self.qobject = qobject
def __enter__(self):
+ """Enter the q block signal context context."""
if Mixins.qObjectIsValid(self.qobject) and hasattr(
self.qobject, 'blockSignals'
):
self.qobject.blockSignals(True)
def __exit__(self, exceptionType, exceptionValue, tb):
+ """Exit the q block signal context context and restore state."""
if Mixins.qObjectIsValid(self.qobject) and hasattr(
self.qobject, 'blockSignals'
):
self.qobject.blockSignals(False)
class QTranslatable:
+ """Represent q translatable."""
ObjectsPool = list()
def __init__(self, *args, **kwargs):
+ """Initialize the QTranslatable."""
self.translatable = kwargs.pop('translatable', True)
self.useQSetDisabled = kwargs.pop('useQSetDisabled', True)
@@ -192,10 +221,12 @@ class Mixins:
)
def retranslate(self):
+ """Refresh translated text for the q translatable."""
raise NotImplementedError
@staticmethod
def unregister(ob):
+ """Handle unregister for the q translatable."""
try:
Mixins.QTranslatable.ObjectsPool.remove(ob)
except ValueError:
@@ -203,12 +234,14 @@ class Mixins:
@staticmethod
def pruneObjectsPool():
+ """Handle prune objects pool for the q translatable."""
Mixins.QTranslatable.ObjectsPool = list(
filter(Mixins.qObjectIsValid, Mixins.QTranslatable.ObjectsPool)
)
@staticmethod
def retranslateAll():
+ """Handle retranslate all for the q translatable."""
Mixins.QTranslatable.pruneObjectsPool()
for ob in list(Mixins.QTranslatable.ObjectsPool):
diff --git a/Furious/Frozenlib/PySide6Legacy.py b/Furious/Frozenlib/PySide6Legacy.py
index 2190253b..dc4989d5 100644
--- a/Furious/Frozenlib/PySide6Legacy.py
+++ b/Furious/Frozenlib/PySide6Legacy.py
@@ -15,6 +15,8 @@
# You should have received a copy of the GNU General Public License
# along with this program. If not, see .
+"""Provide bundled py side6 legacy."""
+
from __future__ import annotations
from Furious.Frozenlib.Constants import *
@@ -27,9 +29,11 @@ __all__ = ['PySide6Legacy']
class PySide6Legacy:
+ """Represent py side6 legacy."""
@staticmethod
def enumValueWrapper(enum) -> int:
# Protect PySide6 enum wrapper behavior changes
+ """Return the enum value wrapper value used by the py side6 legacy."""
if versionToValue(PYSIDE6_VERSION) < versionToValue('6.2.2'):
return enum
else:
@@ -39,6 +43,7 @@ class PySide6Legacy:
def eventLoopWait(ms) -> None:
# Protect qWait method does not exist in some
# old PySide6 version
+ """Handle event loop wait for the py side6 legacy."""
if versionToValue(PYSIDE6_VERSION) < versionToValue('6.3.1'):
for counter in range(0, ms, 10):
time.sleep(10 / 1000)
diff --git a/Furious/Frozenlib/StartupOnBoot.py b/Furious/Frozenlib/StartupOnBoot.py
index b9f49fda..5a6c692d 100644
--- a/Furious/Frozenlib/StartupOnBoot.py
+++ b/Furious/Frozenlib/StartupOnBoot.py
@@ -15,6 +15,8 @@
# You should have received a copy of the GNU General Public License
# along with this program. If not, see .
+"""Provide bundled startup on boot."""
+
from __future__ import annotations
from Furious.Frozenlib.Constants import *
@@ -33,9 +35,12 @@ logger = logging.getLogger(__name__)
class StartupOnBoot:
+ """Represent startup on boot."""
@staticmethod
def on_():
+ """Enable the startup on boot."""
def _on_():
+ """Return the on value used by the startup on boot."""
if SystemRuntime.isScriptMode():
# Script mode
logger.info('ignore turn on StartupOnBoot in script mode')
@@ -143,7 +148,9 @@ class StartupOnBoot:
@staticmethod
def off():
+ """Disable the startup on boot."""
def _off():
+ """Return the off value used by the startup on boot."""
if PLATFORM == 'Windows':
settings = QtCore.QSettings(
'HKEY_CURRENT_USER\\SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\Run',
diff --git a/Furious/Frozenlib/SystemProxy.py b/Furious/Frozenlib/SystemProxy.py
index 2046719a..ec20b64c 100644
--- a/Furious/Frozenlib/SystemProxy.py
+++ b/Furious/Frozenlib/SystemProxy.py
@@ -15,6 +15,8 @@
# You should have received a copy of the GNU General Public License
# along with this program. If not, see .
+"""Provide bundled system proxy."""
+
from __future__ import annotations
from Furious.Frozenlib.Constants import *
@@ -32,6 +34,7 @@ logger = logging.getLogger(__name__)
def handleAppSystemProxyMode() -> bool:
+ """Handle app system proxy mode."""
try:
if AppSettings.get('SystemProxyMode') == AppBuiltinProxyMode.Auto.value:
# Automatically configure
@@ -47,6 +50,7 @@ def handleAppSystemProxyMode() -> bool:
def linuxProxyConfig(proxy_args, arg0, arg1):
+ """Handle linux proxy config for the application."""
command = 'gsettings'
if SystemRuntime.flatpakID():
@@ -67,7 +71,9 @@ def linuxProxyConfig(proxy_args, arg0, arg1):
def darwinProxyConfig(operation, *args):
+ """Return the darwin proxy config value used by the application."""
def getNetworkServices():
+ """Return network services."""
command = runExternalCommand(
['networksetup', '-listallnetworkservices'],
stdout=subprocess.PIPE,
@@ -92,12 +98,16 @@ def darwinProxyConfig(operation, *args):
class _SystemProxy:
+ """Represent system proxy."""
def __init__(self):
+ """Initialize the _SystemProxy."""
self._daemonThread = None
@staticmethod
def pac(pac_url):
+ """Configure the system proxy with a PAC URL."""
def _pac():
+ """Return the pac value used by the system proxy."""
if PLATFORM == 'Windows':
try:
import sysproxy
@@ -141,7 +151,9 @@ class _SystemProxy:
@staticmethod
def set(server, bypass):
+ """Set data managed by the system proxy."""
def _set():
+ """Return the set value used by the system proxy."""
if PLATFORM == 'Windows':
try:
import sysproxy
@@ -198,7 +210,9 @@ class _SystemProxy:
@staticmethod
def off():
+ """Disable the system proxy."""
def _off():
+ """Return the off value used by the system proxy."""
if PLATFORM == 'Windows':
try:
import sysproxy
@@ -242,7 +256,9 @@ class _SystemProxy:
logger.error('turn off proxy failed')
def daemonOn_(self):
+ """Return the daemon on value used by the system proxy."""
def _daemonOn_():
+ """Return the daemon on value used by the system proxy."""
if PLATFORM == 'Windows':
try:
import sysproxy
@@ -273,7 +289,9 @@ class _SystemProxy:
logger.error('turn on proxy daemon failed')
def daemonOff(self):
+ """Return the daemon off value used by the system proxy."""
def _daemonOff():
+ """Return the daemon off value used by the system proxy."""
if PLATFORM == 'Windows':
try:
import sysproxy
diff --git a/Furious/Frozenlib/SystemRoutingTable.py b/Furious/Frozenlib/SystemRoutingTable.py
index 3437896f..1598a617 100644
--- a/Furious/Frozenlib/SystemRoutingTable.py
+++ b/Furious/Frozenlib/SystemRoutingTable.py
@@ -15,6 +15,8 @@
# You should have received a copy of the GNU General Public License
# along with this program. If not, see .
+"""Provide bundled system routing table."""
+
from __future__ import annotations
from Furious.Frozenlib.Constants import *
@@ -39,6 +41,7 @@ else:
def dictRepr(returncode, stdout, stderr):
+ """Return the dict repr value used by the application."""
return {
'returncode': returncode,
'stdout': stdout.decode(SYSTEM_PREFERRED_ENCODING, 'replace').strip(),
@@ -47,6 +50,7 @@ def dictRepr(returncode, stdout, stderr):
class SystemRoutingTable:
+ """Represent system routing table."""
Relations = list()
DEFAULT_GATEWAY_WIN32 = re.compile(
@@ -61,7 +65,9 @@ class SystemRoutingTable:
@staticmethod
def add(sourceIP, destinationIP):
+ """Add the system routing table."""
def _add():
+ """Return the add value used by the system routing table."""
if PLATFORM == 'Windows':
try:
result = runExternalCommand(
@@ -112,11 +118,13 @@ class SystemRoutingTable:
@staticmethod
def addRelations():
+ """Add relations."""
for sourceIP, destinationIP in SystemRoutingTable.Relations:
SystemRoutingTable.add(sourceIP, destinationIP)
@staticmethod
def WIN32GetInterfaceAliasByIP(ipaddress) -> str:
+ """Return the win32 get interface alias by ip value."""
assert PLATFORM == 'Windows'
try:
@@ -151,6 +159,7 @@ class SystemRoutingTable:
@staticmethod
def WIN32SetInterfaceDNS(name, address=None, dhcp=True):
+ """Return the win32 set interface DNS value."""
assert PLATFORM == 'Windows'
try:
@@ -189,6 +198,7 @@ class SystemRoutingTable:
@staticmethod
def WIN32FlushDNSCache():
+ """Return the win32 flush DNS cache value."""
assert PLATFORM == 'Windows'
try:
@@ -215,6 +225,7 @@ class SystemRoutingTable:
@staticmethod
def WIN32IpconfigFindContent(content: str) -> bool:
+ """Return the win32 ipconfig find content value."""
assert PLATFORM == 'Windows'
try:
@@ -244,7 +255,9 @@ class SystemRoutingTable:
@staticmethod
def DarwinGetDNSServers() -> list:
+ """Return the darwin get DNS servers value."""
def getNetworkServices():
+ """Return network services."""
_command = runExternalCommand(
['networksetup', '-listallnetworkservices'],
stdout=subprocess.PIPE,
@@ -295,6 +308,7 @@ class SystemRoutingTable:
@staticmethod
def DarwinSetDNSServers(service: str, dnsserver: str):
+ """Return the darwin set DNS servers value."""
assert PLATFORM == 'Darwin'
dnsserverRepr = [dnsserver]
@@ -335,6 +349,7 @@ class SystemRoutingTable:
@staticmethod
def LinuxFindTUNDevice(deviceName: str) -> bool:
+ """Return the linux find TUN device value."""
assert PLATFORM == 'Linux'
command = 'ip tuntap show'
@@ -369,6 +384,7 @@ class SystemRoutingTable:
@staticmethod
def LinuxDeleteTUNDevice(deviceName: str) -> bool:
+ """Return the linux delete TUN device value."""
assert PLATFORM == 'Linux'
command = 'ip tuntap del mode tun dev'
@@ -406,6 +422,7 @@ class SystemRoutingTable:
@staticmethod
def LinuxExecutePrivilegedScript(filepath, shell='bash') -> bool:
+ """Return the linux execute privileged script value."""
assert PLATFORM == 'Linux'
command = 'pkexec'
@@ -443,6 +460,7 @@ class SystemRoutingTable:
@staticmethod
def LinuxGetIpRoute() -> str:
+ """Return the linux get ip route value."""
assert PLATFORM == 'Linux'
command = 'ip route show'
@@ -477,7 +495,9 @@ class SystemRoutingTable:
@staticmethod
def getDefaultGateway() -> list:
+ """Return default gateway."""
def _get():
+ """Return the get value used by the system routing table."""
if PLATFORM == 'Windows':
# Note: On Windows interface IP is also captured
@@ -543,7 +563,9 @@ class SystemRoutingTable:
@staticmethod
def setDeviceGateway(deviceName, deviceIP, deviceGateway):
+ """Set device gateway."""
def _set():
+ """Return the set value used by the system routing table."""
if PLATFORM == 'Windows':
try:
result = runExternalCommand(
@@ -602,7 +624,9 @@ class SystemRoutingTable:
@staticmethod
def delete(sourceIP, destinationIP):
+ """Delete the system routing table."""
def _delete():
+ """Return the delete value used by the system routing table."""
if PLATFORM == 'Windows':
try:
result = runExternalCommand(
@@ -657,6 +681,7 @@ class SystemRoutingTable:
@staticmethod
def deleteRelations(clear=True):
+ """Delete relations."""
if PLATFORM == 'Windows':
if len(SystemRoutingTable.Relations):
SystemRoutingTable.delete('0.0.0.0', APPLICATION_TUN_GATEWAY_ADDRESS)
diff --git a/Furious/Frozenlib/SystemRuntime.py b/Furious/Frozenlib/SystemRuntime.py
index 5d508cd2..5c50124b 100644
--- a/Furious/Frozenlib/SystemRuntime.py
+++ b/Furious/Frozenlib/SystemRuntime.py
@@ -15,6 +15,8 @@
# You should have received a copy of the GNU General Public License
# along with this program. If not, see .
+"""Provide bundled system runtime."""
+
from __future__ import annotations
from Furious.Frozenlib.Constants import *
@@ -31,9 +33,11 @@ __all__ = ['SystemRuntime']
class SystemRuntime:
+ """Represent system runtime."""
@staticmethod
@functools.lru_cache(None)
def ubuntuRelease() -> str:
+ """Return the ubuntu release value used by the system runtime."""
try:
if PLATFORM != 'Linux':
return ''
@@ -64,6 +68,7 @@ class SystemRuntime:
@staticmethod
def appImagePath() -> str:
+ """Return the app image path value used by the system runtime."""
if PLATFORM != 'Linux':
return ''
@@ -74,6 +79,7 @@ class SystemRuntime:
@staticmethod
def flatpakID() -> str:
+ """Return the flatpak id value used by the system runtime."""
if PLATFORM != 'Linux':
return ''
@@ -84,11 +90,13 @@ class SystemRuntime:
@staticmethod
def isAssetsFolderWritable() -> bool:
+ """Return whether assets folder writable."""
return os.access(XRAY_ASSET_DIR, os.W_OK)
@staticmethod
@functools.lru_cache(None)
def isAdmin() -> bool:
+ """Return whether admin."""
if PLATFORM == 'Windows':
return ctypes.windll.shell32.IsUserAnAdmin() == 1
else:
@@ -96,6 +104,7 @@ class SystemRuntime:
@staticmethod
def isTUNMode() -> bool:
+ """Return whether TUN mode."""
if PLATFORM == 'Linux':
if SystemRuntime.flatpakID():
# TUN Mode disabled in flatpak
@@ -108,12 +117,15 @@ class SystemRuntime:
@staticmethod
@functools.lru_cache(None)
def isScriptMode() -> bool:
+ """Return whether script mode."""
return sys.argv[0].endswith('.py')
@staticmethod
@functools.lru_cache(None)
def isPythonw() -> bool:
+ """Return whether pythonw."""
def isRealFile(file):
+ """Return whether real file."""
if not hasattr(file, 'fileno'):
return False
@@ -134,4 +146,5 @@ class SystemRuntime:
@staticmethod
@functools.lru_cache(None)
def isWindows7() -> bool:
+ """Return whether windows7."""
return PLATFORM == 'Windows' and PLATFORM_RELEASE == '7'
diff --git a/Furious/Frozenlib/Tcping.py b/Furious/Frozenlib/Tcping.py
index b9306c82..362c5862 100644
--- a/Furious/Frozenlib/Tcping.py
+++ b/Furious/Frozenlib/Tcping.py
@@ -15,6 +15,8 @@
# You should have received a copy of the GNU General Public License
# along with this program. If not, see .
+"""Provide bundled tcping."""
+
from __future__ import annotations
from typing import Tuple
@@ -34,6 +36,7 @@ def tcping(
count: int,
interval: float,
) -> Tuple[int, list]:
+ """Return the tcping value used by the application."""
host = socket.gethostbyname(address)
addressobject = ipaddress.ip_address(host)
diff --git a/Furious/Frozenlib/Utility.py b/Furious/Frozenlib/Utility.py
index cdac1425..215fb5e4 100644
--- a/Furious/Frozenlib/Utility.py
+++ b/Furious/Frozenlib/Utility.py
@@ -15,6 +15,8 @@
# You should have received a copy of the GNU General Public License
# along with this program. If not, see .
+"""Provide bundled utility."""
+
from __future__ import annotations
from Furious.Frozenlib.Constants import *
@@ -47,6 +49,7 @@ __all__ = [
class Protocol(Enum):
+ """Enumerate proxy protocols recognized by Furious."""
Unknown = 'Unknown'
VMess = 'VMess'
VLESS = 'VLESS'
@@ -59,6 +62,7 @@ class Protocol(Enum):
@staticmethod
@functools.lru_cache(None)
def toEnum(protocol: str):
+ """Return the to enum value used by the protocol."""
if not isinstance(protocol, str):
return Protocol.Unknown
@@ -88,9 +92,11 @@ def callRateLimited(maxCallPerSecond):
called = time.monotonic()
def decorator(func):
+ """Decorate a callable with the enclosing behavior."""
@functools.wraps(func)
def wrapper(*args, **kwargs):
# Previously called
+ """Invoke the wrapped callable with the enclosing behavior."""
nonlocal called
elapsed = time.monotonic() - called
@@ -110,9 +116,12 @@ def callRateLimited(maxCallPerSecond):
def forceToLocalhostIfPossible():
+ """Return the force to localhost if possible value used by the application."""
def decorator(func):
+ """Decorate a callable with the enclosing behavior."""
@functools.wraps(func)
def wrapper(*args, **kwargs):
+ """Invoke the wrapped callable with the enclosing behavior."""
result = func(*args, **kwargs)
if (
@@ -146,6 +155,7 @@ def callOnceOnly(func):
called = False
def wrapper(*args, **kwargs):
+ """Invoke the wrapped callable with the enclosing behavior."""
nonlocal result, called
if not called:
@@ -158,11 +168,13 @@ def callOnceOnly(func):
def classname(ob) -> str:
+ """Return the classname value used by the application."""
return ob.__class__.__name__
@functools.lru_cache(None)
def isValidIPAddress(address) -> bool:
+ """Return whether valid ip address."""
try:
ipaddress.ip_address(address)
except Exception:
@@ -176,6 +188,7 @@ def isValidIPAddress(address) -> bool:
# Can throw exceptions
@functools.lru_cache(None)
def parseHostPort(address: str) -> Tuple[AnyStr | None, str | None]:
+ """Parse host port."""
if address.find('//') == -1:
result = urllib.parse.urlsplit('//' + address)
else:
@@ -195,6 +208,7 @@ def parseHostPort(address: str) -> Tuple[AnyStr | None, str | None]:
def runExternalCommand(*args, **kwargs):
+ """Run external command."""
if PLATFORM == 'Windows':
creationflags = kwargs.pop('creationflags', subprocess.CREATE_NO_WINDOW)
@@ -205,13 +219,16 @@ def runExternalCommand(*args, **kwargs):
@functools.lru_cache(None)
def absolutePath(path) -> pathlib.Path:
+ """Return the absolute path value used by the application."""
return pathlib.Path(path) if os.path.isabs(path) else ROOT_DIR / path
@functools.lru_cache(None)
def versionToValue(version: str) -> int:
+ """Return the version to value value used by the application."""
def split():
# x or x.y or x.y.z or x.y.z.u
+ """Split the application."""
result = version.split('.')
while len(result) < 4:
diff --git a/Furious/Frozenlib/Win32Session.py b/Furious/Frozenlib/Win32Session.py
index 83c30a38..3dfe6ae8 100644
--- a/Furious/Frozenlib/Win32Session.py
+++ b/Furious/Frozenlib/Win32Session.py
@@ -15,6 +15,8 @@
# You should have received a copy of the GNU General Public License
# along with this program. If not, see .
+"""Provide bundled win32 session."""
+
from __future__ import annotations
from Furious.Frozenlib.Constants import *
@@ -30,11 +32,14 @@ logger = logging.getLogger(__name__)
class _Win32Session:
+ """Represent win32 session."""
def __init__(self):
+ """Initialize the _Win32Session."""
self._daemonThread = None
@staticmethod
def set(callback: Callable[[], None]) -> bool:
+ """Set data managed by the win32 session."""
if PLATFORM == 'Windows':
import win32session
@@ -45,6 +50,7 @@ class _Win32Session:
return False
def off(self) -> bool:
+ """Disable the win32 session."""
if PLATFORM == 'Windows':
import win32session
@@ -62,6 +68,7 @@ class _Win32Session:
return False
def run(self) -> bool:
+ """Run the win32 session task."""
if PLATFORM == 'Windows':
import win32session
diff --git a/Furious/Frozenlib/__init__.py b/Furious/Frozenlib/__init__.py
index 52c66a9b..3e8a1d52 100644
--- a/Furious/Frozenlib/__init__.py
+++ b/Furious/Frozenlib/__init__.py
@@ -15,6 +15,8 @@
# You should have received a copy of the GNU General Public License
# along with this program. If not, see .
+"""Expose bundled platform, settings, and compatibility helpers."""
+
from __future__ import annotations
from .AppSettings import *
diff --git a/Furious/Interface/ApplicationFactory.py b/Furious/Interface/ApplicationFactory.py
index fdb15613..0bf69f51 100644
--- a/Furious/Interface/ApplicationFactory.py
+++ b/Furious/Interface/ApplicationFactory.py
@@ -15,6 +15,8 @@
# You should have received a copy of the GNU General Public License
# along with this program. If not, see .
+"""Define the contract for the top-level application runner."""
+
from __future__ import annotations
from enum import Enum
@@ -23,14 +25,18 @@ __all__ = ['ApplicationFactory']
class ApplicationFactory:
+ """Define the lifecycle contract for the top-level application runner."""
class ExitCode(Enum):
+ """Enumerate process exit codes."""
ExitSuccess = 0
UnknownException = 61
PlatformNotSupported = 62
AssertionError = 63
def __init__(self, *args, **kwargs):
+ """Initialize the ApplicationFactory."""
super().__init__(*args, **kwargs)
def run(self):
+ """Run the application factory task."""
raise NotImplementedError
diff --git a/Furious/Interface/ConfigFactory.py b/Furious/Interface/ConfigFactory.py
index fa4de776..968c78e6 100644
--- a/Furious/Interface/ConfigFactory.py
+++ b/Furious/Interface/ConfigFactory.py
@@ -15,6 +15,8 @@
# You should have received a copy of the GNU General Public License
# along with this program. If not, see .
+"""Define the shared configuration model and conversion interface."""
+
from __future__ import annotations
from Furious.Interface.UserServersTableItem import *
@@ -57,10 +59,12 @@ class ConfigFactory(UserServersTableItem, dict):
@functools.singledispatchmethod
def _init_dispatch(self, config):
+ """Initialize the configuration from a supported input type."""
super().__init__()
@_init_dispatch.register(str)
def _(self, config):
+ """Handle the registered singledispatch variant."""
try:
jsonObject = ujson.loads(config)
except Exception:
@@ -77,61 +81,75 @@ class ConfigFactory(UserServersTableItem, dict):
@_init_dispatch.register(dict)
def _(self, config):
+ """Handle the registered singledispatch variant."""
super().__init__(**config)
def __getitem__(self, item: str):
+ """Return an item from the config factory."""
if not isinstance(item, str):
raise TypeError(f'Bad type {type(item)} for __getitem__ call')
return super().__getitem__(item)
def __setitem__(self, item: str, value):
+ """Set an item on the config factory."""
if not isinstance(item, str):
raise TypeError(f'Bad type {type(item)} for __setitem__ call')
return super().__setitem__(item, value)
def deepcopy(self) -> ConfigFactory:
+ """Return an independent copy of the configuration."""
return copy.deepcopy(self)
def coreName(self) -> str:
+ """Return the core implementation name."""
return 'Unknown'
def isValid(self) -> bool:
+ """Return whether valid."""
return bool(self)
def getExtras(self, item):
+ """Return non-core metadata associated with the configuration."""
return self.kwargs.get(item, '')
def setExtras(self, item, value):
+ """Store non-core metadata associated with the configuration."""
self.kwargs[item] = value
@property
def index(self) -> int:
+ """Return the index value."""
return self._index
@index.setter
def index(self, value: int):
+ """Set the index value."""
assert isinstance(value, int)
self._index = value
@property
def deleted(self) -> bool:
+ """Return the deleted value."""
return self._deleted
@deleted.setter
def deleted(self, value: bool):
+ """Set the deleted value."""
assert isinstance(value, bool)
self._deleted = value
@property
def itemRemark(self) -> str:
+ """Return the item remark value."""
return self.getExtras('remark')
@property
def itemSubscription(self) -> str:
+ """Return the item subscription value."""
try:
app = QApplication.instance()
@@ -149,10 +167,12 @@ class ConfigFactory(UserServersTableItem, dict):
@property
def itemLatency(self) -> str:
+ """Return the item latency value."""
return self.getExtras('delayResult')
@property
def itemSpeed(self) -> str:
+ """Return the item speed value."""
return self.getExtras('speedResult')
def toJSONString(self, **kwargs) -> str:
@@ -182,6 +202,7 @@ class ConfigFactory(UserServersTableItem, dict):
return ''
def toStorageObject(self) -> dict:
+ """Build the persisted representation of the configuration."""
if self.kwargs.get('remark') is None:
# compatibility: remark field is mandatory in previous application version
self.kwargs['remark'] = ''
diff --git a/Furious/Interface/CoreProcessFactory.py b/Furious/Interface/CoreProcessFactory.py
index cdefec8d..83a56ac4 100644
--- a/Furious/Interface/CoreProcessFactory.py
+++ b/Furious/Interface/CoreProcessFactory.py
@@ -15,6 +15,8 @@
# You should have received a copy of the GNU General Public License
# along with this program. If not, see .
+"""Define the common proxy-core process interface."""
+
from __future__ import annotations
from Furious.Frozenlib import *
@@ -31,7 +33,9 @@ __all__ = ['CoreProcessFactory']
class CoreProcessFactory(ABC):
+ """Define the interface and shared behavior for core process objects."""
class ExitCode(Enum):
+ """Enumerate process exit codes."""
ConfigurationError = 23
# Windows: 4294967295. Darwin, Linux: 255 (-1)
ServerStartFailure = 4294967295 if PLATFORM == 'Windows' else 255
@@ -42,28 +46,34 @@ class CoreProcessFactory(ABC):
self,
exitCallback: Union[Callable[[CoreProcessFactory, int], None], None] = None,
):
+ """Initialize the CoreProcessFactory."""
super().__init__()
self._exitCallback = exitCallback
def callExitCallback(self, exitcode: int):
+ """Call exit callback."""
if callable(self._exitCallback):
self._exitCallback(self, exitcode)
@functools.singledispatchmethod
def toJSONString(self, config, **kwargs) -> str:
+ """Serialize the configuration as JSON text."""
return ''
@toJSONString.register(str)
def _(self, config, **kwargs) -> str:
+ """Handle the registered singledispatch variant."""
return config
@toJSONString.register(ConfigFactory)
def _(self, config, **kwargs) -> str:
+ """Handle the registered singledispatch variant."""
return config.toJSONString(**kwargs)
@toJSONString.register(dict)
def _(self, config, **kwargs) -> str:
+ """Handle the registered singledispatch variant."""
try:
ensure_ascii = kwargs.pop('ensure_ascii', False)
escape_forward_slashes = kwargs.pop('escape_forward_slashes', False)
@@ -82,17 +92,21 @@ class CoreProcessFactory(ABC):
@staticmethod
@abstractmethod
def name() -> str:
+ """Return the process implementation name."""
raise NotImplementedError
@staticmethod
@abstractmethod
def version() -> str:
+ """Return the bundled core version."""
raise NotImplementedError
@abstractmethod
def start(self, *args, **kwargs) -> bool:
+ """Start the core process factory."""
raise NotImplementedError
@abstractmethod
def stop(self):
+ """Stop the core process factory."""
raise NotImplementedError
diff --git a/Furious/Interface/EncoderFactory.py b/Furious/Interface/EncoderFactory.py
index 00ef4868..1064b950 100644
--- a/Furious/Interface/EncoderFactory.py
+++ b/Furious/Interface/EncoderFactory.py
@@ -15,6 +15,8 @@
# You should have received a copy of the GNU General Public License
# along with this program. If not, see .
+"""Define the common data encoder interface."""
+
from __future__ import annotations
from abc import ABC, abstractmethod
@@ -24,10 +26,13 @@ __all__ = ['EncoderFactory']
class EncoderFactory(ABC):
+ """Define the interface and shared behavior for encoder objects."""
@abstractmethod
def encode(self, data: Any, **kwargs) -> Any:
+ """Encode data with the encoder factory."""
raise NotImplementedError
@abstractmethod
def decode(self, data: Any, **kwargs) -> Any:
+ """Decode data with the encoder factory."""
raise NotImplementedError
diff --git a/Furious/Interface/GuiEditorItemFactory.py b/Furious/Interface/GuiEditorItemFactory.py
index e5a8c233..0fc852c0 100644
--- a/Furious/Interface/GuiEditorItemFactory.py
+++ b/Furious/Interface/GuiEditorItemFactory.py
@@ -15,6 +15,8 @@
# You should have received a copy of the GNU General Public License
# along with this program. If not, see .
+"""Define configuration-editor binding interfaces."""
+
from __future__ import annotations
from typing import Any
@@ -23,19 +25,26 @@ __all__ = ['GuiEditorItemFactory', 'GuiEditorItemWidgetContainer']
class GuiEditorItemFactory:
+ """Define the interface and shared behavior for GUI editor item objects."""
def __init__(self, *args, **kwargs):
+ """Initialize the GuiEditorItemFactory."""
super().__init__(*args, **kwargs)
def inputToFactory(self, *args, **kwargs) -> Any:
+ """Apply the current editor value to the configuration."""
raise NotImplementedError
def factoryToInput(self, *args, **kwargs) -> Any:
+ """Load the configuration value into the editor."""
raise NotImplementedError
class GuiEditorItemWidgetContainer(GuiEditorItemFactory):
+ """Bind one or more editor widgets to configuration data."""
def __init__(self, *args, **kwargs):
+ """Initialize the GuiEditorItemWidgetContainer."""
super().__init__(*args, **kwargs)
def widgets(self) -> Any:
+ """Return the widgets owned by this editor item."""
raise NotImplementedError
diff --git a/Furious/Interface/StorageFactory.py b/Furious/Interface/StorageFactory.py
index b821b709..d5252fa4 100644
--- a/Furious/Interface/StorageFactory.py
+++ b/Furious/Interface/StorageFactory.py
@@ -15,6 +15,8 @@
# You should have received a copy of the GNU General Public License
# along with this program. If not, see .
+"""Define the persistent storage collection interface."""
+
from __future__ import annotations
from abc import ABC, abstractmethod
@@ -24,10 +26,13 @@ __all__ = ['StorageFactory']
class StorageFactory(ABC):
+ """Define the interface and shared behavior for storage objects."""
@abstractmethod
def sync(self):
+ """Persist the current storage factory data."""
raise NotImplementedError
@abstractmethod
def data(self) -> Any:
+ """Return the data managed by the storage factory."""
raise NotImplementedError
diff --git a/Furious/Interface/UserServersTableItem.py b/Furious/Interface/UserServersTableItem.py
index e667ecdd..9ceb1656 100644
--- a/Furious/Interface/UserServersTableItem.py
+++ b/Furious/Interface/UserServersTableItem.py
@@ -15,47 +15,60 @@
# You should have received a copy of the GNU General Public License
# along with this program. If not, see .
+"""Define display fields for server-table rows."""
+
from __future__ import annotations
__all__ = ['UserServersTableItem']
class UserServersTableItem:
+ """Define the display fields required by a server-table row."""
def __init__(self, *args, **kwargs):
+ """Initialize the UserServersTableItem."""
super().__init__(*args, **kwargs)
@property
def itemRemark(self) -> str:
+ """Return the item remark value."""
return ''
@property
def itemProtocol(self) -> str:
+ """Return the item protocol value."""
return ''
@property
def itemAddress(self) -> str:
+ """Return the item address value."""
return ''
@property
def itemPort(self) -> str:
+ """Return the item port value."""
return ''
@property
def itemTransport(self) -> str:
+ """Return the item transport value."""
return ''
@property
def itemTLS(self) -> str:
+ """Return the item TLS value."""
return ''
@property
def itemSubscription(self) -> str:
+ """Return the item subscription value."""
return ''
@property
def itemLatency(self) -> str:
+ """Return the item latency value."""
return ''
@property
def itemSpeed(self) -> str:
+ """Return the item speed value."""
return ''
diff --git a/Furious/Interface/__init__.py b/Furious/Interface/__init__.py
index 7201edbb..e0374aa1 100644
--- a/Furious/Interface/__init__.py
+++ b/Furious/Interface/__init__.py
@@ -15,6 +15,8 @@
# You should have received a copy of the GNU General Public License
# along with this program. If not, see .
+"""Expose the public application interface contracts."""
+
from __future__ import annotations
from .ApplicationFactory import *
diff --git a/Furious/Library/Configuration.py b/Furious/Library/Configuration.py
index 3b574929..b17697b4 100644
--- a/Furious/Library/Configuration.py
+++ b/Furious/Library/Configuration.py
@@ -15,6 +15,8 @@
# You should have received a copy of the GNU General Public License
# along with this program. If not, see .
+"""Parse, build, import, and export supported proxy configurations."""
+
from __future__ import annotations
from Furious.Frozenlib import *
@@ -35,6 +37,7 @@ urlunparse = functools.partial(urllib.parse.urlunparse)
def queryStringFromItems(items):
+ """Encode non-empty query items as a URI query string."""
return urllib.parse.urlencode(
list((key, value) for key, value in items if value != '' and value is not None),
doseq=True,
@@ -57,6 +60,7 @@ __all__ = [
class ConfigXrayProxyOutboundObjectV(dict):
+ """Represent and transform Xray proxy outbound object v configuration data."""
def __init__(
self,
protocol,
@@ -68,6 +72,7 @@ class ConfigXrayProxyOutboundObjectV(dict):
security,
**kwargs,
):
+ """Initialize the ConfigXrayProxyOutboundObjectV."""
(
networkObjectArgs,
securityArgs,
@@ -146,7 +151,9 @@ class ConfigXrayProxyOutboundObjectV(dict):
class ConfigXrayProxyOutboundObjectSS(dict):
+ """Represent and transform Xray proxy outbound object ss configuration data."""
def __init__(self, method, password, address, port):
+ """Initialize the ConfigXrayProxyOutboundObjectSS."""
super().__init__(
**{
'tag': 'proxy',
@@ -175,7 +182,9 @@ class ConfigXrayProxyOutboundObjectSS(dict):
class ConfigXrayProxyOutboundObjectSocks(dict):
+ """Represent and transform Xray proxy outbound object SOCKS configuration data."""
def __init__(self, address, port, user='', password=''):
+ """Initialize the ConfigXrayProxyOutboundObjectSocks."""
settings = {
'address': address,
'port': int(port),
@@ -202,7 +211,9 @@ class ConfigXrayProxyOutboundObjectSocks(dict):
class ConfigXrayProxyOutboundObjectTrojan(dict):
+ """Represent and transform Xray proxy outbound object trojan configuration data."""
def __init__(self, password, address, port, type_, security, **kwargs):
+ """Initialize the ConfigXrayProxyOutboundObjectTrojan."""
networkObjectArgs, securityArgs, TLSObjectArgs = {}, {}, {}
if type_ == 'h2':
@@ -337,14 +348,18 @@ BLANK_CONFIG_XRAY = {
class ConfigXray(ConfigFactory):
+ """Represent Xray configuration and supported share-link formats."""
def __init__(self, config: Union[str, dict] = '', **kwargs):
+ """Initialize the ConfigXray."""
super().__init__(config, **kwargs)
def coreName(self) -> str:
+ """Return the core implementation name."""
return 'Xray-core'
@staticmethod
def getProxyOutboundObject(config: dict, default=None) -> dict:
+ """Return proxy outbound object."""
if not isinstance(config.get('outbounds'), list):
config['outbounds'] = []
@@ -363,6 +378,7 @@ class ConfigXray(ConfigFactory):
@staticmethod
def getProxyOutboundStream(config: dict, **kwargs) -> dict:
+ """Return proxy outbound stream."""
proxyOutbound = ConfigXray.getProxyOutboundObject(config, **kwargs)
if not isinstance(proxyOutbound.get('streamSettings'), dict):
@@ -372,6 +388,7 @@ class ConfigXray(ConfigFactory):
@staticmethod
def getProxyOutboundServer(config: dict, protocol: Protocol, **kwargs) -> dict:
+ """Return proxy outbound server."""
value, proxyOutbound = (
protocol.value.lower(),
ConfigXray.getProxyOutboundObject(config, **kwargs),
@@ -398,6 +415,7 @@ class ConfigXray(ConfigFactory):
@staticmethod
def getProxyOutboundUser(config: dict, protocol: Protocol, **kwargs) -> dict:
+ """Return proxy outbound user."""
proxyOutboundServer = ConfigXray.getProxyOutboundServer(
config, protocol, **kwargs
)
@@ -416,6 +434,7 @@ class ConfigXray(ConfigFactory):
@property
def proxyOutboundObject(self) -> dict:
+ """Return the proxy outbound object value."""
try:
return ConfigXray.getProxyOutboundObject(self)
except Exception:
@@ -425,6 +444,7 @@ class ConfigXray(ConfigFactory):
@property
def proxyProtocol(self) -> str:
+ """Return the proxy protocol value."""
try:
return self.proxyOutboundObject['protocol']
except Exception:
@@ -434,6 +454,7 @@ class ConfigXray(ConfigFactory):
@property
def proxyServerObject(self) -> dict:
+ """Return the proxy server object value."""
try:
proxyProtocol = self.proxyProtocol.lower()
@@ -454,6 +475,7 @@ class ConfigXray(ConfigFactory):
@property
def proxyUserObject(self) -> dict:
+ """Return the proxy user object value."""
try:
if self.proxyProtocol.lower() == 'socks':
return self.proxyServerObject
@@ -466,6 +488,7 @@ class ConfigXray(ConfigFactory):
@property
def proxyStreamSettingsObject(self) -> dict:
+ """Return the proxy stream settings object value."""
try:
return ConfigXray.getProxyOutboundStream(self)
except Exception:
@@ -475,6 +498,7 @@ class ConfigXray(ConfigFactory):
@property
def proxyStreamSettingsTLS(self) -> str:
+ """Return the proxy stream settings TLS value."""
try:
return self.proxyStreamSettingsObject.get('security', 'none')
except Exception:
@@ -484,6 +508,7 @@ class ConfigXray(ConfigFactory):
@property
def proxyStreamSettingsTLSObject(self) -> dict:
+ """Return the proxy stream settings TLS object value."""
try:
# tlsSettings, realitySettings
TLSKey = f'{self.proxyStreamSettingsTLS}Settings'
@@ -496,6 +521,7 @@ class ConfigXray(ConfigFactory):
@property
def proxyStreamSettingsNetwork(self) -> str:
+ """Return the proxy stream settings network value."""
try:
return self.proxyStreamSettingsObject['network']
except Exception:
@@ -505,6 +531,7 @@ class ConfigXray(ConfigFactory):
@property
def proxyStreamSettingsNetworkObject(self) -> dict:
+ """Return the proxy stream settings network object value."""
try:
if self.proxyStreamSettingsNetwork == 'h2':
networkKey = 'httpSettings'
@@ -531,6 +558,7 @@ class ConfigXray(ConfigFactory):
@property
def kwargsFromVMessProxyStreamSettingsNetworkObject(self) -> dict:
+ """Return the kwargs from v mess proxy stream settings network object value."""
kwargs = {}
try:
@@ -550,6 +578,7 @@ class ConfigXray(ConfigFactory):
return kwargs
def hasKey(key):
+ """Return whether key."""
return networkObject.get(key) is not None
if network == 'tcp' or network == 'raw':
@@ -662,6 +691,7 @@ class ConfigXray(ConfigFactory):
@property
def kwargsFromVLESSProxyStreamSettingsNetworkObject(self) -> dict:
+ """Return the kwargs from VLESS proxy stream settings network object value."""
kwargs = {}
try:
@@ -681,6 +711,7 @@ class ConfigXray(ConfigFactory):
return kwargs
def hasKey(key):
+ """Return whether key."""
return networkObject.get(key) is not None
if network == 'tcp' or network == 'raw':
@@ -804,6 +835,7 @@ class ConfigXray(ConfigFactory):
# v2rayN share standard doesn't require unquote. Still
# unquote value according to (VMess AEAD / VLESS) standard
+ """Build Xray transport settings from share-link keyword arguments."""
if type_ == 'tcp' or type_ == 'raw':
TcpObject = {}
@@ -982,6 +1014,7 @@ class ConfigXray(ConfigFactory):
@property
def kwargsFromProxyStreamSettingsTLSObject(self) -> dict:
+ """Return the kwargs from proxy stream settings TLS object value."""
kwargs = {}
TLS = self.proxyStreamSettingsTLS
@@ -1029,6 +1062,7 @@ class ConfigXray(ConfigFactory):
def kwargs2ProxyStreamSettingsTLSObject(
protocol, remote_host, security, kwargs
) -> dict:
+ """Build Xray TLS or REALITY settings from share-link keyword arguments."""
TLSObject = {}
if security == 'reality' or security == 'tls':
@@ -1094,6 +1128,7 @@ class ConfigXray(ConfigFactory):
@staticmethod
def kwargs2ProxyUserObject(protocol, uuid_, encryption, kwargs) -> dict:
+ """Build an Xray proxy user object from share-link values."""
if protocol == 'vmess':
UserObject = {
'id': uuid_,
@@ -1125,6 +1160,7 @@ class ConfigXray(ConfigFactory):
@staticmethod
def URI2ProxyOutboundObjectVMess(URI: str) -> Tuple[str, dict]:
+ """Parse a VMess URI into a remark and Xray proxy outbound."""
try:
myHead, myBody = URI.split('://')
except Exception:
@@ -1142,6 +1178,7 @@ class ConfigXray(ConfigFactory):
else:
def getOrDefault(key, default=''):
+ """Return or default."""
return myJSON.get(key, default)
remark = unquote(getOrDefault('ps'))
@@ -1176,6 +1213,7 @@ class ConfigXray(ConfigFactory):
@staticmethod
def URI2ProxyOutboundObjectVLESS(URI: str, **kwargs) -> Tuple[str, dict]:
+ """Parse a VLESS URI into a remark and Xray proxy outbound."""
result = urlparse(URI)
remark = unquote(result.fragment)
queryObject = {key: value for key, value in parse_qsl(result.query)}
@@ -1205,6 +1243,7 @@ class ConfigXray(ConfigFactory):
@staticmethod
def URI2ProxyOutboundObjectSS(URI: str) -> Tuple[str, dict]:
+ """Parse a Shadowsocks URI into a remark and Xray proxy outbound."""
try:
result = urlparse(URI)
remark = unquote(result.fragment)
@@ -1215,6 +1254,7 @@ class ConfigXray(ConfigFactory):
def getSSParams():
# Begin SIP002...
+ """Return ss params."""
try:
# Try pack with 3 element
userinfo, server = result.netloc.split('@')
@@ -1261,6 +1301,7 @@ class ConfigXray(ConfigFactory):
@staticmethod
def URI2ProxyOutboundObjectSocks(URI: str) -> Tuple[str, dict]:
+ """Parse a SOCKS URI into a remark and Xray proxy outbound."""
result = urlparse(URI)
remark = unquote(result.fragment)
@@ -1279,6 +1320,7 @@ class ConfigXray(ConfigFactory):
@staticmethod
def URI2ProxyOutboundObjectTrojan(URI: str) -> Tuple[str, dict]:
+ """Parse a Trojan URI into a remark and Xray proxy outbound."""
result = urlparse(URI)
remark = unquote(result.fragment)
queryObject = {key: value for key, value in parse_qsl(result.query)}
@@ -1300,6 +1342,7 @@ class ConfigXray(ConfigFactory):
@staticmethod
def URI2ProxyOutboundObject(URI: str) -> Tuple[str, dict]:
+ """Dispatch a supported share URI to its Xray outbound parser."""
if URI.startswith('vmess://'):
return ConfigXray.URI2ProxyOutboundObjectVMess(URI)
@@ -1319,44 +1362,53 @@ class ConfigXray(ConfigFactory):
@property
def itemProtocol(self) -> str:
+ """Return the item protocol value."""
return Protocol.toEnum(self.proxyProtocol).value
@property
def itemAddress(self) -> str:
+ """Return the item address value."""
addr = self.proxyServerObject.get('address', '')
return str(addr)
@property
def itemPort(self) -> str:
+ """Return the item port value."""
port = self.proxyServerObject.get('port', '')
return str(port)
@property
def itemTransport(self) -> str:
+ """Return the item transport value."""
return self.proxyStreamSettingsNetwork
@property
def itemTLS(self) -> str:
+ """Return the item TLS value."""
return self.proxyStreamSettingsTLS
@property
def itemLatency(self) -> str:
# Backward compatibility
+ """Return the item latency value."""
return self.getExtras('delayResult')
@property
def itemSpeed(self) -> str:
# Backward compatibility
+ """Return the item speed value."""
return self.getExtras('speedResult')
def toJSONString(self, **kwargs) -> str:
+ """Serialize the configuration as JSON text."""
indent = kwargs.pop('indent', 2)
return super().toJSONString(indent=indent)
def toURI(self, remark: str = '') -> str:
+ """Export the configuration as a share URI."""
if remark == '':
override = self.itemRemark
else:
@@ -1472,6 +1524,7 @@ class ConfigXray(ConfigFactory):
return ''
def fromURI(self, URI: str) -> bool:
+ """Populate the configuration from a share URI."""
try:
remark, proxyOutboundObject = ConfigXray.URI2ProxyOutboundObject(URI)
@@ -1508,6 +1561,7 @@ class ConfigXray(ConfigFactory):
return False
def httpProxy(self) -> str:
+ """Return the configured local HTTP proxy endpoint."""
try:
for inbound in self['inbounds']:
if inbound['protocol'] == 'http':
@@ -1522,6 +1576,7 @@ class ConfigXray(ConfigFactory):
return ''
def socksProxy(self) -> str:
+ """Return the configured local SOCKS proxy endpoint."""
try:
for inbound in self['inbounds']:
if inbound['protocol'] == 'socks':
@@ -1536,6 +1591,7 @@ class ConfigXray(ConfigFactory):
return ''
def setHttpProxy(self, endpoint: str) -> bool:
+ """Set the local HTTP proxy endpoint."""
try:
if self.get('inbounds') is None:
self['inbounds'] = []
@@ -1593,6 +1649,7 @@ class ConfigXray(ConfigFactory):
return False
def setSocksProxy(self, endpoint: str) -> bool:
+ """Set the local SOCKS proxy endpoint."""
try:
if self.get('inbounds') is None:
self['inbounds'] = []
@@ -1663,18 +1720,23 @@ BLANK_CONFIG_HYSTERIA1 = {
class ConfigHysteria1(ConfigFactory):
+ """Represent Hysteria 1 client configuration and share links."""
def __init__(self, config: Union[str, dict] = '', **kwargs):
+ """Initialize the ConfigHysteria1."""
super().__init__(config, **kwargs)
def coreName(self) -> str:
+ """Return the core implementation name."""
return 'Hysteria1'
@property
def itemProtocol(self) -> str:
+ """Return the item protocol value."""
return Protocol.Hysteria1.value
@property
def itemAddress(self) -> str:
+ """Return the item address value."""
server = self.get('server', '')
pos = server.rfind(':')
@@ -1686,6 +1748,7 @@ class ConfigHysteria1(ConfigFactory):
@property
def itemPort(self) -> str:
+ """Return the item port value."""
server = self.get('server', '')
pos = server.rfind(':')
@@ -1697,26 +1760,32 @@ class ConfigHysteria1(ConfigFactory):
@property
def itemTransport(self) -> str:
+ """Return the item transport value."""
return ''
@property
def itemTLS(self) -> str:
+ """Return the item TLS value."""
return ''
@property
def itemLatency(self) -> str:
+ """Return the item latency value."""
return self.getExtras('delayResult')
@property
def itemSpeed(self) -> str:
+ """Return the item speed value."""
return self.getExtras('speedResult')
def toJSONString(self, **kwargs) -> str:
+ """Serialize the configuration as JSON text."""
indent = kwargs.pop('indent', 4)
return super().toJSONString(indent=indent)
def toURI(self, remark: str = '') -> str:
+ """Export the configuration as a share URI."""
if remark == '':
override = self.itemRemark
else:
@@ -1782,6 +1851,7 @@ class ConfigHysteria1(ConfigFactory):
return urlunparse(['hysteria', netloc, '', '', query, quote(override)])
def fromURI(self, URI: str) -> bool:
+ """Populate the configuration from a share URI."""
try:
result = urlparse(URI)
remark = unquote(result.fragment)
@@ -1864,6 +1934,7 @@ class ConfigHysteria1(ConfigFactory):
return False
def httpProxy(self) -> str:
+ """Return the configured local HTTP proxy endpoint."""
try:
return self['http']['listen']
except Exception:
@@ -1872,6 +1943,7 @@ class ConfigHysteria1(ConfigFactory):
return ''
def socksProxy(self) -> str:
+ """Return the configured local SOCKS proxy endpoint."""
try:
return self['socks5']['listen']
except Exception:
@@ -1880,6 +1952,7 @@ class ConfigHysteria1(ConfigFactory):
return ''
def setHttpProxy(self, endpoint: str) -> bool:
+ """Set the local HTTP proxy endpoint."""
try:
if endpoint == '':
self.pop('http', None)
@@ -1898,6 +1971,7 @@ class ConfigHysteria1(ConfigFactory):
return False
def setSocksProxy(self, endpoint: str) -> bool:
+ """Set the local SOCKS proxy endpoint."""
try:
if endpoint == '':
self.pop('socks5', None)
@@ -1931,18 +2005,23 @@ BLANK_CONFIG_HYSTERIA2 = {
class ConfigHysteria2(ConfigFactory):
+ """Represent Hysteria 2 client configuration and share links."""
def __init__(self, config: Union[str, dict] = '', **kwargs):
+ """Initialize the ConfigHysteria2."""
super().__init__(config, **kwargs)
def coreName(self) -> str:
+ """Return the core implementation name."""
return 'Hysteria2'
@property
def itemProtocol(self) -> str:
+ """Return the item protocol value."""
return Protocol.Hysteria2.value
@staticmethod
def splitServerAddressPort(server: str) -> Tuple[str, str]:
+ """Split a standard or Realm-mode server value into display fields."""
if server.startswith(('realm://', 'realm+http://')):
result = urlparse(server)
@@ -1962,39 +2041,47 @@ class ConfigHysteria2(ConfigFactory):
@property
def itemAddress(self) -> str:
+ """Return the item address value."""
address, _port = self.splitServerAddressPort(self.get('server', ''))
return address
@property
def itemPort(self) -> str:
+ """Return the item port value."""
_address, port = self.splitServerAddressPort(self.get('server', ''))
return port
@property
def itemTransport(self) -> str:
+ """Return the item transport value."""
return ''
@property
def itemTLS(self) -> str:
+ """Return the item TLS value."""
return ''
@property
def itemLatency(self) -> str:
+ """Return the item latency value."""
return self.getExtras('delayResult')
@property
def itemSpeed(self) -> str:
+ """Return the item speed value."""
return self.getExtras('speedResult')
def toJSONString(self, **kwargs) -> str:
+ """Serialize the configuration as JSON text."""
indent = kwargs.pop('indent', 4)
return super().toJSONString(indent=indent)
@staticmethod
def splitHysteria2RealmQueryItems(queryItems):
+ """Separate Realm transport parameters from Hysteria 2 parameters."""
realmItems = []
hysteria2Items = []
@@ -2007,6 +2094,7 @@ class ConfigHysteria2(ConfigFactory):
return realmItems, hysteria2Items
def toURI(self, remark: str = '') -> str:
+ """Export the configuration as a share URI."""
if remark == '':
override = self.itemRemark
else:
@@ -2064,6 +2152,7 @@ class ConfigHysteria2(ConfigFactory):
return urlunparse(['hysteria2', netloc, '', '', query, quote(override)])
def fromURI(self, URI: str) -> bool:
+ """Populate the configuration from a share URI."""
try:
result = urlparse(URI)
remark = unquote(result.fragment)
@@ -2160,6 +2249,7 @@ class ConfigHysteria2(ConfigFactory):
return False
def httpProxy(self) -> str:
+ """Return the configured local HTTP proxy endpoint."""
try:
return self['http']['listen']
except Exception:
@@ -2168,6 +2258,7 @@ class ConfigHysteria2(ConfigFactory):
return ''
def socksProxy(self) -> str:
+ """Return the configured local SOCKS proxy endpoint."""
try:
return self['socks5']['listen']
except Exception:
@@ -2176,6 +2267,7 @@ class ConfigHysteria2(ConfigFactory):
return ''
def setHttpProxy(self, endpoint: str) -> bool:
+ """Set the local HTTP proxy endpoint."""
try:
if endpoint == '':
self.pop('http', None)
@@ -2194,6 +2286,7 @@ class ConfigHysteria2(ConfigFactory):
return False
def setSocksProxy(self, endpoint: str) -> bool:
+ """Set the local SOCKS proxy endpoint."""
try:
if endpoint == '':
self.pop('socks5', None)
@@ -2213,6 +2306,7 @@ class ConfigHysteria2(ConfigFactory):
def configXrayEmptyProxyOutboundObject(protocol: Protocol) -> dict:
+ """Return the config Xray empty proxy outbound object value used by the application."""
value = protocol.value.lower()
if value == 'vless' or value == 'vmess':
@@ -2307,10 +2401,12 @@ def configXrayEmptyProxyOutboundObject(protocol: Protocol) -> dict:
def configFactoryFromDict(config: dict, **kwargs) -> ConfigFactory:
+ """Return the config factory from dict value used by the application."""
if not isinstance(config, dict):
return ConfigFactory()
def hasField(field):
+ """Return whether field."""
return config.get(field) is not None
if hasField('inbounds') or hasField('outbounds'):
@@ -2354,6 +2450,7 @@ def configFactoryFromDict(config: dict, **kwargs) -> ConfigFactory:
def configFactoryFromAny(config: Union[str, dict], **kwargs) -> ConfigFactory:
+ """Return the config factory from any value used by the application."""
if isinstance(config, str):
if config.startswith(
(
@@ -2394,6 +2491,7 @@ def configFactoryFromAny(config: Union[str, dict], **kwargs) -> ConfigFactory:
def configFactoryBlank(protocol: Protocol) -> ConfigFactory:
+ """Return the config factory blank value used by the application."""
if protocol == Protocol.VMess or protocol == Protocol.VLESS:
factory = configFactoryFromDict(BLANK_CONFIG_XRAY)
factory['outbounds'][0]['protocol'] = protocol.value.lower()
diff --git a/Furious/Library/Encoder.py b/Furious/Library/Encoder.py
index af7ff6a2..f5322202 100644
--- a/Furious/Library/Encoder.py
+++ b/Furious/Library/Encoder.py
@@ -15,6 +15,8 @@
# You should have received a copy of the GNU General Public License
# along with this program. If not, see .
+"""Provide JSON and Base64 encoder implementations."""
+
from __future__ import annotations
from Furious.Interface import *
@@ -35,20 +37,25 @@ __all__ = [
class JSONEncoder(EncoderFactory):
+ """Encode and decode data using JSON."""
@staticmethod
def encode(data: Any, **kwargs) -> str:
+ """Encode data with the JSON encoder."""
ensure_ascii = kwargs.pop('ensure_ascii', False)
return json.dumps(data, ensure_ascii=ensure_ascii, **kwargs)
@staticmethod
def decode(data: AnyStr, **kwargs) -> Any:
+ """Decode data with the JSON encoder."""
return json.loads(data, **kwargs)
class UJSONEncoder(EncoderFactory):
+ """Encode and decode data using ujson."""
@staticmethod
def encode(data: Any, **kwargs) -> str:
+ """Encode data with the ujson encoder."""
ensure_ascii = kwargs.pop('ensure_ascii', False)
escape_forward_slashes = kwargs.pop('escape_forward_slashes', False)
@@ -61,28 +68,35 @@ class UJSONEncoder(EncoderFactory):
@staticmethod
def decode(data: AnyStr, **kwargs) -> Any:
+ """Decode data with the ujson encoder."""
return ujson.loads(data, **kwargs)
class Base64Encoder(EncoderFactory):
+ """Encode and decode data using base64."""
@staticmethod
def encode(data: Any, **kwargs) -> bytes:
+ """Encode data with the base64 encoder."""
return base64.b64encode(data, **kwargs)
@staticmethod
def decode(data: Any, **kwargs) -> bytes:
+ """Decode data with the base64 encoder."""
validate = kwargs.pop('validate', False)
return base64.b64decode(data, validate=validate, **kwargs)
class PyBase64Encoder(EncoderFactory):
+ """Encode and decode data using py base64."""
@staticmethod
def encode(data: Any, **kwargs) -> bytes:
+ """Encode data with the py base64 encoder."""
return pybase64.b64encode(data, **kwargs)
@staticmethod
def decode(data: Any, **kwargs) -> bytes:
+ """Decode data with the py base64 encoder."""
validate = kwargs.pop('validate', False)
return pybase64.b64decode(data, validate=validate, **kwargs)
diff --git a/Furious/Library/Storage.py b/Furious/Library/Storage.py
index 4a77eb56..e530c1d6 100644
--- a/Furious/Library/Storage.py
+++ b/Furious/Library/Storage.py
@@ -15,6 +15,8 @@
# You should have received a copy of the GNU General Public License
# along with this program. If not, see .
+"""Provide cached access to persisted user data collections."""
+
from __future__ import annotations
from Furious.Frozenlib import *
@@ -32,8 +34,10 @@ __all__ = ['Storage']
class Storage:
+ """Provide cached access to persisted user configuration collections."""
@staticmethod
def UserActivatedItemIndex() -> int:
+ """Return the user activated item index value."""
try:
return int(AppSettings.get('ActivatedItemIndex'))
except Exception:
@@ -44,6 +48,7 @@ class Storage:
@staticmethod
@functools.lru_cache(None)
def UserServers() -> list[ConfigFactory]:
+ """Return the user servers value."""
assert APP() is not None
return UserServers().data()
@@ -51,6 +56,7 @@ class Storage:
@staticmethod
@functools.lru_cache(None)
def UserSubs() -> dict[str, dict]:
+ """Return the user subs value."""
assert APP() is not None
return UserSubs().data()
@@ -58,6 +64,7 @@ class Storage:
@staticmethod
@functools.lru_cache(None)
def UserTUNSettings() -> dict[str, str]:
+ """Return the user TUN settings value."""
assert APP() is not None
return UserTUNSettings().data()
@@ -65,14 +72,17 @@ class Storage:
@staticmethod
@functools.lru_cache(None)
def UserRoutings() -> dict[str, dict]:
+ """Return the user routings value."""
assert APP() is not None
return UserRoutings().data()
class Extras:
+ """Derive display and proxy values from the active server."""
@staticmethod
@forceToLocalhostIfPossible()
def UserHttpProxy() -> Union[str, None]:
+ """Return the user HTTP proxy value."""
try:
if APP().isSystemTrayConnected():
index, servers = (
@@ -94,6 +104,7 @@ class Storage:
@staticmethod
def UserServerRemark() -> Union[str, None]:
+ """Return the user server remark value."""
try:
if APP().isSystemTrayConnected():
index, servers = (
diff --git a/Furious/Library/UserRoutings.py b/Furious/Library/UserRoutings.py
index 9304c4e7..6f6db603 100644
--- a/Furious/Library/UserRoutings.py
+++ b/Furious/Library/UserRoutings.py
@@ -15,6 +15,8 @@
# You should have received a copy of the GNU General Public License
# along with this program. If not, see .
+"""Persist custom routing definitions."""
+
from __future__ import annotations
from Furious.Frozenlib import *
@@ -27,10 +29,13 @@ registerAppSettings('CustomRouting')
class UserRoutings(Mixins.CleanupOnExit, StorageFactory):
+ """Manage the persisted custom-routing collection."""
def __init__(self, *args, **kwargs):
+ """Initialize the UserRoutings."""
super().__init__(*args, **kwargs)
def restore():
+ """Restore the user routings."""
try:
data = UJSONEncoder.decode(
PyBase64Encoder.decode(AppSettings.get('CustomRouting'))
@@ -48,6 +53,7 @@ class UserRoutings(Mixins.CleanupOnExit, StorageFactory):
self._data = restore()
def sync(self):
+ """Persist the current user routings data."""
AppSettings.set(
'CustomRouting',
PyBase64Encoder.encode(UJSONEncoder.encode(self._data).encode()),
@@ -55,7 +61,9 @@ class UserRoutings(Mixins.CleanupOnExit, StorageFactory):
def data(self) -> dict[str, dict]:
# Shallow copy
+ """Return the data managed by the user routings."""
return self._data
def cleanup(self):
+ """Release resources owned by the user routings."""
self.sync()
diff --git a/Furious/Library/UserServers.py b/Furious/Library/UserServers.py
index f00baac5..a936e793 100644
--- a/Furious/Library/UserServers.py
+++ b/Furious/Library/UserServers.py
@@ -15,6 +15,8 @@
# You should have received a copy of the GNU General Public License
# along with this program. If not, see .
+"""Persist and reconstruct user server configurations."""
+
from __future__ import annotations
from Furious.Frozenlib import *
@@ -28,6 +30,7 @@ registerAppSettings('Configuration')
class UserServer:
+ """Represent user server."""
remark: str
config: str
subsId: str
@@ -35,10 +38,13 @@ class UserServer:
class UserServers(Mixins.CleanupOnExit, StorageFactory):
# remark, config, subsId. (subsId corresponds to unique in user subscription)
+ """Manage the persisted list of server configurations."""
def __init__(self, *args, **kwargs):
+ """Initialize the UserServers."""
super().__init__(*args, **kwargs)
def restore():
+ """Restore the user servers."""
try:
return UJSONEncoder.decode(
PyBase64Encoder.decode(AppSettings.get('Configuration'))
@@ -55,6 +61,7 @@ class UserServers(Mixins.CleanupOnExit, StorageFactory):
)
def sync(self):
+ """Persist the current user servers data."""
AppSettings.set(
'Configuration',
PyBase64Encoder.encode(
@@ -66,7 +73,9 @@ class UserServers(Mixins.CleanupOnExit, StorageFactory):
def data(self) -> list[ConfigFactory]:
# Shallow copy
+ """Return the data managed by the user servers."""
return self._list
def cleanup(self):
+ """Release resources owned by the user servers."""
self.sync()
diff --git a/Furious/Library/UserSubs.py b/Furious/Library/UserSubs.py
index cd77257a..5763e4a2 100644
--- a/Furious/Library/UserSubs.py
+++ b/Furious/Library/UserSubs.py
@@ -15,6 +15,8 @@
# You should have received a copy of the GNU General Public License
# along with this program. If not, see .
+"""Persist subscription definitions."""
+
from __future__ import annotations
from Furious.Frozenlib import *
@@ -27,6 +29,7 @@ registerAppSettings('CustomSubscription')
class UserSubEntry:
+ """Describe one user sub entry."""
remark: str
webURL: str
autoupdate: str
@@ -34,15 +37,19 @@ class UserSubEntry:
class UserSub:
+ """Represent user sub."""
unique: dict[str, dict]
class UserSubs(Mixins.CleanupOnExit, StorageFactory):
# unique: { remark, webURL }
+ """Manage the persisted subscription collection."""
def __init__(self, *args, **kwargs):
+ """Initialize the UserSubs."""
super().__init__(*args, **kwargs)
def restore():
+ """Restore the user subs."""
try:
return UJSONEncoder.decode(
PyBase64Encoder.decode(AppSettings.get('CustomSubscription'))
@@ -55,6 +62,7 @@ class UserSubs(Mixins.CleanupOnExit, StorageFactory):
self._data = restore()
def sync(self):
+ """Persist the current user subs data."""
AppSettings.set(
'CustomSubscription',
PyBase64Encoder.encode(
@@ -64,7 +72,9 @@ class UserSubs(Mixins.CleanupOnExit, StorageFactory):
def data(self) -> dict[str, dict]:
# Shallow copy
+ """Return the data managed by the user subs."""
return self._data
def cleanup(self):
+ """Release resources owned by the user subs."""
self.sync()
diff --git a/Furious/Library/UserTUNSettings.py b/Furious/Library/UserTUNSettings.py
index c772a461..e2e19e4b 100644
--- a/Furious/Library/UserTUNSettings.py
+++ b/Furious/Library/UserTUNSettings.py
@@ -15,6 +15,8 @@
# You should have received a copy of the GNU General Public License
# along with this program. If not, see .
+"""Persist user-defined TUN settings."""
+
from __future__ import annotations
from Furious.Frozenlib import *
@@ -27,10 +29,13 @@ registerAppSettings('CustomTUNSettings')
class UserTUNSettings(Mixins.CleanupOnExit, StorageFactory):
+ """Manage persisted TUN customization values."""
def __init__(self, *args, **kwargs):
+ """Initialize the UserTUNSettings."""
super().__init__(*args, **kwargs)
def restore():
+ """Restore the user TUN settings."""
try:
return UJSONEncoder.decode(
PyBase64Encoder.decode(AppSettings.get('CustomTUNSettings'))
@@ -43,6 +48,7 @@ class UserTUNSettings(Mixins.CleanupOnExit, StorageFactory):
self._data = restore()
def sync(self):
+ """Persist the current user TUN settings data."""
AppSettings.set(
'CustomTUNSettings',
PyBase64Encoder.encode(
@@ -52,7 +58,9 @@ class UserTUNSettings(Mixins.CleanupOnExit, StorageFactory):
def data(self) -> dict[str, str]:
# Shallow copy
+ """Return the data managed by the user TUN settings."""
return self._data
def cleanup(self):
+ """Release resources owned by the user TUN settings."""
self.sync()
diff --git a/Furious/Library/__init__.py b/Furious/Library/__init__.py
index f631f13d..9aa90431 100644
--- a/Furious/Library/__init__.py
+++ b/Furious/Library/__init__.py
@@ -15,6 +15,8 @@
# You should have received a copy of the GNU General Public License
# along with this program. If not, see .
+"""Expose configuration, encoding, and storage services."""
+
from __future__ import annotations
from .Encoder import *
diff --git a/Furious/Qt/AppStyleSheet.py b/Furious/Qt/AppStyleSheet.py
index 6e6c858f..47f50f22 100644
--- a/Furious/Qt/AppStyleSheet.py
+++ b/Furious/Qt/AppStyleSheet.py
@@ -15,6 +15,8 @@
# You should have received a copy of the GNU General Public License
# along with this program. If not, see .
+"""Provide Qt support for app style sheet."""
+
from __future__ import annotations
from textwrap import dedent
@@ -23,6 +25,7 @@ __all__ = ['AppStyleSheet']
class AppStyleSheet:
+ """Represent app style sheet."""
Light = 'Light'
Dark = 'Dark'
FontPointSize = 10
@@ -82,6 +85,7 @@ class AppStyleSheet:
@staticmethod
def normalizeTheme(theme):
+ """Return the normalize theme value used by the app style sheet."""
if theme == AppStyleSheet.Dark:
return AppStyleSheet.Dark
@@ -89,6 +93,7 @@ class AppStyleSheet:
@staticmethod
def forTheme(theme):
+ """Return the for theme value used by the app style sheet."""
normalizedTheme = AppStyleSheet.normalizeTheme(theme)
palette = AppStyleSheet.Palettes[normalizedTheme]
iconPrefix = ':/Icons/bootstrap'
diff --git a/Furious/Qt/DNSResolver.py b/Furious/Qt/DNSResolver.py
index 2ec8d435..44d5b32f 100644
--- a/Furious/Qt/DNSResolver.py
+++ b/Furious/Qt/DNSResolver.py
@@ -15,6 +15,8 @@
# You should have received a copy of the GNU General Public License
# along with this program. If not, see .
+"""Provide Qt support for DNS resolver."""
+
from __future__ import annotations
from Furious.Frozenlib import *
@@ -34,13 +36,16 @@ logger = logging.getLogger(__name__)
class _DNSResolver(WebGETManager):
+ """Represent DNS resolver."""
def __init__(self, parent=None, **kwargs):
+ """Initialize the _DNSResolver."""
actionMessage = kwargs.pop('actionMessage', 'DNS resolution')
super().__init__(parent, actionMessage=actionMessage)
@staticmethod
def request(address) -> QNetworkRequest:
+ """Return the request value used by the DNS resolver."""
request = QNetworkRequest(
QtCore.QUrl(f'https://cloudflare-dns.com/dns-query?name={address}')
)
@@ -49,6 +54,7 @@ class _DNSResolver(WebGETManager):
return request
def successCallback(self, networkReply, **kwargs):
+ """Handle a successful network operation."""
domain = kwargs.pop('domain', '')
resultMap = kwargs.pop('resultMap', {})
@@ -87,6 +93,7 @@ class _DNSResolver(WebGETManager):
resultMap['depth'] -= 1
def failureCallback(self, networkReply: QNetworkReply, **kwargs):
+ """Handle a failed network operation."""
domain = kwargs.pop('domain', '')
resultMap = kwargs.pop('resultMap', {})
@@ -98,6 +105,7 @@ class _DNSResolver(WebGETManager):
resultMap['depth'] -= 1
def resolve(self, domain, timeout=30000) -> Tuple[bool, list[str]]:
+ """Resolve the DNS resolver."""
resultMap = {
'domain': domain,
'depth': 0,
@@ -123,6 +131,7 @@ class _DNSResolver(WebGETManager):
@staticmethod
def wait(resultMap, startCounter=0, timeout=30000, step=100):
+ """Wait for the DNS resolver operation to complete."""
domain = resultMap.get('domain', '')
if not domain:
diff --git a/Furious/Qt/DynamicTheme.py b/Furious/Qt/DynamicTheme.py
index b7823b12..6a16861a 100644
--- a/Furious/Qt/DynamicTheme.py
+++ b/Furious/Qt/DynamicTheme.py
@@ -15,6 +15,8 @@
# You should have received a copy of the GNU General Public License
# along with this program. If not, see .
+"""Provide Qt support for dynamic theme."""
+
from __future__ import annotations
from Furious.Frozenlib import *
@@ -24,21 +26,26 @@ __all__ = ['AppHue']
class AppHue:
+ """Represent app hue."""
class ColorRGB:
+ """Represent color rgb."""
LIGHT_BLUE = '#43ACED'
LIGHT_RED = '#FF7276'
LIGHT_PURPLE = '#DA70D6'
@staticmethod
def disconnectedColor() -> str:
+ """Disconnect ed color."""
return AppHue.ColorRGB.LIGHT_BLUE
@staticmethod
def disconnectedWindowIcon() -> AppQIcon:
+ """Disconnect ed window icon."""
return bootstrapIcon('rocket-takeoff-window.svg')
@staticmethod
def connectedColor() -> str:
+ """Connect ed color."""
if not SystemRuntime.isAdmin():
return AppHue.ColorRGB.LIGHT_RED
else:
@@ -46,6 +53,7 @@ class AppHue:
@staticmethod
def connectedWindowIcon() -> AppQIcon:
+ """Connect ed window icon."""
if not SystemRuntime.isAdmin():
return bootstrapIcon('rocket-takeoff-connected-dark.svg')
else:
@@ -53,6 +61,7 @@ class AppHue:
@staticmethod
def currentColor() -> str:
+ """Return the current color value."""
if APP().isSystemTrayConnected():
return AppHue.connectedColor()
else:
@@ -60,6 +69,7 @@ class AppHue:
@staticmethod
def currentWindowIcon() -> AppQIcon:
+ """Return the current window icon value."""
if APP().isSystemTrayConnected():
return AppHue.connectedWindowIcon()
else:
diff --git a/Furious/Qt/DynamicTranslate.py b/Furious/Qt/DynamicTranslate.py
index 21306653..636351d8 100644
--- a/Furious/Qt/DynamicTranslate.py
+++ b/Furious/Qt/DynamicTranslate.py
@@ -15,6 +15,8 @@
# You should have received a copy of the GNU General Public License
# along with this program. If not, see .
+"""Provide Qt support for dynamic translate."""
+
from __future__ import annotations
from Furious.Frozenlib import *
@@ -29,13 +31,16 @@ __all__ = [
class Translator:
+ """Represent translator."""
def __init__(self):
+ """Initialize the Translator."""
super().__init__()
self.translation = dict()
self.dictEnglish = dict()
def install(self, translation):
+ """Handle install for the translator."""
self.translation = translation
for key, value in translation.items():
@@ -51,6 +56,7 @@ class Translator:
self.dictEnglish.update(dict(list((key, key) for key in translation.keys())))
def translate(self, source, locale):
+ """Return the translate value used by the translator."""
try:
return self.translation[self.dictEnglish[source]][locale]
except Exception:
@@ -64,10 +70,12 @@ translator = Translator()
def installTranslation(translation):
+ """Handle install translation for the application."""
translator.install(translation)
def gettext(source, locale=None):
+ """Return text."""
if locale is None:
if APP() is None:
# Normally should not reach here if performing a translation
diff --git a/Furious/Qt/GuiEditorXXX.py b/Furious/Qt/GuiEditorXXX.py
index 24b6e884..ed3841a8 100644
--- a/Furious/Qt/GuiEditorXXX.py
+++ b/Furious/Qt/GuiEditorXXX.py
@@ -15,6 +15,8 @@
# You should have received a copy of the GNU General Public License
# along with this program. If not, see .
+"""Provide Qt support for GUI editor xxx."""
+
from __future__ import annotations
from Furious.Frozenlib import *
@@ -44,7 +46,9 @@ __all__ = [
class GuiEditorItemTextInput(GuiEditorItemWidgetContainer):
+ """Represent GUI editor item text input."""
def __init__(self, *args, **kwargs):
+ """Initialize the GuiEditorItemTextInput."""
title = kwargs.pop('title', '')
translatable = kwargs.pop('translatable', True)
parent = kwargs.pop('parent', None)
@@ -58,17 +62,22 @@ class GuiEditorItemTextInput(GuiEditorItemWidgetContainer):
self._input = QLineEdit(parent=parent)
def text(self) -> str:
+ """Return the text value."""
return self._input.text()
def setText(self, text: str):
+ """Set text."""
self._input.setText(text)
def widgets(self):
+ """Return the widgets owned by this editor item."""
return self._title, self._input
class GuiEditorItemTextSpinBox(GuiEditorItemWidgetContainer):
+ """Represent GUI editor item text spin box."""
def __init__(self, *args, **kwargs):
+ """Initialize the GuiEditorItemTextSpinBox."""
title = kwargs.pop('title', '')
translatable = kwargs.pop('translatable', True)
parent = kwargs.pop('parent', None)
@@ -82,20 +91,26 @@ class GuiEditorItemTextSpinBox(GuiEditorItemWidgetContainer):
self._input = AppQSpinBox(parent=parent)
def value(self) -> int:
+ """Return the value value."""
return self._input.value()
def setValue(self, value: int):
+ """Set value."""
self._input.setValue(value)
def setRange(self, minRange: int, maxRange: int):
+ """Set range."""
self._input.setRange(minRange, maxRange)
def widgets(self):
+ """Return the widgets owned by this editor item."""
return self._title, self._input
class GuiEditorItemTextComboBox(GuiEditorItemWidgetContainer):
+ """Represent GUI editor item text combo box."""
def __init__(self, *args, **kwargs):
+ """Initialize the GuiEditorItemTextComboBox."""
title = kwargs.pop('title', '')
translatable = kwargs.pop('translatable', True)
parent = kwargs.pop('parent', None)
@@ -109,23 +124,30 @@ class GuiEditorItemTextComboBox(GuiEditorItemWidgetContainer):
self._input = AppQComboBox(parent=parent, translatable=False)
def text(self) -> str:
+ """Return the text value."""
return self._input.currentText()
def setText(self, text: str):
+ """Set text."""
self._input.setCurrentText(text)
def addItems(self, texts: Sequence[str]):
+ """Add items."""
self._input.addItems(texts)
def connectActivated(self, func: Callable):
+ """Connect activated."""
self._input.activated.connect(func)
def widgets(self):
+ """Return the widgets owned by this editor item."""
return self._title, self._input
class GuiEditorItemTextCheckBox(GuiEditorItemWidgetContainer):
+ """Represent GUI editor item text check box."""
def __init__(self, *args, **kwargs):
+ """Initialize the GuiEditorItemTextCheckBox."""
title = kwargs.pop('title', '')
translatable = kwargs.pop('translatable', True)
parent = kwargs.pop('parent', None)
@@ -135,20 +157,26 @@ class GuiEditorItemTextCheckBox(GuiEditorItemWidgetContainer):
self._input = AppQCheckBox(_(title), translatable=translatable, parent=parent)
def isChecked(self) -> bool:
+ """Return whether checked."""
return self._input.isChecked()
def setChecked(self, checked: bool):
+ """Set checked."""
self._input.setChecked(checked)
def widgets(self):
+ """Return the widgets owned by this editor item."""
return (self._input,)
class GuiEditorItemBasicRemark(GuiEditorItemTextInput):
+ """Represent GUI editor item basic remark."""
def __init__(self, *args, **kwargs):
+ """Initialize the GuiEditorItemBasicRemark."""
super().__init__(*args, **kwargs)
def inputToFactory(self, config: ConfigFactory) -> bool:
+ """Apply the current editor value to the configuration."""
oldRemark = config.getExtras('remark')
newRemark = self.text()
@@ -162,14 +190,18 @@ class GuiEditorItemBasicRemark(GuiEditorItemTextInput):
return False
def factoryToInput(self, config: ConfigFactory):
+ """Load the configuration value into the editor."""
self.setText(config.getExtras('remark'))
class GuiEditorItemProxyHttp(GuiEditorItemTextInput):
+ """Represent GUI editor item proxy HTTP."""
def __init__(self, *args, **kwargs):
+ """Initialize the GuiEditorItemProxyHttp."""
super().__init__(*args, **kwargs)
def inputToFactory(self, config: ConfigFactory) -> bool:
+ """Apply the current editor value to the configuration."""
oldHttp = config.httpProxy()
newHttp = self.text()
@@ -194,6 +226,7 @@ class GuiEditorItemProxyHttp(GuiEditorItemTextInput):
return True
def factoryToInput(self, config: ConfigFactory):
+ """Load the configuration value into the editor."""
try:
self.setText(config.httpProxy())
except Exception:
@@ -203,10 +236,13 @@ class GuiEditorItemProxyHttp(GuiEditorItemTextInput):
class GuiEditorItemProxySocks(GuiEditorItemTextInput):
+ """Represent GUI editor item proxy SOCKS."""
def __init__(self, *args, **kwargs):
+ """Initialize the GuiEditorItemProxySocks."""
super().__init__(*args, **kwargs)
def inputToFactory(self, config: ConfigFactory) -> bool:
+ """Apply the current editor value to the configuration."""
oldSocks = config.socksProxy()
newSocks = self.text()
@@ -231,6 +267,7 @@ class GuiEditorItemProxySocks(GuiEditorItemTextInput):
return True
def factoryToInput(self, config: ConfigFactory):
+ """Load the configuration value into the editor."""
try:
self.setText(config.socksProxy())
except Exception:
@@ -240,15 +277,19 @@ class GuiEditorItemProxySocks(GuiEditorItemTextInput):
class GuiEditorWidget(GuiEditorItemFactory):
+ """Provide the GUI editor widget."""
def __init__(self, *args, **kwargs):
+ """Initialize the GuiEditorWidget."""
super().__init__(*args, **kwargs)
self._containers = self.containerSequence()
def containerSequence(self) -> Sequence[GuiEditorItemWidgetContainer]:
+ """Return the editor item containers in display order."""
raise NotImplementedError
def inputToFactory(self, config: dict) -> bool:
+ """Apply the current editor value to the configuration."""
modified = False
for container in self._containers:
@@ -262,6 +303,7 @@ class GuiEditorWidget(GuiEditorItemFactory):
return modified
def factoryToInput(self, config: dict):
+ """Load the configuration value into the editor."""
for container in self._containers:
try:
container.factoryToInput(config)
@@ -272,12 +314,15 @@ class GuiEditorWidget(GuiEditorItemFactory):
class GuiEditorWidgetQWidget(GuiEditorWidget, QWidget):
+ """Provide the GUI editor Qt widget."""
def __init__(self, *args, **kwargs):
+ """Initialize the GuiEditorWidgetQWidget."""
super().__init__(*args, **kwargs)
self.setupLayout()
def setupLayout(self):
+ """Set up layout."""
layout = QFormLayout()
layout.setFormAlignment(QtCore.Qt.AlignmentFlag.AlignLeft)
layout.setFieldGrowthPolicy(QFormLayout.FieldGrowthPolicy.ExpandingFieldsGrow)
@@ -289,7 +334,9 @@ class GuiEditorWidgetQWidget(GuiEditorWidget, QWidget):
class GuiEditorWidgetQGroupBox(GuiEditorWidget, AppQGroupBox):
+ """Group the GUI editor widget q editor controls."""
def __init__(self, *args, **kwargs):
+ """Initialize the GuiEditorWidgetQGroupBox."""
super().__init__(*args, **kwargs)
page = QWidget()
@@ -305,6 +352,7 @@ class GuiEditorWidgetQGroupBox(GuiEditorWidget, AppQGroupBox):
self.setLayout(vboxLayout)
def setupPageLayout(self):
+ """Set up page layout."""
layout = QFormLayout()
layout.setFormAlignment(QtCore.Qt.AlignmentFlag.AlignLeft)
layout.setFieldGrowthPolicy(QFormLayout.FieldGrowthPolicy.ExpandingFieldsGrow)
@@ -316,7 +364,9 @@ class GuiEditorWidgetQGroupBox(GuiEditorWidget, AppQGroupBox):
class GuiEditorWidgetQDialog(GuiEditorItemFactory, AppQDialog):
+ """Present the GUI editor widget Qt dialog."""
def __init__(self, *args, **kwargs):
+ """Initialize the GuiEditorWidgetQDialog."""
tabText, tabTranslatable, style = (
kwargs.pop('tabText', ''),
kwargs.pop('tabTranslatable', False),
@@ -357,9 +407,11 @@ class GuiEditorWidgetQDialog(GuiEditorItemFactory, AppQDialog):
@functools.lru_cache(None)
def groupBoxSequence(self) -> Sequence[GuiEditorWidgetQGroupBox]:
+ """Return the configuration group boxes in display order."""
raise NotImplementedError
def setGroupBoxStyle(self, style: str):
+ """Set group box style."""
if style == 'grid':
for index, groupBox in enumerate(self.groupBoxSequence()):
self.tabCentralWidgetLayout.addWidget(groupBox, index // 2, index % 2)
@@ -371,13 +423,16 @@ class GuiEditorWidgetQDialog(GuiEditorItemFactory, AppQDialog):
self.tabCentralWidgetLayout.addWidget(groupBox, index, 0)
def setTabText(self, text: str):
+ """Set tab text."""
self.tabWidget.setTabText(0, text)
def closeEvent(self, event):
+ """Handle closure of the GUI editor widget Qt dialog."""
self.accepted.disconnect()
self.rejected.disconnect()
def inputToFactory(self, config: dict) -> bool:
+ """Apply the current editor value to the configuration."""
modified = False
for groupBox in self.groupBoxSequence():
@@ -386,5 +441,6 @@ class GuiEditorWidgetQDialog(GuiEditorItemFactory, AppQDialog):
return modified
def factoryToInput(self, config: dict):
+ """Load the configuration value into the editor."""
for groupBox in self.groupBoxSequence():
groupBox.factoryToInput(config)
diff --git a/Furious/Qt/NetworkConnectivityManager.py b/Furious/Qt/NetworkConnectivityManager.py
index 1c10594f..33f76ac1 100644
--- a/Furious/Qt/NetworkConnectivityManager.py
+++ b/Furious/Qt/NetworkConnectivityManager.py
@@ -15,6 +15,8 @@
# You should have received a copy of the GNU General Public License
# along with this program. If not, see .
+"""Provide Qt support for network connectivity manager."""
+
from __future__ import annotations
from Furious.Frozenlib import *
@@ -32,10 +34,12 @@ logger = logging.getLogger(__name__)
class NetworkConnectivityManager(Mixins.ConnectionAware, WebGETManager):
+ """Coordinate network connectivity operations."""
MIN_JOB_INTERVAL = 2500
MAX_JOB_INTERVAL = 2000000000
def __init__(self, parent=None, **kwargs):
+ """Initialize the NetworkConnectivityManager."""
actionMessage = kwargs.pop('actionMessage', 'test network connectivity')
super().__init__(parent, actionMessage=actionMessage)
@@ -49,6 +53,7 @@ class NetworkConnectivityManager(Mixins.ConnectionAware, WebGETManager):
self.jobArrangeTimer.timeout.connect(lambda: self.startSingleTest())
def recalculateJobInterval(self, jobStatus: bool) -> int:
+ """Return the recalculate job interval value used by the network connectivity manager."""
assert isinstance(jobStatus, bool)
if self.jobStatus is jobStatus:
@@ -65,15 +70,18 @@ class NetworkConnectivityManager(Mixins.ConnectionAware, WebGETManager):
return self.jobInterval
def successCallback(self, networkReply, **kwargs):
+ """Handle a successful network operation."""
self.jobTimeoutTimer.stop()
self.jobArrangeTimer.start(self.recalculateJobInterval(jobStatus=True))
def failureCallback(self, networkReply, **kwargs):
+ """Handle a failed network operation."""
self.jobTimeoutTimer.stop()
self.jobArrangeTimer.start(self.recalculateJobInterval(jobStatus=False))
def startSingleTest(self):
# Use custom network connectivity test URL if possible
+ """Start single test."""
settings = AppSettings.get('CustomNetworkConnectivityTestURL')
if isinstance(settings, str):
@@ -84,6 +92,7 @@ class NetworkConnectivityManager(Mixins.ConnectionAware, WebGETManager):
networkReply = self.webGET(url)
def abort(_networkReply):
+ """Cancel the active network connectivity manager operation."""
if isinstance(_networkReply, QNetworkReply):
_networkReply.abort()
@@ -91,10 +100,12 @@ class NetworkConnectivityManager(Mixins.ConnectionAware, WebGETManager):
self.jobTimeoutTimer.start(NetworkConnectivityManager.MIN_JOB_INTERVAL - 500)
def stopTest(self):
+ """Stop test."""
self.jobArrangeTimer.stop()
self.jobTimeoutTimer.stop()
def connectedCallback(self):
+ """Update the network connectivity manager for a connected state."""
if AppSettings.isStateON_('PowerSaveMode'):
# Power optimization
logger.info('no job for network connectivity manager in power save mode')
@@ -106,4 +117,5 @@ class NetworkConnectivityManager(Mixins.ConnectionAware, WebGETManager):
self.jobArrangeTimer.start(self.jobInterval)
def disconnectedCallback(self):
+ """Update the network connectivity manager for a disconnected state."""
self.stopTest()
diff --git a/Furious/Qt/QtGui.py b/Furious/Qt/QtGui.py
index 2e439232..ce7765e1 100644
--- a/Furious/Qt/QtGui.py
+++ b/Furious/Qt/QtGui.py
@@ -15,6 +15,8 @@
# You should have received a copy of the GNU General Public License
# along with this program. If not, see .
+"""Provide Qt support for qt GUI."""
+
from __future__ import annotations
from Furious.Frozenlib import *
@@ -41,13 +43,16 @@ __all__ = [
class AppQIcon(QIcon):
+ """Represent app q icon."""
def __init__(self, iconFileName: str):
+ """Initialize the AppQIcon."""
super().__init__(iconFileName)
self.iconFileName = iconFileName
def iconFn(prefix, name):
+ """Return the icon fn value used by the application."""
if name.startswith('rocket-takeoff'):
# Colorful. Use default
return AppQIcon(f':/Icons/bootstrap/{name}')
@@ -60,6 +65,7 @@ bootstrapIconWhite = functools.partial(iconFn, 'bootstrap/white')
def setIconAsMask(icon):
+ """Set icon as mask."""
if hasattr(icon, 'setIsMask'):
icon.setIsMask(True)
@@ -68,11 +74,13 @@ def setIconAsMask(icon):
@functools.lru_cache(None)
def bootstrapIconMask(name):
+ """Return the bootstrap icon mask value used by the application."""
return setIconAsMask(bootstrapIconWhite(name))
@functools.lru_cache(None)
def bootstrapIconWithOpacity(name, opacity, isMask=False):
+ """Return the bootstrap icon with opacity value used by the application."""
sourceIcon = bootstrapIconWhite(name)
icon = AppQIcon('')
@@ -103,6 +111,7 @@ def bootstrapIconWithOpacity(name, opacity, isMask=False):
class AppQAction(Mixins.QTranslatable, Mixins.ThemeAware, QAction):
+ """Handle the app q action."""
def __init__(
self,
text,
@@ -118,6 +127,7 @@ class AppQAction(Mixins.QTranslatable, Mixins.ThemeAware, QAction):
isTrayAction=False,
**kwargs,
):
+ """Initialize the AppQAction."""
super().__init__(text=text, **kwargs)
# Do not use QProtection because it's been managed somewhere else!!!
@@ -164,6 +174,7 @@ class AppQAction(Mixins.QTranslatable, Mixins.ThemeAware, QAction):
@QtCore.Slot(bool)
def triggerSignal(paramChecked):
+ """Handle trigger signal for the app q action."""
logger.info(f'action is \'{self.textEnglish}\'. Checked is {paramChecked}')
if callable(self.callback):
@@ -174,6 +185,7 @@ class AppQAction(Mixins.QTranslatable, Mixins.ThemeAware, QAction):
self.triggered.connect(triggerSignal)
def addAction(self, action):
+ """Add action."""
if self._menu is not None:
self._menu.addAction(action)
@@ -181,6 +193,7 @@ class AppQAction(Mixins.QTranslatable, Mixins.ThemeAware, QAction):
self._actionGroup.addAction(action)
def removeAction(self, action):
+ """Remove action."""
if self._menu is not None:
self._menu.removeAction(action)
@@ -189,17 +202,21 @@ class AppQAction(Mixins.QTranslatable, Mixins.ThemeAware, QAction):
@property
def textEnglish(self):
+ """Return the text english value."""
return _(self.text(), 'EN')
def __str__(self):
+ """Return the display text for the app q action."""
return self.__class__.__name__
def textCompare(self, compare):
+ """Return the text compare value used by the app q action."""
return self.textEnglish == compare
@staticmethod
@functools.lru_cache(None)
def getIconFileName(fileName):
+ """Return icon file name."""
try:
return fileName.split('/')[-1]
except Exception:
@@ -208,6 +225,7 @@ class AppQAction(Mixins.QTranslatable, Mixins.ThemeAware, QAction):
return ''
def setIconByTheme(self, theme):
+ """Set icon by theme."""
if not self.iconFileName:
return
@@ -238,6 +256,7 @@ class AppQAction(Mixins.QTranslatable, Mixins.ThemeAware, QAction):
super().setIcon(bootstrapIcon(self.iconFileName))
def setIcon(self, icon: AppQIcon):
+ """Set icon."""
self.iconFileName = self.getIconFileName(icon.iconFileName)
if not self.iconFileName:
@@ -247,10 +266,13 @@ class AppQAction(Mixins.QTranslatable, Mixins.ThemeAware, QAction):
self.setIconByTheme(APP().theme())
def themeChangedCallback(self, theme):
+ """Update the app q action for a theme change."""
self.setIconByTheme(theme)
def retranslate(self):
+ """Refresh translated text for the app q action."""
def recursiveTranslate(action, memo):
+ """Handle recursive translate for the app q action."""
if action not in memo and not action.isSeparator() and action.translatable:
action.setText(_(action.text()))
action.setStatusTip(_(action.statusTip()))
@@ -268,11 +290,14 @@ class AppQAction(Mixins.QTranslatable, Mixins.ThemeAware, QAction):
def triggeredCallback(self, checked):
# Not a mandatory re-implementation in child class
+ """Handle activation of the action."""
pass
class AppQActionGroup(QActionGroup):
+ """Represent app q action group."""
def __init__(self, parent, *actions):
+ """Initialize the AppQActionGroup."""
super().__init__(parent)
for action in actions:
@@ -280,5 +305,7 @@ class AppQActionGroup(QActionGroup):
class AppQSeperator(QAction):
+ """Represent app q seperator."""
def __init__(self):
+ """Initialize the AppQSeperator."""
super().__init__()
diff --git a/Furious/Qt/QtNetwork.py b/Furious/Qt/QtNetwork.py
index aa48e9ff..b585738e 100644
--- a/Furious/Qt/QtNetwork.py
+++ b/Furious/Qt/QtNetwork.py
@@ -15,6 +15,8 @@
# You should have received a copy of the GNU General Public License
# along with this program. If not, see .
+"""Provide Qt support for qt network."""
+
from __future__ import annotations
from Furious.Frozenlib import *
@@ -27,10 +29,13 @@ __all__ = ['AppQNetworkAccessManager']
class AppQNetworkAccessManager(QNetworkAccessManager):
+ """Coordinate app q network access operations."""
def __init__(self, parent=None):
+ """Initialize the AppQNetworkAccessManager."""
super().__init__(parent)
def configureHttpProxy(self, httpProxy: Union[str, None]) -> bool:
+ """Configure HTTP proxy."""
if httpProxy is None:
useProxy = False
else:
diff --git a/Furious/Qt/QtWidgets.py b/Furious/Qt/QtWidgets.py
index 563504c0..23803329 100644
--- a/Furious/Qt/QtWidgets.py
+++ b/Furious/Qt/QtWidgets.py
@@ -15,6 +15,8 @@
# You should have received a copy of the GNU General Public License
# along with this program. If not, see .
+"""Provide Qt support for qt widgets."""
+
from __future__ import annotations
from Furious.Frozenlib import *
@@ -64,6 +66,7 @@ __all__ = [
def moveToCenter(widget, parent=None):
+ """Move to center."""
geometry = widget.frameGeometry()
if parent is not None:
@@ -81,24 +84,32 @@ def moveToCenter(widget, parent=None):
class AppQCheckBox(Mixins.QTranslatable, QCheckBox):
+ """Represent app q check box."""
def __init__(self, *args, **kwargs):
+ """Initialize the AppQCheckBox."""
super().__init__(*args, **kwargs)
def retranslate(self):
+ """Refresh translated text for the app q check box."""
self.setText(_(self.text()))
class AppQComboBox(Mixins.QTranslatable, QComboBox):
+ """Represent app q combo box."""
def __init__(self, *args, **kwargs):
+ """Initialize the AppQComboBox."""
super().__init__(*args, **kwargs)
def retranslate(self):
+ """Refresh translated text for the app q combo box."""
for index in range(self.count()):
self.setItemText(index, _(self.itemText(index)))
class AppQDialog(Mixins.QTranslatable, Mixins.ConnectionAware, QDialog):
+ """Present the app Qt dialog."""
def __init__(self, *args, **kwargs):
+ """Initialize the AppQDialog."""
super().__init__(*args, **kwargs)
self._firstShowCall = True
@@ -109,19 +120,23 @@ class AppQDialog(Mixins.QTranslatable, Mixins.ConnectionAware, QDialog):
self.setWindowIcon(AppHue.currentWindowIcon())
def setWidthAndHeight(self):
+ """Apply the default size for the app Qt dialog."""
pass
def exec(self):
+ """Show and execute the app Qt dialog modally."""
self.show()
return super().exec()
def open(self):
+ """Open the app Qt dialog asynchronously."""
self.show()
return super().open()
def show(self):
+ """Show and position the app Qt dialog."""
super().show()
if PLATFORM == 'Darwin':
@@ -133,40 +148,52 @@ class AppQDialog(Mixins.QTranslatable, Mixins.ConnectionAware, QDialog):
moveToCenter(self)
def retranslate(self):
+ """Refresh translated text for the app Qt dialog."""
self.setWindowTitle(_(self.windowTitle()))
def disconnectedCallback(self):
+ """Update the app Qt dialog for a disconnected state."""
self.setWindowIcon(AppHue.disconnectedWindowIcon())
def connectedCallback(self):
+ """Update the app Qt dialog for a connected state."""
self.setWindowIcon(AppHue.connectedWindowIcon())
class AppQDialogButtonBox(Mixins.QTranslatable, QDialogButtonBox):
+ """Represent app Qt dialog button box."""
def __init__(self, *args, **kwargs):
+ """Initialize the AppQDialogButtonBox."""
super().__init__(*args, **kwargs)
def retranslate(self):
+ """Refresh translated text for the app Qt dialog button box."""
for button in self.buttons():
button.setText(_(button.text()))
class AppQGroupBox(Mixins.QTranslatable, QGroupBox):
+ """Group the app q editor controls."""
def __init__(self, *args, **kwargs):
+ """Initialize the AppQGroupBox."""
super().__init__(*args, **kwargs)
def retranslate(self):
+ """Refresh translated text for the app q group box."""
self.setTitle(_(self.title()))
class AppQHeaderView(Mixins.CleanupOnExit, Mixins.ConnectionAware, QHeaderView):
+ """Represent app q header view."""
def sectionSizeSettingsEmpty(self):
+ """Return the section size settings empty value used by the app q header view."""
return (
self.legacySectionSizeSettingsName == ''
and self.sectionSizeSettingsName == ''
)
def __init__(self, *args, **kwargs):
+ """Initialize the AppQHeaderView."""
self.legacySectionSizeSettingsName = kwargs.pop(
'legacySectionSizeSettingsName', ''
)
@@ -193,6 +220,7 @@ class AppQHeaderView(Mixins.CleanupOnExit, Mixins.ConnectionAware, QHeaderView):
# self.sectionResized.connect(self.handleSectionResized)
def restoreSectionSize(self):
+ """Restore section size."""
if self.sectionSizeSettingsEmpty():
return
@@ -237,6 +265,7 @@ class AppQHeaderView(Mixins.CleanupOnExit, Mixins.ConnectionAware, QHeaderView):
def setCustomSectionResizeMode(self):
# Horizontal header resize mode
+ """Set custom section resize mode."""
for index in range(self.columnCount):
if index < self.columnCount - 1:
self.setSectionResizeMode(index, AppQHeaderView.ResizeMode.Interactive)
@@ -245,16 +274,19 @@ class AppQHeaderView(Mixins.CleanupOnExit, Mixins.ConnectionAware, QHeaderView):
@staticmethod
def getStyleSheet(color):
+ """Return style sheet."""
return f'QHeaderView::section:hover {{ background-color: {color}; }}'
def disconnectedCallback(self):
# self.setStyleSheet(self.getStyleSheet(AppHue.disconnectedColor()))
+ """Update the app q header view for a disconnected state."""
pass
def connectedCallback(self):
# self.setStyleSheet(self.getStyleSheet(AppHue.connectedColor()))
+ """Update the app q header view for a connected state."""
pass
# Legacy method. Not used
@@ -267,6 +299,7 @@ class AppQHeaderView(Mixins.CleanupOnExit, Mixins.ConnectionAware, QHeaderView):
# self.sectionSizeTable[str(index)] = newSize
def cleanup(self):
+ """Release resources owned by the app q header view."""
if self.sectionSizeSettingsEmpty():
return
@@ -279,28 +312,37 @@ class AppQHeaderView(Mixins.CleanupOnExit, Mixins.ConnectionAware, QHeaderView):
class AppQLabel(Mixins.QTranslatable, QLabel):
+ """Represent app q label."""
def __init__(self, *args, **kwargs):
+ """Initialize the AppQLabel."""
super().__init__(*args, **kwargs)
def retranslate(self):
+ """Refresh translated text for the app q label."""
self.setText(_(self.text()))
class AppQLineEdit(Mixins.QTranslatable, QLineEdit):
+ """Represent app q line edit."""
def __init__(self, *args, **kwargs):
+ """Initialize the AppQLineEdit."""
super().__init__(*args, **kwargs)
def retranslate(self):
+ """Refresh translated text for the app q line edit."""
self.setPlaceholderText(_(self.placeholderText()))
class AppQListWidget(Mixins.ConnectionAware, QListWidget):
+ """Provide the app Qt list widget."""
def __init__(self, *args, **kwargs):
+ """Initialize the AppQListWidget."""
super().__init__(*args, **kwargs)
# self.setSelectionColor(AppHue.disconnectedColor())
def setSelectionColor(self, color):
+ """Set selection color."""
self.setStyleSheet(
f'QListWidget::item:selected {{'
f' background: {color};'
@@ -313,16 +355,19 @@ class AppQListWidget(Mixins.ConnectionAware, QListWidget):
@property
def selectedIndex(self):
+ """Return the selected index value."""
return sorted(list(set(index.row() for index in self.selectedIndexes())))
def disconnectedCallback(self):
# self.setSelectionColor(AppHue.disconnectedColor())
+ """Update the app Qt list widget for a disconnected state."""
pass
def connectedCallback(self):
# self.setSelectionColor(AppHue.connectedColor())
+ """Update the app Qt list widget for a connected state."""
pass
@@ -332,7 +377,9 @@ class AppQMainWindow(
Mixins.CleanupOnExit,
QMainWindow,
):
+ """Present the app q main window."""
def __init__(self, *args, **kwargs):
+ """Initialize the AppQMainWindow."""
super().__init__(*args, **kwargs)
self._firstShowCall = True
@@ -346,9 +393,11 @@ class AppQMainWindow(
self.setWidthAndHeight()
def setWidthAndHeight(self):
+ """Apply the default size for the app q main window."""
pass
def show(self):
+ """Show and position the app q main window."""
super().show()
if PLATFORM == 'Darwin':
@@ -366,20 +415,26 @@ class AppQMainWindow(
self.raise_()
def retranslate(self):
+ """Refresh translated text for the app q main window."""
self.setWindowTitle(_(self.windowTitle()))
def disconnectedCallback(self):
+ """Update the app q main window for a disconnected state."""
self.setWindowIcon(AppHue.disconnectedWindowIcon())
def connectedCallback(self):
+ """Update the app q main window for a connected state."""
self.setWindowIcon(AppHue.connectedWindowIcon())
def cleanup(self):
+ """Release resources owned by the app q main window."""
pass
class AppQMenu(Mixins.QTranslatable, QMenu):
+ """Represent app q menu."""
def __init__(self, *actions, **kwargs):
+ """Initialize the AppQMenu."""
super().__init__(**kwargs)
# In some old version PySide6, the self.actions() method
@@ -399,38 +454,47 @@ class AppQMenu(Mixins.QTranslatable, QMenu):
pass
def retranslate(self):
+ """Refresh translated text for the app q menu."""
self.setTitle(_(self.title()))
class AppQMenuBar(QMenuBar):
+ """Represent app q menu bar."""
def __init__(self, *args, **kwargs):
+ """Initialize the AppQMenuBar."""
super().__init__(*args, **kwargs)
class AppQMessageBox(Mixins.QTranslatable, Mixins.ConnectionAware, QMessageBox):
+ """Represent app q message box."""
def __init__(self, *args, **kwargs):
+ """Initialize the AppQMessageBox."""
super().__init__(*args, **kwargs)
self.setWindowIcon(AppHue.currentWindowIcon())
def moveToCenter(self):
+ """Move to center."""
moveToCenter(self, self.parentWidget())
return self
def exec(self):
+ """Show and execute the app q message box modally."""
self.show()
self.moveToCenter()
return super().exec()
def open(self):
+ """Open the app q message box asynchronously."""
self.show()
self.moveToCenter()
return super().open()
def retranslate(self):
+ """Refresh translated text for the app q message box."""
self.setWindowTitle(_(self.windowTitle()))
self.setText(_(self.text()))
@@ -450,14 +514,18 @@ class AppQMessageBox(Mixins.QTranslatable, Mixins.ConnectionAware, QMessageBox):
self.moveToCenter()
def disconnectedCallback(self):
+ """Update the app q message box for a disconnected state."""
self.setWindowIcon(AppHue.disconnectedWindowIcon())
def connectedCallback(self):
+ """Update the app q message box for a connected state."""
self.setWindowIcon(AppHue.connectedWindowIcon())
class AppQPushButton(Mixins.QTranslatable, Mixins.ThemeAware, QPushButton):
+ """Represent app q push button."""
def __init__(self, *args, **kwargs):
+ """Initialize the AppQPushButton."""
icon = kwargs.pop('icon', None)
super().__init__(*args, **kwargs)
@@ -470,6 +538,7 @@ class AppQPushButton(Mixins.QTranslatable, Mixins.ThemeAware, QPushButton):
@staticmethod
@functools.lru_cache(None)
def getIconFileName(fileName):
+ """Return icon file name."""
try:
return fileName.split('/')[-1]
except Exception:
@@ -478,6 +547,7 @@ class AppQPushButton(Mixins.QTranslatable, Mixins.ThemeAware, QPushButton):
return ''
def setIconByTheme(self, theme):
+ """Set icon by theme."""
if not self.iconFileName:
return
@@ -503,6 +573,7 @@ class AppQPushButton(Mixins.QTranslatable, Mixins.ThemeAware, QPushButton):
super().setIcon(bootstrapIcon(self.iconFileName))
def setIcon(self, icon: AppQIcon):
+ """Set icon."""
self.iconFileName = self.getIconFileName(icon.iconFileName)
if not self.iconFileName:
@@ -512,17 +583,22 @@ class AppQPushButton(Mixins.QTranslatable, Mixins.ThemeAware, QPushButton):
self.setIconByTheme(APP().theme())
def themeChangedCallback(self, theme):
+ """Update the app q push button for a theme change."""
self.setIconByTheme(theme)
def retranslate(self):
+ """Refresh translated text for the app q push button."""
self.setText(_(self.text()))
class AppQSpinBox(QSpinBox):
+ """Represent app q spin box."""
def __init__(self, *args, **kwargs):
+ """Initialize the AppQSpinBox."""
super().__init__(*args, **kwargs)
def resizeHints(self):
+ """Handle resize hints for the app q spin box."""
self.setMinimumWidth(
max(
self.sizeHint().width(),
@@ -531,40 +607,50 @@ class AppQSpinBox(QSpinBox):
)
def setRange(self, *args, **kwargs):
+ """Set range."""
super().setRange(*args, **kwargs)
self.resizeHints()
class AppQTableView(Mixins.ConnectionAware, QTableView):
+ """Represent app Qt table view."""
def __init__(self, *args, **kwargs):
+ """Initialize the AppQTableView."""
super().__init__(*args, **kwargs)
self.setWordWrap(False)
self.setAlternatingRowColors(True)
def setDefaultRowHeight(self, height: int):
+ """Set default row height."""
self.verticalHeader().setSectionResizeMode(QHeaderView.ResizeMode.Fixed)
self.verticalHeader().setDefaultSectionSize(height)
@staticmethod
def getStyleSheet(color):
+ """Return style sheet."""
return f'QTableView {{ selection-background-color: {color}; }}'
def setSelectionColor(self, color):
# self.setStyleSheet(self.getStyleSheet(color))
+ """Set selection color."""
pass
def disconnectedCallback(self):
+ """Update the app Qt table view for a disconnected state."""
self.setSelectionColor(AppHue.disconnectedColor())
def connectedCallback(self):
+ """Update the app Qt table view for a connected state."""
self.setSelectionColor(AppHue.connectedColor())
class AppQTableWidget(Mixins.ConnectionAware, QTableWidget):
+ """Provide the app Qt table widget."""
def __init__(self, *args, **kwargs):
+ """Initialize the AppQTableWidget."""
super().__init__(*args, **kwargs)
self.setWordWrap(False)
@@ -572,18 +658,22 @@ class AppQTableWidget(Mixins.ConnectionAware, QTableWidget):
@property
def selectedIndex(self):
+ """Return the selected index value."""
return sorted(list(set(index.row() for index in self.selectedIndexes())))
@staticmethod
def getStyleSheet(color):
+ """Return style sheet."""
return f'QTableWidget {{ selection-background-color: {color}; }}'
def setSelectionColor(self, color):
# self.setStyleSheet(self.getStyleSheet(color))
+ """Set selection color."""
pass
def activateItemByIndex(self, index, activate):
+ """Activate item by index."""
if activate:
for column in range(self.columnCount()):
item = self.item(int(index), column)
@@ -612,6 +702,7 @@ class AppQTableWidget(Mixins.ConnectionAware, QTableWidget):
item.setForeground(QBrush())
def selectMultipleRows(self, indexes: list[int], clearCurrentSelection: bool):
+ """Select multiple rows."""
if clearCurrentSelection:
self.selectionModel().clearSelection()
@@ -628,23 +719,30 @@ class AppQTableWidget(Mixins.ConnectionAware, QTableWidget):
)
def disconnectedCallback(self):
+ """Update the app Qt table widget for a disconnected state."""
self.setSelectionColor(AppHue.disconnectedColor())
def connectedCallback(self):
+ """Update the app Qt table widget for a connected state."""
self.setSelectionColor(AppHue.connectedColor())
class AppQTabWidget(Mixins.QTranslatable, QTabWidget):
+ """Provide the app q tab widget."""
def __init__(self, *args, **kwargs):
+ """Initialize the AppQTabWidget."""
super().__init__(*args, **kwargs)
def retranslate(self):
+ """Refresh translated text for the app q tab widget."""
for index in range(self.count()):
self.setTabText(index, _(self.tabText(index)))
class AppQToolBar(Mixins.QTranslatable, QToolBar):
+ """Represent app q tool bar."""
def __init__(self, *actions, **kwargs):
+ """Initialize the AppQToolBar."""
super().__init__(**kwargs)
self._actions = []
@@ -666,8 +764,10 @@ class AppQToolBar(Mixins.QTranslatable, QToolBar):
@QtCore.Slot(AppQAction)
def showMenuBelow(self, action: AppQAction):
+ """Show menu below."""
def toolBarWidgetForAction() -> Union[QWidget | None]:
# Walk through the toolbar to find the widget for the action
+ """Return the tool bar widget for action value used by the app q tool bar."""
for child in self.children():
if hasattr(child, 'defaultAction'):
# PySide6.QtWidgets.QMenu.defaultAction
@@ -688,14 +788,18 @@ class AppQToolBar(Mixins.QTranslatable, QToolBar):
@staticmethod
def getStyleSheet():
+ """Return style sheet."""
return f'QToolBar {{ spacing: 5px; }}'
def retranslate(self):
+ """Refresh translated text for the app q tool bar."""
self.setWindowTitle(_(self.windowTitle()))
class MBoxQuestionDelete(AppQMessageBox):
+ """Represent m box question delete."""
def __init__(self, *args, **kwargs):
+ """Initialize the MBoxQuestionDelete."""
super().__init__(*args, **kwargs)
self.isMulti = False
@@ -707,12 +811,14 @@ class MBoxQuestionDelete(AppQMessageBox):
)
def customText(self) -> str:
+ """Return the user-facing message text for the m box question delete."""
if self.isMulti:
return _('Delete these items?')
else:
return _('Delete this item?') + f'\n\n{self.possibleRemark}'
def retranslate(self):
+ """Refresh translated text for the m box question delete."""
self.setWindowTitle(_(self.windowTitle()))
self.setText(self.customText())
@@ -722,7 +828,9 @@ class MBoxQuestionDelete(AppQMessageBox):
class MBoxNewChangesNextTime(AppQMessageBox):
+ """Represent m box new changes next time."""
def __init__(self, *args, **kwargs):
+ """Initialize the MBoxNewChangesNextTime."""
super().__init__(*args, **kwargs)
self.setWindowTitle(_(APPLICATION_NAME))
@@ -734,11 +842,13 @@ class MBoxNewChangesNextTime(AppQMessageBox):
@staticmethod
def customText() -> str:
+ """Return the user-facing message text for the m box new changes next time."""
return (
_('New changes will take effect next time') + '\n\n' + _('Reconnect now?')
)
def retranslate(self):
+ """Refresh translated text for the m box new changes next time."""
self.setWindowTitle(_(self.windowTitle()))
self.setText(self.customText())
@@ -749,8 +859,10 @@ class MBoxNewChangesNextTime(AppQMessageBox):
def showMBoxNewChangesNextTime(**kwargs):
+ """Show m box new changes next time."""
@QtCore.Slot(int)
def handleResultCode(code):
+ """Handle result code."""
if code == PySide6Legacy.enumValueWrapper(AppQMessageBox.StandardButton.Yes):
APP().systemTray.ConnectAction.doReconnect()
else:
@@ -782,7 +894,9 @@ def showMBoxNewChangesNextTime(**kwargs):
class MBoxDirectRulesNotAllowed(AppQMessageBox):
+ """Represent m box direct rules not allowed."""
def __init__(self, *args, **kwargs):
+ """Initialize the MBoxDirectRulesNotAllowed."""
super().__init__(*args, **kwargs)
self.setWindowTitle(_('Unable to connect'))
@@ -794,6 +908,7 @@ class MBoxDirectRulesNotAllowed(AppQMessageBox):
@staticmethod
def customText() -> str:
+ """Return the user-facing message text for the m box direct rules not allowed."""
return (
_('Routing option with direct rules is not allowed in TUN mode')
+ '\n\n'
@@ -801,6 +916,7 @@ class MBoxDirectRulesNotAllowed(AppQMessageBox):
)
def retranslate(self):
+ """Refresh translated text for the m box direct rules not allowed."""
self.setWindowTitle(_(self.windowTitle()))
self.setText(self.customText())
@@ -810,8 +926,10 @@ class MBoxDirectRulesNotAllowed(AppQMessageBox):
def showMBoxDirectRulesNotAllowed(**kwargs):
+ """Show m box direct rules not allowed."""
@QtCore.Slot(int)
def handleResultCode(code):
+ """Handle result code."""
if code == PySide6Legacy.enumValueWrapper(AppQMessageBox.StandardButton.Yes):
APP().systemTray.RoutingAction.getGlobalAction().trigger()
APP().systemTray.ConnectAction.trigger()
@@ -832,7 +950,9 @@ def showMBoxDirectRulesNotAllowed(**kwargs):
class MBoxUnrecognizedConfig(AppQMessageBox):
+ """Represent m box unrecognized config."""
def __init__(self, *args, **kwargs):
+ """Initialize the MBoxUnrecognizedConfig."""
super().__init__(*args, **kwargs)
self.setIcon(AppQMessageBox.Icon.Critical)
@@ -840,6 +960,7 @@ class MBoxUnrecognizedConfig(AppQMessageBox):
def showMBoxUnrecognizedConfig(**kwargs):
+ """Show m box unrecognized config."""
mbox = MBoxUnrecognizedConfig(**kwargs)
if isinstance(mbox.parent(), QMainWindow):
diff --git a/Furious/Qt/TextEditor.py b/Furious/Qt/TextEditor.py
index db04355d..8565087b 100644
--- a/Furious/Qt/TextEditor.py
+++ b/Furious/Qt/TextEditor.py
@@ -15,6 +15,8 @@
# You should have received a copy of the GNU General Public License
# along with this program. If not, see .
+"""Provide Qt support for text editor."""
+
from __future__ import annotations
from Furious.Frozenlib import *
@@ -38,10 +40,13 @@ __all__ = [
class SupportPointSizeSettings(Mixins.CleanupOnExit):
+ """Store and validate support point size settings."""
def pointSizeSettingsEmpty(self):
+ """Return the point size settings empty value used by the support point size settings."""
return self.pointSizeSettingsName == ''
def __init__(self, *args, **kwargs):
+ """Initialize the SupportPointSizeSettings."""
self.pointSizeSettingsName = kwargs.pop('pointSizeSettingsName', '')
super().__init__(*args, **kwargs)
@@ -51,17 +56,22 @@ class SupportPointSizeSettings(Mixins.CleanupOnExit):
self.restorePointSize()
def restorePointSize(self):
+ """Restore point size."""
raise NotImplementedError
def cleanup(self):
+ """Release resources owned by the support point size settings."""
raise NotImplementedError
class AppQPlainTextEdit(SupportPointSizeSettings, QPlainTextEdit):
+ """Represent app q plain text edit."""
def __init__(self, *args, **kwargs):
+ """Initialize the AppQPlainTextEdit."""
super().__init__(*args, **kwargs)
def restorePointSize(self):
+ """Restore point size."""
if self.pointSizeSettingsEmpty():
return
@@ -79,6 +89,7 @@ class AppQPlainTextEdit(SupportPointSizeSettings, QPlainTextEdit):
@staticmethod
@functools.lru_cache(128)
def getIndent(line):
+ """Return indent."""
indent = ''
for char in line:
@@ -90,6 +101,7 @@ class AppQPlainTextEdit(SupportPointSizeSettings, QPlainTextEdit):
return indent
def getPrevAndNextChar(self, cursor):
+ """Return prev and next char."""
plainText = self.toPlainText()
cursor.movePosition(QTextCursor.MoveOperation.Left)
@@ -114,6 +126,7 @@ class AppQPlainTextEdit(SupportPointSizeSettings, QPlainTextEdit):
return prevChar + nextChar
def smartIndent(self, event):
+ """Handle smart indent for the app q plain text edit."""
cursor = self.textCursor()
indent = self.getIndent(cursor.block().text())
@@ -126,6 +139,7 @@ class AppQPlainTextEdit(SupportPointSizeSettings, QPlainTextEdit):
self.setTextCursor(cursor)
def smartSymbolPair(self, event, pair):
+ """Handle smart symbol pair for the app q plain text edit."""
plainText = self.toPlainText()
cursor = self.textCursor()
@@ -148,6 +162,7 @@ class AppQPlainTextEdit(SupportPointSizeSettings, QPlainTextEdit):
self.setTextCursor(cursor)
def smartBackspace(self, event):
+ """Handle smart backspace for the app q plain text edit."""
cursor = self.textCursor()
chPair = self.getPrevAndNextChar(cursor)
@@ -158,6 +173,7 @@ class AppQPlainTextEdit(SupportPointSizeSettings, QPlainTextEdit):
super().keyPressEvent(event)
def hideTabAndSpaces(self):
+ """Hide tab and spaces."""
textOption = QTextOption()
self.document().setDefaultTextOption(textOption)
@@ -166,6 +182,7 @@ class AppQPlainTextEdit(SupportPointSizeSettings, QPlainTextEdit):
self.setLineWrapMode(QPlainTextEdit.LineWrapMode.NoWrap)
def showTabAndSpaces(self):
+ """Show tab and spaces."""
textOption = QTextOption()
textOption.setFlags(QTextOption.Flag.ShowTabsAndSpaces)
@@ -175,6 +192,7 @@ class AppQPlainTextEdit(SupportPointSizeSettings, QPlainTextEdit):
self.setLineWrapMode(QPlainTextEdit.LineWrapMode.NoWrap)
def keyPressEvent(self, event):
+ """Handle a key press for the app q plain text edit."""
if (
event.key() == QtCore.Qt.Key.Key_Return
or event.key() == QtCore.Qt.Key.Key_Enter
@@ -192,6 +210,7 @@ class AppQPlainTextEdit(SupportPointSizeSettings, QPlainTextEdit):
super().keyPressEvent(event)
def wheelEvent(self, event):
+ """Handle the wheel event."""
if event.modifiers() == QtCore.Qt.KeyboardModifier.ControlModifier:
delta = event.angleDelta().y()
@@ -203,6 +222,7 @@ class AppQPlainTextEdit(SupportPointSizeSettings, QPlainTextEdit):
super().wheelEvent(event)
def cleanup(self):
+ """Release resources owned by the app q plain text edit."""
if self.pointSizeSettingsEmpty():
return
@@ -210,10 +230,13 @@ class AppQPlainTextEdit(SupportPointSizeSettings, QPlainTextEdit):
class AppQTextBrowser(SupportPointSizeSettings, QTextBrowser):
+ """Represent app q text browser."""
def __init__(self, *args, **kwargs):
+ """Initialize the AppQTextBrowser."""
super().__init__(*args, **kwargs)
def appendLine(self, line: str):
+ """Append line."""
hScrollBar = self.horizontalScrollBar()
vScrollBar = self.verticalScrollBar()
scrollEnds = vScrollBar.maximum() - vScrollBar.value() <= 10
@@ -226,6 +249,7 @@ class AppQTextBrowser(SupportPointSizeSettings, QTextBrowser):
hScrollBar.setValue(0) # scroll to the left
def restorePointSize(self):
+ """Restore point size."""
if self.pointSizeSettingsEmpty():
return
@@ -241,6 +265,7 @@ class AppQTextBrowser(SupportPointSizeSettings, QTextBrowser):
pass
def wheelEvent(self, event):
+ """Handle the wheel event."""
if event.modifiers() == QtCore.Qt.KeyboardModifier.ControlModifier:
delta = event.angleDelta().y()
@@ -252,6 +277,7 @@ class AppQTextBrowser(SupportPointSizeSettings, QTextBrowser):
super().wheelEvent(event)
def cleanup(self):
+ """Release resources owned by the app q text browser."""
if self.pointSizeSettingsEmpty():
return
@@ -259,7 +285,9 @@ class AppQTextBrowser(SupportPointSizeSettings, QTextBrowser):
class DraculaTextEditor(AppQPlainTextEdit):
+ """Represent dracula text editor."""
def __init__(self, *args, **kwargs):
+ """Initialize the DraculaTextEditor."""
fontFamily = kwargs.pop('fontFamily', '')
super().__init__(*args, **kwargs)
@@ -276,6 +304,7 @@ class DraculaTextEditor(AppQPlainTextEdit):
@QtCore.Slot(bool)
def handleModificationChanged(changed):
+ """Handle modification changed."""
if changed:
self.document().setModified(False)
@@ -284,6 +313,7 @@ class DraculaTextEditor(AppQPlainTextEdit):
@QtCore.Slot()
def handleCursorPositionChanged():
+ """Handle cursor position changed."""
if callable(self._cursorPositionChangedCb):
self._cursorPositionChangedCb(self.textCursor())
@@ -291,21 +321,27 @@ class DraculaTextEditor(AppQPlainTextEdit):
self.cursorPositionChanged.connect(handleCursorPositionChanged)
def registerCursorPositionChangedCb(self, callback: Callable[[QTextCursor], None]):
+ """Handle register cursor position changed cb for the dracula text editor."""
self._cursorPositionChangedCb = callback
def registerModificationChangedCb(self, callback: Callable[[], None]):
+ """Handle register modification changed cb for the dracula text editor."""
self._modificationChangedCb = callback
class DraculaJSONTextEditor(DraculaTextEditor):
+ """Represent dracula JSON text editor."""
def __init__(self, *args, **kwargs):
+ """Initialize the DraculaJSONTextEditor."""
super().__init__(*args, **kwargs)
self._syntaxHighlighter = DraculaJSONSyntaxHighlighter(self.document())
class DraculaTextBrowser(AppQTextBrowser):
+ """Represent dracula text browser."""
def __init__(self, *args, **kwargs):
+ """Initialize the DraculaTextBrowser."""
fontFamily = kwargs.pop('fontFamily', '')
super().__init__(*args, **kwargs)
diff --git a/Furious/Qt/TextEditorTheme.py b/Furious/Qt/TextEditorTheme.py
index 97f06bec..ecb0a94b 100644
--- a/Furious/Qt/TextEditorTheme.py
+++ b/Furious/Qt/TextEditorTheme.py
@@ -15,6 +15,8 @@
# You should have received a copy of the GNU General Public License
# along with this program. If not, see .
+"""Provide Qt support for text editor theme."""
+
from __future__ import annotations
from Furious.Core import *
@@ -30,7 +32,9 @@ __all__ = [
class EditorHighlightRules:
+ """Represent editor highlight rules."""
def __init__(self, regex, color, isBold=False, isItalic=False, isJSONKey=False):
+ """Initialize the EditorHighlightRules."""
self.regex = QtCore.QRegularExpression(regex)
self.color = QColor(color)
@@ -47,20 +51,26 @@ class EditorHighlightRules:
class EditorTheme:
+ """Represent editor theme."""
def __init__(self, *args, **kwargs):
+ """Initialize the EditorTheme."""
super().__init__(*args, **kwargs)
@staticmethod
def getStyleSheet(*args, **kwargs):
+ """Return style sheet."""
raise NotImplementedError
class DraculaEditorTheme(EditorTheme):
+ """Represent dracula editor theme."""
def __init__(self, *args, **kwargs):
+ """Initialize the DraculaEditorTheme."""
super().__init__(*args, **kwargs)
@staticmethod
def getStyleSheet(widgetName, fontFamily):
+ """Return style sheet."""
return (
f'{widgetName} {{'
f' background-color: #282A36;'
@@ -71,12 +81,15 @@ class DraculaEditorTheme(EditorTheme):
class AppQSyntaxHighlighter(QSyntaxHighlighter):
+ """Apply syntax highlighting for app q syntax text."""
def __init__(self, *args, **kwargs):
+ """Initialize the AppQSyntaxHighlighter."""
super().__init__(*args, **kwargs)
self.highlightRules = list()
def highlightBlock(self, text):
+ """Handle highlight block for the app q syntax highlighter."""
for highlightRule in self.highlightRules:
iterator = highlightRule.regex.globalMatch(text)
@@ -110,7 +123,9 @@ class AppQSyntaxHighlighter(QSyntaxHighlighter):
class DraculaJSONSyntaxHighlighter(AppQSyntaxHighlighter):
+ """Apply syntax highlighting for dracula JSON syntax text."""
def __init__(self, *args, **kwargs):
+ """Initialize the DraculaJSONSyntaxHighlighter."""
super().__init__(*args, **kwargs)
self.highlightRules = [
@@ -133,6 +148,7 @@ class DraculaJSONSyntaxHighlighter(AppQSyntaxHighlighter):
class DraculaLoggerSyntaxHighlighter(AppQSyntaxHighlighter):
# https://ihateregex.io/expr/ip/
+ """Apply syntax highlighting for dracula logger syntax text."""
IPV4_REGEX = (
r'(\b25[0-5]|\b2[0-4][0-9]|\b[01]?[0-9][0-9]?)'
r'(\.(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)){3}(?::\d{1,5})?\b'
@@ -159,6 +175,7 @@ class DraculaLoggerSyntaxHighlighter(AppQSyntaxHighlighter):
)
def __init__(self, *args, **kwargs):
+ """Initialize the DraculaLoggerSyntaxHighlighter."""
super().__init__(*args, **kwargs)
self.highlightRules = [
diff --git a/Furious/Qt/UpdatesManager.py b/Furious/Qt/UpdatesManager.py
index db6e1a5f..8670b3db 100644
--- a/Furious/Qt/UpdatesManager.py
+++ b/Furious/Qt/UpdatesManager.py
@@ -15,6 +15,8 @@
# You should have received a copy of the GNU General Public License
# along with this program. If not, see .
+"""Provide Qt support for updates manager."""
+
from __future__ import annotations
from Furious.Frozenlib import *
@@ -37,7 +39,9 @@ logger = logging.getLogger(__name__)
class MBoxQuestionUpdate(AppQMessageBox):
+ """Represent m box question update."""
def __init__(self, *args, **kwargs):
+ """Initialize the MBoxQuestionUpdate."""
super().__init__(*args, **kwargs)
self.version = '0.0.0'
@@ -47,9 +51,11 @@ class MBoxQuestionUpdate(AppQMessageBox):
)
def customText(self):
+ """Return the user-facing message text for the m box question update."""
return _('New version available') + f': {self.version}'
def retranslate(self):
+ """Refresh translated text for the m box question update."""
self.setText(self.customText())
self.setWindowTitle(_(self.windowTitle()))
self.setInformativeText(_(self.informativeText()))
@@ -60,18 +66,21 @@ class MBoxQuestionUpdate(AppQMessageBox):
class UpdatesManager(WebGETManager):
+ """Coordinate updates operations."""
API_URL = (
f'https://api.github.com/repos/'
f'{APPLICATION_REPO_OWNER_NAME}/{APPLICATION_REPO_NAME}/releases/latest'
)
def __init__(self, parent=None, **kwargs):
+ """Initialize the UpdatesManager."""
actionMessage = kwargs.pop('actionMessage', 'check for updates')
super().__init__(parent, actionMessage=actionMessage)
@staticmethod
def showErrorMessageBox(parent=None):
+ """Show error message box."""
mbox = AppQMessageBox(parent=parent, icon=AppQMessageBox.Icon.Critical)
mbox.setWindowTitle(_(APPLICATION_NAME))
mbox.setText(_('Check for updates failed'))
@@ -80,6 +89,7 @@ class UpdatesManager(WebGETManager):
mbox.open()
def successCallback(self, networkReply, **kwargs):
+ """Handle a successful network operation."""
parent = kwargs.pop('parent', None)
showMessageBox = kwargs.pop('showMessageBox', True)
hasNewVersionCallback = kwargs.pop('hasNewVersionCallback', None)
@@ -101,6 +111,7 @@ class UpdatesManager(WebGETManager):
if versionToValue(newVersion) > versionToValue(APPLICATION_VERSION):
def handleResultCode(code):
+ """Handle result code."""
if code == PySide6Legacy.enumValueWrapper(
AppQMessageBox.StandardButton.Yes
):
@@ -140,6 +151,7 @@ class UpdatesManager(WebGETManager):
mbox.open()
def failureCallback(self, networkReply, **kwargs):
+ """Handle a failed network operation."""
showMessageBox = kwargs.pop('showMessageBox', True)
if showMessageBox:
@@ -151,6 +163,7 @@ class UpdatesManager(WebGETManager):
hasNewVersionCallback: Callable[[str], None] = None,
**kwargs,
):
+ """Check for updates."""
self.webGET(
self.API_URL,
showMessageBox=showMessageBox,
diff --git a/Furious/Qt/WebGETManager.py b/Furious/Qt/WebGETManager.py
index 2632e883..2d78e0e1 100644
--- a/Furious/Qt/WebGETManager.py
+++ b/Furious/Qt/WebGETManager.py
@@ -15,6 +15,8 @@
# You should have received a copy of the GNU General Public License
# along with this program. If not, see .
+"""Provide Qt support for web get manager."""
+
from __future__ import annotations
from Furious.Frozenlib import *
@@ -34,7 +36,9 @@ logger = logging.getLogger(__name__)
class WebGETManager(AppQNetworkAccessManager):
+ """Coordinate web get operations."""
def __init__(self, parent=None, actionMessage='web GET', **kwargs):
+ """Initialize the WebGETManager."""
super().__init__(parent)
self.actionMessage = actionMessage
@@ -43,19 +47,25 @@ class WebGETManager(AppQNetworkAccessManager):
self.mustCalled = False
def successCallback(self, networkReply: QNetworkReply, **kwargs):
+ """Handle a successful network operation."""
pass
def hasDataCallback(self, networkReply: QNetworkReply, **kwargs):
+ """Handle newly available network response data."""
pass
def failureCallback(self, networkReply: QNetworkReply, **kwargs):
+ """Handle a failed network operation."""
pass
def mustCall(self, **kwargs):
+ """Perform the required completion hook."""
pass
def must(self, **kwargs):
+ """Run the required completion hook according to its call policy."""
def call():
+ """Invoke the registered completion callback."""
try:
self.mustCall(**kwargs)
except Exception as ex:
@@ -71,9 +81,11 @@ class WebGETManager(AppQNetworkAccessManager):
call()
def handleReadyReadByNetworkReply(self, networkReply: QNetworkReply, **kwargs):
+ """Handle ready read by network reply."""
self.hasDataCallback(networkReply, **kwargs)
def handleFinishedByNetworkReply(self, networkReply: QNetworkReply, **kwargs):
+ """Handle finished by network reply."""
try:
if not isinstance(networkReply, QNetworkReply):
# Some PySide6 version does not have networkReply as
@@ -102,6 +114,7 @@ class WebGETManager(AppQNetworkAccessManager):
self.must(**kwargs)
def configureHttpProxy(self, httpProxy: Union[str, None]) -> bool:
+ """Configure HTTP proxy."""
useProxy = super().configureHttpProxy(httpProxy)
if useProxy:
@@ -112,6 +125,7 @@ class WebGETManager(AppQNetworkAccessManager):
return useProxy
def webGET(self, request: Union[QNetworkRequest, str], **kwargs) -> QNetworkReply:
+ """Return the web get value used by the web get manager."""
if isinstance(request, QNetworkRequest):
networkReply = self.get(request)
else:
diff --git a/Furious/Qt/XrayAssetDownloadManager.py b/Furious/Qt/XrayAssetDownloadManager.py
index 33dcf8ba..75ec3554 100644
--- a/Furious/Qt/XrayAssetDownloadManager.py
+++ b/Furious/Qt/XrayAssetDownloadManager.py
@@ -15,6 +15,8 @@
# You should have received a copy of the GNU General Public License
# along with this program. If not, see .
+"""Provide Qt support for Xray asset download manager."""
+
from __future__ import annotations
from Furious.Frozenlib import *
@@ -35,27 +37,33 @@ logger = logging.getLogger(__name__)
class SHA256Worker(QtCore.QObject, QtCore.QRunnable):
+ """Run SHA-256 work in the background."""
finished = QtCore.Signal(str)
def __init__(self, string=b''):
# Explictly called __init__
+ """Initialize the SHA256Worker."""
QtCore.QObject.__init__(self)
QtCore.QRunnable.__init__(self)
self.string = string
def run(self):
+ """Run the SHA-256 worker task."""
self.finished.emit(hashlib.sha256(self.string).hexdigest())
class XrayAssetSHA256DownloadManager(WebGETManager):
+ """Coordinate Xray asset SHA-256 download operations."""
def __init__(self, parent=None, **kwargs):
+ """Initialize the XrayAssetSHA256DownloadManager."""
actionMessage = kwargs.pop('actionMessage', 'download sha256')
super().__init__(parent, actionMessage=actionMessage)
@staticmethod
def fileContent(filepath, mode='rb') -> AnyStr:
+ """Return the file content value used by the Xray asset SHA-256 download manager."""
try:
with open(filepath, mode) as file:
return file.read()
@@ -65,6 +73,7 @@ class XrayAssetSHA256DownloadManager(WebGETManager):
return b''
def successCallback(self, networkReply, **kwargs):
+ """Handle a successful network operation."""
filepath = kwargs.pop('filepath', '')
downloadCallback = kwargs.pop('downloadCallback', None)
@@ -84,6 +93,7 @@ class XrayAssetSHA256DownloadManager(WebGETManager):
basename = os.path.basename(filepath)
def handleFinished(_digest, _value=''):
+ """Handle finished."""
logger.debug(
f'computed digest is \'{_digest}\' while repo digest is \'{_value}\''
)
@@ -104,6 +114,7 @@ class XrayAssetSHA256DownloadManager(WebGETManager):
AppThreadPool().start(worker)
def download(self, url, filepath, downloadCallback: Callable[[], None]):
+ """Download the Xray asset SHA-256 download manager."""
self.webGET(
url,
filepath=str(filepath),
@@ -112,12 +123,15 @@ class XrayAssetSHA256DownloadManager(WebGETManager):
class XrayAssetAssetsDownloadManager(WebGETManager):
+ """Coordinate Xray asset assets download operations."""
def __init__(self, parent=None, **kwargs):
+ """Initialize the XrayAssetAssetsDownloadManager."""
actionMessage = kwargs.pop('actionMessage', 'download assets')
super().__init__(parent, actionMessage=actionMessage)
def successCallback(self, networkReply, **kwargs):
+ """Handle a successful network operation."""
filepath = kwargs.pop('filepath', '')
saveFile = QtCore.QSaveFile(filepath)
@@ -139,11 +153,14 @@ class XrayAssetAssetsDownloadManager(WebGETManager):
logger.info(f'save file to \'{filepath}\' success')
def download(self, url, filepath):
+ """Download the Xray asset assets download manager."""
self.webGET(url, filepath=str(filepath))
class XrayAssetPairDownloadHelper:
+ """Represent Xray asset pair download helper."""
def __init__(self, *args, **kwargs):
+ """Initialize the XrayAssetPairDownloadHelper."""
sha256ActionMessage = kwargs.pop('sha256ActionMessage', 'download sha256')
assetsActionMessage = kwargs.pop('assetsActionMessage', 'download assets')
@@ -157,6 +174,7 @@ class XrayAssetPairDownloadHelper:
)
def configureHttpProxy(self, httpProxy: Union[str, None]) -> bool:
+ """Configure HTTP proxy."""
return all(
[
self.sha256Downloader.configureHttpProxy(httpProxy),
@@ -165,6 +183,7 @@ class XrayAssetPairDownloadHelper:
)
def download(self, sha256URL, assetsURL, filepath):
+ """Download the Xray asset pair download helper."""
self.sha256Downloader.download(
url=sha256URL,
filepath=filepath,
@@ -175,7 +194,9 @@ class XrayAssetPairDownloadHelper:
class XrayAssetDownloadManager:
+ """Coordinate Xray asset download operations."""
def __init__(self, *args, **kwargs):
+ """Initialize the XrayAssetDownloadManager."""
super().__init__(*args, **kwargs)
self.downloadHelperGeosite = XrayAssetPairDownloadHelper(
@@ -188,6 +209,7 @@ class XrayAssetDownloadManager:
)
def configureHttpProxy(self, httpProxy: Union[str, None]) -> bool:
+ """Configure HTTP proxy."""
return all(
[
self.downloadHelperGeosite.configureHttpProxy(httpProxy),
@@ -196,6 +218,7 @@ class XrayAssetDownloadManager:
)
def download(self):
+ """Download the Xray asset download manager."""
self.downloadHelperGeosite.download(
sha256URL=URL_GEOSITE_SHA256,
assetsURL=URL_GEOSITE,
diff --git a/Furious/Qt/__init__.py b/Furious/Qt/__init__.py
index 3252db41..1195869e 100644
--- a/Furious/Qt/__init__.py
+++ b/Furious/Qt/__init__.py
@@ -15,6 +15,8 @@
# You should have received a copy of the GNU General Public License
# along with this program. If not, see .
+"""Expose reusable Qt helpers and application-aware widgets."""
+
from __future__ import annotations
from .DNSResolver import *
diff --git a/Furious/TrayActions/Connect.py b/Furious/TrayActions/Connect.py
index 3d177db2..9277b559 100644
--- a/Furious/TrayActions/Connect.py
+++ b/Furious/TrayActions/Connect.py
@@ -15,6 +15,8 @@
# You should have received a copy of the GNU General Public License
# along with this program. If not, see .
+"""Implement tray actions for connect."""
+
from __future__ import annotations
from Furious.Frozenlib import *
@@ -39,6 +41,7 @@ registerAppSettings('Connect', isBinary=True)
def validateProxyServer(server) -> bool:
+ """Validate proxy server."""
try:
host, port = parseHostPort(server)
@@ -53,7 +56,9 @@ def validateProxyServer(server) -> bool:
class ConnectAction(AppQAction):
+ """Handle the connect action."""
def __init__(self, **kwargs):
+ """Initialize the ConnectAction."""
super().__init__(
_('Connect'),
icon=bootstrapIcon('unlock-fill.svg'),
@@ -72,6 +77,7 @@ class ConnectAction(AppQAction):
self.assetDownloadManager = XrayAssetDownloadManager()
def reset(self):
+ """Restore the connect action to its initial state."""
self.hideProgressBar(True)
self.setText(_('Connect'))
self.setIcon(bootstrapIcon('unlock-fill.svg'))
@@ -83,6 +89,7 @@ class ConnectAction(AppQAction):
self.setDisabledAction(False)
def showProgressBar(self):
+ """Show progress bar."""
if AppSettings.isStateON_('ShowProgressBarWhenConnecting'):
self.progressBar.setValue(0)
# Update the progress bar every 50ms
@@ -92,6 +99,7 @@ class ConnectAction(AppQAction):
return self
def hideProgressBar(self, done: bool):
+ """Hide progress bar."""
if done:
self.progressBar.setValue(100)
@@ -101,6 +109,7 @@ class ConnectAction(AppQAction):
return self
def setDisabledAction(self, value):
+ """Set disabled action."""
self.setDisabled(value)
APP().systemTray.RoutingAction.setDisabled(value)
@@ -112,12 +121,15 @@ class ConnectAction(AppQAction):
TUNModeAction.setDisabled(value)
def isConnected(self) -> bool:
+ """Return whether connected."""
return self.textCompare('Disconnect')
def isConnecting(self):
+ """Return whether connecting."""
return self.textCompare('Connecting')
def doConnecting(self):
+ """Handle do connecting for the connect action."""
self.setText(_('Connecting'))
self.setIcon(bootstrapIcon('lock-fill.svg'))
# Do not accept new action
@@ -125,6 +137,7 @@ class ConnectAction(AppQAction):
self.showProgressBar()
def doConnected(self):
+ """Handle do connected for the connect action."""
self.hideProgressBar(True)
# Connected
self.setText(_('Disconnect'))
@@ -137,6 +150,7 @@ class ConnectAction(AppQAction):
self.setDisabledAction(False)
def doDisconnect(self):
+ """Handle do disconnect for the connect action."""
SystemProxy.off()
self.actionTimer.stop()
@@ -155,16 +169,19 @@ class ConnectAction(AppQAction):
Mixins.ConnectionAware.callDisconnectedCallback()
def doDisconnectWithTrayMessage(self, message: str):
+ """Handle do disconnect with tray message for the connect action."""
self.doDisconnect()
APP().systemTray.showMessage(message)
def doReconnect(self, message=''):
+ """Handle do reconnect for the connect action."""
self.doDisconnectWithTrayMessage(message)
self.trigger()
def doConnect(self):
# Connect action
+ """Return the do connect value used by the connect action."""
assert self.textCompare('Connect')
if not Storage.UserServers():
@@ -212,6 +229,7 @@ class ConnectAction(AppQAction):
@forceToLocalhostIfPossible()
def getHttpProxy() -> str:
+ """Return HTTP proxy."""
return config.httpProxy()
httpProxy = getHttpProxy()
@@ -304,7 +322,9 @@ class ConnectAction(AppQAction):
@callOnceOnly
def doConnectedCallOnceOnly(self):
+ """Handle do connected call once only for the connect action."""
def newVersionCallback(newVersion):
+ """Handle the new version callback."""
APP().systemTray.showMessage(
f'{APPLICATION_NAME} {newVersion} ' + _('is available to download')
)
@@ -333,6 +353,7 @@ class ConnectAction(AppQAction):
logger.info('skipped auto assets update due to settings')
def callActionFromQueue(self):
+ """Call action from queue."""
try:
action = self.actionQueue.get_nowait()
except queue.Empty:
@@ -348,7 +369,9 @@ class ConnectAction(AppQAction):
action()
def coreExitCallback(self, core: CoreProcessFactory, exitcode: int):
+ """Handle the core exit callback."""
def putItem(item):
+ """Handle put item for the connect action."""
try:
self.actionQueue.put_nowait(item)
except Exception:
@@ -401,6 +424,7 @@ class ConnectAction(AppQAction):
return None
def triggeredCallback(self, checked):
+ """Handle activation of the action."""
if checked:
self.doConnect()
else:
diff --git a/Furious/TrayActions/EditConfiguration.py b/Furious/TrayActions/EditConfiguration.py
index 63283acc..74929508 100644
--- a/Furious/TrayActions/EditConfiguration.py
+++ b/Furious/TrayActions/EditConfiguration.py
@@ -15,6 +15,8 @@
# You should have received a copy of the GNU General Public License
# along with this program. If not, see .
+"""Implement tray actions for edit configuration."""
+
from __future__ import annotations
from Furious.Frozenlib import *
@@ -25,7 +27,9 @@ __all__ = ['EditConfigurationAction']
class EditConfigurationAction(AppQAction):
+ """Handle the edit configuration action."""
def __init__(self, **kwargs):
+ """Initialize the EditConfigurationAction."""
super().__init__(
_('Edit Configuration...'),
icon=bootstrapIcon('pencil-square.svg'),
@@ -33,4 +37,5 @@ class EditConfigurationAction(AppQAction):
)
def triggeredCallback(self, checked):
+ """Handle activation of the action."""
APP().mainWindow.show()
diff --git a/Furious/TrayActions/Exit.py b/Furious/TrayActions/Exit.py
index 7542f6fd..ce9dd3a4 100644
--- a/Furious/TrayActions/Exit.py
+++ b/Furious/TrayActions/Exit.py
@@ -15,6 +15,8 @@
# You should have received a copy of the GNU General Public License
# along with this program. If not, see .
+"""Implement tray actions for exit."""
+
from __future__ import annotations
from Furious.Frozenlib import *
@@ -25,7 +27,9 @@ __all__ = ['ExitAction']
class ExitAction(AppQAction):
+ """Handle the exit action."""
def __init__(self, **kwargs):
+ """Initialize the ExitAction."""
super().__init__(
_('Exit'),
icon=bootstrapIcon('box-arrow-in-left.svg'),
@@ -33,4 +37,5 @@ class ExitAction(AppQAction):
)
def triggeredCallback(self, checked):
+ """Handle activation of the action."""
APP().exit()
diff --git a/Furious/TrayActions/Import.py b/Furious/TrayActions/Import.py
index b5c2630d..721a5233 100644
--- a/Furious/TrayActions/Import.py
+++ b/Furious/TrayActions/Import.py
@@ -15,6 +15,8 @@
# You should have received a copy of the GNU General Public License
# along with this program. If not, see .
+"""Implement tray actions for import."""
+
from __future__ import annotations
from Furious.Frozenlib import *
@@ -49,6 +51,7 @@ logger = logging.getLogger(__name__)
def showMBoxImportError(clipboard: str):
+ """Show m box import error."""
mbox = MBoxImportError(icon=AppQMessageBox.Icon.Critical)
if len(clipboard) > 1000:
@@ -64,6 +67,7 @@ def showMBoxImportError(clipboard: str):
def importURIFromClipboard(clipboard: str):
+ """Import URI from clipboard."""
factory = configFactoryFromAny(clipboard)
if not factory.isValid():
@@ -80,6 +84,7 @@ def importURIFromClipboard(clipboard: str):
def importURIs(*uris, failureCallback: Union[Callable[[], None], None] = None):
+ """Import ur is."""
if len(uris) > 1:
dialog = ImportURIsProgressDialog(
uris,
@@ -124,6 +129,7 @@ def importURIs(*uris, failureCallback: Union[Callable[[], None], None] = None):
class ImportURIsProgressDialog(AppQDialog):
+ """Present progress and cancellation controls for import ur is."""
ActiveDialogs = list()
def __init__(
@@ -132,6 +138,7 @@ class ImportURIsProgressDialog(AppQDialog):
failureCallback: Union[Callable[[], None], None] = None,
parent=None,
):
+ """Initialize the ImportURIsProgressDialog."""
super().__init__(parent)
self.uris = uris
@@ -175,9 +182,11 @@ class ImportURIsProgressDialog(AppQDialog):
self.updateStatus()
def setWidthAndHeight(self):
+ """Apply the default size for the import ur is progress dialog."""
self.resize(420, 150)
def open(self):
+ """Open the import ur is progress dialog asynchronously."""
ImportURIsProgressDialog.ActiveDialogs.append(self)
result = super().open()
@@ -189,14 +198,17 @@ class ImportURIsProgressDialog(AppQDialog):
return result
def reject(self):
+ """Reject the current import ur is progress dialog values."""
self.cancel()
def cancel(self):
+ """Cancel the import ur is progress dialog operation."""
self.canceled = True
self.cancelButton.setEnabled(False)
self.updateStatus()
def updateStatus(self):
+ """Update status."""
total = len(self.uris)
processed = min(self.currentIndex, total)
@@ -212,6 +224,7 @@ class ImportURIsProgressDialog(AppQDialog):
@staticmethod
def limitedRemark(remark: str) -> str:
+ """Return the limited remark value used by the import ur is progress dialog."""
remark = str(remark).strip()
if len(remark) <= 120:
@@ -220,6 +233,7 @@ class ImportURIsProgressDialog(AppQDialog):
return remark[:117] + '...'
def importNext(self):
+ """Import next."""
if self.canceled or self.currentIndex >= len(self.uris):
self.finishImport()
@@ -246,6 +260,7 @@ class ImportURIsProgressDialog(AppQDialog):
QtCore.QTimer.singleShot(0, self.importNext)
def finishImport(self):
+ """Handle finish import for the import ur is progress dialog."""
if self.finishedImport:
return
@@ -281,18 +296,22 @@ class ImportURIsProgressDialog(AppQDialog):
mbox.open()
def retranslate(self):
+ """Refresh translated text for the import ur is progress dialog."""
self.setWindowTitle(_(self.windowTitle()))
self.cancelButton.setText(_(self.cancelButton.text()))
self.updateStatus()
class MBoxImportError(AppQMessageBox):
+ """Represent m box import error."""
def __init__(self, *args, **kwargs):
+ """Initialize the MBoxImportError."""
super().__init__(*args, **kwargs)
self.setWindowTitle(_('Import'))
def retranslate(self):
+ """Refresh translated text for the m box import error."""
self.setWindowTitle(_(self.windowTitle()))
self.setText(_(self.text()))
@@ -302,7 +321,9 @@ class MBoxImportError(AppQMessageBox):
class MBoxImportMultiSuccess(AppQMessageBox):
+ """Represent m box import multi success."""
def __init__(self, *args, **kwargs):
+ """Initialize the MBoxImportMultiSuccess."""
super().__init__(*args, **kwargs)
self.imported = list()
@@ -312,6 +333,7 @@ class MBoxImportMultiSuccess(AppQMessageBox):
self.setIcon(AppQMessageBox.Icon.Information)
def customText(self):
+ """Return the user-facing message text for the m box import multi success."""
text = (
_('Import share link success')
+ '\n\n'
@@ -332,6 +354,7 @@ class MBoxImportMultiSuccess(AppQMessageBox):
return _('Import share link success') + f'\n\n...'
def retranslate(self):
+ """Refresh translated text for the m box import multi success."""
self.setWindowTitle(_(self.windowTitle()))
self.setText(self.customText())
@@ -341,19 +364,23 @@ class MBoxImportMultiSuccess(AppQMessageBox):
class MBoxImportSuccess(AppQMessageBox):
+ """Represent m box import success."""
def __init__(self, *args, **kwargs):
+ """Initialize the MBoxImportSuccess."""
super().__init__(*args, **kwargs)
self.remark = ''
self.setWindowTitle(_('Import'))
def customText(self):
+ """Return the user-facing message text for the m box import success."""
if self.remark:
return _('Import success') + f': {self.remark}'
else:
return _('Import success')
def retranslate(self):
+ """Refresh translated text for the m box import success."""
self.setWindowTitle(_(self.windowTitle()))
self.setText(self.customText())
@@ -363,7 +390,9 @@ class MBoxImportSuccess(AppQMessageBox):
class ImportFromFileAction(AppQAction):
+ """Handle the import from file action."""
def __init__(self, **kwargs):
+ """Initialize the ImportFromFileAction."""
super().__init__(
_('Import From File...'),
icon=bootstrapIcon('folder2.svg'),
@@ -371,6 +400,7 @@ class ImportFromFileAction(AppQAction):
)
def triggeredCallback(self, checked):
+ """Handle activation of the action."""
filename, selectedFilter = QFileDialog.getOpenFileName(
None,
_('Import File'),
@@ -415,10 +445,13 @@ class ImportFromFileAction(AppQAction):
class ImportURIFromClipboardAction(AppQAction):
+ """Handle the import URI from clipboard action."""
def __init__(self, **kwargs):
+ """Initialize the ImportURIFromClipboardAction."""
super().__init__(_('Import Share Link From Clipboard'), **kwargs)
def triggeredCallback(self, checked):
+ """Handle activation of the action."""
clipboard = QApplication.clipboard().text().strip()
try:
@@ -435,17 +468,22 @@ class ImportURIFromClipboardAction(AppQAction):
class ImportJSONFromClipboardAction(AppQAction):
+ """Handle the import JSON from clipboard action."""
def __init__(self, **kwargs):
+ """Initialize the ImportJSONFromClipboardAction."""
super().__init__(_('Import JSON Configuration From Clipboard'), **kwargs)
def triggeredCallback(self, checked):
+ """Handle activation of the action."""
clipboard = QApplication.clipboard().text().strip()
importURIFromClipboard(clipboard)
class ImportQRCodeOnTheScreenAction(Mixins.CleanupOnExit, AppQAction):
+ """Handle the import QR code on the screen action."""
def __init__(self, **kwargs):
+ """Initialize the ImportQRCodeOnTheScreenAction."""
super().__init__(
_('Scan QR Code On The Screen'),
icon=bootstrapIcon('qr-code-scan.svg'),
@@ -462,6 +500,7 @@ class ImportQRCodeOnTheScreenAction(Mixins.CleanupOnExit, AppQAction):
self.sct = None
def _importFromQRCode(self):
+ """Handle import from QR code for the import QR code on the screen action."""
if self.sct is None:
# Nothing to do
return
@@ -487,6 +526,7 @@ class ImportQRCodeOnTheScreenAction(Mixins.CleanupOnExit, AppQAction):
importURIs(*uris)
def triggeredCallback(self, checked):
+ """Handle activation of the action."""
try:
self._importFromQRCode()
except Exception as ex:
@@ -495,6 +535,7 @@ class ImportQRCodeOnTheScreenAction(Mixins.CleanupOnExit, AppQAction):
logger.error(f'error while importing QR code on the screen: {ex}')
def cleanup(self):
+ """Release resources owned by the import QR code on the screen action."""
try:
if self.sct is not None:
self.sct.close()
@@ -505,7 +546,9 @@ class ImportQRCodeOnTheScreenAction(Mixins.CleanupOnExit, AppQAction):
class ImportAction(AppQAction):
+ """Handle the import action."""
def __init__(self, **kwargs):
+ """Initialize the ImportAction."""
super().__init__(
_('Import'),
icon=bootstrapIcon('lightning-charge.svg'),
diff --git a/Furious/TrayActions/Language.py b/Furious/TrayActions/Language.py
index 960a97b3..e0f93933 100644
--- a/Furious/TrayActions/Language.py
+++ b/Furious/TrayActions/Language.py
@@ -15,6 +15,8 @@
# You should have received a copy of the GNU General Public License
# along with this program. If not, see .
+"""Implement tray actions for language."""
+
from __future__ import annotations
from Furious.Frozenlib import *
@@ -38,10 +40,13 @@ registerAppSettings(
class LanguageChildAction(AppQAction):
+ """Handle the language child action."""
def __init__(self, *args, **kwargs):
+ """Initialize the LanguageChildAction."""
super().__init__(*args, **kwargs)
def triggeredCallback(self, checked):
+ """Handle activation of the action."""
abbr = LANGUAGE_TO_ABBR[self.text()]
if AppSettings.get('Language') != abbr:
@@ -53,11 +58,14 @@ class LanguageChildAction(AppQAction):
def retranslate(self):
# Nothing to do
+ """Refresh translated text for the language child action."""
pass
class LanguageAction(AppQAction):
+ """Handle the language action."""
def __init__(self, **kwargs):
+ """Initialize the LanguageAction."""
super().__init__(
_('Language'),
icon=bootstrapIcon('globe2.svg'),
diff --git a/Furious/TrayActions/Routing.py b/Furious/TrayActions/Routing.py
index 26c666ba..d47c9cf3 100644
--- a/Furious/TrayActions/Routing.py
+++ b/Furious/TrayActions/Routing.py
@@ -15,6 +15,8 @@
# You should have received a copy of the GNU General Public License
# along with this program. If not, see .
+"""Implement tray actions for routing."""
+
from __future__ import annotations
from Furious.Frozenlib import *
@@ -35,12 +37,15 @@ _TRANSLATABLE_BUILTIN_ROUTING = [
class RoutingChildAction(AppQAction):
+ """Handle the routing child action."""
def __init__(self, *args, **kwargs):
+ """Initialize the RoutingChildAction."""
self.routingValue = kwargs.pop('routingValue', None)
super().__init__(*args, **kwargs)
def triggeredCallback(self, checked):
+ """Handle activation of the action."""
textEnglish = self.routingValue or self.textEnglish
if AppSettings.get('Routing') != textEnglish:
@@ -51,7 +56,9 @@ class RoutingChildAction(AppQAction):
class RoutingAction(AppQAction):
+ """Handle the routing action."""
def __init__(self, **kwargs):
+ """Initialize the RoutingAction."""
if AppSettings.get('Routing') == 'Bypass':
# Update value for backward compatibility
AppSettings.set('Routing', AppBuiltinRouting.BypassMainlandChina.value)
@@ -66,6 +73,7 @@ class RoutingAction(AppQAction):
self.rebuildMenu()
def routingActions(self):
+ """Return the routing actions value used by the routing action."""
actions = list(
RoutingChildAction(
_(routing.value),
@@ -95,6 +103,7 @@ class RoutingAction(AppQAction):
return actions
def rebuildMenu(self):
+ """Handle rebuild menu for the routing action."""
self._menu.clear()
self._menu._actions.clear()
self._actionGroup = AppQActionGroup(self)
@@ -110,4 +119,5 @@ class RoutingAction(AppQAction):
def getGlobalAction(self):
# 2nd action
+ """Return global action."""
return self._menu.actions()[1]
diff --git a/Furious/TrayActions/Settings.py b/Furious/TrayActions/Settings.py
index 6cbeb783..b9bc28c8 100644
--- a/Furious/TrayActions/Settings.py
+++ b/Furious/TrayActions/Settings.py
@@ -15,6 +15,8 @@
# You should have received a copy of the GNU General Public License
# along with this program. If not, see .
+"""Implement tray actions for settings."""
+
from __future__ import annotations
from Furious.Frozenlib import *
@@ -53,7 +55,9 @@ _TRANSLATABLE_TUN_MODE = [
class TUNModeAction(AppQAction):
+ """Handle the TUN mode action."""
def __init__(self, **kwargs):
+ """Initialize the TUNModeAction."""
if PLATFORM == 'Linux':
super().__init__(_('TUN Mode'), **kwargs)
else:
@@ -67,6 +71,7 @@ class TUNModeAction(AppQAction):
self.setDisabled(True)
def triggeredCallback(self, checked):
+ """Handle activation of the action."""
if PLATFORM != 'Linux':
assert SystemRuntime.isAdmin()
@@ -79,10 +84,13 @@ class TUNModeAction(AppQAction):
class SettingsChildAction(AppQAction):
+ """Handle the settings child action."""
def __init__(self, *args, **kwargs):
+ """Initialize the SettingsChildAction."""
super().__init__(*args, **kwargs)
def triggeredCallback(self, checked):
+ """Handle activation of the action."""
if self.textCompare('Dark Mode'):
# Settings turn on/off order matters here
if checked:
@@ -170,7 +178,9 @@ class SettingsChildAction(AppQAction):
class SettingsAction(AppQAction):
+ """Handle the settings action."""
def __init__(self, **kwargs):
+ """Initialize the SettingsAction."""
if SystemRuntime.flatpakID():
tunActions = []
else:
@@ -263,4 +273,5 @@ class SettingsAction(AppQAction):
def getTUNModeAction(self) -> AppQAction:
# 1st action
+ """Return TUN mode action."""
return self._menu.actions()[0]
diff --git a/Furious/TrayActions/SystemProxy.py b/Furious/TrayActions/SystemProxy.py
index 94b02ae9..62bc1bbe 100644
--- a/Furious/TrayActions/SystemProxy.py
+++ b/Furious/TrayActions/SystemProxy.py
@@ -15,6 +15,8 @@
# You should have received a copy of the GNU General Public License
# along with this program. If not, see .
+"""Implement tray actions for system proxy."""
+
from __future__ import annotations
from Furious.Frozenlib import *
@@ -29,10 +31,13 @@ registerAppSettings(
class SystemProxyChildAction(AppQAction):
+ """Handle the system proxy child action."""
def __init__(self, *args, **kwargs):
+ """Initialize the SystemProxyChildAction."""
super().__init__(*args, **kwargs)
def triggeredCallback(self, checked):
+ """Handle activation of the action."""
if self.textCompare('Automatically Configure System Proxy'):
AppSettings.set('SystemProxyMode', AppBuiltinProxyMode.Auto.value)
elif self.textCompare('Do Not Change System Proxy'):
@@ -40,7 +45,9 @@ class SystemProxyChildAction(AppQAction):
class SystemProxyAction(AppQAction):
+ """Handle the system proxy action."""
def __init__(self, **kwargs):
+ """Initialize the SystemProxyAction."""
super().__init__(
_('System Proxy'),
icon=bootstrapIcon('hdd-network.svg'),
diff --git a/Furious/TrayActions/__init__.py b/Furious/TrayActions/__init__.py
index f42aa751..29f448cf 100644
--- a/Furious/TrayActions/__init__.py
+++ b/Furious/TrayActions/__init__.py
@@ -15,6 +15,8 @@
# You should have received a copy of the GNU General Public License
# along with this program. If not, see .
+"""Expose system-tray actions used by the application."""
+
from __future__ import annotations
from .Connect import *
diff --git a/Furious/Utility/AppMainProcess.py b/Furious/Utility/AppMainProcess.py
index d7e20cf3..2153fed4 100644
--- a/Furious/Utility/AppMainProcess.py
+++ b/Furious/Utility/AppMainProcess.py
@@ -15,6 +15,8 @@
# You should have received a copy of the GNU General Public License
# along with this program. If not, see .
+"""Provide process utilities for app main process."""
+
from __future__ import annotations
from Furious.Frozenlib import *
@@ -43,7 +45,9 @@ else:
class AppMainProcess(ProcessContext.Process):
+ """Represent app main process."""
def __init__(self, func: Callable[[], ApplicationFactory], **kwargs):
+ """Initialize the AppMainProcess."""
super().__init__(**kwargs)
self.startupTime = str(datetime.datetime.now()).replace(':', '')
@@ -54,6 +58,7 @@ class AppMainProcess(ProcessContext.Process):
self.application = None
def exceptHook(self, exceptionType, exceptionValue, tb):
+ """Handle except hook for the app main process."""
if logger.level < logging.CRITICAL:
traceback.print_exception(exceptionType, exceptionValue, tb)
@@ -72,6 +77,7 @@ class AppMainProcess(ProcessContext.Process):
sys.exit(exitcode)
def saveCrashLog(self, exceptionType, exceptionValue, tb):
+ """Save crash log."""
try:
os.mkdir(CRASH_LOG_DIR)
except FileExistsError:
@@ -104,11 +110,13 @@ class AppMainProcess(ProcessContext.Process):
pass
def handler(self, signum, frame):
+ """Handle r."""
logger.info(f'received signal {signal.Signals(signum).name}. Exit application')
self.application.exit()
def run(self):
+ """Run the app main process task."""
sys.excepthook = self.exceptHook
self.application = self.func()
diff --git a/Furious/Utility/__init__.py b/Furious/Utility/__init__.py
index 9a35def5..0470e27b 100644
--- a/Furious/Utility/__init__.py
+++ b/Furious/Utility/__init__.py
@@ -15,6 +15,8 @@
# You should have received a copy of the GNU General Public License
# along with this program. If not, see .
+"""Expose application process utilities."""
+
from __future__ import annotations
from .AppMainProcess import *
diff --git a/Furious/Version.py b/Furious/Version.py
index 64ce33a4..0db813f4 100644
--- a/Furious/Version.py
+++ b/Furious/Version.py
@@ -15,6 +15,8 @@
# You should have received a copy of the GNU General Public License
# along with this program. If not, see .
+"""Define the Furious package version."""
+
from __future__ import annotations
__version__ = '0.7.0'
diff --git a/Furious/Widget/Application.py b/Furious/Widget/Application.py
index 15844d0c..97fe83ce 100644
--- a/Furious/Widget/Application.py
+++ b/Furious/Widget/Application.py
@@ -15,6 +15,8 @@
# You should have received a copy of the GNU General Public License
# along with this program. If not, see .
+"""Provide widgets for application."""
+
from __future__ import annotations
from Furious.Frozenlib import *
@@ -48,36 +50,46 @@ registerAppSettings('LogViewerWidgetPointSizeTun_')
class SystemTrayUnavailable(Exception):
+ """Represent system tray unavailable."""
pass
class AppLogHandler(logging.Handler):
+ """Represent app log handler."""
def __init__(self, emitCallback):
+ """Initialize the AppLogHandler."""
super().__init__()
self.emitCallback = emitCallback
def emit(self, record):
+ """Emit the current app log handler event or message."""
if callable(self.emitCallback):
self.emitCallback(self.format(record))
class ApplicationExitHelper(QApplication):
+ """Represent application exit helper."""
def __init__(self, argv):
+ """Initialize the ApplicationExitHelper."""
super().__init__(argv)
# Exiting flag
self._exiting = False
def setExitingFlag(self, value: bool):
+ """Set exiting flag."""
self._exiting = value
def isExiting(self) -> bool:
+ """Return whether exiting."""
return self._exiting is True
class SingletonApplication(ApplicationExitHelper):
+ """Represent singleton application."""
def __init__(self, argv):
+ """Initialize the SingletonApplication."""
super().__init__(argv)
self.serverName = LOCAL_SERVER_NAME
@@ -86,6 +98,7 @@ class SingletonApplication(ApplicationExitHelper):
self.server = QLocalServer(self)
def hasRunningApp(self) -> bool:
+ """Return whether running app."""
self.socket.connectToServer(self.serverName)
if self.socket.waitForConnected(1000):
@@ -134,18 +147,23 @@ class SingletonApplication(ApplicationExitHelper):
@QtCore.Slot()
def handleNewConnection(self):
+ """Handle new connection."""
raise NotImplementedError
class ApplicationThemeDetector(QtCore.QObject):
+ """Represent application theme detector."""
themeChanged = QtCore.Signal(str)
def __init__(self, *args, **kwargs):
+ """Initialize the ApplicationThemeDetector."""
super().__init__(*args, **kwargs)
class Application(ApplicationFactory, SingletonApplication):
+ """Represent application."""
def __init__(self, argv):
+ """Initialize the Application."""
super().__init__(argv)
self.setApplicationName(APPLICATION_NAME)
@@ -187,11 +205,13 @@ class Application(ApplicationFactory, SingletonApplication):
@callRateLimited(maxCallPerSecond=2)
@QtCore.Slot()
def handleNewConnection(self):
+ """Handle new connection."""
socket = self.server.nextPendingConnection()
socket.readyRead.connect(functools.partial(self.handleNewData, socket))
@QtCore.Slot(QLocalSocket)
def handleNewData(self, socket: QLocalSocket):
+ """Handle new data."""
data = socket.readAll().data()
if isinstance(data, bytes):
@@ -216,6 +236,7 @@ class Application(ApplicationFactory, SingletonApplication):
pass
def configureLogging(self):
+ """Configure logging."""
self.logViewerWindowSelf = LogViewerWindow(
tabTitle=_('Furious Log'),
fontFamily=self.customFontName,
@@ -245,6 +266,7 @@ class Application(ApplicationFactory, SingletonApplication):
logging.raiseExceptions = False
def addCustomFont(self):
+ """Add custom font."""
fontFile = str(DATA_DIR / 'font' / 'CascadiaMono')
fontName = 'Cascadia Mono'
@@ -258,6 +280,7 @@ class Application(ApplicationFactory, SingletonApplication):
self.customFontLoadMsg = f'custom font {fontName} load failed'
def configureApplicationFont(self):
+ """Configure application font."""
font = self.font()
font.setPointSize(AppStyleSheet.FontPointSize)
@@ -266,6 +289,7 @@ class Application(ApplicationFactory, SingletonApplication):
@staticmethod
def addEnviron():
# Xray environment variables
+ """Add environ."""
os.environ['XRAY_LOCATION_ASSET'] = str(XRAY_ASSET_DIR)
if SystemRuntime.flatpakID():
@@ -276,23 +300,27 @@ class Application(ApplicationFactory, SingletonApplication):
def addStorage(self):
# Protected storage access
+ """Add storage."""
self._userActivatedItemIndex = Storage.UserActivatedItemIndex()
self._userServers = Storage.UserServers()
self._userSubs = Storage.UserSubs()
self._userTUNSettings = Storage.UserTUNSettings()
def isSystemTrayConnected(self):
+ """Return whether system tray connected."""
if isinstance(self.systemTray, SystemTrayIcon):
return self.systemTray.ConnectAction.isConnected()
else:
return False
def isDarkMode(self):
+ """Return whether dark mode."""
backgroudColor = self.palette().color(QPalette.ColorRole.Window)
return backgroudColor.lightness() < 128
def systemTheme(self) -> str:
+ """Return the system theme value used by the application."""
try:
theme = darkdetect.theme()
@@ -314,23 +342,28 @@ class Application(ApplicationFactory, SingletonApplication):
# else:
# return self.isDarkMode()
+ """Return whether dark mode enabled."""
return AppSettings.isStateON_('DarkMode')
def theme(self):
+ """Return the theme value used by the application."""
if self.isDarkModeEnabled():
return AppStyleSheet.Dark
return self.systemTheme()
def applyStyleSheetForTheme(self, theme):
+ """Handle apply style sheet for theme for the application."""
self.setStyleSheet(AppStyleSheet.forTheme(theme))
def switchToDarkMode(self):
+ """Handle switch to dark mode for the application."""
self.applyStyleSheetForTheme(AppStyleSheet.Dark)
Mixins.ThemeAware.callThemeChangedCallbackUnchecked(AppStyleSheet.Dark)
def switchToAutoMode(self):
+ """Handle switch to auto mode for the application."""
theme = self.systemTheme()
self.applyStyleSheetForTheme(theme)
@@ -339,6 +372,7 @@ class Application(ApplicationFactory, SingletonApplication):
@QtCore.Slot(str)
def handleSystemThemeChanged(self, theme):
+ """Handle system theme changed."""
if theme not in [AppStyleSheet.Dark, AppStyleSheet.Light]:
theme = self.systemTheme()
@@ -351,6 +385,7 @@ class Application(ApplicationFactory, SingletonApplication):
@callOnceOnly
@QtCore.Slot()
def cleanup():
+ """Release resources owned by the application."""
Mixins.CleanupOnExit.cleanupAll()
if AppSettings.get('SystemProxyMode') == AppBuiltinProxyMode.Auto.value:
@@ -364,6 +399,7 @@ class Application(ApplicationFactory, SingletonApplication):
@staticmethod
def setDockIconVisible(visible: bool):
+ """Set dock icon visible."""
if PLATFORM != 'Darwin':
return
@@ -375,6 +411,7 @@ class Application(ApplicationFactory, SingletonApplication):
def eventFilter(self, watched, event):
# Show Dock icon on macOS when window is shown
# and hide only when window is closed (not minimized)
+ """Return the event filter value used by the application."""
if PLATFORM == 'Darwin' and watched is self.mainWindow:
if event.type() == QtCore.QEvent.Type.Show:
self.setDockIconVisible(True)
@@ -386,6 +423,7 @@ class Application(ApplicationFactory, SingletonApplication):
return super().eventFilter(watched, event)
def installDockIconVisibilityFeature(self, remove=False):
+ """Handle install dock icon visibility feature for the application."""
if remove:
self.mainWindow.removeEventFilter(self)
self.setDockIconVisible(True)
@@ -397,6 +435,7 @@ class Application(ApplicationFactory, SingletonApplication):
self.setDockIconVisible(False)
def exit(self, exitcode=0):
+ """Shut down and exit the application."""
self.setExitingFlag(True)
self.threadPool.clear()
@@ -408,6 +447,7 @@ class Application(ApplicationFactory, SingletonApplication):
super().exit(exitcode)
def run(self):
+ """Run the application task."""
try:
if self.hasRunningApp():
# See: https://github.com/python/cpython/issues/79908
@@ -468,6 +508,7 @@ class Application(ApplicationFactory, SingletonApplication):
@QtCore.Slot()
def handleTimeout():
+ """Handle timeout."""
currentTheme = self.systemTheme()
if self.currentTheme != currentTheme:
@@ -483,6 +524,7 @@ class Application(ApplicationFactory, SingletonApplication):
logger.info('theme detect method uses listener implementation')
def listener(*args, **kwargs):
+ """Handle listener for the application."""
try:
darkdetect.listener(*args, **kwargs)
except NotImplementedError:
@@ -524,6 +566,7 @@ class Application(ApplicationFactory, SingletonApplication):
self.installDockIconVisibilityFeature()
def onApplicationStateChange(state):
+ """Handle on application state change for the application."""
if state == QtCore.Qt.ApplicationState.ApplicationActive:
if (
not self.mainWindow.isVisible()
diff --git a/Furious/Widget/ConnectProgressBar.py b/Furious/Widget/ConnectProgressBar.py
index a7347cd0..63deae6b 100644
--- a/Furious/Widget/ConnectProgressBar.py
+++ b/Furious/Widget/ConnectProgressBar.py
@@ -15,6 +15,8 @@
# You should have received a copy of the GNU General Public License
# along with this program. If not, see .
+"""Provide widgets for connect progress bar."""
+
from __future__ import annotations
from Furious.Frozenlib import *
@@ -28,7 +30,9 @@ __all__ = ['ConnectProgressBar']
class AutoUpdateProgressBar(Mixins.ConnectionAware, QProgressBar):
+ """Represent auto update progress bar."""
def __init__(self, **kwargs):
+ """Initialize the AutoUpdateProgressBar."""
super().__init__(**kwargs)
self.setRange(0, 100)
@@ -36,6 +40,7 @@ class AutoUpdateProgressBar(Mixins.ConnectionAware, QProgressBar):
@QtCore.Slot()
def update():
# Update the progress bar value
+ """Update the auto update progress bar."""
if self.value() < 90:
self.setValue(self.value() + 1)
@@ -49,13 +54,16 @@ class AutoUpdateProgressBar(Mixins.ConnectionAware, QProgressBar):
self.setStyleSheet(self.getStyleSheet())
def start(self, msec: int):
+ """Start the auto update progress bar."""
self.timer.start(msec)
def stop(self):
+ """Stop the auto update progress bar."""
self.timer.stop()
@staticmethod
def getStyleSheet():
+ """Return style sheet."""
return (
f'QProgressBar {{'
f' border-radius: 2px;'
@@ -73,14 +81,18 @@ class AutoUpdateProgressBar(Mixins.ConnectionAware, QProgressBar):
)
def disconnectedCallback(self):
+ """Update the auto update progress bar for a disconnected state."""
pass
def connectedCallback(self):
+ """Update the auto update progress bar for a connected state."""
pass
class ConnectProgressBar(Mixins.QTranslatable, Mixins.ConnectionAware, QWidget):
+ """Provide the connect progress bar widget."""
def __init__(self, parent=None):
+ """Initialize the ConnectProgressBar."""
super().__init__(parent)
self.setWindowTitle(_(APPLICATION_NAME))
@@ -97,19 +109,25 @@ class ConnectProgressBar(Mixins.QTranslatable, Mixins.ConnectionAware, QWidget):
self.setLayout(self._layout)
def setValue(self, value: int):
+ """Set value."""
self._widget.setValue(value)
def start(self, msec: int):
+ """Start the connect progress bar."""
self._widget.start(msec)
def stop(self):
+ """Stop the connect progress bar."""
self._widget.stop()
def disconnectedCallback(self):
+ """Update the connect progress bar for a disconnected state."""
self.setWindowIcon(AppHue.disconnectedWindowIcon())
def connectedCallback(self):
+ """Update the connect progress bar for a connected state."""
self.setWindowIcon(AppHue.connectedWindowIcon())
def retranslate(self):
+ """Refresh translated text for the connect progress bar."""
self.setWindowTitle(_(self.windowTitle()))
diff --git a/Furious/Widget/GuiCustomizeNetworkTest.py b/Furious/Widget/GuiCustomizeNetworkTest.py
index f1f13715..7962f74e 100644
--- a/Furious/Widget/GuiCustomizeNetworkTest.py
+++ b/Furious/Widget/GuiCustomizeNetworkTest.py
@@ -15,6 +15,8 @@
# You should have received a copy of the GNU General Public License
# along with this program. If not, see .
+"""Provide widgets for GUI customize network test."""
+
from __future__ import annotations
from Furious.Frozenlib import *
@@ -36,7 +38,9 @@ registerAppSettings('CustomNetworkConnectivityTestURL')
class GuiCustomizeNetworkTestDialog(AppQDialog):
+ """Present the GUI customize network test dialog."""
def __init__(self, *args, **kwargs):
+ """Initialize the GuiCustomizeNetworkTestDialog."""
super().__init__(*args, **kwargs)
self.setWindowTitle(_('Customize Network Test URL'))
@@ -100,9 +104,11 @@ class GuiCustomizeNetworkTestDialog(AppQDialog):
self.finished.connect(functools.partial(self.handleResultCode))
def setWidthAndHeight(self):
+ """Apply the default size for the GUI customize network test dialog."""
self.resize(656, 180)
def handleResultCode(self, code):
+ """Handle result code."""
for widget, info, settingsName, reset in [
(
self.speedTestURLEdit,
diff --git a/Furious/Widget/GuiCustomizeProxyBypass.py b/Furious/Widget/GuiCustomizeProxyBypass.py
index 2e0d23b0..a177ad61 100644
--- a/Furious/Widget/GuiCustomizeProxyBypass.py
+++ b/Furious/Widget/GuiCustomizeProxyBypass.py
@@ -15,6 +15,8 @@
# You should have received a copy of the GNU General Public License
# along with this program. If not, see .
+"""Provide widgets for GUI customize proxy bypass."""
+
from __future__ import annotations
from Furious.Frozenlib import *
@@ -35,7 +37,9 @@ registerAppSettings('CustomProxyBypass')
class GuiCustomizeProxyBypassDialog(AppQDialog):
+ """Present the GUI customize proxy bypass dialog."""
def __init__(self, *args, **kwargs):
+ """Initialize the GuiCustomizeProxyBypassDialog."""
super().__init__(*args, **kwargs)
self.setWindowTitle(_('Customize Proxy Bypass Address'))
@@ -79,14 +83,17 @@ class GuiCustomizeProxyBypassDialog(AppQDialog):
self.finished.connect(functools.partial(self.handleResultCode))
def setWidthAndHeight(self):
+ """Apply the default size for the GUI customize proxy bypass dialog."""
self.resize(656, 125)
def handleResultCode(self, code):
+ """Handle result code."""
if code == PySide6Legacy.enumValueWrapper(AppQDialog.DialogCode.Accepted):
oldValue = AppSettings.get('CustomProxyBypass')
newValue = self.proxyBypassEdit.text()
def writeNewSettings(value):
+ """Handle write new settings for the GUI customize proxy bypass dialog."""
logger.info(f'setting custom proxy bypass address to \'{value}\'')
AppSettings.set('CustomProxyBypass', value)
@@ -116,4 +123,5 @@ class GuiCustomizeProxyBypassDialog(AppQDialog):
@QtCore.Slot()
def handleResetButtonClicked(self):
+ """Handle reset button clicked."""
self.proxyBypassEdit.setText(PROXY_SERVER_BYPASS)
diff --git a/Furious/Widget/GuiHysteria1.py b/Furious/Widget/GuiHysteria1.py
index 9b85f8bb..65445b1c 100644
--- a/Furious/Widget/GuiHysteria1.py
+++ b/Furious/Widget/GuiHysteria1.py
@@ -15,6 +15,8 @@
# You should have received a copy of the GNU General Public License
# along with this program. If not, see .
+"""Provide widgets for GUI hysteria1."""
+
from __future__ import annotations
from Furious.Frozenlib import *
@@ -35,7 +37,9 @@ logger = logging.getLogger(__name__)
class GuiHy1ItemTextInput(GuiEditorItemTextInput):
+ """Represent GUI hy1 item text input."""
def __init__(self, *args, **kwargs):
+ """Initialize the GuiHy1ItemTextInput."""
key = kwargs.pop('key', '')
super().__init__(*args, **kwargs)
@@ -43,6 +47,7 @@ class GuiHy1ItemTextInput(GuiEditorItemTextInput):
self.key = key
def inputToFactory(self, config: ConfigFactory) -> bool:
+ """Apply the current editor value to the configuration."""
oldValue = config.get(self.key, '')
newValue = self.text()
@@ -63,6 +68,7 @@ class GuiHy1ItemTextInput(GuiEditorItemTextInput):
return True
def factoryToInput(self, config: ConfigFactory):
+ """Load the configuration value into the editor."""
try:
self.setText(config.get(self.key, ''))
except Exception:
@@ -72,12 +78,15 @@ class GuiHy1ItemTextInput(GuiEditorItemTextInput):
class GuiHy1ItemBasicProtocol(GuiEditorItemTextComboBox):
+ """Represent GUI hy1 item basic protocol."""
def __init__(self, *args, **kwargs):
+ """Initialize the GuiHy1ItemBasicProtocol."""
super().__init__(*args, **kwargs)
self.addItems(['', 'udp', 'wechat-video', 'faketcp'])
def inputToFactory(self, config: ConfigFactory) -> bool:
+ """Apply the current editor value to the configuration."""
oldProtocol = config.get('protocol', '')
newProtocol = self.text()
@@ -94,6 +103,7 @@ class GuiHy1ItemBasicProtocol(GuiEditorItemTextComboBox):
return True
def factoryToInput(self, config: ConfigFactory):
+ """Load the configuration value into the editor."""
try:
self.setText(config.get('protocol', 'udp'))
except Exception:
@@ -103,13 +113,16 @@ class GuiHy1ItemBasicProtocol(GuiEditorItemTextComboBox):
class GuiHy1ItemSpeedUpMbps(GuiEditorItemTextSpinBox):
+ """Represent GUI hy1 item speed up mbps."""
def __init__(self, *args, **kwargs):
+ """Initialize the GuiHy1ItemSpeedUpMbps."""
super().__init__(*args, **kwargs)
# Range
self.setRange(0, 1048576)
def inputToFactory(self, config: ConfigFactory) -> bool:
+ """Apply the current editor value to the configuration."""
oldUpMbps = config.get('up_mbps')
newUpMbps = self.value()
@@ -126,6 +139,7 @@ class GuiHy1ItemSpeedUpMbps(GuiEditorItemTextSpinBox):
return True
def factoryToInput(self, config: ConfigFactory):
+ """Load the configuration value into the editor."""
try:
self.setValue(config.get('up_mbps'))
except Exception:
@@ -135,13 +149,16 @@ class GuiHy1ItemSpeedUpMbps(GuiEditorItemTextSpinBox):
class GuiHy1ItemSpeedDownMbps(GuiEditorItemTextSpinBox):
+ """Represent GUI hy1 item speed down mbps."""
def __init__(self, *args, **kwargs):
+ """Initialize the GuiHy1ItemSpeedDownMbps."""
super().__init__(*args, **kwargs)
# Range
self.setRange(0, 1048576)
def inputToFactory(self, config: ConfigFactory) -> bool:
+ """Apply the current editor value to the configuration."""
oldDownMbps = config.get('down_mbps')
newDownMbps = self.value()
@@ -158,6 +175,7 @@ class GuiHy1ItemSpeedDownMbps(GuiEditorItemTextSpinBox):
return True
def factoryToInput(self, config: ConfigFactory):
+ """Load the configuration value into the editor."""
try:
self.setValue(config.get('down_mbps'))
except Exception:
@@ -167,10 +185,13 @@ class GuiHy1ItemSpeedDownMbps(GuiEditorItemTextSpinBox):
class GuiHy1ItemTLSInsecure(GuiEditorItemTextCheckBox):
+ """Represent GUI hy1 item TLS insecure."""
def __init__(self, *args, **kwargs):
+ """Initialize the GuiHy1ItemTLSInsecure."""
super().__init__(*args, **kwargs)
def inputToFactory(self, config: ConfigFactory) -> bool:
+ """Apply the current editor value to the configuration."""
try:
oldChecked = config.get('insecure')
except Exception:
@@ -202,6 +223,7 @@ class GuiHy1ItemTLSInsecure(GuiEditorItemTextCheckBox):
return False
def factoryToInput(self, config: ConfigFactory):
+ """Load the configuration value into the editor."""
try:
checked = config['insecure']
except Exception:
@@ -216,13 +238,16 @@ class GuiHy1ItemTLSInsecure(GuiEditorItemTextCheckBox):
class GuiHy1ProjectWebsiteURL(AppQLabel):
+ """Represent GUI hy1 project website URL."""
def __init__(self, *args, **kwargs):
+ """Initialize the GuiHy1ProjectWebsiteURL."""
super().__init__(*args, **kwargs)
self.setWebsiteURL()
self.linkActivated.connect(self.handleLinkActivated)
def setWebsiteURL(self):
+ """Set website URL."""
self.setText(
'
'
''
@@ -233,20 +258,25 @@ class GuiHy1ProjectWebsiteURL(AppQLabel):
@staticmethod
def handleLinkActivated(link: str):
+ """Handle link activated."""
if QDesktopServices.openUrl(QtCore.QUrl(link)):
logger.info(f'open link \'{link}\' success')
else:
logger.error(f'open link \'{link}\' failed')
def retranslate(self):
+ """Refresh translated text for the GUI hy1 project website URL."""
self.setWebsiteURL()
class GuiHy1GroupBoxBasic(GuiEditorWidgetQGroupBox):
+ """Represent GUI hy1 group box basic."""
def __init__(self, **kwargs):
+ """Initialize the GuiHy1GroupBoxBasic."""
super().__init__(_('Basic Configuration'), **kwargs)
def containerSequence(self):
+ """Return the editor item containers in display order."""
return [
GuiEditorItemBasicRemark(title=_('Remark')),
GuiHy1ItemTextInput(title=_('Server'), key='server'),
@@ -257,10 +287,13 @@ class GuiHy1GroupBoxBasic(GuiEditorWidgetQGroupBox):
class GuiHy1GroupBoxProxy(GuiEditorWidgetQGroupBox):
+ """Represent GUI hy1 group box proxy."""
def __init__(self, **kwargs):
+ """Initialize the GuiHy1GroupBoxProxy."""
super().__init__(_('Proxy'), **kwargs)
def containerSequence(self):
+ """Return the editor item containers in display order."""
return [
GuiEditorItemProxyHttp(title='http', translatable=False),
GuiEditorItemProxySocks(title='socks', translatable=False),
@@ -268,10 +301,13 @@ class GuiHy1GroupBoxProxy(GuiEditorWidgetQGroupBox):
class GuiHy1GroupBoxSpeed(GuiEditorWidgetQGroupBox):
+ """Represent GUI hy1 group box speed."""
def __init__(self, **kwargs):
+ """Initialize the GuiHy1GroupBoxSpeed."""
super().__init__(_('Speed'), **kwargs)
def containerSequence(self):
+ """Return the editor item containers in display order."""
return [
GuiHy1ItemSpeedUpMbps(title='up_mbps', translatable=False),
GuiHy1ItemSpeedDownMbps(title='down_mbps', translatable=False),
@@ -279,12 +315,15 @@ class GuiHy1GroupBoxSpeed(GuiEditorWidgetQGroupBox):
class GuiHy1GroupBoxTLS(GuiEditorWidgetQGroupBox):
+ """Represent GUI hy1 group box TLS."""
def __init__(self, **kwargs):
+ """Initialize the GuiHy1GroupBoxTLS."""
super().__init__('TLS', **kwargs)
self.translatable = False
def containerSequence(self):
+ """Return the editor item containers in display order."""
return [
GuiHy1ItemTextInput(title='sni', translatable=False, key='server_name'),
GuiHy1ItemTextInput(title='alpn', translatable=False, key='alpn'),
@@ -294,7 +333,9 @@ class GuiHy1GroupBoxTLS(GuiEditorWidgetQGroupBox):
class GuiHy1GroupBoxOther(GuiEditorItemFactory, AppQGroupBox):
+ """Represent GUI hy1 group box other."""
def __init__(self, **kwargs):
+ """Initialize the GuiHy1GroupBoxOther."""
super().__init__(_('Other'), **kwargs)
self._website = GuiHy1ProjectWebsiteURL()
@@ -305,20 +346,25 @@ class GuiHy1GroupBoxOther(GuiEditorItemFactory, AppQGroupBox):
self.setLayout(layout)
def inputToFactory(self, config: ConfigFactory) -> bool:
+ """Apply the current editor value to the configuration."""
return False
def factoryToInput(self, config: ConfigFactory):
+ """Load the configuration value into the editor."""
pass
class GuiHysteria1(GuiEditorWidgetQDialog):
+ """Represent GUI hysteria1."""
def __init__(self, *args, **kwargs):
+ """Initialize the GuiHysteria1."""
super().__init__(*args, **kwargs)
self.setTabText(Protocol.Hysteria1.value)
@functools.lru_cache(None)
def groupBoxSequence(self):
+ """Return the configuration group boxes in display order."""
return [
GuiHy1GroupBoxBasic(),
GuiHy1GroupBoxProxy(),
diff --git a/Furious/Widget/GuiHysteria2.py b/Furious/Widget/GuiHysteria2.py
index f01d39e2..c1492aaa 100644
--- a/Furious/Widget/GuiHysteria2.py
+++ b/Furious/Widget/GuiHysteria2.py
@@ -15,6 +15,8 @@
# You should have received a copy of the GNU General Public License
# along with this program. If not, see .
+"""Provide widgets for GUI hysteria2."""
+
from __future__ import annotations
from Furious.Frozenlib import *
@@ -37,10 +39,13 @@ HY2_OBFS_TYPES = ['', 'salamander', 'gecko']
class GuiHy2ItemBasicServer(GuiEditorItemTextInput):
+ """Represent GUI hy2 item basic server."""
def __init__(self, *args, **kwargs):
+ """Initialize the GuiHy2ItemBasicServer."""
super().__init__(*args, **kwargs)
def inputToFactory(self, config: ConfigFactory) -> bool:
+ """Apply the current editor value to the configuration."""
oldServer = config.get('server', '')
newServer = self.text()
@@ -57,6 +62,7 @@ class GuiHy2ItemBasicServer(GuiEditorItemTextInput):
return True
def factoryToInput(self, config: ConfigFactory):
+ """Load the configuration value into the editor."""
try:
self.setText(config.get('server', ''))
except Exception:
@@ -66,10 +72,13 @@ class GuiHy2ItemBasicServer(GuiEditorItemTextInput):
class GuiHy2ItemBasicAuth(GuiEditorItemTextInput):
+ """Represent GUI hy2 item basic auth."""
def __init__(self, *args, **kwargs):
+ """Initialize the GuiHy2ItemBasicAuth."""
super().__init__(*args, **kwargs)
def inputToFactory(self, config: ConfigFactory) -> bool:
+ """Apply the current editor value to the configuration."""
oldAuth = config.get('auth')
newAuth = self.text()
@@ -94,6 +103,7 @@ class GuiHy2ItemBasicAuth(GuiEditorItemTextInput):
return True
def factoryToInput(self, config: ConfigFactory):
+ """Load the configuration value into the editor."""
try:
self.setText(config.get('auth', ''))
except Exception:
@@ -103,9 +113,11 @@ class GuiHy2ItemBasicAuth(GuiEditorItemTextInput):
class GuiHy2ItemBasicCongestionComboBox(GuiEditorItemTextComboBox):
+ """Represent GUI hy2 item basic congestion combo box."""
CONGESTION_KEYS = ['type', 'bbrProfile']
def __init__(self, *args, **kwargs):
+ """Initialize the GuiHy2ItemBasicCongestionComboBox."""
key = kwargs.pop('key', '')
assert key in self.CONGESTION_KEYS
@@ -125,6 +137,7 @@ class GuiHy2ItemBasicCongestionComboBox(GuiEditorItemTextComboBox):
raise
def inputToFactory(self, config: ConfigFactory) -> bool:
+ """Apply the current editor value to the configuration."""
newValue = self.text()
if newValue == '':
@@ -179,6 +192,7 @@ class GuiHy2ItemBasicCongestionComboBox(GuiEditorItemTextComboBox):
return False
def factoryToInput(self, config: ConfigFactory):
+ """Load the configuration value into the editor."""
try:
value = config['congestion'][self.key]
except Exception:
@@ -190,12 +204,15 @@ class GuiHy2ItemBasicCongestionComboBox(GuiEditorItemTextComboBox):
class GuiHy2ItemObfsType(GuiEditorItemTextComboBox):
+ """Represent GUI hy2 item obfs type."""
def __init__(self, *args, **kwargs):
+ """Initialize the GuiHy2ItemObfsType."""
super().__init__(*args, **kwargs)
self.addItems(HY2_OBFS_TYPES)
def inputToFactory(self, config: ConfigFactory) -> bool:
+ """Apply the current editor value to the configuration."""
try:
oldObfsType = config['obfs']['type']
except Exception:
@@ -229,6 +246,7 @@ class GuiHy2ItemObfsType(GuiEditorItemTextComboBox):
return False
def factoryToInput(self, config: ConfigFactory):
+ """Load the configuration value into the editor."""
try:
obfsType = config['obfs']['type']
except Exception:
@@ -243,12 +261,15 @@ class GuiHy2ItemObfsType(GuiEditorItemTextComboBox):
class GuiHy2ItemObfsPassword(GuiEditorItemTextInput):
+ """Represent GUI hy2 item obfs password."""
def __init__(self, *args, **kwargs):
+ """Initialize the GuiHy2ItemObfsPassword."""
self.obfsType = kwargs.pop('obfsType', 'salamander')
super().__init__(*args, **kwargs)
def inputToFactory(self, config: ConfigFactory) -> bool:
+ """Apply the current editor value to the configuration."""
newObfsPassword = self.text()
try:
@@ -288,6 +309,7 @@ class GuiHy2ItemObfsPassword(GuiEditorItemTextInput):
return modified
def factoryToInput(self, config: ConfigFactory):
+ """Load the configuration value into the editor."""
try:
obfsType = config['obfs']['type']
except Exception:
@@ -311,7 +333,9 @@ class GuiHy2ItemObfsPassword(GuiEditorItemTextInput):
class GuiHy2ItemObfsPacketSize(GuiEditorItemTextSpinBox):
+ """Represent GUI hy2 item obfs packet size."""
def __init__(self, *args, **kwargs):
+ """Initialize the GuiHy2ItemObfsPacketSize."""
self.obfsType, self.key, self.default = (
kwargs.pop('obfsType', 'gecko'),
kwargs.pop('key', ''),
@@ -324,6 +348,7 @@ class GuiHy2ItemObfsPacketSize(GuiEditorItemTextSpinBox):
self.setValue(self.default)
def inputToFactory(self, config: ConfigFactory) -> bool:
+ """Apply the current editor value to the configuration."""
try:
obfsType = config['obfs']['type']
except Exception:
@@ -351,6 +376,7 @@ class GuiHy2ItemObfsPacketSize(GuiEditorItemTextSpinBox):
return False
def factoryToInput(self, config: ConfigFactory):
+ """Load the configuration value into the editor."""
try:
obfsType = config['obfs']['type']
except Exception:
@@ -377,16 +403,20 @@ class GuiHy2ItemObfsPacketSize(GuiEditorItemTextSpinBox):
class GuiHy2PageObfsXXX(GuiEditorWidgetQWidget):
+ """Represent GUI hy2 page obfs xxx."""
def __init__(self, *args, **kwargs):
+ """Initialize the GuiHy2PageObfsXXX."""
super().__init__(*args, **kwargs)
def setObfsTypeText(self, text: str):
+ """Set obfs type text."""
obfsType = self._containers[0]
if isinstance(obfsType, GuiEditorItemTextComboBox):
obfsType.setText(text)
def connectActivated(self, func):
+ """Connect activated."""
obfsType = self._containers[0]
if isinstance(obfsType, GuiEditorItemTextComboBox):
@@ -394,14 +424,18 @@ class GuiHy2PageObfsXXX(GuiEditorWidgetQWidget):
class GuiHy2PageObfsEmpty(GuiHy2PageObfsXXX):
+ """Represent GUI hy2 page obfs empty."""
def containerSequence(self):
+ """Return the editor item containers in display order."""
return [
GuiHy2ItemObfsType(title='obfs-type', translatable=False),
]
class GuiHy2PageObfsSalamander(GuiHy2PageObfsXXX):
+ """Represent GUI hy2 page obfs salamander."""
def containerSequence(self):
+ """Return the editor item containers in display order."""
return [
GuiHy2ItemObfsType(title='obfs-type', translatable=False),
GuiHy2ItemObfsPassword(
@@ -413,7 +447,9 @@ class GuiHy2PageObfsSalamander(GuiHy2PageObfsXXX):
class GuiHy2PageObfsGecko(GuiHy2PageObfsXXX):
+ """Represent GUI hy2 page obfs gecko."""
def __init__(self, *args, **kwargs):
+ """Initialize the GuiHy2PageObfsGecko."""
super().__init__(*args, **kwargs)
self.minPacketSizeItem = self._containers[2]
@@ -425,6 +461,7 @@ class GuiHy2PageObfsGecko(GuiHy2PageObfsXXX):
self.handleMinPacketSizeChanged(self.minPacketSizeItem.value())
def containerSequence(self):
+ """Return the editor item containers in display order."""
return [
GuiHy2ItemObfsType(title='obfs-type', translatable=False),
GuiHy2ItemObfsPassword(
@@ -447,6 +484,7 @@ class GuiHy2PageObfsGecko(GuiHy2PageObfsXXX):
]
def handleMinPacketSizeChanged(self, value: int):
+ """Handle min packet size changed."""
self.maxPacketSizeItem._input.setMinimum(value)
if self.maxPacketSizeItem.value() < value:
@@ -454,7 +492,9 @@ class GuiHy2PageObfsGecko(GuiHy2PageObfsXXX):
class GuiHy2ObfsPageStackedWidget(QStackedWidget):
+ """Provide the GUI hy2 obfs page stacked widget."""
def __init__(self, *args, **kwargs):
+ """Initialize the GuiHy2ObfsPageStackedWidget."""
super().__init__(*args, **kwargs)
self._pages = [
@@ -467,15 +507,19 @@ class GuiHy2ObfsPageStackedWidget(QStackedWidget):
self.addWidget(page)
def page(self, index: int) -> GuiHy2PageObfsXXX:
+ """Return the page value."""
return self._pages[index]
def connectActivated(self, func):
+ """Connect activated."""
for page in self._pages:
page.connectActivated(func)
class GuiHy2ItemTLSTextInput(GuiEditorItemTextInput):
+ """Represent GUI hy2 item TLS text input."""
def __init__(self, *args, **kwargs):
+ """Initialize the GuiHy2ItemTLSTextInput."""
key = kwargs.pop('key', '')
super().__init__(*args, **kwargs)
@@ -483,6 +527,7 @@ class GuiHy2ItemTLSTextInput(GuiEditorItemTextInput):
self.key = key
def inputToFactory(self, config: ConfigFactory) -> bool:
+ """Apply the current editor value to the configuration."""
newValue = self.text()
if newValue == '':
@@ -539,6 +584,7 @@ class GuiHy2ItemTLSTextInput(GuiEditorItemTextInput):
return False
def factoryToInput(self, config: ConfigFactory):
+ """Load the configuration value into the editor."""
try:
value = config['tls'][self.key]
except Exception:
@@ -550,10 +596,13 @@ class GuiHy2ItemTLSTextInput(GuiEditorItemTextInput):
class GuiHy2ItemTLSInsecure(GuiEditorItemTextCheckBox):
+ """Represent GUI hy2 item TLS insecure."""
def __init__(self, *args, **kwargs):
+ """Initialize the GuiHy2ItemTLSInsecure."""
super().__init__(*args, **kwargs)
def inputToFactory(self, config: ConfigFactory) -> bool:
+ """Apply the current editor value to the configuration."""
try:
oldChecked = config['tls']['insecure']
except Exception:
@@ -585,6 +634,7 @@ class GuiHy2ItemTLSInsecure(GuiEditorItemTextCheckBox):
return False
def factoryToInput(self, config: ConfigFactory):
+ """Load the configuration value into the editor."""
try:
checked = config['tls']['insecure']
except Exception:
@@ -599,10 +649,13 @@ class GuiHy2ItemTLSInsecure(GuiEditorItemTextCheckBox):
class GuiHy2GroupBoxBasic(GuiEditorWidgetQGroupBox):
+ """Represent GUI hy2 group box basic."""
def __init__(self, **kwargs):
+ """Initialize the GuiHy2GroupBoxBasic."""
super().__init__(_('Basic Configuration'), **kwargs)
def containerSequence(self):
+ """Return the editor item containers in display order."""
return [
GuiEditorItemBasicRemark(title=_('Remark')),
GuiHy2ItemBasicServer(title=_('Server')),
@@ -617,10 +670,13 @@ class GuiHy2GroupBoxBasic(GuiEditorWidgetQGroupBox):
class GuiHy2GroupBoxProxy(GuiEditorWidgetQGroupBox):
+ """Represent GUI hy2 group box proxy."""
def __init__(self, **kwargs):
+ """Initialize the GuiHy2GroupBoxProxy."""
super().__init__(_('Proxy'), **kwargs)
def containerSequence(self):
+ """Return the editor item containers in display order."""
return [
GuiEditorItemProxyHttp(title='http', translatable=False),
GuiEditorItemProxySocks(title='socks', translatable=False),
@@ -628,7 +684,9 @@ class GuiHy2GroupBoxProxy(GuiEditorWidgetQGroupBox):
class GuiHy2GroupBoxObfs(GuiEditorItemFactory, AppQGroupBox):
+ """Represent GUI hy2 group box obfs."""
def __init__(self, **kwargs):
+ """Initialize the GuiHy2GroupBoxObfs."""
translatable = kwargs.pop('translatable', False)
super().__init__('obfs', **kwargs, translatable=translatable)
@@ -644,15 +702,19 @@ class GuiHy2GroupBoxObfs(GuiEditorItemFactory, AppQGroupBox):
self.setLayout(layout)
def currentIndex(self) -> int:
+ """Return the current index value."""
return self._widget.currentIndex()
def setCurrentIndex(self, index: int):
+ """Set current index."""
self._widget.setCurrentIndex(index)
def page(self, index: int) -> GuiHy2PageObfsXXX:
+ """Return the page value."""
return self._widget.page(index)
def handleActivated(self, index: int):
+ """Handle activated."""
page = self.page(index)
page.factoryToInput(self._config)
page.setObfsTypeText(HY2_OBFS_TYPES[index])
@@ -660,6 +722,7 @@ class GuiHy2GroupBoxObfs(GuiEditorItemFactory, AppQGroupBox):
self.setCurrentIndex(index)
def inputToFactory(self, config: ConfigFactory) -> bool:
+ """Apply the current editor value to the configuration."""
oldObfs = config.get('obfs')
oldObfsType = ''
@@ -694,6 +757,7 @@ class GuiHy2GroupBoxObfs(GuiEditorItemFactory, AppQGroupBox):
return modified
def factoryToInput(self, config: ConfigFactory):
+ """Load the configuration value into the editor."""
self._config = config
try:
@@ -714,12 +778,15 @@ class GuiHy2GroupBoxObfs(GuiEditorItemFactory, AppQGroupBox):
class GuiHy2GroupBoxTLS(GuiEditorWidgetQGroupBox):
+ """Represent GUI hy2 group box TLS."""
def __init__(self, **kwargs):
+ """Initialize the GuiHy2GroupBoxTLS."""
super().__init__('TLS', **kwargs)
self.translatable = False
def containerSequence(self):
+ """Return the editor item containers in display order."""
return [
GuiHy2ItemTLSTextInput(title='sni', key='sni', translatable=False),
GuiHy2ItemTLSTextInput(
@@ -731,13 +798,16 @@ class GuiHy2GroupBoxTLS(GuiEditorWidgetQGroupBox):
class GuiHy2ProjectWebsiteURL(AppQLabel):
+ """Represent GUI hy2 project website URL."""
def __init__(self, *args, **kwargs):
+ """Initialize the GuiHy2ProjectWebsiteURL."""
super().__init__(*args, **kwargs)
self.setWebsiteURL()
self.linkActivated.connect(self.handleLinkActivated)
def setWebsiteURL(self):
+ """Set website URL."""
self.setText(
'
'
''
@@ -748,17 +818,21 @@ class GuiHy2ProjectWebsiteURL(AppQLabel):
@staticmethod
def handleLinkActivated(link: str):
+ """Handle link activated."""
if QDesktopServices.openUrl(QtCore.QUrl(link)):
logger.info(f'open link \'{link}\' success')
else:
logger.error(f'open link \'{link}\' failed')
def retranslate(self):
+ """Refresh translated text for the GUI hy2 project website URL."""
self.setWebsiteURL()
class GuiHy2GroupBoxOther(GuiEditorItemFactory, AppQGroupBox):
+ """Represent GUI hy2 group box other."""
def __init__(self, **kwargs):
+ """Initialize the GuiHy2GroupBoxOther."""
super().__init__(_('Other'), **kwargs)
self._website = GuiHy2ProjectWebsiteURL()
@@ -769,20 +843,25 @@ class GuiHy2GroupBoxOther(GuiEditorItemFactory, AppQGroupBox):
self.setLayout(layout)
def inputToFactory(self, config: ConfigFactory) -> bool:
+ """Apply the current editor value to the configuration."""
return False
def factoryToInput(self, config: ConfigFactory):
+ """Load the configuration value into the editor."""
pass
class GuiHysteria2(GuiEditorWidgetQDialog):
+ """Represent GUI hysteria2."""
def __init__(self, *args, **kwargs):
+ """Initialize the GuiHysteria2."""
super().__init__(*args, **kwargs)
self.setTabText(Protocol.Hysteria2.value)
@functools.lru_cache(None)
def groupBoxSequence(self):
+ """Return the configuration group boxes in display order."""
return [
GuiHy2GroupBoxBasic(),
GuiHy2GroupBoxProxy(),
diff --git a/Furious/Widget/GuiShadowsocks.py b/Furious/Widget/GuiShadowsocks.py
index a3dd919e..ad071fab 100644
--- a/Furious/Widget/GuiShadowsocks.py
+++ b/Furious/Widget/GuiShadowsocks.py
@@ -15,6 +15,8 @@
# You should have received a copy of the GNU General Public License
# along with this program. If not, see .
+"""Provide widgets for GUI shadowsocks."""
+
from __future__ import annotations
from Furious.Frozenlib import *
@@ -37,7 +39,9 @@ getProxyOutboundServer = functools.partial(
class GuiSSItemTextInput(GuiEditorItemTextInput):
+ """Represent GUI ss item text input."""
def __init__(self, *args, **kwargs):
+ """Initialize the GuiSSItemTextInput."""
key = kwargs.pop('key', '')
super().__init__(*args, **kwargs)
@@ -45,6 +49,7 @@ class GuiSSItemTextInput(GuiEditorItemTextInput):
self.key = key
def inputToFactory(self, config: ConfigFactory) -> bool:
+ """Apply the current editor value to the configuration."""
proxyOutboundServer = getProxyOutboundServer(config)
oldValue = proxyOutboundServer.get(self.key, '')
@@ -67,6 +72,7 @@ class GuiSSItemTextInput(GuiEditorItemTextInput):
return True
def factoryToInput(self, config: ConfigFactory):
+ """Load the configuration value into the editor."""
try:
proxyOutboundServer = getProxyOutboundServer(config)
@@ -78,13 +84,16 @@ class GuiSSItemTextInput(GuiEditorItemTextInput):
class GuiSSItemBasicPort(GuiEditorItemTextSpinBox):
+ """Represent GUI ss item basic port."""
def __init__(self, *args, **kwargs):
+ """Initialize the GuiSSItemBasicPort."""
super().__init__(*args, **kwargs)
# Range
self.setRange(0, 65535)
def inputToFactory(self, config: ConfigFactory) -> bool:
+ """Apply the current editor value to the configuration."""
proxyOutboundServer = getProxyOutboundServer(config)
oldPort = proxyOutboundServer.get('port')
@@ -103,6 +112,7 @@ class GuiSSItemBasicPort(GuiEditorItemTextSpinBox):
return True
def factoryToInput(self, config: ConfigFactory):
+ """Load the configuration value into the editor."""
try:
proxyOutboundServer = getProxyOutboundServer(config)
@@ -114,7 +124,9 @@ class GuiSSItemBasicPort(GuiEditorItemTextSpinBox):
class GuiSSItemBasicMethod(GuiEditorItemTextComboBox):
+ """Represent GUI ss item basic method."""
def __init__(self, *args, **kwargs):
+ """Initialize the GuiSSItemBasicMethod."""
super().__init__(*args, **kwargs)
self.addItems(
@@ -135,6 +147,7 @@ class GuiSSItemBasicMethod(GuiEditorItemTextComboBox):
)
def inputToFactory(self, config: ConfigFactory) -> bool:
+ """Apply the current editor value to the configuration."""
proxyOutboundServer = getProxyOutboundServer(config)
oldMethod = proxyOutboundServer.get('method', '')
@@ -153,6 +166,7 @@ class GuiSSItemBasicMethod(GuiEditorItemTextComboBox):
return True
def factoryToInput(self, config: ConfigFactory):
+ """Load the configuration value into the editor."""
try:
proxyOutboundServer = getProxyOutboundServer(config)
@@ -164,10 +178,13 @@ class GuiSSItemBasicMethod(GuiEditorItemTextComboBox):
class GuiSSGroupBoxBasic(GuiEditorWidgetQGroupBox):
+ """Represent GUI ss group box basic."""
def __init__(self, **kwargs):
+ """Initialize the GuiSSGroupBoxBasic."""
super().__init__(_('Basic Configuration'), **kwargs)
def containerSequence(self):
+ """Return the editor item containers in display order."""
return [
GuiEditorItemBasicRemark(title=_('Remark')),
GuiSSItemTextInput(title=_('Address'), key='address'),
@@ -178,10 +195,13 @@ class GuiSSGroupBoxBasic(GuiEditorWidgetQGroupBox):
class GuiSSGroupBoxProxy(GuiEditorWidgetQGroupBox):
+ """Represent GUI ss group box proxy."""
def __init__(self, **kwargs):
+ """Initialize the GuiSSGroupBoxProxy."""
super().__init__(_('Proxy'), **kwargs)
def containerSequence(self):
+ """Return the editor item containers in display order."""
return [
GuiEditorItemProxyHttp(title='http', translatable=False),
GuiEditorItemProxySocks(title='socks', translatable=False),
@@ -189,13 +209,16 @@ class GuiSSGroupBoxProxy(GuiEditorWidgetQGroupBox):
class GuiShadowsocks(GuiEditorWidgetQDialog):
+ """Represent GUI shadowsocks."""
def __init__(self, *args, **kwargs):
+ """Initialize the GuiShadowsocks."""
super().__init__(*args, **kwargs)
self.setTabText(Protocol.Shadowsocks.value)
@functools.lru_cache(None)
def groupBoxSequence(self):
+ """Return the configuration group boxes in display order."""
return [
GuiSSGroupBoxBasic(),
GuiSSGroupBoxProxy(),
diff --git a/Furious/Widget/GuiSocks.py b/Furious/Widget/GuiSocks.py
index 27457e09..97a5c581 100644
--- a/Furious/Widget/GuiSocks.py
+++ b/Furious/Widget/GuiSocks.py
@@ -15,6 +15,8 @@
# You should have received a copy of the GNU General Public License
# along with this program. If not, see .
+"""Provide widgets for GUI SOCKS."""
+
from __future__ import annotations
from Furious.Frozenlib import *
@@ -37,7 +39,9 @@ getProxyOutboundServer = functools.partial(
class GuiSocksItemTextInput(GuiEditorItemTextInput):
+ """Represent GUI SOCKS item text input."""
def __init__(self, *args, **kwargs):
+ """Initialize the GuiSocksItemTextInput."""
key = kwargs.pop('key', '')
super().__init__(*args, **kwargs)
@@ -45,6 +49,7 @@ class GuiSocksItemTextInput(GuiEditorItemTextInput):
self.key = key
def inputToFactory(self, config: ConfigFactory) -> bool:
+ """Apply the current editor value to the configuration."""
proxyOutboundServer = getProxyOutboundServer(config)
oldValue = proxyOutboundServer.get(self.key, '')
@@ -66,6 +71,7 @@ class GuiSocksItemTextInput(GuiEditorItemTextInput):
return True
def factoryToInput(self, config: ConfigFactory):
+ """Load the configuration value into the editor."""
try:
proxyOutboundServer = getProxyOutboundServer(config)
@@ -77,12 +83,15 @@ class GuiSocksItemTextInput(GuiEditorItemTextInput):
class GuiSocksItemBasicPort(GuiEditorItemTextSpinBox):
+ """Represent GUI SOCKS item basic port."""
def __init__(self, *args, **kwargs):
+ """Initialize the GuiSocksItemBasicPort."""
super().__init__(*args, **kwargs)
self.setRange(0, 65535)
def inputToFactory(self, config: ConfigFactory) -> bool:
+ """Apply the current editor value to the configuration."""
proxyOutboundServer = getProxyOutboundServer(config)
oldPort = proxyOutboundServer.get('port')
@@ -101,6 +110,7 @@ class GuiSocksItemBasicPort(GuiEditorItemTextSpinBox):
return True
def factoryToInput(self, config: ConfigFactory):
+ """Load the configuration value into the editor."""
try:
proxyOutboundServer = getProxyOutboundServer(config)
@@ -112,10 +122,13 @@ class GuiSocksItemBasicPort(GuiEditorItemTextSpinBox):
class GuiSocksGroupBoxBasic(GuiEditorWidgetQGroupBox):
+ """Represent GUI SOCKS group box basic."""
def __init__(self, **kwargs):
+ """Initialize the GuiSocksGroupBoxBasic."""
super().__init__(_('Basic Configuration'), **kwargs)
def containerSequence(self):
+ """Return the editor item containers in display order."""
return [
GuiEditorItemBasicRemark(title=_('Remark')),
GuiSocksItemTextInput(title=_('Address'), key='address'),
@@ -126,10 +139,13 @@ class GuiSocksGroupBoxBasic(GuiEditorWidgetQGroupBox):
class GuiSocksGroupBoxProxy(GuiEditorWidgetQGroupBox):
+ """Represent GUI SOCKS group box proxy."""
def __init__(self, **kwargs):
+ """Initialize the GuiSocksGroupBoxProxy."""
super().__init__(_('Proxy'), **kwargs)
def containerSequence(self):
+ """Return the editor item containers in display order."""
return [
GuiEditorItemProxyHttp(title='http', translatable=False),
GuiEditorItemProxySocks(title='socks', translatable=False),
@@ -137,13 +153,16 @@ class GuiSocksGroupBoxProxy(GuiEditorWidgetQGroupBox):
class GuiSocks(GuiEditorWidgetQDialog):
+ """Represent GUI SOCKS."""
def __init__(self, *args, **kwargs):
+ """Initialize the GuiSocks."""
super().__init__(*args, **kwargs)
self.setTabText(Protocol.Socks.value)
@functools.lru_cache(None)
def groupBoxSequence(self):
+ """Return the configuration group boxes in display order."""
return [
GuiSocksGroupBoxBasic(),
GuiSocksGroupBoxProxy(),
diff --git a/Furious/Widget/GuiTUNSettings.py b/Furious/Widget/GuiTUNSettings.py
index 847a8e9f..1aa04b94 100644
--- a/Furious/Widget/GuiTUNSettings.py
+++ b/Furious/Widget/GuiTUNSettings.py
@@ -15,6 +15,8 @@
# You should have received a copy of the GNU General Public License
# along with this program. If not, see .
+"""Provide widgets for GUI TUN settings."""
+
from __future__ import annotations
from Furious.Frozenlib import *
@@ -35,12 +37,15 @@ logger = logging.getLogger(__name__)
class GuiTUNSettingsItemXXX(GuiEditorItemTextInput):
+ """Represent GUI TUN settings item xxx."""
def __init__(self, *args, **kwargs):
+ """Initialize the GuiTUNSettingsItemXXX."""
self.key = kwargs.pop('key', '')
super().__init__(*args, **kwargs)
def inputToFactory(self, config: dict) -> bool:
+ """Apply the current editor value to the configuration."""
oldItem = config.get(self.key, '')
newItem = self.text()
@@ -54,11 +59,14 @@ class GuiTUNSettingsItemXXX(GuiEditorItemTextInput):
return False
def factoryToInput(self, config: dict):
+ """Load the configuration value into the editor."""
self.setText(config.get(self.key, ''))
class GuiTUNSettingsItemSpinBoxBufferSizeXXX(GuiEditorItemTextSpinBox):
+ """Represent GUI TUN settings item spin box buffer size xxx."""
def __init__(self, *args, **kwargs):
+ """Initialize the GuiTUNSettingsItemSpinBoxBufferSizeXXX."""
self.key = kwargs.pop('key', '')
self.default = kwargs.pop('default', 1)
self.start = kwargs.pop('start', 1)
@@ -70,6 +78,7 @@ class GuiTUNSettingsItemSpinBoxBufferSizeXXX(GuiEditorItemTextSpinBox):
self.setRange(self.start, self.end)
def inputToFactory(self, config: dict) -> bool:
+ """Apply the current editor value to the configuration."""
oldValue = config.get(self.key, self.default)
newValue = self.value()
@@ -86,6 +95,7 @@ class GuiTUNSettingsItemSpinBoxBufferSizeXXX(GuiEditorItemTextSpinBox):
return False
def factoryToInput(self, config: dict):
+ """Load the configuration value into the editor."""
oldValue = config.get(self.key, self.default)
if not isinstance(oldValue, int):
@@ -95,13 +105,16 @@ class GuiTUNSettingsItemSpinBoxBufferSizeXXX(GuiEditorItemTextSpinBox):
class GuiTUNSettingsItemCheckBoxXXX(GuiEditorItemTextCheckBox):
+ """Represent GUI TUN settings item check box xxx."""
def __init__(self, *args, **kwargs):
+ """Initialize the GuiTUNSettingsItemCheckBoxXXX."""
self.key = kwargs.pop('key', '')
self.default = kwargs.pop('default', 'False')
super().__init__(*args, **kwargs)
def inputToFactory(self, config: dict) -> bool:
+ """Apply the current editor value to the configuration."""
oldItem = config.get(self.key, self.default)
newItem = self.isChecked()
@@ -119,17 +132,21 @@ class GuiTUNSettingsItemCheckBoxXXX(GuiEditorItemTextCheckBox):
return False
def factoryToInput(self, config: dict):
+ """Load the configuration value into the editor."""
self.setChecked(config.get(self.key, self.default) == 'True')
class AppQLabelHelpPage(AppQLabel):
+ """Represent app q label help page."""
def __init__(self, *args, **kwargs):
+ """Initialize the AppQLabelHelpPage."""
super().__init__(*args, **kwargs)
self.setWebsiteURL()
self.linkActivated.connect(self.handleLinkActivated)
def setWebsiteURL(self):
+ """Set website URL."""
self.setText(
f'
'
f''
@@ -140,30 +157,38 @@ class AppQLabelHelpPage(AppQLabel):
@staticmethod
def handleLinkActivated(link: str):
+ """Handle link activated."""
if QDesktopServices.openUrl(QtCore.QUrl(link)):
logger.info(f'open link \'{link}\' success')
else:
logger.error(f'open link \'{link}\' failed')
def retranslate(self):
+ """Refresh translated text for the app q label help page."""
self.setWebsiteURL()
class GuiTUNSettingsItemHelpPage(GuiEditorItemWidgetContainer):
+ """Provide the TUN settings item help configuration editor page."""
def __init__(self, *args, **kwargs):
+ """Initialize the GuiTUNSettingsItemHelpPage."""
super().__init__(*args, **kwargs)
self._page = AppQLabelHelpPage()
def widgets(self):
+ """Return the widgets owned by this editor item."""
return [self._page]
class GuiTUNSettingsGroupBoxBasic(GuiEditorWidgetQGroupBox):
+ """Represent GUI TUN settings group box basic."""
def __init__(self, **kwargs):
+ """Initialize the GuiTUNSettingsGroupBoxBasic."""
super().__init__(_('Basic Configuration'), **kwargs)
def setupPageLayout(self):
+ """Set up page layout."""
layout = QFormLayout()
layout.setFormAlignment(QtCore.Qt.AlignmentFlag.AlignLeft)
layout.setFieldGrowthPolicy(QFormLayout.FieldGrowthPolicy.ExpandingFieldsGrow)
@@ -173,6 +198,7 @@ class GuiTUNSettingsGroupBoxBasic(GuiEditorWidgetQGroupBox):
pairsLayout.setColumnStretch(3, 1)
def addPair(index: int, row: int, column: int):
+ """Add pair."""
label, inputWidget = self._containers[index].widgets()
pairsLayout.addWidget(label, row, column)
@@ -191,6 +217,7 @@ class GuiTUNSettingsGroupBoxBasic(GuiEditorWidgetQGroupBox):
return layout
def containerSequence(self):
+ """Return the editor item containers in display order."""
return [
GuiTUNSettingsItemXXX(
title=_('Primary Adapter Interface Name'),
@@ -224,10 +251,13 @@ class GuiTUNSettingsGroupBoxBasic(GuiEditorWidgetQGroupBox):
class GuiTUNSettingsGroupBoxMemory(GuiEditorWidgetQGroupBox):
+ """Represent GUI TUN settings group box memory."""
def __init__(self, **kwargs):
+ """Initialize the GuiTUNSettingsGroupBoxMemory."""
super().__init__(_('Memory Optimization'), **kwargs)
def containerSequence(self):
+ """Return the editor item containers in display order."""
return [
GuiTUNSettingsItemSpinBoxBufferSizeXXX(
title=_('TCP Send Buffer Size (MiB)'),
@@ -252,7 +282,9 @@ class GuiTUNSettingsGroupBoxMemory(GuiEditorWidgetQGroupBox):
class GuiTUNSettings(GuiEditorWidgetQDialog):
+ """Store and validate GUI TUN settings."""
def __init__(self, *args, **kwargs):
+ """Initialize the GuiTUNSettings."""
tabTranslatable = kwargs.pop('tabTranslatable', True)
style = kwargs.pop('style', 'portrait')
@@ -279,6 +311,7 @@ class GuiTUNSettings(GuiEditorWidgetQDialog):
self.rejected.connect(functools.partial(self.handleRejected))
def handleAccepted(self, config: dict):
+ """Handle accepted."""
modified = self.inputToFactory(config)
if modified and SystemRuntime.isTUNMode():
@@ -288,10 +321,12 @@ class GuiTUNSettings(GuiEditorWidgetQDialog):
self.rejected.disconnect()
def handleRejected(self):
+ """Handle rejected."""
self.accepted.disconnect()
self.rejected.disconnect()
def inputToFactory(self, config: dict) -> bool:
+ """Apply the current editor value to the configuration."""
modified = False
for groupBox in self.groupBoxSequence():
@@ -300,11 +335,13 @@ class GuiTUNSettings(GuiEditorWidgetQDialog):
return modified
def factoryToInput(self, config: dict):
+ """Load the configuration value into the editor."""
for groupBox in self.groupBoxSequence():
groupBox.factoryToInput(config)
@functools.lru_cache(None)
def groupBoxSequence(self):
+ """Return the configuration group boxes in display order."""
return [
GuiTUNSettingsGroupBoxBasic(),
GuiTUNSettingsGroupBoxMemory(),
diff --git a/Furious/Widget/GuiTrojan.py b/Furious/Widget/GuiTrojan.py
index 697e2fe6..43345c79 100644
--- a/Furious/Widget/GuiTrojan.py
+++ b/Furious/Widget/GuiTrojan.py
@@ -15,6 +15,8 @@
# You should have received a copy of the GNU General Public License
# along with this program. If not, see .
+"""Provide widgets for GUI trojan."""
+
from __future__ import annotations
from Furious.Frozenlib import *
@@ -37,7 +39,9 @@ getProxyOutboundServer = functools.partial(
class GuiTrojanItemTextInput(GuiEditorItemTextInput):
+ """Represent GUI trojan item text input."""
def __init__(self, *args, **kwargs):
+ """Initialize the GuiTrojanItemTextInput."""
key = kwargs.pop('key', '')
super().__init__(*args, **kwargs)
@@ -45,6 +49,7 @@ class GuiTrojanItemTextInput(GuiEditorItemTextInput):
self.key = key
def inputToFactory(self, config: ConfigFactory) -> bool:
+ """Apply the current editor value to the configuration."""
proxyOutboundServer = getProxyOutboundServer(config)
oldValue = proxyOutboundServer.get(self.key, '')
@@ -67,6 +72,7 @@ class GuiTrojanItemTextInput(GuiEditorItemTextInput):
return True
def factoryToInput(self, config: ConfigFactory):
+ """Load the configuration value into the editor."""
try:
proxyOutboundServer = getProxyOutboundServer(config)
@@ -78,13 +84,16 @@ class GuiTrojanItemTextInput(GuiEditorItemTextInput):
class GuiTrojanItemBasicPort(GuiEditorItemTextSpinBox):
+ """Represent GUI trojan item basic port."""
def __init__(self, *args, **kwargs):
+ """Initialize the GuiTrojanItemBasicPort."""
super().__init__(*args, **kwargs)
# Range
self.setRange(0, 65535)
def inputToFactory(self, config: ConfigFactory) -> bool:
+ """Apply the current editor value to the configuration."""
proxyOutboundServer = getProxyOutboundServer(config)
oldPort = proxyOutboundServer.get('port')
@@ -103,6 +112,7 @@ class GuiTrojanItemBasicPort(GuiEditorItemTextSpinBox):
return True
def factoryToInput(self, config: ConfigFactory):
+ """Load the configuration value into the editor."""
try:
proxyOutboundServer = getProxyOutboundServer(config)
@@ -114,10 +124,13 @@ class GuiTrojanItemBasicPort(GuiEditorItemTextSpinBox):
class GuiTrojanGroupBoxBasic(GuiEditorWidgetQGroupBox):
+ """Represent GUI trojan group box basic."""
def __init__(self, **kwargs):
+ """Initialize the GuiTrojanGroupBoxBasic."""
super().__init__(_('Basic Configuration'), **kwargs)
def containerSequence(self):
+ """Return the editor item containers in display order."""
return [
GuiEditorItemBasicRemark(title=_('Remark')),
GuiTrojanItemTextInput(title=_('Address'), key='address'),
@@ -127,10 +140,13 @@ class GuiTrojanGroupBoxBasic(GuiEditorWidgetQGroupBox):
class GuiTrojanGroupBoxProxy(GuiEditorWidgetQGroupBox):
+ """Represent GUI trojan group box proxy."""
def __init__(self, **kwargs):
+ """Initialize the GuiTrojanGroupBoxProxy."""
super().__init__(_('Proxy'), **kwargs)
def containerSequence(self):
+ """Return the editor item containers in display order."""
return [
GuiEditorItemProxyHttp(title='http', translatable=False),
GuiEditorItemProxySocks(title='socks', translatable=False),
@@ -138,13 +154,16 @@ class GuiTrojanGroupBoxProxy(GuiEditorWidgetQGroupBox):
class GuiTrojan(GuiEditorWidgetQDialog):
+ """Represent GUI trojan."""
def __init__(self, *args, **kwargs):
+ """Initialize the GuiTrojan."""
super().__init__(*args, **kwargs)
self.setTabText(Protocol.Trojan.value)
@functools.lru_cache(None)
def groupBoxSequence(self):
+ """Return the configuration group boxes in display order."""
return [
GuiTrojanGroupBoxBasic(),
GuiTrojanGroupBoxProxy(),
diff --git a/Furious/Widget/GuiVLESS.py b/Furious/Widget/GuiVLESS.py
index 52439488..e6ef7626 100644
--- a/Furious/Widget/GuiVLESS.py
+++ b/Furious/Widget/GuiVLESS.py
@@ -15,6 +15,8 @@
# You should have received a copy of the GNU General Public License
# along with this program. If not, see .
+"""Provide widgets for GUI VLESS."""
+
from __future__ import annotations
from Furious.Frozenlib import *
@@ -46,10 +48,13 @@ getProxyOutboundUser = functools.partial(
class GuiVLESSItemBasicAddress(GuiEditorItemTextInput):
+ """Represent GUI VLESS item basic address."""
def __init__(self, *args, **kwargs):
+ """Initialize the GuiVLESSItemBasicAddress."""
super().__init__(*args, **kwargs)
def inputToFactory(self, config: ConfigFactory) -> bool:
+ """Apply the current editor value to the configuration."""
proxyOutboundServer = getProxyOutboundServer(config)
oldAddress = proxyOutboundServer.get('address', '')
@@ -72,6 +77,7 @@ class GuiVLESSItemBasicAddress(GuiEditorItemTextInput):
return True
def factoryToInput(self, config: ConfigFactory):
+ """Load the configuration value into the editor."""
try:
proxyOutboundServer = getProxyOutboundServer(config)
@@ -83,13 +89,16 @@ class GuiVLESSItemBasicAddress(GuiEditorItemTextInput):
class GuiVLESSItemBasicPort(GuiEditorItemTextSpinBox):
+ """Represent GUI VLESS item basic port."""
def __init__(self, *args, **kwargs):
+ """Initialize the GuiVLESSItemBasicPort."""
super().__init__(*args, **kwargs)
# Range
self.setRange(0, 65535)
def inputToFactory(self, config: ConfigFactory) -> bool:
+ """Apply the current editor value to the configuration."""
proxyOutboundServer = getProxyOutboundServer(config)
oldPort = proxyOutboundServer.get('port')
@@ -108,6 +117,7 @@ class GuiVLESSItemBasicPort(GuiEditorItemTextSpinBox):
return True
def factoryToInput(self, config: ConfigFactory):
+ """Load the configuration value into the editor."""
try:
proxyOutboundServer = getProxyOutboundServer(config)
@@ -119,7 +129,9 @@ class GuiVLESSItemBasicPort(GuiEditorItemTextSpinBox):
class GuiVLESSItemBasicId(GuiEditorItemTextInput):
+ """Represent GUI VLESS item basic id."""
def __init__(self, *args, **kwargs):
+ """Initialize the GuiVLESSItemBasicId."""
super().__init__(*args, **kwargs)
self.generateButton = AppQPushButton(_('Generate'))
@@ -133,12 +145,15 @@ class GuiVLESSItemBasicId(GuiEditorItemTextInput):
layout.addWidget(self.generateButton)
def generateUUID(self):
+ """Handle generate uuid for the GUI VLESS item basic id."""
self.setText(str(uuid.uuid4()))
def widgets(self):
+ """Return the widgets owned by this editor item."""
return self._title, self._widget
def inputToFactory(self, config: ConfigFactory) -> bool:
+ """Apply the current editor value to the configuration."""
proxyOutboundUser = getProxyOutboundUser(config)
oldId = proxyOutboundUser.get('id', '')
@@ -161,6 +176,7 @@ class GuiVLESSItemBasicId(GuiEditorItemTextInput):
return True
def factoryToInput(self, config: ConfigFactory):
+ """Load the configuration value into the editor."""
try:
proxyOutboundUser = getProxyOutboundUser(config)
@@ -172,10 +188,13 @@ class GuiVLESSItemBasicId(GuiEditorItemTextInput):
class GuiVLESSItemBasicEncryption(GuiEditorItemTextInput):
+ """Represent GUI VLESS item basic encryption."""
def __init__(self, *args, **kwargs):
+ """Initialize the GuiVLESSItemBasicEncryption."""
super().__init__(*args, **kwargs)
def inputToFactory(self, config: ConfigFactory) -> bool:
+ """Apply the current editor value to the configuration."""
proxyOutboundUser = getProxyOutboundUser(config)
oldEncryption = proxyOutboundUser.get('encryption', '')
@@ -198,6 +217,7 @@ class GuiVLESSItemBasicEncryption(GuiEditorItemTextInput):
return True
def factoryToInput(self, config: ConfigFactory):
+ """Load the configuration value into the editor."""
try:
proxyOutboundUser = getProxyOutboundUser(config)
@@ -209,7 +229,9 @@ class GuiVLESSItemBasicEncryption(GuiEditorItemTextInput):
class GuiVLESSItemBasicFlow(GuiEditorItemTextComboBox):
+ """Represent GUI VLESS item basic flow."""
def __init__(self, *args, **kwargs):
+ """Initialize the GuiVLESSItemBasicFlow."""
super().__init__(*args, **kwargs)
self.addItems(
@@ -222,6 +244,7 @@ class GuiVLESSItemBasicFlow(GuiEditorItemTextComboBox):
)
def inputToFactory(self, config: ConfigFactory) -> bool:
+ """Apply the current editor value to the configuration."""
proxyOutboundUser = getProxyOutboundUser(config)
oldFlow = proxyOutboundUser.get('flow', '')
@@ -240,6 +263,7 @@ class GuiVLESSItemBasicFlow(GuiEditorItemTextComboBox):
return True
def factoryToInput(self, config: ConfigFactory):
+ """Load the configuration value into the editor."""
try:
proxyOutboundUser = getProxyOutboundUser(config)
@@ -251,15 +275,19 @@ class GuiVLESSItemBasicFlow(GuiEditorItemTextComboBox):
class GuiVLESSGroupBoxBasic(GuiEditorWidgetQGroupBox):
+ """Represent GUI VLESS group box basic."""
def __init__(self, **kwargs):
+ """Initialize the GuiVLESSGroupBoxBasic."""
super().__init__(_('Basic Configuration'), **kwargs)
def setupPageLayout(self):
+ """Set up page layout."""
layout = QGridLayout()
layout.setColumnStretch(1, 1)
layout.setColumnStretch(3, 1)
def keepSpinBoxCompact(widget: QWidget):
+ """Handle keep spin box compact for the GUI VLESS group box basic."""
if isinstance(widget, QSpinBox):
widget.setSizePolicy(
QSizePolicy.Policy.Fixed,
@@ -267,6 +295,7 @@ class GuiVLESSGroupBoxBasic(GuiEditorWidgetQGroupBox):
)
def addPair(index: int, row: int, column: int):
+ """Add pair."""
label, inputWidget = self._containers[index].widgets()
keepSpinBoxCompact(inputWidget)
@@ -275,6 +304,7 @@ class GuiVLESSGroupBoxBasic(GuiEditorWidgetQGroupBox):
layout.addWidget(inputWidget, row, column + 1)
def addFullRow(index: int, row: int):
+ """Add full row."""
label, inputWidget = self._containers[index].widgets()
keepSpinBoxCompact(inputWidget)
@@ -294,6 +324,7 @@ class GuiVLESSGroupBoxBasic(GuiEditorWidgetQGroupBox):
return layout
def containerSequence(self):
+ """Return the editor item containers in display order."""
return [
GuiEditorItemBasicRemark(title=_('Remark')),
GuiVLESSItemBasicAddress(title=_('Address')),
@@ -305,10 +336,13 @@ class GuiVLESSGroupBoxBasic(GuiEditorWidgetQGroupBox):
class GuiVLESSGroupBoxProxy(GuiEditorWidgetQGroupBox):
+ """Represent GUI VLESS group box proxy."""
def __init__(self, **kwargs):
+ """Initialize the GuiVLESSGroupBoxProxy."""
super().__init__(_('Proxy'), **kwargs)
def containerSequence(self):
+ """Return the editor item containers in display order."""
return [
GuiEditorItemProxyHttp(title='http', translatable=False),
GuiEditorItemProxySocks(title='socks', translatable=False),
@@ -316,13 +350,16 @@ class GuiVLESSGroupBoxProxy(GuiEditorWidgetQGroupBox):
class GuiVLESS(GuiEditorWidgetQDialog):
+ """Represent GUI VLESS."""
def __init__(self, *args, **kwargs):
+ """Initialize the GuiVLESS."""
super().__init__(*args, **kwargs)
self.setTabText(Protocol.VLESS.value)
@functools.lru_cache(None)
def groupBoxSequence(self):
+ """Return the configuration group boxes in display order."""
return [
GuiVLESSGroupBoxBasic(),
GuiVLESSGroupBoxProxy(),
diff --git a/Furious/Widget/GuiVMess.py b/Furious/Widget/GuiVMess.py
index d3acde00..7096c7c5 100644
--- a/Furious/Widget/GuiVMess.py
+++ b/Furious/Widget/GuiVMess.py
@@ -15,6 +15,8 @@
# You should have received a copy of the GNU General Public License
# along with this program. If not, see .
+"""Provide widgets for GUI v mess."""
+
from __future__ import annotations
from Furious.Frozenlib import *
@@ -46,10 +48,13 @@ getProxyOutboundUser = functools.partial(
class GuiVMessItemBasicAddress(GuiEditorItemTextInput):
+ """Represent GUI v mess item basic address."""
def __init__(self, *args, **kwargs):
+ """Initialize the GuiVMessItemBasicAddress."""
super().__init__(*args, **kwargs)
def inputToFactory(self, config: ConfigFactory) -> bool:
+ """Apply the current editor value to the configuration."""
proxyOutboundServer = getProxyOutboundServer(config)
oldAddress = proxyOutboundServer.get('address', '')
@@ -72,6 +77,7 @@ class GuiVMessItemBasicAddress(GuiEditorItemTextInput):
return True
def factoryToInput(self, config: ConfigFactory):
+ """Load the configuration value into the editor."""
try:
proxyOutboundServer = getProxyOutboundServer(config)
@@ -83,13 +89,16 @@ class GuiVMessItemBasicAddress(GuiEditorItemTextInput):
class GuiVMessItemBasicPort(GuiEditorItemTextSpinBox):
+ """Represent GUI v mess item basic port."""
def __init__(self, *args, **kwargs):
+ """Initialize the GuiVMessItemBasicPort."""
super().__init__(*args, **kwargs)
# Range
self.setRange(0, 65535)
def inputToFactory(self, config: ConfigFactory) -> bool:
+ """Apply the current editor value to the configuration."""
proxyOutboundServer = getProxyOutboundServer(config)
oldPort = proxyOutboundServer.get('port')
@@ -108,6 +117,7 @@ class GuiVMessItemBasicPort(GuiEditorItemTextSpinBox):
return True
def factoryToInput(self, config: ConfigFactory):
+ """Load the configuration value into the editor."""
try:
proxyOutboundServer = getProxyOutboundServer(config)
@@ -119,7 +129,9 @@ class GuiVMessItemBasicPort(GuiEditorItemTextSpinBox):
class GuiVMessItemBasicId(GuiEditorItemTextInput):
+ """Represent GUI v mess item basic id."""
def __init__(self, *args, **kwargs):
+ """Initialize the GuiVMessItemBasicId."""
super().__init__(*args, **kwargs)
self.generateButton = AppQPushButton(_('Generate'))
@@ -133,12 +145,15 @@ class GuiVMessItemBasicId(GuiEditorItemTextInput):
layout.addWidget(self.generateButton)
def generateUUID(self):
+ """Handle generate uuid for the GUI v mess item basic id."""
self.setText(str(uuid.uuid4()))
def widgets(self):
+ """Return the widgets owned by this editor item."""
return self._title, self._widget
def inputToFactory(self, config: ConfigFactory) -> bool:
+ """Apply the current editor value to the configuration."""
proxyOutboundUser = getProxyOutboundUser(config)
oldId = proxyOutboundUser.get('id', '')
@@ -161,6 +176,7 @@ class GuiVMessItemBasicId(GuiEditorItemTextInput):
return True
def factoryToInput(self, config: ConfigFactory):
+ """Load the configuration value into the editor."""
try:
proxyOutboundUser = getProxyOutboundUser(config)
@@ -172,13 +188,16 @@ class GuiVMessItemBasicId(GuiEditorItemTextInput):
class GuiVMessItemBasicAlterId(GuiEditorItemTextSpinBox):
+ """Represent GUI v mess item basic alter id."""
def __init__(self, *args, **kwargs):
+ """Initialize the GuiVMessItemBasicAlterId."""
super().__init__(*args, **kwargs)
# Range
self.setRange(0, 65535)
def inputToFactory(self, config: ConfigFactory) -> bool:
+ """Apply the current editor value to the configuration."""
proxyOutboundUser = getProxyOutboundUser(config)
oldAlterId = proxyOutboundUser.get('alterId')
@@ -197,6 +216,7 @@ class GuiVMessItemBasicAlterId(GuiEditorItemTextSpinBox):
return True
def factoryToInput(self, config: ConfigFactory):
+ """Load the configuration value into the editor."""
try:
proxyOutboundUser = getProxyOutboundUser(config)
@@ -208,7 +228,9 @@ class GuiVMessItemBasicAlterId(GuiEditorItemTextSpinBox):
class GuiVMessItemBasicSecurity(GuiEditorItemTextComboBox):
+ """Represent GUI v mess item basic security."""
def __init__(self, *args, **kwargs):
+ """Initialize the GuiVMessItemBasicSecurity."""
super().__init__(*args, **kwargs)
self.addItems(
@@ -223,6 +245,7 @@ class GuiVMessItemBasicSecurity(GuiEditorItemTextComboBox):
)
def inputToFactory(self, config: ConfigFactory) -> bool:
+ """Apply the current editor value to the configuration."""
proxyOutboundUser = getProxyOutboundUser(config)
oldSecurity = proxyOutboundUser.get('security', '')
@@ -241,6 +264,7 @@ class GuiVMessItemBasicSecurity(GuiEditorItemTextComboBox):
return True
def factoryToInput(self, config: ConfigFactory):
+ """Load the configuration value into the editor."""
try:
proxyOutboundUser = getProxyOutboundUser(config)
@@ -252,14 +276,18 @@ class GuiVMessItemBasicSecurity(GuiEditorItemTextComboBox):
class GuiVMessGroupBoxBasic(GuiEditorWidgetQGroupBox):
+ """Represent GUI v mess group box basic."""
def __init__(self, **kwargs):
+ """Initialize the GuiVMessGroupBoxBasic."""
super().__init__(_('Basic Configuration'), **kwargs)
def setupPageLayout(self):
+ """Set up page layout."""
layout = QGridLayout()
layout.setColumnStretch(4, 1)
def keepSpinBoxCompact(widget: QWidget):
+ """Handle keep spin box compact for the GUI v mess group box basic."""
if isinstance(widget, QSpinBox):
widget.setSizePolicy(
QSizePolicy.Policy.Fixed,
@@ -267,6 +295,7 @@ class GuiVMessGroupBoxBasic(GuiEditorWidgetQGroupBox):
)
def keepComboBoxCompact(widget: QWidget):
+ """Handle keep combo box compact for the GUI v mess group box basic."""
if isinstance(widget, QComboBox):
widget.setSizePolicy(
QSizePolicy.Policy.Fixed,
@@ -274,6 +303,7 @@ class GuiVMessGroupBoxBasic(GuiEditorWidgetQGroupBox):
)
def addPair(index: int, row: int, column: int):
+ """Add pair."""
label, inputWidget = self._containers[index].widgets()
keepSpinBoxCompact(inputWidget)
@@ -282,6 +312,7 @@ class GuiVMessGroupBoxBasic(GuiEditorWidgetQGroupBox):
layout.addWidget(inputWidget, row, column + 1)
def addFullRow(index: int, row: int):
+ """Add full row."""
label, inputWidget = self._containers[index].widgets()
keepSpinBoxCompact(inputWidget)
@@ -302,6 +333,7 @@ class GuiVMessGroupBoxBasic(GuiEditorWidgetQGroupBox):
return layout
def containerSequence(self):
+ """Return the editor item containers in display order."""
return [
GuiEditorItemBasicRemark(title=_('Remark')),
GuiVMessItemBasicAddress(title=_('Address')),
@@ -313,10 +345,13 @@ class GuiVMessGroupBoxBasic(GuiEditorWidgetQGroupBox):
class GuiVMessGroupBoxProxy(GuiEditorWidgetQGroupBox):
+ """Represent GUI v mess group box proxy."""
def __init__(self, **kwargs):
+ """Initialize the GuiVMessGroupBoxProxy."""
super().__init__(_('Proxy'), **kwargs)
def containerSequence(self):
+ """Return the editor item containers in display order."""
return [
GuiEditorItemProxyHttp(title='http', translatable=False),
GuiEditorItemProxySocks(title='socks', translatable=False),
@@ -324,13 +359,16 @@ class GuiVMessGroupBoxProxy(GuiEditorWidgetQGroupBox):
class GuiVMess(GuiEditorWidgetQDialog):
+ """Represent GUI v mess."""
def __init__(self, *args, **kwargs):
+ """Initialize the GuiVMess."""
super().__init__(*args, **kwargs)
self.setTabText(Protocol.VMess.value)
@functools.lru_cache(None)
def groupBoxSequence(self):
+ """Return the configuration group boxes in display order."""
return [
GuiVMessGroupBoxBasic(),
GuiVMessGroupBoxProxy(),
diff --git a/Furious/Widget/GuiVTLS.py b/Furious/Widget/GuiVTLS.py
index a23645b3..791d448e 100644
--- a/Furious/Widget/GuiVTLS.py
+++ b/Furious/Widget/GuiVTLS.py
@@ -15,6 +15,8 @@
# You should have received a copy of the GNU General Public License
# along with this program. If not, see .
+"""Provide widgets for GUI vtls."""
+
from __future__ import annotations
from Furious.Interface import *
@@ -37,12 +39,15 @@ STREAM_SECURITY = [
class GuiVTLSItemSecurity(GuiEditorItemTextComboBox):
+ """Represent GUI vtls item security."""
def __init__(self, *args, **kwargs):
+ """Initialize the GuiVTLSItemSecurity."""
super().__init__(*args, **kwargs)
self.addItems(STREAM_SECURITY)
def inputToFactory(self, config: ConfigFactory) -> bool:
+ """Apply the current editor value to the configuration."""
streamSettings = ConfigXray.getProxyOutboundStream(config)
try:
@@ -55,6 +60,7 @@ class GuiVTLSItemSecurity(GuiEditorItemTextComboBox):
newSecurity = self.text()
def setNewSecurity():
+ """Set new security."""
if newSecurity == '':
streamSettings.pop('security', None)
else:
@@ -82,6 +88,7 @@ class GuiVTLSItemSecurity(GuiEditorItemTextComboBox):
return True
def factoryToInput(self, config: ConfigFactory):
+ """Load the configuration value into the editor."""
try:
streamSettings = ConfigXray.getProxyOutboundStream(config)
@@ -93,8 +100,10 @@ class GuiVTLSItemSecurity(GuiEditorItemTextComboBox):
class GuiVTLSItemXXXServerName(GuiEditorItemTextInput):
+ """Represent GUI vtls item xxx server name."""
def __init__(self, *args, **kwargs):
# Mandatory
+ """Initialize the GuiVTLSItemXXXServerName."""
securityKey = kwargs.pop('securityKey')
super().__init__(*args, **kwargs)
@@ -102,6 +111,7 @@ class GuiVTLSItemXXXServerName(GuiEditorItemTextInput):
self.securityKey = securityKey
def inputToFactory(self, config: ConfigFactory) -> bool:
+ """Apply the current editor value to the configuration."""
streamSettings = ConfigXray.getProxyOutboundStream(config)
if not isinstance(streamSettings.get(self.securityKey), dict):
@@ -119,6 +129,7 @@ class GuiVTLSItemXXXServerName(GuiEditorItemTextInput):
newServerName = self.text()
def setNewServerName():
+ """Set new server name."""
if newServerName == '':
xxxObject.pop('serverName', None)
else:
@@ -137,6 +148,7 @@ class GuiVTLSItemXXXServerName(GuiEditorItemTextInput):
return True
def factoryToInput(self, config: ConfigFactory):
+ """Load the configuration value into the editor."""
try:
xxxObject = ConfigXray.getProxyOutboundStream(config)[self.securityKey]
@@ -148,22 +160,28 @@ class GuiVTLSItemXXXServerName(GuiEditorItemTextInput):
class GuiVTLSItemTLSServerName(GuiVTLSItemXXXServerName):
+ """Represent GUI vtls item TLS server name."""
def __init__(self, *args, **kwargs):
+ """Initialize the GuiVTLSItemTLSServerName."""
securityKey = kwargs.pop('securityKey', 'tlsSettings')
super().__init__(*args, **kwargs, securityKey=securityKey)
class GuiVTLSItemRealityServerName(GuiVTLSItemXXXServerName):
+ """Represent GUI vtls item reality server name."""
def __init__(self, *args, **kwargs):
+ """Initialize the GuiVTLSItemRealityServerName."""
securityKey = kwargs.pop('securityKey', 'realitySettings')
super().__init__(*args, **kwargs, securityKey=securityKey)
class GuiVTLSItemXXXFingerprint(GuiEditorItemTextInput):
+ """Represent GUI vtls item xxx fingerprint."""
def __init__(self, *args, **kwargs):
# Mandatory
+ """Initialize the GuiVTLSItemXXXFingerprint."""
securityKey = kwargs.pop('securityKey')
super().__init__(*args, **kwargs)
@@ -171,6 +189,7 @@ class GuiVTLSItemXXXFingerprint(GuiEditorItemTextInput):
self.securityKey = securityKey
def inputToFactory(self, config: ConfigFactory) -> bool:
+ """Apply the current editor value to the configuration."""
streamSettings = ConfigXray.getProxyOutboundStream(config)
if not isinstance(streamSettings.get(self.securityKey), dict):
@@ -188,6 +207,7 @@ class GuiVTLSItemXXXFingerprint(GuiEditorItemTextInput):
newFingerprint = self.text()
def setNewFingerprint():
+ """Set new fingerprint."""
if newFingerprint == '':
xxxObject.pop('fingerprint', None)
else:
@@ -206,6 +226,7 @@ class GuiVTLSItemXXXFingerprint(GuiEditorItemTextInput):
return True
def factoryToInput(self, config: ConfigFactory):
+ """Load the configuration value into the editor."""
try:
xxxObject = ConfigXray.getProxyOutboundStream(config)[self.securityKey]
@@ -217,24 +238,31 @@ class GuiVTLSItemXXXFingerprint(GuiEditorItemTextInput):
class GuiVTLSItemTLSFingerprint(GuiVTLSItemXXXFingerprint):
+ """Represent GUI vtls item TLS fingerprint."""
def __init__(self, *args, **kwargs):
+ """Initialize the GuiVTLSItemTLSFingerprint."""
securityKey = kwargs.pop('securityKey', 'tlsSettings')
super().__init__(*args, **kwargs, securityKey=securityKey)
class GuiVTLSItemRealityFingerprint(GuiVTLSItemXXXFingerprint):
+ """Represent GUI vtls item reality fingerprint."""
def __init__(self, *args, **kwargs):
+ """Initialize the GuiVTLSItemRealityFingerprint."""
securityKey = kwargs.pop('securityKey', 'realitySettings')
super().__init__(*args, **kwargs, securityKey=securityKey)
class GuiVTLSItemTLSAlpn(GuiEditorItemTextInput):
+ """Represent GUI vtls item TLS alpn."""
def __init__(self, *args, **kwargs):
+ """Initialize the GuiVTLSItemTLSAlpn."""
super().__init__(*args, **kwargs)
def inputToFactory(self, config: ConfigFactory) -> bool:
+ """Apply the current editor value to the configuration."""
streamSettings = ConfigXray.getProxyOutboundStream(config)
if not isinstance(streamSettings.get('tlsSettings'), dict):
@@ -252,6 +280,7 @@ class GuiVTLSItemTLSAlpn(GuiEditorItemTextInput):
newAlpn = self.text()
def setNewAlpn():
+ """Set new alpn."""
if newAlpn == '':
tlsObject.pop('alpn', None)
else:
@@ -270,6 +299,7 @@ class GuiVTLSItemTLSAlpn(GuiEditorItemTextInput):
return True
def factoryToInput(self, config: ConfigFactory):
+ """Load the configuration value into the editor."""
try:
tlsObject = ConfigXray.getProxyOutboundStream(config)['tlsSettings']
@@ -281,8 +311,10 @@ class GuiVTLSItemTLSAlpn(GuiEditorItemTextInput):
class GuiVTLSItemTLSXXXTextInput(GuiEditorItemTextInput):
+ """Represent GUI vtls item tlsxxx text input."""
def __init__(self, *args, **kwargs):
# Mandatory
+ """Initialize the GuiVTLSItemTLSXXXTextInput."""
key = kwargs.pop('key')
super().__init__(*args, **kwargs)
@@ -290,6 +322,7 @@ class GuiVTLSItemTLSXXXTextInput(GuiEditorItemTextInput):
self.key = key
def inputToFactory(self, config: ConfigFactory) -> bool:
+ """Apply the current editor value to the configuration."""
streamSettings = ConfigXray.getProxyOutboundStream(config)
if not isinstance(streamSettings.get('tlsSettings'), dict):
@@ -301,6 +334,7 @@ class GuiVTLSItemTLSXXXTextInput(GuiEditorItemTextInput):
newValue = self.text()
def setNewTLSXXXValue():
+ """Set new tlsxxx value."""
if newValue == '':
tlsObject.pop(self.key, None)
else:
@@ -319,6 +353,7 @@ class GuiVTLSItemTLSXXXTextInput(GuiEditorItemTextInput):
return True
def factoryToInput(self, config: ConfigFactory):
+ """Load the configuration value into the editor."""
try:
tlsObject = ConfigXray.getProxyOutboundStream(config)['tlsSettings']
@@ -330,10 +365,13 @@ class GuiVTLSItemTLSXXXTextInput(GuiEditorItemTextInput):
class GuiVTLSItemTLSAllowInsecure(GuiEditorItemTextCheckBox):
+ """Represent GUI vtls item TLS allow insecure."""
def __init__(self, *args, **kwargs):
+ """Initialize the GuiVTLSItemTLSAllowInsecure."""
super().__init__(*args, **kwargs)
def inputToFactory(self, config: ConfigFactory) -> bool:
+ """Apply the current editor value to the configuration."""
streamSettings = ConfigXray.getProxyOutboundStream(config)
if not isinstance(streamSettings.get('tlsSettings'), dict):
@@ -366,6 +404,7 @@ class GuiVTLSItemTLSAllowInsecure(GuiEditorItemTextCheckBox):
return False
def factoryToInput(self, config: ConfigFactory):
+ """Load the configuration value into the editor."""
try:
tlsObject = ConfigXray.getProxyOutboundStream(config)['tlsSettings']
@@ -377,8 +416,10 @@ class GuiVTLSItemTLSAllowInsecure(GuiEditorItemTextCheckBox):
class GuiVTLSItemRealityXXX(GuiEditorItemTextInput):
+ """Represent GUI vtls item reality xxx."""
def __init__(self, *args, **kwargs):
# Mandatory
+ """Initialize the GuiVTLSItemRealityXXX."""
realityKey = kwargs.pop('realityKey')
super().__init__(*args, **kwargs)
@@ -386,6 +427,7 @@ class GuiVTLSItemRealityXXX(GuiEditorItemTextInput):
self.realityKey = realityKey
def inputToFactory(self, config: ConfigFactory) -> bool:
+ """Apply the current editor value to the configuration."""
streamSettings = ConfigXray.getProxyOutboundStream(config)
if not isinstance(streamSettings.get('realitySettings'), dict):
@@ -403,6 +445,7 @@ class GuiVTLSItemRealityXXX(GuiEditorItemTextInput):
newPublicKey = self.text()
def setNewPublicKey():
+ """Set new public key."""
if newPublicKey == '':
realityObject.pop(self.realityKey, None)
else:
@@ -421,6 +464,7 @@ class GuiVTLSItemRealityXXX(GuiEditorItemTextInput):
return True
def factoryToInput(self, config: ConfigFactory):
+ """Load the configuration value into the editor."""
try:
realityObject = ConfigXray.getProxyOutboundStream(config)['realitySettings']
@@ -432,44 +476,56 @@ class GuiVTLSItemRealityXXX(GuiEditorItemTextInput):
class GuiVTLSItemRealityPublicKey(GuiVTLSItemRealityXXX):
+ """Represent GUI vtls item reality public key."""
def __init__(self, *args, **kwargs):
+ """Initialize the GuiVTLSItemRealityPublicKey."""
realityKey = kwargs.pop('realityKey', 'publicKey')
super().__init__(*args, **kwargs, realityKey=realityKey)
class GuiVTLSItemRealityShortId(GuiVTLSItemRealityXXX):
+ """Represent GUI vtls item reality short id."""
def __init__(self, *args, **kwargs):
+ """Initialize the GuiVTLSItemRealityShortId."""
realityKey = kwargs.pop('realityKey', 'shortId')
super().__init__(*args, **kwargs, realityKey=realityKey)
class GuiVTLSItemRealityMldsa65Verify(GuiVTLSItemRealityXXX):
+ """Represent GUI vtls item reality mldsa65 verify."""
def __init__(self, *args, **kwargs):
+ """Initialize the GuiVTLSItemRealityMldsa65Verify."""
realityKey = kwargs.pop('realityKey', 'mldsa65Verify')
super().__init__(*args, **kwargs, realityKey=realityKey)
class GuiVTLSItemRealitySpiderX(GuiVTLSItemRealityXXX):
+ """Represent GUI vtls item reality spider x."""
def __init__(self, *args, **kwargs):
+ """Initialize the GuiVTLSItemRealitySpiderX."""
realityKey = kwargs.pop('realityKey', 'spiderX')
super().__init__(*args, **kwargs, realityKey=realityKey)
class GuiVTLSPageXXX(GuiEditorWidgetQWidget):
+ """Represent GUI vtls page xxx."""
def __init__(self, *args, **kwargs):
+ """Initialize the GuiVTLSPageXXX."""
super().__init__(*args, **kwargs)
def setSecurityText(self, text: str):
+ """Set security text."""
security = self._containers[0]
if isinstance(security, GuiEditorItemTextComboBox):
security.setText(text)
def connectActivated(self, func: Callable):
+ """Connect activated."""
security = self._containers[0]
if isinstance(security, GuiEditorItemTextComboBox):
@@ -477,30 +533,39 @@ class GuiVTLSPageXXX(GuiEditorWidgetQWidget):
class GuiVTLSPageEmpty(GuiVTLSPageXXX):
+ """Represent GUI vtls page empty."""
def __init__(self, *args, **kwargs):
+ """Initialize the GuiVTLSPageEmpty."""
super().__init__(*args, **kwargs)
def containerSequence(self):
+ """Return the editor item containers in display order."""
return [
GuiVTLSItemSecurity(title='TLS', translatable=False),
]
class GuiVTLSPageNone(GuiVTLSPageXXX):
+ """Represent GUI vtls page none."""
def __init__(self, *args, **kwargs):
+ """Initialize the GuiVTLSPageNone."""
super().__init__(*args, **kwargs)
def containerSequence(self):
+ """Return the editor item containers in display order."""
return [
GuiVTLSItemSecurity(title='TLS', translatable=False),
]
class GuiVTLSPageTLS(GuiVTLSPageXXX):
+ """Represent GUI vtls page TLS."""
def __init__(self, *args, **kwargs):
+ """Initialize the GuiVTLSPageTLS."""
super().__init__(*args, **kwargs)
def setupLayout(self):
+ """Set up layout."""
layout = QFormLayout()
layout.setFormAlignment(QtCore.Qt.AlignmentFlag.AlignLeft)
layout.setFieldGrowthPolicy(QFormLayout.FieldGrowthPolicy.ExpandingFieldsGrow)
@@ -510,18 +575,21 @@ class GuiVTLSPageTLS(GuiVTLSPageXXX):
basicLayout.setColumnStretch(3, 1)
def addPair(index: int, row: int, column: int):
+ """Add pair."""
label, inputWidget = self._containers[index].widgets()
basicLayout.addWidget(label, row, column)
basicLayout.addWidget(inputWidget, row, column + 1)
def addBasicFullRow(index: int, row: int):
+ """Add basic full row."""
label, inputWidget = self._containers[index].widgets()
basicLayout.addWidget(label, row, 0)
basicLayout.addWidget(inputWidget, row, 1, 1, 3)
def addFullRow(index: int):
+ """Add full row."""
widgets = self._containers[index].widgets()
if len(widgets) == 1:
@@ -546,6 +614,7 @@ class GuiVTLSPageTLS(GuiVTLSPageXXX):
self.setLayout(layout)
def containerSequence(self):
+ """Return the editor item containers in display order."""
return [
GuiVTLSItemSecurity(title='TLS', translatable=False),
GuiVTLSItemTLSServerName(title='SNI', translatable=False),
@@ -571,22 +640,27 @@ class GuiVTLSPageTLS(GuiVTLSPageXXX):
class GuiVTLSPageReality(GuiVTLSPageXXX):
+ """Represent GUI vtls page reality."""
def __init__(self, *args, **kwargs):
+ """Initialize the GuiVTLSPageReality."""
super().__init__(*args, **kwargs)
def setupLayout(self):
+ """Set up layout."""
layout = QGridLayout()
layout.setColumnStretch(1, 1)
layout.setColumnStretch(3, 1)
layout.setHorizontalSpacing(12)
def addPair(index: int, row: int, column: int):
+ """Add pair."""
label, inputWidget = self._containers[index].widgets()
layout.addWidget(label, row, column)
layout.addWidget(inputWidget, row, column + 1)
def addFullRow(index: int, row: int):
+ """Add full row."""
label, inputWidget = self._containers[index].widgets()
layout.addWidget(label, row, 0)
@@ -603,6 +677,7 @@ class GuiVTLSPageReality(GuiVTLSPageXXX):
self.setLayout(layout)
def containerSequence(self):
+ """Return the editor item containers in display order."""
return [
GuiVTLSItemSecurity(title='TLS', translatable=False),
GuiVTLSItemRealityServerName(title='SNI', translatable=False),
@@ -615,7 +690,9 @@ class GuiVTLSPageReality(GuiVTLSPageXXX):
class GuiVTLSPageStackedWidget(QStackedWidget):
+ """Provide the GUI vtls page stacked widget."""
def __init__(self, *args, **kwargs):
+ """Initialize the GuiVTLSPageStackedWidget."""
super().__init__(*args, **kwargs)
# Corresponds to stream security
@@ -630,15 +707,19 @@ class GuiVTLSPageStackedWidget(QStackedWidget):
self.addWidget(page)
def page(self, index: int) -> GuiVTLSPageXXX:
+ """Return the page value."""
return self._pages[index]
def connectActivated(self, func: Callable):
+ """Connect activated."""
for page in self._pages:
page.connectActivated(func)
class GuiVTLSQGroupBox(GuiEditorItemFactory, AppQGroupBox):
+ """Group the GUI vtlsq editor controls."""
def __init__(self, **kwargs):
+ """Initialize the GuiVTLSQGroupBox."""
translatable = kwargs.pop('translatable', False)
super().__init__('TLS', **kwargs, translatable=translatable)
@@ -654,15 +735,19 @@ class GuiVTLSQGroupBox(GuiEditorItemFactory, AppQGroupBox):
self.setLayout(layout)
def currentIndex(self) -> int:
+ """Return the current index value."""
return self._widget.currentIndex()
def setCurrentIndex(self, index: int):
+ """Set current index."""
self._widget.setCurrentIndex(index)
def page(self, index: int) -> GuiVTLSPageXXX:
+ """Return the page value."""
return self._widget.page(index)
def handleActivated(self, index: int):
+ """Handle activated."""
page = self.page(index)
page.factoryToInput(self._config)
page.setSecurityText(STREAM_SECURITY[index])
@@ -670,10 +755,12 @@ class GuiVTLSQGroupBox(GuiEditorItemFactory, AppQGroupBox):
self.setCurrentIndex(index)
def inputToFactory(self, config: ConfigFactory) -> bool:
+ """Apply the current editor value to the configuration."""
return self.page(self.currentIndex()).inputToFactory(config)
def factoryToInput(self, config: ConfigFactory):
# Shallow copy
+ """Load the configuration value into the editor."""
self._config = config
streamSettings = ConfigXray.getProxyOutboundStream(config)
diff --git a/Furious/Widget/GuiVTransport.py b/Furious/Widget/GuiVTransport.py
index 07fd4631..9fd53359 100644
--- a/Furious/Widget/GuiVTransport.py
+++ b/Furious/Widget/GuiVTransport.py
@@ -15,6 +15,8 @@
# You should have received a copy of the GNU General Public License
# along with this program. If not, see .
+"""Provide widgets for GUI v transport."""
+
from __future__ import annotations
from Furious.Interface import *
@@ -44,12 +46,15 @@ STREAM_NETWORK = [
class GuiVTransportItemNetwork(GuiEditorItemTextComboBox):
+ """Represent GUI v transport item network."""
def __init__(self, *args, **kwargs):
+ """Initialize the GuiVTransportItemNetwork."""
super().__init__(*args, **kwargs)
self.addItems(STREAM_NETWORK)
def inputToFactory(self, config: ConfigFactory) -> bool:
+ """Apply the current editor value to the configuration."""
streamSettings = ConfigXray.getProxyOutboundStream(config)
try:
@@ -62,6 +67,7 @@ class GuiVTransportItemNetwork(GuiEditorItemTextComboBox):
newNetwork = self.text()
def setNewNetwork():
+ """Set new network."""
streamSettings['network'] = newNetwork
for network in STREAM_NETWORK:
@@ -89,6 +95,7 @@ class GuiVTransportItemNetwork(GuiEditorItemTextComboBox):
return True
def factoryToInput(self, config: ConfigFactory):
+ """Load the configuration value into the editor."""
try:
streamSettings = ConfigXray.getProxyOutboundStream(config)
@@ -100,10 +107,13 @@ class GuiVTransportItemNetwork(GuiEditorItemTextComboBox):
class GuiVTransportItemFinalMask(GuiEditorItemTextInput):
+ """Represent GUI v transport item final mask."""
def __init__(self, *args, **kwargs):
+ """Initialize the GuiVTransportItemFinalMask."""
super().__init__(*args, **kwargs)
def inputToFactory(self, config: ConfigFactory) -> bool:
+ """Apply the current editor value to the configuration."""
streamSettings = ConfigXray.getProxyOutboundStream(config)
oldFinalMaskObject = streamSettings.get('finalmask')
@@ -137,6 +147,7 @@ class GuiVTransportItemFinalMask(GuiEditorItemTextInput):
return False
def factoryToInput(self, config: ConfigFactory):
+ """Load the configuration value into the editor."""
try:
finalMaskObject = ConfigXray.getProxyOutboundStream(config)['finalmask']
@@ -148,7 +159,9 @@ class GuiVTransportItemFinalMask(GuiEditorItemTextInput):
class GuiVTransportItemTypeXXX(GuiEditorItemTextComboBox):
+ """Represent GUI v transport item type xxx."""
def __init__(self, *args, **kwargs):
+ """Initialize the GuiVTransportItemTypeXXX."""
networkKey = kwargs.pop('networkKey', '')
super().__init__(*args, **kwargs)
@@ -156,6 +169,7 @@ class GuiVTransportItemTypeXXX(GuiEditorItemTextComboBox):
self.networkKey = networkKey
def inputToFactory(self, config: ConfigFactory) -> bool:
+ """Apply the current editor value to the configuration."""
streamSettings = ConfigXray.getProxyOutboundStream(config)
if not isinstance(streamSettings.get(self.networkKey), dict):
@@ -173,6 +187,7 @@ class GuiVTransportItemTypeXXX(GuiEditorItemTextComboBox):
newType = self.text()
def setNewType():
+ """Set new type."""
if not isinstance(xxxObject.get('header'), dict):
xxxObject['header'] = {}
@@ -194,6 +209,7 @@ class GuiVTransportItemTypeXXX(GuiEditorItemTextComboBox):
return True
def factoryToInput(self, config: ConfigFactory):
+ """Load the configuration value into the editor."""
try:
xxxObject = ConfigXray.getProxyOutboundStream(config)[self.networkKey]
@@ -205,7 +221,9 @@ class GuiVTransportItemTypeXXX(GuiEditorItemTextComboBox):
class GuiVTransportItemTypeTcpOrRaw(GuiVTransportItemTypeXXX):
+ """Represent GUI v transport item type TCP or raw."""
def __init__(self, *args, **kwargs):
+ """Initialize the GuiVTransportItemTypeTcpOrRaw."""
networkKey = kwargs.pop('networkKey', 'tcpSettings')
super().__init__(*args, **kwargs, networkKey=networkKey)
@@ -220,7 +238,9 @@ class GuiVTransportItemTypeTcpOrRaw(GuiVTransportItemTypeXXX):
class GuiVTransportItemHostTcpOrRaw(GuiEditorItemTextInput):
+ """Represent GUI v transport item host TCP or raw."""
def __init__(self, *args, **kwargs):
+ """Initialize the GuiVTransportItemHostTcpOrRaw."""
networkKey = kwargs.pop('networkKey', 'tcpSettings')
super().__init__(*args, **kwargs)
@@ -228,6 +248,7 @@ class GuiVTransportItemHostTcpOrRaw(GuiEditorItemTextInput):
self.networkKey = networkKey
def inputToFactory(self, config: ConfigFactory) -> bool:
+ """Apply the current editor value to the configuration."""
streamSettings = ConfigXray.getProxyOutboundStream(config)
if not isinstance(streamSettings.get(self.networkKey), dict):
@@ -245,6 +266,7 @@ class GuiVTransportItemHostTcpOrRaw(GuiEditorItemTextInput):
newHost = self.text()
def setNewHost():
+ """Set new host."""
if not isinstance(tcpObject.get('header'), dict):
tcpObject['header'] = {}
@@ -272,6 +294,7 @@ class GuiVTransportItemHostTcpOrRaw(GuiEditorItemTextInput):
return True
def factoryToInput(self, config: ConfigFactory):
+ """Load the configuration value into the editor."""
try:
tcpObject = ConfigXray.getProxyOutboundStream(config)[self.networkKey]
@@ -283,7 +306,9 @@ class GuiVTransportItemHostTcpOrRaw(GuiEditorItemTextInput):
class GuiVTransportItemPathTcpOrRaw(GuiEditorItemTextInput):
+ """Represent GUI v transport item path TCP or raw."""
def __init__(self, *args, **kwargs):
+ """Initialize the GuiVTransportItemPathTcpOrRaw."""
networkKey = kwargs.pop('networkKey', 'tcpSettings')
super().__init__(*args, **kwargs)
@@ -291,6 +316,7 @@ class GuiVTransportItemPathTcpOrRaw(GuiEditorItemTextInput):
self.networkKey = networkKey
def inputToFactory(self, config: ConfigFactory) -> bool:
+ """Apply the current editor value to the configuration."""
streamSettings = ConfigXray.getProxyOutboundStream(config)
if not isinstance(streamSettings.get(self.networkKey), dict):
@@ -308,6 +334,7 @@ class GuiVTransportItemPathTcpOrRaw(GuiEditorItemTextInput):
newPath = self.text()
def setNewPath():
+ """Set new path."""
if not isinstance(tcpObject.get('header'), dict):
tcpObject['header'] = {}
@@ -332,6 +359,7 @@ class GuiVTransportItemPathTcpOrRaw(GuiEditorItemTextInput):
return True
def factoryToInput(self, config: ConfigFactory):
+ """Load the configuration value into the editor."""
try:
tcpObject = ConfigXray.getProxyOutboundStream(config)[self.networkKey]
@@ -343,7 +371,9 @@ class GuiVTransportItemPathTcpOrRaw(GuiEditorItemTextInput):
class GuiVTransportItemTypeKcp(GuiVTransportItemTypeXXX):
+ """Represent GUI v transport item type kcp."""
def __init__(self, *args, **kwargs):
+ """Initialize the GuiVTransportItemTypeKcp."""
networkKey = kwargs.pop('networkKey', 'kcpSettings')
super().__init__(*args, **kwargs, networkKey=networkKey)
@@ -363,10 +393,13 @@ class GuiVTransportItemTypeKcp(GuiVTransportItemTypeXXX):
class GuiVTransportItemSeedKcp(GuiEditorItemTextInput):
+ """Represent GUI v transport item seed kcp."""
def __init__(self, *args, **kwargs):
+ """Initialize the GuiVTransportItemSeedKcp."""
super().__init__(*args, **kwargs)
def inputToFactory(self, config: ConfigFactory) -> bool:
+ """Apply the current editor value to the configuration."""
streamSettings = ConfigXray.getProxyOutboundStream(config)
if not isinstance(streamSettings.get('kcpSettings'), dict):
@@ -384,6 +417,7 @@ class GuiVTransportItemSeedKcp(GuiEditorItemTextInput):
newSeed = self.text()
def setNewSeed():
+ """Set new seed."""
if newSeed == '':
kcpObject.pop('seed', None)
else:
@@ -402,6 +436,7 @@ class GuiVTransportItemSeedKcp(GuiEditorItemTextInput):
return True
def factoryToInput(self, config: ConfigFactory):
+ """Load the configuration value into the editor."""
try:
kcpObject = ConfigXray.getProxyOutboundStream(config)['kcpSettings']
@@ -413,10 +448,13 @@ class GuiVTransportItemSeedKcp(GuiEditorItemTextInput):
class GuiVTransportItemHostWs(GuiEditorItemTextInput):
+ """Represent GUI v transport item host ws."""
def __init__(self, *args, **kwargs):
+ """Initialize the GuiVTransportItemHostWs."""
super().__init__(*args, **kwargs)
def inputToFactory(self, config: ConfigFactory) -> bool:
+ """Apply the current editor value to the configuration."""
streamSettings = ConfigXray.getProxyOutboundStream(config)
if not isinstance(streamSettings.get('wsSettings'), dict):
@@ -434,6 +472,7 @@ class GuiVTransportItemHostWs(GuiEditorItemTextInput):
newHost = self.text()
def setNewHost():
+ """Set new host."""
if not isinstance(wsObject.get('headers'), dict):
wsObject['headers'] = {}
@@ -455,6 +494,7 @@ class GuiVTransportItemHostWs(GuiEditorItemTextInput):
return True
def factoryToInput(self, config: ConfigFactory):
+ """Load the configuration value into the editor."""
try:
wsObject = ConfigXray.getProxyOutboundStream(config)['wsSettings']
@@ -466,10 +506,13 @@ class GuiVTransportItemHostWs(GuiEditorItemTextInput):
class GuiVTransportItemPathWs(GuiEditorItemTextInput):
+ """Represent GUI v transport item path ws."""
def __init__(self, *args, **kwargs):
+ """Initialize the GuiVTransportItemPathWs."""
super().__init__(*args, **kwargs)
def inputToFactory(self, config: ConfigFactory) -> bool:
+ """Apply the current editor value to the configuration."""
streamSettings = ConfigXray.getProxyOutboundStream(config)
if not isinstance(streamSettings.get('wsSettings'), dict):
@@ -487,6 +530,7 @@ class GuiVTransportItemPathWs(GuiEditorItemTextInput):
newPath = self.text()
def setNewPath():
+ """Set new path."""
if newPath == '':
wsObject.pop('path', None)
else:
@@ -505,6 +549,7 @@ class GuiVTransportItemPathWs(GuiEditorItemTextInput):
return True
def factoryToInput(self, config: ConfigFactory):
+ """Load the configuration value into the editor."""
try:
wsObject = ConfigXray.getProxyOutboundStream(config)['wsSettings']
@@ -516,10 +561,13 @@ class GuiVTransportItemPathWs(GuiEditorItemTextInput):
class GuiVTransportItemHostHttpUpgrade(GuiEditorItemTextInput):
+ """Represent GUI v transport item host HTTP upgrade."""
def __init__(self, *args, **kwargs):
+ """Initialize the GuiVTransportItemHostHttpUpgrade."""
super().__init__(*args, **kwargs)
def inputToFactory(self, config: ConfigFactory) -> bool:
+ """Apply the current editor value to the configuration."""
streamSettings = ConfigXray.getProxyOutboundStream(config)
if not isinstance(streamSettings.get('httpupgradeSettings'), dict):
@@ -537,6 +585,7 @@ class GuiVTransportItemHostHttpUpgrade(GuiEditorItemTextInput):
newHost = self.text()
def setNewHost():
+ """Set new host."""
if newHost == '':
httpUpgradeObject.pop('host', None)
else:
@@ -555,6 +604,7 @@ class GuiVTransportItemHostHttpUpgrade(GuiEditorItemTextInput):
return True
def factoryToInput(self, config: ConfigFactory):
+ """Load the configuration value into the editor."""
try:
httpUpgradeObject = ConfigXray.getProxyOutboundStream(config)[
'httpupgradeSettings'
@@ -568,10 +618,13 @@ class GuiVTransportItemHostHttpUpgrade(GuiEditorItemTextInput):
class GuiVTransportItemPathHttpUpgrade(GuiEditorItemTextInput):
+ """Represent GUI v transport item path HTTP upgrade."""
def __init__(self, *args, **kwargs):
+ """Initialize the GuiVTransportItemPathHttpUpgrade."""
super().__init__(*args, **kwargs)
def inputToFactory(self, config: ConfigFactory) -> bool:
+ """Apply the current editor value to the configuration."""
streamSettings = ConfigXray.getProxyOutboundStream(config)
if not isinstance(streamSettings.get('httpupgradeSettings'), dict):
@@ -589,6 +642,7 @@ class GuiVTransportItemPathHttpUpgrade(GuiEditorItemTextInput):
newPath = self.text()
def setNewPath():
+ """Set new path."""
if newPath == '':
httpUpgradeObject.pop('path', None)
else:
@@ -607,6 +661,7 @@ class GuiVTransportItemPathHttpUpgrade(GuiEditorItemTextInput):
return True
def factoryToInput(self, config: ConfigFactory):
+ """Load the configuration value into the editor."""
try:
httpUpgradeObject = ConfigXray.getProxyOutboundStream(config)[
'httpupgradeSettings'
@@ -620,10 +675,13 @@ class GuiVTransportItemPathHttpUpgrade(GuiEditorItemTextInput):
class GuiVTransportItemHostSplitHttp(GuiEditorItemTextInput):
+ """Represent GUI v transport item host split HTTP."""
def __init__(self, *args, **kwargs):
+ """Initialize the GuiVTransportItemHostSplitHttp."""
super().__init__(*args, **kwargs)
def inputToFactory(self, config: ConfigFactory) -> bool:
+ """Apply the current editor value to the configuration."""
streamSettings = ConfigXray.getProxyOutboundStream(config)
if not isinstance(streamSettings.get('splithttpSettings'), dict):
@@ -641,6 +699,7 @@ class GuiVTransportItemHostSplitHttp(GuiEditorItemTextInput):
newHost = self.text()
def setNewHost():
+ """Set new host."""
if newHost == '':
splitHttpObject.pop('host', None)
else:
@@ -659,6 +718,7 @@ class GuiVTransportItemHostSplitHttp(GuiEditorItemTextInput):
return True
def factoryToInput(self, config: ConfigFactory):
+ """Load the configuration value into the editor."""
try:
splitHttpObject = ConfigXray.getProxyOutboundStream(config)[
'splithttpSettings'
@@ -672,10 +732,13 @@ class GuiVTransportItemHostSplitHttp(GuiEditorItemTextInput):
class GuiVTransportItemPathSplitHttp(GuiEditorItemTextInput):
+ """Represent GUI v transport item path split HTTP."""
def __init__(self, *args, **kwargs):
+ """Initialize the GuiVTransportItemPathSplitHttp."""
super().__init__(*args, **kwargs)
def inputToFactory(self, config: ConfigFactory) -> bool:
+ """Apply the current editor value to the configuration."""
streamSettings = ConfigXray.getProxyOutboundStream(config)
if not isinstance(streamSettings.get('splithttpSettings'), dict):
@@ -693,6 +756,7 @@ class GuiVTransportItemPathSplitHttp(GuiEditorItemTextInput):
newPath = self.text()
def setNewPath():
+ """Set new path."""
if newPath == '':
splitHttpObject.pop('path', None)
else:
@@ -711,6 +775,7 @@ class GuiVTransportItemPathSplitHttp(GuiEditorItemTextInput):
return True
def factoryToInput(self, config: ConfigFactory):
+ """Load the configuration value into the editor."""
try:
splitHttpObject = ConfigXray.getProxyOutboundStream(config)[
'splithttpSettings'
@@ -724,10 +789,13 @@ class GuiVTransportItemPathSplitHttp(GuiEditorItemTextInput):
class GuiVTransportItemHostXHttp(GuiEditorItemTextInput):
+ """Represent GUI v transport item host x HTTP."""
def __init__(self, *args, **kwargs):
+ """Initialize the GuiVTransportItemHostXHttp."""
super().__init__(*args, **kwargs)
def inputToFactory(self, config: ConfigFactory) -> bool:
+ """Apply the current editor value to the configuration."""
streamSettings = ConfigXray.getProxyOutboundStream(config)
if not isinstance(streamSettings.get('xhttpSettings'), dict):
@@ -745,6 +813,7 @@ class GuiVTransportItemHostXHttp(GuiEditorItemTextInput):
newHost = self.text()
def setNewHost():
+ """Set new host."""
if newHost == '':
xhttpObject.pop('host', None)
else:
@@ -763,6 +832,7 @@ class GuiVTransportItemHostXHttp(GuiEditorItemTextInput):
return True
def factoryToInput(self, config: ConfigFactory):
+ """Load the configuration value into the editor."""
try:
xhttpObject = ConfigXray.getProxyOutboundStream(config)['xhttpSettings']
@@ -774,10 +844,13 @@ class GuiVTransportItemHostXHttp(GuiEditorItemTextInput):
class GuiVTransportItemPathXHttp(GuiEditorItemTextInput):
+ """Represent GUI v transport item path x HTTP."""
def __init__(self, *args, **kwargs):
+ """Initialize the GuiVTransportItemPathXHttp."""
super().__init__(*args, **kwargs)
def inputToFactory(self, config: ConfigFactory) -> bool:
+ """Apply the current editor value to the configuration."""
streamSettings = ConfigXray.getProxyOutboundStream(config)
if not isinstance(streamSettings.get('xhttpSettings'), dict):
@@ -795,6 +868,7 @@ class GuiVTransportItemPathXHttp(GuiEditorItemTextInput):
newPath = self.text()
def setNewPath():
+ """Set new path."""
if newPath == '':
xhttpObject.pop('path', None)
else:
@@ -813,6 +887,7 @@ class GuiVTransportItemPathXHttp(GuiEditorItemTextInput):
return True
def factoryToInput(self, config: ConfigFactory):
+ """Load the configuration value into the editor."""
try:
xhttpObject = ConfigXray.getProxyOutboundStream(config)['xhttpSettings']
@@ -824,7 +899,9 @@ class GuiVTransportItemPathXHttp(GuiEditorItemTextInput):
class GuiVTransportItemModeXHttp(GuiEditorItemTextComboBox):
+ """Represent GUI v transport item mode x HTTP."""
def __init__(self, *args, **kwargs):
+ """Initialize the GuiVTransportItemModeXHttp."""
super().__init__(*args, **kwargs)
self.addItems(
@@ -837,6 +914,7 @@ class GuiVTransportItemModeXHttp(GuiEditorItemTextComboBox):
)
def inputToFactory(self, config: ConfigFactory) -> bool:
+ """Apply the current editor value to the configuration."""
streamSettings = ConfigXray.getProxyOutboundStream(config)
if not isinstance(streamSettings.get('xhttpSettings'), dict):
@@ -854,6 +932,7 @@ class GuiVTransportItemModeXHttp(GuiEditorItemTextComboBox):
newMode = self.text()
def setNewMode():
+ """Set new mode."""
if newMode == '':
xhttpObject.pop('mode', None)
else:
@@ -872,6 +951,7 @@ class GuiVTransportItemModeXHttp(GuiEditorItemTextComboBox):
return True
def factoryToInput(self, config: ConfigFactory):
+ """Load the configuration value into the editor."""
try:
xhttpObject = ConfigXray.getProxyOutboundStream(config)['xhttpSettings']
@@ -883,10 +963,13 @@ class GuiVTransportItemModeXHttp(GuiEditorItemTextComboBox):
class GuiVTransportItemExtraXHttp(GuiEditorItemTextInput):
+ """Represent GUI v transport item extra x HTTP."""
def __init__(self, *args, **kwargs):
+ """Initialize the GuiVTransportItemExtraXHttp."""
super().__init__(*args, **kwargs)
def inputToFactory(self, config: ConfigFactory) -> bool:
+ """Apply the current editor value to the configuration."""
streamSettings = ConfigXray.getProxyOutboundStream(config)
if not isinstance(streamSettings.get('xhttpSettings'), dict):
@@ -907,6 +990,7 @@ class GuiVTransportItemExtraXHttp(GuiEditorItemTextInput):
newExtra = self.text()
def setNewExtra():
+ """Set new extra."""
if newExtra == '':
xhttpObject.pop('extra', None)
else:
@@ -930,6 +1014,7 @@ class GuiVTransportItemExtraXHttp(GuiEditorItemTextInput):
return True
def factoryToInput(self, config: ConfigFactory):
+ """Load the configuration value into the editor."""
try:
xhttpObject = ConfigXray.getProxyOutboundStream(config)['xhttpSettings']
@@ -944,10 +1029,13 @@ class GuiVTransportItemExtraXHttp(GuiEditorItemTextInput):
class GuiVTransportItemHostH2(GuiEditorItemTextInput):
+ """Represent GUI v transport item host h2."""
def __init__(self, *args, **kwargs):
+ """Initialize the GuiVTransportItemHostH2."""
super().__init__(*args, **kwargs)
def inputToFactory(self, config: ConfigFactory) -> bool:
+ """Apply the current editor value to the configuration."""
streamSettings = ConfigXray.getProxyOutboundStream(config)
if not isinstance(streamSettings.get('httpSettings'), dict):
@@ -965,6 +1053,7 @@ class GuiVTransportItemHostH2(GuiEditorItemTextInput):
newHost = self.text()
def setNewHost():
+ """Set new host."""
if newHost == '':
httpObject.pop('host', None)
else:
@@ -983,6 +1072,7 @@ class GuiVTransportItemHostH2(GuiEditorItemTextInput):
return True
def factoryToInput(self, config: ConfigFactory):
+ """Load the configuration value into the editor."""
try:
httpObject = ConfigXray.getProxyOutboundStream(config)['httpSettings']
@@ -994,10 +1084,13 @@ class GuiVTransportItemHostH2(GuiEditorItemTextInput):
class GuiVTransportItemPathH2(GuiEditorItemTextInput):
+ """Represent GUI v transport item path h2."""
def __init__(self, *args, **kwargs):
+ """Initialize the GuiVTransportItemPathH2."""
super().__init__(*args, **kwargs)
def inputToFactory(self, config: ConfigFactory) -> bool:
+ """Apply the current editor value to the configuration."""
streamSettings = ConfigXray.getProxyOutboundStream(config)
if not isinstance(streamSettings.get('httpSettings'), dict):
@@ -1015,6 +1108,7 @@ class GuiVTransportItemPathH2(GuiEditorItemTextInput):
newPath = self.text()
def setNewPath():
+ """Set new path."""
if newPath == '':
httpObject.pop('path', None)
else:
@@ -1033,6 +1127,7 @@ class GuiVTransportItemPathH2(GuiEditorItemTextInput):
return True
def factoryToInput(self, config: ConfigFactory):
+ """Load the configuration value into the editor."""
try:
httpObject = ConfigXray.getProxyOutboundStream(config)['httpSettings']
@@ -1044,7 +1139,9 @@ class GuiVTransportItemPathH2(GuiEditorItemTextInput):
class GuiVTransportItemTypeQuic(GuiVTransportItemTypeXXX):
+ """Represent GUI v transport item type quic."""
def __init__(self, *args, **kwargs):
+ """Initialize the GuiVTransportItemTypeQuic."""
networkKey = kwargs.pop('networkKey', 'quicSettings')
super().__init__(*args, **kwargs, networkKey=networkKey)
@@ -1063,7 +1160,9 @@ class GuiVTransportItemTypeQuic(GuiVTransportItemTypeXXX):
class GuiVTransportItemSecurityQuic(GuiEditorItemTextComboBox):
+ """Represent GUI v transport item security quic."""
def __init__(self, *args, **kwargs):
+ """Initialize the GuiVTransportItemSecurityQuic."""
super().__init__(*args, **kwargs)
self.addItems(
@@ -1076,6 +1175,7 @@ class GuiVTransportItemSecurityQuic(GuiEditorItemTextComboBox):
)
def inputToFactory(self, config: ConfigFactory) -> bool:
+ """Apply the current editor value to the configuration."""
streamSettings = ConfigXray.getProxyOutboundStream(config)
if not isinstance(streamSettings.get('quicSettings'), dict):
@@ -1093,6 +1193,7 @@ class GuiVTransportItemSecurityQuic(GuiEditorItemTextComboBox):
newSecurity = self.text()
def setNewSecurity():
+ """Set new security."""
quicObject['security'] = newSecurity
if isinstance(oldSecurity, str):
@@ -1108,6 +1209,7 @@ class GuiVTransportItemSecurityQuic(GuiEditorItemTextComboBox):
return True
def factoryToInput(self, config: ConfigFactory):
+ """Load the configuration value into the editor."""
try:
quicObject = ConfigXray.getProxyOutboundStream(config)['quicSettings']
@@ -1119,10 +1221,13 @@ class GuiVTransportItemSecurityQuic(GuiEditorItemTextComboBox):
class GuiVTransportItemKeyQuic(GuiEditorItemTextInput):
+ """Represent GUI v transport item key quic."""
def __init__(self, *args, **kwargs):
+ """Initialize the GuiVTransportItemKeyQuic."""
super().__init__(*args, **kwargs)
def inputToFactory(self, config: ConfigFactory) -> bool:
+ """Apply the current editor value to the configuration."""
streamSettings = ConfigXray.getProxyOutboundStream(config)
if not isinstance(streamSettings.get('quicSettings'), dict):
@@ -1140,6 +1245,7 @@ class GuiVTransportItemKeyQuic(GuiEditorItemTextInput):
newKey = self.text()
def setNewKey():
+ """Set new key."""
if newKey == '':
quicObject.pop('key', None)
else:
@@ -1158,6 +1264,7 @@ class GuiVTransportItemKeyQuic(GuiEditorItemTextInput):
return True
def factoryToInput(self, config: ConfigFactory):
+ """Load the configuration value into the editor."""
try:
quicObject = ConfigXray.getProxyOutboundStream(config)['quicSettings']
@@ -1169,7 +1276,9 @@ class GuiVTransportItemKeyQuic(GuiEditorItemTextInput):
class GuiVTransportItemModeGRPC(GuiEditorItemTextComboBox):
+ """Represent GUI v transport item mode grpc."""
def __init__(self, *args, **kwargs):
+ """Initialize the GuiVTransportItemModeGRPC."""
super().__init__(*args, **kwargs)
self.addItems(
@@ -1181,6 +1290,7 @@ class GuiVTransportItemModeGRPC(GuiEditorItemTextComboBox):
)
def inputToFactory(self, config: ConfigFactory) -> bool:
+ """Apply the current editor value to the configuration."""
streamSettings = ConfigXray.getProxyOutboundStream(config)
if not isinstance(streamSettings.get('grpcSettings'), dict):
@@ -1203,6 +1313,7 @@ class GuiVTransportItemModeGRPC(GuiEditorItemTextComboBox):
newMode = self.text()
def setNewMode():
+ """Set new mode."""
if newMode == 'multi':
grpcObject['multiMode'] = True
else:
@@ -1221,6 +1332,7 @@ class GuiVTransportItemModeGRPC(GuiEditorItemTextComboBox):
return True
def factoryToInput(self, config: ConfigFactory):
+ """Load the configuration value into the editor."""
try:
grpcObject = ConfigXray.getProxyOutboundStream(config)['grpcSettings']
@@ -1239,10 +1351,13 @@ class GuiVTransportItemModeGRPC(GuiEditorItemTextComboBox):
class GuiVTransportItemAuthorityGRPC(GuiEditorItemTextInput):
+ """Represent GUI v transport item authority grpc."""
def __init__(self, *args, **kwargs):
+ """Initialize the GuiVTransportItemAuthorityGRPC."""
super().__init__(*args, **kwargs)
def inputToFactory(self, config: ConfigFactory) -> bool:
+ """Apply the current editor value to the configuration."""
streamSettings = ConfigXray.getProxyOutboundStream(config)
if not isinstance(streamSettings.get('grpcSettings'), dict):
@@ -1260,6 +1375,7 @@ class GuiVTransportItemAuthorityGRPC(GuiEditorItemTextInput):
newAuthority = self.text()
def setNewAuthority():
+ """Set new authority."""
if newAuthority == '':
grpcObject.pop('authority', None)
else:
@@ -1278,6 +1394,7 @@ class GuiVTransportItemAuthorityGRPC(GuiEditorItemTextInput):
return True
def factoryToInput(self, config: ConfigFactory):
+ """Load the configuration value into the editor."""
try:
grpcObject = ConfigXray.getProxyOutboundStream(config)['grpcSettings']
@@ -1289,10 +1406,13 @@ class GuiVTransportItemAuthorityGRPC(GuiEditorItemTextInput):
class GuiVTransportItemServiceNameGRPC(GuiEditorItemTextInput):
+ """Represent GUI v transport item service name grpc."""
def __init__(self, *args, **kwargs):
+ """Initialize the GuiVTransportItemServiceNameGRPC."""
super().__init__(*args, **kwargs)
def inputToFactory(self, config: ConfigFactory) -> bool:
+ """Apply the current editor value to the configuration."""
streamSettings = ConfigXray.getProxyOutboundStream(config)
if not isinstance(streamSettings.get('grpcSettings'), dict):
@@ -1310,6 +1430,7 @@ class GuiVTransportItemServiceNameGRPC(GuiEditorItemTextInput):
newServiceName = self.text()
def setNewServiceName():
+ """Set new service name."""
if newServiceName == '':
grpcObject.pop('serviceName', None)
else:
@@ -1328,6 +1449,7 @@ class GuiVTransportItemServiceNameGRPC(GuiEditorItemTextInput):
return True
def factoryToInput(self, config: ConfigFactory):
+ """Load the configuration value into the editor."""
try:
grpcObject = ConfigXray.getProxyOutboundStream(config)['grpcSettings']
@@ -1339,13 +1461,16 @@ class GuiVTransportItemServiceNameGRPC(GuiEditorItemTextInput):
class GuiVTransportItemVersionHysteria(GuiEditorItemTextSpinBox):
+ """Represent GUI v transport item version hysteria."""
def __init__(self, *args, **kwargs):
+ """Initialize the GuiVTransportItemVersionHysteria."""
super().__init__(*args, **kwargs)
# Range. 0 means invalid
self.setRange(0, 2)
def inputToFactory(self, config: ConfigFactory) -> bool:
+ """Apply the current editor value to the configuration."""
streamSettings = ConfigXray.getProxyOutboundStream(config)
if not isinstance(streamSettings.get('hysteriaSettings'), dict):
@@ -1369,6 +1494,7 @@ class GuiVTransportItemVersionHysteria(GuiEditorItemTextSpinBox):
return True
def factoryToInput(self, config: ConfigFactory):
+ """Load the configuration value into the editor."""
try:
hysteriaObject = ConfigXray.getProxyOutboundStream(config)[
'hysteriaSettings'
@@ -1382,10 +1508,13 @@ class GuiVTransportItemVersionHysteria(GuiEditorItemTextSpinBox):
class GuiVTransportItemAuthHysteria(GuiEditorItemTextInput):
+ """Represent GUI v transport item auth hysteria."""
def __init__(self, *args, **kwargs):
+ """Initialize the GuiVTransportItemAuthHysteria."""
super().__init__(*args, **kwargs)
def inputToFactory(self, config: ConfigFactory) -> bool:
+ """Apply the current editor value to the configuration."""
streamSettings = ConfigXray.getProxyOutboundStream(config)
if not isinstance(streamSettings.get('hysteriaSettings'), dict):
@@ -1403,6 +1532,7 @@ class GuiVTransportItemAuthHysteria(GuiEditorItemTextInput):
newAuth = self.text()
def setNewAuth():
+ """Set new auth."""
if newAuth == '':
hysteriaObject.pop('auth', None)
else:
@@ -1421,6 +1551,7 @@ class GuiVTransportItemAuthHysteria(GuiEditorItemTextInput):
return True
def factoryToInput(self, config: ConfigFactory):
+ """Load the configuration value into the editor."""
try:
hysteriaObject = ConfigXray.getProxyOutboundStream(config)[
'hysteriaSettings'
@@ -1434,10 +1565,13 @@ class GuiVTransportItemAuthHysteria(GuiEditorItemTextInput):
class GuiVTransportItemPasswordHysteria(GuiEditorItemTextInput):
+ """Represent GUI v transport item password hysteria."""
def __init__(self, *args, **kwargs):
+ """Initialize the GuiVTransportItemPasswordHysteria."""
super().__init__(*args, **kwargs)
def inputToFactory(self, config: ConfigFactory) -> bool:
+ """Apply the current editor value to the configuration."""
streamSettings = ConfigXray.getProxyOutboundStream(config)
if not isinstance(streamSettings.get('finalmask'), dict):
@@ -1460,6 +1594,7 @@ class GuiVTransportItemPasswordHysteria(GuiEditorItemTextInput):
newPassword = self.text()
def setNewPassword():
+ """Set new password."""
if newPassword == '':
finalmaskObject.pop('salamander', None)
else:
@@ -1478,6 +1613,7 @@ class GuiVTransportItemPasswordHysteria(GuiEditorItemTextInput):
return True
def factoryToInput(self, config: ConfigFactory):
+ """Load the configuration value into the editor."""
try:
salamander = ConfigXray.getProxyOutboundStream(config)['finalmask'][
'salamander'
@@ -1491,16 +1627,20 @@ class GuiVTransportItemPasswordHysteria(GuiEditorItemTextInput):
class GuiVTransportPageXXX(GuiEditorWidgetQWidget):
+ """Represent GUI v transport page xxx."""
def __init__(self, *args, **kwargs):
+ """Initialize the GuiVTransportPageXXX."""
super().__init__(*args, **kwargs)
def setNetworkText(self, text: str):
+ """Set network text."""
network = self._containers[0]
if isinstance(network, GuiEditorItemTextComboBox):
network.setText(text)
def connectActivated(self, func: Callable):
+ """Connect activated."""
network = self._containers[0]
if isinstance(network, GuiEditorItemTextComboBox):
@@ -1508,10 +1648,13 @@ class GuiVTransportPageXXX(GuiEditorWidgetQWidget):
class GuiVTransportPageTcp(GuiVTransportPageXXX):
+ """Represent GUI v transport page TCP."""
def __init__(self, *args, **kwargs):
+ """Initialize the GuiVTransportPageTcp."""
super().__init__(*args, **kwargs)
def containerSequence(self):
+ """Return the editor item containers in display order."""
return [
GuiVTransportItemNetwork(title='Network', translatable=False),
GuiVTransportItemFinalMask(title='Finalmask', translatable=False),
@@ -1528,10 +1671,13 @@ class GuiVTransportPageTcp(GuiVTransportPageXXX):
class GuiVTransportPageRaw(GuiVTransportPageXXX):
+ """Represent GUI v transport page raw."""
def __init__(self, *args, **kwargs):
+ """Initialize the GuiVTransportPageRaw."""
super().__init__(*args, **kwargs)
def containerSequence(self):
+ """Return the editor item containers in display order."""
return [
GuiVTransportItemNetwork(title='Network', translatable=False),
GuiVTransportItemFinalMask(title='Finalmask', translatable=False),
@@ -1548,10 +1694,13 @@ class GuiVTransportPageRaw(GuiVTransportPageXXX):
class GuiVTransportPageKcp(GuiVTransportPageXXX):
+ """Represent GUI v transport page kcp."""
def __init__(self, *args, **kwargs):
+ """Initialize the GuiVTransportPageKcp."""
super().__init__(*args, **kwargs)
def containerSequence(self):
+ """Return the editor item containers in display order."""
return [
GuiVTransportItemNetwork(title='Network', translatable=False),
GuiVTransportItemFinalMask(title='Finalmask', translatable=False),
@@ -1561,10 +1710,13 @@ class GuiVTransportPageKcp(GuiVTransportPageXXX):
class GuiVTransportPageWs(GuiVTransportPageXXX):
+ """Represent GUI v transport page ws."""
def __init__(self, *args, **kwargs):
+ """Initialize the GuiVTransportPageWs."""
super().__init__(*args, **kwargs)
def containerSequence(self):
+ """Return the editor item containers in display order."""
return [
GuiVTransportItemNetwork(title='Network', translatable=False),
GuiVTransportItemFinalMask(title='Finalmask', translatable=False),
@@ -1574,10 +1726,13 @@ class GuiVTransportPageWs(GuiVTransportPageXXX):
class GuiVTransportPageHttpUpgrade(GuiVTransportPageXXX):
+ """Represent GUI v transport page HTTP upgrade."""
def __init__(self, *args, **kwargs):
+ """Initialize the GuiVTransportPageHttpUpgrade."""
super().__init__(*args, **kwargs)
def containerSequence(self):
+ """Return the editor item containers in display order."""
return [
GuiVTransportItemNetwork(title='Network', translatable=False),
GuiVTransportItemFinalMask(title='Finalmask', translatable=False),
@@ -1587,10 +1742,13 @@ class GuiVTransportPageHttpUpgrade(GuiVTransportPageXXX):
class GuiVTransportPageSplitHttp(GuiVTransportPageXXX):
+ """Represent GUI v transport page split HTTP."""
def __init__(self, *args, **kwargs):
+ """Initialize the GuiVTransportPageSplitHttp."""
super().__init__(*args, **kwargs)
def containerSequence(self):
+ """Return the editor item containers in display order."""
return [
GuiVTransportItemNetwork(title='Network', translatable=False),
GuiVTransportItemFinalMask(title='Finalmask', translatable=False),
@@ -1600,10 +1758,13 @@ class GuiVTransportPageSplitHttp(GuiVTransportPageXXX):
class GuiVTransportPageXHttp(GuiVTransportPageXXX):
+ """Represent GUI v transport page x HTTP."""
def __init__(self, *args, **kwargs):
+ """Initialize the GuiVTransportPageXHttp."""
super().__init__(*args, **kwargs)
def containerSequence(self):
+ """Return the editor item containers in display order."""
return [
GuiVTransportItemNetwork(title='Network', translatable=False),
GuiVTransportItemFinalMask(title='Finalmask', translatable=False),
@@ -1615,10 +1776,13 @@ class GuiVTransportPageXHttp(GuiVTransportPageXXX):
class GuiVTransportPageH2(GuiVTransportPageXXX):
+ """Represent GUI v transport page h2."""
def __init__(self, *args, **kwargs):
+ """Initialize the GuiVTransportPageH2."""
super().__init__(*args, **kwargs)
def containerSequence(self):
+ """Return the editor item containers in display order."""
return [
GuiVTransportItemNetwork(title='Network', translatable=False),
GuiVTransportItemFinalMask(title='Finalmask', translatable=False),
@@ -1628,10 +1792,13 @@ class GuiVTransportPageH2(GuiVTransportPageXXX):
class GuiVTransportPageQuic(GuiVTransportPageXXX):
+ """Represent GUI v transport page quic."""
def __init__(self, *args, **kwargs):
+ """Initialize the GuiVTransportPageQuic."""
super().__init__(*args, **kwargs)
def containerSequence(self):
+ """Return the editor item containers in display order."""
return [
GuiVTransportItemNetwork(title='Network', translatable=False),
GuiVTransportItemFinalMask(title='Finalmask', translatable=False),
@@ -1642,10 +1809,13 @@ class GuiVTransportPageQuic(GuiVTransportPageXXX):
class GuiVTransportPageGRPC(GuiVTransportPageXXX):
+ """Represent GUI v transport page grpc."""
def __init__(self, *args, **kwargs):
+ """Initialize the GuiVTransportPageGRPC."""
super().__init__(*args, **kwargs)
def containerSequence(self):
+ """Return the editor item containers in display order."""
return [
GuiVTransportItemNetwork(title='Network', translatable=False),
GuiVTransportItemFinalMask(title='Finalmask', translatable=False),
@@ -1658,10 +1828,13 @@ class GuiVTransportPageGRPC(GuiVTransportPageXXX):
class GuiVTransportPageHysteria(GuiVTransportPageXXX):
+ """Represent GUI v transport page hysteria."""
def __init__(self, *args, **kwargs):
+ """Initialize the GuiVTransportPageHysteria."""
super().__init__(*args, **kwargs)
def containerSequence(self):
+ """Return the editor item containers in display order."""
return [
GuiVTransportItemNetwork(title='Network', translatable=False),
GuiVTransportItemFinalMask(title='Finalmask', translatable=False),
@@ -1672,7 +1845,9 @@ class GuiVTransportPageHysteria(GuiVTransportPageXXX):
class GuiVTransportPageStackedWidget(QStackedWidget):
+ """Provide the GUI v transport page stacked widget."""
def __init__(self, *args, **kwargs):
+ """Initialize the GuiVTransportPageStackedWidget."""
super().__init__(*args, **kwargs)
# Corresponds to stream network
@@ -1696,15 +1871,19 @@ class GuiVTransportPageStackedWidget(QStackedWidget):
self.addWidget(page)
def page(self, index: int) -> GuiVTransportPageXXX:
+ """Return the page value."""
return self._pages[index]
def connectActivated(self, func: Callable):
+ """Connect activated."""
for page in self._pages:
page.connectActivated(func)
class GuiVTransportQGroupBox(GuiEditorItemFactory, AppQGroupBox):
+ """Group the GUI v transport q editor controls."""
def __init__(self, **kwargs):
+ """Initialize the GuiVTransportQGroupBox."""
super().__init__(_('Transport'), **kwargs)
self._config = ConfigFactory()
@@ -1718,15 +1897,19 @@ class GuiVTransportQGroupBox(GuiEditorItemFactory, AppQGroupBox):
self.setLayout(layout)
def currentIndex(self) -> int:
+ """Return the current index value."""
return self._widget.currentIndex()
def setCurrentIndex(self, index: int):
+ """Set current index."""
self._widget.setCurrentIndex(index)
def page(self, index: int) -> GuiVTransportPageXXX:
+ """Return the page value."""
return self._widget.page(index)
def handleActivated(self, index: int):
+ """Handle activated."""
page = self.page(index)
page.factoryToInput(self._config)
page.setNetworkText(STREAM_NETWORK[index])
@@ -1734,10 +1917,12 @@ class GuiVTransportQGroupBox(GuiEditorItemFactory, AppQGroupBox):
self.setCurrentIndex(index)
def inputToFactory(self, config: ConfigFactory) -> bool:
+ """Apply the current editor value to the configuration."""
return self.page(self.currentIndex()).inputToFactory(config)
def factoryToInput(self, config: ConfigFactory):
# Shallow copy
+ """Load the configuration value into the editor."""
self._config = config
streamSettings = ConfigXray.getProxyOutboundStream(config)
@@ -1747,6 +1932,7 @@ class GuiVTransportQGroupBox(GuiEditorItemFactory, AppQGroupBox):
return
def alterNetwork(sourceNetwork, targetNetwork):
+ """Handle alter network for the GUI v transport q group box."""
nonlocal network
if network == sourceNetwork:
diff --git a/Furious/Widget/IndentSpinBox.py b/Furious/Widget/IndentSpinBox.py
index fa4af19c..45e90534 100644
--- a/Furious/Widget/IndentSpinBox.py
+++ b/Furious/Widget/IndentSpinBox.py
@@ -15,6 +15,8 @@
# You should have received a copy of the GNU General Public License
# along with this program. If not, see .
+"""Provide widgets for indent spin box."""
+
from __future__ import annotations
from Furious.Qt import *
@@ -27,7 +29,9 @@ __all__ = ['IndentSpinBox']
class IndentSpinBox(AppQDialog):
+ """Represent indent spin box."""
def __init__(self, parent=None):
+ """Initialize the IndentSpinBox."""
super().__init__(parent)
self.setWindowTitle(_('Set Indent'))
@@ -54,4 +58,5 @@ class IndentSpinBox(AppQDialog):
self.setLayout(layout)
def value(self):
+ """Return the value value."""
return self.indentSpin.value()
diff --git a/Furious/Widget/SystemTrayIcon.py b/Furious/Widget/SystemTrayIcon.py
index 3d252962..0bf4ca2f 100644
--- a/Furious/Widget/SystemTrayIcon.py
+++ b/Furious/Widget/SystemTrayIcon.py
@@ -15,6 +15,8 @@
# You should have received a copy of the GNU General Public License
# along with this program. If not, see .
+"""Provide widgets for system tray icon."""
+
from __future__ import annotations
from Furious.Frozenlib import *
@@ -45,7 +47,9 @@ class SystemTrayIcon(
Mixins.ThemeAware,
QSystemTrayIcon,
):
+ """Represent system tray icon."""
def __init__(self, *args, **kwargs):
+ """Initialize the SystemTrayIcon."""
super().__init__(*args, **kwargs)
actions = [
@@ -112,6 +116,7 @@ class SystemTrayIcon(
self.activated.connect(self.handleActivated)
def rebuildDynamicMenus(self):
+ """Handle rebuild dynamic menus for the system tray icon."""
for action in self._actions:
if hasattr(action, 'rebuildMenu'):
action.rebuildMenu()
@@ -126,6 +131,7 @@ class SystemTrayIcon(
menu.addAction(childAction)
def bootstrap(self):
+ """Handle bootstrap for the system tray icon."""
if AppSettings.isStateON_('StartupOnBoot'):
# Rrefresh startup application location
StartupOnBoot.on_()
@@ -134,11 +140,14 @@ class SystemTrayIcon(
self.ConnectAction.trigger()
def showMessage(self, message: str, *args, **kwargs):
+ """Show message."""
if message:
super().showMessage(_(APPLICATION_NAME), message, *args, **kwargs)
def setMonochromeIconByTheme(self, theme):
+ """Set monochrome icon by theme."""
def switchMonochrome():
+ """Handle switch monochrome for the system tray icon."""
if theme == 'Dark':
self.setIcon(bootstrapIconWhite('monochrome-rocket-takeoff.svg'))
else:
@@ -166,18 +175,21 @@ class SystemTrayIcon(
switchMonochrome()
def setMonochromeIcon(self):
+ """Set monochrome icon."""
if APP().isSystemTrayConnected():
self.setConnectedMonochromeIcon()
else:
self.setDisconnectedMonochromeIcon()
def setConnectedMonochromeIcon(self):
+ """Set connected monochrome icon."""
if PLATFORM == 'Darwin':
self.setIcon(bootstrapIconMask('monochrome-rocket-takeoff.svg'))
else:
self.setMonochromeIconByTheme(APP().theme())
def setDisconnectedMonochromeIcon(self):
+ """Set disconnected monochrome icon."""
if PLATFORM == 'Darwin':
icon = bootstrapIconWithOpacity(
'monochrome-rocket-takeoff.svg',
@@ -189,6 +201,7 @@ class SystemTrayIcon(
self.setMonochromeIconByTheme(APP().theme())
def setDisconnectedIcon(self):
+ """Set disconnected icon."""
if AppSettings.isStateON_('UseMonochromeTrayIcon'):
self.setDisconnectedMonochromeIcon()
@@ -205,6 +218,7 @@ class SystemTrayIcon(
self.setIcon(bootstrapIcon('rocket-takeoff-disconnected.svg'))
def setConnectedIcon(self):
+ """Set connected icon."""
if AppSettings.isStateON_('UseMonochromeTrayIcon'):
self.setConnectedMonochromeIcon()
@@ -225,10 +239,12 @@ class SystemTrayIcon(
@QtCore.Slot(QSystemTrayIcon.ActivationReason)
def handleActivated(self, reason):
+ """Handle activated."""
if reason == QSystemTrayIcon.ActivationReason.DoubleClick:
APP().mainWindow.show()
def setCustomToolTip(self):
+ """Set custom tool tip."""
if SystemRuntime.isAdmin():
self.setToolTip(
f'{_(APPLICATION_NAME)} {APPLICATION_VERSION} ({_(ADMINISTRATOR_NAME)})'
@@ -237,12 +253,15 @@ class SystemTrayIcon(
self.setToolTip(f'{_(APPLICATION_NAME)} {APPLICATION_VERSION}')
def disconnectedCallback(self):
+ """Update the system tray icon for a disconnected state."""
self.setDisconnectedIcon()
def connectedCallback(self):
+ """Update the system tray icon for a connected state."""
self.setConnectedIcon()
def themeChangedCallback(self, theme):
+ """Update the system tray icon for a theme change."""
if AppSettings.isStateON_('UseMonochromeTrayIcon'):
# 'theme' is not used. Instead, always query current theme
self.setMonochromeIcon()
@@ -255,4 +274,5 @@ class SystemTrayIcon(
self.setDisconnectedIcon()
def retranslate(self):
+ """Refresh translated text for the system tray icon."""
self.setCustomToolTip()
diff --git a/Furious/Widget/UserServersQTableView.py b/Furious/Widget/UserServersQTableView.py
index 138e15c5..54a56aac 100644
--- a/Furious/Widget/UserServersQTableView.py
+++ b/Furious/Widget/UserServersQTableView.py
@@ -15,6 +15,8 @@
# You should have received a copy of the GNU General Public License
# along with this program. If not, see .
+"""Provide widgets for user servers Qt table view."""
+
from __future__ import annotations
from Furious.Frozenlib import *
@@ -59,6 +61,7 @@ registerAppSettings('UserServersHeaderViewState')
def appIsExiting() -> bool:
+ """Return the app is exiting value used by the application."""
app = APP()
if app is None:
@@ -73,7 +76,9 @@ def appIsExiting() -> bool:
class MBoxUpdateSubsInfo(AppQMessageBox):
+ """Represent m box update subs info."""
def __init__(self, *args, **kwargs):
+ """Initialize the MBoxUpdateSubsInfo."""
self.successArgs = kwargs.pop('successArgs', list())
self.failureArgs = kwargs.pop('failureArgs', list())
@@ -82,6 +87,7 @@ class MBoxUpdateSubsInfo(AppQMessageBox):
self.setWindowTitle(_(APPLICATION_NAME))
def customText(self):
+ """Return the user-facing message text for the m box update subs info."""
if self.successArgs:
text = _('Update subscription completed') + '\n\n'
else:
@@ -120,6 +126,7 @@ class MBoxUpdateSubsInfo(AppQMessageBox):
return text
def setColumnMinWidth(self):
+ """Set column min width."""
if PLATFORM == 'Windows':
self.findChild(QGridLayout).setColumnMinimumWidth(
2,
@@ -128,6 +135,7 @@ class MBoxUpdateSubsInfo(AppQMessageBox):
)
def retranslate(self):
+ """Refresh translated text for the m box update subs info."""
self.setWindowTitle(_(self.windowTitle()))
self.setText(self.customText())
self.setColumnMinWidth()
@@ -138,12 +146,15 @@ class MBoxUpdateSubsInfo(AppQMessageBox):
class SubscriptionManager(WebGETManager):
+ """Coordinate subscription operations."""
def __init__(self, parent, **kwargs):
+ """Initialize the SubscriptionManager."""
actionMessage = kwargs.pop('actionMessage', 'update subs')
super().__init__(parent, actionMessage=actionMessage, mustCallOnce=False)
def handleItemDeletionAndInsertion(self, **kwargs):
+ """Handle item deletion and insertion."""
successArgs = kwargs.pop('successArgs', list())
failureArgs = kwargs.pop('failureArgs', list())
showMessageBox = kwargs.pop('showMessageBox', True)
@@ -213,6 +224,7 @@ class SubscriptionManager(WebGETManager):
mbox.open()
def mustCall(self, **kwargs):
+ """Perform the required completion hook."""
depthMap = kwargs.get('depthMap', {})
depthMap['depth'] -= 1
@@ -220,6 +232,7 @@ class SubscriptionManager(WebGETManager):
self.handleItemDeletionAndInsertion(**kwargs)
def successCallback(self, networkReply, **kwargs):
+ """Handle a successful network operation."""
remark = kwargs.get('remark', '')
webURL = kwargs.get('webURL', '')
successArgs = kwargs.get('successArgs', list())
@@ -273,6 +286,7 @@ class SubscriptionManager(WebGETManager):
successArgs.append({'uris': uris, **kwargs})
def failureCallback(self, networkReply, **kwargs):
+ """Handle a failed network operation."""
remark = kwargs.get('remark', '')
webURL = kwargs.get('webURL', '')
successArgs = kwargs.get('successArgs', list())
@@ -285,6 +299,7 @@ class SubscriptionManager(WebGETManager):
failureArgs.append({'error': networkReply.errorString(), **kwargs})
def updateSubsByWebGET(self, **kwargs):
+ """Update subs by web get."""
url = kwargs.get('webURL', '')
if not url:
@@ -304,6 +319,7 @@ class SubscriptionManager(WebGETManager):
self.webGET(request, logActionMessage=logActionMessage, **kwargs)
def updateSubsByUnique(self, unique: str, **kwargs):
+ """Update subs by unique."""
depthMap = kwargs.get('depthMap', {'depth': 1})
successArgs = kwargs.get('successArgs', list())
failureArgs = kwargs.get('failureArgs', list())
@@ -320,6 +336,7 @@ class SubscriptionManager(WebGETManager):
self.updateSubsByWebGET(unique=unique, **Storage.UserSubs()[unique], **kwargs)
def updateSubs(self, **kwargs):
+ """Update subs."""
depthMap = {'depth': len(Storage.UserSubs())}
successArgs = list()
failureArgs = list()
@@ -335,16 +352,19 @@ class SubscriptionManager(WebGETManager):
class TestPingLatencyWorker(QtCore.QObject, QtCore.QRunnable):
+ """Run test ping latency work in the background."""
finished = QtCore.Signal()
def __init__(self, factory: ConfigFactory):
# Explictly called __init__
+ """Initialize the TestPingLatencyWorker."""
QtCore.QObject.__init__(self)
QtCore.QRunnable.__init__(self)
self.factory = factory
def run(self):
+ """Run the test ping latency worker task."""
index = self.factory.index
if self.factory.deleted or index < 0 or index >= len(Storage.UserServers()):
@@ -380,16 +400,19 @@ class TestPingLatencyWorker(QtCore.QObject, QtCore.QRunnable):
class TestTcpingLatencyWorker(QtCore.QObject, QtCore.QRunnable):
+ """Run test tcping latency work in the background."""
finished = QtCore.Signal()
def __init__(self, factory: ConfigFactory):
# Explictly called __init__
+ """Initialize the TestTcpingLatencyWorker."""
QtCore.QObject.__init__(self)
QtCore.QRunnable.__init__(self)
self.factory = factory
def run(self):
+ """Run the test tcping latency worker task."""
index = self.factory.index
if self.factory.deleted or index < 0 or index >= len(Storage.UserServers()):
@@ -422,6 +445,7 @@ class TestTcpingLatencyWorker(QtCore.QObject, QtCore.QRunnable):
class TestDownloadSpeedWorker(WebGETManager):
+ """Run test download speed work in the background."""
progressed = QtCore.Signal()
finished = QtCore.Signal(object)
@@ -433,6 +457,7 @@ class TestDownloadSpeedWorker(WebGETManager):
parent=None,
**kwargs,
):
+ """Initialize the TestDownloadSpeedWorker."""
actionMessage = kwargs.pop('actionMessage', 'test download speed')
super().__init__(parent, actionMessage=actionMessage)
@@ -457,25 +482,30 @@ class TestDownloadSpeedWorker(WebGETManager):
self.timeoutTimer.timeout.connect(self.handleTimeout)
def mustCall(self):
+ """Perform the required completion hook."""
self.timeoutTimer.stop()
self.finished.emit(self)
def sync(self):
# Extra guard
+ """Persist the current test download speed worker data."""
if not appIsExiting():
self.progressed.emit()
def isFinished(self) -> bool:
+ """Return whether finished."""
if isinstance(self.networkReply, QNetworkReply):
return self.networkReply.isFinished()
else:
return True
def abort(self):
+ """Cancel the active test download speed worker operation."""
if isinstance(self.networkReply, QNetworkReply):
self.networkReply.abort()
def handleTimeout(self):
+ """Handle timeout."""
try:
if not self.isFinished():
self.abort()
@@ -483,6 +513,7 @@ class TestDownloadSpeedWorker(WebGETManager):
self.must()
def coreExitCallback(self, config: ConfigFactory, exitcode: int):
+ """Handle the core exit callback."""
try:
if exitcode == CoreProcessFactory.ExitCode.ConfigurationError.value:
self.factory.setExtras('speedResult', f'Invalid')
@@ -500,6 +531,7 @@ class TestDownloadSpeedWorker(WebGETManager):
@staticmethod
def coreMsgCallback(line):
+ """Handle the core msg callback."""
try:
AppLoggerWindow.Core().appendLine(line)
except Exception:
@@ -509,6 +541,7 @@ class TestDownloadSpeedWorker(WebGETManager):
@functools.singledispatchmethod
def _startCore(self, config) -> bool:
+ """Return the start core value used by the test download speed worker."""
self.factory.setExtras('speedResult', 'Invalid')
self.sync()
@@ -517,6 +550,7 @@ class TestDownloadSpeedWorker(WebGETManager):
@_startCore.register(ConfigXray)
def _(self, config) -> bool:
+ """Handle the registered singledispatch variant."""
self.factory.setExtras('speedResult', 'Starting')
self.sync()
@@ -566,6 +600,7 @@ class TestDownloadSpeedWorker(WebGETManager):
@_startCore.register(ConfigHysteria1)
@_startCore.register(ConfigHysteria2)
def _(self, config) -> bool:
+ """Handle the registered singledispatch variant."""
self.factory.setExtras('speedResult', 'Starting')
self.sync()
@@ -590,6 +625,7 @@ class TestDownloadSpeedWorker(WebGETManager):
)
def start(self):
+ """Start the test download speed worker."""
try:
if appIsExiting():
raise
@@ -629,6 +665,7 @@ class TestDownloadSpeedWorker(WebGETManager):
self.must()
def successCallback(self, networkReply, **kwargs):
+ """Handle a successful network operation."""
if self.coreManager.allRunning():
self.totalBytesRead += networkReply.readAll().length()
@@ -644,6 +681,7 @@ class TestDownloadSpeedWorker(WebGETManager):
self.sync()
def hasDataCallback(self, networkReply, **kwargs):
+ """Handle newly available network response data."""
self.hasDataCounter += 1
if self.coreManager.allRunning():
@@ -662,6 +700,7 @@ class TestDownloadSpeedWorker(WebGETManager):
self.sync()
def failureCallback(self, networkReply, **kwargs):
+ """Handle a failed network operation."""
if not self.hasSpeedResult:
if not self.coreManager.allRunning():
# Core ExitCallback has been called
@@ -699,6 +738,7 @@ class TestDownloadSpeedWorker(WebGETManager):
class DownloadSpeedTestJob:
+ """Represent download speed test job."""
def __init__(
self,
index: int,
@@ -706,6 +746,7 @@ class DownloadSpeedTestJob:
timeout: int,
logActionMessage=False,
):
+ """Initialize the DownloadSpeedTestJob."""
super().__init__()
self.index = index
@@ -715,11 +756,13 @@ class DownloadSpeedTestJob:
class DownloadSpeedTestScheduler(QtCore.QObject):
+ """Schedule and coordinate download speed test jobs."""
SinglePort = 20809
MultiPortStart = 30000
MultiPortStop = 40000
def __init__(self, table, isMulti: bool, parent=None):
+ """Initialize the DownloadSpeedTestScheduler."""
super().__init__(parent)
self.table = table
@@ -739,17 +782,20 @@ class DownloadSpeedTestScheduler(QtCore.QObject):
timeout: int,
logActionMessage=False,
):
+ """Handle enqueue for the download speed test scheduler."""
self.queue.append(
DownloadSpeedTestJob(index, factory, timeout, logActionMessage)
)
self.scheduleDrain()
def enqueueMany(self, jobs: list[DownloadSpeedTestJob]):
+ """Handle enqueue many for the download speed test scheduler."""
self.queue.extend(jobs)
self.scheduleDrain()
def cancelAll(self):
+ """Return whether cel all."""
self.queue.clear()
for worker, _, _ in list(self.activeJobs.values()):
@@ -762,6 +808,7 @@ class DownloadSpeedTestScheduler(QtCore.QObject):
worker.must()
def scheduleDrain(self):
+ """Handle schedule drain for the download speed test scheduler."""
if self.drainScheduled:
return
@@ -770,6 +817,7 @@ class DownloadSpeedTestScheduler(QtCore.QObject):
QtCore.QTimer.singleShot(0, self.drain)
def drain(self):
+ """Handle drain for the download speed test scheduler."""
self.drainScheduled = False
if appIsExiting():
@@ -795,6 +843,7 @@ class DownloadSpeedTestScheduler(QtCore.QObject):
self.startJob(job, port)
def allocatePort(self) -> Union[int, None]:
+ """Return whether allocate port."""
if not self.isMulti:
if self.activeJobs:
return None
@@ -818,9 +867,11 @@ class DownloadSpeedTestScheduler(QtCore.QObject):
return None
def releasePort(self, port: int):
+ """Handle release port for the download speed test scheduler."""
self.activePorts.discard(port)
def startJob(self, job: DownloadSpeedTestJob, port: int):
+ """Start job."""
worker = TestDownloadSpeedWorker(
job.factory,
port,
@@ -843,6 +894,7 @@ class DownloadSpeedTestScheduler(QtCore.QObject):
@QtCore.Slot(object)
def handleWorkerFinished(self, worker):
+ """Handle worker finished."""
workerId = id(worker)
try:
@@ -855,7 +907,9 @@ class DownloadSpeedTestScheduler(QtCore.QObject):
class DeleteServersProgressDialog(AppQDialog):
+ """Present progress and cancellation controls for delete servers."""
def __init__(self, table, indexes, showTrayMessage=True, parent=None):
+ """Initialize the DeleteServersProgressDialog."""
super().__init__(parent)
self.table = table
@@ -901,9 +955,11 @@ class DeleteServersProgressDialog(AppQDialog):
self.updateStatus()
def setWidthAndHeight(self):
+ """Apply the default size for the delete servers progress dialog."""
self.resize(420, 150)
def open(self):
+ """Open the delete servers progress dialog asynchronously."""
self.spinner.start()
QtCore.QTimer.singleShot(0, self.deleteNext)
@@ -911,14 +967,17 @@ class DeleteServersProgressDialog(AppQDialog):
return super().open()
def reject(self):
+ """Reject the current delete servers progress dialog values."""
self.cancel()
def cancel(self):
+ """Cancel the delete servers progress dialog operation."""
self.canceled = True
self.cancelButton.setEnabled(False)
self.updateStatus()
def updateStatus(self):
+ """Update status."""
if self.canceled:
self.statusLabel.setText(
_('Canceling delete') + f'... {self.deletedCount}/{self.total}'
@@ -935,6 +994,7 @@ class DeleteServersProgressDialog(AppQDialog):
@staticmethod
def limitedRemark(remark: str) -> str:
+ """Return the limited remark value used by the delete servers progress dialog."""
remark = str(remark).strip()
if len(remark) <= 120:
@@ -943,6 +1003,7 @@ class DeleteServersProgressDialog(AppQDialog):
return remark[:117] + '...'
def deleteNext(self):
+ """Delete next."""
if self.canceled or self.nextIndex >= self.total:
self.finishDeletion()
@@ -988,6 +1049,7 @@ class DeleteServersProgressDialog(AppQDialog):
QtCore.QTimer.singleShot(0, self.deleteNext)
def finishDeletion(self):
+ """Handle finish deletion for the delete servers progress dialog."""
if self.finishedDeletion:
return
@@ -1012,66 +1074,82 @@ class DeleteServersProgressDialog(AppQDialog):
self.accept()
def retranslate(self):
+ """Refresh translated text for the delete servers progress dialog."""
self.setWindowTitle(_(self.windowTitle()))
self.cancelButton.setText(_(self.cancelButton.text()))
self.updateStatus()
class UserServersQTableViewHorizontalHeader(AppQHeaderView):
+ """Provide the user servers Qt table view horizontal table header."""
def __init__(self, *args, **kwargs):
+ """Initialize the UserServersQTableViewHorizontalHeader."""
super().__init__(QtCore.Qt.Orientation.Horizontal, *args, **kwargs)
class UserServersQTableViewVerticalHeader(AppQHeaderView):
+ """Provide the user servers Qt table view vertical table header."""
def __init__(self, *args, **kwargs):
+ """Initialize the UserServersQTableViewVerticalHeader."""
super().__init__(QtCore.Qt.Orientation.Vertical, *args, **kwargs)
class UserServersQTableViewHeaders:
+ """Describe and render user servers Qt table view table columns."""
def __init__(self, name: str, func: Callable[[ConfigFactory], str] = None):
+ """Initialize the UserServersQTableViewHeaders."""
self.name = name
self.func = func
def __call__(self, item: ConfigFactory) -> str:
+ """Invoke the user servers Qt table view headers as a callable."""
if callable(self.func):
return self.func(item)
else:
return getattr(item, f'item{self}')
def __eq__(self, other):
+ """Compare the user servers Qt table view headers with another value."""
return str(self) == str(other)
def __str__(self):
+ """Return the display text for the user servers Qt table view headers."""
return self.name
class UserServersTableModel(QtCore.QAbstractTableModel):
+ """Expose user servers table data through a Qt item model."""
SortRole = QtCore.Qt.ItemDataRole.UserRole + 1
def __init__(self, headers: list[UserServersQTableViewHeaders], parent=None):
+ """Initialize the UserServersTableModel."""
super().__init__(parent)
self.headers = headers
def rowCount(self, parent=QtCore.QModelIndex()) -> int:
+ """Return the number of rows exposed by the model."""
if parent.isValid():
return 0
return len(Storage.UserServers())
def columnCount(self, parent=QtCore.QModelIndex()) -> int:
+ """Return the number of columns exposed by the model."""
if parent.isValid():
return 0
return len(self.headers)
def flags(self, index):
+ """Return the Qt item flags for a model index."""
if not index.isValid():
return QtCore.Qt.ItemFlag.NoItemFlags
return QtCore.Qt.ItemFlag.ItemIsEnabled | QtCore.Qt.ItemFlag.ItemIsSelectable
def data(self, index, role=QtCore.Qt.ItemDataRole.DisplayRole):
+ """Return the data managed by the user servers table model."""
if not index.isValid():
return None
@@ -1134,6 +1212,7 @@ class UserServersTableModel(QtCore.QAbstractTableModel):
orientation: QtCore.Qt.Orientation,
role=QtCore.Qt.ItemDataRole.DisplayRole,
):
+ """Return display data for a table header section."""
if role != QtCore.Qt.ItemDataRole.DisplayRole:
return None
@@ -1147,6 +1226,7 @@ class UserServersTableModel(QtCore.QAbstractTableModel):
@staticmethod
def testResultSortValue(text: str, suffix: str):
+ """Return the test result sort value value used by the user servers table model."""
if text.endswith(suffix):
text = text[: -len(suffix)]
@@ -1158,6 +1238,7 @@ class UserServersTableModel(QtCore.QAbstractTableModel):
return abs(hash(text)) + 2**20
def emitRowChanged(self, row: int, column: Union[int, None] = None):
+ """Handle emit row changed for the user servers table model."""
if row < 0 or row >= self.rowCount():
return
@@ -1171,6 +1252,7 @@ class UserServersTableModel(QtCore.QAbstractTableModel):
self.dataChanged.emit(left, right, [])
def emitAllChanged(self):
+ """Handle emit all changed for the user servers table model."""
if self.rowCount() == 0 or self.columnCount() == 0:
return
@@ -1182,6 +1264,7 @@ class UserServersTableModel(QtCore.QAbstractTableModel):
@staticmethod
def refreshIndexes():
+ """Refresh indexes."""
for index, item in enumerate(Storage.UserServers()):
item.index = index
@@ -1190,6 +1273,7 @@ class UserServersTableModel(QtCore.QAbstractTableModel):
column: int,
order: QtCore.Qt.SortOrder = QtCore.Qt.SortOrder.AscendingOrder,
):
+ """Sort the user servers table model."""
if column < 0 or column >= self.columnCount():
return
@@ -1203,6 +1287,7 @@ class UserServersTableModel(QtCore.QAbstractTableModel):
header = self.headers[column]
def keyFn(factory: ConfigFactory):
+ """Return the key fn value used by the user servers table model."""
data = header(factory)
if str(header) == 'Latency':
@@ -1232,7 +1317,9 @@ class UserServersTableModel(QtCore.QAbstractTableModel):
class UserServersSortFilterProxyModel(QtCore.QSortFilterProxyModel):
+ """Filter and sort user servers sort filter data."""
def __init__(self, parent=None):
+ """Initialize the UserServersSortFilterProxyModel."""
super().__init__(parent)
self.searchPattern = ''
@@ -1249,6 +1336,7 @@ class UserServersSortFilterProxyModel(QtCore.QSortFilterProxyModel):
column: int,
order: QtCore.Qt.SortOrder = QtCore.Qt.SortOrder.AscendingOrder,
):
+ """Sort the user servers sort filter proxy model."""
if self.sortSuspended:
super().sort(-1, order)
@@ -1272,6 +1360,7 @@ class UserServersSortFilterProxyModel(QtCore.QSortFilterProxyModel):
caseSensitive: bool = False,
regex: bool = True,
):
+ """Set search pattern."""
self.searchPattern = str(pattern or '')
self.searchCaseSensitive = caseSensitive
self.searchUseRegex = regex
@@ -1296,6 +1385,7 @@ class UserServersSortFilterProxyModel(QtCore.QSortFilterProxyModel):
self.invalidateFilter()
def filterAcceptsRow(self, sourceRow: int, sourceParent) -> bool:
+ """Filter accepts row."""
if not self.searchPattern or self.searchRegex is None:
return True
@@ -1323,6 +1413,7 @@ class UserServersSortFilterProxyModel(QtCore.QSortFilterProxyModel):
orientation: QtCore.Qt.Orientation,
role=QtCore.Qt.ItemDataRole.DisplayRole,
):
+ """Return display data for a table header section."""
if (
orientation == QtCore.Qt.Orientation.Vertical
and role == QtCore.Qt.ItemDataRole.DisplayRole
@@ -1351,6 +1442,7 @@ class UserServersQTableView(
Mixins.CleanupOnExit,
AppQTableView,
):
+ """Represent user servers Qt table view."""
RowHeight = 42
Headers = [
@@ -1366,6 +1458,7 @@ class UserServersQTableView(
]
def __init__(self, *args, **kwargs):
+ """Initialize the UserServersQTableView."""
super().__init__(*args, **kwargs)
self.sourceModel = UserServersTableModel(self.Headers, parent=self)
@@ -1635,6 +1728,7 @@ class UserServersQTableView(
@property
def selectedIndex(self):
+ """Return the selected index value."""
indexes = list(
self.sourceRowFromProxyIndex(index)
for index in self.selectionModel().selectedRows()
@@ -1643,18 +1737,21 @@ class UserServersQTableView(
return sorted(list(set(index for index in indexes if index >= 0)))
def sourceIndexFromProxyIndex(self, index: QtCore.QModelIndex):
+ """Return the source index from proxy index value used by the user servers Qt table view."""
if not index.isValid():
return QtCore.QModelIndex()
return self.proxyModel.mapToSource(index)
def proxyIndexFromSourceIndex(self, index: QtCore.QModelIndex):
+ """Return the proxy index from source index value used by the user servers Qt table view."""
if not index.isValid():
return QtCore.QModelIndex()
return self.proxyModel.mapFromSource(index)
def sourceRowFromProxyIndex(self, index: QtCore.QModelIndex) -> int:
+ """Return the source row from proxy index value used by the user servers Qt table view."""
sourceIndex = self.sourceIndexFromProxyIndex(index)
if sourceIndex.isValid():
@@ -1663,15 +1760,18 @@ class UserServersQTableView(
return -1
def sourceRowFromProxyRow(self, row: int) -> int:
+ """Return the source row from proxy row value used by the user servers Qt table view."""
return self.sourceRowFromProxyIndex(self.proxyModel.index(row, 0))
def proxyIndexFromSourceRow(self, row: int, column: int = 0):
+ """Return the proxy index from source row value used by the user servers Qt table view."""
if row < 0 or row >= self.sourceModel.rowCount():
return QtCore.QModelIndex()
return self.proxyIndexFromSourceIndex(self.sourceModel.index(row, column))
def selectMultipleRows(self, indexes: list[int], clearCurrentSelection: bool):
+ """Select multiple rows."""
if clearCurrentSelection:
self.selectionModel().clearSelection()
@@ -1689,16 +1789,19 @@ class UserServersQTableView(
)
def disconnectedCallback(self):
+ """Update the user servers Qt table view for a disconnected state."""
super().disconnectedCallback()
self.activateItemByIndex(Storage.UserActivatedItemIndex(), True)
def connectedCallback(self):
+ """Update the user servers Qt table view for a connected state."""
super().connectedCallback()
self.activateItemByIndex(Storage.UserActivatedItemIndex(), True)
def handleItemSelectionChanged(self, *args):
+ """Handle item selection changed."""
if len(self.selectedIndex) > 1:
for action in [
self.customizeJSONConfigActionRef,
@@ -1714,6 +1817,7 @@ class UserServersQTableView(
@QtCore.Slot(QtCore.QModelIndex)
def handleItemActivated(self, index: QtCore.QModelIndex):
+ """Handle item activated."""
if self.doubleClickedFlag:
# Ignore double-click
self.doubleClickedFlag = False
@@ -1755,6 +1859,7 @@ class UserServersQTableView(
@functools.lru_cache(None)
def getGuiEditorByProtocol(self, protocol: Protocol, **kwargs):
+ """Return GUI editor by protocol."""
logger.debug(f'getGuiEditorByProtocol called with protocol {protocol.value}')
if protocol == Protocol.VMess:
@@ -1778,24 +1883,29 @@ class UserServersQTableView(
def getGuiEditorByFactory(
self, factory, **kwargs
) -> Union[GuiEditorWidgetQDialog, None]:
+ """Return GUI editor by factory."""
return None
@getGuiEditorByFactory.register(ConfigXray)
def _(self, factory, **kwargs):
+ """Handle the registered singledispatch variant."""
return self.getGuiEditorByProtocol(
Protocol.toEnum(factory.proxyProtocol), **kwargs
)
@getGuiEditorByFactory.register(ConfigHysteria1)
def _(self, factory, **kwargs):
+ """Handle the registered singledispatch variant."""
return self.getGuiEditorByProtocol(Protocol.Hysteria1, **kwargs)
@getGuiEditorByFactory.register(ConfigHysteria2)
def _(self, factory, **kwargs):
+ """Handle the registered singledispatch variant."""
return self.getGuiEditorByProtocol(Protocol.Hysteria2, **kwargs)
@QtCore.Slot(QtCore.QModelIndex)
def handleItemDoubleClicked(self, modelIndex: QtCore.QModelIndex):
+ """Handle item double clicked."""
self.doubleClickedFlag = True
index = self.sourceRowFromProxyIndex(modelIndex)
@@ -1845,6 +1955,7 @@ class UserServersQTableView(
index: int,
factory: ConfigFactory,
):
+ """Handle GUI editor accepted."""
logger.debug(f'guiEditor accepted with index {index}')
modified = editor.inputToFactory(factory)
@@ -1860,14 +1971,17 @@ class UserServersQTableView(
@staticmethod
def handleGuiEditorRejected(editor: GuiEditorWidgetQDialog):
+ """Handle GUI editor rejected."""
editor.accepted.disconnect()
editor.rejected.disconnect()
@QtCore.Slot(QtCore.QPoint)
def handleCustomContextMenuRequested(self, point):
+ """Handle custom context menu requested."""
self.contextMenu.exec(self.viewport().mapToGlobal(point))
def customSortFn(self, clickedIndex, **kwargs):
+ """Handle custom sort fn for the user servers Qt table view."""
order = (
QtCore.Qt.SortOrder.DescendingOrder
if kwargs.get('reverse', False)
@@ -1877,9 +1991,11 @@ class UserServersQTableView(
self.sortByColumn(clickedIndex, order)
def activatedIndex(self):
+ """Activate d index."""
return self.proxyIndexFromSourceRow(Storage.UserActivatedItemIndex(), 0)
def activateItemByIndex(self, index, activate):
+ """Activate item by index."""
oldIndex = Storage.UserActivatedItemIndex()
if activate:
@@ -1889,6 +2005,7 @@ class UserServersQTableView(
self.sourceModel.emitRowChanged(index)
def flushItem(self, row: int, column: int, item: ConfigFactory):
+ """Refresh item."""
itemIndex = item.index
if item.deleted or itemIndex < 0 or itemIndex >= len(Storage.UserServers()):
@@ -1896,6 +2013,7 @@ class UserServersQTableView(
return
def searchIndex(start, stop, step=1):
+ """Search index."""
nonlocal itemIndex
for _index in range(start, stop, step):
@@ -1933,6 +2051,7 @@ class UserServersQTableView(
caseSensitive: bool = False,
regex: bool = True,
):
+ """Search the user servers Qt table view."""
self.proxyModel.setSearchPattern(
pattern,
caseSensitive=caseSensitive,
@@ -1940,6 +2059,7 @@ class UserServersQTableView(
)
def clearSearch(self):
+ """Clear search."""
self.search('')
def addServerViaGui(
@@ -1948,6 +2068,7 @@ class UserServersQTableView(
windowTitle: str = APPLICATION_NAME,
**kwargs,
):
+ """Add server via GUI."""
factory = configFactoryBlank(protocol)
guiEditor = self.getGuiEditorByFactory(factory, **kwargs)
@@ -1985,6 +2106,7 @@ class UserServersQTableView(
editor: GuiEditorWidgetQDialog,
factory: ConfigFactory,
):
+ """Handle add server via GUI accepted."""
editor.inputToFactory(factory)
self.appendNewItemByFactory(factory)
@@ -1993,10 +2115,12 @@ class UserServersQTableView(
editor.rejected.disconnect()
def handleAddServerViaGuiRejected(self, editor: GuiEditorWidgetQDialog):
+ """Handle add server via GUI rejected."""
editor.accepted.disconnect()
editor.rejected.disconnect()
def flushRow(self, row: int, item: ConfigFactory):
+ """Refresh row."""
itemIndex = item.index
if item.deleted or itemIndex < 0 or itemIndex >= len(Storage.UserServers()):
@@ -2010,11 +2134,14 @@ class UserServersQTableView(
def flushAll(self):
# Refresh index
+ """Refresh all."""
self.sourceModel.refreshIndexes()
self.sourceModel.emitAllChanged()
def swapItem(self, index0: int, index1: int):
+ """Handle swap item for the user servers Qt table view."""
def swapSequenceItem(sequence: MutableSequence, param0: int, param1: int):
+ """Handle swap sequence item for the user servers Qt table view."""
swap = sequence[param0]
sequence[param0] = sequence[param1]
@@ -2039,9 +2166,11 @@ class UserServersQTableView(
self.activateItemByIndex(index0, True)
def newEmptyItem(self):
+ """Handle new empty item for the user servers Qt table view."""
self.appendNewItem(remark=_('Untitled'), acceptInvalid=True)
def moveUpItemByIndex(self, index):
+ """Move up item by index."""
if index <= 0 or index >= len(Storage.UserServers()):
# The top item, or does not exist. Do nothing
return
@@ -2049,6 +2178,7 @@ class UserServersQTableView(
self.swapItem(index, index - 1)
def moveUpSelectedItem(self):
+ """Move up selected item."""
indexes = self.selectedIndex
if len(indexes) == 0:
@@ -2064,6 +2194,7 @@ class UserServersQTableView(
self.selectMultipleRows(list(index - 1 for index in indexes), True)
def moveDownItemByIndex(self, index):
+ """Move down item by index."""
if index < 0 or index >= len(Storage.UserServers()) - 1:
# The bottom item, or does not exist. Do nothing
return
@@ -2071,6 +2202,7 @@ class UserServersQTableView(
self.swapItem(index, index + 1)
def moveDownSelectedItem(self):
+ """Move down selected item."""
indexes = self.selectedIndex
if len(indexes) == 0:
@@ -2086,6 +2218,7 @@ class UserServersQTableView(
self.selectMultipleRows(list(index + 1 for index in indexes), True)
def duplicateSelectedItem(self):
+ """Handle duplicate selected item for the user servers Qt table view."""
indexes = self.selectedIndex
if len(indexes) == 0:
@@ -2105,6 +2238,7 @@ class UserServersQTableView(
def deleteItemByIndex(
self, indexes, showTrayMessage=True, showProgress=True
) -> int:
+ """Delete item by index."""
indexes = sorted(set(indexes))
if len(indexes) == 0:
@@ -2166,6 +2300,7 @@ class UserServersQTableView(
return len(indexes)
def deleteSelectedItem(self):
+ """Delete selected item."""
indexes = self.selectedIndex
if len(indexes) == 0:
@@ -2173,6 +2308,7 @@ class UserServersQTableView(
return
def handleResultCode(_indexes, code):
+ """Handle result code."""
if code == PySide6Legacy.enumValueWrapper(
AppQMessageBox.StandardButton.Yes
):
@@ -2201,6 +2337,7 @@ class UserServersQTableView(
mbox.open()
def editSelectedItemConfiguration(self):
+ """Handle edit selected item configuration for the user servers Qt table view."""
indexes = self.selectedIndex
if len(indexes) == 0:
@@ -2223,6 +2360,7 @@ class UserServersQTableView(
self.textEditorWindow.show()
def activateSelectedServer(self):
+ """Activate selected server."""
indexes = self.selectedIndex
if len(indexes) == 0:
@@ -2239,6 +2377,7 @@ class UserServersQTableView(
self.handleItemActivated(item)
def scrollToActivatedItem(self):
+ """Handle scroll to activated item for the user servers Qt table view."""
activatedItem = self.activatedIndex()
if activatedItem.isValid():
@@ -2246,6 +2385,7 @@ class UserServersQTableView(
self.scrollTo(activatedItem)
def rowFromFactory(self, fallbackIndex: int, factory: ConfigFactory) -> int:
+ """Return the row from factory value."""
if (
0 <= factory.index < len(Storage.UserServers())
and Storage.UserServers()[factory.index] is factory
@@ -2265,6 +2405,7 @@ class UserServersQTableView(
return -1
def flushDownloadSpeedItem(self, fallbackIndex: int, factory: ConfigFactory):
+ """Refresh download speed item."""
index = self.rowFromFactory(fallbackIndex, factory)
if index < 0:
@@ -2273,6 +2414,7 @@ class UserServersQTableView(
self.flushItem(index, self.Headers.index('Speed'), factory)
def testSelectedItemPingLatency(self):
+ """Handle test selected item ping latency for the user servers Qt table view."""
indexes = self.selectedIndex
if len(indexes) == 0:
@@ -2305,6 +2447,7 @@ class UserServersQTableView(
AppThreadPool().start(worker)
def testSelectedItemTcpingLatency(self):
+ """Handle test selected item tcping latency for the user servers Qt table view."""
indexes = self.selectedIndex
if len(indexes) == 0:
@@ -2347,6 +2490,7 @@ class UserServersQTableView(
step=100,
logActionMessage=False,
):
+ """Handle test download speed by factory for the user servers Qt table view."""
scheduler = (
self.downloadSpeedMultiScheduler if isMulti else self.downloadSpeedScheduler
)
@@ -2357,6 +2501,7 @@ class UserServersQTableView(
scheduler: DownloadSpeedTestScheduler,
timeout: int,
):
+ """Handle test selected item download speed with timeout xxx for the user servers Qt table view."""
indexes = self.selectedIndex
if len(indexes) == 0:
@@ -2373,24 +2518,29 @@ class UserServersQTableView(
scheduler.enqueueMany(jobs)
def testSelectedItemDownloadSpeedWithTimeout(self, timeout: int):
+ """Handle test selected item download speed with timeout for the user servers Qt table view."""
self.testSelectedItemDownloadSpeedWithTimeoutXXX(
self.downloadSpeedScheduler,
timeout,
)
def testSelectedItemDownloadSpeedWithTimeoutMulti(self, timeout: int):
+ """Handle test selected item download speed with timeout multi for the user servers Qt table view."""
self.testSelectedItemDownloadSpeedWithTimeoutXXX(
self.downloadSpeedMultiScheduler,
timeout,
)
def testSelectedItemDownloadSpeed(self):
+ """Handle test selected item download speed for the user servers Qt table view."""
self.testSelectedItemDownloadSpeedWithTimeout(5000)
def testSelectedItemDownloadSpeedMulti(self):
+ """Handle test selected item download speed multi for the user servers Qt table view."""
self.testSelectedItemDownloadSpeedWithTimeoutMulti(5000)
def clearSelectedItemTestResult(self):
+ """Clear selected item test result."""
indexes = self.selectedIndex
if len(indexes) == 0:
@@ -2406,20 +2556,24 @@ class UserServersQTableView(
self.flushItem(index, self.Headers.index('Speed'), factory)
def cleanup(self):
+ """Release resources owned by the user servers Qt table view."""
self.downloadSpeedScheduler.cancelAll()
self.downloadSpeedMultiScheduler.cancelAll()
def updateSubsByUnique(self, unique: str, httpProxy: Union[str, None], **kwargs):
+ """Update subs by unique."""
self.subsManager.configureHttpProxy(httpProxy)
self.subsManager.updateSubsByUnique(unique, **kwargs)
def updateSubs(self, httpProxy: Union[str, None], **kwargs):
+ """Update subs."""
self.selectionModel().clearSelection()
self.subsManager.configureHttpProxy(httpProxy)
self.subsManager.updateSubs(**kwargs)
def appendNewItemByFactory(self, factory: ConfigFactory):
+ """Append new item by factory."""
index = len(Storage.UserServers())
# Set index
@@ -2444,6 +2598,7 @@ class UserServersQTableView(
self.activateItemByIndex(0, True)
def appendNewItem(self, **kwargs):
+ """Append new item."""
acceptInvalid = kwargs.pop('acceptInvalid', False)
model = {
@@ -2464,6 +2619,7 @@ class UserServersQTableView(
logger.error(f'invalid item: {tostr}')
def exportSelectedItemURI(self):
+ """Export selected item URI."""
indexes = self.selectedIndex
if len(indexes) == 0:
@@ -2471,6 +2627,7 @@ class UserServersQTableView(
return
def toURI(factory) -> str:
+ """Export the configuration as a share URI."""
assert isinstance(factory, ConfigFactory)
try:
@@ -2486,6 +2643,7 @@ class UserServersQTableView(
)
def exportSelectedItemQR(self):
+ """Export selected item QR."""
indexes = self.selectedIndex
if len(indexes) == 0:
@@ -2499,6 +2657,7 @@ class UserServersQTableView(
window.show()
def exportSelectedItemJSON(self):
+ """Export selected item JSON."""
indexes = self.selectedIndex
if len(indexes) == 0:
@@ -2513,12 +2672,15 @@ class UserServersQTableView(
)
def showTabAndSpaces(self):
+ """Show tab and spaces."""
self.textEditorWindow.showTabAndSpaces()
def hideTabAndSpaces(self):
+ """Hide tab and spaces."""
self.textEditorWindow.hideTabAndSpaces()
def keyPressEvent(self, event):
+ """Handle a key press for the user servers Qt table view."""
if event.key() == QtCore.Qt.Key.Key_Return:
if PLATFORM == 'Darwin':
# Activate by Enter key on macOS
@@ -2529,6 +2691,7 @@ class UserServersQTableView(
super().keyPressEvent(event)
def retranslate(self):
+ """Refresh translated text for the user servers Qt table view."""
self.sourceModel.headerDataChanged.emit(
QtCore.Qt.Orientation.Horizontal,
0,
diff --git a/Furious/Widget/UserSubsQTableView.py b/Furious/Widget/UserSubsQTableView.py
index 653a8ae8..14e2cc88 100644
--- a/Furious/Widget/UserSubsQTableView.py
+++ b/Furious/Widget/UserSubsQTableView.py
@@ -15,6 +15,8 @@
# You should have received a copy of the GNU General Public License
# along with this program. If not, see .
+"""Provide widgets for user subs Qt table view."""
+
from __future__ import annotations
from Furious.Frozenlib import *
@@ -41,68 +43,85 @@ registerAppSettings('UserSubsHeaderViewState')
class UserSubsQTableViewHorizontalHeader(AppQHeaderView):
+ """Provide the user subs Qt table view horizontal table header."""
def __init__(self, *args, **kwargs):
+ """Initialize the UserSubsQTableViewHorizontalHeader."""
super().__init__(QtCore.Qt.Orientation.Horizontal, *args, **kwargs)
class UserSubsQTableViewVerticalHeader(AppQHeaderView):
+ """Provide the user subs Qt table view vertical table header."""
def __init__(self, *args, **kwargs):
+ """Initialize the UserSubsQTableViewVerticalHeader."""
super().__init__(QtCore.Qt.Orientation.Vertical, *args, **kwargs)
class UserSubsQTableViewHeaders:
+ """Describe and render user subs Qt table view table columns."""
def __init__(self, name: str, func: Callable[[dict], str] = None):
+ """Initialize the UserSubsQTableViewHeaders."""
self.name = name
self.func = func
def __call__(self, item: dict) -> str:
+ """Invoke the user subs Qt table view headers as a callable."""
if callable(self.func):
return self.func(item)
else:
return ''
def __eq__(self, other):
+ """Compare the user subs Qt table view headers with another value."""
return str(self) == str(other)
def __str__(self):
+ """Return the display text for the user subs Qt table view headers."""
return self.name
class UserSubsAppQComboBox(AppQComboBox):
+ """Represent user subs app q combo box."""
def __init__(self, *args, **kwargs):
+ """Initialize the UserSubsAppQComboBox."""
super().__init__(*args, **kwargs)
def retranslate(self):
# Do not emit 'currentTextChanged' when retranslate
+ """Refresh translated text for the user subs app q combo box."""
with Mixins.QBlockSignalContext(self):
super().retranslate()
class UserSubsTableModel(QtCore.QAbstractTableModel):
+ """Expose user subs table data through a Qt item model."""
def __init__(
self,
headers: list[UserSubsQTableViewHeaders],
itemKey: list[str],
parent=None,
):
+ """Initialize the UserSubsTableModel."""
super().__init__(parent)
self.headers = headers
self.itemKey = itemKey
def rowCount(self, parent=QtCore.QModelIndex()) -> int:
+ """Return the number of rows exposed by the model."""
if parent.isValid():
return 0
return len(Storage.UserSubs())
def columnCount(self, parent=QtCore.QModelIndex()) -> int:
+ """Return the number of columns exposed by the model."""
if parent.isValid():
return 0
return len(self.headers)
def flags(self, index):
+ """Return the Qt item flags for a model index."""
if not index.isValid():
return QtCore.Qt.ItemFlag.NoItemFlags
@@ -114,6 +133,7 @@ class UserSubsTableModel(QtCore.QAbstractTableModel):
return flags
def data(self, index, role=QtCore.Qt.ItemDataRole.DisplayRole):
+ """Return the data managed by the user subs table model."""
if not index.isValid():
return None
@@ -147,6 +167,7 @@ class UserSubsTableModel(QtCore.QAbstractTableModel):
return None
def setData(self, index, value, role=QtCore.Qt.ItemDataRole.EditRole) -> bool:
+ """Update model data for the requested role."""
if role != QtCore.Qt.ItemDataRole.EditRole or not index.isValid():
return False
@@ -172,6 +193,7 @@ class UserSubsTableModel(QtCore.QAbstractTableModel):
orientation: QtCore.Qt.Orientation,
role=QtCore.Qt.ItemDataRole.DisplayRole,
):
+ """Return display data for a table header section."""
if role != QtCore.Qt.ItemDataRole.DisplayRole:
return None
@@ -185,13 +207,16 @@ class UserSubsTableModel(QtCore.QAbstractTableModel):
@staticmethod
def uniqueByRow(row: int) -> str:
+ """Return the unique by row value used by the user subs table model."""
return list(Storage.UserSubs().keys())[row]
@classmethod
def subsObjectByRow(cls, row: int) -> dict:
+ """Return the subs object by row value used by the user subs table model."""
return Storage.UserSubs()[cls.uniqueByRow(row)]
def emitRowChanged(self, row: int, column: Union[int, None] = None):
+ """Handle emit row changed for the user subs table model."""
if row < 0 or row >= self.rowCount():
return
@@ -205,6 +230,7 @@ class UserSubsTableModel(QtCore.QAbstractTableModel):
self.dataChanged.emit(left, right, [])
def emitAllChanged(self):
+ """Handle emit all changed for the user subs table model."""
if self.rowCount() == 0 or self.columnCount() == 0:
return
@@ -241,6 +267,7 @@ _TRANSLATABLE_HEADERS = [
class UserSubsQTableView(Mixins.QTranslatable, AppQTableView):
+ """Represent user subs Qt table view."""
RowHeight = 42
AutoUpdateOptions = {
@@ -283,6 +310,7 @@ class UserSubsQTableView(Mixins.QTranslatable, AppQTableView):
ItemKey = ['remark', 'webURL', 'autoupdate', 'proxy']
def __init__(self, *args, **kwargs):
+ """Initialize the UserSubsQTableView."""
self.deleteUniqueCallback = kwargs.pop('deleteUniqueCallback', None)
super().__init__(*args, **kwargs)
@@ -337,15 +365,18 @@ class UserSubsQTableView(Mixins.QTranslatable, AppQTableView):
@property
def selectedIndex(self):
+ """Return the selected index value."""
return sorted(
list(set(index.row() for index in self.selectionModel().selectedRows()))
)
@QtCore.Slot(QtCore.QPoint)
def handleCustomContextMenuRequested(self, point):
+ """Handle custom context menu requested."""
self.contextMenu.exec(self.viewport().mapToGlobal(point))
def deleteSelectedItem(self):
+ """Delete selected item."""
indexes = self.selectedIndex
if len(indexes) == 0:
@@ -353,6 +384,7 @@ class UserSubsQTableView(Mixins.QTranslatable, AppQTableView):
return
def handleResultCode(_indexes, code):
+ """Handle result code."""
if code == PySide6Legacy.enumValueWrapper(
AppQMessageBox.StandardButton.Yes
):
@@ -421,6 +453,7 @@ class UserSubsQTableView(Mixins.QTranslatable, AppQTableView):
mbox.open()
def handleAutoUpdateComboBoxCurrentTextChanged(self, text: str, row: int):
+ """Handle auto update combo box current text changed."""
textEnglish = _(text, 'EN')
unique = list(Storage.UserSubs().keys())[row]
@@ -452,6 +485,7 @@ class UserSubsQTableView(Mixins.QTranslatable, AppQTableView):
)
def getHttpProxy(_subsob):
+ """Return HTTP proxy."""
return self.ProxyOptions[_subsob.get('proxy', '')]()
if self.timerConnected[row]:
@@ -496,6 +530,7 @@ class UserSubsQTableView(Mixins.QTranslatable, AppQTableView):
return textEnglish
def handleProxyComboBoxCurrentTextChanged(self, text: str, row: int):
+ """Handle proxy combo box current text changed."""
textEnglish = _(text, 'EN')
unique = list(Storage.UserSubs().keys())[row]
@@ -526,6 +561,7 @@ class UserSubsQTableView(Mixins.QTranslatable, AppQTableView):
return textEnglish
def flushItem(self, row, column, item):
+ """Refresh item."""
if row < 0 or row >= self.sourceModel.rowCount():
return
@@ -581,16 +617,19 @@ class UserSubsQTableView(Mixins.QTranslatable, AppQTableView):
self.sourceModel.emitRowChanged(row, column)
def flushRow(self, row, item):
+ """Refresh row."""
for column in list(range(self.sourceModel.columnCount())):
self.flushItem(row, column, item)
def flushAll(self):
+ """Refresh all."""
self.sourceModel.emitAllChanged()
for index, key in enumerate(Storage.UserSubs()):
self.flushRow(index, Storage.UserSubs()[key])
def appendNewItem(self, **kwargs):
+ """Append new item."""
unique, remark, webURL, autoupdate, proxy = (
kwargs.pop('unique', ''),
kwargs.pop('remark', ''),
@@ -631,6 +670,7 @@ class UserSubsQTableView(Mixins.QTranslatable, AppQTableView):
def updateSubsByUnique(
unique: str, httpProxy: Union[str, Callable, None], **kwargs
):
+ """Update subs by unique."""
showMessageBox = kwargs.pop('showMessageBox', False)
if callable(httpProxy):
@@ -653,6 +693,7 @@ class UserSubsQTableView(Mixins.QTranslatable, AppQTableView):
)
def retranslate(self):
+ """Refresh translated text for the user subs Qt table view."""
self.sourceModel.headerDataChanged.emit(
QtCore.Qt.Orientation.Horizontal,
0,
diff --git a/Furious/Widget/WaitingSpinner.py b/Furious/Widget/WaitingSpinner.py
index e4697c4e..c437d432 100644
--- a/Furious/Widget/WaitingSpinner.py
+++ b/Furious/Widget/WaitingSpinner.py
@@ -54,6 +54,7 @@ class WaitingSpinner(QWidget):
speed: float = math.pi / 2,
color: QColor = QColor(0, 0, 0),
) -> None:
+ """Initialize the WaitingSpinner."""
super().__init__(parent)
self._center_on_parent: bool = center_on_parent
diff --git a/Furious/Widget/XrayAssetViewerQListWidget.py b/Furious/Widget/XrayAssetViewerQListWidget.py
index 9f789cc1..06def656 100644
--- a/Furious/Widget/XrayAssetViewerQListWidget.py
+++ b/Furious/Widget/XrayAssetViewerQListWidget.py
@@ -15,6 +15,8 @@
# You should have received a copy of the GNU General Public License
# along with this program. If not, see .
+"""Provide widgets for Xray asset viewer Qt list widget."""
+
from __future__ import annotations
from Furious.Frozenlib import *
@@ -37,7 +39,9 @@ logger = logging.getLogger(__name__)
class MBoxAssetExists(AppQMessageBox):
+ """Represent m box asset exists."""
def __init__(self, *args, **kwargs):
+ """Initialize the MBoxAssetExists."""
super().__init__(*args, **kwargs)
self.setStandardButtons(
@@ -45,6 +49,7 @@ class MBoxAssetExists(AppQMessageBox):
)
def retranslate(self):
+ """Refresh translated text for the m box asset exists."""
self.setWindowTitle(_(self.windowTitle()))
self.setText(_(self.text()))
@@ -54,7 +59,9 @@ class MBoxAssetExists(AppQMessageBox):
class XrayAssetViewerQListWidget(Mixins.ThemeAware, AppQListWidget):
+ """Provide the Xray asset viewer Qt list widget."""
def __init__(self, *args, **kwargs):
+ """Initialize the XrayAssetViewerQListWidget."""
super().__init__(*args, **kwargs)
self.setAlternatingRowColors(True)
@@ -89,9 +96,11 @@ class XrayAssetViewerQListWidget(Mixins.ThemeAware, AppQListWidget):
@QtCore.Slot(QtCore.QPoint)
def handleCustomContextMenuRequested(self, point):
+ """Handle custom context menu requested."""
self.contextMenu.exec(self.viewport().mapToGlobal(point))
def flushItemByTheme(self, theme: str):
+ """Refresh item by theme."""
self.clear()
maxlen = max(
@@ -141,6 +150,7 @@ class XrayAssetViewerQListWidget(Mixins.ThemeAware, AppQListWidget):
self.addItem(item)
def flushItem(self):
+ """Refresh item."""
if PLATFORM == 'Linux' and SystemRuntime.ubuntuRelease() == '20.04':
assert self.initialTheme is not None
@@ -150,7 +160,9 @@ class XrayAssetViewerQListWidget(Mixins.ThemeAware, AppQListWidget):
self.flushItemByTheme(APP().theme())
def appendNewItem(self, filename: str):
+ """Append new item."""
def append(_filename):
+ """Append the Xray asset viewer Qt list widget."""
try:
shutil.copy(_filename, XRAY_ASSET_DIR)
except shutil.SameFileError:
@@ -181,6 +193,7 @@ class XrayAssetViewerQListWidget(Mixins.ThemeAware, AppQListWidget):
if os.path.isfile(XRAY_ASSET_DIR / basename):
def handleResultCode(_filename, code):
+ """Handle result code."""
if code == PySide6Legacy.enumValueWrapper(
AppQMessageBox.StandardButton.Yes
):
@@ -201,6 +214,7 @@ class XrayAssetViewerQListWidget(Mixins.ThemeAware, AppQListWidget):
append(filename)
def deleteSelectedItem(self):
+ """Delete selected item."""
indexes = self.selectedIndex
if len(indexes) == 0:
@@ -208,6 +222,7 @@ class XrayAssetViewerQListWidget(Mixins.ThemeAware, AppQListWidget):
return
def handleResultCode(_indexes, code):
+ """Handle result code."""
if code == PySide6Legacy.enumValueWrapper(
AppQMessageBox.StandardButton.Yes
):
@@ -238,12 +253,14 @@ class XrayAssetViewerQListWidget(Mixins.ThemeAware, AppQListWidget):
mbox.open()
def keyPressEvent(self, event):
+ """Handle a key press for the Xray asset viewer Qt list widget."""
if event.key() == QtCore.Qt.Key.Key_Delete:
self.deleteSelectedItem()
else:
super().keyPressEvent(event)
def themeChangedCallback(self, theme):
+ """Update the Xray asset viewer Qt list widget for a theme change."""
if PLATFORM == 'Linux' and SystemRuntime.ubuntuRelease() == '20.04':
# Ubuntu 20.04 system dark theme does not
# change menu color. Do nothing
diff --git a/Furious/Widget/__init__.py b/Furious/Widget/__init__.py
index fbf46ec6..c77ee9cd 100644
--- a/Furious/Widget/__init__.py
+++ b/Furious/Widget/__init__.py
@@ -15,6 +15,8 @@
# You should have received a copy of the GNU General Public License
# along with this program. If not, see .
+"""Expose reusable application widgets and configuration editors."""
+
from __future__ import annotations
from .Application import *
diff --git a/Furious/Window/AppMainWindow.py b/Furious/Window/AppMainWindow.py
index 52cb1b07..0f453160 100644
--- a/Furious/Window/AppMainWindow.py
+++ b/Furious/Window/AppMainWindow.py
@@ -15,6 +15,8 @@
# You should have received a copy of the GNU General Public License
# along with this program. If not, see .
+"""Provide the application window for app main window."""
+
from __future__ import annotations
from Furious.Frozenlib import *
@@ -51,10 +53,13 @@ registerAppSettings('AppMainWindowState')
class AppNetworkConnectivityManager(NetworkConnectivityManager):
+ """Coordinate app network connectivity operations."""
def __init__(self, parent=None):
+ """Initialize the AppNetworkConnectivityManager."""
super().__init__(parent)
def successCallback(self, networkReply, **kwargs):
+ """Handle a successful network operation."""
parent = self.parent()
if isinstance(parent, AppMainWindow):
@@ -63,6 +68,7 @@ class AppNetworkConnectivityManager(NetworkConnectivityManager):
super().successCallback(networkReply, **kwargs)
def failureCallback(self, networkReply, **kwargs):
+ """Handle a failed network operation."""
parent = self.parent()
if isinstance(parent, AppMainWindow):
@@ -71,6 +77,7 @@ class AppNetworkConnectivityManager(NetworkConnectivityManager):
super().failureCallback(networkReply, **kwargs)
def startSingleTest(self):
+ """Start single test."""
if not APP().isSystemTrayConnected():
parent = self.parent()
@@ -92,6 +99,7 @@ class AppNetworkConnectivityManager(NetworkConnectivityManager):
super().startSingleTest()
def disconnectedCallback(self):
+ """Update the app network connectivity manager for a disconnected state."""
parent = self.parent()
if isinstance(parent, AppMainWindow):
@@ -101,6 +109,7 @@ class AppNetworkConnectivityManager(NetworkConnectivityManager):
class NetworkStateBadge(Mixins.QTranslatable, Mixins.ThemeAware, QWidget):
+ """Provide the network state badge widget."""
DefaultIconFileName = 'reception-4.svg'
StateIconFileName = {
'success': 'reception-4.svg',
@@ -109,6 +118,7 @@ class NetworkStateBadge(Mixins.QTranslatable, Mixins.ThemeAware, QWidget):
IconSize = QtCore.QSize(16, 16)
def __init__(self, parent=None):
+ """Initialize the NetworkStateBadge."""
super().__init__(parent)
self.currentIconFileName = self.DefaultIconFileName
@@ -132,6 +142,7 @@ class NetworkStateBadge(Mixins.QTranslatable, Mixins.ThemeAware, QWidget):
self.setIconByTheme(APP().theme())
def setIconByTheme(self, theme: str, iconFileName: Union[str, None] = None):
+ """Set icon by theme."""
iconFileName = iconFileName or self.currentIconFileName
if theme == AppStyleSheet.Dark:
@@ -142,6 +153,7 @@ class NetworkStateBadge(Mixins.QTranslatable, Mixins.ThemeAware, QWidget):
self.iconLabel.setPixmap(icon.pixmap(self.IconSize))
def statusText(self):
+ """Return the status text value used by the network state badge."""
if self.currentState == 'success':
return f'{_("Network OK")} - {self.currentRemark}'
@@ -151,6 +163,7 @@ class NetworkStateBadge(Mixins.QTranslatable, Mixins.ThemeAware, QWidget):
return ''
def statusToolTip(self):
+ """Return the status tool tip value used by the network state badge."""
if self.currentState == 'success':
return self.currentRemark
@@ -160,6 +173,7 @@ class NetworkStateBadge(Mixins.QTranslatable, Mixins.ThemeAware, QWidget):
return ''
def updateStatusText(self):
+ """Update status text."""
text = self.statusText()
tooltip = self.statusToolTip()
@@ -169,6 +183,7 @@ class NetworkStateBadge(Mixins.QTranslatable, Mixins.ThemeAware, QWidget):
self.textLabel.setToolTip(tooltip)
def setStatus(self, state: str, remark: str, errorString: str = ''):
+ """Set status."""
self.currentState = state
self.currentRemark = remark
self.currentErrorString = errorString
@@ -182,6 +197,7 @@ class NetworkStateBadge(Mixins.QTranslatable, Mixins.ThemeAware, QWidget):
self.refreshStyle()
def clearStatus(self):
+ """Clear status."""
self.currentState = ''
self.currentRemark = ''
self.currentErrorString = ''
@@ -194,6 +210,7 @@ class NetworkStateBadge(Mixins.QTranslatable, Mixins.ThemeAware, QWidget):
self.refreshStyle()
def refreshStyle(self):
+ """Refresh style."""
for widget in [self, self.iconLabel, self.textLabel]:
widget.style().unpolish(widget)
widget.style().polish(widget)
@@ -202,14 +219,18 @@ class NetworkStateBadge(Mixins.QTranslatable, Mixins.ThemeAware, QWidget):
self.setVisible(bool(self.textLabel.text()))
def themeChangedCallback(self, theme: str):
+ """Update the network state badge for a theme change."""
self.setIconByTheme(theme)
def retranslate(self):
+ """Refresh translated text for the network state badge."""
self.updateStatusText()
class SearchButton(AppQPushButton):
+ """Represent search button."""
def __init__(self, *args, **kwargs):
+ """Initialize the SearchButton."""
super().__init__(*args, **kwargs)
self.setText(self.customText())
@@ -217,19 +238,23 @@ class SearchButton(AppQPushButton):
@staticmethod
def customText():
+ """Return the user-facing message text for the search button."""
return ' ' * 2 + _('Search')
def retranslate(self):
+ """Refresh translated text for the search button."""
self.setText(self.customText())
class AppMainWindow(AppQMainWindow):
+ """Present the app main window."""
DEFAULT_WINDOW_SIZE_DARWIN = QtCore.QSize(1500, 780)
DEFAULT_WINDOW_SIZE = (
QtCore.QSize(1800, 960) if PLATFORM != 'Darwin' else DEFAULT_WINDOW_SIZE_DARWIN
)
def __init__(self, *args, **kwargs):
+ """Initialize the AppMainWindow."""
super().__init__(*args, **kwargs)
if SystemRuntime.isAdmin():
@@ -610,27 +635,35 @@ class AppMainWindow(AppQMainWindow):
@QtCore.Slot(str)
def handleUserServersSearchTextChanged(self, text: str):
+ """Handle user servers search text changed."""
if not text:
self.userServersQTableWidget.clearSearch()
def updateSubsByUnique(self, unique: str, httpProxy: Union[str, None], **kwargs):
+ """Update subs by unique."""
self.userServersQTableWidget.updateSubsByUnique(unique, httpProxy, **kwargs)
def appendNewItemByFactory(self, factory: ConfigFactory):
+ """Append new item by factory."""
self.userServersQTableWidget.appendNewItemByFactory(factory)
def flushRow(self, row: int, item: ConfigFactory):
+ """Refresh row."""
self.userServersQTableWidget.flushRow(row, item)
def showTabAndSpaces(self):
+ """Show tab and spaces."""
self.userServersQTableWidget.showTabAndSpaces()
def hideTabAndSpaces(self):
+ """Hide tab and spaces."""
self.userServersQTableWidget.hideTabAndSpaces()
def getGuiTUNSettings(self, **kwargs):
+ """Return GUI TUN settings."""
@functools.lru_cache(None)
def cachedGuiTUNSettings():
+ """Return the cached GUI TUN settings value used by the app main window."""
parent = kwargs.pop('parent', self)
return GuiTUNSettings(parent=parent, **kwargs)
@@ -641,13 +674,16 @@ class AppMainWindow(AppQMainWindow):
return guiTUNSettings
def checkForUpdates(self, **kwargs):
+ """Check for updates."""
self.updatesManager.configureHttpProxy(Storage.Extras.UserHttpProxy())
self.updatesManager.checkForUpdates(**kwargs)
def resetNetworkState(self):
+ """Reset network state."""
self.networkState.clearStatus()
def setNetworkState(self, success: bool, **kwargs):
+ """Set network state."""
remark = Storage.Extras.UserServerRemark()
if not remark:
@@ -673,6 +709,7 @@ class AppMainWindow(AppQMainWindow):
@staticmethod
def openApplicationFolder():
+ """Open application folder."""
appFolder = os.path.dirname(APP().applicationFilePath())
if QDesktopServices.openUrl(QtCore.QUrl(appFolder)):
@@ -682,6 +719,7 @@ class AppMainWindow(AppQMainWindow):
@staticmethod
def restartAsAdmin():
+ """Handle restart as admin for the app main window."""
if not SystemRuntime.isScriptMode():
if not SystemRuntime.isAdmin():
process = QtCore.QProcess()
@@ -718,12 +756,14 @@ class AppMainWindow(AppQMainWindow):
@staticmethod
def openAboutPage():
+ """Open about page."""
if QDesktopServices.openUrl(QtCore.QUrl(APPLICATION_ABOUT_PAGE)):
logger.info('open about page success')
else:
logger.error('open about page failed')
def setWidthAndHeight(self):
+ """Apply the default size for the app main window."""
if AppSettings.get('AppMainWindowGeometry') is None:
# Migrate legacy settings
try:
@@ -773,11 +813,13 @@ class AppMainWindow(AppQMainWindow):
)
def retranslate(self):
+ """Refresh translated text for the app main window."""
if SystemRuntime.isAdmin():
self.setWindowTitle(f'{_(APPLICATION_NAME)} ({_(ADMINISTRATOR_NAME)})')
else:
self.setWindowTitle(f'{_(APPLICATION_NAME)}')
def cleanup(self):
+ """Release resources owned by the app main window."""
AppSettings.set('AppMainWindowGeometry', self.saveGeometry())
AppSettings.set('AppMainWindowState', self.saveState())
diff --git a/Furious/Window/LogViewerWindow.py b/Furious/Window/LogViewerWindow.py
index 67e8329a..3bb94347 100644
--- a/Furious/Window/LogViewerWindow.py
+++ b/Furious/Window/LogViewerWindow.py
@@ -15,6 +15,8 @@
# You should have received a copy of the GNU General Public License
# along with this program. If not, see .
+"""Provide the application window for log viewer window."""
+
from __future__ import annotations
from Furious.Qt import *
@@ -27,18 +29,22 @@ __all__ = ['LogViewerWindow']
class MBoxSaveError(AppQMessageBox):
+ """Represent m box save error."""
def __init__(self, *args, **kwargs):
+ """Initialize the MBoxSaveError."""
super().__init__(*args, **kwargs)
self.saveError = ''
def customText(self):
+ """Return the user-facing message text for the m box save error."""
if self.saveError:
return _('Unable to save log') + f'\n\n{self.saveError}'
else:
return _('Unable to save log')
def retranslate(self):
+ """Refresh translated text for the m box save error."""
self.setWindowTitle(_(self.windowTitle()))
self.setText(self.customText())
@@ -48,6 +54,7 @@ class MBoxSaveError(AppQMessageBox):
def saveAsFile(content: str):
+ """Save as file."""
filename, selectedFilter = QFileDialog.getSaveFileName(
None, _('Save File'), filter=_('Text files (*.txt);;All files (*)')
)
@@ -69,7 +76,9 @@ def saveAsFile(content: str):
class LogViewerWindow(AppQMainWindow):
+ """Present the log viewer window."""
def __init__(self, *args, **kwargs):
+ """Initialize the LogViewerWindow."""
tabTitle = kwargs.pop('tabTitle', '')
fontFamily = kwargs.pop('fontFamily', '')
pointSizeSettingsName = kwargs.pop('pointSizeSettingsName', '')
@@ -152,10 +161,13 @@ class LogViewerWindow(AppQMainWindow):
self.menuBar().addMenu(self._viewMenu)
def plainText(self) -> str:
+ """Return the plain text value used by the log viewer window."""
return self.textBrowser.toPlainText()
def appendLine(self, line: str):
+ """Append line."""
self.textBrowser.appendLine(line)
def clear(self):
+ """Remove all data from the log viewer window."""
self.textBrowser.clear()
diff --git a/Furious/Window/QRCodeWindow.py b/Furious/Window/QRCodeWindow.py
index 589a339e..64e56b4c 100644
--- a/Furious/Window/QRCodeWindow.py
+++ b/Furious/Window/QRCodeWindow.py
@@ -15,6 +15,8 @@
# You should have received a copy of the GNU General Public License
# along with this program. If not, see .
+"""Provide the application window for QR code window."""
+
from __future__ import annotations
from Furious.Frozenlib import *
@@ -33,7 +35,9 @@ __all__ = ['QRCodeWindow']
class QRCodeWindow(AppQMainWindow):
+ """Present the QR code window."""
def __init__(self, *args, **kwargs):
+ """Initialize the QRCodeWindow."""
super().__init__(*args, **kwargs)
self.setWindowTitle(_(APPLICATION_NAME))
@@ -46,9 +50,11 @@ class QRCodeWindow(AppQMainWindow):
self.setCentralWidget(self.tabWidget)
def tabCount(self) -> int:
+ """Return the tab count value used by the QR code window."""
return self.tabWidget.count()
def initTabByIndex(self, indexes):
+ """Handle init tab by index for the QR code window."""
self.tabWidget.clear()
for index in indexes:
@@ -79,6 +85,7 @@ class QRCodeWindow(AppQMainWindow):
@QtCore.Slot(int)
def handleTabCloseRequested(self, index):
+ """Handle tab close requested."""
self.tabWidget.removeTab(index)
if self.tabWidget.count() == 0:
diff --git a/Furious/Window/TextEditorWindow.py b/Furious/Window/TextEditorWindow.py
index b22f3778..3ddbe304 100644
--- a/Furious/Window/TextEditorWindow.py
+++ b/Furious/Window/TextEditorWindow.py
@@ -15,6 +15,8 @@
# You should have received a copy of the GNU General Public License
# along with this program. If not, see .
+"""Provide the application window for text editor window."""
+
from __future__ import annotations
from Furious.Frozenlib import *
@@ -35,7 +37,9 @@ registerAppSettings('ServerWidgetPointSize')
class MBoxQuestionSave(AppQMessageBox):
+ """Represent m box question save."""
def __init__(self, *args, **kwargs):
+ """Initialize the MBoxQuestionSave."""
super().__init__(*args, **kwargs)
self.setWindowTitle(_('Save Changes'))
@@ -51,18 +55,22 @@ class MBoxQuestionSave(AppQMessageBox):
class MBoxJSONDecodeError(AppQMessageBox):
+ """Represent m box JSON decode error."""
def __init__(self, *args, **kwargs):
+ """Initialize the MBoxJSONDecodeError."""
super().__init__(*args, **kwargs)
self.error = ''
def customText(self):
+ """Return the user-facing message text for the m box JSON decode error."""
return (
_('Please check if the configuration is in valid JSON format')
+ f'\n\n{self.error}'
)
def retranslate(self):
+ """Refresh translated text for the m box JSON decode error."""
self.setWindowTitle(_(self.windowTitle()))
self.setText(self.customText())
@@ -72,7 +80,9 @@ class MBoxJSONDecodeError(AppQMessageBox):
class TextEditorWindow(AppQMainWindow):
+ """Present the text editor window."""
def __init__(self, *args, **kwargs):
+ """Initialize the TextEditorWindow."""
super().__init__(*args, **kwargs)
self.customWindowTitle = ''
@@ -90,9 +100,11 @@ class TextEditorWindow(AppQMainWindow):
self.statusBar().addPermanentWidget(self.lineColumnLabel)
def modificationCallback():
+ """Handle the modification callback."""
self.markAsModified()
def cursorChangedCallback(cursor: QTextCursor):
+ """Handle the cursor changed callback."""
self.lineColumnLabel.setText(
f'{cursor.blockNumber() + 1}:{cursor.columnNumber() + 1} {cursor.position()}'
)
@@ -233,14 +245,17 @@ class TextEditorWindow(AppQMainWindow):
self.menuBar().addMenu(self.viewMenu)
def markAsModified(self):
+ """Handle mark as modified for the text editor window."""
self.modified = True
self.setWindowTitle(self.customWindowTitle + self.modifiedMark)
def markAsSaved(self):
+ """Handle mark as saved for the text editor window."""
self.modified = False
self.setWindowTitle(self.customWindowTitle)
def setPlainText(self, text: str, blockSignals: bool):
+ """Set plain text."""
if blockSignals:
with Mixins.QBlockSignalContext(self.jsonEditor):
self.jsonEditor.setPlainText(text)
@@ -248,6 +263,7 @@ class TextEditorWindow(AppQMainWindow):
self.jsonEditor.setPlainText(text)
def save(self, showChangesMethod='open') -> bool:
+ """Save the text editor window."""
index = self.currentIndex
if index < 0:
@@ -296,6 +312,7 @@ class TextEditorWindow(AppQMainWindow):
return True
def saveAsFile(self):
+ """Save as file."""
filename, selectedFilter = QFileDialog.getSaveFileName(
None, _('Save File'), filter=_('Text files (*.json);;All files (*)')
)
@@ -317,7 +334,9 @@ class TextEditorWindow(AppQMainWindow):
mbox.open()
def setIndent(self):
+ """Set indent."""
def handleResultCode(_indentSpinBox, code):
+ """Handle result code."""
if code == PySide6Legacy.enumValueWrapper(AppQDialog.DialogCode.Accepted):
plain = self.jsonEditor.toPlainText()
@@ -353,6 +372,7 @@ class TextEditorWindow(AppQMainWindow):
indentSpinBox.open()
def showTabAndSpaces(self):
+ """Show tab and spaces."""
textOption = QTextOption()
textOption.setFlags(QTextOption.Flag.ShowTabsAndSpaces)
@@ -362,6 +382,7 @@ class TextEditorWindow(AppQMainWindow):
self.jsonEditor.setLineWrapMode(QPlainTextEdit.LineWrapMode.NoWrap)
def hideTabAndSpaces(self):
+ """Hide tab and spaces."""
textOption = QTextOption()
self.jsonEditor.document().setDefaultTextOption(textOption)
@@ -370,11 +391,13 @@ class TextEditorWindow(AppQMainWindow):
self.jsonEditor.setLineWrapMode(QPlainTextEdit.LineWrapMode.NoWrap)
def closeEvent(self, event: QtCore.QEvent):
+ """Handle closure of the text editor window."""
if self.modified:
mbox = MBoxQuestionSave(icon=AppQMessageBox.Icon.Question, parent=self)
mbox.setWindowModality(QtCore.Qt.WindowModality.WindowModal)
def handleButtonClicked(button):
+ """Handle button clicked."""
if button == mbox.button0:
# Save
if self.save(showChangesMethod='exec'):
@@ -403,4 +426,5 @@ class TextEditorWindow(AppQMainWindow):
def retranslate(self):
# Do nothing
+ """Refresh translated text for the text editor window."""
pass
diff --git a/Furious/Window/UserRoutingWindow.py b/Furious/Window/UserRoutingWindow.py
index c86a0397..6b872984 100644
--- a/Furious/Window/UserRoutingWindow.py
+++ b/Furious/Window/UserRoutingWindow.py
@@ -15,6 +15,8 @@
# You should have received a copy of the GNU General Public License
# along with this program. If not, see .
+"""Provide the application window for user routing window."""
+
from __future__ import annotations
from Furious.Frozenlib import *
@@ -48,6 +50,7 @@ DOMAIN_STRATEGIES, ROUTING_STATES = (
def valuesToList(text: str) -> list[str]:
+ """Return the values to list value used by the application."""
values = list()
for line in text.splitlines():
@@ -61,6 +64,7 @@ def valuesToList(text: str) -> list[str]:
def listToText(value) -> str:
+ """Return the list to text value used by the application."""
if isinstance(value, list):
return '\n'.join(str(item) for item in value)
@@ -68,6 +72,7 @@ def listToText(value) -> str:
def routingStateText(state: str) -> str:
+ """Return the routing state text value used by the application."""
if state == 'Enabled':
return _('Enabled')
elif state == 'Disabled':
@@ -77,6 +82,7 @@ def routingStateText(state: str) -> str:
def addRoutingStateItems(combo: QComboBox):
+ """Add routing state items."""
combo.clear()
for state in ROUTING_STATES:
@@ -84,6 +90,7 @@ def addRoutingStateItems(combo: QComboBox):
def comboCurrentData(combo: QComboBox, default=''):
+ """Return the combo current data value used by the application."""
data = combo.currentData()
if data is None:
@@ -93,6 +100,7 @@ def comboCurrentData(combo: QComboBox, default=''):
def routingObjectFromProfile(routingProfile: dict):
+ """Return the routing object from profile value used by the application."""
if not isinstance(routingProfile, dict):
routingProfile = dict()
@@ -116,7 +124,9 @@ def routingObjectFromProfile(routingProfile: dict):
class RoutingPreviewDialog(AppQDialog):
+ """Present the routing preview dialog."""
def __init__(self, routingProfile: dict, parent=None):
+ """Initialize the RoutingPreviewDialog."""
super().__init__(parent)
self.setWindowTitle(_('Preview Routing'))
@@ -151,11 +161,14 @@ class RoutingPreviewDialog(AppQDialog):
self.setLayout(layout)
def setWidthAndHeight(self):
+ """Apply the default size for the routing preview dialog."""
self.setFixedSize(400, int(400 * GOLDEN_RATIO))
class RoutingTextEditDialog(AppQDialog):
+ """Present the routing text edit dialog."""
def __init__(self, text='', parent=None):
+ """Initialize the RoutingTextEditDialog."""
super().__init__(parent)
self.setWindowTitle(_('Edit Text'))
@@ -179,22 +192,28 @@ class RoutingTextEditDialog(AppQDialog):
self.setLayout(layout)
def setWidthAndHeight(self):
+ """Apply the default size for the routing text edit dialog."""
self.setFixedSize(760, 470)
def text(self):
+ """Return the text value."""
return self.textEdit.toPlainText()
class RoutingTextEdit(Mixins.QTranslatable, QTextEdit):
+ """Represent routing text edit."""
def __init__(self, text='', parent=None):
+ """Initialize the RoutingTextEdit."""
super().__init__(text, parent)
self.setToolTip(_('Double-click to enlarge'))
def mouseDoubleClickEvent(self, event):
+ """Handle the mouse double click event."""
dialog = RoutingTextEditDialog(self.toPlainText(), parent=self)
def handleResultCode(code):
+ """Handle result code."""
if code == PySide6Legacy.enumValueWrapper(AppQDialog.DialogCode.Accepted):
self.setPlainText(dialog.text())
@@ -204,19 +223,23 @@ class RoutingTextEdit(Mixins.QTranslatable, QTextEdit):
event.accept()
def retranslate(self):
+ """Refresh translated text for the routing text edit."""
self.setToolTip(_(self.toolTip()))
class RoutingDocumentationURL(AppQLabel):
+ """Represent routing documentation URL."""
URL = 'https://xtls.github.io/config/routing.html'
def __init__(self, *args, **kwargs):
+ """Initialize the RoutingDocumentationURL."""
super().__init__(*args, **kwargs)
self.setWebsiteURL()
self.linkActivated.connect(self.handleLinkActivated)
def setWebsiteURL(self):
+ """Set website URL."""
self.setText(
'
'
f''
@@ -227,37 +250,44 @@ class RoutingDocumentationURL(AppQLabel):
@staticmethod
def handleLinkActivated(link: str):
+ """Handle link activated."""
if QDesktopServices.openUrl(QtCore.QUrl(link)):
logger.info(f'open link \'{link}\' success')
else:
logger.error(f'open link \'{link}\' failed')
def retranslate(self):
+ """Refresh translated text for the routing documentation URL."""
self.setWebsiteURL()
class RoutingProfilesModel(QtCore.QAbstractTableModel):
+ """Expose routing profiles data through a Qt item model."""
Headers = ['Remark', 'Domain Strategy', 'State']
def rowCount(self, parent=QtCore.QModelIndex()) -> int:
+ """Return the number of rows exposed by the model."""
if parent.isValid():
return 0
return len(Storage.UserRoutings())
def columnCount(self, parent=QtCore.QModelIndex()) -> int:
+ """Return the number of columns exposed by the model."""
if parent.isValid():
return 0
return len(self.Headers)
def flags(self, index):
+ """Return the Qt item flags for a model index."""
if not index.isValid():
return QtCore.Qt.ItemFlag.NoItemFlags
return QtCore.Qt.ItemFlag.ItemIsEnabled | QtCore.Qt.ItemFlag.ItemIsSelectable
def headerData(self, section, orientation, role=QtCore.Qt.ItemDataRole.DisplayRole):
+ """Return display data for a table header section."""
if role != QtCore.Qt.ItemDataRole.DisplayRole:
return None
@@ -272,12 +302,15 @@ class RoutingProfilesModel(QtCore.QAbstractTableModel):
return section + 1
def routingUniqueByRow(self, row: int):
+ """Return the routing unique by row value used by the routing profiles model."""
return list(Storage.UserRoutings().keys())[row]
def routingByRow(self, row: int):
+ """Return the routing by row value used by the routing profiles model."""
return Storage.UserRoutings()[self.routingUniqueByRow(row)]
def data(self, index, role=QtCore.Qt.ItemDataRole.DisplayRole):
+ """Return the data managed by the routing profiles model."""
if not index.isValid():
return None
@@ -304,9 +337,11 @@ class RoutingProfilesModel(QtCore.QAbstractTableModel):
return None
def setData(self, index, value, role=QtCore.Qt.ItemDataRole.EditRole):
+ """Update model data for the requested role."""
return False
def emitAllChanged(self):
+ """Handle emit all changed for the routing profiles model."""
if self.rowCount() == 0:
return
@@ -317,11 +352,13 @@ class RoutingProfilesModel(QtCore.QAbstractTableModel):
class RoutingRuleEditDialog(AppQDialog):
+ """Present the routing rule edit dialog."""
MatchInputHeight = 72
ShortInputWidth = 240
@staticmethod
def textEdit(text=''):
+ """Return the text edit value used by the routing rule edit dialog."""
widget = RoutingTextEdit(text)
widget.setFixedHeight(RoutingRuleEditDialog.MatchInputHeight)
@@ -329,12 +366,14 @@ class RoutingRuleEditDialog(AppQDialog):
@staticmethod
def lineEdit(text=''):
+ """Return the line edit value used by the routing rule edit dialog."""
widget = QLineEdit(text)
widget.setFixedHeight(RoutingRuleEditDialog.MatchInputHeight)
return widget
def __init__(self, rule: dict, parent=None):
+ """Initialize the RoutingRuleEditDialog."""
super().__init__(parent)
self.rule = dict(rule)
@@ -500,9 +539,11 @@ class RoutingRuleEditDialog(AppQDialog):
self.setLayout(layout)
def setWidthAndHeight(self):
+ """Apply the default size for the routing rule edit dialog."""
self.resize(int(800 * GOLDEN_RATIO), 800)
def routingRule(self):
+ """Return the routing rule value used by the routing rule edit dialog."""
rule = {
'type': 'field',
'outboundTag': self.outboundEdit.text().strip() or 'proxy',
@@ -539,7 +580,9 @@ class RoutingRuleEditDialog(AppQDialog):
class RoutingRemarkEditDialog(AppQDialog):
+ """Present the routing remark edit dialog."""
def __init__(self, remark: str, parent=None):
+ """Initialize the RoutingRemarkEditDialog."""
super().__init__(parent)
self.setWindowTitle(_('Edit Routing Remark'))
@@ -563,14 +606,18 @@ class RoutingRemarkEditDialog(AppQDialog):
self.setLayout(layout)
def setWidthAndHeight(self):
+ """Apply the default size for the routing remark edit dialog."""
self.resize(420, 120)
def remark(self):
+ """Return the remark value used by the routing remark edit dialog."""
return self.remarkEdit.text().strip()
class RoutingProfileEditDialog(AppQDialog):
+ """Present the routing profile edit dialog."""
def __init__(self, parent=None):
+ """Initialize the RoutingProfileEditDialog."""
super().__init__(parent)
self.setWindowTitle(_('Add Routing'))
@@ -607,9 +654,11 @@ class RoutingProfileEditDialog(AppQDialog):
self.setLayout(layout)
def setWidthAndHeight(self):
+ """Apply the default size for the routing profile edit dialog."""
self.resize(460, 160)
def routing(self):
+ """Return the routing value used by the routing profile edit dialog."""
return {
'remark': self.remarkEdit.text().strip() or _('New Routing'),
'domainStrategy': self.domainStrategyCombo.currentText(),
@@ -619,12 +668,14 @@ class RoutingProfileEditDialog(AppQDialog):
class RoutingRulesQListWidget(AppQListWidget):
+ """Provide the routing rules Qt list widget."""
editRequested, deleteRequested = (
QtCore.Signal(),
QtCore.Signal(),
)
def __init__(self, routing: dict, parent=None):
+ """Initialize the RoutingRulesQListWidget."""
super().__init__(parent)
self.routing = routing
@@ -646,6 +697,7 @@ class RoutingRulesQListWidget(AppQListWidget):
self.flushAll()
def rules(self):
+ """Return the rules value used by the routing rules Qt list widget."""
rules = self.routing.setdefault('rules', list())
if not isinstance(rules, list):
@@ -654,9 +706,11 @@ class RoutingRulesQListWidget(AppQListWidget):
return rules
def ruleAt(self, index: int):
+ """Return the rule at value used by the routing rules Qt list widget."""
return self.rules()[index]
def ruleText(self, rule: dict) -> str:
+ """Return the rule text value used by the routing rules Qt list widget."""
name, outbound, domains, ips = (
rule.get('ruleTag', '') or 'Untitled Rule',
rule.get('outboundTag', 'proxy'),
@@ -667,6 +721,7 @@ class RoutingRulesQListWidget(AppQListWidget):
return f'{name} -> {outbound} ({domains} domains, {ips} IPs)'
def selectedRuleText(self):
+ """Select ed rule text."""
indexes = self.selectedIndex
if not indexes:
@@ -675,20 +730,24 @@ class RoutingRulesQListWidget(AppQListWidget):
return self.ruleText(self.ruleAt(indexes[0]))
def appendRule(self, rule: dict):
+ """Append rule."""
self.rules().append(rule)
self.flushAll()
def setRule(self, index: int, rule: dict):
+ """Set rule."""
self.rules()[index] = rule
self.flushAll()
def deleteRules(self, indexes: list[int]):
+ """Delete rules."""
for i in range(len(indexes)):
self.rules().pop(indexes[i] - i)
self.flushAll()
def flushAll(self):
+ """Refresh all."""
self.clear()
for rule in self.rules():
@@ -696,11 +755,14 @@ class RoutingRulesQListWidget(AppQListWidget):
@QtCore.Slot(QtCore.QPoint)
def handleCustomContextMenuRequested(self, point):
+ """Handle custom context menu requested."""
self.contextMenu.exec(self.viewport().mapToGlobal(point))
class RoutingRulesDialog(AppQDialog):
+ """Present the routing rules dialog."""
def __init__(self, routing: dict, parent=None):
+ """Initialize the RoutingRulesDialog."""
super().__init__(parent)
self.routing = routing
@@ -744,13 +806,16 @@ class RoutingRulesDialog(AppQDialog):
self.setLayout(layout)
def setWidthAndHeight(self):
+ """Apply the default size for the routing rules dialog."""
self.setFixedSize(760, 470)
def addRule(self):
+ """Add rule."""
rule = {'type': 'field', 'outboundTag': 'proxy', 'ruleTag': 'New Rule'}
dialog = RoutingRuleEditDialog(rule, parent=None)
def handleResultCode(code):
+ """Handle result code."""
if code == PySide6Legacy.enumValueWrapper(AppQDialog.DialogCode.Accepted):
self.listWidget.appendRule(dialog.routingRule())
@@ -758,6 +823,7 @@ class RoutingRulesDialog(AppQDialog):
dialog.open()
def editRule(self):
+ """Handle edit rule for the routing rules dialog."""
indexes = self.listWidget.selectedIndex
if len(indexes) != 1:
@@ -767,6 +833,7 @@ class RoutingRulesDialog(AppQDialog):
dialog = RoutingRuleEditDialog(self.listWidget.ruleAt(index), parent=None)
def handleResultCode(_index, code):
+ """Handle result code."""
if code == PySide6Legacy.enumValueWrapper(AppQDialog.DialogCode.Accepted):
self.listWidget.setRule(_index, dialog.routingRule())
@@ -774,12 +841,14 @@ class RoutingRulesDialog(AppQDialog):
dialog.open()
def deleteRule(self):
+ """Delete rule."""
indexes = self.listWidget.selectedIndex
if len(indexes) == 0:
return
def handleResultCode(_indexes, code):
+ """Handle result code."""
if code == PySide6Legacy.enumValueWrapper(
AppQMessageBox.StandardButton.Yes
):
@@ -809,14 +878,18 @@ class RoutingRulesDialog(AppQDialog):
class UserRoutingQTableViewHorizontalHeader(AppQHeaderView):
+ """Provide the user routing Qt table view horizontal table header."""
def __init__(self, *args, **kwargs):
+ """Initialize the UserRoutingQTableViewHorizontalHeader."""
super().__init__(QtCore.Qt.Orientation.Horizontal, *args, **kwargs)
class UserRoutingTableView(Mixins.QTranslatable, AppQTableView):
+ """Represent user routing table view."""
RowHeight = 42
def __init__(self, parent=None):
+ """Initialize the UserRoutingTableView."""
super().__init__(parent)
self.sourceModel = RoutingProfilesModel(parent=self)
@@ -862,6 +935,7 @@ class UserRoutingTableView(Mixins.QTranslatable, AppQTableView):
self.flushAll()
def configureHeader(self):
+ """Configure header."""
header = self.horizontalHeader()
header.setSectionsMovable(True)
header.setFirstSectionMovable(True)
@@ -870,15 +944,18 @@ class UserRoutingTableView(Mixins.QTranslatable, AppQTableView):
@property
def selectedIndex(self):
+ """Return the selected index value."""
return sorted(
list(set(index.row() for index in self.selectionModel().selectedRows()))
)
def routingUniqueByRow(self, row):
+ """Return the routing unique by row value used by the user routing table view."""
return self.sourceModel.routingUniqueByRow(row)
@QtCore.Slot(QtCore.QPoint)
def handleCustomContextMenuRequested(self, point):
+ """Handle custom context menu requested."""
indexes = self.selectedIndex
isUniqueFlag = len(indexes) == 1
@@ -889,6 +966,7 @@ class UserRoutingTableView(Mixins.QTranslatable, AppQTableView):
self.contextMenu.exec(self.viewport().mapToGlobal(point))
def flushItem(self, row: int, column: int):
+ """Refresh item."""
index = self.sourceModel.index(row, column)
if column == 1:
@@ -924,16 +1002,19 @@ class UserRoutingTableView(Mixins.QTranslatable, AppQTableView):
self.setIndexWidget(index, combo)
def flushRow(self, row: int):
+ """Refresh row."""
for column in range(self.sourceModel.columnCount()):
self.flushItem(row, column)
def flushAll(self):
+ """Refresh all."""
self.sourceModel.emitAllChanged()
for row in range(self.sourceModel.rowCount()):
self.flushRow(row)
def retranslate(self):
+ """Refresh translated text for the user routing table view."""
self.sourceModel.headerDataChanged.emit(
QtCore.Qt.Orientation.Horizontal,
0,
@@ -942,6 +1023,7 @@ class UserRoutingTableView(Mixins.QTranslatable, AppQTableView):
self.flushAll()
def setDomainStrategy(self, row: int, text: str):
+ """Set domain strategy."""
if row < 0 or row >= self.sourceModel.rowCount():
return
@@ -949,6 +1031,7 @@ class UserRoutingTableView(Mixins.QTranslatable, AppQTableView):
self.sourceModel.emitAllChanged()
def setEnabled(self, row: int, state: str):
+ """Set enabled."""
if row < 0 or row >= self.sourceModel.rowCount():
return
@@ -956,9 +1039,11 @@ class UserRoutingTableView(Mixins.QTranslatable, AppQTableView):
self.sourceModel.emitAllChanged()
def appendNewItem(self):
+ """Append new item."""
dialog = RoutingProfileEditDialog(parent=self)
def handleResultCode(code):
+ """Handle result code."""
if code != PySide6Legacy.enumValueWrapper(AppQDialog.DialogCode.Accepted):
return
@@ -976,6 +1061,7 @@ class UserRoutingTableView(Mixins.QTranslatable, AppQTableView):
dialog.open()
def deleteSelectedItem(self):
+ """Delete selected item."""
indexes = self.selectedIndex
if not indexes:
@@ -983,6 +1069,7 @@ class UserRoutingTableView(Mixins.QTranslatable, AppQTableView):
return
def handleResultCode(_indexes, code):
+ """Handle result code."""
if code != PySide6Legacy.enumValueWrapper(
AppQMessageBox.StandardButton.Yes
):
@@ -1027,6 +1114,7 @@ class UserRoutingTableView(Mixins.QTranslatable, AppQTableView):
mbox.open()
def renameSelectedItem(self):
+ """Handle rename selected item for the user routing table view."""
indexes = self.selectedIndex
if not indexes:
@@ -1042,6 +1130,7 @@ class UserRoutingTableView(Mixins.QTranslatable, AppQTableView):
dialog = RoutingRemarkEditDialog(routing.get('remark', ''), parent=self)
def handleResultCode(code):
+ """Handle result code."""
if code != PySide6Legacy.enumValueWrapper(AppQDialog.DialogCode.Accepted):
return
@@ -1055,6 +1144,7 @@ class UserRoutingTableView(Mixins.QTranslatable, AppQTableView):
dialog.open()
def previewSelectedItem(self):
+ """Handle preview selected item for the user routing table view."""
indexes = self.selectedIndex
if len(indexes) != 1:
@@ -1066,6 +1156,7 @@ class UserRoutingTableView(Mixins.QTranslatable, AppQTableView):
dialog.open()
def editSelectedRules(self):
+ """Handle edit selected rules for the user routing table view."""
indexes = self.selectedIndex
if not indexes:
@@ -1084,9 +1175,11 @@ class UserRoutingTableView(Mixins.QTranslatable, AppQTableView):
class UserRoutingWindow(AppQMainWindow):
+ """Present the user routing window."""
DEFAULT_WINDOW_SIZE = QtCore.QSize(980, 560)
def __init__(self, *args, **kwargs):
+ """Initialize the UserRoutingWindow."""
super().__init__(*args, **kwargs)
self.setWindowTitle(_('Edit Routing'))
@@ -1116,6 +1209,7 @@ class UserRoutingWindow(AppQMainWindow):
self.setCentralWidget(centralWidget)
def setWidthAndHeight(self):
+ """Apply the default size for the user routing window."""
if AppSettings.get('UserRoutingWindowGeometry') is None:
self.resize(UserRoutingWindow.DEFAULT_WINDOW_SIZE)
else:
@@ -1137,5 +1231,6 @@ class UserRoutingWindow(AppQMainWindow):
pass
def cleanup(self):
+ """Release resources owned by the user routing window."""
AppSettings.set('UserRoutingWindowGeometry', self.saveGeometry())
AppSettings.set('UserRoutingWindowState', self.saveState())
diff --git a/Furious/Window/UserSubsWindow.py b/Furious/Window/UserSubsWindow.py
index a1096e92..b70fa8b2 100644
--- a/Furious/Window/UserSubsWindow.py
+++ b/Furious/Window/UserSubsWindow.py
@@ -15,6 +15,8 @@
# You should have received a copy of the GNU General Public License
# along with this program. If not, see .
+"""Provide the application window for user subs window."""
+
from __future__ import annotations
from Furious.Frozenlib import *
@@ -37,7 +39,9 @@ registerAppSettings('UserSubsWindowState')
class AddSubsDialog(AppQDialog):
+ """Present the add subs dialog."""
def __init__(self, *args, **kwargs):
+ """Initialize the AddSubsDialog."""
super().__init__(*args, **kwargs)
self.setWindowTitle(_('Add Subscription'))
@@ -67,19 +71,24 @@ class AddSubsDialog(AppQDialog):
self.setLayout(layout)
def setWidthAndHeight(self):
+ """Apply the default size for the add subs dialog."""
self.resize(456, 150)
def subsRemark(self):
+ """Return the subs remark value used by the add subs dialog."""
return self.remarkEdit.text()
def subsWebURL(self):
+ """Return the subs web URL value used by the add subs dialog."""
return self.webURLEdit.text()
class UserSubsWindow(AppQMainWindow):
+ """Present the user subs window."""
DEFAULT_WINDOW_SIZE = QtCore.QSize(1120, 600)
def __init__(self, *args, **kwargs):
+ """Initialize the UserSubsWindow."""
callback = kwargs.pop('deleteUniqueCallback', None)
super().__init__(*args, **kwargs)
@@ -116,7 +125,9 @@ class UserSubsWindow(AppQMainWindow):
self.setCentralWidget(self.fakeCentralWidget)
def addSubs(self):
+ """Add subs."""
def handleResultCode(_addSubsDialog, code):
+ """Handle result code."""
if code == PySide6Legacy.enumValueWrapper(AppQDialog.DialogCode.Accepted):
remark = _addSubsDialog.subsRemark()
webURL = _addSubsDialog.subsWebURL()
@@ -142,15 +153,18 @@ class UserSubsWindow(AppQMainWindow):
addSubsDialog.open()
def deleteSelectedItem(self):
+ """Delete selected item."""
self.userSubsQTableWidget.deleteSelectedItem()
def keyPressEvent(self, event):
+ """Handle a key press for the user subs window."""
if event.key() == QtCore.Qt.Key.Key_Delete:
self.deleteSelectedItem()
else:
super().keyPressEvent(event)
def setWidthAndHeight(self):
+ """Apply the default size for the user subs window."""
if AppSettings.get('UserSubsWindowGeometry') is None:
# Migrate legacy settings
try:
@@ -185,5 +199,6 @@ class UserSubsWindow(AppQMainWindow):
pass
def cleanup(self):
+ """Release resources owned by the user subs window."""
AppSettings.set('UserSubsWindowGeometry', self.saveGeometry())
AppSettings.set('UserSubsWindowState', self.saveState())
diff --git a/Furious/Window/XrayAssetViewerWindow.py b/Furious/Window/XrayAssetViewerWindow.py
index 8c898c93..65bc0375 100644
--- a/Furious/Window/XrayAssetViewerWindow.py
+++ b/Furious/Window/XrayAssetViewerWindow.py
@@ -15,6 +15,8 @@
# You should have received a copy of the GNU General Public License
# along with this program. If not, see .
+"""Provide the application window for Xray asset viewer window."""
+
from __future__ import annotations
from Furious.Frozenlib import *
@@ -33,7 +35,9 @@ logger = logging.getLogger(__name__)
class XrayAssetViewerWindow(AppQMainWindow):
+ """Present the Xray asset viewer window."""
def __init__(self, *args, **kwargs):
+ """Initialize the XrayAssetViewerWindow."""
super().__init__(*args, **kwargs)
self.setWindowTitle(_('Xray-core Asset File'))
@@ -83,19 +87,23 @@ class XrayAssetViewerWindow(AppQMainWindow):
self.menuBar().addMenu(self.fileMenu)
def setWidthAndHeight(self):
+ """Apply the default size for the Xray asset viewer window."""
self.resize(360, 360 * GOLDEN_RATIO)
def flushItem(self):
+ """Refresh item."""
self.xrayAssetViewerWidget.flushItem()
@staticmethod
def openAssetDirectory():
+ """Open asset directory."""
if QDesktopServices.openUrl(QtCore.QUrl.fromLocalFile(XRAY_ASSET_DIR)):
logger.info(f'open Xray-core asset dir success')
else:
logger.error(f'open Xray-core asset dir failed')
def appendNewItem(self):
+ """Append new item."""
filename, selectedFilter = QFileDialog.getOpenFileName(
None, _('Import File'), filter=_('All files (*)')
)
diff --git a/Furious/Window/__init__.py b/Furious/Window/__init__.py
index a3b1f660..3dd14ce0 100644
--- a/Furious/Window/__init__.py
+++ b/Furious/Window/__init__.py
@@ -15,6 +15,8 @@
# You should have received a copy of the GNU General Public License
# along with this program. If not, see .
+"""Expose the application window classes."""
+
from __future__ import annotations
from .AppMainWindow import *
diff --git a/Furious/__init__.py b/Furious/__init__.py
index 1d2f9afd..106bcf22 100644
--- a/Furious/__init__.py
+++ b/Furious/__init__.py
@@ -15,6 +15,8 @@
# You should have received a copy of the GNU General Public License
# along with this program. If not, see .
+"""Initialize the Furious application package."""
+
from __future__ import annotations
from .Frozenlib import AppResources
diff --git a/Furious/__main__.py b/Furious/__main__.py
index 4b82e7e3..7e56971f 100644
--- a/Furious/__main__.py
+++ b/Furious/__main__.py
@@ -15,6 +15,8 @@
# You should have received a copy of the GNU General Public License
# along with this program. If not, see .
+"""Provide the Furious GUI application entry point."""
+
from __future__ import annotations
from Furious.Frozenlib import *
@@ -39,6 +41,7 @@ logger = logging.getLogger(__name__)
def runClearSettings():
+ """Run clear settings."""
app = QtCore.QCoreApplication(sys.argv)
app.setApplicationName(APPLICATION_NAME)
app.setApplicationVersion(APPLICATION_VERSION)
@@ -55,6 +58,7 @@ def runClearSettings():
def runAppMain():
+ """Run app main."""
process = AppMainProcess(functools.partial(Application, sys.argv))
process.start()
@@ -130,6 +134,7 @@ def runAppMain():
button1 = mbox.addButton(_('OK'), AppQMessageBox.ButtonRole.RejectRole)
def handleButtonClicked(button):
+ """Handle button clicked."""
if button == button0:
# Open
if QDesktopServices.openUrl(
@@ -157,6 +162,7 @@ def runAppMain():
def main():
+ """Run the module command-line entry point."""
try:
if len(sys.argv) > 1 and sys.argv[1] == AppBuiltinCommand.Clear.value:
runClearSettings()