July launch MouseMux V3 just launched. We'll be polishing it all summer with free updates, so early adopters get 25% off with code JULY25. See pricing

Last updated July 12, 2026

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

RequirementDetails
MouseMux V2Must be installed and running
SDK modeEnabled in MouseMux configuration
BrowserModern browser with WebSocket and ES6 support
ElectronOptional -- 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:

HandlerData FieldsDescription
onPointerMotionhwid, userId, x, y, user, deviceType, inputSourcePointer moved
onPointerButtonhwid, userId, x, y, button, user, deviceType, inputSourceButton pressed/released
onPointerWheelhwid, userId, x, y, delta, horizontal, user, deviceType, inputSourceScroll wheel
onPointerPenhwid, userId, x, y, pressure, tilt_x, tilt_y, rotation, user, deviceType, inputSourcePen/stylus input
onKeyboardhwid, userId, vkey, message, scan, flags, user, deviceType, inputSourceKeyboard input
onUserListusers[]Full user list received
onUserCreatehwid_ms, hwid_kb, nameNew user connected
onUserDisposehwid_ms, hwid_kbUser disconnected
onConnectedurlConnected to server
onDisconnected--Disconnected from server

Input event fields:

FieldDescription
hwidDevice hardware ID (different for mouse vs keyboard)
userIdUser ID -- same for mouse and keyboard from the same user. Use this to identify users.
userFull 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

HandlerDescription
onServerShutdownServer shutting down (data: {reason})
onServerTimeoutWarningTimeout warning (data: {minutes}; return true to suppress default alert)
onServerTimeoutStoppedSession ended due to timeout (data: {reason})
onDeviceNewSDK saw a new HWID for the first time (data: {hwid, data})
onUserMapKeyboard mapped to existing user (data: {action, userId, hwid_ms, hwid_kb, name})
onUserUpdateUser state changed (data: {action, userId, ...})
onUserChangedAny user state change -- create/map/dispose/update
onScreenListScreen configuration received (data: {virtual, screens[]})
onScreenChangeScreen configuration changed
onWindowInfoTracked window moved/resized/changed (data: {hwnd, pid, title, ...})
onWindowDestroyedTracked window was destroyed (data: {hwnd})
onServerModeServer 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:

ConstantValueEvent
BUTTON_LEFT_DOWN0x01Left button pressed
BUTTON_LEFT_UP0x02Left button released
BUTTON_RIGHT_DOWN0x04Right button pressed
BUTTON_RIGHT_UP0x08Right button released
BUTTON_MIDDLE_DOWN0x10Middle button pressed
BUTTON_MIDDLE_UP0x20Middle button released
BUTTON_X1_DOWN0x40X1/Back pressed
BUTTON_X1_UP0x80X1/Back released
BUTTON_X2_DOWN0x100X2/Forward pressed
BUTTON_X2_UP0x200X2/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

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

Hardware ID Ranges

RangeHexDescription
0 - 40950x0000-0x0FFFPhysical hardware devices
12288 - 133110x3000-0x33FFSDK server-assigned devices
13312 - 143350x3400-0x37FFSDK client-side virtual users
163830x3FFFNative input (debug mode)
24576 - 266230x6000-0x67FFRustDesk 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

ActionHandlerWhen
createonUserCreateMouse arrives, new user created
maponUserMap (optional)Keyboard mapped to existing user
disposeonUserDisposeUser removed

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

Pointer Subtypes

ValueDescription
mouseStandard mouse
pen_externalExternal pen/stylus
pen_internalBuilt-in pen (tablet)
touchTouch input
touchpadTouchpad

User Flags

FlagDescription
activeUser is active
rootAdmin user
lockedUser locked
capturedDevice captured (SDK)
cursor_hiddenCursor hidden
cursor_move_lockedMovement locked
cursor_type_lockedType locked
macro_recordingRecording macro
macro_playingPlaying 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

ModeHow it worksWhen to use
ManualClient-side JS: screenToElement -> normalize -> de-normalize to target screenFull client-side control
ServerRaw physical coords + transform ID sent to server; server does the mappingProduction 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

PropertyTypeDescription
mode'manual' or 'server'Forwarding mode
activebooleanWhether currently active
transformIdnumber or nullServer-side transform ID (server mode)
targetScreenobject or nullTarget screen from screen list
elementHTMLElementSource DOM element
screenIndexnumberTarget 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

ActionDescriptionParams
showShow window--
hideHide window--
minimizeMinimize to taskbar--
maximizeMaximize window--
restoreRestore from min/max--
foregroundBring to foreground--
focusSet keyboard focus--
topBring to top of Z-order--
enableEnable mouse/keyboard input--
disableDisable mouse/keyboard input--
closeClose window--
moveMove to positionx, y
resizeResize windoww, h
transparentSet opacityalpha (0-255)
topmostAlways on top--
no_topmostRemove always on top--
clickthroughMake click-through--
no_clickthroughRestore 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"
    }
}
FieldTypeDescription
hardstring[]Required modes -- app will not function without these
hintstring[]Suggested modes -- app works best with these
helpstringHuman-readable explanation

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


API Reference

Constructor Options

OptionTypeDefaultDescription
appNamestring'Unknown App'Application name (sent to server on login)
appVersionstring'0.0.0'Application version
appBuildDatestring'unknown'Application build date
urlstring'ws://localhost:41001'WebSocket server URL
autoConnectbooleantrueAuto-connect on construction
strictValidationbooleanfalseEnable strict field validation on messages

Connection Methods

MethodReturnsDescription
connect(url?)voidConnect to WebSocket server
connectAsync(url?, options?)PromiseConnect with Promise and timeout
tryAutoConnect()Promise<boolean>Try auto-connect silently
disconnect()voidDisconnect from server
disconnectAsync(reason?)PromiseGraceful disconnect with logout
isConnected()booleanCheck connection status
send(type, params?)booleanSend message to server
sendLog(level, message)booleanSend log message to server

Device Methods

MethodReturnsDescription
getDevice(hwid)objectGet device state
getAllDevices()MapGet all devices
getLastActiveDevice()numberGet last active HWID
isMyDevice(hwid)booleanCheck if local device
getMyDevices()SetGet all local HWIDs
setDeviceData(hwid, key, value)voidStore custom data
getDeviceData(hwid, key)anyGet custom data
getAllDeviceData(hwid)objectGet all custom data
getDeviceColor(hwid)stringGet auto-assigned color
setColorPalette(colors)voidSet custom color palette
getColorPalette()string[]Get current palette

User Methods

MethodReturnsDescription
requestUserList()booleanRequest user list from server
getUserId(hwid)numberGet user ID for any device
isSameUser(hwid1, hwid2)booleanCheck if same user
getDevicesForUser(userId)number[]Get all HWIDs for a user
getUserForDevice(hwid)objectGet full user object
getDeviceName(hwid)stringGet friendly device name
getUsers()MapGet all users
getStats()objectGet event statistics
getSystemId()stringGet system ID of connected server
getServerInfo(options?)PromiseGet server info
requestServerInfo()voidRequest server info
requestServerPath(what, options?)PromiseResolve path key to filesystem path
requestHardwareIDs(options?)PromiseRequest virtual HWIDs
requestServerMode()voidRequest current server mode

Screen & Transform Methods

MethodReturnsDescription
requestScreenList()booleanRequest monitor/screen list
activateTransform(params)booleanActivate coordinate transform
updateTransform(params)booleanUpdate transform viewport
deactivateTransform(params)booleanDeactivate transform
createCoordinateForwarder(el, screenIdx, opts?)CoordinateForwarderCreate coordinate forwarder

Window Methods

MethodReturnsDescription
requestWindowInfo(hwnd, options?)Promise<Object>One-shot window info query
trackWindow(hwnd, options?)Promise<Object>Subscribe to live window updates
untrackWindow(hwnd)booleanUnsubscribe from window updates
controlWindow(hwnd, action, params?, options?)Promise<Object>Control a window
showWindow(hwnd)PromiseShow window
hideWindow(hwnd)PromiseHide window
minimizeWindow(hwnd)PromiseMinimize window
maximizeWindow(hwnd)PromiseMaximize window
restoreWindow(hwnd)PromiseRestore window
foregroundWindow(hwnd)PromiseBring to foreground
focusWindow(hwnd)PromiseSet keyboard focus
topWindow(hwnd)PromiseBring to top
closeWindow(hwnd)PromiseClose window
moveWindow(hwnd, x, y)PromiseMove window
resizeWindow(hwnd, w, h)PromiseResize window
setWindowTransparent(hwnd, alpha)PromiseSet opacity (0-255)
setWindowTopmost(hwnd)PromiseAlways on top
clearWindowTopmost(hwnd)PromiseRemove always on top
setWindowClickthrough(hwnd)PromiseMake click-through
clearWindowClickthrough(hwnd)PromiseRestore mouse input

Coordinate Methods (via sdk.coords)

MethodReturnsDescription
screenToElement(x, y, el){x,y} or nullConvert to element coords
screenToContentCss(x, y){x,y} or nullConvert to CSS coords
isInElement(x, y, el)booleanCheck if point is in element
elementAtPoint(x, y)Element or nullGet element at screen coords
hasTransformCache()booleanCheck conversion accuracy
hasElectronBridge()booleanCheck if Electron bridge available
onTransformUpdate(cb)functionSubscribe to transform updates
getElementPhysicalBounds(el){x,y,w,h} or nullGet element bounds in physical pixels
findClickedElement(x, y, selector)Element or nullFind element at coords

Input Methods

MethodReturnsDescription
bindClick(selector, callback)functionBind click to selector
clearClickBindings()voidRemove all bindings
enableNativeInput(container)voidEnable native input mode
disableNativeInput()voidDisable native input mode
isNativeInputEnabled()booleanCheck if native input active

Virtual Input Methods

MethodReturnsDescription
enableVirtualInput()voidEnable virtual input mode
disableVirtualInput()voidDisable and destroy all virtual users
isVirtualInputEnabled()booleanCheck if enabled
createVirtualUser(name?){hwid_ms, hwid_kb, name, color}Create virtual user
destroyVirtualUser(hwid_ms)voidDestroy a virtual user
getVirtualUsers()MapGet all active virtual users
setVirtualUserName(hwid_ms, name)voidUpdate display name
injectVirtualPointerMotion(hwid, x, y)voidInject pointer motion
injectVirtualPointerButton(hwid, x, y, button)voidInject button event
injectVirtualPointerWheel(hwid, x, y, delta)voidInject wheel event
injectVirtualKeyboard(hwid, vkey, message, scan?, flags?)voidInject keyboard event
injectVirtualClick(hwid, x, y, domButton?)voidInstant down+up click

Cursor Methods

MethodReturnsDescription
enableRemoteCursors(container, opts)voidShow remote cursors
disableRemoteCursors()voidHide remote cursors

Event System

MethodReturnsDescription
on(event, callback)voidRegister event listener
off(event, callback)voidRemove event listener
emit(event, data)voidEmit event

Static Methods

MethodReturnsDescription
MouseMuxSDK.probe(url, options?)PromiseTest 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)

MessageDescription
server.info.notify.M2AServer info (version, systemId, protocol_version)
server.mode.notify.M2AServer mode (array: ["multiplex", "multi-keyboard"])
server.ping.notify.M2AKeepalive ping (SDK auto-responds)
server.shutdown.notify.M2AServer shutting down
server.timeout.warning.notify.M2ATimeout warning
server.timeout.stopped.notify.M2ASession ended due to timeout
screen.list.notify.M2AScreen/monitor configuration
screen.change.notify.M2AScreen config changed
user.list.notify.M2AFull user list
user.create.notify.M2AUser created
user.dispose.notify.M2AUser removed
user.changed.notify.M2AUser state changed
pointer.motion.notify.M2APointer moved
pointer.button.notify.M2AButton changed
pointer.wheel.notify.M2AWheel scroll
pointer.pen.notify.M2APen input
keyboard.key.notify.M2AKey event
window.info.notify.M2ATracked window changed
window.destroyed.notify.M2ATracked window destroyed

A2M Messages (App -> Server)

MessageDescription
client.login.request.A2MLogin to server (automatic)
client.logout.request.A2MLogout from server
client.pong.request.A2MPong response (automatic)
client.log.request.A2MSend log message
user.create.request.A2MCreate virtual user
user.dispose.request.A2MRemove virtual user
user.list.request.A2MRequest user list
user.hwid.request.A2MRequest available HWIDs
pointer.motion.request.A2MMove pointer
pointer.button.request.A2MSend button event
pointer.wheel.request.A2MSend wheel event
pointer.capture.request.A2MCapture mouse
keyboard.key.request.A2MSend keyboard event
cursor.hide.request.A2MShow/hide cursor
cursor.type.request.A2MSet cursor type
screen.list.request.A2MRequest screen list
transform.activate.request.A2MActivate coordinate transform
window.info.request.A2MQuery window info
window.track.request.A2MSubscribe to window changes
window.control.request.A2MControl a window
server.info.request.A2MRequest server info
server.mode.request.A2MRequest server mode
server.path.request.A2MResolve path key
theme.list.request.A2MRequest 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:

AppDemonstrates
Multi-PaintCollaborative drawing with brush/eraser tools
Team VoteMulti-user voting and quiz system
WhiteboardShared whiteboard with shapes and text
SCADA SimulatorIndustrial control panel demonstration
Service DeskMulti-user customer support interface
Sound BoardCollaborative music application

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


Troubleshooting

Connection Issues

ProblemSolution
Cannot connectVerify MouseMux is running on ws://localhost:41001
Connection dropsCheck network stability, enable reconnect
Wrong URLCheck WebSocket URL in connection dialog

Coordinate Issues

ProblemSolution
Coordinates wrongCheck sdk.coords.hasTransformCache()
Multi-monitor problemsEnsure displays array is populated
Electron zoom issuesVerify preload bridge is set up

Handler Issues

ProblemSolution
Handlers not firingCall registerHandlers() before connect()
Missing handlers errorProvide all 10 required handlers
Handler throws errorAdd try-catch in handler code

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