MouseMux Web SDK

Share

Modular ES6 JavaScript library for building multi-user web applications.

Supported from MouseMux v2.2.8+  |  Current SDK: v2.2.36


Introduction

The 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
  • Create virtual users for programmatic input injection
  • Screen mirroring and coordinate forwarding for remote desktop apps
  • Window info, tracking, and control (show/hide/minimize/maximize/move/resize and more)
  • Unified input architecture -- same handlers work for server and native input

Quick Start

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

// 2. Register all 10 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.button & MouseMuxSDK.BUTTON_LEFT_DOWN) handleClick(data);
    },
    onPointerWheel:   () => {},
    onPointerPen:     () => {},
    onKeyboard:       () => {},
    onUserList:       (data) => updateUserCount(data.users.length),
    onUserCreate:     () => {},
    onUserDispose:    () => {},
    onConnected:      () => console.log('Connected!'),
    onDisconnected:   () => console.log('Disconnected')
});

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

Requirements

Requirement Details
MouseMux V2 Must be installed and running
SDK mode Enabled in MouseMux configuration
Browser Modern browser with WebSocket and ES6 support
Electron Optional -- 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

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
|   +-- forwarder.js        # Coordinate forwarding (screen mirror/sharing)
|   +-- hit-detection.js    # Hit detection wrappers
|   +-- cursors.js          # Remote cursor visualization
|   +-- click-bindings.js   # Declarative click bindings
|
+-- input/                  # Input handling
|   +-- simplified.js       # Simplified event handlers
|   +-- native.js           # Debug/offline mode
|
+-- i18n.js                 # Internationalization
+-- ui.js                   # UI components
+-- index.js                # Module assembler

Module Dependencies

events.js (no deps)
    |
handlers.js (no deps)
    |
connection.js --> events, handlers
    |
devices.js --> users (optional)
    |
users.js --> events, connection
    |
messages.js --> events, handlers, devices, users, connection
    |
coords.js (standalone)
    |
forwarder.js --> coords, users (via sdk instance)
    |
hit-detection.js --> coords
    |
cursors.js --> events, devices, users
    |
click-bindings.js --> events, hit-detection
    |
simplified.js --> events, devices
    |
native.js --> messages
    |
index.js (assembles all)

Connection & Handlers

Handler Registration

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

Handler Data Fields Description
onPointerMotion hwid, userId, x, y, user, deviceType, inputSource Pointer moved
onPointerButton hwid, userId, x, y, button, user, deviceType, inputSource Button pressed/released
onPointerWheel hwid, userId, x, y, delta, horizontal, user, deviceType, inputSource Scroll wheel
onPointerPen hwid, userId, x, y, pressure, tilt_x, tilt_y, rotation, user, deviceType, inputSource Pen/stylus input
onKeyboard hwid, userId, vkey, message, scan, flags, user, deviceType, inputSource Keyboard input
onUserList users[] Full user list received
onUserCreate hwid_ms, hwid_kb, name New user connected
onUserDispose hwid_ms, hwid_kb User disconnected
onConnected url Connected to server
onDisconnected -- Disconnected from server

Input event fields:

Field Description
hwid Device hardware ID (different for mouse vs keyboard)
userId User ID -- same for mouse and keyboard from the same user. Use this to identify users.
user Full user object, or null if user list not requested
deviceType 'mouse', 'keyboard', or 'pen'
inputSource 'server' for MouseMux input, 'native' for browser input

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

Optional Handlers

Handler Description
onServerShutdown Server shutting down (data: {reason})
onServerTimeoutWarning Timeout warning (data: {minutes}; return true to suppress default alert)
onServerTimeoutStopped Session ended due to timeout (data: {reason})
onDeviceNew SDK saw a new HWID for the first time (data: {hwid, data})
onUserMap Keyboard mapped to existing user (data: {action, userId, hwid_ms, hwid_kb, name})
onUserUpdate User state changed (data: {action, userId, ...})
onUserChanged Any user state change -- create/map/dispose/update
onScreenList Screen configuration received (data: {virtual, screens[]})
onScreenChange Screen configuration changed
onWindowInfo Tracked window moved/resized/changed (data: {hwnd, pid, title, ...})
onWindowDestroyed Tracked window was destroyed (data: {hwnd})
onServerMode Server mode changed (data: {mode[]})

Connection Flow

// Async connection with error handling
try {
    await sdk.connectAsync('ws://localhost:41001', { timeout: 5000 });
    console.log('Connected!');
} catch (err) {
    console.error('Connection failed:', err.message);
    sdk.enableNativeInput(document.body);
}

Login Protocol

The connection handshake has two parts:

1. Server Info (Server -> App)

When the WebSocket opens, the server sends server.info.notify.M2A containing server metadata and a machine fingerprint:

{
    type: 'server.info.notify.M2A',
    version: '3.0.1',
    protocol_version: '2.2.45',
    mode: 4,
    buildDate: 'Mar 15 2026',
    buildTime: '11:31:56',
    systemId: 'A1B2C3D4-E5F60718-9A0B1C2D-3E4F5061-000000000000000000000000-000'
}

2. Client Login (App -> Server)

The SDK automatically sends a login request:

{
    type: 'client.login.request.A2M',
    appName: 'Your App',
    appVersion: '1.0.0',
    appBuildDate: '2026-03-15',
    sdkVersion: '1.0.0',
    sdkBuildDate: '2026-03-15'
}

Listen for the server info event:

sdk.on('server.info.notify.M2A', (data) => {
    console.log('Server:', data.version, '| System:', data.systemId);
});

Keepalive

The server sends periodic keepalive pings. The SDK handles pong responses automatically -- no app code required.

Silent Auto-Connect & Server Probe

// Try auto-connect silently, show dialog only on failure
const autoConnected = await sdk.tryAutoConnect();
if (!autoConnected) {
    const result = await MouseMuxUI.showConnectionDialog({ ... });
}

// Or probe first
try {
    const info = await MouseMuxSDK.probe('ws://localhost:41001');
    console.log('Server running: v' + info.version);
    sdk.connect('ws://localhost:41001');
} catch (err) {
    console.log('Server not available');
}

Graceful Disconnect

// Graceful shutdown - waits for server to clean up virtual users and captures
await sdk.disconnectAsync('user');

Button Events

The SDK sends button events (transitions), not cumulative state. Each button has paired DOWN and UP constants:

Constant Value Event
BUTTON_LEFT_DOWN 0x01 Left button pressed
BUTTON_LEFT_UP 0x02 Left button released
BUTTON_RIGHT_DOWN 0x04 Right button pressed
BUTTON_RIGHT_UP 0x08 Right button released
BUTTON_MIDDLE_DOWN 0x10 Middle button pressed
BUTTON_MIDDLE_UP 0x20 Middle button released
BUTTON_X1_DOWN 0x40 X1/Back pressed
BUTTON_X1_UP 0x80 X1/Back released
BUTTON_X2_DOWN 0x100 X2/Forward pressed
BUTTON_X2_UP 0x200 X2/Forward released

All constants are accessed via MouseMuxSDK.BUTTON_*.

Usage

const { BUTTON_LEFT_DOWN, BUTTON_LEFT_UP, BUTTON_RIGHT_DOWN } = MouseMuxSDK;

onPointerButton: (data) => {
    if (data.button & BUTTON_LEFT_DOWN)  handleClick(data.x, data.y, data.userId);
    if (data.button & BUTTON_LEFT_UP)    handleRelease(data.hwid);
    if (data.button & BUTTON_RIGHT_DOWN) showContextMenu(data);
}

Tracking Button State

Since events are transitions, track cumulative state yourself:

const { BUTTON_LEFT_DOWN, BUTTON_LEFT_UP } = MouseMuxSDK;

onPointerButton: (data) => {
    const device = sdk.getDevice(data.hwid);
    if (data.button & BUTTON_LEFT_DOWN) device.buttonDown = true;
    if (data.button & BUTTON_LEFT_UP)   device.buttonDown = false;
}

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();

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.

Two Conversion Paths

Path Accuracy How it works
Electron (recommended) Accurate Main process pushes transform cache on window move/resize. Zero IPC per event. Handles multi-monitor + mixed DPI + zoom.
Browser (fallback) Approximate Uses window.devicePixelRatio and window.screen*. Works for single monitor, same-DPI setups.

Hardware ID Ranges

Range Hex Description
0 - 4095 0x0000-0x0FFF Physical hardware devices
12288 - 13311 0x3000-0x33FF SDK server-assigned devices
13312 - 14335 0x3400-0x37FF SDK client-side virtual users
16383 0x3FFF Native input (debug mode)
24576 - 26623 0x6000-0x67FF RustDesk remote devices

Device Management

Device State

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

Custom Data

// Store per-device app data
sdk.setDeviceData(hwid, 'brushColor', '#ff0000');
sdk.setDeviceData(hwid, 'brushSize', 10);

// Retrieve
const color = sdk.getDeviceData(hwid, 'brushColor');
const all   = sdk.getAllDeviceData(hwid);

Device Colors

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

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

Device Queries

const devices  = sdk.getAllDevices();         // Map of all devices
const lastHwid = sdk.getLastActiveDevice();   // Most recently active HWID
const isLocal  = sdk.isMyDevice(hwid);        // Is this one of my devices?
const myHwids  = sdk.getMyDevices();          // Set of local HWIDs

User Management

Users are physical persons with one or more devices (mouse + keyboard):

// Get user for a device
const user = sdk.getUserForDevice(hwid);
// {
//   id: number,
//   name: string,          // Color-based name: "Red", "Blue", etc.
//   flag: number,
//   flags: ['active', 'root'],
//   devices: [{ hwid, type, subtype, path, buttons, udev }],
//   cursor: { x, y, type, size, hidden, theme },
//   color: { r, g, b }
// }

// Get user ID from any device HWID (mouse or keyboard)
const userId = sdk.getUserId(hwid);

// Check if two devices belong to the same user
if (sdk.isSameUser(mouseHwid, keyboardHwid)) { /* same person */ }

// Get all HWIDs for a user
const hwids = sdk.getDevicesForUser(userId);

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

// Request user list from server (most apps need this)
sdk.requestUserList();

User Lifecycle Events

Action Handler When
create onUserCreate Mouse arrives, new user created
map onUserMap (optional) Keyboard mapped to existing user
dispose onUserDispose User removed

All changes also trigger onUserChanged(data) where data.action indicates the change type.

Pointer Subtypes

Value Description
mouse Standard mouse
pen_external External pen/stylus
pen_internal Built-in pen (tablet)
touch Touch input
touchpad Touchpad

User Flags

Flag Description
active User is active
root Admin user
locked User locked
captured Device captured (SDK)
cursor_hidden Cursor hidden
cursor_move_locked Movement locked
cursor_type_locked Type locked
macro_recording Recording macro
macro_playing Playing macro

Virtual Users

Create programmatic users without a server connection. Each virtual user gets a deterministic cursor color and name tag.

sdk.enableVirtualInput();

// Create virtual users
const alice = sdk.createVirtualUser('Alice');
// { hwid_ms: 0x3400, hwid_kb: 0x3401, name: 'Alice', color: '#e6194b' }

const bob = sdk.createVirtualUser('Bob');
// { hwid_ms: 0x3402, hwid_kb: 0x3403, name: 'Bob', color: '#3cb44b' }

// Inject input events
sdk.injectVirtualPointerMotion(alice.hwid_ms, 500, 300);
sdk.injectVirtualPointerButton(alice.hwid_ms, 500, 300, MouseMuxSDK.BUTTON_LEFT_DOWN);
sdk.injectVirtualPointerWheel(bob.hwid_ms, 200, 100, 120);
sdk.injectVirtualKeyboard(alice.hwid_kb, 0x41, 0x100);  // 'A' key down

// Convenience: instant down+up click
sdk.injectVirtualClick(alice.hwid_ms, 500, 300);

// Update name
sdk.setVirtualUserName(alice.hwid_ms, 'Alice (host)');

// Cleanup
sdk.destroyVirtualUser(alice.hwid_ms);
sdk.disableVirtualInput();  // destroys all remaining

Virtual users integrate with remote cursors automatically:

sdk.enableRemoteCursors(document.body, { showLabels: true });
sdk.enableVirtualInput();
const vu = sdk.createVirtualUser('Remote Viewer');
sdk.injectVirtualPointerMotion(vu.hwid_ms, 400, 200);  // cursor appears

Colors are deterministic by HWID slot -- destroying and recreating at the same slot gives the same color.


Screen Mirroring & Coordinate Forwarding

For screen sharing, remote desktop, and mirroring apps, the SDK's CoordinateForwarder maps coordinates between a video element showing a captured screen and the target screen.

Two Forwarding Modes

Mode How it works When to use
Manual Client-side JS: screenToElement -> normalize -> de-normalize to target screen Full client-side control
Server Raw physical coords + transform ID sent to server; server does the mapping Production use

Both modes use (w-1) normalization to match the server's integer math exactly.

Usage

// 1. Connect and get screen list
await sdk.connectAsync('ws://localhost:41001');
sdk.requestScreenList();

// 2. Create forwarder
const fwd = sdk.createCoordinateForwarder(videoElement, screenIndex, {
    mode: 'server'
});
await fwd.activate();

// 3. Forward coordinates in your handler
onPointerMotion: (data) => {
    const params = fwd.buildMotionParams(vuMouseHwid, data.x, data.y);
    if (params) sdk.send('pointer.motion.request.A2M', params);
}

// 4. Cleanup
fwd.destroy();

CoordinateForwarder Properties

Property Type Description
mode 'manual' or 'server' Forwarding mode
active boolean Whether currently active
transformId number or null Server-side transform ID (server mode)
targetScreen object or null Target screen from screen list
element HTMLElement Source DOM element
screenIndex number Target screen index

Use Cases

  • Screen mirroring -- Display a captured screen, let users interact via virtual cursors
  • Remote desktop -- Forward input from a shared screen view back to the host
  • Multi-screen management -- Live previews with interactive forwarding
  • Presentation control -- Display presenter's screen with audience annotation
  • KVM-style apps -- Control multiple machines from a single interface

Window Info, Tracking & Control

Query Window Info

const info = await sdk.requestWindowInfo(hwnd);
// {
//   hwnd: 12345, pid: 9876,
//   title: 'Untitled - Notepad',
//   monitor: 0, dpi: 144,
//   state: 'normal', visible: true,
//   window: { x: 100, y: 200, w: 800, h: 600 },
//   client: { x: 108, y: 230, w: 784, h: 561 }
// }

Track Window Changes

const info = await sdk.trackWindow(hwnd);

sdk.registerHandlers({
    // ... required handlers ...
    onWindowInfo: (data) => {
        console.log('Window changed:', data.title, data.state);
    },
    onWindowDestroyed: (data) => {
        console.log('Window destroyed:', data.hwnd);
    }
});

sdk.untrackWindow(hwnd);  // stop tracking

Control Windows

// Convenience methods
await sdk.showWindow(hwnd);
await sdk.hideWindow(hwnd);
await sdk.minimizeWindow(hwnd);
await sdk.maximizeWindow(hwnd);
await sdk.restoreWindow(hwnd);
await sdk.foregroundWindow(hwnd);
await sdk.focusWindow(hwnd);
await sdk.topWindow(hwnd);
await sdk.enableWindow(hwnd);
await sdk.disableWindow(hwnd);
await sdk.closeWindow(hwnd);
await sdk.moveWindow(hwnd, 100, 200);
await sdk.resizeWindow(hwnd, 800, 600);
await sdk.setWindowTransparent(hwnd, 128);   // 50% opacity
await sdk.setWindowTopmost(hwnd);
await sdk.clearWindowTopmost(hwnd);
await sdk.setWindowClickthrough(hwnd);
await sdk.clearWindowClickthrough(hwnd);

// Or use the generic method
await sdk.controlWindow(hwnd, 'minimize');
await sdk.controlWindow(hwnd, 'move', { x: 100, y: 200 });
await sdk.controlWindow(hwnd, 'resize', { w: 800, h: 600 });
await sdk.controlWindow(hwnd, 'transparent', { alpha: 128 });

Window Control Actions

Action Description Params
show Show window --
hide Hide window --
minimize Minimize to taskbar --
maximize Maximize window --
restore Restore from min/max --
foreground Bring to foreground --
focus Set keyboard focus --
top Bring to top of Z-order --
enable Enable mouse/keyboard input --
disable Disable mouse/keyboard input --
close Close window --
move Move to position x, y
resize Resize window w, h
transparent Set opacity alpha (0-255)
topmost Always on top --
no_topmost Remove always on top --
clickthrough Make click-through --
no_clickthrough Restore mouse input --

Unified Input Architecture

The same handlers work for both MouseMux server input and native browser input:

sdk.registerHandlers({
    onPointerMotion: (data) => {
        // Works for BOTH server and native input
        // data.inputSource: 'server' or 'native'
        const pos = sdk.coords.screenToElement(data.x, data.y, canvas);
        if (pos) handleMotion(pos.x, pos.y, data.hwid);
    },
    onPointerButton: (data) => {
        if (data.button & MouseMuxSDK.BUTTON_LEFT_DOWN) handleClick(data);
    }
    // ...
});

// Connect to server OR enable native input - handlers work either way
if (result.action === 'connect') {
    sdk.connect(result.url);
} else {
    sdk.enableNativeInput(document.body);
}

Rules:

  • Never add separate native addEventListener calls
  • Never use useMouseMux flags in handlers
  • Use data.inputSource if you need to distinguish sources
  • Use sdk.bindClick() for UI buttons (works for both modes)

Input Events

Pointer Motion

onPointerMotion: (data) => {
    const pos = sdk.coords.screenToElement(data.x, data.y, canvas);
    if (pos) {
        // pos.x, pos.y are element-relative
    }
}

Pen/Stylus

onPointerPen: (data) => {
    const pressure = data.pressure / 1023;  // normalize to 0-1
    const minSize = 2, maxSize = 20;
    const size = minSize + (maxSize - minSize) * pressure;
}

Keyboard

onKeyboard: (data) => {
    const isDown = data.message === 0x100;  // WM_KEYDOWN
    const isUp   = data.message === 0x101;  // WM_KEYUP

    if (data.vkey === 0x42 && isDown) setTool('brush');   // 'B'
    if (data.vkey === 0x45 && isDown) setTool('eraser');  // 'E'
}

Click Bindings

Declaratively bind clicks to elements:

sdk.bindClick('.color-swatch', (element, hwid) => {
    const color = element.dataset.color;
    sdk.setDeviceData(hwid, 'selectedColor', color);
    element.style.borderColor = sdk.getDeviceColor(hwid);
});

sdk.bindClick('.answer-card', (element, hwid) => {
    state.votes.set(hwid, parseInt(element.dataset.index));
    element.style.boxShadow = '0 0 10px ' + sdk.getDeviceColor(hwid);
    updateVoteDisplay();
});

sdk.clearClickBindings();  // remove all

Remote Cursors

sdk.enableRemoteCursors(document.body, {
    showLabels: true,
    autoHide: false,
    customHTML: (hwid, device) => {
        return '<div style="color:' + device.color + ';">&#x25CF;</div>';
    }
});

sdk.disableRemoteCursors();

UI Module

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') {
    sdk.enableNativeInput(document.body);
}

Status Banner

MouseMuxUI.showBanner({
    appName: 'My App',
    appVersion: '1.0.0',
    appDate: '2026-03-15',
    appTime: '11:31:56',
    sdk: sdk,
    showThemeToggle: true,
    showLanguageSelector: true
});

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' });

Confirmation Dialogs

const confirmed = await ui.confirm('Are you sure?', {
    title: 'Confirm Action',
    confirmText: 'Yes',
    cancelText: 'No'
});

const danger = await ui.confirmDanger('Delete all data?', {
    title: 'Danger',
    confirmText: 'Delete'
});

Internationalization

9 languages supported: English, German, Spanish, French, Italian, Japanese, Korean, Dutch, Chinese.

Setup

// 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

MouseMuxI18n.t('sdk.connected');                         // SDK string
MouseMuxI18n.t('myapp.brushSize');                       // App string
MouseMuxI18n.t('sdk.timeoutWarning', { minutes: 5 });   // With params

await MouseMuxI18n.setLanguage('de');
const lang = MouseMuxI18n.getLanguage();

MouseMuxI18n.getInstance().on('languageChanged', (lang) => {
    updateUIStrings();
});

Locale File Format

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

Native Input (Debug Mode)

Test your app without a MouseMux server:

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

if (sdk.isNativeInputEnabled()) {
    console.log('Running in debug mode');
}

sdk.disableNativeInput();

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


System ID

The server sends a systemId string on connect -- a machine fingerprint that uniquely identifies the host system.

Format

{CPUID}-{MAC}-{MACHINEGUID}-{SID}-{LICENSE}

Four 8-character uppercase hex fields (zero-padded), followed by a variable-length license string.

Example: A1B2C3D4-E5F60718-9A0B1C2D-3E4F5061-000000000000000000000000-000

Access

const sysId = sdk.getSystemId();

if (sdk.serverInfo) {
    console.log('System ID:', sdk.serverInfo.systemId);
    console.log('Version:', sdk.serverInfo.version);
}

sdk.on('server.info.notify.M2A', (data) => {
    console.log('System ID:', data.systemId);
});

Parsing

function parseSystemId(systemId) {
    const parts = systemId.split('-');
    return {
        cpuid:       parts[0],
        mac:         parts[1],
        machineGuid: parts[2],
        sid:         parts[3],
        license:     parts.slice(4).join('-')
    };
}

Electron Integration

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);
    },
    onShutdown: (cb) => {
        const handler = async () => {
            await cb();
            ipcRenderer.send('mmx-shutdown-complete');
        };
        ipcRenderer.on('mmx-shutdown', handler);
        return () => ipcRenderer.removeListener('mmx-shutdown', handler);
    }
});

Important: The onShutdown handler ensures the SDK can send client.logout.request.A2M and wait for the response before the window closes. Without this, virtual users and captures may be orphaned.

main.js

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

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

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);

// Graceful shutdown
let shutdownComplete = false;

ipcMain.on('mmx-shutdown-complete', () => {
    shutdownComplete = true;
    win.destroy();
});

win.on('close', (e) => {
    if (!shutdownComplete) {
        e.preventDefault();
        win.webContents.send('mmx-shutdown');
        setTimeout(() => { shutdownComplete = true; win.destroy(); }, 2000);
    }
});

App Architecture Pattern

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 Sequence

Apps must follow this exact order:

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

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

    // 3. Register handlers (before connection dialog!)
    sdk.registerHandlers({ /* all 10 handlers */ });

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

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

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

    // 7. Show status banner
    MouseMuxUI.showBanner({
        appName: appInfo.name,
        appVersion: appInfo.info?.make,
        appDate: appInfo.date,
        appTime: appInfo.time,
        sdk: sdk,
        showThemeToggle: true
    });
})();

app.json Mode Field

Declare which operating modes your app needs:

{
    "mode": {
        "hard": ["multiplex"],
        "hint": ["switched"],
        "help": "Works best in Switched mode for independent cursors"
    }
}
Field Type Description
hard string[] Required modes -- app will not function without these
hint string[] Suggested modes -- app works best with these
help string Human-readable explanation

Mode values: "native", "switched", "multiplex", "multi-keyboard"


API Reference

Constructor Options

Option Type Default Description
appName string 'Unknown App' Application name (sent to server on login)
appVersion string '0.0.0' Application version
appBuildDate string 'unknown' Application build date
url string 'ws://localhost:41001' WebSocket server URL
autoConnect boolean true Auto-connect on construction
strictValidation boolean false Enable strict field validation on messages

Connection Methods

Method Returns Description
connect(url?) void Connect to WebSocket server
connectAsync(url?, options?) Promise Connect with Promise and timeout
tryAutoConnect() Promise<boolean> Try auto-connect silently
disconnect() void Disconnect from server
disconnectAsync(reason?) Promise Graceful disconnect with logout
isConnected() boolean Check connection status
send(type, params?) boolean Send message to server
sendLog(level, message) boolean Send log message to server

Device Methods

Method Returns Description
getDevice(hwid) object Get device state
getAllDevices() Map Get all devices
getLastActiveDevice() number Get last active HWID
isMyDevice(hwid) boolean Check if local device
getMyDevices() Set Get all local HWIDs
setDeviceData(hwid, key, value) void Store custom data
getDeviceData(hwid, key) any Get custom data
getAllDeviceData(hwid) object Get all custom data
getDeviceColor(hwid) string Get auto-assigned color
setColorPalette(colors) void Set custom color palette
getColorPalette() string[] Get current palette

User Methods

Method Returns Description
requestUserList() boolean Request user list from server
getUserId(hwid) number Get user ID for any device
isSameUser(hwid1, hwid2) boolean Check if same user
getDevicesForUser(userId) number[] Get all HWIDs for a user
getUserForDevice(hwid) object Get full user object
getDeviceName(hwid) string Get friendly device name
getUsers() Map Get all users
getStats() object Get event statistics
getSystemId() string Get system ID of connected server
getServerInfo(options?) Promise Get server info
requestServerInfo() void Request server info
requestServerPath(what, options?) Promise Resolve path key to filesystem path
requestHardwareIDs(options?) Promise Request virtual HWIDs
requestServerMode() void Request current server mode

Screen & Transform Methods

Method Returns Description
requestScreenList() boolean Request monitor/screen list
activateTransform(params) boolean Activate coordinate transform
updateTransform(params) boolean Update transform viewport
deactivateTransform(params) boolean Deactivate transform
createCoordinateForwarder(el, screenIdx, opts?) CoordinateForwarder Create coordinate forwarder

Window Methods

Method Returns Description
requestWindowInfo(hwnd, options?) Promise<Object> One-shot window info query
trackWindow(hwnd, options?) Promise<Object> Subscribe to live window updates
untrackWindow(hwnd) boolean Unsubscribe from window updates
controlWindow(hwnd, action, params?, options?) Promise<Object> Control a window
showWindow(hwnd) Promise Show window
hideWindow(hwnd) Promise Hide window
minimizeWindow(hwnd) Promise Minimize window
maximizeWindow(hwnd) Promise Maximize window
restoreWindow(hwnd) Promise Restore window
foregroundWindow(hwnd) Promise Bring to foreground
focusWindow(hwnd) Promise Set keyboard focus
topWindow(hwnd) Promise Bring to top
closeWindow(hwnd) Promise Close window
moveWindow(hwnd, x, y) Promise Move window
resizeWindow(hwnd, w, h) Promise Resize window
setWindowTransparent(hwnd, alpha) Promise Set opacity (0-255)
setWindowTopmost(hwnd) Promise Always on top
clearWindowTopmost(hwnd) Promise Remove always on top
setWindowClickthrough(hwnd) Promise Make click-through
clearWindowClickthrough(hwnd) Promise Restore mouse input

Coordinate Methods (via sdk.coords)

Method Returns Description
screenToElement(x, y, el) {x,y} or null Convert to element coords
screenToContentCss(x, y) {x,y} or null Convert to CSS coords
isInElement(x, y, el) boolean Check if point is in element
elementAtPoint(x, y) Element or null Get element at screen coords
hasTransformCache() boolean Check conversion accuracy
hasElectronBridge() boolean Check if Electron bridge available
onTransformUpdate(cb) function Subscribe to transform updates
getElementPhysicalBounds(el) {x,y,w,h} or null Get element bounds in physical pixels
findClickedElement(x, y, selector) Element or null Find element at coords

Input Methods

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

Virtual Input Methods

Method Returns Description
enableVirtualInput() void Enable virtual input mode
disableVirtualInput() void Disable and destroy all virtual users
isVirtualInputEnabled() boolean Check if enabled
createVirtualUser(name?) {hwid_ms, hwid_kb, name, color} Create virtual user
destroyVirtualUser(hwid_ms) void Destroy a virtual user
getVirtualUsers() Map Get all active virtual users
setVirtualUserName(hwid_ms, name) void Update display name
injectVirtualPointerMotion(hwid, x, y) void Inject pointer motion
injectVirtualPointerButton(hwid, x, y, button) void Inject button event
injectVirtualPointerWheel(hwid, x, y, delta) void Inject wheel event
injectVirtualKeyboard(hwid, vkey, message, scan?, flags?) void Inject keyboard event
injectVirtualClick(hwid, x, y, domButton?) void Instant down+up click

Cursor Methods

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

Event System

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

Static Methods

Method Returns Description
MouseMuxSDK.probe(url, options?) Promise Test server availability

WebSocket Message Protocol

Message Convention

  • *.request.A2M -- App to MouseMux (outgoing commands)
  • *.notify.M2A -- MouseMux to App (incoming events)

All A2M commands return a response with ok: true/false and optional error string.

M2A Messages (Server -> App)

Message Description
server.info.notify.M2A Server info (version, systemId, protocol_version)
server.mode.notify.M2A Server mode (array: ["multiplex", "multi-keyboard"])
server.ping.notify.M2A Keepalive ping (SDK auto-responds)
server.shutdown.notify.M2A Server shutting down
server.timeout.warning.notify.M2A Timeout warning
server.timeout.stopped.notify.M2A Session ended due to timeout
screen.list.notify.M2A Screen/monitor configuration
screen.change.notify.M2A Screen config changed
user.list.notify.M2A Full user list
user.create.notify.M2A User created
user.dispose.notify.M2A User removed
user.changed.notify.M2A User state changed
pointer.motion.notify.M2A Pointer moved
pointer.button.notify.M2A Button changed
pointer.wheel.notify.M2A Wheel scroll
pointer.pen.notify.M2A Pen input
keyboard.key.notify.M2A Key event
window.info.notify.M2A Tracked window changed
window.destroyed.notify.M2A Tracked window destroyed

A2M Messages (App -> Server)

Message Description
client.login.request.A2M Login to server (automatic)
client.logout.request.A2M Logout from server
client.pong.request.A2M Pong response (automatic)
client.log.request.A2M Send log message
user.create.request.A2M Create virtual user
user.dispose.request.A2M Remove virtual user
user.list.request.A2M Request user list
user.hwid.request.A2M Request available HWIDs
pointer.motion.request.A2M Move pointer
pointer.button.request.A2M Send button event
pointer.wheel.request.A2M Send wheel event
pointer.capture.request.A2M Capture mouse
keyboard.key.request.A2M Send keyboard event
cursor.hide.request.A2M Show/hide cursor
cursor.type.request.A2M Set cursor type
screen.list.request.A2M Request screen list
transform.activate.request.A2M Activate coordinate transform
window.info.request.A2M Query window info
window.track.request.A2M Subscribe to window changes
window.control.request.A2M Control a window
server.info.request.A2M Request server info
server.mode.request.A2M Request server mode
server.path.request.A2M Resolve path key
theme.list.request.A2M Request cursor theme list

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.

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:

App Demonstrates
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.


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 10 required handlers
Handler throws error Add try-catch in handler code

For more information or custom integration support, contact [email protected]