diff --git a/Furious/Actions/Connection.py b/Furious/Actions/Connection.py
index be6fe91..a65d08f 100644
--- a/Furious/Actions/Connection.py
+++ b/Furious/Actions/Connection.py
@@ -180,7 +180,7 @@ class ConnectAction(AppQAction):
self.setDisabled(value)
try:
- APP().systemTray.RoutingAction.setDisabled(value)
+ APP().routingController.setInteractionEnabled(not value)
except (AttributeError, RuntimeError):
pass
diff --git a/Furious/Actions/Routing.py b/Furious/Actions/Routing.py
index 94a8851..adabb59 100644
--- a/Furious/Actions/Routing.py
+++ b/Furious/Actions/Routing.py
@@ -15,19 +15,17 @@
# You should have received a copy of the GNU General Public License
# along with this program. If not, see .
-"""Implement tray actions for routing."""
+"""Present shared routing state through the tray action menu."""
from __future__ import annotations
-from Furious.Frozenlib import *
-from Furious.Repository import *
-from Furious.Plugins import getPluginRegistry
+from Furious.Controllers import RoutingController
from Furious.Qt import *
from Furious.Qt import gettext as _
-__all__ = ['RoutingAction']
+from PySide6 import QtCore
-registerAppSettings('Routing', default=AppBuiltinRouting.BypassMainlandChina.value)
+__all__ = ['RoutingAction']
# ALL BUILTIN ROUTING VALUE
_TRANSLATABLE_BUILTIN_ROUTING = [
@@ -38,33 +36,32 @@ _TRANSLATABLE_BUILTIN_ROUTING = [
class RoutingChildAction(AppQAction):
- """Handle the routing child action."""
+ """Forward one tray route choice to the shared controller."""
- def __init__(self, *args, **kwargs):
+ def __init__(
+ self,
+ *args,
+ controller: RoutingController,
+ routingValue: str,
+ **kwargs,
+ ):
"""Initialize the RoutingChildAction."""
- self.routingValue = kwargs.pop('routingValue', None)
+ self.controller = controller
+ self.routingValue = routingValue
super().__init__(*args, **kwargs)
def triggeredCallback(self, checked):
"""Handle activation of the action."""
- textEnglish = self.routingValue or self.textEnglish
-
- if AppSettings.get('Routing') != textEnglish:
- AppSettings.set('Routing', textEnglish)
-
- if APP().isSystemTrayConnected():
- APP().systemTray.ConnectAction.doReconnect()
+ self.controller.selectRouting(self.routingValue)
class RoutingAction(AppQAction):
- """Handle the routing action."""
+ """Render application routing options as a synchronized tray menu."""
- def __init__(self, **kwargs):
+ def __init__(self, controller: RoutingController, **kwargs):
"""Initialize the RoutingAction."""
- if AppSettings.get('Routing') == 'Bypass':
- # Update value for backward compatibility
- AppSettings.set('Routing', AppBuiltinRouting.BypassMainlandChina.value)
+ self.controller = controller
super().__init__(
_('Routing'),
@@ -73,17 +70,13 @@ class RoutingAction(AppQAction):
**kwargs,
)
- self.rebuildMenu()
+ self.controller.stateChanged.connect(self._applyState)
+ self.controller.interactionEnabledChanged.connect(self.setEnabled)
- def routingActions(self):
- """Return routing actions supported by the active core plugin."""
- config = self.activeConfig()
- if config is None:
- return list()
+ self._applyState(*self.controller.state())
- pluginRegistry = getPluginRegistry()
- options = pluginRegistry.routingOptions(config)
- routing = pluginRegistry.normalizeRouting(config, AppSettings.get('Routing'))
+ def routingActions(self, options, routing: str):
+ """Build tray actions from one shared controller snapshot."""
actions = list()
for option in options:
@@ -97,6 +90,7 @@ class RoutingAction(AppQAction):
if option.translatable
else option.displayName
),
+ controller=self.controller,
routingValue=option.id,
checkable=True,
checked=routing == option.id,
@@ -105,27 +99,20 @@ class RoutingAction(AppQAction):
return actions
- @staticmethod
- def activeConfig():
- """Return the currently selected server configuration, if any."""
- try:
- index = Storage.UserActivatedItemIndex()
- servers = Storage.UserServers()
+ @QtCore.Slot(object, str)
+ def _applyState(self, options, routing: str):
+ """Rebuild the tray menu from one controller state snapshot."""
+ oldActionGroup = getattr(self, '_actionGroup', None)
- if 0 <= index < len(servers):
- return servers[index]
- except Exception:
- # The tray may be built before persistent storage is available.
- pass
-
- return None
-
- def rebuildMenu(self):
- """Handle rebuild menu for the routing action."""
self._menu.clear()
self._menu._actions.clear()
+
+ if oldActionGroup is not None:
+ oldActionGroup.deleteLater()
+
self._actionGroup = AppQActionGroup(self)
- actions = self.routingActions()
+
+ actions = self.routingActions(options, routing)
for action in actions:
if isinstance(action, AppQSeperator):
@@ -137,15 +124,8 @@ class RoutingAction(AppQAction):
self._actionGroup.addAction(action)
self.setVisible(bool(actions))
+ self.setEnabled(self.controller.interactionEnabled)
- def getGlobalAction(self):
- """Return the active plugin's global routing action, if supported."""
- return next(
- (
- action
- for action in self._menu._actions
- if isinstance(action, RoutingChildAction)
- and action.routingValue == AppBuiltinRouting.Global.value
- ),
- None,
- )
+ def rebuildMenu(self):
+ """Refresh plugin options immediately before the tray menu opens."""
+ self.controller.refresh(force=True)
diff --git a/Furious/Application/DesktopApplication.py b/Furious/Application/DesktopApplication.py
index ef6b72e..b791415 100644
--- a/Furious/Application/DesktopApplication.py
+++ b/Furious/Application/DesktopApplication.py
@@ -23,6 +23,7 @@ from Furious.Frozenlib import *
from Furious.Interface import *
from Furious.Core import Tun2socks
from Furious.Backends import OFFICIAL_PLUGIN_TYPES
+from Furious.Controllers import RoutingController
from Furious.Extensions import BUNDLED_EXTENSION_TYPES
from Furious.Plugins import getPluginRegistry, initializePluginRegistry
from Furious.Qt import AppStyleSheet
@@ -181,6 +182,7 @@ class DesktopApplication(ApplicationRunner, SingletonApplication):
self.mainWindow = None
self.systemTray = None
self.connectionAction = None
+ self.routingController = None
# Unified logging service and presentation
self.logManager = None
@@ -544,8 +546,9 @@ class DesktopApplication(ApplicationRunner, SingletonApplication):
SystemProxy.off()
SystemProxy.daemonOn_()
- # The application owns the connection operation. Home and tray bind
- # to this same action, regardless of their construction order.
+ # The application owns connection and routing operations. Home and
+ # tray bind to these same objects regardless of construction order.
+ self.routingController = RoutingController(parent=self)
self.connectionAction = ConnectAction(isTrayAction=True)
self.mainWindow = MainWindow()
diff --git a/Furious/Application/TrayIcon.py b/Furious/Application/TrayIcon.py
index b3d3fac..f893d5d 100644
--- a/Furious/Application/TrayIcon.py
+++ b/Furious/Application/TrayIcon.py
@@ -55,7 +55,7 @@ class TrayIcon(
actions = [
APP().connectionAction,
- RoutingAction(isTrayAction=True),
+ RoutingAction(APP().routingController, isTrayAction=True),
ImportAction(isTrayAction=True),
AppQSeperator(),
ShowHomePageAction(isTrayAction=True),
diff --git a/Furious/Controllers/RoutingController.py b/Furious/Controllers/RoutingController.py
new file mode 100644
index 0000000..e6bebdf
--- /dev/null
+++ b/Furious/Controllers/RoutingController.py
@@ -0,0 +1,153 @@
+# Copyright (C) 2024–present Loren Eteval & contributors
+#
+# 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 .
+
+"""Own application routing selection independently from its UI controls."""
+
+from __future__ import annotations
+
+from Furious.Frozenlib import (
+ APP,
+ AppBuiltinRouting,
+ AppSettings,
+ registerAppSettings,
+)
+from Furious.Plugins import getPluginRegistry
+from Furious.Repository import Storage
+
+from PySide6 import QtCore
+
+__all__ = ['RoutingController']
+
+registerAppSettings('Routing', default=AppBuiltinRouting.BypassMainlandChina.value)
+
+
+class RoutingController(QtCore.QObject):
+ """Expose one routing state shared by every application surface."""
+
+ stateChanged = QtCore.Signal(object, str)
+ optionsChanged = QtCore.Signal(object)
+ routingChanged = QtCore.Signal(str)
+ interactionEnabledChanged = QtCore.Signal(bool)
+
+ def __init__(self, parent=None):
+ """Initialize persisted routing state and plugin-provided options."""
+ super().__init__(parent)
+
+ if AppSettings.get('Routing') == 'Bypass':
+ # Update the value used by older Furious releases.
+ AppSettings.set('Routing', AppBuiltinRouting.BypassMainlandChina.value)
+
+ self._options = tuple()
+ self._routing = str(AppSettings.get('Routing'))
+ self._interactionEnabled = True
+
+ self.refresh()
+
+ @staticmethod
+ def activeConfiguration():
+ """Return the active server configuration, if one is available."""
+ try:
+ index = Storage.UserActivatedItemIndex()
+ servers = Storage.UserServers()
+
+ if 0 <= index < len(servers):
+ return servers[index]
+ except Exception:
+ # The controller can exist before persistent storage is ready.
+ pass
+
+ return None
+
+ @property
+ def options(self):
+ """Return routing options supported by the active configuration."""
+ return self._options
+
+ @property
+ def routing(self) -> str:
+ """Return the normalized route shown by routing controls."""
+ return self._routing
+
+ @property
+ def interactionEnabled(self) -> bool:
+ """Return whether users may change routing at this lifecycle state."""
+ return self._interactionEnabled
+
+ def state(self):
+ """Return the current immutable option and selection snapshot."""
+ return self.options, self.routing
+
+ def refresh(self, *, force=False):
+ """Refresh routing capabilities from the active proxy-core plugin."""
+ config = self.activeConfiguration()
+ registry = getPluginRegistry()
+ options = registry.routingOptions(config) if config is not None else tuple()
+ routing = (
+ registry.normalizeRouting(config, AppSettings.get('Routing'))
+ if config is not None
+ else str(AppSettings.get('Routing'))
+ )
+
+ optionsChanged = options != self._options
+ routingChanged = routing != self._routing
+
+ self._options = options
+ self._routing = routing
+
+ if optionsChanged:
+ self.optionsChanged.emit(options)
+
+ if routingChanged:
+ self.routingChanged.emit(routing)
+
+ if force or optionsChanged or routingChanged:
+ self.stateChanged.emit(options, routing)
+
+ return self.state()
+
+ @QtCore.Slot(str)
+ def selectRouting(self, routing: str) -> bool:
+ """Persist a supported route and reconnect the active proxy if needed."""
+ options, _current = self.refresh()
+
+ if routing not in tuple(option.id for option in options):
+ return False
+
+ if AppSettings.get('Routing') == routing:
+ return False
+
+ AppSettings.set('Routing', routing)
+
+ self.refresh(force=True)
+
+ app = APP()
+
+ if app is not None and app.isSystemTrayConnected():
+ app.connectionAction.doReconnect()
+
+ return True
+
+ @QtCore.Slot(bool)
+ def setInteractionEnabled(self, enabled: bool):
+ """Publish whether routing controls may accept user interaction."""
+ enabled = bool(enabled)
+
+ if enabled == self._interactionEnabled:
+ return
+
+ self._interactionEnabled = enabled
+ self.interactionEnabledChanged.emit(enabled)
diff --git a/Furious/Controllers/__init__.py b/Furious/Controllers/__init__.py
index 38163c6..4944a1e 100644
--- a/Furious/Controllers/__init__.py
+++ b/Furious/Controllers/__init__.py
@@ -17,6 +17,7 @@
"""Expose presentation-independent application controllers."""
+from .RoutingController import RoutingController
from .SettingsController import SettingsController
-__all__ = ['SettingsController']
+__all__ = ['RoutingController', 'SettingsController']
diff --git a/Furious/Qt/QtWidgets.py b/Furious/Qt/QtWidgets.py
index c0f01c1..1b62754 100644
--- a/Furious/Qt/QtWidgets.py
+++ b/Furious/Qt/QtWidgets.py
@@ -38,6 +38,7 @@ __all__ = [
'moveToCenter',
'AppQCheckBox',
'AppQComboBox',
+ 'AppQComboBoxSeparatorDelegate',
'AppQDialog',
'AppQDialogButtonBox',
'AppQGroupBox',
@@ -96,6 +97,55 @@ class AppQCheckBox(Mixins.QTranslatable, QCheckBox):
self.setText(_(self.text()))
+class AppQComboBoxSeparatorDelegate(Mixins.ThemeAware, QAbstractItemDelegate):
+ """Paint real combo-box separators with application theme tokens."""
+
+ SeparatorRoleValue = 'separator'
+ HorizontalMargin = 8
+
+ def __init__(self, wrappedDelegate, comboBox):
+ """Wrap Qt's native delegate without changing normal item painting."""
+ self.wrappedDelegate = wrappedDelegate
+ self.comboBox = comboBox
+
+ super().__init__(comboBox)
+
+ @staticmethod
+ def isSeparator(index) -> bool:
+ """Return whether *index* was created by QComboBox.insertSeparator()."""
+ return (
+ index.data(QtCore.Qt.ItemDataRole.AccessibleDescriptionRole)
+ == AppQComboBoxSeparatorDelegate.SeparatorRoleValue
+ )
+
+ def paint(self, painter, option, index):
+ """Paint separators and preserve Qt's native rendering for all items."""
+ if not self.isSeparator(index):
+ self.wrappedDelegate.paint(painter, option, index)
+
+ return
+
+ try:
+ color = QColor(
+ AppStyleSheet.paletteForTheme(APP().theme())['border_strong']
+ )
+ except (AttributeError, RuntimeError):
+ color = option.palette.color(QPalette.ColorRole.Mid)
+
+ line = option.rect.adjusted(self.HorizontalMargin, 0, -self.HorizontalMargin, 0)
+ y = line.center().y()
+
+ painter.fillRect(line.left(), y, max(0, line.width()), 1, color)
+
+ def sizeHint(self, option, index):
+ """Keep the native popup's item and separator geometry unchanged."""
+ return self.wrappedDelegate.sizeHint(option, index)
+
+ def themeChangedCallback(self, theme: str):
+ """Repaint the popup viewport after an application theme change."""
+ self.comboBox.view().viewport().update()
+
+
class AppQComboBox(Mixins.QTranslatable, QComboBox):
"""Represent app q combo box."""
@@ -103,6 +153,18 @@ class AppQComboBox(Mixins.QTranslatable, QComboBox):
"""Initialize the AppQComboBox."""
super().__init__(*args, **kwargs)
+ self._themedSeparatorDelegate = None
+
+ def enableThemedSeparators(self):
+ """Theme real insertSeparator() rows while preserving native items."""
+ if self._themedSeparatorDelegate is not None:
+ return
+
+ self._themedSeparatorDelegate = AppQComboBoxSeparatorDelegate(
+ self.itemDelegate(), self
+ )
+ self.setItemDelegate(self._themedSeparatorDelegate)
+
def retranslate(self):
"""Refresh translated text for the app q combo box."""
for index in range(self.count()):
@@ -1073,10 +1135,20 @@ def showMBoxDirectRulesNotAllowed(**kwargs):
def handleResultCode(code):
"""Handle result code."""
if code == PySide6Legacy.enumValueWrapper(AppQMessageBox.StandardButton.Yes):
- globalAction = APP().systemTray.RoutingAction.getGlobalAction()
- if globalAction is not None:
- globalAction.trigger()
- APP().systemTray.ConnectAction.trigger()
+ app = APP()
+
+ controller, wasConnected = (
+ app.routingController,
+ app.isSystemTrayConnected(),
+ )
+
+ changed = controller.selectRouting(AppBuiltinRouting.Global.value)
+
+ if changed or controller.routing == AppBuiltinRouting.Global.value:
+ # Selecting a route reconnects an established connection itself.
+ # During an initial connection attempt, resume that attempt here.
+ if not wasConnected:
+ app.connectionAction.trigger()
else:
# Do nothing
pass
diff --git a/Furious/Qt/__init__.py b/Furious/Qt/__init__.py
index 441bbb7..67a7022 100644
--- a/Furious/Qt/__init__.py
+++ b/Furious/Qt/__init__.py
@@ -53,6 +53,7 @@ from .QtNetwork import AppQNetworkAccessManager
from .QtWidgets import (
AppQCheckBox,
AppQComboBox,
+ AppQComboBoxSeparatorDelegate,
AppQDialog,
AppQDialogButtonBox,
AppQGroupBox,
@@ -102,6 +103,7 @@ __all__ = [
'AppQActionGroup',
'AppQCheckBox',
'AppQComboBox',
+ 'AppQComboBoxSeparatorDelegate',
'AppQDialog',
'AppQDialogButtonBox',
'AppQGroupBox',
diff --git a/Furious/Widget/RoutingSelector.py b/Furious/Widget/RoutingSelector.py
new file mode 100644
index 0000000..964145f
--- /dev/null
+++ b/Furious/Widget/RoutingSelector.py
@@ -0,0 +1,106 @@
+# Copyright (C) 2024–present Loren Eteval & contributors
+#
+# 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 .
+
+"""Present the shared routing state as a compact Fluent-style selector."""
+
+from __future__ import annotations
+
+from Furious.Controllers import RoutingController
+from Furious.Frozenlib import Mixins
+from Furious.Qt import AppQComboBox
+from Furious.Qt import gettext as _
+
+from PySide6 import QtCore
+from PySide6.QtWidgets import QSizePolicy
+
+__all__ = ['RoutingSelector']
+
+
+class RoutingSelector(AppQComboBox):
+ """Select routes through the application-wide routing controller."""
+
+ def __init__(self, controller: RoutingController, parent=None):
+ """Bind the selector to one shared routing controller."""
+ self.controller = controller
+
+ # This widget restores its enabled state from the shared controller in
+ # retranslate(), so the generic translation disable/enable wrapper is
+ # neither needed nor safe during connection transitions.
+ super().__init__(parent, useQSetDisabled=False)
+
+ self.setObjectName('HomeRoutingSelector')
+ self.enableThemedSeparators()
+ self.setMinimumWidth(180)
+ self.setMaximumWidth(280)
+ self.setSizePolicy(QSizePolicy.Policy.Preferred, QSizePolicy.Policy.Fixed)
+ self.setAccessibleName(_('Routing'))
+ self.setToolTip(_('Routing'))
+
+ self.currentIndexChanged.connect(self._handleCurrentIndexChanged)
+ self.controller.stateChanged.connect(self._applyState)
+ self.controller.interactionEnabledChanged.connect(self._applyInteractionEnabled)
+
+ self._applyState(*self.controller.state())
+
+ @staticmethod
+ def _optionText(option) -> str:
+ """Return an option's translated or literal display name."""
+ return _(option.displayName) if option.translatable else option.displayName
+
+ @QtCore.Slot(object, str)
+ def _applyState(self, options, routing: str):
+ """Replace items atomically from one controller state snapshot."""
+ with Mixins.QBlockSignalContext(self):
+ self.clear()
+
+ for option in options:
+ if option.separatorBefore and self.count():
+ self.insertSeparator(self.count())
+
+ self.addItem(self._optionText(option), option.id)
+
+ index = self.findData(routing)
+
+ self.setCurrentIndex(index if index >= 0 else 0)
+
+ self.setVisible(bool(options))
+ self._applyInteractionEnabled(self.controller.interactionEnabled)
+
+ @QtCore.Slot(bool)
+ def _applyInteractionEnabled(self, enabled: bool):
+ """Enable selection only when options and connection state allow it."""
+ self.setEnabled(bool(enabled) and self.count() > 0)
+
+ @QtCore.Slot(int)
+ def _handleCurrentIndexChanged(self, index: int):
+ """Forward user selection without retaining independent state."""
+ routing = self.itemData(index)
+
+ if isinstance(routing, str):
+ self.controller.selectRouting(routing)
+
+ def showPopup(self):
+ """Refresh plugin capabilities immediately before showing options."""
+ self.controller.refresh(force=True)
+
+ super().showPopup()
+
+ def retranslate(self):
+ """Refresh option text and routing accessibility metadata."""
+ self._applyState(*self.controller.state())
+ self.setAccessibleName(_('Routing'))
+ self.setToolTip(_('Routing'))
diff --git a/Furious/Widget/ServerTableView.py b/Furious/Widget/ServerTableView.py
index 7391a1e..1c6bfcb 100644
--- a/Furious/Widget/ServerTableView.py
+++ b/Furious/Widget/ServerTableView.py
@@ -1041,6 +1041,8 @@ class DeleteServersProgressDialog(AppQDialog):
# Set invalid first
AppSettings.set('ActivatedItemIndex', str(-1))
+ self.table.activeServerChanged.emit()
+
if APP().isSystemTrayConnected():
if self.showTrayMessage:
# Trigger disconnect
@@ -1462,6 +1464,8 @@ class ServerTableView(
):
"""Represent user servers Qt table view."""
+ activeServerChanged = QtCore.Signal()
+
RowHeight = 42
Headers = [
@@ -1953,6 +1957,7 @@ class ServerTableView(
def activateItemByIndex(self, index, activate):
"""Activate item by index."""
oldIndex = Storage.UserActivatedItemIndex()
+ changed = activate and oldIndex != int(index)
if activate:
AppSettings.set('ActivatedItemIndex', str(index))
@@ -1960,6 +1965,9 @@ class ServerTableView(
self.sourceModel.emitRowChanged(oldIndex)
self.sourceModel.emitRowChanged(index)
+ if changed:
+ self.activeServerChanged.emit()
+
def flushItem(self, row: int, column: int, item: ServerProfile):
"""Refresh item."""
itemIndex = item.index
@@ -2092,6 +2100,9 @@ class ServerTableView(
self.sourceModel.emitRowChanged(row)
+ if row == Storage.UserActivatedItemIndex():
+ self.activeServerChanged.emit()
+
def flushAll(self):
# Refresh index
"""Refresh all."""
@@ -2251,6 +2262,8 @@ class ServerTableView(
# Set invalid first
AppSettings.set('ActivatedItemIndex', str(-1))
+ self.activeServerChanged.emit()
+
if APP().isSystemTrayConnected():
if showTrayMessage:
# Trigger disconnect
@@ -2574,6 +2587,8 @@ class ServerTableView(
AppSettings.set('ActivatedItemIndex', str(newActivatedIndex))
+ self.activeServerChanged.emit()
+
self.sourceModel.refreshIndexes()
self.proxyModel.invalidate()
diff --git a/Furious/Widget/__init__.py b/Furious/Widget/__init__.py
index 30bebd8..f222b12 100644
--- a/Furious/Widget/__init__.py
+++ b/Furious/Widget/__init__.py
@@ -22,6 +22,7 @@ from __future__ import annotations
from .ConnectionProgressWidget import ConnectionProgressWidget
from .MetricsGraph import MetricsGraphWidget
from .NavigationView import NavigationView
+from .RoutingSelector import RoutingSelector
from .ServerTableView import ServerTableView
from .SubscriptionTableView import SubscriptionTableView
from .WaitingSpinner import WaitingSpinner
@@ -30,6 +31,7 @@ __all__ = [
'ConnectionProgressWidget',
'MetricsGraphWidget',
'NavigationView',
+ 'RoutingSelector',
'ServerTableView',
'SubscriptionTableView',
'WaitingSpinner',
diff --git a/Furious/Window/HomePage.py b/Furious/Window/HomePage.py
index 67ffc0a..aafbc43 100644
--- a/Furious/Window/HomePage.py
+++ b/Furious/Window/HomePage.py
@@ -40,6 +40,7 @@ from Furious.Actions.Import import (
ImportURIFromClipboardAction,
)
from Furious.Widget.ConnectionButton import ConnectionButton
+from Furious.Widget.RoutingSelector import RoutingSelector
from Furious.Widget.ServerTableView import *
from Furious.Window.NetworkTestDialog import *
from Furious.Window.ProxyBypassDialog import *
@@ -515,6 +516,8 @@ class HomePage(Mixins.QTranslatable, QMainWindow):
self.activateSelectedServerForConnection,
parent=self,
)
+ self.routingController = APP().routingController
+ self.routingSelector = RoutingSelector(self.routingController, parent=self)
self.searchLineEdit = AppQLineEdit()
self.searchLineEdit.setPlaceholderText(
@@ -550,6 +553,7 @@ class HomePage(Mixins.QTranslatable, QMainWindow):
self.actionLayout.setContentsMargins(0, 0, 0, 0)
self.actionLayout.setSpacing(8)
self.actionLayout.addWidget(self.connectButton)
+ self.actionLayout.addWidget(self.routingSelector)
self.actionLayout.addWidget(self.serverButton)
self.actionLayout.addStretch(1)
self.actionLayout.addWidget(self.subscriptionFilterComboBox)
@@ -577,6 +581,9 @@ class HomePage(Mixins.QTranslatable, QMainWindow):
self.userServersQTableWidget.selectionModel().selectionChanged.connect(
self.handleServerSelectionChanged
)
+ self.userServersQTableWidget.activeServerChanged.connect(
+ self.routingController.refresh
+ )
self.refreshSubscriptionFilter()
self.handleServerSelectionChanged()
@@ -607,6 +614,7 @@ class HomePage(Mixins.QTranslatable, QMainWindow):
return False
self.userServersQTableWidget.activateSelectedServer()
+ self.routingController.refresh()
return Storage.UserActivatedItemIndex() == indexes[0]