local json = require("json") local fb = require("framebuffer") local ringbuffer = require("ringbuffer") local UUID = "8b9faf9f-9470-4a50-b405-0af5f0152550" local ENDPOINT = "ws://localhost:8000/monitoring/computer/" .. UUID .. "/ws" print("[MAIN] Init") local enum SocketState "reset" "connecting" -- currently unused "ok" end local record Socket state: SocketState ws: http.Websocket end local socket: Socket = { state = "reset", ws = nil, } local function send(message: string) -- "message" needs to be valid JSON -- otherwise the server will not accept it if socket.state ~= "ok" then return end local r = { pcall(socket.ws.send, message) } if r[1] == false then if (r[2] as string):sub(-11) == "closed file" then socket.state = "reset" elseif (r[2] as string):sub(-9) == "too large" then -- TODO handle -- the connection stays open though end end end -- Set up framebuffer capture and statusline print("[MAIN] Setup framebuffer") local orig_native = term.native local buffer = fb.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 ws_task = coroutine.create(function() while true do if socket.state == "reset" then set_bar("[WS] RST", "78870111") local r = http.websocket(ENDPOINT) if r ~= false then socket.ws = r as http.Websocket set_bar("[WS] OK\x03", "78870DD5") socket.state = "ok" else set_bar("[WS] ERR", "78870EEE") end end repeat sleep(1) until socket.state ~= "ok" end end) local report_task = coroutine.create(function() local last_report = -1.0 while true do local now = os.clock() if now - last_report >= 0.05 then local message = json.encode({ screen = buffer.serialize() }) 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.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 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 term.native = orig_native term.redirect(term.native()) term.clear() term.setCursorPos(1,1)