Skip to content

Fix Back passthrough on webOS 25 - #37

Open
afonsojramos wants to merge 1 commit into
andrewfraley:mainfrom
afonsojramos:fix-webos-25-back-passthrough
Open

Fix Back passthrough on webOS 25#37
afonsojramos wants to merge 1 commit into
andrewfraley:mainfrom
afonsojramos:fix-webos-25-back-passthrough

Conversation

@afonsojramos

Copy link
Copy Markdown

Summary

  • Use LGE M-RCU - Builtin [1] for exclusive passthrough on webOS 25.
  • Normalize the Linux Back event (code 412) into one fresh, complete keypress.
  • Preserve the existing passthrough behavior on webOS 9 and earlier.

Problem

On webOS 25, relaying remote events through the existing Builtin [2] output changes Back behavior: pressing Back inside a nested menu exits the entire app instead of closing the menu.

This affected every app while Magic Mapper was running in exclusive mode. Stopping Magic Mapper restored native Back behavior.

Hardware validation

Tested on an LG C3 running webOS 25 / Rockhopper 10.3.1-3006:

  • Native Back closed only the nested menu.
  • Existing exclusive passthrough through [2] exited the app.
  • A clean Back press through [1] closed only the menu while [0] was exclusively grabbed.
  • With the complete patch running persistently:
    • Back closed only the nested menu.
    • Arrow and OK input responded immediately afterward.
    • Configured Netflix and Rakuten buttons remained blocked.

python3 -m py_compile magic_mapper.py also passes.

@andrewfraley andrewfraley left a comment

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

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

Thanks for digging into this — the webOS 25 Back regression looks real. A few things I'd like resolved before it merges.

Conflict with current main: 412: "back" was added to BUTTONS in "Add missing buttons from LG C5 remote", which merged after this branch point. That makes WEBOS_25_BACK_CODE = 412 a duplicate of the map entry, and more importantly the new special-case continue runs before actions = button_map.get(key), so on webOS 10+ it would swallow any user-configured back action. Worth rebasing on main and reworking that block around the existing button map.

One finding that can't be inline (the line isn't in the diff): press_button() at line 259 still resolves OUTPUT_DEVICE_NAME (Builtin [2]) directly — the device this PR declares broken on webOS 25. After this change the two write paths disagree about which device to use.

The rest is inline. The blocking ones for me are the missing Builtin [1] fallback and the unguarded os.open — both can take a currently-working setup down, at startup or mid-run.

Comment thread magic_mapper.py

OUTPUT_DEVICE_NAME = 'LGE M-RCU - Builtin [2]' # unbound codes get resent to this device in exclusive mode
WEBOS_25_OUTPUT_DEVICE_NAME = 'LGE M-RCU - Builtin [1]'
WEBOS_25_BACK_CODE = 412

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

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

412 is already in BUTTONS as "back" on main (added after this branch point), so this constant duplicates it. Suggest dropping it and matching on the resolved key == "back" instead, so there's a single source of truth for the code.

Comment thread magic_mapper.py
send_input_event(device, 0, 0, 0)


def send_clean_keypress(device, keycode):

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

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

This reopens the device on every Back press even though output_device is already open a few lines up in input_loop. There's no try/except around the call site, so one os.open failure here terminates the daemon. Reusing the existing output_device fd avoids both the churn and that failure mode.

Comment thread magic_mapper.py
return int(major_version)


def get_output_device_name():

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

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

The two halves of this PR look mutually redundant. Back is intercepted below and continues before ever reaching output_device, so switching the output device can't be what fixes Back — it only changes where the other relayed keys go (volume, home, arrows, OK, wheel, and anything unmapped).

Which of the two did you verify in isolation on webOS 25? If the replay path is the actual fix, this device switch is an untested behavior change for every other passthrough key. If the device switch is the fix, the replay path can go entirely — and the long-press problem goes with it.

Comment thread magic_mapper.py
fcntl.ioctl(input_device, EVIOCGRAB, 1)
output_device_path = resolve_input_device_by_name(OUTPUT_DEVICE_NAME)
output_device_name = get_output_device_name()
output_device_path = resolve_input_device_by_name(output_device_name)

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

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

If LGE M-RCU - Builtin [1] isn't present on a given webOS 10+ TV, resolve_input_device_by_name returns None and os.open(None, os.O_WRONLY) raises TypeError here, killing startup.

The webOS version gate is a coarse proxy for device naming, so this turns a working install into a crash-on-boot for anyone whose TV only exposes Builtin [2]. Please fall back to OUTPUT_DEVICE_NAME when the lookup comes back None.

Comment thread magic_mapper.py
event = input_device.read(event_size)
(tv_sec, tv_usec, event_type, code, value) = struct.unpack(input_format, event)

if suppress_next_sync and event_type == 0:

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

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

suppress_next_sync is armed only on key-down, so the SYN following each suppressed autorepeat and the one following the suppressed key-up still fall through to os.write(output_device, event) below, carrying a stale timestamp — the exact condition send_clean_keypress exists to avoid.

The flag also isn't scoped to code 412, so it can swallow a SYN belonging to an unrelated event that happens to arrive next.

Comment thread magic_mapper.py
value = 0
buttons_waiting[code] = now

if EXCLUSIVE_MODE and WEBOS_MAJOR_VERSION >= 10 and event_type == 1 and code == WEBOS_25_BACK_CODE:

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

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

Two issues with this block:

  1. It sits before actions = button_map.get(key), so on webOS 10+ it preempts any back action the user configured. That matters now that 412: "back" is in BUTTONS on main — the button became configurable, and this would silently disable that.
  2. Back long-press is no longer possible: key-down, every autorepeat, and key-up are all swallowed and replaced with a fixed ~80 ms synthetic tap emitted on release. Press-and-hold registers as one short tap, and even a normal press is delayed until the button comes up.

@andrewfraley

Copy link
Copy Markdown
Owner

Note this specific comment is really me, everything else above is the Claude Code Opus 5 review skill.

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.

2 participants