local json = require("json") local Framebuffer = require("framebuffer") local Ringbuffer = require("ringbuffer") local Socket = require("socket") local UUID = "8b9faf9f-9470-4a50-b405-0af5f0152550" local ENDPOINT = "ws://localhost:8000/ipmi/computer/" .. UUID .. "/ws" print("[MAIN] Init") local socket = Socket.new(ENDPOINT) -- Set up framebuffer capture and statusline print("[MAIN] Setup framebuffer") local prev_term = term.current() local orig_native = term.native local buffer = Framebuffer.wrap(orig_native()) term.native = function(): term.Redirect return buffer.target end local width, height = term.getSize() local top_line = window.create(buffer.target, 1, 1, width, 1) local main_view = window.create(buffer.target, 1, 2, width, height - 1) term.redirect(main_view as term.Redirect) local function set_bar(text: string, fg: string | nil, bg: string | nil) fg = fg or ("9"):rep(text:len()) bg = bg or ("f"):rep(text:len()) top_line.clear() top_line.setCursorPos(1,1) top_line.blit(text, fg, bg) main_view.restoreCursor() end -- Create tasks local bar_codes: { Socket.State: {string} } = { ["reset"] = {"[WS] RST", "78870111"}, ["error"] = {"[WS] ERR", "78870EEE"}, ["ok"] = {"[WS] OK\x03", "78870DD5"}, ["viewer_connected"] = {"[WS] CON", "78870999"}, } socket:on_state_change(function(new_state: Socket.State) set_bar(table.unpack(bar_codes[new_state])) end) local ws_task = coroutine.create(function() while true do if socket:is_bad_state() then socket:reconnect() end sleep(1) end end) local report_task = coroutine.create(function() local last_report = -1.0 while true do local now = os.clock() local interval = (socket.state == "viewer_connected") and 0.05 or 1 if now - last_report >= interval then local message = json.encode({ screen = buffer.serialize() }) socket:send(message) last_report = now end sleep(0) -- until next gametick end end) local shell_task = coroutine.create(function() shell.run("shell") end) -- basically parallel.waitForAny local record Task coro: thread filter: string | nil end local tasks: {Task} = { {coro = shell_task}, -- pid 1 {coro = ws_task}, {coro = report_task}, } local event_queue: Ringbuffer.Ringbuffer = Ringbuffer.new(64) event_queue:push({n = 0}) local shell_running = true while shell_running do local e: table if not event_queue:is_empty() then e = event_queue:pop() as table else e = table.pack(os.pullEventRaw()) end if e[1] == "websocket_message" and e[2] == ENDPOINT then local payload = json.decode(e[3] as string) as table if payload["type"] == "push_event" then event_queue:push(payload["event"] as table) elseif payload["type"] == "viewer_connect" then socket:signal_viewer_connect(true) elseif payload["type"] == "viewer_disconnect" then socket:signal_viewer_connect(false) end else for pid = 1, #tasks do local task = tasks[pid] if task.filter == nil or task.filter == e[1] or e[1] == "terminate" then local ok, param = coroutine.resume(task.coro, table.unpack(e as {any})) if not ok then term.redirect(orig_native()) term.clear() term.setCursorPos(1,1) print("OMEGABIG OOF") print(("pid %d"):format(pid)) error(param, 0) else task.filter = param as string end if pid == 1 and coroutine.status(task.coro) == "dead" then shell_running = false end end end end end socket:close() term.native = orig_native term.redirect(prev_term) term.clear() term.setCursorPos(1,1)