hydraveil-gui/gui/v2/ui/pages/menu_page.py
2026-08-28 11:12:57 -04:00

1473 lines
65 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.errors.logger import logger
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.database_health import GuiStorageDatabaseError
from gui.v2.actions.profile_order import normalize_profile_order
from gui.v2.actions.profile_status import is_profile_enabled_for_gui
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.operation_result_popup import OperationResultPopup
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 _safe_profiles(self):
try:
return ProfileController.get_all()
except GuiStorageDatabaseError as error:
logger.error(f"[MENU] Storage database error while loading profiles: {error}")
self.update_status.update_status("Local storage database could not be read. Restart and recover storage.db.")
except Exception as error:
logger.error(f"[MENU] Error while loading profiles: {error}")
self.update_status.update_status("Could not load profiles. Sync or restart and try again.")
return {}
def _safe_profile(self, profile_id):
try:
return ProfileController.get(int(profile_id))
except GuiStorageDatabaseError as error:
logger.error(f"[MENU] Storage database error while loading profile {profile_id}: {error}")
self.update_status.update_status("Local storage database could not be read. Restart and recover storage.db.")
except Exception as error:
logger.error(f"[MENU] Error while loading profile {profile_id}: {error}")
self.update_status.update_status("Could not load profile data. Sync or restart and try again.")
return None
def _connection_code(self, profile):
connection = getattr(profile, 'connection', None)
return getattr(connection, 'code', None) or 'unknown'
def _location_key(self, profile):
location = getattr(profile, 'location', None)
if isinstance(location, dict):
country_code = location.get("country_code") or "na"
code = location.get("code") or "na"
return f'{country_code}_{code}'
country_code = getattr(location, 'country_code', None)
code = getattr(location, 'code', None)
if country_code and code:
return f'{country_code}_{code}'
return 'na_na'
def _browser_info(self, profile):
if not isinstance(profile, SessionProfile):
return "unknown browser", "", False
application_version = getattr(profile, 'application_version', None)
if isinstance(application_version, dict):
browser = application_version.get('application_code') or "unknown browser"
version = application_version.get('version_number') or ""
return browser, version, False
browser = getattr(application_version, 'application_code', None) or "unknown browser"
version = getattr(application_version, 'version_number', None) or ""
supported = bool(getattr(application_version, 'supported', False))
return browser, version, supported
def _profile_incomplete_reason(self, profile):
if profile is None:
return "Profile data could not be loaded. Sync or restart and try again."
connection = getattr(profile, 'connection', None)
if not getattr(connection, 'code', None):
return "Profile connection data is incomplete. Sync the database and try again."
location = getattr(profile, 'location', None)
if location is None or isinstance(location, dict):
return "Profile location data is incomplete. Sync the database and try again."
if isinstance(profile, SessionProfile):
application_version = getattr(profile, 'application_version', None)
if isinstance(application_version, dict) or application_version is None:
return "Profile browser data is incomplete. Sync the database and try again."
if not getattr(application_version, 'application_code', None) or not getattr(application_version, 'version_number', None):
return "Profile browser data is incomplete. Sync the database and try again."
return None
def _location_operator(self, profile):
location = getattr(profile, 'location', None)
if isinstance(location, dict) or location is None:
return None
return getattr(location, 'operator', None)
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:
profile = profiles_dict.get(idx)
if profile is None:
continue
new_profile = {}
protocol = self._connection_code(profile)
location = self._location_key(profile)
new_profile['location'] = location
if protocol in ('wireguard', 'hysteria2', 'vless'):
new_profile['protocol'] = protocol
else:
new_profile['protocol'] = 'hidetor'
connection_type = 'browser-only' if isinstance(
profile, SessionProfile) else 'system-wide'
if protocol in ('wireguard', 'hysteria2', 'vless'):
new_profile['connection'] = connection_type
elif protocol == 'tor':
new_profile['connection'] = protocol
else:
new_profile['connection'] = 'just proxy'
browser, browser_version, browser_supported = self._browser_info(profile)
new_profile['browser'] = browser
new_profile['browser_version'] = browser_version
new_profile['browser_supported'] = browser_supported
resolution = profile.resolution if hasattr(
profile, 'resolution') else 'None'
new_profile['dimentions'] = resolution
new_profile['name'] = getattr(profile, 'name', None) or f'Profile {idx}'
new_profile['incomplete_reason'] = self._profile_incomplete_reason(profile)
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(
self._safe_profiles())
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(
self._safe_profiles())
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 = self._safe_profiles()
self.button_states.clear()
self.connection_manager._connected_profiles.clear()
self.IsSystem = 0
for profile_id, profile in profiles.items():
try:
is_enabled = is_profile_enabled_for_gui(profile)
except Exception:
is_enabled = False
if is_enabled:
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(str(name or "Unnamed Profile"))
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):
protocol = value.get('protocol', '')
if label_name == 'protocol':
if protocol in ('hysteria2', 'vless'):
return os.path.join(self.btn_path, f"{protocol}_mini.png")
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':
if protocol in ('hysteria2', 'vless'):
return os.path.join(self.btn_path, "wireguard_system_wide.png")
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 = self._location_operator(profile_obj)
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 = self._safe_profile(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"
profile_location = getattr(profile_obj, 'location', None)
if isinstance(profile_location, dict):
profile_location = None
profile_operator = self._location_operator(profile_obj)
operator_name = getattr(profile_operator, 'name', '') if profile_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 isinstance(profile_obj, SessionProfile):
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_location.country_name}, {profile_location.name}" if profile_location else ""
o_name = getattr(profile_operator, 'name', '') if profile_operator else ""
n_key = getattr(profile_operator, 'nostr_public_key', '') if profile_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", "hysteria2", "vless"]:
label_principal = QLabel(self)
label_principal.setGeometry(0, 90, 400, 300)
if protocol.lower() in ["hysteria2", "vless"]:
pixmap = self.build_encrypted_proxy_detail_pixmap(
protocol.lower(), location)
else:
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", "hysteria2", "vless"]:
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 build_encrypted_proxy_detail_pixmap(self, protocol, location):
pixmap = QPixmap(os.path.join(self.btn_path, f"{protocol}.png"))
if pixmap.isNull():
return pixmap
location_pixmap = QPixmap(os.path.join(
self.btn_path, f"icon_mini_{location}.png"))
if location_pixmap.isNull():
location_pixmap = QPixmap(os.path.join(
self.btn_path, "default_location_mini.png"))
if location_pixmap.isNull():
return pixmap
icon_size = max(42, min(72, int(min(pixmap.width(), pixmap.height()) * 0.22)))
location_pixmap = location_pixmap.scaled(
icon_size,
icon_size,
Qt.AspectRatioMode.KeepAspectRatio,
Qt.TransformationMode.SmoothTransformation)
x = int(pixmap.width() * 0.58)
if protocol == 'hysteria2':
y = int(pixmap.height() * 0.35)
else:
y = int(pixmap.height() * 0.30)
x = min(max(0, x), max(0, pixmap.width() - location_pixmap.width()))
y = min(max(0, y), max(0, pixmap.height() - location_pixmap.height()))
painter = QPainter(pixmap)
painter.drawPixmap(x, y, location_pixmap)
painter.end()
return pixmap
def show_profile_verification(self, profile_id):
profile = self._safe_profile(profile_id)
profile_location = getattr(profile, 'location', None)
if not profile or not profile_location or isinstance(profile_location, dict):
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 = self._safe_profile(int(self.reverse_id))
incomplete_reason = self._profile_incomplete_reason(profile)
if incomplete_reason:
self.update_status.update_status(incomplete_reason)
self.boton_just.setEnabled(True)
self.boton_just_session.setEnabled(True)
return
try:
is_tor = self.update_status.get_current_connection() == 'tor'
except Exception:
is_tor = False
connection = getattr(profile, 'connection', None)
application_version = getattr(profile, 'application_version', None)
if getattr(connection, 'code', None) == 'tor' and not getattr(application_version, 'installed', False) 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 = self._safe_profile(int(self.reverse_id))
if profile is None:
self.update_status.update_status("Could not load profile data. Sync or restart and try again.")
self.disconnect_button.setEnabled(True)
self.disconnect_system_wide_button.setEnabled(True)
return
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.text_output.connect(
lambda text: self.update_status.update_status(str(text)))
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)
selected_profile_id = self.reverse_id
self.refresh_menu_buttons()
if selected_profile_id is not None and self._safe_profile(selected_profile_id):
self.print_profile_details(f"Profile_{selected_profile_id}")
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 = self._safe_profile(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 profile is None:
self.update_status.update_status("Profile data could not be loaded. Sync or restart and try again.")
return
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)
self.worker.operation_failed.connect(self.handle_operation_failure)
thread = threading.Thread(target=self.worker.run)
thread.start()
def handle_operation_failure(self, payload):
if not isinstance(payload, dict):
payload = {
'title': 'Operation Failed',
'message': str(payload),
'actions': [{'key': 'dismiss', 'label': 'OK', 'role': 'primary'}],
'severity': 'error',
'context': {},
}
message = str(payload.get('message') or 'The operation could not be completed.')
self.update_status.update_status(message)
self.boton_just.setEnabled(True)
self.boton_just_session.setEnabled(True)
self.disconnect_button.setEnabled(True)
self.disconnect_system_wide_button.setEnabled(True)
self.popup = OperationResultPopup(
self,
title=payload.get('title', 'Operation Failed'),
message=message,
actions=payload.get('actions'),
severity=payload.get('severity', 'error'),
)
self.popup.action_selected.connect(
lambda action, current_payload=payload: self.handle_operation_popup_action(action, current_payload))
self.popup.show()
def handle_operation_popup_action(self, action, payload):
if action != 'retry':
return
context = payload.get('context') or {}
profile_data = context.get('profile_data')
if not isinstance(profile_data, dict) or 'id' not in profile_data:
self.update_status.update_status('Retry is unavailable for this operation.')
return
self.update_status.update_status('Retrying...')
self.enabling_profile(dict(profile_data))
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 is not None and 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:
try:
current_connection = self.update_status.get_current_connection()
except Exception:
current_connection = None
profile = self._safe_profile(int(self.reverse_id))
incomplete_reason = self._profile_incomplete_reason(profile)
if incomplete_reason:
self.update_status.update_status(incomplete_reason)
elif current_connection != 'tor' and getattr(getattr(profile, 'connection', None), 'code', None) == '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 = self._safe_profile(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.')