diff --git a/src/main/resources/assets/opencomputers/loot/openos/bin/echo.lua b/src/main/resources/assets/opencomputers/loot/openos/bin/echo.lua index 59dbc8a5c..3986ea031 100644 --- a/src/main/resources/assets/opencomputers/loot/openos/bin/echo.lua +++ b/src/main/resources/assets/opencomputers/loot/openos/bin/echo.lua @@ -1,9 +1,11 @@ local args, options = require("shell").parse(...) if options.help then - print([[`echo` writes the provided string(s) to the standard output. + io.write([[ +`echo` writes the provided string(s) to the standard output. -n do not output the trialing newline -e enable interpretation of backslash escapes - --help display this help and exit]]) + --help display this help and exit +]]) return end if options.e then @@ -16,5 +18,5 @@ if options.e then end io.write(table.concat(args," ")) if not options.n then - print() + io.write("\n") end diff --git a/src/main/resources/assets/opencomputers/loot/openos/bin/sh.lua b/src/main/resources/assets/opencomputers/loot/openos/bin/sh.lua index 01f627e4c..d1b5bdb4e 100644 --- a/src/main/resources/assets/opencomputers/loot/openos/bin/sh.lua +++ b/src/main/resources/assets/opencomputers/loot/openos/bin/sh.lua @@ -23,7 +23,7 @@ if #args == 0 then end io.write(sh.expand(os.getenv("PS1") or "$ ")) end - local command = tty:read(input_handler) + local command = tty.read(input_handler) if command then command = text.trim(command) if command == "exit" then diff --git a/src/main/resources/assets/opencomputers/loot/openos/bin/sleep.lua b/src/main/resources/assets/opencomputers/loot/openos/bin/sleep.lua index 43e2d5222..b7c682233 100644 --- a/src/main/resources/assets/opencomputers/loot/openos/bin/sleep.lua +++ b/src/main/resources/assets/opencomputers/loot/openos/bin/sleep.lua @@ -1,4 +1,5 @@ -local shell = require('shell') +local shell = require("shell") +local tty = require("tty") local args, options = shell.parse(...) if options.help then @@ -50,4 +51,9 @@ for _,v in ipairs(args) do total_time = total_time + time_type_multiplier(time_type) * interval end -os.sleep(total_time) +local stdin_stream = io.stdin.stream +if stdin_stream.pull then + return stdin_stream:pull(nil, total_time, "interrupted") +else + os.sleep(total_time) +end diff --git a/src/main/resources/assets/opencomputers/loot/openos/boot/03_io.lua b/src/main/resources/assets/opencomputers/loot/openos/boot/03_io.lua index 1a8fa061b..f223b0d00 100644 --- a/src/main/resources/assets/opencomputers/loot/openos/boot/03_io.lua +++ b/src/main/resources/assets/opencomputers/loot/openos/boot/03_io.lua @@ -1,14 +1,14 @@ local buffer = require("buffer") -local tty = require("tty") +local tty_stream = require("tty").stream -local core_stdin = buffer.new("r", tty) -local core_stdout = buffer.new("w", tty) +local core_stdin = buffer.new("r", tty_stream) +local core_stdout = buffer.new("w", tty_stream) local core_stderr = buffer.new("w", setmetatable( { write = function(_, str) - return tty:write("\27[31m"..str.."\27[37m") + return tty_stream:write("\27[31m"..str.."\27[37m") end - }, {__index=tty})) + }, {__index=tty_stream})) core_stdout:setvbuf("no") core_stderr:setvbuf("no") @@ -16,9 +16,9 @@ core_stdin.tty = true core_stdout.tty = true core_stderr.tty = true -core_stdin.close = tty.close -core_stdout.close = tty.close -core_stderr.close = tty.close +core_stdin.close = tty_stream.close +core_stdout.close = tty_stream.close +core_stderr.close = tty_stream.close local io_mt = getmetatable(io) or {} io_mt.__index = function(_, k) diff --git a/src/main/resources/assets/opencomputers/loot/openos/lib/core/boot.lua b/src/main/resources/assets/opencomputers/loot/openos/lib/core/boot.lua index c0be1655b..775eca3f5 100644 --- a/src/main/resources/assets/opencomputers/loot/openos/lib/core/boot.lua +++ b/src/main/resources/assets/opencomputers/loot/openos/lib/core/boot.lua @@ -1,7 +1,7 @@ -- called from /init.lua local raw_loadfile = ... -_G._OSVERSION = "OpenOS 1.6.7" +_G._OSVERSION = "OpenOS 1.6.8" local component = component local computer = computer diff --git a/src/main/resources/assets/opencomputers/loot/openos/lib/core/lua_shell.lua b/src/main/resources/assets/opencomputers/loot/openos/lib/core/lua_shell.lua index ad7997a63..48bdeeacd 100644 --- a/src/main/resources/assets/opencomputers/loot/openos/lib/core/lua_shell.lua +++ b/src/main/resources/assets/opencomputers/loot/openos/lib/core/lua_shell.lua @@ -10,12 +10,11 @@ end local env -- forward declare for binding in metamethod env = setmetatable({}, { - __index = function(t, k) + __index = function(_, k) _ENV[k] = _ENV[k] or optrequire(k) return _ENV[k] end, - __pairs = function(self) - local t = self + __pairs = function(t) return function(_, key) local k, v = next(t, key) if not k and t == env then @@ -94,7 +93,7 @@ io.write("Press Ctrl+D to exit the interpreter.\n\27[37m") while tty.isAvailable() do io.write(env._PROMPT) - local command = tty:read(read_handler) + local command = tty.read(read_handler) if not command then -- eof return end diff --git a/src/main/resources/assets/opencomputers/loot/openos/lib/term.lua b/src/main/resources/assets/opencomputers/loot/openos/lib/term.lua index 3ce1df9ff..c7466d0ca 100644 --- a/src/main/resources/assets/opencomputers/loot/openos/lib/term.lua +++ b/src/main/resources/assets/opencomputers/loot/openos/lib/term.lua @@ -70,7 +70,7 @@ end local function build_horizontal_reader(cursor) cursor.clear_tail = function(self) local w,_,dx,dy,x,y = tty.getViewport() - local _,s2=tty.internal.split(self) + local _,s2=tty.split(self) local wlen = math.min(unicode.wlen(s2),w-x+1) tty.gpu().fill(x+dx,y+dy,wlen,1," ") end @@ -198,15 +198,16 @@ function term.read(history, dobreak, hint, pwchar, filter) local handler = history handler.hint = handler.hint or hint - local cursor = tty.internal.build_vertical_reader() + local cursor = tty.build_vertical_reader() if handler.nowrap then build_horizontal_reader(cursor) end inject_filter(handler, filter) inject_mask(cursor, dobreak, pwchar or history.pwchar) + handler.cursor = cursor - return tty:read(handler, cursor) + return tty.read(handler) end function term.getGlobalArea(window) @@ -228,7 +229,15 @@ function term.pull(...) timeout = table.remove(args, 1) args.n = args.n - 1 end - return tty.pull(nil, timeout, table.unpack(args, 1, args.n)) + local stdin_stream = io.stdin.stream + if stdin_stream.pull then + return stdin_stream:pull(nil, timeout, table.unpack(args, 1, args.n)) + end + -- if stdin does not have pull() we can build the result + local result = io.read(1) + if result then + return "clipboard", nil, result + end end function term.bind(gpu, window) diff --git a/src/main/resources/assets/opencomputers/loot/openos/lib/thread.lua b/src/main/resources/assets/opencomputers/loot/openos/lib/thread.lua index 317b28a7e..b1d860fd8 100644 --- a/src/main/resources/assets/opencomputers/loot/openos/lib/thread.lua +++ b/src/main/resources/assets/opencomputers/loot/openos/lib/thread.lua @@ -77,17 +77,27 @@ local function get_box_thread_handle(handles, bCreate) end function box_thread:resume() - if self:status() ~= "suspended" then - return nil, "cannot resume " .. self:status() .. " thread" + local mt = getmetatable(self) + if mt.__status ~= "suspended" then + return nil, "cannot resume " .. mt.__status .. " thread" + end + mt.__status = "running" + -- register the thread to wake up + if coroutine.status(self.pco.root) == "suspended" and not mt.reg then + mt.register(0) end - getmetatable(self).__status = "running" end function box_thread:suspend() - if self:status() ~= "running" then - return nil, "cannot suspend " .. self:status() .. " thread" + local mt = getmetatable(self) + if mt.__status ~= "running" then + return nil, "cannot suspend " .. mt.__status .. " thread" + end + mt.__status = "suspended" + local pco_status = coroutine.status(self.pco.root) + if pco_status == "running" or pco_status == "normal" then + mt.coma() end - getmetatable(self).__status = "suspended" end function box_thread:status() @@ -113,29 +123,27 @@ function box_thread:attach(parent) if not proc then return nil, "thread failed to attach, process not found" end if mt.attached == proc then return self end -- already attached - local waiting_handler if mt.attached then local prev_btHandle = assert(get_box_thread_handle(mt.attached.data.handles), "thread panic: no thread handle") for i,h in ipairs(prev_btHandle) do if h == self then table.remove(prev_btHandle, i) - if mt.id then - waiting_handler = assert(mt.attached.data.handlers[mt.id], "thread panic: no event handler") - mt.attached.data.handlers[mt.id] = nil - end break end end end + -- registration happens on the attached proc, unregister before reparenting + local waiting_handler = mt.unregister() + -- attach to parent or the current process mt.attached = proc - local handles = proc.data.handles -- this process may not have a box_thread manager handle - local btHandle = get_box_thread_handle(handles, true) + local btHandle = get_box_thread_handle(proc.data.handles, true) table.insert(btHandle, self) + -- register on the new parent if waiting_handler then -- event-waiting mt.register(waiting_handler.timeout - computer.uptime()) end @@ -143,6 +151,23 @@ function box_thread:attach(parent) return self end +function thread.current() + local proc = process.findProcess() + local thread_root + while proc do + if thread_root then + for _,bt in ipairs(get_box_thread_handle(proc.data.handles) or {}) do + if bt.pco.root == thread_root then + return bt + end + end + else + thread_root = proc.data.coroutine_handler.root + end + proc = proc.parent + end +end + function thread.create(fp, ...) checkArg(1, fp, "function") @@ -153,8 +178,8 @@ function thread.create(fp, ...) mt.__status = "running" local fp_co = t.pco.create(fp) -- run fp_co until dead - -- pullSignal will yield_all past this point - -- but yield will return here, we pullSignal from here to yield_all + -- pullSignal will yield_past this point + -- but yield will return here, we pullSignal from here to yield_past local args = table.pack(...) while true do local result = table.pack(t.pco.resume(fp_co, table.unpack(args, 1, args.n))) @@ -188,7 +213,7 @@ function thread.create(fp, ...) --special resume to keep track of process death function mt.private_resume(...) - mt.id = nil + mt.unregister() -- this thread may have been killed if t:status() == "dead" then return end local result = table.pack(t.pco.resume(t.pco.root, ...)) @@ -209,17 +234,50 @@ function thread.create(fp, ...) timeout, -- wait for the time specified by the caller 1, -- we only want this thread to wake up once mt.attached.data.handlers) -- optional arg, to specify our own handlers + mt.reg = mt.attached.data.handlers[mt.id] + end + + function mt.unregister() + local id = mt.id + local reg = mt.reg + mt.id = nil + mt.reg = nil + -- before just removing a handler, make sure it is still ours + if id and mt.attached and mt.attached.data.handlers[id] == reg then + mt.attached.data.handlers[id] = nil + return reg + end + end + + function mt.coma() + mt.unregister() -- we should not wake up again (until resumed) + while mt.__status == "suspended" do + t.pco.yield_past(t.pco.root, 0) + end end function mt.process.data.pull(_, timeout) + --[==[ + yield_past(root) will yield until out of this thread + registration puts in a callback to resume this thread + + Subsequent registrations are necessary in case the thread is suspended + This thread yields when suspended, entering a coma state + -> coma state: yield without registration + + resume will regsiter a wakeup call, breaks coma + + subsequent yields need not specify a timeout because + we already legitimately resumed only to find out we had been suspended + + 3 places register for wake up + 1. computer.pullSignal [this path] + 2. t:attach(proc) will unregister and re-register + 3. t:resume() of a suspended thread + ]==] mt.register(timeout) - -- yield_past(root) will yield until out of this thread - -- the callback will resume this stack - local event_data - repeat - event_data = table.pack(t.pco.yield_past(t.pco.root, timeout)) - -- during sleep, we may have been suspended - until t:status() ~= "suspended" + local event_data = table.pack(t.pco.yield_past(t.pco.root, timeout)) + mt.coma() return table.unpack(event_data, 1, event_data.n) end diff --git a/src/main/resources/assets/opencomputers/loot/openos/lib/tty.lua b/src/main/resources/assets/opencomputers/loot/openos/lib/tty.lua index 131975a8a..163bc0ce0 100644 --- a/src/main/resources/assets/opencomputers/loot/openos/lib/tty.lua +++ b/src/main/resources/assets/opencomputers/loot/openos/lib/tty.lua @@ -16,7 +16,7 @@ tty.window = y = 1, } -tty.internal = {} +tty.stream = {} function tty.key_down_handler(handler, cursor, char, code) local data = cursor.data @@ -101,7 +101,7 @@ function tty.isAvailable() return not not (gpu and gpu.getScreen()) end -function tty.pull(cursor, timeout, ...) +function tty.stream:pull(cursor, timeout, ...) local blink = tty.getCursorBlink() timeout = timeout or math.huge local blink_timeout = blink and .5 or math.huge @@ -171,7 +171,7 @@ function tty.pull(cursor, timeout, ...) end end -function tty.internal.split(cursor) +function tty.split(cursor) local data, index = cursor.data, cursor.index local dlen = unicode.len(data) index = math.max(0, math.min(index, dlen)) @@ -179,7 +179,7 @@ function tty.internal.split(cursor) return unicode.sub(data, 1, index), tail == 0 and "" or unicode.sub(data, -tail) end -function tty.internal.build_vertical_reader() +function tty.build_vertical_reader() local x, y = tty.getCursor() return { @@ -194,7 +194,7 @@ function tty.internal.build_vertical_reader() move = function(self, n) local win = tty.window self.index = math.min(math.max(0, self.index + n), unicode.len(self.data)) - local s1, s2 = tty.internal.split(self) + local s1, s2 = tty.split(self) s2 = unicode.sub(s2.." ", 1, 1) local data_remaining = ("_"):rep(self.promptx - 1)..s1..s2 win.y = self.prompty - self.sy @@ -221,7 +221,7 @@ function tty.internal.build_vertical_reader() tty.gpu().fill(cx + dx, ey + dy, width - cx + 1, 1, " ") end, update = function(self, arg) - local s1, s2 = tty.internal.split(self) + local s1, s2 = tty.split(self) if type(arg) == "string" then self.data = s1 .. arg .. s2 self.index = self.index + unicode.len(arg) @@ -256,28 +256,30 @@ function tty.internal.build_vertical_reader() self.data = "" end, draw = function(self, text) - self.sy = self.sy + tty:write(text) + self.sy = self.sy + tty.stream:write(text) end } end --- PLEASE do not use this method directly, use io.read or tty.read -function tty.read(_, handler, cursor) - checkArg(1, handler, "table", "number") - checkArg(2, cursor, "table", "nil") +function tty.read(handler) + tty.window.handler = handler - if not io.stdin.tty then - return io.stdin:readLine(false) - end + local result = table.pack(pcall(io.read)) + tty.window.handler = nil - if type(handler) ~= "table" then - handler = {} - end + return select(2, assert(table.unpack(result))) +end + +-- PLEASE do not use this method directly, use io.read or term.read +function tty.stream:read() + local handler = tty.window.handler or {} + local cursor = handler.cursor or tty.build_vertical_reader() + + tty.window.handler = nil handler.index = 0 - cursor = cursor or tty.internal.build_vertical_reader() while true do - local name, address, char, code = tty.pull(cursor) + local name, address, char, code = tty.stream:pull(cursor) -- we may have lost tty during the pull if not tty.isAvailable() then return @@ -288,7 +290,7 @@ function tty.read(_, handler, cursor) local main_kb = tty.keyboard() local main_sc = tty.screen() if name == "interrupted" then - tty:write("^C\n") + tty.stream:write("^C\n") return false elseif address == main_kb or address == main_sc then local handler_method = handler[name] or @@ -321,10 +323,7 @@ function tty.setCursor(x, y) end -- PLEASE do not use this method directly, use io.write or term.write -function tty.write(_, value) - if not io.stdout.tty then - return io.write(value) - end +function tty.stream:write(value) local gpu = tty.gpu() if not gpu then return @@ -446,10 +445,12 @@ function tty.bind(gpu) end end local window = tty.window - window.gpu = gpu - window.keyboard = nil -- without a keyboard bound, always use the screen's main keyboard (1st) + if not window.gpu or window.gpu == gpu then + window.gpu = gpu + window.keyboard = nil -- without a keyboard bound, always use the screen's main keyboard (1st) + tty.getViewport() + end screen_reset(gpu) - tty.getViewport() end function tty.keyboard() @@ -492,17 +493,29 @@ function tty.screen() return gpu.getScreen() end -function tty.scroll(number) +function tty.scroll(lines) local gpu = tty.gpu() if not gpu then return 0 end local width, height, dx, dy, x, y = tty.getViewport() - local lines = number or (y - height) - if lines == 0 -- if zero scroll length is requested, do nothing - or not number and lines < 0 then -- do not auto scroll back up, only down - return 0 + -- nil lines indicates a request to auto scroll + -- auto scroll is when the cursor has gone below the bottom on the terminal + -- and the text is scroll up, pulling the cursor back into view + + -- lines<0 scrolls up (text down) + -- lines>0 scrolls down (text up) + + -- no lines count given, the user is asking to auto scroll y back into view + if not lines then + if y < 1 then + lines = y - 1 -- y==0 scrolls back -1 + elseif y > height then + lines = y - height -- y==height+1 scroll forward 1 + else + return 0 -- do nothing + end end lines = math.min(lines, height) @@ -516,16 +529,16 @@ function tty.scroll(number) gpu.copy(dx + 1, dy + 1 + math.max(0, lines), width, box_height, 0, -lines) gpu.fill(dx + 1, fill_top, width, abs_lines, ' ') - tty.setCursor(x, math.min(y, height)) + tty.setCursor(x, math.max(1, math.min(y, height))) return lines end -- stream methods local function bfd() return nil, "tty: invalid operation" end -tty.close = bfd -tty.seek = bfd -tty.handle = "tty" +tty.stream.close = bfd +tty.stream.seek = bfd +tty.stream.handle = "tty" require("package").delay(tty, "/lib/core/full_tty.lua")