local vtansi = {} local keyboardIgnore = {} vtansi.activeBuffers = {} vtansi.sequences = { [28] = "\n", -- newline [200] = "\27[A", -- up [203] = "\27[D", -- left [205] = "\27[C", -- right [208] = "\27[B", -- down [201] = "\27[5~", -- page up [209] = "\27[6~" -- page down } vtansi.keys = {} vtansi.keys[0x38] = "lalt" vtansi.keys[0xB8] = "ralt" function vtansi.saveToBuffer(gpu,idx) gpu.bitblt(idx, nil, nil, nil, nil, 0) end function vtansi.loadFromBuffer(gpu,idx) gpu.bitblt(0, nil, nil, nil, nil, idx) end function vtansi.switchToBuffer(gpu,idx) -- copy screen to the active buffer vtansi.saveToBuffer(gpu,vtansi.activeBuffers[gpu.address]) -- copy the new buffer to the screen vtansi.loadFromBuffer(gpu,idx) vtansi.activeBuffers[gpu.address] = idx end function vtansi.vtemu(gpu,bn) -- table number -- function -- takes GPU component proxy *gpu* and returns a function to write to it in a manner like an ANSI terminal, either allocating a new buffer or using *bn*. local colours = {0x0,0xFF0000,0x00FF00,0xFFFF00,0x0000FF,0xFF00FF,0x00B6FF,0xFFFFFF} local mx, my = gpu.maxResolution() local buffer = nil local cx, cy = 1, 1 local pc = " " local lc = "" local mode = 0 -- 0 normal, 1 escape, 2 command local lw = true local sx, sy = 1,1 local cs = "" local bg, fg = 0, 0xFFFFFF -- setup if gpu.getActiveBuffer then buffer = bn or gpu.allocateBuffer(mx,my) vtansi.activeBuffers[gpu.address] = vtansi.activeBuffers[gpu.address] or buffer local oldActiveBuffer = vtansi.activeBuffers[gpu.address] gpu.setActiveBuffer(buffer) gpu.setResolution(mx,my) gpu.fill(1,1,mx,my," ") gpu.setActiveBuffer(oldActiveBuffer) else gpu.setResolution(mx,my) gpu.fill(1,1,mx,my," ") end local function checkCursor() if cx > mx and lw then cx, cy = 1, cy+1 end if cy > my then gpu.copy(1,2,mx,my-1,0,-1) gpu.fill(1,my,mx,1," ") cy=my end if cy < 1 then cy = 1 end if cx < 1 then cx = 1 end end local function termwrite(s) if buffer then if vtansi.activeBuffers[gpu.address] == buffer then gpu.setActiveBuffer(0) else gpu.setActiveBuffer(buffer) end end local wb = "" local lb, ec = nil, nil local function flushwb() while wb:len() > 0 do checkCursor() local wl = wb:sub(1,mx-cx+1) wb = wb:sub(wl:len()+1) gpu.set(cx, cy, wl) cx = cx + wl:len() end end local rs = "" s=s:gsub("\8","\27[D") pc = gpu.get(cx,cy) gpu.setForeground(fg) gpu.setBackground(bg) gpu.set(cx,cy,pc) for cc in s:gmatch(".") do if mode == 0 then if cc == "\n" then flushwb() cx,cy = 1, cy+1 checkCursor() elseif cc == "\t" then wb=wb..(" "):rep((((cx//8)+1) * 8) - cx + 1) elseif cc == "\27" then flushwb() mode = 1 else wb = wb .. cc end elseif mode == 1 then if cc == "[" then mode = 2 else mode = 0 end elseif mode == 2 then if cc:match("[%d;]") then cs = cs .. cc else mode = 0 local tA = {} for s in cs:gmatch("%d+") do tA[#tA+1] = tonumber(s) end if cc == "H" then cx, cy = math.min(mx,tA[1] or 1), math.min(my,tA[2] or 1) elseif cc == "A" then for i = 1, (tA[1] or 1) do cy = cy - 1 checkCursor() end elseif cc == "B" then for i = 1, (tA[1] or 1) do cy = cy + 1 checkCursor() end elseif cc == "C" then for i = 1, (tA[1] or 1) do cx = cx + 1 checkCursor() end elseif cc == "D" then for i = 1, (tA[1] or 1) do cx = cx - 1 checkCursor() end elseif cc == "s" then sx, sy = cx, cy elseif cc == "u" then cx, cy = sx, sy elseif cc == "n" and tA[1] == 6 then rs = string.format("%s\27[%d;%dR",rs,cx,cy) elseif cc == "K" and tA[1] == 1 then gpu.fill(1,cy,cx,1," ") elseif cc == "K" and tA[1] == 2 then gpu.fill(cx,cy,mx,1," ") elseif cc == "K" then gpu.fill(1,cy,mx,1," ") elseif cc == "J" and tA[1] == 1 then gpu.fill(1,1,mx,cy," ") elseif cc == "J" and tA[1] == 2 then gpu.fill(1,1,mx,my," ") cx, cy = 1, 1 elseif cc == "J" then gpu.fill(1,cy,mx,my," ") elseif cc == "m" then for _,num in ipairs(tA) do if num == 0 then fg,bg,ec,lb = 0xFFFFFF,0,false,true elseif num == 7 then local nfg,nbg = bg, fg fg, bg = nfg, nbg elseif num > 29 and num < 38 then fg = colours[num-29] elseif num > 39 and num < 48 then bg = colours[num-39] elseif num == 100 or num == 8 then -- disable local echo ec = false elseif num == 101 then -- disable line mode lb = false end end gpu.setForeground(fg) gpu.setBackground(bg) end cs = "" checkCursor() end end end flushwb() checkCursor() pc = gpu.get(cx,cy) gpu.setForeground(bg) gpu.setBackground(fg) gpu.set(cx,cy,pc) gpu.setForeground(fg) gpu.setBackground(bg) return rs, lb, ec end return termwrite, buffer end function vtansi.vtsession(gpua,scra,bn) -- string string number -- function function function -- creates a process to handle the GPU and screen address combination *gpua*/*scra*, optionally using buffer number *bn* specifically. Returns read, write and "close" functions. local modifiers = {} local gpu = component.proxy(gpua) -- gpu.bind(scra) local write, bn = vtansi.vtemu(gpu,bn) local kba = {} for k,v in ipairs(component.invoke(scra,"getKeyboards")) do kba[v]=true end local buf, lbuf, echo = "", false, false os.spawn(function() while true do local ty,ka,ch,kc = coroutine.yield() if kba[ka] and keyboardIgnore[ka] == bn then keyboardIgnore[ka] = nil end if kba[ka] and vtansi.keys[kc] then modifiers[vtansi.keys[kc]] = ty == "key_down" end if ty == "key_down" and kba[ka] and (bn == nil or vtansi.activeBuffers[gpua] == bn) then if bn and ty == "key_down" and kba[ka] and ch == 46 and kc == 52 and (modifiers.lalt or modifiers.ralt) then -- next buffer local allBuffers = gpu.buffers() for k,v in ipairs(allBuffers) do if v == vtansi.activeBuffers[gpu.address] and allBuffers[k+1] and not keyboardIgnore[ka] then keyboardIgnore[ka] = bn vtansi.switchToBuffer(gpu,allBuffers[k+1]) end end elseif bn and ty == "key_down" and kba[ka] and ch == 44 and kc == 51 and (modifiers.lalt or modifiers.ralt) then -- previous buffer local allBuffers = gpu.buffers() for k,v in ipairs(allBuffers) do if v == vtansi.activeBuffers[gpu.address] and allBuffers[k-1] and not keyboardIgnore[ka] then keyboardIgnore[ka] = bn vtansi.switchToBuffer(gpu,allBuffers[k-1]) end end else local outs if ch > 0 then outs = string.char(ch) end outs = vtansi.sequences[kc] or outs if outs then if echo then write(outs) end buf=buf..outs end end end end end,string.format("ttyd[%s:%s/%i]",gpua:sub(1,8),scra:sub(1,8),tonumber(bn) or 0)) local function bread(n) coroutine.yield() local r = buf buf = "" return r end local function bwrite(d) local ba, lb, ec = write(d) buf = buf .. ba if lb ~= nil then lbuf = lb end if ec ~= nil then echo = ec end end return bread, bwrite, function() io.write("\27[2J\27[H") end end return vtansi