local json = require("json") local Framebuffer = require("framebuffer") local Ringbuffer = require("ringbuffer") local Socket = require("socket") local auth = require("auth") local ENDPOINT = auth.server:gsub("http", "ws", 1) .. "/ipmi/computer/" .. auth.id .. "/ws" local HEADERS = { ["Authorization"] = "Bearer " .. auth.token } print("[MAIN] Init") local socket = Socket.new(ENDPOINT, HEADERS) -- 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) -- basically parallel.waitForAny local record Task coro: thread filter: string | nil end local background_tasks: {Task} = { {coro = ws_task}, {coro = report_task}, } local shell_task: Task = { coro = coroutine.create(function() shell.run("shell") end) } local function handle_event(e: table, pid: integer) if e[1] == "terminate" then return end local task = background_tasks[pid] if task.filter == nil or task.filter == e[1] then local ok, param = coroutine.resume(task.coro, table.unpack(e as {any})) if not ok then term.native = orig_native term.redirect(term.native()) term.clear() term.setCursorPos(1,1) print(("OMEGABIG OOF @ PID %d"):format(pid)) error(param, 0) else task.filter = param as string end end end local event_queue: Ringbuffer.Ringbuffer = Ringbuffer.new(64) event_queue:push({n = 0}) local shell_deaths: {any} = {} 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, #background_tasks do handle_event(e, pid) end if shell_task.filter == nil or shell_task.filter == e[1] or e[1] == "terminate" then local ok, param = coroutine.resume(shell_task.coro, table.unpack(e as {any})) if not ok then -- shell died i guess? table.insert(shell_deaths, param) else shell_task.filter = param as string end end if coroutine.status(shell_task.coro) == "dead" then shell_running = false end end end socket:close() term.native = orig_native term.redirect(prev_term) term.clear() term.setCursorPos(1,1) for i = 1, #shell_deaths do print(shell_deaths[i]) end