XRUIInputModule does not consider multiple monitors for calculating the mouse position

When using the XRUIInputModule with “Enable Mouse Input = true” the mouse processing does not take multiple displays into account.

I’m having this issue when I want to render the VR mirror display on the main screen and have a second screen with UI.

I have created a quick fix by copying the package in the packages folder locally and modified the following:

MouseModel.cs now contains a property called displayIndex which is set from XRUIInputModule.cs in the
ProcessMouse() method.

m_MouseState.displayIndex = Mouse.current.displayIndex.ReadValue();

XRUIInputModule.cs

using System.Collections.Generic;
using UnityEngine.InputSystem;
using UnityEngine.InputSystem.Controls;
using UnityEngine.XR.Interaction.Toolkit.Utilities.Pooling;

namespace UnityEngine.XR.Interaction.Toolkit.UI
{
    /// <summary>
    /// Matches the UI Model to the state of the Interactor.
    /// </summary>
    public interface IUIInteractor
    {
        /// <summary>
        /// Updates the current UI Model to match the state of the Interactor.
        /// </summary>
        /// <param name="model">The returned model that will match this Interactor.</param>
        void UpdateUIModel(ref TrackedDeviceModel model);

        /// <summary>
        /// Attempts to retrieve the current UI Model.
        /// </summary>
        /// <param name="model">The returned model that reflects the UI state of this Interactor.</param>
        /// <returns>Returns <see langword="true"/> if the model was able to retrieved. Otherwise, returns <see langword="false"/>.</returns>
        bool TryGetUIModel(out TrackedDeviceModel model);
    }

    /// <summary>
    /// Matches the UI Model to the state of the Interactor with support for hover events.
    /// </summary>
    public interface IUIHoverInteractor : IUIInteractor
    {
        /// <summary>
        /// The event that is called when the Interactor begins hovering over a UI element.
        /// </summary>
        /// <remarks>
        /// The <see cref="UIHoverEventArgs"/> passed to each listener is only valid while the event is invoked,
        /// do not hold a reference to it.
        /// </remarks>
        UIHoverEnterEvent uiHoverEntered { get; }

        /// <summary>
        /// The event that is called when this Interactor ends hovering over a UI element.
        /// </summary>
        /// <remarks>
        /// The <see cref="UIHoverEventArgs"/> passed to each listener is only valid while the event is invoked,
        /// do not hold a reference to it.
        /// </remarks>
        UIHoverExitEvent uiHoverExited { get; }

        /// <summary>
        /// The <see cref="XRUIInputModule"/> calls this method when the Interactor begins hovering over a UI element.
        /// </summary>
        /// <param name="args">Event data containing the UI element that is being hovered over.</param>
        /// <remarks>
        /// <paramref name="args"/> is only valid during this method call, do not hold a reference to it.
        /// </remarks>
        /// <seealso cref="OnUIHoverExited(UIHoverEventArgs)"/>
        void OnUIHoverEntered(UIHoverEventArgs args);

        /// <summary>
        /// The <see cref="XRUIInputModule"/> calls this method when the Interactor ends hovering over a UI element.
        /// </summary>
        /// <param name="args">Event data containing the UI element that is no longer hovered over.</param>
        /// <remarks>
        /// <paramref name="args"/> is only valid during this method call, do not hold a reference to it.
        /// </remarks>
        /// <seealso cref="OnUIHoverEntered(UIHoverEventArgs)"/>
        void OnUIHoverExited(UIHoverEventArgs args);
    }

    /// <summary>
    /// Custom class for input modules that send UI input in XR.
    /// </summary>
    [AddComponentMenu("Event/XR UI Input Module", 11)]
    [HelpURL(XRHelpURLConstants.k_XRUIInputModule)]
    public partial class XRUIInputModule : UIInputModule
    {
        struct RegisteredInteractor
        {
            public IUIInteractor interactor;
            public TrackedDeviceModel model;

            public RegisteredInteractor(IUIInteractor interactor, int deviceIndex)
            {
                this.interactor = interactor;
                model = new TrackedDeviceModel(deviceIndex);
            }
        }

        struct RegisteredTouch
        {
            public bool isValid;
            public int touchId;
            public TouchModel model;

            public RegisteredTouch(Touch touch, int deviceIndex)
            {
                touchId = touch.fingerId;
                model = new TouchModel(deviceIndex);
                isValid = true;
            }
        }

        /// <summary>
        /// Represents which Active Input Mode will be used in the situation where the Active Input Handling project setting is set to Both.
        /// </summary>
        /// /// <seealso cref="activeInputMode"/>
        public enum ActiveInputMode
        {
            /// <summary>
            /// Only use input polled through the built-in Unity Input Manager (Old).
            /// </summary>
            InputManagerBindings,

            /// <summary>
            /// Only use input polled from <see cref="InputActionReference"/> through the newer Input System package.
            /// </summary>
            InputSystemActions,

            /// <summary>
            /// Scan through input from both Unity Input Manager and Input System action references.
            /// Note: This may cause undesired effects or may impact performance if input configuration is duplicated.
            /// </summary>
            Both,
        }

#if !ENABLE_INPUT_SYSTEM || !ENABLE_LEGACY_INPUT_MANAGER
        [HideInInspector]
#endif
        [SerializeField]
        [Tooltip("Represents which Active Input Mode will be used in the situation where the Active Input Handling project setting is set to Both.")]
        ActiveInputMode m_ActiveInputMode;

        /// <summary>
        /// Configures which Active Input Mode will be used in the situation where the Active Input Handling project setting is set to Both.
        /// </summary>
        /// <seealso cref="ActiveInputMode"/>
        public ActiveInputMode activeInputMode
        {
            get => m_ActiveInputMode;
            set => m_ActiveInputMode = value;
        }

        [SerializeField, HideInInspector]
        [Tooltip("The maximum distance to ray cast with tracked devices to find hit objects.")]
        float m_MaxTrackedDeviceRaycastDistance = 1000f;

        [Header("Input Devices")]
        [SerializeField]
        [Tooltip("If true, will forward 3D tracked device data to UI elements.")]
        bool m_EnableXRInput = true;

        /// <summary>
        /// If <see langword="true"/>, will forward 3D tracked device data to UI elements.
        /// </summary>
        public bool enableXRInput
        {
            get => m_EnableXRInput;
            set => m_EnableXRInput = value;
        }

        [SerializeField]
        [Tooltip("If true, will forward 2D mouse data to UI elements.")]
        bool m_EnableMouseInput = true;

        /// <summary>
        /// If <see langword="true"/>, will forward 2D mouse data to UI elements.
        /// </summary>
        public bool enableMouseInput
        {
            get => m_EnableMouseInput;
            set => m_EnableMouseInput = value;
        }

        [SerializeField]
        [Tooltip("If true, will forward 2D touch data to UI elements.")]
        bool m_EnableTouchInput = true;

        /// <summary>
        /// If <see langword="true"/>, will forward 2D touch data to UI elements.
        /// </summary>
        public bool enableTouchInput
        {
            get => m_EnableTouchInput;
            set => m_EnableTouchInput = value;
        }

#if ENABLE_INPUT_SYSTEM
        [Header("Input System UI Actions")]
#else
        [HideInInspector]
#endif
        [SerializeField]
        [Tooltip("Pointer input action reference, such as a mouse or single-finger touch device.")]
        InputActionReference m_PointAction;
        /// <summary>
        /// The Input System action to use to move the pointer on the currently active UI. Must be a <see cref="Vector2Control"/> Control.
        /// </summary>
        public InputActionReference pointAction
        {
            get => m_PointAction;
            set => SetInputAction(ref m_PointAction, value);
        }

#if !ENABLE_INPUT_SYSTEM
        [HideInInspector]
#endif
        [SerializeField]
        [Tooltip("Left-click input action reference, typically the left button on a mouse.")]
        InputActionReference m_LeftClickAction;
        /// <summary>
        /// The Input System action to use to determine whether the left button of a pointer is pressed. Must be a <see cref="ButtonControl"/> Control.
        /// </summary>
        public InputActionReference leftClickAction
        {
            get => m_LeftClickAction;
            set => SetInputAction(ref m_LeftClickAction, value);
        }

#if !ENABLE_INPUT_SYSTEM
        [HideInInspector]
#endif
        [SerializeField]
        [Tooltip("Middle-click input action reference, typically the middle button on a mouse.")]
        InputActionReference m_MiddleClickAction;
        /// <summary>
        /// The Input System action to use to determine whether the middle button of a pointer is pressed. Must be a <see cref="ButtonControl"/> Control.
        /// </summary>
        public InputActionReference middleClickAction
        {
            get => m_MiddleClickAction;
            set => SetInputAction(ref m_MiddleClickAction, value);
        }

#if !ENABLE_INPUT_SYSTEM
        [HideInInspector]
#endif
        [SerializeField]
        [Tooltip("Right-click input action reference, typically the right button on a mouse.")]
        InputActionReference m_RightClickAction;
        /// <summary>
        /// The Input System action to use to determine whether the right button of a pointer is pressed. Must be a <see cref="ButtonControl"/> Control.
        /// </summary>
        public InputActionReference rightClickAction
        {
            get => m_RightClickAction;
            set => SetInputAction(ref m_RightClickAction, value);
        }

#if !ENABLE_INPUT_SYSTEM
        [HideInInspector]
#endif
        [SerializeField]
        [Tooltip("Scroll wheel input action reference, typically the scroll wheel on a mouse.")]
        InputActionReference m_ScrollWheelAction;
        /// <summary>
        /// The Input System action to use to move the pointer on the currently active UI. Must be a <see cref="Vector2Control"/> Control.
        /// </summary>
        public InputActionReference scrollWheelAction
        {
            get => m_ScrollWheelAction;
            set => SetInputAction(ref m_ScrollWheelAction, value);
        }

#if !ENABLE_INPUT_SYSTEM
        [HideInInspector]
#endif
        [SerializeField]
        [Tooltip("Navigation input action reference will change which UI element is currently selected to the one up, down, left of or right of the currently selected one.")]
        InputActionReference m_NavigateAction;
        /// <summary>
        /// The Input System action to use to navigate the currently active UI. Must be a <see cref="Vector2Control"/> Control.
        /// </summary>
        public InputActionReference navigateAction
        {
            get => m_NavigateAction;
            set => SetInputAction(ref m_NavigateAction, value);
        }

#if !ENABLE_INPUT_SYSTEM
        [HideInInspector]
#endif
        [SerializeField]
        [Tooltip("Submit input action reference will trigger a submission of the currently selected UI in the Event System.")]
        InputActionReference m_SubmitAction;
        /// <summary>
        /// The Input System action to use for submitting or activating a UI element. Must be a <see cref="ButtonControl"/> Control.
        /// </summary>
        public InputActionReference submitAction
        {
            get => m_SubmitAction;
            set => SetInputAction(ref m_SubmitAction, value);
        }

#if !ENABLE_INPUT_SYSTEM
        [HideInInspector]
#endif
        [SerializeField]
        [Tooltip("Cancel input action reference will trigger canceling out of the currently selected UI in the Event System.")]
        InputActionReference m_CancelAction;
        /// <summary>
        /// The Input System action to use for cancelling or backing out of a UI element. Must be a <see cref="ButtonControl"/> Control.
        /// </summary>
        public InputActionReference cancelAction
        {
            get => m_CancelAction;
            set => SetInputAction(ref m_CancelAction, value);
        }

#if !ENABLE_INPUT_SYSTEM
        [HideInInspector]
#endif
        [SerializeField]
        [Tooltip("When enabled, built-in Input System actions will be used if no Input System UI Actions are assigned.")]
        bool m_EnableBuiltinActionsAsFallback = true;
        /// <summary>
        /// When enabled, built-in Input System actions will be used if no Input System UI Actions are assigned. This uses the
        /// currently enabled Input System devices: <see cref="Mouse.current"/>, <see cref="Gamepad.current"/>, and <see cref="Joystick.current"/>.
        /// </summary>
        public bool enableBuiltinActionsAsFallback
        {
            get => m_EnableBuiltinActionsAsFallback;
            set
            {
                m_EnableBuiltinActionsAsFallback = value;
                m_UseBuiltInInputSystemActions = m_EnableBuiltinActionsAsFallback && !InputActionReferencesAreSet();
            }
        }

#if ENABLE_LEGACY_INPUT_MANAGER
        [Header("Input Manager (Old) Gamepad/Joystick Bindings")]
#else
        [HideInInspector]
#endif
        [SerializeField]
        [Tooltip("If true, will use the Input Manager (Old) configuration to forward gamepad data to UI elements.")]
        bool m_EnableGamepadInput = true;

        /// <summary>
        /// If <see langword="true"/>, will forward gamepad data to UI elements.
        /// </summary>
        public bool enableGamepadInput
        {
            get => m_EnableGamepadInput;
            set => m_EnableGamepadInput = value;
        }

#if !ENABLE_LEGACY_INPUT_MANAGER
        [HideInInspector]
#endif
        [SerializeField]
        [Tooltip("If true, will use the Input Manager (Old) configuration to forward joystick data to UI elements.")]
        bool m_EnableJoystickInput = true;

        /// <summary>
        /// If <see langword="true"/>, will forward joystick data to UI elements.
        /// </summary>
        public bool enableJoystickInput
        {
            get => m_EnableJoystickInput;
            set => m_EnableJoystickInput = value;
        }

#if !ENABLE_LEGACY_INPUT_MANAGER
        [HideInInspector]
#endif
        [SerializeField]
        [Tooltip("Name of the horizontal axis for gamepad/joystick UI navigation when using the old Input Manager.")]
        string m_HorizontalAxis = "Horizontal";

        /// <summary>
        /// Name of the horizontal axis for UI navigation when using the old Input Manager.
        /// </summary>
        public string horizontalAxis
        {
            get => m_HorizontalAxis;
            set => m_HorizontalAxis = value;
        }

#if !ENABLE_LEGACY_INPUT_MANAGER
        [HideInInspector]
#endif
        [SerializeField]
        [Tooltip("Name of the vertical axis for gamepad/joystick UI navigation when using the old Input Manager.")]
        string m_VerticalAxis = "Vertical";

        /// <summary>
        /// Name of the vertical axis for UI navigation when using the old Input Manager.
        /// </summary>
        public string verticalAxis
        {
            get => m_VerticalAxis;
            set => m_VerticalAxis = value;
        }

#if !ENABLE_LEGACY_INPUT_MANAGER
        [HideInInspector]
#endif
        [SerializeField]
        [Tooltip("Name of the gamepad/joystick button to use for UI selection or submission when using the old Input Manager.")]
        string m_SubmitButton = "Submit";

        /// <summary>
        /// Name of the gamepad/joystick button to use for UI selection or submission when using the old Input Manager.
        /// </summary>
        public string submitButton
        {
            get => m_SubmitButton;
            set => m_SubmitButton = value;
        }

#if !ENABLE_LEGACY_INPUT_MANAGER
        [HideInInspector]
#endif
        [SerializeField]
        [Tooltip("Name of the gamepad/joystick button to use for UI cancel or back commands when using the old Input Manager.")]
        string m_CancelButton = "Cancel";

        /// <summary>
        /// Name of the gamepad/joystick button to use for UI cancel or back commands when using the old Input Manager.
        /// </summary>
        public string cancelButton
        {
            get => m_CancelButton;
            set => m_CancelButton = value;
        }

        int m_RollingPointerId;
        bool m_UseBuiltInInputSystemActions;

        MouseModel m_MouseState;
        NavigationModel m_NavigationState;

        internal const float kPixelPerLine = 20f;

        readonly List<RegisteredTouch> m_RegisteredTouches = new List<RegisteredTouch>();
        readonly List<RegisteredInteractor> m_RegisteredInteractors = new List<RegisteredInteractor>();

        // Reusable event args
        readonly LinkedPool<UIHoverEventArgs> m_UIHoverEventArgs = new LinkedPool<UIHoverEventArgs>(() => new UIHoverEventArgs(), collectionCheck: false);

        /// <summary>
        /// See <a href="https://docs.unity3d.com/ScriptReference/MonoBehaviour.OnEnable.html">MonoBehavior.OnEnable</a>.
        /// </summary>
        protected override void OnEnable()
        {
            base.OnEnable();

            // Check active input mode is correct
#if !ENABLE_INPUT_SYSTEM && ENABLE_LEGACY_INPUT_MANAGER
            m_ActiveInputMode = ActiveInputMode.InputManagerBindings;
#elif ENABLE_INPUT_SYSTEM && !ENABLE_LEGACY_INPUT_MANAGER
            m_ActiveInputMode = ActiveInputMode.InputSystemActions;
#endif
            m_MouseState = new MouseModel(m_RollingPointerId++);
            m_NavigationState = new NavigationModel();

            m_UseBuiltInInputSystemActions = m_EnableBuiltinActionsAsFallback && !InputActionReferencesAreSet();

            if (m_ActiveInputMode != ActiveInputMode.InputManagerBindings)
                EnableAllActions();
        }

        /// <summary>
        /// See <a href="https://docs.unity3d.com/ScriptReference/MonoBehaviour.OnDisable.html">MonoBehavior.OnDisable</a>.
        /// </summary>
        protected override void OnDisable()
        {
            RemovePointerEventData(m_MouseState.pointerId);

            if (m_ActiveInputMode != ActiveInputMode.InputManagerBindings)
                DisableAllActions();

            base.OnDisable();
        }

        /// <summary>
        /// Register an <see cref="IUIInteractor"/> with the UI system.
        /// Calling this will enable it to start interacting with UI.
        /// </summary>
        /// <param name="interactor">The <see cref="IUIInteractor"/> to use.</param>
        public void RegisterInteractor(IUIInteractor interactor)
        {
            for (var i = 0; i < m_RegisteredInteractors.Count; i++)
            {
                if (m_RegisteredInteractors[i].interactor == interactor)
                    return;
            }

            m_RegisteredInteractors.Add(new RegisteredInteractor(interactor, m_RollingPointerId++));
        }

        /// <summary>
        /// Unregisters an <see cref="IUIInteractor"/> with the UI system.
        /// This cancels all UI Interaction and makes the <see cref="IUIInteractor"/> no longer able to affect UI.
        /// </summary>
        /// <param name="interactor">The <see cref="IUIInteractor"/> to stop using.</param>
        public void UnregisterInteractor(IUIInteractor interactor)
        {
            for (var i = 0; i < m_RegisteredInteractors.Count; i++)
            {
                if (m_RegisteredInteractors[i].interactor == interactor)
                {
                    var registeredInteractor = m_RegisteredInteractors[i];
                    registeredInteractor.interactor = null;
                    m_RegisteredInteractors[i] = registeredInteractor;
                    return;
                }
            }
        }

        /// <summary>
        /// Gets an <see cref="IUIInteractor"/> from its corresponding Unity UI Pointer Id.
        /// This can be used to identify individual Interactors from the underlying UI Events.
        /// </summary>
        /// <param name="pointerId">A unique integer representing an object that can point at UI.</param>
        /// <returns>Returns the interactor associated with <paramref name="pointerId"/>.
        /// Returns <see langword="null"/> if no Interactor is associated (e.g. if it's a mouse event).</returns>
        public IUIInteractor GetInteractor(int pointerId)
        {
            for (var i = 0; i < m_RegisteredInteractors.Count; i++)
            {
                if (m_RegisteredInteractors[i].model.pointerId == pointerId)
                {
                    return m_RegisteredInteractors[i].interactor;
                }
            }

            return null;
        }

        /// <summary>Retrieves the UI Model for a selected <see cref="IUIInteractor"/>.</summary>
        /// <param name="interactor">The <see cref="IUIInteractor"/> you want the model for.</param>
        /// <param name="model">The returned model that reflects the UI state of the <paramref name="interactor"/>.</param>
        /// <returns>Returns <see langword="true"/> if the model was able to retrieved. Otherwise, returns <see langword="false"/>.</returns>
        public bool GetTrackedDeviceModel(IUIInteractor interactor, out TrackedDeviceModel model)
        {
            for (var i = 0; i < m_RegisteredInteractors.Count; i++)
            {
                if (m_RegisteredInteractors[i].interactor == interactor)
                {
                    model = m_RegisteredInteractors[i].model;
                    return true;
                }
            }

            model = new TrackedDeviceModel(-1);
            return false;
        }

        /// <inheritdoc />
        protected override void DoProcess()
        {
            if (m_EnableXRInput)
            {
                for (var i = 0; i < m_RegisteredInteractors.Count; i++)
                {
                    var registeredInteractor = m_RegisteredInteractors[i];

                    var oldTarget = registeredInteractor.model.implementationData.pointerTarget;

                    // If device is removed, we send a default state to unclick any UI
                    if (registeredInteractor.interactor == null)
                    {
                        registeredInteractor.model.Reset(false);
                        ProcessTrackedDevice(ref registeredInteractor.model, true);
                        RemovePointerEventData(registeredInteractor.model.pointerId);
                        m_RegisteredInteractors.RemoveAt(i--);
                    }
                    else
                    {
                        registeredInteractor.interactor.UpdateUIModel(ref registeredInteractor.model);
                        ProcessTrackedDevice(ref registeredInteractor.model);
                        m_RegisteredInteractors[i] = registeredInteractor;
                    }
                    // If hover target changed, send event
                    var newTarget = registeredInteractor.model.implementationData.pointerTarget;
                    if (oldTarget != newTarget)
                    {
                        using (m_UIHoverEventArgs.Get(out var args))
                        {
                            args.interactorObject = registeredInteractor.interactor;
                            args.deviceModel = registeredInteractor.model;
                            if (args.interactorObject != null && args.interactorObject is IUIHoverInteractor hoverInteractor)
                            {
                                if (oldTarget != null)
                                {
                                    args.uiObject = oldTarget;
                                    hoverInteractor.OnUIHoverExited(args);
                                }

                                if (newTarget != null)
                                {
                                    args.uiObject = newTarget;
                                    hoverInteractor.OnUIHoverEntered(args);
                                }
                            }
                        }
                    }
                }
            }

            // Touch needs to take precedence because of the mouse emulation layer
            var hasTouches = false;
            if (m_EnableTouchInput)
                hasTouches = ProcessTouches();

            if (m_EnableMouseInput && !hasTouches)
                ProcessMouse();

            ProcessNavigation();
        }

        void ProcessMouse()
        {
            if (m_ActiveInputMode != ActiveInputMode.InputManagerBindings)
            {
                if (m_UseBuiltInInputSystemActions)
                {
                    if (Mouse.current != null)
                    {
                        m_MouseState.position = Mouse.current.position.ReadValue();
                        m_MouseState.displayIndex = Mouse.current.displayIndex.ReadValue();
                        m_MouseState.scrollDelta = Mouse.current.scroll.ReadValue() * (1 / kPixelPerLine);
                        m_MouseState.leftButtonPressed = Mouse.current.leftButton.isPressed;
                        m_MouseState.rightButtonPressed = Mouse.current.rightButton.isPressed;
                        m_MouseState.middleButtonPressed = Mouse.current.middleButton.isPressed;
                    }
                }
                else
                {
                    if (IsActionEnabled(m_PointAction))
                        m_MouseState.position = m_PointAction.action.ReadValue<Vector2>();
                    if (IsActionEnabled(m_ScrollWheelAction))
                        m_MouseState.scrollDelta = m_ScrollWheelAction.action.ReadValue<Vector2>() * (1 / kPixelPerLine);
                    if (IsActionEnabled(m_LeftClickAction))
                        m_MouseState.leftButtonPressed = m_LeftClickAction.action.IsPressed();
                    if (IsActionEnabled(m_RightClickAction))
                        m_MouseState.rightButtonPressed = m_RightClickAction.action.IsPressed();
                    if (IsActionEnabled(m_MiddleClickAction))
                        m_MouseState.middleButtonPressed = m_MiddleClickAction.action.IsPressed();
                }
            }

            if (m_ActiveInputMode != ActiveInputMode.InputSystemActions && Input.mousePresent)
            {
                m_MouseState.position = Input.mousePosition;
                m_MouseState.scrollDelta = Input.mouseScrollDelta;
                m_MouseState.leftButtonPressed = Input.GetMouseButton(0);
                m_MouseState.rightButtonPressed = Input.GetMouseButton(1);
                m_MouseState.middleButtonPressed = Input.GetMouseButton(2);
            }

            ProcessMouseState(ref m_MouseState);
        }

        bool ProcessTouches()
        {
            var hasTouches = Input.touchCount > 0;
            if (!hasTouches)
                return false;

            var touchCount = Input.touchCount;
            for (var touchIndex = 0; touchIndex < touchCount; ++touchIndex)
            {
                var touch = Input.GetTouch(touchIndex);
                var registeredTouchIndex = -1;

                // Find if touch already exists
                for (var j = 0; j < m_RegisteredTouches.Count; j++)
                {
                    if (touch.fingerId == m_RegisteredTouches[j].touchId)
                    {
                        registeredTouchIndex = j;
                        break;
                    }
                }

                if (registeredTouchIndex < 0)
                {
                    // Not found, search empty pool
                    for (var j = 0; j < m_RegisteredTouches.Count; j++)
                    {
                        if (!m_RegisteredTouches[j].isValid)
                        {
                            // Re-use the Id
                            var pointerId = m_RegisteredTouches[j].model.pointerId;
                            m_RegisteredTouches[j] = new RegisteredTouch(touch, pointerId);
                            registeredTouchIndex = j;
                            break;
                        }
                    }

                    if (registeredTouchIndex < 0)
                    {
                        // No Empty slots, add one
                        registeredTouchIndex = m_RegisteredTouches.Count;
                        m_RegisteredTouches.Add(new RegisteredTouch(touch, m_RollingPointerId++));
                    }
                }

                var registeredTouch = m_RegisteredTouches[registeredTouchIndex];
                registeredTouch.model.selectPhase = touch.phase;
                registeredTouch.model.position = touch.position;
                m_RegisteredTouches[registeredTouchIndex] = registeredTouch;
            }

            for (var i = 0; i < m_RegisteredTouches.Count; i++)
            {
                var registeredTouch = m_RegisteredTouches[i];
                ProcessTouch(ref registeredTouch.model);
                if (registeredTouch.model.selectPhase == TouchPhase.Ended || registeredTouch.model.selectPhase == TouchPhase.Canceled)
                    registeredTouch.isValid = false;
                m_RegisteredTouches[i] = registeredTouch;
            }

            return true;
        }

        void ProcessNavigation()
        {
            if (m_ActiveInputMode != ActiveInputMode.InputManagerBindings)
            {
                if (m_UseBuiltInInputSystemActions)
                {
                    if (Gamepad.current != null)
                    {
                        // Combine left stick and dpad for navigation movement
                        m_NavigationState.move = Gamepad.current.leftStick.ReadValue() + Gamepad.current.dpad.ReadValue();
                        m_NavigationState.submitButtonDown = Gamepad.current.buttonSouth.isPressed;
                        m_NavigationState.cancelButtonDown = Gamepad.current.buttonEast.isPressed;
                    }
                    if (Joystick.current != null)
                    {
                        // Combine main joystick and hatswitch for navigation movement
                        m_NavigationState.move = Joystick.current.stick.ReadValue() +
                            (Joystick.current.hatswitch != null ? Joystick.current.hatswitch.ReadValue() : Vector2.zero);
                        m_NavigationState.submitButtonDown = Joystick.current.trigger.isPressed;
                        // This will always be false until we can rely on a secondary button from the joystick
                        m_NavigationState.cancelButtonDown = false;
                    }
                }
                else
                {
                    if (IsActionEnabled(m_NavigateAction))
                        m_NavigationState.move = m_NavigateAction.action.ReadValue<Vector2>();
                    if (IsActionEnabled(m_SubmitAction))
                        m_NavigationState.submitButtonDown = m_SubmitAction.action.WasPressedThisFrame();
                    if (IsActionEnabled(m_CancelAction))
                        m_NavigationState.cancelButtonDown = m_CancelAction.action.WasPressedThisFrame();
                }
            }

            if (m_ActiveInputMode != ActiveInputMode.InputSystemActions && (m_EnableGamepadInput || m_EnableJoystickInput) && Input.GetJoystickNames().Length > 0)
            {
                m_NavigationState.move = new Vector2(Input.GetAxis(m_HorizontalAxis), Input.GetAxis(m_VerticalAxis));
                m_NavigationState.submitButtonDown = Input.GetButton(m_SubmitButton);
                m_NavigationState.cancelButtonDown = Input.GetButton(m_CancelButton);
            }

            base.ProcessNavigationState(ref m_NavigationState);
        }

        bool InputActionReferencesAreSet()
        {
            return (m_PointAction != null ||
                m_LeftClickAction != null ||
                m_RightClickAction != null ||
                m_MiddleClickAction != null ||
                m_NavigateAction != null ||
                m_SubmitAction != null ||
                m_CancelAction != null ||
                m_ScrollWheelAction != null);
        }

        void EnableAllActions()
        {
            EnableInputAction(m_PointAction);
            EnableInputAction(m_LeftClickAction);
            EnableInputAction(m_RightClickAction);
            EnableInputAction(m_MiddleClickAction);
            EnableInputAction(m_NavigateAction);
            EnableInputAction(m_SubmitAction);
            EnableInputAction(m_CancelAction);
            EnableInputAction(m_ScrollWheelAction);
        }

        void DisableAllActions()
        {
            DisableInputAction(m_PointAction);
            DisableInputAction(m_LeftClickAction);
            DisableInputAction(m_RightClickAction);
            DisableInputAction(m_MiddleClickAction);
            DisableInputAction(m_NavigateAction);
            DisableInputAction(m_SubmitAction);
            DisableInputAction(m_CancelAction);
            DisableInputAction(m_ScrollWheelAction);
        }

        static bool IsActionEnabled(InputActionReference inputAction)
        {
            return inputAction != null && inputAction.action != null && inputAction.action.enabled;
        }

        static void EnableInputAction(InputActionReference inputAction)
        {
            if (inputAction == null || inputAction.action == null)
                return;
            inputAction.action.Enable();
        }

        static void DisableInputAction(InputActionReference inputAction)
        {
            if (inputAction == null || inputAction.action == null)
                return;
            inputAction.action.Disable();
        }

        void SetInputAction(ref InputActionReference inputAction, InputActionReference value)
        {
            if (Application.isPlaying && inputAction != null)
                inputAction.action?.Disable();

            inputAction = value;

            if (Application.isPlaying && isActiveAndEnabled && inputAction != null)
                inputAction.action?.Enable();
        }
    }
}

MouseModel.cs

using System;
using System.Collections.Generic;
using UnityEngine.EventSystems;
using UnityEngine.InputSystem.Controls;

namespace UnityEngine.XR.Interaction.Toolkit.UI
{
    /// <summary>
    /// A series of flags to determine if a button has been pressed or released since the last time checked.
    /// Useful for identifying press/release events that occur in a single frame or sample.
    /// </summary>
    [Flags]
    public enum ButtonDeltaState
    {
        /// <summary>
        /// No change since last time checked.
        /// </summary>
        NoChange = 0,

        /// <summary>
        /// Button pressed since last time checked.
        /// </summary>
        Pressed = 1 << 0,

        /// <summary>
        /// Button released since last time checked.
        /// </summary>
        Released = 1 << 1,
    }

    /// <summary>
    /// Represents the state of a single mouse button within the Unity UI (UGUI) system. Keeps track of various book-keeping regarding clicks, drags, and presses.
    /// Can be converted to and from PointerEventData for sending into Unity UI (UGUI).
    /// </summary>
    public struct MouseButtonModel
    {
        internal struct ImplementationData
        {
            /// <summary>
            /// Used to cache whether or not the current mouse button is being dragged.
            /// </summary>
            /// <seealso cref="PointerEventData.dragging"/>
            public bool isDragging { get; set; }

            /// <summary>
            /// Used to cache the last time this button was pressed.
            /// </summary>
            /// <seealso cref="PointerEventData.clickTime"/>
            public float pressedTime { get; set; }

            /// <summary>
            /// The position on the screen that this button was last pressed.
            /// In the same scale as <see cref="MouseModel.position"/>, and caches the same value as <see cref="PointerEventData.pressPosition"/>.
            /// </summary>
            /// <seealso cref="PointerEventData.pressPosition"/>
            public Vector2 pressedPosition { get; set; }

            /// <summary>
            /// The Raycast data from the time it was pressed.
            /// </summary>
            /// <seealso cref="PointerEventData.pointerPressRaycast"/>
            public RaycastResult pressedRaycast { get; set; }

            /// <summary>
            /// The last GameObject pressed on that can handle press or click events.
            /// </summary>
            /// <seealso cref="PointerEventData.pointerPress"/>
            public GameObject pressedGameObject { get; set; }

            /// <summary>
            /// The last GameObject pressed on regardless of whether it can handle events or not.
            /// </summary>
            /// <seealso cref="PointerEventData.rawPointerPress"/>
            public GameObject pressedGameObjectRaw { get; set; }

            /// <summary>
            /// The GameObject currently being dragged if any.
            /// </summary>
            /// <seealso cref="PointerEventData.pointerDrag"/>
            public GameObject draggedGameObject { get; set; }

            /// <summary>
            /// Resets this object to its default, unused state.
            /// </summary>
            public void Reset()
            {
                isDragging = false;
                pressedTime = 0f;
                pressedPosition = Vector2.zero;
                pressedRaycast = new RaycastResult();
                pressedGameObject = pressedGameObjectRaw = draggedGameObject = null;
            }
        }

        /// <summary>
        /// Used to store the current binary state of the button. When set, will also track the changes between calls of <see cref="OnFrameFinished"/> in <see cref="lastFrameDelta"/>.
        /// </summary>
        public bool isDown
        {
            get => m_IsDown;
            set
            {
                if (m_IsDown != value)
                {
                    m_IsDown = value;
                    lastFrameDelta |= value ? ButtonDeltaState.Pressed : ButtonDeltaState.Released;
                }
            }
        }

        /// <summary>
        /// A set of flags to identify the changes that have occurred between calls of <see cref="OnFrameFinished"/>.
        /// </summary>
        internal ButtonDeltaState lastFrameDelta { get; private set; }

        /// <summary>
        /// Resets this object to it's default, unused state.
        /// </summary>
        public void Reset()
        {
            lastFrameDelta = ButtonDeltaState.NoChange;
            m_IsDown = false;

            m_ImplementationData.Reset();
        }

        /// <summary>
        /// Call this on each frame in order to reset properties that detect whether or not a certain condition was met this frame.
        /// </summary>
        public void OnFrameFinished() => lastFrameDelta = ButtonDeltaState.NoChange;

        /// <summary>
        /// Fills a <see cref="PointerEventData"/> with this mouse button's internally cached values.
        /// </summary>
        /// <param name="eventData">These objects are used to send data through the Unity UI (UGUI) system.</param>
        public void CopyTo(PointerEventData eventData)
        {
            eventData.dragging = m_ImplementationData.isDragging;
            eventData.clickTime = m_ImplementationData.pressedTime;
            eventData.pressPosition = m_ImplementationData.pressedPosition;
            eventData.pointerPressRaycast = m_ImplementationData.pressedRaycast;
            eventData.pointerPress = m_ImplementationData.pressedGameObject;
            eventData.rawPointerPress = m_ImplementationData.pressedGameObjectRaw;
            eventData.pointerDrag = m_ImplementationData.draggedGameObject;
        }

        /// <summary>
        /// Fills this object with the values from a <see cref="PointerEventData"/>.
        /// </summary>
        /// <param name="eventData">These objects are used to send data through the Unity UI (UGUI) system.</param>
        public void CopyFrom(PointerEventData eventData)
        {
            m_ImplementationData.isDragging = eventData.dragging;
            m_ImplementationData.pressedTime = eventData.clickTime;
            m_ImplementationData.pressedPosition = eventData.pressPosition;
            m_ImplementationData.pressedRaycast = eventData.pointerPressRaycast;
            m_ImplementationData.pressedGameObject = eventData.pointerPress;
            m_ImplementationData.pressedGameObjectRaw = eventData.rawPointerPress;
            m_ImplementationData.draggedGameObject = eventData.pointerDrag;
        }

        bool m_IsDown;
        ImplementationData m_ImplementationData;
    }

    struct MouseModel
    {
        internal struct InternalData
        {
            /// <summary>
            /// This tracks the current GUI targets being hovered over.
            /// </summary>
            /// <seealso cref="PointerEventData.hovered"/>
            public List<GameObject> hoverTargets { get; set; }

            /// <summary>
            /// Tracks the current enter/exit target being hovered over at any given moment.
            /// </summary>
            /// <seealso cref="PointerEventData.pointerEnter"/>
            public GameObject pointerTarget { get; set; }

            public void Reset()
            {
                pointerTarget = null;

                if (hoverTargets == null)
                    hoverTargets = new List<GameObject>();
                else
                    hoverTargets.Clear();
            }
        }

        /// <summary>
        /// An Id representing a unique pointer.
        /// </summary>
        public int pointerId { get; }

        /// <summary>
        /// A boolean value representing whether any mouse data has changed this frame, meaning that events should be processed.
        /// </summary>
        /// <remarks>
        /// This only checks for changes in mouse state (<see cref="position"/>, <see cref="leftButton"/>, <see cref="rightButton"/>, <see cref="middleButton"/>, or <see cref="scrollDelta"/>).
        /// </remarks>
        public bool changedThisFrame { get; private set; }

        Vector2 m_Position;

        public Vector2 position
        {
            get => m_Position;
            set
            {
                if (m_Position != value)
                {
                    deltaPosition = value - m_Position;
                    m_Position = value;
                    changedThisFrame = true;
                }
            }
        }

        /// <summary>
        /// The pixel-space change in <see cref="position"/> since the last call to <see cref="OnFrameFinished"/>.
        /// </summary>
        public Vector2 deltaPosition { get; private set; }

        Vector2 m_ScrollDelta;

        /// <summary>
        /// The amount of scroll since the last call to <see cref="OnFrameFinished"/>.
        /// </summary>
        public Vector2 scrollDelta
        {
            get => m_ScrollDelta;
            set
            {
                if (m_ScrollDelta != value)
                {
                    m_ScrollDelta = value;
                    changedThisFrame = true;
                }
            }
        }

        MouseButtonModel m_LeftButton;

        /// <summary>
        /// Cached data and button state representing a left mouse button on a mouse.
        /// Used by Unity UI (UGUI) to keep track of persistent click, press, and drag states.
        /// </summary>
        public MouseButtonModel leftButton
        {
            get => m_LeftButton;
            set
            {
                changedThisFrame |= (value.lastFrameDelta != ButtonDeltaState.NoChange);
                m_LeftButton = value;
            }
        }

        /// <summary>
        /// Sets the pressed state of the left mouse button.
        /// </summary>
        public bool leftButtonPressed
        {
            set
            {
                changedThisFrame |= m_LeftButton.isDown != value;
                m_LeftButton.isDown = value;
            }
        }

        MouseButtonModel m_RightButton;

        /// <summary>
        /// Cached data and button state representing a right mouse button on a mouse.
        /// Unity UI (UGUI) uses this to keep track of persistent click, press, and drag states.
        /// </summary>
        public MouseButtonModel rightButton
        {
            get => m_RightButton;
            set
            {
                changedThisFrame |= (value.lastFrameDelta != ButtonDeltaState.NoChange);
                m_RightButton = value;
            }
        }

        /// <summary>
        /// Sets the pressed state of the right mouse button.
        /// </summary>
        public bool rightButtonPressed
        {
            set
            {
                changedThisFrame |= m_RightButton.isDown != value;
                m_RightButton.isDown = value;
            }
        }

        MouseButtonModel m_MiddleButton;

        /// <summary>
        /// Cached data and button state representing a middle mouse button on a mouse.
        /// Used by Unity UI (UGUI) to keep track of persistent click, press, and drag states.
        /// </summary>
        public MouseButtonModel middleButton
        {
            get => m_MiddleButton;
            set
            {
                changedThisFrame |= (value.lastFrameDelta != ButtonDeltaState.NoChange);
                m_MiddleButton = value;
            }
        }

        /// <summary>
        /// Sets the pressed state of the middle mouse button.
        /// </summary>
        public bool middleButtonPressed
        {
            set
            {
                changedThisFrame |= m_MiddleButton.isDown != value;
                m_MiddleButton.isDown = value;
            }
        }
      
        public int displayIndex
        {
            get => m_DisplayIndex;
            set
            {
                if (m_DisplayIndex != value)
                {
                    m_DisplayIndex = value;
                    changedThisFrame = true;
                }
            }
        }
      
        private int m_DisplayIndex;

        InternalData m_InternalData;
      

        public MouseModel(int pointerId)
        {
            this.pointerId = pointerId;
            changedThisFrame = false;
            m_Position = Vector2.zero;
            deltaPosition = Vector2.zero;
            m_ScrollDelta = Vector2.zero;

            m_LeftButton = new MouseButtonModel();
            m_RightButton = new MouseButtonModel();
            m_MiddleButton = new MouseButtonModel();
            m_LeftButton.Reset();
            m_RightButton.Reset();
            m_MiddleButton.Reset();
            m_DisplayIndex = 0;
            m_InternalData = new InternalData();
            m_InternalData.Reset();
        }

        /// <summary>
        /// Call this at the end of polling for per-frame changes.  This resets delta values, such as <see cref="deltaPosition"/>, <see cref="scrollDelta"/>, and <see cref="MouseButtonModel.lastFrameDelta"/>.
        /// </summary>
        public void OnFrameFinished()
        {
            changedThisFrame = false;
            deltaPosition = Vector2.zero;
            m_DisplayIndex = 0;
            m_ScrollDelta = Vector2.zero;
            m_LeftButton.OnFrameFinished();
            m_RightButton.OnFrameFinished();
            m_MiddleButton.OnFrameFinished();
        }

        public void CopyTo(PointerEventData eventData)
        {
            eventData.pointerId = pointerId;
            eventData.position = position;
            eventData.delta = deltaPosition;
            eventData.scrollDelta = scrollDelta;
            eventData.pointerEnter = m_InternalData.pointerTarget;
            eventData.hovered.Clear();
            eventData.hovered.AddRange(m_InternalData.hoverTargets);
#if UNITY_2022_3_OR_NEWER
            eventData.displayIndex = displayIndex;
#endif
        }

        public void CopyFrom(PointerEventData eventData)
        {
            var hoverTargets = m_InternalData.hoverTargets;
            m_InternalData.hoverTargets.Clear();
            m_InternalData.hoverTargets.AddRange(eventData.hovered);
            m_InternalData.hoverTargets = hoverTargets;
            m_InternalData.pointerTarget = eventData.pointerEnter;
#if UNITY_2022_3_OR_NEWER
            m_DisplayIndex = eventData.displayIndex;
#endif
        }
    }
}
1 Like

I just ran across this issue myself. I will test to see if this works for me and confirm later.

This bug has been replicated and is being investigated internally. Please stand by as we work out a fix.

1 Like

This worked for me but I had to make an additional edit to XRUIInputModule.cs. The new version is pasted below.

My project uses the built in input system actions, so in ProcessMouse() the if condition if (m_UseBuiltInInputSystemActions) equates to false. So with @jp-franssen 's solution I still wasn’t receiving mouse inputs on secondary monitors. But all I had to do was simply copy and paste the same line jeepee-ef added into the else statement and it worked. Thanks for figuring this out 2 weeks before I needed it @jp-franssen :smile: <3

using System.Collections.Generic;
using UnityEngine.InputSystem;
using UnityEngine.InputSystem.Controls;
using UnityEngine.XR.Interaction.Toolkit.Utilities.Pooling;

namespace UnityEngine.XR.Interaction.Toolkit.UI
{
    /// <summary>
    /// Matches the UI Model to the state of the Interactor.
    /// </summary>
    public interface IUIInteractor
    {
        /// <summary>
        /// Updates the current UI Model to match the state of the Interactor.
        /// </summary>
        /// <param name="model">The returned model that will match this Interactor.</param>
        void UpdateUIModel(ref TrackedDeviceModel model);

        /// <summary>
        /// Attempts to retrieve the current UI Model.
        /// </summary>
        /// <param name="model">The returned model that reflects the UI state of this Interactor.</param>
        /// <returns>Returns <see langword="true"/> if the model was able to retrieved. Otherwise, returns <see langword="false"/>.</returns>
        bool TryGetUIModel(out TrackedDeviceModel model);
    }

    /// <summary>
    /// Matches the UI Model to the state of the Interactor with support for hover events.
    /// </summary>
    public interface IUIHoverInteractor : IUIInteractor
    {
        /// <summary>
        /// The event that is called when the Interactor begins hovering over a UI element.
        /// </summary>
        /// <remarks>
        /// The <see cref="UIHoverEventArgs"/> passed to each listener is only valid while the event is invoked,
        /// do not hold a reference to it.
        /// </remarks>
        UIHoverEnterEvent uiHoverEntered { get; }

        /// <summary>
        /// The event that is called when this Interactor ends hovering over a UI element.
        /// </summary>
        /// <remarks>
        /// The <see cref="UIHoverEventArgs"/> passed to each listener is only valid while the event is invoked,
        /// do not hold a reference to it.
        /// </remarks>
        UIHoverExitEvent uiHoverExited { get; }

        /// <summary>
        /// The <see cref="XRUIInputModule"/> calls this method when the Interactor begins hovering over a UI element.
        /// </summary>
        /// <param name="args">Event data containing the UI element that is being hovered over.</param>
        /// <remarks>
        /// <paramref name="args"/> is only valid during this method call, do not hold a reference to it.
        /// </remarks>
        /// <seealso cref="OnUIHoverExited(UIHoverEventArgs)"/>
        void OnUIHoverEntered(UIHoverEventArgs args);

        /// <summary>
        /// The <see cref="XRUIInputModule"/> calls this method when the Interactor ends hovering over a UI element.
        /// </summary>
        /// <param name="args">Event data containing the UI element that is no longer hovered over.</param>
        /// <remarks>
        /// <paramref name="args"/> is only valid during this method call, do not hold a reference to it.
        /// </remarks>
        /// <seealso cref="OnUIHoverEntered(UIHoverEventArgs)"/>
        void OnUIHoverExited(UIHoverEventArgs args);
    }

    /// <summary>
    /// Custom class for input modules that send UI input in XR.
    /// </summary>
    [AddComponentMenu("Event/XR UI Input Module", 11)]
    [HelpURL(XRHelpURLConstants.k_XRUIInputModule)]
    public partial class XRUIInputModule : UIInputModule
    {
        struct RegisteredInteractor
        {
            public IUIInteractor interactor;
            public TrackedDeviceModel model;

            public RegisteredInteractor(IUIInteractor interactor, int deviceIndex)
            {
                this.interactor = interactor;
                model = new TrackedDeviceModel(deviceIndex);
            }
        }

        struct RegisteredTouch
        {
            public bool isValid;
            public int touchId;
            public TouchModel model;

            public RegisteredTouch(Touch touch, int deviceIndex)
            {
                touchId = touch.fingerId;
                model = new TouchModel(deviceIndex);
                isValid = true;
            }
        }

        /// <summary>
        /// Represents which Active Input Mode will be used in the situation where the Active Input Handling project setting is set to Both.
        /// </summary>
        /// /// <seealso cref="activeInputMode"/>
        public enum ActiveInputMode
        {
            /// <summary>
            /// Only use input polled through the built-in Unity Input Manager (Old).
            /// </summary>
            InputManagerBindings,

            /// <summary>
            /// Only use input polled from <see cref="InputActionReference"/> through the newer Input System package.
            /// </summary>
            InputSystemActions,

            /// <summary>
            /// Scan through input from both Unity Input Manager and Input System action references.
            /// Note: This may cause undesired effects or may impact performance if input configuration is duplicated.
            /// </summary>
            Both,
        }

#if !ENABLE_INPUT_SYSTEM || !ENABLE_LEGACY_INPUT_MANAGER
        [HideInInspector]
#endif
        [SerializeField]
        [Tooltip("Represents which Active Input Mode will be used in the situation where the Active Input Handling project setting is set to Both.")]
        ActiveInputMode m_ActiveInputMode;

        /// <summary>
        /// Configures which Active Input Mode will be used in the situation where the Active Input Handling project setting is set to Both.
        /// </summary>
        /// <seealso cref="ActiveInputMode"/>
        public ActiveInputMode activeInputMode
        {
            get => m_ActiveInputMode;
            set => m_ActiveInputMode = value;
        }

        [SerializeField, HideInInspector]
        [Tooltip("The maximum distance to ray cast with tracked devices to find hit objects.")]
        float m_MaxTrackedDeviceRaycastDistance = 1000f;

        [Header("Input Devices")]
        [SerializeField]
        [Tooltip("If true, will forward 3D tracked device data to UI elements.")]
        bool m_EnableXRInput = true;

        /// <summary>
        /// If <see langword="true"/>, will forward 3D tracked device data to UI elements.
        /// </summary>
        public bool enableXRInput
        {
            get => m_EnableXRInput;
            set => m_EnableXRInput = value;
        }

        [SerializeField]
        [Tooltip("If true, will forward 2D mouse data to UI elements.")]
        bool m_EnableMouseInput = true;

        /// <summary>
        /// If <see langword="true"/>, will forward 2D mouse data to UI elements.
        /// </summary>
        public bool enableMouseInput
        {
            get => m_EnableMouseInput;
            set => m_EnableMouseInput = value;
        }

        [SerializeField]
        [Tooltip("If true, will forward 2D touch data to UI elements.")]
        bool m_EnableTouchInput = true;

        /// <summary>
        /// If <see langword="true"/>, will forward 2D touch data to UI elements.
        /// </summary>
        public bool enableTouchInput
        {
            get => m_EnableTouchInput;
            set => m_EnableTouchInput = value;
        }

#if ENABLE_INPUT_SYSTEM
        [Header("Input System UI Actions")]
#else
        [HideInInspector]
#endif
        [SerializeField]
        [Tooltip("Pointer input action reference, such as a mouse or single-finger touch device.")]
        InputActionReference m_PointAction;
        /// <summary>
        /// The Input System action to use to move the pointer on the currently active UI. Must be a <see cref="Vector2Control"/> Control.
        /// </summary>
        public InputActionReference pointAction
        {
            get => m_PointAction;
            set => SetInputAction(ref m_PointAction, value);
        }

#if !ENABLE_INPUT_SYSTEM
        [HideInInspector]
#endif
        [SerializeField]
        [Tooltip("Left-click input action reference, typically the left button on a mouse.")]
        InputActionReference m_LeftClickAction;
        /// <summary>
        /// The Input System action to use to determine whether the left button of a pointer is pressed. Must be a <see cref="ButtonControl"/> Control.
        /// </summary>
        public InputActionReference leftClickAction
        {
            get => m_LeftClickAction;
            set => SetInputAction(ref m_LeftClickAction, value);
        }

#if !ENABLE_INPUT_SYSTEM
        [HideInInspector]
#endif
        [SerializeField]
        [Tooltip("Middle-click input action reference, typically the middle button on a mouse.")]
        InputActionReference m_MiddleClickAction;
        /// <summary>
        /// The Input System action to use to determine whether the middle button of a pointer is pressed. Must be a <see cref="ButtonControl"/> Control.
        /// </summary>
        public InputActionReference middleClickAction
        {
            get => m_MiddleClickAction;
            set => SetInputAction(ref m_MiddleClickAction, value);
        }

#if !ENABLE_INPUT_SYSTEM
        [HideInInspector]
#endif
        [SerializeField]
        [Tooltip("Right-click input action reference, typically the right button on a mouse.")]
        InputActionReference m_RightClickAction;
        /// <summary>
        /// The Input System action to use to determine whether the right button of a pointer is pressed. Must be a <see cref="ButtonControl"/> Control.
        /// </summary>
        public InputActionReference rightClickAction
        {
            get => m_RightClickAction;
            set => SetInputAction(ref m_RightClickAction, value);
        }

#if !ENABLE_INPUT_SYSTEM
        [HideInInspector]
#endif
        [SerializeField]
        [Tooltip("Scroll wheel input action reference, typically the scroll wheel on a mouse.")]
        InputActionReference m_ScrollWheelAction;
        /// <summary>
        /// The Input System action to use to move the pointer on the currently active UI. Must be a <see cref="Vector2Control"/> Control.
        /// </summary>
        public InputActionReference scrollWheelAction
        {
            get => m_ScrollWheelAction;
            set => SetInputAction(ref m_ScrollWheelAction, value);
        }

#if !ENABLE_INPUT_SYSTEM
        [HideInInspector]
#endif
        [SerializeField]
        [Tooltip("Navigation input action reference will change which UI element is currently selected to the one up, down, left of or right of the currently selected one.")]
        InputActionReference m_NavigateAction;
        /// <summary>
        /// The Input System action to use to navigate the currently active UI. Must be a <see cref="Vector2Control"/> Control.
        /// </summary>
        public InputActionReference navigateAction
        {
            get => m_NavigateAction;
            set => SetInputAction(ref m_NavigateAction, value);
        }

#if !ENABLE_INPUT_SYSTEM
        [HideInInspector]
#endif
        [SerializeField]
        [Tooltip("Submit input action reference will trigger a submission of the currently selected UI in the Event System.")]
        InputActionReference m_SubmitAction;
        /// <summary>
        /// The Input System action to use for submitting or activating a UI element. Must be a <see cref="ButtonControl"/> Control.
        /// </summary>
        public InputActionReference submitAction
        {
            get => m_SubmitAction;
            set => SetInputAction(ref m_SubmitAction, value);
        }

#if !ENABLE_INPUT_SYSTEM
        [HideInInspector]
#endif
        [SerializeField]
        [Tooltip("Cancel input action reference will trigger canceling out of the currently selected UI in the Event System.")]
        InputActionReference m_CancelAction;
        /// <summary>
        /// The Input System action to use for cancelling or backing out of a UI element. Must be a <see cref="ButtonControl"/> Control.
        /// </summary>
        public InputActionReference cancelAction
        {
            get => m_CancelAction;
            set => SetInputAction(ref m_CancelAction, value);
        }

#if !ENABLE_INPUT_SYSTEM
        [HideInInspector]
#endif
        [SerializeField]
        [Tooltip("When enabled, built-in Input System actions will be used if no Input System UI Actions are assigned.")]
        bool m_EnableBuiltinActionsAsFallback = true;
        /// <summary>
        /// When enabled, built-in Input System actions will be used if no Input System UI Actions are assigned. This uses the
        /// currently enabled Input System devices: <see cref="Mouse.current"/>, <see cref="Gamepad.current"/>, and <see cref="Joystick.current"/>.
        /// </summary>
        public bool enableBuiltinActionsAsFallback
        {
            get => m_EnableBuiltinActionsAsFallback;
            set
            {
                m_EnableBuiltinActionsAsFallback = value;
                m_UseBuiltInInputSystemActions = m_EnableBuiltinActionsAsFallback && !InputActionReferencesAreSet();
            }
        }

#if ENABLE_LEGACY_INPUT_MANAGER
        [Header("Input Manager (Old) Gamepad/Joystick Bindings")]
#else
        [HideInInspector]
#endif
        [SerializeField]
        [Tooltip("If true, will use the Input Manager (Old) configuration to forward gamepad data to UI elements.")]
        bool m_EnableGamepadInput = true;

        /// <summary>
        /// If <see langword="true"/>, will forward gamepad data to UI elements.
        /// </summary>
        public bool enableGamepadInput
        {
            get => m_EnableGamepadInput;
            set => m_EnableGamepadInput = value;
        }

#if !ENABLE_LEGACY_INPUT_MANAGER
        [HideInInspector]
#endif
        [SerializeField]
        [Tooltip("If true, will use the Input Manager (Old) configuration to forward joystick data to UI elements.")]
        bool m_EnableJoystickInput = true;

        /// <summary>
        /// If <see langword="true"/>, will forward joystick data to UI elements.
        /// </summary>
        public bool enableJoystickInput
        {
            get => m_EnableJoystickInput;
            set => m_EnableJoystickInput = value;
        }

#if !ENABLE_LEGACY_INPUT_MANAGER
        [HideInInspector]
#endif
        [SerializeField]
        [Tooltip("Name of the horizontal axis for gamepad/joystick UI navigation when using the old Input Manager.")]
        string m_HorizontalAxis = "Horizontal";

        /// <summary>
        /// Name of the horizontal axis for UI navigation when using the old Input Manager.
        /// </summary>
        public string horizontalAxis
        {
            get => m_HorizontalAxis;
            set => m_HorizontalAxis = value;
        }

#if !ENABLE_LEGACY_INPUT_MANAGER
        [HideInInspector]
#endif
        [SerializeField]
        [Tooltip("Name of the vertical axis for gamepad/joystick UI navigation when using the old Input Manager.")]
        string m_VerticalAxis = "Vertical";

        /// <summary>
        /// Name of the vertical axis for UI navigation when using the old Input Manager.
        /// </summary>
        public string verticalAxis
        {
            get => m_VerticalAxis;
            set => m_VerticalAxis = value;
        }

#if !ENABLE_LEGACY_INPUT_MANAGER
        [HideInInspector]
#endif
        [SerializeField]
        [Tooltip("Name of the gamepad/joystick button to use for UI selection or submission when using the old Input Manager.")]
        string m_SubmitButton = "Submit";

        /// <summary>
        /// Name of the gamepad/joystick button to use for UI selection or submission when using the old Input Manager.
        /// </summary>
        public string submitButton
        {
            get => m_SubmitButton;
            set => m_SubmitButton = value;
        }

#if !ENABLE_LEGACY_INPUT_MANAGER
        [HideInInspector]
#endif
        [SerializeField]
        [Tooltip("Name of the gamepad/joystick button to use for UI cancel or back commands when using the old Input Manager.")]
        string m_CancelButton = "Cancel";

        /// <summary>
        /// Name of the gamepad/joystick button to use for UI cancel or back commands when using the old Input Manager.
        /// </summary>
        public string cancelButton
        {
            get => m_CancelButton;
            set => m_CancelButton = value;
        }

        int m_RollingPointerId;
        bool m_UseBuiltInInputSystemActions;

        MouseModel m_MouseState;
        NavigationModel m_NavigationState;

        internal const float kPixelPerLine = 20f;

        readonly List<RegisteredTouch> m_RegisteredTouches = new List<RegisteredTouch>();
        readonly List<RegisteredInteractor> m_RegisteredInteractors = new List<RegisteredInteractor>();

        // Reusable event args
        readonly LinkedPool<UIHoverEventArgs> m_UIHoverEventArgs = new LinkedPool<UIHoverEventArgs>(() => new UIHoverEventArgs(), collectionCheck: false);

        /// <summary>
        /// See <a href="https://docs.unity3d.com/ScriptReference/MonoBehaviour.OnEnable.html">MonoBehavior.OnEnable</a>.
        /// </summary>
        protected override void OnEnable()
        {
            base.OnEnable();

            // Check active input mode is correct
#if !ENABLE_INPUT_SYSTEM && ENABLE_LEGACY_INPUT_MANAGER
            m_ActiveInputMode = ActiveInputMode.InputManagerBindings;
#elif ENABLE_INPUT_SYSTEM && !ENABLE_LEGACY_INPUT_MANAGER
            m_ActiveInputMode = ActiveInputMode.InputSystemActions;
#endif
            m_MouseState = new MouseModel(m_RollingPointerId++);
            m_NavigationState = new NavigationModel();

            m_UseBuiltInInputSystemActions = m_EnableBuiltinActionsAsFallback && !InputActionReferencesAreSet();

            if (m_ActiveInputMode != ActiveInputMode.InputManagerBindings)
                EnableAllActions();
        }

        /// <summary>
        /// See <a href="https://docs.unity3d.com/ScriptReference/MonoBehaviour.OnDisable.html">MonoBehavior.OnDisable</a>.
        /// </summary>
        protected override void OnDisable()
        {
            RemovePointerEventData(m_MouseState.pointerId);

            if (m_ActiveInputMode != ActiveInputMode.InputManagerBindings)
                DisableAllActions();

            base.OnDisable();
        }

        /// <summary>
        /// Register an <see cref="IUIInteractor"/> with the UI system.
        /// Calling this will enable it to start interacting with UI.
        /// </summary>
        /// <param name="interactor">The <see cref="IUIInteractor"/> to use.</param>
        public void RegisterInteractor(IUIInteractor interactor)
        {
            for (var i = 0; i < m_RegisteredInteractors.Count; i++)
            {
                if (m_RegisteredInteractors[i].interactor == interactor)
                    return;
            }

            m_RegisteredInteractors.Add(new RegisteredInteractor(interactor, m_RollingPointerId++));
        }

        /// <summary>
        /// Unregisters an <see cref="IUIInteractor"/> with the UI system.
        /// This cancels all UI Interaction and makes the <see cref="IUIInteractor"/> no longer able to affect UI.
        /// </summary>
        /// <param name="interactor">The <see cref="IUIInteractor"/> to stop using.</param>
        public void UnregisterInteractor(IUIInteractor interactor)
        {
            for (var i = 0; i < m_RegisteredInteractors.Count; i++)
            {
                if (m_RegisteredInteractors[i].interactor == interactor)
                {
                    var registeredInteractor = m_RegisteredInteractors[i];
                    registeredInteractor.interactor = null;
                    m_RegisteredInteractors[i] = registeredInteractor;
                    return;
                }
            }
        }

        /// <summary>
        /// Gets an <see cref="IUIInteractor"/> from its corresponding Unity UI Pointer Id.
        /// This can be used to identify individual Interactors from the underlying UI Events.
        /// </summary>
        /// <param name="pointerId">A unique integer representing an object that can point at UI.</param>
        /// <returns>Returns the interactor associated with <paramref name="pointerId"/>.
        /// Returns <see langword="null"/> if no Interactor is associated (e.g. if it's a mouse event).</returns>
        public IUIInteractor GetInteractor(int pointerId)
        {
            for (var i = 0; i < m_RegisteredInteractors.Count; i++)
            {
                if (m_RegisteredInteractors[i].model.pointerId == pointerId)
                {
                    return m_RegisteredInteractors[i].interactor;
                }
            }

            return null;
        }

        /// <summary>Retrieves the UI Model for a selected <see cref="IUIInteractor"/>.</summary>
        /// <param name="interactor">The <see cref="IUIInteractor"/> you want the model for.</param>
        /// <param name="model">The returned model that reflects the UI state of the <paramref name="interactor"/>.</param>
        /// <returns>Returns <see langword="true"/> if the model was able to retrieved. Otherwise, returns <see langword="false"/>.</returns>
        public bool GetTrackedDeviceModel(IUIInteractor interactor, out TrackedDeviceModel model)
        {
            for (var i = 0; i < m_RegisteredInteractors.Count; i++)
            {
                if (m_RegisteredInteractors[i].interactor == interactor)
                {
                    model = m_RegisteredInteractors[i].model;
                    return true;
                }
            }

            model = new TrackedDeviceModel(-1);
            return false;
        }

        /// <inheritdoc />
        protected override void DoProcess()
        {
            if (m_EnableXRInput)
            {
                for (var i = 0; i < m_RegisteredInteractors.Count; i++)
                {
                    var registeredInteractor = m_RegisteredInteractors[i];

                    var oldTarget = registeredInteractor.model.implementationData.pointerTarget;

                    // If device is removed, we send a default state to unclick any UI
                    if (registeredInteractor.interactor == null)
                    {
                        registeredInteractor.model.Reset(false);
                        ProcessTrackedDevice(ref registeredInteractor.model, true);
                        RemovePointerEventData(registeredInteractor.model.pointerId);
                        m_RegisteredInteractors.RemoveAt(i--);
                    }
                    else
                    {
                        registeredInteractor.interactor.UpdateUIModel(ref registeredInteractor.model);
                        ProcessTrackedDevice(ref registeredInteractor.model);
                        m_RegisteredInteractors[i] = registeredInteractor;
                    }
                    // If hover target changed, send event
                    var newTarget = registeredInteractor.model.implementationData.pointerTarget;
                    if (oldTarget != newTarget)
                    {
                        using (m_UIHoverEventArgs.Get(out var args))
                        {
                            args.interactorObject = registeredInteractor.interactor;
                            args.deviceModel = registeredInteractor.model;
                            if (args.interactorObject != null && args.interactorObject is IUIHoverInteractor hoverInteractor)
                            {
                                if (oldTarget != null)
                                {
                                    args.uiObject = oldTarget;
                                    hoverInteractor.OnUIHoverExited(args);
                                }

                                if (newTarget != null)
                                {
                                    args.uiObject = newTarget;
                                    hoverInteractor.OnUIHoverEntered(args);
                                }
                            }
                        }
                    }
                }
            }

            // Touch needs to take precedence because of the mouse emulation layer
            var hasTouches = false;
            if (m_EnableTouchInput)
                hasTouches = ProcessTouches();

            if (m_EnableMouseInput && !hasTouches)
                ProcessMouse();

            ProcessNavigation();
        }

        void ProcessMouse()
        {
            if (m_ActiveInputMode != ActiveInputMode.InputManagerBindings)
            {
                if (m_UseBuiltInInputSystemActions)             
                {
                    if (Mouse.current != null)
                    {
                        m_MouseState.position = Mouse.current.position.ReadValue();
                        // NOTE: Fixes mouse not interacting with on secondary monitors.
                        m_MouseState.displayIndex = Mouse.current.displayIndex.ReadValue();
                        // END NOTE
                        m_MouseState.scrollDelta = Mouse.current.scroll.ReadValue() * (1 / kPixelPerLine);
                        m_MouseState.leftButtonPressed = Mouse.current.leftButton.isPressed;
                        m_MouseState.rightButtonPressed = Mouse.current.rightButton.isPressed;
                        m_MouseState.middleButtonPressed = Mouse.current.middleButton.isPressed;
                    }
                }
                else
                {
                    if (IsActionEnabled(m_PointAction))
                        m_MouseState.position = m_PointAction.action.ReadValue<Vector2>();
                    if (IsActionEnabled(m_ScrollWheelAction))
                        m_MouseState.scrollDelta = m_ScrollWheelAction.action.ReadValue<Vector2>() * (1 / kPixelPerLine);
                    if (IsActionEnabled(m_LeftClickAction))
                        m_MouseState.leftButtonPressed = m_LeftClickAction.action.IsPressed();
                    if (IsActionEnabled(m_RightClickAction))
                        m_MouseState.rightButtonPressed = m_RightClickAction.action.IsPressed();
                    if (IsActionEnabled(m_MiddleClickAction))
                        m_MouseState.middleButtonPressed = m_MiddleClickAction.action.IsPressed();
                 
                    // NOTE: Fixes mouse not interacting with on secondary monitors.
                    m_MouseState.displayIndex = Mouse.current.displayIndex.ReadValue();
                    // END NOTE
                }
            }

            if (m_ActiveInputMode != ActiveInputMode.InputSystemActions && Input.mousePresent)
            {
                m_MouseState.position = Input.mousePosition;
                m_MouseState.scrollDelta = Input.mouseScrollDelta;
                m_MouseState.leftButtonPressed = Input.GetMouseButton(0);
                m_MouseState.rightButtonPressed = Input.GetMouseButton(1);
                m_MouseState.middleButtonPressed = Input.GetMouseButton(2);
            }

            ProcessMouseState(ref m_MouseState);
        }

        bool ProcessTouches()
        {
            var hasTouches = Input.touchCount > 0;
            if (!hasTouches)
                return false;

            var touchCount = Input.touchCount;
            for (var touchIndex = 0; touchIndex < touchCount; ++touchIndex)
            {
                var touch = Input.GetTouch(touchIndex);
                var registeredTouchIndex = -1;

                // Find if touch already exists
                for (var j = 0; j < m_RegisteredTouches.Count; j++)
                {
                    if (touch.fingerId == m_RegisteredTouches[j].touchId)
                    {
                        registeredTouchIndex = j;
                        break;
                    }
                }

                if (registeredTouchIndex < 0)
                {
                    // Not found, search empty pool
                    for (var j = 0; j < m_RegisteredTouches.Count; j++)
                    {
                        if (!m_RegisteredTouches[j].isValid)
                        {
                            // Re-use the Id
                            var pointerId = m_RegisteredTouches[j].model.pointerId;
                            m_RegisteredTouches[j] = new RegisteredTouch(touch, pointerId);
                            registeredTouchIndex = j;
                            break;
                        }
                    }

                    if (registeredTouchIndex < 0)
                    {
                        // No Empty slots, add one
                        registeredTouchIndex = m_RegisteredTouches.Count;
                        m_RegisteredTouches.Add(new RegisteredTouch(touch, m_RollingPointerId++));
                    }
                }

                var registeredTouch = m_RegisteredTouches[registeredTouchIndex];
                registeredTouch.model.selectPhase = touch.phase;
                registeredTouch.model.position = touch.position;
                m_RegisteredTouches[registeredTouchIndex] = registeredTouch;
            }

            for (var i = 0; i < m_RegisteredTouches.Count; i++)
            {
                var registeredTouch = m_RegisteredTouches[i];
                ProcessTouch(ref registeredTouch.model);
                if (registeredTouch.model.selectPhase == TouchPhase.Ended || registeredTouch.model.selectPhase == TouchPhase.Canceled)
                    registeredTouch.isValid = false;
                m_RegisteredTouches[i] = registeredTouch;
            }

            return true;
        }

        void ProcessNavigation()
        {
            if (m_ActiveInputMode != ActiveInputMode.InputManagerBindings)
            {
                if (m_UseBuiltInInputSystemActions)
                {
                    if (Gamepad.current != null)
                    {
                        // Combine left stick and dpad for navigation movement
                        m_NavigationState.move = Gamepad.current.leftStick.ReadValue() + Gamepad.current.dpad.ReadValue();
                        m_NavigationState.submitButtonDown = Gamepad.current.buttonSouth.isPressed;
                        m_NavigationState.cancelButtonDown = Gamepad.current.buttonEast.isPressed;
                    }
                    if (Joystick.current != null)
                    {
                        // Combine main joystick and hatswitch for navigation movement
                        m_NavigationState.move = Joystick.current.stick.ReadValue() +
                            (Joystick.current.hatswitch != null ? Joystick.current.hatswitch.ReadValue() : Vector2.zero);
                        m_NavigationState.submitButtonDown = Joystick.current.trigger.isPressed;
                        // This will always be false until we can rely on a secondary button from the joystick
                        m_NavigationState.cancelButtonDown = false;
                    }
                }
                else
                {
                    if (IsActionEnabled(m_NavigateAction))
                        m_NavigationState.move = m_NavigateAction.action.ReadValue<Vector2>();
                    if (IsActionEnabled(m_SubmitAction))
                        m_NavigationState.submitButtonDown = m_SubmitAction.action.WasPressedThisFrame();
                    if (IsActionEnabled(m_CancelAction))
                        m_NavigationState.cancelButtonDown = m_CancelAction.action.WasPressedThisFrame();
                }
            }

            if (m_ActiveInputMode != ActiveInputMode.InputSystemActions && (m_EnableGamepadInput || m_EnableJoystickInput) && Input.GetJoystickNames().Length > 0)
            {
                m_NavigationState.move = new Vector2(Input.GetAxis(m_HorizontalAxis), Input.GetAxis(m_VerticalAxis));
                m_NavigationState.submitButtonDown = Input.GetButton(m_SubmitButton);
                m_NavigationState.cancelButtonDown = Input.GetButton(m_CancelButton);
            }

            base.ProcessNavigationState(ref m_NavigationState);
        }

        bool InputActionReferencesAreSet()
        {
            return (m_PointAction != null ||
                m_LeftClickAction != null ||
                m_RightClickAction != null ||
                m_MiddleClickAction != null ||
                m_NavigateAction != null ||
                m_SubmitAction != null ||
                m_CancelAction != null ||
                m_ScrollWheelAction != null);
        }

        void EnableAllActions()
        {
            EnableInputAction(m_PointAction);
            EnableInputAction(m_LeftClickAction);
            EnableInputAction(m_RightClickAction);
            EnableInputAction(m_MiddleClickAction);
            EnableInputAction(m_NavigateAction);
            EnableInputAction(m_SubmitAction);
            EnableInputAction(m_CancelAction);
            EnableInputAction(m_ScrollWheelAction);
        }

        void DisableAllActions()
        {
            DisableInputAction(m_PointAction);
            DisableInputAction(m_LeftClickAction);
            DisableInputAction(m_RightClickAction);
            DisableInputAction(m_MiddleClickAction);
            DisableInputAction(m_NavigateAction);
            DisableInputAction(m_SubmitAction);
            DisableInputAction(m_CancelAction);
            DisableInputAction(m_ScrollWheelAction);
        }

        static bool IsActionEnabled(InputActionReference inputAction)
        {
            return inputAction != null && inputAction.action != null && inputAction.action.enabled;
        }

        static void EnableInputAction(InputActionReference inputAction)
        {
            if (inputAction == null || inputAction.action == null)
                return;
            inputAction.action.Enable();
        }

        static void DisableInputAction(InputActionReference inputAction)
        {
            if (inputAction == null || inputAction.action == null)
                return;
            inputAction.action.Disable();
        }

        void SetInputAction(ref InputActionReference inputAction, InputActionReference value)
        {
            if (Application.isPlaying && inputAction != null)
                inputAction.action?.Disable();

            inputAction = value;

            if (Application.isPlaying && isActiveAndEnabled && inputAction != null)
                inputAction.action?.Enable();
        }
    }
}
1 Like

Is there an issue tracker link?

Is it the same bug as this one? https://issuetracker.unity3d.com/issues/mouse-input-stops-working-to-click-world-space-ui-when-xr-subsystem-is-running

@ntt_ebehar here you go, I believe this issue is not the same:

1 Like