forked from Support/sp-hydra-veil-gui
update: added v2 gui
This commit is contained in:
parent
68154c4576
commit
9f714b9f7b
120 changed files with 12842 additions and 0 deletions
0
gui/v2/__init__.py
Executable file
0
gui/v2/__init__.py
Executable file
709
gui/v2/__main__.py
Executable file
709
gui/v2/__main__.py
Executable 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())
|
||||||
BIN
gui/v2/__pycache__/__init__.cpython-312.pyc
Normal file
BIN
gui/v2/__pycache__/__init__.cpython-312.pyc
Normal file
Binary file not shown.
BIN
gui/v2/__pycache__/__main__.cpython-312.pyc
Normal file
BIN
gui/v2/__pycache__/__main__.cpython-312.pyc
Normal file
Binary file not shown.
0
gui/v2/actions/__init__.py
Executable file
0
gui/v2/actions/__init__.py
Executable file
BIN
gui/v2/actions/__pycache__/__init__.cpython-312.pyc
Normal file
BIN
gui/v2/actions/__pycache__/__init__.cpython-312.pyc
Normal file
Binary file not shown.
BIN
gui/v2/actions/__pycache__/config.cpython-312.pyc
Normal file
BIN
gui/v2/actions/__pycache__/config.cpython-312.pyc
Normal file
Binary file not shown.
BIN
gui/v2/actions/__pycache__/flags.cpython-312.pyc
Normal file
BIN
gui/v2/actions/__pycache__/flags.cpython-312.pyc
Normal file
Binary file not shown.
BIN
gui/v2/actions/__pycache__/key_interpretation.cpython-312.pyc
Normal file
BIN
gui/v2/actions/__pycache__/key_interpretation.cpython-312.pyc
Normal file
Binary file not shown.
BIN
gui/v2/actions/__pycache__/locations.cpython-312.pyc
Normal file
BIN
gui/v2/actions/__pycache__/locations.cpython-312.pyc
Normal file
Binary file not shown.
BIN
gui/v2/actions/__pycache__/logging_setup.cpython-312.pyc
Normal file
BIN
gui/v2/actions/__pycache__/logging_setup.cpython-312.pyc
Normal file
Binary file not shown.
BIN
gui/v2/actions/__pycache__/profile_data.cpython-312.pyc
Normal file
BIN
gui/v2/actions/__pycache__/profile_data.cpython-312.pyc
Normal file
Binary file not shown.
BIN
gui/v2/actions/__pycache__/profile_order.cpython-312.pyc
Normal file
BIN
gui/v2/actions/__pycache__/profile_order.cpython-312.pyc
Normal file
Binary file not shown.
Binary file not shown.
BIN
gui/v2/actions/__pycache__/sync.cpython-312.pyc
Normal file
BIN
gui/v2/actions/__pycache__/sync.cpython-312.pyc
Normal file
Binary file not shown.
BIN
gui/v2/actions/__pycache__/ticket_failure.cpython-312.pyc
Normal file
BIN
gui/v2/actions/__pycache__/ticket_failure.cpython-312.pyc
Normal file
Binary file not shown.
38
gui/v2/actions/config.py
Executable file
38
gui/v2/actions/config.py
Executable 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
63
gui/v2/actions/flags.py
Executable 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
|
||||||
30
gui/v2/actions/key_interpretation.py
Executable file
30
gui/v2/actions/key_interpretation.py
Executable 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
34
gui/v2/actions/locations.py
Executable 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
66
gui/v2/actions/logging_setup.py
Executable 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
10
gui/v2/actions/profile_data.py
Executable 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
61
gui/v2/actions/profile_order.py
Executable 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)
|
||||||
9
gui/v2/actions/should_be_synchronized.py
Executable file
9
gui/v2/actions/should_be_synchronized.py
Executable 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
19
gui/v2/actions/sync.py
Executable 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
|
||||||
57
gui/v2/actions/ticket_failure.py
Executable file
57
gui/v2/actions/ticket_failure.py
Executable 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}")
|
||||||
0
gui/v2/infrastructure/__init__.py
Executable file
0
gui/v2/infrastructure/__init__.py
Executable file
BIN
gui/v2/infrastructure/__pycache__/__init__.cpython-312.pyc
Executable file
BIN
gui/v2/infrastructure/__pycache__/__init__.cpython-312.pyc
Executable file
Binary file not shown.
Binary file not shown.
BIN
gui/v2/infrastructure/__pycache__/navigator.cpython-312.pyc
Normal file
BIN
gui/v2/infrastructure/__pycache__/navigator.cpython-312.pyc
Normal file
Binary file not shown.
BIN
gui/v2/infrastructure/__pycache__/page_registry.cpython-312.pyc
Normal file
BIN
gui/v2/infrastructure/__pycache__/page_registry.cpython-312.pyc
Normal file
Binary file not shown.
Binary file not shown.
76
gui/v2/infrastructure/connection_manager.py
Executable file
76
gui/v2/infrastructure/connection_manager.py
Executable 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 []
|
||||||
33
gui/v2/infrastructure/navigator.py
Executable file
33
gui/v2/infrastructure/navigator.py
Executable 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
|
||||||
32
gui/v2/infrastructure/page_registry.py
Executable file
32
gui/v2/infrastructure/page_registry.py
Executable 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"),
|
||||||
|
}
|
||||||
74
gui/v2/infrastructure/setup_observers.py
Executable file
74
gui/v2/infrastructure/setup_observers.py
Executable 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
0
gui/v2/ui/__init__.py
Executable file
BIN
gui/v2/ui/__pycache__/__init__.cpython-312.pyc
Executable file
BIN
gui/v2/ui/__pycache__/__init__.cpython-312.pyc
Executable file
Binary file not shown.
0
gui/v2/ui/builders/__init__.py
Executable file
0
gui/v2/ui/builders/__init__.py
Executable file
BIN
gui/v2/ui/builders/__pycache__/__init__.cpython-312.pyc
Normal file
BIN
gui/v2/ui/builders/__pycache__/__init__.cpython-312.pyc
Normal file
Binary file not shown.
BIN
gui/v2/ui/builders/__pycache__/bottom_section.cpython-312.pyc
Normal file
BIN
gui/v2/ui/builders/__pycache__/bottom_section.cpython-312.pyc
Normal file
Binary file not shown.
BIN
gui/v2/ui/builders/__pycache__/system_tray.cpython-312.pyc
Normal file
BIN
gui/v2/ui/builders/__pycache__/system_tray.cpython-312.pyc
Normal file
Binary file not shown.
BIN
gui/v2/ui/builders/__pycache__/top_section.cpython-312.pyc
Normal file
BIN
gui/v2/ui/builders/__pycache__/top_section.cpython-312.pyc
Normal file
Binary file not shown.
54
gui/v2/ui/builders/bottom_section.py
Executable file
54
gui/v2/ui/builders/bottom_section.py
Executable 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,
|
||||||
|
}
|
||||||
34
gui/v2/ui/builders/system_tray.py
Executable file
34
gui/v2/ui/builders/system_tray.py
Executable 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
|
||||||
30
gui/v2/ui/builders/top_section.py
Executable file
30
gui/v2/ui/builders/top_section.py
Executable 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
86
gui/v2/ui/pages/Page.py
Executable 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
0
gui/v2/ui/pages/__init__.py
Executable file
BIN
gui/v2/ui/pages/__pycache__/Page.cpython-312.pyc
Normal file
BIN
gui/v2/ui/pages/__pycache__/Page.cpython-312.pyc
Normal file
Binary file not shown.
BIN
gui/v2/ui/pages/__pycache__/__init__.cpython-312.pyc
Executable file
BIN
gui/v2/ui/pages/__pycache__/__init__.cpython-312.pyc
Executable file
Binary file not shown.
BIN
gui/v2/ui/pages/__pycache__/_dummy_page.cpython-312.pyc
Executable file
BIN
gui/v2/ui/pages/__pycache__/_dummy_page.cpython-312.pyc
Executable file
Binary file not shown.
BIN
gui/v2/ui/pages/__pycache__/browser_page.cpython-312.pyc
Normal file
BIN
gui/v2/ui/pages/__pycache__/browser_page.cpython-312.pyc
Normal file
Binary file not shown.
BIN
gui/v2/ui/pages/__pycache__/editor_page.cpython-312.pyc
Normal file
BIN
gui/v2/ui/pages/__pycache__/editor_page.cpython-312.pyc
Normal file
Binary file not shown.
Binary file not shown.
BIN
gui/v2/ui/pages/__pycache__/hidetor_page.cpython-312.pyc
Normal file
BIN
gui/v2/ui/pages/__pycache__/hidetor_page.cpython-312.pyc
Normal file
Binary file not shown.
BIN
gui/v2/ui/pages/__pycache__/location_page.cpython-312.pyc
Normal file
BIN
gui/v2/ui/pages/__pycache__/location_page.cpython-312.pyc
Normal file
Binary file not shown.
Binary file not shown.
BIN
gui/v2/ui/pages/__pycache__/menu_page.cpython-312.pyc
Normal file
BIN
gui/v2/ui/pages/__pycache__/menu_page.cpython-312.pyc
Normal file
Binary file not shown.
BIN
gui/v2/ui/pages/__pycache__/protocol_page.cpython-312.pyc
Normal file
BIN
gui/v2/ui/pages/__pycache__/protocol_page.cpython-312.pyc
Normal file
Binary file not shown.
BIN
gui/v2/ui/pages/__pycache__/screen_page.cpython-312.pyc
Normal file
BIN
gui/v2/ui/pages/__pycache__/screen_page.cpython-312.pyc
Normal file
Binary file not shown.
BIN
gui/v2/ui/pages/__pycache__/settings_page.cpython-312.pyc
Normal file
BIN
gui/v2/ui/pages/__pycache__/settings_page.cpython-312.pyc
Normal file
Binary file not shown.
17
gui/v2/ui/pages/_blank_page.py
Executable file
17
gui/v2/ui/pages/_blank_page.py
Executable 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
241
gui/v2/ui/pages/browser_page.py
Executable 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")
|
||||||
48
gui/v2/ui/pages/connection_page.py
Executable file
48
gui/v2/ui/pages/connection_page.py
Executable 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)
|
||||||
65
gui/v2/ui/pages/currency_selection_page.py
Executable file
65
gui/v2/ui/pages/currency_selection_page.py
Executable 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")
|
||||||
92
gui/v2/ui/pages/duration_selection_page.py
Executable file
92
gui/v2/ui/pages/duration_selection_page.py
Executable 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
817
gui/v2/ui/pages/editor_page.py
Executable 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')
|
||||||
105
gui/v2/ui/pages/fast_mode_prompt_page.py
Executable file
105
gui/v2/ui/pages/fast_mode_prompt_page.py
Executable 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
|
||||||
787
gui/v2/ui/pages/fast_registration_page.py
Executable file
787
gui/v2/ui/pages/fast_registration_page.py
Executable 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
105
gui/v2/ui/pages/hidetor_page.py
Executable 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
190
gui/v2/ui/pages/id_page.py
Executable 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")
|
||||||
488
gui/v2/ui/pages/install_system_package.py
Executable file
488
gui/v2/ui/pages/install_system_package.py
Executable 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
254
gui/v2/ui/pages/location_page.py
Executable 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
|
||||||
284
gui/v2/ui/pages/location_verification_page.py
Executable file
284
gui/v2/ui/pages/location_verification_page.py
Executable 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
1247
gui/v2/ui/pages/menu_page.py
Executable file
File diff suppressed because it is too large
Load diff
149
gui/v2/ui/pages/payment_confirmed_page.py
Executable file
149
gui/v2/ui/pages/payment_confirmed_page.py
Executable 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")
|
||||||
419
gui/v2/ui/pages/payment_details_page.py
Executable file
419
gui/v2/ui/pages/payment_details_page.py
Executable 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')
|
||||||
101
gui/v2/ui/pages/plan_picker_page.py
Executable file
101
gui/v2/ui/pages/plan_picker_page.py
Executable 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")
|
||||||
136
gui/v2/ui/pages/policy_suggestion_page.py
Executable file
136
gui/v2/ui/pages/policy_suggestion_page.py
Executable 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")
|
||||||
87
gui/v2/ui/pages/protocol_page.py
Executable file
87
gui/v2/ui/pages/protocol_page.py
Executable 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")
|
||||||
101
gui/v2/ui/pages/residential_page.py
Executable file
101
gui/v2/ui/pages/residential_page.py
Executable 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
463
gui/v2/ui/pages/resume_page.py
Executable 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
347
gui/v2/ui/pages/screen_page.py
Executable 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
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
177
gui/v2/ui/pages/sync_screen.py
Executable 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")
|
||||||
115
gui/v2/ui/pages/systemwide_prompt_page.py
Executable file
115
gui/v2/ui/pages/systemwide_prompt_page.py
Executable 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")
|
||||||
112
gui/v2/ui/pages/ticket_crypto_picker_page.py
Executable file
112
gui/v2/ui/pages/ticket_crypto_picker_page.py
Executable 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")
|
||||||
117
gui/v2/ui/pages/ticket_or_billing_choice_page.py
Executable file
117
gui/v2/ui/pages/ticket_or_billing_choice_page.py
Executable 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")
|
||||||
117
gui/v2/ui/pages/ticket_prep_page.py
Executable file
117
gui/v2/ui/pages/ticket_prep_page.py
Executable 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
84
gui/v2/ui/pages/tor_page.py
Executable 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
124
gui/v2/ui/pages/welcome_page.py
Executable 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
136
gui/v2/ui/pages/wireguard_page.py
Executable 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
0
gui/v2/ui/popups/__init__.py
Executable file
BIN
gui/v2/ui/popups/__pycache__/__init__.cpython-312.pyc
Normal file
BIN
gui/v2/ui/popups/__pycache__/__init__.cpython-312.pyc
Normal file
Binary file not shown.
BIN
gui/v2/ui/popups/__pycache__/confirmation_popup.cpython-312.pyc
Normal file
BIN
gui/v2/ui/popups/__pycache__/confirmation_popup.cpython-312.pyc
Normal file
Binary file not shown.
Binary file not shown.
Binary file not shown.
91
gui/v2/ui/popups/confirmation_popup.py
Executable file
91
gui/v2/ui/popups/confirmation_popup.py
Executable 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()
|
||||||
122
gui/v2/ui/popups/endpoint_verification_popup.py
Executable file
122
gui/v2/ui/popups/endpoint_verification_popup.py
Executable 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
105
gui/v2/ui/popups/qrcode_dialog.py
Executable 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
Loading…
Reference in a new issue