diff --git a/gui/v2/ui/pages/fast_registration_page.py b/gui/v2/ui/pages/fast_registration_page.py index 4a2a2c4..f624e3a 100755 --- a/gui/v2/ui/pages/fast_registration_page.py +++ b/gui/v2/ui/pages/fast_registration_page.py @@ -11,6 +11,7 @@ from PyQt6 import QtCore from core.controllers.ProfileController import ProfileController 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.browser_page import BrowserPage from gui.v2.ui.pages.location_page import LocationPage @@ -20,6 +21,12 @@ from gui.v2.workers.worker_thread import WorkerThread class FastRegistrationPage(Page): + SINGBOX_PROTOCOLS = ("hysteria2", "vless") + PROTOCOLS = ("wireguard", "hysteria2", "vless", "hidetor") + PROTOCOL_BUTTON_ASSETS = { + "hysteria2": "hystria2", + } + def __init__(self, page_stack, main_window, prepared=None): super().__init__("FastRegistration", page_stack, main_window) self.page_stack = page_stack @@ -85,10 +92,12 @@ class FastRegistrationPage(Page): def initialize_default_selections(self): if not self.selected_values['location']: - locations = self.connection_manager.get_location_list() + locations = self._available_locations_for_protocol() if locations: random_index = random.randint(0, len(locations) - 1) self.selected_values['location'] = locations[random_index] + else: + self._select_valid_location_for_protocol() if not self.selected_values['browser']: browsers = self.connection_manager.get_browser_list() @@ -167,8 +176,8 @@ class FastRegistrationPage(Page): label = QLabel("Protocol", self) label.setGeometry(300, 150, 185, 75) - protocol_image = QPixmap(os.path.join( - self.btn_path, f"{self.selected_values['protocol']}_button.png")) + protocol_image = QPixmap(self._protocol_button_asset( + self.selected_values['protocol'])) label.setPixmap(protocol_image) label.setScaledContents(True) label.show() @@ -196,6 +205,10 @@ class FastRegistrationPage(Page): next_button.setIconSize(next_button.size()) self.buttons.append(next_button) + 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 create_connection_section(self): label = QLabel("Connection", self) label.setGeometry(150, 250, 185, 75) @@ -227,6 +240,8 @@ class FastRegistrationPage(Page): rotated_icon = icon.transformed(transform) prev_button.setIcon(QIcon(rotated_icon)) prev_button.setIconSize(prev_button.size()) + if self.selected_values['protocol'] in self.SINGBOX_PROTOCOLS: + prev_button.setDisabled(True) self.buttons.append(prev_button) next_button = QPushButton(self) @@ -236,6 +251,8 @@ class FastRegistrationPage(Page): next_button.setIcon( QIcon(os.path.join(self.btn_path, "UP_button.png"))) next_button.setIconSize(next_button.size()) + if self.selected_values['protocol'] in self.SINGBOX_PROTOCOLS: + next_button.setDisabled(True) self.buttons.append(next_button) def create_location_section(self): @@ -302,7 +319,7 @@ class FastRegistrationPage(Page): locations = self.connection_manager.get_location_info( self.selected_values['location']) - if self.selected_values['protocol'] == 'hidetor' and locations and not (hasattr(locations, 'is_proxy_capable') and locations.is_proxy_capable): + if locations and not self._location_supports_selected_protocol(locations): label.hide() else: label.show() @@ -529,46 +546,34 @@ class FastRegistrationPage(Page): def update_ui_state_for_connection(self): is_system_wide = self.selected_values['connection'] == 'system-wide' + is_singbox = self.selected_values['protocol'] in self.SINGBOX_PROTOCOLS for button in self.buttons: if hasattr(button, 'geometry'): button_geometry = button.geometry() + if is_singbox and button_geometry.y() == 250 and button_geometry.x() in [115, 340]: + button.setEnabled(False) if button_geometry.y() == 350: if button_geometry.x() in [115, 340, 400, 625]: button.setEnabled(not is_system_wide) def show_previous_value(self, key): if key == 'protocol': - protocols = ['wireguard', 'hidetor'] + protocols = list(self.PROTOCOLS) current_index = protocols.index(self.selected_values[key]) previous_index = (current_index - 1) % len(protocols) self.selected_values[key] = protocols[previous_index] - if self.selected_values[key] == 'wireguard': - self.selected_values['connection'] = 'browser-only' - else: - self.selected_values['connection'] = 'tor' - loc_info = self.connection_manager.get_location_info( - self.selected_values['location']) - if not (loc_info and hasattr(loc_info, 'is_proxy_capable') and loc_info.is_proxy_capable): - locations = self.connection_manager.get_location_list() - proxy_locations = [loc for loc in locations if (l := self.connection_manager.get_location_info( - loc)) and hasattr(l, 'is_proxy_capable') and l.is_proxy_capable] - if proxy_locations: - self.selected_values['location'] = proxy_locations[0] + self._apply_protocol_defaults() elif key == 'connection': - if self.selected_values['protocol'] == 'wireguard': - connections = ['browser-only', 'system-wide'] - else: - connections = ['tor', 'just proxy'] + connections = self._connections_for_protocol() + if self.selected_values[key] not in connections: + self.selected_values[key] = connections[0] current_index = connections.index(self.selected_values[key]) previous_index = (current_index - 1) % len(connections) self.selected_values[key] = connections[previous_index] self.update_ui_state_for_connection() elif key == 'location': - locations = self.connection_manager.get_location_list() - if self.selected_values['protocol'] == 'hidetor': - locations = [loc for loc in locations if (l := self.connection_manager.get_location_info( - loc)) and hasattr(l, 'is_proxy_capable') and l.is_proxy_capable] + locations = self._available_locations_for_protocol() if locations and self.selected_values[key] in locations: current_index = locations.index(self.selected_values[key]) @@ -617,36 +622,21 @@ class FastRegistrationPage(Page): def show_next_value(self, key): if key == 'protocol': - protocols = ['wireguard', 'hidetor'] + protocols = list(self.PROTOCOLS) current_index = protocols.index(self.selected_values[key]) next_index = (current_index + 1) % len(protocols) self.selected_values[key] = protocols[next_index] - if self.selected_values[key] == 'wireguard': - self.selected_values['connection'] = 'browser-only' - else: - self.selected_values['connection'] = 'tor' - loc_info = self.connection_manager.get_location_info( - self.selected_values['location']) - if not (loc_info and hasattr(loc_info, 'is_proxy_capable') and loc_info.is_proxy_capable): - locations = self.connection_manager.get_location_list() - proxy_locations = [loc for loc in locations if (l := self.connection_manager.get_location_info( - loc)) and hasattr(l, 'is_proxy_capable') and l.is_proxy_capable] - if proxy_locations: - self.selected_values['location'] = proxy_locations[0] + self._apply_protocol_defaults() elif key == 'connection': - if self.selected_values['protocol'] == 'wireguard': - connections = ['browser-only', 'system-wide'] - else: - connections = ['tor', 'just proxy'] + connections = self._connections_for_protocol() + if self.selected_values[key] not in connections: + self.selected_values[key] = connections[0] current_index = connections.index(self.selected_values[key]) next_index = (current_index + 1) % len(connections) self.selected_values[key] = connections[next_index] self.update_ui_state_for_connection() elif key == 'location': - locations = self.connection_manager.get_location_list() - if self.selected_values['protocol'] == 'hidetor': - locations = [loc for loc in locations if (l := self.connection_manager.get_location_info( - loc)) and hasattr(l, 'is_proxy_capable') and l.is_proxy_capable] + locations = self._available_locations_for_protocol() if locations and self.selected_values[key] in locations: current_index = locations.index(self.selected_values[key]) @@ -693,6 +683,57 @@ class FastRegistrationPage(Page): self.create_interface_elements() + def _connections_for_protocol(self): + protocol = self.selected_values['protocol'] + if protocol == 'wireguard': + return ['browser-only', 'system-wide'] + if protocol in self.SINGBOX_PROTOCOLS: + return ['system-wide'] + return ['tor', 'just proxy'] + + def _apply_protocol_defaults(self): + protocol = self.selected_values['protocol'] + if protocol == 'wireguard': + self.selected_values['connection'] = 'browser-only' + elif protocol in self.SINGBOX_PROTOCOLS: + self.selected_values['connection'] = 'system-wide' + else: + self.selected_values['connection'] = 'tor' + self._select_valid_location_for_protocol() + + def _is_enabled_capability(self, value): + return value in (True, 1, "1", "true", "True") + + def _location_supports_selected_protocol(self, location_info): + protocol = self.selected_values['protocol'] + if protocol == 'hidetor': + return bool(getattr(location_info, 'is_proxy_capable', False)) + capability_by_protocol = { + "wireguard": "is_wireguard_capable", + "hysteria2": "is_hysteria2_capable", + "vless": "is_vless_capable", + } + capability_name = capability_by_protocol.get(protocol) + if capability_name is None: + return True + return self._is_enabled_capability(getattr(location_info, capability_name, False)) + + def _available_locations_for_protocol(self): + locations = self.connection_manager.get_location_list() + return [ + loc for loc in locations + if (info := self.connection_manager.get_location_info(loc)) + and self._location_supports_selected_protocol(info) + ] + + def _select_valid_location_for_protocol(self): + locations = self._available_locations_for_protocol() + if not locations: + self.selected_values['location'] = '' + return + if self.selected_values['location'] not in locations: + self.selected_values['location'] = locations[0] + def go_back(self): menu_page = self.custom_window.navigator.get_cached("menu") if menu_page is not None and hasattr(menu_page, 'refresh_menu_buttons'): @@ -734,6 +775,9 @@ class FastRegistrationPage(Page): if self.selected_values['protocol'] == 'wireguard': self.create_wireguard_profile(profile_data) + elif self.selected_values['protocol'] in self.SINGBOX_PROTOCOLS: + self.create_singbox_profile(profile_data) + return else: self.create_tor_profile(profile_data) @@ -778,6 +822,58 @@ class FastRegistrationPage(Page): profile_id, profiles.keys()) + def create_singbox_profile(self, profile_data): + location_info = self.connection_manager.get_location_info( + profile_data['location']) + if not location_info: + self.update_status.update_status('Invalid location selected') + return + + profiles = self._prefetched_profiles if self._prefetched_profiles is not None else ProfileController.get_all() + profile_id = self.get_next_available_profile_id(profiles) + existing_profile_ids = tuple(profiles.keys()) + + if not singbox_prereqs_installed(): + self.show_singbox_prereq_setup(profile_data, profile_id, existing_profile_ids) + return + + self.finish_singbox_profile_creation(profile_data, profile_id, existing_profile_ids) + + def show_singbox_prereq_setup(self, profile_data, 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_singbox_profile_creation(profile_data, profile_id, existing_profile_ids)) + + def finish_singbox_profile_creation(self, profile_data, profile_id, existing_profile_ids): + location_info = self.connection_manager.get_location_info( + profile_data['location']) + if not location_info: + self.update_status.update_status('Invalid location selected') + return + + profile_data_for_resume = { + 'id': profile_id, + 'name': profile_data['name'], + 'country_code': location_info.country_code, + 'code': location_info.code, + 'application': '', + 'connection_type': profile_data['protocol'], + 'resolution': '', + } + + self._spawn_create_profile_worker( + 'CREATE_SYSTEM_PROFILE', profile_data_for_resume, 'system') + + if ProfileController.get(profile_id) is not None: + append_profile_to_visual_order( + getattr(self.update_status, 'gui_config_file', None), + profile_id, + existing_profile_ids) + self.go_back() + def create_tor_profile(self, profile_data): location_info = self.connection_manager.get_location_info( profile_data['location']) diff --git a/gui/v2/ui/pages/ticket_crypto_picker_page.py b/gui/v2/ui/pages/ticket_crypto_picker_page.py index 41a5c23..2c79f00 100755 --- a/gui/v2/ui/pages/ticket_crypto_picker_page.py +++ b/gui/v2/ui/pages/ticket_crypto_picker_page.py @@ -1,12 +1,11 @@ import os -import shutil from PyQt6.QtWidgets import QButtonGroup, QMessageBox, QPushButton from PyQt6.QtGui import QIcon from PyQt6.QtCore import QSize from PyQt6 import QtCore -from core.Constants import Constants +from core.services.prepare_tickets.ticket_tracker import delete_ticket_data from gui.v2.ui.pages.Page import Page from gui.v2.ui.popups.message_box import style_message_box, mark_confirm_button @@ -135,29 +134,14 @@ class TicketCryptoPickerPage(Page): self.update_status.update_status("Cancelled.") def _restart_payment_after_wipe(self): - if not self._delete_ticket_data(): + if not delete_ticket_data(): + self.update_status.update_status("Could not wipe ticket data.") return self.bypass_existing = False currency_btn = self.buttonGroup.checkedButton() if currency_btn: self.start_initiate_payment(currency_btn.property('currency')) - def _delete_ticket_data(self): - paths = [ - Constants.TICKET_TRACKER_PATH, - os.path.join(Constants.HV_TICKETING_CONFIG_HOME, "billing_choices.json"), - ] - try: - for path in paths: - if os.path.exists(path): - os.remove(path) - 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): self.update_status.update_status(f"Payment error: {msg}")