From 5f4279b47561549ad3465cdd9f76f362d4293399 Mon Sep 17 00:00:00 2001 From: openhands Date: Mon, 23 Feb 2026 19:24:44 +0000 Subject: [PATCH 01/10] Add GitHub Actions workflow for Windows build Co-authored-by: openhands --- .github/workflows/build.yml | 63 +++++++++++++++++++++++++++++++++++++ 1 file changed, 63 insertions(+) create mode 100644 .github/workflows/build.yml diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml new file mode 100644 index 0000000..b932b10 --- /dev/null +++ b/.github/workflows/build.yml @@ -0,0 +1,63 @@ +name: Build Windows Executable + +on: + push: + branches: [ main ] + tags: + - 'v*' + pull_request: + branches: [ main ] + workflow_dispatch: + +jobs: + build: + runs-on: windows-latest + + steps: + - uses: actions/checkout@v4 + + - name: Set up Python + uses: actions/setup-python@v5 + with: + python-version: '3.11' + + - name: Install dependencies + run: | + python -m pip install --upgrade pip + pip install -r requirements.txt + pip install pyinstaller + + - name: Create version info + run: python build.py + continue-on-error: true + + - name: Build executable + run: | + pyinstaller --clean OpenGameBoost.spec + + - name: Upload artifact + uses: actions/upload-artifact@v4 + with: + name: OpenGameBoost-Windows + path: dist/OpenGameBoost.exe + retention-days: 30 + + release: + needs: build + runs-on: ubuntu-latest + if: startsWith(github.ref, 'refs/tags/') + + steps: + - name: Download artifact + uses: actions/download-artifact@v4 + with: + name: OpenGameBoost-Windows + + - name: Create Release + uses: softprops/action-gh-release@v1 + with: + files: OpenGameBoost.exe + draft: false + prerelease: false + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} From 8cdf2f202d37695b0be710454772d4317300f75b Mon Sep 17 00:00:00 2001 From: openhands Date: Tue, 24 Feb 2026 19:16:43 +0000 Subject: [PATCH 02/10] Address PR review comments: naming consistency and attribute guards 1. Rename suspend_background to should_suspend_background for consistency with other attributes (should_suspend_explorer, should_suspend_browsers, should_suspend_launchers) 2. Add System Info section to _create_main_ui so that _update_system_info actually displays data instead of being a no-op 3. Initialize all UI attributes from dead-code paths to None in __init__ and add guards in methods that reference them to prevent AttributeError: - mode_label, mode_indicator, boost_btn, game_status - memory_card, network_card, power_card, registry_card, game_card Co-authored-by: openhands --- main.py | 108 ++++++++++++++++++++++++++++-------- services/suspend_service.py | 6 +- 2 files changed, 87 insertions(+), 27 deletions(-) diff --git a/main.py b/main.py index c67a79e..0a57181 100644 --- a/main.py +++ b/main.py @@ -180,6 +180,17 @@ def __init__(self): self.power_label = None self.system_label = None + # Initialize UI attributes from dead-code paths (set to None to prevent AttributeError) + self.mode_label = None + self.mode_indicator = None + self.boost_btn = None + self.game_status = None + self.memory_card = None + self.network_card = None + self.power_card = None + self.registry_card = None + self.game_card = None + # Initialize services (Windows only) if os.name == 'nt': self.suspend_service = SuspendService() @@ -409,9 +420,38 @@ def _create_main_ui(self): desc="Activate when games launch" ) + # === SYSTEM INFO SECTION === + info_frame = ctk.CTkFrame(main_frame, fg_color="#12121e", corner_radius=10) + info_frame.pack(fill="x", pady=(15, 10)) + info_frame.grid_columnconfigure((0, 1, 2), weight=1) + + # Memory info + self.mem_label = ctk.CTkLabel( + info_frame, text="Memory: --", font=("Segoe UI", 10), + text_color="#888888" + ) + self.mem_label.grid(row=0, column=0, padx=10, pady=10) + + # Power info + self.power_label = ctk.CTkLabel( + info_frame, text="Power: --", font=("Segoe UI", 10), + text_color="#888888" + ) + self.power_label.grid(row=0, column=1, padx=10, pady=10) + + # System type + self.system_label = ctk.CTkLabel( + info_frame, text="System: --", font=("Segoe UI", 10), + text_color="#888888" + ) + self.system_label.grid(row=0, column=2, padx=10, pady=10) + + # Update system info + self._update_system_info() + # === FOOTER === footer = ctk.CTkFrame(main_frame, fg_color="transparent") - footer.pack(side="bottom", fill="x", pady=(20, 0)) + footer.pack(side="bottom", fill="x", pady=(10, 0)) # Copy specs button specs_btn = ctk.CTkButton( @@ -907,46 +947,58 @@ def _scan_games(self) -> bool: self.game_detector._check_games() games = self.game_detector.get_running_games() if games: - self.game_status.configure(text=f"Detected: {', '.join(games)}") + if self.game_status: + self.game_status.configure(text=f"Detected: {', '.join(games)}") self._set_game_mode(True) else: - self.game_status.configure(text="No games detected") + if self.game_status: + self.game_status.configure(text="No games detected") self._set_game_mode(False) return True def _boost_all(self): """Apply all optimizations.""" - self.boost_btn.configure(text="⏳ Boosting...", state="disabled") + if self.boost_btn: + self.boost_btn.configure(text="⏳ Boosting...", state="disabled") def run_boost(): try: results = [] if self.memory_service and self.memory_service.enabled: - self.memory_card.set_status("Optimizing...", "#ffaa00") + if self.memory_card: + self.memory_card.set_status("Optimizing...", "#ffaa00") results.append(self._optimize_memory()) - self.memory_card.set_status("Optimized", "#00ff88") + if self.memory_card: + self.memory_card.set_status("Optimized", "#00ff88") if self.network_service and self.network_service.enabled: - self.network_card.set_status("Optimizing...", "#ffaa00") + if self.network_card: + self.network_card.set_status("Optimizing...", "#ffaa00") results.append(self._optimize_network()) - self.network_card.set_status("Optimized", "#00ff88") + if self.network_card: + self.network_card.set_status("Optimized", "#00ff88") if self.power_service and self.power_service.enabled: - self.power_card.set_status("Optimizing...", "#ffaa00") + if self.power_card: + self.power_card.set_status("Optimizing...", "#ffaa00") results.append(self._optimize_power()) - self.power_card.set_status("Optimized", "#00ff88") + if self.power_card: + self.power_card.set_status("Optimized", "#00ff88") if self.registry_service and self.registry_service.enabled: - self.registry_card.set_status("Optimizing...", "#ffaa00") + if self.registry_card: + self.registry_card.set_status("Optimizing...", "#ffaa00") results.append(self._optimize_registry()) - self.registry_card.set_status("Optimized", "#00ff88") + if self.registry_card: + self.registry_card.set_status("Optimized", "#00ff88") self._set_game_mode(True) logger.info("All optimizations applied") finally: - self.root.after(0, lambda: self.boost_btn.configure( - text="🚀 BOOST NOW", state="normal" - )) + if self.boost_btn: + self.root.after(0, lambda: self.boost_btn.configure( + text="🚀 BOOST NOW", state="normal" + )) threading.Thread(target=run_boost, daemon=True).start() @@ -963,7 +1015,8 @@ def _restore_all(self): # Reset card statuses for card in [self.memory_card, self.network_card, self.power_card, self.registry_card, self.game_card]: - card.set_status("Ready", "#00ff88") + if card: + card.set_status("Ready", "#00ff88") logger.info("All settings restored to defaults") @@ -971,11 +1024,15 @@ def _set_game_mode(self, active: bool): """Set the game mode indicator.""" self.game_mode_active = active if active: - self.mode_label.configure(text="GAME MODE ACTIVE", text_color="#00ff88") - self.mode_indicator.configure(text_color="#00ff88") + if self.mode_label: + self.mode_label.configure(text="GAME MODE ACTIVE", text_color="#00ff88") + if self.mode_indicator: + self.mode_indicator.configure(text_color="#00ff88") else: - self.mode_label.configure(text="STANDBY MODE", text_color="#666666") - self.mode_indicator.configure(text_color="#666666") + if self.mode_label: + self.mode_label.configure(text="STANDBY MODE", text_color="#666666") + if self.mode_indicator: + self.mode_indicator.configure(text_color="#666666") def _update_system_info(self): """Update the system information display.""" @@ -1006,8 +1063,9 @@ def _on_game_detected(self, game_name: str): def _handle_game_detected(self, game_name: str): """Handle game detection on main thread.""" - self.game_status.configure(text=f"🎮 Playing: {game_name}") - if self.game_detector.auto_optimize: + if self.game_status: + self.game_status.configure(text=f"🎮 Playing: {game_name}") + if self.game_detector and self.game_detector.auto_optimize: self._boost_all() def _on_game_closed(self, game_name: str): @@ -1019,9 +1077,11 @@ def _handle_game_closed(self, game_name: str): """Handle game closed on main thread.""" games = self.game_detector.get_running_games() if self.game_detector else [] if games: - self.game_status.configure(text=f"Detected: {', '.join(games)}") + if self.game_status: + self.game_status.configure(text=f"Detected: {', '.join(games)}") else: - self.game_status.configure(text="No games detected") + if self.game_status: + self.game_status.configure(text="No games detected") self._restore_all() def _open_github(self): diff --git a/services/suspend_service.py b/services/suspend_service.py index bf2a88b..cf0f8d5 100644 --- a/services/suspend_service.py +++ b/services/suspend_service.py @@ -61,7 +61,7 @@ def __init__(self): self.should_suspend_explorer = True self.should_suspend_browsers = True self.should_suspend_launchers = True - self.suspend_background = False # Optional, off by default + self.should_suspend_background = False # Optional, off by default self._suspended_pids: Set[int] = set() self._explorer_pid: Optional[int] = None @@ -284,7 +284,7 @@ def activate_game_mode(self) -> Dict[str, any]: launcher_results = self.suspend_launchers() results["launchers_suspended"] = launcher_results["suspended"] - if self.suspend_background: + if self.should_suspend_background: bg_results = self.suspend_background_apps() results["background_suspended"] = bg_results["suspended"] @@ -372,6 +372,6 @@ def get_status(self) -> Dict[str, any]: "suspend_explorer": self.should_suspend_explorer, "suspend_browsers": self.should_suspend_browsers, "suspend_launchers": self.should_suspend_launchers, - "suspend_background": self.suspend_background, + "suspend_background": self.should_suspend_background, } } From 5b7ce6cbbcfb9a429c7419b2af81dbf19845a230 Mon Sep 17 00:00:00 2001 From: openhands Date: Tue, 24 Feb 2026 19:24:41 +0000 Subject: [PATCH 03/10] Address additional PR review comments 1. Update GitHub workflow to use softprops/action-gh-release@v2 - Added explicit 'permissions: contents: write' block - Changed from 'env: GITHUB_TOKEN' to 'with: token:' pattern 2. Fix thread-unsafe tkinter widget updates - Added set_status_threadsafe() to ServiceCard that uses self.after() - Fixed _boost_all() to marshal all widget updates via root.after() - Fixed _set_game_mode() call to run on main thread 3. Fix critical safety issue in _on_close() - Now calls deactivate_game_mode() to resume suspended processes - Prevents explorer.exe, browsers, launchers staying frozen after app close Co-authored-by: openhands --- .github/workflows/build.yml | 7 ++--- main.py | 52 +++++++++++++++++++++++-------------- 2 files changed, 36 insertions(+), 23 deletions(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index b932b10..7fdcb9c 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -46,6 +46,8 @@ jobs: needs: build runs-on: ubuntu-latest if: startsWith(github.ref, 'refs/tags/') + permissions: + contents: write steps: - name: Download artifact @@ -54,10 +56,9 @@ jobs: name: OpenGameBoost-Windows - name: Create Release - uses: softprops/action-gh-release@v1 + uses: softprops/action-gh-release@v2 with: files: OpenGameBoost.exe draft: false prerelease: false - env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + token: ${{ secrets.GITHUB_TOKEN }} diff --git a/main.py b/main.py index 0a57181..55999e3 100644 --- a/main.py +++ b/main.py @@ -145,12 +145,12 @@ def _run_optimization(self): if self.on_optimize: result = self.on_optimize() if result: - self.set_status("Optimized", "#00ff88") + self.set_status_threadsafe("Optimized", "#00ff88") else: - self.set_status("Partial", "#ffaa00") + self.set_status_threadsafe("Partial", "#ffaa00") except Exception as e: logger.error(f"Optimization error: {e}") - self.set_status("Error", "#ff4444") + self.set_status_threadsafe("Error", "#ff4444") def _update_status(self): if self.enabled: @@ -161,9 +161,15 @@ def _update_status(self): self.optimize_btn.configure(state="disabled") def set_status(self, status: str, color: str = "#00ff88"): + """Set status - call from main thread only.""" if CTK_AVAILABLE: self.status = status self.status_label.configure(text=f"● {status}", text_color=color) + + def set_status_threadsafe(self, status: str, color: str = "#00ff88"): + """Set status from any thread - marshals to main thread.""" + if CTK_AVAILABLE: + self.after(0, lambda: self.set_status(status, color)) class OpenGameBoostApp: @@ -961,38 +967,36 @@ def _boost_all(self): if self.boost_btn: self.boost_btn.configure(text="⏳ Boosting...", state="disabled") + def update_card_status(card, status, color): + """Helper to update card status on main thread.""" + if card: + self.root.after(0, lambda: card.set_status(status, color)) + def run_boost(): try: results = [] if self.memory_service and self.memory_service.enabled: - if self.memory_card: - self.memory_card.set_status("Optimizing...", "#ffaa00") + update_card_status(self.memory_card, "Optimizing...", "#ffaa00") results.append(self._optimize_memory()) - if self.memory_card: - self.memory_card.set_status("Optimized", "#00ff88") + update_card_status(self.memory_card, "Optimized", "#00ff88") if self.network_service and self.network_service.enabled: - if self.network_card: - self.network_card.set_status("Optimizing...", "#ffaa00") + update_card_status(self.network_card, "Optimizing...", "#ffaa00") results.append(self._optimize_network()) - if self.network_card: - self.network_card.set_status("Optimized", "#00ff88") + update_card_status(self.network_card, "Optimized", "#00ff88") if self.power_service and self.power_service.enabled: - if self.power_card: - self.power_card.set_status("Optimizing...", "#ffaa00") + update_card_status(self.power_card, "Optimizing...", "#ffaa00") results.append(self._optimize_power()) - if self.power_card: - self.power_card.set_status("Optimized", "#00ff88") + update_card_status(self.power_card, "Optimized", "#00ff88") if self.registry_service and self.registry_service.enabled: - if self.registry_card: - self.registry_card.set_status("Optimizing...", "#ffaa00") + update_card_status(self.registry_card, "Optimizing...", "#ffaa00") results.append(self._optimize_registry()) - if self.registry_card: - self.registry_card.set_status("Optimized", "#00ff88") + update_card_status(self.registry_card, "Optimized", "#00ff88") - self._set_game_mode(True) + # Marshal _set_game_mode to main thread + self.root.after(0, lambda: self._set_game_mode(True)) logger.info("All optimizations applied") finally: if self.boost_btn: @@ -1110,6 +1114,14 @@ def update_loop(): def _on_close(self): """Handle application close.""" + # Resume any suspended processes before closing + if self.suspend_service: + try: + self.suspend_service.deactivate_game_mode() + logger.info("Resumed suspended processes on app close") + except Exception as e: + logger.error(f"Failed to resume suspended processes: {e}") + if self.game_detector: self.game_detector.stop() self.config.save() From 4156c47c5764f3a58711ddca53f77b92841e54f8 Mon Sep 17 00:00:00 2001 From: openhands Date: Tue, 24 Feb 2026 19:32:04 +0000 Subject: [PATCH 04/10] Fix thread-unsafe _update_system_info calls in optimization methods Marshal _update_system_info() to main thread in: - _optimize_memory(): line 921 - _optimize_power(): line 938 These methods can be called from background threads via run_boost in _boost_all, so widget updates must go through root.after(). Co-authored-by: openhands --- main.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/main.py b/main.py index 55999e3..486f47e 100644 --- a/main.py +++ b/main.py @@ -918,7 +918,7 @@ def _optimize_memory(self) -> bool: return False result = self.memory_service.optimize_memory() logger.info(f"Memory optimization: {result}") - self._update_system_info() + self.root.after(0, self._update_system_info) return result.get("status") == "completed" def _optimize_network(self) -> bool: @@ -935,7 +935,7 @@ def _optimize_power(self) -> bool: return False result = self.power_service.optimize_power_settings() logger.info(f"Power optimization: {result}") - self._update_system_info() + self.root.after(0, self._update_system_info) return result.get("status") == "completed" def _optimize_registry(self) -> bool: From 2c7226966763937943d57edc2514259c8a1f8a74 Mon Sep 17 00:00:00 2001 From: openhands Date: Tue, 24 Feb 2026 19:35:49 +0000 Subject: [PATCH 05/10] Remove dead code and fix game detector config/UI default inconsistency 1. Remove dead-code methods that were never called: - _create_header() (lines 692-737) - _create_main_content() (lines 739-874) - _create_footer() (lines 876-893) These methods created UI attributes (mode_label, mode_indicator, boost_btn, game_status, memory_card, network_card, power_card, registry_card, game_card) that were guarded with null checks but never actually created since the active UI path uses _create_main_ui() instead. 2. Fix game detector config/UI default inconsistency: - Changed config.get('game_detector', 'enabled', True) to default False - Both in _configure_services() and run() - Now matches UI toggle (game_detect_var) which defaults to False - Prevents game detector from auto-starting on first launch when UI shows it as disabled Co-authored-by: openhands --- main.py | 209 +------------------------------------------------------- 1 file changed, 3 insertions(+), 206 deletions(-) diff --git a/main.py b/main.py index 486f47e..a15948f 100644 --- a/main.py +++ b/main.py @@ -226,7 +226,7 @@ def __init__(self): def _configure_services(self): """Configure services from saved config.""" if self.game_detector: - self.game_detector.enabled = self.config.get("game_detector", "enabled", True) + self.game_detector.enabled = self.config.get("game_detector", "enabled", False) self.game_detector.auto_optimize = self.config.get("game_detector", "auto_optimize", True) if self.memory_service: @@ -689,209 +689,6 @@ def _copy_specs(self): except Exception as e: logger.error(f"Failed to copy specs: {e}") - def _create_header(self): - """Create the header section with logo and status.""" - header = ctk.CTkFrame(self.root, height=100, fg_color="#0f0f1a") - header.grid(row=0, column=0, sticky="ew", padx=0, pady=0) - header.grid_columnconfigure(1, weight=1) - - # Logo and title - logo_frame = ctk.CTkFrame(header, fg_color="transparent") - logo_frame.grid(row=0, column=0, padx=20, pady=15, sticky="w") - - # Logo icon (stylized) - logo = ctk.CTkLabel( - logo_frame, text="🎮", font=("Segoe UI", 36) - ) - logo.pack(side="left", padx=(0, 15)) - - title_frame = ctk.CTkFrame(logo_frame, fg_color="transparent") - title_frame.pack(side="left") - - title = ctk.CTkLabel( - title_frame, text="OpenGameBoost", - font=("Segoe UI", 24, "bold"), text_color="#ffffff" - ) - title.pack(anchor="w") - - subtitle = ctk.CTkLabel( - title_frame, text="Open Source Gaming Optimizer", - font=("Segoe UI", 11), text_color="#888888" - ) - subtitle.pack(anchor="w") - - # Status indicator - status_frame = ctk.CTkFrame(header, fg_color="transparent") - status_frame.grid(row=0, column=1, padx=20, pady=15, sticky="e") - - self.mode_label = ctk.CTkLabel( - status_frame, text="STANDBY MODE", - font=("Segoe UI", 12, "bold"), text_color="#666666" - ) - self.mode_label.pack(side="right", padx=10) - - self.mode_indicator = ctk.CTkLabel( - status_frame, text="●", font=("Segoe UI", 20), - text_color="#666666" - ) - self.mode_indicator.pack(side="right") - - def _create_main_content(self): - """Create the main content area with service cards.""" - # Create scrollable frame for content - main_frame = ctk.CTkScrollableFrame( - self.root, fg_color="#0a0a14", - scrollbar_button_color="#333344", - scrollbar_button_hover_color="#444455" - ) - main_frame.grid(row=1, column=0, sticky="nsew", padx=0, pady=0) - main_frame.grid_columnconfigure((0, 1), weight=1) - - # Quick Actions Section - quick_section = ctk.CTkFrame(main_frame, fg_color="transparent") - quick_section.grid(row=0, column=0, columnspan=2, sticky="ew", padx=20, pady=(20, 10)) - - section_title = ctk.CTkLabel( - quick_section, text="⚡ Quick Actions", - font=("Segoe UI", 14, "bold"), text_color="#ffffff" - ) - section_title.pack(anchor="w", pady=(0, 10)) - - # Quick action buttons - btn_frame = ctk.CTkFrame(quick_section, fg_color="transparent") - btn_frame.pack(fill="x") - - self.boost_btn = ctk.CTkButton( - btn_frame, text="🚀 BOOST NOW", width=200, height=50, - font=("Segoe UI", 14, "bold"), fg_color="#00d4ff", - hover_color="#00a8cc", text_color="#000000", - command=self._boost_all - ) - self.boost_btn.pack(side="left", padx=(0, 10)) - - restore_btn = ctk.CTkButton( - btn_frame, text="↩ Restore Defaults", width=150, height=50, - font=("Segoe UI", 12), fg_color="#333344", - hover_color="#444455", text_color="#ffffff", - command=self._restore_all - ) - restore_btn.pack(side="left", padx=(0, 10)) - - # Game Detection Status - self.game_status = ctk.CTkLabel( - btn_frame, text="No games detected", - font=("Segoe UI", 11), text_color="#666666" - ) - self.game_status.pack(side="right", padx=10) - - # Services Section - services_title = ctk.CTkLabel( - main_frame, text="🔧 Optimization Services", - font=("Segoe UI", 14, "bold"), text_color="#ffffff" - ) - services_title.grid(row=1, column=0, columnspan=2, sticky="w", padx=20, pady=(20, 10)) - - # Service Cards - self.memory_card = ServiceCard( - main_frame, "Memory Optimizer", - "Flushes unused memory from processes to free up RAM for your games.", - icon="💾", - on_toggle=lambda e: self._toggle_service("memory", e), - on_optimize=self._optimize_memory - ) - self.memory_card.grid(row=2, column=0, sticky="nsew", padx=(20, 10), pady=10) - - self.network_card = ServiceCard( - main_frame, "Network Optimizer", - "Disables Nagle's algorithm and NetBIOS to reduce network latency.", - icon="🌐", - on_toggle=lambda e: self._toggle_service("network", e), - on_optimize=self._optimize_network - ) - self.network_card.grid(row=2, column=1, sticky="nsew", padx=(10, 20), pady=10) - - self.power_card = ServiceCard( - main_frame, "Power Optimizer", - "Switches to High Performance power plan for maximum CPU/GPU performance.", - icon="⚡", - on_toggle=lambda e: self._toggle_service("power", e), - on_optimize=self._optimize_power - ) - self.power_card.grid(row=3, column=0, sticky="nsew", padx=(20, 10), pady=10) - - self.registry_card = ServiceCard( - main_frame, "GPU & System Tweaks", - "Applies registry optimizations for GPU priority and gaming performance.", - icon="🎮", - on_toggle=lambda e: self._toggle_service("registry", e), - on_optimize=self._optimize_registry - ) - self.registry_card.grid(row=3, column=1, sticky="nsew", padx=(10, 20), pady=10) - - # Game Detection Card - self.game_card = ServiceCard( - main_frame, "Game Detector", - "Automatically detects running games and applies optimizations.", - icon="🎯", - on_toggle=lambda e: self._toggle_service("game_detector", e), - on_optimize=self._scan_games - ) - self.game_card.grid(row=4, column=0, columnspan=2, sticky="ew", padx=20, pady=10) - - # System Info Section - info_title = ctk.CTkLabel( - main_frame, text="📊 System Status", - font=("Segoe UI", 14, "bold"), text_color="#ffffff" - ) - info_title.grid(row=5, column=0, columnspan=2, sticky="w", padx=20, pady=(20, 10)) - - info_frame = ctk.CTkFrame(main_frame, fg_color="#1a1a2e", corner_radius=10) - info_frame.grid(row=6, column=0, columnspan=2, sticky="ew", padx=20, pady=(0, 20)) - info_frame.grid_columnconfigure((0, 1, 2), weight=1) - - # Memory info - self.mem_label = ctk.CTkLabel( - info_frame, text="Memory: --", font=("Segoe UI", 12), - text_color="#ffffff" - ) - self.mem_label.grid(row=0, column=0, padx=20, pady=15) - - # Power info - self.power_label = ctk.CTkLabel( - info_frame, text="Power: --", font=("Segoe UI", 12), - text_color="#ffffff" - ) - self.power_label.grid(row=0, column=1, padx=20, pady=15) - - # System type - self.system_label = ctk.CTkLabel( - info_frame, text="System: --", font=("Segoe UI", 12), - text_color="#ffffff" - ) - self.system_label.grid(row=0, column=2, padx=20, pady=15) - - # Update system info - self._update_system_info() - - def _create_footer(self): - """Create the footer with version and links.""" - footer = ctk.CTkFrame(self.root, height=40, fg_color="#0f0f1a") - footer.grid(row=2, column=0, sticky="ew") - - version_label = ctk.CTkLabel( - footer, text=f"v{self.VERSION} | Open Source | MIT License", - font=("Segoe UI", 10), text_color="#555555" - ) - version_label.pack(side="left", padx=20, pady=10) - - github_btn = ctk.CTkButton( - footer, text="⭐ GitHub", width=80, height=25, - font=("Segoe UI", 10), fg_color="transparent", - hover_color="#333344", text_color="#00d4ff", - command=self._open_github - ) - github_btn.pack(side="right", padx=20, pady=10) - def _toggle_service(self, service: str, enabled: bool): """Toggle a service on/off.""" self.config.set(service, "enabled", enabled) @@ -1095,8 +892,8 @@ def _open_github(self): def run(self): """Run the application.""" - # Start game detector if enabled - if self.game_detector and self.config.get("game_detector", "enabled", True): + # Start game detector if enabled (default False to match UI toggle) + if self.game_detector and self.config.get("game_detector", "enabled", False): self.game_detector.start() # Update system info periodically From 7bd90375aec6945f3bcdef4c06d387735749012f Mon Sep 17 00:00:00 2001 From: openhands Date: Tue, 24 Feb 2026 19:43:23 +0000 Subject: [PATCH 06/10] Fix game_detector.enabled default in config.py (source of truth) Changed DEFAULT_CONFIG['game_detector']['enabled'] from True to False in config.py line 19. This is the actual source of truth for config defaults - the fallback values in main.py are now consistent but redundant. This ensures on fresh install the game detector is disabled by default, matching the UI toggle (game_detect_var) which also defaults to False. Co-authored-by: openhands --- config.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/config.py b/config.py index b26c308..5c04f29 100644 --- a/config.py +++ b/config.py @@ -16,7 +16,7 @@ "check_updates": True, }, "game_detector": { - "enabled": True, + "enabled": False, "auto_optimize": True, "check_interval": 5, }, From 75036b99ebb4374f83b0aba51efed096cd1a33ee Mon Sep 17 00:00:00 2001 From: openhands Date: Tue, 24 Feb 2026 19:48:00 +0000 Subject: [PATCH 07/10] Fix _on_game_mode_error to reset game_mode_active state Added self.game_mode_active = False in _on_game_mode_error() to ensure consistent state after errors. Previously, the button text would reset to 'ACTIVATE GAME MODE' but game_mode_active remained True, causing _toggle_game_mode to incorrectly branch into _deactivate_game_mode on the next click. Co-authored-by: openhands --- main.py | 1 + 1 file changed, 1 insertion(+) diff --git a/main.py b/main.py index a15948f..b015399 100644 --- a/main.py +++ b/main.py @@ -645,6 +645,7 @@ def _on_game_mode_deactivated(self): def _on_game_mode_error(self): """Called when an error occurs.""" + self.game_mode_active = False self.game_mode_btn.configure( text="ACTIVATE GAME MODE", fg_color="#00d4ff", From 853616cf4cf75fd9352144120fe0d725294bb777 Mon Sep 17 00:00:00 2001 From: bensheed Date: Tue, 24 Feb 2026 19:57:40 +0000 Subject: [PATCH 08/10] Restore power plan and network settings on app close when game mode is active --- main.py | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/main.py b/main.py index b015399..5d3366c 100644 --- a/main.py +++ b/main.py @@ -920,6 +920,18 @@ def _on_close(self): except Exception as e: logger.error(f"Failed to resume suspended processes: {e}") + if self.game_mode_active: + if self.power_service: + try: + self.power_service.restore_power_plan() + except Exception as e: + logger.error(f"Failed to restore power plan: {e}") + if self.network_service: + try: + self.network_service.restore_network() + except Exception as e: + logger.error(f"Failed to restore network settings: {e}") + if self.game_detector: self.game_detector.stop() self.config.save() From c080e9df5ebbbe1c4c72096934936a4c82ed0b50 Mon Sep 17 00:00:00 2001 From: openhands Date: Tue, 24 Feb 2026 20:02:20 +0000 Subject: [PATCH 09/10] Fix shallow copy of DEFAULT_CONFIG to prevent mutation Added _deep_copy_defaults() helper that uses json.loads(json.dumps()) to create a true deep copy of DEFAULT_CONFIG. This prevents mutations to self.config from affecting the module-level DEFAULT_CONFIG dict. Fixed three locations: - Line 78: load() when creating new config file - Line 84: load() fallback on error - Line 128: reset_to_defaults() Previously, since DEFAULT_CONFIG contains nested dicts, the shallow copy meant that modifying self.config['game_detector'] etc. would mutate the original DEFAULT_CONFIG, causing subsequent resets to return modified values instead of true defaults. Co-authored-by: openhands --- config.py | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/config.py b/config.py index 5c04f29..f52091a 100644 --- a/config.py +++ b/config.py @@ -60,6 +60,10 @@ def __init__(self, config_path: str = None): self.config: Dict[str, Any] = {} self.load() + def _deep_copy_defaults(self) -> Dict[str, Any]: + """Create a deep copy of DEFAULT_CONFIG to prevent mutation.""" + return json.loads(json.dumps(DEFAULT_CONFIG)) + def load(self) -> bool: """Load configuration from file.""" try: @@ -71,13 +75,13 @@ def load(self) -> bool: logger.info(f"Configuration loaded from {self.config_path}") return True else: - self.config = DEFAULT_CONFIG.copy() + self.config = self._deep_copy_defaults() self.save() logger.info("Created default configuration") return True except Exception as e: logger.error(f"Error loading configuration: {e}") - self.config = DEFAULT_CONFIG.copy() + self.config = self._deep_copy_defaults() return False def save(self) -> bool: @@ -121,5 +125,5 @@ def set(self, section: str, key: str, value: Any): def reset_to_defaults(self): """Reset all configuration to defaults.""" - self.config = DEFAULT_CONFIG.copy() + self.config = self._deep_copy_defaults() self.save() From cbd5a12f8fe62a073b5bb32cc1b9d9cf722c5592 Mon Sep 17 00:00:00 2001 From: bensheed Date: Tue, 24 Feb 2026 21:12:50 +0000 Subject: [PATCH 10/10] Fix _merge_defaults to use deep copy of DEFAULT_CONFIG to prevent mutation --- config.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/config.py b/config.py index f52091a..204feb9 100644 --- a/config.py +++ b/config.py @@ -106,7 +106,7 @@ def merge_dict(base: dict, override: dict) -> dict: result[key] = value return result - self.config = merge_dict(DEFAULT_CONFIG, self.config) + self.config = merge_dict(self._deep_copy_defaults(), self.config) def get(self, section: str, key: str = None, default: Any = None) -> Any: """Get a configuration value."""