from PyQt6.QtWidgets import QComboBox,QButtonGroup, QLineEdit,QMainWindow, QLabel, QWidget, QVBoxLayout, QStackedWidget, QApplication, QPushButton, QTextEdit, QFrame, QHBoxLayout, QVBoxLayout, QScrollArea, QSystemTrayIcon, QMessageBox, QGridLayout, QCheckBox, QStackedLayout, QGroupBox from PyQt6.QtGui import QIcon, QPixmap,QIcon, QPixmap, QTransform, QPainter, QColor, QFont from PyQt6 import QtGui from PyQt6 import QtCore from PyQt6.QtCore import Qt,QSize,QThread,pyqtSignal, QTimer, QPointF, QRect, QMutex, QMutexLocker, QObject import os import sys import functools import threading import logging import traceback import random import subprocess from typing import Union from core.Errors import CommandNotFoundError, MissingSubscriptionError, InvalidSubscriptionError, ProfileActivationError, UnsupportedApplicationVersionError, FileIntegrityError, ProfileModificationError, ProfileStateConflictError from core.controllers.ApplicationVersionController import ApplicationVersionController from core.controllers.ApplicationController import ApplicationController from core.controllers.ClientController import ClientController from core.controllers.ConfigurationController import ConfigurationController from core.controllers.InvoiceController import InvoiceController from core.controllers.LocationController import LocationController from core.controllers.ProfileController import ProfileController from core.controllers.SubscriptionController import SubscriptionController from core.controllers.SubscriptionPlanController import SubscriptionPlanController from core.models.session.SessionConnection import SessionConnection from core.models.session.SessionProfile import SessionProfile from core.models.system.SystemConnection import SystemConnection from core.models.system.SystemProfile import SystemProfile from core.observers.ApplicationVersionObserver import ApplicationVersionObserver from core.observers.ClientObserver import ClientObserver from core.observers.ConnectionObserver import ConnectionObserver from core.observers.InvoiceObserver import InvoiceObserver from core.observers.ProfileObserver import ProfileObserver from datetime import datetime, timezone, timedelta from core.Constants import Constants import atexit import json application_version_observer = ApplicationVersionObserver() client_observer = ClientObserver() connection_observer = ConnectionObserver() invoice_observer = InvoiceObserver() profile_observer = ProfileObserver() class WorkerThread(QThread): text_output = pyqtSignal(str) sync_output = pyqtSignal(list, bool, bool, list, bool) invoice_output = pyqtSignal(dict, str) invoice_finished = pyqtSignal(bool) profiles_output = pyqtSignal(dict) special_output = pyqtSignal(str) finished = pyqtSignal(bool) update_finished = pyqtSignal(dict) def __init__(self, action=None, profile_data=None, profile_type=None, package_name=None, package_command=None): super().__init__() self.action = action self.profile_data = profile_data self.profile_type = profile_type self.package_name = package_name self.package_command = package_command self.is_running = True self.is_disabling = False def run(self): if self.action == 'LIST_PROFILES': self.list_profiles() elif self.action == 'CREATE_SESSION_PROFILE': self.create_profile(self.profile_type) elif self.action == 'CREATE_SYSTEM_PROFILE': self.create_profile(self.profile_type) elif self.action == 'GET_SUBSCRIPTION': self.get_subscription() elif self.action == 'DISABLE_PROFILE': self.disable_profile() elif self.action == 'SYNC': self.sync() elif self.action == 'SYNC_TOR': self.tor_sync() elif self.action == 'GET_CONNECTION': self.get_connection() elif self.action == 'SET_CONNECTION': self.set_connection() elif self.action == 'DESTROY_PROFILE': self.destroy_profile() elif self.action == 'DISABLE_ALL_PROFILES': self.disable_all_profiles() elif self.action == 'INSTALL_PACKAGE': self.install_package() elif self.action == 'CHECK_FOR_UPDATE': self.check_for_update() elif self.action == 'DOWNLOAD_UPDATE': self.download_update() def download_update(self): self.text_output.emit("Starting update process...") ClientController.__update(client_observer=client_observer) client_observer.subscribe('update_progressing', lambda event: self.text_output.emit(f"Downloading: {event.meta.get('progress'):.1f}%")) client_observer.subscribe('updated', lambda event: self.text_output.emit("Update process completed")) def check_for_update(self): self.text_output.emit("Checking for updates...") update_available = ClientController.can_be_updated() if update_available: self.text_output.emit("An update is available. Downloading...") self.finished.emit(True) else: self.text_output.emit("No updates available.") self.finished.emit(False) def install_package(self): try: self.text_output.emit(f"Installing {self.package_name}...") subprocess.run(self.package_command.split(), check=True) self.text_output.emit(f"{self.package_name} installed!") self.finished.emit(True) except subprocess.CalledProcessError: self.text_output.emit("Installation failed") self.finished.emit(False) except Exception as e: self.text_output.emit(f"An error occurred when installing {self.package_name}: {e}") self.finished.emit(False) def disable_all_profiles(self): try: for profile_id in self.profile_data: profile = ProfileController.get(int(profile_id)) if isinstance(profile, SessionProfile): ProfileController.disable(profile, force=True, profile_observer=profile_observer) for profile_id in self.profile_data: profile = ProfileController.get(int(profile_id)) if isinstance(profile, SystemProfile): ProfileController.disable(profile, force=True, profile_observer=profile_observer) self.text_output.emit("All profiles were successfully disabled") except Exception: self.text_output.emit("An error occurred when disabling profile") finally: self.finished.emit(True) def destroy_profile(self): profile_id = int(self.profile_data['id']) profile = ProfileController.get(profile_id) if profile is not None: ProfileController.destroy(profile) self.text_output.emit(f'Profile {profile_id} deleted') else: self.text_output.emit(f'Profile {profile_id} does not exist') def list_profiles(self): profiles = ProfileController.get_all() self.profiles_output.emit(profiles) def create_profile(self, profile_type): location = LocationController.get(self.profile_data['country_code'], self.profile_data['code']) if location is None: self.text_output.emit(f"Invalid location code: {self.profile_data['location_code']}") return profile_id = int(self.profile_data['id']) if self.profile_data['id'] else None name = self.profile_data['name'] connection_type = self.profile_data['connection_type'] if profile_type == "session": application_details = self.profile_data['application'].split(':', 1) application_version = ApplicationVersionController.get(application_details[0], application_details[1] if len(application_details) > 1 else None) if application_version is None: self.text_output.emit(f"Invalid application: {self.profile_data['application']}") return mask_connection = True if connection_type == 'tor' or connection_type == 'system' else False resolution = self.profile_data['resolution'] connection = SessionConnection(connection_type, mask_connection) profile = SessionProfile(profile_id, name, None, location, resolution, application_version, connection) elif profile_type == "system": connection = SystemConnection(connection_type) profile = SystemProfile(profile_id, name, None, location, connection) else: self.text_output.emit(f"Invalid profile type: {profile_type}") return try: ProfileController.create(profile, profile_observer=profile_observer) except Exception as e: self.text_output.emit(f"An error occurred when creating profile {profile.id}") self.text_output.emit(f"{profile_type.capitalize()} Profile created with ID: {profile.id}") def disable_profile(self): try: profile = ProfileController.get(int(self.profile_data['id'])) if profile: ProfileController.disable(profile, profile_observer=profile_observer) else: self.text_output.emit(f"No profile found with ID: {self.profile_data['id']}") except Exception as e: self.text_output.emit("An error occurred when disabling profile") finally: self.finished.emit(True) def sync(self): try: ConfigurationController.set_connection('system') ClientController.sync(client_observer=client_observer, connection_observer=connection_observer) locations = LocationController.get_all() print(f'locations: {locations}') all_location_codes = [location.country_name for location in locations] self.sync_output.emit(all_location_codes, True, False, locations, False) except Exception as e: print(e) self.sync_output.emit([], False, False, [], False) def tor_sync(self): try: ConfigurationController.set_connection('tor') self.text_output.emit('Syncing in progress...') ClientController.sync(client_observer=client_observer, connection_observer=connection_observer) locations = LocationController.get_all() all_location_codes = [location.country_name for location in locations] self.sync_output.emit(all_location_codes, True, True, locations, False) except ConnectionError: self.sync_output.emit([], False, False, [], False) except CommandNotFoundError: self.sync_output.emit([], False, False, [], True) def get_connection(self): connection = ConfigurationController.get_connection() self.text_output.emit(f"Current connection: {connection}") def set_connection(self): ConfigurationController.set_connection(self.profile_data['connection_type']) self.text_output.emit(f"Connection set to '{self.profile_data['connection_type']}'") def get_subscription(self): try: invoice_observer.subscribe('retrieved', lambda event: self.handle_event(event, self.profile_data['currency'])) invoice_observer.subscribe('processing', lambda event: self.text_output.emit('A payment has been detected and is being verified...')) invoice_observer.subscribe('settled', lambda event: self.text_output.emit('The payment has been successfully verified.')) profile = ProfileController.get(int(self.profile_data['id'])) subscription_plan = SubscriptionPlanController.get(profile.connection, self.profile_data['duration']) if subscription_plan is None: self.text_output.emit('No compatible subscription plan was found.') return potential_subscription = SubscriptionController.create(subscription_plan, profile, connection_observer=connection_observer) if potential_subscription is not None: ProfileController.attach_subscription(profile, potential_subscription) else: self.text_output.emit('The subscription could not be created. Try again later.') return subscription = InvoiceController.handle_payment(potential_subscription.billing_code, invoice_observer=invoice_observer, connection_observer=connection_observer) if subscription is not None: ProfileController.attach_subscription(profile, subscription) self.text_output.emit('Successfully activated the subscription') self.invoice_finished.emit(True) else: self.text_output.emit('The subscription could not be activated. Try again later.') self.invoice_finished.emit(False) except Exception as e: self.text_output.emit('An unknown error occurred') self.invoice_finished.emit(False) def _get_billing_details(self, invoice, currency_code): for payment_method in invoice.payment_methods: if payment_method.code == currency_code: return { 'billing_code': invoice.billing_code, 'due_amount': payment_method.due, 'address': payment_method.address } return f"Payment method for currency '{currency_code}' not found." def handle_connection_events(self, event): self.text_output.emit(f'Profile disabled') def handle_event(self, event, currency): invoice = event.subject billing_details = self._get_billing_details(invoice, currency) if isinstance(billing_details, dict): self.invoice_output.emit(billing_details, '') self.text_output.emit("Invoice generated. Awaiting payment...") else: self.text_output.emit("No payment method found for the selected currency.") class CustomWindow(QMainWindow): def __init__(self): super().__init__() self.setWindowFlags( Qt.WindowType.Window ) self.setAttribute(Qt.WidgetAttribute.WA_TranslucentBackground) self.setAttribute(Qt.WidgetAttribute.WA_NoSystemBackground) self.setWindowTitle("HydraVeil VPN") self._data = {"Profile_1": {}} self.gui_dir = os.path.join(Constants.HV_DATA_HOME, "gui") self.gui_config_file = os.path.join(self.gui_dir, "config.json") self.gui_log_file = os.path.join(self.gui_dir, "gui.log") self._setup_gui_directory() current_dir = os.path.dirname(os.path.abspath(__file__)) self.btn_path = os.getenv('BTN_PATH', os.path.join(current_dir, 'resources', 'images')) self.css_path = os.getenv('CSS_PATH', os.path.join(current_dir, 'resources', 'styles')) icon_base = os.getenv('SYSTEM_TRAY_ICON', '') self.tray = None self.init_system_tray(icon_base) self.is_downloading = False self.current_profile_id = None self.connection_manager = ConnectionManager() current_connection = ConfigurationController.get_connection() self.is_tor_mode = current_connection == 'tor' self.is_animating = False self.animation_step = 0 self.page_history = [] self.log_path = None profile_observer.subscribe('created', lambda event: self.update_status('Profile Created')) profile_observer.subscribe('destroyed', lambda event: self.update_status('Profile destroyed')) application_version_observer.subscribe('downloading', lambda event: self.update_status(f'Downloading {ApplicationController.get(event.subject.application_code).name}')) application_version_observer.subscribe('download_progressing', lambda event: self.update_status(f'Downloading {ApplicationController.get(event.subject.application_code).name} {event.meta.get('progress'):.2f}%')) application_version_observer.subscribe('downloaded', lambda event: self.update_status(f'Downloaded {ApplicationController.get(event.subject.application_code).name}')) connection_observer.subscribe('connecting', lambda event: self.update_status(f'[{event.subject.get("attempt_count")}/{event.subject.get("maximum_number_of_attempts")}] Performing connection attempt...')) self.setFixedSize(800, 570) self.central_widget = QWidget(self) self.central_widget.setMaximumSize(800, 600) self.central_widget.setGeometry(0, 0, 850, 600) self.central_widget.setObjectName("centralwidget") # Creamos el QLabel para mostrar la imagen self.label = QLabel(self.central_widget) self.label.setGeometry(0, 0, 800, 570) css_file = os.path.join(self.css_path, 'look.css') self.setStyleSheet(open(css_file).read() if os.path.exists(css_file) else '') # Opcional: si deseas cargar estilos CSS desde un archivo self.status_label = QLabel(self) self.status_label.setGeometry(15, 518, 780, 20) self.status_label.setAlignment(Qt.AlignmentFlag.AlignLeft | Qt.AlignmentFlag.AlignVCenter) self.status_label.setStyleSheet("color: rgb(0, 255, 255); font-size: 16px;") self.status_label.setText(f"Status: Client version {Constants.HV_CLIENT_VERSION_NUMBER}") self.text_start_x = 15 self.text_end_x = 420 self.text_width = self.text_end_x - self.text_start_x self.marquee_text = "" self.marquee_position = 0 self.marquee_timer = QTimer(self) self.marquee_timer.timeout.connect(self.update_marquee) self.marquee_enabled = False self.scroll_speed = 1 self.tor_text = QLabel("Billing & Browsers", self) self.tor_text.setGeometry(522, 542, 150, 20) self.tor_text.setStyleSheet(""" QLabel { color: cyan; font-size: 11px; font-weight: bold; } """) self.update_image() self.set_scroll_speed(7) self.tor_icon = QLabel(self) self.tor_icon.setGeometry(705, 537, 25, 25) self.set_tor_icon() self.toggle_button = QPushButton(self) self.toggle_button.setGeometry(645, 541, 50, 22) self.toggle_button.setCheckable(True) self.toggle_button.clicked.connect(self.toggle_mode) self.create_toggle(self.is_tor_mode) self.animation_timer = QTimer() self.animation_timer.timeout.connect(self.animate_toggle) self.check_logging() self.init_ui() def _setup_gui_directory(self): os.makedirs(self.gui_dir, exist_ok=True) if not os.path.exists(self.gui_config_file): default_config = { "logging": { "gui_logging_enabled": False, "log_level": "INFO" } } with open(self.gui_config_file, 'w') as f: json.dump(default_config, f, indent=4) def _load_gui_config(self): try: with open(self.gui_config_file, 'r') as f: return json.load(f) except FileNotFoundError: return None except json.JSONDecodeError: logging.error("Invalid GUI config file format") return None def _save_gui_config(self, config): with open(self.gui_config_file, 'w') as f: json.dump(config, f, indent=4) def check_logging(self): config = self._load_gui_config() if config is None: return if config["logging"]["gui_logging_enabled"]: self._setup_gui_logging() def stop_gui_logging(self): if os.path.exists(self.gui_log_file): os.remove(self.gui_log_file) config = self._load_gui_config() if config: config["logging"]["gui_logging_enabled"] = False self._save_gui_config(config) sys.excepthook = sys.__excepthook__ def _setup_gui_logging(self): os.makedirs(self.gui_dir, exist_ok=True) formatter = logging.Formatter( '%(asctime)s - %(levelname)s - %(message)s', datefmt='%Y-%m-%d %H:%M:%S' ) file_handler = logging.FileHandler(self.gui_log_file) file_handler.setLevel(logging.INFO) file_handler.setFormatter(formatter) logger = logging.getLogger() logger.setLevel(logging.INFO) logger.handlers = [] logger.addHandler(file_handler) sys.excepthook = self._handle_uncaught_exception if not os.path.exists(self.gui_log_file) or os.path.getsize(self.gui_log_file) == 0: logging.info("GUI logging system initialized") def _handle_uncaught_exception(self, exc_type, exc_value, exc_traceback): logging.error( f"Uncaught exception:\n" f"Type: {exc_type.__name__}\n" f"Value: {str(exc_value)}\n" f"Traceback:\n{''.join(traceback.format_tb(exc_traceback))}" ) sys.__excepthook__(exc_type, exc_value, exc_traceback) def set_tor_icon(self): if self.is_tor_mode: icon_path = os.path.join(self.btn_path, "toricon_mini.png") else: icon_path = os.path.join(self.btn_path, "toricon_mini_false.png") pixmap = QPixmap(icon_path) self.tor_icon.setPixmap(pixmap) self.tor_icon.setScaledContents(True) def create_toggle(self, is_tor_mode): handle_x = 30 if is_tor_mode else 3 colors = self._get_toggle_colors(is_tor_mode) if not hasattr(self, 'animation_timer'): self.animation_timer = QTimer() self.animation_timer.timeout.connect(self.animate_toggle) # Create background self.toggle_bg = QLabel(self.toggle_button) self.toggle_bg.setGeometry(0, 0, 50, 22) # Create handle self.toggle_handle = QLabel(self.toggle_button) self.toggle_handle.setGeometry(handle_x, 3, 16, 16) self.toggle_handle.setStyleSheet(""" QLabel { background-color: white; border-radius: 8px; } """) # Create ON/OFF labels self.on_label = QLabel("ON", self.toggle_button) self.on_label.setGeometry(8, 4, 15, 14) self.off_label = QLabel("OFF", self.toggle_button) self.off_label.setGeometry(25, 4, 40, 14) # Apply colors self._update_toggle_colors(colors) if is_tor_mode: self.set_tor_icon() def _get_toggle_colors(self, is_tor_mode): return { 'bg': "#00a8ff" if is_tor_mode else "#2c3e50", 'tor': "white" if is_tor_mode else "transparent", 'sys': "transparent" if is_tor_mode else "white" } def _update_toggle_colors(self, colors): self.toggle_bg.setStyleSheet(f""" QLabel {{ background-color: {colors['bg']}; border-radius: 11px; }} """) self.on_label.setStyleSheet(f""" QLabel {{ color: {colors['tor']}; font-size: 9px; font-weight: bold; }} """) self.off_label.setStyleSheet(f""" QLabel {{ color: {colors['sys']}; font-size: 9px; font-weight: bold; }} """) def set_toggle_state(self, is_tor_mode): self.is_tor_mode = is_tor_mode handle_x = 30 if is_tor_mode else 3 self.toggle_handle.setGeometry(handle_x, 3, 16, 16) self._update_toggle_colors(self._get_toggle_colors(is_tor_mode)) self.set_tor_icon() def toggle_mode(self): if not self.is_animating: self.is_animating = True self.animation_step = 0 self.animation_timer.start(16) self.is_tor_mode = not self.is_tor_mode ConfigurationController.set_connection('tor' if self.is_tor_mode else 'system') def animate_toggle(self): TOTAL_STEPS = 5 if self.animation_step <= TOTAL_STEPS: progress = self.animation_step / TOTAL_STEPS if self.is_tor_mode: new_x = 20 + (10 * progress) start_color = "#2c3e50" end_color = "#00a8ff" else: new_x = 31 - (28 * progress) start_color = "#00a8ff" end_color = "#2c3e50" self.toggle_handle.setGeometry(int(new_x), 3, 16, 16) bg_color = self.interpolate_color(start_color, end_color, progress) self.toggle_bg.setStyleSheet(f""" QLabel {{ background-color: {bg_color}; border-radius: 11px; }} """) self.off_label.setStyleSheet(f""" QLabel {{ color: rgba(255, 255, 255, {1 - progress if self.is_tor_mode else progress}); font-size: 9px; font-weight: bold; }} """) self.on_label.setStyleSheet(f""" QLabel {{ color: rgba(255, 255, 255, {progress if self.is_tor_mode else 1 - progress}); font-size: 9px; font-weight: bold; }} """) self.animation_step += 1 else: self.animation_timer.stop() self.is_animating = False self.set_tor_icon() def interpolate_color(self, start_color, end_color, progress): def hex_to_rgb(hex_color): hex_color = hex_color.lstrip('#') return tuple(int(hex_color[i:i+2], 16) for i in (0, 2, 4)) start_rgb = hex_to_rgb(start_color) end_rgb = hex_to_rgb(end_color) current_rgb = tuple( int(start_rgb[i] + (end_rgb[i] - start_rgb[i]) * progress) for i in range(3) ) return f"rgb{current_rgb}" def init_system_tray(self, icon_base): if QSystemTrayIcon.isSystemTrayAvailable(): tray_icon = QIcon() window_icon = QIcon() tray_sizes = [22, 24] for size in tray_sizes: icon_path = os.path.join(icon_base, f"{size}x{size}/apps/simplified-privacy.png") if os.path.exists(icon_path): tray_icon.addFile(icon_path, QSize(size, size)) window_sizes = [32, 48, 64, 128] for size in window_sizes: icon_path = os.path.join(icon_base, f"{size}x{size}/apps/simplified-privacy.png") if os.path.exists(icon_path): window_icon.addFile(icon_path, QSize(size, size)) self.tray = QSystemTrayIcon(self) self.tray.setIcon(tray_icon) self.tray.setVisible(True) self.setWindowIcon(window_icon) def closeEvent(self, event=None): connected_profiles = self.connection_manager.get_connected_profiles() if connected_profiles: self._show_disconnect_confirmation(connected_profiles, event) else: pass def _show_disconnect_confirmation(self, connected_profiles, event=None, button_text='Exit', message=None): if event is not None: event.ignore() message = self._create_disconnect_message(connected_profiles) self.popup = self._create_confirmation_popup(message, button_text) self.popup.finished.connect(lambda result: self._handle_popup_result(result, connected_profiles, event)) self.popup.show() def _create_disconnect_message(self, connected_profiles): profile_numbers = ', '.join(str(profile_id) for profile_id in connected_profiles) is_are = "is" if len(connected_profiles) == 1 else "are" return f'Profile{"" if len(connected_profiles) == 1 else "s"} {profile_numbers} {is_are} still connected.\nAll connected profiles will be disconnected on exit. \nDo you want to proceed?' def _create_confirmation_popup(self, message, button_text): popup = ConfirmationPopup(self, message=message, action_button_text=button_text, cancel_button_text="Cancel") popup.setWindowModality(Qt.WindowModality.ApplicationModal) return popup def _handle_popup_result(self, result, connected_profiles, event): if result: if event is None: self._disable_profiles(connected_profiles, exit=False) return self._disable_profiles(connected_profiles) else: event.ignore() def _disable_profiles(self, connected_profiles, exit=True): self.update_status('Disabling profiles...') self.worker_thread = WorkerThread('DISABLE_ALL_PROFILES', profile_data=connected_profiles) self.worker_thread.text_output.connect(lambda text: self._on_disable_status(text, exit)) self.worker_thread.start() def _on_disable_status(self, text, exit): if exit: self.update_status('All profiles diabled. Bye!') QApplication.instance().quit() else: self.update_status(text) self.update_image() def _perform_cleanup_and_close(self, event): self.cleanup() super().closeEvent(event) def update_image(self, appearance_value = "original"): image_path = os.path.join(self.btn_path, f"{appearance_value}.png") # Configuramos la imagen en el QLabel pixmap = QPixmap(image_path) self.label.setPixmap(pixmap) self.label.setScaledContents(True) def init_ui(self): # Configuración del diseño de la ventana principal layout = QVBoxLayout(self.central_widget) self.page_stack = QStackedWidget() layout.addWidget(self.page_stack) self.menu_page = MenuPage(self.page_stack, self) self.editor_page = EditorPage(self.page_stack, self) self.pages = [MenuPage(self.page_stack, self), ProtocolPage(self.page_stack, self), WireGuardPage(self.page_stack, self), ResidentialPage(self.page_stack, self), HidetorPage(self.page_stack, self), lokinetPage(self.page_stack, self), OpenPage(self.page_stack, self), TorPage(self.page_stack, self), TorLocationPage(self.page_stack, self), PaymentPage(self.page_stack, self), LocationPage(self.page_stack, self), BrowserPage(self.page_stack, self), ScreenPage(self.page_stack, self), ResumePage(self.page_stack, self), IdPage(self.page_stack, self), InstallSystemPackage(self.page_stack, self), WelcomePage(self.page_stack, self), PaymentConfirmed(self.page_stack, self), EditorPage(self.page_stack, self), Settings(self.page_stack, self), ConnectionPage(self.page_stack, self)] for page in self.pages: self.page_stack.addWidget(page) # Conectar la señal currentChanged al método page_changed self.page_stack.currentChanged.connect(self.page_changed) if self.check_first_launch(): self.page_stack.setCurrentIndex(self.page_stack.indexOf(self.page_stack.findChild(WelcomePage))) def read_data(self): return self._data["Profile_1"] def write_data(self, data): self._data["Profile_1"].update(data) def clear_data(self): self._data = {"Profile_1": {}} def update_status(self, text, clear=False, is_downloading=False): if text is None: self.status_label.setText('Status:') self.disable_marquee() return if is_downloading and not self.is_downloading: self.is_downloading = True if self.is_downloading and 'Downloading' not in text: return if 'Downloaded' in text: self.is_downloading = False if clear: self.status_label.setText('') return full_text = f'Status: {text}' self.status_label.setText(full_text) self.disable_marquee() def check_first_launch(self): config_file = os.path.join(Constants.HV_CONFIG_HOME, '.first_launch') os.makedirs(Constants.HV_CONFIG_HOME, exist_ok=True) is_first_launch = not os.path.exists(config_file) if is_first_launch: try: with open(config_file, 'w') as f: f.write('launched') except Exception as e: print(f"Error writing to launch file: {e}") return True else: return False def enable_marquee(self, text): self.marquee_text = text + " " self.marquee_position = 0 self.marquee_enabled = True self.marquee_timer.start(500) def disable_marquee(self): self.marquee_enabled = False self.marquee_timer.stop() def update_marquee(self): if not self.marquee_enabled: return metrics = self.status_label.fontMetrics() text_width = metrics.horizontalAdvance(self.marquee_text) self.marquee_position += self.scroll_speed if self.marquee_position >= text_width: self.marquee_position = 0 looped_text = self.marquee_text * 2 chars_that_fit = metrics.horizontalAdvance(looped_text[:self.text_width]) visible_text = looped_text[self.marquee_position:self.marquee_position + chars_that_fit] while metrics.horizontalAdvance(visible_text) < self.text_width: visible_text += self.marquee_text while metrics.horizontalAdvance(visible_text) > self.text_width: visible_text = visible_text[:-1] display_text = 'Status: ' + ' ' * (self.text_start_x - metrics.horizontalAdvance('Status: ')) display_text += visible_text self.status_label.setText(display_text) def set_scroll_speed(self, speed): self.scroll_speed = speed def page_changed(self, index): current_page = self.page_stack.widget(index) if self.page_history: previous_page = self.page_history[-1] else: previous_page = None if isinstance(current_page, MenuPage): self.update_status(None) if isinstance(previous_page, (ResumePage, EditorPage, Settings)): current_page.eliminacion() else: pass elif isinstance(current_page, ResumePage): self.update_status('Choose a name for your profile') current_page.eliminacion() elif isinstance(current_page, ProtocolPage): if not self.connection_manager.is_synced(): self.update_status('Choose a sync method') elif isinstance(current_page, IdPage): pass elif isinstance(current_page, LocationPage): self.update_status(None, clear=True) elif isinstance(current_page, InstallSystemPackage): self.update_status("Required tools are not installed. Please install them.") else: self.update_status(None) if isinstance(current_page, MenuPage): self.tor_text.setVisible(True) self.toggle_button.setGeometry(645, 541, 50, 22) self.tor_icon.setGeometry(705, 537, 25, 25) else: self.tor_text.setVisible(False) self.toggle_button.setGeometry(540, 541, 50, 22) self.tor_icon.setGeometry(600, 537, 25, 25) if current_page != previous_page: self.page_history.append(current_page) self.page_history = self.page_history[-5:] self.show() class Page(QWidget): def __init__(self, name, page_stack, custom_window, parent=None): super().__init__(parent) self.custom_window = custom_window self.btn_path = custom_window.btn_path self.name = name self.page_stack = page_stack self.init_ui() self.selected_profiles = [] self.selected_wireguard= [] # Lista para almacenar perfiles seleccionados self.selected_residential= [] self.buttons = [] def add_selected_profile(self, profile): self.selected_profiles.clear() self.selected_wireguard.clear() self.selected_residential.clear() self.selected_profiles.append(profile) # self.selected_wireguard.append(wireguard) # self.selected_residential.append(residential) def init_ui(self): self.title = QLabel(self) self.title.setAlignment(QtCore.Qt.AlignmentFlag.AlignCenter) self.title.setObjectName("titles") self.display = QLabel(self) self.display.setObjectName("display") self.display.setAlignment(QtCore.Qt.AlignmentFlag.AlignCenter) #self.display.setStyleSheet("background-color: yellow;") buttons_info = [ (QPushButton, "button_back", "back", (660, 525, 48, 35)), (QPushButton, "button_next", "next", (720, 525, 48, 35)), (QPushButton, "button_reverse", "back", (660, 525, 48, 35)), (QPushButton, "button_go", "next", (720, 525, 48, 35)), (QPushButton, "button_apply", "apply", (720, 525, 48, 35)), ] for button_type, object_name, icon_name, geometry in buttons_info: button = button_type(self) button.setObjectName(object_name) button.setGeometry(*geometry) button.setIcon(QIcon(os.path.join(self.btn_path, f"{icon_name}.png"))) button.setIconSize(QtCore.QSize(48, 35)) button.setVisible(False) # Ocultar el botón inicialmente if object_name == "button_back": self.button_back = button self.button_back.clicked.connect(self.gestionar_back) if object_name == "button_next": self.button_next = button self.button_next.clicked.connect(self.gestionar_next) if object_name == "button_reverse": self.button_reverse = button self.button_reverse.clicked.connect(self.limpiar) if object_name == "button_go": self.button_go = button self.button_go.clicked.connect(self.limpiar) if object_name == "button_apply": self.button_apply = button def gestionar_back(self): current_index = self.page_stack.currentIndex() if current_index == 18: return if current_index > 0: self.page_stack.setCurrentIndex(current_index - 1) def gestionar_next(self): current_index = self.page_stack.currentIndex() next_index = (current_index + 1) % self.page_stack.count() self.page_stack.setCurrentIndex(next_index) self.limpiar() def limpiar(self): self.display.clear() for boton in self.buttons: boton.setChecked(False) self.button_next.setVisible(False) class Worker(QObject): update_signal = pyqtSignal(str, bool, int, int, str) change_page = pyqtSignal(str, bool) def __init__(self, profile_data): self.profile_data = profile_data super().__init__() profile_observer.subscribe('disabled', lambda event: self.handle_profile_status(event.subject, False)) profile_observer.subscribe('enabled', lambda event: self.handle_profile_status(event.subject, True)) self.profile_type = None self.force = self.profile_data.get('force', False) def run(self): self.profile = ProfileController.get(int(self.profile_data['id'])) if 'billing_code' in self.profile_data: subscription = SubscriptionController.get(self.profile_data['billing_code'], connection_observer=connection_observer) if subscription is not None: ProfileController.attach_subscription(self.profile, subscription) else: self.change_page.emit('The billing code associated with this profile is invalid.', True) return if self.profile: try: ProfileController.enable(self.profile, force=self.force, profile_observer=profile_observer, application_version_observer=application_version_observer, connection_observer=connection_observer) except (InvalidSubscriptionError, MissingSubscriptionError) as e: self.change_page.emit(f"Subscription missing or invalid for profile {self.profile_data['id']}", True) except ProfileActivationError: self.update_signal.emit("The profile could not be enabled", False, None, None, None) except UnsupportedApplicationVersionError: self.update_signal.emit("The application version in question is not supported", False, None, None, None) except FileIntegrityError: self.update_signal.emit("Application version file integrity could not be verified.", False, None, None, None) except ProfileModificationError: self.update_signal.emit("WireGuard configuration could not be attached.", False, None, None, None) except OSError: self.update_signal.emit("Connection could not be established.", False, None, None, None) except ProfileStateConflictError: self.update_signal.emit("The profile is already being enabled...", False, None, None, None) except CommandNotFoundError as e : self.update_signal.emit(str(e), False, -1, None, None) except Exception as e: self.update_signal.emit("An unknown error occurred", False, None, None, None) else: self.update_signal.emit(f"No profile found with ID: {self.profile_data['id']}", False, None, None, None) def handle_profile_status(self, profile, is_enabled): profile_id = profile.id profile_connection = str(profile.connection.code) message = self.generate_profile_message(profile, is_enabled) if isinstance(profile, SessionProfile): self.profile_type = 1 elif isinstance(profile, SystemProfile): self.profile_type = 2 else: self.profile_type = None self.update_signal.emit(message, is_enabled, profile_id, self.profile_type, profile_connection) @staticmethod def generate_profile_message(profile, is_enabled, idle=False): profile_id = profile.id if not profile.subscription or not profile.subscription.expires_at: return f"Offline. No subscription found." profile_date = profile.subscription.expires_at status = 'enabled' if is_enabled else 'disabled' expiration_date = profile_date.replace(tzinfo=timezone.utc) time_left = expiration_date - datetime.now(timezone.utc) days_left = time_left.days hours_left, remainder = divmod(time_left.seconds, 3600) formatted_expiration = expiration_date.strftime("%Y-%m-%d %H:%M:%S") if expiration_date < datetime.now(timezone.utc): return "Offline. Subscription has expired." if idle: return f"Offline. Expires in {days_left} days." if is_enabled: return f"Profile {int(profile_id)} {status}. Expires on {formatted_expiration}. Time left: {days_left} days, {hours_left} hours." else: return f"Profile {int(profile_id)} {status}" 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.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.update_button = QPushButton(self) self.update_button.setGeometry(750, 530, 25, 25) self.update_button.setObjectName("update_button") update_icon = QtGui.QIcon(os.path.join(self.btn_path, "available-updates.png")) self.update_button.setIcon(update_icon) self.update_button.setIconSize(self.update_button.size()) self.update_button.clicked.connect(self.check_updates) #self.label.setStyleSheet("background-color: rgba(0, 255, 0, 51);") self.create_interface_elements() # Establecer el color de fondo y el color del texto utilizando hojas de estilo def check_updates(self): self.update_status.update_status("Checking for updates...") self.worker = WorkerThread('CHECK_FOR_UPDATE') self.worker.text_output.connect(self.update_status.update_status) self.worker.finished.connect(self.on_update_check_finished) self.worker.start() def on_update_check_finished(self, result): if result: 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.text_output.connect(self.update_status.update_status) self.worker.update_finished.connect(self.on_update_download_finished) self.worker.start() else: self.update_status.update_status("Update cancelled by user.") else: self.update_status.update_status("No updates available.") def on_update_download_finished(self, result: dict): if result["status"] and result["path"]: self.countdown_timer = QTimer() self.countdown_timer.setInterval(1000) self.countdown_seconds = 3 self.new_app_path = result["path"] def update_countdown(): if self.countdown_seconds > 0: self.update_status.update_status(f"New version downloaded. Restarting in {self.countdown_seconds} second{'s' if self.countdown_seconds > 1 else ''}...") self.countdown_seconds -= 1 else: self.countdown_timer.stop() self.restart_application() def restart_application(): QApplication.quit() current_app = Constants.APPIMAGE_PATH if os.path.exists(self.new_app_path): try: os.remove(current_app) os.execv(self.new_app_path, [self.new_app_path] + sys.argv[1:]) except PermissionError: self.update_status.update_status(f"Permission denied: Unable to remove {current_app}") else: self.update_status.update_status(f"Error: New application not found at {self.new_app_path}") self.restart_application = restart_application self.countdown_timer.timeout.connect(update_countdown) self.countdown_timer.start() atexit.register(restart_application) else: self.update_status.update_status("Failed to download update.") 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)), #("delete_profile", self.delete_profile, (670, 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 =="delete_profile": # self.boton_settings=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 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): 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) def match_core_profiles(self, profiles_dict): new_dict = {} for idx, profile in profiles_dict.items(): new_profile = {} protocol = profile.connection.code location_code = profile.location.code if profile.location else 'None' location = self.connection_manager.get_location_info(location_code) new_profile['location'] = location if protocol == 'wireguard': new_profile['protocol'] = 'wireguard' elif location in self.connection_manager.get_non_residential_proxy_list(): new_profile['protocol'] = 'hidetor' else: new_profile['protocol'] = 'residential' 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' browser = profile.application_version.application_code if hasattr(profile, 'application_version') else 'unknown' if browser != 'unknown': new_profile['browser'] = browser new_profile['browser_version'] = profile.application_version.version_number else: new_profile['browser'] = 'unknown browser' 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.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) def create_profile_widget(self, key, value): profile_id = int(key.split('_')[1]) visible = profile_id <= 6 parent_label = self.create_parent_label(profile_id, visible) 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, visible): parent_label = QLabel(self) index = profile_id - 1 row, column = divmod(index, 2) parent_label.setGeometry(column * 185 + 420, row * 120 + 80, 175, 100) parent_label.setVisible(visible) 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) # Use profile_id directly in lambda 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) image_path = self.get_icon_path(label_name, value, connection_type) pixmap = QPixmap(image_path) # Check if the pixmap is null (image doesn't exist) if pixmap.isNull() and label_name == 'location': # Use a fallback/default image 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)) 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) image_path = self.get_icon_path(label_name, value, connection_type) pixmap = QPixmap(image_path) if pixmap.isNull() and label_name == 'location': # Use a fallback/default image fallback_path = os.path.join(self.btn_path, "default_location_mini.png") pixmap = QPixmap(fallback_path) # If even the fallback is null, create a simple colored square if pixmap.isNull(): pixmap = QPixmap(50, 50) pixmap.fill(QtGui.QColor(200, 200, 200)) # Light gray fallback 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") if value['location'] in self.connection_manager.get_non_residential_proxy_list() else os.path.join(self.btn_path, "residential tor_mini.png") elif connection_type == 'just proxy': return os.path.join(self.btn_path, "just proxy_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"{value[label_name]}_mini.png") 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) #self.boton_settings.setEnabled(True) # Eliminar labels adicionales anteriores for label in self.additional_labels: label.deleteLater() # Limpiar la lista de labels adicionales 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] # Extraer el número del perfil del nombre del perfil name = profile.get("name", "") protocol = profile.get("protocol", "") location = profile.get("location", "") connection = profile.get("connection", "") country_garaje = profile.get("country_garaje", "") if protocol.lower() in ["wireguard", "open", "lokinet","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() == "hidetor": 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"{location}.png") else: image_path = os.path.join(self.btn_path, f"{location}_hdtor.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": # Redimensionar el QLabel principal label_principal.setGeometry(0, 80, 400, 300) # Crear QLabel con la imagen os.path.join(self.btn_path, "browser only.png") detrás del label_principal label_background = QLabel(self) label_background.setGeometry(0, 60, 410, 354) # Geometría según necesidades pixmap = QPixmap(os.path.join(self.btn_path, "browser only.png")) 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) #label_background.setStyleSheet("background-color: blue;") 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) #label_garaje.setStyleSheet("background-color: red;") 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")) 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) #label_background.setStyleSheet("background-color: blue;") 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) #label_garaje.setStyleSheet("background-color: red;") self.additional_labels.append(label_garaje) # Actualizar perfil para editar editar_profile = {"profile_number": profile_number, "name": name, "protocol": protocol} self.page_stack.currentWidget().add_selected_profile(editar_profile) def launch(self): pass def connect(self): self.boton_just.setEnabled(False) self.boton_just_session.setEnabled(False) if self.connection_manager.get_is_profile_being_enabled(int(self.reverse_id)): self.update_status.update_status('Profile is already being enabled...') return profile = ProfileController.get(int(self.reverse_id)) is_tor = ConfigurationController.get_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.update_status._create_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.page_stack.setCurrentIndex(self.page_stack.indexOf(self.page_stack.findChild(IdPage))) else: self.get_billing_code_by_id() else: self.popup.close() 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.update_status._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 create_prof(self): if len(self.profile_info) == 6: self.update_status.update_status('Maximum number of profiles reached!') return self.page_stack.setCurrentIndex(self.page_stack.indexOf(self.page_stack.findChild(ProtocolPage))) 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): self.page_stack.setCurrentIndex(self.page_stack.indexOf(self.page_stack.findChild(EditorPage))) def settings_gui(self): self.page_stack.setCurrentIndex(self.page_stack.indexOf(self.page_stack.findChild(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) for widget in self.findChildren(QLabel): if widget != self.title: widget.setParent(None) self.profile_info.clear() 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): ''' if self.worker is not None: self.worker.update_signal.disconnect(self.update_gui_main_thread) self.worker.change_page.disconnect(self.change_app_page) ''' self.connection_manager.set_is_profile_being_enabled(profile_data['id'], True) self.worker = Worker(profile_data) self.worker.update_signal.connect(self.update_gui_main_thread) self.worker.change_page.connect(self.change_app_page) thread = threading.Thread(target=self.worker.run) thread.start() def DisplayInstallScreen(self, package_name): install_page = self.page_stack.findChild(InstallSystemPackage) install_page.configure(package_name=package_name, distro='debian') self.page_stack.setCurrentIndex(self.page_stack.indexOf(install_page)) def handle_enable_force_result(self, result): if result: self.get_billing_code_by_id(force=True) else: pass self.popup.close() def update_gui_main_thread(self, text, is_enabled, profile_id, profile_type, profile_connection): if '...' in text: message = f'The profile is already enabled. Do you want to force enable it anyway?' self.popup = self.update_status._create_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 <= 6 and 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 = ConfigurationController.get_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.update_status._create_confirmation_popup(message, button_text='Proceed') self.popup.finished.connect(lambda result: self._handle_popup_result(result, True)) self.popup.show() else: self.page_stack.setCurrentIndex(self.page_stack.indexOf(self.page_stack.findChild(IdPage))) self.boton_just.setEnabled(True) self.boton_just_session.setEnabled(True) self.connection_manager.set_is_profile_being_enabled(self.reverse_id, False) def on_finished_update_gui(self, is_enabled, profile_id, profile_type, profile_connection): self.connection_manager.set_is_profile_being_enabled(profile_id, False) try: if is_enabled: self.connection_manager.add_connected_profile(profile_id) if profile_type == 1: target_button: QPushButton = self.connection_manager.get_profile_button_objects(profile_id) icon_path = self.connected_tor_profile if profile_connection == 'tor' else self.connected_profile target_button.setIcon(QIcon(icon_path)) self.button_states[profile_id] = icon_path else: target_button: QPushButton = self.connection_manager.get_profile_button_objects(profile_id) target_button.setIcon(QIcon(self.system_wide_connected_profile)) self.update_status.update_image(appearance_value='background_connected') self.button_states[profile_id] = self.system_wide_connected_profile self.IsSystem += 1 self.show_disconnect_button(True, profile_type, target_button) self.boton_edit.setEnabled(False) else: self.connection_manager.remove_connected_profile(profile_id) if profile_type == 1: target_button: QPushButton = self.connection_manager.get_profile_button_objects(profile_id) target_button.setIcon(QIcon(self.not_connected_profile)) if profile_id in self.button_states: del self.button_states[profile_id] else: self.IsSystem -= 1 target_button: QPushButton = self.connection_manager.get_profile_button_objects(profile_id) target_button.setIcon(QIcon(self.not_connected_profile)) if profile_id in self.button_states: del self.button_states[profile_id] if self.IsSystem == 0: self.update_status.update_image() self.show_disconnect_button(False, profile_type, target_button) self.boton_edit.setEnabled(True) except IndexError: self.update_status.update_status('An error occurred while updating profile state.') class ConnectionManager: def __init__(self): self._connected_profiles = set() self._is_synced = False self._is_profile_being_enabled = {} self.profile_button_objects = {} self._location_mapping = { 'md': 'Moldova', 'nl': 'The Netherlands', 'us': 'US (Ohio)', 'ca': 'US (Seattle)', 'fi': 'Finland', 'is': 'Iceland', 'my': 'Malaysia', 'la': 'US (Los Angeles)', 'ru': 'Russia', 'ro': 'Romania', 'ch': 'Switzerland' } self._timezone_mapping = { 'md': 'Europe/Chisinau', 'nl': 'Europe/Amsterdam', 'us': 'America/New_York', 'ca': 'America/Los_Angeles', 'fi': 'Europe/Helsinki', 'is': 'Atlantic/Reykjavik', 'my': 'Asia/Kuala_Lumpur', 'la': 'America/Los_Angeles', 'ru': 'Europe/Moscow', 'ro': 'Europe/Bucharest', 'ch': 'Europe/Zurich' } self._location_list = [] self.wireguard_locations = ['Moldova', 'The Netherlands', 'US (Ohio)', 'US (Seattle)', 'Iceland', 'Malaysia'] self.non_resident_proxy_locations = ['Moldova', 'The Netherlands', 'US (Ohio)', 'US (Seattle)', 'Iceland'] def get_profile_button_objects(self, profile_id): return self.profile_button_objects.get(profile_id, None) def set_profile_button_objects(self, profile_id, profile_button_objects): self.profile_button_objects[profile_id] = profile_button_objects def get_timezone(self, location_code): if location_code in self._timezone_mapping: return self._timezone_mapping[location_code] return None def add_connected_profile(self, profile_id): self._connected_profiles.add(profile_id) def remove_connected_profile(self, profile_id): self._connected_profiles.discard(profile_id) def is_profile_connected(self, profile_id): return profile_id in self._connected_profiles def get_connected_profiles(self): return list(self._connected_profiles) def set_synced(self, status): self._is_synced = status def is_synced(self): return self._is_synced def get_location_info(self, location_key): if location_key in self._location_mapping: return self._location_mapping[location_key] return None def store_locations(self, locations): self._location_mapping = {} self._location_list = locations for location in locations: self._location_mapping[location.country_name] = location def get_location_list(self): if self.is_synced(): location_names = [location.country_name for location in self._location_list] return location_names else: return self.wireguard_locations def get_non_residential_proxy_list(self): return self.non_resident_proxy_locations def set_is_profile_being_enabled(self, profile_id: int, status: bool): self._is_profile_being_enabled[profile_id] = status def get_is_profile_being_enabled(self, profile_id: int) -> bool: return self._is_profile_being_enabled.get(profile_id, False) class InstallSystemPackage(Page): def __init__(self, page_stack, main_window=None, parent=None): super().__init__("InstallPackage", page_stack, main_window, parent) self.btn_path = main_window.btn_path self.update_status = main_window self.package_name = "" self.install_command = "" self.manual_install_command = "" self.is_sync = False self.ui_elements = [] self.permanent_elements = [] self.current_distro = "debian" self.distro_buttons = {} self._setup_permanent_ui() self._setup_distro_buttons() self.auto_install_package_commands = { 'all': { 'debian': 'pkexec apt install -y bubblewrap iproute2 microsocks proxychains4 ratpoison tor wireguard xserver-xephyr resolvconf', 'arch': 'pkexec pacman -S bubblewrap iproute2 microsocks proxychains-ng tor wireguard-tools xorg-server-xephyr resolvconf', 'fedora': 'pkexec dnf -y install bubblewrap iproute-doc proxychains4 ratpoison tor wireguard xorg-x11-server-Xephyr resolvconf' }, 'wireguard': { 'debian': 'pkexec apt -y install wireguard resolvconf', 'arch': 'pkexec pacman -S wireguard-tools resolvconf', 'fedora': 'pkexec dnf -y install wireguard-tools resolvconf' }, 'resolvconf': { 'debian': 'pkexec apt -y install resolvconf', 'arch': 'pkexec pacman -S resolvconf', 'fedora': 'pkexec dnf -y install resolvconf' }, 'tor': { 'debian': 'pkexec apt -y install tor proxychains4 microsocks', 'arch': 'pkexec pacman -S tor proxychains4 microsocks', 'fedora': 'pkexec dnf -y install tor proxychains4' }, 'tor_sync': { 'debian': 'pkexec apt -y install tor', 'arch': 'pkexec pacman -S tor', 'fedora': 'pkexec dnf -y install tor' }, 'proxychains4': { 'debian': 'pkexec apt -y install proxychains4', 'arch': 'pkexec pacman -S proxychains-ng', 'fedora': 'pkexec dnf -y install proxychains4' }, 'microsocks': { 'debian': 'pkexec apt -y install microsocks', 'arch': 'pkexec pacman -S microsocks', 'fedora': 'pkexec dnf -y install microsocks' }, 'xserver-xephyr': { 'debian': 'pkexec apt -y install xserver-xephyr bubblewrap ratpoison', 'arch': 'pkexec pacman -S xorg-server-xephyr bubblewrap ratpoison', 'fedora': 'pkexec dnf -y install xorg-x11-server-Xephyr bubblewrap ratpoison' }, 'bubblewrap': { 'debian': 'pkexec apt -y install bubblewrap', 'arch': 'pkexec pacman -S bubblewrap', 'fedora': 'pkexec dnf -y install bubblewrap' } } self.manual_install_package_commands = { 'all': { 'debian': 'sudo apt install -y bubblewrap iproute2 microsocks proxychains4 ratpoison tor wireguard xserver-xephyr resolvconf', 'arch': 'sudo pacman -S bubblewrap iproute2 microsocks proxychains-ng tor wireguard-tools xorg-server-xephyr resolvconf', 'fedora': 'sudo dnf -y install bubblewrap iproute-doc proxychains4 ratpoison tor wireguard xorg-x11-server-Xephyr resolvconf' }, 'polkit': { 'debian': 'sudo apt -y install policykit-1', 'arch': 'sudo pacman -S polkit', 'fedora': 'sudo dnf -y install polkit' }, 'wireguard': { 'debian': 'sudo apt -y install wireguard resolvconf', 'arch': 'sudo pacman -S wireguard-tools resolvconf', 'fedora': 'sudo dnf -y install wireguard-tools resolvconf' }, 'resolvconf': { 'debian': 'sudo apt -y install resolvconf', 'arch': 'sudo pacman -S resolvconf', 'fedora': 'sudo dnf -y install resolvconf' }, 'tor': { 'debian': 'sudo apt -y install tor proxychains4 microsocks', 'arch': 'sudo pacman -S tor proxychains-ng microsocks', 'fedora': 'sudo dnf -y install tor proxychains4' }, 'tor_sync': { 'debian': 'sudo apt -y install tor', 'arch': 'sudo pacman -S tor', 'fedora': 'sudo dnf -y install tor' }, 'proxychains4': { 'debian': 'sudo apt -y install proxychains4', 'arch': 'sudo pacman -S proxychains-ng', 'fedora': 'sudo dnf -y install proxychains4' }, 'microsocks': { 'debian': 'sudo apt -y install microsocks', 'arch': 'sudo pacman -S microsocks', 'fedora': 'sudo dnf -y install microsocks' }, 'xserver-xephyr': { 'debian': 'sudo apt -y install xserver-xephyr bubblewrap ratpoison', 'arch': 'sudo pacman -S xorg-server-xephyr bubblewrap ratpoison', 'fedora': 'sudo dnf -y install xorg-x11-server-Xephyr bubblewrap ratpoison' }, 'bubblewrap': { 'debian': 'sudo apt -y install bubblewrap', 'arch': 'sudo pacman -S bubblewrap', 'fedora': 'sudo dnf -y install bubblewrap' } } def _setup_permanent_ui(self): self.title.setGeometry(20, 50, 300, 60) self.title.setText("Package Installation") self.title.setStyleSheet("font-size: 24px; font-weight: bold;") self.permanent_elements.append(self.title) self.display.setGeometry(QtCore.QRect(100, 160, 580, 435)) self.permanent_elements.append(self.display) def _setup_distro_buttons(self): distros = ["debian", "arch", "fedora"] for i, distro in enumerate(distros): btn = QPushButton(self) btn.setGeometry(360 + i*140, 40, 120, 49) btn.setIcon(QIcon(os.path.join(self.btn_path, f"{distro}.png"))) btn.setIconSize(QSize(120, 49)) btn.setCheckable(True) btn.clicked.connect(lambda checked, d=distro: self.switch_distro(d)) self.distro_buttons[distro] = btn self.permanent_elements.append(btn) self.distro_buttons[self.current_distro].setChecked(True) def switch_distro(self, distro): self.current_distro = distro for d, btn in self.distro_buttons.items(): btn.setChecked(d == distro) if self.package_name: self.update_command() self.setup_ui() def update_command(self): if self.package_name in self.auto_install_package_commands: if self.is_sync: self.install_command = self.auto_install_package_commands['tor_sync'][self.current_distro] self.manual_install_command = self.manual_install_package_commands['tor_sync'][self.current_distro] else: self.install_command = self.auto_install_package_commands[self.package_name][self.current_distro] self.manual_install_command = self.manual_install_package_commands[self.package_name][self.current_distro] else: self.install_command = "" self.manual_install_command = "" def configure(self, package_name, distro, is_sync=False): self.package_name = package_name self.current_distro = distro self.is_sync = is_sync self.update_command() self.setup_ui() return self def setup_ui(self): self.button_back.setVisible(False) self.button_go.setVisible(True) if not self.package_name: return self.clear_ui() self._setup_auto_install() self._setup_manual_install() self._setup_status_and_nav() for element in self.ui_elements: element.setParent(self) element.setVisible(True) element.raise_() def clear_ui(self): for element in self.ui_elements: element.setParent(None) element.deleteLater() self.ui_elements.clear() self.command_box = None self.status_label = None self.install_button = None self.check_button = None def _setup_auto_install(self): auto_install_label = QLabel(f"Automatic Installation for {self.current_distro}", self) auto_install_label.setGeometry(80, 130, 580, 30) auto_install_label.setStyleSheet("font-size: 18px; font-weight: bold;") auto_install_label.setVisible(True) self.ui_elements.append(auto_install_label) auto_install_desc = QLabel("Click the button below to automatically install the missing packages:", self) auto_install_desc.setGeometry(80, 170, 650, 30) auto_install_desc.setVisible(True) self.ui_elements.append(auto_install_desc) self.install_button = QPushButton(self) self.install_button.setGeometry(135, 210, 100, 40) self.install_button.setIcon(QIcon(os.path.join(self.btn_path, "install.png"))) self.install_button.setIconSize(self.install_button.size()) self.install_button.clicked.connect(self.install_package) self.install_button.setVisible(True) self.ui_elements.append(self.install_button) if self.current_distro == 'arch': auto_install_label.setGeometry(50, 110, 580, 30) auto_install_desc.setGeometry(50, 140, 650, 30) self.install_button.setGeometry(150, 185, 100, 40) self.check_button = QPushButton(self) self.check_button.setGeometry(270, 185, 100, 40) self.check_button.setIcon(QIcon(os.path.join(self.btn_path, "check.png"))) self.check_button.setIconSize(self.check_button.size()) self.check_button.clicked.connect(self.check_package) self.check_button.setVisible(True) self.ui_elements.append(self.check_button) def _setup_manual_install(self): if self.current_distro != 'arch': manual_install_label = QLabel(f"Manual Installation for {self.current_distro}", self) manual_install_label.setGeometry(80, 290, 650, 30) manual_install_label.setStyleSheet("font-size: 18px; font-weight: bold;") manual_install_label.setVisible(True) self.ui_elements.append(manual_install_label) manual_install_desc = QLabel("Run this command in your terminal:", self) manual_install_desc.setGeometry(80, 330, 380, 30) manual_install_desc.setVisible(True) self.ui_elements.append(manual_install_desc) self.command_box = QLabel(self.manual_install_command, self) self.command_box.setGeometry(80, 370, 380, 40) if self.current_distro == 'arch': self.command_box.setGeometry(50, 280, 380, 40) metrics = self.command_box.fontMetrics() text_width = metrics.horizontalAdvance(self.manual_install_command) + 100 available_width = self.command_box.width() - 20 font_size = 18 MIN_FONT_SIZE = 10 while text_width > available_width: if font_size <= MIN_FONT_SIZE: break font_size -= 1 text_width -= 10 self.command_box.setStyleSheet(f""" background-color: #2b2b2b; color: #ffffff; padding: 10px; border-radius: 5px; font-family: monospace; font-size: {font_size}px; """) self.command_box.mousePressEvent = lambda _: self.copy_command() self.command_box.setVisible(True) self.ui_elements.append(self.command_box) if self.current_distro == 'arch': # Pacman commands section pacman_label = QLabel("Pacman Installation:", self) pacman_label.setGeometry(50, 240, 650, 30) pacman_label.setStyleSheet("font-size: 18px; font-weight: bold; color: #00ffff;") pacman_label.setVisible(True) self.ui_elements.append(pacman_label) # AUR commands section aur_label = QLabel("AUR / Yay Installation:", self) aur_label.setGeometry(50, 350, 300, 30) aur_label.setStyleSheet("font-size: 18px; font-weight: bold; color: #00ffff;") self.ui_elements.append(aur_label) # Yay command self.yay_command_box = QLabel("sudo yay -S ratpoison", self) self.yay_command_box.setGeometry(50, 390, 300, 40) self._setup_command_box_style(self.yay_command_box) self.yay_command_box.mousePressEvent = lambda _: self.copy_command("sudo yay -S ratpoison", self.yay_command_box) self.ui_elements.append(self.yay_command_box) # Manual AUR command aur_command = "git clone https://aur.archlinux.org/ratpoison.git\ncd ratpoison\nmakepkg -si --skippgpcheck" self.aur_command_box = QLabel(aur_command, self) self.aur_command_box.setGeometry(370, 390, 380, 60) self._setup_command_box_style(self.aur_command_box) self.aur_command_box.setWordWrap(True) self.aur_command_box.mousePressEvent = lambda _: self.copy_command(aur_command, self.aur_command_box) self.ui_elements.append(self.aur_command_box) else: self.command_box = QLabel(self.manual_install_command, self) self.command_box.setGeometry(80, 370, 380, 40) metrics = self.command_box.fontMetrics() text_width = metrics.horizontalAdvance(self.manual_install_command) + 100 available_width = self.command_box.width() - 20 font_size = 18 MIN_FONT_SIZE = 10 while text_width > available_width: if font_size <= MIN_FONT_SIZE: break font_size -= 1 text_width -= 10 self.command_box.setStyleSheet(f""" background-color: #2b2b2b; color: #ffffff; padding: 10px; border-radius: 5px; font-family: monospace; font-size: {font_size}px; """) self.command_box.mousePressEvent = lambda _: self.copy_command() self.command_box.setVisible(True) self.ui_elements.append(self.command_box) def _setup_command_box_style(self, command_box): metrics = command_box.fontMetrics() text_width = metrics.horizontalAdvance(command_box.text()) + 100 available_width = command_box.width() - 20 font_size = 18 MIN_FONT_SIZE = 10 while text_width > available_width: if font_size <= MIN_FONT_SIZE: break font_size -= 1 text_width -= 10 command_box.setStyleSheet(f""" background-color: #2b2b2b; color: #ffffff; padding: 10px; border-radius: 5px; font-family: monospace; font-size: {font_size}px; """) command_box.setVisible(True) def copy_yay_command(self): clipboard = QApplication.clipboard() clipboard.setText("sudo yay -S ratpoison") original_style = self.yay_command_box.styleSheet() self.yay_command_box.setStyleSheet(original_style + "background-color: #27ae60;") QTimer.singleShot(200, lambda: self.yay_command_box.setStyleSheet(original_style)) self.update_status.update_status("Yay command copied to clipboard") def _setup_status_and_nav(self): if self.current_distro != 'arch': self.check_button = QPushButton(self) self.check_button.setGeometry(130, 430, 100, 40) self.check_button.setIcon(QIcon(os.path.join(self.btn_path, "check.png"))) self.check_button.setIconSize(self.check_button.size()) self.check_button.clicked.connect(self.check_package) self.ui_elements.append(self.check_button) self.button_go.clicked.connect(self.go_next) self.button_back.clicked.connect(self.go_next) self.status_label = QLabel("", self) self.status_label.setGeometry(300, 430, 250, 40) if self.current_distro == 'arch': self.status_label.setGeometry(450, 185, 250, 40) self.ui_elements.append(self.status_label) def copy_command(self, command=None, command_box=None): if command_box is None: command_box = self.command_box clipboard = QApplication.clipboard() if command: clipboard.setText(command) else: clipboard.setText(self.manual_install_command) original_style = command_box.styleSheet() command_box.setStyleSheet(original_style + "background-color: #27ae60;") QTimer.singleShot(200, lambda: command_box.setStyleSheet(original_style)) self.update_status.update_status("Command copied to clipboard") def install_package(self): if not self.install_command: self.update_status.update_status(f"Installation not supported for {self.current_distro}") return if subprocess.getstatusoutput('pkexec --help')[0] == 127: self.update_status.update_status("polkit toolkit is not installed") self.install_button.setDisabled(True) return self.worker_thread = WorkerThread('INSTALL_PACKAGE', package_command=self.install_command, package_name=self.package_name) self.worker_thread.text_output.connect(self.update_status.update_status) self.worker_thread.finished.connect(self.installation_complete) self.worker_thread.start() def installation_complete(self, success): if success: self.check_package() def check_package(self): try: if self.package_name.lower() == 'all': packages = ['bwrap', 'ip', 'microsocks', 'proxychains4', 'ratpoison', 'tor', 'Xephyr', 'wg', 'resolvconf'] for package in packages: if subprocess.getstatusoutput(f'{package} --help')[0] == 127: self.status_label.setText(f"{package} is not installed") self.status_label.setStyleSheet("color: red;") return self.status_label.setText("All packages installed!") self.status_label.setStyleSheet("color: green;") elif self.package_name.lower() == 'wireguard': self.install_name = 'wg' elif self.package_name.lower() == 'bubblewrap': self.install_name = 'bwrap' elif self.package_name.lower() == 'xserver-xephyr': self.install_name = 'Xephyr' else: self.install_name = self.package_name if subprocess.getstatusoutput(f'{self.install_name.lower()} --help')[0] == 127: self.status_label.setText(f"{self.package_name} is not installed") self.status_label.setStyleSheet("color: red;") else: self.status_label.setText(f"{self.package_name} is installed!") self.status_label.setStyleSheet("color: green;") except Exception: self.status_label.setText("Check failed") self.status_label.setStyleSheet("color: red;") def go_next(self): self.page_stack.setCurrentIndex(self.page_stack.indexOf(self.page_stack.findChild(MenuPage))) class WelcomePage(Page): def __init__(self, page_stack, main_window=None, parent=None): super().__init__("Welcome", page_stack, main_window, parent) self.btn_path = main_window.btn_path self.update_status = main_window self.ui_elements = [] self.button_next.clicked.connect(self.go_to_install) self.button_next.setVisible(True) self._setup_welcome_ui() self._setup_stats_display() def _setup_welcome_ui(self): welcome_title = QLabel("Welcome to HydraVeilVPN", self) welcome_title.setGeometry(20, 45, 760, 60) welcome_title.setStyleSheet(""" font-size: 32px; font-weight: bold; color: cyan; """) welcome_title.setAlignment(Qt.AlignmentFlag.AlignCenter) welcome_msg = QLabel( "Before we begin your journey, we need to set up a few essential components. " "Click 'Next' to take you to the installation page.", self) welcome_msg.setGeometry(40, 100, 720, 80) welcome_msg.setWordWrap(True) welcome_msg.setStyleSheet(""" font-size: 16px; color: cyan; """) welcome_msg.setAlignment(Qt.AlignmentFlag.AlignCenter) def _setup_stats_display(self): stats_container = QWidget(self) stats_container.setGeometry(70, 200, 730, 240) stats_title = QLabel(stats_container) stats_title.setText("Features & Services") stats_title.setGeometry(190, 10, 300, 30) stats_title.setStyleSheet(""" font-size: 22px; font-weight: bold; color: cyan; """) grid_widget = QWidget(stats_container) grid_widget.setGeometry(0, 70, 700, 190) grid_layout = QGridLayout(grid_widget) grid_layout.setSpacing(30) stats = [ ("Browsers", "browsers_mini.png", "10 Supported Browsers", True), ("WireGuard", "wireguard_mini.png", "6 Global Locations", True), ("Proxy", "just proxy_mini.png", "5 Proxy Locations", True), ("Tor", "toricon_mini.png", "Tor Network Access", True) ] for i, (title, icon_name, count, available) in enumerate(stats): row = i // 2 col = i % 2 stat_widget = QWidget() stat_layout = QHBoxLayout(stat_widget) stat_layout.setContentsMargins(20, 10, 2, 10) stat_layout.setSpacing(15) icon_label = QLabel() icon_path = os.path.join(self.btn_path, icon_name) icon_label.setPixmap(QPixmap(icon_path).scaled(30, 30, Qt.AspectRatioMode.KeepAspectRatio, Qt.TransformationMode.SmoothTransformation)) icon_label.setFixedSize(30, 30) stat_layout.addWidget(icon_label) text_widget = QWidget() text_layout = QVBoxLayout(text_widget) text_layout.setSpacing(5) text_layout.setContentsMargins(0, 0, 30, 0) title_widget = QWidget() title_layout = QHBoxLayout(title_widget) title_layout.setContentsMargins(0, 0, 0, 0) title_layout.setSpacing(10) title_label = QLabel(title) title_label.setStyleSheet("font-size: 16px; font-weight: bold; color: #2c3e50;") title_layout.addWidget(title_label) status_indicator = QLabel("●") status_indicator.setStyleSheet("color: #2ecc71; font-size: 16px;") title_layout.addWidget(status_indicator) title_layout.addStretch() text_layout.addWidget(title_widget) count_label = QLabel(count) count_label.setStyleSheet("color: #34495e;") text_layout.addWidget(count_label) stat_layout.addWidget(text_widget, stretch=1) stat_widget.setStyleSheet(""" QWidget { background-color: transparent; border-radius: 10px; } """) grid_layout.addWidget(stat_widget, row, col) def go_to_install(self): install_page = self.page_stack.findChild(InstallSystemPackage) install_page.configure(package_name='all', distro='debian') self.page_stack.setCurrentIndex(self.page_stack.indexOf(install_page)) class ProtocolPage(Page): def __init__(self, page_stack, main_window=None, parent=None): super().__init__("Protocol", page_stack,main_window, parent) self.main_window = main_window self.selected_protocol = None self.btn_path = main_window.btn_path self.selected_protocol_icon = None self.connection_manager = main_window.connection_manager self.button_back.setVisible(True) self.update_status = main_window self.button_go.clicked.connect(self.go_selected) self.coming_soon_label = QLabel("Coming soon", self) self.coming_soon_label.setGeometry(210, 50, 200, 40) self.coming_soon_label.setStyleSheet("font-size: 22px;") self.sync_label = QLabel("Sync finds out what plans, countries, \n& browsers are available", self) self.sync_label.setGeometry(100, 120,500,60) self.sync_label.setStyleSheet("font-size: 20px;") self.button_sync = QPushButton(self) self.button_sync.setObjectName("sync_button") self.button_sync.setGeometry(100, 250,150,60) self.button_sync.setIconSize(self.button_sync.size()) icon = QIcon(os.path.join(self.btn_path, "sync_button.png")) self.button_sync.setIcon(icon) self.button_sync.clicked.connect(self.handle_core_action_sync_button) self.tor_button_sync = QPushButton(self) self.tor_button_sync.setObjectName("tor_sync") self.tor_button_sync.setGeometry(300, 250,150,60) self.tor_button_sync.setIconSize(self.tor_button_sync.size()) tor_icon = QIcon(os.path.join(self.btn_path, "tor_sync.png")) self.tor_button_sync.setIcon(tor_icon) self.tor_button_sync.clicked.connect(lambda: self.handle_core_action_sync_button(True)) self.button_sync.setVisible(True) self.tor_button_sync.setVisible(True) self.coming_soon_label.setVisible(False) self.title.setGeometry(585, 40, 185, 40); self.title.setText("Pick a Protocol") self.display.setGeometry(QtCore.QRect(0, 50, 580, 435))#relacion 4:3 self.create_interface_elements() def sync_button(self, toggle: bool, sync_complete: bool=False): self.button_sync.setDisabled(toggle) self.tor_button_sync.setDisabled(toggle) if sync_complete: for button in self.buttons: button.setDisabled(toggle) self.button_sync.setVisible(False) self.tor_button_sync.setVisible(False) self.sync_label.setVisible(False) def handle_core_action_sync_button(self, tor: bool = False): self.sync_button(True) if tor: self.main_window.set_toggle_state(True) self.sync_button(True) self.update_status.update_status('Syncing through Tor in progress...') self.worker_thread = WorkerThread('SYNC_TOR') self.worker_thread.sync_output.connect(self.update_output) self.worker_thread.text_output.connect(self.update_text_output) self.worker_thread.start() return self.update_status.update_status('Syncing in progress...') self.worker_thread = WorkerThread('SYNC') self.worker_thread.sync_output.connect(self.update_output) self.worker_thread.start() def update_text_output(self, text): self.update_status.update_status(text) def update_output(self, available_locations, status, is_tor, locations, is_os_error): if is_os_error: install_page = self.page_stack.findChild(InstallSystemPackage) install_page.configure(package_name='tor', distro='debian', is_sync=True) self.page_stack.setCurrentIndex(self.page_stack.indexOf(install_page)) self.sync_button(False) return if status is False: self.sync_button(False) self.update_status.update_status('An error occurred during sync') return self.update_status.update_status('Sync complete') self.connection_manager.set_synced(True) available_locations_list = [] self.connection_manager.store_locations(locations) grid_positions = [ (395, 90, 185, 75), (585, 90, 185, 75), (395, 195, 185, 75), (585, 195, 185, 75), (395, 300, 185, 75), (585, 300, 185, 75), (395, 405, 185, 75), (585, 405, 185, 75) ] list1 = [] for i, location in enumerate(available_locations): position_index = i % len(grid_positions) list1.append((QPushButton, location, grid_positions[position_index])) for item in list1: available_locations_list.append(item) self.sync_button(False, True) location_page = self.find_location_page() hidetor_page = self.find_hidetor_page() if location_page: location_page.create_interface_elements(available_locations_list) if hidetor_page: hidetor_page.create_interface_elements(available_locations_list) def find_location_page(self): for i in range(self.page_stack.count()): page = self.page_stack.widget(i) if isinstance(page, LocationPage): return page return None def find_hidetor_page(self): for i in range(self.page_stack.count()): page = self.page_stack.widget(i) if isinstance(page, HidetorPage): return page return None def create_interface_elements(self): self.buttonGroup = QButtonGroup(self) self.buttons = [] for j, (object_type,icon_name, page_class, geometry) in enumerate([ (QPushButton,"wireguard", WireGuardPage, (585, 90,185,75)), (QPushButton,"residential", ResidentialPage, (585, 90+30+75,185,75)), (QPushButton,"hidetor", HidetorPage, (585, 90+30+75+30+75,185,75)), (QPushButton,"open", LocationPage, (585, 90+30+75+30+75+30+75,185,75)) ]): boton = object_type(self) boton.setGeometry(*geometry) boton.setIconSize(boton.size()) boton.setCheckable(True) boton.setDisabled(True) boton.setIcon(QIcon(os.path.join(self.btn_path, f"{icon_name}_button.png"))) self.buttons.append(boton) self.buttonGroup.addButton(boton, j) boton.clicked.connect(lambda _, page=page_class, protocol=icon_name: self.show_protocol(page, protocol)) def update_swarp_json(self): self.update_status.write_data({"protocol": self.selected_protocol_icon}) def show_protocol(self, page_class, protocol): self.display.setPixmap(QPixmap(os.path.join(self.btn_path, f"{protocol}.png")).scaled(self.display.size(), Qt.AspectRatioMode.KeepAspectRatio)) self.selected_protocol_icon = protocol self.selected_page_class = page_class if protocol in ["wireguard", "hidetor"]: self.button_go.setVisible(True) self.coming_soon_label.setVisible(False) else: self.button_go.setVisible(False) self.coming_soon_label.setVisible(True) self.update_swarp_json() def go_selected(self): if self.selected_page_class: selected_page = self.selected_page_class self.page_stack.setCurrentIndex(self.page_stack.indexOf(self.page_stack.findChild(selected_page))) self.display.clear() for boton in self.buttons: boton.setChecked(False) self.button_go.setVisible(False) def find_menu_page(self): for i in range(self.page_stack.count()): page = self.page_stack.widget(i) if isinstance(page, MenuPage): return page return None class WireGuardPage(Page): browser_label_created = False # Variable de clase para verificar si el QLabel ya se ha creado def __init__(self, page_stack, main_window, parent=None): super().__init__("Wireguard", page_stack, main_window, parent) self.buttonGroup = QButtonGroup(self) self.btn_path = main_window.btn_path self.update_status = main_window self.buttons = [] self.selected_protocol = None self.selected_protocol_icon = None self.button_back.setVisible(True) self.button_go.clicked.connect(self.go_selected) self.additional_labels=[ ] self.title.setGeometry(585, 40, 185, 40); self.title.setText("Pick a Protocol") #self.display.setGeometry(QtCore.QRect(15, 50, 550, 411))#relacion 4:3 #self.display.setPixmap(QPixmap(os.path.join(self.btn_path, f"{wireguard}.png")).scaled(self.display.size(), Qt.AspectRatioMode.KeepAspectRatio)) self.sudo_warning = QLabel("Requires Sudo", self) self.sudo_warning.setGeometry(165, 90, 300, 40) self.sudo_warning.setStyleSheet(""" font-size: 24px; font-weight: bold; color: cyan; """) self.sudo_warning.setVisible(False) self.create_interface_elements() def create_interface_elements(self): for j, (object_type, icon_name, page_class, geometry) in enumerate([ (QPushButton, "system-wide", LocationPage, (585, 90, 185, 75)), (QPushButton, "browser-only", LocationPage, (585, 90+30+75+30+75, 185, 75)), (QLabel, None, None, (570, 170, 210, 105)), (QLabel, None, None, (570, 385, 210, 115)) ]): if object_type == QPushButton: boton = object_type(self) boton.setGeometry(*geometry) boton.setIconSize(boton.size()) boton.setCheckable(True) boton.setIcon(QIcon(os.path.join(self.btn_path, f"{icon_name}.png"))) self.buttons.append(boton) self.buttonGroup.addButton(boton, j) # Usamos argumentos por defecto para capturar los valores boton.clicked.connect(lambda _, page=page_class, protocol=icon_name: self.show_protocol(page, protocol)) elif object_type == QLabel: label = object_type(self) label.setGeometry(*geometry) label.setAlignment(QtCore.Qt.AlignmentFlag.AlignCenter) label.setWordWrap(True) if geometry == (570, 170, 210, 105): text1 = "VPN for the whole PC\n($1/month - 1 location)" label.setText(text1) elif geometry == (570, 385, 210, 115): text2 = "1 Profile.\n1 Country.\nIsolated VPN for just the browser\n(1$/month)" label.setText(text2) # Conexión de la señal clicked del botón con la función correspondiente def update_swarp_json(self): inserted_data = { "protocol": "wireguard", "connection": self.selected_protocol_icon } self.update_status.write_data(inserted_data) def show_protocol(self, page_class, protocol): if protocol == "browser-only": self.sudo_warning.setVisible(False) else: self.sudo_warning.setVisible(True) self.protocol_chosen = protocol self.selected_protocol_icon = protocol self.selected_page_class = page_class self.button_go.setVisible(True) self.update_swarp_json() # Eliminar labels adicionales anteriores for label in self.additional_labels: label.deleteLater() self.additional_labels.clear() if protocol == "system-wide": # Crear QLabel con la imagen os.path.join(self.btn_path, "browser only.png") detrás del label_principal label_principal = QLabel(self) label_principal.setGeometry(0, 60, 540, 460) # Geometría según necesidades pixmap = QPixmap(os.path.join(self.btn_path, "wireguard.png")) label_principal.setPixmap(pixmap) label_principal.show() label_principal.setScaledContents(True) label_principal.lower() # Colocar label_background debajo del label_principal self.additional_labels.append(label_principal) if protocol == "browser-only": # Crear QLabel principal label_principal = QLabel(self) label_principal.setGeometry(0, 80, 530, 380) pixmap = QPixmap(os.path.join(self.btn_path, "wireguard.png")) label_principal.setPixmap(pixmap) label_principal.show() label_principal.setScaledContents(True) label_principal.show() self.additional_labels.append(label_principal) # Crear QLabel con la imagen os.path.join(self.btn_path, "browser only.png") detrás del label_principal label_background = QLabel(self) label_background.setGeometry(0, 60, 540, 460) # Geometría según necesidades pixmap = QPixmap(os.path.join(self.btn_path, "browser only.png")) label_background.setPixmap(pixmap) label_background.show() label_background.setScaledContents(True) label_background.lower() # Colocar label_background debajo del label_principal self.additional_labels.append(label_background) def go_selected(self): if self.selected_page_class: if self.protocol_chosen == 'system-wide': self.page_stack.setCurrentIndex(self.page_stack.indexOf(self.page_stack.findChild(LocationPage))) else: selected_page = self.selected_page_class self.page_stack.setCurrentIndex(self.page_stack.indexOf(self.page_stack.findChild(selected_page))) self.display.clear() for boton in self.buttons: boton.setChecked(False) self.button_go.setVisible(False) def reverse(self): self.display.clear() for boton in self.buttons: boton.setChecked(False) self.button_go.setVisible(False) self.page_stack.setCurrentIndex(self.page_stack.indexOf(self.page_stack.findChild(ProtocolPage))) class lokinetPage(Page): def __init__(self, page_stack, parent=None): super().__init__("Lokinet", page_stack, parent) self.create_interface_elements() def create_interface_elements(self): pass class OpenPage(Page): def __init__(self, page_stack, parent=None): super().__init__("Opne", page_stack, parent) pass class HidetorPage(Page): def __init__(self, page_stack, main_window, parent=None): super().__init__("HideTor", page_stack, main_window, parent) self.selected_location_icon = None self.update_status = main_window self.button_next.clicked.connect(self.go_selected) self.button_reverse.setVisible(True) self.button_reverse.clicked.connect(self.reverse) self.display.setGeometry(QtCore.QRect(5, 10, 390, 520)) self.title.setGeometry(395, 40, 380, 40); self.title.setText("Pick a location") def create_interface_elements(self, available_locations): self.buttonGroup = QButtonGroup(self) self.buttons = [] for j, (object_type, icon_name, geometry) in enumerate(available_locations): boton = object_type(self) boton.setGeometry(*geometry) boton.setIconSize(boton.size()) boton.setCheckable(True) if icon_name == 'Malaysia': boton.setVisible(False) boton.setIcon(QIcon(os.path.join(self.btn_path, f"{icon_name}_button.png"))) self.buttons.append(boton) self.buttonGroup.addButton(boton, j) boton.clicked.connect(lambda _, location=icon_name: self.show_location(location)) def update_swarp_json(self): inserted_data = { "location": self.selected_location_icon, "connection": 'tor' } self.update_status.write_data(inserted_data) def show_location(self, location): tor_hide_img = f'{location}_hdtor' self.display.setPixmap(QPixmap(os.path.join(self.btn_path, f"{tor_hide_img}.png")).scaled(self.display.size(), Qt.AspectRatioMode.KeepAspectRatio)) self.selected_location_icon = location self.button_next.setVisible(True) self.update_swarp_json() def reverse(self): self.page_stack.setCurrentIndex(self.page_stack.indexOf(self.page_stack.findChild(ProtocolPage))) def go_selected(self): self.page_stack.setCurrentIndex(self.page_stack.indexOf(self.page_stack.findChild(BrowserPage))) class ResidentialPage(Page): def __init__(self, page_stack, main_window, parent=None): super().__init__("Wireguard", page_stack, main_window, parent) self.title.setGeometry(585, 40, 185, 40); self.title.setText("Pick a Protocol") self.update_status = main_window self.button_reverse.setVisible(True) self.button_reverse.clicked.connect(self.reverse) self.button_go.clicked.connect(self.go_selected) self.display_1 = QLabel(self) self.display_1.setGeometry(QtCore.QRect(15, 50, 550, 465))#relacion 4:3 self.display_1.setPixmap(QPixmap(os.path.join(self.btn_path, "browser only.png"))) self.label = QLabel(self) self.label.setGeometry(440, 370, 86,130) self.label.setPixmap(QPixmap(os.path.join(self.btn_path, "tor 86x130.png"))) self.label.hide() def showEvent(self, event): super().showEvent(event) self.create_interface_elements() def create_interface_elements(self): self.buttonGroup = QButtonGroup(self) self.buttons = [] for j, (object_type, icon_name, page_class, geometry) in enumerate([ (QPushButton, "tor", TorPage, (585, 90, 185, 75)), (QPushButton, "just proxy", TorPage, (585, 90+30+75+30+75, 185, 75)), (QLabel, None, None, (570, 170, 210, 105)), (QLabel, None, None, (570, 385, 210, 115)) ]): if object_type == QPushButton: boton = object_type(self) boton.setGeometry(*geometry) boton.setIconSize(boton.size()) boton.setCheckable(True) boton.setIcon(QIcon(os.path.join(self.btn_path, f"{icon_name}_button.png"))) self.buttons.append(boton) self.buttonGroup.addButton(boton, j) boton.show() # Conectar el botón a la función show_residential boton.clicked.connect(lambda checked,page=page_class, name=icon_name: self.show_residential(name,page)) elif object_type == QLabel: label = object_type(self) label.setGeometry(*geometry) label.setAlignment(QtCore.Qt.AlignmentFlag.AlignCenter) label.setWordWrap(True) label.show() if geometry == (570, 170, 210, 105): text1 = "Connect to tor first\nThen the residential proxy\nThis hides Tor from websites" label.setText(text1) elif geometry == (570, 385, 210, 115): text2 = "Connect directly\nTo the Proxy\nin a browser\nThis has no encryption" label.setText(text2) def show_residential(self, icon_name,page_class): self.selected_page_class = page_class if icon_name == "tor": self.label.show() if icon_name == "just proxy": self.label.hide() self.button_go.setVisible(True) self.update_swarp_json(icon_name) def go_selected(self): if self.selected_page_class: selected_page = self.selected_page_class self.page_stack.setCurrentIndex(self.page_stack.indexOf(self.page_stack.findChild(selected_page))) for boton in self.buttons: boton.setChecked(False) self.button_go.setVisible(False) def update_swarp_json(self, icon_name): inserted_data = { "connection": icon_name } self.update_status.write_data(inserted_data) def reverse(self): self.page_stack.setCurrentIndex(self.page_stack.indexOf(self.page_stack.findChild(ProtocolPage))) class TorPage(Page): def __init__(self, page_stack, main_window, parent=None): super().__init__("TorPage", page_stack, main_window, parent) self.update_status = main_window self.button_back.setVisible(True) self.title.setGeometry(585, 40, 185, 40); self.title.setText("Pick a Country") self.display.setGeometry(QtCore.QRect(0, 100, 540, 405))#relacion 4:3 self.display0=QLabel(self) self.display0.setGeometry(QtCore.QRect(0, 50, 600, 465))#relacion 4:3 self.display0.setPixmap(QPixmap(os.path.join(self.btn_path, "browser only.png"))) self.display0.lower() # Colocar display2 debajo de otros widgets self.button_go.clicked.connect(self.go_selected) self.button_reverse.setVisible(True) self.button_reverse.clicked.connect(self.reverse_selected) self.label = QLabel(self) self.label.setGeometry(440, 370, 86,130) pixmap = QPixmap(os.path.join(self.btn_path, "tor 86x130.png")) # Reemplaza "ruta/a/la/imagen.png" por la ruta de tu imagen self.label.setPixmap(pixmap) self.label.hide() def showEvent(self, event): super().showEvent(event) self.extraccion() def extraccion(self): self.data_profile = {} profile = self.update_status.read_data() profile_1 = profile.get('Profile_1') self.data_profile['connection'] = profile_1.get('connection') for key, value in self.data_profile.items(): print(f"{key}: {value}") self.verificate(value) def verificate(self, value): # Verificar el valor de key if value == "just proxy": self.display0.show() self.label.hide() elif value == "tor": self.display0.show() self.label.show() # Colocar display2 debajo de otros widgets else: pass self.buttonGroup = QButtonGroup(self) self.buttons = [] ''' for j, (object_type, icon_name, geometry) in enumerate([ (QPushButton, "brazil", (585, 90, 185, 75)), (QPushButton, "germany", (585, 170, 185, 75)), (QPushButton, "eeuu", (585, 250, 185, 75)), (QPushButton, "united kingdom", (585, 330, 185, 75)), (QPushButton, "hong kong", (585, 410, 185, 75)) ]): boton = object_type(self) boton.setGeometry(*geometry) boton.setIconSize(boton.size()) boton.setCheckable(True) boton.setIcon(QIcon(os.path.join(self.btn_path, f"{{icon_name}}_button.png"))) self.buttons.append(boton) self.buttonGroup.addButton(boton, j) boton.clicked.connect(lambda _, dimentions=icon_name: self.show_dimentions(dimentions)) boton.show() ''' def show_dimentions(self, dimentions): self.display.setPixmap(QPixmap(os.path.join(self.btn_path, f"{dimentions} garaje.png")).scaled(self.display.size(), Qt.AspectRatioMode.KeepAspectRatio)) self.selected_dimentions_icon = dimentions self.button_go.setVisible(True) self.update_swarp_json() def update_swarp_json(self): inserted_data = { "country_garaje": self.selected_dimentions_icon } self.update_status.write_data(inserted_data) def reverse_selected(self): self.page_stack.setCurrentIndex(self.page_stack.indexOf(self.page_stack.findChild(ResidentialPage))) def go_selected(self): self.page_stack.setCurrentIndex(self.page_stack.indexOf(self.page_stack.findChild(BrowserPage))) self.display.clear() for boton in self.buttons: boton.setChecked(False) self.button_go.setVisible(False) class TorLocationPage(Page): def __init__(self, page_stack, parent=None): super().__init__("TorPage", page_stack, parent) pass class ConnectionPage(Page): def __init__(self, page_stack, parent=None): super().__init__("Connection", page_stack, parent) self.create_interface_elements() def create_interface_elements(self): self.display.setGeometry(QtCore.QRect(5, 50, 390, 430)) self.title.setGeometry(QtCore.QRect(465, 40, 300, 20)) self.title.setText("Pick a TOR") labels_info = [ ("server", None, (410, 115)), ("", "rr2", (560, 115)), ("port", None, (410, 205)), ("", "rr2", (560, 205)), ("username",None, (410, 295)), ("", "rr2", (560, 295)), ("password",None, (410, 385)), ("", "rr2", (560, 385)) ] for j, (text, image_name, position) in enumerate(labels_info): label = QLabel(self) label.setAlignment(QtCore.Qt.AlignmentFlag.AlignCenter) if image_name: # Si hay un nombre de imagen, usar el tamaño predeterminado (220x50) label.setGeometry(position[0], position[1], 220, 50) else: # Si no hay una imagen, usar el tamaño personalizado (130x30) label.setGeometry(position[0], position[1], 140, 50) label.setStyleSheet("background-color: rgba(255, 255, 255, 51); padding: 3px;") if image_name: # Si hay un nombre de imagen, crear una QLabel con la imagen pixmap = QPixmap(os.path.join(self.btn_path, f"{image_name}.png")) label.setPixmap(pixmap) label.setScaledContents(True) # Escalar contenido para ajustarlo al tamaño del QLabel else: # Si no hay una imagen, crear una QLabel con el texto label.setText(text) class LocationPage(Page): def __init__(self, page_stack, main_window, parent=None): super().__init__("Location", page_stack, main_window, parent) self.selected_location_icon = None self.update_status = main_window self.button_reverse.setVisible(True) self.button_reverse.clicked.connect(self.reverse) self.display.setGeometry(QtCore.QRect(5, 10, 390, 520)) self.title.setGeometry(395, 40, 380, 40); self.title.setText("Pick a location") # Add initial display self.initial_display = QLabel(self) self.initial_display.setGeometry(5, 22, 355, 485) self.initial_display.setAlignment(Qt.AlignmentFlag.AlignCenter) self.initial_display.setWordWrap(True) def showEvent(self, event): super().showEvent(event) self.button_next.setVisible(False) for button in self.buttons: button.setChecked(False) def create_interface_elements(self, available_locations): print(available_locations) self.buttonGroup = QButtonGroup(self) self.buttons = [] for j, (object_type, icon_name, geometry) in enumerate(available_locations): print(object_type, icon_name, geometry) boton = object_type(self) boton.setGeometry(*geometry) boton.setIconSize(boton.size()) boton.setCheckable(True) boton.setIcon(QIcon(os.path.join(self.btn_path, f"{icon_name.lower()}_button.png"))) self.buttons.append(boton) self.buttonGroup.addButton(boton, j) boton.clicked.connect(lambda _, location=icon_name: self.show_location(location)) def update_swarp_json(self, get_connection=False): profile_data = self.update_status.read_data() self.connection_type = profile_data.get("connection", "") if get_connection: return self.connection_type inserted_data = { "location": self.selected_location_icon } self.update_status.write_data(inserted_data) def show_location(self, location): self.initial_display.hide() self.display.setPixmap(QPixmap(os.path.join(self.btn_path, f"{location}.png")).scaled(self.display.size(), Qt.AspectRatioMode.KeepAspectRatio)) self.selected_location_icon = location self.button_next.setVisible(True) self.button_next.clicked.connect(self.go_selected) self.update_swarp_json() def reverse(self): self.page_stack.setCurrentIndex(self.page_stack.indexOf(self.page_stack.findChild(ProtocolPage))) def go_selected(self): if self.connection_type == "system-wide": self.page_stack.setCurrentIndex(self.page_stack.indexOf(self.page_stack.findChild(ResumePage))) else: self.page_stack.setCurrentIndex(self.page_stack.indexOf(self.page_stack.findChild(BrowserPage))) class BrowserPage(Page): def __init__(self, page_stack, main_window, parent=None): super().__init__("Browser", page_stack, main_window, parent) self.btn_path = main_window.btn_path self.update_status = main_window self.selected_browser_icon = None self.display.setGeometry(QtCore.QRect(5, 10, 390, 520)) self.title.setGeometry(395, 40, 380, 40) self.title.setText("Pick a Browser") self.button_back.setVisible(True) self.create_interface_elements() def create_interface_elements(self): self._setup_scroll_area() button_widget = self._create_button_widget() self._populate_button_widget(button_widget) self.scroll_area.setWidget(button_widget) def _setup_scroll_area(self): self.scroll_area = QScrollArea(self) self.scroll_area.setGeometry(400, 90, 385, 400) self.scroll_area.setWidgetResizable(True) self.scroll_area.setHorizontalScrollBarPolicy(Qt.ScrollBarPolicy.ScrollBarAlwaysOff) self._apply_scroll_area_style() def _apply_scroll_area_style(self): self.scroll_area.setStyleSheet(""" QScrollArea { background: transparent; border: none; } QScrollBar:vertical { border: 1px solid white; background: white; width: 10px; margin: 0px 0px 0px 0px; } QScrollBar::handle:vertical { background: cyan; min-height: 0px; } QScrollBar::add-line:vertical { height: 0px; subcontrol-position: bottom; subcontrol-origin: margin; } QScrollBar::sub-line:vertical { height: 0px; subcontrol-position: top; subcontrol-origin: margin; } """) def _create_button_widget(self): button_widget = QWidget() button_widget.setStyleSheet("background: transparent;") self.buttonGroup = QButtonGroup(self) self.buttons = [] return button_widget def _populate_button_widget(self, button_widget): browser_icons = [ ("brave 1.70.123", "librewolf 130.0.1-1"), ("chromium 129.0.6668.89-1", "chromium 122.0.6261.94-1"), ("firefox 131.0.2", "firefox 123.0"), ("librewolf 125.0.2-1", "brave 1.63.165"), ("firefox 128.0.2", "brave 1.71.121") ] dimensions = self._get_button_dimensions() total_height = self._create_browser_buttons(button_widget, browser_icons, dimensions) self._set_final_widget_size(button_widget, dimensions, total_height) def _get_button_dimensions(self): return { 'width': 185, 'height': 75, 'spacing': 20, 'x_offset': 195 } def _create_browser_buttons(self, button_widget, browser_icons, dims): total_height = 0 for row_idx, row in enumerate(browser_icons): for col_idx, icon_name in enumerate(row): y_coord = row_idx * (dims['height'] + dims['spacing']) x_coord = 0 if col_idx == 0 else dims['x_offset'] self._create_single_button(button_widget, icon_name, x_coord, y_coord, dims) total_height = y_coord + dims['height'] return total_height def _create_single_button(self, parent, icon_name, x_coord, y_coord, dims): button = QPushButton(parent) button.setFixedSize(dims['width'], dims['height']) button.move(x_coord, y_coord) button.setIconSize(button.size()) button.setCheckable(True) button.setIcon(QIcon(BrowserPage.create_browser_button_image(icon_name, self.btn_path))) self._apply_button_style(button) self.buttons.append(button) self.buttonGroup.addButton(button) button.clicked.connect(lambda _, b=icon_name: self.show_browser(b)) @staticmethod def create_browser_button_image(browser_text, btn_path): browser_name, version = browser_text.split()[0], ' '.join(browser_text.split()[1:]) base_image = QPixmap(os.path.join(btn_path, f"{browser_name}_button.png")) painter = QPainter(base_image) BrowserPage._setup_version_font(painter, version, base_image) text_rect = painter.fontMetrics().boundingRect(version) x = (base_image.width() - text_rect.width()) // 2 - 27 y = (base_image.height() + text_rect.height()) // 2 + 10 painter.drawText(x, y, version) painter.end() return base_image @staticmethod def _setup_version_font(painter, version, base_image): font_size = 11 painter.setFont(QFont('Arial', font_size)) painter.setPen(QColor(0x88, 0x88, 0x88)) text_rect = painter.fontMetrics().boundingRect(version) while text_rect.width() > base_image.width() * 0.6 and font_size > 6: font_size -= 1 painter.setFont(QFont('Arial', font_size)) text_rect = painter.fontMetrics().boundingRect(version) def _apply_button_style(self, button): button.setStyleSheet(""" QPushButton { background: transparent; border: none; } QPushButton:hover { background-color: rgba(200, 200, 200, 30); } QPushButton:checked { background-color: rgba(200, 200, 200, 50); } """) def _set_final_widget_size(self, widget, dims, total_height): widget.setFixedSize(dims['width'] * 2 + 5, total_height + dims['spacing']) def show_browser(self, browser): browser_name, version = browser.split()[0], ' '.join(browser.split()[1:]) base_image = self._create_browser_display_image(browser_name, version) self.display.setPixmap(base_image.scaled(self.display.size(), Qt.AspectRatioMode.KeepAspectRatio)) self.selected_browser_icon = browser self.button_next.setVisible(True) self.update_swarp_json() def _create_browser_display_image(self, browser_name, version): base_image = QPixmap(os.path.join(self.btn_path, f"{browser_name}_icon.png")) painter = QPainter(base_image) painter.setFont(QFont('Arial', 25)) painter.setPen(QColor(0, 255, 255)) text_rect = painter.fontMetrics().boundingRect(version) x = (base_image.width() - text_rect.width()) // 2 y = base_image.height() - 45 painter.drawText(x, y, version) painter.end() return base_image def update_swarp_json(self): inserted_data = { "browser": self.selected_browser_icon } self.update_status.write_data(inserted_data) class ScreenPage(Page): def __init__(self, page_stack, main_window, parent=None): super().__init__("Screen", page_stack, main_window, parent) self.selected_dimentions = None self.update_status = main_window self.selected_dimentions_icon = None self.button_back.setVisible(True) self.title.setGeometry(585, 40, 185, 40); self.title.setText("Pick a Resolution") self.display.setGeometry(QtCore.QRect(5, 50, 580, 435))#relacion 4:3 self.create_interface_elements() def create_interface_elements(self): self.buttonGroup = QButtonGroup(self) self.buttons = [] for j, (object_type, icon_name, geometry) in enumerate([ (QPushButton, "800x600", (585, 90, 185, 75)), (QPushButton, "1024x760", (585, 170, 185, 75)), (QPushButton, "1152x1080", (585, 250, 185, 75)), (QPushButton, "1280x1024", (585, 330, 185, 75)), (QPushButton, "1920x1080", (585, 410, 185, 75)) ]): boton = object_type(self) boton.setGeometry(*geometry) boton.setIconSize(boton.size()) boton.setCheckable(True) boton.setIcon(QIcon(os.path.join(self.btn_path, f"{icon_name}_button.png"))) self.buttons.append(boton) self.buttonGroup.addButton(boton, j) boton.clicked.connect(lambda _, dimentions=icon_name: self.show_dimentions(dimentions)) def update_swarp_json(self): inserted_data = { "dimentions": self.selected_dimentions_icon } self.update_status.write_data(inserted_data) def show_dimentions(self, dimentions): self.display.setPixmap(QPixmap(os.path.join(self.btn_path, f"{dimentions}.png")).scaled(self.display.size(), Qt.AspectRatioMode.KeepAspectRatio)) self.selected_dimentions_icon = dimentions self.button_next.setVisible(True) self.update_swarp_json() class ResumePage(Page): def __init__(self, page_stack, main_window=None, parent=None): super().__init__("Resume", page_stack, main_window, parent) self.update_status = main_window self.connection_manager = main_window.connection_manager self.btn_path = main_window.btn_path self.labels_creados = [] self.additional_labels = [] self.button_go.clicked.connect(self.copy_profile) self.button_back.setVisible(True) self.title.setGeometry(585, 40, 185, 40); self.title.setText("Profile Summary") self.display.setGeometry(QtCore.QRect(5, 50, 580, 435))#relacion 4:3 self.buttonGroup = QButtonGroup(self) self.button_back.clicked.connect(self.reverse) self.create_arrow() self.create_interface_elements() def reverse(self): if self.connection_type == "system-wide": self.page_stack.setCurrentIndex(self.page_stack.indexOf(self.page_stack.findChild(LocationPage))) else: self.page_stack.setCurrentIndex(self.page_stack.indexOf(self.page_stack.findChild(ScreenPage))) def create_arrow(self): self.arrow_label = QLabel(self) self.arrow_label.setGeometry(400, 115, 200, 200) arrow_pixmap = QPixmap(os.path.join(self.btn_path, "arrow.png")) self.arrow_label.setPixmap(arrow_pixmap) self.arrow_label.setScaledContents(True) self.arrow_label.raise_() def showEvent(self, event): super().showEvent(event) self.arrow_label.show() self.arrow_label.raise_() def create_interface_elements(self): for j, (object_type, icon_name, geometry) in enumerate([ (QLineEdit, None, (130, 70, 300, 30)), (QLabel, None, (0,0,0,0)) ]): if object_type == QLabel: label = object_type(self) label.setGeometry(*geometry) icon_path = os.path.join(self.btn_path, f"{icon_name}_button.png") if os.path.exists(icon_path): label.setPixmap(QPixmap(icon_path)) elif object_type == QLineEdit: self.line_edit = object_type(self) # Define line_edit como un atributo de la clase ResumePage self.line_edit.setGeometry(*geometry) self.line_edit.setMaxLength(13) self.line_edit.textChanged.connect(self.toggle_button_visibility) self.line_edit.setStyleSheet("background-color: rgba(22, 10, 30, 0.5);") self.line_edit.setPlaceholderText("Type a name here") self.line_edit.setAlignment(Qt.AlignmentFlag.AlignCenter) self.create() def create(self): for label in self.additional_labels: label.deleteLater() self.additional_labels.clear() profile_1 = self.update_status.read_data() self.connection_type = profile_1.get("connection", "") connection_exists = 'connection' in profile_1 if connection_exists and profile_1['connection'] not in ["tor", "just proxy"]: items = ["protocol", "connection", "location", "browser", "dimentions"] initial_y = 90 label_height = 80 elif connection_exists: # si la conexión existe pero su texto es 'tor' o 'just proxy' items = ["protocol", "connection", "location", "browser", "dimentions"] initial_y = 90 label_height = 80 else: items = ["protocol", "location", "browser", "dimentions"] initial_y = 90 label_height = 105 for i, item in enumerate(items): text = profile_1.get(item, "") if text: if item == 'browser': # Use BrowserPage's static method base_image = BrowserPage.create_browser_button_image(text, self.btn_path) geometry = (585, initial_y + i * label_height, 185, 75) parent_label = QLabel(self) parent_label.setGeometry(*geometry) parent_label.setPixmap(base_image) parent_label.show() self.labels_creados.append(parent_label) else: icon_path = os.path.join(self.btn_path, f"{text}_button.png") geometry = (585, initial_y + i * label_height, 185, 75) parent_label = QLabel(self) parent_label.setGeometry(*geometry) parent_label.setPixmap(QPixmap(icon_path)) parent_label.show() self.labels_creados.append(parent_label) if connection_exists: if profile_1.get("connection", "") == "system-wide": image_path = os.path.join(self.btn_path, f"{profile_1.get('protocol', '')} {profile_1.get('location', '')}.png") main_label = QLabel(self) main_label.setGeometry(10, 130, 500, 375) main_label.setPixmap(QPixmap(image_path)) main_label.setScaledContents(True) main_label.show() self.labels_creados.append(main_label) if profile_1.get("connection", "") == "just proxy": image_path = os.path.join(self.btn_path, "browser only.png") label_background = QLabel(self) label_background.setGeometry(10, 50, 535, 460) label_background.setPixmap(QPixmap(image_path)) label_background.setScaledContents(True) label_background.show() label_background.lower() self.labels_creados.append(label_background) image_path = os.path.join(self.btn_path, f"{profile_1.get('country_garaje', '')} garaje.png") main_label = QLabel(self) main_label.setGeometry(10, 105, 530, 398) main_label.setPixmap(QPixmap(image_path)) main_label.setScaledContents(True) main_label.show() self.labels_creados.append(main_label) if profile_1.get("connection", "") == "tor": if profile_1.get("protocol", "") == "hidetor": location = profile_1.get("location", "") image_path = f'btn/{location}_hdtor.png' else: image_path = os.path.join(self.btn_path, "browser only.png") label_background = QLabel(self) label_background.setGeometry(10, 50, 535, 460) label_background.setPixmap(QPixmap(image_path)) label_background.setScaledContents(True) label_background.show() label_background.lower() self.labels_creados.append(label_background) image_path = os.path.join(self.btn_path, f"{profile_1.get('country_garaje', '')} garaje.png") main_label = QLabel(self) main_label.setGeometry(10, 105, 530, 398) main_label.setPixmap(QPixmap(image_path)) main_label.setScaledContents(True) main_label.show() self.labels_creados.append(main_label) image_path =os.path.join(self.btn_path, "tor 86x130.png") tor_label = QLabel(self) tor_label.setGeometry(450, 380, 86, 130) tor_label.setPixmap(QPixmap(image_path)) tor_label.setScaledContents(True) tor_label.show() self.labels_creados.append(tor_label) if profile_1.get("connection", "") == "browser-only": image_path = os.path.join(self.btn_path, "browser only.png") label_background = QLabel(self) label_background.setGeometry(10, 50, 535, 460) label_background.setPixmap(QPixmap(image_path)) label_background.setScaledContents(True) label_background.show() label_background.lower() self.labels_creados.append(label_background) image_path = os.path.join(self.btn_path, f"{profile_1.get('protocol', '')} {profile_1.get('location', '')}.png") main_label = QLabel(self) main_label.setGeometry(10, 130, 500, 375) main_label.setPixmap(QPixmap(image_path)) main_label.setScaledContents(True) main_label.show() self.labels_creados.append(main_label) else: image_path = os.path.join(self.btn_path, f"{profile_1.get('protocol', '')} {profile_1.get('location', '')}.png") main_label = QLabel(self) main_label.setGeometry(10, 130, 500, 375) main_label.setPixmap(QPixmap(image_path)) main_label.setScaledContents(True) main_label.show() self.labels_creados.append(main_label) if hasattr(self, 'arrow_label'): self.arrow_label.raise_() def toggle_button_visibility(self): self.button_go.setVisible(bool(self.line_edit.text())) def find_menu_page(self): # Method to find the MenuPage instance for i in range(self.page_stack.count()): page = self.page_stack.widget(i) if isinstance(page, MenuPage): return page return None def copy_profile(self): # Obtener el nombre del perfil ingresado en el QLineEdit profile_name = self.line_edit.text() menu_page = self.find_menu_page() if menu_page: number_of_profiles = menu_page.number_of_profiles # Abrir y cargar los datos de swarp.json profile_data = self.update_status.read_data() # Verificar si los campos necesarios están llenos required_fields = [profile_data.get("protocol"), profile_name] if not all(required_fields): print("Error: Some required fields are empty!") return # Actualizar el nombre del perfil con el texto ingresado profile_data["name"] = profile_name profile_data["visible"] = "yes" #profile_id = int(number_of_profiles) + 1 profiles = ProfileController.get_all() profile_id = self.get_next_available_id(profiles) new_profile = profile_data self.create_core_profiles(new_profile, profile_id) # Restablecer el índice de la página actual al menú principal self.page_stack.setCurrentIndex(self.page_stack.indexOf(self.page_stack.findChild(MenuPage))) self.update_status.clear_data() # Limpiar el QLineEdit self.line_edit.clear() self.display.clear() self.button_go.setVisible(False) def get_next_available_id(self, profiles: dict) -> int: if not profiles: return 1 existing_ids = sorted(profiles.keys()) for i in range(1, max(existing_ids) + 2): if i not in existing_ids: return i return None def create_core_profiles(self, profile, id): if profile.get('connection') != 'system-wide': browser_info = profile.get('browser', '').split() if len(browser_info) < 2: self.update_status.update_status('Application version not supported') return application = f"{browser_info[0].lower()}:{browser_info[1]}" else: application = '' print(profile.get('location')) location = self.connection_manager.get_location_info(profile.get('location')) print(location) if profile.get('protocol') == 'wireguard': connection_type = 'wireguard' elif profile.get('protocol') == 'residential': if profile.get('connection') == 'tor': connection_type = 'tor' elif profile.get('connection') == 'just proxy': connection_type = 'system' elif profile.get('protocol') == 'hidetor': connection_type = 'tor' else: self.update_status.update_status('Connection type not supported') return profile_data = { 'id': int(id), 'name': profile.get('name'), 'country_code': location.country_code, 'code': location.code, 'application': application, 'connection_type': connection_type, 'resolution': profile.get('dimentions', ''), } profile_type = 'system' if profile.get('connection') == 'system-wide' else 'session' action = f'CREATE_{profile_type.upper()}_PROFILE' self.handle_core_action_create_profile(action, profile_data, profile_type) def eliminacion(self): for label in self.labels_creados: label.deleteLater() # Limpia la lista de referencia self.labels_creados = [] self.create() if hasattr(self, 'arrow_label'): self.arrow_label.show() self.arrow_label.raise_() def handle_core_action_create_profile(self, action, profile_data=None, profile_type=None): self.worker_thread = WorkerThread(action, profile_data, profile_type) self.worker_thread.text_output.connect(self.update_output) self.worker_thread.start() self.worker_thread.wait() def update_output(self, text): self.update_status.update_status(text) def on_profile_creation(self, result): if self.worker_thread: self.worker_thread.quit() self.worker_thread.wait() self.worker_thread = None class EditorPage(Page): def __init__(self, page_stack, main_window): super().__init__("Editor", page_stack, main_window) self.page_stack = page_stack self.update_status = main_window self.connection_manager = main_window.connection_manager self.labels = [] # Lista para almacenar los labels dinámicamente self.buttons = [] # Lista para almacenar los labels dinámicamente self.title.setGeometry(570, 40, 185, 40); self.title.setText("Edit Profile") self.button_apply.setVisible(True) self.button_apply.clicked.connect(self.go_selected) self.button_back.setVisible(True) self.button_back.clicked.connect(self.go_back) self.name_handle = QLabel(self) self.name_handle.setGeometry(125, 70, 400, 30) self.name_handle.setText("Profile Name:") self.name = QLineEdit(self) self.name.setGeometry(250, 68, 400, 30) self.name.setStyleSheet("border: transparent;") self.temp_changes = {} self.original_values = {} self.display.setGeometry(QtCore.QRect(0, 60, 540, 405)) self.display.hide() self.garaje=QLabel(self) self.garaje.setGeometry(QtCore.QRect(0, 70, 540, 460)) self.brow_disp=QLabel(self) self.brow_disp.setGeometry(QtCore.QRect(0, 50, 540, 460)) self.brow_disp.setPixmap(QPixmap(os.path.join(self.btn_path, "browser only.png"))) self.brow_disp.hide() self.brow_disp.lower() def update_name_value(self): original_name = self.data_profile.get('name', '') new_name = self.name.text() if original_name != new_name: self.update_temp_value('name', new_name) else: pass def go_back(self): selected_profiles_str = ', '.join([f"Profile_{profile['profile_number']}" for profile in self.page_stack.widget(0).selected_profiles]) if self.has_unsaved_changes(selected_profiles_str): self.show_unsaved_changes_popup() else: self.page_stack.setCurrentIndex(self.page_stack.indexOf(self.page_stack.findChild(MenuPage))) def find_menu_page(self): for i in range(self.page_stack.count()): page = self.page_stack.widget(i) if isinstance(page, MenuPage): return page return None def find_resume_page(self): for i in range(self.page_stack.count()): page = self.page_stack.widget(i) if isinstance(page, ResumePage): return page return None def find_protocol_page(self): for i in range(self.page_stack.count()): page = self.page_stack.widget(i) if isinstance(page, ProtocolPage): return page return None def showEvent(self, event): super().showEvent(event) self.extraccion() def extraccion(self): self.data_profile = {} for label in self.labels: label.deleteLater() self.labels = [] for button in self.buttons: button.deleteLater() self.buttons = [] selected_profiles_str = ', '.join([f"Profile_{profile['profile_number']}" for profile in self.page_stack.widget(0).selected_profiles]) menu_page = self.find_menu_page() if menu_page: new_profiles = ProfileController.get_all() self.profiles_data = menu_page.match_core_profiles(profiles_dict=new_profiles) self.data_profile = self.profiles_data[selected_profiles_str].copy() if selected_profiles_str in self.temp_changes: for key, value in self.temp_changes[selected_profiles_str].items(): self.data_profile[key] = value self.name.textChanged.connect(self.update_name_value) self.verificate(self.data_profile, selected_profiles_str) def verificate(self, data_profile, selected_profile_str): protocol = data_profile.get("protocol", "") try: if protocol == "wireguard": self.process_and_show_labels(data_profile, { "protocol": ['wireguard', 'residential', 'hidetor'], "connection": ['browser-only', 'system-wide'], "location": self.connection_manager.get_location_list(), "browser": ['brave 1.63.165','brave 1.70.123', 'firefox 123.0', 'firefox 131.0.2', 'chromium 122.0.6261.94-1', 'chromium 129.0.6668.89-1', 'librewolf 125.0.2-1', 'librewolf 130.0.1-1', 'firefox 128.0.2', 'brave 1.71.121'], "dimentions": ['800x600', '1024x760', '1152x1080', '1280x1024', '1920x1080'] }, selected_profile_str) elif protocol == "residential" or protocol == "hidetor": self.process_and_show_labels(data_profile, { "protocol": ['residential', 'wireguard', 'hidetor'], "connection": ['tor', 'just proxy'], "location": self.connection_manager.get_non_residential_proxy_list(), "browser": ['brave 1.63.165','brave 1.70.123', 'firefox 123.0', 'firefox 131.0.2', 'chromium 122.0.6261.94-1', 'chromium 129.0.6668.89-1', 'librewolf 125.0.2-1', 'librewolf 130.0.1-1', 'firefox 128.0.2', 'brave 1.71.121'], "dimentions": ['800x600', '1024x760', '1152x1080', '1280x1024', '1920x1080'] }, selected_profile_str) elif protocol == "lokinet": self.process_and_show_labels(data_profile, { "protocol": ['lokinet'] }, selected_profile_str) elif protocol == "open": self.process_and_show_labels(data_profile, { "protocol": ['open'] }, selected_profile_str) except Exception as e: print(f'An error occurred: {e}') def process_and_show_labels(self, data_profile, parameters, selected_profile_str): protocol = data_profile.get('protocol', "") connection= data_profile.get('connection', "") location = data_profile.get('location', "") country_garaje = location name = data_profile.get('name', "") self.name.setText(name) if protocol=="hidetor": if connection == "just proxy": self.display.setPixmap(QPixmap(os.path.join(self.btn_path, f"{location}.png"))) else: self.display.setPixmap(QPixmap(os.path.join(self.btn_path, f"{location}_hdtor.png"))) self.display.show() self.display.setGeometry(0, 100, 400, 394) self.display.setScaledContents(True) self.garaje.hide() self.brow_disp.hide() if protocol=="wireguard": self.display.setGeometry(0, 60, 540, 405) self.display.show() self.display.setPixmap(QPixmap(os.path.join(self.btn_path, f"{protocol} {location}.png"))) self.garaje.hide() if connection == "browser-only": self.brow_disp.show() else: self.brow_disp.hide() if protocol=="residential": self.display.setGeometry(0, 60, 540, 405) self.brow_disp.show() self.garaje.show() self.display.hide() if country_garaje: country_garaje = 'brazil' self.garaje.setPixmap(QPixmap(os.path.join(self.btn_path, f"{country_garaje} garaje.png"))) for i, key in enumerate(parameters.keys()): if key == 'browser': current_value = f"{data_profile.get(key, '')}" split_value = current_value.split(' ', 1) browser_version = split_value[1] if len(split_value) > 1 else '' if browser_version == '': browser_version = data_profile.get('browser_version', '') current_value = f"{data_profile.get(key, '')} {browser_version}" if connection == 'system-wide' or not current_value.strip(): base_image = QPixmap() else: base_image = BrowserPage.create_browser_button_image(current_value, self.btn_path) else: image_path = os.path.join(self.btn_path, f"{data_profile.get(key, '')}_button.png") current_value = data_profile.get(key, '') base_image = QPixmap(image_path) label = QLabel(key, self) label.setGeometry(565, 90 + i * 80, 185, 75) label.setPixmap(base_image) label.setScaledContents(True) label.show() self.labels.append(label) try: value_index = parameters[key].index(current_value) except ValueError: value_index = 0 # Botón para mostrar el valor anterior prev_button = QPushButton(self) prev_button.setGeometry(535, 90 + i * 80, 30, 75) prev_button.clicked.connect(lambda _, k=key, idx=value_index: self.show_previous_value(k, idx, parameters)) prev_button.show() icon_path = os.path.join(self.btn_path, f"UP_button.png") icon = QPixmap(icon_path) transform = QTransform().rotate(180) rotated_icon = icon.transformed(transform) # Establecer el ícono girado en el botón prev_button.setIcon(QIcon(rotated_icon)) prev_button.setIconSize(prev_button.size()) self.buttons.append(prev_button) # Botón para mostrar el valor siguiente next_button = QPushButton( self) next_button.setGeometry(750, 90 + i * 80, 30, 75) next_button.clicked.connect(lambda _, k=key, idx=value_index: self.show_next_value(k, idx, parameters)) next_button.show() self.buttons.append(next_button) next_button.setIcon(QIcon( os.path.join(self.btn_path, f"UP_button.png"))) next_button.setIconSize(next_button.size()) prev_button.setVisible(True) next_button.setVisible(True) if key == 'protocol' or (protocol == 'wireguard' and key == 'connection'): prev_button.setDisabled(True) next_button.setDisabled(True) if connection == 'system-wide' and key == 'location': prev_button.setDisabled(True) next_button.setDisabled(True) if connection == 'system-wide' and (key == 'browser' or key == 'dimentions'): prev_button.setVisible(False) next_button.setVisible(False) def show_previous_value(self, key: str, index: int, parameters: dict) -> None: if len(parameters[key]) > 1: previous_index = (index - 1) % len(parameters[key]) previous_value = parameters[key][previous_index] self.update_temp_value(key, previous_value) def show_next_value(self, key: str, index: int, parameters: dict) -> None: if len(parameters[key]) > 1: next_index = (index + 1) % len(parameters[key]) next_value = parameters[key][next_index] self.update_temp_value(key, next_value) def update_temp_value(self, key: str, new_value: str) -> None: selected_profiles_str = ', '.join([f"Profile_{profile['profile_number']}" for profile in self.page_stack.widget(0).selected_profiles]) if selected_profiles_str not in self.temp_changes: self.temp_changes[selected_profiles_str] = {} self.original_values[selected_profiles_str] = self.data_profile.copy() self.temp_changes[selected_profiles_str][key] = new_value self.extraccion() def has_unsaved_changes(self, profile_str: str) -> bool: return profile_str in self.temp_changes and bool(self.temp_changes[profile_str]) def show_apply_changes_popup(self, selected_profiles_str: str) -> bool: if selected_profiles_str not in self.temp_changes: return False temp_changes = self.temp_changes[selected_profiles_str] current_data = self.original_values[selected_profiles_str] current_browser = f"{current_data.get('browser', '')} {current_data.get('browser_version', '')}" if 'location' in temp_changes and temp_changes['location'] != current_data.get('location'): message = "A new location would require a new subscription code.\nDo you want to proceed with erasing the old one?" elif 'browser' in temp_changes and temp_changes['browser'] != current_browser: message = "Changing the browser would delete all data associated with it. Proceed?" else: return False self.popup = ConfirmationPopup( self, message=message, action_button_text="Continue", cancel_button_text="Cancel" ) self.popup.setWindowModality(Qt.WindowModality.ApplicationModal) self.popup.finished.connect(lambda result: self.handle_apply_changes_response(result)) self.popup.show() return True def show_unsaved_changes_popup(self) -> None: self.popup = ConfirmationPopup( self, message="You have unsaved changes. Do you want to continue?", action_button_text="Continue", cancel_button_text="Cancel" ) self.popup.setWindowModality(Qt.WindowModality.ApplicationModal) self.popup.finished.connect(lambda result: self.handle_unsaved_changes_response(result)) self.popup.show() def handle_apply_changes_response(self, result: bool) -> None: if result: self.commit_changes() self.page_stack.setCurrentIndex(self.page_stack.indexOf(self.page_stack.findChild(MenuPage))) def handle_unsaved_changes_response(self, result: bool) -> None: if result: self.temp_changes.clear() self.page_stack.setCurrentIndex(self.page_stack.indexOf(self.page_stack.findChild(MenuPage))) def go_selected(self) -> None: selected_profiles_str = ', '.join([f"Profile_{profile['profile_number']}" for profile in self.page_stack.widget(0).selected_profiles]) if selected_profiles_str in self.temp_changes and self.temp_changes[selected_profiles_str]: needs_confirmation = self.show_apply_changes_popup(selected_profiles_str) if not needs_confirmation: self.commit_changes() self.page_stack.setCurrentIndex(self.page_stack.indexOf(self.page_stack.findChild(MenuPage))) else: self.page_stack.setCurrentIndex(self.page_stack.indexOf(self.page_stack.findChild(MenuPage))) def commit_changes(self) -> None: selected_profiles_str = ', '.join([f"Profile_{profile['profile_number']}" for profile in self.page_stack.widget(0).selected_profiles]) if selected_profiles_str in self.temp_changes: for key, new_value in self.temp_changes[selected_profiles_str].items(): self.update_core_profiles(key, new_value) self.temp_changes.pop(selected_profiles_str, None) self.original_values.pop(selected_profiles_str, None) def update_core_profiles(self, key, new_value): profile = ProfileController.get(int(self.update_status.current_profile_id)) self.update_res = False if key == 'dimentions': profile.resolution = new_value self.update_res = True elif key == 'name': profile.name = new_value elif key == 'connection': if new_value == 'tor': profile.connection.code = new_value profile.connection.masked = True elif new_value == 'just proxy': profile.connection.code = 'system' profile.connection.masked = True else: self.update_status.update_status('System wide profiles not supported atm') elif key == 'browser': browser_type, browser_version = new_value.split(' ', 1) profile.application_version.application_code = browser_type profile.application_version.version_number = browser_version elif key == 'protocol': if self.data_profile.get('connection') == 'system-wide': self.edit_to_session() else: profile.connection.code = 'wireguard' if new_value == 'wireguard' else 'tor' profile.connection.masked = False if new_value == 'wireguard' else True else: location = self.connection_manager.get_location_info(new_value) if profile.has_wireguard_configuration(): profile.delete_wireguard_configuration() if profile.has_proxy_configuration(): profile.delete_proxy_configuration() if location: profile.location.code = location.country_code profile.location.time_zone = self.connection_manager.get_timezone(location) profile.subscription = None ProfileController.update(profile) ''' def edit_to_system(self): id = int(self.update_status.current_profile_id) profile = self.data_profile if profile.get('location') == 'The Netherlands': location = 'nl' elif profile.get('location') == 'Moldova': location = 'md' elif profile.get('location') == 'US (Ohio)': location = 'us' else: pass if profile.get('connection') == 'tor': connection_type = 'tor' elif profile.get('connection') == 'just proxy': connection_type = 'system' else: connection_type = 'wireguard' profile_data = { 'id': int(id), 'name': profile.get('name'), 'location_code': location, 'connection_type': connection_type, } resume_page = self.find_resume_page() if resume_page: resume_page.handle_core_action_create_profile('CREATE_SYSTEM_PROFILE', profile_data, 'system') ''' def edit_to_session(self): id = int(self.update_status.current_profile_id) profile = self.data_profile default_app = 'firefox:123.0' default_resolution = '1024x760' location_code = self.connection_manager.get_location_info(profile.get('location')) connection_type = 'tor' profile_data = { 'id': int(id), 'name': profile.get('name'), 'country_code': location_code.country_code, 'code': location_code.code, 'application': default_app , 'connection_type': connection_type, 'resolution': default_resolution, } resume_page = self.find_resume_page() if resume_page: resume_page.handle_core_action_create_profile('CREATE_SESSION_PROFILE', profile_data, 'session') class Settings(Page): def __init__(self, page_stack, main_window, parent=None): super().__init__("Settings", page_stack, main_window, parent) self.update_status = main_window self.update_logging = main_window self.button_reverse.setVisible(True) self.button_reverse.setEnabled(True) self.button_reverse.clicked.connect(self.reverse) self.title.setGeometry(585, 40, 185, 40) self.title.setText("Settings") self.setup_ui() def setup_ui(self): main_container = QWidget(self) main_container.setGeometry(0, 0, 800, 520) self.layout = QHBoxLayout(main_container) self.layout.setContentsMargins(0, 60, 0, 0) self.left_panel = QWidget() self.left_panel.setFixedWidth(200) self.left_layout = QVBoxLayout(self.left_panel) self.left_layout.setContentsMargins(0, 0, 0, 0) self.left_layout.setSpacing(0) self.content_widget = QWidget() self.content_layout = QStackedLayout(self.content_widget) self.layout.addWidget(self.left_panel) self.layout.addWidget(self.content_widget) self.setup_menu_buttons() self.setup_pages() def setup_menu_buttons(self): menu_items = [ ("Overview", self.show_account_page), ("Subscriptions", self.show_subscription_page), ("Delete Profile", self.show_delete_page), ("Error Logs", self.show_logs_page) ] self.menu_buttons = [] for text, callback in menu_items: btn = QPushButton(text) btn.setFixedHeight(40) btn.setCursor(Qt.CursorShape.PointingHandCursor) btn.setCheckable(True) btn.clicked.connect(callback) btn.setStyleSheet(""" QPushButton { text-align: left; padding-left: 20px; border: none; background: transparent; color: #808080; } QPushButton:checked { background: rgba(255, 255, 255, 0.1); color: white; border-left: 3px solid #007AFF; } QPushButton:hover:!checked { background: rgba(255, 255, 255, 0.05); } """) self.left_layout.addWidget(btn) self.menu_buttons.append(btn) self.left_layout.addStretch() def setup_pages(self): self.account_page = self.create_account_page() self.subscription_page = self.create_subscription_page() self.logs_page = self.create_logs_page() self.delete_page = self.create_delete_page() self.content_layout.addWidget(self.account_page) self.content_layout.addWidget(self.wireguard_page) self.content_layout.addWidget(self.subscription_page) self.content_layout.addWidget(self.logs_page) self.content_layout.addWidget(self.delete_page) self.show_account_page() def create_delete_page(self): page = QWidget() layout = QVBoxLayout(page) layout.setContentsMargins(20, 20, 20, 20) layout.setSpacing(15) title = QLabel("DELETE PROFILE") title.setStyleSheet("color: #808080; font-size: 12px; font-weight: bold;") layout.addWidget(title) grid = QGridLayout() grid.setSpacing(10) self.profile_buttons = QButtonGroup() self.profile_buttons.setExclusive(True) profiles = ProfileController.get_all() for profile_id in range(1, 7): row = (profile_id - 1) // 2 col = (profile_id - 1) % 2 profile = profiles.get(profile_id) if profile: btn = QPushButton(f"Profile {profile_id}\n{profile.name}") btn.setCheckable(True) btn.setFixedSize(180, 60) btn.setStyleSheet(""" QPushButton { background: rgba(255, 255, 255, 0.1); border: none; color: white; border-radius: 5px; text-align: center; } QPushButton:checked { background: rgba(255, 255, 255, 0.3); border: 2px solid #007AFF; } QPushButton:hover:!checked { background: rgba(255, 255, 255, 0.2); } """) self.profile_buttons.addButton(btn, profile_id) else: btn = QWidget() btn.setFixedSize(180, 60) grid.addWidget(btn, row, col) layout.addLayout(grid) self.delete_button = QPushButton("Delete") self.delete_button.setEnabled(False) self.delete_button.setFixedSize(120, 40) self.delete_button.setStyleSheet(""" QPushButton { background: #ff4d4d; color: white; border: none; border-radius: 5px; font-weight: bold; } QPushButton:disabled { background: #666666; } QPushButton:hover:!disabled { background: #ff3333; } """) self.delete_button.clicked.connect(self.delete_selected_profile) self.profile_buttons.buttonClicked.connect(self.on_profile_selected) button_layout = QHBoxLayout() button_layout.addStretch() button_layout.addWidget(self.delete_button) button_layout.addStretch() layout.addStretch() layout.addLayout(button_layout) return page def on_profile_selected(self, button): self.delete_button.setEnabled(True) self.selected_profile_id = self.profile_buttons.id(button) def delete_selected_profile(self): if hasattr(self, 'selected_profile_id'): self.popup = ConfirmationPopup( self, message=f"Are you sure you want to delete Profile {self.selected_profile_id}?", action_button_text="Delete", cancel_button_text="Cancel" ) self.popup.setWindowModality(Qt.WindowModality.ApplicationModal) self.popup.finished.connect(self.handle_delete_confirmation) self.popup.show() def handle_delete_confirmation(self, confirmed): if confirmed: self.update_status.update_status(f'Deleting profile...') self.worker_thread = WorkerThread('DESTROY_PROFILE', profile_data={'id': self.selected_profile_id}) self.worker_thread.text_output.connect(self.delete_status_update) self.worker_thread.start() def delete_status_update(self, message): self.update_status.update_status(message) self.content_layout.removeWidget(self.delete_page) self.delete_page = self.create_delete_page() self.content_layout.addWidget(self.delete_page) self.content_layout.setCurrentWidget(self.delete_page) def get_combobox_style(self) -> str: return """ QComboBox { color: black; background: #f0f0f0; padding: 5px 30px 5px 10px; border: 1px solid #ccc; border-radius: 4px; min-width: 120px; margin-bottom: 10px; } QComboBox:disabled { color: #666; background: #e0e0e0; } QComboBox::drop-down { border: none; width: 30px; } QComboBox::down-arrow { image: url(assets/down_arrow.png); width: 12px; height: 12px; } QComboBox QAbstractItemView { color: black; background: white; selection-background-color: #007bff; selection-color: white; border: 1px solid #ccc; } """ def get_checkbox_style(self) -> str: return """ QCheckBox { color: white; spacing: 10px; margin-top: 10px; margin-bottom: 10px; } QCheckBox::indicator { width: 18px; height: 18px; } """ def populate_wireguard_profiles(self) -> None: self.wireguard_profile_selector.clear() profiles = ProfileController.get_all() for profile_id, profile in profiles.items(): if profile.connection.code == 'wireguard': self.wireguard_profile_selector.addItem(f"Profile {profile_id}: {profile.name}", profile_id) def convert_duration(self, value: Union[str, int], to_hours: bool = True) -> Union[str, int]: if to_hours: number, unit = value.split(' ') number = int(number) if unit in ['day', 'days']: return number * 24 elif unit in ['week', 'weeks']: return number * 7 * 24 else: raise ValueError(f"Unsupported duration unit: {unit}") else: hours = int(value) if hours % (7 * 24) == 0: weeks = hours // (7 * 24) return f"{weeks} {'week' if weeks == 1 else 'weeks'}" elif hours % 24 == 0: days = hours // 24 return f"{days} {'day' if days == 1 else 'days'}" else: raise ValueError(f"Hours value {hours} cannot be converted to days or weeks cleanly") def show_account_page(self): self.content_layout.setCurrentWidget(self.account_page) self.update_button_states(0) def show_subscription_page(self): self.content_layout.setCurrentWidget(self.subscription_page) self.update_button_states(1) def show_delete_page(self): self.content_layout.setCurrentWidget(self.delete_page) self.update_button_states(2) def show_logs_page(self): self.content_layout.setCurrentWidget(self.logs_page) self.update_button_states(3) def update_button_states(self, active_index): for i, btn in enumerate(self.menu_buttons): btn.setChecked(i == active_index) def reverse(self): self.page_stack.setCurrentIndex(self.page_stack.indexOf(self.page_stack.findChild(MenuPage))) def create_subscription_page(self): page = QWidget() layout = QVBoxLayout(page) layout.setSpacing(20) layout.setContentsMargins(20, 20, 20, 20) title = QLabel("Subscription Info") title.setStyleSheet("color: #808080; font-size: 12px; font-weight: bold;") layout.addWidget(title) profile_group = QGroupBox("Profile Selection") profile_group.setStyleSheet(""" QGroupBox { color: white; font-weight: bold; border: 1px solid rgba(255, 255, 255, 0.1); border-radius: 8px; padding: 15px; margin-top: 15px; } QGroupBox::title { subcontrol-origin: margin; left: 10px; padding: 0 5px; } """) profile_layout = QVBoxLayout(profile_group) self.profile_selector = QComboBox() self.profile_selector.setStyleSheet(self.get_combobox_style()) profiles = ProfileController.get_all() if profiles: for profile_id, profile in profiles.items(): self.profile_selector.addItem(f"Profile {profile_id}: {profile.name}", profile_id) profile_layout.addWidget(self.profile_selector) layout.addWidget(profile_group) subscription_group = QGroupBox("Subscription Details") subscription_group.setStyleSheet(""" QGroupBox { color: white; font-weight: bold; border: 1px solid rgba(255, 255, 255, 0.1); border-radius: 8px; padding: 15px; margin-top: 15px; } QGroupBox::title { subcontrol-origin: margin; left: 10px; padding: 0 5px; } """) subscription_layout = QGridLayout(subscription_group) subscription_layout.setSpacing(10) self.subscription_info = {} info_items = [ ("Billing Code", "billing_code"), ("Expires At", "expires_at") ] for i, (label, key) in enumerate(info_items): stat_widget = self.create_stat_widget(label, "N/A") row, col = divmod(i, 2) subscription_layout.addWidget(stat_widget, row, col) old_label = stat_widget.findChild(QLabel, "value_label") if old_label: old_label.setParent(None) old_label.deleteLater() value_label = ClickableValueLabel("N/A", stat_widget) value_label.setObjectName("value_label") value_label.setStyleSheet("color: #00ffff; font-size: 13px; font-weight: bold;") value_label.setAlignment(Qt.AlignmentFlag.AlignCenter) stat_widget.layout().insertWidget(0, value_label) self.subscription_info[key] = value_label layout.addWidget(subscription_group) layout.addStretch() self.profile_selector.currentIndexChanged.connect(self.update_subscription_info) if self.profile_selector.count() > 0: self.update_subscription_info(0) return page def update_subscription_info(self, index): if index < 0: return profile_id = self.profile_selector.itemData(index) if profile_id is None: return profile = ProfileController.get(profile_id) if profile and hasattr(profile, 'subscription') and profile.subscription: try: self.subscription_info["billing_code"].setText(str(profile.subscription.billing_code)) expires_at = profile.subscription.expires_at.strftime("%Y-%m-%d %H:%M:%S UTC") self.subscription_info["expires_at"].setText(expires_at) except Exception as e: print(f"Error updating subscription info: {e}") else: for label in self.subscription_info.values(): label.setText("N/A") def showEvent(self, event): super().showEvent(event) current_index = self.content_layout.currentIndex() self.content_layout.removeWidget(self.account_page) self.account_page = self.create_account_page() self.content_layout.addWidget(self.account_page) self.content_layout.removeWidget(self.subscription_page) self.subscription_page = self.create_subscription_page() self.content_layout.addWidget(self.subscription_page) self.content_layout.removeWidget(self.delete_page) self.delete_page = self.create_delete_page() self.content_layout.addWidget(self.delete_page) self.content_layout.removeWidget(self.logs_page) self.logs_page = self.create_logs_page() self.content_layout.addWidget(self.logs_page) self.content_layout.setCurrentIndex(current_index) if self.content_layout.currentWidget() == self.subscription_page: if self.profile_selector.count() > 0: self.update_subscription_info(0) def create_account_page(self): page = QWidget() layout = QVBoxLayout(page) layout.setSpacing(20) layout.setContentsMargins(20, 20, 20, 20) title = QLabel("Account Overview") title.setStyleSheet("color: #808080; font-size: 12px; font-weight: bold;") layout.addWidget(title) scroll_area = QScrollArea() scroll_area.setWidgetResizable(True) scroll_area.setStyleSheet(""" QScrollArea { border: none; background: transparent; } QScrollArea > QWidget > QWidget { background: transparent; } """) scroll_content = QWidget() scroll_layout = QVBoxLayout(scroll_content) scroll_layout.setSpacing(15) info_sections = [ ("Profile Statistics", self.create_profile_stats()), ("System Resources", self.create_system_stats()) ] for section_title, content_widget in info_sections: group = QGroupBox(section_title) group.setStyleSheet(""" QGroupBox { color: white; font-weight: bold; border: 1px solid rgba(255, 255, 255, 0.1); border-radius: 8px; padding: 15px; margin-top: 15px; } QGroupBox::title { subcontrol-origin: margin; left: 10px; padding: 0 5px; } """) group_layout = QVBoxLayout(group) group_layout.addWidget(content_widget) scroll_layout.addWidget(group) scroll_area.setWidget(scroll_content) layout.addWidget(scroll_area) return page def create_profile_stats(self): widget = QWidget() layout = QGridLayout(widget) layout.setSpacing(10) profiles = ProfileController.get_all() total_profiles = len(profiles) session_profiles = sum(1 for p in profiles.values() if isinstance(p, SessionProfile)) system_profiles = sum(1 for p in profiles.values() if isinstance(p, SystemProfile)) active_profiles = sum( 1 for p in profiles.values() if p.subscription and p.subscription.expires_at and p.subscription.expires_at > datetime.now(timezone.utc) ) stats = [ ("Total Profiles", total_profiles), ("Browser-only", session_profiles), ("System-wide", system_profiles), ("Active Profiles", active_profiles) ] for i, (label, value) in enumerate(stats): stat_widget = self.create_stat_widget(label, value) row, col = divmod(i, 2) layout.addWidget(stat_widget, row, col) return widget def create_system_stats(self): widget = QWidget() layout = QGridLayout(widget) layout.setSpacing(10) config_path = Constants.HV_CONFIG_HOME stats = [ ("Config Path", config_path), ("Client Version", Constants.HV_CLIENT_VERSION_NUMBER), ("Connection", ConfigurationController.get_connection()), ] for i, (label, value) in enumerate(stats): info_widget = QLabel(f"{label}: {value}") info_widget.setStyleSheet("color: white; padding: 5px;") info_widget.setWordWrap(True) layout.addWidget(info_widget, i, 0) return widget def create_stat_widget(self, label, value): widget = QFrame() widget.setStyleSheet(""" QFrame { background: rgba(255, 255, 255, 0.05); border-radius: 8px; padding: 10px; } """) layout = QVBoxLayout(widget) value_label = QLabel(str(value)) value_label.setObjectName("value_label") value_label.setStyleSheet("color: #00ffff; font-size: 24px; font-weight: bold;") value_label.setAlignment(Qt.AlignmentFlag.AlignCenter) desc_label = QLabel(label) desc_label.setStyleSheet("color: white; font-size: 12px;") desc_label.setAlignment(Qt.AlignmentFlag.AlignCenter) layout.addWidget(value_label) layout.addWidget(desc_label) return widget def create_horizontal_stat(self, label, value, total): widget = QWidget() layout = QHBoxLayout(widget) layout.setContentsMargins(0, 5, 0, 5) text_label = QLabel(f"{label}") text_label.setStyleSheet("color: white; font-size: 12px;") text_label.setFixedWidth(100) progress = QFrame() progress.setStyleSheet(""" QFrame { background: rgba(0, 255, 255, 0.3); border-radius: 3px; } """) percentage = (value / total) * 100 if total > 0 else 0 progress.setFixedSize(int(200 * (percentage / 100)), 20) value_label = QLabel(f"{value} ({percentage:.1f}%)") value_label.setStyleSheet("color: white; font-size: 12px;") value_label.setAlignment(Qt.AlignmentFlag.AlignRight) progress_container = QFrame() progress_container.setStyleSheet(""" QFrame { background: rgba(255, 255, 255, 0.1); border-radius: 3px; } """) progress_container.setFixedSize(200, 20) progress_layout = QHBoxLayout(progress_container) progress_layout.setContentsMargins(0, 0, 0, 0) progress_layout.addWidget(progress) progress.setFixedHeight(20) layout.addWidget(text_label) layout.addWidget(progress_container) layout.addWidget(value_label) layout.addStretch() return widget def setup_pages(self): self.account_page = self.create_account_page() self.subscription_page = self.create_subscription_page() self.logs_page = self.create_logs_page() self.delete_page = self.create_delete_page() self.content_layout.addWidget(self.account_page) self.content_layout.addWidget(self.subscription_page) self.content_layout.addWidget(self.logs_page) self.content_layout.addWidget(self.delete_page) self.show_account_page() def create_logs_page(self): page = QWidget() layout = QVBoxLayout(page) layout.setSpacing(20) layout.setContentsMargins(20, 20, 20, 20) title = QLabel("LOGGING SETTINGS") title.setStyleSheet("color: #808080; font-size: 12px; font-weight: bold;") layout.addWidget(title) log_text = QLabel(f"If enabled, these Error Logs would be stored locally on your computer\nat {Constants.HV_DATA_HOME}/gui") log_text.setStyleSheet("color: white; font-size: 14px;") layout.addWidget(log_text) logs_group = QGroupBox("Log Configuration") logs_group.setStyleSheet("QGroupBox { color: white; padding: 15px; }") logs_layout = QVBoxLayout(logs_group) self.enable_gui_logging = QCheckBox("Enable GUI logging") self.enable_gui_logging.setStyleSheet(self.get_checkbox_style()) logs_layout.addWidget(self.enable_gui_logging) layout.addWidget(logs_group) save_button = QPushButton() save_button.setFixedSize(75, 46) save_button.setIcon(QIcon(os.path.join(self.btn_path, f"save.png"))) save_button.setIconSize(QSize(75, 46)) save_button.clicked.connect(self.save_logs_settings) button_layout = QHBoxLayout() button_layout.addStretch() button_layout.addWidget(save_button) layout.addLayout(button_layout) layout.addStretch() self.load_logs_settings() return page def load_logs_settings(self) -> None: try: config = self.update_status._load_gui_config() if config and "logging" in config: self.enable_gui_logging.setChecked(config["logging"]["gui_logging_enabled"]) except Exception as e: logging.error(f"Error loading logging settings: {str(e)}") self.enable_gui_logging.setChecked(True) def save_logs_settings(self) -> None: try: config = self.update_status._load_gui_config() if config is None: config = { "logging": { "gui_logging_enabled": True, "log_level": "INFO" } } config["logging"]["gui_logging_enabled"] = self.enable_gui_logging.isChecked() self.update_status._save_gui_config(config) if self.enable_gui_logging.isChecked(): self.update_status._setup_gui_logging() else: self.update_status.stop_gui_logging() self.update_status.update_status("Logging settings saved successfully") except Exception as e: logging.error(f"Error saving logging settings: {str(e)}") self.update_status.update_status("Error saving logging settings") class IdPage(Page): def __init__(self, page_stack, main_window=None, parent=None): super().__init__("Id", page_stack, main_window, parent) self.update_status = main_window self.btn_path = main_window.btn_path self.buttonGroup = QButtonGroup(self) self.display.setGeometry(QtCore.QRect(-10, 50, 550, 460)) self.display.setPixmap(QPixmap(os.path.join(self.btn_path, "wireguardx.png")).scaled(self.display.size(), Qt.AspectRatioMode.KeepAspectRatio)) self.create_interface_elements() self.connect_button = QPushButton(self) self.connect_button.setObjectName("connect_button") self.connect_button.setGeometry(625, 450, 90, 50) self.connect_button.setIconSize(self.connect_button.size()) tor_icon = QIcon(os.path.join(self.btn_path, "connect.png")) self.connect_button.setIcon(tor_icon) self.connect_button.clicked.connect(self.on_connect) self.connect_button.setDisabled(True) self.button_reverse.setVisible(True) def on_connect(self): text = self.text_edit.toPlainText() import re pattern = r'^[A-Z0-9]{4}-[A-Z0-9]{4}-[A-Z0-9]{4}-[A-Z0-9]{4}$' if re.match(pattern, text): self.update_status.update_status('Enabling profile in progress...') profile_data = { 'id' : int(self.update_status.current_profile_id), 'billing_code': str(text) } menu_page = self.find_menu_page() if menu_page: menu_page.enabling_profile(profile_data) else: self.update_text_output('Incorrect format for the billing code') def find_menu_page(self): for i in range(self.page_stack.count()): page = self.page_stack.widget(i) if isinstance(page, MenuPage): return page return None def create_interface_elements(self): self.object_selected = None self.display.setGeometry(QtCore.QRect(5, 50, 550, 460)) self.title = QLabel("Entry Id", self) self.title.setGeometry(QtCore.QRect(560, 50, 220, 40)) self.title.setAlignment(QtCore.Qt.AlignmentFlag.AlignCenter) self.button_reverse.clicked.connect(self.reverse) self.button_go.clicked.connect(self.go_selected) objects_info = [ (QPushButton, os.path.join(self.btn_path, "new_id.png"), self.show_next, PaymentPage, (575, 100, 185, 75)), (QLabel, os.path.join(self.btn_path, "button230x220.png"), None, None, (550, 220, 250, 220)), (QTextEdit, None, self.validate_password, PaymentPage, (550, 230, 230, 190)) ] for obj_type, icon_name, function, page_class, geometry in objects_info: obj = obj_type(self) obj.setGeometry(*geometry) if isinstance(obj, QPushButton): obj.setIconSize(QtCore.QSize(190, 120)) obj.setIcon(QtGui.QIcon(icon_name)) obj.clicked.connect(lambda _, func=function, page=page_class: self.show_object_selected(func, page)) elif isinstance(obj, QLabel): obj.setPixmap(QPixmap(icon_name).scaled(obj.size(), Qt.AspectRatioMode.KeepAspectRatio)) elif isinstance(obj, QTextEdit): obj.setPlaceholderText("Or use an existing billing Id, enter it here") obj.textChanged.connect(self.toggle_button_state) self.text_edit = obj def toggle_button_state(self): text = self.text_edit.toPlainText() if text.strip(): self.connect_button.setEnabled(True) else: self.connect_button.setEnabled(False) def show_next(self): self.text_edit.clear() self.page_stack.setCurrentIndex(self.page_stack.indexOf(self.page_stack.findChild(PaymentPage))) def validate_password(self): self.button_go.setVisible(False) text = self.text_edit.toPlainText().strip().lower() # Comparar el texto con la palabra "zenaku" ignorando mayúsculas y minúsculas if text == "zenaku": self.button_go.setVisible(True) def show_object_selected(self, function, page_class): function() self.object_selected = page_class def go_selected(self): if self.object_selected: self.page_stack.setCurrentIndex(self.page_stack.indexOf(self.page_stack.findChild(self.object_selected))) self.display.clear() for boton in self.buttons: boton.setChecked(False) self.button_go.setVisible(False) def reverse(self): self.page_stack.setCurrentIndex(self.page_stack.indexOf(self.page_stack.findChild(MenuPage))) class PaymentPage(Page): def __init__(self, page_stack, main_window=None, parent=None): super().__init__("Payment", page_stack, main_window, parent) self.update_status = main_window self.text_fields = [] self.create_interface_elements() self.button_reverse.setVisible(True) def on_request_invoice(self): selected_currency = self.get_selected_currency() selected_time = self.get_selected_time_length() if selected_currency: self.currency = selected_currency else: self.update_status.update_status('No currency selected') return duration_month_num = int(selected_time.split()[0]) if duration_month_num == 12: total_hours = 365 * 24 else: total_hours = duration_month_num * (30 * 24) profile_data = { 'id': int(self.update_status.current_profile_id), 'duration': total_hours, 'currency': 'xmr' if self.currency == 'monero' else 'btc' if self.currency == 'bitcoin' else 'btc-ln' if self.currency == 'lightning' else 'ltc' if self.currency == 'litecoin' else None } self.update_status.update_status('Generating Invoice...') self.worker_thread = WorkerThread('GET_SUBSCRIPTION', profile_data=profile_data) self.worker_thread.text_output.connect(self.invoice_update_text_output) self.worker_thread.invoice_output.connect(self.on_invoice_generation_finished) self.worker_thread.invoice_finished.connect(self.on_invoice_finished) self.worker_thread.start() def invoice_update_text_output(self, text): self.update_status.update_status(text) if text == "No payment method found for the selected currency.": for field in self.text_fields: field.setText('') else: self.text_fields[3].setText(text) def on_invoice_generation_finished(self, billing_details: dict, text: str): billing_values = list(billing_details.values()) for i, dict_value in enumerate(billing_values): if i < 3: if i == 2: text = str(dict_value) self.full_address = text metrics = self.text_fields[2].fontMetrics() width = self.text_fields[2].width() elided_text = metrics.elidedText(text, QtCore.Qt.TextElideMode.ElideMiddle, width) self.text_fields[2].setText(elided_text) else: self.text_fields[i].setProperty("fullText", str(dict_value)) self.text_fields[i].setText(str(dict_value)) self.text_fields[3].setText(text) def on_invoice_finished(self, result): if result: self.show_payment_confirmed(self.text_fields[0].text()) return self.update_status.update_status('An error occurred when generating invoice') def show_payment_confirmed(self, billing_code): payment_page = self.find_payment_confirmed_page() if payment_page: for line in self.text_fields: line.setText('') payment_page.set_billing_code(billing_code) self.page_stack.setCurrentWidget(payment_page) else: print("PaymentConfirmed page not found") def find_payment_confirmed_page(self): for i in range(self.page_stack.count()): page = self.page_stack.widget(i) if isinstance(page, PaymentConfirmed): return page return None def create_interface_elements(self): self.title = QLabel("Payment Money", self) self.title.setGeometry(QtCore.QRect(530, 30, 210, 40)) self.title.setAlignment(QtCore.Qt.AlignmentFlag.AlignCenter) self.button_reverse.clicked.connect(self.reverse) labels_info = [ ("Your new billing ID", None, (20, 80),(240,50)), ("", "cuadro400x50", (20, 130),(400,50)), ("Time length", None, (20, 195),(150,50)), ("", "cuadro150x50", (200, 195),(150,50)), ("Pay",None, (20, 260),(120,50)), ("", "cuadro150x50", (200, 260),(150,50)), ("To address",None, (20, 325),(120,50)), ("", "cuadro400x50", (20, 375),(400,50)), ("", "cuadro400x50", (20, 450),(400,50)) ] self.clickable_labels = [] for j, (text, image_name, position, tamaño) in enumerate(labels_info): label = QLabel(self) label.setAlignment(QtCore.Qt.AlignmentFlag.AlignCenter) label.setGeometry(position[0], position[1], tamaño[0], tamaño[1]) if image_name: pixmap = QPixmap(os.path.join(self.btn_path, f"{image_name}.png")) label.setPixmap(pixmap) label.setScaledContents(True) else: label.setText(text) self.combo_box = QComboBox(self) self.combo_box.setGeometry(205, 205, 160, 30) self.combo_box.addItems(["1 month ($1)", "3 months ($3)", "6 months ($5)", "12 months ($10)"]) self.combo_box.setEditable(True) self.combo_box.setMaxVisibleItems(4) self.combo_box.setDuplicatesEnabled(True) self.combo_box.setPlaceholderText("Select options") line_edit_info = [ (20, 130, 400, 50), (200, 260, 150, 50), (20, 375, 400, 50), (20, 450, 400, 50) ] for j, (x, y, width, height) in enumerate(line_edit_info): line_edit = QLineEdit(self) line_edit.setGeometry(x, y, width, height) line_edit.setReadOnly(True) self.text_fields.append(line_edit) line_edit.setAlignment(QtCore.Qt.AlignmentFlag.AlignCenter) #line_edit.setStyleSheet("padding: 3px;") self.button = QPushButton(self) self.button.setGeometry(430, 130, 50, 50) icono = QIcon(os.path.join(self.btn_path, f"paste_button.png")) self.button.setIcon(icono) self.button.setIconSize(self.button.size()) self.button.clicked.connect(lambda : self.copy_text(0)) self.button = QPushButton(self) self.button.setGeometry(430, 375, 50, 50) icono = QIcon(os.path.join(self.btn_path, f"paste_button.png")) self.button.setIcon(icono) self.button.setIconSize(self.button.size()) self.button.clicked.connect(lambda: self.copy_text(2)) self.button = QPushButton(self) self.button.setGeometry(360, 260, 50, 50) icono = QIcon(os.path.join(self.btn_path, f"paste_button.png")) self.button.setIcon(icono) self.button.setIconSize(self.button.size()) self.button.clicked.connect(lambda: self.copy_text(1)) button_info = [ ("monero", self.show_monero, (545, 75)), ("bitcoin", self.show_bitcoin, (545, 290)), ("lightnering", self.show_lightning, (545, 180)), ("litecoin", self.show_litecoin, (545, 395)) ] self.buttonGroup = QButtonGroup(self) self.buttons = [] for j, (icon_name, function, position) in enumerate(button_info): boton = QPushButton(self) boton.setGeometry(position[0], position[1], 185, 75) boton.setIconSize(QSize(190, 120)) boton.setCheckable(True) self.buttons.append(boton) self.buttonGroup.addButton(boton, j) boton.setIcon(QIcon(os.path.join(self.btn_path, f"{icon_name}.png"))) #boton.clicked.connect(lambda _, func=function: func()) boton.clicked.connect(self.on_request_invoice) # Establecer la exclusividad de los botones def copy_text(self, field: int): try: original_status = self.update_status.status_label.text() original_status = original_status.replace("Status: ", "") if int(field) == 2: text = self.full_address else: text = self.text_fields[int(field)].text() QApplication.clipboard().setText(text) if field == 0: self.update_status.update_status('Billing code copied to clipboard!') elif field == 2: self.update_status.update_status('Address copied to clipboard!') else: self.update_status.update_status('Pay amount copied to clipboard!') QTimer.singleShot(2000, lambda: self.update_status.update_status(original_status)) except AttributeError: self.update_status.update_status('No content available for copying') except Exception as e: self.update_status.update_status(f'An error occurred when copying the text') def show_next(self): pass def get_selected_time_length(self): return self.combo_box.currentText() def get_selected_currency(self): selected_button = self.buttonGroup.checkedButton() if selected_button: index = self.buttonGroup.id(selected_button) currencies = ["monero", "bitcoin", "lightning", "litecoin"] return currencies[index] return None def show_monero(self): print("Monero selected") def show_bitcoin(self): print("Bitcoin selected") def show_lightning(self): print("Lightning Network selected") def show_litecoin(self): print("Litecoin selected") def reverse(self): for line in self.text_fields: line.setText('') self.page_stack.setCurrentIndex(self.page_stack.indexOf(self.page_stack.findChild(IdPage))) class ConfettiParticle: def __init__(self, x, y): self.x = x self.y = y self.color = QColor(random.randint(0, 255), random.randint(0, 255), random.randint(0, 255)) self.size = random.randint(5, 15) self.speed = random.uniform(1, 3) def update(self): self.y += self.speed class ConfettiThread(QThread): update_signal = pyqtSignal(list) def __init__(self, width, height): super().__init__() self.width = width self.height = height self.running = True self.mutex = QMutex() self.confetti = [ConfettiParticle(random.randint(0, width), random.randint(-height, 0)) for _ in range(100)] def run(self): while self.running: with QMutexLocker(self.mutex): for particle in self.confetti: particle.update() if particle.y > self.height: particle.y = random.randint(-100, 0) particle.x = random.randint(0, self.width) self.update_signal.emit(self.confetti.copy()) self.msleep(16) def stop(self): self.running = False def update_dimensions(self, width, height): with QMutexLocker(self.mutex): self.width = width self.height = height class ClickableLabel(QLabel): clicked = pyqtSignal() def __init__(self, text, parent=None, **kwargs): super().__init__(text, parent, **kwargs) self.setCursor(Qt.CursorShape.PointingHandCursor) self.hover_width = self.width() self.is_hovered = False def mousePressEvent(self, event): self.clicked.emit() super().mousePressEvent(event) def enterEvent(self, event): self.is_hovered = True self.update() super().enterEvent(event) def leaveEvent(self, event): self.is_hovered = False self.update() super().leaveEvent(event) def set_hover_width(self, width): self.hover_width = width self.update() def paintEvent(self, event): super().paintEvent(event) if self.is_hovered: painter = QPainter(self) painter.setRenderHint(QPainter.RenderHint.Antialiasing) hover_color = QColor(255, 255, 255, 25) center_x = self.width() // 2 start_x = center_x - (self.hover_width // 2) painter.fillRect(start_x, 0, self.hover_width, self.height(), hover_color) painter.end() class PaymentConfirmed(Page): def __init__(self, page_stack, main_window=None, parent=None): super().__init__("Id", page_stack, main_window, parent) self.update_status = main_window self.buttonGroup = QButtonGroup(self) self.display.setGeometry(QRect(-10, 50, 550, 460)) self.display.setPixmap(QPixmap(os.path.join(self.btn_path, f"wireguardx.png")).scaled(self.display.size(), Qt.AspectRatioMode.KeepAspectRatio)) self.button_reverse.setVisible(False) self.confetti = [] self.icon_label = QLabel(self) icon = QIcon(os.path.join(self.btn_path, "check_icon.png")) pixmap = icon.pixmap(QSize(64, 64)) self.icon_label.setPixmap(pixmap) self.icon_label.setAlignment(Qt.AlignmentFlag.AlignCenter) self.label = QLabel("Payment Completed!", alignment=Qt.AlignmentFlag.AlignCenter, parent=self) self.label.setStyleSheet("font-size: 24px; color: #2ecc71; font-weight: bold;") self.billing_label = QLabel("Your billing code:", alignment=Qt.AlignmentFlag.AlignCenter, parent=self) self.billing_label.setStyleSheet("font-size: 18px; color: white;") self.billing_code = ClickableLabel("", alignment=Qt.AlignmentFlag.AlignCenter, parent=self) self.billing_code.setStyleSheet(""" font-size: 24px; color: white; font-weight: bold; padding: 5px; border-radius: 5px; """) self.billing_code.clicked.connect(self.copy_billing_code) self.billing_code.set_hover_width(450) self.top_line = QFrame(self) self.top_line.setFrameShape(QFrame.Shape.HLine) self.top_line.setStyleSheet("color: #bdc3c7;") self.bottom_line = QFrame(self) self.bottom_line.setFrameShape(QFrame.Shape.HLine) self.bottom_line.setStyleSheet("color: #bdc3c7;") self.next_button = QPushButton("Next", self) self.next_button.setStyleSheet(""" QPushButton { background-color: #3498db; color: white; border: none; padding: 10px; font-size: 18px; border-radius: 5px; } QPushButton:hover { background-color: #2980b9; } """) self.next_button.clicked.connect(self.on_next_button) self.confetti_thread = ConfettiThread(self.width(), self.height()) self.confetti_thread.update_signal.connect(self.update_confetti) self.confetti_thread.start() def copy_billing_code(self): clipboard = QApplication.clipboard() clipboard.setText(self.billing_code.text()) original_style = self.billing_code.styleSheet() self.billing_code.setText("Copied!") self.billing_code.setStyleSheet(original_style + "background-color: #27ae60;") QTimer.singleShot(1000, lambda: self.reset_billing_code_style(original_style)) def reset_billing_code_style(self, original_style): self.billing_code.setStyleSheet(original_style) self.billing_code.setText(self.current_billing_code) def set_billing_code(self, code): self.current_billing_code = code self.billing_code.setText(code) def update_confetti(self, confetti): self.confetti = confetti self.update() def paintEvent(self, event): super().paintEvent(event) painter = QPainter(self) painter.setRenderHint(QPainter.RenderHint.Antialiasing) for particle in self.confetti: painter.setBrush(particle.color) painter.setPen(Qt.PenStyle.NoPen) painter.drawEllipse(QPointF(particle.x, particle.y), particle.size, particle.size) painter.end() def resizeEvent(self, event): super().resizeEvent(event) width = event.size().width() height = event.size().height() self.icon_label.setGeometry(QRect(width // 2 - 32, height // 8, 64, 64)) self.label.setGeometry(QRect(0, height // 4, width, 50)) self.billing_label.setGeometry(QRect(0, height // 2 - 50, width, 30)) self.top_line.setGeometry(QRect(width // 4, height // 2 + 20 , width // 2, 1)) self.billing_code.setGeometry(QRect(0, height // 2 + 28, width, 40)) self.bottom_line.setGeometry(QRect(width // 4, height // 2 + 75, width // 2, 1)) self.next_button.setGeometry(QRect(width // 4, height * 3 // 4, width // 2, 50)) self.confetti_thread.update_dimensions(width, height) def closeEvent(self, event): self.confetti_thread.stop() self.confetti_thread.wait() super().closeEvent(event) def on_next_button(self): self.page_stack.setCurrentIndex(self.page_stack.indexOf(self.page_stack.findChild(MenuPage))) def find_menu_page(self): for i in range(self.page_stack.count()): page = self.page_stack.widget(i) if isinstance(page, MenuPage): return page return None def reverse(self): self.page_stack.setCurrentIndex(self.page_stack.indexOf(self.page_stack.findChild(WireGuardPage))) class ConfirmationPopup(QWidget): finished = pyqtSignal(bool) def __init__(self, parent=None, message="", action_button_text="", cancel_button_text="Cancel"): super().__init__(parent) self.parent_window = parent self.message = message self.action_button_text = action_button_text self.cancel_button_text = cancel_button_text self.initUI() def initUI(self): self.setMinimumSize(400, 200) self.setWindowFlags(Qt.WindowType.FramelessWindowHint) self.setAttribute(Qt.WidgetAttribute.WA_TranslucentBackground) main_layout = QVBoxLayout() self.setLayout(main_layout) bg_widget = QWidget(self) bg_widget.setStyleSheet(""" background-color: white; border-radius: 10px; """) main_layout.addWidget(bg_widget) content_layout = QVBoxLayout(bg_widget) close_button = QPushButton("✕", self) close_button.setStyleSheet(""" QPushButton { background-color: transparent; color: #888888; font-size: 16px; font-weight: bold; border: none; } QPushButton:hover { color: #ff4d4d; } """) close_button.setFixedSize(30, 30) close_button.clicked.connect(self.close) content_layout.addWidget(close_button, alignment=Qt.AlignmentFlag.AlignRight) scroll_area = QScrollArea() scroll_area.setWidgetResizable(True) scroll_area.setFrameShape(QFrame.Shape.NoFrame) content_layout.addWidget(scroll_area) scroll_content = QWidget() scroll_layout = QVBoxLayout(scroll_content) message_label = QLabel(self.message) message_label.setAlignment(Qt.AlignmentFlag.AlignCenter) message_label.setFont(QFont("Arial", 14)) message_label.setStyleSheet("color: #333333; margin: 10px 0;") message_label.setWordWrap(True) scroll_layout.addWidget(message_label) scroll_area.setWidget(scroll_content) button_layout = QHBoxLayout() content_layout.addLayout(button_layout) cancel_button = QPushButton(self.cancel_button_text) cancel_button.setFixedSize(150, 50) cancel_button.setFont(QFont("Arial", 12)) cancel_button.setStyleSheet(""" QPushButton { background-color: #e0e0e0; border: none; color: #333333; border-radius: 5px; font-weight: bold; } QPushButton:hover { background-color: #d0d0d0; } """) cancel_button.clicked.connect(self.close) button_layout.addWidget(cancel_button) action_button = QPushButton(self.action_button_text) action_button.setFixedSize(150, 50) action_button.setFont(QFont("Arial", 12)) action_button.setStyleSheet(""" QPushButton { background-color: #ff4d4d; border: none; color: white; border-radius: 5px; font-weight: bold; } QPushButton:hover { background-color: #ff3333; } """) action_button.clicked.connect(self.perform_action) button_layout.addWidget(action_button) content_layout.addStretch() def perform_action(self): self.finished.emit(True) self.close() def mousePressEvent(self, event): self.oldPos = event.globalPosition().toPoint() def mouseMoveEvent(self, event): delta = event.globalPosition().toPoint() - self.oldPos self.move(self.x() + delta.x(), self.y() + delta.y()) self.oldPos = event.globalPosition().toPoint() class ClickableValueLabel(QLabel): def __init__(self, text: str, parent=None): super().__init__(text, parent) self.setObjectName("value_label") self.setStyleSheet("color: #00ffff; font-size: 13px; font-weight: bold;") self.setCursor(Qt.CursorShape.PointingHandCursor) self.timer = QTimer(self) self.timer.setSingleShot(True) self.timer.timeout.connect(self.reset_style) self.default_style = "color: #00ffff; font-size: 13px; font-weight: bold;" self.success_style = "color: #00ff00; font-size: 13px; font-weight: bold;" def mousePressEvent(self, event): if event.button() == Qt.MouseButton.LeftButton: clipboard = QApplication.clipboard() clipboard.setText(self.text()) self.setStyleSheet(self.success_style) self.timer.start(100) def reset_style(self): self.setStyleSheet(self.default_style) if __name__ == "__main__": app = QApplication(sys.argv) window = CustomWindow() window.show() sys.exit(app.exec())