Last active
June 1, 2025 16:46
-
-
Save evanesoteric/1cce652264a43a55e29ec7dc94a2a972 to your computer and use it in GitHub Desktop.
Autofocus the HorizonXI game window when gamepad input is detected. A Final Fantasy XI (FFXI) gaming script.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| #!/usr/bin/env python3 | |
| import evdev | |
| import time | |
| import subprocess | |
| import sys | |
| import threading | |
| # ==================================================================== | |
| # CONFIGURATION - MODIFY THIS SECTION FOR YOUR SETUP | |
| # ==================================================================== | |
| USERNAME = "evanesoteric" # Change this to your username (will be auto-capitalized) | |
| # ==================================================================== | |
| class GamepadAutoFocus: | |
| def __init__(self, cooldown_seconds=10): | |
| self.cooldown_seconds = cooldown_seconds | |
| self.is_monitoring = True | |
| self.last_focus_time = 0 | |
| self.device = None | |
| self.target_window = None | |
| self.target_window_name = USERNAME.capitalize() # Capitalize the username | |
| print(f"Looking for window named: '{self.target_window_name}'") | |
| print(f"(Based on username: '{USERNAME}')\n") | |
| def find_gamesir(self): | |
| devices = [evdev.InputDevice(path) for path in evdev.list_devices()] | |
| for device in devices: | |
| if "GameSir G7 SE" in device.name: | |
| return device.path | |
| return None | |
| def get_target_window(self): | |
| """Get the target window ID based on the configured username""" | |
| try: | |
| # Get horizon-loader PID | |
| result = subprocess.run(['pgrep', '-f', 'horizon-loader'], | |
| capture_output=True, text=True) | |
| if result.returncode != 0: | |
| return None | |
| horizon_pid = result.stdout.strip() | |
| print(f"horizon-loader PID: {horizon_pid}") | |
| # Get all windows for this PID | |
| result = subprocess.run(['xdotool', 'search', '--pid', horizon_pid], | |
| capture_output=True, text=True) | |
| if result.returncode != 0: | |
| return None | |
| window_ids = result.stdout.strip().split('\n') | |
| print(f"Found {len(window_ids)} windows for horizon-loader:") | |
| target_window = None | |
| for i, window_id in enumerate(window_ids, 1): | |
| try: | |
| name_result = subprocess.run(['xdotool', 'getwindowname', window_id], | |
| capture_output=True, text=True) | |
| window_name = name_result.stdout.strip() if name_result.returncode == 0 else "No name" | |
| print(f" Window {i}: {window_id} = '{window_name}'") | |
| if window_name == self.target_window_name: | |
| target_window = window_id | |
| print(f" ★ This is the target game window!") | |
| except Exception as e: | |
| print(f" Window {i}: {window_id} = Error: {e}") | |
| return target_window | |
| except Exception as e: | |
| print(f"Error: {e}") | |
| return None | |
| def is_window_focused(self): | |
| """Check if the target window is currently focused""" | |
| try: | |
| result = subprocess.run(['xdotool', 'getactivewindow'], | |
| capture_output=True, text=True) | |
| if result.returncode == 0: | |
| active_window = result.stdout.strip() | |
| return active_window == self.target_window | |
| except: | |
| pass | |
| return False | |
| def focus_window(self): | |
| """Focus the target window and start cooldown""" | |
| try: | |
| subprocess.run(['xdotool', 'windowfocus', self.target_window], | |
| stderr=subprocess.DEVNULL) | |
| current_time = time.time() | |
| self.last_focus_time = current_time | |
| print(f"🎮 Focused {self.target_window_name} window - entering {self.cooldown_seconds}s cooldown") | |
| # Start cooldown timer in a separate thread | |
| threading.Thread(target=self.cooldown_timer, daemon=True).start() | |
| # Stop monitoring temporarily | |
| self.is_monitoring = False | |
| except Exception as e: | |
| print(f"Error focusing window: {e}") | |
| def cooldown_timer(self): | |
| """Handle the cooldown period""" | |
| time.sleep(self.cooldown_seconds) | |
| self.is_monitoring = True | |
| print(f"✅ Cooldown ended - monitoring gamepad again") | |
| def monitor_gamepad(self): | |
| """Monitor gamepad input with smart focusing""" | |
| print(f"Monitoring gamepad - will focus game window when you use it") | |
| print(f"After focusing, will wait {self.cooldown_seconds} seconds before monitoring again") | |
| print("Press Ctrl+C to stop\n") | |
| event_count = 0 | |
| significant_input_threshold = 3 # Require multiple inputs to avoid accidental triggers | |
| try: | |
| for event in self.device.read_loop(): | |
| # Only process events when monitoring is enabled | |
| if not self.is_monitoring: | |
| continue | |
| # Only respond to button presses and significant stick movements | |
| if event.type == evdev.ecodes.EV_KEY and event.value == 1: | |
| # Button press | |
| event_count += 1 | |
| elif event.type == evdev.ecodes.EV_ABS and abs(event.value) > 10000: | |
| # Significant analog stick/trigger movement | |
| event_count += 1 | |
| else: | |
| continue | |
| # Check if we have enough significant input to warrant focusing | |
| if event_count >= significant_input_threshold: | |
| # Check if window is already focused to avoid unnecessary focusing | |
| if not self.is_window_focused(): | |
| self.focus_window() | |
| else: | |
| print("🎯 Game window already focused - entering cooldown anyway") | |
| self.is_monitoring = False | |
| threading.Thread(target=self.cooldown_timer, daemon=True).start() | |
| event_count = 0 # Reset counter | |
| # Reset counter if no significant input for a while | |
| current_time = time.time() | |
| if hasattr(self, '_last_input_time'): | |
| if current_time - self._last_input_time > 2.0: # 2 second timeout | |
| event_count = 0 | |
| self._last_input_time = current_time | |
| except KeyboardInterrupt: | |
| print("\nStopped monitoring gamepad") | |
| def main(): | |
| # Parse command line arguments for cooldown time | |
| cooldown_time = 10 # Default 10 seconds | |
| if len(sys.argv) > 1: | |
| try: | |
| cooldown_time = int(sys.argv[1]) | |
| print(f"Using custom cooldown time: {cooldown_time} seconds") | |
| except ValueError: | |
| print(f"Invalid cooldown time '{sys.argv[1]}', using default: 10 seconds") | |
| autofocus = GamepadAutoFocus(cooldown_seconds=cooldown_time) | |
| # Find gamepad | |
| device_path = autofocus.find_gamesir() | |
| if not device_path: | |
| print("GameSir G7 SE not found") | |
| sys.exit(1) | |
| # Find the target window | |
| autofocus.target_window = autofocus.get_target_window() | |
| if not autofocus.target_window: | |
| print(f"Could not find window named '{autofocus.target_window_name}'!") | |
| print("Make sure:") | |
| print(f"1. The game is running") | |
| print(f"2. The USERNAME variable is set correctly at the top of this script") | |
| print(f"3. Your actual window name is '{autofocus.target_window_name}' (case-sensitive)") | |
| return | |
| print(f"\nUsing target window: {autofocus.target_window} ('{autofocus.target_window_name}')") | |
| # Open device | |
| try: | |
| autofocus.device = evdev.InputDevice(device_path) | |
| print(f"Connected to: {autofocus.device.name}") | |
| except Exception as e: | |
| print(f"Error opening gamepad: {e}") | |
| return | |
| # Start monitoring | |
| try: | |
| autofocus.monitor_gamepad() | |
| except Exception as e: | |
| print(f"Error: {e}") | |
| finally: | |
| if autofocus.device: | |
| autofocus.device.close() | |
| if __name__ == "__main__": | |
| main() |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment