From ea6b987c216d600b94137557eda75dbaf42062a8 Mon Sep 17 00:00:00 2001 From: gamemanj Date: Thu, 27 Oct 2016 20:59:25 +0100 Subject: [PATCH] Added 'omniterm' - basic terminal app Omniterm, + some sort of RPC->net interface, should allow applications that just need a text console to run, and have a nice transparent interface between local and remote control. And in any case, it should definitely work for the more likely use case of wanting to have a chat over a local OpenComputers network in a base. --- apps/omniterm.lua | 268 ++++++++++++++++++++++++++++++++++++++++++++++ init.lua | 2 +- 2 files changed, 269 insertions(+), 1 deletion(-) create mode 100644 apps/omniterm.lua diff --git a/apps/omniterm.lua b/apps/omniterm.lua new file mode 100644 index 0000000..bd0eb0a --- /dev/null +++ b/apps/omniterm.lua @@ -0,0 +1,268 @@ +-- OmniTerm: multi-driver terminal +-- Has RPC (lets apps use OmniTerm as a helper. Protocol commands are "msg", "join".), +-- Echo (test), +-- + +local table, unicode, math, proc, lang = A.request("table", "unicode", "math", "proc", "lang") + +local app = {} +local termWidth = 25 +-- console buffer cannot scroll left/right +local consoleBuffer = lang.getTable() +if not consoleBuffer then + consoleBuffer = { + --1234512345123451234512345 + "OmniTerm Interfaces:", + "rpc: |RPC", + "tun: |Linked Card", + "net::|Net.Card", + --Allows talking with everybody on a network. + "net: |Net.Card-BC", + --Allows talking with MineOS Chat... in theory... + "moschat: |MineOS Chat", + "componentlist|Components.", + "Exit with Ctrl-W." + } +end + +local lineBuffer = "" +local function incomingSubLine(txt) + for i = 1, #consoleBuffer - 1 do + consoleBuffer[i] = consoleBuffer[i + 1] + end + consoleBuffer[#consoleBuffer] = txt +end +local function incomingLine(txt) + txt = unicode.safeTextFormat(txt) + -- Note: >2 wide characters can *stay away*. Well away. + -- The result will always leave a 1-char gap at the end of a line, + -- which suffices to allow wide characters to work properly. + while unicode.len(txt) > termWidth do + incomingSubLine(unicode.sub(txt, 1, termWidth - 1)) + txt = " " .. unicode.sub(txt, termWidth) + end + incomingSubLine(txt) +end +local function outgoingLine(txt) +end +local function driverShutdown() +end +-- Driver utils +local function parseAddrPort(id) + if id:match("[a-f0-9%-]+:[0-9]+") ~= id then + error("Bad Syntax (::)") + end + local addr = id:match("[a-f0-9%-]+") + local port = tonumber(id:match(":[0-9]+"):sub(2)) + return addr, port +end +local drivers = { + -- rpc:appID + rpc = function (id) + proc.sendRPC(id, "join") + app.rpc = function (srcP, srcD, cmd, txt) + if srcD == id then + if cmd == "msg" then + incomingLine(tostring(txt)) + A.timer(0.01) + end + end + end + outgoingLine = function (txt) + proc.sendRPC(id, "msg", txt) + end + end, + -- tun: + tun = function (id) + local cT = A.request("c.tunnel") + local tunnel = nil + if id ~= "" then + for v in cT.list() do + if v.address:sub(1, id:len()) == id then tunnel = v end + end + end + if not tunnel then error("Couldn't find that tunnel.") end + app.event = function (...) + local ev = {...} + if ev[1] == "modem_message" then + if ev[2] == tunnel.address then + incomingLine(tostring(ev[6])) + return true + end + end + end + outgoingLine = function (txt) + tunnel.send(txt) + end + end, + -- net:targetAddr:port + -- Uses all modems to send to a target. (Tunnels do not count.) + net = function (id) + local addr = nil + local port = tonumber(id) + if not port then + addr, port = parseAddrPort(id) + end + local cM = A.request("c.modem") + for v in cM.list() do + v.open(port) + if v.isWireless() then + -- Probably not the best, but it'll do until a proper strength controller is written. + -- I don't know if it's set to full strength by default, and that's critical! + v.setStrength(400) + end + end + app.event = function (...) + local a = {...} + if a[1] == "modem_message" then + if (a[3] == addr) or (not addr) then + if a[4] == port then + incomingLine(tostring(a[6])) + end + end + end + return true + end + outgoingLine = function (txt) + for v in cM.list() do + if addr then + v.send(addr, port, txt) + else + v.broadcast(port, txt) + end + end + end + end, + -- moschat:
+ -- Allows basic communication with MineOS Chat systems, in theory. + -- In practice? This is untested against real MineOS. + moschat = function (id) + local port = 899 -- MOSChat port + local cM = A.request("c.modem") + for v in cM.list() do + v.open(port) + end + app.event = function (...) + local a = {...} + if a[1] == "modem_message" then + if a[3] == id then + if a[4] == port then + if a[6] == "HereIsMessageToYou" then + incomingLine(tostring(a[8])) + end + end + end + end + return true + end + outgoingLine = function (txt) + for v in cM.list() do + v.send(id, port, "HereIsMessageToYou", nil, txt) + end + end + end, + -- echo:postfix + -- Echoes back any line sent, plus a postfix. + echo = function (id) + outgoingLine = function (txt) + incomingLine(txt .. id) + end + end, + -- componentlist + -- Used to list components + -- Useful because OpenComputers stupidly cuts off text, + -- which is really bad when you need the *whole* ID... + -- >.< like when doing anything involving networking. + componentlist = function (id) + local stat = A.request("stat") + outgoingLine = function (txt) + incomingLine("Components [" .. txt .. "]:") + for ad, tp in stat.componentList() do + if tp == txt then + incomingLine(ad) + end + end + end + end +} +local hasDriver = false +local function typedLine(txt) + if not hasDriver then + -- Initialize driver + local dname = txt:match("^[^:]+") + if not dname then + incomingLine("You must specify what to connect to.") + return + end + if not drivers[dname] then + incomingLine("No such driver '" .. dname .. "'") + return + end + txt = txt:sub(dname:len() + 2) + incomingLine("Connecting with driver '" .. dname .. "'") + local ok, err = pcall(drivers[dname], txt) + if not ok then + incomingLine("Error: " .. err) + else + local a = "" + for i = 1, termWidth do a = a .. "-" end + incomingLine(a) + hasDriver = true + end + else + incomingLine(txt) + outgoingLine(txt) + end +end + +-- triggered by drivers that can't directly cause redraws +function app.update() + return true +end + +function app.get_ch(x, y) + local l, lp = consoleBuffer[y], 0 + if y == #consoleBuffer + 1 then + lp = unicode.len(lineBuffer) - math.floor(termWidth / 2) + if lp < 1 then lp = 1 end + l, lp = unicode.safeTextFormat(lineBuffer, lp) + lp = lp - 1 + end + return unicode.sub(l, x + lp, x + lp) +end + +local ctrlFlag = false +function app.key(ka, kc, down) + if kc == 29 then + ctrlFlag = down + return false + end + if ctrlFlag then + if down then + if kc == 17 then -- W + driverShutdown() + A.die() + end + end + return false + end + if down then + if ka ~= 0 then + if ka == 13 then + typedLine(lineBuffer) + lineBuffer = "" + return true + else + if ka == 8 then + lineBuffer = unicode.sub(lineBuffer, 1, unicode.len(lineBuffer) - 1) + return true + else + lineBuffer = lineBuffer .. unicode.char(ka) + return true + end + end + end + end +end + +return app, termWidth, #consoleBuffer + 1 \ No newline at end of file diff --git a/init.lua b/init.lua index 0d9e3eb..61d3871 100644 --- a/init.lua +++ b/init.lua @@ -668,7 +668,7 @@ while true do if t then for k, v in pairs(apps) do if v.hasAccess["root"] or v.hasAccess["s." .. t] then - handleEvNRD(v, "event", table.unpack(signal)) + handleEvNRD(k, "event", table.unpack(signal)) end end if t == "key_down" then