update: added v2 gui

This commit is contained in:
JOhn 2026-06-13 16:19:11 -04:00
parent 68154c4576
commit 9f714b9f7b
120 changed files with 12842 additions and 0 deletions

0
gui/v2/__init__.py Executable file
View file

709
gui/v2/__main__.py Executable file
View file

@ -0,0 +1,709 @@
import logging
import os
import sys
import traceback
from PyQt6.QtWidgets import (
QApplication, QMainWindow, QStackedWidget, QLabel, QPushButton,
QDialog, QVBoxLayout, QHBoxLayout
)
from PyQt6.QtGui import QPixmap, QFont, QFontDatabase
from PyQt6 import QtGui
from PyQt6.QtCore import Qt, QTimer
from core.Constants import Constants
from core.controllers.ConfigurationController import ConfigurationController
from core.Errors import UnknownConnectionTypeError
from core.errors.logger import logger as core_logger
core_logger.propagate = False
from gui.v2.infrastructure.navigator import Navigator
from gui.v2.infrastructure.setup_observers import setup_observers
from gui.v2.infrastructure.connection_manager import ConnectionManager
from gui.v2.workers.worker_thread import WorkerThread
from gui.v2.ui.builders.bottom_section import create_bottom_section
from gui.v2.ui.builders.top_section import create_top_section
from gui.v2.ui.builders.system_tray import init_system_tray
from gui.v2.actions.config import (
setup_gui_directory,
load_gui_config,
save_gui_config,
default_gui_config,
)
from gui.v2.actions.logging_setup import (
is_logging_enabled,
setup_gui_logging,
teardown_gui_logging,
)
from gui.v2.actions.flags import (
has_shown_systemwide_prompt,
mark_systemwide_prompt_shown,
has_shown_fast_mode_prompt,
mark_fast_mode_prompt_shown,
set_fast_mode_enabled,
check_first_launch,
)
from gui.v2.actions.profile_data import (
read_profile_data,
write_profile_data,
clear_profile_data,
)
from gui.v2.actions.ticket_failure import (
save_ticket_verification_failure,
get_ticket_verification_failure,
clear_ticket_verification_failure,
)
from gui.v2.actions.should_be_synchronized import should_be_synchronized
class CustomWindow(QMainWindow):
def __init__(self):
super().__init__()
sys.excepthook = self._handle_exception
self.setWindowFlags(Qt.WindowType.Window)
current_dir = os.path.dirname(os.path.abspath(__file__))
parent_dir = os.path.dirname(current_dir)
font_path = os.path.join(parent_dir, 'resources', 'fonts')
retro_gaming_path = os.path.join(font_path, 'retro-gaming.ttf')
font_id = QFontDatabase.addApplicationFont(retro_gaming_path)
font_family = QFontDatabase.applicationFontFamilies(font_id)[0]
app_font = QFont(font_family)
app_font.setPointSize(10)
QApplication.setFont(app_font)
self.open_sans_family = None
open_sans_path = os.path.join(font_path, 'open-sans.ttf')
if os.path.exists(open_sans_path):
open_sans_id = QFontDatabase.addApplicationFont(open_sans_path)
if open_sans_id != -1:
self.open_sans_family = QFontDatabase.applicationFontFamilies(open_sans_id)[0]
self.setAttribute(Qt.WidgetAttribute.WA_NoSystemBackground)
self.setWindowTitle('HydraVeil')
screen = QApplication.primaryScreen()
ratio = screen.devicePixelRatio()
self.host_screen_width = int(screen.size().width() * ratio)
self.host_screen_height = int(screen.size().height() * ratio)
self._data = {"Profile_1": {}}
self.gui_config_home = os.path.join(Constants.HV_CONFIG_HOME, 'gui')
self.gui_config_file = os.path.join(self.gui_config_home, 'config.json')
self.gui_cache_home = os.path.join(Constants.HV_CACHE_HOME, 'gui')
self.gui_log_file = os.path.join(self.gui_cache_home, 'gui.log')
self._gui_log_handlers = []
setup_gui_directory(
self.gui_cache_home, self.gui_config_home, self.gui_config_file)
self.btn_path = os.getenv('BTN_PATH', os.path.join(
parent_dir, 'resources', 'images'))
self.css_path = os.getenv('CSS_PATH', os.path.join(
parent_dir, 'resources', 'styles'))
self.is_downloading = False
self.current_profile_id = None
self.connection_manager = ConnectionManager()
current_connection = None
self.is_tor_mode = current_connection == 'tor'
self.is_animating = False
self.animation_step = 0
self.page_history = []
self.log_path = None
self.setFixedSize(800, 570)
top = create_top_section(self, self.css_path)
self.__dict__.update(top)
self.marquee_text = ""
self.marquee_position = 0
self.marquee_enabled = False
self.scroll_speed = 1
self.page_stack = QStackedWidget(self)
self.page_stack.setGeometry(0, 0, 800, 570)
bottom = create_bottom_section(
self,
self.btn_path,
Constants.HV_CLIENT_VERSION_NUMBER,
should_be_synchronized(),
)
self.__dict__.update(bottom)
self.status_label = bottom['status_label']
self.tor_text = bottom['tor_text']
self.toggle_button = bottom['toggle_button']
self.tor_icon = bottom['tor_icon']
self.sync_button = bottom['sync_button']
self.page_stack.raise_()
self.observers = setup_observers(self.update_status)
self.update_image()
self.set_scroll_speed(7)
self.toggle_button.clicked.connect(self.toggle_mode)
self.sync_button.clicked.connect(self.sync)
self.create_toggle(self.is_tor_mode)
self.animation_timer = QTimer()
self.animation_timer.timeout.connect(self.animate_toggle)
self.check_logging()
self.navigator = Navigator(self.page_stack, self)
self.page_stack.currentChanged.connect(self.page_changed)
self.show()
self.init_ui()
self.tray = None
self.tray = init_system_tray(self, self.btn_path)
current_connection = self.get_current_connection()
self.is_tor_mode = current_connection == 'tor'
self.set_toggle_state(self.is_tor_mode)
core_logger.info(
f"HydraVeil application opened (client version {Constants.HV_CLIENT_VERSION_NUMBER}, "
f"connection={current_connection}, tor_mode={self.is_tor_mode})"
)
def init_ui(self):
if self.check_first_launch():
self.navigator.navigate("welcome")
else:
self.navigator.navigate("menu")
def perform_update_check(self):
from core.controllers.ClientController import ClientController
update_available = ClientController.can_be_updated()
if update_available:
menu_page = self.navigator.get_cached("menu")
if menu_page is not None:
menu_page.on_update_check_finished()
def update_values(self, available_locations, available_browsers, status, is_tor, locations, all_browsers):
if not status:
self.update_status('Sync failed. Please try again later.')
return
from PyQt6.QtWidgets import QPushButton
from gui.v2.actions.sync import generate_grid_positions
self.connection_manager.set_synced(True)
self.connection_manager.store_locations(locations)
self.connection_manager.store_browsers(available_browsers)
location_positions = generate_grid_positions(len(available_locations))
browser_positions = generate_grid_positions(len(available_browsers))
available_locations_list = [
(QPushButton, loc, location_positions[i])
for i, loc in enumerate(available_locations)
]
available_browsers_list = [
(QPushButton, brw, browser_positions[i])
for i, brw in enumerate(available_browsers)
]
browser_page = self.navigator.get_cached("browser")
if browser_page is not None:
browser_page.create_interface_elements(available_browsers_list)
location_page = self.navigator.get_cached("location")
if location_page is not None:
location_page.create_interface_elements(available_locations_list)
hidetor_page = self.navigator.get_cached("hidetor")
if hidetor_page is not None:
hidetor_page.create_interface_elements(available_locations_list)
protocol_page = self.navigator.get_cached("protocol")
if protocol_page is not None:
protocol_page.enable_protocol_buttons()
menu_page = self.navigator.get_cached("menu")
if menu_page is not None:
if hasattr(menu_page, 'refresh_profiles_data'):
menu_page.refresh_profiles_data()
if hasattr(menu_page, 'buttons'):
for button in menu_page.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(True)
def sync(self):
core_logger.info("User clicked sync button")
if ConfigurationController.get_connection() == 'tor':
self.worker_thread = WorkerThread('SYNC_TOR')
else:
self.worker_thread = WorkerThread('SYNC')
self.sync_button.setIcon(QtGui.QIcon(
os.path.join(self.btn_path, 'icon_sync.png')))
self.worker_thread.finished.connect(
lambda: self.perform_update_check())
self.worker_thread.sync_output.connect(self.update_values)
self.worker_thread.start()
def _handle_exception(self, identifier, message, trace):
if issubclass(identifier, UnknownConnectionTypeError):
self.setup_popup()
os.execv(sys.executable, [sys.executable] + sys.argv)
else:
config = self._load_gui_config()
if config and config["logging"]["gui_logging_enabled"] == True:
logging.error(
f"Uncaught exception:\n"
f"Type: {identifier.__name__}\n"
f"Value: {str(message)}\n"
f"Traceback:\n{''.join(traceback.format_tb(trace))}"
)
sys.__excepthook__(identifier, message, trace)
def get_current_connection(self):
return ConfigurationController.get_connection()
def setup_popup(self):
connection_dialog = QDialog(self)
connection_dialog.setWindowTitle("Connection Type")
connection_dialog.setWindowFlags(Qt.WindowType.Dialog | Qt.WindowType.WindowStaysOnTopHint |
Qt.WindowType.CustomizeWindowHint | Qt.WindowType.WindowTitleHint)
connection_dialog.setModal(True)
connection_dialog.setFixedSize(400, 200)
layout = QVBoxLayout(connection_dialog)
label = QLabel(
"Please set your preference for connecting to Simplified Privacy's billing server.")
label.setWordWrap(True)
label.setAlignment(Qt.AlignmentFlag.AlignCenter)
label.setStyleSheet(
"font-size: 12px; margin-bottom: 20px; color: black;")
button_layout = QHBoxLayout()
regular_btn = QPushButton("Regular")
tor_btn = QPushButton("Tor")
button_style = """
QPushButton {
background-color: #2b2b2b;
color: white;
border: none;
padding: 10px 20px;
border-radius: 5px;
font-size: 12px;
}
QPushButton:hover {
background-color: #3b3b3b;
}
QPushButton:pressed {
background-color: #1b1b1b;
}
"""
regular_btn.setStyleSheet(button_style)
tor_btn.setStyleSheet(button_style)
button_layout.addWidget(regular_btn)
button_layout.addWidget(tor_btn)
layout.addWidget(label)
layout.addLayout(button_layout)
def set_regular_connection():
ConfigurationController.set_connection('system')
self.is_tor_mode = False
self.set_toggle_state(False)
core_logger.info("User selected 'Regular' connection type from popup")
connection_dialog.accept()
def set_tor_connection():
ConfigurationController.set_connection('tor')
self.is_tor_mode = True
self.set_toggle_state(True)
core_logger.info("User selected 'Tor' connection type from popup")
connection_dialog.accept()
regular_btn.clicked.connect(set_regular_connection)
tor_btn.clicked.connect(set_tor_connection)
connection_dialog.exec()
return ConfigurationController.get_connection()
def _setup_gui_directory(self):
setup_gui_directory(
self.gui_cache_home, self.gui_config_home, self.gui_config_file)
def _load_gui_config(self):
return load_gui_config(self.gui_config_file)
def _save_gui_config(self, config):
save_gui_config(self.gui_config_file, config)
def _default_gui_config(self):
return default_gui_config()
def save_ticket_verification_failure(self, result):
return save_ticket_verification_failure(self.gui_config_file, result)
def get_ticket_verification_failure(self):
return get_ticket_verification_failure(self.gui_config_file)
def clear_ticket_verification_failure(self):
clear_ticket_verification_failure(self.gui_config_file)
def check_logging(self):
config = self._load_gui_config()
if is_logging_enabled(config):
self._setup_gui_logging()
def has_shown_systemwide_prompt(self):
return has_shown_systemwide_prompt(self.gui_config_file)
def mark_systemwide_prompt_shown(self):
mark_systemwide_prompt_shown(self.gui_config_file)
def stop_gui_logging(self):
teardown_gui_logging(self.gui_log_file, self._gui_log_handlers)
self._gui_log_handlers = []
config = self._load_gui_config()
if config:
config["logging"]["gui_logging_enabled"] = False
self._save_gui_config(config)
def _setup_gui_logging(self):
self._gui_log_handlers = setup_gui_logging(
self.gui_cache_home, self.gui_log_file, self._gui_log_handlers)
def set_tor_icon(self):
if self.is_tor_mode:
icon_path = os.path.join(self.btn_path, "toricon_mini.png")
else:
icon_path = os.path.join(self.btn_path, "toricon_mini_false.png")
pixmap = QPixmap(icon_path)
self.tor_icon.setPixmap(pixmap)
self.tor_icon.setScaledContents(True)
def create_toggle(self, is_tor_mode):
handle_x = 30 if is_tor_mode else 3
colors = self._get_toggle_colors(is_tor_mode)
if not hasattr(self, 'animation_timer'):
self.animation_timer = QTimer()
self.animation_timer.timeout.connect(self.animate_toggle)
self.toggle_bg = QLabel(self.toggle_button)
self.toggle_bg.setGeometry(0, 0, 50, 22)
self.toggle_handle = QLabel(self.toggle_button)
self.toggle_handle.setGeometry(handle_x, 3, 16, 16)
self.toggle_handle.setStyleSheet("""
QLabel {
background-color: white;
border-radius: 8px;
}
""")
self.on_label = QLabel("ON", self.toggle_button)
self.on_label.setGeometry(8, 4, 15, 14)
self.off_label = QLabel("OFF", self.toggle_button)
self.off_label.setGeometry(25, 4, 40, 14)
self._update_toggle_colors(colors)
if is_tor_mode:
self.set_tor_icon()
def _get_toggle_colors(self, is_tor_mode):
return {
'bg': "#00a8ff" if is_tor_mode else "#2c3e50",
'tor': "white" if is_tor_mode else "transparent",
'sys': "transparent" if is_tor_mode else "white"
}
def _update_toggle_colors(self, colors):
self.toggle_bg.setStyleSheet(f"""
QLabel {{
background-color: {colors['bg']};
border-radius: 11px;
}}
""")
self.on_label.setStyleSheet(f"""
QLabel {{
color: {colors['tor']};
font-size: 9px;
font-weight: bold;
}}
""")
self.off_label.setStyleSheet(f"""
QLabel {{
color: {colors['sys']};
font-size: 9px;
font-weight: bold;
}}
""")
def set_toggle_state(self, is_tor_mode):
self.is_tor_mode = is_tor_mode
handle_x = 30 if is_tor_mode else 3
self.toggle_handle.setGeometry(handle_x, 3, 16, 16)
self._update_toggle_colors(self._get_toggle_colors(is_tor_mode))
self.set_tor_icon()
def toggle_mode(self):
if not self.is_animating:
self.is_animating = True
self.animation_step = 0
self.animation_timer.start(16)
self.is_tor_mode = not self.is_tor_mode
new_connection = 'tor' if self.is_tor_mode else 'system'
ConfigurationController.set_connection(new_connection)
core_logger.info(f"User toggled connection mode to '{new_connection}'")
def animate_toggle(self):
TOTAL_STEPS = 5
if self.animation_step <= TOTAL_STEPS:
progress = self.animation_step / TOTAL_STEPS
if self.is_tor_mode:
new_x = 20 + (10 * progress)
start_color = "#2c3e50"
end_color = "#00a8ff"
else:
new_x = 31 - (28 * progress)
start_color = "#00a8ff"
end_color = "#2c3e50"
self.toggle_handle.setGeometry(int(new_x), 3, 16, 16)
bg_color = self.interpolate_color(start_color, end_color, progress)
self.toggle_bg.setStyleSheet(f"""
QLabel {{
background-color: {bg_color};
border-radius: 11px;
}}
""")
self.off_label.setStyleSheet(f"""
QLabel {{
color: rgba(255, 255, 255, {1 - progress if self.is_tor_mode else progress});
font-size: 9px;
font-weight: bold;
}}
""")
self.on_label.setStyleSheet(f"""
QLabel {{
color: rgba(255, 255, 255, {progress if self.is_tor_mode else 1 - progress});
font-size: 9px;
font-weight: bold;
}}
""")
self.animation_step += 1
else:
self.animation_timer.stop()
self.is_animating = False
self.set_tor_icon()
def interpolate_color(self, start_color, end_color, progress):
def hex_to_rgb(hex_color):
hex_color = hex_color.lstrip('#')
return tuple(int(hex_color[i:i+2], 16) for i in (0, 2, 4))
start_rgb = hex_to_rgb(start_color)
end_rgb = hex_to_rgb(end_color)
current_rgb = tuple(
int(start_rgb[i] + (end_rgb[i] - start_rgb[i]) * progress)
for i in range(3)
)
return f"rgb{current_rgb}"
def closeEvent(self, event=None):
core_logger.info("HydraVeil application closing")
connected_profiles = self.connection_manager.get_connected_profiles()
if connected_profiles:
self.update_status('Profiles are still connected (disconnect flow lands in Chunk 9).')
if event is not None:
event.accept()
else:
if event is not None:
event.accept()
def update_image(self, appearance_value="original"):
image_path = os.path.join(self.btn_path, f"{appearance_value}.png")
pixmap = QPixmap(image_path)
self.label.setPixmap(pixmap)
self.label.setScaledContents(True)
def read_data(self):
return read_profile_data(self._data)
def write_data(self, data):
write_profile_data(self._data, data)
def clear_data(self):
self._data = clear_profile_data()
def _set_status_font_size(self, font_size):
self.status_label.setStyleSheet(
f"color: rgb(0, 255, 255); font-size: {font_size}px;")
def update_status(self, text, clear=False):
if text is None:
self._set_status_font_size(16)
self.status_label.setText('Status:')
self.disable_marquee()
return
if clear:
self._set_status_font_size(16)
self.status_label.setText('')
return
full_text = 'Status: ' + text
metrics = self.status_label.fontMetrics()
available_width = self.status_label.width() - 10
if metrics.horizontalAdvance(full_text) > available_width:
self.enable_marquee(text)
else:
self.status_label.setText(full_text)
self.disable_marquee()
def check_first_launch(self):
return check_first_launch(self.gui_config_file)
def has_shown_fast_mode_prompt(self):
return has_shown_fast_mode_prompt(self.gui_config_file)
def mark_fast_mode_prompt_shown(self):
mark_fast_mode_prompt_shown(self.gui_config_file)
def set_fast_mode_enabled(self, enabled):
set_fast_mode_enabled(self.gui_config_file, enabled)
def navigate_after_profile_created(self):
if not self.has_shown_fast_mode_prompt():
self.navigator.navigate("fast_mode_prompt")
else:
menu_page = self.navigator.get_cached("menu")
if menu_page is not None and hasattr(menu_page, 'refresh_menu_buttons'):
menu_page.refresh_menu_buttons()
self.navigator.navigate("menu")
def enable_marquee(self, text):
self.marquee_text = text + " "
self.marquee_position = 0
self.marquee_enabled = True
self.marquee_timer.start(500)
def disable_marquee(self):
self.marquee_enabled = False
self.marquee_timer.stop()
def update_marquee(self):
if not self.marquee_enabled:
return
metrics = self.status_label.fontMetrics()
text_width = metrics.horizontalAdvance(self.marquee_text)
self.marquee_position += self.scroll_speed
if self.marquee_position >= text_width:
self.marquee_position = 0
looped_text = self.marquee_text * 2
chars_that_fit = metrics.horizontalAdvance(
looped_text[:self.text_width])
visible_text = looped_text[self.marquee_position:
self.marquee_position + chars_that_fit]
while metrics.horizontalAdvance(visible_text) < self.text_width:
visible_text += self.marquee_text
while metrics.horizontalAdvance(visible_text) > self.text_width:
visible_text = visible_text[:-1]
display_text = 'Status: ' + ' ' * \
(self.text_start_x - metrics.horizontalAdvance('Status: '))
display_text += visible_text
self.status_label.setText(display_text)
def set_scroll_speed(self, speed):
self.scroll_speed = speed
def page_changed(self, index):
if should_be_synchronized():
sync_icon = QtGui.QIcon(os.path.join(
self.btn_path, 'icon_sync_urgent.png'))
else:
sync_icon = QtGui.QIcon(os.path.join(
self.btn_path, 'icon_sync.png'))
self.sync_button.setIcon(sync_icon)
current_page = self.page_stack.widget(index)
if self.page_history:
previous_page = self.page_history[-1]
else:
previous_page = None
current_name = getattr(current_page, 'name', None)
previous_name = getattr(previous_page, 'name', None)
if current_name == 'Menu':
self.update_status(None)
if previous_name in ('Resume', 'Editor', 'Settings', 'FastRegistration', 'FastModePrompt'):
if hasattr(current_page, 'eliminacion'):
current_page.eliminacion()
elif current_name == 'Resume':
self.update_status('Choose a name for your profile')
if hasattr(current_page, 'eliminacion'):
current_page.eliminacion()
elif current_name == 'Id':
pass
elif current_name == 'Location':
self.update_status(None, clear=True)
elif current_name == 'InstallPackage':
self.update_status('Please check package availability.')
else:
self.update_status(None)
if current_name == 'Menu':
self.sync_button.setVisible(True)
self.tor_text.setVisible(True)
self.toggle_button.setGeometry(670, 541, 50, 22)
self.tor_icon.setGeometry(730, 537, 25, 25)
else:
self.sync_button.setVisible(False)
self.tor_text.setVisible(False)
self.toggle_button.setGeometry(540, 541, 50, 22)
self.tor_icon.setGeometry(600, 537, 25, 25)
if current_page != previous_page:
self.page_history.append(current_page)
if __name__ == "__main__":
app = QApplication(sys.argv)
window = CustomWindow()
sys.exit(app.exec())

Binary file not shown.

Binary file not shown.

0
gui/v2/actions/__init__.py Executable file
View file

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

38
gui/v2/actions/config.py Executable file
View file

@ -0,0 +1,38 @@
import json
import logging
import os
def setup_gui_directory(gui_cache_home, gui_config_home, gui_config_file):
os.makedirs(gui_cache_home, exist_ok=True)
os.makedirs(gui_config_home, exist_ok=True)
if not os.path.exists(gui_config_file):
default_config = {
"logging": {
"gui_logging_enabled": False,
"log_level": "INFO"
}
}
with open(gui_config_file, 'w') as f:
json.dump(default_config, f, indent=4)
def load_gui_config(gui_config_file):
try:
with open(gui_config_file, 'r') as f:
return json.load(f)
except FileNotFoundError:
return None
except json.JSONDecodeError:
logging.error("Invalid GUI config file format")
return None
def save_gui_config(gui_config_file, config):
with open(gui_config_file, 'w') as f:
json.dump(config, f, indent=4)
def default_gui_config():
return {"logging": {"gui_logging_enabled": False, "log_level": "INFO"}}

63
gui/v2/actions/flags.py Executable file
View file

@ -0,0 +1,63 @@
from gui.v2.actions.config import load_gui_config, save_gui_config, default_gui_config
def has_shown_systemwide_prompt(gui_config_file):
config = load_gui_config(gui_config_file)
if not config:
return False
return config.get("systemwide", {}).get("prompt_shown", False)
def mark_systemwide_prompt_shown(gui_config_file):
config = load_gui_config(gui_config_file)
if config is None:
config = default_gui_config()
if "systemwide" not in config:
config["systemwide"] = {}
config["systemwide"]["prompt_shown"] = True
save_gui_config(gui_config_file, config)
def has_shown_fast_mode_prompt(gui_config_file):
config = load_gui_config(gui_config_file)
if not config:
return False
return config.get("fast_mode", {}).get("prompt_shown", False)
def mark_fast_mode_prompt_shown(gui_config_file):
config = load_gui_config(gui_config_file)
if config is None:
config = default_gui_config()
if "fast_mode" not in config:
config["fast_mode"] = {}
config["fast_mode"]["prompt_shown"] = True
save_gui_config(gui_config_file, config)
def set_fast_mode_enabled(gui_config_file, enabled):
config = load_gui_config(gui_config_file)
if config is None:
config = default_gui_config()
if "registrations" not in config:
config["registrations"] = {}
config["registrations"]["fast_registration_enabled"] = bool(enabled)
save_gui_config(gui_config_file, config)
def check_first_launch(gui_config_file):
config = load_gui_config(gui_config_file)
if config is None:
config = default_gui_config()
is_first_launch = not config.get(
"first_launch", {}).get("completed", False)
if is_first_launch:
if "first_launch" not in config:
config["first_launch"] = {}
config["first_launch"]["completed"] = True
save_gui_config(gui_config_file, config)
return True
else:
return False

View file

@ -0,0 +1,30 @@
def interpret_key_results(result: dict) -> str:
if not isinstance(result, dict):
return "There was an error with the format of the reply from the operation."
valid = result.get('valid', False)
comparison = result.get('comparison', 'error')
error_msg = result.get('message', None)
if error_msg:
if error_msg == "api_connection_issue":
return "There was a connection error with connecting to the API."
elif error_msg == "invalid_key":
return "Your original public key is in an invalid format to begin with."
else:
return "Unknown Error."
if comparison == "same":
first_sentence = "The key you had locally matched what the server had also."
elif comparison == "different":
first_sentence = "Your local key was different than the server's key."
else:
first_sentence = "There was an error with the comparison."
if valid:
second_sentence = "The signature now matches with the new key."
else:
second_sentence = "But the signature still does not match, even with the new key. If you prepare tickets anyway, they might not verify when used."
final_msg = first_sentence + " " + second_sentence
return final_msg

34
gui/v2/actions/locations.py Executable file
View file

@ -0,0 +1,34 @@
def location_candidates(profile, preferred=None):
candidates = []
seen = set()
def add(val):
if val is None:
return
s = str(val).strip().lower().replace(' ', '_')
if s and s not in seen:
seen.add(s)
candidates.append(s)
if preferred:
add(preferred)
sources = []
try:
if profile and profile.connection and profile.connection.location:
sources.append(profile.connection.location)
except Exception:
pass
try:
if profile and profile.location:
sources.append(profile.location)
except Exception:
pass
for source in sources:
add(getattr(source, 'country_name', None))
add(getattr(source, 'name', None))
add(getattr(source, 'code', None))
add(getattr(source, 'country_code', None))
return candidates

66
gui/v2/actions/logging_setup.py Executable file
View file

@ -0,0 +1,66 @@
import logging
import os
import sys
from core.errors.logger import logger as core_logger
def is_logging_enabled(config):
if config is None:
return False
return bool(config.get("logging", {}).get("gui_logging_enabled"))
def setup_gui_logging(gui_cache_home, gui_log_file, current_handlers):
os.makedirs(gui_cache_home, exist_ok=True)
formatter = logging.Formatter(
'%(asctime)s - %(levelname)s - %(message)s',
datefmt='%Y-%m-%d %H:%M:%S'
)
file_handler = logging.FileHandler(gui_log_file)
file_handler.setLevel(logging.INFO)
file_handler.setFormatter(formatter)
stream_handler = logging.StreamHandler(sys.stdout)
stream_handler.setLevel(logging.INFO)
stream_handler.setFormatter(formatter)
root_logger = logging.getLogger()
root_logger.setLevel(logging.INFO)
root_logger.handlers = []
root_logger.addHandler(file_handler)
root_logger.addHandler(stream_handler)
for handler in current_handlers:
try:
core_logger.removeHandler(handler)
except Exception:
pass
core_logger.addHandler(file_handler)
core_logger.addHandler(stream_handler)
if not os.path.exists(gui_log_file) or os.path.getsize(gui_log_file) == 0:
logging.info("GUI logging system initialized")
return [file_handler, stream_handler]
def teardown_gui_logging(gui_log_file, current_handlers):
for handler in current_handlers:
try:
core_logger.removeHandler(handler)
except Exception:
pass
root_logger = logging.getLogger()
for handler in list(root_logger.handlers):
root_logger.removeHandler(handler)
try:
handler.close()
except Exception:
pass
if os.path.exists(gui_log_file):
os.remove(gui_log_file)

10
gui/v2/actions/profile_data.py Executable file
View file

@ -0,0 +1,10 @@
def read_profile_data(data):
return data["Profile_1"]
def write_profile_data(data, payload):
data["Profile_1"].update(payload)
def clear_profile_data():
return {"Profile_1": {}}

61
gui/v2/actions/profile_order.py Executable file
View file

@ -0,0 +1,61 @@
from gui.v2.actions.config import default_gui_config, load_gui_config, save_gui_config
def _clean_ids(profile_ids):
ids = []
for profile_id in profile_ids or []:
try:
profile_id = int(profile_id)
except (TypeError, ValueError):
continue
if profile_id not in ids:
ids.append(profile_id)
return ids
def _load_order(gui_config_file):
if not gui_config_file:
return []
config = load_gui_config(gui_config_file)
if not config:
return []
return _clean_ids(config.get("profiles", {}).get("visual_order", []))
def _save_order(gui_config_file, order):
if not gui_config_file:
return
config = load_gui_config(gui_config_file)
if config is None:
config = default_gui_config()
if "profiles" not in config:
config["profiles"] = {}
config["profiles"]["visual_order"] = _clean_ids(order)
save_gui_config(gui_config_file, config)
def normalize_profile_order(gui_config_file, existing_ids):
existing_order = _clean_ids(existing_ids)
existing = set(existing_order)
saved_order = [profile_id for profile_id in _load_order(
gui_config_file) if profile_id in existing]
ordered = saved_order + [
profile_id for profile_id in existing_order if profile_id not in saved_order]
_save_order(gui_config_file, ordered)
return ordered
def append_profile_to_visual_order(gui_config_file, profile_id, existing_ids):
try:
profile_id = int(profile_id)
except (TypeError, ValueError):
return
existing_order = [
existing_id for existing_id in _clean_ids(existing_ids) if existing_id != profile_id]
existing = set(existing_order)
saved_order = [
existing_id for existing_id in _load_order(gui_config_file) if existing_id in existing]
ordered = saved_order + [
existing_id for existing_id in existing_order if existing_id not in saved_order]
ordered.append(profile_id)
_save_order(gui_config_file, ordered)

View file

@ -0,0 +1,9 @@
from datetime import datetime, timezone, timedelta
from core.controllers.ConfigurationController import ConfigurationController
def should_be_synchronized():
current_datetime = datetime.today().astimezone(timezone.utc)
last_synced_at = ConfigurationController.get_last_synced_at()
return last_synced_at is None or (current_datetime - last_synced_at) >= timedelta(days=30)

19
gui/v2/actions/sync.py Executable file
View file

@ -0,0 +1,19 @@
def generate_grid_positions(num_items):
positions = []
start_x = 395
start_y = 90
button_width = 185
button_height = 75
h_spacing = 10
v_spacing = 105
for i in range(num_items):
col = i % 2
row = i // 2
x = start_x + (col * (button_width + h_spacing))
y = start_y + (row * v_spacing)
positions.append((x, y, button_width, button_height))
return positions

View file

@ -0,0 +1,57 @@
import logging
from datetime import datetime, timezone
from gui.v2.actions.config import load_gui_config, save_gui_config, default_gui_config
def save_ticket_verification_failure(gui_config_file, result):
try:
failed_validations = result.get('failed_validations', []) if isinstance(result, dict) else []
if not failed_validations:
return False
config = load_gui_config(gui_config_file)
if config is None:
config = default_gui_config()
if "tickets" not in config:
config["tickets"] = {}
config["tickets"]["failed_verification"] = {
"message": result.get('message', 'verification_failed'),
"how_many_failed": result.get('how_many_failed', len(failed_validations)),
"failed_validations": list(failed_validations),
"updated_at": datetime.now(timezone.utc).isoformat(),
}
save_gui_config(gui_config_file, config)
return True
except Exception as e:
logging.error(f"Error saving ticket verification failure: {e}")
return False
def get_ticket_verification_failure(gui_config_file):
try:
config = load_gui_config(gui_config_file)
if not config:
return None
failure = config.get("tickets", {}).get("failed_verification")
if not isinstance(failure, dict):
return None
failed_validations = failure.get("failed_validations")
if not failed_validations:
return None
return failure
except Exception as e:
logging.error(f"Error loading ticket verification failure: {e}")
return None
def clear_ticket_verification_failure(gui_config_file):
try:
config = load_gui_config(gui_config_file)
if not config or "tickets" not in config:
return
config["tickets"].pop("failed_verification", None)
save_gui_config(gui_config_file, config)
except Exception as e:
logging.error(f"Error clearing ticket verification failure: {e}")

View file

Binary file not shown.

View file

@ -0,0 +1,76 @@
from core.controllers.ProfileController import ProfileController
class ConnectionManager:
def __init__(self):
self._connected_profiles = set()
self._is_synced = False
self._is_profile_being_enabled = {}
self.profile_button_objects = {}
self.available_resolutions = ['800x600', '1024x760', '1152x1080', '1280x1024',
'1920x1080', '2560x1440', '2560x1600', '1920x1440', '1792x1344', '2048x1152']
self._location_list = []
self._browser_list = []
def get_profile_button_objects(self, profile_id):
return self.profile_button_objects.get(profile_id, None)
def set_profile_button_objects(self, profile_id, profile_button_objects):
self.profile_button_objects[profile_id] = profile_button_objects
def get_available_resolutions(self, profile_id):
profile = ProfileController.get(profile_id)
if profile and hasattr(profile, 'resolution') and profile.resolution:
self.available_resolutions.append(profile.resolution)
return self.available_resolutions
def add_custom_resolution(self, resolution):
self.available_resolutions.append(resolution)
def add_connected_profile(self, profile_id):
self._connected_profiles.add(profile_id)
def remove_connected_profile(self, profile_id):
self._connected_profiles.discard(profile_id)
def is_profile_connected(self, profile_id):
return profile_id in self._connected_profiles
def get_connected_profiles(self):
return list(self._connected_profiles)
def set_synced(self, status):
self._is_synced = status
def is_synced(self):
return self._is_synced
def get_location_info(self, location_key):
if not location_key:
return None
try:
country_code, location_code = location_key.split('_')
for location in self._location_list:
if location.country_code == country_code and location.code == location_code:
return location
return None
except (ValueError, AttributeError):
return None
def store_locations(self, locations):
self._location_list = locations
def store_browsers(self, browsers):
self._browser_list = browsers
def get_browser_list(self):
return self._browser_list
def get_location_list(self):
if self.is_synced():
location_names = [
f'{location.country_code}_{location.code}' for location in self._location_list]
return location_names
else:
return []

View file

@ -0,0 +1,33 @@
import importlib
from gui.v2.infrastructure.page_registry import PAGE_REGISTRY
class Navigator:
def __init__(self, page_stack, custom_window):
self.page_stack = page_stack
self.custom_window = custom_window
self._instances = {}
def navigate(self, name):
page = self._instances.get(name)
if page is None:
if name not in PAGE_REGISTRY:
self._warn_not_migrated(name)
return
module_path, class_name = PAGE_REGISTRY[name]
module = importlib.import_module(module_path)
cls = getattr(module, class_name)
page = cls(self.page_stack, self.custom_window)
self.page_stack.addWidget(page)
self._instances[name] = page
self.page_stack.setCurrentIndex(self.page_stack.indexOf(page))
def get_cached(self, name):
return self._instances.get(name)
def _warn_not_migrated(self, name):
try:
self.custom_window.update_status(f"Page '{name}' not migrated yet.")
except Exception:
pass

View file

@ -0,0 +1,32 @@
PAGE_REGISTRY = {
"blank": ("gui.v2.ui.pages._blank_page", "BlankPage"),
"welcome": ("gui.v2.ui.pages.welcome_page", "WelcomePage"),
"menu": ("gui.v2.ui.pages.menu_page", "MenuPage"),
"protocol": ("gui.v2.ui.pages.protocol_page", "ProtocolPage"),
"hidetor": ("gui.v2.ui.pages.hidetor_page", "HidetorPage"),
"residential": ("gui.v2.ui.pages.residential_page", "ResidentialPage"),
"tor": ("gui.v2.ui.pages.tor_page", "TorPage"),
"connection": ("gui.v2.ui.pages.connection_page", "ConnectionPage"),
"location": ("gui.v2.ui.pages.location_page", "LocationPage"),
"screen": ("gui.v2.ui.pages.screen_page", "ScreenPage"),
"resume": ("gui.v2.ui.pages.resume_page", "ResumePage"),
"browser": ("gui.v2.ui.pages.browser_page", "BrowserPage"),
"editor": ("gui.v2.ui.pages.editor_page", "EditorPage"),
"install_system_package": ("gui.v2.ui.pages.install_system_package", "InstallSystemPackage"),
"wireguard": ("gui.v2.ui.pages.wireguard_page", "WireGuardPage"),
"sync_screen": ("gui.v2.ui.pages.sync_screen", "SyncScreen"),
"settings": ("gui.v2.ui.pages.settings_page", "Settings"),
"id": ("gui.v2.ui.pages.id_page", "IdPage"),
"payment_confirmed": ("gui.v2.ui.pages.payment_confirmed_page", "PaymentConfirmed"),
"systemwide_prompt": ("gui.v2.ui.pages.systemwide_prompt_page", "SystemwidePromptPage"),
"policy_suggestion": ("gui.v2.ui.pages.policy_suggestion_page", "PolicySuggestionPage"),
"duration_selection": ("gui.v2.ui.pages.duration_selection_page", "DurationSelectionPage"),
"currency_selection": ("gui.v2.ui.pages.currency_selection_page", "CurrencySelectionPage"),
"payment_details": ("gui.v2.ui.pages.payment_details_page", "PaymentDetailsPage"),
"plan_picker": ("gui.v2.ui.pages.plan_picker_page", "PlanPickerPage"),
"ticket_crypto_picker": ("gui.v2.ui.pages.ticket_crypto_picker_page", "TicketCryptoPickerPage"),
"ticket_prep": ("gui.v2.ui.pages.ticket_prep_page", "TicketPrepPage"),
"ticket_or_billing_choice": ("gui.v2.ui.pages.ticket_or_billing_choice_page", "TicketOrBillingChoicePage"),
"fast_registration": ("gui.v2.ui.pages.fast_registration_page", "FastRegistrationPage"),
"fast_mode_prompt": ("gui.v2.ui.pages.fast_mode_prompt_page", "FastModePromptPage"),
}

View file

@ -0,0 +1,74 @@
from core.observers.ApplicationVersionObserver import ApplicationVersionObserver
from core.observers.ClientObserver import ClientObserver
from core.observers.ConnectionObserver import ConnectionObserver
from core.observers.InvoiceObserver import InvoiceObserver
from core.observers.ProfileObserver import ProfileObserver
from core.observers.TicketObserver import TicketObserver
from core.controllers.ApplicationController import ApplicationController
application_version_observer = ApplicationVersionObserver()
client_observer = ClientObserver()
connection_observer = ConnectionObserver()
invoice_observer = InvoiceObserver()
profile_observer = ProfileObserver()
ticket_observer = TicketObserver()
def setup_observers(update_status):
profile_observer.subscribe(
'created', lambda event: update_status('Profile Created'))
profile_observer.subscribe(
'destroyed', lambda event: update_status('Profile destroyed'))
client_observer.subscribe(
'synchronizing', lambda event: update_status('Sync in progress...'))
client_observer.subscribe(
'synchronized', lambda event: update_status('Sync complete'))
client_observer.subscribe(
'updating', lambda event: update_status('Updating client...'))
client_observer.subscribe('update_progressing', lambda event: update_status(
f'Current progress: {event.meta.get('progress'):.2f}%'))
client_observer.subscribe('updated', lambda event: update_status(
'Restart client to apply update.'))
application_version_observer.subscribe('downloading', lambda event: update_status(
f'Downloading {ApplicationController.get(event.subject.application_code).name}'))
application_version_observer.subscribe('download_progressing', lambda event: update_status(
f'Downloading {ApplicationController.get(event.subject.application_code).name} {event.meta.get('progress'):.2f}%'))
application_version_observer.subscribe('downloaded', lambda event: update_status(
f'Downloaded {ApplicationController.get(event.subject.application_code).name}'))
connection_observer.subscribe('connecting', lambda event: update_status(
f'[{event.subject.get("attempt_count")}/{event.subject.get("maximum_number_of_attempts")}] Performing connection attempt...'))
connection_observer.subscribe('tor_bootstrapping', lambda event: update_status(
'Establishing Tor connection...'))
connection_observer.subscribe('tor_bootstrap_progressing', lambda event: update_status(
f'Bootstrapping Tor {event.meta.get('progress'):.2f}%'))
connection_observer.subscribe(
'tor_bootstrapped', lambda event: update_status('Tor connection established.'))
ticket_observer.subscribe('connecting', lambda event: update_status('Connecting to ticket server...'))
ticket_observer.subscribe('sync_done', lambda event: update_status('Ticket prices synced.'))
ticket_observer.subscribe('waiting', lambda event: update_status('Waiting for payment...'))
ticket_observer.subscribe('paid', lambda event: update_status('Payment received.'))
ticket_observer.subscribe('ticket_ready', lambda event: update_status('Ticket ready.'))
ticket_observer.subscribe('used', lambda event: update_status('Ticket used.'))
ticket_observer.subscribe('connection_error', lambda event: update_status('Ticket server connection error.'))
ticket_observer.subscribe('failed_output', lambda event: update_status(f'{event.subject if event.subject else ""}'))
ticket_observer.subscribe('failed_input', lambda event: update_status(f'{event.subject if event.subject else ""}'))
ticket_observer.subscribe('unknown_error', lambda event: update_status('Unknown ticket error.'))
ticket_observer.subscribe('error', lambda event: update_status(f'Ticket error: {event.subject if event.subject else ""}'))
return {
'application_version_observer': application_version_observer,
'client_observer': client_observer,
'connection_observer': connection_observer,
'invoice_observer': invoice_observer,
'profile_observer': profile_observer,
'ticket_observer': ticket_observer,
}

0
gui/v2/ui/__init__.py Executable file
View file

Binary file not shown.

0
gui/v2/ui/builders/__init__.py Executable file
View file

View file

@ -0,0 +1,54 @@
import os
from PyQt6.QtWidgets import QLabel, QPushButton
from PyQt6.QtGui import QIcon
from PyQt6.QtCore import Qt
def create_bottom_section(parent, btn_path, client_version, is_sync_urgent):
status_label = QLabel(parent)
status_label.setGeometry(15, 518, 780, 20)
status_label.setAlignment(
Qt.AlignmentFlag.AlignLeft | Qt.AlignmentFlag.AlignVCenter)
status_label.setStyleSheet(
"color: rgb(0, 255, 255); font-size: 16px;")
status_label.setText(
f"Status: Client version {client_version}")
tor_text = QLabel("Billing & Browsers", parent)
tor_text.setGeometry(532, 541, 150, 20)
tor_text.setStyleSheet("""
QLabel {
color: cyan;
font-size: 11px;
font-weight: bold;
}
""")
toggle_button = QPushButton(parent)
toggle_button.setGeometry(670, 541, 50, 22)
toggle_button.setCheckable(True)
tor_icon = QLabel(parent)
tor_icon.setGeometry(730, 537, 25, 25)
sync_button = QPushButton(parent)
sync_button.setGeometry(771, 541, 22, 22)
sync_button.setObjectName('sync_button')
if is_sync_urgent:
sync_icon = QIcon(os.path.join(btn_path, 'icon_sync_urgent.png'))
else:
sync_icon = QIcon(os.path.join(btn_path, 'icon_sync.png'))
sync_button.setToolTip('Sync')
sync_button.setIcon(sync_icon)
sync_button.setIconSize(sync_button.size())
return {
'status_label': status_label,
'tor_text': tor_text,
'toggle_button': toggle_button,
'tor_icon': tor_icon,
'sync_button': sync_button,
}

View file

@ -0,0 +1,34 @@
import os
from PyQt6.QtWidgets import QSystemTrayIcon
from PyQt6.QtGui import QIcon
from PyQt6.QtCore import QSize
def init_system_tray(parent, icon_base_path):
if not QSystemTrayIcon.isSystemTrayAvailable():
return None
tray_icon = QIcon()
window_icon = QIcon()
tray_sizes = [22, 24]
for size in tray_sizes:
icon_path = os.path.join(
icon_base_path, f"hv-icon-{size}x{size}.png")
if os.path.exists(icon_path):
tray_icon.addFile(icon_path, QSize(size, size))
window_sizes = [32, 48, 64, 128]
for size in window_sizes:
icon_path = os.path.join(
icon_base_path, f"hv-icon-{size}x{size}.png")
if os.path.exists(icon_path):
window_icon.addFile(icon_path, QSize(size, size))
tray = QSystemTrayIcon(parent)
tray.setToolTip('HydraVeil')
tray.setIcon(tray_icon)
tray.setVisible(True)
parent.setWindowIcon(window_icon)
return tray

View file

@ -0,0 +1,30 @@
import os
from PyQt6.QtWidgets import QLabel, QWidget
from PyQt6.QtCore import QTimer
def create_top_section(parent, css_path):
central_widget = QWidget(parent)
central_widget.setMaximumSize(800, 600)
central_widget.setGeometry(0, 0, 850, 600)
central_widget.setObjectName("centralwidget")
label = QLabel(central_widget)
label.setGeometry(0, 0, 800, 570)
css_file = os.path.join(css_path, 'look.css')
parent.setStyleSheet(open(css_file).read()
if os.path.exists(css_file) else '')
marquee_timer = QTimer(parent)
marquee_timer.timeout.connect(parent.update_marquee)
return {
'central_widget': central_widget,
'label': label,
'marquee_timer': marquee_timer,
'text_start_x': 15,
'text_end_x': 420,
'text_width': 405,
}

86
gui/v2/ui/pages/Page.py Executable file
View file

@ -0,0 +1,86 @@
import os
from PyQt6.QtWidgets import QWidget, QLabel, QPushButton
from PyQt6.QtGui import QIcon
from PyQt6 import QtCore
class Page(QWidget):
def __init__(self, name, page_stack, custom_window, parent=None):
super().__init__(parent)
self.custom_window = custom_window
self.btn_path = custom_window.btn_path
self.name = name
self.page_stack = page_stack
self.init_ui()
self.selected_profiles = []
self.selected_wireguard = []
self.selected_residential = []
self.buttons = []
def add_selected_profile(self, profile):
self.selected_profiles.clear()
self.selected_wireguard.clear()
self.selected_residential.clear()
self.selected_profiles.append(profile)
def init_ui(self):
self.title = QLabel(self)
self.title.setAlignment(QtCore.Qt.AlignmentFlag.AlignCenter)
self.title.setObjectName("titles")
self.display = QLabel(self)
self.display.setObjectName("display")
self.display.setAlignment(QtCore.Qt.AlignmentFlag.AlignCenter)
buttons_info = [
(QPushButton, "button_back", "back", (660, 534, 48, 35)),
(QPushButton, "button_next", "next", (720, 534, 48, 35)),
(QPushButton, "button_reverse", "back", (660, 534, 48, 35)),
(QPushButton, "button_go", "next", (720, 534, 48, 35)),
(QPushButton, "button_apply", "apply", (720, 534, 48, 35)),
]
for button_type, object_name, icon_name, geometry in buttons_info:
button = button_type(self)
button.setObjectName(object_name)
button.setGeometry(*geometry)
button.setIcon(
QIcon(os.path.join(self.btn_path, f"{icon_name}.png")))
button.setIconSize(QtCore.QSize(48, 35))
button.setVisible(False)
if object_name == "button_back":
self.button_back = button
self.button_back.clicked.connect(self.gestionar_back)
if object_name == "button_next":
self.button_next = button
self.button_next.clicked.connect(self.gestionar_next)
if object_name == "button_reverse":
self.button_reverse = button
self.button_reverse.clicked.connect(self.limpiar)
if object_name == "button_go":
self.button_go = button
self.button_go.clicked.connect(self.limpiar)
if object_name == "button_apply":
self.button_apply = button
def gestionar_back(self):
current_index = self.page_stack.currentIndex()
if current_index == 18:
return
if current_index > 0:
self.page_stack.setCurrentIndex(current_index - 1)
def gestionar_next(self):
current_index = self.page_stack.currentIndex()
next_index = (current_index + 1) % self.page_stack.count()
self.page_stack.setCurrentIndex(next_index)
self.limpiar()
def limpiar(self):
self.display.clear()
for boton in self.buttons:
boton.setChecked(False)
self.button_next.setVisible(False)

0
gui/v2/ui/pages/__init__.py Executable file
View file

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

17
gui/v2/ui/pages/_blank_page.py Executable file
View file

@ -0,0 +1,17 @@
from PyQt6.QtWidgets import QWidget, QVBoxLayout, QLabel
from PyQt6.QtCore import Qt
class BlankPage(QWidget):
def __init__(self, page_stack, custom_window, parent=None):
super().__init__(parent)
self.page_stack = page_stack
self.custom_window = custom_window
layout = QVBoxLayout(self)
layout.setAlignment(Qt.AlignmentFlag.AlignCenter)
label = QLabel("v2 — no page migrated yet")
label.setStyleSheet("color: #888888; font-size: 14px;")
label.setAlignment(Qt.AlignmentFlag.AlignCenter)
layout.addWidget(label)

241
gui/v2/ui/pages/browser_page.py Executable file
View file

@ -0,0 +1,241 @@
import os
from PyQt6.QtWidgets import (
QApplication, QWidget, QPushButton, QButtonGroup, QScrollArea
)
from PyQt6.QtGui import QPixmap, QIcon, QPainter, QColor, QFont
from PyQt6.QtCore import Qt
from PyQt6 import QtCore
from gui.v2.ui.pages.Page import Page
class BrowserPage(Page):
def __init__(self, page_stack, main_window, parent=None):
super().__init__("Browser", page_stack, main_window, parent)
self.btn_path = main_window.btn_path
self.update_status = main_window
self.selected_browser_icon = None
self.display.setGeometry(QtCore.QRect(5, 10, 390, 520))
self.title.setGeometry(395, 40, 380, 40)
self.title.setText("Pick a Browser")
self.button_back.setVisible(True)
cm = main_window.connection_manager
if cm.is_synced():
from gui.v2.actions.sync import generate_grid_positions
browsers = cm.get_browser_list()
positions = generate_grid_positions(len(browsers))
available = [(QPushButton, brw, positions[i]) for i, brw in enumerate(browsers)]
self.create_interface_elements(available)
def create_interface_elements(self, available_browsers):
self._setup_scroll_area()
button_widget = self._create_button_widget()
self._populate_button_widget(button_widget, available_browsers)
self.scroll_area.setWidget(button_widget)
def _setup_scroll_area(self):
self.scroll_area = QScrollArea(self)
self.scroll_area.setGeometry(400, 90, 385, 400)
self.scroll_area.setWidgetResizable(True)
self.scroll_area.setHorizontalScrollBarPolicy(
Qt.ScrollBarPolicy.ScrollBarAlwaysOff)
self._apply_scroll_area_style()
def _apply_scroll_area_style(self):
self.scroll_area.setStyleSheet("""
QScrollArea {
background: transparent;
border: none;
}
QScrollBar:vertical {
border: 1px solid white;
background: white;
width: 10px;
margin: 0px 0px 0px 0px;
}
QScrollBar::handle:vertical {
background: cyan;
min-height: 0px;
}
QScrollBar::add-line:vertical {
height: 0px;
subcontrol-position: bottom;
subcontrol-origin: margin;
}
QScrollBar::sub-line:vertical {
height: 0px;
subcontrol-position: top;
subcontrol-origin: margin;
}
""")
def _create_button_widget(self):
button_widget = QWidget()
button_widget.setStyleSheet("background: transparent;")
self.buttonGroup = QButtonGroup(self)
self.buttons = []
return button_widget
def _populate_button_widget(self, button_widget, available_browsers):
dimensions = self._get_button_dimensions()
total_height = self._create_browser_buttons(
button_widget, available_browsers, dimensions)
self._set_final_widget_size(button_widget, dimensions, total_height)
def _get_button_dimensions(self):
return {
'width': 185,
'height': 75,
'spacing': 20,
'x_offset': 195
}
def _create_browser_buttons(self, button_widget, browser_icons, dims):
total_height = 0
for browser_tuple in browser_icons:
_, browser_string, geometry = browser_tuple
x_coord = geometry[0] - 395
y_coord = geometry[1] - 90
self._create_single_button(
button_widget, browser_string, x_coord, y_coord, dims)
total_height = max(total_height, y_coord + dims['height'])
return total_height
def _create_single_button(self, parent, icon_name, x_coord, y_coord, dims):
button = QPushButton(parent)
button.setFixedSize(dims['width'], dims['height'])
button.move(x_coord, y_coord)
button.setIconSize(button.size())
button.setCheckable(True)
browser_name, version = icon_name.split(':')
button.setIcon(QIcon(BrowserPage.create_browser_button_image(
f"{browser_name} {version}", self.btn_path)))
if button.icon().isNull():
fallback_path = os.path.join(
self.btn_path, "default_browser_button.png")
button.setIcon(QIcon(BrowserPage.create_browser_button_image(
f"{browser_name} {version}", fallback_path, True)))
self._apply_button_style(button)
self.buttons.append(button)
self.buttonGroup.addButton(button)
button.clicked.connect(
lambda _, b=icon_name: self.show_browser(b.replace(':', ' ')))
@staticmethod
def create_browser_button_image(browser_text, btn_path, is_fallback=False):
if ':' in browser_text:
browser_name, version = browser_text.split(':', 1)
else:
browser_name, version = browser_text.split(
)[0], ' '.join(browser_text.split()[1:])
if is_fallback:
base_image = QPixmap(btn_path)
else:
base_image = QPixmap(os.path.join(
btn_path, f"{browser_name}_button.png"))
if base_image.isNull():
base_image = QPixmap(185, 75)
base_image.fill(QColor(44, 62, 80))
painter = QPainter(base_image)
if painter.isActive():
BrowserPage._setup_version_font(painter, version, base_image)
if is_fallback:
BrowserPage._setup_browser_name(
painter, browser_name, base_image)
painter.end()
return base_image
@staticmethod
def _setup_browser_name(painter, browser_name, base_image):
font_size = 13
app_font = QApplication.font()
app_font.setPointSize(font_size)
app_font.setWeight(QFont.Weight.Bold)
painter.setFont(app_font)
painter.setPen(QColor(0, 255, 255))
text_rect = painter.fontMetrics().boundingRect(browser_name)
while text_rect.width() > base_image.width() * 0.6 and font_size > 6:
font_size -= 1
x = (base_image.width() - text_rect.width()) // 2 - 30
y = (base_image.height() + text_rect.height()) // 2 - 10
painter.drawText(x, y, browser_name)
@staticmethod
def _setup_version_font(painter, version, base_image):
font_size = 11
painter.setFont(QFont('Arial', font_size))
painter.setPen(QColor(0x88, 0x88, 0x88))
text_rect = painter.fontMetrics().boundingRect(version)
while text_rect.width() > base_image.width() * 0.6 and font_size > 6:
font_size -= 1
painter.setFont(QFont('Arial', font_size))
text_rect = painter.fontMetrics().boundingRect(version)
x = (base_image.width() - text_rect.width()) // 2 - 27
y = (base_image.height() + text_rect.height()) // 2 + 10
painter.drawText(x, y, version)
def _apply_button_style(self, button):
button.setStyleSheet("""
QPushButton {
background: transparent;
border: none;
}
QPushButton:hover {
background-color: rgba(200, 200, 200, 30);
}
QPushButton:checked {
background-color: rgba(200, 200, 200, 50);
}
""")
def _set_final_widget_size(self, widget, dims, total_height):
widget.setFixedSize(dims['width'] * 2 + 5,
total_height + dims['spacing'])
def show_browser(self, browser):
browser_name, version = browser.split(
)[0], ' '.join(browser.split()[1:])
try:
base_image = self._create_browser_display_image(
browser_name, version)
self.display.setPixmap(base_image.scaled(
self.display.size(), Qt.AspectRatioMode.KeepAspectRatio))
except Exception:
pass
self.selected_browser_icon = browser
self.button_next.setVisible(True)
self.update_swarp_json()
def _create_browser_display_image(self, browser_name, version):
base_image = QPixmap(os.path.join(
self.btn_path, f"{browser_name}_icon.png"))
painter = QPainter(base_image)
painter.setFont(QFont('Arial', 25))
painter.setPen(QColor(0, 255, 255))
text_rect = painter.fontMetrics().boundingRect(version)
x = (base_image.width() - text_rect.width()) // 2
y = base_image.height() - 45
painter.drawText(x, y, version)
painter.end()
return base_image
def update_swarp_json(self):
inserted_data = {
"browser": self.selected_browser_icon
}
self.update_status.write_data(inserted_data)
def gestionar_next(self):
self.custom_window.navigator.navigate("screen")

View file

@ -0,0 +1,48 @@
import os
from PyQt6.QtWidgets import QLabel
from PyQt6.QtGui import QPixmap
from PyQt6 import QtCore
from gui.v2.ui.pages.Page import Page
class ConnectionPage(Page):
def __init__(self, page_stack, main_window=None, parent=None):
super().__init__("Connection", page_stack, main_window, parent)
self.create_interface_elements()
def create_interface_elements(self):
self.display.setGeometry(QtCore.QRect(5, 50, 390, 430))
self.title.setGeometry(QtCore.QRect(465, 40, 300, 20))
self.title.setText("Pick a TOR")
labels_info = [
("server", None, (410, 115)),
("", "rr2", (560, 115)),
("port", None, (410, 205)),
("", "rr2", (560, 205)),
("username", None, (410, 295)),
("", "rr2", (560, 295)),
("password", None, (410, 385)),
("", "rr2", (560, 385))
]
for j, (text, image_name, position) in enumerate(labels_info):
label = QLabel(self)
label.setAlignment(QtCore.Qt.AlignmentFlag.AlignCenter)
if image_name:
label.setGeometry(position[0], position[1], 220, 50)
else:
label.setGeometry(position[0], position[1], 140, 50)
label.setStyleSheet(
"background-color: rgba(255, 255, 255, 51); padding: 3px;")
if image_name:
pixmap = QPixmap(os.path.join(
self.btn_path, f"{image_name}.png"))
label.setPixmap(pixmap)
label.setScaledContents(True)
else:
label.setText(text)

View file

@ -0,0 +1,65 @@
import os
from PyQt6.QtWidgets import QButtonGroup, QLabel, QPushButton
from PyQt6.QtGui import QIcon
from PyQt6.QtCore import QSize
from PyQt6 import QtCore
from gui.v2.ui.pages.Page import Page
class CurrencySelectionPage(Page):
def __init__(self, page_stack, main_window=None, parent=None):
super().__init__("Select Currency", page_stack, main_window, parent)
self.update_status = main_window
self.selected_duration = None
self.create_interface_elements()
self.button_reverse.setVisible(True)
self.button_next.setVisible(False)
def create_interface_elements(self):
self.title = QLabel("Payment Method", self)
self.title.setGeometry(QtCore.QRect(510, 30, 250, 40))
self.title.setAlignment(QtCore.Qt.AlignmentFlag.AlignCenter)
self.button_reverse.clicked.connect(self.reverse)
button_info = [
("monero", (545, 75)),
("bitcoin", (545, 290)),
("lightnering", (545, 180)),
("litecoin", (545, 395))
]
self.buttonGroup = QButtonGroup(self)
self.buttons = []
for j, (icon_name, position) in enumerate(button_info):
button = QPushButton(self)
button.setGeometry(position[0], position[1], 185, 75)
button.setIconSize(QSize(190, 120))
button.setCheckable(True)
self.buttons.append(button)
self.buttonGroup.addButton(button, j)
button.setIcon(
QIcon(os.path.join(self.btn_path, f"{icon_name}.png")))
button.clicked.connect(self.on_currency_selected)
def on_currency_selected(self):
self.custom_window.navigator.navigate("payment_details")
payment_details_page = self.custom_window.navigator.get_cached("payment_details")
if payment_details_page is not None:
payment_details_page.selected_duration = self.selected_duration
payment_details_page.selected_currency = self.get_selected_currency()
payment_details_page.check_invoice()
self.update_status.update_status('Loading payment details...')
def get_selected_currency(self):
selected_button = self.buttonGroup.checkedButton()
if selected_button:
index = self.buttonGroup.id(selected_button)
currencies = ["monero", "bitcoin", "lightning", "litecoin"]
return currencies[index]
return None
def reverse(self):
self.custom_window.navigator.navigate("duration_selection")

View file

@ -0,0 +1,92 @@
from PyQt6.QtWidgets import QComboBox, QLabel, QPushButton
from PyQt6 import QtCore
from gui.v2.ui.pages.Page import Page
class DurationSelectionPage(Page):
def __init__(self, page_stack, main_window=None, parent=None):
super().__init__("Select Duration", page_stack, main_window, parent)
self.update_status = main_window
self.create_interface_elements()
self.button_reverse.setVisible(True)
def create_interface_elements(self):
self.title = QLabel("Select Duration", self)
self.title.setGeometry(QtCore.QRect(530, 30, 210, 40))
self.title.setAlignment(QtCore.Qt.AlignmentFlag.AlignCenter)
self.button_reverse.clicked.connect(self.reverse)
self.combo_box = QComboBox(self)
self.combo_box.setGeometry(200, 200, 400, 60)
self.combo_box.addItems(
["1 month (€1.50)", "3 months (€4.30)", "6 months (€8)", "12 months (€16)"])
self.combo_box.setEditable(False)
self.combo_box.setMaxVisibleItems(4)
self.combo_box.setDuplicatesEnabled(True)
self.combo_box.setPlaceholderText("Select options")
self.combo_box.setStyleSheet("""
QComboBox {
font-size: 16px;
padding: 10px;
background-color: #000000;
color: #00ffff;
border: 2px solid #00ffff;
border-radius: 8px;
}
QComboBox::drop-down {
width: 30px;
background-color: #1a1a1a;
border-left: 1px solid #00ffff;
}
QComboBox::down-arrow {
width: 12px;
height: 12px;
color: #00ffff;
}
QComboBox QAbstractItemView {
background-color: #0a0a0a;
border: 1px solid #00ffff;
border-radius: 4px;
selection-background-color: #003333;
selection-color: #00ffff;
}
QComboBox QAbstractItemView::item {
padding: 8px;
border: none;
color: #00ffff;
background-color: #0a0a0a;
}
QComboBox QAbstractItemView::item:hover {
background-color: #1a1a1a;
color: #00ffff;
}
QComboBox QAbstractItemView::item:selected {
background-color: #003333;
color: #00ffff;
}
""")
self.next_button = QPushButton("Next", self)
self.next_button.setGeometry(350, 300, 100, 40)
self.next_button.setStyleSheet("""
QPushButton {
font-size: 18px;
padding: 10px;
border: 2px solid #ccc;
border-radius: 10px;
background-color: cyan;
color: black;
font-weight: bold;
}
""")
self.next_button.clicked.connect(self.show_next)
def show_next(self):
self.custom_window.navigator.navigate("currency_selection")
currency_page = self.custom_window.navigator.get_cached("currency_selection")
if currency_page is not None:
currency_page.selected_duration = self.combo_box.currentText()
def reverse(self):
self.custom_window.navigator.navigate("id")

817
gui/v2/ui/pages/editor_page.py Executable file
View file

@ -0,0 +1,817 @@
import os
from PyQt6.QtWidgets import (
QLabel, QPushButton, QLineEdit, QTextEdit, QFrame, QDialog,
QVBoxLayout, QHBoxLayout, QMessageBox, QGraphicsDropShadowEffect
)
from PyQt6.QtGui import QPixmap, QIcon, QPainter, QColor, QTransform
from PyQt6.QtCore import Qt, QSize, QTimer, QPointF
from PyQt6 import QtCore, QtGui
from core.controllers.ProfileController import ProfileController
from core.controllers.LocationController import LocationController
from gui.v2.ui.pages.Page import Page
from gui.v2.ui.pages.browser_page import BrowserPage
from gui.v2.ui.pages.location_page import LocationPage
from gui.v2.ui.pages.screen_page import ScreenPage
from gui.v2.ui.popups.confirmation_popup import ConfirmationPopup
class EditorPage(Page):
def __init__(self, page_stack, main_window):
super().__init__("Editor", page_stack, main_window)
self.page_stack = page_stack
self.update_status = main_window
self.connection_manager = main_window.connection_manager
self.labels = []
self.buttons = []
self.title.setGeometry(570, 40, 185, 40)
self.title.setText("Edit Profile")
self.button_apply.setVisible(True)
self.button_apply.clicked.connect(self.go_selected)
self.button_back.setVisible(True)
try:
self.button_back.clicked.disconnect()
except TypeError:
pass
self.button_back.clicked.connect(self.go_back)
self.name_handle = QLabel(self)
self.name_handle.setGeometry(110, 70, 400, 30)
self.name_handle.setStyleSheet('color: #cacbcb;')
self.name_handle.setText("Profile Name:")
self.name_hint = QLabel(self)
self.name_hint.setGeometry(265, 100, 190, 20)
self.name_hint.setStyleSheet(
'color: #888888; font-size: 10px; font-style: italic;')
self.name_hint.setText("Click here to edit profile name")
self.name_hint.setAlignment(QtCore.Qt.AlignmentFlag.AlignCenter)
self.name = QLineEdit(self)
self.name.setPlaceholderText("Enter name")
self.name.setGeometry(265, 70, 190, 30)
self.name.setStyleSheet(
"color: cyan; border: 1px solid #666666; border-radius: 3px; background-color: rgba(0, 0, 0, 0.3);")
self.name.setCursor(QtCore.Qt.CursorShape.IBeamCursor)
self.name.focusInEvent = lambda event: self.name_hint.hide()
self.name.focusOutEvent = lambda event: self.name_hint.show(
) if not self.name.text() else self.name_hint.hide()
self.name.textChanged.connect(
lambda text: self.name_hint.hide() if text else self.name_hint.show())
self.temp_changes = {}
self.original_values = {}
self.res_hint_shown = False
self.display.setGeometry(QtCore.QRect(0, 60, 540, 405))
self.display.hide()
self.garaje = QLabel(self)
self.garaje.setGeometry(QtCore.QRect(0, 70, 540, 460))
self.brow_disp = QLabel(self)
self.brow_disp.setGeometry(QtCore.QRect(0, 50, 540, 460))
self.brow_disp.setPixmap(
QPixmap(os.path.join(self.btn_path, "browser only.png")))
self.brow_disp.hide()
self.brow_disp.lower()
def _selected_profiles_str(self):
menu_page = self.find_menu_page()
if menu_page is not None and menu_page.selected_profiles:
return ', '.join(
[f"Profile_{profile['profile_number']}" for profile in menu_page.selected_profiles])
profile_id = getattr(self.update_status, 'current_profile_id', None)
if profile_id is not None:
return f"Profile_{int(profile_id)}"
return ''
def update_name_value(self):
original_name = self.data_profile.get('name', '')
new_name = self.name.text()
if original_name != new_name:
self.update_temp_value('name', new_name)
else:
pass
def go_back(self):
selected_profiles_str = self._selected_profiles_str()
if self.has_unsaved_changes(selected_profiles_str):
self.show_unsaved_changes_popup()
else:
self.custom_window.navigator.navigate("menu")
def find_menu_page(self):
return self.custom_window.navigator.get_cached("menu")
def find_resume_page(self):
return self.custom_window.navigator.get_cached("resume")
def find_protocol_page(self):
return self.custom_window.navigator.get_cached("protocol")
def showEvent(self, event):
super().showEvent(event)
self.res_hint_shown = False
self.extraccion()
def extraccion(self):
self.data_profile = {}
for label in self.labels:
label.deleteLater()
self.labels = []
for button in self.buttons:
button.deleteLater()
self.buttons = []
selected_profiles_str = self._selected_profiles_str()
menu_page = self.find_menu_page()
if menu_page:
new_profiles = ProfileController.get_all()
self.profiles_data = menu_page.match_core_profiles(
profiles_dict=new_profiles)
self.data_profile = self.profiles_data[selected_profiles_str].copy(
)
profile_id = int(selected_profiles_str.split('_')[1])
self.data_profile['id'] = profile_id
if selected_profiles_str in self.temp_changes:
for key, value in self.temp_changes[selected_profiles_str].items():
self.data_profile[key] = value
self.name.textChanged.connect(self.update_name_value)
self.verificate(self.data_profile, selected_profiles_str)
def verificate(self, data_profile, selected_profile_str):
protocol = data_profile.get("protocol", "")
try:
if protocol == "wireguard":
self.process_and_show_labels(data_profile, {
"protocol": ['wireguard', 'residential', 'hidetor'],
"connection": ['browser-only', 'system-wide'],
"location": self.connection_manager.get_location_list(),
"browser": self.connection_manager.get_browser_list(),
"dimentions": self.connection_manager.get_available_resolutions(data_profile.get('id', ''))
}, selected_profile_str)
elif protocol == "residential" or protocol == "hidetor":
self.process_and_show_labels(data_profile, {
"protocol": ['residential', 'wireguard', 'hidetor'],
"connection": ['tor', 'just proxy'],
"location": self.connection_manager.get_location_list(),
"browser": self.connection_manager.get_browser_list(),
"dimentions": self.connection_manager.get_available_resolutions(data_profile.get('id', ''))
}, selected_profile_str)
elif protocol == "open":
self.process_and_show_labels(data_profile, {
"protocol": ['open']
}, selected_profile_str)
except Exception as e:
print(f'An error occurred in verificate: {e}')
def process_and_show_labels(self, data_profile, parameters, selected_profile_str):
protocol = data_profile.get('protocol', "")
connection = data_profile.get('connection', "")
location = data_profile.get('location', "")
country_garaje = location
name = data_profile.get('name', "")
self.name.setText(name)
if protocol == "hidetor":
if connection == "just proxy":
self.display.setPixmap(QPixmap(os.path.join(
self.btn_path, f"icon_{location}.png")))
else:
self.display.setPixmap(QPixmap(os.path.join(
self.btn_path, f"hdtor_{location}.png")))
self.display.show()
self.display.setGeometry(0, 100, 400, 394)
self.display.setScaledContents(True)
self.garaje.hide()
self.brow_disp.hide()
if protocol == "wireguard":
self.display.setGeometry(0, 60, 540, 405)
self.display.show()
self.display.setPixmap(QPixmap(os.path.join(
self.btn_path, f"wireguard_{location}.png")))
self.garaje.hide()
if connection == "browser-only":
is_supported = data_profile.get('browser_supported', False)
image_name = "browser only.png" if is_supported else "unsupported_browser_only.png"
self.brow_disp.setPixmap(
QPixmap(os.path.join(self.btn_path, image_name)))
self.brow_disp.show()
else:
self.brow_disp.hide()
if protocol == "residential":
self.display.setGeometry(0, 60, 540, 405)
self.brow_disp.show()
self.garaje.show()
self.display.hide()
if country_garaje:
country_garaje = 'brazil'
self.garaje.setPixmap(QPixmap(os.path.join(
self.btn_path, f"{country_garaje} garaje.png")))
profile_id = data_profile.get('id')
profile_obj = ProfileController.get(profile_id)
location_info = None
if location:
try:
if '_' in location:
country_code, location_code = location.split('_', 1)
location_info = LocationController.get(
country_code, location_code)
except Exception:
location_info = None
operator_name = ""
l_name = ""
o_name = ""
n_key = ""
if location_info:
if hasattr(location_info, 'operator') and location_info.operator:
operator_name = location_info.operator.name
o_name = location_info.operator.name
n_key = location_info.operator.nostr_public_key
l_name = f"{location_info.country_name}, {location_info.name}" if hasattr(
location_info, 'country_name') else ""
if operator_name != 'Simplified Privacy' and operator_name != "" and protocol != 'hidetor':
text_color = "white"
if profile_obj and profile_obj.is_session_profile():
text_color = "black"
l_img = QLabel(self)
l_img.setGeometry(20, 125, 150, 150)
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.labels.append(l_img)
info_txt = QTextEdit(self)
info_txt.setGeometry(180, 120, 300, 250)
info_txt.setReadOnly(True)
info_txt.setFrameStyle(QFrame.Shape.NoFrame)
info_txt.setStyleSheet(
f"background: transparent; border: none; color: {text_color}; font-size: 19px;")
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.labels.append(info_txt)
nostr_txt = QTextEdit(self)
nostr_txt.setGeometry(20, 300, 450, 170)
nostr_txt.setReadOnly(True)
nostr_txt.setFrameStyle(QFrame.Shape.NoFrame)
nostr_txt.setStyleSheet(
f"background: transparent; border: none; color: {text_color}; font-size: 21px;")
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.labels.append(nostr_txt)
for i, key in enumerate(parameters.keys()):
if key == 'browser':
browser_value = f"{data_profile.get(key, '')}"
split_value = browser_value.split(':', 1)
browser_type = split_value[0]
browser_version = split_value[1] if len(
split_value) > 1 else ''
browser_value = f"{browser_type} {browser_version}"
if browser_version == '':
browser_version = data_profile.get('browser_version', '')
browser_value = f"{browser_type} {browser_version}"
if connection == 'system-wide' or not browser_value.strip():
base_image = QPixmap()
else:
base_image = BrowserPage.create_browser_button_image(
browser_value, self.btn_path)
if base_image.isNull():
fallback_path = os.path.join(
self.btn_path, "default_browser_button.png")
base_image = BrowserPage.create_browser_button_image(
browser_value, fallback_path, True)
elif key == 'location':
current_value = f"{data_profile.get(key, '')}"
image_path = os.path.join(
self.btn_path, f"button_{current_value}.png")
base_image = QPixmap(image_path)
locations = self.connection_manager.get_location_info(
current_value)
provider = locations.operator.name if locations and hasattr(
locations, 'operator') else None
fallback_path = os.path.join(
self.btn_path, "default_location_button.png")
if base_image.isNull():
if locations and hasattr(locations, 'country_name'):
location_name = locations.country_name
base_image = LocationPage.create_location_button_image(
location_name, fallback_path, provider)
else:
base_image = LocationPage.create_location_button_image(
current_value, fallback_path, provider)
else:
if locations and hasattr(locations, 'country_name'):
base_image = LocationPage.create_location_button_image(
f'{locations.country_code}_{locations.code}', fallback_path, provider, image_path)
else:
base_image = LocationPage.create_location_button_image(
current_value, fallback_path, provider, image_path)
elif key == 'dimentions':
current_value = f"{data_profile.get(key, '')}"
if current_value != 'None':
base_image = ScreenPage.create_resolution_button_image(
self, current_value)
else:
image_path = os.path.join(
self.btn_path, f"{data_profile.get(key, '')}_button.png")
current_value = data_profile.get(key, '')
base_image = QPixmap(image_path)
if key == 'dimentions':
label = QPushButton(self)
label.setGeometry(565, 90 + i * 80, 185, 75)
label.setFlat(True)
label.setStyleSheet("border: none; background: transparent;")
original_icon = QIcon(base_image)
label.setIconSize(QSize(185, 75))
label.setCursor(QtCore.Qt.CursorShape.PointingHandCursor)
label.clicked.connect(self.show_custom_res_dialog)
template_path = os.path.join(
self.btn_path, "resolution_template_button.png")
template_pixmap = QPixmap(template_path).scaled(
187, 75) if os.path.exists(template_path) else None
if not getattr(self, 'res_hint_shown', False) and connection != 'system-wide':
self.res_hint_shown = True
if template_pixmap:
label.setIcon(QIcon(template_pixmap))
else:
label.setIcon(QIcon())
hint_label = QLabel("Click here to customize", label)
hint_label.setGeometry(15, 0, 155, 75)
hint_label.setWordWrap(True)
hint_label.setAlignment(
QtCore.Qt.AlignmentFlag.AlignCenter)
hint_label.setStyleSheet(
"color: #00ffff; font-weight: bold; font-size: 16px; background: transparent;")
hint_label.setCursor(
QtCore.Qt.CursorShape.PointingHandCursor)
hint_label.setAttribute(
QtCore.Qt.WidgetAttribute.WA_TransparentForMouseEvents)
shadow = QGraphicsDropShadowEffect(hint_label)
shadow.setBlurRadius(20)
shadow.setColor(QColor(0, 255, 255))
shadow.setOffset(0, 0)
hint_label.setGraphicsEffect(shadow)
def restore():
try:
if hint_label and not hint_label.isHidden():
hint_label.hide()
label.setIcon(original_icon)
except:
pass
QTimer.singleShot(3500, restore)
else:
label.setIcon(original_icon)
else:
label = QLabel(key, self)
label.setGeometry(565, 90 + i * 80, 185, 75)
label.setPixmap(base_image)
label.setScaledContents(True)
label.show()
self.labels.append(label)
try:
if key == 'browser':
value_index = [item.replace(
':', ' ') for item in parameters[key]].index(browser_value)
else:
value_index = parameters[key].index(current_value)
except ValueError:
value_index = 0
prev_button = QPushButton(self)
prev_button.setGeometry(535, 90 + i * 80, 30, 75)
prev_button.clicked.connect(
lambda _, k=key, idx=value_index: self.show_previous_value(k, idx, parameters))
prev_button.show()
icon_path = os.path.join(self.btn_path, f"UP_button.png")
icon = QPixmap(icon_path)
transform = QTransform().rotate(180)
rotated_icon = icon.transformed(transform)
prev_button.setIcon(QIcon(rotated_icon))
prev_button.setIconSize(prev_button.size())
if (key == 'location' or key == 'browser') and not self.connection_manager.is_synced():
w = prev_button.width()
h = prev_button.height()
outline_pix = QPixmap(w, h)
outline_pix.fill(Qt.GlobalColor.transparent)
painter = QPainter(outline_pix)
painter.setRenderHint(QPainter.RenderHint.Antialiasing)
pen = QtGui.QPen(QColor(0, 255, 0))
pen.setWidth(3)
painter.setPen(pen)
painter.setBrush(Qt.BrushStyle.NoBrush)
left_points = [
QPointF(w*0.65, h*0.15), QPointF(w*0.25, h*0.50), QPointF(w*0.65, h*0.85)]
painter.drawPolygon(QtGui.QPolygonF(left_points))
painter.end()
prev_button.setIcon(QIcon(outline_pix))
prev_button.setIconSize(prev_button.size())
if (key == 'location' or key == 'browser') and not self.connection_manager.is_synced():
prev_button.setStyleSheet("""
QPushButton {
background-color: transparent;
border: none;
}
QPushButton:hover {
background-color: rgba(0, 255, 0, 0.1);
border-radius: 3px;
}
QPushButton:pressed {
background-color: rgba(0, 255, 0, 0.2);
border-radius: 3px;
}
""")
self.buttons.append(prev_button)
next_button = QPushButton(self)
next_button.setGeometry(750, 90 + i * 80, 30, 75)
next_button.clicked.connect(
lambda _, k=key, idx=value_index: self.show_next_value(k, idx, parameters))
next_button.show()
self.buttons.append(next_button)
next_button.setIcon(
QIcon(os.path.join(self.btn_path, f"UP_button.png")))
next_button.setIconSize(next_button.size())
if (key == 'location' or key == 'browser') and not self.connection_manager.is_synced():
w = next_button.width()
h = next_button.height()
outline_pix_r = QPixmap(w, h)
outline_pix_r.fill(Qt.GlobalColor.transparent)
painter_r = QPainter(outline_pix_r)
painter_r.setRenderHint(QPainter.RenderHint.Antialiasing)
pen_r = QtGui.QPen(QColor(0, 255, 0))
pen_r.setWidth(3)
painter_r.setPen(pen_r)
painter_r.setBrush(Qt.BrushStyle.NoBrush)
right_points = [
QPointF(w*0.35, h*0.15), QPointF(w*0.75, h*0.50), QPointF(w*0.35, h*0.85)]
painter_r.drawPolygon(QtGui.QPolygonF(right_points))
painter_r.end()
next_button.setIcon(QIcon(outline_pix_r))
next_button.setIconSize(next_button.size())
if (key == 'location' or key == 'browser') and not self.connection_manager.is_synced():
next_button.setStyleSheet("""
QPushButton {
background-color: transparent;
border: none;
}
QPushButton:hover {
background-color: rgba(0, 255, 0, 0.1);
border-radius: 3px;
}
QPushButton:pressed {
background-color: rgba(0, 255, 0, 0.2);
border-radius: 3px;
}
""")
prev_button.setVisible(True)
next_button.setVisible(True)
if key == 'protocol' or (protocol == 'wireguard' and key == 'connection'):
prev_button.setDisabled(True)
next_button.setDisabled(True)
if connection == 'system-wide' and (key == 'browser' or key == 'dimentions'):
prev_button.setVisible(False)
next_button.setVisible(False)
def on_sync_complete_for_edit_profile(self, available_locations, available_browsers, status, is_tor, locations, all_browsers):
if status:
self.update_status.update_status('Sync complete.')
self.extraccion()
else:
self.update_status.update_status(
'Sync failed. Please try again later.')
def show_previous_value(self, key: str, index: int, parameters: dict) -> None:
if key == 'browser' or key == 'location':
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_edit_profile)
return
previous_index = (index - 1) % len(parameters[key])
previous_value = parameters[key][previous_index]
self.update_temp_value(key, previous_value)
def show_next_value(self, key: str, index: int, parameters: dict) -> None:
if key == 'browser' or key == 'location':
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_edit_profile)
return
next_index = (index + 1) % len(parameters[key])
next_value = parameters[key][next_index]
self.update_temp_value(key, next_value)
def update_temp_value(self, key: str, new_value: str) -> None:
selected_profiles_str = self._selected_profiles_str()
if selected_profiles_str not in self.temp_changes:
self.temp_changes[selected_profiles_str] = {}
self.original_values[selected_profiles_str] = self.data_profile.copy(
)
self.temp_changes[selected_profiles_str][key] = new_value
self.extraccion()
def show_custom_res_dialog(self):
dialog = QDialog(self)
dialog.setWindowTitle("Custom Resolution")
dialog.setFixedSize(300, 150)
dialog.setModal(True)
dialog.setStyleSheet("""
QDialog {
background-color: #2c3e50;
border: 2px solid #00ffff;
border-radius: 10px;
}
QLabel {
color: white;
font-size: 12px;
}
QLineEdit {
background-color: #34495e;
color: white;
border: 1px solid #00ffff;
border-radius: 5px;
padding: 5px;
font-size: 12px;
}
QPushButton {
background-color: #2c3e50;
color: white;
border: 2px solid #00ffff;
border-radius: 5px;
padding: 8px;
font-size: 12px;
}
QPushButton:hover {
background-color: #34495e;
}
""")
layout = QVBoxLayout()
input_layout = QHBoxLayout()
width_label = QLabel("Width:")
width_input = QLineEdit()
width_input.setPlaceholderText("1920")
height_label = QLabel("Height:")
height_input = QLineEdit()
height_input.setPlaceholderText("1080")
input_layout.addWidget(width_label)
input_layout.addWidget(width_input)
input_layout.addWidget(height_label)
input_layout.addWidget(height_input)
button_layout = QHBoxLayout()
ok_button = QPushButton("OK")
cancel_button = QPushButton("Cancel")
button_layout.addWidget(cancel_button)
button_layout.addWidget(ok_button)
layout.addLayout(input_layout)
layout.addLayout(button_layout)
dialog.setLayout(layout)
def validate_and_accept():
try:
width = int(width_input.text())
height = int(height_input.text())
if width <= 0 or height <= 0:
return
host_w = self.custom_window.host_screen_width
host_h = self.custom_window.host_screen_height
adjusted = False
if width > host_w:
width = host_w - 50
adjusted = True
if height > host_h:
height = host_h - 50
adjusted = True
if adjusted:
QMessageBox.information(
dialog, "Notice", "Adjusted to fit host size")
self.update_temp_value('dimentions', f"{width}x{height}")
dialog.accept()
except ValueError:
pass
ok_button.clicked.connect(validate_and_accept)
cancel_button.clicked.connect(dialog.reject)
width_input.returnPressed.connect(validate_and_accept)
height_input.returnPressed.connect(validate_and_accept)
dialog.exec()
def has_unsaved_changes(self, profile_str: str) -> bool:
return profile_str in self.temp_changes and bool(self.temp_changes[profile_str])
def show_apply_changes_popup(self, selected_profiles_str: str) -> bool:
if selected_profiles_str not in self.temp_changes:
return False
temp_changes = self.temp_changes[selected_profiles_str]
current_data = self.original_values[selected_profiles_str]
current_browser = f"{current_data.get('browser', '')} {current_data.get('browser_version', '')}"
if 'location' in temp_changes and temp_changes['location'] != current_data.get('location'):
message = "A new location would require a new subscription code.\nDo you want to proceed with erasing the old one?"
elif 'browser' in temp_changes and temp_changes['browser'] != current_browser:
message = "Changing the browser would delete all data associated with it. Proceed?"
else:
return False
self.popup = ConfirmationPopup(
self,
message=message,
action_button_text="Continue",
cancel_button_text="Cancel"
)
self.popup.setWindowModality(Qt.WindowModality.ApplicationModal)
self.popup.finished.connect(
lambda result: self.handle_apply_changes_response(result))
self.popup.show()
return True
def show_unsaved_changes_popup(self) -> None:
self.popup = ConfirmationPopup(
self,
message="You have unsaved changes. Do you want to continue?",
action_button_text="Continue",
cancel_button_text="Cancel"
)
self.popup.setWindowModality(Qt.WindowModality.ApplicationModal)
self.popup.finished.connect(
lambda result: self.handle_unsaved_changes_response(result))
self.popup.show()
def _refresh_menu_and_navigate(self):
menu_page = self.find_menu_page()
if menu_page is not None and hasattr(menu_page, 'refresh_menu_buttons'):
menu_page.refresh_menu_buttons()
self.custom_window.navigator.navigate("menu")
def handle_apply_changes_response(self, result: bool) -> None:
if result:
self.commit_changes()
self._refresh_menu_and_navigate()
def handle_unsaved_changes_response(self, result: bool) -> None:
if result:
self.temp_changes.clear()
self._refresh_menu_and_navigate()
def go_selected(self) -> None:
selected_profiles_str = self._selected_profiles_str()
if selected_profiles_str in self.temp_changes and self.temp_changes[selected_profiles_str]:
needs_confirmation = self.show_apply_changes_popup(
selected_profiles_str)
if not needs_confirmation:
self.commit_changes()
self._refresh_menu_and_navigate()
else:
self.custom_window.navigator.navigate("menu")
def commit_changes(self) -> None:
selected_profiles_str = self._selected_profiles_str()
if selected_profiles_str in self.temp_changes:
for key, new_value in self.temp_changes[selected_profiles_str].items():
self.update_core_profiles(key, new_value)
self.temp_changes.pop(selected_profiles_str, None)
self.original_values.pop(selected_profiles_str, None)
def update_core_profiles(self, key, new_value):
profile = ProfileController.get(
int(self.update_status.current_profile_id))
self.update_res = False
if key == 'dimentions':
profile.resolution = new_value
self.update_res = True
elif key == 'name':
profile.name = new_value
elif key == 'connection':
if new_value == 'tor':
profile.connection.code = new_value
profile.connection.masked = True
elif new_value == 'just proxy':
profile.connection.code = 'system'
profile.connection.masked = True
else:
self.update_status.update_status(
'System wide profiles not supported atm')
elif key == 'browser':
browser_type, browser_version = new_value.split(':', 1)
profile.application_version.application_code = browser_type
profile.application_version.version_number = browser_version
elif key == 'protocol':
if self.data_profile.get('connection') == 'system-wide':
self.edit_to_session()
else:
profile.connection.code = 'wireguard' if new_value == 'wireguard' else 'tor'
profile.connection.masked = False if new_value == 'wireguard' else True
else:
location = self.connection_manager.get_location_info(new_value)
if location:
profile.location.code = location.code
profile.location.country_code = location.country_code
profile.location.time_zone = location.time_zone
profile.subscription = None
ProfileController.update(profile)
def edit_to_session(self):
id = int(self.update_status.current_profile_id)
profile = self.data_profile
default_app = 'firefox:123.0'
default_resolution = '1024x760'
location_code = self.connection_manager.get_location_info(
profile.get('location'))
connection_type = 'tor'
profile_data = {
'id': int(id),
'name': profile.get('name'),
'country_code': location_code.country_code,
'code': location_code.code,
'application': default_app,
'connection_type': connection_type,
'resolution': default_resolution,
}
resume_page = self.find_resume_page()
if resume_page:
resume_page.handle_core_action_create_profile(
'CREATE_SESSION_PROFILE', profile_data, 'session')

View file

@ -0,0 +1,105 @@
import random
from PyQt6.QtWidgets import QHBoxLayout, QLabel, QPushButton, QVBoxLayout, QWidget
from PyQt6 import QtCore
from core.controllers.ProfileController import ProfileController
from gui.v2.ui.pages.Page import Page
class FastModePromptPage(Page):
def __init__(self, page_stack, main_window):
super().__init__("FastModePrompt", page_stack, main_window)
self.page_stack = page_stack
self.update_status = main_window
self.title.setGeometry(500, 40, 350, 40)
self.title.setText("Quick Setup Option")
self.button_back.setVisible(False)
self.button_apply.setVisible(False)
container = QWidget(self)
container.setGeometry(QtCore.QRect(80, 100, 640, 360))
v = QVBoxLayout(container)
v.setContentsMargins(0, 0, 0, 0)
v.setSpacing(20)
large_text = QLabel(
"Would you like to switch to convenient \"fast mode\" for profile creation going forward? (recommended)")
large_text.setWordWrap(True)
large_text.setStyleSheet("color: white; font-size: 18px;")
large_text.setAlignment(QtCore.Qt.AlignmentFlag.AlignCenter)
v.addWidget(large_text)
buttons_row = QWidget()
h = QHBoxLayout(buttons_row)
h.setContentsMargins(0, 0, 0, 0)
h.setSpacing(20)
yes_btn = QPushButton("Yes Fast Mode")
yes_btn.setCursor(QtCore.Qt.CursorShape.PointingHandCursor)
yes_btn.setFixedSize(240, 56)
yes_btn.setStyleSheet(
"background-color: #27ae60; color: white; font-weight: bold; font-size: 16px; border: none; border-radius: 6px;")
yes_btn.clicked.connect(self.choose_yes)
no_btn = QPushButton("No Keep This")
no_btn.setCursor(QtCore.Qt.CursorShape.PointingHandCursor)
no_btn.setFixedSize(160, 42)
no_btn.setStyleSheet(
"background-color: #c0392b; color: white; font-size: 14px; border: none; border-radius: 6px;")
no_btn.clicked.connect(self.choose_no)
h.addStretch()
h.addWidget(yes_btn)
h.addWidget(no_btn)
h.addStretch()
v.addWidget(buttons_row)
small_text = QLabel(
"You can toggle this anytime in the \"Options\" Menu, under \"Create/Edit\"")
small_text.setWordWrap(True)
small_text.setStyleSheet("color: #999999; font-size: 12px;")
small_text.setAlignment(QtCore.Qt.AlignmentFlag.AlignCenter)
v.addWidget(small_text)
def finalize(self):
self.update_status.mark_fast_mode_prompt_shown()
menu_page = self.custom_window.navigator.get_cached("menu")
if menu_page is not None and hasattr(menu_page, 'refresh_menu_buttons'):
menu_page.refresh_menu_buttons()
self.custom_window.navigator.navigate("menu")
def choose_yes(self):
self.update_status.set_fast_mode_enabled(True)
self.finalize()
def choose_no(self):
self.update_status.set_fast_mode_enabled(False)
self.finalize()
def initialize_default_selections(self):
if not self.selected_values['location']:
locations = self.connection_manager.get_location_list()
if locations:
random_index = random.randint(0, len(locations) - 1)
self.selected_values['location'] = locations[random_index]
if not self.selected_values['browser']:
browsers = self.connection_manager.get_browser_list()
if browsers:
random_index = random.randint(0, len(browsers) - 1)
self.selected_values['browser'] = browsers[random_index]
def get_next_available_profile_id(self) -> int:
profiles = ProfileController.get_all()
if not profiles:
return 1
existing_ids = sorted(profiles.keys())
for i in range(1, max(existing_ids) + 2):
if i not in existing_ids:
return i
return 1

View file

@ -0,0 +1,787 @@
import os
import random
from PyQt6.QtWidgets import (
QDialog, QHBoxLayout, QLabel, QLineEdit, QMessageBox, QPushButton, QVBoxLayout
)
from PyQt6.QtGui import QIcon, QPixmap, QTransform
from PyQt6.QtCore import QSize
from PyQt6 import QtCore
from core.controllers.ProfileController import ProfileController
from gui.v2.actions.profile_order import append_profile_to_visual_order
from gui.v2.ui.pages.Page import Page
from gui.v2.ui.pages.browser_page import BrowserPage
from gui.v2.ui.pages.location_page import LocationPage
from gui.v2.ui.pages.location_verification_page import LocationVerificationPage
from gui.v2.workers.worker_thread import WorkerThread
class FastRegistrationPage(Page):
def __init__(self, page_stack, main_window):
super().__init__("FastRegistration", page_stack, main_window)
self.page_stack = page_stack
self.update_status = main_window
self.connection_manager = main_window.connection_manager
self.labels = []
self.buttons = []
self.title.setGeometry(550, 40, 250, 40)
self.title.setText("Fast Creation")
self.button_apply.setVisible(True)
self.button_apply.clicked.connect(self.create_profile)
self.button_back.setVisible(True)
try:
self.button_back.clicked.disconnect()
except TypeError:
pass
self.button_back.clicked.connect(self.go_back)
self.name_handle = QLabel(self)
self.name_handle.setGeometry(110, 70, 400, 30)
self.name_handle.setStyleSheet('color: #cacbcb;')
self.name_handle.setText("Profile Name:")
self.name_hint = QLabel(self)
self.name_hint.setGeometry(265, 100, 190, 20)
self.name_hint.setStyleSheet(
'color: #888888; font-size: 10px; font-style: italic;')
self.name_hint.setText("Click here to edit profile name")
self.name_hint.setAlignment(QtCore.Qt.AlignmentFlag.AlignCenter)
self.name = QLineEdit(self)
self.name.setPlaceholderText("Enter name")
self.name.setGeometry(265, 70, 190, 30)
self.name.setStyleSheet(
"color: cyan; border: 1px solid #666666; border-radius: 3px; background-color: rgba(0, 0, 0, 0.3);")
self.name.setCursor(QtCore.Qt.CursorShape.IBeamCursor)
self.name.focusInEvent = lambda event: self.name_hint.hide()
self.name.focusOutEvent = lambda event: self.name_hint.show(
) if not self.name.text() else self.name_hint.hide()
self.name.textChanged.connect(
lambda text: self.name_hint.hide() if text else self.name_hint.show())
self.profile_data = {}
self.selected_values = {
'protocol': 'wireguard',
'connection': 'browser-only',
'location': '',
'browser': '',
'resolution': '1024x760'
}
self.res_index = 1
self.initialize_default_selections()
def initialize_default_selections(self):
if not self.selected_values['location']:
locations = self.connection_manager.get_location_list()
if locations:
random_index = random.randint(0, len(locations) - 1)
self.selected_values['location'] = locations[random_index]
if not self.selected_values['browser']:
browsers = self.connection_manager.get_browser_list()
if browsers:
random_index = random.randint(0, len(browsers) - 1)
self.selected_values['browser'] = browsers[random_index]
def get_next_available_profile_id(self, profiles=None) -> int:
if profiles is None:
profiles = ProfileController.get_all()
if not profiles:
return 1
existing_ids = sorted(profiles.keys())
for i in range(1, max(existing_ids) + 2):
if i not in existing_ids:
return i
return 1
def showEvent(self, event):
super().showEvent(event)
self.initialize_default_selections()
self.create_interface_elements()
def create_interface_elements(self):
for label in self.labels:
label.deleteLater()
self.labels = []
for button in self.buttons:
button.deleteLater()
self.buttons = []
if not self.name.text():
self.name_hint.show()
else:
self.name_hint.hide()
self.host_info_label = QLabel(
f"Host Screen: {self.update_status.host_screen_width}x{self.update_status.host_screen_height}", self)
self.host_info_label.setGeometry(415, 470, 250, 20)
self.host_info_label.setAlignment(QtCore.Qt.AlignmentFlag.AlignCenter)
self.host_info_label.setStyleSheet(
"color: #00ffff; font-size: 13px; font-weight: bold;")
self.host_info_label.show()
self.labels.append(self.host_info_label)
self.create_protocol_section()
self.create_connection_section()
self.create_location_section()
if self.selected_values['connection'] != 'system-wide':
self.create_browser_section()
self.create_resolution_section()
self.update_ui_state_for_connection()
def create_protocol_section(self):
label = QLabel("Protocol", self)
label.setGeometry(300, 150, 185, 75)
protocol_image = QPixmap(os.path.join(
self.btn_path, f"{self.selected_values['protocol']}_button.png"))
label.setPixmap(protocol_image)
label.setScaledContents(True)
label.show()
self.labels.append(label)
prev_button = QPushButton(self)
prev_button.setGeometry(265, 150, 30, 75)
prev_button.clicked.connect(
lambda: self.show_previous_value('protocol'))
prev_button.show()
icon_path = os.path.join(self.btn_path, "UP_button.png")
icon = QPixmap(icon_path)
transform = QTransform().rotate(180)
rotated_icon = icon.transformed(transform)
prev_button.setIcon(QIcon(rotated_icon))
prev_button.setIconSize(prev_button.size())
self.buttons.append(prev_button)
next_button = QPushButton(self)
next_button.setGeometry(490, 150, 30, 75)
next_button.clicked.connect(lambda: self.show_next_value('protocol'))
next_button.show()
next_button.setIcon(
QIcon(os.path.join(self.btn_path, "UP_button.png")))
next_button.setIconSize(next_button.size())
self.buttons.append(next_button)
def create_connection_section(self):
label = QLabel("Connection", self)
label.setGeometry(150, 250, 185, 75)
if self.selected_values['connection']:
connection_image = QPixmap(os.path.join(
self.btn_path, f"{self.selected_values['connection']}_button.png"))
if connection_image.isNull():
fallback_path = os.path.join(
self.btn_path, "browser-only_button.png")
connection_image = QPixmap(fallback_path)
else:
connection_image = QPixmap(os.path.join(
self.btn_path, "browser-only_button.png"))
label.setPixmap(connection_image)
label.setScaledContents(True)
label.show()
self.labels.append(label)
prev_button = QPushButton(self)
prev_button.setGeometry(115, 250, 30, 75)
prev_button.clicked.connect(
lambda: self.show_previous_value('connection'))
prev_button.show()
icon_path = os.path.join(self.btn_path, "UP_button.png")
icon = QPixmap(icon_path)
transform = QTransform().rotate(180)
rotated_icon = icon.transformed(transform)
prev_button.setIcon(QIcon(rotated_icon))
prev_button.setIconSize(prev_button.size())
self.buttons.append(prev_button)
next_button = QPushButton(self)
next_button.setGeometry(340, 250, 30, 75)
next_button.clicked.connect(lambda: self.show_next_value('connection'))
next_button.show()
next_button.setIcon(
QIcon(os.path.join(self.btn_path, "UP_button.png")))
next_button.setIconSize(next_button.size())
self.buttons.append(next_button)
def create_location_section(self):
info_label = QLabel("Click on the location for more info", self)
info_label.setGeometry(480, 80, 300, 20)
info_label.setStyleSheet(
"color: #888888; font-size: 14px; font-style: italic;")
info_label.setAlignment(QtCore.Qt.AlignmentFlag.AlignRight)
info_label.show()
self.labels.append(info_label)
arrow_label = QLabel(self)
arrow_label.setGeometry(540, 100, 150, 150)
arrow_pixmap = QPixmap(os.path.join(self.btn_path, "arrow.png"))
transform = QTransform().rotate(270)
rotated_arrow = arrow_pixmap.transformed(transform)
arrow_label.setPixmap(rotated_arrow)
arrow_label.setScaledContents(True)
arrow_label.show()
self.labels.append(arrow_label)
label = QPushButton(self)
label.setGeometry(435, 250, 185, 75)
label.setFlat(True)
label.setStyleSheet("background: transparent; border: none;")
if self.selected_values['location']:
image_path = os.path.join(
self.btn_path, f"button_{self.selected_values['location']}.png")
location_image = QPixmap(image_path)
locations = self.connection_manager.get_location_info(
self.selected_values['location'])
provider = locations.operator.name if locations and hasattr(
locations, 'operator') else None
fallback_path = os.path.join(
self.btn_path, "default_location_button.png")
if location_image.isNull():
if locations and hasattr(locations, 'country_name'):
location_name = locations.country_name
location_image = LocationPage.create_location_button_image(
location_name, fallback_path, provider)
else:
location_image = LocationPage.create_location_button_image(
self.selected_values['location'], fallback_path, provider)
else:
if locations and hasattr(locations, 'country_name'):
location_image = LocationPage.create_location_button_image(
f'{locations.country_code}_{locations.code}', fallback_path, provider, image_path)
else:
location_image = LocationPage.create_location_button_image(
self.selected_values['location'], fallback_path, provider, image_path)
else:
location_image = QPixmap(os.path.join(
self.btn_path, "default_location_button.png"))
label.setIcon(QIcon(location_image))
label.setIconSize(QSize(185, 75))
if self.selected_values['location']:
label.location_icon_name = self.selected_values['location']
label.setCursor(QtCore.Qt.CursorShape.PointingHandCursor)
label.clicked.connect(
lambda checked, loc=self.selected_values['location']: self.show_location_verification(loc))
locations = self.connection_manager.get_location_info(
self.selected_values['location'])
if self.selected_values['protocol'] == 'hidetor' and locations and not (hasattr(locations, 'is_proxy_capable') and locations.is_proxy_capable):
label.hide()
else:
label.show()
self.labels.append(label)
prev_button = QPushButton(self)
prev_button.setGeometry(400, 250, 30, 75)
prev_button.clicked.connect(
lambda: self.show_previous_value('location'))
prev_button.show()
icon_path = os.path.join(self.btn_path, "UP_button.png")
icon = QPixmap(icon_path)
transform = QTransform().rotate(180)
rotated_icon = icon.transformed(transform)
prev_button.setIcon(QIcon(rotated_icon))
prev_button.setIconSize(prev_button.size())
self.buttons.append(prev_button)
next_button = QPushButton(self)
next_button.setGeometry(625, 250, 30, 75)
next_button.clicked.connect(lambda: self.show_next_value('location'))
next_button.show()
next_button.setIcon(
QIcon(os.path.join(self.btn_path, "UP_button.png")))
next_button.setIconSize(next_button.size())
self.buttons.append(next_button)
def create_browser_section(self):
label = QLabel("Browser", self)
label.setGeometry(150, 350, 185, 75)
if self.selected_values['browser']:
browser_image = BrowserPage.create_browser_button_image(
self.selected_values['browser'], self.btn_path)
if browser_image.isNull():
fallback_path = os.path.join(
self.btn_path, "default_browser_button.png")
browser_image = BrowserPage.create_browser_button_image(
self.selected_values['browser'], fallback_path, True)
else:
browser_image = QPixmap()
label.setPixmap(browser_image)
label.setScaledContents(True)
label.show()
self.labels.append(label)
prev_button = QPushButton(self)
prev_button.setGeometry(115, 350, 30, 75)
prev_button.clicked.connect(
lambda: self.show_previous_value('browser'))
prev_button.show()
icon_path = os.path.join(self.btn_path, "UP_button.png")
icon = QPixmap(icon_path)
transform = QTransform().rotate(180)
rotated_icon = icon.transformed(transform)
prev_button.setIcon(QIcon(rotated_icon))
prev_button.setIconSize(prev_button.size())
self.buttons.append(prev_button)
next_button = QPushButton(self)
next_button.setGeometry(340, 350, 30, 75)
next_button.clicked.connect(lambda: self.show_next_value('browser'))
next_button.show()
next_button.setIcon(
QIcon(os.path.join(self.btn_path, "UP_button.png")))
next_button.setIconSize(next_button.size())
self.buttons.append(next_button)
def create_resolution_section(self):
from gui.v2.ui.pages.screen_page import ScreenPage
label = QLabel("Resolution", self)
label.setGeometry(435, 350, 185, 75)
screen_page = self.custom_window.navigator.get_cached("screen")
if screen_page is None:
self.custom_window.navigator.navigate("screen")
screen_page = self.custom_window.navigator.get_cached("screen")
self.custom_window.navigator.navigate("fast_registration")
if self.selected_values['resolution']:
resolution_image = screen_page.create_resolution_button_image(
self.selected_values['resolution'])
else:
resolution_image = screen_page.create_resolution_button_image(
"1024x760")
label.setPixmap(resolution_image)
label.setScaledContents(True)
label.show()
self.labels.append(label)
prev_button = QPushButton(self)
prev_button.setGeometry(400, 350, 30, 75)
prev_button.clicked.connect(
lambda: self.show_previous_value('resolution'))
prev_button.show()
icon_path = os.path.join(self.btn_path, "UP_button.png")
icon = QPixmap(icon_path)
transform = QTransform().rotate(180)
rotated_icon = icon.transformed(transform)
prev_button.setIcon(QIcon(rotated_icon))
prev_button.setIconSize(prev_button.size())
self.buttons.append(prev_button)
next_button = QPushButton(self)
next_button.setGeometry(625, 350, 30, 75)
next_button.clicked.connect(lambda: self.show_next_value('resolution'))
next_button.show()
next_button.setIcon(
QIcon(os.path.join(self.btn_path, "UP_button.png")))
next_button.setIconSize(next_button.size())
self.buttons.append(next_button)
custom_button = QPushButton("Custom", self)
custom_button.setGeometry(435, 430, 185, 30)
custom_button.setStyleSheet("""
QPushButton {
background-color: rgba(0, 255, 255, 0.1);
color: #00ffff;
border: 1px solid #00ffff;
border-radius: 5px;
font-size: 12px;
font-weight: bold;
}
QPushButton:hover {
background-color: rgba(0, 255, 255, 0.2);
}
""")
custom_button.clicked.connect(self.show_custom_res_dialog)
custom_button.show()
self.buttons.append(custom_button)
def show_custom_res_dialog(self):
dialog = QDialog(self)
dialog.setWindowTitle("Custom Resolution")
dialog.setFixedSize(300, 150)
dialog.setModal(True)
dialog.setStyleSheet("""
QDialog {
background-color: #2c3e50;
border: 2px solid #00ffff;
border-radius: 10px;
}
QLabel {
color: white;
font-size: 12px;
}
QLineEdit {
background-color: #34495e;
color: white;
border: 1px solid #00ffff;
border-radius: 5px;
padding: 5px;
font-size: 12px;
}
QPushButton {
background-color: #2c3e50;
color: white;
border: 2px solid #00ffff;
border-radius: 5px;
padding: 8px;
font-size: 12px;
}
QPushButton:hover {
background-color: #34495e;
}
""")
layout = QVBoxLayout()
input_layout = QHBoxLayout()
width_label = QLabel("Width:")
width_input = QLineEdit()
width_input.setPlaceholderText("1920")
height_label = QLabel("Height:")
height_input = QLineEdit()
height_input.setPlaceholderText("1080")
input_layout.addWidget(width_label)
input_layout.addWidget(width_input)
input_layout.addWidget(height_label)
input_layout.addWidget(height_input)
button_layout = QHBoxLayout()
ok_button = QPushButton("OK")
cancel_button = QPushButton("Cancel")
button_layout.addWidget(cancel_button)
button_layout.addWidget(ok_button)
layout.addLayout(input_layout)
layout.addLayout(button_layout)
dialog.setLayout(layout)
def validate_and_accept():
try:
width = int(width_input.text())
height = int(height_input.text())
if width <= 0 or height <= 0:
return
host_w = self.update_status.host_screen_width
host_h = self.update_status.host_screen_height
adjusted = False
if width > host_w:
width = host_w - 50
adjusted = True
if height > host_h:
height = host_h - 50
adjusted = True
if adjusted:
QMessageBox.information(
dialog, "Notice", "Adjusted to fit host size")
self.selected_values['resolution'] = f"{width}x{height}"
self.create_interface_elements()
dialog.accept()
except ValueError:
pass
ok_button.clicked.connect(validate_and_accept)
cancel_button.clicked.connect(dialog.reject)
width_input.returnPressed.connect(validate_and_accept)
height_input.returnPressed.connect(validate_and_accept)
dialog.exec()
def update_ui_state_for_connection(self):
is_system_wide = self.selected_values['connection'] == 'system-wide'
for button in self.buttons:
if hasattr(button, 'geometry'):
button_geometry = button.geometry()
if button_geometry.y() == 350:
if button_geometry.x() in [115, 340, 400, 625]:
button.setEnabled(not is_system_wide)
def show_previous_value(self, key):
if key == 'protocol':
protocols = ['wireguard', 'hidetor']
current_index = protocols.index(self.selected_values[key])
previous_index = (current_index - 1) % len(protocols)
self.selected_values[key] = protocols[previous_index]
if self.selected_values[key] == 'wireguard':
self.selected_values['connection'] = 'browser-only'
else:
self.selected_values['connection'] = 'tor'
loc_info = self.connection_manager.get_location_info(
self.selected_values['location'])
if not (loc_info and hasattr(loc_info, 'is_proxy_capable') and loc_info.is_proxy_capable):
locations = self.connection_manager.get_location_list()
proxy_locations = [loc for loc in locations if (l := self.connection_manager.get_location_info(
loc)) and hasattr(l, 'is_proxy_capable') and l.is_proxy_capable]
if proxy_locations:
self.selected_values['location'] = proxy_locations[0]
elif key == 'connection':
if self.selected_values['protocol'] == 'wireguard':
connections = ['browser-only', 'system-wide']
else:
connections = ['tor', 'just proxy']
current_index = connections.index(self.selected_values[key])
previous_index = (current_index - 1) % len(connections)
self.selected_values[key] = connections[previous_index]
self.update_ui_state_for_connection()
elif key == 'location':
locations = self.connection_manager.get_location_list()
if self.selected_values['protocol'] == 'hidetor':
locations = [loc for loc in locations if (l := self.connection_manager.get_location_info(
loc)) and hasattr(l, 'is_proxy_capable') and l.is_proxy_capable]
if locations and self.selected_values[key] in locations:
current_index = locations.index(self.selected_values[key])
previous_index = (current_index - 1) % len(locations)
self.selected_values[key] = locations[previous_index]
elif locations:
self.selected_values[key] = locations[0]
elif key == 'browser':
browsers = self.connection_manager.get_browser_list()
if browsers and self.selected_values[key] in browsers:
current_index = browsers.index(self.selected_values[key])
previous_index = (current_index - 1) % len(browsers)
self.selected_values[key] = browsers[previous_index]
elif browsers:
self.selected_values[key] = browsers[0]
elif key == 'resolution':
config = self.update_status._load_gui_config()
dynamic_enabled = config.get("registrations", {}).get(
"dynamic_larger_profiles", False) if config else False
if dynamic_enabled:
resolutions = []
host_w = self.update_status.host_screen_width
host_h = self.update_status.host_screen_height
for i in range(5):
resolutions.append(
f"{host_w - (50 * (i + 1))}x{host_h - (50 * (i + 1))}")
self.res_index = (self.res_index - 1) % len(resolutions)
self.selected_values[key] = resolutions[self.res_index]
else:
resolutions = ['800x600', '1024x760', '1152x1080', '1280x1024', '1920x1080',
'2560x1440', '2560x1600', '1920x1440', '1792x1344', '2048x1152']
self.res_index = (self.res_index - 1) % len(resolutions)
choice = resolutions[self.res_index]
w, h = map(int, choice.split('x'))
host_w = self.update_status.host_screen_width
host_h = self.update_status.host_screen_height
new_w, new_h = w, h
if w > host_w:
new_w = host_w - 50
if h > host_h:
new_h = host_h - 50
self.selected_values[key] = f"{new_w}x{new_h}"
self.create_interface_elements()
def show_next_value(self, key):
if key == 'protocol':
protocols = ['wireguard', 'hidetor']
current_index = protocols.index(self.selected_values[key])
next_index = (current_index + 1) % len(protocols)
self.selected_values[key] = protocols[next_index]
if self.selected_values[key] == 'wireguard':
self.selected_values['connection'] = 'browser-only'
else:
self.selected_values['connection'] = 'tor'
loc_info = self.connection_manager.get_location_info(
self.selected_values['location'])
if not (loc_info and hasattr(loc_info, 'is_proxy_capable') and loc_info.is_proxy_capable):
locations = self.connection_manager.get_location_list()
proxy_locations = [loc for loc in locations if (l := self.connection_manager.get_location_info(
loc)) and hasattr(l, 'is_proxy_capable') and l.is_proxy_capable]
if proxy_locations:
self.selected_values['location'] = proxy_locations[0]
elif key == 'connection':
if self.selected_values['protocol'] == 'wireguard':
connections = ['browser-only', 'system-wide']
else:
connections = ['tor', 'just proxy']
current_index = connections.index(self.selected_values[key])
next_index = (current_index + 1) % len(connections)
self.selected_values[key] = connections[next_index]
self.update_ui_state_for_connection()
elif key == 'location':
locations = self.connection_manager.get_location_list()
if self.selected_values['protocol'] == 'hidetor':
locations = [loc for loc in locations if (l := self.connection_manager.get_location_info(
loc)) and hasattr(l, 'is_proxy_capable') and l.is_proxy_capable]
if locations and self.selected_values[key] in locations:
current_index = locations.index(self.selected_values[key])
next_index = (current_index + 1) % len(locations)
self.selected_values[key] = locations[next_index]
elif locations:
self.selected_values[key] = locations[0]
elif key == 'browser':
browsers = self.connection_manager.get_browser_list()
if browsers and self.selected_values[key] in browsers:
current_index = browsers.index(self.selected_values[key])
next_index = (current_index + 1) % len(browsers)
self.selected_values[key] = browsers[next_index]
elif browsers:
self.selected_values[key] = browsers[0]
elif key == 'resolution':
config = self.update_status._load_gui_config()
dynamic_enabled = config.get("registrations", {}).get(
"dynamic_larger_profiles", False) if config else False
if dynamic_enabled:
resolutions = []
host_w = self.update_status.host_screen_width
host_h = self.update_status.host_screen_height
for i in range(5):
resolutions.append(
f"{host_w - (50 * (i + 1))}x{host_h - (50 * (i + 1))}")
self.res_index = (self.res_index + 1) % len(resolutions)
self.selected_values[key] = resolutions[self.res_index]
else:
resolutions = ['800x600', '1024x760', '1152x1080', '1280x1024', '1920x1080',
'2560x1440', '2560x1600', '1920x1440', '1792x1344', '2048x1152']
self.res_index = (self.res_index + 1) % len(resolutions)
choice = resolutions[self.res_index]
w, h = map(int, choice.split('x'))
host_w = self.update_status.host_screen_width
host_h = self.update_status.host_screen_height
new_w, new_h = w, h
if w > host_w:
new_w = host_w - 50
if h > host_h:
new_h = host_h - 50
self.selected_values[key] = f"{new_w}x{new_h}"
self.create_interface_elements()
def go_back(self):
menu_page = self.custom_window.navigator.get_cached("menu")
if menu_page is not None and hasattr(menu_page, 'refresh_menu_buttons'):
menu_page.refresh_menu_buttons()
self.custom_window.navigator.navigate("menu")
def show_location_verification(self, 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 create_profile(self):
profile_name = self.name.text()
if not profile_name:
self.update_status.update_status('Please enter a profile name')
return
if not self.selected_values['location']:
self.update_status.update_status('Please select a location')
return
if self.selected_values['connection'] != 'system-wide' and not self.selected_values['browser']:
self.update_status.update_status('Please select a browser')
return
profile_data = {
'name': profile_name,
'protocol': self.selected_values['protocol'],
'connection': self.selected_values['connection'],
'location': self.selected_values['location'],
'browser': self.selected_values['browser'],
'resolution': self.selected_values['resolution']
}
self.profile_data = profile_data
self.update_status.write_data(profile_data)
if self.selected_values['protocol'] == 'wireguard':
self.create_wireguard_profile(profile_data)
else:
self.create_tor_profile(profile_data)
self.go_back()
def _spawn_create_profile_worker(self, action, profile_data, profile_type):
self.worker_thread = WorkerThread(action, profile_data, profile_type)
self.worker_thread.text_output.connect(
self.update_status.update_status)
self.worker_thread.start()
self.worker_thread.wait()
def create_wireguard_profile(self, profile_data):
location_info = self.connection_manager.get_location_info(
profile_data['location'])
if not location_info:
self.update_status.update_status('Invalid location selected')
return
profiles = ProfileController.get_all()
profile_id = self.get_next_available_profile_id(profiles)
profile_data_for_resume = {
'id': profile_id,
'name': profile_data['name'],
'country_code': location_info.country_code,
'code': location_info.code,
'application': profile_data['browser'],
'connection_type': 'wireguard',
'resolution': profile_data['resolution'],
}
if profile_data['connection'] == 'system-wide':
self._spawn_create_profile_worker(
'CREATE_SYSTEM_PROFILE', profile_data_for_resume, 'system')
else:
self._spawn_create_profile_worker(
'CREATE_SESSION_PROFILE', profile_data_for_resume, 'session')
if ProfileController.get(profile_id) is not None:
append_profile_to_visual_order(
getattr(self.update_status, 'gui_config_file', None),
profile_id,
profiles.keys())
def create_tor_profile(self, profile_data):
location_info = self.connection_manager.get_location_info(
profile_data['location'])
if not location_info:
self.update_status.update_status('Invalid location selected')
return
connection_type = 'tor' if profile_data['connection'] == 'tor' else 'system'
profiles = ProfileController.get_all()
profile_id = self.get_next_available_profile_id(profiles)
profile_data_for_resume = {
'id': profile_id,
'name': profile_data['name'],
'country_code': location_info.country_code,
'code': location_info.code,
'application': profile_data['browser'],
'connection_type': connection_type,
'resolution': profile_data['resolution'],
}
self._spawn_create_profile_worker(
'CREATE_SESSION_PROFILE', profile_data_for_resume, 'session')
if ProfileController.get(profile_id) is not None:
append_profile_to_visual_order(
getattr(self.update_status, 'gui_config_file', None),
profile_id,
profiles.keys())
def find_resume_page(self):
return self.custom_window.navigator.get_cached("resume")

105
gui/v2/ui/pages/hidetor_page.py Executable file
View file

@ -0,0 +1,105 @@
import os
from PyQt6.QtWidgets import QPushButton, QButtonGroup
from PyQt6.QtGui import QPixmap, QIcon
from PyQt6.QtCore import Qt
from PyQt6 import QtCore
from gui.v2.ui.pages.Page import Page
from gui.v2.ui.pages.location_page import LocationPage
class HidetorPage(Page):
def __init__(self, page_stack, main_window, parent=None):
super().__init__("HideTor", page_stack, main_window, parent)
self.selected_location_icon = None
self.connection_manager = main_window.connection_manager
self.update_status = main_window
self.button_next.clicked.connect(self.go_selected)
self.button_reverse.setVisible(True)
self.button_reverse.clicked.connect(self.reverse)
self.display.setGeometry(QtCore.QRect(5, 10, 390, 520))
self.title.setGeometry(395, 40, 380, 40)
self.title.setText("Pick a location")
if self.connection_manager.is_synced():
from gui.v2.actions.sync import generate_grid_positions
locs = self.connection_manager.get_location_list()
positions = generate_grid_positions(len(locs))
available = [(QPushButton, loc, positions[i]) for i, loc in enumerate(locs)]
self.create_interface_elements(available)
def create_interface_elements(self, available_locations):
self.buttonGroup = QButtonGroup(self)
self.buttons = []
for j, (object_type, icon_name, geometry) in enumerate(available_locations):
boton = object_type(self)
boton.setGeometry(*geometry)
boton.setIconSize(boton.size())
boton.setCheckable(True)
locations = self.connection_manager.get_location_info(icon_name)
if icon_name == 'my_14':
boton.setVisible(False)
if locations and not (hasattr(locations, 'is_proxy_capable') and locations.is_proxy_capable):
boton.setVisible(False)
icon_path = os.path.join(self.btn_path, f"button_{icon_name}.png")
boton.setIcon(QIcon(icon_path))
fallback_path = os.path.join(
self.btn_path, "default_location_button.png")
provider = locations.operator.name if locations and hasattr(
locations, 'operator') else None
if boton.icon().isNull():
if locations and hasattr(locations, 'country_name'):
location_name = locations.country_name
base_image = LocationPage.create_location_button_image(
location_name, fallback_path, provider)
boton.setIcon(QIcon(base_image))
else:
base_image = LocationPage.create_location_button_image(
'', fallback_path, provider)
boton.setIcon(QIcon(base_image))
else:
if locations and hasattr(locations, 'country_name'):
base_image = LocationPage.create_location_button_image(
f'{locations.country_code}_{locations.code}', fallback_path, provider, icon_path)
boton.setIcon(QIcon(base_image))
else:
base_image = LocationPage.create_location_button_image(
'', fallback_path, provider, icon_path)
boton.setIcon(QIcon(base_image))
self.buttons.append(boton)
self.buttonGroup.addButton(boton, j)
boton.location_icon_name = icon_name
boton.setCursor(QtCore.Qt.CursorShape.PointingHandCursor)
boton.clicked.connect(
lambda checked, loc=icon_name: self.show_location(loc))
def update_swarp_json(self):
inserted_data = {
"location": self.selected_location_icon,
"connection": "tor"
}
self.update_status.write_data(inserted_data)
def show_location(self, location):
tor_hide_img = f'hdtor_{location}'
self.display.setPixmap(QPixmap(os.path.join(self.btn_path, f"{tor_hide_img}.png")).scaled(
self.display.size(), Qt.AspectRatioMode.KeepAspectRatio))
self.selected_location_icon = location
self.button_next.setVisible(True)
self.update_swarp_json()
def reverse(self):
self.custom_window.navigator.navigate("protocol")
def go_selected(self):
self.custom_window.navigator.navigate("browser")
def show_location_verification(self, location_icon_name):
from gui.v2.ui.pages.location_verification_page import LocationVerificationPage
verification_page = LocationVerificationPage(
self.page_stack, self.update_status, location_icon_name, self)
self.page_stack.addWidget(verification_page)
self.page_stack.setCurrentIndex(
self.page_stack.indexOf(verification_page))

190
gui/v2/ui/pages/id_page.py Executable file
View file

@ -0,0 +1,190 @@
import os
import re
from PyQt6.QtWidgets import (
QButtonGroup, QFrame, QLabel, QPushButton, QTextEdit,
)
from PyQt6.QtGui import QIcon, QPixmap
from PyQt6.QtCore import Qt
from PyQt6 import QtCore
from gui.v2.ui.pages.Page import Page
HOW_MANY_PROFILES_DEFAULT = 6
class IdPage(Page):
def __init__(self, page_stack, main_window=None, parent=None):
super().__init__("Id", page_stack, main_window, parent)
self.update_status = main_window
self.btn_path = main_window.btn_path
self.buttonGroup = QButtonGroup(self)
self.display.setGeometry(QtCore.QRect(-10, 50, 550, 460))
self.create_interface_elements()
self.connect_button = QPushButton(self)
self.connect_button.setObjectName("connect_button")
self.connect_button.setGeometry(625, 450, 90, 50)
self.connect_button.setIconSize(self.connect_button.size())
tor_icon = QIcon(os.path.join(self.btn_path, "connect.png"))
self.connect_button.setIcon(tor_icon)
self.connect_button.clicked.connect(self.on_connect)
self.connect_button.setDisabled(True)
self.button_reverse.setVisible(True)
def on_connect(self):
text = self.text_edit.toPlainText()
pattern = r'^[A-Z0-9]{4}-[A-Z0-9]{4}-[A-Z0-9]{4}-[A-Z0-9]{4}$'
if re.match(pattern, text):
self.update_status.update_status('Enabling profile in progress...')
profile_data = {
'id': int(self.update_status.current_profile_id),
'billing_code': str(text)
}
menu_page = self.find_menu_page()
if menu_page:
menu_page.enabling_profile(profile_data)
else:
self.update_status.update_status(
'Incorrect format for the billing code')
def find_menu_page(self):
return self.custom_window.navigator.get_cached("menu")
def create_interface_elements(self):
self.object_selected = None
self.display.setGeometry(QtCore.QRect(0, 0, 0, 0))
self.button_reverse.clicked.connect(self.reverse)
self.button_go.clicked.connect(self.go_selected)
column_title_style = "color: #00ffff; font-size: 18px; font-weight: bold;"
column_button_style = """
QPushButton {
background-color: #000000;
color: #00ffff;
border: 2px solid #00ffff;
border-radius: 10px;
font-size: 15px;
font-weight: bold;
padding: 10px;
}
QPushButton:hover { background-color: #003333; }
"""
column_desc_style = "color: #888888; font-size: 12px; font-style: italic;"
self.single_title = QLabel("Single Profile", self)
self.single_title.setGeometry(15, 50, 240, 40)
self.single_title.setStyleSheet(column_title_style)
self.single_title.setAlignment(QtCore.Qt.AlignmentFlag.AlignCenter)
self.single_button = QPushButton("One Profile Only", self)
self.single_button.setGeometry(40, 220, 200, 100)
self.single_button.setStyleSheet(column_button_style)
self.single_button.clicked.connect(self.go_single_profile)
self.single_desc = QLabel("Buy a single billing for one profile.", self)
self.single_desc.setGeometry(15, 335, 240, 40)
self.single_desc.setStyleSheet(column_desc_style)
self.single_desc.setAlignment(QtCore.Qt.AlignmentFlag.AlignCenter)
self.single_desc.setWordWrap(True)
self.multiple_title = QLabel("Multiple Profiles", self)
self.multiple_title.setGeometry(285, 50, 240, 40)
self.multiple_title.setStyleSheet(column_title_style)
self.multiple_title.setAlignment(QtCore.Qt.AlignmentFlag.AlignCenter)
self.multiple_button = QPushButton("Multiple Profiles\nat Once", self)
self.multiple_button.setGeometry(310, 220, 200, 100)
self.multiple_button.setStyleSheet(column_button_style)
self.multiple_button.clicked.connect(self.go_multiple_profiles)
self.multiple_desc = QLabel(
f"Buy a bundle of {HOW_MANY_PROFILES_DEFAULT} tickets at once, which are cryptographically seperated from each other.", self)
self.multiple_desc.setGeometry(285, 335, 240, 80)
self.multiple_desc.setStyleSheet(column_desc_style)
self.multiple_desc.setAlignment(QtCore.Qt.AlignmentFlag.AlignCenter)
self.multiple_desc.setWordWrap(True)
self.title = QLabel("Use Existing", self)
self.title.setGeometry(QtCore.QRect(555, 50, 230, 40))
self.title.setAlignment(QtCore.Qt.AlignmentFlag.AlignCenter)
self.title.setStyleSheet(column_title_style)
self.note_label = QLabel(
"Billing IDs are tied to a single location", self)
self.note_label.setGeometry(555, 90, 230, 50)
self.note_label.setStyleSheet(column_desc_style)
self.note_label.setAlignment(QtCore.Qt.AlignmentFlag.AlignCenter)
self.note_label.setWordWrap(True)
self.note_label.show()
self.id_bg_label = QLabel(self)
self.id_bg_label.setGeometry(550, 220, 250, 220)
self.id_bg_label.setPixmap(QPixmap(os.path.join(self.btn_path, "button230x220.png")).scaled(
self.id_bg_label.size(), Qt.AspectRatioMode.KeepAspectRatio))
self.text_edit = QTextEdit(self)
self.text_edit.setGeometry(550, 230, 230, 190)
self.text_edit.setPlaceholderText("Enter your existing billing Id here")
self.text_edit.textChanged.connect(self.toggle_button_state)
separator_style = "color: #00ffff; background-color: #00ffff;"
self.separator_left = QFrame(self)
self.separator_left.setFrameShape(QFrame.Shape.VLine)
self.separator_left.setFrameShadow(QFrame.Shadow.Plain)
self.separator_left.setGeometry(270, 60, 2, 420)
self.separator_left.setStyleSheet(separator_style)
self.separator_right = QFrame(self)
self.separator_right.setFrameShape(QFrame.Shape.VLine)
self.separator_right.setFrameShadow(QFrame.Shadow.Plain)
self.separator_right.setGeometry(540, 60, 2, 420)
self.separator_right.setStyleSheet(separator_style)
def go_single_profile(self):
self.text_edit.clear()
self.custom_window.navigator.navigate("duration_selection")
def go_multiple_profiles(self):
self.custom_window.navigator.navigate("plan_picker")
plan_page = self.custom_window.navigator.get_cached("plan_picker")
if plan_page is not None:
plan_page.start_sync()
def toggle_button_state(self):
text = self.text_edit.toPlainText()
if text.strip():
self.connect_button.setEnabled(True)
else:
self.connect_button.setEnabled(False)
def show_next(self):
self.text_edit.clear()
self.custom_window.navigator.navigate("duration_selection")
def validate_password(self):
self.button_go.setVisible(False)
text = self.text_edit.toPlainText().strip().lower()
if text == "zenaku":
self.button_go.setVisible(True)
def show_object_selected(self, function, page_class):
function()
self.object_selected = page_class
def go_selected(self):
if self.object_selected:
self.custom_window.navigator.navigate(self.object_selected)
self.display.clear()
for boton in self.buttons:
boton.setChecked(False)
self.button_go.setVisible(False)
def reverse(self):
self.custom_window.navigator.navigate("menu")

View file

@ -0,0 +1,488 @@
import os
import subprocess
from PyQt6.QtWidgets import QApplication, QPushButton, QLabel
from PyQt6.QtGui import QIcon
from PyQt6.QtCore import QSize, QTimer
from PyQt6 import QtCore
from gui.v2.ui.pages.Page import Page
from gui.v2.workers.worker_thread import WorkerThread
class InstallSystemPackage(Page):
def __init__(self, page_stack, main_window=None, parent=None):
super().__init__("InstallPackage", page_stack, main_window, parent)
self.btn_path = main_window.btn_path
self.update_status = main_window
self.package_name = ""
self.install_command = ""
self.manual_install_command = ""
self.is_sync = False
self.ui_elements = []
self.permanent_elements = []
self.current_distro = "debian"
self.distro_buttons = {}
self._setup_permanent_ui()
self._setup_distro_buttons()
self.auto_install_package_commands = {
'all': {
'debian': 'pkexec apt install -y bubblewrap iproute2 microsocks proxychains4 ratpoison tor wireguard xserver-xephyr',
'arch': 'pkexec pacman -S bubblewrap iproute2 microsocks proxychains-ng tor wireguard-tools xorg-server-xephyr',
'fedora': 'pkexec dnf -y install bubblewrap iproute proxychains4 ratpoison tor wireguard-tools xorg-x11-server-Xephyr'
},
'bwrap': {
'debian': 'pkexec apt -y install bubblewrap',
'arch': 'pkexec pacman -S bubblewrap',
'fedora': 'pkexec dnf -y install bubblewrap'
},
'ip': {
'debian': 'pkexec apt -y install iproute2',
'arch': 'pkexec pacman -S iproute2',
'fedora': 'pkexec dnf -y install iproute'
},
'microsocks': {
'debian': 'pkexec apt -y install microsocks',
'arch': 'pkexec pacman -S microsocks',
'fedora': 'zenity --warning --text="An essential dependency (microsocks) is not included in your distribution\'s default repository. Please build it from source or contact support."'
},
'proxychains4': {
'debian': 'pkexec apt -y install proxychains4',
'arch': 'pkexec pacman -S proxychains-ng',
'fedora': 'pkexec dnf -y install proxychains4'
},
'ratpoison': {
'debian': 'pkexec apt -y install ratpoison',
'arch': 'zenity --warning --text="An essential dependency (ratpoison) is not included in your distribution\'s default repository. Please build it from source or contact support."',
'fedora': 'pkexec dnf -y install ratpoison'
},
'tor': {
'debian': 'pkexec apt -y install tor',
'arch': 'pkexec pacman -S tor',
'fedora': 'pkexec dnf -y install tor'
},
'wg-quick': {
'debian': 'pkexec apt -y install wireguard',
'arch': 'pkexec pacman -S wireguard-tools',
'fedora': 'pkexec dnf -y install wireguard-tools'
},
'Xephyr': {
'debian': 'pkexec apt -y install xserver-xephyr',
'arch': 'pkexec pacman -S xorg-server-xephyr',
'fedora': 'pkexec dnf -y install xorg-x11-server-Xephyr'
}
}
self.manual_install_package_commands = {
'all': {
'debian': 'sudo apt install -y bubblewrap iproute2 microsocks proxychains4 ratpoison tor wireguard xserver-xephyr',
'arch': 'sudo pacman -S bubblewrap iproute2 microsocks proxychains-ng tor wireguard-tools xorg-server-xephyr',
'fedora': 'sudo dnf -y install bubblewrap iproute proxychains4 ratpoison tor wireguard-tools xorg-x11-server-Xephyr'
},
'bwrap': {
'debian': 'sudo apt -y install bubblewrap',
'arch': 'sudo pacman -S bubblewrap',
'fedora': 'sudo dnf -y install bubblewrap'
},
'ip': {
'debian': 'sudo apt -y install iproute2',
'arch': 'sudo pacman -S iproute2',
'fedora': 'sudo dnf -y install iproute'
},
'microsocks': {
'debian': 'sudo apt -y install microsocks',
'arch': 'sudo pacman -S microsocks',
'fedora': '# Package not available.'
},
'proxychains4': {
'debian': 'sudo apt -y install proxychains4',
'arch': 'sudo pacman -S proxychains-ng',
'fedora': 'sudo dnf -y install proxychains4'
},
'ratpoison': {
'debian': 'sudo apt -y install ratpoison',
'arch': '# Package not available.',
'fedora': 'sudo dnf -y install ratpoison'
},
'tor': {
'debian': 'sudo apt -y install tor',
'arch': 'sudo pacman -S tor',
'fedora': 'sudo dnf -y install tor'
},
'wg-quick': {
'debian': 'sudo apt -y install wireguard',
'arch': 'sudo pacman -S wireguard-tools',
'fedora': 'sudo dnf -y install wireguard-tools'
},
'Xephyr': {
'debian': 'sudo apt -y install xserver-xephyr',
'arch': 'sudo pacman -S xorg-server-xephyr',
'fedora': 'sudo dnf -y install xorg-x11-server-Xephyr'
}
}
def _setup_permanent_ui(self):
self.title.setGeometry(20, 50, 300, 60)
self.title.setText("Package Installation")
self.title.setStyleSheet("font-size: 22px; font-weight: bold;")
self.permanent_elements.append(self.title)
self.display.setGeometry(QtCore.QRect(100, 160, 580, 435))
self.permanent_elements.append(self.display)
def _setup_distro_buttons(self):
distros = ["debian", "arch", "fedora"]
for i, distro in enumerate(distros):
btn = QPushButton(self)
btn.setGeometry(360 + i*140, 40, 120, 49)
btn.setIcon(QIcon(os.path.join(self.btn_path, f"{distro}.png")))
btn.setIconSize(QSize(120, 49))
btn.setCheckable(True)
btn.clicked.connect(
lambda checked, d=distro: self.switch_distro(d))
self.distro_buttons[distro] = btn
self.permanent_elements.append(btn)
self.distro_buttons[self.current_distro].setChecked(True)
def switch_distro(self, distro):
self.current_distro = distro
for d, btn in self.distro_buttons.items():
btn.setChecked(d == distro)
if self.package_name:
self.update_command()
self.setup_ui()
def update_command(self):
if self.package_name in self.auto_install_package_commands:
if self.is_sync:
self.install_command = self.auto_install_package_commands[
'tor_sync'][self.current_distro]
self.manual_install_command = self.manual_install_package_commands[
'tor_sync'][self.current_distro]
else:
self.install_command = self.auto_install_package_commands[
self.package_name][self.current_distro]
self.manual_install_command = self.manual_install_package_commands[
self.package_name][self.current_distro]
else:
self.install_command = ""
self.manual_install_command = ""
def configure(self, package_name, distro, is_sync=False):
self.package_name = package_name
self.current_distro = distro
self.is_sync = is_sync
self.update_command()
self.setup_ui()
return self
def setup_ui(self):
self.button_back.setVisible(False)
self.button_go.setVisible(True)
if not self.package_name:
return
self.clear_ui()
self._setup_auto_install()
self._setup_manual_install()
self._setup_status_and_nav()
for element in self.ui_elements:
element.setParent(self)
element.setVisible(True)
element.raise_()
def clear_ui(self):
for element in self.ui_elements:
element.setParent(None)
element.deleteLater()
self.ui_elements.clear()
self.command_box = None
self.status_label = None
self.install_button = None
self.check_button = None
def _setup_auto_install(self):
auto_install_label = QLabel(
f"Automatic Installation for {self.current_distro}", self)
auto_install_label.setGeometry(80, 130, 580, 30)
auto_install_label.setStyleSheet("font-size: 18px; font-weight: bold;")
auto_install_label.setVisible(True)
self.ui_elements.append(auto_install_label)
auto_install_desc = QLabel(
"Click the button below to automatically install the missing packages:", self)
auto_install_desc.setStyleSheet("font-size: 14px; font-weight: bold;")
auto_install_desc.setGeometry(80, 170, 650, 30)
auto_install_desc.setVisible(True)
self.ui_elements.append(auto_install_desc)
self.install_button = QPushButton(self)
self.install_button.setGeometry(135, 210, 100, 40)
self.install_button.setIcon(
QIcon(os.path.join(self.btn_path, "install.png")))
self.install_button.setIconSize(self.install_button.size())
self.install_button.clicked.connect(self.install_package)
self.install_button.setVisible(True)
self.ui_elements.append(self.install_button)
if self.current_distro == 'arch':
auto_install_label.setGeometry(50, 110, 580, 30)
auto_install_desc.setGeometry(50, 140, 650, 30)
self.install_button.setGeometry(150, 185, 100, 40)
self.check_button = QPushButton(self)
self.check_button.setGeometry(270, 185, 100, 40)
self.check_button.setIcon(
QIcon(os.path.join(self.btn_path, "check.png")))
self.check_button.setIconSize(self.check_button.size())
self.check_button.clicked.connect(self.check_package)
self.check_button.setVisible(True)
self.ui_elements.append(self.check_button)
def _setup_manual_install(self):
if self.current_distro != 'arch':
manual_install_label = QLabel(
f"Manual Installation for {self.current_distro}", self)
manual_install_label.setGeometry(80, 290, 650, 30)
manual_install_label.setStyleSheet(
"font-size: 18px; font-weight: bold;")
manual_install_label.setVisible(True)
self.ui_elements.append(manual_install_label)
manual_install_desc = QLabel(
"Run this command in your terminal:", self)
manual_install_desc.setStyleSheet(
"font-size: 14px; font-weight: bold;")
manual_install_desc.setGeometry(80, 330, 380, 30)
manual_install_desc.setVisible(True)
self.ui_elements.append(manual_install_desc)
self.command_box = QLabel(self.manual_install_command, self)
self.command_box.setGeometry(80, 370, 380, 40)
if self.current_distro == 'arch':
self.command_box.setGeometry(50, 280, 380, 40)
metrics = self.command_box.fontMetrics()
text_width = metrics.horizontalAdvance(
self.manual_install_command) + 100
available_width = self.command_box.width() - 20
font_size = 18
MIN_FONT_SIZE = 10
while text_width > available_width:
if font_size <= MIN_FONT_SIZE:
break
font_size -= 1
text_width -= 10
self.command_box.setStyleSheet(f"""
background-color: #2b2b2b;
color: #ffffff;
padding: 10px;
border-radius: 5px;
font-family: monospace;
font-size: {font_size}px;
""")
self.command_box.mousePressEvent = lambda _: self.copy_command()
self.command_box.setVisible(True)
self.ui_elements.append(self.command_box)
if self.current_distro == 'arch':
pacman_label = QLabel("Pacman Installation:", self)
pacman_label.setGeometry(50, 240, 650, 30)
pacman_label.setStyleSheet(
"font-size: 18px; font-weight: bold; color: #00ffff;")
pacman_label.setVisible(True)
self.ui_elements.append(pacman_label)
aur_label = QLabel("AUR / Yay Installation:", self)
aur_label.setGeometry(50, 350, 300, 30)
aur_label.setStyleSheet(
"font-size: 18px; font-weight: bold; color: #00ffff;")
self.ui_elements.append(aur_label)
self.yay_command_box = QLabel("sudo yay -S ratpoison", self)
self.yay_command_box.setGeometry(50, 390, 300, 40)
self._setup_command_box_style(self.yay_command_box)
self.yay_command_box.mousePressEvent = lambda _: self.copy_command(
"sudo yay -S ratpoison", self.yay_command_box)
self.ui_elements.append(self.yay_command_box)
aur_command = "git clone https://aur.archlinux.org/ratpoison.git\ncd ratpoison\nmakepkg -si --skippgpcheck"
self.aur_command_box = QLabel(aur_command, self)
self.aur_command_box.setGeometry(370, 390, 380, 60)
self._setup_command_box_style(self.aur_command_box)
self.aur_command_box.setWordWrap(True)
self.aur_command_box.mousePressEvent = lambda _: self.copy_command(
aur_command, self.aur_command_box)
self.ui_elements.append(self.aur_command_box)
else:
self.command_box = QLabel(self.manual_install_command, self)
self.command_box.setGeometry(80, 370, 380, 40)
metrics = self.command_box.fontMetrics()
text_width = metrics.horizontalAdvance(
self.manual_install_command) + 100
available_width = self.command_box.width() - 20
font_size = 18
MIN_FONT_SIZE = 10
while text_width > available_width:
if font_size <= MIN_FONT_SIZE:
break
font_size -= 1
text_width -= 10
self.command_box.setStyleSheet(f"""
background-color: #2b2b2b;
color: #ffffff;
padding: 10px;
border-radius: 5px;
font-family: monospace;
font-size: {font_size}px;
""")
self.command_box.mousePressEvent = lambda _: self.copy_command()
self.command_box.setVisible(True)
self.ui_elements.append(self.command_box)
def _setup_command_box_style(self, command_box):
metrics = command_box.fontMetrics()
text_width = metrics.horizontalAdvance(command_box.text()) + 100
available_width = command_box.width() - 20
font_size = 18
MIN_FONT_SIZE = 10
while text_width > available_width:
if font_size <= MIN_FONT_SIZE:
break
font_size -= 1
text_width -= 10
command_box.setStyleSheet(f"""
background-color: #2b2b2b;
color: #ffffff;
padding: 10px;
border-radius: 5px;
font-family: monospace;
font-size: {font_size}px;
""")
command_box.setVisible(True)
def copy_yay_command(self):
clipboard = QApplication.clipboard()
clipboard.setText("sudo yay -S ratpoison")
original_style = self.yay_command_box.styleSheet()
self.yay_command_box.setStyleSheet(
original_style + "background-color: #27ae60;")
QTimer.singleShot(
200, lambda: self.yay_command_box.setStyleSheet(original_style))
self.update_status.update_status("Yay command copied to clipboard")
def _setup_status_and_nav(self):
if self.current_distro != 'arch':
self.check_button = QPushButton(self)
self.check_button.setGeometry(130, 430, 100, 40)
self.check_button.setIcon(
QIcon(os.path.join(self.btn_path, "check.png")))
self.check_button.setIconSize(self.check_button.size())
self.check_button.clicked.connect(self.check_package)
self.ui_elements.append(self.check_button)
self.button_go.clicked.connect(self.go_next)
self.button_back.clicked.connect(self.go_next)
self.status_label = QLabel("", self)
self.status_label.setGeometry(300, 430, 250, 40)
if self.current_distro == 'arch':
self.status_label.setGeometry(450, 185, 250, 40)
self.ui_elements.append(self.status_label)
def copy_command(self, command=None, command_box=None):
if command_box is None:
command_box = self.command_box
clipboard = QApplication.clipboard()
if command:
clipboard.setText(command)
else:
clipboard.setText(self.manual_install_command)
original_style = command_box.styleSheet()
command_box.setStyleSheet(
original_style + "background-color: #27ae60;")
QTimer.singleShot(
200, lambda: command_box.setStyleSheet(original_style))
self.update_status.update_status("Command copied to clipboard")
def install_package(self):
if not self.install_command:
self.update_status.update_status(
f"Installation not supported for {self.current_distro}")
return
if subprocess.getstatusoutput('pkexec --help')[0] == 127:
self.update_status.update_status("polkit toolkit is not installed")
self.install_button.setDisabled(True)
return
self.worker_thread = WorkerThread('INSTALL_PACKAGE',
package_command=self.install_command,
package_name=self.package_name)
self.worker_thread.text_output.connect(
self.update_status.update_status)
self.worker_thread.finished.connect(self.installation_complete)
self.worker_thread.start()
def installation_complete(self, success):
if success:
self.check_package()
def check_package(self):
try:
if self.package_name.lower() == 'all':
packages = ['bwrap', 'ip', 'microsocks',
'proxychains4', 'ratpoison', 'tor', 'Xephyr', 'wg']
for package in packages:
if subprocess.getstatusoutput(f'{package} --help')[0] == 127:
self.status_label.setText(
f"{package} is not installed")
self.status_label.setStyleSheet("color: red;")
return
self.status_label.setText("All packages installed!")
self.status_label.setStyleSheet("color: green;")
elif self.package_name.lower() == 'wireguard':
self.install_name = 'wg'
elif self.package_name.lower() == 'bubblewrap':
self.install_name = 'bwrap'
elif self.package_name.lower() == 'xserver-xephyr':
self.install_name = 'Xephyr'
else:
self.install_name = self.package_name
if subprocess.getstatusoutput(f'{self.install_name.lower()} --help')[0] == 127:
self.status_label.setText(
f"{self.package_name} is not installed")
self.status_label.setStyleSheet("color: red;")
else:
self.status_label.setText(
f"{self.package_name} is installed!")
self.status_label.setStyleSheet("color: green;")
except Exception:
self.status_label.setText("Check failed")
self.status_label.setStyleSheet("color: red;")
def go_next(self):
self.custom_window.navigator.navigate("menu")

254
gui/v2/ui/pages/location_page.py Executable file
View file

@ -0,0 +1,254 @@
import os
from PyQt6.QtWidgets import QApplication, QLabel, QPushButton, QButtonGroup
from PyQt6.QtGui import QPixmap, QIcon, QPainter, QColor, QFont, QTransform
from PyQt6.QtCore import Qt
from PyQt6 import QtCore
from gui.v2.ui.pages.Page import Page
class LocationPage(Page):
def __init__(self, page_stack, main_window, parent=None):
super().__init__("Location", page_stack, main_window, parent)
self.selected_location_icon = None
self.update_status = main_window
self.button_reverse.setVisible(True)
self.connection_manager = main_window.connection_manager
self.button_reverse.clicked.connect(self.reverse)
self.display.setGeometry(QtCore.QRect(5, 10, 390, 520))
self.title.setGeometry(395, 40, 380, 40)
self.title.setText("Pick a location")
self.initial_display = QLabel(self)
self.initial_display.setGeometry(5, 22, 355, 485)
self.initial_display.setAlignment(Qt.AlignmentFlag.AlignCenter)
self.initial_display.setWordWrap(True)
self.verification_button = QPushButton("More Info", self)
self.verification_button.setGeometry(10, 70, 160, 40)
self.verification_button.setStyleSheet(f"""
QPushButton {{
background-color: rgba(60, 80, 120, 1.0);
border: 2px solid rgba(100, 140, 200, 1.0);
border-radius: 5px;
color: rgb(255, 255, 255);
font-size: 15px;
font-weight: bold;
padding: 5px 10px;
}}
QPushButton:hover {{
background-color: rgba(80, 100, 140, 1.0);
border: 2px solid rgba(120, 160, 220, 1.0);
}}
QPushButton:disabled {{
background-color: rgba(40, 50, 70, 0.6);
border: 2px solid rgba(70, 90, 110, 0.6);
opacity: 0.5;
}}
""")
self.verification_button.setCursor(
QtCore.Qt.CursorShape.PointingHandCursor)
self.verification_button.setEnabled(False)
self.verification_button.show()
if self.connection_manager.is_synced():
from gui.v2.actions.sync import generate_grid_positions
locs = self.connection_manager.get_location_list()
positions = generate_grid_positions(len(locs))
available = [(QPushButton, loc, positions[i]) for i, loc in enumerate(locs)]
self.create_interface_elements(available)
def showEvent(self, event):
super().showEvent(event)
self.button_next.setVisible(False)
for button in self.buttons:
button.setChecked(False)
if hasattr(self, 'verification_button'):
self.verification_button.setEnabled(False)
def create_interface_elements(self, available_locations):
self.buttonGroup = QButtonGroup(self)
self.buttons = []
for j, (object_type, icon_name, geometry) in enumerate(available_locations):
boton = object_type(self)
boton.setGeometry(*geometry)
boton.setIconSize(boton.size())
boton.setCheckable(True)
locations = self.connection_manager.get_location_info(icon_name)
if locations and not (hasattr(locations, 'is_wireguard_capable') and locations.is_wireguard_capable):
boton.setVisible(False)
icon_path = os.path.join(self.btn_path, f"button_{icon_name}.png")
boton.setIcon(QIcon(icon_path))
fallback_path = os.path.join(
self.btn_path, "default_location_button.png")
provider = locations.operator.name if locations and hasattr(
locations, 'operator') else None
if boton.icon().isNull():
if locations and hasattr(locations, 'country_name'):
location_name = locations.country_name
base_image = LocationPage.create_location_button_image(
location_name, fallback_path, provider)
boton.setIcon(QIcon(base_image))
else:
base_image = LocationPage.create_location_button_image(
'', fallback_path, provider)
boton.setIcon(QIcon(base_image))
else:
if locations and hasattr(locations, 'country_name'):
base_image = LocationPage.create_location_button_image(
f'{locations.country_code}_{locations.code}', fallback_path, provider, icon_path)
boton.setIcon(QIcon(base_image))
else:
base_image = LocationPage.create_location_button_image(
'', fallback_path, provider, icon_path)
boton.setIcon(QIcon(base_image))
self.buttons.append(boton)
self.buttonGroup.addButton(boton, j)
boton.location_icon_name = icon_name
boton.setCursor(QtCore.Qt.CursorShape.PointingHandCursor)
boton.clicked.connect(
lambda checked, loc=icon_name: self.show_location(loc))
def update_swarp_json(self, get_connection=False):
profile_data = self.update_status.read_data()
self.connection_type = profile_data.get("connection", "")
if get_connection:
return self.connection_type
inserted_data = {
"location": self.selected_location_icon
}
self.update_status.write_data(inserted_data)
def show_location(self, location):
self.initial_display.hide()
max_size = QtCore.QSize(300, 300)
target_size = self.display.size().boundedTo(max_size)
self.display.setPixmap(QPixmap(os.path.join(self.btn_path, f"icon_{location}.png")).scaled(
target_size, Qt.AspectRatioMode.KeepAspectRatio))
self.selected_location_icon = location
self.button_next.setVisible(True)
self.button_next.clicked.connect(self.go_selected)
self.update_swarp_json()
try:
self.verification_button.clicked.disconnect()
except TypeError:
pass
self.verification_button.clicked.connect(
lambda: self.show_location_verification(location))
self.verification_button.setEnabled(True)
def reverse(self):
self.custom_window.navigator.navigate("protocol")
def go_selected(self):
if self.connection_type == "system-wide":
self.custom_window.navigator.navigate("resume")
else:
self.custom_window.navigator.navigate("browser")
def show_location_verification(self, location_icon_name):
from gui.v2.ui.pages.location_verification_page import LocationVerificationPage
verification_page = LocationVerificationPage(
self.page_stack, self.update_status, location_icon_name, self)
self.page_stack.addWidget(verification_page)
self.page_stack.setCurrentIndex(
self.page_stack.indexOf(verification_page))
@staticmethod
def create_location_button_image(location_name, fallback_image_path, provider=None, existing_icon_path=None):
if existing_icon_path:
base_image = QPixmap(existing_icon_path)
if base_image.isNull():
base_image = QPixmap(fallback_image_path)
draw_location_text = False
else:
base_image = QPixmap(fallback_image_path)
draw_location_text = True
if base_image.isNull():
return base_image
painter = QPainter(base_image)
try:
if draw_location_text:
font_size = 12
app_font = QApplication.font()
app_font.setPointSize(font_size)
app_font.setWeight(QFont.Weight.Bold)
text_length = len(location_name)
if text_length <= 10:
font_stretch = 100
text_width_scale = 1.1
text_height_scale = 1.4
elif text_length <= 15:
font_stretch = 90
text_width_scale = 1.0
text_height_scale = 2
else:
font_stretch = 100
text_width_scale = 1.1
text_height_scale = 4.5
app_font.setStretch(font_stretch)
painter.setFont(app_font)
painter.setPen(QColor(0, 255, 255))
text_rect = painter.fontMetrics().boundingRect(location_name)
max_width = 110
while text_rect.width() > max_width:
font_size -= 1
app_font.setPointSize(font_size)
app_font.setWeight(QFont.Weight.Bold)
painter.setFont(app_font)
text_rect = painter.fontMetrics().boundingRect(location_name)
x = ((base_image.width() - text_rect.width()) // 2) - 30
y = ((base_image.height() + text_rect.height()) // 2) - 15
transform = QTransform()
transform.scale(text_width_scale, text_height_scale)
painter.setTransform(transform)
x_scaled = int(x / text_width_scale)
y_scaled = int(y / text_height_scale)
shadow_offset = 1
painter.setPen(QColor(0, 0, 0))
painter.drawText(x_scaled + shadow_offset,
y_scaled + shadow_offset, location_name)
painter.setPen(QColor(0, 255, 255))
painter.drawText(x_scaled, y_scaled, location_name)
painter.setTransform(QTransform())
if provider:
painter.setPen(QColor(128, 128, 128))
provider_text_font = QApplication.font()
provider_text_font.setPointSize(7)
provider_text_font.setWeight(QFont.Weight.Normal)
painter.setFont(provider_text_font)
provider_text_rect = painter.fontMetrics().boundingRect(provider)
provider_x = (
(base_image.width() - provider_text_rect.width()) // 2) - 30
provider_y = 50
px = int(provider_x)
py = int(provider_y)
painter.setFont(provider_text_font)
painter.drawText(px + 5, py + 2, provider)
finally:
painter.end()
return base_image

View file

@ -0,0 +1,284 @@
import os
from PyQt6.QtWidgets import QApplication, QLabel, QPushButton
from PyQt6.QtGui import QPixmap, QIcon
from PyQt6.QtCore import Qt, QSize
from PyQt6 import QtCore
from gui.v2.ui.pages.Page import Page
class LocationVerificationPage(Page):
def __init__(self, page_stack, main_window, location_icon_name, previous_page, parent=None):
super().__init__("LocationVerification", page_stack, main_window, parent)
self.location_icon_name = location_icon_name
self.previous_page = previous_page
self.connection_manager = main_window.connection_manager
self.font_style = f"font-family: '{main_window.open_sans_family}';"
self.verification_info = {}
self.verification_checkmarks = {}
self.verification_full_values = {}
self.verification_labels = {}
self.verification_copy_buttons = {}
self.verification_display_names = {
"operator_name": "Operator Name",
"provider_name": "Provider Name",
"nostr_public_key": "Nostr Key",
"hydraveil_public_key": "HydraVeil Key",
"nostr_attestation_event_reference": "Nostr Verification"
}
self.setup_ui()
def setup_ui(self):
location_display = QLabel(self)
location_display.setGeometry(0, 0, 390, 520)
location_display.setAlignment(Qt.AlignmentFlag.AlignCenter)
icon_path = os.path.join(
self.btn_path, f"icon_{self.location_icon_name}.png")
location_pixmap = QPixmap(icon_path)
fallback_path = os.path.join(
self.btn_path, "default_location_button.png")
if location_pixmap.isNull():
location_pixmap = QPixmap(fallback_path)
locations = self.connection_manager.get_location_info(
self.location_icon_name)
operator_name = locations.operator.name if locations and hasattr(
locations, 'operator') else ""
max_size = QtCore.QSize(300, 300)
target_size = location_display.size().boundedTo(max_size)
location_display.setPixmap(location_pixmap.scaled(
target_size, Qt.AspectRatioMode.KeepAspectRatio, Qt.TransformationMode.SmoothTransformation))
location_display.show()
title = QLabel("Operator Information", self)
title.setGeometry(350, 50, 350, 30)
title.setStyleSheet(
f"color: #808080; font-size: 20px; font-weight: bold; {self.font_style}")
title.show()
info_items = [
("Operator Name", "operator_name", 130),
("Provider Name", "provider_name", 180),
("Nostr Key", "nostr_public_key", 230),
("HydraVeil Key", "hydraveil_public_key", 280),
("Nostr Verification", "nostr_attestation_event_reference", 330),
]
label_x = 350
label_width = 150
label_height = 30
value_x = 510
value_width = 210
value_height = 30
checkmark_x = 725
checkmark_width = 30
checkmark_height = 30
copy_button_x = 750
copy_button_width = 30
copy_button_height = 30
for label_text, key, y_pos in info_items:
label_widget = QLabel(label_text + ":", self)
label_widget.setGeometry(label_x, y_pos, label_width, label_height)
label_widget.setStyleSheet(
f"color: white; font-size: 18px; {self.font_style}")
label_widget.setAlignment(
Qt.AlignmentFlag.AlignLeft | Qt.AlignmentFlag.AlignVCenter)
label_widget.show()
self.verification_labels[key] = label_widget
value_widget = QLabel("N/A", self)
value_widget.setGeometry(value_x, y_pos, value_width, value_height)
value_widget.setStyleSheet(
f"color: white; font-size: 16px; {self.font_style}")
value_widget.setAlignment(
Qt.AlignmentFlag.AlignLeft | Qt.AlignmentFlag.AlignVCenter)
value_widget.show()
checkmark_widget = QLabel("", self)
checkmark_widget.setGeometry(
checkmark_x, y_pos, checkmark_width, checkmark_height)
checkmark_widget.setStyleSheet(
f"color: #4CAF50; font-size: 28px; font-weight: bold; {self.font_style}")
checkmark_widget.setAlignment(Qt.AlignmentFlag.AlignCenter)
checkmark_widget.hide()
self.verification_info[key] = value_widget
self.verification_checkmarks[key] = checkmark_widget
copy_button = QPushButton(self)
copy_button.setGeometry(
copy_button_x, y_pos, copy_button_width, copy_button_height)
copy_button.setIcon(
QIcon(os.path.join(self.btn_path, "paste_button.png")))
copy_button.setIconSize(QSize(24, 24))
copy_button.setStyleSheet(f"""
QPushButton {{
background: transparent;
border: none;
}}
QPushButton:hover {{
background: rgba(255, 255, 255, 0.1);
border-radius: 4px;
}}
""")
copy_button.clicked.connect(
lambda checked=False, k=key: self.copy_verification_value(k))
copy_button.show()
self.verification_copy_buttons[key] = copy_button
self.back_button = QPushButton(self)
self.back_button.setGeometry(720, 533, 48, 35)
self.back_button.setIcon(
QIcon(os.path.join(self.btn_path, "back.png")))
self.back_button.setIconSize(QSize(48, 35))
self.back_button.setStyleSheet(f"""
QPushButton {{
background: transparent;
border: none;
}}
QPushButton:hover {{
background: rgba(255, 255, 255, 0.1);
border-radius: 4px;
}}
""")
self.back_button.clicked.connect(self.go_back)
self.back_button.show()
self.update_verification_info()
def truncate_text_by_width(self, text, widget, max_width):
if not text or text == "N/A":
return text
metrics = widget.fontMetrics()
text_width = metrics.horizontalAdvance(text)
if text_width <= max_width:
return text
ellipsis = "..."
ellipsis_width = metrics.horizontalAdvance(ellipsis)
available_width = max_width - ellipsis_width
if available_width <= 0:
return ellipsis
start_chars = 1
end_chars = 1
while True:
start_text = text[:start_chars]
end_text = text[-end_chars:]
combined_width = metrics.horizontalAdvance(
start_text + ellipsis + end_text)
if combined_width <= max_width:
if start_chars + end_chars >= len(text):
return text
start_chars += 1
if start_chars + end_chars >= len(text):
return text
end_chars += 1
else:
if start_chars > 1:
start_chars -= 1
if end_chars > 1:
end_chars -= 1
break
if start_chars + end_chars >= len(text):
return text
return text[:start_chars] + ellipsis + text[-end_chars:]
def update_verification_info(self):
locations = self.connection_manager.get_location_info(
self.location_icon_name)
value_width = 210
if not locations:
for key, widget in self.verification_info.items():
widget.setText("N/A")
self.verification_full_values[key] = "N/A"
if key in self.verification_checkmarks:
self.verification_checkmarks[key].hide()
return
operator = None
if locations.operator:
operator = locations.operator
if operator:
operator_name = operator.name or "N/A"
self.verification_full_values["operator_name"] = operator_name
display_operator = self.truncate_text_by_width(
operator_name, self.verification_info["operator_name"], value_width)
self.verification_info["operator_name"].setText(display_operator)
provider_name = locations.provider_name if locations and hasattr(
locations, 'provider_name') and locations.provider_name else "N/A"
self.verification_full_values["provider_name"] = provider_name
display_provider = self.truncate_text_by_width(
provider_name, self.verification_info["provider_name"], value_width)
self.verification_info["provider_name"].setText(display_provider)
nostr_key = operator.nostr_public_key or "N/A"
self.verification_full_values["nostr_public_key"] = nostr_key
display_nostr = self.truncate_text_by_width(
nostr_key, self.verification_info["nostr_public_key"], value_width)
self.verification_info["nostr_public_key"].setText(display_nostr)
hydraveil_key = operator.public_key or "N/A"
self.verification_full_values["hydraveil_public_key"] = hydraveil_key
display_hydraveil = self.truncate_text_by_width(
hydraveil_key, self.verification_info["hydraveil_public_key"], value_width)
self.verification_info["hydraveil_public_key"].setText(
display_hydraveil)
nostr_verification = operator.nostr_attestation_event_reference or "N/A"
self.verification_full_values["nostr_attestation_event_reference"] = nostr_verification
display_nostr_verif = self.truncate_text_by_width(
nostr_verification, self.verification_info["nostr_attestation_event_reference"], value_width)
self.verification_info["nostr_attestation_event_reference"].setText(
display_nostr_verif)
else:
self.verification_info["operator_name"].setText("N/A")
self.verification_full_values["operator_name"] = "N/A"
self.verification_info["provider_name"].setText("N/A")
self.verification_full_values["provider_name"] = "N/A"
self.verification_info["nostr_public_key"].setText("N/A")
self.verification_full_values["nostr_public_key"] = "N/A"
self.verification_info["hydraveil_public_key"].setText("N/A")
self.verification_full_values["hydraveil_public_key"] = "N/A"
self.verification_info["nostr_attestation_event_reference"].setText(
"N/A")
self.verification_full_values["nostr_attestation_event_reference"] = "N/A"
for key, widget in self.verification_info.items():
if key in self.verification_checkmarks:
full_value = self.verification_full_values.get(key, "")
if full_value and full_value != "N/A":
self.verification_checkmarks[key].show()
else:
self.verification_checkmarks[key].hide()
def copy_verification_value(self, key):
full_value = self.verification_full_values.get(key, "")
if full_value and full_value != "N/A":
clipboard = QApplication.clipboard()
clipboard.setText(full_value)
display_name = self.verification_display_names.get(
key, "Verification value")
self.custom_window.update_status(
f"{display_name} copied to clipboard")
def go_back(self):
if self.previous_page:
self.page_stack.setCurrentIndex(
self.page_stack.indexOf(self.previous_page))

1247
gui/v2/ui/pages/menu_page.py Executable file

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,149 @@
import os
from PyQt6.QtWidgets import QApplication, QButtonGroup, QFrame, QLabel, QPushButton
from PyQt6.QtGui import QIcon, QPainter
from PyQt6.QtCore import Qt, QPointF, QRect, QSize, QTimer
from gui.v2.ui.pages.Page import Page
from gui.v2.ui.widgets.clickable_label import ClickableLabel
from gui.v2.ui.widgets.confetti import ConfettiThread
class PaymentConfirmed(Page):
def __init__(self, page_stack, main_window=None, parent=None):
super().__init__("Id", page_stack, main_window, parent)
self.update_status = main_window
self.buttonGroup = QButtonGroup(self)
self.display.setGeometry(QRect(-10, 50, 550, 460))
self.button_reverse.setVisible(False)
self.confetti = []
self.icon_label = QLabel(self)
icon = QIcon(os.path.join(self.btn_path, "check_icon.png"))
pixmap = icon.pixmap(QSize(64, 64))
self.icon_label.setPixmap(pixmap)
self.icon_label.setAlignment(Qt.AlignmentFlag.AlignCenter)
self.label = QLabel("Payment Completed!",
alignment=Qt.AlignmentFlag.AlignCenter, parent=self)
self.label.setStyleSheet(
"font-size: 24px; color: #2ecc71; font-weight: bold;")
self.billing_label = QLabel(
"Your billing code:", alignment=Qt.AlignmentFlag.AlignCenter, parent=self)
self.billing_label.setStyleSheet("font-size: 18px; color: white;")
self.billing_code = ClickableLabel(
"", alignment=Qt.AlignmentFlag.AlignCenter, parent=self)
self.billing_code.setStyleSheet("""
font-size: 24px;
color: white;
font-weight: bold;
padding: 5px;
border-radius: 5px;
""")
self.billing_code.clicked.connect(self.copy_billing_code)
self.billing_code.set_hover_width(450)
self.top_line = QFrame(self)
self.top_line.setFrameShape(QFrame.Shape.HLine)
self.top_line.setStyleSheet("color: #bdc3c7;")
self.bottom_line = QFrame(self)
self.bottom_line.setFrameShape(QFrame.Shape.HLine)
self.bottom_line.setStyleSheet("color: #bdc3c7;")
self.next_button = QPushButton("Next", self)
self.next_button.setStyleSheet("""
QPushButton {
background-color: #3498db;
color: white;
border: none;
padding: 10px;
font-size: 18px;
border-radius: 5px;
}
QPushButton:hover {
background-color: #2980b9;
}
""")
self.next_button.clicked.connect(self.on_next_button)
self.confetti_thread = ConfettiThread(self.width(), self.height())
self.confetti_thread.update_signal.connect(self.update_confetti)
self.confetti_thread.start()
def copy_billing_code(self):
clipboard = QApplication.clipboard()
clipboard.setText(self.billing_code.text())
original_style = self.billing_code.styleSheet()
self.billing_code.setText("Copied!")
self.billing_code.setStyleSheet(
original_style + "background-color: #27ae60;")
QTimer.singleShot(
1000, lambda: self.reset_billing_code_style(original_style))
def reset_billing_code_style(self, original_style):
self.billing_code.setStyleSheet(original_style)
self.billing_code.setText(self.current_billing_code)
def set_billing_code(self, code):
self.current_billing_code = code
self.billing_code.setText(code)
def update_confetti(self, confetti):
self.confetti = confetti
self.update()
def paintEvent(self, event):
super().paintEvent(event)
painter = QPainter(self)
painter.setRenderHint(QPainter.RenderHint.Antialiasing)
for particle in self.confetti:
painter.setBrush(particle.color)
painter.setPen(Qt.PenStyle.NoPen)
painter.drawEllipse(QPointF(particle.x, particle.y),
particle.size, particle.size)
painter.end()
def resizeEvent(self, event):
super().resizeEvent(event)
width = event.size().width()
height = event.size().height()
self.icon_label.setGeometry(
QRect(width // 2 - 32, height // 8, 64, 64))
self.label.setGeometry(QRect(0, height // 4, width, 50))
self.billing_label.setGeometry(QRect(0, height // 2 - 50, width, 30))
self.top_line.setGeometry(
QRect(width // 4, height // 2 + 20, width // 2, 1))
self.billing_code.setGeometry(QRect(0, height // 2 + 28, width, 40))
self.bottom_line.setGeometry(
QRect(width // 4, height // 2 + 75, width // 2, 1))
self.next_button.setGeometry(
QRect(width // 4, height * 3 // 4, width // 2, 50))
self.confetti_thread.update_dimensions(width, height)
def closeEvent(self, event):
self.confetti_thread.stop()
self.confetti_thread.wait()
super().closeEvent(event)
def on_next_button(self):
self.custom_window.navigator.navigate("menu")
def find_menu_page(self):
return self.custom_window.navigator.get_cached("menu")
def reverse(self):
self.custom_window.navigator.navigate("wireguard")

View file

@ -0,0 +1,419 @@
import os
from PyQt6.QtWidgets import QApplication, QLabel, QLineEdit, QPushButton
from PyQt6.QtGui import QIcon, QPixmap
from PyQt6.QtCore import QSize, QTimer
from PyQt6 import QtCore
from gui.v2.ui.pages.Page import Page
from gui.v2.ui.popups.qrcode_dialog import QRCodeDialog
from gui.v2.workers.ticketing_worker_thread import TicketingWorkerThread
from gui.v2.workers.worker_thread import WorkerThread
class PaymentDetailsPage(Page):
def __init__(self, page_stack, main_window=None, parent=None):
super().__init__("Payment Details", page_stack, main_window, parent)
self.update_status = main_window
self.text_fields = []
self.invoice_data = {}
self.selected_duration = None
self.selected_currency = None
self.ticketing = False
self.temp_billing_code = None
self._ticket_check_running = False
self._ticket_worker = None
self.ticket_poll_timer = QTimer(self)
self.ticket_poll_timer.timeout.connect(self._tick_check_paid)
self.create_interface_elements()
self.button_reverse.setVisible(True)
def create_interface_elements(self):
self.title = QLabel("Payment Details", self)
self.title.setGeometry(QtCore.QRect(530, 30, 210, 40))
self.title.setAlignment(QtCore.Qt.AlignmentFlag.AlignCenter)
self.button_reverse.clicked.connect(self.reverse)
self.qr_code_button = QPushButton(self)
self.qr_code_button.setGeometry(360, 435, 50, 50)
qr_icon = QIcon(os.path.join(self.btn_path, "qr-code.png"))
self.qr_code_button.setIcon(qr_icon)
self.qr_code_button.setIconSize(self.qr_code_button.size())
self.qr_code_button.clicked.connect(self.show_qr_code)
self.qr_code_button.setToolTip("Show QR Code")
self.qr_code_button.setDisabled(True)
labels_info = [
("Your new billing ID", None, (20, 40), (240, 50)),
("", "cuadro400x50", (20, 90), (400, 50)),
("Duration", None, (20, 155), (150, 50)),
("", "cuadro150x50", (200, 155), (150, 50)),
("Amount", None, (10, 220), (150, 50)),
("", "cuadro150x50", (200, 220), (150, 50)),
("Hit this white icon to copy paste the address:",
None, (15, 305), (500, 30)),
("", "cuadro400x50", (20, 350), (400, 50)),
("Hit this icon to Scan the QR code:", None, (15, 450), (360, 30)),
]
for text, image_name, position, size in labels_info:
label = QLabel(self)
label.setGeometry(position[0], position[1], size[0], size[1])
if "Hit" in text:
label.setStyleSheet("font-size: 16px;")
label.setAlignment(
QtCore.Qt.AlignmentFlag.AlignLeft | QtCore.Qt.AlignmentFlag.AlignVCenter)
else:
label.setAlignment(QtCore.Qt.AlignmentFlag.AlignCenter)
if image_name:
pixmap = QPixmap(os.path.join(
self.btn_path, f"{image_name}.png"))
label.setPixmap(pixmap)
label.setScaledContents(True)
else:
label.setText(text)
line_edit_info = [
(20, 90, 400, 50),
(200, 155, 150, 50),
(200, 220, 150, 50),
(20, 350, 400, 50),
]
for x, y, width, height in line_edit_info:
line_edit = QLineEdit(self)
line_edit.setGeometry(x, y, width, height)
line_edit.setReadOnly(True)
self.text_fields.append(line_edit)
line_edit.setAlignment(QtCore.Qt.AlignmentFlag.AlignCenter)
copy_button_info = [
(430, 90, 0),
(360, 220, 2),
(480, 290, 3),
]
for x, y, field_index in copy_button_info:
button = QPushButton(self)
button.setGeometry(x, y, 50, 50)
icon = QIcon(os.path.join(self.btn_path, "paste_button.png"))
button.setIcon(icon)
button.setIconSize(button.size())
button.clicked.connect(
lambda checked, index=field_index: self.copy_text(index))
currency_button_info = [
("monero", (545, 75)),
("bitcoin", (545, 290)),
("lightnering", (545, 180)),
("litecoin", (545, 395))
]
self.currency_display_buttons = []
for icon_name, position in currency_button_info:
button = QPushButton(self)
button.setGeometry(position[0], position[1], 185, 75)
button.setIconSize(QSize(190, 120))
button.setCheckable(True)
button.setEnabled(False)
self.currency_display_buttons.append(button)
button.setIcon(
QIcon(os.path.join(self.btn_path, f"{icon_name}.png")))
def fetch_invoice_duration(self):
duration_month_num = int(self.selected_duration.split()[0])
if duration_month_num == 12:
total_hours = 365 * 24
else:
total_hours = duration_month_num * (30 * 24)
return total_hours
def display_selected_options(self):
if self.selected_duration:
self.text_fields[1].setText(self.selected_duration)
self.text_fields[1].setStyleSheet('font-size: 13px;')
if self.selected_currency:
currency_index_map = {
"monero": 0,
"bitcoin": 1,
"lightning": 2,
"litecoin": 3
}
currency_index = currency_index_map.get(self.selected_currency, -1)
if currency_index >= 0:
for i, button in enumerate(self.currency_display_buttons):
button.setChecked(i == currency_index)
button.setEnabled(i == currency_index)
def on_request_invoice(self):
total_hours = self.fetch_invoice_duration()
if self.selected_currency is None:
self.update_status.update_status('No currency selected')
return
profile_data = {
'id': int(self.update_status.current_profile_id),
'duration': total_hours,
'currency': 'xmr' if self.selected_currency == 'monero' else 'btc' if self.selected_currency == 'bitcoin' else 'btc-ln' if self.selected_currency == 'lightning' else 'ltc' if self.selected_currency == 'litecoin' else None
}
self.update_status.update_status('Generating Invoice...')
self.worker_thread = WorkerThread(
'GET_SUBSCRIPTION', profile_data=profile_data)
self.worker_thread.text_output.connect(self.invoice_update_text_output)
self.worker_thread.invoice_output.connect(
self.on_invoice_generation_finished)
self.worker_thread.invoice_finished.connect(self.on_invoice_finished)
self.worker_thread.start()
def invoice_update_text_output(self, text):
self.update_status.update_status(text)
if 'No compatible' in text:
for line in self.text_fields:
line.setText('')
def store_invoice_data(self, billing_details: dict, duration: int):
self.invoice_data = {
'billing_details': billing_details,
'duration': duration
}
def check_invoice(self):
self.display_selected_options()
total_hours = self.fetch_invoice_duration()
if self.invoice_data and self.invoice_data['duration'] == total_hours:
self.update_status.update_status('Checking invoice status...')
self.check_invoice_status(
self.invoice_data['billing_details'].billing_code)
else:
self.on_request_invoice()
def check_invoice_status(self, billing_code: str):
self.worker_thread = WorkerThread('CHECK_INVOICE_STATUS', profile_data={
'billing_code': billing_code})
self.worker_thread.invoice_finished.connect(
self.on_invoice_status_finished)
self.worker_thread.start()
def on_invoice_status_finished(self, result):
if result:
self.parse_invoice_data(self.invoice_data['billing_details'])
else:
self.update_status.update_status(
'Invoice has expired. Generating new invoice...')
self.on_request_invoice()
def on_invoice_generation_finished(self, billing_details: object, text: str):
total_hours = self.fetch_invoice_duration()
self.store_invoice_data(billing_details, duration=total_hours)
self.parse_invoice_data(billing_details)
def parse_invoice_data(self, invoice_data: object):
billing_details = {
'billing_code': invoice_data.billing_code
}
if self.selected_currency.lower() == 'lightning':
currency = 'Bitcoin Lightning'
else:
currency = self.selected_currency
preferred_method = next(
(pm for pm in invoice_data.payment_methods if pm.name.lower() == currency.lower()), None)
if preferred_method:
billing_details['due_amount'] = preferred_method.due
billing_details['address'] = preferred_method.address
else:
self.update_status.update_status(
'No payment method found for the selected currency.')
return
billing_values = list(billing_details.values())
text_field_indices = [0, 2, 3]
for i, dict_value in enumerate(billing_values):
if i < 3:
field_index = text_field_indices[i]
if i == 2:
text = str(dict_value)
self.full_address = text
metrics = self.text_fields[field_index].fontMetrics()
width = self.text_fields[field_index].width()
elided_text = metrics.elidedText(
text, QtCore.Qt.TextElideMode.ElideMiddle, width)
self.text_fields[field_index].setText(elided_text)
elif i == 1:
text = str(dict_value)
self.text_fields[field_index].setProperty("fullText", text)
self.text_fields[field_index].setText(text)
font_size = 14
if len(text) > 9:
font_size = 15
if len(text) > 13:
font_size = 10
if len(text) > 16:
font_size = 10
self.text_fields[field_index].setStyleSheet(
f"font-size: {font_size}px; font-weight: bold;")
else:
self.text_fields[field_index].setProperty(
"fullText", str(dict_value))
self.text_fields[field_index].setText(str(dict_value))
self.qr_code_button.setDisabled(False)
self.update_status.update_status(
'Invoice generated. Awaiting payment...')
def on_invoice_finished(self, result):
if result:
self.invoice_data.clear()
self.show_payment_confirmed(self.text_fields[0].text())
return
self.update_status.update_status(
'An error occurred when generating invoice')
def show_payment_confirmed(self, billing_code):
self.custom_window.navigator.navigate("payment_confirmed")
payment_page = self.custom_window.navigator.get_cached("payment_confirmed")
if payment_page is not None:
for line in self.text_fields:
line.setText('')
payment_page.set_billing_code(billing_code)
else:
print("PaymentConfirmed page not found")
def find_payment_confirmed_page(self):
return self.custom_window.navigator.get_cached("payment_confirmed")
def copy_text(self, field: int):
try:
original_status = self.update_status.status_label.text()
original_status = original_status.replace("Status: ", "")
if int(field) == 3:
text = self.full_address
else:
text = self.text_fields[int(field)].text()
QApplication.clipboard().setText(text)
if field == 0:
self.update_status.update_status(
'Billing code copied to clipboard!')
elif field == 3:
self.update_status.update_status(
'Address copied to clipboard!')
else:
self.update_status.update_status(
'Pay amount copied to clipboard!')
QTimer.singleShot(
2000, lambda: self.update_status.update_status(original_status))
except AttributeError:
self.update_status.update_status(
'No content available for copying')
except Exception as e:
self.update_status.update_status(
f'An error occurred when copying the text')
def reverse(self):
if self.ticket_poll_timer.isActive():
self.ticket_poll_timer.stop()
if self.ticketing:
self.ticketing = False
self.custom_window.navigator.navigate("ticket_crypto_picker")
return
self.custom_window.navigator.navigate("currency_selection")
def set_ticket_invoice(self, invoice, plan_key):
self.ticketing = True
self.temp_billing_code = getattr(invoice, 'temp_billing_code', None)
raw_currency = getattr(invoice, 'selected_currency', None) or ''
self.selected_currency = self._normalize_currency(raw_currency)
self.selected_duration = f"Plan {plan_key}" if plan_key else "Plan"
self._populate_ticket_fields(invoice)
if self.ticket_poll_timer.isActive():
self.ticket_poll_timer.stop()
self.ticket_poll_timer.start(3000)
self.update_status.update_status('Awaiting ticket payment...')
def _populate_ticket_fields(self, invoice):
self.text_fields[0].setText(str(getattr(invoice, 'temp_billing_code', '') or ''))
self.text_fields[1].setText(self.selected_duration)
self.text_fields[1].setStyleSheet('font-size: 13px;')
amount = getattr(invoice, 'due_amount', '') or ''
self.text_fields[2].setText(str(amount))
addr = str(getattr(invoice, 'address', '') or '')
self.full_address = addr
metrics = self.text_fields[3].fontMetrics()
width = self.text_fields[3].width()
elided = metrics.elidedText(addr, QtCore.Qt.TextElideMode.ElideMiddle, width)
self.text_fields[3].setText(elided)
currency_index_map = {'monero': 0, 'bitcoin': 1, 'lightning': 2, 'litecoin': 3}
idx = currency_index_map.get(self.selected_currency, -1)
if idx >= 0:
for i, btn in enumerate(self.currency_display_buttons):
btn.setChecked(i == idx)
btn.setEnabled(i == idx)
self.qr_code_button.setDisabled(False)
def _normalize_currency(self, raw):
if not raw:
return None
raw = str(raw).lower()
m = {
'monero': 'monero', 'xmr': 'monero',
'bitcoin': 'bitcoin', 'btc': 'bitcoin',
'lightning': 'lightning', 'btc-ln': 'lightning', 'ln': 'lightning',
'litecoin': 'litecoin', 'ltc': 'litecoin',
}
return m.get(raw, raw)
def _tick_check_paid(self):
if not self.temp_billing_code or self._ticket_check_running:
return
self._ticket_check_running = True
self._ticket_worker = TicketingWorkerThread('CHECK_PAID', params={
'temp_billing_code': self.temp_billing_code
})
self._ticket_worker.paid.connect(self._on_ticket_paid)
self._ticket_worker.not_paid.connect(self._on_ticket_not_paid)
self._ticket_worker.paid_check_failed.connect(self._on_ticket_check_failed)
self._ticket_worker.error.connect(self._on_ticket_check_failed)
self._ticket_worker.finished.connect(self._on_ticket_check_done)
self._ticket_worker.start()
def _on_ticket_check_done(self):
self._ticket_check_running = False
def _on_ticket_paid(self):
self.ticket_poll_timer.stop()
self.ticketing = False
self.update_status.update_status('Payment received.')
self.custom_window.navigator.navigate("ticket_prep")
prep_page = self.custom_window.navigator.get_cached("ticket_prep")
if prep_page is not None:
prep_page.start_prep()
def _on_ticket_not_paid(self):
self.update_status.update_status('Awaiting ticket payment...')
def _on_ticket_check_failed(self, msg):
self.update_status.update_status(f'Payment check failed: {msg}')
def show_qr_code(self):
full_amount = self.text_fields[2].text()
if hasattr(self, 'full_address') and self.full_address:
currency_type = getattr(self, 'selected_currency', None)
qr_dialog = QRCodeDialog(
self.full_address, full_amount, currency_type, self)
qr_dialog.exec()
else:
self.update_status.update_status(
'No address available for QR code')

View file

@ -0,0 +1,101 @@
from PyQt6.QtWidgets import QLabel, QListWidget, QListWidgetItem
from PyQt6 import QtCore
from gui.v2.ui.pages.Page import Page
from gui.v2.ui.styles.styles import TERMINAL_LIST_QSS
from gui.v2.workers.ticketing_worker_thread import TicketingWorkerThread
class PlanPickerPage(Page):
def __init__(self, page_stack, main_window=None, parent=None):
super().__init__("PickPlan", page_stack, main_window, parent)
self.update_status = main_window
self.pricing_data = None
self.worker = None
self.title.setText("Pick a Plan")
self.title.setGeometry(QtCore.QRect(290, 30, 220, 40))
self.title.setAlignment(QtCore.Qt.AlignmentFlag.AlignCenter)
self.button_reverse.setVisible(True)
self.button_reverse.clicked.connect(self.reverse)
self.intro_label = QLabel(
"All members of the group expire at the same time. How much you pay depends on when you join.",
self)
self.intro_label.setGeometry(40, 90, 720, 40)
self.intro_label.setStyleSheet("color: white; font-size: 13px;")
self.intro_label.setWordWrap(True)
self.intro_label.setAlignment(QtCore.Qt.AlignmentFlag.AlignCenter)
self.plan_list = QListWidget(self)
self.plan_list.setGeometry(60, 140, 680, 320)
self.plan_list.setStyleSheet(TERMINAL_LIST_QSS)
self.plan_list.itemClicked.connect(self.on_plan_clicked)
self.status_label = QLabel("", self)
self.status_label.setGeometry(60, 470, 680, 30)
self.status_label.setStyleSheet("color: #cccccc; font-size: 13px;")
self.status_label.setAlignment(QtCore.Qt.AlignmentFlag.AlignCenter)
def start_sync(self):
self.plan_list.clear()
self.status_label.setText("Syncing prices...")
self.update_status.update_status("Syncing ticket prices...")
self.worker = TicketingWorkerThread('SYNC_PRICES')
self.worker.sync_done.connect(self.on_sync_done)
self.worker.error.connect(self.on_error)
self.worker.start()
def on_sync_done(self, pricing_data):
if isinstance(pricing_data, dict) and pricing_data.get('valid') is False:
err = pricing_data.get('error_code', 'unknown')
self.status_label.setText(f"Sync failed: {err}")
self.update_status.update_status(f"Sync failed: {err}")
return
if isinstance(pricing_data, dict) and isinstance(pricing_data.get('data'), dict):
plans = pricing_data['data']
elif isinstance(pricing_data, dict):
plans = pricing_data
else:
self.status_label.setText("Unexpected pricing data.")
return
self.pricing_data = plans
self.populate_plans(plans)
self.status_label.setText("Pick a plan above.")
self.update_status.update_status("Plans loaded.")
def populate_plans(self, pricing_data):
self.plan_list.clear()
if not isinstance(pricing_data, dict):
self.status_label.setText("Unexpected pricing data.")
return
for plan_key, plan_data in pricing_data.items():
if not isinstance(plan_data, dict):
continue
cost = plan_data.get('cost', '?')
months = plan_data.get('months', '?')
days = plan_data.get('days', '?')
profiles = plan_data.get('profiles', '?')
label = f" Plan {plan_key} | €{cost} | {months}months {days}d left | {profiles} profiles"
item = QListWidgetItem(label)
item.setData(QtCore.Qt.ItemDataRole.UserRole, plan_key)
self.plan_list.addItem(item)
def on_plan_clicked(self, item):
plan_key = item.data(QtCore.Qt.ItemDataRole.UserRole)
if not plan_key:
return
self.custom_window.navigator.navigate("ticket_crypto_picker")
crypto_page = self.custom_window.navigator.get_cached("ticket_crypto_picker")
if crypto_page is not None:
crypto_page.set_selected_plan(plan_key)
def on_error(self, msg):
self.status_label.setText(f"Error: {msg}")
self.update_status.update_status(f"Sync error: {msg}")
def reverse(self):
self.custom_window.navigator.navigate("id")

View file

@ -0,0 +1,136 @@
from PyQt6.QtWidgets import QLabel, QPushButton, QTextEdit
from core.controllers.PolicyController import PolicyController
from core.Errors import (
CommandNotFoundError,
PolicyAssignmentError,
PolicyInstatementError,
)
from gui.v2.ui.pages.Page import Page
class PolicySuggestionPage(Page):
def __init__(self, page_stack, main_window=None, parent=None, policy_type='capability'):
super().__init__("PolicySuggestion", page_stack, main_window, parent)
self.btn_path = main_window.btn_path
self.update_status = main_window
self.policy_type = policy_type
self.policy = PolicyController.get(policy_type)
self.button_back.setVisible(False)
self.button_next.setVisible(False)
self.button_go.setVisible(False)
self.status_label = QLabel(self)
self.status_label.setGeometry(80, 430, 640, 40)
self.status_label.setStyleSheet("font-size: 14px; color: cyan;")
self.setup_ui()
def setup_ui(self):
policy_name = "Capability" if self.policy_type == 'capability' else "Privilege"
self.title.setGeometry(20, 50, 760, 40)
self.title.setText(f"Policy Suggestion: {policy_name} Policy")
description = QLabel(self)
description.setGeometry(80, 100, 640, 60)
description.setWordWrap(True)
description.setStyleSheet("font-size: 14px; color: cyan;")
description.setText(
f"A {policy_name.lower()} policy is available and can be instated to improve functionality. Review the policy details below before proceeding.")
preview_label = QLabel(self)
preview_label.setGeometry(80, 170, 640, 30)
preview_label.setStyleSheet(
"font-size: 13px; color: white; font-weight: bold;")
preview_label.setText("Policy Preview:")
preview_text = QTextEdit(self)
preview_text.setGeometry(80, 200, 640, 150)
preview_text.setReadOnly(True)
preview_text.setStyleSheet("""
QTextEdit {
background-color: #1a1a1a;
color: #00ffff;
border: 1px solid #333;
border-radius: 4px;
font-family: monospace;
font-size: 11px;
padding: 5px;
}
""")
try:
preview_content = PolicyController.preview(self.policy)
preview_text.setText(preview_content)
except Exception as e:
preview_text.setText(f"Error loading preview: {str(e)}")
yes_button = QPushButton(f"Instate {policy_name} Policy", self)
yes_button.setGeometry(170, 370, 250, 50)
yes_button.setStyleSheet("""
QPushButton {
background: #007AFF;
color: white;
border: none;
border-radius: 4px;
font-size: 13px;
font-weight: bold;
}
QPushButton:hover {
background: #0056CC;
}
""")
yes_button.clicked.connect(self.instate_policy)
no_button = QPushButton("Skip", self)
no_button.setGeometry(440, 370, 120, 50)
no_button.setStyleSheet("""
QPushButton {
background: #666;
color: white;
border: none;
border-radius: 4px;
font-size: 13px;
font-weight: bold;
}
QPushButton:hover {
background: #555;
}
""")
no_button.clicked.connect(self.skip_policy)
self.refresh_status()
def refresh_status(self):
try:
if PolicyController.is_instated(self.policy):
self.status_label.setText(
f"Current status: {self.policy_type} policy is instated.")
self.status_label.setStyleSheet(
"font-size: 14px; color: #2ecc71;")
else:
self.status_label.setText(
f"Current status: {self.policy_type} policy is not instated.")
self.status_label.setStyleSheet(
"font-size: 14px; color: #e67e22;")
except Exception:
self.status_label.setText("Unable to determine policy status.")
self.status_label.setStyleSheet("font-size: 14px; color: #e67e22;")
def instate_policy(self):
try:
PolicyController.instate(self.policy)
self.refresh_status()
policy_name = "Capability" if self.policy_type == 'capability' else "Privilege"
self.update_status.update_status(f"{policy_name} policy instated")
self.custom_window.navigator.navigate("menu")
except CommandNotFoundError as e:
self.status_label.setText(str(e))
self.status_label.setStyleSheet("font-size: 14px; color: red;")
except (PolicyAssignmentError, PolicyInstatementError) as e:
self.status_label.setText(str(e))
self.status_label.setStyleSheet("font-size: 14px; color: red;")
except Exception as e:
self.status_label.setText(f"Failed to instate policy: {str(e)}")
self.status_label.setStyleSheet("font-size: 14px; color: red;")
def skip_policy(self):
self.custom_window.navigator.navigate("menu")

View file

@ -0,0 +1,87 @@
import os
from PyQt6.QtWidgets import QPushButton, QLabel, QButtonGroup
from PyQt6.QtGui import QPixmap, QIcon
from PyQt6.QtCore import Qt
from PyQt6 import QtCore
from gui.v2.ui.pages.Page import Page
class ProtocolPage(Page):
def __init__(self, page_stack, main_window=None, parent=None):
super().__init__("Protocol", page_stack, main_window, parent)
self.main_window = main_window
self.selected_protocol = None
self.btn_path = main_window.btn_path
self.selected_protocol_icon = None
self.connection_manager = main_window.connection_manager
self.button_back.setVisible(True)
self.update_status = main_window
self.button_go.clicked.connect(self.go_selected)
self.coming_soon_label = QLabel("Coming soon", self)
self.coming_soon_label.setGeometry(210, 50, 200, 40)
self.coming_soon_label.setStyleSheet("font-size: 22px;")
self.coming_soon_label.setVisible(False)
self.title.setGeometry(585, 40, 185, 40)
self.title.setText("Pick a Protocol")
self.display.setGeometry(QtCore.QRect(0, 50, 580, 435))
self.create_interface_elements()
if self.connection_manager.is_synced():
self.enable_protocol_buttons()
def create_interface_elements(self):
self.buttonGroup = QButtonGroup(self)
self.buttons = []
self.selected_page_name = None
for j, (object_type, icon_name, page_name, geometry) in enumerate([
(QPushButton, "wireguard", "wireguard", (585, 90, 185, 75)),
(QPushButton, "residential", "residential", (585, 90+30+75, 185, 75)),
(QPushButton, "hidetor", "hidetor", (585, 90+30+75+30+75, 185, 75))
]):
boton = object_type(self)
boton.setGeometry(*geometry)
boton.setIconSize(boton.size())
boton.setCheckable(True)
boton.setDisabled(True)
boton.setIcon(
QIcon(os.path.join(self.btn_path, f"{icon_name}_button.png")))
self.buttons.append(boton)
self.buttonGroup.addButton(boton, j)
boton.clicked.connect(
lambda _, name=page_name, protocol=icon_name: self.show_protocol(name, protocol))
def enable_protocol_buttons(self):
for button in self.buttons:
button.setDisabled(False)
def update_swarp_json(self):
self.update_status.write_data(
{"protocol": self.selected_protocol_icon})
def show_protocol(self, page_name, protocol):
self.update_status.clear_data()
self.display.setPixmap(QPixmap(os.path.join(self.btn_path, f"{protocol}.png")).scaled(
self.display.size(), Qt.AspectRatioMode.KeepAspectRatio))
self.selected_protocol_icon = protocol
self.selected_page_name = page_name
if protocol in ["wireguard", "hidetor"]:
self.button_go.setVisible(True)
self.coming_soon_label.setVisible(False)
else:
self.button_go.setVisible(False)
self.coming_soon_label.setVisible(True)
self.update_swarp_json()
def go_selected(self):
if self.selected_page_name:
self.custom_window.navigator.navigate(self.selected_page_name)
self.display.clear()
for boton in self.buttons:
boton.setChecked(False)
self.button_go.setVisible(False)
def find_menu_page(self):
return self.custom_window.navigator.get_cached("menu")

View file

@ -0,0 +1,101 @@
import os
from PyQt6.QtWidgets import QPushButton, QLabel, QButtonGroup
from PyQt6.QtGui import QPixmap, QIcon
from PyQt6 import QtCore
from gui.v2.ui.pages.Page import Page
class ResidentialPage(Page):
def __init__(self, page_stack, main_window, parent=None):
super().__init__("Wireguard", page_stack, main_window, parent)
self.title.setGeometry(585, 40, 185, 40)
self.title.setText("Pick a Protocol")
self.update_status = main_window
self.connection_choice = None
self.button_reverse.setVisible(True)
self.button_reverse.clicked.connect(self.reverse)
self.button_go.clicked.connect(self.go_selected)
self.display_1 = QLabel(self)
self.display_1.setGeometry(QtCore.QRect(
15, 50, 550, 465))
self.display_1.setPixmap(
QPixmap(os.path.join(self.btn_path, "browser only.png")))
self.label = QLabel(self)
self.label.setGeometry(440, 370, 86, 130)
self.label.setPixmap(
QPixmap(os.path.join(self.btn_path, "tor 86x130.png")))
self.label.hide()
def showEvent(self, event):
super().showEvent(event)
self.create_interface_elements()
def create_interface_elements(self):
self.buttonGroup = QButtonGroup(self)
self.buttons = []
for j, (object_type, icon_name, page_class, geometry) in enumerate([
(QPushButton, "tor", self.set_connection_choice(
'tor'), (585, 90, 185, 75)),
(QPushButton, "just proxy", self.set_connection_choice(
'just proxy'), (585, 90+30+75+30+75, 185, 75)),
(QLabel, None, None, (570, 170, 210, 105)),
(QLabel, None, None, (570, 385, 210, 115))
]):
if object_type == QPushButton:
boton = object_type(self)
boton.setGeometry(*geometry)
boton.setIconSize(boton.size())
boton.setCheckable(True)
boton.setIcon(
QIcon(os.path.join(self.btn_path, f"{icon_name}_button.png")))
self.buttons.append(boton)
self.buttonGroup.addButton(boton, j)
boton.show()
boton.clicked.connect(
lambda checked, page=page_class, name=icon_name: self.show_residential(name, page))
elif object_type == QLabel:
label = object_type(self)
label.setGeometry(*geometry)
label.setAlignment(QtCore.Qt.AlignmentFlag.AlignCenter)
label.setWordWrap(True)
label.show()
if geometry == (570, 170, 210, 105):
text1 = "Connect to tor first\nThen the residential proxy\nThis hides Tor from websites"
label.setText(text1)
elif geometry == (570, 385, 210, 115):
text2 = "Connect directly\nTo the Proxy\nin a browser\nThis has no encryption"
label.setText(text2)
def set_connection_choice(self, connection_choice):
pass
def show_residential(self, icon_name, page_class):
self.connection_choice = icon_name
self.selected_page_class = page_class
if icon_name == "tor":
self.label.show()
if icon_name == "just proxy":
self.label.hide()
self.button_go.setVisible(True)
def go_selected(self):
self.update_swarp_json(self.connection_choice)
self.custom_window.navigator.navigate("hidetor")
self.button_go.setVisible(False)
def update_swarp_json(self, icon_name):
inserted_data = {
"connection": icon_name
}
self.update_status.write_data(inserted_data)
def reverse(self):
self.custom_window.navigator.navigate("protocol")

463
gui/v2/ui/pages/resume_page.py Executable file
View file

@ -0,0 +1,463 @@
import os
from PyQt6.QtWidgets import (
QLabel, QPushButton, QButtonGroup, QLineEdit, QTextEdit, QFrame
)
from PyQt6.QtGui import QPixmap, QIcon
from PyQt6.QtCore import Qt, QSize
from PyQt6 import QtCore, QtGui
from core.controllers.ProfileController import ProfileController
from gui.v2.actions.profile_order import append_profile_to_visual_order
from gui.v2.ui.pages.Page import Page
from gui.v2.ui.pages.location_page import LocationPage
from gui.v2.ui.pages.screen_page import ScreenPage
class ResumePage(Page):
def __init__(self, page_stack, main_window=None, parent=None):
super().__init__("Resume", page_stack, main_window, parent)
self.update_status = main_window
self.connection_manager = main_window.connection_manager
self.btn_path = main_window.btn_path
self.labels_creados = []
self.additional_labels = []
self.button_go.clicked.connect(self.copy_profile)
self.button_back.setVisible(True)
self.title.setGeometry(585, 40, 185, 40)
self.title.setText("Profile Summary")
self.display.setGeometry(QtCore.QRect(5, 50, 580, 435))
self.buttonGroup = QButtonGroup(self)
self.button_back.clicked.connect(self.reverse)
self.create_arrow()
self.create_interface_elements()
def reverse(self):
if self.connection_type == "system-wide":
self.custom_window.navigator.navigate("location")
else:
self.custom_window.navigator.navigate("screen")
def show_location_verification(self, location_icon_name):
from gui.v2.ui.pages.location_verification_page import LocationVerificationPage
verification_page = LocationVerificationPage(
self.page_stack, self.update_status, location_icon_name, self)
self.page_stack.addWidget(verification_page)
self.page_stack.setCurrentIndex(
self.page_stack.indexOf(verification_page))
def create_arrow(self):
self.arrow_label = QLabel(self)
self.arrow_label.setGeometry(400, 115, 200, 200)
arrow_pixmap = QPixmap(os.path.join(self.btn_path, "arrow.png"))
self.arrow_label.setPixmap(arrow_pixmap)
self.arrow_label.setScaledContents(True)
self.arrow_label.raise_()
def showEvent(self, event):
super().showEvent(event)
self.arrow_label.show()
self.arrow_label.raise_()
def create_interface_elements(self):
for j, (object_type, icon_name, geometry) in enumerate([
(QLineEdit, None, (130, 73, 300, 24)),
(QLabel, None, (0, 0, 0, 0))
]):
if object_type == QLabel:
label = object_type(self)
label.setGeometry(*geometry)
icon_path = os.path.join(
self.btn_path, f"{icon_name}_button.png")
if os.path.exists(icon_path):
label.setPixmap(QPixmap(icon_path))
locations = self.connection_manager.get_location_info(
icon_name)
provider = locations.operator.name if locations and hasattr(
locations, 'operator') else None
if label.pixmap().isNull():
if locations and hasattr(locations, 'country_name'):
location_name = locations.country_name
label.setPixmap(LocationPage.create_location_button_image(
location_name, os.path.join(self.btn_path, "default_location_button.png"), provider))
else:
label.setPixmap(LocationPage.create_location_button_image(
'', os.path.join(self.btn_path, "default_location_button.png"), provider))
else:
if locations and hasattr(locations, 'country_name'):
label.setPixmap(LocationPage.create_location_button_image(f'{locations.country_code}_{locations.code}', os.path.join(
self.btn_path, "default_location_button.png"), provider, icon_path))
else:
label.setPixmap(LocationPage.create_location_button_image('', os.path.join(
self.btn_path, "default_location_button.png"), provider, icon_path))
elif object_type == QLineEdit:
self.line_edit = object_type(self)
self.line_edit.setGeometry(*geometry)
self.line_edit.setMaxLength(13)
self.line_edit.textChanged.connect(
self.toggle_button_visibility)
self.line_edit.setStyleSheet(
"background-color: transparent; border: none; color: cyan;")
self.line_edit.setPlaceholderText("Enter profile name")
self.line_edit.setAlignment(Qt.AlignmentFlag.AlignCenter)
self.create()
def create(self):
for label in self.additional_labels:
label.deleteLater()
self.additional_labels.clear()
profile_1 = self.update_status.read_data()
self.connection_type = profile_1.get("connection", "")
connection_exists = 'connection' in profile_1
if connection_exists and profile_1['connection'] not in ["tor", "just proxy"]:
items = ["protocol", "connection",
"location", "browser", "dimentions"]
initial_y = 90
label_height = 80
elif connection_exists:
items = ["protocol", "connection",
"location", "browser", "dimentions"]
initial_y = 90
label_height = 80
else:
items = ["protocol", "location", "browser", "dimentions"]
initial_y = 90
label_height = 105
for i, item in enumerate(items):
text = profile_1.get(item, "")
if text:
if item == 'browser':
from gui.v2.ui.pages.browser_page import BrowserPage
base_image = BrowserPage.create_browser_button_image(
text, self.btn_path)
geometry = (585, initial_y + i * label_height, 185, 75)
parent_label = QLabel(self)
parent_label.setGeometry(*geometry)
parent_label.setPixmap(base_image)
if parent_label.pixmap().isNull():
fallback_path = os.path.join(
self.btn_path, "default_browser_button.png")
base_image = BrowserPage.create_browser_button_image(
text, fallback_path, True)
parent_label.setPixmap(base_image)
parent_label.show()
self.labels_creados.append(parent_label)
elif item == 'location':
icon_path = os.path.join(
self.btn_path, f"button_{text}.png")
geometry = (585, initial_y + i * label_height, 185, 75)
parent_label = QPushButton(self)
parent_label.setGeometry(*geometry)
parent_label.setFlat(True)
parent_label.setStyleSheet(
"background: transparent; border: none;")
location_pixmap = QPixmap(icon_path)
locations = self.connection_manager.get_location_info(text)
provider = locations.operator.name if locations and hasattr(
locations, 'operator') else None
fallback_path = os.path.join(
self.btn_path, "default_location_button.png")
if location_pixmap.isNull():
if locations and hasattr(locations, 'country_name'):
location_name = locations.country_name
base_image = LocationPage.create_location_button_image(
location_name, fallback_path, provider)
parent_label.setIcon(QIcon(base_image))
else:
base_image = LocationPage.create_location_button_image(
'', fallback_path, provider)
parent_label.setIcon(QIcon(base_image))
else:
if locations and hasattr(locations, 'country_name'):
base_image = LocationPage.create_location_button_image(
f'{locations.country_code}_{locations.code}', fallback_path, provider, icon_path)
parent_label.setIcon(QIcon(base_image))
else:
base_image = LocationPage.create_location_button_image(
'', fallback_path, provider, icon_path)
parent_label.setIcon(QIcon(base_image))
parent_label.setIconSize(QSize(185, 75))
parent_label.location_icon_name = text
parent_label.setCursor(
QtCore.Qt.CursorShape.PointingHandCursor)
parent_label.clicked.connect(
lambda checked, loc=text: self.show_location_verification(loc))
parent_label.show()
self.labels_creados.append(parent_label)
elif item == 'dimentions':
base_image = ScreenPage.create_resolution_button_image(
self, text)
geometry = (585, initial_y + i * label_height, 185, 75)
parent_label = QLabel(self)
parent_label.setGeometry(*geometry)
parent_label.setPixmap(base_image)
parent_label.show()
self.labels_creados.append(parent_label)
else:
icon_path = os.path.join(
self.btn_path, f"{text}_button.png")
geometry = (585, initial_y + i * label_height, 185, 75)
parent_label = QLabel(self)
parent_label.setGeometry(*geometry)
parent_label.setPixmap(QPixmap(icon_path))
parent_label.show()
self.labels_creados.append(parent_label)
location_text = profile_1.get("location", "")
location_info = self.connection_manager.get_location_info(
location_text)
operator_name = ""
if location_info and hasattr(location_info, 'operator') and location_info.operator:
operator_name = location_info.operator.name or ""
if operator_name != 'Simplified Privacy' and operator_name != "":
text_color = "white"
if self.connection_type != "system-wide":
text_color = "black"
image_name = "browser only.png"
image_path = os.path.join(self.btn_path, image_name)
label_background = QLabel(self)
label_background.setGeometry(10, 50, 535, 460)
label_background.setPixmap(QPixmap(image_path))
label_background.setScaledContents(True)
label_background.show()
label_background.lower()
self.labels_creados.append(label_background)
l_img = QLabel(self)
l_img.setGeometry(30, 135, 150, 150)
pixmap_loc = QPixmap(os.path.join(
self.btn_path, f"icon_{location_text}.png"))
l_img.setPixmap(pixmap_loc)
l_img.setScaledContents(True)
l_img.show()
self.labels_creados.append(l_img)
l_name = f"{location_info.country_name}, {location_info.name}" if location_info and hasattr(
location_info, 'country_name') else ""
o_name = operator_name
n_key = location_info.operator.nostr_public_key if location_info and hasattr(
location_info, 'operator') and location_info.operator else ""
info_txt = QTextEdit(self)
info_txt.setGeometry(190, 130, 310, 250)
info_txt.setReadOnly(True)
info_txt.setFrameStyle(QFrame.Shape.NoFrame)
info_txt.setStyleSheet(
f"background: transparent; border: none; color: {text_color}; font-size: 19px;")
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.labels_creados.append(info_txt)
nostr_txt = QTextEdit(self)
nostr_txt.setGeometry(30, 310, 480, 170)
nostr_txt.setReadOnly(True)
nostr_txt.setFrameStyle(QFrame.Shape.NoFrame)
nostr_txt.setStyleSheet(
f"background: transparent; border: none; color: {text_color}; font-size: 21px;")
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.labels_creados.append(nostr_txt)
elif connection_exists:
if profile_1.get("connection", "") == "system-wide":
image_path = os.path.join(
self.btn_path, f"wireguard_{profile_1.get('location', '')}.png")
main_label = QLabel(self)
main_label.setGeometry(10, 130, 500, 375)
main_label.setPixmap(QPixmap(image_path))
main_label.setScaledContents(True)
main_label.show()
self.labels_creados.append(main_label)
if profile_1.get("connection", "") == "just proxy":
image_path = os.path.join(
self.btn_path, f"icon_{profile_1.get('location', '')}.png")
main_label = QLabel(self)
main_label.setGeometry(10, 105, 530, 398)
main_label.setPixmap(QPixmap(image_path))
main_label.setScaledContents(True)
main_label.show()
self.labels_creados.append(main_label)
if profile_1.get("connection", "") == "tor":
image_path = os.path.join(
self.btn_path, f"hdtor_{profile_1.get('location', '')}.png")
main_label = QLabel(self)
main_label.setGeometry(10, 105, 530, 398)
main_label.setPixmap(QPixmap(image_path))
main_label.setScaledContents(True)
main_label.show()
self.labels_creados.append(main_label)
if profile_1.get("connection", "") == "browser-only":
image_name = "browser only.png"
image_path = os.path.join(self.btn_path, image_name)
label_background = QLabel(self)
label_background.setGeometry(10, 50, 535, 460)
label_background.setPixmap(QPixmap(image_path))
label_background.setScaledContents(True)
label_background.show()
label_background.lower()
self.labels_creados.append(label_background)
image_path = os.path.join(
self.btn_path, f"wireguard_{profile_1.get('location', '')}.png")
main_label = QLabel(self)
main_label.setGeometry(10, 130, 500, 375)
main_label.setPixmap(QPixmap(image_path))
main_label.setScaledContents(True)
main_label.show()
self.labels_creados.append(main_label)
else:
image_path = os.path.join(
self.btn_path, f"icon_{profile_1.get('location', '')}.png")
main_label = QLabel(self)
main_label.setGeometry(10, 130, 500, 375)
main_label.setPixmap(QPixmap(image_path))
main_label.setScaledContents(True)
main_label.show()
self.labels_creados.append(main_label)
if hasattr(self, 'arrow_label'):
self.arrow_label.raise_()
def toggle_button_visibility(self):
self.button_go.setVisible(bool(self.line_edit.text()))
def find_menu_page(self):
return self.custom_window.navigator.get_cached("menu")
def copy_profile(self):
profile_name = self.line_edit.text()
menu_page = self.find_menu_page()
if menu_page:
number_of_profiles = menu_page.number_of_profiles
profile_data = self.update_status.read_data()
required_fields = [profile_data.get("protocol"), profile_name]
if not all(required_fields):
print("Error: Some required fields are empty!")
return
profile_data["name"] = profile_name
profile_data["visible"] = "yes"
profiles = ProfileController.get_all()
profile_id = self.get_next_available_id(profiles)
new_profile = profile_data
self.create_core_profiles(new_profile, profile_id)
if ProfileController.get(profile_id) is not None:
append_profile_to_visual_order(
getattr(self.update_status, 'gui_config_file', None),
profile_id,
profiles.keys())
main = self.update_status
if hasattr(main, 'navigate_after_profile_created'):
main.navigate_after_profile_created()
else:
self.custom_window.navigator.navigate("menu")
self.update_status.clear_data()
self.line_edit.clear()
self.display.clear()
self.button_go.setVisible(False)
def get_next_available_id(self, profiles: dict) -> int:
if not profiles:
return 1
existing_ids = sorted(profiles.keys())
for i in range(1, max(existing_ids) + 2):
if i not in existing_ids:
return i
return None
def create_core_profiles(self, profile, id):
if profile.get('connection') != 'system-wide':
browser_info = profile.get('browser', '').split()
if len(browser_info) < 2:
self.update_status.update_status(
'Application version not supported')
return
application = f"{browser_info[0].lower()}:{browser_info[1]}"
else:
application = ''
parts = profile.get('location').split('_')
country_code = parts[0]
location_code = parts[1]
if profile.get('protocol') == 'wireguard':
connection_type = 'wireguard'
elif profile.get('protocol') == 'hidetor' or profile.get('protocol') == 'residential':
if profile.get('connection') == 'tor':
connection_type = 'tor'
elif profile.get('connection') == 'just proxy':
connection_type = 'system'
else:
self.update_status.update_status('Connection type not supported')
return
profile_data = {
'id': int(id),
'name': profile.get('name'),
'country_code': country_code,
'code': location_code,
'application': application,
'connection_type': connection_type,
'resolution': profile.get('dimentions', ''),
}
profile_type = 'system' if profile.get(
'connection') == 'system-wide' else 'session'
action = f'CREATE_{profile_type.upper()}_PROFILE'
self.handle_core_action_create_profile(
action, profile_data, profile_type)
def eliminacion(self):
for label in self.labels_creados:
label.deleteLater()
self.labels_creados = []
self.create()
if hasattr(self, 'arrow_label'):
self.arrow_label.show()
self.arrow_label.raise_()
def handle_core_action_create_profile(self, action, profile_data=None, profile_type=None):
from gui.v2.workers.worker_thread import WorkerThread
self.worker_thread = WorkerThread(action, profile_data, profile_type)
self.worker_thread.text_output.connect(self.update_output)
self.worker_thread.start()
self.worker_thread.wait()
def update_output(self, text):
self.update_status.update_status(text)
def on_profile_creation(self, result):
if self.worker_thread:
self.worker_thread.quit()
self.worker_thread.wait()
self.worker_thread = None

347
gui/v2/ui/pages/screen_page.py Executable file
View file

@ -0,0 +1,347 @@
import os
from PyQt6.QtWidgets import (
QPushButton, QLabel, QButtonGroup, QDialog, QLineEdit,
QVBoxLayout, QHBoxLayout, QMessageBox
)
from PyQt6.QtGui import QPixmap, QIcon, QPainter, QColor, QFont
from PyQt6.QtCore import Qt, QRect
from PyQt6 import QtCore
from gui.v2.ui.pages.Page import Page
class ScreenPage(Page):
def __init__(self, page_stack, main_window, parent=None):
super().__init__("Screen", page_stack, main_window, parent)
self.selected_dimentions = None
self.update_status = main_window
self.selected_dimentions_icon = None
self.button_back.setVisible(True)
self.title.setGeometry(585, 40, 200, 40)
self.title.setText("Pick a Resolution")
self.host_screen_info = QLabel(
f"Host: {self.custom_window.host_screen_width}x{self.custom_window.host_screen_height}", self)
self.host_screen_info.setGeometry(QtCore.QRect(355, 30, 200, 30))
self.host_screen_info.setAlignment(QtCore.Qt.AlignmentFlag.AlignCenter)
self.host_screen_info.setStyleSheet(
"color: #00ffff; font-size: 14px; font-weight: bold;")
self.host_screen_info.show()
self.display.setGeometry(QtCore.QRect(5, 50, 580, 435))
self.showing_larger_resolutions = False
self.larger_resolution_buttons = []
self.create_interface_elements()
def create_interface_elements(self):
self.buttonGroup = QButtonGroup(self)
self.buttons = []
resolutions = [
("800x600", (595, 90, 180, 70)),
("1024x760", (595, 170, 180, 70)),
("1152x1080", (595, 250, 180, 70)),
("1280x1024", (595, 330, 180, 70)),
("1920x1080", (595, 410, 180, 70))
]
valid_resolutions = []
for res, geom in resolutions:
w, h = map(int, res.split('x'))
if w < self.custom_window.host_screen_width and h < self.custom_window.host_screen_height:
valid_resolutions.append((QPushButton, res, geom))
for j, (object_type, icon_name, geometry) in enumerate(valid_resolutions):
boton = object_type(self)
boton.setGeometry(*geometry)
boton.setIconSize(boton.size())
boton.setCheckable(True)
boton.setIcon(
QIcon(self.create_resolution_button_image(icon_name)))
self.buttons.append(boton)
self.buttonGroup.addButton(boton, j)
boton.clicked.connect(
lambda _, dimentions=icon_name: self.show_dimentions(dimentions))
self.larger_resolutions_button = QPushButton(self)
self.larger_resolutions_button.setGeometry(100, 450, 185, 50)
self.larger_resolutions_button.setIconSize(
self.larger_resolutions_button.size())
self.larger_resolutions_button.setIcon(
QIcon(self.create_resolution_button_image("Larger Resolutions")))
self.larger_resolutions_button.clicked.connect(
self.show_larger_resolutions)
self.custom_resolution_button = QPushButton(self)
self.custom_resolution_button.setGeometry(300, 450, 195, 50)
self.custom_resolution_button.setIconSize(
self.custom_resolution_button.size())
self.custom_resolution_button.setIcon(
QIcon(self.create_resolution_button_image("Custom Resolution")))
self.custom_resolution_button.clicked.connect(
self.show_custom_resolution_dialog)
def create_resolution_button_image(self, resolution_text):
template_path = os.path.join(
self.btn_path, "resolution_template_button.png")
if os.path.exists(template_path):
base_image = QPixmap(template_path)
if 'Resolution' in resolution_text:
base_image = base_image.scaled(195, 50)
font_size = 10
else:
base_image = base_image.scaled(187, 75)
font_size = 13
else:
base_image = QPixmap(185, 75)
base_image.fill(QColor(44, 62, 80))
font_size = 13
painter = QPainter(base_image)
painter.setRenderHint(QPainter.RenderHint.Antialiasing)
font = QFont()
font.setPointSize(font_size)
font.setBold(True)
painter.setFont(font)
painter.setPen(QColor(0, 255, 255))
text_rect = base_image.rect()
painter.drawText(text_rect, Qt.AlignmentFlag.AlignCenter |
Qt.AlignmentFlag.AlignVCenter, resolution_text)
painter.end()
return base_image
def show_larger_resolutions(self):
if self.showing_larger_resolutions:
self.hide_larger_resolutions()
else:
self.hide_standard_resolutions()
self.showing_larger_resolutions = True
self.larger_resolutions_button.setIcon(
QIcon(self.create_resolution_button_image("Standard Resolutions")))
larger_resolutions_list = [
("2560x1600", (595, 90, 180, 70)),
("2560x1440", (595, 170, 180, 70)),
("1920x1440", (595, 250, 180, 70)),
("1792x1344", (595, 330, 180, 70)),
("2048x1152", (595, 410, 180, 70))
]
valid_larger = list(larger_resolutions_list)
for i, (resolution, geometry) in enumerate(valid_larger):
button = QPushButton(self)
button.setGeometry(*geometry)
button.setIconSize(button.size())
button.setCheckable(True)
button.setVisible(True)
button.setIcon(
QIcon(self.create_resolution_button_image(resolution)))
button.clicked.connect(
lambda _, res=resolution: self.show_dimentions(res))
self.larger_resolution_buttons.append(button)
self.buttonGroup.addButton(button, len(self.buttons) + i)
def hide_larger_resolutions(self):
for button in self.larger_resolution_buttons:
self.buttonGroup.removeButton(button)
button.deleteLater()
self.larger_resolution_buttons.clear()
self.showing_larger_resolutions = False
self.larger_resolutions_button.setIcon(
QIcon(self.create_resolution_button_image("Larger Resolutions")))
self.show_standard_resolutions()
def hide_standard_resolutions(self):
for button in self.buttons:
button.setVisible(False)
def show_standard_resolutions(self):
for button in self.buttons:
button.setVisible(True)
def show_custom_resolution_dialog(self):
dialog = QDialog(self)
dialog.setWindowTitle("Custom Resolution")
dialog.setFixedSize(300, 150)
dialog.setModal(True)
dialog.setStyleSheet("""
QDialog {
background-color: #2c3e50;
border: 2px solid #00ffff;
border-radius: 10px;
}
QLabel {
color: white;
font-size: 12px;
}
QLineEdit {
background-color: #34495e;
color: white;
border: 1px solid #00ffff;
border-radius: 5px;
padding: 5px;
font-size: 12px;
}
QPushButton {
background-color: #2c3e50;
color: white;
border: 2px solid #00ffff;
border-radius: 5px;
padding: 8px;
font-size: 12px;
}
QPushButton:hover {
background-color: #34495e;
}
""")
layout = QVBoxLayout()
input_layout = QHBoxLayout()
width_label = QLabel("Width:")
width_input = QLineEdit()
width_input.setPlaceholderText("1920")
height_label = QLabel("Height:")
height_input = QLineEdit()
height_input.setPlaceholderText("1080")
input_layout.addWidget(width_label)
input_layout.addWidget(width_input)
input_layout.addWidget(height_label)
input_layout.addWidget(height_input)
button_layout = QHBoxLayout()
ok_button = QPushButton("OK")
cancel_button = QPushButton("Cancel")
button_layout.addWidget(cancel_button)
button_layout.addWidget(ok_button)
layout.addLayout(input_layout)
layout.addLayout(button_layout)
dialog.setLayout(layout)
def validate_and_accept():
try:
width = int(width_input.text())
height = int(height_input.text())
if width <= 0 or height <= 0:
QMessageBox.warning(
dialog, "Invalid Resolution", "Width and height must be positive numbers.")
return
host_w = self.custom_window.host_screen_width
host_h = self.custom_window.host_screen_height
adjusted = False
if width > host_w:
width = host_w - 50
adjusted = True
if height > host_h:
height = host_h - 50
adjusted = True
if adjusted:
QMessageBox.information(
dialog, "Notice", "Adjusted to fit host size")
if width > 10000 or height > 10000:
QMessageBox.warning(
dialog, "Invalid Resolution", "Resolution too large. Maximum 10000x10000.")
return
custom_resolution = f"{width}x{height}"
self.show_dimentions(custom_resolution)
dialog.accept()
except ValueError:
QMessageBox.warning(
dialog, "Invalid Input", "Please enter valid numbers for width and height.")
ok_button.clicked.connect(validate_and_accept)
cancel_button.clicked.connect(dialog.reject)
width_input.returnPressed.connect(validate_and_accept)
height_input.returnPressed.connect(validate_and_accept)
dialog.exec()
def update_swarp_json(self):
inserted_data = {
"dimentions": self.selected_dimentions_icon
}
self.update_status.write_data(inserted_data)
def show_dimentions(self, dimentions):
try:
image_path = os.path.join(self.btn_path, f"{dimentions}.png")
if os.path.exists(image_path):
self.display.setPixmap(QPixmap(image_path).scaled(
self.display.size(), Qt.AspectRatioMode.KeepAspectRatio))
else:
self.create_resolution_preview_image(dimentions)
except:
self.display.clear()
self.selected_dimentions_icon = dimentions
self.button_next.setVisible(True)
self.update_swarp_json()
def create_resolution_preview_image(self, resolution_text):
template_path = os.path.join(self.btn_path, "resolution_template.png")
if os.path.exists(template_path):
base_image = QPixmap(template_path)
base_image = base_image.scaled(
580, 435, Qt.AspectRatioMode.KeepAspectRatio, Qt.TransformationMode.SmoothTransformation)
else:
base_image = QPixmap(580, 435)
base_image.fill(QColor(44, 62, 80))
painter = QPainter(base_image)
painter.setRenderHint(QPainter.RenderHint.Antialiasing)
try:
width, height = map(int, resolution_text.split('x'))
gcd = self._gcd(width, height)
aspect_ratio = f"{width//gcd}:{height//gcd}"
except:
aspect_ratio = ""
font = QFont()
font.setPointSize(24)
font.setBold(True)
painter.setFont(font)
painter.setPen(QColor(0, 255, 255))
text_rect = base_image.rect()
painter.drawText(
text_rect, Qt.AlignmentFlag.AlignCenter, resolution_text)
if aspect_ratio:
font.setPointSize(16)
painter.setFont(font)
painter.setPen(QColor(200, 200, 200))
aspect_rect = QRect(
text_rect.x(), text_rect.y() + 80, text_rect.width(), 40)
painter.drawText(
aspect_rect, Qt.AlignmentFlag.AlignCenter, f"({aspect_ratio})")
painter.end()
self.display.setPixmap(base_image)
def _gcd(self, a, b):
while b:
a, b = b, a % b
return a
def gestionar_next(self):
self.custom_window.navigator.navigate("resume")

2325
gui/v2/ui/pages/settings_page.py Executable file

File diff suppressed because it is too large Load diff

177
gui/v2/ui/pages/sync_screen.py Executable file
View file

@ -0,0 +1,177 @@
import os
from PyQt6.QtWidgets import QLabel, QPushButton
from PyQt6.QtGui import QPixmap
from core.controllers.ClientController import ClientController
from gui.v2.ui.pages.Page import Page
from gui.v2.workers.worker_thread import WorkerThread
class SyncScreen(Page):
def __init__(self, page_stack, main_window=None, parent=None):
super().__init__("SyncScreen", page_stack, main_window, parent)
self.main_window = main_window
self.btn_path = main_window.btn_path
self.update_status = main_window
self.connection_manager = main_window.connection_manager
self.is_tor_mode = main_window.is_tor_mode
self.heading_label = QLabel(
"You're about to fetch data from\nSimplified Privacy.", self)
self.heading_label.setGeometry(15, 80, 750, 120)
font_style = "font-size: 34px; font-weight: bold; color: white;"
if self.custom_window.open_sans_family:
font_style += f" font-family: '{self.custom_window.open_sans_family}';"
self.heading_label.setStyleSheet(font_style)
self.data_description = QLabel(
"This data includes what plans, countries,\nbrowsers, and version upgrades are\navailable. As well as coordinating billing.", self)
self.data_description.setGeometry(22, 190, 750, 120)
font_style = "font-size: 24px; font-weight: bold; color: #ffff00;"
if self.custom_window.open_sans_family:
font_style += f" font-family: '{self.custom_window.open_sans_family}';"
self.data_description.setStyleSheet(font_style)
self.instructions = QLabel(
"Use the toggle in the bottom right to\ndecide if you want to use Tor or not.\nThen hit \"Next\"", self)
self.instructions.setGeometry(22, 345, 750, 120)
font_style = "font-size: 28px; font-weight: bold; color: white;"
if self.custom_window.open_sans_family:
font_style += f" font-family: '{self.custom_window.open_sans_family}';"
self.instructions.setStyleSheet(font_style)
self.arrow_label = QLabel(self)
self.arrow_label.setGeometry(520, 400, 180, 130)
arrow_pixmap = QPixmap(os.path.join(self.btn_path, "arrow-down.png"))
self.arrow_label.setPixmap(arrow_pixmap)
self.arrow_label.raise_()
self.button_go.setVisible(True)
self.button_back.setVisible(True)
self.button_go.clicked.connect(self.perform_sync)
self.button_back.clicked.connect(self.go_back)
def go_back(self):
self.custom_window.navigator.navigate("menu")
def perform_sync(self):
self.button_go.setEnabled(False)
self.button_back.setEnabled(False)
self.update_status.update_status('Sync in progress...')
if self.main_window.get_current_connection() == 'tor':
self.worker_thread = WorkerThread('SYNC_TOR')
else:
self.worker_thread = WorkerThread('SYNC')
self.worker_thread.sync_output.connect(self.update_output)
self.worker_thread.start()
def update_output(self, available_locations, available_browsers, status, is_tor, locations, all_browsers):
if isinstance(all_browsers, bool) and not all_browsers:
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='tor', distro='debian', is_sync=True)
self.button_go.setEnabled(True)
self.button_back.setEnabled(True)
return
if status is False:
self.button_go.setEnabled(True)
self.button_back.setEnabled(True)
self.update_status.update_status('An error occurred during sync')
return
self.update_status.update_status('Sync complete')
update_available = ClientController.can_be_updated()
if update_available:
menu_page = self.find_menu_page()
if menu_page is not None:
menu_page.on_update_check_finished()
self.custom_window.update_values(
available_locations, available_browsers, status, is_tor, locations, all_browsers)
self.custom_window.navigator.navigate("protocol")
def update_after_sync(self, available_locations, available_browsers, locations, all_browsers=None):
self.connection_manager.set_synced(True)
available_locations_list = []
available_browsers_list = []
self.connection_manager.store_locations(locations)
self.connection_manager.store_browsers(available_browsers)
browser_positions = self.generate_grid_positions(
len(available_browsers))
location_positions = self.generate_grid_positions(
len(available_locations))
for i, location in enumerate(available_locations):
available_locations_list.append(
(QPushButton, location, location_positions[i]))
for i, browser in enumerate(available_browsers):
available_browsers_list.append(
(QPushButton, browser, browser_positions[i]))
location_page = self.find_location_page()
hidetor_page = self.find_hidetor_page()
protocol_page = self.find_protocol_page()
browser_page = self.find_browser_page()
if browser_page:
browser_page.create_interface_elements(available_browsers_list)
if location_page:
location_page.create_interface_elements(available_locations_list)
if hidetor_page:
hidetor_page.create_interface_elements(available_locations_list)
if protocol_page:
protocol_page.enable_protocol_buttons()
menu_page = self.find_menu_page()
if menu_page:
menu_page.refresh_profiles_data()
def generate_grid_positions(self, num_items):
positions = []
start_x = 395
start_y = 90
button_width = 185
button_height = 75
h_spacing = 10
v_spacing = 105
for i in range(num_items):
col = i % 2
row = i // 2
x = start_x + (col * (button_width + h_spacing))
y = start_y + (row * v_spacing)
positions.append((x, y, button_width, button_height))
return positions
def find_browser_page(self):
return self.custom_window.navigator.get_cached("browser")
def find_location_page(self):
return self.custom_window.navigator.get_cached("location")
def find_hidetor_page(self):
return self.custom_window.navigator.get_cached("hidetor")
def find_protocol_page(self):
return self.custom_window.navigator.get_cached("protocol")
def find_menu_page(self):
return self.custom_window.navigator.get_cached("menu")

View file

@ -0,0 +1,115 @@
import os
from PyQt6.QtWidgets import QLabel, QPushButton
from PyQt6.QtGui import QPixmap
from PyQt6.QtCore import Qt
from core.controllers.PolicyController import PolicyController
from core.Errors import (
CommandNotFoundError,
PolicyAssignmentError,
PolicyInstatementError,
)
from gui.v2.ui.pages.Page import Page
class SystemwidePromptPage(Page):
def __init__(self, page_stack, main_window=None, parent=None):
super().__init__("Systemwide", page_stack, main_window, parent)
self.btn_path = main_window.btn_path
self.update_status = main_window
self.button_back.setVisible(True)
self.button_back.clicked.connect(self.back_to_install)
self.button_next.setVisible(False)
self.button_go.setVisible(False)
self.status_label = QLabel(self)
self.status_label.setGeometry(80, 430, 640, 40)
self.status_label.setStyleSheet("font-size: 14px; color: cyan;")
self.setup_ui()
def setup_ui(self):
self.title.setGeometry(20, 50, 760, 40)
self.title.setText("Enable \"no sudo\" systemwide")
description = QLabel(self)
description.setGeometry(80, 100, 640, 120)
description.setWordWrap(True)
description.setStyleSheet("font-size: 14px; color: cyan;")
description.setText("If you're using Systemwide profiles, you may wish to enable them without having to enter the sudo password each time for convenience. This requires the sudo password to setup profiles and disable them (for security), but not to turn it on each time. You can choose to set this up now or later in the options menu.")
icon_label = QLabel(self)
icon_label.setGeometry(80, 300, 64, 64)
icon_pix = QPixmap(os.path.join(
self.btn_path, "wireguard_system_wide.png"))
icon_label.setPixmap(icon_pix.scaled(
64, 64, Qt.AspectRatioMode.KeepAspectRatio, Qt.TransformationMode.SmoothTransformation))
yes_button = QPushButton("Yes, enable system-wide profiles", self)
yes_button.setGeometry(170, 300, 300, 50)
yes_button.setStyleSheet("""
QPushButton {
background: #007AFF;
color: white;
border: none;
border-radius: 4px;
font-size: 13px;
font-weight: bold;
}
QPushButton:hover {
background: #0056CC;
}
""")
yes_button.clicked.connect(self.enable_systemwide)
no_button = QPushButton("Not now", self)
no_button.setGeometry(170, 370, 120, 40)
no_button.setStyleSheet("""
QPushButton {
background: #007AFF;
color: white;
border: none;
border-radius: 4px;
font-size: 13px;
font-weight: bold;
}
QPushButton:hover {
background: #0056CC;
}
""")
no_button.clicked.connect(self.skip_systemwide)
self.refresh_status()
def refresh_status(self):
privilege_policy = PolicyController.get('privilege')
if privilege_policy is not None and PolicyController.is_instated(privilege_policy):
self.status_label.setText(
"Current status: system-wide policy is enabled.")
self.status_label.setStyleSheet("font-size: 14px; color: #2ecc71;")
else:
self.status_label.setText(
"Current status: system-wide policy is disabled.")
self.status_label.setStyleSheet("font-size: 14px; color: #e67e22;")
def enable_systemwide(self):
try:
privilege_policy = PolicyController.get('privilege')
if privilege_policy is not None:
PolicyController.instate(privilege_policy)
self.update_status.mark_systemwide_prompt_shown()
self.refresh_status()
self.update_status.update_status("System-wide policy enabled")
self.custom_window.navigator.navigate("menu")
except CommandNotFoundError as e:
self.status_label.setText(str(e))
self.status_label.setStyleSheet("font-size: 14px; color: red;")
except (PolicyAssignmentError, PolicyInstatementError) as e:
self.status_label.setText(str(e))
self.status_label.setStyleSheet("font-size: 14px; color: red;")
except Exception:
self.status_label.setText("Failed to enable system-wide policy")
self.status_label.setStyleSheet("font-size: 14px; color: red;")
def skip_systemwide(self):
self.update_status.mark_systemwide_prompt_shown()
self.custom_window.navigator.navigate("menu")
def back_to_install(self):
self.custom_window.navigator.navigate("install_system_package")

View file

@ -0,0 +1,112 @@
import os
from PyQt6.QtWidgets import QButtonGroup, QMessageBox, QPushButton
from PyQt6.QtGui import QIcon
from PyQt6.QtCore import QSize
from PyQt6 import QtCore
from gui.v2.ui.pages.Page import Page
from gui.v2.workers.ticketing_worker_thread import TicketingWorkerThread
HOW_MANY_PROFILES_DEFAULT = 6
class TicketCryptoPickerPage(Page):
def __init__(self, page_stack, main_window=None, parent=None):
super().__init__("TicketCrypto", page_stack, main_window, parent)
self.update_status = main_window
self.selected_plan = None
self.bypass_existing = False
self.worker = None
self.title.setText("Payment Method")
self.title.setGeometry(QtCore.QRect(510, 30, 250, 40))
self.title.setAlignment(QtCore.Qt.AlignmentFlag.AlignCenter)
self.button_reverse.setVisible(True)
self.button_reverse.clicked.connect(self.reverse)
button_info = [
("monero", "monero", (545, 75)),
("bitcoin", "bitcoin", (545, 290)),
("lightning", "lightnering", (545, 180)),
("litecoin", "litecoin", (545, 395)),
]
self.buttonGroup = QButtonGroup(self)
self.buttons = []
for j, (currency, icon_name, position) in enumerate(button_info):
button = QPushButton(self)
button.setGeometry(position[0], position[1], 185, 75)
button.setIconSize(QSize(190, 120))
button.setCheckable(True)
button.setIcon(QIcon(os.path.join(self.btn_path, f"{icon_name}.png")))
button.setProperty('currency', currency)
self.buttons.append(button)
self.buttonGroup.addButton(button, j)
button.clicked.connect(self.on_currency_selected)
def set_selected_plan(self, plan_key):
self.selected_plan = plan_key
self.bypass_existing = False
self.update_status.update_status(f"Selected plan: {plan_key}")
for btn in self.buttons:
btn.setChecked(False)
def on_currency_selected(self):
selected_button = self.buttonGroup.checkedButton()
if not selected_button or not self.selected_plan:
return
currency = selected_button.property('currency')
self.start_initiate_payment(currency)
def start_initiate_payment(self, currency):
self.update_status.update_status("Initiating payment...")
self.worker = TicketingWorkerThread('INITIATE_PAYMENT', params={
'how_many_profiles': HOW_MANY_PROFILES_DEFAULT,
'which_key': self.selected_plan,
'which_cryptocurrency': currency,
'bypass_existing': self.bypass_existing,
})
self.worker.invoice_ready.connect(self.on_invoice_ready)
self.worker.error.connect(self.on_error)
self.worker.start()
def on_invoice_ready(self, invoice):
if invoice is None or invoice is False:
self.update_status.update_status("Could not initiate payment.")
return
error_code = getattr(invoice, 'error_code', None)
if error_code == 'already_exists' and not self.bypass_existing:
self._prompt_wipe_existing(invoice)
return
if error_code:
msg = getattr(invoice, 'final_error_msg', None) or error_code
self.update_status.update_status(f"Payment error: {msg}")
return
self.custom_window.navigator.navigate("payment_details")
payment_page = self.custom_window.navigator.get_cached("payment_details")
if payment_page is not None:
payment_page.set_ticket_invoice(invoice, self.selected_plan)
def _prompt_wipe_existing(self, invoice):
msg = QMessageBox(self)
msg.setWindowTitle("Existing tickets found")
msg.setText("You already have ticket data. Wipe it and start over?")
msg.setStandardButtons(QMessageBox.StandardButton.Yes | QMessageBox.StandardButton.No)
result = msg.exec()
if result == QMessageBox.StandardButton.Yes:
self.bypass_existing = True
currency_btn = self.buttonGroup.checkedButton()
if currency_btn:
self.start_initiate_payment(currency_btn.property('currency'))
else:
self.update_status.update_status("Cancelled.")
def on_error(self, msg):
self.update_status.update_status(f"Payment error: {msg}")
def reverse(self):
self.custom_window.navigator.navigate("plan_picker")

View file

@ -0,0 +1,117 @@
from PyQt6.QtWidgets import QLabel, QListWidget, QListWidgetItem, QPushButton
from PyQt6 import QtCore
from core.controllers.ProfileController import ProfileController
from core.controllers.tickets.UseTicketController import get_unused_tickets
from gui.v2.actions.locations import location_candidates
from gui.v2.infrastructure.setup_observers import ticket_observer
from gui.v2.ui.pages.Page import Page
class TicketOrBillingChoicePage(Page):
def __init__(self, page_stack, main_window=None, parent=None):
super().__init__("TicketOrBilling", page_stack, main_window, parent)
self.update_status = main_window
self.title.setText("Pick a Ticket or Use Billing")
self.title.setGeometry(QtCore.QRect(220, 30, 360, 40))
self.title.setAlignment(QtCore.Qt.AlignmentFlag.AlignCenter)
self.button_reverse.setVisible(True)
self.button_reverse.clicked.connect(self.reverse)
self.intro = QLabel(
"Random ticket use is OFF. Pick a ticket to use, or fall back to a billing code.",
self)
self.intro.setGeometry(40, 90, 720, 40)
self.intro.setStyleSheet("color: white; font-size: 13px;")
self.intro.setWordWrap(True)
self.intro.setAlignment(QtCore.Qt.AlignmentFlag.AlignCenter)
self.ticket_list = QListWidget(self)
self.ticket_list.setGeometry(60, 140, 380, 320)
self.ticket_list.setStyleSheet("""
QListWidget {
background-color: #000000; color: #00ffff;
border: 2px solid #00ffff; border-radius: 8px;
font-size: 14px;
}
QListWidget::item { padding: 10px; }
QListWidget::item:hover { background-color: #003333; }
QListWidget::item:selected { background-color: #006666; color: white; }
""")
self.use_ticket_button = QPushButton("Use Selected Ticket", self)
self.use_ticket_button.setGeometry(60, 470, 380, 40)
self.use_ticket_button.setStyleSheet("""
QPushButton {
background-color: #00aaff; color: white; border: none;
border-radius: 5px; font-size: 14px; font-weight: bold;
}
QPushButton:disabled { background-color: #555555; }
""")
self.use_ticket_button.clicked.connect(self.on_use_ticket)
self.use_billing_button = QPushButton("Use a Billing Code Instead", self)
self.use_billing_button.setGeometry(470, 200, 280, 60)
self.use_billing_button.setStyleSheet("""
QPushButton {
background-color: #000000; color: #00ffff;
border: 2px solid #00ffff; border-radius: 10px;
font-size: 14px; font-weight: bold;
}
QPushButton:hover { background-color: #003333; }
""")
self.use_billing_button.clicked.connect(self.on_use_billing)
self.status_label = QLabel("", self)
self.status_label.setGeometry(20, 520, 760, 20)
self.status_label.setStyleSheet("color: #cccccc; font-size: 12px;")
self.status_label.setAlignment(QtCore.Qt.AlignmentFlag.AlignCenter)
def populate(self):
self.ticket_list.clear()
try:
unused = get_unused_tickets(ticket_observer)
except Exception as e:
self.status_label.setText(f"Error reading tickets: {e}")
return
if unused.get('valid'):
for t in unused.get('data', []):
item = QListWidgetItem(f"Ticket #{t}")
item.setData(QtCore.Qt.ItemDataRole.UserRole, t)
self.ticket_list.addItem(item)
else:
self.status_label.setText(unused.get('message', 'No tickets available.'))
def on_use_ticket(self):
item = self.ticket_list.currentItem()
if not item:
self.status_label.setText("Pick a ticket from the list first.")
return
which_ticket = item.data(QtCore.Qt.ItemDataRole.UserRole)
profile_id = self.update_status.current_profile_id
try:
profile = ProfileController.get(int(profile_id))
except Exception:
profile = None
candidates = location_candidates(profile)
if not candidates:
self.status_label.setText("Could not determine profile location.")
return
profile_data = {
'id': int(profile_id),
'use_ticket': which_ticket,
'ticket_location': candidates[0],
}
menu_page = self.custom_window.navigator.get_cached("menu")
if menu_page:
self.update_status.update_status(f"Using ticket #{which_ticket}...")
menu_page.enabling_profile(profile_data)
def on_use_billing(self):
self.custom_window.navigator.navigate("id")
def reverse(self):
self.custom_window.navigator.navigate("menu")

View file

@ -0,0 +1,117 @@
from PyQt6.QtWidgets import QLabel, QPushButton
from PyQt6 import QtCore
from gui.v2.infrastructure.setup_observers import ticket_observer
from gui.v2.ui.pages.Page import Page
from gui.v2.ui.widgets.terminal_widget import TerminalWidget
from gui.v2.workers.ticketing_worker_thread import TicketingWorkerThread
HOW_MANY_PROFILES_DEFAULT = 6
class TicketPrepPage(Page):
def __init__(self, page_stack, main_window=None, parent=None):
super().__init__("TicketPrep", page_stack, main_window, parent)
self.update_status = main_window
self.worker = None
self._terminal_bound = False
self._tickets_ready = False
self.title.setText("Preparing Tickets")
self.title.setGeometry(QtCore.QRect(280, 20, 240, 40))
self.title.setAlignment(QtCore.Qt.AlignmentFlag.AlignCenter)
self.terminal = TerminalWidget(self)
self.terminal.setGeometry(20, 70, 760, 380)
self.status_label = QLabel("", self)
self.status_label.setGeometry(20, 460, 760, 30)
self.status_label.setStyleSheet("color: #00ffff; font-size: 14px;")
self.status_label.setAlignment(QtCore.Qt.AlignmentFlag.AlignCenter)
self.continue_button = QPushButton("Continue", self)
self.continue_button.setGeometry(340, 490, 120, 35)
self.continue_button.setStyleSheet("""
QPushButton {
background-color: #2ecc71; color: white; border: none;
border-radius: 5px; font-size: 14px; font-weight: bold;
}
QPushButton:disabled { background-color: #555555; }
""")
self.continue_button.setEnabled(True)
self.continue_button.clicked.connect(self.on_continue)
def _bind_terminal_once(self):
if self._terminal_bound:
return
self.terminal.bind_observer(ticket_observer, [
'preparing', 'connecting', 'sync_done', 'waiting', 'paid',
'ticket_ready', 'used', 'failed_input', 'failed_output',
'connection_error', 'unknown_error', 'error',
])
self._terminal_bound = True
def start_prep(self):
self._bind_terminal_once()
self.terminal.append("=== Starting ticket preparation ===")
self._tickets_ready = False
self.continue_button.setEnabled(True)
self.status_label.setText("Preparing tickets...")
self.update_status.update_status("Preparing tickets...")
self.worker = TicketingWorkerThread('PREPARE_TICKETS', params={
'how_many_profiles': HOW_MANY_PROFILES_DEFAULT,
})
self.worker.prep_done.connect(self.on_prep_done)
self.worker.error.connect(self.on_error)
self.worker.start()
def on_prep_done(self, result):
if not isinstance(result, dict):
self.terminal.append("ERROR: invalid result format.")
self.status_label.setText("Preparation failed.")
return
if result.get('valid') is True:
self._tickets_ready = True
self.update_status.clear_ticket_verification_failure()
self.terminal.append("=== Ticket preparation complete ===")
self.status_label.setText("Tickets ready. Click Continue to apply one to your profile.")
self.update_status.update_status("Tickets ready.")
self.continue_button.setEnabled(True)
else:
msg = result.get('message', 'failed')
self.terminal.append(f"ERROR: {msg}")
if msg == 'verification_failed':
how_many_failed = result.get('how_many_failed', '?')
failed_validations = result.get('failed_validations', [])
self.terminal.append(f" failed count: {how_many_failed}")
self.terminal.append(f" failed indices: {failed_validations}")
if self.update_status.save_ticket_verification_failure(result):
self.terminal.append("Recovery data saved. Open Settings > Tickets to evaluate or resume.")
self.status_label.setText("Verification failed. Open Settings > Tickets for recovery.")
self.update_status.update_status("Ticket verification failed")
self.continue_button.setEnabled(True)
return
self.status_label.setText(f"Preparation failed: {msg}")
self.update_status.update_status(f"An error occurred")
self.continue_button.setEnabled(True)
def on_error(self, msg):
self.terminal.append(f"EXCEPTION: {msg}")
self.status_label.setText(f"An unkown error occured")
self.continue_button.setEnabled(True)
def on_continue(self):
menu_page = self.custom_window.navigator.get_cached("menu")
profile_id = getattr(self.update_status, 'current_profile_id', None)
if menu_page:
self.custom_window.navigator.navigate("menu")
if not self._tickets_ready:
self.update_status.update_status("Ticket preparation was not completed.")
return
if profile_id is None or menu_page is None:
self.update_status.update_status("Tickets ready. Random ticket use is ON.")
return
self.update_status.update_status("Applying random ticket to profile...")
menu_page.enabling_profile({'id': int(profile_id)})

84
gui/v2/ui/pages/tor_page.py Executable file
View file

@ -0,0 +1,84 @@
import os
from PyQt6.QtWidgets import QLabel, QButtonGroup
from PyQt6.QtGui import QPixmap
from PyQt6.QtCore import Qt
from PyQt6 import QtCore
from gui.v2.ui.pages.Page import Page
class TorPage(Page):
def __init__(self, page_stack, main_window, parent=None):
super().__init__("TorPage", page_stack, main_window, parent)
self.update_status = main_window
self.button_back.setVisible(True)
self.title.setGeometry(585, 40, 185, 40)
self.title.setText("Pick a Country")
self.display.setGeometry(QtCore.QRect(
0, 100, 540, 405))
self.display0 = QLabel(self)
self.display0.setGeometry(QtCore.QRect(
0, 50, 600, 465))
self.display0.setPixmap(
QPixmap(os.path.join(self.btn_path, "browser only.png")))
self.display0.lower()
self.button_go.clicked.connect(self.go_selected)
self.button_reverse.setVisible(True)
self.button_reverse.clicked.connect(self.reverse_selected)
self.label = QLabel(self)
self.label.setGeometry(440, 370, 86, 130)
pixmap = QPixmap(os.path.join(self.btn_path, "tor 86x130.png"))
self.label.setPixmap(pixmap)
self.label.hide()
def showEvent(self, event):
super().showEvent(event)
self.extraccion()
def extraccion(self):
self.data_profile = {}
profile = self.update_status.read_data()
profile_1 = profile.get('Profile_1')
self.verificate()
def verificate(self, value='tor'):
if value == "just proxy":
self.display0.show()
self.label.hide()
elif value == "tor":
self.display0.show()
self.label.show()
else:
pass
self.buttonGroup = QButtonGroup(self)
self.buttons = []
def show_dimentions(self, dimentions):
self.display.setPixmap(QPixmap(os.path.join(self.btn_path, f"{dimentions} garaje.png")).scaled(
self.display.size(), Qt.AspectRatioMode.KeepAspectRatio))
self.selected_dimentions_icon = dimentions
self.button_go.setVisible(True)
self.update_swarp_json()
def update_swarp_json(self):
inserted_data = {
"country_garaje": self.selected_dimentions_icon
}
self.update_status.write_data(inserted_data)
def reverse_selected(self):
self.custom_window.navigator.navigate("residential")
def go_selected(self):
self.custom_window.navigator.navigate("browser")
self.display.clear()
for boton in self.buttons:
boton.setChecked(False)
self.button_go.setVisible(False)

124
gui/v2/ui/pages/welcome_page.py Executable file
View file

@ -0,0 +1,124 @@
import os
from PyQt6.QtWidgets import QLabel, QWidget, QGridLayout, QHBoxLayout, QVBoxLayout
from PyQt6.QtGui import QPixmap
from PyQt6.QtCore import Qt
from gui.v2.ui.pages.Page import Page
class WelcomePage(Page):
def __init__(self, page_stack, main_window=None, parent=None):
super().__init__("Welcome", page_stack, main_window, parent)
self.btn_path = main_window.btn_path
self.update_status = main_window
self.ui_elements = []
self.button_next.clicked.connect(self.go_to_install)
self.button_next.setVisible(True)
self._setup_welcome_ui()
self._setup_stats_display()
def _setup_welcome_ui(self):
welcome_title = QLabel('Welcome to HydraVeil', self)
welcome_title.setGeometry(20, 45, 760, 60)
welcome_title.setStyleSheet("""
font-size: 32px;
font-weight: bold;
color: cyan;
""")
welcome_title.setAlignment(Qt.AlignmentFlag.AlignCenter)
welcome_msg = QLabel(
"Before we begin your journey, we need to set up a few essential components. "
"Click 'Next' to take you to the installation page.", self)
welcome_msg.setGeometry(40, 100, 720, 80)
welcome_msg.setWordWrap(True)
welcome_msg.setStyleSheet("""
font-size: 16px;
color: cyan;
""")
welcome_msg.setAlignment(Qt.AlignmentFlag.AlignCenter)
def _setup_stats_display(self):
stats_container = QWidget(self)
stats_container.setGeometry(70, 200, 730, 240)
stats_title = QLabel(stats_container)
stats_title.setText("Features & Services")
stats_title.setGeometry(190, 10, 300, 30)
stats_title.setStyleSheet("""
font-size: 22px;
font-weight: bold;
color: cyan;
""")
grid_widget = QWidget(stats_container)
grid_widget.setGeometry(0, 70, 700, 190)
grid_layout = QGridLayout(grid_widget)
grid_layout.setSpacing(30)
stats = [
("Browsers", "browsers_mini.png", "10 Supported Browsers", True),
("WireGuard", "wireguard_mini.png", "6 Global Locations", True),
("Proxy", "just proxy_mini.png", "5 Proxy Locations", True),
("Tor", "toricon_mini.png", "Tor Network Access", True)
]
for i, (title, icon_name, count, available) in enumerate(stats):
row = i // 2
col = i % 2
stat_widget = QWidget()
stat_layout = QHBoxLayout(stat_widget)
stat_layout.setContentsMargins(20, 10, 2, 10)
stat_layout.setSpacing(15)
icon_label = QLabel()
icon_path = os.path.join(self.btn_path, icon_name)
icon_label.setPixmap(QPixmap(icon_path).scaled(
30, 30, Qt.AspectRatioMode.KeepAspectRatio, Qt.TransformationMode.SmoothTransformation))
icon_label.setFixedSize(30, 30)
stat_layout.addWidget(icon_label)
text_widget = QWidget()
text_layout = QVBoxLayout(text_widget)
text_layout.setSpacing(5)
text_layout.setContentsMargins(0, 0, 30, 0)
title_widget = QWidget()
title_layout = QHBoxLayout(title_widget)
title_layout.setContentsMargins(0, 0, 0, 0)
title_layout.setSpacing(10)
title_label = QLabel(title)
title_label.setStyleSheet(
"font-size: 16px; font-weight: bold; color: #2c3e50;")
title_layout.addWidget(title_label)
status_indicator = QLabel("")
status_indicator.setStyleSheet("color: #2ecc71; font-size: 16px;")
title_layout.addWidget(status_indicator)
title_layout.addStretch()
text_layout.addWidget(title_widget)
count_label = QLabel(count)
count_label.setStyleSheet("font-size: 16px; color: #34495e;")
text_layout.addWidget(count_label)
stat_layout.addWidget(text_widget, stretch=1)
stat_widget.setStyleSheet("""
QWidget {
background-color: transparent;
border-radius: 10px;
}
""")
grid_layout.addWidget(stat_widget, row, col)
def go_to_install(self):
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='all', distro='debian')

136
gui/v2/ui/pages/wireguard_page.py Executable file
View file

@ -0,0 +1,136 @@
import os
from PyQt6.QtWidgets import QPushButton, QLabel, QButtonGroup
from PyQt6.QtGui import QPixmap, QIcon
from PyQt6 import QtCore
from gui.v2.ui.pages.Page import Page
class WireGuardPage(Page):
browser_label_created = False
def __init__(self, page_stack, main_window, parent=None):
super().__init__("Wireguard", page_stack, main_window, parent)
self.buttonGroup = QButtonGroup(self)
self.btn_path = main_window.btn_path
self.update_status = main_window
self.buttons = []
self.selected_protocol = None
self.selected_protocol_icon = None
self.button_back.setVisible(True)
self.button_go.clicked.connect(self.go_selected)
self.additional_labels = []
self.title.setGeometry(585, 40, 185, 40)
self.title.setText("Pick a Protocol")
self.sudo_warning = QLabel("Requires Sudo", self)
self.sudo_warning.setGeometry(165, 90, 300, 40)
self.sudo_warning.setStyleSheet("""
font-size: 24px;
font-weight: bold;
color: cyan;
""")
self.sudo_warning.setVisible(False)
self.create_interface_elements()
def create_interface_elements(self):
for j, (object_type, icon_name, page_name, geometry) in enumerate([
(QPushButton, "system-wide", "location", (585, 90, 185, 75)),
(QPushButton, "browser-only", "location",
(585, 90+30+75+30+75, 185, 75)),
(QLabel, None, None, (570, 170, 210, 105)),
(QLabel, None, None, (570, 385, 210, 115))
]):
if object_type == QPushButton:
boton = object_type(self)
boton.setGeometry(*geometry)
boton.setIconSize(boton.size())
boton.setCheckable(True)
boton.setIcon(
QIcon(os.path.join(self.btn_path, f"{icon_name}.png")))
self.buttons.append(boton)
self.buttonGroup.addButton(boton, j)
boton.clicked.connect(
lambda _, name=page_name, protocol=icon_name: self.show_protocol(name, protocol))
elif object_type == QLabel:
label = object_type(self)
label.setGeometry(*geometry)
label.setAlignment(QtCore.Qt.AlignmentFlag.AlignCenter)
label.setWordWrap(True)
if geometry == (570, 170, 210, 105):
text1 = "VPN for the whole PC\n(€1/month - 1 location)"
label.setText(text1)
elif geometry == (570, 385, 210, 115):
text2 = "1 Profile.\n1 Country.\nIsolated VPN for just the browser\n(1€/month)"
label.setText(text2)
def update_swarp_json(self):
inserted_data = {
"protocol": "wireguard",
"connection": self.selected_protocol_icon
}
self.update_status.write_data(inserted_data)
def show_protocol(self, page_name, protocol):
if protocol == "browser-only":
self.sudo_warning.setVisible(False)
else:
self.sudo_warning.setVisible(True)
self.protocol_chosen = protocol
self.selected_protocol_icon = protocol
self.selected_page_name = page_name
self.button_go.setVisible(True)
self.update_swarp_json()
for label in self.additional_labels:
label.deleteLater()
self.additional_labels.clear()
if protocol == "system-wide":
label_principal = QLabel(self)
label_principal.setGeometry(0, 60, 540, 460)
pixmap = QPixmap(os.path.join(self.btn_path, "wireguard.png"))
label_principal.setPixmap(pixmap)
label_principal.show()
label_principal.setScaledContents(True)
label_principal.lower()
self.additional_labels.append(label_principal)
if protocol == "browser-only":
label_principal = QLabel(self)
label_principal.setGeometry(0, 80, 530, 380)
pixmap = QPixmap(os.path.join(self.btn_path, "wireguard.png"))
label_principal.setPixmap(pixmap)
label_principal.show()
label_principal.setScaledContents(True)
label_principal.show()
self.additional_labels.append(label_principal)
label_background = QLabel(self)
label_background.setGeometry(0, 60, 540, 460)
pixmap = QPixmap(os.path.join(self.btn_path, "browser only.png"))
label_background.setPixmap(pixmap)
label_background.show()
label_background.setScaledContents(True)
label_background.lower()
self.additional_labels.append(label_background)
def go_selected(self):
if self.selected_page_name:
self.custom_window.navigator.navigate("location")
self.display.clear()
for boton in self.buttons:
boton.setChecked(False)
self.button_go.setVisible(False)
def reverse(self):
self.display.clear()
for boton in self.buttons:
boton.setChecked(False)
self.button_go.setVisible(False)
self.custom_window.navigator.navigate("protocol")

0
gui/v2/ui/popups/__init__.py Executable file
View file

Binary file not shown.

View file

@ -0,0 +1,91 @@
from PyQt6.QtWidgets import (
QWidget, QPushButton, QLabel, QFrame, QVBoxLayout, QHBoxLayout, QScrollArea
)
from PyQt6.QtGui import QFont
from PyQt6.QtCore import Qt, pyqtSignal
from gui.v2.ui.styles.styles import (
POPUP_ACTION_BUTTON_RED_QSS,
POPUP_BG_QSS,
POPUP_CANCEL_BUTTON_QSS,
POPUP_CLOSE_BUTTON_QSS,
)
class ConfirmationPopup(QWidget):
finished = pyqtSignal(bool)
def __init__(self, parent=None, message="", action_button_text="", cancel_button_text="Cancel"):
super().__init__(parent)
self.parent_window = parent
self.message = message
self.action_button_text = action_button_text
self.cancel_button_text = cancel_button_text
self.initUI()
def initUI(self):
self.setMinimumSize(400, 200)
self.setWindowFlags(Qt.WindowType.FramelessWindowHint)
self.setAttribute(Qt.WidgetAttribute.WA_TranslucentBackground)
main_layout = QVBoxLayout()
self.setLayout(main_layout)
bg_widget = QWidget(self)
bg_widget.setStyleSheet(POPUP_BG_QSS)
main_layout.addWidget(bg_widget)
content_layout = QVBoxLayout(bg_widget)
close_button = QPushButton("", self)
close_button.setStyleSheet(POPUP_CLOSE_BUTTON_QSS)
close_button.setFixedSize(30, 30)
close_button.clicked.connect(self.close)
content_layout.addWidget(
close_button, alignment=Qt.AlignmentFlag.AlignRight)
scroll_area = QScrollArea()
scroll_area.setWidgetResizable(True)
scroll_area.setFrameShape(QFrame.Shape.NoFrame)
content_layout.addWidget(scroll_area)
scroll_content = QWidget()
scroll_layout = QVBoxLayout(scroll_content)
message_label = QLabel(self.message)
message_label.setAlignment(Qt.AlignmentFlag.AlignCenter)
message_label.setFont(QFont("Arial", 14))
message_label.setStyleSheet("color: #333333; margin: 10px 0;")
message_label.setWordWrap(True)
scroll_layout.addWidget(message_label)
scroll_area.setWidget(scroll_content)
button_layout = QHBoxLayout()
content_layout.addLayout(button_layout)
cancel_button = QPushButton(self.cancel_button_text)
cancel_button.setFixedSize(150, 50)
cancel_button.setFont(QFont("Arial", 12))
cancel_button.setStyleSheet(POPUP_CANCEL_BUTTON_QSS)
cancel_button.clicked.connect(self.close)
button_layout.addWidget(cancel_button)
action_button = QPushButton(self.action_button_text)
action_button.setFixedSize(150, 50)
action_button.setFont(QFont("Arial", 12))
action_button.setStyleSheet(POPUP_ACTION_BUTTON_RED_QSS)
action_button.clicked.connect(self.perform_action)
button_layout.addWidget(action_button)
def perform_action(self):
self.finished.emit(True)
self.close()
def mousePressEvent(self, event):
self.oldPos = event.globalPosition().toPoint()
def mouseMoveEvent(self, event):
delta = event.globalPosition().toPoint() - self.oldPos
self.move(self.x() + delta.x(), self.y() + delta.y())
self.oldPos = event.globalPosition().toPoint()

View file

@ -0,0 +1,122 @@
from PyQt6.QtWidgets import (
QWidget, QPushButton, QLabel, QFrame, QVBoxLayout, QHBoxLayout, QScrollArea
)
from PyQt6.QtGui import QFont
from PyQt6.QtCore import Qt, pyqtSignal
from gui.v2.ui.styles.styles import (
POPUP_BG_QSS,
POPUP_CANCEL_BUTTON_QSS,
POPUP_CLOSE_BUTTON_QSS,
)
class EndpointVerificationPopup(QWidget):
finished = pyqtSignal(bool, str)
def __init__(self, parent=None, message=""):
super().__init__(parent)
self.parent_window = parent
self.message = message
self.initUI()
def initUI(self):
self.setMinimumSize(500, 250)
self.setWindowFlags(Qt.WindowType.FramelessWindowHint)
self.setAttribute(Qt.WidgetAttribute.WA_TranslucentBackground)
main_layout = QVBoxLayout()
self.setLayout(main_layout)
bg_widget = QWidget(self)
bg_widget.setStyleSheet(POPUP_BG_QSS)
main_layout.addWidget(bg_widget)
content_layout = QVBoxLayout(bg_widget)
close_button = QPushButton("", self)
close_button.setStyleSheet(POPUP_CLOSE_BUTTON_QSS)
close_button.setFixedSize(30, 30)
close_button.clicked.connect(lambda: self.close_with_action("abort"))
content_layout.addWidget(
close_button, alignment=Qt.AlignmentFlag.AlignRight)
scroll_area = QScrollArea()
scroll_area.setWidgetResizable(True)
scroll_area.setFrameShape(QFrame.Shape.NoFrame)
content_layout.addWidget(scroll_area)
scroll_content = QWidget()
scroll_layout = QVBoxLayout(scroll_content)
message_label = QLabel(self.message)
message_label.setAlignment(Qt.AlignmentFlag.AlignCenter)
message_label.setFont(QFont("Arial", 14))
message_label.setStyleSheet("color: #333333; margin: 10px 0;")
message_label.setWordWrap(True)
scroll_layout.addWidget(message_label)
scroll_area.setWidget(scroll_content)
button_layout = QHBoxLayout()
content_layout.addLayout(button_layout)
sync_button = QPushButton("Sync")
sync_button.setFixedSize(120, 50)
sync_button.setFont(QFont("Arial", 12))
sync_button.setStyleSheet("""
QPushButton {
background-color: #4CAF50;
border: none;
color: white;
border-radius: 5px;
font-weight: bold;
}
QPushButton:hover {
background-color: #45a049;
}
""")
sync_button.clicked.connect(lambda: self.close_with_action("sync"))
button_layout.addWidget(sync_button)
abort_button = QPushButton("Abort")
abort_button.setFixedSize(120, 50)
abort_button.setFont(QFont("Arial", 12))
abort_button.setStyleSheet(POPUP_CANCEL_BUTTON_QSS)
abort_button.clicked.connect(lambda: self.close_with_action("abort"))
button_layout.addWidget(abort_button)
continue_button = QPushButton("Continue Anyway")
continue_button.setFixedSize(150, 50)
continue_button.setFont(QFont("Arial", 12))
continue_button.setStyleSheet("""
QPushButton {
background-color: #ff9800;
border: none;
color: white;
border-radius: 5px;
font-weight: bold;
}
QPushButton:hover {
background-color: #fb8c00;
}
""")
continue_button.clicked.connect(
lambda: self.close_with_action("continue"))
button_layout.addWidget(continue_button)
def close_with_action(self, action):
self.finished.emit(True, action)
self.close()
def closeEvent(self, event):
self.finished.emit(False, "abort")
event.accept()
def mousePressEvent(self, event):
self.oldPos = event.globalPosition().toPoint()
def mouseMoveEvent(self, event):
delta = event.globalPosition().toPoint() - self.oldPos
self.move(self.x() + delta.x(), self.y() + delta.y())
self.oldPos = event.globalPosition().toPoint()

105
gui/v2/ui/popups/qrcode_dialog.py Executable file
View file

@ -0,0 +1,105 @@
from io import BytesIO
import qrcode
from PyQt6.QtWidgets import QDialog, QLabel, QPushButton, QVBoxLayout
from PyQt6.QtGui import QPixmap
from PyQt6.QtCore import Qt
class QRCodeDialog(QDialog):
def __init__(self, address_text, full_amount, currency_type=None, parent=None):
super().__init__(parent)
self.currency_type = currency_type
self.setWindowTitle("Payment Address QR Code")
self.setFixedSize(400, 450)
self.setModal(True)
layout = QVBoxLayout(self)
title_label = QLabel("Scan QR Code to Copy Address", self)
title_label.setAlignment(Qt.AlignmentFlag.AlignCenter)
title_label.setStyleSheet(
"font-size: 16px; font-weight: bold; color: #00ffff; margin: 10px;")
layout.addWidget(title_label)
qr_label = QLabel(self)
qr_label.setAlignment(Qt.AlignmentFlag.AlignCenter)
qr_label.setStyleSheet(
"margin: 10px; border: 2px solid #00ffff; border-radius: 10px;")
qr_pixmap = self.generate_qr_code(address_text, full_amount)
qr_label.setPixmap(qr_pixmap)
layout.addWidget(qr_label)
amount_label = QLabel("Amount:", self)
amount_label.setStyleSheet(
"font-size: 12px; color: #ffffff; margin-top: 10px;")
layout.addWidget(amount_label)
amount_text_label = QLabel(full_amount, self)
amount_text_label.setStyleSheet(
"font-size: 10px; color: #cccccc; margin: 5px; padding: 5px; border: 1px solid #666; border-radius: 5px;")
layout.addWidget(amount_text_label)
address_label = QLabel("Address:", self)
address_label.setStyleSheet(
"font-size: 12px; color: #ffffff; margin-top: 10px;")
layout.addWidget(address_label)
address_text_label = QLabel(address_text, self)
address_text_label.setWordWrap(True)
address_text_label.setStyleSheet(
"font-size: 10px; color: #cccccc; margin: 5px; padding: 5px; border: 1px solid #666; border-radius: 5px;")
layout.addWidget(address_text_label)
close_button = QPushButton("Close", self)
close_button.setStyleSheet(
"font-size: 14px; padding: 8px; margin: 10px;")
close_button.clicked.connect(self.close)
layout.addWidget(close_button)
self.setStyleSheet("""
QDialog {
background-color: #2c3e50;
border: 2px solid #00ffff;
border-radius: 10px;
}
""")
def generate_qr_code(self, text, full_amount):
qr = qrcode.QRCode(
version=1,
error_correction=qrcode.constants.ERROR_CORRECT_L,
box_size=8,
border=4,
)
if self.currency_type:
currency_lower = self.currency_type.lower()
if currency_lower == 'bitcoin':
qr_data = f"bitcoin:{text}?amount={full_amount}"
elif currency_lower == 'monero':
qr_data = f"monero:{text}?amount={full_amount}"
elif currency_lower == 'lightning':
qr_data = f"bitcoin:{text}?amount={full_amount}&lightning=ln"
elif currency_lower == 'litecoin':
qr_data = f"litecoin:{text}?amount={full_amount}"
else:
qr_data = f"{text}?amount={full_amount}"
else:
qr_data = text
qr.add_data(qr_data)
qr.make(fit=True)
qr_image = qr.make_image(fill_color="black", back_color="white")
buffer = BytesIO()
qr_image.save(buffer, format='PNG')
buffer.seek(0)
pixmap = QPixmap()
pixmap.loadFromData(buffer.getvalue())
return pixmap.scaled(150, 150, Qt.AspectRatioMode.KeepAspectRatio, Qt.TransformationMode.SmoothTransformation)

Some files were not shown because too many files have changed in this diff Show more