5866 lines
235 KiB
Python
5866 lines
235 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, QDialog
|
|
from PyQt6.QtGui import QIcon, QPixmap,QIcon, QPixmap, QTransform, QPainter, QColor, QFont, QPen
|
|
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 UnknownConnectionTypeError, 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' or self.action == 'SYNC_TOR':
|
|
self.sync()
|
|
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:
|
|
if self.action == 'SYNC_TOR':
|
|
ConfigurationController.set_connection('tor')
|
|
else:
|
|
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 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__()
|
|
sys.excepthook = self._handle_exception
|
|
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 = None
|
|
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()
|
|
|
|
current_connection = self.get_current_connection()
|
|
self.is_tor_mode = current_connection == 'tor'
|
|
self.set_toggle_state(self.is_tor_mode)
|
|
|
|
|
|
|
|
|
|
|
|
def _handle_exception(self, identifier, message, trace):
|
|
if issubclass(identifier, UnknownConnectionTypeError):
|
|
self.setup_popup()
|
|
os.execv(sys.executable, [sys.executable] + sys.argv)
|
|
|
|
else:
|
|
|
|
config = self._load_gui_config()
|
|
|
|
if config and config["logging"]["gui_logging_enabled"] == True:
|
|
|
|
logging.error(
|
|
f"Uncaught exception:\n"
|
|
f"Type: {identifier.__name__}\n"
|
|
f"Value: {str(message)}\n"
|
|
f"Traceback:\n{''.join(traceback.format_tb(trace))}"
|
|
)
|
|
|
|
sys.__excepthook__(identifier, message, trace)
|
|
|
|
def get_current_connection(self):
|
|
return ConfigurationController.get_connection()
|
|
|
|
|
|
def setup_popup(self):
|
|
connection_dialog = QDialog(self)
|
|
connection_dialog.setWindowTitle("Connection Type")
|
|
connection_dialog.setWindowFlags(Qt.WindowType.Dialog | Qt.WindowType.WindowStaysOnTopHint | Qt.WindowType.CustomizeWindowHint | Qt.WindowType.WindowTitleHint)
|
|
connection_dialog.setModal(True)
|
|
connection_dialog.setFixedSize(400, 200)
|
|
|
|
layout = QVBoxLayout(connection_dialog)
|
|
|
|
label = QLabel("Please set your preference for connecting to Simplified Privacy's billing server.")
|
|
label.setWordWrap(True)
|
|
label.setAlignment(Qt.AlignmentFlag.AlignCenter)
|
|
label.setStyleSheet("font-size: 12px; margin-bottom: 20px; color: black;")
|
|
|
|
button_layout = QHBoxLayout()
|
|
|
|
regular_btn = QPushButton("Regular")
|
|
tor_btn = QPushButton("Tor")
|
|
|
|
button_style = """
|
|
QPushButton {
|
|
background-color: #2b2b2b;
|
|
color: white;
|
|
border: none;
|
|
padding: 10px 20px;
|
|
border-radius: 5px;
|
|
font-size: 12px;
|
|
}
|
|
QPushButton:hover {
|
|
background-color: #3b3b3b;
|
|
}
|
|
QPushButton:pressed {
|
|
background-color: #1b1b1b;
|
|
}
|
|
"""
|
|
regular_btn.setStyleSheet(button_style)
|
|
tor_btn.setStyleSheet(button_style)
|
|
|
|
button_layout.addWidget(regular_btn)
|
|
button_layout.addWidget(tor_btn)
|
|
|
|
layout.addWidget(label)
|
|
layout.addLayout(button_layout)
|
|
|
|
def set_regular_connection():
|
|
ConfigurationController.set_connection('system')
|
|
self.is_tor_mode = False
|
|
self.set_toggle_state(False)
|
|
connection_dialog.accept()
|
|
|
|
def set_tor_connection():
|
|
ConfigurationController.set_connection('tor')
|
|
self.is_tor_mode = True
|
|
self.set_toggle_state(True)
|
|
connection_dialog.accept()
|
|
|
|
regular_btn.clicked.connect(set_regular_connection)
|
|
tor_btn.clicked.connect(set_tor_connection)
|
|
|
|
connection_dialog.exec()
|
|
|
|
return ConfigurationController.get_connection()
|
|
|
|
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),
|
|
LocationPage(self.page_stack, self),
|
|
BrowserPage(self.page_stack, self),
|
|
ScreenPage(self.page_stack, self),
|
|
ResumePage(self.page_stack, self),
|
|
WireGuardPage(self.page_stack, self),
|
|
ResidentialPage(self.page_stack, self),
|
|
HidetorPage(self.page_stack, self),
|
|
InstallSystemPackage(self.page_stack, self),
|
|
WelcomePage(self.page_stack, self),
|
|
PaymentPage(self.page_stack, self),
|
|
PaymentConfirmed(self.page_stack, self),
|
|
IdPage(self.page_stack, self),
|
|
TorPage(self.page_stack, self),
|
|
EditorPage(self.page_stack, self),
|
|
Settings(self.page_stack, self),
|
|
ConnectionPage(self.page_stack, self),
|
|
SyncScreen(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 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(self.gui_dir, "first_launch.txt")
|
|
|
|
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, 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 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.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 on_update_check_finished(self):
|
|
reply = QMessageBox.question(self, 'Update Available',
|
|
'An update is available. Would you like to download it?',
|
|
QMessageBox.StandardButton.Yes | QMessageBox.StandardButton.No)
|
|
if reply == QMessageBox.StandardButton.Yes:
|
|
self.worker = WorkerThread('DOWNLOAD_UPDATE')
|
|
self.worker.start()
|
|
else:
|
|
self.update_status.update_status("Update cancelled by user.")
|
|
|
|
|
|
|
|
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 = self.update_status.get_current_connection() == 'tor'
|
|
if profile.connection.code == 'tor' and not profile.application_version.installed and not is_tor:
|
|
message = f'You are using a Tor profile, but the associated browser is not installed. If you want the browser to be downloaded with Tor, you must switch to a Tor connection. Otherwise, proceed with a clearweb connection.'
|
|
|
|
else:
|
|
self.get_billing_code_by_id()
|
|
return
|
|
|
|
self.popup = self.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
|
|
if not self.connection_manager.is_synced():
|
|
self.page_stack.setCurrentIndex(self.page_stack.indexOf(self.page_stack.findChild(SyncScreen)))
|
|
else:
|
|
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 = self.update_status.get_current_connection()
|
|
profile = ProfileController.get(int(self.reverse_id))
|
|
if current_connection != 'tor' and profile.connection.code == 'tor':
|
|
message = f'You are using a Tor profile, but the profile subscription is missing or expired. If you want the billing to be done thourgh Tor, you must switch to a Tor connection. Otherwise, proceed with a clearweb connection.'
|
|
self.popup = self.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.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 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 enable_protocol_buttons(self):
|
|
for button in self.buttons:
|
|
button.setDisabled(False)
|
|
|
|
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", self.update_status.get_current_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):
|
|
raise UnknownConnectionTypeError('The connection type is unknown')
|
|
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)
|
|
|
|
|
|
class SyncScreen(Page):
|
|
def __init__(self, page_stack, main_window=None, parent=None):
|
|
super().__init__("SyncScreen", page_stack, main_window, parent)
|
|
self.main_window = main_window
|
|
self.btn_path = main_window.btn_path
|
|
self.update_status = main_window
|
|
self.connection_manager = main_window.connection_manager
|
|
self.is_tor_mode = main_window.is_tor_mode
|
|
|
|
|
|
|
|
self.heading_label = QLabel("You're about to fetch data from\nSimplified Privacy.", self)
|
|
self.heading_label.setGeometry(15, 80, 750, 80)
|
|
self.heading_label.setStyleSheet("font-size: 36px; font-weight: bold; color: white;")
|
|
|
|
self.data_description = QLabel("This data includes what plans, countries,\nbrowsers, and version upgrades are\navailable. As well as coordinating billing.", self)
|
|
self.data_description.setGeometry(22, 190, 750, 90)
|
|
self.data_description.setStyleSheet("font-size: 26px; font-weight: bold; color: #ffff00;")
|
|
|
|
self.instructions = QLabel("Use the toggle in the bottom right to\ndecide if you want to use Tor or not.\nThen hit \"Next\"", self)
|
|
self.instructions.setGeometry(22, 345, 750, 100)
|
|
self.instructions.setStyleSheet("font-size: 30px; font-weight: bold; color: white;")
|
|
|
|
self.arrow_label = QLabel(self)
|
|
self.arrow_label.setGeometry(520, 400, 180, 130)
|
|
arrow_pixmap = QPixmap(os.path.join(self.btn_path, "arrow-down.png"))
|
|
|
|
self.arrow_label.setPixmap(arrow_pixmap)
|
|
self.arrow_label.raise_()
|
|
|
|
self.button_go.setVisible(True)
|
|
self.button_back.setVisible(True)
|
|
self.button_go.clicked.connect(self.perform_sync)
|
|
self.button_back.clicked.connect(self.go_back)
|
|
|
|
def go_back(self):
|
|
self.page_stack.setCurrentIndex(self.page_stack.indexOf(self.page_stack.findChild(MenuPage)))
|
|
|
|
def perform_sync(self):
|
|
self.button_go.setEnabled(False)
|
|
self.button_back.setEnabled(False)
|
|
self.update_status.update_status('Syncing in progress...')
|
|
if self.main_window.get_current_connection() == 'tor':
|
|
self.worker_thread = WorkerThread('SYNC_TOR')
|
|
else:
|
|
self.worker_thread = WorkerThread('SYNC')
|
|
|
|
self.worker_thread.sync_output.connect(self.update_output)
|
|
self.worker_thread.start()
|
|
|
|
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.button_go.setEnabled(True)
|
|
self.button_back.setEnabled(True)
|
|
return
|
|
|
|
if status is False:
|
|
self.button_go.setEnabled(True)
|
|
self.button_back.setEnabled(True)
|
|
self.update_status.update_status('An error occurred during sync')
|
|
return
|
|
|
|
self.update_status.update_status('Sync complete')
|
|
self.connection_manager.set_synced(True)
|
|
|
|
update_available = ClientController.can_be_updated()
|
|
|
|
if not update_available:
|
|
menu_page = self.page_stack.findChild(MenuPage)
|
|
menu_page.on_update_check_finished()
|
|
|
|
|
|
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)
|
|
|
|
location_page = self.find_location_page()
|
|
hidetor_page = self.find_hidetor_page()
|
|
protocol_page = self.find_protocol_page()
|
|
|
|
if location_page:
|
|
location_page.create_interface_elements(available_locations_list)
|
|
if hidetor_page:
|
|
hidetor_page.create_interface_elements(available_locations_list)
|
|
if protocol_page:
|
|
protocol_page.enable_protocol_buttons()
|
|
|
|
self.page_stack.setCurrentIndex(self.page_stack.indexOf(self.page_stack.findChild(ProtocolPage)))
|
|
|
|
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 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
|
|
|
|
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))
|
|
|
|
|
|
if __name__ == "__main__":
|
|
app = QApplication(sys.argv)
|
|
window = CustomWindow()
|
|
window.show()
|
|
sys.exit(app.exec())
|
|
|