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

View File

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

View File

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

View File

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

View File

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

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -9,16 +9,16 @@
--
if (...) then
local Node = {}
Node.__index = Node
local Node = {}
Node.__index = Node
function Node:new(x,y,z)
return setmetatable({x = x, y = y, z = z }, Node)
end
function Node:new(x,y,z)
return setmetatable({x = x, y = y, z = z }, Node)
end
-- Enables the use of operator '<' to compare nodes.
-- Will be used to sort a collection of nodes in a binary heap on the basis of their F-cost
function Node.__lt(A,B) return (A._f < B._f) end
-- Enables the use of operator '<' to compare nodes.
-- Will be used to sort a collection of nodes in a binary heap on the basis of their F-cost
function Node.__lt(A,B) return (A._f < B._f) end
function Node:getX() return self.x end
function Node:getY() return self.y end
@ -33,7 +33,7 @@ if (...) then
return self
end
return setmetatable(Node,
return setmetatable(Node,
{__call = function(_,...)
return Node:new(...)
end}

View File

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

View File

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

View File

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

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,
the code was modified to support 3D maps.
Note that this is only a partial copy of the full jumper code base. Also,
the code was modified to support 3D maps.
--]]
--[[
This work is under MIT-LICENSE
Copyright (c) 2012-2013 Roland Yonaba.
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
-- https://opensource.org/licenses/MIT
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
--]]
local _VERSION = ""
@ -33,87 +18,87 @@ local _RELEASEDATE = ""
if (...) then
-- Dependencies
local _PATH = (...):gsub('%.pathfinder$','')
-- Dependencies
local _PATH = (...):gsub('%.pathfinder$','')
local Utils = require (_PATH .. '.core.utils')
-- Internalization
local pairs = pairs
local assert = assert
local setmetatable = setmetatable
-- Internalization
local pairs = pairs
local assert = assert
local setmetatable = setmetatable
--- Finders (search algorithms implemented). Refers to the search algorithms actually implemented in Jumper.
-- <li>[A*](http://en.wikipedia.org/wiki/A*_search_algorithm)</li>
local Finders = {
['ASTAR'] = require (_PATH .. '.search.astar'),
}
local Finders = {
['ASTAR'] = require (_PATH .. '.search.astar'),
}
-- Will keep track of all nodes expanded during the search
-- to easily reset their properties for the next pathfinding call
local toClear = {}
-- Will keep track of all nodes expanded during the search
-- to easily reset their properties for the next pathfinding call
local toClear = {}
-- Performs a traceback from the goal node to the start node
-- Only happens when the path was found
-- Performs a traceback from the goal node to the start node
-- Only happens when the path was found
local Pathfinder = {}
Pathfinder.__index = Pathfinder
local Pathfinder = {}
Pathfinder.__index = Pathfinder
function Pathfinder:new(heuristic)
local newPathfinder = {}
setmetatable(newPathfinder, Pathfinder)
self._finder = Finders.ASTAR
self._heuristic = heuristic
return newPathfinder
end
function Pathfinder:new(heuristic)
local newPathfinder = {}
setmetatable(newPathfinder, Pathfinder)
self._finder = Finders.ASTAR
self._heuristic = heuristic
return newPathfinder
end
function Pathfinder:setGrid(grid)
self._grid = grid
return self
end
--- Calculates a `path`. Returns the `path` from start to end location
-- Both locations must exist on the collision map. The starting location can be unwalkable.
-- @treturn path a path (array of nodes) when found, otherwise nil
-- @usage local path = myFinder:getPath(1,1,5,5)
function Pathfinder:getPath(startX, startY, startZ, ih, endX, endY, endZ, oh)
self:reset()
local startNode = self._grid:getNodeAt(startX, startY, startZ)
local endNode = self._grid:getNodeAt(endX, endY, endZ)
if not startNode or not endNode then
return nil
end
startNode.heading = ih
endNode.heading = oh
assert(startNode, ('Invalid location [%d, %d, %d]'):format(startX, startY, startZ))
assert(endNode and self._grid:isWalkableAt(endX, endY, endZ),
('Invalid or unreachable location [%d, %d, %d]'):format(endX, endY, endZ))
local _endNode = self._finder(self, startNode, endNode, toClear)
if _endNode then
return Utils.traceBackPath(self, _endNode, startNode)
end
return nil
end
--- Resets the `pathfinder`. This function is called internally between
-- successive pathfinding calls, so you should not
-- use it explicitely, unless under specific circumstances.
-- @class function
-- @treturn pathfinder self (the calling `pathfinder` itself, can be chained)
-- @usage local path, len = myFinder:getPath(1,1,5,5)
function Pathfinder:reset()
for node in pairs(toClear) do node:reset() end
toClear = {}
function Pathfinder:setGrid(grid)
self._grid = grid
return self
end
-- Returns Pathfinder class
--- Calculates a `path`. Returns the `path` from start to end location
-- Both locations must exist on the collision map. The starting location can be unwalkable.
-- @treturn path a path (array of nodes) when found, otherwise nil
-- @usage local path = myFinder:getPath(1,1,5,5)
function Pathfinder:getPath(startX, startY, startZ, ih, endX, endY, endZ, oh)
self:reset()
local startNode = self._grid:getNodeAt(startX, startY, startZ)
local endNode = self._grid:getNodeAt(endX, endY, endZ)
if not startNode or not endNode then
return nil
end
startNode.heading = ih
endNode.heading = oh
assert(startNode, ('Invalid location [%d, %d, %d]'):format(startX, startY, startZ))
assert(endNode and self._grid:isWalkableAt(endX, endY, endZ),
('Invalid or unreachable location [%d, %d, %d]'):format(endX, endY, endZ))
local _endNode = self._finder(self, startNode, endNode, toClear)
if _endNode then
return Utils.traceBackPath(self, _endNode, startNode)
end
return nil
end
--- Resets the `pathfinder`. This function is called internally between
-- successive pathfinding calls, so you should not
-- use it explicitely, unless under specific circumstances.
-- @class function
-- @treturn pathfinder self (the calling `pathfinder` itself, can be chained)
-- @usage local path, len = myFinder:getPath(1,1,5,5)
function Pathfinder:reset()
for node in pairs(toClear) do node:reset() end
toClear = {}
return self
end
-- Returns Pathfinder class
Pathfinder._VERSION = _VERSION
Pathfinder._RELEASEDATE = _RELEASEDATE
return setmetatable(Pathfinder,{
__call = function(self,...)
return self:new(...)
end
})
return setmetatable(Pathfinder,{
__call = function(self,...)
return self:new(...)
end
})
end

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -1,35 +1,18 @@
local sha1 = {
_VERSION = "sha.lua 0.5.0",
_URL = "https://github.com/kikito/sha.lua",
_DESCRIPTION = [[
SHA-1 secure hash computation, and HMAC-SHA1 signature computation in Lua (5.1)
Based on code originally by Jeffrey Friedl (http://regex.info/blog/lua/sha1)
And modified by Eike Decker - (http://cube3d.de/uploads/Main/sha1.txt)
]],
_LICENSE = [[
MIT LICENSE
_VERSION = "sha.lua 0.5.0",
_URL = "https://github.com/kikito/sha.lua",
_DESCRIPTION = [[
SHA-1 secure hash computation, and HMAC-SHA1 signature computation in Lua (5.1)
Based on code originally by Jeffrey Friedl (http://regex.info/blog/lua/sha1)
And modified by Eike Decker - (http://cube3d.de/uploads/Main/sha1.txt)
]],
_LICENSE = [[
MIT LICENSE
Copyright (c) 2013 Enrique Garcia Cota + Eike Decker + Jeffrey Friedl
Copyright (c) 2013 Enrique Garcia Cota + Eike Decker + Jeffrey Friedl
Permission is hereby granted, free of charge, to any person obtaining a
copy of this software and associated documentation files (the
"Software"), to deal in the Software without restriction, including
without limitation the rights to use, copy, modify, merge, publish,
distribute, sublicense, and/or sell copies of the Software, and to
permit persons to whom the Software is furnished to do so, subject to
the following conditions:
The above copyright notice and this permission notice shall be included
in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
]]
https://opensource.org/licenses/MIT
]]
}
-----------------------------------------------------------------------------------
@ -47,85 +30,85 @@ local char,format,rep = string.char,string.format,string.rep
local function bytes_to_w32(a,b,c,d) return a*0x1000000+b*0x10000+c*0x100+d end
-- split a 32 bit word into four 8 bit numbers
local function w32_to_bytes(i)
return floor(i/0x1000000)%0x100,floor(i/0x10000)%0x100,floor(i/0x100)%0x100,i%0x100
return floor(i/0x1000000)%0x100,floor(i/0x10000)%0x100,floor(i/0x100)%0x100,i%0x100
end
-- shift the bits of a 32 bit word. Don't use negative values for "bits"
local function w32_rot(bits,a)
local b2 = 2^(32-bits)
local a,b = modf(a/b2)
return a+b*b2*(2^(bits))
local b2 = 2^(32-bits)
local a,b = modf(a/b2)
return a+b*b2*(2^(bits))
end
-- caching function for functions that accept 2 arguments, both of values between
-- 0 and 255. The function to be cached is passed, all values are calculated
-- during loading and a function is returned that returns the cached values (only)
local function cache2arg(fn)
if not PRELOAD_CACHE then return fn end
local lut = {}
for i=0,0xffff do
local a,b = floor(i/0x100),i%0x100
lut[i] = fn(a,b)
end
return function(a,b)
return lut[a*0x100+b]
end
if not PRELOAD_CACHE then return fn end
local lut = {}
for i=0,0xffff do
local a,b = floor(i/0x100),i%0x100
lut[i] = fn(a,b)
end
return function(a,b)
return lut[a*0x100+b]
end
end
-- splits an 8-bit number into 8 bits, returning all 8 bits as booleans
local function byte_to_bits(b)
local b = function(n)
local b = floor(b/n)
return b%2==1
end
return b(1),b(2),b(4),b(8),b(16),b(32),b(64),b(128)
local b = function(n)
local b = floor(b/n)
return b%2==1
end
return b(1),b(2),b(4),b(8),b(16),b(32),b(64),b(128)
end
-- builds an 8bit number from 8 booleans
local function bits_to_byte(a,b,c,d,e,f,g,h)
local function n(b,x) return b and x or 0 end
return n(a,1)+n(b,2)+n(c,4)+n(d,8)+n(e,16)+n(f,32)+n(g,64)+n(h,128)
local function n(b,x) return b and x or 0 end
return n(a,1)+n(b,2)+n(c,4)+n(d,8)+n(e,16)+n(f,32)+n(g,64)+n(h,128)
end
-- bitwise "and" function for 2 8bit number
local band = cache2arg (function(a,b)
local A,B,C,D,E,F,G,H = byte_to_bits(b)
local a,b,c,d,e,f,g,h = byte_to_bits(a)
return bits_to_byte(
A and a, B and b, C and c, D and d,
E and e, F and f, G and g, H and h)
local A,B,C,D,E,F,G,H = byte_to_bits(b)
local a,b,c,d,e,f,g,h = byte_to_bits(a)
return bits_to_byte(
A and a, B and b, C and c, D and d,
E and e, F and f, G and g, H and h)
end)
-- bitwise "or" function for 2 8bit numbers
local bor = cache2arg(function(a,b)
local A,B,C,D,E,F,G,H = byte_to_bits(b)
local a,b,c,d,e,f,g,h = byte_to_bits(a)
return bits_to_byte(
A or a, B or b, C or c, D or d,
E or e, F or f, G or g, H or h)
local A,B,C,D,E,F,G,H = byte_to_bits(b)
local a,b,c,d,e,f,g,h = byte_to_bits(a)
return bits_to_byte(
A or a, B or b, C or c, D or d,
E or e, F or f, G or g, H or h)
end)
-- bitwise "xor" function for 2 8bit numbers
local bxor = cache2arg(function(a,b)
local A,B,C,D,E,F,G,H = byte_to_bits(b)
local a,b,c,d,e,f,g,h = byte_to_bits(a)
return bits_to_byte(
A ~= a, B ~= b, C ~= c, D ~= d,
E ~= e, F ~= f, G ~= g, H ~= h)
local A,B,C,D,E,F,G,H = byte_to_bits(b)
local a,b,c,d,e,f,g,h = byte_to_bits(a)
return bits_to_byte(
A ~= a, B ~= b, C ~= c, D ~= d,
E ~= e, F ~= f, G ~= g, H ~= h)
end)
-- bitwise complement for one 8bit number
local function bnot(x)
return 255-(x % 256)
return 255-(x % 256)
end
-- creates a function to combine to 32bit numbers using an 8bit combination function
local function w32_comb(fn)
return function(a,b)
local aa,ab,ac,ad = w32_to_bytes(a)
local ba,bb,bc,bd = w32_to_bytes(b)
return bytes_to_w32(fn(aa,ba),fn(ab,bb),fn(ac,bc),fn(ad,bd))
end
return function(a,b)
local aa,ab,ac,ad = w32_to_bytes(a)
local ba,bb,bc,bd = w32_to_bytes(b)
return bytes_to_w32(fn(aa,ba),fn(ab,bb),fn(ac,bc),fn(ad,bd))
end
end
-- create functions for and, xor and or, all for 2 32bit numbers
@ -135,27 +118,27 @@ local w32_or = w32_comb(bor)
-- xor function that may receive a variable number of arguments
local function w32_xor_n(a,...)
local aa,ab,ac,ad = w32_to_bytes(a)
for i=1,select('#',...) do
local ba,bb,bc,bd = w32_to_bytes(select(i,...))
aa,ab,ac,ad = bxor(aa,ba),bxor(ab,bb),bxor(ac,bc),bxor(ad,bd)
end
return bytes_to_w32(aa,ab,ac,ad)
local aa,ab,ac,ad = w32_to_bytes(a)
for i=1,select('#',...) do
local ba,bb,bc,bd = w32_to_bytes(select(i,...))
aa,ab,ac,ad = bxor(aa,ba),bxor(ab,bb),bxor(ac,bc),bxor(ad,bd)
end
return bytes_to_w32(aa,ab,ac,ad)
end
-- combining 3 32bit numbers through binary "or" operation
local function w32_or3(a,b,c)
local aa,ab,ac,ad = w32_to_bytes(a)
local ba,bb,bc,bd = w32_to_bytes(b)
local ca,cb,cc,cd = w32_to_bytes(c)
return bytes_to_w32(
bor(aa,bor(ba,ca)), bor(ab,bor(bb,cb)), bor(ac,bor(bc,cc)), bor(ad,bor(bd,cd))
)
local aa,ab,ac,ad = w32_to_bytes(a)
local ba,bb,bc,bd = w32_to_bytes(b)
local ca,cb,cc,cd = w32_to_bytes(c)
return bytes_to_w32(
bor(aa,bor(ba,ca)), bor(ab,bor(bb,cb)), bor(ac,bor(bc,cc)), bor(ad,bor(bd,cd))
)
end
-- binary complement for 32bit numbers
local function w32_not(a)
return 4294967295-(a % 4294967296)
return 4294967295-(a % 4294967296)
end
-- adding 2 32bit numbers, cutting off the remainder on 33th bit
@ -163,18 +146,18 @@ local function w32_add(a,b) return (a+b) % 4294967296 end
-- adding n 32bit numbers, cutting off the remainder (again)
local function w32_add_n(a,...)
for i=1,select('#',...) do
a = (a+select(i,...)) % 4294967296
end
return a
for i=1,select('#',...) do
a = (a+select(i,...)) % 4294967296
end
return a
end
-- converting the number to a hexadecimal string
local function w32_to_hexstring(w) return format("%08x",w) end
local function hex_to_binary(hex)
return hex:gsub('..', function(hexval)
return string.char(tonumber(hexval, 16))
end)
return hex:gsub('..', function(hexval)
return string.char(tonumber(hexval, 16))
end)
end
-- building the lookuptables ahead of time (instead of littering the source code
@ -182,114 +165,114 @@ end
local xor_with_0x5c = {}
local xor_with_0x36 = {}
for i=0,0xff do
xor_with_0x5c[char(i)] = char(bxor(i,0x5c))
xor_with_0x36[char(i)] = char(bxor(i,0x36))
xor_with_0x5c[char(i)] = char(bxor(i,0x5c))
xor_with_0x36[char(i)] = char(bxor(i,0x36))
end
-----------------------------------------------------------------------------
-- calculating the SHA1 for some text
function sha1.sha1(msg)
local H0,H1,H2,H3,H4 = 0x67452301,0xEFCDAB89,0x98BADCFE,0x10325476,0xC3D2E1F0
local msg_len_in_bits = #msg * 8
local H0,H1,H2,H3,H4 = 0x67452301,0xEFCDAB89,0x98BADCFE,0x10325476,0xC3D2E1F0
local msg_len_in_bits = #msg * 8
local first_append = char(0x80) -- append a '1' bit plus seven '0' bits
local first_append = char(0x80) -- append a '1' bit plus seven '0' bits
local non_zero_message_bytes = #msg +1 +8 -- the +1 is the appended bit 1, the +8 are for the final appended length
local current_mod = non_zero_message_bytes % 64
local second_append = current_mod>0 and rep(char(0), 64 - current_mod) or ""
local non_zero_message_bytes = #msg +1 +8 -- the +1 is the appended bit 1, the +8 are for the final appended length
local current_mod = non_zero_message_bytes % 64
local second_append = current_mod>0 and rep(char(0), 64 - current_mod) or ""
-- now to append the length as a 64-bit number.
local B1, R1 = modf(msg_len_in_bits / 0x01000000)
local B2, R2 = modf( 0x01000000 * R1 / 0x00010000)
local B3, R3 = modf( 0x00010000 * R2 / 0x00000100)
local B4 = 0x00000100 * R3
-- now to append the length as a 64-bit number.
local B1, R1 = modf(msg_len_in_bits / 0x01000000)
local B2, R2 = modf( 0x01000000 * R1 / 0x00010000)
local B3, R3 = modf( 0x00010000 * R2 / 0x00000100)
local B4 = 0x00000100 * R3
local L64 = char( 0) .. char( 0) .. char( 0) .. char( 0) -- high 32 bits
.. char(B1) .. char(B2) .. char(B3) .. char(B4) -- low 32 bits
local L64 = char( 0) .. char( 0) .. char( 0) .. char( 0) -- high 32 bits
.. char(B1) .. char(B2) .. char(B3) .. char(B4) -- low 32 bits
msg = msg .. first_append .. second_append .. L64
msg = msg .. first_append .. second_append .. L64
assert(#msg % 64 == 0)
assert(#msg % 64 == 0)
local chunks = #msg / 64
local chunks = #msg / 64
local W = { }
local start, A, B, C, D, E, f, K, TEMP
local chunk = 0
local W = { }
local start, A, B, C, D, E, f, K, TEMP
local chunk = 0
while chunk < chunks do
--
-- break chunk up into W[0] through W[15]
--
start,chunk = chunk * 64 + 1,chunk + 1
while chunk < chunks do
--
-- break chunk up into W[0] through W[15]
--
start,chunk = chunk * 64 + 1,chunk + 1
for t = 0, 15 do
W[t] = bytes_to_w32(msg:byte(start, start + 3))
start = start + 4
end
for t = 0, 15 do
W[t] = bytes_to_w32(msg:byte(start, start + 3))
start = start + 4
end
--
-- build W[16] through W[79]
--
for t = 16, 79 do
-- For t = 16 to 79 let Wt = S1(Wt-3 XOR Wt-8 XOR Wt-14 XOR Wt-16).
W[t] = w32_rot(1, w32_xor_n(W[t-3], W[t-8], W[t-14], W[t-16]))
end
--
-- build W[16] through W[79]
--
for t = 16, 79 do
-- For t = 16 to 79 let Wt = S1(Wt-3 XOR Wt-8 XOR Wt-14 XOR Wt-16).
W[t] = w32_rot(1, w32_xor_n(W[t-3], W[t-8], W[t-14], W[t-16]))
end
A,B,C,D,E = H0,H1,H2,H3,H4
A,B,C,D,E = H0,H1,H2,H3,H4
for t = 0, 79 do
if t <= 19 then
-- (B AND C) OR ((NOT B) AND D)
f = w32_or(w32_and(B, C), w32_and(w32_not(B), D))
K = 0x5A827999
elseif t <= 39 then
-- B XOR C XOR D
f = w32_xor_n(B, C, D)
K = 0x6ED9EBA1
elseif t <= 59 then
-- (B AND C) OR (B AND D) OR (C AND D
f = w32_or3(w32_and(B, C), w32_and(B, D), w32_and(C, D))
K = 0x8F1BBCDC
else
-- B XOR C XOR D
f = w32_xor_n(B, C, D)
K = 0xCA62C1D6
end
for t = 0, 79 do
if t <= 19 then
-- (B AND C) OR ((NOT B) AND D)
f = w32_or(w32_and(B, C), w32_and(w32_not(B), D))
K = 0x5A827999
elseif t <= 39 then
-- B XOR C XOR D
f = w32_xor_n(B, C, D)
K = 0x6ED9EBA1
elseif t <= 59 then
-- (B AND C) OR (B AND D) OR (C AND D
f = w32_or3(w32_and(B, C), w32_and(B, D), w32_and(C, D))
K = 0x8F1BBCDC
else
-- B XOR C XOR D
f = w32_xor_n(B, C, D)
K = 0xCA62C1D6
end
-- TEMP = S5(A) + ft(B,C,D) + E + Wt + Kt;
A,B,C,D,E = w32_add_n(w32_rot(5, A), f, E, W[t], K),
A, w32_rot(30, B), C, D
end
-- Let H0 = H0 + A, H1 = H1 + B, H2 = H2 + C, H3 = H3 + D, H4 = H4 + E.
H0,H1,H2,H3,H4 = w32_add(H0, A),w32_add(H1, B),w32_add(H2, C),w32_add(H3, D),w32_add(H4, E)
end
local f = w32_to_hexstring
return f(H0) .. f(H1) .. f(H2) .. f(H3) .. f(H4)
-- TEMP = S5(A) + ft(B,C,D) + E + Wt + Kt;
A,B,C,D,E = w32_add_n(w32_rot(5, A), f, E, W[t], K),
A, w32_rot(30, B), C, D
end
-- Let H0 = H0 + A, H1 = H1 + B, H2 = H2 + C, H3 = H3 + D, H4 = H4 + E.
H0,H1,H2,H3,H4 = w32_add(H0, A),w32_add(H1, B),w32_add(H2, C),w32_add(H3, D),w32_add(H4, E)
end
local f = w32_to_hexstring
return f(H0) .. f(H1) .. f(H2) .. f(H3) .. f(H4)
end
function sha1.binary(msg)
return hex_to_binary(sha1.sha1(msg))
return hex_to_binary(sha1.sha1(msg))
end
function sha1.hmac(key, text)
assert(type(key) == 'string', "key passed to sha1.hmac should be a string")
assert(type(text) == 'string', "text passed to sha1.hmac should be a string")
assert(type(key) == 'string', "key passed to sha1.hmac should be a string")
assert(type(text) == 'string', "text passed to sha1.hmac should be a string")
if #key > BLOCK_SIZE then
key = sha1.binary(key)
end
if #key > BLOCK_SIZE then
key = sha1.binary(key)
end
local key_xord_with_0x36 = key:gsub('.', xor_with_0x36) .. string.rep(string.char(0x36), BLOCK_SIZE - #key)
local key_xord_with_0x5c = key:gsub('.', xor_with_0x5c) .. string.rep(string.char(0x5c), BLOCK_SIZE - #key)
local key_xord_with_0x36 = key:gsub('.', xor_with_0x36) .. string.rep(string.char(0x36), BLOCK_SIZE - #key)
local key_xord_with_0x5c = key:gsub('.', xor_with_0x5c) .. string.rep(string.char(0x5c), BLOCK_SIZE - #key)
return sha1.sha1(key_xord_with_0x5c .. sha1.binary(key_xord_with_0x36 .. text))
return sha1.sha1(key_xord_with_0x5c .. sha1.binary(key_xord_with_0x36 .. text))
end
function sha1.hmac_binary(key, text)
return hex_to_binary(sha1.hmac(key, text))
return hex_to_binary(sha1.hmac(key, text))
end
setmetatable(sha1, {__call = function(_,msg) return sha1.sha1(msg) end })

View File

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

View File

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

View File

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

File diff suppressed because it is too large Load Diff

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -1,31 +1,14 @@
local tween = {
_VERSION = 'tween 2.1.1',
_DESCRIPTION = 'tweening for lua',
_URL = 'https://github.com/kikito/tween.lua',
_LICENSE = [[
MIT LICENSE
_VERSION = 'tween 2.1.1',
_DESCRIPTION = 'tweening for lua',
_URL = 'https://github.com/kikito/tween.lua',
_LICENSE = [[
MIT LICENSE
Copyright (c) 2014 Enrique García Cota, Yuichi Tateno, Emmanuel Oga
Copyright (c) 2014 Enrique García Cota, Yuichi Tateno, Emmanuel Oga
Permission is hereby granted, free of charge, to any person obtaining a
copy of this software and associated documentation files (the
"Software"), to deal in the Software without restriction, including
without limitation the rights to use, copy, modify, merge, publish,
distribute, sublicense, and/or sell copies of the Software, and to
permit persons to whom the Software is furnished to do so, subject to
the following conditions:
The above copyright notice and this permission notice shall be included
in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
]]
Licence details: https://opensource.org/licenses/MIT
]]
}
-- easing
@ -45,57 +28,57 @@ local function linear(t, b, c, d) return c * t / d + b end
-- quad
local function inQuad(t, b, c, d) return c * pow(t / d, 2) + b end
local function outQuad(t, b, c, d)
t = t / d
return -c * t * (t - 2) + b
t = t / d
return -c * t * (t - 2) + b
end
local function inOutQuad(t, b, c, d)
t = t / d * 2
if t < 1 then return c / 2 * pow(t, 2) + b end
return -c / 2 * ((t - 1) * (t - 3) - 1) + b
t = t / d * 2
if t < 1 then return c / 2 * pow(t, 2) + b end
return -c / 2 * ((t - 1) * (t - 3) - 1) + b
end
local function outInQuad(t, b, c, d)
if t < d / 2 then return outQuad(t * 2, b, c / 2, d) end
return inQuad((t * 2) - d, b + c / 2, c / 2, d)
if t < d / 2 then return outQuad(t * 2, b, c / 2, d) end
return inQuad((t * 2) - d, b + c / 2, c / 2, d)
end
-- cubic
local function inCubic (t, b, c, d) return c * pow(t / d, 3) + b end
local function outCubic(t, b, c, d) return c * (pow(t / d - 1, 3) + 1) + b end
local function inOutCubic(t, b, c, d)
t = t / d * 2
if t < 1 then return c / 2 * t * t * t + b end
t = t - 2
return c / 2 * (t * t * t + 2) + b
t = t / d * 2
if t < 1 then return c / 2 * t * t * t + b end
t = t - 2
return c / 2 * (t * t * t + 2) + b
end
local function outInCubic(t, b, c, d)
if t < d / 2 then return outCubic(t * 2, b, c / 2, d) end
return inCubic((t * 2) - d, b + c / 2, c / 2, d)
if t < d / 2 then return outCubic(t * 2, b, c / 2, d) end
return inCubic((t * 2) - d, b + c / 2, c / 2, d)
end
-- quart
local function inQuart(t, b, c, d) return c * pow(t / d, 4) + b end
local function outQuart(t, b, c, d) return -c * (pow(t / d - 1, 4) - 1) + b end
local function inOutQuart(t, b, c, d)
t = t / d * 2
if t < 1 then return c / 2 * pow(t, 4) + b end
return -c / 2 * (pow(t - 2, 4) - 2) + b
t = t / d * 2
if t < 1 then return c / 2 * pow(t, 4) + b end
return -c / 2 * (pow(t - 2, 4) - 2) + b
end
local function outInQuart(t, b, c, d)
if t < d / 2 then return outQuart(t * 2, b, c / 2, d) end
return inQuart((t * 2) - d, b + c / 2, c / 2, d)
if t < d / 2 then return outQuart(t * 2, b, c / 2, d) end
return inQuart((t * 2) - d, b + c / 2, c / 2, d)
end
-- quint
local function inQuint(t, b, c, d) return c * pow(t / d, 5) + b end
local function outQuint(t, b, c, d) return c * (pow(t / d - 1, 5) + 1) + b end
local function inOutQuint(t, b, c, d)
t = t / d * 2
if t < 1 then return c / 2 * pow(t, 5) + b end
return c / 2 * (pow(t - 2, 5) + 2) + b
t = t / d * 2
if t < 1 then return c / 2 * pow(t, 5) + b end
return c / 2 * (pow(t - 2, 5) + 2) + b
end
local function outInQuint(t, b, c, d)
if t < d / 2 then return outQuint(t * 2, b, c / 2, d) end
return inQuint((t * 2) - d, b + c / 2, c / 2, d)
if t < d / 2 then return outQuint(t * 2, b, c / 2, d) end
return inQuint((t * 2) - d, b + c / 2, c / 2, d)
end
-- sine
@ -103,142 +86,142 @@ local function inSine(t, b, c, d) return -c * cos(t / d * (pi / 2)) + c + b end
local function outSine(t, b, c, d) return c * sin(t / d * (pi / 2)) + b end
local function inOutSine(t, b, c, d) return -c / 2 * (cos(pi * t / d) - 1) + b end
local function outInSine(t, b, c, d)
if t < d / 2 then return outSine(t * 2, b, c / 2, d) end
return inSine((t * 2) -d, b + c / 2, c / 2, d)
if t < d / 2 then return outSine(t * 2, b, c / 2, d) end
return inSine((t * 2) -d, b + c / 2, c / 2, d)
end
-- expo
local function inExpo(t, b, c, d)
if t == 0 then return b end
return c * pow(2, 10 * (t / d - 1)) + b - c * 0.001
if t == 0 then return b end
return c * pow(2, 10 * (t / d - 1)) + b - c * 0.001
end
local function outExpo(t, b, c, d)
if t == d then return b + c end
return c * 1.001 * (-pow(2, -10 * t / d) + 1) + b
if t == d then return b + c end
return c * 1.001 * (-pow(2, -10 * t / d) + 1) + b
end
local function inOutExpo(t, b, c, d)
if t == 0 then return b end
if t == d then return b + c end
t = t / d * 2
if t < 1 then return c / 2 * pow(2, 10 * (t - 1)) + b - c * 0.0005 end
return c / 2 * 1.0005 * (-pow(2, -10 * (t - 1)) + 2) + b
if t == 0 then return b end
if t == d then return b + c end
t = t / d * 2
if t < 1 then return c / 2 * pow(2, 10 * (t - 1)) + b - c * 0.0005 end
return c / 2 * 1.0005 * (-pow(2, -10 * (t - 1)) + 2) + b
end
local function outInExpo(t, b, c, d)
if t < d / 2 then return outExpo(t * 2, b, c / 2, d) end
return inExpo((t * 2) - d, b + c / 2, c / 2, d)
if t < d / 2 then return outExpo(t * 2, b, c / 2, d) end
return inExpo((t * 2) - d, b + c / 2, c / 2, d)
end
-- circ
local function inCirc(t, b, c, d) return(-c * (sqrt(1 - pow(t / d, 2)) - 1) + b) end
local function outCirc(t, b, c, d) return(c * sqrt(1 - pow(t / d - 1, 2)) + b) end
local function inOutCirc(t, b, c, d)
t = t / d * 2
if t < 1 then return -c / 2 * (sqrt(1 - t * t) - 1) + b end
t = t - 2
return c / 2 * (sqrt(1 - t * t) + 1) + b
t = t / d * 2
if t < 1 then return -c / 2 * (sqrt(1 - t * t) - 1) + b end
t = t - 2
return c / 2 * (sqrt(1 - t * t) + 1) + b
end
local function outInCirc(t, b, c, d)
if t < d / 2 then return outCirc(t * 2, b, c / 2, d) end
return inCirc((t * 2) - d, b + c / 2, c / 2, d)
if t < d / 2 then return outCirc(t * 2, b, c / 2, d) end
return inCirc((t * 2) - d, b + c / 2, c / 2, d)
end
-- elastic
local function calculatePAS(p,a,c,d)
p, a = p or d * 0.3, a or 0
if a < abs(c) then return p, c, p / 4 end -- p, a, s
return p, a, p / (2 * pi) * asin(c/a) -- p,a,s
p, a = p or d * 0.3, a or 0
if a < abs(c) then return p, c, p / 4 end -- p, a, s
return p, a, p / (2 * pi) * asin(c/a) -- p,a,s
end
local function inElastic(t, b, c, d, a, p)
local s
if t == 0 then return b end
t = t / d
if t == 1 then return b + c end
p,a,s = calculatePAS(p,a,c,d)
t = t - 1
return -(a * pow(2, 10 * t) * sin((t * d - s) * (2 * pi) / p)) + b
local s
if t == 0 then return b end
t = t / d
if t == 1 then return b + c end
p,a,s = calculatePAS(p,a,c,d)
t = t - 1
return -(a * pow(2, 10 * t) * sin((t * d - s) * (2 * pi) / p)) + b
end
local function outElastic(t, b, c, d, a, p)
local s
if t == 0 then return b end
t = t / d
if t == 1 then return b + c end
p,a,s = calculatePAS(p,a,c,d)
return a * pow(2, -10 * t) * sin((t * d - s) * (2 * pi) / p) + c + b
local s
if t == 0 then return b end
t = t / d
if t == 1 then return b + c end
p,a,s = calculatePAS(p,a,c,d)
return a * pow(2, -10 * t) * sin((t * d - s) * (2 * pi) / p) + c + b
end
local function inOutElastic(t, b, c, d, a, p)
local s
if t == 0 then return b end
t = t / d * 2
if t == 2 then return b + c end
p,a,s = calculatePAS(p,a,c,d)
t = t - 1
if t < 0 then return -0.5 * (a * pow(2, 10 * t) * sin((t * d - s) * (2 * pi) / p)) + b end
return a * pow(2, -10 * t) * sin((t * d - s) * (2 * pi) / p ) * 0.5 + c + b
local s
if t == 0 then return b end
t = t / d * 2
if t == 2 then return b + c end
p,a,s = calculatePAS(p,a,c,d)
t = t - 1
if t < 0 then return -0.5 * (a * pow(2, 10 * t) * sin((t * d - s) * (2 * pi) / p)) + b end
return a * pow(2, -10 * t) * sin((t * d - s) * (2 * pi) / p ) * 0.5 + c + b
end
local function outInElastic(t, b, c, d, a, p)
if t < d / 2 then return outElastic(t * 2, b, c / 2, d, a, p) end
return inElastic((t * 2) - d, b + c / 2, c / 2, d, a, p)
if t < d / 2 then return outElastic(t * 2, b, c / 2, d, a, p) end
return inElastic((t * 2) - d, b + c / 2, c / 2, d, a, p)
end
-- back
local function inBack(t, b, c, d, s)
s = s or 1.70158
t = t / d
return c * t * t * ((s + 1) * t - s) + b
s = s or 1.70158
t = t / d
return c * t * t * ((s + 1) * t - s) + b
end
local function outBack(t, b, c, d, s)
s = s or 1.70158
t = t / d - 1
return c * (t * t * ((s + 1) * t + s) + 1) + b
s = s or 1.70158
t = t / d - 1
return c * (t * t * ((s + 1) * t + s) + 1) + b
end
local function inOutBack(t, b, c, d, s)
s = (s or 1.70158) * 1.525
t = t / d * 2
if t < 1 then return c / 2 * (t * t * ((s + 1) * t - s)) + b end
t = t - 2
return c / 2 * (t * t * ((s + 1) * t + s) + 2) + b
s = (s or 1.70158) * 1.525
t = t / d * 2
if t < 1 then return c / 2 * (t * t * ((s + 1) * t - s)) + b end
t = t - 2
return c / 2 * (t * t * ((s + 1) * t + s) + 2) + b
end
local function outInBack(t, b, c, d, s)
if t < d / 2 then return outBack(t * 2, b, c / 2, d, s) end
return inBack((t * 2) - d, b + c / 2, c / 2, d, s)
if t < d / 2 then return outBack(t * 2, b, c / 2, d, s) end
return inBack((t * 2) - d, b + c / 2, c / 2, d, s)
end
-- bounce
local function outBounce(t, b, c, d)
t = t / d
if t < 1 / 2.75 then return c * (7.5625 * t * t) + b end
if t < 2 / 2.75 then
t = t - (1.5 / 2.75)
return c * (7.5625 * t * t + 0.75) + b
elseif t < 2.5 / 2.75 then
t = t - (2.25 / 2.75)
return c * (7.5625 * t * t + 0.9375) + b
end
t = t - (2.625 / 2.75)
return c * (7.5625 * t * t + 0.984375) + b
t = t / d
if t < 1 / 2.75 then return c * (7.5625 * t * t) + b end
if t < 2 / 2.75 then
t = t - (1.5 / 2.75)
return c * (7.5625 * t * t + 0.75) + b
elseif t < 2.5 / 2.75 then
t = t - (2.25 / 2.75)
return c * (7.5625 * t * t + 0.9375) + b
end
t = t - (2.625 / 2.75)
return c * (7.5625 * t * t + 0.984375) + b
end
local function inBounce(t, b, c, d) return c - outBounce(d - t, 0, c, d) + b end
local function inOutBounce(t, b, c, d)
if t < d / 2 then return inBounce(t * 2, 0, c, d) * 0.5 + b end
return outBounce(t * 2 - d, 0, c, d) * 0.5 + c * .5 + b
if t < d / 2 then return inBounce(t * 2, 0, c, d) * 0.5 + b end
return outBounce(t * 2 - d, 0, c, d) * 0.5 + c * .5 + b
end
local function outInBounce(t, b, c, d)
if t < d / 2 then return outBounce(t * 2, b, c / 2, d) end
return inBounce((t * 2) - d, b + c / 2, c / 2, d)
if t < d / 2 then return outBounce(t * 2, b, c / 2, d) end
return inBounce((t * 2) - d, b + c / 2, c / 2, d)
end
tween.easing = {
linear = linear,
inQuad = inQuad, outQuad = outQuad, inOutQuad = inOutQuad, outInQuad = outInQuad,
inCubic = inCubic, outCubic = outCubic, inOutCubic = inOutCubic, outInCubic = outInCubic,
inQuart = inQuart, outQuart = outQuart, inOutQuart = inOutQuart, outInQuart = outInQuart,
inQuint = inQuint, outQuint = outQuint, inOutQuint = inOutQuint, outInQuint = outInQuint,
inSine = inSine, outSine = outSine, inOutSine = inOutSine, outInSine = outInSine,
inExpo = inExpo, outExpo = outExpo, inOutExpo = inOutExpo, outInExpo = outInExpo,
inCirc = inCirc, outCirc = outCirc, inOutCirc = inOutCirc, outInCirc = outInCirc,
inElastic = inElastic, outElastic = outElastic, inOutElastic = inOutElastic, outInElastic = outInElastic,
inBack = inBack, outBack = outBack, inOutBack = inOutBack, outInBack = outInBack,
inBounce = inBounce, outBounce = outBounce, inOutBounce = inOutBounce, outInBounce = outInBounce
linear = linear,
inQuad = inQuad, outQuad = outQuad, inOutQuad = inOutQuad, outInQuad = outInQuad,
inCubic = inCubic, outCubic = outCubic, inOutCubic = inOutCubic, outInCubic = outInCubic,
inQuart = inQuart, outQuart = outQuart, inOutQuart = inOutQuart, outInQuart = outInQuart,
inQuint = inQuint, outQuint = outQuint, inOutQuint = inOutQuint, outInQuint = outInQuint,
inSine = inSine, outSine = outSine, inOutSine = inOutSine, outInSine = outInSine,
inExpo = inExpo, outExpo = outExpo, inOutExpo = inOutExpo, outInExpo = outInExpo,
inCirc = inCirc, outCirc = outCirc, inOutCirc = inOutCirc, outInCirc = outInCirc,
inElastic = inElastic, outElastic = outElastic, inOutElastic = inOutElastic, outInElastic = outInElastic,
inBack = inBack, outBack = outBack, inOutBack = inOutBack, outInBack = outInBack,
inBounce = inBounce, outBounce = outBounce, inOutBounce = inOutBounce, outInBounce = outInBounce
}
@ -246,68 +229,68 @@ tween.easing = {
-- private stuff
local function copyTables(destination, keysTable, valuesTable)
valuesTable = valuesTable or keysTable
local mt = getmetatable(keysTable)
if mt and getmetatable(destination) == nil then
setmetatable(destination, mt)
end
for k,v in pairs(keysTable) do
if type(v) == 'table' then
destination[k] = copyTables({}, v, valuesTable[k])
else
destination[k] = valuesTable[k]
end
end
return destination
valuesTable = valuesTable or keysTable
local mt = getmetatable(keysTable)
if mt and getmetatable(destination) == nil then
setmetatable(destination, mt)
end
for k,v in pairs(keysTable) do
if type(v) == 'table' then
destination[k] = copyTables({}, v, valuesTable[k])
else
destination[k] = valuesTable[k]
end
end
return destination
end
local function checkSubjectAndTargetRecursively(subject, target, path)
path = path or {}
local targetType, newPath
for k,targetValue in pairs(target) do
targetType, newPath = type(targetValue), copyTables({}, path)
table.insert(newPath, tostring(k))
if targetType == 'number' then
assert(type(subject[k]) == 'number', "Parameter '" .. table.concat(newPath,'/') .. "' is missing from subject or isn't a number")
elseif targetType == 'table' then
checkSubjectAndTargetRecursively(subject[k], targetValue, newPath)
else
assert(targetType == 'number', "Parameter '" .. table.concat(newPath,'/') .. "' must be a number or table of numbers")
end
end
path = path or {}
local targetType, newPath
for k,targetValue in pairs(target) do
targetType, newPath = type(targetValue), copyTables({}, path)
table.insert(newPath, tostring(k))
if targetType == 'number' then
assert(type(subject[k]) == 'number', "Parameter '" .. table.concat(newPath,'/') .. "' is missing from subject or isn't a number")
elseif targetType == 'table' then
checkSubjectAndTargetRecursively(subject[k], targetValue, newPath)
else
assert(targetType == 'number', "Parameter '" .. table.concat(newPath,'/') .. "' must be a number or table of numbers")
end
end
end
local function checkNewParams(duration, subject, target, easing)
assert(type(duration) == 'number' and duration > 0, "duration must be a positive number. Was " .. tostring(duration))
local tsubject = type(subject)
assert(tsubject == 'table' or tsubject == 'userdata', "subject must be a table or userdata. Was " .. tostring(subject))
assert(type(target)== 'table', "target must be a table. Was " .. tostring(target))
assert(type(easing)=='function', "easing must be a function. Was " .. tostring(easing))
checkSubjectAndTargetRecursively(subject, target)
assert(type(duration) == 'number' and duration > 0, "duration must be a positive number. Was " .. tostring(duration))
local tsubject = type(subject)
assert(tsubject == 'table' or tsubject == 'userdata', "subject must be a table or userdata. Was " .. tostring(subject))
assert(type(target)== 'table', "target must be a table. Was " .. tostring(target))
assert(type(easing)=='function', "easing must be a function. Was " .. tostring(easing))
checkSubjectAndTargetRecursively(subject, target)
end
local function getEasingFunction(easing)
easing = easing or "linear"
if type(easing) == 'string' then
local name = easing
easing = tween.easing[name]
if type(easing) ~= 'function' then
error("The easing function name '" .. name .. "' is invalid")
end
end
return easing
easing = easing or "linear"
if type(easing) == 'string' then
local name = easing
easing = tween.easing[name]
if type(easing) ~= 'function' then
error("The easing function name '" .. name .. "' is invalid")
end
end
return easing
end
local function performEasingOnSubject(subject, target, initial, clock, duration, easing)
local t,b,c,d
for k,v in pairs(target) do
if type(v) == 'table' then
performEasingOnSubject(subject[k], v, initial[k], clock, duration, easing)
else
t,b,c,d = clock, initial[k], v - initial[k], duration
subject[k] = easing(t,b,c,d)
end
end
local t,b,c,d
for k,v in pairs(target) do
if type(v) == 'table' then
performEasingOnSubject(subject[k], v, initial[k], clock, duration, easing)
else
t,b,c,d = clock, initial[k], v - initial[k], duration
subject[k] = easing(t,b,c,d)
end
end
end
-- Tween methods
@ -316,52 +299,52 @@ local Tween = {}
local Tween_mt = {__index = Tween}
function Tween:set(clock)
assert(type(clock) == 'number', "clock must be a positive number or 0")
assert(type(clock) == 'number', "clock must be a positive number or 0")
self.initial = self.initial or copyTables({}, self.target, self.subject)
self.clock = clock
self.initial = self.initial or copyTables({}, self.target, self.subject)
self.clock = clock
if self.clock <= 0 then
if self.clock <= 0 then
self.clock = 0
copyTables(self.subject, self.initial)
self.clock = 0
copyTables(self.subject, self.initial)
elseif self.clock >= self.duration then -- the tween has expired
elseif self.clock >= self.duration then -- the tween has expired
self.clock = self.duration
copyTables(self.subject, self.target)
self.clock = self.duration
copyTables(self.subject, self.target)
else
else
performEasingOnSubject(self.subject, self.target, self.initial, self.clock, self.duration, self.easing)
performEasingOnSubject(self.subject, self.target, self.initial, self.clock, self.duration, self.easing)
end
end
return self.clock >= self.duration
return self.clock >= self.duration
end
function Tween:reset()
return self:set(0)
return self:set(0)
end
function Tween:update(dt)
assert(type(dt) == 'number', "dt must be a number")
return self:set(self.clock + dt)
assert(type(dt) == 'number', "dt must be a number")
return self:set(self.clock + dt)
end
-- Public interface
function tween.new(duration, subject, target, easing)
easing = getEasingFunction(easing)
checkNewParams(duration, subject, target, easing)
return setmetatable({
duration = duration,
subject = subject,
target = target,
easing = easing,
clock = 0
}, Tween_mt)
easing = getEasingFunction(easing)
checkNewParams(duration, subject, target, easing)
return setmetatable({
duration = duration,
subject = subject,
target = target,
easing = easing,
clock = 0
}, Tween_mt)
end
return tween

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

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -1,7 +1,7 @@
_G.requireInjector(_ENV)
--[[
Adds a task and the control-d hotkey to view the kernel log.
Adds a task and the control-d hotkey to view the kernel log.
--]]
local kernel = _G.kernel
@ -10,42 +10,46 @@ local multishell = _ENV.multishell
local os = _G.os
local term = _G.term
local routine = kernel.getCurrent()
if multishell then
multishell.setTitle(multishell.getCurrent(), 'System Log')
multishell.hideTab(routine.uid)
local function systemLog()
local routine = kernel.getCurrent()
local w, h = kernel.window.getSize()
kernel.window.reposition(1, 2, w, h - 1)
routine.terminal = kernel.window
routine.window = kernel.window
term.redirect(kernel.window)
kernel.hook('mouse_scroll', function(_, eventData)
local dir, y = eventData[1], eventData[3]
if y > 1 then
local currentTab = kernel.getFocused()
if currentTab.terminal.scrollUp and not currentTab.terminal.noAutoScroll then
if dir == -1 then
currentTab.terminal.scrollUp()
else
currentTab.terminal.scrollDown()
end
end
end
end)
keyboard.addHotkey('control-d', function()
local current = kernel.getFocused()
if current.uid ~= routine.uid then
kernel.raise(routine.uid)
elseif kernel.routines[2] then
kernel.raise(kernel.routines[2].uid)
end
end)
os.pullEventRaw('terminate')
keyboard.removeHotkey('control-d')
end
local w, h = kernel.window.getSize()
kernel.window.reposition(1, 2, w, h - 1)
routine.terminal = kernel.window
routine.window = kernel.window
term.redirect(kernel.window)
kernel.hook('mouse_scroll', function(_, eventData)
local dir, y = eventData[1], eventData[3]
if y > 1 then
local currentTab = kernel.getFocused()
if currentTab.terminal.scrollUp and not currentTab.terminal.noAutoScroll then
if dir == -1 then
currentTab.terminal.scrollUp()
else
currentTab.terminal.scrollDown()
end
end
end
end)
keyboard.addHotkey('control-d', function()
local current = kernel.getFocused()
if current.uid ~= routine.uid then
kernel.raise(routine.uid)
elseif kernel.routines[2] then
kernel.raise(kernel.routines[2].uid)
end
end)
os.pullEventRaw('terminate')
keyboard.removeHotkey('control-d')
multishell.openTab({
title = 'System Log',
fn = systemLog,
hidden = true,
})

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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