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()