Skip to content

hotkeygen leaves the emulator's quit keys stuck held after a hotkey exit - only one game can be launched per session (v43, X11) #16213

Description

@netstrider-canada

Description

On Batocera v43/v43.1 (X11), after exiting any game with the controller exit combo (hotkey+start), no further game can be launched: every new emulator dies within a second with

X connection to :N broken (explicit kill or server shutdown).

EmulationStation, openbox and X all stay alive and look healthy, xrandr/glxinfo -B answer normally, the X log records nothing at the moment of death, and restarting the ES session (/etc/init.d/S31emulationstation restart) "fixes" it — until the next hotkey exit. This did not occur on v41 on the same hardware.

Root cause

hotkeygen --permanent translates the pad exit combo into the emulator's native quit keystrokes on its persistent uinput keyboard (batocera hotkeys). In __handle_event, it presses the context's exit keys when the combo goes down and releases them when the combo goes up:

if begin:
    send_keys(self.target, keys, True)     # combo down -> e.g. ALT+F4 pressed
else:
    send_keys(self.target, keys, False)    # combo up   -> ALT+F4 released

But a hotkey exit means the emulator quits while the player is still holding the combo, and emulatorlauncher then kills evmapy and resets the hotkeygen context (SIGHUP) mid-hold. When the player finally releases:

  • the release event never reaches hotkeygen (evmapy is already dead), or
  • it arrives after the context switch and maps to different keys (e.g. cemu context exit = ["KEY_LEFTALT","KEY_F4"], default ES context exit = "KEY_ESC"), so the release sends the wrong key.

Either way, the originally pressed quit keys are never released and stay held forever on the persistent virtual keyboard. Verified directly with active_keys() on the batocera hotkeys device after a cemu hotkey exit:

[('KEY_LEFTALT', 56), ('KEY_F4', 62)]

(A retroarch-context exit sticks KEY_LEFTSHIFT the same way: pressed SHIFT+ESC, released bare ESC after the context reset.)

Why it kills every subsequent game

X autorepeat fires the held ALT+F4 continuously, and the shipped openbox rc.xml binds A-F4Close. For a window that has not yet registered WM_DELETE_WINDOW — i.e. any emulator window during early init — openbox falls back to XKillClient, producing exactly the X connection to :N broken (explicit kill or server shutdown) seen in the emulator logs. Windowless clients (xrandr, glxinfo) are unaffected, which makes the failure masquerade as an emulator or GPU bug. A session restart only "fixes" it because a fresh X server ignores pre-existing key state on the still-held device.

Steps to reproduce (deterministic, no controller needed)

  1. Launch any game through ES (the HTTP API works: curl -X POST -H "Content-Type: text/plain" --data "/path/to/rom" http://localhost:1234/launch).

  2. While it runs, synthesize the combo signal evmapy would send, and hold it through the teardown (this mimics a player keeping the buttons pressed while the game exits, which is the natural way to use an exit combo):

    import time, evdev
    from evdev import ecodes
    kb = evdev.UInput(name="repro", events={ecodes.EV_KEY: [ecodes.KEY_EXIT]})
    time.sleep(0.5)
    kb.write(ecodes.EV_KEY, ecodes.KEY_EXIT, 1); kb.syn()   # combo down -> game exits
    time.sleep(20)                                          # held through teardown
    kb.write(ecodes.EV_KEY, ecodes.KEY_EXIT, 0); kb.syn()   # released long after
  3. Check the virtual keyboard: ALT+F4 (or the context's exit keys) are now permanently held.

  4. Launch any game — it dies instantly with the X connection broken error. All subsequent launches fail until the session (or just hotkeygen) is restarted.

Recovery / workaround

/etc/init.d/S90hotkeygen restart releases the stuck keys (the uinput device is destroyed) and fixes the session in under a second — no ES restart needed. As a workaround we installed a /userdata/system/scripts gameStop script that does exactly that after every game.

Suggested fix

hotkeygen should track which keys it has pressed on its target device and force-release all of them on SIGHUP / context change (and arguably also when a release event's action maps to different keys than the press did). That closes the race regardless of when the player lets go of the combo.

Environment

  • Batocera v43.1, x86_64, X11 session (modesetting DDX), openbox
  • Beelink SER8, Ryzen 7 8845HS / Radeon 780M iGPU
  • Observed with both Cemu (ALT+F4 context) and libretro/RetroArch (SHIFT+ESC context)

Side observation while debugging (possibly a separate issue): every game stop logs an evmapy crash — FileNotFoundError: '/var/run/evmapy/evmapy.socket' in controller.cleanup() — so evmapy never shuts down cleanly.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions