import functools import os import threading from PyQt6 import QtCore, QtGui from PyQt6.QtCore import Qt, QSize from PyQt6.QtGui import QIcon, QPainter, QPixmap from PyQt6.QtWidgets import ( QApplication, QButtonGroup, QFrame, QHBoxLayout, QLabel, QLineEdit, QMessageBox, QPushButton, QScrollArea, QTextEdit, QVBoxLayout, QWidget, ) from core.controllers.ConfigurationController import ConfigurationController from core.controllers.ProfileController import ProfileController from core.controllers.tickets.UseTicketController import ( do_we_use_a_random_ticket, get_unused_tickets, ) from core.models.session.SessionProfile import SessionProfile from core.models.system.SystemProfile import SystemProfile from gui.v2.infrastructure.setup_observers import ticket_observer from gui.v2.actions.profile_order import normalize_profile_order from gui.v2.ui.pages.Page import Page from gui.v2.ui.pages.location_verification_page import LocationVerificationPage from gui.v2.ui.styles.styles import SCROLLBAR_CYAN_QSS from gui.v2.ui.popups.confirmation_popup import ConfirmationPopup from gui.v2.ui.popups.endpoint_verification_popup import EndpointVerificationPopup from gui.v2.ui.popups.ticket_data_loss_popup import TicketDataLossPopup from gui.v2.workers.worker import Worker from gui.v2.workers.worker_thread import WorkerThread class MenuPage(Page): def __init__(self, page_stack=None, main_window=None, parent=None): super().__init__("Menu", page_stack, main_window, parent) self.main_window = main_window self.connection_manager = main_window.connection_manager self.is_tor_mode = main_window.is_tor_mode self.is_animating = main_window.is_animating self.animation_step = main_window.animation_step self.btn_path = main_window.btn_path self.page_stack = page_stack self.profile_info = {} self.additional_labels = [] self.update_status = main_window self.title.setGeometry(400, 40, 380, 30) self.title.setText("Load Profile") self.scroll_area = QScrollArea(self) self.scroll_area.setGeometry(420, 80, 370, 350) self.scroll_area.setWidgetResizable(True) self.scroll_area.setHorizontalScrollBarPolicy( Qt.ScrollBarPolicy.ScrollBarAlwaysOff) self.scroll_area.setVerticalScrollBarPolicy( Qt.ScrollBarPolicy.ScrollBarAsNeeded) self.scroll_area.setStyleSheet(SCROLLBAR_CYAN_QSS) self.scroll_widget = QWidget() self.scroll_widget.setStyleSheet("background-color: transparent;") self.scroll_area.setWidget(self.scroll_widget) self.not_connected_profile = os.path.join( self.btn_path, "button_profile.png") self.connected_profile = os.path.join( self.btn_path, "button_session_profile.png") self.system_wide_connected_profile = os.path.join( self.btn_path, "button_system_profile.png") self.connected_tor_profile = os.path.join( self.btn_path, "button_session_tor.png") self.worker = None self.IsSystem: int = 0 self.button_states = {} self.is_system_connected = False self.profile_button_map = {} self.font_style = f"font-family: '{main_window.open_sans_family}';" if main_window.open_sans_family else "" self.create_interface_elements() def showEvent(self, event): super().showEvent(event) if hasattr(self, 'buttons'): for button in self.buttons: parent = button.parent() if parent: verification_icons = parent.findChildren(QPushButton) for icon in verification_icons: if icon.geometry().width() == 20 and icon.geometry().height() == 20: icon.setEnabled( self.connection_manager.is_synced()) def on_update_check_finished(self): reply = QMessageBox.question(self, 'Update Available', 'An update is available. Would you like to download it?', QMessageBox.StandardButton.Yes | QMessageBox.StandardButton.No) if reply == QMessageBox.StandardButton.Yes: self.worker = WorkerThread('DOWNLOAD_UPDATE') self.worker.start() else: self.update_status.update_status('Update canceled') def create_interface_elements(self): for icon_name, function, position in [ ("create_profile", self.create_prof, (410, 440, 110, 60)), ("edit_profile", self.edit_prof, (540, 440, 110, 60)), ("launch", self.launch, (0, 440, 185, 63)), ("disconnect", self.disconnect, (200, 433, 185, 73)), ("disconnect_system_wide", self.disconnect, (200, 433, 185, 73)), ("just", self.connect, (200, 433, 185, 73)), ("just_session", self.connect, (200, 433, 185, 73)), ("settings", self.settings_gui, (670, 440, 110, 60)), ]: boton = QPushButton(self) boton.setGeometry(QtCore.QRect(*position)) boton.setObjectName("create_edit_settings_buttons") icon = QtGui.QIcon(os.path.join(self.btn_path, f"{icon_name}.png")) boton.setIcon(icon) boton.setIconSize(boton.size()) boton.clicked.connect(functools.partial(function)) boton.setEnabled(False) if icon_name == "disconnect": boton.setEnabled(True) boton.setVisible(False) self.disconnect_button = boton if icon_name == "launch": self.boton_launch = boton self.boton_launch.setVisible(False) if icon_name == "create_profile": self.boton_create = boton self.boton_create.setEnabled(True) if icon_name == "edit_profile": self.boton_edit = boton if icon_name == "just": boton.setVisible(False) self.boton_just = boton self.connect_button = boton if icon_name == "just_session": self.boton_just_session = boton self.connect_button = boton if icon_name == "disconnect_system_wide": boton.setEnabled(True) boton.setVisible(False) self.disconnect_system_wide_button = boton if icon_name == "settings": self.settings_button = boton self.settings_button.setEnabled(True) self.create() def _make_confirmation_popup(self, message, button_text='Proceed'): popup = ConfirmationPopup( self, message=message, action_button_text=button_text, cancel_button_text="Cancel") popup.setWindowModality(Qt.WindowModality.ApplicationModal) return popup def _show_disconnect_confirmation(self, connected_profiles, button_text='Disable', message=None): self.popup = self._make_confirmation_popup(message, button_text) def on_result(result): if result: self.update_status.update_status('Disabling profiles...') self.worker_thread = WorkerThread( 'DISABLE_ALL_PROFILES', profile_data=connected_profiles) self.worker_thread.text_output.connect( lambda text: self.update_status.update_status(text)) self.worker_thread.start() self.popup.finished.connect(on_result) self.popup.show() def delete_profile(self): self.popup = ConfirmationPopup(self, message="Are you sure you want to\ndelete this profile?", action_button_text="Delete", cancel_button_text="Cancel") self.popup.setWindowModality(Qt.WindowModality.ApplicationModal) self.popup.finished.connect( lambda result: self.on_popup_finished(result)) self.popup.show() def on_popup_finished(self, result): if result: profile_id = self.reverse_id self.update_status.update_status(f'Deleting profile...') self.worker_thread = WorkerThread( 'DESTROY_PROFILE', profile_data={'id': profile_id}) self.worker_thread.text_output.connect(self.delete_status_update) self.worker_thread.start() else: pass def delete_status_update(self, text): self.update_status.update_status(text) self.eliminacion() def show_disconnect_button(self, toggle, profile_type, target_button): try: if target_button.isChecked(): if profile_type == 1: self.disconnect_button.setVisible(toggle) self.boton_just_session.setVisible(not toggle) else: self.disconnect_system_wide_button.setVisible(toggle) self.boton_just.setVisible(not toggle) except RuntimeError: pass def match_core_profiles(self, profiles_dict): new_dict = {} profile_ids = normalize_profile_order( getattr(self.update_status, 'gui_config_file', None), profiles_dict.keys()) for idx in profile_ids: profile = profiles_dict[idx] new_profile = {} protocol = profile.connection.code location = f'{profile.location.country_code}_{profile.location.code}' new_profile['location'] = location if protocol == 'wireguard': new_profile['protocol'] = 'wireguard' else: new_profile['protocol'] = 'hidetor' connection_type = 'browser-only' if isinstance( profile, SessionProfile) else 'system-wide' if protocol == 'wireguard': new_profile['connection'] = connection_type elif protocol == 'tor': new_profile['connection'] = protocol else: new_profile['connection'] = 'just proxy' if isinstance(profile, SessionProfile): browser = profile.application_version.application_code else: browser = 'unknown' if browser != 'unknown': new_profile['browser'] = browser new_profile['browser_version'] = profile.application_version.version_number new_profile['browser_supported'] = profile.application_version.supported else: new_profile['browser'] = 'unknown browser' new_profile['browser_supported'] = False resolution = profile.resolution if hasattr( profile, 'resolution') else 'None' new_profile['dimentions'] = resolution new_profile['name'] = profile.name new_dict[f'Profile_{idx}'] = new_profile return new_dict def create(self): self.boton_just.setEnabled(False) self.boton_just_session.setEnabled(False) self.boton_edit.setEnabled(False) if hasattr(self, 'verification_button'): self.verification_button.setEnabled(False) self.profiles_data = self.match_core_profiles( ProfileController.get_all()) self.number_of_profiles = len(self.profiles_data) self.profile_info.update(self.profiles_data) self.button_group = QButtonGroup(self) self.buttons = [] for profile_name, profile_value in self.profiles_data.items(): self.create_profile_widget(profile_name, profile_value) self.update_scroll_widget_size() def refresh_profiles_data(self): self.profiles_data = self.match_core_profiles( ProfileController.get_all()) self.number_of_profiles = len(self.profiles_data) self.profile_info.update(self.profiles_data) for profile_name, profile_value in self.profiles_data.items(): self.create_profile_widget(profile_name, profile_value) self.update_scroll_widget_size() def refresh_menu_buttons(self): profiles = ProfileController.get_all() self.button_states.clear() self.connection_manager._connected_profiles.clear() self.IsSystem = 0 for profile_id, profile in profiles.items(): if ProfileController.is_enabled(profile): self.connection_manager.add_connected_profile(profile_id) if isinstance(profile, SessionProfile): if profile.connection and profile.connection.code == 'tor': self.button_states[profile_id] = self.connected_tor_profile else: self.button_states[profile_id] = self.connected_profile elif isinstance(profile, SystemProfile): self.button_states[profile_id] = self.system_wide_connected_profile self.IsSystem += 1 if self.IsSystem > 0: self.update_status.update_image( appearance_value='background_connected') else: self.update_status.update_image() for widget in self.scroll_widget.findChildren(QLabel): widget.setParent(None) for widget in self.scroll_widget.findChildren(QPushButton): widget.setParent(None) self.profile_button_map.clear() self.button_group = QButtonGroup(self) self.buttons = [] self.create() def update_scroll_widget_size(self): if self.number_of_profiles == 0: return rows = (self.number_of_profiles + 1) // 2 height = rows * 120 self.scroll_widget.setFixedSize(370, height) def create_profile_widget(self, key, value): profile_id = int(key.split('_')[1]) parent_label = self.create_parent_label(profile_id) button = self.create_profile_button(parent_label, profile_id, key) self.profile_button_map[profile_id] = button self.create_profile_name_label(parent_label, value["name"]) self.create_profile_icons(parent_label, value) def create_parent_label(self, profile_id): parent_label = QLabel(self.scroll_widget) profile_list = list(self.profiles_data.keys()) index = profile_list.index(f'Profile_{profile_id}') row, column = divmod(index, 2) parent_label.setGeometry(column * 179, row * 120, 175, 100) parent_label.setVisible(True) return parent_label def create_profile_button(self, parent_label, profile_id, key): button = QPushButton(parent_label) button.setGeometry(0, 0, 175, 60) button.setStyleSheet(""" QPushButton { background-color: transparent; border: none; } QPushButton:hover { background-color: rgba(200, 200, 200, 50); } QPushButton:checked { background-color: rgba(200, 200, 200, 80); } """) icon = QIcon(self.button_states.get( profile_id, self.not_connected_profile)) button.setIcon(icon) button.setIconSize(QtCore.QSize(175, 60)) button.show() button.setCheckable(True) self.button_group.addButton(button) self.buttons.append(button) button.clicked.connect( lambda checked, pid=profile_id: self.print_profile_details(f"Profile_{pid}")) return button def create_profile_name_label(self, parent_label, name): child_label = QLabel(parent_label) child_label.setGeometry(0, 65, 175, 30) child_label.setText(name) child_label.setAlignment(Qt.AlignmentFlag.AlignCenter) child_label.setAttribute( Qt.WidgetAttribute.WA_TransparentForMouseEvents) child_label.show() def create_profile_icons(self, parent_label, value): connection_type = value.get('connection', '') if connection_type in ['tor', 'just proxy']: self.create_tor_proxy_icons(parent_label, value, connection_type) else: self.create_other_icons(parent_label, value, connection_type) def create_tor_proxy_icons(self, parent_label, value, connection_type): for j, label_name in enumerate(['protocol', 'location', 'browser']): child_label = QLabel(parent_label) child_label.setObjectName(f"label_{j}") child_label.setGeometry(3 + j * 60, 5, 50, 50) child_label.setObjectName("label_profiles") child_label.setAttribute( Qt.WidgetAttribute.WA_TransparentForMouseEvents) child_label.setStyleSheet( "background-color: rgba(128, 128, 128, 0.5); border-radius: 25px;") image_path = self.get_icon_path(label_name, value, connection_type) pixmap = QPixmap(image_path) if pixmap.isNull() and label_name == 'location': fallback_path = os.path.join( self.btn_path, "default_location_mini.png") pixmap = QPixmap(fallback_path) if pixmap.isNull(): pixmap = QPixmap(50, 50) pixmap.fill(QtGui.QColor(200, 200, 200)) if pixmap.isNull() and label_name == 'browser': fallback_path = os.path.join( self.btn_path, "default_browser_mini.png") pixmap = QPixmap(fallback_path) if pixmap.isNull(): pixmap = QPixmap(50, 50) pixmap.fill(QtGui.QColor(200, 200, 200)) child_label.setPixmap(pixmap) child_label.show() def create_other_icons(self, parent_label, value, connection_type): for j, label_name in enumerate(['protocol', 'location', 'browser']): child_label = QLabel(parent_label) child_label.setObjectName(f"label_{j}") child_label.setGeometry(3 + j * 60, 5, 50, 50) child_label.setObjectName("label_profiles") child_label.setAttribute( Qt.WidgetAttribute.WA_TransparentForMouseEvents) child_label.setStyleSheet( "background-color: rgba(128, 128, 128, 0.5); border-radius: 25px;") image_path = self.get_icon_path(label_name, value, connection_type) pixmap = QPixmap(image_path) if pixmap.isNull() and label_name == 'location': fallback_path = os.path.join( self.btn_path, "default_location_mini.png") pixmap = QPixmap(fallback_path) if pixmap.isNull(): pixmap = QPixmap(50, 50) pixmap.fill(QtGui.QColor(200, 200, 200)) if pixmap.isNull() and label_name == 'browser': fallback_path = os.path.join( self.btn_path, "default_browser_mini.png") pixmap = QPixmap(fallback_path) if pixmap.isNull(): pixmap = QPixmap(50, 50) pixmap.fill(QtGui.QColor(200, 200, 200)) if label_name == 'browser' and not value.get('browser_supported', True) and value.get('browser') != 'unknown browser': warning_pixmap = QPixmap( os.path.join(self.btn_path, 'warning.png')) painter = QPainter(pixmap) painter.drawPixmap(pixmap.width() - 22, pixmap.height() - 21, warning_pixmap) painter.end() child_label.setPixmap(pixmap) child_label.show() def get_icon_path(self, label_name, value, connection_type): if label_name == 'protocol': if connection_type == 'tor': return os.path.join(self.btn_path, "toricon_mini.png") elif connection_type == 'just proxy': return os.path.join(self.btn_path, "just proxy_mini.png") return os.path.join(self.btn_path, "wireguard_mini.png") elif label_name == 'browser': if connection_type == 'system-wide': return os.path.join(self.btn_path, "wireguard_system_wide.png") else: return os.path.join(self.btn_path, f"{value[label_name]} latest_mini.png") return os.path.join(self.btn_path, f"icon_mini_{value[label_name]}.png") def truncate_key(self, text, max_length=50): if not text or text == "N/A" or len(text) <= max_length: return text start_len = max_length // 2 - 2 end_len = max_length // 2 - 2 return text[:start_len] + "....." + text[-end_len:] def create_verification_widget(self, profile_obj): verification_widget = QWidget(self) verification_widget.setGeometry(0, 90, 400, 300) verification_widget.setStyleSheet("background: transparent;") verification_layout = QVBoxLayout(verification_widget) verification_layout.setContentsMargins(20, 20, 20, 20) verification_layout.setSpacing(15) operator = None if profile_obj.location and profile_obj.location.operator: operator = profile_obj.location.operator info_items = [ ("Operator Name", "operator_name"), ("Nostr Key", "nostr_public_key"), ("HydraVeil Key", "hydraveil_public_key"), ] for label, key in info_items: container = QWidget() container.setStyleSheet("background: transparent;") container_layout = QHBoxLayout(container) container_layout.setContentsMargins(0, 0, 0, 0) container_layout.setSpacing(10) label_widget = QLabel(label + ":") label_widget.setStyleSheet( f"color: white; font-size: 12px; {self.font_style}") label_widget.setMinimumWidth(120) container_layout.addWidget(label_widget) value_widget = QLineEdit() value_widget.setReadOnly(True) if operator: if key == "operator_name": value = operator.name or "N/A" elif key == "nostr_public_key": value = operator.nostr_public_key or "N/A" value = self.truncate_key( value) if value != "N/A" else value elif key == "hydraveil_public_key": value = operator.public_key or "N/A" value = self.truncate_key( value) if value != "N/A" else value else: value = "N/A" else: value = "N/A" value_widget.setText(value) value_widget.setCursorPosition(0) value_widget.setStyleSheet(f""" QLineEdit {{ color: white; background: transparent; border: none; border-bottom: 1px solid rgba(255, 255, 255, 0.3); padding: 5px 0px; font-size: 12px; {self.font_style} }} """) container_layout.addWidget(value_widget, 1) checkmark_widget = QLabel("✓") checkmark_widget.setStyleSheet( f"color: #4CAF50; font-size: 20px; font-weight: bold; {self.font_style}") checkmark_widget.setFixedSize(20, 20) checkmark_widget.setAlignment(Qt.AlignmentFlag.AlignCenter) if value and value != "N/A": checkmark_widget.show() else: checkmark_widget.hide() container_layout.addWidget(checkmark_widget) copy_button = QPushButton() copy_button.setIcon( QIcon(os.path.join(self.btn_path, "paste_button.png"))) copy_button.setIconSize(QSize(24, 24)) copy_button.setFixedSize(30, 30) copy_button.setStyleSheet(f""" QPushButton {{ background: transparent; border: none; }} QPushButton:hover {{ background: rgba(255, 255, 255, 0.1); border-radius: 4px; }} """) if operator: if key == "operator_name": full_value = operator.name or "N/A" elif key == "nostr_public_key": full_value = operator.nostr_public_key or "N/A" elif key == "hydraveil_public_key": full_value = operator.public_key or "N/A" else: full_value = "N/A" else: full_value = "N/A" copy_button.clicked.connect( lambda checked=False, val=full_value: self.copy_verification_value(val)) container_layout.addWidget(copy_button) verification_layout.addWidget(container) verification_widget.show() return verification_widget def copy_verification_value(self, value): if value and value != "N/A": clipboard = QApplication.clipboard() clipboard.setText(value) self.update_status.update_status( "Verification value copied to clipboard") def print_profile_details(self, profile_name): self.reverse_id = int(profile_name.split('_')[1]) target_button = self.profile_button_map.get(self.reverse_id) if target_button: self.connection_manager.set_profile_button_objects( self.reverse_id, target_button) else: print(f"No button found for profile {self.reverse_id}") is_connected = self.connection_manager.is_profile_connected( int(self.reverse_id)) if is_connected: self.boton_edit.setEnabled(False) else: self.boton_edit.setEnabled(True) profile = self.profile_info.get(profile_name) self.boton_launch.setEnabled(True) self.boton_just.setEnabled(True) self.boton_just_session.setEnabled(True) self.boton_create.setEnabled(True) if hasattr(self, 'buttons'): for button in self.buttons: parent = button.parent() if parent: verification_icons = parent.findChildren(QPushButton) for icon in verification_icons: if icon.geometry().width() == 20 and icon.geometry().height() == 20: icon.setEnabled( self.connection_manager.is_synced()) for label in self.additional_labels: label.deleteLater() self.additional_labels.clear() self.change_connect_button() if profile: self.update_status.current_profile_id = self.reverse_id profile_number = profile_name.split("_")[1] name = profile.get("name", "") protocol = profile.get("protocol", "") location = profile.get("location", "") connection = profile.get("connection", "") country_garaje = profile.get("country_garaje", "") profile_obj = ProfileController.get(self.reverse_id) is_profile_enabled = self.connection_manager.is_profile_connected( self.reverse_id) show_verification_widget = False label_principal = None label_tor = None text_color = "white" operator_name = profile_obj.location.operator.name if profile_obj.location and profile_obj.location.operator else "" if protocol.lower() == "wireguard" and is_profile_enabled and profile_obj and profile_obj.connection and profile_obj.connection.code == 'wireguard': verification_widget = self.create_verification_widget( profile_obj) self.additional_labels.append(verification_widget) show_verification_widget = True label_principal = None elif operator_name != 'Simplified Privacy' and protocol.lower() == "wireguard": if profile_obj.is_session_profile(): text_color = "black" label_background = QLabel(self) label_background.setGeometry(0, 60, 410, 354) pixmap_bg = QPixmap(os.path.join( self.btn_path, "browser only.png")) label_background.setPixmap(pixmap_bg) label_background.setScaledContents(True) label_background.show() label_background.lower() self.additional_labels.append(label_background) l_img = QLabel(self) l_img.setGeometry(20, 125, 100, 100) pixmap_loc = QPixmap(os.path.join( self.btn_path, f"icon_{location}.png")) l_img.setPixmap(pixmap_loc) l_img.setScaledContents(True) l_img.show() self.additional_labels.append(l_img) l_name = f"{profile_obj.location.country_name}, {profile_obj.location.name}" if profile_obj.location else "" o_name = profile_obj.location.operator.name if profile_obj.location and profile_obj.location.operator else "" n_key = profile_obj.location.operator.nostr_public_key if profile_obj.location and profile_obj.location.operator else "" info_txt = QTextEdit(self) info_txt.setGeometry(130, 110, 260, 250) info_txt.setReadOnly(True) info_txt.setFrameStyle(QFrame.Shape.NoFrame) info_txt.setStyleSheet( f"background: transparent; border: none; color: {text_color}; font-size: 17px;") info_txt.setVerticalScrollBarPolicy( Qt.ScrollBarPolicy.ScrollBarAlwaysOff) info_txt.setHorizontalScrollBarPolicy( Qt.ScrollBarPolicy.ScrollBarAlwaysOff) info_txt.setWordWrapMode( QtGui.QTextOption.WrapMode.WrapAtWordBoundaryOrAnywhere) info_txt.setText( f"Location: {l_name}\n\nOperator: {o_name}") info_txt.show() self.additional_labels.append(info_txt) nostr_txt = QTextEdit(self) nostr_txt.setGeometry(20, 260, 370, 80) nostr_txt.setReadOnly(True) nostr_txt.setFrameStyle(QFrame.Shape.NoFrame) nostr_txt.setStyleSheet( f"background: transparent; border: none; color: {text_color}; font-size: 17px;") nostr_txt.setVerticalScrollBarPolicy( Qt.ScrollBarPolicy.ScrollBarAlwaysOff) nostr_txt.setHorizontalScrollBarPolicy( Qt.ScrollBarPolicy.ScrollBarAlwaysOff) nostr_txt.setWordWrapMode( QtGui.QTextOption.WrapMode.WrapAnywhere) nostr_txt.setText(f"Nostr: {n_key}") nostr_txt.show() self.additional_labels.append(nostr_txt) elif protocol.lower() in ["wireguard", "open", "residential", "hidetor"]: label_principal = QLabel(self) label_principal.setGeometry(0, 90, 400, 300) pixmap = QPixmap(os.path.join( self.btn_path, f"{protocol}_{location}.png")) label_principal.setPixmap(pixmap) label_principal.setScaledContents(True) label_principal.show() self.additional_labels.append(label_principal) if protocol.lower() in ["wireguard", "open", "residential", "hidetor"]: 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': verified_icon = QLabel(self) verified_icon.setGeometry(20, 50, 100, 80) verified_pixmap = QPixmap(os.path.join( self.btn_path, "verified_profile.png")) verified_icon.setPixmap(verified_pixmap) verified_icon.setScaledContents(True) verified_icon.show() self.additional_labels.append(verified_icon) if protocol.lower() == "hidetor" and not show_verification_widget: if label_principal: label_principal.setGeometry(0, 80, 400, 300) label_background = QLabel(self) label_background.setGeometry(0, 60, 410, 354) if connection == 'just proxy': image_path = os.path.join( self.btn_path, f"icon_{location}.png") else: image_path = os.path.join( self.btn_path, f"hdtor_{location}.png") pixmap = QPixmap(image_path) label_background.setPixmap(pixmap) label_background.show() label_background.setScaledContents(True) label_background.lower() self.additional_labels.append(label_background) if protocol.lower() == "wireguard" and connection.lower() == "browser-only" and not show_verification_widget: if label_principal: label_principal.setGeometry(0, 80, 400, 300) label_background = QLabel(self) label_background.setGeometry(0, 60, 410, 354) is_supported = profile.get('browser_supported', False) image_name = "browser only.png" if is_supported else "unsupported_browser_only.png" pixmap = QPixmap(os.path.join(self.btn_path, image_name)) label_background.setPixmap(pixmap) label_background.show() label_background.setScaledContents(True) label_background.lower() self.additional_labels.append(label_background) if protocol.lower() == "residential" and connection.lower() == "tor": label_background = QLabel(self) label_background.setGeometry(0, 60, 410, 354) pixmap = QPixmap(os.path.join( self.btn_path, "browser_tor.png")) label_background.setPixmap(pixmap) label_background.show() label_background.setScaledContents(True) self.additional_labels.append(label_background) label_garaje = QLabel(self) label_garaje.setGeometry(5, 110, 400, 300) pixmap = QPixmap(os.path.join( self.btn_path, f"{country_garaje} garaje.png")) label_garaje.setPixmap(pixmap) label_garaje.show() label_garaje.setScaledContents(True) self.additional_labels.append(label_garaje) label_tor = QLabel(self) label_tor.setGeometry(330, 300, 75, 113) pixmap = QPixmap(os.path.join(self.btn_path, "tor 86x130.png")) if label_tor: label_tor.setPixmap(pixmap) label_tor.show() label_tor.setScaledContents(True) self.additional_labels.append(label_tor) if protocol.lower() == "residential" and connection.lower() == "just proxy": label_background = QLabel(self) label_background.setGeometry(0, 60, 410, 354) pixmap = QPixmap(os.path.join( self.btn_path, "browser_just_proxy.png")) label_background.setPixmap(pixmap) label_background.show() label_background.setScaledContents(True) self.additional_labels.append(label_background) label_garaje = QLabel(self) label_garaje.setGeometry(5, 110, 400, 300) pixmap = QPixmap(os.path.join( self.btn_path, f"{country_garaje} garaje.png")) label_garaje.setPixmap(pixmap) label_garaje.show() label_garaje.setScaledContents(True) self.additional_labels.append(label_garaje) editar_profile = {"profile_number": profile_number, "name": name, "protocol": protocol} self.add_selected_profile(editar_profile) self.current_profile_location = location def show_profile_verification(self, profile_id): profile = ProfileController.get(profile_id) if not profile or not profile.location: return location_key = f'{profile.location.country_code}_{profile.location.code}' location_info = self.connection_manager.get_location_info(location_key) if not location_info: return location_icon_name = None btn_path = self.btn_path for icon_file in os.listdir(btn_path): if icon_file.startswith('button_') and icon_file.endswith('.png'): icon_name = icon_file.replace( 'button_', '').replace('.png', '') test_loc = self.connection_manager.get_location_info(icon_name) if test_loc and test_loc.country_code == location_info.country_code and test_loc.code == location_info.code: location_icon_name = icon_name break if location_icon_name: verification_page = LocationVerificationPage( self.page_stack, self.custom_window, location_icon_name, self) self.page_stack.addWidget(verification_page) self.page_stack.setCurrentIndex( self.page_stack.indexOf(verification_page)) def launch(self): pass def connect(self): self.boton_just.setEnabled(False) self.boton_just_session.setEnabled(False) profile = ProfileController.get(int(self.reverse_id)) is_tor = self.update_status.get_current_connection() == 'tor' if profile.connection.code == 'tor' and not profile.application_version.installed and not is_tor: message = f'You are using a Tor profile, but the associated browser is not installed. If you want the browser to be downloaded with Tor, you must switch to a Tor connection. Otherwise, proceed with a clearweb connection.' else: self.get_billing_code_by_id() return self.popup = self._make_confirmation_popup(message, button_text='Proceed') self.popup.finished.connect( lambda result: self._handle_popup_result(result)) self.popup.show() def _handle_popup_result(self, result, change_screen=False): if result: if change_screen: self._route_to_billing_entry() else: self.get_billing_code_by_id() else: self.popup.close() def _route_to_billing_entry(self): try: unused = get_unused_tickets(ticket_observer) random_setting, _ = do_we_use_a_random_ticket(ticket_observer) except Exception: unused = {'valid': False} random_setting = None has_tickets = isinstance(unused, dict) and unused.get('valid') and len(unused.get('data', [])) > 0 random_off = random_setting is None if has_tickets and random_off: chooser = self.custom_window.navigator.get_cached("ticket_or_billing_choice") if chooser: chooser.populate() self.page_stack.setCurrentWidget(chooser) return id_page = self.custom_window.navigator.get_cached("id") if id_page: self.page_stack.setCurrentWidget(id_page) else: self.custom_window.navigator.navigate("id") def disconnect(self): self.disconnect_button.setEnabled(False) self.disconnect_system_wide_button.setEnabled(False) self.update_status.update_status('Disconnecting...') profile_data = { 'id': int(self.reverse_id) } profile = ProfileController.get(int(self.reverse_id)) is_session_profile = isinstance(profile, SessionProfile) if not is_session_profile: connected_profiles = self.connection_manager.get_connected_profiles() if len(connected_profiles) > 1: message = f'Profile{"" if len(connected_profiles) == 1 else "s"} {", ".join(str(profile_id) for profile_id in connected_profiles)} are still connected.\nAll connected session profiles will be disconnected. \nDo you want to proceed?' self._show_disconnect_confirmation( connected_profiles, button_text='Disable', message=message) self.disconnect_button.setEnabled(True) self.disconnect_system_wide_button.setEnabled(True) return self.worker_thread = WorkerThread( 'DISABLE_PROFILE', profile_data=profile_data) self.worker_thread.finished.connect(self.on_disconnect_done) self.worker_thread.start() def on_disconnect_done(self): self.disconnect_button.setEnabled(True) self.disconnect_system_wide_button.setEnabled(True) pass def _config_flag(self, section, key, default=False): try: config = self.update_status._load_gui_config() if config and section in config: return config[section].get(key, default) except Exception: pass return default def create_prof(self): fast_mode = self._config_flag("registrations", "fast_registration_enabled") if fast_mode: if not self.connection_manager.is_synced(): self.update_status.update_status('Syncing in progress..') self.update_status.sync() self.update_status.worker_thread.sync_output.connect( self.on_sync_complete_for_fast_registration) else: self.custom_window.navigator.navigate("fast_registration") return if not self.connection_manager.is_synced(): self.custom_window.navigator.navigate("sync_screen") else: self.custom_window.navigator.navigate("protocol") def on_sync_complete_for_fast_registration(self, available_locations, available_browsers, status, is_tor, locations, all_browsers): if status: self.custom_window.navigator.navigate("fast_registration") else: self.update_status.update_status( 'Sync failed. Please try again later.') def change_connect_button(self): profile = ProfileController.get(int(self.reverse_id)) is_connected = self.connection_manager.is_profile_connected( int(self.reverse_id)) is_session_profile = isinstance(profile, SessionProfile) self.update_button_visibility(is_connected, is_session_profile) self.update_status_message(profile, is_connected) def update_button_visibility(self, is_connected, is_session_profile): self.boton_just_session.setVisible( not is_connected and is_session_profile) self.boton_just.setVisible(not is_connected and not is_session_profile) self.disconnect_button.setVisible(is_connected and is_session_profile) self.disconnect_system_wide_button.setVisible( is_connected and not is_session_profile) def update_status_message(self, profile, is_connected): if is_connected: message = Worker.generate_profile_message(profile, is_enabled=True) self.update_status.enable_marquee(message) else: message = Worker.generate_profile_message( profile, is_enabled=True, idle=True) self.update_status.update_status(message) def edit_prof(self): auto_sync = self._config_flag("registrations", "auto_sync_enabled") if auto_sync and not self.connection_manager.is_synced(): self.update_status.update_status('Syncing in progress..') self.update_status.sync() self.update_status.worker_thread.sync_output.connect( self.on_sync_complete_for_edit_profile) else: self.custom_window.navigator.navigate("editor") def on_sync_complete_for_edit_profile(self, available_locations, available_browsers, status, is_tor, locations, all_browsers): if status: self.custom_window.navigator.navigate("editor") else: self.update_status.update_status( 'Sync failed. Please try again later.') def settings_gui(self): self.custom_window.navigator.navigate("settings") def eliminacion(self): current_state = { 'is_tor_mode': self.is_tor_mode, 'is_animating': self.is_animating, 'animation_step': self.animation_step } self.main_window.toggle_bg.setParent(None) self.main_window.toggle_handle.setParent(None) self.main_window.on_label.setParent(None) self.main_window.off_label.setParent(None) self.refresh_menu_buttons() self.main_window.toggle_bg.setParent(self.main_window.toggle_button) self.main_window.toggle_handle.setParent( self.main_window.toggle_button) self.main_window.on_label.setParent(self.main_window.toggle_button) self.main_window.off_label.setParent(self.main_window.toggle_button) self.main_window.toggle_bg.show() self.main_window.toggle_handle.show() self.main_window.on_label.show() self.main_window.off_label.show() self.is_tor_mode = current_state['is_tor_mode'] self.is_animating = current_state['is_animating'] self.animation_step = current_state['animation_step'] self.create() def get_billing_code_by_id(self, force: bool = False): profile_id = self.update_status.current_profile_id profile_data = { 'id': profile_id, 'force': force } self.update_status.update_status('Attempting to connect...') self.enabling_profile(profile_data) def enabling_profile(self, profile_data): self.worker = Worker(profile_data) self.worker.update_signal.connect(self.update_gui_main_thread) self.worker.change_page.connect(self.change_app_page) self.worker.ticket_data_loss.connect(self.show_ticket_data_loss_popup) thread = threading.Thread(target=self.worker.run) thread.start() def show_ticket_data_loss_popup(self, ticket_number, billing_code): self.custom_window.navigator.navigate("menu") self.update_status.update_status('Critical: invalid billing code from ticket.') self.popup = TicketDataLossPopup( self, ticket_number=ticket_number, billing_code=billing_code, ) self.popup.show() def DisplayInstallScreen(self, package_name): self.custom_window.navigator.navigate("install_system_package") install_page = self.custom_window.navigator.get_cached("install_system_package") if install_page is not None: install_page.configure(package_name=package_name, distro='debian') def handle_enable_force_result(self, result): if result: profile_data = {'id': self.reverse_id, 'ignore_profile_state_conflict': True} if hasattr(self, '_pending_endpoint_verification_ignore') and self._pending_endpoint_verification_ignore: profile_data['ignore_endpoint_verification'] = True self.enabling_profile(profile_data) else: pass if hasattr(self, '_pending_profile_state_conflict_ignore'): delattr(self, '_pending_profile_state_conflict_ignore') if hasattr(self, '_pending_endpoint_verification_ignore'): delattr(self, '_pending_endpoint_verification_ignore') self.popup.close() def handle_endpoint_verification_result(self, result, action, profile_id): if action == "continue": profile_data = {'id': profile_id, 'ignore_endpoint_verification': True} if hasattr(self, '_pending_profile_state_conflict_ignore') and self._pending_profile_state_conflict_ignore: profile_data['ignore_profile_state_conflict'] = True self.enabling_profile(profile_data) elif action == "sync": self.update_status.update_status("Syncing...") self.worker_thread = WorkerThread('SYNC') self.worker_thread.finished.connect( lambda: self.handle_sync_after_verification(profile_id)) self.worker_thread.sync_output.connect( self.update_status.update_values) self.worker_thread.start() else: self.update_status.update_status("Profile enable aborted") if hasattr(self, 'popup'): self.popup.close() def handle_sync_after_verification(self, profile_id): profile_data = {'id': profile_id} if hasattr(self, '_pending_profile_state_conflict_ignore') and self._pending_profile_state_conflict_ignore: profile_data['ignore_profile_state_conflict'] = True self.enabling_profile(profile_data) def update_gui_main_thread(self, text, is_enabled, profile_id, profile_type, profile_connection): if text == "ENDPOINT_VERIFICATION_ERROR": message = "Operator verification failed. Do you want to continue anyway?" self.popup = EndpointVerificationPopup(self, message=message) self.popup.finished.connect( lambda result, action: self.handle_endpoint_verification_result(result, action, profile_id)) self.popup.show() return if text == "PROFILE_STATE_CONFLICT_ERROR": message = f'The profile is already enabled. Do you want to force enable it anyway?' self._pending_profile_state_conflict_ignore = True self.popup = self._make_confirmation_popup(message, button_text='Proceed') self.popup.finished.connect( lambda result: self.handle_enable_force_result(result)) self.popup.show() return if profile_id < 0: self.DisplayInstallScreen(text) return if is_enabled: self.update_status.enable_marquee(str(text)) else: self.update_status.update_status(str(text)) if profile_id is not None: self.on_finished_update_gui( is_enabled, profile_id, profile_type, profile_connection) self.boton_just.setEnabled(True) self.boton_just_session.setEnabled(True) def change_app_page(self, text, Is_changed): self.update_status.update_status(str(text)) if Is_changed: current_connection = self.update_status.get_current_connection() profile = ProfileController.get(int(self.reverse_id)) if current_connection != 'tor' and profile.connection.code == 'tor': message = f'You are using a Tor profile, but the profile subscription is missing or expired. If you want the billing to be done thourgh Tor, you must switch to a Tor connection. Otherwise, proceed with a clearweb connection.' self.popup = self._make_confirmation_popup(message, button_text='Proceed') self.popup.finished.connect( lambda result: self._handle_popup_result(result, True)) self.popup.show() else: self._route_to_billing_entry() self.boton_just.setEnabled(True) self.boton_just_session.setEnabled(True) def on_finished_update_gui(self, is_enabled, profile_id, profile_type, profile_connection): try: if is_enabled: self.connection_manager.add_connected_profile(profile_id) target_button: QPushButton = self.connection_manager.get_profile_button_objects( profile_id) if profile_type == 1: if target_button: icon_path = self.connected_tor_profile if profile_connection == 'tor' else self.connected_profile try: target_button.setIcon(QIcon(icon_path)) except RuntimeError: pass self.button_states[profile_id] = icon_path else: if target_button: try: target_button.setIcon( QIcon(self.system_wide_connected_profile)) except RuntimeError: pass self.update_status.update_image( appearance_value='background_connected') self.button_states[profile_id] = self.system_wide_connected_profile self.IsSystem += 1 if target_button: self.show_disconnect_button( True, profile_type, target_button) self.boton_edit.setEnabled(False) if profile_id == self.reverse_id: profile_obj = ProfileController.get(profile_id) if profile_obj and profile_obj.connection and profile_obj.connection.code == 'wireguard' and ConfigurationController.get_endpoint_verification_enabled(): self.print_profile_details(f"Profile_{profile_id}") else: self.connection_manager.remove_connected_profile(profile_id) if profile_type == 1: if profile_id in self.button_states: del self.button_states[profile_id] else: self.IsSystem -= 1 if profile_id in self.button_states: del self.button_states[profile_id] if self.IsSystem == 0: self.update_status.update_image() self.boton_edit.setEnabled(True) if profile_id == self.reverse_id: self.print_profile_details(f"Profile_{profile_id}") self.refresh_menu_buttons() except IndexError: self.update_status.update_status( 'An error occurred while updating profile state.')