Expose application controller accessors

Signed-off-by: Loren Eteval <loren.eteval@proton.me>
This commit is contained in:
Loren Eteval
2026-08-12 12:24:04 +08:00
parent a471f63ead
commit dae6cbc1dd
19 changed files with 113 additions and 90 deletions
+3 -4
View File
@@ -20,11 +20,10 @@
from __future__ import annotations
from Furious.Controllers.ConnectionController import (
ConnectionController,
ConnectionError,
ConnectionState,
)
from Furious.Frozenlib import APP, AppSettings
from Furious.Frozenlib import APP, AppConnectionController, AppSettings
from Furious.Qt import AppQAction, AppQMessageBox, bootstrapIcon
from Furious.Qt import gettext as _
from Furious.Widget.ConnectionProgressWidget import ConnectionProgressWidget
@@ -44,9 +43,9 @@ _TRANSLATABLE_CONNECTION_STATES = (
class ConnectAction(AppQAction):
"""Adapt connection state and operations to a tray QAction."""
def __init__(self, controller: ConnectionController, **kwargs):
def __init__(self, **kwargs):
"""Bind tray presentation to the shared connection controller."""
self.controller = controller
self.controller = AppConnectionController()
super().__init__(
_('Connect'),
+5 -13
View File
@@ -19,7 +19,7 @@
from __future__ import annotations
from Furious.Controllers import RoutingController
from Furious.Frozenlib import AppRoutingController
from Furious.Qt import *
from Furious.Qt import gettext as _
@@ -38,30 +38,23 @@ _TRANSLATABLE_BUILTIN_ROUTING = [
class RoutingChildAction(AppQAction):
"""Forward one tray route choice to the shared controller."""
def __init__(
self,
*args,
controller: RoutingController,
routingValue: str,
**kwargs,
):
def __init__(self, *args, routingValue: str, **kwargs):
"""Initialize the RoutingChildAction."""
self.controller = controller
self.routingValue = routingValue
super().__init__(*args, **kwargs)
def triggeredCallback(self, checked):
"""Handle activation of the action."""
self.controller.selectRouting(self.routingValue)
AppRoutingController().selectRouting(self.routingValue)
class RoutingAction(AppQAction):
"""Render application routing options as a synchronized tray menu."""
def __init__(self, controller: RoutingController, **kwargs):
def __init__(self, **kwargs):
"""Initialize the RoutingAction."""
self.controller = controller
self.controller = AppRoutingController()
super().__init__(
_('Routing'),
@@ -90,7 +83,6 @@ class RoutingAction(AppQAction):
if option.translatable
else option.displayName
),
controller=self.controller,
routingValue=option.id,
checkable=True,
checked=routing == option.id,
+24 -8
View File
@@ -23,7 +23,11 @@ from Furious.Frozenlib import *
from Furious.Interface import *
from Furious.Core import Tun2socks
from Furious.Backends import OFFICIAL_PLUGIN_TYPES
from Furious.Controllers import ConnectionController, RoutingController
from Furious.Controllers import (
ConnectionController,
RoutingController,
SettingsController,
)
from Furious.Extensions import BUNDLED_EXTENSION_TYPES
from Furious.Plugins import getPluginRegistry, initializePluginRegistry
from Furious.Qt import AppStyleSheet
@@ -182,6 +186,7 @@ class DesktopApplication(ApplicationRunner, SingletonApplication):
self.systemTray = None
self.connectionController = None
self.routingController = None
self.settingsController = None
# Unified logging service and presentation
self.logManager = None
@@ -369,7 +374,7 @@ class DesktopApplication(ApplicationRunner, SingletonApplication):
@QtCore.Slot()
def cleanup():
"""Release resources owned by the application."""
controller = getattr(APP(), 'connectionController', None)
controller = AppConnectionController()
if controller is not None:
controller.shutdown()
@@ -452,13 +457,23 @@ class DesktopApplication(ApplicationRunner, SingletonApplication):
self.addEnviron()
self.addStorage()
# Controllers exist before presentation services so even early
# widgets and plugin UI can observe a stable disconnected state.
self.connectionController = ConnectionController(parent=self)
self.routingController = RoutingController(parent=self)
# The application owns these lifetime-scoped services; Frozenlib
# exposes them through App*Controller accessors.
(
self.connectionController,
self.routingController,
self.settingsController,
) = (
ConnectionController(parent=self),
RoutingController(parent=self),
SettingsController(),
)
self.connectionController.interactionEnabledChanged.connect(
self.routingController.setInteractionEnabled
)
self.addCustomFont()
# self.configureApplicationFont()
self.configureLogging()
@@ -566,7 +581,7 @@ class DesktopApplication(ApplicationRunner, SingletonApplication):
if state == QtCore.Qt.ApplicationState.ApplicationActive:
if (
not self.mainWindow.isVisible()
and not self.connectionController.isConnecting()
and not AppConnectionController().isConnecting()
):
self.mainWindow.show()
@@ -581,7 +596,8 @@ class DesktopApplication(ApplicationRunner, SingletonApplication):
self.systemTray.show()
self.systemTray.setCustomToolTip()
self.systemTray.bootstrap()
self.connectionController.restoreStartupState()
AppConnectionController().restoreStartupState()
return self.exec()
except SystemTrayUnavailable:
+4 -4
View File
@@ -54,8 +54,8 @@ class TrayIcon(
super().__init__(*args, **kwargs)
actions = [
ConnectAction(APP().connectionController, isTrayAction=True),
RoutingAction(APP().routingController, isTrayAction=True),
ConnectAction(isTrayAction=True),
RoutingAction(isTrayAction=True),
ImportAction(isTrayAction=True),
AppQSeperator(),
ShowHomePageAction(isTrayAction=True),
@@ -173,7 +173,7 @@ class TrayIcon(
def setMonochromeIcon(self):
"""Set monochrome icon."""
if APP().connectionController.isConnected():
if AppConnectionController().isConnected():
self.setConnectedMonochromeIcon()
else:
self.setDisconnectedMonochromeIcon()
@@ -265,7 +265,7 @@ class TrayIcon(
return
if APP().connectionController.isConnected():
if AppConnectionController().isConnected():
self.setConnectedIcon()
else:
self.setDisconnectedIcon()
+3 -5
View File
@@ -20,7 +20,7 @@
from __future__ import annotations
from Furious.Frozenlib import (
APP,
AppConnectionController,
AppBuiltinRouting,
AppSettings,
registerAppSettings,
@@ -134,10 +134,8 @@ class RoutingController(QtCore.QObject):
self.refresh(force=True)
app = APP()
if app is not None and app.connectionController.isConnected():
app.connectionController.startReconnection()
if AppConnectionController().isConnected():
AppConnectionController().startReconnection()
return True
+1 -1
View File
@@ -112,7 +112,7 @@ class SettingsController:
try:
if enabled:
APP().systemTray.setMonochromeIcon()
elif APP().connectionController.isConnected():
elif AppConnectionController().isConnected():
APP().systemTray.setConnectedIcon()
else:
APP().systemTray.setDisconnectedIcon()
+9
View File
@@ -25,9 +25,12 @@ import functools
__all__ = [
'APP',
'AppConnectionController',
'AppFontName',
'AppLogManager',
'AppLogPage',
'AppRoutingController',
'AppSettingsController',
'AppThreadPool',
]
@@ -40,13 +43,19 @@ def getAppAttributes(name: str):
(
AppConnectionController,
AppFontName,
AppLogManager,
AppLogPage,
AppRoutingController,
AppSettingsController,
AppThreadPool,
) = (
functools.partial(getAppAttributes, 'connectionController'),
functools.partial(getAppAttributes, 'customFontName'),
functools.partial(getAppAttributes, 'logManager'),
functools.partial(getAppAttributes, 'logPage'),
functools.partial(getAppAttributes, 'routingController'),
functools.partial(getAppAttributes, 'settingsController'),
functools.partial(getAppAttributes, 'threadPool'),
)
+13 -1
View File
@@ -67,7 +67,16 @@ from .Constants import (
XRAY_ASSET_PATH_GEOSITE,
)
from .Enum import AppBuiltinCommand, AppBuiltinProxyMode, AppBuiltinRouting
from .Globals import APP, AppFontName, AppLogManager, AppLogPage, AppThreadPool
from .Globals import (
APP,
AppConnectionController,
AppFontName,
AppLogManager,
AppLogPage,
AppRoutingController,
AppSettingsController,
AppThreadPool,
)
from .Mixins import Mixins
from .PySide6Legacy import PySide6Legacy
from .StartupOnBoot import StartupOnBoot
@@ -110,11 +119,14 @@ __all__ = [
'AppBuiltinCommand',
'AppBuiltinProxyMode',
'AppBuiltinRouting',
'AppConnectionController',
'AppFontName',
'AppLogManager',
'AppLogPage',
'AppResources',
'AppSettings',
'AppRoutingController',
'AppSettingsController',
'AppThreadPool',
'CORE_CHECK_ALIVE_INTERVAL',
'CRASH_LOG_DIR',
+2 -2
View File
@@ -64,7 +64,7 @@ class AppHue:
@staticmethod
def currentColor() -> str:
"""Return the current color value."""
if APP().connectionController.isConnected():
if AppConnectionController().isConnected():
return AppHue.connectedColor()
else:
return AppHue.disconnectedColor()
@@ -72,7 +72,7 @@ class AppHue:
@staticmethod
def currentWindowIcon() -> AppQIcon:
"""Return the current window icon value."""
if APP().connectionController.isConnected():
if AppConnectionController().isConnected():
return AppHue.connectedWindowIcon()
else:
return AppHue.disconnectedWindowIcon()
+5 -7
View File
@@ -1066,7 +1066,7 @@ def showMBoxNewChangesNextTime(**kwargs):
def handleResultCode(code):
"""Handle result code."""
if code == PySide6Legacy.enumValueWrapper(AppQMessageBox.StandardButton.Yes):
APP().connectionController.startReconnection()
AppConnectionController().startReconnection()
else:
# Do nothing
pass
@@ -1074,7 +1074,7 @@ def showMBoxNewChangesNextTime(**kwargs):
try:
method = kwargs.pop('method', 'open')
if APP().connectionController.isConnected():
if AppConnectionController().isConnected():
mbox = MBoxNewChangesNextTime(**kwargs)
if isinstance(mbox.parent(), QMainWindow):
@@ -1135,11 +1135,9 @@ def showMBoxDirectRulesNotAllowed(**kwargs):
def handleResultCode(code):
"""Handle result code."""
if code == PySide6Legacy.enumValueWrapper(AppQMessageBox.StandardButton.Yes):
app = APP()
controller, wasConnected = (
app.routingController,
app.connectionController.isConnected(),
AppRoutingController(),
AppConnectionController().isConnected(),
)
changed = controller.selectRouting(AppBuiltinRouting.Global.value)
@@ -1148,7 +1146,7 @@ def showMBoxDirectRulesNotAllowed(**kwargs):
# Selecting a route reconnects an established connection itself.
# During an initial connection attempt, resume that attempt here.
if not wasConnected:
app.connectionController.startConnection()
AppConnectionController().startConnection()
else:
# Do nothing
pass
+4 -2
View File
@@ -127,7 +127,8 @@ class Storage:
def UserHttpProxy() -> Union[str, None]:
"""Return the user HTTP proxy value."""
try:
controller = APP().connectionController
controller = AppConnectionController()
configuration = controller.activeConfiguration
if controller.isConnected() and isinstance(
@@ -145,7 +146,8 @@ class Storage:
def UserServerRemark() -> Union[str, None]:
"""Return the user server remark value."""
try:
controller = APP().connectionController
controller = AppConnectionController()
configuration = controller.activeConfiguration
if not controller.isConnected() or not isinstance(
+2 -2
View File
@@ -19,7 +19,7 @@
from __future__ import annotations
from Furious.Frozenlib import APP
from Furious.Frozenlib import AppConnectionController
from Furious.Plugins import (
CapabilityKind,
NavigationPageDescriptor,
@@ -38,7 +38,7 @@ logger = logging.getLogger(__name__)
def isCoreActive(coreType) -> bool:
"""Return whether the active connection owns a core of ``coreType``."""
try:
controller = APP().connectionController
controller = AppConnectionController()
return controller.isConnected() and any(
isinstance(process, coreType) for process in controller.processes
+2 -1
View File
@@ -21,6 +21,7 @@ from __future__ import annotations
from Furious.Frozenlib import (
APP,
AppConnectionController,
AppBinarySettings,
AppSettings,
Mixins,
@@ -239,7 +240,7 @@ class TrafficStatsManager(
def _activeProcesses():
"""Return the processes owned by the active connection controller."""
try:
return APP().connectionController.processes
return AppConnectionController().processes
except (AttributeError, RuntimeError):
return tuple()
+3 -4
View File
@@ -19,8 +19,8 @@
from __future__ import annotations
from Furious.Controllers import ConnectionController, ConnectionState
from Furious.Frozenlib import Mixins
from Furious.Controllers import ConnectionState
from Furious.Frozenlib import AppConnectionController, Mixins
from Furious.Qt import bootstrapIcon, AppQPushButton
from Furious.Qt import gettext as _
@@ -37,12 +37,11 @@ class ConnectionButton(AppQPushButton):
def __init__(
self,
controller: ConnectionController,
activateSelected: Callable[[], bool],
parent=None,
):
"""Bind Home presentation and selection policy to the controller."""
self.controller = controller
self.controller = AppConnectionController()
self.activateSelected = activateSelected
self._selectionCount = 0
+3 -4
View File
@@ -19,8 +19,7 @@
from __future__ import annotations
from Furious.Controllers import RoutingController
from Furious.Frozenlib import Mixins
from Furious.Frozenlib import AppRoutingController, Mixins
from Furious.Qt import AppQComboBox
from Furious.Qt import gettext as _
@@ -33,9 +32,9 @@ __all__ = ['RoutingSelector']
class RoutingSelector(AppQComboBox):
"""Select routes through the application-wide routing controller."""
def __init__(self, controller: RoutingController, parent=None):
def __init__(self, parent=None):
"""Bind the selector to one shared routing controller."""
self.controller = controller
self.controller = AppRoutingController()
# This widget restores its enabled state from the shared controller in
# retranslate(), so the generic translation disable/enable wrapper is
+9 -9
View File
@@ -1043,7 +1043,7 @@ class DeleteServersProgressDialog(AppQDialog):
self.table.activeServerChanged.emit()
controller = APP().connectionController
controller = AppConnectionController()
if controller.isConnected():
controller.startDisconnection(
@@ -1817,7 +1817,7 @@ class ServerTableView(
# Same item activated. Do nothing
return
if APP().connectionController.isConnecting():
if AppConnectionController().isConnecting():
mbox = AppQMessageBox(icon=AppQMessageBox.Icon.Information)
mbox.setWindowTitle(_('Connecting'))
mbox.setText(_('Connecting. Please wait...'))
@@ -1837,8 +1837,8 @@ class ServerTableView(
self.activateItemByIndex(newIndex, True)
if APP().connectionController.isConnected():
APP().connectionController.startReconnection()
if AppConnectionController().isConnected():
AppConnectionController().startReconnection()
@functools.lru_cache(None)
def getGuiEditorByProtocol(self, protocol, **kwargs):
@@ -2263,7 +2263,7 @@ class ServerTableView(
self.activeServerChanged.emit()
controller = APP().connectionController
controller = AppConnectionController()
if controller.isConnected():
controller.startDisconnection(
@@ -2565,7 +2565,7 @@ class ServerTableView(
active.itemSubscription == unique and active.itemSubscriptionManaged
)
wasConnected = APP().connectionController.isConnected()
wasConnected = AppConnectionController().isConnected()
self.sourceModel.beginResetModel()
@@ -2592,9 +2592,9 @@ class ServerTableView(
if wasConnected and activeProfileId:
if newActivatedIndex < 0 and activeWasManagedByGroup:
APP().connectionController.startDisconnection()
AppConnectionController().startDisconnection()
elif activeProfileId in result.changedProfileIds:
APP().connectionController.startReconnection()
AppConnectionController().startReconnection()
return result
@@ -2620,7 +2620,7 @@ class ServerTableView(
self.setCurrentIndex(self.proxyIndexFromSourceRow(0))
# Try to be user-friendly in some extreme cases
if not APP().connectionController.isConnected():
if not AppConnectionController().isConnected():
# Activate automatically
self.activateItemByIndex(0, True)
+5 -6
View File
@@ -90,7 +90,7 @@ class AppConnectivityManager(ConnectivityManager):
def startSingleTest(self):
"""Start single test."""
if not APP().connectionController.isConnected():
if not AppConnectionController().isConnected():
parent = self.parent()
if isinstance(parent, HomePage):
@@ -512,12 +512,10 @@ class HomePage(Mixins.QTranslatable, QMainWindow):
self.pageTitleLabel.setObjectName('HomePageTitle')
self.connectButton = ConnectionButton(
APP().connectionController,
self.activateSelectedServerForConnection,
parent=self,
)
self.routingController = APP().routingController
self.routingSelector = RoutingSelector(self.routingController, parent=self)
self.routingSelector = RoutingSelector(parent=self)
self.searchLineEdit = AppQLineEdit()
self.searchLineEdit.setPlaceholderText(
@@ -582,7 +580,7 @@ class HomePage(Mixins.QTranslatable, QMainWindow):
self.handleServerSelectionChanged
)
self.userServersQTableWidget.activeServerChanged.connect(
self.routingController.refresh
AppRoutingController().refresh
)
self.refreshSubscriptionFilter()
@@ -614,7 +612,8 @@ class HomePage(Mixins.QTranslatable, QMainWindow):
return False
self.userServersQTableWidget.activateSelectedServer()
self.routingController.refresh()
AppRoutingController().refresh()
return Storage.UserActivatedItemIndex() == indexes[0]
+2 -2
View File
@@ -101,12 +101,12 @@ class MainWindow(AppQMainWindow):
parent=self.navigationView,
)
APP().connectionController.interactionEnabledChanged.connect(
AppConnectionController().interactionEnabledChanged.connect(
self.settingsPage.setConnectionControlsEnabled
)
self.settingsPage.setConnectionControlsEnabled(
APP().connectionController.interactionEnabled
AppConnectionController().interactionEnabled
)
if not isinstance(self.logPage, LogPage):
+14 -15
View File
@@ -19,7 +19,6 @@
from __future__ import annotations
from Furious.Controllers import SettingsController
from Furious.Frozenlib import *
from Furious.Plugins import (
CapabilityKind,
@@ -499,7 +498,7 @@ class _LanguageSettingsCard(_SettingsCard):
language = self.comboBox.currentData()
if isinstance(language, str):
SettingsController.setLanguage(language)
AppSettingsController().setLanguage(language)
class _SystemProxySettingsCard(_SettingsCard):
@@ -550,7 +549,7 @@ class _SystemProxySettingsCard(_SettingsCard):
mode = self.comboBox.currentData()
if isinstance(mode, str):
SettingsController.setSystemProxyMode(mode)
AppSettingsController().setSystemProxyMode(mode)
class _SettingsSection(QWidget):
@@ -645,7 +644,7 @@ class SettingsPage(Mixins.QTranslatable, QMainWindow):
self.tunModeCard = _ToggleSettingsCard(
'shield-check.svg',
'VPNMode',
SettingsController.setTUNMode,
AppSettingsController().setTUNMode,
_tunModeTitle(),
_('Route system traffic through the active proxy connection.'),
)
@@ -661,7 +660,7 @@ class SettingsPage(Mixins.QTranslatable, QMainWindow):
_ToggleSettingsCard(
'moon-stars.svg',
'DarkMode',
SettingsController.setDarkMode,
AppSettingsController().setDarkMode,
_('Dark Mode'),
_('Use the application dark theme instead of automatic appearance.'),
),
@@ -672,21 +671,21 @@ class SettingsPage(Mixins.QTranslatable, QMainWindow):
_ToggleSettingsCard(
'circle-half.svg',
'UseMonochromeTrayIcon',
SettingsController.setMonochromeTrayIcon,
AppSettingsController().setMonochromeTrayIcon,
_('Use Monochrome Tray Icon'),
_('Use a theme-aware single-color system tray icon.'),
),
_ToggleSettingsCard(
'power.svg',
'StartupOnBoot',
SettingsController.setStartupOnBoot,
AppSettingsController().setStartupOnBoot,
_('Startup On Boot'),
_('Start the application automatically after signing in.'),
),
_ToggleSettingsCard(
'battery-half.svg',
'PowerSaveMode',
SettingsController.setPowerSaveMode,
AppSettingsController().setPowerSaveMode,
_('Power Save Mode'),
_('Reduce background activity when the application is idle.'),
),
@@ -701,7 +700,7 @@ class SettingsPage(Mixins.QTranslatable, QMainWindow):
self.hideDockCard = _ToggleSettingsCard(
'window.svg',
'HideDockIcon',
SettingsController.setDockIconHidden,
AppSettingsController().setDockIconHidden,
_('Hide Dock Icon'),
_('Keep the application available from the menu bar only.'),
)
@@ -753,28 +752,28 @@ class SettingsPage(Mixins.QTranslatable, QMainWindow):
_ToggleSettingsCard(
'pc-display-horizontal.svg',
'ForceToLocalhostWhenSettingLocalProxy',
SettingsController.setForceLocalProxy,
AppSettingsController().setForceLocalProxy,
_('Force To 127.0.0.1 When Setting Local Proxy'),
_('Use the IPv4 loopback address when configuring the system proxy.'),
),
_ToggleSettingsCard(
'hourglass-split.svg',
'ShowProgressBarWhenConnecting',
SettingsController.setConnectionProgressVisible,
AppSettingsController().setConnectionProgressVisible,
_('Show Progress Bar When Connecting'),
_('Show connection progress while proxy services are starting.'),
),
_ToggleSettingsCard(
'speedometer2.svg',
METRICS_COLLECTION_SETTING,
SettingsController.setMetricsCollectionEnabled,
AppSettingsController().setMetricsCollectionEnabled,
_('Enable Metrics Collection'),
_('Collect network speed and traffic history while connected.'),
),
_ToggleSettingsCard(
'arrow-repeat.svg',
CLEAR_TRAFFIC_USAGE_ON_RECONNECT_SETTING,
SettingsController.setClearTrafficUsageOnReconnect,
AppSettingsController().setClearTrafficUsageOnReconnect,
_('Clear Traffic Usage Statistics On Reconnect'),
_(
'Start accumulated upload and download usage from zero after reconnecting.'
@@ -783,7 +782,7 @@ class SettingsPage(Mixins.QTranslatable, QMainWindow):
_ToggleSettingsCard(
'text-paragraph.svg',
'ShowTabAndSpacesInEditor',
SettingsController.setEditorWhitespaceVisible,
AppSettingsController().setEditorWhitespaceVisible,
_('Show Tab And Spaces In Editor'),
_('Display whitespace markers in configuration editors.'),
),
@@ -805,7 +804,7 @@ class SettingsPage(Mixins.QTranslatable, QMainWindow):
self.autoAssetsCard = _ToggleSettingsCard(
'cloud-arrow-down.svg',
'AutoUpdateAssetFiles',
SettingsController.setAutoUpdateAssets,
AppSettingsController().setAutoUpdateAssets,
_('Automatically Update Asset Files'),
_('Keep supported proxy-core data files up to date.'),
)