Skip to content

Add keyboard input from the focused visualizer window - #8158

Merged
ooctipus merged 7 commits into
isaac-sim:mainfrom
rwiltz:rwiltz/visualizer-key-events
Oct 24, 2026
Merged

ooctipus merged 7 commits into
isaac-sim:mainfrom
rwiltz:rwiltz/visualizer-key-events

Conversation

@rwiltz

@rwiltz rwiltz commented Sep 29, 2026 •

Copy link
Copy Markdown
Contributor

Description

Adds a keyboard input API to visualizers, so code can read the keys pressed in a visualizer's window, e.g. for keyboard teleoperation or script hotkeys, independent of Kit/carb.

  • BaseVisualizer.key_event_source returns a KeyEventSource for the visualizer's window, or None when it has none (the default; also before initialization, after close, and headless). It is created
    on first access and closed with the visualizer.
  • KeyEventSource.add_key_listener(on_key, on_focus_lost) returns a closable KeyboardSubscription. Presses and releases arrive as W3C KeyboardEvent.code strings ("KeyW"), forwarded as the window
    reports them; on_focus_lost runs on blur, when the visualizer's UI takes the keyboard, and on close. Subscribing, closing and callbacks all happen on the visualizer's thread.
  • BaseVisualizer.set_camera_keys_enabled(enabled) lets the caller turn off keyboard camera movement (Newton WASD/QE and arrow keys) while it uses those keys; a no-op by default.
  • Implemented for Kit (physical keys; Kit visualizers share the app window, so each reports the same keys) and for Newton GL and Newton RTX (one pyglet viewer window; US-layout key mapping, presses
    typed into the ImGui UI are withheld). Viser, Rerun and headless runs return None.
  • Each visualizer closes its keyboard source first in close(), while the window is still open; a failure there is logged and the rest of the teardown still runs.

Opt-in: nothing is hooked until a listener subscribes, and existing visualizer behavior is unchanged.

Used by #8797 (keyboard, gamepad and SpaceMouse teleop through Isaac Capture) and #8798 (remaining scripts and deprecation of the isaaclab.devices keyboard, gamepad and SpaceMouse devices).

Fixes # (issue)

Type of change

  • New feature (non-breaking change which adds functionality)

Release backport

  • Backport this pull request to the active release branch after it merges into develop

Checklist

Docker and GPU tests run on demand. Push the commits you want tested, then
comment run-ci on the pull request.

  • I have read and understood the contribution guidelines
  • I have run the pre-commit checks with uv run isaaclab --format
  • I have made corresponding changes to the documentation
  • My changes generate no new warnings
  • I have validated the changed behavior with focused coverage and applied the test-audit criteria to any test changes
  • I have added a changelog fragment under source/<pkg>/changelog.d/ for every touched package (do not edit CHANGELOG.rst or bump package versions — CI handles that)
  • I have added my name to the CONTRIBUTORS.md or my name already exists there

@github-actions github-actions Bot added documentation Improvements or additions to documentation isaac-lab Related to Isaac Lab team labels Sep 29, 2026
@rwiltz
rwiltz force-pushed the rwiltz/visualizer-key-events branch 5 times, most recently from d78dfca to a82dd1e Compare October 8, 2026 14:20
@rwiltz
rwiltz marked this pull request as ready for review October 8, 2026 14:20
@rwiltz
rwiltz requested a review from a team October 8, 2026 14:20
@rwiltz

rwiltz commented Oct 8, 2026

Copy link
Copy Markdown
Contributor Author

run-ci

@isaaclab-bot isaaclab-bot Bot added ci:run-docker Trigger the on-demand Docker and GPU CI workflow and removed ci:run-docker Trigger the on-demand Docker and GPU CI workflow labels Oct 8, 2026
@greptile-apps

greptile-apps Bot commented Oct 8, 2026 •

Copy link
Copy Markdown
Contributor

RetriggerConfidence Score: 5/5

[Medium risk] Adds keyboard input capture to visualizer windows.

The PR appears safe to merge; no new actionable issue was found.

What we checked:

  • Test runs only fixed code: The test builds code from fixed strings and passes it directly to Python without a shell.
  • Closed listeners reject later keys: Final focus loss marks each subscription closed. Its key callback then rejects further events, even from an earlier snapshot.

Summary

Adds opt-in keyboard input for Kit and Newton GL visualizer windows.

  • Shares listener, focus-loss, and capture behavior through KeyEventSource.
  • Filters Newton UI input and suspends its keyboard camera movement while captured.
  • Keeps the closing source available during shutdown callbacks, preventing replacement sources.
  • Both earlier, unnumbered findings are addressed: camera capture now exercises _update_camera, and shutdown callbacks cannot create a replacement source.

Tests were inspected but not run.

Diagram

%%{init: {'theme': 'neutral'}}%%
flowchart LR
  Window[Focused window] --> Backend[Kit or Newton GL]
  Backend --> Source[KeyEventSource]
  Source --> Listener[Key and focus-loss callbacks]
  Capture[Keyboard capture] --> Camera[Newton camera keys suspended]
  Close[Visualizer closes] --> Shutdown[Close source before dropping references]
Loading

Reviews (3) · Last reviewed commit: "Report keys from the Kit and Newton GL v..." · Reviewed by Greptile

Comment thread source/isaaclab_visualizers/test/test_key_event_sources.py Outdated

@isaaclab-review-bot isaaclab-review-bot Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Isaac Lab Review Bot

Needs attention: Scope and over-engineering (2), Implementation (3), Style consistency (2), Test quality (2). Other reviewed categories look good.

  • PR description: The body falsely claims a Viser implementation although the diff and capability table return None; remove that claim and the empty Fixes #, then list focused validation commands.
  • Design and architecture: Architecture looks good.
  • Scope and over-engineering: Minimal implementation: extend BaseVisualizer, KitVisualizer, and NewtonVisualizer; keep key_event_source.py, keep kit_key_event_source.py but inline its one-use _kit() wrapper, and keep newton_key_event_source.py for pyglet mapping and capture. Delete both redundant _key_input_closed flags; retain lazy creation, backend hooks, event delivery, and visualizer-owned cleanup. Scope is cohesive; no split recommended.
  • API: API changes look good.
  • Compatibility and deprecation: No breaking changes identified.
  • Implementation: Backend cleanup is not failure-safe: Kit can remain marked listening after losing hooks, and Newton can retain its frame callback after unhook failure; preserve consistent state and clear callbacks during release.
  • Style consistency: The new property and capabilities declaration violate required class organization: move properties before public methods and annotate backend-shared capabilities with ClassVar[KeyboardCapabilities].
  • Test quality: The added suites cover distinct lifecycle and backend behavior, but capability-literal assertions and mirrored shift-order rows duplicate stronger behavioral coverage; remove those cases.

Minor fixes needed. Posted 9 inline findings.

Automated review; human maintainers own approval decisions.

Comment thread source/isaaclab/isaaclab/visualizers/base_visualizer.py
Comment thread source/isaaclab/isaaclab/visualizers/key_event_source.py Outdated
Comment thread source/isaaclab_visualizers/isaaclab_visualizers/kit/kit_key_event_source.py Outdated
Comment thread source/isaaclab_visualizers/isaaclab_visualizers/kit/kit_key_event_source.py Outdated
Comment thread source/isaaclab_visualizers/isaaclab_visualizers/kit/kit_visualizer.py Outdated
Comment thread source/isaaclab_visualizers/test/test_key_event_sources.py
Comment thread source/isaaclab_visualizers/test/test_key_event_sources.py Outdated
@rwiltz
rwiltz force-pushed the rwiltz/visualizer-key-events branch from a82dd1e to 5811c22 Compare October 8, 2026 14:49
@rwiltz

rwiltz commented Oct 8, 2026

Copy link
Copy Markdown
Contributor Author

@greptile

Comment thread source/isaaclab_visualizers/isaaclab_visualizers/newton/newton_visualizer.py Outdated
@rwiltz
rwiltz force-pushed the rwiltz/visualizer-key-events branch from 5811c22 to 773a726 Compare October 8, 2026 15:11
@rwiltz

rwiltz commented Oct 8, 2026

Copy link
Copy Markdown
Contributor Author

@greptile

@rwiltz

rwiltz commented Oct 8, 2026

Copy link
Copy Markdown
Contributor Author

run-ci

@isaaclab-bot isaaclab-bot Bot added ci:run-docker Trigger the on-demand Docker and GPU CI workflow and removed ci:run-docker Trigger the on-demand Docker and GPU CI workflow labels Oct 8, 2026
@rwiltz
rwiltz force-pushed the rwiltz/visualizer-key-events branch from 773a726 to 1c1d547 Compare October 22, 2026 16:43
@ooctipus

Copy link
Copy Markdown
Collaborator

The goal makes sense, but I’m concerned about how much responsibility is going into KeyEventSource. It handles window events, tracks held keys for each listener, manages requests to disable camera controls, and coordinates subscriptions and cleanup across threads.

Could we first connect this to Se2Keyboard or Se3Keyboard and use that example to define the smallest interface we need? Both still subscribe directly to Carb, so the PR does not yet show how the new API fits the existing keyboard controls.

I’d expect the ownership to be roughly:

  • The visualizer receives and translates key/focus events and cleans up its window hooks.
  • The keyboard controller tracks the keys it uses and turns them into robot commands.
  • The viewer owns camera keyboard controls, with the caller deciding when to disable them.

The shared source may need some bookkeeping to deliver reliable events, and focus-loss handling is important. But which actual callers need subscription/cleanup from different threads and multiple independent keyboard capture handles? Would requiring operations on the visualizer thread and providing a simple camera-keyboard toggle cover the current use case?

I’d like us to validate those boundaries with one real controller before committing to the larger public API.

@rwiltz

rwiltz commented Oct 22, 2026

Copy link
Copy Markdown
Contributor Author

The goal makes sense, but I’m concerned about how much responsibility is going into KeyEventSource. It handles window events, tracks held keys for each listener, manages requests to disable camera controls, and coordinates subscriptions and cleanup across threads.

Could we first connect this to Se2Keyboard or Se3Keyboard and use that example to define the smallest interface we need? Both still subscribe directly to Carb, so the PR does not yet show how the new API fits the existing keyboard controls.

I’d expect the ownership to be roughly:

  • The visualizer receives and translates key/focus events and cleans up its window hooks.
  • The keyboard controller tracks the keys it uses and turns them into robot commands.
  • The viewer owns camera keyboard controls, with the caller deciding when to disable them.

The shared source may need some bookkeeping to deliver reliable events, and focus-loss handling is important. But which actual callers need subscription/cleanup from different threads and multiple independent keyboard capture handles? Would requiring operations on the visualizer thread and providing a simple camera-keyboard toggle cover the current use case?

I’d like us to validate those boundaries with one real controller before committing to the larger public API.

Thanks Octi, agreed on validating against a real controller. There is one: the follow-up branch rwiltz/keyboard-focus-surfaces moves the keyboard teleop to isaaclab_teleop on top of this API and deprecates Se2Keyboard/Se3Keyboard (which still subscribe to Carb directly). I'll open it as a draft PR stacked on this one so both can be reviewed together.

The ownership you describe matches how that branch splits it:

  • Visualizer: hooks its window, translates keys to W3C codes, reports focus loss (blur, its UI taking the keyboard, close), and unhooks on close.
  • Controller: Isaac Capture's KeyboardSource tracks held keys and turns them into commands; the teleop session attaches it to each visualizer's key_event_source.
  • Viewer: owns its camera keys; the caller decides when to turn them off.

On your questions, the real caller doesn't need everything this PR provides:

  • Threads: no. The teleop session attaches and detaches on the sim thread (session start/stop and Kit pre-shutdown), the same thread that steps the visualizers. I'll drop the locks and document that subscribing, closing and callbacks all happen on the visualizer thread.
  • Per-listener held keys: the controller already does this. Isaac Capture's keyboard ignores repeated presses and releases everything on focus loss. I'll remove that bookkeeping from the source, so it only forwards events and focus loss.
  • Multiple capture handles: no real need. The session is the one place that decides camera keys should yield while a keyboard is attached. I'll replace capture_keyboard() with a simple camera-keys toggle on the visualizer, and keep the handle shape Isaac Capture expects in a small adapter in isaaclab_teleop, where that decision is made.

That reduces the public API to key_event_source with add_key_listener(on_key, on_focus_lost) returning a closable subscription, plus the camera-keys toggle. Does that match what you had in mind? If so, I'll push it here and open the stacked PR to show it working end to end.

@ooctipus

Copy link
Copy Markdown
Collaborator

Yes, that matches what I had in mind. Thanks for clarifying.

Let's simplify this PR as you described: keep key/focus events and a closable subscription, remove the locks and per-listener held-key tracking, and replace capture handles with a camera-keyboard toggle.

Please open the controller integration as a follow-up draft PR so we can review both together. That should make the ownership much clearer.

@rwiltz
rwiltz force-pushed the rwiltz/visualizer-key-events branch from 1c1d547 to 3699be9 Compare October 22, 2026 22:06
BaseVisualizer.key_event_source exposes keys typed into a visualizer's own
window, or None when it has none. A KeyEventSource reports each press and
release as a W3C KeyboardEvent.code string, plus a focus loss when the
window stops delivering keys (blur, the visualizer's UI taking the
keyboard, or close).

add_key_listener returns a KeyboardSubscription, an idempotent handle the
caller closes. Events are forwarded as the window reports them, so a
listener tracks the keys it holds. Subscribing, closing and callbacks all
happen on the thread that steps the visualizer. Closing the source is
terminal: every listener gets one final focus loss.

BaseVisualizer.set_camera_keys_enabled lets a keyboard consumer turn off
the visualizer's keyboard camera movement while it uses those keys; it
does nothing by default.
KitVisualizer reports physical keys from the Kit app window and a focus
loss on window blur. Kit has no Python query for UI keyboard ownership,
so keys typed into its UI are reported too. Its viewport camera keys only
act while the right mouse button is held, so it keeps the default
set_camera_keys_enabled.

NewtonGLVisualizer and NewtonRTXVisualizer report layout-mapped keys from
their shared pyglet viewer window. They withhold presses typed into its
ImGui widgets, checking ImGui's keyboard ownership after every frame.
set_camera_keys_enabled(False) stops the window's WASD/QE and arrow-key
camera movement, including for a window opened later.

Each visualizer closes its keyboard source first in its teardown, while
the window is still open, and finishes that teardown when the source
fails to close. Headless runs and the Viser and Rerun visualizers have
no keyboard source.
@rwiltz

rwiltz commented Oct 23, 2026

Copy link
Copy Markdown
Contributor Author

run-ci

@isaaclab-bot isaaclab-bot Bot added ci:run-docker Trigger the on-demand Docker and GPU CI workflow and removed ci:run-docker Trigger the on-demand Docker and GPU CI workflow labels Oct 23, 2026
ooctipus and others added 4 commits October 23, 2026 14:36
Kit and Newton visualizers now connect keyboard input when their window
initializes. Keyboard input is optional, so a failure there is logged
and key_event_source stays None instead of stopping the visualizer from
starting.
Keep the deleted helper out of the direct rendering test architecture and check keyboard availability in the existing scene test.

Preserve all production changes from the author branch, including optional keyboard initialization.
@ooctipus

Copy link
Copy Markdown
Collaborator

run-ci

@isaaclab-bot isaaclab-bot Bot added ci:run-docker Trigger the on-demand Docker and GPU CI workflow and removed ci:run-docker Trigger the on-demand Docker and GPU CI workflow labels Oct 24, 2026
@ooctipus

Copy link
Copy Markdown
Collaborator

run-ci

@isaaclab-bot isaaclab-bot Bot added ci:run-docker Trigger the on-demand Docker and GPU CI workflow and removed ci:run-docker Trigger the on-demand Docker and GPU CI workflow labels Oct 24, 2026
@ooctipus
ooctipus merged commit 88f4896 into isaac-sim:main Oct 24, 2026
59 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

documentation Improvements or additions to documentation isaac-lab Related to Isaac Lab team

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants