Compare commits

...

2 Commits

Author SHA1 Message Date
20kdc fe17b0fb93 Finally settle on an exact level of support 2020-04-02 21:39:30 +01:00
20kdc c9157a1b7c More terminal improvements. Yes. Again. 2020-04-02 14:33:35 +01:00
6 changed files with 157 additions and 205 deletions

View File

@ -93,7 +93,7 @@ return {
}, },
["app-kmt"] = { ["app-kmt"] = {
desc = "Line-terminal for MUDs & such", desc = "Line-terminal for MUDs & such",
v = 1, v = 2,
deps = { deps = {
"neo", "neo",
"zzz-license-pd" "zzz-license-pd"

View File

@ -10,11 +10,11 @@ local termClose
if not ok then if not ok then
termId = nil termId = nil
neo.executeAsync("svc-t", function (res) assert(neo.executeAsync("svc-t", function (res)
termId = res.access termId = res.access
termClose = res.close termClose = res.close
neo.scheduleTimer(0) neo.scheduleTimer(0)
end, "luashell") end, "luashell"))
while not termId do while not termId do
coroutine.yield() coroutine.yield()
end end
@ -32,7 +32,7 @@ event.listen("k.procdie", function (_, _, pid)
end end
end) end)
TERM.write(([[ TERM.write([[
KittenOS NEO Shell Usage Notes KittenOS NEO Shell Usage Notes
Prefixing = is an alias for 'return '. Prefixing = is an alias for 'return '.
@ -46,7 +46,7 @@ os.exit(): quit the shell
=listCmdApps(): -t- (terminal) apps =listCmdApps(): -t- (terminal) apps
event: useful for setting up listeners event: useful for setting up listeners
without breaking shell functionality without breaking shell functionality
]]):gsub("[\r]*\n", "\r\n")) ]])
function listCmdApps() function listCmdApps()
local apps = {} local apps = {}

View File

@ -30,7 +30,10 @@ local console = {}
-- This must not go below 3. -- This must not go below 3.
local conW = 40 local conW = 40
local conCX, conCY = 1, 1 local conCX, conCY = 1, 1
local conCV = false local conSCX, conSCY = 1, 1
-- Performance
local consoleShown = {}
local conCYShown
for i = 1, 14 do for i = 1, 14 do
console[i] = (" "):rep(conW) console[i] = (" "):rep(conW)
end end
@ -70,6 +73,9 @@ local window = neo.requireAccess("x.neo.pub.window", "window")(conW, #console +
local function setSize(w, h) local function setSize(w, h)
conW = w conW = w
for i = 1, #console do
consoleShown[i] = nil
end
while #console < h do while #console < h do
table.insert(console, "") table.insert(console, "")
end end
@ -101,7 +107,7 @@ end
local function draw(i) local function draw(i)
if console[i] then if console[i] then
window.span(1, i, console[i], 0, 0xFFFFFF) window.span(1, i, console[i], 0, 0xFFFFFF)
if i == conCY and conCV then if i == conCY and not leText then
window.span(conCX, i, unicode.sub(console[i], conCX, conCX), 0xFFFFFF, 0) window.span(conCX, i, unicode.sub(console[i], conCX, conCX), 0xFFFFFF, 0)
end end
elseif leText then elseif leText then
@ -109,7 +115,13 @@ local function draw(i)
end end
end end
local function drawDisplay() local function drawDisplay()
for i = 1, #console do draw(i) end for i = 1, #console do
if consoleShown[i] ~= console[i] or i == conCY or i == conCYShown then
draw(i)
consoleShown[i] = console[i]
end
end
conCYShown = conCY
end end
-- Terminal Visual -- -- Terminal Visual --
@ -127,6 +139,13 @@ local function consoleSU()
end end
end end
local function consoleCLS()
for i = 1, #console do
console[i] = (" "):rep(conW)
end
conCX, conCY = 1, 1
end
local function writeFF() local function writeFF()
if conCY ~= #console then if conCY ~= #console then
conCY = conCY + 1 conCY = conCY + 1
@ -139,6 +158,7 @@ local function writeData(data)
-- handle data until completion -- handle data until completion
while #data > 0 do while #data > 0 do
local char = unicode.sub(data, 1, 1) local char = unicode.sub(data, 1, 1)
--neo.emergency("svc-t.data: " .. char:byte())
data = unicode.sub(data, 2) data = unicode.sub(data, 2)
-- handle character -- handle character
if char == "\t" then if char == "\t" then
@ -147,6 +167,8 @@ local function writeData(data)
end end
if char == "\r" then if char == "\r" then
conCX = 1 conCX = 1
elseif char == "\x00" then
-- caused by TELNET \r rules
elseif char == "\n" then elseif char == "\n" then
conCX = 1 conCX = 1
writeFF() writeFF()
@ -171,29 +193,17 @@ local function writeData(data)
end end
local function writeANSI(s) local function writeANSI(s)
--neo.emergency("svc-t.ansi: " .. s)
-- This supports just about enough to get by. -- This supports just about enough to get by.
if s == "c" then if s == "c" then
for i = 1, #console do consoleCLS()
console[i] = (" "):rep(conW)
end
conCX, conCY = 1, 1
return return
end end
local pfx = s:sub(1, 1) local pfx = s:sub(1, 1)
local cmd = s:sub(#s) local cmd = s:sub(#s)
if pfx == "[" then if pfx == "[" then
local np = tonumber(s:sub(2, -2)) or 1 local np = tonumber(s:sub(2, -2)) or 1
if cmd == "H" or cmd == "f" then if cmd == "A" then
local p = s:find(";")
if not p then
conCX, conCY = 1, 1
else
conCY = tonumber(s:sub(2, p - 1)) or 1
conCX = tonumber(s:sub(p + 1, -2)) or 1
end
elseif cmd == "K" then
console[conCY] = unicode.sub(console[conCY], 1, conCX - 1) .. (" "):rep(1 + conW - conCX)
elseif cmd == "A" then
conCY = conCY - np conCY = conCY - np
elseif cmd == "B" then elseif cmd == "B" then
conCY = conCY + np conCY = conCY + np
@ -201,14 +211,34 @@ local function writeANSI(s)
conCX = conCX + np conCX = conCX + np
elseif cmd == "D" then elseif cmd == "D" then
conCX = conCX - np conCX = conCX - np
elseif cmd == "S" then elseif cmd == "f" or cmd == "H" then
for i = 1, np do local p = s:find(";")
consoleSU() if not p then
conCY = np
conCX = 1
else
conCY = tonumber(s:sub(2, p - 1)) or 1
conCX = tonumber(s:sub(p + 1, -2)) or 1
end end
elseif cmd == "T" then elseif cmd == "J" then
for i = 1, np do consoleCLS()
consoleSD() elseif cmd == "K" then
if s == "[K" or s == "[0K" then
-- bash needs this
console[conCY] = unicode.sub(console[conCY], 1, conCX - 1) .. (" "):rep(1 + conW - conCX)
else
console[conCY] = (" "):rep(conW)
end end
elseif cmd == "n" then
if s == "[6n" then
for _, v in pairs(sendSigs) do
v("data", "\x1b[" .. conY .. ";" .. conX .. "R")
end
end
elseif cmd == "s" then
conSCX, conSCY = conCX, conCY
elseif cmd == "u" then
conCX, conCY = conSCX, conSCY
end end
end end
conCX = math.min(math.max(math.floor(conCX), 1), conW) conCX = math.min(math.max(math.floor(conCX), 1), conW)
@ -372,10 +402,24 @@ local function key(a, c)
-- Line Editing not active. -- Line Editing not active.
-- For now support a bare minimum. -- For now support a bare minimum.
for _, v in pairs(sendSigs) do for _, v in pairs(sendSigs) do
if control then if a == "\x03" then
if a == 99 then v("telnet", "\xFF\xF4")
v("telnet", "\xFF\xF4") elseif c == 199 then
end v("data", "\x1b[H")
elseif c == 201 then
v("data", "\x1b[5~")
elseif c == 207 then
v("data", "\x1b[F")
elseif c == 209 then
v("data", "\x1b[6~")
elseif c == 203 then
v("data", "\x1b[D")
elseif c == 205 then
v("data", "\x1b[C")
elseif c == 200 then
v("data", "\x1b[A")
elseif c == 208 then
v("data", "\x1b[B")
elseif a == "\r" then elseif a == "\r" then
v("data", "\r\n") v("data", "\r\n")
elseif a then elseif a then

View File

@ -3,8 +3,8 @@ ocemu {
components { components {
{"gpu", "c1-gpu-tier3", 0, 160, 50, 3}, {"gpu", "c1-gpu-tier3", 0, 160, 50, 3},
{"gpu", "c1-gpu-tier1", 0, 50, 16, 1}, {"gpu", "c1-gpu-tier1", 0, 50, 16, 1},
-- {"screen_sdl2", "c1-screen-tier3", -1, 160, 50, 3}, {"screen_sdl2", "c1-screen-tier3", -1, 160, 50, 3},
{"screen_sdl2", "c1-screen-tier1", -1, 50, 16, 1}, -- {"screen_sdl2", "c1-screen-tier1", -1, 50, 16, 1},
{"modem", "c1-modem", 1, false}, {"modem", "c1-modem", 1, false},
{"eeprom", "c1-eeprom", 9, "lua/bios.lua"}, {"eeprom", "c1-eeprom", 9, "lua/bios.lua"},
{"filesystem", "c1-tmpfs", -1, "tmpfs", "tmpfs", false, 5}, {"filesystem", "c1-tmpfs", -1, "tmpfs", "tmpfs", false, 5},

View File

@ -1,181 +1,96 @@
-- This is released into the public domain. -- This is released into the public domain.
-- No warranty is provided, implied or otherwise. -- No warranty is provided, implied or otherwise.
-- app-kmt.lua : LC emergency plan -- app-kmt.lua : just a utility now
-- Authors: 20kdc -- Authors: 20kdc
local lcOverride = false
local l15 = "20kdc.duckdns.org:8888"
-- unicode.safeTextFormat'd lines
local console = {
"┎┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┒",
"┋ ┃ ┃╲ ╱┃ ▀▀┃▀▀ ┋",
"┋ ┃╳ ┃ ╲ ┃ ┃ ┋",
"┋ ┃ ╲ ┃ ╲╱ ┃ ┃ ┋",
"┋ ┋",
"┋ KittenOS NEO MUD Terminal ┋",
"┖┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┚",
":Type text, Enter key sends.",
":'>' is 'from you', '<' 'to you'.",
":':' is 'internal message'.",
":Control-Arrows resizes window.",
-- ":F5/F6 copies/pastes current line.",
":",
":",
":Enter target server:port",
-- 14
}
--++++++++++++++++++++++++++++++++++++++++
-- sW must not go below 3.
-- sH must not go below 2.
local sW, sH = 40, 15
local inet = neo.requireAccess("c.internet", "internet").list()() local inet = neo.requireAccess("c.internet", "internet").list()()
local windows = neo.requireAccess("x.neo.pub.window", "windows")
local window = windows(sW, sH)
local tcp = nil local _, _, termId = ...
local dummyTcp = {read = function() return "" end, write = function() end, close = function() end} local ok = pcall(function ()
local tcpBuf = "" assert(string.sub(termId, 1, 12) == "x.neo.pub.t/")
end)
local function fmtLine(s) local termClose
s = unicode.safeTextFormat(s)
local l = unicode.len(s)
return unicode.sub(s .. (" "):rep(sW - l), -sW)
end
local function line(i) if not ok then
if i ~= sH then termId = nil
assert(console[i], "console" .. i) assert(neo.executeAsync("svc-t", function (res)
window.span(1, i, fmtLine(console[i]), 0xFFFFFF, 0) termId = res.access
else termClose = res.close
window.span(1, i, fmtLine(l15), 0, 0xFFFFFF) neo.scheduleTimer(0)
end, "kmt"))
while true do
if coroutine.yield() == "k.timer" then break end
end end
end end
local term = neo.requireAccess(termId, "terminal")
local function incoming(s) term.write([[
local function shift(f)
for i = 1, #console - 1 do
console[i] = console[i + 1]
end
console[#console] = f
end KittenOS NEO MUD Terminal
-- Need to break this safely.
shift("") export TERM=ansi.sys <- IMPORTANT!!!
for i = 1, unicode.len(s) do Enter target server:port...
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
local function submitLine() local targetBuffer = ""
incoming(">" .. l15)
if not tcp then
tcp = inet.connect(l15)
if not tcp then
incoming(":The connection could not be created.")
tcp = dummyTcp
else
if not tcp.finishConnect() then
incoming(":Warning: finishConnect = false")
end
neo.scheduleTimer(os.uptime() + 0.1)
end
else
-- PRJblackstar doesn't need \r but others might
tcp.write(l15 .. "\r\n")
end
l15 = ""
line(sH)
end
if lcOverride then neo.scheduleTimer(0)
submitLine()
end
local function clipEnt(tx)
tx = tx:gsub("\r", "")
local ci = tx:find("\n") or (#tx + 1)
tx = tx:sub(1, ci - 1)
l15 = tx
line(sH)
end
local control = false
while true do while true do
local e = {coroutine.yield()} local e = {coroutine.yield()}
if e[1] == "k.timer" then if e[1] == "k.timer" then
while true do while tcp do
local b, e = tcp.read(neo.readBufSize) local b, e = tcp.read(neo.readBufSize)
if not b then if not b then
if e then if e then
incoming(":Warning: " .. e) term.write("\nkmt: " .. tostring(e) .. "\n")
tcp.close() tcp.close()
tcp = dummyTcp tcp = nil
end end
break
elseif b == "" then elseif b == "" then
break break
else else
tcpBuf = tcpBuf .. b:gsub("\r", "") term.write(b)
while true do
local nlp = tcpBuf:find("\n")
if nlp then
incoming("<" .. tcpBuf:sub(1, nlp - 1))
tcpBuf = tcpBuf:sub(nlp + 1)
else
break
end
end
end end
end end
neo.scheduleTimer(os.uptime() + 0.1) neo.scheduleTimer(os.uptime() + 0.049)
elseif e[1] == "x.neo.pub.window" then elseif e[1] == "k.procdie" then
if e[3] == "close" then if e[3] == term.pid then
if tcp then break
tcp.close() end
end elseif e[1] == termId then
return if targetBuffer and e[2] == "data" then
elseif e[3] == "clipboard" then targetBuffer = targetBuffer .. e[3]:gsub("\r", "")
clipEnt(e[4]) local p = targetBuffer:find("\n")
elseif e[3] == "key" then if p then
if e[5] == 29 or e[5] == 157 then local ok, res, rer = pcall(inet.connect, targetBuffer:sub(1, p - 1))
control = e[6] targetBuffer = targetBuffer:sub(p + 1):gsub("\n", "\r\n")
elseif e[6] then if not ok then
if not control then -- Likes to return this kind
if e[4] == 8 or e[4] == 127 then term.write("kmt: " .. tostring(res) .. "\n")
l15 = unicode.sub(l15, 1, -2) elseif not res then
elseif e[4] == 13 then -- Could theoretically return this kind
submitLine() term.write("kmt: " .. tostring(rer) .. "\n")
elseif e[4] >= 32 then else
l15 = l15 .. unicode.char(e[4]) -- Hopefully this kind
end term.write("kmt: Connecting...\n")
line(sH) tcp = res
elseif e[5] == 203 and sW > 8 then tcp.write(targetBuffer)
sW = sW - 1 targetBuffer = nil
window.setSize(sW, sH)
elseif e[5] == 200 and sH > 2 then
sH = sH - 1
table.remove(console, 1)
window.setSize(sW, sH)
elseif e[5] == 205 then
sW = sW + 1
window.setSize(sW, sH)
elseif e[5] == 208 then
sH = sH + 1
table.insert(console, 1, "")
window.setSize(sW, sH)
end end
end end
elseif e[3] == "line" then elseif tcp and e[2] == "data" or e[2] == "telnet" then
line(e[4]) tcp.write(e[3])
end end
end end
end end
if tcp then
tcp.close()
end

View File

@ -1,34 +1,27 @@
The "svc-t" program / "x.svc.t" The "svc-t" program / "x.neo.pub.t"
permission makes up the terminal permission makes up the terminal
subsystem for KittenOS NEO. subsystem for KittenOS NEO.
--- THEORETICAL TERMINALS MODEL --- --- THEORETICAL TERMINALS MODEL ---
The theoretical model for terminals The theoretical model for terminals
in KittenOS NEO is a TELNET client in KittenOS NEO is a TELNET client,
that only supports the ECHO option, using the non-standard behavior of
and uses the non-standard behavior treating a lack of remote echo as
of treating ECHO ON as 'enable local meaning 'local line editing'.
line editing'.
To prevent code size going too far, To prevent code size going too far,
the client is extremely restricted the shipped terminal supports:
in capabilities.
1. Built-in history as part of the
line editing functionality
2. ANSI.SYS-compatible display, but
no support for attributes.
If you really want full support, If you really want full support,
write a better terminal application. 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
@ -82,7 +75,7 @@ When the terminal has shown, the
function provided is called with a function provided is called with a
table as follows: table as follows:
access = "x.svc.t/<...>" access = "x.neo.pub.t/<...>"
close = function (): close terminal close = function (): close terminal
The k.kill permission and the close The k.kill permission and the close
@ -95,7 +88,7 @@ In either case, when the access has
been acquired, the following API is been acquired, the following API is
presented: presented:
id = "x.svc.t/<...>" id = "x.neo.pub.t/<...>"
pid = <The terminal's PID.> pid = <The terminal's PID.>
write = function (text): Writes the write = function (text): Writes the
TELNET data to the terminal. TELNET data to the terminal.