forked from Support/sp-hydra-veil-gui
update: update for vless and hysteria
This commit is contained in:
parent
2258eda245
commit
d8582c8d6d
6 changed files with 264 additions and 37 deletions
|
|
@ -66,6 +66,7 @@ class LocationPage(Page):
|
||||||
button.setChecked(False)
|
button.setChecked(False)
|
||||||
if hasattr(self, 'verification_button'):
|
if hasattr(self, 'verification_button'):
|
||||||
self.verification_button.setEnabled(False)
|
self.verification_button.setEnabled(False)
|
||||||
|
self.refresh_protocol_location_visibility()
|
||||||
|
|
||||||
def create_interface_elements(self, available_locations):
|
def create_interface_elements(self, available_locations):
|
||||||
|
|
||||||
|
|
@ -79,7 +80,7 @@ class LocationPage(Page):
|
||||||
boton.setCheckable(True)
|
boton.setCheckable(True)
|
||||||
|
|
||||||
locations = self.connection_manager.get_location_info(icon_name)
|
locations = self.connection_manager.get_location_info(icon_name)
|
||||||
if locations and not (hasattr(locations, 'is_wireguard_capable') and locations.is_wireguard_capable):
|
if locations and not self._location_supports_selected_protocol(locations):
|
||||||
boton.setVisible(False)
|
boton.setVisible(False)
|
||||||
icon_path = os.path.join(self.btn_path, f"button_{icon_name}.png")
|
icon_path = os.path.join(self.btn_path, f"button_{icon_name}.png")
|
||||||
boton.setIcon(QIcon(icon_path))
|
boton.setIcon(QIcon(icon_path))
|
||||||
|
|
@ -121,6 +122,30 @@ class LocationPage(Page):
|
||||||
boton.clicked.connect(
|
boton.clicked.connect(
|
||||||
lambda checked, loc=icon_name: self.show_location(loc))
|
lambda checked, loc=icon_name: self.show_location(loc))
|
||||||
|
|
||||||
|
def refresh_protocol_location_visibility(self):
|
||||||
|
for button in self.buttons:
|
||||||
|
location_key = getattr(button, 'location_icon_name', None)
|
||||||
|
locations = self.connection_manager.get_location_info(location_key)
|
||||||
|
button.setVisible(not locations or self._location_supports_selected_protocol(locations))
|
||||||
|
|
||||||
|
def _current_protocol(self):
|
||||||
|
profile_data = self.update_status.read_data()
|
||||||
|
return profile_data.get("protocol", "wireguard")
|
||||||
|
|
||||||
|
def _is_enabled_capability(self, value):
|
||||||
|
return value in (True, 1, "1", "true", "True")
|
||||||
|
|
||||||
|
def _location_supports_selected_protocol(self, locations):
|
||||||
|
capability_by_protocol = {
|
||||||
|
"wireguard": "is_wireguard_capable",
|
||||||
|
"hysteria2": "is_hysteria2_capable",
|
||||||
|
"vless": "is_vless_capable",
|
||||||
|
}
|
||||||
|
capability_name = capability_by_protocol.get(self._current_protocol())
|
||||||
|
if capability_name is None:
|
||||||
|
return True
|
||||||
|
return self._is_enabled_capability(getattr(locations, capability_name, False))
|
||||||
|
|
||||||
def update_swarp_json(self, get_connection=False):
|
def update_swarp_json(self, get_connection=False):
|
||||||
profile_data = self.update_status.read_data()
|
profile_data = self.update_status.read_data()
|
||||||
self.connection_type = profile_data.get("connection", "")
|
self.connection_type = profile_data.get("connection", "")
|
||||||
|
|
|
||||||
|
|
@ -307,15 +307,15 @@ class MenuPage(Page):
|
||||||
|
|
||||||
new_profile['location'] = location
|
new_profile['location'] = location
|
||||||
|
|
||||||
if protocol == 'wireguard':
|
if protocol in ('wireguard', 'hysteria2', 'vless'):
|
||||||
new_profile['protocol'] = 'wireguard'
|
new_profile['protocol'] = protocol
|
||||||
else:
|
else:
|
||||||
new_profile['protocol'] = 'hidetor'
|
new_profile['protocol'] = 'hidetor'
|
||||||
|
|
||||||
connection_type = 'browser-only' if isinstance(
|
connection_type = 'browser-only' if isinstance(
|
||||||
profile, SessionProfile) else 'system-wide'
|
profile, SessionProfile) else 'system-wide'
|
||||||
|
|
||||||
if protocol == 'wireguard':
|
if protocol in ('wireguard', 'hysteria2', 'vless'):
|
||||||
new_profile['connection'] = connection_type
|
new_profile['connection'] = connection_type
|
||||||
elif protocol == 'tor':
|
elif protocol == 'tor':
|
||||||
new_profile['connection'] = protocol
|
new_profile['connection'] = protocol
|
||||||
|
|
@ -575,7 +575,10 @@ class MenuPage(Page):
|
||||||
child_label.show()
|
child_label.show()
|
||||||
|
|
||||||
def get_icon_path(self, label_name, value, connection_type):
|
def get_icon_path(self, label_name, value, connection_type):
|
||||||
|
protocol = value.get('protocol', '')
|
||||||
if label_name == 'protocol':
|
if label_name == 'protocol':
|
||||||
|
if protocol in ('hysteria2', 'vless'):
|
||||||
|
return os.path.join(self.btn_path, f"{protocol}_mini.png")
|
||||||
if connection_type == 'tor':
|
if connection_type == 'tor':
|
||||||
return os.path.join(self.btn_path, "toricon_mini.png")
|
return os.path.join(self.btn_path, "toricon_mini.png")
|
||||||
elif connection_type == 'just proxy':
|
elif connection_type == 'just proxy':
|
||||||
|
|
@ -584,6 +587,8 @@ class MenuPage(Page):
|
||||||
return os.path.join(self.btn_path, "wireguard_mini.png")
|
return os.path.join(self.btn_path, "wireguard_mini.png")
|
||||||
elif label_name == 'browser':
|
elif label_name == 'browser':
|
||||||
if connection_type == 'system-wide':
|
if connection_type == 'system-wide':
|
||||||
|
if protocol in ('hysteria2', 'vless'):
|
||||||
|
return os.path.join(self.btn_path, "system_wide_global.png")
|
||||||
return os.path.join(self.btn_path, "wireguard_system_wide.png")
|
return os.path.join(self.btn_path, "wireguard_system_wide.png")
|
||||||
else:
|
else:
|
||||||
return os.path.join(self.btn_path, f"{value[label_name]} latest_mini.png")
|
return os.path.join(self.btn_path, f"{value[label_name]} latest_mini.png")
|
||||||
|
|
@ -841,17 +846,20 @@ class MenuPage(Page):
|
||||||
nostr_txt.show()
|
nostr_txt.show()
|
||||||
self.additional_labels.append(nostr_txt)
|
self.additional_labels.append(nostr_txt)
|
||||||
|
|
||||||
elif protocol.lower() in ["wireguard", "open", "residential", "hidetor"]:
|
elif protocol.lower() in ["wireguard", "open", "residential", "hidetor", "hysteria2", "vless"]:
|
||||||
label_principal = QLabel(self)
|
label_principal = QLabel(self)
|
||||||
label_principal.setGeometry(0, 90, 400, 300)
|
label_principal.setGeometry(0, 90, 400, 300)
|
||||||
pixmap = QPixmap(os.path.join(
|
pixmap = QPixmap(os.path.join(
|
||||||
self.btn_path, f"{protocol}_{location}.png"))
|
self.btn_path, f"{protocol}_{location}.png"))
|
||||||
|
if pixmap.isNull() and protocol.lower() in ["hysteria2", "vless"]:
|
||||||
|
pixmap = QPixmap(os.path.join(
|
||||||
|
self.btn_path, f"icon_{location}.png"))
|
||||||
label_principal.setPixmap(pixmap)
|
label_principal.setPixmap(pixmap)
|
||||||
label_principal.setScaledContents(True)
|
label_principal.setScaledContents(True)
|
||||||
label_principal.show()
|
label_principal.show()
|
||||||
self.additional_labels.append(label_principal)
|
self.additional_labels.append(label_principal)
|
||||||
|
|
||||||
if protocol.lower() in ["wireguard", "open", "residential", "hidetor"]:
|
if protocol.lower() in ["wireguard", "open", "residential", "hidetor", "hysteria2", "vless"]:
|
||||||
|
|
||||||
if protocol.lower() == "wireguard" and ConfigurationController.get_endpoint_verification_enabled():
|
if protocol.lower() == "wireguard" and ConfigurationController.get_endpoint_verification_enabled():
|
||||||
if is_profile_enabled and profile_obj and profile_obj.connection and profile_obj.connection.code == 'wireguard':
|
if is_profile_enabled and profile_obj and profile_obj.connection and profile_obj.connection.code == 'wireguard':
|
||||||
|
|
|
||||||
|
|
@ -4,13 +4,14 @@ from PyQt6.QtWidgets import QLabel, QPushButton, QWidget
|
||||||
from PyQt6.QtGui import QPixmap
|
from PyQt6.QtGui import QPixmap
|
||||||
from PyQt6.QtCore import Qt, QTimer
|
from PyQt6.QtCore import Qt, QTimer
|
||||||
|
|
||||||
from core.services.helpers.setup_sudo_scripts import auto_install_sudo_script, test_if_in_sudo_folder
|
from core.services.helpers.setup_sudo_scripts import auto_install_sudo_script, test_if_in_sudo_folder, is_singbox_wrapper_ready
|
||||||
from core.services.helpers.manage_assets import sudo_assets_folder_setup
|
from core.services.helpers.manage_assets import sudo_assets_folder_setup
|
||||||
from core.models.Result import Result, ResultError
|
from core.models.Result import Result, ResultError
|
||||||
|
|
||||||
import sys
|
import sys
|
||||||
|
|
||||||
from gui.v2.ui.pages.Page import Page
|
from gui.v2.ui.pages.Page import Page
|
||||||
|
from gui.v2.workers.worker_thread import WorkerThread
|
||||||
|
|
||||||
|
|
||||||
class NetworkingSetupPage(Page):
|
class NetworkingSetupPage(Page):
|
||||||
|
|
@ -19,6 +20,10 @@ class NetworkingSetupPage(Page):
|
||||||
self.btn_path = main_window.btn_path
|
self.btn_path = main_window.btn_path
|
||||||
self.update_status = main_window
|
self.update_status = main_window
|
||||||
self.manual_scripts_ready = False
|
self.manual_scripts_ready = False
|
||||||
|
self.singbox_completion = None
|
||||||
|
self.singbox_setup_started = False
|
||||||
|
self.singbox_worker = None
|
||||||
|
self.latest_singbox_message = ""
|
||||||
self.setStyleSheet("font-family: Arial;")
|
self.setStyleSheet("font-family: Arial;")
|
||||||
self.button_next.clicked.disconnect(self.gestionar_next)
|
self.button_next.clicked.disconnect(self.gestionar_next)
|
||||||
self.button_next.setVisible(True)
|
self.button_next.setVisible(True)
|
||||||
|
|
@ -29,29 +34,32 @@ class NetworkingSetupPage(Page):
|
||||||
super().showEvent(event)
|
super().showEvent(event)
|
||||||
self.button_next.setVisible(True)
|
self.button_next.setVisible(True)
|
||||||
self.button_next.raise_()
|
self.button_next.raise_()
|
||||||
|
if self._is_singbox_mode():
|
||||||
|
self.button_next.setVisible(False)
|
||||||
|
QTimer.singleShot(0, self.prepare_singbox_prereqs)
|
||||||
|
|
||||||
def _setup_ui(self):
|
def _setup_ui(self):
|
||||||
header_icon = self._icon_label("networking_shield.png", 48, self)
|
header_icon = self._icon_label("networking_shield.png", 48, self)
|
||||||
header_icon.setGeometry(74, 66, 48, 48)
|
header_icon.setGeometry(74, 66, 48, 48)
|
||||||
|
|
||||||
title = QLabel("Firewall & Managed-DNS Setup", self)
|
self.title_label = QLabel("Firewall & Managed-DNS Setup", self)
|
||||||
title.setGeometry(138, 68, 590, 44)
|
self.title_label.setGeometry(138, 68, 590, 44)
|
||||||
title.setStyleSheet("font-family: Arial; font-size: 26px; font-weight: bold; color: cyan;")
|
self.title_label.setStyleSheet("font-family: Arial; font-size: 26px; font-weight: bold; color: cyan;")
|
||||||
|
|
||||||
subtitle = QLabel(
|
self.subtitle_label = QLabel(
|
||||||
"Optional privileged networking helpers",
|
"Optional privileged networking helpers",
|
||||||
self)
|
self)
|
||||||
subtitle.setGeometry(140, 108, 560, 24)
|
self.subtitle_label.setGeometry(140, 108, 560, 24)
|
||||||
subtitle.setStyleSheet("font-family: Arial; font-size: 14px; color: #d8ffff;")
|
self.subtitle_label.setStyleSheet("font-family: Arial; font-size: 14px; color: #d8ffff;")
|
||||||
|
|
||||||
description = QLabel(
|
self.description_label = QLabel(
|
||||||
"These firewall and managed-DNS helpers are separate bash scripts. "
|
"These firewall and managed-DNS helpers are separate bash scripts. "
|
||||||
"They stay outside the AppImage Python runtime, and if installed they are placed "
|
"They stay outside the AppImage Python runtime, and if installed they are placed "
|
||||||
"in sudo-protected folders owned by root.",
|
"in sudo-protected folders owned by root.",
|
||||||
self)
|
self)
|
||||||
description.setGeometry(80, 152, 640, 72)
|
self.description_label.setGeometry(80, 152, 640, 72)
|
||||||
description.setWordWrap(True)
|
self.description_label.setWordWrap(True)
|
||||||
description.setStyleSheet("font-family: Arial; font-size: 15px; color: cyan;")
|
self.description_label.setStyleSheet("font-family: Arial; font-size: 15px; color: cyan;")
|
||||||
|
|
||||||
manual_panel = self._option_panel(74, 248, "Option 1", "Manual review")
|
manual_panel = self._option_panel(74, 248, "Option 1", "Manual review")
|
||||||
manual_text = QLabel(
|
manual_text = QLabel(
|
||||||
|
|
@ -152,6 +160,90 @@ class NetworkingSetupPage(Page):
|
||||||
def _result_message(self, result: Result, fallback):
|
def _result_message(self, result: Result, fallback):
|
||||||
return getattr(result, "message", fallback)
|
return getattr(result, "message", fallback)
|
||||||
|
|
||||||
|
def _is_singbox_mode(self):
|
||||||
|
return self.singbox_completion is not None
|
||||||
|
|
||||||
|
def configure_for_singbox_profile(self, completion_callback):
|
||||||
|
self.singbox_completion = completion_callback
|
||||||
|
self.singbox_setup_started = False
|
||||||
|
self.singbox_worker = None
|
||||||
|
self.latest_singbox_message = ""
|
||||||
|
self.manual_scripts_ready = False
|
||||||
|
self.title_label.setText("Singbox Prerequisites")
|
||||||
|
self.subtitle_label.setText("Required for Hysteria2 and VLESS systemwide profiles")
|
||||||
|
self.description_label.setText(
|
||||||
|
"Hysteria2 and VLESS need the firewall, managed-DNS, Singbox wrapper, "
|
||||||
|
"and Singbox binary before the profile can be created.")
|
||||||
|
self.manual_button.setText("Get scripts")
|
||||||
|
self.manual_button.setDisabled(False)
|
||||||
|
self.auto_button.setDisabled(False)
|
||||||
|
self.button_next.setVisible(False)
|
||||||
|
self._set_status("Checking Singbox prerequisites...")
|
||||||
|
return self
|
||||||
|
|
||||||
|
def _sudo_scripts_ready_for_singbox(self):
|
||||||
|
if not is_singbox_wrapper_ready():
|
||||||
|
return False
|
||||||
|
confirmed = test_if_in_sudo_folder()
|
||||||
|
return confirmed.valid
|
||||||
|
|
||||||
|
def prepare_singbox_prereqs(self):
|
||||||
|
if not self._is_singbox_mode():
|
||||||
|
return
|
||||||
|
if not self._sudo_scripts_ready_for_singbox():
|
||||||
|
self.manual_button.setDisabled(False)
|
||||||
|
self.auto_button.setDisabled(False)
|
||||||
|
self.button_next.setVisible(False)
|
||||||
|
self._set_status("Firewall, managed-DNS, and the Singbox wrapper are required. Choose manual or automated setup.", "#d8ffff")
|
||||||
|
return
|
||||||
|
self.start_singbox_binary_setup()
|
||||||
|
|
||||||
|
def start_singbox_binary_setup(self):
|
||||||
|
if self.singbox_setup_started:
|
||||||
|
return
|
||||||
|
self.singbox_setup_started = True
|
||||||
|
self.latest_singbox_message = ""
|
||||||
|
self.manual_button.setDisabled(True)
|
||||||
|
self.auto_button.setDisabled(True)
|
||||||
|
self.button_next.setVisible(False)
|
||||||
|
self._set_status("Sudo wrapper scripts are ready. Setting up Singbox binary...")
|
||||||
|
self.singbox_worker = WorkerThread('SETUP_SINGBOX_BINARY')
|
||||||
|
self.singbox_worker.text_output.connect(self._set_singbox_status)
|
||||||
|
self.singbox_worker.finished.connect(self.on_singbox_setup_finished)
|
||||||
|
self.singbox_worker.start()
|
||||||
|
|
||||||
|
def _set_singbox_status(self, message):
|
||||||
|
self.latest_singbox_message = message
|
||||||
|
self._set_status(message)
|
||||||
|
|
||||||
|
def on_singbox_setup_finished(self, success):
|
||||||
|
if success:
|
||||||
|
self._set_status("Singbox setup complete. Creating profile...", "#2ecc71")
|
||||||
|
QTimer.singleShot(600, self.finish_singbox_prereq_flow)
|
||||||
|
return
|
||||||
|
self.singbox_setup_started = False
|
||||||
|
self.manual_button.setDisabled(False)
|
||||||
|
self.auto_button.setDisabled(False)
|
||||||
|
self.button_next.setVisible(True)
|
||||||
|
self._set_status(self.latest_singbox_message or "Singbox setup failed.", "#ff6b6b")
|
||||||
|
|
||||||
|
def finish_singbox_prereq_flow(self):
|
||||||
|
completion_callback = self.singbox_completion
|
||||||
|
self.singbox_completion = None
|
||||||
|
self.singbox_setup_started = False
|
||||||
|
self.latest_singbox_message = ""
|
||||||
|
self.title_label.setText("Firewall & Managed-DNS Setup")
|
||||||
|
self.subtitle_label.setText("Optional privileged networking helpers")
|
||||||
|
self.description_label.setText(
|
||||||
|
"These firewall and managed-DNS helpers are separate bash scripts. "
|
||||||
|
"They stay outside the AppImage Python runtime, and if installed they are placed "
|
||||||
|
"in sudo-protected folders owned by root.")
|
||||||
|
self.manual_button.setText("Get scripts")
|
||||||
|
self.manual_button.setDisabled(False)
|
||||||
|
self.auto_button.setDisabled(False)
|
||||||
|
if completion_callback is not None:
|
||||||
|
completion_callback()
|
||||||
|
|
||||||
def handle_manual_setup(self):
|
def handle_manual_setup(self):
|
||||||
if self.manual_scripts_ready:
|
if self.manual_scripts_ready:
|
||||||
self.confirm_sudo_scripts()
|
self.confirm_sudo_scripts()
|
||||||
|
|
@ -169,6 +261,10 @@ class NetworkingSetupPage(Page):
|
||||||
def confirm_sudo_scripts(self):
|
def confirm_sudo_scripts(self):
|
||||||
confirmed = test_if_in_sudo_folder()
|
confirmed = test_if_in_sudo_folder()
|
||||||
if confirmed.valid:
|
if confirmed.valid:
|
||||||
|
if self._is_singbox_mode():
|
||||||
|
self._set_status("Confirmed it worked. Continuing to Singbox setup.", "#2ecc71")
|
||||||
|
QTimer.singleShot(600, self.start_singbox_binary_setup)
|
||||||
|
return
|
||||||
self._set_status("Confirmed it worked. Continuing to prerequisite installation.", "#2ecc71")
|
self._set_status("Confirmed it worked. Continuing to prerequisite installation.", "#2ecc71")
|
||||||
QTimer.singleShot(600, self.go_to_install)
|
QTimer.singleShot(600, self.go_to_install)
|
||||||
else:
|
else:
|
||||||
|
|
@ -180,6 +276,10 @@ class NetworkingSetupPage(Page):
|
||||||
if results.valid:
|
if results.valid:
|
||||||
confirmed = test_if_in_sudo_folder()
|
confirmed = test_if_in_sudo_folder()
|
||||||
if confirmed.valid:
|
if confirmed.valid:
|
||||||
|
if self._is_singbox_mode():
|
||||||
|
self._set_status("The setup script ran successfully. Continuing to Singbox setup.", "#2ecc71")
|
||||||
|
QTimer.singleShot(600, self.start_singbox_binary_setup)
|
||||||
|
return
|
||||||
self._set_status("The setup script ran successfully. Continuing to prerequisite installation.", "#2ecc71")
|
self._set_status("The setup script ran successfully. Continuing to prerequisite installation.", "#2ecc71")
|
||||||
QTimer.singleShot(600, self.go_to_install)
|
QTimer.singleShot(600, self.go_to_install)
|
||||||
else:
|
else:
|
||||||
|
|
@ -190,6 +290,9 @@ class NetworkingSetupPage(Page):
|
||||||
f"Setup script did NOT work because {results.message}", "#ff6b6b")
|
f"Setup script did NOT work because {results.message}", "#ff6b6b")
|
||||||
|
|
||||||
def go_to_install(self):
|
def go_to_install(self):
|
||||||
|
if self._is_singbox_mode():
|
||||||
|
self.prepare_singbox_prereqs()
|
||||||
|
return
|
||||||
self.custom_window.navigator.navigate("install_system_package")
|
self.custom_window.navigator.navigate("install_system_package")
|
||||||
install_page = self.custom_window.navigator.get_cached("install_system_package")
|
install_page = self.custom_window.navigator.get_cached("install_system_package")
|
||||||
if install_page is not None:
|
if install_page is not None:
|
||||||
|
|
|
||||||
|
|
@ -9,6 +9,11 @@ from gui.v2.ui.pages.Page import Page
|
||||||
|
|
||||||
|
|
||||||
class ProtocolPage(Page):
|
class ProtocolPage(Page):
|
||||||
|
SINGBOX_PROTOCOLS = ("hysteria2", "vless")
|
||||||
|
PROTOCOL_BUTTON_ASSETS = {
|
||||||
|
"hysteria2": "hystria2",
|
||||||
|
}
|
||||||
|
|
||||||
def __init__(self, page_stack, main_window=None, parent=None):
|
def __init__(self, page_stack, main_window=None, parent=None):
|
||||||
super().__init__("Protocol", page_stack, main_window, parent)
|
super().__init__("Protocol", page_stack, main_window, parent)
|
||||||
self.main_window = main_window
|
self.main_window = main_window
|
||||||
|
|
@ -36,9 +41,11 @@ class ProtocolPage(Page):
|
||||||
self.buttons = []
|
self.buttons = []
|
||||||
self.selected_page_name = None
|
self.selected_page_name = None
|
||||||
for j, (object_type, icon_name, page_name, geometry) in enumerate([
|
for j, (object_type, icon_name, page_name, geometry) in enumerate([
|
||||||
(QPushButton, "wireguard", "wireguard", (585, 90, 185, 75)),
|
(QPushButton, "wireguard", "wireguard", (585, 80, 185, 75)),
|
||||||
(QPushButton, "residential", "residential", (585, 90+30+75, 185, 75)),
|
(QPushButton, "hysteria2", "location", (585, 160, 185, 75)),
|
||||||
(QPushButton, "hidetor", "hidetor", (585, 90+30+75+30+75, 185, 75))
|
(QPushButton, "vless", "location", (585, 240, 185, 75)),
|
||||||
|
(QPushButton, "residential", "residential", (585, 320, 185, 75)),
|
||||||
|
(QPushButton, "hidetor", "hidetor", (585, 400, 185, 75))
|
||||||
]):
|
]):
|
||||||
boton = object_type(self)
|
boton = object_type(self)
|
||||||
boton.setGeometry(*geometry)
|
boton.setGeometry(*geometry)
|
||||||
|
|
@ -46,28 +53,40 @@ class ProtocolPage(Page):
|
||||||
boton.setCheckable(True)
|
boton.setCheckable(True)
|
||||||
boton.setDisabled(True)
|
boton.setDisabled(True)
|
||||||
boton.setIcon(
|
boton.setIcon(
|
||||||
QIcon(os.path.join(self.btn_path, f"{icon_name}_button.png")))
|
QIcon(self._button_asset(icon_name)))
|
||||||
self.buttons.append(boton)
|
self.buttons.append(boton)
|
||||||
self.buttonGroup.addButton(boton, j)
|
self.buttonGroup.addButton(boton, j)
|
||||||
boton.clicked.connect(
|
boton.clicked.connect(
|
||||||
lambda _, name=page_name, protocol=icon_name: self.show_protocol(name, protocol))
|
lambda _, name=page_name, protocol=icon_name: self.show_protocol(name, protocol))
|
||||||
|
|
||||||
|
def _button_asset(self, protocol):
|
||||||
|
asset_name = self.PROTOCOL_BUTTON_ASSETS.get(protocol, protocol)
|
||||||
|
return os.path.join(self.btn_path, f"{asset_name}_button.png")
|
||||||
|
|
||||||
|
def _display_asset(self, protocol):
|
||||||
|
full_size_path = os.path.join(self.btn_path, f"{protocol}.png")
|
||||||
|
if os.path.exists(full_size_path):
|
||||||
|
return full_size_path
|
||||||
|
return self._button_asset(protocol)
|
||||||
|
|
||||||
def enable_protocol_buttons(self):
|
def enable_protocol_buttons(self):
|
||||||
for button in self.buttons:
|
for button in self.buttons:
|
||||||
button.setDisabled(False)
|
button.setDisabled(False)
|
||||||
|
|
||||||
def update_swarp_json(self):
|
def update_swarp_json(self):
|
||||||
self.update_status.write_data(
|
data = {"protocol": self.selected_protocol_icon}
|
||||||
{"protocol": self.selected_protocol_icon})
|
if self.selected_protocol_icon in self.SINGBOX_PROTOCOLS:
|
||||||
|
data["connection"] = "system-wide"
|
||||||
|
self.update_status.write_data(data)
|
||||||
|
|
||||||
def show_protocol(self, page_name, protocol):
|
def show_protocol(self, page_name, protocol):
|
||||||
self.update_status.clear_data()
|
self.update_status.clear_data()
|
||||||
self.display.setPixmap(QPixmap(os.path.join(self.btn_path, f"{protocol}.png")).scaled(
|
self.display.setPixmap(QPixmap(self._display_asset(protocol)).scaled(
|
||||||
self.display.size(), Qt.AspectRatioMode.KeepAspectRatio))
|
self.display.size(), Qt.AspectRatioMode.KeepAspectRatio))
|
||||||
self.selected_protocol_icon = protocol
|
self.selected_protocol_icon = protocol
|
||||||
self.selected_page_name = page_name
|
self.selected_page_name = page_name
|
||||||
|
|
||||||
if protocol in ["wireguard", "hidetor"]:
|
if protocol in ["wireguard", "hidetor", *self.SINGBOX_PROTOCOLS]:
|
||||||
self.button_go.setVisible(True)
|
self.button_go.setVisible(True)
|
||||||
self.coming_soon_label.setVisible(False)
|
self.coming_soon_label.setVisible(False)
|
||||||
else:
|
else:
|
||||||
|
|
|
||||||
|
|
@ -16,6 +16,11 @@ from gui.v2.ui.pages.screen_page import ScreenPage
|
||||||
|
|
||||||
|
|
||||||
class ResumePage(Page):
|
class ResumePage(Page):
|
||||||
|
SINGBOX_PROTOCOLS = ("hysteria2", "vless")
|
||||||
|
PROTOCOL_BUTTON_ASSETS = {
|
||||||
|
"hysteria2": "hystria2",
|
||||||
|
}
|
||||||
|
|
||||||
def __init__(self, page_stack, main_window=None, parent=None):
|
def __init__(self, page_stack, main_window=None, parent=None):
|
||||||
super().__init__("Resume", page_stack, main_window, parent)
|
super().__init__("Resume", page_stack, main_window, parent)
|
||||||
self.update_status = main_window
|
self.update_status = main_window
|
||||||
|
|
@ -199,6 +204,9 @@ class ResumePage(Page):
|
||||||
parent_label.setPixmap(base_image)
|
parent_label.setPixmap(base_image)
|
||||||
parent_label.show()
|
parent_label.show()
|
||||||
self.labels_creados.append(parent_label)
|
self.labels_creados.append(parent_label)
|
||||||
|
else:
|
||||||
|
if item == 'protocol':
|
||||||
|
icon_path = self._protocol_button_asset(text)
|
||||||
else:
|
else:
|
||||||
icon_path = os.path.join(
|
icon_path = os.path.join(
|
||||||
self.btn_path, f"{text}_button.png")
|
self.btn_path, f"{text}_button.png")
|
||||||
|
|
@ -278,8 +286,8 @@ class ResumePage(Page):
|
||||||
|
|
||||||
elif connection_exists:
|
elif connection_exists:
|
||||||
if profile_1.get("connection", "") == "system-wide":
|
if profile_1.get("connection", "") == "system-wide":
|
||||||
image_path = os.path.join(
|
image_path = self._system_profile_image(
|
||||||
self.btn_path, f"wireguard_{profile_1.get('location', '')}.png")
|
profile_1.get('protocol', 'wireguard'), profile_1.get('location', ''))
|
||||||
main_label = QLabel(self)
|
main_label = QLabel(self)
|
||||||
main_label.setGeometry(10, 130, 500, 375)
|
main_label.setGeometry(10, 130, 500, 375)
|
||||||
main_label.setPixmap(QPixmap(image_path))
|
main_label.setPixmap(QPixmap(image_path))
|
||||||
|
|
@ -336,6 +344,21 @@ class ResumePage(Page):
|
||||||
if hasattr(self, 'arrow_label'):
|
if hasattr(self, 'arrow_label'):
|
||||||
self.arrow_label.raise_()
|
self.arrow_label.raise_()
|
||||||
|
|
||||||
|
def _protocol_button_asset(self, protocol):
|
||||||
|
asset_name = self.PROTOCOL_BUTTON_ASSETS.get(protocol, protocol)
|
||||||
|
return os.path.join(self.btn_path, f"{asset_name}_button.png")
|
||||||
|
|
||||||
|
def _system_profile_image(self, protocol, location):
|
||||||
|
candidates = [
|
||||||
|
os.path.join(self.btn_path, f"{protocol}_{location}.png"),
|
||||||
|
os.path.join(self.btn_path, f"icon_{location}.png"),
|
||||||
|
os.path.join(self.btn_path, "system_wide_global.png"),
|
||||||
|
]
|
||||||
|
for candidate in candidates:
|
||||||
|
if os.path.exists(candidate):
|
||||||
|
return candidate
|
||||||
|
return candidates[-1]
|
||||||
|
|
||||||
def toggle_button_visibility(self):
|
def toggle_button_visibility(self):
|
||||||
self.button_go.setVisible(bool(self.line_edit.text()))
|
self.button_go.setVisible(bool(self.line_edit.text()))
|
||||||
|
|
||||||
|
|
@ -344,10 +367,6 @@ class ResumePage(Page):
|
||||||
|
|
||||||
def copy_profile(self):
|
def copy_profile(self):
|
||||||
profile_name = self.line_edit.text()
|
profile_name = self.line_edit.text()
|
||||||
menu_page = self.find_menu_page()
|
|
||||||
if menu_page:
|
|
||||||
number_of_profiles = menu_page.number_of_profiles
|
|
||||||
|
|
||||||
profile_data = self.update_status.read_data()
|
profile_data = self.update_status.read_data()
|
||||||
|
|
||||||
required_fields = [profile_data.get("protocol"), profile_name]
|
required_fields = [profile_data.get("protocol"), profile_name]
|
||||||
|
|
@ -361,13 +380,29 @@ class ResumePage(Page):
|
||||||
profiles = ProfileController.get_all()
|
profiles = ProfileController.get_all()
|
||||||
profile_id = self.get_next_available_id(profiles)
|
profile_id = self.get_next_available_id(profiles)
|
||||||
new_profile = profile_data
|
new_profile = profile_data
|
||||||
|
existing_profile_ids = tuple(profiles.keys())
|
||||||
|
|
||||||
|
if new_profile.get('protocol') in self.SINGBOX_PROTOCOLS:
|
||||||
|
self.show_singbox_prereq_setup(new_profile, profile_id, existing_profile_ids)
|
||||||
|
return
|
||||||
|
|
||||||
|
self.finish_profile_creation(new_profile, profile_id, existing_profile_ids)
|
||||||
|
|
||||||
|
def show_singbox_prereq_setup(self, profile, profile_id, existing_profile_ids):
|
||||||
|
setup_page = self.custom_window.navigator.navigate("networking_setup")
|
||||||
|
if setup_page is None:
|
||||||
|
self.update_status.update_status("Singbox prerequisite page is unavailable.")
|
||||||
|
return
|
||||||
|
setup_page.configure_for_singbox_profile(
|
||||||
|
lambda: self.finish_profile_creation(profile, profile_id, existing_profile_ids))
|
||||||
|
|
||||||
|
def finish_profile_creation(self, new_profile, profile_id, existing_profile_ids):
|
||||||
self.create_core_profiles(new_profile, profile_id)
|
self.create_core_profiles(new_profile, profile_id)
|
||||||
if ProfileController.get(profile_id) is not None:
|
if ProfileController.get(profile_id) is not None:
|
||||||
append_profile_to_visual_order(
|
append_profile_to_visual_order(
|
||||||
getattr(self.update_status, 'gui_config_file', None),
|
getattr(self.update_status, 'gui_config_file', None),
|
||||||
profile_id,
|
profile_id,
|
||||||
profiles.keys())
|
existing_profile_ids)
|
||||||
|
|
||||||
main = self.update_status
|
main = self.update_status
|
||||||
if hasattr(main, 'navigate_after_profile_created'):
|
if hasattr(main, 'navigate_after_profile_created'):
|
||||||
|
|
@ -376,7 +411,6 @@ class ResumePage(Page):
|
||||||
self.custom_window.navigator.navigate("menu")
|
self.custom_window.navigator.navigate("menu")
|
||||||
|
|
||||||
self.update_status.clear_data()
|
self.update_status.clear_data()
|
||||||
|
|
||||||
self.line_edit.clear()
|
self.line_edit.clear()
|
||||||
self.display.clear()
|
self.display.clear()
|
||||||
self.button_go.setVisible(False)
|
self.button_go.setVisible(False)
|
||||||
|
|
@ -408,8 +442,8 @@ class ResumePage(Page):
|
||||||
parts = profile.get('location').split('_')
|
parts = profile.get('location').split('_')
|
||||||
country_code = parts[0]
|
country_code = parts[0]
|
||||||
location_code = parts[1]
|
location_code = parts[1]
|
||||||
if profile.get('protocol') == 'wireguard':
|
if profile.get('protocol') in ('wireguard', 'hysteria2', 'vless'):
|
||||||
connection_type = 'wireguard'
|
connection_type = profile.get('protocol')
|
||||||
elif profile.get('protocol') == 'hidetor' or profile.get('protocol') == 'residential':
|
elif profile.get('protocol') == 'hidetor' or profile.get('protocol') == 'residential':
|
||||||
if profile.get('connection') == 'tor':
|
if profile.get('connection') == 'tor':
|
||||||
connection_type = 'tor'
|
connection_type = 'tor'
|
||||||
|
|
|
||||||
|
|
@ -20,8 +20,10 @@ from core.models.BaseProfile import ProfileType
|
||||||
from core.models.system.SystemProfile import SystemProfile
|
from core.models.system.SystemProfile import SystemProfile
|
||||||
from core.errors.exceptions import SudoScript, MissingPreReqs, FirewallError
|
from core.errors.exceptions import SudoScript, MissingPreReqs, FirewallError
|
||||||
from core.models.Result import Result, ResultError
|
from core.models.Result import Result, ResultError
|
||||||
|
from core.services.helpers.install_dependencies import setup_singbox_binary as install_singbox_binary
|
||||||
|
|
||||||
from gui.v2.infrastructure.setup_observers import (
|
from gui.v2.infrastructure.setup_observers import (
|
||||||
|
application_version_observer,
|
||||||
client_observer,
|
client_observer,
|
||||||
connection_observer,
|
connection_observer,
|
||||||
invoice_observer,
|
invoice_observer,
|
||||||
|
|
@ -69,6 +71,8 @@ class WorkerThread(QThread):
|
||||||
self.disable_all_profiles()
|
self.disable_all_profiles()
|
||||||
elif self.action == 'INSTALL_PACKAGE':
|
elif self.action == 'INSTALL_PACKAGE':
|
||||||
self.install_package()
|
self.install_package()
|
||||||
|
elif self.action == 'SETUP_SINGBOX_BINARY':
|
||||||
|
self.setup_singbox_binary()
|
||||||
elif self.action == 'CHECK_FOR_UPDATE':
|
elif self.action == 'CHECK_FOR_UPDATE':
|
||||||
self.check_for_update()
|
self.check_for_update()
|
||||||
elif self.action == 'DOWNLOAD_UPDATE':
|
elif self.action == 'DOWNLOAD_UPDATE':
|
||||||
|
|
@ -124,6 +128,40 @@ class WorkerThread(QThread):
|
||||||
f"An error occurred when installing {self.package_name}: {e}")
|
f"An error occurred when installing {self.package_name}: {e}")
|
||||||
self.finished.emit(False)
|
self.finished.emit(False)
|
||||||
|
|
||||||
|
def setup_singbox_binary(self):
|
||||||
|
connection_error = "Connection problems downloading Singbox or related data. Please disable Tor or try again with a better connection."
|
||||||
|
try:
|
||||||
|
setup_result = install_singbox_binary(application_version_observer, connection_observer)
|
||||||
|
except ConnectionError as e:
|
||||||
|
self.text_output.emit(f"{connection_error}: {str(e)}")
|
||||||
|
self.finished.emit(False)
|
||||||
|
return
|
||||||
|
except ValueError as e:
|
||||||
|
self.text_output.emit(f"Your configuration files may be corrupted, or a server-side error gave bad data: {str(e)}")
|
||||||
|
self.finished.emit(False)
|
||||||
|
return
|
||||||
|
except Exception as e:
|
||||||
|
self.text_output.emit(f"Unknown error: {str(e)}")
|
||||||
|
self.finished.emit(False)
|
||||||
|
return
|
||||||
|
|
||||||
|
if setup_result.valid:
|
||||||
|
self.text_output.emit("Setup done. You're all set to proceed with Singbox.")
|
||||||
|
self.finished.emit(True)
|
||||||
|
return
|
||||||
|
|
||||||
|
messages = {
|
||||||
|
ResultError.NEED_SYNC: "You must sync to find out which Singbox version is supported.",
|
||||||
|
ResultError.FILE_SYSTEM: "Please check the configuration file, disk space, permissions, and filesystem health.",
|
||||||
|
ResultError.CONNECTION: connection_error,
|
||||||
|
ResultError.INVALID_INPUT: "This is a rare bug. Check the error logs, then run the program again from the terminal with DEBUG=true.",
|
||||||
|
ResultError.PERMISSION: "Singbox requires sudo for setup. After that, the wrapper allows it to run without sudo on an ongoing basis.",
|
||||||
|
ResultError.MISSING_FILE: "Singbox download or file setup did not complete. Please try again.",
|
||||||
|
ResultError.UNKNOWN: f"Unknown error: {setup_result.message}",
|
||||||
|
}
|
||||||
|
self.text_output.emit(messages.get(setup_result.error_type, setup_result.message or "Unknown Singbox setup error"))
|
||||||
|
self.finished.emit(False)
|
||||||
|
|
||||||
def disable_all_profiles(self):
|
def disable_all_profiles(self):
|
||||||
try:
|
try:
|
||||||
for profile_id in self.profile_data:
|
for profile_id in self.profile_data:
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue