mirror of
https://github.com/kepler155c/opus
synced 2025-01-11 16:20:26 +00:00
format and installer branches
This commit is contained in:
parent
1eea0d7cd8
commit
5a32fe208e
129
startup
129
startup
@ -4,67 +4,67 @@ local settings = _G.settings
|
||||
local term = _G.term
|
||||
|
||||
local bootOptions = {
|
||||
{ prompt = os.version() },
|
||||
{ prompt = 'Opus' , args = { '/sys/boot/opus.boot' } },
|
||||
{ prompt = 'Opus Shell' , args = { '/sys/boot/opus.boot', 'sys/apps/shell' } },
|
||||
{ prompt = os.version() },
|
||||
{ prompt = 'Opus' , args = { '/sys/boot/opus.boot' } },
|
||||
{ prompt = 'Opus Shell' , args = { '/sys/boot/opus.boot', 'sys/apps/shell' } },
|
||||
}
|
||||
local bootOption = 2
|
||||
if settings then
|
||||
settings.load('.settings')
|
||||
bootOption = tonumber(settings.get('opus.boot_option') or 2) or 2
|
||||
settings.load('.settings')
|
||||
bootOption = tonumber(settings.get('opus.boot_option') or 2) or 2
|
||||
end
|
||||
|
||||
local function startupMenu()
|
||||
while true do
|
||||
term.clear()
|
||||
term.setCursorPos(1, 1)
|
||||
print('Select startup mode')
|
||||
print()
|
||||
for k,option in pairs(bootOptions) do
|
||||
print(k .. ' : ' .. option.prompt)
|
||||
end
|
||||
print('')
|
||||
term.write('> ')
|
||||
local ch = tonumber(_G.read())
|
||||
if ch and bootOptions[ch] then
|
||||
return ch
|
||||
end
|
||||
end
|
||||
while true do
|
||||
term.clear()
|
||||
term.setCursorPos(1, 1)
|
||||
print('Select startup mode')
|
||||
print()
|
||||
for k,option in pairs(bootOptions) do
|
||||
print(k .. ' : ' .. option.prompt)
|
||||
end
|
||||
print('')
|
||||
term.write('> ')
|
||||
local ch = tonumber(_G.read())
|
||||
if ch and bootOptions[ch] then
|
||||
return ch
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
local function splash()
|
||||
local w, h = term.current().getSize()
|
||||
local w, h = term.current().getSize()
|
||||
|
||||
term.setTextColor(colors.white)
|
||||
if not term.isColor() then
|
||||
local str = 'Opus OS'
|
||||
term.setCursorPos((w - #str) / 2, h / 2)
|
||||
term.write(str)
|
||||
else
|
||||
term.setBackgroundColor(colors.black)
|
||||
term.clear()
|
||||
local opus = {
|
||||
'fffff00',
|
||||
'ffff07000',
|
||||
'ff00770b00 4444',
|
||||
'ff077777444444444',
|
||||
'f07777744444444444',
|
||||
'f0000777444444444',
|
||||
'070000111744444',
|
||||
'777770000',
|
||||
'7777000000',
|
||||
'70700000000',
|
||||
'077000000000',
|
||||
}
|
||||
for k,line in ipairs(opus) do
|
||||
term.setCursorPos((w - 18) / 2, k + (h - #opus) / 2)
|
||||
term.blit(string.rep(' ', #line), string.rep('a', #line), line)
|
||||
end
|
||||
end
|
||||
term.setTextColor(colors.white)
|
||||
if not term.isColor() then
|
||||
local str = 'Opus OS'
|
||||
term.setCursorPos((w - #str) / 2, h / 2)
|
||||
term.write(str)
|
||||
else
|
||||
term.setBackgroundColor(colors.black)
|
||||
term.clear()
|
||||
local opus = {
|
||||
'fffff00',
|
||||
'ffff07000',
|
||||
'ff00770b00 4444',
|
||||
'ff077777444444444',
|
||||
'f07777744444444444',
|
||||
'f0000777444444444',
|
||||
'070000111744444',
|
||||
'777770000',
|
||||
'7777000000',
|
||||
'70700000000',
|
||||
'077000000000',
|
||||
}
|
||||
for k,line in ipairs(opus) do
|
||||
term.setCursorPos((w - 18) / 2, k + (h - #opus) / 2)
|
||||
term.blit(string.rep(' ', #line), string.rep('a', #line), line)
|
||||
end
|
||||
end
|
||||
|
||||
local str = 'Press any key for menu'
|
||||
term.setCursorPos((w - #str) / 2, h)
|
||||
term.write(str)
|
||||
local str = 'Press any key for menu'
|
||||
term.setCursorPos((w - #str) / 2, h)
|
||||
term.write(str)
|
||||
end
|
||||
|
||||
term.clear()
|
||||
@ -72,24 +72,25 @@ splash()
|
||||
|
||||
local timerId = os.startTimer(1.5)
|
||||
while true do
|
||||
local e, id = os.pullEvent()
|
||||
if e == 'timer' and id == timerId then
|
||||
break
|
||||
end
|
||||
if e == 'char' then
|
||||
bootOption = startupMenu()
|
||||
if settings then
|
||||
settings.set('opus.boot_option', bootOption)
|
||||
settings.save('.settings')
|
||||
end
|
||||
break
|
||||
end
|
||||
local e, id = os.pullEvent()
|
||||
if e == 'timer' and id == timerId then
|
||||
break
|
||||
end
|
||||
if e == 'char' then
|
||||
bootOption = startupMenu()
|
||||
if settings then
|
||||
settings.set('opus.boot_option', bootOption)
|
||||
settings.save('.settings')
|
||||
end
|
||||
break
|
||||
end
|
||||
end
|
||||
|
||||
term.clear()
|
||||
term.setCursorPos(1, 1)
|
||||
if bootOptions[bootOption].args then
|
||||
os.run(_G.getfenv(1), table.unpack(bootOptions[bootOption].args))
|
||||
os.run(_G.getfenv(1), table.unpack(bootOptions[bootOption].args))
|
||||
else
|
||||
print(bootOptions[bootOption].prompt)
|
||||
print(bootOptions[bootOption].prompt)
|
||||
end
|
||||
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -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)
|
||||
|
@ -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
151
sys/apis/entry.lua
Normal 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
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
204
sys/apis/gps.lua
204
sys/apis/gps.lua
@ -5,151 +5,151 @@ local gps = _G.gps
|
||||
local turtle = _G.turtle
|
||||
|
||||
function GPS.locate(timeout, debug)
|
||||
local pt = { }
|
||||
timeout = timeout or 10
|
||||
pt.x, pt.y, pt.z = gps.locate(timeout, debug)
|
||||
if pt.x then
|
||||
return pt
|
||||
end
|
||||
local pt = { }
|
||||
timeout = timeout or 10
|
||||
pt.x, pt.y, pt.z = gps.locate(timeout, debug)
|
||||
if pt.x then
|
||||
return pt
|
||||
end
|
||||
end
|
||||
|
||||
function GPS.isAvailable()
|
||||
return device.wireless_modem and GPS.locate()
|
||||
return device.wireless_modem and GPS.locate()
|
||||
end
|
||||
|
||||
function GPS.getPoint(timeout, debug)
|
||||
local pt = GPS.locate(timeout, debug)
|
||||
if not pt then
|
||||
return
|
||||
end
|
||||
local pt = GPS.locate(timeout, debug)
|
||||
if not pt then
|
||||
return
|
||||
end
|
||||
|
||||
pt.x = math.floor(pt.x)
|
||||
pt.y = math.floor(pt.y)
|
||||
pt.z = math.floor(pt.z)
|
||||
pt.x = math.floor(pt.x)
|
||||
pt.y = math.floor(pt.y)
|
||||
pt.z = math.floor(pt.z)
|
||||
|
||||
if _G.pocket then
|
||||
pt.y = pt.y - 1
|
||||
end
|
||||
if _G.pocket then
|
||||
pt.y = pt.y - 1
|
||||
end
|
||||
|
||||
return pt
|
||||
return pt
|
||||
end
|
||||
|
||||
function GPS.getHeading(timeout)
|
||||
|
||||
if not turtle then
|
||||
return
|
||||
end
|
||||
if not turtle then
|
||||
return
|
||||
end
|
||||
|
||||
local apt = GPS.locate(timeout)
|
||||
if not apt then
|
||||
return
|
||||
end
|
||||
local apt = GPS.locate(timeout)
|
||||
if not apt then
|
||||
return
|
||||
end
|
||||
|
||||
local heading = turtle.point.heading
|
||||
local heading = turtle.point.heading
|
||||
|
||||
while not turtle.forward() do
|
||||
turtle.turnRight()
|
||||
if turtle.getHeading() == heading then
|
||||
_G.printError('GPS.getPoint: Unable to move forward')
|
||||
return
|
||||
end
|
||||
end
|
||||
while not turtle.forward() do
|
||||
turtle.turnRight()
|
||||
if turtle.getHeading() == heading then
|
||||
_G.printError('GPS.getPoint: Unable to move forward')
|
||||
return
|
||||
end
|
||||
end
|
||||
|
||||
local bpt = GPS.locate()
|
||||
if not bpt then
|
||||
return
|
||||
end
|
||||
local bpt = GPS.locate()
|
||||
if not bpt then
|
||||
return
|
||||
end
|
||||
|
||||
if apt.x < bpt.x then
|
||||
return 0
|
||||
elseif apt.z < bpt.z then
|
||||
return 1
|
||||
elseif apt.x > bpt.x then
|
||||
return 2
|
||||
end
|
||||
return 3
|
||||
if apt.x < bpt.x then
|
||||
return 0
|
||||
elseif apt.z < bpt.z then
|
||||
return 1
|
||||
elseif apt.x > bpt.x then
|
||||
return 2
|
||||
end
|
||||
return 3
|
||||
end
|
||||
|
||||
function GPS.getPointAndHeading(timeout)
|
||||
local heading = GPS.getHeading(timeout)
|
||||
if heading then
|
||||
local pt = GPS.getPoint()
|
||||
if pt then
|
||||
pt.heading = heading
|
||||
end
|
||||
return pt
|
||||
end
|
||||
local heading = GPS.getHeading(timeout)
|
||||
if heading then
|
||||
local pt = GPS.getPoint()
|
||||
if pt then
|
||||
pt.heading = heading
|
||||
end
|
||||
return pt
|
||||
end
|
||||
end
|
||||
|
||||
-- from stock gps API
|
||||
local function trilaterate(A, B, C)
|
||||
local a2b = B.position - A.position
|
||||
local a2c = C.position - A.position
|
||||
local a2b = B.position - A.position
|
||||
local a2c = C.position - A.position
|
||||
|
||||
if math.abs( a2b:normalize():dot( a2c:normalize() ) ) > 0.999 then
|
||||
return
|
||||
end
|
||||
if math.abs( a2b:normalize():dot( a2c:normalize() ) ) > 0.999 then
|
||||
return
|
||||
end
|
||||
|
||||
local d = a2b:length()
|
||||
local ex = a2b:normalize( )
|
||||
local i = ex:dot( a2c )
|
||||
local ey = (a2c - (ex * i)):normalize()
|
||||
local j = ey:dot( a2c )
|
||||
local ez = ex:cross( ey )
|
||||
local d = a2b:length()
|
||||
local ex = a2b:normalize( )
|
||||
local i = ex:dot( a2c )
|
||||
local ey = (a2c - (ex * i)):normalize()
|
||||
local j = ey:dot( a2c )
|
||||
local ez = ex:cross( ey )
|
||||
|
||||
local r1 = A.distance
|
||||
local r2 = B.distance
|
||||
local r3 = C.distance
|
||||
local r1 = A.distance
|
||||
local r2 = B.distance
|
||||
local r3 = C.distance
|
||||
|
||||
local x = (r1*r1 - r2*r2 + d*d) / (2*d)
|
||||
local y = (r1*r1 - r3*r3 - x*x + (x-i)*(x-i) + j*j) / (2*j)
|
||||
local x = (r1*r1 - r2*r2 + d*d) / (2*d)
|
||||
local y = (r1*r1 - r3*r3 - x*x + (x-i)*(x-i) + j*j) / (2*j)
|
||||
|
||||
local result = A.position + (ex * x) + (ey * y)
|
||||
local result = A.position + (ex * x) + (ey * y)
|
||||
|
||||
local zSquared = r1*r1 - x*x - y*y
|
||||
if zSquared > 0 then
|
||||
local z = math.sqrt( zSquared )
|
||||
local result1 = result + (ez * z)
|
||||
local result2 = result - (ez * z)
|
||||
local zSquared = r1*r1 - x*x - y*y
|
||||
if zSquared > 0 then
|
||||
local z = math.sqrt( zSquared )
|
||||
local result1 = result + (ez * z)
|
||||
local result2 = result - (ez * z)
|
||||
|
||||
local rounded1, rounded2 = result1:round(), result2:round()
|
||||
if rounded1.x ~= rounded2.x or rounded1.y ~= rounded2.y or rounded1.z ~= rounded2.z then
|
||||
return rounded1, rounded2
|
||||
else
|
||||
return rounded1
|
||||
end
|
||||
end
|
||||
return result:round()
|
||||
local rounded1, rounded2 = result1:round(), result2:round()
|
||||
if rounded1.x ~= rounded2.x or rounded1.y ~= rounded2.y or rounded1.z ~= rounded2.z then
|
||||
return rounded1, rounded2
|
||||
else
|
||||
return rounded1
|
||||
end
|
||||
end
|
||||
return result:round()
|
||||
end
|
||||
|
||||
local function narrow( p1, p2, fix )
|
||||
local dist1 = math.abs( (p1 - fix.position):length() - fix.distance )
|
||||
local dist2 = math.abs( (p2 - fix.position):length() - fix.distance )
|
||||
local dist1 = math.abs( (p1 - fix.position):length() - fix.distance )
|
||||
local dist2 = math.abs( (p2 - fix.position):length() - fix.distance )
|
||||
|
||||
if math.abs(dist1 - dist2) < 0.05 then
|
||||
return p1, p2
|
||||
elseif dist1 < dist2 then
|
||||
return p1:round()
|
||||
else
|
||||
return p2:round()
|
||||
end
|
||||
if math.abs(dist1 - dist2) < 0.05 then
|
||||
return p1, p2
|
||||
elseif dist1 < dist2 then
|
||||
return p1:round()
|
||||
else
|
||||
return p2:round()
|
||||
end
|
||||
end
|
||||
-- end stock gps api
|
||||
|
||||
function GPS.trilaterate(tFixes)
|
||||
local pos1, pos2 = trilaterate(tFixes[1], tFixes[2], tFixes[3])
|
||||
local pos1, pos2 = trilaterate(tFixes[1], tFixes[2], tFixes[3])
|
||||
|
||||
if pos2 then
|
||||
pos1, pos2 = narrow(pos1, pos2, tFixes[4])
|
||||
end
|
||||
if pos2 then
|
||||
pos1, pos2 = narrow(pos1, pos2, tFixes[4])
|
||||
end
|
||||
|
||||
if pos1 and pos2 then
|
||||
print("Ambiguous position")
|
||||
print("Could be "..pos1.x..","..pos1.y..","..pos1.z.." or "..pos2.x..","..pos2.y..","..pos2.z )
|
||||
return
|
||||
end
|
||||
if pos1 and pos2 then
|
||||
print("Ambiguous position")
|
||||
print("Could be "..pos1.x..","..pos1.y..","..pos1.z.." or "..pos2.x..","..pos2.y..","..pos2.z )
|
||||
return
|
||||
end
|
||||
|
||||
return pos1
|
||||
return pos1
|
||||
end
|
||||
|
||||
return GPS
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -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
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -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}
|
||||
|
@ -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
|
@ -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,
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -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)
|
||||
|
@ -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
|
112
sys/apis/nft.lua
112
sys/apis/nft.lua
@ -6,75 +6,75 @@ local NFT = { }
|
||||
|
||||
local tColourLookup = { }
|
||||
for n = 1, 16 do
|
||||
tColourLookup[string.byte("0123456789abcdef", n, n)] = 2 ^ (n - 1)
|
||||
tColourLookup[string.byte("0123456789abcdef", n, n)] = 2 ^ (n - 1)
|
||||
end
|
||||
|
||||
local function getColourOf(hex)
|
||||
return tColourLookup[hex:byte()]
|
||||
return tColourLookup[hex:byte()]
|
||||
end
|
||||
|
||||
function NFT.parse(imageText)
|
||||
local image = {
|
||||
fg = { },
|
||||
bg = { },
|
||||
text = { },
|
||||
}
|
||||
local image = {
|
||||
fg = { },
|
||||
bg = { },
|
||||
text = { },
|
||||
}
|
||||
|
||||
local num = 1
|
||||
local lines = Util.split(imageText)
|
||||
while #lines[#lines] == 0 do
|
||||
table.remove(lines, #lines)
|
||||
end
|
||||
local num = 1
|
||||
local lines = Util.split(imageText)
|
||||
while #lines[#lines] == 0 do
|
||||
table.remove(lines, #lines)
|
||||
end
|
||||
|
||||
for _,sLine in ipairs(lines) do
|
||||
table.insert(image.fg, { })
|
||||
table.insert(image.bg, { })
|
||||
table.insert(image.text, { })
|
||||
for _,sLine in ipairs(lines) do
|
||||
table.insert(image.fg, { })
|
||||
table.insert(image.bg, { })
|
||||
table.insert(image.text, { })
|
||||
|
||||
--As we're no longer 1-1, we keep track of what index to write to
|
||||
local writeIndex = 1
|
||||
--Tells us if we've hit a 30 or 31 (BG and FG respectively)- next char specifies the curr colour
|
||||
local bgNext, fgNext = false, false
|
||||
--The current background and foreground colours
|
||||
local currBG, currFG = nil,nil
|
||||
for i = 1, #sLine do
|
||||
local nextChar = string.sub(sLine, i, i)
|
||||
if nextChar:byte() == 30 then
|
||||
bgNext = true
|
||||
elseif nextChar:byte() == 31 then
|
||||
fgNext = true
|
||||
elseif bgNext then
|
||||
currBG = getColourOf(nextChar)
|
||||
bgNext = false
|
||||
elseif fgNext then
|
||||
currFG = getColourOf(nextChar)
|
||||
fgNext = false
|
||||
else
|
||||
if nextChar ~= " " and currFG == nil then
|
||||
currFG = _G.colors.white
|
||||
end
|
||||
image.bg[num][writeIndex] = currBG
|
||||
image.fg[num][writeIndex] = currFG
|
||||
image.text[num][writeIndex] = nextChar
|
||||
writeIndex = writeIndex + 1
|
||||
end
|
||||
end
|
||||
image.height = num
|
||||
if not image.width or writeIndex - 1 > image.width then
|
||||
image.width = writeIndex - 1
|
||||
end
|
||||
num = num+1
|
||||
end
|
||||
return image
|
||||
--As we're no longer 1-1, we keep track of what index to write to
|
||||
local writeIndex = 1
|
||||
--Tells us if we've hit a 30 or 31 (BG and FG respectively)- next char specifies the curr colour
|
||||
local bgNext, fgNext = false, false
|
||||
--The current background and foreground colours
|
||||
local currBG, currFG = nil,nil
|
||||
for i = 1, #sLine do
|
||||
local nextChar = string.sub(sLine, i, i)
|
||||
if nextChar:byte() == 30 then
|
||||
bgNext = true
|
||||
elseif nextChar:byte() == 31 then
|
||||
fgNext = true
|
||||
elseif bgNext then
|
||||
currBG = getColourOf(nextChar)
|
||||
bgNext = false
|
||||
elseif fgNext then
|
||||
currFG = getColourOf(nextChar)
|
||||
fgNext = false
|
||||
else
|
||||
if nextChar ~= " " and currFG == nil then
|
||||
currFG = _G.colors.white
|
||||
end
|
||||
image.bg[num][writeIndex] = currBG
|
||||
image.fg[num][writeIndex] = currFG
|
||||
image.text[num][writeIndex] = nextChar
|
||||
writeIndex = writeIndex + 1
|
||||
end
|
||||
end
|
||||
image.height = num
|
||||
if not image.width or writeIndex - 1 > image.width then
|
||||
image.width = writeIndex - 1
|
||||
end
|
||||
num = num+1
|
||||
end
|
||||
return image
|
||||
end
|
||||
|
||||
function NFT.load(path)
|
||||
|
||||
local imageText = Util.readFile(path)
|
||||
if not imageText then
|
||||
error('Unable to read image file')
|
||||
end
|
||||
return NFT.parse(imageText)
|
||||
local imageText = Util.readFile(path)
|
||||
if not imageText then
|
||||
error('Unable to read image file')
|
||||
end
|
||||
return NFT.parse(imageText)
|
||||
end
|
||||
|
||||
return NFT
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -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 })
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -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()
|
||||
|
||||
|
4653
sys/apis/ui.lua
4653
sys/apis/ui.lua
File diff suppressed because it is too large
Load Diff
@ -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
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -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
@ -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
456
sys/apps/Installer.lua
Normal 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
|
@ -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()
|
||||
|
@ -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)
|
||||
|
@ -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')
|
||||
|
@ -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)
|
||||
|
@ -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)
|
||||
|
@ -1,4 +1,4 @@
|
||||
_G.requireInjector()
|
||||
_G.requireInjector(_ENV)
|
||||
|
||||
local Event = require('event')
|
||||
local Util = require('util')
|
||||
|
@ -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
|
||||
|
966
sys/apps/shell
966
sys/apps/shell
File diff suppressed because it is too large
Load Diff
@ -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
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -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)
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -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)
|
||||
|
@ -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,
|
||||
})
|
||||
|
@ -1,3 +0,0 @@
|
||||
term.clear()
|
||||
term.setCursorPos(1, 1)
|
||||
print(os.version())
|
@ -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
|
||||
|
@ -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')
|
||||
|
212
sys/etc/app.db
212
sys/etc/app.db
@ -1,140 +1,140 @@
|
||||
{
|
||||
[ "53ebc572b4a44802ba114729f07bdaaf5409a9d7" ] = {
|
||||
category = "Apps",
|
||||
icon = "\0304 \030 \
|
||||
[ "53ebc572b4a44802ba114729f07bdaaf5409a9d7" ] = {
|
||||
category = "Apps",
|
||||
icon = "\0304 \030 \
|
||||
\030f \0304 \0307 \030 \031 \031f)\
|
||||
\030f \0304 \0307 \030 \031f)",
|
||||
title = "Network",
|
||||
run = "Network.lua",
|
||||
},
|
||||
c7116629a6a855cb774d9c7c8ad822fd83c71fb5 = {
|
||||
title = "Reboot",
|
||||
category = "System",
|
||||
icon = "\0304\031f \030f\0310o..\0304\031f \
|
||||
title = "Network",
|
||||
run = "Network.lua",
|
||||
},
|
||||
c7116629a6a855cb774d9c7c8ad822fd83c71fb5 = {
|
||||
title = "Reboot",
|
||||
category = "System",
|
||||
icon = "\0304\031f \030f\0310o..\0304\031f \
|
||||
\0304\031f \030f\0310.o.\0304\031f \
|
||||
\0304\031f - ",
|
||||
run = "rom/programs/reboot",
|
||||
},
|
||||
fb91e24fa52d8d2b32937bf04d843f730319a902 = {
|
||||
title = "Update",
|
||||
category = "System",
|
||||
icon = "\0301\03171\03180\030 \031 \
|
||||
run = "rom/programs/reboot",
|
||||
},
|
||||
fb91e24fa52d8d2b32937bf04d843f730319a902 = {
|
||||
title = "Update",
|
||||
category = "System",
|
||||
icon = "\0301\03171\03180\030 \031 \
|
||||
\0301\03181\030 \031 \
|
||||
\0301\03170\03180\03171\0307\031f>",
|
||||
run = "http://pastebin.com/raw/sj4VMVJj",
|
||||
},
|
||||
c47ae15370cfe1ed2781eedc1dc2547d12d9e972 = {
|
||||
title = "Help",
|
||||
category = "Apps",
|
||||
icon = " \031f?\031 \
|
||||
run = "http://pastebin.com/raw/sj4VMVJj",
|
||||
},
|
||||
c47ae15370cfe1ed2781eedc1dc2547d12d9e972 = {
|
||||
title = "Help",
|
||||
category = "Apps",
|
||||
icon = " \031f?\031 \
|
||||
\031f?\031 \
|
||||
\031f?",
|
||||
run = "Help.lua",
|
||||
},
|
||||
b0832074630eb731d7fbe8074de48a90cd9bb220 = {
|
||||
title = "Lua",
|
||||
category = "Apps",
|
||||
icon = "\030f \
|
||||
\031f?",
|
||||
run = "Help.lua",
|
||||
},
|
||||
b0832074630eb731d7fbe8074de48a90cd9bb220 = {
|
||||
title = "Lua",
|
||||
category = "Apps",
|
||||
icon = "\030f \
|
||||
\030f\0310lua>\031 \
|
||||
\030f ",
|
||||
run = "sys/apps/Lua.lua",
|
||||
},
|
||||
df485c871329671f46570634d63216761441bcd6 = {
|
||||
title = "Devices",
|
||||
category = "System",
|
||||
icon = "\0304 \030 \
|
||||
run = "sys/apps/Lua.lua",
|
||||
},
|
||||
df485c871329671f46570634d63216761441bcd6 = {
|
||||
title = "Devices",
|
||||
category = "System",
|
||||
icon = "\0304 \030 \
|
||||
\030f \0304 \0307 \030 \031 \031f_\
|
||||
\030f \0304 \0307 \030 \031f/",
|
||||
run = "Devices.lua",
|
||||
},
|
||||
bc0792d8dc81e8aa30b987246a5ce97c40cd6833 = {
|
||||
title = "System",
|
||||
category = "System",
|
||||
icon = " \0307\031f| \
|
||||
run = "Devices.lua",
|
||||
},
|
||||
bc0792d8dc81e8aa30b987246a5ce97c40cd6833 = {
|
||||
title = "System",
|
||||
category = "System",
|
||||
icon = " \0307\031f| \
|
||||
\0307\031f---o\030 \031 \
|
||||
\0307\031f| ",
|
||||
run = "System.lua",
|
||||
},
|
||||
c5497bca58468ae64aed6c0fd921109217988db3 = {
|
||||
title = "Events",
|
||||
category = "System",
|
||||
icon = "\0304\031f \030 \0311e\
|
||||
\0307\031f| ",
|
||||
run = "System.lua",
|
||||
},
|
||||
c5497bca58468ae64aed6c0fd921109217988db3 = {
|
||||
title = "Events",
|
||||
category = "System",
|
||||
icon = "\0304\031f \030 \0311e\
|
||||
\030f\031f \0304 \030 \0311ee\031f \
|
||||
\030f\031f \0304 \030 \0311e\031f ",
|
||||
run = "Events.lua",
|
||||
},
|
||||
[ "2a4d562b1d9a9c90bdede6fac8ce4f7402462b86" ] = {
|
||||
title = "Tasks",
|
||||
category = "System",
|
||||
icon = "\030f\031f \0315/\
|
||||
run = "Events.lua",
|
||||
},
|
||||
[ "2a4d562b1d9a9c90bdede6fac8ce4f7402462b86" ] = {
|
||||
title = "Tasks",
|
||||
category = "System",
|
||||
icon = "\030f\031f \0315/\
|
||||
\030f\031f \0315/\\/ \
|
||||
\030f\0315/\031f ",
|
||||
run = "Tasks.lua",
|
||||
},
|
||||
[ "6ce6c512ea433a7fc5c8841628e7696cd0ff7f2b" ] = {
|
||||
title = "Files",
|
||||
category = "Apps",
|
||||
icon = "\0300\0317==\031 \0307 \
|
||||
run = "Tasks.lua",
|
||||
},
|
||||
[ "6ce6c512ea433a7fc5c8841628e7696cd0ff7f2b" ] = {
|
||||
title = "Files",
|
||||
category = "Apps",
|
||||
icon = "\0300\0317==\031 \0307 \
|
||||
\0300\0317====\
|
||||
\0300\0317====",
|
||||
run = "Files.lua",
|
||||
},
|
||||
[ "7fddb7d8d1d60b1eeefa9af01082e0811d4b484d" ] = {
|
||||
title = "Shutdown",
|
||||
category = "System",
|
||||
icon = "\0304\031f \
|
||||
run = "Files.lua",
|
||||
},
|
||||
[ "7fddb7d8d1d60b1eeefa9af01082e0811d4b484d" ] = {
|
||||
title = "Shutdown",
|
||||
category = "System",
|
||||
icon = "\0304\031f \
|
||||
\0304\031f \030f\0310zz\031 \
|
||||
\0304\031f \030f ",
|
||||
run = "/rom/programs/shutdown",
|
||||
},
|
||||
bdc1fd5d3c0f3dcfd55d010426e61bf9451e680d = {
|
||||
title = "Shell",
|
||||
category = "Apps",
|
||||
icon = "\030f\0314\151\131\131\131\131\
|
||||
run = "/rom/programs/shutdown",
|
||||
},
|
||||
bdc1fd5d3c0f3dcfd55d010426e61bf9451e680d = {
|
||||
title = "Shell",
|
||||
category = "Apps",
|
||||
icon = "\030f\0314\151\131\131\131\131\
|
||||
\030f\0314\149\030f\0314> \0310_ \
|
||||
\030f\0314\149\030f ",
|
||||
run = "shell",
|
||||
},
|
||||
b77aad5fb24921ef76ac8f3e500ed93fddae8f2a = {
|
||||
title = "Redirection",
|
||||
category = "Games",
|
||||
icon = "\0307 \0308 \0307 \
|
||||
run = "shell",
|
||||
},
|
||||
b77aad5fb24921ef76ac8f3e500ed93fddae8f2a = {
|
||||
title = "Redirection",
|
||||
category = "Games",
|
||||
icon = "\0307 \0308 \0307 \
|
||||
\0308\031b> \030b\0310>\0308\0318 \
|
||||
\0307 ",
|
||||
run = "rom/programs/fun/advanced/redirection",
|
||||
requires = 'advanced',
|
||||
},
|
||||
f39d173d91c22348565c20283b89d4d1cabd3b7e = {
|
||||
title = "Falling",
|
||||
category = "Games",
|
||||
icon = "\030f \0302 \
|
||||
run = "rom/programs/fun/advanced/redirection",
|
||||
requires = 'advanced',
|
||||
},
|
||||
f39d173d91c22348565c20283b89d4d1cabd3b7e = {
|
||||
title = "Falling",
|
||||
category = "Games",
|
||||
icon = "\030f \0302 \
|
||||
\0309 \0302 \0301 \
|
||||
\030e \0309 \0301 ",
|
||||
run = "rom/programs/pocket/falling",
|
||||
requires = 'advancedPocket',
|
||||
},
|
||||
db56e2e1db9f7accfc37f2b132d27505c66ba521 = {
|
||||
title = "Adventure",
|
||||
category = "Games",
|
||||
icon = "\030f\0310You \031 \
|
||||
run = "rom/programs/pocket/falling",
|
||||
requires = 'advancedPocket',
|
||||
},
|
||||
db56e2e1db9f7accfc37f2b132d27505c66ba521 = {
|
||||
title = "Adventure",
|
||||
category = "Games",
|
||||
icon = "\030f\0310You \031 \
|
||||
\030f\0310Ther\030 \031 \
|
||||
\030f\0314?\031f \031 \030 ",
|
||||
run = "rom/programs/fun/adventure",
|
||||
},
|
||||
[ "76b849f460640bc789c433894382fb5acbac42a2" ] = {
|
||||
title = "Worm",
|
||||
category = "Games",
|
||||
icon = "\030d \030 \030e \030 \
|
||||
run = "rom/programs/fun/adventure",
|
||||
},
|
||||
[ "76b849f460640bc789c433894382fb5acbac42a2" ] = {
|
||||
title = "Worm",
|
||||
category = "Games",
|
||||
icon = "\030d \030 \030e \030 \
|
||||
\030d \030 \
|
||||
\030d ",
|
||||
run = "/rom/programs/fun/worm",
|
||||
},
|
||||
[ "9f46ca3ef617166776ef6014a58d4e66859caa62" ] = {
|
||||
title = "DJ",
|
||||
category = "Games",
|
||||
icon = " \030f \
|
||||
run = "/rom/programs/fun/worm",
|
||||
},
|
||||
[ "9f46ca3ef617166776ef6014a58d4e66859caa62" ] = {
|
||||
title = "DJ",
|
||||
category = "Games",
|
||||
icon = " \030f \
|
||||
\030f \0307 \
|
||||
\030f \0307 \0300 ",
|
||||
run = "/rom/programs/fun/dj",
|
||||
},
|
||||
run = "/rom/programs/fun/dj",
|
||||
},
|
||||
}
|
||||
|
@ -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',
|
||||
},
|
||||
}
|
||||
|
@ -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)
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -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
@ -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)
|
||||
|
376
sys/kernel.lua
376
sys/kernel.lua
@ -4,9 +4,9 @@ local Terminal = require('terminal')
|
||||
local Util = require('util')
|
||||
|
||||
_G.kernel = {
|
||||
UID = 0,
|
||||
hooks = { },
|
||||
routines = { },
|
||||
UID = 0,
|
||||
hooks = { },
|
||||
routines = { },
|
||||
}
|
||||
|
||||
local fs = _G.fs
|
||||
@ -23,271 +23,271 @@ kernel.window = window.create(kernel.terminal, 1, 1, w, h, false)
|
||||
Terminal.scrollable(kernel.window)
|
||||
|
||||
local focusedRoutineEvents = Util.transpose {
|
||||
'char', 'key', 'key_up',
|
||||
'mouse_click', 'mouse_drag', 'mouse_scroll', 'mouse_up',
|
||||
'paste', 'terminate',
|
||||
'char', 'key', 'key_up',
|
||||
'mouse_click', 'mouse_drag', 'mouse_scroll', 'mouse_up',
|
||||
'paste', 'terminate',
|
||||
}
|
||||
|
||||
_G.debug = function(pattern, ...)
|
||||
local oldTerm = term.redirect(kernel.window)
|
||||
Util.print(pattern, ...)
|
||||
term.redirect(oldTerm)
|
||||
local oldTerm = term.redirect(kernel.window)
|
||||
Util.print(pattern, ...)
|
||||
term.redirect(oldTerm)
|
||||
end
|
||||
|
||||
-- any function that runs in a kernel hook does not run in
|
||||
-- a separate coroutine or have a window. an error in a hook
|
||||
-- function will crash the system.
|
||||
function kernel.hook(event, fn)
|
||||
if type(event) == 'table' then
|
||||
for _,v in pairs(event) do
|
||||
kernel.hook(v, fn)
|
||||
end
|
||||
else
|
||||
if not kernel.hooks[event] then
|
||||
kernel.hooks[event] = { }
|
||||
end
|
||||
table.insert(kernel.hooks[event], fn)
|
||||
end
|
||||
if type(event) == 'table' then
|
||||
for _,v in pairs(event) do
|
||||
kernel.hook(v, fn)
|
||||
end
|
||||
else
|
||||
if not kernel.hooks[event] then
|
||||
kernel.hooks[event] = { }
|
||||
end
|
||||
table.insert(kernel.hooks[event], fn)
|
||||
end
|
||||
end
|
||||
|
||||
-- you can only unhook from within the function that hooked
|
||||
function kernel.unhook(event, fn)
|
||||
local eventHooks = kernel.hooks[event]
|
||||
if eventHooks then
|
||||
Util.removeByValue(eventHooks, fn)
|
||||
if #eventHooks == 0 then
|
||||
kernel.hooks[event] = nil
|
||||
end
|
||||
end
|
||||
local eventHooks = kernel.hooks[event]
|
||||
if eventHooks then
|
||||
Util.removeByValue(eventHooks, fn)
|
||||
if #eventHooks == 0 then
|
||||
kernel.hooks[event] = nil
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
local Routine = { }
|
||||
|
||||
function Routine:resume(event, ...)
|
||||
if not self.co or coroutine.status(self.co) == 'dead' then
|
||||
return
|
||||
end
|
||||
if not self.co or coroutine.status(self.co) == 'dead' then
|
||||
return
|
||||
end
|
||||
|
||||
if not self.filter or self.filter == event or event == "terminate" then
|
||||
local previousTerm = term.redirect(self.terminal)
|
||||
if not self.filter or self.filter == event or event == "terminate" then
|
||||
local previousTerm = term.redirect(self.terminal)
|
||||
|
||||
local previous = kernel.running
|
||||
kernel.running = self -- stupid shell set title
|
||||
local ok, result = coroutine.resume(self.co, event, ...)
|
||||
kernel.running = previous
|
||||
local previous = kernel.running
|
||||
kernel.running = self -- stupid shell set title
|
||||
local ok, result = coroutine.resume(self.co, event, ...)
|
||||
kernel.running = previous
|
||||
|
||||
if ok then
|
||||
self.filter = result
|
||||
else
|
||||
_G.printError(result)
|
||||
end
|
||||
if ok then
|
||||
self.filter = result
|
||||
else
|
||||
_G.printError(result)
|
||||
end
|
||||
|
||||
self.terminal = term.current()
|
||||
term.redirect(previousTerm)
|
||||
self.terminal = term.current()
|
||||
term.redirect(previousTerm)
|
||||
|
||||
if not ok and self.haltOnError then
|
||||
error(result)
|
||||
end
|
||||
if coroutine.status(self.co) == 'dead' then
|
||||
Util.removeByValue(kernel.routines, self)
|
||||
if #kernel.routines > 0 then
|
||||
os.queueEvent('kernel_focus', kernel.routines[1].uid)
|
||||
end
|
||||
if self.haltOnExit then
|
||||
kernel.halt()
|
||||
end
|
||||
end
|
||||
return ok, result
|
||||
end
|
||||
if not ok and self.haltOnError then
|
||||
error(result)
|
||||
end
|
||||
if coroutine.status(self.co) == 'dead' then
|
||||
Util.removeByValue(kernel.routines, self)
|
||||
if #kernel.routines > 0 then
|
||||
os.queueEvent('kernel_focus', kernel.routines[1].uid)
|
||||
end
|
||||
if self.haltOnExit then
|
||||
kernel.halt()
|
||||
end
|
||||
end
|
||||
return ok, result
|
||||
end
|
||||
end
|
||||
|
||||
function kernel.getFocused()
|
||||
return kernel.routines[1]
|
||||
return kernel.routines[1]
|
||||
end
|
||||
|
||||
function kernel.getCurrent()
|
||||
return kernel.running
|
||||
return kernel.running
|
||||
end
|
||||
|
||||
function kernel.newRoutine(args)
|
||||
kernel.UID = kernel.UID + 1
|
||||
kernel.UID = kernel.UID + 1
|
||||
|
||||
local routine = setmetatable({
|
||||
uid = kernel.UID,
|
||||
timestamp = os.clock(),
|
||||
terminal = kernel.window,
|
||||
window = kernel.window,
|
||||
}, { __index = Routine })
|
||||
local routine = setmetatable({
|
||||
uid = kernel.UID,
|
||||
timestamp = os.clock(),
|
||||
terminal = kernel.window,
|
||||
window = kernel.window,
|
||||
}, { __index = Routine })
|
||||
|
||||
Util.merge(routine, args)
|
||||
routine.env = args.env or Util.shallowCopy(shell.getEnv())
|
||||
Util.merge(routine, args)
|
||||
routine.env = args.env or Util.shallowCopy(shell.getEnv())
|
||||
|
||||
return routine
|
||||
return routine
|
||||
end
|
||||
|
||||
function kernel.launch(routine)
|
||||
routine.co = routine.co or coroutine.create(function()
|
||||
local result, err
|
||||
routine.co = routine.co or coroutine.create(function()
|
||||
local result, err
|
||||
|
||||
if routine.fn then
|
||||
result, err = Util.runFunction(routine.env, routine.fn, table.unpack(routine.args or { } ))
|
||||
elseif routine.path then
|
||||
result, err = Util.run(routine.env, routine.path, table.unpack(routine.args or { } ))
|
||||
else
|
||||
err = 'kernel: invalid routine'
|
||||
end
|
||||
if routine.fn then
|
||||
result, err = Util.runFunction(routine.env, routine.fn, table.unpack(routine.args or { } ))
|
||||
elseif routine.path then
|
||||
result, err = Util.run(routine.env, routine.path, table.unpack(routine.args or { } ))
|
||||
else
|
||||
err = 'kernel: invalid routine'
|
||||
end
|
||||
|
||||
if not result and err ~= 'Terminated' then
|
||||
error(err or 'Error occurred', 2)
|
||||
end
|
||||
end)
|
||||
if not result and err ~= 'Terminated' then
|
||||
error(err or 'Error occurred', 2)
|
||||
end
|
||||
end)
|
||||
|
||||
table.insert(kernel.routines, routine)
|
||||
table.insert(kernel.routines, routine)
|
||||
|
||||
local s, m = routine:resume()
|
||||
local s, m = routine:resume()
|
||||
|
||||
return not s and s or routine.uid, m
|
||||
return not s and s or routine.uid, m
|
||||
end
|
||||
|
||||
function kernel.run(args)
|
||||
local routine = kernel.newRoutine(args)
|
||||
kernel.launch(routine)
|
||||
return routine
|
||||
local routine = kernel.newRoutine(args)
|
||||
kernel.launch(routine)
|
||||
return routine
|
||||
end
|
||||
|
||||
function kernel.raise(uid)
|
||||
local routine = Util.find(kernel.routines, 'uid', uid)
|
||||
local routine = Util.find(kernel.routines, 'uid', uid)
|
||||
|
||||
if routine then
|
||||
local previous = kernel.routines[1]
|
||||
if routine ~= previous then
|
||||
Util.removeByValue(kernel.routines, routine)
|
||||
table.insert(kernel.routines, 1, routine)
|
||||
end
|
||||
os.queueEvent('kernel_focus', routine.uid, previous and previous.uid)
|
||||
return true
|
||||
end
|
||||
return false
|
||||
if routine then
|
||||
local previous = kernel.routines[1]
|
||||
if routine ~= previous then
|
||||
Util.removeByValue(kernel.routines, routine)
|
||||
table.insert(kernel.routines, 1, routine)
|
||||
end
|
||||
os.queueEvent('kernel_focus', routine.uid, previous and previous.uid)
|
||||
return true
|
||||
end
|
||||
return false
|
||||
end
|
||||
|
||||
function kernel.lower(uid)
|
||||
local routine = Util.find(kernel.routines, 'uid', uid)
|
||||
local routine = Util.find(kernel.routines, 'uid', uid)
|
||||
|
||||
if routine and #kernel.routines > 1 then
|
||||
if routine == kernel.routines[1] then
|
||||
local nextRoutine = kernel.routines[2]
|
||||
if nextRoutine then
|
||||
kernel.raise(nextRoutine.uid)
|
||||
end
|
||||
end
|
||||
if routine and #kernel.routines > 1 then
|
||||
if routine == kernel.routines[1] then
|
||||
local nextRoutine = kernel.routines[2]
|
||||
if nextRoutine then
|
||||
kernel.raise(nextRoutine.uid)
|
||||
end
|
||||
end
|
||||
|
||||
Util.removeByValue(kernel.routines, routine)
|
||||
table.insert(kernel.routines, routine)
|
||||
return true
|
||||
end
|
||||
return false
|
||||
Util.removeByValue(kernel.routines, routine)
|
||||
table.insert(kernel.routines, routine)
|
||||
return true
|
||||
end
|
||||
return false
|
||||
end
|
||||
|
||||
function kernel.find(uid)
|
||||
return Util.find(kernel.routines, 'uid', uid)
|
||||
return Util.find(kernel.routines, 'uid', uid)
|
||||
end
|
||||
|
||||
function kernel.halt()
|
||||
os.queueEvent('kernel_halt')
|
||||
os.queueEvent('kernel_halt')
|
||||
end
|
||||
|
||||
function kernel.event(event, eventData)
|
||||
local stopPropagation
|
||||
local stopPropagation
|
||||
|
||||
local eventHooks = kernel.hooks[event]
|
||||
if eventHooks then
|
||||
for i = #eventHooks, 1, -1 do
|
||||
stopPropagation = eventHooks[i](event, eventData)
|
||||
if stopPropagation then
|
||||
break
|
||||
end
|
||||
end
|
||||
end
|
||||
local eventHooks = kernel.hooks[event]
|
||||
if eventHooks then
|
||||
for i = #eventHooks, 1, -1 do
|
||||
stopPropagation = eventHooks[i](event, eventData)
|
||||
if stopPropagation then
|
||||
break
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
if not stopPropagation then
|
||||
if focusedRoutineEvents[event] then
|
||||
local active = kernel.routines[1]
|
||||
if active then
|
||||
active:resume(event, table.unpack(eventData))
|
||||
end
|
||||
else
|
||||
-- Passthrough to all processes
|
||||
for _,routine in pairs(Util.shallowCopy(kernel.routines)) do
|
||||
routine:resume(event, table.unpack(eventData))
|
||||
end
|
||||
end
|
||||
end
|
||||
if not stopPropagation then
|
||||
if focusedRoutineEvents[event] then
|
||||
local active = kernel.routines[1]
|
||||
if active then
|
||||
active:resume(event, table.unpack(eventData))
|
||||
end
|
||||
else
|
||||
-- Passthrough to all processes
|
||||
for _,routine in pairs(Util.shallowCopy(kernel.routines)) do
|
||||
routine:resume(event, table.unpack(eventData))
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
function kernel.start()
|
||||
local s, m = pcall(function()
|
||||
repeat
|
||||
local eventData = { os.pullEventRaw() }
|
||||
local event = table.remove(eventData, 1)
|
||||
kernel.event(event, eventData)
|
||||
until event == 'kernel_halt'
|
||||
end)
|
||||
local s, m = pcall(function()
|
||||
repeat
|
||||
local eventData = { os.pullEventRaw() }
|
||||
local event = table.remove(eventData, 1)
|
||||
kernel.event(event, eventData)
|
||||
until event == 'kernel_halt'
|
||||
end)
|
||||
|
||||
if not s then
|
||||
kernel.window.setVisible(true)
|
||||
term.redirect(kernel.window)
|
||||
print('\nCrash detected\n')
|
||||
_G.printError(m)
|
||||
end
|
||||
term.redirect(kernel.terminal)
|
||||
if not s then
|
||||
kernel.window.setVisible(true)
|
||||
term.redirect(kernel.window)
|
||||
print('\nCrash detected\n')
|
||||
_G.printError(m)
|
||||
end
|
||||
term.redirect(kernel.terminal)
|
||||
end
|
||||
|
||||
local function init(...)
|
||||
local args = { ... }
|
||||
local args = { ... }
|
||||
|
||||
local runLevel = #args > 0 and 6 or 7
|
||||
local runLevel = #args > 0 and 6 or 7
|
||||
|
||||
print('Starting Opus OS')
|
||||
local dir = 'sys/extensions'
|
||||
local files = fs.list(dir)
|
||||
table.sort(files)
|
||||
for _,file in ipairs(files) do
|
||||
local level = file:match('(%d).%S+.lua')
|
||||
if tonumber(level) <= runLevel then
|
||||
local s, m = shell.run(fs.combine(dir, file))
|
||||
if not s then
|
||||
error(m)
|
||||
end
|
||||
os.sleep(0)
|
||||
end
|
||||
end
|
||||
print('Starting Opus OS')
|
||||
local dir = 'sys/extensions'
|
||||
local files = fs.list(dir)
|
||||
table.sort(files)
|
||||
for _,file in ipairs(files) do
|
||||
local level = file:match('(%d).%S+.lua')
|
||||
if tonumber(level) <= runLevel then
|
||||
local s, m = shell.run(fs.combine(dir, file))
|
||||
if not s then
|
||||
error(m)
|
||||
end
|
||||
os.sleep(0)
|
||||
end
|
||||
end
|
||||
|
||||
os.queueEvent('kernel_ready')
|
||||
os.queueEvent('kernel_ready')
|
||||
|
||||
if args[1] then
|
||||
kernel.hook('kernel_ready', function()
|
||||
local s, m = kernel.run({
|
||||
title = args[1],
|
||||
path = 'sys/apps/shell',
|
||||
args = args,
|
||||
haltOnExit = true,
|
||||
haltOnError = true,
|
||||
terminal = kernel.terminal,
|
||||
})
|
||||
if s then
|
||||
kernel.raise(s.uid)
|
||||
else
|
||||
error(m)
|
||||
end
|
||||
end)
|
||||
end
|
||||
if args[1] then
|
||||
kernel.hook('kernel_ready', function()
|
||||
local s, m = kernel.run({
|
||||
title = args[1],
|
||||
path = 'sys/apps/shell',
|
||||
args = args,
|
||||
haltOnExit = true,
|
||||
haltOnError = true,
|
||||
terminal = kernel.terminal,
|
||||
})
|
||||
if s then
|
||||
kernel.raise(s.uid)
|
||||
else
|
||||
error(m)
|
||||
end
|
||||
end)
|
||||
end
|
||||
end
|
||||
|
||||
kernel.run({
|
||||
fn = init,
|
||||
title = 'init',
|
||||
haltOnError = true,
|
||||
args = { ... },
|
||||
fn = init,
|
||||
title = 'init',
|
||||
haltOnError = true,
|
||||
args = { ... },
|
||||
})
|
||||
|
||||
kernel.start()
|
||||
|
@ -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)
|
||||
|
@ -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
|
||||
|
@ -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)
|
||||
|
@ -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)
|
||||
|
@ -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)
|
||||
|
@ -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)
|
||||
|
@ -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)
|
||||
|
@ -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)
|
||||
|
Loading…
Reference in New Issue
Block a user