local textgpu = {} local mapping = {} local palette = {} -- generate color mapping for g=0, 255, 0x24 do for b=0, 256, 0x40 do for r=0, 255, 0x33 do mapping[#mapping+1] = (math.min(r, 255) * 0x10000) + (math.min(g, 255) * 0x100) + math.min(b, 255) end end end for i=0, 15, 1 do mapping[#mapping+1] = (i+1) * 0x0f0f0f palette[i] = (i+1) * 0x0f0f0f end local write = io.write local flush = io.flush local background = 1 local foreground = 256 local tbuffer = {} local bbuffer = {} local fbuffer = {} local function prep(w, h) local tl = string.rep(" ", w) local bb = string.rep("\0", w) local fb = string.rep("\255", w) for i=1, h, 1 do tbuffer[i] = tl bbuffer[i] = bb fbuffer[i] = fb end end local fg = 256 local bg = 1 local w, h = 1, 1 local function rgb(i) return i>>16&0xFF, i>>8&0xFF, i&0xFF end local unsub, fullRefresh local function set(x, y, text, f, b, safe) if x > w or x < 1 or y > h or y < 1 or not tbuffer[y] then if safe then return else error("index out of bounds: " .. y, 3) end end if not text then return end f = f or fg b = b or bg local len = utf8.len(text) if x + len > w then --len = (x + len) - w --text = unsub(text, 1, len) end tbuffer[y] = unsub(tbuffer[y], 1, x - 1) .. text .. unsub(tbuffer[y], x + len) if type(f) == "string" then fbuffer[y] = fbuffer[y]:sub(1, x - 1) .. f .. fbuffer[y]:sub(x + len) else local F = mapping[f] local r, g, _b = rgb(F) write("\27[38;2;", r, ";", g, ";", _b, "m") fbuffer[y] = fbuffer[y]:sub(1, x - 1) .. string.rep(string.char(f - 1), len) .. fbuffer[y]:sub(x + len) end if type(b) == "string" then bbuffer[y] = bbuffer[y]:sub(1, x - 1) .. b .. bbuffer[y]:sub(x + len) else local B = mapping[b] local r, g, _b = rgb(B) write("\27[48;2;", r, ";", g, ";", _b, "m") bbuffer[y] = bbuffer[y]:sub(1, x - 1) .. string.rep(string.char(b - 1), len) .. bbuffer[y]:sub(x + len) end write("\27[", y, ";", x, "H", text) flush() end fullRefresh = function() for i=1, h, 1 do local text = tbuffer[i] local fgt = fbuffer[i] local bgt = bbuffer[i] local pb, pf fgt = fgt:gsub("()(.)", function(n, fc) local bc, tc = bgt:sub(n,n), text:sub(n,n) local str = "" if bc ~= pb then pb = bc local B = mapping[bc:byte() + 1] str = str .. string.format("\27[48;2;%d;%d;%dm", rgb(B)) end if fc ~= pf then pf = fc local F = mapping[fc:byte() + 1] str = str .. string.format("\27[38;2;%d;%d;%dm", rgb(F)) end str = str .. tc return str end) write("\27[", i, ";1H", fgt) end flush() end local function get(x, y, len, safe) if x < 1 or x > w or y < 1 or y > h then if safe then return else error("index out of bounds: " .. y, 3) end end len = (len or 1) - 1 if x + len > w then len = (x + len) - w end local text = unsub(tbuffer[y], x, x + len) if len == 0 then return text, mapping[fbuffer[y]:sub(x, x):byte() + 1], mapping[bbuffer[y]:sub(x, x):byte() + 1] else return text, fbuffer[y]:sub(x, x + len), bbuffer[y]:sub(x, x + len) end end function textgpu.start() unsub = modules.sandbox.unicode.sub local gpu = {} function gpu.setForeground(color, ispalette) checkArg(1, color, "number") checkArg(2, ispalette, "boolean", "nil") if ispalette then if not palette[color] then error("invalid palette index", 2) end color = palette[color] end local index = math.floor(modules.color.nearest(color, mapping)) fg = index or fg return true end function gpu.setBackground(color, ispalette) checkArg(1, color, "number") checkArg(2, ispalette, "boolean", "nil") if ispalette then if not palette[color] then error("invalid palette index", 2) end color = palette[color] end local index = math.floor(modules.color.nearest(color, mapping)) bg = index or bg return true end function gpu.setPaletteColor(c, v) checkArg(1, c, "number") checkArg(2, v, "number") if not palette[c] then error("invalid palette index", 2) end palette[c] = v end function gpu.getPaletteColor(c) checkArg(1, c, "number") if not palette[c] then error("invalid palette index", 2) end return palette[c] end function gpu.getForeground() return mapping[fg] end function gpu.getBackground() return mapping[bg] end function gpu.getDepth() return 4 end function gpu.maxDepth() return 4 end function gpu.setDepth() return true end function gpu.getResolution() return termutils.getSize() end function gpu.setResolution() return false end gpu.maxResolution = gpu.getResolution gpu.getViewport = gpu.getResolution gpu.setViewport = gpu.setResolution function gpu.set(x, y, text, vert) checkArg(1, x, "number") checkArg(2, y, "number") checkArg(3, text, "string") checkArg(4, vert, "boolean", "nil") vert = false if vert then local i = 1 local len = utf8.len(text) while i <= len do set(x, y + i - 1, unsub(text, i, i)) i = i + 1 end return true else set(x, y, text) return true end end function gpu.get(x, y) checkArg(1, x, "number") checkArg(2, y, "number") return get(x, y) end function gpu.fill(x, y, W, H, c) checkArg(1, x, "number") checkArg(1, y, "number") checkArg(1, W, "number") checkArg(1, H, "number") checkArg(1, c, "string") c = unsub(c, 1, 1) if #c == 0 then return true end local str = c:rep(W) for i=1, H, 1 do set(x, y + i - 1, str, nil, nil, true) end return true end function gpu.copy(x, y, W, H, xd, yd) checkArg(1, x, "number") checkArg(2, y, "number") checkArg(3, W, "number") checkArg(4, H, "number") checkArg(5, xd, "number") checkArg(6, yd, "number") if xd == 0 and yd == 0 then return true end -- hah local start, stop, step if yd < 0 then -- moving up - copy from the top down start, stop, step = y, y + H, 1 else -- moving down - copy from the bottom up start, stop, step = y + H, y, -1 end for i=start, stop, step do local str, fstr, bstr = get(x, i, W, true) if str and fstr and bstr then set(x + xd, i + yd, str, fstr, bstr, true) end end fullRefresh() return true end local screenAddr function gpu.getScreen() return screenAddr end function gpu.bind() --[[STUB]] end if not termutils.init() then return nil, "Cannot initialize terminal based gpu" end write("\x1b[?25l") --Disable cursor w, h = gpu.getResolution() prep(w, h) fullRefresh() gpu.setForeground(0xFFFFFF) gpu.setBackground(0x000000) local gpuaddr = modules.component.api.register(nil, "gpu", gpu) screenAddr = modules.component.api.register(nil, "screen", {getKeyboards = function() return {"TODO:SetThisUuid"} end}) --verry dummy screen, TODO: make it better, kbd uuid also in epoll.c modules.component.api.register("TODO:SetThisUuid", "keyboard", {}) deadhooks[#deadhooks + 1] = function() write("\x1b[?25h\x1b[" .. ((h-1)|0) .. ";1H") --Enable cursor on quit io.flush() termutils.restore() end return gpuaddr end return textgpu