diff --git a/startup b/startup index 0c29997..917ddab 100644 --- a/startup +++ b/startup @@ -4,67 +4,67 @@ local settings = _G.settings local term = _G.term local bootOptions = { - { prompt = os.version() }, - { prompt = 'Opus' , args = { '/sys/boot/opus.boot' } }, - { prompt = 'Opus Shell' , args = { '/sys/boot/opus.boot', 'sys/apps/shell' } }, + { prompt = os.version() }, + { prompt = 'Opus' , args = { '/sys/boot/opus.boot' } }, + { prompt = 'Opus Shell' , args = { '/sys/boot/opus.boot', 'sys/apps/shell' } }, } local bootOption = 2 if settings then - settings.load('.settings') - bootOption = tonumber(settings.get('opus.boot_option') or 2) or 2 + settings.load('.settings') + bootOption = tonumber(settings.get('opus.boot_option') or 2) or 2 end local function startupMenu() - while true do - term.clear() - term.setCursorPos(1, 1) - print('Select startup mode') - print() - for k,option in pairs(bootOptions) do - print(k .. ' : ' .. option.prompt) - end - print('') - term.write('> ') - local ch = tonumber(_G.read()) - if ch and bootOptions[ch] then - return ch - end - end + while true do + term.clear() + term.setCursorPos(1, 1) + print('Select startup mode') + print() + for k,option in pairs(bootOptions) do + print(k .. ' : ' .. option.prompt) + end + print('') + term.write('> ') + local ch = tonumber(_G.read()) + if ch and bootOptions[ch] then + return ch + end + end end local function splash() - local w, h = term.current().getSize() + local w, h = term.current().getSize() - term.setTextColor(colors.white) - if not term.isColor() then - local str = 'Opus OS' - term.setCursorPos((w - #str) / 2, h / 2) - term.write(str) - else - term.setBackgroundColor(colors.black) - term.clear() - local opus = { - 'fffff00', - 'ffff07000', - 'ff00770b00 4444', - 'ff077777444444444', - 'f07777744444444444', - 'f0000777444444444', - '070000111744444', - '777770000', - '7777000000', - '70700000000', - '077000000000', - } - for k,line in ipairs(opus) do - term.setCursorPos((w - 18) / 2, k + (h - #opus) / 2) - term.blit(string.rep(' ', #line), string.rep('a', #line), line) - end - end + term.setTextColor(colors.white) + if not term.isColor() then + local str = 'Opus OS' + term.setCursorPos((w - #str) / 2, h / 2) + term.write(str) + else + term.setBackgroundColor(colors.black) + term.clear() + local opus = { + 'fffff00', + 'ffff07000', + 'ff00770b00 4444', + 'ff077777444444444', + 'f07777744444444444', + 'f0000777444444444', + '070000111744444', + '777770000', + '7777000000', + '70700000000', + '077000000000', + } + for k,line in ipairs(opus) do + term.setCursorPos((w - 18) / 2, k + (h - #opus) / 2) + term.blit(string.rep(' ', #line), string.rep('a', #line), line) + end + end - local str = 'Press any key for menu' - term.setCursorPos((w - #str) / 2, h) - term.write(str) + local str = 'Press any key for menu' + term.setCursorPos((w - #str) / 2, h) + term.write(str) end term.clear() @@ -72,24 +72,25 @@ splash() local timerId = os.startTimer(1.5) while true do - local e, id = os.pullEvent() - if e == 'timer' and id == timerId then - break - end - if e == 'char' then - bootOption = startupMenu() - if settings then - settings.set('opus.boot_option', bootOption) - settings.save('.settings') - end - break - end + local e, id = os.pullEvent() + if e == 'timer' and id == timerId then + break + end + if e == 'char' then + bootOption = startupMenu() + if settings then + settings.set('opus.boot_option', bootOption) + settings.save('.settings') + end + break + end end term.clear() term.setCursorPos(1, 1) if bootOptions[bootOption].args then - os.run(_G.getfenv(1), table.unpack(bootOptions[bootOption].args)) + os.run(_G.getfenv(1), table.unpack(bootOptions[bootOption].args)) else - print(bootOptions[bootOption].prompt) + print(bootOptions[bootOption].prompt) end + diff --git a/sys/apis/ansi.lua b/sys/apis/ansi.lua index b037e07..fd87b39 100644 --- a/sys/apis/ansi.lua +++ b/sys/apis/ansi.lua @@ -1,55 +1,55 @@ local Ansi = setmetatable({ }, { - __call = function(_, ...) - local str = '\027[' - for k,v in ipairs({ ...}) do - if k == 1 then - str = str .. v - else - str = str .. ';' .. v - end - end - return str .. 'm' - end + __call = function(_, ...) + local str = '\027[' + for k,v in ipairs({ ...}) do + if k == 1 then + str = str .. v + else + str = str .. ';' .. v + end + end + return str .. 'm' + end }) Ansi.codes = { - reset = 0, - white = 1, - orange = 2, - magenta = 3, - lightBlue = 4, - yellow = 5, - lime = 6, - pink = 7, - gray = 8, - lightGray = 9, - cyan = 10, - purple = 11, - blue = 12, - brown = 13, - green = 14, - red = 15, - black = 16, - onwhite = 21, - onorange = 22, - onmagenta = 23, - onlightBlue = 24, - onyellow = 25, - onlime = 26, - onpink = 27, - ongray = 28, - onlightGray = 29, - oncyan = 30, - onpurple = 31, - onblue = 32, - onbrown = 33, - ongreen = 34, - onred = 35, - onblack = 36, + reset = 0, + white = 1, + orange = 2, + magenta = 3, + lightBlue = 4, + yellow = 5, + lime = 6, + pink = 7, + gray = 8, + lightGray = 9, + cyan = 10, + purple = 11, + blue = 12, + brown = 13, + green = 14, + red = 15, + black = 16, + onwhite = 21, + onorange = 22, + onmagenta = 23, + onlightBlue = 24, + onyellow = 25, + onlime = 26, + onpink = 27, + ongray = 28, + onlightGray = 29, + oncyan = 30, + onpurple = 31, + onblue = 32, + onbrown = 33, + ongreen = 34, + onred = 35, + onblack = 36, } for k,v in pairs(Ansi.codes) do - Ansi[k] = Ansi(v) + Ansi[k] = Ansi(v) end return Ansi diff --git a/sys/apis/class.lua b/sys/apis/class.lua index 0466bc8..f01a9e0 100644 --- a/sys/apis/class.lua +++ b/sys/apis/class.lua @@ -4,43 +4,43 @@ -- class.lua -- Compatible with Lua 5.1 (not 5.0). return function(base) - local c = { } -- a new class instance - if type(base) == 'table' then - -- our new class is a shallow copy of the base class! - for i,v in pairs(base) do - c[i] = v - end - c._base = base - end - -- the class will be the metatable for all its objects, - -- and they will look up their methods in it. - c.__index = c + local c = { } -- a new class instance + if type(base) == 'table' then + -- our new class is a shallow copy of the base class! + for i,v in pairs(base) do + c[i] = v + end + c._base = base + end + -- the class will be the metatable for all its objects, + -- and they will look up their methods in it. + c.__index = c - -- expose a constructor which can be called by () - setmetatable(c, { - __call = function(class_tbl, ...) - local obj = { } - setmetatable(obj,c) - if class_tbl.init then - class_tbl.init(obj, ...) - else - -- make sure that any stuff from the base class is initialized! - if base and base.init then - base.init(obj, ...) - end - end - return obj - end - }) + -- expose a constructor which can be called by () + setmetatable(c, { + __call = function(class_tbl, ...) + local obj = { } + setmetatable(obj,c) + if class_tbl.init then + class_tbl.init(obj, ...) + else + -- make sure that any stuff from the base class is initialized! + if base and base.init then + base.init(obj, ...) + end + end + return obj + end + }) - c.is_a = - function(self, klass) - local m = getmetatable(self) - while m do - if m == klass then return true end - m = m._base - end - return false - end - return c + c.is_a = + function(self, klass) + local m = getmetatable(self) + while m do + if m == klass then return true end + m = m._base + end + return false + end + return c end diff --git a/sys/apis/config.lua b/sys/apis/config.lua index 519349f..52ce0c0 100644 --- a/sys/apis/config.lua +++ b/sys/apis/config.lua @@ -9,13 +9,13 @@ function Config.load(fname, data) local filename = 'usr/config/' .. fname if not fs.exists('usr/config') then - fs.makeDir('usr/config') + fs.makeDir('usr/config') end if not fs.exists(filename) then - Util.writeTable(filename, data) + Util.writeTable(filename, data) else - Util.merge(data, Util.readTable(filename) or { }) + Util.merge(data, Util.readTable(filename) or { }) end end @@ -23,17 +23,17 @@ function Config.loadWithCheck(fname, data) local filename = 'usr/config/' .. fname if not fs.exists(filename) then - Config.load(fname, data) - print() - print('The configuration file has been created.') - print('The file name is: ' .. filename) - print() - _G.printError('Press enter to configure') - _G.read() - shell.run('edit ' .. filename) + Config.load(fname, data) + print() + print('The configuration file has been created.') + print('The file name is: ' .. filename) + print() + _G.printError('Press enter to configure') + _G.read() + shell.run('edit ' .. filename) end - Config.load(fname, data) + Config.load(fname, data) end function Config.update(fname, data) diff --git a/sys/apis/crypto.lua b/sys/apis/crypto.lua index 4de464a..fc1075b 100644 --- a/sys/apis/crypto.lua +++ b/sys/apis/crypto.lua @@ -3,148 +3,148 @@ local Crypto = { } local function serialize(t) - local sType = type(t) - if sType == "table" then - local lstcnt=0 - for k,v in pairs(t) do - lstcnt = lstcnt + 1 - end - local result = "{" - local aset=1 - for k,v in pairs(t) do - if k==aset then - result = result..serialize(v).."," - aset=aset+1 - else - result = result..("["..serialize(k).."]="..serialize(v)..",") - end - end - result = result.."}" - return result - elseif sType == "string" then - return string.format("%q",t) - elseif sType == "number" or sType == "boolean" or sType == "nil" then - return tostring(t) - elseif sType == "function" then - local status,data=pcall(string.dump,t) - if status then - data2="" - for char in string.gmatch(data,".") do - data2=data2..zfill(string.byte(char)) - end - return 'f("'..data2..'")' - else - error("Invalid function: "..data) - end - else - error("Could not serialize type "..sType..".") - end + local sType = type(t) + if sType == "table" then + local lstcnt=0 + for k,v in pairs(t) do + lstcnt = lstcnt + 1 + end + local result = "{" + local aset=1 + for k,v in pairs(t) do + if k==aset then + result = result..serialize(v).."," + aset=aset+1 + else + result = result..("["..serialize(k).."]="..serialize(v)..",") + end + end + result = result.."}" + return result + elseif sType == "string" then + return string.format("%q",t) + elseif sType == "number" or sType == "boolean" or sType == "nil" then + return tostring(t) + elseif sType == "function" then + local status,data=pcall(string.dump,t) + if status then + data2="" + for char in string.gmatch(data,".") do + data2=data2..zfill(string.byte(char)) + end + return 'f("'..data2..'")' + else + error("Invalid function: "..data) + end + else + error("Could not serialize type "..sType..".") + end end local function unserialize( s ) - local func, e = loadstring( "return "..s, "serialize" ) - if not func then - return s,e - else - setfenv( func, { - f=function(S) - return loadstring(splitnum(S)) - end, - }) - return func() - end + local func, e = loadstring( "return "..s, "serialize" ) + if not func then + return s,e + else + setfenv( func, { + f=function(S) + return loadstring(splitnum(S)) + end, + }) + return func() + end end local function splitnum(S) - local Out="" - for l1=1,#S,2 do - local l2=(#S-l1)+1 - local function sure(N,n) - if (l2-n)<1 then N="0" end - return N - end - local CNum=tonumber("0x"..sure(string.sub(S,l2-1,l2-1),1) .. sure(string.sub(S,l2,l2),0)) - Out=string.char(CNum)..Out - end - return Out + local Out="" + for l1=1,#S,2 do + local l2=(#S-l1)+1 + local function sure(N,n) + if (l2-n)<1 then N="0" end + return N + end + local CNum=tonumber("0x"..sure(string.sub(S,l2-1,l2-1),1) .. sure(string.sub(S,l2,l2),0)) + Out=string.char(CNum)..Out + end + return Out end local function zfill(N) - N=string.format("%X",N) - Zs="" - if #N==1 then - Zs="0" - end - return Zs..N + N=string.format("%X",N) + Zs="" + if #N==1 then + Zs="0" + end + return Zs..N end local function wrap(N) - return N-(math.floor(N/256)*256) + return N-(math.floor(N/256)*256) end local function checksum(S) - local sum=0 - for char in string.gmatch(S,".") do - math.randomseed(string.byte(char)+sum) - sum=sum+math.random(0,9999) - end - math.randomseed(sum) - return sum + local sum=0 + for char in string.gmatch(S,".") do + math.randomseed(string.byte(char)+sum) + sum=sum+math.random(0,9999) + end + math.randomseed(sum) + return sum end local function genkey(len,psw) - checksum(psw) - local key={} - local tKeys={} - for l1=1,len do - local num=math.random(1,len) - while tKeys[num] do - num=math.random(1,len) - end - tKeys[num]=true - key[l1]={num,math.random(0,255)} - end - return key + checksum(psw) + local key={} + local tKeys={} + for l1=1,len do + local num=math.random(1,len) + while tKeys[num] do + num=math.random(1,len) + end + tKeys[num]=true + key[l1]={num,math.random(0,255)} + end + return key end function Crypto.encrypt(data,psw) - data=serialize(data) - local chs=checksum(data) - local key=genkey(#data,psw) - local out={} - local cnt=1 - for char in string.gmatch(data,".") do - table.insert(out,key[cnt][1],zfill(wrap(string.byte(char)+key[cnt][2])),chars) - cnt=cnt+1 - end - return string.sub(serialize({chs,table.concat(out)}),2,-3) + data=serialize(data) + local chs=checksum(data) + local key=genkey(#data,psw) + local out={} + local cnt=1 + for char in string.gmatch(data,".") do + table.insert(out,key[cnt][1],zfill(wrap(string.byte(char)+key[cnt][2])),chars) + cnt=cnt+1 + end + return string.sub(serialize({chs,table.concat(out)}),2,-3) end function Crypto.decrypt(data,psw) - local oData=data - data=unserialize("{"..data.."}") - if type(data)~="table" then - return oData - end - local chs=data[1] - data=data[2] - local key=genkey((#data)/2,psw) - local sKey={} - for k,v in pairs(key) do - sKey[v[1]]={k,v[2]} - end - local str=splitnum(data) - local cnt=1 - local out={} - for char in string.gmatch(str,".") do - table.insert(out,sKey[cnt][1],string.char(wrap(string.byte(char)-sKey[cnt][2]))) - cnt=cnt+1 - end - out=table.concat(out) - if checksum(out or "")==chs then - return unserialize(out) - end - return oData,out,chs + local oData=data + data=unserialize("{"..data.."}") + if type(data)~="table" then + return oData + end + local chs=data[1] + data=data[2] + local key=genkey((#data)/2,psw) + local sKey={} + for k,v in pairs(key) do + sKey[v[1]]={k,v[2]} + end + local str=splitnum(data) + local cnt=1 + local out={} + for char in string.gmatch(str,".") do + table.insert(out,sKey[cnt][1],string.char(wrap(string.byte(char)-sKey[cnt][2]))) + cnt=cnt+1 + end + out=table.concat(out) + if checksum(out or "")==chs then + return unserialize(out) + end + return oData,out,chs end return Crypto diff --git a/sys/apis/entry.lua b/sys/apis/entry.lua new file mode 100644 index 0000000..992e967 --- /dev/null +++ b/sys/apis/entry.lua @@ -0,0 +1,151 @@ +local class = require('class') + +local os = _G.os + +local Entry = class() + +function Entry:init(args) + self.pos = 0 + self.scroll = 0 + self.value = '' + self.width = args.width + self.limit = 1024 +end + +function Entry:reset() + self.pos = 0 + self.scroll = 0 + self.value = '' +end + +local function nextWord(line, cx) + local result = { line:find("(%w+)", cx) } + if #result > 1 and result[2] > cx then + return result[2] + 1 + elseif #result > 0 and result[1] == cx then + result = { line:find("(%w+)", result[2] + 1) } + if #result > 0 then + return result[1] + end + end +end + +function Entry:updateScroll() + if self.pos - self.scroll > self.width then + self.scroll = self.pos - (self.width) + elseif self.pos < self.scroll then + self.scroll = self.pos + end +end + +function Entry:process(ie) + local updated = false + + if ie.code == 'left' then + if self.pos > 0 then + self.pos = math.max(self.pos - 1, 0) + updated = true + end + + elseif ie.code == 'right' then + local input = tostring(self.value) + if self.pos < #input then + self.pos = math.min(self.pos + 1, #input) + updated = true + end + + elseif ie.code == 'home' then + if self.pos ~= 0 then + self.pos = 0 + updated = true + end + + elseif ie.code == 'end' then + if self.pos ~= #tostring(self.value) then + self.pos = #tostring(self.value) + updated = true + end + + elseif ie.code == 'backspace' then + if self.pos > 0 then + local input = tostring(self.value) + self.value = input:sub(1, self.pos - 1) .. input:sub(self.pos + 1) + self.pos = self.pos - 1 + updated = true + end + + elseif ie.code == 'control-right' then + local nx = nextWord(self.value, self.pos + 1) + if nx then + self.pos = math.min(nx - 1, #self.value) + elseif self.pos < #self.value then + self.pos = #self.value + end + updated = true + + elseif ie.code == 'control-left' then + if self.pos ~= 0 then + local lx = 1 + while true do + local nx = nextWord(self.value, lx) + if not nx or nx >= self.pos then + break + end + lx = nx + end + if not lx then + self.pos = 0 + else + self.pos = lx - 1 + end + updated = true + end + + elseif ie.code == 'delete' then + local input = tostring(self.value) + if self.pos < #input then + self.value = input:sub(1, self.pos) .. input:sub(self.pos + 2) + self.update = true + updated = true + end + + elseif ie.code == 'char' then + local input = tostring(self.value) + if #input < self.limit then + self.value = input:sub(1, self.pos) .. ie.ch .. input:sub(self.pos + 1) + self.pos = self.pos + 1 + self.update = true + updated = true + end + + elseif ie.code == 'copy' then + os.queueEvent('clipboard_copy', self.value) + + elseif ie.code == 'paste' then + local input = tostring(self.value) + if #input + #ie.text > self.limit then + ie.text = ie.text:sub(1, self.limit-#input) + end + self.value = input:sub(1, self.pos) .. ie.text .. input:sub(self.pos + 1) + self.pos = self.pos + #ie.text + updated = true + + elseif ie.code == 'mouse_click' then + -- need starting x passed in instead of hardcoding 3 + self.pos = math.min(ie.x - 3 + self.scroll, #self.value) + updated = true + + elseif ie.code == 'mouse_rightclick' then + local input = tostring(self.value) + if #input > 0 then + self:reset() + updated = true + end + end + + self:updateScroll() + + return updated +end + +return Entry diff --git a/sys/apis/event.lua b/sys/apis/event.lua index 2bfac8e..fda4946 100644 --- a/sys/apis/event.lua +++ b/sys/apis/event.lua @@ -1,223 +1,223 @@ local os = _G.os local Event = { - uid = 1, -- unique id for handlers - routines = { }, -- coroutines - types = { }, -- event handlers - timers = { }, -- named timers - terminate = false, + uid = 1, -- unique id for handlers + routines = { }, -- coroutines + types = { }, -- event handlers + timers = { }, -- named timers + terminate = false, } local Routine = { } function Routine:isDead() - if not self.co then - return true - end - return coroutine.status(self.co) == 'dead' + if not self.co then + return true + end + return coroutine.status(self.co) == 'dead' end function Routine:terminate() - if self.co then - self:resume('terminate') - end + if self.co then + self:resume('terminate') + end end function Routine:resume(event, ...) - --if coroutine.status(self.co) == 'running' then - --return - --end + --if coroutine.status(self.co) == 'running' then + --return + --end - if not self.co then - error('Cannot resume a dead routine') - end + if not self.co then + error('Cannot resume a dead routine') + end - if not self.filter or self.filter == event or event == "terminate" then - local s, m = coroutine.resume(self.co, event, ...) + if not self.filter or self.filter == event or event == "terminate" then + local s, m = coroutine.resume(self.co, event, ...) - if coroutine.status(self.co) == 'dead' then - self.co = nil - self.filter = nil - Event.routines[self.uid] = nil - else - self.filter = m - end + if coroutine.status(self.co) == 'dead' then + self.co = nil + self.filter = nil + Event.routines[self.uid] = nil + else + self.filter = m + end - if not s and event ~= 'terminate' then - error('\n' .. (m or 'Error processing event')) - end + if not s and event ~= 'terminate' then + error('\n' .. (m or 'Error processing event')) + end - return s, m - end + return s, m + end - return true, self.filter + return true, self.filter end local function nextUID() - Event.uid = Event.uid + 1 - return Event.uid - 1 + Event.uid = Event.uid + 1 + return Event.uid - 1 end function Event.on(events, fn) - events = type(events) == 'table' and events or { events } + events = type(events) == 'table' and events or { events } - local handler = setmetatable({ - uid = nextUID(), - event = events, - fn = fn, - }, { __index = Routine }) + local handler = setmetatable({ + uid = nextUID(), + event = events, + fn = fn, + }, { __index = Routine }) - for _,event in pairs(events) do - local handlers = Event.types[event] - if not handlers then - handlers = { } - Event.types[event] = handlers - end + for _,event in pairs(events) do + local handlers = Event.types[event] + if not handlers then + handlers = { } + Event.types[event] = handlers + end - handlers[handler.uid] = handler - end + handlers[handler.uid] = handler + end - return handler + return handler end function Event.off(h) - if h and h.event then - for _,event in pairs(h.event) do - Event.types[event][h.uid] = nil - end - end + if h and h.event then + for _,event in pairs(h.event) do + Event.types[event][h.uid] = nil + end + end end local function addTimer(interval, recurring, fn) - local timerId = os.startTimer(interval) - local handler + local timerId = os.startTimer(interval) + local handler - handler = Event.on('timer', function(t, id) - if timerId == id then - fn(t, id) - if recurring then - timerId = os.startTimer(interval) - else - Event.off(handler) - end - end - end) + handler = Event.on('timer', function(t, id) + if timerId == id then + fn(t, id) + if recurring then + timerId = os.startTimer(interval) + else + Event.off(handler) + end + end + end) - return handler + return handler end function Event.onInterval(interval, fn) - return addTimer(interval, true, fn) + return addTimer(interval, true, fn) end function Event.onTimeout(timeout, fn) - return addTimer(timeout, false, fn) + return addTimer(timeout, false, fn) end function Event.addNamedTimer(name, interval, recurring, fn) - Event.cancelNamedTimer(name) - Event.timers[name] = addTimer(interval, recurring, fn) + Event.cancelNamedTimer(name) + Event.timers[name] = addTimer(interval, recurring, fn) end function Event.cancelNamedTimer(name) - local timer = Event.timers[name] - if timer then - Event.off(timer) - end + local timer = Event.timers[name] + if timer then + Event.off(timer) + end end function Event.waitForEvent(event, timeout) - local timerId = os.startTimer(timeout) - repeat - local e = { os.pullEvent() } - if e[1] == event then - return table.unpack(e) - end - until e[1] == 'timer' and e[2] == timerId + local timerId = os.startTimer(timeout) + repeat + local e = { os.pullEvent() } + if e[1] == event then + return table.unpack(e) + end + until e[1] == 'timer' and e[2] == timerId end function Event.addRoutine(fn) - local r = setmetatable({ - co = coroutine.create(fn), - uid = nextUID() - }, { __index = Routine }) + local r = setmetatable({ + co = coroutine.create(fn), + uid = nextUID() + }, { __index = Routine }) - Event.routines[r.uid] = r - r:resume() + Event.routines[r.uid] = r + r:resume() - return r + return r end function Event.pullEvents(...) - for _, fn in ipairs({ ... }) do - Event.addRoutine(fn) - end + for _, fn in ipairs({ ... }) do + Event.addRoutine(fn) + end - repeat - Event.pullEvent() - until Event.terminate + repeat + Event.pullEvent() + until Event.terminate - Event.terminate = false + Event.terminate = false end function Event.exitPullEvents() - Event.terminate = true - os.sleep(0) + Event.terminate = true + os.sleep(0) end local function processHandlers(event) - local handlers = Event.types[event] - if handlers then - for _,h in pairs(handlers) do - if not h.co then - -- callbacks are single threaded (only 1 co per handler) - h.co = coroutine.create(h.fn) - Event.routines[h.uid] = h - end - end - end + local handlers = Event.types[event] + if handlers then + for _,h in pairs(handlers) do + if not h.co then + -- callbacks are single threaded (only 1 co per handler) + h.co = coroutine.create(h.fn) + Event.routines[h.uid] = h + end + end + end end local function tokeys(t) - local keys = { } - for k in pairs(t) do - keys[#keys+1] = k - end - return keys + local keys = { } + for k in pairs(t) do + keys[#keys+1] = k + end + return keys end local function processRoutines(...) - local keys = tokeys(Event.routines) - for _,key in ipairs(keys) do - local r = Event.routines[key] - if r then - r:resume(...) - end - end + local keys = tokeys(Event.routines) + for _,key in ipairs(keys) do + local r = Event.routines[key] + if r then + r:resume(...) + end + end end function Event.processEvent(e) - processHandlers(e[1]) - processRoutines(table.unpack(e)) + processHandlers(e[1]) + processRoutines(table.unpack(e)) end function Event.pullEvent(eventType) - while true do - local e = { os.pullEventRaw() } + while true do + local e = { os.pullEventRaw() } - Event.terminate = Event.terminate or e[1] == 'terminate' + Event.terminate = Event.terminate or e[1] == 'terminate' - processHandlers(e[1]) - processRoutines(table.unpack(e)) + processHandlers(e[1]) + processRoutines(table.unpack(e)) - if Event.terminate then - return { 'terminate' } - end + if Event.terminate then + return { 'terminate' } + end - if not eventType or e[1] == eventType then - return e - end - end + if not eventType or e[1] == eventType then + return e + end + end end return Event diff --git a/sys/apis/fs/gitfs.lua b/sys/apis/fs/gitfs.lua index 8b54d4a..026d9e8 100644 --- a/sys/apis/fs/gitfs.lua +++ b/sys/apis/fs/gitfs.lua @@ -5,17 +5,17 @@ local fs = _G.fs local gitfs = { } function gitfs.mount(dir, repo) - if not repo then - error('gitfs syntax: repo') - end + if not repo then + error('gitfs syntax: repo') + end - local list = git.list(repo) - for path, entry in pairs(list) do - if not fs.exists(fs.combine(dir, path)) then - local node = fs.mount(fs.combine(dir, path), 'urlfs', entry.url) - node.size = entry.size - end - end + local list = git.list(repo) + for path, entry in pairs(list) do + if not fs.exists(fs.combine(dir, path)) then + local node = fs.mount(fs.combine(dir, path), 'urlfs', entry.url) + node.size = entry.size + end + end end return gitfs diff --git a/sys/apis/fs/linkfs.lua b/sys/apis/fs/linkfs.lua index f497947..e46342c 100644 --- a/sys/apis/fs/linkfs.lua +++ b/sys/apis/fs/linkfs.lua @@ -3,61 +3,61 @@ local fs = _G.fs local linkfs = { } local methods = { 'exists', 'getFreeSpace', 'getSize', - 'isDir', 'isReadOnly', 'list', 'listEx', 'makeDir', 'open', 'getDrive' } + 'isDir', 'isReadOnly', 'list', 'listEx', 'makeDir', 'open', 'getDrive' } for _,m in pairs(methods) do - linkfs[m] = function(node, dir, ...) - dir = dir:gsub(node.mountPoint, node.source, 1) - return fs[m](dir, ...) - end + linkfs[m] = function(node, dir, ...) + dir = dir:gsub(node.mountPoint, node.source, 1) + return fs[m](dir, ...) + end end function linkfs.mount(_, source) - if not source then - error('Source is required') - end - source = fs.combine(source, '') - if fs.isDir(source) then - return { - source = source, - nodes = { }, - } - end - return { - source = source - } + if not source then + error('Source is required') + end + source = fs.combine(source, '') + if fs.isDir(source) then + return { + source = source, + nodes = { }, + } + end + return { + source = source + } end function linkfs.copy(node, s, t) - s = s:gsub(node.mountPoint, node.source, 1) - t = t:gsub(node.mountPoint, node.source, 1) - return fs.copy(s, t) + s = s:gsub(node.mountPoint, node.source, 1) + t = t:gsub(node.mountPoint, node.source, 1) + return fs.copy(s, t) end function linkfs.delete(node, dir) - if dir == node.mountPoint then - fs.unmount(node.mountPoint) - else - dir = dir:gsub(node.mountPoint, node.source, 1) - return fs.delete(dir) - end + if dir == node.mountPoint then + fs.unmount(node.mountPoint) + else + dir = dir:gsub(node.mountPoint, node.source, 1) + return fs.delete(dir) + end end function linkfs.find(node, spec) - spec = spec:gsub(node.mountPoint, node.source, 1) + spec = spec:gsub(node.mountPoint, node.source, 1) - local list = fs.find(spec) - for k,f in ipairs(list) do - list[k] = f:gsub(node.source, node.mountPoint, 1) - end + local list = fs.find(spec) + for k,f in ipairs(list) do + list[k] = f:gsub(node.source, node.mountPoint, 1) + end - return list + return list end function linkfs.move(node, s, t) - s = s:gsub(node.mountPoint, node.source, 1) - t = t:gsub(node.mountPoint, node.source, 1) - return fs.move(s, t) + s = s:gsub(node.mountPoint, node.source, 1) + t = t:gsub(node.mountPoint, node.source, 1) + return fs.move(s, t) end return linkfs diff --git a/sys/apis/fs/netfs.lua b/sys/apis/fs/netfs.lua index c9f8253..58089a4 100644 --- a/sys/apis/fs/netfs.lua +++ b/sys/apis/fs/netfs.lua @@ -7,159 +7,159 @@ local netfs = { } local function remoteCommand(node, msg) - for _ = 1, 2 do - if not node.socket then - node.socket = Socket.connect(node.id, 139) - end + for _ = 1, 2 do + if not node.socket then + node.socket = Socket.connect(node.id, 139) + end - if not node.socket then - error('netfs: Unable to establish connection to ' .. node.id) - fs.unmount(node.mountPoint) - return - end + if not node.socket then + error('netfs: Unable to establish connection to ' .. node.id) + fs.unmount(node.mountPoint) + return + end - local ret - synchronized(node.socket, function() - node.socket:write(msg) - ret = node.socket:read(1) - end) + local ret + synchronized(node.socket, function() + node.socket:write(msg) + ret = node.socket:read(1) + end) - if ret then - return ret.response - end - node.socket:close() - node.socket = nil - end - error('netfs: Connection failed', 2) + if ret then + return ret.response + end + node.socket:close() + node.socket = nil + end + error('netfs: Connection failed', 2) end local methods = { 'delete', 'exists', 'getFreeSpace', 'makeDir', 'list', 'listEx' } local function resolveDir(dir, node) - dir = dir:gsub(node.mountPoint, '', 1) - return fs.combine(node.directory, dir) + dir = dir:gsub(node.mountPoint, '', 1) + return fs.combine(node.directory, dir) end for _,m in pairs(methods) do - netfs[m] = function(node, dir) - dir = resolveDir(dir, node) + netfs[m] = function(node, dir) + dir = resolveDir(dir, node) - return remoteCommand(node, { - fn = m, - args = { dir }, - }) - end + return remoteCommand(node, { + fn = m, + args = { dir }, + }) + end end function netfs.mount(_, id, directory) - if not id or not tonumber(id) then - error('ramfs syntax: computerId [directory]') - end - return { - id = tonumber(id), - nodes = { }, - directory = directory or '', - } + if not id or not tonumber(id) then + error('ramfs syntax: computerId [directory]') + end + return { + id = tonumber(id), + nodes = { }, + directory = directory or '', + } end function netfs.getDrive() - return 'net' + return 'net' end function netfs.complete(node, partial, dir, includeFiles, includeSlash) - dir = resolveDir(dir, node) + dir = resolveDir(dir, node) - return remoteCommand(node, { - fn = 'complete', - args = { partial, dir, includeFiles, includeSlash }, - }) + return remoteCommand(node, { + fn = 'complete', + args = { partial, dir, includeFiles, includeSlash }, + }) end function netfs.copy(node, s, t) - s = resolveDir(s, node) - t = resolveDir(t, node) + s = resolveDir(s, node) + t = resolveDir(t, node) - return remoteCommand(node, { - fn = 'copy', - args = { s, t }, - }) + return remoteCommand(node, { + fn = 'copy', + args = { s, t }, + }) end function netfs.isDir(node, dir) - if dir == node.mountPoint and node.directory == '' then - return true - end - return remoteCommand(node, { - fn = 'isDir', - args = { resolveDir(dir, node) }, - }) + if dir == node.mountPoint and node.directory == '' then + return true + end + return remoteCommand(node, { + fn = 'isDir', + args = { resolveDir(dir, node) }, + }) end function netfs.isReadOnly(node, dir) - if dir == node.mountPoint and node.directory == '' then - return false - end - return remoteCommand(node, { - fn = 'isReadOnly', - args = { resolveDir(dir, node) }, - }) + if dir == node.mountPoint and node.directory == '' then + return false + end + return remoteCommand(node, { + fn = 'isReadOnly', + args = { resolveDir(dir, node) }, + }) end function netfs.getSize(node, dir) - if dir == node.mountPoint and node.directory == '' then - return 0 - end - return remoteCommand(node, { - fn = 'getSize', - args = { resolveDir(dir, node) }, - }) + if dir == node.mountPoint and node.directory == '' then + return 0 + end + return remoteCommand(node, { + fn = 'getSize', + args = { resolveDir(dir, node) }, + }) end function netfs.find(node, spec) - spec = resolveDir(spec, node) - local list = remoteCommand(node, { - fn = 'find', - args = { spec }, - }) + spec = resolveDir(spec, node) + local list = remoteCommand(node, { + fn = 'find', + args = { spec }, + }) - for k,f in ipairs(list) do - list[k] = fs.combine(node.mountPoint, f) - end + for k,f in ipairs(list) do + list[k] = fs.combine(node.mountPoint, f) + end - return list + return list end function netfs.move(node, s, t) - s = resolveDir(s, node) - t = resolveDir(t, node) + s = resolveDir(s, node) + t = resolveDir(t, node) - return remoteCommand(node, { - fn = 'move', - args = { s, t }, - }) + return remoteCommand(node, { + fn = 'move', + args = { s, t }, + }) end function netfs.open(node, fn, fl) - fn = resolveDir(fn, node) + fn = resolveDir(fn, node) - local vfh = remoteCommand(node, { - fn = 'open', - args = { fn, fl }, - }) + local vfh = remoteCommand(node, { + fn = 'open', + args = { fn, fl }, + }) - if vfh then - vfh.node = node - for _,m in ipairs(vfh.methods) do - vfh[m] = function(...) - return remoteCommand(node, { - fn = 'fileOp', - args = { vfh.fileUid, m, ... }, - }) - end - end - end + if vfh then + vfh.node = node + for _,m in ipairs(vfh.methods) do + vfh[m] = function(...) + return remoteCommand(node, { + fn = 'fileOp', + args = { vfh.fileUid, m, ... }, + }) + end + end + end - return vfh + return vfh end return netfs diff --git a/sys/apis/fs/ramfs.lua b/sys/apis/fs/ramfs.lua index b4376c9..5dc7470 100644 --- a/sys/apis/fs/ramfs.lua +++ b/sys/apis/fs/ramfs.lua @@ -5,143 +5,143 @@ local fs = _G.fs local ramfs = { } function ramfs.mount(_, nodeType) - if nodeType == 'directory' then - return { - nodes = { }, - size = 0, - } - elseif nodeType == 'file' then - return { - size = 0, - } - end - error('ramfs syntax: [directory, file]') + if nodeType == 'directory' then + return { + nodes = { }, + size = 0, + } + elseif nodeType == 'file' then + return { + size = 0, + } + end + error('ramfs syntax: [directory, file]') end function ramfs.delete(node, dir) - if node.mountPoint == dir then - fs.unmount(node.mountPoint) - end + if node.mountPoint == dir then + fs.unmount(node.mountPoint) + end end function ramfs.exists(node, fn) - return node.mountPoint == fn + return node.mountPoint == fn end function ramfs.getSize(node) - return node.size + return node.size end function ramfs.isReadOnly() - return false + return false end function ramfs.makeDir(_, dir) - fs.mount(dir, 'ramfs', 'directory') + fs.mount(dir, 'ramfs', 'directory') end function ramfs.isDir(node) - return not not node.nodes + return not not node.nodes end function ramfs.getDrive() - return 'ram' + return 'ram' end function ramfs.list(node, dir) - if node.nodes and node.mountPoint == dir then - local files = { } - for k in pairs(node.nodes) do - table.insert(files, k) - end - return files - end - error('Not a directory') + if node.nodes and node.mountPoint == dir then + local files = { } + for k in pairs(node.nodes) do + table.insert(files, k) + end + return files + end + error('Not a directory') end function ramfs.open(node, fn, fl) - if fl ~= 'r' and fl ~= 'w' and fl ~= 'rb' and fl ~= 'wb' then - error('Unsupported mode') - end + if fl ~= 'r' and fl ~= 'w' and fl ~= 'rb' and fl ~= 'wb' then + error('Unsupported mode') + end - if fl == 'r' then - if node.mountPoint ~= fn then - return - end + if fl == 'r' then + if node.mountPoint ~= fn then + return + end - local ctr = 0 - local lines - return { - readLine = function() - if not lines then - lines = Util.split(node.contents) - end - ctr = ctr + 1 - return lines[ctr] - end, - readAll = function() - return node.contents - end, - close = function() - lines = nil - end, - } - elseif fl == 'w' then - node = fs.mount(fn, 'ramfs', 'file') + local ctr = 0 + local lines + return { + readLine = function() + if not lines then + lines = Util.split(node.contents) + end + ctr = ctr + 1 + return lines[ctr] + end, + readAll = function() + return node.contents + end, + close = function() + lines = nil + end, + } + elseif fl == 'w' then + node = fs.mount(fn, 'ramfs', 'file') - local c = '' - return { - write = function(str) - c = c .. str - end, - writeLine = function(str) - c = c .. str .. '\n' - end, - flush = function() - node.contents = c - node.size = #c - end, - close = function() - node.contents = c - node.size = #c - c = nil - end, - } - elseif fl == 'rb' then - if node.mountPoint ~= fn or not node.contents then - return - end + local c = '' + return { + write = function(str) + c = c .. str + end, + writeLine = function(str) + c = c .. str .. '\n' + end, + flush = function() + node.contents = c + node.size = #c + end, + close = function() + node.contents = c + node.size = #c + c = nil + end, + } + elseif fl == 'rb' then + if node.mountPoint ~= fn or not node.contents then + return + end - local ctr = 0 - return { - read = function() - ctr = ctr + 1 - return node.contents[ctr] - end, - close = function() - end, - } + local ctr = 0 + return { + read = function() + ctr = ctr + 1 + return node.contents[ctr] + end, + close = function() + end, + } - elseif fl == 'wb' then - node = fs.mount(fn, 'ramfs', 'file') + elseif fl == 'wb' then + node = fs.mount(fn, 'ramfs', 'file') - local c = { } - return { - write = function(b) - table.insert(c, b) - end, - flush = function() - node.contents = c - node.size = #c - end, - close = function() - node.contents = c - node.size = #c - c = nil - end, - } - end + local c = { } + return { + write = function(b) + table.insert(c, b) + end, + flush = function() + node.contents = c + node.size = #c + end, + close = function() + node.contents = c + node.size = #c + c = nil + end, + } + end end return ramfs diff --git a/sys/apis/fs/urlfs.lua b/sys/apis/fs/urlfs.lua index ef6d3ab..1c3c60d 100644 --- a/sys/apis/fs/urlfs.lua +++ b/sys/apis/fs/urlfs.lua @@ -5,91 +5,91 @@ local fs = _G.fs local urlfs = { } function urlfs.mount(_, url) - if not url then - error('URL is required') - end - return { - url = url, - } + if not url then + error('URL is required') + end + return { + url = url, + } end function urlfs.delete(_, dir) - fs.unmount(dir) + fs.unmount(dir) end function urlfs.exists() - return true + return true end function urlfs.getSize(node) - return node.size or 0 + return node.size or 0 end function urlfs.isReadOnly() - return true + return true end function urlfs.isDir() - return false + return false end function urlfs.getDrive() - return 'url' + return 'url' end function urlfs.open(node, fn, fl) - if fl == 'w' or fl == 'wb' then - fs.delete(fn) - return fs.open(fn, fl) - end + if fl == 'w' or fl == 'wb' then + fs.delete(fn) + return fs.open(fn, fl) + end - if fl ~= 'r' and fl ~= 'rb' then - error('Unsupported mode') - end + if fl ~= 'r' and fl ~= 'rb' then + error('Unsupported mode') + end - local c = node.cache - if not c then - c = Util.httpGet(node.url) - if c then - node.cache = c - node.size = #c - end - end + local c = node.cache + if not c then + c = Util.httpGet(node.url) + if c then + node.cache = c + node.size = #c + end + end - if not c then - return - end + if not c then + return + end - local ctr = 0 - local lines + local ctr = 0 + local lines - if fl == 'r' then - return { - readLine = function() - if not lines then - lines = Util.split(c) - end - ctr = ctr + 1 - return lines[ctr] - end, - readAll = function() - return c - end, - close = function() - lines = nil - end, - } - end - return { - read = function() - ctr = ctr + 1 - return c:sub(ctr, ctr):byte() - end, - close = function() - ctr = 0 - end, - } + if fl == 'r' then + return { + readLine = function() + if not lines then + lines = Util.split(c) + end + ctr = ctr + 1 + return lines[ctr] + end, + readAll = function() + return c + end, + close = function() + lines = nil + end, + } + end + return { + read = function() + ctr = ctr + 1 + return c:sub(ctr, ctr):byte() + end, + close = function() + ctr = 0 + end, + } end return urlfs diff --git a/sys/apis/git.lua b/sys/apis/git.lua index 6e8d9e1..67f27b3 100644 --- a/sys/apis/git.lua +++ b/sys/apis/git.lua @@ -8,42 +8,42 @@ local git = { } function git.list(repository) - local t = Util.split(repository, '(.-)/') + local t = Util.split(repository, '(.-)/') - local user = t[1] - local repo = t[2] - local branch = t[3] or 'master' + local user = t[1] + local repo = t[2] + local branch = t[3] or 'master' - local dataUrl = string.format(TREE_URL, user, repo, branch) - local contents = Util.download(dataUrl) + local dataUrl = string.format(TREE_URL, user, repo, branch) + local contents = Util.download(dataUrl) - if not contents then - error('Invalid repository') - end + if not contents then + error('Invalid repository') + end - local data = json.decode(contents) + local data = json.decode(contents) - if data.message and data.message:find("API rate limit exceeded") then - error("Out of API calls, try again later") - end + if data.message and data.message:find("API rate limit exceeded") then + error("Out of API calls, try again later") + end - if data.message and data.message == "Not found" then - error("Invalid repository") - end + if data.message and data.message == "Not found" then + error("Invalid repository") + end - local list = { } + local list = { } - for _,v in pairs(data.tree) do - if v.type == "blob" then - v.path = v.path:gsub("%s","%%20") - list[v.path] = { - url = string.format(FILE_URL, user, repo, branch, v.path), - size = v.size, - } - end - end + for _,v in pairs(data.tree) do + if v.type == "blob" then + v.path = v.path:gsub("%s","%%20") + list[v.path] = { + url = string.format(FILE_URL, user, repo, branch, v.path), + size = v.size, + } + end + end - return list + return list end return git diff --git a/sys/apis/gps.lua b/sys/apis/gps.lua index 8d60d9e..cf53d61 100644 --- a/sys/apis/gps.lua +++ b/sys/apis/gps.lua @@ -5,151 +5,151 @@ local gps = _G.gps local turtle = _G.turtle function GPS.locate(timeout, debug) - local pt = { } - timeout = timeout or 10 - pt.x, pt.y, pt.z = gps.locate(timeout, debug) - if pt.x then - return pt - end + local pt = { } + timeout = timeout or 10 + pt.x, pt.y, pt.z = gps.locate(timeout, debug) + if pt.x then + return pt + end end function GPS.isAvailable() - return device.wireless_modem and GPS.locate() + return device.wireless_modem and GPS.locate() end function GPS.getPoint(timeout, debug) - local pt = GPS.locate(timeout, debug) - if not pt then - return - end + local pt = GPS.locate(timeout, debug) + if not pt then + return + end - pt.x = math.floor(pt.x) - pt.y = math.floor(pt.y) - pt.z = math.floor(pt.z) + pt.x = math.floor(pt.x) + pt.y = math.floor(pt.y) + pt.z = math.floor(pt.z) - if _G.pocket then - pt.y = pt.y - 1 - end + if _G.pocket then + pt.y = pt.y - 1 + end - return pt + return pt end function GPS.getHeading(timeout) - if not turtle then - return - end + if not turtle then + return + end - local apt = GPS.locate(timeout) - if not apt then - return - end + local apt = GPS.locate(timeout) + if not apt then + return + end - local heading = turtle.point.heading + local heading = turtle.point.heading - while not turtle.forward() do - turtle.turnRight() - if turtle.getHeading() == heading then - _G.printError('GPS.getPoint: Unable to move forward') - return - end - end + while not turtle.forward() do + turtle.turnRight() + if turtle.getHeading() == heading then + _G.printError('GPS.getPoint: Unable to move forward') + return + end + end - local bpt = GPS.locate() - if not bpt then - return - end + local bpt = GPS.locate() + if not bpt then + return + end - if apt.x < bpt.x then - return 0 - elseif apt.z < bpt.z then - return 1 - elseif apt.x > bpt.x then - return 2 - end - return 3 + if apt.x < bpt.x then + return 0 + elseif apt.z < bpt.z then + return 1 + elseif apt.x > bpt.x then + return 2 + end + return 3 end function GPS.getPointAndHeading(timeout) - local heading = GPS.getHeading(timeout) - if heading then - local pt = GPS.getPoint() - if pt then - pt.heading = heading - end - return pt - end + local heading = GPS.getHeading(timeout) + if heading then + local pt = GPS.getPoint() + if pt then + pt.heading = heading + end + return pt + end end -- from stock gps API local function trilaterate(A, B, C) - local a2b = B.position - A.position - local a2c = C.position - A.position + local a2b = B.position - A.position + local a2c = C.position - A.position - if math.abs( a2b:normalize():dot( a2c:normalize() ) ) > 0.999 then - return - end + if math.abs( a2b:normalize():dot( a2c:normalize() ) ) > 0.999 then + return + end - local d = a2b:length() - local ex = a2b:normalize( ) - local i = ex:dot( a2c ) - local ey = (a2c - (ex * i)):normalize() - local j = ey:dot( a2c ) - local ez = ex:cross( ey ) + local d = a2b:length() + local ex = a2b:normalize( ) + local i = ex:dot( a2c ) + local ey = (a2c - (ex * i)):normalize() + local j = ey:dot( a2c ) + local ez = ex:cross( ey ) - local r1 = A.distance - local r2 = B.distance - local r3 = C.distance + local r1 = A.distance + local r2 = B.distance + local r3 = C.distance - local x = (r1*r1 - r2*r2 + d*d) / (2*d) - local y = (r1*r1 - r3*r3 - x*x + (x-i)*(x-i) + j*j) / (2*j) + local x = (r1*r1 - r2*r2 + d*d) / (2*d) + local y = (r1*r1 - r3*r3 - x*x + (x-i)*(x-i) + j*j) / (2*j) - local result = A.position + (ex * x) + (ey * y) + local result = A.position + (ex * x) + (ey * y) - local zSquared = r1*r1 - x*x - y*y - if zSquared > 0 then - local z = math.sqrt( zSquared ) - local result1 = result + (ez * z) - local result2 = result - (ez * z) + local zSquared = r1*r1 - x*x - y*y + if zSquared > 0 then + local z = math.sqrt( zSquared ) + local result1 = result + (ez * z) + local result2 = result - (ez * z) - local rounded1, rounded2 = result1:round(), result2:round() - if rounded1.x ~= rounded2.x or rounded1.y ~= rounded2.y or rounded1.z ~= rounded2.z then - return rounded1, rounded2 - else - return rounded1 - end - end - return result:round() + local rounded1, rounded2 = result1:round(), result2:round() + if rounded1.x ~= rounded2.x or rounded1.y ~= rounded2.y or rounded1.z ~= rounded2.z then + return rounded1, rounded2 + else + return rounded1 + end + end + return result:round() end local function narrow( p1, p2, fix ) - local dist1 = math.abs( (p1 - fix.position):length() - fix.distance ) - local dist2 = math.abs( (p2 - fix.position):length() - fix.distance ) + local dist1 = math.abs( (p1 - fix.position):length() - fix.distance ) + local dist2 = math.abs( (p2 - fix.position):length() - fix.distance ) - if math.abs(dist1 - dist2) < 0.05 then - return p1, p2 - elseif dist1 < dist2 then - return p1:round() - else - return p2:round() - end + if math.abs(dist1 - dist2) < 0.05 then + return p1, p2 + elseif dist1 < dist2 then + return p1:round() + else + return p2:round() + end end -- end stock gps api function GPS.trilaterate(tFixes) - local pos1, pos2 = trilaterate(tFixes[1], tFixes[2], tFixes[3]) + local pos1, pos2 = trilaterate(tFixes[1], tFixes[2], tFixes[3]) - if pos2 then - pos1, pos2 = narrow(pos1, pos2, tFixes[4]) - end + if pos2 then + pos1, pos2 = narrow(pos1, pos2, tFixes[4]) + end - if pos1 and pos2 then - print("Ambiguous position") - print("Could be "..pos1.x..","..pos1.y..","..pos1.z.." or "..pos2.x..","..pos2.y..","..pos2.z ) - return - end + if pos1 and pos2 then + print("Ambiguous position") + print("Could be "..pos1.x..","..pos1.y..","..pos1.z.." or "..pos2.x..","..pos2.y..","..pos2.z ) + return + end - return pos1 + return pos1 end return GPS \ No newline at end of file diff --git a/sys/apis/history.lua b/sys/apis/history.lua index 3bef67e..7162a48 100644 --- a/sys/apis/history.lua +++ b/sys/apis/history.lua @@ -5,46 +5,46 @@ local History_mt = { __index = History } function History.load(filename, limit) - local self = setmetatable({ - limit = limit, - filename = filename, - }, History_mt) + local self = setmetatable({ + limit = limit, + filename = filename, + }, History_mt) - self.entries = Util.readLines(filename) or { } - self.pos = #self.entries + 1 + self.entries = Util.readLines(filename) or { } + self.pos = #self.entries + 1 - return self + return self end function History:add(line) - if line ~= self.entries[#self.entries] then - table.insert(self.entries, line) - if self.limit then - while #self.entries > self.limit do - table.remove(self.entries, 1) - end - end - Util.writeLines(self.filename, self.entries) - self.pos = #self.entries + 1 - end + if line ~= self.entries[#self.entries] then + table.insert(self.entries, line) + if self.limit then + while #self.entries > self.limit do + table.remove(self.entries, 1) + end + end + Util.writeLines(self.filename, self.entries) + self.pos = #self.entries + 1 + end end function History:reset() - self.pos = #self.entries + 1 + self.pos = #self.entries + 1 end function History:back() - if self.pos > 1 then - self.pos = self.pos - 1 - return self.entries[self.pos] - end + if self.pos > 1 then + self.pos = self.pos - 1 + return self.entries[self.pos] + end end function History:forward() - if self.pos <= #self.entries then - self.pos = self.pos + 1 - return self.entries[self.pos] - end + if self.pos <= #self.entries then + self.pos = self.pos + 1 + return self.entries[self.pos] + end end return History diff --git a/sys/apis/injector.lua b/sys/apis/injector.lua index 66762ad..0b060c5 100644 --- a/sys/apis/injector.lua +++ b/sys/apis/injector.lua @@ -8,170 +8,170 @@ local http = _G.http local os = _G.os if not http._patched then - -- fix broken http get - local syncLocks = { } + -- fix broken http get + local syncLocks = { } - local function sync(obj, fn) - local key = tostring(obj) - if syncLocks[key] then - local cos = tostring(coroutine.running()) - table.insert(syncLocks[key], cos) - repeat - local _, co = os.pullEvent('sync_lock') - until co == cos - else - syncLocks[key] = { } - end - fn() - local co = table.remove(syncLocks[key], 1) - if co then - os.queueEvent('sync_lock', co) - else - syncLocks[key] = nil - end - end + local function sync(obj, fn) + local key = tostring(obj) + if syncLocks[key] then + local cos = tostring(coroutine.running()) + table.insert(syncLocks[key], cos) + repeat + local _, co = os.pullEvent('sync_lock') + until co == cos + else + syncLocks[key] = { } + end + fn() + local co = table.remove(syncLocks[key], 1) + if co then + os.queueEvent('sync_lock', co) + else + syncLocks[key] = nil + end + end - -- todo -- completely replace http.get with function that - -- checks for success on permanent redirects (minecraft 1.75 bug) + -- todo -- completely replace http.get with function that + -- checks for success on permanent redirects (minecraft 1.75 bug) - http._patched = http.get - function http.get(url, headers) - local s, m - sync(url, function() - s, m = http._patched(url, headers) - end) - return s, m - end + http._patched = http.get + function http.get(url, headers) + local s, m + sync(url, function() + s, m = http._patched(url, headers) + end) + return s, m + end end local function loadUrl(url) - local c - local h = http.get(url) - if h then - c = h.readAll() - h.close() - end - if c and #c > 0 then - return c - end + local c + local h = http.get(url) + if h then + c = h.readAll() + h.close() + end + if c and #c > 0 then + return c + end end -- Add require and package to the environment local function requireWrapper(env) - local function standardSearcher(modname) - if env.package.loaded[modname] then - return function() - return env.package.loaded[modname] - end - end - end + local function standardSearcher(modname) + if env.package.loaded[modname] then + return function() + return env.package.loaded[modname] + end + end + end - local function shellSearcher(modname) - local fname = modname:gsub('%.', '/') .. '.lua' + local function shellSearcher(modname) + local fname = modname:gsub('%.', '/') .. '.lua' - if env.shell and type(env.shell.dir) == 'function' then - local path = env.shell.resolve(fname) - if fs.exists(path) and not fs.isDir(path) then - return loadfile(path, env) - end - end - end + if env.shell and type(env.shell.dir) == 'function' then + local path = env.shell.resolve(fname) + if fs.exists(path) and not fs.isDir(path) then + return loadfile(path, env) + end + end + end - local function pathSearcher(modname) - local fname = modname:gsub('%.', '/') .. '.lua' + local function pathSearcher(modname) + local fname = modname:gsub('%.', '/') .. '.lua' - for dir in string.gmatch(env.package.path, "[^:]+") do - local path = fs.combine(dir, fname) - if fs.exists(path) and not fs.isDir(path) then - return loadfile(path, env) - end - end - end + for dir in string.gmatch(env.package.path, "[^:]+") do + local path = fs.combine(dir, fname) + if fs.exists(path) and not fs.isDir(path) then + return loadfile(path, env) + end + end + end - -- require('BniCQPVf') - local function pastebinSearcher(modname) - if #modname == 8 and not modname:match('%W') then - local url = PASTEBIN_URL .. '/' .. modname - local c = loadUrl(url) - if c then - return load(c, modname, nil, env) - end - end - end + -- require('BniCQPVf') + local function pastebinSearcher(modname) + if #modname == 8 and not modname:match('%W') then + local url = PASTEBIN_URL .. '/' .. modname + local c = loadUrl(url) + if c then + return load(c, modname, nil, env) + end + end + end - -- require('kepler155c.opus.master.sys.apis.util') - local function gitSearcher(modname) - local fname = modname:gsub('%.', '/') .. '.lua' - local _, count = fname:gsub("/", "") - if count >= 3 then - local url = GIT_URL .. '/' .. fname - local c = loadUrl(url) - if c then - return load(c, modname, nil, env) - end - end - end + -- require('kepler155c.opus.master.sys.apis.util') + local function gitSearcher(modname) + local fname = modname:gsub('%.', '/') .. '.lua' + local _, count = fname:gsub("/", "") + if count >= 3 then + local url = GIT_URL .. '/' .. fname + local c = loadUrl(url) + if c then + return load(c, modname, nil, env) + end + end + end - local function urlSearcher(modname) - local fname = modname:gsub('%.', '/') .. '.lua' + local function urlSearcher(modname) + local fname = modname:gsub('%.', '/') .. '.lua' - if fname:sub(1, 1) ~= '/' then - for entry in string.gmatch(env.package.upath, "[^;]+") do - local url = entry .. '/' .. fname - local c = loadUrl(url) - if c then - return load(c, modname, nil, env) - end - end - end - end + if fname:sub(1, 1) ~= '/' then + for entry in string.gmatch(env.package.upath, "[^;]+") do + local url = entry .. '/' .. fname + local c = loadUrl(url) + if c then + return load(c, modname, nil, env) + end + end + end + end - -- place package and require function into env - env.package = { - path = env.LUA_PATH or os.getenv('LUA_PATH') or DEFAULT_PATH, - upath = env.LUA_UPATH or os.getenv('LUA_UPATH') or DEFAULT_UPATH, - config = '/\n:\n?\n!\n-', - loaded = { - math = math, - string = string, - table = table, - io = io, - os = os, - }, - loaders = { - standardSearcher, - shellSearcher, - pathSearcher, - pastebinSearcher, - gitSearcher, - urlSearcher, - } - } + -- place package and require function into env + env.package = { + path = env.LUA_PATH or os.getenv('LUA_PATH') or DEFAULT_PATH, + upath = env.LUA_UPATH or os.getenv('LUA_UPATH') or DEFAULT_UPATH, + config = '/\n:\n?\n!\n-', + loaded = { + math = math, + string = string, + table = table, + io = io, + os = os, + }, + loaders = { + standardSearcher, + shellSearcher, + pathSearcher, + pastebinSearcher, + gitSearcher, + urlSearcher, + } + } - function env.require(modname) - for _,searcher in ipairs(env.package.loaders) do - local fn, msg = searcher(modname) - if fn then - local module, msg2 = fn(modname, env) - if not module then - error(msg2 or (modname .. ' module returned nil'), 2) - end - env.package.loaded[modname] = module - return module - end - if msg then - error(msg, 2) - end - end - error('Unable to find module ' .. modname) - end + function env.require(modname) + for _,searcher in ipairs(env.package.loaders) do + local fn, msg = searcher(modname) + if fn then + local module, msg2 = fn(modname, env) + if not module then + error(msg2 or (modname .. ' module returned nil'), 2) + end + env.package.loaded[modname] = module + return module + end + if msg then + error(msg, 2) + end + end + error('Unable to find module ' .. modname) + end - return env.require -- backwards compatible + return env.require -- backwards compatible end return function(env) - env = env or getfenv(2) - --setfenv(requireWrapper, env) - return requireWrapper(env) + env = env or getfenv(2) + --setfenv(requireWrapper, env) + return requireWrapper(env) end diff --git a/sys/apis/input.lua b/sys/apis/input.lua index 9bb2f9f..3ac6061 100644 --- a/sys/apis/input.lua +++ b/sys/apis/input.lua @@ -5,150 +5,176 @@ local keys = _G.keys local os = _G.os local modifiers = Util.transpose { - keys.leftCtrl, keys.rightCtrl, - keys.leftShift, keys.rightShift, - keys.leftAlt, keys.rightAlt, + keys.leftCtrl, keys.rightCtrl, + keys.leftShift, keys.rightShift, + keys.leftAlt, keys.rightAlt, } local input = { - state = { }, + state = { }, } function input:modifierPressed() - return keyboard.state[keys.leftCtrl] or - keyboard.state[keys.rightCtrl] or - keyboard.state[keys.leftAlt] or - keyboard.state[keys.rightAlt] + return keyboard.state[keys.leftCtrl] or + keyboard.state[keys.rightCtrl] or + keyboard.state[keys.leftAlt] or + keyboard.state[keys.rightAlt] end function input:toCode(ch, code) - local result = { } + local result = { } - if keyboard.state[keys.leftCtrl] or keyboard.state[keys.rightCtrl] or - code == keys.leftCtrl or code == keys.rightCtrl then - table.insert(result, 'control') - end + if not ch and code == 1 then + ch = 'escape' + end - if keyboard.state[keys.leftAlt] or keyboard.state[keys.rightAlt] or - code == keys.leftAlt or code == keys.rightAlt then - table.insert(result, 'alt') - end + if keyboard.state[keys.leftCtrl] or keyboard.state[keys.rightCtrl] or + code == keys.leftCtrl or code == keys.rightCtrl then + table.insert(result, 'control') + end - if keyboard.state[keys.leftShift] or keyboard.state[keys.rightShift] or - code == keys.leftShift or code == keys.rightShift then - if code and modifiers[code] then - table.insert(result, 'shift') - elseif #ch == 1 then - table.insert(result, ch:upper()) - else - table.insert(result, 'shift') - table.insert(result, ch) - end - elseif not code or not modifiers[code] then - table.insert(result, ch) - end + -- the key-up event for alt keys is not generated if the minecraft + -- window loses focus + -- + -- if keyboard.state[keys.leftAlt] or keyboard.state[keys.rightAlt] or + -- code == keys.leftAlt or code == keys.rightAlt then + -- table.insert(result, 'alt') + -- end - return table.concat(result, '-') + if keyboard.state[keys.leftShift] or keyboard.state[keys.rightShift] or + code == keys.leftShift or code == keys.rightShift then + if code and modifiers[code] then + table.insert(result, 'shift') + elseif #ch == 1 then + table.insert(result, ch:upper()) + else + table.insert(result, 'shift') + table.insert(result, ch) + end + elseif not code or not modifiers[code] then + table.insert(result, ch) + end + + return table.concat(result, '-') end function input:reset() - self.state = { } - self.fired = nil + self.state = { } + self.fired = nil - self.timer = nil - self.mch = nil - self.mfired = nil + self.timer = nil + self.mch = nil + self.mfired = nil end function input:translate(event, code, p1, p2) - if event == 'key' then - if p1 then -- key is held down - if not modifiers[code] then - self.fired = true - return input:toCode(keys.getName(code), code) - end - else - self.state[code] = true - if self:modifierPressed() and not modifiers[code] or code == 57 then - self.fired = true - return input:toCode(keys.getName(code), code) - else - self.fired = false - end - end + if event == 'key' then + if p1 then -- key is held down + if not modifiers[code] then + self.fired = true + return { code = input:toCode(keys.getName(code), code) } + end + else + self.state[code] = true + if self:modifierPressed() and not modifiers[code] or code == 57 then + self.fired = true + return { code = input:toCode(keys.getName(code), code) } + else + self.fired = false + end + end - elseif event == 'char' then - if not self:modifierPressed() then - self.fired = true - return input:toCode(code) - end + elseif event == 'char' then + if not self:modifierPressed() then + self.fired = true + return { code = event, ch = input:toCode(code) } + end - elseif event == 'key_up' then - if not self.fired then - if self.state[code] then - self.fired = true - local ch = input:toCode(keys.getName(code), code) - self.state[code] = nil - return ch - end - end - self.state[code] = nil + elseif event == 'key_up' then + if not self.fired then + if self.state[code] then + self.fired = true + local ch = input:toCode(keys.getName(code), code) + self.state[code] = nil + return { code = ch } + end + end + self.state[code] = nil - elseif event == 'paste' then - --self.state[keys.leftCtrl] = nil - --self.state[keys.rightCtrl] = nil - self.fired = true - return input:toCode('paste', 255) + elseif event == 'paste' then + self.fired = true + if keyboard.state[keys.leftShift] or keyboard.state[keys.rightShift] then + return { code = 'shift-paste', text = code } + else + return { code = 'paste', text = code } + end - elseif event == 'mouse_click' then - local buttons = { 'mouse_click', 'mouse_rightclick' } - self.mch = buttons[code] - self.mfired = nil + elseif event == 'mouse_click' then + local buttons = { 'mouse_click', 'mouse_rightclick' } + self.mch = buttons[code] + self.mfired = nil - elseif event == 'mouse_drag' then - self.mfired = true - self.fired = true - return input:toCode('mouse_drag', 255) + elseif event == 'mouse_drag' then + self.mfired = true + self.fired = true + return { + code = input:toCode('mouse_drag', 255), + button = code, + x = p1, + y = p2, + } - elseif event == 'mouse_up' then - if not self.mfired then - local clock = os.clock() - if self.timer and - p1 == self.x and p2 == self.y and - (clock - self.timer < .5) then + elseif event == 'mouse_up' then + if not self.mfired then + local clock = os.clock() + if self.timer and + p1 == self.x and p2 == self.y and + (clock - self.timer < .5) then - self.mch = 'mouse_doubleclick' - self.timer = nil - else - self.timer = os.clock() - self.x = p1 - self.y = p2 - end - self.mfired = input:toCode(self.mch, 255) - else - self.mch = 'mouse_up' - self.mfired = input:toCode(self.mch, 255) - end - self.fired = true - return self.mfired + self.mch = 'mouse_doubleclick' + self.timer = nil + else + self.timer = os.clock() + self.x = p1 + self.y = p2 + end + self.mfired = input:toCode(self.mch, 255) + else + self.mch = 'mouse_up' + self.mfired = input:toCode(self.mch, 255) + end + self.fired = true + return { + code = self.mfired, + button = code, + x = p1, + y = p2, + } - elseif event == "mouse_scroll" then - local directions = { - [ -1 ] = 'scrollUp', - [ 1 ] = 'scrollDown' - } - self.fired = true - return input:toCode(directions[code], 255) - end + elseif event == "mouse_scroll" then + local directions = { + [ -1 ] = 'scroll_up', + [ 1 ] = 'scroll_down' + } + self.fired = true + return { + code = input:toCode(directions[code], 255), + x = p1, + y = p2, + } + + elseif event == 'terminate' then + return { code = 'terminate' } + end end function input:test() - while true do - local ch = self:translate(os.pullEvent()) - if ch then - print('GOT: ' .. tostring(ch)) - end - end + while true do + local ch = self:translate(os.pullEvent()) + if ch then + Util.print(ch) + end + end end return input \ No newline at end of file diff --git a/sys/apis/json.lua b/sys/apis/json.lua index af65f80..6a1a921 100644 --- a/sys/apis/json.lua +++ b/sys/apis/json.lua @@ -7,215 +7,215 @@ local json = { } local controls = {["\n"]="\\n", ["\r"]="\\r", ["\t"]="\\t", ["\b"]="\\b", ["\f"]="\\f", ["\""]="\\\"", ["\\"]="\\\\"} local function isArray(t) - local max = 0 - for k,v in pairs(t) do - if type(k) ~= "number" then - return false - elseif k > max then - max = k - end - end - return max == #t + local max = 0 + for k,v in pairs(t) do + if type(k) ~= "number" then + return false + elseif k > max then + max = k + end + end + return max == #t end local whites = {['\n']=true; ['\r']=true; ['\t']=true; [' ']=true; [',']=true; [':']=true} local function removeWhite(str) - while whites[str:sub(1, 1)] do - str = str:sub(2) - end - return str + while whites[str:sub(1, 1)] do + str = str:sub(2) + end + return str end ------------------------------------------------------------------ encoding local function encodeCommon(val, pretty, tabLevel, tTracking) - local str = "" + local str = "" - -- Tabbing util - local function tab(s) - str = str .. ("\t"):rep(tabLevel) .. s - end + -- Tabbing util + local function tab(s) + str = str .. ("\t"):rep(tabLevel) .. s + end - local function arrEncoding(val, bracket, closeBracket, iterator, loopFunc) - str = str .. bracket - if pretty then - str = str .. "\n" - tabLevel = tabLevel + 1 - end - for k,v in iterator(val) do - tab("") - loopFunc(k,v) - str = str .. "," - if pretty then str = str .. "\n" end - end - if pretty then - tabLevel = tabLevel - 1 - end - if str:sub(-2) == ",\n" then - str = str:sub(1, -3) .. "\n" - elseif str:sub(-1) == "," then - str = str:sub(1, -2) - end - tab(closeBracket) - end + local function arrEncoding(val, bracket, closeBracket, iterator, loopFunc) + str = str .. bracket + if pretty then + str = str .. "\n" + tabLevel = tabLevel + 1 + end + for k,v in iterator(val) do + tab("") + loopFunc(k,v) + str = str .. "," + if pretty then str = str .. "\n" end + end + if pretty then + tabLevel = tabLevel - 1 + end + if str:sub(-2) == ",\n" then + str = str:sub(1, -3) .. "\n" + elseif str:sub(-1) == "," then + str = str:sub(1, -2) + end + tab(closeBracket) + end - -- Table encoding - if type(val) == "table" then - assert(not tTracking[val], "Cannot encode a table holding itself recursively") - tTracking[val] = true - if isArray(val) then - arrEncoding(val, "[", "]", ipairs, function(k,v) - str = str .. encodeCommon(v, pretty, tabLevel, tTracking) - end) - else + -- Table encoding + if type(val) == "table" then + assert(not tTracking[val], "Cannot encode a table holding itself recursively") + tTracking[val] = true + if isArray(val) then + arrEncoding(val, "[", "]", ipairs, function(k,v) + str = str .. encodeCommon(v, pretty, tabLevel, tTracking) + end) + else debug(val) - arrEncoding(val, "{", "}", pairs, function(k,v) - assert(type(k) == "string", "JSON object keys must be strings", 2) - str = str .. encodeCommon(k, pretty, tabLevel, tTracking) - str = str .. (pretty and ": " or ":") .. encodeCommon(v, pretty, tabLevel, tTracking) - end) - end - -- String encoding - elseif type(val) == "string" then - str = '"' .. val:gsub("[%c\"\\]", controls) .. '"' - -- Number encoding - elseif type(val) == "number" or type(val) == "boolean" then - str = tostring(val) - else - error("JSON only supports arrays, objects, numbers, booleans, and strings", 2) - end - return str + arrEncoding(val, "{", "}", pairs, function(k,v) + assert(type(k) == "string", "JSON object keys must be strings", 2) + str = str .. encodeCommon(k, pretty, tabLevel, tTracking) + str = str .. (pretty and ": " or ":") .. encodeCommon(v, pretty, tabLevel, tTracking) + end) + end + -- String encoding + elseif type(val) == "string" then + str = '"' .. val:gsub("[%c\"\\]", controls) .. '"' + -- Number encoding + elseif type(val) == "number" or type(val) == "boolean" then + str = tostring(val) + else + error("JSON only supports arrays, objects, numbers, booleans, and strings", 2) + end + return str end function json.encode(val) - return encodeCommon(val, false, 0, {}) + return encodeCommon(val, false, 0, {}) end function json.encodePretty(val) - return encodeCommon(val, true, 0, {}) + return encodeCommon(val, true, 0, {}) end function json.encodeToFile(path, val) - local file = io.open(path, "w") - assert(file, "Unable to open file") - file:write(json.encodePretty(val)) - file:close() + local file = io.open(path, "w") + assert(file, "Unable to open file") + file:write(json.encodePretty(val)) + file:close() end ------------------------------------------------------------------ decoding local decodeControls = {} for k,v in pairs(controls) do - decodeControls[v] = k + decodeControls[v] = k end local function parseBoolean(str) - if str:sub(1, 4) == "true" then - return true, removeWhite(str:sub(5)) - else - return false, removeWhite(str:sub(6)) - end + if str:sub(1, 4) == "true" then + return true, removeWhite(str:sub(5)) + else + return false, removeWhite(str:sub(6)) + end end local function parseNull(str) - return nil, removeWhite(str:sub(5)) + return nil, removeWhite(str:sub(5)) end local numChars = {['e']=true; ['E']=true; ['+']=true; ['-']=true; ['.']=true} local function parseNumber(str) - local i = 1 - while numChars[str:sub(i, i)] or tonumber(str:sub(i, i)) do - i = i + 1 - end - local val = tonumber(str:sub(1, i - 1)) - str = removeWhite(str:sub(i)) - return val, str + local i = 1 + while numChars[str:sub(i, i)] or tonumber(str:sub(i, i)) do + i = i + 1 + end + local val = tonumber(str:sub(1, i - 1)) + str = removeWhite(str:sub(i)) + return val, str end local function parseString(str) - str = str:sub(2) - local s = "" - while str:sub(1,1) ~= "\"" do - local next = str:sub(1,1) - str = str:sub(2) - assert(next ~= "\n", "Unclosed string") + str = str:sub(2) + local s = "" + while str:sub(1,1) ~= "\"" do + local next = str:sub(1,1) + str = str:sub(2) + assert(next ~= "\n", "Unclosed string") - if next == "\\" then - local escape = str:sub(1,1) - str = str:sub(2) + if next == "\\" then + local escape = str:sub(1,1) + str = str:sub(2) - next = assert(decodeControls[next..escape], "Invalid escape character") - end + next = assert(decodeControls[next..escape], "Invalid escape character") + end - s = s .. next - end - return s, removeWhite(str:sub(2)) + s = s .. next + end + return s, removeWhite(str:sub(2)) end function json.parseArray(str) - str = removeWhite(str:sub(2)) + str = removeWhite(str:sub(2)) - local val = {} - local i = 1 - while str:sub(1, 1) ~= "]" do - local v - v, str = json.parseValue(str) - val[i] = v - i = i + 1 - str = removeWhite(str) - end - str = removeWhite(str:sub(2)) - return val, str + local val = {} + local i = 1 + while str:sub(1, 1) ~= "]" do + local v + v, str = json.parseValue(str) + val[i] = v + i = i + 1 + str = removeWhite(str) + end + str = removeWhite(str:sub(2)) + return val, str end function json.parseValue(str) - local fchar = str:sub(1, 1) - if fchar == "{" then - return json.parseObject(str) - elseif fchar == "[" then - return json.parseArray(str) - elseif tonumber(fchar) ~= nil or numChars[fchar] then - return parseNumber(str) - elseif str:sub(1, 4) == "true" or str:sub(1, 5) == "false" then - return parseBoolean(str) - elseif fchar == "\"" then - return parseString(str) - elseif str:sub(1, 4) == "null" then - return parseNull(str) - end + local fchar = str:sub(1, 1) + if fchar == "{" then + return json.parseObject(str) + elseif fchar == "[" then + return json.parseArray(str) + elseif tonumber(fchar) ~= nil or numChars[fchar] then + return parseNumber(str) + elseif str:sub(1, 4) == "true" or str:sub(1, 5) == "false" then + return parseBoolean(str) + elseif fchar == "\"" then + return parseString(str) + elseif str:sub(1, 4) == "null" then + return parseNull(str) + end end function json.parseMember(str) - local k, val - k, str = json.parseValue(str) - val, str = json.parseValue(str) - return k, val, str + local k, val + k, str = json.parseValue(str) + val, str = json.parseValue(str) + return k, val, str end function json.parseObject(str) - str = removeWhite(str:sub(2)) + str = removeWhite(str:sub(2)) - local val = {} - while str:sub(1, 1) ~= "}" do - local k, v - k, v, str = json.parseMember(str) - val[k] = v - str = removeWhite(str) - end - str = removeWhite(str:sub(2)) - return val, str + local val = {} + while str:sub(1, 1) ~= "}" do + local k, v + k, v, str = json.parseMember(str) + val[k] = v + str = removeWhite(str) + end + str = removeWhite(str:sub(2)) + return val, str end function json.decode(str) - str = removeWhite(str) - return json.parseValue(str) + str = removeWhite(str) + return json.parseValue(str) end function json.decodeFromFile(path) - local file = assert(fs.open(path, "r")) - local decoded = json.decode(file.readAll()) - file.close() - return decoded + local file = assert(fs.open(path, "r")) + local decoded = json.decode(file.readAll()) + file.close() + return decoded end return json diff --git a/sys/apis/jumper/core/bheap.lua b/sys/apis/jumper/core/bheap.lua index e3b3c83..c58ce2f 100644 --- a/sys/apis/jumper/core/bheap.lua +++ b/sys/apis/jumper/core/bheap.lua @@ -14,9 +14,9 @@ -- --[[ - Notes: - This lighter implementation of binary heaps, based on : - https://github.com/Yonaba/Binary-Heaps + Notes: + This lighter implementation of binary heaps, based on : + https://github.com/Yonaba/Binary-Heaps --]] if (...) then diff --git a/sys/apis/jumper/core/node.lua b/sys/apis/jumper/core/node.lua index c476230..09d1db4 100644 --- a/sys/apis/jumper/core/node.lua +++ b/sys/apis/jumper/core/node.lua @@ -9,16 +9,16 @@ -- if (...) then - local Node = {} - Node.__index = Node + local Node = {} + Node.__index = Node - function Node:new(x,y,z) - return setmetatable({x = x, y = y, z = z }, Node) - end + function Node:new(x,y,z) + return setmetatable({x = x, y = y, z = z }, Node) + end - -- Enables the use of operator '<' to compare nodes. - -- Will be used to sort a collection of nodes in a binary heap on the basis of their F-cost - function Node.__lt(A,B) return (A._f < B._f) end + -- Enables the use of operator '<' to compare nodes. + -- Will be used to sort a collection of nodes in a binary heap on the basis of their F-cost + function Node.__lt(A,B) return (A._f < B._f) end function Node:getX() return self.x end function Node:getY() return self.y end @@ -33,7 +33,7 @@ if (...) then return self end - return setmetatable(Node, + return setmetatable(Node, {__call = function(_,...) return Node:new(...) end} diff --git a/sys/apis/jumper/core/path.lua b/sys/apis/jumper/core/path.lua index d3c5c16..f02c41b 100644 --- a/sys/apis/jumper/core/path.lua +++ b/sys/apis/jumper/core/path.lua @@ -8,60 +8,60 @@ if (...) then - local t_remove = table.remove + local t_remove = table.remove - local Path = {} - Path.__index = Path + local Path = {} + Path.__index = Path - function Path:new() - return setmetatable({_nodes = {}}, Path) - end + function Path:new() + return setmetatable({_nodes = {}}, Path) + end - --- Iterates on each single `node` along a `path`. At each step of iteration, - -- returns the `node` plus a count value. Aliased as @{Path:nodes} + --- Iterates on each single `node` along a `path`. At each step of iteration, + -- returns the `node` plus a count value. Aliased as @{Path:nodes} -- @usage -- for node, count in p:iter() do -- ... -- end - function Path:nodes() - local i = 1 - return function() - if self._nodes[i] then - i = i+1 - return self._nodes[i-1],i-1 - end - end - end + function Path:nodes() + local i = 1 + return function() + if self._nodes[i] then + i = i+1 + return self._nodes[i-1],i-1 + end + end + end - --- `Path` compression modifier. Given a `path`, eliminates useless nodes to return a lighter `path` - -- consisting of straight moves. Does the opposite of @{Path:fill} - -- @class function - -- @treturn path self (the calling `path` itself, can be chained) - -- @see Path:fill - -- @usage p:filter() - function Path:filter() - local i = 2 - local xi,yi,zi,dx,dy,dz, olddx, olddy, olddz - xi,yi,zi = self._nodes[i].x, self._nodes[i].y, self._nodes[i].z - dx, dy,dz = xi - self._nodes[i-1].x, yi-self._nodes[i-1].y, zi-self._nodes[i-1].z - while true do - olddx, olddy, olddz = dx, dy, dz - if self._nodes[i+1] then - i = i+1 - xi, yi, zi = self._nodes[i].x, self._nodes[i].y, self._nodes[i].z - dx, dy, dz = xi - self._nodes[i-1].x, yi - self._nodes[i-1].y, zi - self._nodes[i-1].z - if olddx == dx and olddy == dy and olddz == dz then - t_remove(self._nodes, i-1) - i = i - 1 - end - else break end - end - return self - end + --- `Path` compression modifier. Given a `path`, eliminates useless nodes to return a lighter `path` + -- consisting of straight moves. Does the opposite of @{Path:fill} + -- @class function + -- @treturn path self (the calling `path` itself, can be chained) + -- @see Path:fill + -- @usage p:filter() + function Path:filter() + local i = 2 + local xi,yi,zi,dx,dy,dz, olddx, olddy, olddz + xi,yi,zi = self._nodes[i].x, self._nodes[i].y, self._nodes[i].z + dx, dy,dz = xi - self._nodes[i-1].x, yi-self._nodes[i-1].y, zi-self._nodes[i-1].z + while true do + olddx, olddy, olddz = dx, dy, dz + if self._nodes[i+1] then + i = i+1 + xi, yi, zi = self._nodes[i].x, self._nodes[i].y, self._nodes[i].z + dx, dy, dz = xi - self._nodes[i-1].x, yi - self._nodes[i-1].y, zi - self._nodes[i-1].z + if olddx == dx and olddy == dy and olddz == dz then + t_remove(self._nodes, i-1) + i = i - 1 + end + else break end + end + return self + end - return setmetatable(Path, - {__call = function(_,...) - return Path:new(...) - end - }) + return setmetatable(Path, + {__call = function(_,...) + return Path:new(...) + end + }) end \ No newline at end of file diff --git a/sys/apis/jumper/core/utils.lua b/sys/apis/jumper/core/utils.lua index 1ebf876..ab5f764 100644 --- a/sys/apis/jumper/core/utils.lua +++ b/sys/apis/jumper/core/utils.lua @@ -20,19 +20,19 @@ if (...) then end -- Extract a path from a given start/end position - local function traceBackPath(finder, node, startNode) - local path = Path:new() - path._grid = finder._grid - while true do - if node._parent then - t_insert(path._nodes,1,node) - node = node._parent - else - t_insert(path._nodes,1,startNode) - return path - end - end - end + local function traceBackPath(finder, node, startNode) + local path = Path:new() + path._grid = finder._grid + while true do + if node._parent then + t_insert(path._nodes,1,node) + node = node._parent + else + t_insert(path._nodes,1,startNode) + return path + end + end + end -- Lookup for value in a table local indexOf = function(t,v) @@ -43,9 +43,9 @@ if (...) then end -- Is i out of range - local function outOfRange(i,low,up) - return (i< low or i > up) - end + local function outOfRange(i,low,up) + return (i< low or i > up) + end return { arraySize = arraySize, diff --git a/sys/apis/jumper/grid.lua b/sys/apis/jumper/grid.lua index ab48255..6cfc53a 100644 --- a/sys/apis/jumper/grid.lua +++ b/sys/apis/jumper/grid.lua @@ -9,93 +9,93 @@ if (...) then -- Dependencies - local _PATH = (...):gsub('%.grid$','') + local _PATH = (...):gsub('%.grid$','') -- Local references - local Utils = require (_PATH .. '.core.utils') - local Node = require (_PATH .. '.core.node') + local Utils = require (_PATH .. '.core.utils') + local Node = require (_PATH .. '.core.node') -- Local references local setmetatable = setmetatable - -- Offsets for straights moves - local straightOffsets = { - {x = 1, y = 0, z = 0} --[[W]], {x = -1, y = 0, z = 0}, --[[E]] - {x = 0, y = 1, z = 0} --[[S]], {x = 0, y = -1, z = 0}, --[[N]] - {x = 0, y = 0, z = 1} --[[U]], {x = 0, y = -0, z = -1}, --[[D]] - } + -- Offsets for straights moves + local straightOffsets = { + {x = 1, y = 0, z = 0} --[[W]], {x = -1, y = 0, z = 0}, --[[E]] + {x = 0, y = 1, z = 0} --[[S]], {x = 0, y = -1, z = 0}, --[[N]] + {x = 0, y = 0, z = 1} --[[U]], {x = 0, y = -0, z = -1}, --[[D]] + } - local Grid = {} - Grid.__index = Grid + local Grid = {} + Grid.__index = Grid - function Grid:new(dim) - local newGrid = { } - newGrid._min_x, newGrid._max_x = dim.x, dim.ex - newGrid._min_y, newGrid._max_y = dim.y, dim.ey - newGrid._min_z, newGrid._max_z = dim.z, dim.ez - newGrid._nodes = { } - newGrid._width = (newGrid._max_x-newGrid._min_x)+1 - newGrid._height = (newGrid._max_y-newGrid._min_y)+1 - newGrid._length = (newGrid._max_z-newGrid._min_z)+1 - return setmetatable(newGrid,Grid) - end + function Grid:new(dim) + local newGrid = { } + newGrid._min_x, newGrid._max_x = dim.x, dim.ex + newGrid._min_y, newGrid._max_y = dim.y, dim.ey + newGrid._min_z, newGrid._max_z = dim.z, dim.ez + newGrid._nodes = { } + newGrid._width = (newGrid._max_x-newGrid._min_x)+1 + newGrid._height = (newGrid._max_y-newGrid._min_y)+1 + newGrid._length = (newGrid._max_z-newGrid._min_z)+1 + return setmetatable(newGrid,Grid) + end - function Grid:isWalkableAt(x, y, z) - local node = self:getNodeAt(x,y,z) - return node and node.walkable ~= 1 - end + function Grid:isWalkableAt(x, y, z) + local node = self:getNodeAt(x,y,z) + return node and node.walkable ~= 1 + end - function Grid:getWidth() - return self._width - end + function Grid:getWidth() + return self._width + end - function Grid:getHeight() - return self._height - end + function Grid:getHeight() + return self._height + end - function Grid:getNodes() - return self._nodes - end + function Grid:getNodes() + return self._nodes + end function Grid:getBounds() return self._min_x, self._min_y, self._min_z, self._max_x, self._max_y, self._max_z end - --- Returns neighbours. The returned value is an array of __walkable__ nodes neighbouring a given `node`. - -- @treturn {node,...} an array of nodes neighbouring a given node - function Grid:getNeighbours(node) + --- Returns neighbours. The returned value is an array of __walkable__ nodes neighbouring a given `node`. + -- @treturn {node,...} an array of nodes neighbouring a given node + function Grid:getNeighbours(node) local neighbours = {} - for i = 1,#straightOffsets do - local n = self:getNodeAt( - node.x + straightOffsets[i].x, - node.y + straightOffsets[i].y, - node.z + straightOffsets[i].z - ) - if n and self:isWalkableAt(n.x, n.y, n.z) then - neighbours[#neighbours+1] = n - end - end + for i = 1,#straightOffsets do + local n = self:getNodeAt( + node.x + straightOffsets[i].x, + node.y + straightOffsets[i].y, + node.z + straightOffsets[i].z + ) + if n and self:isWalkableAt(n.x, n.y, n.z) then + neighbours[#neighbours+1] = n + end + end - return neighbours - end + return neighbours + end function Grid:getNodeAt(x,y,z) - if not x or not y or not z then return end - if Utils.outOfRange(x,self._min_x,self._max_x) then return end - if Utils.outOfRange(y,self._min_y,self._max_y) then return end - if Utils.outOfRange(z,self._min_z,self._max_z) then return end + if not x or not y or not z then return end + if Utils.outOfRange(x,self._min_x,self._max_x) then return end + if Utils.outOfRange(y,self._min_y,self._max_y) then return end + if Utils.outOfRange(z,self._min_z,self._max_z) then return end - -- inefficient - if not self._nodes[y] then self._nodes[y] = {} end - if not self._nodes[y][x] then self._nodes[y][x] = {} end - if not self._nodes[y][x][z] then self._nodes[y][x][z] = Node:new(x,y,z) end - return self._nodes[y][x][z] - end + -- inefficient + if not self._nodes[y] then self._nodes[y] = {} end + if not self._nodes[y][x] then self._nodes[y][x] = {} end + if not self._nodes[y][x][z] then self._nodes[y][x][z] = Node:new(x,y,z) end + return self._nodes[y][x][z] + end - return setmetatable(Grid,{ - __call = function(self,...) - return self:new(...) - end - }) + return setmetatable(Grid,{ + __call = function(self,...) + return self:new(...) + end + }) end diff --git a/sys/apis/jumper/pathfinder.lua b/sys/apis/jumper/pathfinder.lua index 93d48ff..0ff2844 100644 --- a/sys/apis/jumper/pathfinder.lua +++ b/sys/apis/jumper/pathfinder.lua @@ -1,31 +1,16 @@ --[[ - The following License applies to all files within the jumper directory. + The following License applies to all files within the jumper directory. - Note that this is only a partial copy of the full jumper code base. Also, - the code was modified to support 3D maps. + Note that this is only a partial copy of the full jumper code base. Also, + the code was modified to support 3D maps. --]] --[[ This work is under MIT-LICENSE Copyright (c) 2012-2013 Roland Yonaba. -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: +-- https://opensource.org/licenses/MIT -The above copyright notice and this permission notice shall be included in -all copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN -THE SOFTWARE. --]] local _VERSION = "" @@ -33,87 +18,87 @@ local _RELEASEDATE = "" if (...) then - -- Dependencies - local _PATH = (...):gsub('%.pathfinder$','') + -- Dependencies + local _PATH = (...):gsub('%.pathfinder$','') local Utils = require (_PATH .. '.core.utils') - -- Internalization - local pairs = pairs - local assert = assert - local setmetatable = setmetatable + -- Internalization + local pairs = pairs + local assert = assert + local setmetatable = setmetatable --- Finders (search algorithms implemented). Refers to the search algorithms actually implemented in Jumper. --
  • [A*](http://en.wikipedia.org/wiki/A*_search_algorithm)
  • - local Finders = { - ['ASTAR'] = require (_PATH .. '.search.astar'), - } + local Finders = { + ['ASTAR'] = require (_PATH .. '.search.astar'), + } - -- Will keep track of all nodes expanded during the search - -- to easily reset their properties for the next pathfinding call - local toClear = {} + -- Will keep track of all nodes expanded during the search + -- to easily reset their properties for the next pathfinding call + local toClear = {} - -- Performs a traceback from the goal node to the start node - -- Only happens when the path was found + -- Performs a traceback from the goal node to the start node + -- Only happens when the path was found - local Pathfinder = {} - Pathfinder.__index = Pathfinder + local Pathfinder = {} + Pathfinder.__index = Pathfinder - function Pathfinder:new(heuristic) - local newPathfinder = {} - setmetatable(newPathfinder, Pathfinder) - self._finder = Finders.ASTAR - self._heuristic = heuristic - return newPathfinder - end + function Pathfinder:new(heuristic) + local newPathfinder = {} + setmetatable(newPathfinder, Pathfinder) + self._finder = Finders.ASTAR + self._heuristic = heuristic + return newPathfinder + end - function Pathfinder:setGrid(grid) - self._grid = grid - return self - end - - --- Calculates a `path`. Returns the `path` from start to end location - -- Both locations must exist on the collision map. The starting location can be unwalkable. - -- @treturn path a path (array of nodes) when found, otherwise nil - -- @usage local path = myFinder:getPath(1,1,5,5) - function Pathfinder:getPath(startX, startY, startZ, ih, endX, endY, endZ, oh) - self:reset() - local startNode = self._grid:getNodeAt(startX, startY, startZ) - local endNode = self._grid:getNodeAt(endX, endY, endZ) - if not startNode or not endNode then - return nil - end - - startNode.heading = ih - endNode.heading = oh - - assert(startNode, ('Invalid location [%d, %d, %d]'):format(startX, startY, startZ)) - assert(endNode and self._grid:isWalkableAt(endX, endY, endZ), - ('Invalid or unreachable location [%d, %d, %d]'):format(endX, endY, endZ)) - local _endNode = self._finder(self, startNode, endNode, toClear) - if _endNode then - return Utils.traceBackPath(self, _endNode, startNode) - end - return nil - end - - --- Resets the `pathfinder`. This function is called internally between - -- successive pathfinding calls, so you should not - -- use it explicitely, unless under specific circumstances. - -- @class function - -- @treturn pathfinder self (the calling `pathfinder` itself, can be chained) - -- @usage local path, len = myFinder:getPath(1,1,5,5) - function Pathfinder:reset() - for node in pairs(toClear) do node:reset() end - toClear = {} + function Pathfinder:setGrid(grid) + self._grid = grid return self end - -- Returns Pathfinder class + --- Calculates a `path`. Returns the `path` from start to end location + -- Both locations must exist on the collision map. The starting location can be unwalkable. + -- @treturn path a path (array of nodes) when found, otherwise nil + -- @usage local path = myFinder:getPath(1,1,5,5) + function Pathfinder:getPath(startX, startY, startZ, ih, endX, endY, endZ, oh) + self:reset() + local startNode = self._grid:getNodeAt(startX, startY, startZ) + local endNode = self._grid:getNodeAt(endX, endY, endZ) + if not startNode or not endNode then + return nil + end + + startNode.heading = ih + endNode.heading = oh + + assert(startNode, ('Invalid location [%d, %d, %d]'):format(startX, startY, startZ)) + assert(endNode and self._grid:isWalkableAt(endX, endY, endZ), + ('Invalid or unreachable location [%d, %d, %d]'):format(endX, endY, endZ)) + local _endNode = self._finder(self, startNode, endNode, toClear) + if _endNode then + return Utils.traceBackPath(self, _endNode, startNode) + end + return nil + end + + --- Resets the `pathfinder`. This function is called internally between + -- successive pathfinding calls, so you should not + -- use it explicitely, unless under specific circumstances. + -- @class function + -- @treturn pathfinder self (the calling `pathfinder` itself, can be chained) + -- @usage local path, len = myFinder:getPath(1,1,5,5) + function Pathfinder:reset() + for node in pairs(toClear) do node:reset() end + toClear = {} + return self + end + + -- Returns Pathfinder class Pathfinder._VERSION = _VERSION Pathfinder._RELEASEDATE = _RELEASEDATE - return setmetatable(Pathfinder,{ - __call = function(self,...) - return self:new(...) - end - }) + return setmetatable(Pathfinder,{ + __call = function(self,...) + return self:new(...) + end + }) end diff --git a/sys/apis/jumper/search/astar.lua b/sys/apis/jumper/search/astar.lua index d42f07c..c92dabc 100644 --- a/sys/apis/jumper/search/astar.lua +++ b/sys/apis/jumper/search/astar.lua @@ -35,9 +35,9 @@ if (...) then end end - -- Calculates a path. - -- Returns the path from location `` to location ``. - return function (finder, startNode, endNode, toClear) + -- Calculates a path. + -- Returns the path from location `` to location ``. + return function (finder, startNode, endNode, toClear) local openList = Heap() startNode._g = 0 startNode._h = finder._heuristic(endNode, startNode) diff --git a/sys/apis/logger.lua b/sys/apis/logger.lua index a4ae160..d693f4c 100644 --- a/sys/apis/logger.lua +++ b/sys/apis/logger.lua @@ -1,133 +1,133 @@ local Logger = { - fn = function() end, - filteredEvents = { }, + fn = function() end, + filteredEvents = { }, } function Logger.setLogger(fn) - Logger.fn = fn + Logger.fn = fn end function Logger.disable() - Logger.setLogger(function() end) + Logger.setLogger(function() end) end function Logger.setDaemonLogging() - Logger.setLogger(function (text) - os.queueEvent('log', { text = text }) - end) + Logger.setLogger(function (text) + os.queueEvent('log', { text = text }) + end) end function Logger.setMonitorLogging() - local debugMon = device.monitor + local debugMon = device.monitor - if not debugMon then - debugMon.setTextScale(.5) - debugMon.clear() - debugMon.setCursorPos(1, 1) - Logger.setLogger(function(text) - debugMon.write(text) - debugMon.scroll(-1) - debugMon.setCursorPos(1, 1) - end) - end + if not debugMon then + debugMon.setTextScale(.5) + debugMon.clear() + debugMon.setCursorPos(1, 1) + Logger.setLogger(function(text) + debugMon.write(text) + debugMon.scroll(-1) + debugMon.setCursorPos(1, 1) + end) + end end function Logger.setScreenLogging() - Logger.setLogger(function(text) - local x, y = term.getCursorPos() - if x ~= 1 then - local sx, sy = term.getSize() - term.setCursorPos(1, sy) - --term.scroll(1) - end - print(text) - end) + Logger.setLogger(function(text) + local x, y = term.getCursorPos() + if x ~= 1 then + local sx, sy = term.getSize() + term.setCursorPos(1, sy) + --term.scroll(1) + end + print(text) + end) end function Logger.setWirelessLogging() - if device.wireless_modem then - Logger.filter('modem_message') - Logger.filter('modem_receive') - Logger.filter('rednet_message') - Logger.setLogger(function(text) - device.wireless_modem.transmit(59998, os.getComputerID(), { - type = 'log', contents = text - }) - end) - Logger.debug('Logging enabled') - return true - end + if device.wireless_modem then + Logger.filter('modem_message') + Logger.filter('modem_receive') + Logger.filter('rednet_message') + Logger.setLogger(function(text) + device.wireless_modem.transmit(59998, os.getComputerID(), { + type = 'log', contents = text + }) + end) + Logger.debug('Logging enabled') + return true + end end function Logger.setFileLogging(fileName) - fs.delete(fileName) - Logger.setLogger(function (text) - local logFile + fs.delete(fileName) + Logger.setLogger(function (text) + local logFile - local mode = 'w' - if fs.exists(fileName) then - mode = 'a' - end - local file = io.open(fileName, mode) - if file then - file:write(text) - file:write('\n') - file:close() - end - end) + local mode = 'w' + if fs.exists(fileName) then + mode = 'a' + end + local file = io.open(fileName, mode) + if file then + file:write(text) + file:write('\n') + file:close() + end + end) end function Logger.log(category, value, ...) - if Logger.filteredEvents[category] then - return - end + if Logger.filteredEvents[category] then + return + end - if type(value) == 'table' then - local str - for k,v in pairs(value) do - if not str then - str = '{ ' - else - str = str .. ', ' - end - str = str .. k .. '=' .. tostring(v) - end - if str then - value = str .. ' }' - else - value = '{ }' - end - elseif type(value) == 'string' then - local args = { ... } - if #args > 0 then - value = string.format(value, unpack(args)) - end - else - value = tostring(value) - end - Logger.fn(category .. ': ' .. value) + if type(value) == 'table' then + local str + for k,v in pairs(value) do + if not str then + str = '{ ' + else + str = str .. ', ' + end + str = str .. k .. '=' .. tostring(v) + end + if str then + value = str .. ' }' + else + value = '{ }' + end + elseif type(value) == 'string' then + local args = { ... } + if #args > 0 then + value = string.format(value, unpack(args)) + end + else + value = tostring(value) + end + Logger.fn(category .. ': ' .. value) end function Logger.debug(value, ...) - Logger.log('debug', value, ...) + Logger.log('debug', value, ...) end function Logger.logNestedTable(t, indent) - for _,v in ipairs(t) do - if type(v) == 'table' then - log('table') - logNestedTable(v) --, indent+1) - else - log(v) - end - end + for _,v in ipairs(t) do + if type(v) == 'table' then + log('table') + logNestedTable(v) --, indent+1) + else + log(v) + end + end end function Logger.filter( ...) - local events = { ... } - for _,event in pairs(events) do - Logger.filteredEvents[event] = true - end + local events = { ... } + for _,event in pairs(events) do + Logger.filteredEvents[event] = true + end end return Logger \ No newline at end of file diff --git a/sys/apis/nft.lua b/sys/apis/nft.lua index 1a4cdd8..056961b 100644 --- a/sys/apis/nft.lua +++ b/sys/apis/nft.lua @@ -6,75 +6,75 @@ local NFT = { } local tColourLookup = { } for n = 1, 16 do - tColourLookup[string.byte("0123456789abcdef", n, n)] = 2 ^ (n - 1) + tColourLookup[string.byte("0123456789abcdef", n, n)] = 2 ^ (n - 1) end local function getColourOf(hex) - return tColourLookup[hex:byte()] + return tColourLookup[hex:byte()] end function NFT.parse(imageText) - local image = { - fg = { }, - bg = { }, - text = { }, - } + local image = { + fg = { }, + bg = { }, + text = { }, + } - local num = 1 - local lines = Util.split(imageText) - while #lines[#lines] == 0 do - table.remove(lines, #lines) - end + local num = 1 + local lines = Util.split(imageText) + while #lines[#lines] == 0 do + table.remove(lines, #lines) + end - for _,sLine in ipairs(lines) do - table.insert(image.fg, { }) - table.insert(image.bg, { }) - table.insert(image.text, { }) + for _,sLine in ipairs(lines) do + table.insert(image.fg, { }) + table.insert(image.bg, { }) + table.insert(image.text, { }) - --As we're no longer 1-1, we keep track of what index to write to - local writeIndex = 1 - --Tells us if we've hit a 30 or 31 (BG and FG respectively)- next char specifies the curr colour - local bgNext, fgNext = false, false - --The current background and foreground colours - local currBG, currFG = nil,nil - for i = 1, #sLine do - local nextChar = string.sub(sLine, i, i) - if nextChar:byte() == 30 then - bgNext = true - elseif nextChar:byte() == 31 then - fgNext = true - elseif bgNext then - currBG = getColourOf(nextChar) - bgNext = false - elseif fgNext then - currFG = getColourOf(nextChar) - fgNext = false - else - if nextChar ~= " " and currFG == nil then - currFG = _G.colors.white - end - image.bg[num][writeIndex] = currBG - image.fg[num][writeIndex] = currFG - image.text[num][writeIndex] = nextChar - writeIndex = writeIndex + 1 - end - end - image.height = num - if not image.width or writeIndex - 1 > image.width then - image.width = writeIndex - 1 - end - num = num+1 - end - return image + --As we're no longer 1-1, we keep track of what index to write to + local writeIndex = 1 + --Tells us if we've hit a 30 or 31 (BG and FG respectively)- next char specifies the curr colour + local bgNext, fgNext = false, false + --The current background and foreground colours + local currBG, currFG = nil,nil + for i = 1, #sLine do + local nextChar = string.sub(sLine, i, i) + if nextChar:byte() == 30 then + bgNext = true + elseif nextChar:byte() == 31 then + fgNext = true + elseif bgNext then + currBG = getColourOf(nextChar) + bgNext = false + elseif fgNext then + currFG = getColourOf(nextChar) + fgNext = false + else + if nextChar ~= " " and currFG == nil then + currFG = _G.colors.white + end + image.bg[num][writeIndex] = currBG + image.fg[num][writeIndex] = currFG + image.text[num][writeIndex] = nextChar + writeIndex = writeIndex + 1 + end + end + image.height = num + if not image.width or writeIndex - 1 > image.width then + image.width = writeIndex - 1 + end + num = num+1 + end + return image end function NFT.load(path) - local imageText = Util.readFile(path) - if not imageText then - error('Unable to read image file') - end - return NFT.parse(imageText) + local imageText = Util.readFile(path) + if not imageText then + error('Unable to read image file') + end + return NFT.parse(imageText) end return NFT diff --git a/sys/apis/peripheral.lua b/sys/apis/peripheral.lua index 0f7e2da..aa96c56 100644 --- a/sys/apis/peripheral.lua +++ b/sys/apis/peripheral.lua @@ -1,4 +1,4 @@ -local Event = require('event') +local Event = require('event') local Socket = require('socket') local Util = require('util') @@ -7,235 +7,235 @@ local os = _G.os local Peripheral = Util.shallowCopy(_G.peripheral) function Peripheral.getList() - if _G.device then - return _G.device - end + if _G.device then + return _G.device + end - local deviceList = { } - for _,side in pairs(Peripheral.getNames()) do - Peripheral.addDevice(deviceList, side) - end + local deviceList = { } + for _,side in pairs(Peripheral.getNames()) do + Peripheral.addDevice(deviceList, side) + end - return deviceList + return deviceList end function Peripheral.addDevice(deviceList, side) - local name = side - local ptype = Peripheral.getType(side) + local name = side + local ptype = Peripheral.getType(side) - if not ptype then - return - end + if not ptype then + return + end - if ptype == 'modem' then - if Peripheral.call(name, 'isWireless') then - ptype = 'wireless_modem' - else - ptype = 'wired_modem' - end - end + if ptype == 'modem' then + if Peripheral.call(name, 'isWireless') then + ptype = 'wireless_modem' + else + ptype = 'wired_modem' + end + end - local sides = { - front = true, - back = true, - top = true, - bottom = true, - left = true, - right = true - } + local sides = { + front = true, + back = true, + top = true, + bottom = true, + left = true, + right = true + } - if sides[name] then - local i = 1 - local uniqueName = ptype - while deviceList[uniqueName] do - uniqueName = ptype .. '_' .. i - i = i + 1 - end - name = uniqueName - end + if sides[name] then + local i = 1 + local uniqueName = ptype + while deviceList[uniqueName] do + uniqueName = ptype .. '_' .. i + i = i + 1 + end + name = uniqueName + end - local s, m = pcall(function() deviceList[name] = Peripheral.wrap(side) end) - if not s and m then - _G.printError('wrap failed') - _G.printError(m) - end + local s, m = pcall(function() deviceList[name] = Peripheral.wrap(side) end) + if not s and m then + _G.printError('wrap failed') + _G.printError(m) + end - if deviceList[name] then - Util.merge(deviceList[name], { - name = name, - type = ptype, - side = side, - }) + if deviceList[name] then + Util.merge(deviceList[name], { + name = name, + type = ptype, + side = side, + }) - return deviceList[name] - end + return deviceList[name] + end end function Peripheral.getBySide(side) - return Util.find(Peripheral.getList(), 'side', side) + return Util.find(Peripheral.getList(), 'side', side) end function Peripheral.getByType(typeName) - return Util.find(Peripheral.getList(), 'type', typeName) + return Util.find(Peripheral.getList(), 'type', typeName) end function Peripheral.getByMethod(method) - for _,p in pairs(Peripheral.getList()) do - if p[method] then - return p - end - end + for _,p in pairs(Peripheral.getList()) do + if p[method] then + return p + end + end end -- match any of the passed arguments function Peripheral.get(args) - if type(args) == 'string' then - args = { type = args } - end + if type(args) == 'string' then + args = { type = args } + end - if args.name then - return _G.device[args.name] - end + if args.name then + return _G.device[args.name] + end - if args.type then - local p = Peripheral.getByType(args.type) - if p then - return p - end - end + if args.type then + local p = Peripheral.getByType(args.type) + if p then + return p + end + end - if args.method then - local p = Peripheral.getByMethod(args.method) - if p then - return p - end - end + if args.method then + local p = Peripheral.getByMethod(args.method) + if p then + return p + end + end - if args.side then - local p = Peripheral.getBySide(args.side) - if p then - return p - end - end + if args.side then + local p = Peripheral.getBySide(args.side) + if p then + return p + end + end end local function getProxy(pi) - local socket, msg = Socket.connect(pi.host, 189) + local socket, msg = Socket.connect(pi.host, 189) - if not socket then - error("Timed out attaching peripheral: " .. pi.uri .. '\n' .. msg) - end + if not socket then + error("Timed out attaching peripheral: " .. pi.uri .. '\n' .. msg) + end - -- write the uri of the periperal we are requesting... - -- ie. type/monitor - socket:write(pi.path) - local proxy = socket:read(3) + -- write the uri of the periperal we are requesting... + -- ie. type/monitor + socket:write(pi.path) + local proxy = socket:read(3) - if not proxy then - error("Timed out attaching peripheral: " .. pi.uri) - end + if not proxy then + error("Timed out attaching peripheral: " .. pi.uri) + end - if type(proxy) == 'string' then - error(proxy) - end + if type(proxy) == 'string' then + error(proxy) + end - local methods = proxy.methods - proxy.methods = nil + local methods = proxy.methods + proxy.methods = nil - for _,method in pairs(methods) do - proxy[method] = function(...) - socket:write({ fn = method, args = { ... } }) - local resp = socket:read() - if not resp then - error("Timed out communicating with peripheral: " .. pi.uri) - end - return table.unpack(resp) - end - end + for _,method in pairs(methods) do + proxy[method] = function(...) + socket:write({ fn = method, args = { ... } }) + local resp = socket:read() + if not resp then + error("Timed out communicating with peripheral: " .. pi.uri) + end + return table.unpack(resp) + end + end - if proxy.blit then - local methods = { 'clear', 'clearLine', 'setCursorPos', 'write', 'blit', - 'setTextColor', 'setTextColour', 'setBackgroundColor', - 'setBackgroundColour', 'scroll', 'setCursorBlink', } - local queue = nil + if proxy.blit then + local methods = { 'clear', 'clearLine', 'setCursorPos', 'write', 'blit', + 'setTextColor', 'setTextColour', 'setBackgroundColor', + 'setBackgroundColour', 'scroll', 'setCursorBlink', } + local queue = nil - for _,method in pairs(methods) do - proxy[method] = function(...) - if not queue then - queue = { } - Event.onTimeout(0, function() - if not socket:write({ fn = 'fastBlit', args = { queue } }) then - error("Timed out communicating with peripheral: " .. pi.uri) - end - queue = nil - socket:read() - end) - end - if not socket.connected then - error("Timed out communicating with peripheral: " .. pi.uri) - end + for _,method in pairs(methods) do + proxy[method] = function(...) + if not queue then + queue = { } + Event.onTimeout(0, function() + if not socket:write({ fn = 'fastBlit', args = { queue } }) then + error("Timed out communicating with peripheral: " .. pi.uri) + end + queue = nil + socket:read() + end) + end + if not socket.connected then + error("Timed out communicating with peripheral: " .. pi.uri) + end - table.insert(queue, { - fn = method, - args = { ... }, - }) - end - end - end + table.insert(queue, { + fn = method, + args = { ... }, + }) + end + end + end - if proxy.type == 'monitor' then - Event.addRoutine(function() - while true do - local data = socket:read() - if not data then - break - end - if data.fn and data.fn == 'event' then - os.queueEvent(table.unpack(data.data)) - end - end - end) - end + if proxy.type == 'monitor' then + Event.addRoutine(function() + while true do + local data = socket:read() + if not data then + break + end + if data.fn and data.fn == 'event' then + os.queueEvent(table.unpack(data.data)) + end + end + end) + end - return proxy + return proxy end --[[ - Parse a uri into it's components + Parse a uri into it's components - Examples: - monitor = { name = 'monitor' } - side/top = { side = 'top' } - method/list = { method = 'list' } - 12://name/monitor = { host = 12, name = 'monitor' } + Examples: + monitor = { name = 'monitor' } + side/top = { side = 'top' } + method/list = { method = 'list' } + 12://name/monitor = { host = 12, name = 'monitor' } ]]-- local function parse(uri) - local pi = Util.split(uri:gsub('^%d*://', ''), '(.-)/') + local pi = Util.split(uri:gsub('^%d*://', ''), '(.-)/') - if #pi == 1 then - pi = { - 'name', - pi[1], - } - end + if #pi == 1 then + pi = { + 'name', + pi[1], + } + end - return { - host = uri:match('^(%d*)%:'), -- 12 - uri = uri, -- 12://name/monitor - path = uri:gsub('^%d*://', ''), -- name/monitor - [ pi[1] ] = pi[2], -- name = 'monitor' - } + return { + host = uri:match('^(%d*)%:'), -- 12 + uri = uri, -- 12://name/monitor + path = uri:gsub('^%d*://', ''), -- name/monitor + [ pi[1] ] = pi[2], -- name = 'monitor' + } end function Peripheral.lookup(uri) - local pi = parse(uri) + local pi = parse(uri) - if pi.host and _G.device.wireless_modem then - return getProxy(pi) - end + if pi.host and _G.device.wireless_modem then + return getProxy(pi) + end - return Peripheral.get(pi) + return Peripheral.get(pi) end return Peripheral diff --git a/sys/apis/point.lua b/sys/apis/point.lua index f72a936..1bd68ba 100644 --- a/sys/apis/point.lua +++ b/sys/apis/point.lua @@ -3,38 +3,38 @@ local Util = require('util') local Point = { } Point.directions = { - [ 0 ] = { xd = 1, zd = 0, yd = 0, heading = 0, direction = 'east' }, - [ 1 ] = { xd = 0, zd = 1, yd = 0, heading = 1, direction = 'south' }, - [ 2 ] = { xd = -1, zd = 0, yd = 0, heading = 2, direction = 'west' }, - [ 3 ] = { xd = 0, zd = -1, yd = 0, heading = 3, direction = 'north' }, - [ 4 ] = { xd = 0, zd = 0, yd = 1, heading = 4, direction = 'up' }, - [ 5 ] = { xd = 0, zd = 0, yd = -1, heading = 5, direction = 'down' }, + [ 0 ] = { xd = 1, zd = 0, yd = 0, heading = 0, direction = 'east' }, + [ 1 ] = { xd = 0, zd = 1, yd = 0, heading = 1, direction = 'south' }, + [ 2 ] = { xd = -1, zd = 0, yd = 0, heading = 2, direction = 'west' }, + [ 3 ] = { xd = 0, zd = -1, yd = 0, heading = 3, direction = 'north' }, + [ 4 ] = { xd = 0, zd = 0, yd = 1, heading = 4, direction = 'up' }, + [ 5 ] = { xd = 0, zd = 0, yd = -1, heading = 5, direction = 'down' }, } Point.facings = { - [ 0 ] = Point.directions[0], - [ 1 ] = Point.directions[1], - [ 2 ] = Point.directions[2], - [ 3 ] = Point.directions[3], - east = Point.directions[0], - south = Point.directions[1], - west = Point.directions[2], - north = Point.directions[3], + [ 0 ] = Point.directions[0], + [ 1 ] = Point.directions[1], + [ 2 ] = Point.directions[2], + [ 3 ] = Point.directions[3], + east = Point.directions[0], + south = Point.directions[1], + west = Point.directions[2], + north = Point.directions[3], } Point.headings = { - [ 0 ] = Point.directions[0], - [ 1 ] = Point.directions[1], - [ 2 ] = Point.directions[2], - [ 3 ] = Point.directions[3], - [ 4 ] = Point.directions[4], - [ 5 ] = Point.directions[5], - east = Point.directions[0], - south = Point.directions[1], - west = Point.directions[2], - north = Point.directions[3], - up = Point.directions[4], - down = Point.directions[5], + [ 0 ] = Point.directions[0], + [ 1 ] = Point.directions[1], + [ 2 ] = Point.directions[2], + [ 3 ] = Point.directions[3], + [ 4 ] = Point.directions[4], + [ 5 ] = Point.directions[5], + east = Point.directions[0], + south = Point.directions[1], + west = Point.directions[2], + north = Point.directions[3], + up = Point.directions[4], + down = Point.directions[5], } Point.EAST = 0 @@ -45,262 +45,262 @@ Point.UP = 4 Point.DOWN = 5 function Point.copy(pt) - return { x = pt.x, y = pt.y, z = pt.z } + return { x = pt.x, y = pt.y, z = pt.z } end function Point.same(pta, ptb) - return pta.x == ptb.x and - pta.y == ptb.y and - pta.z == ptb.z + return pta.x == ptb.x and + pta.y == ptb.y and + pta.z == ptb.z end function Point.above(pt) - return { x = pt.x, y = pt.y + 1, z = pt.z, heading = pt.heading } + return { x = pt.x, y = pt.y + 1, z = pt.z, heading = pt.heading } end function Point.below(pt) - return { x = pt.x, y = pt.y - 1, z = pt.z, heading = pt.heading } + return { x = pt.x, y = pt.y - 1, z = pt.z, heading = pt.heading } end function Point.subtract(a, b) - a.x = a.x - b.x - a.y = a.y - b.y - a.z = a.z - b.z + a.x = a.x - b.x + a.y = a.y - b.y + a.z = a.z - b.z end -- Euclidian distance function Point.distance(a, b) - return math.sqrt( - math.pow(a.x - b.x, 2) + - math.pow(a.y - b.y, 2) + - math.pow(a.z - b.z, 2)) + return math.sqrt( + math.pow(a.x - b.x, 2) + + math.pow(a.y - b.y, 2) + + math.pow(a.z - b.z, 2)) end -- turtle distance (manhattan) function Point.turtleDistance(a, b) - if a.y and b.y then - return math.abs(a.x - b.x) + - math.abs(a.y - b.y) + - math.abs(a.z - b.z) - else - return math.abs(a.x - b.x) + - math.abs(a.z - b.z) - end + if a.y and b.y then + return math.abs(a.x - b.x) + + math.abs(a.y - b.y) + + math.abs(a.z - b.z) + else + return math.abs(a.x - b.x) + + math.abs(a.z - b.z) + end end function Point.calculateTurns(ih, oh) - if ih == oh then - return 0 - end - if (ih % 2) == (oh % 2) then - return 2 - end - return 1 + if ih == oh then + return 0 + end + if (ih % 2) == (oh % 2) then + return 2 + end + return 1 end function Point.calculateHeading(pta, ptb) - local heading - local xd, zd = pta.x - ptb.x, pta.z - ptb.z + local heading + local xd, zd = pta.x - ptb.x, pta.z - ptb.z - if (pta.heading % 2) == 0 and zd ~= 0 then - heading = zd < 0 and 1 or 3 - elseif (pta.heading % 2) == 1 and xd ~= 0 then - heading = xd < 0 and 0 or 2 - elseif pta.heading == 0 and xd > 0 then - heading = 2 - elseif pta.heading == 2 and xd < 0 then - heading = 0 - elseif pta.heading == 1 and zd > 0 then - heading = 3 - elseif pta.heading == 3 and zd < 0 then - heading = 1 - end + if (pta.heading % 2) == 0 and zd ~= 0 then + heading = zd < 0 and 1 or 3 + elseif (pta.heading % 2) == 1 and xd ~= 0 then + heading = xd < 0 and 0 or 2 + elseif pta.heading == 0 and xd > 0 then + heading = 2 + elseif pta.heading == 2 and xd < 0 then + heading = 0 + elseif pta.heading == 1 and zd > 0 then + heading = 3 + elseif pta.heading == 3 and zd < 0 then + heading = 1 + end - return heading or pta.heading + return heading or pta.heading end -- Calculate distance to location including turns -- also returns the resulting heading function Point.calculateMoves(pta, ptb, distance) - local heading = pta.heading - local moves = distance or Point.turtleDistance(pta, ptb) - if (pta.heading % 2) == 0 and pta.z ~= ptb.z then - moves = moves + 1 - if ptb.heading and (ptb.heading % 2 == 1) then - heading = ptb.heading - elseif ptb.z > pta.z then - heading = 1 - else - heading = 3 - end - elseif (pta.heading % 2) == 1 and pta.x ~= ptb.x then - moves = moves + 1 - if ptb.heading and (ptb.heading % 2 == 0) then - heading = ptb.heading - elseif ptb.x > pta.x then - heading = 0 - else - heading = 2 - end - end + local heading = pta.heading + local moves = distance or Point.turtleDistance(pta, ptb) + if (pta.heading % 2) == 0 and pta.z ~= ptb.z then + moves = moves + 1 + if ptb.heading and (ptb.heading % 2 == 1) then + heading = ptb.heading + elseif ptb.z > pta.z then + heading = 1 + else + heading = 3 + end + elseif (pta.heading % 2) == 1 and pta.x ~= ptb.x then + moves = moves + 1 + if ptb.heading and (ptb.heading % 2 == 0) then + heading = ptb.heading + elseif ptb.x > pta.x then + heading = 0 + else + heading = 2 + end + end - if ptb.heading then - if heading ~= ptb.heading then - moves = moves + Point.calculateTurns(heading, ptb.heading) - heading = ptb.heading - end - end + if ptb.heading then + if heading ~= ptb.heading then + moves = moves + Point.calculateTurns(heading, ptb.heading) + heading = ptb.heading + end + end - return moves, heading + return moves, heading end -- given a set of points, find the one taking the least moves function Point.closest(reference, pts) - if #pts == 1 then - return pts[1] - end + if #pts == 1 then + return pts[1] + end - local lm, lpt = math.huge - for _,pt in pairs(pts) do - local distance = Point.turtleDistance(reference, pt) - if distance < lm then - local m = Point.calculateMoves(reference, pt, distance) - if m < lm then - lpt = pt - lm = m - end - end - end - return lpt + local lm, lpt = math.huge + for _,pt in pairs(pts) do + local distance = Point.turtleDistance(reference, pt) + if distance < lm then + local m = Point.calculateMoves(reference, pt, distance) + if m < lm then + lpt = pt + lm = m + end + end + end + return lpt end function Point.eachClosest(spt, ipts, fn) - local pts = Util.shallowCopy(ipts) - while #pts > 0 do - local pt = Point.closest(spt, pts) - local r = fn(pt) - if r then - return r - end - Util.removeByValue(pts, pt) - end + local pts = Util.shallowCopy(ipts) + while #pts > 0 do + local pt = Point.closest(spt, pts) + local r = fn(pt) + if r then + return r + end + Util.removeByValue(pts, pt) + end end function Point.adjacentPoints(pt) - local pts = { } + local pts = { } - for i = 0, 5 do - local hi = Point.headings[i] - table.insert(pts, { x = pt.x + hi.xd, y = pt.y + hi.yd, z = pt.z + hi.zd }) - end + for i = 0, 5 do + local hi = Point.headings[i] + table.insert(pts, { x = pt.x + hi.xd, y = pt.y + hi.yd, z = pt.z + hi.zd }) + end - return pts + return pts end -- get the point nearest A that is in the direction of B function Point.nearestTo(pta, ptb) - local heading + local heading - if pta.x < ptb.x then - heading = 0 - elseif pta.z < ptb.z then - heading = 1 - elseif pta.x > ptb.x then - heading = 2 - elseif pta.z > ptb.z then - heading = 3 - elseif pta.y < ptb.y then - heading = 4 - elseif pta.y > ptb.y then - heading = 5 - end + if pta.x < ptb.x then + heading = 0 + elseif pta.z < ptb.z then + heading = 1 + elseif pta.x > ptb.x then + heading = 2 + elseif pta.z > ptb.z then + heading = 3 + elseif pta.y < ptb.y then + heading = 4 + elseif pta.y > ptb.y then + heading = 5 + end - if heading then - return { - x = pta.x + Point.headings[heading].xd, - y = pta.y + Point.headings[heading].yd, - z = pta.z + Point.headings[heading].zd, - } - end + if heading then + return { + x = pta.x + Point.headings[heading].xd, + y = pta.y + Point.headings[heading].yd, + z = pta.z + Point.headings[heading].zd, + } + end - return pta -- error ? + return pta -- error ? end function Point.rotate(pt, facing) - local x, z = pt.x, pt.z - if facing == 1 then - pt.x = z - pt.z = -x - elseif facing == 2 then - pt.x = -x - pt.z = -z - elseif facing == 3 then - pt.x = -z - pt.z = x - end + local x, z = pt.x, pt.z + if facing == 1 then + pt.x = z + pt.z = -x + elseif facing == 2 then + pt.x = -x + pt.z = -z + elseif facing == 3 then + pt.x = -z + pt.z = x + end end function Point.makeBox(pt1, pt2) - return { - x = pt1.x, - y = pt1.y, - z = pt1.z, - ex = pt2.x, - ey = pt2.y, - ez = pt2.z, - } + return { + x = pt1.x, + y = pt1.y, + z = pt1.z, + ex = pt2.x, + ey = pt2.y, + ez = pt2.z, + } end -- expand box to include point function Point.expandBox(box, pt) - if pt.x < box.x then - box.x = pt.x - elseif pt.x > box.ex then - box.ex = pt.x - end - if pt.y < box.y then - box.y = pt.y - elseif pt.y > box.ey then - box.ey = pt.y - end - if pt.z < box.z then - box.z = pt.z - elseif pt.z > box.ez then - box.ez = pt.z - end + if pt.x < box.x then + box.x = pt.x + elseif pt.x > box.ex then + box.ex = pt.x + end + if pt.y < box.y then + box.y = pt.y + elseif pt.y > box.ey then + box.ey = pt.y + end + if pt.z < box.z then + box.z = pt.z + elseif pt.z > box.ez then + box.ez = pt.z + end end function Point.normalizeBox(box) - return { - x = math.min(box.x, box.ex), - y = math.min(box.y, box.ey), - z = math.min(box.z, box.ez), - ex = math.max(box.x, box.ex), - ey = math.max(box.y, box.ey), - ez = math.max(box.z, box.ez), - } + return { + x = math.min(box.x, box.ex), + y = math.min(box.y, box.ey), + z = math.min(box.z, box.ez), + ex = math.max(box.x, box.ex), + ey = math.max(box.y, box.ey), + ez = math.max(box.z, box.ez), + } end function Point.inBox(pt, box) - return pt.x >= box.x and - pt.y >= box.y and - pt.z >= box.z and - pt.x <= box.ex and - pt.y <= box.ey and - pt.z <= box.ez + return pt.x >= box.x and + pt.y >= box.y and + pt.z >= box.z and + pt.x <= box.ex and + pt.y <= box.ey and + pt.z <= box.ez end function Point.closestPointInBox(pt, box) - local cpt = { - x = math.abs(pt.x - box.x) < math.abs(pt.x - box.ex) and box.x or box.ex, - y = math.abs(pt.y - box.y) < math.abs(pt.y - box.ey) and box.y or box.ey, - z = math.abs(pt.z - box.z) < math.abs(pt.z - box.ez) and box.z or box.ez, - } - cpt.x = pt.x > box.x and pt.x < box.ex and pt.x or cpt.x - cpt.y = pt.y > box.y and pt.y < box.ey and pt.y or cpt.y - cpt.z = pt.z > box.z and pt.z < box.ez and pt.z or cpt.z + local cpt = { + x = math.abs(pt.x - box.x) < math.abs(pt.x - box.ex) and box.x or box.ex, + y = math.abs(pt.y - box.y) < math.abs(pt.y - box.ey) and box.y or box.ey, + z = math.abs(pt.z - box.z) < math.abs(pt.z - box.ez) and box.z or box.ez, + } + cpt.x = pt.x > box.x and pt.x < box.ex and pt.x or cpt.x + cpt.y = pt.y > box.y and pt.y < box.ey and pt.y or cpt.y + cpt.z = pt.z > box.z and pt.z < box.ez and pt.z or cpt.z - return cpt + return cpt end return Point diff --git a/sys/apis/security.lua b/sys/apis/security.lua index 9390a09..24d24d4 100644 --- a/sys/apis/security.lua +++ b/sys/apis/security.lua @@ -5,56 +5,56 @@ local config = { } local Security = { } function Security.verifyPassword(password) - Config.load('os', config) - return config.password and password == config.password + Config.load('os', config) + return config.password and password == config.password end function Security.hasPassword() - return not not config.password + return not not config.password end function Security.getSecretKey() - Config.load('os', config) - if not config.secretKey then - config.secretKey = math.random(100000, 999999) - Config.update('os', config) - end - return config.secretKey + Config.load('os', config) + if not config.secretKey then + config.secretKey = math.random(100000, 999999) + Config.update('os', config) + end + return config.secretKey end function Security.getPublicKey() - local exchange = { - base = 11, - primeMod = 625210769 - } + local exchange = { + base = 11, + primeMod = 625210769 + } - local function modexp(base, exponent, modulo) - local remainder = base + local function modexp(base, exponent, modulo) + local remainder = base - for _ = 1, exponent-1 do - remainder = remainder * remainder - if remainder >= modulo then - remainder = remainder % modulo - end - end + for _ = 1, exponent-1 do + remainder = remainder * remainder + if remainder >= modulo then + remainder = remainder % modulo + end + end - return remainder - end + return remainder + end - local secretKey = Security.getSecretKey() - return modexp(exchange.base, secretKey, exchange.primeMod) + local secretKey = Security.getSecretKey() + return modexp(exchange.base, secretKey, exchange.primeMod) end function Security.updatePassword(password) - Config.load('os', config) - config.password = password - Config.update('os', config) + Config.load('os', config) + config.password = password + Config.update('os', config) end function Security.getPassword() - Config.load('os', config) - return config.password + Config.load('os', config) + return config.password end return Security diff --git a/sys/apis/sha1.lua b/sys/apis/sha1.lua index 9a8fc8c..0be5024 100644 --- a/sys/apis/sha1.lua +++ b/sys/apis/sha1.lua @@ -1,35 +1,18 @@ local sha1 = { - _VERSION = "sha.lua 0.5.0", - _URL = "https://github.com/kikito/sha.lua", - _DESCRIPTION = [[ - SHA-1 secure hash computation, and HMAC-SHA1 signature computation in Lua (5.1) - Based on code originally by Jeffrey Friedl (http://regex.info/blog/lua/sha1) - And modified by Eike Decker - (http://cube3d.de/uploads/Main/sha1.txt) - ]], - _LICENSE = [[ - MIT LICENSE + _VERSION = "sha.lua 0.5.0", + _URL = "https://github.com/kikito/sha.lua", + _DESCRIPTION = [[ + SHA-1 secure hash computation, and HMAC-SHA1 signature computation in Lua (5.1) + Based on code originally by Jeffrey Friedl (http://regex.info/blog/lua/sha1) + And modified by Eike Decker - (http://cube3d.de/uploads/Main/sha1.txt) + ]], + _LICENSE = [[ + MIT LICENSE - Copyright (c) 2013 Enrique Garcia Cota + Eike Decker + Jeffrey Friedl + Copyright (c) 2013 Enrique Garcia Cota + Eike Decker + Jeffrey Friedl - Permission is hereby granted, free of charge, to any person obtaining a - copy of this software and associated documentation files (the - "Software"), to deal in the Software without restriction, including - without limitation the rights to use, copy, modify, merge, publish, - distribute, sublicense, and/or sell copies of the Software, and to - permit persons to whom the Software is furnished to do so, subject to - the following conditions: - - The above copyright notice and this permission notice shall be included - in all copies or substantial portions of the Software. - - THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS - OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF - MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. - IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY - CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, - TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE - SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. - ]] + https://opensource.org/licenses/MIT + ]] } ----------------------------------------------------------------------------------- @@ -47,85 +30,85 @@ local char,format,rep = string.char,string.format,string.rep local function bytes_to_w32(a,b,c,d) return a*0x1000000+b*0x10000+c*0x100+d end -- split a 32 bit word into four 8 bit numbers local function w32_to_bytes(i) - return floor(i/0x1000000)%0x100,floor(i/0x10000)%0x100,floor(i/0x100)%0x100,i%0x100 + return floor(i/0x1000000)%0x100,floor(i/0x10000)%0x100,floor(i/0x100)%0x100,i%0x100 end -- shift the bits of a 32 bit word. Don't use negative values for "bits" local function w32_rot(bits,a) - local b2 = 2^(32-bits) - local a,b = modf(a/b2) - return a+b*b2*(2^(bits)) + local b2 = 2^(32-bits) + local a,b = modf(a/b2) + return a+b*b2*(2^(bits)) end -- caching function for functions that accept 2 arguments, both of values between -- 0 and 255. The function to be cached is passed, all values are calculated -- during loading and a function is returned that returns the cached values (only) local function cache2arg(fn) - if not PRELOAD_CACHE then return fn end - local lut = {} - for i=0,0xffff do - local a,b = floor(i/0x100),i%0x100 - lut[i] = fn(a,b) - end - return function(a,b) - return lut[a*0x100+b] - end + if not PRELOAD_CACHE then return fn end + local lut = {} + for i=0,0xffff do + local a,b = floor(i/0x100),i%0x100 + lut[i] = fn(a,b) + end + return function(a,b) + return lut[a*0x100+b] + end end -- splits an 8-bit number into 8 bits, returning all 8 bits as booleans local function byte_to_bits(b) - local b = function(n) - local b = floor(b/n) - return b%2==1 - end - return b(1),b(2),b(4),b(8),b(16),b(32),b(64),b(128) + local b = function(n) + local b = floor(b/n) + return b%2==1 + end + return b(1),b(2),b(4),b(8),b(16),b(32),b(64),b(128) end -- builds an 8bit number from 8 booleans local function bits_to_byte(a,b,c,d,e,f,g,h) - local function n(b,x) return b and x or 0 end - return n(a,1)+n(b,2)+n(c,4)+n(d,8)+n(e,16)+n(f,32)+n(g,64)+n(h,128) + local function n(b,x) return b and x or 0 end + return n(a,1)+n(b,2)+n(c,4)+n(d,8)+n(e,16)+n(f,32)+n(g,64)+n(h,128) end -- bitwise "and" function for 2 8bit number local band = cache2arg (function(a,b) - local A,B,C,D,E,F,G,H = byte_to_bits(b) - local a,b,c,d,e,f,g,h = byte_to_bits(a) - return bits_to_byte( - A and a, B and b, C and c, D and d, - E and e, F and f, G and g, H and h) + local A,B,C,D,E,F,G,H = byte_to_bits(b) + local a,b,c,d,e,f,g,h = byte_to_bits(a) + return bits_to_byte( + A and a, B and b, C and c, D and d, + E and e, F and f, G and g, H and h) end) -- bitwise "or" function for 2 8bit numbers local bor = cache2arg(function(a,b) - local A,B,C,D,E,F,G,H = byte_to_bits(b) - local a,b,c,d,e,f,g,h = byte_to_bits(a) - return bits_to_byte( - A or a, B or b, C or c, D or d, - E or e, F or f, G or g, H or h) + local A,B,C,D,E,F,G,H = byte_to_bits(b) + local a,b,c,d,e,f,g,h = byte_to_bits(a) + return bits_to_byte( + A or a, B or b, C or c, D or d, + E or e, F or f, G or g, H or h) end) -- bitwise "xor" function for 2 8bit numbers local bxor = cache2arg(function(a,b) - local A,B,C,D,E,F,G,H = byte_to_bits(b) - local a,b,c,d,e,f,g,h = byte_to_bits(a) - return bits_to_byte( - A ~= a, B ~= b, C ~= c, D ~= d, - E ~= e, F ~= f, G ~= g, H ~= h) + local A,B,C,D,E,F,G,H = byte_to_bits(b) + local a,b,c,d,e,f,g,h = byte_to_bits(a) + return bits_to_byte( + A ~= a, B ~= b, C ~= c, D ~= d, + E ~= e, F ~= f, G ~= g, H ~= h) end) -- bitwise complement for one 8bit number local function bnot(x) - return 255-(x % 256) + return 255-(x % 256) end -- creates a function to combine to 32bit numbers using an 8bit combination function local function w32_comb(fn) - return function(a,b) - local aa,ab,ac,ad = w32_to_bytes(a) - local ba,bb,bc,bd = w32_to_bytes(b) - return bytes_to_w32(fn(aa,ba),fn(ab,bb),fn(ac,bc),fn(ad,bd)) - end + return function(a,b) + local aa,ab,ac,ad = w32_to_bytes(a) + local ba,bb,bc,bd = w32_to_bytes(b) + return bytes_to_w32(fn(aa,ba),fn(ab,bb),fn(ac,bc),fn(ad,bd)) + end end -- create functions for and, xor and or, all for 2 32bit numbers @@ -135,27 +118,27 @@ local w32_or = w32_comb(bor) -- xor function that may receive a variable number of arguments local function w32_xor_n(a,...) - local aa,ab,ac,ad = w32_to_bytes(a) - for i=1,select('#',...) do - local ba,bb,bc,bd = w32_to_bytes(select(i,...)) - aa,ab,ac,ad = bxor(aa,ba),bxor(ab,bb),bxor(ac,bc),bxor(ad,bd) - end - return bytes_to_w32(aa,ab,ac,ad) + local aa,ab,ac,ad = w32_to_bytes(a) + for i=1,select('#',...) do + local ba,bb,bc,bd = w32_to_bytes(select(i,...)) + aa,ab,ac,ad = bxor(aa,ba),bxor(ab,bb),bxor(ac,bc),bxor(ad,bd) + end + return bytes_to_w32(aa,ab,ac,ad) end -- combining 3 32bit numbers through binary "or" operation local function w32_or3(a,b,c) - local aa,ab,ac,ad = w32_to_bytes(a) - local ba,bb,bc,bd = w32_to_bytes(b) - local ca,cb,cc,cd = w32_to_bytes(c) - return bytes_to_w32( - bor(aa,bor(ba,ca)), bor(ab,bor(bb,cb)), bor(ac,bor(bc,cc)), bor(ad,bor(bd,cd)) - ) + local aa,ab,ac,ad = w32_to_bytes(a) + local ba,bb,bc,bd = w32_to_bytes(b) + local ca,cb,cc,cd = w32_to_bytes(c) + return bytes_to_w32( + bor(aa,bor(ba,ca)), bor(ab,bor(bb,cb)), bor(ac,bor(bc,cc)), bor(ad,bor(bd,cd)) + ) end -- binary complement for 32bit numbers local function w32_not(a) - return 4294967295-(a % 4294967296) + return 4294967295-(a % 4294967296) end -- adding 2 32bit numbers, cutting off the remainder on 33th bit @@ -163,18 +146,18 @@ local function w32_add(a,b) return (a+b) % 4294967296 end -- adding n 32bit numbers, cutting off the remainder (again) local function w32_add_n(a,...) - for i=1,select('#',...) do - a = (a+select(i,...)) % 4294967296 - end - return a + for i=1,select('#',...) do + a = (a+select(i,...)) % 4294967296 + end + return a end -- converting the number to a hexadecimal string local function w32_to_hexstring(w) return format("%08x",w) end local function hex_to_binary(hex) - return hex:gsub('..', function(hexval) - return string.char(tonumber(hexval, 16)) - end) + return hex:gsub('..', function(hexval) + return string.char(tonumber(hexval, 16)) + end) end -- building the lookuptables ahead of time (instead of littering the source code @@ -182,114 +165,114 @@ end local xor_with_0x5c = {} local xor_with_0x36 = {} for i=0,0xff do - xor_with_0x5c[char(i)] = char(bxor(i,0x5c)) - xor_with_0x36[char(i)] = char(bxor(i,0x36)) + xor_with_0x5c[char(i)] = char(bxor(i,0x5c)) + xor_with_0x36[char(i)] = char(bxor(i,0x36)) end ----------------------------------------------------------------------------- -- calculating the SHA1 for some text function sha1.sha1(msg) - local H0,H1,H2,H3,H4 = 0x67452301,0xEFCDAB89,0x98BADCFE,0x10325476,0xC3D2E1F0 - local msg_len_in_bits = #msg * 8 + local H0,H1,H2,H3,H4 = 0x67452301,0xEFCDAB89,0x98BADCFE,0x10325476,0xC3D2E1F0 + local msg_len_in_bits = #msg * 8 - local first_append = char(0x80) -- append a '1' bit plus seven '0' bits + local first_append = char(0x80) -- append a '1' bit plus seven '0' bits - local non_zero_message_bytes = #msg +1 +8 -- the +1 is the appended bit 1, the +8 are for the final appended length - local current_mod = non_zero_message_bytes % 64 - local second_append = current_mod>0 and rep(char(0), 64 - current_mod) or "" + local non_zero_message_bytes = #msg +1 +8 -- the +1 is the appended bit 1, the +8 are for the final appended length + local current_mod = non_zero_message_bytes % 64 + local second_append = current_mod>0 and rep(char(0), 64 - current_mod) or "" - -- now to append the length as a 64-bit number. - local B1, R1 = modf(msg_len_in_bits / 0x01000000) - local B2, R2 = modf( 0x01000000 * R1 / 0x00010000) - local B3, R3 = modf( 0x00010000 * R2 / 0x00000100) - local B4 = 0x00000100 * R3 + -- now to append the length as a 64-bit number. + local B1, R1 = modf(msg_len_in_bits / 0x01000000) + local B2, R2 = modf( 0x01000000 * R1 / 0x00010000) + local B3, R3 = modf( 0x00010000 * R2 / 0x00000100) + local B4 = 0x00000100 * R3 - local L64 = char( 0) .. char( 0) .. char( 0) .. char( 0) -- high 32 bits - .. char(B1) .. char(B2) .. char(B3) .. char(B4) -- low 32 bits + local L64 = char( 0) .. char( 0) .. char( 0) .. char( 0) -- high 32 bits + .. char(B1) .. char(B2) .. char(B3) .. char(B4) -- low 32 bits - msg = msg .. first_append .. second_append .. L64 + msg = msg .. first_append .. second_append .. L64 - assert(#msg % 64 == 0) + assert(#msg % 64 == 0) - local chunks = #msg / 64 + local chunks = #msg / 64 - local W = { } - local start, A, B, C, D, E, f, K, TEMP - local chunk = 0 + local W = { } + local start, A, B, C, D, E, f, K, TEMP + local chunk = 0 - while chunk < chunks do - -- - -- break chunk up into W[0] through W[15] - -- - start,chunk = chunk * 64 + 1,chunk + 1 + while chunk < chunks do + -- + -- break chunk up into W[0] through W[15] + -- + start,chunk = chunk * 64 + 1,chunk + 1 - for t = 0, 15 do - W[t] = bytes_to_w32(msg:byte(start, start + 3)) - start = start + 4 - end + for t = 0, 15 do + W[t] = bytes_to_w32(msg:byte(start, start + 3)) + start = start + 4 + end - -- - -- build W[16] through W[79] - -- - for t = 16, 79 do - -- For t = 16 to 79 let Wt = S1(Wt-3 XOR Wt-8 XOR Wt-14 XOR Wt-16). - W[t] = w32_rot(1, w32_xor_n(W[t-3], W[t-8], W[t-14], W[t-16])) - end + -- + -- build W[16] through W[79] + -- + for t = 16, 79 do + -- For t = 16 to 79 let Wt = S1(Wt-3 XOR Wt-8 XOR Wt-14 XOR Wt-16). + W[t] = w32_rot(1, w32_xor_n(W[t-3], W[t-8], W[t-14], W[t-16])) + end - A,B,C,D,E = H0,H1,H2,H3,H4 + A,B,C,D,E = H0,H1,H2,H3,H4 - for t = 0, 79 do - if t <= 19 then - -- (B AND C) OR ((NOT B) AND D) - f = w32_or(w32_and(B, C), w32_and(w32_not(B), D)) - K = 0x5A827999 - elseif t <= 39 then - -- B XOR C XOR D - f = w32_xor_n(B, C, D) - K = 0x6ED9EBA1 - elseif t <= 59 then - -- (B AND C) OR (B AND D) OR (C AND D - f = w32_or3(w32_and(B, C), w32_and(B, D), w32_and(C, D)) - K = 0x8F1BBCDC - else - -- B XOR C XOR D - f = w32_xor_n(B, C, D) - K = 0xCA62C1D6 - end + for t = 0, 79 do + if t <= 19 then + -- (B AND C) OR ((NOT B) AND D) + f = w32_or(w32_and(B, C), w32_and(w32_not(B), D)) + K = 0x5A827999 + elseif t <= 39 then + -- B XOR C XOR D + f = w32_xor_n(B, C, D) + K = 0x6ED9EBA1 + elseif t <= 59 then + -- (B AND C) OR (B AND D) OR (C AND D + f = w32_or3(w32_and(B, C), w32_and(B, D), w32_and(C, D)) + K = 0x8F1BBCDC + else + -- B XOR C XOR D + f = w32_xor_n(B, C, D) + K = 0xCA62C1D6 + end - -- TEMP = S5(A) + ft(B,C,D) + E + Wt + Kt; - A,B,C,D,E = w32_add_n(w32_rot(5, A), f, E, W[t], K), - A, w32_rot(30, B), C, D - end - -- Let H0 = H0 + A, H1 = H1 + B, H2 = H2 + C, H3 = H3 + D, H4 = H4 + E. - H0,H1,H2,H3,H4 = w32_add(H0, A),w32_add(H1, B),w32_add(H2, C),w32_add(H3, D),w32_add(H4, E) - end - local f = w32_to_hexstring - return f(H0) .. f(H1) .. f(H2) .. f(H3) .. f(H4) + -- TEMP = S5(A) + ft(B,C,D) + E + Wt + Kt; + A,B,C,D,E = w32_add_n(w32_rot(5, A), f, E, W[t], K), + A, w32_rot(30, B), C, D + end + -- Let H0 = H0 + A, H1 = H1 + B, H2 = H2 + C, H3 = H3 + D, H4 = H4 + E. + H0,H1,H2,H3,H4 = w32_add(H0, A),w32_add(H1, B),w32_add(H2, C),w32_add(H3, D),w32_add(H4, E) + end + local f = w32_to_hexstring + return f(H0) .. f(H1) .. f(H2) .. f(H3) .. f(H4) end function sha1.binary(msg) - return hex_to_binary(sha1.sha1(msg)) + return hex_to_binary(sha1.sha1(msg)) end function sha1.hmac(key, text) - assert(type(key) == 'string', "key passed to sha1.hmac should be a string") - assert(type(text) == 'string', "text passed to sha1.hmac should be a string") + assert(type(key) == 'string', "key passed to sha1.hmac should be a string") + assert(type(text) == 'string', "text passed to sha1.hmac should be a string") - if #key > BLOCK_SIZE then - key = sha1.binary(key) - end + if #key > BLOCK_SIZE then + key = sha1.binary(key) + end - local key_xord_with_0x36 = key:gsub('.', xor_with_0x36) .. string.rep(string.char(0x36), BLOCK_SIZE - #key) - local key_xord_with_0x5c = key:gsub('.', xor_with_0x5c) .. string.rep(string.char(0x5c), BLOCK_SIZE - #key) + local key_xord_with_0x36 = key:gsub('.', xor_with_0x36) .. string.rep(string.char(0x36), BLOCK_SIZE - #key) + local key_xord_with_0x5c = key:gsub('.', xor_with_0x5c) .. string.rep(string.char(0x5c), BLOCK_SIZE - #key) - return sha1.sha1(key_xord_with_0x5c .. sha1.binary(key_xord_with_0x36 .. text)) + return sha1.sha1(key_xord_with_0x5c .. sha1.binary(key_xord_with_0x36 .. text)) end function sha1.hmac_binary(key, text) - return hex_to_binary(sha1.hmac(key, text)) + return hex_to_binary(sha1.hmac(key, text)) end setmetatable(sha1, {__call = function(_,msg) return sha1.sha1(msg) end }) diff --git a/sys/apis/socket.lua b/sys/apis/socket.lua index 9c0c0d1..19bd292 100644 --- a/sys/apis/socket.lua +++ b/sys/apis/socket.lua @@ -9,211 +9,211 @@ local os = _G.os local socketClass = { } function socketClass:read(timeout) - local data, distance = _G.transport.read(self) - if data then - return data, distance - end + local data, distance = _G.transport.read(self) + if data then + return data, distance + end - if not self.connected then - Logger.log('socket', 'read: No connection') - return - end + if not self.connected then + Logger.log('socket', 'read: No connection') + return + end - local timerId = os.startTimer(timeout or 5) + local timerId = os.startTimer(timeout or 5) - while true do - local e, id = os.pullEvent() + while true do + local e, id = os.pullEvent() - if e == 'transport_' .. self.uid then - data, distance = _G.transport.read(self) - if data then - os.cancelTimer(timerId) - return data, distance - end - if not self.connected then - break - end + if e == 'transport_' .. self.uid then + data, distance = _G.transport.read(self) + if data then + os.cancelTimer(timerId) + return data, distance + end + if not self.connected then + break + end - elseif e == 'timer' and id == timerId then - if timeout or not self.connected then - break - end - timerId = os.startTimer(5) - self:ping() - end - end + elseif e == 'timer' and id == timerId then + if timeout or not self.connected then + break + end + timerId = os.startTimer(5) + self:ping() + end + end end function socketClass:write(data) - if self.connected then - _G.transport.write(self, { - type = 'DATA', - seq = self.wseq, - data = data, - }) - return true - end + if self.connected then + _G.transport.write(self, { + type = 'DATA', + seq = self.wseq, + data = data, + }) + return true + end end function socketClass:ping() - if self.connected then - _G.transport.ping(self) - return true - end + if self.connected then + _G.transport.ping(self) + return true + end end function socketClass:close() - if self.connected then - Logger.log('socket', 'closing socket ' .. self.sport) - self.transmit(self.dport, self.dhost, { - type = 'DISC', - }) - self.connected = false - end - device.wireless_modem.close(self.sport) - _G.transport.close(self) + if self.connected then + Logger.log('socket', 'closing socket ' .. self.sport) + self.transmit(self.dport, self.dhost, { + type = 'DISC', + }) + self.connected = false + end + device.wireless_modem.close(self.sport) + _G.transport.close(self) end local Socket = { } local function loopback(port, sport, msg) - os.queueEvent('modem_message', 'loopback', port, sport, msg, 0) + os.queueEvent('modem_message', 'loopback', port, sport, msg, 0) end local function newSocket(isLoopback) - for i = 16384, 32767 do - if not device.wireless_modem.isOpen(i) then - local socket = { - shost = os.getComputerID(), - sport = i, - transmit = device.wireless_modem.transmit, - wseq = math.random(100, 100000), - rseq = math.random(100, 100000), - timers = { }, - messages = { }, - } - setmetatable(socket, { __index = socketClass }) + for i = 16384, 32767 do + if not device.wireless_modem.isOpen(i) then + local socket = { + shost = os.getComputerID(), + sport = i, + transmit = device.wireless_modem.transmit, + wseq = math.random(100, 100000), + rseq = math.random(100, 100000), + timers = { }, + messages = { }, + } + setmetatable(socket, { __index = socketClass }) - device.wireless_modem.open(socket.sport) + device.wireless_modem.open(socket.sport) - if isLoopback then - socket.transmit = loopback - end - return socket - end - end - error('No ports available') + if isLoopback then + socket.transmit = loopback + end + return socket + end + end + error('No ports available') end function Socket.connect(host, port) - if not device.wireless_modem then - return false, 'Wireless modem not found' - end + if not device.wireless_modem then + return false, 'Wireless modem not found' + end - local socket = newSocket(host == os.getComputerID()) - socket.dhost = tonumber(host) - Logger.log('socket', 'connecting to ' .. port) + local socket = newSocket(host == os.getComputerID()) + socket.dhost = tonumber(host) + Logger.log('socket', 'connecting to ' .. port) - socket.transmit(port, socket.sport, { - type = 'OPEN', - shost = socket.shost, - dhost = socket.dhost, - t = Crypto.encrypt({ ts = os.time(), seq = socket.seq }, Security.getPublicKey()), - rseq = socket.wseq, - wseq = socket.rseq, - }) + socket.transmit(port, socket.sport, { + type = 'OPEN', + shost = socket.shost, + dhost = socket.dhost, + t = Crypto.encrypt({ ts = os.time(), seq = socket.seq }, Security.getPublicKey()), + rseq = socket.wseq, + wseq = socket.rseq, + }) - local timerId = os.startTimer(3) - repeat - local e, id, sport, dport, msg = os.pullEvent() - if e == 'modem_message' and - sport == socket.sport and - msg.dhost == socket.shost then + local timerId = os.startTimer(3) + repeat + local e, id, sport, dport, msg = os.pullEvent() + if e == 'modem_message' and + sport == socket.sport and + msg.dhost == socket.shost then - os.cancelTimer(timerId) + os.cancelTimer(timerId) - if msg.type == 'CONN' then + if msg.type == 'CONN' then - socket.dport = dport - socket.connected = true - Logger.log('socket', 'connection established to %d %d->%d', - host, socket.sport, socket.dport) + socket.dport = dport + socket.connected = true + Logger.log('socket', 'connection established to %d %d->%d', + host, socket.sport, socket.dport) - _G.transport.open(socket) + _G.transport.open(socket) - return socket - elseif msg.type == 'REJE' then - return false, 'Password not set on target or not trusted' - end - end - until e == 'timer' and id == timerId + return socket + elseif msg.type == 'REJE' then + return false, 'Password not set on target or not trusted' + end + end + until e == 'timer' and id == timerId - socket:close() + socket:close() - return false, 'Connection timed out' + return false, 'Connection timed out' end local function trusted(msg, port) - if port == 19 or msg.shost == os.getComputerID() then - -- no auth for trust server or loopback - return true - end + if port == 19 or msg.shost == os.getComputerID() then + -- no auth for trust server or loopback + return true + end - if not Security.hasPassword() then - -- no password has been set on this computer - return true - end + if not Security.hasPassword() then + -- no password has been set on this computer + return true + end - local trustList = Util.readTable('usr/.known_hosts') or { } - local pubKey = trustList[msg.shost] + local trustList = Util.readTable('usr/.known_hosts') or { } + local pubKey = trustList[msg.shost] - if pubKey then - local data = Crypto.decrypt(msg.t or '', pubKey) + if pubKey then + local data = Crypto.decrypt(msg.t or '', pubKey) - --local sharedKey = modexp(pubKey, exchange.secretKey, public.primeMod) - return data.ts and tonumber(data.ts) and math.abs(os.time() - data.ts) < 1 - end + --local sharedKey = modexp(pubKey, exchange.secretKey, public.primeMod) + return data.ts and tonumber(data.ts) and math.abs(os.time() - data.ts) < 1 + end end function Socket.server(port) - device.wireless_modem.open(port) - Logger.log('socket', 'Waiting for connections on port ' .. port) + device.wireless_modem.open(port) + Logger.log('socket', 'Waiting for connections on port ' .. port) - while true do - local _, _, sport, dport, msg = os.pullEvent('modem_message') + while true do + local _, _, sport, dport, msg = os.pullEvent('modem_message') - if sport == port and - msg and - msg.dhost == os.getComputerID() and - msg.type == 'OPEN' then + if sport == port and + msg and + msg.dhost == os.getComputerID() and + msg.type == 'OPEN' then - local socket = newSocket(msg.shost == os.getComputerID()) - socket.dport = dport - socket.dhost = msg.shost - socket.wseq = msg.wseq - socket.rseq = msg.rseq + local socket = newSocket(msg.shost == os.getComputerID()) + socket.dport = dport + socket.dhost = msg.shost + socket.wseq = msg.wseq + socket.rseq = msg.rseq - if trusted(msg, port) then - socket.connected = true - socket.transmit(socket.dport, socket.sport, { - type = 'CONN', - dhost = socket.dhost, - shost = socket.shost, - }) - Logger.log('socket', 'Connection established %d->%d', socket.sport, socket.dport) + if trusted(msg, port) then + socket.connected = true + socket.transmit(socket.dport, socket.sport, { + type = 'CONN', + dhost = socket.dhost, + shost = socket.shost, + }) + Logger.log('socket', 'Connection established %d->%d', socket.sport, socket.dport) - _G.transport.open(socket) - return socket - end + _G.transport.open(socket) + return socket + end - socket.transmit(socket.dport, socket.sport, { - type = 'REJE', - dhost = socket.dhost, - shost = socket.shost, - }) - socket:close() - end - end + socket.transmit(socket.dport, socket.sport, { + type = 'REJE', + dhost = socket.dhost, + shost = socket.shost, + }) + socket:close() + end + end end return Socket diff --git a/sys/apis/terminal.lua b/sys/apis/terminal.lua index 507eda3..fbc860c 100644 --- a/sys/apis/terminal.lua +++ b/sys/apis/terminal.lua @@ -8,327 +8,328 @@ local Terminal = { } -- add scrolling functions to a window function Terminal.scrollable(win, maxScroll) - local lines = { } - local scrollPos = 0 - local oblit, oreposition = win.blit, win.reposition + local lines = { } + local scrollPos = 0 + local oblit, oreposition = win.blit, win.reposition - local palette = { } - for n = 1, 16 do - palette[2 ^ (n - 1)] = _sub("0123456789abcdef", n, n) - end + local palette = { } + for n = 1, 16 do + palette[2 ^ (n - 1)] = _sub("0123456789abcdef", n, n) + end - maxScroll = maxScroll or 100 + maxScroll = maxScroll or 100 - -- should only do if window is visible... - local function redraw() - local _, h = win.getSize() - local x, y = win.getCursorPos() - for i = 1, h do - local line = lines[i + scrollPos] - if line and line.dirty then - win.setCursorPos(1, i) - oblit(line.text, line.fg, line.bg) - line.dirty = false - end - end - win.setCursorPos(x, y) - end + -- should only do if window is visible... + local function redraw() + local _, h = win.getSize() + local x, y = win.getCursorPos() + for i = 1, h do + local line = lines[i + scrollPos] + if line and line.dirty then + win.setCursorPos(1, i) + oblit(line.text, line.fg, line.bg) + line.dirty = false + end + end + win.setCursorPos(x, y) + end - local function scrollTo(p, forceRedraw) - local _, h = win.getSize() - local ms = #lines - h -- max scroll - p = math.min(math.max(p, 0), ms) -- normalize + local function scrollTo(p, forceRedraw) + local _, h = win.getSize() + local ms = #lines - h -- max scroll + p = math.min(math.max(p, 0), ms) -- normalize - if p ~= scrollPos or forceRedraw then - scrollPos = p - for _, line in pairs(lines) do - line.dirty = true - end - end - end + if p ~= scrollPos or forceRedraw then + scrollPos = p + for _, line in pairs(lines) do + line.dirty = true + end + end + end - function win.write(text) - local _, h = win.getSize() + function win.write(text) + local _, h = win.getSize() - scrollTo(#lines - h) - win.blit(tostring(text), - _rep(palette[win.getTextColor()], #text), - _rep(palette[win.getBackgroundColor()], #text)) - local x, y = win.getCursorPos() - win.setCursorPos(x + #text, y) - end + text = tostring(text) or '' + scrollTo(#lines - h) + win.blit(text, + _rep(palette[win.getTextColor()], #text), + _rep(palette[win.getBackgroundColor()], #text)) + local x, y = win.getCursorPos() + win.setCursorPos(x + #text, y) + end - function win.clearLine() - local w, h = win.getSize() - local _, y = win.getCursorPos() + function win.clearLine() + local w, h = win.getSize() + local _, y = win.getCursorPos() - scrollTo(#lines - h) - lines[y + scrollPos] = { - text = _rep(' ', w), - fg = _rep(palette[win.getTextColor()], w), - bg = _rep(palette[win.getBackgroundColor()], w), - dirty = true, - } - redraw() - end + scrollTo(#lines - h) + lines[y + scrollPos] = { + text = _rep(' ', w), + fg = _rep(palette[win.getTextColor()], w), + bg = _rep(palette[win.getBackgroundColor()], w), + dirty = true, + } + redraw() + end - function win.blit(text, fg, bg) - local x, y = win.getCursorPos() - local w, h = win.getSize() + function win.blit(text, fg, bg) + local x, y = win.getCursorPos() + local w, h = win.getSize() - if y > 0 and y <= h and x <= w then - local width = #text + if y > 0 and y <= h and x <= w then + local width = #text - -- fix ffs - if x < 1 then - text = _sub(text, 2 - x) - if bg then - bg = _sub(bg, 2 - x) - end - if bg then - fg = _sub(fg, 2 - x) - end - width = width + x - 1 - x = 1 - end + -- fix ffs + if x < 1 then + text = _sub(text, 2 - x) + if bg then + bg = _sub(bg, 2 - x) + end + if bg then + fg = _sub(fg, 2 - x) + end + width = width + x - 1 + x = 1 + end - if x + width - 1 > w then - text = _sub(text, 1, w - x + 1) - if bg then - bg = _sub(bg, 1, w - x + 1) - end - if bg then - fg = _sub(fg, 1, w - x + 1) - end - width = #text - end + if x + width - 1 > w then + text = _sub(text, 1, w - x + 1) + if bg then + bg = _sub(bg, 1, w - x + 1) + end + if bg then + fg = _sub(fg, 1, w - x + 1) + end + width = #text + end - if width > 0 then - local function replace(sstr, pos, rstr) - if pos == 1 and width == w then - return rstr - elseif pos == 1 then - return rstr .. _sub(sstr, pos+width) - elseif pos + width > w then - return _sub(sstr, 1, pos-1) .. rstr - end - return _sub(sstr, 1, pos-1) .. rstr .. _sub(sstr, pos+width) - end + if width > 0 then + local function replace(sstr, pos, rstr) + if pos == 1 and width == w then + return rstr + elseif pos == 1 then + return rstr .. _sub(sstr, pos+width) + elseif pos + width > w then + return _sub(sstr, 1, pos-1) .. rstr + end + return _sub(sstr, 1, pos-1) .. rstr .. _sub(sstr, pos+width) + end - local line = lines[y + scrollPos] - line.dirty = true - line.text = replace(line.text, x, text, width) - if fg then - line.fg = replace(line.fg, x, fg, width) - end - if bg then - line.bg = replace(line.bg, x, bg, width) - end - end - end - redraw() - end + local line = lines[y + scrollPos] + line.dirty = true + line.text = replace(line.text, x, text, width) + if fg then + line.fg = replace(line.fg, x, fg, width) + end + if bg then + line.bg = replace(line.bg, x, bg, width) + end + end + end + redraw() + end - function win.clear() - local w, h = win.getSize() + function win.clear() + local w, h = win.getSize() - local text = _rep(' ', w) - local fg = _rep(palette[win.getTextColor()], w) - local bg = _rep(palette[win.getBackgroundColor()], w) - lines = { } - for y = 1, h do - lines[y] = { - dirty = true, - text = text, - fg = fg, - bg = bg, - } - end - scrollPos = 0 - redraw() - end + local text = _rep(' ', w) + local fg = _rep(palette[win.getTextColor()], w) + local bg = _rep(palette[win.getBackgroundColor()], w) + lines = { } + for y = 1, h do + lines[y] = { + dirty = true, + text = text, + fg = fg, + bg = bg, + } + end + scrollPos = 0 + redraw() + end - -- doesn't support negative scrolling... - function win.scroll(n) - local w = win.getSize() + -- doesn't support negative scrolling... + function win.scroll(n) + local w = win.getSize() - for _ = 1, n do - lines[#lines + 1] = { - text = _rep(' ', w), - fg = _rep(palette[win.getTextColor()], w), - bg = _rep(palette[win.getBackgroundColor()], w), - } - end + for _ = 1, n do + lines[#lines + 1] = { + text = _rep(' ', w), + fg = _rep(palette[win.getTextColor()], w), + bg = _rep(palette[win.getBackgroundColor()], w), + } + end - while #lines > maxScroll do - table.remove(lines, 1) - end + while #lines > maxScroll do + table.remove(lines, 1) + end - scrollTo(maxScroll, true) - redraw() - end + scrollTo(maxScroll, true) + redraw() + end - function win.scrollUp() - scrollTo(scrollPos - 1) - redraw() - end + function win.scrollUp() + scrollTo(scrollPos - 1) + redraw() + end - function win.scrollDown() - scrollTo(scrollPos + 1) - redraw() - end + function win.scrollDown() + scrollTo(scrollPos + 1) + redraw() + end - function win.reposition(x, y, nw, nh) - local w, h = win.getSize() - local D = (nh or h) - h + function win.reposition(x, y, nw, nh) + local w, h = win.getSize() + local D = (nh or h) - h - if D > 0 then - for _ = 1, D do - lines[#lines + 1] = { - text = _rep(' ', w), - fg = _rep(palette[win.getTextColor()], w), - bg = _rep(palette[win.getBackgroundColor()], w), - } - end - elseif D < 0 then - for _ = D, -1 do - lines[#lines] = nil - end - end - return oreposition(x, y, nw, nh) - end + if D > 0 then + for _ = 1, D do + lines[#lines + 1] = { + text = _rep(' ', w), + fg = _rep(palette[win.getTextColor()], w), + bg = _rep(palette[win.getBackgroundColor()], w), + } + end + elseif D < 0 then + for _ = D, -1 do + lines[#lines] = nil + end + end + return oreposition(x, y, nw, nh) + end - win.clear() + win.clear() end -- get windows contents function Terminal.getContents(win, parent) - local oblit, oscp = parent.blit, parent.setCursorPos - local lines = { } + local oblit, oscp = parent.blit, parent.setCursorPos + local lines = { } - parent.blit = function(text, fg, bg) - lines[#lines + 1] = { - text = text, - fg = fg, - bg = bg, - } - end - parent.setCursorPos = function() end + parent.blit = function(text, fg, bg) + lines[#lines + 1] = { + text = text, + fg = fg, + bg = bg, + } + end + parent.setCursorPos = function() end - win.setVisible(true) - win.redraw() + win.setVisible(true) + win.redraw() - parent.blit = oblit - parent.setCursorPos = oscp + parent.blit = oblit + parent.setCursorPos = oscp - return lines + return lines end function Terminal.toGrayscale(ct) - local scolors = { - [ colors.white ] = colors.white, - [ colors.orange ] = colors.lightGray, - [ colors.magenta ] = colors.lightGray, - [ colors.lightBlue ] = colors.lightGray, - [ colors.yellow ] = colors.lightGray, - [ colors.lime ] = colors.lightGray, - [ colors.pink ] = colors.lightGray, - [ colors.gray ] = colors.gray, - [ colors.lightGray ] = colors.lightGray, - [ colors.cyan ] = colors.lightGray, - [ colors.purple ] = colors.gray, - [ colors.blue ] = colors.gray, - [ colors.brown ] = colors.gray, - [ colors.green ] = colors.lightGray, - [ colors.red ] = colors.gray, - [ colors.black ] = colors.black, - } + local scolors = { + [ colors.white ] = colors.white, + [ colors.orange ] = colors.lightGray, + [ colors.magenta ] = colors.lightGray, + [ colors.lightBlue ] = colors.lightGray, + [ colors.yellow ] = colors.lightGray, + [ colors.lime ] = colors.lightGray, + [ colors.pink ] = colors.lightGray, + [ colors.gray ] = colors.gray, + [ colors.lightGray ] = colors.lightGray, + [ colors.cyan ] = colors.lightGray, + [ colors.purple ] = colors.gray, + [ colors.blue ] = colors.gray, + [ colors.brown ] = colors.gray, + [ colors.green ] = colors.lightGray, + [ colors.red ] = colors.gray, + [ colors.black ] = colors.black, + } - local methods = { 'setBackgroundColor', 'setBackgroundColour', - 'setTextColor', 'setTextColour' } - for _,v in pairs(methods) do - local fn = ct[v] - ct[v] = function(c) - fn(scolors[c]) - end - end + local methods = { 'setBackgroundColor', 'setBackgroundColour', + 'setTextColor', 'setTextColour' } + for _,v in pairs(methods) do + local fn = ct[v] + ct[v] = function(c) + fn(scolors[c]) + end + end - local bcolors = { - [ '1' ] = '8', - [ '2' ] = '8', - [ '3' ] = '8', - [ '4' ] = '8', - [ '5' ] = '8', - [ '6' ] = '8', - [ '9' ] = '8', - [ 'a' ] = '7', - [ 'b' ] = '7', - [ 'c' ] = '7', - [ 'd' ] = '8', - [ 'e' ] = '7', - } + local bcolors = { + [ '1' ] = '8', + [ '2' ] = '8', + [ '3' ] = '8', + [ '4' ] = '8', + [ '5' ] = '8', + [ '6' ] = '8', + [ '9' ] = '8', + [ 'a' ] = '7', + [ 'b' ] = '7', + [ 'c' ] = '7', + [ 'd' ] = '8', + [ 'e' ] = '7', + } - local function translate(s) - if s then - s = _gsub(s, "%w", bcolors) - end - return s - end + local function translate(s) + if s then + s = _gsub(s, "%w", bcolors) + end + return s + end - local fn = ct.blit - ct.blit = function(text, fg, bg) - fn(text, translate(fg), translate(bg)) - end + local fn = ct.blit + ct.blit = function(text, fg, bg) + fn(text, translate(fg), translate(bg)) + end end function Terminal.getNullTerm(ct) - local nt = Terminal.copy(ct) + local nt = Terminal.copy(ct) - local methods = { 'blit', 'clear', 'clearLine', 'scroll', - 'setCursorBlink', 'setCursorPos', 'write' } - for _,v in pairs(methods) do - nt[v] = function() end - end + local methods = { 'blit', 'clear', 'clearLine', 'scroll', + 'setCursorBlink', 'setCursorPos', 'write' } + for _,v in pairs(methods) do + nt[v] = function() end + end - return nt + return nt end function Terminal.copy(it, ot) - ot = ot or { } - for k,v in pairs(it) do - if type(v) == 'function' then - ot[k] = v - end - end - return ot + ot = ot or { } + for k,v in pairs(it) do + if type(v) == 'function' then + ot[k] = v + end + end + return ot end function Terminal.mirror(ct, dt) - for k,f in pairs(ct) do - ct[k] = function(...) - local ret = { f(...) } - if dt[k] then - dt[k](...) - end - return table.unpack(ret) - end - end + for k,f in pairs(ct) do + ct[k] = function(...) + local ret = { f(...) } + if dt[k] then + dt[k](...) + end + return table.unpack(ret) + end + end end function Terminal.readPassword(prompt) - if prompt then - term.write(prompt) - end - local fn = term.current().write - term.current().write = function() end - local s - pcall(function() s = _G.read(prompt) end) - term.current().write = fn + if prompt then + term.write(prompt) + end + local fn = term.current().write + term.current().write = function() end + local s + pcall(function() s = _G.read(prompt) end) + term.current().write = fn - if s == '' then - return - end - return s + if s == '' then + return + end + return s end return Terminal diff --git a/sys/apis/turtle/pathfind.lua b/sys/apis/turtle/pathfind.lua index bd574eb..e0144b4 100644 --- a/sys/apis/turtle/pathfind.lua +++ b/sys/apis/turtle/pathfind.lua @@ -1,5 +1,3 @@ -_G.requireInjector() - local Grid = require('jumper.grid') local Pathfinder = require('jumper.pathfinder') local Point = require('point') @@ -63,11 +61,11 @@ end local function dimsAreEqual(d1, d2) return d1.ex == d2.ex and - d1.ey == d2.ey and - d1.ez == d2.ez and - d1.x == d2.x and - d1.y == d2.y and - d1.z == d2.z + d1.ey == d2.ey and + d1.ez == d2.ez and + d1.x == d2.x and + d1.y == d2.y and + d1.z == d2.z end -- turtle sensor returns blocks in relation to the world - not turtle orientation @@ -98,13 +96,13 @@ local function selectDestination(pts, box, grid) while #pts > 0 do local pt = Point.closest(turtle.point, pts) if box and not Point.inBox(pt, box) then - Util.removeByValue(pts, pt) + Util.removeByValue(pts, pt) else if grid:isWalkableAt(pt.x, pt.y, pt.z) then return pt end - Util.removeByValue(pts, pt) - end + Util.removeByValue(pts, pt) + end end end @@ -156,7 +154,7 @@ local function pathTo(dest, options) dest.x, dest.y, dest.z, dest.heading) if not path then - Util.removeByValue(dests, dest) + Util.removeByValue(dests, dest) else path:filter() diff --git a/sys/apis/ui.lua b/sys/apis/ui.lua index 497ad9a..01cf867 100644 --- a/sys/apis/ui.lua +++ b/sys/apis/ui.lua @@ -3,7 +3,6 @@ local class = require('class') local Event = require('event') local Input = require('input') local Peripheral = require('peripheral') -local Terminal = require('terminal') local Transition = require('ui.transition') local Util = require('util') @@ -17,391 +16,391 @@ local term = _G.term local window = _G.window --[[ - Using the shorthand window definition, elements are created from - the bottom up. Once reaching the top, setParent is called top down. + Using the shorthand window definition, elements are created from + the bottom up. Once reaching the top, setParent is called top down. - On :init(), elements do not know the parent or can calculate sizing. + On :init(), elements do not know the parent or can calculate sizing. ]] local function safeValue(v) - local t = type(v) - if t == 'string' or t == 'number' then - return v - end - return tostring(v) + local t = type(v) + if t == 'string' or t == 'number' then + return v + end + return tostring(v) end -- need to add offsets to this test local function getPosition(element) - local x, y = 1, 1 - repeat - x = element.x + x - 1 - y = element.y + y - 1 - element = element.parent - until not element - return x, y + local x, y = 1, 1 + repeat + x = element.x + x - 1 + y = element.y + y - 1 + element = element.parent + until not element + return x, y end --[[-- Top Level Manager --]]-- local Manager = class() function Manager:init() - local function keyFunction(event, code, held) - local ch = Input:translate(event, code, held) + local function keyFunction(event, code, held) + local ie = Input:translate(event, code, held) - if ch and self.currentPage then - local target = self.currentPage.focused or self.currentPage - self:inputEvent(target, - { type = 'key', key = ch, element = target }) - self.currentPage:sync() - end - end + if ie and self.currentPage then + local target = self.currentPage.focused or self.currentPage + self:inputEvent(target, + { type = 'key', key = ie.code == 'char' and ie.ch or ie.code, element = target }) + self.currentPage:sync() + end + end - local handlers = { - char = keyFunction, - key_up = keyFunction, - key = keyFunction, + local handlers = { + char = keyFunction, + key_up = keyFunction, + key = keyFunction, - term_resize = function(_, side) - if self.currentPage then - -- the parent doesn't have any children set... - -- that's why we have to resize both the parent and the current page - -- kinda makes sense - if self.currentPage.parent.device.side == side then - self.currentPage.parent:resize() + term_resize = function(_, side) + if self.currentPage then + -- the parent doesn't have any children set... + -- that's why we have to resize both the parent and the current page + -- kinda makes sense + if self.currentPage.parent.device.side == side then + self.currentPage.parent:resize() - self.currentPage:resize() - self.currentPage:draw() - self.currentPage:sync() - end - end - end, + self.currentPage:resize() + self.currentPage:draw() + self.currentPage:sync() + end + end + end, - mouse_scroll = function(_, direction, x, y) - if self.currentPage then - local event = self.currentPage:pointToChild(x, y) - local directions = { - [ -1 ] = 'up', - [ 1 ] = 'down' - } - -- revisit - should send out scroll_up and scroll_down events - -- let the element convert them to up / down - self:inputEvent(event.element, - { type = 'key', key = directions[direction] }) - self.currentPage:sync() - end - end, + mouse_scroll = function(_, direction, x, y) + if self.currentPage then + local event = self.currentPage:pointToChild(x, y) + local directions = { + [ -1 ] = 'up', + [ 1 ] = 'down' + } + -- revisit - should send out scroll_up and scroll_down events + -- let the element convert them to up / down + self:inputEvent(event.element, + { type = 'key', key = directions[direction] }) + self.currentPage:sync() + end + end, - -- this should be moved to the device ! - monitor_touch = function(_, side, x, y) - Input:translate('mouse_click', 1, x, y) - local ch = Input:translate('mouse_up', 1, x, y) - if self.currentPage then - if self.currentPage.parent.device.side == side then - self:click(ch, 1, x, y) - end - end - end, + -- this should be moved to the device ! + monitor_touch = function(_, side, x, y) + Input:translate('mouse_click', 1, x, y) + local ie = Input:translate('mouse_up', 1, x, y) + if self.currentPage then + if self.currentPage.parent.device.side == side then + self:click(ie.code, 1, x, y) + end + end + end, - mouse_click = function(_, button, x, y) - Input:translate('mouse_click', button, x, y) + mouse_click = function(_, button, x, y) + Input:translate('mouse_click', button, x, y) - if self.currentPage then - if not self.currentPage.parent.device.side then - local event = self.currentPage:pointToChild(x, y) - if event.element.focus and not event.element.inactive then - self.currentPage:setFocus(event.element) - self.currentPage:sync() - end - end - end - end, + if self.currentPage then + if not self.currentPage.parent.device.side then + local event = self.currentPage:pointToChild(x, y) + if event.element.focus and not event.element.inactive then + self.currentPage:setFocus(event.element) + self.currentPage:sync() + end + end + end + end, - mouse_up = function(_, button, x, y) - local ch = Input:translate('mouse_up', button, x, y) + mouse_up = function(_, button, x, y) + local ie = Input:translate('mouse_up', button, x, y) - if ch == 'control-shift-mouse_click' then -- hack - local event = self.currentPage:pointToChild(x, y) - _ENV.multishell.openTab({ - path = 'sys/apps/Lua.lua', - args = { event.element }, - focused = true }) + if ie.code == 'control-shift-mouse_click' then -- hack + local event = self.currentPage:pointToChild(x, y) + _ENV.multishell.openTab({ + path = 'sys/apps/Lua.lua', + args = { event.element }, + focused = true }) - elseif ch and self.currentPage then - --if not self.currentPage.parent.device.side then - self:click(ch, button, x, y) - --end - end - end, + elseif ie and self.currentPage then + --if not self.currentPage.parent.device.side then + self:click(ie.code, button, x, y) + --end + end + end, - mouse_drag = function(_, button, x, y) - local ch = Input:translate('mouse_drag', button, x, y) - if ch and self.currentPage then - local event = self.currentPage:pointToChild(x, y) - event.type = ch - self:inputEvent(event.element, event) - self.currentPage:sync() - end - end, + mouse_drag = function(_, button, x, y) + local ie = Input:translate('mouse_drag', button, x, y) + if ie and self.currentPage then + local event = self.currentPage:pointToChild(x, y) + event.type = ie.code + self:inputEvent(event.element, event) + self.currentPage:sync() + end + end, - paste = function(_, text) - Input:translate('paste') - self:emitEvent({ type = 'paste', text = text }) - self.currentPage:sync() - end, - } + paste = function(_, text) + Input:translate('paste') + self:emitEvent({ type = 'paste', text = text }) + self.currentPage:sync() + end, + } - -- use 1 handler to single thread all events - Event.on({ - 'char', 'key_up', 'key', 'term_resize', - 'mouse_scroll', 'monitor_touch', 'mouse_click', - 'mouse_up', 'mouse_drag', 'paste' }, - function(event, ...) - handlers[event](event, ...) - end) + -- use 1 handler to single thread all events + Event.on({ + 'char', 'key_up', 'key', 'term_resize', + 'mouse_scroll', 'monitor_touch', 'mouse_click', + 'mouse_up', 'mouse_drag', 'paste' }, + function(event, ...) + handlers[event](event, ...) + end) end function Manager:configure(appName, ...) - local options = { - device = { arg = 'd', type = 'string', - desc = 'Device type' }, - textScale = { arg = 't', type = 'number', - desc = 'Text scale' }, - } - local defaults = Util.loadTable('usr/config/' .. appName) or { } - if not defaults.device then - defaults.device = { } - end + local options = { + device = { arg = 'd', type = 'string', + desc = 'Device type' }, + textScale = { arg = 't', type = 'number', + desc = 'Text scale' }, + } + local defaults = Util.loadTable('usr/config/' .. appName) or { } + if not defaults.device then + defaults.device = { } + end - Util.getOptions(options, { ... }, true) - local optionValues = { - name = options.device.value, - textScale = options.textScale.value, - } + Util.getOptions(options, { ... }, true) + local optionValues = { + name = options.device.value, + textScale = options.textScale.value, + } - Util.merge(defaults.device, optionValues) + Util.merge(defaults.device, optionValues) - if defaults.device.name then + if defaults.device.name then - local dev + local dev - if defaults.device.name == 'terminal' then - dev = term.current() - else - dev = Peripheral.lookup(defaults.device.name) --- device[defaults.device.name] - end + if defaults.device.name == 'terminal' then + dev = term.current() + else + dev = Peripheral.lookup(defaults.device.name) --- device[defaults.device.name] + end - if not dev then - error('Invalid display device') - end - self:setDefaultDevice(self.Device({ - device = dev, - textScale = defaults.device.textScale, - })) - end + if not dev then + error('Invalid display device') + end + self:setDefaultDevice(self.Device({ + device = dev, + textScale = defaults.device.textScale, + })) + end - if defaults.theme then - for k,v in pairs(defaults.theme) do - if self[k] and self[k].defaults then - Util.merge(self[k].defaults, v) - end - end - end + if defaults.theme then + for k,v in pairs(defaults.theme) do + if self[k] and self[k].defaults then + Util.merge(self[k].defaults, v) + end + end + end end function Manager:disableEffects() - self.defaultDevice.effectsEnabled = false + self.defaultDevice.effectsEnabled = false end function Manager:loadTheme(filename) - if fs.exists(filename) then - local theme, err = Util.loadTable(filename) - if not theme then - error(err) - end - for k,v in pairs(theme) do - if self[k] and self[k].defaults then - Util.merge(self[k].defaults, v) - end - end - end + if fs.exists(filename) then + local theme, err = Util.loadTable(filename) + if not theme then + error(err) + end + for k,v in pairs(theme) do + if self[k] and self[k].defaults then + Util.merge(self[k].defaults, v) + end + end + end end function Manager:emitEvent(event) - if self.currentPage and self.currentPage.focused then - return self.currentPage.focused:emit(event) - end + if self.currentPage and self.currentPage.focused then + return self.currentPage.focused:emit(event) + end end function Manager:inputEvent(parent, event) - while parent do - if parent.accelerators then - local acc = parent.accelerators[event.key] - if acc then - if parent:emit({ type = acc, element = parent }) then - return true - end - end - end - if parent.eventHandler then - if parent:eventHandler(event) then - return true - end - end - parent = parent.parent - end + while parent do + if parent.accelerators then + local acc = parent.accelerators[event.key] + if acc then + if parent:emit({ type = acc, element = parent }) then + return true + end + end + end + if parent.eventHandler then + if parent:eventHandler(event) then + return true + end + end + parent = parent.parent + end end function Manager:click(code, button, x, y) - if self.currentPage then + if self.currentPage then - local target = self.currentPage + local target = self.currentPage - -- need to add offsets into this check - --[[ - if x < target.x or y < target.y or - x > target.x + target.width - 1 or - y > target.y + target.height - 1 then - target:emit({ type = 'mouse_out' }) + -- need to add offsets into this check + --[[ + if x < target.x or y < target.y or + x > target.x + target.width - 1 or + y > target.y + target.height - 1 then + target:emit({ type = 'mouse_out' }) - target = self.currentPage - end - --]] + target = self.currentPage + end + --]] - local clickEvent = target:pointToChild(x, y) + local clickEvent = target:pointToChild(x, y) - if code == 'mouse_doubleclick' then - if self.doubleClickElement ~= clickEvent.element then - return - end - else - self.doubleClickElement = clickEvent.element - end + if code == 'mouse_doubleclick' then + if self.doubleClickElement ~= clickEvent.element then + return + end + else + self.doubleClickElement = clickEvent.element + end - clickEvent.button = button - clickEvent.type = code - clickEvent.key = code + clickEvent.button = button + clickEvent.type = code + clickEvent.key = code - if clickEvent.element.focus then - self.currentPage:setFocus(clickEvent.element) - end - if not self:inputEvent(clickEvent.element, clickEvent) then - --[[ - if button == 3 then - -- if the double-click was not captured - -- send through a single-click - clickEvent.button = 1 - clickEvent.type = events[1] - clickEvent.key = events[1] - self:inputEvent(clickEvent.element, clickEvent) - end - ]] - end + if clickEvent.element.focus then + self.currentPage:setFocus(clickEvent.element) + end + if not self:inputEvent(clickEvent.element, clickEvent) then + --[[ + if button == 3 then + -- if the double-click was not captured + -- send through a single-click + clickEvent.button = 1 + clickEvent.type = events[1] + clickEvent.key = events[1] + self:inputEvent(clickEvent.element, clickEvent) + end + ]] + end - self.currentPage:sync() - end + self.currentPage:sync() + end end function Manager:setDefaultDevice(dev) - self.defaultDevice = dev - self.term = dev + self.defaultDevice = dev + self.term = dev end function Manager:addPage(name, page) - self.pages[name] = page + self.pages[name] = page end function Manager:setPages(pages) - self.pages = pages + self.pages = pages end function Manager:getPage(pageName) - local page = self.pages[pageName] + local page = self.pages[pageName] - if not page then - error('UI:getPage: Invalid page: ' .. tostring(pageName), 2) - end + if not page then + error('UI:getPage: Invalid page: ' .. tostring(pageName), 2) + end - return page + return page end function Manager:setPage(pageOrName, ...) - local page = pageOrName + local page = pageOrName - if type(pageOrName) == 'string' then - page = self.pages[pageOrName] or error('Invalid page: ' .. pageOrName) - end + if type(pageOrName) == 'string' then + page = self.pages[pageOrName] or error('Invalid page: ' .. pageOrName) + end - if page == self.currentPage then - page:draw() - else - local needSync - if self.currentPage then - if self.currentPage.focused then - self.currentPage.focused.focused = false - self.currentPage.focused:focus() - end - self.currentPage:disable() - page.previousPage = self.currentPage - else - needSync = true - end - self.currentPage = page - self.currentPage:clear(page.backgroundColor) - page:enable(...) - page:draw() - if self.currentPage.focused then - self.currentPage.focused.focused = true - self.currentPage.focused:focus() - end - if needSync then - page:sync() -- first time a page has been set - end - end + if page == self.currentPage then + page:draw() + else + local needSync + if self.currentPage then + if self.currentPage.focused then + self.currentPage.focused.focused = false + self.currentPage.focused:focus() + end + self.currentPage:disable() + page.previousPage = self.currentPage + else + needSync = true + end + self.currentPage = page + self.currentPage:clear(page.backgroundColor) + page:enable(...) + page:draw() + if self.currentPage.focused then + self.currentPage.focused.focused = true + self.currentPage.focused:focus() + end + if needSync then + page:sync() -- first time a page has been set + end + end end function Manager:getCurrentPage() - return self.currentPage + return self.currentPage end function Manager:setPreviousPage() - if self.currentPage.previousPage then - local previousPage = self.currentPage.previousPage.previousPage - self:setPage(self.currentPage.previousPage) - self.currentPage.previousPage = previousPage - end + if self.currentPage.previousPage then + local previousPage = self.currentPage.previousPage.previousPage + self:setPage(self.currentPage.previousPage) + self.currentPage.previousPage = previousPage + end end function Manager:getDefaults(element, args) - local defaults = Util.deepCopy(element.defaults) - if args then - Manager:mergeProperties(defaults, args) - end - return defaults + local defaults = Util.deepCopy(element.defaults) + if args then + Manager:mergeProperties(defaults, args) + end + return defaults end function Manager:mergeProperties(obj, args) - if args then - for k,v in pairs(args) do - if k == 'accelerators' then - if obj.accelerators then - Util.merge(obj.accelerators, args.accelerators) - else - obj[k] = v - end - else - obj[k] = v - end - end - end + if args then + for k,v in pairs(args) do + if k == 'accelerators' then + if obj.accelerators then + Util.merge(obj.accelerators, args.accelerators) + else + obj[k] = v + end + else + obj[k] = v + end + end + end end function Manager:pullEvents(...) - Event.pullEvents(...) - self.term:reset() + Event.pullEvents(...) + self.term:reset() end function Manager:exitPullEvents() - Event.exitPullEvents() + Event.exitPullEvents() end local UI = Manager() @@ -410,2927 +409,2943 @@ local UI = Manager() UI.Window = class() UI.Window.uid = 1 UI.Window.defaults = { - UIElement = 'Window', - x = 1, - y = 1, - -- z = 0, -- eventually... - offx = 0, - offy = 0, - cursorX = 1, - cursorY = 1, + UIElement = 'Window', + x = 1, + y = 1, + -- z = 0, -- eventually... + offx = 0, + offy = 0, + cursorX = 1, + cursorY = 1, } function UI.Window:init(args) - -- merge defaults for all subclasses - local defaults = args - local m = getmetatable(self) -- get the class for this instance - repeat - defaults = UI:getDefaults(m, defaults) - m = m._base - until not m - UI:mergeProperties(self, defaults) + -- merge defaults for all subclasses + local defaults = args + local m = getmetatable(self) -- get the class for this instance + repeat + defaults = UI:getDefaults(m, defaults) + m = m._base + until not m + UI:mergeProperties(self, defaults) - -- each element has a unique ID - self.uid = UI.Window.uid - UI.Window.uid = UI.Window.uid + 1 + -- each element has a unique ID + self.uid = UI.Window.uid + UI.Window.uid = UI.Window.uid + 1 - -- at this time, the object has all the properties set + -- at this time, the object has all the properties set - -- postInit is a special constructor. the element does not need to implement - -- the method. But we need to guarantee that each subclass which has this - -- method is called. - m = self - local lpi - repeat - if m.postInit and m.postInit ~= lpi then - m.postInit(self) - lpi = m.postInit - end - m = m._base - until not m + -- postInit is a special constructor. the element does not need to implement + -- the method. But we need to guarantee that each subclass which has this + -- method is called. + m = self + local lpi + repeat + if m.postInit and m.postInit ~= lpi then + m.postInit(self) + lpi = m.postInit + end + m = m._base + until not m end function UI.Window:postInit() - if self.parent then - -- this will cascade down the whole tree of elements starting at the - -- top level window (which has a device as a parent) - self:setParent() - end + if self.parent then + -- this will cascade down the whole tree of elements starting at the + -- top level window (which has a device as a parent) + self:setParent() + end end function UI.Window:initChildren() - local children = self.children + local children = self.children - -- insert any UI elements created using the shorthand - -- window definition into the children array - for k,child in pairs(self) do - if k ~= 'parent' then -- reserved - if type(child) == 'table' and child.UIElement and not child.parent then - if not children then - children = { } - end - table.insert(children, child) - end - end - end - if children then - for _,child in pairs(children) do - if not child.parent then - child.parent = self - child:setParent() - -- child:reposition() -- maybe - if self.enabled then - child:enable() - end - end - end - self.children = children - end + -- insert any UI elements created using the shorthand + -- window definition into the children array + for k,child in pairs(self) do + if k ~= 'parent' then -- reserved + if type(child) == 'table' and child.UIElement and not child.parent then + if not children then + children = { } + end + table.insert(children, child) + end + end + end + if children then + for _,child in pairs(children) do + if not child.parent then + child.parent = self + child:setParent() + -- child:reposition() -- maybe + if self.enabled then + child:enable() + end + end + end + self.children = children + end end local function setSize(self) - if self.x < 0 then - self.x = self.parent.width + self.x + 1 - end - if self.y < 0 then - self.y = self.parent.height + self.y + 1 - end + if self.x < 0 then + self.x = self.parent.width + self.x + 1 + end + if self.y < 0 then + self.y = self.parent.height + self.y + 1 + end - if self.ex then - local ex = self.ex - if self.ex <= 1 then - ex = self.parent.width + self.ex + 1 - end - if self.width then - self.x = ex - self.width + 1 - else - self.width = ex - self.x + 1 - end - end - if self.ey then - local ey = self.ey - if self.ey <= 1 then - ey = self.parent.height + self.ey + 1 - end - if self.height then - self.y = ey - self.height + 1 - else - self.height = ey - self.y + 1 - end - end + if self.ex then + local ex = self.ex + if self.ex <= 1 then + ex = self.parent.width + self.ex + 1 + end + if self.width then + self.x = ex - self.width + 1 + else + self.width = ex - self.x + 1 + end + end + if self.ey then + local ey = self.ey + if self.ey <= 1 then + ey = self.parent.height + self.ey + 1 + end + if self.height then + self.y = ey - self.height + 1 + else + self.height = ey - self.y + 1 + end + end - if not self.width then - self.width = self.parent.width - self.x + 1 - end - if not self.height then - self.height = self.parent.height - self.y + 1 - end + if not self.width then + self.width = self.parent.width - self.x + 1 + end + if not self.height then + self.height = self.parent.height - self.y + 1 + end end -- bad name... should be called something like postInit -- normally used to determine sizes since the parent is -- only known at this point function UI.Window:setParent() - self.oh, self.ow = self.height, self.width - self.ox, self.oy = self.x, self.y + self.oh, self.ow = self.height, self.width + self.ox, self.oy = self.x, self.y - setSize(self) + setSize(self) - self:initChildren() + self:initChildren() end function UI.Window:resize() - self.height, self.width = self.oh, self.ow - self.x, self.y = self.ox, self.oy + self.height, self.width = self.oh, self.ow + self.x, self.y = self.ox, self.oy - setSize(self) + setSize(self) - if self.children then - for _,child in ipairs(self.children) do - child:resize() - end - end + if self.children then + for _,child in ipairs(self.children) do + child:resize() + end + end end function UI.Window:add(children) - UI:mergeProperties(self, children) - self:initChildren() + UI:mergeProperties(self, children) + self:initChildren() end function UI.Window:getCursorPos() - return self.cursorX, self.cursorY + return self.cursorX, self.cursorY end function UI.Window:setCursorPos(x, y) - self.cursorX = x - self.cursorY = y - self.parent:setCursorPos(self.x + x - 1, self.y + y - 1) + self.cursorX = x + self.cursorY = y + self.parent:setCursorPos(self.x + x - 1, self.y + y - 1) end function UI.Window:setCursorBlink(blink) - self.parent:setCursorBlink(blink) + self.parent:setCursorBlink(blink) end function UI.Window:draw() - self:clear(self.backgroundColor) - if self.children then - for _,child in pairs(self.children) do - if child.enabled then - child:draw() - end - end - end + self:clear(self.backgroundColor) + if self.children then + for _,child in pairs(self.children) do + if child.enabled then + child:draw() + end + end + end end function UI.Window:sync() - if self.parent then - self.parent:sync() - end + if self.parent then + self.parent:sync() + end end function UI.Window:enable() - self.enabled = true - if self.children then - for _,child in pairs(self.children) do - child:enable() - end - end + self.enabled = true + if self.children then + for _,child in pairs(self.children) do + child:enable() + end + end end function UI.Window:disable() - self.enabled = false - if self.children then - for _,child in pairs(self.children) do - child:disable() - end - end + self.enabled = false + if self.children then + for _,child in pairs(self.children) do + child:disable() + end + end end function UI.Window:setTextScale(textScale) - self.textScale = textScale - self.parent:setTextScale(textScale) + self.textScale = textScale + self.parent:setTextScale(textScale) end function UI.Window:clear(bg, fg) - if self.canvas then - self.canvas:clear(bg or self.backgroundColor, fg or self.textColor) - else - self:clearArea(1 + self.offx, 1 + self.offy, self.width, self.height, bg) - end + if self.canvas then + self.canvas:clear(bg or self.backgroundColor, fg or self.textColor) + else + self:clearArea(1 + self.offx, 1 + self.offy, self.width, self.height, bg) + end end function UI.Window:clearLine(y, bg) - self:write(1, y, _rep(' ', self.width), bg) + self:write(1, y, _rep(' ', self.width), bg) end function UI.Window:clearArea(x, y, width, height, bg) - if width > 0 then - local filler = _rep(' ', width) - for i = 0, height - 1 do - self:write(x, y + i, filler, bg) - end - end + if width > 0 then + local filler = _rep(' ', width) + for i = 0, height - 1 do + self:write(x, y + i, filler, bg) + end + end end function UI.Window:write(x, y, text, bg, tc) - bg = bg or self.backgroundColor - tc = tc or self.textColor - x = x - self.offx - y = y - self.offy - if y <= self.height and y > 0 then - if self.canvas then - self.canvas:write(x, y, text, bg, tc) - else - self.parent:write( - self.x + x - 1, self.y + y - 1, tostring(text), bg, tc) - end - end + bg = bg or self.backgroundColor + tc = tc or self.textColor + x = x - self.offx + y = y - self.offy + if y <= self.height and y > 0 then + if self.canvas then + self.canvas:write(x, y, text, bg, tc) + else + self.parent:write( + self.x + x - 1, self.y + y - 1, tostring(text), bg, tc) + end + end end function UI.Window:centeredWrite(y, text, bg, fg) - if #text >= self.width then - self:write(1, y, text, bg, fg) - else - local space = math.floor((self.width-#text) / 2) - local filler = _rep(' ', space + 1) - local str = _sub(filler, 1, space) .. text - str = str .. _sub(filler, self.width - #str + 1) - self:write(1, y, str, bg, fg) - end + if #text >= self.width then + self:write(1, y, text, bg, fg) + else + local space = math.floor((self.width-#text) / 2) + local filler = _rep(' ', space + 1) + local str = _sub(filler, 1, space) .. text + str = str .. _sub(filler, self.width - #str + 1) + self:write(1, y, str, bg, fg) + end end function UI.Window:print(text, bg, fg) - local marginLeft = self.marginLeft or 0 - local marginRight = self.marginRight or 0 - local width = self.width - marginLeft - marginRight + local marginLeft = self.marginLeft or 0 + local marginRight = self.marginRight or 0 + local width = self.width - marginLeft - marginRight - local function nextWord(line, cx) - local result = { line:find("(%w+)", cx) } - if #result > 1 and result[2] > cx then - return _sub(line, cx, result[2] + 1) - elseif #result > 0 and result[1] == cx then - result = { line:find("(%w+)", result[2]) } - if #result > 0 then - return _sub(line, cx, result[1] + 1) - end - end - if cx <= #line then - return _sub(line, cx, #line) - end - end + local function nextWord(line, cx) + local result = { line:find("(%w+)", cx) } + if #result > 1 and result[2] > cx then + return _sub(line, cx, result[2] + 1) + elseif #result > 0 and result[1] == cx then + result = { line:find("(%w+)", result[2]) } + if #result > 0 then + return _sub(line, cx, result[1] + 1) + end + end + if cx <= #line then + return _sub(line, cx, #line) + end + end - local function pieces(f, bg, fg) - local pos = 1 - local t = { } - while true do - local s = string.find(f, '\027', pos, true) - if not s then - break - end - if pos < s then - table.insert(t, _sub(f, pos, s - 1)) - end - local seq = _sub(f, s) - seq = seq:match("\027%[([%d;]+)m") - local e = { } - for color in string.gmatch(seq, "%d+") do - color = tonumber(color) - if color == 0 then - e.fg = fg - e.bg = bg - elseif color > 20 then - e.bg = 2 ^ (color - 21) - else - e.fg = 2 ^ (color - 1) - end - end - table.insert(t, e) - pos = s + #seq + 3 - end - if pos <= #f then - table.insert(t, _sub(f, pos)) - end - return t - end + local function pieces(f, bg, fg) + local pos = 1 + local t = { } + while true do + local s = string.find(f, '\027', pos, true) + if not s then + break + end + if pos < s then + table.insert(t, _sub(f, pos, s - 1)) + end + local seq = _sub(f, s) + seq = seq:match("\027%[([%d;]+)m") + local e = { } + for color in string.gmatch(seq, "%d+") do + color = tonumber(color) + if color == 0 then + e.fg = fg + e.bg = bg + elseif color > 20 then + e.bg = 2 ^ (color - 21) + else + e.fg = 2 ^ (color - 1) + end + end + table.insert(t, e) + pos = s + #seq + 3 + end + if pos <= #f then + table.insert(t, _sub(f, pos)) + end + return t + end - local lines = Util.split(text) - for k,line in pairs(lines) do - local fragments = pieces(line, bg, fg) - for _, fragment in ipairs(fragments) do - local lx = 1 - if type(fragment) == 'table' then -- ansi sequence - fg = fragment.fg - bg = fragment.bg - else - while true do - local word = nextWord(fragment, lx) - if not word then - break - end - local w = word - if self.cursorX + #word > width then - self.cursorX = marginLeft + 1 - self.cursorY = self.cursorY + 1 - w = word:gsub('^ ', '') - end - self:write(self.cursorX, self.cursorY, w, bg, fg) - self.cursorX = self.cursorX + #w - lx = lx + #word - end - end - end - if lines[k + 1] then - self.cursorX = marginLeft + 1 - self.cursorY = self.cursorY + 1 - end - end + local lines = Util.split(text) + for k,line in pairs(lines) do + local fragments = pieces(line, bg, fg) + for _, fragment in ipairs(fragments) do + local lx = 1 + if type(fragment) == 'table' then -- ansi sequence + fg = fragment.fg + bg = fragment.bg + else + while true do + local word = nextWord(fragment, lx) + if not word then + break + end + local w = word + if self.cursorX + #word > width then + self.cursorX = marginLeft + 1 + self.cursorY = self.cursorY + 1 + w = word:gsub('^ ', '') + end + self:write(self.cursorX, self.cursorY, w, bg, fg) + self.cursorX = self.cursorX + #w + lx = lx + #word + end + end + end + if lines[k + 1] then + self.cursorX = marginLeft + 1 + self.cursorY = self.cursorY + 1 + end + end - return self.cursorX, self.cursorY + return self.cursorX, self.cursorY end function UI.Window:setFocus(focus) - if self.parent then - self.parent:setFocus(focus) - end + if self.parent then + self.parent:setFocus(focus) + end end function UI.Window:capture(child) - if self.parent then - self.parent:capture(child) - end + if self.parent then + self.parent:capture(child) + end end function UI.Window:release(child) - if self.parent then - self.parent:release(child) - end + if self.parent then + self.parent:release(child) + end end function UI.Window:pointToChild(x, y) - x = x + self.offx - self.x + 1 - y = y + self.offy - self.y + 1 - if self.children then - for _,child in pairs(self.children) do - if child.enabled and not child.inactive and - x >= child.x and x < child.x + child.width and - y >= child.y and y < child.y + child.height then - local c = child:pointToChild(x, y) - if c then - return c - end - end - end - end - return { - element = self, - x = x, - y = y - } + x = x + self.offx - self.x + 1 + y = y + self.offy - self.y + 1 + if self.children then + for _,child in pairs(self.children) do + if child.enabled and not child.inactive and + x >= child.x and x < child.x + child.width and + y >= child.y and y < child.y + child.height then + local c = child:pointToChild(x, y) + if c then + return c + end + end + end + end + return { + element = self, + x = x, + y = y + } end function UI.Window:getFocusables() - local focusable = { } + local focusable = { } - local function focusSort(a, b) - if a.y == b.y then - return a.x < b.x - end - return a.y < b.y - end + local function focusSort(a, b) + if a.y == b.y then + return a.x < b.x + end + return a.y < b.y + end - local function getFocusable(parent, x, y) - for _,child in Util.spairs(parent.children, focusSort) do - if child.enabled and child.focus and not child.inactive then - table.insert(focusable, child) - end - if child.children then - getFocusable(child, child.x + x, child.y + y) - end - end - end + local function getFocusable(parent, x, y) + for _,child in Util.spairs(parent.children, focusSort) do + if child.enabled and child.focus and not child.inactive then + table.insert(focusable, child) + end + if child.children then + getFocusable(child, child.x + x, child.y + y) + end + end + end - if self.children then - getFocusable(self, self.x, self.y) - end + if self.children then + getFocusable(self, self.x, self.y) + end - return focusable + return focusable end function UI.Window:focusFirst() - local focusables = self:getFocusables() - local focused = focusables[1] - if focused then - self:setFocus(focused) - end + local focusables = self:getFocusables() + local focused = focusables[1] + if focused then + self:setFocus(focused) + end end function UI.Window:refocus() - local el = self - while el do - local focusables = el:getFocusables() - if focusables[1] then - self:setFocus(focusables[1]) - break - end - el = el.parent - end + local el = self + while el do + local focusables = el:getFocusables() + if focusables[1] then + self:setFocus(focusables[1]) + break + end + el = el.parent + end end function UI.Window:scrollIntoView() - local parent = self.parent + local parent = self.parent - if self.x <= parent.offx then - parent.offx = math.max(0, self.x - 1) - parent:draw() - elseif self.x + self.width > parent.width + parent.offx then - parent.offx = self.x + self.width - parent.width - 1 - parent:draw() - end + if self.x <= parent.offx then + parent.offx = math.max(0, self.x - 1) + parent:draw() + elseif self.x + self.width > parent.width + parent.offx then + parent.offx = self.x + self.width - parent.width - 1 + parent:draw() + end - if self.y <= parent.offy then - parent.offy = math.max(0, self.y - 1) - parent:draw() - elseif self.y + self.height > parent.height + parent.offy then - parent.offy = self.y + self.height - parent.height - 1 - parent:draw() - end + if self.y <= parent.offy then + parent.offy = math.max(0, self.y - 1) + parent:draw() + elseif self.y + self.height > parent.height + parent.offy then + parent.offy = self.y + self.height - parent.height - 1 + parent:draw() + end end function UI.Window:getCanvas() - local el = self - repeat - if el.canvas then - return el.canvas - end - el = el.parent - until not el + local el = self + repeat + if el.canvas then + return el.canvas + end + el = el.parent + until not el end function UI.Window:addLayer(bg, fg) - local canvas = self:getCanvas() - canvas = canvas:addLayer(self, bg, fg) - canvas:clear(bg or self.backgroundColor, fg or self.textColor) - return canvas + local canvas = self:getCanvas() + canvas = canvas:addLayer(self, bg, fg) + canvas:clear(bg or self.backgroundColor, fg or self.textColor) + return canvas end function UI.Window:addTransition(effect, args) - if self.parent then - args = args or { } - if not args.x then -- not good - args.x, args.y = getPosition(self) - args.width = self.width - args.height = self.height - end + if self.parent then + args = args or { } + if not args.x then -- not good + args.x, args.y = getPosition(self) + args.width = self.width + args.height = self.height + end - args.canvas = args.canvas or self.canvas - self.parent:addTransition(effect, args) - end + args.canvas = args.canvas or self.canvas + self.parent:addTransition(effect, args) + end end function UI.Window:emit(event) - local parent = self - while parent do - if parent.eventHandler then - if parent:eventHandler(event) then - return true - end - end - parent = parent.parent - end + local parent = self + while parent do + if parent.eventHandler then + if parent:eventHandler(event) then + return true + end + end + parent = parent.parent + end end function UI.Window:find(uid) - if self.children then - return Util.find(self.children, 'uid', uid) - end + if self.children then + return Util.find(self.children, 'uid', uid) + end end function UI.Window:eventHandler(event) - return false + return false end --[[-- Terminal for computer / advanced computer / monitor --]]-- UI.Device = class(UI.Window) UI.Device.defaults = { - UIElement = 'Device', - backgroundColor = colors.black, - textColor = colors.white, - textScale = 1, - effectsEnabled = true, + UIElement = 'Device', + backgroundColor = colors.black, + textColor = colors.white, + textScale = 1, + effectsEnabled = true, } function UI.Device:postInit() - self.device = self.device or term.current() + self.device = self.device or term.current() - if self.deviceType then - self.device = device[self.deviceType] - end + if self.deviceType then + self.device = device[self.deviceType] + end - if not self.device.setTextScale then - self.device.setTextScale = function() end - end + if not self.device.setTextScale then + self.device.setTextScale = function() end + end - self.device.setTextScale(self.textScale) - self.width, self.height = self.device.getSize() + self.device.setTextScale(self.textScale) + self.width, self.height = self.device.getSize() - self.isColor = self.device.isColor() + self.isColor = self.device.isColor() - self.canvas = Canvas({ - x = 1, y = 1, width = self.width, height = self.height, - isColor = self.isColor, - }) - self.canvas:clear(self.backgroundColor, self.textColor) + self.canvas = Canvas({ + x = 1, y = 1, width = self.width, height = self.height, + isColor = self.isColor, + }) + self.canvas:clear(self.backgroundColor, self.textColor) end function UI.Device:resize() - self.width, self.height = self.device.getSize() - self.lines = { } - self.canvas:resize(self.width, self.height) - self.canvas:clear(self.backgroundColor, self.textColor) + self.width, self.height = self.device.getSize() + self.lines = { } + self.canvas:resize(self.width, self.height) + self.canvas:clear(self.backgroundColor, self.textColor) end function UI.Device:setCursorPos(x, y) - self.cursorX = x - self.cursorY = y + self.cursorX = x + self.cursorY = y end function UI.Device:getCursorBlink() - return self.cursorBlink + return self.cursorBlink end function UI.Device:setCursorBlink(blink) - self.cursorBlink = blink - self.device.setCursorBlink(blink) + self.cursorBlink = blink + self.device.setCursorBlink(blink) end function UI.Device:setTextScale(textScale) - self.textScale = textScale - self.device.setTextScale(self.textScale) + self.textScale = textScale + self.device.setTextScale(self.textScale) end function UI.Device:reset() - self.device.setBackgroundColor(colors.black) - self.device.setTextColor(colors.white) - self.device.clear() - self.device.setCursorPos(1, 1) + self.device.setBackgroundColor(colors.black) + self.device.setTextColor(colors.white) + self.device.clear() + self.device.setCursorPos(1, 1) end function UI.Device:addTransition(effect, args) - if not self.transitions then - self.transitions = { } - end + if not self.transitions then + self.transitions = { } + end - args = args or { } - args.ex = args.x + args.width - 1 - args.ey = args.y + args.height - 1 - args.canvas = args.canvas or self.canvas + args = args or { } + args.ex = args.x + args.width - 1 + args.ey = args.y + args.height - 1 + args.canvas = args.canvas or self.canvas - if type(effect) == 'string' then - effect = Transition[effect] - if not effect then - error('Invalid transition') - end - end + if type(effect) == 'string' then + effect = Transition[effect] + if not effect then + error('Invalid transition') + end + end - table.insert(self.transitions, { update = effect(args), args = args }) + table.insert(self.transitions, { update = effect(args), args = args }) end function UI.Device:runTransitions(transitions, canvas) - for _,t in ipairs(transitions) do - canvas:punch(t.args) -- punch out the effect areas - end - canvas:blitClipped(self.device) -- and blit the remainder - canvas:reset() + for _,t in ipairs(transitions) do + canvas:punch(t.args) -- punch out the effect areas + end + canvas:blitClipped(self.device) -- and blit the remainder + canvas:reset() - while true do - for _,k in ipairs(Util.keys(transitions)) do - local transition = transitions[k] - if not transition.update(self.device) then - transitions[k] = nil - end - end - if Util.empty(transitions) then - break - end - os.sleep(0) - end + while true do + for _,k in ipairs(Util.keys(transitions)) do + local transition = transitions[k] + if not transition.update(self.device) then + transitions[k] = nil + end + end + if Util.empty(transitions) then + break + end + os.sleep(0) + end end function UI.Device:sync() - local transitions - if self.transitions and self.effectsEnabled then - transitions = self.transitions - self.transitions = nil - end + local transitions + if self.transitions and self.effectsEnabled then + transitions = self.transitions + self.transitions = nil + end - if self:getCursorBlink() then - self.device.setCursorBlink(false) - end + if self:getCursorBlink() then + self.device.setCursorBlink(false) + end - if transitions then - self:runTransitions(transitions, self.canvas) - else - self.canvas:render(self.device) - end + if transitions then + self:runTransitions(transitions, self.canvas) + else + self.canvas:render(self.device) + end - if self:getCursorBlink() then - self.device.setCursorPos(self.cursorX, self.cursorY) - self.device.setCursorBlink(true) - end + if self:getCursorBlink() then + self.device.setCursorPos(self.cursorX, self.cursorY) + self.device.setCursorBlink(true) + end end --[[-- StringBuffer --]]-- -- justs optimizes string concatenations UI.StringBuffer = class() function UI.StringBuffer:init(bufSize) - self.bufSize = bufSize - self.buffer = {} + self.bufSize = bufSize + self.buffer = {} end function UI.StringBuffer:insert(s, width) - local len = #tostring(s or '') - if len > width then - s = _sub(s, 1, width) - end - table.insert(self.buffer, s) - if len < width then - table.insert(self.buffer, _rep(' ', width - len)) - end + local len = #tostring(s or '') + if len > width then + s = _sub(s, 1, width) + end + table.insert(self.buffer, s) + if len < width then + table.insert(self.buffer, _rep(' ', width - len)) + end end function UI.StringBuffer:get(sep) - return Util.widthify(table.concat(self.buffer, sep or ''), self.bufSize) + return Util.widthify(table.concat(self.buffer, sep or ''), self.bufSize) end function UI.StringBuffer:clear() - self.buffer = { } + self.buffer = { } end -- For manipulating text in a fixed width string local SB = { } function SB:new(width) - return setmetatable({ - width = width, - buf = _rep(' ', width) - }, { __index = SB }) + return setmetatable({ + width = width, + buf = _rep(' ', width) + }, { __index = SB }) end function SB:insert(x, str, width) - if x < 1 then - x = self.width + x + 1 - end - width = width or #str - if x + width - 1 > self.width then - width = self.width - x - end - if width > 0 then - self.buf = _sub(self.buf, 1, x - 1) .. _sub(str, 1, width) .. _sub(self.buf, x + width) - end + if x < 1 then + x = self.width + x + 1 + end + width = width or #str + if x + width - 1 > self.width then + width = self.width - x + end + if width > 0 then + self.buf = _sub(self.buf, 1, x - 1) .. _sub(str, 1, width) .. _sub(self.buf, x + width) + end end function SB:fill(x, ch, width) - width = width or self.width - x + 1 - self:insert(x, _rep(ch, width)) + width = width or self.width - x + 1 + self:insert(x, _rep(ch, width)) end function SB:center(str) - self:insert(math.max(1, math.ceil((self.width - #str + 1) / 2)), str) + self:insert(math.max(1, math.ceil((self.width - #str + 1) / 2)), str) end function SB:get() - return self.buf + return self.buf end --[[-- Page (focus manager) --]]-- UI.Page = class(UI.Window) UI.Page.defaults = { - UIElement = 'Page', - accelerators = { - down = 'focus_next', - enter = 'focus_next', - tab = 'focus_next', - ['shift-tab' ] = 'focus_prev', - up = 'focus_prev', - }, - backgroundColor = colors.cyan, - textColor = colors.white, + UIElement = 'Page', + accelerators = { + down = 'focus_next', + enter = 'focus_next', + tab = 'focus_next', + ['shift-tab' ] = 'focus_prev', + up = 'focus_prev', + }, + backgroundColor = colors.cyan, + textColor = colors.white, } function UI.Page:postInit() - self.parent = self.parent or UI.defaultDevice - self.__target = self + self.parent = self.parent or UI.defaultDevice + self.__target = self end function UI.Page:setParent() - UI.Window.setParent(self) - if self.z then - self.canvas = self:addLayer(self.backgroundColor, self.textColor) - self.canvas:clear(self.backgroundColor, self.textColor) - else - self.canvas = self.parent.canvas - end + UI.Window.setParent(self) + if self.z then + self.canvas = self:addLayer(self.backgroundColor, self.textColor) + self.canvas:clear(self.backgroundColor, self.textColor) + else + self.canvas = self.parent.canvas + end end function UI.Page:enable() - self.canvas.visible = true - UI.Window.enable(self) + self.canvas.visible = true + UI.Window.enable(self) - if not self.focused or not self.focused.enabled then - self:focusFirst() - end + if not self.focused or not self.focused.enabled then + self:focusFirst() + end end function UI.Page:disable() - if self.z then - self.canvas.visible = false - end + if self.z then + self.canvas.visible = false + end end function UI.Page:capture(child) - self.__target = child + self.__target = child end function UI.Page:release(child) - if self.__target == child then - self.__target = self - end + if self.__target == child then + self.__target = self + end end function UI.Page:pointToChild(x, y) - if self.__target == self then - return UI.Window.pointToChild(self, x, y) - end - x = x + self.offx - self.x + 1 - y = y + self.offy - self.y + 1 - return self.__target:pointToChild(x, y) + if self.__target == self then + return UI.Window.pointToChild(self, x, y) + end + x = x + self.offx - self.x + 1 + y = y + self.offy - self.y + 1 + return self.__target:pointToChild(x, y) end function UI.Page:getFocusables() - if self.__target == self or self.__target.pageType ~= 'modal' then - return UI.Window.getFocusables(self) - end - return self.__target:getFocusables() + if self.__target == self or self.__target.pageType ~= 'modal' then + return UI.Window.getFocusables(self) + end + return self.__target:getFocusables() end function UI.Page:getFocused() - return self.focused + return self.focused end function UI.Page:focusPrevious() - local function getPreviousFocus(focused) - local focusables = self:getFocusables() - local k = Util.contains(focusables, focused) - if k then - if k > 1 then - return focusables[k - 1] - end - return focusables[#focusables] - end - end + local function getPreviousFocus(focused) + local focusables = self:getFocusables() + local k = Util.contains(focusables, focused) + if k then + if k > 1 then + return focusables[k - 1] + end + return focusables[#focusables] + end + end - local focused = getPreviousFocus(self.focused) - if focused then - self:setFocus(focused) - end + local focused = getPreviousFocus(self.focused) + if focused then + self:setFocus(focused) + end end function UI.Page:focusNext() - local function getNextFocus(focused) - local focusables = self:getFocusables() - local k = Util.contains(focusables, focused) - if k then - if k < #focusables then - return focusables[k + 1] - end - return focusables[1] - end - end + local function getNextFocus(focused) + local focusables = self:getFocusables() + local k = Util.contains(focusables, focused) + if k then + if k < #focusables then + return focusables[k + 1] + end + return focusables[1] + end + end - local focused = getNextFocus(self.focused) - if focused then - self:setFocus(focused) - end + local focused = getNextFocus(self.focused) + if focused then + self:setFocus(focused) + end end function UI.Page:setFocus(child) - if not child.focus then - return - end + if not child.focus then + return + end - if self.focused and self.focused ~= child then - self.focused.focused = false - self.focused:focus() - self.focused:emit({ type = 'focus_lost', focused = child }) - end + if self.focused and self.focused ~= child then + self.focused.focused = false + self.focused:focus() + self.focused:emit({ type = 'focus_lost', focused = child }) + end - self.focused = child - if not child.focused then - child.focused = true - self:emit({ type = 'focus_change', focused = child }) - end + self.focused = child + if not child.focused then + child.focused = true + self:emit({ type = 'focus_change', focused = child }) + end - child:focus() + child:focus() end function UI.Page:eventHandler(event) - if self.focused then - if event.type == 'focus_next' then - self:focusNext() - return true - elseif event.type == 'focus_prev' then - self:focusPrevious() - return true - end - end + if self.focused then + if event.type == 'focus_next' then + self:focusNext() + return true + elseif event.type == 'focus_prev' then + self:focusPrevious() + return true + end + end end --[[-- Grid --]]-- UI.Grid = class(UI.Window) UI.Grid.defaults = { - UIElement = 'Grid', - index = 1, - inverseSort = false, - disableHeader = false, - marginRight = 0, - textColor = colors.white, - textSelectedColor = colors.white, - backgroundColor = colors.black, - backgroundSelectedColor = colors.gray, - headerBackgroundColor = colors.cyan, - headerTextColor = colors.white, - unfocusedTextSelectedColor = colors.white, - unfocusedBackgroundSelectedColor = colors.gray, - focusIndicator = '>', - sortIndicator = ' ', - inverseSortIndicator = '^', - values = { }, - columns = { }, - accelerators = { - enter = 'key_enter', - [ 'control-c' ] = 'copy', - down = 'scroll_down', - up = 'scroll_up', - home = 'scroll_top', - [ 'end' ] = 'scroll_bottom', - pageUp = 'scroll_pageUp', - [ 'control-b' ] = 'scroll_pageUp', - pageDown = 'scroll_pageDown', - [ 'control-f' ] = 'scroll_pageDown', - }, + UIElement = 'Grid', + index = 1, + inverseSort = false, + disableHeader = false, + marginRight = 0, + textColor = colors.white, + textSelectedColor = colors.white, + backgroundColor = colors.black, + backgroundSelectedColor = colors.gray, + headerBackgroundColor = colors.cyan, + headerTextColor = colors.white, + unfocusedTextSelectedColor = colors.white, + unfocusedBackgroundSelectedColor = colors.gray, + focusIndicator = '>', + sortIndicator = ' ', + inverseSortIndicator = '^', + values = { }, + columns = { }, + accelerators = { + enter = 'key_enter', + [ 'control-c' ] = 'copy', + down = 'scroll_down', + up = 'scroll_up', + home = 'scroll_top', + [ 'end' ] = 'scroll_bottom', + pageUp = 'scroll_pageUp', + [ 'control-b' ] = 'scroll_pageUp', + pageDown = 'scroll_pageDown', + [ 'control-f' ] = 'scroll_pageDown', + }, } function UI.Grid:setParent() - UI.Window.setParent(self) + UI.Window.setParent(self) - for _,c in pairs(self.columns) do - c.cw = c.width - if not c.heading then - c.heading = '' - end - end + for _,c in pairs(self.columns) do + c.cw = c.width + if not c.heading then + c.heading = '' + end + end - self:update() + self:update() - if not self.pageSize then - if self.disableHeader then - self.pageSize = self.height - else - self.pageSize = self.height - 1 - end - end + if not self.pageSize then + if self.disableHeader then + self.pageSize = self.height + else + self.pageSize = self.height - 1 + end + end end function UI.Grid:resize() - UI.Window.resize(self) + UI.Window.resize(self) - if self.disableHeader then - self.pageSize = self.height - else - self.pageSize = self.height - 1 - end - self:adjustWidth() + if self.disableHeader then + self.pageSize = self.height + else + self.pageSize = self.height - 1 + end + self:adjustWidth() end function UI.Grid:adjustWidth() - local t = { } -- cols without width - local w = self.width - #self.columns - 1 - self.marginRight -- width remaining + local t = { } -- cols without width + local w = self.width - #self.columns - 1 - self.marginRight -- width remaining - for _,c in pairs(self.columns) do - if c.width then - c.cw = c.width - w = w - c.cw - else - table.insert(t, c) - end - end + for _,c in pairs(self.columns) do + if c.width then + c.cw = c.width + w = w - c.cw + else + table.insert(t, c) + end + end - if #t == 0 then - return - end + if #t == 0 then + return + end - if #t == 1 then - t[1].cw = #(t[1].heading or '') - t[1].cw = math.max(t[1].cw, w) - return - end + if #t == 1 then + t[1].cw = #(t[1].heading or '') + t[1].cw = math.max(t[1].cw, w) + return + end - if not self.autospace then - for k,c in ipairs(t) do - c.cw = math.floor(w / (#t - k + 1)) - w = w - c.cw - end + if not self.autospace then + for k,c in ipairs(t) do + c.cw = math.floor(w / (#t - k + 1)) + w = w - c.cw + end - else - for _,c in ipairs(t) do - c.cw = #(c.heading or '') - w = w - c.cw - end - -- adjust the size to the length of the value - for key,row in pairs(self.values) do - if w <= 0 then - break - end - row = self:getDisplayValues(row, key) - for _,col in pairs(t) do - local value = row[col.key] - if value then - value = tostring(value) - if #value > col.cw then - w = w + col.cw - col.cw = math.min(#value, w) - w = w - col.cw - if w <= 0 then - break - end - end - end - end - end + else + for _,c in ipairs(t) do + c.cw = #(c.heading or '') + w = w - c.cw + end + -- adjust the size to the length of the value + for key,row in pairs(self.values) do + if w <= 0 then + break + end + row = self:getDisplayValues(row, key) + for _,col in pairs(t) do + local value = row[col.key] + if value then + value = tostring(value) + if #value > col.cw then + w = w + col.cw + col.cw = math.min(#value, w) + w = w - col.cw + if w <= 0 then + break + end + end + end + end + end - -- last column does not get padding (right alignment) - if not self.columns[#self.columns].width then - Util.removeByValue(t, self.columns[#self.columns]) - end + -- last column does not get padding (right alignment) + if not self.columns[#self.columns].width then + Util.removeByValue(t, self.columns[#self.columns]) + end - -- got some extra room - add some padding - if w > 0 then - for k,c in ipairs(t) do - local padding = math.floor(w / (#t - k + 1)) - c.cw = c.cw + padding - w = w - padding - end - end - end + -- got some extra room - add some padding + if w > 0 then + for k,c in ipairs(t) do + local padding = math.floor(w / (#t - k + 1)) + c.cw = c.cw + padding + w = w - padding + end + end + end end function UI.Grid:setPageSize(pageSize) - self.pageSize = pageSize + self.pageSize = pageSize end function UI.Grid:getValues() - return self.values + return self.values end function UI.Grid:setValues(t) - self.values = t - self:update() + self.values = t + self:update() end function UI.Grid:setInverseSort(inverseSort) - self.inverseSort = inverseSort - self:update() - self:setIndex(self.index) + self.inverseSort = inverseSort + self:update() + self:setIndex(self.index) end function UI.Grid:setSortColumn(column) - self.sortColumn = column + self.sortColumn = column end function UI.Grid:getDisplayValues(row, key) - return row + return row end function UI.Grid:getSelected() - if self.sorted then - return self.values[self.sorted[self.index]], self.sorted[self.index] - end + if self.sorted then + return self.values[self.sorted[self.index]], self.sorted[self.index] + end end function UI.Grid:focus() - self:drawRows() + self:drawRows() end function UI.Grid:draw() - if not self.disableHeader then - self:drawHeadings() - end + if not self.disableHeader then + self:drawHeadings() + end - if self.index <= 0 then - self:setIndex(1) - elseif self.index > #self.sorted then - self:setIndex(#self.sorted) - end - self:drawRows() + if self.index <= 0 then + self:setIndex(1) + elseif self.index > #self.sorted then + self:setIndex(#self.sorted) + end + self:drawRows() end -- Something about the displayed table has changed -- resort the table function UI.Grid:update() - local function sort(a, b) - if not a[self.sortColumn] then - return false - elseif not b[self.sortColumn] then - return true - end - return self:sortCompare(a, b) - end + local function sort(a, b) + if not a[self.sortColumn] then + return false + elseif not b[self.sortColumn] then + return true + end + return self:sortCompare(a, b) + end - local function inverseSort(a, b) - return not sort(a, b) - end + local function inverseSort(a, b) + return not sort(a, b) + end - local order - if self.sortColumn then - order = sort - if self.inverseSort then - order = inverseSort - end - end + local order + if self.sortColumn then + order = sort + if self.inverseSort then + order = inverseSort + end + end - self.sorted = Util.keys(self.values) - if order then - table.sort(self.sorted, function(a,b) - return order(self.values[a], self.values[b]) - end) - end + self.sorted = Util.keys(self.values) + if order then + table.sort(self.sorted, function(a,b) + return order(self.values[a], self.values[b]) + end) + end - self:adjustWidth() + self:adjustWidth() end function UI.Grid:drawHeadings() - local sb = UI.StringBuffer(self.width) - for _,col in ipairs(self.columns) do - local ind = ' ' - if col.key == self.sortColumn then - if self.inverseSort then - ind = self.inverseSortIndicator - else - ind = self.sortIndicator - end - end - sb:insert(ind .. col.heading, col.cw + 1) - end - self:write(1, 1, sb:get(), self.headerBackgroundColor, self.headerTextColor) + local sb = UI.StringBuffer(self.width) + for _,col in ipairs(self.columns) do + local ind = ' ' + if col.key == self.sortColumn then + if self.inverseSort then + ind = self.inverseSortIndicator + else + ind = self.sortIndicator + end + end + sb:insert(ind .. col.heading, col.cw + 1) + end + self:write(1, 1, sb:get(), self.headerBackgroundColor, self.headerTextColor) end function UI.Grid:sortCompare(a, b) - a = safeValue(a[self.sortColumn]) - b = safeValue(b[self.sortColumn]) - if type(a) == type(b) then - return a < b - end - return tostring(a) < tostring(b) + a = safeValue(a[self.sortColumn]) + b = safeValue(b[self.sortColumn]) + if type(a) == type(b) then + return a < b + end + return tostring(a) < tostring(b) end function UI.Grid:drawRows() - local y = 1 - local startRow = math.max(1, self:getStartRow()) - local sb = UI.StringBuffer(self.width) + local y = 1 + local startRow = math.max(1, self:getStartRow()) + local sb = UI.StringBuffer(self.width) - if not self.disableHeader then - y = y + 1 - end + if not self.disableHeader then + y = y + 1 + end - local lastRow = math.min(startRow + self.pageSize - 1, #self.sorted) - for index = startRow, lastRow do + local lastRow = math.min(startRow + self.pageSize - 1, #self.sorted) + for index = startRow, lastRow do - local sindex = self.sorted[index] - local rawRow = self.values[sindex] - local key = sindex - local row = self:getDisplayValues(rawRow, key) + local sindex = self.sorted[index] + local rawRow = self.values[sindex] + local key = sindex + local row = self:getDisplayValues(rawRow, key) - sb:clear() + sb:clear() - local ind = ' ' - if self.focused and index == self.index and not self.inactive then - ind = self.focusIndicator - end + local ind = ' ' + if self.focused and index == self.index and not self.inactive then + ind = self.focusIndicator + end - for _,col in pairs(self.columns) do - sb:insert(ind .. safeValue(row[col.key] or ''), col.cw + 1) - ind = ' ' - end + for _,col in pairs(self.columns) do + sb:insert(ind .. safeValue(row[col.key] or ''), col.cw + 1) + ind = ' ' + end - local selected = index == self.index and not self.inactive + local selected = index == self.index and not self.inactive - self:write(1, y, sb:get(), - self:getRowBackgroundColor(rawRow, selected), - self:getRowTextColor(rawRow, selected)) + self:write(1, y, sb:get(), + self:getRowBackgroundColor(rawRow, selected), + self:getRowTextColor(rawRow, selected)) - y = y + 1 - end + y = y + 1 + end - if y <= self.height then - self:clearArea(1, y, self.width, self.height - y + 1) - end + if y <= self.height then + self:clearArea(1, y, self.width, self.height - y + 1) + end end function UI.Grid:getRowTextColor(row, selected) - if selected then - if self.focused then - return self.textSelectedColor - end - return self.unfocusedTextSelectedColor - end - return self.textColor + if selected then + if self.focused then + return self.textSelectedColor + end + return self.unfocusedTextSelectedColor + end + return self.textColor end function UI.Grid:getRowBackgroundColor(row, selected) - if selected then - if self.focused then - return self.backgroundSelectedColor - end - return self.unfocusedBackgroundSelectedColor - end - return self.backgroundColor + if selected then + if self.focused then + return self.backgroundSelectedColor + end + return self.unfocusedBackgroundSelectedColor + end + return self.backgroundColor end function UI.Grid:getIndex() - return self.index + return self.index end function UI.Grid:setIndex(index) - index = math.max(1, index) - self.index = math.min(index, #self.sorted) + index = math.max(1, index) + self.index = math.min(index, #self.sorted) - local selected = self:getSelected() - if selected ~= self.selected then - self:drawRows() - self.selected = selected - if selected then - self:emit({ type = 'grid_focus_row', selected = selected, element = self }) - end - end + local selected = self:getSelected() + if selected ~= self.selected then + self:drawRows() + self.selected = selected + if selected then + self:emit({ type = 'grid_focus_row', selected = selected, element = self }) + end + end end function UI.Grid:getStartRow() - return math.floor((self.index - 1) / self.pageSize) * self.pageSize + 1 + return math.floor((self.index - 1) / self.pageSize) * self.pageSize + 1 end function UI.Grid:getPage() - return math.floor(self.index / self.pageSize) + 1 + return math.floor(self.index / self.pageSize) + 1 end function UI.Grid:getPageCount() - local tableSize = Util.size(self.values) - local pc = math.floor(tableSize / self.pageSize) - if tableSize % self.pageSize > 0 then - pc = pc + 1 - end - return pc + local tableSize = Util.size(self.values) + local pc = math.floor(tableSize / self.pageSize) + if tableSize % self.pageSize > 0 then + pc = pc + 1 + end + return pc end function UI.Grid:nextPage() - self:setPage(self:getPage() + 1) + self:setPage(self:getPage() + 1) end function UI.Grid:previousPage() - self:setPage(self:getPage() - 1) + self:setPage(self:getPage() - 1) end function UI.Grid:setPage(pageNo) - -- 1 based paging - self:setIndex((pageNo-1) * self.pageSize + 1) + -- 1 based paging + self:setIndex((pageNo-1) * self.pageSize + 1) end function UI.Grid:eventHandler(event) - if event.type == 'mouse_click' or - event.type == 'mouse_rightclick' or - event.type == 'mouse_doubleclick' then - if not self.disableHeader then - if event.y == 1 then - local col = 2 - for _,c in ipairs(self.columns) do - if event.x < col + c.cw then - if self.sortColumn == c.key then - self:setInverseSort(not self.inverseSort) - else - self.sortColumn = c.key - self:setInverseSort(false) - end - self:draw() - break - end - col = col + c.cw + 1 - end - return true - end - end - local row = self:getStartRow() + event.y - 1 - if not self.disableHeader then - row = row - 1 - end - if row > 0 and row <= Util.size(self.values) then - self:setIndex(row) - if event.type == 'mouse_doubleclick' then - self:emit({ type = 'key_enter' }) - elseif event.type == 'mouse_rightclick' then - self:emit({ type = 'grid_select_right', selected = self.selected, element = self }) - end - return true - end - return false + if event.type == 'mouse_click' or + event.type == 'mouse_rightclick' or + event.type == 'mouse_doubleclick' then + if not self.disableHeader then + if event.y == 1 then + local col = 2 + for _,c in ipairs(self.columns) do + if event.x < col + c.cw then + if self.sortColumn == c.key then + self:setInverseSort(not self.inverseSort) + else + self.sortColumn = c.key + self:setInverseSort(false) + end + self:draw() + break + end + col = col + c.cw + 1 + end + return true + end + end + local row = self:getStartRow() + event.y - 1 + if not self.disableHeader then + row = row - 1 + end + if row > 0 and row <= Util.size(self.values) then + self:setIndex(row) + if event.type == 'mouse_doubleclick' then + self:emit({ type = 'key_enter' }) + elseif event.type == 'mouse_rightclick' then + self:emit({ type = 'grid_select_right', selected = self.selected, element = self }) + end + return true + end + return false - elseif event.type == 'scroll_down' then - self:setIndex(self.index + 1) - elseif event.type == 'scroll_up' then - self:setIndex(self.index - 1) - elseif event.type == 'scroll_top' then - self:setIndex(1) - elseif event.type == 'scroll_bottom' then - self:setIndex(Util.size(self.values)) - elseif event.type == 'scroll_pageUp' then - self:setIndex(self.index - self.pageSize) - elseif event.type == 'scroll_pageDown' then - self:setIndex(self.index + self.pageSize) - elseif event.type == 'key_enter' then - if self.selected then - self:emit({ type = 'grid_select', selected = self.selected, element = self }) - end - elseif event.type == 'copy' then - if self.selected then - os.queueEvent('clipboard_copy', self.selected) - end - else - return false - end - return true + elseif event.type == 'scroll_down' then + self:setIndex(self.index + 1) + elseif event.type == 'scroll_up' then + self:setIndex(self.index - 1) + elseif event.type == 'scroll_top' then + self:setIndex(1) + elseif event.type == 'scroll_bottom' then + self:setIndex(Util.size(self.values)) + elseif event.type == 'scroll_pageUp' then + self:setIndex(self.index - self.pageSize) + elseif event.type == 'scroll_pageDown' then + self:setIndex(self.index + self.pageSize) + elseif event.type == 'key_enter' then + if self.selected then + self:emit({ type = 'grid_select', selected = self.selected, element = self }) + end + elseif event.type == 'copy' then + if self.selected then + os.queueEvent('clipboard_copy', self.selected) + end + else + return false + end + return true end --[[-- ScrollingGrid --]]-- UI.ScrollingGrid = class(UI.Grid) UI.ScrollingGrid.defaults = { - UIElement = 'ScrollingGrid', - scrollOffset = 0, - marginRight = 1, + UIElement = 'ScrollingGrid', + scrollOffset = 0, + marginRight = 1, } function UI.ScrollingGrid:postInit() - self.scrollBar = UI.ScrollBar() + self.scrollBar = UI.ScrollBar() end function UI.ScrollingGrid:drawRows() - UI.Grid.drawRows(self) - self.scrollBar:draw() + UI.Grid.drawRows(self) + self.scrollBar:draw() end function UI.ScrollingGrid:getViewArea() - local y = 1 - if not self.disableHeader then - y = 2 - end - return { - static = true, -- the container doesn't scroll - y = y, -- scrollbar Y - height = self.pageSize, -- viewable height - totalHeight = Util.size(self.values), -- total height - offsetY = self.scrollOffset, -- scroll offset - } + local y = 1 + if not self.disableHeader then + y = 2 + end + return { + static = true, -- the container doesn't scroll + y = y, -- scrollbar Y + height = self.pageSize, -- viewable height + totalHeight = Util.size(self.values), -- total height + offsetY = self.scrollOffset, -- scroll offset + } end function UI.ScrollingGrid:getStartRow() - local ts = Util.size(self.values) - if ts < self.pageSize then - self.scrollOffset = 0 - end - return self.scrollOffset + 1 + local ts = Util.size(self.values) + if ts < self.pageSize then + self.scrollOffset = 0 + end + return self.scrollOffset + 1 end function UI.ScrollingGrid:setIndex(index) - if index < self.scrollOffset + 1 then - self.scrollOffset = index - 1 - elseif index - self.scrollOffset > self.pageSize then - self.scrollOffset = index - self.pageSize - end + if index < self.scrollOffset + 1 then + self.scrollOffset = index - 1 + elseif index - self.scrollOffset > self.pageSize then + self.scrollOffset = index - self.pageSize + end - if self.scrollOffset < 0 then - self.scrollOffset = 0 - else - local ts = Util.size(self.values) - if self.pageSize + self.scrollOffset + 1 > ts then - self.scrollOffset = math.max(0, ts - self.pageSize) - end - end - UI.Grid.setIndex(self, index) + if self.scrollOffset < 0 then + self.scrollOffset = 0 + else + local ts = Util.size(self.values) + if self.pageSize + self.scrollOffset + 1 > ts then + self.scrollOffset = math.max(0, ts - self.pageSize) + end + end + UI.Grid.setIndex(self, index) end --[[-- Menu --]]-- UI.Menu = class(UI.Grid) UI.Menu.defaults = { - UIElement = 'Menu', - disableHeader = true, - columns = { { heading = 'Prompt', key = 'prompt', width = 20 } }, + UIElement = 'Menu', + disableHeader = true, + columns = { { heading = 'Prompt', key = 'prompt', width = 20 } }, } function UI.Menu:postInit() - self.values = self.menuItems - self.pageSize = #self.menuItems + self.values = self.menuItems + self.pageSize = #self.menuItems end function UI.Menu:setParent() - UI.Grid.setParent(self) - self.itemWidth = 1 - for _,v in pairs(self.values) do - if #v.prompt > self.itemWidth then - self.itemWidth = #v.prompt - end - end - self.columns[1].width = self.itemWidth + UI.Grid.setParent(self) + self.itemWidth = 1 + for _,v in pairs(self.values) do + if #v.prompt > self.itemWidth then + self.itemWidth = #v.prompt + end + end + self.columns[1].width = self.itemWidth - if self.centered then - self:center() - else - self.width = self.itemWidth + 2 - end + if self.centered then + self:center() + else + self.width = self.itemWidth + 2 + end end function UI.Menu:center() - self.x = (self.width - self.itemWidth + 2) / 2 - self.width = self.itemWidth + 2 + self.x = (self.width - self.itemWidth + 2) / 2 + self.width = self.itemWidth + 2 end function UI.Menu:eventHandler(event) - if event.type == 'key' then - if event.key == 'enter' then - local selected = self.menuItems[self.index] - self:emit({ - type = selected.event or 'menu_select', - selected = selected - }) - return true - end - elseif event.type == 'mouse_click' then - if event.y <= #self.menuItems then - UI.Grid.setIndex(self, event.y) - local selected = self.menuItems[self.index] - self:emit({ - type = selected.event or 'menu_select', - selected = selected - }) - return true - end - end - return UI.Grid.eventHandler(self, event) + if event.type == 'key' then + if event.key == 'enter' then + local selected = self.menuItems[self.index] + self:emit({ + type = selected.event or 'menu_select', + selected = selected + }) + return true + end + elseif event.type == 'mouse_click' then + if event.y <= #self.menuItems then + UI.Grid.setIndex(self, event.y) + local selected = self.menuItems[self.index] + self:emit({ + type = selected.event or 'menu_select', + selected = selected + }) + return true + end + end + return UI.Grid.eventHandler(self, event) end --[[-- Viewport --]]-- UI.Viewport = class(UI.Window) UI.Viewport.defaults = { - UIElement = 'Viewport', - backgroundColor = colors.cyan, - accelerators = { - down = 'scroll_down', - up = 'scroll_up', - home = 'scroll_top', - [ 'end' ] = 'scroll_bottom', - pageUp = 'scroll_pageUp', - [ 'control-b' ] = 'scroll_pageUp', - pageDown = 'scroll_pageDown', - [ 'control-f' ] = 'scroll_pageDown', - }, + UIElement = 'Viewport', + backgroundColor = colors.cyan, + accelerators = { + down = 'scroll_down', + up = 'scroll_up', + home = 'scroll_top', + [ 'end' ] = 'scroll_bottom', + pageUp = 'scroll_pageUp', + [ 'control-b' ] = 'scroll_pageUp', + pageDown = 'scroll_pageDown', + [ 'control-f' ] = 'scroll_pageDown', + }, } function UI.Viewport:setScrollPosition(offset) - local oldOffset = self.offy - self.offy = math.max(offset, 0) - local max = self.ymax or self.height - if self.children then - for _, child in ipairs(self.children) do - if child ~= self.scrollBar then -- hack ! - max = math.max(child.y + child.height - 1, max) - end - end - end - self.offy = math.min(self.offy, math.max(max, self.height) - self.height) - if self.offy ~= oldOffset then - self:draw() - end + local oldOffset = self.offy + self.offy = math.max(offset, 0) + local max = self.ymax or self.height + if self.children then + for _, child in ipairs(self.children) do + if child ~= self.scrollBar then -- hack ! + max = math.max(child.y + child.height - 1, max) + end + end + end + self.offy = math.min(self.offy, math.max(max, self.height) - self.height) + if self.offy ~= oldOffset then + self:draw() + end end function UI.Viewport:reset() - self.offy = 0 + self.offy = 0 end function UI.Viewport:getViewArea() - return { - y = (self.offy or 0) + 1, - height = self.height, - totalHeight = self.ymax, - offsetY = self.offy or 0, - } + return { + y = (self.offy or 0) + 1, + height = self.height, + totalHeight = self.ymax, + offsetY = self.offy or 0, + } end function UI.Viewport:eventHandler(event) - if event.type == 'scroll_down' then - self:setScrollPosition(self.offy + 1) - elseif event.type == 'scroll_up' then - self:setScrollPosition(self.offy - 1) - elseif event.type == 'scroll_top' then - self:setScrollPosition(0) - elseif event.type == 'scroll_bottom' then - self:setScrollPosition(10000000) - elseif event.type == 'scroll_pageUp' then - self:setScrollPosition(self.offy - self.height) - elseif event.type == 'scroll_pageDown' then - self:setScrollPosition(self.offy + self.height) - else - return false - end - return true + if event.type == 'scroll_down' then + self:setScrollPosition(self.offy + 1) + elseif event.type == 'scroll_up' then + self:setScrollPosition(self.offy - 1) + elseif event.type == 'scroll_top' then + self:setScrollPosition(0) + elseif event.type == 'scroll_bottom' then + self:setScrollPosition(10000000) + elseif event.type == 'scroll_pageUp' then + self:setScrollPosition(self.offy - self.height) + elseif event.type == 'scroll_pageDown' then + self:setScrollPosition(self.offy + self.height) + else + return false + end + return true end --[[-- TitleBar --]]-- UI.TitleBar = class(UI.Window) UI.TitleBar.defaults = { - UIElement = 'TitleBar', - height = 1, - textColor = colors.white, - backgroundColor = colors.cyan, - title = '', - frameChar = '-', - closeInd = '*', + UIElement = 'TitleBar', + height = 1, + textColor = colors.white, + backgroundColor = colors.cyan, + title = '', + frameChar = '-', + closeInd = '*', } function UI.TitleBar:draw() - local sb = SB:new(self.width) - sb:fill(2, self.frameChar, sb.width - 3) - sb:center(string.format(' %s ', self.title)) - if self.previousPage or self.event then - sb:insert(-1, self.closeInd) - end - self:write(1, 1, sb:get()) + local sb = SB:new(self.width) + sb:fill(2, self.frameChar, sb.width - 3) + sb:center(string.format(' %s ', self.title)) + if self.previousPage or self.event then + sb:insert(-1, self.closeInd) + end + self:write(1, 1, sb:get()) end function UI.TitleBar:eventHandler(event) - if event.type == 'mouse_click' then - if (self.previousPage or self.event) and event.x == self.width then - if self.event then - self:emit({ type = self.event, element = self }) - elseif type(self.previousPage) == 'string' or - type(self.previousPage) == 'table' then - UI:setPage(self.previousPage) - else - UI:setPreviousPage() - end - return true - end - end + if event.type == 'mouse_click' then + if (self.previousPage or self.event) and event.x == self.width then + if self.event then + self:emit({ type = self.event, element = self }) + elseif type(self.previousPage) == 'string' or + type(self.previousPage) == 'table' then + UI:setPage(self.previousPage) + else + UI:setPreviousPage() + end + return true + end + end end --[[-- Button --]]-- UI.Button = class(UI.Window) UI.Button.defaults = { - UIElement = 'Button', - text = 'button', - backgroundColor = colors.lightGray, - backgroundFocusColor = colors.gray, - textFocusColor = colors.white, - textInactiveColor = colors.gray, - textColor = colors.black, - centered = true, - height = 1, - focusIndicator = ' ', - event = 'button_press', - accelerators = { - space = 'button_activate', - enter = 'button_activate', - mouse_click = 'button_activate', - } + UIElement = 'Button', + text = 'button', + backgroundColor = colors.lightGray, + backgroundFocusColor = colors.gray, + textFocusColor = colors.white, + textInactiveColor = colors.gray, + textColor = colors.black, + centered = true, + height = 1, + focusIndicator = ' ', + event = 'button_press', + accelerators = { + space = 'button_activate', + enter = 'button_activate', + mouse_click = 'button_activate', + } } function UI.Button:setParent() - if not self.width and not self.ex then - self.width = #self.text + 2 - end - UI.Window.setParent(self) + if not self.width and not self.ex then + self.width = #self.text + 2 + end + UI.Window.setParent(self) end function UI.Button:draw() - local fg = self.textColor - local bg = self.backgroundColor - local ind = ' ' - if self.focused then - bg = self.backgroundFocusColor - fg = self.textFocusColor - ind = self.focusIndicator - elseif self.inactive then - fg = self.textInactiveColor - end - local text = ind .. self.text .. ' ' - if self.centered then - self:clear(bg) - self:centeredWrite(1 + math.floor(self.height / 2), text, bg, fg) - else - self:write(1, 1, Util.widthify(text, self.width), bg, fg) - end + local fg = self.textColor + local bg = self.backgroundColor + local ind = ' ' + if self.focused then + bg = self.backgroundFocusColor + fg = self.textFocusColor + ind = self.focusIndicator + elseif self.inactive then + fg = self.textInactiveColor + end + local text = ind .. self.text .. ' ' + if self.centered then + self:clear(bg) + self:centeredWrite(1 + math.floor(self.height / 2), text, bg, fg) + else + self:write(1, 1, Util.widthify(text, self.width), bg, fg) + end end function UI.Button:focus() - if self.focused then - self:scrollIntoView() - end - self:draw() + if self.focused then + self:scrollIntoView() + end + self:draw() end function UI.Button:eventHandler(event) - if event.type == 'button_activate' then - self:emit({ type = self.event, button = self }) - return true - end - return false + if event.type == 'button_activate' then + self:emit({ type = self.event, button = self }) + return true + end + return false end --[[-- MenuItem --]]-- UI.MenuItem = class(UI.Button) UI.MenuItem.defaults = { - UIElement = 'MenuItem', - textColor = colors.black, - backgroundColor = colors.lightGray, - textFocusColor = colors.white, - backgroundFocusColor = colors.lightGray, + UIElement = 'MenuItem', + textColor = colors.black, + backgroundColor = colors.lightGray, + textFocusColor = colors.white, + backgroundFocusColor = colors.lightGray, } --[[-- MenuBar --]]-- UI.MenuBar = class(UI.Window) UI.MenuBar.defaults = { - UIElement = 'MenuBar', - buttons = { }, - height = 1, - backgroundColor = colors.lightGray, - textColor = colors.black, - spacing = 2, - lastx = 1, - showBackButton = false, - buttonClass = 'MenuItem', + UIElement = 'MenuBar', + buttons = { }, + height = 1, + backgroundColor = colors.lightGray, + textColor = colors.black, + spacing = 2, + lastx = 1, + showBackButton = false, + buttonClass = 'MenuItem', } UI.MenuBar.spacer = { spacer = true, text = 'spacer', inactive = true } function UI.MenuBar:postInit() - self:addButtons(self.buttons) + self:addButtons(self.buttons) end function UI.MenuBar:addButtons(buttons) - if not self.children then - self.children = { } - end + if not self.children then + self.children = { } + end - for _,button in pairs(buttons) do - if button.UIElement then - table.insert(self.children, button) - else - local buttonProperties = { - x = self.lastx, - width = #button.text + self.spacing, - centered = false, - } - self.lastx = self.lastx + buttonProperties.width - UI:mergeProperties(buttonProperties, button) + for _,button in pairs(buttons) do + if button.UIElement then + table.insert(self.children, button) + else + local buttonProperties = { + x = self.lastx, + width = #button.text + self.spacing, + centered = false, + } + self.lastx = self.lastx + buttonProperties.width + UI:mergeProperties(buttonProperties, button) - button = UI[self.buttonClass](buttonProperties) - if button.name then - self[button.name] = button - else - table.insert(self.children, button) - end + button = UI[self.buttonClass](buttonProperties) + if button.name then + self[button.name] = button + else + table.insert(self.children, button) + end - if button.dropdown then - button.dropmenu = UI.DropMenu { buttons = button.dropdown } - end - end - end - if self.parent then - self:initChildren() - end + if button.dropdown then + button.dropmenu = UI.DropMenu { buttons = button.dropdown } + end + end + end + if self.parent then + self:initChildren() + end end function UI.MenuBar:getActive(menuItem) - return not menuItem.inactive + return not menuItem.inactive end function UI.MenuBar:eventHandler(event) - if event.type == 'button_press' and event.button.dropmenu then - if event.button.dropmenu.enabled then - event.button.dropmenu:hide() - return true - else - local x, y = getPosition(event.button) - if x + event.button.dropmenu.width > self.width then - x = self.width - event.button.dropmenu.width + 1 - end - for _,c in pairs(event.button.dropmenu.children) do - if not c.spacer then - c.inactive = not self:getActive(c) - end - end - event.button.dropmenu:show(x, y + 1) - end - return true - end + if event.type == 'button_press' and event.button.dropmenu then + if event.button.dropmenu.enabled then + event.button.dropmenu:hide() + return true + else + local x, y = getPosition(event.button) + if x + event.button.dropmenu.width > self.width then + x = self.width - event.button.dropmenu.width + 1 + end + for _,c in pairs(event.button.dropmenu.children) do + if not c.spacer then + c.inactive = not self:getActive(c) + end + end + event.button.dropmenu:show(x, y + 1) + end + return true + end end --[[-- DropMenuItem --]]-- UI.DropMenuItem = class(UI.Button) UI.DropMenuItem.defaults = { - UIElement = 'DropMenuItem', - textColor = colors.black, - backgroundColor = colors.white, - textFocusColor = colors.white, - textInactiveColor = colors.lightGray, - backgroundFocusColor = colors.lightGray, + UIElement = 'DropMenuItem', + textColor = colors.black, + backgroundColor = colors.white, + textFocusColor = colors.white, + textInactiveColor = colors.lightGray, + backgroundFocusColor = colors.lightGray, } function UI.DropMenuItem:eventHandler(event) - if event.type == 'button_activate' then - self.parent:hide() - end - return UI.Button.eventHandler(self, event) + if event.type == 'button_activate' then + self.parent:hide() + end + return UI.Button.eventHandler(self, event) end --[[-- DropMenu --]]-- UI.DropMenu = class(UI.MenuBar) UI.DropMenu.defaults = { - UIElement = 'DropMenu', - backgroundColor = colors.white, - buttonClass = 'DropMenuItem', + UIElement = 'DropMenu', + backgroundColor = colors.white, + buttonClass = 'DropMenuItem', } function UI.DropMenu:setParent() - UI.MenuBar.setParent(self) + UI.MenuBar.setParent(self) - local maxWidth = 1 - for y,child in ipairs(self.children) do - child.x = 1 - child.y = y - if #(child.text or '') > maxWidth then - maxWidth = #child.text - end - end - for _,child in ipairs(self.children) do - child.width = maxWidth + 2 - if child.spacer then - child.text = string.rep('-', child.width - 2) - end - end + local maxWidth = 1 + for y,child in ipairs(self.children) do + child.x = 1 + child.y = y + if #(child.text or '') > maxWidth then + maxWidth = #child.text + end + end + for _,child in ipairs(self.children) do + child.width = maxWidth + 2 + if child.spacer then + child.text = string.rep('-', child.width - 2) + end + end - self.height = #self.children + 1 - self.width = maxWidth + 2 - self.ow = self.width + self.height = #self.children + 1 + self.width = maxWidth + 2 + self.ow = self.width - self.canvas = self:addLayer() + self.canvas = self:addLayer() end function UI.DropMenu:enable() - self.enabled = false + self.enabled = false end function UI.DropMenu:show(x, y) - self.x, self.y = x, y - self.canvas:move(x, y) - self.canvas:setVisible(true) + self.x, self.y = x, y + self.canvas:move(x, y) + self.canvas:setVisible(true) - self.enabled = true - for _,child in pairs(self.children) do - child:enable() - end + self.enabled = true + for _,child in pairs(self.children) do + child:enable() + end - self:draw() - self:capture(self) - self:focusFirst() + self:draw() + self:capture(self) + self:focusFirst() end function UI.DropMenu:hide() - self:disable() - self.canvas:setVisible(false) - self:release(self) + self:disable() + self.canvas:setVisible(false) + self:release(self) end function UI.DropMenu:eventHandler(event) - if event.type == 'focus_lost' and self.enabled then - if not Util.contains(self.children, event.focused) then - self:hide() - end - elseif event.type == 'mouse_out' and self.enabled then - self:hide() - self:refocus() - else - return UI.MenuBar.eventHandler(self, event) - end - return true + if event.type == 'focus_lost' and self.enabled then + if not Util.contains(self.children, event.focused) then + self:hide() + end + elseif event.type == 'mouse_out' and self.enabled then + self:hide() + self:refocus() + else + return UI.MenuBar.eventHandler(self, event) + end + return true end --[[-- TabBarMenuItem --]]-- UI.TabBarMenuItem = class(UI.Button) UI.TabBarMenuItem.defaults = { - UIElement = 'TabBarMenuItem', - event = 'tab_select', - textColor = colors.black, - selectedBackgroundColor = colors.cyan, - unselectedBackgroundColor = colors.lightGray, - backgroundColor = colors.lightGray, + UIElement = 'TabBarMenuItem', + event = 'tab_select', + textColor = colors.black, + selectedBackgroundColor = colors.cyan, + unselectedBackgroundColor = colors.lightGray, + backgroundColor = colors.lightGray, } function UI.TabBarMenuItem:draw() - if self.selected then - self.backgroundColor = self.selectedBackgroundColor - self.backgroundFocusColor = self.selectedBackgroundColor - else - self.backgroundColor = self.unselectedBackgroundColor - self.backgroundFocusColor = self.unselectedBackgroundColor - end - UI.Button.draw(self) + if self.selected then + self.backgroundColor = self.selectedBackgroundColor + self.backgroundFocusColor = self.selectedBackgroundColor + else + self.backgroundColor = self.unselectedBackgroundColor + self.backgroundFocusColor = self.unselectedBackgroundColor + end + UI.Button.draw(self) end --[[-- TabBar --]]-- UI.TabBar = class(UI.MenuBar) UI.TabBar.defaults = { - UIElement = 'TabBar', - buttonClass = 'TabBarMenuItem', - selectedBackgroundColor = colors.cyan, + UIElement = 'TabBar', + buttonClass = 'TabBarMenuItem', + selectedBackgroundColor = colors.cyan, } function UI.TabBar:enable() - UI.MenuBar.enable(self) - if not Util.find(self.children, 'selected', true) then - local menuItem = self:getFocusables()[1] - if menuItem then - menuItem.selected = true - end - end + UI.MenuBar.enable(self) + if not Util.find(self.children, 'selected', true) then + local menuItem = self:getFocusables()[1] + if menuItem then + menuItem.selected = true + end + end end function UI.TabBar:eventHandler(event) - if event.type == 'tab_select' then - local selected, si = Util.find(self:getFocusables(), 'uid', event.button.uid) - local previous, pi = Util.find(self:getFocusables(), 'selected', true) + if event.type == 'tab_select' then + local selected, si = Util.find(self:getFocusables(), 'uid', event.button.uid) + local previous, pi = Util.find(self:getFocusables(), 'selected', true) - if si ~= pi then - selected.selected = true - previous.selected = false - self:emit({ type = 'tab_change', current = si, last = pi, tab = selected }) - end - UI.MenuBar.draw(self) - end - return UI.MenuBar.eventHandler(self, event) + if si ~= pi then + selected.selected = true + previous.selected = false + self:emit({ type = 'tab_change', current = si, last = pi, tab = selected }) + end + UI.MenuBar.draw(self) + end + return UI.MenuBar.eventHandler(self, event) end function UI.TabBar:selectTab(text) - local menuItem = Util.find(self.children, 'text', text) - if menuItem then - menuItem.selected = true - end + local menuItem = Util.find(self.children, 'text', text) + if menuItem then + menuItem.selected = true + end end --[[-- Tabs --]]-- UI.Tabs = class(UI.Window) UI.Tabs.defaults = { - UIElement = 'Tabs', + UIElement = 'Tabs', } function UI.Tabs:postInit() - self:add(self) + self:add(self) end function UI.Tabs:add(children) - local buttons = { } - for _,child in pairs(children) do - if type(child) == 'table' and child.UIElement and child.tabTitle then - child.y = 2 - table.insert(buttons, { - text = child.tabTitle, - event = 'tab_select', - tabUid = child.uid, - }) - end - end + local buttons = { } + for _,child in pairs(children) do + if type(child) == 'table' and child.UIElement and child.tabTitle then + child.y = 2 + table.insert(buttons, { + text = child.tabTitle, + event = 'tab_select', + tabUid = child.uid, + }) + end + end - if not self.tabBar then - self.tabBar = UI.TabBar({ - buttons = buttons, - }) - else - self.tabBar:addButtons(buttons) - end + if not self.tabBar then + self.tabBar = UI.TabBar({ + buttons = buttons, + }) + else + self.tabBar:addButtons(buttons) + end - if self.parent then - return UI.Window.add(self, children) - end + if self.parent then + return UI.Window.add(self, children) + end end function UI.Tabs:enable() - self.enabled = true - self.tabBar:enable() + self.enabled = true + self.tabBar:enable() - local menuItem = Util.find(self.tabBar.children, 'selected', true) + local menuItem = Util.find(self.tabBar.children, 'selected', true) - for _,child in pairs(self.children) do - if child.uid == menuItem.tabUid then - child:enable() - self:emit({ type = 'tab_activate', activated = child }) - elseif child.tabTitle then - child:disable() - end - end + for _,child in pairs(self.children) do + if child.uid == menuItem.tabUid then + child:enable() + self:emit({ type = 'tab_activate', activated = child }) + elseif child.tabTitle then + child:disable() + end + end end function UI.Tabs:eventHandler(event) - if event.type == 'tab_change' then - local tab = self:find(event.tab.tabUid) - if event.current > event.last then - tab:addTransition('slideLeft') - else - tab:addTransition('slideRight') - end + if event.type == 'tab_change' then + local tab = self:find(event.tab.tabUid) + if event.current > event.last then + tab:addTransition('slideLeft') + else + tab:addTransition('slideRight') + end - for _,child in pairs(self.children) do - if child.uid == event.tab.tabUid then - child:enable() - elseif child.tabTitle then - child:disable() - end - end - self:emit({ type = 'tab_activate', activated = tab }) - tab:draw() - end + for _,child in pairs(self.children) do + if child.uid == event.tab.tabUid then + child:enable() + elseif child.tabTitle then + child:disable() + end + end + self:emit({ type = 'tab_activate', activated = tab }) + tab:draw() + end end --[[-- Wizard --]]-- UI.Wizard = class(UI.Window) UI.Wizard.defaults = { - UIElement = 'Wizard', - pages = { }, + UIElement = 'Wizard', + pages = { }, } function UI.Wizard:postInit() - self.cancelButton = UI.Button { - x = 2, y = -1, - text = 'Cancel', - event = 'cancel', - } - self.previousButton = UI.Button { - x = -18, y = -1, - text = '< Back', - event = 'previousView', - } - self.nextButton = UI.Button { - x = -9, y = -1, - text = 'Next >', - event = 'nextView', - } + self.cancelButton = UI.Button { + x = 2, y = -1, + text = 'Cancel', + event = 'cancel', + } + self.previousButton = UI.Button { + x = -18, y = -1, + text = '< Back', + event = 'previousView', + } + self.nextButton = UI.Button { + x = -9, y = -1, + text = 'Next >', + event = 'nextView', + } - Util.merge(self, self.pages) - for _, child in pairs(self.pages) do - child.ey = -2 - end + Util.merge(self, self.pages) + for _, child in pairs(self.pages) do + child.ey = -2 + end end function UI.Wizard:add(pages) - Util.merge(self.pages, pages) - Util.merge(self, pages) + Util.merge(self.pages, pages) + Util.merge(self, pages) - for _, child in pairs(self.pages) do - child.ey = child.ey or -2 - end + for _, child in pairs(self.pages) do + child.ey = child.ey or -2 + end - if self.parent then - self:initChildren() - end + if self.parent then + self:initChildren() + end end function UI.Wizard:enable() - self.enabled = true - for _,child in ipairs(self.children) do - if not child.index then - child:enable() - elseif child.index == 1 then - child:enable() - else - child:disable() - end - end - self:emit({ type = 'enable_view', view = Util.find(self.pages, 'index', 1) }) + self.enabled = true + for _,child in ipairs(self.children) do + if not child.index then + child:enable() + elseif child.index == 1 then + child:enable() + else + child:disable() + end + end + self:emit({ type = 'enable_view', next = Util.find(self.pages, 'index', 1) }) end function UI.Wizard:nextView() - local currentView = Util.find(self.pages, 'enabled', true) - local nextView = Util.find(self.pages, 'index', currentView.index + 1) + local currentView = Util.find(self.pages, 'enabled', true) + local nextView = Util.find(self.pages, 'index', currentView.index + 1) - if nextView then - self:emit({ type = 'enable_view', view = nextView }) - self:addTransition('slideLeft') - currentView:disable() - nextView:enable() - end + if nextView then + self:emit({ type = 'enable_view', view = nextView }) + self:addTransition('slideLeft') + currentView:disable() + nextView:enable() + end end function UI.Wizard:prevView() - local currentView = Util.find(self.pages, 'enabled', true) - local nextView = Util.find(self.pages, 'index', currentView.index - 1) + local currentView = Util.find(self.pages, 'enabled', true) + local nextView = Util.find(self.pages, 'index', currentView.index - 1) - if nextView then - self:emit({ type = 'enable_view', view = nextView }) - self:addTransition('slideRight') - currentView:disable() - nextView:enable() - end + if nextView then + self:emit({ type = 'enable_view', view = nextView }) + self:addTransition('slideRight') + currentView:disable() + nextView:enable() + end end function UI.Wizard:eventHandler(event) - if event.type == 'nextView' then - self:nextView() - self:draw() - return true + if event.type == 'nextView' then + local currentView = Util.find(self.pages, 'enabled', true) + local nextView = Util.find(self.pages, 'index', currentView.index + 1) + currentView:emit({ type = 'enable_view', next = nextView, current = currentView }) - elseif event.type == 'previousView' then - self:prevView() - self:draw() - return true + elseif event.type == 'previousView' then + local currentView = Util.find(self.pages, 'enabled', true) + local nextView = Util.find(self.pages, 'index', currentView.index - 1) + currentView:emit({ type = 'enable_view', prev = nextView, current = currentView }) + return true - elseif event.type == 'enable_view' then - if Util.find(self.pages, 'index', event.view.index - 1) then - self.previousButton:enable() - else - self.previousButton:disable() - end + elseif event.type == 'enable_view' then + if event.current then + if event.next then + self:addTransition('slideLeft') + elseif event.prev then + self:addTransition('slideRight') + end + event.current:disable() + end - if Util.find(self.pages, 'index', event.view.index + 1) then - self.nextButton.text = 'Next >' - self.nextButton.event = 'nextView' - else - self.nextButton.text = 'Accept' - self.nextButton.event = 'accept' - end - end + -- a new current view + local current = event.next or event.prev + current:enable() + + if Util.find(self.pages, 'index', current.index - 1) then + self.previousButton:enable() + else + self.previousButton:disable() + end + + if Util.find(self.pages, 'index', current.index + 1) then + self.nextButton.text = 'Next >' + self.nextButton.event = 'nextView' + else + self.nextButton.text = 'Accept' + self.nextButton.event = 'accept' + end + self:draw() + end end --[[-- SlideOut --]]-- UI.SlideOut = class(UI.Window) UI.SlideOut.defaults = { - UIElement = 'SlideOut', - pageType = 'modal', + UIElement = 'SlideOut', + pageType = 'modal', } function UI.SlideOut:setParent() - UI.Window.setParent(self) - self.canvas = self:addLayer() + UI.Window.setParent(self) + self.canvas = self:addLayer() end function UI.SlideOut:enable() - self.enabled = false + self.enabled = false end function UI.SlideOut:show() - self:addTransition('expandUp') - self.canvas:setVisible(true) - self.enabled = true - for _,child in pairs(self.children) do - child:enable() - end - self:draw() - self:capture(self) - self:focusFirst() + self:addTransition('expandUp') + self.canvas:setVisible(true) + self.enabled = true + for _,child in pairs(self.children) do + child:enable() + end + self:draw() + self:capture(self) + self:focusFirst() end function UI.SlideOut:disable() - self.canvas:setVisible(false) - self.enabled = false - if self.children then - for _,child in pairs(self.children) do - child:disable() - end - end + self.canvas:setVisible(false) + self.enabled = false + if self.children then + for _,child in pairs(self.children) do + child:disable() + end + end end function UI.SlideOut:hide() - self:disable() - self:release(self) - self:refocus() + self:disable() + self:release(self) + self:refocus() end function UI.SlideOut:eventHandler(event) - if event.type == 'slide_show' then - self:show() - return true + if event.type == 'slide_show' then + self:show() + return true - elseif event.type == 'slide_hide' then - self:hide() - return true - end + elseif event.type == 'slide_hide' then + self:hide() + return true + end end --[[-- Embedded --]]-- UI.Embedded = class(UI.Window) UI.Embedded.defaults = { - UIElement = 'Embedded', - backgroundColor = colors.black, - textColor = colors.white, - accelerators = { - up = 'scroll_up', - down = 'scroll_down', - } + UIElement = 'Embedded', + backgroundColor = colors.black, + textColor = colors.white, + accelerators = { + up = 'scroll_up', + down = 'scroll_down', + } } function UI.Embedded:setParent() - UI.Window.setParent(self) - self.win = window.create(UI.term.device, 1, 1, self.width, self.height, false) - Canvas.convertWindow(self.win, UI.term.device, self.x, self.y) - Terminal.scrollable(self.win, 100) + UI.Window.setParent(self) + self.win = window.create(UI.term.device, 1, 1, self.width, self.height, false) + Canvas.scrollingWindow(self.win, self.x, self.y) + self.win.setParent(UI.term.device) + self.win.setMaxScroll(100) - local canvas = self:getCanvas() - self.win.canvas.parent = canvas - table.insert(canvas.layers, self.win.canvas) - self.canvas = self.win.canvas + local canvas = self:getCanvas() + self.win.canvas.parent = canvas + table.insert(canvas.layers, self.win.canvas) + self.canvas = self.win.canvas - self.win.setCursorPos(1, 1) - self.win.setBackgroundColor(self.backgroundColor) - self.win.setTextColor(self.textColor) - self.win.clear() + self.win.setCursorPos(1, 1) + self.win.setBackgroundColor(self.backgroundColor) + self.win.setTextColor(self.textColor) + self.win.clear() end function UI.Embedded:draw() - self.canvas:dirty() + self.canvas:dirty() end function UI.Embedded:enable() - self.canvas:setVisible(true) - UI.Window.enable(self) + self.canvas:setVisible(true) + UI.Window.enable(self) end function UI.Embedded:disable() - self.canvas:setVisible(false) - UI.Window.disable(self) + self.canvas:setVisible(false) + UI.Window.disable(self) end function UI.Embedded:eventHandler(event) - if event.type == 'scroll_up' then - self.win.scrollUp() - return true - elseif event.type == 'scroll_down' then - self.win.scrollDown() - return true - end + if event.type == 'scroll_up' then + self.win.scrollUp() + return true + elseif event.type == 'scroll_down' then + self.win.scrollDown() + return true + end end function UI.Embedded:focus() - -- allow scrolling + -- allow scrolling end --[[-- Notification --]]-- UI.Notification = class(UI.Window) UI.Notification.defaults = { - UIElement = 'Notification', - backgroundColor = colors.gray, - height = 3, + UIElement = 'Notification', + backgroundColor = colors.gray, + height = 3, } function UI.Notification:draw() end function UI.Notification:enable() - self.enabled = false + self.enabled = false end function UI.Notification:error(value, timeout) - self.backgroundColor = colors.red - self:display(value, timeout) + self.backgroundColor = colors.red + self:display(value, timeout) end function UI.Notification:info(value, timeout) - self.backgroundColor = colors.gray - self:display(value, timeout) + self.backgroundColor = colors.gray + self:display(value, timeout) end function UI.Notification:success(value, timeout) - self.backgroundColor = colors.green - self:display(value, timeout) + self.backgroundColor = colors.green + self:display(value, timeout) end function UI.Notification:cancel() - if self.canvas then - Event.cancelNamedTimer('notificationTimer') - self.enabled = false - self.canvas:removeLayer() - self.canvas = nil - end + if self.canvas then + Event.cancelNamedTimer('notificationTimer') + self.enabled = false + self.canvas:removeLayer() + self.canvas = nil + end end function UI.Notification:display(value, timeout) - self.enabled = true - local lines = Util.wordWrap(value, self.width - 2) - self.height = #lines + 1 - self.y = self.parent.height - self.height + 1 - if self.canvas then - self.canvas:removeLayer() - end + self.enabled = true + local lines = Util.wordWrap(value, self.width - 2) + self.height = #lines + 1 + self.y = self.parent.height - self.height + 1 + if self.canvas then + self.canvas:removeLayer() + end - self.canvas = self:addLayer(self.backgroundColor, self.textColor) - self:addTransition('expandUp', { ticks = self.height }) - self.canvas:setVisible(true) - self:clear() - for k,v in pairs(lines) do - self:write(2, k, v) - end + self.canvas = self:addLayer(self.backgroundColor, self.textColor) + self:addTransition('expandUp', { ticks = self.height }) + self.canvas:setVisible(true) + self:clear() + for k,v in pairs(lines) do + self:write(2, k, v) + end - Event.addNamedTimer('notificationTimer', timeout or 3, false, function() - self:cancel() - self:sync() - end) + Event.addNamedTimer('notificationTimer', timeout or 3, false, function() + self:cancel() + self:sync() + end) end --[[-- Throttle --]]-- UI.Throttle = class(UI.Window) UI.Throttle.defaults = { - UIElement = 'Throttle', - backgroundColor = colors.gray, - height = 6, - width = 10, - timeout = .095, - ctr = 0, - image = { - ' //) (O )~@ &~&-( ?Q ', - ' //) (O )- @ \\-( ?) && ', - ' //) (O ), @ \\-(?) && ', - ' //) (O ). @ \\-d ) (@ ' - } + UIElement = 'Throttle', + backgroundColor = colors.gray, + height = 6, + width = 10, + timeout = .095, + ctr = 0, + image = { + ' //) (O )~@ &~&-( ?Q ', + ' //) (O )- @ \\-( ?) && ', + ' //) (O ), @ \\-(?) && ', + ' //) (O ). @ \\-d ) (@ ' + } } function UI.Throttle:setParent() - self.x = math.ceil((self.parent.width - self.width) / 2) - self.y = math.ceil((self.parent.height - self.height) / 2) - UI.Window.setParent(self) + self.x = math.ceil((self.parent.width - self.width) / 2) + self.y = math.ceil((self.parent.height - self.height) / 2) + UI.Window.setParent(self) end function UI.Throttle:enable() - self.enabled = false + self.enabled = false end function UI.Throttle:disable() - if self.canvas then - self.enabled = false - self.canvas:removeLayer() - self.canvas = nil - self.c = nil - end + if self.canvas then + self.enabled = false + self.canvas:removeLayer() + self.canvas = nil + self.c = nil + end end function UI.Throttle:update() - local cc = os.clock() - if not self.c then - self.c = cc - elseif cc > self.c + self.timeout then - os.sleep(0) - self.c = os.clock() - self.enabled = true - if not self.canvas then - self.canvas = self:addLayer(self.backgroundColor, colors.cyan) - self.canvas:setVisible(true) - self:clear(colors.cyan) - end - local image = self.image[self.ctr + 1] - local width = self.width - 2 - for i = 0, #self.image do - self:write(2, i + 2, image:sub(width * i + 1, width * i + width), colors.black, colors.white) - end + local cc = os.clock() + if not self.c then + self.c = cc + elseif cc > self.c + self.timeout then + os.sleep(0) + self.c = os.clock() + self.enabled = true + if not self.canvas then + self.canvas = self:addLayer(self.backgroundColor, colors.cyan) + self.canvas:setVisible(true) + self:clear(colors.cyan) + end + local image = self.image[self.ctr + 1] + local width = self.width - 2 + for i = 0, #self.image do + self:write(2, i + 2, image:sub(width * i + 1, width * i + width), colors.black, colors.white) + end - self.ctr = (self.ctr + 1) % #self.image + self.ctr = (self.ctr + 1) % #self.image - self:sync() - end + self:sync() + end end --[[-- StatusBar --]]-- UI.StatusBar = class(UI.Window) UI.StatusBar.defaults = { - UIElement = 'StatusBar', - backgroundColor = colors.lightGray, - textColor = colors.gray, - height = 1, - ey = -1, + UIElement = 'StatusBar', + backgroundColor = colors.lightGray, + textColor = colors.gray, + height = 1, + ey = -1, } function UI.StatusBar:adjustWidth() - -- Can only have 1 adjustable width - if self.columns then - local w = self.width - #self.columns - 1 - for _,c in pairs(self.columns) do - if c.width then - c.cw = c.width -- computed width - w = w - c.width - end - end - for _,c in pairs(self.columns) do - if not c.width then - c.cw = w - end - end - end + -- Can only have 1 adjustable width + if self.columns then + local w = self.width - #self.columns - 1 + for _,c in pairs(self.columns) do + if c.width then + c.cw = c.width -- computed width + w = w - c.width + end + end + for _,c in pairs(self.columns) do + if not c.width then + c.cw = w + end + end + end end function UI.StatusBar:resize() - UI.Window.resize(self) - self:adjustWidth() + UI.Window.resize(self) + self:adjustWidth() end function UI.StatusBar:setParent() - UI.Window.setParent(self) - self:adjustWidth() + UI.Window.setParent(self) + self:adjustWidth() end function UI.StatusBar:setStatus(status) - if self.values ~= status then - self.values = status - self:draw() - end + if self.values ~= status then + self.values = status + self:draw() + end end function UI.StatusBar:setValue(name, value) - if not self.values then - self.values = { } - end - self.values[name] = value + if not self.values then + self.values = { } + end + self.values[name] = value end function UI.StatusBar:getValue(name) - if self.values then - return self.values[name] - end + if self.values then + return self.values[name] + end end function UI.StatusBar:timedStatus(status, timeout) - timeout = timeout or 3 - self:write(2, 1, Util.widthify(status, self.width-2), self.backgroundColor) - Event.addNamedTimer('statusTimer', timeout, false, function() - if self.parent.enabled then - self:draw() - self:sync() - end - end) + timeout = timeout or 3 + self:write(2, 1, Util.widthify(status, self.width-2), self.backgroundColor) + Event.addNamedTimer('statusTimer', timeout, false, function() + if self.parent.enabled then + self:draw() + self:sync() + end + end) end function UI.StatusBar:getColumnWidth(name) - local c = Util.find(self.columns, 'key', name) - return c and c.cw + local c = Util.find(self.columns, 'key', name) + return c and c.cw end function UI.StatusBar:setColumnWidth(name, width) - local c = Util.find(self.columns, 'key', name) - if c then - c.cw = width - end + local c = Util.find(self.columns, 'key', name) + if c then + c.cw = width + end end function UI.StatusBar:draw() - if not self.values then - self:clear() - elseif type(self.values) == 'string' then - self:write(1, 1, Util.widthify(' ' .. self.values, self.width)) - else - local s = '' - for _,c in ipairs(self.columns) do - s = s .. ' ' .. Util.widthify(tostring(self.values[c.key] or ''), c.cw) - end - self:write(1, 1, Util.widthify(s, self.width)) - end + if not self.values then + self:clear() + elseif type(self.values) == 'string' then + self:write(1, 1, Util.widthify(' ' .. self.values, self.width)) + else + local s = '' + for _,c in ipairs(self.columns) do + s = s .. ' ' .. Util.widthify(tostring(self.values[c.key] or ''), c.cw) + end + self:write(1, 1, Util.widthify(s, self.width)) + end end --[[-- ProgressBar --]]-- UI.ProgressBar = class(UI.Window) UI.ProgressBar.defaults = { - UIElement = 'ProgressBar', - progressColor = colors.lime, - backgroundColor = colors.gray, - height = 1, - value = 0, + UIElement = 'ProgressBar', + progressColor = colors.lime, + backgroundColor = colors.gray, + height = 1, + value = 0, } function UI.ProgressBar:draw() - self:clear() - local width = math.ceil(self.value / 100 * self.width) - self:clearArea(1, 1, width, self.height, self.progressColor) + self:clear() + local width = math.ceil(self.value / 100 * self.width) + self:clearArea(1, 1, width, self.height, self.progressColor) end --[[-- VerticalMeter --]]-- UI.VerticalMeter = class(UI.Window) UI.VerticalMeter.defaults = { - UIElement = 'VerticalMeter', - backgroundColor = colors.gray, - meterColor = colors.lime, - width = 1, - value = 0, + UIElement = 'VerticalMeter', + backgroundColor = colors.gray, + meterColor = colors.lime, + width = 1, + value = 0, } function UI.VerticalMeter:draw() - local height = self.height - math.ceil(self.value / 100 * self.height) - self:clear() - self:clearArea(1, height + 1, self.width, self.height, self.meterColor) + local height = self.height - math.ceil(self.value / 100 * self.height) + self:clear() + self:clearArea(1, height + 1, self.width, self.height, self.meterColor) end --[[-- TextEntry --]]-- UI.TextEntry = class(UI.Window) UI.TextEntry.defaults = { - UIElement = 'TextEntry', - value = '', - shadowText = '', - focused = false, - textColor = colors.white, - shadowTextColor = colors.gray, - backgroundColor = colors.black, -- colors.lightGray, - backgroundFocusColor = colors.black, --lightGray, - height = 1, - limit = 6, - pos = 0, - accelerators = { - [ 'control-c' ] = 'copy', - } + UIElement = 'TextEntry', + value = '', + shadowText = '', + focused = false, + textColor = colors.white, + shadowTextColor = colors.gray, + backgroundColor = colors.black, -- colors.lightGray, + backgroundFocusColor = colors.black, --lightGray, + height = 1, + limit = 6, + pos = 0, + accelerators = { + [ 'control-c' ] = 'copy', + } } function UI.TextEntry:postInit() - self.value = tostring(self.value) + self.value = tostring(self.value) end function UI.TextEntry:setValue(value) - self.value = value + self.value = value end function UI.TextEntry:setPosition(pos) - self.pos = pos + self.pos = pos end function UI.TextEntry:updateScroll() - if not self.scroll then - self.scroll = 0 - end + if not self.scroll then + self.scroll = 0 + end - if not self.pos then - self.pos = #tostring(self.value) - self.scroll = 0 - elseif self.pos > #tostring(self.value) then - self.pos = #tostring(self.value) - self.scroll = 0 - end + if not self.pos then + self.pos = #tostring(self.value) + self.scroll = 0 + elseif self.pos > #tostring(self.value) then + self.pos = #tostring(self.value) + self.scroll = 0 + end - if self.pos - self.scroll > self.width - 2 then - self.scroll = self.pos - (self.width - 2) - elseif self.pos < self.scroll then - self.scroll = self.pos - end + if self.pos - self.scroll > self.width - 2 then + self.scroll = self.pos - (self.width - 2) + elseif self.pos < self.scroll then + self.scroll = self.pos + end end function UI.TextEntry:draw() - local bg = self.backgroundColor - local tc = self.textColor - if self.focused then - bg = self.backgroundFocusColor - end + local bg = self.backgroundColor + local tc = self.textColor + if self.focused then + bg = self.backgroundFocusColor + end - self:updateScroll() - local text = tostring(self.value) - if #text > 0 then - if self.scroll and self.scroll > 0 then - text = text:sub(1 + self.scroll) - end - if self.mask then - text = _rep('*', #text) - end - else - tc = self.shadowTextColor - text = self.shadowText - end + self:updateScroll() + local text = tostring(self.value) + if #text > 0 then + if self.scroll and self.scroll > 0 then + text = text:sub(1 + self.scroll) + end + if self.mask then + text = _rep('*', #text) + end + else + tc = self.shadowTextColor + text = self.shadowText + end - self:write(1, 1, ' ' .. Util.widthify(text, self.width - 2) .. ' ', bg, tc) - if self.focused then - self:setCursorPos(self.pos-self.scroll+2, 1) - end + self:write(1, 1, ' ' .. Util.widthify(text, self.width - 2) .. ' ', bg, tc) + if self.focused then + self:setCursorPos(self.pos-self.scroll+2, 1) + end end function UI.TextEntry:reset() - self.pos = 0 - self.value = '' - self:draw() - self:updateCursor() + self.pos = 0 + self.value = '' + self:draw() + self:updateCursor() end function UI.TextEntry:updateCursor() - self:updateScroll() - self:setCursorPos(self.pos-self.scroll+2, 1) + self:updateScroll() + self:setCursorPos(self.pos-self.scroll+2, 1) end function UI.TextEntry:focus() - self:draw() - if self.focused then - self:setCursorBlink(true) - else - self:setCursorBlink(false) - end + self:draw() + if self.focused then + self:setCursorBlink(true) + else + self:setCursorBlink(false) + end end --[[ - A few lines below from theoriginalbit - http://www.computercraft.info/forums2/index.php?/topic/16070-read-and-limit-length-of-the-input-field/ + A few lines below from theoriginalbit + http://www.computercraft.info/forums2/index.php?/topic/16070-read-and-limit-length-of-the-input-field/ --]] function UI.TextEntry:eventHandler(event) - if event.type == 'key' then - local ch = event.key - if ch == 'left' then - if self.pos > 0 then - self.pos = math.max(self.pos-1, 0) - self:draw() - end - elseif ch == 'right' then - local input = tostring(self.value) - if self.pos < #input then - self.pos = math.min(self.pos+1, #input) - self:draw() - end - elseif ch == 'home' then - self.pos = 0 - self:draw() - elseif ch == 'end' then - self.pos = #tostring(self.value) - self:draw() - elseif ch == 'backspace' then - if self.pos > 0 then - local input = tostring(self.value) - self.value = input:sub(1, self.pos-1) .. input:sub(self.pos+1) - self.pos = self.pos - 1 - self:draw() - self:emit({ type = 'text_change', text = self.value, element = self }) - end - elseif ch == 'delete' then - local input = tostring(self.value) - if self.pos < #input then - self.value = input:sub(1, self.pos) .. input:sub(self.pos+2) - self:draw() - self:emit({ type = 'text_change', text = self.value, element = self }) - end - elseif #ch == 1 then - local input = tostring(self.value) - if #input < self.limit then - self.value = input:sub(1, self.pos) .. ch .. input:sub(self.pos+1) - self.pos = self.pos + 1 - self:draw() - self:emit({ type = 'text_change', text = self.value, element = self }) - end - else - return false - end - return true + if event.type == 'key' then + local ch = event.key + if ch == 'left' then + if self.pos > 0 then + self.pos = math.max(self.pos-1, 0) + self:draw() + end + elseif ch == 'right' then + local input = tostring(self.value) + if self.pos < #input then + self.pos = math.min(self.pos+1, #input) + self:draw() + end + elseif ch == 'home' then + self.pos = 0 + self:draw() + elseif ch == 'end' then + self.pos = #tostring(self.value) + self:draw() + elseif ch == 'backspace' then + if self.pos > 0 then + local input = tostring(self.value) + self.value = input:sub(1, self.pos-1) .. input:sub(self.pos+1) + self.pos = self.pos - 1 + self:draw() + self:emit({ type = 'text_change', text = self.value, element = self }) + end + elseif ch == 'delete' then + local input = tostring(self.value) + if self.pos < #input then + self.value = input:sub(1, self.pos) .. input:sub(self.pos+2) + self:draw() + self:emit({ type = 'text_change', text = self.value, element = self }) + end + elseif #ch == 1 then + local input = tostring(self.value) + if #input < self.limit then + self.value = input:sub(1, self.pos) .. ch .. input:sub(self.pos+1) + self.pos = self.pos + 1 + self:draw() + self:emit({ type = 'text_change', text = self.value, element = self }) + end + else + return false + end + return true - elseif event.type == 'copy' then - os.queueEvent('clipboard_copy', self.value) + elseif event.type == 'copy' then + os.queueEvent('clipboard_copy', self.value) - elseif event.type == 'paste' then - local input = tostring(self.value) - local text = event.text - if #input + #text > self.limit then - text = text:sub(1, self.limit-#input) - end - self.value = input:sub(1, self.pos) .. text .. input:sub(self.pos+1) - self.pos = self.pos + #text - self:draw() - self:updateCursor() - self:emit({ type = 'text_change', text = self.value, element = self }) - return true + elseif event.type == 'paste' then + local input = tostring(self.value) + local text = event.text + if #input + #text > self.limit then + text = text:sub(1, self.limit-#input) + end + self.value = input:sub(1, self.pos) .. text .. input:sub(self.pos+1) + self.pos = self.pos + #text + self:draw() + self:updateCursor() + self:emit({ type = 'text_change', text = self.value, element = self }) + return true - elseif event.type == 'mouse_click' then - if self.focused and event.x > 1 then - self.pos = event.x + self.scroll - 2 - self:updateCursor() - return true - end - elseif event.type == 'mouse_rightclick' then - local input = tostring(self.value) - if #input > 0 then - self:reset() - self:emit({ type = 'text_change', text = self.value, element = self }) - end - end + elseif event.type == 'mouse_click' then + if self.focused and event.x > 1 then + self.pos = event.x + self.scroll - 2 + self:updateCursor() + return true + end + elseif event.type == 'mouse_rightclick' then + local input = tostring(self.value) + if #input > 0 then + self:reset() + self:emit({ type = 'text_change', text = self.value, element = self }) + end + end - return false + return false end --[[-- Chooser --]]-- UI.Chooser = class(UI.Window) UI.Chooser.defaults = { - UIElement = 'Chooser', - choices = { }, - nochoice = 'Select', - backgroundFocusColor = colors.lightGray, - height = 1, + UIElement = 'Chooser', + choices = { }, + nochoice = 'Select', + backgroundFocusColor = colors.lightGray, + height = 1, } function UI.Chooser:setParent() - if not self.width and not self.ex then - self.width = 1 - for _,v in pairs(self.choices) do - if #v.name > self.width then - self.width = #v.name - end - end - self.width = self.width + 4 - end - UI.Window.setParent(self) + if not self.width and not self.ex then + self.width = 1 + for _,v in pairs(self.choices) do + if #v.name > self.width then + self.width = #v.name + end + end + self.width = self.width + 4 + end + UI.Window.setParent(self) end function UI.Chooser:draw() - local bg = self.backgroundColor - if self.focused then - bg = self.backgroundFocusColor - end - local choice = Util.find(self.choices, 'value', self.value) - local value = self.nochoice - if choice then - value = choice.name - end - self:write(1, 1, '<', bg, colors.black) - self:write(2, 1, ' ' .. Util.widthify(value, self.width-4) .. ' ', bg) - self:write(self.width, 1, '>', bg, colors.black) + local bg = self.backgroundColor + if self.focused then + bg = self.backgroundFocusColor + end + local choice = Util.find(self.choices, 'value', self.value) + local value = self.nochoice + if choice then + value = choice.name + end + self:write(1, 1, '<', bg, colors.black) + self:write(2, 1, ' ' .. Util.widthify(value, self.width-4) .. ' ', bg) + self:write(self.width, 1, '>', bg, colors.black) end function UI.Chooser:focus() - self:draw() + self:draw() end function UI.Chooser:eventHandler(event) - if event.type == 'key' then - if event.key == 'right' or event.key == 'space' then - local _,k = Util.find(self.choices, 'value', self.value) - if k and k < #self.choices then - self.value = self.choices[k+1].value - else - self.value = self.choices[1].value - end - self:emit({ type = 'choice_change', value = self.value }) - self:draw() - return true - elseif event.key == 'left' then - local _,k = Util.find(self.choices, 'value', self.value) - if k and k > 1 then - self.value = self.choices[k-1].value - else - self.value = self.choices[#self.choices].value - end - self:emit({ type = 'choice_change', value = self.value }) - self:draw() - return true - end - elseif event.type == 'mouse_click' then - if event.x == 1 then - self:emit({ type = 'key', key = 'left' }) - return true - elseif event.x == self.width then - self:emit({ type = 'key', key = 'right' }) - return true - end - end + if event.type == 'key' then + if event.key == 'right' or event.key == 'space' then + local _,k = Util.find(self.choices, 'value', self.value) + if k and k < #self.choices then + self.value = self.choices[k+1].value + else + self.value = self.choices[1].value + end + self:emit({ type = 'choice_change', value = self.value }) + self:draw() + return true + elseif event.key == 'left' then + local _,k = Util.find(self.choices, 'value', self.value) + if k and k > 1 then + self.value = self.choices[k-1].value + else + self.value = self.choices[#self.choices].value + end + self:emit({ type = 'choice_change', value = self.value }) + self:draw() + return true + end + elseif event.type == 'mouse_click' then + if event.x == 1 then + self:emit({ type = 'key', key = 'left' }) + return true + elseif event.x == self.width then + self:emit({ type = 'key', key = 'right' }) + return true + end + end end --[[-- Text --]]-- UI.Text = class(UI.Window) UI.Text.defaults = { - UIElement = 'Text', - value = '', - height = 1, + UIElement = 'Text', + value = '', + height = 1, } function UI.Text:setParent() - if not self.width and not self.ex then - self.width = #tostring(self.value) - end - UI.Window.setParent(self) + if not self.width and not self.ex then + self.width = #tostring(self.value) + end + UI.Window.setParent(self) end function UI.Text:draw() - self:write(1, 1, Util.widthify(self.value or '', self.width), self.backgroundColor) + self:write(1, 1, Util.widthify(self.value or '', self.width), self.backgroundColor) end --[[-- ScrollBar --]]-- UI.ScrollBar = class(UI.Window) UI.ScrollBar.defaults = { - UIElement = 'ScrollBar', - lineChar = '|', - sliderChar = '#', - upArrowChar = '^', - downArrowChar = 'v', - scrollbarColor = colors.lightGray, - value = '', - width = 1, - x = -1, - ey = -1, + UIElement = 'ScrollBar', + lineChar = '|', + sliderChar = '#', + upArrowChar = '^', + downArrowChar = 'v', + scrollbarColor = colors.lightGray, + value = '', + width = 1, + x = -1, + ey = -1, } function UI.ScrollBar:draw() - local parent = self.parent - local view = parent:getViewArea() + local parent = self.parent + local view = parent:getViewArea() - if view.totalHeight > view.height then - local maxScroll = view.totalHeight - view.height - local percent = view.offsetY / maxScroll - local sliderSize = math.max(1, Util.round(view.height / view.totalHeight * (view.height - 2))) - local x = self.width + if view.totalHeight > view.height then + local maxScroll = view.totalHeight - view.height + local percent = view.offsetY / maxScroll + local sliderSize = math.max(1, Util.round(view.height / view.totalHeight * (view.height - 2))) + local x = self.width - local row = view.y - if not view.static then -- does the container scroll ? - self.y = row -- if so, move the scrollbar onscreen - row = 1 - end + local row = view.y + if not view.static then -- does the container scroll ? + self.y = row -- if so, move the scrollbar onscreen + row = 1 + end - for i = 1, view.height - 2 do - self:write(x, row + i, self.lineChar, nil, self.scrollbarColor) - end + for i = 1, view.height - 2 do + self:write(x, row + i, self.lineChar, nil, self.scrollbarColor) + end - local y = Util.round((view.height - 2 - sliderSize) * percent) - for i = 1, sliderSize do - self:write(x, row + y + i, self.sliderChar, nil, self.scrollbarColor) - end + local y = Util.round((view.height - 2 - sliderSize) * percent) + for i = 1, sliderSize do + self:write(x, row + y + i, self.sliderChar, nil, self.scrollbarColor) + end - local color = self.scrollbarColor - if view.offsetY > 0 then - color = colors.white - end - self:write(x, row, self.upArrowChar, nil, color) + local color = self.scrollbarColor + if view.offsetY > 0 then + color = colors.white + end + self:write(x, row, self.upArrowChar, nil, color) - color = self.scrollbarColor - if view.offsetY + view.height < view.totalHeight then - color = colors.white - end - self:write(x, row + view.height - 1, self.downArrowChar, nil, color) - end + color = self.scrollbarColor + if view.offsetY + view.height < view.totalHeight then + color = colors.white + end + self:write(x, row + view.height - 1, self.downArrowChar, nil, color) + end end function UI.ScrollBar:eventHandler(event) - if event.type == 'mouse_click' or event.type == 'mouse_doubleclick' then - if event.x == 1 then - local view = self.parent:getViewArea() - if view.totalHeight > view.height then - if event.y == view.y then - self:emit({ type = 'scroll_up'}) - elseif event.y == self.height then - self:emit({ type = 'scroll_down'}) - -- else - -- ... percentage ... - end - end - return true - end - end + if event.type == 'mouse_click' or event.type == 'mouse_doubleclick' then + if event.x == 1 then + local view = self.parent:getViewArea() + if view.totalHeight > view.height then + if event.y == view.y then + self:emit({ type = 'scroll_up'}) + elseif event.y == self.height then + self:emit({ type = 'scroll_down'}) + -- else + -- ... percentage ... + end + end + return true + end + end end --[[-- TextArea --]]-- UI.TextArea = class(UI.Viewport) UI.TextArea.defaults = { - UIElement = 'TextArea', - marginRight = 2, - value = '', + UIElement = 'TextArea', + marginRight = 2, + value = '', } function UI.TextArea:postInit() - self.scrollBar = UI.ScrollBar() + self.scrollBar = UI.ScrollBar() end function UI.TextArea:setText(text) - self.offy = 0 - self.ymax = nil - self.value = text - self:draw() + self.offy = 0 + self.ymax = nil + self.value = text + self:draw() end function UI.TextArea:focus() - -- allow keyboard scrolling + -- allow keyboard scrolling end function UI.TextArea:draw() - self:clear() + self:clear() -- self:setCursorPos(1, 1) - self.cursorX, self.cursorY = 1, 1 - self:print(self.value) - self.ymax = self.cursorY + 1 + self.cursorX, self.cursorY = 1, 1 + self:print(self.value) + self.ymax = self.cursorY + 1 - for _,child in pairs(self.children) do - if child.enabled then - child:draw() - end - end + for _,child in pairs(self.children) do + if child.enabled then + child:draw() + end + end end --[[-- Form --]]-- UI.Form = class(UI.Window) UI.Form.defaults = { - UIElement = 'Form', - values = { }, - margin = 2, - event = 'form_complete', + UIElement = 'Form', + values = { }, + margin = 2, + event = 'form_complete', } function UI.Form:postInit() - self:createForm() + self:createForm() end function UI.Form:reset() - for _,child in pairs(self.children) do - if child.reset then - child:reset() - end - end + for _,child in pairs(self.children) do + if child.reset then + child:reset() + end + end end function UI.Form:setValues(values) - self:reset() - self.values = values - for _,child in pairs(self.children) do - if child.formKey then - -- this should be child:setValue(self.values[child.formKey]) - -- so chooser can set default choice if null - -- null should be valid as well - child.value = self.values[child.formKey] or '' - end - end + self:reset() + self.values = values + for _,child in pairs(self.children) do + if child.formKey then + -- this should be child:setValue(self.values[child.formKey]) + -- so chooser can set default choice if null + -- null should be valid as well + child.value = self.values[child.formKey] or '' + end + end end function UI.Form:createForm() - self.children = self.children or { } + self.children = self.children or { } - if not self.labelWidth then - self.labelWidth = 1 - for _, child in pairs(self) do - if type(child) == 'table' and child.UIElement then - if child.formLabel then - self.labelWidth = math.max(self.labelWidth, #child.formLabel + 2) - end - end - end - end + if not self.labelWidth then + self.labelWidth = 1 + for _, child in pairs(self) do + if type(child) == 'table' and child.UIElement then + if child.formLabel then + self.labelWidth = math.max(self.labelWidth, #child.formLabel + 2) + end + end + end + end - local y = self.margin - for _, child in pairs(self) do - if type(child) == 'table' and child.UIElement then - if child.formKey then - child.value = self.values[child.formKey] or '' - end - if child.formLabel then - child.x = self.labelWidth + self.margin - 1 - child.y = y - if not child.width and not child.ex then - child.ex = -self.margin - end + local y = self.margin + for _, child in pairs(self) do + if type(child) == 'table' and child.UIElement then + if child.formKey then + child.value = self.values[child.formKey] or '' + end + if child.formLabel then + child.x = self.labelWidth + self.margin - 1 + child.y = y + if not child.width and not child.ex then + child.ex = -self.margin + end - table.insert(self.children, UI.Text { - x = self.margin, - y = y, - textColor = colors.black, - width = #child.formLabel, - value = child.formLabel, - }) - end - if child.formKey or child.formLabel then - y = y + 1 - end - end - end + table.insert(self.children, UI.Text { + x = self.margin, + y = y, + textColor = colors.black, + width = #child.formLabel, + value = child.formLabel, + }) + end + if child.formKey or child.formLabel then + y = y + 1 + end + end + end - table.insert(self.children, UI.Button { - y = -self.margin, x = -12 - self.margin, - text = 'Ok', - event = 'form_ok', - }) - table.insert(self.children, UI.Button { - y = -self.margin, x = -7 - self.margin, - text = 'Cancel', - event = 'form_cancel', - }) + table.insert(self.children, UI.Button { + y = -self.margin, x = -12 - self.margin, + text = 'Ok', + event = 'form_ok', + }) + table.insert(self.children, UI.Button { + y = -self.margin, x = -7 - self.margin, + text = 'Cancel', + event = 'form_cancel', + }) end function UI.Form:validateField(field) - if field.required then - if not field.value or #tostring(field.value) == 0 then - return false, 'Field is required' - end - end - return true + if field.required then + if not field.value or #tostring(field.value) == 0 then + return false, 'Field is required' + end + end + return true end function UI.Form:eventHandler(event) - if event.type == 'form_ok' then - for _,child in pairs(self.children) do - if child.formKey then - local s, m = self:validateField(child) - if not s then - self:setFocus(child) - self:emit({ type = 'form_invalid', message = m, field = child }) - return false - end - end - end - for _,child in pairs(self.children) do - if child.formKey then - self.values[child.formKey] = child.value - end - end - self:emit({ type = self.event, UIElement = self }) - else - return UI.Window.eventHandler(self, event) - end - return true + if event.type == 'form_ok' then + for _,child in pairs(self.children) do + if child.formKey then + local s, m = self:validateField(child) + if not s then + self:setFocus(child) + self:emit({ type = 'form_invalid', message = m, field = child }) + return false + end + end + end + for _,child in pairs(self.children) do + if child.formKey then + self.values[child.formKey] = child.value + end + end + self:emit({ type = self.event, UIElement = self }) + else + return UI.Window.eventHandler(self, event) + end + return true end --[[-- Dialog --]]-- UI.Dialog = class(UI.Page) UI.Dialog.defaults = { - UIElement = 'Dialog', - x = 7, - y = 4, - z = 2, - height = 7, - textColor = colors.black, - backgroundColor = colors.white, + UIElement = 'Dialog', + x = 7, + y = 4, + z = 2, + height = 7, + textColor = colors.black, + backgroundColor = colors.white, } function UI.Dialog:postInit() - self.titleBar = UI.TitleBar({ previousPage = true, title = self.title }) + self.titleBar = UI.TitleBar({ previousPage = true, title = self.title }) end function UI.Dialog:setParent() - if not self.width then - self.width = self.parent.width - 11 - end - if self.width > self.parent.width then - self.width = self.parent.width - end - self.x = math.floor((self.parent.width - self.width) / 2) + 1 - self.y = math.floor((self.parent.height - self.height) / 2) + 1 - UI.Page.setParent(self) + if not self.width then + self.width = self.parent.width - 11 + end + if self.width > self.parent.width then + self.width = self.parent.width + end + self.x = math.floor((self.parent.width - self.width) / 2) + 1 + self.y = math.floor((self.parent.height - self.height) / 2) + 1 + UI.Page.setParent(self) end function UI.Dialog:disable() - self.previousPage.canvas.palette = self.oldPalette - UI.Page.disable(self) + self.previousPage.canvas.palette = self.oldPalette + UI.Page.disable(self) end function UI.Dialog:enable(...) - self.oldPalette = self.previousPage.canvas.palette - self.previousPage.canvas:applyPalette(Canvas.darkPalette) - self:addTransition('grow') - UI.Page.enable(self, ...) + self.oldPalette = self.previousPage.canvas.palette + self.previousPage.canvas:applyPalette(Canvas.darkPalette) + self:addTransition('grow') + UI.Page.enable(self, ...) end function UI.Dialog:eventHandler(event) - if event.type == 'cancel' then - UI:setPreviousPage() - end - return UI.Page.eventHandler(self, event) + if event.type == 'cancel' then + UI:setPreviousPage() + end + return UI.Page.eventHandler(self, event) end --[[-- Image --]]-- UI.Image = class(UI.Window) UI.Image.defaults = { - UIElement = 'Image', - event = 'button_press', + UIElement = 'Image', + event = 'button_press', } function UI.Image:setParent() - if self.image then - self.height = #self.image - end - if self.image and not self.width then - self.width = #self.image[1] - end - UI.Window.setParent(self) + if self.image then + self.height = #self.image + end + if self.image and not self.width then + self.width = #self.image[1] + end + UI.Window.setParent(self) end function UI.Image:draw() - self:clear() - if self.image then - for y = 1, #self.image do - local line = self.image[y] - for x = 1, #line do - local ch = line[x] - if type(ch) == 'number' then - if ch > 0 then - self:write(x, y, ' ', ch) - end - else - self:write(x, y, ch) - end - end - end - end + self:clear() + if self.image then + for y = 1, #self.image do + local line = self.image[y] + for x = 1, #line do + local ch = line[x] + if type(ch) == 'number' then + if ch > 0 then + self:write(x, y, ' ', ch) + end + else + self:write(x, y, ch) + end + end + end + end end function UI.Image:setImage(image) - self.image = image + self.image = image end --[[-- NftImage --]]-- UI.NftImage = class(UI.Window) UI.NftImage.defaults = { - UIElement = 'NftImage', - event = 'button_press', + UIElement = 'NftImage', + event = 'button_press', } function UI.NftImage:setParent() - if self.image then - self.height = self.image.height - end - if self.image and not self.width then - self.width = self.image.width - end - UI.Window.setParent(self) + if self.image then + self.height = self.image.height + end + if self.image and not self.width then + self.width = self.image.width + end + UI.Window.setParent(self) end function UI.NftImage:draw() - if self.image then - for y = 1, self.image.height do - for x = 1, #self.image.text[y] do - self:write(x, y, self.image.text[y][x], self.image.bg[y][x], self.image.fg[y][x]) - end - end - else - self:clear() - end + if self.image then + for y = 1, self.image.height do + for x = 1, #self.image.text[y] do + self:write(x, y, self.image.text[y][x], self.image.bg[y][x], self.image.fg[y][x]) + end + end + else + self:clear() + end end function UI.NftImage:setImage(image) - self.image = image + self.image = image end UI:loadTheme('usr/config/ui.theme') if Util.getVersion() >= 1.76 then - UI:loadTheme('sys/etc/ext.theme') + UI:loadTheme('sys/etc/ext.theme') end UI:setDefaultDevice(UI.Device({ device = term.current() })) diff --git a/sys/apis/ui/canvas.lua b/sys/apis/ui/canvas.lua index 38e9898..baa011c 100644 --- a/sys/apis/ui/canvas.lua +++ b/sys/apis/ui/canvas.lua @@ -14,357 +14,485 @@ Canvas.darkPalette = { } Canvas.grayscalePalette = { } for n = 1, 16 do - Canvas.colorPalette[2 ^ (n - 1)] = _sub("0123456789abcdef", n, n) - Canvas.grayscalePalette[2 ^ (n - 1)] = _sub("088888878877787f", n, n) - Canvas.darkPalette[2 ^ (n - 1)] = _sub("8777777f77fff77f", n, n) + Canvas.colorPalette[2 ^ (n - 1)] = _sub("0123456789abcdef", n, n) + Canvas.grayscalePalette[2 ^ (n - 1)] = _sub("088888878877787f", n, n) + Canvas.darkPalette[2 ^ (n - 1)] = _sub("8777777f77fff77f", n, n) end function Canvas:init(args) - self.x = 1 - self.y = 1 - self.layers = { } + self.x = 1 + self.y = 1 + self.layers = { } - Util.merge(self, args) + Util.merge(self, args) - self.ex = self.x + self.width - 1 - self.ey = self.y + self.height - 1 + self.ex = self.x + self.width - 1 + self.ey = self.y + self.height - 1 - if not self.palette then - if self.isColor then - self.palette = Canvas.colorPalette - else - self.palette = Canvas.grayscalePalette - end - end + if not self.palette then + if self.isColor then + self.palette = Canvas.colorPalette + else + self.palette = Canvas.grayscalePalette + end + end - self.lines = { } - for i = 1, self.height do - self.lines[i] = { } - end + self.lines = { } + for i = 1, self.height do + self.lines[i] = { } + end end function Canvas:move(x, y) - self.x, self.y = x, y - self.ex = self.x + self.width - 1 - self.ey = self.y + self.height - 1 + self.x, self.y = x, y + self.ex = self.x + self.width - 1 + self.ey = self.y + self.height - 1 end function Canvas:resize(w, h) - for i = self.height, h do - self.lines[i] = { } - end + for i = self.height, h do + self.lines[i] = { } + end - while #self.lines > h do - table.remove(self.lines, #self.lines) - end + while #self.lines > h do + table.remove(self.lines, #self.lines) + end - if w ~= self.width then - for i = 1, self.height do - self.lines[i] = { dirty = true } - end - end + if w ~= self.width then + for i = 1, self.height do + self.lines[i] = { dirty = true } + end + end - self.ex = self.x + w - 1 - self.ey = self.y + h - 1 - self.width = w - self.height = h + self.ex = self.x + w - 1 + self.ey = self.y + h - 1 + self.width = w + self.height = h end function Canvas:copy() - local b = Canvas({ - x = self.x, - y = self.y, - width = self.width, - height = self.height, - isColor = self.isColor, - }) - for i = 1, self.height do - b.lines[i].text = self.lines[i].text - b.lines[i].fg = self.lines[i].fg - b.lines[i].bg = self.lines[i].bg - end - return b + local b = Canvas({ + x = self.x, + y = self.y, + width = self.width, + height = self.height, + isColor = self.isColor, + }) + for i = 1, self.height do + b.lines[i].text = self.lines[i].text + b.lines[i].fg = self.lines[i].fg + b.lines[i].bg = self.lines[i].bg + end + return b end function Canvas:addLayer(layer) - local canvas = Canvas({ - x = layer.x, - y = layer.y, - width = layer.width, - height = layer.height, - isColor = self.isColor, - }) - canvas.parent = self - table.insert(self.layers, canvas) - return canvas + local canvas = Canvas({ + x = layer.x, + y = layer.y, + width = layer.width, + height = layer.height, + isColor = self.isColor, + }) + canvas.parent = self + table.insert(self.layers, canvas) + return canvas end function Canvas:removeLayer() - for k, layer in pairs(self.parent.layers) do - if layer == self then - self:setVisible(false) - table.remove(self.parent.layers, k) - break - end - end + for k, layer in pairs(self.parent.layers) do + if layer == self then + self:setVisible(false) + table.remove(self.parent.layers, k) + break + end + end end function Canvas:setVisible(visible) - self.visible = visible - if not visible then - self.parent:dirty() - -- set parent's lines to dirty for each line in self - end + self.visible = visible + if not visible then + self.parent:dirty() + -- set parent's lines to dirty for each line in self + end end function Canvas:write(x, y, text, bg, fg) - if bg then - bg = _rep(self.palette[bg], #text) - end - if fg then - fg = _rep(self.palette[fg], #text) - end - self:writeBlit(x, y, text, bg, fg) + if bg then + bg = _rep(self.palette[bg], #text) + end + if fg then + fg = _rep(self.palette[fg], #text) + end + self:writeBlit(x, y, text, bg, fg) end function Canvas:writeBlit(x, y, text, bg, fg) - if y > 0 and y <= self.height and x <= self.width then - local width = #text + if y > 0 and y <= #self.lines and x <= self.width then + local width = #text - -- fix ffs - if x < 1 then - text = _sub(text, 2 - x) - if bg then - bg = _sub(bg, 2 - x) - end - if bg then - fg = _sub(fg, 2 - x) - end - width = width + x - 1 - x = 1 - end + -- fix ffs + if x < 1 then + text = _sub(text, 2 - x) + if bg then + bg = _sub(bg, 2 - x) + end + if bg then + fg = _sub(fg, 2 - x) + end + width = width + x - 1 + x = 1 + end - if x + width - 1 > self.width then - text = _sub(text, 1, self.width - x + 1) - if bg then - bg = _sub(bg, 1, self.width - x + 1) - end - if bg then - fg = _sub(fg, 1, self.width - x + 1) - end - width = #text - end + if x + width - 1 > self.width then + text = _sub(text, 1, self.width - x + 1) + if bg then + bg = _sub(bg, 1, self.width - x + 1) + end + if bg then + fg = _sub(fg, 1, self.width - x + 1) + end + width = #text + end - if width > 0 then + if width > 0 then - local function replace(sstr, pos, rstr, width) - if pos == 1 and width == self.width then - return rstr - elseif pos == 1 then - return rstr .. _sub(sstr, pos+width) - elseif pos + width > self.width then - return _sub(sstr, 1, pos-1) .. rstr - end - return _sub(sstr, 1, pos-1) .. rstr .. _sub(sstr, pos+width) - end + local function replace(sstr, pos, rstr, width) + if pos == 1 and width == self.width then + return rstr + elseif pos == 1 then + return rstr .. _sub(sstr, pos+width) + elseif pos + width > self.width then + return _sub(sstr, 1, pos-1) .. rstr + end + return _sub(sstr, 1, pos-1) .. rstr .. _sub(sstr, pos+width) + end - local line = self.lines[y] - line.dirty = true - line.text = replace(line.text, x, text, width) - if fg then - line.fg = replace(line.fg, x, fg, width) - end - if bg then - line.bg = replace(line.bg, x, bg, width) - end - end - end + local line = self.lines[y] + line.dirty = true + line.text = replace(line.text, x, text, width) + if fg then + line.fg = replace(line.fg, x, fg, width) + end + if bg then + line.bg = replace(line.bg, x, bg, width) + end + end + end end function Canvas:writeLine(y, text, fg, bg) - self.lines[y].dirty = true - self.lines[y].text = text - self.lines[y].fg = fg - self.lines[y].bg = bg + self.lines[y].dirty = true + self.lines[y].text = text + self.lines[y].fg = fg + self.lines[y].bg = bg end function Canvas:reset() - self.regions = nil + self.regions = nil end function Canvas:clear(bg, fg) - local text = _rep(' ', self.width) - fg = _rep(self.palette[fg or colors.white], self.width) - bg = _rep(self.palette[bg or colors.black], self.width) - for i = 1, self.height do - self:writeLine(i, text, fg, bg) - end + local text = _rep(' ', self.width) + fg = _rep(self.palette[fg or colors.white], self.width) + bg = _rep(self.palette[bg or colors.black], self.width) + for i = 1, self.height do + self:writeLine(i, text, fg, bg) + end end function Canvas:punch(rect) - if not self.regions then - self.regions = Region.new(self.x, self.y, self.ex, self.ey) - end - self.regions:subRect(rect.x, rect.y, rect.ex, rect.ey) + if not self.regions then + self.regions = Region.new(self.x, self.y, self.ex, self.ey) + end + self.regions:subRect(rect.x, rect.y, rect.ex, rect.ey) end function Canvas:blitClipped(device) - for _,region in ipairs(self.regions.region) do - self:blit(device, - { x = region[1] - self.x + 1, - y = region[2] - self.y + 1, - ex = region[3]- self.x + 1, - ey = region[4] - self.y + 1 }, - { x = region[1], y = region[2] }) - end + for _,region in ipairs(self.regions.region) do + self:blit(device, + { x = region[1] - self.x + 1, + y = region[2] - self.y + 1, + ex = region[3]- self.x + 1, + ey = region[4] - self.y + 1 }, + { x = region[1], y = region[2] }) + end end function Canvas:redraw(device) - self:reset() - if #self.layers > 0 then - for _,layer in pairs(self.layers) do - self:punch(layer) - end - self:blitClipped(device) - else - self:blit(device) - end - self:clean() + self:reset() + if #self.layers > 0 then + for _,layer in pairs(self.layers) do + self:punch(layer) + end + self:blitClipped(device) + else + self:blit(device) + end + self:clean() end function Canvas:isDirty() - for _, line in pairs(self.lines) do - if line.dirty then - return true - end - end + for _, line in pairs(self.lines) do + if line.dirty then + return true + end + end end function Canvas:dirty() - for _, line in pairs(self.lines) do - line.dirty = true - end + for _, line in pairs(self.lines) do + line.dirty = true + end end function Canvas:clean() - for _, line in pairs(self.lines) do - line.dirty = false - end + for _, line in pairs(self.lines) do + line.dirty = false + end end function Canvas:render(device, layers) --- redrawAll ? - layers = layers or self.layers - if #layers > 0 then - self.regions = Region.new(self.x, self.y, self.ex, self.ey) - local l = Util.shallowCopy(layers) - for _, canvas in ipairs(layers) do - table.remove(l, 1) - if canvas.visible then - self:punch(canvas) - canvas:render(device, l) - end - end - self:blitClipped(device) - self:reset() - else - self:blit(device) - end - self:clean() + layers = layers or self.layers + if #layers > 0 then + self.regions = Region.new(self.x, self.y, self.ex, self.ey) + local l = Util.shallowCopy(layers) + for _, canvas in ipairs(layers) do + table.remove(l, 1) + if canvas.visible then + self:punch(canvas) + canvas:render(device, l) + end + end + self:blitClipped(device) + self:reset() + else + self:blit(device) + end + self:clean() end function Canvas:blit(device, src, tgt) - src = src or { x = 1, y = 1, ex = self.ex - self.x + 1, ey = self.ey - self.y + 1 } - tgt = tgt or self + src = src or { x = 1, y = 1, ex = self.ex - self.x + 1, ey = self.ey - self.y + 1 } + tgt = tgt or self - for i = 0, src.ey - src.y do - local line = self.lines[src.y + i] - if line and line.dirty then - local t, fg, bg = line.text, line.fg, line.bg - if src.x > 1 or src.ex < self.ex then - t = _sub(t, src.x, src.ex) - fg = _sub(fg, src.x, src.ex) - bg = _sub(bg, src.x, src.ex) - end - --if tgt.y + i > self.ey then -- wrong place to do clipping ?? - -- break - --end - device.setCursorPos(tgt.x, tgt.y + i) - device.blit(t, fg, bg) - end - end + for i = 0, src.ey - src.y do + local line = self.lines[src.y + i] + if line and line.dirty then + local t, fg, bg = line.text, line.fg, line.bg + if src.x > 1 or src.ex < self.ex then + t = _sub(t, src.x, src.ex) + fg = _sub(fg, src.x, src.ex) + bg = _sub(bg, src.x, src.ex) + end + --if tgt.y + i > self.ey then -- wrong place to do clipping ?? + -- break + --end + device.setCursorPos(tgt.x, tgt.y + i) + device.blit(t, fg, bg) + end + end end function Canvas:applyPalette(palette) - local lookup = { } - for n = 1, 16 do - lookup[self.palette[2 ^ (n - 1)]] = palette[2 ^ (n - 1)] - end + local lookup = { } + for n = 1, 16 do + lookup[self.palette[2 ^ (n - 1)]] = palette[2 ^ (n - 1)] + end - for _, l in pairs(self.lines) do - l.fg = _gsub(l.fg, '%w', lookup) - l.bg = _gsub(l.bg, '%w', lookup) - l.dirty = true - end + for _, l in pairs(self.lines) do + l.fg = _gsub(l.fg, '%w', lookup) + l.bg = _gsub(l.bg, '%w', lookup) + l.dirty = true + end - self.palette = palette + self.palette = palette end function Canvas.convertWindow(win, parent, wx, wy) - local w, h = win.getSize() + local w, h = win.getSize() - win.canvas = Canvas({ - x = wx, - y = wy, - width = w, - height = h, - isColor = win.isColor(), - }) + win.canvas = Canvas({ + x = wx, + y = wy, + width = w, + height = h, + isColor = win.isColor(), + }) - function win.clear() - win.canvas:clear(win.getBackgroundColor(), win.getTextColor()) - end + function win.clear() + win.canvas:clear(win.getBackgroundColor(), win.getTextColor()) + end - function win.clearLine() - local _, y = win.getCursorPos() - win.canvas:write(1, - y, - _rep(' ', win.canvas.width), - win.getBackgroundColor(), - win.getTextColor()) - end + function win.clearLine() + local _, y = win.getCursorPos() + win.canvas:write(1, + y, + _rep(' ', win.canvas.width), + win.getBackgroundColor(), + win.getTextColor()) + end - function win.write(str) - local x, y = win.getCursorPos() - win.canvas:write(x, - y, - str, - win.getBackgroundColor(), - win.getTextColor()) - win.setCursorPos(x + #str, y) - end + function win.write(str) + local x, y = win.getCursorPos() + win.canvas:write(x, + y, + str, + win.getBackgroundColor(), + win.getTextColor()) + win.setCursorPos(x + #str, y) + end - function win.blit(text, fg, bg) - local x, y = win.getCursorPos() - win.canvas:writeBlit(x, y, text, bg, fg) - end + function win.blit(text, fg, bg) + local x, y = win.getCursorPos() + win.canvas:writeBlit(x, y, text, bg, fg) + end - function win.redraw() - win.canvas:redraw(parent) - end + function win.redraw() + win.canvas:redraw(parent) + end - function win.scroll(n) - table.insert(win.canvas.lines, table.remove(win.canvas.lines, 1)) - win.canvas.lines[#win.canvas.lines].text = _rep(' ', win.canvas.width) - win.canvas:dirty() - end + function win.scroll(n) + table.insert(win.canvas.lines, table.remove(win.canvas.lines, 1)) + win.canvas.lines[#win.canvas.lines].text = _rep(' ', win.canvas.width) + win.canvas:dirty() + end - function win.reposition(x, y, width, height) - win.canvas.x, win.canvas.y = x, y - win.canvas:resize(width or win.canvas.width, height or win.canvas.height) - end + function win.reposition(x, y, width, height) + win.canvas.x, win.canvas.y = x, y + win.canvas:resize(width or win.canvas.width, height or win.canvas.height) + end - win.clear() + win.clear() end +function Canvas.scrollingWindow(win, wx, wy) + local w, h = win.getSize() + local scrollPos = 0 + local maxScroll = h + + -- canvas lines are are a sliding window within the local lines table + local lines = { } + + local parent + local canvas = Canvas({ + x = wx, + y = wy, + width = w, + height = h, + isColor = win.isColor(), + }) + win.canvas = canvas + + local function scrollTo(p, forceRedraw) + local ms = #lines - canvas.height -- max scroll + p = math.min(math.max(p, 0), ms) -- normalize + + if p ~= scrollPos or forceRedraw then + scrollPos = p + for i = 1, canvas.height do + canvas.lines[i] = lines[i + scrollPos] + end + canvas:dirty() + end + end + + function win.blit(text, fg, bg) + local x, y = win.getCursorPos() + win.canvas:writeBlit(x, y, text, bg, fg) + win.redraw() + end + + function win.clear() + lines = { } + for i = 1, canvas.height do + lines[i] = canvas.lines[i] + end + scrollPos = 0 + canvas:clear(win.getBackgroundColor(), win.getTextColor()) + win.redraw() + end + + function win.clearLine() + local _, y = win.getCursorPos() + + scrollTo(#lines - canvas.height) + win.canvas:write(1, + y, + _rep(' ', win.canvas.width), + win.getBackgroundColor(), + win.getTextColor()) + win.redraw() + end + + function win.redraw() + if parent and canvas.visible then + local x, y = win.getCursorPos() + for i = 1, canvas.height do + local line = canvas.lines[i] + if line and line.dirty then + parent.setCursorPos(canvas.x, canvas.y + i - 1) + parent.blit(line.text, line.fg, line.bg) + line.dirty = false + end + end + win.setCursorPos(x, y) + end + end + + -- doesn't support negative scrolling... + function win.scroll(n) + for _ = 1, n do + lines[#lines + 1] = { + text = _rep(' ', canvas.width), + fg = _rep(canvas.palette[win.getTextColor()], canvas.width), + bg = _rep(canvas.palette[win.getBackgroundColor()], canvas.width), + } + end + + while #lines > maxScroll do + table.remove(lines, 1) + end + + scrollTo(maxScroll, true) + win.redraw() + end + + function win.scrollDown() + scrollTo(scrollPos + 1) + win.redraw() + end + + function win.scrollUp() + scrollTo(scrollPos - 1) + win.redraw() + end + + function win.setMaxScroll(ms) + maxScroll = ms + end + + function win.setParent(p) + parent = p + end + + function win.write(str) + str = tostring(str) or '' + + local x, y = win.getCursorPos() + scrollTo(#lines - canvas.height) + win.blit(str, + _rep(canvas.palette[win.getTextColor()], #str), + _rep(canvas.palette[win.getBackgroundColor()], #str)) + win.setCursorPos(x + #str, y) + end + + function win.reposition(x, y, width, height) + win.canvas.x, win.canvas.y = x, y + win.canvas:resize(width or win.canvas.width, height or win.canvas.height) + end + + win.clear() +end return Canvas diff --git a/sys/apis/ui/fileui.lua b/sys/apis/ui/fileui.lua index d6d90d3..02e2bc7 100644 --- a/sys/apis/ui/fileui.lua +++ b/sys/apis/ui/fileui.lua @@ -6,140 +6,140 @@ local fs = _G.fs return function(args) - local columns = { - { heading = 'Name', key = 'name' }, - } + local columns = { + { heading = 'Name', key = 'name' }, + } - if UI.term.width > 28 then - table.insert(columns, - { heading = 'Size', key = 'size', width = 5 } - ) - end + if UI.term.width > 28 then + table.insert(columns, + { heading = 'Size', key = 'size', width = 5 } + ) + end - args = args or { } + args = args or { } - local selectFile = UI.Dialog { - x = args.x or 3, - y = args.y or 2, - z = args.z or 2, + local selectFile = UI.Dialog { + x = args.x or 3, + y = args.y or 2, + z = args.z or 2, -- rex = args.rex or -3, -- rey = args.rey or -3, - height = args.height, - width = args.width, - title = 'Select File', - grid = UI.ScrollingGrid { - x = 2, - y = 2, - ex = -2, - ey = -4, - path = '', - sortColumn = 'name', - columns = columns, - }, - path = UI.TextEntry { - x = 2, - y = -2, - ex = -11, - limit = 256, - accelerators = { - enter = 'path_enter', - } - }, - cancel = UI.Button { - text = 'Cancel', - x = -9, - y = -2, - event = 'cancel', - }, - } + height = args.height, + width = args.width, + title = 'Select File', + grid = UI.ScrollingGrid { + x = 2, + y = 2, + ex = -2, + ey = -4, + path = '', + sortColumn = 'name', + columns = columns, + }, + path = UI.TextEntry { + x = 2, + y = -2, + ex = -11, + limit = 256, + accelerators = { + enter = 'path_enter', + } + }, + cancel = UI.Button { + text = 'Cancel', + x = -9, + y = -2, + event = 'cancel', + }, + } - function selectFile:enable(path, fn) - self:setPath(path) - self.fn = fn - UI.Dialog.enable(self) - end + function selectFile:enable(path, fn) + self:setPath(path) + self.fn = fn + UI.Dialog.enable(self) + end - function selectFile:setPath(path) - self.grid.dir = path - while not fs.isDir(self.grid.dir) do - self.grid.dir = fs.getDir(self.grid.dir) - end + function selectFile:setPath(path) + self.grid.dir = path + while not fs.isDir(self.grid.dir) do + self.grid.dir = fs.getDir(self.grid.dir) + end - self.path.value = self.grid.dir - end + self.path.value = self.grid.dir + end - function selectFile.grid:draw() - local files = fs.listEx(self.dir) - if #self.dir > 0 then - table.insert(files, { - name = '..', - isDir = true, - }) - end - self:setValues(files) - self:setIndex(1) - UI.Grid.draw(self) - end + function selectFile.grid:draw() + local files = fs.listEx(self.dir) + if #self.dir > 0 then + table.insert(files, { + name = '..', + isDir = true, + }) + end + self:setValues(files) + self:setIndex(1) + UI.Grid.draw(self) + end - function selectFile.grid:getDisplayValues(row) - if row.size then - row = Util.shallowCopy(row) - row.size = Util.toBytes(row.size) - end - return row - end + function selectFile.grid:getDisplayValues(row) + if row.size then + row = Util.shallowCopy(row) + row.size = Util.toBytes(row.size) + end + return row + end - function selectFile.grid:getRowTextColor(file) - if file.isDir then - return colors.cyan - end - if file.isReadOnly then - return colors.pink - end - return colors.white - end + function selectFile.grid:getRowTextColor(file) + if file.isDir then + return colors.cyan + end + if file.isReadOnly then + return colors.pink + end + return colors.white + end - function selectFile.grid:sortCompare(a, b) - if self.sortColumn == 'size' then - return a.size < b.size - end - if a.isDir == b.isDir then - return a.name:lower() < b.name:lower() - end - return a.isDir - end + function selectFile.grid:sortCompare(a, b) + if self.sortColumn == 'size' then + return a.size < b.size + end + if a.isDir == b.isDir then + return a.name:lower() < b.name:lower() + end + return a.isDir + end - function selectFile:eventHandler(event) + function selectFile:eventHandler(event) - if event.type == 'grid_select' then - self.grid.dir = fs.combine(self.grid.dir, event.selected.name) - self.path.value = self.grid.dir - if event.selected.isDir then - self.grid:draw() - self.path:draw() - else - UI:setPreviousPage() - self.fn(self.path.value) - end + if event.type == 'grid_select' then + self.grid.dir = fs.combine(self.grid.dir, event.selected.name) + self.path.value = self.grid.dir + if event.selected.isDir then + self.grid:draw() + self.path:draw() + else + UI:setPreviousPage() + self.fn(self.path.value) + end - elseif event.type == 'path_enter' then - if fs.isDir(self.path.value) then - self:setPath(self.path.value) - self.grid:draw() - self.path:draw() - else - UI:setPreviousPage() - self.fn(self.path.value) - end + elseif event.type == 'path_enter' then + if fs.isDir(self.path.value) then + self:setPath(self.path.value) + self.grid:draw() + self.path:draw() + else + UI:setPreviousPage() + self.fn(self.path.value) + end - elseif event.type == 'cancel' then - UI:setPreviousPage() - self.fn() - else - return UI.Dialog.eventHandler(self, event) - end - return true - end + elseif event.type == 'cancel' then + UI:setPreviousPage() + self.fn() + else + return UI.Dialog.eventHandler(self, event) + end + return true + end - return selectFile + return selectFile end diff --git a/sys/apis/ui/glasses.lua b/sys/apis/ui/glasses.lua index db70fcb..cd8a26f 100644 --- a/sys/apis/ui/glasses.lua +++ b/sys/apis/ui/glasses.lua @@ -7,190 +7,190 @@ local Peripheral = require('peripheral') local Glasses = class() function Glasses:init(args) - local defaults = { - backgroundColor = colors.black, - textColor = colors.white, - textScale = .5, - backgroundOpacity = .5, - multiplier = 2.6665, + local defaults = { + backgroundColor = colors.black, + textColor = colors.white, + textScale = .5, + backgroundOpacity = .5, + multiplier = 2.6665, -- multiplier = 2.333, - } - defaults.width, defaults.height = term.getSize() + } + defaults.width, defaults.height = term.getSize() - UI:setProperties(defaults, args) - UI:setProperties(self, defaults) + UI:setProperties(defaults, args) + UI:setProperties(self, defaults) - self.bridge = Peripheral.get({ - type = 'openperipheral_bridge', - method = 'addBox', - }) - self.bridge.clear() + self.bridge = Peripheral.get({ + type = 'openperipheral_bridge', + method = 'addBox', + }) + self.bridge.clear() - self.setBackgroundColor = function(...) end - self.setTextColor = function(...) end + self.setBackgroundColor = function(...) end + self.setTextColor = function(...) end - self.t = { } - for i = 1, self.height do - self.t[i] = { - text = string.rep(' ', self.width+1), - --text = self.bridge.addText(0, 40+i*4, string.rep(' ', self.width+1), 0xffffff), - bg = { }, - textFields = { }, - } - end + self.t = { } + for i = 1, self.height do + self.t[i] = { + text = string.rep(' ', self.width+1), + --text = self.bridge.addText(0, 40+i*4, string.rep(' ', self.width+1), 0xffffff), + bg = { }, + textFields = { }, + } + end end function Glasses:setBackgroundBox(boxes, ax, bx, y, bgColor) - local colors = { - [ colors.black ] = 0x000000, - [ colors.brown ] = 0x7F664C, - [ colors.blue ] = 0x253192, - [ colors.red ] = 0xFF0000, - [ colors.gray ] = 0x272727, - [ colors.lime ] = 0x426A0D, - [ colors.green ] = 0x2D5628, - [ colors.white ] = 0xFFFFFF - } + local colors = { + [ colors.black ] = 0x000000, + [ colors.brown ] = 0x7F664C, + [ colors.blue ] = 0x253192, + [ colors.red ] = 0xFF0000, + [ colors.gray ] = 0x272727, + [ colors.lime ] = 0x426A0D, + [ colors.green ] = 0x2D5628, + [ colors.white ] = 0xFFFFFF + } - local function overlap(box, ax, bx) - if bx < box.ax or ax > box.bx then - return false - end - return true - end + local function overlap(box, ax, bx) + if bx < box.ax or ax > box.bx then + return false + end + return true + end - for _,box in pairs(boxes) do - if overlap(box, ax, bx) then - if box.bgColor == bgColor then - ax = math.min(ax, box.ax) - bx = math.max(bx, box.bx) - box.ax = box.bx + 1 - elseif ax == box.ax then - box.ax = bx + 1 - elseif ax > box.ax then - if bx < box.bx then - table.insert(boxes, { -- split - ax = bx + 1, - bx = box.bx, - bgColor = box.bgColor - }) - box.bx = ax - 1 - break - else - box.ax = box.bx + 1 - end - elseif ax < box.ax then - if bx > box.bx then - box.ax = box.bx + 1 -- delete - else - box.ax = bx + 1 - end - end - end - end - if bgColor ~= colors.black then - table.insert(boxes, { - ax = ax, - bx = bx, - bgColor = bgColor - }) - end + for _,box in pairs(boxes) do + if overlap(box, ax, bx) then + if box.bgColor == bgColor then + ax = math.min(ax, box.ax) + bx = math.max(bx, box.bx) + box.ax = box.bx + 1 + elseif ax == box.ax then + box.ax = bx + 1 + elseif ax > box.ax then + if bx < box.bx then + table.insert(boxes, { -- split + ax = bx + 1, + bx = box.bx, + bgColor = box.bgColor + }) + box.bx = ax - 1 + break + else + box.ax = box.bx + 1 + end + elseif ax < box.ax then + if bx > box.bx then + box.ax = box.bx + 1 -- delete + else + box.ax = bx + 1 + end + end + end + end + if bgColor ~= colors.black then + table.insert(boxes, { + ax = ax, + bx = bx, + bgColor = bgColor + }) + end - local deleted - repeat - deleted = false - for k,box in pairs(boxes) do - if box.ax > box.bx then - if box.box then - box.box.delete() - end - table.remove(boxes, k) - deleted = true - break - end - if not box.box then - box.box = self.bridge.addBox( - math.floor(self.x + (box.ax - 1) * self.multiplier), - self.y + y * 4, - math.ceil((box.bx - box.ax + 1) * self.multiplier), - 4, - colors[bgColor], - self.backgroundOpacity) - else - box.box.setX(self.x + math.floor((box.ax - 1) * self.multiplier)) - box.box.setWidth(math.ceil((box.bx - box.ax + 1) * self.multiplier)) - end - end - until not deleted + local deleted + repeat + deleted = false + for k,box in pairs(boxes) do + if box.ax > box.bx then + if box.box then + box.box.delete() + end + table.remove(boxes, k) + deleted = true + break + end + if not box.box then + box.box = self.bridge.addBox( + math.floor(self.x + (box.ax - 1) * self.multiplier), + self.y + y * 4, + math.ceil((box.bx - box.ax + 1) * self.multiplier), + 4, + colors[bgColor], + self.backgroundOpacity) + else + box.box.setX(self.x + math.floor((box.ax - 1) * self.multiplier)) + box.box.setWidth(math.ceil((box.bx - box.ax + 1) * self.multiplier)) + end + end + until not deleted end function Glasses:write(x, y, text, bg) - if x < 1 then - error(' less ', 6) - end - if y <= #self.t then - local line = self.t[y] - local str = line.text - str = str:sub(1, x-1) .. text .. str:sub(x + #text) - self.t[y].text = str + if x < 1 then + error(' less ', 6) + end + if y <= #self.t then + local line = self.t[y] + local str = line.text + str = str:sub(1, x-1) .. text .. str:sub(x + #text) + self.t[y].text = str - for _,tf in pairs(line.textFields) do - tf.delete() - end - line.textFields = { } + for _,tf in pairs(line.textFields) do + tf.delete() + end + line.textFields = { } - local function split(st) - local words = { } - local offset = 0 - while true do - local b,e,w = st:find('(%S+)') - if not b then - break - end - table.insert(words, { - offset = b + offset - 1, - text = w, - }) - offset = offset + e - st = st:sub(e + 1) - end - return words - end + local function split(st) + local words = { } + local offset = 0 + while true do + local b,e,w = st:find('(%S+)') + if not b then + break + end + table.insert(words, { + offset = b + offset - 1, + text = w, + }) + offset = offset + e + st = st:sub(e + 1) + end + return words + end - local words = split(str) - for _,word in pairs(words) do - local tf = self.bridge.addText(self.x + word.offset * self.multiplier, - self.y+y*4, '', 0xffffff) - tf.setScale(self.textScale) - tf.setZ(1) - tf.setText(word.text) - table.insert(line.textFields, tf) - end + local words = split(str) + for _,word in pairs(words) do + local tf = self.bridge.addText(self.x + word.offset * self.multiplier, + self.y+y*4, '', 0xffffff) + tf.setScale(self.textScale) + tf.setZ(1) + tf.setText(word.text) + table.insert(line.textFields, tf) + end - self:setBackgroundBox(line.bg, x, x + #text - 1, y, bg) - end + self:setBackgroundBox(line.bg, x, x + #text - 1, y, bg) + end end function Glasses:clear(bg) - for _,line in pairs(self.t) do - for _,tf in pairs(line.textFields) do - tf.delete() - end - line.textFields = { } - line.text = string.rep(' ', self.width+1) + for _,line in pairs(self.t) do + for _,tf in pairs(line.textFields) do + tf.delete() + end + line.textFields = { } + line.text = string.rep(' ', self.width+1) -- self.t[i].text.setText('') - end + end end function Glasses:reset() - self:clear() - self.bridge.clear() - self.bridge.sync() + self:clear() + self.bridge.clear() + self.bridge.sync() end function Glasses:sync() - self.bridge.sync() + self.bridge.sync() end return Glasses diff --git a/sys/apis/ui/region.lua b/sys/apis/ui/region.lua index f5abcea..7d199ef 100644 --- a/sys/apis/ui/region.lua +++ b/sys/apis/ui/region.lua @@ -1,57 +1,18 @@ -------------------------------------------------------------------------------- -- -- tek.lib.region -- Written by Timm S. Mueller --- +-- -- Copyright 2008 - 2016 by the authors and contributors: --- +-- -- * Timm S. Muller -- * Franciska Schulze -- * Tobias Schwinger --- --- Permission is hereby granted, free of charge, to any person obtaining --- a copy of this software and associated documentation files (the --- "Software"), to deal in the Software without restriction, including --- without limitation the rights to use, copy, modify, merge, publish, --- distribute, sublicense, and/or sell copies of the Software, and to --- permit persons to whom the Software is furnished to do so, subject to --- the following conditions: --- --- The above copyright notice and this permission notice shall be --- included in all copies or substantial portions of the Software. --- --- === Disclaimer === --- --- THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, --- EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF --- MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. --- IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY --- CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, --- TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE --- SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. -- --- OVERVIEW:: --- This library implements the management of regions, which are --- collections of non-overlapping rectangles. +-- https://opensource.org/licenses/MIT -- --- FUNCTIONS:: --- - Region:andRect() - ''And''s a rectangle to a region --- - Region:andRegion() - ''And''s a region to a region --- - Region:checkIntersect() - Checks if a rectangle intersects a region --- - Region:forEach() - Calls a function for each rectangle in a region --- - Region:get() - Get region's min/max extents --- - Region.intersect() - Returns the intersection of two rectangles --- - Region:isEmpty() - Checks if a Region is empty --- - Region.new() - Creates a new Region --- - Region:orRect() - ''Or''s a rectangle to a region --- - Region:orRegion() - ''Or''s a region to a region --- - Region:setRect() - Resets a region to the given rectangle --- - Region:shift() - Displaces a region --- - Region:subRect() - Subtracts a rectangle from a region --- - Region:subRegion() - Subtracts a region from a region --- - Region:xorRect() - ''Exclusive Or''s a rectangle to a region --- -------------------------------------------------------------------------------- +-- Some comments have been removed to reduce file size, see: +-- https://github.com/technosaurus/tekui/blob/master/etc/region.lua +-- for the full source local insert = table.insert local ipairs = ipairs @@ -65,24 +26,18 @@ Region._VERSION = "Region 11.3" Region.__index = Region -------------------------------------------------------------------------------- -- x0, y0, x1, y1 = Region.intersect(d1, d2, d3, d4, s1, s2, s3, s4): -- Returns the coordinates of a rectangle where a rectangle specified by -- the coordinates s1, s2, s3, s4 overlaps with the rectangle specified -- by the coordinates d1, d2, d3, d4. The return value is '''nil''' if -- the rectangles do not overlap. -------------------------------------------------------------------------------- - function Region.intersect(d1, d2, d3, d4, s1, s2, s3, s4) if s3 >= d1 and s1 <= d3 and s4 >= d2 and s2 <= d4 then return max(s1, d1), max(s2, d2), min(s3, d3), min(s4, d4) end end -------------------------------------------------------------------------------- -- insertrect: insert rect to table, merging with an existing one if possible -------------------------------------------------------------------------------- - local function insertrect(d, s1, s2, s3, s4) for i = 1, min(4, #d) do local a = d[i] @@ -108,10 +63,7 @@ local function insertrect(d, s1, s2, s3, s4) insert(d, 1, { s1, s2, s3, s4 }) end -------------------------------------------------------------------------------- -- cutrect: cut rect d into table of new rects, using rect s as a punch -------------------------------------------------------------------------------- - local function cutrect(d1, d2, d3, d4, s1, s2, s3, s4) if not Region.intersect(d1, d2, d3, d4, s1, s2, s3, s4) then return { { d1, d2, d3, d4 } } @@ -135,10 +87,7 @@ local function cutrect(d1, d2, d3, d4, s1, s2, s3, s4) return r end -------------------------------------------------------------------------------- -- cutregion: cut region d, using s as a punch -------------------------------------------------------------------------------- - local function cutregion(d, s1, s2, s3, s4) local r = { } for _, dr in ipairs(d) do @@ -150,11 +99,8 @@ local function cutregion(d, s1, s2, s3, s4) return r end -------------------------------------------------------------------------------- -- region = Region.new(r1, r2, r3, r4): Creates a new region from the given -- coordinates. -------------------------------------------------------------------------------- - function Region.new(r1, r2, r3, r4) if r1 then return setmetatable({ region = { { r1, r2, r3, r4 } } }, Region) @@ -162,39 +108,27 @@ function Region.new(r1, r2, r3, r4) return setmetatable({ region = { } }, Region) end -------------------------------------------------------------------------------- -- self = region:setRect(r1, r2, r3, r4): Resets an existing region -- to the specified rectangle. -------------------------------------------------------------------------------- - function Region:setRect(r1, r2, r3, r4) self.region = { { r1, r2, r3, r4 } } return self end -------------------------------------------------------------------------------- -- region:orRect(r1, r2, r3, r4): Logical ''or''s a rectangle to a region -------------------------------------------------------------------------------- - function Region:orRect(s1, s2, s3, s4) self.region = cutregion(self.region, s1, s2, s3, s4) insertrect(self.region, s1, s2, s3, s4) end -------------------------------------------------------------------------------- -- region:orRegion(region): Logical ''or''s another region to a region -------------------------------------------------------------------------------- - function Region:orRegion(s) for _, r in ipairs(s) do self:orRect(r[1], r[2], r[3], r[4]) end end -------------------------------------------------------------------------------- -- region:andRect(r1, r2, r3, r4): Logical ''and''s a rectange to a region -------------------------------------------------------------------------------- - function Region:andRect(s1, s2, s3, s4) local r = { } for _, d in ipairs(self.region) do @@ -207,10 +141,7 @@ function Region:andRect(s1, s2, s3, s4) self.region = r end -------------------------------------------------------------------------------- -- region:xorRect(r1, r2, r3, r4): Logical ''xor''s a rectange to a region -------------------------------------------------------------------------------- - function Region:xorRect(s1, s2, s3, s4) local r1 = { } local r2 = { { s1, s2, s3, s4 } } @@ -225,10 +156,7 @@ function Region:xorRect(s1, s2, s3, s4) self:orRegion(r2) end -------------------------------------------------------------------------------- -- self = region:subRect(r1, r2, r3, r4): Subtracts a rectangle from a region -------------------------------------------------------------------------------- - function Region:subRect(s1, s2, s3, s4) local r1 = { } for _, d in ipairs(self.region) do @@ -241,10 +169,7 @@ function Region:subRect(s1, s2, s3, s4) return self end -------------------------------------------------------------------------------- -- region:getRect - gets an iterator on the rectangles in a region [internal] -------------------------------------------------------------------------------- - function Region:getRects() local index = 0 return function(object) @@ -255,12 +180,9 @@ function Region:getRects() end, self.region end -------------------------------------------------------------------------------- -- success = region:checkIntersect(x0, y0, x1, y1): Returns a boolean -- indicating whether a rectangle specified by its coordinates overlaps -- with a region. -------------------------------------------------------------------------------- - function Region:checkIntersect(s1, s2, s3, s4) for _, d in ipairs(self.region) do if Region.intersect(d[1], d[2], d[3], d[4], s1, s2, s3, s4) then @@ -270,10 +192,7 @@ function Region:checkIntersect(s1, s2, s3, s4) return false end -------------------------------------------------------------------------------- -- region:subRegion(region2): Subtracts {{region2}} from {{region}}. -------------------------------------------------------------------------------- - function Region:subRegion(region) if region then for r1, r2, r3, r4 in region:getRects() do @@ -282,10 +201,7 @@ function Region:subRegion(region) end end -------------------------------------------------------------------------------- -- region:andRegion(r): Logically ''and''s a region to a region -------------------------------------------------------------------------------- - function Region:andRegion(s) local r = { } for _, s in ipairs(s.region) do @@ -301,23 +217,17 @@ function Region:andRegion(s) self.region = r end -------------------------------------------------------------------------------- -- region:forEach(func, obj, ...): For each rectangle in a region, calls the -- specified function according the following scheme: -- func(obj, x0, y0, x1, y1, ...) -- Extra arguments are passed through to the function. -------------------------------------------------------------------------------- - function Region:forEach(func, obj, ...) for x0, y0, x1, y1 in self:getRects() do func(obj, x0, y0, x1, y1, ...) end end -------------------------------------------------------------------------------- -- region:shift(dx, dy): Shifts a region by delta x and y. -------------------------------------------------------------------------------- - function Region:shift(dx, dy) for _, r in ipairs(self.region) do r[1] = r[1] + dx @@ -327,18 +237,12 @@ function Region:shift(dx, dy) end end -------------------------------------------------------------------------------- -- region:isEmpty(): Returns '''true''' if a region is empty. -------------------------------------------------------------------------------- - function Region:isEmpty() return #self.region == 0 end -------------------------------------------------------------------------------- -- minx, miny, maxx, maxy = region:get(): Get region's min/max extents -------------------------------------------------------------------------------- - function Region:get() if #self.region > 0 then local minx = 1000000 -- ui.HUGE diff --git a/sys/apis/ui/transition.lua b/sys/apis/ui/transition.lua index 6cdd5f0..16e2127 100644 --- a/sys/apis/ui/transition.lua +++ b/sys/apis/ui/transition.lua @@ -3,88 +3,88 @@ local Tween = require('ui.tween') local Transition = { } function Transition.slideLeft(args) - local ticks = args.ticks or 6 - local easing = args.easing or 'outQuint' - local pos = { x = args.ex } - local tween = Tween.new(ticks, pos, { x = args.x }, easing) - local lastScreen = args.canvas:copy() + local ticks = args.ticks or 6 + local easing = args.easing or 'outQuint' + local pos = { x = args.ex } + local tween = Tween.new(ticks, pos, { x = args.x }, easing) + local lastScreen = args.canvas:copy() - return function(device) - local finished = tween:update(1) - local x = math.floor(pos.x) - lastScreen:dirty() - lastScreen:blit(device, { - x = args.ex - x + args.x, - y = args.y, - ex = args.ex, - ey = args.ey }, - { x = args.x, y = args.y }) - args.canvas:blit(device, { - x = args.x, - y = args.y, - ex = args.ex - x + args.x, - ey = args.ey }, - { x = x, y = args.y }) - return not finished - end + return function(device) + local finished = tween:update(1) + local x = math.floor(pos.x) + lastScreen:dirty() + lastScreen:blit(device, { + x = args.ex - x + args.x, + y = args.y, + ex = args.ex, + ey = args.ey }, + { x = args.x, y = args.y }) + args.canvas:blit(device, { + x = args.x, + y = args.y, + ex = args.ex - x + args.x, + ey = args.ey }, + { x = x, y = args.y }) + return not finished + end end function Transition.slideRight(args) - local ticks = args.ticks or 6 - local easing = args.easing or'outQuint' - local pos = { x = args.x } - local tween = Tween.new(ticks, pos, { x = args.ex }, easing) - local lastScreen = args.canvas:copy() + local ticks = args.ticks or 6 + local easing = args.easing or'outQuint' + local pos = { x = args.x } + local tween = Tween.new(ticks, pos, { x = args.ex }, easing) + local lastScreen = args.canvas:copy() - return function(device) - local finished = tween:update(1) - local x = math.floor(pos.x) - lastScreen:dirty() - lastScreen:blit(device, { - x = args.x, - y = args.y, - ex = args.ex - x + args.x, - ey = args.ey }, - { x = x, y = args.y }) - args.canvas:blit(device, { - x = args.ex - x + args.x, - y = args.y, - ex = args.ex, - ey = args.ey }, - { x = args.x, y = args.y }) - return not finished - end + return function(device) + local finished = tween:update(1) + local x = math.floor(pos.x) + lastScreen:dirty() + lastScreen:blit(device, { + x = args.x, + y = args.y, + ex = args.ex - x + args.x, + ey = args.ey }, + { x = x, y = args.y }) + args.canvas:blit(device, { + x = args.ex - x + args.x, + y = args.y, + ex = args.ex, + ey = args.ey }, + { x = args.x, y = args.y }) + return not finished + end end function Transition.expandUp(args) - local ticks = args.ticks or 3 - local easing = args.easing or 'linear' - local pos = { y = args.ey + 1 } - local tween = Tween.new(ticks, pos, { y = args.y }, easing) + local ticks = args.ticks or 3 + local easing = args.easing or 'linear' + local pos = { y = args.ey + 1 } + local tween = Tween.new(ticks, pos, { y = args.y }, easing) - return function(device) - local finished = tween:update(1) - args.canvas:blit(device, nil, { x = args.x, y = math.floor(pos.y) }) - return not finished - end + return function(device) + local finished = tween:update(1) + args.canvas:blit(device, nil, { x = args.x, y = math.floor(pos.y) }) + return not finished + end end function Transition.grow(args) - local ticks = args.ticks or 3 - local easing = args.easing or 'linear' - local tween = Tween.new(ticks, - { x = args.width / 2 - 1, y = args.height / 2 - 1, w = 1, h = 1 }, - { x = 1, y = 1, w = args.width, h = args.height }, easing) + local ticks = args.ticks or 3 + local easing = args.easing or 'linear' + local tween = Tween.new(ticks, + { x = args.width / 2 - 1, y = args.height / 2 - 1, w = 1, h = 1 }, + { x = 1, y = 1, w = args.width, h = args.height }, easing) - return function(device) - local finished = tween:update(1) - local subj = tween.subject - local rect = { x = math.floor(subj.x), y = math.floor(subj.y) } - rect.ex = math.floor(rect.x + subj.w - 1) - rect.ey = math.floor(rect.y + subj.h - 1) - args.canvas:blit(device, rect, { x = args.x + rect.x - 1, y = args.y + rect.y - 1}) - return not finished - end + return function(device) + local finished = tween:update(1) + local subj = tween.subject + local rect = { x = math.floor(subj.x), y = math.floor(subj.y) } + rect.ex = math.floor(rect.x + subj.w - 1) + rect.ey = math.floor(rect.y + subj.h - 1) + args.canvas:blit(device, rect, { x = args.x + rect.x - 1, y = args.y + rect.y - 1}) + return not finished + end end return Transition diff --git a/sys/apis/ui/tween.lua b/sys/apis/ui/tween.lua index 48a1143..7d0a13c 100644 --- a/sys/apis/ui/tween.lua +++ b/sys/apis/ui/tween.lua @@ -1,31 +1,14 @@ local tween = { - _VERSION = 'tween 2.1.1', - _DESCRIPTION = 'tweening for lua', - _URL = 'https://github.com/kikito/tween.lua', - _LICENSE = [[ - MIT LICENSE + _VERSION = 'tween 2.1.1', + _DESCRIPTION = 'tweening for lua', + _URL = 'https://github.com/kikito/tween.lua', + _LICENSE = [[ + MIT LICENSE - Copyright (c) 2014 Enrique García Cota, Yuichi Tateno, Emmanuel Oga + Copyright (c) 2014 Enrique García Cota, Yuichi Tateno, Emmanuel Oga - Permission is hereby granted, free of charge, to any person obtaining a - copy of this software and associated documentation files (the - "Software"), to deal in the Software without restriction, including - without limitation the rights to use, copy, modify, merge, publish, - distribute, sublicense, and/or sell copies of the Software, and to - permit persons to whom the Software is furnished to do so, subject to - the following conditions: - - The above copyright notice and this permission notice shall be included - in all copies or substantial portions of the Software. - - THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS - OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF - MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. - IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY - CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, - TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE - SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. - ]] + Licence details: https://opensource.org/licenses/MIT + ]] } -- easing @@ -45,57 +28,57 @@ local function linear(t, b, c, d) return c * t / d + b end -- quad local function inQuad(t, b, c, d) return c * pow(t / d, 2) + b end local function outQuad(t, b, c, d) - t = t / d - return -c * t * (t - 2) + b + t = t / d + return -c * t * (t - 2) + b end local function inOutQuad(t, b, c, d) - t = t / d * 2 - if t < 1 then return c / 2 * pow(t, 2) + b end - return -c / 2 * ((t - 1) * (t - 3) - 1) + b + t = t / d * 2 + if t < 1 then return c / 2 * pow(t, 2) + b end + return -c / 2 * ((t - 1) * (t - 3) - 1) + b end local function outInQuad(t, b, c, d) - if t < d / 2 then return outQuad(t * 2, b, c / 2, d) end - return inQuad((t * 2) - d, b + c / 2, c / 2, d) + if t < d / 2 then return outQuad(t * 2, b, c / 2, d) end + return inQuad((t * 2) - d, b + c / 2, c / 2, d) end -- cubic local function inCubic (t, b, c, d) return c * pow(t / d, 3) + b end local function outCubic(t, b, c, d) return c * (pow(t / d - 1, 3) + 1) + b end local function inOutCubic(t, b, c, d) - t = t / d * 2 - if t < 1 then return c / 2 * t * t * t + b end - t = t - 2 - return c / 2 * (t * t * t + 2) + b + t = t / d * 2 + if t < 1 then return c / 2 * t * t * t + b end + t = t - 2 + return c / 2 * (t * t * t + 2) + b end local function outInCubic(t, b, c, d) - if t < d / 2 then return outCubic(t * 2, b, c / 2, d) end - return inCubic((t * 2) - d, b + c / 2, c / 2, d) + if t < d / 2 then return outCubic(t * 2, b, c / 2, d) end + return inCubic((t * 2) - d, b + c / 2, c / 2, d) end -- quart local function inQuart(t, b, c, d) return c * pow(t / d, 4) + b end local function outQuart(t, b, c, d) return -c * (pow(t / d - 1, 4) - 1) + b end local function inOutQuart(t, b, c, d) - t = t / d * 2 - if t < 1 then return c / 2 * pow(t, 4) + b end - return -c / 2 * (pow(t - 2, 4) - 2) + b + t = t / d * 2 + if t < 1 then return c / 2 * pow(t, 4) + b end + return -c / 2 * (pow(t - 2, 4) - 2) + b end local function outInQuart(t, b, c, d) - if t < d / 2 then return outQuart(t * 2, b, c / 2, d) end - return inQuart((t * 2) - d, b + c / 2, c / 2, d) + if t < d / 2 then return outQuart(t * 2, b, c / 2, d) end + return inQuart((t * 2) - d, b + c / 2, c / 2, d) end -- quint local function inQuint(t, b, c, d) return c * pow(t / d, 5) + b end local function outQuint(t, b, c, d) return c * (pow(t / d - 1, 5) + 1) + b end local function inOutQuint(t, b, c, d) - t = t / d * 2 - if t < 1 then return c / 2 * pow(t, 5) + b end - return c / 2 * (pow(t - 2, 5) + 2) + b + t = t / d * 2 + if t < 1 then return c / 2 * pow(t, 5) + b end + return c / 2 * (pow(t - 2, 5) + 2) + b end local function outInQuint(t, b, c, d) - if t < d / 2 then return outQuint(t * 2, b, c / 2, d) end - return inQuint((t * 2) - d, b + c / 2, c / 2, d) + if t < d / 2 then return outQuint(t * 2, b, c / 2, d) end + return inQuint((t * 2) - d, b + c / 2, c / 2, d) end -- sine @@ -103,142 +86,142 @@ local function inSine(t, b, c, d) return -c * cos(t / d * (pi / 2)) + c + b end local function outSine(t, b, c, d) return c * sin(t / d * (pi / 2)) + b end local function inOutSine(t, b, c, d) return -c / 2 * (cos(pi * t / d) - 1) + b end local function outInSine(t, b, c, d) - if t < d / 2 then return outSine(t * 2, b, c / 2, d) end - return inSine((t * 2) -d, b + c / 2, c / 2, d) + if t < d / 2 then return outSine(t * 2, b, c / 2, d) end + return inSine((t * 2) -d, b + c / 2, c / 2, d) end -- expo local function inExpo(t, b, c, d) - if t == 0 then return b end - return c * pow(2, 10 * (t / d - 1)) + b - c * 0.001 + if t == 0 then return b end + return c * pow(2, 10 * (t / d - 1)) + b - c * 0.001 end local function outExpo(t, b, c, d) - if t == d then return b + c end - return c * 1.001 * (-pow(2, -10 * t / d) + 1) + b + if t == d then return b + c end + return c * 1.001 * (-pow(2, -10 * t / d) + 1) + b end local function inOutExpo(t, b, c, d) - if t == 0 then return b end - if t == d then return b + c end - t = t / d * 2 - if t < 1 then return c / 2 * pow(2, 10 * (t - 1)) + b - c * 0.0005 end - return c / 2 * 1.0005 * (-pow(2, -10 * (t - 1)) + 2) + b + if t == 0 then return b end + if t == d then return b + c end + t = t / d * 2 + if t < 1 then return c / 2 * pow(2, 10 * (t - 1)) + b - c * 0.0005 end + return c / 2 * 1.0005 * (-pow(2, -10 * (t - 1)) + 2) + b end local function outInExpo(t, b, c, d) - if t < d / 2 then return outExpo(t * 2, b, c / 2, d) end - return inExpo((t * 2) - d, b + c / 2, c / 2, d) + if t < d / 2 then return outExpo(t * 2, b, c / 2, d) end + return inExpo((t * 2) - d, b + c / 2, c / 2, d) end -- circ local function inCirc(t, b, c, d) return(-c * (sqrt(1 - pow(t / d, 2)) - 1) + b) end local function outCirc(t, b, c, d) return(c * sqrt(1 - pow(t / d - 1, 2)) + b) end local function inOutCirc(t, b, c, d) - t = t / d * 2 - if t < 1 then return -c / 2 * (sqrt(1 - t * t) - 1) + b end - t = t - 2 - return c / 2 * (sqrt(1 - t * t) + 1) + b + t = t / d * 2 + if t < 1 then return -c / 2 * (sqrt(1 - t * t) - 1) + b end + t = t - 2 + return c / 2 * (sqrt(1 - t * t) + 1) + b end local function outInCirc(t, b, c, d) - if t < d / 2 then return outCirc(t * 2, b, c / 2, d) end - return inCirc((t * 2) - d, b + c / 2, c / 2, d) + if t < d / 2 then return outCirc(t * 2, b, c / 2, d) end + return inCirc((t * 2) - d, b + c / 2, c / 2, d) end -- elastic local function calculatePAS(p,a,c,d) - p, a = p or d * 0.3, a or 0 - if a < abs(c) then return p, c, p / 4 end -- p, a, s - return p, a, p / (2 * pi) * asin(c/a) -- p,a,s + p, a = p or d * 0.3, a or 0 + if a < abs(c) then return p, c, p / 4 end -- p, a, s + return p, a, p / (2 * pi) * asin(c/a) -- p,a,s end local function inElastic(t, b, c, d, a, p) - local s - if t == 0 then return b end - t = t / d - if t == 1 then return b + c end - p,a,s = calculatePAS(p,a,c,d) - t = t - 1 - return -(a * pow(2, 10 * t) * sin((t * d - s) * (2 * pi) / p)) + b + local s + if t == 0 then return b end + t = t / d + if t == 1 then return b + c end + p,a,s = calculatePAS(p,a,c,d) + t = t - 1 + return -(a * pow(2, 10 * t) * sin((t * d - s) * (2 * pi) / p)) + b end local function outElastic(t, b, c, d, a, p) - local s - if t == 0 then return b end - t = t / d - if t == 1 then return b + c end - p,a,s = calculatePAS(p,a,c,d) - return a * pow(2, -10 * t) * sin((t * d - s) * (2 * pi) / p) + c + b + local s + if t == 0 then return b end + t = t / d + if t == 1 then return b + c end + p,a,s = calculatePAS(p,a,c,d) + return a * pow(2, -10 * t) * sin((t * d - s) * (2 * pi) / p) + c + b end local function inOutElastic(t, b, c, d, a, p) - local s - if t == 0 then return b end - t = t / d * 2 - if t == 2 then return b + c end - p,a,s = calculatePAS(p,a,c,d) - t = t - 1 - if t < 0 then return -0.5 * (a * pow(2, 10 * t) * sin((t * d - s) * (2 * pi) / p)) + b end - return a * pow(2, -10 * t) * sin((t * d - s) * (2 * pi) / p ) * 0.5 + c + b + local s + if t == 0 then return b end + t = t / d * 2 + if t == 2 then return b + c end + p,a,s = calculatePAS(p,a,c,d) + t = t - 1 + if t < 0 then return -0.5 * (a * pow(2, 10 * t) * sin((t * d - s) * (2 * pi) / p)) + b end + return a * pow(2, -10 * t) * sin((t * d - s) * (2 * pi) / p ) * 0.5 + c + b end local function outInElastic(t, b, c, d, a, p) - if t < d / 2 then return outElastic(t * 2, b, c / 2, d, a, p) end - return inElastic((t * 2) - d, b + c / 2, c / 2, d, a, p) + if t < d / 2 then return outElastic(t * 2, b, c / 2, d, a, p) end + return inElastic((t * 2) - d, b + c / 2, c / 2, d, a, p) end -- back local function inBack(t, b, c, d, s) - s = s or 1.70158 - t = t / d - return c * t * t * ((s + 1) * t - s) + b + s = s or 1.70158 + t = t / d + return c * t * t * ((s + 1) * t - s) + b end local function outBack(t, b, c, d, s) - s = s or 1.70158 - t = t / d - 1 - return c * (t * t * ((s + 1) * t + s) + 1) + b + s = s or 1.70158 + t = t / d - 1 + return c * (t * t * ((s + 1) * t + s) + 1) + b end local function inOutBack(t, b, c, d, s) - s = (s or 1.70158) * 1.525 - t = t / d * 2 - if t < 1 then return c / 2 * (t * t * ((s + 1) * t - s)) + b end - t = t - 2 - return c / 2 * (t * t * ((s + 1) * t + s) + 2) + b + s = (s or 1.70158) * 1.525 + t = t / d * 2 + if t < 1 then return c / 2 * (t * t * ((s + 1) * t - s)) + b end + t = t - 2 + return c / 2 * (t * t * ((s + 1) * t + s) + 2) + b end local function outInBack(t, b, c, d, s) - if t < d / 2 then return outBack(t * 2, b, c / 2, d, s) end - return inBack((t * 2) - d, b + c / 2, c / 2, d, s) + if t < d / 2 then return outBack(t * 2, b, c / 2, d, s) end + return inBack((t * 2) - d, b + c / 2, c / 2, d, s) end -- bounce local function outBounce(t, b, c, d) - t = t / d - if t < 1 / 2.75 then return c * (7.5625 * t * t) + b end - if t < 2 / 2.75 then - t = t - (1.5 / 2.75) - return c * (7.5625 * t * t + 0.75) + b - elseif t < 2.5 / 2.75 then - t = t - (2.25 / 2.75) - return c * (7.5625 * t * t + 0.9375) + b - end - t = t - (2.625 / 2.75) - return c * (7.5625 * t * t + 0.984375) + b + t = t / d + if t < 1 / 2.75 then return c * (7.5625 * t * t) + b end + if t < 2 / 2.75 then + t = t - (1.5 / 2.75) + return c * (7.5625 * t * t + 0.75) + b + elseif t < 2.5 / 2.75 then + t = t - (2.25 / 2.75) + return c * (7.5625 * t * t + 0.9375) + b + end + t = t - (2.625 / 2.75) + return c * (7.5625 * t * t + 0.984375) + b end local function inBounce(t, b, c, d) return c - outBounce(d - t, 0, c, d) + b end local function inOutBounce(t, b, c, d) - if t < d / 2 then return inBounce(t * 2, 0, c, d) * 0.5 + b end - return outBounce(t * 2 - d, 0, c, d) * 0.5 + c * .5 + b + if t < d / 2 then return inBounce(t * 2, 0, c, d) * 0.5 + b end + return outBounce(t * 2 - d, 0, c, d) * 0.5 + c * .5 + b end local function outInBounce(t, b, c, d) - if t < d / 2 then return outBounce(t * 2, b, c / 2, d) end - return inBounce((t * 2) - d, b + c / 2, c / 2, d) + if t < d / 2 then return outBounce(t * 2, b, c / 2, d) end + return inBounce((t * 2) - d, b + c / 2, c / 2, d) end tween.easing = { - linear = linear, - inQuad = inQuad, outQuad = outQuad, inOutQuad = inOutQuad, outInQuad = outInQuad, - inCubic = inCubic, outCubic = outCubic, inOutCubic = inOutCubic, outInCubic = outInCubic, - inQuart = inQuart, outQuart = outQuart, inOutQuart = inOutQuart, outInQuart = outInQuart, - inQuint = inQuint, outQuint = outQuint, inOutQuint = inOutQuint, outInQuint = outInQuint, - inSine = inSine, outSine = outSine, inOutSine = inOutSine, outInSine = outInSine, - inExpo = inExpo, outExpo = outExpo, inOutExpo = inOutExpo, outInExpo = outInExpo, - inCirc = inCirc, outCirc = outCirc, inOutCirc = inOutCirc, outInCirc = outInCirc, - inElastic = inElastic, outElastic = outElastic, inOutElastic = inOutElastic, outInElastic = outInElastic, - inBack = inBack, outBack = outBack, inOutBack = inOutBack, outInBack = outInBack, - inBounce = inBounce, outBounce = outBounce, inOutBounce = inOutBounce, outInBounce = outInBounce + linear = linear, + inQuad = inQuad, outQuad = outQuad, inOutQuad = inOutQuad, outInQuad = outInQuad, + inCubic = inCubic, outCubic = outCubic, inOutCubic = inOutCubic, outInCubic = outInCubic, + inQuart = inQuart, outQuart = outQuart, inOutQuart = inOutQuart, outInQuart = outInQuart, + inQuint = inQuint, outQuint = outQuint, inOutQuint = inOutQuint, outInQuint = outInQuint, + inSine = inSine, outSine = outSine, inOutSine = inOutSine, outInSine = outInSine, + inExpo = inExpo, outExpo = outExpo, inOutExpo = inOutExpo, outInExpo = outInExpo, + inCirc = inCirc, outCirc = outCirc, inOutCirc = inOutCirc, outInCirc = outInCirc, + inElastic = inElastic, outElastic = outElastic, inOutElastic = inOutElastic, outInElastic = outInElastic, + inBack = inBack, outBack = outBack, inOutBack = inOutBack, outInBack = outInBack, + inBounce = inBounce, outBounce = outBounce, inOutBounce = inOutBounce, outInBounce = outInBounce } @@ -246,68 +229,68 @@ tween.easing = { -- private stuff local function copyTables(destination, keysTable, valuesTable) - valuesTable = valuesTable or keysTable - local mt = getmetatable(keysTable) - if mt and getmetatable(destination) == nil then - setmetatable(destination, mt) - end - for k,v in pairs(keysTable) do - if type(v) == 'table' then - destination[k] = copyTables({}, v, valuesTable[k]) - else - destination[k] = valuesTable[k] - end - end - return destination + valuesTable = valuesTable or keysTable + local mt = getmetatable(keysTable) + if mt and getmetatable(destination) == nil then + setmetatable(destination, mt) + end + for k,v in pairs(keysTable) do + if type(v) == 'table' then + destination[k] = copyTables({}, v, valuesTable[k]) + else + destination[k] = valuesTable[k] + end + end + return destination end local function checkSubjectAndTargetRecursively(subject, target, path) - path = path or {} - local targetType, newPath - for k,targetValue in pairs(target) do - targetType, newPath = type(targetValue), copyTables({}, path) - table.insert(newPath, tostring(k)) - if targetType == 'number' then - assert(type(subject[k]) == 'number', "Parameter '" .. table.concat(newPath,'/') .. "' is missing from subject or isn't a number") - elseif targetType == 'table' then - checkSubjectAndTargetRecursively(subject[k], targetValue, newPath) - else - assert(targetType == 'number', "Parameter '" .. table.concat(newPath,'/') .. "' must be a number or table of numbers") - end - end + path = path or {} + local targetType, newPath + for k,targetValue in pairs(target) do + targetType, newPath = type(targetValue), copyTables({}, path) + table.insert(newPath, tostring(k)) + if targetType == 'number' then + assert(type(subject[k]) == 'number', "Parameter '" .. table.concat(newPath,'/') .. "' is missing from subject or isn't a number") + elseif targetType == 'table' then + checkSubjectAndTargetRecursively(subject[k], targetValue, newPath) + else + assert(targetType == 'number', "Parameter '" .. table.concat(newPath,'/') .. "' must be a number or table of numbers") + end + end end local function checkNewParams(duration, subject, target, easing) - assert(type(duration) == 'number' and duration > 0, "duration must be a positive number. Was " .. tostring(duration)) - local tsubject = type(subject) - assert(tsubject == 'table' or tsubject == 'userdata', "subject must be a table or userdata. Was " .. tostring(subject)) - assert(type(target)== 'table', "target must be a table. Was " .. tostring(target)) - assert(type(easing)=='function', "easing must be a function. Was " .. tostring(easing)) - checkSubjectAndTargetRecursively(subject, target) + assert(type(duration) == 'number' and duration > 0, "duration must be a positive number. Was " .. tostring(duration)) + local tsubject = type(subject) + assert(tsubject == 'table' or tsubject == 'userdata', "subject must be a table or userdata. Was " .. tostring(subject)) + assert(type(target)== 'table', "target must be a table. Was " .. tostring(target)) + assert(type(easing)=='function', "easing must be a function. Was " .. tostring(easing)) + checkSubjectAndTargetRecursively(subject, target) end local function getEasingFunction(easing) - easing = easing or "linear" - if type(easing) == 'string' then - local name = easing - easing = tween.easing[name] - if type(easing) ~= 'function' then - error("The easing function name '" .. name .. "' is invalid") - end - end - return easing + easing = easing or "linear" + if type(easing) == 'string' then + local name = easing + easing = tween.easing[name] + if type(easing) ~= 'function' then + error("The easing function name '" .. name .. "' is invalid") + end + end + return easing end local function performEasingOnSubject(subject, target, initial, clock, duration, easing) - local t,b,c,d - for k,v in pairs(target) do - if type(v) == 'table' then - performEasingOnSubject(subject[k], v, initial[k], clock, duration, easing) - else - t,b,c,d = clock, initial[k], v - initial[k], duration - subject[k] = easing(t,b,c,d) - end - end + local t,b,c,d + for k,v in pairs(target) do + if type(v) == 'table' then + performEasingOnSubject(subject[k], v, initial[k], clock, duration, easing) + else + t,b,c,d = clock, initial[k], v - initial[k], duration + subject[k] = easing(t,b,c,d) + end + end end -- Tween methods @@ -316,52 +299,52 @@ local Tween = {} local Tween_mt = {__index = Tween} function Tween:set(clock) - assert(type(clock) == 'number', "clock must be a positive number or 0") + assert(type(clock) == 'number', "clock must be a positive number or 0") - self.initial = self.initial or copyTables({}, self.target, self.subject) - self.clock = clock + self.initial = self.initial or copyTables({}, self.target, self.subject) + self.clock = clock - if self.clock <= 0 then + if self.clock <= 0 then - self.clock = 0 - copyTables(self.subject, self.initial) + self.clock = 0 + copyTables(self.subject, self.initial) - elseif self.clock >= self.duration then -- the tween has expired + elseif self.clock >= self.duration then -- the tween has expired - self.clock = self.duration - copyTables(self.subject, self.target) + self.clock = self.duration + copyTables(self.subject, self.target) - else + else - performEasingOnSubject(self.subject, self.target, self.initial, self.clock, self.duration, self.easing) + performEasingOnSubject(self.subject, self.target, self.initial, self.clock, self.duration, self.easing) - end + end - return self.clock >= self.duration + return self.clock >= self.duration end function Tween:reset() - return self:set(0) + return self:set(0) end function Tween:update(dt) - assert(type(dt) == 'number', "dt must be a number") - return self:set(self.clock + dt) + assert(type(dt) == 'number', "dt must be a number") + return self:set(self.clock + dt) end -- Public interface function tween.new(duration, subject, target, easing) - easing = getEasingFunction(easing) - checkNewParams(duration, subject, target, easing) - return setmetatable({ - duration = duration, - subject = subject, - target = target, - easing = easing, - clock = 0 - }, Tween_mt) + easing = getEasingFunction(easing) + checkNewParams(duration, subject, target, easing) + return setmetatable({ + duration = duration, + subject = subject, + target = target, + easing = easing, + clock = 0 + }, Tween_mt) end return tween \ No newline at end of file diff --git a/sys/apis/util.lua b/sys/apis/util.lua index 267b8db..29fd5e1 100644 --- a/sys/apis/util.lua +++ b/sys/apis/util.lua @@ -7,518 +7,518 @@ local term = _G.term local textutils = _G.textutils function Util.tryTimed(timeout, f, ...) - local c = os.clock() - repeat - local ret = f(...) - if ret then - return ret - end - until os.clock()-c >= timeout + local c = os.clock() + repeat + local ret = f(...) + if ret then + return ret + end + until os.clock()-c >= timeout end function Util.tryTimes(attempts, f, ...) - local result - for _ = 1, attempts do - result = { f(...) } - if result[1] then - return unpack(result) - end - end - return unpack(result) + local result + for _ = 1, attempts do + result = { f(...) } + if result[1] then + return unpack(result) + end + end + return unpack(result) end function Util.throttle(fn) - local ts = os.clock() - local timeout = .095 - return function(...) - local nts = os.clock() - if nts > ts + timeout then - os.sleep(0) - ts = os.clock() - if fn then - fn(...) - end - end - end + local ts = os.clock() + local timeout = .095 + return function(...) + local nts = os.clock() + if nts > ts + timeout then + os.sleep(0) + ts = os.clock() + if fn then + fn(...) + end + end + end end function Util.tostring(pattern, ...) - local function serialize(tbl, width) - local str = '{\n' - for k, v in pairs(tbl) do - local value - if type(v) == 'table' then - value = string.format('table: %d', Util.size(v)) - else - value = tostring(v) - end - str = str .. string.format(' %s: %s\n', k, value) - end - --if #str < width then - --str = str:gsub('\n', '') .. ' }' - --else - str = str .. '}' - --end - return str - end + local function serialize(tbl, width) + local str = '{\n' + for k, v in pairs(tbl) do + local value + if type(v) == 'table' then + value = string.format('table: %d', Util.size(v)) + else + value = tostring(v) + end + str = str .. string.format(' %s: %s\n', k, value) + end + --if #str < width then + --str = str:gsub('\n', '') .. ' }' + --else + str = str .. '}' + --end + return str + end - if type(pattern) == 'string' then - return string.format(pattern, ...) - elseif type(pattern) == 'table' then - return serialize(pattern, term.current().getSize()) - end - return tostring(pattern) + if type(pattern) == 'string' then + return string.format(pattern, ...) + elseif type(pattern) == 'table' then + return serialize(pattern, term.current().getSize()) + end + return tostring(pattern) end function Util.print(pattern, ...) - print(Util.tostring(pattern, ...)) + print(Util.tostring(pattern, ...)) end function Util.getVersion() - local version + local version - if _G._CC_VERSION then - version = tonumber(_G._CC_VERSION:match('[%d]+%.?[%d][%d]')) - end - if not version and _G._HOST then - version = tonumber(_G._HOST:match('[%d]+%.?[%d][%d]')) - end + if _G._CC_VERSION then + version = tonumber(_G._CC_VERSION:match('[%d]+%.?[%d][%d]')) + end + if not version and _G._HOST then + version = tonumber(_G._HOST:match('[%d]+%.?[%d][%d]')) + end - return version or 1.7 + return version or 1.7 end function Util.getMinecraftVersion() - local mcVersion = _G._MC_VERSION or 'unknown' - if _G._HOST then - local version = _G._HOST:match('%S+ %S+ %((%S.+)%)') - if version then - mcVersion = version:match('Minecraft (%S+)') or version - end - end - return mcVersion + local mcVersion = _G._MC_VERSION or 'unknown' + if _G._HOST then + local version = _G._HOST:match('%S+ %S+ %((%S.+)%)') + if version then + mcVersion = version:match('Minecraft (%S+)') or version + end + end + return mcVersion end function Util.checkMinecraftVersion(minVersion) - local version = Util.getMinecraftVersion() - local function convert(v) - local m1, m2, m3 = v:match('(%d)%.(%d)%.?(%d?)') - return tonumber(m1) * 10000 + tonumber(m2) * 100 + (tonumber(m3) or 0) - end + local version = Util.getMinecraftVersion() + local function convert(v) + local m1, m2, m3 = v:match('(%d)%.(%d)%.?(%d?)') + return tonumber(m1) * 10000 + tonumber(m2) * 100 + (tonumber(m3) or 0) + end - return convert(version) >= convert(tostring(minVersion)) + return convert(version) >= convert(tostring(minVersion)) end -- http://lua-users.org/wiki/SimpleRound function Util.round(num, idp) - local mult = 10^(idp or 0) - return math.floor(num * mult + 0.5) / mult + local mult = 10^(idp or 0) + return math.floor(num * mult + 0.5) / mult end function Util.random(max, min) - min = min or 0 - return math.random(0, max-min) + min + min = min or 0 + return math.random(0, max-min) + min end --[[ Table functions ]] -- function Util.clear(t) - local keys = Util.keys(t) - for _,k in pairs(keys) do - t[k] = nil - end + local keys = Util.keys(t) + for _,k in pairs(keys) do + t[k] = nil + end end function Util.empty(t) - return not next(t) + return not next(t) end function Util.key(t, value) - for k,v in pairs(t) do - if v == value then - return k - end - end + for k,v in pairs(t) do + if v == value then + return k + end + end end function Util.keys(t) - local keys = { } - for k in pairs(t) do - keys[#keys+1] = k - end - return keys + local keys = { } + for k in pairs(t) do + keys[#keys+1] = k + end + return keys end function Util.merge(obj, args) - if args then - for k,v in pairs(args) do - obj[k] = v - end - end - return obj + if args then + for k,v in pairs(args) do + obj[k] = v + end + end + return obj end function Util.deepMerge(obj, args) - if args then - for k,v in pairs(args) do - if type(v) == 'table' then - if not obj[k] then - obj[k] = { } - end - Util.deepMerge(obj[k], v) - else - obj[k] = v - end - end - end + if args then + for k,v in pairs(args) do + if type(v) == 'table' then + if not obj[k] then + obj[k] = { } + end + Util.deepMerge(obj[k], v) + else + obj[k] = v + end + end + end end function Util.transpose(t) - local tt = { } - for k,v in pairs(t) do - tt[v] = k - end - return tt + local tt = { } + for k,v in pairs(t) do + tt[v] = k + end + return tt end function Util.contains(t, value) - for k,v in pairs(t) do - if v == value then - return k - end - end + for k,v in pairs(t) do + if v == value then + return k + end + end end function Util.find(t, name, value) - for k,v in pairs(t) do - if v[name] == value then - return v, k - end - end + for k,v in pairs(t) do + if v[name] == value then + return v, k + end + end end function Util.findAll(t, name, value) - local rt = { } - for _,v in pairs(t) do - if v[name] == value then - table.insert(rt, v) - end - end - return rt + local rt = { } + for _,v in pairs(t) do + if v[name] == value then + table.insert(rt, v) + end + end + return rt end function Util.shallowCopy(t) - local t2 = { } - for k,v in pairs(t) do - t2[k] = v - end - return t2 + local t2 = { } + for k,v in pairs(t) do + t2[k] = v + end + return t2 end function Util.deepCopy(t) - if type(t) ~= 'table' then - return t - end - --local mt = getmetatable(t) - local res = {} - for k,v in pairs(t) do - if type(v) == 'table' then - v = Util.deepCopy(v) - end - res[k] = v - end - --setmetatable(res,mt) - return res + if type(t) ~= 'table' then + return t + end + --local mt = getmetatable(t) + local res = {} + for k,v in pairs(t) do + if type(v) == 'table' then + v = Util.deepCopy(v) + end + res[k] = v + end + --setmetatable(res,mt) + return res end -- http://snippets.luacode.org/?p=snippets/Filter_a_table_in-place_119 function Util.filterInplace(t, predicate) - local j = 1 + local j = 1 - for i = 1,#t do - local v = t[i] - if predicate(v) then - t[j] = v - j = j + 1 - end - end + for i = 1,#t do + local v = t[i] + if predicate(v) then + t[j] = v + j = j + 1 + end + end - while t[j] ~= nil do - t[j] = nil - j = j + 1 - end + while t[j] ~= nil do + t[j] = nil + j = j + 1 + end - return t + return t end function Util.filter(it, f) - local ot = { } - for k,v in pairs(it) do - if f(v) then - ot[k] = v - end - end - return ot + local ot = { } + for k,v in pairs(it) do + if f(v) then + ot[k] = v + end + end + return ot end function Util.size(list) - if type(list) == 'table' then - local length = 0 - for _ in pairs(list) do - length = length + 1 - end - return length - end - return 0 + if type(list) == 'table' then + local length = 0 + for _ in pairs(list) do + length = length + 1 + end + return length + end + return 0 end function Util.removeByValue(t, e) - for k,v in pairs(t) do - if v == e then - table.remove(t, k) - break - end - end + for k,v in pairs(t) do + if v == e then + table.remove(t, k) + break + end + end end function Util.each(list, func) - for index, value in pairs(list) do - func(value, index, list) - end + for index, value in pairs(list) do + func(value, index, list) + end end function Util.rpairs(t) - local tkeys = Util.keys(t) - local i = #tkeys - return function() - local key = tkeys[i] - local k,v = key, t[key] - i = i - 1 - if v then - return k, v - end - end + local tkeys = Util.keys(t) + local i = #tkeys + return function() + local key = tkeys[i] + local k,v = key, t[key] + i = i - 1 + if v then + return k, v + end + end end -- http://stackoverflow.com/questions/15706270/sort-a-table-in-lua function Util.spairs(t, order) - local keys = Util.keys(t) + local keys = Util.keys(t) - -- if order function given, sort by it by passing the table and keys a, b, - -- otherwise just sort the keys - if order then - table.sort(keys, function(a,b) return order(t[a], t[b]) end) - else - table.sort(keys) - end + -- if order function given, sort by it by passing the table and keys a, b, + -- otherwise just sort the keys + if order then + table.sort(keys, function(a,b) return order(t[a], t[b]) end) + else + table.sort(keys) + end - -- return the iterator function - local i = 0 - return function() - i = i + 1 - if keys[i] then - return keys[i], t[keys[i]] - end - end + -- return the iterator function + local i = 0 + return function() + i = i + 1 + if keys[i] then + return keys[i], t[keys[i]] + end + end end function Util.first(t, order) - local keys = Util.keys(t) - if order then - table.sort(keys, function(a,b) return order(t[a], t[b]) end) - else - table.sort(keys) - end - return keys[1], t[keys[1]] + local keys = Util.keys(t) + if order then + table.sort(keys, function(a,b) return order(t[a], t[b]) end) + else + table.sort(keys) + end + return keys[1], t[keys[1]] end --[[ File functions ]]-- function Util.readFile(fname) - local f = fs.open(fname, "r") - if f then - local t = f.readAll() - f.close() - return t - end + local f = fs.open(fname, "r") + if f then + local t = f.readAll() + f.close() + return t + end end function Util.writeFile(fname, data) - local file = io.open(fname, "w") - if not file then - error('Unable to open ' .. fname, 2) - end - file:write(data) - file:close() + local file = io.open(fname, "w") + if not file then + error('Unable to open ' .. fname, 2) + end + file:write(data) + file:close() end function Util.readLines(fname) - local file = fs.open(fname, "r") - if file then - local t = {} - local line = file.readLine() - while line do - table.insert(t, line) - line = file.readLine() - end - file.close() - return t - end + local file = fs.open(fname, "r") + if file then + local t = {} + local line = file.readLine() + while line do + table.insert(t, line) + line = file.readLine() + end + file.close() + return t + end end function Util.writeLines(fname, lines) - local file = fs.open(fname, 'w') - if file then - for _,line in ipairs(lines) do - file.writeLine(line) - end - file.close() - return true - end + local file = fs.open(fname, 'w') + if file then + for _,line in ipairs(lines) do + file.writeLine(line) + end + file.close() + return true + end end function Util.readTable(fname) - local t = Util.readFile(fname) - if t then - return textutils.unserialize(t) - end + local t = Util.readFile(fname) + if t then + return textutils.unserialize(t) + end end function Util.writeTable(fname, data) - Util.writeFile(fname, textutils.serialize(data)) + Util.writeFile(fname, textutils.serialize(data)) end function Util.loadTable(fname) - local fc = Util.readFile(fname) - if not fc then - return false, 'Unable to read file' - end - local s, m = loadstring('return ' .. fc, fname) - if s then - s, m = pcall(s) - if s then - return m - end - end - return s, m + local fc = Util.readFile(fname) + if not fc then + return false, 'Unable to read file' + end + local s, m = loadstring('return ' .. fc, fname) + if s then + s, m = pcall(s) + if s then + return m + end + end + return s, m end --[[ loading and running functions ]] -- function Util.httpGet(url, headers) - local h, msg = http.get(url, headers) - if h then - local contents = h.readAll() - h.close() - return contents - end - return h, msg + local h, msg = http.get(url, headers) + if h then + local contents = h.readAll() + h.close() + return contents + end + return h, msg end function Util.download(url, filename) - local contents, msg = Util.httpGet(url) - if not contents then - error(string.format('Failed to download %s\n%s', url, msg)) - end + local contents, msg = Util.httpGet(url) + if not contents then + error(string.format('Failed to download %s\n%s', url, msg)) + end - if filename then - Util.writeFile(filename, contents) - end - return contents + if filename then + Util.writeFile(filename, contents) + end + return contents end function Util.loadUrl(url, env) -- loadfile equivalent - local c, msg = Util.httpGet(url) - if not c then - return c, msg - end - return load(c, url, nil, env) + local c, msg = Util.httpGet(url) + if not c then + return c, msg + end + return load(c, url, nil, env) end function Util.runUrl(env, url, ...) -- os.run equivalent - setmetatable(env, { __index = _G }) - local fn, m = Util.loadUrl(url, env) - if fn then - return pcall(fn, ...) - end - return fn, m + setmetatable(env, { __index = _G }) + local fn, m = Util.loadUrl(url, env) + if fn then + return pcall(fn, ...) + end + return fn, m end function Util.run(env, path, ...) - if type(env) ~= 'table' then error('Util.run: env must be a table', 2) end - setmetatable(env, { __index = _G }) - local fn, m = loadfile(path, env) - if fn then - return pcall(fn, ...) - end - return fn, m + if type(env) ~= 'table' then error('Util.run: env must be a table', 2) end + setmetatable(env, { __index = _G }) + local fn, m = loadfile(path, env) + if fn then + return pcall(fn, ...) + end + return fn, m end function Util.runFunction(env, fn, ...) - setfenv(fn, env) - setmetatable(env, { __index = _G }) - return pcall(fn, ...) + setfenv(fn, env) + setmetatable(env, { __index = _G }) + return pcall(fn, ...) end --[[ String functions ]] -- function Util.toBytes(n) - if not tonumber(n) then error('Util.toBytes: n must be a number', 2) end - if n >= 1000000 or n <= -1000000 then - return string.format('%sM', math.floor(n/1000000 * 10) / 10) - elseif n >= 10000 or n <= -10000 then - return string.format('%sK', math.floor(n/1000)) - elseif n >= 1000 or n <= -1000 then - return string.format('%sK', math.floor(n/1000 * 10) / 10) - end - return tostring(n) + if not tonumber(n) then error('Util.toBytes: n must be a number', 2) end + if n >= 1000000 or n <= -1000000 then + return string.format('%sM', math.floor(n/1000000 * 10) / 10) + elseif n >= 10000 or n <= -10000 then + return string.format('%sK', math.floor(n/1000)) + elseif n >= 1000 or n <= -1000 then + return string.format('%sK', math.floor(n/1000 * 10) / 10) + end + return tostring(n) end function Util.insertString(str, istr, pos) - return str:sub(1, pos - 1) .. istr .. str:sub(pos) + return str:sub(1, pos - 1) .. istr .. str:sub(pos) end function Util.split(str, pattern) - pattern = pattern or "(.-)\n" - local t = {} - local function helper(line) table.insert(t, line) return "" end - helper((str:gsub(pattern, helper))) - return t + pattern = pattern or "(.-)\n" + local t = {} + local function helper(line) table.insert(t, line) return "" end + helper((str:gsub(pattern, helper))) + return t end function Util.matches(str, pattern) - pattern = pattern or '%S+' - local t = { } - for s in str:gmatch(pattern) do - table.insert(t, s) - end - return t + pattern = pattern or '%S+' + local t = { } + for s in str:gmatch(pattern) do + table.insert(t, s) + end + return t end function Util.startsWidth(s, match) - return string.sub(s, 1, #match) == match + return string.sub(s, 1, #match) == match end function Util.widthify(s, len) - s = s or '' - local slen = #s - if slen < len then - s = s .. string.rep(' ', len - #s) - elseif slen > len then - s = s:sub(1, len) - end - return s + s = s or '' + local slen = #s + if slen < len then + s = s .. string.rep(' ', len - #s) + elseif slen > len then + s = s:sub(1, len) + end + return s end -- http://snippets.luacode.org/?p=snippets/trim_whitespace_from_string_76 function Util.trim(s) - return s:find'^%s*$' and '' or s:match'^%s*(.*%S)' + return s:find'^%s*$' and '' or s:match'^%s*(.*%S)' end -- trim whitespace from left end of string function Util.triml(s) - return s:match'^%s*(.*)' + return s:match'^%s*(.*)' end -- trim whitespace from right end of string function Util.trimr(s) - return s:find'^%s*$' and '' or s:match'^(.*%S)' + return s:find'^%s*$' and '' or s:match'^(.*%S)' end -- end http://snippets.luacode.org/?p=snippets/trim_whitespace_from_string_76 @@ -526,133 +526,133 @@ end -- https://www.rosettacode.org/wiki/Word_wrap#Lua and -- http://lua-users.org/wiki/StringRecipes local function paragraphwrap(text, linewidth, res) - linewidth = linewidth or 75 - local spaceleft = linewidth - local line = { } + linewidth = linewidth or 75 + local spaceleft = linewidth + local line = { } - for word in text:gmatch("%S+") do - local len = #word + 1 + for word in text:gmatch("%S+") do + local len = #word + 1 - --if colorMode then - -- word:gsub('()@([@%d])', function(pos, c) len = len - 2 end) - --end + --if colorMode then + -- word:gsub('()@([@%d])', function(pos, c) len = len - 2 end) + --end - if len > spaceleft then - table.insert(res, table.concat(line, ' ')) - line = { word } - spaceleft = linewidth - len - 1 - else - table.insert(line, word) - spaceleft = spaceleft - len - end - end + if len > spaceleft then + table.insert(res, table.concat(line, ' ')) + line = { word } + spaceleft = linewidth - len - 1 + else + table.insert(line, word) + spaceleft = spaceleft - len + end + end - table.insert(res, table.concat(line, ' ')) - return table.concat(res, '\n') + table.insert(res, table.concat(line, ' ')) + return table.concat(res, '\n') end -- end word wrapping function Util.wordWrap(str, limit) - local longLines = Util.split(str) - local lines = { } + local longLines = Util.split(str) + local lines = { } - for _,line in ipairs(longLines) do - paragraphwrap(line, limit, lines) - end + for _,line in ipairs(longLines) do + paragraphwrap(line, limit, lines) + end - return lines + return lines end function Util.args(arg) - local options, args = { }, { } + local options, args = { }, { } - local k = 1 - while k <= #arg do - local v = arg[k] - if string.sub(v, 1, 1) == '-' then - local opt = string.sub(v, 2) - options[opt] = arg[k + 1] - k = k + 1 - else - table.insert(args, v) - end - k = k + 1 - end - return options, args + local k = 1 + while k <= #arg do + local v = arg[k] + if string.sub(v, 1, 1) == '-' then + local opt = string.sub(v, 2) + options[opt] = arg[k + 1] + k = k + 1 + else + table.insert(args, v) + end + k = k + 1 + end + return options, args end -- http://lua-users.org/wiki/AlternativeGetOpt local function getopt( arg, options ) - local tab = {} - for k, v in ipairs(arg) do - if type(v) == 'string' then - if string.sub( v, 1, 2) == "--" then - local x = string.find( v, "=", 1, true ) - if x then tab[ string.sub( v, 3, x-1 ) ] = string.sub( v, x+1 ) - else tab[ string.sub( v, 3 ) ] = true - end - elseif string.sub( v, 1, 1 ) == "-" then - local y = 2 - local l = string.len(v) - local jopt - while ( y <= l ) do - jopt = string.sub( v, y, y ) - if string.find( options, jopt, 1, true ) then - if y < l then - tab[ jopt ] = string.sub( v, y+1 ) - y = l - else - tab[ jopt ] = arg[ k + 1 ] - end - else - tab[ jopt ] = true - end - y = y + 1 - end - end - end - end - return tab + local tab = {} + for k, v in ipairs(arg) do + if type(v) == 'string' then + if string.sub( v, 1, 2) == "--" then + local x = string.find( v, "=", 1, true ) + if x then tab[ string.sub( v, 3, x-1 ) ] = string.sub( v, x+1 ) + else tab[ string.sub( v, 3 ) ] = true + end + elseif string.sub( v, 1, 1 ) == "-" then + local y = 2 + local l = string.len(v) + local jopt + while ( y <= l ) do + jopt = string.sub( v, y, y ) + if string.find( options, jopt, 1, true ) then + if y < l then + tab[ jopt ] = string.sub( v, y+1 ) + y = l + else + tab[ jopt ] = arg[ k + 1 ] + end + else + tab[ jopt ] = true + end + y = y + 1 + end + end + end + end + return tab end function Util.showOptions(options) - print('Arguments: ') - for _, v in pairs(options) do - print(string.format('-%s %s', v.arg, v.desc)) - end + print('Arguments: ') + for _, v in pairs(options) do + print(string.format('-%s %s', v.arg, v.desc)) + end end function Util.getOptions(options, args, ignoreInvalid) - local argLetters = '' - for _,o in pairs(options) do - if o.type ~= 'flag' then - argLetters = argLetters .. o.arg - end - end - local rawOptions = getopt(args, argLetters) + local argLetters = '' + for _,o in pairs(options) do + if o.type ~= 'flag' then + argLetters = argLetters .. o.arg + end + end + local rawOptions = getopt(args, argLetters) - for k,ro in pairs(rawOptions) do - local found = false - for _,o in pairs(options) do - if o.arg == k then - found = true - if o.type == 'number' then - o.value = tonumber(ro) - elseif o.type == 'help' then - Util.showOptions(options) - return false - else - o.value = ro - end - end - end - if not found and not ignoreInvalid then - print('Invalid argument') - Util.showOptions(options) - return false - end - end - return true, Util.size(rawOptions) + for k,ro in pairs(rawOptions) do + local found = false + for _,o in pairs(options) do + if o.arg == k then + found = true + if o.type == 'number' then + o.value = tonumber(ro) + elseif o.type == 'help' then + Util.showOptions(options) + return false + else + o.value = ro + end + end + end + if not found and not ignoreInvalid then + print('Invalid argument') + Util.showOptions(options) + return false + end + end + return true, Util.size(rawOptions) end diff --git a/sys/apps/Help.lua b/sys/apps/Help.lua index bb2a913..c4b0c9f 100644 --- a/sys/apps/Help.lua +++ b/sys/apps/Help.lua @@ -1,4 +1,4 @@ -_G.requireInjector() +_G.requireInjector(_ENV) local UI = require('ui') local Util = require('util') @@ -10,89 +10,89 @@ UI:configure('Help', ...) local topics = { } for _,topic in pairs(help.topics()) do - if help.lookup(topic) then - table.insert(topics, { name = topic }) - end + if help.lookup(topic) then + table.insert(topics, { name = topic }) + end end local page = UI.Page { - labelText = UI.Text { - x = 3, y = 2, - value = 'Search', - }, - filter = UI.TextEntry { - x = 10, y = 2, ex = -3, - limit = 32, - }, - grid = UI.ScrollingGrid { - y = 4, - values = topics, - columns = { - { heading = 'Topic', key = 'name' }, - }, - sortColumn = 'name', - }, - accelerators = { - q = 'quit', - enter = 'grid_select', - }, + labelText = UI.Text { + x = 3, y = 2, + value = 'Search', + }, + filter = UI.TextEntry { + x = 10, y = 2, ex = -3, + limit = 32, + }, + grid = UI.ScrollingGrid { + y = 4, + values = topics, + columns = { + { heading = 'Topic', key = 'name' }, + }, + sortColumn = 'name', + }, + accelerators = { + q = 'quit', + enter = 'grid_select', + }, } local topicPage = UI.Page { - backgroundColor = colors.black, - titleBar = UI.TitleBar { - title = 'text', - previousPage = true, - }, - helpText = UI.TextArea { - backgroundColor = colors.black, - x = 2, ex = -1, y = 3, ey = -2, - }, - accelerators = { - q = 'back', - backspace = 'back', - }, + backgroundColor = colors.black, + titleBar = UI.TitleBar { + title = 'text', + previousPage = true, + }, + helpText = UI.TextArea { + backgroundColor = colors.black, + x = 2, ex = -1, y = 3, ey = -2, + }, + accelerators = { + q = 'back', + backspace = 'back', + }, } function topicPage:eventHandler(event) - if event.type == 'back' then - UI:setPreviousPage() - end - return UI.Page.eventHandler(self, event) + if event.type == 'back' then + UI:setPreviousPage() + end + return UI.Page.eventHandler(self, event) end function page:eventHandler(event) - if event.type == 'quit' then - UI:exitPullEvents() + if event.type == 'quit' then + UI:exitPullEvents() - elseif event.type == 'grid_select' then - if self.grid:getSelected() then - local name = self.grid:getSelected().name - local f = help.lookup(name) + elseif event.type == 'grid_select' then + if self.grid:getSelected() then + local name = self.grid:getSelected().name + local f = help.lookup(name) - topicPage.titleBar.title = name - topicPage.helpText:setText(Util.readFile(f)) + topicPage.titleBar.title = name + topicPage.helpText:setText(Util.readFile(f)) - UI:setPage(topicPage) - end + UI:setPage(topicPage) + end - elseif event.type == 'text_change' then - if #event.text == 0 then - self.grid.values = topics - else - self.grid.values = { } - for _,f in pairs(topics) do - if string.find(f.name, event.text) then - table.insert(self.grid.values, f) - end - end - end - self.grid:update() - self.grid:setIndex(1) - self.grid:draw() - else - return UI.Page.eventHandler(self, event) - end + elseif event.type == 'text_change' then + if #event.text == 0 then + self.grid.values = topics + else + self.grid.values = { } + for _,f in pairs(topics) do + if string.find(f.name, event.text) then + table.insert(self.grid.values, f) + end + end + end + self.grid:update() + self.grid:setIndex(1) + self.grid:draw() + else + return UI.Page.eventHandler(self, event) + end end UI:setPage(page) diff --git a/sys/apps/Installer.lua b/sys/apps/Installer.lua new file mode 100644 index 0000000..bd1ecb7 --- /dev/null +++ b/sys/apps/Installer.lua @@ -0,0 +1,456 @@ +local colors = _G.colors +local fs = _G.fs +local http = _G.http +local install = _ENV.install +local os = _G.os + +local requireInjector +if not install.testing then + local url ='https://raw.githubusercontent.com/kepler155c/opus/master/sys/apis/injector.lua' + requireInjector = load(http.get(url).readAll())() +else + requireInjector = _G.requireInjector +end + +requireInjector(_ENV) + +if not install.testing then + if package then + for _ = 1, 4 do + table.remove(package.loaders, 1) + end + end +end + +local Git = require('git') +local UI = require('ui') +local Util = require('util') + +local currentFile = '' +local currentProgress = 0 +local cancelEvent + +local args = { ... } +local steps = install.steps[args[1] or 'install'] + +if not steps then + error('Invalid install type') +end + +local mode = steps[#steps] + +if UI.term.width < 32 then + cancelEvent = 'quit' +end + +local page = UI.Page { + backgroundColor = colors.cyan, + titleBar = UI.TitleBar { + event = cancelEvent, + }, + wizard = UI.Wizard { + y = 2, ey = -2, + }, + notification = UI.Notification(), + accelerators = { + q = 'quit', + }, +} + +local pages = { + splash = UI.Viewport { }, + review = UI.Viewport { }, + license = UI.Viewport { + backgroundColor = colors.black, + }, + branch = UI.Window { + grid = UI.ScrollingGrid { + ey = -3, + columns = { + { heading = 'Branch', key = 'branch' }, + { heading = 'Description', key = 'description' }, + }, + values = install.branches, + autospace = true, + }, + }, + files = UI.Window { + grid = UI.ScrollingGrid { + ey = -3, + columns = { + { heading = 'Files', key = 'file' }, + }, + sortColumn = 'file', + }, + }, + install = UI.Window { + progressBar = UI.ProgressBar { + y = -1, + }, + }, + uninstall = UI.Window { + progressBar = UI.ProgressBar { + y = -1, + }, + }, +} + +local function getFileList() + if install.gitRepo then + local gitFiles = Git.list(string.format('%s/%s', install.gitRepo, install.gitBranch or 'master')) + install.files = { } + install.diskspace = 0 + for path, entry in pairs(gitFiles) do + install.files[path] = entry.url + install.diskspace = install.diskspace + entry.size + end + end + + if not install.files or Util.empty(install.files) then + error('File list is missing or empty') + end +end + +--[[ Splash ]]-- +function pages.splash:enable() + page.titleBar.title = 'Installer v1.0' + UI.Viewport.enable(self) +end + +function pages.splash:draw() + self:clear() + self:setCursorPos(1, 1) + self:print( + string.format('%s v%s\n', install.title, install.version), nil, colors.yellow) + self:print( + string.format('By: %s\n\n%s\n', install.author, install.description)) + + self.ymax = self.cursorY +end + +--[[ License ]]-- +function pages.license:enable() + page.titleBar.title = 'License Review' + page.wizard.nextButton.text = 'Accept' + UI.Viewport.enable(self) +end + +function pages.license:draw() + self:clear() + self:setCursorPos(1, 1) + self:print( + string.format('Copyright (c) %s %s\n\n', install.copyrightYear, + install.copyrightHolders), + nil, colors.yellow) + self:print(install.license) + + self.ymax = self.cursorY + 1 +end + +--[[ Review ]]-- +function pages.review:enable() + if mode == 'uninstall' then + page.nextButton.text = 'Remove' + page.titleBar.title = 'Remove Installed Files' + else + page.wizard.nextButton.text = 'Begin' + page.titleBar.title = 'Download and Install' + end + UI.Viewport.enable(self) +end + +function pages.review:draw() + self:clear() + self:setCursorPos(1, 1) + + local text = 'Ready to begin installation.\n\nProceeding will download and install the files to the hard drive.' + if mode == 'uninstall' then + text = 'Ready to begin.\n\nProceeding will remove the files previously installed.' + end + self:print(text) + + self.ymax = self.cursorY + 1 +end + +--[[ Files ]]-- +function pages.files:enable() + page.titleBar.title = 'Review Files' + self.grid.values = { } + for k,v in pairs(install.files) do + table.insert(self.grid.values, { file = k, code = v }) + end + self.grid:update() + UI.Window.enable(self) +end + +function pages.files:draw() + self:clear() + + local function formatSize(size) + if size >= 1000000 then + return string.format('%dM', math.floor(size/1000000, 2)) + elseif size >= 1000 then + return string.format('%dK', math.floor(size/1000, 2)) + end + return size + end + + if install.diskspace then + + local bg = self.backgroundColor + + local diskFree = fs.getFreeSpace('/') + if install.diskspace > diskFree then + bg = colors.red + end + + local text = string.format('Space Required: %s, Free: %s', + formatSize(install.diskspace), formatSize(diskFree)) + + if #text > self.width then + text = string.format('Space: %s Free: %s', + formatSize(install.diskspace), formatSize(diskFree)) + end + + self:write(1, self.height, Util.widthify(text, self.width), bg) + end + self.grid:draw() +end + +--[[ +function pages.files:view(url) + local s, m = pcall(function() + page.notification:info('Downloading') + page:sync() + Util.download(url, '/.source') + end) + page.notification:disable() + if s then + shell.run('edit /.source') + fs.delete('/.source') + page:draw() + page.notification:cancel() + else + page.notification:error(m:gsub('.*: (.*)', '%1')) + end +end + +function pages.files:eventHandler(event) + if event.type == 'grid_select' then + self:view(event.selected.code) + return true + end +end +--]] + +local function drawCommon(self) + if currentFile then + self:write(1, 3, 'File:') + self:write(1, 4, Util.widthify(currentFile, self.width)) + else + self:write(1, 3, 'Finished') + end + if self.failed then + self:write(1, 5, Util.widthify(self.failed, self.width), colors.red) + end + self:write(1, self.height - 1, 'Progress') + + self.progressBar.value = currentProgress + self.progressBar:draw() + self:sync() +end + +--[[ Branch ]]-- +function pages.branch:enable() + page.titleBar.title = 'Select Branch' + UI.Window.enable(self) +end + +function pages.branch:eventHandler(event) + -- user is navigating to next view (not previous) + if event.type == 'enable_view' and event.next then + install.gitBranch = self.grid:getSelected().branch + getFileList() + end +end + +--[[ Install ]]-- +function pages.install:enable() + page.wizard.cancelButton:disable() + page.wizard.previousButton:disable() + page.wizard.nextButton:disable() + + page.titleBar.title = 'Installing...' + page.titleBar.event = nil + + UI.Window.enable(self) + + page:draw() + page:sync() + + local i = 0 + local numFiles = Util.size(install.files) + for filename,url in pairs(install.files) do + currentFile = filename + currentProgress = i / numFiles * 100 + self:draw(self) + self:sync() + local s, m = pcall(function() + Util.download(url, fs.combine(install.directory or '', filename)) + end) + if not s then + self.failed = m:gsub('.*: (.*)', '%1') + break + end + i = i + 1 + end + + if not self.failed then + currentProgress = 100 + currentFile = nil + + if install.postInstall then + local s, m = pcall(function() install.postInstall(page, UI) end) + if not s then + self.failed = m:gsub('.*: (.*)', '%1') + end + end + end + + page.wizard.nextButton.text = 'Exit' + page.wizard.nextButton.event = 'quit' + if not self.failed and install.rebootAfter then + page.wizard.nextButton.text = 'Reboot' + page.wizard.nextButton.event = 'reboot' + end + + page.wizard.nextButton:enable() + page:draw() + page:sync() + + if not self.failed and Util.key(args, 'automatic') then + if install.rebootAfter then + os.reboot() + else + UI:exitPullEvents() + end + end +end + +function pages.install:draw() + self:clear() + local text = 'The files are being installed' + if #text > self.width then + text = 'Installing files' + end + self:write(1, 1, text, nil, colors.yellow) + + drawCommon(self) +end + +--[[ Uninstall ]]-- +function pages.uninstall:enable() + page.wizard.cancelButton:disable() + page.wizard.previousButton:disable() + page.wizard.nextButton:disable() + + page.titleBar.title = 'Uninstalling...' + page.titleBar.event = nil + + page:draw() + page:sync() + + UI.Window.enable(self) + + local function pruneDir(dir) + if #dir > 0 then + if fs.exists(dir) then + local files = fs.list(dir) + if #files == 0 then + fs.delete(dir) + pruneDir(fs.getDir(dir)) + end + end + end + end + + local i = 0 + local numFiles = Util.size(install.files) + for k in pairs(install.files) do + currentFile = k + currentProgress = i / numFiles * 100 + self:draw() + self:sync() + fs.delete(k) + pruneDir(fs.getDir(k)) + i = i + 1 + end + + currentProgress = 100 + currentFile = nil + + page.wizard.nextButton.text = 'Exit' + page.wizard.nextButton.event = 'quit' + page.wizard.nextButton:enable() + + page:draw() + page:sync() +end + +function pages.uninstall:draw() + self:clear() + self:write(1, 1, 'Uninstalling files', nil, colors.yellow) + drawCommon(self) +end + +function page:eventHandler(event) + if event.type == 'cancel' then + UI:exitPullEvents() + + elseif event.type == 'reboot' then + os.reboot() + + elseif event.type == 'quit' then + UI:exitPullEvents() + + else + return UI.Page.eventHandler(self, event) + end + return true +end + +function page:enable() + UI.Page.enable(self) + self:setFocus(page.wizard.nextButton) + if UI.term.width < 32 then + page.wizard.cancelButton:disable() + page.wizard.previousButton.x = 2 + end +end + +getFileList() + +local wizardPages = { } +for k,v in ipairs(steps) do + if not pages[v] then + error('Invalid step: ' .. v) + end + wizardPages[k] = pages[v] + wizardPages[k].index = k + wizardPages[k].x = 2 + wizardPages[k].y = 2 + wizardPages[k].ey = -3 + wizardPages[k].ex = -2 +end +page.wizard:add(wizardPages) + +if Util.key(steps, 'install') and install.preInstall then + install.preInstall(page, UI) +end + +UI:setPage(page) +local s, m = pcall(function() UI:pullEvents() end) +if not s then + UI.term:reset() + _G.printError(m) +end diff --git a/sys/apps/Lua.lua b/sys/apps/Lua.lua index 94c0fe2..f4653a2 100644 --- a/sys/apps/Lua.lua +++ b/sys/apps/Lua.lua @@ -1,6 +1,5 @@ _G.requireInjector(_ENV) -local Event = require('event') local History = require('history') local UI = require('ui') local Util = require('util') @@ -10,8 +9,10 @@ local os = _G.os local textutils = _G.textutils local term = _G.term +local _exit + local sandboxEnv = setmetatable(Util.shallowCopy(_ENV), { __index = _G }) -sandboxEnv.exit = function() Event.exitPullEvents() end +sandboxEnv.exit = function() _exit = true end sandboxEnv._echo = function( ... ) return { ... } end _G.requireInjector(sandboxEnv) @@ -19,7 +20,6 @@ UI:configure('Lua', ...) local command = '' local history = History.load('usr/.lua_history', 25) -local extChars = Util.getVersion() > 1.76 local page = UI.Page { menuBar = UI.MenuBar { @@ -41,10 +41,6 @@ local page = UI.Page { [ 'control-space' ] = 'autocomplete', }, }, - indicator = UI.Text { - backgroundColor = colors.black, - y = 2, x = -1, width = 1, - }, grid = UI.ScrollingGrid { y = 3, ey = -2, columns = { @@ -62,21 +58,10 @@ local page = UI.Page { }, output = UI.Embedded { y = -6, + backgroundColor = colors.gray, }, - --notification = UI.Notification(), } -function page.indicator:showResult(s) - local values = { - [ true ] = { c = colors.green, i = (extChars and '\003') or '+' }, - [ false ] = { c = colors.red, i = 'x' } - } - - self.textColor = values[s].c - self.value = values[s].i - self:draw() -end - function page:setPrompt(value, focus) self.prompt:setValue(value) self.prompt.scroll = 0 @@ -133,12 +118,12 @@ end function page:eventHandler(event) if event.type == 'global' then - self:setPrompt('', true) + self:setPrompt('_G', true) self:executeStatement('_G') command = nil elseif event.type == 'local' then - self:setPrompt('', true) + self:setPrompt('_ENV', true) self:executeStatement('_ENV') command = nil @@ -341,8 +326,11 @@ end function page:executeStatement(statement) command = statement + local s, m local oterm = term.redirect(self.output.win) - local s, m = self:rawExecute(command) + pcall(function() + s, m = self:rawExecute(command) + end) if not s then _G.printError(m) end @@ -353,14 +341,14 @@ function page:executeStatement(statement) else self.grid:setValues({ }) self.grid:draw() - if m then - if not self.output.enabled then - self:emit({ type = 'show_output' }) - end - --self.notification:error(m, 5) + if m and not self.output.enabled then + self:emit({ type = 'show_output' }) end end - self.indicator:showResult(not not s) + + if _exit then + UI:exitPullEvents() + end end local args = { ... } @@ -371,5 +359,4 @@ if args[1] then end UI:setPage(page) -Event.pullEvents() -UI.term:reset() +UI:pullEvents() diff --git a/sys/apps/Network.lua b/sys/apps/Network.lua index ca3e44d..e6d50c4 100644 --- a/sys/apps/Network.lua +++ b/sys/apps/Network.lua @@ -15,196 +15,196 @@ local shell = _ENV.shell UI:configure('Network', ...) local gridColumns = { - { heading = 'Label', key = 'label' }, - { heading = 'Dist', key = 'distance' }, - { heading = 'Status', key = 'status' }, + { heading = 'Label', key = 'label' }, + { heading = 'Dist', key = 'distance' }, + { heading = 'Status', key = 'status' }, } if UI.term.width >= 30 then - table.insert(gridColumns, { heading = 'Fuel', key = 'fuel', width = 5 }) - table.insert(gridColumns, { heading = 'Uptime', key = 'uptime' }) + table.insert(gridColumns, { heading = 'Fuel', key = 'fuel', width = 5 }) + table.insert(gridColumns, { heading = 'Uptime', key = 'uptime' }) end local page = UI.Page { - menuBar = UI.MenuBar { - buttons = { - { text = 'Connect', dropdown = { - { text = 'Telnet t', event = 'telnet' }, - { text = 'VNC v', event = 'vnc' }, - UI.MenuBar.spacer, - { text = 'Reboot r', event = 'reboot' }, - } }, - --{ text = 'Chat', event = 'chat' }, - { text = 'Trust', dropdown = { - { text = 'Establish', event = 'trust' }, - { text = 'Remove', event = 'untrust' }, - } }, - { text = 'Help', event = 'help' }, - }, - }, - grid = UI.ScrollingGrid { - y = 2, - values = network, - columns = gridColumns, - sortColumn = 'label', - autospace = true, - }, - notification = UI.Notification { }, - accelerators = { - t = 'telnet', - v = 'vnc', - r = 'reboot', - q = 'quit', - c = 'clear', - }, + menuBar = UI.MenuBar { + buttons = { + { text = 'Connect', dropdown = { + { text = 'Telnet t', event = 'telnet' }, + { text = 'VNC v', event = 'vnc' }, + UI.MenuBar.spacer, + { text = 'Reboot r', event = 'reboot' }, + } }, + --{ text = 'Chat', event = 'chat' }, + { text = 'Trust', dropdown = { + { text = 'Establish', event = 'trust' }, + { text = 'Remove', event = 'untrust' }, + } }, + { text = 'Help', event = 'help' }, + }, + }, + grid = UI.ScrollingGrid { + y = 2, + values = network, + columns = gridColumns, + sortColumn = 'label', + autospace = true, + }, + notification = UI.Notification { }, + accelerators = { + t = 'telnet', + v = 'vnc', + r = 'reboot', + q = 'quit', + c = 'clear', + }, } local function sendCommand(host, command) - if not device.wireless_modem then - page.notification:error('Wireless modem not present') - return - end + if not device.wireless_modem then + page.notification:error('Wireless modem not present') + return + end - page.notification:info('Connecting') - page:sync() + page.notification:info('Connecting') + page:sync() - local socket = Socket.connect(host, 161) - if socket then - socket:write({ type = command }) - socket:close() - page.notification:success('Command sent') - else - page.notification:error('Failed to connect') - end + local socket = Socket.connect(host, 161) + if socket then + socket:write({ type = command }) + socket:close() + page.notification:success('Command sent') + else + page.notification:error('Failed to connect') + end end function page:eventHandler(event) - local t = self.grid:getSelected() - if t then - if event.type == 'telnet' then - multishell.openTab({ - path = 'sys/apps/telnet.lua', - focused = true, - args = { t.id }, - title = t.label, - }) - elseif event.type == 'vnc' then - multishell.openTab({ - path = 'sys/apps/vnc.lua', - focused = true, - args = { t.id }, - title = t.label, - }) - elseif event.type == 'clear' then - Util.clear(network) - page.grid:update() - page.grid:draw() + local t = self.grid:getSelected() + if t then + if event.type == 'telnet' then + multishell.openTab({ + path = 'sys/apps/telnet.lua', + focused = true, + args = { t.id }, + title = t.label, + }) + elseif event.type == 'vnc' then + multishell.openTab({ + path = 'sys/apps/vnc.lua', + focused = true, + args = { t.id }, + title = t.label, + }) + elseif event.type == 'clear' then + Util.clear(network) + page.grid:update() + page.grid:draw() - elseif event.type == 'trust' then - shell.openForegroundTab('trust ' .. t.id) + elseif event.type == 'trust' then + shell.openForegroundTab('trust ' .. t.id) - elseif event.type == 'untrust' then - local trustList = Util.readTable('usr/.known_hosts') or { } - trustList[t.id] = nil - Util.writeTable('usr/.known_hosts', trustList) + elseif event.type == 'untrust' then + local trustList = Util.readTable('usr/.known_hosts') or { } + trustList[t.id] = nil + Util.writeTable('usr/.known_hosts', trustList) - elseif event.type == 'chat' then - multishell.openTab({ - path = 'sys/apps/shell', - args = { 'chat join opusChat-' .. t.id .. ' guest-' .. os.getComputerID() }, - title = 'Chatroom', - focused = true, - }) - elseif event.type == 'reboot' then - sendCommand(t.id, 'reboot') + elseif event.type == 'chat' then + multishell.openTab({ + path = 'sys/apps/shell', + args = { 'chat join opusChat-' .. t.id .. ' guest-' .. os.getComputerID() }, + title = 'Chatroom', + focused = true, + }) + elseif event.type == 'reboot' then + sendCommand(t.id, 'reboot') - elseif event.type == 'shutdown' then - sendCommand(t.id, 'shutdown') - end - end - if event.type == 'help' then - UI:setPage(UI.Dialog { - title = 'Network Help', - height = 10, - backgroundColor = colors.white, - text = UI.TextArea { - x = 2, y = 2, - backgroundColor = colors.white, - value = [[ + elseif event.type == 'shutdown' then + sendCommand(t.id, 'shutdown') + end + end + if event.type == 'help' then + UI:setPage(UI.Dialog { + title = 'Network Help', + height = 10, + backgroundColor = colors.white, + text = UI.TextArea { + x = 2, y = 2, + backgroundColor = colors.white, + value = [[ In order to connect to another computer: - 1. The target computer must have a password set (run 'password' from the shell prompt). - 2. From this computer, click trust and enter the password for that computer. + 1. The target computer must have a password set (run 'password' from the shell prompt). + 2. From this computer, click trust and enter the password for that computer. This only needs to be done once. - ]], - }, - accelerators = { - q = 'cancel', - } - }) - elseif event.type == 'quit' then - Event.exitPullEvents() - end - UI.Page.eventHandler(self, event) + ]], + }, + accelerators = { + q = 'cancel', + } + }) + elseif event.type == 'quit' then + Event.exitPullEvents() + end + UI.Page.eventHandler(self, event) end function page.menuBar:getActive(menuItem) - local t = page.grid:getSelected() - if menuItem.event == 'untrust' then - local trustList = Util.readTable('usr/.known_hosts') or { } - return t and trustList[t.id] - end - return not not t + local t = page.grid:getSelected() + if menuItem.event == 'untrust' then + local trustList = Util.readTable('usr/.known_hosts') or { } + return t and trustList[t.id] + end + return not not t end function page.grid:getRowTextColor(row, selected) - if not row.active then - return colors.orange - end - return UI.Grid.getRowTextColor(self, row, selected) + if not row.active then + return colors.orange + end + return UI.Grid.getRowTextColor(self, row, selected) end function page.grid:getDisplayValues(row) - row = Util.shallowCopy(row) - if row.uptime then - if row.uptime < 60 then - row.uptime = string.format("%ds", math.floor(row.uptime)) - else - row.uptime = string.format("%sm", math.floor(row.uptime/6)/10) - end - end - if row.fuel then - row.fuel = Util.toBytes(row.fuel) - end - if row.distance then - row.distance = Util.round(row.distance, 1) - end - return row + row = Util.shallowCopy(row) + if row.uptime then + if row.uptime < 60 then + row.uptime = string.format("%ds", math.floor(row.uptime)) + else + row.uptime = string.format("%sm", math.floor(row.uptime/6)/10) + end + end + if row.fuel then + row.fuel = Util.toBytes(row.fuel) + end + if row.distance then + row.distance = Util.round(row.distance, 1) + end + return row end Event.onInterval(1, function() - page.grid:update() - page.grid:draw() - page:sync() + page.grid:update() + page.grid:draw() + page:sync() end) Event.on('device_attach', function(_, deviceName) - if deviceName == 'wireless_modem' then - page.notification:success('Modem connected') - page:sync() - end + if deviceName == 'wireless_modem' then + page.notification:success('Modem connected') + page:sync() + end end) Event.on('device_detach', function(_, deviceName) - if deviceName == 'wireless_modem' then - page.notification:error('Wireless modem not attached') - page:sync() - end + if deviceName == 'wireless_modem' then + page.notification:error('Wireless modem not attached') + page:sync() + end end) if not device.wireless_modem then - page.notification:error('Wireless modem not attached') + page.notification:error('Wireless modem not attached') end UI:setPage(page) diff --git a/sys/apps/Overview.lua b/sys/apps/Overview.lua index 49abcd8..1d1961d 100644 --- a/sys/apps/Overview.lua +++ b/sys/apps/Overview.lua @@ -1,4 +1,4 @@ -_G.requireInjector() +_G.requireInjector(_ENV) local class = require('class') local Config = require('config') @@ -18,7 +18,7 @@ local term = _G.term local turtle = _G.turtle if not _ENV.multishell then - error('multishell is required') + error('multishell is required') end local REGISTRY_DIR = 'usr/.registry' @@ -26,8 +26,8 @@ local REGISTRY_DIR = 'usr/.registry' UI:configure('Overview', ...) local config = { - Recent = { }, - currentCategory = 'Apps', + Recent = { }, + currentCategory = 'Apps', } Config.load('Overview', config) @@ -35,51 +35,51 @@ local applications = { } local function loadApplications() - local requirements = { - turtle = function() return turtle end, - advancedTurtle = function() return turtle and term.isColor() end, - advanced = function() return term.isColor() end, - pocket = function() return pocket end, - advancedPocket = function() return pocket and term.isColor() end, - advancedComputer = function() return not turtle and not pocket and term.isColor() end, - } + local requirements = { + turtle = function() return turtle end, + advancedTurtle = function() return turtle and term.isColor() end, + advanced = function() return term.isColor() end, + pocket = function() return pocket end, + advancedPocket = function() return pocket and term.isColor() end, + advancedComputer = function() return not turtle and not pocket and term.isColor() end, + } - applications = Util.readTable('sys/etc/app.db') + applications = Util.readTable('sys/etc/app.db') - if fs.exists('usr/etc/apps') then - local dbs = fs.list('usr/etc/apps') - for _, db in pairs(dbs) do - local apps = Util.readTable('usr/etc/apps/' .. db) or { } - Util.merge(applications, apps) - end - end + if fs.exists('usr/etc/apps') then + local dbs = fs.list('usr/etc/apps') + for _, db in pairs(dbs) do + local apps = Util.readTable('usr/etc/apps/' .. db) or { } + Util.merge(applications, apps) + end + end - if fs.exists(REGISTRY_DIR) then - local files = fs.list(REGISTRY_DIR) - for _,file in pairs(files) do - local app = Util.readTable(fs.combine(REGISTRY_DIR, file)) - if app and app.key then - app.filename = fs.combine(REGISTRY_DIR, file) - applications[app.key] = app - end - end - end + if fs.exists(REGISTRY_DIR) then + local files = fs.list(REGISTRY_DIR) + for _,file in pairs(files) do + local app = Util.readTable(fs.combine(REGISTRY_DIR, file)) + if app and app.key then + app.filename = fs.combine(REGISTRY_DIR, file) + applications[app.key] = app + end + end + end - Util.each(applications, function(v, k) v.key = k end) - applications = Util.filter(applications, function(a) - if a.disabled then - return false - end + Util.each(applications, function(v, k) v.key = k end) + applications = Util.filter(applications, function(a) + if a.disabled then + return false + end - if a.requires then - local fn = requirements[a.requires] - if fn and not fn() then - return false - end - end + if a.requires then + local fn = requirements[a.requires] + if fn and not fn() then + return false + end + end - return true -- Util.startsWidth(a.run, 'http') or shell.resolveProgram(a.run) - end) + return true -- Util.startsWidth(a.run, 'http') or shell.resolveProgram(a.run) + end) end loadApplications() @@ -92,457 +92,457 @@ local sx, sy = term.current().getSize() local maxRecent = math.ceil(sx * sy / 62) local function elipse(s, len) - if #s > len then - s = s:sub(1, len - 2) .. '..' - end - return s + if #s > len then + s = s:sub(1, len - 2) .. '..' + end + return s end local buttons = { } local categories = { } for _,f in pairs(applications) do - if not categories[f.category] then - categories[f.category] = true - table.insert(buttons, { text = f.category }) - end + if not categories[f.category] then + categories[f.category] = true + table.insert(buttons, { text = f.category }) + end end table.sort(buttons, function(a, b) return a.text < b.text end) table.insert(buttons, 1, { text = 'Recent' }) table.insert(buttons, { text = '+', event = 'new' }) local function parseIcon(iconText) - local icon + local icon - local s, m = pcall(function() - icon = NFT.parse(iconText) - if icon then - if icon.height > 3 or icon.width > 8 then - error('Invalid size') - end - end - return icon - end) + local s, m = pcall(function() + icon = NFT.parse(iconText) + if icon then + if icon.height > 3 or icon.width > 8 then + error('Invalid size') + end + end + return icon + end) - if s then - return icon - end + if s then + return icon + end - return s, m + return s, m end UI.VerticalTabBar = class(UI.TabBar) function UI.VerticalTabBar:setParent() - self.x = 1 - self.width = 8 - self.height = nil - self.ey = -1 - UI.TabBar.setParent(self) - for k,c in pairs(self.children) do - c.x = 1 - c.y = k + 1 - c.ox, c.oy = c.x, c.y - c.ow = 8 - c.width = 8 - end + self.x = 1 + self.width = 8 + self.height = nil + self.ey = -1 + UI.TabBar.setParent(self) + for k,c in pairs(self.children) do + c.x = 1 + c.y = k + 1 + c.ox, c.oy = c.x, c.y + c.ow = 8 + c.width = 8 + end end local cx = 9 local cy = 1 if sx < 30 then - UI.VerticalTabBar = UI.TabBar - cx = 1 - cy = 2 + UI.VerticalTabBar = UI.TabBar + cx = 1 + cy = 2 end local page = UI.Page { - tabBar = UI.VerticalTabBar { - buttons = buttons, - }, - container = UI.Viewport { - x = cx, - y = cy, - }, - notification = UI.Notification(), - accelerators = { - r = 'refresh', - e = 'edit', - f = 'files', - s = 'shell', - l = 'lua', - [ 'control-n' ] = 'new', - delete = 'delete', - }, + tabBar = UI.VerticalTabBar { + buttons = buttons, + }, + container = UI.Viewport { + x = cx, + y = cy, + }, + notification = UI.Notification(), + accelerators = { + r = 'refresh', + e = 'edit', + f = 'files', + s = 'shell', + l = 'lua', + [ 'control-n' ] = 'new', + delete = 'delete', + }, } UI.Icon = class(UI.Window) UI.Icon.defaults = { - UIElement = 'Icon', - width = 14, - height = 4, + UIElement = 'Icon', + width = 14, + height = 4, } function UI.Icon:eventHandler(event) - if event.type == 'mouse_click' then - self:setFocus(self.button) - return true - elseif event.type == 'mouse_doubleclick' then - self:emit({ type = self.button.event, button = self.button }) - elseif event.type == 'mouse_rightclick' then - self:setFocus(self.button) - self:emit({ type = 'edit', button = self.button }) - end - return UI.Window.eventHandler(self, event) + if event.type == 'mouse_click' then + self:setFocus(self.button) + return true + elseif event.type == 'mouse_doubleclick' then + self:emit({ type = self.button.event, button = self.button }) + elseif event.type == 'mouse_rightclick' then + self:setFocus(self.button) + self:emit({ type = 'edit', button = self.button }) + end + return UI.Window.eventHandler(self, event) end function page.container:setCategory(categoryName, animate) - -- reset the viewport window - self.children = { } - self.offy = 0 + -- reset the viewport window + self.children = { } + self.offy = 0 - local function filter(it, f) - local ot = { } - for _,v in pairs(it) do - if f(v) then - table.insert(ot, v) - end - end - return ot - end + local function filter(it, f) + local ot = { } + for _,v in pairs(it) do + if f(v) then + table.insert(ot, v) + end + end + return ot + end - local filtered + local filtered - if categoryName == 'Recent' then - filtered = { } + if categoryName == 'Recent' then + filtered = { } - for _,v in ipairs(config.Recent) do - local app = Util.find(applications, 'key', v) - if app then -- and fs.exists(app.run) then - table.insert(filtered, app) - end - end + for _,v in ipairs(config.Recent) do + local app = Util.find(applications, 'key', v) + if app then -- and fs.exists(app.run) then + table.insert(filtered, app) + end + end - else - filtered = filter(applications, function(a) - return a.category == categoryName -- and fs.exists(a.run) - end) - table.sort(filtered, function(a, b) return a.title < b.title end) - end + else + filtered = filter(applications, function(a) + return a.category == categoryName -- and fs.exists(a.run) + end) + table.sort(filtered, function(a, b) return a.title < b.title end) + end - for _,program in ipairs(filtered) do + for _,program in ipairs(filtered) do - local icon - if program.icon then - icon = parseIcon(program.icon) - end - if not icon then - icon = defaultIcon - end + local icon + if program.icon then + icon = parseIcon(program.icon) + end + if not icon then + icon = defaultIcon + end - local title = elipse(program.title, 8) + local title = elipse(program.title, 8) - local width = math.max(icon.width + 2, #title + 2) - table.insert(self.children, UI.Icon({ - width = width, - image = UI.NftImage({ - x = math.floor((width - icon.width) / 2) + 1, - image = icon, - width = 5, - height = 3, - }), - button = UI.Button({ - x = math.floor((width - #title - 2) / 2) + 1, - y = 4, - text = title, - backgroundColor = self.backgroundColor, - backgroundFocusColor = colors.gray, - textColor = colors.white, - textFocusColor = colors.white, - width = #title + 2, - event = 'button', - app = program, - }), - })) - end + local width = math.max(icon.width + 2, #title + 2) + table.insert(self.children, UI.Icon({ + width = width, + image = UI.NftImage({ + x = math.floor((width - icon.width) / 2) + 1, + image = icon, + width = 5, + height = 3, + }), + button = UI.Button({ + x = math.floor((width - #title - 2) / 2) + 1, + y = 4, + text = title, + backgroundColor = self.backgroundColor, + backgroundFocusColor = colors.gray, + textColor = colors.white, + textFocusColor = colors.white, + width = #title + 2, + event = 'button', + app = program, + }), + })) + end - local gutter = 2 - if UI.term.width <= 26 then - gutter = 1 - end - local col, row = gutter, 2 - local count = #self.children + local gutter = 2 + if UI.term.width <= 26 then + gutter = 1 + end + local col, row = gutter, 2 + local count = #self.children - local r = math.random(1, 5) - -- reposition all children - for k,child in ipairs(self.children) do - if r == 1 then - child.x = math.random(1, self.width) - child.y = math.random(1, self.height) - elseif r == 2 then - child.x = self.width - child.y = self.height - elseif r == 3 then - child.x = math.floor(self.width / 2) - child.y = math.floor(self.height / 2) - elseif r == 4 then - child.x = self.width - col - child.y = row - elseif r == 5 then - child.x = col - child.y = row - if k == #self.children then - child.x = self.width - child.y = self.height - end - end - child.tween = Tween.new(6, child, { x = col, y = row }, 'linear') + local r = math.random(1, 5) + -- reposition all children + for k,child in ipairs(self.children) do + if r == 1 then + child.x = math.random(1, self.width) + child.y = math.random(1, self.height) + elseif r == 2 then + child.x = self.width + child.y = self.height + elseif r == 3 then + child.x = math.floor(self.width / 2) + child.y = math.floor(self.height / 2) + elseif r == 4 then + child.x = self.width - col + child.y = row + elseif r == 5 then + child.x = col + child.y = row + if k == #self.children then + child.x = self.width + child.y = self.height + end + end + child.tween = Tween.new(6, child, { x = col, y = row }, 'linear') - if not animate then - child.x = col - child.y = row - end + if not animate then + child.x = col + child.y = row + end - if k < count then - col = col + child.width - if col + self.children[k + 1].width + gutter - 2 > self.width then - col = gutter - row = row + 5 - end - end - end + if k < count then + col = col + child.width + if col + self.children[k + 1].width + gutter - 2 > self.width then + col = gutter + row = row + 5 + end + end + end - self:initChildren() - if animate then -- need to fix transitions under layers - local function transition(args) - local i = 1 - return function(device) - self:clear() - for _,child in pairs(self.children) do - child.tween:update(1) - child.x = math.floor(child.x) - child.y = math.floor(child.y) - child:draw() - end - args.canvas:blit(device, args, args) - i = i + 1 - return i < 7 - end - end - self:addTransition(transition) - end + self:initChildren() + if animate then -- need to fix transitions under layers + local function transition(args) + local i = 1 + return function(device) + self:clear() + for _,child in pairs(self.children) do + child.tween:update(1) + child.x = math.floor(child.x) + child.y = math.floor(child.y) + child:draw() + end + args.canvas:blit(device, args, args) + i = i + 1 + return i < 7 + end + end + self:addTransition(transition) + end end function page:refresh() - local pos = self.container.offy - self:focusFirst(self) - self.container:setCategory(config.currentCategory) - self.container:setScrollPosition(pos) + local pos = self.container.offy + self:focusFirst(self) + self.container:setCategory(config.currentCategory) + self.container:setScrollPosition(pos) end function page:resize() - UI.Page.resize(self) - self:refresh() + UI.Page.resize(self) + self:refresh() end function page:eventHandler(event) - if event.type == 'tab_select' then - self.container:setCategory(event.button.text, true) - self.container:draw() + if event.type == 'tab_select' then + self.container:setCategory(event.button.text, true) + self.container:draw() - config.currentCategory = event.button.text - Config.update('Overview', config) + config.currentCategory = event.button.text + Config.update('Overview', config) - elseif event.type == 'button' then - for k,v in ipairs(config.Recent) do - if v == event.button.app.key then - table.remove(config.Recent, k) - break - end - end - table.insert(config.Recent, 1, event.button.app.key) - if #config.Recent > maxRecent then - table.remove(config.Recent, maxRecent + 1) - end - Config.update('Overview', config) - shell.switchTab(shell.openTab(event.button.app.run)) + elseif event.type == 'button' then + for k,v in ipairs(config.Recent) do + if v == event.button.app.key then + table.remove(config.Recent, k) + break + end + end + table.insert(config.Recent, 1, event.button.app.key) + if #config.Recent > maxRecent then + table.remove(config.Recent, maxRecent + 1) + end + Config.update('Overview', config) + shell.switchTab(shell.openTab(event.button.app.run)) - elseif event.type == 'shell' then - shell.switchTab(shell.openTab('sys/apps/shell')) + elseif event.type == 'shell' then + shell.switchTab(shell.openTab('sys/apps/shell')) - elseif event.type == 'lua' then - shell.switchTab(shell.openTab('sys/apps/Lua.lua')) + elseif event.type == 'lua' then + shell.switchTab(shell.openTab('sys/apps/Lua.lua')) - elseif event.type == 'files' then - shell.switchTab(shell.openTab('sys/apps/Files.lua')) + elseif event.type == 'files' then + shell.switchTab(shell.openTab('sys/apps/Files.lua')) - elseif event.type == 'focus_change' then - if event.focused.parent.UIElement == 'Icon' then - event.focused.parent:scrollIntoView() - end + elseif event.type == 'focus_change' then + if event.focused.parent.UIElement == 'Icon' then + event.focused.parent:scrollIntoView() + end - elseif event.type == 'refresh' then -- remove this after fixing notification - loadApplications() - self:refresh() - self:draw() - self.notification:success('Refreshed') + elseif event.type == 'refresh' then -- remove this after fixing notification + loadApplications() + self:refresh() + self:draw() + self.notification:success('Refreshed') - elseif event.type == 'delete' then - local focused = page:getFocused() - if focused.app then - focused.app.disabled = true - local filename = focused.app.filename or fs.combine(REGISTRY_DIR, focused.app.key) - Util.writeTable(filename, focused.app) - loadApplications() - page:refresh() - page:draw() - self.notification:success('Removed') - end + elseif event.type == 'delete' then + local focused = page:getFocused() + if focused.app then + focused.app.disabled = true + local filename = focused.app.filename or fs.combine(REGISTRY_DIR, focused.app.key) + Util.writeTable(filename, focused.app) + loadApplications() + page:refresh() + page:draw() + self.notification:success('Removed') + end - elseif event.type == 'new' then - local category = 'Apps' - if config.currentCategory ~= 'Recent' then - category = config.currentCategory or 'Apps' - end - UI:setPage('editor', { category = category }) + elseif event.type == 'new' then + local category = 'Apps' + if config.currentCategory ~= 'Recent' then + category = config.currentCategory or 'Apps' + end + UI:setPage('editor', { category = category }) - elseif event.type == 'edit' then - local focused = page:getFocused() - if focused.app then - UI:setPage('editor', focused.app) - end + elseif event.type == 'edit' then + local focused = page:getFocused() + if focused.app then + UI:setPage('editor', focused.app) + end - else - UI.Page.eventHandler(self, event) - end - return true + else + UI.Page.eventHandler(self, event) + end + return true end local formWidth = math.max(UI.term.width - 8, 26) local editor = UI.Dialog { - height = 11, - width = formWidth, - title = 'Edit Application', - form = UI.Form { - y = 2, - height = 9, - title = UI.TextEntry { - formLabel = 'Title', formKey = 'title', limit = 11, help = 'Application title', - required = true, - }, - run = UI.TextEntry { - formLabel = 'Run', formKey = 'run', limit = 100, help = 'Full path to application', - required = true, - }, - category = UI.TextEntry { - formLabel = 'Category', formKey = 'category', limit = 11, help = 'Category of application', - required = true, - }, - loadIcon = UI.Button { - x = 11, y = 6, - text = 'Icon', event = 'loadIcon', help = 'Select icon' - }, - image = UI.NftImage { - y = 6, x = 2, height = 3, width = 8, - }, - }, - statusBar = UI.StatusBar(), - iconFile = '', + height = 11, + width = formWidth, + title = 'Edit Application', + form = UI.Form { + y = 2, + height = 9, + title = UI.TextEntry { + formLabel = 'Title', formKey = 'title', limit = 11, help = 'Application title', + required = true, + }, + run = UI.TextEntry { + formLabel = 'Run', formKey = 'run', limit = 100, help = 'Full path to application', + required = true, + }, + category = UI.TextEntry { + formLabel = 'Category', formKey = 'category', limit = 11, help = 'Category of application', + required = true, + }, + loadIcon = UI.Button { + x = 11, y = 6, + text = 'Icon', event = 'loadIcon', help = 'Select icon' + }, + image = UI.NftImage { + y = 6, x = 2, height = 3, width = 8, + }, + }, + statusBar = UI.StatusBar(), + iconFile = '', } function editor:enable(app) - if app then - self.form:setValues(app) + if app then + self.form:setValues(app) - local icon - if app.icon then - icon = parseIcon(app.icon) - end - self.form.image:setImage(icon) - end - UI.Dialog.enable(self) - self:focusFirst() + local icon + if app.icon then + icon = parseIcon(app.icon) + end + self.form.image:setImage(icon) + end + UI.Dialog.enable(self) + self:focusFirst() end function editor.form.image:draw() - self:clear() - UI.NftImage.draw(self) + self:clear() + UI.NftImage.draw(self) end function editor:updateApplications(app) - if not app.key then - app.key = SHA1.sha1(app.title) - end - local filename = app.filename or fs.combine(REGISTRY_DIR, app.key) - Util.writeTable(filename, app) - loadApplications() + if not app.key then + app.key = SHA1.sha1(app.title) + end + local filename = app.filename or fs.combine(REGISTRY_DIR, app.key) + Util.writeTable(filename, app) + loadApplications() end function editor:eventHandler(event) - if event.type == 'form_cancel' or event.type == 'cancel' then - UI:setPreviousPage() + if event.type == 'form_cancel' or event.type == 'cancel' then + UI:setPreviousPage() - elseif event.type == 'focus_change' then - self.statusBar:setStatus(event.focused.help or '') - self.statusBar:draw() + elseif event.type == 'focus_change' then + self.statusBar:setStatus(event.focused.help or '') + self.statusBar:draw() - elseif event.type == 'loadIcon' then - local fileui = FileUI({ - x = self.x, - y = self.y, - z = 2, - width = self.width, - height = self.height, - }) - UI:setPage(fileui, fs.getDir(self.iconFile), function(fileName) - if fileName then - self.iconFile = fileName - local s, m = pcall(function() - local iconLines = Util.readFile(fileName) - if not iconLines then - error('Unable to load file') - end - local icon, m = parseIcon(iconLines) - if not icon then - error(m) - end - self.form.values.icon = iconLines - self.form.image:setImage(icon) - self.form.image:draw() - end) - if not s and m then - local msg = m:gsub('.*: (.*)', '%1') - page.notification:error(msg) - end - end - end) + elseif event.type == 'loadIcon' then + local fileui = FileUI({ + x = self.x, + y = self.y, + z = 2, + width = self.width, + height = self.height, + }) + UI:setPage(fileui, fs.getDir(self.iconFile), function(fileName) + if fileName then + self.iconFile = fileName + local s, m = pcall(function() + local iconLines = Util.readFile(fileName) + if not iconLines then + error('Unable to load file') + end + local icon, m = parseIcon(iconLines) + if not icon then + error(m) + end + self.form.values.icon = iconLines + self.form.image:setImage(icon) + self.form.image:draw() + end) + if not s and m then + local msg = m:gsub('.*: (.*)', '%1') + page.notification:error(msg) + end + end + end) - elseif event.type == 'form_invalid' then - page.notification:error(event.message) + elseif event.type == 'form_invalid' then + page.notification:error(event.message) - elseif event.type == 'form_complete' then - local values = self.form.values - UI:setPreviousPage() - self:updateApplications(values) - page:refresh() - page:draw() - else - return UI.Dialog.eventHandler(self, event) - end - return true + elseif event.type == 'form_complete' then + local values = self.form.values + UI:setPreviousPage() + self:updateApplications(values) + page:refresh() + page:draw() + else + return UI.Dialog.eventHandler(self, event) + end + return true end UI:setPages({ - editor = editor, - main = page, + editor = editor, + main = page, }) Event.on('os_register_app', function() - loadApplications() - page:refresh() - page:draw() - page:sync() + loadApplications() + page:refresh() + page:draw() + page:sync() end) page.tabBar:selectTab(config.currentCategory or 'Apps') diff --git a/sys/apps/System.lua b/sys/apps/System.lua index 078fffb..e0ac3ab 100644 --- a/sys/apps/System.lua +++ b/sys/apps/System.lua @@ -1,4 +1,4 @@ -_G.requireInjector() +_G.requireInjector(_ENV) local Config = require('config') local Security = require('security') @@ -15,307 +15,313 @@ local turtle = _G.turtle UI:configure('System', ...) local env = { - path = shell.path(), - aliases = shell.aliases(), - lua_path = _ENV.LUA_PATH, + path = shell.path(), + aliases = shell.aliases(), + lua_path = _ENV.LUA_PATH, } Config.load('shell', env) local systemPage = UI.Page { - tabs = UI.Tabs { - pathTab = UI.Window { - tabTitle = 'Path', - entry = UI.TextEntry { - x = 2, y = 2, ex = -2, - limit = 256, - value = shell.path(), - shadowText = 'enter system path', - accelerators = { - enter = 'update_path', - }, - }, - grid = UI.Grid { - y = 4, - disableHeader = true, - columns = { { key = 'value' } }, - autospace = true, - }, - }, + tabs = UI.Tabs { + pathTab = UI.Window { + tabTitle = 'Path', + entry = UI.TextEntry { + x = 2, y = 2, ex = -2, + limit = 256, + value = shell.path(), + shadowText = 'enter system path', + accelerators = { + enter = 'update_path', + }, + }, + grid = UI.Grid { + y = 4, + disableHeader = true, + columns = { { key = 'value' } }, + autospace = true, + }, + }, - aliasTab = UI.Window { - tabTitle = 'Alias', - alias = UI.TextEntry { - x = 2, y = 2, ex = -2, - limit = 32, - shadowText = 'Alias', - }, - path = UI.TextEntry { - y = 3, x = 2, ex = -2, - limit = 256, - shadowText = 'Program path', - accelerators = { - enter = 'new_alias', - }, - }, - grid = UI.Grid { - y = 5, - sortColumn = 'alias', - columns = { - { heading = 'Alias', key = 'alias' }, - { heading = 'Program', key = 'path' }, - }, - accelerators = { - delete = 'delete_alias', - }, - }, - }, + aliasTab = UI.Window { + tabTitle = 'Alias', + alias = UI.TextEntry { + x = 2, y = 2, ex = -2, + limit = 32, + shadowText = 'Alias', + }, + path = UI.TextEntry { + y = 3, x = 2, ex = -2, + limit = 256, + shadowText = 'Program path', + accelerators = { + enter = 'new_alias', + }, + }, + grid = UI.Grid { + y = 5, + sortColumn = 'alias', + columns = { + { heading = 'Alias', key = 'alias' }, + { heading = 'Program', key = 'path' }, + }, + accelerators = { + delete = 'delete_alias', + }, + }, + }, - passwordTab = UI.Window { - tabTitle = 'Password', - oldPass = UI.TextEntry { - x = 2, y = 2, ex = -2, - limit = 32, - mask = true, - shadowText = 'old password', - inactive = not Security.getPassword(), - }, - newPass = UI.TextEntry { - y = 3, x = 2, ex = -2, - limit = 32, - mask = true, - shadowText = 'new password', - accelerators = { - enter = 'new_password', - }, - }, - button = UI.Button { - x = 2, y = 5, - text = 'Update', - event = 'update_password', - }, - info = UI.TextArea { - x = 2, ex = -2, - y = 7, - inactive = true, - value = 'Add a password to enable other computers to connect to this one.', - } - }, + passwordTab = UI.Window { + tabTitle = 'Password', + oldPass = UI.TextEntry { + x = 2, y = 2, ex = -2, + limit = 32, + mask = true, + shadowText = 'old password', + inactive = not Security.getPassword(), + }, + newPass = UI.TextEntry { + y = 3, x = 2, ex = -2, + limit = 32, + mask = true, + shadowText = 'new password', + accelerators = { + enter = 'new_password', + }, + }, + button = UI.Button { + x = 2, y = 5, + text = 'Update', + event = 'update_password', + }, + info = UI.TextArea { + x = 2, ex = -2, + y = 7, + inactive = true, + value = 'Add a password to enable other computers to connect to this one.', + } + }, - infoTab = UI.Window { - tabTitle = 'Info', - labelText = UI.Text { - x = 3, y = 2, - value = 'Label' - }, - label = UI.TextEntry { - x = 9, y = 2, ex = -4, - limit = 32, - value = os.getComputerLabel(), - accelerators = { - enter = 'update_label', - }, - }, - grid = UI.ScrollingGrid { - y = 3, - values = { - { name = '', value = '' }, - { name = 'CC version', value = Util.getVersion() }, - { name = 'Lua version', value = _VERSION }, - { name = 'MC version', value = Util.getMinecraftVersion() }, - { name = 'Disk free', value = Util.toBytes(fs.getFreeSpace('/')) }, - { name = 'Computer ID', value = tostring(os.getComputerID()) }, - { name = 'Day', value = tostring(os.day()) }, - }, - inactive = true, - columns = { - { key = 'name', width = 12 }, - { key = 'value' }, - }, - }, - }, - }, - notification = UI.Notification(), - accelerators = { - q = 'quit', - }, + infoTab = UI.Window { + tabTitle = 'Info', + labelText = UI.Text { + x = 3, y = 2, + value = 'Label' + }, + label = UI.TextEntry { + x = 9, y = 2, ex = -4, + limit = 32, + value = os.getComputerLabel(), + accelerators = { + enter = 'update_label', + }, + }, + grid = UI.ScrollingGrid { + y = 3, + values = { + { name = '', value = '' }, + { name = 'CC version', value = Util.getVersion() }, + { name = 'Lua version', value = _VERSION }, + { name = 'MC version', value = Util.getMinecraftVersion() }, + { name = 'Disk free', value = Util.toBytes(fs.getFreeSpace('/')) }, + { name = 'Computer ID', value = tostring(os.getComputerID()) }, + { name = 'Day', value = tostring(os.day()) }, + }, + inactive = true, + columns = { + { key = 'name', width = 12 }, + { key = 'value' }, + }, + }, + }, + }, + notification = UI.Notification(), + accelerators = { + q = 'quit', + }, } if turtle then - local Home = require('turtle.home') + local Home = require('turtle.home') - local values = { } - Config.load('gps', values.home or { }) + local values = { } + Config.load('gps', values.home or { }) - systemPage.tabs:add({ - gpsTab = UI.Window { - tabTitle = 'GPS', - labelText = UI.Text { - x = 3, y = 2, - value = 'On restart, return to this location' - }, - grid = UI.Grid { - x = 3, ex = -3, y = 4, - height = 2, - values = values, - inactive = true, - columns = { - { heading = 'x', key = 'x' }, - { heading = 'y', key = 'y' }, - { heading = 'z', key = 'z' }, - }, - }, - button1 = UI.Button { - x = 3, y = 7, - text = 'Set home', - event = 'gps_set', - }, - button2 = UI.Button { - ex = -3, y = 7, width = 7, - text = 'Clear', - event = 'gps_clear', - }, - }, - }) - function systemPage.tabs.gpsTab:eventHandler(event) - if event.type == 'gps_set' then - systemPage.notification:info('Determining location', 10) - systemPage:sync() - if Home.set() then - Config.load('gps', values) - self.grid:setValues(values.home or { }) - self.grid:draw() - systemPage.notification:success('Location set') - else - systemPage.notification:error('Unable to determine location') - end - return true - elseif event.type == 'gps_clear' then - fs.delete('usr/config/gps') - self.grid:setValues({ }) - self.grid:draw() - return true - end - end + systemPage.tabs:add({ + gpsTab = UI.Window { + tabTitle = 'GPS', + labelText = UI.Text { + x = 3, y = 2, + value = 'On restart, return to this location' + }, + grid = UI.Grid { + x = 3, ex = -3, y = 4, + height = 2, + values = values, + inactive = true, + columns = { + { heading = 'x', key = 'x' }, + { heading = 'y', key = 'y' }, + { heading = 'z', key = 'z' }, + }, + }, + button1 = UI.Button { + x = 3, y = 7, + text = 'Set home', + event = 'gps_set', + }, + button2 = UI.Button { + ex = -3, y = 7, width = 7, + text = 'Clear', + event = 'gps_clear', + }, + }, + }) + function systemPage.tabs.gpsTab:eventHandler(event) + if event.type == 'gps_set' then + systemPage.notification:info('Determining location', 10) + systemPage:sync() + if Home.set() then + Config.load('gps', values) + self.grid:setValues(values.home or { }) + self.grid:draw() + systemPage.notification:success('Location set') + else + systemPage.notification:error('Unable to determine location') + end + return true + elseif event.type == 'gps_clear' then + fs.delete('usr/config/gps') + self.grid:setValues({ }) + self.grid:draw() + return true + end + end end if settings then - local values = { } - for _,v in pairs(settings.getNames()) do - table.insert(values, { - name = v, - value = not not settings.get(v), - }) - end + local values = { } + for _,v in pairs(settings.getNames()) do + local value = settings.get(v) + if not value then + value = false + end + table.insert(values, { + name = v, + value = value, + }) + end - systemPage.tabs:add({ - settingsTab = UI.Window { - tabTitle = 'Settings', - grid = UI.Grid { - y = 1, - values = values, - autospace = true, - sortColumn = 'name', - columns = { - { heading = 'Setting', key = 'name' }, - { heading = 'Value', key = 'value' }, - }, - }, - } - }) - function systemPage.tabs.settingsTab:eventHandler(event) - if event.type == 'grid_select' then - event.selected.value = not event.selected.value - settings.set(event.selected.name, event.selected.value) - settings.save('.settings') - self.grid:draw() - return true - end - end + systemPage.tabs:add({ + settingsTab = UI.Window { + tabTitle = 'Settings', + grid = UI.Grid { + y = 1, + values = values, + autospace = true, + sortColumn = 'name', + columns = { + { heading = 'Setting', key = 'name' }, + { heading = 'Value', key = 'value' }, + }, + }, + } + }) + function systemPage.tabs.settingsTab:eventHandler(event) + if event.type == 'grid_select' then + if not event.selected.value or type(event.selected.value) == 'boolean' then + event.selected.value = not event.selected.value + end + settings.set(event.selected.name, event.selected.value) + settings.save('.settings') + self.grid:draw() + return true + end + end end function systemPage.tabs.pathTab.grid:draw() - self.values = { } - for _,v in ipairs(Util.split(env.path, '(.-):')) do - table.insert(self.values, { value = v }) - end - self:update() - UI.Grid.draw(self) + self.values = { } + for _,v in ipairs(Util.split(env.path, '(.-):')) do + table.insert(self.values, { value = v }) + end + self:update() + UI.Grid.draw(self) end function systemPage.tabs.pathTab:eventHandler(event) - if event.type == 'update_path' then - env.path = self.entry.value - self.grid:setIndex(self.grid:getIndex()) - self.grid:draw() - Config.update('shell', env) - systemPage.notification:success('reboot to take effect') - return true - end + if event.type == 'update_path' then + env.path = self.entry.value + self.grid:setIndex(self.grid:getIndex()) + self.grid:draw() + Config.update('shell', env) + systemPage.notification:success('reboot to take effect') + return true + end end function systemPage.tabs.aliasTab.grid:draw() - self.values = { } - for k,v in pairs(env.aliases) do - table.insert(self.values, { alias = k, path = v }) - end - self:update() - UI.Grid.draw(self) + self.values = { } + for k,v in pairs(env.aliases) do + table.insert(self.values, { alias = k, path = v }) + end + self:update() + UI.Grid.draw(self) end function systemPage.tabs.aliasTab:eventHandler(event) - if event.type == 'delete_alias' then - env.aliases[self.grid:getSelected().alias] = nil - self.grid:setIndex(self.grid:getIndex()) - self.grid:draw() - Config.update('shell', env) - systemPage.notification:success('reboot to take effect') - return true + if event.type == 'delete_alias' then + env.aliases[self.grid:getSelected().alias] = nil + self.grid:setIndex(self.grid:getIndex()) + self.grid:draw() + Config.update('shell', env) + systemPage.notification:success('reboot to take effect') + return true - elseif event.type == 'new_alias' then - env.aliases[self.alias.value] = self.path.value - self.alias:reset() - self.path:reset() - self:draw() - self:setFocus(self.alias) - Config.update('shell', env) - systemPage.notification:success('reboot to take effect') - return true - end + elseif event.type == 'new_alias' then + env.aliases[self.alias.value] = self.path.value + self.alias:reset() + self.path:reset() + self:draw() + self:setFocus(self.alias) + Config.update('shell', env) + systemPage.notification:success('reboot to take effect') + return true + end end function systemPage.tabs.passwordTab:eventHandler(event) - if event.type == 'update_password' then - if #self.newPass.value == 0 then - systemPage.notification:error('Invalid password') - elseif Security.getPassword() and not Security.verifyPassword(SHA1.sha1(self.oldPass.value)) then - systemPage.notification:error('Passwords do not match') - else - Security.updatePassword(SHA1.sha1(self.newPass.value)) - self.oldPass.inactive = false - systemPage.notification:success('Password updated') - end + if event.type == 'update_password' then + if #self.newPass.value == 0 then + systemPage.notification:error('Invalid password') + elseif Security.getPassword() and not Security.verifyPassword(SHA1.sha1(self.oldPass.value)) then + systemPage.notification:error('Passwords do not match') + else + Security.updatePassword(SHA1.sha1(self.newPass.value)) + self.oldPass.inactive = false + systemPage.notification:success('Password updated') + end - return true - end + return true + end end function systemPage.tabs.infoTab:eventHandler(event) - if event.type == 'update_label' then - os.setComputerLabel(self.label.value) - systemPage.notification:success('Label updated') - return true - end + if event.type == 'update_label' then + os.setComputerLabel(self.label.value) + systemPage.notification:success('Label updated') + return true + end end function systemPage:eventHandler(event) - if event.type == 'quit' then - UI:exitPullEvents() - elseif event.type == 'tab_activate' then - event.activated:focusFirst() - else - return UI.Page.eventHandler(self, event) - end - return true + if event.type == 'quit' then + UI:exitPullEvents() + elseif event.type == 'tab_activate' then + event.activated:focusFirst() + else + return UI.Page.eventHandler(self, event) + end + return true end UI:setPage(systemPage) diff --git a/sys/apps/Tasks.lua b/sys/apps/Tasks.lua index d375832..5bcd043 100644 --- a/sys/apps/Tasks.lua +++ b/sys/apps/Tasks.lua @@ -10,66 +10,62 @@ local multishell = _ENV.multishell UI:configure('Tasks', ...) local page = UI.Page { - menuBar = UI.MenuBar { - buttons = { - { text = 'Activate', event = 'activate' }, - { text = 'Terminate', event = 'terminate' }, - }, - }, - grid = UI.ScrollingGrid { - y = 2, - columns = { - { heading = 'ID', key = 'uid', width = 3 }, - { heading = 'Title', key = 'title' }, - { heading = 'Status', key = 'status' }, - { heading = 'Time', key = 'timestamp' }, - }, - values = kernel.routines, - sortColumn = 'uid', - autospace = true, - }, - accelerators = { - q = 'quit', - space = 'activate', - t = 'terminate', - }, + menuBar = UI.MenuBar { + buttons = { + { text = 'Activate', event = 'activate' }, + { text = 'Terminate', event = 'terminate' }, + }, + }, + grid = UI.ScrollingGrid { + y = 2, + columns = { + { heading = 'ID', key = 'uid', width = 3 }, + { heading = 'Title', key = 'title' }, + { heading = 'Status', key = 'status' }, + { heading = 'Time', key = 'timestamp' }, + }, + values = kernel.routines, + sortColumn = 'uid', + autospace = true, + }, + accelerators = { + q = 'quit', + space = 'activate', + t = 'terminate', + }, } function page:eventHandler(event) - local t = self.grid:getSelected() - if t then - if event.type == 'activate' or event.type == 'grid_select' then - multishell.setFocus(t.uid) - elseif event.type == 'terminate' then - multishell.terminate(t.uid) - end - end - if event.type == 'quit' then - Event.exitPullEvents() - end - UI.Page.eventHandler(self, event) + local t = self.grid:getSelected() + if t then + if event.type == 'activate' or event.type == 'grid_select' then + multishell.setFocus(t.uid) + elseif event.type == 'terminate' then + multishell.terminate(t.uid) + end + end + if event.type == 'quit' then + Event.exitPullEvents() + end + UI.Page.eventHandler(self, event) end function page.grid:getDisplayValues(row) - row = Util.shallowCopy(row) - local elapsed = os.clock()-row.timestamp - if elapsed < 60 then - row.timestamp = string.format("%ds", math.floor(elapsed)) - else - row.timestamp = string.format("%sm", math.floor(elapsed/6)/10) - end - if row.isDead then - row.status = 'error' - else - row.status = coroutine.status(row.co) - end - return row + row = Util.shallowCopy(row) + local elapsed = os.clock()-row.timestamp + if elapsed < 60 then + row.timestamp = string.format("%ds", math.floor(elapsed)) + else + row.timestamp = string.format("%sm", math.floor(elapsed/6)/10) + end + row.status = row.isDead and 'error' or coroutine.status(row.co) + return row end Event.onInterval(1, function() - page.grid:update() - page.grid:draw() - page:sync() + page.grid:update() + page.grid:draw() + page:sync() end) UI:setPage(page) diff --git a/sys/apps/netdaemon.lua b/sys/apps/netdaemon.lua index 5449578..ae51a19 100644 --- a/sys/apps/netdaemon.lua +++ b/sys/apps/netdaemon.lua @@ -1,4 +1,4 @@ -_G.requireInjector() +_G.requireInjector(_ENV) local Event = require('event') local Util = require('util') diff --git a/sys/apps/password.lua b/sys/apps/password.lua index f6f3ef1..eb3c5b0 100644 --- a/sys/apps/password.lua +++ b/sys/apps/password.lua @@ -1,4 +1,4 @@ -_G.requireInjector() +_G.requireInjector(_ENV) local Security = require('security') local SHA1 = require('sha1') @@ -7,6 +7,6 @@ local Terminal = require('terminal') local password = Terminal.readPassword('Enter new password: ') if password then - Security.updatePassword(SHA1.sha1(password)) - print('Password updated') + Security.updatePassword(SHA1.sha1(password)) + print('Password updated') end diff --git a/sys/apps/shell b/sys/apps/shell index a2bc0be..20b8ef5 100644 --- a/sys/apps/shell +++ b/sys/apps/shell @@ -7,11 +7,11 @@ local shell = _ENV.shell local sandboxEnv = setmetatable({ }, { __index = _G }) for k,v in pairs(_ENV) do - sandboxEnv[k] = v + sandboxEnv[k] = v end sandboxEnv.shell = shell -_G.requireInjector() +_G.requireInjector(_ENV) local Util = require('util') @@ -24,79 +24,79 @@ local bExit = false local tProgramStack = {} local function tokenise( ... ) - local sLine = table.concat( { ... }, " " ) - local tWords = {} - local bQuoted = false - for match in string.gmatch( sLine .. "\"", "(.-)\"" ) do - if bQuoted then - table.insert( tWords, match ) - else - for m in string.gmatch( match, "[^ \t]+" ) do - table.insert( tWords, m ) - end - end - bQuoted = not bQuoted - end + local sLine = table.concat( { ... }, " " ) + local tWords = {} + local bQuoted = false + for match in string.gmatch( sLine .. "\"", "(.-)\"" ) do + if bQuoted then + table.insert( tWords, match ) + else + for m in string.gmatch( match, "[^ \t]+" ) do + table.insert( tWords, m ) + end + end + bQuoted = not bQuoted + end - return tWords + return tWords end local function run(env, ...) - local args = tokenise(...) - local command = table.remove(args, 1) or error('No such program') - local isUrl = not not command:match("^(https?:)") + local args = tokenise(...) + local command = table.remove(args, 1) or error('No such program') + local isUrl = not not command:match("^(https?:)") - local path, loadFn - if isUrl then - path = command - loadFn = Util.loadUrl - else - path = shell.resolveProgram(command) or error('No such program') - loadFn = loadfile - end + local path, loadFn + if isUrl then + path = command + loadFn = Util.loadUrl + else + path = shell.resolveProgram(command) or error('No such program') + loadFn = loadfile + end - local fn, err = loadFn(path, env) - if not fn then - error(err) - end + local fn, err = loadFn(path, env) + if not fn then + error(err) + end - if _ENV.multishell then - _ENV.multishell.setTitle(_ENV.multishell.getCurrent(), fs.getName(path):match('([^%.]+)')) - end + if _ENV.multishell then + _ENV.multishell.setTitle(_ENV.multishell.getCurrent(), fs.getName(path):match('([^%.]+)')) + end - if isUrl then - tProgramStack[#tProgramStack + 1] = path:match("^https?://([^/:]+:?[0-9]*/?.*)$") - else - tProgramStack[#tProgramStack + 1] = path - end + if isUrl then + tProgramStack[#tProgramStack + 1] = path:match("^https?://([^/:]+:?[0-9]*/?.*)$") + else + tProgramStack[#tProgramStack + 1] = path + end - local r = { fn(table.unpack(args)) } + local r = { fn(table.unpack(args)) } - tProgramStack[#tProgramStack] = nil + tProgramStack[#tProgramStack] = nil - return table.unpack(r) + return table.unpack(r) end -- Install shell API function shell.run(...) - local oldTitle + local oldTitle - if _ENV.multishell then - oldTitle = _ENV.multishell.getTitle(_ENV.multishell.getCurrent()) - end + if _ENV.multishell then + oldTitle = _ENV.multishell.getTitle(_ENV.multishell.getCurrent()) + end - local env = setmetatable(Util.shallowCopy(sandboxEnv), { __index = _G }) - local r = { pcall(run, env, ...) } + local env = setmetatable(Util.shallowCopy(sandboxEnv), { __index = _G }) + local r = { pcall(run, env, ...) } - if _ENV.multishell then - _ENV.multishell.setTitle(_ENV.multishell.getCurrent(), oldTitle or 'shell') - end + if _ENV.multishell then + _ENV.multishell.setTitle(_ENV.multishell.getCurrent(), oldTitle or 'shell') + end - return table.unpack(r) + return table.unpack(r) end function shell.exit() - bExit = true + bExit = true end function shell.dir() return DIR end @@ -105,258 +105,261 @@ function shell.path() return PATH end function shell.setPath(p) PATH = p end function shell.resolve( _sPath ) - local sStartChar = string.sub( _sPath, 1, 1 ) - if sStartChar == "/" or sStartChar == "\\" then - return fs.combine( "", _sPath ) - else - return fs.combine(DIR, _sPath ) - end + local sStartChar = string.sub( _sPath, 1, 1 ) + if sStartChar == "/" or sStartChar == "\\" then + return fs.combine( "", _sPath ) + else + return fs.combine(DIR, _sPath ) + end end function shell.resolveProgram( _sCommand ) - if tAliases[_sCommand] ~= nil then - _sCommand = tAliases[_sCommand] - end + if tAliases[_sCommand] ~= nil then + _sCommand = tAliases[_sCommand] + end - local path = shell.resolve(_sCommand) - if fs.exists(path) and not fs.isDir(path) then - return path - end - if fs.exists(path .. '.lua') then - return path .. '.lua' - end + if _sCommand:match("^(https?:)") then + return _sCommand + end - -- If the path is a global path, use it directly - local sStartChar = string.sub( _sCommand, 1, 1 ) - if sStartChar == "/" or sStartChar == "\\" then - local sPath = fs.combine( "", _sCommand ) - if fs.exists( sPath ) and not fs.isDir( sPath ) then - return sPath - end - return nil - end + local path = shell.resolve(_sCommand) + if fs.exists(path) and not fs.isDir(path) then + return path + end + if fs.exists(path .. '.lua') then + return path .. '.lua' + end - -- Otherwise, look on the path variable - for sPath in string.gmatch(PATH or '', "[^:]+") do - sPath = fs.combine(sPath, _sCommand ) - if fs.exists( sPath ) and not fs.isDir( sPath ) then - return sPath - end - if fs.exists(sPath .. '.lua') then - return sPath .. '.lua' - end - end - -- Not found - return nil + -- If the path is a global path, use it directly + local sStartChar = string.sub( _sCommand, 1, 1 ) + if sStartChar == "/" or sStartChar == "\\" then + local sPath = fs.combine( "", _sCommand ) + if fs.exists( sPath ) and not fs.isDir( sPath ) then + return sPath + end + return nil + end + + -- Otherwise, look on the path variable + for sPath in string.gmatch(PATH or '', "[^:]+") do + sPath = fs.combine(sPath, _sCommand ) + if fs.exists( sPath ) and not fs.isDir( sPath ) then + return sPath + end + if fs.exists(sPath .. '.lua') then + return sPath .. '.lua' + end + end + -- Not found + return nil end function shell.programs( _bIncludeHidden ) - local tItems = {} + local tItems = {} - -- Add programs from the path - for sPath in string.gmatch(PATH, "[^:]+") do - sPath = shell.resolve(sPath) - if fs.isDir( sPath ) then - local tList = fs.list( sPath ) - for _,sFile in pairs( tList ) do - if not fs.isDir( fs.combine( sPath, sFile ) ) and - (_bIncludeHidden or string.sub( sFile, 1, 1 ) ~= ".") then - tItems[ sFile ] = true - end - end - end - end + -- Add programs from the path + for sPath in string.gmatch(PATH, "[^:]+") do + sPath = shell.resolve(sPath) + if fs.isDir( sPath ) then + local tList = fs.list( sPath ) + for _,sFile in pairs( tList ) do + if not fs.isDir( fs.combine( sPath, sFile ) ) and + (_bIncludeHidden or string.sub( sFile, 1, 1 ) ~= ".") then + tItems[ sFile ] = true + end + end + end + end - -- Sort and return - local tItemList = {} - for sItem in pairs( tItems ) do - table.insert( tItemList, sItem ) - end - table.sort( tItemList ) - return tItemList + -- Sort and return + local tItemList = {} + for sItem in pairs( tItems ) do + table.insert( tItemList, sItem ) + end + table.sort( tItemList ) + return tItemList end local function completeProgram( sLine ) - if #sLine > 0 and string.sub( sLine, 1, 1 ) == "/" then - -- Add programs from the root - return fs.complete( sLine, "", true, false ) - else - local tResults = {} - local tSeen = {} + if #sLine > 0 and string.sub( sLine, 1, 1 ) == "/" then + -- Add programs from the root + return fs.complete( sLine, "", true, false ) + else + local tResults = {} + local tSeen = {} - -- Add aliases - for sAlias in pairs( tAliases ) do - if #sAlias > #sLine and string.sub( sAlias, 1, #sLine ) == sLine then - local sResult = string.sub( sAlias, #sLine + 1 ) - if not tSeen[ sResult ] then - table.insert( tResults, sResult ) - tSeen[ sResult ] = true - end - end - end + -- Add aliases + for sAlias in pairs( tAliases ) do + if #sAlias > #sLine and string.sub( sAlias, 1, #sLine ) == sLine then + local sResult = string.sub( sAlias, #sLine + 1 ) + if not tSeen[ sResult ] then + table.insert( tResults, sResult ) + tSeen[ sResult ] = true + end + end + end - -- Add programs from the path - local tPrograms = shell.programs() - for n=1,#tPrograms do - local sProgram = tPrograms[n] - if #sProgram > #sLine and string.sub( sProgram, 1, #sLine ) == sLine then - local sResult = string.sub( sProgram, #sLine + 1 ) - if not tSeen[ sResult ] then - table.insert( tResults, sResult ) - tSeen[ sResult ] = true - end - end - end + -- Add programs from the path + local tPrograms = shell.programs() + for n=1,#tPrograms do + local sProgram = tPrograms[n] + if #sProgram > #sLine and string.sub( sProgram, 1, #sLine ) == sLine then + local sResult = string.sub( sProgram, #sLine + 1 ) + if not tSeen[ sResult ] then + table.insert( tResults, sResult ) + tSeen[ sResult ] = true + end + end + end - -- Sort and return - table.sort( tResults ) - return tResults - end + -- Sort and return + table.sort( tResults ) + return tResults + end end local function completeProgramArgument( sProgram, nArgument, sPart, tPreviousParts ) - local tInfo = tCompletionInfo[ sProgram ] - if tInfo then - return tInfo.fnComplete( shell, nArgument, sPart, tPreviousParts ) - end - return nil + local tInfo = tCompletionInfo[ sProgram ] + if tInfo then + return tInfo.fnComplete( shell, nArgument, sPart, tPreviousParts ) + end + return nil end function shell.complete(sLine) - if #sLine > 0 then - local tWords = tokenise( sLine ) - local nIndex = #tWords - if string.sub( sLine, #sLine, #sLine ) == " " then - nIndex = nIndex + 1 - end - if nIndex == 1 then - local sBit = tWords[1] or "" - local sPath = shell.resolveProgram( sBit ) - if tCompletionInfo[ sPath ] then - return { " " } - else - local tResults = completeProgram( sBit ) - for n=1,#tResults do - local sResult = tResults[n] - local cPath = shell.resolveProgram( sBit .. sResult ) - if tCompletionInfo[ cPath ] then - tResults[n] = sResult .. " " - end - end - return tResults - end + if #sLine > 0 then + local tWords = tokenise( sLine ) + local nIndex = #tWords + if string.sub( sLine, #sLine, #sLine ) == " " then + nIndex = nIndex + 1 + end + if nIndex == 1 then + local sBit = tWords[1] or "" + local sPath = shell.resolveProgram( sBit ) + if tCompletionInfo[ sPath ] then + return { " " } + else + local tResults = completeProgram( sBit ) + for n=1,#tResults do + local sResult = tResults[n] + local cPath = shell.resolveProgram( sBit .. sResult ) + if tCompletionInfo[ cPath ] then + tResults[n] = sResult .. " " + end + end + return tResults + end - elseif nIndex > 1 then - local sPath = shell.resolveProgram( tWords[1] ) - local sPart = tWords[nIndex] or "" - local tPreviousParts = tWords - tPreviousParts[nIndex] = nil - return completeProgramArgument( sPath , nIndex - 1, sPart, tPreviousParts ) - end - end + elseif nIndex > 1 then + local sPath = shell.resolveProgram( tWords[1] ) + local sPart = tWords[nIndex] or "" + local tPreviousParts = tWords + tPreviousParts[nIndex] = nil + return completeProgramArgument( sPath , nIndex - 1, sPart, tPreviousParts ) + end + end end function shell.completeProgram( sProgram ) - return completeProgram( sProgram ) + return completeProgram( sProgram ) end function shell.setCompletionFunction(sProgram, fnComplete) - tCompletionInfo[sProgram] = { fnComplete = fnComplete } + tCompletionInfo[sProgram] = { fnComplete = fnComplete } end function shell.getCompletionInfo() - return tCompletionInfo + return tCompletionInfo end function shell.getRunningProgram() - return tProgramStack[#tProgramStack] + return tProgramStack[#tProgramStack] end function shell.setEnv(name, value) - _ENV[name] = value - sandboxEnv[name] = value + _ENV[name] = value + sandboxEnv[name] = value end function shell.getEnv() - return sandboxEnv + return sandboxEnv end function shell.setAlias( _sCommand, _sProgram ) - tAliases[_sCommand] = _sProgram + tAliases[_sCommand] = _sProgram end function shell.clearAlias( _sCommand ) - tAliases[_sCommand] = nil + tAliases[_sCommand] = nil end function shell.aliases() - local tCopy = {} - for sAlias, sCommand in pairs(tAliases) do - tCopy[sAlias] = sCommand - end - return tCopy + local tCopy = {} + for sAlias, sCommand in pairs(tAliases) do + tCopy[sAlias] = sCommand + end + return tCopy end function shell.newTab(tabInfo, ...) - local args = tokenise(...) - local path = table.remove(args, 1) - path = shell.resolveProgram(path) + local args = tokenise(...) + local path = table.remove(args, 1) + path = shell.resolveProgram(path) - if path then - tabInfo.path = path - tabInfo.env = Util.shallowCopy(sandboxEnv) - tabInfo.args = args - tabInfo.title = fs.getName(path):match('([^%.]+)') + if path then + tabInfo.path = path + tabInfo.env = Util.shallowCopy(sandboxEnv) + tabInfo.args = args + tabInfo.title = fs.getName(path):match('([^%.]+)') - if path ~= 'sys/apps/shell' then - table.insert(tabInfo.args, 1, tabInfo.path) - tabInfo.path = 'sys/apps/shell' - end - return _ENV.multishell.openTab(tabInfo) - end - return nil, 'No such program' + if path ~= 'sys/apps/shell' then + table.insert(tabInfo.args, 1, tabInfo.path) + tabInfo.path = 'sys/apps/shell' + end + return _ENV.multishell.openTab(tabInfo) + end + return nil, 'No such program' end function shell.openTab( ... ) - -- needs to use multishell.launch .. so we can run with stock multishell - local tWords = tokenise( ... ) - local sCommand = tWords[1] - if sCommand then - local sPath = shell.resolveProgram(sCommand) - if sPath == "sys/apps/shell" then - return _ENV.multishell.launch(Util.shallowCopy(sandboxEnv), sPath, table.unpack(tWords, 2)) - elseif sPath ~= nil then - return _ENV.multishell.launch(Util.shallowCopy(sandboxEnv), "sys/apps/shell", sCommand, table.unpack(tWords, 2)) - else - return false, "No such program" - end - end + -- needs to use multishell.launch .. so we can run with stock multishell + local tWords = tokenise( ... ) + local sCommand = tWords[1] + if sCommand then + local sPath = shell.resolveProgram(sCommand) + if sPath == "sys/apps/shell" then + return _ENV.multishell.launch(Util.shallowCopy(sandboxEnv), sPath, table.unpack(tWords, 2)) + else + return _ENV.multishell.launch(Util.shallowCopy(sandboxEnv), "sys/apps/shell", sCommand, table.unpack(tWords, 2)) + end + end end function shell.openForegroundTab( ... ) - return shell.newTab({ focused = true }, ...) + return shell.newTab({ focused = true }, ...) end function shell.openHiddenTab( ... ) - return shell.newTab({ hidden = true }, ...) + return shell.newTab({ hidden = true }, ...) end function shell.switchTab(tabId) - _ENV.multishell.setFocus(tabId) + _ENV.multishell.setFocus(tabId) end local tArgs = { ... } if #tArgs > 0 then - local env = setmetatable(Util.shallowCopy(sandboxEnv), { __index = _G }) - return run(env, ...) + local env = setmetatable(Util.shallowCopy(sandboxEnv), { __index = _G }) + return run(env, ...) end -local Config = require('config') -local History = require('history') +local Config = require('config') +local Entry = require('entry') +local History = require('history') +local Input = require('input') local Terminal = require('terminal') local colors = _G.colors -local keys = _G.keys local os = _G.os local term = _G.term local textutils = _G.textutils @@ -366,328 +369,281 @@ Terminal.scrollable(terminal, 100) terminal.noAutoScroll = true local config = { - standard = { - textColor = colors.white, - commandTextColor = colors.lightGray, - directoryTextColor = colors.gray, - directoryBackgroundColor = colors.black, - promptTextColor = colors.gray, - promptBackgroundColor = colors.black, - directoryColor = colors.gray, - }, - color = { - textColor = colors.white, - commandTextColor = colors.yellow, - directoryTextColor = colors.orange, - directoryBackgroundColor = colors.black, - promptTextColor = colors.blue, - promptBackgroundColor = colors.black, - directoryColor = colors.green, - }, - displayDirectory = true, + standard = { + textColor = colors.white, + commandTextColor = colors.lightGray, + directoryTextColor = colors.gray, + directoryBackgroundColor = colors.black, + promptTextColor = colors.gray, + promptBackgroundColor = colors.black, + directoryColor = colors.gray, + }, + color = { + textColor = colors.white, + commandTextColor = colors.yellow, + directoryTextColor = colors.orange, + directoryBackgroundColor = colors.black, + promptTextColor = colors.blue, + promptBackgroundColor = colors.black, + directoryColor = colors.green, + }, + displayDirectory = true, } Config.load('shellprompt', config) local _colors = config.standard if term.isColor() then - _colors = config.color + _colors = config.color end local function autocompleteArgument(program, words) - local word = '' - if #words > 1 then - word = words[#words] - end + local word = '' + if #words > 1 then + word = words[#words] + end - local tInfo = tCompletionInfo[program] - return tInfo.fnComplete(shell, #words - 1, word, words) + local tInfo = tCompletionInfo[program] + return tInfo.fnComplete(shell, #words - 1, word, words) end local function autocompleteAnything(line, words) - local results = shell.complete(line) + local results = shell.complete(line) - if results and #results == 0 and #words == 1 then - results = nil - end - if not results then - results = fs.complete(words[#words] or '', shell.dir(), true, false) - end + if results and #results == 0 and #words == 1 then + results = nil + end + if not results then + results = fs.complete(words[#words] or '', shell.dir(), true, false) + end - return results + return results end local function autocomplete(line) - local words = { } - for word in line:gmatch("%S+") do - table.insert(words, word) - end - if line:match(' $') then - table.insert(words, '') - end - if #words == 0 then - words = { '' } - end + local words = { } + for word in line:gmatch("%S+") do + table.insert(words, word) + end + if line:match(' $') then + table.insert(words, '') + end + if #words == 0 then + words = { '' } + end - local results + local results - local program = shell.resolveProgram(words[1]) - if tCompletionInfo[program] then - results = autocompleteArgument(program, words) or { } - else - results = autocompleteAnything(line, words) or { } - end + local program = shell.resolveProgram(words[1]) + if tCompletionInfo[program] then + results = autocompleteArgument(program, words) or { } + else + results = autocompleteAnything(line, words) or { } + end - Util.filterInplace(results, function(f) - return not Util.key(results, f .. '/') - end) - local w = words[#words] or '' - for k,arg in pairs(results) do - results[k] = w .. arg - end + Util.filterInplace(results, function(f) + return not Util.key(results, f .. '/') + end) + local w = words[#words] or '' + for k,arg in pairs(results) do + results[k] = w .. arg + end - if #results == 1 then - words[#words] = results[1] - return table.concat(words, ' ') - elseif #results > 1 then + if #results == 1 then + words[#words] = results[1] + return table.concat(words, ' ') + elseif #results > 1 then - local function someComplete() - -- ugly (complete as much as possible) - local word = words[#words] or '' - local i = #word + 1 - while true do - local ch - for _,f in ipairs(results) do - if #f < i then - words[#words] = string.sub(f, 1, i - 1) - return table.concat(words, ' ') - end - if not ch then - ch = string.sub(f, i, i) - elseif string.sub(f, i, i) ~= ch then - if i == #word + 1 then - return - end - words[#words] = string.sub(f, 1, i - 1) - return table.concat(words, ' ') - end - end - i = i + 1 - end - end + local function someComplete() + -- ugly (complete as much as possible) + local word = words[#words] or '' + local i = #word + 1 + while true do + local ch + for _,f in ipairs(results) do + if #f < i then + words[#words] = string.sub(f, 1, i - 1) + return table.concat(words, ' ') + end + if not ch then + ch = string.sub(f, i, i) + elseif string.sub(f, i, i) ~= ch then + if i == #word + 1 then + return + end + words[#words] = string.sub(f, 1, i - 1) + return table.concat(words, ' ') + end + end + i = i + 1 + end + end - local t = someComplete() - if t then - return t - end + local t = someComplete() + if t then + return t + end - print() + print() - local word = words[#words] or '' - local prefix = word:match("(.*/)") or '' - if #prefix > 0 then - for _,f in ipairs(results) do - if f:match("^" .. prefix) ~= prefix then - prefix = '' - break - end - end - end + local word = words[#words] or '' + local prefix = word:match("(.*/)") or '' + if #prefix > 0 then + for _,f in ipairs(results) do + if f:match("^" .. prefix) ~= prefix then + prefix = '' + break + end + end + end - local tDirs, tFiles = { }, { } - for _,f in ipairs(results) do - if fs.isDir(shell.resolve(f)) then - f = f:gsub(prefix, '', 1) - table.insert(tDirs, f) - else - f = f:gsub(prefix, '', 1) - table.insert(tFiles, f) - end - end - table.sort(tDirs) - table.sort(tFiles) + local tDirs, tFiles = { }, { } + for _,f in ipairs(results) do + if fs.isDir(shell.resolve(f)) then + f = f:gsub(prefix, '', 1) + table.insert(tDirs, f) + else + f = f:gsub(prefix, '', 1) + table.insert(tFiles, f) + end + end + table.sort(tDirs) + table.sort(tFiles) - if #tDirs > 0 and #tDirs < #tFiles then - local tw = term.getSize() - local nMaxLen = tw / 8 - for _,sItem in pairs(results) do - nMaxLen = math.max(string.len(sItem) + 1, nMaxLen) - end - local nCols = math.floor(w / nMaxLen) - if #tDirs < nCols then - for _ = #tDirs + 1, nCols do - table.insert(tDirs, '') - end - end - end + if #tDirs > 0 and #tDirs < #tFiles then + local tw = term.getSize() + local nMaxLen = tw / 8 + for _,sItem in pairs(results) do + nMaxLen = math.max(string.len(sItem) + 1, nMaxLen) + end + local nCols = math.floor(w / nMaxLen) + if #tDirs < nCols then + for _ = #tDirs + 1, nCols do + table.insert(tDirs, '') + end + end + end - if #tDirs > 0 then - textutils.tabulate(_colors.directoryColor, tDirs, colors.white, tFiles) - else - textutils.tabulate(colors.white, tFiles) - end + if #tDirs > 0 then + textutils.tabulate(_colors.directoryColor, tDirs, colors.white, tFiles) + else + textutils.tabulate(colors.white, tFiles) + end - term.setTextColour(_colors.promptTextColor) - term.setBackgroundColor(_colors.promptBackgroundColor) - term.write("$ " ) + term.setTextColour(_colors.promptTextColor) + term.setBackgroundColor(_colors.promptBackgroundColor) + term.write("$ " ) - term.setTextColour(_colors.commandTextColor) - term.setBackgroundColor(colors.black) - return line - end + term.setTextColour(_colors.commandTextColor) + term.setBackgroundColor(colors.black) + return line + end end local function shellRead(history) - term.setCursorBlink( true ) + local lastLen = 0 + local entry = Entry({ + width = term.getSize() - 3 + }) - local sLine = "" - local nPos = 0 + history:reset() + term.setCursorBlink(true) - local w = term.getSize() - local sx = term.getCursorPos() + local function redraw() + local _,cy = term.getCursorPos() + term.setCursorPos(3, cy) + local filler = #entry.value < lastLen + and string.rep(' ', lastLen - #entry.value) + or '' + local str = string.sub(entry.value, entry.scroll + 1) + term.write(string.sub(str, 1, entry.width) .. filler) + term.setCursorPos(3 + entry.pos - entry.scroll, cy) + lastLen = #entry.value + end - history:reset() + while true do + local event, p1, p2, p3 = os.pullEventRaw() - local function redraw( sReplace ) - local nScroll = 0 - if sx + nPos >= w then - nScroll = (sx + nPos) - w - end + local ie = Input:translate(event, p1, p2, p3) + if ie then + if ie.code == 'scroll_up' then + terminal.scrollUp() - local _,cy = term.getCursorPos() - term.setCursorPos( sx, cy ) - if sReplace then - term.write( string.rep( sReplace, math.max( string.len(sLine) - nScroll, 0 ) ) ) - else - term.write( string.sub( sLine, nScroll + 1 ) ) - end - term.setCursorPos( sx + nPos - nScroll, cy ) - end + elseif ie.code == 'scroll_down' then + terminal.scrollDown() - while true do - local sEvent, param = os.pullEventRaw() + elseif ie.code == 'terminate' then + bExit = true + break - if sEvent == "char" then - sLine = string.sub( sLine, 1, nPos ) .. param .. string.sub( sLine, nPos + 1 ) - nPos = nPos + 1 - redraw() - elseif sEvent == "paste" then - sLine = string.sub( sLine, 1, nPos ) .. param .. string.sub( sLine, nPos + 1 ) - nPos = nPos + string.len( param ) - redraw() - elseif sEvent == 'mouse_click' and param == 2 then - redraw(string.rep(' ', #sLine)) - sLine = '' - nPos = 0 - redraw() - elseif sEvent == 'mouse_scroll' then - if param == -1 then - terminal.scrollUp() - else - terminal.scrollDown() - end - elseif sEvent == 'terminate' then - bExit = true - break - elseif sEvent == "key" then - if param == keys.enter then - -- Enter - break - elseif param == keys.tab then - if nPos == #sLine then - local cline = autocomplete(sLine) - if cline then - sLine = cline - nPos = #sLine - redraw() - end - end - elseif param == keys.left then - if nPos > 0 then - nPos = nPos - 1 - redraw() - end - elseif param == keys.right then - if nPos < string.len(sLine) then - redraw(" ") - nPos = nPos + 1 - redraw() - end - elseif param == keys.up or param == keys.down then - redraw(" ") - if param == keys.up then - sLine = history:back() - else - sLine = history:forward() - end - if sLine then - nPos = string.len(sLine) - else - sLine = "" - nPos = 0 - end - redraw() - elseif param == keys.backspace then - if nPos > 0 then - redraw(" ") - sLine = string.sub( sLine, 1, nPos - 1 ) .. string.sub( sLine, nPos + 1 ) - nPos = nPos - 1 - redraw() - end - elseif param == keys.home then - redraw(" ") - nPos = 0 - redraw() - elseif param == keys.delete then - if nPos < string.len(sLine) then - redraw(" ") - sLine = string.sub( sLine, 1, nPos ) .. string.sub( sLine, nPos + 2 ) - redraw() - end - elseif param == keys["end"] then - redraw(" ") - nPos = string.len(sLine) - redraw() - end - elseif sEvent == "term_resize" then - w = term.getSize() - redraw() - end - end + elseif ie.code == 'enter' then + break - local _, cy = term.getCursorPos() - term.setCursorPos( w + 1, cy ) - print() - term.setCursorBlink( false ) - return sLine + elseif ie.code == 'up' or ie.code == 'down' then + if ie.code == 'up' then + entry.value = history:back() or '' + else + entry.value = history:forward() or '' + end + entry.pos = string.len(entry.value) + entry.scroll = 0 + entry:updateScroll() + redraw() + + elseif ie.code == 'tab' then + if entry.pos == #entry.value then + local cline = autocomplete(entry.value) + if cline then + entry.value = cline + entry.pos = #entry.value + entry:updateScroll() + redraw() + end + end + + elseif entry:process(ie) then + redraw() + end + + elseif event == "term_resize" then + entry.width = term.getSize() - 3 + redraw() + end + end + + --local _, cy = term.getCursorPos() + --term.setCursorPos( w + 1, cy ) + print() + term.setCursorBlink( false ) + return entry.value end local history = History.load('usr/.shell_history', 25) while not bExit do - if config.displayDirectory then - term.setTextColour(_colors.directoryTextColor) - term.setBackgroundColor(_colors.directoryBackgroundColor) - print('==' .. os.getComputerLabel() .. ':/' .. DIR) - end - term.setTextColour(_colors.promptTextColor) - term.setBackgroundColor(_colors.promptBackgroundColor) - term.write("$ " ) - term.setTextColour(_colors.commandTextColor) - term.setBackgroundColor(colors.black) - local sLine = shellRead(history) - if bExit then -- terminated - break - end - sLine = Util.trim(sLine) - if #sLine > 0 and sLine ~= 'exit' then - history:add(sLine) - end - term.setTextColour(_colors.textColor) - if #sLine > 0 then - local result, err = shell.run(sLine) - if not result and err then - _G.printError(err) - end - end + if config.displayDirectory then + term.setTextColour(_colors.directoryTextColor) + term.setBackgroundColor(_colors.directoryBackgroundColor) + print('==' .. os.getComputerLabel() .. ':/' .. DIR) + end + term.setTextColour(_colors.promptTextColor) + term.setBackgroundColor(_colors.promptBackgroundColor) + term.write("$ " ) + term.setTextColour(_colors.commandTextColor) + term.setBackgroundColor(colors.black) + local sLine = shellRead(history) + if bExit then -- terminated + break + end + sLine = Util.trim(sLine) + if #sLine > 0 and sLine ~= 'exit' then + history:add(sLine) + end + term.setTextColour(_colors.textColor) + if #sLine > 0 then + local result, err = shell.run(sLine) + if not result and err then + _G.printError(err) + end + end end diff --git a/sys/apps/telnet.lua b/sys/apps/telnet.lua index e77bc83..0684f8f 100644 --- a/sys/apps/telnet.lua +++ b/sys/apps/telnet.lua @@ -1,4 +1,4 @@ -_G.requireInjector() +_G.requireInjector(_ENV) local Event = require('event') local Socket = require('socket') @@ -14,73 +14,73 @@ local options, args = Util.args({ ... }) local remoteId = tonumber(table.remove(args, 1) or '') if not remoteId then - print('Enter host ID') - remoteId = tonumber(read()) + print('Enter host ID') + remoteId = tonumber(read()) end if not remoteId then - error('Syntax: telnet [-title TITLE] ID [PROGRAM]') + error('Syntax: telnet [-title TITLE] ID [PROGRAM]') end if options.title and multishell then - multishell.setTitle(multishell.getCurrent(), options.title) + multishell.setTitle(multishell.getCurrent(), options.title) end local socket, msg = Socket.connect(remoteId, 23) if not socket then - error(msg) + error(msg) end local ct = Util.shallowCopy(term.current()) if not ct.isColor() then - Terminal.toGrayscale(ct) + Terminal.toGrayscale(ct) end local w, h = ct.getSize() socket:write({ - width = w, - height = h, - isColor = ct.isColor(), - program = args, - pos = { ct.getCursorPos() }, + width = w, + height = h, + isColor = ct.isColor(), + program = args, + pos = { ct.getCursorPos() }, }) Event.addRoutine(function() - while true do - local data = socket:read() - if not data then - break - end - for _,v in ipairs(data) do - ct[v.f](table.unpack(v.args)) - end - end + while true do + local data = socket:read() + if not data then + break + end + for _,v in ipairs(data) do + ct[v.f](table.unpack(v.args)) + end + end end) --ct.clear() --ct.setCursorPos(1, 1) local filter = Util.transpose { - 'char', 'paste', 'key', 'key_up', 'terminate', - 'mouse_scroll', 'mouse_click', 'mouse_drag', 'mouse_up', + 'char', 'paste', 'key', 'key_up', 'terminate', + 'mouse_scroll', 'mouse_click', 'mouse_drag', 'mouse_up', } while true do - local e = { os.pullEventRaw() } - local event = e[1] + local e = { os.pullEventRaw() } + local event = e[1] - if filter[event] then - socket:write(e) - else - Event.processEvent(e) - end + if filter[event] then + socket:write(e) + else + Event.processEvent(e) + end - if not socket.connected then + if not socket.connected then -- print() -- print('Connection lost') -- print('Press enter to exit') -- pcall(read) - break - end + break + end end diff --git a/sys/apps/trust.lua b/sys/apps/trust.lua index 54e7a29..b8cb705 100644 --- a/sys/apps/trust.lua +++ b/sys/apps/trust.lua @@ -1,4 +1,4 @@ -_G.requireInjector() +_G.requireInjector(_ENV) local Crypto = require('crypto') local Security = require('security') @@ -12,27 +12,27 @@ local remoteId local args = { ... } if #args == 1 then - remoteId = tonumber(args[1]) + remoteId = tonumber(args[1]) else - print('Enter host ID') - remoteId = tonumber(_G.read()) + print('Enter host ID') + remoteId = tonumber(_G.read()) end if not remoteId then - error('Syntax: trust ') + error('Syntax: trust ') end local password = Terminal.readPassword('Enter password: ') if not password then - error('Invalid password') + error('Invalid password') end print('connecting...') local socket, msg = Socket.connect(remoteId, 19) if not socket then - error(msg) + error(msg) end local publicKey = Security.getPublicKey() @@ -43,9 +43,9 @@ local data = socket:read(2) socket:close() if data and data.success then - print(data.msg) + print(data.msg) elseif data then - error(data.msg) + error(data.msg) else - error('No response') + error('No response') end diff --git a/sys/apps/vnc.lua b/sys/apps/vnc.lua index 748f8f0..6ea3852 100644 --- a/sys/apps/vnc.lua +++ b/sys/apps/vnc.lua @@ -1,4 +1,4 @@ -_G.requireInjector() +_G.requireInjector(_ENV) local Event = require('event') local Socket = require('socket') @@ -12,35 +12,35 @@ local term = _G.term local remoteId local args = { ... } if #args == 1 then - remoteId = tonumber(args[1]) + remoteId = tonumber(args[1]) else - print('Enter host ID') - remoteId = tonumber(_G.read()) + print('Enter host ID') + remoteId = tonumber(_G.read()) end if not remoteId then - error('Syntax: vnc ') + error('Syntax: vnc ') end if multishell then - multishell.setTitle(multishell.getCurrent(), 'VNC-' .. remoteId) + multishell.setTitle(multishell.getCurrent(), 'VNC-' .. remoteId) end print('connecting...') local socket, msg = Socket.connect(remoteId, 5900) if not socket then - error(msg) + error(msg) end local function writeTermInfo() - local w, h = term.getSize() - socket:write({ - type = 'termInfo', - width = w, - height = h, - isColor = term.isColor(), - }) + local w, h = term.getSize() + socket:write({ + type = 'termInfo', + width = w, + height = h, + isColor = term.isColor(), + }) end writeTermInfo() @@ -48,53 +48,53 @@ writeTermInfo() local ct = Util.shallowCopy(term.current()) if not ct.isColor() then - Terminal.toGrayscale(ct) + Terminal.toGrayscale(ct) end Event.addRoutine(function() - while true do - local data = socket:read() - if not data then - break - end - for _,v in ipairs(data) do - ct[v.f](unpack(v.args)) - end - end + while true do + local data = socket:read() + if not data then + break + end + for _,v in ipairs(data) do + ct[v.f](unpack(v.args)) + end + end end) ct.clear() ct.setCursorPos(1, 1) local filter = Util.transpose({ - 'char', 'paste', 'key', 'key_up', - 'mouse_scroll', 'mouse_click', 'mouse_drag', 'mouse_up', + 'char', 'paste', 'key', 'key_up', + 'mouse_scroll', 'mouse_click', 'mouse_drag', 'mouse_up', }) while true do - local e = Event.pullEvent() - local event = e[1] + local e = Event.pullEvent() + local event = e[1] - if not socket.connected then - print() - print('Connection lost') - print('Press enter to exit') - _G.read() - break - end + if not socket.connected then + print() + print('Connection lost') + print('Press enter to exit') + _G.read() + break + end - if filter[event] then - socket:write({ - type = 'shellRemote', - event = e, - }) - elseif event == 'term_resize' then - writeTermInfo() - elseif event == 'terminate' then - socket:close() - ct.setBackgroundColor(colors.black) - ct.clear() - ct.setCursorPos(1, 1) - break - end + if filter[event] then + socket:write({ + type = 'shellRemote', + event = e, + }) + elseif event == 'term_resize' then + writeTermInfo() + elseif event == 'terminate' then + socket:close() + ct.setBackgroundColor(colors.black) + ct.clear() + ct.setCursorPos(1, 1) + break + end end diff --git a/sys/autorun/clipboard.lua b/sys/autorun/clipboard.lua index 204b699..2dd8017 100644 --- a/sys/autorun/clipboard.lua +++ b/sys/autorun/clipboard.lua @@ -10,17 +10,17 @@ local textutils = _G.textutils local data kernel.hook('clipboard_copy', function(_, args) - data = args[1] + data = args[1] end) -keyboard.addHotkey('control-shift-paste', function(_, args) - if type(data) == 'table' then - local s, m = pcall(textutils.serialize, data) - data = (s and m) or Util.tostring(data) - end - -- replace the event paste data with our internal data - args[1] = Util.tostring(data or '') - if data then - os.queueEvent('paste', data) +keyboard.addHotkey('shift-paste', function() + if type(data) == 'table' then + local s, m = pcall(textutils.serialize, data) + data = (s and m) or Util.tostring(data) + end + -- replace the event paste data with our internal data + -- args[1] = Util.tostring(data or '') + if data then + os.queueEvent('paste', data) end end) diff --git a/sys/autorun/gps.lua b/sys/autorun/gps.lua index b5372d7..8a51fea 100644 --- a/sys/autorun/gps.lua +++ b/sys/autorun/gps.lua @@ -2,42 +2,42 @@ local modem = _G.device.wireless_modem local turtle = _G.turtle if turtle and modem then - local s, m = turtle.run(function() + local s, m = turtle.run(function() - _G.requireInjector() + _G.requireInjector(_ENV) - local Config = require('config') - local config = { - destructive = false, - } - Config.load('gps', config) + local Config = require('config') + local config = { + destructive = false, + } + Config.load('gps', config) - if config.home then + if config.home then - local s = turtle.enableGPS(2) - if not s then - s = turtle.enableGPS(2) - end - if not s and config.destructive then - turtle.setPolicy('turtleSafe') - s = turtle.enableGPS(2) - end + local s = turtle.enableGPS(2) + if not s then + s = turtle.enableGPS(2) + end + if not s and config.destructive then + turtle.setPolicy('turtleSafe') + s = turtle.enableGPS(2) + end - if not s then - error('Unable to get GPS position') - end + if not s then + error('Unable to get GPS position') + end - if config.destructive then - turtle.setPolicy('turtleSafe') - end + if config.destructive then + turtle.setPolicy('turtleSafe') + end - if not turtle.pathfind(config.home) then - error('Failed to return home') - end - end - end) + if not turtle.pathfind(config.home) then + error('Failed to return home') + end + end + end) - if not s and m then - error(m) - end + if not s and m then + error(m) + end end diff --git a/sys/autorun/gpshost.lua b/sys/autorun/gpshost.lua index c06f4be..188d009 100644 --- a/sys/autorun/gpshost.lua +++ b/sys/autorun/gpshost.lua @@ -1,14 +1,19 @@ if _G.device.wireless_modem then - _G.requireInjector() - local Config = require('config') + _G.requireInjector(_ENV) + local Config = require('config') - local config = { } - Config.load('gps', config) + local kernel = _G.kernel - if config.host and type(config.host) == 'table' then - _ENV._APP_TITLE = 'GPS Daemon' - os.run(_ENV, '/rom/programs/gps', 'host', config.host.x, config.host.y, config.host.z) - print('GPS daemon stopped') - end + local config = { } + Config.load('gps', config) + + if config.host and type(config.host) == 'table' then + kernel.run({ + title = 'GPS Daemon', + hidden = true, + path = '/rom/programs/gps', + args = { 'host', config.host.x, config.host.y, config.host.z }, + }) + end end diff --git a/sys/autorun/hotkeys.lua b/sys/autorun/hotkeys.lua index b6e32d9..9c9dfce 100644 --- a/sys/autorun/hotkeys.lua +++ b/sys/autorun/hotkeys.lua @@ -1,4 +1,4 @@ -_G.requireInjector() +_G.requireInjector(_ENV) local Util = require('util') @@ -8,50 +8,50 @@ local multishell = _ENV.multishell -- overview keyboard.addHotkey('control-o', function() - for _,tab in pairs(multishell.getTabs()) do - if tab.isOverview then - multishell.setFocus(tab.tabId) - end - end + for _,tab in pairs(multishell.getTabs()) do + if tab.isOverview then + multishell.setFocus(tab.uid) + end + end end) -- restart tab keyboard.addHotkey('control-backspace', function() - local uid = multishell.getFocus() - local tab = kernel.find(uid) - if not tab.isOverview then - multishell.terminate(uid) - tab = Util.shallowCopy(tab) - tab.isDead = false - tab.focused = true - multishell.openTab(tab) - end + local uid = multishell.getFocus() + local tab = kernel.find(uid) + if not tab.isOverview then + multishell.terminate(uid) + tab = Util.shallowCopy(tab) + tab.isDead = false + tab.focused = true + multishell.openTab(tab) + end end) -- next tab keyboard.addHotkey('control-tab', function() - local tabs = multishell.getTabs() - local visibleTabs = { } - local currentTabId = multishell.getFocus() + local tabs = multishell.getTabs() + local visibleTabs = { } + local currentTabId = multishell.getFocus() - local function compareTab(a, b) - return a.uid < b.uid - end - for _,tab in Util.spairs(tabs, compareTab) do - if not tab.hidden then - table.insert(visibleTabs, tab) - end - end + local function compareTab(a, b) + return a.uid < b.uid + end + for _,tab in Util.spairs(tabs, compareTab) do + if not tab.hidden then + table.insert(visibleTabs, tab) + end + end - for k,tab in ipairs(visibleTabs) do - if tab.uid == currentTabId then - if k < #visibleTabs then - multishell.setFocus(visibleTabs[k + 1].uid) - return - end - end - end - if #visibleTabs > 0 then - multishell.setFocus(visibleTabs[1].uid) - end + for k,tab in ipairs(visibleTabs) do + if tab.uid == currentTabId then + if k < #visibleTabs then + multishell.setFocus(visibleTabs[k + 1].uid) + return + end + end + end + if #visibleTabs > 0 then + multishell.setFocus(visibleTabs[1].uid) + end end) diff --git a/sys/autorun/log.lua b/sys/autorun/log.lua index 75b3016..224d6e9 100644 --- a/sys/autorun/log.lua +++ b/sys/autorun/log.lua @@ -1,7 +1,7 @@ _G.requireInjector(_ENV) --[[ - Adds a task and the control-d hotkey to view the kernel log. + Adds a task and the control-d hotkey to view the kernel log. --]] local kernel = _G.kernel @@ -10,42 +10,46 @@ local multishell = _ENV.multishell local os = _G.os local term = _G.term -local routine = kernel.getCurrent() -if multishell then - multishell.setTitle(multishell.getCurrent(), 'System Log') - multishell.hideTab(routine.uid) +local function systemLog() + local routine = kernel.getCurrent() + + local w, h = kernel.window.getSize() + kernel.window.reposition(1, 2, w, h - 1) + + routine.terminal = kernel.window + routine.window = kernel.window + term.redirect(kernel.window) + + kernel.hook('mouse_scroll', function(_, eventData) + local dir, y = eventData[1], eventData[3] + + if y > 1 then + local currentTab = kernel.getFocused() + if currentTab.terminal.scrollUp and not currentTab.terminal.noAutoScroll then + if dir == -1 then + currentTab.terminal.scrollUp() + else + currentTab.terminal.scrollDown() + end + end + end + end) + + keyboard.addHotkey('control-d', function() + local current = kernel.getFocused() + if current.uid ~= routine.uid then + kernel.raise(routine.uid) + elseif kernel.routines[2] then + kernel.raise(kernel.routines[2].uid) + end + end) + + os.pullEventRaw('terminate') + keyboard.removeHotkey('control-d') end -local w, h = kernel.window.getSize() -kernel.window.reposition(1, 2, w, h - 1) - -routine.terminal = kernel.window -routine.window = kernel.window -term.redirect(kernel.window) - -kernel.hook('mouse_scroll', function(_, eventData) - local dir, y = eventData[1], eventData[3] - - if y > 1 then - local currentTab = kernel.getFocused() - if currentTab.terminal.scrollUp and not currentTab.terminal.noAutoScroll then - if dir == -1 then - currentTab.terminal.scrollUp() - else - currentTab.terminal.scrollDown() - end - end - end -end) - -keyboard.addHotkey('control-d', function() - local current = kernel.getFocused() - if current.uid ~= routine.uid then - kernel.raise(routine.uid) - elseif kernel.routines[2] then - kernel.raise(kernel.routines[2].uid) - end -end) - -os.pullEventRaw('terminate') -keyboard.removeHotkey('control-d') +multishell.openTab({ + title = 'System Log', + fn = systemLog, + hidden = true, +}) diff --git a/sys/boot/default.boot b/sys/boot/default.boot deleted file mode 100644 index f32ff8c..0000000 --- a/sys/boot/default.boot +++ /dev/null @@ -1,3 +0,0 @@ -term.clear() -term.setCursorPos(1, 1) -print(os.version()) \ No newline at end of file diff --git a/sys/boot/opus.boot b/sys/boot/opus.boot index ed0ca41..25cd652 100644 --- a/sys/boot/opus.boot +++ b/sys/boot/opus.boot @@ -9,69 +9,69 @@ local BASE = 'https://raw.githubusercontent.com/' .. GIT_REPO local sandboxEnv = setmetatable({ }, { __index = _G }) for k,v in pairs(_ENV) do - sandboxEnv[k] = v + sandboxEnv[k] = v end sandboxEnv.BRANCH = BRANCH _G.debug = function() end local function makeEnv() - local env = setmetatable({ }, { __index = _G }) - for k,v in pairs(sandboxEnv) do - env[k] = v - end - return env + local env = setmetatable({ }, { __index = _G }) + for k,v in pairs(sandboxEnv) do + env[k] = v + end + return env end local function run(file, ...) - local s, m = loadfile(file, makeEnv()) - if s then - return s(...) - end - error('Error loading ' .. file .. '\n' .. m) + local s, m = loadfile(file, makeEnv()) + if s then + return s(...) + end + error('Error loading ' .. file .. '\n' .. m) end local function runUrl(file, ...) - local url = BASE .. '/' .. file + local url = BASE .. '/' .. file - local u = http.get(url) - if u then - local fn = load(u.readAll(), url, nil, makeEnv()) - u.close() - if fn then - return fn(...) - end - end - error('Failed to download ' .. url) + local u = http.get(url) + if u then + local fn = load(u.readAll(), url, nil, makeEnv()) + u.close() + if fn then + return fn(...) + end + end + error('Failed to download ' .. url) end function os.getenv(varname) - return sandboxEnv[varname] + return sandboxEnv[varname] end function os.setenv(varname, value) - sandboxEnv[varname] = value + sandboxEnv[varname] = value end -- Install require shim if fs.exists('sys/apis/injector.lua') then - _G.requireInjector = run('sys/apis/injector.lua') + _G.requireInjector = run('sys/apis/injector.lua') else - -- not local, run the file system directly from git - _G.requireInjector = runUrl('sys/apis/injector.lua') - runUrl('sys/extensions/2.vfs.lua') + -- not local, run the file system directly from git + _G.requireInjector = runUrl('sys/apis/injector.lua') + runUrl('sys/extensions/2.vfs.lua') - -- install file system - fs.mount('', 'gitfs', GIT_REPO) + -- install file system + fs.mount('', 'gitfs', GIT_REPO) end local s, m = pcall(run, 'sys/apps/shell', 'sys/kernel.lua', ...) if not s then - print('\nError loading Opus OS\n') - _G.printError(m .. '\n') + print('\nError loading Opus OS\n') + _G.printError(m .. '\n') end if fs.restore then - fs.restore() + fs.restore() end diff --git a/sys/boot/tlco.boot b/sys/boot/tlco.boot index cd6d1bb..350e68f 100644 --- a/sys/boot/tlco.boot +++ b/sys/boot/tlco.boot @@ -2,14 +2,14 @@ local pullEvent = os.pullEventRaw local shutdown = os.shutdown os.pullEventRaw = function() - error('') + error('') end os.shutdown = function() - os.pullEventRaw = pullEvent - os.shutdown = shutdown + os.pullEventRaw = pullEvent + os.shutdown = shutdown - os.run(getfenv(1), 'sys/boot/opus.boot') + os.run(getfenv(1), 'sys/boot/opus.boot') end os.queueEvent('modem_message') diff --git a/sys/etc/app.db b/sys/etc/app.db index 0e26970..352846e 100644 --- a/sys/etc/app.db +++ b/sys/etc/app.db @@ -1,140 +1,140 @@ { - [ "53ebc572b4a44802ba114729f07bdaaf5409a9d7" ] = { - category = "Apps", - icon = "\0304 \030 \ + [ "53ebc572b4a44802ba114729f07bdaaf5409a9d7" ] = { + category = "Apps", + icon = "\0304 \030 \ \030f \0304 \0307 \030 \031 \031f)\ \030f \0304 \0307 \030 \031f)", - title = "Network", - run = "Network.lua", - }, - c7116629a6a855cb774d9c7c8ad822fd83c71fb5 = { - title = "Reboot", - category = "System", - icon = "\0304\031f \030f\0310o..\0304\031f \ + title = "Network", + run = "Network.lua", + }, + c7116629a6a855cb774d9c7c8ad822fd83c71fb5 = { + title = "Reboot", + category = "System", + icon = "\0304\031f \030f\0310o..\0304\031f \ \0304\031f \030f\0310.o.\0304\031f \ \0304\031f - ", - run = "rom/programs/reboot", - }, - fb91e24fa52d8d2b32937bf04d843f730319a902 = { - title = "Update", - category = "System", - icon = "\0301\03171\03180\030 \031 \ + run = "rom/programs/reboot", + }, + fb91e24fa52d8d2b32937bf04d843f730319a902 = { + title = "Update", + category = "System", + icon = "\0301\03171\03180\030 \031 \ \0301\03181\030 \031 \ \0301\03170\03180\03171\0307\031f>", - run = "http://pastebin.com/raw/sj4VMVJj", - }, - c47ae15370cfe1ed2781eedc1dc2547d12d9e972 = { - title = "Help", - category = "Apps", - icon = " \031f?\031 \ + run = "http://pastebin.com/raw/sj4VMVJj", + }, + c47ae15370cfe1ed2781eedc1dc2547d12d9e972 = { + title = "Help", + category = "Apps", + icon = " \031f?\031 \ \031f?\031 \ - \031f?", - run = "Help.lua", - }, - b0832074630eb731d7fbe8074de48a90cd9bb220 = { - title = "Lua", - category = "Apps", - icon = "\030f \ + \031f?", + run = "Help.lua", + }, + b0832074630eb731d7fbe8074de48a90cd9bb220 = { + title = "Lua", + category = "Apps", + icon = "\030f \ \030f\0310lua>\031 \ \030f ", - run = "sys/apps/Lua.lua", - }, - df485c871329671f46570634d63216761441bcd6 = { - title = "Devices", - category = "System", - icon = "\0304 \030 \ + run = "sys/apps/Lua.lua", + }, + df485c871329671f46570634d63216761441bcd6 = { + title = "Devices", + category = "System", + icon = "\0304 \030 \ \030f \0304 \0307 \030 \031 \031f_\ \030f \0304 \0307 \030 \031f/", - run = "Devices.lua", - }, - bc0792d8dc81e8aa30b987246a5ce97c40cd6833 = { - title = "System", - category = "System", - icon = " \0307\031f| \ + run = "Devices.lua", + }, + bc0792d8dc81e8aa30b987246a5ce97c40cd6833 = { + title = "System", + category = "System", + icon = " \0307\031f| \ \0307\031f---o\030 \031 \ - \0307\031f| ", - run = "System.lua", - }, - c5497bca58468ae64aed6c0fd921109217988db3 = { - title = "Events", - category = "System", - icon = "\0304\031f \030 \0311e\ + \0307\031f| ", + run = "System.lua", + }, + c5497bca58468ae64aed6c0fd921109217988db3 = { + title = "Events", + category = "System", + icon = "\0304\031f \030 \0311e\ \030f\031f \0304 \030 \0311ee\031f \ \030f\031f \0304 \030 \0311e\031f ", - run = "Events.lua", - }, - [ "2a4d562b1d9a9c90bdede6fac8ce4f7402462b86" ] = { - title = "Tasks", - category = "System", - icon = "\030f\031f \0315/\ + run = "Events.lua", + }, + [ "2a4d562b1d9a9c90bdede6fac8ce4f7402462b86" ] = { + title = "Tasks", + category = "System", + icon = "\030f\031f \0315/\ \030f\031f \0315/\\/ \ \030f\0315/\031f ", - run = "Tasks.lua", - }, - [ "6ce6c512ea433a7fc5c8841628e7696cd0ff7f2b" ] = { - title = "Files", - category = "Apps", - icon = "\0300\0317==\031 \0307 \ + run = "Tasks.lua", + }, + [ "6ce6c512ea433a7fc5c8841628e7696cd0ff7f2b" ] = { + title = "Files", + category = "Apps", + icon = "\0300\0317==\031 \0307 \ \0300\0317====\ \0300\0317====", - run = "Files.lua", - }, - [ "7fddb7d8d1d60b1eeefa9af01082e0811d4b484d" ] = { - title = "Shutdown", - category = "System", - icon = "\0304\031f \ + run = "Files.lua", + }, + [ "7fddb7d8d1d60b1eeefa9af01082e0811d4b484d" ] = { + title = "Shutdown", + category = "System", + icon = "\0304\031f \ \0304\031f \030f\0310zz\031 \ \0304\031f \030f ", - run = "/rom/programs/shutdown", - }, - bdc1fd5d3c0f3dcfd55d010426e61bf9451e680d = { - title = "Shell", - category = "Apps", - icon = "\030f\0314\151\131\131\131\131\ + run = "/rom/programs/shutdown", + }, + bdc1fd5d3c0f3dcfd55d010426e61bf9451e680d = { + title = "Shell", + category = "Apps", + icon = "\030f\0314\151\131\131\131\131\ \030f\0314\149\030f\0314> \0310_ \ \030f\0314\149\030f ", - run = "shell", - }, - b77aad5fb24921ef76ac8f3e500ed93fddae8f2a = { - title = "Redirection", - category = "Games", - icon = "\0307 \0308 \0307 \ + run = "shell", + }, + b77aad5fb24921ef76ac8f3e500ed93fddae8f2a = { + title = "Redirection", + category = "Games", + icon = "\0307 \0308 \0307 \ \0308\031b> \030b\0310>\0308\0318 \ \0307 ", - run = "rom/programs/fun/advanced/redirection", - requires = 'advanced', - }, - f39d173d91c22348565c20283b89d4d1cabd3b7e = { - title = "Falling", - category = "Games", - icon = "\030f \0302 \ + run = "rom/programs/fun/advanced/redirection", + requires = 'advanced', + }, + f39d173d91c22348565c20283b89d4d1cabd3b7e = { + title = "Falling", + category = "Games", + icon = "\030f \0302 \ \0309 \0302 \0301 \ \030e \0309 \0301 ", - run = "rom/programs/pocket/falling", - requires = 'advancedPocket', - }, - db56e2e1db9f7accfc37f2b132d27505c66ba521 = { - title = "Adventure", - category = "Games", - icon = "\030f\0310You \031 \ + run = "rom/programs/pocket/falling", + requires = 'advancedPocket', + }, + db56e2e1db9f7accfc37f2b132d27505c66ba521 = { + title = "Adventure", + category = "Games", + icon = "\030f\0310You \031 \ \030f\0310Ther\030 \031 \ \030f\0314?\031f \031 \030 ", - run = "rom/programs/fun/adventure", - }, - [ "76b849f460640bc789c433894382fb5acbac42a2" ] = { - title = "Worm", - category = "Games", - icon = "\030d \030 \030e \030 \ + run = "rom/programs/fun/adventure", + }, + [ "76b849f460640bc789c433894382fb5acbac42a2" ] = { + title = "Worm", + category = "Games", + icon = "\030d \030 \030e \030 \ \030d \030 \ \030d ", - run = "/rom/programs/fun/worm", - }, - [ "9f46ca3ef617166776ef6014a58d4e66859caa62" ] = { - title = "DJ", - category = "Games", - icon = " \030f \ + run = "/rom/programs/fun/worm", + }, + [ "9f46ca3ef617166776ef6014a58d4e66859caa62" ] = { + title = "DJ", + category = "Games", + icon = " \030f \ \030f \0307 \ \030f \0307 \0300 ", - run = "/rom/programs/fun/dj", - }, + run = "/rom/programs/fun/dj", + }, } diff --git a/sys/etc/ext.theme b/sys/etc/ext.theme index 927a1c1..e09bfc6 100644 --- a/sys/etc/ext.theme +++ b/sys/etc/ext.theme @@ -1,19 +1,19 @@ { - ScrollBar = { - lineChar = '|', - sliderChar = '\127', - upArrowChar = '\30', - downArrowChar = '\31', - }, - Button = { - --focusIndicator = '\183', - }, - Grid = { - focusIndicator = '\183', - inverseSortIndicator = '\24', - }, - TitleBar = { - frameChar = '\140', - closeInd = '\215', - }, + ScrollBar = { + lineChar = '|', + sliderChar = '\127', + upArrowChar = '\30', + downArrowChar = '\31', + }, + Button = { + --focusIndicator = '\183', + }, + Grid = { + focusIndicator = '\183', + inverseSortIndicator = '\24', + }, + TitleBar = { + frameChar = '\140', + closeInd = '\215', + }, } diff --git a/sys/extensions/1.device.lua b/sys/extensions/1.device.lua index ed20c49..2ec843a 100644 --- a/sys/extensions/1.device.lua +++ b/sys/extensions/1.device.lua @@ -13,15 +13,15 @@ _G.device.keyboard = { side = 'keyboard', type = 'keyboard', name = 'keyboard', - hotkeys = { }, - state = { }, + hotkeys = { }, + state = { }, } _G.device.mouse = { side = 'mouse', type = 'mouse', name = 'mouse', - state = { }, + state = { }, } local Input = require('input') @@ -34,24 +34,24 @@ local mouse = _G.device.mouse local os = _G.os kernel.hook('peripheral', function(_, eventData) - local side = eventData[1] - if side then - local dev = Peripheral.addDevice(device, side) - if dev then - os.queueEvent('device_attach', dev.name) - end - end + local side = eventData[1] + if side then + local dev = Peripheral.addDevice(device, side) + if dev then + os.queueEvent('device_attach', dev.name) + end + end end) kernel.hook('peripheral_detach', function(_, eventData) - local side = eventData[1] - if side then - local dev = Util.find(device, 'side', side) - if dev then - os.queueEvent('device_detach', dev.name) - device[dev.name] = nil - end - end + local side = eventData[1] + if side then + local dev = Util.find(device, 'side', side) + if dev then + os.queueEvent('device_detach', dev.name) + device[dev.name] = nil + end + end end) kernel.hook({ 'key', 'key_up', 'char', 'paste' }, function(event, eventData) @@ -68,38 +68,38 @@ kernel.hook({ 'key', 'key_up', 'char', 'paste' }, function(event, eventData) end -- and fire hotkeys - local hotkey = Input:translate(event, eventData[1], eventData[2]) + local hotkey = Input:translate(event, eventData[1], eventData[2]) - if hotkey and keyboard.hotkeys[hotkey] then - keyboard.hotkeys[hotkey](event, eventData) - end + if hotkey and keyboard.hotkeys[hotkey.code] then + keyboard.hotkeys[hotkey.code](event, eventData) + end end) kernel.hook({ 'mouse_click', 'mouse_up', 'mouse_drag' }, function(event, eventData) - local button = eventData[1] - if event == 'mouse_click' then - mouse.state[button] = true - else - if not mouse.state[button] then - return true -- ensure mouse ups are only generated if a mouse down was sent - end - if event == 'mouse_up' then - mouse.state[button] = nil - end - end + local button = eventData[1] + if event == 'mouse_click' then + mouse.state[button] = true + else + if not mouse.state[button] then + return true -- ensure mouse ups are only generated if a mouse down was sent + end + if event == 'mouse_up' then + mouse.state[button] = nil + end + end end) kernel.hook('kernel_focus', function() - Util.clear(keyboard.state) - Util.clear(mouse.state) + Util.clear(keyboard.state) + Util.clear(mouse.state) end) function keyboard.addHotkey(code, fn) - keyboard.hotkeys[code] = fn + keyboard.hotkeys[code] = fn end function keyboard.removeHotkey(code) - keyboard.hotkeys[code] = nil + keyboard.hotkeys[code] = nil end kernel.hook('monitor_touch', function(event, eventData) diff --git a/sys/extensions/2.vfs.lua b/sys/extensions/2.vfs.lua index 8a29213..2cf39ab 100644 --- a/sys/extensions/2.vfs.lua +++ b/sys/extensions/2.vfs.lua @@ -1,8 +1,8 @@ if fs.native then - return + return end -_G.requireInjector() +_G.requireInjector(_ENV) local Util = require('util') local fs = _G.fs @@ -13,330 +13,330 @@ local fstypes = { } local nativefs = { } for k,fn in pairs(fs) do - if type(fn) == 'function' then - nativefs[k] = function(node, ...) - return fn(...) - end - end + if type(fn) == 'function' then + nativefs[k] = function(node, ...) + return fn(...) + end + end end function nativefs.list(node, dir) - local files - if fs.native.isDir(dir) then - files = fs.native.list(dir) - end + local files + if fs.native.isDir(dir) then + files = fs.native.list(dir) + end - local function inList(l, e) - for _,v in ipairs(l) do - if v == e then - return true - end - end - end + local function inList(l, e) + for _,v in ipairs(l) do + if v == e then + return true + end + end + end - if dir == node.mountPoint and node.nodes then - files = files or { } - for k in pairs(node.nodes) do - if not inList(files, k) then - table.insert(files, k) - end - end - end + if dir == node.mountPoint and node.nodes then + files = files or { } + for k in pairs(node.nodes) do + if not inList(files, k) then + table.insert(files, k) + end + end + end - if not files then - error('Not a directory', 2) - end + if not files then + error('Not a directory', 2) + end - return files + return files end function nativefs.getSize(node, dir, recursive) - if recursive and fs.native.isDir(dir) then - local function sum(dir) - local total = 0 - local files = fs.native.list(dir) - for _,f in ipairs(files) do - local fullName = fs.combine(dir, f) - if fs.native.isDir(fullName) then - total = total + sum(fullName) - else - total = total + fs.native.getSize(fullName) - end - end - return total - end - return sum(dir) - end - if node.mountPoint == dir and node.nodes then - return 0 - end - return fs.native.getSize(dir) + if recursive and fs.native.isDir(dir) then + local function sum(dir) + local total = 0 + local files = fs.native.list(dir) + for _,f in ipairs(files) do + local fullName = fs.combine(dir, f) + if fs.native.isDir(fullName) then + total = total + sum(fullName) + else + total = total + fs.native.getSize(fullName) + end + end + return total + end + return sum(dir) + end + if node.mountPoint == dir and node.nodes then + return 0 + end + return fs.native.getSize(dir) end function nativefs.isDir(node, dir) - if node.mountPoint == dir then - return not not node.nodes - end - return fs.native.isDir(dir) + if node.mountPoint == dir then + return not not node.nodes + end + return fs.native.isDir(dir) end function nativefs.exists(node, dir) - if node.mountPoint == dir then - return true - end - return fs.native.exists(dir) + if node.mountPoint == dir then + return true + end + return fs.native.exists(dir) end function nativefs.delete(node, dir) - if node.mountPoint == dir then - fs.unmount(dir) - else - fs.native.delete(dir) - end + if node.mountPoint == dir then + fs.unmount(dir) + else + fs.native.delete(dir) + end end fstypes.nativefs = nativefs fs.nodes = { - fs = nativefs, - mountPoint = '', - fstype = 'nativefs', - nodes = { }, + fs = nativefs, + mountPoint = '', + fstype = 'nativefs', + nodes = { }, } local function splitpath(path) - local parts = { } - for match in string.gmatch(path, "[^/]+") do - table.insert(parts, match) - end - return parts + local parts = { } + for match in string.gmatch(path, "[^/]+") do + table.insert(parts, match) + end + return parts end local function getNode(dir) - local cd = fs.combine(dir, '') - local parts = splitpath(cd) - local node = fs.nodes + local cd = fs.combine(dir, '') + local parts = splitpath(cd) + local node = fs.nodes - for _,d in ipairs(parts) do - if node.nodes and node.nodes[d] then - node = node.nodes[d] - else - break - end - end + for _,d in ipairs(parts) do + if node.nodes and node.nodes[d] then + node = node.nodes[d] + else + break + end + end - return node + return node end local methods = { 'delete', 'getFreeSpace', 'exists', 'isDir', 'getSize', - 'isReadOnly', 'makeDir', 'getDrive', 'list', 'open' } + 'isReadOnly', 'makeDir', 'getDrive', 'list', 'open' } for _,m in pairs(methods) do - fs[m] = function(dir, ...) - dir = fs.combine(dir or '', '') - local node = getNode(dir) - return node.fs[m](node, dir, ...) - end + fs[m] = function(dir, ...) + dir = fs.combine(dir or '', '') + local node = getNode(dir) + return node.fs[m](node, dir, ...) + end end function fs.complete(partial, dir, includeFiles, includeSlash) - dir = fs.combine(dir, '') - local node = getNode(dir) - if node.fs.complete then - return node.fs.complete(node, partial, dir, includeFiles, includeSlash) - end - return fs.native.complete(partial, dir, includeFiles, includeSlash) + dir = fs.combine(dir, '') + local node = getNode(dir) + if node.fs.complete then + return node.fs.complete(node, partial, dir, includeFiles, includeSlash) + end + return fs.native.complete(partial, dir, includeFiles, includeSlash) end function fs.listEx(dir) - local node = getNode(dir) - if node.fs.listEx then - return node.fs.listEx(node, dir) - end + local node = getNode(dir) + if node.fs.listEx then + return node.fs.listEx(node, dir) + end - local t = { } - local files = node.fs.list(node, dir) + local t = { } + local files = node.fs.list(node, dir) - pcall(function() - for _,f in ipairs(files) do - local fullName = fs.combine(dir, f) - local file = { - name = f, - isDir = fs.isDir(fullName), - isReadOnly = fs.isReadOnly(fullName), - } - if not file.isDir then - file.size = fs.getSize(fullName) - end - table.insert(t, file) - end - end) - return t + pcall(function() + for _,f in ipairs(files) do + local fullName = fs.combine(dir, f) + local file = { + name = f, + isDir = fs.isDir(fullName), + isReadOnly = fs.isReadOnly(fullName), + } + if not file.isDir then + file.size = fs.getSize(fullName) + end + table.insert(t, file) + end + end) + return t end function fs.copy(s, t) - local sp = getNode(s) - local tp = getNode(t) - if sp == tp and sp.fs.copy then - return sp.fs.copy(sp, s, t) - end + local sp = getNode(s) + local tp = getNode(t) + if sp == tp and sp.fs.copy then + return sp.fs.copy(sp, s, t) + end - if fs.exists(t) then - error('File exists') - end + if fs.exists(t) then + error('File exists') + end - if fs.isDir(s) then - fs.makeDir(t) - local list = fs.list(s) - for _,f in ipairs(list) do - fs.copy(fs.combine(s, f), fs.combine(t, f)) - end + if fs.isDir(s) then + fs.makeDir(t) + local list = fs.list(s) + for _,f in ipairs(list) do + fs.copy(fs.combine(s, f), fs.combine(t, f)) + end - else - local sf = Util.readFile(s) - if not sf then - error('No such file') - end + else + local sf = Util.readFile(s) + if not sf then + error('No such file') + end - Util.writeFile(t, sf) - end + Util.writeFile(t, sf) + end end function fs.find(spec) -- not optimized -- local node = getNode(spec) -- local files = node.fs.find(node, spec) - local files = { } - -- method from https://github.com/N70/deltaOS/blob/dev/vfs - local function recurse_spec(results, path, spec) - local segment = spec:match('([^/]*)'):gsub('/', '') - local pattern = '^' .. segment:gsub("[%.%[%]%(%)%%%+%-%?%^%$]","%%%1"):gsub("%z","%%z"):gsub("%*","[^/]-") .. '$' - if fs.isDir(path) then - for _, file in ipairs(fs.list(path)) do - if file:match(pattern) then - local f = fs.combine(path, file) - if spec == segment then - table.insert(results, f) - end - if fs.isDir(f) then - recurse_spec(results, f, spec:sub(#segment + 2)) - end - end - end - end - end - recurse_spec(files, '', spec) - table.sort(files) + local files = { } + -- method from https://github.com/N70/deltaOS/blob/dev/vfs + local function recurse_spec(results, path, spec) + local segment = spec:match('([^/]*)'):gsub('/', '') + local pattern = '^' .. segment:gsub("[%.%[%]%(%)%%%+%-%?%^%$]","%%%1"):gsub("%z","%%z"):gsub("%*","[^/]-") .. '$' + if fs.isDir(path) then + for _, file in ipairs(fs.list(path)) do + if file:match(pattern) then + local f = fs.combine(path, file) + if spec == segment then + table.insert(results, f) + end + if fs.isDir(f) then + recurse_spec(results, f, spec:sub(#segment + 2)) + end + end + end + end + end + recurse_spec(files, '', spec) + table.sort(files) - return files + return files end function fs.move(s, t) - local sp = getNode(s) - local tp = getNode(t) - if sp == tp and sp.fs.move then - return sp.fs.move(sp, s, t) - end - fs.copy(s, t) - fs.delete(s) + local sp = getNode(s) + local tp = getNode(t) + if sp == tp and sp.fs.move then + return sp.fs.move(sp, s, t) + end + fs.copy(s, t) + fs.delete(s) end local function getfstype(fstype) - local vfs = fstypes[fstype] - if not vfs then - vfs = require('fs.' .. fstype) - fs.registerType(fstype, vfs) - end - return vfs + local vfs = fstypes[fstype] + if not vfs then + vfs = require('fs.' .. fstype) + fs.registerType(fstype, vfs) + end + return vfs end function fs.mount(path, fstype, ...) - local vfs = getfstype(fstype) - if not vfs then - error('Invalid file system type') - end - local node = vfs.mount(path, ...) - if node then - local parts = splitpath(path) - local targetName = table.remove(parts, #parts) + local vfs = getfstype(fstype) + if not vfs then + error('Invalid file system type') + end + local node = vfs.mount(path, ...) + if node then + local parts = splitpath(path) + local targetName = table.remove(parts, #parts) - local tp = fs.nodes - for _,d in ipairs(parts) do - if not tp.nodes then - tp.nodes = { } - end - if not tp.nodes[d] then - tp.nodes[d] = Util.shallowCopy(tp) - tp.nodes[d].nodes = { } - tp.nodes[d].mountPoint = fs.combine(tp.mountPoint, d) - end - tp = tp.nodes[d] - end + local tp = fs.nodes + for _,d in ipairs(parts) do + if not tp.nodes then + tp.nodes = { } + end + if not tp.nodes[d] then + tp.nodes[d] = Util.shallowCopy(tp) + tp.nodes[d].nodes = { } + tp.nodes[d].mountPoint = fs.combine(tp.mountPoint, d) + end + tp = tp.nodes[d] + end - node.fs = vfs - node.fstype = fstype - if not targetName then - node.mountPoint = '' - fs.nodes = node - else - node.mountPoint = fs.combine(tp.mountPoint, targetName) - tp.nodes[targetName] = node - end - end - return node + node.fs = vfs + node.fstype = fstype + if not targetName then + node.mountPoint = '' + fs.nodes = node + else + node.mountPoint = fs.combine(tp.mountPoint, targetName) + tp.nodes[targetName] = node + end + end + return node end function fs.loadTab(path) - local mounts = Util.readFile(path) - if mounts then - for _,l in ipairs(Util.split(mounts)) do - if l:sub(1, 1) ~= '#' then - local s, m = pcall(function() - fs.mount(table.unpack(Util.matches(l))) - end) - if not s then - _G.printError('Mount failed') - _G.printError(l) - _G.printError(m) - end - end - end - end + local mounts = Util.readFile(path) + if mounts then + for _,l in ipairs(Util.split(mounts)) do + if l:sub(1, 1) ~= '#' then + local s, m = pcall(function() + fs.mount(table.unpack(Util.matches(l))) + end) + if not s then + _G.printError('Mount failed') + _G.printError(l) + _G.printError(m) + end + end + end + end end local function getNodeByParts(parts) - local node = fs.nodes + local node = fs.nodes - for _,d in ipairs(parts) do - if not node.nodes[d] then - return - end - node = node.nodes[d] - end - return node + for _,d in ipairs(parts) do + if not node.nodes[d] then + return + end + node = node.nodes[d] + end + return node end function fs.unmount(path) - local parts = splitpath(path) - local targetName = table.remove(parts, #parts) + local parts = splitpath(path) + local targetName = table.remove(parts, #parts) - local node = getNodeByParts(parts) + local node = getNodeByParts(parts) - if node and node.nodes[targetName] then - node.nodes[targetName] = nil - end + if node and node.nodes[targetName] then + node.nodes[targetName] = nil + end end function fs.registerType(name, fs) - fstypes[name] = fs + fstypes[name] = fs end function fs.getTypes() - return fstypes + return fstypes end function fs.restore() - local native = fs.native - Util.clear(fs) - Util.merge(fs, native) + local native = fs.native + Util.clear(fs) + Util.merge(fs, native) end diff --git a/sys/extensions/4.label.lua b/sys/extensions/4.label.lua index 8878598..0a34862 100644 --- a/sys/extensions/4.label.lua +++ b/sys/extensions/4.label.lua @@ -2,14 +2,14 @@ local os = _G.os -- Default label if not os.getComputerLabel() then - local id = os.getComputerID() - if _G.turtle then - os.setComputerLabel('turtle_' .. id) - elseif _G.pocket then - os.setComputerLabel('pocket_' .. id) - elseif _G.commands then - os.setComputerLabel('command_' .. id) - else - os.setComputerLabel('computer_' .. id) - end + local id = os.getComputerID() + if _G.turtle then + os.setComputerLabel('turtle_' .. id) + elseif _G.pocket then + os.setComputerLabel('pocket_' .. id) + elseif _G.commands then + os.setComputerLabel('command_' .. id) + else + os.setComputerLabel('computer_' .. id) + end end diff --git a/sys/extensions/4.user.lua b/sys/extensions/4.user.lua index 41df8d3..ad59fb7 100644 --- a/sys/extensions/4.user.lua +++ b/sys/extensions/4.user.lua @@ -1,4 +1,4 @@ -_G.requireInjector() +_G.requireInjector(_ENV) local Util = require('util') @@ -7,32 +7,32 @@ local os = _G.os local shell = _ENV.shell if not fs.exists('usr/apps') then - fs.makeDir('usr/apps') + fs.makeDir('usr/apps') end if not fs.exists('usr/autorun') then - fs.makeDir('usr/autorun') + fs.makeDir('usr/autorun') end if not fs.exists('usr/config/fstab') then - Util.writeFile('usr/config/fstab', - 'usr gitfs kepler155c/opus-apps/' .. os.getenv('BRANCH')) + Util.writeFile('usr/config/fstab', + 'usr gitfs kepler155c/opus-apps/' .. os.getenv('BRANCH')) end if not fs.exists('usr/config/shell') then - Util.writeTable('usr/config/shell', { - aliases = shell.aliases(), - path = 'usr/apps:sys/apps:' .. shell.path(), - lua_path = 'sys/apis:usr/apis', - }) + Util.writeTable('usr/config/shell', { + aliases = shell.aliases(), + path = 'usr/apps:sys/apps:' .. shell.path(), + lua_path = 'sys/apis:usr/apis', + }) end local config = Util.readTable('usr/config/shell') if config.aliases then - for k in pairs(shell.aliases()) do - shell.clearAlias(k) - end - for k,v in pairs(config.aliases) do - shell.setAlias(k, v) - end + for k in pairs(shell.aliases()) do + shell.clearAlias(k) + end + for k,v in pairs(config.aliases) do + shell.setAlias(k, v) + end end shell.setPath(config.path) os.setenv('LUA_PATH', config.lua_path) diff --git a/sys/extensions/6.tl3.lua b/sys/extensions/6.tl3.lua index b89f0bb..4c0e763 100644 --- a/sys/extensions/6.tl3.lua +++ b/sys/extensions/6.tl3.lua @@ -1,8 +1,8 @@ if not _G.turtle then - return + return end -_G.requireInjector() +_G.requireInjector(_ENV) local Pathing = require('turtle.pathfind') local GPS = require('gps') @@ -28,146 +28,146 @@ function turtle.getStatus() return state.status end function turtle.setStatus(s) state.status = s end local function _defaultMove(action) - while not action.move() do - if not state.digPolicy(action) and not state.attackPolicy(action) then - return false - end - end - return true + while not action.move() do + if not state.digPolicy(action) and not state.attackPolicy(action) then + return false + end + end + return true end function turtle.setPoint(pt, isGPS) - turtle.point.x = pt.x - turtle.point.y = pt.y - turtle.point.z = pt.z - if pt.heading then - turtle.point.heading = pt.heading - end - turtle.point.gps = isGPS - return true + turtle.point.x = pt.x + turtle.point.y = pt.y + turtle.point.z = pt.z + if pt.heading then + turtle.point.heading = pt.heading + end + turtle.point.gps = isGPS + return true end function turtle.resetState() - state.abort = false - state.status = 'idle' - state.attackPolicy = noop - state.digPolicy = noop - state.movePolicy = _defaultMove - state.moveCallback = noop - Pathing.reset() - return true + state.abort = false + state.status = 'idle' + state.attackPolicy = noop + state.digPolicy = noop + state.movePolicy = _defaultMove + state.moveCallback = noop + Pathing.reset() + return true end function turtle.reset() - turtle.point.x = 0 - turtle.point.y = 0 - turtle.point.z = 0 - turtle.point.heading = 0 -- should be facing - turtle.point.gps = false + turtle.point.x = 0 + turtle.point.y = 0 + turtle.point.z = 0 + turtle.point.heading = 0 -- should be facing + turtle.point.gps = false - turtle.resetState() - return true + turtle.resetState() + return true end turtle.reset() local actions = { - up = { - detect = turtle.native.detectUp, - dig = turtle.native.digUp, - move = turtle.native.up, - attack = turtle.native.attackUp, - place = turtle.native.placeUp, - drop = turtle.native.dropUp, - suck = turtle.native.suckUp, - compare = turtle.native.compareUp, - inspect = turtle.native.inspectUp, - side = 'top' - }, - down = { - detect = turtle.native.detectDown, - dig = turtle.native.digDown, - move = turtle.native.down, - attack = turtle.native.attackDown, - place = turtle.native.placeDown, - drop = turtle.native.dropDown, - suck = turtle.native.suckDown, - compare = turtle.native.compareDown, - inspect = turtle.native.inspectDown, - side = 'bottom' - }, - forward = { - detect = turtle.native.detect, - dig = turtle.native.dig, - move = turtle.native.forward, - attack = turtle.native.attack, - place = turtle.native.place, - drop = turtle.native.drop, - suck = turtle.native.suck, - compare = turtle.native.compare, - inspect = turtle.native.inspect, - side = 'front' - }, - back = { - detect = noop, - dig = noop, - move = turtle.native.back, - attack = noop, - place = noop, - suck = noop, - compare = noop, - side = 'back' - }, + up = { + detect = turtle.native.detectUp, + dig = turtle.native.digUp, + move = turtle.native.up, + attack = turtle.native.attackUp, + place = turtle.native.placeUp, + drop = turtle.native.dropUp, + suck = turtle.native.suckUp, + compare = turtle.native.compareUp, + inspect = turtle.native.inspectUp, + side = 'top' + }, + down = { + detect = turtle.native.detectDown, + dig = turtle.native.digDown, + move = turtle.native.down, + attack = turtle.native.attackDown, + place = turtle.native.placeDown, + drop = turtle.native.dropDown, + suck = turtle.native.suckDown, + compare = turtle.native.compareDown, + inspect = turtle.native.inspectDown, + side = 'bottom' + }, + forward = { + detect = turtle.native.detect, + dig = turtle.native.dig, + move = turtle.native.forward, + attack = turtle.native.attack, + place = turtle.native.place, + drop = turtle.native.drop, + suck = turtle.native.suck, + compare = turtle.native.compare, + inspect = turtle.native.inspect, + side = 'front' + }, + back = { + detect = noop, + dig = noop, + move = turtle.native.back, + attack = noop, + place = noop, + suck = noop, + compare = noop, + side = 'back' + }, } function turtle.getAction(direction) - return actions[direction] + return actions[direction] end function turtle.getHeadingInfo(heading) - heading = heading or turtle.point.heading - return headings[heading] + heading = heading or turtle.point.heading + return headings[heading] end -- [[ Basic turtle actions ]] -- local function inventoryAction(fn, name, qty) - local slots = turtle.getFilledSlots() - local s - for _,slot in pairs(slots) do - if slot.key == name or slot.name == name then - turtle.native.select(slot.index) - if not qty then - s = fn() - else - s = fn(math.min(qty, slot.count)) - qty = qty - slot.count - if qty < 0 then - break - end - end - end - end - if not s then - return false, 'No items found' - end - return s + local slots = turtle.getFilledSlots() + local s + for _,slot in pairs(slots) do + if slot.key == name or slot.name == name then + turtle.native.select(slot.index) + if not qty then + s = fn() + else + s = fn(math.min(qty, slot.count)) + qty = qty - slot.count + if qty < 0 then + break + end + end + end + end + if not s then + return false, 'No items found' + end + return s end -- [[ Attack ]] -- local function _attack(action) - if action.attack() then - repeat until not action.attack() - return true - end - return false + if action.attack() then + repeat until not action.attack() + return true + end + return false end turtle.attackPolicies = { - none = noop, + none = noop, - attack = function(action) - return _attack(action) - end, + attack = function(action) + return _attack(action) + end, } function turtle.attack() return _attack(actions.forward) end @@ -179,32 +179,32 @@ function turtle.setAttackPolicy(policy) state.attackPolicy = policy end -- [[ Place ]] -- local function _place(action, indexOrId) - local slot + local slot - if indexOrId then - slot = turtle.getSlot(indexOrId) - if not slot then - return false, 'No items to place' - end - end + if indexOrId then + slot = turtle.getSlot(indexOrId) + if not slot then + return false, 'No items to place' + end + end - if slot and slot.qty == 0 then - return false, 'No items to place' - end + if slot and slot.qty == 0 then + return false, 'No items to place' + end - return Util.tryTimes(3, function() - if slot then - turtle.select(slot.index) - end - local result = { action.place() } - if result[1] then - return true - end - if not state.digPolicy(action) then - state.attackPolicy(action) - end - return unpack(result) - end) + return Util.tryTimes(3, function() + if slot then + turtle.select(slot.index) + end + local result = { action.place() } + if result[1] then + return true + end + if not state.digPolicy(action) then + state.attackPolicy(action) + end + return unpack(result) + end) end function turtle.place(slot) return _place(actions.forward, slot) end @@ -212,10 +212,10 @@ function turtle.placeUp(slot) return _place(actions.up, slot) end function turtle.placeDown(slot) return _place(actions.down, slot) end local function _drop(action, qtyOrName, qty) - if not qtyOrName or type(qtyOrName) == 'number' then - return action.drop(qtyOrName or 64) - end - return inventoryAction(action.drop, qtyOrName, qty) + if not qtyOrName or type(qtyOrName) == 'number' then + return action.drop(qtyOrName or 64) + end + return inventoryAction(action.drop, qtyOrName, qty) end function turtle.drop(count, slot) return _drop(actions.forward, count, slot) end @@ -223,108 +223,108 @@ function turtle.dropUp(count, slot) return _drop(actions.up, count, slot) function turtle.dropDown(count, slot) return _drop(actions.down, count, slot) end function turtle.refuel(qtyOrName, qty) - if not qtyOrName or type(qtyOrName) == 'number' then - return turtle.native.refuel(qtyOrName or 64) - end - return inventoryAction(turtle.native.refuel, qtyOrName, qty or 64) + if not qtyOrName or type(qtyOrName) == 'number' then + return turtle.native.refuel(qtyOrName or 64) + end + return inventoryAction(turtle.native.refuel, qtyOrName, qty or 64) end function turtle.isTurtleAtSide(side) - local sideType = peripheral.getType(side) - return sideType and sideType == 'turtle' + local sideType = peripheral.getType(side) + return sideType and sideType == 'turtle' end turtle.digPolicies = { - none = noop, + none = noop, - dig = function(action) - return action.dig() - end, + dig = function(action) + return action.dig() + end, - turtleSafe = function(action) - if action.side == 'back' then - return false - end - if not turtle.isTurtleAtSide(action.side) then - return action.dig() - end - return Util.tryTimes(6, function() + turtleSafe = function(action) + if action.side == 'back' then + return false + end + if not turtle.isTurtleAtSide(action.side) then + return action.dig() + end + return Util.tryTimes(6, function() -- if not turtle.isTurtleAtSide(action.side) then -- return true --action.dig() -- end - os.sleep(.25) - if not action.detect() then - return true - end - end) - end, + os.sleep(.25) + if not action.detect() then + return true + end + end) + end, - digAndDrop = function(action) - if action.detect() then - local slots = turtle.getInventory() - if action.dig() then - turtle.reconcileInventory(slots) - return true - end - end - return false - end + digAndDrop = function(action) + if action.detect() then + local slots = turtle.getInventory() + if action.dig() then + turtle.reconcileInventory(slots) + return true + end + end + return false + end } turtle.movePolicies = { - none = noop, - default = _defaultMove, - assured = function(action) - if not _defaultMove(action) then - if action.side == 'back' then - return false - end - local oldStatus = state.status - print('assured move: stuck') - state.status = 'stuck' - repeat - os.sleep(1) - until _defaultMove(action) - state.status = oldStatus - end - return true - end, + none = noop, + default = _defaultMove, + assured = function(action) + if not _defaultMove(action) then + if action.side == 'back' then + return false + end + local oldStatus = state.status + print('assured move: stuck') + state.status = 'stuck' + repeat + os.sleep(1) + until _defaultMove(action) + state.status = oldStatus + end + return true + end, } turtle.policies = { - none = { dig = turtle.digPolicies.none, attack = turtle.attackPolicies.none }, - digOnly = { dig = turtle.digPolicies.dig, attack = turtle.attackPolicies.none }, - attackOnly = { dig = turtle.digPolicies.none, attack = turtle.attackPolicies.attack }, - digAttack = { dig = turtle.digPolicies.dig, attack = turtle.attackPolicies.attack }, - turtleSafe = { dig = turtle.digPolicies.turtleSafe, attack = turtle.attackPolicies.attack }, + none = { dig = turtle.digPolicies.none, attack = turtle.attackPolicies.none }, + digOnly = { dig = turtle.digPolicies.dig, attack = turtle.attackPolicies.none }, + attackOnly = { dig = turtle.digPolicies.none, attack = turtle.attackPolicies.attack }, + digAttack = { dig = turtle.digPolicies.dig, attack = turtle.attackPolicies.attack }, + turtleSafe = { dig = turtle.digPolicies.turtleSafe, attack = turtle.attackPolicies.attack }, - attack = { attack = turtle.attackPolicies.attack }, + attack = { attack = turtle.attackPolicies.attack }, - defaultMove = { move = turtle.movePolicies.default }, - assuredMove = { move = turtle.movePolicies.assured }, + defaultMove = { move = turtle.movePolicies.default }, + assuredMove = { move = turtle.movePolicies.assured }, } function turtle.setPolicy(...) - local args = { ... } - for _, policy in pairs(args) do - if type(policy) == 'string' then - policy = turtle.policies[policy] - end - if not policy then - error('Invalid policy') - -- return false, 'Invalid policy' - end - if policy.dig then - state.digPolicy = policy.dig - end - if policy.attack then - state.attackPolicy = policy.attack - end - if policy.move then - state.movePolicy = policy.move - end - end - return true + local args = { ... } + for _, policy in pairs(args) do + if type(policy) == 'string' then + policy = turtle.policies[policy] + end + if not policy then + error('Invalid policy') + -- return false, 'Invalid policy' + end + if policy.dig then + state.digPolicy = policy.dig + end + if policy.attack then + state.attackPolicy = policy.attack + end + if policy.move then + state.movePolicy = policy.move + end + end + return true end function turtle.setDigPolicy(policy) state.digPolicy = policy end @@ -334,646 +334,646 @@ function turtle.getMoveCallback() return state.moveCallback end -- [[ Heading ]] -- function turtle.getHeading() - return turtle.point.heading + return turtle.point.heading end function turtle.turnRight() - turtle.setHeading((turtle.point.heading + 1) % 4) - return turtle.point + turtle.setHeading((turtle.point.heading + 1) % 4) + return turtle.point end function turtle.turnLeft() - turtle.setHeading((turtle.point.heading - 1) % 4) - return turtle.point + turtle.setHeading((turtle.point.heading - 1) % 4) + return turtle.point end function turtle.turnAround() - turtle.setHeading((turtle.point.heading + 2) % 4) - return turtle.point + turtle.setHeading((turtle.point.heading + 2) % 4) + return turtle.point end function turtle.setHeading(heading) - if not heading then - return false, 'Invalid heading' - end + if not heading then + return false, 'Invalid heading' + end - local fi = Point.facings[heading] - if not fi then - return false, 'Invalid heading' - end + local fi = Point.facings[heading] + if not fi then + return false, 'Invalid heading' + end - heading = fi.heading % 4 - if heading ~= turtle.point.heading then - while heading < turtle.point.heading do - heading = heading + 4 - end - if heading - turtle.point.heading == 3 then - turtle.native.turnLeft() - turtle.point.heading = (turtle.point.heading - 1) % 4 - state.moveCallback('turn', turtle.point) - else - local turns = heading - turtle.point.heading - while turns > 0 do - turns = turns - 1 - turtle.native.turnRight() - turtle.point.heading = (turtle.point.heading + 1) % 4 - state.moveCallback('turn', turtle.point) - end - end - end + heading = fi.heading % 4 + if heading ~= turtle.point.heading then + while heading < turtle.point.heading do + heading = heading + 4 + end + if heading - turtle.point.heading == 3 then + turtle.native.turnLeft() + turtle.point.heading = (turtle.point.heading - 1) % 4 + state.moveCallback('turn', turtle.point) + else + local turns = heading - turtle.point.heading + while turns > 0 do + turns = turns - 1 + turtle.native.turnRight() + turtle.point.heading = (turtle.point.heading + 1) % 4 + state.moveCallback('turn', turtle.point) + end + end + end - return turtle.point + return turtle.point end function turtle.headTowardsX(dx) - if turtle.point.x ~= dx then - if turtle.point.x > dx then - turtle.setHeading(2) - else - turtle.setHeading(0) - end - end + if turtle.point.x ~= dx then + if turtle.point.x > dx then + turtle.setHeading(2) + else + turtle.setHeading(0) + end + end end function turtle.headTowardsZ(dz) - if turtle.point.z ~= dz then - if turtle.point.z > dz then - turtle.setHeading(3) - else - turtle.setHeading(1) - end - end + if turtle.point.z ~= dz then + if turtle.point.z > dz then + turtle.setHeading(3) + else + turtle.setHeading(1) + end + end end function turtle.headTowards(pt) - local xd = math.abs(turtle.point.x - pt.x) - local zd = math.abs(turtle.point.z - pt.z) - if xd > zd then - turtle.headTowardsX(pt.x) - else - turtle.headTowardsZ(pt.z) - end + local xd = math.abs(turtle.point.x - pt.x) + local zd = math.abs(turtle.point.z - pt.z) + if xd > zd then + turtle.headTowardsX(pt.x) + else + turtle.headTowardsZ(pt.z) + end end -- [[ move ]] -- function turtle.up() - if state.movePolicy(actions.up) then - turtle.point.y = turtle.point.y + 1 - state.moveCallback('up', turtle.point) - return true, turtle.point - end + if state.movePolicy(actions.up) then + turtle.point.y = turtle.point.y + 1 + state.moveCallback('up', turtle.point) + return true, turtle.point + end end function turtle.down() - if state.movePolicy(actions.down) then - turtle.point.y = turtle.point.y - 1 - state.moveCallback('down', turtle.point) - return true, turtle.point - end + if state.movePolicy(actions.down) then + turtle.point.y = turtle.point.y - 1 + state.moveCallback('down', turtle.point) + return true, turtle.point + end end function turtle.forward() - if state.movePolicy(actions.forward) then - turtle.point.x = turtle.point.x + headings[turtle.point.heading].xd - turtle.point.z = turtle.point.z + headings[turtle.point.heading].zd - state.moveCallback('forward', turtle.point) - return true, turtle.point - end + if state.movePolicy(actions.forward) then + turtle.point.x = turtle.point.x + headings[turtle.point.heading].xd + turtle.point.z = turtle.point.z + headings[turtle.point.heading].zd + state.moveCallback('forward', turtle.point) + return true, turtle.point + end end function turtle.back() - if state.movePolicy(actions.back) then - turtle.point.x = turtle.point.x - headings[turtle.point.heading].xd - turtle.point.z = turtle.point.z - headings[turtle.point.heading].zd - state.moveCallback('back', turtle.point) - return true, turtle.point - end + if state.movePolicy(actions.back) then + turtle.point.x = turtle.point.x - headings[turtle.point.heading].xd + turtle.point.z = turtle.point.z - headings[turtle.point.heading].zd + state.moveCallback('back', turtle.point) + return true, turtle.point + end end local function moveTowardsX(dx) - local direction = dx - turtle.point.x - local move + local direction = dx - turtle.point.x + local move - if direction == 0 then - return true - end + if direction == 0 then + return true + end - if direction > 0 and turtle.point.heading == 0 or - direction < 0 and turtle.point.heading == 2 then - move = turtle.forward - else - move = turtle.back - end + if direction > 0 and turtle.point.heading == 0 or + direction < 0 and turtle.point.heading == 2 then + move = turtle.forward + else + move = turtle.back + end - repeat - if not move() then - return false - end - until turtle.point.x == dx - return true + repeat + if not move() then + return false + end + until turtle.point.x == dx + return true end local function moveTowardsZ(dz) - local direction = dz - turtle.point.z - local move + local direction = dz - turtle.point.z + local move - if direction == 0 then - return true - end + if direction == 0 then + return true + end - if direction > 0 and turtle.point.heading == 1 or - direction < 0 and turtle.point.heading == 3 then - move = turtle.forward - else - move = turtle.back - end + if direction > 0 and turtle.point.heading == 1 or + direction < 0 and turtle.point.heading == 3 then + move = turtle.forward + else + move = turtle.back + end - repeat - if not move() then - return false - end - until turtle.point.z == dz - return true + repeat + if not move() then + return false + end + until turtle.point.z == dz + return true end -- [[ go ]] -- -- 1 turn goto (going backwards if possible) function turtle.gotoSingleTurn(dx, dy, dz, dh) - dx = dx or turtle.point.x - dy = dy or turtle.point.y - dz = dz or turtle.point.z + dx = dx or turtle.point.x + dy = dy or turtle.point.y + dz = dz or turtle.point.z - local function gx() - if turtle.point.x ~= dx then - moveTowardsX(dx) - end - if turtle.point.z ~= dz then - if dh and dh % 2 == 1 then - turtle.setHeading(dh) - else - turtle.headTowardsZ(dz) - end - end - end + local function gx() + if turtle.point.x ~= dx then + moveTowardsX(dx) + end + if turtle.point.z ~= dz then + if dh and dh % 2 == 1 then + turtle.setHeading(dh) + else + turtle.headTowardsZ(dz) + end + end + end - local function gz() - if turtle.point.z ~= dz then - moveTowardsZ(dz) - end - if turtle.point.x ~= dx then - if dh and dh % 2 == 0 then - turtle.setHeading(dh) - else - turtle.headTowardsX(dx) - end - end - end + local function gz() + if turtle.point.z ~= dz then + moveTowardsZ(dz) + end + if turtle.point.x ~= dx then + if dh and dh % 2 == 0 then + turtle.setHeading(dh) + else + turtle.headTowardsX(dx) + end + end + end - repeat - local x, z - local y = turtle.point.y + repeat + local x, z + local y = turtle.point.y - repeat - x, z = turtle.point.x, turtle.point.z + repeat + x, z = turtle.point.x, turtle.point.z - if turtle.point.heading % 2 == 0 then - gx() - gz() - else - gz() - gx() - end - until x == turtle.point.x and z == turtle.point.z + if turtle.point.heading % 2 == 0 then + gx() + gz() + else + gz() + gx() + end + until x == turtle.point.x and z == turtle.point.z - if turtle.point.y ~= dy then - turtle.gotoY(dy) - end + if turtle.point.y ~= dy then + turtle.gotoY(dy) + end - if turtle.point.x == dx and turtle.point.z == dz and turtle.point.y == dy then - return true - end + if turtle.point.x == dx and turtle.point.z == dz and turtle.point.y == dy then + return true + end - until x == turtle.point.x and z == turtle.point.z and y == turtle.point.y + until x == turtle.point.x and z == turtle.point.z and y == turtle.point.y - return false + return false end local function gotoEx(dx, dy, dz) - -- determine the heading to ensure the least amount of turns - -- first check is 1 turn needed - remaining require 2 turns - if turtle.point.heading == 0 and turtle.point.x <= dx or - turtle.point.heading == 2 and turtle.point.x >= dx or - turtle.point.heading == 1 and turtle.point.z <= dz or - turtle.point.heading == 3 and turtle.point.z >= dz then - -- maintain current heading - -- nop - elseif dz > turtle.point.z and turtle.point.heading == 0 or - dz < turtle.point.z and turtle.point.heading == 2 or - dx < turtle.point.x and turtle.point.heading == 1 or - dx > turtle.point.x and turtle.point.heading == 3 then - turtle.turnRight() - else - turtle.turnLeft() - end + -- determine the heading to ensure the least amount of turns + -- first check is 1 turn needed - remaining require 2 turns + if turtle.point.heading == 0 and turtle.point.x <= dx or + turtle.point.heading == 2 and turtle.point.x >= dx or + turtle.point.heading == 1 and turtle.point.z <= dz or + turtle.point.heading == 3 and turtle.point.z >= dz then + -- maintain current heading + -- nop + elseif dz > turtle.point.z and turtle.point.heading == 0 or + dz < turtle.point.z and turtle.point.heading == 2 or + dx < turtle.point.x and turtle.point.heading == 1 or + dx > turtle.point.x and turtle.point.heading == 3 then + turtle.turnRight() + else + turtle.turnLeft() + end - if (turtle.point.heading % 2) == 1 then - if not turtle.gotoZ(dz) then return false end - if not turtle.gotoX(dx) then return false end - else - if not turtle.gotoX(dx) then return false end - if not turtle.gotoZ(dz) then return false end - end + if (turtle.point.heading % 2) == 1 then + if not turtle.gotoZ(dz) then return false end + if not turtle.gotoX(dx) then return false end + else + if not turtle.gotoX(dx) then return false end + if not turtle.gotoZ(dz) then return false end + end - if dy then - if not turtle.gotoY(dy) then return false end - end + if dy then + if not turtle.gotoY(dy) then return false end + end - return true + return true end -- fallback goto - will turn around if was previously moving backwards local function gotoMultiTurn(dx, dy, dz) - if gotoEx(dx, dy, dz) then - return true - end + if gotoEx(dx, dy, dz) then + return true + end - local moved - repeat - local x, y, z = turtle.point.x, turtle.point.y, turtle.point.z + local moved + repeat + local x, y, z = turtle.point.x, turtle.point.y, turtle.point.z - -- try going the other way - if (turtle.point.heading % 2) == 1 then - turtle.headTowardsX(dx) - else - turtle.headTowardsZ(dz) - end + -- try going the other way + if (turtle.point.heading % 2) == 1 then + turtle.headTowardsX(dx) + else + turtle.headTowardsZ(dz) + end - if gotoEx(dx, dz, dy) then - return true - end + if gotoEx(dx, dz, dy) then + return true + end - if dy then - turtle.gotoY(dy) - end + if dy then + turtle.gotoY(dy) + end - moved = x ~= turtle.point.x or y ~= turtle.point.y or z ~= turtle.point.z - until not moved + moved = x ~= turtle.point.x or y ~= turtle.point.y or z ~= turtle.point.z + until not moved - return false + return false end -- go backwards - turning around if necessary to fight mobs / break blocks function turtle.goback() - local hi = headings[turtle.point.heading] - return turtle._goto({ - x = turtle.point.x - hi.xd, - y = turtle.point.y, - z = turtle.point.z - hi.zd, - heading = turtle.point.heading, - }) + local hi = headings[turtle.point.heading] + return turtle._goto({ + x = turtle.point.x - hi.xd, + y = turtle.point.y, + z = turtle.point.z - hi.zd, + heading = turtle.point.heading, + }) end function turtle.gotoYfirst(pt) - if turtle._gotoY(pt.y) then - if turtle._goto(pt) then - turtle.setHeading(pt.heading) - return true - end - end + if turtle._gotoY(pt.y) then + if turtle._goto(pt) then + turtle.setHeading(pt.heading) + return true + end + end end function turtle.gotoYlast(pt) - if turtle._goto({ x = pt.x, z = pt.z, heading = pt.heading }) then - if turtle.gotoY(pt.y) then - turtle.setHeading(pt.heading) - return true - end - end + if turtle._goto({ x = pt.x, z = pt.z, heading = pt.heading }) then + if turtle.gotoY(pt.y) then + turtle.setHeading(pt.heading) + return true + end + end end function turtle._goto(pt) - local dx, dy, dz, dh = pt.x, pt.y, pt.z, pt.heading - if not turtle.gotoSingleTurn(dx, dy, dz, dh) then - if not gotoMultiTurn(dx, dy, dz) then - return false - end - end - turtle.setHeading(dh) - return true + local dx, dy, dz, dh = pt.x, pt.y, pt.z, pt.heading + if not turtle.gotoSingleTurn(dx, dy, dz, dh) then + if not gotoMultiTurn(dx, dy, dz) then + return false + end + end + turtle.setHeading(dh) + return true end -- avoid lint errors turtle['goto'] = turtle._goto function turtle.gotoX(dx) - turtle.headTowardsX(dx) + turtle.headTowardsX(dx) - while turtle.point.x ~= dx do - if not turtle.forward() then - return false - end - end - return true + while turtle.point.x ~= dx do + if not turtle.forward() then + return false + end + end + return true end function turtle.gotoZ(dz) - turtle.headTowardsZ(dz) + turtle.headTowardsZ(dz) - while turtle.point.z ~= dz do - if not turtle.forward() then - return false - end - end - return true + while turtle.point.z ~= dz do + if not turtle.forward() then + return false + end + end + return true end function turtle.gotoY(dy) - while turtle.point.y > dy do - if not turtle.down() then - return false - end - end + while turtle.point.y > dy do + if not turtle.down() then + return false + end + end - while turtle.point.y < dy do - if not turtle.up() then - return false - end - end - return true + while turtle.point.y < dy do + if not turtle.up() then + return false + end + end + return true end -- [[ Slot management ]] -- function turtle.getSlot(indexOrId, slots) - if type(indexOrId) == 'string' then - slots = slots or turtle.getInventory() - local _,c = string.gsub(indexOrId, ':', '') - if c == 2 then -- combined id and dmg .. ie. minecraft:coal:0 - return Util.find(slots, 'iddmg', indexOrId) - end - return Util.find(slots, 'id', indexOrId) - end + if type(indexOrId) == 'string' then + slots = slots or turtle.getInventory() + local _,c = string.gsub(indexOrId, ':', '') + if c == 2 then -- combined id and dmg .. ie. minecraft:coal:0 + return Util.find(slots, 'iddmg', indexOrId) + end + return Util.find(slots, 'id', indexOrId) + end - local detail = turtle.getItemDetail(indexOrId) - if detail then - return { - name = detail.name, - damage = detail.damage, - count = detail.count, - key = detail.name .. ':' .. detail.damage, + local detail = turtle.getItemDetail(indexOrId) + if detail then + return { + name = detail.name, + damage = detail.damage, + count = detail.count, + key = detail.name .. ':' .. detail.damage, - index = indexOrId, + index = indexOrId, - -- deprecate - qty = detail.count, - dmg = detail.damage, - id = detail.name, - iddmg = detail.name .. ':' .. detail.damage, - } - end + -- deprecate + qty = detail.count, + dmg = detail.damage, + id = detail.name, + iddmg = detail.name .. ':' .. detail.damage, + } + end - return { - qty = 0, -- deprecate - count = 0, - index = indexOrId, - } + return { + qty = 0, -- deprecate + count = 0, + index = indexOrId, + } end function turtle.select(indexOrId) - if type(indexOrId) == 'number' then - return turtle.native.select(indexOrId) - end + if type(indexOrId) == 'number' then + return turtle.native.select(indexOrId) + end - local s = turtle.getSlot(indexOrId) - if s then - turtle.native.select(s.index) - return s - end + local s = turtle.getSlot(indexOrId) + if s then + turtle.native.select(s.index) + return s + end - return false, 'Inventory does not contain item' + return false, 'Inventory does not contain item' end function turtle.getInventory(slots) - slots = slots or { } - for i = 1, 16 do - slots[i] = turtle.getSlot(i) - end - return slots + slots = slots or { } + for i = 1, 16 do + slots[i] = turtle.getSlot(i) + end + return slots end function turtle.getSummedInventory() - local slots = turtle.getFilledSlots() - local t = { } - for _,slot in pairs(slots) do - local entry = t[slot.iddmg] - if not entry then - entry = { - count = 0, - damage = slot.damage, - name = slot.name, - key = slot.key, + local slots = turtle.getFilledSlots() + local t = { } + for _,slot in pairs(slots) do + local entry = t[slot.iddmg] + if not entry then + entry = { + count = 0, + damage = slot.damage, + name = slot.name, + key = slot.key, - -- deprecate - qty = 0, - dmg = slot.dmg, - id = slot.id, - iddmg = slot.iddmg, - } - t[slot.iddmg] = entry - end - entry.qty = entry.qty + slot.qty - entry.count = entry.qty - end - return t + -- deprecate + qty = 0, + dmg = slot.dmg, + id = slot.id, + iddmg = slot.iddmg, + } + t[slot.iddmg] = entry + end + entry.qty = entry.qty + slot.qty + entry.count = entry.qty + end + return t end function turtle.has(item, count) - local slot = turtle.getSummedInventory()[item] - return slot and slot.count >= (count or 1) + local slot = turtle.getSummedInventory()[item] + return slot and slot.count >= (count or 1) end function turtle.getFilledSlots(startSlot) - startSlot = startSlot or 1 + startSlot = startSlot or 1 - local slots = { } - for i = startSlot, 16 do - local count = turtle.getItemCount(i) - if count > 0 then - slots[i] = turtle.getSlot(i) - end - end - return slots + local slots = { } + for i = startSlot, 16 do + local count = turtle.getItemCount(i) + if count > 0 then + slots[i] = turtle.getSlot(i) + end + end + return slots end function turtle.eachFilledSlot(fn) - local slots = turtle.getFilledSlots() - for _,slot in pairs(slots) do - fn(slot) - end + local slots = turtle.getFilledSlots() + for _,slot in pairs(slots) do + fn(slot) + end end function turtle.emptyInventory(dropAction) - dropAction = dropAction or turtle.native.drop - turtle.eachFilledSlot(function(slot) - turtle.select(slot.index) - dropAction() - end) - turtle.select(1) + dropAction = dropAction or turtle.native.drop + turtle.eachFilledSlot(function(slot) + turtle.select(slot.index) + dropAction() + end) + turtle.select(1) end function turtle.reconcileInventory(slots, dropAction) - dropAction = dropAction or turtle.native.drop - for _,s in pairs(slots) do - local qty = turtle.getItemCount(s.index) - if qty > s.qty then - turtle.select(s.index) - dropAction(qty-s.qty, s) - end - end + dropAction = dropAction or turtle.native.drop + for _,s in pairs(slots) do + local qty = turtle.getItemCount(s.index) + if qty > s.qty then + turtle.select(s.index) + dropAction(qty-s.qty, s) + end + end end function turtle.selectSlotWithItems(startSlot) - startSlot = startSlot or 1 - for i = startSlot, 16 do - if turtle.getItemCount(i) > 0 then - turtle.select(i) - return i - end - end + startSlot = startSlot or 1 + for i = startSlot, 16 do + if turtle.getItemCount(i) > 0 then + turtle.select(i) + return i + end + end end function turtle.selectSlotWithQuantity(qty, startSlot) - startSlot = startSlot or 1 + startSlot = startSlot or 1 - for i = startSlot, 16 do - if turtle.getItemCount(i) == qty then - turtle.select(i) - return i - end - end + for i = startSlot, 16 do + if turtle.getItemCount(i) == qty then + turtle.select(i) + return i + end + end end function turtle.selectOpenSlot(startSlot) - return turtle.selectSlotWithQuantity(0, startSlot) + return turtle.selectSlotWithQuantity(0, startSlot) end function turtle.condense() - local slots = turtle.getInventory() + local slots = turtle.getInventory() - for i = 16, 1, -1 do - if slots[i].count > 0 then - for j = 1, i - 1 do - if slots[j].count == 0 or slots[i].key == slots[j].key then - turtle.select(i) - turtle.transferTo(j, 64) - local transferred = slots[i].qty - turtle.getItemCount(i) - slots[j].count = slots[j].count + transferred - slots[i].count = slots[i].count - transferred - slots[j].key = slots[i].key - if slots[i].count == 0 then - break - end - end - end - end - end - return true + for i = 16, 1, -1 do + if slots[i].count > 0 then + for j = 1, i - 1 do + if slots[j].count == 0 or slots[i].key == slots[j].key then + turtle.select(i) + turtle.transferTo(j, 64) + local transferred = slots[i].qty - turtle.getItemCount(i) + slots[j].count = slots[j].count + transferred + slots[i].count = slots[i].count - transferred + slots[j].key = slots[i].key + if slots[i].count == 0 then + break + end + end + end + end + end + return true end function turtle.getItemCount(idOrName) - if type(idOrName) == 'number' then - return turtle.native.getItemCount(idOrName) - end - local slots = turtle.getFilledSlots() - local count = 0 - for _,slot in pairs(slots) do - if slot.iddmg == idOrName or slot.name == idOrName then - count = count + slot.qty - end - end - return count + if type(idOrName) == 'number' then + return turtle.native.getItemCount(idOrName) + end + local slots = turtle.getFilledSlots() + local count = 0 + for _,slot in pairs(slots) do + if slot.iddmg == idOrName or slot.name == idOrName then + count = count + slot.qty + end + end + return count end function turtle.equip(side, item) - if item then - if not turtle.select(item) then - return false, 'Unable to equip ' .. item - end - end + if item then + if not turtle.select(item) then + return false, 'Unable to equip ' .. item + end + end - if side == 'left' then - return turtle.equipLeft() - end - return turtle.equipRight() + if side == 'left' then + return turtle.equipLeft() + end + return turtle.equipRight() end function turtle.isEquipped(item) - if peripheral.getType('left') == item then - return 'left' - elseif peripheral.getType('right') == item then - return 'right' - end + if peripheral.getType('left') == item then + return 'left' + elseif peripheral.getType('right') == item then + return 'right' + end end -- [[ ]] -- function turtle.run(fn, ...) - local args = { ... } - local s, m + local args = { ... } + local s, m - if type(fn) == 'string' then - fn = turtle[fn] - end + if type(fn) == 'string' then + fn = turtle[fn] + end - synchronized(turtle, function() - turtle.resetState() - s, m = pcall(function() fn(unpack(args)) end) - turtle.resetState() - if not s and m then - _G.printError(m) - end - end) + synchronized(turtle, function() + turtle.resetState() + s, m = pcall(function() fn(unpack(args)) end) + turtle.resetState() + if not s and m then + _G.printError(m) + end + end) - return s, m + return s, m end function turtle.abort(abort) - state.abort = abort - if abort then - os.queueEvent('turtle_abort') - end + state.abort = abort + if abort then + os.queueEvent('turtle_abort') + end end -- [[ Pathing ]] -- function turtle.setPersistent(isPersistent) - if isPersistent then - Pathing.setBlocks({ }) - else - Pathing.setBlocks() - end + if isPersistent then + Pathing.setBlocks({ }) + else + Pathing.setBlocks() + end end function turtle.setPathingBox(box) - Pathing.setBox(box) + Pathing.setBox(box) end function turtle.addWorldBlock(pt) - Pathing.addBlock(pt) + Pathing.addBlock(pt) end function turtle.faceAgainst(pt, options) -- 4 sided - options = options or { } - options.dest = { } + options = options or { } + options.dest = { } - for i = 0, 3 do - local hi = Point.facings[i] - table.insert(options.dest, { - x = pt.x + hi.xd, - z = pt.z + hi.zd, - y = pt.y + hi.yd, - heading = (hi.heading + 2) % 4, - }) - end + for i = 0, 3 do + local hi = Point.facings[i] + table.insert(options.dest, { + x = pt.x + hi.xd, + z = pt.z + hi.zd, + y = pt.y + hi.yd, + heading = (hi.heading + 2) % 4, + }) + end - return turtle.pathfind(Point.closest(turtle.point, options.dest), options) + return turtle.pathfind(Point.closest(turtle.point, options.dest), options) end -- move against this point @@ -981,79 +981,79 @@ end -- will face the block (if on same plane) -- if above or below, the heading is undetermined unless specified function turtle.moveAgainst(pt, options) -- 6 sided - options = options or { } - options.dest = { } + options = options or { } + options.dest = { } - for i = 0, 5 do - local hi = turtle.getHeadingInfo(i) - local heading, direction - if i < 4 then - heading = (hi.heading + 2) % 4 - direction = 'forward' - elseif i == 4 then - direction = 'down' - elseif i == 5 then - direction = 'up' - end + for i = 0, 5 do + local hi = turtle.getHeadingInfo(i) + local heading, direction + if i < 4 then + heading = (hi.heading + 2) % 4 + direction = 'forward' + elseif i == 4 then + direction = 'down' + elseif i == 5 then + direction = 'up' + end - table.insert(options.dest, { - x = pt.x + hi.xd, - z = pt.z + hi.zd, - y = pt.y + hi.yd, - direction = direction, - heading = pt.heading or heading, - }) - end + table.insert(options.dest, { + x = pt.x + hi.xd, + z = pt.z + hi.zd, + y = pt.y + hi.yd, + direction = direction, + heading = pt.heading or heading, + }) + end - return turtle.pathfind(Point.closest(turtle.point, options.dest), options) + return turtle.pathfind(Point.closest(turtle.point, options.dest), options) end local actionsAt = { - detect = { - up = turtle.detectUp, - down = turtle.detectDown, - forward = turtle.detect, - }, - dig = { - up = turtle.digUp, - down = turtle.digDown, - forward = turtle.dig, - }, - move = { - up = turtle.moveUp, - down = turtle.moveDown, - forward = turtle.move, - }, - attack = { - up = turtle.attackUp, - down = turtle.attackDown, - forward = turtle.attack, - }, - place = { - up = turtle.placeUp, - down = turtle.placeDown, - forward = turtle.place, - }, - drop = { - up = turtle.dropUp, - down = turtle.dropDown, - forward = turtle.drop, - }, - suck = { - up = turtle.suckUp, - down = turtle.suckDown, - forward = turtle.suck, - }, - compare = { - up = turtle.compareUp, - down = turtle.compareDown, - forward = turtle.compare, - }, - inspect = { - up = turtle.inspectUp, - down = turtle.inspectDown, - forward = turtle.inspect, - }, + detect = { + up = turtle.detectUp, + down = turtle.detectDown, + forward = turtle.detect, + }, + dig = { + up = turtle.digUp, + down = turtle.digDown, + forward = turtle.dig, + }, + move = { + up = turtle.moveUp, + down = turtle.moveDown, + forward = turtle.move, + }, + attack = { + up = turtle.attackUp, + down = turtle.attackDown, + forward = turtle.attack, + }, + place = { + up = turtle.placeUp, + down = turtle.placeDown, + forward = turtle.place, + }, + drop = { + up = turtle.dropUp, + down = turtle.dropDown, + forward = turtle.drop, + }, + suck = { + up = turtle.suckUp, + down = turtle.suckDown, + forward = turtle.suck, + }, + compare = { + up = turtle.compareUp, + down = turtle.compareDown, + forward = turtle.compare, + }, + inspect = { + up = turtle.inspectUp, + down = turtle.inspectDown, + forward = turtle.inspect, + }, } -- pt = { x,y,z,heading,direction } @@ -1061,56 +1061,56 @@ local actionsAt = { -- heading can be provided to tell which way to face during action -- ex: place a block at the point from above facing east local function _actionAt(action, pt, ...) - if not pt.heading and not pt.direction then - local msg - pt, msg = turtle.moveAgainst(pt) - if pt then - return action[pt.direction](...) - end - return pt, msg - end + if not pt.heading and not pt.direction then + local msg + pt, msg = turtle.moveAgainst(pt) + if pt then + return action[pt.direction](...) + end + return pt, msg + end - local reversed = - { [0] = 2, [1] = 3, [2] = 0, [3] = 1, [4] = 5, [5] = 4, } - local dir = reversed[headings[pt.direction or pt.heading].heading] - local apt = { x = pt.x + headings[dir].xd, - y = pt.y + headings[dir].yd, - z = pt.z + headings[dir].zd, } - local direction + local reversed = + { [0] = 2, [1] = 3, [2] = 0, [3] = 1, [4] = 5, [5] = 4, } + local dir = reversed[headings[pt.direction or pt.heading].heading] + local apt = { x = pt.x + headings[dir].xd, + y = pt.y + headings[dir].yd, + z = pt.z + headings[dir].zd, } + local direction - -- ex: place a block at this point, from above, facing east - if dir < 4 then - apt.heading = (dir + 2) % 4 - direction = 'forward' - elseif dir == 4 then - apt.heading = pt.heading - direction = 'down' - elseif dir == 5 then - apt.heading = pt.heading - direction = 'up' - end + -- ex: place a block at this point, from above, facing east + if dir < 4 then + apt.heading = (dir + 2) % 4 + direction = 'forward' + elseif dir == 4 then + apt.heading = pt.heading + direction = 'down' + elseif dir == 5 then + apt.heading = pt.heading + direction = 'up' + end - if turtle.pathfind(apt) then - return action[direction](...) - end + if turtle.pathfind(apt) then + return action[direction](...) + end end local function _actionDownAt(action, pt, ...) - pt = Util.shallowCopy(pt) - pt.direction = Point.DOWN - return _actionAt(action, pt, ...) + pt = Util.shallowCopy(pt) + pt.direction = Point.DOWN + return _actionAt(action, pt, ...) end local function _actionUpAt(action, pt, ...) - pt = Util.shallowCopy(pt) - pt.direction = Point.UP - return _actionAt(action, pt, ...) + pt = Util.shallowCopy(pt) + pt.direction = Point.UP + return _actionAt(action, pt, ...) end local function _actionForwardAt(action, pt, ...) - if turtle.faceAgainst(pt) then - return action.forward(...) - end + if turtle.faceAgainst(pt) then + return action.forward(...) + end end function turtle.detectAt(pt) return _actionAt(actionsAt.detect, pt) end @@ -1155,15 +1155,15 @@ function turtle.inspectUpAt(pt) return _actionUpAt(actionsAt.inspect, p -- [[ GPS ]] -- function turtle.enableGPS(timeout) - local pt = GPS.getPointAndHeading(timeout) - if pt then - turtle.setPoint(pt, true) - return turtle.point - end + local pt = GPS.getPointAndHeading(timeout) + if pt then + turtle.setPoint(pt, true) + return turtle.point + end end function turtle.addFeatures(...) - for _,feature in pairs({ ... }) do - require('turtle.' .. feature) - end + for _,feature in pairs({ ... }) do + require('turtle.' .. feature) + end end diff --git a/sys/extensions/7.multishell.lua b/sys/extensions/7.multishell.lua index a3b678e..0a4a81d 100644 --- a/sys/extensions/7.multishell.lua +++ b/sys/extensions/7.multishell.lua @@ -1,4 +1,4 @@ -_G.requireInjector() +_G.requireInjector(_ENV) local Config = require('config') local Util = require('util') @@ -25,367 +25,371 @@ shell.setEnv('multishell', multishell) multishell.term = parentTerm --deprecated local config = { - standard = { - textColor = colors.lightGray, - tabBarTextColor = colors.lightGray, - focusTextColor = colors.white, - backgroundColor = colors.gray, - tabBarBackgroundColor = colors.gray, - focusBackgroundColor = colors.gray, - }, - color = { - textColor = colors.lightGray, - tabBarTextColor = colors.lightGray, - focusTextColor = colors.white, - backgroundColor = colors.gray, - tabBarBackgroundColor = colors.gray, - focusBackgroundColor = colors.gray, - }, + standard = { + textColor = colors.lightGray, + tabBarTextColor = colors.lightGray, + focusTextColor = colors.white, + backgroundColor = colors.gray, + tabBarBackgroundColor = colors.gray, + focusBackgroundColor = colors.gray, + }, + color = { + textColor = colors.lightGray, + tabBarTextColor = colors.lightGray, + focusTextColor = colors.white, + backgroundColor = colors.gray, + tabBarBackgroundColor = colors.gray, + focusBackgroundColor = colors.gray, + }, } Config.load('multishell', config) local _colors = parentTerm.isColor() and config.color or config.standard local function redrawMenu() - if not tabsDirty then - os.queueEvent('multishell_redraw') - tabsDirty = true - end + if not tabsDirty then + os.queueEvent('multishell_redraw') + tabsDirty = true + end end function multishell.getFocus() - local currentTab = kernel.getFocused() - return currentTab.uid + local currentTab = kernel.getFocused() + return currentTab.uid end function multishell.setFocus(tabId) - return kernel.raise(tabId) + return kernel.raise(tabId) end function multishell.getTitle(tabId) - local tab = kernel.find(tabId) - return tab and tab.title + local tab = kernel.find(tabId) + return tab and tab.title end function multishell.setTitle(tabId, title) - local tab = kernel.find(tabId) - if tab then - tab.title = title - redrawMenu() - end + local tab = kernel.find(tabId) + if tab then + tab.title = title + redrawMenu() + end end function multishell.getCurrent() - local runningTab = kernel.getCurrent() - return runningTab and runningTab.uid + local runningTab = kernel.getCurrent() + return runningTab and runningTab.uid end function multishell.getTab(tabId) - return kernel.find(tabId) + return kernel.find(tabId) end function multishell.terminate(tabId) - os.queueEvent('multishell_terminate', tabId) + os.queueEvent('multishell_terminate', tabId) end function multishell.getTabs() - return kernel.routines + return kernel.routines end function multishell.launch( tProgramEnv, sProgramPath, ... ) - -- backwards compatibility - return multishell.openTab({ - env = tProgramEnv, - path = sProgramPath, - args = { ... }, - }) + -- backwards compatibility + return multishell.openTab({ + env = tProgramEnv, + path = sProgramPath, + args = { ... }, + }) end function multishell.openTab(tab) - if not tab.title and tab.path then - tab.title = fs.getName(tab.path):match('([^%.]+)') - end - tab.title = tab.title or 'untitled' - tab.window = tab.window or window.create(parentTerm, 1, 2, w, h - 1, false) - tab.terminal = tab.terminal or tab.window + if not tab.title and tab.path then + tab.title = fs.getName(tab.path):match('([^%.]+)') + end + tab.title = tab.title or 'untitled' + tab.window = tab.window or window.create(parentTerm, 1, 2, w, h - 1, false) + tab.terminal = tab.terminal or tab.window - local routine = kernel.newRoutine(tab) + local routine = kernel.newRoutine(tab) - routine.co = coroutine.create(function() - local result, err + routine.co = coroutine.create(function() + local result, err - if tab.fn then - result, err = Util.runFunction(routine.env, tab.fn, table.unpack(tab.args or { } )) - elseif tab.path then - result, err = Util.run(routine.env, tab.path, table.unpack(tab.args or { } )) - else - err = 'multishell: invalid tab' - end + if tab.fn then + result, err = Util.runFunction(routine.env, tab.fn, table.unpack(tab.args or { } )) + elseif tab.path then + result, err = Util.run(routine.env, tab.path, table.unpack(tab.args or { } )) + else + err = 'multishell: invalid tab' + end - if not result and err and err ~= 'Terminated' then - if err then - printError(tostring(err)) - end - print('\nPress enter to close') - routine.isDead = true - routine.hidden = false - while true do - local e, code = os.pullEventRaw('key') - if e == 'terminate' or e == 'key' and code == keys.enter then - break - end - end - end - end) + if not result and err and err ~= 'Terminated' then + if err then + printError(tostring(err)) + end + print('\nPress enter to close') + routine.isDead = true + routine.hidden = false + while true do + local e, code = os.pullEventRaw('key') + if e == 'terminate' or e == 'key' and code == keys.enter then + break + end + end + end + end) - kernel.launch(routine) + kernel.launch(routine) - if tab.focused then - multishell.setFocus(routine.uid) - else - redrawMenu() - end - return routine.uid + if tab.focused then + multishell.setFocus(routine.uid) + else + redrawMenu() + end + return routine.uid end function multishell.hideTab(tabId) - local tab = kernel.find(tabId) - if tab then - tab.hidden = true - kernel.lower(tab.uid) - redrawMenu() - end + local tab = kernel.find(tabId) + if tab then + tab.hidden = true + kernel.lower(tab.uid) + redrawMenu() + end end function multishell.unhideTab(tabId) - local tab = kernel.find(tabId) - if tab then - tab.hidden = false - redrawMenu() - end + local tab = kernel.find(tabId) + if tab then + tab.hidden = false + redrawMenu() + end end function multishell.getCount() - return #kernel.routines + return #kernel.routines end kernel.hook('kernel_focus', function(_, eventData) - local previous = eventData[2] - if previous then - local routine = kernel.find(previous) - if routine and routine.window then - routine.window.setVisible(false) - if routine.hidden then - kernel.lower(previous) - end - end - end + local previous = eventData[2] + if previous then + local routine = kernel.find(previous) + if routine and routine.window then + routine.window.setVisible(false) + if routine.hidden then + kernel.lower(previous) + end + end + end - local focused = kernel.find(eventData[1]) - if focused and focused.window then - focused.window.setVisible(true) - end + local focused = kernel.find(eventData[1]) + if focused and focused.window then + focused.window.setVisible(true) + end - redrawMenu() + redrawMenu() end) kernel.hook('multishell_terminate', function(_, eventData) - local tab = kernel.find(eventData[1]) - if tab and not tab.isOverview then - if coroutine.status(tab.co) ~= 'dead' then - tab:resume("terminate") - end - end - return true + local tab = kernel.find(eventData[1]) + if tab and not tab.isOverview then + if coroutine.status(tab.co) ~= 'dead' then + tab:resume("terminate") + end + end + return true +end) + +kernel.hook('terminate', function() + return kernel.getFocused().isOverview end) kernel.hook('multishell_redraw', function() - tabsDirty = false + tabsDirty = false - local function write(x, text, bg, fg) - parentTerm.setBackgroundColor(bg) - parentTerm.setTextColor(fg) - parentTerm.setCursorPos(x, 1) - parentTerm.write(text) - end + local function write(x, text, bg, fg) + parentTerm.setBackgroundColor(bg) + parentTerm.setTextColor(fg) + parentTerm.setCursorPos(x, 1) + parentTerm.write(text) + end - local bg = _colors.tabBarBackgroundColor - parentTerm.setBackgroundColor(bg) - parentTerm.setCursorPos(1, 1) - parentTerm.clearLine() + local bg = _colors.tabBarBackgroundColor + parentTerm.setBackgroundColor(bg) + parentTerm.setCursorPos(1, 1) + parentTerm.clearLine() - local currentTab = kernel.getFocused() + local currentTab = kernel.getFocused() - for _,tab in pairs(kernel.routines) do - if tab.hidden and tab ~= currentTab then - tab.width = 0 - else - tab.width = #tab.title + 1 - end - end + for _,tab in pairs(kernel.routines) do + if tab.hidden and tab ~= currentTab then + tab.width = 0 + else + tab.width = #tab.title + 1 + end + end - local function width() - local tw = 0 - Util.each(kernel.routines, function(t) tw = tw + t.width end) - return tw - end + local function width() + local tw = 0 + Util.each(kernel.routines, function(t) tw = tw + t.width end) + return tw + end - while width() > w - 3 do - local tab = select(2, - Util.spairs(kernel.routines, function(a, b) return a.width > b.width end)()) - tab.width = tab.width - 1 - end + while width() > w - 3 do + local tab = select(2, + Util.spairs(kernel.routines, function(a, b) return a.width > b.width end)()) + tab.width = tab.width - 1 + end - local function compareTab(a, b) - if a.hidden then return false end - return b.hidden or a.uid < b.uid - end + local function compareTab(a, b) + if a.hidden then return false end + return b.hidden or a.uid < b.uid + end - local tabX = 0 - for _,tab in Util.spairs(kernel.routines, compareTab) do - if tab.width > 0 then - tab.sx = tabX + 1 - tab.ex = tabX + tab.width - tabX = tabX + tab.width - if tab ~= currentTab then - write(tab.sx, tab.title:sub(1, tab.width - 1), - _colors.backgroundColor, _colors.textColor) - end - end - end + local tabX = 0 + for _,tab in Util.spairs(kernel.routines, compareTab) do + if tab.width > 0 then + tab.sx = tabX + 1 + tab.ex = tabX + tab.width + tabX = tabX + tab.width + if tab ~= currentTab then + write(tab.sx, tab.title:sub(1, tab.width - 1), + _colors.backgroundColor, _colors.textColor) + end + end + end - if currentTab then - write(currentTab.sx - 1, - ' ' .. currentTab.title:sub(1, currentTab.width - 1) .. ' ', - _colors.focusBackgroundColor, _colors.focusTextColor) - if not currentTab.isOverview then - write(w, closeInd, _colors.backgroundColor, _colors.focusTextColor) - end - end + if currentTab then + write(currentTab.sx - 1, + ' ' .. currentTab.title:sub(1, currentTab.width - 1) .. ' ', + _colors.focusBackgroundColor, _colors.focusTextColor) + if not currentTab.isOverview then + write(w, closeInd, _colors.backgroundColor, _colors.focusTextColor) + end + end - if currentTab and currentTab.window then - currentTab.window.restoreCursor() - end + if currentTab and currentTab.window then + currentTab.window.restoreCursor() + end - return true + return true end) kernel.hook('term_resize', function(_, eventData) - if not eventData[1] then --- TEST - w,h = parentTerm.getSize() + if not eventData[1] then --- TEST + w,h = parentTerm.getSize() - local windowHeight = h-1 + local windowHeight = h-1 - for _,key in pairs(Util.keys(kernel.routines)) do - local tab = kernel.routines[key] - local x,y = tab.window.getCursorPos() - if y > windowHeight then - tab.window.scroll(y - windowHeight) - tab.window.setCursorPos(x, windowHeight) - end - tab.window.reposition(1, 2, w, windowHeight) - end + for _,key in pairs(Util.keys(kernel.routines)) do + local tab = kernel.routines[key] + local x,y = tab.window.getCursorPos() + if y > windowHeight then + tab.window.scroll(y - windowHeight) + tab.window.setCursorPos(x, windowHeight) + end + tab.window.reposition(1, 2, w, windowHeight) + end - redrawMenu() - end + redrawMenu() + end end) kernel.hook('mouse_click', function(_, eventData) - local x, y = eventData[2], eventData[3] + local x, y = eventData[2], eventData[3] - if y == 1 then - if x == 1 then - multishell.setFocus(overviewId) - elseif x == w then - local currentTab = kernel.getFocused() - if currentTab then - multishell.terminate(currentTab.uid) - end - else - for _,tab in pairs(kernel.routines) do - if not tab.hidden and tab.sx then - if x >= tab.sx and x <= tab.ex then - multishell.setFocus(tab.uid) - break - end - end - end - end - return true - end - eventData[3] = eventData[3] - 1 + if y == 1 then + if x == 1 then + multishell.setFocus(overviewId) + elseif x == w then + local currentTab = kernel.getFocused() + if currentTab then + multishell.terminate(currentTab.uid) + end + else + for _,tab in pairs(kernel.routines) do + if not tab.hidden and tab.sx then + if x >= tab.sx and x <= tab.ex then + multishell.setFocus(tab.uid) + break + end + end + end + end + return true + end + eventData[3] = eventData[3] - 1 end) kernel.hook({ 'mouse_up', 'mouse_drag' }, function(_, eventData) - eventData[3] = eventData[3] - 1 + eventData[3] = eventData[3] - 1 end) kernel.hook('mouse_scroll', function(_, eventData) - if eventData[3] == 1 then - return true - end - eventData[3] = eventData[3] - 1 + if eventData[3] == 1 then + return true + end + eventData[3] = eventData[3] - 1 end) local function startup() - local success = true + local success = true - local function runDir(directory, open) - if not fs.exists(directory) then - return true - end + local function runDir(directory, open) + if not fs.exists(directory) then + return true + end - local files = fs.list(directory) - table.sort(files) + local files = fs.list(directory) + table.sort(files) - for _,file in ipairs(files) do - os.sleep(0) - local result, err = open(directory .. '/' .. file) + for _,file in ipairs(files) do + os.sleep(0) + local result, err = open(directory .. '/' .. file) - if result then - if term.isColor() then - term.setTextColor(colors.green) - end - term.write('[PASS] ') - term.setTextColor(colors.white) - term.write(fs.combine(directory, file)) - print() - else - if term.isColor() then - term.setTextColor(colors.red) - end - term.write('[FAIL] ') - term.setTextColor(colors.white) - term.write(fs.combine(directory, file)) - if err then - _G.printError('\n' .. err) - end - print() - success = false - end - end - end + if result then + if term.isColor() then + term.setTextColor(colors.green) + end + term.write('[PASS] ') + term.setTextColor(colors.white) + term.write(fs.combine(directory, file)) + print() + else + if term.isColor() then + term.setTextColor(colors.red) + end + term.write('[FAIL] ') + term.setTextColor(colors.white) + term.write(fs.combine(directory, file)) + if err then + _G.printError('\n' .. err) + end + print() + success = false + end + end + end - runDir('sys/autorun', shell.run) - runDir('usr/autorun', shell.run) + runDir('sys/autorun', shell.run) + runDir('usr/autorun', shell.run) - if not success then - multishell.setFocus(multishell.getCurrent()) - printError('\nA startup program has errored') - os.pullEvent('terminate') - end + if not success then + multishell.setFocus(multishell.getCurrent()) + printError('\nA startup program has errored') + os.pullEvent('terminate') + end end kernel.hook('kernel_ready', function() - overviewId = multishell.openTab({ - path = 'sys/apps/Overview.lua', - isOverview = true, - focused = true, - title = '+', - }) + overviewId = multishell.openTab({ + path = 'sys/apps/Overview.lua', + isOverview = true, + focused = true, + title = '+', + }) - multishell.openTab({ - fn = startup, - title = 'Autorun', - }) + multishell.openTab({ + fn = startup, + title = 'Autorun', + }) end) diff --git a/sys/kernel.lua b/sys/kernel.lua index 90e0ed6..7ff088e 100644 --- a/sys/kernel.lua +++ b/sys/kernel.lua @@ -4,9 +4,9 @@ local Terminal = require('terminal') local Util = require('util') _G.kernel = { - UID = 0, - hooks = { }, - routines = { }, + UID = 0, + hooks = { }, + routines = { }, } local fs = _G.fs @@ -23,271 +23,271 @@ kernel.window = window.create(kernel.terminal, 1, 1, w, h, false) Terminal.scrollable(kernel.window) local focusedRoutineEvents = Util.transpose { - 'char', 'key', 'key_up', - 'mouse_click', 'mouse_drag', 'mouse_scroll', 'mouse_up', - 'paste', 'terminate', + 'char', 'key', 'key_up', + 'mouse_click', 'mouse_drag', 'mouse_scroll', 'mouse_up', + 'paste', 'terminate', } _G.debug = function(pattern, ...) - local oldTerm = term.redirect(kernel.window) - Util.print(pattern, ...) - term.redirect(oldTerm) + local oldTerm = term.redirect(kernel.window) + Util.print(pattern, ...) + term.redirect(oldTerm) end -- any function that runs in a kernel hook does not run in -- a separate coroutine or have a window. an error in a hook -- function will crash the system. function kernel.hook(event, fn) - if type(event) == 'table' then - for _,v in pairs(event) do - kernel.hook(v, fn) - end - else - if not kernel.hooks[event] then - kernel.hooks[event] = { } - end - table.insert(kernel.hooks[event], fn) - end + if type(event) == 'table' then + for _,v in pairs(event) do + kernel.hook(v, fn) + end + else + if not kernel.hooks[event] then + kernel.hooks[event] = { } + end + table.insert(kernel.hooks[event], fn) + end end -- you can only unhook from within the function that hooked function kernel.unhook(event, fn) - local eventHooks = kernel.hooks[event] - if eventHooks then - Util.removeByValue(eventHooks, fn) - if #eventHooks == 0 then - kernel.hooks[event] = nil - end - end + local eventHooks = kernel.hooks[event] + if eventHooks then + Util.removeByValue(eventHooks, fn) + if #eventHooks == 0 then + kernel.hooks[event] = nil + end + end end local Routine = { } function Routine:resume(event, ...) - if not self.co or coroutine.status(self.co) == 'dead' then - return - end + if not self.co or coroutine.status(self.co) == 'dead' then + return + end - if not self.filter or self.filter == event or event == "terminate" then - local previousTerm = term.redirect(self.terminal) + if not self.filter or self.filter == event or event == "terminate" then + local previousTerm = term.redirect(self.terminal) - local previous = kernel.running - kernel.running = self -- stupid shell set title - local ok, result = coroutine.resume(self.co, event, ...) - kernel.running = previous + local previous = kernel.running + kernel.running = self -- stupid shell set title + local ok, result = coroutine.resume(self.co, event, ...) + kernel.running = previous - if ok then - self.filter = result - else - _G.printError(result) - end + if ok then + self.filter = result + else + _G.printError(result) + end - self.terminal = term.current() - term.redirect(previousTerm) + self.terminal = term.current() + term.redirect(previousTerm) - if not ok and self.haltOnError then - error(result) - end - if coroutine.status(self.co) == 'dead' then - Util.removeByValue(kernel.routines, self) - if #kernel.routines > 0 then - os.queueEvent('kernel_focus', kernel.routines[1].uid) - end - if self.haltOnExit then - kernel.halt() - end - end - return ok, result - end + if not ok and self.haltOnError then + error(result) + end + if coroutine.status(self.co) == 'dead' then + Util.removeByValue(kernel.routines, self) + if #kernel.routines > 0 then + os.queueEvent('kernel_focus', kernel.routines[1].uid) + end + if self.haltOnExit then + kernel.halt() + end + end + return ok, result + end end function kernel.getFocused() - return kernel.routines[1] + return kernel.routines[1] end function kernel.getCurrent() - return kernel.running + return kernel.running end function kernel.newRoutine(args) - kernel.UID = kernel.UID + 1 + kernel.UID = kernel.UID + 1 - local routine = setmetatable({ - uid = kernel.UID, - timestamp = os.clock(), - terminal = kernel.window, - window = kernel.window, - }, { __index = Routine }) + local routine = setmetatable({ + uid = kernel.UID, + timestamp = os.clock(), + terminal = kernel.window, + window = kernel.window, + }, { __index = Routine }) - Util.merge(routine, args) - routine.env = args.env or Util.shallowCopy(shell.getEnv()) + Util.merge(routine, args) + routine.env = args.env or Util.shallowCopy(shell.getEnv()) - return routine + return routine end function kernel.launch(routine) - routine.co = routine.co or coroutine.create(function() - local result, err + routine.co = routine.co or coroutine.create(function() + local result, err - if routine.fn then - result, err = Util.runFunction(routine.env, routine.fn, table.unpack(routine.args or { } )) - elseif routine.path then - result, err = Util.run(routine.env, routine.path, table.unpack(routine.args or { } )) - else - err = 'kernel: invalid routine' - end + if routine.fn then + result, err = Util.runFunction(routine.env, routine.fn, table.unpack(routine.args or { } )) + elseif routine.path then + result, err = Util.run(routine.env, routine.path, table.unpack(routine.args or { } )) + else + err = 'kernel: invalid routine' + end - if not result and err ~= 'Terminated' then - error(err or 'Error occurred', 2) - end - end) + if not result and err ~= 'Terminated' then + error(err or 'Error occurred', 2) + end + end) - table.insert(kernel.routines, routine) + table.insert(kernel.routines, routine) - local s, m = routine:resume() + local s, m = routine:resume() - return not s and s or routine.uid, m + return not s and s or routine.uid, m end function kernel.run(args) - local routine = kernel.newRoutine(args) - kernel.launch(routine) - return routine + local routine = kernel.newRoutine(args) + kernel.launch(routine) + return routine end function kernel.raise(uid) - local routine = Util.find(kernel.routines, 'uid', uid) + local routine = Util.find(kernel.routines, 'uid', uid) - if routine then - local previous = kernel.routines[1] - if routine ~= previous then - Util.removeByValue(kernel.routines, routine) - table.insert(kernel.routines, 1, routine) - end - os.queueEvent('kernel_focus', routine.uid, previous and previous.uid) - return true - end - return false + if routine then + local previous = kernel.routines[1] + if routine ~= previous then + Util.removeByValue(kernel.routines, routine) + table.insert(kernel.routines, 1, routine) + end + os.queueEvent('kernel_focus', routine.uid, previous and previous.uid) + return true + end + return false end function kernel.lower(uid) - local routine = Util.find(kernel.routines, 'uid', uid) + local routine = Util.find(kernel.routines, 'uid', uid) - if routine and #kernel.routines > 1 then - if routine == kernel.routines[1] then - local nextRoutine = kernel.routines[2] - if nextRoutine then - kernel.raise(nextRoutine.uid) - end - end + if routine and #kernel.routines > 1 then + if routine == kernel.routines[1] then + local nextRoutine = kernel.routines[2] + if nextRoutine then + kernel.raise(nextRoutine.uid) + end + end - Util.removeByValue(kernel.routines, routine) - table.insert(kernel.routines, routine) - return true - end - return false + Util.removeByValue(kernel.routines, routine) + table.insert(kernel.routines, routine) + return true + end + return false end function kernel.find(uid) - return Util.find(kernel.routines, 'uid', uid) + return Util.find(kernel.routines, 'uid', uid) end function kernel.halt() - os.queueEvent('kernel_halt') + os.queueEvent('kernel_halt') end function kernel.event(event, eventData) - local stopPropagation + local stopPropagation - local eventHooks = kernel.hooks[event] - if eventHooks then - for i = #eventHooks, 1, -1 do - stopPropagation = eventHooks[i](event, eventData) - if stopPropagation then - break - end - end - end + local eventHooks = kernel.hooks[event] + if eventHooks then + for i = #eventHooks, 1, -1 do + stopPropagation = eventHooks[i](event, eventData) + if stopPropagation then + break + end + end + end - if not stopPropagation then - if focusedRoutineEvents[event] then - local active = kernel.routines[1] - if active then - active:resume(event, table.unpack(eventData)) - end - else - -- Passthrough to all processes - for _,routine in pairs(Util.shallowCopy(kernel.routines)) do - routine:resume(event, table.unpack(eventData)) - end - end - end + if not stopPropagation then + if focusedRoutineEvents[event] then + local active = kernel.routines[1] + if active then + active:resume(event, table.unpack(eventData)) + end + else + -- Passthrough to all processes + for _,routine in pairs(Util.shallowCopy(kernel.routines)) do + routine:resume(event, table.unpack(eventData)) + end + end + end end function kernel.start() - local s, m = pcall(function() - repeat - local eventData = { os.pullEventRaw() } - local event = table.remove(eventData, 1) - kernel.event(event, eventData) - until event == 'kernel_halt' - end) + local s, m = pcall(function() + repeat + local eventData = { os.pullEventRaw() } + local event = table.remove(eventData, 1) + kernel.event(event, eventData) + until event == 'kernel_halt' + end) - if not s then - kernel.window.setVisible(true) - term.redirect(kernel.window) - print('\nCrash detected\n') - _G.printError(m) - end - term.redirect(kernel.terminal) + if not s then + kernel.window.setVisible(true) + term.redirect(kernel.window) + print('\nCrash detected\n') + _G.printError(m) + end + term.redirect(kernel.terminal) end local function init(...) - local args = { ... } + local args = { ... } - local runLevel = #args > 0 and 6 or 7 + local runLevel = #args > 0 and 6 or 7 - print('Starting Opus OS') - local dir = 'sys/extensions' - local files = fs.list(dir) - table.sort(files) - for _,file in ipairs(files) do - local level = file:match('(%d).%S+.lua') - if tonumber(level) <= runLevel then - local s, m = shell.run(fs.combine(dir, file)) - if not s then - error(m) - end - os.sleep(0) - end - end + print('Starting Opus OS') + local dir = 'sys/extensions' + local files = fs.list(dir) + table.sort(files) + for _,file in ipairs(files) do + local level = file:match('(%d).%S+.lua') + if tonumber(level) <= runLevel then + local s, m = shell.run(fs.combine(dir, file)) + if not s then + error(m) + end + os.sleep(0) + end + end - os.queueEvent('kernel_ready') + os.queueEvent('kernel_ready') - if args[1] then - kernel.hook('kernel_ready', function() - local s, m = kernel.run({ - title = args[1], - path = 'sys/apps/shell', - args = args, - haltOnExit = true, - haltOnError = true, - terminal = kernel.terminal, - }) - if s then - kernel.raise(s.uid) - else - error(m) - end - end) - end + if args[1] then + kernel.hook('kernel_ready', function() + local s, m = kernel.run({ + title = args[1], + path = 'sys/apps/shell', + args = args, + haltOnExit = true, + haltOnError = true, + terminal = kernel.terminal, + }) + if s then + kernel.raise(s.uid) + else + error(m) + end + end) + end end kernel.run({ - fn = init, - title = 'init', - haltOnError = true, - args = { ... }, + fn = init, + title = 'init', + haltOnError = true, + args = { ... }, }) kernel.start() diff --git a/sys/network/peripheral.lua b/sys/network/peripheral.lua index 3f10411..a491b30 100644 --- a/sys/network/peripheral.lua +++ b/sys/network/peripheral.lua @@ -1,5 +1,5 @@ --[[ - Allow sharing of local peripherals. + Allow sharing of local peripherals. ]]-- local Event = require('event') @@ -7,76 +7,76 @@ local Peripheral = require('peripheral') local Socket = require('socket') Event.addRoutine(function() - print('peripheral: listening on port 189') - while true do - local socket = Socket.server(189) + print('peripheral: listening on port 189') + while true do + local socket = Socket.server(189) - print('peripheral: connection from ' .. socket.dhost) + print('peripheral: connection from ' .. socket.dhost) - Event.addRoutine(function() - local uri = socket:read(2) - if uri then - local peripheral = Peripheral.lookup(uri) + Event.addRoutine(function() + local uri = socket:read(2) + if uri then + local peripheral = Peripheral.lookup(uri) -- only 1 proxy of this device can happen at one time -- need to prevent multiple shares - if not peripheral then - print('peripheral: invalid peripheral ' .. uri) - socket:write('Invalid peripheral: ' .. uri) - else - print('peripheral: proxing ' .. uri) - local proxy = { - methods = { } - } + if not peripheral then + print('peripheral: invalid peripheral ' .. uri) + socket:write('Invalid peripheral: ' .. uri) + else + print('peripheral: proxing ' .. uri) + local proxy = { + methods = { } + } - if peripheral.blit then - --peripheral = Util.shallowCopy(peripheral) - peripheral.fastBlit = function(data) - for _,v in ipairs(data) do - peripheral[v.fn](unpack(v.args)) - end - end - end + if peripheral.blit then + --peripheral = Util.shallowCopy(peripheral) + peripheral.fastBlit = function(data) + for _,v in ipairs(data) do + peripheral[v.fn](unpack(v.args)) + end + end + end - for k,v in pairs(peripheral) do - if type(v) == 'function' then - table.insert(proxy.methods, k) - else - proxy[k] = v - end - end + for k,v in pairs(peripheral) do + if type(v) == 'function' then + table.insert(proxy.methods, k) + else + proxy[k] = v + end + end - socket:write(proxy) + socket:write(proxy) - if proxy.type == 'monitor' then - peripheral.eventChannel = function(...) - socket:write({ - fn = 'event', - data = { ... } - }) - end - end + if proxy.type == 'monitor' then + peripheral.eventChannel = function(...) + socket:write({ + fn = 'event', + data = { ... } + }) + end + end - while true do - local data = socket:read() - if not data then - print('peripheral: lost connection from ' .. socket.dhost) - break - end - if not _G.device[peripheral.name] then - print('periperal: detached') - socket:close() - break - end - if peripheral[data.fn] then - socket:write({ peripheral[data.fn](table.unpack(data.args)) }) - end - end + while true do + local data = socket:read() + if not data then + print('peripheral: lost connection from ' .. socket.dhost) + break + end + if not _G.device[peripheral.name] then + print('periperal: detached') + socket:close() + break + end + if peripheral[data.fn] then + socket:write({ peripheral[data.fn](table.unpack(data.args)) }) + end + end - peripheral.eventChannel = nil - peripheral.fastBlit = nil - end - end - end) - end + peripheral.eventChannel = nil + peripheral.fastBlit = nil + end + end + end) + end end) diff --git a/sys/network/proxy.lua b/sys/network/proxy.lua index 62dab17..65ada42 100644 --- a/sys/network/proxy.lua +++ b/sys/network/proxy.lua @@ -2,18 +2,18 @@ local Event = require('event') local Socket = require('socket') Event.addRoutine(function() - while true do - print('proxy: listening on port 188') - local socket = Socket.server(188) + while true do + print('proxy: listening on port 188') + local socket = Socket.server(188) - print('proxy: connection from ' .. socket.dhost) + print('proxy: connection from ' .. socket.dhost) - Event.addRoutine(function() - local api = socket:read(2) - if api then + Event.addRoutine(function() + local api = socket:read(2) + if api then local proxy = _G[api] - local methods = { } + local methods = { } for k,v in pairs(proxy) do if type(v) == 'function' then table.insert(methods, k) @@ -21,14 +21,14 @@ Event.addRoutine(function() end socket:write(methods) - while true do - local data = socket:read() - if not data then + while true do + local data = socket:read() + if not data then print('proxy: lost connection from ' .. socket.dhost) - break - end - socket:write({ proxy[data.fn](table.unpack(data.args)) }) - end + break + end + socket:write({ proxy[data.fn](table.unpack(data.args)) }) + end end end) end diff --git a/sys/network/samba.lua b/sys/network/samba.lua index 6469015..c87c1c0 100644 --- a/sys/network/samba.lua +++ b/sys/network/samba.lua @@ -7,77 +7,77 @@ local fileUid = 0 local fileHandles = { } local function remoteOpen(fn, fl) - local fh = fs.open(fn, fl) - if fh then - local methods = { 'close', 'write', 'writeLine', 'flush', 'read', 'readLine', 'readAll', } - fileUid = fileUid + 1 - fileHandles[fileUid] = fh + local fh = fs.open(fn, fl) + if fh then + local methods = { 'close', 'write', 'writeLine', 'flush', 'read', 'readLine', 'readAll', } + fileUid = fileUid + 1 + fileHandles[fileUid] = fh - local vfh = { - methods = { }, - fileUid = fileUid, - } + local vfh = { + methods = { }, + fileUid = fileUid, + } - for _,m in ipairs(methods) do - if fh[m] then - table.insert(vfh.methods, m) - end - end - return vfh - end + for _,m in ipairs(methods) do + if fh[m] then + table.insert(vfh.methods, m) + end + end + return vfh + end end local function remoteFileOperation(fileId, op, ...) - local fh = fileHandles[fileId] - if fh then - return fh[op](...) - end + local fh = fileHandles[fileId] + if fh then + return fh[op](...) + end end local function sambaConnection(socket) - while true do - local msg = socket:read() - if not msg then - break - end - local fn = fs[msg.fn] - if msg.fn == 'open' then - fn = remoteOpen - elseif msg.fn == 'fileOp' then - fn = remoteFileOperation - end - local ret - local s, m = pcall(function() - ret = fn(unpack(msg.args)) - end) - if not s and m then - _G.printError('samba: ' .. m) - end - socket:write({ response = ret }) - end + while true do + local msg = socket:read() + if not msg then + break + end + local fn = fs[msg.fn] + if msg.fn == 'open' then + fn = remoteOpen + elseif msg.fn == 'fileOp' then + fn = remoteFileOperation + end + local ret + local s, m = pcall(function() + ret = fn(unpack(msg.args)) + end) + if not s and m then + _G.printError('samba: ' .. m) + end + socket:write({ response = ret }) + end - print('samba: Connection closed') + print('samba: Connection closed') end Event.addRoutine(function() - print('samba: listening on port 139') + print('samba: listening on port 139') - while true do - local socket = Socket.server(139) + while true do + local socket = Socket.server(139) - Event.addRoutine(function() - print('samba: connection from ' .. socket.dhost) - sambaConnection(socket) - print('samba: closing connection to ' .. socket.dhost) - end) - end + Event.addRoutine(function() + print('samba: connection from ' .. socket.dhost) + sambaConnection(socket) + print('samba: closing connection to ' .. socket.dhost) + end) + end end) Event.on('network_attach', function(_, computer) - fs.mount(fs.combine('network', computer.label), 'netfs', computer.id) + fs.mount(fs.combine('network', computer.label), 'netfs', computer.id) end) Event.on('network_detach', function(_, computer) - print('samba: detaching ' .. computer.label) - fs.unmount(fs.combine('network', computer.label)) + print('samba: detaching ' .. computer.label) + fs.unmount(fs.combine('network', computer.label)) end) diff --git a/sys/network/snmp.lua b/sys/network/snmp.lua index 66f722e..77fdd15 100644 --- a/sys/network/snmp.lua +++ b/sys/network/snmp.lua @@ -15,156 +15,156 @@ local gpsLastPoint local gpsLastRequestTime local function snmpConnection(socket) - while true do - local msg = socket:read() - if not msg then - break - end + while true do + local msg = socket:read() + if not msg then + break + end - if msg.type == 'reboot' then - os.reboot() + if msg.type == 'reboot' then + os.reboot() - elseif msg.type == 'shutdown' then - os.shutdown() + elseif msg.type == 'shutdown' then + os.shutdown() - elseif msg.type == 'ping' then - socket:write('pong') + elseif msg.type == 'ping' then + socket:write('pong') - elseif msg.type == 'script' then - local fn, err = loadstring(msg.args, 'script') - if fn then - kernel.run({ - fn = fn, - title = 'script', - }) - else - _G.printError(err) - end + elseif msg.type == 'script' then + local fn, err = loadstring(msg.args, 'script') + if fn then + kernel.run({ + fn = fn, + title = 'script', + }) + else + _G.printError(err) + end - elseif msg.type == 'scriptEx' then - local s, m = pcall(function() - local env = setmetatable(Util.shallowCopy(_ENV), { __index = _G }) - local fn, m = load(msg.args, 'script', nil, env) - if not fn then - error(m) - end - return { fn() } - end) - if s then - socket:write(m) - else - socket:write({ s, m }) - end + elseif msg.type == 'scriptEx' then + local s, m = pcall(function() + local env = setmetatable(Util.shallowCopy(_ENV), { __index = _G }) + local fn, m = load(msg.args, 'script', nil, env) + if not fn then + error(m) + end + return { fn() } + end) + if s then + socket:write(m) + else + socket:write({ s, m }) + end - elseif msg.type == 'gps' then - if gpsRequested then - repeat - os.sleep(0) - until not gpsRequested - end + elseif msg.type == 'gps' then + if gpsRequested then + repeat + os.sleep(0) + until not gpsRequested + end - if gpsLastPoint and os.clock() - gpsLastRequestTime < .5 then - socket:write(gpsLastPoint) - else + if gpsLastPoint and os.clock() - gpsLastRequestTime < .5 then + socket:write(gpsLastPoint) + else - gpsRequested = true - local pt = GPS.getPoint(2) - if pt then - socket:write(pt) - else - print('snmp: Unable to get GPS point') - end - gpsRequested = false - gpsLastPoint = pt - if pt then - gpsLastRequestTime = os.clock() - end - end + gpsRequested = true + local pt = GPS.getPoint(2) + if pt then + socket:write(pt) + else + print('snmp: Unable to get GPS point') + end + gpsRequested = false + gpsLastPoint = pt + if pt then + gpsLastRequestTime = os.clock() + end + end - elseif msg.type == 'info' then - local info = { - id = os.getComputerID(), - label = os.getComputerLabel(), - uptime = math.floor(os.clock()), - } - if turtle then - info.fuel = turtle.getFuelLevel() - info.status = turtle.getStatus() - end - socket:write(info) - end - end + elseif msg.type == 'info' then + local info = { + id = os.getComputerID(), + label = os.getComputerLabel(), + uptime = math.floor(os.clock()), + } + if turtle then + info.fuel = turtle.getFuelLevel() + info.status = turtle.getStatus() + end + socket:write(info) + end + end end Event.addRoutine(function() - print('snmp: listening on port 161') + print('snmp: listening on port 161') - while true do - local socket = Socket.server(161) + while true do + local socket = Socket.server(161) - Event.addRoutine(function() - print('snmp: connection from ' .. socket.dhost) - snmpConnection(socket) - print('snmp: closing connection to ' .. socket.dhost) - end) - end + Event.addRoutine(function() + print('snmp: connection from ' .. socket.dhost) + snmpConnection(socket) + print('snmp: closing connection to ' .. socket.dhost) + end) + end end) device.wireless_modem.open(999) print('discovery: listening on port 999') Event.on('modem_message', function(_, _, sport, id, info, distance) - if sport == 999 and tonumber(id) and type(info) == 'table' then - if not network[id] then - network[id] = { } - end - Util.merge(network[id], info) - network[id].distance = distance - network[id].timestamp = os.clock() + if sport == 999 and tonumber(id) and type(info) == 'table' then + if not network[id] then + network[id] = { } + end + Util.merge(network[id], info) + network[id].distance = distance + network[id].timestamp = os.clock() - if not network[id].active then - network[id].active = true - os.queueEvent('network_attach', network[id]) - end - end + if not network[id].active then + network[id].active = true + os.queueEvent('network_attach', network[id]) + end + end end) local info = { - id = os.getComputerID() + id = os.getComputerID() } local infoTimer = os.clock() local function sendInfo() - if os.clock() - infoTimer >= 1 then -- don't flood - infoTimer = os.clock() - info.label = os.getComputerLabel() - info.uptime = math.floor(os.clock()) - if turtle then - info.fuel = turtle.getFuelLevel() - info.status = turtle.getStatus() - info.point = turtle.point - info.inventory = turtle.getInventory() - info.slotIndex = turtle.getSelectedSlot() - end - device.wireless_modem.transmit(999, os.getComputerID(), info) - end + if os.clock() - infoTimer >= 1 then -- don't flood + infoTimer = os.clock() + info.label = os.getComputerLabel() + info.uptime = math.floor(os.clock()) + if turtle then + info.fuel = turtle.getFuelLevel() + info.status = turtle.getStatus() + info.point = turtle.point + info.inventory = turtle.getInventory() + info.slotIndex = turtle.getSelectedSlot() + end + device.wireless_modem.transmit(999, os.getComputerID(), info) + end end -- every 10 seconds, send out this computer's info Event.onInterval(10, function() - sendInfo() - for _,c in pairs(_G.network) do - local elapsed = os.clock()-c.timestamp - if c.active and elapsed > 15 then - c.active = false - os.queueEvent('network_detach', c) - end - end + sendInfo() + for _,c in pairs(_G.network) do + local elapsed = os.clock()-c.timestamp + if c.active and elapsed > 15 then + c.active = false + os.queueEvent('network_detach', c) + end + end end) Event.on('turtle_response', function() - if turtle.getStatus() ~= info.status or - turtle.fuel ~= info.fuel then - sendInfo() - end + if turtle.getStatus() ~= info.status or + turtle.fuel ~= info.fuel then + sendInfo() + end end) diff --git a/sys/network/telnet.lua b/sys/network/telnet.lua index ce7e8ff..c0d5f07 100644 --- a/sys/network/telnet.lua +++ b/sys/network/telnet.lua @@ -7,76 +7,76 @@ local term = _G.term local window = _G.window local function telnetHost(socket) - local methods = { 'clear', 'clearLine', 'setCursorPos', 'write', 'blit', - 'setTextColor', 'setTextColour', 'setBackgroundColor', - 'setBackgroundColour', 'scroll', 'setCursorBlink', } + local methods = { 'clear', 'clearLine', 'setCursorPos', 'write', 'blit', + 'setTextColor', 'setTextColour', 'setBackgroundColor', + 'setBackgroundColour', 'scroll', 'setCursorBlink', } - local termInfo = socket:read(5) - if not termInfo then - _G.printError('read failed') - return - end + local termInfo = socket:read(5) + if not termInfo then + _G.printError('read failed') + return + end - local win = window.create(_G.device.terminal, 1, 1, termInfo.width, termInfo.height, false) - win.setCursorPos(table.unpack(termInfo.pos)) + local win = window.create(_G.device.terminal, 1, 1, termInfo.width, termInfo.height, false) + win.setCursorPos(table.unpack(termInfo.pos)) - for _,k in pairs(methods) do - local fn = win[k] - win[k] = function(...) + for _,k in pairs(methods) do + local fn = win[k] + win[k] = function(...) - if not socket.queue then - socket.queue = { } - Event.onTimeout(0, function() - socket:write(socket.queue) - socket.queue = nil - end) - end + if not socket.queue then + socket.queue = { } + Event.onTimeout(0, function() + socket:write(socket.queue) + socket.queue = nil + end) + end - table.insert(socket.queue, { - f = k, - args = { ... }, - }) - fn(...) - end - end + table.insert(socket.queue, { + f = k, + args = { ... }, + }) + fn(...) + end + end - local shellThread = kernel.run({ - terminal = win, - window = win, - title = 'Telnet client', - hidden = true, - co = coroutine.create(function() - Util.run(_ENV, 'sys/apps/shell', table.unpack(termInfo.program)) - if socket.queue then - socket:write(socket.queue) - end - socket:close() - end) - }) + local shellThread = kernel.run({ + terminal = win, + window = win, + title = 'Telnet client', + hidden = true, + co = coroutine.create(function() + Util.run(_ENV, 'sys/apps/shell', table.unpack(termInfo.program)) + if socket.queue then + socket:write(socket.queue) + end + socket:close() + end) + }) - Event.addRoutine(function() - while true do - local data = socket:read() - if not data then - shellThread:resume('terminate') - break - end - local previousTerm = term.current() - shellThread:resume(table.unpack(data)) - term.redirect(previousTerm) - end - end) + Event.addRoutine(function() + while true do + local data = socket:read() + if not data then + shellThread:resume('terminate') + break + end + local previousTerm = term.current() + shellThread:resume(table.unpack(data)) + term.redirect(previousTerm) + end + end) end Event.addRoutine(function() - print('telnet: listening on port 23') - while true do - local socket = Socket.server(23) + print('telnet: listening on port 23') + while true do + local socket = Socket.server(23) - print('telnet: connection from ' .. socket.dhost) + print('telnet: connection from ' .. socket.dhost) - Event.addRoutine(function() - telnetHost(socket) - end) - end + Event.addRoutine(function() + telnetHost(socket) + end) + end end) diff --git a/sys/network/transport.lua b/sys/network/transport.lua index fea060b..040e3ce 100644 --- a/sys/network/transport.lua +++ b/sys/network/transport.lua @@ -1,8 +1,8 @@ --[[ - Low level socket protocol implementation. + Low level socket protocol implementation. - * sequencing - * background read buffering + * sequencing + * background read buffering ]]-- local Event = require('event') @@ -11,120 +11,120 @@ local os = _G.os local computerId = os.getComputerID() local transport = { - timers = { }, - sockets = { }, - UID = 0, + timers = { }, + sockets = { }, + UID = 0, } _G.transport = transport function transport.open(socket) - transport.UID = transport.UID + 1 + transport.UID = transport.UID + 1 - transport.sockets[socket.sport] = socket - socket.activityTimer = os.clock() - socket.uid = transport.UID + transport.sockets[socket.sport] = socket + socket.activityTimer = os.clock() + socket.uid = transport.UID end function transport.read(socket) - local data = table.remove(socket.messages, 1) - if data then - return unpack(data) - end + local data = table.remove(socket.messages, 1) + if data then + return unpack(data) + end end function transport.write(socket, data) - --debug('>> ' .. Util.tostring({ type = 'DATA', seq = socket.wseq })) - socket.transmit(socket.dport, socket.dhost, data) + --debug('>> ' .. Util.tostring({ type = 'DATA', seq = socket.wseq })) + socket.transmit(socket.dport, socket.dhost, data) - --local timerId = os.startTimer(3) + --local timerId = os.startTimer(3) - --transport.timers[timerId] = socket - --socket.timers[socket.wseq] = timerId + --transport.timers[timerId] = socket + --socket.timers[socket.wseq] = timerId - socket.wseq = socket.wseq + 1 + socket.wseq = socket.wseq + 1 end function transport.ping(socket) - --debug('>> ' .. Util.tostring({ type = 'DATA', seq = socket.wseq })) - if os.clock() - socket.activityTimer > 10 then - socket.activityTimer = os.clock() - socket.transmit(socket.dport, socket.dhost, { - type = 'PING', - seq = -1, - }) + --debug('>> ' .. Util.tostring({ type = 'DATA', seq = socket.wseq })) + if os.clock() - socket.activityTimer > 10 then + socket.activityTimer = os.clock() + socket.transmit(socket.dport, socket.dhost, { + type = 'PING', + seq = -1, + }) - local timerId = os.startTimer(3) - transport.timers[timerId] = socket - socket.timers[-1] = timerId - end + local timerId = os.startTimer(3) + transport.timers[timerId] = socket + socket.timers[-1] = timerId + end end function transport.close(socket) - transport.sockets[socket.sport] = nil + transport.sockets[socket.sport] = nil end Event.on('timer', function(_, timerId) - local socket = transport.timers[timerId] + local socket = transport.timers[timerId] - if socket and socket.connected then - print('transport timeout - closing socket ' .. socket.sport) - socket:close() - transport.timers[timerId] = nil - end + if socket and socket.connected then + print('transport timeout - closing socket ' .. socket.sport) + socket:close() + transport.timers[timerId] = nil + end end) Event.on('modem_message', function(_, _, dport, dhost, msg, distance) - if dhost == computerId and msg then - local socket = transport.sockets[dport] - if socket and socket.connected then + if dhost == computerId and msg then + local socket = transport.sockets[dport] + if socket and socket.connected then - --if msg.type then debug('<< ' .. Util.tostring(msg)) end + --if msg.type then debug('<< ' .. Util.tostring(msg)) end - if msg.type == 'DISC' then - -- received disconnect from other end - if socket.connected then - os.queueEvent('transport_' .. socket.uid) - end - socket.connected = false - socket:close() + if msg.type == 'DISC' then + -- received disconnect from other end + if socket.connected then + os.queueEvent('transport_' .. socket.uid) + end + socket.connected = false + socket:close() - elseif msg.type == 'ACK' then - local ackTimerId = socket.timers[msg.seq] - if ackTimerId then - os.cancelTimer(ackTimerId) - socket.timers[msg.seq] = nil - socket.activityTimer = os.clock() - transport.timers[ackTimerId] = nil - end + elseif msg.type == 'ACK' then + local ackTimerId = socket.timers[msg.seq] + if ackTimerId then + os.cancelTimer(ackTimerId) + socket.timers[msg.seq] = nil + socket.activityTimer = os.clock() + transport.timers[ackTimerId] = nil + end - elseif msg.type == 'PING' then - socket.activityTimer = os.clock() - socket.transmit(socket.dport, socket.dhost, { - type = 'ACK', - seq = msg.seq, - }) + elseif msg.type == 'PING' then + socket.activityTimer = os.clock() + socket.transmit(socket.dport, socket.dhost, { + type = 'ACK', + seq = msg.seq, + }) - elseif msg.type == 'DATA' and msg.data then - socket.activityTimer = os.clock() - if msg.seq ~= socket.rseq then - print('transport seq error - closing socket ' .. socket.sport) - socket:close() - else - socket.rseq = socket.rseq + 1 - table.insert(socket.messages, { msg.data, distance }) + elseif msg.type == 'DATA' and msg.data then + socket.activityTimer = os.clock() + if msg.seq ~= socket.rseq then + print('transport seq error - closing socket ' .. socket.sport) + socket:close() + else + socket.rseq = socket.rseq + 1 + table.insert(socket.messages, { msg.data, distance }) - -- use resume instead ?? - if not socket.messages[2] then -- table size is 1 - os.queueEvent('transport_' .. socket.uid) - end + -- use resume instead ?? + if not socket.messages[2] then -- table size is 1 + os.queueEvent('transport_' .. socket.uid) + end - --debug('>> ' .. Util.tostring({ type = 'ACK', seq = msg.seq })) - --socket.transmit(socket.dport, socket.dhost, { - -- type = 'ACK', - -- seq = msg.seq, - --}) - end - end - end - end + --debug('>> ' .. Util.tostring({ type = 'ACK', seq = msg.seq })) + --socket.transmit(socket.dport, socket.dhost, { + -- type = 'ACK', + -- seq = msg.seq, + --}) + end + end + end + end end) diff --git a/sys/network/trust.lua b/sys/network/trust.lua index 4e5344a..03ed4e8 100644 --- a/sys/network/trust.lua +++ b/sys/network/trust.lua @@ -6,30 +6,30 @@ local Util = require('util') Event.addRoutine(function() - print('trust: listening on port 19') - while true do - local socket = Socket.server(19) + print('trust: listening on port 19') + while true do + local socket = Socket.server(19) - print('trust: connection from ' .. socket.dhost) + print('trust: connection from ' .. socket.dhost) - local data = socket:read(2) - if data then - local password = Security.getPassword() - if not password then - socket:write({ msg = 'No password has been set' }) - else - data = Crypto.decrypt(data, password) - if data and data.pk and data.dh == socket.dhost then - local trustList = Util.readTable('usr/.known_hosts') or { } - trustList[data.dh] = data.pk - Util.writeTable('usr/.known_hosts', trustList) + local data = socket:read(2) + if data then + local password = Security.getPassword() + if not password then + socket:write({ msg = 'No password has been set' }) + else + data = Crypto.decrypt(data, password) + if data and data.pk and data.dh == socket.dhost then + local trustList = Util.readTable('usr/.known_hosts') or { } + trustList[data.dh] = data.pk + Util.writeTable('usr/.known_hosts', trustList) - socket:write({ success = true, msg = 'Trust accepted' }) - else - socket:write({ msg = 'Invalid password' }) - end - end - end - socket:close() - end + socket:write({ success = true, msg = 'Trust accepted' }) + else + socket:write({ msg = 'Invalid password' }) + end + end + end + socket:close() + end end) diff --git a/sys/network/vnc.lua b/sys/network/vnc.lua index 0724249..7c4febc 100644 --- a/sys/network/vnc.lua +++ b/sys/network/vnc.lua @@ -6,64 +6,64 @@ local os = _G.os local terminal = _G.device.terminal local function vncHost(socket) - local methods = { 'blit', 'clear', 'clearLine', 'setCursorPos', 'write', - 'setTextColor', 'setTextColour', 'setBackgroundColor', - 'setBackgroundColour', 'scroll', 'setCursorBlink', } + local methods = { 'blit', 'clear', 'clearLine', 'setCursorPos', 'write', + 'setTextColor', 'setTextColour', 'setBackgroundColor', + 'setBackgroundColour', 'scroll', 'setCursorBlink', } - local oldTerm = Util.shallowCopy(terminal) + local oldTerm = Util.shallowCopy(terminal) - for _,k in pairs(methods) do - terminal[k] = function(...) - if not socket.queue then - socket.queue = { } - Event.onTimeout(0, function() - socket:write(socket.queue) - socket.queue = nil - end) - end - table.insert(socket.queue, { - f = k, - args = { ... }, - }) - oldTerm[k](...) - end - end + for _,k in pairs(methods) do + terminal[k] = function(...) + if not socket.queue then + socket.queue = { } + Event.onTimeout(0, function() + socket:write(socket.queue) + socket.queue = nil + end) + end + table.insert(socket.queue, { + f = k, + args = { ... }, + }) + oldTerm[k](...) + end + end - while true do - local data = socket:read() - if not data then - print('vnc: closing connection to ' .. socket.dhost) - break - end + while true do + local data = socket:read() + if not data then + print('vnc: closing connection to ' .. socket.dhost) + break + end - if data.type == 'shellRemote' then - os.queueEvent(table.unpack(data.event)) - elseif data.type == 'termInfo' then - terminal.getSize = function() - return data.width, data.height - end - os.queueEvent('term_resize') - end - end + if data.type == 'shellRemote' then + os.queueEvent(table.unpack(data.event)) + elseif data.type == 'termInfo' then + terminal.getSize = function() + return data.width, data.height + end + os.queueEvent('term_resize') + end + end - for k,v in pairs(oldTerm) do - terminal[k] = v - end - os.queueEvent('term_resize') + for k,v in pairs(oldTerm) do + terminal[k] = v + end + os.queueEvent('term_resize') end Event.addRoutine(function() - print('vnc: listening on port 5900') + print('vnc: listening on port 5900') - while true do - local socket = Socket.server(5900) + while true do + local socket = Socket.server(5900) - print('vnc: connection from ' .. socket.dhost) + print('vnc: connection from ' .. socket.dhost) - -- no new process - only 1 connection allowed - -- due to term size issues - vncHost(socket) - socket:close() - end + -- no new process - only 1 connection allowed + -- due to term size issues + vncHost(socket) + socket:close() + end end)