Bugfixes to Everest, finalize the terminal API

This commit is contained in:
20kdc 2020-04-01 23:21:36 +01:00
parent 5ac8f9ff11
commit 7c70a1128c
9 changed files with 374 additions and 176 deletions

View File

@ -55,7 +55,7 @@ return {
}, },
["neo-everest"] = { ["neo-everest"] = {
desc = "KittenOS NEO / Everest (windowing)", desc = "KittenOS NEO / Everest (windowing)",
v = 5, v = 9,
deps = { deps = {
"neo" "neo"
}, },
@ -68,7 +68,7 @@ return {
}, },
["neo-icecap"] = { ["neo-icecap"] = {
desc = "KittenOS NEO / Icecap", desc = "KittenOS NEO / Icecap",
v = 8, v = 9,
deps = { deps = {
"neo" "neo"
}, },
@ -85,7 +85,7 @@ return {
}, },
["neo-secpolicy"] = { ["neo-secpolicy"] = {
desc = "KittenOS NEO / Secpolicy", desc = "KittenOS NEO / Secpolicy",
v = 8, v = 9,
deps = { deps = {
}, },
dirs = { dirs = {

View File

@ -3,12 +3,16 @@
local _, _, termId = ... local _, _, termId = ...
local ok = pcall(function () local ok = pcall(function ()
assert(string.sub(termId, 1, 8) == "x.svc.t/") assert(string.sub(termId, 1, 12) == "x.neo.pub.t/")
end) end)
local termClose
if not ok then if not ok then
termId = nil termId = nil
neo.executeAsync("svc-t", function (res) neo.executeAsync("svc-t", function (res)
termId = res.access termId = res.access
termClose = res.close
neo.scheduleTimer(0) neo.scheduleTimer(0)
end, "luashell") end, "luashell")
while not termId do while not termId do
@ -17,7 +21,18 @@ if not ok then
end end
TERM = neo.requireAccess(termId, "terminal") TERM = neo.requireAccess(termId, "terminal")
TERM.line("KittenOS NEO Lua Shell") -- Using event makes it easier for stuff
-- within the shell to not spectacularly explode.
event = require("event")(neo)
local alive = true
event.listen("k.procdie", function (_, _, pid)
if pid == TERM.pid then
alive = false
end
end)
TERM.write("KittenOS NEO Lua Shell\r\n")
print = function (...) print = function (...)
local n = {} local n = {}
@ -29,11 +44,9 @@ print = function (...)
end end
table.insert(n, tostring(v)) table.insert(n, tostring(v))
end end
TERM.line(table.concat(n, " ")) TERM.write(table.concat(n, " ") .. "\r\n")
end end
local alive = true
run = function (x, ...) run = function (x, ...)
local subPid = neo.executeAsync(x, ...) local subPid = neo.executeAsync(x, ...)
if not subPid then if not subPid then
@ -49,40 +62,63 @@ run = function (x, ...)
error("cannot find " .. x) error("cannot find " .. x)
end end
while true do while true do
local e = {coroutine.yield()} local e = {event.pull()}
if e[1] == "k.procdie" then if e[1] == "k.procdie" then
if e[3] == TERM.pid then if e[3] == subPid then
alive = false
return
elseif e[3] == subPid then
return return
end end
end end
end end
end end
exit = function () local ioBuffer = ""
io = {
read = function ()
while alive do
local pos = ioBuffer:find("\n")
if pos then
local line = ioBuffer:sub(1, pos):gsub("\r", "")
ioBuffer = ioBuffer:sub(pos + 1)
return line
end
local e = {event.pull()}
if e[1] == TERM.id then
if e[2] == "data" then
ioBuffer = ioBuffer .. e[3]
end
end
end
end,
write = function (s) TERM.write(s) end
}
local originalOS = os
os = setmetatable({}, {
__index = originalOS
})
os.exit = function ()
alive = false alive = false
end end
while alive do while alive do
local e = {coroutine.yield()} TERM.write("> ")
if e[1] == "k.procdie" then local code = io.read()
if e[3] == TERM.pid then if code then
alive = false local ok, err = pcall(function ()
end if code:sub(1, 1) == "=" then
elseif e[1] == TERM.id then code = "return " .. code:sub(2)
if e[2] == "line" then
TERM.line("> " .. e[3])
local ok, err = pcall(function ()
if e[3]:sub(1, 1) == "=" then
e[3] = "return " .. e[3]:sub(2)
end
print(assert(load(e[3]))())
end)
if not ok then
TERM.line(tostring(err))
end end
print(assert(load(code))())
end)
if not ok then
TERM.write(tostring(err) .. "\r\n")
end end
end end
end end
if termClose then
termClose()
end

View File

@ -15,66 +15,203 @@ local function rW()
return string.format("%04x", math.random(0, 65535)) return string.format("%04x", math.random(0, 65535))
end end
local id = "svc.t/" .. rW() .. rW() .. rW() .. rW() local id = "neo.pub.t/" .. rW() .. rW() .. rW() .. rW()
local closeNow = false local closeNow = false
-- Terminus Registration State --
local tReg = neo.requireAccess("r." .. id, "registration") local tReg = neo.requireAccess("r." .. id, "registration")
local sendSigs = {}
-- unicode.safeTextFormat'd lines -- Display State --
-- unicode.safeTextFormat'd lines.
-- The size of this must not go below 1.
local console = {} local console = {}
-- This must not go below 3.
local conW = 40
local conCX, conCY = 1, 1
for i = 1, 14 do for i = 1, 14 do
console[i] = (" "):rep(40) console[i] = (" "):rep(conW)
end end
local l15 = "" -- Line Editing State --
--++++++++++++++++++++++++++++++++++++++++ -- Nil if line editing is off.
-- In this case, the console height
-- sW must not go below 3. -- must be adjusted accordingly.
-- sH must not go below 2. local leText = ""
local sW, sH = 40, 15 -- These are NOT nil'd out,
local cX = 1 -- particularly not the history buffer.
local windows = neo.requireAccess("x.neo.pub.window", "windows") local leCX = 1
local window = windows(sW, sH, title) local leHistory = {
-- Size = history buffer size
local function fmtLine(s) "", "", "", ""
s = unicode.safeTextFormat(s) }
local l = unicode.len(s) local function cycleHistoryUp()
return unicode.sub(s .. (" "):rep(sW - l), -sW) local backupFirst = leHistory[1]
for i = 1, #leHistory - 1 do
leHistory[i] = leHistory[i + 1]
end
leHistory[#leHistory] = backupFirst
end
local function cycleHistoryDown()
local backup = leHistory[1]
for i = 2, #leHistory do
backup, leHistory[i] = leHistory[i], backup
end
leHistory[1] = backup
end end
local function line(i) -- Window --
local l, c = console[i] or l15
l, c = unicode.safeTextFormat(l, cX) local window = neo.requireAccess("x.neo.pub.window", "window")(conW, #console + 1, title)
l = require("lineedit").draw(sW, l, i == sH and c)
if i ~= sH then -- Core Terminal Functions --
window.span(1, i, l, 0xFFFFFF, 0)
local function setSize(w, h)
conW = w
while #console < h do
table.insert(console, "")
end
while #console > h do
table.remove(console, 1)
end
for i = 1, #console do
console[i] = unicode.sub(console[i], 1, w) .. (" "):rep(w - unicode.len(console[i]))
end
if leText then
window.setSize(w, h + 1)
else else
window.span(1, i, l, 0, 0xFFFFFF) window.setSize(w, h)
end
conCX, conCY = 1, h
end
local function setLineEditing(state)
if state and not leText then
leText = ""
leCX = 1
setSize(conW, #console)
elseif leText and not state then
leText = nil
setSize(conW, #console)
end end
end end
local function incoming(s) local function draw(i)
local function shift(f) if console[i] then
window.span(1, i, console[i], 0, 0xFFFFFF)
elseif leText then
window.span(1, i, require("lineedit").draw(conW, unicode.safeTextFormat(leText, leCX)), 0xFFFFFF, 0)
end
end
local function drawDisplay()
for i = 1, #console do draw(i) end
end
-- Terminal Visual --
local function writeFF()
if conCY ~= #console then
conCY = conCY + 1
else
for i = 1, #console - 1 do for i = 1, #console - 1 do
console[i] = console[i + 1] console[i] = console[i + 1]
end end
console[#console] = f console[#console] = (" "):rep(conW)
end
-- Need to break this safely.
shift("")
for i = 1, unicode.len(s) do
local ch = unicode.sub(s, i, i)
if unicode.wlen(console[#console] .. ch) > sW then
shift(" ")
end
console[#console] = console[#console] .. ch
end
for i = 1, #console do
line(i)
end end
end end
local sendSigs = {} local function writeData(data)
-- handle data until completion
while #data > 0 do
local char = unicode.sub(data, 1, 1)
data = unicode.sub(data, 2)
-- handle character
if char == "\r" then
conCX = 1
elseif char == "\n" then
conCX = 1
writeFF()
elseif char == "\a" then
-- Bell (er...)
elseif char == "\b" then
conCX = math.max(1, conCX - 1)
elseif char == "\v" or char == "\f" then
writeFF()
else
local charL = unicode.wlen(char)
if (conCX + charL - 1) > conW then
conCX = 1
writeFF()
end
local spaces = (" "):rep(charL - 1)
console[conCY] = unicode.sub(console[conCY], 1, conCX - 1) .. char .. spaces .. unicode.sub(console[conCY], conCX + charL)
conCX = conCX + charL
end
end
end
-- The Terminus --
local tvBuildingCmd = ""
local tvBuildingUTF = ""
local function incoming(s)
tvBuildingCmd = tvBuildingCmd .. s
-- Flush Cmd
while #tvBuildingCmd > 0 do
if tvBuildingCmd:byte() == 255 then
-- It's a command. Uhoh.
if #tvBuildingCmd < 2 then break end
local cmd = tvBuildingCmd:byte(2)
local param = tvBuildingCmd:byte(3)
local cmdLen = 2
-- Command Lengths
if cmd >= 251 and cmd <= 254 then cmdLen = 3 end
if #tvBuildingCmd < cmdLen then break end
if cmd == 251 and param == 1 then
-- WILL ECHO (respond with DO ECHO, disable line editing)
-- test using io.write("\xFF\xFB\x01")
for _, v in pairs(sendSigs) do
v("telnet", "\xFF\xFD\x01")
end
setLineEditing(false)
elseif cmd == 252 and param == 1 then
-- WON'T ECHO (respond with DON'T ECHO, enable line editing)
for _, v in pairs(sendSigs) do
v("telnet", "\xFF\xFE\x01")
end
setLineEditing(true)
elseif cmd == 253 or cmd == 254 then
-- DO/DON'T (x) (respond with WON'T (X))
local res = "\xFF\xFC" .. string.char(param)
for _, v in pairs(sendSigs) do
v("telnet", res)
end
elseif cmd == 255 then
tvBuildingUTF = tvBuildingUTF .. "\xFF"
end
tvBuildingCmd = tvBuildingCmd:sub(cmdLen + 1)
else
tvBuildingUTF = tvBuildingUTF .. tvBuildingCmd:sub(1, 1)
tvBuildingCmd = tvBuildingCmd:sub(2)
end
end
-- Flush UTF
while #tvBuildingUTF > 0 do
local head = tvBuildingUTF:byte()
local len = 1
if head >= 192 and head < 224 then len = 2 end
if head >= 224 and head < 240 then len = 3 end
if head >= 240 and head < 248 then len = 4 end
if head >= 248 and head < 252 then len = 5 end
if head >= 252 and head < 254 then len = 6 end
if #tvBuildingUTF < len then break end
-- verified one full character...
local char = tvBuildingUTF:sub(1, len)
tvBuildingUTF = tvBuildingUTF:sub(len + 1)
writeData(char)
end
end
do do
tReg(function (_, pid, sendSig) tReg(function (_, pid, sendSig)
@ -82,11 +219,12 @@ do
return { return {
id = "x." .. id, id = "x." .. id,
pid = neo.pid, pid = neo.pid,
line = function (text) write = function (text)
incoming(tostring(text)) incoming(tostring(text))
drawDisplay()
end end
} }
end) end, true)
if retTbl then if retTbl then
coroutine.resume(coroutine.create(retTbl), { coroutine.resume(coroutine.create(retTbl), {
@ -99,51 +237,45 @@ do
end end
end end
-- This decides the history buffer size.
local history = {
"", "", "", ""
}
local function cycleHistoryUp()
local backupFirst = history[1]
for i = 1, #history - 1 do
history[i] = history[i + 1]
end
history[#history] = backupFirst
end
local function cycleHistoryDown()
local backup = history[1]
for i = 2, #history do
backup, history[i] = history[i], backup
end
history[1] = backup
end
local function key(a, c) local function key(a, c)
if c == 200 then if not leText then
-- History cursor up (history down)
l15 = history[#history]
cX = 1
cycleHistoryDown()
return
elseif c == 208 then
-- History cursor down (history up)
l15 = history[#history]
cX = 1
cycleHistoryUp()
return
end
local lT, lC, lX = require("lineedit").key(a, c, l15, cX)
l15 = lT or l15
cX = lC or cX
if lX == "nl" then
cycleHistoryUp()
history[#history] = l15
for _, v in pairs(sendSigs) do for _, v in pairs(sendSigs) do
v("line", l15) if a == "\r" then
v("data", "\r\n")
elseif a then
v("data", a)
end
end
else
-- Line Editing active
if c == 200 or c == 208 then
-- History cursor up (history down)
leText = leHistory[#leHistory]
leCX = unicode.len(leText)
if c == 208 then
cycleHistoryUp()
else
cycleHistoryDown()
end
return
end
local lT, lC, lX = require("lineedit").key(a, c, leText, leCX)
leText = lT or leText
leCX = lC or leCX
if lX == "nl" then
cycleHistoryUp()
leHistory[#leHistory] = leText
-- the whole thing {
local fullText = leText .. "\r\n"
writeData(fullText)
drawDisplay()
for _, v in pairs(sendSigs) do
v("data", fullText)
end
-- }
leText = ""
leCX = 1
end end
l15 = ""
cX = 1
end end
end end
@ -165,32 +297,26 @@ while not closeNow do
key(c, 0) key(c, 0)
end end
end end
line(sH) draw(#console + 1)
elseif e[3] == "key" then elseif e[3] == "key" then
if e[5] == 29 or e[5] == 157 then if e[5] == 29 or e[5] == 157 then
control = e[6] control = e[6]
elseif e[6] then elseif e[6] then
if not control then if not control then
key(e[4] ~= 0 and unicode.char(e[4]), e[5]) key(e[4] ~= 0 and unicode.char(e[4]), e[5])
line(sH) draw(#console + 1)
elseif e[5] == 203 and sW > 8 then elseif e[5] == 203 and sW > 8 then
sW = sW - 1 setSize(conW - 1, #console)
window.setSize(sW, sH) elseif e[5] == 200 and #console > 1 then
elseif e[5] == 200 and sH > 2 then setSize(conW, #console - 1)
sH = sH - 1
table.remove(console, 1)
window.setSize(sW, sH)
elseif e[5] == 205 then elseif e[5] == 205 then
sW = sW + 1 setSize(conW + 1, #console)
window.setSize(sW, sH)
elseif e[5] == 208 then elseif e[5] == 208 then
sH = sH + 1 setSize(conW, #console + 1)
table.insert(console, 1, "")
window.setSize(sW, sH)
end end
end end
elseif e[3] == "line" then elseif e[3] == "line" then
line(e[4]) draw(e[4])
end end
end end
end end

View File

@ -616,7 +616,7 @@ local function key(ku, ka, kc, down)
elseif kc == 56 then elseif kc == 56 then
isAltDown = down isAltDown = down
end end
if isAltDown and kc == 122 then if isAltDown and ka == 122 then
if focus and down then if focus and down then
local n = table.remove(surfaces, 1) local n = table.remove(surfaces, 1)
table.insert(surfaces, n) table.insert(surfaces, n)

View File

@ -81,20 +81,12 @@ local function getPfx(xd, pkg)
end end
end end
local endAcPattern = "/[a-z0-9/%.]*$" local function splitAC(ac)
local sb = ac:match("/[a-z0-9/%.]*$")
local function matchesSvc(xd, pkg, perm) if sb then
local pfx = getPfx(xd, pkg) return ac:sub(1, #ac - #sb), sb
if pfx then
local permAct = perm
local paP = permAct:match(endAcPattern)
if paP then
permAct = permAct:sub(1, #permAct - #paP)
end
if permAct == pfx then
return "allow"
end
end end
return ac
end end
donkonitDFProvider(function (pkg, pid, sendSig) donkonitDFProvider(function (pkg, pid, sendSig)
@ -130,7 +122,8 @@ donkonitDFProvider(function (pkg, pid, sendSig)
myApi = getPfx("", pkg), myApi = getPfx("", pkg),
lockPerm = function (perm) lockPerm = function (perm)
-- Are we allowed to? -- Are we allowed to?
if not matchesSvc("x.", pkg, perm) then local permPfx, detail = splitAC(perm)
if getPfx("x.", pkg) ~= permPfx then
return false, "You don't own this permission." return false, "You don't own this permission."
end end
local set = "perm|*|" .. perm local set = "perm|*|" .. perm
@ -223,7 +216,11 @@ local function secPolicyStage2(pid, proc, perm, req)
-- Push to ICECAP thread to avoid deadlock b/c wrong event-pull context -- Push to ICECAP thread to avoid deadlock b/c wrong event-pull context
neo.scheduleTimer(0) neo.scheduleTimer(0)
table.insert(todo, function () table.insert(todo, function ()
local ok, err = pcall(secpol, nexus, settings, proc.pkg, pid, perm, req, matchesSvc) local fPerm = perm
if fPerm:sub(1, 2) == "r." then
fPerm = splitAC(fPerm)
end
local ok, err = pcall(secpol, nexus, settings, proc.pkg, pid, fPerm, req, getPfx("", proc.pkg))
if not ok then if not ok then
neo.emergency("Used fallback policy because of run-err: " .. err) neo.emergency("Used fallback policy because of run-err: " .. err)
req(def) req(def)
@ -243,11 +240,7 @@ rootAccess.securityPolicy = function (pid, proc, perm, req)
end end
-- Do we need to start it? -- Do we need to start it?
if perm:sub(1, 6) == "x.svc." and not neo.usAccessExists(perm) then if perm:sub(1, 6) == "x.svc." and not neo.usAccessExists(perm) then
local appAct = perm:sub(7) local appAct = splitAC(perm:sub(3))
local paP = appAct:match(endAcPattern)
if paP then
appAct = appAct:sub(1, #appAct - #paP)
end
-- Prepare for success -- Prepare for success
onReg[perm] = onReg[perm] or {} onReg[perm] = onReg[perm] or {}
local orp = onReg[perm] local orp = onReg[perm]

View File

@ -459,7 +459,7 @@ function retrieveAccess(perm, pkg, pid)
accesses[uid] = function (pkg, pid) accesses[uid] = function (pkg, pid)
return nil return nil
end end
return function (f) return function (f, secret)
-- Registration function -- Registration function
ensureType(f, "function") ensureType(f, "function")
local accessObjectCache = {} local accessObjectCache = {}
@ -481,8 +481,10 @@ function retrieveAccess(perm, pkg, pid)
end end
-- returns nil and fails -- returns nil and fails
end end
-- Announce registration if not secret then
distEvent(nil, "k.registration", uid) -- Announce registration
distEvent(nil, "k.registration", uid)
end
end, function () end, function ()
-- Registration becomes null (access is held but other processes cannot retrieve object) -- Registration becomes null (access is held but other processes cannot retrieve object)
if accesses[uid] then if accesses[uid] then

View File

@ -11,13 +11,19 @@
-- IRC is usually pretty safe, but no guarantees. -- IRC is usually pretty safe, but no guarantees.
-- Returns "allow", "deny", or "ask". -- Returns "allow", "deny", or "ask".
local function actualPolicy(pkg, pid, perm, matchesSvc) local function actualPolicy(pkg, pid, perm, pkgSvcPfx)
-- System stuff is allowed. -- System stuff is allowed.
if pkg:sub(1, 4) == "sys-" then if pkg:sub(1, 4) == "sys-" then
return "allow" return "allow"
end end
-- svc-t's job is solely to emulate terminals
-- TO INSTALL YOUR OWN TERMINAL EMULATOR:
-- perm|app-yourterm|r.neo.t
if pkg == "svc-t" and perm == "r.neo.pub.t" then
return "allow"
end
-- <The following is for apps & services> -- <The following is for apps & services>
-- x.neo.pub (aka Icecap) is open to all -- x.neo.pub.* is open to all
if perm:sub(1, 10) == "x.neo.pub." then if perm:sub(1, 10) == "x.neo.pub." then
return "allow" return "allow"
end end
@ -25,7 +31,8 @@ local function actualPolicy(pkg, pid, perm, matchesSvc)
if perm == "s.h.component_added" or perm == "s.h.component_removed" or perm == "s.h.tablet_use" or perm == "c.tablet" then if perm == "s.h.component_added" or perm == "s.h.component_removed" or perm == "s.h.tablet_use" or perm == "c.tablet" then
return "allow" return "allow"
end end
if matchesSvc("r.", pkg, perm) then -- Userlevel can register for itself
if perm == "r." .. pkgSvcPfx then
return "allow" return "allow"
end end
-- Userlevel has no other registration rights -- Userlevel has no other registration rights
@ -44,8 +51,8 @@ local function actualPolicy(pkg, pid, perm, matchesSvc)
return "ask" return "ask"
end end
return function (nexus, settings, pkg, pid, perm, rsp, matchesSvc) return function (nexus, settings, pkg, pid, perm, rsp, pkgSvcPfx)
local res = actualPolicy(pkg, pid, perm, matchesSvc) local res = actualPolicy(pkg, pid, perm, pkgSvcPfx)
if settings then if settings then
res = settings.getSetting("perm|" .. pkg .. "|" .. perm) or res = settings.getSetting("perm|" .. pkg .. "|" .. perm) or
settings.getSetting("perm|*|" .. perm) or res settings.getSetting("perm|*|" .. perm) or res

View File

@ -63,7 +63,8 @@ The security check may be aliased to
"r.*": Registers a service's API for "r.*": Registers a service's API for
retrieval via the "x." mechanism. retrieval via the "x." mechanism.
Returns a: Returns a:
function (function (pkg, pid, send)) function (function (pkg, pid, send),
secret)
While the registration is locked on While the registration is locked on
success, attempting to use it will success, attempting to use it will
fail, as no handler has been given. fail, as no handler has been given.
@ -71,6 +72,11 @@ The security check may be aliased to
registration with a callback used registration with a callback used
for when a process tries to use the for when a process tries to use the
registered API. registered API.
Unless 'secret' is truthy, a
k.registration event is sent to all
processes; using the secret flag is
useful for a more ad-hoc security
approach.
What that API returns goes to the What that API returns goes to the
target process. target process.
The given "sendSig" function can be The given "sendSig" function can be

View File

@ -5,20 +5,34 @@ The "svc-t" program / "x.svc.t"
--- THEORETICAL TERMINALS MODEL --- --- THEORETICAL TERMINALS MODEL ---
The theoretical model for terminals The theoretical model for terminals
in KittenOS NEO is that of a stack in KittenOS NEO is a TELNET client
of processes controlling a player's that only supports the ECHO option,
connection to a MUD, where text is and uses the non-standard behavior
provided to the server and to the of treating ECHO ON as 'enable local
player in a line-by-line format, line editing'.
with no "flow control"/ttyattrs.
To prevent code size going too far,
the client is extremely restricted
in capabilities.
If you really want full support,
write a better terminal application.
Features that get added will be added
in accordance with ANSI/TELNET where
reasonable or in a compatible-ish
fashion where unreasonable.
The defaults will be set based on
whatever app-luashell requires, as
this is what is expected of modern
terminal systems regardless of what
the standards may have to say.
A process starting another process A process starting another process
connected to the same terminal is connected to the same terminal is
advised to wait for that process to advised to wait for that process to
die before continuing in terminal die before continuing reading input.
activities, unless some sort of
'in-band notification' functionality
is intended.
The controlling process is whichever The controlling process is whichever
process is supposed to be accepting process is supposed to be accepting
@ -32,8 +46,8 @@ The controlling process should show
acknowledgement that user input has acknowledgement that user input has
been received. been received.
User input IS NOT automatically For convenience, terminal echo is on
echoed by the terminal. by default; this is easily remedied.
--- ACTUAL USAGE OF TERMINALS --- --- ACTUAL USAGE OF TERMINALS ---
@ -42,12 +56,15 @@ Access control on terminals is looser
to be able to be 'sublet' in some to be able to be 'sublet' in some
cases, including events. cases, including events.
As such, the secret flag is set for
terminal registration.
A terminal program is given a string A terminal program is given a string
argument for the ID of the terminal argument for the ID of the terminal
to connect to. to connect to.
A terminal always has an ID beginning A terminal always has an ID beginning
with "x.svc.t/". ALWAYS CHECK THIS. with "x.neo.pub.t/". ALWAYS CHECK.
Requiring the responsible access Requiring the responsible access
connects to the terminal. All connects to the terminal. All
@ -80,11 +97,22 @@ In either case, when the access has
id = "x.svc.t/<...>" id = "x.svc.t/<...>"
pid = <The terminal's PID.> pid = <The terminal's PID.>
line = function (text): Shows a line. write = function (text): Writes the
TELNET data to the terminal.
When the user sends a line, an event User input is provided in events:
of: <id>, "line", <text> <id>, "data", <data>
is provided.
TELNET commands are provided in:
<id>, "telnet", <data>
There is a total of one TELNET
command per event, unpadded.
Notably, intermixing the data part
of the data/telnet events in order
produces the full terminal-to-server
TELNET stream.
-- This is released into -- This is released into
the public domain. the public domain.