hydraveil-gui/gui/v2/ui/pages/menu_page.py

1285 lines
56 KiB
Python
Executable file

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