mirror of
https://github.com/bryanthaboi/gen1recomp.git
synced 2026-08-18 11:44:42 +02:00
138 lines
4.4 KiB
Lua
138 lines
4.4 KiB
Lua
-- Cross-platform desktop secondary display. LOVE owns one window, so a
|
|
-- second minimal instance of this same app owns the companion window. ENet
|
|
-- is bundled with LOVE; binding it to loopback keeps frames and input local.
|
|
|
|
local Platform = require("src.core.Platform")
|
|
local HostShell = require("src.core.HostShell")
|
|
local okEnet, enet = pcall(require, "enet")
|
|
|
|
local DesktopScreen = {}
|
|
local state = {
|
|
enabled = false, blocked = false, host = nil, peer = nil,
|
|
token = nil, port = nil, touches = {}, retryAt = 0, heartbeatAt = 0,
|
|
}
|
|
|
|
local function now()
|
|
return love and love.timer and love.timer.getTime and love.timer.getTime()
|
|
or os.clock()
|
|
end
|
|
|
|
local function destroy(sendQuit)
|
|
if state.peer and sendQuit then
|
|
pcall(state.peer.send, state.peer, "Q" .. state.token, 1, "reliable")
|
|
end
|
|
if state.peer then pcall(state.peer.disconnect_now, state.peer) end
|
|
if state.host then pcall(state.host.destroy, state.host) end
|
|
state.host, state.peer, state.token, state.port = nil, nil, nil, nil
|
|
state.touches = {}
|
|
end
|
|
|
|
local function token()
|
|
local seed = table.concat({ tostring(os.time()), tostring(now()), tostring({}) }, ":")
|
|
local digest = love.data.hash("sha256", seed)
|
|
return love.data.encode("string", "hex", digest):sub(1, 24)
|
|
end
|
|
|
|
local function start()
|
|
if state.host or state.blocked or now() < state.retryAt then
|
|
return state.host ~= nil
|
|
end
|
|
local base = 49152 + math.floor(now() * 1000) % 12000
|
|
for attempt = 0, 31 do
|
|
local port = 49152 + (base - 49152 + attempt * 37) % 12000
|
|
local ok, host = pcall(enet.host_create,
|
|
("127.0.0.1:%d"):format(port), 1, 2)
|
|
if ok and host then
|
|
state.host, state.port, state.token = host, port, token()
|
|
local launched = HostShell.spawnSelfDetached({
|
|
("--display-companion=%d,%s"):format(port, state.token),
|
|
})
|
|
if launched then return true end
|
|
destroy(false)
|
|
break
|
|
end
|
|
end
|
|
state.retryAt = now() + 1
|
|
return false
|
|
end
|
|
|
|
local function service()
|
|
if not state.enabled or state.blocked then return end
|
|
if not state.host and not start() then return end
|
|
while state.host do
|
|
local ok, event = pcall(state.host.service, state.host, 0)
|
|
if not ok then
|
|
destroy(false)
|
|
state.retryAt = now() + 1
|
|
return
|
|
end
|
|
if not event then break end
|
|
if event.type == "receive" then
|
|
local data = event.data or ""
|
|
if data == "H" .. state.token then
|
|
state.peer = event.peer
|
|
elseif event.peer == state.peer
|
|
and data:sub(1, #state.token + 2) == "I" .. state.token .. "\n" then
|
|
state.touches[#state.touches + 1] = data:sub(#state.token + 3)
|
|
elseif event.peer == state.peer and data == "C" .. state.token then
|
|
state.blocked = true
|
|
destroy(false)
|
|
return
|
|
end
|
|
elseif event.type == "disconnect" and event.peer == state.peer then
|
|
destroy(false)
|
|
state.retryAt = now() + 1
|
|
return
|
|
end
|
|
end
|
|
if state.peer and now() >= state.heartbeatAt then
|
|
state.heartbeatAt = now() + 1
|
|
pcall(state.peer.send, state.peer, "P" .. state.token, 1, "unreliable")
|
|
end
|
|
end
|
|
|
|
function DesktopScreen.usable()
|
|
return okEnet and enet ~= nil and Platform.canSpawnProcess()
|
|
end
|
|
|
|
function DesktopScreen.available()
|
|
return DesktopScreen.detected()
|
|
end
|
|
|
|
function DesktopScreen.detected()
|
|
service()
|
|
return state.peer ~= nil
|
|
end
|
|
|
|
function DesktopScreen.push(imageData, w, h, background, preference)
|
|
service()
|
|
if not state.peer or not imageData or not imageData.getString then return false end
|
|
w, h = tonumber(w), tonumber(h)
|
|
if not w or not h or w < 1 or h < 1 or w > 4096 or h > 4096 then return false end
|
|
local ok, raw = pcall(imageData.getString, imageData)
|
|
if not ok or type(raw) ~= "string" or #raw ~= w * h * 4 then return false end
|
|
local packed = love.data.compress("string", "lz4", raw, 1)
|
|
local header = ("F%s\n%d,%d,%u,%s\n"):format(state.token, w, h,
|
|
tonumber(background) or 0, tostring(preference or "auto"):gsub("[^%w_:.-]", ""))
|
|
local sent = pcall(state.peer.send, state.peer, header .. packed, 0, "reliable")
|
|
return sent
|
|
end
|
|
|
|
function DesktopScreen.pollTouch()
|
|
service()
|
|
return table.remove(state.touches, 1)
|
|
end
|
|
|
|
function DesktopScreen.setEnabled(on)
|
|
on = on == true
|
|
if on == state.enabled then
|
|
if on then service() end
|
|
return
|
|
end
|
|
state.enabled = on
|
|
state.blocked = false
|
|
if on then start(); service() else destroy(true) end
|
|
end
|
|
|
|
return DesktopScreen
|