1
0
mirror of https://github.com/kepler155c/opus synced 2025-01-11 16:20:26 +00:00

format and installer branches

This commit is contained in:
kepler155c@gmail.com 2018-01-24 17:39:38 -05:00
parent 1eea0d7cd8
commit 5a32fe208e
80 changed files with 10742 additions and 10156 deletions

129
startup
View File

@ -4,67 +4,67 @@ local settings = _G.settings
local term = _G.term local term = _G.term
local bootOptions = { local bootOptions = {
{ prompt = os.version() }, { prompt = os.version() },
{ prompt = 'Opus' , args = { '/sys/boot/opus.boot' } }, { prompt = 'Opus' , args = { '/sys/boot/opus.boot' } },
{ prompt = 'Opus Shell' , args = { '/sys/boot/opus.boot', 'sys/apps/shell' } }, { prompt = 'Opus Shell' , args = { '/sys/boot/opus.boot', 'sys/apps/shell' } },
} }
local bootOption = 2 local bootOption = 2
if settings then if settings then
settings.load('.settings') settings.load('.settings')
bootOption = tonumber(settings.get('opus.boot_option') or 2) or 2 bootOption = tonumber(settings.get('opus.boot_option') or 2) or 2
end end
local function startupMenu() local function startupMenu()
while true do while true do
term.clear() term.clear()
term.setCursorPos(1, 1) term.setCursorPos(1, 1)
print('Select startup mode') print('Select startup mode')
print() print()
for k,option in pairs(bootOptions) do for k,option in pairs(bootOptions) do
print(k .. ' : ' .. option.prompt) print(k .. ' : ' .. option.prompt)
end end
print('') print('')
term.write('> ') term.write('> ')
local ch = tonumber(_G.read()) local ch = tonumber(_G.read())
if ch and bootOptions[ch] then if ch and bootOptions[ch] then
return ch return ch
end end
end end
end end
local function splash() local function splash()
local w, h = term.current().getSize() local w, h = term.current().getSize()
term.setTextColor(colors.white) term.setTextColor(colors.white)
if not term.isColor() then if not term.isColor() then
local str = 'Opus OS' local str = 'Opus OS'
term.setCursorPos((w - #str) / 2, h / 2) term.setCursorPos((w - #str) / 2, h / 2)
term.write(str) term.write(str)
else else
term.setBackgroundColor(colors.black) term.setBackgroundColor(colors.black)
term.clear() term.clear()
local opus = { local opus = {
'fffff00', 'fffff00',
'ffff07000', 'ffff07000',
'ff00770b00 4444', 'ff00770b00 4444',
'ff077777444444444', 'ff077777444444444',
'f07777744444444444', 'f07777744444444444',
'f0000777444444444', 'f0000777444444444',
'070000111744444', '070000111744444',
'777770000', '777770000',
'7777000000', '7777000000',
'70700000000', '70700000000',
'077000000000', '077000000000',
} }
for k,line in ipairs(opus) do for k,line in ipairs(opus) do
term.setCursorPos((w - 18) / 2, k + (h - #opus) / 2) term.setCursorPos((w - 18) / 2, k + (h - #opus) / 2)
term.blit(string.rep(' ', #line), string.rep('a', #line), line) term.blit(string.rep(' ', #line), string.rep('a', #line), line)
end end
end end
local str = 'Press any key for menu' local str = 'Press any key for menu'
term.setCursorPos((w - #str) / 2, h) term.setCursorPos((w - #str) / 2, h)
term.write(str) term.write(str)
end end
term.clear() term.clear()
@ -72,24 +72,25 @@ splash()
local timerId = os.startTimer(1.5) local timerId = os.startTimer(1.5)
while true do while true do
local e, id = os.pullEvent() local e, id = os.pullEvent()
if e == 'timer' and id == timerId then if e == 'timer' and id == timerId then
break break
end end
if e == 'char' then if e == 'char' then
bootOption = startupMenu() bootOption = startupMenu()
if settings then if settings then
settings.set('opus.boot_option', bootOption) settings.set('opus.boot_option', bootOption)
settings.save('.settings') settings.save('.settings')
end end
break break
end end
end end
term.clear() term.clear()
term.setCursorPos(1, 1) term.setCursorPos(1, 1)
if bootOptions[bootOption].args then 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 else
print(bootOptions[bootOption].prompt) print(bootOptions[bootOption].prompt)
end end

View File

@ -1,55 +1,55 @@
local Ansi = setmetatable({ }, { local Ansi = setmetatable({ }, {
__call = function(_, ...) __call = function(_, ...)
local str = '\027[' local str = '\027['
for k,v in ipairs({ ...}) do for k,v in ipairs({ ...}) do
if k == 1 then if k == 1 then
str = str .. v str = str .. v
else else
str = str .. ';' .. v str = str .. ';' .. v
end end
end end
return str .. 'm' return str .. 'm'
end end
}) })
Ansi.codes = { Ansi.codes = {
reset = 0, reset = 0,
white = 1, white = 1,
orange = 2, orange = 2,
magenta = 3, magenta = 3,
lightBlue = 4, lightBlue = 4,
yellow = 5, yellow = 5,
lime = 6, lime = 6,
pink = 7, pink = 7,
gray = 8, gray = 8,
lightGray = 9, lightGray = 9,
cyan = 10, cyan = 10,
purple = 11, purple = 11,
blue = 12, blue = 12,
brown = 13, brown = 13,
green = 14, green = 14,
red = 15, red = 15,
black = 16, black = 16,
onwhite = 21, onwhite = 21,
onorange = 22, onorange = 22,
onmagenta = 23, onmagenta = 23,
onlightBlue = 24, onlightBlue = 24,
onyellow = 25, onyellow = 25,
onlime = 26, onlime = 26,
onpink = 27, onpink = 27,
ongray = 28, ongray = 28,
onlightGray = 29, onlightGray = 29,
oncyan = 30, oncyan = 30,
onpurple = 31, onpurple = 31,
onblue = 32, onblue = 32,
onbrown = 33, onbrown = 33,
ongreen = 34, ongreen = 34,
onred = 35, onred = 35,
onblack = 36, onblack = 36,
} }
for k,v in pairs(Ansi.codes) do for k,v in pairs(Ansi.codes) do
Ansi[k] = Ansi(v) Ansi[k] = Ansi(v)
end end
return Ansi return Ansi

View File

@ -4,43 +4,43 @@
-- class.lua -- class.lua
-- Compatible with Lua 5.1 (not 5.0). -- Compatible with Lua 5.1 (not 5.0).
return function(base) return function(base)
local c = { } -- a new class instance local c = { } -- a new class instance
if type(base) == 'table' then if type(base) == 'table' then
-- our new class is a shallow copy of the base class! -- our new class is a shallow copy of the base class!
for i,v in pairs(base) do for i,v in pairs(base) do
c[i] = v c[i] = v
end end
c._base = base c._base = base
end end
-- the class will be the metatable for all its objects, -- the class will be the metatable for all its objects,
-- and they will look up their methods in it. -- and they will look up their methods in it.
c.__index = c c.__index = c
-- expose a constructor which can be called by <classname>(<args>) -- expose a constructor which can be called by <classname>(<args>)
setmetatable(c, { setmetatable(c, {
__call = function(class_tbl, ...) __call = function(class_tbl, ...)
local obj = { } local obj = { }
setmetatable(obj,c) setmetatable(obj,c)
if class_tbl.init then if class_tbl.init then
class_tbl.init(obj, ...) class_tbl.init(obj, ...)
else else
-- make sure that any stuff from the base class is initialized! -- make sure that any stuff from the base class is initialized!
if base and base.init then if base and base.init then
base.init(obj, ...) base.init(obj, ...)
end end
end end
return obj return obj
end end
}) })
c.is_a = c.is_a =
function(self, klass) function(self, klass)
local m = getmetatable(self) local m = getmetatable(self)
while m do while m do
if m == klass then return true end if m == klass then return true end
m = m._base m = m._base
end end
return false return false
end end
return c return c
end end

View File

@ -9,13 +9,13 @@ function Config.load(fname, data)
local filename = 'usr/config/' .. fname local filename = 'usr/config/' .. fname
if not fs.exists('usr/config') then if not fs.exists('usr/config') then
fs.makeDir('usr/config') fs.makeDir('usr/config')
end end
if not fs.exists(filename) then if not fs.exists(filename) then
Util.writeTable(filename, data) Util.writeTable(filename, data)
else else
Util.merge(data, Util.readTable(filename) or { }) Util.merge(data, Util.readTable(filename) or { })
end end
end end
@ -23,17 +23,17 @@ function Config.loadWithCheck(fname, data)
local filename = 'usr/config/' .. fname local filename = 'usr/config/' .. fname
if not fs.exists(filename) then if not fs.exists(filename) then
Config.load(fname, data) Config.load(fname, data)
print() print()
print('The configuration file has been created.') print('The configuration file has been created.')
print('The file name is: ' .. filename) print('The file name is: ' .. filename)
print() print()
_G.printError('Press enter to configure') _G.printError('Press enter to configure')
_G.read() _G.read()
shell.run('edit ' .. filename) shell.run('edit ' .. filename)
end end
Config.load(fname, data) Config.load(fname, data)
end end
function Config.update(fname, data) function Config.update(fname, data)

View File

@ -3,148 +3,148 @@
local Crypto = { } local Crypto = { }
local function serialize(t) local function serialize(t)
local sType = type(t) local sType = type(t)
if sType == "table" then if sType == "table" then
local lstcnt=0 local lstcnt=0
for k,v in pairs(t) do for k,v in pairs(t) do
lstcnt = lstcnt + 1 lstcnt = lstcnt + 1
end end
local result = "{" local result = "{"
local aset=1 local aset=1
for k,v in pairs(t) do for k,v in pairs(t) do
if k==aset then if k==aset then
result = result..serialize(v).."," result = result..serialize(v)..","
aset=aset+1 aset=aset+1
else else
result = result..("["..serialize(k).."]="..serialize(v)..",") result = result..("["..serialize(k).."]="..serialize(v)..",")
end end
end end
result = result.."}" result = result.."}"
return result return result
elseif sType == "string" then elseif sType == "string" then
return string.format("%q",t) return string.format("%q",t)
elseif sType == "number" or sType == "boolean" or sType == "nil" then elseif sType == "number" or sType == "boolean" or sType == "nil" then
return tostring(t) return tostring(t)
elseif sType == "function" then elseif sType == "function" then
local status,data=pcall(string.dump,t) local status,data=pcall(string.dump,t)
if status then if status then
data2="" data2=""
for char in string.gmatch(data,".") do for char in string.gmatch(data,".") do
data2=data2..zfill(string.byte(char)) data2=data2..zfill(string.byte(char))
end end
return 'f("'..data2..'")' return 'f("'..data2..'")'
else else
error("Invalid function: "..data) error("Invalid function: "..data)
end end
else else
error("Could not serialize type "..sType..".") error("Could not serialize type "..sType..".")
end end
end end
local function unserialize( s ) local function unserialize( s )
local func, e = loadstring( "return "..s, "serialize" ) local func, e = loadstring( "return "..s, "serialize" )
if not func then if not func then
return s,e return s,e
else else
setfenv( func, { setfenv( func, {
f=function(S) f=function(S)
return loadstring(splitnum(S)) return loadstring(splitnum(S))
end, end,
}) })
return func() return func()
end end
end end
local function splitnum(S) local function splitnum(S)
local Out="" local Out=""
for l1=1,#S,2 do for l1=1,#S,2 do
local l2=(#S-l1)+1 local l2=(#S-l1)+1
local function sure(N,n) local function sure(N,n)
if (l2-n)<1 then N="0" end if (l2-n)<1 then N="0" end
return N return N
end end
local CNum=tonumber("0x"..sure(string.sub(S,l2-1,l2-1),1) .. sure(string.sub(S,l2,l2),0)) 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 Out=string.char(CNum)..Out
end end
return Out return Out
end end
local function zfill(N) local function zfill(N)
N=string.format("%X",N) N=string.format("%X",N)
Zs="" Zs=""
if #N==1 then if #N==1 then
Zs="0" Zs="0"
end end
return Zs..N return Zs..N
end end
local function wrap(N) local function wrap(N)
return N-(math.floor(N/256)*256) return N-(math.floor(N/256)*256)
end end
local function checksum(S) local function checksum(S)
local sum=0 local sum=0
for char in string.gmatch(S,".") do for char in string.gmatch(S,".") do
math.randomseed(string.byte(char)+sum) math.randomseed(string.byte(char)+sum)
sum=sum+math.random(0,9999) sum=sum+math.random(0,9999)
end end
math.randomseed(sum) math.randomseed(sum)
return sum return sum
end end
local function genkey(len,psw) local function genkey(len,psw)
checksum(psw) checksum(psw)
local key={} local key={}
local tKeys={} local tKeys={}
for l1=1,len do for l1=1,len do
local num=math.random(1,len) local num=math.random(1,len)
while tKeys[num] do while tKeys[num] do
num=math.random(1,len) num=math.random(1,len)
end end
tKeys[num]=true tKeys[num]=true
key[l1]={num,math.random(0,255)} key[l1]={num,math.random(0,255)}
end end
return key return key
end end
function Crypto.encrypt(data,psw) function Crypto.encrypt(data,psw)
data=serialize(data) data=serialize(data)
local chs=checksum(data) local chs=checksum(data)
local key=genkey(#data,psw) local key=genkey(#data,psw)
local out={} local out={}
local cnt=1 local cnt=1
for char in string.gmatch(data,".") do for char in string.gmatch(data,".") do
table.insert(out,key[cnt][1],zfill(wrap(string.byte(char)+key[cnt][2])),chars) table.insert(out,key[cnt][1],zfill(wrap(string.byte(char)+key[cnt][2])),chars)
cnt=cnt+1 cnt=cnt+1
end end
return string.sub(serialize({chs,table.concat(out)}),2,-3) return string.sub(serialize({chs,table.concat(out)}),2,-3)
end end
function Crypto.decrypt(data,psw) function Crypto.decrypt(data,psw)
local oData=data local oData=data
data=unserialize("{"..data.."}") data=unserialize("{"..data.."}")
if type(data)~="table" then if type(data)~="table" then
return oData return oData
end end
local chs=data[1] local chs=data[1]
data=data[2] data=data[2]
local key=genkey((#data)/2,psw) local key=genkey((#data)/2,psw)
local sKey={} local sKey={}
for k,v in pairs(key) do for k,v in pairs(key) do
sKey[v[1]]={k,v[2]} sKey[v[1]]={k,v[2]}
end end
local str=splitnum(data) local str=splitnum(data)
local cnt=1 local cnt=1
local out={} local out={}
for char in string.gmatch(str,".") do for char in string.gmatch(str,".") do
table.insert(out,sKey[cnt][1],string.char(wrap(string.byte(char)-sKey[cnt][2]))) table.insert(out,sKey[cnt][1],string.char(wrap(string.byte(char)-sKey[cnt][2])))
cnt=cnt+1 cnt=cnt+1
end end
out=table.concat(out) out=table.concat(out)
if checksum(out or "")==chs then if checksum(out or "")==chs then
return unserialize(out) return unserialize(out)
end end
return oData,out,chs return oData,out,chs
end end
return Crypto return Crypto

151
sys/apis/entry.lua Normal file
View File

@ -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

View File

@ -1,223 +1,223 @@
local os = _G.os local os = _G.os
local Event = { local Event = {
uid = 1, -- unique id for handlers uid = 1, -- unique id for handlers
routines = { }, -- coroutines routines = { }, -- coroutines
types = { }, -- event handlers types = { }, -- event handlers
timers = { }, -- named timers timers = { }, -- named timers
terminate = false, terminate = false,
} }
local Routine = { } local Routine = { }
function Routine:isDead() function Routine:isDead()
if not self.co then if not self.co then
return true return true
end end
return coroutine.status(self.co) == 'dead' return coroutine.status(self.co) == 'dead'
end end
function Routine:terminate() function Routine:terminate()
if self.co then if self.co then
self:resume('terminate') self:resume('terminate')
end end
end end
function Routine:resume(event, ...) function Routine:resume(event, ...)
--if coroutine.status(self.co) == 'running' then --if coroutine.status(self.co) == 'running' then
--return --return
--end --end
if not self.co then if not self.co then
error('Cannot resume a dead routine') error('Cannot resume a dead routine')
end end
if not self.filter or self.filter == event or event == "terminate" then if not self.filter or self.filter == event or event == "terminate" then
local s, m = coroutine.resume(self.co, event, ...) local s, m = coroutine.resume(self.co, event, ...)
if coroutine.status(self.co) == 'dead' then if coroutine.status(self.co) == 'dead' then
self.co = nil self.co = nil
self.filter = nil self.filter = nil
Event.routines[self.uid] = nil Event.routines[self.uid] = nil
else else
self.filter = m self.filter = m
end end
if not s and event ~= 'terminate' then if not s and event ~= 'terminate' then
error('\n' .. (m or 'Error processing event')) error('\n' .. (m or 'Error processing event'))
end end
return s, m return s, m
end end
return true, self.filter return true, self.filter
end end
local function nextUID() local function nextUID()
Event.uid = Event.uid + 1 Event.uid = Event.uid + 1
return Event.uid - 1 return Event.uid - 1
end end
function Event.on(events, fn) function Event.on(events, fn)
events = type(events) == 'table' and events or { events } events = type(events) == 'table' and events or { events }
local handler = setmetatable({ local handler = setmetatable({
uid = nextUID(), uid = nextUID(),
event = events, event = events,
fn = fn, fn = fn,
}, { __index = Routine }) }, { __index = Routine })
for _,event in pairs(events) do for _,event in pairs(events) do
local handlers = Event.types[event] local handlers = Event.types[event]
if not handlers then if not handlers then
handlers = { } handlers = { }
Event.types[event] = handlers Event.types[event] = handlers
end end
handlers[handler.uid] = handler handlers[handler.uid] = handler
end end
return handler return handler
end end
function Event.off(h) function Event.off(h)
if h and h.event then if h and h.event then
for _,event in pairs(h.event) do for _,event in pairs(h.event) do
Event.types[event][h.uid] = nil Event.types[event][h.uid] = nil
end end
end end
end end
local function addTimer(interval, recurring, fn) local function addTimer(interval, recurring, fn)
local timerId = os.startTimer(interval) local timerId = os.startTimer(interval)
local handler local handler
handler = Event.on('timer', function(t, id) handler = Event.on('timer', function(t, id)
if timerId == id then if timerId == id then
fn(t, id) fn(t, id)
if recurring then if recurring then
timerId = os.startTimer(interval) timerId = os.startTimer(interval)
else else
Event.off(handler) Event.off(handler)
end end
end end
end) end)
return handler return handler
end end
function Event.onInterval(interval, fn) function Event.onInterval(interval, fn)
return addTimer(interval, true, fn) return addTimer(interval, true, fn)
end end
function Event.onTimeout(timeout, fn) function Event.onTimeout(timeout, fn)
return addTimer(timeout, false, fn) return addTimer(timeout, false, fn)
end end
function Event.addNamedTimer(name, interval, recurring, fn) function Event.addNamedTimer(name, interval, recurring, fn)
Event.cancelNamedTimer(name) Event.cancelNamedTimer(name)
Event.timers[name] = addTimer(interval, recurring, fn) Event.timers[name] = addTimer(interval, recurring, fn)
end end
function Event.cancelNamedTimer(name) function Event.cancelNamedTimer(name)
local timer = Event.timers[name] local timer = Event.timers[name]
if timer then if timer then
Event.off(timer) Event.off(timer)
end end
end end
function Event.waitForEvent(event, timeout) function Event.waitForEvent(event, timeout)
local timerId = os.startTimer(timeout) local timerId = os.startTimer(timeout)
repeat repeat
local e = { os.pullEvent() } local e = { os.pullEvent() }
if e[1] == event then if e[1] == event then
return table.unpack(e) return table.unpack(e)
end end
until e[1] == 'timer' and e[2] == timerId until e[1] == 'timer' and e[2] == timerId
end end
function Event.addRoutine(fn) function Event.addRoutine(fn)
local r = setmetatable({ local r = setmetatable({
co = coroutine.create(fn), co = coroutine.create(fn),
uid = nextUID() uid = nextUID()
}, { __index = Routine }) }, { __index = Routine })
Event.routines[r.uid] = r Event.routines[r.uid] = r
r:resume() r:resume()
return r return r
end end
function Event.pullEvents(...) function Event.pullEvents(...)
for _, fn in ipairs({ ... }) do for _, fn in ipairs({ ... }) do
Event.addRoutine(fn) Event.addRoutine(fn)
end end
repeat repeat
Event.pullEvent() Event.pullEvent()
until Event.terminate until Event.terminate
Event.terminate = false Event.terminate = false
end end
function Event.exitPullEvents() function Event.exitPullEvents()
Event.terminate = true Event.terminate = true
os.sleep(0) os.sleep(0)
end end
local function processHandlers(event) local function processHandlers(event)
local handlers = Event.types[event] local handlers = Event.types[event]
if handlers then if handlers then
for _,h in pairs(handlers) do for _,h in pairs(handlers) do
if not h.co then if not h.co then
-- callbacks are single threaded (only 1 co per handler) -- callbacks are single threaded (only 1 co per handler)
h.co = coroutine.create(h.fn) h.co = coroutine.create(h.fn)
Event.routines[h.uid] = h Event.routines[h.uid] = h
end end
end end
end end
end end
local function tokeys(t) local function tokeys(t)
local keys = { } local keys = { }
for k in pairs(t) do for k in pairs(t) do
keys[#keys+1] = k keys[#keys+1] = k
end end
return keys return keys
end end
local function processRoutines(...) local function processRoutines(...)
local keys = tokeys(Event.routines) local keys = tokeys(Event.routines)
for _,key in ipairs(keys) do for _,key in ipairs(keys) do
local r = Event.routines[key] local r = Event.routines[key]
if r then if r then
r:resume(...) r:resume(...)
end end
end end
end end
function Event.processEvent(e) function Event.processEvent(e)
processHandlers(e[1]) processHandlers(e[1])
processRoutines(table.unpack(e)) processRoutines(table.unpack(e))
end end
function Event.pullEvent(eventType) function Event.pullEvent(eventType)
while true do while true do
local e = { os.pullEventRaw() } local e = { os.pullEventRaw() }
Event.terminate = Event.terminate or e[1] == 'terminate' Event.terminate = Event.terminate or e[1] == 'terminate'
processHandlers(e[1]) processHandlers(e[1])
processRoutines(table.unpack(e)) processRoutines(table.unpack(e))
if Event.terminate then if Event.terminate then
return { 'terminate' } return { 'terminate' }
end end
if not eventType or e[1] == eventType then if not eventType or e[1] == eventType then
return e return e
end end
end end
end end
return Event return Event

View File

@ -5,17 +5,17 @@ local fs = _G.fs
local gitfs = { } local gitfs = { }
function gitfs.mount(dir, repo) function gitfs.mount(dir, repo)
if not repo then if not repo then
error('gitfs syntax: repo') error('gitfs syntax: repo')
end end
local list = git.list(repo) local list = git.list(repo)
for path, entry in pairs(list) do for path, entry in pairs(list) do
if not fs.exists(fs.combine(dir, path)) then if not fs.exists(fs.combine(dir, path)) then
local node = fs.mount(fs.combine(dir, path), 'urlfs', entry.url) local node = fs.mount(fs.combine(dir, path), 'urlfs', entry.url)
node.size = entry.size node.size = entry.size
end end
end end
end end
return gitfs return gitfs

View File

@ -3,61 +3,61 @@ local fs = _G.fs
local linkfs = { } local linkfs = { }
local methods = { 'exists', 'getFreeSpace', 'getSize', 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 for _,m in pairs(methods) do
linkfs[m] = function(node, dir, ...) linkfs[m] = function(node, dir, ...)
dir = dir:gsub(node.mountPoint, node.source, 1) dir = dir:gsub(node.mountPoint, node.source, 1)
return fs[m](dir, ...) return fs[m](dir, ...)
end end
end end
function linkfs.mount(_, source) function linkfs.mount(_, source)
if not source then if not source then
error('Source is required') error('Source is required')
end end
source = fs.combine(source, '') source = fs.combine(source, '')
if fs.isDir(source) then if fs.isDir(source) then
return { return {
source = source, source = source,
nodes = { }, nodes = { },
} }
end end
return { return {
source = source source = source
} }
end end
function linkfs.copy(node, s, t) function linkfs.copy(node, s, t)
s = s:gsub(node.mountPoint, node.source, 1) s = s:gsub(node.mountPoint, node.source, 1)
t = t:gsub(node.mountPoint, node.source, 1) t = t:gsub(node.mountPoint, node.source, 1)
return fs.copy(s, t) return fs.copy(s, t)
end end
function linkfs.delete(node, dir) function linkfs.delete(node, dir)
if dir == node.mountPoint then if dir == node.mountPoint then
fs.unmount(node.mountPoint) fs.unmount(node.mountPoint)
else else
dir = dir:gsub(node.mountPoint, node.source, 1) dir = dir:gsub(node.mountPoint, node.source, 1)
return fs.delete(dir) return fs.delete(dir)
end end
end end
function linkfs.find(node, spec) 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) local list = fs.find(spec)
for k,f in ipairs(list) do for k,f in ipairs(list) do
list[k] = f:gsub(node.source, node.mountPoint, 1) list[k] = f:gsub(node.source, node.mountPoint, 1)
end end
return list return list
end end
function linkfs.move(node, s, t) function linkfs.move(node, s, t)
s = s:gsub(node.mountPoint, node.source, 1) s = s:gsub(node.mountPoint, node.source, 1)
t = t:gsub(node.mountPoint, node.source, 1) t = t:gsub(node.mountPoint, node.source, 1)
return fs.move(s, t) return fs.move(s, t)
end end
return linkfs return linkfs

View File

@ -7,159 +7,159 @@ local netfs = { }
local function remoteCommand(node, msg) local function remoteCommand(node, msg)
for _ = 1, 2 do for _ = 1, 2 do
if not node.socket then if not node.socket then
node.socket = Socket.connect(node.id, 139) node.socket = Socket.connect(node.id, 139)
end end
if not node.socket then if not node.socket then
error('netfs: Unable to establish connection to ' .. node.id) error('netfs: Unable to establish connection to ' .. node.id)
fs.unmount(node.mountPoint) fs.unmount(node.mountPoint)
return return
end end
local ret local ret
synchronized(node.socket, function() synchronized(node.socket, function()
node.socket:write(msg) node.socket:write(msg)
ret = node.socket:read(1) ret = node.socket:read(1)
end) end)
if ret then if ret then
return ret.response return ret.response
end end
node.socket:close() node.socket:close()
node.socket = nil node.socket = nil
end end
error('netfs: Connection failed', 2) error('netfs: Connection failed', 2)
end end
local methods = { 'delete', 'exists', 'getFreeSpace', 'makeDir', 'list', 'listEx' } local methods = { 'delete', 'exists', 'getFreeSpace', 'makeDir', 'list', 'listEx' }
local function resolveDir(dir, node) local function resolveDir(dir, node)
dir = dir:gsub(node.mountPoint, '', 1) dir = dir:gsub(node.mountPoint, '', 1)
return fs.combine(node.directory, dir) return fs.combine(node.directory, dir)
end end
for _,m in pairs(methods) do for _,m in pairs(methods) do
netfs[m] = function(node, dir) netfs[m] = function(node, dir)
dir = resolveDir(dir, node) dir = resolveDir(dir, node)
return remoteCommand(node, { return remoteCommand(node, {
fn = m, fn = m,
args = { dir }, args = { dir },
}) })
end end
end end
function netfs.mount(_, id, directory) function netfs.mount(_, id, directory)
if not id or not tonumber(id) then if not id or not tonumber(id) then
error('ramfs syntax: computerId [directory]') error('ramfs syntax: computerId [directory]')
end end
return { return {
id = tonumber(id), id = tonumber(id),
nodes = { }, nodes = { },
directory = directory or '', directory = directory or '',
} }
end end
function netfs.getDrive() function netfs.getDrive()
return 'net' return 'net'
end end
function netfs.complete(node, partial, dir, includeFiles, includeSlash) function netfs.complete(node, partial, dir, includeFiles, includeSlash)
dir = resolveDir(dir, node) dir = resolveDir(dir, node)
return remoteCommand(node, { return remoteCommand(node, {
fn = 'complete', fn = 'complete',
args = { partial, dir, includeFiles, includeSlash }, args = { partial, dir, includeFiles, includeSlash },
}) })
end end
function netfs.copy(node, s, t) function netfs.copy(node, s, t)
s = resolveDir(s, node) s = resolveDir(s, node)
t = resolveDir(t, node) t = resolveDir(t, node)
return remoteCommand(node, { return remoteCommand(node, {
fn = 'copy', fn = 'copy',
args = { s, t }, args = { s, t },
}) })
end end
function netfs.isDir(node, dir) function netfs.isDir(node, dir)
if dir == node.mountPoint and node.directory == '' then if dir == node.mountPoint and node.directory == '' then
return true return true
end end
return remoteCommand(node, { return remoteCommand(node, {
fn = 'isDir', fn = 'isDir',
args = { resolveDir(dir, node) }, args = { resolveDir(dir, node) },
}) })
end end
function netfs.isReadOnly(node, dir) function netfs.isReadOnly(node, dir)
if dir == node.mountPoint and node.directory == '' then if dir == node.mountPoint and node.directory == '' then
return false return false
end end
return remoteCommand(node, { return remoteCommand(node, {
fn = 'isReadOnly', fn = 'isReadOnly',
args = { resolveDir(dir, node) }, args = { resolveDir(dir, node) },
}) })
end end
function netfs.getSize(node, dir) function netfs.getSize(node, dir)
if dir == node.mountPoint and node.directory == '' then if dir == node.mountPoint and node.directory == '' then
return 0 return 0
end end
return remoteCommand(node, { return remoteCommand(node, {
fn = 'getSize', fn = 'getSize',
args = { resolveDir(dir, node) }, args = { resolveDir(dir, node) },
}) })
end end
function netfs.find(node, spec) function netfs.find(node, spec)
spec = resolveDir(spec, node) spec = resolveDir(spec, node)
local list = remoteCommand(node, { local list = remoteCommand(node, {
fn = 'find', fn = 'find',
args = { spec }, args = { spec },
}) })
for k,f in ipairs(list) do for k,f in ipairs(list) do
list[k] = fs.combine(node.mountPoint, f) list[k] = fs.combine(node.mountPoint, f)
end end
return list return list
end end
function netfs.move(node, s, t) function netfs.move(node, s, t)
s = resolveDir(s, node) s = resolveDir(s, node)
t = resolveDir(t, node) t = resolveDir(t, node)
return remoteCommand(node, { return remoteCommand(node, {
fn = 'move', fn = 'move',
args = { s, t }, args = { s, t },
}) })
end end
function netfs.open(node, fn, fl) function netfs.open(node, fn, fl)
fn = resolveDir(fn, node) fn = resolveDir(fn, node)
local vfh = remoteCommand(node, { local vfh = remoteCommand(node, {
fn = 'open', fn = 'open',
args = { fn, fl }, args = { fn, fl },
}) })
if vfh then if vfh then
vfh.node = node vfh.node = node
for _,m in ipairs(vfh.methods) do for _,m in ipairs(vfh.methods) do
vfh[m] = function(...) vfh[m] = function(...)
return remoteCommand(node, { return remoteCommand(node, {
fn = 'fileOp', fn = 'fileOp',
args = { vfh.fileUid, m, ... }, args = { vfh.fileUid, m, ... },
}) })
end end
end end
end end
return vfh return vfh
end end
return netfs return netfs

View File

@ -5,143 +5,143 @@ local fs = _G.fs
local ramfs = { } local ramfs = { }
function ramfs.mount(_, nodeType) function ramfs.mount(_, nodeType)
if nodeType == 'directory' then if nodeType == 'directory' then
return { return {
nodes = { }, nodes = { },
size = 0, size = 0,
} }
elseif nodeType == 'file' then elseif nodeType == 'file' then
return { return {
size = 0, size = 0,
} }
end end
error('ramfs syntax: [directory, file]') error('ramfs syntax: [directory, file]')
end end
function ramfs.delete(node, dir) function ramfs.delete(node, dir)
if node.mountPoint == dir then if node.mountPoint == dir then
fs.unmount(node.mountPoint) fs.unmount(node.mountPoint)
end end
end end
function ramfs.exists(node, fn) function ramfs.exists(node, fn)
return node.mountPoint == fn return node.mountPoint == fn
end end
function ramfs.getSize(node) function ramfs.getSize(node)
return node.size return node.size
end end
function ramfs.isReadOnly() function ramfs.isReadOnly()
return false return false
end end
function ramfs.makeDir(_, dir) function ramfs.makeDir(_, dir)
fs.mount(dir, 'ramfs', 'directory') fs.mount(dir, 'ramfs', 'directory')
end end
function ramfs.isDir(node) function ramfs.isDir(node)
return not not node.nodes return not not node.nodes
end end
function ramfs.getDrive() function ramfs.getDrive()
return 'ram' return 'ram'
end end
function ramfs.list(node, dir) function ramfs.list(node, dir)
if node.nodes and node.mountPoint == dir then if node.nodes and node.mountPoint == dir then
local files = { } local files = { }
for k in pairs(node.nodes) do for k in pairs(node.nodes) do
table.insert(files, k) table.insert(files, k)
end end
return files return files
end end
error('Not a directory') error('Not a directory')
end end
function ramfs.open(node, fn, fl) function ramfs.open(node, fn, fl)
if fl ~= 'r' and fl ~= 'w' and fl ~= 'rb' and fl ~= 'wb' then if fl ~= 'r' and fl ~= 'w' and fl ~= 'rb' and fl ~= 'wb' then
error('Unsupported mode') error('Unsupported mode')
end end
if fl == 'r' then if fl == 'r' then
if node.mountPoint ~= fn then if node.mountPoint ~= fn then
return return
end end
local ctr = 0 local ctr = 0
local lines local lines
return { return {
readLine = function() readLine = function()
if not lines then if not lines then
lines = Util.split(node.contents) lines = Util.split(node.contents)
end end
ctr = ctr + 1 ctr = ctr + 1
return lines[ctr] return lines[ctr]
end, end,
readAll = function() readAll = function()
return node.contents return node.contents
end, end,
close = function() close = function()
lines = nil lines = nil
end, end,
} }
elseif fl == 'w' then elseif fl == 'w' then
node = fs.mount(fn, 'ramfs', 'file') node = fs.mount(fn, 'ramfs', 'file')
local c = '' local c = ''
return { return {
write = function(str) write = function(str)
c = c .. str c = c .. str
end, end,
writeLine = function(str) writeLine = function(str)
c = c .. str .. '\n' c = c .. str .. '\n'
end, end,
flush = function() flush = function()
node.contents = c node.contents = c
node.size = #c node.size = #c
end, end,
close = function() close = function()
node.contents = c node.contents = c
node.size = #c node.size = #c
c = nil c = nil
end, end,
} }
elseif fl == 'rb' then elseif fl == 'rb' then
if node.mountPoint ~= fn or not node.contents then if node.mountPoint ~= fn or not node.contents then
return return
end end
local ctr = 0 local ctr = 0
return { return {
read = function() read = function()
ctr = ctr + 1 ctr = ctr + 1
return node.contents[ctr] return node.contents[ctr]
end, end,
close = function() close = function()
end, end,
} }
elseif fl == 'wb' then elseif fl == 'wb' then
node = fs.mount(fn, 'ramfs', 'file') node = fs.mount(fn, 'ramfs', 'file')
local c = { } local c = { }
return { return {
write = function(b) write = function(b)
table.insert(c, b) table.insert(c, b)
end, end,
flush = function() flush = function()
node.contents = c node.contents = c
node.size = #c node.size = #c
end, end,
close = function() close = function()
node.contents = c node.contents = c
node.size = #c node.size = #c
c = nil c = nil
end, end,
} }
end end
end end
return ramfs return ramfs

View File

@ -5,91 +5,91 @@ local fs = _G.fs
local urlfs = { } local urlfs = { }
function urlfs.mount(_, url) function urlfs.mount(_, url)
if not url then if not url then
error('URL is required') error('URL is required')
end end
return { return {
url = url, url = url,
} }
end end
function urlfs.delete(_, dir) function urlfs.delete(_, dir)
fs.unmount(dir) fs.unmount(dir)
end end
function urlfs.exists() function urlfs.exists()
return true return true
end end
function urlfs.getSize(node) function urlfs.getSize(node)
return node.size or 0 return node.size or 0
end end
function urlfs.isReadOnly() function urlfs.isReadOnly()
return true return true
end end
function urlfs.isDir() function urlfs.isDir()
return false return false
end end
function urlfs.getDrive() function urlfs.getDrive()
return 'url' return 'url'
end end
function urlfs.open(node, fn, fl) function urlfs.open(node, fn, fl)
if fl == 'w' or fl == 'wb' then if fl == 'w' or fl == 'wb' then
fs.delete(fn) fs.delete(fn)
return fs.open(fn, fl) return fs.open(fn, fl)
end end
if fl ~= 'r' and fl ~= 'rb' then if fl ~= 'r' and fl ~= 'rb' then
error('Unsupported mode') error('Unsupported mode')
end end
local c = node.cache local c = node.cache
if not c then if not c then
c = Util.httpGet(node.url) c = Util.httpGet(node.url)
if c then if c then
node.cache = c node.cache = c
node.size = #c node.size = #c
end end
end end
if not c then if not c then
return return
end end
local ctr = 0 local ctr = 0
local lines local lines
if fl == 'r' then if fl == 'r' then
return { return {
readLine = function() readLine = function()
if not lines then if not lines then
lines = Util.split(c) lines = Util.split(c)
end end
ctr = ctr + 1 ctr = ctr + 1
return lines[ctr] return lines[ctr]
end, end,
readAll = function() readAll = function()
return c return c
end, end,
close = function() close = function()
lines = nil lines = nil
end, end,
} }
end end
return { return {
read = function() read = function()
ctr = ctr + 1 ctr = ctr + 1
return c:sub(ctr, ctr):byte() return c:sub(ctr, ctr):byte()
end, end,
close = function() close = function()
ctr = 0 ctr = 0
end, end,
} }
end end
return urlfs return urlfs

View File

@ -8,42 +8,42 @@ local git = { }
function git.list(repository) function git.list(repository)
local t = Util.split(repository, '(.-)/') local t = Util.split(repository, '(.-)/')
local user = t[1] local user = t[1]
local repo = t[2] local repo = t[2]
local branch = t[3] or 'master' local branch = t[3] or 'master'
local dataUrl = string.format(TREE_URL, user, repo, branch) local dataUrl = string.format(TREE_URL, user, repo, branch)
local contents = Util.download(dataUrl) local contents = Util.download(dataUrl)
if not contents then if not contents then
error('Invalid repository') error('Invalid repository')
end end
local data = json.decode(contents) local data = json.decode(contents)
if data.message and data.message:find("API rate limit exceeded") then if data.message and data.message:find("API rate limit exceeded") then
error("Out of API calls, try again later") error("Out of API calls, try again later")
end end
if data.message and data.message == "Not found" then if data.message and data.message == "Not found" then
error("Invalid repository") error("Invalid repository")
end end
local list = { } local list = { }
for _,v in pairs(data.tree) do for _,v in pairs(data.tree) do
if v.type == "blob" then if v.type == "blob" then
v.path = v.path:gsub("%s","%%20") v.path = v.path:gsub("%s","%%20")
list[v.path] = { list[v.path] = {
url = string.format(FILE_URL, user, repo, branch, v.path), url = string.format(FILE_URL, user, repo, branch, v.path),
size = v.size, size = v.size,
} }
end end
end end
return list return list
end end
return git return git

View File

@ -5,151 +5,151 @@ local gps = _G.gps
local turtle = _G.turtle local turtle = _G.turtle
function GPS.locate(timeout, debug) function GPS.locate(timeout, debug)
local pt = { } local pt = { }
timeout = timeout or 10 timeout = timeout or 10
pt.x, pt.y, pt.z = gps.locate(timeout, debug) pt.x, pt.y, pt.z = gps.locate(timeout, debug)
if pt.x then if pt.x then
return pt return pt
end end
end end
function GPS.isAvailable() function GPS.isAvailable()
return device.wireless_modem and GPS.locate() return device.wireless_modem and GPS.locate()
end end
function GPS.getPoint(timeout, debug) function GPS.getPoint(timeout, debug)
local pt = GPS.locate(timeout, debug) local pt = GPS.locate(timeout, debug)
if not pt then if not pt then
return return
end end
pt.x = math.floor(pt.x) pt.x = math.floor(pt.x)
pt.y = math.floor(pt.y) pt.y = math.floor(pt.y)
pt.z = math.floor(pt.z) pt.z = math.floor(pt.z)
if _G.pocket then if _G.pocket then
pt.y = pt.y - 1 pt.y = pt.y - 1
end end
return pt return pt
end end
function GPS.getHeading(timeout) function GPS.getHeading(timeout)
if not turtle then if not turtle then
return return
end end
local apt = GPS.locate(timeout) local apt = GPS.locate(timeout)
if not apt then if not apt then
return return
end end
local heading = turtle.point.heading local heading = turtle.point.heading
while not turtle.forward() do while not turtle.forward() do
turtle.turnRight() turtle.turnRight()
if turtle.getHeading() == heading then if turtle.getHeading() == heading then
_G.printError('GPS.getPoint: Unable to move forward') _G.printError('GPS.getPoint: Unable to move forward')
return return
end end
end end
local bpt = GPS.locate() local bpt = GPS.locate()
if not bpt then if not bpt then
return return
end end
if apt.x < bpt.x then if apt.x < bpt.x then
return 0 return 0
elseif apt.z < bpt.z then elseif apt.z < bpt.z then
return 1 return 1
elseif apt.x > bpt.x then elseif apt.x > bpt.x then
return 2 return 2
end end
return 3 return 3
end end
function GPS.getPointAndHeading(timeout) function GPS.getPointAndHeading(timeout)
local heading = GPS.getHeading(timeout) local heading = GPS.getHeading(timeout)
if heading then if heading then
local pt = GPS.getPoint() local pt = GPS.getPoint()
if pt then if pt then
pt.heading = heading pt.heading = heading
end end
return pt return pt
end end
end end
-- from stock gps API -- from stock gps API
local function trilaterate(A, B, C) local function trilaterate(A, B, C)
local a2b = B.position - A.position local a2b = B.position - A.position
local a2c = C.position - A.position local a2c = C.position - A.position
if math.abs( a2b:normalize():dot( a2c:normalize() ) ) > 0.999 then if math.abs( a2b:normalize():dot( a2c:normalize() ) ) > 0.999 then
return return
end end
local d = a2b:length() local d = a2b:length()
local ex = a2b:normalize( ) local ex = a2b:normalize( )
local i = ex:dot( a2c ) local i = ex:dot( a2c )
local ey = (a2c - (ex * i)):normalize() local ey = (a2c - (ex * i)):normalize()
local j = ey:dot( a2c ) local j = ey:dot( a2c )
local ez = ex:cross( ey ) local ez = ex:cross( ey )
local r1 = A.distance local r1 = A.distance
local r2 = B.distance local r2 = B.distance
local r3 = C.distance local r3 = C.distance
local x = (r1*r1 - r2*r2 + d*d) / (2*d) 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 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 local zSquared = r1*r1 - x*x - y*y
if zSquared > 0 then if zSquared > 0 then
local z = math.sqrt( zSquared ) local z = math.sqrt( zSquared )
local result1 = result + (ez * z) local result1 = result + (ez * z)
local result2 = result - (ez * z) local result2 = result - (ez * z)
local rounded1, rounded2 = result1:round(), result2:round() local rounded1, rounded2 = result1:round(), result2:round()
if rounded1.x ~= rounded2.x or rounded1.y ~= rounded2.y or rounded1.z ~= rounded2.z then if rounded1.x ~= rounded2.x or rounded1.y ~= rounded2.y or rounded1.z ~= rounded2.z then
return rounded1, rounded2 return rounded1, rounded2
else else
return rounded1 return rounded1
end end
end end
return result:round() return result:round()
end end
local function narrow( p1, p2, fix ) local function narrow( p1, p2, fix )
local dist1 = math.abs( (p1 - 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 ) local dist2 = math.abs( (p2 - fix.position):length() - fix.distance )
if math.abs(dist1 - dist2) < 0.05 then if math.abs(dist1 - dist2) < 0.05 then
return p1, p2 return p1, p2
elseif dist1 < dist2 then elseif dist1 < dist2 then
return p1:round() return p1:round()
else else
return p2:round() return p2:round()
end end
end end
-- end stock gps api -- end stock gps api
function GPS.trilaterate(tFixes) 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 if pos2 then
pos1, pos2 = narrow(pos1, pos2, tFixes[4]) pos1, pos2 = narrow(pos1, pos2, tFixes[4])
end end
if pos1 and pos2 then if pos1 and pos2 then
print("Ambiguous position") print("Ambiguous position")
print("Could be "..pos1.x..","..pos1.y..","..pos1.z.." or "..pos2.x..","..pos2.y..","..pos2.z ) print("Could be "..pos1.x..","..pos1.y..","..pos1.z.." or "..pos2.x..","..pos2.y..","..pos2.z )
return return
end end
return pos1 return pos1
end end
return GPS return GPS

View File

@ -5,46 +5,46 @@ local History_mt = { __index = History }
function History.load(filename, limit) function History.load(filename, limit)
local self = setmetatable({ local self = setmetatable({
limit = limit, limit = limit,
filename = filename, filename = filename,
}, History_mt) }, History_mt)
self.entries = Util.readLines(filename) or { } self.entries = Util.readLines(filename) or { }
self.pos = #self.entries + 1 self.pos = #self.entries + 1
return self return self
end end
function History:add(line) function History:add(line)
if line ~= self.entries[#self.entries] then if line ~= self.entries[#self.entries] then
table.insert(self.entries, line) table.insert(self.entries, line)
if self.limit then if self.limit then
while #self.entries > self.limit do while #self.entries > self.limit do
table.remove(self.entries, 1) table.remove(self.entries, 1)
end end
end end
Util.writeLines(self.filename, self.entries) Util.writeLines(self.filename, self.entries)
self.pos = #self.entries + 1 self.pos = #self.entries + 1
end end
end end
function History:reset() function History:reset()
self.pos = #self.entries + 1 self.pos = #self.entries + 1
end end
function History:back() function History:back()
if self.pos > 1 then if self.pos > 1 then
self.pos = self.pos - 1 self.pos = self.pos - 1
return self.entries[self.pos] return self.entries[self.pos]
end end
end end
function History:forward() function History:forward()
if self.pos <= #self.entries then if self.pos <= #self.entries then
self.pos = self.pos + 1 self.pos = self.pos + 1
return self.entries[self.pos] return self.entries[self.pos]
end end
end end
return History return History

View File

@ -8,170 +8,170 @@ local http = _G.http
local os = _G.os local os = _G.os
if not http._patched then if not http._patched then
-- fix broken http get -- fix broken http get
local syncLocks = { } local syncLocks = { }
local function sync(obj, fn) local function sync(obj, fn)
local key = tostring(obj) local key = tostring(obj)
if syncLocks[key] then if syncLocks[key] then
local cos = tostring(coroutine.running()) local cos = tostring(coroutine.running())
table.insert(syncLocks[key], cos) table.insert(syncLocks[key], cos)
repeat repeat
local _, co = os.pullEvent('sync_lock') local _, co = os.pullEvent('sync_lock')
until co == cos until co == cos
else else
syncLocks[key] = { } syncLocks[key] = { }
end end
fn() fn()
local co = table.remove(syncLocks[key], 1) local co = table.remove(syncLocks[key], 1)
if co then if co then
os.queueEvent('sync_lock', co) os.queueEvent('sync_lock', co)
else else
syncLocks[key] = nil syncLocks[key] = nil
end end
end end
-- todo -- completely replace http.get with function that -- todo -- completely replace http.get with function that
-- checks for success on permanent redirects (minecraft 1.75 bug) -- checks for success on permanent redirects (minecraft 1.75 bug)
http._patched = http.get http._patched = http.get
function http.get(url, headers) function http.get(url, headers)
local s, m local s, m
sync(url, function() sync(url, function()
s, m = http._patched(url, headers) s, m = http._patched(url, headers)
end) end)
return s, m return s, m
end end
end end
local function loadUrl(url) local function loadUrl(url)
local c local c
local h = http.get(url) local h = http.get(url)
if h then if h then
c = h.readAll() c = h.readAll()
h.close() h.close()
end end
if c and #c > 0 then if c and #c > 0 then
return c return c
end end
end end
-- Add require and package to the environment -- Add require and package to the environment
local function requireWrapper(env) local function requireWrapper(env)
local function standardSearcher(modname) local function standardSearcher(modname)
if env.package.loaded[modname] then if env.package.loaded[modname] then
return function() return function()
return env.package.loaded[modname] return env.package.loaded[modname]
end end
end end
end end
local function shellSearcher(modname) local function shellSearcher(modname)
local fname = modname:gsub('%.', '/') .. '.lua' local fname = modname:gsub('%.', '/') .. '.lua'
if env.shell and type(env.shell.dir) == 'function' then if env.shell and type(env.shell.dir) == 'function' then
local path = env.shell.resolve(fname) local path = env.shell.resolve(fname)
if fs.exists(path) and not fs.isDir(path) then if fs.exists(path) and not fs.isDir(path) then
return loadfile(path, env) return loadfile(path, env)
end end
end end
end end
local function pathSearcher(modname) local function pathSearcher(modname)
local fname = modname:gsub('%.', '/') .. '.lua' local fname = modname:gsub('%.', '/') .. '.lua'
for dir in string.gmatch(env.package.path, "[^:]+") do for dir in string.gmatch(env.package.path, "[^:]+") do
local path = fs.combine(dir, fname) local path = fs.combine(dir, fname)
if fs.exists(path) and not fs.isDir(path) then if fs.exists(path) and not fs.isDir(path) then
return loadfile(path, env) return loadfile(path, env)
end end
end end
end end
-- require('BniCQPVf') -- require('BniCQPVf')
local function pastebinSearcher(modname) local function pastebinSearcher(modname)
if #modname == 8 and not modname:match('%W') then if #modname == 8 and not modname:match('%W') then
local url = PASTEBIN_URL .. '/' .. modname local url = PASTEBIN_URL .. '/' .. modname
local c = loadUrl(url) local c = loadUrl(url)
if c then if c then
return load(c, modname, nil, env) return load(c, modname, nil, env)
end end
end end
end end
-- require('kepler155c.opus.master.sys.apis.util') -- require('kepler155c.opus.master.sys.apis.util')
local function gitSearcher(modname) local function gitSearcher(modname)
local fname = modname:gsub('%.', '/') .. '.lua' local fname = modname:gsub('%.', '/') .. '.lua'
local _, count = fname:gsub("/", "") local _, count = fname:gsub("/", "")
if count >= 3 then if count >= 3 then
local url = GIT_URL .. '/' .. fname local url = GIT_URL .. '/' .. fname
local c = loadUrl(url) local c = loadUrl(url)
if c then if c then
return load(c, modname, nil, env) return load(c, modname, nil, env)
end end
end end
end end
local function urlSearcher(modname) local function urlSearcher(modname)
local fname = modname:gsub('%.', '/') .. '.lua' local fname = modname:gsub('%.', '/') .. '.lua'
if fname:sub(1, 1) ~= '/' then if fname:sub(1, 1) ~= '/' then
for entry in string.gmatch(env.package.upath, "[^;]+") do for entry in string.gmatch(env.package.upath, "[^;]+") do
local url = entry .. '/' .. fname local url = entry .. '/' .. fname
local c = loadUrl(url) local c = loadUrl(url)
if c then if c then
return load(c, modname, nil, env) return load(c, modname, nil, env)
end end
end end
end end
end end
-- place package and require function into env -- place package and require function into env
env.package = { env.package = {
path = env.LUA_PATH or os.getenv('LUA_PATH') or DEFAULT_PATH, path = env.LUA_PATH or os.getenv('LUA_PATH') or DEFAULT_PATH,
upath = env.LUA_UPATH or os.getenv('LUA_UPATH') or DEFAULT_UPATH, upath = env.LUA_UPATH or os.getenv('LUA_UPATH') or DEFAULT_UPATH,
config = '/\n:\n?\n!\n-', config = '/\n:\n?\n!\n-',
loaded = { loaded = {
math = math, math = math,
string = string, string = string,
table = table, table = table,
io = io, io = io,
os = os, os = os,
}, },
loaders = { loaders = {
standardSearcher, standardSearcher,
shellSearcher, shellSearcher,
pathSearcher, pathSearcher,
pastebinSearcher, pastebinSearcher,
gitSearcher, gitSearcher,
urlSearcher, urlSearcher,
} }
} }
function env.require(modname) function env.require(modname)
for _,searcher in ipairs(env.package.loaders) do for _,searcher in ipairs(env.package.loaders) do
local fn, msg = searcher(modname) local fn, msg = searcher(modname)
if fn then if fn then
local module, msg2 = fn(modname, env) local module, msg2 = fn(modname, env)
if not module then if not module then
error(msg2 or (modname .. ' module returned nil'), 2) error(msg2 or (modname .. ' module returned nil'), 2)
end end
env.package.loaded[modname] = module env.package.loaded[modname] = module
return module return module
end end
if msg then if msg then
error(msg, 2) error(msg, 2)
end end
end end
error('Unable to find module ' .. modname) error('Unable to find module ' .. modname)
end end
return env.require -- backwards compatible return env.require -- backwards compatible
end end
return function(env) return function(env)
env = env or getfenv(2) env = env or getfenv(2)
--setfenv(requireWrapper, env) --setfenv(requireWrapper, env)
return requireWrapper(env) return requireWrapper(env)
end end

View File

@ -5,150 +5,176 @@ local keys = _G.keys
local os = _G.os local os = _G.os
local modifiers = Util.transpose { local modifiers = Util.transpose {
keys.leftCtrl, keys.rightCtrl, keys.leftCtrl, keys.rightCtrl,
keys.leftShift, keys.rightShift, keys.leftShift, keys.rightShift,
keys.leftAlt, keys.rightAlt, keys.leftAlt, keys.rightAlt,
} }
local input = { local input = {
state = { }, state = { },
} }
function input:modifierPressed() function input:modifierPressed()
return keyboard.state[keys.leftCtrl] or return keyboard.state[keys.leftCtrl] or
keyboard.state[keys.rightCtrl] or keyboard.state[keys.rightCtrl] or
keyboard.state[keys.leftAlt] or keyboard.state[keys.leftAlt] or
keyboard.state[keys.rightAlt] keyboard.state[keys.rightAlt]
end end
function input:toCode(ch, code) function input:toCode(ch, code)
local result = { } local result = { }
if keyboard.state[keys.leftCtrl] or keyboard.state[keys.rightCtrl] or if not ch and code == 1 then
code == keys.leftCtrl or code == keys.rightCtrl then ch = 'escape'
table.insert(result, 'control') end
end
if keyboard.state[keys.leftAlt] or keyboard.state[keys.rightAlt] or if keyboard.state[keys.leftCtrl] or keyboard.state[keys.rightCtrl] or
code == keys.leftAlt or code == keys.rightAlt then code == keys.leftCtrl or code == keys.rightCtrl then
table.insert(result, 'alt') table.insert(result, 'control')
end end
if keyboard.state[keys.leftShift] or keyboard.state[keys.rightShift] or -- the key-up event for alt keys is not generated if the minecraft
code == keys.leftShift or code == keys.rightShift then -- window loses focus
if code and modifiers[code] then --
table.insert(result, 'shift') -- if keyboard.state[keys.leftAlt] or keyboard.state[keys.rightAlt] or
elseif #ch == 1 then -- code == keys.leftAlt or code == keys.rightAlt then
table.insert(result, ch:upper()) -- table.insert(result, 'alt')
else -- end
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, '-') 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 end
function input:reset() function input:reset()
self.state = { } self.state = { }
self.fired = nil self.fired = nil
self.timer = nil self.timer = nil
self.mch = nil self.mch = nil
self.mfired = nil self.mfired = nil
end end
function input:translate(event, code, p1, p2) function input:translate(event, code, p1, p2)
if event == 'key' then if event == 'key' then
if p1 then -- key is held down if p1 then -- key is held down
if not modifiers[code] then if not modifiers[code] then
self.fired = true self.fired = true
return input:toCode(keys.getName(code), code) return { code = input:toCode(keys.getName(code), code) }
end end
else else
self.state[code] = true self.state[code] = true
if self:modifierPressed() and not modifiers[code] or code == 57 then if self:modifierPressed() and not modifiers[code] or code == 57 then
self.fired = true self.fired = true
return input:toCode(keys.getName(code), code) return { code = input:toCode(keys.getName(code), code) }
else else
self.fired = false self.fired = false
end end
end end
elseif event == 'char' then elseif event == 'char' then
if not self:modifierPressed() then if not self:modifierPressed() then
self.fired = true self.fired = true
return input:toCode(code) return { code = event, ch = input:toCode(code) }
end end
elseif event == 'key_up' then elseif event == 'key_up' then
if not self.fired then if not self.fired then
if self.state[code] then if self.state[code] then
self.fired = true self.fired = true
local ch = input:toCode(keys.getName(code), code) local ch = input:toCode(keys.getName(code), code)
self.state[code] = nil self.state[code] = nil
return ch return { code = ch }
end end
end end
self.state[code] = nil self.state[code] = nil
elseif event == 'paste' then elseif event == 'paste' then
--self.state[keys.leftCtrl] = nil self.fired = true
--self.state[keys.rightCtrl] = nil if keyboard.state[keys.leftShift] or keyboard.state[keys.rightShift] then
self.fired = true return { code = 'shift-paste', text = code }
return input:toCode('paste', 255) else
return { code = 'paste', text = code }
end
elseif event == 'mouse_click' then elseif event == 'mouse_click' then
local buttons = { 'mouse_click', 'mouse_rightclick' } local buttons = { 'mouse_click', 'mouse_rightclick' }
self.mch = buttons[code] self.mch = buttons[code]
self.mfired = nil self.mfired = nil
elseif event == 'mouse_drag' then elseif event == 'mouse_drag' then
self.mfired = true self.mfired = true
self.fired = true self.fired = true
return input:toCode('mouse_drag', 255) return {
code = input:toCode('mouse_drag', 255),
button = code,
x = p1,
y = p2,
}
elseif event == 'mouse_up' then elseif event == 'mouse_up' then
if not self.mfired then if not self.mfired then
local clock = os.clock() local clock = os.clock()
if self.timer and if self.timer and
p1 == self.x and p2 == self.y and p1 == self.x and p2 == self.y and
(clock - self.timer < .5) then (clock - self.timer < .5) then
self.mch = 'mouse_doubleclick' self.mch = 'mouse_doubleclick'
self.timer = nil self.timer = nil
else else
self.timer = os.clock() self.timer = os.clock()
self.x = p1 self.x = p1
self.y = p2 self.y = p2
end end
self.mfired = input:toCode(self.mch, 255) self.mfired = input:toCode(self.mch, 255)
else else
self.mch = 'mouse_up' self.mch = 'mouse_up'
self.mfired = input:toCode(self.mch, 255) self.mfired = input:toCode(self.mch, 255)
end end
self.fired = true self.fired = true
return self.mfired return {
code = self.mfired,
button = code,
x = p1,
y = p2,
}
elseif event == "mouse_scroll" then elseif event == "mouse_scroll" then
local directions = { local directions = {
[ -1 ] = 'scrollUp', [ -1 ] = 'scroll_up',
[ 1 ] = 'scrollDown' [ 1 ] = 'scroll_down'
} }
self.fired = true self.fired = true
return input:toCode(directions[code], 255) return {
end code = input:toCode(directions[code], 255),
x = p1,
y = p2,
}
elseif event == 'terminate' then
return { code = 'terminate' }
end
end end
function input:test() function input:test()
while true do while true do
local ch = self:translate(os.pullEvent()) local ch = self:translate(os.pullEvent())
if ch then if ch then
print('GOT: ' .. tostring(ch)) Util.print(ch)
end end
end end
end end
return input return input

View File

@ -7,215 +7,215 @@ local json = { }
local controls = {["\n"]="\\n", ["\r"]="\\r", ["\t"]="\\t", ["\b"]="\\b", ["\f"]="\\f", ["\""]="\\\"", ["\\"]="\\\\"} local controls = {["\n"]="\\n", ["\r"]="\\r", ["\t"]="\\t", ["\b"]="\\b", ["\f"]="\\f", ["\""]="\\\"", ["\\"]="\\\\"}
local function isArray(t) local function isArray(t)
local max = 0 local max = 0
for k,v in pairs(t) do for k,v in pairs(t) do
if type(k) ~= "number" then if type(k) ~= "number" then
return false return false
elseif k > max then elseif k > max then
max = k max = k
end end
end end
return max == #t return max == #t
end end
local whites = {['\n']=true; ['\r']=true; ['\t']=true; [' ']=true; [',']=true; [':']=true} local whites = {['\n']=true; ['\r']=true; ['\t']=true; [' ']=true; [',']=true; [':']=true}
local function removeWhite(str) local function removeWhite(str)
while whites[str:sub(1, 1)] do while whites[str:sub(1, 1)] do
str = str:sub(2) str = str:sub(2)
end end
return str return str
end end
------------------------------------------------------------------ encoding ------------------------------------------------------------------ encoding
local function encodeCommon(val, pretty, tabLevel, tTracking) local function encodeCommon(val, pretty, tabLevel, tTracking)
local str = "" local str = ""
-- Tabbing util -- Tabbing util
local function tab(s) local function tab(s)
str = str .. ("\t"):rep(tabLevel) .. s str = str .. ("\t"):rep(tabLevel) .. s
end end
local function arrEncoding(val, bracket, closeBracket, iterator, loopFunc) local function arrEncoding(val, bracket, closeBracket, iterator, loopFunc)
str = str .. bracket str = str .. bracket
if pretty then if pretty then
str = str .. "\n" str = str .. "\n"
tabLevel = tabLevel + 1 tabLevel = tabLevel + 1
end end
for k,v in iterator(val) do for k,v in iterator(val) do
tab("") tab("")
loopFunc(k,v) loopFunc(k,v)
str = str .. "," str = str .. ","
if pretty then str = str .. "\n" end if pretty then str = str .. "\n" end
end end
if pretty then if pretty then
tabLevel = tabLevel - 1 tabLevel = tabLevel - 1
end end
if str:sub(-2) == ",\n" then if str:sub(-2) == ",\n" then
str = str:sub(1, -3) .. "\n" str = str:sub(1, -3) .. "\n"
elseif str:sub(-1) == "," then elseif str:sub(-1) == "," then
str = str:sub(1, -2) str = str:sub(1, -2)
end end
tab(closeBracket) tab(closeBracket)
end end
-- Table encoding -- Table encoding
if type(val) == "table" then if type(val) == "table" then
assert(not tTracking[val], "Cannot encode a table holding itself recursively") assert(not tTracking[val], "Cannot encode a table holding itself recursively")
tTracking[val] = true tTracking[val] = true
if isArray(val) then if isArray(val) then
arrEncoding(val, "[", "]", ipairs, function(k,v) arrEncoding(val, "[", "]", ipairs, function(k,v)
str = str .. encodeCommon(v, pretty, tabLevel, tTracking) str = str .. encodeCommon(v, pretty, tabLevel, tTracking)
end) end)
else else
debug(val) debug(val)
arrEncoding(val, "{", "}", pairs, function(k,v) arrEncoding(val, "{", "}", pairs, function(k,v)
assert(type(k) == "string", "JSON object keys must be strings", 2) assert(type(k) == "string", "JSON object keys must be strings", 2)
str = str .. encodeCommon(k, pretty, tabLevel, tTracking) str = str .. encodeCommon(k, pretty, tabLevel, tTracking)
str = str .. (pretty and ": " or ":") .. encodeCommon(v, pretty, tabLevel, tTracking) str = str .. (pretty and ": " or ":") .. encodeCommon(v, pretty, tabLevel, tTracking)
end) end)
end end
-- String encoding -- String encoding
elseif type(val) == "string" then elseif type(val) == "string" then
str = '"' .. val:gsub("[%c\"\\]", controls) .. '"' str = '"' .. val:gsub("[%c\"\\]", controls) .. '"'
-- Number encoding -- Number encoding
elseif type(val) == "number" or type(val) == "boolean" then elseif type(val) == "number" or type(val) == "boolean" then
str = tostring(val) str = tostring(val)
else else
error("JSON only supports arrays, objects, numbers, booleans, and strings", 2) error("JSON only supports arrays, objects, numbers, booleans, and strings", 2)
end end
return str return str
end end
function json.encode(val) function json.encode(val)
return encodeCommon(val, false, 0, {}) return encodeCommon(val, false, 0, {})
end end
function json.encodePretty(val) function json.encodePretty(val)
return encodeCommon(val, true, 0, {}) return encodeCommon(val, true, 0, {})
end end
function json.encodeToFile(path, val) function json.encodeToFile(path, val)
local file = io.open(path, "w") local file = io.open(path, "w")
assert(file, "Unable to open file") assert(file, "Unable to open file")
file:write(json.encodePretty(val)) file:write(json.encodePretty(val))
file:close() file:close()
end end
------------------------------------------------------------------ decoding ------------------------------------------------------------------ decoding
local decodeControls = {} local decodeControls = {}
for k,v in pairs(controls) do for k,v in pairs(controls) do
decodeControls[v] = k decodeControls[v] = k
end end
local function parseBoolean(str) local function parseBoolean(str)
if str:sub(1, 4) == "true" then if str:sub(1, 4) == "true" then
return true, removeWhite(str:sub(5)) return true, removeWhite(str:sub(5))
else else
return false, removeWhite(str:sub(6)) return false, removeWhite(str:sub(6))
end end
end end
local function parseNull(str) local function parseNull(str)
return nil, removeWhite(str:sub(5)) return nil, removeWhite(str:sub(5))
end end
local numChars = {['e']=true; ['E']=true; ['+']=true; ['-']=true; ['.']=true} local numChars = {['e']=true; ['E']=true; ['+']=true; ['-']=true; ['.']=true}
local function parseNumber(str) local function parseNumber(str)
local i = 1 local i = 1
while numChars[str:sub(i, i)] or tonumber(str:sub(i, i)) do while numChars[str:sub(i, i)] or tonumber(str:sub(i, i)) do
i = i + 1 i = i + 1
end end
local val = tonumber(str:sub(1, i - 1)) local val = tonumber(str:sub(1, i - 1))
str = removeWhite(str:sub(i)) str = removeWhite(str:sub(i))
return val, str return val, str
end end
local function parseString(str) local function parseString(str)
str = str:sub(2) str = str:sub(2)
local s = "" local s = ""
while str:sub(1,1) ~= "\"" do while str:sub(1,1) ~= "\"" do
local next = str:sub(1,1) local next = str:sub(1,1)
str = str:sub(2) str = str:sub(2)
assert(next ~= "\n", "Unclosed string") assert(next ~= "\n", "Unclosed string")
if next == "\\" then if next == "\\" then
local escape = str:sub(1,1) local escape = str:sub(1,1)
str = str:sub(2) str = str:sub(2)
next = assert(decodeControls[next..escape], "Invalid escape character") next = assert(decodeControls[next..escape], "Invalid escape character")
end end
s = s .. next s = s .. next
end end
return s, removeWhite(str:sub(2)) return s, removeWhite(str:sub(2))
end end
function json.parseArray(str) function json.parseArray(str)
str = removeWhite(str:sub(2)) str = removeWhite(str:sub(2))
local val = {} local val = {}
local i = 1 local i = 1
while str:sub(1, 1) ~= "]" do while str:sub(1, 1) ~= "]" do
local v local v
v, str = json.parseValue(str) v, str = json.parseValue(str)
val[i] = v val[i] = v
i = i + 1 i = i + 1
str = removeWhite(str) str = removeWhite(str)
end end
str = removeWhite(str:sub(2)) str = removeWhite(str:sub(2))
return val, str return val, str
end end
function json.parseValue(str) function json.parseValue(str)
local fchar = str:sub(1, 1) local fchar = str:sub(1, 1)
if fchar == "{" then if fchar == "{" then
return json.parseObject(str) return json.parseObject(str)
elseif fchar == "[" then elseif fchar == "[" then
return json.parseArray(str) return json.parseArray(str)
elseif tonumber(fchar) ~= nil or numChars[fchar] then elseif tonumber(fchar) ~= nil or numChars[fchar] then
return parseNumber(str) return parseNumber(str)
elseif str:sub(1, 4) == "true" or str:sub(1, 5) == "false" then elseif str:sub(1, 4) == "true" or str:sub(1, 5) == "false" then
return parseBoolean(str) return parseBoolean(str)
elseif fchar == "\"" then elseif fchar == "\"" then
return parseString(str) return parseString(str)
elseif str:sub(1, 4) == "null" then elseif str:sub(1, 4) == "null" then
return parseNull(str) return parseNull(str)
end end
end end
function json.parseMember(str) function json.parseMember(str)
local k, val local k, val
k, str = json.parseValue(str) k, str = json.parseValue(str)
val, str = json.parseValue(str) val, str = json.parseValue(str)
return k, val, str return k, val, str
end end
function json.parseObject(str) function json.parseObject(str)
str = removeWhite(str:sub(2)) str = removeWhite(str:sub(2))
local val = {} local val = {}
while str:sub(1, 1) ~= "}" do while str:sub(1, 1) ~= "}" do
local k, v local k, v
k, v, str = json.parseMember(str) k, v, str = json.parseMember(str)
val[k] = v val[k] = v
str = removeWhite(str) str = removeWhite(str)
end end
str = removeWhite(str:sub(2)) str = removeWhite(str:sub(2))
return val, str return val, str
end end
function json.decode(str) function json.decode(str)
str = removeWhite(str) str = removeWhite(str)
return json.parseValue(str) return json.parseValue(str)
end end
function json.decodeFromFile(path) function json.decodeFromFile(path)
local file = assert(fs.open(path, "r")) local file = assert(fs.open(path, "r"))
local decoded = json.decode(file.readAll()) local decoded = json.decode(file.readAll())
file.close() file.close()
return decoded return decoded
end end
return json return json

View File

@ -14,9 +14,9 @@
-- --
--[[ --[[
Notes: Notes:
This lighter implementation of binary heaps, based on : This lighter implementation of binary heaps, based on :
https://github.com/Yonaba/Binary-Heaps https://github.com/Yonaba/Binary-Heaps
--]] --]]
if (...) then if (...) then

View File

@ -9,16 +9,16 @@
-- --
if (...) then if (...) then
local Node = {} local Node = {}
Node.__index = Node Node.__index = Node
function Node:new(x,y,z) function Node:new(x,y,z)
return setmetatable({x = x, y = y, z = z }, Node) return setmetatable({x = x, y = y, z = z }, Node)
end end
-- Enables the use of operator '<' to compare nodes. -- 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 -- 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.__lt(A,B) return (A._f < B._f) end
function Node:getX() return self.x end function Node:getX() return self.x end
function Node:getY() return self.y end function Node:getY() return self.y end
@ -33,7 +33,7 @@ if (...) then
return self return self
end end
return setmetatable(Node, return setmetatable(Node,
{__call = function(_,...) {__call = function(_,...)
return Node:new(...) return Node:new(...)
end} end}

View File

@ -8,60 +8,60 @@
if (...) then if (...) then
local t_remove = table.remove local t_remove = table.remove
local Path = {} local Path = {}
Path.__index = Path Path.__index = Path
function Path:new() function Path:new()
return setmetatable({_nodes = {}}, Path) return setmetatable({_nodes = {}}, Path)
end end
--- Iterates on each single `node` along a `path`. At each step of iteration, --- Iterates on each single `node` along a `path`. At each step of iteration,
-- returns the `node` plus a count value. Aliased as @{Path:nodes} -- returns the `node` plus a count value. Aliased as @{Path:nodes}
-- @usage -- @usage
-- for node, count in p:iter() do -- for node, count in p:iter() do
-- ... -- ...
-- end -- end
function Path:nodes() function Path:nodes()
local i = 1 local i = 1
return function() return function()
if self._nodes[i] then if self._nodes[i] then
i = i+1 i = i+1
return self._nodes[i-1],i-1 return self._nodes[i-1],i-1
end end
end end
end end
--- `Path` compression modifier. Given a `path`, eliminates useless nodes to return a lighter `path` --- `Path` compression modifier. Given a `path`, eliminates useless nodes to return a lighter `path`
-- consisting of straight moves. Does the opposite of @{Path:fill} -- consisting of straight moves. Does the opposite of @{Path:fill}
-- @class function -- @class function
-- @treturn path self (the calling `path` itself, can be chained) -- @treturn path self (the calling `path` itself, can be chained)
-- @see Path:fill -- @see Path:fill
-- @usage p:filter() -- @usage p:filter()
function Path:filter() function Path:filter()
local i = 2 local i = 2
local xi,yi,zi,dx,dy,dz, olddx, olddy, olddz 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 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 dx, dy,dz = xi - self._nodes[i-1].x, yi-self._nodes[i-1].y, zi-self._nodes[i-1].z
while true do while true do
olddx, olddy, olddz = dx, dy, dz olddx, olddy, olddz = dx, dy, dz
if self._nodes[i+1] then if self._nodes[i+1] then
i = i+1 i = i+1
xi, yi, zi = self._nodes[i].x, self._nodes[i].y, self._nodes[i].z 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 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 if olddx == dx and olddy == dy and olddz == dz then
t_remove(self._nodes, i-1) t_remove(self._nodes, i-1)
i = i - 1 i = i - 1
end end
else break end else break end
end end
return self return self
end end
return setmetatable(Path, return setmetatable(Path,
{__call = function(_,...) {__call = function(_,...)
return Path:new(...) return Path:new(...)
end end
}) })
end end

View File

@ -20,19 +20,19 @@ if (...) then
end end
-- Extract a path from a given start/end position -- Extract a path from a given start/end position
local function traceBackPath(finder, node, startNode) local function traceBackPath(finder, node, startNode)
local path = Path:new() local path = Path:new()
path._grid = finder._grid path._grid = finder._grid
while true do while true do
if node._parent then if node._parent then
t_insert(path._nodes,1,node) t_insert(path._nodes,1,node)
node = node._parent node = node._parent
else else
t_insert(path._nodes,1,startNode) t_insert(path._nodes,1,startNode)
return path return path
end end
end end
end end
-- Lookup for value in a table -- Lookup for value in a table
local indexOf = function(t,v) local indexOf = function(t,v)
@ -43,9 +43,9 @@ if (...) then
end end
-- Is i out of range -- Is i out of range
local function outOfRange(i,low,up) local function outOfRange(i,low,up)
return (i< low or i > up) return (i< low or i > up)
end end
return { return {
arraySize = arraySize, arraySize = arraySize,

View File

@ -9,93 +9,93 @@
if (...) then if (...) then
-- Dependencies -- Dependencies
local _PATH = (...):gsub('%.grid$','') local _PATH = (...):gsub('%.grid$','')
-- Local references -- Local references
local Utils = require (_PATH .. '.core.utils') local Utils = require (_PATH .. '.core.utils')
local Node = require (_PATH .. '.core.node') local Node = require (_PATH .. '.core.node')
-- Local references -- Local references
local setmetatable = setmetatable local setmetatable = setmetatable
-- Offsets for straights moves -- Offsets for straights moves
local straightOffsets = { local straightOffsets = {
{x = 1, y = 0, z = 0} --[[W]], {x = -1, y = 0, z = 0}, --[[E]] {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 = 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]] {x = 0, y = 0, z = 1} --[[U]], {x = 0, y = -0, z = -1}, --[[D]]
} }
local Grid = {} local Grid = {}
Grid.__index = Grid Grid.__index = Grid
function Grid:new(dim) function Grid:new(dim)
local newGrid = { } local newGrid = { }
newGrid._min_x, newGrid._max_x = dim.x, dim.ex newGrid._min_x, newGrid._max_x = dim.x, dim.ex
newGrid._min_y, newGrid._max_y = dim.y, dim.ey newGrid._min_y, newGrid._max_y = dim.y, dim.ey
newGrid._min_z, newGrid._max_z = dim.z, dim.ez newGrid._min_z, newGrid._max_z = dim.z, dim.ez
newGrid._nodes = { } newGrid._nodes = { }
newGrid._width = (newGrid._max_x-newGrid._min_x)+1 newGrid._width = (newGrid._max_x-newGrid._min_x)+1
newGrid._height = (newGrid._max_y-newGrid._min_y)+1 newGrid._height = (newGrid._max_y-newGrid._min_y)+1
newGrid._length = (newGrid._max_z-newGrid._min_z)+1 newGrid._length = (newGrid._max_z-newGrid._min_z)+1
return setmetatable(newGrid,Grid) return setmetatable(newGrid,Grid)
end end
function Grid:isWalkableAt(x, y, z) function Grid:isWalkableAt(x, y, z)
local node = self:getNodeAt(x,y,z) local node = self:getNodeAt(x,y,z)
return node and node.walkable ~= 1 return node and node.walkable ~= 1
end end
function Grid:getWidth() function Grid:getWidth()
return self._width return self._width
end end
function Grid:getHeight() function Grid:getHeight()
return self._height return self._height
end end
function Grid:getNodes() function Grid:getNodes()
return self._nodes return self._nodes
end end
function Grid:getBounds() function Grid:getBounds()
return self._min_x, self._min_y, self._min_z, self._max_x, self._max_y, self._max_z return self._min_x, self._min_y, self._min_z, self._max_x, self._max_y, self._max_z
end end
--- Returns neighbours. The returned value is an array of __walkable__ nodes neighbouring a given `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 -- @treturn {node,...} an array of nodes neighbouring a given node
function Grid:getNeighbours(node) function Grid:getNeighbours(node)
local neighbours = {} local neighbours = {}
for i = 1,#straightOffsets do for i = 1,#straightOffsets do
local n = self:getNodeAt( local n = self:getNodeAt(
node.x + straightOffsets[i].x, node.x + straightOffsets[i].x,
node.y + straightOffsets[i].y, node.y + straightOffsets[i].y,
node.z + straightOffsets[i].z node.z + straightOffsets[i].z
) )
if n and self:isWalkableAt(n.x, n.y, n.z) then if n and self:isWalkableAt(n.x, n.y, n.z) then
neighbours[#neighbours+1] = n neighbours[#neighbours+1] = n
end end
end end
return neighbours return neighbours
end end
function Grid:getNodeAt(x,y,z) function Grid:getNodeAt(x,y,z)
if not x or not y or not 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(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(y,self._min_y,self._max_y) then return end
if Utils.outOfRange(z,self._min_z,self._max_z) then return end if Utils.outOfRange(z,self._min_z,self._max_z) then return end
-- inefficient -- inefficient
if not self._nodes[y] then self._nodes[y] = {} end 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] 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 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] return self._nodes[y][x][z]
end end
return setmetatable(Grid,{ return setmetatable(Grid,{
__call = function(self,...) __call = function(self,...)
return self:new(...) return self:new(...)
end end
}) })
end end

View File

@ -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, Note that this is only a partial copy of the full jumper code base. Also,
the code was modified to support 3D maps. the code was modified to support 3D maps.
--]] --]]
--[[ --[[
This work is under MIT-LICENSE This work is under MIT-LICENSE
Copyright (c) 2012-2013 Roland Yonaba. Copyright (c) 2012-2013 Roland Yonaba.
Permission is hereby granted, free of charge, to any person obtaining a copy -- https://opensource.org/licenses/MIT
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.
--]] --]]
local _VERSION = "" local _VERSION = ""
@ -33,87 +18,87 @@ local _RELEASEDATE = ""
if (...) then if (...) then
-- Dependencies -- Dependencies
local _PATH = (...):gsub('%.pathfinder$','') local _PATH = (...):gsub('%.pathfinder$','')
local Utils = require (_PATH .. '.core.utils') local Utils = require (_PATH .. '.core.utils')
-- Internalization -- Internalization
local pairs = pairs local pairs = pairs
local assert = assert local assert = assert
local setmetatable = setmetatable local setmetatable = setmetatable
--- Finders (search algorithms implemented). Refers to the search algorithms actually implemented in Jumper. --- Finders (search algorithms implemented). Refers to the search algorithms actually implemented in Jumper.
-- <li>[A*](http://en.wikipedia.org/wiki/A*_search_algorithm)</li> -- <li>[A*](http://en.wikipedia.org/wiki/A*_search_algorithm)</li>
local Finders = { local Finders = {
['ASTAR'] = require (_PATH .. '.search.astar'), ['ASTAR'] = require (_PATH .. '.search.astar'),
} }
-- Will keep track of all nodes expanded during the search -- Will keep track of all nodes expanded during the search
-- to easily reset their properties for the next pathfinding call -- to easily reset their properties for the next pathfinding call
local toClear = {} local toClear = {}
-- Performs a traceback from the goal node to the start node -- Performs a traceback from the goal node to the start node
-- Only happens when the path was found -- Only happens when the path was found
local Pathfinder = {} local Pathfinder = {}
Pathfinder.__index = Pathfinder Pathfinder.__index = Pathfinder
function Pathfinder:new(heuristic) function Pathfinder:new(heuristic)
local newPathfinder = {} local newPathfinder = {}
setmetatable(newPathfinder, Pathfinder) setmetatable(newPathfinder, Pathfinder)
self._finder = Finders.ASTAR self._finder = Finders.ASTAR
self._heuristic = heuristic self._heuristic = heuristic
return newPathfinder return newPathfinder
end end
function Pathfinder:setGrid(grid) function Pathfinder:setGrid(grid)
self._grid = 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 = {}
return self return self
end 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._VERSION = _VERSION
Pathfinder._RELEASEDATE = _RELEASEDATE Pathfinder._RELEASEDATE = _RELEASEDATE
return setmetatable(Pathfinder,{ return setmetatable(Pathfinder,{
__call = function(self,...) __call = function(self,...)
return self:new(...) return self:new(...)
end end
}) })
end end

View File

@ -35,9 +35,9 @@ if (...) then
end end
end end
-- Calculates a path. -- Calculates a path.
-- Returns the path from location `<startX, startY>` to location `<endX, endY>`. -- Returns the path from location `<startX, startY>` to location `<endX, endY>`.
return function (finder, startNode, endNode, toClear) return function (finder, startNode, endNode, toClear)
local openList = Heap() local openList = Heap()
startNode._g = 0 startNode._g = 0
startNode._h = finder._heuristic(endNode, startNode) startNode._h = finder._heuristic(endNode, startNode)

View File

@ -1,133 +1,133 @@
local Logger = { local Logger = {
fn = function() end, fn = function() end,
filteredEvents = { }, filteredEvents = { },
} }
function Logger.setLogger(fn) function Logger.setLogger(fn)
Logger.fn = fn Logger.fn = fn
end end
function Logger.disable() function Logger.disable()
Logger.setLogger(function() end) Logger.setLogger(function() end)
end end
function Logger.setDaemonLogging() function Logger.setDaemonLogging()
Logger.setLogger(function (text) Logger.setLogger(function (text)
os.queueEvent('log', { text = text }) os.queueEvent('log', { text = text })
end) end)
end end
function Logger.setMonitorLogging() function Logger.setMonitorLogging()
local debugMon = device.monitor local debugMon = device.monitor
if not debugMon then if not debugMon then
debugMon.setTextScale(.5) debugMon.setTextScale(.5)
debugMon.clear() debugMon.clear()
debugMon.setCursorPos(1, 1) debugMon.setCursorPos(1, 1)
Logger.setLogger(function(text) Logger.setLogger(function(text)
debugMon.write(text) debugMon.write(text)
debugMon.scroll(-1) debugMon.scroll(-1)
debugMon.setCursorPos(1, 1) debugMon.setCursorPos(1, 1)
end) end)
end end
end end
function Logger.setScreenLogging() function Logger.setScreenLogging()
Logger.setLogger(function(text) Logger.setLogger(function(text)
local x, y = term.getCursorPos() local x, y = term.getCursorPos()
if x ~= 1 then if x ~= 1 then
local sx, sy = term.getSize() local sx, sy = term.getSize()
term.setCursorPos(1, sy) term.setCursorPos(1, sy)
--term.scroll(1) --term.scroll(1)
end end
print(text) print(text)
end) end)
end end
function Logger.setWirelessLogging() function Logger.setWirelessLogging()
if device.wireless_modem then if device.wireless_modem then
Logger.filter('modem_message') Logger.filter('modem_message')
Logger.filter('modem_receive') Logger.filter('modem_receive')
Logger.filter('rednet_message') Logger.filter('rednet_message')
Logger.setLogger(function(text) Logger.setLogger(function(text)
device.wireless_modem.transmit(59998, os.getComputerID(), { device.wireless_modem.transmit(59998, os.getComputerID(), {
type = 'log', contents = text type = 'log', contents = text
}) })
end) end)
Logger.debug('Logging enabled') Logger.debug('Logging enabled')
return true return true
end end
end end
function Logger.setFileLogging(fileName) function Logger.setFileLogging(fileName)
fs.delete(fileName) fs.delete(fileName)
Logger.setLogger(function (text) Logger.setLogger(function (text)
local logFile local logFile
local mode = 'w' local mode = 'w'
if fs.exists(fileName) then if fs.exists(fileName) then
mode = 'a' mode = 'a'
end end
local file = io.open(fileName, mode) local file = io.open(fileName, mode)
if file then if file then
file:write(text) file:write(text)
file:write('\n') file:write('\n')
file:close() file:close()
end end
end) end)
end end
function Logger.log(category, value, ...) function Logger.log(category, value, ...)
if Logger.filteredEvents[category] then if Logger.filteredEvents[category] then
return return
end end
if type(value) == 'table' then if type(value) == 'table' then
local str local str
for k,v in pairs(value) do for k,v in pairs(value) do
if not str then if not str then
str = '{ ' str = '{ '
else else
str = str .. ', ' str = str .. ', '
end end
str = str .. k .. '=' .. tostring(v) str = str .. k .. '=' .. tostring(v)
end end
if str then if str then
value = str .. ' }' value = str .. ' }'
else else
value = '{ }' value = '{ }'
end end
elseif type(value) == 'string' then elseif type(value) == 'string' then
local args = { ... } local args = { ... }
if #args > 0 then if #args > 0 then
value = string.format(value, unpack(args)) value = string.format(value, unpack(args))
end end
else else
value = tostring(value) value = tostring(value)
end end
Logger.fn(category .. ': ' .. value) Logger.fn(category .. ': ' .. value)
end end
function Logger.debug(value, ...) function Logger.debug(value, ...)
Logger.log('debug', value, ...) Logger.log('debug', value, ...)
end end
function Logger.logNestedTable(t, indent) function Logger.logNestedTable(t, indent)
for _,v in ipairs(t) do for _,v in ipairs(t) do
if type(v) == 'table' then if type(v) == 'table' then
log('table') log('table')
logNestedTable(v) --, indent+1) logNestedTable(v) --, indent+1)
else else
log(v) log(v)
end end
end end
end end
function Logger.filter( ...) function Logger.filter( ...)
local events = { ... } local events = { ... }
for _,event in pairs(events) do for _,event in pairs(events) do
Logger.filteredEvents[event] = true Logger.filteredEvents[event] = true
end end
end end
return Logger return Logger

View File

@ -6,75 +6,75 @@ local NFT = { }
local tColourLookup = { } local tColourLookup = { }
for n = 1, 16 do for n = 1, 16 do
tColourLookup[string.byte("0123456789abcdef", n, n)] = 2 ^ (n - 1) tColourLookup[string.byte("0123456789abcdef", n, n)] = 2 ^ (n - 1)
end end
local function getColourOf(hex) local function getColourOf(hex)
return tColourLookup[hex:byte()] return tColourLookup[hex:byte()]
end end
function NFT.parse(imageText) function NFT.parse(imageText)
local image = { local image = {
fg = { }, fg = { },
bg = { }, bg = { },
text = { }, text = { },
} }
local num = 1 local num = 1
local lines = Util.split(imageText) local lines = Util.split(imageText)
while #lines[#lines] == 0 do while #lines[#lines] == 0 do
table.remove(lines, #lines) table.remove(lines, #lines)
end end
for _,sLine in ipairs(lines) do for _,sLine in ipairs(lines) do
table.insert(image.fg, { }) table.insert(image.fg, { })
table.insert(image.bg, { }) table.insert(image.bg, { })
table.insert(image.text, { }) table.insert(image.text, { })
--As we're no longer 1-1, we keep track of what index to write to --As we're no longer 1-1, we keep track of what index to write to
local writeIndex = 1 local writeIndex = 1
--Tells us if we've hit a 30 or 31 (BG and FG respectively)- next char specifies the curr colour --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 local bgNext, fgNext = false, false
--The current background and foreground colours --The current background and foreground colours
local currBG, currFG = nil,nil local currBG, currFG = nil,nil
for i = 1, #sLine do for i = 1, #sLine do
local nextChar = string.sub(sLine, i, i) local nextChar = string.sub(sLine, i, i)
if nextChar:byte() == 30 then if nextChar:byte() == 30 then
bgNext = true bgNext = true
elseif nextChar:byte() == 31 then elseif nextChar:byte() == 31 then
fgNext = true fgNext = true
elseif bgNext then elseif bgNext then
currBG = getColourOf(nextChar) currBG = getColourOf(nextChar)
bgNext = false bgNext = false
elseif fgNext then elseif fgNext then
currFG = getColourOf(nextChar) currFG = getColourOf(nextChar)
fgNext = false fgNext = false
else else
if nextChar ~= " " and currFG == nil then if nextChar ~= " " and currFG == nil then
currFG = _G.colors.white currFG = _G.colors.white
end end
image.bg[num][writeIndex] = currBG image.bg[num][writeIndex] = currBG
image.fg[num][writeIndex] = currFG image.fg[num][writeIndex] = currFG
image.text[num][writeIndex] = nextChar image.text[num][writeIndex] = nextChar
writeIndex = writeIndex + 1 writeIndex = writeIndex + 1
end end
end end
image.height = num image.height = num
if not image.width or writeIndex - 1 > image.width then if not image.width or writeIndex - 1 > image.width then
image.width = writeIndex - 1 image.width = writeIndex - 1
end end
num = num+1 num = num+1
end end
return image return image
end end
function NFT.load(path) function NFT.load(path)
local imageText = Util.readFile(path) local imageText = Util.readFile(path)
if not imageText then if not imageText then
error('Unable to read image file') error('Unable to read image file')
end end
return NFT.parse(imageText) return NFT.parse(imageText)
end end
return NFT return NFT

View File

@ -1,4 +1,4 @@
local Event = require('event') local Event = require('event')
local Socket = require('socket') local Socket = require('socket')
local Util = require('util') local Util = require('util')
@ -7,235 +7,235 @@ local os = _G.os
local Peripheral = Util.shallowCopy(_G.peripheral) local Peripheral = Util.shallowCopy(_G.peripheral)
function Peripheral.getList() function Peripheral.getList()
if _G.device then if _G.device then
return _G.device return _G.device
end end
local deviceList = { } local deviceList = { }
for _,side in pairs(Peripheral.getNames()) do for _,side in pairs(Peripheral.getNames()) do
Peripheral.addDevice(deviceList, side) Peripheral.addDevice(deviceList, side)
end end
return deviceList return deviceList
end end
function Peripheral.addDevice(deviceList, side) function Peripheral.addDevice(deviceList, side)
local name = side local name = side
local ptype = Peripheral.getType(side) local ptype = Peripheral.getType(side)
if not ptype then if not ptype then
return return
end end
if ptype == 'modem' then if ptype == 'modem' then
if Peripheral.call(name, 'isWireless') then if Peripheral.call(name, 'isWireless') then
ptype = 'wireless_modem' ptype = 'wireless_modem'
else else
ptype = 'wired_modem' ptype = 'wired_modem'
end end
end end
local sides = { local sides = {
front = true, front = true,
back = true, back = true,
top = true, top = true,
bottom = true, bottom = true,
left = true, left = true,
right = true right = true
} }
if sides[name] then if sides[name] then
local i = 1 local i = 1
local uniqueName = ptype local uniqueName = ptype
while deviceList[uniqueName] do while deviceList[uniqueName] do
uniqueName = ptype .. '_' .. i uniqueName = ptype .. '_' .. i
i = i + 1 i = i + 1
end end
name = uniqueName name = uniqueName
end end
local s, m = pcall(function() deviceList[name] = Peripheral.wrap(side) end) local s, m = pcall(function() deviceList[name] = Peripheral.wrap(side) end)
if not s and m then if not s and m then
_G.printError('wrap failed') _G.printError('wrap failed')
_G.printError(m) _G.printError(m)
end end
if deviceList[name] then if deviceList[name] then
Util.merge(deviceList[name], { Util.merge(deviceList[name], {
name = name, name = name,
type = ptype, type = ptype,
side = side, side = side,
}) })
return deviceList[name] return deviceList[name]
end end
end end
function Peripheral.getBySide(side) function Peripheral.getBySide(side)
return Util.find(Peripheral.getList(), 'side', side) return Util.find(Peripheral.getList(), 'side', side)
end end
function Peripheral.getByType(typeName) function Peripheral.getByType(typeName)
return Util.find(Peripheral.getList(), 'type', typeName) return Util.find(Peripheral.getList(), 'type', typeName)
end end
function Peripheral.getByMethod(method) function Peripheral.getByMethod(method)
for _,p in pairs(Peripheral.getList()) do for _,p in pairs(Peripheral.getList()) do
if p[method] then if p[method] then
return p return p
end end
end end
end end
-- match any of the passed arguments -- match any of the passed arguments
function Peripheral.get(args) function Peripheral.get(args)
if type(args) == 'string' then if type(args) == 'string' then
args = { type = args } args = { type = args }
end end
if args.name then if args.name then
return _G.device[args.name] return _G.device[args.name]
end end
if args.type then if args.type then
local p = Peripheral.getByType(args.type) local p = Peripheral.getByType(args.type)
if p then if p then
return p return p
end end
end end
if args.method then if args.method then
local p = Peripheral.getByMethod(args.method) local p = Peripheral.getByMethod(args.method)
if p then if p then
return p return p
end end
end end
if args.side then if args.side then
local p = Peripheral.getBySide(args.side) local p = Peripheral.getBySide(args.side)
if p then if p then
return p return p
end end
end end
end end
local function getProxy(pi) local function getProxy(pi)
local socket, msg = Socket.connect(pi.host, 189) local socket, msg = Socket.connect(pi.host, 189)
if not socket then if not socket then
error("Timed out attaching peripheral: " .. pi.uri .. '\n' .. msg) error("Timed out attaching peripheral: " .. pi.uri .. '\n' .. msg)
end end
-- write the uri of the periperal we are requesting... -- write the uri of the periperal we are requesting...
-- ie. type/monitor -- ie. type/monitor
socket:write(pi.path) socket:write(pi.path)
local proxy = socket:read(3) local proxy = socket:read(3)
if not proxy then if not proxy then
error("Timed out attaching peripheral: " .. pi.uri) error("Timed out attaching peripheral: " .. pi.uri)
end end
if type(proxy) == 'string' then if type(proxy) == 'string' then
error(proxy) error(proxy)
end end
local methods = proxy.methods local methods = proxy.methods
proxy.methods = nil proxy.methods = nil
for _,method in pairs(methods) do for _,method in pairs(methods) do
proxy[method] = function(...) proxy[method] = function(...)
socket:write({ fn = method, args = { ... } }) socket:write({ fn = method, args = { ... } })
local resp = socket:read() local resp = socket:read()
if not resp then if not resp then
error("Timed out communicating with peripheral: " .. pi.uri) error("Timed out communicating with peripheral: " .. pi.uri)
end end
return table.unpack(resp) return table.unpack(resp)
end end
end end
if proxy.blit then if proxy.blit then
local methods = { 'clear', 'clearLine', 'setCursorPos', 'write', 'blit', local methods = { 'clear', 'clearLine', 'setCursorPos', 'write', 'blit',
'setTextColor', 'setTextColour', 'setBackgroundColor', 'setTextColor', 'setTextColour', 'setBackgroundColor',
'setBackgroundColour', 'scroll', 'setCursorBlink', } 'setBackgroundColour', 'scroll', 'setCursorBlink', }
local queue = nil local queue = nil
for _,method in pairs(methods) do for _,method in pairs(methods) do
proxy[method] = function(...) proxy[method] = function(...)
if not queue then if not queue then
queue = { } queue = { }
Event.onTimeout(0, function() Event.onTimeout(0, function()
if not socket:write({ fn = 'fastBlit', args = { queue } }) then if not socket:write({ fn = 'fastBlit', args = { queue } }) then
error("Timed out communicating with peripheral: " .. pi.uri) error("Timed out communicating with peripheral: " .. pi.uri)
end end
queue = nil queue = nil
socket:read() socket:read()
end) end)
end end
if not socket.connected then if not socket.connected then
error("Timed out communicating with peripheral: " .. pi.uri) error("Timed out communicating with peripheral: " .. pi.uri)
end end
table.insert(queue, { table.insert(queue, {
fn = method, fn = method,
args = { ... }, args = { ... },
}) })
end end
end end
end end
if proxy.type == 'monitor' then if proxy.type == 'monitor' then
Event.addRoutine(function() Event.addRoutine(function()
while true do while true do
local data = socket:read() local data = socket:read()
if not data then if not data then
break break
end end
if data.fn and data.fn == 'event' then if data.fn and data.fn == 'event' then
os.queueEvent(table.unpack(data.data)) os.queueEvent(table.unpack(data.data))
end end
end end
end) end)
end end
return proxy return proxy
end end
--[[ --[[
Parse a uri into it's components Parse a uri into it's components
Examples: Examples:
monitor = { name = 'monitor' } monitor = { name = 'monitor' }
side/top = { side = 'top' } side/top = { side = 'top' }
method/list = { method = 'list' } method/list = { method = 'list' }
12://name/monitor = { host = 12, name = 'monitor' } 12://name/monitor = { host = 12, name = 'monitor' }
]]-- ]]--
local function parse(uri) local function parse(uri)
local pi = Util.split(uri:gsub('^%d*://', ''), '(.-)/') local pi = Util.split(uri:gsub('^%d*://', ''), '(.-)/')
if #pi == 1 then if #pi == 1 then
pi = { pi = {
'name', 'name',
pi[1], pi[1],
} }
end end
return { return {
host = uri:match('^(%d*)%:'), -- 12 host = uri:match('^(%d*)%:'), -- 12
uri = uri, -- 12://name/monitor uri = uri, -- 12://name/monitor
path = uri:gsub('^%d*://', ''), -- name/monitor path = uri:gsub('^%d*://', ''), -- name/monitor
[ pi[1] ] = pi[2], -- name = 'monitor' [ pi[1] ] = pi[2], -- name = 'monitor'
} }
end end
function Peripheral.lookup(uri) function Peripheral.lookup(uri)
local pi = parse(uri) local pi = parse(uri)
if pi.host and _G.device.wireless_modem then if pi.host and _G.device.wireless_modem then
return getProxy(pi) return getProxy(pi)
end end
return Peripheral.get(pi) return Peripheral.get(pi)
end end
return Peripheral return Peripheral

View File

@ -3,38 +3,38 @@ local Util = require('util')
local Point = { } local Point = { }
Point.directions = { Point.directions = {
[ 0 ] = { xd = 1, zd = 0, yd = 0, heading = 0, direction = 'east' }, [ 0 ] = { xd = 1, zd = 0, yd = 0, heading = 0, direction = 'east' },
[ 1 ] = { xd = 0, zd = 1, yd = 0, heading = 1, direction = 'south' }, [ 1 ] = { xd = 0, zd = 1, yd = 0, heading = 1, direction = 'south' },
[ 2 ] = { xd = -1, zd = 0, yd = 0, heading = 2, direction = 'west' }, [ 2 ] = { xd = -1, zd = 0, yd = 0, heading = 2, direction = 'west' },
[ 3 ] = { xd = 0, zd = -1, yd = 0, heading = 3, direction = 'north' }, [ 3 ] = { xd = 0, zd = -1, yd = 0, heading = 3, direction = 'north' },
[ 4 ] = { xd = 0, zd = 0, yd = 1, heading = 4, direction = 'up' }, [ 4 ] = { xd = 0, zd = 0, yd = 1, heading = 4, direction = 'up' },
[ 5 ] = { xd = 0, zd = 0, yd = -1, heading = 5, direction = 'down' }, [ 5 ] = { xd = 0, zd = 0, yd = -1, heading = 5, direction = 'down' },
} }
Point.facings = { Point.facings = {
[ 0 ] = Point.directions[0], [ 0 ] = Point.directions[0],
[ 1 ] = Point.directions[1], [ 1 ] = Point.directions[1],
[ 2 ] = Point.directions[2], [ 2 ] = Point.directions[2],
[ 3 ] = Point.directions[3], [ 3 ] = Point.directions[3],
east = Point.directions[0], east = Point.directions[0],
south = Point.directions[1], south = Point.directions[1],
west = Point.directions[2], west = Point.directions[2],
north = Point.directions[3], north = Point.directions[3],
} }
Point.headings = { Point.headings = {
[ 0 ] = Point.directions[0], [ 0 ] = Point.directions[0],
[ 1 ] = Point.directions[1], [ 1 ] = Point.directions[1],
[ 2 ] = Point.directions[2], [ 2 ] = Point.directions[2],
[ 3 ] = Point.directions[3], [ 3 ] = Point.directions[3],
[ 4 ] = Point.directions[4], [ 4 ] = Point.directions[4],
[ 5 ] = Point.directions[5], [ 5 ] = Point.directions[5],
east = Point.directions[0], east = Point.directions[0],
south = Point.directions[1], south = Point.directions[1],
west = Point.directions[2], west = Point.directions[2],
north = Point.directions[3], north = Point.directions[3],
up = Point.directions[4], up = Point.directions[4],
down = Point.directions[5], down = Point.directions[5],
} }
Point.EAST = 0 Point.EAST = 0
@ -45,262 +45,262 @@ Point.UP = 4
Point.DOWN = 5 Point.DOWN = 5
function Point.copy(pt) 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 end
function Point.same(pta, ptb) function Point.same(pta, ptb)
return pta.x == ptb.x and return pta.x == ptb.x and
pta.y == ptb.y and pta.y == ptb.y and
pta.z == ptb.z pta.z == ptb.z
end end
function Point.above(pt) 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 end
function Point.below(pt) 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 end
function Point.subtract(a, b) function Point.subtract(a, b)
a.x = a.x - b.x a.x = a.x - b.x
a.y = a.y - b.y a.y = a.y - b.y
a.z = a.z - b.z a.z = a.z - b.z
end end
-- Euclidian distance -- Euclidian distance
function Point.distance(a, b) function Point.distance(a, b)
return math.sqrt( return math.sqrt(
math.pow(a.x - b.x, 2) + math.pow(a.x - b.x, 2) +
math.pow(a.y - b.y, 2) + math.pow(a.y - b.y, 2) +
math.pow(a.z - b.z, 2)) math.pow(a.z - b.z, 2))
end end
-- turtle distance (manhattan) -- turtle distance (manhattan)
function Point.turtleDistance(a, b) function Point.turtleDistance(a, b)
if a.y and b.y then if a.y and b.y then
return math.abs(a.x - b.x) + return math.abs(a.x - b.x) +
math.abs(a.y - b.y) + math.abs(a.y - b.y) +
math.abs(a.z - b.z) math.abs(a.z - b.z)
else else
return math.abs(a.x - b.x) + return math.abs(a.x - b.x) +
math.abs(a.z - b.z) math.abs(a.z - b.z)
end end
end end
function Point.calculateTurns(ih, oh) function Point.calculateTurns(ih, oh)
if ih == oh then if ih == oh then
return 0 return 0
end end
if (ih % 2) == (oh % 2) then if (ih % 2) == (oh % 2) then
return 2 return 2
end end
return 1 return 1
end end
function Point.calculateHeading(pta, ptb) function Point.calculateHeading(pta, ptb)
local heading local heading
local xd, zd = pta.x - ptb.x, pta.z - ptb.z local xd, zd = pta.x - ptb.x, pta.z - ptb.z
if (pta.heading % 2) == 0 and zd ~= 0 then if (pta.heading % 2) == 0 and zd ~= 0 then
heading = zd < 0 and 1 or 3 heading = zd < 0 and 1 or 3
elseif (pta.heading % 2) == 1 and xd ~= 0 then elseif (pta.heading % 2) == 1 and xd ~= 0 then
heading = xd < 0 and 0 or 2 heading = xd < 0 and 0 or 2
elseif pta.heading == 0 and xd > 0 then elseif pta.heading == 0 and xd > 0 then
heading = 2 heading = 2
elseif pta.heading == 2 and xd < 0 then elseif pta.heading == 2 and xd < 0 then
heading = 0 heading = 0
elseif pta.heading == 1 and zd > 0 then elseif pta.heading == 1 and zd > 0 then
heading = 3 heading = 3
elseif pta.heading == 3 and zd < 0 then elseif pta.heading == 3 and zd < 0 then
heading = 1 heading = 1
end end
return heading or pta.heading return heading or pta.heading
end end
-- Calculate distance to location including turns -- Calculate distance to location including turns
-- also returns the resulting heading -- also returns the resulting heading
function Point.calculateMoves(pta, ptb, distance) function Point.calculateMoves(pta, ptb, distance)
local heading = pta.heading local heading = pta.heading
local moves = distance or Point.turtleDistance(pta, ptb) local moves = distance or Point.turtleDistance(pta, ptb)
if (pta.heading % 2) == 0 and pta.z ~= ptb.z then if (pta.heading % 2) == 0 and pta.z ~= ptb.z then
moves = moves + 1 moves = moves + 1
if ptb.heading and (ptb.heading % 2 == 1) then if ptb.heading and (ptb.heading % 2 == 1) then
heading = ptb.heading heading = ptb.heading
elseif ptb.z > pta.z then elseif ptb.z > pta.z then
heading = 1 heading = 1
else else
heading = 3 heading = 3
end end
elseif (pta.heading % 2) == 1 and pta.x ~= ptb.x then elseif (pta.heading % 2) == 1 and pta.x ~= ptb.x then
moves = moves + 1 moves = moves + 1
if ptb.heading and (ptb.heading % 2 == 0) then if ptb.heading and (ptb.heading % 2 == 0) then
heading = ptb.heading heading = ptb.heading
elseif ptb.x > pta.x then elseif ptb.x > pta.x then
heading = 0 heading = 0
else else
heading = 2 heading = 2
end end
end end
if ptb.heading then if ptb.heading then
if heading ~= ptb.heading then if heading ~= ptb.heading then
moves = moves + Point.calculateTurns(heading, ptb.heading) moves = moves + Point.calculateTurns(heading, ptb.heading)
heading = ptb.heading heading = ptb.heading
end end
end end
return moves, heading return moves, heading
end end
-- given a set of points, find the one taking the least moves -- given a set of points, find the one taking the least moves
function Point.closest(reference, pts) function Point.closest(reference, pts)
if #pts == 1 then if #pts == 1 then
return pts[1] return pts[1]
end end
local lm, lpt = math.huge local lm, lpt = math.huge
for _,pt in pairs(pts) do for _,pt in pairs(pts) do
local distance = Point.turtleDistance(reference, pt) local distance = Point.turtleDistance(reference, pt)
if distance < lm then if distance < lm then
local m = Point.calculateMoves(reference, pt, distance) local m = Point.calculateMoves(reference, pt, distance)
if m < lm then if m < lm then
lpt = pt lpt = pt
lm = m lm = m
end end
end end
end end
return lpt return lpt
end end
function Point.eachClosest(spt, ipts, fn) function Point.eachClosest(spt, ipts, fn)
local pts = Util.shallowCopy(ipts) local pts = Util.shallowCopy(ipts)
while #pts > 0 do while #pts > 0 do
local pt = Point.closest(spt, pts) local pt = Point.closest(spt, pts)
local r = fn(pt) local r = fn(pt)
if r then if r then
return r return r
end end
Util.removeByValue(pts, pt) Util.removeByValue(pts, pt)
end end
end end
function Point.adjacentPoints(pt) function Point.adjacentPoints(pt)
local pts = { } local pts = { }
for i = 0, 5 do for i = 0, 5 do
local hi = Point.headings[i] local hi = Point.headings[i]
table.insert(pts, { x = pt.x + hi.xd, y = pt.y + hi.yd, z = pt.z + hi.zd }) table.insert(pts, { x = pt.x + hi.xd, y = pt.y + hi.yd, z = pt.z + hi.zd })
end end
return pts return pts
end end
-- get the point nearest A that is in the direction of B -- get the point nearest A that is in the direction of B
function Point.nearestTo(pta, ptb) function Point.nearestTo(pta, ptb)
local heading local heading
if pta.x < ptb.x then if pta.x < ptb.x then
heading = 0 heading = 0
elseif pta.z < ptb.z then elseif pta.z < ptb.z then
heading = 1 heading = 1
elseif pta.x > ptb.x then elseif pta.x > ptb.x then
heading = 2 heading = 2
elseif pta.z > ptb.z then elseif pta.z > ptb.z then
heading = 3 heading = 3
elseif pta.y < ptb.y then elseif pta.y < ptb.y then
heading = 4 heading = 4
elseif pta.y > ptb.y then elseif pta.y > ptb.y then
heading = 5 heading = 5
end end
if heading then if heading then
return { return {
x = pta.x + Point.headings[heading].xd, x = pta.x + Point.headings[heading].xd,
y = pta.y + Point.headings[heading].yd, y = pta.y + Point.headings[heading].yd,
z = pta.z + Point.headings[heading].zd, z = pta.z + Point.headings[heading].zd,
} }
end end
return pta -- error ? return pta -- error ?
end end
function Point.rotate(pt, facing) function Point.rotate(pt, facing)
local x, z = pt.x, pt.z local x, z = pt.x, pt.z
if facing == 1 then if facing == 1 then
pt.x = z pt.x = z
pt.z = -x pt.z = -x
elseif facing == 2 then elseif facing == 2 then
pt.x = -x pt.x = -x
pt.z = -z pt.z = -z
elseif facing == 3 then elseif facing == 3 then
pt.x = -z pt.x = -z
pt.z = x pt.z = x
end end
end end
function Point.makeBox(pt1, pt2) function Point.makeBox(pt1, pt2)
return { return {
x = pt1.x, x = pt1.x,
y = pt1.y, y = pt1.y,
z = pt1.z, z = pt1.z,
ex = pt2.x, ex = pt2.x,
ey = pt2.y, ey = pt2.y,
ez = pt2.z, ez = pt2.z,
} }
end end
-- expand box to include point -- expand box to include point
function Point.expandBox(box, pt) function Point.expandBox(box, pt)
if pt.x < box.x then if pt.x < box.x then
box.x = pt.x box.x = pt.x
elseif pt.x > box.ex then elseif pt.x > box.ex then
box.ex = pt.x box.ex = pt.x
end end
if pt.y < box.y then if pt.y < box.y then
box.y = pt.y box.y = pt.y
elseif pt.y > box.ey then elseif pt.y > box.ey then
box.ey = pt.y box.ey = pt.y
end end
if pt.z < box.z then if pt.z < box.z then
box.z = pt.z box.z = pt.z
elseif pt.z > box.ez then elseif pt.z > box.ez then
box.ez = pt.z box.ez = pt.z
end end
end end
function Point.normalizeBox(box) function Point.normalizeBox(box)
return { return {
x = math.min(box.x, box.ex), x = math.min(box.x, box.ex),
y = math.min(box.y, box.ey), y = math.min(box.y, box.ey),
z = math.min(box.z, box.ez), z = math.min(box.z, box.ez),
ex = math.max(box.x, box.ex), ex = math.max(box.x, box.ex),
ey = math.max(box.y, box.ey), ey = math.max(box.y, box.ey),
ez = math.max(box.z, box.ez), ez = math.max(box.z, box.ez),
} }
end end
function Point.inBox(pt, box) function Point.inBox(pt, box)
return pt.x >= box.x and return pt.x >= box.x and
pt.y >= box.y and pt.y >= box.y and
pt.z >= box.z and pt.z >= box.z and
pt.x <= box.ex and pt.x <= box.ex and
pt.y <= box.ey and pt.y <= box.ey and
pt.z <= box.ez pt.z <= box.ez
end end
function Point.closestPointInBox(pt, box) function Point.closestPointInBox(pt, box)
local cpt = { local cpt = {
x = math.abs(pt.x - box.x) < math.abs(pt.x - box.ex) and box.x or box.ex, 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, 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, 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.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.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 cpt.z = pt.z > box.z and pt.z < box.ez and pt.z or cpt.z
return cpt return cpt
end end
return Point return Point

View File

@ -5,56 +5,56 @@ local config = { }
local Security = { } local Security = { }
function Security.verifyPassword(password) function Security.verifyPassword(password)
Config.load('os', config) Config.load('os', config)
return config.password and password == config.password return config.password and password == config.password
end end
function Security.hasPassword() function Security.hasPassword()
return not not config.password return not not config.password
end end
function Security.getSecretKey() function Security.getSecretKey()
Config.load('os', config) Config.load('os', config)
if not config.secretKey then if not config.secretKey then
config.secretKey = math.random(100000, 999999) config.secretKey = math.random(100000, 999999)
Config.update('os', config) Config.update('os', config)
end end
return config.secretKey return config.secretKey
end end
function Security.getPublicKey() function Security.getPublicKey()
local exchange = { local exchange = {
base = 11, base = 11,
primeMod = 625210769 primeMod = 625210769
} }
local function modexp(base, exponent, modulo) local function modexp(base, exponent, modulo)
local remainder = base local remainder = base
for _ = 1, exponent-1 do for _ = 1, exponent-1 do
remainder = remainder * remainder remainder = remainder * remainder
if remainder >= modulo then if remainder >= modulo then
remainder = remainder % modulo remainder = remainder % modulo
end end
end end
return remainder return remainder
end end
local secretKey = Security.getSecretKey() local secretKey = Security.getSecretKey()
return modexp(exchange.base, secretKey, exchange.primeMod) return modexp(exchange.base, secretKey, exchange.primeMod)
end end
function Security.updatePassword(password) function Security.updatePassword(password)
Config.load('os', config) Config.load('os', config)
config.password = password config.password = password
Config.update('os', config) Config.update('os', config)
end end
function Security.getPassword() function Security.getPassword()
Config.load('os', config) Config.load('os', config)
return config.password return config.password
end end
return Security return Security

View File

@ -1,35 +1,18 @@
local sha1 = { local sha1 = {
_VERSION = "sha.lua 0.5.0", _VERSION = "sha.lua 0.5.0",
_URL = "https://github.com/kikito/sha.lua", _URL = "https://github.com/kikito/sha.lua",
_DESCRIPTION = [[ _DESCRIPTION = [[
SHA-1 secure hash computation, and HMAC-SHA1 signature computation in Lua (5.1) 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) 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) And modified by Eike Decker - (http://cube3d.de/uploads/Main/sha1.txt)
]], ]],
_LICENSE = [[ _LICENSE = [[
MIT 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 https://opensource.org/licenses/MIT
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.
]]
} }
----------------------------------------------------------------------------------- -----------------------------------------------------------------------------------
@ -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 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 -- split a 32 bit word into four 8 bit numbers
local function w32_to_bytes(i) 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 end
-- shift the bits of a 32 bit word. Don't use negative values for "bits" -- shift the bits of a 32 bit word. Don't use negative values for "bits"
local function w32_rot(bits,a) local function w32_rot(bits,a)
local b2 = 2^(32-bits) local b2 = 2^(32-bits)
local a,b = modf(a/b2) local a,b = modf(a/b2)
return a+b*b2*(2^(bits)) return a+b*b2*(2^(bits))
end end
-- caching function for functions that accept 2 arguments, both of values between -- 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 -- 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) -- during loading and a function is returned that returns the cached values (only)
local function cache2arg(fn) local function cache2arg(fn)
if not PRELOAD_CACHE then return fn end if not PRELOAD_CACHE then return fn end
local lut = {} local lut = {}
for i=0,0xffff do for i=0,0xffff do
local a,b = floor(i/0x100),i%0x100 local a,b = floor(i/0x100),i%0x100
lut[i] = fn(a,b) lut[i] = fn(a,b)
end end
return function(a,b) return function(a,b)
return lut[a*0x100+b] return lut[a*0x100+b]
end end
end end
-- splits an 8-bit number into 8 bits, returning all 8 bits as booleans -- splits an 8-bit number into 8 bits, returning all 8 bits as booleans
local function byte_to_bits(b) local function byte_to_bits(b)
local b = function(n) local b = function(n)
local b = floor(b/n) local b = floor(b/n)
return b%2==1 return b%2==1
end end
return b(1),b(2),b(4),b(8),b(16),b(32),b(64),b(128) return b(1),b(2),b(4),b(8),b(16),b(32),b(64),b(128)
end end
-- builds an 8bit number from 8 booleans -- builds an 8bit number from 8 booleans
local function bits_to_byte(a,b,c,d,e,f,g,h) 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 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) 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 end
-- bitwise "and" function for 2 8bit number -- bitwise "and" function for 2 8bit number
local band = cache2arg (function(a,b) 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(b)
local a,b,c,d,e,f,g,h = byte_to_bits(a) local a,b,c,d,e,f,g,h = byte_to_bits(a)
return bits_to_byte( return bits_to_byte(
A and a, B and b, C and c, D and d, A and a, B and b, C and c, D and d,
E and e, F and f, G and g, H and h) E and e, F and f, G and g, H and h)
end) end)
-- bitwise "or" function for 2 8bit numbers -- bitwise "or" function for 2 8bit numbers
local bor = cache2arg(function(a,b) 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(b)
local a,b,c,d,e,f,g,h = byte_to_bits(a) local a,b,c,d,e,f,g,h = byte_to_bits(a)
return bits_to_byte( return bits_to_byte(
A or a, B or b, C or c, D or d, A or a, B or b, C or c, D or d,
E or e, F or f, G or g, H or h) E or e, F or f, G or g, H or h)
end) end)
-- bitwise "xor" function for 2 8bit numbers -- bitwise "xor" function for 2 8bit numbers
local bxor = cache2arg(function(a,b) 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(b)
local a,b,c,d,e,f,g,h = byte_to_bits(a) local a,b,c,d,e,f,g,h = byte_to_bits(a)
return bits_to_byte( return bits_to_byte(
A ~= a, B ~= b, C ~= c, D ~= d, A ~= a, B ~= b, C ~= c, D ~= d,
E ~= e, F ~= f, G ~= g, H ~= h) E ~= e, F ~= f, G ~= g, H ~= h)
end) end)
-- bitwise complement for one 8bit number -- bitwise complement for one 8bit number
local function bnot(x) local function bnot(x)
return 255-(x % 256) return 255-(x % 256)
end end
-- creates a function to combine to 32bit numbers using an 8bit combination function -- creates a function to combine to 32bit numbers using an 8bit combination function
local function w32_comb(fn) local function w32_comb(fn)
return function(a,b) return function(a,b)
local aa,ab,ac,ad = w32_to_bytes(a) local aa,ab,ac,ad = w32_to_bytes(a)
local ba,bb,bc,bd = w32_to_bytes(b) 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)) return bytes_to_w32(fn(aa,ba),fn(ab,bb),fn(ac,bc),fn(ad,bd))
end end
end end
-- create functions for and, xor and or, all for 2 32bit numbers -- 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 -- xor function that may receive a variable number of arguments
local function w32_xor_n(a,...) local function w32_xor_n(a,...)
local aa,ab,ac,ad = w32_to_bytes(a) local aa,ab,ac,ad = w32_to_bytes(a)
for i=1,select('#',...) do for i=1,select('#',...) do
local ba,bb,bc,bd = w32_to_bytes(select(i,...)) 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) aa,ab,ac,ad = bxor(aa,ba),bxor(ab,bb),bxor(ac,bc),bxor(ad,bd)
end end
return bytes_to_w32(aa,ab,ac,ad) return bytes_to_w32(aa,ab,ac,ad)
end end
-- combining 3 32bit numbers through binary "or" operation -- combining 3 32bit numbers through binary "or" operation
local function w32_or3(a,b,c) local function w32_or3(a,b,c)
local aa,ab,ac,ad = w32_to_bytes(a) local aa,ab,ac,ad = w32_to_bytes(a)
local ba,bb,bc,bd = w32_to_bytes(b) local ba,bb,bc,bd = w32_to_bytes(b)
local ca,cb,cc,cd = w32_to_bytes(c) local ca,cb,cc,cd = w32_to_bytes(c)
return bytes_to_w32( return bytes_to_w32(
bor(aa,bor(ba,ca)), bor(ab,bor(bb,cb)), bor(ac,bor(bc,cc)), bor(ad,bor(bd,cd)) bor(aa,bor(ba,ca)), bor(ab,bor(bb,cb)), bor(ac,bor(bc,cc)), bor(ad,bor(bd,cd))
) )
end end
-- binary complement for 32bit numbers -- binary complement for 32bit numbers
local function w32_not(a) local function w32_not(a)
return 4294967295-(a % 4294967296) return 4294967295-(a % 4294967296)
end end
-- adding 2 32bit numbers, cutting off the remainder on 33th bit -- 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) -- adding n 32bit numbers, cutting off the remainder (again)
local function w32_add_n(a,...) local function w32_add_n(a,...)
for i=1,select('#',...) do for i=1,select('#',...) do
a = (a+select(i,...)) % 4294967296 a = (a+select(i,...)) % 4294967296
end end
return a return a
end end
-- converting the number to a hexadecimal string -- converting the number to a hexadecimal string
local function w32_to_hexstring(w) return format("%08x",w) end local function w32_to_hexstring(w) return format("%08x",w) end
local function hex_to_binary(hex) local function hex_to_binary(hex)
return hex:gsub('..', function(hexval) return hex:gsub('..', function(hexval)
return string.char(tonumber(hexval, 16)) return string.char(tonumber(hexval, 16))
end) end)
end end
-- building the lookuptables ahead of time (instead of littering the source code -- building the lookuptables ahead of time (instead of littering the source code
@ -182,114 +165,114 @@ end
local xor_with_0x5c = {} local xor_with_0x5c = {}
local xor_with_0x36 = {} local xor_with_0x36 = {}
for i=0,0xff do for i=0,0xff do
xor_with_0x5c[char(i)] = char(bxor(i,0x5c)) xor_with_0x5c[char(i)] = char(bxor(i,0x5c))
xor_with_0x36[char(i)] = char(bxor(i,0x36)) xor_with_0x36[char(i)] = char(bxor(i,0x36))
end end
----------------------------------------------------------------------------- -----------------------------------------------------------------------------
-- calculating the SHA1 for some text -- calculating the SHA1 for some text
function sha1.sha1(msg) function sha1.sha1(msg)
local H0,H1,H2,H3,H4 = 0x67452301,0xEFCDAB89,0x98BADCFE,0x10325476,0xC3D2E1F0 local H0,H1,H2,H3,H4 = 0x67452301,0xEFCDAB89,0x98BADCFE,0x10325476,0xC3D2E1F0
local msg_len_in_bits = #msg * 8 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 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 current_mod = non_zero_message_bytes % 64
local second_append = current_mod>0 and rep(char(0), 64 - current_mod) or "" local second_append = current_mod>0 and rep(char(0), 64 - current_mod) or ""
-- now to append the length as a 64-bit number. -- now to append the length as a 64-bit number.
local B1, R1 = modf(msg_len_in_bits / 0x01000000) local B1, R1 = modf(msg_len_in_bits / 0x01000000)
local B2, R2 = modf( 0x01000000 * R1 / 0x00010000) local B2, R2 = modf( 0x01000000 * R1 / 0x00010000)
local B3, R3 = modf( 0x00010000 * R2 / 0x00000100) local B3, R3 = modf( 0x00010000 * R2 / 0x00000100)
local B4 = 0x00000100 * R3 local B4 = 0x00000100 * R3
local L64 = char( 0) .. char( 0) .. char( 0) .. char( 0) -- high 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 .. 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 W = { }
local start, A, B, C, D, E, f, K, TEMP local start, A, B, C, D, E, f, K, TEMP
local chunk = 0 local chunk = 0
while chunk < chunks do while chunk < chunks do
-- --
-- break chunk up into W[0] through W[15] -- break chunk up into W[0] through W[15]
-- --
start,chunk = chunk * 64 + 1,chunk + 1 start,chunk = chunk * 64 + 1,chunk + 1
for t = 0, 15 do for t = 0, 15 do
W[t] = bytes_to_w32(msg:byte(start, start + 3)) W[t] = bytes_to_w32(msg:byte(start, start + 3))
start = start + 4 start = start + 4
end end
-- --
-- build W[16] through W[79] -- build W[16] through W[79]
-- --
for t = 16, 79 do for t = 16, 79 do
-- For t = 16 to 79 let Wt = S1(Wt-3 XOR Wt-8 XOR Wt-14 XOR Wt-16). -- 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])) W[t] = w32_rot(1, w32_xor_n(W[t-3], W[t-8], W[t-14], W[t-16]))
end 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 for t = 0, 79 do
if t <= 19 then if t <= 19 then
-- (B AND C) OR ((NOT B) AND D) -- (B AND C) OR ((NOT B) AND D)
f = w32_or(w32_and(B, C), w32_and(w32_not(B), D)) f = w32_or(w32_and(B, C), w32_and(w32_not(B), D))
K = 0x5A827999 K = 0x5A827999
elseif t <= 39 then elseif t <= 39 then
-- B XOR C XOR D -- B XOR C XOR D
f = w32_xor_n(B, C, D) f = w32_xor_n(B, C, D)
K = 0x6ED9EBA1 K = 0x6ED9EBA1
elseif t <= 59 then elseif t <= 59 then
-- (B AND C) OR (B AND D) OR (C AND D -- (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)) f = w32_or3(w32_and(B, C), w32_and(B, D), w32_and(C, D))
K = 0x8F1BBCDC K = 0x8F1BBCDC
else else
-- B XOR C XOR D -- B XOR C XOR D
f = w32_xor_n(B, C, D) f = w32_xor_n(B, C, D)
K = 0xCA62C1D6 K = 0xCA62C1D6
end end
-- TEMP = S5(A) + ft(B,C,D) + E + Wt + Kt; -- 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,B,C,D,E = w32_add_n(w32_rot(5, A), f, E, W[t], K),
A, w32_rot(30, B), C, D A, w32_rot(30, B), C, D
end end
-- Let H0 = H0 + A, H1 = H1 + B, H2 = H2 + C, H3 = H3 + D, H4 = H4 + E. -- 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) 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 end
local f = w32_to_hexstring local f = w32_to_hexstring
return f(H0) .. f(H1) .. f(H2) .. f(H3) .. f(H4) return f(H0) .. f(H1) .. f(H2) .. f(H3) .. f(H4)
end end
function sha1.binary(msg) function sha1.binary(msg)
return hex_to_binary(sha1.sha1(msg)) return hex_to_binary(sha1.sha1(msg))
end end
function sha1.hmac(key, text) function sha1.hmac(key, text)
assert(type(key) == 'string', "key 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") assert(type(text) == 'string', "text passed to sha1.hmac should be a string")
if #key > BLOCK_SIZE then if #key > BLOCK_SIZE then
key = sha1.binary(key) key = sha1.binary(key)
end end
local key_xord_with_0x36 = key:gsub('.', xor_with_0x36) .. string.rep(string.char(0x36), 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) 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 end
function sha1.hmac_binary(key, text) function sha1.hmac_binary(key, text)
return hex_to_binary(sha1.hmac(key, text)) return hex_to_binary(sha1.hmac(key, text))
end end
setmetatable(sha1, {__call = function(_,msg) return sha1.sha1(msg) end }) setmetatable(sha1, {__call = function(_,msg) return sha1.sha1(msg) end })

View File

@ -9,211 +9,211 @@ local os = _G.os
local socketClass = { } local socketClass = { }
function socketClass:read(timeout) function socketClass:read(timeout)
local data, distance = _G.transport.read(self) local data, distance = _G.transport.read(self)
if data then if data then
return data, distance return data, distance
end end
if not self.connected then if not self.connected then
Logger.log('socket', 'read: No connection') Logger.log('socket', 'read: No connection')
return return
end end
local timerId = os.startTimer(timeout or 5) local timerId = os.startTimer(timeout or 5)
while true do while true do
local e, id = os.pullEvent() local e, id = os.pullEvent()
if e == 'transport_' .. self.uid then if e == 'transport_' .. self.uid then
data, distance = _G.transport.read(self) data, distance = _G.transport.read(self)
if data then if data then
os.cancelTimer(timerId) os.cancelTimer(timerId)
return data, distance return data, distance
end end
if not self.connected then if not self.connected then
break break
end end
elseif e == 'timer' and id == timerId then elseif e == 'timer' and id == timerId then
if timeout or not self.connected then if timeout or not self.connected then
break break
end end
timerId = os.startTimer(5) timerId = os.startTimer(5)
self:ping() self:ping()
end end
end end
end end
function socketClass:write(data) function socketClass:write(data)
if self.connected then if self.connected then
_G.transport.write(self, { _G.transport.write(self, {
type = 'DATA', type = 'DATA',
seq = self.wseq, seq = self.wseq,
data = data, data = data,
}) })
return true return true
end end
end end
function socketClass:ping() function socketClass:ping()
if self.connected then if self.connected then
_G.transport.ping(self) _G.transport.ping(self)
return true return true
end end
end end
function socketClass:close() function socketClass:close()
if self.connected then if self.connected then
Logger.log('socket', 'closing socket ' .. self.sport) Logger.log('socket', 'closing socket ' .. self.sport)
self.transmit(self.dport, self.dhost, { self.transmit(self.dport, self.dhost, {
type = 'DISC', type = 'DISC',
}) })
self.connected = false self.connected = false
end end
device.wireless_modem.close(self.sport) device.wireless_modem.close(self.sport)
_G.transport.close(self) _G.transport.close(self)
end end
local Socket = { } local Socket = { }
local function loopback(port, sport, msg) 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 end
local function newSocket(isLoopback) local function newSocket(isLoopback)
for i = 16384, 32767 do for i = 16384, 32767 do
if not device.wireless_modem.isOpen(i) then if not device.wireless_modem.isOpen(i) then
local socket = { local socket = {
shost = os.getComputerID(), shost = os.getComputerID(),
sport = i, sport = i,
transmit = device.wireless_modem.transmit, transmit = device.wireless_modem.transmit,
wseq = math.random(100, 100000), wseq = math.random(100, 100000),
rseq = math.random(100, 100000), rseq = math.random(100, 100000),
timers = { }, timers = { },
messages = { }, messages = { },
} }
setmetatable(socket, { __index = socketClass }) setmetatable(socket, { __index = socketClass })
device.wireless_modem.open(socket.sport) device.wireless_modem.open(socket.sport)
if isLoopback then if isLoopback then
socket.transmit = loopback socket.transmit = loopback
end end
return socket return socket
end end
end end
error('No ports available') error('No ports available')
end end
function Socket.connect(host, port) function Socket.connect(host, port)
if not device.wireless_modem then if not device.wireless_modem then
return false, 'Wireless modem not found' return false, 'Wireless modem not found'
end end
local socket = newSocket(host == os.getComputerID()) local socket = newSocket(host == os.getComputerID())
socket.dhost = tonumber(host) socket.dhost = tonumber(host)
Logger.log('socket', 'connecting to ' .. port) Logger.log('socket', 'connecting to ' .. port)
socket.transmit(port, socket.sport, { socket.transmit(port, socket.sport, {
type = 'OPEN', type = 'OPEN',
shost = socket.shost, shost = socket.shost,
dhost = socket.dhost, dhost = socket.dhost,
t = Crypto.encrypt({ ts = os.time(), seq = socket.seq }, Security.getPublicKey()), t = Crypto.encrypt({ ts = os.time(), seq = socket.seq }, Security.getPublicKey()),
rseq = socket.wseq, rseq = socket.wseq,
wseq = socket.rseq, wseq = socket.rseq,
}) })
local timerId = os.startTimer(3) local timerId = os.startTimer(3)
repeat repeat
local e, id, sport, dport, msg = os.pullEvent() local e, id, sport, dport, msg = os.pullEvent()
if e == 'modem_message' and if e == 'modem_message' and
sport == socket.sport and sport == socket.sport and
msg.dhost == socket.shost then 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.dport = dport
socket.connected = true socket.connected = true
Logger.log('socket', 'connection established to %d %d->%d', Logger.log('socket', 'connection established to %d %d->%d',
host, socket.sport, socket.dport) host, socket.sport, socket.dport)
_G.transport.open(socket) _G.transport.open(socket)
return socket return socket
elseif msg.type == 'REJE' then elseif msg.type == 'REJE' then
return false, 'Password not set on target or not trusted' return false, 'Password not set on target or not trusted'
end end
end end
until e == 'timer' and id == timerId until e == 'timer' and id == timerId
socket:close() socket:close()
return false, 'Connection timed out' return false, 'Connection timed out'
end end
local function trusted(msg, port) local function trusted(msg, port)
if port == 19 or msg.shost == os.getComputerID() then if port == 19 or msg.shost == os.getComputerID() then
-- no auth for trust server or loopback -- no auth for trust server or loopback
return true return true
end end
if not Security.hasPassword() then if not Security.hasPassword() then
-- no password has been set on this computer -- no password has been set on this computer
return true return true
end end
local trustList = Util.readTable('usr/.known_hosts') or { } local trustList = Util.readTable('usr/.known_hosts') or { }
local pubKey = trustList[msg.shost] local pubKey = trustList[msg.shost]
if pubKey then if pubKey then
local data = Crypto.decrypt(msg.t or '', pubKey) local data = Crypto.decrypt(msg.t or '', pubKey)
--local sharedKey = modexp(pubKey, exchange.secretKey, public.primeMod) --local sharedKey = modexp(pubKey, exchange.secretKey, public.primeMod)
return data.ts and tonumber(data.ts) and math.abs(os.time() - data.ts) < 1 return data.ts and tonumber(data.ts) and math.abs(os.time() - data.ts) < 1
end end
end end
function Socket.server(port) function Socket.server(port)
device.wireless_modem.open(port) device.wireless_modem.open(port)
Logger.log('socket', 'Waiting for connections on port ' .. port) Logger.log('socket', 'Waiting for connections on port ' .. port)
while true do while true do
local _, _, sport, dport, msg = os.pullEvent('modem_message') local _, _, sport, dport, msg = os.pullEvent('modem_message')
if sport == port and if sport == port and
msg and msg and
msg.dhost == os.getComputerID() and msg.dhost == os.getComputerID() and
msg.type == 'OPEN' then msg.type == 'OPEN' then
local socket = newSocket(msg.shost == os.getComputerID()) local socket = newSocket(msg.shost == os.getComputerID())
socket.dport = dport socket.dport = dport
socket.dhost = msg.shost socket.dhost = msg.shost
socket.wseq = msg.wseq socket.wseq = msg.wseq
socket.rseq = msg.rseq socket.rseq = msg.rseq
if trusted(msg, port) then if trusted(msg, port) then
socket.connected = true socket.connected = true
socket.transmit(socket.dport, socket.sport, { socket.transmit(socket.dport, socket.sport, {
type = 'CONN', type = 'CONN',
dhost = socket.dhost, dhost = socket.dhost,
shost = socket.shost, shost = socket.shost,
}) })
Logger.log('socket', 'Connection established %d->%d', socket.sport, socket.dport) Logger.log('socket', 'Connection established %d->%d', socket.sport, socket.dport)
_G.transport.open(socket) _G.transport.open(socket)
return socket return socket
end end
socket.transmit(socket.dport, socket.sport, { socket.transmit(socket.dport, socket.sport, {
type = 'REJE', type = 'REJE',
dhost = socket.dhost, dhost = socket.dhost,
shost = socket.shost, shost = socket.shost,
}) })
socket:close() socket:close()
end end
end end
end end
return Socket return Socket

View File

@ -8,327 +8,328 @@ local Terminal = { }
-- add scrolling functions to a window -- add scrolling functions to a window
function Terminal.scrollable(win, maxScroll) function Terminal.scrollable(win, maxScroll)
local lines = { } local lines = { }
local scrollPos = 0 local scrollPos = 0
local oblit, oreposition = win.blit, win.reposition local oblit, oreposition = win.blit, win.reposition
local palette = { } local palette = { }
for n = 1, 16 do for n = 1, 16 do
palette[2 ^ (n - 1)] = _sub("0123456789abcdef", n, n) palette[2 ^ (n - 1)] = _sub("0123456789abcdef", n, n)
end end
maxScroll = maxScroll or 100 maxScroll = maxScroll or 100
-- should only do if window is visible... -- should only do if window is visible...
local function redraw() local function redraw()
local _, h = win.getSize() local _, h = win.getSize()
local x, y = win.getCursorPos() local x, y = win.getCursorPos()
for i = 1, h do for i = 1, h do
local line = lines[i + scrollPos] local line = lines[i + scrollPos]
if line and line.dirty then if line and line.dirty then
win.setCursorPos(1, i) win.setCursorPos(1, i)
oblit(line.text, line.fg, line.bg) oblit(line.text, line.fg, line.bg)
line.dirty = false line.dirty = false
end end
end end
win.setCursorPos(x, y) win.setCursorPos(x, y)
end end
local function scrollTo(p, forceRedraw) local function scrollTo(p, forceRedraw)
local _, h = win.getSize() local _, h = win.getSize()
local ms = #lines - h -- max scroll local ms = #lines - h -- max scroll
p = math.min(math.max(p, 0), ms) -- normalize p = math.min(math.max(p, 0), ms) -- normalize
if p ~= scrollPos or forceRedraw then if p ~= scrollPos or forceRedraw then
scrollPos = p scrollPos = p
for _, line in pairs(lines) do for _, line in pairs(lines) do
line.dirty = true line.dirty = true
end end
end end
end end
function win.write(text) function win.write(text)
local _, h = win.getSize() local _, h = win.getSize()
scrollTo(#lines - h) text = tostring(text) or ''
win.blit(tostring(text), scrollTo(#lines - h)
_rep(palette[win.getTextColor()], #text), win.blit(text,
_rep(palette[win.getBackgroundColor()], #text)) _rep(palette[win.getTextColor()], #text),
local x, y = win.getCursorPos() _rep(palette[win.getBackgroundColor()], #text))
win.setCursorPos(x + #text, y) local x, y = win.getCursorPos()
end win.setCursorPos(x + #text, y)
end
function win.clearLine() function win.clearLine()
local w, h = win.getSize() local w, h = win.getSize()
local _, y = win.getCursorPos() local _, y = win.getCursorPos()
scrollTo(#lines - h) scrollTo(#lines - h)
lines[y + scrollPos] = { lines[y + scrollPos] = {
text = _rep(' ', w), text = _rep(' ', w),
fg = _rep(palette[win.getTextColor()], w), fg = _rep(palette[win.getTextColor()], w),
bg = _rep(palette[win.getBackgroundColor()], w), bg = _rep(palette[win.getBackgroundColor()], w),
dirty = true, dirty = true,
} }
redraw() redraw()
end end
function win.blit(text, fg, bg) function win.blit(text, fg, bg)
local x, y = win.getCursorPos() local x, y = win.getCursorPos()
local w, h = win.getSize() local w, h = win.getSize()
if y > 0 and y <= h and x <= w then if y > 0 and y <= h and x <= w then
local width = #text local width = #text
-- fix ffs -- fix ffs
if x < 1 then if x < 1 then
text = _sub(text, 2 - x) text = _sub(text, 2 - x)
if bg then if bg then
bg = _sub(bg, 2 - x) bg = _sub(bg, 2 - x)
end end
if bg then if bg then
fg = _sub(fg, 2 - x) fg = _sub(fg, 2 - x)
end end
width = width + x - 1 width = width + x - 1
x = 1 x = 1
end end
if x + width - 1 > w then if x + width - 1 > w then
text = _sub(text, 1, w - x + 1) text = _sub(text, 1, w - x + 1)
if bg then if bg then
bg = _sub(bg, 1, w - x + 1) bg = _sub(bg, 1, w - x + 1)
end end
if bg then if bg then
fg = _sub(fg, 1, w - x + 1) fg = _sub(fg, 1, w - x + 1)
end end
width = #text width = #text
end end
if width > 0 then if width > 0 then
local function replace(sstr, pos, rstr) local function replace(sstr, pos, rstr)
if pos == 1 and width == w then if pos == 1 and width == w then
return rstr return rstr
elseif pos == 1 then elseif pos == 1 then
return rstr .. _sub(sstr, pos+width) return rstr .. _sub(sstr, pos+width)
elseif pos + width > w then elseif pos + width > w then
return _sub(sstr, 1, pos-1) .. rstr return _sub(sstr, 1, pos-1) .. rstr
end end
return _sub(sstr, 1, pos-1) .. rstr .. _sub(sstr, pos+width) return _sub(sstr, 1, pos-1) .. rstr .. _sub(sstr, pos+width)
end end
local line = lines[y + scrollPos] local line = lines[y + scrollPos]
line.dirty = true line.dirty = true
line.text = replace(line.text, x, text, width) line.text = replace(line.text, x, text, width)
if fg then if fg then
line.fg = replace(line.fg, x, fg, width) line.fg = replace(line.fg, x, fg, width)
end end
if bg then if bg then
line.bg = replace(line.bg, x, bg, width) line.bg = replace(line.bg, x, bg, width)
end end
end end
end end
redraw() redraw()
end end
function win.clear() function win.clear()
local w, h = win.getSize() local w, h = win.getSize()
local text = _rep(' ', w) local text = _rep(' ', w)
local fg = _rep(palette[win.getTextColor()], w) local fg = _rep(palette[win.getTextColor()], w)
local bg = _rep(palette[win.getBackgroundColor()], w) local bg = _rep(palette[win.getBackgroundColor()], w)
lines = { } lines = { }
for y = 1, h do for y = 1, h do
lines[y] = { lines[y] = {
dirty = true, dirty = true,
text = text, text = text,
fg = fg, fg = fg,
bg = bg, bg = bg,
} }
end end
scrollPos = 0 scrollPos = 0
redraw() redraw()
end end
-- doesn't support negative scrolling... -- doesn't support negative scrolling...
function win.scroll(n) function win.scroll(n)
local w = win.getSize() local w = win.getSize()
for _ = 1, n do for _ = 1, n do
lines[#lines + 1] = { lines[#lines + 1] = {
text = _rep(' ', w), text = _rep(' ', w),
fg = _rep(palette[win.getTextColor()], w), fg = _rep(palette[win.getTextColor()], w),
bg = _rep(palette[win.getBackgroundColor()], w), bg = _rep(palette[win.getBackgroundColor()], w),
} }
end end
while #lines > maxScroll do while #lines > maxScroll do
table.remove(lines, 1) table.remove(lines, 1)
end end
scrollTo(maxScroll, true) scrollTo(maxScroll, true)
redraw() redraw()
end end
function win.scrollUp() function win.scrollUp()
scrollTo(scrollPos - 1) scrollTo(scrollPos - 1)
redraw() redraw()
end end
function win.scrollDown() function win.scrollDown()
scrollTo(scrollPos + 1) scrollTo(scrollPos + 1)
redraw() redraw()
end end
function win.reposition(x, y, nw, nh) function win.reposition(x, y, nw, nh)
local w, h = win.getSize() local w, h = win.getSize()
local D = (nh or h) - h local D = (nh or h) - h
if D > 0 then if D > 0 then
for _ = 1, D do for _ = 1, D do
lines[#lines + 1] = { lines[#lines + 1] = {
text = _rep(' ', w), text = _rep(' ', w),
fg = _rep(palette[win.getTextColor()], w), fg = _rep(palette[win.getTextColor()], w),
bg = _rep(palette[win.getBackgroundColor()], w), bg = _rep(palette[win.getBackgroundColor()], w),
} }
end end
elseif D < 0 then elseif D < 0 then
for _ = D, -1 do for _ = D, -1 do
lines[#lines] = nil lines[#lines] = nil
end end
end end
return oreposition(x, y, nw, nh) return oreposition(x, y, nw, nh)
end end
win.clear() win.clear()
end end
-- get windows contents -- get windows contents
function Terminal.getContents(win, parent) function Terminal.getContents(win, parent)
local oblit, oscp = parent.blit, parent.setCursorPos local oblit, oscp = parent.blit, parent.setCursorPos
local lines = { } local lines = { }
parent.blit = function(text, fg, bg) parent.blit = function(text, fg, bg)
lines[#lines + 1] = { lines[#lines + 1] = {
text = text, text = text,
fg = fg, fg = fg,
bg = bg, bg = bg,
} }
end end
parent.setCursorPos = function() end parent.setCursorPos = function() end
win.setVisible(true) win.setVisible(true)
win.redraw() win.redraw()
parent.blit = oblit parent.blit = oblit
parent.setCursorPos = oscp parent.setCursorPos = oscp
return lines return lines
end end
function Terminal.toGrayscale(ct) function Terminal.toGrayscale(ct)
local scolors = { local scolors = {
[ colors.white ] = colors.white, [ colors.white ] = colors.white,
[ colors.orange ] = colors.lightGray, [ colors.orange ] = colors.lightGray,
[ colors.magenta ] = colors.lightGray, [ colors.magenta ] = colors.lightGray,
[ colors.lightBlue ] = colors.lightGray, [ colors.lightBlue ] = colors.lightGray,
[ colors.yellow ] = colors.lightGray, [ colors.yellow ] = colors.lightGray,
[ colors.lime ] = colors.lightGray, [ colors.lime ] = colors.lightGray,
[ colors.pink ] = colors.lightGray, [ colors.pink ] = colors.lightGray,
[ colors.gray ] = colors.gray, [ colors.gray ] = colors.gray,
[ colors.lightGray ] = colors.lightGray, [ colors.lightGray ] = colors.lightGray,
[ colors.cyan ] = colors.lightGray, [ colors.cyan ] = colors.lightGray,
[ colors.purple ] = colors.gray, [ colors.purple ] = colors.gray,
[ colors.blue ] = colors.gray, [ colors.blue ] = colors.gray,
[ colors.brown ] = colors.gray, [ colors.brown ] = colors.gray,
[ colors.green ] = colors.lightGray, [ colors.green ] = colors.lightGray,
[ colors.red ] = colors.gray, [ colors.red ] = colors.gray,
[ colors.black ] = colors.black, [ colors.black ] = colors.black,
} }
local methods = { 'setBackgroundColor', 'setBackgroundColour', local methods = { 'setBackgroundColor', 'setBackgroundColour',
'setTextColor', 'setTextColour' } 'setTextColor', 'setTextColour' }
for _,v in pairs(methods) do for _,v in pairs(methods) do
local fn = ct[v] local fn = ct[v]
ct[v] = function(c) ct[v] = function(c)
fn(scolors[c]) fn(scolors[c])
end end
end end
local bcolors = { local bcolors = {
[ '1' ] = '8', [ '1' ] = '8',
[ '2' ] = '8', [ '2' ] = '8',
[ '3' ] = '8', [ '3' ] = '8',
[ '4' ] = '8', [ '4' ] = '8',
[ '5' ] = '8', [ '5' ] = '8',
[ '6' ] = '8', [ '6' ] = '8',
[ '9' ] = '8', [ '9' ] = '8',
[ 'a' ] = '7', [ 'a' ] = '7',
[ 'b' ] = '7', [ 'b' ] = '7',
[ 'c' ] = '7', [ 'c' ] = '7',
[ 'd' ] = '8', [ 'd' ] = '8',
[ 'e' ] = '7', [ 'e' ] = '7',
} }
local function translate(s) local function translate(s)
if s then if s then
s = _gsub(s, "%w", bcolors) s = _gsub(s, "%w", bcolors)
end end
return s return s
end end
local fn = ct.blit local fn = ct.blit
ct.blit = function(text, fg, bg) ct.blit = function(text, fg, bg)
fn(text, translate(fg), translate(bg)) fn(text, translate(fg), translate(bg))
end end
end end
function Terminal.getNullTerm(ct) function Terminal.getNullTerm(ct)
local nt = Terminal.copy(ct) local nt = Terminal.copy(ct)
local methods = { 'blit', 'clear', 'clearLine', 'scroll', local methods = { 'blit', 'clear', 'clearLine', 'scroll',
'setCursorBlink', 'setCursorPos', 'write' } 'setCursorBlink', 'setCursorPos', 'write' }
for _,v in pairs(methods) do for _,v in pairs(methods) do
nt[v] = function() end nt[v] = function() end
end end
return nt return nt
end end
function Terminal.copy(it, ot) function Terminal.copy(it, ot)
ot = ot or { } ot = ot or { }
for k,v in pairs(it) do for k,v in pairs(it) do
if type(v) == 'function' then if type(v) == 'function' then
ot[k] = v ot[k] = v
end end
end end
return ot return ot
end end
function Terminal.mirror(ct, dt) function Terminal.mirror(ct, dt)
for k,f in pairs(ct) do for k,f in pairs(ct) do
ct[k] = function(...) ct[k] = function(...)
local ret = { f(...) } local ret = { f(...) }
if dt[k] then if dt[k] then
dt[k](...) dt[k](...)
end end
return table.unpack(ret) return table.unpack(ret)
end end
end end
end end
function Terminal.readPassword(prompt) function Terminal.readPassword(prompt)
if prompt then if prompt then
term.write(prompt) term.write(prompt)
end end
local fn = term.current().write local fn = term.current().write
term.current().write = function() end term.current().write = function() end
local s local s
pcall(function() s = _G.read(prompt) end) pcall(function() s = _G.read(prompt) end)
term.current().write = fn term.current().write = fn
if s == '' then if s == '' then
return return
end end
return s return s
end end
return Terminal return Terminal

View File

@ -1,5 +1,3 @@
_G.requireInjector()
local Grid = require('jumper.grid') local Grid = require('jumper.grid')
local Pathfinder = require('jumper.pathfinder') local Pathfinder = require('jumper.pathfinder')
local Point = require('point') local Point = require('point')
@ -63,11 +61,11 @@ end
local function dimsAreEqual(d1, d2) local function dimsAreEqual(d1, d2)
return d1.ex == d2.ex and return d1.ex == d2.ex and
d1.ey == d2.ey and d1.ey == d2.ey and
d1.ez == d2.ez and d1.ez == d2.ez and
d1.x == d2.x and d1.x == d2.x and
d1.y == d2.y and d1.y == d2.y and
d1.z == d2.z d1.z == d2.z
end end
-- turtle sensor returns blocks in relation to the world - not turtle orientation -- 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 while #pts > 0 do
local pt = Point.closest(turtle.point, pts) local pt = Point.closest(turtle.point, pts)
if box and not Point.inBox(pt, box) then if box and not Point.inBox(pt, box) then
Util.removeByValue(pts, pt) Util.removeByValue(pts, pt)
else else
if grid:isWalkableAt(pt.x, pt.y, pt.z) then if grid:isWalkableAt(pt.x, pt.y, pt.z) then
return pt return pt
end end
Util.removeByValue(pts, pt) Util.removeByValue(pts, pt)
end end
end end
end end
@ -156,7 +154,7 @@ local function pathTo(dest, options)
dest.x, dest.y, dest.z, dest.heading) dest.x, dest.y, dest.z, dest.heading)
if not path then if not path then
Util.removeByValue(dests, dest) Util.removeByValue(dests, dest)
else else
path:filter() path:filter()

File diff suppressed because it is too large Load Diff

View File

@ -14,357 +14,485 @@ Canvas.darkPalette = { }
Canvas.grayscalePalette = { } Canvas.grayscalePalette = { }
for n = 1, 16 do for n = 1, 16 do
Canvas.colorPalette[2 ^ (n - 1)] = _sub("0123456789abcdef", n, n) Canvas.colorPalette[2 ^ (n - 1)] = _sub("0123456789abcdef", n, n)
Canvas.grayscalePalette[2 ^ (n - 1)] = _sub("088888878877787f", n, n) Canvas.grayscalePalette[2 ^ (n - 1)] = _sub("088888878877787f", n, n)
Canvas.darkPalette[2 ^ (n - 1)] = _sub("8777777f77fff77f", n, n) Canvas.darkPalette[2 ^ (n - 1)] = _sub("8777777f77fff77f", n, n)
end end
function Canvas:init(args) function Canvas:init(args)
self.x = 1 self.x = 1
self.y = 1 self.y = 1
self.layers = { } self.layers = { }
Util.merge(self, args) Util.merge(self, args)
self.ex = self.x + self.width - 1 self.ex = self.x + self.width - 1
self.ey = self.y + self.height - 1 self.ey = self.y + self.height - 1
if not self.palette then if not self.palette then
if self.isColor then if self.isColor then
self.palette = Canvas.colorPalette self.palette = Canvas.colorPalette
else else
self.palette = Canvas.grayscalePalette self.palette = Canvas.grayscalePalette
end end
end end
self.lines = { } self.lines = { }
for i = 1, self.height do for i = 1, self.height do
self.lines[i] = { } self.lines[i] = { }
end end
end end
function Canvas:move(x, y) function Canvas:move(x, y)
self.x, self.y = x, y self.x, self.y = x, y
self.ex = self.x + self.width - 1 self.ex = self.x + self.width - 1
self.ey = self.y + self.height - 1 self.ey = self.y + self.height - 1
end end
function Canvas:resize(w, h) function Canvas:resize(w, h)
for i = self.height, h do for i = self.height, h do
self.lines[i] = { } self.lines[i] = { }
end end
while #self.lines > h do while #self.lines > h do
table.remove(self.lines, #self.lines) table.remove(self.lines, #self.lines)
end end
if w ~= self.width then if w ~= self.width then
for i = 1, self.height do for i = 1, self.height do
self.lines[i] = { dirty = true } self.lines[i] = { dirty = true }
end end
end end
self.ex = self.x + w - 1 self.ex = self.x + w - 1
self.ey = self.y + h - 1 self.ey = self.y + h - 1
self.width = w self.width = w
self.height = h self.height = h
end end
function Canvas:copy() function Canvas:copy()
local b = Canvas({ local b = Canvas({
x = self.x, x = self.x,
y = self.y, y = self.y,
width = self.width, width = self.width,
height = self.height, height = self.height,
isColor = self.isColor, isColor = self.isColor,
}) })
for i = 1, self.height do for i = 1, self.height do
b.lines[i].text = self.lines[i].text b.lines[i].text = self.lines[i].text
b.lines[i].fg = self.lines[i].fg b.lines[i].fg = self.lines[i].fg
b.lines[i].bg = self.lines[i].bg b.lines[i].bg = self.lines[i].bg
end end
return b return b
end end
function Canvas:addLayer(layer) function Canvas:addLayer(layer)
local canvas = Canvas({ local canvas = Canvas({
x = layer.x, x = layer.x,
y = layer.y, y = layer.y,
width = layer.width, width = layer.width,
height = layer.height, height = layer.height,
isColor = self.isColor, isColor = self.isColor,
}) })
canvas.parent = self canvas.parent = self
table.insert(self.layers, canvas) table.insert(self.layers, canvas)
return canvas return canvas
end end
function Canvas:removeLayer() function Canvas:removeLayer()
for k, layer in pairs(self.parent.layers) do for k, layer in pairs(self.parent.layers) do
if layer == self then if layer == self then
self:setVisible(false) self:setVisible(false)
table.remove(self.parent.layers, k) table.remove(self.parent.layers, k)
break break
end end
end end
end end
function Canvas:setVisible(visible) function Canvas:setVisible(visible)
self.visible = visible self.visible = visible
if not visible then if not visible then
self.parent:dirty() self.parent:dirty()
-- set parent's lines to dirty for each line in self -- set parent's lines to dirty for each line in self
end end
end end
function Canvas:write(x, y, text, bg, fg) function Canvas:write(x, y, text, bg, fg)
if bg then if bg then
bg = _rep(self.palette[bg], #text) bg = _rep(self.palette[bg], #text)
end end
if fg then if fg then
fg = _rep(self.palette[fg], #text) fg = _rep(self.palette[fg], #text)
end end
self:writeBlit(x, y, text, bg, fg) self:writeBlit(x, y, text, bg, fg)
end end
function Canvas:writeBlit(x, y, text, bg, fg) function Canvas:writeBlit(x, y, text, bg, fg)
if y > 0 and y <= self.height and x <= self.width then if y > 0 and y <= #self.lines and x <= self.width then
local width = #text local width = #text
-- fix ffs -- fix ffs
if x < 1 then if x < 1 then
text = _sub(text, 2 - x) text = _sub(text, 2 - x)
if bg then if bg then
bg = _sub(bg, 2 - x) bg = _sub(bg, 2 - x)
end end
if bg then if bg then
fg = _sub(fg, 2 - x) fg = _sub(fg, 2 - x)
end end
width = width + x - 1 width = width + x - 1
x = 1 x = 1
end end
if x + width - 1 > self.width then if x + width - 1 > self.width then
text = _sub(text, 1, self.width - x + 1) text = _sub(text, 1, self.width - x + 1)
if bg then if bg then
bg = _sub(bg, 1, self.width - x + 1) bg = _sub(bg, 1, self.width - x + 1)
end end
if bg then if bg then
fg = _sub(fg, 1, self.width - x + 1) fg = _sub(fg, 1, self.width - x + 1)
end end
width = #text width = #text
end end
if width > 0 then if width > 0 then
local function replace(sstr, pos, rstr, width) local function replace(sstr, pos, rstr, width)
if pos == 1 and width == self.width then if pos == 1 and width == self.width then
return rstr return rstr
elseif pos == 1 then elseif pos == 1 then
return rstr .. _sub(sstr, pos+width) return rstr .. _sub(sstr, pos+width)
elseif pos + width > self.width then elseif pos + width > self.width then
return _sub(sstr, 1, pos-1) .. rstr return _sub(sstr, 1, pos-1) .. rstr
end end
return _sub(sstr, 1, pos-1) .. rstr .. _sub(sstr, pos+width) return _sub(sstr, 1, pos-1) .. rstr .. _sub(sstr, pos+width)
end end
local line = self.lines[y] local line = self.lines[y]
line.dirty = true line.dirty = true
line.text = replace(line.text, x, text, width) line.text = replace(line.text, x, text, width)
if fg then if fg then
line.fg = replace(line.fg, x, fg, width) line.fg = replace(line.fg, x, fg, width)
end end
if bg then if bg then
line.bg = replace(line.bg, x, bg, width) line.bg = replace(line.bg, x, bg, width)
end end
end end
end end
end end
function Canvas:writeLine(y, text, fg, bg) function Canvas:writeLine(y, text, fg, bg)
self.lines[y].dirty = true self.lines[y].dirty = true
self.lines[y].text = text self.lines[y].text = text
self.lines[y].fg = fg self.lines[y].fg = fg
self.lines[y].bg = bg self.lines[y].bg = bg
end end
function Canvas:reset() function Canvas:reset()
self.regions = nil self.regions = nil
end end
function Canvas:clear(bg, fg) function Canvas:clear(bg, fg)
local text = _rep(' ', self.width) local text = _rep(' ', self.width)
fg = _rep(self.palette[fg or colors.white], self.width) fg = _rep(self.palette[fg or colors.white], self.width)
bg = _rep(self.palette[bg or colors.black], self.width) bg = _rep(self.palette[bg or colors.black], self.width)
for i = 1, self.height do for i = 1, self.height do
self:writeLine(i, text, fg, bg) self:writeLine(i, text, fg, bg)
end end
end end
function Canvas:punch(rect) function Canvas:punch(rect)
if not self.regions then if not self.regions then
self.regions = Region.new(self.x, self.y, self.ex, self.ey) self.regions = Region.new(self.x, self.y, self.ex, self.ey)
end end
self.regions:subRect(rect.x, rect.y, rect.ex, rect.ey) self.regions:subRect(rect.x, rect.y, rect.ex, rect.ey)
end end
function Canvas:blitClipped(device) function Canvas:blitClipped(device)
for _,region in ipairs(self.regions.region) do for _,region in ipairs(self.regions.region) do
self:blit(device, self:blit(device,
{ x = region[1] - self.x + 1, { x = region[1] - self.x + 1,
y = region[2] - self.y + 1, y = region[2] - self.y + 1,
ex = region[3]- self.x + 1, ex = region[3]- self.x + 1,
ey = region[4] - self.y + 1 }, ey = region[4] - self.y + 1 },
{ x = region[1], y = region[2] }) { x = region[1], y = region[2] })
end end
end end
function Canvas:redraw(device) function Canvas:redraw(device)
self:reset() self:reset()
if #self.layers > 0 then if #self.layers > 0 then
for _,layer in pairs(self.layers) do for _,layer in pairs(self.layers) do
self:punch(layer) self:punch(layer)
end end
self:blitClipped(device) self:blitClipped(device)
else else
self:blit(device) self:blit(device)
end end
self:clean() self:clean()
end end
function Canvas:isDirty() function Canvas:isDirty()
for _, line in pairs(self.lines) do for _, line in pairs(self.lines) do
if line.dirty then if line.dirty then
return true return true
end end
end end
end end
function Canvas:dirty() function Canvas:dirty()
for _, line in pairs(self.lines) do for _, line in pairs(self.lines) do
line.dirty = true line.dirty = true
end end
end end
function Canvas:clean() function Canvas:clean()
for _, line in pairs(self.lines) do for _, line in pairs(self.lines) do
line.dirty = false line.dirty = false
end end
end end
function Canvas:render(device, layers) --- redrawAll ? function Canvas:render(device, layers) --- redrawAll ?
layers = layers or self.layers layers = layers or self.layers
if #layers > 0 then if #layers > 0 then
self.regions = Region.new(self.x, self.y, self.ex, self.ey) self.regions = Region.new(self.x, self.y, self.ex, self.ey)
local l = Util.shallowCopy(layers) local l = Util.shallowCopy(layers)
for _, canvas in ipairs(layers) do for _, canvas in ipairs(layers) do
table.remove(l, 1) table.remove(l, 1)
if canvas.visible then if canvas.visible then
self:punch(canvas) self:punch(canvas)
canvas:render(device, l) canvas:render(device, l)
end end
end end
self:blitClipped(device) self:blitClipped(device)
self:reset() self:reset()
else else
self:blit(device) self:blit(device)
end end
self:clean() self:clean()
end end
function Canvas:blit(device, src, tgt) 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 } src = src or { x = 1, y = 1, ex = self.ex - self.x + 1, ey = self.ey - self.y + 1 }
tgt = tgt or self tgt = tgt or self
for i = 0, src.ey - src.y do for i = 0, src.ey - src.y do
local line = self.lines[src.y + i] local line = self.lines[src.y + i]
if line and line.dirty then if line and line.dirty then
local t, fg, bg = line.text, line.fg, line.bg local t, fg, bg = line.text, line.fg, line.bg
if src.x > 1 or src.ex < self.ex then if src.x > 1 or src.ex < self.ex then
t = _sub(t, src.x, src.ex) t = _sub(t, src.x, src.ex)
fg = _sub(fg, src.x, src.ex) fg = _sub(fg, src.x, src.ex)
bg = _sub(bg, src.x, src.ex) bg = _sub(bg, src.x, src.ex)
end end
--if tgt.y + i > self.ey then -- wrong place to do clipping ?? --if tgt.y + i > self.ey then -- wrong place to do clipping ??
-- break -- break
--end --end
device.setCursorPos(tgt.x, tgt.y + i) device.setCursorPos(tgt.x, tgt.y + i)
device.blit(t, fg, bg) device.blit(t, fg, bg)
end end
end end
end end
function Canvas:applyPalette(palette) function Canvas:applyPalette(palette)
local lookup = { } local lookup = { }
for n = 1, 16 do for n = 1, 16 do
lookup[self.palette[2 ^ (n - 1)]] = palette[2 ^ (n - 1)] lookup[self.palette[2 ^ (n - 1)]] = palette[2 ^ (n - 1)]
end end
for _, l in pairs(self.lines) do for _, l in pairs(self.lines) do
l.fg = _gsub(l.fg, '%w', lookup) l.fg = _gsub(l.fg, '%w', lookup)
l.bg = _gsub(l.bg, '%w', lookup) l.bg = _gsub(l.bg, '%w', lookup)
l.dirty = true l.dirty = true
end end
self.palette = palette self.palette = palette
end end
function Canvas.convertWindow(win, parent, wx, wy) function Canvas.convertWindow(win, parent, wx, wy)
local w, h = win.getSize() local w, h = win.getSize()
win.canvas = Canvas({ win.canvas = Canvas({
x = wx, x = wx,
y = wy, y = wy,
width = w, width = w,
height = h, height = h,
isColor = win.isColor(), isColor = win.isColor(),
}) })
function win.clear() function win.clear()
win.canvas:clear(win.getBackgroundColor(), win.getTextColor()) win.canvas:clear(win.getBackgroundColor(), win.getTextColor())
end end
function win.clearLine() function win.clearLine()
local _, y = win.getCursorPos() local _, y = win.getCursorPos()
win.canvas:write(1, win.canvas:write(1,
y, y,
_rep(' ', win.canvas.width), _rep(' ', win.canvas.width),
win.getBackgroundColor(), win.getBackgroundColor(),
win.getTextColor()) win.getTextColor())
end end
function win.write(str) function win.write(str)
local x, y = win.getCursorPos() local x, y = win.getCursorPos()
win.canvas:write(x, win.canvas:write(x,
y, y,
str, str,
win.getBackgroundColor(), win.getBackgroundColor(),
win.getTextColor()) win.getTextColor())
win.setCursorPos(x + #str, y) win.setCursorPos(x + #str, y)
end end
function win.blit(text, fg, bg) function win.blit(text, fg, bg)
local x, y = win.getCursorPos() local x, y = win.getCursorPos()
win.canvas:writeBlit(x, y, text, bg, fg) win.canvas:writeBlit(x, y, text, bg, fg)
end end
function win.redraw() function win.redraw()
win.canvas:redraw(parent) win.canvas:redraw(parent)
end end
function win.scroll(n) function win.scroll(n)
table.insert(win.canvas.lines, table.remove(win.canvas.lines, 1)) table.insert(win.canvas.lines, table.remove(win.canvas.lines, 1))
win.canvas.lines[#win.canvas.lines].text = _rep(' ', win.canvas.width) win.canvas.lines[#win.canvas.lines].text = _rep(' ', win.canvas.width)
win.canvas:dirty() win.canvas:dirty()
end end
function win.reposition(x, y, width, height) function win.reposition(x, y, width, height)
win.canvas.x, win.canvas.y = x, y win.canvas.x, win.canvas.y = x, y
win.canvas:resize(width or win.canvas.width, height or win.canvas.height) win.canvas:resize(width or win.canvas.width, height or win.canvas.height)
end end
win.clear() win.clear()
end 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 return Canvas

View File

@ -6,140 +6,140 @@ local fs = _G.fs
return function(args) return function(args)
local columns = { local columns = {
{ heading = 'Name', key = 'name' }, { heading = 'Name', key = 'name' },
} }
if UI.term.width > 28 then if UI.term.width > 28 then
table.insert(columns, table.insert(columns,
{ heading = 'Size', key = 'size', width = 5 } { heading = 'Size', key = 'size', width = 5 }
) )
end end
args = args or { } args = args or { }
local selectFile = UI.Dialog { local selectFile = UI.Dialog {
x = args.x or 3, x = args.x or 3,
y = args.y or 2, y = args.y or 2,
z = args.z or 2, z = args.z or 2,
-- rex = args.rex or -3, -- rex = args.rex or -3,
-- rey = args.rey or -3, -- rey = args.rey or -3,
height = args.height, height = args.height,
width = args.width, width = args.width,
title = 'Select File', title = 'Select File',
grid = UI.ScrollingGrid { grid = UI.ScrollingGrid {
x = 2, x = 2,
y = 2, y = 2,
ex = -2, ex = -2,
ey = -4, ey = -4,
path = '', path = '',
sortColumn = 'name', sortColumn = 'name',
columns = columns, columns = columns,
}, },
path = UI.TextEntry { path = UI.TextEntry {
x = 2, x = 2,
y = -2, y = -2,
ex = -11, ex = -11,
limit = 256, limit = 256,
accelerators = { accelerators = {
enter = 'path_enter', enter = 'path_enter',
} }
}, },
cancel = UI.Button { cancel = UI.Button {
text = 'Cancel', text = 'Cancel',
x = -9, x = -9,
y = -2, y = -2,
event = 'cancel', event = 'cancel',
}, },
} }
function selectFile:enable(path, fn) function selectFile:enable(path, fn)
self:setPath(path) self:setPath(path)
self.fn = fn self.fn = fn
UI.Dialog.enable(self) UI.Dialog.enable(self)
end end
function selectFile:setPath(path) function selectFile:setPath(path)
self.grid.dir = path self.grid.dir = path
while not fs.isDir(self.grid.dir) do while not fs.isDir(self.grid.dir) do
self.grid.dir = fs.getDir(self.grid.dir) self.grid.dir = fs.getDir(self.grid.dir)
end end
self.path.value = self.grid.dir self.path.value = self.grid.dir
end end
function selectFile.grid:draw() function selectFile.grid:draw()
local files = fs.listEx(self.dir) local files = fs.listEx(self.dir)
if #self.dir > 0 then if #self.dir > 0 then
table.insert(files, { table.insert(files, {
name = '..', name = '..',
isDir = true, isDir = true,
}) })
end end
self:setValues(files) self:setValues(files)
self:setIndex(1) self:setIndex(1)
UI.Grid.draw(self) UI.Grid.draw(self)
end end
function selectFile.grid:getDisplayValues(row) function selectFile.grid:getDisplayValues(row)
if row.size then if row.size then
row = Util.shallowCopy(row) row = Util.shallowCopy(row)
row.size = Util.toBytes(row.size) row.size = Util.toBytes(row.size)
end end
return row return row
end end
function selectFile.grid:getRowTextColor(file) function selectFile.grid:getRowTextColor(file)
if file.isDir then if file.isDir then
return colors.cyan return colors.cyan
end end
if file.isReadOnly then if file.isReadOnly then
return colors.pink return colors.pink
end end
return colors.white return colors.white
end end
function selectFile.grid:sortCompare(a, b) function selectFile.grid:sortCompare(a, b)
if self.sortColumn == 'size' then if self.sortColumn == 'size' then
return a.size < b.size return a.size < b.size
end end
if a.isDir == b.isDir then if a.isDir == b.isDir then
return a.name:lower() < b.name:lower() return a.name:lower() < b.name:lower()
end end
return a.isDir return a.isDir
end end
function selectFile:eventHandler(event) function selectFile:eventHandler(event)
if event.type == 'grid_select' then if event.type == 'grid_select' then
self.grid.dir = fs.combine(self.grid.dir, event.selected.name) self.grid.dir = fs.combine(self.grid.dir, event.selected.name)
self.path.value = self.grid.dir self.path.value = self.grid.dir
if event.selected.isDir then if event.selected.isDir then
self.grid:draw() self.grid:draw()
self.path:draw() self.path:draw()
else else
UI:setPreviousPage() UI:setPreviousPage()
self.fn(self.path.value) self.fn(self.path.value)
end end
elseif event.type == 'path_enter' then elseif event.type == 'path_enter' then
if fs.isDir(self.path.value) then if fs.isDir(self.path.value) then
self:setPath(self.path.value) self:setPath(self.path.value)
self.grid:draw() self.grid:draw()
self.path:draw() self.path:draw()
else else
UI:setPreviousPage() UI:setPreviousPage()
self.fn(self.path.value) self.fn(self.path.value)
end end
elseif event.type == 'cancel' then elseif event.type == 'cancel' then
UI:setPreviousPage() UI:setPreviousPage()
self.fn() self.fn()
else else
return UI.Dialog.eventHandler(self, event) return UI.Dialog.eventHandler(self, event)
end end
return true return true
end end
return selectFile return selectFile
end end

View File

@ -7,190 +7,190 @@ local Peripheral = require('peripheral')
local Glasses = class() local Glasses = class()
function Glasses:init(args) function Glasses:init(args)
local defaults = { local defaults = {
backgroundColor = colors.black, backgroundColor = colors.black,
textColor = colors.white, textColor = colors.white,
textScale = .5, textScale = .5,
backgroundOpacity = .5, backgroundOpacity = .5,
multiplier = 2.6665, multiplier = 2.6665,
-- multiplier = 2.333, -- multiplier = 2.333,
} }
defaults.width, defaults.height = term.getSize() defaults.width, defaults.height = term.getSize()
UI:setProperties(defaults, args) UI:setProperties(defaults, args)
UI:setProperties(self, defaults) UI:setProperties(self, defaults)
self.bridge = Peripheral.get({ self.bridge = Peripheral.get({
type = 'openperipheral_bridge', type = 'openperipheral_bridge',
method = 'addBox', method = 'addBox',
}) })
self.bridge.clear() self.bridge.clear()
self.setBackgroundColor = function(...) end self.setBackgroundColor = function(...) end
self.setTextColor = function(...) end self.setTextColor = function(...) end
self.t = { } self.t = { }
for i = 1, self.height do for i = 1, self.height do
self.t[i] = { self.t[i] = {
text = string.rep(' ', self.width+1), text = string.rep(' ', self.width+1),
--text = self.bridge.addText(0, 40+i*4, string.rep(' ', self.width+1), 0xffffff), --text = self.bridge.addText(0, 40+i*4, string.rep(' ', self.width+1), 0xffffff),
bg = { }, bg = { },
textFields = { }, textFields = { },
} }
end end
end end
function Glasses:setBackgroundBox(boxes, ax, bx, y, bgColor) function Glasses:setBackgroundBox(boxes, ax, bx, y, bgColor)
local colors = { local colors = {
[ colors.black ] = 0x000000, [ colors.black ] = 0x000000,
[ colors.brown ] = 0x7F664C, [ colors.brown ] = 0x7F664C,
[ colors.blue ] = 0x253192, [ colors.blue ] = 0x253192,
[ colors.red ] = 0xFF0000, [ colors.red ] = 0xFF0000,
[ colors.gray ] = 0x272727, [ colors.gray ] = 0x272727,
[ colors.lime ] = 0x426A0D, [ colors.lime ] = 0x426A0D,
[ colors.green ] = 0x2D5628, [ colors.green ] = 0x2D5628,
[ colors.white ] = 0xFFFFFF [ colors.white ] = 0xFFFFFF
} }
local function overlap(box, ax, bx) local function overlap(box, ax, bx)
if bx < box.ax or ax > box.bx then if bx < box.ax or ax > box.bx then
return false return false
end end
return true return true
end end
for _,box in pairs(boxes) do for _,box in pairs(boxes) do
if overlap(box, ax, bx) then if overlap(box, ax, bx) then
if box.bgColor == bgColor then if box.bgColor == bgColor then
ax = math.min(ax, box.ax) ax = math.min(ax, box.ax)
bx = math.max(bx, box.bx) bx = math.max(bx, box.bx)
box.ax = box.bx + 1 box.ax = box.bx + 1
elseif ax == box.ax then elseif ax == box.ax then
box.ax = bx + 1 box.ax = bx + 1
elseif ax > box.ax then elseif ax > box.ax then
if bx < box.bx then if bx < box.bx then
table.insert(boxes, { -- split table.insert(boxes, { -- split
ax = bx + 1, ax = bx + 1,
bx = box.bx, bx = box.bx,
bgColor = box.bgColor bgColor = box.bgColor
}) })
box.bx = ax - 1 box.bx = ax - 1
break break
else else
box.ax = box.bx + 1 box.ax = box.bx + 1
end end
elseif ax < box.ax then elseif ax < box.ax then
if bx > box.bx then if bx > box.bx then
box.ax = box.bx + 1 -- delete box.ax = box.bx + 1 -- delete
else else
box.ax = bx + 1 box.ax = bx + 1
end end
end end
end end
end end
if bgColor ~= colors.black then if bgColor ~= colors.black then
table.insert(boxes, { table.insert(boxes, {
ax = ax, ax = ax,
bx = bx, bx = bx,
bgColor = bgColor bgColor = bgColor
}) })
end end
local deleted local deleted
repeat repeat
deleted = false deleted = false
for k,box in pairs(boxes) do for k,box in pairs(boxes) do
if box.ax > box.bx then if box.ax > box.bx then
if box.box then if box.box then
box.box.delete() box.box.delete()
end end
table.remove(boxes, k) table.remove(boxes, k)
deleted = true deleted = true
break break
end end
if not box.box then if not box.box then
box.box = self.bridge.addBox( box.box = self.bridge.addBox(
math.floor(self.x + (box.ax - 1) * self.multiplier), math.floor(self.x + (box.ax - 1) * self.multiplier),
self.y + y * 4, self.y + y * 4,
math.ceil((box.bx - box.ax + 1) * self.multiplier), math.ceil((box.bx - box.ax + 1) * self.multiplier),
4, 4,
colors[bgColor], colors[bgColor],
self.backgroundOpacity) self.backgroundOpacity)
else else
box.box.setX(self.x + math.floor((box.ax - 1) * self.multiplier)) box.box.setX(self.x + math.floor((box.ax - 1) * self.multiplier))
box.box.setWidth(math.ceil((box.bx - box.ax + 1) * self.multiplier)) box.box.setWidth(math.ceil((box.bx - box.ax + 1) * self.multiplier))
end end
end end
until not deleted until not deleted
end end
function Glasses:write(x, y, text, bg) function Glasses:write(x, y, text, bg)
if x < 1 then if x < 1 then
error(' less ', 6) error(' less ', 6)
end end
if y <= #self.t then if y <= #self.t then
local line = self.t[y] local line = self.t[y]
local str = line.text local str = line.text
str = str:sub(1, x-1) .. text .. str:sub(x + #text) str = str:sub(1, x-1) .. text .. str:sub(x + #text)
self.t[y].text = str self.t[y].text = str
for _,tf in pairs(line.textFields) do for _,tf in pairs(line.textFields) do
tf.delete() tf.delete()
end end
line.textFields = { } line.textFields = { }
local function split(st) local function split(st)
local words = { } local words = { }
local offset = 0 local offset = 0
while true do while true do
local b,e,w = st:find('(%S+)') local b,e,w = st:find('(%S+)')
if not b then if not b then
break break
end end
table.insert(words, { table.insert(words, {
offset = b + offset - 1, offset = b + offset - 1,
text = w, text = w,
}) })
offset = offset + e offset = offset + e
st = st:sub(e + 1) st = st:sub(e + 1)
end end
return words return words
end end
local words = split(str) local words = split(str)
for _,word in pairs(words) do for _,word in pairs(words) do
local tf = self.bridge.addText(self.x + word.offset * self.multiplier, local tf = self.bridge.addText(self.x + word.offset * self.multiplier,
self.y+y*4, '', 0xffffff) self.y+y*4, '', 0xffffff)
tf.setScale(self.textScale) tf.setScale(self.textScale)
tf.setZ(1) tf.setZ(1)
tf.setText(word.text) tf.setText(word.text)
table.insert(line.textFields, tf) table.insert(line.textFields, tf)
end end
self:setBackgroundBox(line.bg, x, x + #text - 1, y, bg) self:setBackgroundBox(line.bg, x, x + #text - 1, y, bg)
end end
end end
function Glasses:clear(bg) function Glasses:clear(bg)
for _,line in pairs(self.t) do for _,line in pairs(self.t) do
for _,tf in pairs(line.textFields) do for _,tf in pairs(line.textFields) do
tf.delete() tf.delete()
end end
line.textFields = { } line.textFields = { }
line.text = string.rep(' ', self.width+1) line.text = string.rep(' ', self.width+1)
-- self.t[i].text.setText('') -- self.t[i].text.setText('')
end end
end end
function Glasses:reset() function Glasses:reset()
self:clear() self:clear()
self.bridge.clear() self.bridge.clear()
self.bridge.sync() self.bridge.sync()
end end
function Glasses:sync() function Glasses:sync()
self.bridge.sync() self.bridge.sync()
end end
return Glasses return Glasses

View File

@ -1,57 +1,18 @@
-------------------------------------------------------------------------------
-- --
-- tek.lib.region -- tek.lib.region
-- Written by Timm S. Mueller <tmueller at schulze-mueller.de> -- Written by Timm S. Mueller <tmueller at schulze-mueller.de>
-- --
-- Copyright 2008 - 2016 by the authors and contributors: -- Copyright 2008 - 2016 by the authors and contributors:
-- --
-- * Timm S. Muller <tmueller at schulze-mueller.de> -- * Timm S. Muller <tmueller at schulze-mueller.de>
-- * Franciska Schulze <fschulze at schulze-mueller.de> -- * Franciska Schulze <fschulze at schulze-mueller.de>
-- * Tobias Schwinger <tschwinger at isonews2.com> -- * Tobias Schwinger <tschwinger at isonews2.com>
--
-- 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:: -- https://opensource.org/licenses/MIT
-- This library implements the management of regions, which are
-- collections of non-overlapping rectangles.
-- --
-- FUNCTIONS:: -- Some comments have been removed to reduce file size, see:
-- - Region:andRect() - ''And''s a rectangle to a region -- https://github.com/technosaurus/tekui/blob/master/etc/region.lua
-- - Region:andRegion() - ''And''s a region to a region -- for the full source
-- - 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
--
-------------------------------------------------------------------------------
local insert = table.insert local insert = table.insert
local ipairs = ipairs local ipairs = ipairs
@ -65,24 +26,18 @@ Region._VERSION = "Region 11.3"
Region.__index = Region Region.__index = Region
-------------------------------------------------------------------------------
-- x0, y0, x1, y1 = Region.intersect(d1, d2, d3, d4, s1, s2, s3, s4): -- 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 -- Returns the coordinates of a rectangle where a rectangle specified by
-- the coordinates s1, s2, s3, s4 overlaps with the rectangle specified -- the coordinates s1, s2, s3, s4 overlaps with the rectangle specified
-- by the coordinates d1, d2, d3, d4. The return value is '''nil''' if -- by the coordinates d1, d2, d3, d4. The return value is '''nil''' if
-- the rectangles do not overlap. -- the rectangles do not overlap.
-------------------------------------------------------------------------------
function Region.intersect(d1, d2, d3, d4, s1, s2, s3, s4) function Region.intersect(d1, d2, d3, d4, s1, s2, s3, s4)
if s3 >= d1 and s1 <= d3 and s4 >= d2 and s2 <= d4 then 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) return max(s1, d1), max(s2, d2), min(s3, d3), min(s4, d4)
end end
end end
-------------------------------------------------------------------------------
-- insertrect: insert rect to table, merging with an existing one if possible -- insertrect: insert rect to table, merging with an existing one if possible
-------------------------------------------------------------------------------
local function insertrect(d, s1, s2, s3, s4) local function insertrect(d, s1, s2, s3, s4)
for i = 1, min(4, #d) do for i = 1, min(4, #d) do
local a = d[i] local a = d[i]
@ -108,10 +63,7 @@ local function insertrect(d, s1, s2, s3, s4)
insert(d, 1, { s1, s2, s3, s4 }) insert(d, 1, { s1, s2, s3, s4 })
end end
-------------------------------------------------------------------------------
-- cutrect: cut rect d into table of new rects, using rect s as a punch -- 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) local function cutrect(d1, d2, d3, d4, s1, s2, s3, s4)
if not Region.intersect(d1, d2, d3, d4, s1, s2, s3, s4) then if not Region.intersect(d1, d2, d3, d4, s1, s2, s3, s4) then
return { { d1, d2, d3, d4 } } return { { d1, d2, d3, d4 } }
@ -135,10 +87,7 @@ local function cutrect(d1, d2, d3, d4, s1, s2, s3, s4)
return r return r
end end
-------------------------------------------------------------------------------
-- cutregion: cut region d, using s as a punch -- cutregion: cut region d, using s as a punch
-------------------------------------------------------------------------------
local function cutregion(d, s1, s2, s3, s4) local function cutregion(d, s1, s2, s3, s4)
local r = { } local r = { }
for _, dr in ipairs(d) do for _, dr in ipairs(d) do
@ -150,11 +99,8 @@ local function cutregion(d, s1, s2, s3, s4)
return r return r
end end
-------------------------------------------------------------------------------
-- region = Region.new(r1, r2, r3, r4): Creates a new region from the given -- region = Region.new(r1, r2, r3, r4): Creates a new region from the given
-- coordinates. -- coordinates.
-------------------------------------------------------------------------------
function Region.new(r1, r2, r3, r4) function Region.new(r1, r2, r3, r4)
if r1 then if r1 then
return setmetatable({ region = { { r1, r2, r3, r4 } } }, Region) return setmetatable({ region = { { r1, r2, r3, r4 } } }, Region)
@ -162,39 +108,27 @@ function Region.new(r1, r2, r3, r4)
return setmetatable({ region = { } }, Region) return setmetatable({ region = { } }, Region)
end end
-------------------------------------------------------------------------------
-- self = region:setRect(r1, r2, r3, r4): Resets an existing region -- self = region:setRect(r1, r2, r3, r4): Resets an existing region
-- to the specified rectangle. -- to the specified rectangle.
-------------------------------------------------------------------------------
function Region:setRect(r1, r2, r3, r4) function Region:setRect(r1, r2, r3, r4)
self.region = { { r1, r2, r3, r4 } } self.region = { { r1, r2, r3, r4 } }
return self return self
end end
-------------------------------------------------------------------------------
-- region:orRect(r1, r2, r3, r4): Logical ''or''s a rectangle to a region -- region:orRect(r1, r2, r3, r4): Logical ''or''s a rectangle to a region
-------------------------------------------------------------------------------
function Region:orRect(s1, s2, s3, s4) function Region:orRect(s1, s2, s3, s4)
self.region = cutregion(self.region, s1, s2, s3, s4) self.region = cutregion(self.region, s1, s2, s3, s4)
insertrect(self.region, s1, s2, s3, s4) insertrect(self.region, s1, s2, s3, s4)
end end
-------------------------------------------------------------------------------
-- region:orRegion(region): Logical ''or''s another region to a region -- region:orRegion(region): Logical ''or''s another region to a region
-------------------------------------------------------------------------------
function Region:orRegion(s) function Region:orRegion(s)
for _, r in ipairs(s) do for _, r in ipairs(s) do
self:orRect(r[1], r[2], r[3], r[4]) self:orRect(r[1], r[2], r[3], r[4])
end end
end end
-------------------------------------------------------------------------------
-- region:andRect(r1, r2, r3, r4): Logical ''and''s a rectange to a region -- region:andRect(r1, r2, r3, r4): Logical ''and''s a rectange to a region
-------------------------------------------------------------------------------
function Region:andRect(s1, s2, s3, s4) function Region:andRect(s1, s2, s3, s4)
local r = { } local r = { }
for _, d in ipairs(self.region) do for _, d in ipairs(self.region) do
@ -207,10 +141,7 @@ function Region:andRect(s1, s2, s3, s4)
self.region = r self.region = r
end end
-------------------------------------------------------------------------------
-- region:xorRect(r1, r2, r3, r4): Logical ''xor''s a rectange to a region -- region:xorRect(r1, r2, r3, r4): Logical ''xor''s a rectange to a region
-------------------------------------------------------------------------------
function Region:xorRect(s1, s2, s3, s4) function Region:xorRect(s1, s2, s3, s4)
local r1 = { } local r1 = { }
local r2 = { { s1, s2, s3, s4 } } local r2 = { { s1, s2, s3, s4 } }
@ -225,10 +156,7 @@ function Region:xorRect(s1, s2, s3, s4)
self:orRegion(r2) self:orRegion(r2)
end end
-------------------------------------------------------------------------------
-- self = region:subRect(r1, r2, r3, r4): Subtracts a rectangle from a region -- self = region:subRect(r1, r2, r3, r4): Subtracts a rectangle from a region
-------------------------------------------------------------------------------
function Region:subRect(s1, s2, s3, s4) function Region:subRect(s1, s2, s3, s4)
local r1 = { } local r1 = { }
for _, d in ipairs(self.region) do for _, d in ipairs(self.region) do
@ -241,10 +169,7 @@ function Region:subRect(s1, s2, s3, s4)
return self return self
end end
-------------------------------------------------------------------------------
-- region:getRect - gets an iterator on the rectangles in a region [internal] -- region:getRect - gets an iterator on the rectangles in a region [internal]
-------------------------------------------------------------------------------
function Region:getRects() function Region:getRects()
local index = 0 local index = 0
return function(object) return function(object)
@ -255,12 +180,9 @@ function Region:getRects()
end, self.region end, self.region
end end
-------------------------------------------------------------------------------
-- success = region:checkIntersect(x0, y0, x1, y1): Returns a boolean -- success = region:checkIntersect(x0, y0, x1, y1): Returns a boolean
-- indicating whether a rectangle specified by its coordinates overlaps -- indicating whether a rectangle specified by its coordinates overlaps
-- with a region. -- with a region.
-------------------------------------------------------------------------------
function Region:checkIntersect(s1, s2, s3, s4) function Region:checkIntersect(s1, s2, s3, s4)
for _, d in ipairs(self.region) do for _, d in ipairs(self.region) do
if Region.intersect(d[1], d[2], d[3], d[4], s1, s2, s3, s4) then 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 return false
end end
-------------------------------------------------------------------------------
-- region:subRegion(region2): Subtracts {{region2}} from {{region}}. -- region:subRegion(region2): Subtracts {{region2}} from {{region}}.
-------------------------------------------------------------------------------
function Region:subRegion(region) function Region:subRegion(region)
if region then if region then
for r1, r2, r3, r4 in region:getRects() do for r1, r2, r3, r4 in region:getRects() do
@ -282,10 +201,7 @@ function Region:subRegion(region)
end end
end end
-------------------------------------------------------------------------------
-- region:andRegion(r): Logically ''and''s a region to a region -- region:andRegion(r): Logically ''and''s a region to a region
-------------------------------------------------------------------------------
function Region:andRegion(s) function Region:andRegion(s)
local r = { } local r = { }
for _, s in ipairs(s.region) do for _, s in ipairs(s.region) do
@ -301,23 +217,17 @@ function Region:andRegion(s)
self.region = r self.region = r
end end
-------------------------------------------------------------------------------
-- region:forEach(func, obj, ...): For each rectangle in a region, calls the -- region:forEach(func, obj, ...): For each rectangle in a region, calls the
-- specified function according the following scheme: -- specified function according the following scheme:
-- func(obj, x0, y0, x1, y1, ...) -- func(obj, x0, y0, x1, y1, ...)
-- Extra arguments are passed through to the function. -- Extra arguments are passed through to the function.
-------------------------------------------------------------------------------
function Region:forEach(func, obj, ...) function Region:forEach(func, obj, ...)
for x0, y0, x1, y1 in self:getRects() do for x0, y0, x1, y1 in self:getRects() do
func(obj, x0, y0, x1, y1, ...) func(obj, x0, y0, x1, y1, ...)
end end
end end
-------------------------------------------------------------------------------
-- region:shift(dx, dy): Shifts a region by delta x and y. -- region:shift(dx, dy): Shifts a region by delta x and y.
-------------------------------------------------------------------------------
function Region:shift(dx, dy) function Region:shift(dx, dy)
for _, r in ipairs(self.region) do for _, r in ipairs(self.region) do
r[1] = r[1] + dx r[1] = r[1] + dx
@ -327,18 +237,12 @@ function Region:shift(dx, dy)
end end
end end
-------------------------------------------------------------------------------
-- region:isEmpty(): Returns '''true''' if a region is empty. -- region:isEmpty(): Returns '''true''' if a region is empty.
-------------------------------------------------------------------------------
function Region:isEmpty() function Region:isEmpty()
return #self.region == 0 return #self.region == 0
end end
-------------------------------------------------------------------------------
-- minx, miny, maxx, maxy = region:get(): Get region's min/max extents -- minx, miny, maxx, maxy = region:get(): Get region's min/max extents
-------------------------------------------------------------------------------
function Region:get() function Region:get()
if #self.region > 0 then if #self.region > 0 then
local minx = 1000000 -- ui.HUGE local minx = 1000000 -- ui.HUGE

View File

@ -3,88 +3,88 @@ local Tween = require('ui.tween')
local Transition = { } local Transition = { }
function Transition.slideLeft(args) function Transition.slideLeft(args)
local ticks = args.ticks or 6 local ticks = args.ticks or 6
local easing = args.easing or 'outQuint' local easing = args.easing or 'outQuint'
local pos = { x = args.ex } local pos = { x = args.ex }
local tween = Tween.new(ticks, pos, { x = args.x }, easing) local tween = Tween.new(ticks, pos, { x = args.x }, easing)
local lastScreen = args.canvas:copy() local lastScreen = args.canvas:copy()
return function(device) return function(device)
local finished = tween:update(1) local finished = tween:update(1)
local x = math.floor(pos.x) local x = math.floor(pos.x)
lastScreen:dirty() lastScreen:dirty()
lastScreen:blit(device, { lastScreen:blit(device, {
x = args.ex - x + args.x, x = args.ex - x + args.x,
y = args.y, y = args.y,
ex = args.ex, ex = args.ex,
ey = args.ey }, ey = args.ey },
{ x = args.x, y = args.y }) { x = args.x, y = args.y })
args.canvas:blit(device, { args.canvas:blit(device, {
x = args.x, x = args.x,
y = args.y, y = args.y,
ex = args.ex - x + args.x, ex = args.ex - x + args.x,
ey = args.ey }, ey = args.ey },
{ x = x, y = args.y }) { x = x, y = args.y })
return not finished return not finished
end end
end end
function Transition.slideRight(args) function Transition.slideRight(args)
local ticks = args.ticks or 6 local ticks = args.ticks or 6
local easing = args.easing or'outQuint' local easing = args.easing or'outQuint'
local pos = { x = args.x } local pos = { x = args.x }
local tween = Tween.new(ticks, pos, { x = args.ex }, easing) local tween = Tween.new(ticks, pos, { x = args.ex }, easing)
local lastScreen = args.canvas:copy() local lastScreen = args.canvas:copy()
return function(device) return function(device)
local finished = tween:update(1) local finished = tween:update(1)
local x = math.floor(pos.x) local x = math.floor(pos.x)
lastScreen:dirty() lastScreen:dirty()
lastScreen:blit(device, { lastScreen:blit(device, {
x = args.x, x = args.x,
y = args.y, y = args.y,
ex = args.ex - x + args.x, ex = args.ex - x + args.x,
ey = args.ey }, ey = args.ey },
{ x = x, y = args.y }) { x = x, y = args.y })
args.canvas:blit(device, { args.canvas:blit(device, {
x = args.ex - x + args.x, x = args.ex - x + args.x,
y = args.y, y = args.y,
ex = args.ex, ex = args.ex,
ey = args.ey }, ey = args.ey },
{ x = args.x, y = args.y }) { x = args.x, y = args.y })
return not finished return not finished
end end
end end
function Transition.expandUp(args) function Transition.expandUp(args)
local ticks = args.ticks or 3 local ticks = args.ticks or 3
local easing = args.easing or 'linear' local easing = args.easing or 'linear'
local pos = { y = args.ey + 1 } local pos = { y = args.ey + 1 }
local tween = Tween.new(ticks, pos, { y = args.y }, easing) local tween = Tween.new(ticks, pos, { y = args.y }, easing)
return function(device) return function(device)
local finished = tween:update(1) local finished = tween:update(1)
args.canvas:blit(device, nil, { x = args.x, y = math.floor(pos.y) }) args.canvas:blit(device, nil, { x = args.x, y = math.floor(pos.y) })
return not finished return not finished
end end
end end
function Transition.grow(args) function Transition.grow(args)
local ticks = args.ticks or 3 local ticks = args.ticks or 3
local easing = args.easing or 'linear' local easing = args.easing or 'linear'
local tween = Tween.new(ticks, local tween = Tween.new(ticks,
{ x = args.width / 2 - 1, y = args.height / 2 - 1, w = 1, h = 1 }, { 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) { x = 1, y = 1, w = args.width, h = args.height }, easing)
return function(device) return function(device)
local finished = tween:update(1) local finished = tween:update(1)
local subj = tween.subject local subj = tween.subject
local rect = { x = math.floor(subj.x), y = math.floor(subj.y) } local rect = { x = math.floor(subj.x), y = math.floor(subj.y) }
rect.ex = math.floor(rect.x + subj.w - 1) rect.ex = math.floor(rect.x + subj.w - 1)
rect.ey = math.floor(rect.y + subj.h - 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}) args.canvas:blit(device, rect, { x = args.x + rect.x - 1, y = args.y + rect.y - 1})
return not finished return not finished
end end
end end
return Transition return Transition

View File

@ -1,31 +1,14 @@
local tween = { local tween = {
_VERSION = 'tween 2.1.1', _VERSION = 'tween 2.1.1',
_DESCRIPTION = 'tweening for lua', _DESCRIPTION = 'tweening for lua',
_URL = 'https://github.com/kikito/tween.lua', _URL = 'https://github.com/kikito/tween.lua',
_LICENSE = [[ _LICENSE = [[
MIT 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 Licence details: https://opensource.org/licenses/MIT
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.
]]
} }
-- easing -- easing
@ -45,57 +28,57 @@ local function linear(t, b, c, d) return c * t / d + b end
-- quad -- quad
local function inQuad(t, b, c, d) return c * pow(t / d, 2) + b end local function inQuad(t, b, c, d) return c * pow(t / d, 2) + b end
local function outQuad(t, b, c, d) local function outQuad(t, b, c, d)
t = t / d t = t / d
return -c * t * (t - 2) + b return -c * t * (t - 2) + b
end end
local function inOutQuad(t, b, c, d) local function inOutQuad(t, b, c, d)
t = t / d * 2 t = t / d * 2
if t < 1 then return c / 2 * pow(t, 2) + b end if t < 1 then return c / 2 * pow(t, 2) + b end
return -c / 2 * ((t - 1) * (t - 3) - 1) + b return -c / 2 * ((t - 1) * (t - 3) - 1) + b
end end
local function outInQuad(t, b, c, d) local function outInQuad(t, b, c, d)
if t < d / 2 then return outQuad(t * 2, b, c / 2, d) end 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) return inQuad((t * 2) - d, b + c / 2, c / 2, d)
end end
-- cubic -- cubic
local function inCubic (t, b, c, d) return c * pow(t / d, 3) + b end 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 outCubic(t, b, c, d) return c * (pow(t / d - 1, 3) + 1) + b end
local function inOutCubic(t, b, c, d) local function inOutCubic(t, b, c, d)
t = t / d * 2 t = t / d * 2
if t < 1 then return c / 2 * t * t * t + b end if t < 1 then return c / 2 * t * t * t + b end
t = t - 2 t = t - 2
return c / 2 * (t * t * t + 2) + b return c / 2 * (t * t * t + 2) + b
end end
local function outInCubic(t, b, c, d) local function outInCubic(t, b, c, d)
if t < d / 2 then return outCubic(t * 2, b, c / 2, d) end 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) return inCubic((t * 2) - d, b + c / 2, c / 2, d)
end end
-- quart -- quart
local function inQuart(t, b, c, d) return c * pow(t / d, 4) + b end 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 outQuart(t, b, c, d) return -c * (pow(t / d - 1, 4) - 1) + b end
local function inOutQuart(t, b, c, d) local function inOutQuart(t, b, c, d)
t = t / d * 2 t = t / d * 2
if t < 1 then return c / 2 * pow(t, 4) + b end if t < 1 then return c / 2 * pow(t, 4) + b end
return -c / 2 * (pow(t - 2, 4) - 2) + b return -c / 2 * (pow(t - 2, 4) - 2) + b
end end
local function outInQuart(t, b, c, d) local function outInQuart(t, b, c, d)
if t < d / 2 then return outQuart(t * 2, b, c / 2, d) end 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) return inQuart((t * 2) - d, b + c / 2, c / 2, d)
end end
-- quint -- quint
local function inQuint(t, b, c, d) return c * pow(t / d, 5) + b end 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 outQuint(t, b, c, d) return c * (pow(t / d - 1, 5) + 1) + b end
local function inOutQuint(t, b, c, d) local function inOutQuint(t, b, c, d)
t = t / d * 2 t = t / d * 2
if t < 1 then return c / 2 * pow(t, 5) + b end if t < 1 then return c / 2 * pow(t, 5) + b end
return c / 2 * (pow(t - 2, 5) + 2) + b return c / 2 * (pow(t - 2, 5) + 2) + b
end end
local function outInQuint(t, b, c, d) local function outInQuint(t, b, c, d)
if t < d / 2 then return outQuint(t * 2, b, c / 2, d) end 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) return inQuint((t * 2) - d, b + c / 2, c / 2, d)
end end
-- sine -- 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 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 inOutSine(t, b, c, d) return -c / 2 * (cos(pi * t / d) - 1) + b end
local function outInSine(t, b, c, d) local function outInSine(t, b, c, d)
if t < d / 2 then return outSine(t * 2, b, c / 2, d) end 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) return inSine((t * 2) -d, b + c / 2, c / 2, d)
end end
-- expo -- expo
local function inExpo(t, b, c, d) local function inExpo(t, b, c, d)
if t == 0 then return b end if t == 0 then return b end
return c * pow(2, 10 * (t / d - 1)) + b - c * 0.001 return c * pow(2, 10 * (t / d - 1)) + b - c * 0.001
end end
local function outExpo(t, b, c, d) local function outExpo(t, b, c, d)
if t == d then return b + c end if t == d then return b + c end
return c * 1.001 * (-pow(2, -10 * t / d) + 1) + b return c * 1.001 * (-pow(2, -10 * t / d) + 1) + b
end end
local function inOutExpo(t, b, c, d) local function inOutExpo(t, b, c, d)
if t == 0 then return b end if t == 0 then return b end
if t == d then return b + c end if t == d then return b + c end
t = t / d * 2 t = t / d * 2
if t < 1 then return c / 2 * pow(2, 10 * (t - 1)) + b - c * 0.0005 end 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 return c / 2 * 1.0005 * (-pow(2, -10 * (t - 1)) + 2) + b
end end
local function outInExpo(t, b, c, d) local function outInExpo(t, b, c, d)
if t < d / 2 then return outExpo(t * 2, b, c / 2, d) end 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) return inExpo((t * 2) - d, b + c / 2, c / 2, d)
end end
-- circ -- circ
local function inCirc(t, b, c, d) return(-c * (sqrt(1 - pow(t / d, 2)) - 1) + b) end 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 outCirc(t, b, c, d) return(c * sqrt(1 - pow(t / d - 1, 2)) + b) end
local function inOutCirc(t, b, c, d) local function inOutCirc(t, b, c, d)
t = t / d * 2 t = t / d * 2
if t < 1 then return -c / 2 * (sqrt(1 - t * t) - 1) + b end if t < 1 then return -c / 2 * (sqrt(1 - t * t) - 1) + b end
t = t - 2 t = t - 2
return c / 2 * (sqrt(1 - t * t) + 1) + b return c / 2 * (sqrt(1 - t * t) + 1) + b
end end
local function outInCirc(t, b, c, d) local function outInCirc(t, b, c, d)
if t < d / 2 then return outCirc(t * 2, b, c / 2, d) end 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) return inCirc((t * 2) - d, b + c / 2, c / 2, d)
end end
-- elastic -- elastic
local function calculatePAS(p,a,c,d) local function calculatePAS(p,a,c,d)
p, a = p or d * 0.3, a or 0 p, a = p or d * 0.3, a or 0
if a < abs(c) then return p, c, p / 4 end -- p, a, s 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 return p, a, p / (2 * pi) * asin(c/a) -- p,a,s
end end
local function inElastic(t, b, c, d, a, p) local function inElastic(t, b, c, d, a, p)
local s local s
if t == 0 then return b end if t == 0 then return b end
t = t / d t = t / d
if t == 1 then return b + c end if t == 1 then return b + c end
p,a,s = calculatePAS(p,a,c,d) p,a,s = calculatePAS(p,a,c,d)
t = t - 1 t = t - 1
return -(a * pow(2, 10 * t) * sin((t * d - s) * (2 * pi) / p)) + b return -(a * pow(2, 10 * t) * sin((t * d - s) * (2 * pi) / p)) + b
end end
local function outElastic(t, b, c, d, a, p) local function outElastic(t, b, c, d, a, p)
local s local s
if t == 0 then return b end if t == 0 then return b end
t = t / d t = t / d
if t == 1 then return b + c end if t == 1 then return b + c end
p,a,s = calculatePAS(p,a,c,d) p,a,s = calculatePAS(p,a,c,d)
return a * pow(2, -10 * t) * sin((t * d - s) * (2 * pi) / p) + c + b return a * pow(2, -10 * t) * sin((t * d - s) * (2 * pi) / p) + c + b
end end
local function inOutElastic(t, b, c, d, a, p) local function inOutElastic(t, b, c, d, a, p)
local s local s
if t == 0 then return b end if t == 0 then return b end
t = t / d * 2 t = t / d * 2
if t == 2 then return b + c end if t == 2 then return b + c end
p,a,s = calculatePAS(p,a,c,d) p,a,s = calculatePAS(p,a,c,d)
t = t - 1 t = t - 1
if t < 0 then return -0.5 * (a * pow(2, 10 * t) * sin((t * d - s) * (2 * pi) / p)) + b end 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 return a * pow(2, -10 * t) * sin((t * d - s) * (2 * pi) / p ) * 0.5 + c + b
end end
local function outInElastic(t, b, c, d, a, p) 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 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) return inElastic((t * 2) - d, b + c / 2, c / 2, d, a, p)
end end
-- back -- back
local function inBack(t, b, c, d, s) local function inBack(t, b, c, d, s)
s = s or 1.70158 s = s or 1.70158
t = t / d t = t / d
return c * t * t * ((s + 1) * t - s) + b return c * t * t * ((s + 1) * t - s) + b
end end
local function outBack(t, b, c, d, s) local function outBack(t, b, c, d, s)
s = s or 1.70158 s = s or 1.70158
t = t / d - 1 t = t / d - 1
return c * (t * t * ((s + 1) * t + s) + 1) + b return c * (t * t * ((s + 1) * t + s) + 1) + b
end end
local function inOutBack(t, b, c, d, s) local function inOutBack(t, b, c, d, s)
s = (s or 1.70158) * 1.525 s = (s or 1.70158) * 1.525
t = t / d * 2 t = t / d * 2
if t < 1 then return c / 2 * (t * t * ((s + 1) * t - s)) + b end if t < 1 then return c / 2 * (t * t * ((s + 1) * t - s)) + b end
t = t - 2 t = t - 2
return c / 2 * (t * t * ((s + 1) * t + s) + 2) + b return c / 2 * (t * t * ((s + 1) * t + s) + 2) + b
end end
local function outInBack(t, b, c, d, s) local function outInBack(t, b, c, d, s)
if t < d / 2 then return outBack(t * 2, b, c / 2, d, s) end 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) return inBack((t * 2) - d, b + c / 2, c / 2, d, s)
end end
-- bounce -- bounce
local function outBounce(t, b, c, d) local function outBounce(t, b, c, d)
t = t / d t = t / d
if t < 1 / 2.75 then return c * (7.5625 * t * t) + b end if t < 1 / 2.75 then return c * (7.5625 * t * t) + b end
if t < 2 / 2.75 then if t < 2 / 2.75 then
t = t - (1.5 / 2.75) t = t - (1.5 / 2.75)
return c * (7.5625 * t * t + 0.75) + b return c * (7.5625 * t * t + 0.75) + b
elseif t < 2.5 / 2.75 then elseif t < 2.5 / 2.75 then
t = t - (2.25 / 2.75) t = t - (2.25 / 2.75)
return c * (7.5625 * t * t + 0.9375) + b return c * (7.5625 * t * t + 0.9375) + b
end end
t = t - (2.625 / 2.75) t = t - (2.625 / 2.75)
return c * (7.5625 * t * t + 0.984375) + b return c * (7.5625 * t * t + 0.984375) + b
end end
local function inBounce(t, b, c, d) return c - outBounce(d - t, 0, c, d) + 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) local function inOutBounce(t, b, c, d)
if t < d / 2 then return inBounce(t * 2, 0, c, d) * 0.5 + b end 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 return outBounce(t * 2 - d, 0, c, d) * 0.5 + c * .5 + b
end end
local function outInBounce(t, b, c, d) local function outInBounce(t, b, c, d)
if t < d / 2 then return outBounce(t * 2, b, c / 2, d) end 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) return inBounce((t * 2) - d, b + c / 2, c / 2, d)
end end
tween.easing = { tween.easing = {
linear = linear, linear = linear,
inQuad = inQuad, outQuad = outQuad, inOutQuad = inOutQuad, outInQuad = outInQuad, inQuad = inQuad, outQuad = outQuad, inOutQuad = inOutQuad, outInQuad = outInQuad,
inCubic = inCubic, outCubic = outCubic, inOutCubic = inOutCubic, outInCubic = outInCubic, inCubic = inCubic, outCubic = outCubic, inOutCubic = inOutCubic, outInCubic = outInCubic,
inQuart = inQuart, outQuart = outQuart, inOutQuart = inOutQuart, outInQuart = outInQuart, inQuart = inQuart, outQuart = outQuart, inOutQuart = inOutQuart, outInQuart = outInQuart,
inQuint = inQuint, outQuint = outQuint, inOutQuint = inOutQuint, outInQuint = outInQuint, inQuint = inQuint, outQuint = outQuint, inOutQuint = inOutQuint, outInQuint = outInQuint,
inSine = inSine, outSine = outSine, inOutSine = inOutSine, outInSine = outInSine, inSine = inSine, outSine = outSine, inOutSine = inOutSine, outInSine = outInSine,
inExpo = inExpo, outExpo = outExpo, inOutExpo = inOutExpo, outInExpo = outInExpo, inExpo = inExpo, outExpo = outExpo, inOutExpo = inOutExpo, outInExpo = outInExpo,
inCirc = inCirc, outCirc = outCirc, inOutCirc = inOutCirc, outInCirc = outInCirc, inCirc = inCirc, outCirc = outCirc, inOutCirc = inOutCirc, outInCirc = outInCirc,
inElastic = inElastic, outElastic = outElastic, inOutElastic = inOutElastic, outInElastic = outInElastic, inElastic = inElastic, outElastic = outElastic, inOutElastic = inOutElastic, outInElastic = outInElastic,
inBack = inBack, outBack = outBack, inOutBack = inOutBack, outInBack = outInBack, inBack = inBack, outBack = outBack, inOutBack = inOutBack, outInBack = outInBack,
inBounce = inBounce, outBounce = outBounce, inOutBounce = inOutBounce, outInBounce = outInBounce inBounce = inBounce, outBounce = outBounce, inOutBounce = inOutBounce, outInBounce = outInBounce
} }
@ -246,68 +229,68 @@ tween.easing = {
-- private stuff -- private stuff
local function copyTables(destination, keysTable, valuesTable) local function copyTables(destination, keysTable, valuesTable)
valuesTable = valuesTable or keysTable valuesTable = valuesTable or keysTable
local mt = getmetatable(keysTable) local mt = getmetatable(keysTable)
if mt and getmetatable(destination) == nil then if mt and getmetatable(destination) == nil then
setmetatable(destination, mt) setmetatable(destination, mt)
end end
for k,v in pairs(keysTable) do for k,v in pairs(keysTable) do
if type(v) == 'table' then if type(v) == 'table' then
destination[k] = copyTables({}, v, valuesTable[k]) destination[k] = copyTables({}, v, valuesTable[k])
else else
destination[k] = valuesTable[k] destination[k] = valuesTable[k]
end end
end end
return destination return destination
end end
local function checkSubjectAndTargetRecursively(subject, target, path) local function checkSubjectAndTargetRecursively(subject, target, path)
path = path or {} path = path or {}
local targetType, newPath local targetType, newPath
for k,targetValue in pairs(target) do for k,targetValue in pairs(target) do
targetType, newPath = type(targetValue), copyTables({}, path) targetType, newPath = type(targetValue), copyTables({}, path)
table.insert(newPath, tostring(k)) table.insert(newPath, tostring(k))
if targetType == 'number' then if targetType == 'number' then
assert(type(subject[k]) == 'number', "Parameter '" .. table.concat(newPath,'/') .. "' is missing from subject or isn't a number") assert(type(subject[k]) == 'number', "Parameter '" .. table.concat(newPath,'/') .. "' is missing from subject or isn't a number")
elseif targetType == 'table' then elseif targetType == 'table' then
checkSubjectAndTargetRecursively(subject[k], targetValue, newPath) checkSubjectAndTargetRecursively(subject[k], targetValue, newPath)
else else
assert(targetType == 'number', "Parameter '" .. table.concat(newPath,'/') .. "' must be a number or table of numbers") assert(targetType == 'number', "Parameter '" .. table.concat(newPath,'/') .. "' must be a number or table of numbers")
end end
end end
end end
local function checkNewParams(duration, subject, target, easing) local function checkNewParams(duration, subject, target, easing)
assert(type(duration) == 'number' and duration > 0, "duration must be a positive number. Was " .. tostring(duration)) assert(type(duration) == 'number' and duration > 0, "duration must be a positive number. Was " .. tostring(duration))
local tsubject = type(subject) local tsubject = type(subject)
assert(tsubject == 'table' or tsubject == 'userdata', "subject must be a table or userdata. Was " .. tostring(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(target)== 'table', "target must be a table. Was " .. tostring(target))
assert(type(easing)=='function', "easing must be a function. Was " .. tostring(easing)) assert(type(easing)=='function', "easing must be a function. Was " .. tostring(easing))
checkSubjectAndTargetRecursively(subject, target) checkSubjectAndTargetRecursively(subject, target)
end end
local function getEasingFunction(easing) local function getEasingFunction(easing)
easing = easing or "linear" easing = easing or "linear"
if type(easing) == 'string' then if type(easing) == 'string' then
local name = easing local name = easing
easing = tween.easing[name] easing = tween.easing[name]
if type(easing) ~= 'function' then if type(easing) ~= 'function' then
error("The easing function name '" .. name .. "' is invalid") error("The easing function name '" .. name .. "' is invalid")
end end
end end
return easing return easing
end end
local function performEasingOnSubject(subject, target, initial, clock, duration, easing) local function performEasingOnSubject(subject, target, initial, clock, duration, easing)
local t,b,c,d local t,b,c,d
for k,v in pairs(target) do for k,v in pairs(target) do
if type(v) == 'table' then if type(v) == 'table' then
performEasingOnSubject(subject[k], v, initial[k], clock, duration, easing) performEasingOnSubject(subject[k], v, initial[k], clock, duration, easing)
else else
t,b,c,d = clock, initial[k], v - initial[k], duration t,b,c,d = clock, initial[k], v - initial[k], duration
subject[k] = easing(t,b,c,d) subject[k] = easing(t,b,c,d)
end end
end end
end end
-- Tween methods -- Tween methods
@ -316,52 +299,52 @@ local Tween = {}
local Tween_mt = {__index = Tween} local Tween_mt = {__index = Tween}
function Tween:set(clock) 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.initial = self.initial or copyTables({}, self.target, self.subject)
self.clock = clock self.clock = clock
if self.clock <= 0 then if self.clock <= 0 then
self.clock = 0 self.clock = 0
copyTables(self.subject, self.initial) 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 self.clock = self.duration
copyTables(self.subject, self.target) 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 end
function Tween:reset() function Tween:reset()
return self:set(0) return self:set(0)
end end
function Tween:update(dt) function Tween:update(dt)
assert(type(dt) == 'number', "dt must be a number") assert(type(dt) == 'number', "dt must be a number")
return self:set(self.clock + dt) return self:set(self.clock + dt)
end end
-- Public interface -- Public interface
function tween.new(duration, subject, target, easing) function tween.new(duration, subject, target, easing)
easing = getEasingFunction(easing) easing = getEasingFunction(easing)
checkNewParams(duration, subject, target, easing) checkNewParams(duration, subject, target, easing)
return setmetatable({ return setmetatable({
duration = duration, duration = duration,
subject = subject, subject = subject,
target = target, target = target,
easing = easing, easing = easing,
clock = 0 clock = 0
}, Tween_mt) }, Tween_mt)
end end
return tween return tween

File diff suppressed because it is too large Load Diff

View File

@ -1,4 +1,4 @@
_G.requireInjector() _G.requireInjector(_ENV)
local UI = require('ui') local UI = require('ui')
local Util = require('util') local Util = require('util')
@ -10,89 +10,89 @@ UI:configure('Help', ...)
local topics = { } local topics = { }
for _,topic in pairs(help.topics()) do for _,topic in pairs(help.topics()) do
if help.lookup(topic) then if help.lookup(topic) then
table.insert(topics, { name = topic }) table.insert(topics, { name = topic })
end end
end end
local page = UI.Page { local page = UI.Page {
labelText = UI.Text { labelText = UI.Text {
x = 3, y = 2, x = 3, y = 2,
value = 'Search', value = 'Search',
}, },
filter = UI.TextEntry { filter = UI.TextEntry {
x = 10, y = 2, ex = -3, x = 10, y = 2, ex = -3,
limit = 32, limit = 32,
}, },
grid = UI.ScrollingGrid { grid = UI.ScrollingGrid {
y = 4, y = 4,
values = topics, values = topics,
columns = { columns = {
{ heading = 'Topic', key = 'name' }, { heading = 'Topic', key = 'name' },
}, },
sortColumn = 'name', sortColumn = 'name',
}, },
accelerators = { accelerators = {
q = 'quit', q = 'quit',
enter = 'grid_select', enter = 'grid_select',
}, },
} }
local topicPage = UI.Page { local topicPage = UI.Page {
backgroundColor = colors.black, backgroundColor = colors.black,
titleBar = UI.TitleBar { titleBar = UI.TitleBar {
title = 'text', title = 'text',
previousPage = true, previousPage = true,
}, },
helpText = UI.TextArea { helpText = UI.TextArea {
backgroundColor = colors.black, backgroundColor = colors.black,
x = 2, ex = -1, y = 3, ey = -2, x = 2, ex = -1, y = 3, ey = -2,
}, },
accelerators = { accelerators = {
q = 'back', q = 'back',
backspace = 'back', backspace = 'back',
}, },
} }
function topicPage:eventHandler(event) function topicPage:eventHandler(event)
if event.type == 'back' then if event.type == 'back' then
UI:setPreviousPage() UI:setPreviousPage()
end end
return UI.Page.eventHandler(self, event) return UI.Page.eventHandler(self, event)
end end
function page:eventHandler(event) function page:eventHandler(event)
if event.type == 'quit' then if event.type == 'quit' then
UI:exitPullEvents() UI:exitPullEvents()
elseif event.type == 'grid_select' then elseif event.type == 'grid_select' then
if self.grid:getSelected() then if self.grid:getSelected() then
local name = self.grid:getSelected().name local name = self.grid:getSelected().name
local f = help.lookup(name) local f = help.lookup(name)
topicPage.titleBar.title = name topicPage.titleBar.title = name
topicPage.helpText:setText(Util.readFile(f)) topicPage.helpText:setText(Util.readFile(f))
UI:setPage(topicPage) UI:setPage(topicPage)
end end
elseif event.type == 'text_change' then elseif event.type == 'text_change' then
if #event.text == 0 then if #event.text == 0 then
self.grid.values = topics self.grid.values = topics
else else
self.grid.values = { } self.grid.values = { }
for _,f in pairs(topics) do for _,f in pairs(topics) do
if string.find(f.name, event.text) then if string.find(f.name, event.text) then
table.insert(self.grid.values, f) table.insert(self.grid.values, f)
end end
end end
end end
self.grid:update() self.grid:update()
self.grid:setIndex(1) self.grid:setIndex(1)
self.grid:draw() self.grid:draw()
else else
return UI.Page.eventHandler(self, event) return UI.Page.eventHandler(self, event)
end end
end end
UI:setPage(page) UI:setPage(page)

456
sys/apps/Installer.lua Normal file
View File

@ -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

View File

@ -1,6 +1,5 @@
_G.requireInjector(_ENV) _G.requireInjector(_ENV)
local Event = require('event')
local History = require('history') local History = require('history')
local UI = require('ui') local UI = require('ui')
local Util = require('util') local Util = require('util')
@ -10,8 +9,10 @@ local os = _G.os
local textutils = _G.textutils local textutils = _G.textutils
local term = _G.term local term = _G.term
local _exit
local sandboxEnv = setmetatable(Util.shallowCopy(_ENV), { __index = _G }) 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 sandboxEnv._echo = function( ... ) return { ... } end
_G.requireInjector(sandboxEnv) _G.requireInjector(sandboxEnv)
@ -19,7 +20,6 @@ UI:configure('Lua', ...)
local command = '' local command = ''
local history = History.load('usr/.lua_history', 25) local history = History.load('usr/.lua_history', 25)
local extChars = Util.getVersion() > 1.76
local page = UI.Page { local page = UI.Page {
menuBar = UI.MenuBar { menuBar = UI.MenuBar {
@ -41,10 +41,6 @@ local page = UI.Page {
[ 'control-space' ] = 'autocomplete', [ 'control-space' ] = 'autocomplete',
}, },
}, },
indicator = UI.Text {
backgroundColor = colors.black,
y = 2, x = -1, width = 1,
},
grid = UI.ScrollingGrid { grid = UI.ScrollingGrid {
y = 3, ey = -2, y = 3, ey = -2,
columns = { columns = {
@ -62,21 +58,10 @@ local page = UI.Page {
}, },
output = UI.Embedded { output = UI.Embedded {
y = -6, 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) function page:setPrompt(value, focus)
self.prompt:setValue(value) self.prompt:setValue(value)
self.prompt.scroll = 0 self.prompt.scroll = 0
@ -133,12 +118,12 @@ end
function page:eventHandler(event) function page:eventHandler(event)
if event.type == 'global' then if event.type == 'global' then
self:setPrompt('', true) self:setPrompt('_G', true)
self:executeStatement('_G') self:executeStatement('_G')
command = nil command = nil
elseif event.type == 'local' then elseif event.type == 'local' then
self:setPrompt('', true) self:setPrompt('_ENV', true)
self:executeStatement('_ENV') self:executeStatement('_ENV')
command = nil command = nil
@ -341,8 +326,11 @@ end
function page:executeStatement(statement) function page:executeStatement(statement)
command = statement command = statement
local s, m
local oterm = term.redirect(self.output.win) 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 if not s then
_G.printError(m) _G.printError(m)
end end
@ -353,14 +341,14 @@ function page:executeStatement(statement)
else else
self.grid:setValues({ }) self.grid:setValues({ })
self.grid:draw() self.grid:draw()
if m then if m and not self.output.enabled then
if not self.output.enabled then self:emit({ type = 'show_output' })
self:emit({ type = 'show_output' })
end
--self.notification:error(m, 5)
end end
end end
self.indicator:showResult(not not s)
if _exit then
UI:exitPullEvents()
end
end end
local args = { ... } local args = { ... }
@ -371,5 +359,4 @@ if args[1] then
end end
UI:setPage(page) UI:setPage(page)
Event.pullEvents() UI:pullEvents()
UI.term:reset()

View File

@ -15,196 +15,196 @@ local shell = _ENV.shell
UI:configure('Network', ...) UI:configure('Network', ...)
local gridColumns = { local gridColumns = {
{ heading = 'Label', key = 'label' }, { heading = 'Label', key = 'label' },
{ heading = 'Dist', key = 'distance' }, { heading = 'Dist', key = 'distance' },
{ heading = 'Status', key = 'status' }, { heading = 'Status', key = 'status' },
} }
if UI.term.width >= 30 then if UI.term.width >= 30 then
table.insert(gridColumns, { heading = 'Fuel', key = 'fuel', width = 5 }) table.insert(gridColumns, { heading = 'Fuel', key = 'fuel', width = 5 })
table.insert(gridColumns, { heading = 'Uptime', key = 'uptime' }) table.insert(gridColumns, { heading = 'Uptime', key = 'uptime' })
end end
local page = UI.Page { local page = UI.Page {
menuBar = UI.MenuBar { menuBar = UI.MenuBar {
buttons = { buttons = {
{ text = 'Connect', dropdown = { { text = 'Connect', dropdown = {
{ text = 'Telnet t', event = 'telnet' }, { text = 'Telnet t', event = 'telnet' },
{ text = 'VNC v', event = 'vnc' }, { text = 'VNC v', event = 'vnc' },
UI.MenuBar.spacer, UI.MenuBar.spacer,
{ text = 'Reboot r', event = 'reboot' }, { text = 'Reboot r', event = 'reboot' },
} }, } },
--{ text = 'Chat', event = 'chat' }, --{ text = 'Chat', event = 'chat' },
{ text = 'Trust', dropdown = { { text = 'Trust', dropdown = {
{ text = 'Establish', event = 'trust' }, { text = 'Establish', event = 'trust' },
{ text = 'Remove', event = 'untrust' }, { text = 'Remove', event = 'untrust' },
} }, } },
{ text = 'Help', event = 'help' }, { text = 'Help', event = 'help' },
}, },
}, },
grid = UI.ScrollingGrid { grid = UI.ScrollingGrid {
y = 2, y = 2,
values = network, values = network,
columns = gridColumns, columns = gridColumns,
sortColumn = 'label', sortColumn = 'label',
autospace = true, autospace = true,
}, },
notification = UI.Notification { }, notification = UI.Notification { },
accelerators = { accelerators = {
t = 'telnet', t = 'telnet',
v = 'vnc', v = 'vnc',
r = 'reboot', r = 'reboot',
q = 'quit', q = 'quit',
c = 'clear', c = 'clear',
}, },
} }
local function sendCommand(host, command) local function sendCommand(host, command)
if not device.wireless_modem then if not device.wireless_modem then
page.notification:error('Wireless modem not present') page.notification:error('Wireless modem not present')
return return
end end
page.notification:info('Connecting') page.notification:info('Connecting')
page:sync() page:sync()
local socket = Socket.connect(host, 161) local socket = Socket.connect(host, 161)
if socket then if socket then
socket:write({ type = command }) socket:write({ type = command })
socket:close() socket:close()
page.notification:success('Command sent') page.notification:success('Command sent')
else else
page.notification:error('Failed to connect') page.notification:error('Failed to connect')
end end
end end
function page:eventHandler(event) function page:eventHandler(event)
local t = self.grid:getSelected() local t = self.grid:getSelected()
if t then if t then
if event.type == 'telnet' then if event.type == 'telnet' then
multishell.openTab({ multishell.openTab({
path = 'sys/apps/telnet.lua', path = 'sys/apps/telnet.lua',
focused = true, focused = true,
args = { t.id }, args = { t.id },
title = t.label, title = t.label,
}) })
elseif event.type == 'vnc' then elseif event.type == 'vnc' then
multishell.openTab({ multishell.openTab({
path = 'sys/apps/vnc.lua', path = 'sys/apps/vnc.lua',
focused = true, focused = true,
args = { t.id }, args = { t.id },
title = t.label, title = t.label,
}) })
elseif event.type == 'clear' then elseif event.type == 'clear' then
Util.clear(network) Util.clear(network)
page.grid:update() page.grid:update()
page.grid:draw() page.grid:draw()
elseif event.type == 'trust' then elseif event.type == 'trust' then
shell.openForegroundTab('trust ' .. t.id) shell.openForegroundTab('trust ' .. t.id)
elseif event.type == 'untrust' then elseif event.type == 'untrust' then
local trustList = Util.readTable('usr/.known_hosts') or { } local trustList = Util.readTable('usr/.known_hosts') or { }
trustList[t.id] = nil trustList[t.id] = nil
Util.writeTable('usr/.known_hosts', trustList) Util.writeTable('usr/.known_hosts', trustList)
elseif event.type == 'chat' then elseif event.type == 'chat' then
multishell.openTab({ multishell.openTab({
path = 'sys/apps/shell', path = 'sys/apps/shell',
args = { 'chat join opusChat-' .. t.id .. ' guest-' .. os.getComputerID() }, args = { 'chat join opusChat-' .. t.id .. ' guest-' .. os.getComputerID() },
title = 'Chatroom', title = 'Chatroom',
focused = true, focused = true,
}) })
elseif event.type == 'reboot' then elseif event.type == 'reboot' then
sendCommand(t.id, 'reboot') sendCommand(t.id, 'reboot')
elseif event.type == 'shutdown' then elseif event.type == 'shutdown' then
sendCommand(t.id, 'shutdown') sendCommand(t.id, 'shutdown')
end end
end end
if event.type == 'help' then if event.type == 'help' then
UI:setPage(UI.Dialog { UI:setPage(UI.Dialog {
title = 'Network Help', title = 'Network Help',
height = 10, height = 10,
backgroundColor = colors.white, backgroundColor = colors.white,
text = UI.TextArea { text = UI.TextArea {
x = 2, y = 2, x = 2, y = 2,
backgroundColor = colors.white, backgroundColor = colors.white,
value = [[ value = [[
In order to connect to another computer: In order to connect to another computer:
1. The target computer must have a password set (run 'password' from the shell prompt). 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. 2. From this computer, click trust and enter the password for that computer.
This only needs to be done once. This only needs to be done once.
]], ]],
}, },
accelerators = { accelerators = {
q = 'cancel', q = 'cancel',
} }
}) })
elseif event.type == 'quit' then elseif event.type == 'quit' then
Event.exitPullEvents() Event.exitPullEvents()
end end
UI.Page.eventHandler(self, event) UI.Page.eventHandler(self, event)
end end
function page.menuBar:getActive(menuItem) function page.menuBar:getActive(menuItem)
local t = page.grid:getSelected() local t = page.grid:getSelected()
if menuItem.event == 'untrust' then if menuItem.event == 'untrust' then
local trustList = Util.readTable('usr/.known_hosts') or { } local trustList = Util.readTable('usr/.known_hosts') or { }
return t and trustList[t.id] return t and trustList[t.id]
end end
return not not t return not not t
end end
function page.grid:getRowTextColor(row, selected) function page.grid:getRowTextColor(row, selected)
if not row.active then if not row.active then
return colors.orange return colors.orange
end end
return UI.Grid.getRowTextColor(self, row, selected) return UI.Grid.getRowTextColor(self, row, selected)
end end
function page.grid:getDisplayValues(row) function page.grid:getDisplayValues(row)
row = Util.shallowCopy(row) row = Util.shallowCopy(row)
if row.uptime then if row.uptime then
if row.uptime < 60 then if row.uptime < 60 then
row.uptime = string.format("%ds", math.floor(row.uptime)) row.uptime = string.format("%ds", math.floor(row.uptime))
else else
row.uptime = string.format("%sm", math.floor(row.uptime/6)/10) row.uptime = string.format("%sm", math.floor(row.uptime/6)/10)
end end
end end
if row.fuel then if row.fuel then
row.fuel = Util.toBytes(row.fuel) row.fuel = Util.toBytes(row.fuel)
end end
if row.distance then if row.distance then
row.distance = Util.round(row.distance, 1) row.distance = Util.round(row.distance, 1)
end end
return row return row
end end
Event.onInterval(1, function() Event.onInterval(1, function()
page.grid:update() page.grid:update()
page.grid:draw() page.grid:draw()
page:sync() page:sync()
end) end)
Event.on('device_attach', function(_, deviceName) Event.on('device_attach', function(_, deviceName)
if deviceName == 'wireless_modem' then if deviceName == 'wireless_modem' then
page.notification:success('Modem connected') page.notification:success('Modem connected')
page:sync() page:sync()
end end
end) end)
Event.on('device_detach', function(_, deviceName) Event.on('device_detach', function(_, deviceName)
if deviceName == 'wireless_modem' then if deviceName == 'wireless_modem' then
page.notification:error('Wireless modem not attached') page.notification:error('Wireless modem not attached')
page:sync() page:sync()
end end
end) end)
if not device.wireless_modem then if not device.wireless_modem then
page.notification:error('Wireless modem not attached') page.notification:error('Wireless modem not attached')
end end
UI:setPage(page) UI:setPage(page)

View File

@ -1,4 +1,4 @@
_G.requireInjector() _G.requireInjector(_ENV)
local class = require('class') local class = require('class')
local Config = require('config') local Config = require('config')
@ -18,7 +18,7 @@ local term = _G.term
local turtle = _G.turtle local turtle = _G.turtle
if not _ENV.multishell then if not _ENV.multishell then
error('multishell is required') error('multishell is required')
end end
local REGISTRY_DIR = 'usr/.registry' local REGISTRY_DIR = 'usr/.registry'
@ -26,8 +26,8 @@ local REGISTRY_DIR = 'usr/.registry'
UI:configure('Overview', ...) UI:configure('Overview', ...)
local config = { local config = {
Recent = { }, Recent = { },
currentCategory = 'Apps', currentCategory = 'Apps',
} }
Config.load('Overview', config) Config.load('Overview', config)
@ -35,51 +35,51 @@ local applications = { }
local function loadApplications() local function loadApplications()
local requirements = { local requirements = {
turtle = function() return turtle end, turtle = function() return turtle end,
advancedTurtle = function() return turtle and term.isColor() end, advancedTurtle = function() return turtle and term.isColor() end,
advanced = function() return term.isColor() end, advanced = function() return term.isColor() end,
pocket = function() return pocket end, pocket = function() return pocket end,
advancedPocket = function() return pocket and term.isColor() end, advancedPocket = function() return pocket and term.isColor() end,
advancedComputer = function() return not turtle and not 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 if fs.exists('usr/etc/apps') then
local dbs = fs.list('usr/etc/apps') local dbs = fs.list('usr/etc/apps')
for _, db in pairs(dbs) do for _, db in pairs(dbs) do
local apps = Util.readTable('usr/etc/apps/' .. db) or { } local apps = Util.readTable('usr/etc/apps/' .. db) or { }
Util.merge(applications, apps) Util.merge(applications, apps)
end end
end end
if fs.exists(REGISTRY_DIR) then if fs.exists(REGISTRY_DIR) then
local files = fs.list(REGISTRY_DIR) local files = fs.list(REGISTRY_DIR)
for _,file in pairs(files) do for _,file in pairs(files) do
local app = Util.readTable(fs.combine(REGISTRY_DIR, file)) local app = Util.readTable(fs.combine(REGISTRY_DIR, file))
if app and app.key then if app and app.key then
app.filename = fs.combine(REGISTRY_DIR, file) app.filename = fs.combine(REGISTRY_DIR, file)
applications[app.key] = app applications[app.key] = app
end end
end end
end end
Util.each(applications, function(v, k) v.key = k end) Util.each(applications, function(v, k) v.key = k end)
applications = Util.filter(applications, function(a) applications = Util.filter(applications, function(a)
if a.disabled then if a.disabled then
return false return false
end end
if a.requires then if a.requires then
local fn = requirements[a.requires] local fn = requirements[a.requires]
if fn and not fn() then if fn and not fn() then
return false return false
end end
end end
return true -- Util.startsWidth(a.run, 'http') or shell.resolveProgram(a.run) return true -- Util.startsWidth(a.run, 'http') or shell.resolveProgram(a.run)
end) end)
end end
loadApplications() loadApplications()
@ -92,457 +92,457 @@ local sx, sy = term.current().getSize()
local maxRecent = math.ceil(sx * sy / 62) local maxRecent = math.ceil(sx * sy / 62)
local function elipse(s, len) local function elipse(s, len)
if #s > len then if #s > len then
s = s:sub(1, len - 2) .. '..' s = s:sub(1, len - 2) .. '..'
end end
return s return s
end end
local buttons = { } local buttons = { }
local categories = { } local categories = { }
for _,f in pairs(applications) do for _,f in pairs(applications) do
if not categories[f.category] then if not categories[f.category] then
categories[f.category] = true categories[f.category] = true
table.insert(buttons, { text = f.category }) table.insert(buttons, { text = f.category })
end end
end end
table.sort(buttons, function(a, b) return a.text < b.text end) table.sort(buttons, function(a, b) return a.text < b.text end)
table.insert(buttons, 1, { text = 'Recent' }) table.insert(buttons, 1, { text = 'Recent' })
table.insert(buttons, { text = '+', event = 'new' }) table.insert(buttons, { text = '+', event = 'new' })
local function parseIcon(iconText) local function parseIcon(iconText)
local icon local icon
local s, m = pcall(function() local s, m = pcall(function()
icon = NFT.parse(iconText) icon = NFT.parse(iconText)
if icon then if icon then
if icon.height > 3 or icon.width > 8 then if icon.height > 3 or icon.width > 8 then
error('Invalid size') error('Invalid size')
end end
end end
return icon return icon
end) end)
if s then if s then
return icon return icon
end end
return s, m return s, m
end end
UI.VerticalTabBar = class(UI.TabBar) UI.VerticalTabBar = class(UI.TabBar)
function UI.VerticalTabBar:setParent() function UI.VerticalTabBar:setParent()
self.x = 1 self.x = 1
self.width = 8 self.width = 8
self.height = nil self.height = nil
self.ey = -1 self.ey = -1
UI.TabBar.setParent(self) UI.TabBar.setParent(self)
for k,c in pairs(self.children) do for k,c in pairs(self.children) do
c.x = 1 c.x = 1
c.y = k + 1 c.y = k + 1
c.ox, c.oy = c.x, c.y c.ox, c.oy = c.x, c.y
c.ow = 8 c.ow = 8
c.width = 8 c.width = 8
end end
end end
local cx = 9 local cx = 9
local cy = 1 local cy = 1
if sx < 30 then if sx < 30 then
UI.VerticalTabBar = UI.TabBar UI.VerticalTabBar = UI.TabBar
cx = 1 cx = 1
cy = 2 cy = 2
end end
local page = UI.Page { local page = UI.Page {
tabBar = UI.VerticalTabBar { tabBar = UI.VerticalTabBar {
buttons = buttons, buttons = buttons,
}, },
container = UI.Viewport { container = UI.Viewport {
x = cx, x = cx,
y = cy, y = cy,
}, },
notification = UI.Notification(), notification = UI.Notification(),
accelerators = { accelerators = {
r = 'refresh', r = 'refresh',
e = 'edit', e = 'edit',
f = 'files', f = 'files',
s = 'shell', s = 'shell',
l = 'lua', l = 'lua',
[ 'control-n' ] = 'new', [ 'control-n' ] = 'new',
delete = 'delete', delete = 'delete',
}, },
} }
UI.Icon = class(UI.Window) UI.Icon = class(UI.Window)
UI.Icon.defaults = { UI.Icon.defaults = {
UIElement = 'Icon', UIElement = 'Icon',
width = 14, width = 14,
height = 4, height = 4,
} }
function UI.Icon:eventHandler(event) function UI.Icon:eventHandler(event)
if event.type == 'mouse_click' then if event.type == 'mouse_click' then
self:setFocus(self.button) self:setFocus(self.button)
return true return true
elseif event.type == 'mouse_doubleclick' then elseif event.type == 'mouse_doubleclick' then
self:emit({ type = self.button.event, button = self.button }) self:emit({ type = self.button.event, button = self.button })
elseif event.type == 'mouse_rightclick' then elseif event.type == 'mouse_rightclick' then
self:setFocus(self.button) self:setFocus(self.button)
self:emit({ type = 'edit', button = self.button }) self:emit({ type = 'edit', button = self.button })
end end
return UI.Window.eventHandler(self, event) return UI.Window.eventHandler(self, event)
end end
function page.container:setCategory(categoryName, animate) function page.container:setCategory(categoryName, animate)
-- reset the viewport window -- reset the viewport window
self.children = { } self.children = { }
self.offy = 0 self.offy = 0
local function filter(it, f) local function filter(it, f)
local ot = { } local ot = { }
for _,v in pairs(it) do for _,v in pairs(it) do
if f(v) then if f(v) then
table.insert(ot, v) table.insert(ot, v)
end end
end end
return ot return ot
end end
local filtered local filtered
if categoryName == 'Recent' then if categoryName == 'Recent' then
filtered = { } filtered = { }
for _,v in ipairs(config.Recent) do for _,v in ipairs(config.Recent) do
local app = Util.find(applications, 'key', v) local app = Util.find(applications, 'key', v)
if app then -- and fs.exists(app.run) then if app then -- and fs.exists(app.run) then
table.insert(filtered, app) table.insert(filtered, app)
end end
end end
else else
filtered = filter(applications, function(a) filtered = filter(applications, function(a)
return a.category == categoryName -- and fs.exists(a.run) return a.category == categoryName -- and fs.exists(a.run)
end) end)
table.sort(filtered, function(a, b) return a.title < b.title end) table.sort(filtered, function(a, b) return a.title < b.title end)
end end
for _,program in ipairs(filtered) do for _,program in ipairs(filtered) do
local icon local icon
if program.icon then if program.icon then
icon = parseIcon(program.icon) icon = parseIcon(program.icon)
end end
if not icon then if not icon then
icon = defaultIcon icon = defaultIcon
end end
local title = elipse(program.title, 8) local title = elipse(program.title, 8)
local width = math.max(icon.width + 2, #title + 2) local width = math.max(icon.width + 2, #title + 2)
table.insert(self.children, UI.Icon({ table.insert(self.children, UI.Icon({
width = width, width = width,
image = UI.NftImage({ image = UI.NftImage({
x = math.floor((width - icon.width) / 2) + 1, x = math.floor((width - icon.width) / 2) + 1,
image = icon, image = icon,
width = 5, width = 5,
height = 3, height = 3,
}), }),
button = UI.Button({ button = UI.Button({
x = math.floor((width - #title - 2) / 2) + 1, x = math.floor((width - #title - 2) / 2) + 1,
y = 4, y = 4,
text = title, text = title,
backgroundColor = self.backgroundColor, backgroundColor = self.backgroundColor,
backgroundFocusColor = colors.gray, backgroundFocusColor = colors.gray,
textColor = colors.white, textColor = colors.white,
textFocusColor = colors.white, textFocusColor = colors.white,
width = #title + 2, width = #title + 2,
event = 'button', event = 'button',
app = program, app = program,
}), }),
})) }))
end end
local gutter = 2 local gutter = 2
if UI.term.width <= 26 then if UI.term.width <= 26 then
gutter = 1 gutter = 1
end end
local col, row = gutter, 2 local col, row = gutter, 2
local count = #self.children local count = #self.children
local r = math.random(1, 5) local r = math.random(1, 5)
-- reposition all children -- reposition all children
for k,child in ipairs(self.children) do for k,child in ipairs(self.children) do
if r == 1 then if r == 1 then
child.x = math.random(1, self.width) child.x = math.random(1, self.width)
child.y = math.random(1, self.height) child.y = math.random(1, self.height)
elseif r == 2 then elseif r == 2 then
child.x = self.width child.x = self.width
child.y = self.height child.y = self.height
elseif r == 3 then elseif r == 3 then
child.x = math.floor(self.width / 2) child.x = math.floor(self.width / 2)
child.y = math.floor(self.height / 2) child.y = math.floor(self.height / 2)
elseif r == 4 then elseif r == 4 then
child.x = self.width - col child.x = self.width - col
child.y = row child.y = row
elseif r == 5 then elseif r == 5 then
child.x = col child.x = col
child.y = row child.y = row
if k == #self.children then if k == #self.children then
child.x = self.width child.x = self.width
child.y = self.height child.y = self.height
end end
end end
child.tween = Tween.new(6, child, { x = col, y = row }, 'linear') child.tween = Tween.new(6, child, { x = col, y = row }, 'linear')
if not animate then if not animate then
child.x = col child.x = col
child.y = row child.y = row
end end
if k < count then if k < count then
col = col + child.width col = col + child.width
if col + self.children[k + 1].width + gutter - 2 > self.width then if col + self.children[k + 1].width + gutter - 2 > self.width then
col = gutter col = gutter
row = row + 5 row = row + 5
end end
end end
end end
self:initChildren() self:initChildren()
if animate then -- need to fix transitions under layers if animate then -- need to fix transitions under layers
local function transition(args) local function transition(args)
local i = 1 local i = 1
return function(device) return function(device)
self:clear() self:clear()
for _,child in pairs(self.children) do for _,child in pairs(self.children) do
child.tween:update(1) child.tween:update(1)
child.x = math.floor(child.x) child.x = math.floor(child.x)
child.y = math.floor(child.y) child.y = math.floor(child.y)
child:draw() child:draw()
end end
args.canvas:blit(device, args, args) args.canvas:blit(device, args, args)
i = i + 1 i = i + 1
return i < 7 return i < 7
end end
end end
self:addTransition(transition) self:addTransition(transition)
end end
end end
function page:refresh() function page:refresh()
local pos = self.container.offy local pos = self.container.offy
self:focusFirst(self) self:focusFirst(self)
self.container:setCategory(config.currentCategory) self.container:setCategory(config.currentCategory)
self.container:setScrollPosition(pos) self.container:setScrollPosition(pos)
end end
function page:resize() function page:resize()
UI.Page.resize(self) UI.Page.resize(self)
self:refresh() self:refresh()
end end
function page:eventHandler(event) function page:eventHandler(event)
if event.type == 'tab_select' then if event.type == 'tab_select' then
self.container:setCategory(event.button.text, true) self.container:setCategory(event.button.text, true)
self.container:draw() self.container:draw()
config.currentCategory = event.button.text config.currentCategory = event.button.text
Config.update('Overview', config) Config.update('Overview', config)
elseif event.type == 'button' then elseif event.type == 'button' then
for k,v in ipairs(config.Recent) do for k,v in ipairs(config.Recent) do
if v == event.button.app.key then if v == event.button.app.key then
table.remove(config.Recent, k) table.remove(config.Recent, k)
break break
end end
end end
table.insert(config.Recent, 1, event.button.app.key) table.insert(config.Recent, 1, event.button.app.key)
if #config.Recent > maxRecent then if #config.Recent > maxRecent then
table.remove(config.Recent, maxRecent + 1) table.remove(config.Recent, maxRecent + 1)
end end
Config.update('Overview', config) Config.update('Overview', config)
shell.switchTab(shell.openTab(event.button.app.run)) shell.switchTab(shell.openTab(event.button.app.run))
elseif event.type == 'shell' then elseif event.type == 'shell' then
shell.switchTab(shell.openTab('sys/apps/shell')) shell.switchTab(shell.openTab('sys/apps/shell'))
elseif event.type == 'lua' then elseif event.type == 'lua' then
shell.switchTab(shell.openTab('sys/apps/Lua.lua')) shell.switchTab(shell.openTab('sys/apps/Lua.lua'))
elseif event.type == 'files' then elseif event.type == 'files' then
shell.switchTab(shell.openTab('sys/apps/Files.lua')) shell.switchTab(shell.openTab('sys/apps/Files.lua'))
elseif event.type == 'focus_change' then elseif event.type == 'focus_change' then
if event.focused.parent.UIElement == 'Icon' then if event.focused.parent.UIElement == 'Icon' then
event.focused.parent:scrollIntoView() event.focused.parent:scrollIntoView()
end end
elseif event.type == 'refresh' then -- remove this after fixing notification elseif event.type == 'refresh' then -- remove this after fixing notification
loadApplications() loadApplications()
self:refresh() self:refresh()
self:draw() self:draw()
self.notification:success('Refreshed') self.notification:success('Refreshed')
elseif event.type == 'delete' then elseif event.type == 'delete' then
local focused = page:getFocused() local focused = page:getFocused()
if focused.app then if focused.app then
focused.app.disabled = true focused.app.disabled = true
local filename = focused.app.filename or fs.combine(REGISTRY_DIR, focused.app.key) local filename = focused.app.filename or fs.combine(REGISTRY_DIR, focused.app.key)
Util.writeTable(filename, focused.app) Util.writeTable(filename, focused.app)
loadApplications() loadApplications()
page:refresh() page:refresh()
page:draw() page:draw()
self.notification:success('Removed') self.notification:success('Removed')
end end
elseif event.type == 'new' then elseif event.type == 'new' then
local category = 'Apps' local category = 'Apps'
if config.currentCategory ~= 'Recent' then if config.currentCategory ~= 'Recent' then
category = config.currentCategory or 'Apps' category = config.currentCategory or 'Apps'
end end
UI:setPage('editor', { category = category }) UI:setPage('editor', { category = category })
elseif event.type == 'edit' then elseif event.type == 'edit' then
local focused = page:getFocused() local focused = page:getFocused()
if focused.app then if focused.app then
UI:setPage('editor', focused.app) UI:setPage('editor', focused.app)
end end
else else
UI.Page.eventHandler(self, event) UI.Page.eventHandler(self, event)
end end
return true return true
end end
local formWidth = math.max(UI.term.width - 8, 26) local formWidth = math.max(UI.term.width - 8, 26)
local editor = UI.Dialog { local editor = UI.Dialog {
height = 11, height = 11,
width = formWidth, width = formWidth,
title = 'Edit Application', title = 'Edit Application',
form = UI.Form { form = UI.Form {
y = 2, y = 2,
height = 9, height = 9,
title = UI.TextEntry { title = UI.TextEntry {
formLabel = 'Title', formKey = 'title', limit = 11, help = 'Application title', formLabel = 'Title', formKey = 'title', limit = 11, help = 'Application title',
required = true, required = true,
}, },
run = UI.TextEntry { run = UI.TextEntry {
formLabel = 'Run', formKey = 'run', limit = 100, help = 'Full path to application', formLabel = 'Run', formKey = 'run', limit = 100, help = 'Full path to application',
required = true, required = true,
}, },
category = UI.TextEntry { category = UI.TextEntry {
formLabel = 'Category', formKey = 'category', limit = 11, help = 'Category of application', formLabel = 'Category', formKey = 'category', limit = 11, help = 'Category of application',
required = true, required = true,
}, },
loadIcon = UI.Button { loadIcon = UI.Button {
x = 11, y = 6, x = 11, y = 6,
text = 'Icon', event = 'loadIcon', help = 'Select icon' text = 'Icon', event = 'loadIcon', help = 'Select icon'
}, },
image = UI.NftImage { image = UI.NftImage {
y = 6, x = 2, height = 3, width = 8, y = 6, x = 2, height = 3, width = 8,
}, },
}, },
statusBar = UI.StatusBar(), statusBar = UI.StatusBar(),
iconFile = '', iconFile = '',
} }
function editor:enable(app) function editor:enable(app)
if app then if app then
self.form:setValues(app) self.form:setValues(app)
local icon local icon
if app.icon then if app.icon then
icon = parseIcon(app.icon) icon = parseIcon(app.icon)
end end
self.form.image:setImage(icon) self.form.image:setImage(icon)
end end
UI.Dialog.enable(self) UI.Dialog.enable(self)
self:focusFirst() self:focusFirst()
end end
function editor.form.image:draw() function editor.form.image:draw()
self:clear() self:clear()
UI.NftImage.draw(self) UI.NftImage.draw(self)
end end
function editor:updateApplications(app) function editor:updateApplications(app)
if not app.key then if not app.key then
app.key = SHA1.sha1(app.title) app.key = SHA1.sha1(app.title)
end end
local filename = app.filename or fs.combine(REGISTRY_DIR, app.key) local filename = app.filename or fs.combine(REGISTRY_DIR, app.key)
Util.writeTable(filename, app) Util.writeTable(filename, app)
loadApplications() loadApplications()
end end
function editor:eventHandler(event) function editor:eventHandler(event)
if event.type == 'form_cancel' or event.type == 'cancel' then if event.type == 'form_cancel' or event.type == 'cancel' then
UI:setPreviousPage() UI:setPreviousPage()
elseif event.type == 'focus_change' then elseif event.type == 'focus_change' then
self.statusBar:setStatus(event.focused.help or '') self.statusBar:setStatus(event.focused.help or '')
self.statusBar:draw() self.statusBar:draw()
elseif event.type == 'loadIcon' then elseif event.type == 'loadIcon' then
local fileui = FileUI({ local fileui = FileUI({
x = self.x, x = self.x,
y = self.y, y = self.y,
z = 2, z = 2,
width = self.width, width = self.width,
height = self.height, height = self.height,
}) })
UI:setPage(fileui, fs.getDir(self.iconFile), function(fileName) UI:setPage(fileui, fs.getDir(self.iconFile), function(fileName)
if fileName then if fileName then
self.iconFile = fileName self.iconFile = fileName
local s, m = pcall(function() local s, m = pcall(function()
local iconLines = Util.readFile(fileName) local iconLines = Util.readFile(fileName)
if not iconLines then if not iconLines then
error('Unable to load file') error('Unable to load file')
end end
local icon, m = parseIcon(iconLines) local icon, m = parseIcon(iconLines)
if not icon then if not icon then
error(m) error(m)
end end
self.form.values.icon = iconLines self.form.values.icon = iconLines
self.form.image:setImage(icon) self.form.image:setImage(icon)
self.form.image:draw() self.form.image:draw()
end) end)
if not s and m then if not s and m then
local msg = m:gsub('.*: (.*)', '%1') local msg = m:gsub('.*: (.*)', '%1')
page.notification:error(msg) page.notification:error(msg)
end end
end end
end) end)
elseif event.type == 'form_invalid' then elseif event.type == 'form_invalid' then
page.notification:error(event.message) page.notification:error(event.message)
elseif event.type == 'form_complete' then elseif event.type == 'form_complete' then
local values = self.form.values local values = self.form.values
UI:setPreviousPage() UI:setPreviousPage()
self:updateApplications(values) self:updateApplications(values)
page:refresh() page:refresh()
page:draw() page:draw()
else else
return UI.Dialog.eventHandler(self, event) return UI.Dialog.eventHandler(self, event)
end end
return true return true
end end
UI:setPages({ UI:setPages({
editor = editor, editor = editor,
main = page, main = page,
}) })
Event.on('os_register_app', function() Event.on('os_register_app', function()
loadApplications() loadApplications()
page:refresh() page:refresh()
page:draw() page:draw()
page:sync() page:sync()
end) end)
page.tabBar:selectTab(config.currentCategory or 'Apps') page.tabBar:selectTab(config.currentCategory or 'Apps')

View File

@ -1,4 +1,4 @@
_G.requireInjector() _G.requireInjector(_ENV)
local Config = require('config') local Config = require('config')
local Security = require('security') local Security = require('security')
@ -15,307 +15,313 @@ local turtle = _G.turtle
UI:configure('System', ...) UI:configure('System', ...)
local env = { local env = {
path = shell.path(), path = shell.path(),
aliases = shell.aliases(), aliases = shell.aliases(),
lua_path = _ENV.LUA_PATH, lua_path = _ENV.LUA_PATH,
} }
Config.load('shell', env) Config.load('shell', env)
local systemPage = UI.Page { local systemPage = UI.Page {
tabs = UI.Tabs { tabs = UI.Tabs {
pathTab = UI.Window { pathTab = UI.Window {
tabTitle = 'Path', tabTitle = 'Path',
entry = UI.TextEntry { entry = UI.TextEntry {
x = 2, y = 2, ex = -2, x = 2, y = 2, ex = -2,
limit = 256, limit = 256,
value = shell.path(), value = shell.path(),
shadowText = 'enter system path', shadowText = 'enter system path',
accelerators = { accelerators = {
enter = 'update_path', enter = 'update_path',
}, },
}, },
grid = UI.Grid { grid = UI.Grid {
y = 4, y = 4,
disableHeader = true, disableHeader = true,
columns = { { key = 'value' } }, columns = { { key = 'value' } },
autospace = true, autospace = true,
}, },
}, },
aliasTab = UI.Window { aliasTab = UI.Window {
tabTitle = 'Alias', tabTitle = 'Alias',
alias = UI.TextEntry { alias = UI.TextEntry {
x = 2, y = 2, ex = -2, x = 2, y = 2, ex = -2,
limit = 32, limit = 32,
shadowText = 'Alias', shadowText = 'Alias',
}, },
path = UI.TextEntry { path = UI.TextEntry {
y = 3, x = 2, ex = -2, y = 3, x = 2, ex = -2,
limit = 256, limit = 256,
shadowText = 'Program path', shadowText = 'Program path',
accelerators = { accelerators = {
enter = 'new_alias', enter = 'new_alias',
}, },
}, },
grid = UI.Grid { grid = UI.Grid {
y = 5, y = 5,
sortColumn = 'alias', sortColumn = 'alias',
columns = { columns = {
{ heading = 'Alias', key = 'alias' }, { heading = 'Alias', key = 'alias' },
{ heading = 'Program', key = 'path' }, { heading = 'Program', key = 'path' },
}, },
accelerators = { accelerators = {
delete = 'delete_alias', delete = 'delete_alias',
}, },
}, },
}, },
passwordTab = UI.Window { passwordTab = UI.Window {
tabTitle = 'Password', tabTitle = 'Password',
oldPass = UI.TextEntry { oldPass = UI.TextEntry {
x = 2, y = 2, ex = -2, x = 2, y = 2, ex = -2,
limit = 32, limit = 32,
mask = true, mask = true,
shadowText = 'old password', shadowText = 'old password',
inactive = not Security.getPassword(), inactive = not Security.getPassword(),
}, },
newPass = UI.TextEntry { newPass = UI.TextEntry {
y = 3, x = 2, ex = -2, y = 3, x = 2, ex = -2,
limit = 32, limit = 32,
mask = true, mask = true,
shadowText = 'new password', shadowText = 'new password',
accelerators = { accelerators = {
enter = 'new_password', enter = 'new_password',
}, },
}, },
button = UI.Button { button = UI.Button {
x = 2, y = 5, x = 2, y = 5,
text = 'Update', text = 'Update',
event = 'update_password', event = 'update_password',
}, },
info = UI.TextArea { info = UI.TextArea {
x = 2, ex = -2, x = 2, ex = -2,
y = 7, y = 7,
inactive = true, inactive = true,
value = 'Add a password to enable other computers to connect to this one.', value = 'Add a password to enable other computers to connect to this one.',
} }
}, },
infoTab = UI.Window { infoTab = UI.Window {
tabTitle = 'Info', tabTitle = 'Info',
labelText = UI.Text { labelText = UI.Text {
x = 3, y = 2, x = 3, y = 2,
value = 'Label' value = 'Label'
}, },
label = UI.TextEntry { label = UI.TextEntry {
x = 9, y = 2, ex = -4, x = 9, y = 2, ex = -4,
limit = 32, limit = 32,
value = os.getComputerLabel(), value = os.getComputerLabel(),
accelerators = { accelerators = {
enter = 'update_label', enter = 'update_label',
}, },
}, },
grid = UI.ScrollingGrid { grid = UI.ScrollingGrid {
y = 3, y = 3,
values = { values = {
{ name = '', value = '' }, { name = '', value = '' },
{ name = 'CC version', value = Util.getVersion() }, { name = 'CC version', value = Util.getVersion() },
{ name = 'Lua version', value = _VERSION }, { name = 'Lua version', value = _VERSION },
{ name = 'MC version', value = Util.getMinecraftVersion() }, { name = 'MC version', value = Util.getMinecraftVersion() },
{ name = 'Disk free', value = Util.toBytes(fs.getFreeSpace('/')) }, { name = 'Disk free', value = Util.toBytes(fs.getFreeSpace('/')) },
{ name = 'Computer ID', value = tostring(os.getComputerID()) }, { name = 'Computer ID', value = tostring(os.getComputerID()) },
{ name = 'Day', value = tostring(os.day()) }, { name = 'Day', value = tostring(os.day()) },
}, },
inactive = true, inactive = true,
columns = { columns = {
{ key = 'name', width = 12 }, { key = 'name', width = 12 },
{ key = 'value' }, { key = 'value' },
}, },
}, },
}, },
}, },
notification = UI.Notification(), notification = UI.Notification(),
accelerators = { accelerators = {
q = 'quit', q = 'quit',
}, },
} }
if turtle then if turtle then
local Home = require('turtle.home') local Home = require('turtle.home')
local values = { } local values = { }
Config.load('gps', values.home or { }) Config.load('gps', values.home or { })
systemPage.tabs:add({ systemPage.tabs:add({
gpsTab = UI.Window { gpsTab = UI.Window {
tabTitle = 'GPS', tabTitle = 'GPS',
labelText = UI.Text { labelText = UI.Text {
x = 3, y = 2, x = 3, y = 2,
value = 'On restart, return to this location' value = 'On restart, return to this location'
}, },
grid = UI.Grid { grid = UI.Grid {
x = 3, ex = -3, y = 4, x = 3, ex = -3, y = 4,
height = 2, height = 2,
values = values, values = values,
inactive = true, inactive = true,
columns = { columns = {
{ heading = 'x', key = 'x' }, { heading = 'x', key = 'x' },
{ heading = 'y', key = 'y' }, { heading = 'y', key = 'y' },
{ heading = 'z', key = 'z' }, { heading = 'z', key = 'z' },
}, },
}, },
button1 = UI.Button { button1 = UI.Button {
x = 3, y = 7, x = 3, y = 7,
text = 'Set home', text = 'Set home',
event = 'gps_set', event = 'gps_set',
}, },
button2 = UI.Button { button2 = UI.Button {
ex = -3, y = 7, width = 7, ex = -3, y = 7, width = 7,
text = 'Clear', text = 'Clear',
event = 'gps_clear', event = 'gps_clear',
}, },
}, },
}) })
function systemPage.tabs.gpsTab:eventHandler(event) function systemPage.tabs.gpsTab:eventHandler(event)
if event.type == 'gps_set' then if event.type == 'gps_set' then
systemPage.notification:info('Determining location', 10) systemPage.notification:info('Determining location', 10)
systemPage:sync() systemPage:sync()
if Home.set() then if Home.set() then
Config.load('gps', values) Config.load('gps', values)
self.grid:setValues(values.home or { }) self.grid:setValues(values.home or { })
self.grid:draw() self.grid:draw()
systemPage.notification:success('Location set') systemPage.notification:success('Location set')
else else
systemPage.notification:error('Unable to determine location') systemPage.notification:error('Unable to determine location')
end end
return true return true
elseif event.type == 'gps_clear' then elseif event.type == 'gps_clear' then
fs.delete('usr/config/gps') fs.delete('usr/config/gps')
self.grid:setValues({ }) self.grid:setValues({ })
self.grid:draw() self.grid:draw()
return true return true
end end
end end
end end
if settings then if settings then
local values = { } local values = { }
for _,v in pairs(settings.getNames()) do for _,v in pairs(settings.getNames()) do
table.insert(values, { local value = settings.get(v)
name = v, if not value then
value = not not settings.get(v), value = false
}) end
end table.insert(values, {
name = v,
value = value,
})
end
systemPage.tabs:add({ systemPage.tabs:add({
settingsTab = UI.Window { settingsTab = UI.Window {
tabTitle = 'Settings', tabTitle = 'Settings',
grid = UI.Grid { grid = UI.Grid {
y = 1, y = 1,
values = values, values = values,
autospace = true, autospace = true,
sortColumn = 'name', sortColumn = 'name',
columns = { columns = {
{ heading = 'Setting', key = 'name' }, { heading = 'Setting', key = 'name' },
{ heading = 'Value', key = 'value' }, { heading = 'Value', key = 'value' },
}, },
}, },
} }
}) })
function systemPage.tabs.settingsTab:eventHandler(event) function systemPage.tabs.settingsTab:eventHandler(event)
if event.type == 'grid_select' then if event.type == 'grid_select' then
event.selected.value = not event.selected.value if not event.selected.value or type(event.selected.value) == 'boolean' then
settings.set(event.selected.name, event.selected.value) event.selected.value = not event.selected.value
settings.save('.settings') end
self.grid:draw() settings.set(event.selected.name, event.selected.value)
return true settings.save('.settings')
end self.grid:draw()
end return true
end
end
end end
function systemPage.tabs.pathTab.grid:draw() function systemPage.tabs.pathTab.grid:draw()
self.values = { } self.values = { }
for _,v in ipairs(Util.split(env.path, '(.-):')) do for _,v in ipairs(Util.split(env.path, '(.-):')) do
table.insert(self.values, { value = v }) table.insert(self.values, { value = v })
end end
self:update() self:update()
UI.Grid.draw(self) UI.Grid.draw(self)
end end
function systemPage.tabs.pathTab:eventHandler(event) function systemPage.tabs.pathTab:eventHandler(event)
if event.type == 'update_path' then if event.type == 'update_path' then
env.path = self.entry.value env.path = self.entry.value
self.grid:setIndex(self.grid:getIndex()) self.grid:setIndex(self.grid:getIndex())
self.grid:draw() self.grid:draw()
Config.update('shell', env) Config.update('shell', env)
systemPage.notification:success('reboot to take effect') systemPage.notification:success('reboot to take effect')
return true return true
end end
end end
function systemPage.tabs.aliasTab.grid:draw() function systemPage.tabs.aliasTab.grid:draw()
self.values = { } self.values = { }
for k,v in pairs(env.aliases) do for k,v in pairs(env.aliases) do
table.insert(self.values, { alias = k, path = v }) table.insert(self.values, { alias = k, path = v })
end end
self:update() self:update()
UI.Grid.draw(self) UI.Grid.draw(self)
end end
function systemPage.tabs.aliasTab:eventHandler(event) function systemPage.tabs.aliasTab:eventHandler(event)
if event.type == 'delete_alias' then if event.type == 'delete_alias' then
env.aliases[self.grid:getSelected().alias] = nil env.aliases[self.grid:getSelected().alias] = nil
self.grid:setIndex(self.grid:getIndex()) self.grid:setIndex(self.grid:getIndex())
self.grid:draw() self.grid:draw()
Config.update('shell', env) Config.update('shell', env)
systemPage.notification:success('reboot to take effect') systemPage.notification:success('reboot to take effect')
return true return true
elseif event.type == 'new_alias' then elseif event.type == 'new_alias' then
env.aliases[self.alias.value] = self.path.value env.aliases[self.alias.value] = self.path.value
self.alias:reset() self.alias:reset()
self.path:reset() self.path:reset()
self:draw() self:draw()
self:setFocus(self.alias) self:setFocus(self.alias)
Config.update('shell', env) Config.update('shell', env)
systemPage.notification:success('reboot to take effect') systemPage.notification:success('reboot to take effect')
return true return true
end end
end end
function systemPage.tabs.passwordTab:eventHandler(event) function systemPage.tabs.passwordTab:eventHandler(event)
if event.type == 'update_password' then if event.type == 'update_password' then
if #self.newPass.value == 0 then if #self.newPass.value == 0 then
systemPage.notification:error('Invalid password') systemPage.notification:error('Invalid password')
elseif Security.getPassword() and not Security.verifyPassword(SHA1.sha1(self.oldPass.value)) then elseif Security.getPassword() and not Security.verifyPassword(SHA1.sha1(self.oldPass.value)) then
systemPage.notification:error('Passwords do not match') systemPage.notification:error('Passwords do not match')
else else
Security.updatePassword(SHA1.sha1(self.newPass.value)) Security.updatePassword(SHA1.sha1(self.newPass.value))
self.oldPass.inactive = false self.oldPass.inactive = false
systemPage.notification:success('Password updated') systemPage.notification:success('Password updated')
end end
return true return true
end end
end end
function systemPage.tabs.infoTab:eventHandler(event) function systemPage.tabs.infoTab:eventHandler(event)
if event.type == 'update_label' then if event.type == 'update_label' then
os.setComputerLabel(self.label.value) os.setComputerLabel(self.label.value)
systemPage.notification:success('Label updated') systemPage.notification:success('Label updated')
return true return true
end end
end end
function systemPage:eventHandler(event) function systemPage:eventHandler(event)
if event.type == 'quit' then if event.type == 'quit' then
UI:exitPullEvents() UI:exitPullEvents()
elseif event.type == 'tab_activate' then elseif event.type == 'tab_activate' then
event.activated:focusFirst() event.activated:focusFirst()
else else
return UI.Page.eventHandler(self, event) return UI.Page.eventHandler(self, event)
end end
return true return true
end end
UI:setPage(systemPage) UI:setPage(systemPage)

View File

@ -10,66 +10,62 @@ local multishell = _ENV.multishell
UI:configure('Tasks', ...) UI:configure('Tasks', ...)
local page = UI.Page { local page = UI.Page {
menuBar = UI.MenuBar { menuBar = UI.MenuBar {
buttons = { buttons = {
{ text = 'Activate', event = 'activate' }, { text = 'Activate', event = 'activate' },
{ text = 'Terminate', event = 'terminate' }, { text = 'Terminate', event = 'terminate' },
}, },
}, },
grid = UI.ScrollingGrid { grid = UI.ScrollingGrid {
y = 2, y = 2,
columns = { columns = {
{ heading = 'ID', key = 'uid', width = 3 }, { heading = 'ID', key = 'uid', width = 3 },
{ heading = 'Title', key = 'title' }, { heading = 'Title', key = 'title' },
{ heading = 'Status', key = 'status' }, { heading = 'Status', key = 'status' },
{ heading = 'Time', key = 'timestamp' }, { heading = 'Time', key = 'timestamp' },
}, },
values = kernel.routines, values = kernel.routines,
sortColumn = 'uid', sortColumn = 'uid',
autospace = true, autospace = true,
}, },
accelerators = { accelerators = {
q = 'quit', q = 'quit',
space = 'activate', space = 'activate',
t = 'terminate', t = 'terminate',
}, },
} }
function page:eventHandler(event) function page:eventHandler(event)
local t = self.grid:getSelected() local t = self.grid:getSelected()
if t then if t then
if event.type == 'activate' or event.type == 'grid_select' then if event.type == 'activate' or event.type == 'grid_select' then
multishell.setFocus(t.uid) multishell.setFocus(t.uid)
elseif event.type == 'terminate' then elseif event.type == 'terminate' then
multishell.terminate(t.uid) multishell.terminate(t.uid)
end end
end end
if event.type == 'quit' then if event.type == 'quit' then
Event.exitPullEvents() Event.exitPullEvents()
end end
UI.Page.eventHandler(self, event) UI.Page.eventHandler(self, event)
end end
function page.grid:getDisplayValues(row) function page.grid:getDisplayValues(row)
row = Util.shallowCopy(row) row = Util.shallowCopy(row)
local elapsed = os.clock()-row.timestamp local elapsed = os.clock()-row.timestamp
if elapsed < 60 then if elapsed < 60 then
row.timestamp = string.format("%ds", math.floor(elapsed)) row.timestamp = string.format("%ds", math.floor(elapsed))
else else
row.timestamp = string.format("%sm", math.floor(elapsed/6)/10) row.timestamp = string.format("%sm", math.floor(elapsed/6)/10)
end end
if row.isDead then row.status = row.isDead and 'error' or coroutine.status(row.co)
row.status = 'error' return row
else
row.status = coroutine.status(row.co)
end
return row
end end
Event.onInterval(1, function() Event.onInterval(1, function()
page.grid:update() page.grid:update()
page.grid:draw() page.grid:draw()
page:sync() page:sync()
end) end)
UI:setPage(page) UI:setPage(page)

View File

@ -1,4 +1,4 @@
_G.requireInjector() _G.requireInjector(_ENV)
local Event = require('event') local Event = require('event')
local Util = require('util') local Util = require('util')

View File

@ -1,4 +1,4 @@
_G.requireInjector() _G.requireInjector(_ENV)
local Security = require('security') local Security = require('security')
local SHA1 = require('sha1') local SHA1 = require('sha1')
@ -7,6 +7,6 @@ local Terminal = require('terminal')
local password = Terminal.readPassword('Enter new password: ') local password = Terminal.readPassword('Enter new password: ')
if password then if password then
Security.updatePassword(SHA1.sha1(password)) Security.updatePassword(SHA1.sha1(password))
print('Password updated') print('Password updated')
end end

File diff suppressed because it is too large Load Diff

View File

@ -1,4 +1,4 @@
_G.requireInjector() _G.requireInjector(_ENV)
local Event = require('event') local Event = require('event')
local Socket = require('socket') local Socket = require('socket')
@ -14,73 +14,73 @@ local options, args = Util.args({ ... })
local remoteId = tonumber(table.remove(args, 1) or '') local remoteId = tonumber(table.remove(args, 1) or '')
if not remoteId then if not remoteId then
print('Enter host ID') print('Enter host ID')
remoteId = tonumber(read()) remoteId = tonumber(read())
end end
if not remoteId then if not remoteId then
error('Syntax: telnet [-title TITLE] ID [PROGRAM]') error('Syntax: telnet [-title TITLE] ID [PROGRAM]')
end end
if options.title and multishell then if options.title and multishell then
multishell.setTitle(multishell.getCurrent(), options.title) multishell.setTitle(multishell.getCurrent(), options.title)
end end
local socket, msg = Socket.connect(remoteId, 23) local socket, msg = Socket.connect(remoteId, 23)
if not socket then if not socket then
error(msg) error(msg)
end end
local ct = Util.shallowCopy(term.current()) local ct = Util.shallowCopy(term.current())
if not ct.isColor() then if not ct.isColor() then
Terminal.toGrayscale(ct) Terminal.toGrayscale(ct)
end end
local w, h = ct.getSize() local w, h = ct.getSize()
socket:write({ socket:write({
width = w, width = w,
height = h, height = h,
isColor = ct.isColor(), isColor = ct.isColor(),
program = args, program = args,
pos = { ct.getCursorPos() }, pos = { ct.getCursorPos() },
}) })
Event.addRoutine(function() Event.addRoutine(function()
while true do while true do
local data = socket:read() local data = socket:read()
if not data then if not data then
break break
end end
for _,v in ipairs(data) do for _,v in ipairs(data) do
ct[v.f](table.unpack(v.args)) ct[v.f](table.unpack(v.args))
end end
end end
end) end)
--ct.clear() --ct.clear()
--ct.setCursorPos(1, 1) --ct.setCursorPos(1, 1)
local filter = Util.transpose { local filter = Util.transpose {
'char', 'paste', 'key', 'key_up', 'terminate', 'char', 'paste', 'key', 'key_up', 'terminate',
'mouse_scroll', 'mouse_click', 'mouse_drag', 'mouse_up', 'mouse_scroll', 'mouse_click', 'mouse_drag', 'mouse_up',
} }
while true do while true do
local e = { os.pullEventRaw() } local e = { os.pullEventRaw() }
local event = e[1] local event = e[1]
if filter[event] then if filter[event] then
socket:write(e) socket:write(e)
else else
Event.processEvent(e) Event.processEvent(e)
end end
if not socket.connected then if not socket.connected then
-- print() -- print()
-- print('Connection lost') -- print('Connection lost')
-- print('Press enter to exit') -- print('Press enter to exit')
-- pcall(read) -- pcall(read)
break break
end end
end end

View File

@ -1,4 +1,4 @@
_G.requireInjector() _G.requireInjector(_ENV)
local Crypto = require('crypto') local Crypto = require('crypto')
local Security = require('security') local Security = require('security')
@ -12,27 +12,27 @@ local remoteId
local args = { ... } local args = { ... }
if #args == 1 then if #args == 1 then
remoteId = tonumber(args[1]) remoteId = tonumber(args[1])
else else
print('Enter host ID') print('Enter host ID')
remoteId = tonumber(_G.read()) remoteId = tonumber(_G.read())
end end
if not remoteId then if not remoteId then
error('Syntax: trust <host ID>') error('Syntax: trust <host ID>')
end end
local password = Terminal.readPassword('Enter password: ') local password = Terminal.readPassword('Enter password: ')
if not password then if not password then
error('Invalid password') error('Invalid password')
end end
print('connecting...') print('connecting...')
local socket, msg = Socket.connect(remoteId, 19) local socket, msg = Socket.connect(remoteId, 19)
if not socket then if not socket then
error(msg) error(msg)
end end
local publicKey = Security.getPublicKey() local publicKey = Security.getPublicKey()
@ -43,9 +43,9 @@ local data = socket:read(2)
socket:close() socket:close()
if data and data.success then if data and data.success then
print(data.msg) print(data.msg)
elseif data then elseif data then
error(data.msg) error(data.msg)
else else
error('No response') error('No response')
end end

View File

@ -1,4 +1,4 @@
_G.requireInjector() _G.requireInjector(_ENV)
local Event = require('event') local Event = require('event')
local Socket = require('socket') local Socket = require('socket')
@ -12,35 +12,35 @@ local term = _G.term
local remoteId local remoteId
local args = { ... } local args = { ... }
if #args == 1 then if #args == 1 then
remoteId = tonumber(args[1]) remoteId = tonumber(args[1])
else else
print('Enter host ID') print('Enter host ID')
remoteId = tonumber(_G.read()) remoteId = tonumber(_G.read())
end end
if not remoteId then if not remoteId then
error('Syntax: vnc <host ID>') error('Syntax: vnc <host ID>')
end end
if multishell then if multishell then
multishell.setTitle(multishell.getCurrent(), 'VNC-' .. remoteId) multishell.setTitle(multishell.getCurrent(), 'VNC-' .. remoteId)
end end
print('connecting...') print('connecting...')
local socket, msg = Socket.connect(remoteId, 5900) local socket, msg = Socket.connect(remoteId, 5900)
if not socket then if not socket then
error(msg) error(msg)
end end
local function writeTermInfo() local function writeTermInfo()
local w, h = term.getSize() local w, h = term.getSize()
socket:write({ socket:write({
type = 'termInfo', type = 'termInfo',
width = w, width = w,
height = h, height = h,
isColor = term.isColor(), isColor = term.isColor(),
}) })
end end
writeTermInfo() writeTermInfo()
@ -48,53 +48,53 @@ writeTermInfo()
local ct = Util.shallowCopy(term.current()) local ct = Util.shallowCopy(term.current())
if not ct.isColor() then if not ct.isColor() then
Terminal.toGrayscale(ct) Terminal.toGrayscale(ct)
end end
Event.addRoutine(function() Event.addRoutine(function()
while true do while true do
local data = socket:read() local data = socket:read()
if not data then if not data then
break break
end end
for _,v in ipairs(data) do for _,v in ipairs(data) do
ct[v.f](unpack(v.args)) ct[v.f](unpack(v.args))
end end
end end
end) end)
ct.clear() ct.clear()
ct.setCursorPos(1, 1) ct.setCursorPos(1, 1)
local filter = Util.transpose({ local filter = Util.transpose({
'char', 'paste', 'key', 'key_up', 'char', 'paste', 'key', 'key_up',
'mouse_scroll', 'mouse_click', 'mouse_drag', 'mouse_up', 'mouse_scroll', 'mouse_click', 'mouse_drag', 'mouse_up',
}) })
while true do while true do
local e = Event.pullEvent() local e = Event.pullEvent()
local event = e[1] local event = e[1]
if not socket.connected then if not socket.connected then
print() print()
print('Connection lost') print('Connection lost')
print('Press enter to exit') print('Press enter to exit')
_G.read() _G.read()
break break
end end
if filter[event] then if filter[event] then
socket:write({ socket:write({
type = 'shellRemote', type = 'shellRemote',
event = e, event = e,
}) })
elseif event == 'term_resize' then elseif event == 'term_resize' then
writeTermInfo() writeTermInfo()
elseif event == 'terminate' then elseif event == 'terminate' then
socket:close() socket:close()
ct.setBackgroundColor(colors.black) ct.setBackgroundColor(colors.black)
ct.clear() ct.clear()
ct.setCursorPos(1, 1) ct.setCursorPos(1, 1)
break break
end end
end end

View File

@ -10,17 +10,17 @@ local textutils = _G.textutils
local data local data
kernel.hook('clipboard_copy', function(_, args) kernel.hook('clipboard_copy', function(_, args)
data = args[1] data = args[1]
end) end)
keyboard.addHotkey('control-shift-paste', function(_, args) keyboard.addHotkey('shift-paste', function()
if type(data) == 'table' then if type(data) == 'table' then
local s, m = pcall(textutils.serialize, data) local s, m = pcall(textutils.serialize, data)
data = (s and m) or Util.tostring(data) data = (s and m) or Util.tostring(data)
end end
-- replace the event paste data with our internal data -- replace the event paste data with our internal data
args[1] = Util.tostring(data or '') -- args[1] = Util.tostring(data or '')
if data then if data then
os.queueEvent('paste', data) os.queueEvent('paste', data)
end end
end) end)

View File

@ -2,42 +2,42 @@ local modem = _G.device.wireless_modem
local turtle = _G.turtle local turtle = _G.turtle
if turtle and modem then 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 = require('config')
local config = { local config = {
destructive = false, destructive = false,
} }
Config.load('gps', config) Config.load('gps', config)
if config.home then if config.home then
local s = turtle.enableGPS(2) local s = turtle.enableGPS(2)
if not s then if not s then
s = turtle.enableGPS(2) s = turtle.enableGPS(2)
end end
if not s and config.destructive then if not s and config.destructive then
turtle.setPolicy('turtleSafe') turtle.setPolicy('turtleSafe')
s = turtle.enableGPS(2) s = turtle.enableGPS(2)
end end
if not s then if not s then
error('Unable to get GPS position') error('Unable to get GPS position')
end end
if config.destructive then if config.destructive then
turtle.setPolicy('turtleSafe') turtle.setPolicy('turtleSafe')
end end
if not turtle.pathfind(config.home) then if not turtle.pathfind(config.home) then
error('Failed to return home') error('Failed to return home')
end end
end end
end) end)
if not s and m then if not s and m then
error(m) error(m)
end end
end end

View File

@ -1,14 +1,19 @@
if _G.device.wireless_modem then if _G.device.wireless_modem then
_G.requireInjector() _G.requireInjector(_ENV)
local Config = require('config') local Config = require('config')
local config = { } local kernel = _G.kernel
Config.load('gps', config)
if config.host and type(config.host) == 'table' then local config = { }
_ENV._APP_TITLE = 'GPS Daemon' Config.load('gps', config)
os.run(_ENV, '/rom/programs/gps', 'host', config.host.x, config.host.y, config.host.z)
print('GPS daemon stopped') if config.host and type(config.host) == 'table' then
end kernel.run({
title = 'GPS Daemon',
hidden = true,
path = '/rom/programs/gps',
args = { 'host', config.host.x, config.host.y, config.host.z },
})
end
end end

View File

@ -1,4 +1,4 @@
_G.requireInjector() _G.requireInjector(_ENV)
local Util = require('util') local Util = require('util')
@ -8,50 +8,50 @@ local multishell = _ENV.multishell
-- overview -- overview
keyboard.addHotkey('control-o', function() keyboard.addHotkey('control-o', function()
for _,tab in pairs(multishell.getTabs()) do for _,tab in pairs(multishell.getTabs()) do
if tab.isOverview then if tab.isOverview then
multishell.setFocus(tab.tabId) multishell.setFocus(tab.uid)
end end
end end
end) end)
-- restart tab -- restart tab
keyboard.addHotkey('control-backspace', function() keyboard.addHotkey('control-backspace', function()
local uid = multishell.getFocus() local uid = multishell.getFocus()
local tab = kernel.find(uid) local tab = kernel.find(uid)
if not tab.isOverview then if not tab.isOverview then
multishell.terminate(uid) multishell.terminate(uid)
tab = Util.shallowCopy(tab) tab = Util.shallowCopy(tab)
tab.isDead = false tab.isDead = false
tab.focused = true tab.focused = true
multishell.openTab(tab) multishell.openTab(tab)
end end
end) end)
-- next tab -- next tab
keyboard.addHotkey('control-tab', function() keyboard.addHotkey('control-tab', function()
local tabs = multishell.getTabs() local tabs = multishell.getTabs()
local visibleTabs = { } local visibleTabs = { }
local currentTabId = multishell.getFocus() local currentTabId = multishell.getFocus()
local function compareTab(a, b) local function compareTab(a, b)
return a.uid < b.uid return a.uid < b.uid
end end
for _,tab in Util.spairs(tabs, compareTab) do for _,tab in Util.spairs(tabs, compareTab) do
if not tab.hidden then if not tab.hidden then
table.insert(visibleTabs, tab) table.insert(visibleTabs, tab)
end end
end end
for k,tab in ipairs(visibleTabs) do for k,tab in ipairs(visibleTabs) do
if tab.uid == currentTabId then if tab.uid == currentTabId then
if k < #visibleTabs then if k < #visibleTabs then
multishell.setFocus(visibleTabs[k + 1].uid) multishell.setFocus(visibleTabs[k + 1].uid)
return return
end end
end end
end end
if #visibleTabs > 0 then if #visibleTabs > 0 then
multishell.setFocus(visibleTabs[1].uid) multishell.setFocus(visibleTabs[1].uid)
end end
end) end)

View File

@ -1,7 +1,7 @@
_G.requireInjector(_ENV) _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 local kernel = _G.kernel
@ -10,42 +10,46 @@ local multishell = _ENV.multishell
local os = _G.os local os = _G.os
local term = _G.term local term = _G.term
local routine = kernel.getCurrent() local function systemLog()
if multishell then local routine = kernel.getCurrent()
multishell.setTitle(multishell.getCurrent(), 'System Log')
multishell.hideTab(routine.uid) 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 end
local w, h = kernel.window.getSize() multishell.openTab({
kernel.window.reposition(1, 2, w, h - 1) title = 'System Log',
fn = systemLog,
routine.terminal = kernel.window hidden = true,
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')

View File

@ -1,3 +0,0 @@
term.clear()
term.setCursorPos(1, 1)
print(os.version())

View File

@ -9,69 +9,69 @@ local BASE = 'https://raw.githubusercontent.com/' .. GIT_REPO
local sandboxEnv = setmetatable({ }, { __index = _G }) local sandboxEnv = setmetatable({ }, { __index = _G })
for k,v in pairs(_ENV) do for k,v in pairs(_ENV) do
sandboxEnv[k] = v sandboxEnv[k] = v
end end
sandboxEnv.BRANCH = BRANCH sandboxEnv.BRANCH = BRANCH
_G.debug = function() end _G.debug = function() end
local function makeEnv() local function makeEnv()
local env = setmetatable({ }, { __index = _G }) local env = setmetatable({ }, { __index = _G })
for k,v in pairs(sandboxEnv) do for k,v in pairs(sandboxEnv) do
env[k] = v env[k] = v
end end
return env return env
end end
local function run(file, ...) local function run(file, ...)
local s, m = loadfile(file, makeEnv()) local s, m = loadfile(file, makeEnv())
if s then if s then
return s(...) return s(...)
end end
error('Error loading ' .. file .. '\n' .. m) error('Error loading ' .. file .. '\n' .. m)
end end
local function runUrl(file, ...) local function runUrl(file, ...)
local url = BASE .. '/' .. file local url = BASE .. '/' .. file
local u = http.get(url) local u = http.get(url)
if u then if u then
local fn = load(u.readAll(), url, nil, makeEnv()) local fn = load(u.readAll(), url, nil, makeEnv())
u.close() u.close()
if fn then if fn then
return fn(...) return fn(...)
end end
end end
error('Failed to download ' .. url) error('Failed to download ' .. url)
end end
function os.getenv(varname) function os.getenv(varname)
return sandboxEnv[varname] return sandboxEnv[varname]
end end
function os.setenv(varname, value) function os.setenv(varname, value)
sandboxEnv[varname] = value sandboxEnv[varname] = value
end end
-- Install require shim -- Install require shim
if fs.exists('sys/apis/injector.lua') then if fs.exists('sys/apis/injector.lua') then
_G.requireInjector = run('sys/apis/injector.lua') _G.requireInjector = run('sys/apis/injector.lua')
else else
-- not local, run the file system directly from git -- not local, run the file system directly from git
_G.requireInjector = runUrl('sys/apis/injector.lua') _G.requireInjector = runUrl('sys/apis/injector.lua')
runUrl('sys/extensions/2.vfs.lua') runUrl('sys/extensions/2.vfs.lua')
-- install file system -- install file system
fs.mount('', 'gitfs', GIT_REPO) fs.mount('', 'gitfs', GIT_REPO)
end end
local s, m = pcall(run, 'sys/apps/shell', 'sys/kernel.lua', ...) local s, m = pcall(run, 'sys/apps/shell', 'sys/kernel.lua', ...)
if not s then if not s then
print('\nError loading Opus OS\n') print('\nError loading Opus OS\n')
_G.printError(m .. '\n') _G.printError(m .. '\n')
end end
if fs.restore then if fs.restore then
fs.restore() fs.restore()
end end

View File

@ -2,14 +2,14 @@ local pullEvent = os.pullEventRaw
local shutdown = os.shutdown local shutdown = os.shutdown
os.pullEventRaw = function() os.pullEventRaw = function()
error('') error('')
end end
os.shutdown = function() os.shutdown = function()
os.pullEventRaw = pullEvent os.pullEventRaw = pullEvent
os.shutdown = shutdown os.shutdown = shutdown
os.run(getfenv(1), 'sys/boot/opus.boot') os.run(getfenv(1), 'sys/boot/opus.boot')
end end
os.queueEvent('modem_message') os.queueEvent('modem_message')

View File

@ -1,140 +1,140 @@
{ {
[ "53ebc572b4a44802ba114729f07bdaaf5409a9d7" ] = { [ "53ebc572b4a44802ba114729f07bdaaf5409a9d7" ] = {
category = "Apps", category = "Apps",
icon = "\0304 \030 \ icon = "\0304 \030 \
\030f \0304 \0307 \030 \031 \031f)\ \030f \0304 \0307 \030 \031 \031f)\
\030f \0304 \0307 \030 \031f)", \030f \0304 \0307 \030 \031f)",
title = "Network", title = "Network",
run = "Network.lua", run = "Network.lua",
}, },
c7116629a6a855cb774d9c7c8ad822fd83c71fb5 = { c7116629a6a855cb774d9c7c8ad822fd83c71fb5 = {
title = "Reboot", title = "Reboot",
category = "System", category = "System",
icon = "\0304\031f \030f\0310o..\0304\031f \ icon = "\0304\031f \030f\0310o..\0304\031f \
\0304\031f \030f\0310.o.\0304\031f \ \0304\031f \030f\0310.o.\0304\031f \
\0304\031f - ", \0304\031f - ",
run = "rom/programs/reboot", run = "rom/programs/reboot",
}, },
fb91e24fa52d8d2b32937bf04d843f730319a902 = { fb91e24fa52d8d2b32937bf04d843f730319a902 = {
title = "Update", title = "Update",
category = "System", category = "System",
icon = "\0301\03171\03180\030 \031 \ icon = "\0301\03171\03180\030 \031 \
\0301\03181\030 \031 \ \0301\03181\030 \031 \
\0301\03170\03180\03171\0307\031f>", \0301\03170\03180\03171\0307\031f>",
run = "http://pastebin.com/raw/sj4VMVJj", run = "http://pastebin.com/raw/sj4VMVJj",
}, },
c47ae15370cfe1ed2781eedc1dc2547d12d9e972 = { c47ae15370cfe1ed2781eedc1dc2547d12d9e972 = {
title = "Help", title = "Help",
category = "Apps", category = "Apps",
icon = " \031f?\031 \ icon = " \031f?\031 \
\031f?\031 \ \031f?\031 \
\031f?", \031f?",
run = "Help.lua", run = "Help.lua",
}, },
b0832074630eb731d7fbe8074de48a90cd9bb220 = { b0832074630eb731d7fbe8074de48a90cd9bb220 = {
title = "Lua", title = "Lua",
category = "Apps", category = "Apps",
icon = "\030f \ icon = "\030f \
\030f\0310lua>\031 \ \030f\0310lua>\031 \
\030f ", \030f ",
run = "sys/apps/Lua.lua", run = "sys/apps/Lua.lua",
}, },
df485c871329671f46570634d63216761441bcd6 = { df485c871329671f46570634d63216761441bcd6 = {
title = "Devices", title = "Devices",
category = "System", category = "System",
icon = "\0304 \030 \ icon = "\0304 \030 \
\030f \0304 \0307 \030 \031 \031f_\ \030f \0304 \0307 \030 \031 \031f_\
\030f \0304 \0307 \030 \031f/", \030f \0304 \0307 \030 \031f/",
run = "Devices.lua", run = "Devices.lua",
}, },
bc0792d8dc81e8aa30b987246a5ce97c40cd6833 = { bc0792d8dc81e8aa30b987246a5ce97c40cd6833 = {
title = "System", title = "System",
category = "System", category = "System",
icon = " \0307\031f| \ icon = " \0307\031f| \
\0307\031f---o\030 \031 \ \0307\031f---o\030 \031 \
\0307\031f| ", \0307\031f| ",
run = "System.lua", run = "System.lua",
}, },
c5497bca58468ae64aed6c0fd921109217988db3 = { c5497bca58468ae64aed6c0fd921109217988db3 = {
title = "Events", title = "Events",
category = "System", category = "System",
icon = "\0304\031f \030 \0311e\ icon = "\0304\031f \030 \0311e\
\030f\031f \0304 \030 \0311ee\031f \ \030f\031f \0304 \030 \0311ee\031f \
\030f\031f \0304 \030 \0311e\031f ", \030f\031f \0304 \030 \0311e\031f ",
run = "Events.lua", run = "Events.lua",
}, },
[ "2a4d562b1d9a9c90bdede6fac8ce4f7402462b86" ] = { [ "2a4d562b1d9a9c90bdede6fac8ce4f7402462b86" ] = {
title = "Tasks", title = "Tasks",
category = "System", category = "System",
icon = "\030f\031f \0315/\ icon = "\030f\031f \0315/\
\030f\031f \0315/\\/ \ \030f\031f \0315/\\/ \
\030f\0315/\031f ", \030f\0315/\031f ",
run = "Tasks.lua", run = "Tasks.lua",
}, },
[ "6ce6c512ea433a7fc5c8841628e7696cd0ff7f2b" ] = { [ "6ce6c512ea433a7fc5c8841628e7696cd0ff7f2b" ] = {
title = "Files", title = "Files",
category = "Apps", category = "Apps",
icon = "\0300\0317==\031 \0307 \ icon = "\0300\0317==\031 \0307 \
\0300\0317====\ \0300\0317====\
\0300\0317====", \0300\0317====",
run = "Files.lua", run = "Files.lua",
}, },
[ "7fddb7d8d1d60b1eeefa9af01082e0811d4b484d" ] = { [ "7fddb7d8d1d60b1eeefa9af01082e0811d4b484d" ] = {
title = "Shutdown", title = "Shutdown",
category = "System", category = "System",
icon = "\0304\031f \ icon = "\0304\031f \
\0304\031f \030f\0310zz\031 \ \0304\031f \030f\0310zz\031 \
\0304\031f \030f ", \0304\031f \030f ",
run = "/rom/programs/shutdown", run = "/rom/programs/shutdown",
}, },
bdc1fd5d3c0f3dcfd55d010426e61bf9451e680d = { bdc1fd5d3c0f3dcfd55d010426e61bf9451e680d = {
title = "Shell", title = "Shell",
category = "Apps", category = "Apps",
icon = "\030f\0314\151\131\131\131\131\ icon = "\030f\0314\151\131\131\131\131\
\030f\0314\149\030f\0314> \0310_ \ \030f\0314\149\030f\0314> \0310_ \
\030f\0314\149\030f ", \030f\0314\149\030f ",
run = "shell", run = "shell",
}, },
b77aad5fb24921ef76ac8f3e500ed93fddae8f2a = { b77aad5fb24921ef76ac8f3e500ed93fddae8f2a = {
title = "Redirection", title = "Redirection",
category = "Games", category = "Games",
icon = "\0307 \0308 \0307 \ icon = "\0307 \0308 \0307 \
\0308\031b> \030b\0310>\0308\0318 \ \0308\031b> \030b\0310>\0308\0318 \
\0307 ", \0307 ",
run = "rom/programs/fun/advanced/redirection", run = "rom/programs/fun/advanced/redirection",
requires = 'advanced', requires = 'advanced',
}, },
f39d173d91c22348565c20283b89d4d1cabd3b7e = { f39d173d91c22348565c20283b89d4d1cabd3b7e = {
title = "Falling", title = "Falling",
category = "Games", category = "Games",
icon = "\030f \0302 \ icon = "\030f \0302 \
\0309 \0302 \0301 \ \0309 \0302 \0301 \
\030e \0309 \0301 ", \030e \0309 \0301 ",
run = "rom/programs/pocket/falling", run = "rom/programs/pocket/falling",
requires = 'advancedPocket', requires = 'advancedPocket',
}, },
db56e2e1db9f7accfc37f2b132d27505c66ba521 = { db56e2e1db9f7accfc37f2b132d27505c66ba521 = {
title = "Adventure", title = "Adventure",
category = "Games", category = "Games",
icon = "\030f\0310You \031 \ icon = "\030f\0310You \031 \
\030f\0310Ther\030 \031 \ \030f\0310Ther\030 \031 \
\030f\0314?\031f \031 \030 ", \030f\0314?\031f \031 \030 ",
run = "rom/programs/fun/adventure", run = "rom/programs/fun/adventure",
}, },
[ "76b849f460640bc789c433894382fb5acbac42a2" ] = { [ "76b849f460640bc789c433894382fb5acbac42a2" ] = {
title = "Worm", title = "Worm",
category = "Games", category = "Games",
icon = "\030d \030 \030e \030 \ icon = "\030d \030 \030e \030 \
\030d \030 \ \030d \030 \
\030d ", \030d ",
run = "/rom/programs/fun/worm", run = "/rom/programs/fun/worm",
}, },
[ "9f46ca3ef617166776ef6014a58d4e66859caa62" ] = { [ "9f46ca3ef617166776ef6014a58d4e66859caa62" ] = {
title = "DJ", title = "DJ",
category = "Games", category = "Games",
icon = " \030f \ icon = " \030f \
\030f \0307 \ \030f \0307 \
\030f \0307 \0300 ", \030f \0307 \0300 ",
run = "/rom/programs/fun/dj", run = "/rom/programs/fun/dj",
}, },
} }

View File

@ -1,19 +1,19 @@
{ {
ScrollBar = { ScrollBar = {
lineChar = '|', lineChar = '|',
sliderChar = '\127', sliderChar = '\127',
upArrowChar = '\30', upArrowChar = '\30',
downArrowChar = '\31', downArrowChar = '\31',
}, },
Button = { Button = {
--focusIndicator = '\183', --focusIndicator = '\183',
}, },
Grid = { Grid = {
focusIndicator = '\183', focusIndicator = '\183',
inverseSortIndicator = '\24', inverseSortIndicator = '\24',
}, },
TitleBar = { TitleBar = {
frameChar = '\140', frameChar = '\140',
closeInd = '\215', closeInd = '\215',
}, },
} }

View File

@ -13,15 +13,15 @@ _G.device.keyboard = {
side = 'keyboard', side = 'keyboard',
type = 'keyboard', type = 'keyboard',
name = 'keyboard', name = 'keyboard',
hotkeys = { }, hotkeys = { },
state = { }, state = { },
} }
_G.device.mouse = { _G.device.mouse = {
side = 'mouse', side = 'mouse',
type = 'mouse', type = 'mouse',
name = 'mouse', name = 'mouse',
state = { }, state = { },
} }
local Input = require('input') local Input = require('input')
@ -34,24 +34,24 @@ local mouse = _G.device.mouse
local os = _G.os local os = _G.os
kernel.hook('peripheral', function(_, eventData) kernel.hook('peripheral', function(_, eventData)
local side = eventData[1] local side = eventData[1]
if side then if side then
local dev = Peripheral.addDevice(device, side) local dev = Peripheral.addDevice(device, side)
if dev then if dev then
os.queueEvent('device_attach', dev.name) os.queueEvent('device_attach', dev.name)
end end
end end
end) end)
kernel.hook('peripheral_detach', function(_, eventData) kernel.hook('peripheral_detach', function(_, eventData)
local side = eventData[1] local side = eventData[1]
if side then if side then
local dev = Util.find(device, 'side', side) local dev = Util.find(device, 'side', side)
if dev then if dev then
os.queueEvent('device_detach', dev.name) os.queueEvent('device_detach', dev.name)
device[dev.name] = nil device[dev.name] = nil
end end
end end
end) end)
kernel.hook({ 'key', 'key_up', 'char', 'paste' }, function(event, eventData) 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 end
-- and fire hotkeys -- 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 if hotkey and keyboard.hotkeys[hotkey.code] then
keyboard.hotkeys[hotkey](event, eventData) keyboard.hotkeys[hotkey.code](event, eventData)
end end
end) end)
kernel.hook({ 'mouse_click', 'mouse_up', 'mouse_drag' }, function(event, eventData) kernel.hook({ 'mouse_click', 'mouse_up', 'mouse_drag' }, function(event, eventData)
local button = eventData[1] local button = eventData[1]
if event == 'mouse_click' then if event == 'mouse_click' then
mouse.state[button] = true mouse.state[button] = true
else else
if not mouse.state[button] then if not mouse.state[button] then
return true -- ensure mouse ups are only generated if a mouse down was sent return true -- ensure mouse ups are only generated if a mouse down was sent
end end
if event == 'mouse_up' then if event == 'mouse_up' then
mouse.state[button] = nil mouse.state[button] = nil
end end
end end
end) end)
kernel.hook('kernel_focus', function() kernel.hook('kernel_focus', function()
Util.clear(keyboard.state) Util.clear(keyboard.state)
Util.clear(mouse.state) Util.clear(mouse.state)
end) end)
function keyboard.addHotkey(code, fn) function keyboard.addHotkey(code, fn)
keyboard.hotkeys[code] = fn keyboard.hotkeys[code] = fn
end end
function keyboard.removeHotkey(code) function keyboard.removeHotkey(code)
keyboard.hotkeys[code] = nil keyboard.hotkeys[code] = nil
end end
kernel.hook('monitor_touch', function(event, eventData) kernel.hook('monitor_touch', function(event, eventData)

View File

@ -1,8 +1,8 @@
if fs.native then if fs.native then
return return
end end
_G.requireInjector() _G.requireInjector(_ENV)
local Util = require('util') local Util = require('util')
local fs = _G.fs local fs = _G.fs
@ -13,330 +13,330 @@ local fstypes = { }
local nativefs = { } local nativefs = { }
for k,fn in pairs(fs) do for k,fn in pairs(fs) do
if type(fn) == 'function' then if type(fn) == 'function' then
nativefs[k] = function(node, ...) nativefs[k] = function(node, ...)
return fn(...) return fn(...)
end end
end end
end end
function nativefs.list(node, dir) function nativefs.list(node, dir)
local files local files
if fs.native.isDir(dir) then if fs.native.isDir(dir) then
files = fs.native.list(dir) files = fs.native.list(dir)
end end
local function inList(l, e) local function inList(l, e)
for _,v in ipairs(l) do for _,v in ipairs(l) do
if v == e then if v == e then
return true return true
end end
end end
end end
if dir == node.mountPoint and node.nodes then if dir == node.mountPoint and node.nodes then
files = files or { } files = files or { }
for k in pairs(node.nodes) do for k in pairs(node.nodes) do
if not inList(files, k) then if not inList(files, k) then
table.insert(files, k) table.insert(files, k)
end end
end end
end end
if not files then if not files then
error('Not a directory', 2) error('Not a directory', 2)
end end
return files return files
end end
function nativefs.getSize(node, dir, recursive) function nativefs.getSize(node, dir, recursive)
if recursive and fs.native.isDir(dir) then if recursive and fs.native.isDir(dir) then
local function sum(dir) local function sum(dir)
local total = 0 local total = 0
local files = fs.native.list(dir) local files = fs.native.list(dir)
for _,f in ipairs(files) do for _,f in ipairs(files) do
local fullName = fs.combine(dir, f) local fullName = fs.combine(dir, f)
if fs.native.isDir(fullName) then if fs.native.isDir(fullName) then
total = total + sum(fullName) total = total + sum(fullName)
else else
total = total + fs.native.getSize(fullName) total = total + fs.native.getSize(fullName)
end end
end end
return total return total
end end
return sum(dir) return sum(dir)
end end
if node.mountPoint == dir and node.nodes then if node.mountPoint == dir and node.nodes then
return 0 return 0
end end
return fs.native.getSize(dir) return fs.native.getSize(dir)
end end
function nativefs.isDir(node, dir) function nativefs.isDir(node, dir)
if node.mountPoint == dir then if node.mountPoint == dir then
return not not node.nodes return not not node.nodes
end end
return fs.native.isDir(dir) return fs.native.isDir(dir)
end end
function nativefs.exists(node, dir) function nativefs.exists(node, dir)
if node.mountPoint == dir then if node.mountPoint == dir then
return true return true
end end
return fs.native.exists(dir) return fs.native.exists(dir)
end end
function nativefs.delete(node, dir) function nativefs.delete(node, dir)
if node.mountPoint == dir then if node.mountPoint == dir then
fs.unmount(dir) fs.unmount(dir)
else else
fs.native.delete(dir) fs.native.delete(dir)
end end
end end
fstypes.nativefs = nativefs fstypes.nativefs = nativefs
fs.nodes = { fs.nodes = {
fs = nativefs, fs = nativefs,
mountPoint = '', mountPoint = '',
fstype = 'nativefs', fstype = 'nativefs',
nodes = { }, nodes = { },
} }
local function splitpath(path) local function splitpath(path)
local parts = { } local parts = { }
for match in string.gmatch(path, "[^/]+") do for match in string.gmatch(path, "[^/]+") do
table.insert(parts, match) table.insert(parts, match)
end end
return parts return parts
end end
local function getNode(dir) local function getNode(dir)
local cd = fs.combine(dir, '') local cd = fs.combine(dir, '')
local parts = splitpath(cd) local parts = splitpath(cd)
local node = fs.nodes local node = fs.nodes
for _,d in ipairs(parts) do for _,d in ipairs(parts) do
if node.nodes and node.nodes[d] then if node.nodes and node.nodes[d] then
node = node.nodes[d] node = node.nodes[d]
else else
break break
end end
end end
return node return node
end end
local methods = { 'delete', 'getFreeSpace', 'exists', 'isDir', 'getSize', local methods = { 'delete', 'getFreeSpace', 'exists', 'isDir', 'getSize',
'isReadOnly', 'makeDir', 'getDrive', 'list', 'open' } 'isReadOnly', 'makeDir', 'getDrive', 'list', 'open' }
for _,m in pairs(methods) do for _,m in pairs(methods) do
fs[m] = function(dir, ...) fs[m] = function(dir, ...)
dir = fs.combine(dir or '', '') dir = fs.combine(dir or '', '')
local node = getNode(dir) local node = getNode(dir)
return node.fs[m](node, dir, ...) return node.fs[m](node, dir, ...)
end end
end end
function fs.complete(partial, dir, includeFiles, includeSlash) function fs.complete(partial, dir, includeFiles, includeSlash)
dir = fs.combine(dir, '') dir = fs.combine(dir, '')
local node = getNode(dir) local node = getNode(dir)
if node.fs.complete then if node.fs.complete then
return node.fs.complete(node, partial, dir, includeFiles, includeSlash) return node.fs.complete(node, partial, dir, includeFiles, includeSlash)
end end
return fs.native.complete(partial, dir, includeFiles, includeSlash) return fs.native.complete(partial, dir, includeFiles, includeSlash)
end end
function fs.listEx(dir) function fs.listEx(dir)
local node = getNode(dir) local node = getNode(dir)
if node.fs.listEx then if node.fs.listEx then
return node.fs.listEx(node, dir) return node.fs.listEx(node, dir)
end end
local t = { } local t = { }
local files = node.fs.list(node, dir) local files = node.fs.list(node, dir)
pcall(function() pcall(function()
for _,f in ipairs(files) do for _,f in ipairs(files) do
local fullName = fs.combine(dir, f) local fullName = fs.combine(dir, f)
local file = { local file = {
name = f, name = f,
isDir = fs.isDir(fullName), isDir = fs.isDir(fullName),
isReadOnly = fs.isReadOnly(fullName), isReadOnly = fs.isReadOnly(fullName),
} }
if not file.isDir then if not file.isDir then
file.size = fs.getSize(fullName) file.size = fs.getSize(fullName)
end end
table.insert(t, file) table.insert(t, file)
end end
end) end)
return t return t
end end
function fs.copy(s, t) function fs.copy(s, t)
local sp = getNode(s) local sp = getNode(s)
local tp = getNode(t) local tp = getNode(t)
if sp == tp and sp.fs.copy then if sp == tp and sp.fs.copy then
return sp.fs.copy(sp, s, t) return sp.fs.copy(sp, s, t)
end end
if fs.exists(t) then if fs.exists(t) then
error('File exists') error('File exists')
end end
if fs.isDir(s) then if fs.isDir(s) then
fs.makeDir(t) fs.makeDir(t)
local list = fs.list(s) local list = fs.list(s)
for _,f in ipairs(list) do for _,f in ipairs(list) do
fs.copy(fs.combine(s, f), fs.combine(t, f)) fs.copy(fs.combine(s, f), fs.combine(t, f))
end end
else else
local sf = Util.readFile(s) local sf = Util.readFile(s)
if not sf then if not sf then
error('No such file') error('No such file')
end end
Util.writeFile(t, sf) Util.writeFile(t, sf)
end end
end end
function fs.find(spec) -- not optimized function fs.find(spec) -- not optimized
-- local node = getNode(spec) -- local node = getNode(spec)
-- local files = node.fs.find(node, spec) -- local files = node.fs.find(node, spec)
local files = { } local files = { }
-- method from https://github.com/N70/deltaOS/blob/dev/vfs -- method from https://github.com/N70/deltaOS/blob/dev/vfs
local function recurse_spec(results, path, spec) local function recurse_spec(results, path, spec)
local segment = spec:match('([^/]*)'):gsub('/', '') local segment = spec:match('([^/]*)'):gsub('/', '')
local pattern = '^' .. segment:gsub("[%.%[%]%(%)%%%+%-%?%^%$]","%%%1"):gsub("%z","%%z"):gsub("%*","[^/]-") .. '$' local pattern = '^' .. segment:gsub("[%.%[%]%(%)%%%+%-%?%^%$]","%%%1"):gsub("%z","%%z"):gsub("%*","[^/]-") .. '$'
if fs.isDir(path) then if fs.isDir(path) then
for _, file in ipairs(fs.list(path)) do for _, file in ipairs(fs.list(path)) do
if file:match(pattern) then if file:match(pattern) then
local f = fs.combine(path, file) local f = fs.combine(path, file)
if spec == segment then if spec == segment then
table.insert(results, f) table.insert(results, f)
end end
if fs.isDir(f) then if fs.isDir(f) then
recurse_spec(results, f, spec:sub(#segment + 2)) recurse_spec(results, f, spec:sub(#segment + 2))
end end
end end
end end
end end
end end
recurse_spec(files, '', spec) recurse_spec(files, '', spec)
table.sort(files) table.sort(files)
return files return files
end end
function fs.move(s, t) function fs.move(s, t)
local sp = getNode(s) local sp = getNode(s)
local tp = getNode(t) local tp = getNode(t)
if sp == tp and sp.fs.move then if sp == tp and sp.fs.move then
return sp.fs.move(sp, s, t) return sp.fs.move(sp, s, t)
end end
fs.copy(s, t) fs.copy(s, t)
fs.delete(s) fs.delete(s)
end end
local function getfstype(fstype) local function getfstype(fstype)
local vfs = fstypes[fstype] local vfs = fstypes[fstype]
if not vfs then if not vfs then
vfs = require('fs.' .. fstype) vfs = require('fs.' .. fstype)
fs.registerType(fstype, vfs) fs.registerType(fstype, vfs)
end end
return vfs return vfs
end end
function fs.mount(path, fstype, ...) function fs.mount(path, fstype, ...)
local vfs = getfstype(fstype) local vfs = getfstype(fstype)
if not vfs then if not vfs then
error('Invalid file system type') error('Invalid file system type')
end end
local node = vfs.mount(path, ...) local node = vfs.mount(path, ...)
if node then if node then
local parts = splitpath(path) local parts = splitpath(path)
local targetName = table.remove(parts, #parts) local targetName = table.remove(parts, #parts)
local tp = fs.nodes local tp = fs.nodes
for _,d in ipairs(parts) do for _,d in ipairs(parts) do
if not tp.nodes then if not tp.nodes then
tp.nodes = { } tp.nodes = { }
end end
if not tp.nodes[d] then if not tp.nodes[d] then
tp.nodes[d] = Util.shallowCopy(tp) tp.nodes[d] = Util.shallowCopy(tp)
tp.nodes[d].nodes = { } tp.nodes[d].nodes = { }
tp.nodes[d].mountPoint = fs.combine(tp.mountPoint, d) tp.nodes[d].mountPoint = fs.combine(tp.mountPoint, d)
end end
tp = tp.nodes[d] tp = tp.nodes[d]
end end
node.fs = vfs node.fs = vfs
node.fstype = fstype node.fstype = fstype
if not targetName then if not targetName then
node.mountPoint = '' node.mountPoint = ''
fs.nodes = node fs.nodes = node
else else
node.mountPoint = fs.combine(tp.mountPoint, targetName) node.mountPoint = fs.combine(tp.mountPoint, targetName)
tp.nodes[targetName] = node tp.nodes[targetName] = node
end end
end end
return node return node
end end
function fs.loadTab(path) function fs.loadTab(path)
local mounts = Util.readFile(path) local mounts = Util.readFile(path)
if mounts then if mounts then
for _,l in ipairs(Util.split(mounts)) do for _,l in ipairs(Util.split(mounts)) do
if l:sub(1, 1) ~= '#' then if l:sub(1, 1) ~= '#' then
local s, m = pcall(function() local s, m = pcall(function()
fs.mount(table.unpack(Util.matches(l))) fs.mount(table.unpack(Util.matches(l)))
end) end)
if not s then if not s then
_G.printError('Mount failed') _G.printError('Mount failed')
_G.printError(l) _G.printError(l)
_G.printError(m) _G.printError(m)
end end
end end
end end
end end
end end
local function getNodeByParts(parts) local function getNodeByParts(parts)
local node = fs.nodes local node = fs.nodes
for _,d in ipairs(parts) do for _,d in ipairs(parts) do
if not node.nodes[d] then if not node.nodes[d] then
return return
end end
node = node.nodes[d] node = node.nodes[d]
end end
return node return node
end end
function fs.unmount(path) function fs.unmount(path)
local parts = splitpath(path) local parts = splitpath(path)
local targetName = table.remove(parts, #parts) local targetName = table.remove(parts, #parts)
local node = getNodeByParts(parts) local node = getNodeByParts(parts)
if node and node.nodes[targetName] then if node and node.nodes[targetName] then
node.nodes[targetName] = nil node.nodes[targetName] = nil
end end
end end
function fs.registerType(name, fs) function fs.registerType(name, fs)
fstypes[name] = fs fstypes[name] = fs
end end
function fs.getTypes() function fs.getTypes()
return fstypes return fstypes
end end
function fs.restore() function fs.restore()
local native = fs.native local native = fs.native
Util.clear(fs) Util.clear(fs)
Util.merge(fs, native) Util.merge(fs, native)
end end

View File

@ -2,14 +2,14 @@ local os = _G.os
-- Default label -- Default label
if not os.getComputerLabel() then if not os.getComputerLabel() then
local id = os.getComputerID() local id = os.getComputerID()
if _G.turtle then if _G.turtle then
os.setComputerLabel('turtle_' .. id) os.setComputerLabel('turtle_' .. id)
elseif _G.pocket then elseif _G.pocket then
os.setComputerLabel('pocket_' .. id) os.setComputerLabel('pocket_' .. id)
elseif _G.commands then elseif _G.commands then
os.setComputerLabel('command_' .. id) os.setComputerLabel('command_' .. id)
else else
os.setComputerLabel('computer_' .. id) os.setComputerLabel('computer_' .. id)
end end
end end

View File

@ -1,4 +1,4 @@
_G.requireInjector() _G.requireInjector(_ENV)
local Util = require('util') local Util = require('util')
@ -7,32 +7,32 @@ local os = _G.os
local shell = _ENV.shell local shell = _ENV.shell
if not fs.exists('usr/apps') then if not fs.exists('usr/apps') then
fs.makeDir('usr/apps') fs.makeDir('usr/apps')
end end
if not fs.exists('usr/autorun') then if not fs.exists('usr/autorun') then
fs.makeDir('usr/autorun') fs.makeDir('usr/autorun')
end end
if not fs.exists('usr/config/fstab') then if not fs.exists('usr/config/fstab') then
Util.writeFile('usr/config/fstab', Util.writeFile('usr/config/fstab',
'usr gitfs kepler155c/opus-apps/' .. os.getenv('BRANCH')) 'usr gitfs kepler155c/opus-apps/' .. os.getenv('BRANCH'))
end end
if not fs.exists('usr/config/shell') then if not fs.exists('usr/config/shell') then
Util.writeTable('usr/config/shell', { Util.writeTable('usr/config/shell', {
aliases = shell.aliases(), aliases = shell.aliases(),
path = 'usr/apps:sys/apps:' .. shell.path(), path = 'usr/apps:sys/apps:' .. shell.path(),
lua_path = 'sys/apis:usr/apis', lua_path = 'sys/apis:usr/apis',
}) })
end end
local config = Util.readTable('usr/config/shell') local config = Util.readTable('usr/config/shell')
if config.aliases then if config.aliases then
for k in pairs(shell.aliases()) do for k in pairs(shell.aliases()) do
shell.clearAlias(k) shell.clearAlias(k)
end end
for k,v in pairs(config.aliases) do for k,v in pairs(config.aliases) do
shell.setAlias(k, v) shell.setAlias(k, v)
end end
end end
shell.setPath(config.path) shell.setPath(config.path)
os.setenv('LUA_PATH', config.lua_path) os.setenv('LUA_PATH', config.lua_path)

File diff suppressed because it is too large Load Diff

View File

@ -1,4 +1,4 @@
_G.requireInjector() _G.requireInjector(_ENV)
local Config = require('config') local Config = require('config')
local Util = require('util') local Util = require('util')
@ -25,367 +25,371 @@ shell.setEnv('multishell', multishell)
multishell.term = parentTerm --deprecated multishell.term = parentTerm --deprecated
local config = { local config = {
standard = { standard = {
textColor = colors.lightGray, textColor = colors.lightGray,
tabBarTextColor = colors.lightGray, tabBarTextColor = colors.lightGray,
focusTextColor = colors.white, focusTextColor = colors.white,
backgroundColor = colors.gray, backgroundColor = colors.gray,
tabBarBackgroundColor = colors.gray, tabBarBackgroundColor = colors.gray,
focusBackgroundColor = colors.gray, focusBackgroundColor = colors.gray,
}, },
color = { color = {
textColor = colors.lightGray, textColor = colors.lightGray,
tabBarTextColor = colors.lightGray, tabBarTextColor = colors.lightGray,
focusTextColor = colors.white, focusTextColor = colors.white,
backgroundColor = colors.gray, backgroundColor = colors.gray,
tabBarBackgroundColor = colors.gray, tabBarBackgroundColor = colors.gray,
focusBackgroundColor = colors.gray, focusBackgroundColor = colors.gray,
}, },
} }
Config.load('multishell', config) Config.load('multishell', config)
local _colors = parentTerm.isColor() and config.color or config.standard local _colors = parentTerm.isColor() and config.color or config.standard
local function redrawMenu() local function redrawMenu()
if not tabsDirty then if not tabsDirty then
os.queueEvent('multishell_redraw') os.queueEvent('multishell_redraw')
tabsDirty = true tabsDirty = true
end end
end end
function multishell.getFocus() function multishell.getFocus()
local currentTab = kernel.getFocused() local currentTab = kernel.getFocused()
return currentTab.uid return currentTab.uid
end end
function multishell.setFocus(tabId) function multishell.setFocus(tabId)
return kernel.raise(tabId) return kernel.raise(tabId)
end end
function multishell.getTitle(tabId) function multishell.getTitle(tabId)
local tab = kernel.find(tabId) local tab = kernel.find(tabId)
return tab and tab.title return tab and tab.title
end end
function multishell.setTitle(tabId, title) function multishell.setTitle(tabId, title)
local tab = kernel.find(tabId) local tab = kernel.find(tabId)
if tab then if tab then
tab.title = title tab.title = title
redrawMenu() redrawMenu()
end end
end end
function multishell.getCurrent() function multishell.getCurrent()
local runningTab = kernel.getCurrent() local runningTab = kernel.getCurrent()
return runningTab and runningTab.uid return runningTab and runningTab.uid
end end
function multishell.getTab(tabId) function multishell.getTab(tabId)
return kernel.find(tabId) return kernel.find(tabId)
end end
function multishell.terminate(tabId) function multishell.terminate(tabId)
os.queueEvent('multishell_terminate', tabId) os.queueEvent('multishell_terminate', tabId)
end end
function multishell.getTabs() function multishell.getTabs()
return kernel.routines return kernel.routines
end end
function multishell.launch( tProgramEnv, sProgramPath, ... ) function multishell.launch( tProgramEnv, sProgramPath, ... )
-- backwards compatibility -- backwards compatibility
return multishell.openTab({ return multishell.openTab({
env = tProgramEnv, env = tProgramEnv,
path = sProgramPath, path = sProgramPath,
args = { ... }, args = { ... },
}) })
end end
function multishell.openTab(tab) function multishell.openTab(tab)
if not tab.title and tab.path then if not tab.title and tab.path then
tab.title = fs.getName(tab.path):match('([^%.]+)') tab.title = fs.getName(tab.path):match('([^%.]+)')
end end
tab.title = tab.title or 'untitled' tab.title = tab.title or 'untitled'
tab.window = tab.window or window.create(parentTerm, 1, 2, w, h - 1, false) tab.window = tab.window or window.create(parentTerm, 1, 2, w, h - 1, false)
tab.terminal = tab.terminal or tab.window tab.terminal = tab.terminal or tab.window
local routine = kernel.newRoutine(tab) local routine = kernel.newRoutine(tab)
routine.co = coroutine.create(function() routine.co = coroutine.create(function()
local result, err local result, err
if tab.fn then if tab.fn then
result, err = Util.runFunction(routine.env, tab.fn, table.unpack(tab.args or { } )) result, err = Util.runFunction(routine.env, tab.fn, table.unpack(tab.args or { } ))
elseif tab.path then elseif tab.path then
result, err = Util.run(routine.env, tab.path, table.unpack(tab.args or { } )) result, err = Util.run(routine.env, tab.path, table.unpack(tab.args or { } ))
else else
err = 'multishell: invalid tab' err = 'multishell: invalid tab'
end end
if not result and err and err ~= 'Terminated' then if not result and err and err ~= 'Terminated' then
if err then if err then
printError(tostring(err)) printError(tostring(err))
end end
print('\nPress enter to close') print('\nPress enter to close')
routine.isDead = true routine.isDead = true
routine.hidden = false routine.hidden = false
while true do while true do
local e, code = os.pullEventRaw('key') local e, code = os.pullEventRaw('key')
if e == 'terminate' or e == 'key' and code == keys.enter then if e == 'terminate' or e == 'key' and code == keys.enter then
break break
end end
end end
end end
end) end)
kernel.launch(routine) kernel.launch(routine)
if tab.focused then if tab.focused then
multishell.setFocus(routine.uid) multishell.setFocus(routine.uid)
else else
redrawMenu() redrawMenu()
end end
return routine.uid return routine.uid
end end
function multishell.hideTab(tabId) function multishell.hideTab(tabId)
local tab = kernel.find(tabId) local tab = kernel.find(tabId)
if tab then if tab then
tab.hidden = true tab.hidden = true
kernel.lower(tab.uid) kernel.lower(tab.uid)
redrawMenu() redrawMenu()
end end
end end
function multishell.unhideTab(tabId) function multishell.unhideTab(tabId)
local tab = kernel.find(tabId) local tab = kernel.find(tabId)
if tab then if tab then
tab.hidden = false tab.hidden = false
redrawMenu() redrawMenu()
end end
end end
function multishell.getCount() function multishell.getCount()
return #kernel.routines return #kernel.routines
end end
kernel.hook('kernel_focus', function(_, eventData) kernel.hook('kernel_focus', function(_, eventData)
local previous = eventData[2] local previous = eventData[2]
if previous then if previous then
local routine = kernel.find(previous) local routine = kernel.find(previous)
if routine and routine.window then if routine and routine.window then
routine.window.setVisible(false) routine.window.setVisible(false)
if routine.hidden then if routine.hidden then
kernel.lower(previous) kernel.lower(previous)
end end
end end
end end
local focused = kernel.find(eventData[1]) local focused = kernel.find(eventData[1])
if focused and focused.window then if focused and focused.window then
focused.window.setVisible(true) focused.window.setVisible(true)
end end
redrawMenu() redrawMenu()
end) end)
kernel.hook('multishell_terminate', function(_, eventData) kernel.hook('multishell_terminate', function(_, eventData)
local tab = kernel.find(eventData[1]) local tab = kernel.find(eventData[1])
if tab and not tab.isOverview then if tab and not tab.isOverview then
if coroutine.status(tab.co) ~= 'dead' then if coroutine.status(tab.co) ~= 'dead' then
tab:resume("terminate") tab:resume("terminate")
end end
end end
return true return true
end)
kernel.hook('terminate', function()
return kernel.getFocused().isOverview
end) end)
kernel.hook('multishell_redraw', function() kernel.hook('multishell_redraw', function()
tabsDirty = false tabsDirty = false
local function write(x, text, bg, fg) local function write(x, text, bg, fg)
parentTerm.setBackgroundColor(bg) parentTerm.setBackgroundColor(bg)
parentTerm.setTextColor(fg) parentTerm.setTextColor(fg)
parentTerm.setCursorPos(x, 1) parentTerm.setCursorPos(x, 1)
parentTerm.write(text) parentTerm.write(text)
end end
local bg = _colors.tabBarBackgroundColor local bg = _colors.tabBarBackgroundColor
parentTerm.setBackgroundColor(bg) parentTerm.setBackgroundColor(bg)
parentTerm.setCursorPos(1, 1) parentTerm.setCursorPos(1, 1)
parentTerm.clearLine() parentTerm.clearLine()
local currentTab = kernel.getFocused() local currentTab = kernel.getFocused()
for _,tab in pairs(kernel.routines) do for _,tab in pairs(kernel.routines) do
if tab.hidden and tab ~= currentTab then if tab.hidden and tab ~= currentTab then
tab.width = 0 tab.width = 0
else else
tab.width = #tab.title + 1 tab.width = #tab.title + 1
end end
end end
local function width() local function width()
local tw = 0 local tw = 0
Util.each(kernel.routines, function(t) tw = tw + t.width end) Util.each(kernel.routines, function(t) tw = tw + t.width end)
return tw return tw
end end
while width() > w - 3 do while width() > w - 3 do
local tab = select(2, local tab = select(2,
Util.spairs(kernel.routines, function(a, b) return a.width > b.width end)()) Util.spairs(kernel.routines, function(a, b) return a.width > b.width end)())
tab.width = tab.width - 1 tab.width = tab.width - 1
end end
local function compareTab(a, b) local function compareTab(a, b)
if a.hidden then return false end if a.hidden then return false end
return b.hidden or a.uid < b.uid return b.hidden or a.uid < b.uid
end end
local tabX = 0 local tabX = 0
for _,tab in Util.spairs(kernel.routines, compareTab) do for _,tab in Util.spairs(kernel.routines, compareTab) do
if tab.width > 0 then if tab.width > 0 then
tab.sx = tabX + 1 tab.sx = tabX + 1
tab.ex = tabX + tab.width tab.ex = tabX + tab.width
tabX = tabX + tab.width tabX = tabX + tab.width
if tab ~= currentTab then if tab ~= currentTab then
write(tab.sx, tab.title:sub(1, tab.width - 1), write(tab.sx, tab.title:sub(1, tab.width - 1),
_colors.backgroundColor, _colors.textColor) _colors.backgroundColor, _colors.textColor)
end end
end end
end end
if currentTab then if currentTab then
write(currentTab.sx - 1, write(currentTab.sx - 1,
' ' .. currentTab.title:sub(1, currentTab.width - 1) .. ' ', ' ' .. currentTab.title:sub(1, currentTab.width - 1) .. ' ',
_colors.focusBackgroundColor, _colors.focusTextColor) _colors.focusBackgroundColor, _colors.focusTextColor)
if not currentTab.isOverview then if not currentTab.isOverview then
write(w, closeInd, _colors.backgroundColor, _colors.focusTextColor) write(w, closeInd, _colors.backgroundColor, _colors.focusTextColor)
end end
end end
if currentTab and currentTab.window then if currentTab and currentTab.window then
currentTab.window.restoreCursor() currentTab.window.restoreCursor()
end end
return true return true
end) end)
kernel.hook('term_resize', function(_, eventData) kernel.hook('term_resize', function(_, eventData)
if not eventData[1] then --- TEST if not eventData[1] then --- TEST
w,h = parentTerm.getSize() w,h = parentTerm.getSize()
local windowHeight = h-1 local windowHeight = h-1
for _,key in pairs(Util.keys(kernel.routines)) do for _,key in pairs(Util.keys(kernel.routines)) do
local tab = kernel.routines[key] local tab = kernel.routines[key]
local x,y = tab.window.getCursorPos() local x,y = tab.window.getCursorPos()
if y > windowHeight then if y > windowHeight then
tab.window.scroll(y - windowHeight) tab.window.scroll(y - windowHeight)
tab.window.setCursorPos(x, windowHeight) tab.window.setCursorPos(x, windowHeight)
end end
tab.window.reposition(1, 2, w, windowHeight) tab.window.reposition(1, 2, w, windowHeight)
end end
redrawMenu() redrawMenu()
end end
end) end)
kernel.hook('mouse_click', function(_, eventData) 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 y == 1 then
if x == 1 then if x == 1 then
multishell.setFocus(overviewId) multishell.setFocus(overviewId)
elseif x == w then elseif x == w then
local currentTab = kernel.getFocused() local currentTab = kernel.getFocused()
if currentTab then if currentTab then
multishell.terminate(currentTab.uid) multishell.terminate(currentTab.uid)
end end
else else
for _,tab in pairs(kernel.routines) do for _,tab in pairs(kernel.routines) do
if not tab.hidden and tab.sx then if not tab.hidden and tab.sx then
if x >= tab.sx and x <= tab.ex then if x >= tab.sx and x <= tab.ex then
multishell.setFocus(tab.uid) multishell.setFocus(tab.uid)
break break
end end
end end
end end
end end
return true return true
end end
eventData[3] = eventData[3] - 1 eventData[3] = eventData[3] - 1
end) end)
kernel.hook({ 'mouse_up', 'mouse_drag' }, function(_, eventData) kernel.hook({ 'mouse_up', 'mouse_drag' }, function(_, eventData)
eventData[3] = eventData[3] - 1 eventData[3] = eventData[3] - 1
end) end)
kernel.hook('mouse_scroll', function(_, eventData) kernel.hook('mouse_scroll', function(_, eventData)
if eventData[3] == 1 then if eventData[3] == 1 then
return true return true
end end
eventData[3] = eventData[3] - 1 eventData[3] = eventData[3] - 1
end) end)
local function startup() local function startup()
local success = true local success = true
local function runDir(directory, open) local function runDir(directory, open)
if not fs.exists(directory) then if not fs.exists(directory) then
return true return true
end end
local files = fs.list(directory) local files = fs.list(directory)
table.sort(files) table.sort(files)
for _,file in ipairs(files) do for _,file in ipairs(files) do
os.sleep(0) os.sleep(0)
local result, err = open(directory .. '/' .. file) local result, err = open(directory .. '/' .. file)
if result then if result then
if term.isColor() then if term.isColor() then
term.setTextColor(colors.green) term.setTextColor(colors.green)
end end
term.write('[PASS] ') term.write('[PASS] ')
term.setTextColor(colors.white) term.setTextColor(colors.white)
term.write(fs.combine(directory, file)) term.write(fs.combine(directory, file))
print() print()
else else
if term.isColor() then if term.isColor() then
term.setTextColor(colors.red) term.setTextColor(colors.red)
end end
term.write('[FAIL] ') term.write('[FAIL] ')
term.setTextColor(colors.white) term.setTextColor(colors.white)
term.write(fs.combine(directory, file)) term.write(fs.combine(directory, file))
if err then if err then
_G.printError('\n' .. err) _G.printError('\n' .. err)
end end
print() print()
success = false success = false
end end
end end
end end
runDir('sys/autorun', shell.run) runDir('sys/autorun', shell.run)
runDir('usr/autorun', shell.run) runDir('usr/autorun', shell.run)
if not success then if not success then
multishell.setFocus(multishell.getCurrent()) multishell.setFocus(multishell.getCurrent())
printError('\nA startup program has errored') printError('\nA startup program has errored')
os.pullEvent('terminate') os.pullEvent('terminate')
end end
end end
kernel.hook('kernel_ready', function() kernel.hook('kernel_ready', function()
overviewId = multishell.openTab({ overviewId = multishell.openTab({
path = 'sys/apps/Overview.lua', path = 'sys/apps/Overview.lua',
isOverview = true, isOverview = true,
focused = true, focused = true,
title = '+', title = '+',
}) })
multishell.openTab({ multishell.openTab({
fn = startup, fn = startup,
title = 'Autorun', title = 'Autorun',
}) })
end) end)

View File

@ -4,9 +4,9 @@ local Terminal = require('terminal')
local Util = require('util') local Util = require('util')
_G.kernel = { _G.kernel = {
UID = 0, UID = 0,
hooks = { }, hooks = { },
routines = { }, routines = { },
} }
local fs = _G.fs local fs = _G.fs
@ -23,271 +23,271 @@ kernel.window = window.create(kernel.terminal, 1, 1, w, h, false)
Terminal.scrollable(kernel.window) Terminal.scrollable(kernel.window)
local focusedRoutineEvents = Util.transpose { local focusedRoutineEvents = Util.transpose {
'char', 'key', 'key_up', 'char', 'key', 'key_up',
'mouse_click', 'mouse_drag', 'mouse_scroll', 'mouse_up', 'mouse_click', 'mouse_drag', 'mouse_scroll', 'mouse_up',
'paste', 'terminate', 'paste', 'terminate',
} }
_G.debug = function(pattern, ...) _G.debug = function(pattern, ...)
local oldTerm = term.redirect(kernel.window) local oldTerm = term.redirect(kernel.window)
Util.print(pattern, ...) Util.print(pattern, ...)
term.redirect(oldTerm) term.redirect(oldTerm)
end end
-- any function that runs in a kernel hook does not run in -- any function that runs in a kernel hook does not run in
-- a separate coroutine or have a window. an error in a hook -- a separate coroutine or have a window. an error in a hook
-- function will crash the system. -- function will crash the system.
function kernel.hook(event, fn) function kernel.hook(event, fn)
if type(event) == 'table' then if type(event) == 'table' then
for _,v in pairs(event) do for _,v in pairs(event) do
kernel.hook(v, fn) kernel.hook(v, fn)
end end
else else
if not kernel.hooks[event] then if not kernel.hooks[event] then
kernel.hooks[event] = { } kernel.hooks[event] = { }
end end
table.insert(kernel.hooks[event], fn) table.insert(kernel.hooks[event], fn)
end end
end end
-- you can only unhook from within the function that hooked -- you can only unhook from within the function that hooked
function kernel.unhook(event, fn) function kernel.unhook(event, fn)
local eventHooks = kernel.hooks[event] local eventHooks = kernel.hooks[event]
if eventHooks then if eventHooks then
Util.removeByValue(eventHooks, fn) Util.removeByValue(eventHooks, fn)
if #eventHooks == 0 then if #eventHooks == 0 then
kernel.hooks[event] = nil kernel.hooks[event] = nil
end end
end end
end end
local Routine = { } local Routine = { }
function Routine:resume(event, ...) function Routine:resume(event, ...)
if not self.co or coroutine.status(self.co) == 'dead' then if not self.co or coroutine.status(self.co) == 'dead' then
return return
end end
if not self.filter or self.filter == event or event == "terminate" then if not self.filter or self.filter == event or event == "terminate" then
local previousTerm = term.redirect(self.terminal) local previousTerm = term.redirect(self.terminal)
local previous = kernel.running local previous = kernel.running
kernel.running = self -- stupid shell set title kernel.running = self -- stupid shell set title
local ok, result = coroutine.resume(self.co, event, ...) local ok, result = coroutine.resume(self.co, event, ...)
kernel.running = previous kernel.running = previous
if ok then if ok then
self.filter = result self.filter = result
else else
_G.printError(result) _G.printError(result)
end end
self.terminal = term.current() self.terminal = term.current()
term.redirect(previousTerm) term.redirect(previousTerm)
if not ok and self.haltOnError then if not ok and self.haltOnError then
error(result) error(result)
end end
if coroutine.status(self.co) == 'dead' then if coroutine.status(self.co) == 'dead' then
Util.removeByValue(kernel.routines, self) Util.removeByValue(kernel.routines, self)
if #kernel.routines > 0 then if #kernel.routines > 0 then
os.queueEvent('kernel_focus', kernel.routines[1].uid) os.queueEvent('kernel_focus', kernel.routines[1].uid)
end end
if self.haltOnExit then if self.haltOnExit then
kernel.halt() kernel.halt()
end end
end end
return ok, result return ok, result
end end
end end
function kernel.getFocused() function kernel.getFocused()
return kernel.routines[1] return kernel.routines[1]
end end
function kernel.getCurrent() function kernel.getCurrent()
return kernel.running return kernel.running
end end
function kernel.newRoutine(args) function kernel.newRoutine(args)
kernel.UID = kernel.UID + 1 kernel.UID = kernel.UID + 1
local routine = setmetatable({ local routine = setmetatable({
uid = kernel.UID, uid = kernel.UID,
timestamp = os.clock(), timestamp = os.clock(),
terminal = kernel.window, terminal = kernel.window,
window = kernel.window, window = kernel.window,
}, { __index = Routine }) }, { __index = Routine })
Util.merge(routine, args) Util.merge(routine, args)
routine.env = args.env or Util.shallowCopy(shell.getEnv()) routine.env = args.env or Util.shallowCopy(shell.getEnv())
return routine return routine
end end
function kernel.launch(routine) function kernel.launch(routine)
routine.co = routine.co or coroutine.create(function() routine.co = routine.co or coroutine.create(function()
local result, err local result, err
if routine.fn then if routine.fn then
result, err = Util.runFunction(routine.env, routine.fn, table.unpack(routine.args or { } )) result, err = Util.runFunction(routine.env, routine.fn, table.unpack(routine.args or { } ))
elseif routine.path then elseif routine.path then
result, err = Util.run(routine.env, routine.path, table.unpack(routine.args or { } )) result, err = Util.run(routine.env, routine.path, table.unpack(routine.args or { } ))
else else
err = 'kernel: invalid routine' err = 'kernel: invalid routine'
end end
if not result and err ~= 'Terminated' then if not result and err ~= 'Terminated' then
error(err or 'Error occurred', 2) error(err or 'Error occurred', 2)
end end
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 end
function kernel.run(args) function kernel.run(args)
local routine = kernel.newRoutine(args) local routine = kernel.newRoutine(args)
kernel.launch(routine) kernel.launch(routine)
return routine return routine
end end
function kernel.raise(uid) function kernel.raise(uid)
local routine = Util.find(kernel.routines, 'uid', uid) local routine = Util.find(kernel.routines, 'uid', uid)
if routine then if routine then
local previous = kernel.routines[1] local previous = kernel.routines[1]
if routine ~= previous then if routine ~= previous then
Util.removeByValue(kernel.routines, routine) Util.removeByValue(kernel.routines, routine)
table.insert(kernel.routines, 1, routine) table.insert(kernel.routines, 1, routine)
end end
os.queueEvent('kernel_focus', routine.uid, previous and previous.uid) os.queueEvent('kernel_focus', routine.uid, previous and previous.uid)
return true return true
end end
return false return false
end end
function kernel.lower(uid) 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 and #kernel.routines > 1 then
if routine == kernel.routines[1] then if routine == kernel.routines[1] then
local nextRoutine = kernel.routines[2] local nextRoutine = kernel.routines[2]
if nextRoutine then if nextRoutine then
kernel.raise(nextRoutine.uid) kernel.raise(nextRoutine.uid)
end end
end end
Util.removeByValue(kernel.routines, routine) Util.removeByValue(kernel.routines, routine)
table.insert(kernel.routines, routine) table.insert(kernel.routines, routine)
return true return true
end end
return false return false
end end
function kernel.find(uid) function kernel.find(uid)
return Util.find(kernel.routines, 'uid', uid) return Util.find(kernel.routines, 'uid', uid)
end end
function kernel.halt() function kernel.halt()
os.queueEvent('kernel_halt') os.queueEvent('kernel_halt')
end end
function kernel.event(event, eventData) function kernel.event(event, eventData)
local stopPropagation local stopPropagation
local eventHooks = kernel.hooks[event] local eventHooks = kernel.hooks[event]
if eventHooks then if eventHooks then
for i = #eventHooks, 1, -1 do for i = #eventHooks, 1, -1 do
stopPropagation = eventHooks[i](event, eventData) stopPropagation = eventHooks[i](event, eventData)
if stopPropagation then if stopPropagation then
break break
end end
end end
end end
if not stopPropagation then if not stopPropagation then
if focusedRoutineEvents[event] then if focusedRoutineEvents[event] then
local active = kernel.routines[1] local active = kernel.routines[1]
if active then if active then
active:resume(event, table.unpack(eventData)) active:resume(event, table.unpack(eventData))
end end
else else
-- Passthrough to all processes -- Passthrough to all processes
for _,routine in pairs(Util.shallowCopy(kernel.routines)) do for _,routine in pairs(Util.shallowCopy(kernel.routines)) do
routine:resume(event, table.unpack(eventData)) routine:resume(event, table.unpack(eventData))
end end
end end
end end
end end
function kernel.start() function kernel.start()
local s, m = pcall(function() local s, m = pcall(function()
repeat repeat
local eventData = { os.pullEventRaw() } local eventData = { os.pullEventRaw() }
local event = table.remove(eventData, 1) local event = table.remove(eventData, 1)
kernel.event(event, eventData) kernel.event(event, eventData)
until event == 'kernel_halt' until event == 'kernel_halt'
end) end)
if not s then if not s then
kernel.window.setVisible(true) kernel.window.setVisible(true)
term.redirect(kernel.window) term.redirect(kernel.window)
print('\nCrash detected\n') print('\nCrash detected\n')
_G.printError(m) _G.printError(m)
end end
term.redirect(kernel.terminal) term.redirect(kernel.terminal)
end end
local function init(...) 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') print('Starting Opus OS')
local dir = 'sys/extensions' local dir = 'sys/extensions'
local files = fs.list(dir) local files = fs.list(dir)
table.sort(files) table.sort(files)
for _,file in ipairs(files) do for _,file in ipairs(files) do
local level = file:match('(%d).%S+.lua') local level = file:match('(%d).%S+.lua')
if tonumber(level) <= runLevel then if tonumber(level) <= runLevel then
local s, m = shell.run(fs.combine(dir, file)) local s, m = shell.run(fs.combine(dir, file))
if not s then if not s then
error(m) error(m)
end end
os.sleep(0) os.sleep(0)
end end
end end
os.queueEvent('kernel_ready') os.queueEvent('kernel_ready')
if args[1] then if args[1] then
kernel.hook('kernel_ready', function() kernel.hook('kernel_ready', function()
local s, m = kernel.run({ local s, m = kernel.run({
title = args[1], title = args[1],
path = 'sys/apps/shell', path = 'sys/apps/shell',
args = args, args = args,
haltOnExit = true, haltOnExit = true,
haltOnError = true, haltOnError = true,
terminal = kernel.terminal, terminal = kernel.terminal,
}) })
if s then if s then
kernel.raise(s.uid) kernel.raise(s.uid)
else else
error(m) error(m)
end end
end) end)
end end
end end
kernel.run({ kernel.run({
fn = init, fn = init,
title = 'init', title = 'init',
haltOnError = true, haltOnError = true,
args = { ... }, args = { ... },
}) })
kernel.start() kernel.start()

View File

@ -1,5 +1,5 @@
--[[ --[[
Allow sharing of local peripherals. Allow sharing of local peripherals.
]]-- ]]--
local Event = require('event') local Event = require('event')
@ -7,76 +7,76 @@ local Peripheral = require('peripheral')
local Socket = require('socket') local Socket = require('socket')
Event.addRoutine(function() Event.addRoutine(function()
print('peripheral: listening on port 189') print('peripheral: listening on port 189')
while true do while true do
local socket = Socket.server(189) local socket = Socket.server(189)
print('peripheral: connection from ' .. socket.dhost) print('peripheral: connection from ' .. socket.dhost)
Event.addRoutine(function() Event.addRoutine(function()
local uri = socket:read(2) local uri = socket:read(2)
if uri then if uri then
local peripheral = Peripheral.lookup(uri) local peripheral = Peripheral.lookup(uri)
-- only 1 proxy of this device can happen at one time -- only 1 proxy of this device can happen at one time
-- need to prevent multiple shares -- need to prevent multiple shares
if not peripheral then if not peripheral then
print('peripheral: invalid peripheral ' .. uri) print('peripheral: invalid peripheral ' .. uri)
socket:write('Invalid peripheral: ' .. uri) socket:write('Invalid peripheral: ' .. uri)
else else
print('peripheral: proxing ' .. uri) print('peripheral: proxing ' .. uri)
local proxy = { local proxy = {
methods = { } methods = { }
} }
if peripheral.blit then if peripheral.blit then
--peripheral = Util.shallowCopy(peripheral) --peripheral = Util.shallowCopy(peripheral)
peripheral.fastBlit = function(data) peripheral.fastBlit = function(data)
for _,v in ipairs(data) do for _,v in ipairs(data) do
peripheral[v.fn](unpack(v.args)) peripheral[v.fn](unpack(v.args))
end end
end end
end end
for k,v in pairs(peripheral) do for k,v in pairs(peripheral) do
if type(v) == 'function' then if type(v) == 'function' then
table.insert(proxy.methods, k) table.insert(proxy.methods, k)
else else
proxy[k] = v proxy[k] = v
end end
end end
socket:write(proxy) socket:write(proxy)
if proxy.type == 'monitor' then if proxy.type == 'monitor' then
peripheral.eventChannel = function(...) peripheral.eventChannel = function(...)
socket:write({ socket:write({
fn = 'event', fn = 'event',
data = { ... } data = { ... }
}) })
end end
end end
while true do while true do
local data = socket:read() local data = socket:read()
if not data then if not data then
print('peripheral: lost connection from ' .. socket.dhost) print('peripheral: lost connection from ' .. socket.dhost)
break break
end end
if not _G.device[peripheral.name] then if not _G.device[peripheral.name] then
print('periperal: detached') print('periperal: detached')
socket:close() socket:close()
break break
end end
if peripheral[data.fn] then if peripheral[data.fn] then
socket:write({ peripheral[data.fn](table.unpack(data.args)) }) socket:write({ peripheral[data.fn](table.unpack(data.args)) })
end end
end end
peripheral.eventChannel = nil peripheral.eventChannel = nil
peripheral.fastBlit = nil peripheral.fastBlit = nil
end end
end end
end) end)
end end
end) end)

View File

@ -2,18 +2,18 @@ local Event = require('event')
local Socket = require('socket') local Socket = require('socket')
Event.addRoutine(function() Event.addRoutine(function()
while true do while true do
print('proxy: listening on port 188') print('proxy: listening on port 188')
local socket = Socket.server(188) local socket = Socket.server(188)
print('proxy: connection from ' .. socket.dhost) print('proxy: connection from ' .. socket.dhost)
Event.addRoutine(function() Event.addRoutine(function()
local api = socket:read(2) local api = socket:read(2)
if api then if api then
local proxy = _G[api] local proxy = _G[api]
local methods = { } local methods = { }
for k,v in pairs(proxy) do for k,v in pairs(proxy) do
if type(v) == 'function' then if type(v) == 'function' then
table.insert(methods, k) table.insert(methods, k)
@ -21,14 +21,14 @@ Event.addRoutine(function()
end end
socket:write(methods) socket:write(methods)
while true do while true do
local data = socket:read() local data = socket:read()
if not data then if not data then
print('proxy: lost connection from ' .. socket.dhost) print('proxy: lost connection from ' .. socket.dhost)
break break
end end
socket:write({ proxy[data.fn](table.unpack(data.args)) }) socket:write({ proxy[data.fn](table.unpack(data.args)) })
end end
end end
end) end)
end end

View File

@ -7,77 +7,77 @@ local fileUid = 0
local fileHandles = { } local fileHandles = { }
local function remoteOpen(fn, fl) local function remoteOpen(fn, fl)
local fh = fs.open(fn, fl) local fh = fs.open(fn, fl)
if fh then if fh then
local methods = { 'close', 'write', 'writeLine', 'flush', 'read', 'readLine', 'readAll', } local methods = { 'close', 'write', 'writeLine', 'flush', 'read', 'readLine', 'readAll', }
fileUid = fileUid + 1 fileUid = fileUid + 1
fileHandles[fileUid] = fh fileHandles[fileUid] = fh
local vfh = { local vfh = {
methods = { }, methods = { },
fileUid = fileUid, fileUid = fileUid,
} }
for _,m in ipairs(methods) do for _,m in ipairs(methods) do
if fh[m] then if fh[m] then
table.insert(vfh.methods, m) table.insert(vfh.methods, m)
end end
end end
return vfh return vfh
end end
end end
local function remoteFileOperation(fileId, op, ...) local function remoteFileOperation(fileId, op, ...)
local fh = fileHandles[fileId] local fh = fileHandles[fileId]
if fh then if fh then
return fh[op](...) return fh[op](...)
end end
end end
local function sambaConnection(socket) local function sambaConnection(socket)
while true do while true do
local msg = socket:read() local msg = socket:read()
if not msg then if not msg then
break break
end end
local fn = fs[msg.fn] local fn = fs[msg.fn]
if msg.fn == 'open' then if msg.fn == 'open' then
fn = remoteOpen fn = remoteOpen
elseif msg.fn == 'fileOp' then elseif msg.fn == 'fileOp' then
fn = remoteFileOperation fn = remoteFileOperation
end end
local ret local ret
local s, m = pcall(function() local s, m = pcall(function()
ret = fn(unpack(msg.args)) ret = fn(unpack(msg.args))
end) end)
if not s and m then if not s and m then
_G.printError('samba: ' .. m) _G.printError('samba: ' .. m)
end end
socket:write({ response = ret }) socket:write({ response = ret })
end end
print('samba: Connection closed') print('samba: Connection closed')
end end
Event.addRoutine(function() Event.addRoutine(function()
print('samba: listening on port 139') print('samba: listening on port 139')
while true do while true do
local socket = Socket.server(139) local socket = Socket.server(139)
Event.addRoutine(function() Event.addRoutine(function()
print('samba: connection from ' .. socket.dhost) print('samba: connection from ' .. socket.dhost)
sambaConnection(socket) sambaConnection(socket)
print('samba: closing connection to ' .. socket.dhost) print('samba: closing connection to ' .. socket.dhost)
end) end)
end end
end) end)
Event.on('network_attach', function(_, computer) 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) end)
Event.on('network_detach', function(_, computer) Event.on('network_detach', function(_, computer)
print('samba: detaching ' .. computer.label) print('samba: detaching ' .. computer.label)
fs.unmount(fs.combine('network', computer.label)) fs.unmount(fs.combine('network', computer.label))
end) end)

View File

@ -15,156 +15,156 @@ local gpsLastPoint
local gpsLastRequestTime local gpsLastRequestTime
local function snmpConnection(socket) local function snmpConnection(socket)
while true do while true do
local msg = socket:read() local msg = socket:read()
if not msg then if not msg then
break break
end end
if msg.type == 'reboot' then if msg.type == 'reboot' then
os.reboot() os.reboot()
elseif msg.type == 'shutdown' then elseif msg.type == 'shutdown' then
os.shutdown() os.shutdown()
elseif msg.type == 'ping' then elseif msg.type == 'ping' then
socket:write('pong') socket:write('pong')
elseif msg.type == 'script' then elseif msg.type == 'script' then
local fn, err = loadstring(msg.args, 'script') local fn, err = loadstring(msg.args, 'script')
if fn then if fn then
kernel.run({ kernel.run({
fn = fn, fn = fn,
title = 'script', title = 'script',
}) })
else else
_G.printError(err) _G.printError(err)
end end
elseif msg.type == 'scriptEx' then elseif msg.type == 'scriptEx' then
local s, m = pcall(function() local s, m = pcall(function()
local env = setmetatable(Util.shallowCopy(_ENV), { __index = _G }) local env = setmetatable(Util.shallowCopy(_ENV), { __index = _G })
local fn, m = load(msg.args, 'script', nil, env) local fn, m = load(msg.args, 'script', nil, env)
if not fn then if not fn then
error(m) error(m)
end end
return { fn() } return { fn() }
end) end)
if s then if s then
socket:write(m) socket:write(m)
else else
socket:write({ s, m }) socket:write({ s, m })
end end
elseif msg.type == 'gps' then elseif msg.type == 'gps' then
if gpsRequested then if gpsRequested then
repeat repeat
os.sleep(0) os.sleep(0)
until not gpsRequested until not gpsRequested
end end
if gpsLastPoint and os.clock() - gpsLastRequestTime < .5 then if gpsLastPoint and os.clock() - gpsLastRequestTime < .5 then
socket:write(gpsLastPoint) socket:write(gpsLastPoint)
else else
gpsRequested = true gpsRequested = true
local pt = GPS.getPoint(2) local pt = GPS.getPoint(2)
if pt then if pt then
socket:write(pt) socket:write(pt)
else else
print('snmp: Unable to get GPS point') print('snmp: Unable to get GPS point')
end end
gpsRequested = false gpsRequested = false
gpsLastPoint = pt gpsLastPoint = pt
if pt then if pt then
gpsLastRequestTime = os.clock() gpsLastRequestTime = os.clock()
end end
end end
elseif msg.type == 'info' then elseif msg.type == 'info' then
local info = { local info = {
id = os.getComputerID(), id = os.getComputerID(),
label = os.getComputerLabel(), label = os.getComputerLabel(),
uptime = math.floor(os.clock()), uptime = math.floor(os.clock()),
} }
if turtle then if turtle then
info.fuel = turtle.getFuelLevel() info.fuel = turtle.getFuelLevel()
info.status = turtle.getStatus() info.status = turtle.getStatus()
end end
socket:write(info) socket:write(info)
end end
end end
end end
Event.addRoutine(function() Event.addRoutine(function()
print('snmp: listening on port 161') print('snmp: listening on port 161')
while true do while true do
local socket = Socket.server(161) local socket = Socket.server(161)
Event.addRoutine(function() Event.addRoutine(function()
print('snmp: connection from ' .. socket.dhost) print('snmp: connection from ' .. socket.dhost)
snmpConnection(socket) snmpConnection(socket)
print('snmp: closing connection to ' .. socket.dhost) print('snmp: closing connection to ' .. socket.dhost)
end) end)
end end
end) end)
device.wireless_modem.open(999) device.wireless_modem.open(999)
print('discovery: listening on port 999') print('discovery: listening on port 999')
Event.on('modem_message', function(_, _, sport, id, info, distance) Event.on('modem_message', function(_, _, sport, id, info, distance)
if sport == 999 and tonumber(id) and type(info) == 'table' then if sport == 999 and tonumber(id) and type(info) == 'table' then
if not network[id] then if not network[id] then
network[id] = { } network[id] = { }
end end
Util.merge(network[id], info) Util.merge(network[id], info)
network[id].distance = distance network[id].distance = distance
network[id].timestamp = os.clock() network[id].timestamp = os.clock()
if not network[id].active then if not network[id].active then
network[id].active = true network[id].active = true
os.queueEvent('network_attach', network[id]) os.queueEvent('network_attach', network[id])
end end
end end
end) end)
local info = { local info = {
id = os.getComputerID() id = os.getComputerID()
} }
local infoTimer = os.clock() local infoTimer = os.clock()
local function sendInfo() local function sendInfo()
if os.clock() - infoTimer >= 1 then -- don't flood if os.clock() - infoTimer >= 1 then -- don't flood
infoTimer = os.clock() infoTimer = os.clock()
info.label = os.getComputerLabel() info.label = os.getComputerLabel()
info.uptime = math.floor(os.clock()) info.uptime = math.floor(os.clock())
if turtle then if turtle then
info.fuel = turtle.getFuelLevel() info.fuel = turtle.getFuelLevel()
info.status = turtle.getStatus() info.status = turtle.getStatus()
info.point = turtle.point info.point = turtle.point
info.inventory = turtle.getInventory() info.inventory = turtle.getInventory()
info.slotIndex = turtle.getSelectedSlot() info.slotIndex = turtle.getSelectedSlot()
end end
device.wireless_modem.transmit(999, os.getComputerID(), info) device.wireless_modem.transmit(999, os.getComputerID(), info)
end end
end end
-- every 10 seconds, send out this computer's info -- every 10 seconds, send out this computer's info
Event.onInterval(10, function() Event.onInterval(10, function()
sendInfo() sendInfo()
for _,c in pairs(_G.network) do for _,c in pairs(_G.network) do
local elapsed = os.clock()-c.timestamp local elapsed = os.clock()-c.timestamp
if c.active and elapsed > 15 then if c.active and elapsed > 15 then
c.active = false c.active = false
os.queueEvent('network_detach', c) os.queueEvent('network_detach', c)
end end
end end
end) end)
Event.on('turtle_response', function() Event.on('turtle_response', function()
if turtle.getStatus() ~= info.status or if turtle.getStatus() ~= info.status or
turtle.fuel ~= info.fuel then turtle.fuel ~= info.fuel then
sendInfo() sendInfo()
end end
end) end)

View File

@ -7,76 +7,76 @@ local term = _G.term
local window = _G.window local window = _G.window
local function telnetHost(socket) local function telnetHost(socket)
local methods = { 'clear', 'clearLine', 'setCursorPos', 'write', 'blit', local methods = { 'clear', 'clearLine', 'setCursorPos', 'write', 'blit',
'setTextColor', 'setTextColour', 'setBackgroundColor', 'setTextColor', 'setTextColour', 'setBackgroundColor',
'setBackgroundColour', 'scroll', 'setCursorBlink', } 'setBackgroundColour', 'scroll', 'setCursorBlink', }
local termInfo = socket:read(5) local termInfo = socket:read(5)
if not termInfo then if not termInfo then
_G.printError('read failed') _G.printError('read failed')
return return
end end
local win = window.create(_G.device.terminal, 1, 1, termInfo.width, termInfo.height, false) local win = window.create(_G.device.terminal, 1, 1, termInfo.width, termInfo.height, false)
win.setCursorPos(table.unpack(termInfo.pos)) win.setCursorPos(table.unpack(termInfo.pos))
for _,k in pairs(methods) do for _,k in pairs(methods) do
local fn = win[k] local fn = win[k]
win[k] = function(...) win[k] = function(...)
if not socket.queue then if not socket.queue then
socket.queue = { } socket.queue = { }
Event.onTimeout(0, function() Event.onTimeout(0, function()
socket:write(socket.queue) socket:write(socket.queue)
socket.queue = nil socket.queue = nil
end) end)
end end
table.insert(socket.queue, { table.insert(socket.queue, {
f = k, f = k,
args = { ... }, args = { ... },
}) })
fn(...) fn(...)
end end
end end
local shellThread = kernel.run({ local shellThread = kernel.run({
terminal = win, terminal = win,
window = win, window = win,
title = 'Telnet client', title = 'Telnet client',
hidden = true, hidden = true,
co = coroutine.create(function() co = coroutine.create(function()
Util.run(_ENV, 'sys/apps/shell', table.unpack(termInfo.program)) Util.run(_ENV, 'sys/apps/shell', table.unpack(termInfo.program))
if socket.queue then if socket.queue then
socket:write(socket.queue) socket:write(socket.queue)
end end
socket:close() socket:close()
end) end)
}) })
Event.addRoutine(function() Event.addRoutine(function()
while true do while true do
local data = socket:read() local data = socket:read()
if not data then if not data then
shellThread:resume('terminate') shellThread:resume('terminate')
break break
end end
local previousTerm = term.current() local previousTerm = term.current()
shellThread:resume(table.unpack(data)) shellThread:resume(table.unpack(data))
term.redirect(previousTerm) term.redirect(previousTerm)
end end
end) end)
end end
Event.addRoutine(function() Event.addRoutine(function()
print('telnet: listening on port 23') print('telnet: listening on port 23')
while true do while true do
local socket = Socket.server(23) local socket = Socket.server(23)
print('telnet: connection from ' .. socket.dhost) print('telnet: connection from ' .. socket.dhost)
Event.addRoutine(function() Event.addRoutine(function()
telnetHost(socket) telnetHost(socket)
end) end)
end end
end) end)

View File

@ -1,8 +1,8 @@
--[[ --[[
Low level socket protocol implementation. Low level socket protocol implementation.
* sequencing * sequencing
* background read buffering * background read buffering
]]-- ]]--
local Event = require('event') local Event = require('event')
@ -11,120 +11,120 @@ local os = _G.os
local computerId = os.getComputerID() local computerId = os.getComputerID()
local transport = { local transport = {
timers = { }, timers = { },
sockets = { }, sockets = { },
UID = 0, UID = 0,
} }
_G.transport = transport _G.transport = transport
function transport.open(socket) function transport.open(socket)
transport.UID = transport.UID + 1 transport.UID = transport.UID + 1
transport.sockets[socket.sport] = socket transport.sockets[socket.sport] = socket
socket.activityTimer = os.clock() socket.activityTimer = os.clock()
socket.uid = transport.UID socket.uid = transport.UID
end end
function transport.read(socket) function transport.read(socket)
local data = table.remove(socket.messages, 1) local data = table.remove(socket.messages, 1)
if data then if data then
return unpack(data) return unpack(data)
end end
end end
function transport.write(socket, data) function transport.write(socket, data)
--debug('>> ' .. Util.tostring({ type = 'DATA', seq = socket.wseq })) --debug('>> ' .. Util.tostring({ type = 'DATA', seq = socket.wseq }))
socket.transmit(socket.dport, socket.dhost, data) socket.transmit(socket.dport, socket.dhost, data)
--local timerId = os.startTimer(3) --local timerId = os.startTimer(3)
--transport.timers[timerId] = socket --transport.timers[timerId] = socket
--socket.timers[socket.wseq] = timerId --socket.timers[socket.wseq] = timerId
socket.wseq = socket.wseq + 1 socket.wseq = socket.wseq + 1
end end
function transport.ping(socket) function transport.ping(socket)
--debug('>> ' .. Util.tostring({ type = 'DATA', seq = socket.wseq })) --debug('>> ' .. Util.tostring({ type = 'DATA', seq = socket.wseq }))
if os.clock() - socket.activityTimer > 10 then if os.clock() - socket.activityTimer > 10 then
socket.activityTimer = os.clock() socket.activityTimer = os.clock()
socket.transmit(socket.dport, socket.dhost, { socket.transmit(socket.dport, socket.dhost, {
type = 'PING', type = 'PING',
seq = -1, seq = -1,
}) })
local timerId = os.startTimer(3) local timerId = os.startTimer(3)
transport.timers[timerId] = socket transport.timers[timerId] = socket
socket.timers[-1] = timerId socket.timers[-1] = timerId
end end
end end
function transport.close(socket) function transport.close(socket)
transport.sockets[socket.sport] = nil transport.sockets[socket.sport] = nil
end end
Event.on('timer', function(_, timerId) Event.on('timer', function(_, timerId)
local socket = transport.timers[timerId] local socket = transport.timers[timerId]
if socket and socket.connected then if socket and socket.connected then
print('transport timeout - closing socket ' .. socket.sport) print('transport timeout - closing socket ' .. socket.sport)
socket:close() socket:close()
transport.timers[timerId] = nil transport.timers[timerId] = nil
end end
end) end)
Event.on('modem_message', function(_, _, dport, dhost, msg, distance) Event.on('modem_message', function(_, _, dport, dhost, msg, distance)
if dhost == computerId and msg then if dhost == computerId and msg then
local socket = transport.sockets[dport] local socket = transport.sockets[dport]
if socket and socket.connected then 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 if msg.type == 'DISC' then
-- received disconnect from other end -- received disconnect from other end
if socket.connected then if socket.connected then
os.queueEvent('transport_' .. socket.uid) os.queueEvent('transport_' .. socket.uid)
end end
socket.connected = false socket.connected = false
socket:close() socket:close()
elseif msg.type == 'ACK' then elseif msg.type == 'ACK' then
local ackTimerId = socket.timers[msg.seq] local ackTimerId = socket.timers[msg.seq]
if ackTimerId then if ackTimerId then
os.cancelTimer(ackTimerId) os.cancelTimer(ackTimerId)
socket.timers[msg.seq] = nil socket.timers[msg.seq] = nil
socket.activityTimer = os.clock() socket.activityTimer = os.clock()
transport.timers[ackTimerId] = nil transport.timers[ackTimerId] = nil
end end
elseif msg.type == 'PING' then elseif msg.type == 'PING' then
socket.activityTimer = os.clock() socket.activityTimer = os.clock()
socket.transmit(socket.dport, socket.dhost, { socket.transmit(socket.dport, socket.dhost, {
type = 'ACK', type = 'ACK',
seq = msg.seq, seq = msg.seq,
}) })
elseif msg.type == 'DATA' and msg.data then elseif msg.type == 'DATA' and msg.data then
socket.activityTimer = os.clock() socket.activityTimer = os.clock()
if msg.seq ~= socket.rseq then if msg.seq ~= socket.rseq then
print('transport seq error - closing socket ' .. socket.sport) print('transport seq error - closing socket ' .. socket.sport)
socket:close() socket:close()
else else
socket.rseq = socket.rseq + 1 socket.rseq = socket.rseq + 1
table.insert(socket.messages, { msg.data, distance }) table.insert(socket.messages, { msg.data, distance })
-- use resume instead ?? -- use resume instead ??
if not socket.messages[2] then -- table size is 1 if not socket.messages[2] then -- table size is 1
os.queueEvent('transport_' .. socket.uid) os.queueEvent('transport_' .. socket.uid)
end end
--debug('>> ' .. Util.tostring({ type = 'ACK', seq = msg.seq })) --debug('>> ' .. Util.tostring({ type = 'ACK', seq = msg.seq }))
--socket.transmit(socket.dport, socket.dhost, { --socket.transmit(socket.dport, socket.dhost, {
-- type = 'ACK', -- type = 'ACK',
-- seq = msg.seq, -- seq = msg.seq,
--}) --})
end end
end end
end end
end end
end) end)

View File

@ -6,30 +6,30 @@ local Util = require('util')
Event.addRoutine(function() Event.addRoutine(function()
print('trust: listening on port 19') print('trust: listening on port 19')
while true do while true do
local socket = Socket.server(19) local socket = Socket.server(19)
print('trust: connection from ' .. socket.dhost) print('trust: connection from ' .. socket.dhost)
local data = socket:read(2) local data = socket:read(2)
if data then if data then
local password = Security.getPassword() local password = Security.getPassword()
if not password then if not password then
socket:write({ msg = 'No password has been set' }) socket:write({ msg = 'No password has been set' })
else else
data = Crypto.decrypt(data, password) data = Crypto.decrypt(data, password)
if data and data.pk and data.dh == socket.dhost then if data and data.pk and data.dh == socket.dhost then
local trustList = Util.readTable('usr/.known_hosts') or { } local trustList = Util.readTable('usr/.known_hosts') or { }
trustList[data.dh] = data.pk trustList[data.dh] = data.pk
Util.writeTable('usr/.known_hosts', trustList) Util.writeTable('usr/.known_hosts', trustList)
socket:write({ success = true, msg = 'Trust accepted' }) socket:write({ success = true, msg = 'Trust accepted' })
else else
socket:write({ msg = 'Invalid password' }) socket:write({ msg = 'Invalid password' })
end end
end end
end end
socket:close() socket:close()
end end
end) end)

View File

@ -6,64 +6,64 @@ local os = _G.os
local terminal = _G.device.terminal local terminal = _G.device.terminal
local function vncHost(socket) local function vncHost(socket)
local methods = { 'blit', 'clear', 'clearLine', 'setCursorPos', 'write', local methods = { 'blit', 'clear', 'clearLine', 'setCursorPos', 'write',
'setTextColor', 'setTextColour', 'setBackgroundColor', 'setTextColor', 'setTextColour', 'setBackgroundColor',
'setBackgroundColour', 'scroll', 'setCursorBlink', } 'setBackgroundColour', 'scroll', 'setCursorBlink', }
local oldTerm = Util.shallowCopy(terminal) local oldTerm = Util.shallowCopy(terminal)
for _,k in pairs(methods) do for _,k in pairs(methods) do
terminal[k] = function(...) terminal[k] = function(...)
if not socket.queue then if not socket.queue then
socket.queue = { } socket.queue = { }
Event.onTimeout(0, function() Event.onTimeout(0, function()
socket:write(socket.queue) socket:write(socket.queue)
socket.queue = nil socket.queue = nil
end) end)
end end
table.insert(socket.queue, { table.insert(socket.queue, {
f = k, f = k,
args = { ... }, args = { ... },
}) })
oldTerm[k](...) oldTerm[k](...)
end end
end end
while true do while true do
local data = socket:read() local data = socket:read()
if not data then if not data then
print('vnc: closing connection to ' .. socket.dhost) print('vnc: closing connection to ' .. socket.dhost)
break break
end end
if data.type == 'shellRemote' then if data.type == 'shellRemote' then
os.queueEvent(table.unpack(data.event)) os.queueEvent(table.unpack(data.event))
elseif data.type == 'termInfo' then elseif data.type == 'termInfo' then
terminal.getSize = function() terminal.getSize = function()
return data.width, data.height return data.width, data.height
end end
os.queueEvent('term_resize') os.queueEvent('term_resize')
end end
end end
for k,v in pairs(oldTerm) do for k,v in pairs(oldTerm) do
terminal[k] = v terminal[k] = v
end end
os.queueEvent('term_resize') os.queueEvent('term_resize')
end end
Event.addRoutine(function() Event.addRoutine(function()
print('vnc: listening on port 5900') print('vnc: listening on port 5900')
while true do while true do
local socket = Socket.server(5900) local socket = Socket.server(5900)
print('vnc: connection from ' .. socket.dhost) print('vnc: connection from ' .. socket.dhost)
-- no new process - only 1 connection allowed -- no new process - only 1 connection allowed
-- due to term size issues -- due to term size issues
vncHost(socket) vncHost(socket)
socket:close() socket:close()
end end
end) end)