diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index ba3f2f4..7fdcb9c 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -41,3 +41,24 @@ jobs: name: OpenGameBoost-Windows path: dist/OpenGameBoost.exe retention-days: 30 + + release: + needs: build + runs-on: ubuntu-latest + if: startsWith(github.ref, 'refs/tags/') + permissions: + contents: write + + steps: + - name: Download artifact + uses: actions/download-artifact@v4 + with: + name: OpenGameBoost-Windows + + - name: Create Release + uses: softprops/action-gh-release@v2 + with: + files: OpenGameBoost.exe + draft: false + prerelease: false + token: ${{ secrets.GITHUB_TOKEN }} diff --git a/config.py b/config.py index b26c308..204feb9 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, }, @@ -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: @@ -102,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.""" @@ -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() diff --git a/main.py b/main.py index c67a79e..5d3366c 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: @@ -180,6 +186,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() @@ -209,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: @@ -409,9 +426,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( @@ -599,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", @@ -643,209 +690,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) @@ -872,7 +716,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: @@ -889,7 +733,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: @@ -907,46 +751,56 @@ 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 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: - self.memory_card.set_status("Optimizing...", "#ffaa00") + update_card_status(self.memory_card, "Optimizing...", "#ffaa00") results.append(self._optimize_memory()) - self.memory_card.set_status("Optimized", "#00ff88") + update_card_status(self.memory_card, "Optimized", "#00ff88") if self.network_service and self.network_service.enabled: - self.network_card.set_status("Optimizing...", "#ffaa00") + update_card_status(self.network_card, "Optimizing...", "#ffaa00") results.append(self._optimize_network()) - self.network_card.set_status("Optimized", "#00ff88") + update_card_status(self.network_card, "Optimized", "#00ff88") if self.power_service and self.power_service.enabled: - self.power_card.set_status("Optimizing...", "#ffaa00") + update_card_status(self.power_card, "Optimizing...", "#ffaa00") results.append(self._optimize_power()) - self.power_card.set_status("Optimized", "#00ff88") + update_card_status(self.power_card, "Optimized", "#00ff88") if self.registry_service and self.registry_service.enabled: - self.registry_card.set_status("Optimizing...", "#ffaa00") + update_card_status(self.registry_card, "Optimizing...", "#ffaa00") results.append(self._optimize_registry()) - 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: - 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 +817,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 +826,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 +865,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 +879,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): @@ -1031,8 +893,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 @@ -1050,6 +912,26 @@ 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_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() 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, } }