Refactor package architecture

Signed-off-by: Loren Eteval <loren.eteval@proton.me>
This commit is contained in:
Loren Eteval
2026-08-12 12:20:47 +08:00
parent aa38caf430
commit f1f262e4ee
88 changed files with 869 additions and 794 deletions
@@ -21,12 +21,13 @@ from __future__ import annotations
from Furious.Frozenlib import *
from Furious.Interface import *
from Furious.Library import *
from Furious.Domain import *
from Furious.Repository import *
from Furious.Plugins import getPluginRegistry
from Furious.Qt import *
from Furious.Qt import gettext as _
from Furious.Service import CoreManager
from Furious.TrayActions.ConnectProgressBar import ConnectProgressBar
from Furious.Service import ConnectionManager, UpdateManager
from Furious.Widget.ConnectionProgressWidget import ConnectionProgressWidget
from PySide6 import QtCore
@@ -69,13 +70,13 @@ class ConnectAction(AppQAction):
)
self.actionQueue = queue.Queue()
self.coreManager = CoreManager()
self.progressBar = ConnectProgressBar()
self.coreManager = ConnectionManager()
self.progressBar = ConnectionProgressWidget()
self.actionTimer = QtCore.QTimer()
self.actionTimer.timeout.connect(lambda: self.callActionFromQueue())
self.updatesManager = UpdatesManager()
self.updatesManager = UpdateManager()
def reset(self):
"""Restore the connect action to its initial state."""
@@ -358,7 +359,7 @@ class ConnectAction(AppQAction):
if callable(action):
action()
def coreExitCallback(self, core: CoreProcessFactory, exitcode: int):
def coreExitCallback(self, core: CoreProcess, exitcode: int):
"""Handle the core exit callback."""
def putItem(item):
@@ -370,11 +371,11 @@ class ConnectAction(AppQAction):
pass
if exitcode == CoreProcessFactory.ExitCode.SystemShuttingDown.value:
if exitcode == CoreProcess.ExitCode.SystemShuttingDown.value:
# System shutting down. Do nothing
return None
if exitcode == CoreProcessFactory.ExitCode.ConfigurationError.value:
if exitcode == CoreProcess.ExitCode.ConfigurationError.value:
putItem(
functools.partial(
self.doDisconnectWithTrayMessage,
@@ -384,7 +385,7 @@ class ConnectAction(AppQAction):
return None
if exitcode == CoreProcessFactory.ExitCode.ServerStartFailure.value:
if exitcode == CoreProcess.ExitCode.ServerStartFailure.value:
putItem(
functools.partial(
self.doDisconnectWithTrayMessage,
@@ -20,10 +20,11 @@
from __future__ import annotations
from Furious.Frozenlib import *
from Furious.Library import *
from Furious.Domain import *
from Furious.Repository import *
from Furious.Qt import *
from Furious.Qt import gettext as _
from Furious.Qt.WaitingSpinner import *
from Furious.Widget.WaitingSpinner import *
from PySide6 import QtCore
from PySide6.QtGui import QColor
@@ -20,7 +20,7 @@
from __future__ import annotations
from Furious.Frozenlib import *
from Furious.Library import *
from Furious.Repository import *
from Furious.Plugins import getPluginRegistry
from Furious.Qt import *
from Furious.Qt import gettext as _
@@ -15,13 +15,12 @@
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <https://www.gnu.org/licenses/>.
"""Expose system-tray actions used by the application."""
"""Expose reusable application actions and action menus."""
from __future__ import annotations
from .Connect import ConnectAction
from .ConnectProgressBar import ConnectProgressBar
from .EditConfiguration import EditConfigurationAction
from .Configuration import EditConfigurationAction
from .Connection import ConnectAction
from .Exit import ExitAction
from .Import import (
ImportAction,
@@ -37,7 +36,6 @@ from .SystemProxy import SystemProxyAction
__all__ = [
'ConnectAction',
'ConnectProgressBar',
'EditConfigurationAction',
'ExitAction',
'ImportAction',
@@ -22,14 +22,15 @@ from __future__ import annotations
from Furious.Frozenlib import *
from Furious.Interface import *
from Furious.Core import Tun2socks
from Furious.Plugins import getPluginRegistry
from Furious.Backends import OFFICIAL_PLUGIN_TYPES
from Furious.Plugins import initializePluginRegistry
from Furious.Qt import AppStyleSheet
from Furious.Qt.TextEditorTheme import configureEditorLogMetadata
from Furious.Qt import gettext as _
from Furious.Library import *
from Furious.Application.SystemTrayIcon import *
from Furious.Window.AppMainWindow import *
from Furious.Window.LogViewerWindow import *
from Furious.Repository import *
from Furious.Application.TrayIcon import *
from Furious.Window.LogWindow import *
from Furious.Window.MainWindow import *
from PySide6 import QtCore
from PySide6.QtGui import *
@@ -168,11 +169,11 @@ class ApplicationThemeDetector(QtCore.QObject):
super().__init__(*args, **kwargs)
class Application(ApplicationFactory, SingletonApplication):
class DesktopApplication(ApplicationRunner, SingletonApplication):
"""Represent application."""
def __init__(self, argv):
"""Initialize the Application."""
"""Initialize the desktop application."""
super().__init__(argv)
self.setApplicationName(APPLICATION_NAME)
@@ -229,7 +230,7 @@ class Application(ApplicationFactory, SingletonApplication):
datastr = str(data)
if datastr == AppBuiltinCommand.Empty.value:
if isinstance(self.systemTray, SystemTrayIcon):
if isinstance(self.systemTray, TrayIcon):
logger.info('attempting to start multiple instance. Show tray message')
self.systemTray.showMessage(_('Already started'))
@@ -246,17 +247,17 @@ class Application(ApplicationFactory, SingletonApplication):
def configureLogging(self):
"""Configure logging."""
self.logViewerWindowSelf = LogViewerWindow(
self.logViewerWindowSelf = LogWindow(
tabTitle=_('Furious Log'),
fontFamily=self.customFontName,
pointSizeSettingsName='LogViewerWidgetPointSizeSelf',
)
self.logViewerWindowCore = LogViewerWindow(
self.logViewerWindowCore = LogWindow(
tabTitle=_('Core Log'),
fontFamily=self.customFontName,
pointSizeSettingsName='LogViewerWidgetPointSizeCore',
)
self.logViewerWindowTun_ = LogViewerWindow(
self.logViewerWindowTun_ = LogWindow(
tabTitle=_('Tun2socks Log'),
fontFamily=self.customFontName,
pointSizeSettingsName='LogViewerWidgetPointSizeTun_',
@@ -298,7 +299,7 @@ class Application(ApplicationFactory, SingletonApplication):
@staticmethod
def addEnviron():
"""Add environ."""
pluginRegistry = getPluginRegistry()
pluginRegistry = initializePluginRegistry(OFFICIAL_PLUGIN_TYPES)
configureEditorLogMetadata(
lambda: (*pluginRegistry.coreVersions(), Tun2socks.version()),
pluginRegistry.logTimestampPatterns,
@@ -321,7 +322,7 @@ class Application(ApplicationFactory, SingletonApplication):
def isSystemTrayConnected(self):
"""Return whether system tray connected."""
if isinstance(self.systemTray, SystemTrayIcon):
if isinstance(self.systemTray, TrayIcon):
return self.systemTray.ConnectAction.isConnected()
else:
return False
@@ -467,11 +468,11 @@ class Application(ApplicationFactory, SingletonApplication):
# sys.exit(None) in multiprocessing will produce
# exitcode 1 in some Python version, which is
# not what we want.
return ApplicationFactory.ExitCode.ExitSuccess.value
return ApplicationRunner.ExitCode.ExitSuccess.value
if not SystemTrayIcon.isSystemTrayAvailable():
if not TrayIcon.isSystemTrayAvailable():
raise SystemTrayUnavailable(
'SystemTrayIcon is not available on this platform'
'TrayIcon is not available on this platform'
)
self.addEnviron()
@@ -560,9 +561,9 @@ class Application(ApplicationFactory, SingletonApplication):
# Mandatory
self.setQuitOnLastWindowClosed(False)
self.aboutToQuit.connect(Application.cleanup)
self.aboutToQuit.connect(DesktopApplication.cleanup)
Win32Session.set(Application.cleanup)
Win32Session.set(DesktopApplication.cleanup)
Win32Session.run()
if AppSettings.get('SystemProxyMode') == AppBuiltinProxyMode.Auto.value:
@@ -570,8 +571,8 @@ class Application(ApplicationFactory, SingletonApplication):
SystemProxy.off()
SystemProxy.daemonOn_()
self.mainWindow = AppMainWindow()
self.systemTray = SystemTrayIcon()
self.mainWindow = MainWindow()
self.systemTray = TrayIcon()
if PLATFORM == 'Darwin':
if AppSettings.isStateON_('HideDockIcon'):
@@ -601,10 +602,10 @@ class Application(ApplicationFactory, SingletonApplication):
return self.exec()
except SystemTrayUnavailable:
return ApplicationFactory.ExitCode.PlatformNotSupported.value
return ApplicationRunner.ExitCode.PlatformNotSupported.value
except Exception:
# Any non-exit exceptions
traceback.print_exc()
return ApplicationFactory.ExitCode.UnknownException.value
return ApplicationRunner.ExitCode.UnknownException.value
@@ -22,7 +22,7 @@ from __future__ import annotations
from Furious.Frozenlib import *
from Furious.Qt import *
from Furious.Qt import gettext as _
from Furious.TrayActions import *
from Furious.Actions import *
from PySide6 import QtCore
from PySide6.QtWidgets import QSystemTrayIcon
@@ -32,7 +32,7 @@ import platform
logger = logging.getLogger(__name__)
__all__ = ['SystemTrayIcon']
__all__ = ['TrayIcon']
_MACOS_DISCONNECTED_MONOCHROME_OPACITY = 0.55
@@ -41,7 +41,7 @@ _TRANSLATABLE_ADMIN = [
]
class SystemTrayIcon(
class TrayIcon(
Mixins.QTranslatable,
Mixins.ConnectionAware,
Mixins.ThemeAware,
@@ -50,7 +50,7 @@ class SystemTrayIcon(
"""Represent system tray icon."""
def __init__(self, *args, **kwargs):
"""Initialize the SystemTrayIcon."""
"""Initialize the tray icon."""
super().__init__(*args, **kwargs)
actions = [
@@ -71,7 +71,7 @@ class SystemTrayIcon(
self._dynamicMenuRefs = []
# Some old version PySide6 does not have setMenu method
# for QAction. Protect it. Currently only used in SystemTrayIcon
# for QAction. Protect it. Currently only used in TrayIcon
if hasattr(AppQAction, 'setMenu'):
logger.info('contextMenu uses setMenu implementation')
+3 -3
View File
@@ -19,7 +19,7 @@
from __future__ import annotations
from .Application import Application
from .SystemTrayIcon import SystemTrayIcon
from .DesktopApplication import DesktopApplication
from .TrayIcon import TrayIcon
__all__ = ['Application', 'SystemTrayIcon']
__all__ = ['DesktopApplication', 'TrayIcon']
@@ -21,8 +21,8 @@ from __future__ import annotations
from Furious.Frozenlib import *
from Furious.Interface import *
from Furious.Library.Configuration import ConfigFactory
from Furious.Library.Encoder import *
from Furious.Domain.Configuration import ConfigFactory
from Furious.Domain.Encoding import *
from typing import Union, Tuple
@@ -21,7 +21,7 @@ from __future__ import annotations
from Furious.Frozenlib import *
from Furious.Interface import *
from Furious.Library import ConfigFactory
from Furious.Domain import ConfigFactory
from Furious.Qt import *
from Furious.Qt import gettext as _
@@ -32,7 +32,7 @@ from PySide6.QtWidgets import *
import logging
import functools
__all__ = ['GuiHysteria1']
__all__ = ['Hysteria1Editor']
logger = logging.getLogger(__name__)
@@ -343,7 +343,7 @@ class GuiHy1GroupBoxTLS(GuiEditorWidgetQGroupBox):
]
class GuiHy1GroupBoxOther(GuiEditorItemFactory, AppQGroupBox):
class GuiHy1GroupBoxOther(EditorBinding, AppQGroupBox):
"""Represent GUI hy1 group box other."""
def __init__(self, **kwargs):
@@ -366,11 +366,11 @@ class GuiHy1GroupBoxOther(GuiEditorItemFactory, AppQGroupBox):
pass
class GuiHysteria1(GuiEditorWidgetQDialog):
class Hysteria1Editor(GuiEditorWidgetQDialog):
"""Represent GUI hysteria1."""
def __init__(self, *args, **kwargs):
"""Initialize the GuiHysteria1."""
"""Initialize the Hysteria 1 configuration editor."""
super().__init__(*args, **kwargs)
self.setTabText(Protocol.Hysteria1.value)
@@ -22,9 +22,9 @@ from __future__ import annotations
from Furious.Frozenlib import *
from Furious.Qt.DynamicTranslate import gettext as _
from Furious.Plugins.API import *
from Furious.Plugins.Official.Configuration import *
from Furious.Backends.Configuration import *
from .Core import *
from .Process import *
import copy
import logging
@@ -94,9 +94,9 @@ class Hysteria1Plugin(FuriousPlugin):
def createEditorForProtocol(self, protocol, parent=None, **kwargs):
"""Create the Hysteria 1 editor."""
# Plugin discovery can occur while the Furious.Qt package is initializing.
from .GuiHysteria1 import GuiHysteria1
from .Editor import Hysteria1Editor
return GuiHysteria1(parent=parent, **kwargs)
return Hysteria1Editor(parent=parent, **kwargs)
def routingOptions(self, config=None):
"""Return the routing modes supported by Hysteria 1."""
@@ -21,7 +21,7 @@ from __future__ import annotations
from Furious.Frozenlib import *
from Furious.Interface import *
from Furious.Library import ConfigFactory
from Furious.Domain import ConfigFactory
from Furious.Qt import *
from Furious.Qt import gettext as _
@@ -32,7 +32,7 @@ from PySide6.QtWidgets import *
import logging
import functools
__all__ = ['GuiHysteria2']
__all__ = ['Hysteria2Editor']
logger = logging.getLogger(__name__)
@@ -699,7 +699,7 @@ class GuiHy2GroupBoxProxy(GuiEditorWidgetQGroupBox):
]
class GuiHy2GroupBoxObfs(GuiEditorItemFactory, AppQGroupBox):
class GuiHy2GroupBoxObfs(EditorBinding, AppQGroupBox):
"""Represent GUI hy2 group box obfs."""
def __init__(self, **kwargs):
@@ -848,7 +848,7 @@ class GuiHy2ProjectWebsiteURL(AppQLabel):
self.setWebsiteURL()
class GuiHy2GroupBoxOther(GuiEditorItemFactory, AppQGroupBox):
class GuiHy2GroupBoxOther(EditorBinding, AppQGroupBox):
"""Represent GUI hy2 group box other."""
def __init__(self, **kwargs):
@@ -871,11 +871,11 @@ class GuiHy2GroupBoxOther(GuiEditorItemFactory, AppQGroupBox):
pass
class GuiHysteria2(GuiEditorWidgetQDialog):
class Hysteria2Editor(GuiEditorWidgetQDialog):
"""Represent GUI hysteria2."""
def __init__(self, *args, **kwargs):
"""Initialize the GuiHysteria2."""
"""Initialize the Hysteria 2 configuration editor."""
super().__init__(*args, **kwargs)
self.setTabText(Protocol.Hysteria2.value)
@@ -22,9 +22,9 @@ from __future__ import annotations
from Furious.Frozenlib import *
from Furious.Qt.DynamicTranslate import gettext as _
from Furious.Plugins.API import *
from Furious.Plugins.Official.Configuration import *
from Furious.Backends.Configuration import *
from .Core import Hysteria2
from .Process import Hysteria2
from .TUN import *
import copy
@@ -103,9 +103,9 @@ class Hysteria2Plugin(FuriousPlugin):
def createEditorForProtocol(self, protocol, parent=None, **kwargs):
"""Create the Hysteria 2 editor."""
# Plugin discovery can occur while the Furious.Qt package is initializing.
from .GuiHysteria2 import GuiHysteria2
from .Editor import Hysteria2Editor
return GuiHysteria2(parent=parent, **kwargs)
return Hysteria2Editor(parent=parent, **kwargs)
def createManagementActions(self, parent=None, **kwargs):
"""Create Hysteria 2 native TUN management actions."""
@@ -115,7 +115,7 @@ class Hysteria2Plugin(FuriousPlugin):
from Furious.Qt import AppQAction, showMBoxNewChangesNextTime
from Furious.Qt import gettext as _
from .GuiTUNSettings import GuiHysteria2TUNSettings
from .TunSettingsDialog import Hysteria2TunSettingsDialog
useHysteria2TUNAction = AppQAction(
_('Use Hysteria2 TUN'),
@@ -136,7 +136,7 @@ class Hysteria2Plugin(FuriousPlugin):
useHysteria2TUNAction,
AppQAction(
_('Customize Hysteria2 TUN Settings...'),
callback=lambda: GuiHysteria2TUNSettings(
callback=lambda: Hysteria2TunSettingsDialog(
parent=parent,
isConnectionActive=lambda: isCoreActive(Hysteria2),
).open(),
@@ -146,7 +146,7 @@ class Hysteria2Plugin(FuriousPlugin):
def prepareTUN(self, config) -> bool:
"""Add Hysteria 2 native TUN mode when enabled and safe to route."""
if not isHysteria2TUNEnabled():
# Work on CoreManager's connection copy so a stored native TUN block
# Work on ConnectionManager's copy so a stored native TUN block
# cannot run alongside the external tun2socks implementation.
config.pop('tun', None)
@@ -154,7 +154,7 @@ class Hysteria2Plugin(FuriousPlugin):
if PLATFORM == 'Linux' and not SystemRuntime.isAdmin():
# Native Hysteria 2 creates the interface and routing table in its
# own process, so it cannot use CoreManager's privileged helper.
# own process, so it cannot use ConnectionManager's privileged helper.
# Keep the existing external tun2socks path available instead.
config.pop('tun', None)
@@ -34,7 +34,7 @@ import copy
import logging
import functools
__all__ = ['GuiHysteria2TUNSettings']
__all__ = ['Hysteria2TunSettingsDialog']
logger = logging.getLogger(__name__)
@@ -262,7 +262,7 @@ class Hysteria2TUNDocumentationURL(AppQLabel):
self.setWebsiteURL()
class GuiHysteria2TUNSettings(GuiEditorWidgetQDialog):
class Hysteria2TunSettingsDialog(GuiEditorWidgetQDialog):
"""Edit and persist Hysteria 2 native TUN settings."""
def __init__(self, *args, **kwargs):
@@ -33,7 +33,7 @@ import logging
import datetime
import functools
__all__ = ['XrayAssetViewerQListWidget']
__all__ = ['XrayAssetListWidget']
logger = logging.getLogger(__name__)
@@ -59,11 +59,11 @@ class MBoxAssetExists(AppQMessageBox):
self.moveToCenter()
class XrayAssetViewerQListWidget(Mixins.ThemeAware, AppQListWidget):
class XrayAssetListWidget(Mixins.ThemeAware, AppQListWidget):
"""Provide the Xray asset viewer Qt list widget."""
def __init__(self, *args, **kwargs):
"""Initialize the XrayAssetViewerQListWidget."""
"""Initialize the Xray asset list widget."""
super().__init__(*args, **kwargs)
self.setAlternatingRowColors(True)
@@ -23,7 +23,7 @@ from Furious.Frozenlib import *
from Furious.Qt import *
from Furious.Qt import gettext as _
from .XrayAssetViewerQListWidget import *
from .AssetListWidget import *
from PySide6 import QtCore
from PySide6.QtGui import *
@@ -31,21 +31,21 @@ from PySide6.QtWidgets import *
import logging
__all__ = ['XrayAssetViewerWindow']
__all__ = ['XrayAssetWindow']
logger = logging.getLogger(__name__)
class XrayAssetViewerWindow(AppQMainWindow):
class XrayAssetWindow(AppQMainWindow):
"""Present the Xray asset viewer window."""
def __init__(self, *args, **kwargs):
"""Initialize the XrayAssetViewerWindow."""
"""Initialize the Xray asset window."""
super().__init__(*args, **kwargs)
self.setWindowTitle(_('Xray-core Asset File'))
self.xrayAssetViewerWidget = XrayAssetViewerQListWidget(parent=self)
self.xrayAssetViewerWidget = XrayAssetListWidget(parent=self)
self.setCentralWidget(self.xrayAssetViewerWidget)
if versionToValue(PYSIDE6_VERSION) <= versionToValue('6.1.3'):
@@ -22,11 +22,11 @@ from __future__ import annotations
from Furious.Frozenlib import *
from Furious.Qt.DynamicTranslate import gettext as _
from Furious.Core import *
from Furious.Library import *
from Furious.Repository import *
from Furious.Plugins.API import *
from Furious.Plugins.Official.Configuration import *
from Furious.Backends.Configuration import *
from .Core import *
from .Process import *
from .Routing import *
from .TUN import *
@@ -161,18 +161,18 @@ class XrayPlugin(FuriousPlugin):
def createEditorForProtocol(self, protocol, parent=None, **kwargs):
"""Create the Xray editor matching a protocol identifier."""
# Plugin discovery can occur while the Furious.Qt package is initializing.
from .GuiShadowsocks import GuiShadowsocks
from .GuiSocks import GuiSocks
from .GuiTrojan import GuiTrojan
from .GuiVLESS import GuiVLESS
from .GuiVMess import GuiVMess
from .ShadowsocksEditor import ShadowsocksEditor
from .SocksEditor import SocksEditor
from .TrojanEditor import TrojanEditor
from .VlessEditor import VlessEditor
from .VmessEditor import VmessEditor
editors = {
'vmess': GuiVMess,
'vless': GuiVLESS,
'shadowsocks': GuiShadowsocks,
'socks': GuiSocks,
'trojan': GuiTrojan,
'vmess': VmessEditor,
'vless': VlessEditor,
'shadowsocks': ShadowsocksEditor,
'socks': SocksEditor,
'trojan': TrojanEditor,
}
editorType = editors.get(_protocolId(protocol))
@@ -195,12 +195,12 @@ class XrayPlugin(FuriousPlugin):
)
from Furious.Qt import gettext as _
from .GuiTUNSettings import GuiXrayTUNSettings
from .UserRoutingWindow import UserRoutingWindow
from .XrayAssetViewerWindow import XrayAssetViewerWindow
from .AssetWindow import XrayAssetWindow
from .RoutingWindow import XrayRoutingWindow
from .TunSettingsDialog import XrayTunSettingsDialog
routingEditor = UserRoutingWindow(parent=parent, **kwargs)
assetViewer = XrayAssetViewerWindow(parent=parent)
routingEditor = XrayRoutingWindow(parent=parent, **kwargs)
assetViewer = XrayAssetWindow(parent=parent)
def showRoutingDialog():
"""Restore and foreground the persistent routing editor window."""
@@ -237,7 +237,7 @@ class XrayPlugin(FuriousPlugin):
useXrayTUNAction,
AppQAction(
_('Customize Xray-core TUN Settings...'),
callback=lambda: GuiXrayTUNSettings(
callback=lambda: XrayTunSettingsDialog(
parent=parent,
isConnectionActive=lambda: isCoreActive(XrayCore),
).open(),
@@ -443,7 +443,7 @@ class XrayPlugin(FuriousPlugin):
return
# The download manager is Qt-based and is not needed until this hook runs.
from .XrayAssetDownloadManager import XrayAssetDownloadManager
from .AssetDownloadManager import XrayAssetDownloadManager
try:
assetDownloadManager = self._assetDownloadManager
@@ -21,7 +21,7 @@ from __future__ import annotations
from Furious.Frozenlib import *
from Furious.Interface import *
from Furious.Library import *
from Furious.Domain import *
from Furious.Core import *
from enum import Enum
@@ -19,7 +19,7 @@
from __future__ import annotations
from Furious.Library import *
from Furious.Repository import *
__all__ = ['cleanRoutingRule', 'customRoutingObjectFromSettings']
@@ -20,7 +20,8 @@
from __future__ import annotations
from Furious.Frozenlib import *
from Furious.Library import *
from Furious.Domain import *
from Furious.Repository import *
from Furious.Qt import *
from Furious.Qt import gettext as _
@@ -35,7 +36,7 @@ import functools
from .Routing import cleanRoutingRule
__all__ = ['UserRoutingWindow']
__all__ = ['XrayRoutingWindow']
logger = logging.getLogger(__name__)
@@ -1187,13 +1188,13 @@ class UserRoutingTableView(Mixins.QTranslatable, AppQTableView):
dialog.open()
class UserRoutingWindow(AppQMainWindow):
class XrayRoutingWindow(AppQMainWindow):
"""Present the user routing window."""
DEFAULT_WINDOW_SIZE = QtCore.QSize(980, 560)
def __init__(self, *args, **kwargs):
"""Initialize the UserRoutingWindow."""
"""Initialize the Xray routing editor window."""
super().__init__(*args, **kwargs)
self.setWindowTitle(_('Edit Routing'))
@@ -1225,17 +1226,17 @@ class UserRoutingWindow(AppQMainWindow):
def setWidthAndHeight(self):
"""Apply the default size for the user routing window."""
if AppSettings.get('UserRoutingWindowGeometry') is None:
self.resize(UserRoutingWindow.DEFAULT_WINDOW_SIZE)
self.resize(XrayRoutingWindow.DEFAULT_WINDOW_SIZE)
else:
if PLATFORM == 'Darwin':
self.resize(UserRoutingWindow.DEFAULT_WINDOW_SIZE)
self.resize(XrayRoutingWindow.DEFAULT_WINDOW_SIZE)
else:
try:
self.restoreGeometry(AppSettings.get('UserRoutingWindowGeometry'))
except Exception:
# Any non-exit exceptions
self.resize(UserRoutingWindow.DEFAULT_WINDOW_SIZE)
self.resize(XrayRoutingWindow.DEFAULT_WINDOW_SIZE)
try:
self.restoreState(AppSettings.get('UserRoutingWindowState'))
@@ -21,16 +21,16 @@ from __future__ import annotations
from Furious.Frozenlib import *
from Furious.Interface import *
from Furious.Library import *
from Furious.Domain import *
from Furious.Qt import *
from Furious.Qt import gettext as _
from Furious.Plugins.Official.Configuration import *
from Furious.Plugins.Official.Xray.GuiVTransport import *
from Furious.Plugins.Official.Xray.GuiVTLS import *
from Furious.Backends.Configuration import *
from Furious.Backends.Xray.TlsEditor import *
from Furious.Backends.Xray.TransportEditor import *
import functools
__all__ = ['GuiShadowsocks']
__all__ = ['ShadowsocksEditor']
getProxyOutboundServer = functools.partial(
ConfigXray.getProxyOutboundServer,
@@ -214,11 +214,11 @@ class GuiSSGroupBoxProxy(GuiEditorWidgetQGroupBox):
]
class GuiShadowsocks(GuiEditorWidgetQDialog):
class ShadowsocksEditor(GuiEditorWidgetQDialog):
"""Represent GUI shadowsocks."""
def __init__(self, *args, **kwargs):
"""Initialize the GuiShadowsocks."""
"""Initialize the Shadowsocks configuration editor."""
super().__init__(*args, **kwargs)
self.setTabText(Protocol.Shadowsocks.value)
@@ -21,16 +21,16 @@ from __future__ import annotations
from Furious.Frozenlib import *
from Furious.Interface import *
from Furious.Library import *
from Furious.Domain import *
from Furious.Qt import *
from Furious.Qt import gettext as _
from Furious.Plugins.Official.Configuration import *
from Furious.Plugins.Official.Xray.GuiVTransport import *
from Furious.Plugins.Official.Xray.GuiVTLS import *
from Furious.Backends.Configuration import *
from Furious.Backends.Xray.TlsEditor import *
from Furious.Backends.Xray.TransportEditor import *
import functools
__all__ = ['GuiSocks']
__all__ = ['SocksEditor']
getProxyOutboundServer = functools.partial(
ConfigXray.getProxyOutboundServer,
@@ -157,11 +157,11 @@ class GuiSocksGroupBoxProxy(GuiEditorWidgetQGroupBox):
]
class GuiSocks(GuiEditorWidgetQDialog):
class SocksEditor(GuiEditorWidgetQDialog):
"""Represent GUI SOCKS."""
def __init__(self, *args, **kwargs):
"""Initialize the GuiSocks."""
"""Initialize the SOCKS configuration editor."""
super().__init__(*args, **kwargs)
self.setTabText(Protocol.Socks.value)
@@ -20,9 +20,9 @@
from __future__ import annotations
from Furious.Interface import *
from Furious.Library import *
from Furious.Domain import *
from Furious.Qt import *
from Furious.Plugins.Official.Configuration import *
from Furious.Backends.Configuration import *
from PySide6 import QtCore
from PySide6.QtWidgets import *
@@ -738,7 +738,7 @@ class GuiVTLSPageStackedWidget(QStackedWidget):
page.connectActivated(func)
class GuiVTLSQGroupBox(GuiEditorItemFactory, AppQGroupBox):
class GuiVTLSQGroupBox(EditorBinding, AppQGroupBox):
"""Group the GUI vtlsq editor controls."""
def __init__(self, **kwargs):
@@ -20,10 +20,10 @@
from __future__ import annotations
from Furious.Interface import *
from Furious.Library import *
from Furious.Domain import *
from Furious.Qt import *
from Furious.Qt import gettext as _
from Furious.Plugins.Official.Configuration import *
from Furious.Backends.Configuration import *
from PySide6.QtWidgets import *
@@ -1923,7 +1923,7 @@ class GuiVTransportPageStackedWidget(QStackedWidget):
page.connectActivated(func)
class GuiVTransportQGroupBox(GuiEditorItemFactory, AppQGroupBox):
class GuiVTransportQGroupBox(EditorBinding, AppQGroupBox):
"""Group the GUI v transport q editor controls."""
def __init__(self, **kwargs):
@@ -21,16 +21,16 @@ from __future__ import annotations
from Furious.Frozenlib import *
from Furious.Interface import *
from Furious.Library import *
from Furious.Domain import *
from Furious.Qt import *
from Furious.Qt import gettext as _
from Furious.Plugins.Official.Configuration import *
from Furious.Plugins.Official.Xray.GuiVTransport import *
from Furious.Plugins.Official.Xray.GuiVTLS import *
from Furious.Backends.Configuration import *
from Furious.Backends.Xray.TlsEditor import *
from Furious.Backends.Xray.TransportEditor import *
import functools
__all__ = ['GuiTrojan']
__all__ = ['TrojanEditor']
getProxyOutboundServer = functools.partial(
ConfigXray.getProxyOutboundServer,
@@ -158,11 +158,11 @@ class GuiTrojanGroupBoxProxy(GuiEditorWidgetQGroupBox):
]
class GuiTrojan(GuiEditorWidgetQDialog):
class TrojanEditor(GuiEditorWidgetQDialog):
"""Represent GUI trojan."""
def __init__(self, *args, **kwargs):
"""Initialize the GuiTrojan."""
"""Initialize the Trojan configuration editor."""
super().__init__(*args, **kwargs)
self.setTabText(Protocol.Trojan.value)
@@ -34,7 +34,7 @@ import copy
import logging
import functools
__all__ = ['GuiXrayTUNSettings']
__all__ = ['XrayTunSettingsDialog']
logger = logging.getLogger(__name__)
@@ -227,7 +227,7 @@ class GuiXrayTUNSettingsGroupBoxNetwork(GuiEditorWidgetQGroupBox):
]
class GuiXrayTUNSettings(GuiEditorWidgetQDialog):
class XrayTunSettingsDialog(GuiEditorWidgetQDialog):
"""Edit and persist Xray-core native TUN settings."""
def __init__(self, *args, **kwargs):
@@ -21,19 +21,19 @@ from __future__ import annotations
from Furious.Frozenlib import *
from Furious.Interface import *
from Furious.Library import *
from Furious.Domain import *
from Furious.Qt import *
from Furious.Qt import gettext as _
from Furious.Plugins.Official.Configuration import *
from Furious.Plugins.Official.Xray.GuiVTransport import *
from Furious.Plugins.Official.Xray.GuiVTLS import *
from Furious.Backends.Configuration import *
from Furious.Backends.Xray.TlsEditor import *
from Furious.Backends.Xray.TransportEditor import *
from PySide6.QtWidgets import *
import uuid
import functools
__all__ = ['GuiVLESS']
__all__ = ['VlessEditor']
getProxyOutboundServer = functools.partial(
ConfigXray.getProxyOutboundServer,
@@ -357,11 +357,11 @@ class GuiVLESSGroupBoxProxy(GuiEditorWidgetQGroupBox):
]
class GuiVLESS(GuiEditorWidgetQDialog):
class VlessEditor(GuiEditorWidgetQDialog):
"""Represent GUI VLESS."""
def __init__(self, *args, **kwargs):
"""Initialize the GuiVLESS."""
"""Initialize the VLESS configuration editor."""
super().__init__(*args, **kwargs)
self.setTabText(Protocol.VLESS.value)
@@ -21,19 +21,19 @@ from __future__ import annotations
from Furious.Frozenlib import *
from Furious.Interface import *
from Furious.Library import *
from Furious.Domain import *
from Furious.Qt import *
from Furious.Qt import gettext as _
from Furious.Plugins.Official.Configuration import *
from Furious.Plugins.Official.Xray.GuiVTransport import *
from Furious.Plugins.Official.Xray.GuiVTLS import *
from Furious.Backends.Configuration import *
from Furious.Backends.Xray.TlsEditor import *
from Furious.Backends.Xray.TransportEditor import *
from PySide6.QtWidgets import *
import uuid
import functools
__all__ = ['GuiVMess']
__all__ = ['VmessEditor']
getProxyOutboundServer = functools.partial(
ConfigXray.getProxyOutboundServer,
@@ -366,11 +366,11 @@ class GuiVMessGroupBoxProxy(GuiEditorWidgetQGroupBox):
]
class GuiVMess(GuiEditorWidgetQDialog):
class VmessEditor(GuiEditorWidgetQDialog):
"""Represent GUI v mess."""
def __init__(self, *args, **kwargs):
"""Initialize the GuiVMess."""
"""Initialize the VMess configuration editor."""
super().__init__(*args, **kwargs)
self.setTabText(Protocol.VMess.value)
@@ -15,7 +15,7 @@
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <https://www.gnu.org/licenses/>.
"""Register the core plugins maintained with Furious itself."""
"""Expose the official proxy backend implementations bundled with Furious."""
from __future__ import annotations
+1 -1
View File
@@ -208,7 +208,7 @@ class MsgQueue(multiprocessing.queues.Queue):
self.startTimer()
class CoreProcessMonitor(CoreProcessFactory, ABC):
class CoreProcessMonitor(CoreProcess, ABC):
"""Track the state and lifetime of a proxy-core child process."""
StopJoinTimeout = 3
@@ -19,7 +19,7 @@
from __future__ import annotations
from Furious.Interface.UserServersTableItem import UserServersTableItem
from Furious.Interface.Server import ServerTableItem
from typing import Union
@@ -39,7 +39,7 @@ __all__ = [
]
class ConfigFactory(UserServersTableItem, dict):
class ConfigFactory(ServerTableItem, dict):
"""
ConfigurationFactory is how Furious sees the core config.
@@ -36,7 +36,7 @@ __all__ = [
]
class JSONEncoder(EncoderFactory):
class JSONEncoder(Codec):
"""Encode and decode data using JSON."""
@staticmethod
@@ -52,7 +52,7 @@ class JSONEncoder(EncoderFactory):
return json.loads(data, **kwargs)
class UJSONEncoder(EncoderFactory):
class UJSONEncoder(Codec):
"""Encode and decode data using ujson."""
@staticmethod
@@ -74,7 +74,7 @@ class UJSONEncoder(EncoderFactory):
return ujson.loads(data, **kwargs)
class Base64Encoder(EncoderFactory):
class Base64Encoder(Codec):
"""Encode and decode data using base64."""
@staticmethod
@@ -90,7 +90,7 @@ class Base64Encoder(EncoderFactory):
return base64.b64decode(data, validate=validate, **kwargs)
class PyBase64Encoder(EncoderFactory):
class PyBase64Encoder(Codec):
"""Encode and decode data using py base64."""
@staticmethod
@@ -15,7 +15,7 @@
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <https://www.gnu.org/licenses/>.
"""Expose domain configuration, encoding, and persistent storage services."""
"""Expose configuration models, provider registration, and data encoding."""
from __future__ import annotations
@@ -28,8 +28,7 @@ from .Configuration import (
configurationRegistry,
registerConfigurationProvider,
)
from .Encoder import Base64Encoder, JSONEncoder, PyBase64Encoder, UJSONEncoder
from .Storage import Storage
from .Encoding import Base64Encoder, JSONEncoder, PyBase64Encoder, UJSONEncoder
__all__ = [
'Base64Encoder',
@@ -37,7 +36,6 @@ __all__ = [
'ConfigurationRegistry',
'JSONEncoder',
'PyBase64Encoder',
'Storage',
'UJSONEncoder',
'configFactoryBlank',
'configFactoryFromAny',
+356 -356
View File
File diff suppressed because it is too large Load Diff
@@ -21,10 +21,10 @@ from __future__ import annotations
from enum import Enum
__all__ = ['ApplicationFactory']
__all__ = ['ApplicationRunner']
class ApplicationFactory:
class ApplicationRunner:
"""Define the lifecycle contract for the top-level application runner."""
class ExitCode(Enum):
@@ -36,7 +36,7 @@ class ApplicationFactory:
AssertionError = 63
def __init__(self, *args, **kwargs):
"""Initialize the ApplicationFactory."""
"""Initialize the application runner."""
super().__init__(*args, **kwargs)
def run(self):
@@ -22,10 +22,10 @@ from __future__ import annotations
from abc import ABC, abstractmethod
from typing import Any
__all__ = ['EncoderFactory']
__all__ = ['Codec']
class EncoderFactory(ABC):
class Codec(ABC):
"""Define the interface and shared behavior for encoder objects."""
@abstractmethod
@@ -21,14 +21,14 @@ from __future__ import annotations
from typing import Any
__all__ = ['GuiEditorItemFactory', 'GuiEditorItemWidgetContainer']
__all__ = ['EditorBinding', 'EditorWidgetBinding']
class GuiEditorItemFactory:
class EditorBinding:
"""Define the interface and shared behavior for GUI editor item objects."""
def __init__(self, *args, **kwargs):
"""Initialize the GuiEditorItemFactory."""
"""Initialize the editor binding."""
super().__init__(*args, **kwargs)
def inputToFactory(self, *args, **kwargs) -> Any:
@@ -40,11 +40,11 @@ class GuiEditorItemFactory:
raise NotImplementedError
class GuiEditorItemWidgetContainer(GuiEditorItemFactory):
class EditorWidgetBinding(EditorBinding):
"""Bind one or more editor widgets to configuration data."""
def __init__(self, *args, **kwargs):
"""Initialize the GuiEditorItemWidgetContainer."""
"""Initialize the editor widget binding."""
super().__init__(*args, **kwargs)
def widgets(self) -> Any:
@@ -28,10 +28,10 @@ from typing import Callable, Union
import ujson
import functools
__all__ = ['CoreProcessFactory']
__all__ = ['CoreProcess']
class CoreProcessFactory(ABC):
class CoreProcess(ABC):
"""Define the interface and shared behavior for core process objects."""
class ExitCode(Enum):
@@ -45,9 +45,9 @@ class CoreProcessFactory(ABC):
def __init__(
self,
exitCallback: Union[Callable[[CoreProcessFactory, int], None], None] = None,
exitCallback: Union[Callable[[CoreProcess, int], None], None] = None,
):
"""Initialize the CoreProcessFactory."""
"""Initialize the core process."""
super().__init__()
self._exitCallback = exitCallback
@@ -19,14 +19,14 @@
from __future__ import annotations
__all__ = ['UserServersTableItem']
__all__ = ['ServerTableItem']
class UserServersTableItem:
class ServerTableItem:
"""Define the display fields required by a server-table row."""
def __init__(self, *args, **kwargs):
"""Initialize the UserServersTableItem."""
"""Initialize the server table item."""
super().__init__(*args, **kwargs)
@property
@@ -22,10 +22,10 @@ from __future__ import annotations
from abc import ABC, abstractmethod
from typing import Any
__all__ = ['StorageFactory']
__all__ = ['StorageBackend']
class StorageFactory(ABC):
class StorageBackend(ABC):
"""Define the interface and shared behavior for storage objects."""
@abstractmethod
+13 -13
View File
@@ -19,19 +19,19 @@
from __future__ import annotations
from .ApplicationFactory import ApplicationFactory
from .CoreProcessFactory import CoreProcessFactory
from .EncoderFactory import EncoderFactory
from .GuiEditorItemFactory import GuiEditorItemFactory, GuiEditorItemWidgetContainer
from .StorageFactory import StorageFactory
from .UserServersTableItem import UserServersTableItem
from .Application import ApplicationRunner
from .Codec import Codec
from .Editor import EditorBinding, EditorWidgetBinding
from .Process import CoreProcess
from .Server import ServerTableItem
from .Storage import StorageBackend
__all__ = [
'ApplicationFactory',
'CoreProcessFactory',
'EncoderFactory',
'GuiEditorItemFactory',
'GuiEditorItemWidgetContainer',
'StorageFactory',
'UserServersTableItem',
'ApplicationRunner',
'Codec',
'CoreProcess',
'EditorBinding',
'EditorWidgetBinding',
'ServerTableItem',
'StorageBackend',
]
+26 -9
View File
@@ -19,7 +19,7 @@
from __future__ import annotations
from Furious.Library.Configuration import configurationRegistry
from Furious.Domain.Configuration import configurationRegistry
from importlib import metadata
@@ -32,6 +32,7 @@ __all__ = [
'PLUGIN_ENTRY_POINT_GROUP',
'PluginRegistry',
'getPluginRegistry',
'initializePluginRegistry',
'registerPlugin',
]
@@ -160,6 +161,10 @@ class PluginRegistry:
"""Return registered plugins in registration order."""
return tuple(self._plugins.values())
def plugin(self, pluginId: str):
"""Return the plugin registered with ``pluginId``, if any."""
return self._plugins.get(pluginId)
def protocolDescriptors(self):
"""Return contributed protocols in their requested menu order."""
descriptors = list(
@@ -443,29 +448,41 @@ _registryLock = threading.RLock()
_registryInitialized = False
def getPluginRegistry() -> PluginRegistry:
"""Return the process-wide registry after lazy official/external discovery."""
def initializePluginRegistry(pluginTypes=()) -> PluginRegistry:
"""Initialize discovery and register host-provided plugin implementations."""
global _registry, _registryInitialized
with _registryLock:
if not _registryInitialized:
# Importing official plugins is an intentional extension-loading
# boundary. Keeping it here prevents package initialization from
# eagerly importing GUI and core implementations.
from .Official import OFFICIAL_PLUGIN_TYPES
registry = PluginRegistry(configurationRegistry)
for pluginType in OFFICIAL_PLUGIN_TYPES:
for pluginType in pluginTypes:
registry.register(pluginType())
registry.discover()
_registry = registry
_registryInitialized = True
else:
for pluginType in pluginTypes:
plugin = pluginType()
registered = _registry.plugin(plugin.pluginId)
if registered is None:
_registry.register(plugin)
elif not isinstance(registered, pluginType):
raise ValueError(
f'plugin {plugin.pluginId!r} is already registered by '
f'{type(registered).__name__}'
)
return _registry
def getPluginRegistry() -> PluginRegistry:
"""Return the process-wide registry, discovering external plugins lazily."""
return initializePluginRegistry()
def registerPlugin(plugin: FuriousPlugin):
"""Register a plugin programmatically and return it."""
return getPluginRegistry().register(plugin)
+2
View File
@@ -24,6 +24,7 @@ from .Registry import (
PLUGIN_ENTRY_POINT_GROUP,
PluginRegistry,
getPluginRegistry,
initializePluginRegistry,
registerPlugin,
)
@@ -35,5 +36,6 @@ __all__ = [
'PluginRegistry',
'PluginRouting',
'getPluginRegistry',
'initializePluginRegistry',
'registerPlugin',
]
@@ -21,7 +21,7 @@ from __future__ import annotations
from Furious.Frozenlib import *
from Furious.Interface import *
from Furious.Library import ConfigFactory
from Furious.Domain import ConfigFactory
from Furious.Qt.DynamicTranslate import gettext as _
from Furious.Qt.QtWidgets import *
@@ -46,7 +46,7 @@ __all__ = [
]
class GuiEditorItemTextInput(GuiEditorItemWidgetContainer):
class GuiEditorItemTextInput(EditorWidgetBinding):
"""Represent GUI editor item text input."""
def __init__(self, *args, **kwargs):
@@ -76,7 +76,7 @@ class GuiEditorItemTextInput(GuiEditorItemWidgetContainer):
return self._title, self._input
class GuiEditorItemTextSpinBox(GuiEditorItemWidgetContainer):
class GuiEditorItemTextSpinBox(EditorWidgetBinding):
"""Represent GUI editor item text spin box."""
def __init__(self, *args, **kwargs):
@@ -110,7 +110,7 @@ class GuiEditorItemTextSpinBox(GuiEditorItemWidgetContainer):
return self._title, self._input
class GuiEditorItemTextComboBox(GuiEditorItemWidgetContainer):
class GuiEditorItemTextComboBox(EditorWidgetBinding):
"""Represent GUI editor item text combo box."""
def __init__(self, *args, **kwargs):
@@ -148,7 +148,7 @@ class GuiEditorItemTextComboBox(GuiEditorItemWidgetContainer):
return self._title, self._input
class GuiEditorItemTextCheckBox(GuiEditorItemWidgetContainer):
class GuiEditorItemTextCheckBox(EditorWidgetBinding):
"""Represent GUI editor item text check box."""
def __init__(self, *args, **kwargs):
@@ -284,7 +284,7 @@ class GuiEditorItemProxySocks(GuiEditorItemTextInput):
self.setText('')
class GuiEditorWidget(GuiEditorItemFactory):
class GuiEditorWidget(EditorBinding):
"""Provide the GUI editor widget."""
def __init__(self, *args, **kwargs):
@@ -293,7 +293,7 @@ class GuiEditorWidget(GuiEditorItemFactory):
self._containers = self.containerSequence()
def containerSequence(self) -> Sequence[GuiEditorItemWidgetContainer]:
def containerSequence(self) -> Sequence[EditorWidgetBinding]:
"""Return the editor item containers in display order."""
raise NotImplementedError
@@ -374,7 +374,7 @@ class GuiEditorWidgetQGroupBox(GuiEditorWidget, AppQGroupBox):
return layout
class GuiEditorWidgetQDialog(GuiEditorItemFactory, AppQDialog):
class GuiEditorWidgetQDialog(EditorBinding, AppQDialog):
"""Present the GUI editor widget Qt dialog."""
def __init__(self, *args, **kwargs):
+1 -1
View File
@@ -147,7 +147,7 @@ class AppQAction(Mixins.QTranslatable, Mixins.ThemeAware, QAction):
self._menu = menu
# Some old version PySide6 does not have setMenu method
# for QAction. Protect it. Currently only used in SystemTrayIcon
# for QAction. Protect it. Currently only used in TrayIcon
if hasattr(self, 'setMenu') and useSetMenu:
self.setMenu(menu)
+1 -1
View File
@@ -20,7 +20,7 @@
from __future__ import annotations
from Furious.Frozenlib import *
from Furious.Library import *
from Furious.Domain import *
from Furious.Qt.AppStyleSheet import *
from Furious.Qt.DynamicTheme import *
from Furious.Qt.DynamicTranslate import gettext as _
+1 -9
View File
@@ -20,7 +20,6 @@
from __future__ import annotations
from .AppStyleSheet import AppStyleSheet
from .DNSResolver import DNSResolver
from .DynamicTheme import AppHue
from .DynamicTranslate import (
ABBR_TO_LANGUAGE,
@@ -28,7 +27,7 @@ from .DynamicTranslate import (
SUPPORTED_LANGUAGE,
gettext,
)
from .GuiEditorXXX import (
from .EditorWidgets import (
GuiEditorItemBasicRemark,
GuiEditorItemProxyHttp,
GuiEditorItemProxySocks,
@@ -40,7 +39,6 @@ from .GuiEditorXXX import (
GuiEditorWidgetQGroupBox,
GuiEditorWidgetQWidget,
)
from .NetworkConnectivityManager import NetworkConnectivityManager
from .QtGui import (
AppQAction,
AppQActionGroup,
@@ -94,8 +92,6 @@ from .TextEditorTheme import (
DraculaLoggerSyntaxHighlighter,
configureEditorLogMetadata,
)
from .UpdatesManager import UpdatesManager
from .WaitingSpinner import WaitingSpinner
from .WebGETManager import WebGETManager
__all__ = [
@@ -128,7 +124,6 @@ __all__ = [
'AppQTextBrowser',
'AppQToolBar',
'AppStyleSheet',
'DNSResolver',
'DraculaEditorTheme',
'DraculaJSONSyntaxHighlighter',
'DraculaJSONTextEditor',
@@ -150,10 +145,7 @@ __all__ = [
'MBoxNewChangesNextTime',
'MBoxQuestionDelete',
'MBoxUnrecognizedConfig',
'NetworkConnectivityManager',
'SUPPORTED_LANGUAGE',
'UpdatesManager',
'WaitingSpinner',
'WebGETManager',
'bootstrapIcon',
'bootstrapIconMask',
@@ -21,14 +21,14 @@ from __future__ import annotations
from Furious.Frozenlib import *
from Furious.Interface import *
from Furious.Library.Encoder import *
from Furious.Domain.Encoding import *
__all__ = ['UserRoutings']
registerAppSettings('CustomRouting')
class UserRoutings(Mixins.CleanupOnExit, StorageFactory):
class UserRoutings(Mixins.CleanupOnExit, StorageBackend):
"""Manage the persisted custom-routing collection."""
def __init__(self, *args, **kwargs):
@@ -21,8 +21,8 @@ from __future__ import annotations
from Furious.Frozenlib import *
from Furious.Interface import *
from Furious.Library.Configuration import ConfigFactory, configFactoryFromAny
from Furious.Library.Encoder import *
from Furious.Domain.Configuration import ConfigFactory, configFactoryFromAny
from Furious.Domain.Encoding import *
__all__ = ['UserServers']
@@ -37,7 +37,7 @@ class UserServer:
subsId: str
class UserServers(Mixins.CleanupOnExit, StorageFactory):
class UserServers(Mixins.CleanupOnExit, StorageBackend):
# remark, config, subsId. (subsId corresponds to unique in user subscription)
"""Manage the persisted list of server configurations."""
@@ -21,10 +21,10 @@ from __future__ import annotations
from Furious.Frozenlib import *
from Furious.Interface import *
from Furious.Library.UserServers import UserServers
from Furious.Library.UserSubs import UserSubs
from Furious.Library.UserRoutings import UserRoutings
from Furious.Library.UserTUNSettings import UserTUNSettings
from Furious.Repository.Routings import UserRoutings
from Furious.Repository.Servers import UserServers
from Furious.Repository.Subscriptions import UserSubs
from Furious.Repository.TunSettings import UserTUNSettings
from typing import Union
@@ -21,7 +21,7 @@ from __future__ import annotations
from Furious.Frozenlib import *
from Furious.Interface import *
from Furious.Library.Encoder import *
from Furious.Domain.Encoding import *
__all__ = ['UserSubs']
@@ -43,7 +43,7 @@ class UserSub:
unique: dict[str, dict]
class UserSubs(Mixins.CleanupOnExit, StorageFactory):
class UserSubs(Mixins.CleanupOnExit, StorageBackend):
# unique: { remark, webURL }
"""Manage the persisted subscription collection."""
@@ -21,14 +21,14 @@ from __future__ import annotations
from Furious.Frozenlib import *
from Furious.Interface import *
from Furious.Library.Encoder import *
from Furious.Domain.Encoding import *
__all__ = ['UserTUNSettings']
registerAppSettings('CustomTUNSettings')
class UserTUNSettings(Mixins.CleanupOnExit, StorageFactory):
class UserTUNSettings(Mixins.CleanupOnExit, StorageBackend):
"""Manage persisted TUN customization values."""
def __init__(self, *args, **kwargs):
+24
View File
@@ -0,0 +1,24 @@
# Copyright (C) 2024present Loren Eteval & contributors <loren.eteval@proton.me>
#
# This file is part of Furious.
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <https://www.gnu.org/licenses/>.
"""Expose persisted user-state repositories through the storage facade."""
from __future__ import annotations
from .Storage import Storage
__all__ = ['Storage']
@@ -21,9 +21,11 @@ from __future__ import annotations
from Furious.Frozenlib import *
from Furious.Interface import *
from Furious.Library import *
from Furious.Domain import *
from Furious.Repository import *
from Furious.Plugins import *
from Furious.Qt import *
from Furious.Service.DnsResolver import DnsResolver
from Furious.Core.CoreProcessWorker import *
from Furious.Core.Tun2socks import *
@@ -34,7 +36,7 @@ import logging
import tempfile
import functools
__all__ = ['CoreManager']
__all__ = ['ConnectionManager']
logger = logging.getLogger(__name__)
@@ -67,11 +69,11 @@ def getUserTUNSettings(*args, **kwargs):
)
class CoreManager(Mixins.CleanupOnExit):
class ConnectionManager(Mixins.CleanupOnExit):
"""Coordinate proxy cores, TUN setup, DNS changes, and routing cleanup."""
def __init__(self, *args, **kwargs):
"""Initialize the CoreManager."""
"""Initialize the connection manager."""
super().__init__(*args, **kwargs)
self.uniqueCleanup = False
@@ -317,9 +319,9 @@ class CoreManager(Mixins.CleanupOnExit):
address = configcopy.itemAddress
if not isValidIPAddress(address):
DNSResolver.configureHttpProxy(configcopy.httpProxy())
DnsResolver.configureHttpProxy(configcopy.httpProxy())
error, resolved = DNSResolver.resolve(address)
error, resolved = DnsResolver.resolve(address)
if error:
SystemRoutingTable.Relations.clear()
@@ -543,7 +545,7 @@ class CoreManager(Mixins.CleanupOnExit):
"""Stop every managed proxy and TUN process."""
try:
for process in list(self.processesPool):
if not isinstance(process, CoreProcessFactory):
if not isinstance(process, CoreProcess):
continue
try:
@@ -28,25 +28,25 @@ from PySide6.QtNetwork import *
import logging
import functools
__all__ = ['NetworkConnectivityManager']
__all__ = ['ConnectivityManager']
logger = logging.getLogger(__name__)
class NetworkConnectivityManager(Mixins.ConnectionAware, WebGETManager):
class ConnectivityManager(Mixins.ConnectionAware, WebGETManager):
"""Coordinate network connectivity operations."""
MIN_JOB_INTERVAL = 2500
MAX_JOB_INTERVAL = 2000000000
def __init__(self, parent=None, **kwargs):
"""Initialize the NetworkConnectivityManager."""
"""Initialize the connectivity manager."""
actionMessage = kwargs.pop('actionMessage', 'test network connectivity')
super().__init__(parent, actionMessage=actionMessage)
self.jobStatus = False
self.jobInterval = NetworkConnectivityManager.MIN_JOB_INTERVAL
self.jobInterval = ConnectivityManager.MIN_JOB_INTERVAL
self.jobTimeoutTimer = QtCore.QTimer()
self.jobArrangeTimer = QtCore.QTimer()
@@ -60,13 +60,13 @@ class NetworkConnectivityManager(Mixins.ConnectionAware, WebGETManager):
if self.jobStatus is jobStatus:
self.jobInterval *= 2
else:
self.jobInterval = NetworkConnectivityManager.MIN_JOB_INTERVAL
self.jobInterval = ConnectivityManager.MIN_JOB_INTERVAL
self.jobStatus = jobStatus
if self.jobInterval >= NetworkConnectivityManager.MAX_JOB_INTERVAL:
if self.jobInterval >= ConnectivityManager.MAX_JOB_INTERVAL:
# Limited
self.jobInterval = NetworkConnectivityManager.MAX_JOB_INTERVAL
self.jobInterval = ConnectivityManager.MAX_JOB_INTERVAL
return self.jobInterval
@@ -98,7 +98,7 @@ class NetworkConnectivityManager(Mixins.ConnectionAware, WebGETManager):
_networkReply.abort()
self.jobTimeoutTimer.timeout.connect(functools.partial(abort, networkReply))
self.jobTimeoutTimer.start(NetworkConnectivityManager.MIN_JOB_INTERVAL - 500)
self.jobTimeoutTimer.start(ConnectivityManager.MIN_JOB_INTERVAL - 500)
def stopTest(self):
"""Stop test."""
@@ -113,7 +113,7 @@ class NetworkConnectivityManager(Mixins.ConnectionAware, WebGETManager):
self.stopTest()
else:
self.jobInterval = NetworkConnectivityManager.MIN_JOB_INTERVAL
self.jobInterval = ConnectivityManager.MIN_JOB_INTERVAL
self.jobArrangeTimer.start(self.jobInterval)
@@ -20,7 +20,7 @@
from __future__ import annotations
from Furious.Frozenlib import *
from Furious.Library import *
from Furious.Domain import *
from Furious.Qt.WebGETManager import *
from PySide6 import QtCore
@@ -30,16 +30,16 @@ from typing import Tuple
import logging
__all__ = ['DNSResolver']
__all__ = ['DnsResolver']
logger = logging.getLogger(__name__)
class _DNSResolver(WebGETManager):
class _DnsResolver(WebGETManager):
"""Represent DNS resolver."""
def __init__(self, parent=None, **kwargs):
"""Initialize the _DNSResolver."""
"""Initialize the DNS resolver."""
actionMessage = kwargs.pop('actionMessage', 'DNS resolution')
super().__init__(parent, actionMessage=actionMessage)
@@ -161,4 +161,4 @@ class _DNSResolver(WebGETManager):
networkReply.abort()
DNSResolver = _DNSResolver()
DnsResolver = _DnsResolver()
@@ -20,7 +20,7 @@
from __future__ import annotations
from Furious.Frozenlib import *
from Furious.Library import *
from Furious.Domain import *
from Furious.Qt.QtWidgets import *
from Furious.Qt.DynamicTranslate import gettext as _
from Furious.Qt.WebGETManager import *
@@ -33,7 +33,7 @@ from typing import Callable
import logging
import functools
__all__ = ['UpdatesManager']
__all__ = ['UpdateManager']
logger = logging.getLogger(__name__)
@@ -66,7 +66,7 @@ class MBoxQuestionUpdate(AppQMessageBox):
self.moveToCenter()
class UpdatesManager(WebGETManager):
class UpdateManager(WebGETManager):
"""Coordinate updates operations."""
API_URL = (
@@ -75,7 +75,7 @@ class UpdatesManager(WebGETManager):
)
def __init__(self, parent=None, **kwargs):
"""Initialize the UpdatesManager."""
"""Initialize the update manager."""
actionMessage = kwargs.pop('actionMessage', 'check for updates')
super().__init__(parent, actionMessage=actionMessage)
+10 -2
View File
@@ -19,6 +19,14 @@
from __future__ import annotations
from .CoreManager import CoreManager
from .ConnectionManager import ConnectionManager
from .ConnectivityManager import ConnectivityManager
from .DnsResolver import DnsResolver
from .UpdateManager import UpdateManager
__all__ = ['CoreManager']
__all__ = [
'ConnectionManager',
'ConnectivityManager',
'DnsResolver',
'UpdateManager',
]
+3 -3
View File
@@ -47,7 +47,7 @@ else:
class AppMainProcess(ProcessContext.Process):
"""Represent app main process."""
def __init__(self, func: Callable[[], ApplicationFactory], **kwargs):
def __init__(self, func: Callable[[], ApplicationRunner], **kwargs):
"""Initialize the AppMainProcess."""
super().__init__(**kwargs)
@@ -64,9 +64,9 @@ class AppMainProcess(ProcessContext.Process):
traceback.print_exception(exceptionType, exceptionValue, tb)
if isinstance(exceptionValue, AssertionError):
exitcode = ApplicationFactory.ExitCode.AssertionError.value
exitcode = ApplicationRunner.ExitCode.AssertionError.value
else:
exitcode = ApplicationFactory.ExitCode.UnknownException.value
exitcode = ApplicationRunner.ExitCode.UnknownException.value
logger.error(f'stopped with exitcode {exitcode}')
@@ -26,14 +26,14 @@ from Furious.Qt import gettext as _
from PySide6 import QtCore
from PySide6.QtWidgets import *
__all__ = ['ConnectProgressBar']
__all__ = ['ConnectionProgressWidget']
class AutoUpdateProgressBar(Mixins.ConnectionAware, QProgressBar):
class ConnectionProgressBar(Mixins.ConnectionAware, QProgressBar):
"""Represent auto update progress bar."""
def __init__(self, **kwargs):
"""Initialize the AutoUpdateProgressBar."""
"""Initialize the connection progress bar."""
super().__init__(**kwargs)
self.setRange(0, 100)
@@ -97,11 +97,11 @@ class AutoUpdateProgressBar(Mixins.ConnectionAware, QProgressBar):
self._setConnectionState('connected')
class ConnectProgressBar(Mixins.QTranslatable, Mixins.ConnectionAware, QWidget):
class ConnectionProgressWidget(Mixins.QTranslatable, Mixins.ConnectionAware, QWidget):
"""Provide the connect progress bar widget."""
def __init__(self, parent=None):
"""Initialize the ConnectProgressBar."""
"""Initialize the connection progress widget."""
super().__init__(parent)
self.setWindowTitle(_(APPLICATION_NAME))
@@ -109,7 +109,7 @@ class ConnectProgressBar(Mixins.QTranslatable, Mixins.ConnectionAware, QWidget):
self.setFixedSize(280, 61)
# Create a progress bar widget
self._widget = AutoUpdateProgressBar(parent=self)
self._widget = ConnectionProgressBar(parent=self)
self._widget.setRange(0, 100)
self._layout = QVBoxLayout()
@@ -21,15 +21,13 @@ from __future__ import annotations
from Furious.Frozenlib import *
from Furious.Interface import *
from Furious.Library import *
from Furious.Domain import *
from Furious.Repository import *
from Furious.Plugins import getPluginRegistry
from Furious.Qt import *
from Furious.Qt import gettext as _
from Furious.Service import CoreManager
from Furious.TrayActions.Import import *
from Furious.Qt.WaitingSpinner import *
from Furious.Widget.QRCodeWindow import *
from Furious.Widget.TextEditorWindow import *
from Furious.Service import ConnectionManager
from Furious.Widget.WaitingSpinner import *
from PySide6 import QtCore
from PySide6.QtGui import *
@@ -44,7 +42,7 @@ import icmplib
import functools
import collections
__all__ = ['UserServersQTableView']
__all__ = ['ServerTableView']
logger = logging.getLogger(__name__)
@@ -160,7 +158,7 @@ class SubscriptionManager(WebGETManager):
parent = self.parent()
if isinstance(parent, UserServersQTableView):
if isinstance(parent, ServerTableView):
isConnected = APP().isSystemTrayConnected()
subsIndexes = list(
@@ -471,7 +469,7 @@ class TestDownloadSpeedWorker(WebGETManager):
self.hasDataCounter = 0
self.coreManager = CoreManager()
self.coreManager = ConnectionManager()
self.networkReply = None
self.elapsedTimer = QtCore.QElapsedTimer()
@@ -514,13 +512,13 @@ class TestDownloadSpeedWorker(WebGETManager):
def coreExitCallback(self, config: ConfigFactory, exitcode: int):
"""Handle the core exit callback."""
try:
if exitcode == CoreProcessFactory.ExitCode.ConfigurationError.value:
if exitcode == CoreProcess.ExitCode.ConfigurationError.value:
self.factory.setExtras('speedResult', f'Invalid')
self.sync()
elif exitcode == CoreProcessFactory.ExitCode.ServerStartFailure.value:
elif exitcode == CoreProcess.ExitCode.ServerStartFailure.value:
self.factory.setExtras('speedResult', f'Core start failed')
self.sync()
elif exitcode == CoreProcessFactory.ExitCode.SystemShuttingDown.value:
elif exitcode == CoreProcess.ExitCode.SystemShuttingDown.value:
pass
else:
self.factory.setExtras('speedResult', f'Core exited {exitcode}')
@@ -1026,27 +1024,27 @@ class DeleteServersProgressDialog(AppQDialog):
self.updateStatus()
class UserServersQTableViewHorizontalHeader(AppQHeaderView):
class ServerTableHorizontalHeader(AppQHeaderView):
"""Provide the user servers Qt table view horizontal table header."""
def __init__(self, *args, **kwargs):
"""Initialize the UserServersQTableViewHorizontalHeader."""
"""Initialize the ServerTableHorizontalHeader."""
super().__init__(QtCore.Qt.Orientation.Horizontal, *args, **kwargs)
class UserServersQTableViewVerticalHeader(AppQHeaderView):
class ServerTableVerticalHeader(AppQHeaderView):
"""Provide the user servers Qt table view vertical table header."""
def __init__(self, *args, **kwargs):
"""Initialize the UserServersQTableViewVerticalHeader."""
"""Initialize the ServerTableVerticalHeader."""
super().__init__(QtCore.Qt.Orientation.Vertical, *args, **kwargs)
class UserServersQTableViewHeaders:
class ServerTableColumn:
"""Describe and render user servers Qt table view table columns."""
def __init__(self, name: str, func: Callable[[ConfigFactory], str] = None):
"""Initialize the UserServersQTableViewHeaders."""
"""Initialize the ServerTableColumn."""
self.name = name
self.func = func
@@ -1078,7 +1076,7 @@ class UserServersTableModel(QtCore.QAbstractTableModel):
SortRole = QtCore.Qt.ItemDataRole.UserRole + 1
def __init__(self, headers: list[UserServersQTableViewHeaders], parent=None):
def __init__(self, headers: list[ServerTableColumn], parent=None):
"""Initialize the UserServersTableModel."""
super().__init__(parent)
@@ -1395,7 +1393,7 @@ _TRANSLATABLE_HEADERS = [
]
class UserServersQTableView(
class ServerTableView(
Mixins.QTranslatable,
Mixins.CleanupOnExit,
AppQTableView,
@@ -1405,19 +1403,23 @@ class UserServersQTableView(
RowHeight = 42
Headers = [
UserServersQTableViewHeaders('Remark'),
UserServersQTableViewHeaders('Protocol'),
UserServersQTableViewHeaders('Address'),
UserServersQTableViewHeaders('Port'),
UserServersQTableViewHeaders('Transport'),
UserServersQTableViewHeaders('TLS'),
UserServersQTableViewHeaders('Subscription', _subscriptionRemark),
UserServersQTableViewHeaders('Latency'),
UserServersQTableViewHeaders('Speed'),
ServerTableColumn('Remark'),
ServerTableColumn('Protocol'),
ServerTableColumn('Address'),
ServerTableColumn('Port'),
ServerTableColumn('Transport'),
ServerTableColumn('TLS'),
ServerTableColumn('Subscription', _subscriptionRemark),
ServerTableColumn('Latency'),
ServerTableColumn('Speed'),
]
def __init__(self, *args, **kwargs):
"""Initialize the UserServersQTableView."""
"""Initialize the server table view."""
configurationEditorFactory = kwargs.pop('configurationEditorFactory')
self.qrCodeWindowFactory = kwargs.pop('qrCodeWindowFactory')
importActionsFactory = kwargs.pop('importActionsFactory')
super().__init__(*args, **kwargs)
self.sourceModel = UserServersTableModel(self.Headers, parent=self)
@@ -1438,18 +1440,17 @@ class UserServersQTableView(
parent=self,
)
# Text Editor Window
self.textEditorWindow = TextEditorWindow(parent=self.parent())
self.configurationEditor = configurationEditorFactory()
# Install custom header
self.setHorizontalHeader(
UserServersQTableViewHorizontalHeader(
ServerTableHorizontalHeader(
parent=self,
legacySectionSizeSettingsName='ServerWidgetSectionSizeTable',
sectionSizeSettingsName='UserServersHeaderViewState',
)
)
self.setVerticalHeader(UserServersQTableViewVerticalHeader(self))
self.setVerticalHeader(ServerTableVerticalHeader(self))
self.setDefaultRowHeight(self.RowHeight)
self.horizontalHeader().setCustomSectionResizeMode()
@@ -1625,21 +1626,7 @@ class UserServersQTableView(
QtCore.Qt.Key.Key_N,
),
),
ImportFromFileAction(),
ImportURIFromClipboardAction(
shortcut=QtCore.QKeyCombination(
QtCore.Qt.KeyboardModifier.ControlModifier,
QtCore.Qt.Key.Key_V,
),
),
ImportJSONFromClipboardAction(
shortcut=QtCore.QKeyCombination(
QtCore.Qt.KeyboardModifier.ControlModifier
| QtCore.Qt.KeyboardModifier.ShiftModifier,
QtCore.Qt.Key.Key_J,
),
),
ImportQRCodeOnTheScreenAction(),
*importActionsFactory(),
AppQSeperator(),
AppQAction(
_('Export Share Link To Clipboard'),
@@ -2287,13 +2274,13 @@ class UserServersQTableView(
index = indexes[0]
title = f'{index + 1} - ' + Storage.UserServers()[index].getExtras('remark')
self.textEditorWindow.currentIndex = index
self.textEditorWindow.customWindowTitle = title
self.textEditorWindow.setWindowTitle(title)
self.textEditorWindow.setPlainText(
self.configurationEditor.currentIndex = index
self.configurationEditor.customWindowTitle = title
self.configurationEditor.setWindowTitle(title)
self.configurationEditor.setPlainText(
Storage.UserServers()[index].toJSONString(), True
)
self.textEditorWindow.show()
self.configurationEditor.show()
def activateSelectedServer(self):
"""Activate selected server."""
@@ -2586,7 +2573,7 @@ class UserServersQTableView(
# Nothing selected. Do nothing
return
window = QRCodeWindow()
window = self.qrCodeWindowFactory()
window.initTabByIndex(indexes)
if window.tabCount() > 0:
@@ -2609,11 +2596,11 @@ class UserServersQTableView(
def showTabAndSpaces(self):
"""Show tab and spaces."""
self.textEditorWindow.showTabAndSpaces()
self.configurationEditor.showTabAndSpaces()
def hideTabAndSpaces(self):
"""Hide tab and spaces."""
self.textEditorWindow.hideTabAndSpaces()
self.configurationEditor.hideTabAndSpaces()
def keyPressEvent(self, event):
"""Handle a key press for the user servers Qt table view."""
@@ -20,7 +20,7 @@
from __future__ import annotations
from Furious.Frozenlib import *
from Furious.Library import *
from Furious.Repository import *
from Furious.Qt import *
from Furious.Qt import gettext as _
@@ -35,34 +35,34 @@ import functools
logger = logging.getLogger(__name__)
__all__ = ['UserSubsQTableView']
__all__ = ['SubscriptionTableView']
# Migrate legacy settings
registerAppSettings('SubscriptionWidgetSectionSizeTable')
registerAppSettings('UserSubsHeaderViewState')
class UserSubsQTableViewHorizontalHeader(AppQHeaderView):
class SubscriptionTableHorizontalHeader(AppQHeaderView):
"""Provide the user subs Qt table view horizontal table header."""
def __init__(self, *args, **kwargs):
"""Initialize the UserSubsQTableViewHorizontalHeader."""
"""Initialize the SubscriptionTableHorizontalHeader."""
super().__init__(QtCore.Qt.Orientation.Horizontal, *args, **kwargs)
class UserSubsQTableViewVerticalHeader(AppQHeaderView):
class SubscriptionTableVerticalHeader(AppQHeaderView):
"""Provide the user subs Qt table view vertical table header."""
def __init__(self, *args, **kwargs):
"""Initialize the UserSubsQTableViewVerticalHeader."""
"""Initialize the SubscriptionTableVerticalHeader."""
super().__init__(QtCore.Qt.Orientation.Vertical, *args, **kwargs)
class UserSubsQTableViewHeaders:
class SubscriptionTableColumn:
"""Describe and render user subs Qt table view table columns."""
def __init__(self, name: str, func: Callable[[dict], str] = None):
"""Initialize the UserSubsQTableViewHeaders."""
"""Initialize the SubscriptionTableColumn."""
self.name = name
self.func = func
@@ -101,7 +101,7 @@ class UserSubsTableModel(QtCore.QAbstractTableModel):
def __init__(
self,
headers: list[UserSubsQTableViewHeaders],
headers: list[SubscriptionTableColumn],
itemKey: list[str],
parent=None,
):
@@ -271,7 +271,7 @@ _TRANSLATABLE_HEADERS = [
]
class UserSubsQTableView(Mixins.QTranslatable, AppQTableView):
class SubscriptionTableView(Mixins.QTranslatable, AppQTableView):
"""Represent user subs Qt table view."""
RowHeight = 42
@@ -302,12 +302,10 @@ class UserSubsQTableView(Mixins.QTranslatable, AppQTableView):
}
Headers = [
UserSubsQTableViewHeaders('Remark', lambda item: item.get('remark', '')),
UserSubsQTableViewHeaders('URL', lambda item: item.get('webURL', '')),
UserSubsQTableViewHeaders(
'Auto Update', lambda item: item.get('autoupdate', '')
),
UserSubsQTableViewHeaders(
SubscriptionTableColumn('Remark', lambda item: item.get('remark', '')),
SubscriptionTableColumn('URL', lambda item: item.get('webURL', '')),
SubscriptionTableColumn('Auto Update', lambda item: item.get('autoupdate', '')),
SubscriptionTableColumn(
'Auto Update Use Proxy', lambda item: item.get('proxy', '')
),
]
@@ -316,7 +314,7 @@ class UserSubsQTableView(Mixins.QTranslatable, AppQTableView):
ItemKey = ['remark', 'webURL', 'autoupdate', 'proxy']
def __init__(self, *args, **kwargs):
"""Initialize the UserSubsQTableView."""
"""Initialize the subscription table view."""
self.deleteUniqueCallback = kwargs.pop('deleteUniqueCallback', None)
super().__init__(*args, **kwargs)
@@ -328,13 +326,13 @@ class UserSubsQTableView(Mixins.QTranslatable, AppQTableView):
# Install custom header
self.setHorizontalHeader(
UserSubsQTableViewHorizontalHeader(
SubscriptionTableHorizontalHeader(
parent=self,
legacySectionSizeSettingsName='SubscriptionWidgetSectionSizeTable',
sectionSizeSettingsName='UserSubsHeaderViewState',
)
)
self.setVerticalHeader(UserSubsQTableViewVerticalHeader(self))
self.setVerticalHeader(SubscriptionTableVerticalHeader(self))
self.setDefaultRowHeight(self.RowHeight)
self.horizontalHeader().setCustomSectionResizeMode()
+9 -17
View File
@@ -15,26 +15,18 @@
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <https://www.gnu.org/licenses/>.
"""Expose reusable application widgets and configuration editors."""
"""Expose reusable visual components that do not own application windows."""
from __future__ import annotations
from .GuiCustomizeNetworkTest import GuiCustomizeNetworkTestDialog
from .GuiCustomizeProxyBypass import GuiCustomizeProxyBypassDialog
from .GuiTUNSettings import GuiTUNSettings
from .IndentSpinBox import IndentSpinBox
from .QRCodeWindow import QRCodeWindow
from .TextEditorWindow import TextEditorWindow
from .UserServersQTableView import UserServersQTableView
from .UserSubsQTableView import UserSubsQTableView
from .ConnectionProgressWidget import ConnectionProgressWidget
from .ServerTableView import ServerTableView
from .SubscriptionTableView import SubscriptionTableView
from .WaitingSpinner import WaitingSpinner
__all__ = [
'GuiCustomizeNetworkTestDialog',
'GuiCustomizeProxyBypassDialog',
'GuiTUNSettings',
'IndentSpinBox',
'QRCodeWindow',
'TextEditorWindow',
'UserServersQTableView',
'UserSubsQTableView',
'ConnectionProgressWidget',
'ServerTableView',
'SubscriptionTableView',
'WaitingSpinner',
]
@@ -25,14 +25,14 @@ from Furious.Qt import gettext as _
from PySide6 import QtCore
from PySide6.QtWidgets import *
__all__ = ['IndentSpinBox']
__all__ = ['IndentDialog']
class IndentSpinBox(AppQDialog):
class IndentDialog(AppQDialog):
"""Represent indent spin box."""
def __init__(self, parent=None):
"""Initialize the IndentSpinBox."""
"""Initialize the indentation dialog."""
super().__init__(parent)
self.setWindowTitle(_('Set Indent'))
@@ -25,7 +25,7 @@ from Furious.Qt import gettext as _
from PySide6 import QtCore
from PySide6.QtWidgets import *
__all__ = ['LogViewerWindow']
__all__ = ['LogWindow']
class MBoxSaveError(AppQMessageBox):
@@ -76,11 +76,11 @@ def saveAsFile(content: str):
mbox.open()
class LogViewerWindow(AppQMainWindow):
class LogWindow(AppQMainWindow):
"""Present the log viewer window."""
def __init__(self, *args, **kwargs):
"""Initialize the LogViewerWindow."""
"""Initialize the log window."""
tabTitle = kwargs.pop('tabTitle', '')
fontFamily = kwargs.pop('fontFamily', '')
pointSizeSettingsName = kwargs.pop('pointSizeSettingsName', '')
@@ -21,15 +21,25 @@ from __future__ import annotations
from Furious.Frozenlib import *
from Furious.Interface import *
from Furious.Library import *
from Furious.Domain import *
from Furious.Repository import *
from Furious.Plugins import getPluginRegistry
from Furious.Qt import *
from Furious.Qt import gettext as _
from Furious.Widget.UserServersQTableView import *
from Furious.Widget.GuiCustomizeNetworkTest import *
from Furious.Widget.GuiCustomizeProxyBypass import *
from Furious.Widget.GuiTUNSettings import *
from Furious.Window.UserSubsWindow import *
from Furious.Service import ConnectivityManager, UpdateManager
from Furious.Actions.Import import (
ImportFromFileAction,
ImportJSONFromClipboardAction,
ImportQRCodeOnTheScreenAction,
ImportURIFromClipboardAction,
)
from Furious.Widget.ServerTableView import *
from Furious.Window.NetworkTestDialog import *
from Furious.Window.ProxyBypassDialog import *
from Furious.Window.QRCodeWindow import QRCodeWindow
from Furious.Window.SubscriptionWindow import *
from Furious.Window.TextEditorWindow import TextEditorWindow
from Furious.Window.TunSettingsDialog import *
from PySide6 import QtCore
from PySide6.QtGui import *
@@ -41,7 +51,7 @@ import os
import logging
import functools
__all__ = ['AppMainWindow']
__all__ = ['MainWindow']
logger = logging.getLogger(__name__)
@@ -66,18 +76,18 @@ registerAppSettings('AppMainWindowGeometry')
registerAppSettings('AppMainWindowState')
class AppNetworkConnectivityManager(NetworkConnectivityManager):
class AppConnectivityManager(ConnectivityManager):
"""Coordinate app network connectivity operations."""
def __init__(self, parent=None):
"""Initialize the AppNetworkConnectivityManager."""
"""Initialize the application connectivity manager."""
super().__init__(parent)
def successCallback(self, networkReply, **kwargs):
"""Handle a successful network operation."""
parent = self.parent()
if isinstance(parent, AppMainWindow):
if isinstance(parent, MainWindow):
parent.setNetworkState(True)
super().successCallback(networkReply, **kwargs)
@@ -86,7 +96,7 @@ class AppNetworkConnectivityManager(NetworkConnectivityManager):
"""Handle a failed network operation."""
parent = self.parent()
if isinstance(parent, AppMainWindow):
if isinstance(parent, MainWindow):
parent.setNetworkState(False, errorString=networkReply.errorString())
super().failureCallback(networkReply, **kwargs)
@@ -96,7 +106,7 @@ class AppNetworkConnectivityManager(NetworkConnectivityManager):
if not APP().isSystemTrayConnected():
parent = self.parent()
if isinstance(parent, AppMainWindow):
if isinstance(parent, MainWindow):
parent.resetNetworkState()
self.stopTest()
@@ -106,7 +116,7 @@ class AppNetworkConnectivityManager(NetworkConnectivityManager):
if connectedHttpProxy is None:
parent = self.parent()
if isinstance(parent, AppMainWindow):
if isinstance(parent, MainWindow):
parent.resetNetworkState()
else:
self.configureHttpProxy(connectedHttpProxy)
@@ -117,7 +127,7 @@ class AppNetworkConnectivityManager(NetworkConnectivityManager):
"""Update the app network connectivity manager for a disconnected state."""
parent = self.parent()
if isinstance(parent, AppMainWindow):
if isinstance(parent, MainWindow):
parent.resetNetworkState()
super().disconnectedCallback()
@@ -263,7 +273,7 @@ class SearchButton(AppQPushButton):
self.setText(self.customText())
class AppMainWindow(AppQMainWindow):
class MainWindow(AppQMainWindow):
"""Present the app main window."""
DEFAULT_WINDOW_SIZE_DARWIN = QtCore.QSize(1500, 780)
@@ -271,8 +281,29 @@ class AppMainWindow(AppQMainWindow):
QtCore.QSize(1800, 960) if PLATFORM != 'Darwin' else DEFAULT_WINDOW_SIZE_DARWIN
)
@staticmethod
def serverImportActions():
"""Build import actions contributed to the server table context menu."""
return (
ImportFromFileAction(),
ImportURIFromClipboardAction(
shortcut=QtCore.QKeyCombination(
QtCore.Qt.KeyboardModifier.ControlModifier,
QtCore.Qt.Key.Key_V,
),
),
ImportJSONFromClipboardAction(
shortcut=QtCore.QKeyCombination(
QtCore.Qt.KeyboardModifier.ControlModifier
| QtCore.Qt.KeyboardModifier.ShiftModifier,
QtCore.Qt.Key.Key_J,
),
),
ImportQRCodeOnTheScreenAction(),
)
def __init__(self, *args, **kwargs):
"""Initialize the AppMainWindow."""
"""Initialize the main window."""
super().__init__(*args, **kwargs)
if SystemRuntime.isAdmin():
@@ -283,11 +314,16 @@ class AppMainWindow(AppQMainWindow):
# TODO: Need this?
# self.setAttribute(QtCore.Qt.WidgetAttribute.WA_ShowWithoutActivating)
self.updatesManager = UpdatesManager(parent=self)
self.networkConnectivityManager = AppNetworkConnectivityManager(parent=self)
self.updatesManager = UpdateManager(parent=self)
self.networkConnectivityManager = AppConnectivityManager(parent=self)
self.userServersQTableWidget = UserServersQTableView(parent=self)
self.userSubsWindow = UserSubsWindow(
self.userServersQTableWidget = ServerTableView(
parent=self,
configurationEditorFactory=lambda: TextEditorWindow(parent=self),
qrCodeWindowFactory=QRCodeWindow,
importActionsFactory=self.serverImportActions,
)
self.userSubsWindow = SubscriptionWindow(
parent=self,
deleteUniqueCallback=lambda unique: self.userServersQTableWidget.deleteItemByIndex(
list(
@@ -299,8 +335,8 @@ class AppMainWindow(AppQMainWindow):
),
)
pluginRegistry = getPluginRegistry()
self.customizeProxyBypassDialog = GuiCustomizeProxyBypassDialog(parent=self)
self.customizeNetworkTestDialog = GuiCustomizeNetworkTestDialog(parent=self)
self.customizeProxyBypassDialog = ProxyBypassDialog(parent=self)
self.customizeNetworkTestDialog = NetworkTestDialog(parent=self)
self.mainTab = AppQTabWidget()
self.mainTab.addTab(self.userServersQTableWidget, _('Server'))
@@ -702,7 +738,7 @@ class AppMainWindow(AppQMainWindow):
"""Return the cached GUI TUN settings value used by the app main window."""
parent = kwargs.pop('parent', self)
return GuiTUNSettings(parent=parent, **kwargs)
return TunSettingsDialog(parent=parent, **kwargs)
guiTUNSettings = cachedGuiTUNSettings()
guiTUNSettings.factoryToInput(Storage.UserTUNSettings())
@@ -808,20 +844,20 @@ class AppMainWindow(AppQMainWindow):
width, height = tuple(int(size) for size in windowSize)
if (width, height) == (640, 480):
self.resize(AppMainWindow.DEFAULT_WINDOW_SIZE)
self.resize(MainWindow.DEFAULT_WINDOW_SIZE)
else:
self.resize(width, height)
except Exception:
# Any non-exit exceptions
self.resize(AppMainWindow.DEFAULT_WINDOW_SIZE)
self.resize(MainWindow.DEFAULT_WINDOW_SIZE)
else:
try:
self.restoreGeometry(AppSettings.get('AppMainWindowGeometry'))
except Exception:
# Any non-exit exceptions
self.resize(AppMainWindow.DEFAULT_WINDOW_SIZE)
self.resize(MainWindow.DEFAULT_WINDOW_SIZE)
try:
self.restoreState(AppSettings.get('AppMainWindowState'))
@@ -839,9 +875,9 @@ class AppMainWindow(AppQMainWindow):
logger.error(
f'detected unresolve Qt bug on macOS. '
f'Resizing main window to default '
f'{AppMainWindow.DEFAULT_WINDOW_SIZE.toTuple()}'
f'{MainWindow.DEFAULT_WINDOW_SIZE.toTuple()}'
)
self.resize(AppMainWindow.DEFAULT_WINDOW_SIZE)
self.resize(MainWindow.DEFAULT_WINDOW_SIZE)
else:
logger.info(
f'restore main window size on macOS success: '
@@ -29,7 +29,7 @@ from PySide6.QtWidgets import *
import logging
import functools
__all__ = ['GuiCustomizeNetworkTestDialog']
__all__ = ['NetworkTestDialog']
logger = logging.getLogger(__name__)
@@ -37,11 +37,11 @@ registerAppSettings('CustomNetworkSpeedTestURL')
registerAppSettings('CustomNetworkConnectivityTestURL')
class GuiCustomizeNetworkTestDialog(AppQDialog):
class NetworkTestDialog(AppQDialog):
"""Present the GUI customize network test dialog."""
def __init__(self, *args, **kwargs):
"""Initialize the GuiCustomizeNetworkTestDialog."""
"""Initialize the network test settings dialog."""
super().__init__(*args, **kwargs)
self.setWindowTitle(_('Customize Network Test URL'))
@@ -29,18 +29,18 @@ from PySide6.QtWidgets import *
import logging
import functools
__all__ = ['GuiCustomizeProxyBypassDialog']
__all__ = ['ProxyBypassDialog']
logger = logging.getLogger(__name__)
registerAppSettings('CustomProxyBypass')
class GuiCustomizeProxyBypassDialog(AppQDialog):
class ProxyBypassDialog(AppQDialog):
"""Present the GUI customize proxy bypass dialog."""
def __init__(self, *args, **kwargs):
"""Initialize the GuiCustomizeProxyBypassDialog."""
"""Initialize the proxy bypass settings dialog."""
super().__init__(*args, **kwargs)
self.setWindowTitle(_('Customize System Proxy Bypass Address'))
@@ -20,7 +20,7 @@
from __future__ import annotations
from Furious.Frozenlib import *
from Furious.Library import *
from Furious.Repository import *
from Furious.Qt import *
from Furious.Qt import gettext as _
@@ -22,7 +22,7 @@ from __future__ import annotations
from Furious.Frozenlib import *
from Furious.Qt import *
from Furious.Qt import gettext as _
from Furious.Widget.UserSubsQTableView import *
from Furious.Widget.SubscriptionTableView import *
from PySide6 import QtCore
from PySide6.QtWidgets import *
@@ -30,7 +30,7 @@ from PySide6.QtWidgets import *
import uuid
import functools
__all__ = ['UserSubsWindow']
__all__ = ['SubscriptionWindow']
# Migrate legacy settings
registerAppSettings('SubscriptionWidgetWindowSize')
@@ -84,20 +84,20 @@ class AddSubsDialog(AppQDialog):
return self.webURLEdit.text()
class UserSubsWindow(AppQMainWindow):
class SubscriptionWindow(AppQMainWindow):
"""Present the user subs window."""
DEFAULT_WINDOW_SIZE = QtCore.QSize(1120, 600)
def __init__(self, *args, **kwargs):
"""Initialize the UserSubsWindow."""
"""Initialize the subscription window."""
callback = kwargs.pop('deleteUniqueCallback', None)
super().__init__(*args, **kwargs)
self.setWindowTitle(_('Edit Subscription'))
self.userSubsQTableWidget = UserSubsQTableView(
self.userSubsQTableWidget = SubscriptionTableView(
deleteUniqueCallback=callback, parent=self
)
@@ -176,23 +176,23 @@ class UserSubsWindow(AppQMainWindow):
width, height = tuple(int(size) for size in windowSize)
if (width, height) == (640, 480):
self.resize(UserSubsWindow.DEFAULT_WINDOW_SIZE)
self.resize(SubscriptionWindow.DEFAULT_WINDOW_SIZE)
else:
self.resize(width, height)
except Exception:
# Any non-exit exceptions
self.resize(UserSubsWindow.DEFAULT_WINDOW_SIZE)
self.resize(SubscriptionWindow.DEFAULT_WINDOW_SIZE)
else:
if PLATFORM == 'Darwin':
self.resize(UserSubsWindow.DEFAULT_WINDOW_SIZE)
self.resize(SubscriptionWindow.DEFAULT_WINDOW_SIZE)
else:
try:
self.restoreGeometry(AppSettings.get('UserSubsWindowGeometry'))
except Exception:
# Any non-exit exceptions
self.resize(UserSubsWindow.DEFAULT_WINDOW_SIZE)
self.resize(SubscriptionWindow.DEFAULT_WINDOW_SIZE)
try:
self.restoreState(AppSettings.get('UserSubsWindowState'))
@@ -20,10 +20,11 @@
from __future__ import annotations
from Furious.Frozenlib import *
from Furious.Library import *
from Furious.Domain import *
from Furious.Repository import *
from Furious.Qt import *
from Furious.Qt import gettext as _
from Furious.Widget.IndentSpinBox import *
from Furious.Window.IndentDialog import *
from PySide6 import QtCore
from PySide6.QtGui import *
@@ -367,7 +368,7 @@ class TextEditorWindow(AppQMainWindow):
# Do nothing
pass
indentSpinBox = IndentSpinBox(parent=self)
indentSpinBox = IndentDialog(parent=self)
indentSpinBox.finished.connect(
functools.partial(handleResultCode, indentSpinBox)
)
@@ -21,7 +21,7 @@ from __future__ import annotations
from Furious.Frozenlib import *
from Furious.Interface import *
from Furious.Library import *
from Furious.Repository import *
from Furious.Qt import *
from Furious.Qt import gettext as _
@@ -32,7 +32,7 @@ from PySide6.QtWidgets import *
import logging
import functools
__all__ = ['GuiTUNSettings']
__all__ = ['TunSettingsDialog']
logger = logging.getLogger(__name__)
@@ -173,7 +173,7 @@ class AppQLabelHelpPage(AppQLabel):
self.setWebsiteURL()
class GuiTUNSettingsItemHelpPage(GuiEditorItemWidgetContainer):
class GuiTUNSettingsItemHelpPage(EditorWidgetBinding):
"""Provide the TUN settings item help configuration editor page."""
def __init__(self, *args, **kwargs):
@@ -289,11 +289,11 @@ class GuiTUNSettingsGroupBoxMemory(GuiEditorWidgetQGroupBox):
]
class GuiTUNSettings(GuiEditorWidgetQDialog):
class TunSettingsDialog(GuiEditorWidgetQDialog):
"""Store and validate GUI TUN settings."""
def __init__(self, *args, **kwargs):
"""Initialize the GuiTUNSettings."""
"""Initialize the tun2socks settings dialog."""
tabTranslatable = kwargs.pop('tabTranslatable', True)
style = kwargs.pop('style', 'portrait')
+21 -5
View File
@@ -15,12 +15,28 @@
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <https://www.gnu.org/licenses/>.
"""Expose the application window classes."""
"""Expose application windows and complete dialog compositions."""
from __future__ import annotations
from .AppMainWindow import AppMainWindow
from .LogViewerWindow import LogViewerWindow
from .UserSubsWindow import UserSubsWindow
from .IndentDialog import IndentDialog
from .LogWindow import LogWindow
from .MainWindow import MainWindow
from .NetworkTestDialog import NetworkTestDialog
from .ProxyBypassDialog import ProxyBypassDialog
from .QRCodeWindow import QRCodeWindow
from .SubscriptionWindow import SubscriptionWindow
from .TextEditorWindow import TextEditorWindow
from .TunSettingsDialog import TunSettingsDialog
__all__ = ['AppMainWindow', 'LogViewerWindow', 'UserSubsWindow']
__all__ = [
'IndentDialog',
'LogWindow',
'MainWindow',
'NetworkTestDialog',
'ProxyBypassDialog',
'QRCodeWindow',
'SubscriptionWindow',
'TextEditorWindow',
'TunSettingsDialog',
]
+5 -5
View File
@@ -24,7 +24,7 @@ from Furious.Interface import *
from Furious.Qt import *
from Furious.Qt import gettext as _
from Furious.Utility import *
from Furious.Application import Application
from Furious.Application import DesktopApplication
from PySide6 import QtCore
from PySide6.QtGui import QDesktopServices
@@ -59,7 +59,7 @@ def runClearSettings():
def runAppMain():
"""Run app main."""
process = AppMainProcess(functools.partial(Application, sys.argv))
process = AppMainProcess(functools.partial(DesktopApplication, sys.argv))
process.start()
process.join()
@@ -70,9 +70,9 @@ def runAppMain():
sys.exit(exitcode)
# For Qt runtime. Not used
_app = Application(sys.argv)
_app = DesktopApplication(sys.argv)
if exitcode == ApplicationFactory.ExitCode.PlatformNotSupported.value:
if exitcode == ApplicationRunner.ExitCode.PlatformNotSupported.value:
mbox = AppQMessageBox(icon=AppQMessageBox.Icon.Critical)
mbox.setWindowIcon(bootstrapIcon('rocket-takeoff-window.svg'))
@@ -102,7 +102,7 @@ def runAppMain():
# Show the MessageBox and wait for the user to close it
mbox.exec()
else:
if exitcode == ApplicationFactory.ExitCode.AssertionError.value:
if exitcode == ApplicationRunner.ExitCode.AssertionError.value:
# Assertion error
text = _(
f'{APPLICATION_NAME} encountered an internal error and needs to be stopped'
+1 -1
View File
@@ -19,7 +19,7 @@ from __future__ import annotations
from Furious.Frozenlib import *
from Furious.Externals import *
from Furious.Library import *
from Furious.Domain import *
import os
import re