Skip to main content

WowMouse AR

Introducing the mouse moment for spatial computing. Pair your headset with your smartwatch and unlock the ultimate controller - your hands.

WowMouse AR

Compatibility

Headsets

We support any spatial computing headset that supports mouse input. For the best experience, we recommend using Magic Leap 2. The following devices currently support auto-pairing:

StatusDevice
Magic Leap 2
Meta Quest
Meta Quest 3

To get listed and optimize default settings tailored for your platform:

Contact us

Smartwatches

For the best experience, we recommend Samsung Galaxy Watch 4 / 5 / 6 or Google Pixel Watch 2, the devices for which we have most thoroughly tested and optimized for.

Devices we have tested so far, and their status:

StatusDevice
Samsung Galaxy Watch 4 / 5 / 6 (All sizes and models)
TicWatch E3
TicWatch Pro 3
Google Pixel Watch 2
Google Pixel Watch 1

Google Pixel Watch (1st Gen.)

Currently, WowMouse can't be installed on Pixel Watch Gen 1, but we are working to open up support for it soon.

Apple Watch

Unfortunately, since watchOS has no support for Bluetooth HID mode, we are unable to provide an app for Apple Watch for now.

Workflow and Setup

  1. Update WowMouse to the most recent version
  2. Open WowMouse and complete the guided tutorial once you open the app for the first time
  3. Tap on Devices > Make Discoverable to select your headset from the devices list
  4. Open your headset system Bluetooth settings and search for your smartwatch
  5. Alternatively, you can scroll down and press Start Scan to establish a connection from your watch
  6. WowMouse should automatically switch to AR mode by changing to the 3D mouse icon
  7. Use your hand as a mouse
tip

If you want to make use of the auto mode switch feature make sure your device is in the list of supported devices and that it is named e.g. “Magic Leap 2”, “Meta Quest 3”, “Meta Quest Pro” in system settings

  1. Activate Settings > AR mode to manually connect a headset that is not listed
  2. When the 3D mouse icon turns green, you should be able to see a moving cursor and use tap to click or palm-up-tap to, e.g. go back home.

WowMouse AR for Your Unity App

tip

You can effortlessly integrate WowMouse AR into your existing app without installing any SDK. WowMouse AR taps into the mouse features of any operating system and Unity’s input system, ensuring a smooth and hassle-free integration process.

Add Mouse Input Actions

  1. Make sure under Project Settings > Player Settings > Active Input Handling is set to Input System Package (New)
  2. Check if there is already an Input Action Asset available in your project. If yes open it; if no create one via right-click Create > Input Action
  3. Create a Control Scheme called MouseInput and add Mouse as Required to the list as seen below Input Actions
  4. Create an Action Map called Mouse
  5. Add for each mouse action an item Input Actions
  6. Set for the Action Type for LeftClick, RightClick and MiddleClick to Button and set the initial state check.
    tip

    The naming of actions and bindings will become relevant for the mouse script later.

  7. Move and Scroll need to be set to Pass Through and Vector 2 Input Actions
  8. Add to each action a binding and select it’s correct path Input Actions
  9. Make sure to check the control scheme box for each binding

Additional Scripts for your Scene

Add a script to an empty GameObject called MouseHandler.cs and make sure Input Action Asset is linked as seen below. Unity

Click to see snippet

// This script handles various mouse input actions using Unity's Input System.
// It subscribes to mouse events such as left click, right click, middle click, mouse movement, and scrolling.
// The script also provides public events that other scripts can subscribe to in order to respond to these mouse actions.

using UnityEngine;
using UnityEngine.InputSystem;
using System;

public class MouseHandler : MonoBehaviour
{
// Reference to the Input Actions asset
public InputActionAsset inputActions;

// Input actions for different mouse events
private InputAction leftClickAction;
private InputAction rightClickAction;
private InputAction middleClickAction;
private InputAction moveAction;
private InputAction scrollAction;

private void Awake()
{
// Find the action map for the mouse
var mouseActionMap = inputActions.FindActionMap("Mouse");
if (mouseActionMap == null)
{
Debug.LogError("Mouse action map not found!");
return;
}

// Find individual actions within the mouse action map
leftClickAction = mouseActionMap.FindAction("LeftClick");
rightClickAction = mouseActionMap.FindAction("RightClick");
middleClickAction = mouseActionMap.FindAction("MiddleClick");
moveAction = mouseActionMap.FindAction("Move");
scrollAction = mouseActionMap.FindAction("Scroll");

// Check if any of the actions are null
if (leftClickAction == null || rightClickAction == null || middleClickAction == null || moveAction == null || scrollAction == null)
{
Debug.LogError("One or more mouse actions not found!");
return;
}

// Subscribe to the performed and canceled events for each action
leftClickAction.performed += HandleLeftClickPerformed;
leftClickAction.canceled += HandleLeftClickReleased;
rightClickAction.performed += HandleRightClickPerformed;
rightClickAction.canceled += HandleRightClickReleased;
middleClickAction.performed += HandleMiddleClickPerformed;
middleClickAction.canceled += HandleMiddleClickReleased;

moveAction.performed += HandleMouseMove;
scrollAction.performed += HandleMouseScroll;
}

private void OnEnable()
{
// Ensure all actions are not null before enabling them
if (leftClickAction == null || rightClickAction == null || middleClickAction == null || moveAction == null || scrollAction == null)
{
Debug.LogError("One or more mouse actions are null in OnEnable!");
return;
}

// Enable all actions
leftClickAction.Enable();
rightClickAction.Enable();
middleClickAction.Enable();
moveAction.Enable();
scrollAction.Enable();
}

private void OnDisable()
{
// Ensure all actions are not null before disabling them
if (leftClickAction == null || rightClickAction == null || middleClickAction == null || moveAction == null || scrollAction == null)
{
Debug.LogError("One or more mouse actions are null in OnDisable!");
return;
}

// Disable all actions
leftClickAction.Disable();
rightClickAction.Disable();
middleClickAction.Disable();
moveAction.Disable();
scrollAction.Disable();
}

// Events for mouse actions
public event Action OnLeftClickPerformed;
public event Action OnLeftClickReleased;
public event Action OnRightClickPerformed;
public event Action OnRightClickReleased;
public event Action OnMiddleClickPerformed;
public event Action OnMiddleClickReleased;
public event Action<Vector2> OnMouseMove;
public event Action<Vector2> OnMouseScroll;

// Handlers for mouse actions
private void HandleLeftClickPerformed(InputAction.CallbackContext context)
{
Debug.Log("Left mouse button pressed!");
OnLeftClickPerformed?.Invoke();
}

private void HandleLeftClickReleased(InputAction.CallbackContext context)
{
Debug.Log("Left mouse button released!");
OnLeftClickReleased?.Invoke();
}

private void HandleRightClickPerformed(InputAction.CallbackContext context)
{
Debug.Log("Right mouse button pressed!");
OnRightClickPerformed?.Invoke();
}

private void HandleRightClickReleased(InputAction.CallbackContext context)
{
Debug.Log("Right mouse button released!");
OnRightClickReleased?.Invoke();
}

private void HandleMiddleClickPerformed(InputAction.CallbackContext context)
{
Debug.Log("Middle mouse button pressed!");
OnMiddleClickPerformed?.Invoke();
}

private void HandleMiddleClickReleased(InputAction.CallbackContext context)
{
Debug.Log("Middle mouse button released!");
OnMiddleClickReleased?.Invoke();
}

private void HandleMouseMove(InputAction.CallbackContext context)
{
Vector2 mouseDelta = context.ReadValue<Vector2>();
//Debug.Log($"Mouse moved: {mouseDelta}");
OnMouseMove?.Invoke(mouseDelta);
}

private void HandleMouseScroll(InputAction.CallbackContext context)
{
Vector2 scrollDelta = context.ReadValue<Vector2>();
//Debug.Log($"Mouse scrolled: {scrollDelta}");
OnMouseScroll?.Invoke(scrollDelta);
}
}


Add another script to another empty GameObject called ClickProvider.cs and make sure Mouse Handler is linked as seen below Unity

Click to see snippet

// This script, ClickProvider, subscribes to mouse click events (left and right clicks) from a MouseHandler component.
// It handles enabling and disabling these subscriptions when the script is enabled or disabled in Unity.

using UnityEngine;

public class ClickProvider : MonoBehaviour
{
// Reference to the MouseHandler component, set in the inspector
[SerializeField] private MouseHandler mouseHandler;

// Called when the script is enabled
private void OnEnable()
{
// If mouseHandler is not assigned, exit the method
if (mouseHandler == null) return;

// Subscribe to the mouse click events
mouseHandler.OnLeftClickPerformed += OnLeftClickPerformed;
mouseHandler.OnRightClickPerformed += OnRightClickPerformed;
}

// Called when the script is disabled
private void OnDisable()
{
// If mouseHandler is not assigned, exit the method
if (mouseHandler == null) return;

// Unsubscribe from the mouse click events
mouseHandler.OnLeftClickPerformed -= OnLeftClickPerformed;
mouseHandler.OnRightClickPerformed -= OnRightClickPerformed;
}

// Handler for the left click event
private void OnLeftClickPerformed()
{
// Add logic for left click handling here
}

// Handler for the right click event
private void OnRightClickPerformed()
{
// Add logic for right click handling here
}
}

Add another script to another empty GameObject called CursorProvider.cs and make sure Mouse Handler and MainCamera is linked as seen below Unity

Click to see snippet

// This script, CursorProvider, handles the positioning and rotation of a cursor object based on mouse movement.
// It subscribes to mouse movement events from a MouseHandler component and adjusts the cursor's transform accordingly.
// The script ensures the cursor stays within specified clamping angles and maintains a relative height to the main camera.

using UnityEngine;

public class CursorProvider : MonoBehaviour
{
// Reference to the main camera transform
[SerializeField] private Transform mainCamera;

// Reference to the MouseHandler script
[SerializeField] private MouseHandler mouseHandler;

// Height of the elbow relative to the main camera
[SerializeField] private float elbowHeight = .5f;

// Clamping angles for cursor movement
[SerializeField] private float clampAngleHorizontal, clampAngleUp, clampAngleDown;



// Subscribe to the OnMouseMove event when the script is enabled
private void OnEnable()
{
if (mouseHandler == null) return; // Exit if mouseHandler is not assigned
mouseHandler.OnMouseMove += OnMouseMove; // Add OnMouseMove method to the event
}

// Unsubscribe from the OnMouseMove event when the script is disabled
private void OnDisable()
{
if (mouseHandler == null) return; // Exit if mouseHandler is not assigned
mouseHandler.OnMouseMove -= OnMouseMove; // Remove OnMouseMove method from the event
}

// Method to handle mouse movement
private void OnMouseMove(Vector2 mouseDelta)
{
// Ignore mouse movements outside the screen bounds
if (mouseDelta.x < 0 || mouseDelta.x > Screen.width || mouseDelta.y < 0 || mouseDelta.y > Screen.height)
{
return;
}

// Normalize mouse position to screen dimensions
var mouseScreenPosition = new Vector2(mouseDelta.x / Screen.width, mouseDelta.y / Screen.height);

// Calculate target rotation based on mouse position and clamping angles
var targetRot = new Vector3(
Mathf.Lerp(clampAngleUp, -clampAngleDown, mouseScreenPosition.y),
Mathf.Lerp(-clampAngleHorizontal, clampAngleHorizontal, mouseScreenPosition.x) + mainCamera.eulerAngles.y,
0
);

// Apply the calculated rotation to the transform
transform.eulerAngles = targetRot;

// Set the position of the transform based on the main camera position and elbow height
transform.position = new Vector3(mainCamera.position.x, mainCamera.position.y * elbowHeight, mainCamera.position.z);
}
}

Add an empty child to CursorProvider called zOffset and adjust its Z-Position to define the default cursor distance Unity

Add another child called WhiteCircle with a Sprite Renderer. Link your desired cursor image to it. Unity

tip

Clamping Angles control the area on the screen where your cursor can move. The larger the canvas, the quicker the cursor movement. For optimal interaction, we suggest expanding the canvas by 20% beyond your interaction zone. Additionally, implementing a head-following script can enhance the user experience. At this stage, we will keep the tutorial simple and not dive into more details for ease of implementation.

If you need help or more example scripts don’t hesitate to ask on our Discord: https://discord.doublepoint.com/