diff --git a/gui/__main__.py b/gui/__main__.py index e69de29..c2fd84c 100644 --- a/gui/__main__.py +++ b/gui/__main__.py @@ -0,0 +1,5792 @@ +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['location_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(locations) + all_location_codes = [location.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.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("Simplified Privacy 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.label = QLineEdit(self) + + self.label.setFont(QFont("Arial", 8)) + self.label.setVisible(False) + self.label.returnPressed.connect(self.show_save_animation) + 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 show_save_animation(self): + profile = ProfileController.get(int(self.reverse_id)) + self.label.setStyleSheet(""" + QLineEdit { + background-color: rgba(40, 167, 69, 0.3); + border: none; + color: black; + } + """) + + QTimer.singleShot(200, lambda: self.label.setStyleSheet(""" + QLineEdit { + background-color: rgba(0, 0, 0, 0); + border: none; + color: %s; + } + """ % ('black' if profile.connection.code == 'wireguard' else 'white'))) + + self.label.setText(self.label.text()) + + profile.url = self.label.text() + ProfileController.update(profile, resolution=True) + + + + 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: + self.label.setVisible(False) + 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) + child_label.setPixmap(QPixmap(image_path)) + 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) + child_label.setPixmap(QPixmap(image_path)) + 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": + self.label.setGeometry(70, 77, 280, 30) + self.label.setStyleSheet(""" + QLineEdit { + background-color: rgba(0, 0, 0, 0); + border: none; + color: white; + } + """) + 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": + self.label.setGeometry(95, 71, 280, 30) + self.label.setStyleSheet(""" + QLineEdit { + background-color: rgba(0, 0, 0, 0); + border: none; + color: black; + } + """) + # 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": + self.label.setStyleSheet(""" + QLineEdit { + background-color: rgba(0, 0, 0, 0); + border: none; + color: white; + border-radius: 10px; + } + """) + 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) + + if profile: + name = profile.get("name", "") + profile = ProfileController.get(int(self.reverse_id)) + if isinstance(profile, SessionProfile): + self.label.setVisible(True) + homepage_url = SessionProfile.get_url(profile) + self.label.setText(homepage_url) + self.label.raise_() + else: + self.label.setVisible(False) + + def check_wireguard_rotation(self): + profile = ProfileController.get(int(self.reverse_id)) + if not profile.has_wireguard_configuration(): + return False + + rotation_frequency = profile.connection.wireguard_rotation_frequency + if not rotation_frequency: + return False + + time_elapsed = datetime.now(timezone.utc) - profile.connection.wireguard_rotation_last_performed + return time_elapsed > timedelta(hours=rotation_frequency) + + + + def launch(self): + pass + + + def connect(self): + if self.check_wireguard_rotation(): + profile = ProfileController.get(int(self.reverse_id)) + profile.delete_wireguard_configuration() + profile.connection.wireguard_rotation_last_performed = datetime.now(timezone.utc) + ProfileController.update(profile) + + 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 and widget != self.label: + widget.setParent(None) + + self.profile_info.clear() + self.label.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] + + for code, name in self._location_mapping.items(): + if name == location_key: + return code + + return None + + def store_locations(self, locations): + self._location_list = [] + for location in locations: + self._location_list.append(location.name) + + def get_location_list(self): + if self.is_synced(): + return self._location_list + 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 SimplifiedPrivacyVPN", 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) + location_geometry = { + "The Netherlands": (395, 300, 185, 75), + "US (Ohio)": (585, 90, 185, 75), + "Malaysia": (395, 90, 185, 75), + "US (Seattle)": (395, 195, 185, 75), + #"Romania": (585, 195, 185, 75), + #"Finland": (395, 300, 185, 75), + "Iceland": (585, 195, 185, 75), + "Moldova": (585, 300, 185, 75) + } + list1 = [(QPushButton, location, location_geometry[location]) for location in available_locations] + 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): + 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) + 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, 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 = '' + + + location = self.connection_manager.get_location_info(profile.get('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'), + 'location_code': location, + '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.textChanged.connect(self.update_name_value) + 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): + new_name = self.name.text() + self.update_temp_value('name', new_name) + + 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.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}" + 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): + print('new value', 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 + profile.location.time_zone = self.connection_manager.get_timezone(location) + profile.subscription = None + + + ProfileController.update(profile, resolution=self.update_res) + + + + + + + + ''' + 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 = self.connection_manager.get_location_info(profile.get('location')) + + connection_type = 'tor' + + profile_data = { + 'id': int(id), + 'name': profile.get('name'), + 'location_code': location, + '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), + ("WireGuard Settings", self.show_wireguard_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.wireguard_page = self.create_wireguard_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 create_wireguard_page(self): + page = QWidget() + layout = QVBoxLayout(page) + layout.setSpacing(20) + layout.setContentsMargins(20, 20, 20, 20) + + title = QLabel("WireGuard Settings") + title.setStyleSheet("color: #808080; font-size: 12px; font-weight: bold;") + layout.addWidget(title) + + profile_group = QGroupBox("Profile Selection") + profile_group.setStyleSheet("QGroupBox { color: white; padding: 15px; }") + profile_layout = QVBoxLayout(profile_group) + + self.wireguard_profile_selector = QComboBox() + self.wireguard_profile_selector.setStyleSheet(self.get_combobox_style()) + self.populate_wireguard_profiles() + profile_layout.addWidget(self.wireguard_profile_selector) + layout.addWidget(profile_group) + + rotation_group = QGroupBox("Key Rotation Settings") + rotation_group.setStyleSheet("QGroupBox { color: white; padding: 15px; }") + rotation_layout = QVBoxLayout(rotation_group) + + self.enable_rotation = QCheckBox("Enable WireGuard key rotation") + self.enable_rotation.setStyleSheet(self.get_checkbox_style()) + rotation_layout.addWidget(self.enable_rotation) + + self.rotation_combo = QComboBox() + self.rotation_combo.addItems(["1 day", "3 days", "1 week", "2 weeks"]) + self.rotation_combo.setStyleSheet(self.get_combobox_style()) + self.rotation_combo.setEnabled(False) + rotation_layout.addWidget(self.rotation_combo) + + layout.addWidget(rotation_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_wireguard_settings) + + button_layout = QHBoxLayout() + button_layout.addStretch() + button_layout.addWidget(save_button) + layout.addLayout(button_layout) + + self.wireguard_profile_selector.currentIndexChanged.connect(self.load_profile_wireguard_settings) + self.enable_rotation.stateChanged.connect(lambda state: self.rotation_combo.setEnabled(state == Qt.CheckState.Checked.value)) + + layout.addStretch() + self.load_profile_wireguard_settings() + return 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 load_profile_wireguard_settings(self) -> None: + profile_id = self.wireguard_profile_selector.currentData() + if profile_id is None: + return + + profile = ProfileController.get(profile_id) + + rotation_settings = profile.connection.wireguard_rotation_frequency + if rotation_settings: + self.enable_rotation.setChecked(True) + self.rotation_combo.setEnabled(True) + frequency_hours = rotation_settings + duration_text = self.convert_duration(frequency_hours, to_hours=False) + self.rotation_combo.setCurrentText(duration_text) + else: + self.enable_rotation.setChecked(False) + self.rotation_combo.setEnabled(False) + + 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 save_wireguard_settings(self) -> None: + profile_id = self.wireguard_profile_selector.currentData() + if profile_id is None: + return + profile = ProfileController.get(profile_id) + if not self.enable_rotation.isChecked(): + profile.connection.wireguard_rotation_frequency = None + profile.connection.wireguard_rotation_last_performed = None + ProfileController.update(profile) + self.update_status.update_status("WireGuard rotation disabled for this profile") + return + + rotation_duration = self.rotation_combo.currentText() + try: + hours = self.convert_duration(rotation_duration, to_hours=True) + profile.connection.wireguard_rotation_frequency = hours + profile.connection.wireguard_rotation_last_performed = datetime.now(timezone.utc) + ProfileController.update(profile) + self.update_status.update_status("WireGuard settings saved successfully") + except ValueError: + self.update_status.update_status("An error occurred while saving the WireGuard settings") + + def show_account_page(self): + self.content_layout.setCurrentWidget(self.account_page) + self.update_button_states(0) + + def show_wireguard_page(self): + self.content_layout.setCurrentWidget(self.wireguard_page) + self.update_button_states(1) + + def show_subscription_page(self): + self.content_layout.setCurrentWidget(self.subscription_page) + self.update_button_states(2) + + def show_delete_page(self): + self.content_layout.setCurrentWidget(self.delete_page) + self.update_button_states(3) + + def show_logs_page(self): + self.content_layout.setCurrentWidget(self.logs_page) + self.update_button_states(4) + + 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.wireguard_page) + self.wireguard_page = self.create_wireguard_page() + self.content_layout.addWidget(self.wireguard_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.wireguard_page = self.create_wireguard_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_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}") + 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()) diff --git a/gui/resources/images/1-country.png b/gui/resources/images/1-country.png new file mode 100644 index 0000000..410d741 Binary files /dev/null and b/gui/resources/images/1-country.png differ diff --git a/gui/resources/images/1024x1280_button.png b/gui/resources/images/1024x1280_button.png new file mode 100644 index 0000000..6f3c83d Binary files /dev/null and b/gui/resources/images/1024x1280_button.png differ diff --git a/gui/resources/images/1024x760.png b/gui/resources/images/1024x760.png new file mode 100644 index 0000000..c095bf7 Binary files /dev/null and b/gui/resources/images/1024x760.png differ diff --git a/gui/resources/images/1024x760_button.png b/gui/resources/images/1024x760_button.png new file mode 100644 index 0000000..f89565e Binary files /dev/null and b/gui/resources/images/1024x760_button.png differ diff --git a/gui/resources/images/1152x1080.png b/gui/resources/images/1152x1080.png new file mode 100644 index 0000000..cb7ef60 Binary files /dev/null and b/gui/resources/images/1152x1080.png differ diff --git a/gui/resources/images/1152x1080_button.png b/gui/resources/images/1152x1080_button.png new file mode 100644 index 0000000..d68281c Binary files /dev/null and b/gui/resources/images/1152x1080_button.png differ diff --git a/gui/resources/images/1280x1024.png b/gui/resources/images/1280x1024.png new file mode 100644 index 0000000..d7ea942 Binary files /dev/null and b/gui/resources/images/1280x1024.png differ diff --git a/gui/resources/images/1280x1024_button.png b/gui/resources/images/1280x1024_button.png new file mode 100644 index 0000000..7e12ff3 Binary files /dev/null and b/gui/resources/images/1280x1024_button.png differ diff --git a/gui/resources/images/1920x1080.png b/gui/resources/images/1920x1080.png new file mode 100644 index 0000000..5ee739b Binary files /dev/null and b/gui/resources/images/1920x1080.png differ diff --git a/gui/resources/images/1920x1080_button.png b/gui/resources/images/1920x1080_button.png new file mode 100644 index 0000000..67aae43 Binary files /dev/null and b/gui/resources/images/1920x1080_button.png differ diff --git a/gui/resources/images/400x50_button.png b/gui/resources/images/400x50_button.png new file mode 100644 index 0000000..39af119 Binary files /dev/null and b/gui/resources/images/400x50_button.png differ diff --git a/gui/resources/images/540x455.png b/gui/resources/images/540x455.png new file mode 100644 index 0000000..9cb0ce4 Binary files /dev/null and b/gui/resources/images/540x455.png differ diff --git a/gui/resources/images/800x600.png b/gui/resources/images/800x600.png new file mode 100644 index 0000000..1302151 Binary files /dev/null and b/gui/resources/images/800x600.png differ diff --git a/gui/resources/images/800x600_button.png b/gui/resources/images/800x600_button.png new file mode 100644 index 0000000..07b3169 Binary files /dev/null and b/gui/resources/images/800x600_button.png differ diff --git a/gui/resources/images/Dark.png b/gui/resources/images/Dark.png new file mode 100644 index 0000000..ad6233d Binary files /dev/null and b/gui/resources/images/Dark.png differ diff --git a/gui/resources/images/Dark2.png b/gui/resources/images/Dark2.png new file mode 100644 index 0000000..59e3361 Binary files /dev/null and b/gui/resources/images/Dark2.png differ diff --git a/gui/resources/images/Default.png b/gui/resources/images/Default.png new file mode 100644 index 0000000..7437888 Binary files /dev/null and b/gui/resources/images/Default.png differ diff --git a/gui/resources/images/Gif earth.mp4 b/gui/resources/images/Gif earth.mp4 new file mode 100644 index 0000000..e134d88 Binary files /dev/null and b/gui/resources/images/Gif earth.mp4 differ diff --git a/gui/resources/images/Iceland.png b/gui/resources/images/Iceland.png new file mode 100644 index 0000000..d638f3b Binary files /dev/null and b/gui/resources/images/Iceland.png differ diff --git a/gui/resources/images/Iceland_button.png b/gui/resources/images/Iceland_button.png new file mode 100644 index 0000000..9dd1cb7 Binary files /dev/null and b/gui/resources/images/Iceland_button.png differ diff --git a/gui/resources/images/Iceland_hdtor.png b/gui/resources/images/Iceland_hdtor.png new file mode 100644 index 0000000..0e34315 Binary files /dev/null and b/gui/resources/images/Iceland_hdtor.png differ diff --git a/gui/resources/images/Iceland_mini.png b/gui/resources/images/Iceland_mini.png new file mode 100644 index 0000000..bb020fe Binary files /dev/null and b/gui/resources/images/Iceland_mini.png differ diff --git a/gui/resources/images/Malaysia.png b/gui/resources/images/Malaysia.png new file mode 100644 index 0000000..89a11b9 Binary files /dev/null and b/gui/resources/images/Malaysia.png differ diff --git a/gui/resources/images/Malaysia_button.png b/gui/resources/images/Malaysia_button.png new file mode 100644 index 0000000..9601b2f Binary files /dev/null and b/gui/resources/images/Malaysia_button.png differ diff --git a/gui/resources/images/Malaysia_hdtor.png b/gui/resources/images/Malaysia_hdtor.png new file mode 100644 index 0000000..875d06a Binary files /dev/null and b/gui/resources/images/Malaysia_hdtor.png differ diff --git a/gui/resources/images/Malaysia_mini.png b/gui/resources/images/Malaysia_mini.png new file mode 100644 index 0000000..26f78a7 Binary files /dev/null and b/gui/resources/images/Malaysia_mini.png differ diff --git a/gui/resources/images/Mesa de trabajo 1.png b/gui/resources/images/Mesa de trabajo 1.png new file mode 100644 index 0000000..3005f19 Binary files /dev/null and b/gui/resources/images/Mesa de trabajo 1.png differ diff --git a/gui/resources/images/Mesa de trabajo 10.png b/gui/resources/images/Mesa de trabajo 10.png new file mode 100644 index 0000000..3c9be40 Binary files /dev/null and b/gui/resources/images/Mesa de trabajo 10.png differ diff --git a/gui/resources/images/Mesa de trabajo 2.png b/gui/resources/images/Mesa de trabajo 2.png new file mode 100644 index 0000000..1350826 Binary files /dev/null and b/gui/resources/images/Mesa de trabajo 2.png differ diff --git a/gui/resources/images/Mesa de trabajo 3.png b/gui/resources/images/Mesa de trabajo 3.png new file mode 100644 index 0000000..74dbb0e Binary files /dev/null and b/gui/resources/images/Mesa de trabajo 3.png differ diff --git a/gui/resources/images/Mesa de trabajo 4.png b/gui/resources/images/Mesa de trabajo 4.png new file mode 100644 index 0000000..58a9ed6 Binary files /dev/null and b/gui/resources/images/Mesa de trabajo 4.png differ diff --git a/gui/resources/images/Mesa de trabajo 8.png b/gui/resources/images/Mesa de trabajo 8.png new file mode 100644 index 0000000..48c1a6a Binary files /dev/null and b/gui/resources/images/Mesa de trabajo 8.png differ diff --git a/gui/resources/images/Moldova.png b/gui/resources/images/Moldova.png new file mode 100644 index 0000000..e103356 Binary files /dev/null and b/gui/resources/images/Moldova.png differ diff --git a/gui/resources/images/Moldova_button.png b/gui/resources/images/Moldova_button.png new file mode 100644 index 0000000..0a290b3 Binary files /dev/null and b/gui/resources/images/Moldova_button.png differ diff --git a/gui/resources/images/Moldova_hdtor.png b/gui/resources/images/Moldova_hdtor.png new file mode 100644 index 0000000..f98be04 Binary files /dev/null and b/gui/resources/images/Moldova_hdtor.png differ diff --git a/gui/resources/images/Moldova_hdtor_mini.png b/gui/resources/images/Moldova_hdtor_mini.png new file mode 100644 index 0000000..6ea1f4a Binary files /dev/null and b/gui/resources/images/Moldova_hdtor_mini.png differ diff --git a/gui/resources/images/Moldova_mini.png b/gui/resources/images/Moldova_mini.png new file mode 100644 index 0000000..f32a01c Binary files /dev/null and b/gui/resources/images/Moldova_mini.png differ diff --git a/gui/resources/images/The Netherlands.png b/gui/resources/images/The Netherlands.png new file mode 100644 index 0000000..79392a1 Binary files /dev/null and b/gui/resources/images/The Netherlands.png differ diff --git a/gui/resources/images/The Netherlands_button.png b/gui/resources/images/The Netherlands_button.png new file mode 100644 index 0000000..c35baea Binary files /dev/null and b/gui/resources/images/The Netherlands_button.png differ diff --git a/gui/resources/images/The Netherlands_hdtor.png b/gui/resources/images/The Netherlands_hdtor.png new file mode 100644 index 0000000..e00814f Binary files /dev/null and b/gui/resources/images/The Netherlands_hdtor.png differ diff --git a/gui/resources/images/The Netherlands_hdtor_mini.png b/gui/resources/images/The Netherlands_hdtor_mini.png new file mode 100644 index 0000000..e6f32af Binary files /dev/null and b/gui/resources/images/The Netherlands_hdtor_mini.png differ diff --git a/gui/resources/images/The Netherlands_mini.png b/gui/resources/images/The Netherlands_mini.png new file mode 100644 index 0000000..6639452 Binary files /dev/null and b/gui/resources/images/The Netherlands_mini.png differ diff --git a/gui/resources/images/UP_button.png b/gui/resources/images/UP_button.png new file mode 100644 index 0000000..5980912 Binary files /dev/null and b/gui/resources/images/UP_button.png differ diff --git a/gui/resources/images/US (Ohio).png b/gui/resources/images/US (Ohio).png new file mode 100644 index 0000000..5ee1489 Binary files /dev/null and b/gui/resources/images/US (Ohio).png differ diff --git a/gui/resources/images/US (Ohio)_button.png b/gui/resources/images/US (Ohio)_button.png new file mode 100644 index 0000000..1a6bca8 Binary files /dev/null and b/gui/resources/images/US (Ohio)_button.png differ diff --git a/gui/resources/images/US (Ohio)_hdtor.png b/gui/resources/images/US (Ohio)_hdtor.png new file mode 100644 index 0000000..8e36ef1 Binary files /dev/null and b/gui/resources/images/US (Ohio)_hdtor.png differ diff --git a/gui/resources/images/US (Ohio)_hdtor_mini.png b/gui/resources/images/US (Ohio)_hdtor_mini.png new file mode 100644 index 0000000..0975c17 Binary files /dev/null and b/gui/resources/images/US (Ohio)_hdtor_mini.png differ diff --git a/gui/resources/images/US (Ohio)_mini.png b/gui/resources/images/US (Ohio)_mini.png new file mode 100644 index 0000000..69d6a59 Binary files /dev/null and b/gui/resources/images/US (Ohio)_mini.png differ diff --git a/gui/resources/images/US (Seattle).png b/gui/resources/images/US (Seattle).png new file mode 100644 index 0000000..1852035 Binary files /dev/null and b/gui/resources/images/US (Seattle).png differ diff --git a/gui/resources/images/US (Seattle)_button.png b/gui/resources/images/US (Seattle)_button.png new file mode 100644 index 0000000..125b0c5 Binary files /dev/null and b/gui/resources/images/US (Seattle)_button.png differ diff --git a/gui/resources/images/US (Seattle)_hdtor.png b/gui/resources/images/US (Seattle)_hdtor.png new file mode 100644 index 0000000..df2690f Binary files /dev/null and b/gui/resources/images/US (Seattle)_hdtor.png differ diff --git a/gui/resources/images/US (Seattle)_mini.png b/gui/resources/images/US (Seattle)_mini.png new file mode 100644 index 0000000..2fa38d8 Binary files /dev/null and b/gui/resources/images/US (Seattle)_mini.png differ diff --git a/gui/resources/images/app_off.png b/gui/resources/images/app_off.png new file mode 100644 index 0000000..be807d8 Binary files /dev/null and b/gui/resources/images/app_off.png differ diff --git a/gui/resources/images/app_on.png b/gui/resources/images/app_on.png new file mode 100644 index 0000000..59b3122 Binary files /dev/null and b/gui/resources/images/app_on.png differ diff --git a/gui/resources/images/apply.png b/gui/resources/images/apply.png new file mode 100644 index 0000000..92a8e74 Binary files /dev/null and b/gui/resources/images/apply.png differ diff --git a/gui/resources/images/arch.png b/gui/resources/images/arch.png new file mode 100644 index 0000000..b0738ea Binary files /dev/null and b/gui/resources/images/arch.png differ diff --git a/gui/resources/images/arrow.png b/gui/resources/images/arrow.png new file mode 100644 index 0000000..4327cbb Binary files /dev/null and b/gui/resources/images/arrow.png differ diff --git a/gui/resources/images/available-updates.png b/gui/resources/images/available-updates.png new file mode 100644 index 0000000..493889e Binary files /dev/null and b/gui/resources/images/available-updates.png differ diff --git a/gui/resources/images/back.png b/gui/resources/images/back.png new file mode 100644 index 0000000..92bfc2d Binary files /dev/null and b/gui/resources/images/back.png differ diff --git a/gui/resources/images/back_transparente.png b/gui/resources/images/back_transparente.png new file mode 100644 index 0000000..3164e00 Binary files /dev/null and b/gui/resources/images/back_transparente.png differ diff --git a/gui/resources/images/backgoud800x600.png b/gui/resources/images/backgoud800x600.png new file mode 100644 index 0000000..d6071ce Binary files /dev/null and b/gui/resources/images/backgoud800x600.png differ diff --git a/gui/resources/images/background.png b/gui/resources/images/background.png new file mode 100644 index 0000000..d5c1182 Binary files /dev/null and b/gui/resources/images/background.png differ diff --git a/gui/resources/images/background_connected.png b/gui/resources/images/background_connected.png new file mode 100644 index 0000000..5375fb9 Binary files /dev/null and b/gui/resources/images/background_connected.png differ diff --git a/gui/resources/images/background_tormode.png b/gui/resources/images/background_tormode.png new file mode 100644 index 0000000..9b67901 Binary files /dev/null and b/gui/resources/images/background_tormode.png differ diff --git a/gui/resources/images/billing_off.png b/gui/resources/images/billing_off.png new file mode 100644 index 0000000..748d902 Binary files /dev/null and b/gui/resources/images/billing_off.png differ diff --git a/gui/resources/images/billing_on.png b/gui/resources/images/billing_on.png new file mode 100644 index 0000000..6beef34 Binary files /dev/null and b/gui/resources/images/billing_on.png differ diff --git a/gui/resources/images/bitcoin.png b/gui/resources/images/bitcoin.png new file mode 100644 index 0000000..eefe5d9 Binary files /dev/null and b/gui/resources/images/bitcoin.png differ diff --git a/gui/resources/images/bitcoin_1year.png b/gui/resources/images/bitcoin_1year.png new file mode 100644 index 0000000..17935c4 Binary files /dev/null and b/gui/resources/images/bitcoin_1year.png differ diff --git a/gui/resources/images/bov_off.png b/gui/resources/images/bov_off.png new file mode 100644 index 0000000..d6b6bb8 Binary files /dev/null and b/gui/resources/images/bov_off.png differ diff --git a/gui/resources/images/bov_on.png b/gui/resources/images/bov_on.png new file mode 100644 index 0000000..d52db01 Binary files /dev/null and b/gui/resources/images/bov_on.png differ diff --git a/gui/resources/images/brasil_button.png b/gui/resources/images/brasil_button.png new file mode 100644 index 0000000..2894fbf Binary files /dev/null and b/gui/resources/images/brasil_button.png differ diff --git a/gui/resources/images/brave latest_mini.png b/gui/resources/images/brave latest_mini.png new file mode 100644 index 0000000..5dee25a Binary files /dev/null and b/gui/resources/images/brave latest_mini.png differ diff --git a/gui/resources/images/brave-latest_mini.xbm b/gui/resources/images/brave-latest_mini.xbm new file mode 100644 index 0000000..a552b1e --- /dev/null +++ b/gui/resources/images/brave-latest_mini.xbm @@ -0,0 +1,33 @@ +#define 6a2e3c05bd3d4859d314a826ef31f5dcuqWRmCFzQ8jhwLsO_width 50 +#define 6a2e3c05bd3d4859d314a826ef31f5dcuqWRmCFzQ8jhwLsO_height 50 +static char 6a2e3c05bd3d4859d314a826ef31f5dcuqWRmCFzQ8jhwLsO_bits[] = { + 0x00, 0x00, 0xFF, 0xFF, 0x03, 0x00, 0x00, 0x00, 0x80, 0xFF, 0xFF, 0x07, + 0x00, 0x00, 0x00, 0xC0, 0xFF, 0xFF, 0x0F, 0x00, 0x00, 0x00, 0xE0, 0xFF, + 0xFF, 0x1F, 0x00, 0x00, 0x80, 0xF7, 0xFF, 0xFF, 0xBF, 0x07, 0x00, 0xC0, + 0xFF, 0xFF, 0xFF, 0xFF, 0x0F, 0x00, 0xE0, 0xFF, 0xFF, 0xFF, 0xFF, 0x3F, + 0x00, 0xF8, 0xFF, 0x7F, 0xF8, 0xFF, 0x7F, 0x00, 0xFC, 0x0F, 0x0C, 0xC0, + 0xC0, 0xFF, 0x00, 0xFC, 0x0F, 0x00, 0x00, 0x80, 0xFF, 0x00, 0xFC, 0x07, + 0x00, 0x00, 0x00, 0x7F, 0x00, 0xF8, 0x03, 0x00, 0x00, 0x00, 0x7E, 0x00, + 0xF8, 0x01, 0x00, 0x00, 0x00, 0x7E, 0x00, 0xF8, 0xC0, 0x0F, 0x80, 0x0F, + 0x7C, 0x00, 0xFC, 0x00, 0x3E, 0xF0, 0x01, 0xF8, 0x00, 0x7C, 0x00, 0x30, + 0x70, 0x00, 0xF8, 0x00, 0x7C, 0x00, 0x20, 0x10, 0x00, 0xF8, 0x00, 0x7C, + 0x00, 0x20, 0x00, 0x00, 0xF8, 0x00, 0xFC, 0x00, 0x20, 0x20, 0x00, 0xF8, + 0x00, 0xFC, 0x01, 0x30, 0x20, 0x00, 0xFE, 0x00, 0xF8, 0x03, 0x18, 0x60, + 0x00, 0x7F, 0x00, 0xF8, 0x07, 0x18, 0x60, 0x80, 0x7F, 0x00, 0xF8, 0x0F, + 0x18, 0xE0, 0xC0, 0x7F, 0x00, 0xF0, 0x1F, 0xF0, 0x38, 0xE0, 0x3F, 0x00, + 0xF0, 0x3F, 0xC0, 0x0F, 0xE0, 0x3F, 0x00, 0xF0, 0x3F, 0x80, 0x07, 0xE0, + 0x3F, 0x00, 0xE0, 0x1F, 0x00, 0x03, 0xE0, 0x3F, 0x00, 0xE0, 0x1F, 0x00, + 0x03, 0xC0, 0x1F, 0x00, 0xE0, 0x1F, 0x80, 0x07, 0xC0, 0x1F, 0x00, 0xE0, + 0x1F, 0xC0, 0x0F, 0xE0, 0x1F, 0x00, 0xC0, 0x3F, 0xE0, 0x19, 0xF0, 0x0F, + 0x00, 0xC0, 0x7F, 0x3C, 0xE0, 0xF8, 0x0F, 0x00, 0xC0, 0xFF, 0x0F, 0x80, + 0xFF, 0x0F, 0x00, 0x80, 0xFF, 0x03, 0x00, 0xFE, 0x0F, 0x00, 0x80, 0xFF, + 0x03, 0x00, 0xFE, 0x07, 0x00, 0x80, 0xFF, 0x07, 0x80, 0xFF, 0x07, 0x00, + 0x80, 0xFF, 0x1F, 0xC0, 0xFF, 0x07, 0x00, 0x00, 0xFF, 0x3F, 0xF0, 0xFF, + 0x03, 0x00, 0x00, 0xFF, 0xFF, 0xF8, 0xFF, 0x03, 0x00, 0x00, 0xFE, 0xFF, + 0xFD, 0xFF, 0x01, 0x00, 0x00, 0xFE, 0xFF, 0xFF, 0xFF, 0x01, 0x00, 0x00, + 0xFC, 0xFF, 0xFF, 0xFF, 0x00, 0x00, 0x00, 0xF0, 0xFF, 0xFF, 0x3F, 0x00, + 0x00, 0x00, 0xE0, 0xFF, 0xFF, 0x1F, 0x00, 0x00, 0x00, 0x80, 0xFF, 0xFF, + 0x07, 0x00, 0x00, 0x00, 0x00, 0xFE, 0xFF, 0x01, 0x00, 0x00, 0x00, 0x00, + 0xFC, 0xFF, 0x00, 0x00, 0x00, 0x00, 0x00, 0xF0, 0x3F, 0x00, 0x00, 0x00, + 0x00, 0x00, 0xC0, 0x1F, 0x00, 0x00, 0x00, 0x00, 0x00, 0x80, 0x07, 0x00, + 0x00, 0x00, }; diff --git a/gui/resources/images/brave_button.png b/gui/resources/images/brave_button.png new file mode 100644 index 0000000..da06431 Binary files /dev/null and b/gui/resources/images/brave_button.png differ diff --git a/gui/resources/images/brave_icon.png b/gui/resources/images/brave_icon.png new file mode 100644 index 0000000..6ad537f Binary files /dev/null and b/gui/resources/images/brave_icon.png differ diff --git a/gui/resources/images/brazil garaje.png b/gui/resources/images/brazil garaje.png new file mode 100644 index 0000000..98011fb Binary files /dev/null and b/gui/resources/images/brazil garaje.png differ diff --git a/gui/resources/images/brazil_button.png b/gui/resources/images/brazil_button.png new file mode 100644 index 0000000..c89decd Binary files /dev/null and b/gui/resources/images/brazil_button.png differ diff --git a/gui/resources/images/browser only.png b/gui/resources/images/browser only.png new file mode 100644 index 0000000..4ea476d Binary files /dev/null and b/gui/resources/images/browser only.png differ diff --git a/gui/resources/images/browser-only.png b/gui/resources/images/browser-only.png new file mode 100644 index 0000000..08602b0 Binary files /dev/null and b/gui/resources/images/browser-only.png differ diff --git a/gui/resources/images/browser-only_button.png b/gui/resources/images/browser-only_button.png new file mode 100644 index 0000000..08602b0 Binary files /dev/null and b/gui/resources/images/browser-only_button.png differ diff --git a/gui/resources/images/browser_just_proxy.png b/gui/resources/images/browser_just_proxy.png new file mode 100644 index 0000000..0f8044d Binary files /dev/null and b/gui/resources/images/browser_just_proxy.png differ diff --git a/gui/resources/images/browser_tor.png b/gui/resources/images/browser_tor.png new file mode 100644 index 0000000..f8c9f82 Binary files /dev/null and b/gui/resources/images/browser_tor.png differ diff --git a/gui/resources/images/browsers_mini.png b/gui/resources/images/browsers_mini.png new file mode 100644 index 0000000..7306f2d Binary files /dev/null and b/gui/resources/images/browsers_mini.png differ diff --git a/gui/resources/images/button230x220.png b/gui/resources/images/button230x220.png new file mode 100644 index 0000000..bae5410 Binary files /dev/null and b/gui/resources/images/button230x220.png differ diff --git a/gui/resources/images/button_profile.png b/gui/resources/images/button_profile.png new file mode 100644 index 0000000..8338f26 Binary files /dev/null and b/gui/resources/images/button_profile.png differ diff --git a/gui/resources/images/button_session_profile.png b/gui/resources/images/button_session_profile.png new file mode 100644 index 0000000..b6edb5a Binary files /dev/null and b/gui/resources/images/button_session_profile.png differ diff --git a/gui/resources/images/button_session_tor.png b/gui/resources/images/button_session_tor.png new file mode 100644 index 0000000..969de56 Binary files /dev/null and b/gui/resources/images/button_session_tor.png differ diff --git a/gui/resources/images/button_system_profile.png b/gui/resources/images/button_system_profile.png new file mode 100644 index 0000000..dcc6da1 Binary files /dev/null and b/gui/resources/images/button_system_profile.png differ diff --git a/gui/resources/images/check.png b/gui/resources/images/check.png new file mode 100644 index 0000000..28755cd Binary files /dev/null and b/gui/resources/images/check.png differ diff --git a/gui/resources/images/check_icon.png b/gui/resources/images/check_icon.png new file mode 100644 index 0000000..c4050f6 Binary files /dev/null and b/gui/resources/images/check_icon.png differ diff --git a/gui/resources/images/chromium latest_mini.png b/gui/resources/images/chromium latest_mini.png new file mode 100644 index 0000000..66a009b Binary files /dev/null and b/gui/resources/images/chromium latest_mini.png differ diff --git a/gui/resources/images/chromium_button.png b/gui/resources/images/chromium_button.png new file mode 100644 index 0000000..0d876d5 Binary files /dev/null and b/gui/resources/images/chromium_button.png differ diff --git a/gui/resources/images/chromium_icon.png b/gui/resources/images/chromium_icon.png new file mode 100644 index 0000000..0b09057 Binary files /dev/null and b/gui/resources/images/chromium_icon.png differ diff --git a/gui/resources/images/config.txt b/gui/resources/images/config.txt new file mode 100644 index 0000000..e69de29 diff --git a/gui/resources/images/connect.png b/gui/resources/images/connect.png new file mode 100644 index 0000000..36e1263 Binary files /dev/null and b/gui/resources/images/connect.png differ diff --git a/gui/resources/images/create_profile.png b/gui/resources/images/create_profile.png new file mode 100644 index 0000000..ace377c Binary files /dev/null and b/gui/resources/images/create_profile.png differ diff --git a/gui/resources/images/cuadro150x50.png b/gui/resources/images/cuadro150x50.png new file mode 100644 index 0000000..cd721a1 Binary files /dev/null and b/gui/resources/images/cuadro150x50.png differ diff --git a/gui/resources/images/cuadro400x50.png b/gui/resources/images/cuadro400x50.png new file mode 100644 index 0000000..39af119 Binary files /dev/null and b/gui/resources/images/cuadro400x50.png differ diff --git a/gui/resources/images/debian.png b/gui/resources/images/debian.png new file mode 100644 index 0000000..ffd0ea4 Binary files /dev/null and b/gui/resources/images/debian.png differ diff --git a/gui/resources/images/delete_profile.png b/gui/resources/images/delete_profile.png new file mode 100644 index 0000000..8500648 Binary files /dev/null and b/gui/resources/images/delete_profile.png differ diff --git a/gui/resources/images/disconnect.png b/gui/resources/images/disconnect.png new file mode 100644 index 0000000..670d00a Binary files /dev/null and b/gui/resources/images/disconnect.png differ diff --git a/gui/resources/images/disconnect_system_wide.png b/gui/resources/images/disconnect_system_wide.png new file mode 100644 index 0000000..69ffc16 Binary files /dev/null and b/gui/resources/images/disconnect_system_wide.png differ diff --git a/gui/resources/images/disconnected.png b/gui/resources/images/disconnected.png new file mode 100644 index 0000000..8eb3732 Binary files /dev/null and b/gui/resources/images/disconnected.png differ diff --git a/gui/resources/images/earth.jpg b/gui/resources/images/earth.jpg new file mode 100644 index 0000000..ebed745 Binary files /dev/null and b/gui/resources/images/earth.jpg differ diff --git a/gui/resources/images/earth.mp4 b/gui/resources/images/earth.mp4 new file mode 100644 index 0000000..6387416 Binary files /dev/null and b/gui/resources/images/earth.mp4 differ diff --git a/gui/resources/images/earth.png b/gui/resources/images/earth.png new file mode 100644 index 0000000..df63a98 Binary files /dev/null and b/gui/resources/images/earth.png differ diff --git a/gui/resources/images/edit_profile.png b/gui/resources/images/edit_profile.png new file mode 100644 index 0000000..a4b4056 Binary files /dev/null and b/gui/resources/images/edit_profile.png differ diff --git a/gui/resources/images/editv.png b/gui/resources/images/editv.png new file mode 100644 index 0000000..e13b22b Binary files /dev/null and b/gui/resources/images/editv.png differ diff --git a/gui/resources/images/eeuu garaje.png b/gui/resources/images/eeuu garaje.png new file mode 100644 index 0000000..70a12d7 Binary files /dev/null and b/gui/resources/images/eeuu garaje.png differ diff --git a/gui/resources/images/eeuu_button.png b/gui/resources/images/eeuu_button.png new file mode 100644 index 0000000..213c45a Binary files /dev/null and b/gui/resources/images/eeuu_button.png differ diff --git a/gui/resources/images/eeuu_mini.png b/gui/resources/images/eeuu_mini.png new file mode 100644 index 0000000..1fa7a11 Binary files /dev/null and b/gui/resources/images/eeuu_mini.png differ diff --git a/gui/resources/images/fedora.png b/gui/resources/images/fedora.png new file mode 100644 index 0000000..5e79631 Binary files /dev/null and b/gui/resources/images/fedora.png differ diff --git a/gui/resources/images/finland.png b/gui/resources/images/finland.png new file mode 100644 index 0000000..9c95d78 Binary files /dev/null and b/gui/resources/images/finland.png differ diff --git a/gui/resources/images/finland_button.png b/gui/resources/images/finland_button.png new file mode 100644 index 0000000..674ee76 Binary files /dev/null and b/gui/resources/images/finland_button.png differ diff --git a/gui/resources/images/finland_mini.png b/gui/resources/images/finland_mini.png new file mode 100644 index 0000000..6ff3e39 Binary files /dev/null and b/gui/resources/images/finland_mini.png differ diff --git a/gui/resources/images/firefox latest_mini.png b/gui/resources/images/firefox latest_mini.png new file mode 100644 index 0000000..2dd5eef Binary files /dev/null and b/gui/resources/images/firefox latest_mini.png differ diff --git a/gui/resources/images/firefox_button.png b/gui/resources/images/firefox_button.png new file mode 100644 index 0000000..7867b25 Binary files /dev/null and b/gui/resources/images/firefox_button.png differ diff --git a/gui/resources/images/firefox_icon.png b/gui/resources/images/firefox_icon.png new file mode 100644 index 0000000..a4a49c8 Binary files /dev/null and b/gui/resources/images/firefox_icon.png differ diff --git a/gui/resources/images/germany garaje.png b/gui/resources/images/germany garaje.png new file mode 100644 index 0000000..ad0e147 Binary files /dev/null and b/gui/resources/images/germany garaje.png differ diff --git a/gui/resources/images/germany_button.png b/gui/resources/images/germany_button.png new file mode 100644 index 0000000..5368482 Binary files /dev/null and b/gui/resources/images/germany_button.png differ diff --git a/gui/resources/images/germany_mini.png b/gui/resources/images/germany_mini.png new file mode 100644 index 0000000..a55e260 Binary files /dev/null and b/gui/resources/images/germany_mini.png differ diff --git a/gui/resources/images/global-acces.png b/gui/resources/images/global-acces.png new file mode 100644 index 0000000..7d0a57c Binary files /dev/null and b/gui/resources/images/global-acces.png differ diff --git a/gui/resources/images/hidetor.png b/gui/resources/images/hidetor.png new file mode 100644 index 0000000..8d2f770 Binary files /dev/null and b/gui/resources/images/hidetor.png differ diff --git a/gui/resources/images/hidetor_button.png b/gui/resources/images/hidetor_button.png new file mode 100644 index 0000000..6bf6274 Binary files /dev/null and b/gui/resources/images/hidetor_button.png differ diff --git a/gui/resources/images/hong kong garaje.png b/gui/resources/images/hong kong garaje.png new file mode 100644 index 0000000..bbecc48 Binary files /dev/null and b/gui/resources/images/hong kong garaje.png differ diff --git a/gui/resources/images/hong kong_button.png b/gui/resources/images/hong kong_button.png new file mode 100644 index 0000000..f060abb Binary files /dev/null and b/gui/resources/images/hong kong_button.png differ diff --git a/gui/resources/images/hong kong_mini.png b/gui/resources/images/hong kong_mini.png new file mode 100644 index 0000000..5f3154f Binary files /dev/null and b/gui/resources/images/hong kong_mini.png differ diff --git a/gui/resources/images/icon-linux.png b/gui/resources/images/icon-linux.png new file mode 100644 index 0000000..5a92094 Binary files /dev/null and b/gui/resources/images/icon-linux.png differ diff --git a/gui/resources/images/install.png b/gui/resources/images/install.png new file mode 100644 index 0000000..f813a66 Binary files /dev/null and b/gui/resources/images/install.png differ diff --git a/gui/resources/images/just proxy_button.png b/gui/resources/images/just proxy_button.png new file mode 100644 index 0000000..31f99c3 Binary files /dev/null and b/gui/resources/images/just proxy_button.png differ diff --git a/gui/resources/images/just proxy_mini.png b/gui/resources/images/just proxy_mini.png new file mode 100644 index 0000000..ef3291a Binary files /dev/null and b/gui/resources/images/just proxy_mini.png differ diff --git a/gui/resources/images/just.png b/gui/resources/images/just.png new file mode 100644 index 0000000..53ead26 Binary files /dev/null and b/gui/resources/images/just.png differ diff --git a/gui/resources/images/just_session.png b/gui/resources/images/just_session.png new file mode 100644 index 0000000..85a957d Binary files /dev/null and b/gui/resources/images/just_session.png differ diff --git a/gui/resources/images/launch.png b/gui/resources/images/launch.png new file mode 100644 index 0000000..c62420a Binary files /dev/null and b/gui/resources/images/launch.png differ diff --git a/gui/resources/images/left.png b/gui/resources/images/left.png new file mode 100644 index 0000000..8d11718 Binary files /dev/null and b/gui/resources/images/left.png differ diff --git a/gui/resources/images/librewolf latest_mini.png b/gui/resources/images/librewolf latest_mini.png new file mode 100644 index 0000000..eb69a8a Binary files /dev/null and b/gui/resources/images/librewolf latest_mini.png differ diff --git a/gui/resources/images/librewolf_button.png b/gui/resources/images/librewolf_button.png new file mode 100644 index 0000000..bcb9d25 Binary files /dev/null and b/gui/resources/images/librewolf_button.png differ diff --git a/gui/resources/images/librewolf_icon.png b/gui/resources/images/librewolf_icon.png new file mode 100644 index 0000000..ff93c01 Binary files /dev/null and b/gui/resources/images/librewolf_icon.png differ diff --git a/gui/resources/images/lightnering.png b/gui/resources/images/lightnering.png new file mode 100644 index 0000000..fcd0021 Binary files /dev/null and b/gui/resources/images/lightnering.png differ diff --git a/gui/resources/images/linux.png b/gui/resources/images/linux.png new file mode 100644 index 0000000..c6d58b2 Binary files /dev/null and b/gui/resources/images/linux.png differ diff --git a/gui/resources/images/litecoin.png b/gui/resources/images/litecoin.png new file mode 100644 index 0000000..5772422 Binary files /dev/null and b/gui/resources/images/litecoin.png differ diff --git a/gui/resources/images/lnew york.png b/gui/resources/images/lnew york.png new file mode 100644 index 0000000..bca8d73 Binary files /dev/null and b/gui/resources/images/lnew york.png differ diff --git a/gui/resources/images/lokinet finland.png b/gui/resources/images/lokinet finland.png new file mode 100644 index 0000000..ed8472d Binary files /dev/null and b/gui/resources/images/lokinet finland.png differ diff --git a/gui/resources/images/lokinet los angeles.png b/gui/resources/images/lokinet los angeles.png new file mode 100644 index 0000000..44b5c16 Binary files /dev/null and b/gui/resources/images/lokinet los angeles.png differ diff --git a/gui/resources/images/lokinet malaysia.png b/gui/resources/images/lokinet malaysia.png new file mode 100644 index 0000000..06b139a Binary files /dev/null and b/gui/resources/images/lokinet malaysia.png differ diff --git a/gui/resources/images/lokinet new york.png b/gui/resources/images/lokinet new york.png new file mode 100644 index 0000000..e3e806d Binary files /dev/null and b/gui/resources/images/lokinet new york.png differ diff --git a/gui/resources/images/lokinet rumania.png b/gui/resources/images/lokinet rumania.png new file mode 100644 index 0000000..9a3a64e Binary files /dev/null and b/gui/resources/images/lokinet rumania.png differ diff --git a/gui/resources/images/lokinet switzerland.png b/gui/resources/images/lokinet switzerland.png new file mode 100644 index 0000000..13c7861 Binary files /dev/null and b/gui/resources/images/lokinet switzerland.png differ diff --git a/gui/resources/images/lokinet.png b/gui/resources/images/lokinet.png new file mode 100644 index 0000000..352cddf Binary files /dev/null and b/gui/resources/images/lokinet.png differ diff --git a/gui/resources/images/lokinet_button.png b/gui/resources/images/lokinet_button.png new file mode 100644 index 0000000..09baac4 Binary files /dev/null and b/gui/resources/images/lokinet_button.png differ diff --git a/gui/resources/images/lokinet_mini.png b/gui/resources/images/lokinet_mini.png new file mode 100644 index 0000000..64a9ede Binary files /dev/null and b/gui/resources/images/lokinet_mini.png differ diff --git a/gui/resources/images/los angeles.png b/gui/resources/images/los angeles.png new file mode 100644 index 0000000..4594a25 Binary files /dev/null and b/gui/resources/images/los angeles.png differ diff --git a/gui/resources/images/los angeles_button.png b/gui/resources/images/los angeles_button.png new file mode 100644 index 0000000..260c997 Binary files /dev/null and b/gui/resources/images/los angeles_button.png differ diff --git a/gui/resources/images/los angeles_mini.png b/gui/resources/images/los angeles_mini.png new file mode 100644 index 0000000..ba437ff Binary files /dev/null and b/gui/resources/images/los angeles_mini.png differ diff --git a/gui/resources/images/marco_miniatura.png b/gui/resources/images/marco_miniatura.png new file mode 100644 index 0000000..478cd9c Binary files /dev/null and b/gui/resources/images/marco_miniatura.png differ diff --git a/gui/resources/images/monero.png b/gui/resources/images/monero.png new file mode 100644 index 0000000..8fba8ea Binary files /dev/null and b/gui/resources/images/monero.png differ diff --git a/gui/resources/images/new york.png b/gui/resources/images/new york.png new file mode 100644 index 0000000..bca8d73 Binary files /dev/null and b/gui/resources/images/new york.png differ diff --git a/gui/resources/images/new york_button.png b/gui/resources/images/new york_button.png new file mode 100644 index 0000000..03062ad Binary files /dev/null and b/gui/resources/images/new york_button.png differ diff --git a/gui/resources/images/new york_mini.png b/gui/resources/images/new york_mini.png new file mode 100644 index 0000000..dc9dd66 Binary files /dev/null and b/gui/resources/images/new york_mini.png differ diff --git a/gui/resources/images/new_id.png b/gui/resources/images/new_id.png new file mode 100644 index 0000000..b728bb8 Binary files /dev/null and b/gui/resources/images/new_id.png differ diff --git a/gui/resources/images/new_york.png b/gui/resources/images/new_york.png new file mode 100644 index 0000000..cf5e9c9 Binary files /dev/null and b/gui/resources/images/new_york.png differ diff --git a/gui/resources/images/next.png b/gui/resources/images/next.png new file mode 100644 index 0000000..324141b Binary files /dev/null and b/gui/resources/images/next.png differ diff --git a/gui/resources/images/noe.png b/gui/resources/images/noe.png new file mode 100644 index 0000000..2a1a6d7 Binary files /dev/null and b/gui/resources/images/noe.png differ diff --git a/gui/resources/images/off.png b/gui/resources/images/off.png new file mode 100644 index 0000000..06b00c1 Binary files /dev/null and b/gui/resources/images/off.png differ diff --git a/gui/resources/images/ohio.png b/gui/resources/images/ohio.png new file mode 100644 index 0000000..8e36ef1 Binary files /dev/null and b/gui/resources/images/ohio.png differ diff --git a/gui/resources/images/on.png b/gui/resources/images/on.png new file mode 100644 index 0000000..b7e9eb3 Binary files /dev/null and b/gui/resources/images/on.png differ diff --git a/gui/resources/images/open finland.png b/gui/resources/images/open finland.png new file mode 100644 index 0000000..8712591 Binary files /dev/null and b/gui/resources/images/open finland.png differ diff --git a/gui/resources/images/open los angeles.png b/gui/resources/images/open los angeles.png new file mode 100644 index 0000000..cf01548 Binary files /dev/null and b/gui/resources/images/open los angeles.png differ diff --git a/gui/resources/images/open malaysia.png b/gui/resources/images/open malaysia.png new file mode 100644 index 0000000..68442c1 Binary files /dev/null and b/gui/resources/images/open malaysia.png differ diff --git a/gui/resources/images/open new york.png b/gui/resources/images/open new york.png new file mode 100644 index 0000000..bca3984 Binary files /dev/null and b/gui/resources/images/open new york.png differ diff --git a/gui/resources/images/open rumania.png b/gui/resources/images/open rumania.png new file mode 100644 index 0000000..c802142 Binary files /dev/null and b/gui/resources/images/open rumania.png differ diff --git a/gui/resources/images/open switzerland.png b/gui/resources/images/open switzerland.png new file mode 100644 index 0000000..5d60585 Binary files /dev/null and b/gui/resources/images/open switzerland.png differ diff --git a/gui/resources/images/open.png b/gui/resources/images/open.png new file mode 100644 index 0000000..dbdf5ff Binary files /dev/null and b/gui/resources/images/open.png differ diff --git a/gui/resources/images/open_button.png b/gui/resources/images/open_button.png new file mode 100644 index 0000000..f82394a Binary files /dev/null and b/gui/resources/images/open_button.png differ diff --git a/gui/resources/images/open_mini.png b/gui/resources/images/open_mini.png new file mode 100644 index 0000000..e86da88 Binary files /dev/null and b/gui/resources/images/open_mini.png differ diff --git a/gui/resources/images/original.png b/gui/resources/images/original.png new file mode 100644 index 0000000..162b3a8 Binary files /dev/null and b/gui/resources/images/original.png differ diff --git a/gui/resources/images/paste_button.png b/gui/resources/images/paste_button.png new file mode 100644 index 0000000..02b93f3 Binary files /dev/null and b/gui/resources/images/paste_button.png differ diff --git a/gui/resources/images/port.png b/gui/resources/images/port.png new file mode 100644 index 0000000..c5c2282 Binary files /dev/null and b/gui/resources/images/port.png differ diff --git a/gui/resources/images/port2.png b/gui/resources/images/port2.png new file mode 100644 index 0000000..4ff83f2 Binary files /dev/null and b/gui/resources/images/port2.png differ diff --git a/gui/resources/images/r.png b/gui/resources/images/r.png new file mode 100644 index 0000000..a0d2c16 Binary files /dev/null and b/gui/resources/images/r.png differ diff --git a/gui/resources/images/r1.png b/gui/resources/images/r1.png new file mode 100644 index 0000000..b8cc5b9 Binary files /dev/null and b/gui/resources/images/r1.png differ diff --git a/gui/resources/images/r2.png b/gui/resources/images/r2.png new file mode 100644 index 0000000..70951eb Binary files /dev/null and b/gui/resources/images/r2.png differ diff --git a/gui/resources/images/r4.png b/gui/resources/images/r4.png new file mode 100644 index 0000000..ef7f6f5 Binary files /dev/null and b/gui/resources/images/r4.png differ diff --git a/gui/resources/images/rb_off.png b/gui/resources/images/rb_off.png new file mode 100644 index 0000000..2642d04 Binary files /dev/null and b/gui/resources/images/rb_off.png differ diff --git a/gui/resources/images/rb_on.png b/gui/resources/images/rb_on.png new file mode 100644 index 0000000..27b22b5 Binary files /dev/null and b/gui/resources/images/rb_on.png differ diff --git a/gui/resources/images/residential tor_mini.png b/gui/resources/images/residential tor_mini.png new file mode 100644 index 0000000..2cd719c Binary files /dev/null and b/gui/resources/images/residential tor_mini.png differ diff --git a/gui/resources/images/residential.png b/gui/resources/images/residential.png new file mode 100644 index 0000000..dcaa957 Binary files /dev/null and b/gui/resources/images/residential.png differ diff --git a/gui/resources/images/residential_button.png b/gui/resources/images/residential_button.png new file mode 100644 index 0000000..71aa544 Binary files /dev/null and b/gui/resources/images/residential_button.png differ diff --git a/gui/resources/images/residential_mini.png b/gui/resources/images/residential_mini.png new file mode 100644 index 0000000..ef63696 Binary files /dev/null and b/gui/resources/images/residential_mini.png differ diff --git a/gui/resources/images/right.png b/gui/resources/images/right.png new file mode 100644 index 0000000..f86654a Binary files /dev/null and b/gui/resources/images/right.png differ diff --git a/gui/resources/images/rp.png b/gui/resources/images/rp.png new file mode 100644 index 0000000..77a1f38 Binary files /dev/null and b/gui/resources/images/rp.png differ diff --git a/gui/resources/images/rp_off.png b/gui/resources/images/rp_off.png new file mode 100644 index 0000000..3eeb9e5 Binary files /dev/null and b/gui/resources/images/rp_off.png differ diff --git a/gui/resources/images/rp_on.png b/gui/resources/images/rp_on.png new file mode 100644 index 0000000..218890b Binary files /dev/null and b/gui/resources/images/rp_on.png differ diff --git a/gui/resources/images/rr1.png b/gui/resources/images/rr1.png new file mode 100644 index 0000000..62da18a Binary files /dev/null and b/gui/resources/images/rr1.png differ diff --git a/gui/resources/images/rr2.png b/gui/resources/images/rr2.png new file mode 100644 index 0000000..5749341 Binary files /dev/null and b/gui/resources/images/rr2.png differ diff --git a/gui/resources/images/rr3.png b/gui/resources/images/rr3.png new file mode 100644 index 0000000..a3dcc26 Binary files /dev/null and b/gui/resources/images/rr3.png differ diff --git a/gui/resources/images/russia.png b/gui/resources/images/russia.png new file mode 100644 index 0000000..b96d7d7 Binary files /dev/null and b/gui/resources/images/russia.png differ diff --git a/gui/resources/images/save.png b/gui/resources/images/save.png new file mode 100644 index 0000000..5d0a562 Binary files /dev/null and b/gui/resources/images/save.png differ diff --git a/gui/resources/images/sett.png b/gui/resources/images/sett.png new file mode 100644 index 0000000..58c08cb Binary files /dev/null and b/gui/resources/images/sett.png differ diff --git a/gui/resources/images/settings.png b/gui/resources/images/settings.png new file mode 100644 index 0000000..dcd3776 Binary files /dev/null and b/gui/resources/images/settings.png differ diff --git a/gui/resources/images/settings_icon.png b/gui/resources/images/settings_icon.png new file mode 100644 index 0000000..4460507 Binary files /dev/null and b/gui/resources/images/settings_icon.png differ diff --git a/gui/resources/images/sweetzerland.png b/gui/resources/images/sweetzerland.png new file mode 100644 index 0000000..4dcac22 Binary files /dev/null and b/gui/resources/images/sweetzerland.png differ diff --git a/gui/resources/images/switzerland.png b/gui/resources/images/switzerland.png new file mode 100644 index 0000000..178ea41 Binary files /dev/null and b/gui/resources/images/switzerland.png differ diff --git a/gui/resources/images/switzerland_button.png b/gui/resources/images/switzerland_button.png new file mode 100644 index 0000000..8b9742a Binary files /dev/null and b/gui/resources/images/switzerland_button.png differ diff --git a/gui/resources/images/switzerland_mini.png b/gui/resources/images/switzerland_mini.png new file mode 100644 index 0000000..90bc5ca Binary files /dev/null and b/gui/resources/images/switzerland_mini.png differ diff --git a/gui/resources/images/sync_button.png b/gui/resources/images/sync_button.png new file mode 100644 index 0000000..012ac2c Binary files /dev/null and b/gui/resources/images/sync_button.png differ diff --git a/gui/resources/images/system-wide.png b/gui/resources/images/system-wide.png new file mode 100644 index 0000000..a47f933 Binary files /dev/null and b/gui/resources/images/system-wide.png differ diff --git a/gui/resources/images/system-wide_button.png b/gui/resources/images/system-wide_button.png new file mode 100644 index 0000000..a47f933 Binary files /dev/null and b/gui/resources/images/system-wide_button.png differ diff --git a/gui/resources/images/system_wide_global.png b/gui/resources/images/system_wide_global.png new file mode 100644 index 0000000..6bd63b4 Binary files /dev/null and b/gui/resources/images/system_wide_global.png differ diff --git a/gui/resources/images/tapa_miniatura.png b/gui/resources/images/tapa_miniatura.png new file mode 100644 index 0000000..c272654 Binary files /dev/null and b/gui/resources/images/tapa_miniatura.png differ diff --git a/gui/resources/images/tor 86x130.png b/gui/resources/images/tor 86x130.png new file mode 100644 index 0000000..a47645a Binary files /dev/null and b/gui/resources/images/tor 86x130.png differ diff --git a/gui/resources/images/tor_button.png b/gui/resources/images/tor_button.png new file mode 100644 index 0000000..a066503 Binary files /dev/null and b/gui/resources/images/tor_button.png differ diff --git a/gui/resources/images/tor_sync.png b/gui/resources/images/tor_sync.png new file mode 100644 index 0000000..91eea46 Binary files /dev/null and b/gui/resources/images/tor_sync.png differ diff --git a/gui/resources/images/torgrande.png b/gui/resources/images/torgrande.png new file mode 100644 index 0000000..edfdfa1 Binary files /dev/null and b/gui/resources/images/torgrande.png differ diff --git a/gui/resources/images/toricon_mini.png b/gui/resources/images/toricon_mini.png new file mode 100644 index 0000000..c9dbc34 Binary files /dev/null and b/gui/resources/images/toricon_mini.png differ diff --git a/gui/resources/images/toricon_mini_false.png b/gui/resources/images/toricon_mini_false.png new file mode 100644 index 0000000..eb3e9fa Binary files /dev/null and b/gui/resources/images/toricon_mini_false.png differ diff --git a/gui/resources/images/torx.png b/gui/resources/images/torx.png new file mode 100644 index 0000000..22ae170 Binary files /dev/null and b/gui/resources/images/torx.png differ diff --git a/gui/resources/images/united kingdom garaje.png b/gui/resources/images/united kingdom garaje.png new file mode 100644 index 0000000..9077087 Binary files /dev/null and b/gui/resources/images/united kingdom garaje.png differ diff --git a/gui/resources/images/united kingdom_button.png b/gui/resources/images/united kingdom_button.png new file mode 100644 index 0000000..a7f39de Binary files /dev/null and b/gui/resources/images/united kingdom_button.png differ diff --git a/gui/resources/images/united kingdom_mini.png b/gui/resources/images/united kingdom_mini.png new file mode 100644 index 0000000..4a6cd1b Binary files /dev/null and b/gui/resources/images/united kingdom_mini.png differ diff --git a/gui/resources/images/windows.png b/gui/resources/images/windows.png new file mode 100644 index 0000000..fc0a860 Binary files /dev/null and b/gui/resources/images/windows.png differ diff --git a/gui/resources/images/windowsx.png b/gui/resources/images/windowsx.png new file mode 100644 index 0000000..17ee105 Binary files /dev/null and b/gui/resources/images/windowsx.png differ diff --git a/gui/resources/images/wireguard Iceland.png b/gui/resources/images/wireguard Iceland.png new file mode 100644 index 0000000..27c248f Binary files /dev/null and b/gui/resources/images/wireguard Iceland.png differ diff --git a/gui/resources/images/wireguard Malaysia.png b/gui/resources/images/wireguard Malaysia.png new file mode 100644 index 0000000..7c78367 Binary files /dev/null and b/gui/resources/images/wireguard Malaysia.png differ diff --git a/gui/resources/images/wireguard Moldova.png b/gui/resources/images/wireguard Moldova.png new file mode 100644 index 0000000..faa7864 Binary files /dev/null and b/gui/resources/images/wireguard Moldova.png differ diff --git a/gui/resources/images/wireguard The Netherlands.png b/gui/resources/images/wireguard The Netherlands.png new file mode 100644 index 0000000..2ea9dce Binary files /dev/null and b/gui/resources/images/wireguard The Netherlands.png differ diff --git a/gui/resources/images/wireguard US (Ohio).png b/gui/resources/images/wireguard US (Ohio).png new file mode 100644 index 0000000..055d273 Binary files /dev/null and b/gui/resources/images/wireguard US (Ohio).png differ diff --git a/gui/resources/images/wireguard US (Seattle).png b/gui/resources/images/wireguard US (Seattle).png new file mode 100644 index 0000000..2b0dda9 Binary files /dev/null and b/gui/resources/images/wireguard US (Seattle).png differ diff --git a/gui/resources/images/wireguard finland.png b/gui/resources/images/wireguard finland.png new file mode 100644 index 0000000..9ffd8cf Binary files /dev/null and b/gui/resources/images/wireguard finland.png differ diff --git a/gui/resources/images/wireguard los angeles.png b/gui/resources/images/wireguard los angeles.png new file mode 100644 index 0000000..1c1dadc Binary files /dev/null and b/gui/resources/images/wireguard los angeles.png differ diff --git a/gui/resources/images/wireguard new york.png b/gui/resources/images/wireguard new york.png new file mode 100644 index 0000000..ea5af80 Binary files /dev/null and b/gui/resources/images/wireguard new york.png differ diff --git a/gui/resources/images/wireguard switzerland.png b/gui/resources/images/wireguard switzerland.png new file mode 100644 index 0000000..bc00bfa Binary files /dev/null and b/gui/resources/images/wireguard switzerland.png differ diff --git a/gui/resources/images/wireguard.png b/gui/resources/images/wireguard.png new file mode 100644 index 0000000..63fd9ee Binary files /dev/null and b/gui/resources/images/wireguard.png differ diff --git a/gui/resources/images/wireguard_browser.png b/gui/resources/images/wireguard_browser.png new file mode 100644 index 0000000..43feb16 Binary files /dev/null and b/gui/resources/images/wireguard_browser.png differ diff --git a/gui/resources/images/wireguard_button.png b/gui/resources/images/wireguard_button.png new file mode 100644 index 0000000..6f35a6a Binary files /dev/null and b/gui/resources/images/wireguard_button.png differ diff --git a/gui/resources/images/wireguard_mini.png b/gui/resources/images/wireguard_mini.png new file mode 100644 index 0000000..629255b Binary files /dev/null and b/gui/resources/images/wireguard_mini.png differ diff --git a/gui/resources/images/wireguard_system_wide.png b/gui/resources/images/wireguard_system_wide.png new file mode 100644 index 0000000..35e4e09 Binary files /dev/null and b/gui/resources/images/wireguard_system_wide.png differ diff --git a/gui/resources/styles/look.css b/gui/resources/styles/look.css new file mode 100644 index 0000000..092a7b9 --- /dev/null +++ b/gui/resources/styles/look.css @@ -0,0 +1,66 @@ +QLabel { + /* background-color: rgba(255, 255, 0, 0.2); */ + font-family: Retro Gaming; + font-size: 18px; + color: rgb(0, 255, 255); +} + +QLineEdit { + background-color:transparent; + font-family: Retro Gaming; + font-size: 18px; + color: rgb(0, 255, 255); +} +QTextEdit { + background-color:transparent; + font-family: Retro Gaming; + font-size: 18px; + color: rgb(0, 255, 255); + padding: 4px; +} + +.browser-version { + font-family: "Retro Gaming"; + font-size: 10px; + color: #888888; +} + +.browser-version-large { + font-family: "Retro Gaming"; + font-size: 25px; + color: rgb(0, 255, 255); +} +QLabel#label_profiles { + background-color: rgba(128, 128, 128, 0.5); /* Gray color with 50% opacity */ + + border-radius: 25px; /* Ajusta el valor según lo desees */ +} +QComboBox { + background-color: transparent; + padding: 3px; + color: rgb(0, 255, 255); + border: none; /* Elimina los bordes */ + font-family: "Retro Gaming"; /* Define el tipo de letra como Retro Gaming */ +} +QPushButton { + border: none; + border-radius: 5px; + padding: 5px 10px; + background-color: rgba(20, 13, 19, 0.288); + color: rgb(255, 255, 255); /* Color del texto */ +} + + + +/* Cambios al pasar el mouse sobre el botón */ +QPushButton:hover, QPushButton:checked { + background-color: rgba(67, 62, 87, 0.8); +} + + + +/* Cambios al presionar el botón */ +QPushButton:pressed, QPushButton:checked:pressed { + background-color: rgba(200, 200, 200, 1.0); + border: 1px solid #000fff; /* Borde al presionar el botón */ +}