diff --git a/source/isaaclab_teleop/config/extension.toml b/source/isaaclab_teleop/config/extension.toml index 937a40316fd..812ca070d27 100644 --- a/source/isaaclab_teleop/config/extension.toml +++ b/source/isaaclab_teleop/config/extension.toml @@ -1,6 +1,6 @@ [package] # Semantic Versioning is used: https://semver.org/ -version = "0.3.0" +version = "0.3.1" # Description title = "Isaac Lab Teleop" diff --git a/source/isaaclab_teleop/docs/CHANGELOG.rst b/source/isaaclab_teleop/docs/CHANGELOG.rst index 84fca39109e..ba664976a44 100644 --- a/source/isaaclab_teleop/docs/CHANGELOG.rst +++ b/source/isaaclab_teleop/docs/CHANGELOG.rst @@ -1,6 +1,15 @@ Changelog --------- +0.3.1 (2026-02-26) +~~~~~~~~~~~~~~~~~~~ + +Fixed +^^^^^ + +* Add cleanup for Isaac Teleop session when Stop XR button is clicked and when Kit is closed. + + 0.3.0 (2026-02-26) ~~~~~~~~~~~~~~~~~~~ @@ -9,6 +18,7 @@ Fixed * Update Isaac Teleop API usage for querying controller button states. + 0.2.0 (2026-02-24) ~~~~~~~~~~~~~~~~~~~ diff --git a/source/isaaclab_teleop/isaaclab_teleop/session_lifecycle.py b/source/isaaclab_teleop/isaaclab_teleop/session_lifecycle.py index 56e07c8c7e3..5ed79798691 100644 --- a/source/isaaclab_teleop/isaaclab_teleop/session_lifecycle.py +++ b/source/isaaclab_teleop/isaaclab_teleop/session_lifecycle.py @@ -81,6 +81,33 @@ def __init__(self, cfg: IsaacTeleopCfg): except (ImportError, ModuleNotFoundError): logger.info("isaacsim.kit.xr.teleop.bridge not available; IsaacTeleop will create its own OpenXR session") + try: + import carb.settings + + # Subscribe to the setting (may not fire when Kit closes; see pre-shutdown below) + self._xr_enabled_subscription = carb.settings.get_settings().subscribe_to_node_change_events( + "/xr/enabled", + self._on_xr_enabled_changed, + ) + except (ImportError, ModuleNotFoundError): + logger.info("carb.settings not available; IsaacTeleop will not be able to detect XR enabled state") + + try: + import omni.kit.app + from carb.eventdispatcher import get_eventdispatcher + + # Subscribe to Kit pre-shutdown so we tear down our session before XRCore + # tears down the OpenXR instance/session (XRCore uses order=0; lowest runs first). + # The /xr/enabled setting often does not fire on close, so this is required. + self._pre_shutdown_subscription = get_eventdispatcher().observe_event( + event_name=omni.kit.app.GLOBAL_EVENT_PRE_SHUTDOWN, + on_event=self._on_pre_shutdown, + observer_name="IsaacTeleop session lifecycle", + order=-100, + ) + except (ImportError, ModuleNotFoundError): + logger.info("omni.kit.app/carb.eventdispatcher not available; IsaacTeleop will not clean up on Kit close") + @property def is_active(self) -> bool: """Whether the teleop session is currently running.""" @@ -190,6 +217,22 @@ def _on_request_required_extensions(self) -> list[str]: logger.info(f"Required extensions: {required_extensions}") return required_extensions + def _on_xr_enabled_changed(self, item, event_type): + import carb.settings + + enabled = carb.settings.get_settings().get("/xr/enabled") + logger.info(f"XR enabled changed to: {enabled}") + + if not enabled: + self._teardown_dead_session() + + def _on_pre_shutdown(self, _event): + """Called when Kit is closing; run full cleanup since the app is exiting.""" + logger.info("Shutting down IsaacTeleop session due to Kit close") + self._pre_shutdown_subscription.unsubscribe() + self._pre_shutdown_subscription = None + self.stop() + # ------------------------------------------------------------------ # Deferred session creation # ------------------------------------------------------------------