2020-04-12 01:59:56 +10:00
local vtansi = { }
2023-06-07 00:16:56 +10:00
local keyboardIgnore = { }
vtansi.activeBuffers = { }
2020-06-11 12:56:07 +10:00
vtansi.sequences = {
[ 28 ] = " \n " , -- newline
[ 200 ] = " \27 [A " , -- up
[ 203 ] = " \27 [D " , -- left
[ 205 ] = " \27 [C " , -- right
[ 208 ] = " \27 [B " , -- down
2020-06-21 21:31:19 +10:00
[ 201 ] = " \27 [5~ " , -- page up
2020-06-11 12:56:07 +10:00
[ 209 ] = " \27 [6~ " -- page down
}
2023-06-07 00:16:56 +10:00
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*.
2020-04-12 01:59:56 +10:00
local colours = { 0x0 , 0xFF0000 , 0x00FF00 , 0xFFFF00 , 0x0000FF , 0xFF00FF , 0x00B6FF , 0xFFFFFF }
local mx , my = gpu.maxResolution ( )
2023-06-07 00:16:56 +10:00
local buffer = nil
2020-04-12 01:59:56 +10:00
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
2023-06-07 00:16:56 +10:00
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
2020-04-12 01:59:56 +10:00
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 )
2023-06-07 00:16:56 +10:00
if buffer then
if vtansi.activeBuffers [ gpu.address ] == buffer then
gpu.setActiveBuffer ( 0 )
else
gpu.setActiveBuffer ( buffer )
end
end
2020-04-12 01:59:56 +10:00
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
2020-06-21 21:31:19 +10:00
checkCursor ( )
2020-04-12 01:59:56 +10:00
elseif cc == " \t " then
2023-08-05 15:15:03 +10:00
wb = wb .. ( " " ) : rep ( ( ( ( cx // 8 ) + 1 ) * 8 ) - cx + 1 )
2020-04-12 01:59:56 +10:00
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
2020-04-12 03:49:14 +10:00
cx , cy = math.min ( mx , tA [ 1 ] or 1 ) , math.min ( my , tA [ 2 ] or 1 )
2020-04-12 01:59:56 +10:00
elseif cc == " A " then
2020-06-21 21:31:19 +10:00
for i = 1 , ( tA [ 1 ] or 1 ) do
cy = cy - 1
checkCursor ( )
end
2020-04-12 01:59:56 +10:00
elseif cc == " B " then
2020-06-21 21:31:19 +10:00
for i = 1 , ( tA [ 1 ] or 1 ) do
cy = cy + 1
checkCursor ( )
end
2020-04-12 01:59:56 +10:00
elseif cc == " C " then
2020-06-21 21:31:19 +10:00
for i = 1 , ( tA [ 1 ] or 1 ) do
cx = cx + 1
checkCursor ( )
end
2020-04-12 01:59:56 +10:00
elseif cc == " D " then
2020-06-21 21:31:19 +10:00
for i = 1 , ( tA [ 1 ] or 1 ) do
cx = cx - 1
checkCursor ( )
end
2020-04-12 01:59:56 +10:00
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 )
2020-04-12 03:49:14 +10:00
dprint ( string.format ( " reporting %d;%d as current cursor position " , cx , cy ) )
2020-04-12 01:59:56 +10:00
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
2020-06-11 12:56:07 +10:00
fg , bg , ec , lb = 0xFFFFFF , 0 , false , true
2020-04-12 01:59:56 +10:00
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 ]
2020-04-26 08:01:53 +10:00
elseif num == 100 or num == 8 then -- disable local echo
2020-04-12 01:59:56 +10:00
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
2023-06-07 00:16:56 +10:00
return termwrite , buffer
2020-04-12 01:59:56 +10:00
end
2023-06-07 00:16:56 +10:00
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 = { }
2020-04-12 01:59:56 +10:00
local gpu = component.proxy ( gpua )
2023-06-07 00:16:56 +10:00
-- gpu.bind(scra)
local write , bn = vtansi.vtemu ( gpu , bn )
2020-04-12 01:59:56 +10:00
local kba = { }
for k , v in ipairs ( component.invoke ( scra , " getKeyboards " ) ) do
kba [ v ] = true
end
2020-06-11 12:56:07 +10:00
local buf , lbuf , echo = " " , false , false
os.spawn ( function ( )
2020-04-12 01:59:56 +10:00
while true do
2020-06-11 12:56:07 +10:00
local ty , ka , ch , kc = coroutine.yield ( )
2023-06-07 00:16:56 +10:00
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
2020-04-12 01:59:56 +10:00
end
end
end
2023-06-07 00:16:56 +10:00
end , string.format ( " ttyd[%s:%s/%i] " , gpua : sub ( 1 , 8 ) , scra : sub ( 1 , 8 ) , tonumber ( bn ) or 0 ) )
2020-04-12 01:59:56 +10:00
local function bread ( n )
2020-06-11 12:56:07 +10:00
coroutine.yield ( )
local r = buf
buf = " "
2020-04-12 01:59:56 +10:00
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