From eb95f9715e78fa83da7325f173e7f79019eb651a Mon Sep 17 00:00:00 2001 From: XeonSquared Date: Wed, 7 Jun 2023 00:16:56 +1000 Subject: [PATCH] virtual terminal support with vtansi and getty, for machines with VRAM available --- lib/vtansi.lua | 98 +++++++++++++++++++++++++++++++++++++++++++++---------- service/getty.lua | 29 +++++++++++----- 2 files changed, 102 insertions(+), 25 deletions(-) diff --git a/lib/vtansi.lua b/lib/vtansi.lua index c6b1208..1831a5f 100644 --- a/lib/vtansi.lua +++ b/lib/vtansi.lua @@ -1,4 +1,6 @@ local vtansi = {} +local keyboardIgnore = {} +vtansi.activeBuffers = {} vtansi.sequences = { [28] = "\n", -- newline [200] = "\27[A", -- up @@ -8,9 +10,26 @@ vtansi.sequences = { [201] = "\27[5~", -- page up [209] = "\27[6~" -- page down } -function vtansi.vtemu(gpu) -- table -- function -- takes GPU component proxy *gpu* and returns a function to write to it in a manner like an ANSI terminal +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 = "" @@ -21,8 +40,19 @@ function vtansi.vtemu(gpu) -- table -- function -- takes GPU component proxy *gp local bg, fg = 0, 0xFFFFFF -- setup - gpu.setResolution(mx,my) - gpu.fill(1,1,mx,my," ") + 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 @@ -37,6 +67,13 @@ function vtansi.vtemu(gpu) -- table -- function -- takes GPU component proxy *gp 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() @@ -161,13 +198,14 @@ function vtansi.vtemu(gpu) -- table -- function -- takes GPU component proxy *gp return rs, lb, ec end - return termwrite + return termwrite, buffer end -function vtansi.vtsession(gpua,scra) -- string string -- table -- creates a process to handle the GPU and screen address combination *gpua*/*scra*. Returns read, write and "close" functions. +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 = vtansi.vtemu(gpu) +-- 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 @@ -176,19 +214,45 @@ function vtansi.vtsession(gpua,scra) -- string string -- table -- creates a proc os.spawn(function() while true do local ty,ka,ch,kc = coroutine.yield() - if ty == "key_down" and kba[ka] then - 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 + 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]",gpua:sub(1,8),scra:sub(1,8))) + 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 diff --git a/service/getty.lua b/service/getty.lua index adceae2..54f78c9 100644 --- a/service/getty.lua +++ b/service/getty.lua @@ -37,20 +37,32 @@ local function spawnShell(fin,fout) io.input(fin) io.output(fout):setvbuf("no") print(_OSVERSION.." - "..tostring(math.floor(computer.totalMemory()/1024)).."K RAM") - return os.spawn(function() local w,r = pcall(shell.interactive) if not w then syslog(r) end end, "shell: "..tostring(fin)) + print((os.getenv("HOSTNAME") or "unknown") .. " on " .. fin) +-- return os.spawn(function() local w,r = pcall(shell.interactive) if not w then syslog(r) end end, "shell: "..tostring(fin)) + return os.spawn(shell.interactive, "shell: "..tostring(fin)) end local function allocate() for k,v in pairs(gpus) do - dprint(k) + dprint("Setting up display "..k) local sA = nextScreen(v[2]) if v[1] == false and sA then - local r,w = vtansi.vtsession(k,sA) - devfs.register("tty"..tostring(ttyn), function() return r,w,function() end end) + local terminals = 1 + local gpu = component.proxy(k) + gpu.bind(sA) + if gpu.buffers then + gpu.freeAllBuffers() + local mw, mh = gpu.maxResolution() + terminals = math.floor(gpu.freeMemory() / (mw * mh)) + end + for i = 1, terminals do + local r,w = vtansi.vtsession(k,sA) + devfs.register("tty"..tostring(ttyn), function() return r,w,function() end end) + pids["tty"..tostring(ttyn)] = {-1} + ttyn = ttyn + 1 + end gpus[k][1] = true screens[sA][1] = true - pids["tty"..tostring(ttyn)] = {-1} - ttyn = ttyn + 1 end end end @@ -59,14 +71,15 @@ function start() basepid = os.spawn(function() scan() allocate() -dprint("screens ready") +dprint("Display setup complete.") while true do coroutine.yield() for k,v in pairs(pids) do if not os.taskInfo(v[1]) then dprint("Spawning new shell for "..k) pids[k][1] = spawnShell(v[2] or "/dev/"..k, v[3] or "/dev/"..k) - pids[k][2], pids[k][3] = pids[k][2] or io.input(), pids[k][3] or io.output() + pids[k][2], pids[k][3] = pids[k][2] or "/dev/"..k, pids[k][3] or "/dev/"..k + coroutine.yield() end end end