05-03-26

Multi-PC Input Mirroring with WebSocket

Mirror your keyboard and mouse input across multiple PCs using Lua scripts and hardware input with Rebind. This guide provides WebSocket-based solutions for low-latency input replication.

rebindlua scriptinput mirroringmulti-pcwebsockethardware inputscript

Question

Can I set up a multi-boxing setup in Rebind where all mouse movements and keystrokes are mirrored to other PCs?

Rebind AI

Yes, you can set up a multi-boxing configuration in Rebind. Here are a few approaches:

Option 1: WebSocket Server (Real-time, Low-Latency)

Set up a Rebind script on your "master" PC that streams all input to one or more "slave" PCs via WebSocket. The slave PCs receive commands and replay them.

Master PC script:

lua
-- rebind: name=Multibox Master
-- rebind: permission=net

local cfg = UI.Schema({
    slave_ips = UI.Text("127.0.0.1:9001", { 
        label = "Slave IPs", 
        placeholder = "comma-separated: 127.0.0.1:9001, 192.168.1.100:9001" 
    }),
})

local clients = {}

function OnStart()
    for ip in cfg.slave_ips:gmatch("[^,]+") do
        ip = ip:match("^%s*(.-)%s*$") -- trim
        table.insert(clients, ip)
    end
    Log.Info("Master ready. Slaves: " .. table.concat(clients, ", "))
end

local function broadcast(msg)
    for _, ip in ipairs(clients) do
        pcall(function()
            Net.Post("http://" .. ip .. "/cmd", JSON.Stringify(msg), { timeout = 1000 })
        end)
    end
end

function OnDown(key)
    broadcast({ t = "down", key = key })
    return true
end

function OnUp(key, duration)
    broadcast({ t = "up", key = key })
    return true
end

function OnMove(dx, dy)
    broadcast({ t = "move", dx = dx, dy = dy })
    return true
end

function OnScroll(delta)
    broadcast({ t = "scroll", delta = delta })
    return true
end

Slave PC script:

lua
-- rebind: name=Multibox Slave
-- rebind: permission=net

local cfg = UI.Schema({
    port = UI.Slider(9001, { min = 8000, max = 9999, label = "Listen Port" }),
})

local server = nil

function OnStart()
    server = Net.Listen(cfg.port, function(req)
        if req.method == "POST" then
            local cmd = JSON.Parse(req.body)
            if cmd.t == "down" then
                HID.Down(cmd.key)
            elseif cmd.t == "up" then
                HID.Up(cmd.key)
            elseif cmd.t == "move" then
                HID.Move(cmd.dx, cmd.dy)
            elseif cmd.t == "scroll" then
                HID.Scroll(cmd.delta)
            end
            return { status = 200, body = "OK" }
        end
        return { status = 404, body = "Not Found" }
    end)
    Log.Info("Slave listening on port " .. cfg.port)
end

function OnStop()
    if server then server:Stop() end
end

Pros:

  • Simple, works over network
  • Easy to set up multiple slaves
  • Works from anywhere (LAN or internet)

Cons:

  • ~10-20ms latency per command (HTTP overhead)
  • Not ideal for high-frequency actions (gaming, fast clicking)

Option 2: WebSocket (Persistent Connection, Better Latency)

Lower latency than HTTP polling. Master opens a persistent WebSocket connection to each slave.

Master:

lua
-- rebind: name=Multibox Master (WebSocket)
-- rebind: permission=net

local cfg = UI.Schema({
    slave_urls = UI.Text("ws://127.0.0.1:9001", { 
        label = "Slave WebSocket URLs",
        placeholder = "ws://192.168.1.100:9001, ws://192.168.1.101:9001"
    }),
})

local conns = {}

function OnStart()
    for url in cfg.slave_urls:gmatch("[^,]+") do
        url = url:match("^%s*(.-)%s*$")
        pcall(function()
            local conn = Net.WSConnect(url, {})
            table.insert(conns, conn)
            Log.Info("Connected to " .. url)
        end)
    end
end

local function broadcast(msg)
    for _, conn in ipairs(conns) do
        pcall(function()
            conn:Send(JSON.Stringify(msg))
        end)
    end
end

function OnDown(key)
    broadcast({ t = "down", key = key })
    return true
end

function OnUp(key)
    broadcast({ t = "up", key = key })
    return true
end

function OnMove(dx, dy)
    broadcast({ t = "move", dx = dx, dy = dy })
    return true
end

function OnScroll(delta)
    broadcast({ t = "scroll", delta = delta })
    return true
end

function OnStop()
    for _, conn in ipairs(conns) do
        pcall(function() conn:Close() end)
    end
end

Slave:

lua
-- rebind: name=Multibox Slave (WebSocket)
-- rebind: permission=net

local cfg = UI.Schema({
    port = UI.Slider(9001, { min = 8000, max = 9999, label = "Listen Port" }),
})

local server = nil

function OnStart()
    server = Net.WSListen(cfg.port, {
        OnMessage = function(client, payload)
            local cmd = JSON.Parse(payload)
            if cmd.t == "down" then
                HID.Down(cmd.key)
            elseif cmd.t == "up" then
                HID.Up(cmd.key)
            elseif cmd.t == "move" then
                HID.Move(cmd.dx, cmd.dy)
            elseif cmd.t == "scroll" then
                HID.Scroll(cmd.delta)
            end
        end,
    })
    Log.Info("WebSocket slave listening on port " .. cfg.port)
end

function OnStop()
    if server then server:Stop() end
end

Pros:

  • ~1-2ms round-trip latency (per SDK benchmarks)
  • Persistent connection, lower overhead
  • Better for fast sequences

Cons:

  • Requires manual connection setup
  • More complex

Option 3: Shared Memory (Local Network Only, Fastest)

Use Pipe for ultra-low latency on the same PC. For network multibox, you'd need external tools.


Option 4: Macro Streaming + Network

Record a macro on master, stream it to slaves via Macro.Stream():

lua
Bind("F9", function()
    local macro = Macro.Record()
    -- ... record input ...
    local m = Macro.Finish()
    
    for _, ip in ipairs(slaves) do
        Net.Post("http://" .. ip .. "/play", JSON.Stringify(m))
    end
end)

Slaves receive and play the macro. Good for synchronized sequences but not real-time input.


Recommendation

For true multiboxing (real-time mirroring): Use WebSocket (Option 2). It gives you:

  • ~1-2ms latency (acceptable for most games)
  • Persistent connection (no reconnect overhead)
  • Scalable to many slaves
  • Works over LAN or internet

For accessibility/casual use: HTTP (Option 1) is simpler to set up.

The WebSocket server performance metrics from the SDK are excellent — the relay can handle ~10,000 req/s sustained, which covers even very high-frequency input mirroring.