Skip to content

Recording & Playback

ZMouse can record mouse and keyboard events and replay them later with preserved timing.

How Recording Works

When you start recording, ZMouse installs Windows low-level hooks that capture:

  • Mouse movements
  • Mouse clicks (left, right, double)
  • Mouse scroll events
  • Keyboard key presses and releases

All events are timestamped relative to when recording started.

CLI Recording

Start Recording

> rec
  Recording started. Use 'stop' to finish.

While recording, ZMouse captures all system-wide mouse and keyboard input.

Stop Recording

> stop
  Recording stopped. 42 events captured.

Save to File

> save my_macro.json
  Saved 42 events to 'my_macro.json'

Load from File

> load my_macro.json
  Loaded 42 events from 'my_macro.json'

Playback

> play
  Playing 42 events...
  Playback complete.

HTTP API Recording

Check Status

bash
curl http://localhost:4000/api/recording/status
# {"recording":false,"events":0}

Start Recording

bash
curl -X POST http://localhost:4000/api/recording/start
# {"ok":true}

Stop Recording

bash
curl -X POST http://localhost:4000/api/recording/stop
# {"ok":true,"events":42}

Save Events

bash
curl -X POST http://localhost:4000/api/recording/save \
  -d '{"filename":"macro.json"}'
# {"ok":true,"events":42}

Load Events

bash
curl -X POST http://localhost:4000/api/recording/load \
  -d '{"filename":"macro.json"}'
# {"ok":true,"events":42}

Playback

bash
curl -X POST http://localhost:4000/api/recording/play
# {"ok":true}

Library API

zig
const zmouse = @import("zmouse");

// Initialize recorder
var recorder = zmouse.Recorder.init(allocator);
defer recorder.deinit();

// Start recording
try recorder.startRecording();

// ... user performs actions ...

// Stop recording
recorder.stopRecording();

// Get event count
const count = recorder.getEventCount();

// Get events
const events = recorder.getEvents();

// Save to file
try zmouse.storage.saveEvents(events, "macro.json", allocator);

// Load from file
const loaded = try zmouse.storage.loadEvents("macro.json", allocator);
defer allocator.free(loaded);

// Set events from loaded data
try recorder.setEvents(loaded);

// Clear events
recorder.clearEvents();

JSON Format

Events are stored in JSON format:

json
{
  "version": 1,
  "events": [
    {"t": 0, "type": "move", "x": 500, "y": 300},
    {"t": 150, "type": "left_down", "x": 500, "y": 300},
    {"t": 200, "type": "left_up", "x": 500, "y": 300},
    {"t": 500, "type": "key_down", "x": 0, "y": 0, "data": 65},
    {"t": 550, "type": "key_up", "x": 0, "y": 0, "data": 65}
  ]
}

Event Types

TypeDescription
moveMouse movement
left_downLeft mouse button pressed
left_upLeft mouse button released
right_downRight mouse button pressed
right_upRight mouse button released
wheelMouse scroll
key_downKeyboard key pressed
key_upKeyboard key released

Fields

FieldDescription
tTimestamp in milliseconds
typeEvent type
x, yMouse coordinates (0 for keyboard)
dataWheel delta or virtual key code

Architecture

┌─────────────────┐
│   Main Thread   │
│   (REPL/HTTP)   │
└────────┬────────┘
         │ startRecording()

┌─────────────────┐
│  Hook Thread    │
│  (message pump) │
│                 │
│ WH_MOUSE_LL     │◄── System mouse events
│ WH_KEYBOARD_LL  │◄── System keyboard events
└────────┬────────┘
         │ appendEvent()

┌─────────────────┐
│ Recorder.events │
│ (ArrayListUnmanaged)
└─────────────────┘

The hook thread runs a Windows message pump to receive low-level input events. Events are stored with timestamps for accurate playback.

Use Cases

  • Automation: Record repetitive tasks and replay them
  • Testing: Record UI interactions for automated testing
  • Macros: Create reusable input sequences
  • Remote control: Record on one machine, replay via HTTP API

Tips

  • Keep recordings short for reliability
  • Avoid recording sensitive keyboard input (passwords)
  • Test recordings before using in production
  • Playback timing is preserved from the original recording

Released under the MIT License.