update: update for vless and hysteria

This commit is contained in:
JOhn 2026-08-19 20:30:15 -04:00
parent 2258eda245
commit d8582c8d6d
6 changed files with 264 additions and 37 deletions

View file

@ -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", "")

View file

@ -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':

View file

@ -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:

View file

@ -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:

View file

@ -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'

View file

@ -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: