Skip to content

feat: auto-switch profiles based on game process detection#1

Merged
cazzoo merged 11 commits into
masterfrom
feat/auto-switch
Apr 23, 2026
Merged

feat: auto-switch profiles based on game process detection#1
cazzoo merged 11 commits into
masterfrom
feat/auto-switch

Conversation

@cazzoo

@cazzoo cazzoo commented Apr 22, 2026

Copy link
Copy Markdown
Owner

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 profile game_processes INI key\n- Auto-loads the matching profile and applies FFB settings to the device\n- Sends desktop notification via notify-send on 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 — added game_processes field + 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

cazzoo added 7 commits April 22, 2026 21:45
- 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
@augmentcode

augmentcode Bot commented Apr 22, 2026

Copy link
Copy Markdown
🤖 Augment PR Summary

Summary: Adds automatic profile switching based on detected game processes, usable from both CLI (daemon mode) and the GTK UI.

Changes:

  • Introduced a /proc-based process scanner and matching logic to map running games to profiles.
  • Added a ProfileAutoSwitcher background thread to load/apply matching profiles and optionally notify via notify-send.
  • Extended the profile format with a new [profile] section supporting game_processes (comma-separated).
  • Added CLI options for headless watching (--watch, --watch-interval, --default-profile) and profile listing.
  • Injected new "Auto-switch profiles" + "Game processes" widgets into the existing Tools tab at runtime (no Glade edits).
  • Refactored parts of GUI device/profile handling and added stop logic for the auto-switcher on shutdown.

Technical Notes: Polling defaults to 2s; matching is case-insensitive and attempts to handle Wine/Proton executable resolution via /proc/*/exe symlinks.

🤖 Was this summary useful? React with 👍 or 👎

@augmentcode augmentcode Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Review completed. 8 suggestions posted.

Fix All in Augment

Comment augment review to trigger a new review at any time.

Comment thread oversteer/application.py
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()

@augmentcode augmentcode Bot Apr 22, 2026

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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:64
  • oversteer/application.py:98
  • oversteer/application.py:155

Fix This in Augment

🤖 Was this useful? React with 👍 or 👎, or 🚀 if it prevented an incident/outage.

Comment thread oversteer/gui.py
self.ui.set_check_permissions(self.check_permissions)

self.models = {}
self.device_manager.start(self.device_changed)

@augmentcode augmentcode Bot Apr 22, 2026

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In oversteer/gui.py:88, self.device_manager.start(self.device_changed) passes a callback, but DeviceManager.start() currently takes no parameters, which will raise a TypeError at GUI startup.

Severity: high

Fix This in Augment

🤖 Was this useful? React with 👍 or 👎, or 🚀 if it prevented an incident/outage.

Comment thread oversteer/gui.py
self.model.flush_device()
self.model.flush_ui()
def change_device(self, device_id):
device = self.device_manager.get_device_by_id(device_id)

@augmentcode augmentcode Bot Apr 22, 2026

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In oversteer/gui.py:161, get_device_by_id is called on DeviceManager, but that method doesn’t exist in device_manager.py, so changing devices from the UI will crash.

Severity: high

Fix This in Augment

🤖 Was this useful? React with 👍 or 👎, or 🚀 if it prevented an incident/outage.

Comment thread oversteer/gtk_handlers.py
self.ui.overlay_wheel_range.set_label(str(wrange))

def on_overlay_decrange_clicked(self, widget):
def on_overlay_decrange_click(self, widget):

@augmentcode augmentcode Bot Apr 22, 2026

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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

Fix This in Augment

🤖 Was this useful? React with 👍 or 👎, or 🚀 if it prevented an incident/outage.

Comment thread oversteer/gui.py Outdated
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])

@augmentcode augmentcode Bot Apr 22, 2026

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In oversteer/gui.py:249, indexing self.button_labels[self.pressed_button_count + 1] can go out of range when pressed_button_count reaches the last label, causing a crash during button setup.

Severity: high

Fix This in Augment

🤖 Was this useful? React with 👍 or 👎, or 🚀 if it prevented an incident/outage.

Comment thread oversteer/model.py Outdated

def set_use_buttons(self, value):
self.set_if_changed('use_buttons', bool(value))
self.set_if_if_changed('use_buttons', bool(value))

@augmentcode augmentcode Bot Apr 22, 2026

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In oversteer/model.py:261, set_use_buttons() calls set_if_if_changed, which doesn’t exist (likely meant set_if_changed), so toggling use_buttons will raise at runtime.

Severity: high

Fix This in Augment

🤖 Was this useful? React with 👍 or 👎, or 🚀 if it prevented an incident/outage.

Comment thread oversteer/model.py
config = configparser.ConfigParser()
config['DEFAULT'] = data

# Write game_processes in a [profile] section for auto-switch

@augmentcode augmentcode Bot Apr 22, 2026

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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

Fix This in Augment

🤖 Was this useful? React with 👍 or 👎, or 🚀 if it prevented an incident/outage.

Comment thread oversteer/auto_switch_ui.py Outdated
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]

@augmentcode augmentcode Bot Apr 22, 2026

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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

Fix This in Augment

🤖 Was this useful? React with 👍 or 👎, or 🚀 if it prevented an incident/outage.

cazzoo added 3 commits April 23, 2026 00:46
- 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 cazzoo left a comment

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for the review. Addressing all 8 comments below.

Comment thread oversteer/application.py
Comment thread oversteer/gui.py
Comment thread oversteer/gui.py
Comment thread oversteer/gtk_handlers.py
Comment thread oversteer/gui.py
Comment thread oversteer/model.py
Comment thread oversteer/model.py
Comment thread oversteer/auto_switch_ui.py
- 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
@cazzoo cazzoo merged commit d35e432 into master Apr 23, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant