05-03-26

Peer-to-Peer Clipboard Sync Script

This Lua script enables peer-to-peer clipboard synchronization between two machines using Rebind hardware input. It allows you to easily share text between devices on the same network.

rebindlua scriptclipboardsyncpeer-to-peernetworkhardware input

Question

Can you configure it so that two machines are both running Rebind?

Rebind AI

Here's a peer-to-peer clipboard synchronization script for use with two machines running Rebind:

lua
-- rebind: min_sdk=0.1.0
-- rebind: name=Peer Clipboard Sync
-- rebind: permission=net

local cfg = UI.Schema({
    mode = UI.Select("Server", { "Server", "Client" }, {
        label = "Mode",
        group = "Network",
    }),
    
    listen_port = UI.Slider(8765, {
        min = 1024,
        max = 65535,
        label = "Listen Port",
        group = "Network",
        showIf = "mode:Server",
    }),
    
    peer_url = UI.Text("http://localhost:8765", {
        label = "Peer URL",
        placeholder = "http://host:port",
        maxLength = 100,
        group = "Network",
        showIf = "mode:Client",
    }),
    
    sync_hotkey = UI.Keybind("F9", { label = "Sync Hotkey" }),
    auto_sync = UI.Toggle(false, { label = "Auto-sync every 2s" }),
    timeout = UI.Slider(5000, { min = 1000, max = 30000, suffix = "ms" }),
})

local server = nil
local lastLocalClip = ""
local lastRemoteClip = ""

local function sanitizeUrl(url)
    if not url:match("^https?://") then
        url = "http://" .. url
    end
    return url:gsub("/$", "")
end

-- ============ SERVER MODE ============
local function startServer()
    server = Net.Listen(cfg.listen_port, function(req)
        if req.method == "GET" and req.path == "/clipboard" then
            local localText = Clipboard.Get() or ""
            lastLocalClip = localText
            return {
                status = 200,
                body = JSON.Stringify({ text = localText }),
                headers = { ["Content-Type"] = "application/json" },
            }
        elseif req.method == "POST" and req.path == "/clipboard" then
            local data = JSON.Parse(req.body)
            local remoteText = data.text or ""
            
            if remoteText ~= lastRemoteClip then
                lastRemoteClip = remoteText
                Clipboard.Set(remoteText)
                Log.Info("Server received: " .. remoteText:sub(1, 50))
            end
            
            return {
                status = 200,
                body = JSON.Stringify({ status = "ok" }),
                headers = { ["Content-Type"] = "application/json" },
            }
        end
        return { status = 404, body = "Not Found" }
    end)
    Log.Info("Clipboard server listening on port " .. cfg.listen_port)
    UI.Notify("Server started on port " .. cfg.listen_port, "success")
end

-- ============ CLIENT MODE ============
local function pullFromPeer()
    local url = sanitizeUrl(cfg.peer_url) .. "/clipboard"
    
    local resp = Net.Get(url, nil, { timeout = cfg.timeout })
    if resp.status == 200 then
        local data = JSON.Parse(resp.body)
        local remoteText = data.text or ""
        
        if remoteText ~= lastRemoteClip and remoteText ~= "" then
            lastRemoteClip = remoteText
            Clipboard.Set(remoteText)
            UI.Notify("Pulled from peer", "success")
            Log.Info("Pulled: " .. remoteText:sub(1, 50))
            return true
        end
    else
        UI.Notify("Pull failed: " .. resp.status, "error")
    end
    return false
end

local function pushToPeer()
    local localText = Clipboard.Get()
    if not localText or localText == lastLocalClip then
        UI.Notify("Local clipboard unchanged", "info")
        return false
    end
    
    local url = sanitizeUrl(cfg.peer_url) .. "/clipboard"
    local payload = JSON.Stringify({ text = localText })
    
    local resp = Net.Post(url, payload, {
        ["Content-Type"] = "application/json",
    }, { timeout = cfg.timeout })
    
    if resp.status == 200 then
        lastLocalClip = localText
        UI.Notify("Pushed to peer", "success")
        Log.Info("Pushed: " .. localText:sub(1, 50))
        return true
    else
        UI.Notify("Push failed: " .. resp.status, "error")
    end
    return false
end

-- ============ LIFECYCLE ============
function OnStart()
    if cfg.mode == "Server" then
        startServer()
    else
        Log.Info("Client mode: targeting " .. cfg.peer_url)
        UI.Notify("Client ready", "info")
    end
    
    if cfg.auto_sync then
        Timer.Every(2000, function()
            if cfg.mode == "Client" then
                Run(function()
                    pullFromPeer()
                end)
            end
        end)
    end
end

function OnStop()
    if server then
        server:Stop()
        Log.Info("Server stopped")
    end
    Timer.CancelAll()
end

-- ============ HOTKEY BINDING ============
if cfg.mode == "Client" then
    Bind(cfg.sync_hotkey, Async(function()
        UI.Notify("Syncing...", "info")
        pullFromPeer()
        Sleep(100)
        pushToPeer()
    end))
else
    Bind(cfg.sync_hotkey, Async(function()
        local localText = Clipboard.Get() or ""
        if localText == lastLocalClip then
            UI.Notify("Clipboard unchanged", "info")
        else
            lastLocalClip = localText
            UI.Notify("Local clipboard updated", "success")
        end
    end))
end

Setup:

  1. Machine A (Server): Load this script, set mode to "Server", keep default port 8765.
  2. Machine B (Client): Load this script, set mode to "Client", enter Machine A's IP: http://192.168.x.x:8765

How it works:

  • Server: Exposes a /clipboard endpoint. GET returns its clipboard, POST receives updates from the client.
  • Client: Pulls from the server's endpoint and pushes its own clipboard.
  • Hotkey (F9):
    • Server: marks local clipboard as "updated" (available for client to fetch).
    • Client: syncs both ways — pulls from server, then pushes local.
  • Auto-sync: Client polls server every 2 seconds for new content.
  • Bidirectional: Server tracks lastRemoteClip to avoid overwriting its own clipboard with client echoes.

Network discovery: If you're on a home network, find Machine A's IP with:

  • Windows: ipconfig (look for IPv4 Address).
  • Mac/Linux: ifconfig or hostname -I.

Then in Machine B's UI, set Peer URL to http://192.168.1.100:8765 (adjust IP and port as needed).


Learn more