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.
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:
// 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');
<!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>
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
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: () => {}
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.
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
}
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);
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();
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
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
}
}
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;
}
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;
}
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');
}
}
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();
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();
The SDK includes ready-to-use UI components:
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);
}
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()
});
MouseMuxUI.toast('Connected!', { type: 'success' });
MouseMuxUI.toast('Warning message', { type: 'warning' });
MouseMuxUI.toast('Error occurred', { type: 'error', duration: 5000 });
MouseMuxUI.toast('Info', { type: 'info' });
The SDK supports 9 languages: English, German, Spanish, French, Italian, Japanese, Korean, Dutch, and Chinese.
// 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'
}
});
// 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();
});
{
"_meta": {
"language": "English",
"code": "en",
"version": "2.2.23"
},
"appName": "My App",
"brushSize": "Brush Size",
"clearCanvas": "Clear Canvas",
"greeting": "Hello, {{name}}!"
}
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.
For accurate coordinate conversion in Electron apps, set up the preload bridge:
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);
}
});
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);
All MouseMux apps follow this recommended structure:
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
└── ...
(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
});
})();
| 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 |
| 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 |
| Method | Description |
|---|---|
getUserForDevice(hwid) |
Get user for device |
getDeviceName(hwid) |
Get friendly device name |
getUsers() |
Get all users as Map |
| 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 |
| 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 |
| Method | Description |
|---|---|
enableRemoteCursors(container, opts) |
Show remote cursors |
disableRemoteCursors() |
Hide remote cursors |
| Method | Description |
|---|---|
on(event, callback) |
Register event listener |
off(event, callback) |
Remove event listener |
emit(event, data) |
Emit event |
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:
sdk/core/, sdk/state/, sdk/helpers/, sdk/input/)sdk/ui.js)locales/)See these apps in our App Library for working examples:
Each app demonstrates different SDK features and patterns. Download any app to explore its source code and SDK integration.
| 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 |
| 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 |
| 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