···267267 case XRT_OPERATION_CANCELLED: DG("XRT_OPERATION_CANCELLED"); return;
268268 case XRT_ERROR_FUTURE_RESULT_NOT_READY: DG("XRT_ERROR_FUTURE_RESULT_NOT_READY"); return;
269269 case XRT_ERROR_FUTURE_ALREADY_COMPLETE: DG("XRT_ERROR_FUTURE_ALREADY_COMPLETE"); return;
270270+ case XRT_ERROR_DEVICE_NOT_ATTACHABLE: DG("XRT_ERROR_DEVICE_NOT_ATTACHABLE"); return;
270271 }
271272 // clang-format on
272273
+68-3
src/xrt/auxiliary/util/u_space_overseer.c
···4343 U_SPACE_TYPE_POSE,
4444 U_SPACE_TYPE_OFFSET,
4545 U_SPACE_TYPE_ROOT,
4646+4747+ /*!
4848+ * Space designed to be attachable to others, most importantly it is
4949+ * re-attachable, and in order to move all of the spaces that has this
5050+ * space as it's parent/next we need a node that can be updated.
5151+ */
5252+ U_SPACE_TYPE_ATTACHABLE,
4653};
47544855/*!
···331338 m_relation_chain_push_relation(xrc, &xsr);
332339 } break;
333340 case U_SPACE_TYPE_OFFSET: m_relation_chain_push_pose_if_not_identity(xrc, &space->offset.pose); break;
334334- case U_SPACE_TYPE_ROOT: return; // Stops the traversing.
341341+ case U_SPACE_TYPE_ROOT: return; // Stops the traversing.
342342+ case U_SPACE_TYPE_ATTACHABLE: break; // No-op
335343 }
336344337345 // Please tail-call optimise this miss compiler.
···353361 case U_SPACE_TYPE_NULL: break;
354362 case U_SPACE_TYPE_POSE: break;
355363 case U_SPACE_TYPE_OFFSET: break;
356356- case U_SPACE_TYPE_ROOT: return; // Stops the traversing.
364364+ case U_SPACE_TYPE_ROOT: return; // Stops the traversing.
365365+ case U_SPACE_TYPE_ATTACHABLE: break; // No-op
357366 }
358367359368 // Can't tail-call optimise this one :(
···372381 } break;
373382 case U_SPACE_TYPE_OFFSET: m_relation_chain_push_inverted_pose_if_not_identity(xrc, &space->offset.pose); break;
374383 case U_SPACE_TYPE_ROOT: assert(false); // Should not get here.
384384+ case U_SPACE_TYPE_ATTACHABLE: break; // No-op
375385 }
376386}
377387···503513504514 if (ptr != NULL) {
505515 xs = (struct xrt_space *)ptr;
516516+ } else if (torig->type == XRT_TRACKING_TYPE_ATTACHABLE) {
517517+ /*
518518+ * If we ever make u_space_overseer sub-classable make sure
519519+ * this calls the right function, can't call interface function
520520+ * as the lock is held here.
521521+ */
522522+ xs = (struct xrt_space *)create_space(U_SPACE_TYPE_ATTACHABLE, u_space(root));
523523+ u_hashmap_int_insert(uso->xto_map, key, xs);
506524 } else {
507525 /*
508508- * If we ever make u_space_overseer sub-classable maek sure
526526+ * If we ever make u_space_overseer sub-classable make sure
509527 * this calls the right function, can't call interface function
510528 * as the lock is held here.
511529 */
···10931111 return add_device_helper(uso, xdev);
10941112}
1095111311141114+static xrt_result_t
11151115+attach_device(struct xrt_space_overseer *xso, struct xrt_device *xdev, struct xrt_space *space)
11161116+{
11171117+ struct u_space_overseer *uso = u_space_overseer(xso);
11181118+11191119+ // Check that the device has the correct tracking origin type.
11201120+ if (xdev->tracking_origin == NULL || xdev->tracking_origin->type != XRT_TRACKING_TYPE_ATTACHABLE) {
11211121+ U_LOG_E("Device '%s' does not have XRT_TRACKING_TYPE_ATTACHABLE tracking origin type", xdev->str);
11221122+ return XRT_ERROR_DEVICE_NOT_ATTACHABLE;
11231123+ }
11241124+11251125+ // If no space is provided, use the root space.
11261126+ struct xrt_space *target_space = space;
11271127+ if (target_space == NULL) {
11281128+ target_space = uso->base.semantic.root;
11291129+ }
11301130+11311131+ xrt_result_t xret = XRT_SUCCESS;
11321132+ pthread_rwlock_wrlock(&uso->lock);
11331133+11341134+11351135+ void *ptr = NULL;
11361136+ uint64_t key = (uint64_t)(intptr_t)xdev->tracking_origin;
11371137+ u_hashmap_int_find(uso->xto_map, key, &ptr);
11381138+ if (ptr == NULL) {
11391139+ U_LOG_E("Device doesn't have space associated with it!");
11401140+ xret = XRT_ERROR_DEVICE_NOT_ATTACHABLE;
11411141+ goto err_unlock;
11421142+ }
11431143+11441144+ struct u_space *us = (struct u_space *)ptr;
11451145+ if (us->type != U_SPACE_TYPE_ATTACHABLE) {
11461146+ U_LOG_E("Device doesn't have a attachable space!");
11471147+ xret = XRT_ERROR_DEVICE_NOT_ATTACHABLE;
11481148+ goto err_unlock;
11491149+ }
11501150+11511151+ // Update the link.
11521152+ u_space_reference(&us->next, u_space(target_space));
11531153+11541154+err_unlock:
11551155+ pthread_rwlock_unlock(&uso->lock);
11561156+11571157+ return xret;
11581158+}
11591159+10961160static void
10971161destroy(struct xrt_space_overseer *xso)
10981162{
···11501214 uso->base.get_reference_space_offset = get_reference_space_offset;
11511215 uso->base.set_reference_space_offset = set_reference_space_offset;
11521216 uso->base.add_device = add_device;
12171217+ uso->base.attach_device = attach_device;
11531218 uso->base.destroy = destroy;
11541219 uso->broadcast = broadcast;
11551220
+6
src/xrt/include/xrt/xrt_results.h
···251251 * Invoking complete on an already completed future
252252 */
253253 XRT_ERROR_FUTURE_ALREADY_COMPLETE = -41,
254254+255255+ /*!
256256+ * The device's tracking origin is not of type
257257+ * XRT_TRACKING_TYPE_ATTACHABLE.
258258+ */
259259+ XRT_ERROR_DEVICE_NOT_ATTACHABLE = -42,
254260} xrt_result_t;
+33
src/xrt/include/xrt/xrt_space.h
···330330 */
331331 xrt_result_t (*add_device)(struct xrt_space_overseer *xso, struct xrt_device *xdev);
332332333333+ /*!
334334+ * Attach a device to a different space then it was associated with
335335+ * originally, the space overseer might not support this operation.
336336+ *
337337+ * For some space overseer implementations this operation requires
338338+ * that the device has the tracking origin type of
339339+ * @ref XRT_TRACKING_TYPE_ATTACHABLE. Which space that becomes the
340340+ * parent space of the device when @p space is NULL is undefined,
341341+ * and the device might become un-trackable.
342342+ *
343343+ * @param[in] xso Owning space overseer.
344344+ * @param[in] xdev Device to attach.
345345+ * @param[in] space Space to attach the device to, may be NULL.
346346+ *
347347+ * @return XRT_SUCCESS on success.
348348+ * @return XRT_ERROR_DEVICE_NOT_ATTACHABLE if the device does not have
349349+ * the XRT_TRACKING_TYPE_ATTACHABLE tracking origin type.
350350+ */
351351+ xrt_result_t (*attach_device)(struct xrt_space_overseer *xso, struct xrt_device *xdev, struct xrt_space *space);
352352+333353334354 /*
335355 *
···560580xrt_space_overseer_add_device(struct xrt_space_overseer *xso, struct xrt_device *xdev)
561581{
562582 return xso->add_device(xso, xdev);
583583+}
584584+585585+/*!
586586+ * @copydoc xrt_space_overseer::attach_device
587587+ *
588588+ * Helper for calling through the function pointer.
589589+ *
590590+ * @public @memberof xrt_space_overseer
591591+ */
592592+static inline xrt_result_t
593593+xrt_space_overseer_attach_device(struct xrt_space_overseer *xso, struct xrt_device *xdev, struct xrt_space *space)
594594+{
595595+ return xso->attach_device(xso, xdev, space);
563596}
564597565598/*!
+4
src/xrt/include/xrt/xrt_tracking.h
···11// Copyright 2019, Collabora, Ltd.
22+// Copyright 2025, NVIDIA CORPORATION.
23// SPDX-License-Identifier: BSL-1.0
34/*!
45 * @file
···60616162 // The device(s) are tracked by other methods.
6263 XRT_TRACKING_TYPE_OTHER,
6464+6565+ // The device(s) are (re)attachable.
6666+ XRT_TRACKING_TYPE_ATTACHABLE,
6367};
64686569/*!
+9
src/xrt/ipc/client/ipc_client_space_overseer.c
···324324 return XRT_ERROR_NOT_IMPLEMENTED;
325325}
326326327327+static xrt_result_t
328328+attach_device(struct xrt_space_overseer *xso, struct xrt_device *xdev, struct xrt_space *space)
329329+{
330330+ // For IPC client, attachable devices are handled on the server side.
331331+ // This should not be called from the client in the typical use case.
332332+ return XRT_ERROR_NOT_IMPLEMENTED;
333333+}
334334+327335static void
328336destroy(struct xrt_space_overseer *xso)
329337{
···381389 icspo->base.get_reference_space_offset = get_reference_space_offset;
382390 icspo->base.set_reference_space_offset = set_reference_space_offset;
383391 icspo->base.add_device = add_device;
392392+ icspo->base.attach_device = attach_device;
384393 icspo->base.destroy = destroy;
385394 icspo->ipc_c = ipc_c;
386395