MouseMux Web SDK

Share

The MouseMux Web SDK is a modular ES6 JavaScript library for building multi-user web applications. It connects to the MouseMux desktop application via WebSocket to receive input events from multiple mice simultaneously.

The Web SDK is supported from MouseMux version 2.2.8 and onwards.


Introduction

The MouseMux Web SDK enables real-time multi-user interaction in web applications. Each physical mouse connected to the system generates independent input events, allowing multiple users to interact simultaneously with the same application.

Key capabilities:

  • Receive input from multiple mice, keyboards, and pen devices
  • Track each device with unique Hardware IDs (HWID)
  • Convert screen coordinates to element-relative positions
  • Auto-assign colors for visual user identification
  • Store custom data per device (brush settings, selections, etc.)
  • Built-in UI components for connection dialogs, status banners, and notifications
  • Internationalization support for 9 languages

Quick Start

// 1. Create SDK instance
const sdk = new MouseMuxSDK({
  appName: 'My App',
  appVersion: '1.0.0',
  autoConnect: false
});

// 2. Register all 11 required handlers
sdk.registerHandlers({
  onPointerMotion: (data) => {
    const pos = sdk.coords.screenToElement(data.x, data.y, canvas);
    if (pos) drawCursor(pos.x, pos.y, data.hwid);
  },
  onPointerButton: (data) => {
    if (data.data & 0x01) handleClick(data);  // Left button
  },
  onPointerWheel: (data) => {},
  onPointerPen: (data) => {},
  onKeyboard: (data) => {},
  onUserList: (data) => updateUserCount(data.users.length),
  onUserCreate: (data) => {},
  onUserDispose: (data) => {},
  onDeviceNew: (data) => {},
  onConnected: () => console.log('Connected!'),
  onDisconnected: () => console.log('Disconnected')
});

// 3. Connect
sdk.connect('ws://localhost:41001');

Requirements

  • MouseMux V2 must be installed and running
  • SDK mode enabled in MouseMux configuration
  • Modern browser with WebSocket and ES6 support
  • For Electron apps: preload bridge for accurate coordinate conversion

HTML Setup

<!DOCTYPE html>
<html>
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>My MouseMux App</title>
  <link rel="stylesheet" href="css/app.css">
</head>
<body>
  <div id="app-container"><!-- Your UI here --></div>

  <!-- SDK Path Configuration -->
  <script src="sdk-path.js"></script>

  <!-- Load SDK modules in order -->
  <script src="../websdk/sdk/core/events.js"></script>
  <script src="../websdk/sdk/core/connection.js"></script>
  <script src="../websdk/sdk/core/messages.js"></script>
  <script src="../websdk/sdk/state/handlers.js"></script>
  <script src="../websdk/sdk/state/devices.js"></script>
  <script src="../websdk/sdk/state/users.js"></script>
  <script src="../websdk/sdk/helpers/coords.js"></script>
  <script src="../websdk/sdk/helpers/hit-detection.js"></script>
  <script src="../websdk/sdk/helpers/cursors.js"></script>
  <script src="../websdk/sdk/helpers/click-bindings.js"></script>
  <script src="../websdk/sdk/input/simplified.js"></script>
  <script src="../websdk/sdk/input/native.js"></script>
  <script src="../websdk/sdk/index.js"></script>

  <!-- Optional: UI and i18n -->
  <script src="../websdk/sdk/i18n.js"></script>
  <script src="../websdk/sdk/ui.js"></script>

  <!-- Your app modules -->
  <script src="js/app-config.js"></script>
  <script src="js/app-state.js"></script>
  <script src="js/app-ui.js"></script>
  <script src="js/app-sdk.js"></script>
  <script src="js/app-init.js"></script>
</body>
</html>

Module Architecture

The SDK is organized into focused modules:

websdk/sdk/
├── core/                   # Core functionality
│   ├── events.js           # Event system (on/off/emit)
│   ├── connection.js       # WebSocket lifecycle
│   └── messages.js         # Message parsing and routing
│
├── state/                  # State management
│   ├── handlers.js         # Handler registration
│   ├── devices.js          # Device state and colors
│   └── users.js            # User management
│
├── helpers/                # UI helpers
│   ├── coords.js           # Coordinate conversion
│   ├── hit-detection.js    # Hit detection wrappers
│   ├── cursors.js          # Remote cursor visualization
│   └── click-bindings.js   # Declarative click bindings
│
├── input/                  # Input handling
│   ├── simplified.js       # High-level event wrappers
│   └── native.js           # Debug/offline mode
│
├── i18n.js                 # Internationalization
├── ui.js                   # UI components
└── index.js                # Module assembler

Handler Registration

All 11 handlers must be registered via registerHandlers() before connecting:

Handler Parameters Description
onPointerMotion {hwid, x, y} Pointer moved
onPointerButton {hwid, x, y, data} Button pressed/released
onPointerWheel {hwid, delta, direction} Scroll wheel used
onPointerPen {hwid, x, y, pressure, ...} Pen/stylus input
onKeyboard {hwid, vkey, message, scan, flags} Keyboard input
onUserList {users[]} Full user list received
onUserCreate {hwid_ms, hwid_kb, name} New user connected
onUserDispose {hwid_ms, hwid_kb} User disconnected
onDeviceNew {hwid, type} New device detected
onConnected - Connected to server
onDisconnected - Disconnected from server

For handlers you don't need, use empty functions: onPointerWheel: () => {}


Coordinate Conversion

MouseMux sends coordinates in physical screen pixels. The SDK provides helpers to convert to element-relative coordinates:

// Convert screen coords to element coords
const pos = sdk.coords.screenToElement(data.x, data.y, canvas);
if (pos) {
  drawAt(pos.x, pos.y);
}

// Check if point is in element
if (sdk.coords.isInElement(data.x, data.y, canvas)) {
  // Handle canvas interaction
}

// Find clicked element by selector
const btn = sdk.findClickedElement(data.x, data.y, '.btn');
if (btn) {
  btn.click();
}

// Check if accurate transform cache is available
if (sdk.coords.hasTransformCache()) {
  console.log('Coordinates are accurate');
}

Important: Always use sdk.coords.screenToElement() - never use raw x/y coordinates directly. The SDK handles multi-monitor layouts, DPI scaling, and Electron zoom factors.


Device Management

Each device (mouse, keyboard, pen) has tracked state:

// Get device state
const device = sdk.getDevice(hwid);
// {
//   hwid: 197992480,
//   buttonDown: true,
//   lastX: 500,
//   lastY: 300,
//   color: '#ef4444',
//   user: { name: 'John', ... }
// }

// Get all devices
const devices = sdk.getAllDevices();  // Returns Map

// Get last active device
const lastHwid = sdk.getLastActiveDevice();

// Check if device is local
if (sdk.isMyDevice(hwid)) {
  // This is one of the local user's devices
}

Custom Device Data

Store app-specific data per device:

// Store custom data per device
sdk.setDeviceData(hwid, 'brushColor', '#ff0000');
sdk.setDeviceData(hwid, 'brushSize', 10);
sdk.setDeviceData(hwid, 'selectedTool', 'pen');

// Retrieve custom data
const color = sdk.getDeviceData(hwid, 'brushColor');
const size = sdk.getDeviceData(hwid, 'brushSize');

// Get all custom data for device
const allData = sdk.getAllDeviceData(hwid);

Device Colors

Each device is auto-assigned a color from the palette:

// Get device color (auto-assigned from palette)
const color = sdk.getDeviceColor(hwid);  // '#ef4444'

// Set custom color palette
sdk.setColorPalette([
  '#3b82f6', '#ef4444', '#10b981', '#f59e0b',
  '#8b5cf6', '#ec4899', '#06b6d4', '#84cc16'
]);

// Get current palette
const palette = sdk.getColorPalette();

User Management

Users are physical persons with one or more devices:

// Get user associated with device
const user = sdk.getUserForDevice(hwid);
// {
//   id: number,
//   name: string,
//   devices: [{hwid, type, subtype, path, buttons}],
//   cursor: {x, y, type, size, hidden, theme},
//   color: {r, g, b}
// }

// Get friendly device name
const name = sdk.getDeviceName(hwid);  // "John's Mouse"

// Get all users
const users = sdk.getUsers();  // Returns Map

Input Event Data

Pointer Motion

onPointerMotion: (data) => {
  const hwid = data.hwid;    // Unique device ID
  const x = data.x;          // Screen X coordinate
  const y = data.y;          // Screen Y coordinate

  // Convert to element coordinates
  const pos = sdk.coords.screenToElement(x, y, canvas);
  if (pos) {
    // pos.x, pos.y are element-relative
  }
}

Pointer Button

onPointerButton: (data) => {
  const hwid = data.hwid;
  const buttonMask = data.data || 0;

  const leftPressed = (buttonMask & 0x01) !== 0;
  const rightPressed = (buttonMask & 0x02) !== 0;
  const middlePressed = (buttonMask & 0x04) !== 0;

  const device = sdk.getDevice(hwid);
  device.buttonDown = leftPressed;
}

Pen/Stylus Input

onPointerPen: (data) => {
  const hwid = data.hwid;
  const x = data.x;
  const y = data.y;
  const pressure = data.pressure;  // 0-1023

  // Normalize pressure to 0-1 range
  const normalizedPressure = pressure / 1023;

  // Calculate variable brush size
  const minSize = 2;
  const maxSize = 20;
  const size = minSize + (maxSize - minSize) * normalizedPressure;
}

Keyboard Input

onKeyboard: (data) => {
  const hwid = data.hwid;
  const vkey = data.vkey;              // Windows virtual key code
  const isDown = data.message === 0x100;  // WM_KEYDOWN
  const isUp = data.message === 0x101;    // WM_KEYUP

  // Handle specific keys
  if (vkey === 0x42 && isDown) {  // 'B' key
    setTool('brush');
  } else if (vkey === 0x45 && isDown) {  // 'E' key
    setTool('eraser');
  }
}

Click Bindings

Declaratively bind clicks to elements:

// Bind clicks to color swatches
sdk.bindClick('.color-swatch', (element, hwid) => {
  const color = element.dataset.color;
  sdk.setDeviceData(hwid, 'selectedColor', color);

  // Visual feedback
  element.classList.add('selected');
  element.style.borderColor = sdk.getDeviceColor(hwid);
});

// Bind clicks to answer cards (voting app)
sdk.bindClick('.answer-card', (element, hwid) => {
  const index = parseInt(element.dataset.index);
  state.votes.set(hwid, index);
  element.style.boxShadow = `0 0 10px ${sdk.getDeviceColor(hwid)}`;
  updateVoteDisplay();
});

// Clear all bindings
sdk.clearClickBindings();

Remote Cursors

Display cursors for all connected users:

// Enable remote cursor visualization
sdk.enableRemoteCursors(document.body, {
  showLabels: true,
  autoHide: false,
  customHTML: (hwid, device) => {
    return `<div style="color: ${device.color};">●</div>`;
  }
});

// Disable when done
sdk.disableRemoteCursors();

UI Module

The SDK includes ready-to-use UI components:

Connection Dialog

const result = await MouseMuxUI.showConnectionDialog({
  appName: 'My App',
  appDescription: 'Multi-user collaborative application',
  defaultUrl: 'ws://localhost:41001',
  showThemeOption: true,
  showLanguageOption: true,
  onThemeChange: (theme) => applyTheme(theme),
  onLanguageChange: (lang) => updateStrings()
});

if (result.action === 'connect') {
  sdk.connect(result.url);
} else if (result.action === 'skip') {
  // User chose offline mode
  sdk.enableNativeInput(document.body);
}

Status Banner

MouseMuxUI.showBanner({
  appName: 'My App',
  appVersion: '1.0.0',
  appDate: '2025-01-01',
  appTime: '14:30:00',
  sdk: sdk,
  showThemeToggle: true,
  showLanguageSelector: true,
  onThemeChange: (theme) => applyTheme(theme),
  onLanguageChange: (lang) => updateStrings()
});

Toast Notifications

MouseMuxUI.toast('Connected!', { type: 'success' });
MouseMuxUI.toast('Warning message', { type: 'warning' });
MouseMuxUI.toast('Error occurred', { type: 'error', duration: 5000 });
MouseMuxUI.toast('Info', { type: 'info' });

Internationalization

The SDK supports 9 languages: English, German, Spanish, French, Italian, Japanese, Korean, Dutch, and Chinese.

Initialization

// SDK locales only
await MouseMuxI18n.init({
  localesPath: '../websdk/locales',
  defaultLanguage: 'en'
});

// SDK + App locales
await MouseMuxI18n.init({
  localesPath: '../websdk/locales',
  appLocales: {
    path: './locales',
    namespace: 'myapp'
  }
});

Usage

// Get translations
MouseMuxI18n.t('sdk.connected');           // SDK string
MouseMuxI18n.t('myapp.brushSize');         // App string
MouseMuxI18n.t('myapp.greeting', { name: 'Alice' });  // With parameters

// Change language
await MouseMuxI18n.setLanguage('de');

// Get current language
const lang = MouseMuxI18n.getLanguage();

// Listen for language changes
MouseMuxI18n.on('languageChanged', (lang) => {
  updateUIStrings();
});

App Locale File Structure

{
  "_meta": {
    "language": "English",
    "code": "en",
    "version": "2.2.23"
  },
  "appName": "My App",
  "brushSize": "Brush Size",
  "clearCanvas": "Clear Canvas",
  "greeting": "Hello, {{name}}!"
}

Native Input (Debug Mode)

Test your app without MouseMux server running:

// Automatic fallback
try {
  await sdk.connectAsync('ws://localhost:41001', { timeout: 2000 });
} catch (err) {
  console.log('Server unavailable, using native input');
  sdk.enableNativeInput(document.body);
}

// Check if native input is active
if (sdk.isNativeInputEnabled()) {
  console.log('Running in debug mode');
}

// Disable native input
sdk.disableNativeInput();

Native input mode uses HWID 0x3FFF for the single debug device.


Electron Integration

For accurate coordinate conversion in Electron apps, set up the preload bridge:

preload.js

const { contextBridge, ipcRenderer } = require('electron');

contextBridge.exposeInMainWorld('mmxElectron', {
  getTransform: () => ipcRenderer.invoke('mmx-get-transform'),
  onTransformUpdate: (cb) => {
    const handler = (_, t) => cb(t);
    ipcRenderer.on('mmx-transform-update', handler);
    return () => ipcRenderer.removeListener('mmx-transform-update', handler);
  }
});

main.js

const { screen, ipcMain } = require('electron');

function pushTransform() {
  const bounds = win.getContentBounds();
  const transform = {
    zoomFactor: win.webContents.getZoomFactor(),
    contentBoundsDip: {
      x: bounds.x,
      y: bounds.y,
      width: bounds.width,
      height: bounds.height
    },
    displays: screen.getAllDisplays().map(d => ({
      id: d.id,
      scaleFactor: d.scaleFactor,
      boundsDip: d.bounds,
      boundsPhys: {
        x: d.bounds.x * d.scaleFactor,
        y: d.bounds.y * d.scaleFactor,
        width: d.bounds.width * d.scaleFactor,
        height: d.bounds.height * d.scaleFactor
      }
    }))
  };
  win.webContents.send('mmx-transform-update', transform);
}

// Debounce updates
const debounce = (fn, ms) => {
  let timeout;
  return (...args) => {
    clearTimeout(timeout);
    timeout = setTimeout(() => fn(...args), ms);
  };
};

win.on('resize', debounce(pushTransform, 50));
win.on('move', debounce(pushTransform, 50));
ipcMain.handle('mmx-get-transform', pushTransform);

App Architecture Pattern

All MouseMux apps follow this recommended structure:

File Organization

myapp/
├── index.html           # Entry point
├── sdk-path.js          # SDK path configuration
├── app.json             # App metadata
├── css/
│   └── app.css          # Styles
├── js/
│   ├── app-config.js    # Constants and configuration
│   ├── app-state.js     # State management
│   ├── app-ui.js        # UI helpers and rendering
│   ├── app-sdk.js       # SDK integration
│   └── app-init.js      # Initialization
└── locales/
    ├── en.json          # English strings
    ├── de.json          # German strings
    └── ...

Initialization Pattern

(async function initializeApp() {
  // Step 1: Load app metadata
  let appInfo = { name: 'My App', info: { make: '2.2.23' } };
  try {
    const response = await fetch('./app.json');
    if (response.ok) appInfo = await response.json();
  } catch (e) {
    console.warn('Could not load app.json');
  }

  // Step 2: Create SDK instance
  const sdk = new MouseMuxSDK({
    appName: appInfo.name,
    appVersion: appInfo.info?.make || '2.2.23',
    autoConnect: false
  });

  // Step 3: Register handlers
  sdk.registerHandlers({
    // All 11 handlers...
  });

  // Step 4: Initialize i18n
  await MouseMuxI18n.init({
    localesPath: MOUSEMUX_SDK_PATH + 'locales',
    appLocales: { path: './locales', namespace: 'myapp' }
  });

  // Step 5: Show connection dialog
  const result = await MouseMuxUI.showConnectionDialog({
    appName: appInfo.name,
    showThemeOption: true,
    showLanguageOption: true
  });

  // Step 6: Connect or enable native input
  if (result.action === 'connect') {
    sdk.connect(result.url);
  } else {
    sdk.enableNativeInput(document.body);
  }

  // Step 7: Show status banner
  MouseMuxUI.showBanner({
    appName: appInfo.name,
    sdk: sdk,
    showThemeToggle: true
  });
})();

API Reference

Connection Methods

Method Description
connect(url?) Connect to WebSocket server
connectAsync(url?, options?) Connect with Promise and timeout
disconnect() Disconnect from server
isConnected() Check connection status
send(type, params?) Send message to server

Device Methods

Method Description
getDevice(hwid) Get device state
getAllDevices() Get all devices as Map
getLastActiveDevice() Get most recent device HWID
isMyDevice(hwid) Check if local device
getMyDevices() Get all local HWIDs as Set
setDeviceData(hwid, key, value) Store custom data
getDeviceData(hwid, key) Get custom data
getAllDeviceData(hwid) Get all custom data
getDeviceColor(hwid) Get auto-assigned color
setColorPalette(colors) Set custom color palette
getColorPalette() Get current palette

User Methods

Method Description
getUserForDevice(hwid) Get user for device
getDeviceName(hwid) Get friendly device name
getUsers() Get all users as Map

Coordinate Methods

Method Description
coords.screenToElement(x, y, el) Convert to element coords
coords.isInElement(x, y, el) Check if point is in element
coords.hasTransformCache() Check conversion accuracy
findClickedElement(x, y, selector) Find element at coords

Input Methods

Method Description
bindClick(selector, callback) Bind click to selector
clearClickBindings() Remove all bindings
enableNativeInput(container) Enable native input mode
disableNativeInput() Disable native input mode
isNativeInputEnabled() Check if native input active

Cursor Methods

Method Description
enableRemoteCursors(container, opts) Show remote cursors
disableRemoteCursors() Hide remote cursors

Event System

Method Description
on(event, callback) Register event listener
off(event, callback) Remove event listener
emit(event, data) Emit event

Availability

The Web SDK is included with every MouseMux app. Download any app from our App Library to get started - each app contains a complete, working websdk/ folder structure that you can use as a reference or starting point for your own projects.

What's included:

  • Complete SDK modules (sdk/core/, sdk/state/, sdk/helpers/, sdk/input/)
  • UI components (sdk/ui.js)
  • Internationalization with 9 language packs (locales/)
  • Working example code you can study and modify

Example Applications

See these apps in our App Library for working examples:

  • Multi-Paint - Collaborative drawing with brush/eraser tools
  • Team Vote - Multi-user voting and quiz system
  • Whiteboard - Shared whiteboard with shapes and text
  • SCADA Simulator - Industrial control panel demonstration
  • Service Desk - Multi-user customer support interface
  • Sound Board - Collaborative music application

Each app demonstrates different SDK features and patterns. Download any app to explore its source code and SDK integration.


Troubleshooting

Connection Issues

Problem Solution
Cannot connect Verify MouseMux is running on ws://localhost:41001
Connection drops Check network stability, enable reconnect
Wrong URL Check WebSocket URL in connection dialog

Coordinate Issues

Problem Solution
Coordinates wrong Check sdk.coords.hasTransformCache()
Multi-monitor problems Ensure displays array is populated
Electron zoom issues Verify preload bridge is set up

Handler Issues

Problem Solution
Handlers not firing Call registerHandlers() before connect()
Missing handlers error Provide all 11 required handlers
Handler throws error Add try-catch in handler code

For more information or custom integration support, contact info@mousemux.com