From 52a4fd8be734e3983a675ec27e67e1c019fffb01 Mon Sep 17 00:00:00 2001 From: John Date: Fri, 19 Sep 2025 01:14:43 +0100 Subject: [PATCH] added new wg debugging tool --- gui/__main__.py | 591 +++++++++++++++++++++++++++++++++++++++++++++--- 1 file changed, 565 insertions(+), 26 deletions(-) mode change 100644 => 100755 gui/__main__.py diff --git a/gui/__main__.py b/gui/__main__.py old mode 100644 new mode 100755 index db1b3b8..d77bf8c --- a/gui/__main__.py +++ b/gui/__main__.py @@ -895,6 +895,7 @@ class CustomWindow(QMainWindow): IdPage(self.page_stack, self), TorPage(self.page_stack, self), EditorPage(self.page_stack, self), + FastModePromptPage(self.page_stack, self), FastRegistrationPage(self.page_stack, self), Settings(self.page_stack, self), ConnectionPage(self.page_stack, self), @@ -949,6 +950,34 @@ class CustomWindow(QMainWindow): else: return False + def has_shown_fast_mode_prompt(self): + flag_file = os.path.join(self.gui_config_home, '.fast-mode-prompted') + return os.path.exists(flag_file) + + def mark_fast_mode_prompt_shown(self): + os.makedirs(self.gui_config_home, exist_ok=True) + flag_file = os.path.join(self.gui_config_home, '.fast-mode-prompted') + try: + with open(flag_file, 'w') as f: + f.write('shown') + except Exception as e: + print(f"Error writing fast mode flag: {e}") + + def set_fast_mode_enabled(self, enabled): + config = self._load_gui_config() + if config is None: + config = {"logging": {"gui_logging_enabled": False, "log_level": "INFO"}} + if "registrations" not in config: + config["registrations"] = {} + config["registrations"]["fast_registration_enabled"] = bool(enabled) + self._save_gui_config(config) + + def navigate_after_profile_created(self): + if not self.has_shown_fast_mode_prompt(): + self.page_stack.setCurrentIndex(self.page_stack.indexOf(self.page_stack.findChild(FastModePromptPage))) + else: + self.page_stack.setCurrentIndex(self.page_stack.indexOf(self.page_stack.findChild(MenuPage))) + def enable_marquee(self, text): self.marquee_text = text + " " self.marquee_position = 0 @@ -1015,7 +1044,7 @@ class CustomWindow(QMainWindow): if isinstance(current_page, MenuPage): self.update_status(None) - if isinstance(previous_page, (ResumePage, EditorPage, Settings, FastRegistrationPage)): + if isinstance(previous_page, (ResumePage, EditorPage, Settings, FastRegistrationPage, FastModePromptPage)): current_page.eliminacion() else: @@ -3893,8 +3922,11 @@ class ResumePage(Page): self.create_core_profiles(new_profile, profile_id) - # Restablecer el índice de la página actual al menú principal - self.page_stack.setCurrentIndex(self.page_stack.indexOf(self.page_stack.findChild(MenuPage))) + main = self.update_status + if hasattr(main, 'navigate_after_profile_created'): + main.navigate_after_profile_created() + else: + self.page_stack.setCurrentIndex(self.page_stack.indexOf(self.page_stack.findChild(MenuPage))) self.update_status.clear_data() @@ -4497,11 +4529,7 @@ class EditorPage(Page): else: location = self.connection_manager.get_location_info(new_value) - if profile.has_wireguard_configuration(): - profile.delete_wireguard_configuration() - if profile.has_proxy_configuration(): - profile.delete_proxy_configuration() if location: profile.location.code = location.code @@ -4614,7 +4642,8 @@ class Settings(Page): ("Subscriptions", self.show_subscription_page), ("Create/Edit", self.show_registrations_page), ("Delete Profile", self.show_delete_page), - ("Error Logs", self.show_logs_page) + ("Error Logs", self.show_logs_page), + ("Debug Help", self.show_debug_page) ] @@ -4657,12 +4686,14 @@ class Settings(Page): self.subscription_page = self.create_subscription_page() self.logs_page = self.create_logs_page() self.delete_page = self.create_delete_page() + self.debug_page = self.create_debug_page() self.content_layout.addWidget(self.account_page) self.content_layout.addWidget(self.wireguard_page) self.content_layout.addWidget(self.subscription_page) self.content_layout.addWidget(self.logs_page) self.content_layout.addWidget(self.delete_page) + self.content_layout.addWidget(self.debug_page) self.show_account_page() @@ -4751,6 +4782,344 @@ class Settings(Page): return page + def create_debug_page(self): + page = QWidget() + layout = QVBoxLayout(page) + layout.setContentsMargins(20, 20, 20, 20) + layout.setSpacing(15) + + title = QLabel("DEBUG HELP") + title.setStyleSheet(f"color: #808080; font-size: 12px; font-weight: bold; {self.font_style}") + layout.addWidget(title) + + scroll_area = QScrollArea() + scroll_area.setWidgetResizable(True) + scroll_area.setStyleSheet(f""" + QScrollArea {{ + border: none; + background: transparent; + }} + QScrollArea > QWidget > QWidget {{ + background: transparent; + }} + """) + + scroll_content = QWidget() + scroll_layout = QVBoxLayout(scroll_content) + scroll_layout.setSpacing(15) + + app_path = sys.executable if hasattr(sys, 'frozen') else os.path.abspath(__file__) + if hasattr(sys, 'frozen'): + app_dir = os.path.dirname(app_path) + else: + app_dir = os.path.dirname(os.path.dirname(app_path)) + + app_path_label = QLabel(f"Application Path: {app_path}") + app_path_label.setStyleSheet(f"color: white; font-size: 14px; {self.font_style}") + app_path_label.setWordWrap(True) + scroll_layout.addWidget(app_path_label) + + app_dir_label = QLabel(f"Application Directory: {app_dir}") + app_dir_label.setStyleSheet(f"color: white; font-size: 14px; {self.font_style}") + app_dir_label.setWordWrap(True) + scroll_layout.addWidget(app_dir_label) + + command_group = QGroupBox("CLI Commands") + command_group.setStyleSheet(f""" + QGroupBox {{ + color: white; + font-weight: bold; + border: 1px solid rgba(255, 255, 255, 0.1); + border-radius: 8px; + padding: 15px; + font-size: 10px; + margin-top: 15px; + {self.font_style} + }} + QGroupBox::title {{ + subcontrol-origin: margin; + left: 10px; + padding: 0 5px; + }} + """) + command_layout = QVBoxLayout(command_group) + + self.cli_command = QLineEdit() + self.cli_command.setReadOnly(True) + self.cli_command.setText(f"{app_path} --cli profile enable -i 1") + self.cli_command.setStyleSheet(f""" + QLineEdit {{ + color: white; + background: rgba(255, 255, 255, 0.1); + border: 1px solid rgba(255, 255, 255, 0.2); + border-radius: 4px; + padding: 8px; + font-family: 'Courier New', monospace; + font-size: 12px; + }} + """) + command_layout.addWidget(self.cli_command) + + copy_button = QPushButton("Copy Command") + copy_button.setFixedSize(120, 40) + copy_button.setStyleSheet(f""" + QPushButton {{ + font-size: 12px; + background: #007AFF; + color: white; + border: none; + border-radius: 5px; + font-weight: bold; + {self.font_style} + }} + QPushButton:hover {{ + background: #0056CC; + }} + """) + copy_button.clicked.connect(self.copy_cli_command) + command_layout.addWidget(copy_button) + + execute_button = QPushButton("Execute Command") + execute_button.setFixedSize(120, 40) + execute_button.setStyleSheet(f""" + QPushButton {{ + font-size: 10px; + background: #4CAF50; + color: white; + border: none; + border-radius: 5px; + font-weight: bold; + {self.font_style} + }} + QPushButton:hover {{ + background: #45a049; + }} + """) + execute_button.clicked.connect(self.execute_cli_command) + command_layout.addWidget(execute_button) + + scroll_layout.addWidget(command_group) + + debug_helper_group = QGroupBox("Debug Helper") + debug_helper_group.setStyleSheet(f""" + QGroupBox {{ + color: white; + font-weight: bold; + border: 1px solid rgba(255, 255, 255, 0.1); + border-radius: 8px; + padding: 15px; + font-size: 10px; + margin-top: 15px; + {self.font_style} + }} + QGroupBox::title {{ + subcontrol-origin: margin; + left: 10px; + padding: 0 5px; + }} + """) + debug_helper_layout = QVBoxLayout(debug_helper_group) + + profile_label = QLabel("Select System-wide Profile:") + profile_label.setStyleSheet(f"color: white; font-size: 12px; {self.font_style}") + debug_helper_layout.addWidget(profile_label) + + self.debug_profile_selector = QComboBox() + self.debug_profile_selector.setStyleSheet(self.get_combobox_style()) + self.debug_profile_selector.currentTextChanged.connect(self.on_debug_profile_selected) + debug_helper_layout.addWidget(self.debug_profile_selector) + + self.ping_instruction_label = QLabel("") + self.ping_instruction_label.setStyleSheet(f"color: white; font-size: 12px; {self.font_style}") + self.ping_instruction_label.setWordWrap(True) + self.ping_instruction_label.hide() + debug_helper_layout.addWidget(self.ping_instruction_label) + + ping_buttons_layout = QHBoxLayout() + + self.copy_ping_button = QPushButton("Copy Ping Command") + self.copy_ping_button.setFixedSize(140, 40) + self.copy_ping_button.setStyleSheet(f""" + QPushButton {{ + font-size: 12px; + background: #007AFF; + color: white; + border: none; + border-radius: 5px; + font-weight: bold; + {self.font_style} + }} + QPushButton:hover {{ + background: #0056CC; + }} + QPushButton:disabled {{ + background: #666666; + }} + """) + self.copy_ping_button.clicked.connect(self.copy_ping_command) + self.copy_ping_button.setEnabled(False) + ping_buttons_layout.addWidget(self.copy_ping_button) + + self.test_ping_button = QPushButton("Test Ping") + self.test_ping_button.setFixedSize(120, 40) + self.test_ping_button.setStyleSheet(f""" + QPushButton {{ + font-size: 12px; + background: #4CAF50; + color: white; + border: none; + border-radius: 5px; + font-weight: bold; + {self.font_style} + }} + QPushButton:hover {{ + background: #45a049; + }} + QPushButton:disabled {{ + background: #666666; + }} + """) + self.test_ping_button.clicked.connect(self.test_ping_connection) + self.test_ping_button.setEnabled(False) + ping_buttons_layout.addWidget(self.test_ping_button) + + debug_helper_layout.addLayout(ping_buttons_layout) + + self.ping_result_label = QLabel("") + self.ping_result_label.setStyleSheet(f"color: white; font-size: 12px; font-weight: bold; {self.font_style}") + self.ping_result_label.setWordWrap(True) + self.ping_result_label.hide() + debug_helper_layout.addWidget(self.ping_result_label) + + self.update_debug_profile_list() + scroll_layout.addWidget(debug_helper_group) + + scroll_area.setWidget(scroll_content) + layout.addWidget(scroll_area) + + return page + + def copy_cli_command(self): + clipboard = QApplication.clipboard() + clipboard.setText(self.cli_command.text()) + self.update_status.update_status("CLI command copied to clipboard") + + def execute_cli_command(self): + try: + command = self.cli_command.text().split() + subprocess.Popen(command) + self.update_status.update_status("CLI command executed") + except Exception as e: + self.update_status.update_status(f"Error executing command: {str(e)}") + + def update_debug_profile_list(self): + self.debug_profile_selector.clear() + profiles = ProfileController.get_all() + system_profiles = {pid: profile for pid, profile in profiles.items() if isinstance(profile, SystemProfile)} + + if system_profiles: + for profile_id, profile in system_profiles.items(): + self.debug_profile_selector.addItem(f"Profile {profile_id}: {profile.name}", profile_id) + else: + self.debug_profile_selector.addItem("No system-wide profiles found", None) + + def on_debug_profile_selected(self, text): + profile_id = self.debug_profile_selector.currentData() + if profile_id is None: + self.ping_instruction_label.hide() + self.copy_ping_button.setEnabled(False) + self.test_ping_button.setEnabled(False) + self.ping_result_label.hide() + return + + profile = ProfileController.get(profile_id) + if profile and isinstance(profile, SystemProfile): + ip_address = self.extract_endpoint_ip(profile) + if ip_address: + self.ping_instruction_label.setText(f"Step 1, Can you Ping it? Copy-paste the command below into your terminal. This VPN Node's IP address is {ip_address}") + self.ping_instruction_label.show() + self.copy_ping_button.setEnabled(True) + self.test_ping_button.setEnabled(True) + self.ping_result_label.hide() + else: + self.ping_instruction_label.setText("Could not extract IP address from WireGuard configuration") + self.ping_instruction_label.show() + self.copy_ping_button.setEnabled(False) + self.test_ping_button.setEnabled(False) + self.ping_result_label.hide() + + def extract_endpoint_ip(self, profile): + try: + profile_path = Constants.HV_PROFILE_CONFIG_HOME + f'/{profile.id}' + wg_conf_path = f'{profile_path}/wg.conf.bak' + if not os.path.exists(wg_conf_path): + return None + + with open(wg_conf_path, 'r') as f: + content = f.read() + + for line in content.split('\n'): + if line.strip().startswith('Endpoint = '): + endpoint = line.strip().split(' = ')[1] + ip_address = endpoint.split(':')[0] + return ip_address + return None + except Exception: + return None + + def copy_ping_command(self): + profile_id = self.debug_profile_selector.currentData() + if profile_id is None: + return + + profile = ProfileController.get(profile_id) + if profile and isinstance(profile, SystemProfile): + ip_address = self.extract_endpoint_ip(profile) + if ip_address: + ping_command = f"ping {ip_address}" + clipboard = QApplication.clipboard() + clipboard.setText(ping_command) + self.update_status.update_status("Ping command copied to clipboard") + + def test_ping_connection(self): + profile_id = self.debug_profile_selector.currentData() + if profile_id is None: + return + + profile = ProfileController.get(profile_id) + if profile and isinstance(profile, SystemProfile): + ip_address = self.extract_endpoint_ip(profile) + if ip_address: + self.test_ping_button.setEnabled(False) + self.ping_result_label.setText("Testing ping...") + self.ping_result_label.setStyleSheet(f"color: #FFA500; font-size: 12px; font-weight: bold; {self.font_style}") + self.ping_result_label.show() + + def ping_test(): + try: + if sys.platform == "win32": + result = subprocess.run(['ping', '-n', '4', ip_address], + capture_output=True, text=True, timeout=10) + else: + result = subprocess.run(['ping', '-c', '4', ip_address], + capture_output=True, text=True, timeout=10) + if result.returncode == 0: + self.ping_result_label.setText("✅ Ping successful - Connection is working!") + self.ping_result_label.setStyleSheet(f"color: #4CAF50; font-size: 12px; font-weight: bold; {self.font_style}") + else: + self.ping_result_label.setText("❌ Ping failed - Connection issue detected") + self.ping_result_label.setStyleSheet(f"color: #F44336; font-size: 12px; font-weight: bold; {self.font_style}") + except subprocess.TimeoutExpired: + self.ping_result_label.setText("❌ Ping timeout - Connection issue detected") + self.ping_result_label.setStyleSheet(f"color: #F44336; font-size: 12px; font-weight: bold; {self.font_style}") + except Exception as e: + self.ping_result_label.setText(f"❌ Ping error: {str(e)}") + self.ping_result_label.setStyleSheet(f"color: #F44336; font-size: 12px; font-weight: bold; {self.font_style}") + finally: + self.test_ping_button.setEnabled(True) + + threading.Thread(target=ping_test, daemon=True).start() + def on_profile_selected(self, button): self.delete_button.setEnabled(True) self.selected_profile_id = self.profile_buttons.id(button) @@ -4903,6 +5272,10 @@ class Settings(Page): self.content_layout.setCurrentWidget(self.logs_page) self.update_button_states(4) + def show_debug_page(self): + self.content_layout.setCurrentWidget(self.debug_page) + self.update_button_states(5) + def update_button_states(self, active_index): for i, btn in enumerate(self.menu_buttons): btn.setChecked(i == active_index) @@ -4975,26 +5348,70 @@ class Settings(Page): ] for i, (label, key) in enumerate(info_items): - stat_widget = self.create_stat_widget(label, "N/A") - row, col = divmod(i, 2) - subscription_layout.addWidget(stat_widget, row, col) - - old_label = stat_widget.findChild(QLabel, "value_label") - if old_label: - old_label.setParent(None) - old_label.deleteLater() - - value_label = ClickableValueLabel("N/A", stat_widget) - value_label.setObjectName("value_label") - value_label.setStyleSheet(f"color: #00ffff; font-size: 12px; font-family: 'Retro Gaming', sans-serif;") - value_label.setAlignment(Qt.AlignmentFlag.AlignCenter) - stat_widget.layout().insertWidget(0, value_label) - - self.subscription_info[key] = value_label + if key == "billing_code": + billing_container = QWidget() + billing_layout = QVBoxLayout(billing_container) + billing_layout.setContentsMargins(0, 0, 0, 0) + billing_layout.setSpacing(5) + + stat_widget = self.create_stat_widget(label, "N/A") + billing_layout.addWidget(stat_widget) + + copy_button = QPushButton("Copy") + copy_button.setFixedSize(60, 30) + copy_button.setStyleSheet(f""" + QPushButton {{ + background: #007AFF; + color: white; + border: none; + border-radius: 4px; + font-size: 11px; + font-weight: bold; + {self.font_style} + }} + QPushButton:hover {{ + background: #0056CC; + }} + """) + copy_button.clicked.connect(lambda: self.copy_billing_code()) + billing_layout.addWidget(copy_button) + + row, col = divmod(i, 2) + subscription_layout.addWidget(billing_container, row, col) + + old_label = stat_widget.findChild(QLabel, "value_label") + if old_label: + old_label.setParent(None) + old_label.deleteLater() + + value_label = ClickableValueLabel("N/A", stat_widget) + value_label.setObjectName("value_label") + value_label.setStyleSheet(f"color: #00ffff; font-size: 12px; font-family: 'Retro Gaming', sans-serif;") + value_label.setAlignment(Qt.AlignmentFlag.AlignCenter) + stat_widget.layout().insertWidget(0, value_label) + + self.subscription_info[key] = value_label + else: + stat_widget = self.create_stat_widget(label, "N/A") + row, col = divmod(i, 2) + subscription_layout.addWidget(stat_widget, row, col) + + old_label = stat_widget.findChild(QLabel, "value_label") + if old_label: + old_label.setParent(None) + old_label.deleteLater() + + value_label = ClickableValueLabel("N/A", stat_widget) + value_label.setObjectName("value_label") + value_label.setStyleSheet(f"color: #00ffff; font-size: 12px; font-family: 'Retro Gaming', sans-serif;") + value_label.setAlignment(Qt.AlignmentFlag.AlignCenter) + stat_widget.layout().insertWidget(0, value_label) + + self.subscription_info[key] = value_label layout.addWidget(subscription_group) - clipboard_hint = QLabel("💡 Click on the billing code to copy it to clipboard") + clipboard_hint = QLabel("💡 Click on the billing code or use the Copy button to copy it to clipboard") clipboard_hint.setStyleSheet(f"color: #666666; font-size: 11px; font-style: italic; {self.font_style}") clipboard_hint.setAlignment(Qt.AlignmentFlag.AlignCenter) layout.addWidget(clipboard_hint) @@ -5032,6 +5449,16 @@ class Settings(Page): for label in self.subscription_info.values(): label.setText("N/A") + def copy_billing_code(self): + if "billing_code" in self.subscription_info: + billing_code = self.subscription_info["billing_code"].text() + if billing_code and billing_code != "N/A": + clipboard = QApplication.clipboard() + clipboard.setText(billing_code) + self.update_status.update_status("Billing code copied to clipboard") + else: + self.update_status.update_status("No billing code available to copy") + def showEvent(self, event): super().showEvent(event) @@ -5058,6 +5485,10 @@ class Settings(Page): self.logs_page = self.create_logs_page() self.content_layout.addWidget(self.logs_page) + self.content_layout.removeWidget(self.debug_page) + self.debug_page = self.create_debug_page() + self.content_layout.addWidget(self.debug_page) + self.content_layout.setCurrentIndex(current_index) if self.content_layout.currentWidget() == self.subscription_page: @@ -5252,12 +5683,14 @@ class Settings(Page): self.registrations_page = self.create_registrations_page() self.logs_page = self.create_logs_page() self.delete_page = self.create_delete_page() + self.debug_page = self.create_debug_page() self.content_layout.addWidget(self.account_page) self.content_layout.addWidget(self.subscription_page) self.content_layout.addWidget(self.registrations_page) self.content_layout.addWidget(self.logs_page) self.content_layout.addWidget(self.delete_page) + self.content_layout.addWidget(self.debug_page) self.show_account_page() @@ -6706,6 +7139,32 @@ class FastRegistrationPage(Page): 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) -> 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 + def showEvent(self, event): super().showEvent(event) self.initialize_default_selections() @@ -6729,6 +7188,7 @@ class FastRegistrationPage(Page): self.create_location_section() self.create_browser_section() self.create_resolution_section() + self.update_ui_state_for_connection() def create_protocol_section(self): label = QLabel("Protocol", self) @@ -6907,6 +7367,17 @@ class FastRegistrationPage(Page): next_button.setIconSize(next_button.size()) self.buttons.append(next_button) + 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'] @@ -6925,6 +7396,7 @@ class FastRegistrationPage(Page): 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 locations and self.selected_values[key] in locations: @@ -6967,6 +7439,7 @@ class FastRegistrationPage(Page): 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 locations and self.selected_values[key] in locations: @@ -7048,7 +7521,10 @@ class FastRegistrationPage(Page): resume_page = self.find_resume_page() if resume_page: - resume_page.handle_core_action_create_profile('CREATE_SESSION_PROFILE', profile_data_for_resume, 'session') + if profile_data['connection'] == 'system-wide': + resume_page.handle_core_action_create_profile('CREATE_SYSTEM_PROFILE', profile_data_for_resume, 'system') + else: + resume_page.handle_core_action_create_profile('CREATE_SESSION_PROFILE', profile_data_for_resume, 'session') def create_tor_profile(self, profile_data): location_info = self.connection_manager.get_location_info(profile_data['location']) @@ -7082,6 +7558,69 @@ class FastRegistrationPage(Page): return page return None +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() + self.page_stack.setCurrentIndex(self.page_stack.indexOf(self.page_stack.findChild(MenuPage))) + + 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()