diff --git a/Furious/Qt/NetworkStateManager.py b/Furious/Qt/NetworkStateManager.py index aafa818..da9c2b5 100644 --- a/Furious/Qt/NetworkStateManager.py +++ b/Furious/Qt/NetworkStateManager.py @@ -73,7 +73,15 @@ class NetworkStateManager(Mixins.ConnectionAware, WebGETManager): self.jobArrangeTimer.start(self.recalculateJobInterval(jobStatus=False)) def startSingleTest(self): - networkReply = self.webGET(NETWORK_STATE_TEST_URL) + # Use custom network state test URL if possible + settings = AppSettings.get('CustomNetworkStateTestURL') + + if settings is None: + url = NETWORK_SPEED_TEST_URL + else: + url = settings + + networkReply = self.webGET(url) def abort(_networkReply): if isinstance(_networkReply, QNetworkReply): diff --git a/Furious/TrayActions/Connect.py b/Furious/TrayActions/Connect.py index 36fc8ce..c2dbe2a 100644 --- a/Furious/TrayActions/Connect.py +++ b/Furious/TrayActions/Connect.py @@ -253,21 +253,44 @@ class ConnectAction(AppQAction): if self.actionQueue.empty(): if success: - SystemProxy.set(httpProxy, PROXY_SERVER_BYPASS) + # Use custom proxy bypass address if possible + settings = AppSettings.get('CustomProxyBypass') - self.doConnected() - - APP().systemTray.showMessage( - f'{config.coreName()}: ' + _('Connected') - ) - - if AppSettings.isStateON_('PowerSaveMode'): - # Power optimization - self.actionTimer.start(CORE_CHECK_ALIVE_INTERVAL * 2) + if settings is None: + proxyServerBypass = PROXY_SERVER_BYPASS else: - self.actionTimer.start(CORE_CHECK_ALIVE_INTERVAL) + proxyServerBypass = settings - self.doConnectedCallOnceOnly() + try: + SystemProxy.set(httpProxy, proxyServerBypass) + except Exception as ex: + # Any non-exit exceptions + + logger.error(f'error while setting http proxy: {ex}') + logger.error( + f'failed to connect successfully. ' + f'This may be due to improper proxy ' + f'server bypass settings: {proxyServerBypass}' + ) + + self.coreManager.stopAll() + self.doDisconnectWithTrayMessage( + f'{config.coreName()}: ' + _('Unknown error') + ) + else: + self.doConnected() + + APP().systemTray.showMessage( + f'{config.coreName()}: ' + _('Connected') + ) + + if AppSettings.isStateON_('PowerSaveMode'): + # Power optimization + self.actionTimer.start(CORE_CHECK_ALIVE_INTERVAL * 2) + else: + self.actionTimer.start(CORE_CHECK_ALIVE_INTERVAL) + + self.doConnectedCallOnceOnly() else: logger.error('failed to start core manager') diff --git a/Furious/Widget/GuiCustomizeNetworkTest.py b/Furious/Widget/GuiCustomizeNetworkTest.py new file mode 100644 index 0000000..32d1ead --- /dev/null +++ b/Furious/Widget/GuiCustomizeNetworkTest.py @@ -0,0 +1,137 @@ +# 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 . + +from __future__ import annotations + +from Furious.Frozenlib import * +from Furious.Qt import * +from Furious.Qt import gettext as _ + +from PySide6 import QtCore +from PySide6.QtWidgets import * + +import logging +import functools + +__all__ = ['GuiCustomizeNetworkTestDialog'] + +logger = logging.getLogger(__name__) + +registerAppSettings('CustomNetworkSpeedTestURL') +registerAppSettings('CustomNetworkStateTestURL') + + +class GuiCustomizeNetworkTestDialog(AppQDialog): + def __init__(self, *args, **kwargs): + super().__init__(*args, **kwargs) + + self.setWindowTitle(_('Customize Network Test URL')) + self.setWindowModality(QtCore.Qt.WindowModality.WindowModal) + + self.speedTestURLText = AppQLabel(_('Enter network speed test URL:')) + self.speedTestURLEdit = QLineEdit() + + speedTestSettings = AppSettings.get('CustomNetworkSpeedTestURL') + + if speedTestSettings is None: + self.speedTestURLEdit.setText(NETWORK_SPEED_TEST_URL) + else: + self.speedTestURLEdit.setText(speedTestSettings) + + self.stateTestURLText = AppQLabel(_('Enter network state test URL:')) + self.stateTestURLEdit = QLineEdit() + + stateTestSettings = AppSettings.get('CustomNetworkStateTestURL') + + if stateTestSettings is None: + self.stateTestURLEdit.setText(NETWORK_STATE_TEST_URL) + else: + self.stateTestURLEdit.setText(stateTestSettings) + + self.dialogBtns = AppQDialogButtonBox(QtCore.Qt.Orientation.Horizontal) + self.dialogBtns.addButton(_('OK'), AppQDialogButtonBox.ButtonRole.AcceptRole) + self.dialogBtns.addButton( + _('Cancel'), AppQDialogButtonBox.ButtonRole.RejectRole + ) + self.dialogBtns.accepted.connect(self.accept) + self.dialogBtns.rejected.connect(self.reject) + + self.speedTestResetBtn = AppQPushButton(_('Reset')) + self.speedTestResetBtn.clicked.connect( + lambda: self.speedTestURLEdit.setText(NETWORK_SPEED_TEST_URL) + ) + self.stateTestResetBtn = AppQPushButton(_('Reset')) + self.stateTestResetBtn.clicked.connect( + lambda: self.stateTestURLEdit.setText(NETWORK_STATE_TEST_URL) + ) + + self.speedTestHboxLayout = QHBoxLayout() + self.speedTestHboxLayout.addWidget(self.speedTestURLEdit) + self.speedTestHboxLayout.addWidget(self.speedTestResetBtn) + + self.stateTestHboxLayout = QHBoxLayout() + self.stateTestHboxLayout.addWidget(self.stateTestURLEdit) + self.stateTestHboxLayout.addWidget(self.stateTestResetBtn) + + layout = QFormLayout() + layout.addRow(self.speedTestURLText) + layout.addRow(self.speedTestHboxLayout) + layout.addRow(self.stateTestURLText) + layout.addRow(self.stateTestHboxLayout) + layout.addRow(self.dialogBtns) + layout.setFormAlignment(QtCore.Qt.AlignmentFlag.AlignCenter) + + self.setLayout(layout) + + self.finished.connect(functools.partial(self.handleResultCode)) + + def setWidthAndHeight(self): + self.resize(656, 180) + + def handleResultCode(self, code): + for widget, info, settingsName, reset in [ + ( + self.speedTestURLEdit, + 'speed test URL', + 'CustomNetworkSpeedTestURL', + NETWORK_SPEED_TEST_URL, + ), + ( + self.stateTestURLEdit, + 'state test URL', + 'CustomNetworkStateTestURL', + NETWORK_STATE_TEST_URL, + ), + ]: + if code == PySide6Legacy.enumValueWrapper(AppQDialog.DialogCode.Accepted): + value = widget.text() + + logger.info(f'setting {info} to \'{value}\'') + + AppSettings.set(settingsName, value) + else: + try: + settings = AppSettings.get(settingsName) + + if settings is not None: + widget.setText(str(settings)) + else: + widget.setText(reset) + except Exception as ex: + # Any non-exit exceptions + + logger.error(f'error restoring {info}: {ex}') diff --git a/Furious/Widget/GuiCustomizeProxyBypass.py b/Furious/Widget/GuiCustomizeProxyBypass.py new file mode 100644 index 0000000..6251939 --- /dev/null +++ b/Furious/Widget/GuiCustomizeProxyBypass.py @@ -0,0 +1,107 @@ +# 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 . + +from __future__ import annotations + +from Furious.Frozenlib import * +from Furious.Qt import * +from Furious.Qt import gettext as _ + +from PySide6 import QtCore +from PySide6.QtWidgets import * + +import logging +import functools + +__all__ = ['GuiCustomizeProxyBypassDialog'] + +logger = logging.getLogger(__name__) + +registerAppSettings('CustomProxyBypass') + + +class GuiCustomizeProxyBypassDialog(AppQDialog): + def __init__(self, *args, **kwargs): + super().__init__(*args, **kwargs) + + self.setWindowTitle(_('Customize Proxy Bypass Address')) + self.setWindowModality(QtCore.Qt.WindowModality.WindowModal) + + self.proxyBypassText = AppQLabel( + _('Enter proxy bypass address (separated by semicolons):') + ) + self.proxyBypassEdit = QLineEdit() + + settings = AppSettings.get('CustomProxyBypass') + + if settings is None: + self.proxyBypassEdit.setText(PROXY_SERVER_BYPASS) + else: + self.proxyBypassEdit.setText(settings) + + self.dialogBtns = AppQDialogButtonBox(QtCore.Qt.Orientation.Horizontal) + self.dialogBtns.addButton(_('OK'), AppQDialogButtonBox.ButtonRole.AcceptRole) + self.dialogBtns.addButton( + _('Cancel'), AppQDialogButtonBox.ButtonRole.RejectRole + ) + self.dialogBtns.accepted.connect(self.accept) + self.dialogBtns.rejected.connect(self.reject) + + self.resetBtn = AppQPushButton(_('Reset')) + self.resetBtn.clicked.connect(functools.partial(self.handleResetButtonClicked)) + + self.hboxLayout = QHBoxLayout() + self.hboxLayout.addWidget(self.resetBtn) + self.hboxLayout.addWidget(self.dialogBtns) + + layout = QFormLayout() + layout.addRow(self.proxyBypassText) + layout.addRow(self.proxyBypassEdit) + layout.addRow(self.hboxLayout) + layout.setFormAlignment(QtCore.Qt.AlignmentFlag.AlignCenter) + + self.setLayout(layout) + + self.finished.connect(functools.partial(self.handleResultCode)) + + def setWidthAndHeight(self): + self.resize(656, 125) + + def handleResultCode(self, code): + if code == PySide6Legacy.enumValueWrapper(AppQDialog.DialogCode.Accepted): + value = self.proxyBypassEdit.text() + + logger.info(f'setting custom proxy bypass address to \'{value}\'') + + AppSettings.set('CustomProxyBypass', value) + + showMBoxNewChangesNextTime() + else: + try: + settings = AppSettings.get('CustomProxyBypass') + + if settings is not None: + self.proxyBypassEdit.setText(str(settings)) + else: + self.proxyBypassEdit.setText(PROXY_SERVER_BYPASS) + except Exception as ex: + # Any non-exit exceptions + + logger.error(f'error restoring proxy bypass address: {ex}') + + def handleResetButtonClicked(self): + self.proxyBypassEdit.setText(PROXY_SERVER_BYPASS) diff --git a/Furious/Widget/UserServersQTableWidget.py b/Furious/Widget/UserServersQTableWidget.py index ad50779..ea6f0cd 100644 --- a/Furious/Widget/UserServersQTableWidget.py +++ b/Furious/Widget/UserServersQTableWidget.py @@ -596,9 +596,15 @@ class TestDownloadSpeedWorker(WebGETManager): self.configureHttpProxy(f'127.0.0.1:{self.port}') - self.networkReply = self.webGET( - NETWORK_SPEED_TEST_URL, **self.kwargs - ) + # Use custom network speed test URL if possible + settings = AppSettings.get('CustomNetworkSpeedTestURL') + + if settings is None: + url = NETWORK_SPEED_TEST_URL + else: + url = settings + + self.networkReply = self.webGET(url, **self.kwargs) self.elapsedTimer.start() self.timeoutTimer.start(self.timeout) diff --git a/Furious/Widget/__init__.py b/Furious/Widget/__init__.py index 6899fd6..dd9bfce 100644 --- a/Furious/Widget/__init__.py +++ b/Furious/Widget/__init__.py @@ -17,6 +17,8 @@ from .Application import * from .ConnectProgressBar import * +from .GuiCustomizeNetworkTest import * +from .GuiCustomizeProxyBypass import * from .GuiHysteria1 import * from .GuiHysteria2 import * from .GuiShadowsocks import * diff --git a/Furious/Window/AppMainWindow.py b/Furious/Window/AppMainWindow.py index 3f1dbcf..27ee892 100644 --- a/Furious/Window/AppMainWindow.py +++ b/Furious/Window/AppMainWindow.py @@ -23,6 +23,8 @@ from Furious.Library import * from Furious.Qt import * from Furious.Qt import gettext as _ from Furious.Widget.UserServersQTableWidget import * +from Furious.Widget.GuiCustomizeNetworkTest import * +from Furious.Widget.GuiCustomizeProxyBypass import * from Furious.Widget.GuiTUNSettings import * from Furious.Window.UserSubsWindow import * from Furious.Window.XrayAssetViewerWindow import * @@ -129,6 +131,8 @@ class AppMainWindow(AppQMainWindow): ), ) self.xrayAssetViewerWindow = XrayAssetViewerWindow(parent=self) + self.customizeProxyBypassDialog = GuiCustomizeProxyBypassDialog(parent=self) + self.customizeNetworkTestDialog = GuiCustomizeNetworkTestDialog(parent=self) self.mainTab = AppQTabWidget() self.mainTab.addTab(self.userServersQTableWidget, _('Server')) @@ -248,7 +252,6 @@ class AppMainWindow(AppQMainWindow): checkable=False, callback=lambda: self.getGuiTUNSettings().open(), ), - AppQSeperator(), ] if PLATFORM == 'Windows' or PLATFORM == 'Darwin': @@ -285,6 +288,18 @@ class AppMainWindow(AppQMainWindow): toolsActions = [ *customizeTUNSettingsAction, + AppQAction( + _('Customize Proxy Bypass Address...'), + checkable=False, + callback=lambda: self.customizeProxyBypassDialog.open(), + ), + AppQAction( + _('Customize Network Test URL...'), + icon=bootstrapIcon('speedometer2.svg'), + checkable=False, + callback=lambda: self.customizeNetworkTestDialog.open(), + ), + AppQSeperator(), *restartAsAdminAction, *openAppFolderAction, AppQAction(