Event Bus

The BusConnector pub/sub system that decouples browser adapters (keyboard, screen, network) from the emulator's device controllers.

Why a bus?

v86 keeps browser APIs (keyboard events, canvas rendering, WebSocket) completely separate from device emulation logic. The BusConnector acts as a message broker — adapters send events in, devices respond and send data out. Neither side knows about the other's implementation.

Bus Architecture

Browser Adapters

Keyboard Adapter
keyboard.js — DOM keydown/keyup
Mouse Adapter
mouse.js — Pointer Lock API
Screen Adapter
screen.js — <canvas> putImageData
Speaker Adapter
speaker.js — Web Audio API
Network Adapter
network.js — WebSocket relay
Serial Adapter
serial.js — <textarea> / xterm.js
BUS

Emulator Devices

PS/2 Controller
ps2.js — keyboard + mouse FIFO
VGA Device
vga.js — pixel buffer rendering
PIT + PC Speaker
pit.js — timer + audio tone
NE2000 / Virtio-net
ne2k.js — Ethernet frames
UART / Virtio-console
uart.js — serial byte stream
v86 Emulator Control
main.js — lifecycle events

Message Catalog

→ Emulator "keyboard-code" keyboard.js

Sender: Keyboard adapter on keydown/keyup DOM events.

Receiver: PS/2 controller — queues byte in keyboard FIFO, raises IRQ1.

Data: A single integer — PC scan code set 2 byte. E0-prefix extended keys send two messages (0xE0, then the key code). Key release sends 0xF0 then the code.

bus.send("keyboard-code", 0x1C);  // 'A' pressed
bus.send("keyboard-code", 0xF0);  // release prefix
bus.send("keyboard-code", 0x1C);  // 'A' released
→ Emulator "mouse-delta" mouse.js

Sender: Mouse adapter on mousemove (with Pointer Lock) or pointermove.

Receiver: PS/2 controller — constructs 3-byte mouse packet (flags, dx, dy), queues it, raises IRQ12.

Data: Array [dx, dy, dz] — relative pixel movement. Clamped to -255..255 per packet.

bus.send("mouse-delta", [dx, dy, dz]);
→ Emulator "mouse-click" mouse.js

Sender: Mouse adapter on mousedown/mouseup events.

Receiver: PS/2 — updates button state bits in the next mouse packet's flag byte.

bus.send("mouse-click", [left_btn, middle_btn, right_btn]);
// e.g. left pressed: [1, 0, 0]
→ Emulator "network-send" ne2k.js / virtio_net.js

Sender: NE2000 or Virtio-net device — called internally when guest transmits a frame.

Receiver: Network adapter — forwards raw Ethernet frame to WebSocket relay server, fetch API, or local loop.

bus.send("network-send", new Uint8Array(ethernet_frame));
→ Emulator "serial0-input" … "serial3-input" serial.js

Sender: Serial adapter — when user types in the terminal UI.

Receiver: UART device — pushes byte into RX FIFO, raises IRQ if interrupts enabled.

bus.send("serial0-input", char_code);  // e.g. 0x0D for Enter
← Adapter "screen-fill-buffer" vga.js

Sender: VGA device — sent every timer tick when dirty scanlines exist.

Receiver: Screen adapter — blits pixel data to canvas using putImageData().

Data: Object with dirty region and pixel buffer reference.

{
  dirty_x: 0, dirty_y: 0,
  dirty_w: 640, dirty_h: 16,   // changed rows
  buffer: Uint8Array,           // RGBA pixels
  image_data: ImageData         // pre-built ImageData object
}
← Adapter "screen-set-mode" vga.js

Sender: VGA device — when guest changes display mode (text↔graphics, resolution change).

Receiver: Screen adapter — resizes canvas, switches rendering logic.

bus.send("screen-set-mode", is_graphical_mode);
// true = pixel buffer mode, false = text mode
← Adapter "pcspeaker-enable" / "pcspeaker-disable" pit.js (via port 0x61)

Sender: PIT device — when guest sets/clears port 0x61 bit 0 (speaker gate) and bit 1 (PIT ch2 gate).

Receiver: Speaker adapter — starts or stops the Web Audio oscillator node.

← Adapter "pcspeaker-update" pit.js

Sender: PIT device — when channel 2 counter is reprogrammed (changes tone frequency).

Receiver: Speaker adapter — updates the oscillator frequency.

bus.send("pcspeaker-update", [pit_mode, frequency_hz]);
← Adapter "network-receive" network.js (adapter → emulator)

Sender: Network adapter — when a packet arrives from the relay server or fetch response.

Receiver: NE2000/Virtio-net device — copies frame into RX ring buffer, raises interrupt.

bus.send("network-receive", new Uint8Array(ethernet_frame));
← Adapter "serial0-output" … "serial3-output" uart.js

Sender: UART device — when TX FIFO byte is ready to be transmitted.

Receiver: Serial adapter — appends character to terminal output buffer.

bus.send("serial0-output", char_code);  // e.g. 0x41 = 'A'
← Adapter "emulator-ready" main.js

Fired when cpu.init() completes — all devices initialized, BIOS loaded, CMOS filled. The emulator is ready to start but has not yet begun executing.

Application code (starter.js) listens for this to call emulator.run().

← Adapter "emulator-started" main.js

Fired when run() is called — the main loop begins ticking. Guest CPU is now executing instructions.

← Adapter "emulator-stopped" main.js

Fired when stop() is called or the guest issues an ACPI shutdown. Main loop halts; guest memory is preserved.

→ Emulator "cpu-event-halt" main.js (from WASM callback)

Fired by the WASM import cpu_event_halt() when the guest executes a HLT instruction. JS suspends ticking until an IRQ wakes the CPU.

← Adapter "download-progress" / "download-error" starter.js

Fired during async file loading (BIOS, disk images). Application can display a progress bar. download-error fires if a fetch fails.

bus.send("download-progress", {
  file_name: "linux.iso",
  loaded: 4194304,
  total: 671088640
});

Bus Implementation

Paired connectors

Bus.create() returns two linked BusConnector objects. Messages sent on one appear as received on the other — there's no global singleton. This lets the emulator and adapters evolve independently.

// src/bus.js — simplified function BusConnector() { this.listeners = {}; // { event_name: [{fn, this_value}] } this.pair = null; } BusConnector.prototype.register = function(name, fn, this_val) { (this.listeners[name] ||= []).push({ fn, this: this_val }); }; BusConnector.prototype.send = function(name, data) { const listeners = this.pair.listeners[name] || []; for (const l of listeners) l.fn.call(l.this, data); }; Bus.create = function() { const a = new BusConnector(), b = new BusConnector(); a.pair = b; b.pair = a; return [a, b]; // [adapter_bus, emulator_bus] };

Simulated Bus Activity

Bus Message Log

Press Start to simulate bus activity…