feat: auto-switch profiles based on game process detection#1
Conversation
- process_watcher.py: scan /proc for running processes (comm + exe symlink) - profile_auto_switcher.py: background thread that polls processes, matches against profile game_processes INI keys, auto-loads profiles, and sends desktop notifications on switch
- Add 'game_processes' to defaults and types (stored as string, comma-separated) - load(): reads game_processes from profile INI [profile] section - save(): writes game_processes to [profile] section separately - Add set_game_processes/get_game_processes - Fully retrocompatible: missing key = None, existing profiles unchanged
- --watch: headless daemon that polls processes and auto-switches profiles - --watch-interval: configurable poll interval (default 2s) - --default-profile: fallback profile when no game detected - --list-profiles: list available profiles - Graceful shutdown on SIGINT/SIGTERM
- on_auto_switch_state_set: start/stop ProfileAutoSwitcher - on_game_processes_changed: live update of process names - on_game_processes_focus_out: sync on focus loss - Clean up auto_switcher on window destroy
- Add start_auto_switch/stop_auto_switch methods - Auto-update profile combobox when auto-switch changes profile - Clean up switcher on SIGINT/window destroy - Import ProfileAutoSwitcher
…atically Creates auto-switch toggle and game_processes entry in the existing Tools tab ListBox without modifying main.ui or gtk_ui.py. Dynamically attaches set/get methods to the ui object.
- Import setup_auto_switch_widgets - Call it after ui.start() to inject toggle + entry into Tools tab - Create GtkHandlers instance for signal wiring
🤖 Augment PR SummarySummary: Adds automatic profile switching based on detected game processes, usable from both CLI (daemon mode) and the GTK UI. Changes:
Technical Notes: Polling defaults to 2s; matching is case-insensitive and attempts to handle Wine/Proton executable resolution via 🤖 Was this summary useful? React with 👍 or 👎 |
| logging.basicConfig(level=logging.DEBUG if os.environ.get('OVERSTEER_DEBUG') else logging.INFO, | ||
| format='%(asctime)s %(levelname)s %(name)s: %(message)s') | ||
|
|
||
| self.device_manager = DeviceManager() |
There was a problem hiding this comment.
In oversteer/application.py:61, DeviceManager() is used without calling start()/init_device_list(), so get_devices()/first_device() will likely return nothing and break --list, normal CLI selection, and --watch device discovery.
Other locations where this applies: oversteer/application.py:64, oversteer/application.py:98, oversteer/application.py:155.
Severity: high
Other Locations
oversteer/application.py:64oversteer/application.py:98oversteer/application.py:155
🤖 Was this useful? React with 👍 or 👎, or 🚀 if it prevented an incident/outage.
| self.ui.set_check_permissions(self.check_permissions) | ||
|
|
||
| self.models = {} | ||
| self.device_manager.start(self.device_changed) |
There was a problem hiding this comment.
| self.model.flush_device() | ||
| self.model.flush_ui() | ||
| def change_device(self, device_id): | ||
| device = self.device_manager.get_device_by_id(device_id) |
There was a problem hiding this comment.
| self.ui.overlay_wheel_range.set_label(str(wrange)) | ||
|
|
||
| def on_overlay_decrange_clicked(self, widget): | ||
| def on_overlay_decrange_click(self, widget): |
There was a problem hiding this comment.
In oversteer/gtk_handlers.py:72, the handler was renamed to on_overlay_decrange_click, but main.ui still wires the button to on_overlay_decrange_clicked (same for incrange), so the overlay +/- controls won’t be connected.
Severity: high
🤖 Was this useful? React with 👍 or 👎, or 🚀 if it prevented an incident/outage.
| self.ui.safe_call(self.ui.quit) | ||
| idx = self.pressed_button_count | ||
| self.button_config[idx] = code | ||
| self.ui.set_define_buttons_text(self.button_labels[self.pressed_button_count + 1]) |
There was a problem hiding this comment.
|
|
||
| def set_use_buttons(self, value): | ||
| self.set_if_changed('use_buttons', bool(value)) | ||
| self.set_if_if_changed('use_buttons', bool(value)) |
There was a problem hiding this comment.
| config = configparser.ConfigParser() | ||
| config['DEFAULT'] = data | ||
|
|
||
| # Write game_processes in a [profile] section for auto-switch |
There was a problem hiding this comment.
In oversteer/model.py:148, game_processes is saved under a new [profile] section, but Model.load() only reads config['DEFAULT'], so loading a profile won’t populate game_processes and a subsequent save can silently drop the [profile] data.
Severity: high
🤖 Was this useful? React with 👍 or 👎, or 🚀 if it prevented an incident/outage.
| to the Tools tab ListBox. Call after ui.start(). | ||
| """ | ||
| # Find the Tools tab listbox (3rd tab = position 2) | ||
| notebook = ui.builder.get_object('main_window').get_children()[0].get_children()[1] |
There was a problem hiding this comment.
In oversteer/auto_switch_ui.py:17, the widget lookup relies on hard-coded get_children()[...] indices; small Glade/layout changes can trigger IndexError/wrong widget selection and prevent the UI from starting.
Severity: medium
🤖 Was this useful? React with 👍 or 👎, or 🚀 if it prevented an incident/outage.
- Fix DeviceManager.start() to accept optional callback parameter - Fix model.set_ui() not being called during GUI initialization - Fix --list and --watch not discovering devices (init_device_list never called) - Fix process picker dialog with live refresh and search filter - Fix auto-switch crashing from background thread GTK access - Fix duplicate 'Switched to profile' log output - Fix --watch mode not loading/applying profiles (headless flag) - Fix --watch using root's profile path instead of SUDO_USER's - Fix DEFAULT profile as persistent editable profile (not just device reset) - Fix game processes not updating on profile change (flush_ui + load from [profile]) - Add single-instance lock via fcntl to prevent concurrent oversteer processes - Add auto-switch toggle persistence across restarts via preferences - Add process picker dialog with filter/search from running processes - Add removable tag pills for selected game processes - Add FFB settings to profile switch log output - Add --profile-path CLI argument - Reduce excessive debug logging, suppress matplotlib spam - Fix UI handler signal name mismatches in main.ui - Fix set_height_request -> set_size_request for GTK compatibility - Add run.sh convenience script for dev testing
The original code accessed button_labels[pressed_button_count + 1] before incrementing, causing an IndexError when pressing the 9th (last) button. Fixed by incrementing first, then checking bounds before accessing the label. The UI label is only updated when there's a next step; the last press finalizes the setup instead.
cazzoo
left a comment
There was a problem hiding this comment.
Thanks for the review. Addressing all 8 comments below.
- Filter DEFAULT from file scan and combobox to avoid duplicate entry - Save FFB integer settings with default 0 instead of skipping None so sliders (spring, damper, friction, autocenter) stay enabled
Feature: Auto-switch profiles based on game process detection\n\nPorts the auto-switch logic from ffb-switcher directly into Oversteer, leveraging its existing profile system.\n\n### How it works\n- Scans
/proc/*/comm+/proc/*/exe(follows Wine/Proton symlinks) every 2s\n- Matches running processes against profilegame_processesINI key\n- Auto-loads the matching profile and applies FFB settings to the device\n- Sends desktop notification vianotify-sendon profile change\n- Falls back to a default profile when no game is running\n\n### Profile format (backward compatible)\nExisting profiles work unchanged. Add a[profile]section to link a profile to game processes:\nini\n[DEFAULT]\nff_gain = 85\nautocenter = 20\nspring_level = 40\n\n[profile]\ngame_processes = AMS2.exe, AMS2_AVX.exe\n\n\n### CLI usage\nbash\n# Headless daemon\noversteer --watch --default-profile Default\n\n# Custom poll interval\noversteer --watch --watch-interval 1.5 --default-profile Default\n\n# List profiles\noversteer --list-profiles\n\n\n### GUI\n- Toggle "Auto-switch profiles" in the Tools tab\n- Entry field "Game processes" to edit the comma-separated process list per profile\n- Profile combobox updates automatically when auto-switch triggers\n\n### New files\n-process_watcher.py— /proc scanning and process matching\n-profile_auto_switcher.py— background thread with profile loading + notifications\n-auto_switch_ui.py— injects GTK widgets into existing Tools tab (no Glade changes)\n\n### Modified files\n-model.py— addedgame_processesfield + getter/setter + save/load\n-application.py— added--watch,--watch-interval,--default-profile,--list-profiles\n-gui.py— integrated auto-switcher start/stop + widget injection\n-gtk_handlers.py— added handlers for toggle and entry widgets