sp-hydra-veil-gui/gui/__main__.py
2025-04-15 15:53:11 +01:00

5732 lines
230 KiB
Python

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(object, 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()
elif self.action == 'CHECK_INVOICE_STATUS':
self.check_invoice_status()
def check_invoice_status(self):
try:
invoice = InvoiceController.get(self.profile_data['billing_code'])
if invoice:
status = invoice.status
if status == "expired":
self.invoice_finished.emit(False)
else:
self.invoice_finished.emit(True)
else:
self.invoice_finished.emit(False)
except Exception as e:
print(f"Error retrieving invoice: {str(e)}")
self.invoice_finished.emit(False)
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...")
ClientController.sync(client_observer=client_observer, connection_observer=connection_observer)
update_available = ClientController.can_be_updated()
if update_available:
self.text_output.emit("An update is available. Downloading...")
self.finished.emit(True)
else:
self.text_output.emit("No updates available.")
self.finished.emit(False)
def install_package(self):
try:
self.text_output.emit(f"Installing {self.package_name}...")
subprocess.run(self.package_command.split(), check=True)
self.text_output.emit(f"{self.package_name} installed!")
self.finished.emit(True)
except subprocess.CalledProcessError:
self.text_output.emit("Installation failed")
self.finished.emit(False)
except Exception as e:
self.text_output.emit(f"An error occurred when installing {self.package_name}: {e}")
self.finished.emit(False)
def disable_all_profiles(self):
try:
for profile_id in self.profile_data:
profile = ProfileController.get(int(profile_id))
if isinstance(profile, SessionProfile):
ProfileController.disable(profile, force=True, profile_observer=profile_observer)
for profile_id in self.profile_data:
profile = ProfileController.get(int(profile_id))
if isinstance(profile, SystemProfile):
ProfileController.disable(profile, force=True, profile_observer=profile_observer)
self.text_output.emit("All profiles were successfully disabled")
except Exception:
self.text_output.emit("An error occurred when disabling profile")
finally:
self.finished.emit(True)
def destroy_profile(self):
profile_id = int(self.profile_data['id'])
profile = ProfileController.get(profile_id)
if profile is not None:
ProfileController.destroy(profile)
self.text_output.emit(f'Profile {profile_id} deleted')
else:
self.text_output.emit(f'Profile {profile_id} does not exist')
def list_profiles(self):
profiles = ProfileController.get_all()
self.profiles_output.emit(profiles)
def create_profile(self, profile_type):
location = LocationController.get(self.profile_data['country_code'], self.profile_data['code'])
if location is None:
self.text_output.emit(f"Invalid location code: {self.profile_data['location_code']}")
return
profile_id = int(self.profile_data['id']) if self.profile_data['id'] else None
name = self.profile_data['name']
connection_type = self.profile_data['connection_type']
if profile_type == "session":
application_details = self.profile_data['application'].split(':', 1)
application_version = ApplicationVersionController.get(application_details[0], application_details[1] if len(application_details) > 1 else None)
if application_version is None:
self.text_output.emit(f"Invalid application: {self.profile_data['application']}")
return
mask_connection = True if connection_type == 'tor' or connection_type == 'system' else False
resolution = self.profile_data['resolution']
connection = SessionConnection(connection_type, mask_connection)
profile = SessionProfile(profile_id, name, None, location, resolution, application_version, connection)
elif profile_type == "system":
connection = SystemConnection(connection_type)
profile = SystemProfile(profile_id, name, None, location, connection)
else:
self.text_output.emit(f"Invalid profile type: {profile_type}")
return
try:
ProfileController.create(profile, profile_observer=profile_observer)
except Exception as e:
self.text_output.emit(f"An error occurred when creating profile {profile.id}")
self.text_output.emit(f"{profile_type.capitalize()} Profile created with ID: {profile.id}")
def disable_profile(self):
try:
profile = ProfileController.get(int(self.profile_data['id']))
if profile:
ProfileController.disable(profile, profile_observer=profile_observer)
else:
self.text_output.emit(f"No profile found with ID: {self.profile_data['id']}")
except Exception as e:
self.text_output.emit("An error occurred when disabling profile")
finally:
self.finished.emit(True)
def sync(self):
try:
ConfigurationController.set_connection('system')
ClientController.sync(client_observer=client_observer, connection_observer=connection_observer)
locations = LocationController.get_all()
print(locations)
all_location_codes = [f"{location.country_code}_{location.code}" for location in locations]
self.sync_output.emit(all_location_codes, True, False, locations, False)
except Exception as e:
print(e)
self.sync_output.emit([], False, False, [], False)
def tor_sync(self):
try:
ConfigurationController.set_connection('tor')
self.text_output.emit('Syncing in progress...')
ClientController.sync(client_observer=client_observer, connection_observer=connection_observer)
locations = LocationController.get_all()
all_location_codes = [location.country_name for location in locations]
self.sync_output.emit(all_location_codes, True, True, locations, False)
except ConnectionError:
self.sync_output.emit([], False, False, [], False)
except CommandNotFoundError:
self.sync_output.emit([], False, False, [], True)
def get_connection(self):
connection = ConfigurationController.get_connection()
self.text_output.emit(f"Current connection: {connection}")
def set_connection(self):
ConfigurationController.set_connection(self.profile_data['connection_type'])
self.text_output.emit(f"Connection set to '{self.profile_data['connection_type']}'")
def get_subscription(self):
try:
invoice_observer.subscribe('retrieved', lambda event: self.handle_event(event, self.profile_data['currency']))
invoice_observer.subscribe('processing', lambda event: self.text_output.emit('A payment has been detected and is being verified...'))
invoice_observer.subscribe('settled', lambda event: self.text_output.emit('The payment has been successfully verified.'))
profile = ProfileController.get(int(self.profile_data['id']))
subscription_plan = SubscriptionPlanController.get(profile.connection, self.profile_data['duration'])
if subscription_plan is None:
self.text_output.emit('No compatible subscription plan was found.')
return
potential_subscription = SubscriptionController.create(subscription_plan, profile, connection_observer=connection_observer)
if potential_subscription is not None:
ProfileController.attach_subscription(profile, potential_subscription)
else:
self.text_output.emit('The subscription could not be created. Try again later.')
return
subscription = InvoiceController.handle_payment(potential_subscription.billing_code, invoice_observer=invoice_observer, connection_observer=connection_observer)
if subscription is not None:
ProfileController.attach_subscription(profile, subscription)
self.text_output.emit('Successfully activated the subscription')
self.invoice_finished.emit(True)
else:
self.text_output.emit('The subscription could not be activated. Try again later.')
self.invoice_finished.emit(False)
except Exception as e:
self.text_output.emit('An unknown error occurred')
self.invoice_finished.emit(False)
def handle_connection_events(self, event):
self.text_output.emit(f'Profile disabled')
def handle_event(self, event, currency=None):
invoice = event.subject
if isinstance(invoice, object):
self.invoice_output.emit(invoice, '')
self.text_output.emit("Invoice generated. Awaiting payment...")
else:
self.text_output.emit("Invalid invoice data received.")
class CustomWindow(QMainWindow):
def __init__(self):
super().__init__()
self.setWindowFlags(
Qt.WindowType.Window
)
self.setAttribute(Qt.WidgetAttribute.WA_TranslucentBackground)
self.setAttribute(Qt.WidgetAttribute.WA_NoSystemBackground)
self.setWindowTitle("HydraVeil VPN")
self._data = {"Profile_1": {}}
self.gui_dir = os.path.join(Constants.HV_DATA_HOME, "gui")
self.gui_config_file = os.path.join(self.gui_dir, "config.json")
self.gui_log_file = os.path.join(self.gui_dir, "gui.log")
self._setup_gui_directory()
current_dir = os.path.dirname(os.path.abspath(__file__))
self.btn_path = os.getenv('BTN_PATH', os.path.join(current_dir, 'resources', 'images'))
self.css_path = os.getenv('CSS_PATH', os.path.join(current_dir, 'resources', 'styles'))
icon_base = os.getenv('SYSTEM_TRAY_ICON', '')
self.tray = None
self.init_system_tray(icon_base)
self.is_downloading = False
self.current_profile_id = None
self.connection_manager = ConnectionManager()
current_connection = ConfigurationController.get_connection()
self.is_tor_mode = current_connection == 'tor'
self.is_animating = False
self.animation_step = 0
self.page_history = []
self.log_path = None
profile_observer.subscribe('created', lambda event: self.update_status('Profile Created'))
profile_observer.subscribe('destroyed', lambda event: self.update_status('Profile destroyed'))
application_version_observer.subscribe('downloading', lambda event: self.update_status(f'Downloading {ApplicationController.get(event.subject.application_code).name}'))
application_version_observer.subscribe('download_progressing', lambda event: self.update_status(f'Downloading {ApplicationController.get(event.subject.application_code).name} {event.meta.get('progress'):.2f}%'))
application_version_observer.subscribe('downloaded', lambda event: self.update_status(f'Downloaded {ApplicationController.get(event.subject.application_code).name}'))
connection_observer.subscribe('connecting', lambda event: self.update_status(f'[{event.subject.get("attempt_count")}/{event.subject.get("maximum_number_of_attempts")}] Performing connection attempt...'))
self.setFixedSize(800, 570)
self.central_widget = QWidget(self)
self.central_widget.setMaximumSize(800, 600)
self.central_widget.setGeometry(0, 0, 850, 600)
self.central_widget.setObjectName("centralwidget")
# Creamos el QLabel para mostrar la imagen
self.label = QLabel(self.central_widget)
self.label.setGeometry(0, 0, 800, 570)
css_file = os.path.join(self.css_path, 'look.css')
self.setStyleSheet(open(css_file).read() if os.path.exists(css_file) else '') # Opcional: si deseas cargar estilos CSS desde un archivo
self.status_label = QLabel(self)
self.status_label.setGeometry(15, 518, 780, 20)
self.status_label.setAlignment(Qt.AlignmentFlag.AlignLeft | Qt.AlignmentFlag.AlignVCenter)
self.status_label.setStyleSheet("color: rgb(0, 255, 255); font-size: 16px;")
self.status_label.setText(f"Status: Client version {Constants.HV_CLIENT_VERSION_NUMBER}")
self.text_start_x = 15
self.text_end_x = 420
self.text_width = self.text_end_x - self.text_start_x
self.marquee_text = ""
self.marquee_position = 0
self.marquee_timer = QTimer(self)
self.marquee_timer.timeout.connect(self.update_marquee)
self.marquee_enabled = False
self.scroll_speed = 1
self.tor_text = QLabel("Billing & Browsers", self)
self.tor_text.setGeometry(522, 542, 150, 20)
self.tor_text.setStyleSheet("""
QLabel {
color: cyan;
font-size: 11px;
font-weight: bold;
}
""")
self.update_image()
self.set_scroll_speed(7)
self.tor_icon = QLabel(self)
self.tor_icon.setGeometry(705, 537, 25, 25)
self.set_tor_icon()
self.toggle_button = QPushButton(self)
self.toggle_button.setGeometry(645, 541, 50, 22)
self.toggle_button.setCheckable(True)
self.toggle_button.clicked.connect(self.toggle_mode)
self.create_toggle(self.is_tor_mode)
self.animation_timer = QTimer()
self.animation_timer.timeout.connect(self.animate_toggle)
self.check_logging()
self.init_ui()
def _setup_gui_directory(self):
os.makedirs(self.gui_dir, exist_ok=True)
if not os.path.exists(self.gui_config_file):
default_config = {
"logging": {
"gui_logging_enabled": False,
"log_level": "INFO"
}
}
with open(self.gui_config_file, 'w') as f:
json.dump(default_config, f, indent=4)
def _load_gui_config(self):
try:
with open(self.gui_config_file, 'r') as f:
return json.load(f)
except FileNotFoundError:
return None
except json.JSONDecodeError:
logging.error("Invalid GUI config file format")
return None
def _save_gui_config(self, config):
with open(self.gui_config_file, 'w') as f:
json.dump(config, f, indent=4)
def check_logging(self):
config = self._load_gui_config()
if config is None:
return
if config["logging"]["gui_logging_enabled"]:
self._setup_gui_logging()
def stop_gui_logging(self):
if os.path.exists(self.gui_log_file):
os.remove(self.gui_log_file)
config = self._load_gui_config()
if config:
config["logging"]["gui_logging_enabled"] = False
self._save_gui_config(config)
sys.excepthook = sys.__excepthook__
def _setup_gui_logging(self):
os.makedirs(self.gui_dir, exist_ok=True)
formatter = logging.Formatter(
'%(asctime)s - %(levelname)s - %(message)s',
datefmt='%Y-%m-%d %H:%M:%S'
)
file_handler = logging.FileHandler(self.gui_log_file)
file_handler.setLevel(logging.INFO)
file_handler.setFormatter(formatter)
logger = logging.getLogger()
logger.setLevel(logging.INFO)
logger.handlers = []
logger.addHandler(file_handler)
sys.excepthook = self._handle_uncaught_exception
if not os.path.exists(self.gui_log_file) or os.path.getsize(self.gui_log_file) == 0:
logging.info("GUI logging system initialized")
def _handle_uncaught_exception(self, exc_type, exc_value, exc_traceback):
logging.error(
f"Uncaught exception:\n"
f"Type: {exc_type.__name__}\n"
f"Value: {str(exc_value)}\n"
f"Traceback:\n{''.join(traceback.format_tb(exc_traceback))}"
)
sys.__excepthook__(exc_type, exc_value, exc_traceback)
def set_tor_icon(self):
if self.is_tor_mode:
icon_path = os.path.join(self.btn_path, "toricon_mini.png")
else:
icon_path = os.path.join(self.btn_path, "toricon_mini_false.png")
pixmap = QPixmap(icon_path)
self.tor_icon.setPixmap(pixmap)
self.tor_icon.setScaledContents(True)
def create_toggle(self, is_tor_mode):
handle_x = 30 if is_tor_mode else 3
colors = self._get_toggle_colors(is_tor_mode)
if not hasattr(self, 'animation_timer'):
self.animation_timer = QTimer()
self.animation_timer.timeout.connect(self.animate_toggle)
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;
}
""")
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)
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:
print(e)
self.update_signal.emit("An unknown error occurred", False, None, None, None)
else:
self.update_signal.emit(f"No profile found with ID: {self.profile_data['id']}", False, None, None, None)
def handle_profile_status(self, profile, is_enabled):
profile_id = profile.id
profile_connection = str(profile.connection.code)
message = self.generate_profile_message(profile, is_enabled)
if isinstance(profile, SessionProfile):
self.profile_type = 1
elif isinstance(profile, SystemProfile):
self.profile_type = 2
else:
self.profile_type = None
self.update_signal.emit(message, is_enabled, profile_id, self.profile_type, profile_connection)
@staticmethod
def generate_profile_message(profile, is_enabled, idle=False):
profile_id = profile.id
if not profile.subscription or not profile.subscription.expires_at:
return f"Offline. No subscription found."
profile_date = profile.subscription.expires_at
status = 'enabled' if is_enabled else 'disabled'
expiration_date = profile_date.replace(tzinfo=timezone.utc)
time_left = expiration_date - datetime.now(timezone.utc)
days_left = time_left.days
hours_left, remainder = divmod(time_left.seconds, 3600)
formatted_expiration = expiration_date.strftime("%Y-%m-%d %H:%M:%S")
if expiration_date < datetime.now(timezone.utc):
return "Offline. Subscription has expired."
if idle:
return f"Offline. Expires in {days_left} days."
if is_enabled:
return f"Profile {int(profile_id)} {status}. Expires on {formatted_expiration}. Time left: {days_left} days, {hours_left} hours."
else:
return f"Profile {int(profile_id)} {status}"
class MenuPage(Page):
def __init__(self, page_stack=None, main_window=None, parent=None):
super().__init__("Menu", page_stack, main_window, parent)
self.main_window = main_window
self.connection_manager = main_window.connection_manager
self.is_tor_mode = main_window.is_tor_mode
self.is_animating = main_window.is_animating
self.animation_step = main_window.animation_step
self.btn_path = main_window.btn_path
self.page_stack = page_stack
self.profile_info = {}
self.additional_labels = []
self.update_status = main_window
self.title.setGeometry(400, 40, 380, 30); self.title.setText("Load Profile")
self.not_connected_profile = os.path.join(self.btn_path, "button_profile.png")
self.connected_profile = os.path.join(self.btn_path, "button_session_profile.png")
self.system_wide_connected_profile = os.path.join(self.btn_path, "button_system_profile.png")
self.connected_tor_profile = os.path.join(self.btn_path, "button_session_tor.png")
self.worker = None
self.IsSystem: int = 0
self.button_states = {}
self.is_system_connected = False
self.profile_button_map = {}
self.update_button = QPushButton(self)
self.update_button.setGeometry(750, 530, 25, 25)
self.update_button.setObjectName("update_button")
update_icon = QtGui.QIcon(os.path.join(self.btn_path, "available-updates.png"))
self.update_button.setIcon(update_icon)
self.update_button.setIconSize(self.update_button.size())
self.update_button.clicked.connect(self.check_updates)
#self.label.setStyleSheet("background-color: rgba(0, 255, 0, 51);")
self.create_interface_elements() # Establecer el color de fondo y el color del texto utilizando hojas de estilo
def check_updates(self):
self.update_status.update_status("Checking for updates...")
self.worker = WorkerThread('CHECK_FOR_UPDATE')
self.worker.text_output.connect(self.update_status.update_status)
self.worker.finished.connect(self.on_update_check_finished)
self.worker.start()
def on_update_check_finished(self, result):
if result:
reply = QMessageBox.question(self, 'Update Available',
'An update is available. Would you like to download it?',
QMessageBox.StandardButton.Yes | QMessageBox.StandardButton.No)
if reply == QMessageBox.StandardButton.Yes:
self.worker = WorkerThread('DOWNLOAD_UPDATE')
self.worker.text_output.connect(self.update_status.update_status)
self.worker.update_finished.connect(self.on_update_download_finished)
self.worker.start()
else:
self.update_status.update_status("Update cancelled by user.")
else:
self.update_status.update_status("No updates available.")
def on_update_download_finished(self, result: dict):
if result["status"] and result["path"]:
self.countdown_timer = QTimer()
self.countdown_timer.setInterval(1000)
self.countdown_seconds = 3
self.new_app_path = result["path"]
def update_countdown():
if self.countdown_seconds > 0:
self.update_status.update_status(f"New version downloaded. Restarting in {self.countdown_seconds} second{'s' if self.countdown_seconds > 1 else ''}...")
self.countdown_seconds -= 1
else:
self.countdown_timer.stop()
self.restart_application()
def restart_application():
QApplication.quit()
current_app = Constants.APPIMAGE_PATH
if os.path.exists(self.new_app_path):
try:
os.remove(current_app)
os.execv(self.new_app_path, [self.new_app_path] + sys.argv[1:])
except PermissionError:
self.update_status.update_status(f"Permission denied: Unable to remove {current_app}")
else:
self.update_status.update_status(f"Error: New application not found at {self.new_app_path}")
self.restart_application = restart_application
self.countdown_timer.timeout.connect(update_countdown)
self.countdown_timer.start()
atexit.register(restart_application)
else:
self.update_status.update_status("Failed to download update.")
def create_interface_elements(self):
for icon_name, function, position in [
("create_profile", self.create_prof, (410, 440, 110, 60)),
("edit_profile", self.edit_prof, (540, 440, 110, 60)),
#("delete_profile", self.delete_profile, (670, 440, 110, 60)),
("launch", self.launch, (0, 440,185,63)),
("disconnect", self.disconnect, (200, 433,185,73)),
("disconnect_system_wide", self.disconnect, (200, 433,185,73)),
("just", self.connect, (200, 433,185,73)),
("just_session", self.connect, (200, 433,185,73)),
("settings", self.settings_gui, (670, 440, 110, 60)),
]:
boton = QPushButton(self)
boton.setGeometry(QtCore.QRect(*position))
boton.setObjectName("create_edit_settings_buttons")
icon = QtGui.QIcon(os.path.join(self.btn_path, f"{icon_name}.png"))
boton.setIcon(icon)
boton.setIconSize(boton.size())
boton.clicked.connect(functools.partial(function))
boton.setEnabled(False)
if icon_name == "disconnect":
boton.setEnabled(True)
boton.setVisible(False)
self.disconnect_button = boton
if icon_name == "launch":
self.boton_launch = boton
self.boton_launch.setVisible(False)
if icon_name =="create_profile":
self.boton_create=boton
self.boton_create.setEnabled(True)
if icon_name =="edit_profile":
self.boton_edit=boton
#if icon_name =="delete_profile":
# self.boton_settings=boton
if icon_name =="just":
boton.setVisible(False)
self.boton_just=boton
self.connect_button = boton
if icon_name =="just_session":
self.boton_just_session=boton
self.connect_button = boton
if icon_name =="disconnect_system_wide":
boton.setEnabled(True)
boton.setVisible(False)
self.disconnect_system_wide_button = boton
if icon_name =="settings":
self.settings_button = boton
self.settings_button.setEnabled(True)
self.create()
def delete_profile(self):
self.popup = ConfirmationPopup(self, message="Are you sure you want to\ndelete this profile?", action_button_text="Delete", cancel_button_text="Cancel")
self.popup.setWindowModality(Qt.WindowModality.ApplicationModal)
self.popup.finished.connect(lambda result: self.on_popup_finished(result))
self.popup.show()
def on_popup_finished(self, result):
if result:
profile_id = self.reverse_id
self.update_status.update_status(f'Deleting profile...')
self.worker_thread = WorkerThread('DESTROY_PROFILE', profile_data={'id': profile_id})
self.worker_thread.text_output.connect(self.delete_status_update)
self.worker_thread.start()
else:
pass
def delete_status_update(self, text):
self.update_status.update_status(text)
self.eliminacion()
def show_disconnect_button(self, toggle, profile_type, target_button):
if target_button.isChecked():
if profile_type == 1:
self.disconnect_button.setVisible(toggle)
self.boton_just_session.setVisible(not toggle)
else:
self.disconnect_system_wide_button.setVisible(toggle)
self.boton_just.setVisible(not toggle)
def match_core_profiles(self, profiles_dict):
new_dict = {}
for idx, profile in profiles_dict.items():
new_profile = {}
protocol = profile.connection.code
location = f'{profile.location.country_code}_{profile.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)
button.clicked.connect(lambda checked, pid=profile_id: self.print_profile_details(f"Profile_{pid}"))
return button
def create_profile_name_label(self, parent_label, name):
child_label = QLabel(parent_label)
child_label.setGeometry(0, 65, 175, 30)
child_label.setText(name)
child_label.setAlignment(Qt.AlignmentFlag.AlignCenter)
child_label.setAttribute(Qt.WidgetAttribute.WA_TransparentForMouseEvents)
child_label.show()
def create_profile_icons(self, parent_label, value):
connection_type = value.get('connection', '')
if connection_type in ['tor', 'just proxy']:
self.create_tor_proxy_icons(parent_label, value, connection_type)
else:
self.create_other_icons(parent_label, value, connection_type)
def create_tor_proxy_icons(self, parent_label, value, connection_type):
for j, label_name in enumerate(['protocol', 'location', 'browser']):
child_label = QLabel(parent_label)
child_label.setObjectName(f"label_{j}")
child_label.setGeometry(3 + j * 60, 5, 50, 50)
child_label.setObjectName("label_profiles")
child_label.setAttribute(Qt.WidgetAttribute.WA_TransparentForMouseEvents)
image_path = self.get_icon_path(label_name, value, connection_type)
pixmap = QPixmap(image_path)
if pixmap.isNull() and label_name == 'location':
fallback_path = os.path.join(self.btn_path, "default_location_mini.png")
pixmap = QPixmap(fallback_path)
if pixmap.isNull():
pixmap = QPixmap(50, 50)
pixmap.fill(QtGui.QColor(200, 200, 200))
child_label.setPixmap(pixmap)
child_label.show()
def create_other_icons(self, parent_label, value, connection_type):
for j, label_name in enumerate(['protocol', 'location', 'browser']):
child_label = QLabel(parent_label)
child_label.setObjectName(f"label_{j}")
child_label.setGeometry(3 + j * 60, 5, 50, 50)
child_label.setObjectName("label_profiles")
child_label.setAttribute(Qt.WidgetAttribute.WA_TransparentForMouseEvents)
image_path = self.get_icon_path(label_name, value, connection_type)
pixmap = QPixmap(image_path)
if pixmap.isNull() and label_name == 'location':
fallback_path = os.path.join(self.btn_path, "default_location_mini.png")
pixmap = QPixmap(fallback_path)
if pixmap.isNull():
pixmap = QPixmap(50, 50)
pixmap.fill(QtGui.QColor(200, 200, 200))
child_label.setPixmap(pixmap)
child_label.show()
def 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")
return os.path.join(self.btn_path, "wireguard_mini.png")
elif label_name == 'browser':
if connection_type == 'system-wide':
return os.path.join(self.btn_path, "wireguard_system_wide.png")
else:
return os.path.join(self.btn_path, f"{value[label_name]} latest_mini.png")
return os.path.join(self.btn_path, f"icon_mini_{value[label_name]}.png")
def print_profile_details(self, profile_name):
self.reverse_id = int(profile_name.split('_')[1])
target_button = self.profile_button_map.get(self.reverse_id)
if target_button:
self.connection_manager.set_profile_button_objects(self.reverse_id, target_button)
else:
print(f"No button found for profile {self.reverse_id}")
is_connected = self.connection_manager.is_profile_connected(int(self.reverse_id))
if is_connected:
self.boton_edit.setEnabled(False)
else:
self.boton_edit.setEnabled(True)
profile = self.profile_info.get(profile_name)
self.boton_launch.setEnabled(True)
self.boton_just.setEnabled(True)
self.boton_just_session.setEnabled(True)
self.boton_create.setEnabled(True)
#self.boton_settings.setEnabled(True)
# Eliminar labels adicionales anteriores
for label in self.additional_labels:
label.deleteLater()
# Limpiar la lista de labels adicionales
self.additional_labels.clear()
self.change_connect_button()
if profile:
self.update_status.current_profile_id = self.reverse_id
profile_number = profile_name.split("_")[1] # Extraer el número del perfil del nombre del perfil
name = profile.get("name", "")
protocol = profile.get("protocol", "")
location = profile.get("location", "")
connection = profile.get("connection", "")
country_garaje = profile.get("country_garaje", "")
if protocol.lower() in ["wireguard", "open", "lokinet","residential", "hidetor"]:
label_principal = QLabel(self)
label_principal.setGeometry(0, 90, 400, 300)
pixmap=QPixmap(os.path.join(self.btn_path, f"{protocol}_{location}.png"))
label_principal.setPixmap(pixmap)
label_principal.setScaledContents(True)
label_principal.show()
self.additional_labels.append(label_principal)
if protocol.lower() == "hidetor":
label_principal.setGeometry(0, 80, 400, 300)
label_background = QLabel(self)
label_background.setGeometry(0, 60, 410, 354)
if connection == 'just proxy':
image_path = os.path.join(self.btn_path, f"icon_{location}.png")
else:
image_path = os.path.join(self.btn_path, f"hdtor_{location}.png")
pixmap = QPixmap(image_path)
label_background.setPixmap(pixmap)
label_background.show()
label_background.setScaledContents(True)
label_background.lower()
self.additional_labels.append(label_background)
if protocol.lower() == "wireguard" and connection.lower() == "browser-only":
# Redimensionar el QLabel principal
label_principal.setGeometry(0, 80, 400, 300)
# Crear QLabel con la imagen os.path.join(self.btn_path, "browser only.png") detrás del label_principal
label_background = QLabel(self)
label_background.setGeometry(0, 60, 410, 354) # Geometría según necesidades
pixmap = QPixmap(os.path.join(self.btn_path, "browser only.png"))
label_background.setPixmap(pixmap)
label_background.show()
label_background.setScaledContents(True)
label_background.lower()
self.additional_labels.append(label_background)
if protocol.lower() == "residential" and connection.lower() == "tor":
label_background = QLabel(self)
label_background.setGeometry(0, 60, 410, 354)
pixmap = QPixmap(os.path.join(self.btn_path, "browser_tor.png"))
label_background.setPixmap(pixmap)
label_background.show()
label_background.setScaledContents(True)
#label_background.setStyleSheet("background-color: blue;")
self.additional_labels.append(label_background)
label_garaje = QLabel(self)
label_garaje.setGeometry(5, 110, 400, 300)
pixmap = QPixmap(os.path.join(self.btn_path, f"{country_garaje} garaje.png"))
label_garaje.setPixmap(pixmap)
label_garaje.show()
label_garaje.setScaledContents(True)
#label_garaje.setStyleSheet("background-color: red;")
self.additional_labels.append(label_garaje)
label_tor = QLabel(self)
label_tor.setGeometry(330, 300, 75, 113)
pixmap = QPixmap(os.path.join(self.btn_path, "tor 86x130.png"))
label_tor.setPixmap(pixmap)
label_tor.show()
label_tor.setScaledContents(True)
self.additional_labels.append(label_tor)
if protocol.lower() == "residential" and connection.lower() == "just proxy":
label_background = QLabel(self)
label_background.setGeometry(0, 60, 410, 354)
pixmap = QPixmap(os.path.join(self.btn_path, "browser_just_proxy.png"))
label_background.setPixmap(pixmap)
label_background.show()
label_background.setScaledContents(True)
#label_background.setStyleSheet("background-color: blue;")
self.additional_labels.append(label_background)
label_garaje = QLabel(self)
label_garaje.setGeometry(5, 110, 400, 300)
pixmap = QPixmap(os.path.join(self.btn_path, f"{country_garaje} garaje.png"))
label_garaje.setPixmap(pixmap)
label_garaje.show()
label_garaje.setScaledContents(True)
#label_garaje.setStyleSheet("background-color: red;")
self.additional_labels.append(label_garaje)
# Actualizar perfil para editar
editar_profile = {"profile_number": profile_number, "name": name, "protocol": protocol}
self.page_stack.currentWidget().add_selected_profile(editar_profile)
def launch(self):
pass
def connect(self):
self.boton_just.setEnabled(False)
self.boton_just_session.setEnabled(False)
if self.connection_manager.get_is_profile_being_enabled(int(self.reverse_id)):
self.update_status.update_status('Profile is already being enabled...')
return
profile = ProfileController.get(int(self.reverse_id))
is_tor = ConfigurationController.get_connection() == 'tor'
if profile.connection.code == 'tor' and not profile.application_version.installed and not is_tor:
message = f'You are using a Tor profile, but the associated browser is not installed. If you want the browser to be downloaded with Tor, you must switch to a Tor connection. Otherwise, proceed with a clearweb connection.'
else:
self.get_billing_code_by_id()
return
self.popup = self.update_status._create_confirmation_popup(message, button_text='Proceed')
self.popup.finished.connect(lambda result: self._handle_popup_result(result))
self.popup.show()
def _handle_popup_result(self, result, change_screen=False):
if result:
if change_screen:
self.page_stack.setCurrentIndex(self.page_stack.indexOf(self.page_stack.findChild(IdPage)))
else:
self.get_billing_code_by_id()
else:
self.popup.close()
def disconnect(self):
self.disconnect_button.setEnabled(False)
self.disconnect_system_wide_button.setEnabled(False)
self.update_status.update_status('Disconnecting...')
profile_data = {
'id': int(self.reverse_id)
}
profile = ProfileController.get(int(self.reverse_id))
is_session_profile = isinstance(profile, SessionProfile)
if not is_session_profile:
connected_profiles = self.connection_manager.get_connected_profiles()
if len(connected_profiles) > 1:
message = f'Profile{"" if len(connected_profiles) == 1 else "s"} {", ".join(str(profile_id) for profile_id in connected_profiles)} are still connected.\nAll connected session profiles will be disconnected. \nDo you want to proceed?'
self.update_status._show_disconnect_confirmation(connected_profiles, button_text='Disable', message=message)
self.disconnect_button.setEnabled(True)
self.disconnect_system_wide_button.setEnabled(True)
return
self.worker_thread = WorkerThread('DISABLE_PROFILE', profile_data=profile_data)
self.worker_thread.finished.connect(self.on_disconnect_done)
self.worker_thread.start()
def on_disconnect_done(self):
self.disconnect_button.setEnabled(True)
self.disconnect_system_wide_button.setEnabled(True)
pass
def create_prof(self):
if len(self.profile_info) == 6:
self.update_status.update_status('Maximum number of profiles reached!')
return
self.page_stack.setCurrentIndex(self.page_stack.indexOf(self.page_stack.findChild(ProtocolPage)))
def change_connect_button(self):
profile = ProfileController.get(int(self.reverse_id))
is_connected = self.connection_manager.is_profile_connected(int(self.reverse_id))
is_session_profile = isinstance(profile, SessionProfile)
self.update_button_visibility(is_connected, is_session_profile)
self.update_status_message(profile, is_connected)
def update_button_visibility(self, is_connected, is_session_profile):
self.boton_just_session.setVisible(not is_connected and is_session_profile)
self.boton_just.setVisible(not is_connected and not is_session_profile)
self.disconnect_button.setVisible(is_connected and is_session_profile)
self.disconnect_system_wide_button.setVisible(is_connected and not is_session_profile)
def update_status_message(self, profile, is_connected):
if is_connected:
message = Worker.generate_profile_message(profile, is_enabled=True)
self.update_status.enable_marquee(message)
else:
message = Worker.generate_profile_message(profile, is_enabled=True, idle=True)
self.update_status.update_status(message)
def edit_prof(self):
self.page_stack.setCurrentIndex(self.page_stack.indexOf(self.page_stack.findChild(EditorPage)))
def settings_gui(self):
self.page_stack.setCurrentIndex(self.page_stack.indexOf(self.page_stack.findChild(Settings)))
def eliminacion(self):
current_state = {
'is_tor_mode': self.is_tor_mode,
'is_animating': self.is_animating,
'animation_step': self.animation_step
}
self.main_window.toggle_bg.setParent(None)
self.main_window.toggle_handle.setParent(None)
self.main_window.on_label.setParent(None)
self.main_window.off_label.setParent(None)
for widget in self.findChildren(QLabel):
if widget != self.title:
widget.setParent(None)
self.profile_info.clear()
self.main_window.toggle_bg.setParent(self.main_window.toggle_button)
self.main_window.toggle_handle.setParent(self.main_window.toggle_button)
self.main_window.on_label.setParent(self.main_window.toggle_button)
self.main_window.off_label.setParent(self.main_window.toggle_button)
self.main_window.toggle_bg.show()
self.main_window.toggle_handle.show()
self.main_window.on_label.show()
self.main_window.off_label.show()
self.is_tor_mode = current_state['is_tor_mode']
self.is_animating = current_state['is_animating']
self.animation_step = current_state['animation_step']
self.create()
def get_billing_code_by_id(self, force: bool = False):
profile_id = self.update_status.current_profile_id
profile_data = {
'id': profile_id,
'force': force
}
self.update_status.update_status('Attempting to connect...')
self.enabling_profile(profile_data)
def enabling_profile(self, profile_data):
'''
if self.worker is not None:
self.worker.update_signal.disconnect(self.update_gui_main_thread)
self.worker.change_page.disconnect(self.change_app_page)
'''
self.connection_manager.set_is_profile_being_enabled(profile_data['id'], True)
self.worker = Worker(profile_data)
self.worker.update_signal.connect(self.update_gui_main_thread)
self.worker.change_page.connect(self.change_app_page)
thread = threading.Thread(target=self.worker.run)
thread.start()
def DisplayInstallScreen(self, package_name):
install_page = self.page_stack.findChild(InstallSystemPackage)
install_page.configure(package_name=package_name, distro='debian')
self.page_stack.setCurrentIndex(self.page_stack.indexOf(install_page))
def handle_enable_force_result(self, result):
if result:
self.get_billing_code_by_id(force=True)
else:
pass
self.popup.close()
def update_gui_main_thread(self, text, is_enabled, profile_id, profile_type, profile_connection):
if '...' in text:
message = f'The profile is already enabled. Do you want to force enable it anyway?'
self.popup = self.update_status._create_confirmation_popup(message, button_text='Proceed')
self.popup.finished.connect(lambda result: self.handle_enable_force_result(result))
self.popup.show()
return
if profile_id < 0:
self.DisplayInstallScreen(text)
return
if is_enabled:
self.update_status.enable_marquee(str(text))
else:
self.update_status.update_status(str(text))
if profile_id <= 6 and profile_id is not None:
self.on_finished_update_gui(is_enabled, profile_id, profile_type, profile_connection)
self.boton_just.setEnabled(True)
self.boton_just_session.setEnabled(True)
def change_app_page(self, text, Is_changed):
self.update_status.update_status(str(text))
if Is_changed:
current_connection = ConfigurationController.get_connection()
profile = ProfileController.get(int(self.reverse_id))
if current_connection != 'tor' and profile.connection.code == 'tor':
message = f'You are using a Tor profile, but the profile subscription is missing or expired. If you want the billing to be done thourgh Tor, you must switch to a Tor connection. Otherwise, proceed with a clearweb connection.'
self.popup = self.update_status._create_confirmation_popup(message, button_text='Proceed')
self.popup.finished.connect(lambda result: self._handle_popup_result(result, True))
self.popup.show()
else:
self.page_stack.setCurrentIndex(self.page_stack.indexOf(self.page_stack.findChild(IdPage)))
self.boton_just.setEnabled(True)
self.boton_just_session.setEnabled(True)
self.connection_manager.set_is_profile_being_enabled(self.reverse_id, False)
def on_finished_update_gui(self, is_enabled, profile_id, profile_type, profile_connection):
self.connection_manager.set_is_profile_being_enabled(profile_id, False)
try:
if is_enabled:
self.connection_manager.add_connected_profile(profile_id)
if profile_type == 1:
target_button: QPushButton = self.connection_manager.get_profile_button_objects(profile_id)
icon_path = self.connected_tor_profile if profile_connection == 'tor' else self.connected_profile
target_button.setIcon(QIcon(icon_path))
self.button_states[profile_id] = icon_path
else:
target_button: QPushButton = self.connection_manager.get_profile_button_objects(profile_id)
target_button.setIcon(QIcon(self.system_wide_connected_profile))
self.update_status.update_image(appearance_value='background_connected')
self.button_states[profile_id] = self.system_wide_connected_profile
self.IsSystem += 1
self.show_disconnect_button(True, profile_type, target_button)
self.boton_edit.setEnabled(False)
else:
self.connection_manager.remove_connected_profile(profile_id)
if profile_type == 1:
target_button: QPushButton = self.connection_manager.get_profile_button_objects(profile_id)
target_button.setIcon(QIcon(self.not_connected_profile))
if profile_id in self.button_states:
del self.button_states[profile_id]
else:
self.IsSystem -= 1
target_button: QPushButton = self.connection_manager.get_profile_button_objects(profile_id)
target_button.setIcon(QIcon(self.not_connected_profile))
if profile_id in self.button_states:
del self.button_states[profile_id]
if self.IsSystem == 0:
self.update_status.update_image()
self.show_disconnect_button(False, profile_type, target_button)
self.boton_edit.setEnabled(True)
except IndexError:
self.update_status.update_status('An error occurred while updating profile state.')
class ConnectionManager:
def __init__(self):
self._connected_profiles = set()
self._is_synced = False
self._is_profile_being_enabled = {}
self.profile_button_objects = {}
self._location_mapping = {
'md': 'Moldova',
'nl': 'The Netherlands',
'us': 'US (Ohio)',
'ca': 'US (Seattle)',
'fi': 'Finland',
'is': 'Iceland',
'my': 'Malaysia',
'la': 'US (Los Angeles)',
'ru': 'Russia',
'ro': 'Romania',
'ch': 'Switzerland'
}
self._timezone_mapping = {
'md': 'Europe/Chisinau',
'nl': 'Europe/Amsterdam',
'us': 'America/New_York',
'ca': 'America/Los_Angeles',
'fi': 'Europe/Helsinki',
'is': 'Atlantic/Reykjavik',
'my': 'Asia/Kuala_Lumpur',
'la': 'America/Los_Angeles',
'ru': 'Europe/Moscow',
'ro': 'Europe/Bucharest',
'ch': 'Europe/Zurich'
}
self._location_list = []
self.wireguard_locations = ['Moldova', 'The Netherlands', 'US (Ohio)', 'US (Seattle)', 'Iceland', 'Malaysia']
self.non_resident_proxy_locations = ['Moldova', 'The Netherlands', 'US (Ohio)', 'US (Seattle)', 'Iceland']
def get_profile_button_objects(self, profile_id):
return self.profile_button_objects.get(profile_id, None)
def set_profile_button_objects(self, profile_id, profile_button_objects):
self.profile_button_objects[profile_id] = profile_button_objects
def get_timezone(self, location_code):
if location_code in self._timezone_mapping:
return self._timezone_mapping[location_code]
return None
def add_connected_profile(self, profile_id):
self._connected_profiles.add(profile_id)
def remove_connected_profile(self, profile_id):
self._connected_profiles.discard(profile_id)
def is_profile_connected(self, profile_id):
return profile_id in self._connected_profiles
def get_connected_profiles(self):
return list(self._connected_profiles)
def set_synced(self, status):
self._is_synced = status
def is_synced(self):
return self._is_synced
def get_location_info(self, location_key):
if location_key in self._location_mapping:
return self._location_mapping[location_key]
return None
def store_locations(self, locations):
self._location_mapping = {}
self._location_list = locations
for location in locations:
self._location_mapping[location.country_name] = location
def get_location_list(self):
if self.is_synced():
location_names = [location.country_name for location in self._location_list]
return location_names
else:
return self.wireguard_locations
def get_non_residential_proxy_list(self):
return self.non_resident_proxy_locations
def set_is_profile_being_enabled(self, profile_id: int, status: bool):
self._is_profile_being_enabled[profile_id] = status
def get_is_profile_being_enabled(self, profile_id: int) -> bool:
return self._is_profile_being_enabled.get(profile_id, False)
class InstallSystemPackage(Page):
def __init__(self, page_stack, main_window=None, parent=None):
super().__init__("InstallPackage", page_stack, main_window, parent)
self.btn_path = main_window.btn_path
self.update_status = main_window
self.package_name = ""
self.install_command = ""
self.manual_install_command = ""
self.is_sync = False
self.ui_elements = []
self.permanent_elements = []
self.current_distro = "debian"
self.distro_buttons = {}
self._setup_permanent_ui()
self._setup_distro_buttons()
self.auto_install_package_commands = {
'all': {
'debian': 'pkexec apt install -y bubblewrap iproute2 microsocks proxychains4 ratpoison tor wireguard xserver-xephyr resolvconf',
'arch': 'pkexec pacman -S bubblewrap iproute2 microsocks proxychains-ng tor wireguard-tools xorg-server-xephyr resolvconf',
'fedora': 'pkexec dnf -y install bubblewrap iproute-doc proxychains4 ratpoison tor wireguard xorg-x11-server-Xephyr resolvconf'
},
'wireguard': {
'debian': 'pkexec apt -y install wireguard resolvconf',
'arch': 'pkexec pacman -S wireguard-tools resolvconf',
'fedora': 'pkexec dnf -y install wireguard-tools resolvconf'
},
'resolvconf': {
'debian': 'pkexec apt -y install resolvconf',
'arch': 'pkexec pacman -S resolvconf',
'fedora': 'pkexec dnf -y install resolvconf'
},
'tor': {
'debian': 'pkexec apt -y install tor proxychains4 microsocks',
'arch': 'pkexec pacman -S tor proxychains4 microsocks',
'fedora': 'pkexec dnf -y install tor proxychains4'
},
'tor_sync': {
'debian': 'pkexec apt -y install tor',
'arch': 'pkexec pacman -S tor',
'fedora': 'pkexec dnf -y install tor'
},
'proxychains4': {
'debian': 'pkexec apt -y install proxychains4',
'arch': 'pkexec pacman -S proxychains-ng',
'fedora': 'pkexec dnf -y install proxychains4'
},
'microsocks': {
'debian': 'pkexec apt -y install microsocks',
'arch': 'pkexec pacman -S microsocks',
'fedora': 'pkexec dnf -y install microsocks'
},
'xserver-xephyr': {
'debian': 'pkexec apt -y install xserver-xephyr bubblewrap ratpoison',
'arch': 'pkexec pacman -S xorg-server-xephyr bubblewrap ratpoison',
'fedora': 'pkexec dnf -y install xorg-x11-server-Xephyr bubblewrap ratpoison'
},
'bubblewrap': {
'debian': 'pkexec apt -y install bubblewrap',
'arch': 'pkexec pacman -S bubblewrap',
'fedora': 'pkexec dnf -y install bubblewrap'
}
}
self.manual_install_package_commands = {
'all': {
'debian': 'sudo apt install -y bubblewrap iproute2 microsocks proxychains4 ratpoison tor wireguard xserver-xephyr resolvconf',
'arch': 'sudo pacman -S bubblewrap iproute2 microsocks proxychains-ng tor wireguard-tools xorg-server-xephyr resolvconf',
'fedora': 'sudo dnf -y install bubblewrap iproute-doc proxychains4 ratpoison tor wireguard xorg-x11-server-Xephyr resolvconf'
},
'polkit': {
'debian': 'sudo apt -y install policykit-1',
'arch': 'sudo pacman -S polkit',
'fedora': 'sudo dnf -y install polkit'
},
'wireguard': {
'debian': 'sudo apt -y install wireguard resolvconf',
'arch': 'sudo pacman -S wireguard-tools resolvconf',
'fedora': 'sudo dnf -y install wireguard-tools resolvconf'
},
'resolvconf': {
'debian': 'sudo apt -y install resolvconf',
'arch': 'sudo pacman -S resolvconf',
'fedora': 'sudo dnf -y install resolvconf'
},
'tor': {
'debian': 'sudo apt -y install tor proxychains4 microsocks',
'arch': 'sudo pacman -S tor proxychains-ng microsocks',
'fedora': 'sudo dnf -y install tor proxychains4'
},
'tor_sync': {
'debian': 'sudo apt -y install tor',
'arch': 'sudo pacman -S tor',
'fedora': 'sudo dnf -y install tor'
},
'proxychains4': {
'debian': 'sudo apt -y install proxychains4',
'arch': 'sudo pacman -S proxychains-ng',
'fedora': 'sudo dnf -y install proxychains4'
},
'microsocks': {
'debian': 'sudo apt -y install microsocks',
'arch': 'sudo pacman -S microsocks',
'fedora': 'sudo dnf -y install microsocks'
},
'xserver-xephyr': {
'debian': 'sudo apt -y install xserver-xephyr bubblewrap ratpoison',
'arch': 'sudo pacman -S xorg-server-xephyr bubblewrap ratpoison',
'fedora': 'sudo dnf -y install xorg-x11-server-Xephyr bubblewrap ratpoison'
},
'bubblewrap': {
'debian': 'sudo apt -y install bubblewrap',
'arch': 'sudo pacman -S bubblewrap',
'fedora': 'sudo dnf -y install bubblewrap'
}
}
def _setup_permanent_ui(self):
self.title.setGeometry(20, 50, 300, 60)
self.title.setText("Package Installation")
self.title.setStyleSheet("font-size: 24px; font-weight: bold;")
self.permanent_elements.append(self.title)
self.display.setGeometry(QtCore.QRect(100, 160, 580, 435))
self.permanent_elements.append(self.display)
def _setup_distro_buttons(self):
distros = ["debian", "arch", "fedora"]
for i, distro in enumerate(distros):
btn = QPushButton(self)
btn.setGeometry(360 + i*140, 40, 120, 49)
btn.setIcon(QIcon(os.path.join(self.btn_path, f"{distro}.png")))
btn.setIconSize(QSize(120, 49))
btn.setCheckable(True)
btn.clicked.connect(lambda checked, d=distro: self.switch_distro(d))
self.distro_buttons[distro] = btn
self.permanent_elements.append(btn)
self.distro_buttons[self.current_distro].setChecked(True)
def switch_distro(self, distro):
self.current_distro = distro
for d, btn in self.distro_buttons.items():
btn.setChecked(d == distro)
if self.package_name:
self.update_command()
self.setup_ui()
def update_command(self):
if self.package_name in self.auto_install_package_commands:
if self.is_sync:
self.install_command = self.auto_install_package_commands['tor_sync'][self.current_distro]
self.manual_install_command = self.manual_install_package_commands['tor_sync'][self.current_distro]
else:
self.install_command = self.auto_install_package_commands[self.package_name][self.current_distro]
self.manual_install_command = self.manual_install_package_commands[self.package_name][self.current_distro]
else:
self.install_command = ""
self.manual_install_command = ""
def configure(self, package_name, distro, is_sync=False):
self.package_name = package_name
self.current_distro = distro
self.is_sync = is_sync
self.update_command()
self.setup_ui()
return self
def setup_ui(self):
self.button_back.setVisible(False)
self.button_go.setVisible(True)
if not self.package_name:
return
self.clear_ui()
self._setup_auto_install()
self._setup_manual_install()
self._setup_status_and_nav()
for element in self.ui_elements:
element.setParent(self)
element.setVisible(True)
element.raise_()
def clear_ui(self):
for element in self.ui_elements:
element.setParent(None)
element.deleteLater()
self.ui_elements.clear()
self.command_box = None
self.status_label = None
self.install_button = None
self.check_button = None
def _setup_auto_install(self):
auto_install_label = QLabel(f"Automatic Installation for {self.current_distro}", self)
auto_install_label.setGeometry(80, 130, 580, 30)
auto_install_label.setStyleSheet("font-size: 18px; font-weight: bold;")
auto_install_label.setVisible(True)
self.ui_elements.append(auto_install_label)
auto_install_desc = QLabel("Click the button below to automatically install the missing packages:", self)
auto_install_desc.setGeometry(80, 170, 650, 30)
auto_install_desc.setVisible(True)
self.ui_elements.append(auto_install_desc)
self.install_button = QPushButton(self)
self.install_button.setGeometry(135, 210, 100, 40)
self.install_button.setIcon(QIcon(os.path.join(self.btn_path, "install.png")))
self.install_button.setIconSize(self.install_button.size())
self.install_button.clicked.connect(self.install_package)
self.install_button.setVisible(True)
self.ui_elements.append(self.install_button)
if self.current_distro == 'arch':
auto_install_label.setGeometry(50, 110, 580, 30)
auto_install_desc.setGeometry(50, 140, 650, 30)
self.install_button.setGeometry(150, 185, 100, 40)
self.check_button = QPushButton(self)
self.check_button.setGeometry(270, 185, 100, 40)
self.check_button.setIcon(QIcon(os.path.join(self.btn_path, "check.png")))
self.check_button.setIconSize(self.check_button.size())
self.check_button.clicked.connect(self.check_package)
self.check_button.setVisible(True)
self.ui_elements.append(self.check_button)
def _setup_manual_install(self):
if self.current_distro != 'arch':
manual_install_label = QLabel(f"Manual Installation for {self.current_distro}", self)
manual_install_label.setGeometry(80, 290, 650, 30)
manual_install_label.setStyleSheet("font-size: 18px; font-weight: bold;")
manual_install_label.setVisible(True)
self.ui_elements.append(manual_install_label)
manual_install_desc = QLabel("Run this command in your terminal:", self)
manual_install_desc.setGeometry(80, 330, 380, 30)
manual_install_desc.setVisible(True)
self.ui_elements.append(manual_install_desc)
self.command_box = QLabel(self.manual_install_command, self)
self.command_box.setGeometry(80, 370, 380, 40)
if self.current_distro == 'arch':
self.command_box.setGeometry(50, 280, 380, 40)
metrics = self.command_box.fontMetrics()
text_width = metrics.horizontalAdvance(self.manual_install_command) + 100
available_width = self.command_box.width() - 20
font_size = 18
MIN_FONT_SIZE = 10
while text_width > available_width:
if font_size <= MIN_FONT_SIZE:
break
font_size -= 1
text_width -= 10
self.command_box.setStyleSheet(f"""
background-color: #2b2b2b;
color: #ffffff;
padding: 10px;
border-radius: 5px;
font-family: monospace;
font-size: {font_size}px;
""")
self.command_box.mousePressEvent = lambda _: self.copy_command()
self.command_box.setVisible(True)
self.ui_elements.append(self.command_box)
if self.current_distro == 'arch':
# Pacman commands section
pacman_label = QLabel("Pacman Installation:", self)
pacman_label.setGeometry(50, 240, 650, 30)
pacman_label.setStyleSheet("font-size: 18px; font-weight: bold; color: #00ffff;")
pacman_label.setVisible(True)
self.ui_elements.append(pacman_label)
# AUR commands section
aur_label = QLabel("AUR / Yay Installation:", self)
aur_label.setGeometry(50, 350, 300, 30)
aur_label.setStyleSheet("font-size: 18px; font-weight: bold; color: #00ffff;")
self.ui_elements.append(aur_label)
# Yay command
self.yay_command_box = QLabel("sudo yay -S ratpoison", self)
self.yay_command_box.setGeometry(50, 390, 300, 40)
self._setup_command_box_style(self.yay_command_box)
self.yay_command_box.mousePressEvent = lambda _: self.copy_command("sudo yay -S ratpoison", self.yay_command_box)
self.ui_elements.append(self.yay_command_box)
# Manual AUR command
aur_command = "git clone https://aur.archlinux.org/ratpoison.git\ncd ratpoison\nmakepkg -si --skippgpcheck"
self.aur_command_box = QLabel(aur_command, self)
self.aur_command_box.setGeometry(370, 390, 380, 60)
self._setup_command_box_style(self.aur_command_box)
self.aur_command_box.setWordWrap(True)
self.aur_command_box.mousePressEvent = lambda _: self.copy_command(aur_command, self.aur_command_box)
self.ui_elements.append(self.aur_command_box)
else:
self.command_box = QLabel(self.manual_install_command, self)
self.command_box.setGeometry(80, 370, 380, 40)
metrics = self.command_box.fontMetrics()
text_width = metrics.horizontalAdvance(self.manual_install_command) + 100
available_width = self.command_box.width() - 20
font_size = 18
MIN_FONT_SIZE = 10
while text_width > available_width:
if font_size <= MIN_FONT_SIZE:
break
font_size -= 1
text_width -= 10
self.command_box.setStyleSheet(f"""
background-color: #2b2b2b;
color: #ffffff;
padding: 10px;
border-radius: 5px;
font-family: monospace;
font-size: {font_size}px;
""")
self.command_box.mousePressEvent = lambda _: self.copy_command()
self.command_box.setVisible(True)
self.ui_elements.append(self.command_box)
def _setup_command_box_style(self, command_box):
metrics = command_box.fontMetrics()
text_width = metrics.horizontalAdvance(command_box.text()) + 100
available_width = command_box.width() - 20
font_size = 18
MIN_FONT_SIZE = 10
while text_width > available_width:
if font_size <= MIN_FONT_SIZE:
break
font_size -= 1
text_width -= 10
command_box.setStyleSheet(f"""
background-color: #2b2b2b;
color: #ffffff;
padding: 10px;
border-radius: 5px;
font-family: monospace;
font-size: {font_size}px;
""")
command_box.setVisible(True)
def copy_yay_command(self):
clipboard = QApplication.clipboard()
clipboard.setText("sudo yay -S ratpoison")
original_style = self.yay_command_box.styleSheet()
self.yay_command_box.setStyleSheet(original_style + "background-color: #27ae60;")
QTimer.singleShot(200, lambda: self.yay_command_box.setStyleSheet(original_style))
self.update_status.update_status("Yay command copied to clipboard")
def _setup_status_and_nav(self):
if self.current_distro != 'arch':
self.check_button = QPushButton(self)
self.check_button.setGeometry(130, 430, 100, 40)
self.check_button.setIcon(QIcon(os.path.join(self.btn_path, "check.png")))
self.check_button.setIconSize(self.check_button.size())
self.check_button.clicked.connect(self.check_package)
self.ui_elements.append(self.check_button)
self.button_go.clicked.connect(self.go_next)
self.button_back.clicked.connect(self.go_next)
self.status_label = QLabel("", self)
self.status_label.setGeometry(300, 430, 250, 40)
if self.current_distro == 'arch':
self.status_label.setGeometry(450, 185, 250, 40)
self.ui_elements.append(self.status_label)
def copy_command(self, command=None, command_box=None):
if command_box is None:
command_box = self.command_box
clipboard = QApplication.clipboard()
if command:
clipboard.setText(command)
else:
clipboard.setText(self.manual_install_command)
original_style = command_box.styleSheet()
command_box.setStyleSheet(original_style + "background-color: #27ae60;")
QTimer.singleShot(200, lambda: command_box.setStyleSheet(original_style))
self.update_status.update_status("Command copied to clipboard")
def install_package(self):
if not self.install_command:
self.update_status.update_status(f"Installation not supported for {self.current_distro}")
return
if subprocess.getstatusoutput('pkexec --help')[0] == 127:
self.update_status.update_status("polkit toolkit is not installed")
self.install_button.setDisabled(True)
return
self.worker_thread = WorkerThread('INSTALL_PACKAGE',
package_command=self.install_command,
package_name=self.package_name)
self.worker_thread.text_output.connect(self.update_status.update_status)
self.worker_thread.finished.connect(self.installation_complete)
self.worker_thread.start()
def installation_complete(self, success):
if success:
self.check_package()
def check_package(self):
try:
if self.package_name.lower() == 'all':
packages = ['bwrap', 'ip', 'microsocks', 'proxychains4', 'ratpoison', 'tor', 'Xephyr', 'wg', 'resolvconf']
for package in packages:
if subprocess.getstatusoutput(f'{package} --help')[0] == 127:
self.status_label.setText(f"{package} is not installed")
self.status_label.setStyleSheet("color: red;")
return
self.status_label.setText("All packages installed!")
self.status_label.setStyleSheet("color: green;")
elif self.package_name.lower() == 'wireguard':
self.install_name = 'wg'
elif self.package_name.lower() == 'bubblewrap':
self.install_name = 'bwrap'
elif self.package_name.lower() == 'xserver-xephyr':
self.install_name = 'Xephyr'
else:
self.install_name = self.package_name
if subprocess.getstatusoutput(f'{self.install_name.lower()} --help')[0] == 127:
self.status_label.setText(f"{self.package_name} is not installed")
self.status_label.setStyleSheet("color: red;")
else:
self.status_label.setText(f"{self.package_name} is installed!")
self.status_label.setStyleSheet("color: green;")
except Exception:
self.status_label.setText("Check failed")
self.status_label.setStyleSheet("color: red;")
def go_next(self):
self.page_stack.setCurrentIndex(self.page_stack.indexOf(self.page_stack.findChild(MenuPage)))
class WelcomePage(Page):
def __init__(self, page_stack, main_window=None, parent=None):
super().__init__("Welcome", page_stack, main_window, parent)
self.btn_path = main_window.btn_path
self.update_status = main_window
self.ui_elements = []
self.button_next.clicked.connect(self.go_to_install)
self.button_next.setVisible(True)
self._setup_welcome_ui()
self._setup_stats_display()
def _setup_welcome_ui(self):
welcome_title = QLabel("Welcome to HydraVeilVPN", self)
welcome_title.setGeometry(20, 45, 760, 60)
welcome_title.setStyleSheet("""
font-size: 32px;
font-weight: bold;
color: cyan;
""")
welcome_title.setAlignment(Qt.AlignmentFlag.AlignCenter)
welcome_msg = QLabel(
"Before we begin your journey, we need to set up a few essential components. "
"Click 'Next' to take you to the installation page.", self)
welcome_msg.setGeometry(40, 100, 720, 80)
welcome_msg.setWordWrap(True)
welcome_msg.setStyleSheet("""
font-size: 16px;
color: cyan;
""")
welcome_msg.setAlignment(Qt.AlignmentFlag.AlignCenter)
def _setup_stats_display(self):
stats_container = QWidget(self)
stats_container.setGeometry(70, 200, 730, 240)
stats_title = QLabel(stats_container)
stats_title.setText("Features & Services")
stats_title.setGeometry(190, 10, 300, 30)
stats_title.setStyleSheet("""
font-size: 22px;
font-weight: bold;
color: cyan;
""")
grid_widget = QWidget(stats_container)
grid_widget.setGeometry(0, 70, 700, 190)
grid_layout = QGridLayout(grid_widget)
grid_layout.setSpacing(30)
stats = [
("Browsers", "browsers_mini.png", "10 Supported Browsers", True),
("WireGuard", "wireguard_mini.png", "6 Global Locations", True),
("Proxy", "just proxy_mini.png", "5 Proxy Locations", True),
("Tor", "toricon_mini.png", "Tor Network Access", True)
]
for i, (title, icon_name, count, available) in enumerate(stats):
row = i // 2
col = i % 2
stat_widget = QWidget()
stat_layout = QHBoxLayout(stat_widget)
stat_layout.setContentsMargins(20, 10, 2, 10)
stat_layout.setSpacing(15)
icon_label = QLabel()
icon_path = os.path.join(self.btn_path, icon_name)
icon_label.setPixmap(QPixmap(icon_path).scaled(30, 30, Qt.AspectRatioMode.KeepAspectRatio, Qt.TransformationMode.SmoothTransformation))
icon_label.setFixedSize(30, 30)
stat_layout.addWidget(icon_label)
text_widget = QWidget()
text_layout = QVBoxLayout(text_widget)
text_layout.setSpacing(5)
text_layout.setContentsMargins(0, 0, 30, 0)
title_widget = QWidget()
title_layout = QHBoxLayout(title_widget)
title_layout.setContentsMargins(0, 0, 0, 0)
title_layout.setSpacing(10)
title_label = QLabel(title)
title_label.setStyleSheet("font-size: 16px; font-weight: bold; color: #2c3e50;")
title_layout.addWidget(title_label)
status_indicator = QLabel("")
status_indicator.setStyleSheet("color: #2ecc71; font-size: 16px;")
title_layout.addWidget(status_indicator)
title_layout.addStretch()
text_layout.addWidget(title_widget)
count_label = QLabel(count)
count_label.setStyleSheet("color: #34495e;")
text_layout.addWidget(count_label)
stat_layout.addWidget(text_widget, stretch=1)
stat_widget.setStyleSheet("""
QWidget {
background-color: transparent;
border-radius: 10px;
}
""")
grid_layout.addWidget(stat_widget, row, col)
def go_to_install(self):
install_page = self.page_stack.findChild(InstallSystemPackage)
install_page.configure(package_name='all', distro='debian')
self.page_stack.setCurrentIndex(self.page_stack.indexOf(install_page))
class ProtocolPage(Page):
def __init__(self, page_stack, main_window=None, parent=None):
super().__init__("Protocol", page_stack,main_window, parent)
self.main_window = main_window
self.selected_protocol = None
self.btn_path = main_window.btn_path
self.selected_protocol_icon = None
self.connection_manager = main_window.connection_manager
self.button_back.setVisible(True)
self.update_status = main_window
self.button_go.clicked.connect(self.go_selected)
self.coming_soon_label = QLabel("Coming soon", self)
self.coming_soon_label.setGeometry(210, 50, 200, 40)
self.coming_soon_label.setStyleSheet("font-size: 22px;")
self.sync_label = QLabel("Sync finds out what plans, countries, \n& browsers are available", self)
self.sync_label.setGeometry(100, 120,500,60)
self.sync_label.setStyleSheet("font-size: 20px;")
self.button_sync = QPushButton(self)
self.button_sync.setObjectName("sync_button")
self.button_sync.setGeometry(100, 250,150,60)
self.button_sync.setIconSize(self.button_sync.size())
icon = QIcon(os.path.join(self.btn_path, "sync_button.png"))
self.button_sync.setIcon(icon)
self.button_sync.clicked.connect(self.handle_core_action_sync_button)
self.tor_button_sync = QPushButton(self)
self.tor_button_sync.setObjectName("tor_sync")
self.tor_button_sync.setGeometry(300, 250,150,60)
self.tor_button_sync.setIconSize(self.tor_button_sync.size())
tor_icon = QIcon(os.path.join(self.btn_path, "tor_sync.png"))
self.tor_button_sync.setIcon(tor_icon)
self.tor_button_sync.clicked.connect(lambda: self.handle_core_action_sync_button(True))
self.button_sync.setVisible(True)
self.tor_button_sync.setVisible(True)
self.coming_soon_label.setVisible(False)
self.title.setGeometry(585, 40, 185, 40); self.title.setText("Pick a Protocol")
self.display.setGeometry(QtCore.QRect(0, 50, 580, 435))#relacion 4:3
self.create_interface_elements()
def sync_button(self, toggle: bool, sync_complete: bool=False):
self.button_sync.setDisabled(toggle)
self.tor_button_sync.setDisabled(toggle)
if sync_complete:
for button in self.buttons:
button.setDisabled(toggle)
self.button_sync.setVisible(False)
self.tor_button_sync.setVisible(False)
self.sync_label.setVisible(False)
def handle_core_action_sync_button(self, tor: bool = False):
self.sync_button(True)
if tor:
self.main_window.set_toggle_state(True)
self.sync_button(True)
self.update_status.update_status('Syncing through Tor in progress...')
self.worker_thread = WorkerThread('SYNC_TOR')
self.worker_thread.sync_output.connect(self.update_output)
self.worker_thread.text_output.connect(self.update_text_output)
self.worker_thread.start()
return
self.update_status.update_status('Syncing in progress...')
self.worker_thread = WorkerThread('SYNC')
self.worker_thread.sync_output.connect(self.update_output)
self.worker_thread.start()
def update_text_output(self, text):
self.update_status.update_status(text)
def update_output(self, available_locations, status, is_tor, locations, is_os_error):
if is_os_error:
install_page = self.page_stack.findChild(InstallSystemPackage)
install_page.configure(package_name='tor', distro='debian', is_sync=True)
self.page_stack.setCurrentIndex(self.page_stack.indexOf(install_page))
self.sync_button(False)
return
if status is False:
self.sync_button(False)
self.update_status.update_status('An error occurred during sync')
return
self.update_status.update_status('Sync complete')
self.connection_manager.set_synced(True)
available_locations_list = []
self.connection_manager.store_locations(locations)
grid_positions = [
(395, 90, 185, 75),
(585, 90, 185, 75),
(395, 195, 185, 75),
(585, 195, 185, 75),
(395, 300, 185, 75),
(585, 300, 185, 75),
(395, 405, 185, 75),
(585, 405, 185, 75)
]
list1 = []
for i, location in enumerate(available_locations):
position_index = i % len(grid_positions)
list1.append((QPushButton, location, grid_positions[position_index]))
for item in list1:
available_locations_list.append(item)
self.sync_button(False, True)
location_page = self.find_location_page()
hidetor_page = self.find_hidetor_page()
print(f' the available locations list is {available_locations_list}')
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"button_{icon_name}.png")))
if boton.icon().isNull():
boton.setPixmap(QPixmap(os.path.join(self.btn_path, "default_location_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'hdtor_{location}'
self.display.setPixmap(QPixmap(os.path.join(self.btn_path, f"{tor_hide_img}.png")).scaled(self.display.size(), Qt.AspectRatioMode.KeepAspectRatio))
self.selected_location_icon = location
self.button_next.setVisible(True)
self.update_swarp_json()
def reverse(self):
self.page_stack.setCurrentIndex(self.page_stack.indexOf(self.page_stack.findChild(ProtocolPage)))
def go_selected(self):
self.page_stack.setCurrentIndex(self.page_stack.indexOf(self.page_stack.findChild(BrowserPage)))
class ResidentialPage(Page):
def __init__(self, page_stack, main_window, parent=None):
super().__init__("Wireguard", page_stack, main_window, parent)
self.title.setGeometry(585, 40, 185, 40); self.title.setText("Pick a Protocol")
self.update_status = main_window
self.button_reverse.setVisible(True)
self.button_reverse.clicked.connect(self.reverse)
self.button_go.clicked.connect(self.go_selected)
self.display_1 = QLabel(self)
self.display_1.setGeometry(QtCore.QRect(15, 50, 550, 465))#relacion 4:3
self.display_1.setPixmap(QPixmap(os.path.join(self.btn_path, "browser only.png")))
self.label = QLabel(self)
self.label.setGeometry(440, 370, 86,130)
self.label.setPixmap(QPixmap(os.path.join(self.btn_path, "tor 86x130.png")))
self.label.hide()
def showEvent(self, event):
super().showEvent(event)
self.create_interface_elements()
def create_interface_elements(self):
self.buttonGroup = QButtonGroup(self)
self.buttons = []
for j, (object_type, icon_name, page_class, geometry) in enumerate([
(QPushButton, "tor", TorPage, (585, 90, 185, 75)),
(QPushButton, "just proxy", TorPage, (585, 90+30+75+30+75, 185, 75)),
(QLabel, None, None, (570, 170, 210, 105)),
(QLabel, None, None, (570, 385, 210, 115))
]):
if object_type == QPushButton:
boton = object_type(self)
boton.setGeometry(*geometry)
boton.setIconSize(boton.size())
boton.setCheckable(True)
boton.setIcon(QIcon(os.path.join(self.btn_path, f"{icon_name}_button.png")))
self.buttons.append(boton)
self.buttonGroup.addButton(boton, j)
boton.show()
# Conectar el botón a la función show_residential
boton.clicked.connect(lambda checked,page=page_class, name=icon_name: self.show_residential(name,page))
elif object_type == QLabel:
label = object_type(self)
label.setGeometry(*geometry)
label.setAlignment(QtCore.Qt.AlignmentFlag.AlignCenter)
label.setWordWrap(True)
label.show()
if geometry == (570, 170, 210, 105):
text1 = "Connect to tor first\nThen the residential proxy\nThis hides Tor from websites"
label.setText(text1)
elif geometry == (570, 385, 210, 115):
text2 = "Connect directly\nTo the Proxy\nin a browser\nThis has no encryption"
label.setText(text2)
def show_residential(self, icon_name,page_class):
self.selected_page_class = page_class
if icon_name == "tor":
self.label.show()
if icon_name == "just proxy":
self.label.hide()
self.button_go.setVisible(True)
self.update_swarp_json(icon_name)
def go_selected(self):
if self.selected_page_class:
selected_page = self.selected_page_class
self.page_stack.setCurrentIndex(self.page_stack.indexOf(self.page_stack.findChild(selected_page)))
for boton in self.buttons:
boton.setChecked(False)
self.button_go.setVisible(False)
def update_swarp_json(self, icon_name):
inserted_data = {
"connection": icon_name
}
self.update_status.write_data(inserted_data)
def reverse(self):
self.page_stack.setCurrentIndex(self.page_stack.indexOf(self.page_stack.findChild(ProtocolPage)))
class TorPage(Page):
def __init__(self, page_stack, main_window, parent=None):
super().__init__("TorPage", page_stack, main_window, parent)
self.update_status = main_window
self.button_back.setVisible(True)
self.title.setGeometry(585, 40, 185, 40); self.title.setText("Pick a Country")
self.display.setGeometry(QtCore.QRect(0, 100, 540, 405))#relacion 4:3
self.display0=QLabel(self)
self.display0.setGeometry(QtCore.QRect(0, 50, 600, 465))#relacion 4:3
self.display0.setPixmap(QPixmap(os.path.join(self.btn_path, "browser only.png")))
self.display0.lower() # Colocar display2 debajo de otros widgets
self.button_go.clicked.connect(self.go_selected)
self.button_reverse.setVisible(True)
self.button_reverse.clicked.connect(self.reverse_selected)
self.label = QLabel(self)
self.label.setGeometry(440, 370, 86,130)
pixmap = QPixmap(os.path.join(self.btn_path, "tor 86x130.png")) # Reemplaza "ruta/a/la/imagen.png" por la ruta de tu imagen
self.label.setPixmap(pixmap)
self.label.hide()
def showEvent(self, event):
super().showEvent(event)
self.extraccion()
def extraccion(self):
self.data_profile = {}
profile = self.update_status.read_data()
profile_1 = profile.get('Profile_1')
self.data_profile['connection'] = profile_1.get('connection')
for key, value in self.data_profile.items():
print(f"{key}: {value}")
self.verificate(value)
def verificate(self, value):
# Verificar el valor de key
if value == "just proxy":
self.display0.show()
self.label.hide()
elif value == "tor":
self.display0.show()
self.label.show() # Colocar display2 debajo de otros widgets
else:
pass
self.buttonGroup = QButtonGroup(self)
self.buttons = []
'''
for j, (object_type, icon_name, geometry) in enumerate([
(QPushButton, "brazil", (585, 90, 185, 75)),
(QPushButton, "germany", (585, 170, 185, 75)),
(QPushButton, "eeuu", (585, 250, 185, 75)),
(QPushButton, "united kingdom", (585, 330, 185, 75)),
(QPushButton, "hong kong", (585, 410, 185, 75))
]):
boton = object_type(self)
boton.setGeometry(*geometry)
boton.setIconSize(boton.size())
boton.setCheckable(True)
boton.setIcon(QIcon(os.path.join(self.btn_path, f"{{icon_name}}_button.png")))
self.buttons.append(boton)
self.buttonGroup.addButton(boton, j)
boton.clicked.connect(lambda _, dimentions=icon_name: self.show_dimentions(dimentions))
boton.show()
'''
def show_dimentions(self, dimentions):
self.display.setPixmap(QPixmap(os.path.join(self.btn_path, f"{dimentions} garaje.png")).scaled(self.display.size(), Qt.AspectRatioMode.KeepAspectRatio))
self.selected_dimentions_icon = dimentions
self.button_go.setVisible(True)
self.update_swarp_json()
def update_swarp_json(self):
inserted_data = {
"country_garaje": self.selected_dimentions_icon
}
self.update_status.write_data(inserted_data)
def reverse_selected(self):
self.page_stack.setCurrentIndex(self.page_stack.indexOf(self.page_stack.findChild(ResidentialPage)))
def go_selected(self):
self.page_stack.setCurrentIndex(self.page_stack.indexOf(self.page_stack.findChild(BrowserPage)))
self.display.clear()
for boton in self.buttons:
boton.setChecked(False)
self.button_go.setVisible(False)
class TorLocationPage(Page):
def __init__(self, page_stack, parent=None):
super().__init__("TorPage", page_stack, parent)
pass
class ConnectionPage(Page):
def __init__(self, page_stack, parent=None):
super().__init__("Connection", page_stack, parent)
self.create_interface_elements()
def create_interface_elements(self):
self.display.setGeometry(QtCore.QRect(5, 50, 390, 430))
self.title.setGeometry(QtCore.QRect(465, 40, 300, 20))
self.title.setText("Pick a TOR")
labels_info = [
("server", None, (410, 115)),
("", "rr2", (560, 115)),
("port", None, (410, 205)),
("", "rr2", (560, 205)),
("username",None, (410, 295)),
("", "rr2", (560, 295)),
("password",None, (410, 385)),
("", "rr2", (560, 385))
]
for j, (text, image_name, position) in enumerate(labels_info):
label = QLabel(self)
label.setAlignment(QtCore.Qt.AlignmentFlag.AlignCenter)
if image_name: # Si hay un nombre de imagen, usar el tamaño predeterminado (220x50)
label.setGeometry(position[0], position[1], 220, 50)
else: # Si no hay una imagen, usar el tamaño personalizado (130x30)
label.setGeometry(position[0], position[1], 140, 50)
label.setStyleSheet("background-color: rgba(255, 255, 255, 51); padding: 3px;")
if image_name: # Si hay un nombre de imagen, crear una QLabel con la imagen
pixmap = QPixmap(os.path.join(self.btn_path, f"{image_name}.png"))
label.setPixmap(pixmap)
label.setScaledContents(True) # Escalar contenido para ajustarlo al tamaño del QLabel
else: # Si no hay una imagen, crear una QLabel con el texto
label.setText(text)
class LocationPage(Page):
def __init__(self, page_stack, main_window, parent=None):
super().__init__("Location", page_stack, main_window, parent)
self.selected_location_icon = None
self.update_status = main_window
self.button_reverse.setVisible(True)
self.button_reverse.clicked.connect(self.reverse)
self.display.setGeometry(QtCore.QRect(5, 10, 390, 520))
self.title.setGeometry(395, 40, 380, 40); self.title.setText("Pick a location")
# Add initial display
self.initial_display = QLabel(self)
self.initial_display.setGeometry(5, 22, 355, 485)
self.initial_display.setAlignment(Qt.AlignmentFlag.AlignCenter)
self.initial_display.setWordWrap(True)
def showEvent(self, event):
super().showEvent(event)
self.button_next.setVisible(False)
for button in self.buttons:
button.setChecked(False)
def create_interface_elements(self, available_locations):
print(available_locations)
self.buttonGroup = QButtonGroup(self)
self.buttons = []
for j, (object_type, icon_name, geometry) in enumerate(available_locations):
print(object_type, icon_name, geometry)
boton = object_type(self)
boton.setGeometry(*geometry)
boton.setIconSize(boton.size())
boton.setCheckable(True)
boton.setIcon(QIcon(os.path.join(self.btn_path, f"button_{icon_name}.png")))
if boton.icon().isNull():
fallback_path = os.path.join(self.btn_path, "default_location_button.png")
boton.setIcon(QIcon(fallback_path))
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"icon_{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))
if label.pixmap().isNull():
label.setPixmap(QPixmap(os.path.join(self.btn_path, "default_location_button.png")))
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':
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)
elif item == 'location':
icon_path = os.path.join(self.btn_path, f"button_{text}.png")
geometry = (585, initial_y + i * label_height, 185, 75)
parent_label = QLabel(self)
parent_label.setGeometry(*geometry)
parent_label.setPixmap(QPixmap(icon_path))
if parent_label.pixmap().isNull():
parent_label.setPixmap(QPixmap(os.path.join(self.btn_path, "default_location_button.png")))
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/hdtor_{location}.png'
else:
image_path = os.path.join(self.btn_path, "browser only.png")
label_background = QLabel(self)
label_background.setGeometry(10, 50, 535, 460)
label_background.setPixmap(QPixmap(image_path))
label_background.setScaledContents(True)
label_background.show()
label_background.lower()
self.labels_creados.append(label_background)
image_path = os.path.join(self.btn_path, f"{profile_1.get('country_garaje', '')} garaje.png")
main_label = QLabel(self)
main_label.setGeometry(10, 105, 530, 398)
main_label.setPixmap(QPixmap(image_path))
main_label.setScaledContents(True)
main_label.show()
self.labels_creados.append(main_label)
image_path =os.path.join(self.btn_path, "tor 86x130.png")
tor_label = QLabel(self)
tor_label.setGeometry(450, 380, 86, 130)
tor_label.setPixmap(QPixmap(image_path))
tor_label.setScaledContents(True)
tor_label.show()
self.labels_creados.append(tor_label)
if profile_1.get("connection", "") == "browser-only":
image_path = os.path.join(self.btn_path, "browser only.png")
label_background = QLabel(self)
label_background.setGeometry(10, 50, 535, 460)
label_background.setPixmap(QPixmap(image_path))
label_background.setScaledContents(True)
label_background.show()
label_background.lower()
self.labels_creados.append(label_background)
image_path = os.path.join(self.btn_path, f"{profile_1.get('protocol', '')} {profile_1.get('location', '')}.png")
main_label = QLabel(self)
main_label.setGeometry(10, 130, 500, 375)
main_label.setPixmap(QPixmap(image_path))
main_label.setScaledContents(True)
main_label.show()
self.labels_creados.append(main_label)
else:
image_path = os.path.join(self.btn_path, f"{profile_1.get('protocol', '')} {profile_1.get('location', '')}.png")
main_label = QLabel(self)
main_label.setGeometry(10, 130, 500, 375)
main_label.setPixmap(QPixmap(image_path))
main_label.setScaledContents(True)
main_label.show()
self.labels_creados.append(main_label)
if hasattr(self, 'arrow_label'):
self.arrow_label.raise_()
def toggle_button_visibility(self):
self.button_go.setVisible(bool(self.line_edit.text()))
def find_menu_page(self):
# Method to find the MenuPage instance
for i in range(self.page_stack.count()):
page = self.page_stack.widget(i)
if isinstance(page, MenuPage):
return page
return None
def copy_profile(self):
# Obtener el nombre del perfil ingresado en el QLineEdit
profile_name = self.line_edit.text()
menu_page = self.find_menu_page()
if menu_page:
number_of_profiles = menu_page.number_of_profiles
# Abrir y cargar los datos de swarp.json
profile_data = self.update_status.read_data()
# Verificar si los campos necesarios están llenos
required_fields = [profile_data.get("protocol"), profile_name]
if not all(required_fields):
print("Error: Some required fields are empty!")
return
# Actualizar el nombre del perfil con el texto ingresado
profile_data["name"] = profile_name
profile_data["visible"] = "yes"
#profile_id = int(number_of_profiles) + 1
profiles = ProfileController.get_all()
profile_id = self.get_next_available_id(profiles)
new_profile = profile_data
self.create_core_profiles(new_profile, profile_id)
# Restablecer el índice de la página actual al menú principal
self.page_stack.setCurrentIndex(self.page_stack.indexOf(self.page_stack.findChild(MenuPage)))
self.update_status.clear_data()
# Limpiar el QLineEdit
self.line_edit.clear()
self.display.clear()
self.button_go.setVisible(False)
def get_next_available_id(self, profiles: dict) -> int:
if not profiles:
return 1
existing_ids = sorted(profiles.keys())
for i in range(1, max(existing_ids) + 2):
if i not in existing_ids:
return i
return None
def create_core_profiles(self, profile, id):
if profile.get('connection') != 'system-wide':
browser_info = profile.get('browser', '').split()
if len(browser_info) < 2:
self.update_status.update_status('Application version not supported')
return
application = f"{browser_info[0].lower()}:{browser_info[1]}"
else:
application = ''
print(profile.get('location'))
parts = profile.get('location').split('_')
country_code = parts[0]
location_code = parts[1]
if profile.get('protocol') == 'wireguard':
connection_type = 'wireguard'
elif profile.get('protocol') == 'residential':
if profile.get('connection') == 'tor':
connection_type = 'tor'
elif profile.get('connection') == 'just proxy':
connection_type = 'system'
elif profile.get('protocol') == 'hidetor':
connection_type = 'tor'
else:
self.update_status.update_status('Connection type not supported')
return
profile_data = {
'id': int(id),
'name': profile.get('name'),
'country_code': country_code,
'code': location_code,
'application': application,
'connection_type': connection_type,
'resolution': profile.get('dimentions', ''),
}
profile_type = 'system' if profile.get('connection') == 'system-wide' else 'session'
action = f'CREATE_{profile_type.upper()}_PROFILE'
self.handle_core_action_create_profile(action, profile_data, profile_type)
def eliminacion(self):
for label in self.labels_creados:
label.deleteLater()
# Limpia la lista de referencia
self.labels_creados = []
self.create()
if hasattr(self, 'arrow_label'):
self.arrow_label.show()
self.arrow_label.raise_()
def handle_core_action_create_profile(self, action, profile_data=None, profile_type=None):
self.worker_thread = WorkerThread(action, profile_data, profile_type)
self.worker_thread.text_output.connect(self.update_output)
self.worker_thread.start()
self.worker_thread.wait()
def update_output(self, text):
self.update_status.update_status(text)
def on_profile_creation(self, result):
if self.worker_thread:
self.worker_thread.quit()
self.worker_thread.wait()
self.worker_thread = None
class EditorPage(Page):
def __init__(self, page_stack, main_window):
super().__init__("Editor", page_stack, main_window)
self.page_stack = page_stack
self.update_status = main_window
self.connection_manager = main_window.connection_manager
self.labels = [] # Lista para almacenar los labels dinámicamente
self.buttons = [] # Lista para almacenar los labels dinámicamente
self.title.setGeometry(570, 40, 185, 40); self.title.setText("Edit Profile")
self.button_apply.setVisible(True)
self.button_apply.clicked.connect(self.go_selected)
self.button_back.setVisible(True)
self.button_back.clicked.connect(self.go_back)
self.name_handle = QLabel(self)
self.name_handle.setGeometry(125, 70, 400, 30)
self.name_handle.setText("Profile Name:")
self.name = QLineEdit(self)
self.name.setGeometry(250, 68, 400, 30)
self.name.setStyleSheet("border: transparent;")
self.temp_changes = {}
self.original_values = {}
self.display.setGeometry(QtCore.QRect(0, 60, 540, 405))
self.display.hide()
self.garaje=QLabel(self)
self.garaje.setGeometry(QtCore.QRect(0, 70, 540, 460))
self.brow_disp=QLabel(self)
self.brow_disp.setGeometry(QtCore.QRect(0, 50, 540, 460))
self.brow_disp.setPixmap(QPixmap(os.path.join(self.btn_path, "browser only.png")))
self.brow_disp.hide()
self.brow_disp.lower()
def update_name_value(self):
original_name = self.data_profile.get('name', '')
new_name = self.name.text()
if original_name != new_name:
self.update_temp_value('name', new_name)
else:
pass
def go_back(self):
selected_profiles_str = ', '.join([f"Profile_{profile['profile_number']}" for profile in self.page_stack.widget(0).selected_profiles])
if self.has_unsaved_changes(selected_profiles_str):
self.show_unsaved_changes_popup()
else:
self.page_stack.setCurrentIndex(self.page_stack.indexOf(self.page_stack.findChild(MenuPage)))
def find_menu_page(self):
for i in range(self.page_stack.count()):
page = self.page_stack.widget(i)
if isinstance(page, MenuPage):
return page
return None
def find_resume_page(self):
for i in range(self.page_stack.count()):
page = self.page_stack.widget(i)
if isinstance(page, ResumePage):
return page
return None
def find_protocol_page(self):
for i in range(self.page_stack.count()):
page = self.page_stack.widget(i)
if isinstance(page, ProtocolPage):
return page
return None
def showEvent(self, event):
super().showEvent(event)
self.extraccion()
def extraccion(self):
self.data_profile = {}
for label in self.labels:
label.deleteLater()
self.labels = []
for button in self.buttons:
button.deleteLater()
self.buttons = []
selected_profiles_str = ', '.join([f"Profile_{profile['profile_number']}" for profile in self.page_stack.widget(0).selected_profiles])
menu_page = self.find_menu_page()
if menu_page:
new_profiles = ProfileController.get_all()
self.profiles_data = menu_page.match_core_profiles(profiles_dict=new_profiles)
self.data_profile = self.profiles_data[selected_profiles_str].copy()
if selected_profiles_str in self.temp_changes:
for key, value in self.temp_changes[selected_profiles_str].items():
self.data_profile[key] = value
self.name.textChanged.connect(self.update_name_value)
self.verificate(self.data_profile, selected_profiles_str)
def verificate(self, data_profile, selected_profile_str):
protocol = data_profile.get("protocol", "")
try:
if protocol == "wireguard":
self.process_and_show_labels(data_profile, {
"protocol": ['wireguard', 'residential', 'hidetor'],
"connection": ['browser-only', 'system-wide'],
"location": self.connection_manager.get_location_list(),
"browser": ['brave 1.63.165','brave 1.70.123', 'firefox 123.0', 'firefox 131.0.2', 'chromium 122.0.6261.94-1', 'chromium 129.0.6668.89-1', 'librewolf 125.0.2-1', 'librewolf 130.0.1-1', 'firefox 128.0.2', 'brave 1.71.121'],
"dimentions": ['800x600', '1024x760', '1152x1080', '1280x1024', '1920x1080']
}, selected_profile_str)
elif protocol == "residential" or protocol == "hidetor":
self.process_and_show_labels(data_profile, {
"protocol": ['residential', 'wireguard', 'hidetor'],
"connection": ['tor', 'just proxy'],
"location": self.connection_manager.get_non_residential_proxy_list(),
"browser": ['brave 1.63.165','brave 1.70.123', 'firefox 123.0', 'firefox 131.0.2', 'chromium 122.0.6261.94-1', 'chromium 129.0.6668.89-1', 'librewolf 125.0.2-1', 'librewolf 130.0.1-1', 'firefox 128.0.2', 'brave 1.71.121'],
"dimentions": ['800x600', '1024x760', '1152x1080', '1280x1024', '1920x1080']
}, selected_profile_str)
elif protocol == "lokinet":
self.process_and_show_labels(data_profile, {
"protocol": ['lokinet']
}, selected_profile_str)
elif protocol == "open":
self.process_and_show_labels(data_profile, {
"protocol": ['open']
}, selected_profile_str)
except Exception as e:
print(f'An error occurred: {e}')
def process_and_show_labels(self, data_profile, parameters, selected_profile_str):
protocol = data_profile.get('protocol', "")
connection= data_profile.get('connection', "")
location = data_profile.get('location', "")
country_garaje = location
name = data_profile.get('name', "")
self.name.setText(name)
if protocol=="hidetor":
if connection == "just proxy":
self.display.setPixmap(QPixmap(os.path.join(self.btn_path, f"icon_{location}.png")))
else:
self.display.setPixmap(QPixmap(os.path.join(self.btn_path, f"hdtor_{location}.png")))
self.display.show()
self.display.setGeometry(0, 100, 400, 394)
self.display.setScaledContents(True)
self.garaje.hide()
self.brow_disp.hide()
if protocol=="wireguard":
self.display.setGeometry(0, 60, 540, 405)
self.display.show()
self.display.setPixmap(QPixmap(os.path.join(self.btn_path, f"{protocol} {location}.png")))
self.garaje.hide()
if connection == "browser-only":
self.brow_disp.show()
else:
self.brow_disp.hide()
if protocol=="residential":
self.display.setGeometry(0, 60, 540, 405)
self.brow_disp.show()
self.garaje.show()
self.display.hide()
if country_garaje:
country_garaje = 'brazil'
self.garaje.setPixmap(QPixmap(os.path.join(self.btn_path, f"{country_garaje} garaje.png")))
for i, key in enumerate(parameters.keys()):
if key == 'browser':
current_value = f"{data_profile.get(key, '')}"
split_value = current_value.split(' ', 1)
browser_version = split_value[1] if len(split_value) > 1 else ''
if browser_version == '':
browser_version = data_profile.get('browser_version', '')
current_value = f"{data_profile.get(key, '')} {browser_version}"
if connection == 'system-wide' or not current_value.strip():
base_image = QPixmap()
else:
base_image = BrowserPage.create_browser_button_image(current_value, self.btn_path)
elif key == 'location':
image_path = os.path.join(self.btn_path, f"button_{data_profile.get(key, '')}.png")
base_image = QPixmap(image_path)
if base_image.isNull():
base_image = QPixmap(os.path.join(self.btn_path, "default_location_button.png"))
else:
image_path = os.path.join(self.btn_path, f"{data_profile.get(key, '')}_button.png")
current_value = data_profile.get(key, '')
base_image = QPixmap(image_path)
label = QLabel(key, self)
label.setGeometry(565, 90 + i * 80, 185, 75)
label.setPixmap(base_image)
label.setScaledContents(True)
label.show()
self.labels.append(label)
try:
value_index = parameters[key].index(current_value)
except ValueError:
value_index = 0
# Botón para mostrar el valor anterior
prev_button = QPushButton(self)
prev_button.setGeometry(535, 90 + i * 80, 30, 75)
prev_button.clicked.connect(lambda _, k=key, idx=value_index: self.show_previous_value(k, idx, parameters))
prev_button.show()
icon_path = os.path.join(self.btn_path, f"UP_button.png")
icon = QPixmap(icon_path)
transform = QTransform().rotate(180)
rotated_icon = icon.transformed(transform)
# Establecer el ícono girado en el botón
prev_button.setIcon(QIcon(rotated_icon))
prev_button.setIconSize(prev_button.size())
self.buttons.append(prev_button)
# Botón para mostrar el valor siguiente
next_button = QPushButton( self)
next_button.setGeometry(750, 90 + i * 80, 30, 75)
next_button.clicked.connect(lambda _, k=key, idx=value_index: self.show_next_value(k, idx, parameters))
next_button.show()
self.buttons.append(next_button)
next_button.setIcon(QIcon( os.path.join(self.btn_path, f"UP_button.png")))
next_button.setIconSize(next_button.size())
prev_button.setVisible(True)
next_button.setVisible(True)
if key == 'protocol' or (protocol == 'wireguard' and key == 'connection'):
prev_button.setDisabled(True)
next_button.setDisabled(True)
if connection == 'system-wide' and key == 'location':
prev_button.setDisabled(True)
next_button.setDisabled(True)
if connection == 'system-wide' and (key == 'browser' or key == 'dimentions'):
prev_button.setVisible(False)
next_button.setVisible(False)
def show_previous_value(self, key: str, index: int, parameters: dict) -> None:
if len(parameters[key]) > 1:
previous_index = (index - 1) % len(parameters[key])
previous_value = parameters[key][previous_index]
self.update_temp_value(key, previous_value)
def show_next_value(self, key: str, index: int, parameters: dict) -> None:
if len(parameters[key]) > 1:
next_index = (index + 1) % len(parameters[key])
next_value = parameters[key][next_index]
self.update_temp_value(key, next_value)
def update_temp_value(self, key: str, new_value: str) -> None:
selected_profiles_str = ', '.join([f"Profile_{profile['profile_number']}" for profile in self.page_stack.widget(0).selected_profiles])
if selected_profiles_str not in self.temp_changes:
self.temp_changes[selected_profiles_str] = {}
self.original_values[selected_profiles_str] = self.data_profile.copy()
self.temp_changes[selected_profiles_str][key] = new_value
self.extraccion()
def has_unsaved_changes(self, profile_str: str) -> bool:
return profile_str in self.temp_changes and bool(self.temp_changes[profile_str])
def show_apply_changes_popup(self, selected_profiles_str: str) -> bool:
if selected_profiles_str not in self.temp_changes:
return False
temp_changes = self.temp_changes[selected_profiles_str]
current_data = self.original_values[selected_profiles_str]
current_browser = f"{current_data.get('browser', '')} {current_data.get('browser_version', '')}"
if 'location' in temp_changes and temp_changes['location'] != current_data.get('location'):
message = "A new location would require a new subscription code.\nDo you want to proceed with erasing the old one?"
elif 'browser' in temp_changes and temp_changes['browser'] != current_browser:
message = "Changing the browser would delete all data associated with it. Proceed?"
else:
return False
self.popup = ConfirmationPopup(
self,
message=message,
action_button_text="Continue",
cancel_button_text="Cancel"
)
self.popup.setWindowModality(Qt.WindowModality.ApplicationModal)
self.popup.finished.connect(lambda result: self.handle_apply_changes_response(result))
self.popup.show()
return True
def show_unsaved_changes_popup(self) -> None:
self.popup = ConfirmationPopup(
self,
message="You have unsaved changes. Do you want to continue?",
action_button_text="Continue",
cancel_button_text="Cancel"
)
self.popup.setWindowModality(Qt.WindowModality.ApplicationModal)
self.popup.finished.connect(lambda result: self.handle_unsaved_changes_response(result))
self.popup.show()
def handle_apply_changes_response(self, result: bool) -> None:
if result:
self.commit_changes()
self.page_stack.setCurrentIndex(self.page_stack.indexOf(self.page_stack.findChild(MenuPage)))
def handle_unsaved_changes_response(self, result: bool) -> None:
if result:
self.temp_changes.clear()
self.page_stack.setCurrentIndex(self.page_stack.indexOf(self.page_stack.findChild(MenuPage)))
def go_selected(self) -> None:
selected_profiles_str = ', '.join([f"Profile_{profile['profile_number']}"
for profile in self.page_stack.widget(0).selected_profiles])
if selected_profiles_str in self.temp_changes and self.temp_changes[selected_profiles_str]:
needs_confirmation = self.show_apply_changes_popup(selected_profiles_str)
if not needs_confirmation:
self.commit_changes()
self.page_stack.setCurrentIndex(self.page_stack.indexOf(self.page_stack.findChild(MenuPage)))
else:
self.page_stack.setCurrentIndex(self.page_stack.indexOf(self.page_stack.findChild(MenuPage)))
def commit_changes(self) -> None:
selected_profiles_str = ', '.join([f"Profile_{profile['profile_number']}" for profile in self.page_stack.widget(0).selected_profiles])
if selected_profiles_str in self.temp_changes:
for key, new_value in self.temp_changes[selected_profiles_str].items():
self.update_core_profiles(key, new_value)
self.temp_changes.pop(selected_profiles_str, None)
self.original_values.pop(selected_profiles_str, None)
def update_core_profiles(self, key, new_value):
profile = ProfileController.get(int(self.update_status.current_profile_id))
self.update_res = False
if key == 'dimentions':
profile.resolution = new_value
self.update_res = True
elif key == 'name':
profile.name = new_value
elif key == 'connection':
if new_value == 'tor':
profile.connection.code = new_value
profile.connection.masked = True
elif new_value == 'just proxy':
profile.connection.code = 'system'
profile.connection.masked = True
else:
self.update_status.update_status('System wide profiles not supported atm')
elif key == 'browser':
browser_type, browser_version = new_value.split(' ', 1)
profile.application_version.application_code = browser_type
profile.application_version.version_number = browser_version
elif key == 'protocol':
if self.data_profile.get('connection') == 'system-wide':
self.edit_to_session()
else:
profile.connection.code = 'wireguard' if new_value == 'wireguard' else 'tor'
profile.connection.masked = False if new_value == 'wireguard' else True
else:
location = self.connection_manager.get_location_info(new_value)
if profile.has_wireguard_configuration():
profile.delete_wireguard_configuration()
if profile.has_proxy_configuration():
profile.delete_proxy_configuration()
if location:
profile.location.code = location.country_code
profile.location.time_zone = self.connection_manager.get_timezone(location)
profile.subscription = None
ProfileController.update(profile)
'''
def edit_to_system(self):
id = int(self.update_status.current_profile_id)
profile = self.data_profile
if profile.get('location') == 'The Netherlands':
location = 'nl'
elif profile.get('location') == 'Moldova':
location = 'md'
elif profile.get('location') == 'US (Ohio)':
location = 'us'
else:
pass
if profile.get('connection') == 'tor':
connection_type = 'tor'
elif profile.get('connection') == 'just proxy':
connection_type = 'system'
else:
connection_type = 'wireguard'
profile_data = {
'id': int(id),
'name': profile.get('name'),
'location_code': location,
'connection_type': connection_type,
}
resume_page = self.find_resume_page()
if resume_page:
resume_page.handle_core_action_create_profile('CREATE_SYSTEM_PROFILE', profile_data, 'system')
'''
def edit_to_session(self):
id = int(self.update_status.current_profile_id)
profile = self.data_profile
default_app = 'firefox:123.0'
default_resolution = '1024x760'
location_code = self.connection_manager.get_location_info(profile.get('location'))
connection_type = 'tor'
profile_data = {
'id': int(id),
'name': profile.get('name'),
'country_code': location_code.country_code,
'code': location_code.code,
'application': default_app ,
'connection_type': connection_type,
'resolution': default_resolution,
}
resume_page = self.find_resume_page()
if resume_page:
resume_page.handle_core_action_create_profile('CREATE_SESSION_PROFILE', profile_data, 'session')
class Settings(Page):
def __init__(self, page_stack, main_window, parent=None):
super().__init__("Settings", page_stack, main_window, parent)
self.update_status = main_window
self.update_logging = main_window
self.button_reverse.setVisible(True)
self.button_reverse.setEnabled(True)
self.button_reverse.clicked.connect(self.reverse)
self.title.setGeometry(585, 40, 185, 40)
self.title.setText("Settings")
self.setup_ui()
def setup_ui(self):
main_container = QWidget(self)
main_container.setGeometry(0, 0, 800, 520)
self.layout = QHBoxLayout(main_container)
self.layout.setContentsMargins(0, 60, 0, 0)
self.left_panel = QWidget()
self.left_panel.setFixedWidth(200)
self.left_layout = QVBoxLayout(self.left_panel)
self.left_layout.setContentsMargins(0, 0, 0, 0)
self.left_layout.setSpacing(0)
self.content_widget = QWidget()
self.content_layout = QStackedLayout(self.content_widget)
self.layout.addWidget(self.left_panel)
self.layout.addWidget(self.content_widget)
self.setup_menu_buttons()
self.setup_pages()
def setup_menu_buttons(self):
menu_items = [
("Overview", self.show_account_page),
("Subscriptions", self.show_subscription_page),
("Delete Profile", self.show_delete_page),
("Error Logs", self.show_logs_page)
]
self.menu_buttons = []
for text, callback in menu_items:
btn = QPushButton(text)
btn.setFixedHeight(40)
btn.setCursor(Qt.CursorShape.PointingHandCursor)
btn.setCheckable(True)
btn.clicked.connect(callback)
btn.setStyleSheet("""
QPushButton {
text-align: left;
padding-left: 20px;
border: none;
background: transparent;
color: #808080;
}
QPushButton:checked {
background: rgba(255, 255, 255, 0.1);
color: white;
border-left: 3px solid #007AFF;
}
QPushButton:hover:!checked {
background: rgba(255, 255, 255, 0.05);
}
""")
self.left_layout.addWidget(btn)
self.menu_buttons.append(btn)
self.left_layout.addStretch()
def setup_pages(self):
self.account_page = self.create_account_page()
self.subscription_page = self.create_subscription_page()
self.logs_page = self.create_logs_page()
self.delete_page = self.create_delete_page()
self.content_layout.addWidget(self.account_page)
self.content_layout.addWidget(self.wireguard_page)
self.content_layout.addWidget(self.subscription_page)
self.content_layout.addWidget(self.logs_page)
self.content_layout.addWidget(self.delete_page)
self.show_account_page()
def create_delete_page(self):
page = QWidget()
layout = QVBoxLayout(page)
layout.setContentsMargins(20, 20, 20, 20)
layout.setSpacing(15)
title = QLabel("DELETE PROFILE")
title.setStyleSheet("color: #808080; font-size: 12px; font-weight: bold;")
layout.addWidget(title)
grid = QGridLayout()
grid.setSpacing(10)
self.profile_buttons = QButtonGroup()
self.profile_buttons.setExclusive(True)
profiles = ProfileController.get_all()
for profile_id in range(1, 7):
row = (profile_id - 1) // 2
col = (profile_id - 1) % 2
profile = profiles.get(profile_id)
if profile:
btn = QPushButton(f"Profile {profile_id}\n{profile.name}")
btn.setCheckable(True)
btn.setFixedSize(180, 60)
btn.setStyleSheet("""
QPushButton {
background: rgba(255, 255, 255, 0.1);
border: none;
color: white;
border-radius: 5px;
text-align: center;
}
QPushButton:checked {
background: rgba(255, 255, 255, 0.3);
border: 2px solid #007AFF;
}
QPushButton:hover:!checked {
background: rgba(255, 255, 255, 0.2);
}
""")
self.profile_buttons.addButton(btn, profile_id)
else:
btn = QWidget()
btn.setFixedSize(180, 60)
grid.addWidget(btn, row, col)
layout.addLayout(grid)
self.delete_button = QPushButton("Delete")
self.delete_button.setEnabled(False)
self.delete_button.setFixedSize(120, 40)
self.delete_button.setStyleSheet("""
QPushButton {
background: #ff4d4d;
color: white;
border: none;
border-radius: 5px;
font-weight: bold;
}
QPushButton:disabled {
background: #666666;
}
QPushButton:hover:!disabled {
background: #ff3333;
}
""")
self.delete_button.clicked.connect(self.delete_selected_profile)
self.profile_buttons.buttonClicked.connect(self.on_profile_selected)
button_layout = QHBoxLayout()
button_layout.addStretch()
button_layout.addWidget(self.delete_button)
button_layout.addStretch()
layout.addStretch()
layout.addLayout(button_layout)
return page
def on_profile_selected(self, button):
self.delete_button.setEnabled(True)
self.selected_profile_id = self.profile_buttons.id(button)
def delete_selected_profile(self):
if hasattr(self, 'selected_profile_id'):
self.popup = ConfirmationPopup(
self,
message=f"Are you sure you want to delete Profile {self.selected_profile_id}?",
action_button_text="Delete",
cancel_button_text="Cancel"
)
self.popup.setWindowModality(Qt.WindowModality.ApplicationModal)
self.popup.finished.connect(self.handle_delete_confirmation)
self.popup.show()
def handle_delete_confirmation(self, confirmed):
if confirmed:
self.update_status.update_status(f'Deleting profile...')
self.worker_thread = WorkerThread('DESTROY_PROFILE', profile_data={'id': self.selected_profile_id})
self.worker_thread.text_output.connect(self.delete_status_update)
self.worker_thread.start()
def delete_status_update(self, message):
self.update_status.update_status(message)
self.content_layout.removeWidget(self.delete_page)
self.delete_page = self.create_delete_page()
self.content_layout.addWidget(self.delete_page)
self.content_layout.setCurrentWidget(self.delete_page)
def get_combobox_style(self) -> str:
return """
QComboBox {
color: black;
background: #f0f0f0;
padding: 5px 30px 5px 10px;
border: 1px solid #ccc;
border-radius: 4px;
min-width: 120px;
margin-bottom: 10px;
}
QComboBox:disabled {
color: #666;
background: #e0e0e0;
}
QComboBox::drop-down {
border: none;
width: 30px;
}
QComboBox::down-arrow {
image: url(assets/down_arrow.png);
width: 12px;
height: 12px;
}
QComboBox QAbstractItemView {
color: black;
background: white;
selection-background-color: #007bff;
selection-color: white;
border: 1px solid #ccc;
}
"""
def get_checkbox_style(self) -> str:
return """
QCheckBox {
color: white;
spacing: 10px;
margin-top: 10px;
margin-bottom: 10px;
}
QCheckBox::indicator {
width: 18px;
height: 18px;
}
"""
def populate_wireguard_profiles(self) -> None:
self.wireguard_profile_selector.clear()
profiles = ProfileController.get_all()
for profile_id, profile in profiles.items():
if profile.connection.code == 'wireguard':
self.wireguard_profile_selector.addItem(f"Profile {profile_id}: {profile.name}", profile_id)
def convert_duration(self, value: Union[str, int], to_hours: bool = True) -> Union[str, int]:
if to_hours:
number, unit = value.split(' ')
number = int(number)
if unit in ['day', 'days']:
return number * 24
elif unit in ['week', 'weeks']:
return number * 7 * 24
else:
raise ValueError(f"Unsupported duration unit: {unit}")
else:
hours = int(value)
if hours % (7 * 24) == 0:
weeks = hours // (7 * 24)
return f"{weeks} {'week' if weeks == 1 else 'weeks'}"
elif hours % 24 == 0:
days = hours // 24
return f"{days} {'day' if days == 1 else 'days'}"
else:
raise ValueError(f"Hours value {hours} cannot be converted to days or weeks cleanly")
def show_account_page(self):
self.content_layout.setCurrentWidget(self.account_page)
self.update_button_states(0)
def show_subscription_page(self):
self.content_layout.setCurrentWidget(self.subscription_page)
self.update_button_states(1)
def show_delete_page(self):
self.content_layout.setCurrentWidget(self.delete_page)
self.update_button_states(2)
def show_logs_page(self):
self.content_layout.setCurrentWidget(self.logs_page)
self.update_button_states(3)
def update_button_states(self, active_index):
for i, btn in enumerate(self.menu_buttons):
btn.setChecked(i == active_index)
def reverse(self):
self.page_stack.setCurrentIndex(self.page_stack.indexOf(self.page_stack.findChild(MenuPage)))
def create_subscription_page(self):
page = QWidget()
layout = QVBoxLayout(page)
layout.setSpacing(20)
layout.setContentsMargins(20, 20, 20, 20)
title = QLabel("Subscription Info")
title.setStyleSheet("color: #808080; font-size: 12px; font-weight: bold;")
layout.addWidget(title)
profile_group = QGroupBox("Profile Selection")
profile_group.setStyleSheet("""
QGroupBox {
color: white;
font-weight: bold;
border: 1px solid rgba(255, 255, 255, 0.1);
border-radius: 8px;
padding: 15px;
margin-top: 15px;
}
QGroupBox::title {
subcontrol-origin: margin;
left: 10px;
padding: 0 5px;
}
""")
profile_layout = QVBoxLayout(profile_group)
self.profile_selector = QComboBox()
self.profile_selector.setStyleSheet(self.get_combobox_style())
profiles = ProfileController.get_all()
if profiles:
for profile_id, profile in profiles.items():
self.profile_selector.addItem(f"Profile {profile_id}: {profile.name}", profile_id)
profile_layout.addWidget(self.profile_selector)
layout.addWidget(profile_group)
subscription_group = QGroupBox("Subscription Details")
subscription_group.setStyleSheet("""
QGroupBox {
color: white;
font-weight: bold;
border: 1px solid rgba(255, 255, 255, 0.1);
border-radius: 8px;
padding: 15px;
margin-top: 15px;
}
QGroupBox::title {
subcontrol-origin: margin;
left: 10px;
padding: 0 5px;
}
""")
subscription_layout = QGridLayout(subscription_group)
subscription_layout.setSpacing(10)
self.subscription_info = {}
info_items = [
("Billing Code", "billing_code"),
("Expires At", "expires_at")
]
for i, (label, key) in enumerate(info_items):
stat_widget = self.create_stat_widget(label, "N/A")
row, col = divmod(i, 2)
subscription_layout.addWidget(stat_widget, row, col)
old_label = stat_widget.findChild(QLabel, "value_label")
if old_label:
old_label.setParent(None)
old_label.deleteLater()
value_label = ClickableValueLabel("N/A", stat_widget)
value_label.setObjectName("value_label")
value_label.setStyleSheet("color: #00ffff; font-size: 13px; font-weight: bold;")
value_label.setAlignment(Qt.AlignmentFlag.AlignCenter)
stat_widget.layout().insertWidget(0, value_label)
self.subscription_info[key] = value_label
layout.addWidget(subscription_group)
layout.addStretch()
self.profile_selector.currentIndexChanged.connect(self.update_subscription_info)
if self.profile_selector.count() > 0:
self.update_subscription_info(0)
return page
def update_subscription_info(self, index):
if index < 0:
return
profile_id = self.profile_selector.itemData(index)
if profile_id is None:
return
profile = ProfileController.get(profile_id)
if profile and hasattr(profile, 'subscription') and profile.subscription:
try:
self.subscription_info["billing_code"].setText(str(profile.subscription.billing_code))
if hasattr(profile.subscription, 'expires_at') and profile.subscription.expires_at:
expires_at = profile.subscription.expires_at.strftime("%Y-%m-%d %H:%M:%S UTC")
self.subscription_info["expires_at"].setText(expires_at)
else:
self.subscription_info["expires_at"].setText("Not available")
except Exception as e:
print(f"Error updating subscription info: {e}")
else:
for label in self.subscription_info.values():
label.setText("N/A")
def showEvent(self, event):
super().showEvent(event)
current_index = self.content_layout.currentIndex()
self.content_layout.removeWidget(self.account_page)
self.account_page = self.create_account_page()
self.content_layout.addWidget(self.account_page)
self.content_layout.removeWidget(self.subscription_page)
self.subscription_page = self.create_subscription_page()
self.content_layout.addWidget(self.subscription_page)
self.content_layout.removeWidget(self.delete_page)
self.delete_page = self.create_delete_page()
self.content_layout.addWidget(self.delete_page)
self.content_layout.removeWidget(self.logs_page)
self.logs_page = self.create_logs_page()
self.content_layout.addWidget(self.logs_page)
self.content_layout.setCurrentIndex(current_index)
if self.content_layout.currentWidget() == self.subscription_page:
if self.profile_selector.count() > 0:
self.update_subscription_info(0)
def create_account_page(self):
page = QWidget()
layout = QVBoxLayout(page)
layout.setSpacing(20)
layout.setContentsMargins(20, 20, 20, 20)
title = QLabel("Account Overview")
title.setStyleSheet("color: #808080; font-size: 12px; font-weight: bold;")
layout.addWidget(title)
scroll_area = QScrollArea()
scroll_area.setWidgetResizable(True)
scroll_area.setStyleSheet("""
QScrollArea {
border: none;
background: transparent;
}
QScrollArea > QWidget > QWidget {
background: transparent;
}
""")
scroll_content = QWidget()
scroll_layout = QVBoxLayout(scroll_content)
scroll_layout.setSpacing(15)
info_sections = [
("Profile Statistics", self.create_profile_stats()),
("System Resources", self.create_system_stats())
]
for section_title, content_widget in info_sections:
group = QGroupBox(section_title)
group.setStyleSheet("""
QGroupBox {
color: white;
font-weight: bold;
border: 1px solid rgba(255, 255, 255, 0.1);
border-radius: 8px;
padding: 15px;
margin-top: 15px;
}
QGroupBox::title {
subcontrol-origin: margin;
left: 10px;
padding: 0 5px;
}
""")
group_layout = QVBoxLayout(group)
group_layout.addWidget(content_widget)
scroll_layout.addWidget(group)
scroll_area.setWidget(scroll_content)
layout.addWidget(scroll_area)
return page
def create_profile_stats(self):
widget = QWidget()
layout = QGridLayout(widget)
layout.setSpacing(10)
profiles = ProfileController.get_all()
total_profiles = len(profiles)
session_profiles = sum(1 for p in profiles.values() if isinstance(p, SessionProfile))
system_profiles = sum(1 for p in profiles.values() if isinstance(p, SystemProfile))
active_profiles = sum(
1 for p in profiles.values()
if p.subscription and p.subscription.expires_at and p.subscription.expires_at > datetime.now(timezone.utc)
)
stats = [
("Total Profiles", total_profiles),
("Browser-only", session_profiles),
("System-wide", system_profiles),
("Active Profiles", active_profiles)
]
for i, (label, value) in enumerate(stats):
stat_widget = self.create_stat_widget(label, value)
row, col = divmod(i, 2)
layout.addWidget(stat_widget, row, col)
return widget
def create_system_stats(self):
widget = QWidget()
layout = QGridLayout(widget)
layout.setSpacing(10)
config_path = Constants.HV_CONFIG_HOME
stats = [
("Config Path", config_path),
("Client Version", Constants.HV_CLIENT_VERSION_NUMBER),
("Connection", ConfigurationController.get_connection()),
]
for i, (label, value) in enumerate(stats):
info_widget = QLabel(f"{label}: {value}")
info_widget.setStyleSheet("color: white; padding: 5px;")
info_widget.setWordWrap(True)
layout.addWidget(info_widget, i, 0)
return widget
def create_stat_widget(self, label, value):
widget = QFrame()
widget.setStyleSheet("""
QFrame {
background: rgba(255, 255, 255, 0.05);
border-radius: 8px;
padding: 10px;
}
""")
layout = QVBoxLayout(widget)
value_label = QLabel(str(value))
value_label.setObjectName("value_label")
value_label.setStyleSheet("color: #00ffff; font-size: 24px; font-weight: bold;")
value_label.setAlignment(Qt.AlignmentFlag.AlignCenter)
desc_label = QLabel(label)
desc_label.setStyleSheet("color: white; font-size: 12px;")
desc_label.setAlignment(Qt.AlignmentFlag.AlignCenter)
layout.addWidget(value_label)
layout.addWidget(desc_label)
return widget
def create_horizontal_stat(self, label, value, total):
widget = QWidget()
layout = QHBoxLayout(widget)
layout.setContentsMargins(0, 5, 0, 5)
text_label = QLabel(f"{label}")
text_label.setStyleSheet("color: white; font-size: 12px;")
text_label.setFixedWidth(100)
progress = QFrame()
progress.setStyleSheet("""
QFrame {
background: rgba(0, 255, 255, 0.3);
border-radius: 3px;
}
""")
percentage = (value / total) * 100 if total > 0 else 0
progress.setFixedSize(int(200 * (percentage / 100)), 20)
value_label = QLabel(f"{value} ({percentage:.1f}%)")
value_label.setStyleSheet("color: white; font-size: 12px;")
value_label.setAlignment(Qt.AlignmentFlag.AlignRight)
progress_container = QFrame()
progress_container.setStyleSheet("""
QFrame {
background: rgba(255, 255, 255, 0.1);
border-radius: 3px;
}
""")
progress_container.setFixedSize(200, 20)
progress_layout = QHBoxLayout(progress_container)
progress_layout.setContentsMargins(0, 0, 0, 0)
progress_layout.addWidget(progress)
progress.setFixedHeight(20)
layout.addWidget(text_label)
layout.addWidget(progress_container)
layout.addWidget(value_label)
layout.addStretch()
return widget
def setup_pages(self):
self.account_page = self.create_account_page()
self.subscription_page = self.create_subscription_page()
self.logs_page = self.create_logs_page()
self.delete_page = self.create_delete_page()
self.content_layout.addWidget(self.account_page)
self.content_layout.addWidget(self.subscription_page)
self.content_layout.addWidget(self.logs_page)
self.content_layout.addWidget(self.delete_page)
self.show_account_page()
def create_logs_page(self):
page = QWidget()
layout = QVBoxLayout(page)
layout.setSpacing(20)
layout.setContentsMargins(20, 20, 20, 20)
title = QLabel("LOGGING SETTINGS")
title.setStyleSheet("color: #808080; font-size: 12px; font-weight: bold;")
layout.addWidget(title)
log_text = QLabel(f"If enabled, these Error Logs would be stored locally on your computer\nat {Constants.HV_DATA_HOME}/gui")
log_text.setStyleSheet("color: white; font-size: 14px;")
layout.addWidget(log_text)
logs_group = QGroupBox("Log Configuration")
logs_group.setStyleSheet("QGroupBox { color: white; padding: 15px; }")
logs_layout = QVBoxLayout(logs_group)
self.enable_gui_logging = QCheckBox("Enable GUI logging")
self.enable_gui_logging.setStyleSheet(self.get_checkbox_style())
logs_layout.addWidget(self.enable_gui_logging)
layout.addWidget(logs_group)
save_button = QPushButton()
save_button.setFixedSize(75, 46)
save_button.setIcon(QIcon(os.path.join(self.btn_path, f"save.png")))
save_button.setIconSize(QSize(75, 46))
save_button.clicked.connect(self.save_logs_settings)
button_layout = QHBoxLayout()
button_layout.addStretch()
button_layout.addWidget(save_button)
layout.addLayout(button_layout)
layout.addStretch()
self.load_logs_settings()
return page
def load_logs_settings(self) -> None:
try:
config = self.update_status._load_gui_config()
if config and "logging" in config:
self.enable_gui_logging.setChecked(config["logging"]["gui_logging_enabled"])
except Exception as e:
logging.error(f"Error loading logging settings: {str(e)}")
self.enable_gui_logging.setChecked(True)
def save_logs_settings(self) -> None:
try:
config = self.update_status._load_gui_config()
if config is None:
config = {
"logging": {
"gui_logging_enabled": True,
"log_level": "INFO"
}
}
config["logging"]["gui_logging_enabled"] = self.enable_gui_logging.isChecked()
self.update_status._save_gui_config(config)
if self.enable_gui_logging.isChecked():
self.update_status._setup_gui_logging()
else:
self.update_status.stop_gui_logging()
self.update_status.update_status("Logging settings saved successfully")
except Exception as e:
logging.error(f"Error saving logging settings: {str(e)}")
self.update_status.update_status("Error saving logging settings")
class IdPage(Page):
def __init__(self, page_stack, main_window=None, parent=None):
super().__init__("Id", page_stack, main_window, parent)
self.update_status = main_window
self.btn_path = main_window.btn_path
self.buttonGroup = QButtonGroup(self)
self.display.setGeometry(QtCore.QRect(-10, 50, 550, 460))
self.display.setPixmap(QPixmap(os.path.join(self.btn_path, "wireguardx.png")).scaled(self.display.size(), Qt.AspectRatioMode.KeepAspectRatio))
self.create_interface_elements()
self.connect_button = QPushButton(self)
self.connect_button.setObjectName("connect_button")
self.connect_button.setGeometry(625, 450, 90, 50)
self.connect_button.setIconSize(self.connect_button.size())
tor_icon = QIcon(os.path.join(self.btn_path, "connect.png"))
self.connect_button.setIcon(tor_icon)
self.connect_button.clicked.connect(self.on_connect)
self.connect_button.setDisabled(True)
self.button_reverse.setVisible(True)
def on_connect(self):
text = self.text_edit.toPlainText()
import re
pattern = r'^[A-Z0-9]{4}-[A-Z0-9]{4}-[A-Z0-9]{4}-[A-Z0-9]{4}$'
if re.match(pattern, text):
self.update_status.update_status('Enabling profile in progress...')
profile_data = {
'id' : int(self.update_status.current_profile_id),
'billing_code': str(text)
}
menu_page = self.find_menu_page()
if menu_page:
menu_page.enabling_profile(profile_data)
else:
self.update_text_output('Incorrect format for the billing code')
def find_menu_page(self):
for i in range(self.page_stack.count()):
page = self.page_stack.widget(i)
if isinstance(page, MenuPage):
return page
return None
def create_interface_elements(self):
self.object_selected = None
self.display.setGeometry(QtCore.QRect(5, 50, 550, 460))
self.title = QLabel("Entry Id", self)
self.title.setGeometry(QtCore.QRect(560, 50, 220, 40))
self.title.setAlignment(QtCore.Qt.AlignmentFlag.AlignCenter)
self.button_reverse.clicked.connect(self.reverse)
self.button_go.clicked.connect(self.go_selected)
objects_info = [
(QPushButton, os.path.join(self.btn_path, "new_id.png"), self.show_next, PaymentPage, (575, 100, 185, 75)),
(QLabel, os.path.join(self.btn_path, "button230x220.png"), None, None, (550, 220, 250, 220)),
(QTextEdit, None, self.validate_password, PaymentPage, (550, 230, 230, 190))
]
for obj_type, icon_name, function, page_class, geometry in objects_info:
obj = obj_type(self)
obj.setGeometry(*geometry)
if isinstance(obj, QPushButton):
obj.setIconSize(QtCore.QSize(190, 120))
obj.setIcon(QtGui.QIcon(icon_name))
obj.clicked.connect(lambda _, func=function, page=page_class: self.show_object_selected(func, page))
elif isinstance(obj, QLabel):
obj.setPixmap(QPixmap(icon_name).scaled(obj.size(), Qt.AspectRatioMode.KeepAspectRatio))
elif isinstance(obj, QTextEdit):
obj.setPlaceholderText("Or use an existing billing Id, enter it here")
obj.textChanged.connect(self.toggle_button_state)
self.text_edit = obj
def toggle_button_state(self):
text = self.text_edit.toPlainText()
if text.strip():
self.connect_button.setEnabled(True)
else:
self.connect_button.setEnabled(False)
def show_next(self):
self.text_edit.clear()
self.page_stack.setCurrentIndex(self.page_stack.indexOf(self.page_stack.findChild(PaymentPage)))
def validate_password(self):
self.button_go.setVisible(False)
text = self.text_edit.toPlainText().strip().lower()
# Comparar el texto con la palabra "zenaku" ignorando mayúsculas y minúsculas
if text == "zenaku":
self.button_go.setVisible(True)
def show_object_selected(self, function, page_class):
function()
self.object_selected = page_class
def go_selected(self):
if self.object_selected:
self.page_stack.setCurrentIndex(self.page_stack.indexOf(self.page_stack.findChild(self.object_selected)))
self.display.clear()
for boton in self.buttons:
boton.setChecked(False)
self.button_go.setVisible(False)
def reverse(self):
self.page_stack.setCurrentIndex(self.page_stack.indexOf(self.page_stack.findChild(MenuPage)))
class PaymentPage(Page):
def __init__(self, page_stack, main_window=None, parent=None):
super().__init__("Payment", page_stack, main_window, parent)
self.update_status = main_window
self.text_fields = []
self.invoice_data = {}
self.create_interface_elements()
self.button_reverse.setVisible(True)
def fetch_invoice_duration(self):
selected_time = self.get_selected_time_length()
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)
return total_hours
def check_invoice(self):
total_hours = self.fetch_invoice_duration()
if self.invoice_data and self.invoice_data['duration'] == total_hours:
self.update_status.update_status('Checking invoice status...')
self.check_invoice_status(self.invoice_data['billing_details'].billing_code)
else:
self.on_request_invoice()
def on_request_invoice(self):
total_hours = self.fetch_invoice_duration()
selected_currency = self.get_selected_currency()
if selected_currency is None:
self.update_status.update_status('No currency selected')
return
profile_data = {
'id': int(self.update_status.current_profile_id),
'duration': total_hours,
'currency': 'xmr' if selected_currency == 'monero' else 'btc' if selected_currency == 'bitcoin' else 'btc-ln' if selected_currency == 'lightning' else 'ltc' if selected_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)
self.text_fields[3].setText(text)
if 'No compatible' in text:
for line in self.text_fields:
line.setText('')
def store_invoice_data(self, billing_details: dict, duration: int):
self.invoice_data = {
'billing_details': billing_details,
'duration': duration
}
def check_invoice_status(self, billing_code: str):
self.worker_thread = WorkerThread('CHECK_INVOICE_STATUS', profile_data={'billing_code': billing_code})
self.worker_thread.invoice_finished.connect(self.on_invoice_status_finished)
self.worker_thread.start()
def on_invoice_status_finished(self, result):
if result:
self.update_status.update_status('Invoice is still valid. Parsing invoice data...')
self.parse_invoice_data(self.invoice_data['billing_details'])
else:
self.update_status.update_status('Invoice has expired. Generating new invoice...')
self.on_request_invoice()
def on_invoice_generation_finished(self, billing_details: object, text: str):
total_hours = self.fetch_invoice_duration()
self.store_invoice_data(billing_details, duration=total_hours)
self.parse_invoice_data(billing_details)
def parse_invoice_data(self, invoice_data: object):
currency = self.get_selected_currency()
billing_details = {
'billing_code': invoice_data.billing_code
}
if currency.lower() == 'lightning':
currency = 'Bitcoin Lightning'
preferred_method = next((pm for pm in invoice_data.payment_methods if pm.name.lower() == currency.lower()), None)
if preferred_method:
billing_details['due_amount'] = preferred_method.due
billing_details['address'] = preferred_method.address
else:
self.update_status.update_status('No payment method found for the selected currency.')
return
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))
def on_invoice_finished(self, result):
if result:
self.invoice_data.clear()
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.check_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())