-
Notifications
You must be signed in to change notification settings - Fork 5
Creating your own Overlay
deana edited this page Jun 20, 2026
·
3 revisions
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
Endpoint: ws://localhost:4455/
- Open the connection
- On
open, immediately send an auth message:the token is the one you set inside the input-overlay-ws{ "type": "auth", "token": "blablabla" } - The server responds with
{ "type": "auth_response", "status": "ok" }and then starts streaming input events
//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;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>