Compare commits

..

No commits in common. "8a0a1fc74d8f67e498500bd05d4ce89bfb42dc03" and "2258eda245d93a80f82efc534107d7612711a4d5" have entirely different histories.

30 changed files with 142 additions and 575 deletions

View file

@ -61,7 +61,6 @@ from gui.v2.actions.profile_data import (
clear_profile_data, clear_profile_data,
) )
from gui.v2.actions.database_health import GuiStorageDatabaseError from gui.v2.actions.database_health import GuiStorageDatabaseError
from gui.v2.actions.profile_status import is_profile_enabled_for_gui
from gui.v2.actions.ticket_failure import ( from gui.v2.actions.ticket_failure import (
save_ticket_verification_failure, save_ticket_verification_failure,
get_ticket_verification_failure, get_ticket_verification_failure,
@ -675,7 +674,7 @@ class CustomWindow(QMainWindow):
self.connection_manager._connected_profiles.clear() self.connection_manager._connected_profiles.clear()
for profile_id, profile in profiles.items(): for profile_id, profile in profiles.items():
try: try:
if is_profile_enabled_for_gui(profile): if ProfileController.is_enabled(profile):
connected_profiles.append(profile_id) connected_profiles.append(profile_id)
self.connection_manager.add_connected_profile(profile_id) self.connection_manager.add_connected_profile(profile_id)
except Exception: except Exception:

Binary file not shown.

After

Width:  |  Height:  |  Size: 37 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 259 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 258 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 259 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 87 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 58 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 58 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 220 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 219 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 220 KiB

View file

@ -1,27 +0,0 @@
from core.controllers.ProfileController import ProfileController
from core.controllers.SystemStateController import SystemStateController
from core.models.system.SystemProfile import SystemProfile
SINGBOX_PROTOCOLS = ("hysteria2", "vless")
def _connection_code(profile):
connection = getattr(profile, "connection", None)
return getattr(connection, "code", None)
def _system_state_profile_id():
state = SystemStateController.get()
if state is None:
return None
try:
return int(state.profile_id)
except (TypeError, ValueError):
return None
def is_profile_enabled_for_gui(profile):
if isinstance(profile, SystemProfile) and _connection_code(profile) in SINGBOX_PROTOCOLS:
return _system_state_profile_id() == int(profile.id)
return ProfileController.is_enabled(profile)

View file

@ -1,20 +0,0 @@
from core.services.helpers.install_dependencies import SUDO_SINGBOX_LOCATION
from core.services.helpers.setup_sudo_scripts import (
is_singbox_wrapper_ready,
test_if_in_sudo_folder,
)
from core.utils.basic_operations.does_file_exist import does_file_exist
def singbox_prereqs_installed():
try:
if not is_singbox_wrapper_ready():
return False
sudo_scripts = test_if_in_sudo_folder()
if not getattr(sudo_scripts, "valid", False):
return False
return does_file_exist(SUDO_SINGBOX_LOCATION)
except Exception:
return False

0
gui/v2/infrastructure/screen_size.py Executable file → Normal file
View file

View file

@ -15,18 +15,6 @@ profile_observer = ProfileObserver()
ticket_observer = TicketObserver() ticket_observer = TicketObserver()
def _format_connecting_status(event):
subject = getattr(event, 'subject', None)
if isinstance(subject, dict):
attempt_count = subject.get("attempt_count")
maximum_attempts = subject.get("maximum_number_of_attempts")
if attempt_count is not None and maximum_attempts is not None:
return f'[{attempt_count}/{maximum_attempts}] Performing connection attempt...'
if subject:
return str(subject)
return 'Connecting..'
def setup_observers(update_status): def setup_observers(update_status):
profile_observer.subscribe( profile_observer.subscribe(
'created', lambda event: update_status('Profile Created')) 'created', lambda event: update_status('Profile Created'))
@ -45,7 +33,7 @@ def setup_observers(update_status):
client_observer.subscribe( client_observer.subscribe(
'updating', lambda event: update_status('Updating client...')) 'updating', lambda event: update_status('Updating client...'))
client_observer.subscribe('update_progressing', lambda event: update_status( client_observer.subscribe('update_progressing', lambda event: update_status(
f"Current progress: {event.meta.get('progress'):.2f}%")) f'Current progress: {event.meta.get('progress'):.2f}%'))
client_observer.subscribe('updated', lambda event: update_status( client_observer.subscribe('updated', lambda event: update_status(
'Restart client to apply update.')) 'Restart client to apply update.'))
@ -70,7 +58,7 @@ def setup_observers(update_status):
# f'Downloaded {ApplicationController.get(event.subject.application_code).name}')) # f'Downloaded {ApplicationController.get(event.subject.application_code).name}'))
connection_observer.subscribe('connecting', lambda event: update_status( connection_observer.subscribe('connecting', lambda event: update_status(
_format_connecting_status(event))) f'[{event.subject.get("attempt_count")}/{event.subject.get("maximum_number_of_attempts")}] Performing connection attempt...'))
connection_observer.subscribe('tor_bootstrapping', lambda event: update_status( connection_observer.subscribe('tor_bootstrapping', lambda event: update_status(
'Establishing Tor connection...')) 'Establishing Tor connection...'))

View file

@ -94,10 +94,3 @@ class Page(QWidget):
boton.setChecked(False) boton.setChecked(False)
self.button_next.setVisible(False) self.button_next.setVisible(False)
def replace_click_handler(self, button, handler):
try:
button.clicked.disconnect()
except TypeError:
pass
button.clicked.connect(handler)

View file

@ -22,11 +22,6 @@ from gui.v2.workers.page_data_worker import PageDataWorker
class EditorPage(Page): class EditorPage(Page):
SINGBOX_PROTOCOLS = ("hysteria2", "vless")
PROTOCOL_BUTTON_ASSETS = {
"hysteria2": "hystria2",
}
def __init__(self, page_stack, main_window, prepared=None): def __init__(self, page_stack, main_window, prepared=None):
super().__init__("Editor", page_stack, main_window) super().__init__("Editor", page_stack, main_window)
self.page_stack = page_stack self.page_stack = page_stack
@ -213,13 +208,6 @@ class EditorPage(Page):
"dimentions": self.connection_manager.get_available_resolutions(data_profile.get('id', '')) "dimentions": self.connection_manager.get_available_resolutions(data_profile.get('id', ''))
}, selected_profile_str) }, selected_profile_str)
elif protocol in self.SINGBOX_PROTOCOLS:
self.process_and_show_labels(data_profile, {
"protocol": [protocol],
"connection": ['system-wide'],
"location": self.connection_manager.get_location_list(),
}, selected_profile_str)
elif protocol == "residential" or protocol == "hidetor": elif protocol == "residential" or protocol == "hidetor":
self.process_and_show_labels(data_profile, { self.process_and_show_labels(data_profile, {
"protocol": ['residential', 'wireguard', 'hidetor'], "protocol": ['residential', 'wireguard', 'hidetor'],
@ -275,20 +263,6 @@ class EditorPage(Page):
else: else:
self.brow_disp.hide() self.brow_disp.hide()
if protocol in self.SINGBOX_PROTOCOLS:
if protocol == "vless":
self.display.setGeometry(0, 90, 540, 405)
else:
self.display.setGeometry(0, 60, 540, 405)
self.display.show()
self.display.setPixmap(self._encrypted_proxy_display_pixmap(
protocol, location).scaled(
self.display.size(),
Qt.AspectRatioMode.KeepAspectRatio,
Qt.TransformationMode.SmoothTransformation))
self.garaje.hide()
self.brow_disp.hide()
if protocol == "residential": if protocol == "residential":
self.display.setGeometry(0, 60, 540, 405) self.display.setGeometry(0, 60, 540, 405)
self.brow_disp.show() self.brow_disp.show()
@ -327,7 +301,7 @@ class EditorPage(Page):
l_name = f"{location_info.country_name}, {location_info.name}" if hasattr( l_name = f"{location_info.country_name}, {location_info.name}" if hasattr(
location_info, 'country_name') else "" location_info, 'country_name') else ""
if operator_name != 'Simplified Privacy' and operator_name != "" and protocol not in ('hidetor', *self.SINGBOX_PROTOCOLS): if operator_name != 'Simplified Privacy' and operator_name != "" and protocol != 'hidetor':
text_color = "white" text_color = "white"
if profile_obj and profile_obj.is_session_profile(): if profile_obj and profile_obj.is_session_profile():
text_color = "black" text_color = "black"
@ -439,12 +413,9 @@ class EditorPage(Page):
base_image = ScreenPage.create_resolution_button_image( base_image = ScreenPage.create_resolution_button_image(
self, current_value) self, current_value)
else: else:
image_path = os.path.join(
self.btn_path, f"{data_profile.get(key, '')}_button.png")
current_value = data_profile.get(key, '') current_value = data_profile.get(key, '')
if key == 'protocol':
image_path = self._protocol_button_asset(current_value)
else:
image_path = os.path.join(
self.btn_path, f"{current_value}_button.png")
base_image = QPixmap(image_path) base_image = QPixmap(image_path)
if key == 'dimentions': if key == 'dimentions':
@ -608,7 +579,7 @@ class EditorPage(Page):
prev_button.setVisible(True) prev_button.setVisible(True)
next_button.setVisible(True) next_button.setVisible(True)
if key == 'protocol' or (connection == 'system-wide' and key == 'connection'): if key == 'protocol' or (protocol == 'wireguard' and key == 'connection'):
prev_button.setDisabled(True) prev_button.setDisabled(True)
next_button.setDisabled(True) next_button.setDisabled(True)
@ -616,44 +587,6 @@ class EditorPage(Page):
prev_button.setVisible(False) prev_button.setVisible(False)
next_button.setVisible(False) next_button.setVisible(False)
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 _encrypted_proxy_display_pixmap(self, protocol, location):
pixmap = QPixmap(os.path.join(self.btn_path, f"{protocol}.png"))
if pixmap.isNull():
pixmap = QPixmap(os.path.join(self.btn_path, "system_wide_global.png"))
if pixmap.isNull():
return pixmap
location_pixmap = QPixmap(os.path.join(
self.btn_path, f"icon_mini_{location}.png"))
if location_pixmap.isNull():
location_pixmap = QPixmap(os.path.join(
self.btn_path, "default_location_mini.png"))
if location_pixmap.isNull():
return pixmap
icon_size = max(42, min(72, int(min(pixmap.width(), pixmap.height()) * 0.22)))
location_pixmap = location_pixmap.scaled(
icon_size,
icon_size,
Qt.AspectRatioMode.KeepAspectRatio,
Qt.TransformationMode.SmoothTransformation)
x = int(pixmap.width() * 0.58)
if protocol == 'hysteria2':
y = int(pixmap.height() * 0.35)
else:
y = int(pixmap.height() * 0.30)
x = min(max(0, x), max(0, pixmap.width() - location_pixmap.width()))
y = min(max(0, y), max(0, pixmap.height() - location_pixmap.height()))
painter = QPainter(pixmap)
painter.drawPixmap(x, y, location_pixmap)
painter.end()
return pixmap
def on_sync_complete_for_edit_profile(self, available_locations, available_browsers, status, is_tor, locations, all_browsers): def on_sync_complete_for_edit_profile(self, available_locations, available_browsers, status, is_tor, locations, all_browsers):
if status: if status:
self.update_status.update_status('Sync complete.') self.update_status.update_status('Sync complete.')

View file

@ -15,9 +15,9 @@ class HidetorPage(Page):
self.selected_location_icon = None self.selected_location_icon = None
self.connection_manager = main_window.connection_manager self.connection_manager = main_window.connection_manager
self.update_status = main_window self.update_status = main_window
self.replace_click_handler(self.button_next, self.go_selected) self.button_next.clicked.connect(self.go_selected)
self.button_reverse.setVisible(True) self.button_reverse.setVisible(True)
self.replace_click_handler(self.button_reverse, self.reverse) self.button_reverse.clicked.connect(self.reverse)
self.display.setGeometry(QtCore.QRect(5, 10, 390, 520)) self.display.setGeometry(QtCore.QRect(5, 10, 390, 520))
self.title.setGeometry(395, 40, 380, 40) self.title.setGeometry(395, 40, 380, 40)
self.title.setText("Pick a location") self.title.setText("Pick a location")
@ -91,12 +91,7 @@ class HidetorPage(Page):
self.update_swarp_json() self.update_swarp_json()
def reverse(self): def reverse(self):
profile_data = self.update_status.read_data() self.custom_window.navigator.navigate("protocol")
self.limpiar()
if profile_data.get("protocol") == "residential":
self.custom_window.navigator.navigate("residential")
else:
self.custom_window.navigator.navigate("protocol")
def go_selected(self): def go_selected(self):
self.custom_window.navigator.navigate("browser") self.custom_window.navigator.navigate("browser")

View file

@ -15,8 +15,7 @@ class LocationPage(Page):
self.update_status = main_window self.update_status = main_window
self.button_reverse.setVisible(True) self.button_reverse.setVisible(True)
self.connection_manager = main_window.connection_manager self.connection_manager = main_window.connection_manager
self.replace_click_handler(self.button_reverse, self.reverse) self.button_reverse.clicked.connect(self.reverse)
self.replace_click_handler(self.button_next, self.go_selected)
self.display.setGeometry(QtCore.QRect(5, 10, 390, 520)) self.display.setGeometry(QtCore.QRect(5, 10, 390, 520))
self.title.setGeometry(395, 40, 380, 40) self.title.setGeometry(395, 40, 380, 40)
self.title.setText("Pick a location") self.title.setText("Pick a location")
@ -67,7 +66,6 @@ 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):
@ -81,7 +79,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 self._location_supports_selected_protocol(locations): if locations and not (hasattr(locations, 'is_wireguard_capable') and locations.is_wireguard_capable):
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))
@ -123,30 +121,6 @@ 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", "")
@ -167,6 +141,7 @@ class LocationPage(Page):
target_size, Qt.AspectRatioMode.KeepAspectRatio)) target_size, Qt.AspectRatioMode.KeepAspectRatio))
self.selected_location_icon = location self.selected_location_icon = location
self.button_next.setVisible(True) self.button_next.setVisible(True)
self.button_next.clicked.connect(self.go_selected)
self.update_swarp_json() self.update_swarp_json()
try: try:
@ -179,19 +154,10 @@ class LocationPage(Page):
self.verification_button.setEnabled(True) self.verification_button.setEnabled(True)
def reverse(self): def reverse(self):
profile_data = self.update_status.read_data() self.custom_window.navigator.navigate("protocol")
self.limpiar()
if hasattr(self, 'initial_display'):
self.initial_display.show()
if hasattr(self, 'verification_button'):
self.verification_button.setEnabled(False)
if profile_data.get("protocol") == "wireguard":
self.custom_window.navigator.navigate("wireguard")
else:
self.custom_window.navigator.navigate("protocol")
def go_selected(self): def go_selected(self):
if self.update_swarp_json(get_connection=True) == "system-wide": if self.connection_type == "system-wide":
self.custom_window.navigator.navigate("resume") self.custom_window.navigator.navigate("resume")
else: else:
self.custom_window.navigator.navigate("browser") self.custom_window.navigator.navigate("browser")

View file

@ -23,7 +23,6 @@ from core.models.system.SystemProfile import SystemProfile
from gui.v2.infrastructure.setup_observers import ticket_observer from gui.v2.infrastructure.setup_observers import ticket_observer
from gui.v2.actions.database_health import GuiStorageDatabaseError from gui.v2.actions.database_health import GuiStorageDatabaseError
from gui.v2.actions.profile_order import normalize_profile_order from gui.v2.actions.profile_order import normalize_profile_order
from gui.v2.actions.profile_status import is_profile_enabled_for_gui
from gui.v2.ui.pages.Page import Page from gui.v2.ui.pages.Page import Page
from gui.v2.ui.pages.location_verification_page import LocationVerificationPage from gui.v2.ui.pages.location_verification_page import LocationVerificationPage
from gui.v2.ui.styles.styles import SCROLLBAR_CYAN_QSS from gui.v2.ui.styles.styles import SCROLLBAR_CYAN_QSS
@ -308,15 +307,15 @@ class MenuPage(Page):
new_profile['location'] = location new_profile['location'] = location
if protocol in ('wireguard', 'hysteria2', 'vless'): if protocol == 'wireguard':
new_profile['protocol'] = protocol new_profile['protocol'] = 'wireguard'
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 in ('wireguard', 'hysteria2', 'vless'): if protocol == 'wireguard':
new_profile['connection'] = connection_type new_profile['connection'] = connection_type
elif protocol == 'tor': elif protocol == 'tor':
new_profile['connection'] = protocol new_profile['connection'] = protocol
@ -376,7 +375,7 @@ class MenuPage(Page):
self.IsSystem = 0 self.IsSystem = 0
for profile_id, profile in profiles.items(): for profile_id, profile in profiles.items():
try: try:
is_enabled = is_profile_enabled_for_gui(profile) is_enabled = ProfileController.is_enabled(profile)
except Exception: except Exception:
is_enabled = False is_enabled = False
if is_enabled: if is_enabled:
@ -576,10 +575,7 @@ 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':
@ -588,8 +584,6 @@ 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, "wireguard_system_wide.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")
@ -847,21 +841,17 @@ 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", "hysteria2", "vless"]: elif protocol.lower() in ["wireguard", "open", "residential", "hidetor"]:
label_principal = QLabel(self) label_principal = QLabel(self)
label_principal.setGeometry(0, 90, 400, 300) label_principal.setGeometry(0, 90, 400, 300)
if protocol.lower() in ["hysteria2", "vless"]: pixmap = QPixmap(os.path.join(
pixmap = self.build_encrypted_proxy_detail_pixmap( self.btn_path, f"{protocol}_{location}.png"))
protocol.lower(), location)
else:
pixmap = QPixmap(os.path.join(
self.btn_path, f"{protocol}_{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", "hysteria2", "vless"]: if protocol.lower() in ["wireguard", "open", "residential", "hidetor"]:
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':
@ -964,38 +954,6 @@ class MenuPage(Page):
self.current_profile_location = location self.current_profile_location = location
def build_encrypted_proxy_detail_pixmap(self, protocol, location):
pixmap = QPixmap(os.path.join(self.btn_path, f"{protocol}.png"))
if pixmap.isNull():
return pixmap
location_pixmap = QPixmap(os.path.join(
self.btn_path, f"icon_mini_{location}.png"))
if location_pixmap.isNull():
location_pixmap = QPixmap(os.path.join(
self.btn_path, "default_location_mini.png"))
if location_pixmap.isNull():
return pixmap
icon_size = max(42, min(72, int(min(pixmap.width(), pixmap.height()) * 0.22)))
location_pixmap = location_pixmap.scaled(
icon_size,
icon_size,
Qt.AspectRatioMode.KeepAspectRatio,
Qt.TransformationMode.SmoothTransformation)
x = int(pixmap.width() * 0.58)
if protocol == 'hysteria2':
y = int(pixmap.height() * 0.35)
else:
y = int(pixmap.height() * 0.30)
x = min(max(0, x), max(0, pixmap.width() - location_pixmap.width()))
y = min(max(0, y), max(0, pixmap.height() - location_pixmap.height()))
painter = QPainter(pixmap)
painter.drawPixmap(x, y, location_pixmap)
painter.end()
return pixmap
def show_profile_verification(self, profile_id): def show_profile_verification(self, profile_id):
profile = self._safe_profile(profile_id) profile = self._safe_profile(profile_id)
profile_location = getattr(profile, 'location', None) profile_location = getattr(profile, 'location', None)
@ -1126,10 +1084,7 @@ class MenuPage(Page):
def on_disconnect_done(self): def on_disconnect_done(self):
self.disconnect_button.setEnabled(True) self.disconnect_button.setEnabled(True)
self.disconnect_system_wide_button.setEnabled(True) self.disconnect_system_wide_button.setEnabled(True)
selected_profile_id = self.reverse_id pass
self.refresh_menu_buttons()
if selected_profile_id is not None and self._safe_profile(selected_profile_id):
self.print_profile_details(f"Profile_{selected_profile_id}")
def _config_flag(self, section, key, default=False): def _config_flag(self, section, key, default=False):
try: try:

View file

@ -4,15 +4,13 @@ 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, is_singbox_wrapper_ready from core.services.helpers.setup_sudo_scripts import auto_install_sudo_script, test_if_in_sudo_folder
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.actions.singbox_prereqs import singbox_prereqs_installed
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):
@ -21,10 +19,6 @@ 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)
@ -35,32 +29,29 @@ 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)
self.title_label = QLabel("Firewall & Managed-DNS Setup", self) title = QLabel("Firewall & Managed-DNS Setup", self)
self.title_label.setGeometry(138, 68, 590, 44) title.setGeometry(138, 68, 590, 44)
self.title_label.setStyleSheet("font-family: Arial; font-size: 26px; font-weight: bold; color: cyan;") title.setStyleSheet("font-family: Arial; font-size: 26px; font-weight: bold; color: cyan;")
self.subtitle_label = QLabel( subtitle = QLabel(
"Optional privileged networking helpers", "Optional privileged networking helpers",
self) self)
self.subtitle_label.setGeometry(140, 108, 560, 24) subtitle.setGeometry(140, 108, 560, 24)
self.subtitle_label.setStyleSheet("font-family: Arial; font-size: 14px; color: #d8ffff;") subtitle.setStyleSheet("font-family: Arial; font-size: 14px; color: #d8ffff;")
self.description_label = QLabel( description = 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)
self.description_label.setGeometry(80, 152, 640, 72) description.setGeometry(80, 152, 640, 72)
self.description_label.setWordWrap(True) description.setWordWrap(True)
self.description_label.setStyleSheet("font-family: Arial; font-size: 15px; color: cyan;") description.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(
@ -161,94 +152,6 @@ 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 singbox_prereqs_installed():
self._set_status("Singbox prerequisites are already installed. Creating profile...", "#2ecc71")
QTimer.singleShot(200, self.finish_singbox_prereq_flow)
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()
@ -266,10 +169,6 @@ 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:
@ -281,10 +180,6 @@ 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:
@ -295,9 +190,6 @@ 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,11 +9,6 @@ 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
@ -23,8 +18,7 @@ class ProtocolPage(Page):
self.connection_manager = main_window.connection_manager self.connection_manager = main_window.connection_manager
self.button_back.setVisible(True) self.button_back.setVisible(True)
self.update_status = main_window self.update_status = main_window
self.replace_click_handler(self.button_back, self.reverse) self.button_go.clicked.connect(self.go_selected)
self.replace_click_handler(self.button_go, self.go_selected)
self.coming_soon_label = QLabel("Coming soon", self) self.coming_soon_label = QLabel("Coming soon", self)
self.coming_soon_label.setGeometry(210, 50, 200, 40) self.coming_soon_label.setGeometry(210, 50, 200, 40)
self.coming_soon_label.setStyleSheet("font-size: 22px;") self.coming_soon_label.setStyleSheet("font-size: 22px;")
@ -42,11 +36,9 @@ 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, 80, 185, 75)), (QPushButton, "wireguard", "wireguard", (585, 90, 185, 75)),
(QPushButton, "hysteria2", "location", (585, 160, 185, 75)), (QPushButton, "residential", "residential", (585, 90+30+75, 185, 75)),
(QPushButton, "vless", "location", (585, 240, 185, 75)), (QPushButton, "hidetor", "hidetor", (585, 90+30+75+30+75, 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)
@ -54,40 +46,28 @@ class ProtocolPage(Page):
boton.setCheckable(True) boton.setCheckable(True)
boton.setDisabled(True) boton.setDisabled(True)
boton.setIcon( boton.setIcon(
QIcon(self._button_asset(icon_name))) QIcon(os.path.join(self.btn_path, f"{icon_name}_button.png")))
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):
data = {"protocol": self.selected_protocol_icon} self.update_status.write_data(
if self.selected_protocol_icon in self.SINGBOX_PROTOCOLS: {"protocol": self.selected_protocol_icon})
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(self._display_asset(protocol)).scaled( self.display.setPixmap(QPixmap(os.path.join(self.btn_path, f"{protocol}.png")).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", *self.SINGBOX_PROTOCOLS]: if protocol in ["wireguard", "hidetor"]:
self.button_go.setVisible(True) self.button_go.setVisible(True)
self.coming_soon_label.setVisible(False) self.coming_soon_label.setVisible(False)
else: else:
@ -105,10 +85,3 @@ class ProtocolPage(Page):
def find_menu_page(self): def find_menu_page(self):
return self.custom_window.navigator.get_cached("menu") return self.custom_window.navigator.get_cached("menu")
def reverse(self):
self.display.clear()
for boton in self.buttons:
boton.setChecked(False)
self.button_go.setVisible(False)
self.custom_window.navigator.navigate("menu")

View file

@ -15,8 +15,8 @@ class ResidentialPage(Page):
self.update_status = main_window self.update_status = main_window
self.connection_choice = None self.connection_choice = None
self.button_reverse.setVisible(True) self.button_reverse.setVisible(True)
self.replace_click_handler(self.button_reverse, self.reverse) self.button_reverse.clicked.connect(self.reverse)
self.replace_click_handler(self.button_go, self.go_selected) self.button_go.clicked.connect(self.go_selected)
self.display_1 = QLabel(self) self.display_1 = QLabel(self)
self.display_1.setGeometry(QtCore.QRect( self.display_1.setGeometry(QtCore.QRect(
@ -98,5 +98,4 @@ class ResidentialPage(Page):
self.update_status.write_data(inserted_data) self.update_status.write_data(inserted_data)
def reverse(self): def reverse(self):
self.button_go.setVisible(False)
self.custom_window.navigator.navigate("protocol") self.custom_window.navigator.navigate("protocol")

View file

@ -10,18 +10,12 @@ from PyQt6 import QtCore, QtGui
from core.controllers.ProfileController import ProfileController from core.controllers.ProfileController import ProfileController
from gui.v2.actions.profile_order import append_profile_to_visual_order from gui.v2.actions.profile_order import append_profile_to_visual_order
from gui.v2.actions.singbox_prereqs import singbox_prereqs_installed
from gui.v2.ui.pages.Page import Page from gui.v2.ui.pages.Page import Page
from gui.v2.ui.pages.location_page import LocationPage from gui.v2.ui.pages.location_page import LocationPage
from gui.v2.ui.pages.screen_page import ScreenPage 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
@ -29,13 +23,13 @@ class ResumePage(Page):
self.btn_path = main_window.btn_path self.btn_path = main_window.btn_path
self.labels_creados = [] self.labels_creados = []
self.additional_labels = [] self.additional_labels = []
self.replace_click_handler(self.button_go, self.copy_profile) self.button_go.clicked.connect(self.copy_profile)
self.button_back.setVisible(True) self.button_back.setVisible(True)
self.title.setGeometry(585, 40, 185, 40) self.title.setGeometry(585, 40, 185, 40)
self.title.setText("Profile Summary") self.title.setText("Profile Summary")
self.display.setGeometry(QtCore.QRect(5, 50, 580, 435)) self.display.setGeometry(QtCore.QRect(5, 50, 580, 435))
self.buttonGroup = QButtonGroup(self) self.buttonGroup = QButtonGroup(self)
self.replace_click_handler(self.button_back, self.reverse) self.button_back.clicked.connect(self.reverse)
self.create_arrow() self.create_arrow()
self.create_interface_elements() self.create_interface_elements()
@ -206,11 +200,8 @@ class ResumePage(Page):
parent_label.show() parent_label.show()
self.labels_creados.append(parent_label) self.labels_creados.append(parent_label)
else: else:
if item == 'protocol': icon_path = os.path.join(
icon_path = self._protocol_button_asset(text) self.btn_path, f"{text}_button.png")
else:
icon_path = os.path.join(
self.btn_path, f"{text}_button.png")
geometry = (585, initial_y + i * label_height, 185, 75) geometry = (585, initial_y + i * label_height, 185, 75)
parent_label = QLabel(self) parent_label = QLabel(self)
parent_label.setGeometry(*geometry) parent_label.setGeometry(*geometry)
@ -287,8 +278,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 = self._system_profile_image( image_path = os.path.join(
profile_1.get('protocol', 'wireguard'), profile_1.get('location', '')) self.btn_path, f"wireguard_{profile_1.get('location', '')}.png")
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))
@ -345,21 +336,6 @@ 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()))
@ -368,6 +344,10 @@ 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]
@ -381,29 +361,13 @@ 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 and not singbox_prereqs_installed():
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,
existing_profile_ids) profiles.keys())
main = self.update_status main = self.update_status
if hasattr(main, 'navigate_after_profile_created'): if hasattr(main, 'navigate_after_profile_created'):
@ -412,6 +376,7 @@ 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)
@ -443,8 +408,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') in ('wireguard', 'hysteria2', 'vless'): if profile.get('protocol') == 'wireguard':
connection_type = profile.get('protocol') connection_type = 'wireguard'
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

@ -345,6 +345,3 @@ class ScreenPage(Page):
def gestionar_next(self): def gestionar_next(self):
self.custom_window.navigator.navigate("resume") self.custom_window.navigator.navigate("resume")
def gestionar_back(self):
self.custom_window.navigator.navigate("browser")

View file

@ -1,13 +1,15 @@
import os import os
import shutil
from PyQt6.QtWidgets import QButtonGroup, QMessageBox, QPushButton from PyQt6.QtWidgets import QApplication, QButtonGroup, QMessageBox, QPushButton
from PyQt6.QtGui import QIcon from PyQt6.QtGui import QIcon
from PyQt6.QtCore import QSize from PyQt6.QtCore import QSize
from PyQt6 import QtCore from PyQt6 import QtCore
from core.Constants import Constants from core.services.prepare_tickets.ticket_tracker import delete_ticket_data
from core.controllers.tickets.TicketPayController import check_if_paid
from core.models.Result import Result, ResultError
from gui.v2.infrastructure.setup_observers import connection_observer, ticket_observer
from gui.v2.ui.pages.Page import Page from gui.v2.ui.pages.Page import Page
from gui.v2.ui.popups.message_box import style_message_box, mark_confirm_button from gui.v2.ui.popups.message_box import style_message_box, mark_confirm_button
from gui.v2.workers.ticketing_worker_thread import TicketingWorkerThread from gui.v2.workers.ticketing_worker_thread import TicketingWorkerThread
@ -64,6 +66,28 @@ class TicketCryptoPickerPage(Page):
currency = selected_button.property('currency') currency = selected_button.property('currency')
self.start_initiate_payment(currency) self.start_initiate_payment(currency)
def check_if_paid_for_existing(self, temp_billing_code):
self.update_status.update_status("Checking if paid...")
result = check_if_paid(temp_billing_code, ticket_observer, connection_observer)
if isinstance(result, dict) and result.get('valid') and result.get('payment_status') == 'paid':
self.update_status.update_status("Already Paid!")
self.custom_window.navigator.navigate("ticket_prep")
prep_page = self.custom_window.navigator.get_cached("ticket_prep")
if prep_page:
prep_page.start_prep()
else:
self.update_status.update_status("Not yet paid.")
clipboard = QApplication.clipboard()
clipboard.setText(temp_billing_code)
not_paid_msg = f"The billing code is not yet showing paid for {temp_billing_code}. Right now, your clipboard has the billing code, to paste it in any text editor. If you did pay, either wait longer for blockchain confirmation, or contact customer support with the code in your clipboard now."
info = QMessageBox(self)
info.setWindowTitle("Not Paid")
info.setText(not_paid_msg)
info.setStandardButtons(QMessageBox.StandardButton.Ok)
style_message_box(info)
mark_confirm_button(info.button(QMessageBox.StandardButton.Ok))
info.exec()
def start_initiate_payment(self, currency): def start_initiate_payment(self, currency):
self.update_status.update_status("Initiating payment...") self.update_status.update_status("Initiating payment...")
self.worker = TicketingWorkerThread('INITIATE_PAYMENT', params={ self.worker = TicketingWorkerThread('INITIATE_PAYMENT', params={
@ -81,82 +105,69 @@ class TicketCryptoPickerPage(Page):
self.update_status.update_status("Could not initiate payment.") self.update_status.update_status("Could not initiate payment.")
return return
if not getattr(invoice, 'is_valid', False): if not invoice.valid:
return self._handle_api_errors(invoice) return self._handle_api_errors(invoice)
# error_code = getattr(invoice, 'error_code', None)
self.custom_window.navigator.navigate("payment_details") self.custom_window.navigator.navigate("payment_details")
payment_page = self.custom_window.navigator.get_cached("payment_details") payment_page = self.custom_window.navigator.get_cached("payment_details")
if payment_page is not None: if payment_page is not None:
payment_page.set_ticket_invoice(invoice, self.selected_plan) payment_page.set_ticket_invoice(invoice.data, self.selected_plan)
def _handle_api_errors(self, invoice): def _handle_api_errors(self, invoice: Result):
error_code = getattr(invoice, 'error_code', None) error_code = invoice.error_type
if error_code == "already_exists" and not self.bypass_existing: if error_code == ResultError.ALREADY_EXISTS and not self.bypass_existing:
self._prompt_wipe_existing() self._prompt_wipe_existing(invoice)
return return
elif error_code == "billing_code_exists" and not self.bypass_existing: elif error_code == ResultError.BILLING_CODE_EXISTS and not self.bypass_existing:
temp_billing_code = getattr(invoice, 'temp_billing_code', None) temp_billing_code = getattr(invoice, 'temp_billing_code', None)
self._prompt_wipe_existing(temp_billing_code) print(f"temp_billing_code is {temp_billing_code}")
return if temp_billing_code:
self._prompt_wipe_billingcode(temp_billing_code)
return
else:
self._prompt_wipe_billingcode("NONE")
return
else: else:
error_msg = getattr(invoice, 'final_error_msg', None) or error_code or "Could not initiate payment." error_msg = invoice.message
# msg = getattr(invoice, 'final_error_msg', None) or error_code
self.update_status.update_status(error_msg) self.update_status.update_status(error_msg)
return return
def _prompt_wipe_existing(self, temp_billing_code=None): def _prompt_wipe_existing(self, invoice):
msg = QMessageBox(self) msg = QMessageBox(self)
if temp_billing_code: msg.setWindowTitle("Existing tickets found")
msg.setWindowTitle("Existing billing code found") msg.setText("You already have ticket data. Wipe it and start over?")
msg.setText("You already have a ticket billing code. Use it, or wipe it and start over?") msg.setStandardButtons(QMessageBox.StandardButton.Yes | QMessageBox.StandardButton.No)
use_button = msg.addButton("Use existing", QMessageBox.ButtonRole.AcceptRole)
wipe_button = msg.addButton("Wipe and start over", QMessageBox.ButtonRole.DestructiveRole)
cancel_button = msg.addButton("Cancel", QMessageBox.ButtonRole.RejectRole)
mark_confirm_button(use_button)
else:
msg.setWindowTitle("Existing tickets found")
msg.setText("You already have ticket data. Wipe it and start over?")
use_button = None
wipe_button = msg.addButton("Wipe and start over", QMessageBox.ButtonRole.DestructiveRole)
cancel_button = msg.addButton("Cancel", QMessageBox.ButtonRole.RejectRole)
mark_confirm_button(wipe_button)
style_message_box(msg) style_message_box(msg)
msg.exec() mark_confirm_button(msg.button(QMessageBox.StandardButton.Yes))
clicked = msg.clickedButton() result = msg.exec()
if temp_billing_code and clicked == use_button: if result == QMessageBox.StandardButton.Yes:
self.update_status.update_status("Reusing existing billing code") self.bypass_existing = True
self.custom_window.navigator.navigate("payment_details") delete_ticket_data()
payment_page = self.custom_window.navigator.get_cached("payment_details") currency_btn = self.buttonGroup.checkedButton()
if payment_page is not None: if currency_btn:
payment_page.resume_ticket_billing(temp_billing_code) self.start_initiate_payment(currency_btn.property('currency'))
return
if clicked == wipe_button:
self._restart_payment_after_wipe()
else: else:
self.update_status.update_status("Cancelled.") self.update_status.update_status("Cancelled.")
def _restart_payment_after_wipe(self): def _prompt_wipe_billingcode(self, temp_billing_code):
if not self._delete_ticket_data(): msg = QMessageBox(self)
return msg.setWindowTitle("Existing billing code found")
self.bypass_existing = False msg.setText("You already have a ticket billing code. Do you want to use it? Only hit YES if you ALREADY paid.")
currency_btn = self.buttonGroup.checkedButton() msg.setStandardButtons(QMessageBox.StandardButton.Yes | QMessageBox.StandardButton.No)
if currency_btn: style_message_box(msg)
self.start_initiate_payment(currency_btn.property('currency')) mark_confirm_button(msg.button(QMessageBox.StandardButton.Yes))
result = msg.exec()
def _delete_ticket_data(self): if result == QMessageBox.StandardButton.Yes:
paths = [ self.update_status.update_status("Reusing same code")
Constants.TICKET_TRACKER_PATH, self.check_if_paid_for_existing(temp_billing_code)
os.path.join(Constants.HV_TICKETING_CONFIG_HOME, "billing_choices.json"), else:
] self.bypass_existing = True
try: delete_ticket_data()
for path in paths: currency_btn = self.buttonGroup.checkedButton()
if os.path.exists(path): if currency_btn:
os.remove(path) self.start_initiate_payment(currency_btn.property('currency'))
if os.path.isdir(Constants.HV_TICKETING_DATA_HOME):
shutil.rmtree(Constants.HV_TICKETING_DATA_HOME)
return True
except OSError as error:
self.update_status.update_status(f"Could not wipe ticket data: {error}")
return False
def on_error(self, msg): def on_error(self, msg):
self.update_status.update_status(f"Payment error: {msg}") self.update_status.update_status(f"Payment error: {msg}")

View file

@ -24,11 +24,10 @@ class TorPage(Page):
QPixmap(os.path.join(self.btn_path, "browser only.png"))) QPixmap(os.path.join(self.btn_path, "browser only.png")))
self.display0.lower() self.display0.lower()
self.replace_click_handler(self.button_go, self.go_selected) self.button_go.clicked.connect(self.go_selected)
self.button_reverse.setVisible(True) self.button_reverse.setVisible(True)
self.replace_click_handler(self.button_reverse, self.reverse_selected) self.button_reverse.clicked.connect(self.reverse_selected)
self.replace_click_handler(self.button_back, self.reverse_selected)
self.label = QLabel(self) self.label = QLabel(self)
self.label.setGeometry(440, 370, 86, 130) self.label.setGeometry(440, 370, 86, 130)
@ -73,8 +72,6 @@ class TorPage(Page):
self.update_status.write_data(inserted_data) self.update_status.write_data(inserted_data)
def reverse_selected(self): def reverse_selected(self):
self.limpiar()
self.button_go.setVisible(False)
self.custom_window.navigator.navigate("residential") self.custom_window.navigator.navigate("residential")
def go_selected(self): def go_selected(self):

View file

@ -19,8 +19,7 @@ class WireGuardPage(Page):
self.selected_protocol = None self.selected_protocol = None
self.selected_protocol_icon = None self.selected_protocol_icon = None
self.button_back.setVisible(True) self.button_back.setVisible(True)
self.replace_click_handler(self.button_back, self.reverse) self.button_go.clicked.connect(self.go_selected)
self.replace_click_handler(self.button_go, self.go_selected)
self.additional_labels = [] self.additional_labels = []
self.title.setGeometry(585, 40, 185, 40) self.title.setGeometry(585, 40, 185, 40)
self.title.setText("Pick a Protocol") self.title.setText("Pick a Protocol")

View file

@ -1,6 +1,5 @@
import shlex import shlex
import subprocess import subprocess
import inspect
from PyQt6.QtCore import QThread, pyqtSignal from PyQt6.QtCore import QThread, pyqtSignal
@ -21,10 +20,8 @@ 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,
@ -53,18 +50,6 @@ class WorkerThread(QThread):
self.is_running = True self.is_running = True
self.is_disabling = False self.is_disabling = False
def _disable_profile(self, profile):
kwargs = {
'profile_observer': profile_observer,
'ticket_observer': ticket_observer,
'connection_observer': connection_observer,
}
supported = inspect.signature(ProfileController.disable).parameters
ProfileController.disable(
profile,
**{key: value for key, value in kwargs.items() if key in supported}
)
def run(self): def run(self):
if self.action == 'LIST_PROFILES': if self.action == 'LIST_PROFILES':
self.list_profiles() self.list_profiles()
@ -84,8 +69,6 @@ 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':
@ -141,50 +124,18 @@ 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:
profile = ProfileController.get(int(profile_id)) profile = ProfileController.get(int(profile_id))
if isinstance(profile, SessionProfile): if isinstance(profile, SessionProfile):
self._disable_profile(profile) ProfileController.disable(
profile, ignore=True, profile_observer=profile_observer, ticket_observer=ticket_observer, connection_observer=connection_observer)
for profile_id in self.profile_data: for profile_id in self.profile_data:
profile = ProfileController.get(int(profile_id)) profile = ProfileController.get(int(profile_id))
if isinstance(profile, SystemProfile): if isinstance(profile, SystemProfile):
self._disable_profile(profile) ProfileController.disable(
profile, ignore=True, profile_observer=profile_observer, ticket_observer=ticket_observer, connection_observer=connection_observer)
self.text_output.emit("All profiles were successfully disabled") self.text_output.emit("All profiles were successfully disabled")
except SudoScript as e: except SudoScript as e:
self.text_output.emit(str(e)) self.text_output.emit(str(e))
@ -293,7 +244,8 @@ class WorkerThread(QThread):
try: try:
profile = ProfileController.get(int(self.profile_data['id'])) profile = ProfileController.get(int(self.profile_data['id']))
if profile: if profile:
self._disable_profile(profile) ProfileController.disable(
profile, profile_observer=profile_observer, ticket_observer=ticket_observer, connection_observer=connection_observer, wipe_assassin=True)
else: else:
self.text_output.emit( self.text_output.emit(
f"No profile found with ID: {self.profile_data['id']}") f"No profile found with ID: {self.profile_data['id']}")