Skip to content

Creating your own Overlay

deana edited this page Jun 20, 2026 · 3 revisions

Creating your own Overlay

Any HTML page added as a Browser Source can connect to the input-overlay-ws app and react to live input events. So this should give you an idea how to create your own HTML Overlay

Connection

Endpoint: ws://localhost:4455/

  1. Open the connection
  2. On open, immediately send an auth message:
    { "type": "auth", "token": "blablabla" }
    the token is the one you set inside the input-overlay-ws
  3. The server responds with { "type": "auth_response", "status": "ok" } and then starts streaming input events

Types

//stuff you can send
interface AuthMessage {
    type: "auth";
    token: string; //empty string if no token set in the app
}

//stuff you can receive
interface AuthResponse {
    type: "auth_response";
    status: "ok" | "error" | "failed";
    message?: string;
}

interface KeyEvent {
    event_type: "key_pressed" | "key_released";
    rawcode: number; //vk code
}

interface MouseButtonEvent {
    event_type: "mouse_pressed" | "mouse_released";
    button: number; //1=left  2=right  3=scroller press  4=M4  5=M5
    mask: number;   //bitmask of all currently held buttons
}

interface MouseMoveEvent {
    event_type: "mouse_moved";
    dx: number;
    dy: number;
}

interface MouseWheelEvent {
    event_type: "mouse_wheel";
    rotation: number; //-1=up,  1=down
}

interface AnalogDepthEvent {
    event_type: "analog_depth";
    rawcode: number;
    depth: number; //0.0 (unpressed) to 1.0 (fully pressed)
}

type InputEvent = KeyEvent | MouseButtonEvent | MouseMoveEvent | MouseWheelEvent | AnalogDepthEvent;

Simple example

This is an example for a HTML overlay which displays a simple input history

<!-- based on https://github.com/univrsal/input-overlay/tree/master/presets/input-history-windows -->
<!DOCTYPE html>
<html>
<head>
  <style>
    *{margin:0;padding:0;box-sizing:border-box}
    body{font:28px/1.4 monospace;color:#fff;overflow:hidden}
    #history{position:fixed;bottom:0;left:0;display:flex;flex-direction:column-reverse;padding:8px;gap:4px}
    .combo{background:rgba(0,0,0,.65);padding:2px 10px;border-radius:4px;animation:fadein .1s ease}
    @keyframes fadein{from{opacity:0}to{opacity:1}}
    #mouse-path{position:fixed;bottom:8px;right:8px;background:rgba(0,0,0,.4);border-radius:6px}
  </style>
</head>

<body>
  <div id="history"></div>
  <canvas id="mouse-path" width="200" height="200"></canvas>

  <script>
    //windows vk codes: https://docs.microsoft.com/en-us/windows/win32/inputdev/virtual-key-codes
    const KEY_LABELS = {
      65: "A", 66: "B", 67: "C", 68: "D", 69: "E", 70: "F",
      71: "G", 72: "H", 73: "I", 74: "J", 75: "K", 76: "L",
      77: "M", 78: "N", 79: "O", 80: "P", 81: "Q", 82: "R",
      83: "S", 84: "T", 85: "U", 86: "V", 87: "W", 88: "X",
      89: "Y", 90: "Z",
      49: "1", 50: "2", 51: "3", 52: "4", 53: "5",
      54: "6", 55: "7", 56: "8", 57: "9", 48: "0",
      160: "Shift", 161: "Shift",
      162: "Ctrl", 163: "Ctrl",
      164: "Alt", 165: "Alt",
      91: "Win", 92: "Win",
      13: "Enter", 8: "Bksp", 32: "Space", 9: "Tab", 27: "Esc",
      37: "←", 38: "↑", 39: "→", 40: "↓",
    };

    const MOUSE_LABELS = { 1: "M1", 2: "M2", 3: "M3", 4: "M4", 5: "M5" };

    class MousePath {
      constructor(canvas, scale = 0.2, trailMs = 500) {
        this.ctx = canvas.getContext("2d");
        this.w = canvas.width;
        this.h = canvas.height;
        this.scale = scale;
        this.trailMs = trailMs;
        this.points = [];
        this.raf = null;
      }

      push(dx, dy) {
        const now = Date.now();
        const prev = this.points.at(-1) ?? { x: this.w / 2, y: this.h / 2 };
        this.points.push({
          x: Math.max(1, Math.min(this.w - 1, prev.x + dx * this.scale)),
          y: Math.max(1, Math.min(this.h - 1, prev.y + dy * this.scale)),
          t: now,
        });
        this.raf ??= requestAnimationFrame(() => this._draw());
      }

      _draw() {
        const now = Date.now();
        this.points = this.points.filter(p => now - p.t < this.trailMs);
        this.ctx.clearRect(0, 0, this.w, this.h);
        for (let i = 1; i < this.points.length; i++) {
          const alpha = 1 - (now - this.points[i].t) / this.trailMs;
          this.ctx.beginPath();
          this.ctx.strokeStyle = `rgba(255,255,255,${alpha.toFixed(2)})`;
          this.ctx.lineWidth = 2;
          this.ctx.moveTo(this.points[i - 1].x, this.points[i - 1].y);
          this.ctx.lineTo(this.points[i].x, this.points[i].y);
          this.ctx.stroke();
        }
        this.raf = this.points.length ? requestAnimationFrame(() => this._draw()) : null;
      }
    }

    class InputHistory {
      constructor(el, maxEntries = 8, fadeMs = 3000) {
        this.el = el;
        this.maxEntries = maxEntries;
        this.fadeMs = fadeMs;
        this.held = new Set();
      }

      press(key) { this.held.add(key); this._push(); }
      release(key) { this.held.delete(key); }

      _label(key) {
        return typeof key === "string"
          ? (MOUSE_LABELS[key.slice(1)] ?? key)
          : (KEY_LABELS[key] ?? String(key));
      }

      _push() {
        if (!this.held.size) return;
        const el = document.createElement("div");
        el.className = "combo";
        el.textContent = [...this.held].map(k => this._label(k)).join(" + ");
        this.el.prepend(el);
        while (this.el.children.length > this.maxEntries) this.el.lastElementChild.remove();
        setTimeout(() => el.remove(), this.fadeMs);
      }
    }

    class OverlayClient {
      constructor(url, token, path, history) {
        this.url = url;
        this.token = token;
        this.path = path;
        this.history = history;
        this._connect();
      }

      _connect() {
        const ws = new WebSocket(this.url);
        ws.onopen = () => ws.send(JSON.stringify({ type: "auth", token: this.token }));
        ws.onmessage = (e) => this._onEvent(JSON.parse(e.data));
        ws.onclose = () => setTimeout(() => this._connect(), 2000);
      }

      _onEvent({ event_type, rawcode, button, dx, dy }) {
        switch (event_type) {
          case "key_pressed":
            this.history.press(rawcode);
            break;
          case "key_released":
            this.history.release(rawcode);
            break;
          case "mouse_pressed":
            this.history.press("m" + button);
            break;
          case "mouse_released":
            this.history.release("m" + button);
            break;
          case "mouse_moved":
            this.path.push(dx, dy);
            break;
        }
      }
    }

    new OverlayClient(
      "ws://localhost:4455/",
      "man_i_just_love_boobs6767",
      new MousePath(document.getElementById("mouse-path")),
      new InputHistory(document.getElementById("history")),
    );
  </script>
</body>
</html>

Clone this wiki locally