_G._OSNAME = "miniOS" _G._OSVER = "0.6.1.5" _G._OSVERSION = _OSNAME .. " " .. _OSVER _G._OSCREDIT = "miniOS by Skye, based off of OpenOS code from OpenComputers.\nminiOS code is under BSD 2-clause license, OpenOS code is under the MIT liecence." --component code function component_code() local adding = {} local removing = {} local primaries = {} ------------------------------------------------------------------------------- -- This allows writing component.modem.open(123) instead of writing -- component.getPrimary("modem").open(123), which may be nicer to read. setmetatable(component, { __index = function(_, key) return component.getPrimary(key) end}) function component.get(address, componentType) checkArg(1, address, "string") checkArg(2, componentType, "string", "nil") for c in component.list(componentType, true) do if c:sub(1, address:len()) == address then return c end end return nil, "no such component" end function component.isAvailable(componentType) checkArg(1, componentType, "string") if not primaries[componentType] then -- This is mostly to avoid out of memory errors preventing proxy -- creation cause confusion by trying to create the proxy again, -- causing the oom error to be thrown again. component.setPrimary(componentType, component.list(componentType, true)()) end return primaries[componentType] ~= nil end function component.isPrimary(address) local componentType = component.type(address) if componentType then if component.isAvailable(componentType) then return primaries[componentType].address == address end end return false end function component.getPrimary(componentType) checkArg(1, componentType, "string") assert(component.isAvailable(componentType), "no primary '" .. componentType .. "' available") return primaries[componentType] end function component.setPrimary(componentType, address) checkArg(1, componentType, "string") checkArg(2, address, "string", "nil") if address ~= nil then address = component.get(address, componentType) assert(address, "no such component") end local wasAvailable = primaries[componentType] if wasAvailable and address == wasAvailable.address then return end local wasAdding = adding[componentType] if wasAdding and address == wasAdding.address then return end if wasAdding then event.cancel(wasAdding.timer) end primaries[componentType] = nil adding[componentType] = nil local primary = address and component.proxy(address) or nil if wasAvailable then computer.pushSignal("component_unavailable", componentType) end if primary then if wasAvailable or wasAdding then adding[componentType] = { address=address, timer=event.timer(0.1, function() adding[componentType] = nil primaries[componentType] = primary computer.pushSignal("component_available", componentType) end) } else primaries[componentType] = primary computer.pushSignal("component_available", componentType) end end end ------------------------------------------------------------------------------- local function onComponentAdded(_, address, componentType) if not (primaries[componentType] or adding[componentType]) then component.setPrimary(componentType, address) end end local function onComponentRemoved(_, address, componentType) if primaries[componentType] and primaries[componentType].address == address or adding[componentType] and adding[componentType].address == address then component.setPrimary(componentType, component.list(componentType, true)()) end end event.listen("component_added", onComponentAdded) event.listen("component_removed", onComponentRemoved) end --text libary function text_code() local text = {} function text.detab(value, tabWidth) checkArg(1, value, "string") checkArg(2, tabWidth, "number", "nil") tabWidth = tabWidth or 8 local function rep(match) local spaces = tabWidth - match:len() % tabWidth return match .. string.rep(" ", spaces) end local result = value:gsub("([^\n]-)\t", rep) -- truncate results return result end function text.padRight(value, length) checkArg(1, value, "string", "nil") checkArg(2, length, "number") if not value or unicode.len(value) == 0 then return string.rep(" ", length) else return value .. string.rep(" ", length - unicode.len(value)) end end function text.padLeft(value, length) checkArg(1, value, "string", "nil") checkArg(2, length, "number") if not value or unicode.len(value) == 0 then return string.rep(" ", length) else return string.rep(" ", length - unicode.len(value)) .. value end end function text.trim(value) -- from http://lua-users.org/wiki/StringTrim local from = string.match(value, "^%s*()") return from > #value and "" or string.match(value, ".*%S", from) end function text.wrap(value, width, maxWidth) checkArg(1, value, "string") checkArg(2, width, "number") checkArg(3, maxWidth, "number") local line, nl = value:match("([^\r\n]*)(\r?\n?)") -- read until newline if unicode.len(line) > width then -- do we even need to wrap? local partial = unicode.sub(line, 1, width) local wrapped = partial:match("(.*[^a-zA-Z0-9._()'`=])") if wrapped or unicode.len(line) > maxWidth then partial = wrapped or partial return partial, unicode.sub(value, unicode.len(partial) + 1), true else return "", value, true -- write in new line. end end local start = unicode.len(line) + unicode.len(nl) + 1 return line, start <= unicode.len(value) and unicode.sub(value, start) or nil, unicode.len(nl) > 0 end function text.wrappedLines(value, width, maxWidth) local line, nl return function() if value then line, value, nl = text.wrap(value, width, maxWidth) return line end end end ------------------------------------------------------------------------------- function text.tokenize(value) checkArg(1, value, "string") local tokens, token = {}, "" local escaped, quoted, start = false, false, -1 for i = 1, unicode.len(value) do local char = unicode.sub(value, i, i) if escaped then -- escaped character escaped = false token = token .. char elseif char == "\\" and quoted ~= "'" then -- escape character? escaped = true token = token .. char elseif char == quoted then -- end of quoted string quoted = false token = token .. char elseif (char == "'" or char == '"') and not quoted then quoted = char start = i token = token .. char elseif string.find(char, "%s") and not quoted then -- delimiter if token ~= "" then table.insert(tokens, token) token = "" end else -- normal char token = token .. char end end if quoted then return nil, "unclosed quote at index " .. start end if token ~= "" then table.insert(tokens, token) end return tokens end ------------------------------------------------------------------------------- function text.endswith(s, send) return #s >= #send and s:find(send, #s-#send+1, true) and true or false end return text end --event code function event_code() local event, listeners, timers = {}, {}, {} local lastInterrupt = -math.huge local function matches(signal, name, filter) if name and not (type(signal[1]) == "string" and signal[1]:match(name)) then return false end for i = 1, filter.n do if filter[i] ~= nil and filter[i] ~= signal[i + 1] then return false end end return true end local function call(callback, ...) local result, message = pcall(callback, ...) if not result and type(event.onError) == "function" then pcall(event.onError, message) return end return message end local function dispatch(signal, ...) if listeners[signal] then local function callbacks() local list = {} for index, listener in ipairs(listeners[signal]) do list[index] = listener end return list end for _, callback in ipairs(callbacks()) do if call(callback, signal, ...) == false then event.ignore(signal, callback) -- alternative method of removing a listener end end end end local function tick() local function elapsed() local list = {} for id, timer in pairs(timers) do if timer.after <= computer.uptime() then table.insert(list, timer.callback) timer.times = timer.times - 1 if timer.times <= 0 then timers[id] = nil else timer.after = computer.uptime() + timer.interval end end end return list end for _, callback in ipairs(elapsed()) do call(callback) end end ------------------------------------------------------------------------------- function event.cancel(timerId) checkArg(1, timerId, "number") if timers[timerId] then timers[timerId] = nil return true end return false end function event.ignore(name, callback) checkArg(1, name, "string") checkArg(2, callback, "function") if listeners[name] then for i = 1, #listeners[name] do if listeners[name][i] == callback then table.remove(listeners[name], i) if #listeners[name] == 0 then listeners[name] = nil end return true end end end return false end function event.listen(name, callback) checkArg(1, name, "string") checkArg(2, callback, "function") if listeners[name] then for i = 1, #listeners[name] do if listeners[name][i] == callback then return false end end else listeners[name] = {} end table.insert(listeners[name], callback) return true end function event.onError(message) local log = io.open("/tmp/event.log", "a") if log then log:write(message .. "\n") log:close() end end function event.pull(...) local args = table.pack(...) local seconds, name, filter if type(args[1]) == "string" then name = args[1] filter = table.pack(table.unpack(args, 2, args.n)) else checkArg(1, args[1], "number", "nil") checkArg(2, args[2], "string", "nil") seconds = args[1] name = args[2] filter = table.pack(table.unpack(args, 3, args.n)) end local hasFilter = name ~= nil if not hasFilter then for i = 1, filter.n do hasFilter = hasFilter or filter[i] ~= nil end end local deadline = seconds and (computer.uptime() + seconds) or (hasFilter and math.huge or 0) repeat local closest = seconds and deadline or math.huge for _, timer in pairs(timers) do closest = math.min(closest, timer.after) end local signal = table.pack(computer.pullSignal(closest - computer.uptime())) if signal.n > 0 then dispatch(table.unpack(signal, 1, signal.n)) end tick() if event.shouldInterrupt() then lastInterrupt = computer.uptime() error("interrupted", 0) end if not (seconds or hasFilter) or matches(signal, name, filter) then return table.unpack(signal, 1, signal.n) end until computer.uptime() >= deadline end function event.shouldInterrupt() return computer.uptime() - lastInterrupt > 1 and keyboard.isControlDown() and keyboard.isAltDown() and keyboard.isKeyDown(keyboard.keys.c) end function event.timer(interval, callback, times) checkArg(1, interval, "number") checkArg(2, callback, "function") checkArg(3, times, "number", "nil") local id repeat id = math.floor(math.random(1, 0x7FFFFFFF)) until not timers[id] timers[id] = { interval = interval, after = computer.uptime() + interval, callback = callback, times = times or 1 } return id end ------------------------------------------------------------------------------- return event end --filesystem code function fs_code() local fs = {} fs.drive = {} --drive mapping table, initilized later fs.drive._map = {} --converts a drive letter into a proxy function fs.drive.letterToProxy(letter) return fs.drive._map[letter] end --finds the proxy associated with the letter function fs.drive.proxyToLetter(proxy) for l,p in pairs(fs.drive._map) do if p == proxy then return l end end return nil end --maps a proxy to a letter function fs.drive.mapProxy(letter, proxy) fs.drive._map[letter] = proxy end --finds the address of a drive letter. function fs.drive.toAddress(letter) return fs.drive._map[letter].address end --finds the drive letter mapped to an address function fs.drive.toLetter(address) for l,p in pairs(fs.drive._map) do if p.address == address then return l end end return nil end function fs.drive.mapAddress(letter, address) --print("mapAddress") fs.drive._map[letter] = fs.proxy(address) end function fs.drive.autoMap(address) --returns the letter if mapped OR already mapped, false if not. --print("autoMap") --we get the address and see if it already is mapped... local l = fs.drive.toLetter(address) if l then return l end --then we take the address and attempt to map it --start at A: l = "A" while true do --see if it is mapped and then go to the next letter... if fs.drive._map[l] then l = ('ABCDEFGHIJKLMNOPQRSTUVWXYZ_'):match(l..'(.)') else fs.drive.mapAddress(l, address) return l end --if we got to the end, fail if l == "_" then return false end end end function fs.drive.listProxy() local t = fs.drive._map local p = {} for n in pairs(t) do table.insert(p, n) end table.sort(p, f) local i = 0 -- iterator variable local iter = function () -- iterator function i = i + 1 if p[i] == nil then return nil else return p[i], t[p[i]] end end return iter end function fs.drive.list() local i = 0 -- iterator variable local proxyIter = fs.drive.listProxy() local iter = function () -- iterator function l, p = proxyIter() if not l then return nil end return l, p.address end return iter end fs.drive._current = "A" --as the boot drive is A: function fs.drive.setcurrent(letter) letter = letter:upper() if not fs.drive._map[letter] then error("Invalid Drive", 2) end fs.drive._current = letter end function fs.drive.drivepathSplit(mixed) local drive = fs.drive._current local path if string.sub(mixed, 2,2) == ":" then drive = string.sub(mixed, 1,1):upper() path = string.sub(mixed, 3) else path = mixed end return drive, path end function fs.drive.getcurrent() return fs.drive._current end function fs.invoke(method, ...) return fs.drive._map[fs.drive._current][method](...) end function fs.proxy(filter) checkArg(1, filter, "string") local address for c in component.list("filesystem") do if component.invoke(c, "getLabel") == filter then address = c break end if filter:sub(2,2) == ":" then if fs.drive.toAddress(filter:sub(1,1)) == c then address = c break end end if c:sub(1, filter:len()) == filter then address = c break end end if not address then return nil, "no such file system" end return component.proxy(address) end function fs.open(file, mode) local drive, handle, proxy drive, path = fs.drive.drivepathSplit(file) proxy = fs.drive.letterToProxy(drive) handle, reason = proxy.open(path, mode or 'r') if not handle then return nil, reason end return {_handle = handle, _proxy = proxy} end function fs.write(handle, data) return handle._proxy.write(handle._handle, data) end function fs.read(handle, length) return handle._proxy.read(handle._handle, length or math.huge) end function fs.close(handle) return handle._proxy.close(handle._handle) end function fs.isDirectory(path) local drive drive, path = fs.drive.drivepathSplit(path) return fs.drive.letterToProxy(drive).isDirectory(path) end function fs.exists(path) local drive drive, path = fs.drive.drivepathSplit(path) return fs.drive.letterToProxy(drive).exists(path) end function fs.remove(path) local drive drive, path = fs.drive.drivepathSplit(path) return fs.drive.letterToProxy(drive).remove(path) end function fs.copy(fromPath, toPath) if fs.isDirectory(fromPath) then return nil, "cannot copy folders" end local input, reason = fs.open(fromPath, "rb") if not input then return nil, reason end local output, reason = fs.open(toPath, "wb") if not output then fs.close(input) return nil, reason end repeat local buffer, reason = filesystem.read(input) if not buffer and reason then return nil, reason elseif buffer then local result, reason = filesystem.write(output, buffer) if not result then filesystem.close(input) filesystem.close(output) return nil, reason end end until not buffer filesystem.close(input) filesystem.close(output) return true end function fs.rename(path1, path2) local drive drive, path = fs.drive.drivepathSplit(path) return fs.drive.letterToProxy(drive).rename(path1, path2) end function fs.makeDirectory(path) local drive drive, path = fs.drive.drivepathSplit(path) return fs.drive.letterToProxy(drive).makeDirectory(path) end function fs.list(path) local drive drive, path = fs.drive.drivepathSplit(path) local i = 0 local t = fs.drive.letterToProxy(drive).list(path) local n = #t return function() i = i + 1 if i <= n then return t[i] end return nil end end function fs.get(path) local drive drive, path = fs.drive.drivepathSplit(path) drive = fs.drive.letterToProxy(drive) if not drive then return nil, "no such file system" else return drive, path end end --handle inserted and removed filesystems local function onComponentAdded(_, address, componentType) if componentType == "filesystem" then fs.drive.autoMap(address) end end local function onComponentRemoved(_, address, componentType) if componentType == "filesystem" then fs.drive.mapAddress(fs.drive.toLetter(address), nil) end end event.listen("component_added", onComponentAdded) event.listen("component_removed", onComponentRemoved) local function driveInit() local boot = fs.proxy(computer.getBootAddress()) local temp = fs.proxy(computer.tmpAddress()) fs.drive._map = { ["A"]=boot, ["X"]=temp } end driveInit() --return the API return fs end --terminal code function terminal_code() local term = {} local cursorX, cursorY = 1, 1 local cursorBlink = nil --- quick and dirty hacks that allow newer programs to run while not actually writing new code term.gpu = function() return component.gpu end term.getViewport = function() return 0, 0, component.gpu.getResolution() end term.getGlobalArea = function(ignored) local w,h,dx,dy = term.getViewport(window) return dx+1,dy+1,w,h end term.pull = event.pull term.keyboard = function() return component.keyboard end term.screen = function() return term.gpu().getScreen() end local function toggleBlink() if term.isAvailable() then cursorBlink.state = not cursorBlink.state if cursorBlink.state then cursorBlink.alt = component.gpu.get(cursorX, cursorY) component.gpu.set(cursorX, cursorY, "_") else component.gpu.set(cursorX, cursorY, cursorBlink.alt) end end end ------------------------------------------------------------------------------- function term.clear() if term.isAvailable() then local w, h = component.gpu.getResolution() component.gpu.fill(1, 1, w, h, " ") end cursorX, cursorY = 1, 1 end function term.clearLine() if term.isAvailable() then local w = component.gpu.getResolution() component.gpu.fill(1, cursorY, w, 1, " ") end cursorX = 1 end function term.getCursor() return cursorX, cursorY end function term.setCursor(col, row) checkArg(1, col, "number") checkArg(2, row, "number") if cursorBlink and cursorBlink.state then toggleBlink() end cursorX = math.floor(col) cursorY = math.floor(row) end function term.getCursorBlink() return cursorBlink ~= nil end function term.setCursorBlink(enabled) checkArg(1, enabled, "boolean") if enabled then if not cursorBlink then cursorBlink = {} cursorBlink.id = event.timer(0.5, toggleBlink, math.huge) cursorBlink.state = false elseif not cursorBlink.state then toggleBlink() end elseif cursorBlink then event.cancel(cursorBlink.id) if cursorBlink.state then toggleBlink() end cursorBlink = nil end end function term.isAvailable() return component.isAvailable("gpu") and component.isAvailable("screen") end function term.readKey(echo) local blink = term.getCursorBlink() term.setCursorBlink(true) local ok, name, address, charOrValue, code = pcall(event.pull, "key_down") if not ok then term.setCursorBlink(blink) error("interrupted", 0) end if name == "key_down" then if echo then term.write(charOrValue) end term.setCursorBlink(blink) end end function term.read(history, dobreak) checkArg(1, history, "table", "nil") history = history or {} table.insert(history, "") local offset = term.getCursor() - 1 local scrollX, scrollY = 0, #history - 1 local function getCursor() local cx, cy = term.getCursor() return cx - offset + scrollX, 1 + scrollY end local function line() local cbx, cby = getCursor() return history[cby] end local function setCursor(nbx, nby) local w, h = component.gpu.getResolution() local cx, cy = term.getCursor() scrollY = nby - 1 nbx = math.max(1, math.min(unicode.len(history[nby]) + 1, nbx)) local ncx = nbx + offset - scrollX if ncx > w then local sx = nbx - (w - offset) local dx = math.abs(scrollX - sx) scrollX = sx component.gpu.copy(1 + offset + dx, cy, w - offset - dx, 1, -dx, 0) local str = unicode.sub(history[nby], nbx - (dx - 1), nbx) str = text.padRight(str, dx) component.gpu.set(1 + math.max(offset, w - dx), cy, unicode.sub(str, 1 + math.max(0, dx - (w - offset)))) elseif ncx < 1 + offset then local sx = nbx - 1 local dx = math.abs(scrollX - sx) scrollX = sx component.gpu.copy(1 + offset, cy, w - offset - dx, 1, dx, 0) local str = unicode.sub(history[nby], nbx, nbx + dx) --str = text.padRight(str, dx) component.gpu.set(1 + offset, cy, str) end term.setCursor(nbx - scrollX + offset, cy) end local function copyIfNecessary() local cbx, cby = getCursor() if cby ~= #history then history[#history] = line() setCursor(cbx, #history) end end local function redraw() local cx, cy = term.getCursor() local bx, by = 1 + scrollX, 1 + scrollY local w, h = component.gpu.getResolution() local l = w - offset local str = unicode.sub(history[by], bx, bx + l) str = text.padRight(str, l) component.gpu.set(1 + offset, cy, str) end local function home() local cbx, cby = getCursor() setCursor(1, cby) end local function ende() local cbx, cby = getCursor() setCursor(unicode.len(line()) + 1, cby) end local function left() local cbx, cby = getCursor() if cbx > 1 then setCursor(cbx - 1, cby) return true -- for backspace end end local function right(n) n = n or 1 local cbx, cby = getCursor() local be = unicode.len(line()) + 1 if cbx < be then setCursor(math.min(be, cbx + n), cby) end end local function up() local cbx, cby = getCursor() if cby > 1 then setCursor(1, cby - 1) redraw() ende() end end local function down() local cbx, cby = getCursor() if cby < #history then setCursor(1, cby + 1) redraw() ende() end end local function delete() copyIfNecessary() local cbx, cby = getCursor() if cbx <= unicode.len(line()) then history[cby] = unicode.sub(line(), 1, cbx - 1) .. unicode.sub(line(), cbx + 1) local cx, cy = term.getCursor() local w, h = component.gpu.getResolution() component.gpu.copy(cx + 1, cy, w - cx, 1, -1, 0) local br = cbx + (w - cx) local char = unicode.sub(line(), br, br) if not char or unicode.len(char) == 0 then char = " " end component.gpu.set(w, cy, char) end end local function insert(value) copyIfNecessary() local cx, cy = term.getCursor() local cbx, cby = getCursor() local w, h = component.gpu.getResolution() history[cby] = unicode.sub(line(), 1, cbx - 1) .. value .. unicode.sub(line(), cbx) local len = unicode.len(value) local n = w - (cx - 1) - len if n > 0 then component.gpu.copy(cx, cy, n, 1, len, 0) end component.gpu.set(cx, cy, value) right(len) end local function onKeyDown(char, code) term.setCursorBlink(false) if code == keyboard.keys.back then if left() then delete() end elseif code == keyboard.keys.delete then delete() elseif code == keyboard.keys.left then left() elseif code == keyboard.keys.right then right() elseif code == keyboard.keys.home then home() elseif code == keyboard.keys["end"] then ende() elseif code == keyboard.keys.up then up() elseif code == keyboard.keys.down then down() elseif code == keyboard.keys.enter then local cbx, cby = getCursor() if cby ~= #history then -- bring entry to front history[#history] = line() table.remove(history, cby) end return true, history[#history] .. "\n" elseif keyboard.isControlDown() and code == keyboard.keys.d then if line() == "" then history[#history] = "" return true, nil end elseif keyboard.isControlDown() and code == keyboard.keys.c then history[#history] = "" return true, nil elseif not keyboard.isControl(char) then insert(unicode.char(char)) end term.setCursorBlink(true) term.setCursorBlink(true) -- force toggle to caret end local function onClipboard(value) copyIfNecessary() term.setCursorBlink(false) local cbx, cby = getCursor() local l = value:find("\n", 1, true) if l then history[cby] = unicode.sub(line(), 1, cbx - 1) redraw() insert(unicode.sub(value, 1, l - 1)) return true, line() .. "\n" else insert(value) term.setCursorBlink(true) term.setCursorBlink(true) -- force toggle to caret end end local function cleanup() if history[#history] == "" then table.remove(history) end term.setCursorBlink(false) if term.getCursor() > 1 and dobreak ~= false then print() end end term.setCursorBlink(true) while term.isAvailable() do local ocx, ocy = getCursor() local ok, name, address, charOrValue, code = pcall(event.pull) if not ok then cleanup() error("interrupted", 0) end local ncx, ncy = getCursor() if ocx ~= ncx or ocy ~= ncy then cleanup() return "" -- soft fail the read if someone messes with the term end if term.isAvailable() and -- may have changed since pull type(address) == "string" and component.isPrimary(address) then local done, result if name == "key_down" then done, result = onKeyDown(charOrValue, code) elseif name == "clipboard" then done, result = onClipboard(charOrValue) end if done then cleanup() return result end end end cleanup() return nil -- fail the read if term becomes unavailable end function term.write(value, wrap) if not term.isAvailable() then return end value = tostring(value) if unicode.len(value) == 0 then return end do local noBell = value:gsub("\a", "") if #noBell ~= #value then value = noBell computer.beep() end end value = text.detab(value) local w, h = component.gpu.getResolution() if not w then return -- gpu lost its screen but the signal wasn't processed yet. end local blink = term.getCursorBlink() term.setCursorBlink(false) local line, nl repeat local wrapAfter, margin = math.huge, math.huge if wrap then wrapAfter, margin = w - (cursorX - 1), w end line, value, nl = text.wrap(value, wrapAfter, margin) component.gpu.set(cursorX, cursorY, line) cursorX = cursorX + unicode.len(line) if nl or (cursorX > w and wrap) then cursorX = 1 cursorY = cursorY + 1 end if cursorY > h then component.gpu.copy(1, 1, w, h, 0, -1) component.gpu.fill(1, h, w, 1, " ") cursorY = h end until not value term.setCursorBlink(blink) end ------------------------------------------------------------------------------- return term end function keyboard_code(keyboard_data) local keyboard = {pressedChars = {}, pressedCodes = {}, keys = keyboard_data.keys} -- Create inverse mapping for name lookup. setmetatable(keyboard.keys, { __index = function(tbl, k) if type(k) ~= "number" then return end for name,value in pairs(tbl) do if value == k then return name end end end }) local function getKeyboardAddress(address) return address or term.keyboard().address end local function getPressedCodes(address) address = getKeyboardAddress(address) return address and keyboard.pressedCodes[address] or false end local function getPressedChars(address) address = getKeyboardAddress(address) return address and keyboard.pressedChars[address] or false end function keyboard.isAltDown(address) checkArg(1, address, "string", "nil") local pressedCodes = getPressedCodes(address) return pressedCodes and (pressedCodes[keyboard.keys.lmenu] or pressedCodes[keyboard.keys.rmenu]) ~= nil end function keyboard.isControl(char) return type(char) == "number" and (char < 0x20 or (char >= 0x7F and char <= 0x9F)) end function keyboard.isControlDown(address) checkArg(1, address, "string", "nil") local pressedCodes = getPressedCodes(address) return pressedCodes and (pressedCodes[keyboard.keys.lcontrol] or pressedCodes[keyboard.keys.rcontrol]) ~= nil end function keyboard.isKeyDown(charOrCode, address) checkArg(1, charOrCode, "string", "number") checkArg(2, address, "string", "nil") if type(charOrCode) == "string" then local pressedChars = getPressedChars(address) return pressedChars and pressedChars[utf8 and utf8.codepoint(charOrCode) or charOrCode:byte()] elseif type(charOrCode) == "number" then local pressedCodes = getPressedCodes(address) return pressedCodes and pressedCodes[charOrCode] end end function keyboard.isShiftDown(address) checkArg(1, address, "string", "nil") local pressedCodes = getPressedCodes(address) return pressedCodes and (pressedCodes[keyboard.keys.lshift] or pressedCodes[keyboard.keys.rshift]) ~= nil end local function onKeyDown(_, address, char, code) if keyboard.pressedChars[address] then keyboard.pressedChars[address][char] = true keyboard.pressedCodes[address][code] = true end end local function onKeyUp(_, address, char, code) if keyboard.pressedChars[address] then keyboard.pressedChars[address][char] = nil keyboard.pressedCodes[address][code] = nil end end local function onComponentAdded(_, address, componentType) if componentType == "keyboard" then keyboard.pressedChars[address] = {} keyboard.pressedCodes[address] = {} end end local function onComponentRemoved(_, address, componentType) if componentType == "keyboard" then keyboard.pressedChars[address] = nil keyboard.pressedCodes[address] = nil end end for address in component.list("keyboard", true) do onComponentAdded("component_added", address, "keyboard") end event.listen("key_down", onKeyDown) event.listen("key_up", onKeyUp) event.listen("component_added", onComponentAdded) event.listen("component_removed", onComponentRemoved) return keyboard end local function printProcess(...) local args = table.pack(...) local argstr = "" for i = 1, args.n do local arg = tostring(args[i]) if i > 1 then arg = "\t" .. arg end argstr = argstr .. arg end return argstr end function print(...) term.write(printProcess(...) .. "\n", true) end function printErr(...) local c = component.gpu.getForeground() component.gpu.setForeground(0xFF0000) print(...) component.gpu.setForeground(c) end function printPaged(...) argstr = printProcess(...) .. "\n" local i = 0 local p = 0 function readline() i = string.find(argstr, "\n", i+1) -- find 'next' newline if i == nil then return nil end local out = argstr:sub(p,i) p = i + 1 return out end local function readlines(file, line, num) local w, h = component.gpu.getResolution() num = num or (h - 1) --num = num or (h) term.setCursorBlink(false) for _ = 1, num do if not line then line = readline() if not line then -- eof return nil end end local wrapped wrapped, line = text.wrap(text.detab(line), w, w) term.write(wrapped .. "\n") end term.setCursor(1, h) term.write("Press enter or space to continue:") term.setCursorBlink(true) return true end local line = nil while true do if not readlines(file, line) then return end while true do local event, address, char, code = event.pull("key_down") if component.isPrimary(address) then if code == keyboard.keys.q then term.setCursorBlink(false) term.clearLine() return elseif code == keyboard.keys.space or code == keyboard.keys.pageDown then term.clearLine() break elseif code == keyboard.keys.enter or code == keyboard.keys.down then term.clearLine() if not readlines(file, line, 1) then return end end end end end end --load programs function loadfile(file, mode, env) local handle, reason = filesystem.open(file) if not handle then error(reason, 2) end local buffer = "" repeat local data, reason = filesystem.read(handle) if not data and reason then error(reason) end buffer = buffer .. (data or "") until not data filesystem.close(handle) if mode == nil then mode = "bt" end if env == nil then env = _G end return load(buffer, "=" .. file) end function dofile(file) local program, reason = loadfile(file) if program then local result = table.pack(pcall(program)) if result[1] then return table.unpack(result, 2, result.n) else error(result[2]) end else error(reason) end end --set up libs event = event_code() component_code() text = text_code() filesystem = fs_code() fs = filesystem keyboard = keyboard_code(dofile("keyboard.lua")) term = terminal_code() -- set up other functions... os.sleep = function(timeout) checkArg(1, timeout, "number", "nil") local deadline = computer.uptime() + (timeout or 0) repeat event.pull(deadline - computer.uptime()) until computer.uptime() >= deadline end os.exit = function(code) error({[1]="INTERRUPT", [2]="EXIT", [3]=code}) end --set up terminal if term.isAvailable() then component.gpu.bind(component.screen.address) component.gpu.setResolution(component.gpu.getResolution()) component.gpu.setBackground(0x000000) component.gpu.setForeground(0xFFFFFF) term.setCursorBlink(true) term.clear() end print("Starting " .. _OSNAME .. "...\n") print(__OSCREDIT) --clean up libs event_code, component_code, text_code, fs_code, terminal_code, keyboard_code = nil, nil, nil, nil, nil, nil --map the drives for address, componentType in component.list() do if componentType == "filesystem" then filesystem.drive.autoMap(address) end end miniOS = {} local function interrupt(data) --print("INTERRUPT!") if data[2] == "RUN" then return miniOS.runfile(data[3], table.unpack(data[4])) end if data[2] == "ERR" then error("This error is for testing!") end if data[2] == "EXIT" then return data[3] end end local function runfile(file, ...) local program, reason = loadfile(file) if program then local targs = {...} local traceback local result = table.pack(xpcall(program, function(err) traceback = debug.traceback(nil, 2); return err end, ...)) --local result = table.pack(pcall(program, ...)) if traceback then local function dropsame(s1,s2) t1,t2={},{} for l in s1:gmatch("(.-)\n") do t1[#t1+1] = l end for l in s2:gmatch("(.-)\n") do t2[#t2+1] = l end for i = #t1,1,-1 do if t1[i] == t2[i] then t1[i] = nil t2[i] = nil else break end end os1,os2 = "","" for k,v in ipairs(t1) do os1 = os1 .. v .. "\n" end for k,v in ipairs(t2) do os2 = os2 .. v .. "\n" end return os1,os2 end traceback = dropsame(traceback, debug.traceback(nil, 2)) .. "\t..." end if result[1] then return table.unpack(result, 2, result.n) else if type(result[2]) == "table" then if result[2][1] then if result[2][1] == "INTERRUPT" then result = {interrupt(result[2])} --if not result[1] then --error(result[2], 3) --else --return table.unpack(result, 2, result.n) --end return end end end error(result[2] .. "\n" .. traceback, 3) end else error(reason, 3) end end local function kernelError() printErr("\nPress any key to try again.") term.readKey() end function miniOS.saferunfile(...) local r = {pcall(runfile, ...)} if not r[1] then local c = component.gpu.getForeground() component.gpu.setForeground(0xFF0000) printPaged(r[2]) component.gpu.setForeground(c) end return r end function miniOS.runfile(...) local r = miniOS.saferunfile(...) return table.unpack(r, 2, r.n) end local function tryrunlib(lib) local ret local opt = {lib .. ".lua", lib} for _,o in ipairs(opt) do if fs.exists(o) then return miniOS.runfile(o) end end error("Can't find the library specified: `" .. lib .. "`", 3) end function require(lib) return _G[lib] or _G[string.lower(lib)] or tryrunlib(lib) end local function shellrun(...) local success = miniOS.saferunfile(...)[1] if not success then printErr("\n\nError in running command interpreter.") return false end return true end miniOS.freeMem = computer.freeMemory() --start command and keep it running. local fallback_drive = fs.drive.getcurrent() if filesystem.exists("autoexec.bat") then shellrun("command.lua", "autoexec.bat") else shellrun("command.lua") end while true do miniOS.freeMem = computer.freeMemory() print() fs.drive.setcurrent(fallback_drive) if not shellrun("command.lua", "-c") then printErr("Will restart command interpreter..."); kernelError() end end