--- https://github.com/SquidDev-CC/cloud-catcher/blob/master/src/host/framebuffer.lua --- but slightly modified because this isn't cloud catcher --- Just another frame buffer, but this one is serialisable! local stringify = require("json").stringify local colour_lookup = {} for i = 0, 15 do colour_lookup[2 ^ i] = string.format("%x", i) end --- Create a buffer which can be converted to a string and transmitted. local function buffer(original) local text = {} local text_colour = {} local back_colour = {} local palette = {} local palette_24 = {} local cursor_x, cursor_y = 1, 1 local cursor_blink = false local cur_text_colour = "0" local cur_back_colour = "f" local sizeX, sizeY = original.getSize() local color = original.isColor() local dirty = false local redirect = {} if original.getPaletteColour then for i = 0, 15 do local c = 2 ^ i palette[c] = { original.getPaletteColour( c ) } palette_24[colour_lookup[c]] = colours.rgb8(original.getPaletteColour( c )) end end function redirect.write(writeText) writeText = tostring(writeText) original.write(writeText) dirty = true -- If we're off the screen then just emulate a write if cursor_y > sizeY or cursor_y < 1 or cursor_x + #writeText <= 1 or cursor_x > sizeX then cursor_x = cursor_x + #writeText return end -- Adjust text to fit on screen if cursor_x < 1 then writeText = writeText:sub(-cursor_x + 2) cursor_x = 1 elseif cursor_x + #writeText > sizeX then writeText = writeText:sub(1, sizeX - cursor_x + 1) end local lineText = text[cursor_y] local lineColor = text_colour[cursor_y] local lineBack = back_colour[cursor_y] local preStop = cursor_x - 1 local preStart = math.min(1, preStop) local postStart = cursor_x + #writeText local postStop = sizeX local sub, rep = string.sub, string.rep text[cursor_y] = sub(lineText, preStart, preStop)..writeText..sub(lineText, postStart, postStop) text_colour[cursor_y] = sub(lineColor, preStart, preStop)..rep(cur_text_colour, #writeText)..sub(lineColor, postStart, postStop) back_colour[cursor_y] = sub(lineBack, preStart, preStop)..rep(cur_back_colour, #writeText)..sub(lineBack, postStart, postStop) cursor_x = cursor_x + #writeText end function redirect.blit(writeText, writeFore, writeBack) original.blit(writeText, writeFore, writeBack) dirty = true -- If we're off the screen then just emulate a write if cursor_y > sizeY or cursor_y < 1 or cursor_x + #writeText <= 1 or cursor_x > sizeX then cursor_x = cursor_x + #writeText return end if cursor_x < 1 then --adjust text to fit on screen starting at one. writeText = writeText:sub(-cursor_x + 2) writeFore = writeFore:sub(-cursor_x + 2) writeBack = writeBack:sub(-cursor_x + 2) cursor_x = 1 elseif cursor_x + #writeText > sizeX then writeText = writeText:sub(1, sizeX - cursor_x + 1) writeFore = writeFore:sub(1, sizeX - cursor_x + 1) writeBack = writeBack:sub(1, sizeX - cursor_x + 1) end local lineText = text[cursor_y] local lineColor = text_colour[cursor_y] local lineBack = back_colour[cursor_y] local preStop = cursor_x - 1 local preStart = math.min(1, preStop) local postStart = cursor_x + #writeText local postStop = sizeX local sub = string.sub text[cursor_y] = sub(lineText, preStart, preStop)..writeText..sub(lineText, postStart, postStop) text_colour[cursor_y] = sub(lineColor, preStart, preStop)..writeFore..sub(lineColor, postStart, postStop) back_colour[cursor_y] = sub(lineBack, preStart, preStop)..writeBack..sub(lineBack, postStart, postStop) cursor_x = cursor_x + #writeText end function redirect.clear() for i = 1, sizeY do text[i] = string.rep(" ", sizeX) text_colour[i] = string.rep(cur_text_colour, sizeX) back_colour[i] = string.rep(cur_back_colour, sizeX) end dirty = true return original.clear() end function redirect.clearLine() -- If we're off the screen then just emulate a clearLine if cursor_y > sizeY or cursor_y < 1 then return end text[cursor_y] = string.rep(" ", sizeX) text_colour[cursor_y] = string.rep(cur_text_colour, sizeX) back_colour[cursor_y] = string.rep(cur_back_colour, sizeX) dirty = true return original.clearLine() end function redirect.getCursorPos() return cursor_x, cursor_y end function redirect.setCursorPos(x, y) if type(x) ~= "number" then error("bad argument #1 (expected number, got " .. type(x) .. ")", 2) end if type(y) ~= "number" then error("bad argument #2 (expected number, got " .. type(y) .. ")", 2) end if x ~= cursor_x or y ~= cursor_y then cursor_x = math.floor(x) cursor_y = math.floor(y) dirty = true end return original.setCursorPos(x, y) end function redirect.setCursorBlink(b) if type(b) ~= "boolean" then error("bad argument #1 (expected boolean, got " .. type(b) .. ")", 2) end if cursor_blink ~= b then cursor_blink = b dirty = true end return original.setCursorBlink(b) end function redirect.getSize() return sizeX, sizeY end function redirect.scroll(n) if type(n) ~= "number" then error("bad argument #1 (expected number, got " .. type(n) .. ")", 2) end local empty_text = string.rep(" ", sizeX) local empty_text_colour = string.rep(cur_text_colour, sizeX) local empty_back_colour = string.rep(cur_back_colour, sizeX) if n > 0 then for i = 1, sizeY do text[i] = text[i + n] or empty_text text_colour[i] = text_colour[i + n] or empty_text_colour back_colour[i] = back_colour[i + n] or empty_back_colour end elseif n < 0 then for i = sizeY, 1, -1 do text[i] = text[i + n] or empty_text text_colour[i] = text_colour[i + n] or empty_text_colour back_colour[i] = back_colour[i + n] or empty_back_colour end end dirty = true return original.scroll(n) end function redirect.setTextColour(clr) if type(clr) ~= "number" then error("bad argument #1 (expected number, got " .. type(clr) .. ")", 2) end local new_colour = colour_lookup[clr] or error("Invalid colour (got " .. clr .. ")" , 2) if new_colour ~= cur_text_colour then dirty = true cur_text_colour = new_colour end return original.setTextColour(clr) end redirect.setTextColor = redirect.setTextColour function redirect.setBackgroundColour(clr) if type(clr) ~= "number" then error("bad argument #1 (expected number, got " .. type(clr) .. ")", 2) end local new_colour = colour_lookup[clr] or error("Invalid colour (got " .. clr .. ")" , 2) if new_colour ~= cur_back_colour then dirty = true cur_back_colour = new_colour end return original.setBackgroundColour(clr) end redirect.setBackgroundColor = redirect.setBackgroundColour function redirect.isColour() return color == true end redirect.isColor = redirect.isColour function redirect.getTextColour() return 2 ^ tonumber(cur_text_colour, 16) end redirect.getTextColor = redirect.getTextColour function redirect.getBackgroundColour() return 2 ^ tonumber(cur_back_colour, 16) end redirect.getBackgroundColor = redirect.getBackgroundColour if original.getPaletteColour then function redirect.setPaletteColour(colour, r, g, b) local palcol = palette[colour] if not palcol then error("Invalid colour (got " .. tostring(colour) .. ")", 2) end if type(r) == "number" and g == nil and b == nil then palcol[1], palcol[2], palcol[3] = colours.rgb8(r) palette_24[colour] = r else if type(r) ~= "number" then error("bad argument #2 (expected number, got " .. type(r) .. ")", 2) end if type(g) ~= "number" then error("bad argument #3 (expected number, got " .. type(g) .. ")", 2) end if type(b) ~= "number" then error("bad argument #4 (expected number, got " .. type(b ) .. ")", 2 ) end palcol[1], palcol[2], palcol[3] = r, g, b palette_24[colour_lookup[colour]] = colours.rgb8(r, g, b) end dirty = true return original.setPaletteColour(colour, r, g, b) end redirect.setPaletteColor = redirect.setPaletteColour function redirect.getPaletteColour(colour) local palcol = palette[colour] if not palcol then error("Invalid colour (got " .. tostring(colour) .. ")", 2) end return palcol[1], palcol[2], palcol[3] end redirect.getPaletteColor = redirect.getPaletteColour end function redirect.is_dirty() return dirty end function redirect.clear_dirty() dirty = false end function redirect.serialize() local palette = {} for i = 0, 15 do palette[i+1] = string.format("#%06x", palette_24[string.format("%x", i)]) end return stringify { framebuffer = { width = sizeX, height = sizeY, cursorX = cursor_x, cursorY = cursor_y, cursorBlink = cursor_blink, -- curFg = cur_text_colour, curBg = cur_back_colour, palette = palette, textBuf = text, fgBuf = text_colour, bgBuf = back_colour }} end -- Ensure we're in sync with the parent terminal redirect.setCursorPos(1, 1) redirect.setBackgroundColor(colours.black) redirect.setTextColor(colours.white) redirect.clear() return redirect end return { buffer = buffer }