rework everything to use stdin/stdout with color
parent
2fad8db849
commit
c146f860cc
@ -0,0 +1,55 @@
|
|||||||
|
local ok, err = xpcall(function(...)
|
||||||
|
local tty = ...
|
||||||
|
io.stdin, io.stdout = tty, tty
|
||||||
|
local term = dofile('/lib/term.lua').wrap(io) --TODO require()
|
||||||
|
|
||||||
|
term:write(_VERSION, ' // ', _OSVERSION, '\n')
|
||||||
|
|
||||||
|
local env = setmetatable({}, {__index = _ENV}) --TODO pcall require() on __index
|
||||||
|
|
||||||
|
::repl::
|
||||||
|
local line, err = term:prompt({ fg = 0x00ff00, 'lua> ' })
|
||||||
|
if not line then
|
||||||
|
if err == nil then --eof
|
||||||
|
return
|
||||||
|
elseif err == 'interrupted' then
|
||||||
|
term:write('\n')
|
||||||
|
goto repl
|
||||||
|
else
|
||||||
|
error(err)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
local f, err = load('return ' .. line, '=expr', 't', env)
|
||||||
|
if not f then f, err = load(line, '=repl', 't', env) end
|
||||||
|
if not f then
|
||||||
|
term:error(tostring(err), '\n')
|
||||||
|
goto repl
|
||||||
|
end
|
||||||
|
|
||||||
|
local results = table.pack(pcall(f))
|
||||||
|
if not results[1] then
|
||||||
|
term:error('error: ', tostring(results[2]), '\n')
|
||||||
|
goto repl
|
||||||
|
end
|
||||||
|
|
||||||
|
for i = 2, results.n do
|
||||||
|
local val = results[i]
|
||||||
|
local t = type(val)
|
||||||
|
if i > 2 then term:write('\t') end
|
||||||
|
if t == 'nil' or t == 'boolean' or t == 'number' then
|
||||||
|
term:write({ fg = 0xffff00, tostring(val) })
|
||||||
|
elseif t == 'string' then
|
||||||
|
term:write({ fg = 0x9292ff, string.format('%q', val) })
|
||||||
|
else
|
||||||
|
term:write({ fg = 0xff80ff, tostring(val) })
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
if results.n > 1 then term:write('\n') end
|
||||||
|
|
||||||
|
goto repl
|
||||||
|
end, function(e) return debug.traceback(tostring(e)) end, ...)
|
||||||
|
if not ok then error(err, 0) end
|
||||||
|
|
||||||
|
-- vi: set ts=2:
|
@ -0,0 +1,176 @@
|
|||||||
|
--provide a tty on each available screen, running the given program
|
||||||
|
local progArgs = table.pack(...)
|
||||||
|
checkArg(1, progArgs[1], 'string') --program path
|
||||||
|
|
||||||
|
|
||||||
|
--identify the highest-tier gpu available
|
||||||
|
local gpu, vmem --use virtual memory as a heuristic for tier
|
||||||
|
for addr in component.list('gpu', true) do
|
||||||
|
local vmemNew = component.invoke(addr, 'totalMemory')
|
||||||
|
if vmemNew > (vmem or -1) then
|
||||||
|
gpu, vmem = addr, vmemNew
|
||||||
|
end
|
||||||
|
end
|
||||||
|
gpu = assert(gpu, 'please install a GPU or APU to continue')
|
||||||
|
|
||||||
|
--track most recently assigned values to skip redundant bind/setBackground/setForeground calls
|
||||||
|
local curScreen, curBg, curFg
|
||||||
|
local function bind(screen)
|
||||||
|
if screen ~= curScreen then
|
||||||
|
component.invoke(gpu, 'bind', screen)
|
||||||
|
curScreen = screen
|
||||||
|
end
|
||||||
|
end
|
||||||
|
local function setBackground(color)
|
||||||
|
if color ~= curBg then
|
||||||
|
component.invoke(gpu, 'setBackground', color)
|
||||||
|
curBg = color
|
||||||
|
end
|
||||||
|
end
|
||||||
|
local function setForeground(color)
|
||||||
|
if color ~= curFg then
|
||||||
|
component.invoke(gpu, 'setForeground', color)
|
||||||
|
curFg = color
|
||||||
|
end
|
||||||
|
end
|
||||||
|
local function draw(cx, text)
|
||||||
|
bind(cx.screen)
|
||||||
|
setBackground(cx.bg)
|
||||||
|
setForeground(cx.fg)
|
||||||
|
return component.invoke(gpu, 'set', cx.x, cx.y, text)
|
||||||
|
end
|
||||||
|
|
||||||
|
local iomt = {}
|
||||||
|
iomt.__index = iomt
|
||||||
|
|
||||||
|
local function isAttachedKeyboard(cx, addr)
|
||||||
|
for i, kb in ipairs(cx.keyboards) do
|
||||||
|
if kb == addr then
|
||||||
|
return i
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
--tail-recursively pullSignal to construct the line of input read from a terminal
|
||||||
|
local function readSignal(cx, input, sig, ...)
|
||||||
|
if sig == 'key_down' then
|
||||||
|
local kb, ch, code = ...
|
||||||
|
if isAttachedKeyboard(cx, kb) then
|
||||||
|
if code == 28 then --enter
|
||||||
|
cx.x, cx.y = 1, cx.y + 1
|
||||||
|
return input .. '\n'
|
||||||
|
elseif code == 14 then --backspace
|
||||||
|
if input ~= '' then
|
||||||
|
cx.x = cx.x - 1
|
||||||
|
draw(cx, ' ')
|
||||||
|
input = input:sub(1, -2)
|
||||||
|
end
|
||||||
|
elseif ch ~= 0 then
|
||||||
|
local char = string.char(ch)
|
||||||
|
draw(cx, char)
|
||||||
|
cx.x = cx.x + 1
|
||||||
|
input = input .. char
|
||||||
|
end
|
||||||
|
end
|
||||||
|
elseif sig == 'component_added' then
|
||||||
|
local addr, ty = ...
|
||||||
|
if ty == 'keyboard' then
|
||||||
|
cx.keyboards = component.invoke(screen, 'getKeyboards')
|
||||||
|
end
|
||||||
|
elseif sig == 'component_removed' then
|
||||||
|
local addr, ty = ...
|
||||||
|
if addr == cx.screen then
|
||||||
|
return nil, 'disconnected'
|
||||||
|
end
|
||||||
|
local idx = ty == 'keyboard' and isAttachedKeyboard(cx, addr)
|
||||||
|
if idx then
|
||||||
|
table.remove(cx.keyboards, idx)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
return readSignal(cx, input, computer.pullSignal())
|
||||||
|
end
|
||||||
|
|
||||||
|
-- file handle methods
|
||||||
|
function iomt:read(n)
|
||||||
|
--technically this should return at maximum `n` bytes, but that would involve a redundant second buffer
|
||||||
|
--we happen to know the implementation works fine returning more than `n`
|
||||||
|
|
||||||
|
return readSignal(self, '', computer.pullSignal())
|
||||||
|
--[[
|
||||||
|
local sig = table.pack(computer.pullSignal())
|
||||||
|
for i = 1, sig.n do
|
||||||
|
sig[i] = string.format(type(sig[i]) == 'string' and '%q' or '%s', sig[i])
|
||||||
|
end
|
||||||
|
return table.concat(sig, ', ', 1, sig.n) .. '\n'
|
||||||
|
]]
|
||||||
|
end
|
||||||
|
|
||||||
|
function iomt:write(data)
|
||||||
|
bind(self.screen) setBackground(self.bg) setForeground(self.fg)
|
||||||
|
local idx = 1
|
||||||
|
::line::
|
||||||
|
local lf = data:find('\n', idx, true)
|
||||||
|
component.invoke(gpu, 'set', self.x, self.y, data:sub(idx, lf and lf - 1))
|
||||||
|
|
||||||
|
if lf then
|
||||||
|
self.x, self.y = 1, self.y + 1
|
||||||
|
idx = lf + 1
|
||||||
|
goto line
|
||||||
|
end
|
||||||
|
|
||||||
|
self.x = self.x + #data - idx + 1
|
||||||
|
return true
|
||||||
|
end
|
||||||
|
|
||||||
|
--no seek, noop close
|
||||||
|
function iomt:close() return true end
|
||||||
|
|
||||||
|
-- tty methods
|
||||||
|
function iomt:mark()
|
||||||
|
return { bg = self.bg, fg = self.fg }
|
||||||
|
end
|
||||||
|
function iomt:reset(mark)
|
||||||
|
self.bg, self.fg = mark.bg, mark.fg
|
||||||
|
end
|
||||||
|
function iomt:setBg(color) self.bg = color end
|
||||||
|
function iomt:setFg(color) self.fg = color end
|
||||||
|
|
||||||
|
|
||||||
|
local function createTerm(screen, prog, ...)
|
||||||
|
checkArg(1, screen, 'string')
|
||||||
|
checkArg(2, prog, 'string')
|
||||||
|
local fh = setmetatable({ screen = screen, keyboards = component.invoke(screen, 'getKeyboards'), x = 1, y = 1, fg = 0xffffff, bg = 0x000000 }, iomt)
|
||||||
|
local stdio = assert(kitn.wrapFile('r+', fh))
|
||||||
|
stdio.tty = fh
|
||||||
|
|
||||||
|
bind(screen)
|
||||||
|
setForeground(0xffffff)
|
||||||
|
setBackground(0x000000)
|
||||||
|
local w, h = component.invoke(gpu, 'maxResolution')
|
||||||
|
component.invoke(gpu, 'setResolution', w, h)
|
||||||
|
component.invoke(gpu, 'fill', 1, 1, w, h, ' ')
|
||||||
|
|
||||||
|
local process = assert(kitn.createProcess(prog, stdio, ...)) --TODO set io via createProcess args
|
||||||
|
return process, stdio
|
||||||
|
end
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
--for each existing screen, spawn prog
|
||||||
|
for screen in component.list('screen') do
|
||||||
|
createTerm(screen, table.unpack(progArgs, 1, progArgs.n))
|
||||||
|
end
|
||||||
|
|
||||||
|
--when new screens are added, spawn prog on those too
|
||||||
|
local function tick(sig, addr, ty)
|
||||||
|
if sig == 'component_added' and ty == 'screen' then
|
||||||
|
createTerm(addr, table.unpack(progArgs, 1, progArgs.n))
|
||||||
|
end
|
||||||
|
|
||||||
|
return tick(computer.pullSignal())
|
||||||
|
end
|
||||||
|
|
||||||
|
return tick(computer.pullSignal())
|
||||||
|
|
||||||
|
-- vi: set ts=2:
|
@ -0,0 +1,89 @@
|
|||||||
|
local mt = {}
|
||||||
|
mt.__index = mt
|
||||||
|
|
||||||
|
--wrap the current process's stdio streams into a tty that supports styled output.
|
||||||
|
local function wrap(io_)
|
||||||
|
checkArg(1, io_, 'table', 'nil')
|
||||||
|
io_ = io_ or io
|
||||||
|
|
||||||
|
--cache the streams here so that `local tty = wrap(io); io.output('/some/file'); tty.prompt()` works as expected
|
||||||
|
local stdin, stdout, stderr = io_.stdin, io_.stdout, io_.stderr
|
||||||
|
if stdout.tty then stdout:setvbuf('no') end
|
||||||
|
if stderr.tty then stderr:setvbuf('no') end
|
||||||
|
|
||||||
|
return setmetatable({ stdin = stdin, stdout = stdout, stderr = stderr }, mt)
|
||||||
|
end
|
||||||
|
|
||||||
|
local function writeStyled(tty, stream, arg)
|
||||||
|
if type(arg) == 'table' then
|
||||||
|
local token = tty:mark()
|
||||||
|
if arg.bg then tty:setBg(arg.bg) end
|
||||||
|
if arg.fg then tty:setFg(arg.fg) end
|
||||||
|
for i = 1, arg.n or #arg do writeStyled(tty, stream, arg[i]) end
|
||||||
|
stream:flush() --ensure
|
||||||
|
tty:reset(token)
|
||||||
|
else
|
||||||
|
stream:write(arg)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
local function writeUnstyled(stream, arg)
|
||||||
|
if type(arg) == 'table' then
|
||||||
|
for i = 1, arg.n or #arg do
|
||||||
|
writeUnstyled(stream, arg[i])
|
||||||
|
end
|
||||||
|
else
|
||||||
|
stream:write(arg)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
--[[write strings to stdout with optional styling.
|
||||||
|
|
||||||
|
tables are assumed to be styled groups, ie sequences of strings/styled groups with optional extra properties:
|
||||||
|
.fg = 0xrrggbb sets foreground color for all contained text, unless overridden by a styled group inside
|
||||||
|
.bg = 0xrrggbb sets background color for all contained text, unless overridden by a styled group inside
|
||||||
|
these properties are applied while rendering the styled group, then reverted to the prior values before rendering
|
||||||
|
any following values in this or future write() calls.
|
||||||
|
|
||||||
|
if stdout is not a tty, all styling is ignored.
|
||||||
|
]]
|
||||||
|
function mt:write(...)
|
||||||
|
local args = table.pack(...)
|
||||||
|
local caps = self.stdout.tty
|
||||||
|
if caps then
|
||||||
|
writeStyled(caps, self.stdout, args)
|
||||||
|
else
|
||||||
|
writeUnstyled(self.stdout, args)
|
||||||
|
end
|
||||||
|
return self
|
||||||
|
end
|
||||||
|
|
||||||
|
--write strings to stderr with optional styling.
|
||||||
|
--see `mt:write` for how styling is interpreted.
|
||||||
|
function mt:error(...)
|
||||||
|
--[[
|
||||||
|
local args = table.pack(...)
|
||||||
|
local caps = self.stderr.tty
|
||||||
|
if caps then
|
||||||
|
writeStyled(caps, self.stderr, args)
|
||||||
|
else
|
||||||
|
writeUnstyled(self.stderr, args)
|
||||||
|
end
|
||||||
|
return self
|
||||||
|
]]
|
||||||
|
return self:write({ fg = 0xff0000, ... })
|
||||||
|
end
|
||||||
|
|
||||||
|
--prompt the user for a line of input.
|
||||||
|
--if stdout isn't a tty, equivalent to `io.stdin:read('l')`.
|
||||||
|
function mt:prompt(...)
|
||||||
|
if self.stdin.tty and self.stdout.tty then
|
||||||
|
self:write(...)
|
||||||
|
self.stdout:flush()
|
||||||
|
end
|
||||||
|
return self.stdin:read('l')
|
||||||
|
end
|
||||||
|
|
||||||
|
return { wrap = wrap }
|
||||||
|
|
||||||
|
-- vi: set ts=2:
|
@ -1,28 +1,6 @@
|
|||||||
--/etc/init.lua for the kitn livedisk and installer
|
--/etc/init.lua for the kitn livedisk and installer
|
||||||
|
|
||||||
--find highest-tier gpu available
|
--spawn a lua repl on each available screen
|
||||||
local bestGpu, bestMem
|
return dofile('/bin/multitty.lua', '/bin/lua.lua')
|
||||||
for addr in component.list('gpu') do
|
|
||||||
local mem = component.invoke(addr, 'totalMemory')
|
|
||||||
if mem > (bestMem or 0) then
|
|
||||||
bestGpu, bestMem = addr, mem
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
assert(bestGpu, 'please install a gpu or apu to continue')
|
-- vi: set ts=2:
|
||||||
|
|
||||||
for screen in component.list('screen') do
|
|
||||||
local proc, err = kitn.createProcess('/bin/tty.lua', bestGpu, screen)
|
|
||||||
if not proc then error('spawn for ' .. screen:sub(1, 4) .. ' failed: ' .. err) end
|
|
||||||
end
|
|
||||||
|
|
||||||
local function tick(signal, addr, ty)
|
|
||||||
if signal == 'component_added' and ty == 'screen' then
|
|
||||||
local proc, err = kitn.createProcess('/bin/tty.lua', bestGpu, addr)
|
|
||||||
if not proc then error('spawn for ' .. screen:sub(1, 4) .. ' failed: ' .. err) end
|
|
||||||
end
|
|
||||||
|
|
||||||
return tick(computer.pullSignal())
|
|
||||||
end
|
|
||||||
|
|
||||||
return tick(computer.pullSignal())
|
|
||||||
|
Loading…
Reference in New Issue