mirror of
https://github.com/kepler155c/opus
synced 2025-04-14 06:43:16 +00:00
Merge branch 'develop-1.8' into master-1.8
This commit is contained in:
commit
ee6af86da8
@ -1,6 +1,6 @@
|
||||
MIT License
|
||||
|
||||
Copyright (c) 2016-2017 kepler155c
|
||||
Copyright (c) 2016-2019 kepler155c
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
|
96
startup
96
startup
@ -1,96 +0,0 @@
|
||||
local colors = _G.colors
|
||||
local os = _G.os
|
||||
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' } },
|
||||
}
|
||||
local bootOption = 2
|
||||
if settings then
|
||||
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
|
||||
end
|
||||
|
||||
local function splash()
|
||||
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
|
||||
|
||||
local str = 'Press any key for menu'
|
||||
term.setCursorPos((w - #str) / 2, h)
|
||||
term.write(str)
|
||||
end
|
||||
|
||||
term.clear()
|
||||
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
|
||||
end
|
||||
|
||||
term.clear()
|
||||
term.setCursorPos(1, 1)
|
||||
if bootOptions[bootOption].args then
|
||||
os.run(_G.getfenv(1), table.unpack(bootOptions[bootOption].args))
|
||||
else
|
||||
print(bootOptions[bootOption].prompt)
|
||||
end
|
||||
|
181
startup.lua
Normal file
181
startup.lua
Normal file
@ -0,0 +1,181 @@
|
||||
--[[
|
||||
.startup.boot
|
||||
delay
|
||||
description: delays amount before starting the default selection
|
||||
default: 1.5
|
||||
|
||||
preload
|
||||
description : runs before menu is displayed, can be used for password
|
||||
locking, drive encryption, etc.
|
||||
example : { [1] = '/path/somefile.lua', [2] = 'path2/another.lua' }
|
||||
|
||||
menu
|
||||
description: array of menu entries (see .startup.boot for examples)
|
||||
]]
|
||||
|
||||
local colors = _G.colors
|
||||
local fs = _G.fs
|
||||
local keys = _G.keys
|
||||
local os = _G.os
|
||||
local settings = _G.settings
|
||||
local term = _G.term
|
||||
local textutils = _G.textutils
|
||||
|
||||
local function loadBootOptions()
|
||||
if not fs.exists('.startup.boot') then
|
||||
local f = fs.open('.startup.boot', 'w')
|
||||
f.write(textutils.serialize({
|
||||
delay = 1.5,
|
||||
preload = { },
|
||||
menu = {
|
||||
{ prompt = os.version() },
|
||||
{ prompt = 'Opus' , args = { '/sys/boot/opus.boot' } },
|
||||
{ prompt = 'Opus Shell' , args = { '/sys/boot/opus.boot', 'sys/apps/shell.lua' } },
|
||||
{ prompt = 'Opus Kiosk' , args = { '/sys/boot/kiosk.boot' } },
|
||||
},
|
||||
}))
|
||||
f.close()
|
||||
end
|
||||
|
||||
local f = fs.open('.startup.boot', 'r')
|
||||
local options = textutils.unserialize(f.readAll())
|
||||
f.close()
|
||||
|
||||
return options
|
||||
end
|
||||
|
||||
local bootOptions = loadBootOptions()
|
||||
|
||||
local bootOption = 2
|
||||
if settings then
|
||||
settings.load('.settings')
|
||||
bootOption = tonumber(settings.get('opus.boot_option')) or bootOption
|
||||
end
|
||||
|
||||
local function startupMenu()
|
||||
local x, y = term.getSize()
|
||||
local align, selected = 0, bootOption
|
||||
|
||||
local function redraw()
|
||||
local title = "Boot Options:"
|
||||
term.clear()
|
||||
term.setTextColor(colors.white)
|
||||
term.setCursorPos((x/2)-(#title/2), (y/2)-(#bootOptions.menu/2)-1)
|
||||
term.write(title)
|
||||
for i, item in pairs(bootOptions.menu) do
|
||||
local txt = i .. ". " .. item.prompt
|
||||
term.setCursorPos((x/2)-(align/2), (y/2)-(#bootOptions.menu/2)+i)
|
||||
term.write(txt)
|
||||
end
|
||||
end
|
||||
|
||||
for _, item in pairs(bootOptions.menu) do
|
||||
if #item.prompt > align then
|
||||
align = #item.prompt
|
||||
end
|
||||
end
|
||||
|
||||
redraw()
|
||||
while true do
|
||||
term.setCursorPos((x/2)-(align/2)-2, (y/2)-(#bootOptions.menu/2)+selected)
|
||||
term.setTextColor(term.isColor() and colors.yellow or colors.lightGray)
|
||||
|
||||
term.write(">")
|
||||
local event, key = os.pullEvent()
|
||||
if event == "mouse_scroll" then
|
||||
key = key == 1 and keys.down or keys.up
|
||||
elseif event == 'key_up' then
|
||||
key = nil -- only process key events
|
||||
end
|
||||
|
||||
if key == keys.enter or key == keys.right then
|
||||
return selected
|
||||
elseif key == keys.down then
|
||||
if selected == #bootOptions.menu then
|
||||
selected = 0
|
||||
end
|
||||
selected = selected + 1
|
||||
elseif key == keys.up then
|
||||
if selected == 1 then
|
||||
selected = #bootOptions.menu + 1
|
||||
end
|
||||
selected = selected - 1
|
||||
elseif event == 'char' then
|
||||
key = tonumber(key) or 0
|
||||
if bootOptions.menu[key] then
|
||||
return key
|
||||
end
|
||||
end
|
||||
|
||||
local cx, cy = term.getCursorPos()
|
||||
term.setCursorPos(cx-1, cy)
|
||||
term.write(" ")
|
||||
end
|
||||
end
|
||||
|
||||
local function splash()
|
||||
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
|
||||
|
||||
local str = 'Press any key for menu'
|
||||
term.setCursorPos((w - #str) / 2, h)
|
||||
term.write(str)
|
||||
end
|
||||
|
||||
for _, v in pairs(bootOptions.preload) do
|
||||
os.run(_ENV, v)
|
||||
end
|
||||
|
||||
term.clear()
|
||||
splash()
|
||||
|
||||
local timerId = os.startTimer(bootOptions.delay)
|
||||
while true do
|
||||
local e, id = os.pullEvent()
|
||||
if e == 'timer' and id == timerId then
|
||||
break
|
||||
end
|
||||
if e == 'char' or e == 'key' 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.menu[bootOption].args then
|
||||
os.run(_ENV, table.unpack(bootOptions.menu[bootOption].args))
|
||||
else
|
||||
print(bootOptions.menu[bootOption].prompt)
|
||||
end
|
||||
|
@ -1,150 +0,0 @@
|
||||
-- https://github.com/PixelToast/ComputerCraft/blob/master/apis/enc
|
||||
|
||||
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
|
||||
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
|
||||
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
|
||||
end
|
||||
|
||||
local function zfill(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)
|
||||
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
|
||||
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
|
||||
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)
|
||||
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
|
||||
end
|
||||
|
||||
return Crypto
|
@ -1,151 +0,0 @@
|
||||
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,61 +0,0 @@
|
||||
--[[
|
||||
Mount a readonly file system from another computer across rednet. The
|
||||
target computer must be running OpusOS or redserver. Dissimlar to samba
|
||||
in that a snapshot of the target is taken upon mounting - making this
|
||||
faster.
|
||||
|
||||
Useful for mounting a non-changing directory tree.
|
||||
|
||||
Syntax:
|
||||
rttp://<id>/directory/subdir
|
||||
|
||||
Examples:
|
||||
rttp://12/usr/etc
|
||||
rttp://8/usr
|
||||
]]--
|
||||
|
||||
local rttp = require('rttp')
|
||||
|
||||
local fs = _G.fs
|
||||
|
||||
local redfs = { }
|
||||
|
||||
local function getListing(uri)
|
||||
local success, response = rttp.get(uri .. '?recursive=true')
|
||||
|
||||
if not success then
|
||||
error(response)
|
||||
end
|
||||
|
||||
if response.statusCode ~= 200 then
|
||||
error('Received response ' .. response.statusCode)
|
||||
end
|
||||
|
||||
local list = { }
|
||||
for _,v in pairs(response.data) do
|
||||
if not v.isDir then
|
||||
list[v.path] = {
|
||||
url = uri .. '/' .. v.path,
|
||||
size = v.size,
|
||||
}
|
||||
end
|
||||
end
|
||||
|
||||
return list
|
||||
end
|
||||
|
||||
function redfs.mount(dir, uri)
|
||||
if not uri then
|
||||
error('redfs syntax: uri')
|
||||
end
|
||||
|
||||
local list = getListing(uri)
|
||||
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 redfs
|
@ -1,178 +0,0 @@
|
||||
local PASTEBIN_URL = 'http://pastebin.com/raw'
|
||||
local GIT_URL = 'https://raw.githubusercontent.com'
|
||||
local DEFAULT_PATH = 'sys/apis'
|
||||
local DEFAULT_BRANCH = _ENV.OPUS_BRANCH or _G.OPUS_BRANCH or 'master'
|
||||
local DEFAULT_UPATH = GIT_URL .. '/kepler155c/opus/' .. DEFAULT_BRANCH .. '/sys/apis'
|
||||
|
||||
local fs = _G.fs
|
||||
local http = _G.http
|
||||
local os = _G.os
|
||||
|
||||
if not http._patched then
|
||||
-- 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
|
||||
|
||||
-- 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
|
||||
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
|
||||
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 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
|
||||
|
||||
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
|
||||
|
||||
-- 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
|
||||
|
||||
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
|
||||
|
||||
-- place package and require function into env
|
||||
env.package = {
|
||||
path = env.LUA_PATH or _G.LUA_PATH or DEFAULT_PATH,
|
||||
upath = env.LUA_UPATH or _G.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
|
||||
|
||||
return env.require -- backwards compatible
|
||||
end
|
||||
|
||||
return function(env)
|
||||
env = env or getfenv(2)
|
||||
--setfenv(requireWrapper, env)
|
||||
return requireWrapper(env)
|
||||
end
|
@ -1,220 +0,0 @@
|
||||
-- credit ElvishJerricco
|
||||
-- http://pastebin.com/raw.php?i=4nRg9CHU
|
||||
|
||||
local json = { }
|
||||
|
||||
------------------------------------------------------------------ utils
|
||||
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
|
||||
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
|
||||
end
|
||||
|
||||
------------------------------------------------------------------ encoding
|
||||
|
||||
local function encodeCommon(val, pretty, tabLevel, tTracking)
|
||||
local str = ""
|
||||
|
||||
-- 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
|
||||
|
||||
-- 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
|
||||
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, {})
|
||||
end
|
||||
|
||||
function json.encodePretty(val)
|
||||
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()
|
||||
end
|
||||
|
||||
------------------------------------------------------------------ decoding
|
||||
|
||||
local decodeControls = {}
|
||||
for k,v in pairs(controls) do
|
||||
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
|
||||
end
|
||||
|
||||
local function parseNull(str)
|
||||
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
|
||||
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")
|
||||
|
||||
if next == "\\" then
|
||||
local escape = str:sub(1,1)
|
||||
str = str:sub(2)
|
||||
|
||||
next = assert(decodeControls[next..escape], "Invalid escape character")
|
||||
end
|
||||
|
||||
s = s .. next
|
||||
end
|
||||
return s, removeWhite(str:sub(2))
|
||||
end
|
||||
|
||||
function json.parseArray(str)
|
||||
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
|
||||
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
|
||||
end
|
||||
|
||||
function json.parseMember(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))
|
||||
|
||||
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)
|
||||
end
|
||||
|
||||
function json.decodeFromFile(path)
|
||||
local file = assert(fs.open(path, "r"))
|
||||
local decoded = json.decode(file.readAll())
|
||||
file.close()
|
||||
return decoded
|
||||
end
|
||||
|
||||
return json
|
@ -1,175 +0,0 @@
|
||||
--- A light implementation of Binary heaps data structure.
|
||||
-- While running a search, some search algorithms (Astar, Dijkstra, Jump Point Search) have to maintains
|
||||
-- a list of nodes called __open list__. Retrieve from this list the lowest cost node can be quite slow,
|
||||
-- as it normally requires to skim through the full set of nodes stored in this list. This becomes a real
|
||||
-- problem especially when dozens of nodes are being processed (on large maps).
|
||||
--
|
||||
-- The current module implements a <a href="http://www.policyalmanac.org/games/binaryHeaps.htm">binary heap</a>
|
||||
-- data structure, from which the search algorithm will instantiate an open list, and cache the nodes being
|
||||
-- examined during a search. As such, retrieving the lower-cost node is faster and globally makes the search end
|
||||
-- up quickly.
|
||||
--
|
||||
-- This module is internally used by the library on purpose.
|
||||
-- It should normally not be used explicitely, yet it remains fully accessible.
|
||||
--
|
||||
|
||||
--[[
|
||||
Notes:
|
||||
This lighter implementation of binary heaps, based on :
|
||||
https://github.com/Yonaba/Binary-Heaps
|
||||
--]]
|
||||
|
||||
if (...) then
|
||||
|
||||
-- Dependency
|
||||
local Utils = require((...):gsub('%.bheap$','.utils'))
|
||||
|
||||
-- Local reference
|
||||
local floor = math.floor
|
||||
|
||||
-- Default comparison function
|
||||
local function f_min(a,b) return a < b end
|
||||
|
||||
-- Percolates up
|
||||
local function percolate_up(heap, index)
|
||||
if index == 1 then return end
|
||||
local pIndex
|
||||
if index <= 1 then return end
|
||||
if index%2 == 0 then
|
||||
pIndex = index/2
|
||||
else pIndex = (index-1)/2
|
||||
end
|
||||
if not heap._sort(heap._heap[pIndex], heap._heap[index]) then
|
||||
heap._heap[pIndex], heap._heap[index] =
|
||||
heap._heap[index], heap._heap[pIndex]
|
||||
percolate_up(heap, pIndex)
|
||||
end
|
||||
end
|
||||
|
||||
-- Percolates down
|
||||
local function percolate_down(heap,index)
|
||||
local lfIndex,rtIndex,minIndex
|
||||
lfIndex = 2*index
|
||||
rtIndex = lfIndex + 1
|
||||
if rtIndex > heap._size then
|
||||
if lfIndex > heap._size then return
|
||||
else minIndex = lfIndex end
|
||||
else
|
||||
if heap._sort(heap._heap[lfIndex],heap._heap[rtIndex]) then
|
||||
minIndex = lfIndex
|
||||
else
|
||||
minIndex = rtIndex
|
||||
end
|
||||
end
|
||||
if not heap._sort(heap._heap[index],heap._heap[minIndex]) then
|
||||
heap._heap[index],heap._heap[minIndex] = heap._heap[minIndex],heap._heap[index]
|
||||
percolate_down(heap,minIndex)
|
||||
end
|
||||
end
|
||||
|
||||
-- Produces a new heap
|
||||
local function newHeap(template,comp)
|
||||
return setmetatable({_heap = {},
|
||||
_sort = comp or f_min, _size = 0},
|
||||
template)
|
||||
end
|
||||
|
||||
|
||||
--- The `heap` class.<br/>
|
||||
-- This class is callable.
|
||||
-- _Therefore,_ <code>heap(...)</code> _is used to instantiate new heaps_.
|
||||
-- @type heap
|
||||
local heap = setmetatable({},
|
||||
{__call = function(self,...)
|
||||
return newHeap(self,...)
|
||||
end})
|
||||
heap.__index = heap
|
||||
|
||||
--- Checks if a `heap` is empty
|
||||
-- @class function
|
||||
-- @treturn bool __true__ of no item is queued in the heap, __false__ otherwise
|
||||
-- @usage
|
||||
-- if myHeap:empty() then
|
||||
-- print('Heap is empty!')
|
||||
-- end
|
||||
function heap:empty()
|
||||
return (self._size==0)
|
||||
end
|
||||
|
||||
--- Clears the `heap` (removes all items queued in the heap)
|
||||
-- @class function
|
||||
-- @treturn heap self (the calling `heap` itself, can be chained)
|
||||
-- @usage myHeap:clear()
|
||||
function heap:clear()
|
||||
self._heap = {}
|
||||
self._size = 0
|
||||
self._sort = self._sort or f_min
|
||||
return self
|
||||
end
|
||||
|
||||
--- Adds a new item in the `heap`
|
||||
-- @class function
|
||||
-- @tparam value item a new value to be queued in the heap
|
||||
-- @treturn heap self (the calling `heap` itself, can be chained)
|
||||
-- @usage
|
||||
-- myHeap:push(1)
|
||||
-- -- or, with chaining
|
||||
-- myHeap:push(1):push(2):push(4)
|
||||
function heap:push(item)
|
||||
if item then
|
||||
self._size = self._size + 1
|
||||
self._heap[self._size] = item
|
||||
percolate_up(self, self._size)
|
||||
end
|
||||
return self
|
||||
end
|
||||
|
||||
--- Pops from the `heap`.
|
||||
-- Removes and returns the lowest cost item (with respect to the comparison function being used) from the `heap`.
|
||||
-- @class function
|
||||
-- @treturn value a value previously pushed into the heap
|
||||
-- @usage
|
||||
-- while not myHeap:empty() do
|
||||
-- local lowestValue = myHeap:pop()
|
||||
-- ...
|
||||
-- end
|
||||
function heap:pop()
|
||||
local root
|
||||
if self._size > 0 then
|
||||
root = self._heap[1]
|
||||
self._heap[1] = self._heap[self._size]
|
||||
self._heap[self._size] = nil
|
||||
self._size = self._size-1
|
||||
if self._size>1 then
|
||||
percolate_down(self, 1)
|
||||
end
|
||||
end
|
||||
return root
|
||||
end
|
||||
|
||||
--- Restores the `heap` property.
|
||||
-- Reorders the `heap` with respect to the comparison function being used.
|
||||
-- When given argument __item__ (a value existing in the `heap`), will sort from that very item in the `heap`.
|
||||
-- Otherwise, the whole `heap` will be cheacked.
|
||||
-- @class function
|
||||
-- @tparam[opt] value item the modified value
|
||||
-- @treturn heap self (the calling `heap` itself, can be chained)
|
||||
-- @usage myHeap:heapify()
|
||||
function heap:heapify(item)
|
||||
if self._size == 0 then return end
|
||||
if item then
|
||||
local i = Utils.indexOf(self._heap,item)
|
||||
if i then
|
||||
percolate_down(self, i)
|
||||
percolate_up(self, i)
|
||||
end
|
||||
return
|
||||
end
|
||||
for i = floor(self._size/2),1,-1 do
|
||||
percolate_down(self,i)
|
||||
end
|
||||
return self
|
||||
end
|
||||
|
||||
return heap
|
||||
end
|
@ -1,41 +0,0 @@
|
||||
--- The Node class.
|
||||
-- The `node` represents a cell (or a tile) on a collision map. Basically, for each single cell (tile)
|
||||
-- in the collision map passed-in upon initialization, a `node` object will be generated
|
||||
-- and then cached within the `grid`.
|
||||
--
|
||||
-- In the following implementation, nodes can be compared using the `<` operator. The comparison is
|
||||
-- made with regards of their `f` cost. From a given node being examined, the `pathfinder` will expand the search
|
||||
-- to the next neighbouring node having the lowest `f` cost. See `core.bheap` for more details.
|
||||
--
|
||||
if (...) then
|
||||
|
||||
local Node = {}
|
||||
Node.__index = Node
|
||||
|
||||
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
|
||||
|
||||
function Node:getX() return self.x end
|
||||
function Node:getY() return self.y end
|
||||
function Node:getZ() return self.z end
|
||||
|
||||
--- Clears temporary cached attributes of a `node`.
|
||||
-- Deletes the attributes cached within a given node after a pathfinding call.
|
||||
-- This function is internally used by the search algorithms, so you should not use it explicitely.
|
||||
function Node:reset()
|
||||
self._g, self._h, self._f = nil, nil, nil
|
||||
self._opened, self._closed, self._parent = nil, nil, nil
|
||||
return self
|
||||
end
|
||||
|
||||
return setmetatable(Node,
|
||||
{__call = function(_,...)
|
||||
return Node:new(...)
|
||||
end}
|
||||
)
|
||||
end
|
@ -1,67 +0,0 @@
|
||||
--- The Path class.
|
||||
-- The `path` class is a structure which represents a path (ordered set of nodes) from a start location to a goal.
|
||||
-- An instance from this class would be a result of a request addressed to `Pathfinder:getPath`.
|
||||
--
|
||||
-- This module is internally used by the library on purpose.
|
||||
-- It should normally not be used explicitely, yet it remains fully accessible.
|
||||
--
|
||||
|
||||
if (...) then
|
||||
|
||||
local t_remove = table.remove
|
||||
|
||||
local Path = {}
|
||||
Path.__index = Path
|
||||
|
||||
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}
|
||||
-- @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
|
||||
|
||||
--- `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
|
||||
})
|
||||
end
|
@ -1,57 +0,0 @@
|
||||
-- Various utilities for Jumper top-level modules
|
||||
|
||||
if (...) then
|
||||
|
||||
-- Dependencies
|
||||
local _PATH = (...):gsub('%.utils$','')
|
||||
local Path = require (_PATH .. '.path')
|
||||
|
||||
-- Local references
|
||||
local pairs = pairs
|
||||
local t_insert = table.insert
|
||||
|
||||
-- Raw array items count
|
||||
local function arraySize(t)
|
||||
local count = 0
|
||||
for _ in pairs(t) do
|
||||
count = count+1
|
||||
end
|
||||
return count
|
||||
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
|
||||
|
||||
-- Lookup for value in a table
|
||||
local indexOf = function(t,v)
|
||||
for i = 1,#t do
|
||||
if t[i] == v then return i end
|
||||
end
|
||||
return nil
|
||||
end
|
||||
|
||||
-- Is i out of range
|
||||
local function outOfRange(i,low,up)
|
||||
return (i< low or i > up)
|
||||
end
|
||||
|
||||
return {
|
||||
arraySize = arraySize,
|
||||
indexOf = indexOf,
|
||||
outOfRange = outOfRange,
|
||||
traceBackPath = traceBackPath
|
||||
}
|
||||
|
||||
end
|
@ -1,101 +0,0 @@
|
||||
--- The Grid class.
|
||||
-- Implementation of the `grid` class.
|
||||
-- The `grid` is a implicit graph which represents the 2D
|
||||
-- world map layout on which the `pathfinder` object will run.
|
||||
-- During a search, the `pathfinder` object needs to save some critical values.
|
||||
-- These values are cached within each `node`
|
||||
-- object, and the whole set of nodes are tight inside the `grid` object itself.
|
||||
|
||||
if (...) then
|
||||
|
||||
-- Dependencies
|
||||
local _PATH = (...):gsub('%.grid$','')
|
||||
|
||||
-- Local references
|
||||
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]]
|
||||
}
|
||||
|
||||
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: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:getHeight()
|
||||
return self._height
|
||||
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)
|
||||
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
|
||||
|
||||
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
|
||||
|
||||
-- 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
|
||||
})
|
||||
|
||||
end
|
@ -1,104 +0,0 @@
|
||||
--[[
|
||||
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.
|
||||
--]]
|
||||
|
||||
--[[
|
||||
This work is under MIT-LICENSE
|
||||
Copyright (c) 2012-2013 Roland Yonaba.
|
||||
|
||||
-- https://opensource.org/licenses/MIT
|
||||
|
||||
--]]
|
||||
|
||||
local _VERSION = ""
|
||||
local _RELEASEDATE = ""
|
||||
|
||||
if (...) then
|
||||
|
||||
-- Dependencies
|
||||
local _PATH = (...):gsub('%.pathfinder$','')
|
||||
local Utils = require (_PATH .. '.core.utils')
|
||||
|
||||
-- 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'),
|
||||
}
|
||||
|
||||
-- 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
|
||||
|
||||
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: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 = {}
|
||||
return self
|
||||
end
|
||||
|
||||
-- Returns Pathfinder class
|
||||
Pathfinder._VERSION = _VERSION
|
||||
Pathfinder._RELEASEDATE = _RELEASEDATE
|
||||
return setmetatable(Pathfinder,{
|
||||
__call = function(self,...)
|
||||
return self:new(...)
|
||||
end
|
||||
})
|
||||
end
|
@ -1,77 +0,0 @@
|
||||
-- Astar algorithm
|
||||
-- This actual implementation of A-star is based on
|
||||
-- [Nash A. & al. pseudocode](http://aigamedev.com/open/tutorials/theta-star-any-angle-paths/)
|
||||
|
||||
if (...) then
|
||||
|
||||
-- Internalization
|
||||
local huge = math.huge
|
||||
|
||||
-- Dependancies
|
||||
local _PATH = (...):match('(.+)%.search.astar$')
|
||||
local Heap = require (_PATH.. '.core.bheap')
|
||||
|
||||
-- Updates G-cost
|
||||
local function computeCost(node, neighbour, heuristic)
|
||||
local mCost, heading = heuristic(neighbour, node) -- Heuristics.EUCLIDIAN(neighbour, node)
|
||||
|
||||
if node._g + mCost < neighbour._g then
|
||||
neighbour._parent = node
|
||||
neighbour._g = node._g + mCost
|
||||
neighbour.heading = heading
|
||||
end
|
||||
end
|
||||
|
||||
-- Updates vertex node-neighbour
|
||||
local function updateVertex(openList, node, neighbour, endNode, heuristic)
|
||||
local oldG = neighbour._g
|
||||
computeCost(node, neighbour, heuristic)
|
||||
if neighbour._g < oldG then
|
||||
if neighbour._opened then neighbour._opened = false end
|
||||
neighbour._h = heuristic(endNode, neighbour)
|
||||
neighbour._f = neighbour._g + neighbour._h
|
||||
openList:push(neighbour)
|
||||
neighbour._opened = true
|
||||
end
|
||||
end
|
||||
|
||||
-- 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)
|
||||
startNode._f = startNode._g + startNode._h
|
||||
openList:push(startNode)
|
||||
toClear[startNode] = true
|
||||
startNode._opened = true
|
||||
|
||||
while not openList:empty() do
|
||||
local node = openList:pop()
|
||||
node._closed = true
|
||||
if node == endNode then return node end
|
||||
local neighbours = finder._grid:getNeighbours(node)
|
||||
for i = 1,#neighbours do
|
||||
local neighbour = neighbours[i]
|
||||
if not neighbour._closed then
|
||||
toClear[neighbour] = true
|
||||
if not neighbour._opened then
|
||||
neighbour._g = huge
|
||||
neighbour._parent = nil
|
||||
end
|
||||
updateVertex(openList, node, neighbour, endNode, finder._heuristic)
|
||||
end
|
||||
end
|
||||
|
||||
--[[
|
||||
printf('x:%d y:%d z:%d g:%d', node.x, node.y, node.z, node._g)
|
||||
for i = 1,#neighbours do
|
||||
local n = neighbours[i]
|
||||
printf('x:%d y:%d z:%d f:%f g:%f h:%d', n.x, n.y, n.z, n._f, n._g, n.heading or -1)
|
||||
end
|
||||
--]]
|
||||
|
||||
end
|
||||
return nil
|
||||
end
|
||||
end
|
@ -1,133 +0,0 @@
|
||||
local Logger = {
|
||||
fn = function() end,
|
||||
filteredEvents = { },
|
||||
}
|
||||
|
||||
function Logger.setLogger(fn)
|
||||
Logger.fn = fn
|
||||
end
|
||||
|
||||
function Logger.disable()
|
||||
Logger.setLogger(function() end)
|
||||
end
|
||||
|
||||
function Logger.setDaemonLogging()
|
||||
Logger.setLogger(function (text)
|
||||
os.queueEvent('log', { text = text })
|
||||
end)
|
||||
end
|
||||
|
||||
function Logger.setMonitorLogging()
|
||||
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
|
||||
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)
|
||||
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
|
||||
end
|
||||
|
||||
function Logger.setFileLogging(fileName)
|
||||
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)
|
||||
end
|
||||
|
||||
function Logger.log(category, value, ...)
|
||||
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)
|
||||
end
|
||||
|
||||
function Logger.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
|
||||
end
|
||||
|
||||
function Logger.filter( ...)
|
||||
local events = { ... }
|
||||
for _,event in pairs(events) do
|
||||
Logger.filteredEvents[event] = true
|
||||
end
|
||||
end
|
||||
|
||||
return Logger
|
@ -1,60 +0,0 @@
|
||||
local Util = require('util')
|
||||
|
||||
local fs = _G.fs
|
||||
local textutils = _G.textutils
|
||||
|
||||
local PACKAGE_DIR = 'packages'
|
||||
|
||||
local Packages = { }
|
||||
|
||||
function Packages:installed()
|
||||
self.cache = { }
|
||||
|
||||
if fs.exists(PACKAGE_DIR) then
|
||||
for _, dir in pairs(fs.list(PACKAGE_DIR)) do
|
||||
local path = fs.combine(fs.combine(PACKAGE_DIR, dir), '.package')
|
||||
self.cache[dir] = Util.readTable(path)
|
||||
end
|
||||
end
|
||||
|
||||
return self.cache
|
||||
end
|
||||
|
||||
function Packages:list()
|
||||
if self.packageList then
|
||||
return self.packageList
|
||||
end
|
||||
self.packageList = Util.readTable('usr/config/packages') or { }
|
||||
|
||||
return self.packageList
|
||||
end
|
||||
|
||||
function Packages:isInstalled(package)
|
||||
return self:installed()[package]
|
||||
end
|
||||
|
||||
function Packages:getManifest(package)
|
||||
local fname = 'packages/' .. package .. '/.package'
|
||||
if fs.exists(fname) then
|
||||
local c = Util.readTable(fname)
|
||||
if c then
|
||||
c.repository = c.repository:gsub('{{OPUS_BRANCH}}', _G.OPUS_BRANCH)
|
||||
return c
|
||||
end
|
||||
end
|
||||
local list = self:list()
|
||||
local url = list and list[package]
|
||||
|
||||
if url then
|
||||
local c = Util.httpGet(url)
|
||||
if c then
|
||||
c = textutils.unserialize(c)
|
||||
if c then
|
||||
c.repository = c.repository:gsub('{{OPUS_BRANCH}}', _G.OPUS_BRANCH)
|
||||
return c
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
return Packages
|
@ -1,242 +0,0 @@
|
||||
local Event = require('event')
|
||||
local Socket = require('socket')
|
||||
local Util = require('util')
|
||||
|
||||
local os = _G.os
|
||||
|
||||
local Peripheral = Util.shallowCopy(_G.peripheral)
|
||||
|
||||
function Peripheral.getList()
|
||||
if _G.device then
|
||||
return _G.device
|
||||
end
|
||||
|
||||
local deviceList = { }
|
||||
for _,side in pairs(Peripheral.getNames()) do
|
||||
Peripheral.addDevice(deviceList, side)
|
||||
end
|
||||
|
||||
return deviceList
|
||||
end
|
||||
|
||||
function Peripheral.addDevice(deviceList, side)
|
||||
local name = side
|
||||
local ptype = Peripheral.getType(side)
|
||||
|
||||
if not ptype then
|
||||
return
|
||||
end
|
||||
|
||||
if ptype == 'modem' then
|
||||
if not 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
|
||||
}
|
||||
|
||||
if sides[name] then
|
||||
local i = 1
|
||||
local uniqueName = ptype
|
||||
while deviceList[uniqueName] do
|
||||
uniqueName = ptype .. '_' .. i
|
||||
i = i + 1
|
||||
end
|
||||
name = uniqueName
|
||||
end
|
||||
|
||||
-- this can randomly fail
|
||||
if not deviceList[name] then
|
||||
pcall(function()
|
||||
deviceList[name] = Peripheral.wrap(side)
|
||||
end)
|
||||
|
||||
if deviceList[name] then
|
||||
Util.merge(deviceList[name], {
|
||||
name = name,
|
||||
type = ptype,
|
||||
side = side,
|
||||
})
|
||||
end
|
||||
end
|
||||
|
||||
return deviceList[name]
|
||||
end
|
||||
|
||||
function Peripheral.getBySide(side)
|
||||
return Util.find(Peripheral.getList(), 'side', side)
|
||||
end
|
||||
|
||||
function Peripheral.getByType(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
|
||||
end
|
||||
|
||||
-- match any of the passed arguments
|
||||
function Peripheral.get(args)
|
||||
|
||||
if type(args) == 'string' then
|
||||
args = { type = args }
|
||||
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.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
|
||||
end
|
||||
|
||||
local function getProxy(pi)
|
||||
local socket, msg = Socket.connect(pi.host, 189)
|
||||
|
||||
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)
|
||||
|
||||
if not proxy then
|
||||
error("Timed out attaching peripheral: " .. pi.uri)
|
||||
end
|
||||
|
||||
if type(proxy) == 'string' then
|
||||
error(proxy)
|
||||
end
|
||||
|
||||
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
|
||||
|
||||
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
|
||||
|
||||
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
|
||||
|
||||
return proxy
|
||||
end
|
||||
|
||||
--[[
|
||||
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' }
|
||||
]]--
|
||||
local function parse(uri)
|
||||
local pi = Util.split(uri:gsub('^%d*://', ''), '(.-)/')
|
||||
|
||||
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'
|
||||
}
|
||||
end
|
||||
|
||||
function Peripheral.lookup(uri)
|
||||
local pi = parse(uri)
|
||||
|
||||
if pi.host and _G.device.wireless_modem then
|
||||
return getProxy(pi)
|
||||
end
|
||||
|
||||
return Peripheral.get(pi)
|
||||
end
|
||||
|
||||
return Peripheral
|
@ -1,95 +0,0 @@
|
||||
local device = _G.device
|
||||
local os = _G.os
|
||||
|
||||
local rttp = { }
|
||||
local computerId = os.getComputerID()
|
||||
|
||||
local function parse(url, default)
|
||||
-- initialize default parameters
|
||||
local parsed = {}
|
||||
local authority
|
||||
|
||||
for i,v in pairs(default or parsed) do parsed[i] = v end
|
||||
-- remove whitespace
|
||||
-- url = string.gsub(url, "%s", "")
|
||||
-- Decode unreserved characters
|
||||
url = string.gsub(url, "%%(%x%x)", function(hex)
|
||||
local char = string.char(tonumber(hex, 16))
|
||||
if string.match(char, "[a-zA-Z0-9._~-]") then
|
||||
return char
|
||||
end
|
||||
-- Hex encodings that are not unreserved must be preserved.
|
||||
return nil
|
||||
end)
|
||||
-- get fragment
|
||||
url = string.gsub(url, "#(.*)$", function(f)
|
||||
parsed.fragment = f
|
||||
return ""
|
||||
end)
|
||||
-- get scheme. Lower-case according to RFC 3986 section 3.1.
|
||||
url = string.gsub(url, "^(%w[%w.+-]*):",
|
||||
function(s) parsed.scheme = string.lower(s); return "" end)
|
||||
-- get authority
|
||||
url = string.gsub(url, "^//([^/]*)", function(n)
|
||||
authority = n
|
||||
return ""
|
||||
end)
|
||||
-- get query stringing
|
||||
url = string.gsub(url, "%?(.*)", function(q)
|
||||
parsed.query = q
|
||||
return ""
|
||||
end)
|
||||
-- get params
|
||||
url = string.gsub(url, "%;(.*)", function(p)
|
||||
parsed.params = p
|
||||
return ""
|
||||
end)
|
||||
|
||||
-- path is whatever was left
|
||||
parsed.path = url
|
||||
|
||||
-- Represents host:port, port = nil if not used.
|
||||
if authority then
|
||||
authority = string.gsub(authority, ":(%d+)$",
|
||||
function(p) parsed.port = tonumber(p); return "" end)
|
||||
if authority ~= "" then
|
||||
parsed.host = authority
|
||||
end
|
||||
end
|
||||
return parsed
|
||||
end
|
||||
|
||||
function rttp.get(url)
|
||||
local modem = device.wireless_modem or error('Modem not found')
|
||||
local parsed = parse(url, { port = 80 })
|
||||
|
||||
parsed.host = tonumber(parsed.host) or error('Invalid url')
|
||||
|
||||
for i = 16384, 32767 do
|
||||
if not modem.isOpen(i) then
|
||||
modem.open(i)
|
||||
local path = parsed.query and parsed.path .. '?' .. parsed.query or parsed.path
|
||||
|
||||
modem.transmit(parsed.port, parsed.host, {
|
||||
method = 'GET',
|
||||
replyAddress = computerId,
|
||||
replyPort = i,
|
||||
path = path,
|
||||
})
|
||||
local timerId = os.startTimer(3)
|
||||
repeat
|
||||
local event, id, dport, dhost, response = os.pullEvent()
|
||||
if event == 'modem_message' and
|
||||
dport == i and
|
||||
dhost == computerId and
|
||||
type(response) == 'table' then
|
||||
modem.close(i)
|
||||
return true, response
|
||||
end
|
||||
until event == 'timer' and id == timerId
|
||||
return false, 'timeout'
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
return rttp
|
@ -1,60 +0,0 @@
|
||||
local Config = require('config')
|
||||
|
||||
local config = { }
|
||||
|
||||
local Security = { }
|
||||
|
||||
function Security.verifyPassword(password)
|
||||
Config.load('os', config)
|
||||
return config.password and password == config.password
|
||||
end
|
||||
|
||||
function Security.hasPassword()
|
||||
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
|
||||
end
|
||||
|
||||
function Security.getPublicKey()
|
||||
|
||||
local exchange = {
|
||||
base = 11,
|
||||
primeMod = 625210769
|
||||
}
|
||||
|
||||
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
|
||||
|
||||
return remainder
|
||||
end
|
||||
|
||||
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)
|
||||
end
|
||||
|
||||
function Security.getPassword()
|
||||
Config.load('os', config)
|
||||
return config.password
|
||||
end
|
||||
|
||||
return Security
|
@ -1,280 +0,0 @@
|
||||
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
|
||||
|
||||
Copyright (c) 2013 Enrique Garcia Cota + Eike Decker + Jeffrey Friedl
|
||||
|
||||
https://opensource.org/licenses/MIT
|
||||
]]
|
||||
}
|
||||
|
||||
-----------------------------------------------------------------------------------
|
||||
|
||||
-- loading this file (takes a while but grants a boost of factor 13)
|
||||
local PRELOAD_CACHE = false
|
||||
|
||||
local BLOCK_SIZE = 64 -- 512 bits
|
||||
|
||||
-- local storing of global functions (minor speedup)
|
||||
local floor,modf = math.floor,math.modf
|
||||
local char,format,rep = string.char,string.format,string.rep
|
||||
|
||||
-- merge 4 bytes to an 32 bit word
|
||||
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
|
||||
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))
|
||||
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
|
||||
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)
|
||||
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)
|
||||
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)
|
||||
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)
|
||||
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)
|
||||
end)
|
||||
|
||||
-- bitwise complement for one 8bit number
|
||||
local function bnot(x)
|
||||
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
|
||||
end
|
||||
|
||||
-- create functions for and, xor and or, all for 2 32bit numbers
|
||||
local w32_and = w32_comb(band)
|
||||
local w32_xor = w32_comb(bxor)
|
||||
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)
|
||||
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))
|
||||
)
|
||||
end
|
||||
|
||||
-- binary complement for 32bit numbers
|
||||
local function w32_not(a)
|
||||
return 4294967295-(a % 4294967296)
|
||||
end
|
||||
|
||||
-- adding 2 32bit numbers, cutting off the remainder on 33th bit
|
||||
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
|
||||
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)
|
||||
end
|
||||
|
||||
-- building the lookuptables ahead of time (instead of littering the source code
|
||||
-- with precalculated values)
|
||||
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))
|
||||
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 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 ""
|
||||
|
||||
-- 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
|
||||
|
||||
msg = msg .. first_append .. second_append .. L64
|
||||
|
||||
assert(#msg % 64 == 0)
|
||||
|
||||
local chunks = #msg / 64
|
||||
|
||||
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
|
||||
|
||||
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
|
||||
|
||||
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
|
||||
|
||||
-- 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))
|
||||
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")
|
||||
|
||||
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)
|
||||
|
||||
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))
|
||||
end
|
||||
|
||||
setmetatable(sha1, {__call = function(_,msg) return sha1.sha1(msg) end })
|
||||
|
||||
return sha1
|
@ -1,222 +0,0 @@
|
||||
local Crypto = require('crypto')
|
||||
local Logger = require('logger')
|
||||
local Security = require('security')
|
||||
local Util = require('util')
|
||||
|
||||
local device = _G.device
|
||||
local os = _G.os
|
||||
|
||||
local socketClass = { }
|
||||
|
||||
function socketClass:read(timeout)
|
||||
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
|
||||
|
||||
local timerId = os.startTimer(timeout or 5)
|
||||
|
||||
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
|
||||
|
||||
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
|
||||
end
|
||||
|
||||
function socketClass:ping()
|
||||
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)
|
||||
end
|
||||
|
||||
local Socket = { }
|
||||
|
||||
local function loopback(port, sport, msg)
|
||||
os.queueEvent('modem_message', 'loopback', port, sport, msg, 0)
|
||||
end
|
||||
|
||||
local function newSocket(isLoopback)
|
||||
for _ = 16384, 32767 do
|
||||
local i = math.random(16384, 32767)
|
||||
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)
|
||||
|
||||
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
|
||||
|
||||
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,
|
||||
})
|
||||
|
||||
local timerId = os.startTimer(3)
|
||||
repeat
|
||||
local e, id, sport, dport, msg = os.pullEvent()
|
||||
if e == 'modem_message' and
|
||||
sport == socket.sport and
|
||||
type(msg) == 'table' and
|
||||
msg.dhost == socket.shost then
|
||||
|
||||
os.cancelTimer(timerId)
|
||||
|
||||
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)
|
||||
|
||||
_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
|
||||
|
||||
socket:close()
|
||||
|
||||
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 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]
|
||||
|
||||
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) < 24
|
||||
end
|
||||
end
|
||||
|
||||
function Socket.server(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')
|
||||
|
||||
if sport == port and
|
||||
msg and
|
||||
type(msg) == 'table' 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
|
||||
|
||||
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
|
||||
|
||||
socket.transmit(socket.dport, socket.sport, {
|
||||
type = 'REJE',
|
||||
dhost = socket.dhost,
|
||||
shost = socket.shost,
|
||||
})
|
||||
socket:close()
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
return Socket
|
@ -1,61 +0,0 @@
|
||||
local Sync = {
|
||||
syncLocks = { }
|
||||
}
|
||||
|
||||
local os = _G.os
|
||||
|
||||
function Sync.sync(obj, fn)
|
||||
local key = tostring(obj)
|
||||
if Sync.syncLocks[key] then
|
||||
local cos = tostring(coroutine.running())
|
||||
table.insert(Sync.syncLocks[key], cos)
|
||||
repeat
|
||||
local _, co = os.pullEvent('sync_lock')
|
||||
until co == cos
|
||||
else
|
||||
Sync.syncLocks[key] = { }
|
||||
end
|
||||
local s, m = pcall(fn)
|
||||
local co = table.remove(Sync.syncLocks[key], 1)
|
||||
if co then
|
||||
os.queueEvent('sync_lock', co)
|
||||
else
|
||||
Sync.syncLocks[key] = nil
|
||||
end
|
||||
if not s then
|
||||
error(m)
|
||||
end
|
||||
end
|
||||
|
||||
function Sync.lock(obj)
|
||||
local key = tostring(obj)
|
||||
if Sync.syncLocks[key] then
|
||||
local cos = tostring(coroutine.running())
|
||||
table.insert(Sync.syncLocks[key], cos)
|
||||
repeat
|
||||
local _, co = os.pullEvent('sync_lock')
|
||||
until co == cos
|
||||
else
|
||||
Sync.syncLocks[key] = { }
|
||||
end
|
||||
end
|
||||
|
||||
function Sync.release(obj)
|
||||
local key = tostring(obj)
|
||||
if not Sync.syncLocks[key] then
|
||||
error('Sync.release: Lock was not obtained', 2)
|
||||
end
|
||||
local co = table.remove(Sync.syncLocks[key], 1)
|
||||
if co then
|
||||
os.queueEvent('sync_lock', co)
|
||||
else
|
||||
Sync.syncLocks[key] = nil
|
||||
end
|
||||
end
|
||||
|
||||
function Sync.isLocked(obj)
|
||||
local key = tostring(obj)
|
||||
return not not Sync.syncLocks[key]
|
||||
end
|
||||
|
||||
return Sync
|
@ -1,335 +0,0 @@
|
||||
local colors = _G.colors
|
||||
local term = _G.term
|
||||
local _gsub = string.gsub
|
||||
local _rep = string.rep
|
||||
local _sub = string.sub
|
||||
|
||||
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 palette = { }
|
||||
for n = 1, 16 do
|
||||
palette[2 ^ (n - 1)] = _sub("0123456789abcdef", n, n)
|
||||
end
|
||||
|
||||
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
|
||||
|
||||
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
|
||||
|
||||
function win.write(text)
|
||||
local _, h = win.getSize()
|
||||
|
||||
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()
|
||||
|
||||
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()
|
||||
|
||||
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
|
||||
|
||||
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
|
||||
|
||||
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()
|
||||
|
||||
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()
|
||||
|
||||
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
|
||||
|
||||
scrollTo(maxScroll, true)
|
||||
redraw()
|
||||
end
|
||||
|
||||
function win.scrollUp()
|
||||
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
|
||||
|
||||
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()
|
||||
end
|
||||
|
||||
-- get windows contents
|
||||
function Terminal.getContents(win, parent)
|
||||
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
|
||||
|
||||
win.setVisible(true)
|
||||
win.redraw()
|
||||
|
||||
parent.blit = oblit
|
||||
parent.setCursorPos = oscp
|
||||
|
||||
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 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 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
|
||||
end
|
||||
|
||||
function Terminal.getNullTerm(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
|
||||
|
||||
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
|
||||
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
|
||||
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 s == '' then
|
||||
return
|
||||
end
|
||||
return s
|
||||
end
|
||||
|
||||
return Terminal
|
@ -1,235 +0,0 @@
|
||||
local Grid = require('jumper.grid')
|
||||
local Pathfinder = require('jumper.pathfinder')
|
||||
local Point = require('point')
|
||||
local Util = require('util')
|
||||
|
||||
local turtle = _G.turtle
|
||||
|
||||
local function addBlock(grid, b, dim)
|
||||
if Point.inBox(b, dim) then
|
||||
local node = grid:getNodeAt(b.x, b.y, b.z)
|
||||
if node then
|
||||
node.walkable = 1
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
-- map shrinks/grows depending upon blocks encountered
|
||||
-- the map will encompass any blocks encountered, the turtle position, and the destination
|
||||
local function mapDimensions(dest, blocks, boundingBox, dests)
|
||||
local box = Point.makeBox(turtle.point, turtle.point)
|
||||
|
||||
Point.expandBox(box, dest)
|
||||
|
||||
for _,d in pairs(dests) do
|
||||
Point.expandBox(box, d)
|
||||
end
|
||||
|
||||
for _,b in pairs(blocks) do
|
||||
Point.expandBox(box, b)
|
||||
end
|
||||
|
||||
-- expand one block out in all directions
|
||||
if boundingBox then
|
||||
box.x = math.max(box.x - 1, boundingBox.x)
|
||||
box.z = math.max(box.z - 1, boundingBox.z)
|
||||
box.y = math.max(box.y - 1, boundingBox.y)
|
||||
box.ex = math.min(box.ex + 1, boundingBox.ex)
|
||||
box.ez = math.min(box.ez + 1, boundingBox.ez)
|
||||
box.ey = math.min(box.ey + 1, boundingBox.ey)
|
||||
else
|
||||
box.x = box.x - 1
|
||||
box.z = box.z - 1
|
||||
box.y = box.y - 1
|
||||
box.ex = box.ex + 1
|
||||
box.ez = box.ez + 1
|
||||
box.ey = box.ey + 1
|
||||
end
|
||||
|
||||
return box
|
||||
end
|
||||
|
||||
local function nodeToPoint(node)
|
||||
return { x = node.x, y = node.y, z = node.z, heading = node.heading }
|
||||
end
|
||||
|
||||
local function heuristic(n, node)
|
||||
return Point.calculateMoves(node, n)
|
||||
-- { x = node.x, y = node.y, z = node.z, heading = node.heading },
|
||||
-- { x = n.x, y = n.y, z = n.z, heading = n.heading })
|
||||
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
|
||||
end
|
||||
|
||||
-- turtle sensor returns blocks in relation to the world - not turtle orientation
|
||||
-- so cannot figure out block location unless we know our orientation in the world
|
||||
-- really kinda dumb since it returns the coordinates as offsets of our location
|
||||
-- instead of true coordinates
|
||||
local function addSensorBlocks(blocks, sblocks)
|
||||
for _,b in pairs(sblocks) do
|
||||
if b.type ~= 'AIR' then
|
||||
local pt = { x = turtle.point.x, y = turtle.point.y + b.y, z = turtle.point.z }
|
||||
pt.x = pt.x - b.x
|
||||
pt.z = pt.z - b.z -- this will only work if we were originally facing west
|
||||
local found = false
|
||||
for _,ob in pairs(blocks) do
|
||||
if pt.x == ob.x and pt.y == ob.y and pt.z == ob.z then
|
||||
found = true
|
||||
break
|
||||
end
|
||||
end
|
||||
if not found then
|
||||
table.insert(blocks, pt)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
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)
|
||||
else
|
||||
if grid:isWalkableAt(pt.x, pt.y, pt.z) then
|
||||
return pt
|
||||
end
|
||||
Util.removeByValue(pts, pt)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
local function pathTo(dest, options)
|
||||
local blocks = options.blocks or turtle.getState().blocks or { }
|
||||
local dests = options.dest or { dest } -- support alternative destinations
|
||||
local box = options.box or turtle.getState().box
|
||||
local lastDim
|
||||
local grid
|
||||
|
||||
if box then
|
||||
box = Point.normalizeBox(box)
|
||||
end
|
||||
|
||||
-- Creates a pathfinder object
|
||||
local finder = Pathfinder(heuristic)
|
||||
|
||||
while turtle.point.x ~= dest.x or turtle.point.z ~= dest.z or turtle.point.y ~= dest.y do
|
||||
|
||||
-- map expands as we encounter obstacles
|
||||
local dim = mapDimensions(dest, blocks, box, dests)
|
||||
|
||||
-- reuse map if possible
|
||||
if not lastDim or not dimsAreEqual(dim, lastDim) then
|
||||
-- Creates a grid object
|
||||
grid = Grid(dim)
|
||||
finder:setGrid(grid)
|
||||
|
||||
lastDim = dim
|
||||
end
|
||||
for _,b in pairs(blocks) do
|
||||
addBlock(grid, b, dim)
|
||||
end
|
||||
|
||||
dest = selectDestination(dests, box, grid)
|
||||
if not dest then
|
||||
return false, 'failed to reach destination'
|
||||
end
|
||||
if turtle.point.x == dest.x and turtle.point.z == dest.z and turtle.point.y == dest.y then
|
||||
break
|
||||
end
|
||||
|
||||
-- Define start and goal locations coordinates
|
||||
local startPt = turtle.point
|
||||
|
||||
-- Calculates the path, and its length
|
||||
local path = finder:getPath(
|
||||
startPt.x, startPt.y, startPt.z, turtle.point.heading,
|
||||
dest.x, dest.y, dest.z, dest.heading)
|
||||
|
||||
if not path then
|
||||
Util.removeByValue(dests, dest)
|
||||
else
|
||||
path:filter()
|
||||
|
||||
for node in path:nodes() do
|
||||
local pt = nodeToPoint(node)
|
||||
|
||||
if turtle.isAborted() then
|
||||
return false, 'aborted'
|
||||
end
|
||||
|
||||
--if this is the next to last node
|
||||
--and we are traveling up or down, then the
|
||||
--heading for this node should be the heading of the last node
|
||||
--or, maybe..
|
||||
--if last node is up or down (or either?)
|
||||
|
||||
-- use single turn method so the turtle doesn't turn around
|
||||
-- when encountering obstacles
|
||||
-- if not turtle.gotoSingleTurn(pt.x, pt.y, pt.z, pt.heading) then
|
||||
if not turtle.goto(pt) then
|
||||
local bpt = Point.nearestTo(turtle.point, pt)
|
||||
|
||||
table.insert(blocks, bpt)
|
||||
-- really need to check if the block we ran into was a turtle.
|
||||
-- if so, this block should be temporary (1-2 secs)
|
||||
|
||||
--local side = turtle.getSide(turtle.point, pt)
|
||||
--if turtle.isTurtleAtSide(side) then
|
||||
-- pt.timestamp = os.clock() + ?
|
||||
--end
|
||||
-- if dim has not changed, then need to update grid with
|
||||
-- walkable = nil (after time has elapsed)
|
||||
|
||||
--if device.turtlesensorenvironment then
|
||||
-- addSensorBlocks(blocks, device.turtlesensorenvironment.sonicScan())
|
||||
--end
|
||||
break
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
if dest.heading then
|
||||
turtle.setHeading(dest.heading)
|
||||
end
|
||||
return dest
|
||||
end
|
||||
|
||||
return {
|
||||
pathfind = function(dest, options)
|
||||
options = options or { }
|
||||
--if not options.blocks and turtle.gotoPoint(dest) then
|
||||
-- return dest
|
||||
--end
|
||||
return pathTo(dest, options)
|
||||
end,
|
||||
|
||||
-- set a global bounding box
|
||||
-- box can be overridden by passing box in pathfind options
|
||||
setBox = function(box)
|
||||
turtle.getState().box = box
|
||||
end,
|
||||
|
||||
setBlocks = function(blocks)
|
||||
turtle.getState().blocks = blocks
|
||||
end,
|
||||
|
||||
addBlock = function(block)
|
||||
if turtle.getState().blocks then
|
||||
table.insert(turtle.getState().blocks, block)
|
||||
end
|
||||
end,
|
||||
|
||||
reset = function()
|
||||
turtle.getState().box = nil
|
||||
turtle.getState().blocks = nil
|
||||
end,
|
||||
}
|
3478
sys/apis/ui.lua
3478
sys/apis/ui.lua
File diff suppressed because it is too large
Load Diff
@ -1,498 +0,0 @@
|
||||
local class = require('class')
|
||||
local Region = require('ui.region')
|
||||
local Util = require('util')
|
||||
|
||||
local _rep = string.rep
|
||||
local _sub = string.sub
|
||||
local _gsub = string.gsub
|
||||
local colors = _G.colors
|
||||
|
||||
local Canvas = class()
|
||||
|
||||
Canvas.colorPalette = { }
|
||||
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)
|
||||
end
|
||||
|
||||
function Canvas:init(args)
|
||||
self.x = 1
|
||||
self.y = 1
|
||||
self.layers = { }
|
||||
|
||||
Util.merge(self, args)
|
||||
|
||||
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
|
||||
|
||||
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
|
||||
end
|
||||
|
||||
function Canvas:resize(w, h)
|
||||
for i = self.height, h do
|
||||
self.lines[i] = { }
|
||||
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
|
||||
|
||||
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
|
||||
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
|
||||
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
|
||||
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
|
||||
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)
|
||||
end
|
||||
|
||||
function Canvas:writeBlit(x, y, text, bg, fg)
|
||||
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 fg 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 fg then
|
||||
fg = _sub(fg, 1, self.width - x + 1)
|
||||
end
|
||||
width = #text
|
||||
end
|
||||
|
||||
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 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
|
||||
end
|
||||
|
||||
function Canvas:reset()
|
||||
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
|
||||
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)
|
||||
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
|
||||
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()
|
||||
end
|
||||
|
||||
function Canvas:isDirty()
|
||||
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
|
||||
end
|
||||
|
||||
function Canvas:clean()
|
||||
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()
|
||||
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
|
||||
|
||||
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
|
||||
|
||||
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
|
||||
end
|
||||
|
||||
function Canvas.convertWindow(win, parent, wx, wy)
|
||||
local w, h = win.getSize()
|
||||
|
||||
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.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.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.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
|
||||
|
||||
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
|
@ -1,145 +0,0 @@
|
||||
local UI = require('ui')
|
||||
local Util = require('util')
|
||||
|
||||
local colors = _G.colors
|
||||
local fs = _G.fs
|
||||
|
||||
return function(args)
|
||||
|
||||
local columns = {
|
||||
{ heading = 'Name', key = 'name' },
|
||||
}
|
||||
|
||||
if UI.term.width > 28 then
|
||||
table.insert(columns,
|
||||
{ heading = 'Size', key = 'size', width = 5 }
|
||||
)
|
||||
end
|
||||
|
||||
args = args or { }
|
||||
|
||||
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',
|
||||
},
|
||||
}
|
||||
|
||||
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
|
||||
|
||||
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: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: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)
|
||||
|
||||
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 == 'cancel' then
|
||||
UI:setPreviousPage()
|
||||
self.fn()
|
||||
else
|
||||
return UI.Dialog.eventHandler(self, event)
|
||||
end
|
||||
return true
|
||||
end
|
||||
|
||||
return selectFile
|
||||
end
|
@ -1,196 +0,0 @@
|
||||
local class = require('class')
|
||||
local UI = require('ui')
|
||||
local Event = require('event')
|
||||
local Peripheral = require('peripheral')
|
||||
|
||||
--[[-- Glasses device --]]--
|
||||
local Glasses = class()
|
||||
function Glasses:init(args)
|
||||
|
||||
local defaults = {
|
||||
backgroundColor = colors.black,
|
||||
textColor = colors.white,
|
||||
textScale = .5,
|
||||
backgroundOpacity = .5,
|
||||
multiplier = 2.6665,
|
||||
-- multiplier = 2.333,
|
||||
}
|
||||
defaults.width, defaults.height = term.getSize()
|
||||
|
||||
UI:setProperties(defaults, args)
|
||||
UI:setProperties(self, defaults)
|
||||
|
||||
self.bridge = Peripheral.get({
|
||||
type = 'openperipheral_bridge',
|
||||
method = 'addBox',
|
||||
})
|
||||
self.bridge.clear()
|
||||
|
||||
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
|
||||
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 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
|
||||
|
||||
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
|
||||
|
||||
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 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
|
||||
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)
|
||||
-- self.t[i].text.setText('')
|
||||
end
|
||||
end
|
||||
|
||||
function Glasses:reset()
|
||||
self:clear()
|
||||
self.bridge.clear()
|
||||
self.bridge.sync()
|
||||
end
|
||||
|
||||
function Glasses:sync()
|
||||
self.bridge.sync()
|
||||
end
|
||||
|
||||
return Glasses
|
@ -1,90 +0,0 @@
|
||||
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()
|
||||
|
||||
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()
|
||||
|
||||
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)
|
||||
|
||||
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)
|
||||
|
||||
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,9 +1,9 @@
|
||||
_G.requireInjector(_ENV)
|
||||
|
||||
local Config = require('config')
|
||||
local Event = require('event')
|
||||
local UI = require('ui')
|
||||
local Util = require('util')
|
||||
local Alt = require('opus.alternate')
|
||||
local Config = require('opus.config')
|
||||
local Event = require('opus.event')
|
||||
local pastebin = require('opus.http.pastebin')
|
||||
local UI = require('opus.ui')
|
||||
local Util = require('opus.util')
|
||||
|
||||
local colors = _G.colors
|
||||
local fs = _G.fs
|
||||
@ -11,402 +11,540 @@ local multishell = _ENV.multishell
|
||||
local os = _G.os
|
||||
local shell = _ENV.shell
|
||||
|
||||
local FILE = 1
|
||||
|
||||
UI:configure('Files', ...)
|
||||
|
||||
local config = {
|
||||
showHidden = false,
|
||||
showDirSizes = false,
|
||||
local config = Config.load('Files', {
|
||||
showHidden = false,
|
||||
showDirSizes = false,
|
||||
})
|
||||
config.associations = config.associations or {
|
||||
nft = 'pain',
|
||||
txt = 'edit',
|
||||
}
|
||||
|
||||
Config.load('Files', config)
|
||||
|
||||
local copied = { }
|
||||
local marked = { }
|
||||
local directories = { }
|
||||
local cutMode = false
|
||||
|
||||
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
|
||||
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
|
||||
|
||||
local Browser = UI.Page {
|
||||
menuBar = UI.MenuBar {
|
||||
buttons = {
|
||||
{ text = '^-', event = 'updir' },
|
||||
{ text = 'File', dropdown = {
|
||||
{ text = 'Run', event = 'run' },
|
||||
{ text = 'Edit e', event = 'edit' },
|
||||
{ text = 'Shell s', event = 'shell' },
|
||||
UI.MenuBar.spacer,
|
||||
{ text = 'Quit q', event = 'quit' },
|
||||
} },
|
||||
{ text = 'Edit', dropdown = {
|
||||
{ text = 'Cut ^x', event = 'cut' },
|
||||
{ text = 'Copy ^c', event = 'copy' },
|
||||
{ text = 'Copy path ', event = 'copy_path' },
|
||||
{ text = 'Paste ^v', event = 'paste' },
|
||||
UI.MenuBar.spacer,
|
||||
{ text = 'Mark m', event = 'mark' },
|
||||
{ text = 'Unmark all u', event = 'unmark' },
|
||||
UI.MenuBar.spacer,
|
||||
{ text = 'Delete del', event = 'delete' },
|
||||
} },
|
||||
{ text = 'View', dropdown = {
|
||||
{ text = 'Refresh r', event = 'refresh' },
|
||||
{ text = 'Hidden ^h', event = 'toggle_hidden' },
|
||||
{ text = 'Dir Size ^s', event = 'toggle_dirSize' },
|
||||
} },
|
||||
},
|
||||
},
|
||||
grid = UI.ScrollingGrid {
|
||||
columns = {
|
||||
{ heading = 'Name', key = 'name' },
|
||||
{ key = 'flags', width = 2 },
|
||||
{ heading = 'Size', key = 'fsize', width = 5 },
|
||||
},
|
||||
sortColumn = 'name',
|
||||
y = 2, ey = -2,
|
||||
},
|
||||
statusBar = UI.StatusBar {
|
||||
columns = {
|
||||
{ key = 'status' },
|
||||
{ key = 'totalSize', width = 6 },
|
||||
},
|
||||
},
|
||||
accelerators = {
|
||||
q = 'quit',
|
||||
e = 'edit',
|
||||
s = 'shell',
|
||||
r = 'refresh',
|
||||
space = 'mark',
|
||||
backspace = 'updir',
|
||||
m = 'move',
|
||||
u = 'unmark',
|
||||
d = 'delete',
|
||||
delete = 'delete',
|
||||
[ 'control-h' ] = 'toggle_hidden',
|
||||
[ 'control-s' ] = 'toggle_dirSize',
|
||||
[ 'control-x' ] = 'cut',
|
||||
[ 'control-c' ] = 'copy',
|
||||
paste = 'paste',
|
||||
},
|
||||
menuBar = UI.MenuBar {
|
||||
buttons = {
|
||||
{ text = '^-', event = 'updir' },
|
||||
{ text = 'File', dropdown = {
|
||||
{ text = 'Run', event = 'run', flags = FILE },
|
||||
{ text = 'Edit e', event = 'edit', flags = FILE },
|
||||
{ text = 'Cloud edit c', event = 'cedit', flags = FILE },
|
||||
{ text = 'Pastebin put p', event = 'pastebin', flags = FILE },
|
||||
{ text = 'Shell s', event = 'shell' },
|
||||
{ spacer = true },
|
||||
{ text = 'Quit ^q', event = 'quit' },
|
||||
} },
|
||||
{ text = 'Edit', dropdown = {
|
||||
{ text = 'Cut ^x', event = 'cut' },
|
||||
{ text = 'Copy ^c', event = 'copy' },
|
||||
{ text = 'Copy path ', event = 'copy_path' },
|
||||
{ text = 'Paste ^v', event = 'paste' },
|
||||
{ spacer = true },
|
||||
{ text = 'Mark m', event = 'mark' },
|
||||
{ text = 'Unmark all u', event = 'unmark' },
|
||||
{ spacer = true },
|
||||
{ text = 'Delete del', event = 'delete' },
|
||||
} },
|
||||
{ text = 'View', dropdown = {
|
||||
{ text = 'Refresh r', event = 'refresh' },
|
||||
{ text = 'Hidden ^h', event = 'toggle_hidden' },
|
||||
{ text = 'Dir Size ^s', event = 'toggle_dirSize' },
|
||||
} },
|
||||
{ text = '\187',
|
||||
x = -3,
|
||||
dropdown = {
|
||||
{ text = 'Associations', event = 'associate' },
|
||||
} },
|
||||
},
|
||||
},
|
||||
grid = UI.ScrollingGrid {
|
||||
columns = {
|
||||
{ heading = 'Name', key = 'name' },
|
||||
{ key = 'flags', width = 2 },
|
||||
{ heading = 'Size', key = 'fsize', width = 5 },
|
||||
},
|
||||
sortColumn = 'name',
|
||||
y = 2, ey = -2,
|
||||
},
|
||||
statusBar = UI.StatusBar {
|
||||
columns = {
|
||||
{ key = 'status' },
|
||||
{ key = 'totalSize', width = 6 },
|
||||
},
|
||||
},
|
||||
notification = UI.Notification { },
|
||||
associations = UI.SlideOut {
|
||||
backgroundColor = colors.cyan,
|
||||
menuBar = UI.MenuBar {
|
||||
buttons = {
|
||||
{ text = 'Save', event = 'save' },
|
||||
{ text = 'Cancel', event = 'cancel' },
|
||||
},
|
||||
},
|
||||
grid = UI.ScrollingGrid {
|
||||
x = 2, ex = -6, y = 3, ey = -5,
|
||||
columns = {
|
||||
{ heading = 'Extension', key = 'name' },
|
||||
{ heading = 'Program', key = 'value' },
|
||||
},
|
||||
autospace = true,
|
||||
sortColumn = 'name',
|
||||
accelerators = {
|
||||
delete = 'remove_entry',
|
||||
},
|
||||
},
|
||||
remove = UI.Button {
|
||||
x = -4, y = 6,
|
||||
text = '-', event = 'remove_entry', help = 'Remove',
|
||||
},
|
||||
form = UI.Form {
|
||||
x = 3, y = -3, ey = -2,
|
||||
margin = 1,
|
||||
manualControls = true,
|
||||
[1] = UI.TextEntry {
|
||||
width = 20,
|
||||
formLabel = 'Extension', formKey = 'name',
|
||||
shadowText = 'extension',
|
||||
required = true,
|
||||
limit = 64,
|
||||
},
|
||||
[2] = UI.TextEntry {
|
||||
width = 20,
|
||||
formLabel = 'Program', formKey = 'value',
|
||||
shadowText = 'program',
|
||||
required = true,
|
||||
limit = 128,
|
||||
},
|
||||
add = UI.Button {
|
||||
x = -11, y = 1,
|
||||
text = 'Add', event = 'add_association',
|
||||
},
|
||||
},
|
||||
statusBar = UI.StatusBar {
|
||||
backgroundColor = colors.cyan,
|
||||
},
|
||||
},
|
||||
accelerators = {
|
||||
[ 'control-q' ] = 'quit',
|
||||
c = 'cedit',
|
||||
e = 'edit',
|
||||
s = 'shell',
|
||||
p = 'pastebin',
|
||||
r = 'refresh',
|
||||
[ ' ' ] = 'mark',
|
||||
m = 'mark',
|
||||
backspace = 'updir',
|
||||
u = 'unmark',
|
||||
d = 'delete',
|
||||
delete = 'delete',
|
||||
[ 'control-h' ] = 'toggle_hidden',
|
||||
[ 'control-s' ] = 'toggle_dirSize',
|
||||
[ 'control-x' ] = 'cut',
|
||||
[ 'control-c' ] = 'copy',
|
||||
paste = 'paste',
|
||||
},
|
||||
}
|
||||
|
||||
function Browser:enable()
|
||||
UI.Page.enable(self)
|
||||
self:setFocus(self.grid)
|
||||
UI.Page.enable(self)
|
||||
self:setFocus(self.grid)
|
||||
end
|
||||
|
||||
function Browser.menuBar:getActive(menuItem)
|
||||
local file = Browser.grid:getSelected()
|
||||
if file then
|
||||
if menuItem.event == 'edit' or menuItem.event == 'run' then
|
||||
return not file.isDir
|
||||
end
|
||||
end
|
||||
return true
|
||||
local file = Browser.grid:getSelected()
|
||||
if menuItem.flags == FILE then
|
||||
return file and not file.isDir
|
||||
end
|
||||
return true
|
||||
end
|
||||
|
||||
function Browser.grid:sortCompare(a, b)
|
||||
if self.sortColumn == 'fsize' then
|
||||
return a.size < b.size
|
||||
elseif self.sortColumn == 'flags' then
|
||||
return a.flags < b.flags
|
||||
end
|
||||
if a.isDir == b.isDir then
|
||||
return a.name:lower() < b.name:lower()
|
||||
end
|
||||
return a.isDir
|
||||
if self.sortColumn == 'fsize' then
|
||||
return a.size < b.size
|
||||
elseif self.sortColumn == 'flags' then
|
||||
return a.flags < b.flags
|
||||
end
|
||||
if a.isDir == b.isDir then
|
||||
return a.name:lower() < b.name:lower()
|
||||
end
|
||||
return a.isDir
|
||||
end
|
||||
|
||||
function Browser.grid:getRowTextColor(file)
|
||||
if file.marked then
|
||||
return colors.green
|
||||
end
|
||||
if file.isDir then
|
||||
return colors.cyan
|
||||
end
|
||||
if file.isReadOnly then
|
||||
return colors.pink
|
||||
end
|
||||
return colors.white
|
||||
if file.marked then
|
||||
return colors.green
|
||||
end
|
||||
if file.isDir then
|
||||
return colors.cyan
|
||||
end
|
||||
if file.isReadOnly then
|
||||
return colors.pink
|
||||
end
|
||||
return colors.white
|
||||
end
|
||||
|
||||
function Browser.grid:eventHandler(event)
|
||||
if event.type == 'copy' then -- let copy be handled by parent
|
||||
return false
|
||||
end
|
||||
return UI.ScrollingGrid.eventHandler(self, event)
|
||||
if event.type == 'copy' then -- let copy be handled by parent
|
||||
return false
|
||||
end
|
||||
return UI.ScrollingGrid.eventHandler(self, event)
|
||||
end
|
||||
|
||||
function Browser.statusBar:draw()
|
||||
if self.parent.dir then
|
||||
local info = '#:' .. Util.size(self.parent.dir.files)
|
||||
local numMarked = Util.size(marked)
|
||||
if numMarked > 0 then
|
||||
info = info .. ' M:' .. numMarked
|
||||
end
|
||||
self:setValue('info', info)
|
||||
self:setValue('totalSize', formatSize(self.parent.dir.totalSize))
|
||||
UI.StatusBar.draw(self)
|
||||
end
|
||||
if self.parent.dir then
|
||||
local info = '#:' .. Util.size(self.parent.dir.files)
|
||||
local numMarked = Util.size(marked)
|
||||
if numMarked > 0 then
|
||||
info = info .. ' M:' .. numMarked
|
||||
end
|
||||
self:setValue('info', info)
|
||||
self:setValue('totalSize', formatSize(self.parent.dir.totalSize))
|
||||
UI.StatusBar.draw(self)
|
||||
end
|
||||
end
|
||||
|
||||
function Browser:setStatus(status, ...)
|
||||
self.statusBar:timedStatus(string.format(status, ...))
|
||||
self.notification:info(string.format(status, ...))
|
||||
end
|
||||
|
||||
function Browser:unmarkAll()
|
||||
for _,m in pairs(marked) do
|
||||
m.marked = false
|
||||
end
|
||||
Util.clear(marked)
|
||||
for _,m in pairs(marked) do
|
||||
m.marked = false
|
||||
end
|
||||
Util.clear(marked)
|
||||
end
|
||||
|
||||
function Browser:getDirectory(directory)
|
||||
local s, dir = pcall(function()
|
||||
local s, dir = pcall(function()
|
||||
|
||||
local dir = directories[directory]
|
||||
if not dir then
|
||||
dir = {
|
||||
name = directory,
|
||||
size = 0,
|
||||
files = { },
|
||||
totalSize = 0,
|
||||
index = 1
|
||||
}
|
||||
directories[directory] = dir
|
||||
end
|
||||
local dir = directories[directory]
|
||||
if not dir then
|
||||
dir = {
|
||||
name = directory,
|
||||
size = 0,
|
||||
files = { },
|
||||
totalSize = 0,
|
||||
index = 1
|
||||
}
|
||||
directories[directory] = dir
|
||||
end
|
||||
|
||||
self:updateDirectory(dir)
|
||||
self:updateDirectory(dir)
|
||||
|
||||
return dir
|
||||
end)
|
||||
return dir
|
||||
end)
|
||||
|
||||
return s, dir
|
||||
return s, dir
|
||||
end
|
||||
|
||||
function Browser:updateDirectory(dir)
|
||||
|
||||
dir.size = 0
|
||||
dir.totalSize = 0
|
||||
Util.clear(dir.files)
|
||||
dir.size = 0
|
||||
dir.totalSize = 0
|
||||
Util.clear(dir.files)
|
||||
|
||||
local files = fs.listEx(dir.name)
|
||||
if files then
|
||||
dir.size = #files
|
||||
for _, file in pairs(files) do
|
||||
file.fullName = fs.combine(dir.name, file.name)
|
||||
file.flags = ''
|
||||
if not file.isDir then
|
||||
dir.totalSize = dir.totalSize + file.size
|
||||
file.fsize = formatSize(file.size)
|
||||
else
|
||||
if config.showDirSizes then
|
||||
file.size = fs.getSize(file.fullName, true)
|
||||
local files = fs.listEx(dir.name)
|
||||
if files then
|
||||
dir.size = #files
|
||||
for _, file in pairs(files) do
|
||||
file.fullName = fs.combine(dir.name, file.name)
|
||||
file.flags = ''
|
||||
if not file.isDir then
|
||||
dir.totalSize = dir.totalSize + file.size
|
||||
file.fsize = formatSize(file.size)
|
||||
else
|
||||
if config.showDirSizes then
|
||||
file.size = fs.getSize(file.fullName, true)
|
||||
|
||||
dir.totalSize = dir.totalSize + file.size
|
||||
file.fsize = formatSize(file.size)
|
||||
end
|
||||
file.flags = 'D'
|
||||
end
|
||||
if file.isReadOnly then
|
||||
file.flags = file.flags .. 'R'
|
||||
end
|
||||
if config.showHidden or file.name:sub(1, 1) ~= '.' then
|
||||
dir.files[file.fullName] = file
|
||||
end
|
||||
end
|
||||
end
|
||||
dir.totalSize = dir.totalSize + file.size
|
||||
file.fsize = formatSize(file.size)
|
||||
end
|
||||
file.flags = 'D'
|
||||
end
|
||||
if file.isReadOnly then
|
||||
file.flags = file.flags .. 'R'
|
||||
end
|
||||
if config.showHidden or file.name:sub(1, 1) ~= '.' then
|
||||
dir.files[file.fullName] = file
|
||||
end
|
||||
end
|
||||
end
|
||||
-- self.grid:update()
|
||||
-- self.grid:setIndex(dir.index)
|
||||
self.grid:setValues(dir.files)
|
||||
self.grid:setValues(dir.files)
|
||||
end
|
||||
|
||||
function Browser:setDir(dirName, noStatus)
|
||||
self:unmarkAll()
|
||||
self:unmarkAll()
|
||||
|
||||
if self.dir then
|
||||
self.dir.index = self.grid:getIndex()
|
||||
end
|
||||
local DIR = fs.combine('', dirName)
|
||||
shell.setDir(DIR)
|
||||
local s, dir = self:getDirectory(DIR)
|
||||
if s then
|
||||
self.dir = dir
|
||||
elseif noStatus then
|
||||
error(dir)
|
||||
else
|
||||
self:setStatus(dir)
|
||||
self:setDir('', true)
|
||||
return
|
||||
end
|
||||
if self.dir then
|
||||
self.dir.index = self.grid:getIndex()
|
||||
end
|
||||
local DIR = fs.combine('', dirName)
|
||||
shell.setDir(DIR)
|
||||
local s, dir = self:getDirectory(DIR)
|
||||
if s then
|
||||
self.dir = dir
|
||||
elseif noStatus then
|
||||
error(dir)
|
||||
else
|
||||
self:setStatus(dir)
|
||||
self:setDir('', true)
|
||||
return
|
||||
end
|
||||
|
||||
if not noStatus then
|
||||
self.statusBar:setValue('status', '/' .. self.dir.name)
|
||||
self.statusBar:draw()
|
||||
end
|
||||
self.grid:setIndex(self.dir.index)
|
||||
if not noStatus then
|
||||
self.statusBar:setValue('status', '/' .. self.dir.name)
|
||||
self.statusBar:draw()
|
||||
end
|
||||
self.grid:setIndex(self.dir.index)
|
||||
end
|
||||
|
||||
function Browser:run(...)
|
||||
if multishell then
|
||||
local tabId = shell.openTab(...)
|
||||
multishell.setFocus(tabId)
|
||||
else
|
||||
shell.run(...)
|
||||
Event.terminate = false
|
||||
self:draw()
|
||||
end
|
||||
if multishell then
|
||||
local tabId = shell.openTab(...)
|
||||
multishell.setFocus(tabId)
|
||||
else
|
||||
shell.run(...)
|
||||
Event.terminate = false
|
||||
self:draw()
|
||||
end
|
||||
end
|
||||
|
||||
function Browser:hasMarked()
|
||||
if Util.size(marked) == 0 then
|
||||
local file = self.grid:getSelected()
|
||||
if file then
|
||||
file.marked = true
|
||||
marked[file.fullName] = file
|
||||
self.grid:draw()
|
||||
end
|
||||
end
|
||||
return Util.size(marked) > 0
|
||||
if Util.size(marked) == 0 then
|
||||
local file = self.grid:getSelected()
|
||||
if file then
|
||||
file.marked = true
|
||||
marked[file.fullName] = file
|
||||
self.grid:draw()
|
||||
end
|
||||
end
|
||||
return Util.size(marked) > 0
|
||||
end
|
||||
|
||||
function Browser:eventHandler(event)
|
||||
local file = self.grid:getSelected()
|
||||
local file = self.grid:getSelected()
|
||||
|
||||
if event.type == 'quit' then
|
||||
Event.exitPullEvents()
|
||||
if event.type == 'quit' then
|
||||
Event.exitPullEvents()
|
||||
|
||||
elseif event.type == 'edit' and file then
|
||||
self:run('edit', file.name)
|
||||
elseif event.type == 'edit' and file then
|
||||
self:run('edit', file.name)
|
||||
|
||||
elseif event.type == 'shell' then
|
||||
self:run('sys/apps/shell')
|
||||
elseif event.type == 'cedit' and file then
|
||||
self:run('cedit', file.name)
|
||||
self:setStatus('Started cloud edit')
|
||||
|
||||
elseif event.type == 'refresh' then
|
||||
self:updateDirectory(self.dir)
|
||||
self.grid:draw()
|
||||
self:setStatus('Refreshed')
|
||||
elseif event.type == 'shell' then
|
||||
self:run(Alt.get('shell'))
|
||||
|
||||
elseif event.type == 'toggle_hidden' then
|
||||
config.showHidden = not config.showHidden
|
||||
Config.update('Files', config)
|
||||
elseif event.type == 'refresh' then
|
||||
self:updateDirectory(self.dir)
|
||||
self.grid:draw()
|
||||
self:setStatus('Refreshed')
|
||||
|
||||
self:updateDirectory(self.dir)
|
||||
self.grid:draw()
|
||||
if not config.showHidden then
|
||||
self:setStatus('Hiding hidden')
|
||||
else
|
||||
self:setStatus('Displaying hidden')
|
||||
end
|
||||
elseif event.type == 'associate' then
|
||||
self.associations:show()
|
||||
|
||||
elseif event.type == 'toggle_dirSize' then
|
||||
config.showDirSizes = not config.showDirSizes
|
||||
Config.update('Files', config)
|
||||
elseif event.type == 'pastebin' then
|
||||
if file and not file.isDir then
|
||||
local s, m = pastebin.put(file.fullName)
|
||||
if s then
|
||||
os.queueEvent('clipboard_copy', s)
|
||||
self.notification:success(string.format('Uploaded as %s', s), 0)
|
||||
else
|
||||
self.notification:error(m)
|
||||
end
|
||||
end
|
||||
|
||||
self:updateDirectory(self.dir)
|
||||
self.grid:draw()
|
||||
if config.showDirSizes then
|
||||
self:setStatus('Displaying dir sizes')
|
||||
end
|
||||
elseif event.type == 'toggle_hidden' then
|
||||
config.showHidden = not config.showHidden
|
||||
Config.update('Files', config)
|
||||
|
||||
elseif event.type == 'mark' and file then
|
||||
file.marked = not file.marked
|
||||
if file.marked then
|
||||
marked[file.fullName] = file
|
||||
else
|
||||
marked[file.fullName] = nil
|
||||
end
|
||||
self.grid:draw()
|
||||
self.statusBar:draw()
|
||||
self:updateDirectory(self.dir)
|
||||
self.grid:draw()
|
||||
if not config.showHidden then
|
||||
self:setStatus('Hiding hidden')
|
||||
else
|
||||
self:setStatus('Displaying hidden')
|
||||
end
|
||||
|
||||
elseif event.type == 'unmark' then
|
||||
self:unmarkAll()
|
||||
self.grid:draw()
|
||||
self:setStatus('Marked files cleared')
|
||||
elseif event.type == 'toggle_dirSize' then
|
||||
config.showDirSizes = not config.showDirSizes
|
||||
Config.update('Files', config)
|
||||
|
||||
elseif event.type == 'grid_select' or event.type == 'run' then
|
||||
if file then
|
||||
if file.isDir then
|
||||
self:setDir(file.fullName)
|
||||
else
|
||||
self:run(file.name)
|
||||
end
|
||||
end
|
||||
self:updateDirectory(self.dir)
|
||||
self.grid:draw()
|
||||
if config.showDirSizes then
|
||||
self:setStatus('Displaying dir sizes')
|
||||
end
|
||||
|
||||
elseif event.type == 'updir' then
|
||||
local dir = (self.dir.name:match("(.*/)"))
|
||||
self:setDir(dir or '/')
|
||||
elseif event.type == 'mark' and file then
|
||||
file.marked = not file.marked
|
||||
if file.marked then
|
||||
marked[file.fullName] = file
|
||||
else
|
||||
marked[file.fullName] = nil
|
||||
end
|
||||
self.grid:draw()
|
||||
self.statusBar:draw()
|
||||
|
||||
elseif event.type == 'delete' then
|
||||
if self:hasMarked() then
|
||||
local width = self.statusBar:getColumnWidth('status')
|
||||
self.statusBar:setColumnWidth('status', UI.term.width)
|
||||
self.statusBar:setValue('status', 'Delete marked? (y/n)')
|
||||
self.statusBar:draw()
|
||||
self.statusBar:sync()
|
||||
local _, ch = os.pullEvent('char')
|
||||
if ch == 'y' or ch == 'Y' then
|
||||
for _,m in pairs(marked) do
|
||||
pcall(function()
|
||||
fs.delete(m.fullName)
|
||||
end)
|
||||
end
|
||||
end
|
||||
marked = { }
|
||||
self.statusBar:setColumnWidth('status', width)
|
||||
self.statusBar:setValue('status', '/' .. self.dir.name)
|
||||
self:updateDirectory(self.dir)
|
||||
elseif event.type == 'unmark' then
|
||||
self:unmarkAll()
|
||||
self.grid:draw()
|
||||
self:setStatus('Marked files cleared')
|
||||
|
||||
self.statusBar:draw()
|
||||
self.grid:draw()
|
||||
self:setFocus(self.grid)
|
||||
end
|
||||
elseif event.type == 'grid_select' or event.type == 'run' then
|
||||
if file then
|
||||
if file.isDir then
|
||||
self:setDir(file.fullName)
|
||||
else
|
||||
local ext = file.name:match('%.(%w+)$')
|
||||
if ext and config.associations[ext] then
|
||||
self:run(config.associations[ext], '/' .. file.fullName)
|
||||
else
|
||||
self:run(file.name)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
elseif event.type == 'copy' or event.type == 'cut' then
|
||||
if self:hasMarked() then
|
||||
cutMode = event.type == 'cut'
|
||||
Util.clear(copied)
|
||||
Util.merge(copied, marked)
|
||||
--self:unmarkAll()
|
||||
self.grid:draw()
|
||||
self:setStatus('Copied %d file(s)', Util.size(copied))
|
||||
end
|
||||
elseif event.type == 'updir' then
|
||||
local dir = (self.dir.name:match("(.*/)"))
|
||||
self:setDir(dir or '/')
|
||||
|
||||
elseif event.type == 'copy_path' then
|
||||
if file then
|
||||
os.queueEvent('clipboard_copy', file.fullName)
|
||||
end
|
||||
elseif event.type == 'delete' then
|
||||
if self:hasMarked() then
|
||||
local width = self.statusBar:getColumnWidth('status')
|
||||
self.statusBar:setColumnWidth('status', UI.term.width)
|
||||
self.statusBar:setValue('status', 'Delete marked? (y/n)')
|
||||
self.statusBar:draw()
|
||||
self.statusBar:sync()
|
||||
local _, ch = os.pullEvent('char')
|
||||
if ch == 'y' or ch == 'Y' then
|
||||
for _,m in pairs(marked) do
|
||||
pcall(function()
|
||||
fs.delete(m.fullName)
|
||||
end)
|
||||
end
|
||||
end
|
||||
marked = { }
|
||||
self.statusBar:setColumnWidth('status', width)
|
||||
self.statusBar:setValue('status', '/' .. self.dir.name)
|
||||
self:updateDirectory(self.dir)
|
||||
|
||||
elseif event.type == 'paste' then
|
||||
for _,m in pairs(copied) do
|
||||
local s, m = pcall(function()
|
||||
if cutMode then
|
||||
fs.move(m.fullName, fs.combine(self.dir.name, m.name))
|
||||
else
|
||||
fs.copy(m.fullName, fs.combine(self.dir.name, m.name))
|
||||
end
|
||||
end)
|
||||
end
|
||||
self:updateDirectory(self.dir)
|
||||
self.grid:draw()
|
||||
self:setStatus('Pasted ' .. Util.size(copied) .. ' file(s)')
|
||||
self.statusBar:draw()
|
||||
self.grid:draw()
|
||||
self:setFocus(self.grid)
|
||||
end
|
||||
|
||||
else
|
||||
return UI.Page.eventHandler(self, event)
|
||||
end
|
||||
self:setFocus(self.grid)
|
||||
return true
|
||||
elseif event.type == 'copy' or event.type == 'cut' then
|
||||
if self:hasMarked() then
|
||||
cutMode = event.type == 'cut'
|
||||
Util.clear(copied)
|
||||
Util.merge(copied, marked)
|
||||
--self:unmarkAll()
|
||||
self.grid:draw()
|
||||
self:setStatus('Copied %d file(s)', Util.size(copied))
|
||||
end
|
||||
|
||||
elseif event.type == 'copy_path' then
|
||||
if file then
|
||||
os.queueEvent('clipboard_copy', file.fullName)
|
||||
end
|
||||
|
||||
elseif event.type == 'paste' then
|
||||
for _,m in pairs(copied) do
|
||||
local s, m = pcall(function()
|
||||
if cutMode then
|
||||
fs.move(m.fullName, fs.combine(self.dir.name, m.name))
|
||||
else
|
||||
fs.copy(m.fullName, fs.combine(self.dir.name, m.name))
|
||||
end
|
||||
end)
|
||||
end
|
||||
self:updateDirectory(self.dir)
|
||||
self.grid:draw()
|
||||
self:setStatus('Pasted ' .. Util.size(copied) .. ' file(s)')
|
||||
|
||||
else
|
||||
return UI.Page.eventHandler(self, event)
|
||||
end
|
||||
self:setFocus(self.grid)
|
||||
return true
|
||||
end
|
||||
|
||||
--[[ Associations slide out ]] --
|
||||
function Browser.associations:show()
|
||||
self.grid.values = { }
|
||||
for k, v in pairs(config.associations) do
|
||||
table.insert(self.grid.values, {
|
||||
name = k,
|
||||
value = v,
|
||||
})
|
||||
end
|
||||
self.grid:update()
|
||||
UI.SlideOut.show(self)
|
||||
self:setFocus(self.form[1])
|
||||
end
|
||||
|
||||
function Browser.associations:eventHandler(event)
|
||||
if event.type == 'remove_entry' then
|
||||
local row = self.grid:getSelected()
|
||||
if row then
|
||||
Util.removeByValue(self.grid.values, row)
|
||||
self.grid:update()
|
||||
self.grid:draw()
|
||||
end
|
||||
|
||||
elseif event.type == 'add_association' then
|
||||
if self.form:save() then
|
||||
local entry = Util.find(self.grid.values, 'name', self.form[1].value) or { }
|
||||
entry.name = self.form[1].value
|
||||
entry.value = self.form[2].value
|
||||
table.insert(self.grid.values, entry)
|
||||
self.form[1]:reset()
|
||||
self.form[2]:reset()
|
||||
self.grid:update()
|
||||
self.grid:draw()
|
||||
end
|
||||
|
||||
elseif event.type == 'cancel' then
|
||||
self:hide()
|
||||
|
||||
elseif event.type == 'save' then
|
||||
config.associations = { }
|
||||
for _, v in pairs(self.grid.values) do
|
||||
config.associations[v.name] = v.value
|
||||
end
|
||||
Config.update('Files', config)
|
||||
self:hide()
|
||||
|
||||
else
|
||||
return UI.SlideOut.eventHandler(self, event)
|
||||
end
|
||||
return true
|
||||
end
|
||||
|
||||
--[[-- Startup logic --]]--
|
||||
local args = { ... }
|
||||
local args = Util.parse(...)
|
||||
|
||||
Browser:setDir(args[1] or shell.dir())
|
||||
|
||||
|
@ -1,10 +1,8 @@
|
||||
_G.requireInjector(_ENV)
|
||||
local UI = require('opus.ui')
|
||||
local Util = require('opus.util')
|
||||
|
||||
local UI = require('ui')
|
||||
local Util = require('util')
|
||||
|
||||
local colors = _G.colors
|
||||
local help = _G.help
|
||||
local colors = _G.colors
|
||||
local help = _G.help
|
||||
|
||||
UI:configure('Help', ...)
|
||||
|
||||
@ -23,6 +21,7 @@ local page = UI.Page {
|
||||
filter = UI.TextEntry {
|
||||
x = 10, y = 2, ex = -3,
|
||||
limit = 32,
|
||||
transform = 'lowercase',
|
||||
},
|
||||
grid = UI.ScrollingGrid {
|
||||
y = 4,
|
||||
@ -33,7 +32,7 @@ local page = UI.Page {
|
||||
sortColumn = 'name',
|
||||
},
|
||||
accelerators = {
|
||||
q = 'quit',
|
||||
[ 'control-q' ] = 'quit',
|
||||
enter = 'grid_select',
|
||||
},
|
||||
}
|
||||
@ -42,21 +41,30 @@ local topicPage = UI.Page {
|
||||
backgroundColor = colors.black,
|
||||
titleBar = UI.TitleBar {
|
||||
title = 'text',
|
||||
previousPage = true,
|
||||
event = 'back',
|
||||
},
|
||||
helpText = UI.TextArea {
|
||||
backgroundColor = colors.black,
|
||||
x = 2, ex = -1, y = 3, ey = -2,
|
||||
},
|
||||
accelerators = {
|
||||
q = 'back',
|
||||
[ 'control-q' ] = 'back',
|
||||
backspace = 'back',
|
||||
},
|
||||
}
|
||||
|
||||
function topicPage:enable(name)
|
||||
local f = help.lookup(name)
|
||||
|
||||
self.titleBar.title = name
|
||||
self.helpText:setText(f and Util.readFile(f) or 'No help available for ' .. name)
|
||||
|
||||
return UI.Page.enable(self)
|
||||
end
|
||||
|
||||
function topicPage:eventHandler(event)
|
||||
if event.type == 'back' then
|
||||
UI:setPreviousPage()
|
||||
UI:setPage(page)
|
||||
end
|
||||
return UI.Page.eventHandler(self, event)
|
||||
end
|
||||
@ -68,12 +76,8 @@ function page:eventHandler(event)
|
||||
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))
|
||||
|
||||
UI:setPage(topicPage)
|
||||
UI:setPage(topicPage, name)
|
||||
end
|
||||
|
||||
elseif event.type == 'text_change' then
|
||||
@ -95,5 +99,6 @@ function page:eventHandler(event)
|
||||
end
|
||||
end
|
||||
|
||||
UI:setPage(page)
|
||||
local args = Util.parse(...)
|
||||
UI:setPage(args[1] and topicPage or page, args[1])
|
||||
UI:pullEvents()
|
||||
|
@ -1,457 +0,0 @@
|
||||
local colors = _G.colors
|
||||
local fs = _G.fs
|
||||
local http = _G.http
|
||||
local install = _ENV.install
|
||||
local os = _G.os
|
||||
|
||||
local injector
|
||||
if not install.testing then
|
||||
_G.OPUS_BRANCH = 'master-1.8'
|
||||
local url ='https://raw.githubusercontent.com/kepler155c/opus/master-1.8/sys/apis/injector.lua'
|
||||
injector = load(http.get(url).readAll(), 'injector.lua', nil, _ENV)()
|
||||
else
|
||||
injector = _G.requireInjector
|
||||
end
|
||||
|
||||
injector(_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
|
131
sys/apps/Lua.lua
131
sys/apps/Lua.lua
@ -1,8 +1,9 @@
|
||||
-- Lua may be called from outside of shell - inject a require
|
||||
_G.requireInjector(_ENV)
|
||||
|
||||
local History = require('history')
|
||||
local UI = require('ui')
|
||||
local Util = require('util')
|
||||
local History = require('opus.history')
|
||||
local UI = require('opus.ui')
|
||||
local Util = require('opus.util')
|
||||
|
||||
local colors = _G.colors
|
||||
local os = _G.os
|
||||
@ -19,6 +20,7 @@ _G.requireInjector(sandboxEnv)
|
||||
UI:configure('Lua', ...)
|
||||
|
||||
local command = ''
|
||||
local counter = 1
|
||||
local history = History.load('usr/.lua_history', 25)
|
||||
|
||||
local page = UI.Page {
|
||||
@ -32,7 +34,7 @@ local page = UI.Page {
|
||||
prompt = UI.TextEntry {
|
||||
y = 2,
|
||||
shadowText = 'enter command',
|
||||
limit = 256,
|
||||
limit = 1024,
|
||||
accelerators = {
|
||||
enter = 'command_enter',
|
||||
up = 'history_back',
|
||||
@ -41,35 +43,40 @@ local page = UI.Page {
|
||||
[ 'control-space' ] = 'autocomplete',
|
||||
},
|
||||
},
|
||||
grid = UI.ScrollingGrid {
|
||||
y = 3, ey = -2,
|
||||
columns = {
|
||||
{ heading = 'Key', key = 'name' },
|
||||
{ heading = 'Value', key = 'value' },
|
||||
tabs = UI.Tabs {
|
||||
y = 3,
|
||||
[1] = UI.Tab {
|
||||
tabTitle = 'Formatted',
|
||||
grid = UI.ScrollingGrid {
|
||||
columns = {
|
||||
{ heading = 'Key', key = 'name' },
|
||||
{ heading = 'Value', key = 'value' },
|
||||
},
|
||||
sortColumn = 'name',
|
||||
autospace = true,
|
||||
},
|
||||
},
|
||||
[2] = UI.Tab {
|
||||
tabTitle = 'Output',
|
||||
output = UI.Embedded {
|
||||
visible = true,
|
||||
maxScroll = 1000,
|
||||
backgroundColor = colors.black,
|
||||
},
|
||||
},
|
||||
sortColumn = 'name',
|
||||
autospace = true,
|
||||
},
|
||||
titleBar = UI.TitleBar {
|
||||
title = 'Output',
|
||||
y = -1,
|
||||
event = 'show_output',
|
||||
closeInd = '^'
|
||||
},
|
||||
output = UI.Embedded {
|
||||
y = -6,
|
||||
backgroundColor = colors.gray,
|
||||
},
|
||||
}
|
||||
|
||||
page.grid = page.tabs[1].grid
|
||||
page.output = page.tabs[2].output
|
||||
|
||||
function page:setPrompt(value, focus)
|
||||
self.prompt:setValue(value)
|
||||
self.prompt.scroll = 0
|
||||
self.prompt:setPosition(#value)
|
||||
self.prompt:updateScroll()
|
||||
|
||||
if value:sub(-1) == ')' then
|
||||
self.prompt:setPosition(#value - 1)
|
||||
else
|
||||
self.prompt:setPosition(#value)
|
||||
end
|
||||
|
||||
self.prompt:draw()
|
||||
@ -79,9 +86,8 @@ function page:setPrompt(value, focus)
|
||||
end
|
||||
|
||||
function page:enable()
|
||||
self:setFocus(self.prompt)
|
||||
UI.Page.enable(self)
|
||||
self.output:disable()
|
||||
self:setFocus(self.prompt)
|
||||
end
|
||||
|
||||
local function autocomplete(env, oLine, x)
|
||||
@ -140,23 +146,16 @@ function page:eventHandler(event)
|
||||
|
||||
self:draw()
|
||||
|
||||
elseif event.type == 'tab_select' then
|
||||
self:setFocus(self.prompt)
|
||||
|
||||
elseif event.type == 'show_output' then
|
||||
self.output:enable()
|
||||
|
||||
self.titleBar.oy = -7
|
||||
self.titleBar.event = 'hide_output'
|
||||
self.titleBar.closeInd = 'v'
|
||||
self.titleBar:resize()
|
||||
|
||||
self.grid.ey = -8
|
||||
self.grid:resize()
|
||||
|
||||
self:draw()
|
||||
self.tabs:selectTab(self.tabs[2])
|
||||
|
||||
elseif event.type == 'autocomplete' then
|
||||
local sz = #self.prompt.value
|
||||
local pos = self.prompt.pos
|
||||
self:setPrompt(autocomplete(sandboxEnv, self.prompt.value, self.prompt.pos))
|
||||
local pos = self.prompt.entry.pos
|
||||
self:setPrompt(autocomplete(sandboxEnv, self.prompt.value, self.prompt.entry.pos))
|
||||
self.prompt:setPosition(pos + #self.prompt.value - sz)
|
||||
self.prompt:updateCursor()
|
||||
|
||||
@ -181,8 +180,6 @@ function page:eventHandler(event)
|
||||
local s = tostring(self.prompt.value)
|
||||
|
||||
if #s > 0 then
|
||||
history:add(s)
|
||||
history:back()
|
||||
self:executeStatement(s)
|
||||
else
|
||||
local t = { }
|
||||
@ -212,10 +209,6 @@ end
|
||||
function page:setResult(result)
|
||||
local t = { }
|
||||
|
||||
local oterm = term.redirect(self.output.win)
|
||||
Util.print(result)
|
||||
term.redirect(oterm)
|
||||
|
||||
local function safeValue(v)
|
||||
if type(v) == 'string' or type(v) == 'number' then
|
||||
return v
|
||||
@ -300,24 +293,45 @@ end
|
||||
|
||||
function page:rawExecute(s)
|
||||
local fn, m
|
||||
local wrapped
|
||||
|
||||
fn = load('return (' ..s.. ')', 'lua', nil, sandboxEnv)
|
||||
|
||||
if fn then
|
||||
fn = load('return {' ..s.. '}', 'lua', nil, sandboxEnv)
|
||||
wrapped = true
|
||||
end
|
||||
|
||||
local t = os.clock()
|
||||
if fn then
|
||||
fn, m = pcall(fn)
|
||||
if #m == 1 then
|
||||
if #m <= 1 and wrapped then
|
||||
m = m[1]
|
||||
end
|
||||
return fn, m
|
||||
else
|
||||
fn, m = load(s, 'lua', nil, sandboxEnv)
|
||||
if fn then
|
||||
t = os.clock()
|
||||
fn, m = pcall(fn)
|
||||
end
|
||||
end
|
||||
|
||||
fn, m = load(s, 'lua', nil, sandboxEnv)
|
||||
if fn then
|
||||
fn, m = pcall(fn)
|
||||
t = os.clock() - t
|
||||
|
||||
local bg, fg = term.getBackgroundColor(), term.getTextColor()
|
||||
term.setTextColor(colors.cyan)
|
||||
term.setBackgroundColor(colors.black)
|
||||
term.write(string.format('out [%.2f]: ', t))
|
||||
term.setBackgroundColor(bg)
|
||||
term.setTextColor(fg)
|
||||
if m or wrapped then
|
||||
Util.print(m or 'nil')
|
||||
else
|
||||
print()
|
||||
end
|
||||
else
|
||||
_G.printError(m)
|
||||
end
|
||||
|
||||
return fn, m
|
||||
@ -326,15 +340,26 @@ end
|
||||
function page:executeStatement(statement)
|
||||
command = statement
|
||||
|
||||
history:add(statement)
|
||||
history:back()
|
||||
|
||||
local s, m
|
||||
local oterm = term.redirect(self.output.win)
|
||||
self.output.win.scrollBottom()
|
||||
local bg, fg = term.getBackgroundColor(), term.getTextColor()
|
||||
term.setBackgroundColor(colors.black)
|
||||
term.setTextColor(colors.green)
|
||||
term.write(string.format('in [%d]: ', counter))
|
||||
term.setBackgroundColor(bg)
|
||||
term.setTextColor(fg)
|
||||
print(tostring(statement))
|
||||
|
||||
pcall(function()
|
||||
s, m = self:rawExecute(command)
|
||||
end)
|
||||
if not s then
|
||||
_G.printError(m)
|
||||
end
|
||||
|
||||
term.redirect(oterm)
|
||||
counter = counter + 1
|
||||
|
||||
if s and m then
|
||||
self:setResult(m)
|
||||
@ -351,7 +376,7 @@ function page:executeStatement(statement)
|
||||
end
|
||||
end
|
||||
|
||||
local args = { ... }
|
||||
local args = Util.parse(...)
|
||||
if args[1] then
|
||||
command = 'args[1]'
|
||||
sandboxEnv.args = args
|
||||
|
@ -1,14 +1,11 @@
|
||||
_G.requireInjector(_ENV)
|
||||
|
||||
local Config = require('config')
|
||||
local Event = require('event')
|
||||
local Socket = require('socket')
|
||||
local UI = require('ui')
|
||||
local Util = require('util')
|
||||
local Config = require('opus.config')
|
||||
local Event = require('opus.event')
|
||||
local Socket = require('opus.socket')
|
||||
local UI = require('opus.ui')
|
||||
local Util = require('opus.util')
|
||||
|
||||
local colors = _G.colors
|
||||
local device = _G.device
|
||||
local multishell = _ENV.multishell
|
||||
local network = _G.network
|
||||
local os = _G.os
|
||||
local shell = _ENV.shell
|
||||
@ -17,16 +14,17 @@ UI:configure('Network', ...)
|
||||
|
||||
local gridColumns = {
|
||||
{ heading = 'Label', key = 'label' },
|
||||
{ heading = 'Dist', key = 'distance' },
|
||||
{ heading = 'Dist', key = 'distance', align = 'right' },
|
||||
{ heading = 'Status', key = 'status' },
|
||||
}
|
||||
|
||||
local trusted = Util.readTable('usr/.known_hosts')
|
||||
local config = Config.load('network', { })
|
||||
|
||||
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, align = 'right' })
|
||||
end
|
||||
if UI.term.width >= 40 then
|
||||
table.insert(gridColumns, { heading = 'Uptime', key = 'uptime', align = 'right' })
|
||||
end
|
||||
|
||||
local page = UI.Page {
|
||||
@ -35,22 +33,19 @@ local page = UI.Page {
|
||||
{ text = 'Connect', dropdown = {
|
||||
{ text = 'Telnet t', event = 'telnet' },
|
||||
{ text = 'VNC v', event = 'vnc' },
|
||||
UI.MenuBar.spacer,
|
||||
{ spacer = true },
|
||||
{ text = 'Reboot r', event = 'reboot' },
|
||||
} },
|
||||
--{ text = 'Chat', event = 'chat' },
|
||||
{ text = 'Trust', dropdown = {
|
||||
{ text = 'Establish', event = 'trust' },
|
||||
{ text = 'Remove', event = 'untrust' },
|
||||
} },
|
||||
{ text = 'Help', event = 'help', noCheck = true },
|
||||
{
|
||||
text = '\206',
|
||||
text = '\187',
|
||||
x = -3,
|
||||
dropdown = {
|
||||
{ text = 'Show all', event = 'show_all', noCheck = true },
|
||||
UI.MenuBar.spacer,
|
||||
{ text = 'Show trusted', event = 'show_trusted', noCheck = true },
|
||||
{ text = 'Port Status', event = 'ports', modem = true },
|
||||
{ spacer = true },
|
||||
{ text = 'Help', event = 'help', noCheck = true },
|
||||
},
|
||||
},
|
||||
},
|
||||
@ -62,12 +57,53 @@ local page = UI.Page {
|
||||
sortColumn = 'label',
|
||||
autospace = true,
|
||||
},
|
||||
ports = UI.SlideOut {
|
||||
titleBar = UI.TitleBar {
|
||||
title = 'Ports',
|
||||
event = 'ports_hide',
|
||||
},
|
||||
grid = UI.ScrollingGrid {
|
||||
y = 2,
|
||||
columns = {
|
||||
{ heading = 'Port', key = 'port' },
|
||||
{ heading = 'State', key = 'state' },
|
||||
{ heading = 'Connection', key = 'connection' },
|
||||
},
|
||||
sortColumn = 'port',
|
||||
autospace = true,
|
||||
},
|
||||
},
|
||||
help = UI.SlideOut {
|
||||
backgroundColor = colors.cyan,
|
||||
x = 5, ex = -5, height = 8, y = -8,
|
||||
titleBar = UI.TitleBar {
|
||||
title = 'Network Help',
|
||||
event = 'slide_hide',
|
||||
},
|
||||
text = UI.TextArea {
|
||||
x = 2, y = 2,
|
||||
backgroundColor = colors.cyan,
|
||||
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.
|
||||
|
||||
This only needs to be done once.
|
||||
]],
|
||||
},
|
||||
accelerators = {
|
||||
q = 'slide_hide',
|
||||
}
|
||||
},
|
||||
notification = UI.Notification { },
|
||||
accelerators = {
|
||||
t = 'telnet',
|
||||
v = 'vnc',
|
||||
r = 'reboot',
|
||||
q = 'quit',
|
||||
[ 'control-q' ] = 'quit',
|
||||
c = 'clear',
|
||||
},
|
||||
}
|
||||
@ -91,23 +127,71 @@ local function sendCommand(host, command)
|
||||
end
|
||||
end
|
||||
|
||||
function page.ports:eventHandler(event)
|
||||
if event.type == 'grid_select' then
|
||||
shell.openForegroundTab('Sniff ' .. event.selected.port)
|
||||
end
|
||||
return UI.SlideOut.eventHandler(self, event)
|
||||
end
|
||||
|
||||
function page.ports.grid:update()
|
||||
local transport = network:getTransport()
|
||||
|
||||
local function findConnection(port)
|
||||
if transport then
|
||||
for _,socket in pairs(transport.sockets) do
|
||||
if socket.sport == port then
|
||||
return socket
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
local connections = { }
|
||||
|
||||
pcall(function() -- guard against modem removal
|
||||
if device.wireless_modem then
|
||||
for i = 0, 65535 do
|
||||
if device.wireless_modem.isOpen(i) then
|
||||
local conn = {
|
||||
port = i
|
||||
}
|
||||
local socket = findConnection(i)
|
||||
if socket then
|
||||
conn.state = 'CONNECTED'
|
||||
local host = socket.dhost
|
||||
if network[host] then
|
||||
host = network[host].label
|
||||
end
|
||||
conn.connection = host .. ':' .. socket.dport
|
||||
else
|
||||
conn.state = 'LISTEN'
|
||||
end
|
||||
table.insert(connections, conn)
|
||||
end
|
||||
end
|
||||
end
|
||||
end)
|
||||
|
||||
self.values = connections
|
||||
UI.Grid.update(self)
|
||||
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,
|
||||
})
|
||||
shell.openForegroundTab('telnet ' .. t.id)
|
||||
|
||||
elseif event.type == 'vnc' then
|
||||
multishell.openTab({
|
||||
path = 'sys/apps/vnc.lua',
|
||||
focused = true,
|
||||
args = { t.id },
|
||||
shell.openForegroundTab('vnc.lua ' .. t.id)
|
||||
os.queueEvent('overview_shortcut', {
|
||||
title = t.label,
|
||||
category = "VNC",
|
||||
icon = "\010\030 \009\009\031e\\\031 \031e/\031dn\010\030 \009\009 \031e\\/\031 \031bc",
|
||||
run = "vnc.lua " .. t.id,
|
||||
})
|
||||
|
||||
elseif event.type == 'clear' then
|
||||
Util.clear(network)
|
||||
page.grid:update()
|
||||
@ -116,18 +200,6 @@ function page:eventHandler(event)
|
||||
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 == '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')
|
||||
|
||||
@ -135,32 +207,23 @@ function page:eventHandler(event)
|
||||
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:
|
||||
self.help:show()
|
||||
|
||||
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.
|
||||
elseif event.type == 'ports' then
|
||||
self.ports.grid:update()
|
||||
self.ports:show()
|
||||
|
||||
This only needs to be done once.
|
||||
]],
|
||||
},
|
||||
accelerators = {
|
||||
q = 'cancel',
|
||||
}
|
||||
})
|
||||
self.portsHandler = Event.onInterval(3, function()
|
||||
self.ports.grid:update()
|
||||
self.ports.grid:draw()
|
||||
self:sync()
|
||||
end)
|
||||
|
||||
elseif event.type == 'show_all' then
|
||||
config.showTrusted = false
|
||||
self.grid:setValues(network)
|
||||
Config.update('network', config)
|
||||
elseif event.type == 'ports_hide' then
|
||||
Event.off(self.portsHandler)
|
||||
self.ports:hide()
|
||||
|
||||
elseif event.type == 'show_trusted' then
|
||||
config.showTrusted = true
|
||||
@ -174,16 +237,15 @@ 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]
|
||||
if menuItem.modem then
|
||||
return not not device.wireless_modem
|
||||
end
|
||||
return menuItem.noCheck or not not t
|
||||
end
|
||||
|
||||
function page.grid:getRowTextColor(row, selected)
|
||||
if not row.active then
|
||||
return colors.orange
|
||||
return colors.lightGray
|
||||
end
|
||||
return UI.Grid.getRowTextColor(self, row, selected)
|
||||
end
|
||||
@ -193,31 +255,23 @@ function page.grid:getDisplayValues(row)
|
||||
if row.uptime then
|
||||
if row.uptime < 60 then
|
||||
row.uptime = string.format("%ds", math.floor(row.uptime))
|
||||
elseif row.uptime < 3600 then
|
||||
row.uptime = string.format("%sm", math.floor(row.uptime / 60))
|
||||
else
|
||||
row.uptime = string.format("%sm", math.floor(row.uptime/6)/10)
|
||||
row.uptime = string.format("%sh", math.floor(row.uptime / 3600))
|
||||
end
|
||||
end
|
||||
if row.fuel then
|
||||
row.fuel = Util.toBytes(row.fuel)
|
||||
row.fuel = row.fuel > 0 and Util.toBytes(row.fuel) or ''
|
||||
end
|
||||
if row.distance then
|
||||
row.distance = Util.round(row.distance, 1)
|
||||
row.distance = Util.toBytes(Util.round(row.distance, 1))
|
||||
end
|
||||
return row
|
||||
end
|
||||
|
||||
Event.onInterval(1, function()
|
||||
local t = { }
|
||||
if config.showTrusted then
|
||||
for k,v in pairs(network) do
|
||||
if trusted[k] then
|
||||
t[k] = v
|
||||
end
|
||||
end
|
||||
page.grid:setValues(t)
|
||||
else
|
||||
page.grid:update()
|
||||
end
|
||||
page.grid:update()
|
||||
page.grid:draw()
|
||||
page:sync()
|
||||
end)
|
||||
|
@ -1,18 +1,18 @@
|
||||
_G.requireInjector(_ENV)
|
||||
|
||||
local class = require('class')
|
||||
local Config = require('config')
|
||||
local Event = require('event')
|
||||
local FileUI = require('ui.fileui')
|
||||
local NFT = require('nft')
|
||||
local Packages = require('packages')
|
||||
local SHA1 = require('sha1')
|
||||
local Tween = require('ui.tween')
|
||||
local UI = require('ui')
|
||||
local Util = require('util')
|
||||
local Alt = require('opus.alternate')
|
||||
local class = require('opus.class')
|
||||
local Config = require('opus.config')
|
||||
local Event = require('opus.event')
|
||||
local NFT = require('opus.nft')
|
||||
local Packages = require('opus.packages')
|
||||
local SHA = require('opus.crypto.sha2')
|
||||
local Tween = require('opus.ui.tween')
|
||||
local UI = require('opus.ui')
|
||||
local Util = require('opus.util')
|
||||
|
||||
local colors = _G.colors
|
||||
local device = _G.device
|
||||
local fs = _G.fs
|
||||
local os = _G.os
|
||||
local pocket = _G.pocket
|
||||
local shell = _ENV.shell
|
||||
local term = _G.term
|
||||
@ -23,6 +23,9 @@ if not _ENV.multishell then
|
||||
end
|
||||
|
||||
local REGISTRY_DIR = 'usr/.registry'
|
||||
local DEFAULT_ICON = NFT.parse("\0308\0317\153\153\153\153\153\
|
||||
\0307\0318\153\153\153\153\153\
|
||||
\0308\0317\153\153\153\153\153")
|
||||
|
||||
UI:configure('Overview', ...)
|
||||
|
||||
@ -32,85 +35,21 @@ local config = {
|
||||
}
|
||||
Config.load('Overview', config)
|
||||
|
||||
local applications = { }
|
||||
local extSupport = Util.getVersion() >= 1.76
|
||||
|
||||
local function loadApplications()
|
||||
local requirements = {
|
||||
turtle = not not turtle,
|
||||
advancedTurtle = turtle and term.isColor(),
|
||||
advanced = term.isColor(),
|
||||
pocket = not not pocket,
|
||||
advancedPocket = pocket and term.isColor(),
|
||||
advancedComputer = not turtle and not pocket and term.isColor(),
|
||||
}
|
||||
|
||||
applications = Util.readTable('sys/etc/app.db')
|
||||
|
||||
for dir in pairs(Packages:installed()) do
|
||||
local path = fs.combine('packages/' .. dir, 'etc/apps')
|
||||
if fs.exists(path) then
|
||||
local dbs = fs.list(path)
|
||||
for _, db in pairs(dbs) do
|
||||
local apps = Util.readTable(fs.combine(path, db)) or { }
|
||||
Util.merge(applications, apps)
|
||||
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
|
||||
|
||||
if a.requires then
|
||||
return requirements[a.requires]
|
||||
end
|
||||
|
||||
return true -- Util.startsWith(a.run, 'http') or shell.resolveProgram(a.run)
|
||||
end)
|
||||
end
|
||||
|
||||
loadApplications()
|
||||
|
||||
local defaultIcon = NFT.parse("\03180\031711\03180\
|
||||
\031800\03171\03180\
|
||||
\03171\031800\03171")
|
||||
local applications = { }
|
||||
local buttons = { }
|
||||
|
||||
local sx, sy = term.current().getSize()
|
||||
local maxRecent = math.ceil(sx * sy / 62)
|
||||
|
||||
local function elipse(s, len)
|
||||
local function ellipsis(s, len)
|
||||
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
|
||||
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
|
||||
|
||||
@ -136,7 +75,7 @@ function UI.VerticalTabBar:setParent()
|
||||
self.x = 1
|
||||
self.width = 8
|
||||
self.height = nil
|
||||
self.ey = -1
|
||||
self.ey = -2
|
||||
UI.TabBar.setParent(self)
|
||||
for k,c in pairs(self.children) do
|
||||
c.x = 1
|
||||
@ -149,20 +88,62 @@ end
|
||||
|
||||
local cx = 9
|
||||
local cy = 1
|
||||
if sx < 30 then
|
||||
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,
|
||||
},
|
||||
tray = UI.Window {
|
||||
y = -1, width = 8,
|
||||
backgroundColor = colors.lightGray,
|
||||
newApp = UI.Button {
|
||||
text = '+', event = 'new',
|
||||
},
|
||||
--[[
|
||||
volume = UI.Button {
|
||||
x = 3,
|
||||
text = '\15', event = 'volume',
|
||||
},]]
|
||||
},
|
||||
editor = UI.SlideOut {
|
||||
y = -12, height = 12,
|
||||
backgroundColor = colors.cyan,
|
||||
titleBar = UI.TitleBar {
|
||||
title = 'Edit Application',
|
||||
event = 'slide_hide',
|
||||
},
|
||||
form = UI.Form {
|
||||
y = 2, ey = -2,
|
||||
[1] = UI.TextEntry {
|
||||
formLabel = 'Title', formKey = 'title', limit = 11, help = 'Application title',
|
||||
required = true,
|
||||
},
|
||||
[2] = UI.TextEntry {
|
||||
formLabel = 'Run', formKey = 'run', limit = 100, help = 'Full path to application',
|
||||
required = true,
|
||||
},
|
||||
[3] = UI.TextEntry {
|
||||
formLabel = 'Category', formKey = 'category', limit = 11, help = 'Category of application',
|
||||
required = true,
|
||||
},
|
||||
iconFile = UI.TextEntry {
|
||||
x = 11, ex = -12, y = 7,
|
||||
limit = 128, help = 'Path to icon file',
|
||||
shadowText = 'Path to icon file',
|
||||
},
|
||||
loadIcon = UI.Button {
|
||||
x = 11, y = 9,
|
||||
text = 'Load', event = 'loadIcon', help = 'Load icon file',
|
||||
},
|
||||
image = UI.NftImage {
|
||||
backgroundColor = colors.black,
|
||||
y = 7, x = 2, height = 3, width = 8,
|
||||
},
|
||||
},
|
||||
notification = UI.Notification(),
|
||||
statusBar = UI.StatusBar(),
|
||||
},
|
||||
notification = UI.Notification(),
|
||||
accelerators = {
|
||||
r = 'refresh',
|
||||
@ -175,8 +156,75 @@ local page = UI.Page {
|
||||
},
|
||||
}
|
||||
|
||||
if extSupport then
|
||||
page.container.backgroundColor = colors.black
|
||||
local function loadApplications()
|
||||
local requirements = {
|
||||
turtle = not not turtle,
|
||||
advancedTurtle = turtle and term.isColor(),
|
||||
advanced = term.isColor(),
|
||||
pocket = not not pocket,
|
||||
advancedPocket = pocket and term.isColor(),
|
||||
advancedComputer = not turtle and not pocket and term.isColor(),
|
||||
neuralInterface = not not device.neuralInterface,
|
||||
}
|
||||
|
||||
applications = Util.readTable('sys/etc/apps.db')
|
||||
|
||||
for dir in pairs(Packages:installed()) do
|
||||
local path = fs.combine('packages/' .. dir, 'etc/apps.db')
|
||||
if fs.exists(path) then
|
||||
local apps = Util.readTable(path) 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
|
||||
|
||||
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
|
||||
return requirements[a.requires]
|
||||
end
|
||||
|
||||
return true -- Util.startsWith(a.run, 'http') or shell.resolveProgram(a.run)
|
||||
end)
|
||||
|
||||
local categories = { }
|
||||
buttons = { }
|
||||
for _,f in pairs(applications) do
|
||||
if not categories[f.category] then
|
||||
categories[f.category] = true
|
||||
table.insert(buttons, {
|
||||
text = f.category,
|
||||
selected = config.currentCategory == f.category
|
||||
})
|
||||
end
|
||||
end
|
||||
table.sort(buttons, function(a, b) return a.text < b.text end)
|
||||
table.insert(buttons, 1, { text = 'Recent' })
|
||||
|
||||
Util.removeByValue(page.children, page.tabBar)
|
||||
|
||||
page:add {
|
||||
tabBar = UI.VerticalTabBar {
|
||||
buttons = buttons,
|
||||
},
|
||||
}
|
||||
|
||||
--page.tabBar:selectTab(config.currentCategory or 'Apps')
|
||||
page.container:setCategory(config.currentCategory or 'Apps')
|
||||
end
|
||||
|
||||
UI.Icon = class(UI.Window)
|
||||
@ -188,6 +236,7 @@ UI.Icon.defaults = {
|
||||
function UI.Icon:eventHandler(event)
|
||||
if event.type == 'mouse_click' then
|
||||
self:setFocus(self.button)
|
||||
--self:emit({ type = self.button.event, button = self.button })
|
||||
return true
|
||||
elseif event.type == 'mouse_doubleclick' then
|
||||
self:emit({ type = self.button.event, button = self.button })
|
||||
@ -201,7 +250,7 @@ end
|
||||
function page.container:setCategory(categoryName, animate)
|
||||
-- reset the viewport window
|
||||
self.children = { }
|
||||
self.offy = 0
|
||||
self:reset()
|
||||
|
||||
local function filter(it, f)
|
||||
local ot = { }
|
||||
@ -242,10 +291,10 @@ function page.container:setCategory(categoryName, animate)
|
||||
icon = parseIcon(program.icon)
|
||||
end
|
||||
if not icon then
|
||||
icon = defaultIcon
|
||||
icon = DEFAULT_ICON
|
||||
end
|
||||
|
||||
local title = elipse(program.title, 8)
|
||||
local title = ellipsis(program.title, 8)
|
||||
|
||||
local width = math.max(icon.width + 2, #title + 2)
|
||||
table.insert(self.children, UI.Icon({
|
||||
@ -283,10 +332,10 @@ function page.container:setCategory(categoryName, animate)
|
||||
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)
|
||||
child.y = math.random(1, self.height - 3)
|
||||
elseif r == 2 then
|
||||
child.x = self.width
|
||||
child.y = self.height
|
||||
child.y = self.height - 3
|
||||
elseif r == 3 then
|
||||
child.x = math.floor(self.width / 2)
|
||||
child.y = math.floor(self.height / 2)
|
||||
@ -298,7 +347,7 @@ function page.container:setCategory(categoryName, animate)
|
||||
child.y = row
|
||||
if k == #self.children then
|
||||
child.x = self.width
|
||||
child.y = self.height
|
||||
child.y = self.height - 3
|
||||
end
|
||||
end
|
||||
child.tween = Tween.new(6, child, { x = col, y = row }, 'linear')
|
||||
@ -318,10 +367,10 @@ function page.container:setCategory(categoryName, animate)
|
||||
end
|
||||
|
||||
self:initChildren()
|
||||
if animate then -- need to fix transitions under layers
|
||||
local function transition(args)
|
||||
if animate then
|
||||
local function transition()
|
||||
local i = 1
|
||||
return function(device)
|
||||
return function()
|
||||
self:clear()
|
||||
for _,child in pairs(self.children) do
|
||||
child.tween:update(1)
|
||||
@ -329,7 +378,6 @@ function page.container:setCategory(categoryName, animate)
|
||||
child.y = math.floor(child.y)
|
||||
child:draw()
|
||||
end
|
||||
args.canvas:blit(device, args, args)
|
||||
i = i + 1
|
||||
return i < 7
|
||||
end
|
||||
@ -373,13 +421,13 @@ function page:eventHandler(event)
|
||||
shell.switchTab(shell.openTab(event.button.app.run))
|
||||
|
||||
elseif event.type == 'shell' then
|
||||
shell.switchTab(shell.openTab('sys/apps/shell'))
|
||||
shell.switchTab(shell.openTab(Alt.get('shell')))
|
||||
|
||||
elseif event.type == 'lua' then
|
||||
shell.switchTab(shell.openTab('sys/apps/Lua.lua'))
|
||||
shell.switchTab(shell.openTab(Alt.get('lua')))
|
||||
|
||||
elseif event.type == 'files' then
|
||||
shell.switchTab(shell.openTab('sys/apps/Files.lua'))
|
||||
shell.switchTab(shell.openTab(Alt.get('files')))
|
||||
|
||||
elseif event.type == 'focus_change' then
|
||||
if event.focused.parent.UIElement == 'Icon' then
|
||||
@ -395,9 +443,13 @@ function page:eventHandler(event)
|
||||
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)
|
||||
if focused.app.filename then
|
||||
fs.delete(focused.app.filename)
|
||||
else
|
||||
focused.app.disabled = true
|
||||
local filename = focused.app.filename or fs.combine(REGISTRY_DIR, focused.app.key)
|
||||
Util.writeTable(filename, focused.app)
|
||||
end
|
||||
loadApplications()
|
||||
page:refresh()
|
||||
page:draw()
|
||||
@ -409,12 +461,12 @@ function page:eventHandler(event)
|
||||
if config.currentCategory ~= 'Recent' then
|
||||
category = config.currentCategory or 'Apps'
|
||||
end
|
||||
UI:setPage('editor', { category = category })
|
||||
self.editor:show({ category = category })
|
||||
|
||||
elseif event.type == 'edit' then
|
||||
local focused = page:getFocused()
|
||||
if focused.app then
|
||||
UI:setPage('editor', focused.app)
|
||||
self.editor:show(focused.app)
|
||||
end
|
||||
|
||||
else
|
||||
@ -423,40 +475,7 @@ function page:eventHandler(event)
|
||||
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 = '',
|
||||
}
|
||||
|
||||
function editor:enable(app)
|
||||
function page.editor:show(app)
|
||||
if app then
|
||||
self.form:setValues(app)
|
||||
|
||||
@ -469,96 +488,101 @@ function editor:enable(app)
|
||||
end
|
||||
self.form.image:setImage(icon)
|
||||
end
|
||||
UI.Dialog.enable(self)
|
||||
UI.SlideOut.show(self)
|
||||
self:focusFirst()
|
||||
end
|
||||
|
||||
function editor.form.image:draw()
|
||||
function page.editor.form.image:draw()
|
||||
self:clear()
|
||||
UI.NftImage.draw(self)
|
||||
end
|
||||
|
||||
function editor:updateApplications(app)
|
||||
function page.editor:updateApplications(app)
|
||||
if not app.key then
|
||||
app.key = SHA1.sha1(app.title)
|
||||
app.key = SHA.compute(app.title)
|
||||
end
|
||||
local filename = app.filename or fs.combine(REGISTRY_DIR, app.key)
|
||||
Util.writeTable(filename, app)
|
||||
loadApplications()
|
||||
end
|
||||
|
||||
function editor:eventHandler(event)
|
||||
function page.editor:eventHandler(event)
|
||||
if event.type == 'form_cancel' or event.type == 'cancel' then
|
||||
UI:setPreviousPage()
|
||||
self:hide()
|
||||
|
||||
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('Must be an NFT image - 3 rows, 8 cols max')
|
||||
end
|
||||
local icon, m = parseIcon(iconLines)
|
||||
if not icon then
|
||||
error(m)
|
||||
end
|
||||
if extSupport then
|
||||
self.form.values.iconExt = iconLines
|
||||
else
|
||||
self.form.values.icon = iconLines
|
||||
end
|
||||
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
|
||||
local s, m = pcall(function()
|
||||
local iconLines = Util.readFile(self.form.iconFile.value)
|
||||
if not iconLines then
|
||||
error('Must be an NFT image - 3 rows, 8 cols max')
|
||||
end
|
||||
local icon, m = parseIcon(iconLines)
|
||||
if not icon then
|
||||
error(m)
|
||||
end
|
||||
if extSupport then
|
||||
self.form.values.iconExt = iconLines
|
||||
else
|
||||
self.form.values.icon = iconLines
|
||||
end
|
||||
self.form.image:setImage(icon)
|
||||
self.form.image:draw()
|
||||
end)
|
||||
if not s and m then
|
||||
local msg = m:gsub('.*: (.*)', '%1')
|
||||
self.notification:error(msg)
|
||||
end
|
||||
|
||||
elseif event.type == 'form_invalid' then
|
||||
page.notification:error(event.message)
|
||||
self.notification:error(event.message)
|
||||
|
||||
elseif event.type == 'form_complete' then
|
||||
local values = self.form.values
|
||||
UI:setPreviousPage()
|
||||
self:hide()
|
||||
self:updateApplications(values)
|
||||
page:refresh()
|
||||
page:draw()
|
||||
--page:refresh()
|
||||
--page:draw()
|
||||
config.currentCategory = values.category
|
||||
Config.update('Overview', config)
|
||||
os.queueEvent('overview_refresh')
|
||||
else
|
||||
return UI.Dialog.eventHandler(self, event)
|
||||
return UI.SlideOut.eventHandler(self, event)
|
||||
end
|
||||
return true
|
||||
end
|
||||
|
||||
UI:setPages({
|
||||
editor = editor,
|
||||
main = page,
|
||||
})
|
||||
|
||||
Event.on('os_register_app', function()
|
||||
local function reload()
|
||||
loadApplications()
|
||||
page:refresh()
|
||||
page:draw()
|
||||
page:sync()
|
||||
end
|
||||
|
||||
Event.on('overview_shortcut', function(_, app)
|
||||
if not app.key then
|
||||
app.key = SHA.compute(app.title)
|
||||
end
|
||||
local filename = app.filename or fs.combine(REGISTRY_DIR, app.key)
|
||||
if not fs.exists(filename) then
|
||||
Util.writeTable(filename, app)
|
||||
reload()
|
||||
end
|
||||
end)
|
||||
|
||||
page.tabBar:selectTab(config.currentCategory or 'Apps')
|
||||
page.container:setCategory(config.currentCategory or 'Apps')
|
||||
Event.on('overview_refresh', function()
|
||||
reload()
|
||||
end)
|
||||
|
||||
loadApplications()
|
||||
|
||||
UI:setPage(page)
|
||||
|
||||
UI:pullEvents()
|
||||
|
@ -1,18 +1,16 @@
|
||||
_G.requireInjector(_ENV)
|
||||
|
||||
local Ansi = require('ansi')
|
||||
local Packages = require('packages')
|
||||
local UI = require('ui')
|
||||
local Ansi = require('opus.ansi')
|
||||
local Packages = require('opus.packages')
|
||||
local UI = require('opus.ui')
|
||||
local Util = require('opus.util')
|
||||
|
||||
local colors = _G.colors
|
||||
local shell = _ENV.shell
|
||||
local term = _G.term
|
||||
|
||||
UI:configure('PackageManager', ...)
|
||||
|
||||
local page = UI.Page {
|
||||
grid = UI.ScrollingGrid {
|
||||
y = 2, ey = 7, x = 2, ex = -6,
|
||||
x = 2, ex = 14, y = 2, ey = -5,
|
||||
values = { },
|
||||
columns = {
|
||||
{ heading = 'Package', key = 'name' },
|
||||
@ -22,42 +20,79 @@ local page = UI.Page {
|
||||
help = 'Select a package',
|
||||
},
|
||||
add = UI.Button {
|
||||
x = -4, y = 4,
|
||||
text = '+',
|
||||
x = 2, y = -3,
|
||||
text = 'Install',
|
||||
event = 'action',
|
||||
help = 'Install or update',
|
||||
},
|
||||
remove = UI.Button {
|
||||
x = -4, y = 6,
|
||||
text = '-',
|
||||
x = 12, y = -3,
|
||||
text = 'Remove ',
|
||||
event = 'action',
|
||||
operation = 'uninstall',
|
||||
operationText = 'Remove',
|
||||
help = 'Remove',
|
||||
},
|
||||
description = UI.TextArea {
|
||||
x = 2, y = 9, ey = -2,
|
||||
--backgroundColor = colors.white,
|
||||
updateall = UI.Button {
|
||||
ex = -2, y = -3, width = 12,
|
||||
text = 'Update All',
|
||||
event = 'updateall',
|
||||
help = 'Update all installed packages',
|
||||
},
|
||||
description = UI.TextArea {
|
||||
x = 16, y = 3, ey = -5,
|
||||
marginRight = 0, marginLeft = 0,
|
||||
},
|
||||
statusBar = UI.StatusBar { },
|
||||
action = UI.SlideOut {
|
||||
backgroundColor = colors.cyan,
|
||||
titleBar = UI.TitleBar {
|
||||
event = 'hide-action',
|
||||
},
|
||||
button = UI.Button {
|
||||
ex = -4, y = 4, width = 7,
|
||||
text = 'Begin', event = 'begin',
|
||||
x = -10, y = 3,
|
||||
text = ' Begin ', event = 'begin',
|
||||
},
|
||||
output = UI.Embedded {
|
||||
y = 6, ey = -2, x = 2, ex = -2,
|
||||
},
|
||||
statusBar = UI.StatusBar {
|
||||
backgroundColor = colors.cyan,
|
||||
y = 5, ey = -2, x = 2, ex = -2,
|
||||
visible = true,
|
||||
},
|
||||
},
|
||||
statusBar = UI.StatusBar { },
|
||||
accelerators = {
|
||||
[ 'control-q' ] = 'quit',
|
||||
},
|
||||
}
|
||||
|
||||
function page:loadPackages()
|
||||
self.grid.values = { }
|
||||
self.statusBar:setStatus('Downloading...')
|
||||
self:sync()
|
||||
|
||||
for k in pairs(Packages:list()) do
|
||||
local manifest = Packages:getManifest(k)
|
||||
if not manifest then
|
||||
manifest = {
|
||||
invalid = true,
|
||||
description = 'Unable to download manifest',
|
||||
title = '',
|
||||
}
|
||||
end
|
||||
table.insert(self.grid.values, {
|
||||
installed = not not Packages:isInstalled(k),
|
||||
name = k,
|
||||
manifest = manifest,
|
||||
})
|
||||
end
|
||||
self.grid:update()
|
||||
self.grid:setIndex(1)
|
||||
self.grid:emit({
|
||||
type = 'grid_focus_row',
|
||||
selected = self.grid:getSelected(),
|
||||
element = self.grid,
|
||||
})
|
||||
self.statusBar:setStatus('Updated packages')
|
||||
end
|
||||
|
||||
function page.grid:getRowTextColor(row, selected)
|
||||
if row.installed then
|
||||
return colors.yellow
|
||||
@ -66,25 +101,26 @@ function page.grid:getRowTextColor(row, selected)
|
||||
end
|
||||
|
||||
function page.action:show()
|
||||
self.output.win:clear()
|
||||
UI.SlideOut.show(self)
|
||||
self.output:draw()
|
||||
self.output.win.redraw()
|
||||
--self.output:draw()
|
||||
--self.output.win.redraw()
|
||||
end
|
||||
|
||||
function page:run(operation, name)
|
||||
local oterm = term.redirect(self.action.output.win)
|
||||
self.action.output:clear()
|
||||
local cmd = string.format('package %s %s', operation, name)
|
||||
--for _ = 1, 3 do
|
||||
-- print(cmd .. '\n')
|
||||
-- os.sleep(1)
|
||||
--end
|
||||
term.setCursorPos(1, 1)
|
||||
term.clear()
|
||||
term.setTextColor(colors.yellow)
|
||||
print(cmd .. '\n')
|
||||
term.setTextColor(colors.white)
|
||||
shell.run(cmd)
|
||||
local s, m = Util.run(_ENV, '/sys/apps/package.lua', operation, name)
|
||||
|
||||
if not s and m then
|
||||
_G.printError(m)
|
||||
end
|
||||
term.redirect(oterm)
|
||||
self.action.output:draw()
|
||||
end
|
||||
@ -92,6 +128,10 @@ end
|
||||
function page:updateSelection(selected)
|
||||
self.add.operation = selected.installed and 'update' or 'install'
|
||||
self.add.operationText = selected.installed and 'Update' or 'Install'
|
||||
self.add.text = selected.installed and 'Update' or 'Install'
|
||||
self.remove.inactive = not selected.installed
|
||||
self.add:draw()
|
||||
self.remove:draw()
|
||||
end
|
||||
|
||||
function page:eventHandler(event)
|
||||
@ -107,13 +147,20 @@ function page:eventHandler(event)
|
||||
self.description:draw()
|
||||
self:updateSelection(event.selected)
|
||||
|
||||
elseif event.type == 'updateall' then
|
||||
self.operation = 'updateall'
|
||||
self.action.button.text = ' Begin '
|
||||
self.action.button.event = 'begin'
|
||||
self.action.titleBar.title = 'Update All'
|
||||
self.action:show()
|
||||
|
||||
elseif event.type == 'action' then
|
||||
local selected = self.grid:getSelected()
|
||||
if selected then
|
||||
self.operation = event.button.operation
|
||||
self.action.button.text = event.button.operationText
|
||||
self.action.titleBar.title = selected.manifest.title
|
||||
self.action.button.text = 'Begin'
|
||||
self.action.button.text = ' Begin '
|
||||
self.action.button.event = 'begin'
|
||||
self.action:show()
|
||||
end
|
||||
@ -122,12 +169,17 @@ function page:eventHandler(event)
|
||||
self.action:hide()
|
||||
|
||||
elseif event.type == 'begin' then
|
||||
local selected = self.grid:getSelected()
|
||||
self:run(self.operation, selected.name)
|
||||
selected.installed = Packages:isInstalled(selected.name)
|
||||
if self.operation == 'updateall' then
|
||||
self:run(self.operation, '')
|
||||
else
|
||||
local selected = self.grid:getSelected()
|
||||
self:run(self.operation, selected.name)
|
||||
selected.installed = Packages:isInstalled(selected.name)
|
||||
|
||||
self:updateSelection(selected)
|
||||
self.action.button.text = 'Done'
|
||||
self:updateSelection(selected)
|
||||
end
|
||||
|
||||
self.action.button.text = ' Done '
|
||||
self.action.button.event = 'hide-action'
|
||||
self.action.button:draw()
|
||||
|
||||
@ -137,22 +189,11 @@ function page:eventHandler(event)
|
||||
UI.Page.eventHandler(self, event)
|
||||
end
|
||||
|
||||
for k in pairs(Packages:list()) do
|
||||
local manifest = Packages:getManifest(k)
|
||||
if not manifest then
|
||||
manifest = {
|
||||
invalid = true,
|
||||
description = 'Unable to download manifest',
|
||||
title = '',
|
||||
}
|
||||
end
|
||||
table.insert(page.grid.values, {
|
||||
installed = not not Packages:isInstalled(k),
|
||||
name = k,
|
||||
manifest = manifest,
|
||||
})
|
||||
end
|
||||
page.grid:update()
|
||||
|
||||
UI:setPage(page)
|
||||
page.statusBar:setStatus('Downloading...')
|
||||
page:sync()
|
||||
Packages:downloadList()
|
||||
page:loadPackages()
|
||||
page:sync()
|
||||
|
||||
UI:pullEvents()
|
||||
|
30
sys/apps/ShellLauncher.lua
Normal file
30
sys/apps/ShellLauncher.lua
Normal file
@ -0,0 +1,30 @@
|
||||
local Alt = require('opus.alternate')
|
||||
|
||||
local kernel = _G.kernel
|
||||
local os = _G.os
|
||||
local shell = _ENV.shell
|
||||
|
||||
local launcherTab = kernel.getCurrent()
|
||||
launcherTab.noFocus = true
|
||||
|
||||
kernel.hook('kernel_focus', function(_, eventData)
|
||||
local focusTab = eventData and eventData[1]
|
||||
if focusTab == launcherTab.uid then
|
||||
local previousTab = eventData[2]
|
||||
local nextTab = launcherTab
|
||||
if not previousTab then
|
||||
for _, v in pairs(kernel.routines) do
|
||||
if not v.hidden and v.uid > nextTab.uid then
|
||||
nextTab = v
|
||||
end
|
||||
end
|
||||
end
|
||||
if nextTab == launcherTab then
|
||||
shell.switchTab(shell.openTab(Alt.get('shell')))
|
||||
else
|
||||
shell.switchTab(nextTab.uid)
|
||||
end
|
||||
end
|
||||
end)
|
||||
|
||||
os.pullEventRaw('kernel_halt')
|
387
sys/apps/Sniff.lua
Normal file
387
sys/apps/Sniff.lua
Normal file
@ -0,0 +1,387 @@
|
||||
local UI = require('opus.ui')
|
||||
local Event = require('opus.event')
|
||||
local Util = require('opus.util')
|
||||
|
||||
local colors = _G.colors
|
||||
local device = _G.device
|
||||
local textutils = _G.textutils
|
||||
local peripheral = _G.peripheral
|
||||
local multishell = _ENV.multishell
|
||||
|
||||
local gridColumns = {}
|
||||
table.insert(gridColumns, { heading = '#', key = 'id', width = 5, align = 'right' })
|
||||
table.insert(gridColumns, { heading = 'Port', key = 'portid', width = 5, align = 'right' })
|
||||
table.insert(gridColumns, { heading = 'Reply', key = 'replyid', width = 5, align = 'right' })
|
||||
if UI.defaultDevice.width > 50 then
|
||||
table.insert(gridColumns, { heading = 'Dist', key = 'distance', width = 6, align = 'right' })
|
||||
end
|
||||
table.insert(gridColumns, { heading = 'Msg', key = 'packetStr' })
|
||||
|
||||
local page = UI.Page {
|
||||
paused = false,
|
||||
index = 1,
|
||||
notification = UI.Notification { },
|
||||
accelerators = { ['control-q'] = 'quit' },
|
||||
|
||||
menuBar = UI.MenuBar {
|
||||
buttons = {
|
||||
{ text = 'Pause', event = 'pause_click', name = 'pauseButton' },
|
||||
{ text = 'Clear', event = 'clear_click' },
|
||||
{ text = 'Config', event = 'config_click' },
|
||||
},
|
||||
},
|
||||
|
||||
packetGrid = UI.ScrollingGrid {
|
||||
y = 2,
|
||||
maxPacket = 300,
|
||||
inverseSort = true,
|
||||
sortColumn = 'id',
|
||||
columns = gridColumns,
|
||||
accelerators = { ['space'] = 'pause_click' },
|
||||
},
|
||||
|
||||
configSlide = UI.SlideOut {
|
||||
y = -11,
|
||||
titleBar = UI.TitleBar { title = 'Sniffer Config', event = 'config_close' },
|
||||
accelerators = { ['backspace'] = 'config_close' },
|
||||
configTabs = UI.Tabs {
|
||||
y = 2,
|
||||
filterTab = UI.Tab {
|
||||
tabTitle = 'Filter',
|
||||
filterGridText = UI.Text {
|
||||
x = 2, y = 2,
|
||||
value = 'ID filter',
|
||||
},
|
||||
filterGrid = UI.ScrollingGrid {
|
||||
x = 2, y = 3,
|
||||
width = 10, height = 4,
|
||||
disableHeader = true,
|
||||
columns = {
|
||||
{ key = 'id', width = 5 },
|
||||
},
|
||||
},
|
||||
filterEntry = UI.TextEntry {
|
||||
x = 2, y = 8,
|
||||
width = 7,
|
||||
shadowText = 'ID',
|
||||
limit = 5,
|
||||
accelerators = { enter = 'filter_add' },
|
||||
},
|
||||
filterAdd = UI.Button {
|
||||
x = 10, y = 8,
|
||||
text = '+',
|
||||
event = 'filter_add',
|
||||
},
|
||||
filterAllCheck = UI.Checkbox {
|
||||
x = 14, y = 8,
|
||||
value = false,
|
||||
},
|
||||
filterAddText = UI.Text {
|
||||
x = 18, y = 8,
|
||||
value = "Use ID filter",
|
||||
},
|
||||
rangeText = UI.Text {
|
||||
x = 15, y = 2,
|
||||
value = "Distance filter",
|
||||
},
|
||||
rangeEntry = UI.TextEntry {
|
||||
x = 15, y = 3,
|
||||
width = 10,
|
||||
limit = 8,
|
||||
shadowText = 'Range',
|
||||
transform = 'number',
|
||||
},
|
||||
},
|
||||
modemTab = UI.Tab {
|
||||
tabTitle = 'Modem',
|
||||
channelGrid = UI.ScrollingGrid {
|
||||
x = 2, y = 2,
|
||||
width = 12, height = 5,
|
||||
autospace = true,
|
||||
columns = {{ heading = 'Open Ports', key = 'port' }},
|
||||
},
|
||||
modemGrid = UI.ScrollingGrid {
|
||||
x = 15, y = 2,
|
||||
ex = -2, height = 5,
|
||||
autospace = true,
|
||||
columns = {
|
||||
{ heading = 'Side', key = 'side' },
|
||||
{ heading = 'Type', key = 'type' },
|
||||
},
|
||||
},
|
||||
channelEntry = UI.TextEntry {
|
||||
x = 2, y = 8,
|
||||
width = 7,
|
||||
shadowText = 'ID',
|
||||
limit = 5,
|
||||
accelerators = { enter = 'channel_add' },
|
||||
},
|
||||
channelAdd = UI.Button {
|
||||
x = 10, y = 8,
|
||||
text = '+',
|
||||
event = 'channel_add',
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
|
||||
packetSlide = UI.SlideOut {
|
||||
titleBar = UI.TitleBar {
|
||||
title = 'Packet Information',
|
||||
event = 'packet_close',
|
||||
},
|
||||
backgroundColor = colors.cyan,
|
||||
accelerators = {
|
||||
['backspace'] = 'packet_close',
|
||||
['left'] = 'prev_packet',
|
||||
['right'] = 'next_packet',
|
||||
},
|
||||
packetMeta = UI.Grid {
|
||||
x = 2, y = 2,
|
||||
ex = 23, height = 4,
|
||||
inactive = true,
|
||||
columns = {
|
||||
{ key = 'text' },
|
||||
{ key = 'value', align = 'right', textColor = colors.yellow },
|
||||
},
|
||||
values = {
|
||||
port = { text = 'Port' },
|
||||
reply = { text = 'Reply' },
|
||||
dist = { text = 'Distance' },
|
||||
}
|
||||
},
|
||||
packetButton = UI.Button {
|
||||
x = 25, y = 5,
|
||||
text = 'Open in Lua',
|
||||
event = 'packet_lua',
|
||||
},
|
||||
packetData = UI.TextArea {
|
||||
y = 7, ey = -1,
|
||||
backgroundColor = colors.black,
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
local filterConfig = page.configSlide.configTabs.filterTab
|
||||
local modemConfig = page.configSlide.configTabs.modemTab
|
||||
|
||||
function filterConfig:eventHandler(event)
|
||||
if event.type == 'filter_add' then
|
||||
local id = tonumber(self.filterEntry.value)
|
||||
if id then self.filterGrid.values[id] = { id = id }
|
||||
self.filterGrid:update()
|
||||
self.filterEntry:reset()
|
||||
self:draw()
|
||||
end
|
||||
|
||||
elseif event.type == 'grid_select' then
|
||||
self.filterGrid.values[event.selected.id] = nil
|
||||
self.filterGrid:update()
|
||||
self.filterGrid:draw()
|
||||
|
||||
else return UI.Tab.eventHandler(self, event)
|
||||
end
|
||||
return true
|
||||
end
|
||||
|
||||
function modemConfig:loadChannel()
|
||||
for chan = 0, 65535 do
|
||||
self.currentModem.openChannels[chan] = self.currentModem.device.isOpen(chan) and { port = chan } or nil
|
||||
end
|
||||
self.channelGrid:setValues(self.currentModem.openChannels)
|
||||
self.currentModem.loaded = true
|
||||
end
|
||||
|
||||
function modemConfig:enable()
|
||||
if not self.currentModem.loaded then
|
||||
self:loadChannel()
|
||||
end
|
||||
|
||||
UI.Tab.enable(self)
|
||||
end
|
||||
|
||||
function modemConfig:eventHandler(event)
|
||||
if event.type == 'channel_add' then
|
||||
local id = tonumber(modemConfig.channelEntry.value)
|
||||
if id then
|
||||
self.currentModem.openChannels[id] = { port = id }
|
||||
self.currentModem.device.open(id)
|
||||
self.channelGrid:setValues(self.currentModem.openChannels)
|
||||
self.channelGrid:update()
|
||||
self.channelEntry:reset()
|
||||
self:draw()
|
||||
end
|
||||
|
||||
elseif event.type == 'grid_select' then
|
||||
if event.element == self.channelGrid then
|
||||
self.currentModem.openChannels[event.selected.port] = nil
|
||||
self.currentModem.device.close(event.selected.port)
|
||||
self.channelGrid:setValues(self.currentModem.openChannels)
|
||||
page.configSlide.configTabs.modemTab.channelGrid:update()
|
||||
page.configSlide.configTabs.modemTab.channelGrid:draw()
|
||||
|
||||
elseif event.element == self.modemGrid then
|
||||
self.currentModem = event.selected
|
||||
page.notification:info("Loading channel list")
|
||||
page:sync()
|
||||
modemConfig:loadChannel()
|
||||
page.notification:success("Now using modem on " .. self.currentModem.side)
|
||||
self.channelGrid:draw()
|
||||
end
|
||||
|
||||
else return UI.Tab.eventHandler(self, event)
|
||||
end
|
||||
return true
|
||||
end
|
||||
|
||||
function page.packetSlide:setPacket(packet)
|
||||
self.currentPacket = packet
|
||||
local p, res = pcall(textutils.serialize, page.packetSlide.currentPacket.message)
|
||||
self.packetData.textColor = p and colors.white or colors.red
|
||||
self.packetData:setText(res)
|
||||
self.packetMeta.values.port.value = page.packetSlide.currentPacket.portid
|
||||
self.packetMeta.values.reply.value = page.packetSlide.currentPacket.replyid
|
||||
self.packetMeta.values.dist.value = Util.round(page.packetSlide.currentPacket.distance, 2)
|
||||
end
|
||||
|
||||
function page.packetSlide:show(packet)
|
||||
self:setPacket(packet)
|
||||
|
||||
UI.SlideOut.show(self)
|
||||
end
|
||||
|
||||
function page.packetSlide:eventHandler(event)
|
||||
if event.type == 'packet_close' then
|
||||
self:hide()
|
||||
page:setFocus(page.packetGrid)
|
||||
|
||||
elseif event.type == 'packet_lua' then
|
||||
multishell.openTab({ path = 'sys/apps/Lua.lua', args = { self.currentPacket.message }, focused = true })
|
||||
|
||||
elseif event.type == 'prev_packet' then
|
||||
local c = self.currentPacket
|
||||
local n = page.packetGrid.values[c.id - 1]
|
||||
if n then
|
||||
self:setPacket(n)
|
||||
self:draw()
|
||||
end
|
||||
|
||||
elseif event.type == 'next_packet' then
|
||||
local c = self.currentPacket
|
||||
local n = page.packetGrid.values[c.id + 1]
|
||||
if n then
|
||||
self:setPacket(n)
|
||||
self:draw()
|
||||
end
|
||||
|
||||
else return UI.SlideOut.eventHandler(self, event)
|
||||
end
|
||||
return true
|
||||
end
|
||||
|
||||
function page.packetGrid:getDisplayValues(row)
|
||||
local row = Util.shallowCopy(row)
|
||||
row.distance = Util.toBytes(Util.round(row.distance), 2)
|
||||
return row
|
||||
end
|
||||
|
||||
function page.packetGrid:addPacket(packet)
|
||||
if not page.paused and (packet.distance <= (filterConfig.rangeEntry.value or math.huge)) and (not filterConfig.filterAllCheck.value or filterConfig.filterGrid.values[packet.portid]) then
|
||||
page.index = page.index + 1
|
||||
local _, res = pcall(textutils.serialize, packet.message)
|
||||
packet.packetStr = res:gsub("\n%s*", "")
|
||||
table.insert(self.values, packet)
|
||||
end
|
||||
if #self.values > self.maxPacket then
|
||||
local t = { }
|
||||
for i = 10, #self.values do
|
||||
t[i - 9] = self.values[i]
|
||||
end
|
||||
self:setValues(t)
|
||||
end
|
||||
|
||||
self:update()
|
||||
self:draw()
|
||||
page:sync()
|
||||
end
|
||||
|
||||
function page:enable()
|
||||
modemConfig.modems = {}
|
||||
peripheral.find('modem', function(side, dev)
|
||||
modemConfig.modems[side] = {
|
||||
type = dev.isWireless() and 'Wireless' or 'Wired',
|
||||
side = side,
|
||||
openChannels = { },
|
||||
device = dev,
|
||||
loaded = false
|
||||
}
|
||||
end)
|
||||
modemConfig.currentModem = device.wireless_modem and
|
||||
modemConfig.modems[device.wireless_modem.side] or
|
||||
device.wired_modem and
|
||||
modemConfig.modems[device.wired_modem.side] or
|
||||
nil
|
||||
|
||||
modemConfig.modemGrid.values = modemConfig.modems
|
||||
modemConfig.modemGrid:update()
|
||||
modemConfig.modemGrid:setSelected(modemConfig.currentModem)
|
||||
|
||||
UI.Page.enable(self)
|
||||
end
|
||||
|
||||
|
||||
function page:eventHandler(event)
|
||||
if event.type == 'pause_click' then
|
||||
self.paused = not self.paused
|
||||
self.menuBar.pauseButton.text = self.paused and 'Resume' or 'Pause'
|
||||
self.notification:success(self.paused and 'Paused' or 'Resumed', 2)
|
||||
self.menuBar:draw()
|
||||
|
||||
elseif event.type == 'clear_click' then
|
||||
self.packetGrid:setValues({ })
|
||||
self.notification:success('Cleared', 2)
|
||||
self.packetGrid:draw()
|
||||
|
||||
elseif event.type == 'config_click' then
|
||||
self.configSlide:show()
|
||||
self:setFocus(filterConfig.filterEntry)
|
||||
|
||||
elseif event.type == 'config_close' then
|
||||
self.configSlide:hide()
|
||||
self:setFocus(self.packetGrid)
|
||||
|
||||
elseif event.type == 'grid_select' then
|
||||
self.packetSlide:show(event.selected)
|
||||
|
||||
elseif event.type == 'quit' then
|
||||
Event.exitPullEvents()
|
||||
|
||||
else return UI.Page.eventHandler(self, event)
|
||||
end
|
||||
return true
|
||||
end
|
||||
|
||||
Event.on('modem_message', function(_, side, chan, reply, msg, dist)
|
||||
if modemConfig.currentModem.side == side then
|
||||
page.packetGrid:addPacket({
|
||||
id = page.index,
|
||||
portid = chan,
|
||||
replyid = reply,
|
||||
message = msg,
|
||||
distance = dist or -1,
|
||||
})
|
||||
end
|
||||
end)
|
||||
|
||||
local args = Util.parse(...)
|
||||
if args[1] then
|
||||
local id = tonumber(args[1])
|
||||
if id then
|
||||
filterConfig.filterGrid.values[id] = { id = id }
|
||||
filterConfig.filterAllCheck:setValue(true)
|
||||
filterConfig.filterGrid:update()
|
||||
end
|
||||
end
|
||||
|
||||
UI:setPage(page)
|
||||
UI:pullEvents()
|
@ -1,316 +1,41 @@
|
||||
_G.requireInjector(_ENV)
|
||||
local UI = require('opus.ui')
|
||||
local Util = require('opus.util')
|
||||
|
||||
local Config = require('config')
|
||||
local Security = require('security')
|
||||
local SHA1 = require('sha1')
|
||||
local UI = require('ui')
|
||||
local Util = require('util')
|
||||
|
||||
local fs = _G.fs
|
||||
local os = _G.os
|
||||
local settings = _G.settings
|
||||
local shell = _ENV.shell
|
||||
local turtle = _G.turtle
|
||||
local fs = _G.fs
|
||||
local shell = _ENV.shell
|
||||
|
||||
UI:configure('System', ...)
|
||||
|
||||
local env = {
|
||||
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,
|
||||
},
|
||||
},
|
||||
|
||||
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.',
|
||||
}
|
||||
},
|
||||
|
||||
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',
|
||||
},
|
||||
},
|
||||
settings = UI.Tab {
|
||||
tabTitle = 'Category',
|
||||
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,
|
||||
y = 2,
|
||||
columns = {
|
||||
{ key = 'name', width = 12 },
|
||||
{ key = 'value' },
|
||||
{ heading = 'Name', key = 'name' },
|
||||
{ heading = 'Description', key = 'description' },
|
||||
},
|
||||
sortColumn = 'name',
|
||||
autospace = true,
|
||||
},
|
||||
},
|
||||
},
|
||||
notification = UI.Notification(),
|
||||
accelerators = {
|
||||
q = 'quit',
|
||||
[ 'control-q' ] = 'quit',
|
||||
},
|
||||
}
|
||||
|
||||
if turtle then
|
||||
pcall(function()
|
||||
local Home = require('turtle.home')
|
||||
-- TODO: dont rely on turtle.home
|
||||
local values = { }
|
||||
Config.load('gps', values.home and { 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 and { 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
|
||||
function systemPage.tabs.settings:eventHandler(event)
|
||||
if event.type == 'grid_select' then
|
||||
local tab = event.selected.tab
|
||||
if not systemPage.tabs[tab.tabTitle] then
|
||||
systemPage.tabs:add({ [ tab.tabTitle ] = tab })
|
||||
tab:disable()
|
||||
end
|
||||
end)
|
||||
end
|
||||
|
||||
if settings then
|
||||
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
|
||||
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)
|
||||
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
|
||||
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)
|
||||
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
|
||||
|
||||
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
|
||||
|
||||
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')
|
||||
systemPage.tabs:selectTab(tab)
|
||||
self.parent:draw()
|
||||
return true
|
||||
end
|
||||
end
|
||||
@ -318,13 +43,43 @@ end
|
||||
function systemPage:eventHandler(event)
|
||||
if event.type == 'quit' then
|
||||
UI:exitPullEvents()
|
||||
|
||||
elseif event.type == 'success_message' then
|
||||
self.notification:success(event.message)
|
||||
|
||||
elseif event.type == 'info_message' then
|
||||
self.notification:info(event.message)
|
||||
|
||||
elseif event.type == 'error_message' then
|
||||
self.notification:error(event.message)
|
||||
|
||||
elseif event.type == 'tab_activate' then
|
||||
event.activated:focusFirst()
|
||||
|
||||
else
|
||||
return UI.Page.eventHandler(self, event)
|
||||
end
|
||||
return true
|
||||
end
|
||||
|
||||
local function loadDirectory(dir)
|
||||
local plugins = { }
|
||||
for _, file in pairs(fs.list(dir)) do
|
||||
local s, m = Util.run(_ENV, fs.combine(dir, file))
|
||||
if not s and m then
|
||||
_G.printError('Error loading: ' .. file)
|
||||
error(m or 'Unknown error')
|
||||
elseif s and m then
|
||||
table.insert(plugins, { tab = m, name = m.tabTitle, description = m.description })
|
||||
end
|
||||
end
|
||||
return plugins
|
||||
end
|
||||
|
||||
local programDir = fs.getDir(shell.getRunningProgram())
|
||||
local plugins = loadDirectory(fs.combine(programDir, 'system'), { })
|
||||
|
||||
systemPage.tabs.settings.grid:setValues(plugins)
|
||||
|
||||
UI:setPage(systemPage)
|
||||
UI:pullEvents()
|
||||
|
@ -1,8 +1,5 @@
|
||||
_G.requireInjector(_ENV)
|
||||
|
||||
local Event = require('event')
|
||||
local UI = require('ui')
|
||||
local Util = require('util')
|
||||
local Event = require('opus.event')
|
||||
local UI = require('opus.ui')
|
||||
|
||||
local kernel = _G.kernel
|
||||
local multishell = _ENV.multishell
|
||||
@ -29,7 +26,7 @@ local page = UI.Page {
|
||||
autospace = true,
|
||||
},
|
||||
accelerators = {
|
||||
q = 'quit',
|
||||
[ 'control-q' ] = 'quit',
|
||||
space = 'activate',
|
||||
t = 'terminate',
|
||||
},
|
||||
@ -51,15 +48,15 @@ function page:eventHandler(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
|
||||
row.status = row.isDead and 'error' or coroutine.status(row.co)
|
||||
return row
|
||||
return {
|
||||
uid = row.uid,
|
||||
title = row.title,
|
||||
status = row.isDead and 'error' or coroutine.status(row.co),
|
||||
timestamp = elapsed < 60 and
|
||||
string.format("%ds", math.floor(elapsed)) or
|
||||
string.format("%sm", math.floor(elapsed/6)/10),
|
||||
}
|
||||
end
|
||||
|
||||
Event.onInterval(1, function()
|
||||
|
161
sys/apps/Welcome.lua
Normal file
161
sys/apps/Welcome.lua
Normal file
@ -0,0 +1,161 @@
|
||||
local Ansi = require('opus.ansi')
|
||||
local Security = require('opus.security')
|
||||
local SHA = require('opus.crypto.sha2')
|
||||
local UI = require('opus.ui')
|
||||
|
||||
local colors = _G.colors
|
||||
local os = _G.os
|
||||
local shell = _ENV.shell
|
||||
|
||||
local splashIntro = [[First Time Setup
|
||||
|
||||
%sThanks for installing Opus OS. The next screens will prompt you for basic settings for this computer.]]
|
||||
local labelIntro = [[Set a friendly name for this computer.
|
||||
|
||||
%sNo spaces recommended.]]
|
||||
local passwordIntro = [[A password is required for wireless access.
|
||||
|
||||
%sLeave blank to skip.]]
|
||||
local packagesIntro = [[Setup Complete
|
||||
|
||||
%sOpen the package manager to add software to this computer.]]
|
||||
local contributorsIntro = [[Contributors%s
|
||||
|
||||
Anavrins: Encryption/security/custom apps
|
||||
Community: Several selected applications
|
||||
hugeblank: Startup screen improvements
|
||||
LDDestroier: Art design + custom apps
|
||||
Lemmmy: Application improvements
|
||||
|
||||
%sContribute at:%s
|
||||
https://github.com/kepler155c/opus]]
|
||||
|
||||
local page = UI.Page {
|
||||
wizard = UI.Wizard {
|
||||
ey = -2,
|
||||
pages = {
|
||||
splash = UI.WizardPage {
|
||||
index = 1,
|
||||
intro = UI.TextArea {
|
||||
textColor = colors.yellow,
|
||||
inactive = true,
|
||||
x = 3, ex = -3, y = 2, ey = -2,
|
||||
value = string.format(splashIntro, Ansi.white),
|
||||
},
|
||||
},
|
||||
label = UI.WizardPage {
|
||||
index = 2,
|
||||
labelText = UI.Text {
|
||||
x = 3, y = 2,
|
||||
value = 'Label'
|
||||
},
|
||||
label = UI.TextEntry {
|
||||
x = 9, y = 2, ex = -3,
|
||||
limit = 32,
|
||||
value = os.getComputerLabel(),
|
||||
},
|
||||
intro = UI.TextArea {
|
||||
textColor = colors.yellow,
|
||||
inactive = true,
|
||||
x = 3, ex = -3, y = 4, ey = -3,
|
||||
value = string.format(labelIntro, Ansi.white),
|
||||
},
|
||||
},
|
||||
password = UI.WizardPage {
|
||||
index = 3,
|
||||
passwordLabel = UI.Text {
|
||||
x = 3, y = 2,
|
||||
value = 'Password'
|
||||
},
|
||||
newPass = UI.TextEntry {
|
||||
x = 12, ex = -3, y = 2,
|
||||
limit = 32,
|
||||
mask = true,
|
||||
shadowText = 'password',
|
||||
},
|
||||
--[[
|
||||
groupLabel = UI.Text {
|
||||
x = 3, y = 3,
|
||||
value = 'Group'
|
||||
},
|
||||
group = UI.TextEntry {
|
||||
x = 12, ex = -3, y = 3,
|
||||
limit = 32,
|
||||
shadowText = 'network group',
|
||||
},
|
||||
]]
|
||||
intro = UI.TextArea {
|
||||
textColor = colors.yellow,
|
||||
inactive = true,
|
||||
x = 3, ex = -3, y = 5, ey = -3,
|
||||
value = string.format(passwordIntro, Ansi.white),
|
||||
},
|
||||
},
|
||||
packages = UI.WizardPage {
|
||||
index = 4,
|
||||
button = UI.Button {
|
||||
x = 3, y = -3,
|
||||
text = 'Open Package Manager',
|
||||
event = 'packages',
|
||||
},
|
||||
intro = UI.TextArea {
|
||||
textColor = colors.yellow,
|
||||
inactive = true,
|
||||
x = 3, ex = -3, y = 2, ey = -4,
|
||||
value = string.format(packagesIntro, Ansi.white),
|
||||
},
|
||||
},
|
||||
contributors = UI.WizardPage {
|
||||
index = 5,
|
||||
intro = UI.TextArea {
|
||||
textColor = colors.yellow,
|
||||
inactive = true,
|
||||
x = 3, ex = -3, y = 2, ey = -2,
|
||||
value = string.format(contributorsIntro, Ansi.white, Ansi.yellow, Ansi.white),
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
notification = UI.Notification { },
|
||||
}
|
||||
|
||||
function page.wizard.pages.label:validate()
|
||||
os.setComputerLabel(self.label.value)
|
||||
return true
|
||||
end
|
||||
|
||||
function page.wizard.pages.password:validate()
|
||||
if type(self.newPass.value) == "string" and #self.newPass.value > 0 then
|
||||
Security.updatePassword(SHA.compute(self.newPass.value))
|
||||
end
|
||||
--[[
|
||||
if #self.group.value > 0 then
|
||||
local config = Config.load('os')
|
||||
config.group = self.group.value
|
||||
Config.update('os', config)
|
||||
end
|
||||
]]
|
||||
return true
|
||||
end
|
||||
|
||||
function page:eventHandler(event)
|
||||
if event.type == 'skip' then
|
||||
self.wizard:emit({ type = 'nextView' })
|
||||
|
||||
elseif event.type == 'view_enabled' then
|
||||
event.view:focusFirst()
|
||||
|
||||
elseif event.type == 'packages' then
|
||||
shell.openForegroundTab('PackageManager')
|
||||
|
||||
elseif event.type == 'wizard_complete' or event.type == 'cancel' then
|
||||
UI.exitPullEvents()
|
||||
|
||||
else
|
||||
return UI.Page.eventHandler(self, event)
|
||||
end
|
||||
return true
|
||||
end
|
||||
|
||||
UI:setPage(page)
|
||||
UI:pullEvents()
|
70
sys/apps/autorun.lua
Normal file
70
sys/apps/autorun.lua
Normal file
@ -0,0 +1,70 @@
|
||||
local Packages = require('opus.packages')
|
||||
|
||||
local colors = _G.colors
|
||||
local fs = _G.fs
|
||||
local keys = _G.keys
|
||||
local multishell = _ENV.multishell
|
||||
local os = _G.os
|
||||
local shell = _ENV.shell
|
||||
local term = _G.term
|
||||
|
||||
local success = true
|
||||
|
||||
local function runDir(directory)
|
||||
if not fs.exists(directory) then
|
||||
return true
|
||||
end
|
||||
|
||||
local files = fs.list(directory)
|
||||
table.sort(files)
|
||||
|
||||
for _,file in ipairs(files) do
|
||||
os.sleep(0)
|
||||
local result, err = shell.run(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
|
||||
|
||||
runDir('sys/autorun')
|
||||
for _, package in pairs(Packages:installedSorted()) do
|
||||
local packageDir = 'packages/' .. package.name .. '/autorun'
|
||||
runDir(packageDir)
|
||||
end
|
||||
runDir('usr/autorun')
|
||||
|
||||
if not success then
|
||||
if multishell then
|
||||
multishell.setFocus(multishell.getCurrent())
|
||||
end
|
||||
_G.printError('A startup program has errored')
|
||||
print('Press enter to continue')
|
||||
|
||||
while true do
|
||||
local e, code = os.pullEventRaw('key')
|
||||
if e == 'terminate' or e == 'key' and code == keys.enter then
|
||||
break
|
||||
end
|
||||
end
|
||||
end
|
||||
|
38
sys/apps/cedit.lua
Normal file
38
sys/apps/cedit.lua
Normal file
@ -0,0 +1,38 @@
|
||||
local Config = require('opus.config')
|
||||
|
||||
local multishell = _ENV.multishell
|
||||
local os = _G.os
|
||||
local read = _G.read
|
||||
local shell = _ENV.shell
|
||||
|
||||
local args = { ... }
|
||||
if not args[1] then
|
||||
error('Syntax: cedit <filename>')
|
||||
end
|
||||
|
||||
if not _G.http.websocket then
|
||||
error('Requires CC: Tweaked')
|
||||
end
|
||||
|
||||
if not _G.cloud_catcher then
|
||||
local key = Config.load('cloud').key
|
||||
|
||||
if not key then
|
||||
print('Visit https://cloud-catcher.squiddev.cc')
|
||||
print('Paste key: ')
|
||||
key = read()
|
||||
if #key == 0 then
|
||||
return
|
||||
end
|
||||
end
|
||||
|
||||
-- open an unfocused tab
|
||||
local id = shell.openTab('cloud ' .. key)
|
||||
print('Connecting...')
|
||||
while not _G.cloud_catcher do
|
||||
os.sleep(.2)
|
||||
end
|
||||
multishell.setTitle(id, 'Cloud')
|
||||
end
|
||||
|
||||
shell.run('cloud edit ' .. table.unpack({ ... }))
|
23
sys/apps/cshell.lua
Normal file
23
sys/apps/cshell.lua
Normal file
@ -0,0 +1,23 @@
|
||||
local Config = require('opus.config')
|
||||
|
||||
local read = _G.read
|
||||
local shell = _ENV.shell
|
||||
|
||||
if not _G.http.websocket then
|
||||
error('Requires CC: Tweaked')
|
||||
end
|
||||
|
||||
if not _G.cloud_catcher then
|
||||
local key = Config.load('cloud').key
|
||||
|
||||
if not key then
|
||||
print('Visit https://cloud-catcher.squiddev.cc')
|
||||
print('Paste key: ')
|
||||
key = read()
|
||||
if #key == 0 then
|
||||
return
|
||||
end
|
||||
end
|
||||
print('Connecting...')
|
||||
shell.run('cloud ' .. key)
|
||||
end
|
@ -3,4 +3,4 @@ local args = { ... }
|
||||
local target = table.remove(args, 1)
|
||||
target = shell.resolve(target)
|
||||
|
||||
fs.mount(target, unpack(args))
|
||||
fs.mount(target, table.unpack(args))
|
||||
|
@ -1,7 +1,7 @@
|
||||
_G.requireInjector(_ENV)
|
||||
|
||||
local Event = require('event')
|
||||
local Util = require('util')
|
||||
local Event = require('opus.event')
|
||||
local Util = require('opus.util')
|
||||
|
||||
local device = _G.device
|
||||
local fs = _G.fs
|
||||
@ -14,9 +14,12 @@ if not device.wireless_modem then
|
||||
end
|
||||
|
||||
print('Net daemon starting')
|
||||
-- don't close as multiple computers may be sharing the
|
||||
-- wireless modem
|
||||
--device.wireless_modem.closeAll()
|
||||
|
||||
for _,file in pairs(fs.list('sys/network')) do
|
||||
local fn, msg = Util.run(_ENV, 'sys/network/' .. file)
|
||||
for _,file in pairs(fs.list('sys/apps/network')) do
|
||||
local fn, msg = Util.run(_ENV, 'sys/apps/network/' .. file)
|
||||
if not fn then
|
||||
printError(msg)
|
||||
end
|
||||
|
39
sys/apps/network/keygen.lua
Normal file
39
sys/apps/network/keygen.lua
Normal file
@ -0,0 +1,39 @@
|
||||
local ECC = require('opus.crypto.ecc')
|
||||
local Event = require('opus.event')
|
||||
local Util = require('opus.util')
|
||||
|
||||
local network = _G.network
|
||||
local os = _G.os
|
||||
|
||||
local keyPairs = { }
|
||||
|
||||
local function generateKeyPair()
|
||||
local key = { }
|
||||
for _ = 1, 32 do
|
||||
table.insert(key, ("%02x"):format(math.random(0, 0xFF)))
|
||||
end
|
||||
local privateKey = Util.hexToByteArray(table.concat(key))
|
||||
return privateKey, ECC.publicKey(privateKey)
|
||||
end
|
||||
|
||||
getmetatable(network).__index.getKeyPair = function()
|
||||
local keys = table.remove(keyPairs)
|
||||
os.queueEvent('generate_keypair')
|
||||
if not keys then
|
||||
return generateKeyPair()
|
||||
end
|
||||
return table.unpack(keys)
|
||||
end
|
||||
|
||||
-- Generate key pairs in the background as this is a time-consuming process
|
||||
Event.on('generate_keypair', function()
|
||||
while true do
|
||||
os.sleep(5)
|
||||
local timer = Util.timer()
|
||||
table.insert(keyPairs, { generateKeyPair() })
|
||||
_G._syslog('Generated keypair in ' .. timer())
|
||||
if #keyPairs >= 3 then
|
||||
break
|
||||
end
|
||||
end
|
||||
end)
|
64
sys/apps/network/proxy.lua
Normal file
64
sys/apps/network/proxy.lua
Normal file
@ -0,0 +1,64 @@
|
||||
local Event = require('opus.event')
|
||||
local Socket = require('opus.socket')
|
||||
local Util = require('opus.util')
|
||||
|
||||
local function getProxy(path)
|
||||
local x = Util.split(path, '(.-)/')
|
||||
local proxy = _G
|
||||
for _, v in pairs(x) do
|
||||
proxy = proxy[v]
|
||||
if not proxy then
|
||||
break
|
||||
end
|
||||
end
|
||||
return proxy
|
||||
end
|
||||
|
||||
local function proxyConnection(socket)
|
||||
local path = socket:read(2)
|
||||
if path then
|
||||
local api = getProxy(path)
|
||||
|
||||
if not api then
|
||||
print('proxy: invalid API')
|
||||
socket:close()
|
||||
return
|
||||
end
|
||||
|
||||
local methods = { }
|
||||
for k,v in pairs(api) do
|
||||
if type(v) == 'function' then
|
||||
table.insert(methods, k)
|
||||
end
|
||||
end
|
||||
socket:write(methods)
|
||||
|
||||
while true do
|
||||
local data = socket:read()
|
||||
if not data then
|
||||
print('proxy: lost connection from ' .. socket.dhost)
|
||||
break
|
||||
end
|
||||
socket:write({ api[data[1]](table.unpack(data, 2)) })
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
Event.addRoutine(function()
|
||||
print('proxy: listening on port 188')
|
||||
while true do
|
||||
local socket = Socket.server(188)
|
||||
|
||||
print('proxy: connection from ' .. socket.dhost)
|
||||
|
||||
Event.addRoutine(function()
|
||||
local s, m = pcall(proxyConnection, socket)
|
||||
print('proxy: closing connection to ' .. socket.dhost)
|
||||
socket:close()
|
||||
if not s and m then
|
||||
print('Proxy error')
|
||||
_G.printError(m)
|
||||
end
|
||||
end)
|
||||
end
|
||||
end)
|
@ -1,5 +1,5 @@
|
||||
local Event = require('event')
|
||||
local Socket = require('socket')
|
||||
local Event = require('opus.event')
|
||||
local Socket = require('opus.socket')
|
||||
|
||||
local fs = _G.fs
|
||||
|
||||
@ -48,7 +48,7 @@ local function sambaConnection(socket)
|
||||
end
|
||||
local ret
|
||||
local s, m = pcall(function()
|
||||
ret = fn(unpack(msg.args))
|
||||
ret = fn(table.unpack(msg.args))
|
||||
end)
|
||||
if not s and m then
|
||||
_G.printError('samba: ' .. m)
|
||||
@ -67,8 +67,13 @@ Event.addRoutine(function()
|
||||
|
||||
Event.addRoutine(function()
|
||||
print('samba: connection from ' .. socket.dhost)
|
||||
sambaConnection(socket)
|
||||
local s, m = pcall(sambaConnection, socket)
|
||||
print('samba: closing connection to ' .. socket.dhost)
|
||||
socket:close()
|
||||
if not s and m then
|
||||
print('Samba error')
|
||||
_G.printError(m)
|
||||
end
|
||||
end)
|
||||
end
|
||||
end)
|
@ -1,7 +1,7 @@
|
||||
local Event = require('event')
|
||||
local GPS = require('gps')
|
||||
local Socket = require('socket')
|
||||
local Util = require('util')
|
||||
local Event = require('opus.event')
|
||||
local GPS = require('opus.gps')
|
||||
local Socket = require('opus.socket')
|
||||
local Util = require('opus.util')
|
||||
|
||||
local device = _G.device
|
||||
local kernel = _G.kernel
|
||||
@ -106,8 +106,12 @@ Event.addRoutine(function()
|
||||
|
||||
Event.addRoutine(function()
|
||||
print('snmp: connection from ' .. socket.dhost)
|
||||
snmpConnection(socket)
|
||||
local s, m = pcall(snmpConnection, socket)
|
||||
print('snmp: closing connection to ' .. socket.dhost)
|
||||
if not s and m then
|
||||
print('snmp error')
|
||||
_G.printError(m)
|
||||
end
|
||||
end)
|
||||
end
|
||||
end)
|
||||
@ -117,16 +121,25 @@ 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 type(info.label) == 'string' and type(info.id) == 'number' then
|
||||
|
||||
if not network[id].active then
|
||||
network[id].active = true
|
||||
os.queueEvent('network_attach', network[id])
|
||||
if not network[id] then
|
||||
network[id] = { }
|
||||
end
|
||||
Util.merge(network[id], info)
|
||||
network[id].distance = type(distance) == 'number' and distance
|
||||
network[id].timestamp = os.clock()
|
||||
|
||||
if not network[id].label then
|
||||
network[id].label = 'unknown'
|
||||
end
|
||||
|
||||
if not network[id].active then
|
||||
network[id].active = true
|
||||
os.queueEvent('network_attach', network[id])
|
||||
end
|
||||
else
|
||||
print('discovery: Invalid alive message ' .. id)
|
||||
end
|
||||
end
|
||||
end)
|
||||
@ -136,27 +149,53 @@ local info = {
|
||||
}
|
||||
local infoTimer = os.clock()
|
||||
|
||||
local function getSlots()
|
||||
return Util.reduce(turtle.getInventory(), function(acc, v)
|
||||
if v.count > 0 then
|
||||
acc[v.index .. ',' .. v.count] = v.key
|
||||
end
|
||||
return acc
|
||||
end, { })
|
||||
end
|
||||
|
||||
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.group = network.getGroup()
|
||||
if turtle and turtle.getStatus then
|
||||
info.fuel = turtle.getFuelLevel()
|
||||
info.status = turtle.getStatus()
|
||||
info.point = turtle.point
|
||||
info.inventory = turtle.getInventory()
|
||||
info.inv = getSlots()
|
||||
info.slotIndex = turtle.getSelectedSlot()
|
||||
end
|
||||
if device.neuralInterface then
|
||||
info.status = device.neuralInterface.status
|
||||
pcall(function()
|
||||
if not info.status and device.neuralInterface.getMetaOwner then
|
||||
info.status = 'health: ' ..
|
||||
math.floor(device.neuralInterface.getMetaOwner().health /
|
||||
device.neuralInterface.getMetaOwner().maxHealth * 100)
|
||||
end
|
||||
end)
|
||||
if not info.status and device.neuralInterface.getMetaOwner then
|
||||
pcall(function()
|
||||
local meta = device.neuralInterface.getMetaOwner()
|
||||
local states = {
|
||||
isWet = 'Swimming',
|
||||
isElytraFlying = 'Flying',
|
||||
isBurning = 'Burning',
|
||||
isDead = 'Deceased',
|
||||
isOnLadder = 'Climbing',
|
||||
isRiding = 'Riding',
|
||||
isSneaking = 'Sneaking',
|
||||
isSprinting = 'Running',
|
||||
}
|
||||
for k,v in pairs(states) do
|
||||
if meta[k] then
|
||||
info.status = v
|
||||
break
|
||||
end
|
||||
end
|
||||
info.status = info.status or 'health: ' ..
|
||||
math.floor(meta.health / meta.maxHealth * 100)
|
||||
end)
|
||||
end
|
||||
end
|
||||
device.wireless_modem.transmit(999, os.getComputerID(), info)
|
||||
end
|
||||
@ -180,3 +219,5 @@ Event.on('turtle_response', function()
|
||||
sendInfo()
|
||||
end
|
||||
end)
|
||||
|
||||
Event.onTimeout(1, sendInfo)
|
@ -1,12 +1,13 @@
|
||||
local Event = require('event')
|
||||
local Socket = require('socket')
|
||||
local Util = require('util')
|
||||
local Alt = require('opus.alternate')
|
||||
local Event = require('opus.event')
|
||||
local Socket = require('opus.socket')
|
||||
local Util = require('opus.util')
|
||||
|
||||
local kernel = _G.kernel
|
||||
local term = _G.term
|
||||
local window = _G.window
|
||||
|
||||
local function telnetHost(socket)
|
||||
local function telnetHost(socket, mode)
|
||||
local methods = { 'clear', 'clearLine', 'setCursorPos', 'write', 'blit',
|
||||
'setTextColor', 'setTextColour', 'setBackgroundColor',
|
||||
'setBackgroundColour', 'scroll', 'setCursorBlink', }
|
||||
@ -43,10 +44,10 @@ local function telnetHost(socket)
|
||||
local shellThread = kernel.run({
|
||||
terminal = win,
|
||||
window = win,
|
||||
title = 'Telnet client',
|
||||
title = mode .. ' client',
|
||||
hidden = true,
|
||||
co = coroutine.create(function()
|
||||
Util.run(_ENV, 'sys/apps/shell', table.unpack(termInfo.program))
|
||||
Util.run(_ENV, Alt.get('shell'), table.unpack(termInfo.program))
|
||||
if socket.queue then
|
||||
socket:write(socket.queue)
|
||||
end
|
||||
@ -68,6 +69,23 @@ local function telnetHost(socket)
|
||||
end)
|
||||
end
|
||||
|
||||
Event.addRoutine(function()
|
||||
print('ssh: listening on port 22')
|
||||
while true do
|
||||
local socket = Socket.server(22, { ENCRYPT = true })
|
||||
|
||||
print('ssh: connection from ' .. socket.dhost)
|
||||
|
||||
Event.addRoutine(function()
|
||||
local s, m = pcall(telnetHost, socket, 'SSH')
|
||||
if not s and m then
|
||||
print('ssh error')
|
||||
_G.printError(m)
|
||||
end
|
||||
end)
|
||||
end
|
||||
end)
|
||||
|
||||
Event.addRoutine(function()
|
||||
print('telnet: listening on port 23')
|
||||
while true do
|
||||
@ -76,7 +94,11 @@ Event.addRoutine(function()
|
||||
print('telnet: connection from ' .. socket.dhost)
|
||||
|
||||
Event.addRoutine(function()
|
||||
telnetHost(socket)
|
||||
local s, m = pcall(telnetHost, socket, 'Telnet')
|
||||
if not s and m then
|
||||
print('Telnet error')
|
||||
_G.printError(m)
|
||||
end
|
||||
end)
|
||||
end
|
||||
end)
|
@ -5,17 +5,23 @@
|
||||
* background read buffering
|
||||
]]--
|
||||
|
||||
local Event = require('event')
|
||||
local Crypto = require('opus.crypto.chacha20')
|
||||
local Event = require('opus.event')
|
||||
|
||||
local network = _G.network
|
||||
local os = _G.os
|
||||
|
||||
local computerId = os.getComputerID()
|
||||
local transport = {
|
||||
timers = { },
|
||||
sockets = { },
|
||||
encryptQueue = { },
|
||||
UID = 0,
|
||||
}
|
||||
_G.transport = transport
|
||||
|
||||
getmetatable(network).__index.getTransport = function()
|
||||
return transport
|
||||
end
|
||||
|
||||
function transport.open(socket)
|
||||
transport.UID = transport.UID + 1
|
||||
@ -28,24 +34,26 @@ end
|
||||
function transport.read(socket)
|
||||
local data = table.remove(socket.messages, 1)
|
||||
if data then
|
||||
return unpack(data)
|
||||
if socket.options.ENCRYPT then
|
||||
return table.unpack(Crypto.decrypt(data[1], socket.enckey)), data[2]
|
||||
end
|
||||
return table.unpack(data)
|
||||
end
|
||||
end
|
||||
|
||||
function transport.write(socket, data)
|
||||
--_debug('>> ' .. Util.tostring({ type = 'DATA', seq = socket.wseq }))
|
||||
socket.transmit(socket.dport, socket.dhost, data)
|
||||
|
||||
--local timerId = os.startTimer(3)
|
||||
|
||||
--transport.timers[timerId] = socket
|
||||
--socket.timers[socket.wseq] = timerId
|
||||
|
||||
socket.wseq = socket.wseq + 1
|
||||
function transport.write(socket, msg)
|
||||
if socket.options.ENCRYPT then
|
||||
if #transport.encryptQueue == 0 then
|
||||
os.queueEvent('transport_encrypt')
|
||||
end
|
||||
table.insert(transport.encryptQueue, { socket.sport, msg })
|
||||
else
|
||||
socket.transmit(socket.dport, socket.dhost, msg)
|
||||
end
|
||||
socket.wseq = socket.wrng:nextInt(5)
|
||||
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, {
|
||||
@ -53,7 +61,7 @@ function transport.ping(socket)
|
||||
seq = -1,
|
||||
})
|
||||
|
||||
local timerId = os.startTimer(5)
|
||||
local timerId = os.startTimer(3)
|
||||
transport.timers[timerId] = socket
|
||||
socket.timers[-1] = timerId
|
||||
end
|
||||
@ -63,6 +71,19 @@ function transport.close(socket)
|
||||
transport.sockets[socket.sport] = nil
|
||||
end
|
||||
|
||||
Event.on('transport_encrypt', function()
|
||||
while #transport.encryptQueue > 0 do
|
||||
local entry = table.remove(transport.encryptQueue, 1)
|
||||
local socket = transport.sockets[entry[1]]
|
||||
|
||||
if socket and socket.connected then
|
||||
local msg = entry[2]
|
||||
msg.data = Crypto.encrypt({ msg.data }, socket.enckey)
|
||||
socket.transmit(socket.dport, socket.dhost, msg)
|
||||
end
|
||||
end
|
||||
end)
|
||||
|
||||
Event.on('timer', function(_, timerId)
|
||||
local socket = transport.timers[timerId]
|
||||
|
||||
@ -78,18 +99,19 @@ Event.on('modem_message', function(_, _, dport, dhost, msg, distance)
|
||||
local socket = transport.sockets[dport]
|
||||
if socket and socket.connected then
|
||||
|
||||
--if msg.type then _debug('<< ' .. Util.tostring(msg)) end
|
||||
if socket.co and coroutine.status(socket.co) == 'dead' then
|
||||
_G._debug('socket coroutine dead')
|
||||
_G._syslog('socket coroutine dead')
|
||||
socket:close()
|
||||
|
||||
elseif msg.type == 'DISC' then
|
||||
-- received disconnect from other end
|
||||
if socket.connected then
|
||||
os.queueEvent('transport_' .. socket.uid)
|
||||
if msg.seq == socket.rseq then
|
||||
if socket.connected then
|
||||
os.queueEvent('transport_' .. socket.uid)
|
||||
end
|
||||
socket.connected = false
|
||||
socket:close()
|
||||
end
|
||||
socket.connected = false
|
||||
socket:close()
|
||||
|
||||
elseif msg.type == 'ACK' then
|
||||
local ackTimerId = socket.timers[msg.seq]
|
||||
@ -108,28 +130,20 @@ Event.on('modem_message', function(_, _, dport, dhost, msg, 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)
|
||||
_debug(msg.data)
|
||||
_debug('current ' .. socket.rseq)
|
||||
_debug('expected ' .. msg.seq)
|
||||
-- socket:close()
|
||||
-- os.queueEvent('transport_' .. socket.uid)
|
||||
print('transport seq error ' .. socket.sport)
|
||||
_syslog(msg.data)
|
||||
_syslog('expected ' .. socket.rseq)
|
||||
_syslog('got ' .. msg.seq)
|
||||
else
|
||||
socket.rseq = socket.rseq + 1
|
||||
socket.activityTimer = os.clock()
|
||||
socket.rseq = socket.rrng:nextInt(5)
|
||||
|
||||
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
|
||||
|
||||
--_debug('>> ' .. Util.tostring({ type = 'ACK', seq = msg.seq }))
|
||||
--socket.transmit(socket.dport, socket.dhost, {
|
||||
-- type = 'ACK',
|
||||
-- seq = msg.seq,
|
||||
--})
|
||||
end
|
||||
end
|
||||
end
|
46
sys/apps/network/trust.lua
Normal file
46
sys/apps/network/trust.lua
Normal file
@ -0,0 +1,46 @@
|
||||
local Crypto = require('opus.crypto.chacha20')
|
||||
local Event = require('opus.event')
|
||||
local Security = require('opus.security')
|
||||
local Socket = require('opus.socket')
|
||||
local Util = require('opus.util')
|
||||
|
||||
local trustId = '01c3ba27fe01383a03a1785276d99df27c3edcef68fbf231ca'
|
||||
|
||||
local function trustConnection(socket)
|
||||
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
|
||||
local s
|
||||
s, data = pcall(Crypto.decrypt, data, password)
|
||||
if s and 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
|
||||
end
|
||||
|
||||
Event.addRoutine(function()
|
||||
print('trust: listening on port 19')
|
||||
|
||||
while true do
|
||||
local socket = Socket.server(19, { identifier = trustId })
|
||||
|
||||
print('trust: connection from ' .. socket.dhost)
|
||||
|
||||
local s, m = pcall(trustConnection, socket)
|
||||
socket:close()
|
||||
if not s and m then
|
||||
print('Trust error')
|
||||
_G.printError(m)
|
||||
end
|
||||
end
|
||||
end)
|
@ -1,6 +1,6 @@
|
||||
local Event = require('event')
|
||||
local Socket = require('socket')
|
||||
local Util = require('util')
|
||||
local Event = require('opus.event')
|
||||
local Socket = require('opus.socket')
|
||||
local Util = require('opus.util')
|
||||
|
||||
local os = _G.os
|
||||
local terminal = _G.device.terminal
|
||||
@ -63,7 +63,31 @@ Event.addRoutine(function()
|
||||
|
||||
-- no new process - only 1 connection allowed
|
||||
-- due to term size issues
|
||||
vncHost(socket)
|
||||
local s, m = pcall(vncHost, socket)
|
||||
socket:close()
|
||||
if not s and m then
|
||||
print('vnc error')
|
||||
_G.printError(m)
|
||||
end
|
||||
end
|
||||
end)
|
||||
|
||||
Event.addRoutine(function()
|
||||
|
||||
print('svnc: listening on port 5901')
|
||||
|
||||
while true do
|
||||
local socket = Socket.server(5901, { ENCRYPT = true })
|
||||
|
||||
print('svnc: connection from ' .. socket.dhost)
|
||||
|
||||
-- no new process - only 1 connection allowed
|
||||
-- due to term size issues
|
||||
local s, m = pcall(vncHost, socket)
|
||||
socket:close()
|
||||
if not s and m then
|
||||
print('vnc error')
|
||||
_G.printError(m)
|
||||
end
|
||||
end
|
||||
end)
|
@ -1,8 +1,7 @@
|
||||
_G.requireInjector(_ENV)
|
||||
|
||||
local Git = require('git')
|
||||
local Packages = require('packages')
|
||||
local Util = require('util')
|
||||
local BulkGet = require('opus.bulkget')
|
||||
local Git = require('opus.git')
|
||||
local Packages = require('opus.packages')
|
||||
local Util = require('opus.util')
|
||||
|
||||
local fs = _G.fs
|
||||
local term = _G.term
|
||||
@ -10,6 +9,12 @@ local term = _G.term
|
||||
local args = { ... }
|
||||
local action = table.remove(args, 1)
|
||||
|
||||
local function makeSandbox()
|
||||
local sandbox = setmetatable(Util.shallowCopy(_ENV), { __index = _G })
|
||||
_G.requireInjector(sandbox)
|
||||
return sandbox
|
||||
end
|
||||
|
||||
local function Syntax(msg)
|
||||
_G.printError(msg)
|
||||
print('\nSyntax: Package list | install [name] ... | update [name] | uninstall [name]')
|
||||
@ -36,24 +41,61 @@ local function progress(max)
|
||||
end
|
||||
end
|
||||
|
||||
local function install(name)
|
||||
local manifest = Packages:getManifest(name) or error('Invalid package')
|
||||
local packageDir = fs.combine('packages', name)
|
||||
local method = args[2] or 'local'
|
||||
if method == 'remote' then
|
||||
Util.writeTable(packageDir .. '/.install', {
|
||||
mount = string.format('%s gitfs %s', packageDir, manifest.repository),
|
||||
})
|
||||
Util.writeTable(fs.combine(packageDir, '.package'), manifest)
|
||||
else
|
||||
local list = Git.list(manifest.repository)
|
||||
local showProgress = progress(Util.size(list))
|
||||
for path, entry in pairs(list) do
|
||||
Util.download(entry.url, fs.combine(packageDir, path))
|
||||
showProgress()
|
||||
local function runScript(script)
|
||||
if script then
|
||||
local s, m = pcall(function()
|
||||
local fn, m = load(script, 'script', nil, makeSandbox())
|
||||
if not fn then
|
||||
error(m)
|
||||
end
|
||||
fn()
|
||||
end)
|
||||
if not s and m then
|
||||
_G.printError(m)
|
||||
end
|
||||
end
|
||||
return
|
||||
end
|
||||
|
||||
local function install(name, isUpdate, ignoreDeps)
|
||||
local manifest = Packages:downloadManifest(name) or error('Invalid package')
|
||||
|
||||
if not ignoreDeps then
|
||||
if manifest.required then
|
||||
for _, v in pairs(manifest.required) do
|
||||
if isUpdate or not Packages:isInstalled(v) then
|
||||
install(v, isUpdate)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
print(string.format('%s: %s',
|
||||
isUpdate and 'Updating' or 'Installing',
|
||||
name))
|
||||
|
||||
local packageDir = fs.combine('packages', name)
|
||||
|
||||
local list = Git.list(manifest.repository)
|
||||
local showProgress = progress(Util.size(list))
|
||||
|
||||
local getList = { }
|
||||
for path, entry in pairs(list) do
|
||||
table.insert(getList, {
|
||||
path = fs.combine(packageDir, path),
|
||||
url = entry.url
|
||||
})
|
||||
end
|
||||
|
||||
BulkGet.download(getList, function(_, s, m)
|
||||
if not s then
|
||||
error(m)
|
||||
end
|
||||
showProgress()
|
||||
end)
|
||||
|
||||
if not isUpdate then
|
||||
runScript(manifest.install)
|
||||
end
|
||||
end
|
||||
|
||||
if action == 'list' then
|
||||
@ -69,7 +111,23 @@ if action == 'install' then
|
||||
error('Package is already installed')
|
||||
end
|
||||
install(name)
|
||||
print('installation complete')
|
||||
print('installation complete\n')
|
||||
_G.printError('Reboot is required')
|
||||
return
|
||||
end
|
||||
|
||||
if action == 'refresh' then
|
||||
print('Downloading...')
|
||||
Packages:downloadList()
|
||||
print('refresh complete')
|
||||
return
|
||||
end
|
||||
|
||||
if action == 'updateall' then
|
||||
for name in pairs(Packages:installed()) do
|
||||
install(name, true, true)
|
||||
end
|
||||
print('updateall complete')
|
||||
return
|
||||
end
|
||||
|
||||
@ -78,7 +136,7 @@ if action == 'update' then
|
||||
if not Packages:isInstalled(name) then
|
||||
error('Package is not installed')
|
||||
end
|
||||
install(name)
|
||||
install(name, true)
|
||||
print('update complete')
|
||||
return
|
||||
end
|
||||
@ -88,6 +146,10 @@ if action == 'uninstall' then
|
||||
if not Packages:isInstalled(name) then
|
||||
error('Package is not installed')
|
||||
end
|
||||
|
||||
local manifest = Packages:getManifest(name)
|
||||
runScript(manifest.uninstall)
|
||||
|
||||
local packageDir = fs.combine('packages', name)
|
||||
fs.delete(packageDir)
|
||||
print('removed: ' .. packageDir)
|
||||
|
@ -1,12 +1,10 @@
|
||||
_G.requireInjector(_ENV)
|
||||
|
||||
local Security = require('security')
|
||||
local SHA1 = require('sha1')
|
||||
local Terminal = require('terminal')
|
||||
local Security = require('opus.security')
|
||||
local SHA = require('opus.crypto.sha2')
|
||||
local Terminal = require('opus.terminal')
|
||||
|
||||
local password = Terminal.readPassword('Enter new password: ')
|
||||
|
||||
if password then
|
||||
Security.updatePassword(SHA1.sha1(password))
|
||||
Security.updatePassword(SHA.compute(password))
|
||||
print('Password updated')
|
||||
end
|
||||
|
114
sys/apps/pastebin.lua
Normal file
114
sys/apps/pastebin.lua
Normal file
@ -0,0 +1,114 @@
|
||||
local function printUsage()
|
||||
print( "Usages:" )
|
||||
print( "pastebin put <filename>" )
|
||||
print( "pastebin get <code> <filename>" )
|
||||
print( "pastebin run <code> <arguments>" )
|
||||
end
|
||||
|
||||
if not http then
|
||||
printError( "Pastebin requires http API" )
|
||||
printError( "Set http_enable to true in ComputerCraft.cfg" )
|
||||
return
|
||||
end
|
||||
|
||||
local pastebin = require('opus.http.pastebin')
|
||||
|
||||
local tArgs = { ... }
|
||||
local sCommand = tArgs[1]
|
||||
|
||||
if sCommand == "put" then
|
||||
-- Upload a file to pastebin.com
|
||||
|
||||
if #tArgs < 2 then
|
||||
printUsage()
|
||||
return
|
||||
end
|
||||
|
||||
-- Determine file to upload
|
||||
local sFile = tArgs[2]
|
||||
local sPath = shell.resolve( sFile )
|
||||
if not fs.exists( sPath ) or fs.isDir( sPath ) then
|
||||
print( "No such file" )
|
||||
return
|
||||
end
|
||||
|
||||
print( "Connecting to pastebin.com... " )
|
||||
|
||||
local resp, msg = pastebin.put(sPath)
|
||||
|
||||
if resp then
|
||||
print( "Uploaded as " .. resp )
|
||||
print( "Run \"pastebin get "..resp.."\" to download anywhere" )
|
||||
|
||||
else
|
||||
printError( msg )
|
||||
end
|
||||
|
||||
elseif sCommand == "get" then
|
||||
-- Download a file from pastebin.com
|
||||
|
||||
if #tArgs < 3 then
|
||||
printUsage()
|
||||
return
|
||||
end
|
||||
|
||||
local sCode = pastebin.parseCode(tArgs[2])
|
||||
if not sCode then
|
||||
return false, "Invalid pastebin code. The code is the ID at the end of the pastebin.com URL."
|
||||
end
|
||||
|
||||
-- Determine file to download
|
||||
local sFile = tArgs[3]
|
||||
local sPath = shell.resolve( sFile )
|
||||
if fs.exists( sPath ) then
|
||||
printError( "File already exists" )
|
||||
return
|
||||
end
|
||||
|
||||
print( "Connecting to pastebin.com... " )
|
||||
|
||||
local resp, msg = pastebin.get(sCode, sPath)
|
||||
|
||||
if resp then
|
||||
print( "Downloaded as " .. sPath )
|
||||
else
|
||||
printError( msg )
|
||||
end
|
||||
|
||||
elseif sCommand == "run" then
|
||||
-- Download and run a file from pastebin.com
|
||||
|
||||
if #tArgs < 2 then
|
||||
printUsage()
|
||||
return
|
||||
end
|
||||
|
||||
local sCode = pastebin.parseCode(tArgs[2])
|
||||
if not sCode then
|
||||
return false, "Invalid pastebin code. The code is the ID at the end of the pastebin.com URL."
|
||||
end
|
||||
|
||||
print( "Connecting to pastebin.com... " )
|
||||
|
||||
local res, msg = pastebin.download(sCode)
|
||||
if not res then
|
||||
printError( msg )
|
||||
return res, msg
|
||||
end
|
||||
|
||||
res, msg = load(res, sCode, "t", _ENV)
|
||||
if not res then
|
||||
printError( msg )
|
||||
return res, msg
|
||||
end
|
||||
|
||||
res, msg = pcall(res, table.unpack(tArgs, 3))
|
||||
if not res then
|
||||
printError( msg )
|
||||
end
|
||||
else
|
||||
|
||||
printUsage()
|
||||
return
|
||||
end
|
||||
|
@ -3,6 +3,7 @@ local parentShell = _ENV.shell
|
||||
_ENV.shell = { }
|
||||
|
||||
local fs = _G.fs
|
||||
local settings = _G.settings
|
||||
local shell = _ENV.shell
|
||||
|
||||
local sandboxEnv = setmetatable({ }, { __index = _G })
|
||||
@ -13,7 +14,8 @@ sandboxEnv.shell = shell
|
||||
|
||||
_G.requireInjector(_ENV)
|
||||
|
||||
local Util = require('util')
|
||||
local trace = require('opus.trace')
|
||||
local Util = require('opus.util')
|
||||
|
||||
local DIR = (parentShell and parentShell.dir()) or ""
|
||||
local PATH = (parentShell and parentShell.path()) or ".:/rom/programs"
|
||||
@ -65,11 +67,12 @@ local function run(env, ...)
|
||||
end
|
||||
|
||||
if isUrl then
|
||||
tProgramStack[#tProgramStack + 1] = path:match("^https?://([^/:]+:?[0-9]*/?.*)$")
|
||||
tProgramStack[#tProgramStack + 1] = path -- path:match("^https?://([^/:]+:?[0-9]*/?.*)$")
|
||||
else
|
||||
tProgramStack[#tProgramStack + 1] = path
|
||||
end
|
||||
|
||||
env[ "arg" ] = { [0] = path, table.unpack(args) }
|
||||
local r = { fn(table.unpack(args)) }
|
||||
|
||||
tProgramStack[#tProgramStack] = nil
|
||||
@ -86,7 +89,9 @@ function shell.run(...)
|
||||
end
|
||||
|
||||
local env = setmetatable(Util.shallowCopy(sandboxEnv), { __index = _G })
|
||||
local r = { pcall(run, env, ...) }
|
||||
_G.requireInjector(env)
|
||||
|
||||
local r = { trace(run, env, ...) }
|
||||
|
||||
if _ENV.multishell then
|
||||
_ENV.multishell.setTitle(_ENV.multishell.getCurrent(), oldTitle or 'shell')
|
||||
@ -312,9 +317,9 @@ function shell.newTab(tabInfo, ...)
|
||||
tabInfo.args = args
|
||||
tabInfo.title = fs.getName(path):match('([^%.]+)')
|
||||
|
||||
if path ~= 'sys/apps/shell' then
|
||||
if path ~= 'sys/apps/shell.lua' then
|
||||
table.insert(tabInfo.args, 1, tabInfo.path)
|
||||
tabInfo.path = 'sys/apps/shell'
|
||||
tabInfo.path = 'sys/apps/shell.lua'
|
||||
end
|
||||
return _ENV.multishell.openTab(tabInfo)
|
||||
end
|
||||
@ -327,10 +332,10 @@ function shell.openTab( ... )
|
||||
local sCommand = tWords[1]
|
||||
if sCommand then
|
||||
local sPath = shell.resolveProgram(sCommand)
|
||||
if sPath == "sys/apps/shell" then
|
||||
if sPath == "sys/apps/shell.lua" then
|
||||
return _ENV.multishell.launch(Util.shallowCopy(sandboxEnv), sPath, table.unpack(tWords, 2))
|
||||
else
|
||||
return _ENV.multishell.launch(Util.shallowCopy(sandboxEnv), "sys/apps/shell", sCommand, table.unpack(tWords, 2))
|
||||
return _ENV.multishell.launch(Util.shallowCopy(sandboxEnv), "sys/apps/shell.lua", sCommand, table.unpack(tWords, 2))
|
||||
end
|
||||
end
|
||||
end
|
||||
@ -350,34 +355,35 @@ end
|
||||
local tArgs = { ... }
|
||||
if #tArgs > 0 then
|
||||
local env = setmetatable(Util.shallowCopy(sandboxEnv), { __index = _G })
|
||||
_G.requireInjector(env)
|
||||
|
||||
return run(env, ...)
|
||||
end
|
||||
|
||||
local Config = require('config')
|
||||
local Entry = require('entry')
|
||||
local History = require('history')
|
||||
local Input = require('input')
|
||||
local Terminal = require('terminal')
|
||||
local Config = require('opus.config')
|
||||
local Entry = require('opus.entry')
|
||||
local History = require('opus.history')
|
||||
local Input = require('opus.input')
|
||||
local Sound = require('opus.sound')
|
||||
local Terminal = require('opus.terminal')
|
||||
|
||||
local colors = _G.colors
|
||||
local os = _G.os
|
||||
local term = _G.term
|
||||
local textutils = _G.textutils
|
||||
|
||||
local terminal = term.current()
|
||||
--Terminal.scrollable(terminal, 100)
|
||||
terminal.noAutoScroll = true
|
||||
local oldTerm
|
||||
local terminal = term.current()
|
||||
local _rep = string.rep
|
||||
local _sub = string.sub
|
||||
|
||||
if not terminal.scrollUp then
|
||||
terminal = Terminal.window(term.current())
|
||||
terminal.setMaxScroll(200)
|
||||
oldTerm = term.redirect(terminal)
|
||||
end
|
||||
|
||||
local config = {
|
||||
standard = {
|
||||
textColor = colors.white,
|
||||
commandTextColor = colors.lightGray,
|
||||
directoryTextColor = colors.gray,
|
||||
directoryBackgroundColor = colors.black,
|
||||
promptTextColor = colors.gray,
|
||||
promptBackgroundColor = colors.black,
|
||||
directoryColor = colors.gray,
|
||||
},
|
||||
color = {
|
||||
textColor = colors.white,
|
||||
commandTextColor = colors.yellow,
|
||||
@ -386,15 +392,34 @@ local config = {
|
||||
promptTextColor = colors.blue,
|
||||
promptBackgroundColor = colors.black,
|
||||
directoryColor = colors.green,
|
||||
fileColor = colors.white,
|
||||
backgroundColor = colors.black,
|
||||
},
|
||||
displayDirectory = true,
|
||||
}
|
||||
|
||||
Config.load('shellprompt', config)
|
||||
|
||||
local _colors = config.standard
|
||||
if term.isColor() then
|
||||
_colors = config.color
|
||||
local _colors = config.color
|
||||
-- temp
|
||||
if not _colors.backgroundColor then
|
||||
_colors.backgroundColor = colors.black
|
||||
_colors.fileColor = colors.white
|
||||
end
|
||||
|
||||
local palette = { }
|
||||
for n = 1, 16 do
|
||||
palette[2 ^ (n - 1)] = _sub("0123456789abcdef", n, n)
|
||||
end
|
||||
|
||||
if not term.isColor() then
|
||||
_colors = { }
|
||||
for k, v in pairs(config.color) do
|
||||
_colors[k] = Terminal.colorToGrayscale(v)
|
||||
end
|
||||
for n = 1, 16 do
|
||||
palette[2 ^ (n - 1)] = _sub("088888878877787f", n, n)
|
||||
end
|
||||
end
|
||||
|
||||
local function autocompleteArgument(program, words)
|
||||
@ -526,9 +551,9 @@ local function autocomplete(line)
|
||||
end
|
||||
|
||||
if #tDirs > 0 then
|
||||
textutils.tabulate(_colors.directoryColor, tDirs, colors.white, tFiles)
|
||||
textutils.tabulate(_colors.directoryColor, tDirs, _colors.fileColor, tFiles)
|
||||
else
|
||||
textutils.tabulate(colors.white, tFiles)
|
||||
textutils.tabulate(_colors.fileColor, tFiles)
|
||||
end
|
||||
|
||||
term.setTextColour(_colors.promptTextColor)
|
||||
@ -536,7 +561,7 @@ local function autocomplete(line)
|
||||
term.write("$ " )
|
||||
|
||||
term.setTextColour(_colors.commandTextColor)
|
||||
term.setBackgroundColor(colors.black)
|
||||
term.setBackgroundColor(_colors.backgroundColor)
|
||||
return line
|
||||
end
|
||||
end
|
||||
@ -544,21 +569,38 @@ end
|
||||
local function shellRead(history)
|
||||
local lastLen = 0
|
||||
local entry = Entry({
|
||||
width = term.getSize() - 3
|
||||
width = term.getSize() - 3,
|
||||
offset = 3,
|
||||
})
|
||||
|
||||
history:reset()
|
||||
term.setCursorBlink(true)
|
||||
|
||||
local function updateCursor()
|
||||
term.setCursorPos(3 + entry.pos - entry.scroll, select(2, term.getCursorPos()))
|
||||
end
|
||||
|
||||
local function redraw()
|
||||
if terminal.scrollBottom then
|
||||
terminal.scrollBottom()
|
||||
end
|
||||
local _,cy = term.getCursorPos()
|
||||
term.setCursorPos(3, cy)
|
||||
local filler = #entry.value < lastLen
|
||||
and string.rep(' ', lastLen - #entry.value)
|
||||
or ''
|
||||
local str = string.sub(entry.value, entry.scroll + 1)
|
||||
term.write(string.sub(str, 1, entry.width) .. filler)
|
||||
term.setCursorPos(3 + entry.pos - entry.scroll, cy)
|
||||
local str = string.sub(entry.value, entry.scroll + 1, entry.width + entry.scroll) .. filler
|
||||
local fg = _rep(palette[_colors.commandTextColor], #str)
|
||||
local bg = _rep(palette[_colors.backgroundColor], #str)
|
||||
if entry.mark.active then
|
||||
local sx = entry.mark.x - entry.scroll + 1
|
||||
local ex = entry.mark.ex - entry.scroll + 1
|
||||
bg = string.rep('f', sx - 1) ..
|
||||
string.rep('7', ex - sx) ..
|
||||
string.rep('f', #str - ex + 1)
|
||||
end
|
||||
term.blit(str, fg, bg)
|
||||
updateCursor()
|
||||
lastLen = #entry.value
|
||||
end
|
||||
|
||||
@ -567,11 +609,11 @@ local function shellRead(history)
|
||||
|
||||
local ie = Input:translate(event, p1, p2, p3)
|
||||
if ie then
|
||||
if ie.code == 'scroll_up' then
|
||||
--terminal.scrollUp()
|
||||
if ie.code == 'scroll_up' and terminal.scrollUp then
|
||||
terminal.scrollUp()
|
||||
|
||||
elseif ie.code == 'scroll_down' then
|
||||
--terminal.scrollDown()
|
||||
elseif ie.code == 'scroll_down' and terminal.scrollDown then
|
||||
terminal.scrollDown()
|
||||
|
||||
elseif ie.code == 'terminate' then
|
||||
bExit = true
|
||||
@ -580,14 +622,15 @@ local function shellRead(history)
|
||||
elseif ie.code == 'enter' then
|
||||
break
|
||||
|
||||
elseif ie.code == 'up' or ie.code == 'down' then
|
||||
if ie.code == 'up' then
|
||||
elseif ie.code == 'up' or ie.code == 'control-p' or
|
||||
ie.code == 'down' or ie.code == 'control-n' then
|
||||
entry:reset()
|
||||
if ie.code == 'up' or ie.code == 'control-p' then
|
||||
entry.value = history:back() or ''
|
||||
else
|
||||
entry.value = history:forward() or ''
|
||||
end
|
||||
entry.pos = string.len(entry.value)
|
||||
entry.scroll = 0
|
||||
entry.pos = #entry.value
|
||||
entry:updateScroll()
|
||||
redraw()
|
||||
|
||||
@ -597,23 +640,30 @@ local function shellRead(history)
|
||||
if cline then
|
||||
entry.value = cline
|
||||
entry.pos = #entry.value
|
||||
entry:unmark()
|
||||
entry:updateScroll()
|
||||
redraw()
|
||||
else
|
||||
Sound.play('entity.villager.no')
|
||||
end
|
||||
end
|
||||
|
||||
elseif entry:process(ie) then
|
||||
redraw()
|
||||
else
|
||||
entry:process(ie)
|
||||
if entry.textChanged then
|
||||
redraw()
|
||||
elseif entry.posChanged then
|
||||
updateCursor()
|
||||
end
|
||||
end
|
||||
|
||||
elseif event == "term_resize" then
|
||||
entry.width = term.getSize() - 3
|
||||
entry:updateScroll()
|
||||
redraw()
|
||||
end
|
||||
end
|
||||
|
||||
--local _, cy = term.getCursorPos()
|
||||
--term.setCursorPos( w + 1, cy )
|
||||
print()
|
||||
term.setCursorBlink( false )
|
||||
return entry.value
|
||||
@ -621,6 +671,13 @@ end
|
||||
|
||||
local history = History.load('usr/.shell_history', 25)
|
||||
|
||||
term.setBackgroundColor(_colors.backgroundColor)
|
||||
term.clear()
|
||||
|
||||
if settings.get("motd.enabled") then
|
||||
shell.run("motd")
|
||||
end
|
||||
|
||||
while not bExit do
|
||||
if config.displayDirectory then
|
||||
term.setTextColour(_colors.directoryTextColor)
|
||||
@ -631,7 +688,7 @@ while not bExit do
|
||||
term.setBackgroundColor(_colors.promptBackgroundColor)
|
||||
term.write("$ " )
|
||||
term.setTextColour(_colors.commandTextColor)
|
||||
term.setBackgroundColor(colors.black)
|
||||
term.setBackgroundColor(_colors.backgroundColor)
|
||||
local sLine = shellRead(history)
|
||||
if bExit then -- terminated
|
||||
break
|
||||
@ -648,3 +705,7 @@ while not bExit do
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
if oldTerm then
|
||||
term.redirect(oldTerm)
|
||||
end
|
72
sys/apps/system/aliases.lua
Normal file
72
sys/apps/system/aliases.lua
Normal file
@ -0,0 +1,72 @@
|
||||
local Config = require('opus.config')
|
||||
local UI = require('opus.ui')
|
||||
|
||||
local kernel = _G.kernel
|
||||
|
||||
local aliasTab = UI.Tab {
|
||||
tabTitle = 'Aliases',
|
||||
description = 'Shell aliases',
|
||||
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',
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
function aliasTab.grid:draw()
|
||||
self.values = { }
|
||||
local env = Config.load('shell')
|
||||
for k in pairs(kernel.getShell().aliases()) do
|
||||
kernel.getShell().clearAlias(k)
|
||||
end
|
||||
for k,v in pairs(env.aliases) do
|
||||
table.insert(self.values, { alias = k, path = v })
|
||||
kernel.getShell().setAlias(k, v)
|
||||
end
|
||||
self:update()
|
||||
UI.Grid.draw(self)
|
||||
end
|
||||
|
||||
function aliasTab:eventHandler(event)
|
||||
if event.type == 'delete_alias' then
|
||||
local env = Config.load('shell', { aliases = { } })
|
||||
env.aliases[self.grid:getSelected().alias] = nil
|
||||
Config.update('shell', env)
|
||||
self.grid:setIndex(self.grid:getIndex())
|
||||
self.grid:draw()
|
||||
self:emit({ type = 'success_message', message = 'Aliases updated' })
|
||||
return true
|
||||
|
||||
elseif event.type == 'new_alias' then
|
||||
local env = Config.load('shell', { aliases = { } })
|
||||
env.aliases[self.alias.value] = self.path.value
|
||||
Config.update('shell', env)
|
||||
self.alias:reset()
|
||||
self.path:reset()
|
||||
self:draw()
|
||||
self:setFocus(self.alias)
|
||||
self:emit({ type = 'success_message', message = 'Aliases updated' })
|
||||
return true
|
||||
end
|
||||
end
|
||||
|
||||
return aliasTab
|
80
sys/apps/system/alternate.lua
Normal file
80
sys/apps/system/alternate.lua
Normal file
@ -0,0 +1,80 @@
|
||||
local Array = require('opus.array')
|
||||
local Config = require('opus.config')
|
||||
local UI = require('opus.ui')
|
||||
|
||||
local colors = _G.colors
|
||||
|
||||
local tab = UI.Tab {
|
||||
tabTitle = 'Preferred',
|
||||
description = 'Select preferred applications',
|
||||
apps = UI.ScrollingGrid {
|
||||
x = 2, y = 2,
|
||||
ex = 12, ey = -3,
|
||||
columns = {
|
||||
{ key = 'name' },
|
||||
},
|
||||
sortColumn = 'name',
|
||||
disableHeader = true,
|
||||
},
|
||||
choices = UI.Grid {
|
||||
x = 14, y = 2,
|
||||
ex = -2, ey = -3,
|
||||
disableHeader = true,
|
||||
columns = {
|
||||
{ key = 'file' },
|
||||
}
|
||||
},
|
||||
statusBar = UI.StatusBar {
|
||||
values = 'Double-click to set as preferred'
|
||||
},
|
||||
}
|
||||
|
||||
function tab.choices:getRowTextColor(row)
|
||||
if row == self.values[1] then
|
||||
return colors.yellow
|
||||
end
|
||||
return UI.Grid.getRowTextColor(self, row)
|
||||
end
|
||||
|
||||
function tab:updateChoices()
|
||||
local app = self.apps:getSelected().name
|
||||
local choices = { }
|
||||
for _, v in pairs(self.config[app]) do
|
||||
table.insert(choices, { file = v })
|
||||
end
|
||||
self.choices:setValues(choices)
|
||||
self.choices:draw()
|
||||
end
|
||||
|
||||
function tab:enable()
|
||||
self.config = Config.load('alternate')
|
||||
|
||||
local apps = { }
|
||||
for k, _ in pairs(self.config) do
|
||||
table.insert(apps, { name = k })
|
||||
end
|
||||
self.apps:setValues(apps)
|
||||
|
||||
self:updateChoices()
|
||||
|
||||
UI.Tab.enable(self)
|
||||
end
|
||||
|
||||
function tab:eventHandler(event)
|
||||
if event.type == 'grid_focus_row' and event.element == self.apps then
|
||||
self:updateChoices()
|
||||
|
||||
elseif event.type == 'grid_select' and event.element == self.choices then
|
||||
local app = self.apps:getSelected().name
|
||||
Array.removeByValue(self.config[app], event.selected.file)
|
||||
table.insert(self.config[app], 1, event.selected.file)
|
||||
self:updateChoices()
|
||||
Config.update('alternate', self.config)
|
||||
|
||||
else
|
||||
return UI.Tab.eventHandler(self, event)
|
||||
end
|
||||
return true
|
||||
end
|
||||
|
||||
return tab
|
57
sys/apps/system/cloud.lua
Normal file
57
sys/apps/system/cloud.lua
Normal file
@ -0,0 +1,57 @@
|
||||
local Ansi = require('opus.ansi')
|
||||
local Config = require('opus.config')
|
||||
local UI = require('opus.ui')
|
||||
|
||||
local colors = _G.colors
|
||||
|
||||
-- -t80x30
|
||||
|
||||
if _G.http.websocket then
|
||||
local config = Config.load('cloud')
|
||||
|
||||
local tab = UI.Tab {
|
||||
tabTitle = 'Cloud',
|
||||
description = 'Cloud Catcher options',
|
||||
key = UI.TextEntry {
|
||||
x = 3, ex = -3, y = 2,
|
||||
limit = 32,
|
||||
value = config.key,
|
||||
shadowText = 'Cloud key',
|
||||
accelerators = {
|
||||
enter = 'update_key',
|
||||
},
|
||||
},
|
||||
button = UI.Button {
|
||||
x = 3, y = 4,
|
||||
text = 'Update',
|
||||
event = 'update_key',
|
||||
},
|
||||
labelText = UI.TextArea {
|
||||
x = 3, ex = -3, y = 6,
|
||||
textColor = colors.yellow,
|
||||
marginLeft = 0, marginRight = 0,
|
||||
value = string.format(
|
||||
[[Use a non-changing cloud key. Note that only a single computer can use this session at one time.
|
||||
To obtain a key, visit:
|
||||
%shttps://cloud-catcher.squiddev.cc%s then bookmark:
|
||||
%shttps://cloud-catcher.squiddev.cc/?id=KEY
|
||||
]],
|
||||
Ansi.white, Ansi.reset, Ansi.white),
|
||||
},
|
||||
}
|
||||
|
||||
function tab:eventHandler(event)
|
||||
if event.type == 'update_key' then
|
||||
if #self.key.value > 0 then
|
||||
config.key = self.key.value
|
||||
else
|
||||
config.key = nil
|
||||
end
|
||||
Config.update('cloud', config)
|
||||
self:emit({ type = 'success_message', message = 'Updated' })
|
||||
end
|
||||
end
|
||||
|
||||
return tab
|
||||
end
|
||||
|
152
sys/apps/system/diskusage.lua
Normal file
152
sys/apps/system/diskusage.lua
Normal file
@ -0,0 +1,152 @@
|
||||
local UI = require('opus.ui')
|
||||
local Event = require('opus.event')
|
||||
local NFT = require('opus.nft')
|
||||
|
||||
local colors = _G.colors
|
||||
local fs = _G.fs
|
||||
local os = _G.os
|
||||
local peripheral = _G.peripheral
|
||||
|
||||
local NftImages = {
|
||||
blank = '\0308\0317\153\153\153\153\153\153\153\153\010\0307\0318\153\153\153\153\153\153\153\153\010\0308\0317\153\153\153\153\153\153\153\153\010\0307\0318\153\153\153\153\153\153\153\153\010\0308\0317\153\153\153\153\153\153\153\153',
|
||||
drive = '\030 \031 \030b\031b\128\0308\0318\128\128\030f\149\030b\149\031 \139\010\030 \031 \030b\031b\128\128\128\128\128\128\010\030 \031 \030b\031b\128\0300\0317____\030b\031b\128\010\030 \031 \030b\031b\128\0300\0317____\030b\031b\128',
|
||||
ram = '\030 \031 \128\0318\144\144\144\144\144\031 \128\010\0308\031 \157\0307\0317\128\128\128\128\128\030 \0318\145\010\030 \0318\136\0307\0317\128\0307\0310RAM\0307\128\030 \0318\132\010\0308\031 \157\0307\0317\128\128\128\128\128\030 \0318\145\010\030 \031 \128\0318\129\129\129\129\129\031 \128',
|
||||
rom = '\030 \031 \128\0318\144\144\144\144\144\031 \128\010\0308\031 \157\0307\0317\128\128\128\128\128\030 \0318\145\010\030 \0318\136\0307\0317\128\0307\0310ROM\0307\128\030 \0318\132\010\0308\031 \157\0307\0317\128\128\128\128\128\030 \0318\145\010\030 \031 \128\0318\129\129\129\129\129\031 \128',
|
||||
hdd = '\030 \031 \0307\0317\128\0300\135\131\139\0307\128\010\030 \031 \0300\0317\149\0310\128\0307\131\0300\128\0307\149\010\030 \031 \0307\0310\130\0300\0317\144\0308\0310\133\0307\159\129\010\030 \031 \0308\0317\149\129\142\159\0307\128\010\030 \031 \030 \0317\143\143\143\143\143',
|
||||
}
|
||||
|
||||
local tab = UI.Tab {
|
||||
tabTitle = 'Disks Usage',
|
||||
description = 'Visualise HDD and disks usage',
|
||||
|
||||
drives = UI.ScrollingGrid {
|
||||
x = 2, y = 1,
|
||||
ex = '47%', ey = -7,
|
||||
columns = {
|
||||
{ heading = 'Drive', key = 'name' },
|
||||
{ heading = 'Side' ,key = 'side', textColor = colors.yellow }
|
||||
},
|
||||
sortColumn = 'name',
|
||||
},
|
||||
infos = UI.Grid {
|
||||
x = '52%', y = 2,
|
||||
ex = -2, ey = -4,
|
||||
disableHeader = true,
|
||||
unfocusedBackgroundSelectedColor = colors.black,
|
||||
inactive = true,
|
||||
backgroundSelectedColor = colors.black,
|
||||
columns = {
|
||||
{ key = 'name' },
|
||||
{ key = 'value', align = 'right', textColor = colors.yellow },
|
||||
}
|
||||
},
|
||||
|
||||
progress = UI.ProgressBar {
|
||||
x = 11, y = -2,
|
||||
ex = -2,
|
||||
},
|
||||
percentage = UI.Text {
|
||||
x = 11, y = -3,
|
||||
ex = '47%',
|
||||
align = 'center',
|
||||
},
|
||||
icon = UI.NftImage {
|
||||
x = 2, y = -5,
|
||||
image = NFT.parse(NftImages.blank)
|
||||
},
|
||||
}
|
||||
|
||||
local function getDrives()
|
||||
local unique = { ['hdd'] = true, ['virt'] = true }
|
||||
local drives = { { name = 'hdd', side = '' } }
|
||||
|
||||
for _, drive in pairs(fs.list('/')) do
|
||||
local side = fs.getDrive(drive)
|
||||
if side and not unique[side] then
|
||||
unique[side] = true
|
||||
table.insert(drives, { name = drive, side = side })
|
||||
end
|
||||
end
|
||||
return drives
|
||||
end
|
||||
|
||||
local function getDriveInfo(p)
|
||||
local files, dirs, total = 0, 0, 0
|
||||
|
||||
if p == "hdd" then p = "/" end
|
||||
p = fs.combine(p, '')
|
||||
local drive = fs.getDrive(p)
|
||||
|
||||
local function recurse(path)
|
||||
if fs.getDrive(path) == drive then
|
||||
if fs.isDir(path) then
|
||||
if path ~= p then
|
||||
total = total + 500
|
||||
dirs = dirs + 1
|
||||
end
|
||||
for _, v in pairs(fs.list(path)) do
|
||||
recurse(fs.combine(path, v))
|
||||
end
|
||||
else
|
||||
local sz = fs.getSize(path)
|
||||
files = files + 1
|
||||
if drive == 'rom' then
|
||||
total = total + sz
|
||||
else
|
||||
total = total + math.max(500, sz)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
recurse(p)
|
||||
|
||||
local info = {}
|
||||
table.insert(info, { name = 'Type', value = peripheral.getType(drive) or drive })
|
||||
table.insert(info, { name = 'Used', value = total })
|
||||
table.insert(info, { name = 'Total', value = total + fs.getFreeSpace(p) })
|
||||
table.insert(info, { name = 'Free', value = fs.getFreeSpace(p) })
|
||||
table.insert(info, { })
|
||||
table.insert(info, { name = 'Files', value = files })
|
||||
table.insert(info, { name = 'Dirs', value = dirs })
|
||||
return info, math.floor((total / (total + fs.getFreeSpace(p))) * 100)
|
||||
end
|
||||
|
||||
function tab:updateInfo()
|
||||
local selected = self.drives:getSelected()
|
||||
local info, percent = getDriveInfo(selected and selected.name or self.drives.values[1].name)
|
||||
self.infos:setValues(info)
|
||||
self.progress.value = percent
|
||||
self.percentage.value = ('%#3d%%'):format(percent)
|
||||
self.icon.image = NFT.parse(NftImages[info[1].value] or NftImages.blank)
|
||||
self:draw()
|
||||
end
|
||||
|
||||
function tab:updateDrives()
|
||||
local drives = getDrives()
|
||||
self.drives:setValues(drives)
|
||||
end
|
||||
|
||||
function tab:enable()
|
||||
self:updateDrives()
|
||||
self:updateInfo()
|
||||
UI.Tab.enable(self)
|
||||
end
|
||||
|
||||
function tab:eventHandler(event)
|
||||
if event.type == 'grid_focus_row' then
|
||||
self:updateInfo()
|
||||
else
|
||||
return UI.Tab.eventHandler(self, event)
|
||||
end
|
||||
return true
|
||||
end
|
||||
|
||||
Event.on({ 'disk', 'disk_eject' }, function()
|
||||
os.sleep(1)
|
||||
tab:updateDrives()
|
||||
tab:updateInfo()
|
||||
tab:sync()
|
||||
end)
|
||||
|
||||
return tab
|
62
sys/apps/system/kiosk.lua
Normal file
62
sys/apps/system/kiosk.lua
Normal file
@ -0,0 +1,62 @@
|
||||
local UI = require('opus.ui')
|
||||
|
||||
local colors = _G.colors
|
||||
local peripheral = _G.peripheral
|
||||
local settings = _G.settings
|
||||
|
||||
local tab = UI.Tab {
|
||||
tabTitle = 'Kiosk',
|
||||
description = 'Kiosk options',
|
||||
form = UI.Form {
|
||||
x = 2, ex = -2,
|
||||
manualControls = true,
|
||||
monitor = UI.Chooser {
|
||||
formLabel = 'Monitor', formKey = 'monitor',
|
||||
},
|
||||
textScale = UI.Chooser {
|
||||
formLabel = 'Font Size', formKey = 'textScale',
|
||||
nochoice = 'Small',
|
||||
choices = {
|
||||
{ name = 'Small', value = '.5' },
|
||||
{ name = 'Large', value = '1' },
|
||||
},
|
||||
help = 'Adjust text scaling',
|
||||
},
|
||||
labelText = UI.TextArea {
|
||||
x = 2, ex = -2, y = 5,
|
||||
textColor = colors.yellow,
|
||||
value = 'Settings apply to kiosk mode selected during startup'
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
function tab:enable()
|
||||
local choices = { }
|
||||
|
||||
peripheral.find('monitor', function(side)
|
||||
table.insert(choices, { name = side, value = side })
|
||||
end)
|
||||
|
||||
self.form.monitor.choices = choices
|
||||
self.form.monitor.value = settings.get('kiosk.monitor')
|
||||
|
||||
self.form.textScale.value = settings.get('kiosk.textscale')
|
||||
|
||||
UI.Tab.enable(self)
|
||||
end
|
||||
|
||||
function tab:eventHandler(event)
|
||||
if event.type == 'choice_change' then
|
||||
if self.form.monitor.value then
|
||||
settings.set('kiosk.monitor', self.form.monitor.value)
|
||||
end
|
||||
if self.form.textScale.value then
|
||||
settings.set('kiosk.textscale', self.form.textScale.value)
|
||||
end
|
||||
settings.save('.settings')
|
||||
end
|
||||
end
|
||||
|
||||
if peripheral.find('monitor') then
|
||||
return tab
|
||||
end
|
49
sys/apps/system/label.lua
Normal file
49
sys/apps/system/label.lua
Normal file
@ -0,0 +1,49 @@
|
||||
local UI = require('opus.ui')
|
||||
local Util = require('opus.util')
|
||||
|
||||
local fs = _G.fs
|
||||
local os = _G.os
|
||||
|
||||
local labelTab = UI.Tab {
|
||||
tabTitle = 'Label',
|
||||
description = 'Set the computer label',
|
||||
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' },
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
function labelTab:eventHandler(event)
|
||||
if event.type == 'update_label' then
|
||||
os.setComputerLabel(self.label.value)
|
||||
self:emit({ type = 'success_message', message = 'Label updated' })
|
||||
return true
|
||||
end
|
||||
end
|
||||
|
||||
return labelTab
|
84
sys/apps/system/launcher.lua
Normal file
84
sys/apps/system/launcher.lua
Normal file
@ -0,0 +1,84 @@
|
||||
local Config = require('opus.config')
|
||||
local UI = require('opus.ui')
|
||||
|
||||
local colors = _G.colors
|
||||
local fs = _G.fs
|
||||
|
||||
local config = Config.load('multishell')
|
||||
|
||||
local tab = UI.Tab {
|
||||
tabTitle = 'Launcher',
|
||||
description = 'Set the application launcher',
|
||||
launcherLabel = UI.Text {
|
||||
x = 3, y = 2,
|
||||
value = 'Launcher',
|
||||
},
|
||||
launcher = UI.Chooser {
|
||||
x = 13, y = 2, width = 12,
|
||||
choices = {
|
||||
{ name = 'Overview', value = 'sys/apps/Overview.lua' },
|
||||
{ name = 'Shell', value = 'sys/apps/ShellLauncher.lua' },
|
||||
{ name = 'Custom', value = 'custom' },
|
||||
},
|
||||
},
|
||||
custom = UI.TextEntry {
|
||||
x = 13, ex = -3, y = 3,
|
||||
limit = 128,
|
||||
shadowText = 'File name',
|
||||
},
|
||||
button = UI.Button {
|
||||
x = 3, y = 5,
|
||||
text = 'Update',
|
||||
event = 'update',
|
||||
},
|
||||
labelText = UI.TextArea {
|
||||
x = 3, ex = -3, y = 7,
|
||||
textColor = colors.yellow,
|
||||
value = 'Choose an application launcher',
|
||||
},
|
||||
}
|
||||
|
||||
function tab:enable()
|
||||
local launcher = config.launcher and 'custom' or 'sys/apps/Overview.lua'
|
||||
|
||||
for _, v in pairs(self.launcher.choices) do
|
||||
if v.value == config.launcher then
|
||||
launcher = v.value
|
||||
break
|
||||
end
|
||||
end
|
||||
|
||||
UI.Tab.enable(self)
|
||||
|
||||
self.launcher.value = launcher
|
||||
self.custom.enabled = launcher == 'custom'
|
||||
end
|
||||
|
||||
function tab:eventHandler(event)
|
||||
if event.type == 'choice_change' then
|
||||
self.custom.enabled = event.value == 'custom'
|
||||
if self.custom.enabled then
|
||||
self.custom.value = config.launcher
|
||||
end
|
||||
self:draw()
|
||||
|
||||
elseif event.type == 'update' then
|
||||
local launcher
|
||||
|
||||
if self.launcher.value ~= 'custom' then
|
||||
launcher = self.launcher.value
|
||||
elseif fs.exists(self.custom.value) and not fs.isDir(self.custom.value) then
|
||||
launcher = self.custom.value
|
||||
end
|
||||
|
||||
if launcher then
|
||||
config.launcher = launcher
|
||||
Config.update('multishell', config)
|
||||
self:emit({ type = 'success_message', message = 'Updated' })
|
||||
else
|
||||
self:emit({ type = 'error_message', message = 'Invalid file' })
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
return tab
|
60
sys/apps/system/network.lua
Normal file
60
sys/apps/system/network.lua
Normal file
@ -0,0 +1,60 @@
|
||||
local Ansi = require('opus.ansi')
|
||||
local Config = require('opus.config')
|
||||
local UI = require('opus.ui')
|
||||
|
||||
local device = _G.device
|
||||
|
||||
local tab = UI.Tab {
|
||||
tabTitle = 'Network',
|
||||
description = 'Networking options',
|
||||
info = UI.TextArea {
|
||||
x = 3, y = 4,
|
||||
value = string.format(
|
||||
[[%sSet the primary modem used for wireless communications.%s
|
||||
|
||||
Reboot to take effect.]], Ansi.yellow, Ansi.reset)
|
||||
},
|
||||
label = UI.Text {
|
||||
x = 3, y = 2,
|
||||
value = 'Modem',
|
||||
},
|
||||
modem = UI.Chooser {
|
||||
x = 10, ex = -3, y = 2,
|
||||
nochoice = 'auto',
|
||||
},
|
||||
}
|
||||
|
||||
function tab:enable()
|
||||
local width = 7
|
||||
local choices = {
|
||||
{ name = 'auto', value = 'auto' },
|
||||
{ name = 'disable', value = 'none' },
|
||||
}
|
||||
|
||||
for k,v in pairs(device) do
|
||||
if v.isWireless and v.isWireless() and k ~= 'wireless_modem' then
|
||||
table.insert(choices, { name = k, value = v.name })
|
||||
width = math.max(width, #k)
|
||||
end
|
||||
end
|
||||
|
||||
self.modem.choices = choices
|
||||
--self.modem.width = width + 4
|
||||
|
||||
local config = Config.load('os')
|
||||
self.modem.value = config.wirelessModem or 'auto'
|
||||
|
||||
UI.Tab.enable(self)
|
||||
end
|
||||
|
||||
function tab:eventHandler(event)
|
||||
if event.type == 'choice_change' then
|
||||
local config = Config.load('os')
|
||||
config.wirelessModem = self.modem.value
|
||||
Config.update('os', config)
|
||||
self:emit({ type = 'success_message', message = 'reboot to take effect' })
|
||||
return true
|
||||
end
|
||||
end
|
||||
|
||||
return tab
|
44
sys/apps/system/password.lua
Normal file
44
sys/apps/system/password.lua
Normal file
@ -0,0 +1,44 @@
|
||||
local Security = require('opus.security')
|
||||
local SHA = require('opus.crypto.sha2')
|
||||
local UI = require('opus.ui')
|
||||
|
||||
local colors = _G.colors
|
||||
|
||||
local passwordTab = UI.Tab {
|
||||
tabTitle = 'Password',
|
||||
description = 'Wireless network password',
|
||||
newPass = UI.TextEntry {
|
||||
x = 3, ex = -3, y = 3,
|
||||
limit = 32,
|
||||
mask = true,
|
||||
shadowText = 'new password',
|
||||
accelerators = {
|
||||
enter = 'new_password',
|
||||
},
|
||||
},
|
||||
button = UI.Button {
|
||||
x = 3, y = 5,
|
||||
text = 'Update',
|
||||
event = 'update_password',
|
||||
},
|
||||
info = UI.TextArea {
|
||||
x = 3, ex = -3, y = 7,
|
||||
textColor = colors.yellow,
|
||||
inactive = true,
|
||||
value = 'Add a password to enable other computers to connect to this one.',
|
||||
}
|
||||
}
|
||||
function passwordTab:eventHandler(event)
|
||||
if event.type == 'update_password' then
|
||||
if not self.newPass.value or #self.newPass.value == 0 then
|
||||
self:emit({ type = 'error_message', message = 'Invalid password' })
|
||||
|
||||
else
|
||||
Security.updatePassword(SHA.compute(self.newPass.value))
|
||||
self:emit({ type = 'success_message', message = 'Password updated' })
|
||||
end
|
||||
return true
|
||||
end
|
||||
end
|
||||
|
||||
return passwordTab
|
103
sys/apps/system/path.lua
Normal file
103
sys/apps/system/path.lua
Normal file
@ -0,0 +1,103 @@
|
||||
local Config = require('opus.config')
|
||||
local UI = require('opus.ui')
|
||||
local Util = require('opus.util')
|
||||
|
||||
local tab = UI.Tab {
|
||||
tabTitle = 'Path',
|
||||
description = 'Set the shell path',
|
||||
tabClose = true,
|
||||
entry = UI.TextEntry {
|
||||
x = 2, y = 2, ex = -2,
|
||||
limit = 256,
|
||||
shadowText = 'enter new path',
|
||||
accelerators = {
|
||||
enter = 'update_path',
|
||||
},
|
||||
help = 'add a new path',
|
||||
},
|
||||
grid = UI.Grid {
|
||||
y = 4, ey = -3,
|
||||
disableHeader = true,
|
||||
columns = { { key = 'value' } },
|
||||
autospace = true,
|
||||
sortColumn = 'index',
|
||||
help = 'double-click to remove, shift-arrow to move',
|
||||
accelerators = {
|
||||
delete = 'remove',
|
||||
},
|
||||
},
|
||||
statusBar = UI.StatusBar { },
|
||||
accelerators = {
|
||||
[ 'shift-up' ] = 'move_up',
|
||||
[ 'shift-down' ] = 'move_down',
|
||||
},
|
||||
}
|
||||
|
||||
function tab:updateList(path)
|
||||
self.grid.values = { }
|
||||
for k,v in ipairs(Util.split(path, '(.-):')) do
|
||||
table.insert(self.grid.values, { index = k, value = v })
|
||||
end
|
||||
self.grid:update()
|
||||
end
|
||||
|
||||
function tab:enable()
|
||||
local env = Config.load('shell')
|
||||
self:updateList(env.path)
|
||||
UI.Tab.enable(self)
|
||||
end
|
||||
|
||||
function tab:save()
|
||||
local t = { }
|
||||
for _, v in ipairs(self.grid.values) do
|
||||
table.insert(t, v.value)
|
||||
end
|
||||
local env = Config.load('shell')
|
||||
env.path = table.concat(t, ':')
|
||||
self:updateList(env.path)
|
||||
Config.update('shell', env)
|
||||
end
|
||||
|
||||
function tab:eventHandler(event)
|
||||
if event.type == 'update_path' then
|
||||
table.insert(self.grid.values, {
|
||||
value = self.entry.value,
|
||||
})
|
||||
self:save()
|
||||
self.entry:reset()
|
||||
self.entry:draw()
|
||||
self.grid:draw()
|
||||
return true
|
||||
|
||||
elseif event.type == 'grid_select' or event.type == 'remove' then
|
||||
local selected = self.grid:getSelected()
|
||||
if selected then
|
||||
table.remove(self.grid.values, selected.index)
|
||||
self:save()
|
||||
self.grid:draw()
|
||||
end
|
||||
|
||||
elseif event.type == 'focus_change' then
|
||||
self.statusBar:setStatus(event.focused.help)
|
||||
|
||||
elseif event.type == 'move_up' then
|
||||
local entry = self.grid:getSelected()
|
||||
if entry.index > 1 then
|
||||
table.insert(self.grid.values, entry.index - 1, table.remove(self.grid.values, entry.index))
|
||||
self.grid:setIndex(entry.index - 1)
|
||||
self:save()
|
||||
self.grid:draw()
|
||||
end
|
||||
|
||||
elseif event.type == 'move_down' then
|
||||
local entry = self.grid:getSelected()
|
||||
if entry.index < #self.grid.values then
|
||||
table.insert(self.grid.values, entry.index + 1, table.remove(self.grid.values, entry.index))
|
||||
self.grid:setIndex(entry.index + 1)
|
||||
self:save()
|
||||
self.grid:draw()
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
return tab
|
104
sys/apps/system/requires.lua
Normal file
104
sys/apps/system/requires.lua
Normal file
@ -0,0 +1,104 @@
|
||||
local Config = require('opus.config')
|
||||
local UI = require('opus.ui')
|
||||
local Util = require('opus.util')
|
||||
|
||||
local tab = UI.Tab {
|
||||
tabTitle = 'Requires',
|
||||
description = 'Require path',
|
||||
tabClose = true,
|
||||
entry = UI.TextEntry {
|
||||
x = 2, y = 2, ex = -2,
|
||||
limit = 256,
|
||||
shadowText = 'Enter new require path',
|
||||
accelerators = {
|
||||
enter = 'update_path',
|
||||
},
|
||||
help = 'add a new path (reboot required)',
|
||||
},
|
||||
grid = UI.Grid {
|
||||
y = 4, ey = -3,
|
||||
disableHeader = true,
|
||||
columns = { { key = 'value' } },
|
||||
autospace = true,
|
||||
sortColumn = 'index',
|
||||
help = 'double-click to remove, shift-arrow to move',
|
||||
accelerators = {
|
||||
delete = 'remove',
|
||||
},
|
||||
},
|
||||
statusBar = UI.StatusBar { },
|
||||
accelerators = {
|
||||
[ 'shift-up' ] = 'move_up',
|
||||
[ 'shift-down' ] = 'move_down',
|
||||
},
|
||||
}
|
||||
|
||||
function tab:updateList(lua_path)
|
||||
self.grid.values = { }
|
||||
for k,v in ipairs(Util.split(lua_path, '(.-);')) do
|
||||
table.insert(self.grid.values, { index = k, value = v })
|
||||
end
|
||||
self.grid:update()
|
||||
end
|
||||
|
||||
function tab:enable()
|
||||
local env = Config.load('shell')
|
||||
self:updateList(env.lua_path)
|
||||
UI.Tab.enable(self)
|
||||
end
|
||||
|
||||
function tab:save()
|
||||
local t = { }
|
||||
for _, v in ipairs(self.grid.values) do
|
||||
table.insert(t, v.value)
|
||||
end
|
||||
local env = Config.load('shell')
|
||||
env.lua_path = table.concat(t, ';')
|
||||
self:updateList(env.lua_path)
|
||||
Config.update('shell', env)
|
||||
end
|
||||
|
||||
function tab:eventHandler(event)
|
||||
if event.type == 'update_path' then
|
||||
table.insert(self.grid.values, {
|
||||
value = self.entry.value,
|
||||
})
|
||||
self:save()
|
||||
self.entry:reset()
|
||||
self.entry:draw()
|
||||
self.grid:draw()
|
||||
return true
|
||||
|
||||
elseif event.type == 'grid_select' or event.type == 'remove' then
|
||||
local selected = self.grid:getSelected()
|
||||
if selected then
|
||||
table.remove(self.grid.values, selected.index)
|
||||
self:save()
|
||||
self.grid:draw()
|
||||
end
|
||||
|
||||
elseif event.type == 'focus_change' then
|
||||
self.statusBar:setStatus(event.focused.help)
|
||||
|
||||
elseif event.type == 'move_up' then
|
||||
local entry = self.grid:getSelected()
|
||||
if entry.index > 1 then
|
||||
table.insert(self.grid.values, entry.index - 1, table.remove(self.grid.values, entry.index))
|
||||
self.grid:setIndex(entry.index - 1)
|
||||
self:save()
|
||||
self.grid:draw()
|
||||
end
|
||||
|
||||
elseif event.type == 'move_down' then
|
||||
local entry = self.grid:getSelected()
|
||||
if entry.index < #self.grid.values then
|
||||
table.insert(self.grid.values, entry.index + 1, table.remove(self.grid.values, entry.index))
|
||||
self.grid:setIndex(entry.index + 1)
|
||||
self:save()
|
||||
self.grid:draw()
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
--this needs rework - see 4.user.lua
|
||||
--return tab
|
49
sys/apps/system/settings.lua
Normal file
49
sys/apps/system/settings.lua
Normal file
@ -0,0 +1,49 @@
|
||||
local UI = require('opus.ui')
|
||||
|
||||
local settings = _G.settings
|
||||
|
||||
if settings then
|
||||
local settingsTab = UI.Tab {
|
||||
tabTitle = 'Settings',
|
||||
description = 'Computercraft configurable settings',
|
||||
grid = UI.Grid {
|
||||
y = 2,
|
||||
autospace = true,
|
||||
sortColumn = 'name',
|
||||
columns = {
|
||||
{ heading = 'Setting', key = 'name' },
|
||||
{ heading = 'Value', key = 'value' },
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
function settingsTab:enable()
|
||||
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
|
||||
self.grid:setValues(values)
|
||||
UI.Tab.enable(self)
|
||||
end
|
||||
|
||||
function 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
|
||||
|
||||
return settingsTab
|
||||
end
|
142
sys/apps/system/shell.lua
Normal file
142
sys/apps/system/shell.lua
Normal file
@ -0,0 +1,142 @@
|
||||
local Config = require('opus.config')
|
||||
local UI = require('opus.ui')
|
||||
local Util = require('opus.util')
|
||||
|
||||
local colors = _G.colors
|
||||
local os = _G.os
|
||||
|
||||
local config = Config.load('shellprompt')
|
||||
|
||||
local allColors = { }
|
||||
for k,v in pairs(colors) do
|
||||
if type(v) == 'number' then
|
||||
table.insert(allColors, { name = k, value = v })
|
||||
end
|
||||
end
|
||||
|
||||
local defaults = {
|
||||
textColor = colors.white,
|
||||
commandTextColor = colors.yellow,
|
||||
directoryTextColor = colors.orange,
|
||||
directoryBackgroundColor = colors.black,
|
||||
promptTextColor = colors.blue,
|
||||
promptBackgroundColor = colors.black,
|
||||
directoryColor = colors.green,
|
||||
fileColor = colors.white,
|
||||
backgroundColor = colors.black,
|
||||
}
|
||||
local _colors = config.color or Util.shallowCopy(defaults)
|
||||
|
||||
local allSettings = { }
|
||||
for k, v in pairs(defaults) do
|
||||
table.insert(allSettings, { name = k })
|
||||
end
|
||||
|
||||
-- temp
|
||||
if not _colors.backgroundColor then
|
||||
_colors.backgroundColor = colors.black
|
||||
_colors.fileColor = colors.white
|
||||
end
|
||||
|
||||
local tab = UI.Tab {
|
||||
tabTitle = 'Shell',
|
||||
description = 'Shell options',
|
||||
grid1 = UI.ScrollingGrid {
|
||||
y = 2, ey = -10, x = 3, ex = -16,
|
||||
disableHeader = true,
|
||||
columns = { { key = 'name' } },
|
||||
values = allSettings,
|
||||
sortColumn = 'name',
|
||||
},
|
||||
grid2 = UI.ScrollingGrid {
|
||||
y = 2, ey = -10, x = -14, ex = -3,
|
||||
disableHeader = true,
|
||||
columns = { { key = 'name' } },
|
||||
values = allColors,
|
||||
sortColumn = 'name',
|
||||
},
|
||||
directoryLabel = UI.Text {
|
||||
x = 2, y = -2,
|
||||
value = 'Display directory',
|
||||
},
|
||||
directory = UI.Checkbox {
|
||||
x = 20, y = -2,
|
||||
value = config.displayDirectory
|
||||
},
|
||||
reset = UI.Button {
|
||||
x = -18, y = -2,
|
||||
text = 'Reset',
|
||||
event = 'reset',
|
||||
},
|
||||
button = UI.Button {
|
||||
x = -9, y = -2,
|
||||
text = 'Update',
|
||||
event = 'update',
|
||||
},
|
||||
display = UI.Window {
|
||||
x = 3, ex = -3, y = -8, height = 5,
|
||||
},
|
||||
}
|
||||
|
||||
function tab.grid2:getRowTextColor(row)
|
||||
local selected = tab.grid1:getSelected()
|
||||
if _colors[selected.name] == row.value then
|
||||
return colors.yellow
|
||||
end
|
||||
return UI.Grid.getRowTextColor(self, row)
|
||||
end
|
||||
|
||||
function tab.display:draw()
|
||||
self:clear(_colors.backgroundColor)
|
||||
local offset = 0
|
||||
if config.displayDirectory then
|
||||
self:write(1, 1,
|
||||
'==' .. os.getComputerLabel() .. ':/dir/etc',
|
||||
_colors.directoryBackgroundColor, _colors.directoryTextColor)
|
||||
offset = 1
|
||||
end
|
||||
|
||||
self:write(1, 1 + offset, '$ ',
|
||||
_colors.promptBackgroundColor, _colors.promptTextColor)
|
||||
|
||||
self:write(3, 1 + offset, 'ls /',
|
||||
_colors.backgroundColor, _colors.commandTextColor)
|
||||
|
||||
self:write(1, 2 + offset, 'sys usr',
|
||||
_colors.backgroundColor, _colors.directoryColor)
|
||||
|
||||
self:write(1, 3 + offset, 'startup',
|
||||
_colors.backgroundColor, _colors.fileColor)
|
||||
end
|
||||
|
||||
function tab:eventHandler(event)
|
||||
if event.type =='checkbox_change' then
|
||||
config.displayDirectory = not not event.checked
|
||||
self.display:draw()
|
||||
|
||||
elseif event.type == 'grid_focus_row' and event.element == self.grid1 then
|
||||
self.grid2:draw()
|
||||
|
||||
elseif event.type == 'grid_select' and event.element == self.grid2 then
|
||||
_colors[tab.grid1:getSelected().name] = event.selected.value
|
||||
self.display:draw()
|
||||
self.grid2:draw()
|
||||
|
||||
elseif event.type == 'reset' then
|
||||
config.color = defaults
|
||||
config.displayDirectory = true
|
||||
self.directory.value = true
|
||||
_colors = Util.shallowCopy(defaults)
|
||||
|
||||
Config.update('shellprompt', config)
|
||||
self:draw()
|
||||
|
||||
elseif event.type == 'update' then
|
||||
config.color = _colors
|
||||
Config.update('shellprompt', config)
|
||||
|
||||
end
|
||||
return UI.Tab.eventHandler(self, event)
|
||||
end
|
||||
|
||||
return tab
|
@ -1,16 +1,15 @@
|
||||
_G.requireInjector(_ENV)
|
||||
|
||||
local Event = require('event')
|
||||
local Socket = require('socket')
|
||||
local Terminal = require('terminal')
|
||||
local Util = require('util')
|
||||
local Event = require('opus.event')
|
||||
local Socket = require('opus.socket')
|
||||
local Terminal = require('opus.terminal')
|
||||
local Util = require('opus.util')
|
||||
|
||||
local multishell = _ENV.multishell
|
||||
local os = _G.os
|
||||
local read = _G.read
|
||||
local shell = _ENV.shell
|
||||
local term = _G.term
|
||||
|
||||
local args = { ... }
|
||||
local args, options = Util.parse(...)
|
||||
|
||||
local remoteId = tonumber(table.remove(args, 1) or '')
|
||||
if not remoteId then
|
||||
@ -23,13 +22,25 @@ if not remoteId then
|
||||
end
|
||||
|
||||
if multishell then
|
||||
multishell.setTitle(multishell.getCurrent(), 'Telnet ' .. remoteId)
|
||||
multishell.setTitle(multishell.getCurrent(),
|
||||
(options.s and 'Secure ' or 'Telnet ') .. remoteId)
|
||||
end
|
||||
|
||||
local socket, msg = Socket.connect(remoteId, 23)
|
||||
local socket, msg, reason
|
||||
|
||||
if not socket then
|
||||
error(msg)
|
||||
while true do
|
||||
socket, msg, reason = Socket.connect(remoteId, options.s and 22 or 23)
|
||||
|
||||
if socket then
|
||||
break
|
||||
elseif reason ~= 'NOTRUST' then
|
||||
error(msg)
|
||||
end
|
||||
|
||||
local s, m = shell.run('trust ' .. remoteId)
|
||||
if not s then
|
||||
error(m)
|
||||
end
|
||||
end
|
||||
|
||||
local ct = Util.shallowCopy(term.current())
|
||||
|
@ -1,10 +1,8 @@
|
||||
_G.requireInjector(_ENV)
|
||||
|
||||
local Crypto = require('crypto')
|
||||
local Security = require('security')
|
||||
local SHA1 = require('sha1')
|
||||
local Socket = require('socket')
|
||||
local Terminal = require('terminal')
|
||||
local Crypto = require('opus.crypto.chacha20')
|
||||
local Security = require('opus.security')
|
||||
local SHA = require('opus.crypto.sha2')
|
||||
local Socket = require('opus.socket')
|
||||
local Terminal = require('opus.terminal')
|
||||
|
||||
local os = _G.os
|
||||
|
||||
@ -29,15 +27,16 @@ if not password then
|
||||
end
|
||||
|
||||
print('connecting...')
|
||||
local socket, msg = Socket.connect(remoteId, 19)
|
||||
local trustId = '01c3ba27fe01383a03a1785276d99df27c3edcef68fbf231ca'
|
||||
local socket, msg = Socket.connect(remoteId, 19, { identifier = trustId })
|
||||
|
||||
if not socket then
|
||||
error(msg)
|
||||
end
|
||||
|
||||
local publicKey = Security.getPublicKey()
|
||||
local identifier = Security.getIdentifier()
|
||||
|
||||
socket:write(Crypto.encrypt({ pk = publicKey, dh = os.getComputerID() }, SHA1.sha1(password)))
|
||||
socket:write(Crypto.encrypt({ pk = identifier, dh = os.getComputerID() }, SHA.compute(password)))
|
||||
|
||||
local data = socket:read(2)
|
||||
socket:close()
|
||||
|
@ -1,17 +1,16 @@
|
||||
_G.requireInjector(_ENV)
|
||||
|
||||
local Event = require('event')
|
||||
local Socket = require('socket')
|
||||
local Terminal = require('terminal')
|
||||
local Util = require('util')
|
||||
local Event = require('opus.event')
|
||||
local Socket = require('opus.socket')
|
||||
local Terminal = require('opus.terminal')
|
||||
local Util = require('opus.util')
|
||||
|
||||
local colors = _G.colors
|
||||
local multishell = _ENV.multishell
|
||||
local os = _G.os
|
||||
local shell = _ENV.shell
|
||||
local term = _G.term
|
||||
|
||||
local remoteId
|
||||
local args = { ... }
|
||||
local args, options = Util.parse(...)
|
||||
if #args == 1 then
|
||||
remoteId = tonumber(args[1])
|
||||
else
|
||||
@ -24,11 +23,20 @@ if not remoteId then
|
||||
end
|
||||
|
||||
if multishell then
|
||||
multishell.setTitle(multishell.getCurrent(), 'VNC-' .. remoteId)
|
||||
multishell.setTitle(multishell.getCurrent(),
|
||||
(options.s and 'SVNC-' or 'VNC-') .. remoteId)
|
||||
end
|
||||
|
||||
local function connect()
|
||||
local socket, msg = Socket.connect(remoteId, 5900)
|
||||
local socket, msg, reason = Socket.connect(remoteId, options.s and 5901 or 5900)
|
||||
|
||||
if reason == 'NOTRUST' then
|
||||
local s, m = shell.run('trust ' .. remoteId)
|
||||
if not s then
|
||||
return s, m
|
||||
end
|
||||
socket, msg = Socket.connect(remoteId, 5900)
|
||||
end
|
||||
|
||||
if not socket then
|
||||
return false, msg
|
||||
@ -62,7 +70,7 @@ local function connect()
|
||||
break
|
||||
end
|
||||
for _,v in ipairs(data) do
|
||||
ct[v.f](unpack(v.args))
|
||||
ct[v.f](table.unpack(v.args))
|
||||
end
|
||||
end
|
||||
end)
|
||||
|
@ -1,25 +1,22 @@
|
||||
_G.requireInjector(_ENV)
|
||||
|
||||
local Util = require('util')
|
||||
local Util = require('opus.util')
|
||||
|
||||
local kernel = _G.kernel
|
||||
local keyboard = _G.device.keyboard
|
||||
local os = _G.os
|
||||
local textutils = _G.textutils
|
||||
|
||||
local data
|
||||
|
||||
kernel.hook('clipboard_copy', function(_, args)
|
||||
data = args[1]
|
||||
keyboard.clipboard = args[1]
|
||||
end)
|
||||
|
||||
keyboard.addHotkey('shift-paste', function()
|
||||
local data = keyboard.clipboard
|
||||
|
||||
if type(data) == 'table' then
|
||||
local s, m = pcall(textutils.serialize, data)
|
||||
data = (s and m) or Util.tostring(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
|
||||
|
22
sys/autorun/complete.lua
Normal file
22
sys/autorun/complete.lua
Normal file
@ -0,0 +1,22 @@
|
||||
local function completeMultipleChoice(sText, tOptions, bAddSpaces)
|
||||
local tResults = { }
|
||||
for n = 1,#tOptions do
|
||||
local sOption = tOptions[n]
|
||||
if #sOption + (bAddSpaces and 1 or 0) > #sText and string.sub(sOption, 1, #sText) == sText then
|
||||
local sResult = string.sub(sOption, #sText + 1)
|
||||
if bAddSpaces then
|
||||
table.insert(tResults, sResult .. " ")
|
||||
else
|
||||
table.insert(tResults, sResult)
|
||||
end
|
||||
end
|
||||
end
|
||||
return tResults
|
||||
end
|
||||
|
||||
_ENV.shell.setCompletionFunction("sys/apps/package.lua",
|
||||
function(_, index, text)
|
||||
if index == 1 then
|
||||
return completeMultipleChoice(text, { "install ", "update ", "uninstall ", "updateall ", "refresh" })
|
||||
end
|
||||
end)
|
@ -1,43 +0,0 @@
|
||||
local modem = _G.device.wireless_modem
|
||||
local turtle = _G.turtle
|
||||
|
||||
if turtle and modem then
|
||||
local s, m = turtle.run(function()
|
||||
|
||||
_G.requireInjector(_ENV)
|
||||
|
||||
local Config = require('config')
|
||||
local config = {
|
||||
destructive = false,
|
||||
}
|
||||
Config.load('gps', config)
|
||||
|
||||
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
|
||||
|
||||
if not s then
|
||||
error('Unable to get GPS position')
|
||||
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 s and m then
|
||||
error(m)
|
||||
end
|
||||
end
|
@ -1,19 +0,0 @@
|
||||
if _G.device.wireless_modem then
|
||||
|
||||
_G.requireInjector(_ENV)
|
||||
local Config = require('config')
|
||||
|
||||
local kernel = _G.kernel
|
||||
|
||||
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,11 +1,13 @@
|
||||
_G.requireInjector(_ENV)
|
||||
|
||||
local Util = require('util')
|
||||
local Util = require('opus.util')
|
||||
|
||||
local kernel = _G.kernel
|
||||
local keyboard = _G.device.keyboard
|
||||
local multishell = _ENV.multishell
|
||||
|
||||
if not multishell or not multishell.getTabs then
|
||||
return
|
||||
end
|
||||
|
||||
-- overview
|
||||
keyboard.addHotkey('control-o', function()
|
||||
for _,tab in pairs(multishell.getTabs()) do
|
||||
@ -21,10 +23,12 @@ keyboard.addHotkey('control-backspace', function()
|
||||
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)
|
||||
multishell.openTab({
|
||||
path = tab.path,
|
||||
env = tab.env,
|
||||
args = tab.args,
|
||||
focused = true,
|
||||
})
|
||||
end
|
||||
end)
|
||||
|
||||
@ -38,7 +42,7 @@ keyboard.addHotkey('control-tab', function()
|
||||
return a.uid < b.uid
|
||||
end
|
||||
for _,tab in Util.spairs(tabs, compareTab) do
|
||||
if not tab.hidden then
|
||||
if not tab.hidden and not tab.noFocus then
|
||||
table.insert(visibleTabs, tab)
|
||||
end
|
||||
end
|
||||
|
@ -1,5 +1,3 @@
|
||||
_G.requireInjector(_ENV)
|
||||
|
||||
--[[
|
||||
Adds a task and the control-d hotkey to view the kernel log.
|
||||
--]]
|
||||
@ -13,23 +11,27 @@ local term = _G.term
|
||||
local function systemLog()
|
||||
local routine = kernel.getCurrent()
|
||||
|
||||
local w, h = kernel.window.getSize()
|
||||
kernel.window.reposition(1, 2, w, h - 1)
|
||||
if multishell and multishell.openTab then
|
||||
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)
|
||||
routine.terminal = kernel.window
|
||||
routine.window = kernel.window
|
||||
term.redirect(kernel.window)
|
||||
end
|
||||
|
||||
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()
|
||||
if currentTab == routine then
|
||||
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
|
||||
@ -48,8 +50,16 @@ local function systemLog()
|
||||
keyboard.removeHotkey('control-d')
|
||||
end
|
||||
|
||||
multishell.openTab({
|
||||
title = 'System Log',
|
||||
fn = systemLog,
|
||||
hidden = true,
|
||||
})
|
||||
if multishell and multishell.openTab then
|
||||
multishell.openTab({
|
||||
title = 'System Log',
|
||||
fn = systemLog,
|
||||
noTerminate = true,
|
||||
hidden = true,
|
||||
})
|
||||
else
|
||||
kernel.run({
|
||||
title = 'Syslog',
|
||||
fn = systemLog,
|
||||
})
|
||||
end
|
||||
|
20
sys/autorun/upgraded.lua
Normal file
20
sys/autorun/upgraded.lua
Normal file
@ -0,0 +1,20 @@
|
||||
local fs = _G.fs
|
||||
|
||||
local function deleteIfExists(path)
|
||||
if fs.exists(path) then
|
||||
fs.delete(path)
|
||||
print("Deleted outdated file at: "..path)
|
||||
end
|
||||
end
|
||||
-- cleanup outdated files
|
||||
deleteIfExists('sys/apps/shell')
|
||||
deleteIfExists('sys/etc/app.db')
|
||||
deleteIfExists('sys/extensions')
|
||||
deleteIfExists('sys/network')
|
||||
deleteIfExists('startup')
|
||||
deleteIfExists('sys/apps/system/turtle.lua')
|
||||
deleteIfExists('sys/autorun/gps.lua')
|
||||
deleteIfExists('sys/autorun/gpshost.lua')
|
||||
deleteIfExists('sys/apps/network/redserver.lua')
|
||||
deleteIfExists('sys/apis')
|
||||
deleteIfExists('sys/autorun/apps.lua')
|
51
sys/autorun/welcome.lua
Normal file
51
sys/autorun/welcome.lua
Normal file
@ -0,0 +1,51 @@
|
||||
local Config = require('opus.config')
|
||||
local Util = require('opus.util')
|
||||
|
||||
local fs = _G.fs
|
||||
local os = _G.os
|
||||
local shell = _ENV.shell
|
||||
|
||||
local config = Config.load('os')
|
||||
if not config.welcomed then
|
||||
config.welcomed = true
|
||||
config.securityUpdate = true
|
||||
config.readNotes = 1
|
||||
Config.update('os', config)
|
||||
if shell.openForegroundTab then
|
||||
shell.openForegroundTab('Welcome')
|
||||
end
|
||||
end
|
||||
|
||||
if not config.securityUpdate then
|
||||
config.securityUpdate = true
|
||||
config.secretKey = nil
|
||||
config.password = nil
|
||||
config.readNotes = 1
|
||||
Config.update('os', config)
|
||||
|
||||
fs.delete('usr/.known_hosts')
|
||||
|
||||
Util.writeFile('sys/notes_1.txt', [[
|
||||
An important security update has been applied.
|
||||
|
||||
Unfortunately, this update has reset the
|
||||
password on the system. You can set a new
|
||||
password in System->System->Password.
|
||||
|
||||
All computers that you connect to will also
|
||||
need to be updated as well.
|
||||
|
||||
Also, I have changed the location for apis.
|
||||
This will require you to update all installed
|
||||
packages. Sorry !
|
||||
|
||||
Thanks for your patience. And... thanks to
|
||||
Anavrins for the much improved security.
|
||||
]])
|
||||
end
|
||||
|
||||
if fs.exists('sys/notes_1.txt') and shell.openForegroundTab then
|
||||
shell.openForegroundTab('edit sys/notes_1.txt')
|
||||
os.sleep(2)
|
||||
fs.delete('sys/notes_1.txt')
|
||||
end
|
40
sys/boot/kiosk.boot
Normal file
40
sys/boot/kiosk.boot
Normal file
@ -0,0 +1,40 @@
|
||||
local os = _G.os
|
||||
local parallel = _G.parallel
|
||||
local peripheral = _G.peripheral
|
||||
local settings = _G.settings
|
||||
local term = _G.term
|
||||
|
||||
local name = settings.get('kiosk.monitor')
|
||||
|
||||
if not name then
|
||||
peripheral.find('monitor', function(s)
|
||||
name = s
|
||||
end)
|
||||
end
|
||||
|
||||
local mon = name and peripheral.wrap(name)
|
||||
|
||||
if mon then
|
||||
print("Opus OS is running in Kiosk mode, and the screen will be redirected to the monitor. To undo this, go to the boot option menu by pressing a key while booting, then select the option 2.")
|
||||
term.redirect(mon)
|
||||
mon.setTextScale(tonumber(settings.get('kiosk.textscale')) or 1)
|
||||
|
||||
parallel.waitForAny(
|
||||
function()
|
||||
os.run(_ENV, '/sys/boot/opus.boot')
|
||||
end,
|
||||
|
||||
function()
|
||||
while true do
|
||||
local event, side, x, y = os.pullEventRaw('monitor_touch')
|
||||
|
||||
if event == 'monitor_touch' and side == name then
|
||||
os.queueEvent('mouse_click', 1, x, y)
|
||||
os.queueEvent('mouse_up', 1, x, y)
|
||||
end
|
||||
end
|
||||
end
|
||||
)
|
||||
else
|
||||
os.run(_ENV, '/sys/boot/opus.boot')
|
||||
end
|
@ -1,61 +1,30 @@
|
||||
-- Loads the Opus environment regardless if the file system is local or not
|
||||
local fs = _G.fs
|
||||
local http = _G.http
|
||||
|
||||
_G.OPUS_BRANCH = 'master-1.8'
|
||||
local GIT_REPO = 'kepler155c/opus/' .. _G.OPUS_BRANCH
|
||||
local BASE = 'https://raw.githubusercontent.com/' .. GIT_REPO
|
||||
local fs = _G.fs
|
||||
|
||||
local sandboxEnv = setmetatable({ }, { __index = _G })
|
||||
for k,v in pairs(_ENV) do
|
||||
sandboxEnv[k] = v
|
||||
end
|
||||
|
||||
_G._debug = function() end
|
||||
|
||||
local function makeEnv()
|
||||
local function run(file, ...)
|
||||
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())
|
||||
local s, m = loadfile(file, env)
|
||||
if s then
|
||||
return s(...)
|
||||
end
|
||||
error('Error loading ' .. file .. '\n' .. m)
|
||||
end
|
||||
|
||||
local function runUrl(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)
|
||||
end
|
||||
_G._syslog = function() end
|
||||
_G.OPUS_BRANCH = 'develop-1.8'
|
||||
|
||||
-- Install require shim
|
||||
if fs.exists('sys/apis/injector.lua') then
|
||||
_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')
|
||||
_G.requireInjector = run('sys/modules/opus/injector.lua')
|
||||
|
||||
-- install file system
|
||||
fs.mount('', 'gitfs', GIT_REPO)
|
||||
end
|
||||
|
||||
local s, m = pcall(run, 'sys/apps/shell', 'sys/kernel.lua', ...)
|
||||
local s, m = pcall(run, 'sys/apps/shell.lua', 'sys/kernel.lua', ...)
|
||||
|
||||
if not s then
|
||||
print('\nError loading Opus OS\n')
|
||||
|
211
sys/etc/app.db
211
sys/etc/app.db
@ -1,211 +0,0 @@
|
||||
{
|
||||
[ "0a999012ffb87b3edac99adbdfc498b12831a1e2" ] = {
|
||||
title = "Packages",
|
||||
category = "System",
|
||||
run = "PackageManager.lua",
|
||||
iconExt = "\030c\0317\151\131\131\131\0307\031c\148\
|
||||
\030c\0317\151\131\0310\143\0317\131\0307\031c\148\
|
||||
\0307\031c\138\030f\0317\151\131\131\131",
|
||||
},
|
||||
[ "53ebc572b4a44802ba114729f07bdaaf5409a9d7" ] = {
|
||||
title = "Network",
|
||||
category = "Apps",
|
||||
icon = "\0304 \030 \
|
||||
\030f \0304 \0307 \030 \031 \031f)\
|
||||
\030f \0304 \0307 \030 \031f)",
|
||||
iconExt = "\030 \031f \0305\031f\140\030f\0315\137\144\
|
||||
\030 \031f\030f\0314\131\131\0304\031f\148\030 \0305\155\150\149\
|
||||
\030 \031f\030f\0310\147\0300\031f\141\0304\149\0307\0318\149\030 ",
|
||||
run = "Network.lua",
|
||||
},
|
||||
c7116629a6a855cb774d9c7c8ad822fd83c71fb5 = {
|
||||
title = "Reboot",
|
||||
category = "System",
|
||||
icon = "\0304\031f \030f\0310o..\0304\031f \
|
||||
\0304\031f \030f\0310.o.\0304\031f \
|
||||
\0304\031f - ",
|
||||
iconExt = "\0307\031f\135\0300\0317\159\0307\0310\144\031f\139\
|
||||
\0300\0317\131\0307\0310\147\0300\0317\156\131\
|
||||
\130\143\143\129",
|
||||
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>",
|
||||
iconExt = "\031f\128\0313\152\131\131\132\031f\128\
|
||||
\0313\139\159\129\0303\031f\159\129\139\
|
||||
\031f\128\0313\136\0303\031f\143\143\030f\0313\134\031f\128",
|
||||
run = "http://pastebin.com/raw/UzGHLbNC",
|
||||
},
|
||||
c47ae15370cfe1ed2781eedc1dc2547d12d9e972 = {
|
||||
title = "Help",
|
||||
category = "Apps",
|
||||
icon = " \031f?\031 \
|
||||
\031f?\031 \
|
||||
\031f?",
|
||||
iconExt = "\0300\031f\129\030f\0310\131\0300\031f\148\030f\0310\148\
|
||||
\030 \031 \0300\031f\131\030f\0310\142\129\
|
||||
\030 \031 \0300\031f\131\030f\128",
|
||||
run = "Help.lua",
|
||||
},
|
||||
b0832074630eb731d7fbe8074de48a90cd9bb220 = {
|
||||
title = "Lua",
|
||||
category = "Apps",
|
||||
icon = "\030f \
|
||||
\030f\0310lua>\031 \
|
||||
\030f ",
|
||||
iconExt = "\0300\031f\151\030f\128\0300\159\159\159\030f\0310\144\0304\031f\159\030f\128\
|
||||
\0300\031f\149\030f\128\0300\149\149\151\145\030f\128\0314\153\
|
||||
\130\131\130\131\130\131\0314\130\031f\128",
|
||||
run = "sys/apps/Lua.lua",
|
||||
},
|
||||
bc0792d8dc81e8aa30b987246a5ce97c40cd6833 = {
|
||||
title = "System",
|
||||
category = "System",
|
||||
icon = " \0307\031f| \
|
||||
\0307\031f---o\030 \031 \
|
||||
\0307\031f| ",
|
||||
iconExt = "\0318\138\0308\031f\130\0318\128\031f\129\030f\0318\133\
|
||||
\0318\143\0308\128\0317\143\0318\128\030f\143\
|
||||
\0318\138\135\143\139\133",
|
||||
run = "System.lua",
|
||||
},
|
||||
[ "2a4d562b1d9a9c90bdede6fac8ce4f7402462b86" ] = {
|
||||
title = "Tasks",
|
||||
category = "System",
|
||||
icon = "\030f\031f \0315/\
|
||||
\030f\031f \0315/\\/ \
|
||||
\030f\0315/\031f ",
|
||||
iconExt = "\031f\128\128\0305\159\030f\128\0305\159\030f\0315\134\031f\128\
|
||||
\031f\128\0315\152\129\137\0305\031f\158\139\030f\0317 \
|
||||
\0315\134\031f\128\128\128\128\0305\154\030f\0317 ",
|
||||
run = "Tasks.lua",
|
||||
},
|
||||
[ "a0365977708b7387ee9ce2c13e5820e6e11732cb" ] = {
|
||||
title = "Pain",
|
||||
category = "Apps",
|
||||
icon = "\030 \031f\0307\031f\159\030 \159\030 \
|
||||
\030 \031f\0308\031f\135\0307\0318\144\140\030f\0317\159\143\031c\139\0302\135\030f\0312\157\
|
||||
\030 \031f\030f\0318\143\133\0312\136\0302\031f\159\159\143\131\030f\0312\132",
|
||||
run = "http://pastebin.com/raw/wJQ7jav0",
|
||||
},
|
||||
[ "48d6857f6b2869d031f463b13aa34df47e18c548" ] = {
|
||||
title = "Breakout",
|
||||
category = "Games",
|
||||
icon = "\0301\031f \0309 \030c \030b \030e \030c \0306 \
|
||||
\030 \031f \
|
||||
\030 \031f \0300 \0310 ",
|
||||
iconExt = "\030 \031f\030f\0319\144\030d\031f\159\030b\159\030f\0311\144\031b\144\030c\031f\159\030f\0311\144\
|
||||
\030 \031f\030f\0311\130\031b\129\0319\130\031e\130\0310\144\031d\129\0316\129\
|
||||
\030 \031f\030f\0310\136\140\140\030 ",
|
||||
run = "https://gist.github.com/LDDestroier/c7528d95bc0103545c2a/raw",
|
||||
},
|
||||
[ "53a5d150062b1e03206b9e15854b81060e3c7552" ] = {
|
||||
title = "Minesweeper",
|
||||
category = "Games",
|
||||
icon = "\030f\031f \03131\0308\031f \030f\031d2\
|
||||
\030f\031f \031d2\03131\0308\031f \030f\03131\
|
||||
\030f\03131\0308\031f \030f\03131\031e3",
|
||||
run = "https://pastebin.com/raw/nsKrHTbN",
|
||||
},
|
||||
[ "01c933b2a36ad8ed2d54089cb2903039046c1216" ] = {
|
||||
title = "Enchat",
|
||||
icon = "\030e\031f\151\030f\031e\156\0311\140\0314\140\0315\140\031d\140\031b\140\031a\132\
|
||||
\030f\0314\128\030e\031f\132\030f\031e\132\0318nchat\
|
||||
\030f\031e\138\141\0311\140\0314\140\0315\132\0317v\03183\031a\132",
|
||||
category = "Apps",
|
||||
run = "https://raw.githubusercontent.com/LDDestroier/enchat/master/enchat3.lua",
|
||||
},
|
||||
[ "6ce6c512ea433a7fc5c8841628e7696cd0ff7f2b" ] = {
|
||||
title = "Files",
|
||||
category = "Apps",
|
||||
icon = "\0300\0317==\031 \0307 \
|
||||
\0300\0317====\
|
||||
\0300\0317====",
|
||||
iconExt = "\030 \031f\0300\031f\136\140\132\0308\130\030f\0318\144\
|
||||
\030 \031f\030f\0310\157\0300\031f\147\030f\0310\142\143\149\
|
||||
\030 \031f\0300\031f\136\140\132\140\030f\0310\149",
|
||||
run = "Files.lua",
|
||||
},
|
||||
[ "7fddb7d8d1d60b1eeefa9af01082e0811d4b484d" ] = {
|
||||
title = "Shutdown",
|
||||
category = "System",
|
||||
icon = "\0304\031f \
|
||||
\0304\031f \030f\0310zz\031 \
|
||||
\0304\031f \030f ",
|
||||
iconExt = "\030e\031f\135\030f\031e\148\030e\128\031f\151\139\
|
||||
\030e\031e\128\030f\031f\128\031e\143\031f\128\030e\031e\128\
|
||||
\031e\139\030e\031f\130\131\129\030f\031e\135",
|
||||
run = "/rom/programs/shutdown",
|
||||
},
|
||||
bdc1fd5d3c0f3dcfd55d010426e61bf9451e680d = {
|
||||
title = "Shell",
|
||||
category = "Apps",
|
||||
icon = "\0304 \030 \
|
||||
\0304 \030f\0314> \0310_\031 \
|
||||
\0304 \030f \030 ",
|
||||
iconExt = "\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 \
|
||||
\0308\031b> \030b\0310>\0308\0318 \
|
||||
\0307 ",
|
||||
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 \
|
||||
\030f\0310Ther\030 \031 \
|
||||
\030f\0314?\031f \031 \030 ",
|
||||
run = "rom/programs/fun/adventure",
|
||||
},
|
||||
[ "76b849f460640bc789c433894382fb5acbac42a2" ] = {
|
||||
title = "Worm",
|
||||
category = "Games",
|
||||
icon = "\030d \030 \030e \030 \
|
||||
\030d \030 \
|
||||
\030d ",
|
||||
iconExt = "\030 \031f\0305\031f\151\030f\0315\135\131\0305\031f\146\
|
||||
\030 \031f\030f\0315\130\141\0305\031f\139\030f\0315\130\
|
||||
\030 \031f\0305\031f\146\143\030f\0315\158\031e\130",
|
||||
run = "/rom/programs/fun/worm",
|
||||
},
|
||||
[ "9f46ca3ef617166776ef6014a58d4e66859caa62" ] = {
|
||||
title = "DJ",
|
||||
category = "Games",
|
||||
icon = " \030f \
|
||||
\030f \0307 \
|
||||
\030f \0307 \0300 ",
|
||||
iconExt = "\031f\128\0307\143\131\131\131\131\143\030f\128\
|
||||
\0307\031f\129\0317\128\0319\136\0309\031b\136\132\0307\0319\132\0317\128\031f\130\
|
||||
\0317\130\143\0307\128\128\128\128\030f\143\129",
|
||||
run = "/rom/programs/fun/dj",
|
||||
},
|
||||
[ "76b849f460640bc789c433894382fb5acbac42a2" ] = {
|
||||
title = "Tron",
|
||||
category = "Games",
|
||||
iconExt = "\030 \031f\030b\031f\143\030f\128\128\030b\143\143\143\030f\128\128\
|
||||
\030 \031f\0309\031b\140\030b\031f\151\030f\031b\131\0307\148\0317\128\030b\151\030f\031b\131\148\
|
||||
\030 \031f\030f\031b\131\031f\128\031b\131\0317\131\031f\128\0317\131\031b\131\031f\128",
|
||||
run = "https://raw.githubusercontent.com/LDDestroier/CC/master/tron.lua",
|
||||
},
|
||||
}
|
136
sys/etc/apps.db
Normal file
136
sys/etc/apps.db
Normal file
@ -0,0 +1,136 @@
|
||||
{
|
||||
[ "0a999012ffb87b3edac99adbdfc498b12831a1e2" ] = {
|
||||
title = "Packages",
|
||||
category = "System",
|
||||
run = "PackageManager.lua",
|
||||
iconExt = "\030c\0317\151\131\131\131\0307\031c\148\010\030c\0317\151\131\0310\143\0317\131\0307\031c\148\010\0307\031c\138\030 \0317\151\131\131\131",
|
||||
},
|
||||
[ "b2efeaa1a7d6d2185ea02473cf758203dfcea3fe" ] = {
|
||||
title = "Cloud",
|
||||
category = "Apps",
|
||||
run = "cshell.lua",
|
||||
iconExt = "\0300\031 \159\131\135\0310\128\128\031 \139\131\030 \0310\144\010\0300\128\031f\137\144\0310\128\030a\136\149\133\0300\128\010\0300\031 \144\031f\134\136\132\031a\142\138\138\030 \0310\159",
|
||||
},
|
||||
[ "53ebc572b4a44802ba114729f07bdaaf5409a9d7" ] = {
|
||||
title = "Network",
|
||||
category = "Apps",
|
||||
icon = "\0304 \030 \010\030f \0304 \0307 \030 \031 \031f)\010\030f \0304 \0307 \030 \031f)",
|
||||
iconExt = "\030 \031 \128\128\128\128\0305\140\030 \0315\137\144\010\0314\131\131\0304\031f\148\030 \031 \128\0305\155\150\149\010\147\0300\031f\141\0304\149\0307\0318\149\030 \031 \128\128\128",
|
||||
run = "Network.lua",
|
||||
},
|
||||
[ "c7116629a6a855cb774d9c7c8ad822fd83c71fb5" ] = {
|
||||
title = "Reboot",
|
||||
category = "System",
|
||||
icon = "\0304\031f \030f\0310o..\0304\031f \010\0304\031f \030f\0310.o.\0304\031f \010\0304\031f - ",
|
||||
iconExt = "\0307\031 \135\0300\0317\159\0307\0310\144\031 \139\010\0300\0317\131\0307\0310\147\0300\0317\156\131\010\030 \130\143\143\129",
|
||||
run = "rom/programs/reboot",
|
||||
},
|
||||
[ "fb91e24fa52d8d2b32937bf04d843f730319a902" ] = {
|
||||
title = "Update",
|
||||
category = "System",
|
||||
icon = "\0301\03171\03180\030 \031 \010\0301\03181\030 \031 \010\0301\03170\03180\03171\0307\031f>",
|
||||
iconExt = "\030 \031 \128\0313\152\131\131\132\031 \128\010\030 \0313\139\159\129\0303\031 \159\129\139\010\030 \031 \128\0313\136\0303\031 \143\143\030 \0313\134\031 \128",
|
||||
run = "update update",
|
||||
},
|
||||
[ "c47ae15370cfe1ed2781eedc1dc2547d12d9e972" ] = {
|
||||
title = "Help",
|
||||
category = "Apps",
|
||||
icon = "\030 \0310 ? \010\030 \0310? \010\030 \0310\009 ?",
|
||||
iconExt = "\0300\031 \129\030 \0310\131\0300\031 \148\030 \0310\148\010\030 \031 \128\0300\131\030 \0310\142\129\010\030 \031 \128\0300\131\030 \128\128",
|
||||
run = "Help.lua",
|
||||
},
|
||||
[ "b0832074630eb731d7fbe8074de48a90cd9bb220" ] = {
|
||||
title = "Lua",
|
||||
category = "Apps",
|
||||
icon = "\030 \010\030 \0310lua>\031 \010\030 ",
|
||||
iconExt = "\0300\031 \151\030 \128\0300\159\159\159\030 \0310\144\0304\031 \159\030 \128\010\0300\031 \149\030 \128\0300\149\149\151\145\030 \128\0314\153\010\030 \130\131\130\131\130\131\0314\130\031 \128",
|
||||
run = "Lua.lua",
|
||||
},
|
||||
[ "bc0792d8dc81e8aa30b987246a5ce97c40cd6833" ] = {
|
||||
title = "System",
|
||||
category = "System",
|
||||
icon = "\030 \0307\031f| \010\0307\031f---o\030 \031 \010\030 \009 \0307\031f| ",
|
||||
iconExt = "\030 \0318\138\0308\031 \130\0318\128\031 \129\030 \0318\133\010\030 \0318\143\0308\128\0317\143\0318\128\030 \143\010\030 \0318\138\135\143\139\133",
|
||||
run = "System.lua",
|
||||
},
|
||||
[ "2a4d562b1d9a9c90bdede6fac8ce4f7402462b86" ] = {
|
||||
title = "Tasks",
|
||||
category = "System",
|
||||
icon = "\030 \031f \0315/\010\030 \031f \0315/\\/ \010\030 \0315/\031f ",
|
||||
iconExt = "\030 \031 \128\128\0305\159\030 \128\0305\159\030 \0315\134\031 \128\010\030 \031 \128\0315\152\129\137\0305\031 \158\139\030 \128\010\030 \0315\134\031 \128\128\128\128\0305\154\030 \128",
|
||||
run = "Tasks.lua",
|
||||
},
|
||||
[ "a0365977708b7387ee9ce2c13e5820e6e11732cb" ] = {
|
||||
title = "Pain",
|
||||
category = "Apps",
|
||||
iconExt = "\0307\031 \159\030 \128\128\128\128\128\128\128\010\0308\031 \135\0307\0318\144\140\030 \0317\159\143\031c\139\0302\135\030 \0312\157\010\030 \0318\143\133\0312\136\0302\031 \159\159\143\131\030 \0312\132",
|
||||
run = "pain",
|
||||
},
|
||||
[ "6a381ca189cbddd63737cbaf6e8b593844ce467ba52b1c5e5e05d8f29864385d" ] = {
|
||||
title = "Sniffer",
|
||||
category = "Apps",
|
||||
iconExt = "\030 \031 \128\128\128\128\0315\149\0305\031 \154\030 \0315\137\010\0304\031 \159\0314\128\128\030 \144\0315\130\0305\031 \155\140\010\0314\151\0304\031f\148\030f\0314\151\0304\031f\148\030 \031 \128\128\128",
|
||||
run = "Sniff.lua",
|
||||
},
|
||||
[ "01c933b2a36ad8ed2d54089cb2903039046c1216" ] = {
|
||||
title = "Enchat",
|
||||
iconExt = "\030e\031f\151\030f\031e\156\0311\140\0314\140\0315\140\031d\140\031b\140\031a\132\010\030f\0314\128\030e\031f\132\030f\031e\132\0318nchat\010\030f\031e\138\141\0311\140\0314\140\0315\132\0317v\03183\031a\132",
|
||||
category = "Apps",
|
||||
run = "Enchat",
|
||||
},
|
||||
[ "6ce6c512ea433a7fc5c8841628e7696cd0ff7f2b" ] = {
|
||||
title = "Files",
|
||||
category = "Apps",
|
||||
icon = "\0300\0317==\031 \0307 \010\0300\0317====\010\0300\0317====",
|
||||
iconExt = "\0300\031f\136\140\132\0308\031 \130\030 \0318\144\010\157\0300\031f\147\030f\0310\142\143\030 \149\010\0300\031f\136\140\132\140\030 \0310\149",
|
||||
run = "Files.lua",
|
||||
},
|
||||
[ "7fddb7d8d1d60b1eeefa9af01082e0811d4b484d" ] = {
|
||||
title = "Shutdown",
|
||||
category = "System",
|
||||
icon = "\0304\031f \010\0304\031f \030f\0310zz\031 \010\0304\031f \030f ",
|
||||
iconExt = "\030e\031 \135\030 \031e\148\030e\128\031 \151\139\010\030e\031e\128\030 \031 \128\031e\143\031 \128\030e\031e\128\010\030 \031e\139\030e\031 \130\131\129\030 \031e\135",
|
||||
run = "/rom/programs/shutdown",
|
||||
},
|
||||
[ "bdc1fd5d3c0f3dcfd55d010426e61bf9451e680d" ] = {
|
||||
title = "Shell",
|
||||
category = "Apps",
|
||||
icon = "\0304 \030 \010\0304 \030f\0314> \0310_\031 \010\0304 \030f \030 ",
|
||||
iconExt = "\030f\0314\151\131\131\131\131\010\030f\0314\149\030f\0314> \0310_ \010\030f\0314\149\030f ",
|
||||
run = "shell",
|
||||
},
|
||||
[ "b77aad5fb24921ef76ac8f3e500ed93fddae8f2a" ] = {
|
||||
title = "Redirection",
|
||||
category = "Games",
|
||||
icon = "\0307 \0308 \0307 \010\0308\031b> \030b\0310>\0308\0318 \010\0307 ",
|
||||
run = "rom/programs/fun/advanced/redirection",
|
||||
requires = 'advanced',
|
||||
},
|
||||
[ "f39d173d91c22348565c20283b89d4d1cabd3b7e" ] = {
|
||||
title = "Falling",
|
||||
category = "Games",
|
||||
icon = "\030f \0302 \010\0309 \0302 \0301 \010\030e \0309 \0301 ",
|
||||
run = "rom/programs/pocket/falling",
|
||||
requires = 'advancedPocket',
|
||||
},
|
||||
[ "db56e2e1db9f7accfc37f2b132d27505c66ba521" ] = {
|
||||
title = "Adventure",
|
||||
category = "Games",
|
||||
icon = "\030f\0310You \031 \010\030f\0310Ther\030 \031 \010\030f\0314?\031f \031 \030 ",
|
||||
run = "rom/programs/fun/adventure",
|
||||
},
|
||||
[ "76b849f460640bc789c433894382fb5acbac42a2" ] = {
|
||||
title = "Worm",
|
||||
category = "Games",
|
||||
icon = "\030d \030 \030e \030 \010\030d \030 \010\030d ",
|
||||
iconExt = "\0305\031 \151\030 \0315\135\131\0305\031 \146\010\030 \0315\130\141\0305\031 \139\030 \0315\130\010\0305\031 \146\143\030 \0315\158\031e\130",
|
||||
run = "/rom/programs/fun/worm",
|
||||
},
|
||||
[ "9f46ca3ef617166776ef6014a58d4e66859caa62" ] = {
|
||||
title = "DJ",
|
||||
category = "Games",
|
||||
icon = " \030f \010\030f \0307 \010\030f \0307 \0300 ",
|
||||
iconExt = "\030 \031 \128\0307\143\131\131\131\131\143\030 \128\010\0307\031 \129\0317\128\0319\136\0309\031b\136\132\0307\0319\132\0317\128\031 \130\010\030 \0317\130\143\0307\128\128\128\128\030 \143\129",
|
||||
run = "/rom/programs/fun/dj",
|
||||
},
|
||||
}
|
@ -1,28 +0,0 @@
|
||||
{
|
||||
ScrollBar = {
|
||||
lineChar = '|',
|
||||
sliderChar = '\127',
|
||||
upArrowChar = '\30',
|
||||
downArrowChar = '\31',
|
||||
},
|
||||
Button = {
|
||||
--focusIndicator = '\183',
|
||||
},
|
||||
Checkbox = {
|
||||
checkedIndicator = '\4',
|
||||
leftMarker = '\124',
|
||||
rightMarker = '\124',
|
||||
},
|
||||
Chooser = {
|
||||
leftIndicator = '\17',
|
||||
rightIndicator = '\16',
|
||||
},
|
||||
Grid = {
|
||||
focusIndicator = '\183',
|
||||
inverseSortIndicator = '\24',
|
||||
},
|
||||
TitleBar = {
|
||||
frameChar = '\140',
|
||||
closeInd = '\215',
|
||||
},
|
||||
}
|
6
sys/etc/fstab
Normal file
6
sys/etc/fstab
Normal file
@ -0,0 +1,6 @@
|
||||
sys/apps/pain.lua urlfs https://github.com/LDDestroier/CC/raw/master/pain.lua
|
||||
sys/apps/update.lua urlfs http://pastebin.com/raw/UzGHLbNC
|
||||
sys/apps/Enchat.lua urlfs https://raw.githubusercontent.com/LDDestroier/enchat/master/enchat3.lua
|
||||
sys/apps/cloud.lua urlfs https://cloud-catcher.squiddev.cc/cloud.lua
|
||||
sys/apps/nfttrans.lua urlfs https://pastebin.com/raw/e8XrzeDY
|
||||
rom/modules/main/opus linkfs sys/modules/opus
|
@ -1,186 +0,0 @@
|
||||
_G.requireInjector(_ENV)
|
||||
|
||||
local Peripheral = require('peripheral')
|
||||
|
||||
_G.device = Peripheral.getList()
|
||||
|
||||
_G.device.terminal = _G.kernel.terminal
|
||||
_G.device.terminal.side = 'terminal'
|
||||
_G.device.terminal.type = 'terminal'
|
||||
_G.device.terminal.name = 'terminal'
|
||||
|
||||
_G.device.keyboard = {
|
||||
side = 'keyboard',
|
||||
type = 'keyboard',
|
||||
name = 'keyboard',
|
||||
hotkeys = { },
|
||||
state = { },
|
||||
}
|
||||
|
||||
_G.device.mouse = {
|
||||
side = 'mouse',
|
||||
type = 'mouse',
|
||||
name = 'mouse',
|
||||
state = { },
|
||||
}
|
||||
|
||||
local Input = require('input')
|
||||
local Util = require('util')
|
||||
|
||||
local device = _G.device
|
||||
local kernel = _G.kernel
|
||||
local keyboard = _G.device.keyboard
|
||||
local mouse = _G.device.mouse
|
||||
local os = _G.os
|
||||
|
||||
local drivers = { }
|
||||
|
||||
kernel.hook('peripheral', function(_, eventData)
|
||||
local side = eventData[1]
|
||||
if side then
|
||||
local dev = Peripheral.addDevice(device, side)
|
||||
if dev then
|
||||
if drivers[dev.type] then
|
||||
local e = drivers[dev.type](dev)
|
||||
if type(e) == 'table' then
|
||||
for _, v in pairs(e) do
|
||||
os.queueEvent('device_attach', v.name)
|
||||
end
|
||||
elseif e then
|
||||
os.queueEvent('device_attach', e.name)
|
||||
end
|
||||
end
|
||||
|
||||
os.queueEvent('device_attach', dev.name, dev)
|
||||
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, dev)
|
||||
if dev._children then
|
||||
for _,v in pairs(dev._children) do
|
||||
os.queueEvent('peripheral_detach', v.name)
|
||||
end
|
||||
end
|
||||
device[dev.name] = nil
|
||||
end
|
||||
end
|
||||
end)
|
||||
|
||||
kernel.hook({ 'key', 'key_up', 'char', 'paste' }, function(event, eventData)
|
||||
local code = eventData[1]
|
||||
|
||||
-- maintain global keyboard state
|
||||
if event == 'key' then
|
||||
keyboard.state[code] = true
|
||||
elseif event == 'key_up' then
|
||||
if not keyboard.state[code] then
|
||||
return true -- ensure key ups are only generated if a key down was sent
|
||||
end
|
||||
keyboard.state[code] = nil
|
||||
end
|
||||
|
||||
-- and fire hotkeys
|
||||
local hotkey = Input:translate(event, eventData[1], eventData[2])
|
||||
|
||||
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
|
||||
end)
|
||||
|
||||
kernel.hook('kernel_focus', function()
|
||||
Util.clear(keyboard.state)
|
||||
Util.clear(mouse.state)
|
||||
end)
|
||||
|
||||
function keyboard.addHotkey(code, fn)
|
||||
keyboard.hotkeys[code] = fn
|
||||
end
|
||||
|
||||
function keyboard.removeHotkey(code)
|
||||
keyboard.hotkeys[code] = nil
|
||||
end
|
||||
|
||||
kernel.hook('monitor_touch', function(event, eventData)
|
||||
local monitor = Peripheral.getBySide(eventData[1])
|
||||
if monitor and monitor.eventChannel then
|
||||
monitor.eventChannel(event, table.unpack(eventData))
|
||||
return true -- stop propagation
|
||||
end
|
||||
end)
|
||||
|
||||
local function createDevice(name, devType, method, manipulator)
|
||||
local dev = {
|
||||
name = name,
|
||||
side = name,
|
||||
type = devType,
|
||||
}
|
||||
local methods = {
|
||||
'drop', 'getDocs', 'getItem', 'getItemMeta', 'getTransferLocations',
|
||||
'list', 'pullItems', 'pushItems', 'size', 'suck',
|
||||
}
|
||||
if manipulator[method] then
|
||||
for _,k in pairs(methods) do
|
||||
dev[k] = function(...)
|
||||
return manipulator[method]()[k](...)
|
||||
end
|
||||
end
|
||||
if not manipulator._children then
|
||||
manipulator._children = { dev }
|
||||
else
|
||||
table.insert(manipulator._children, dev)
|
||||
end
|
||||
device[name] = dev
|
||||
end
|
||||
end
|
||||
|
||||
drivers['manipulator'] = function(dev)
|
||||
if dev.getName then
|
||||
local name
|
||||
pcall(function()
|
||||
name = dev.getName()
|
||||
end)
|
||||
if name then
|
||||
if dev.getInventory then
|
||||
createDevice(name .. ':inventory', 'inventory', 'getInventory', dev)
|
||||
end
|
||||
if dev.getEquipment then
|
||||
createDevice(name .. ':equipment', 'equipment', 'getEquipment', dev)
|
||||
end
|
||||
if dev.getEnder then
|
||||
createDevice(name .. ':enderChest', 'enderChest', 'getEnder', dev)
|
||||
end
|
||||
|
||||
return dev._children
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
-- initialize drivers
|
||||
for _,v in pairs(device) do
|
||||
if drivers[v.type] then
|
||||
local s, m = pcall(drivers[v.type], v)
|
||||
if not s and m then
|
||||
_G.printError(m)
|
||||
end
|
||||
end
|
||||
end
|
@ -1,50 +0,0 @@
|
||||
_G.requireInjector(_ENV)
|
||||
|
||||
local Util = require('util')
|
||||
|
||||
local fs = _G.fs
|
||||
local shell = _ENV.shell
|
||||
|
||||
if not fs.exists('usr/apps') then
|
||||
fs.makeDir('usr/apps')
|
||||
end
|
||||
if not fs.exists('usr/autorun') then
|
||||
fs.makeDir('usr/autorun')
|
||||
end
|
||||
--if not fs.exists('usr/config/fstab') then
|
||||
-- Util.writeFile('usr/config/fstab',
|
||||
-- 'usr gitfs kepler155c/opus-apps/' .. _G.OPUS_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',
|
||||
})
|
||||
end
|
||||
|
||||
if not fs.exists('usr/config/packages') then
|
||||
local packages = {
|
||||
[ 'develop-1.8' ] = 'https://pastebin.com/raw/WhEiNGZE',
|
||||
[ 'master-1.8' ] = 'https://pastebin.com/raw/pexZpAxt',
|
||||
}
|
||||
|
||||
if packages[_G.OPUS_BRANCH] then
|
||||
Util.download(packages[_G.OPUS_BRANCH], 'usr/config/packages')
|
||||
end
|
||||
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
|
||||
end
|
||||
shell.setPath(config.path)
|
||||
_G.LUA_PATH = config.lua_path
|
||||
|
||||
fs.loadTab('usr/config/fstab')
|
@ -1,45 +0,0 @@
|
||||
_G.requireInjector(_ENV)
|
||||
|
||||
local Packages = require('packages')
|
||||
local Util = require('util')
|
||||
|
||||
local shell = _ENV.shell
|
||||
local fs = _G.fs
|
||||
|
||||
local appPaths = Util.split(shell.path(), '(.-):')
|
||||
local luaPaths = Util.split(_G.LUA_PATH, '(.-):')
|
||||
|
||||
local function addPath(t, e)
|
||||
local function hasEntry()
|
||||
for _,v in ipairs(t) do
|
||||
if v == e then
|
||||
return true
|
||||
end
|
||||
end
|
||||
end
|
||||
if not hasEntry() then
|
||||
table.insert(t, 1, e)
|
||||
end
|
||||
end
|
||||
|
||||
-- dependency graph
|
||||
-- https://github.com/mpeterv/depgraph/blob/master/src/depgraph/init.lua
|
||||
|
||||
for name in pairs(Packages:installed()) do
|
||||
local packageDir = fs.combine('packages', name)
|
||||
if fs.exists(fs.combine(packageDir, '.install')) then
|
||||
local install = Util.readTable(fs.combine(packageDir, '.install'))
|
||||
if install and install.mount then
|
||||
fs.mount(table.unpack(Util.matches(install.mount)))
|
||||
end
|
||||
end
|
||||
|
||||
addPath(appPaths, packageDir)
|
||||
local apiPath = fs.combine(fs.combine('packages', name), 'apis')
|
||||
if fs.exists(apiPath) then
|
||||
addPath(luaPaths, apiPath)
|
||||
end
|
||||
end
|
||||
|
||||
shell.setPath(table.concat(appPaths, ':'))
|
||||
_G.LUA_PATH = table.concat(luaPaths, ':')
|
File diff suppressed because it is too large
Load Diff
5
sys/help/CloudCatcher
Normal file
5
sys/help/CloudCatcher
Normal file
@ -0,0 +1,5 @@
|
||||
Cloud Catcher is a web terminal for ComputerCraft, allowing you to interact with any in-game computer in the browser, as well as edit files remotely!
|
||||
|
||||
To get started, visit https://cloud-catcher.squiddev.cc for a session key.
|
||||
|
||||
Within Files, press 'c' to edit a file using Cloud Catcher.
|
39
sys/help/Opus
Normal file
39
sys/help/Opus
Normal file
@ -0,0 +1,39 @@
|
||||
Shortcut Keys
|
||||
=============
|
||||
* Control-o: Show the Overview
|
||||
* Control-tab: Cycle to next tab
|
||||
* Control-shift-tab: Cycle to previous tab
|
||||
* Control-d: Show/toggle logging screen
|
||||
* Control-c: Copy (in most applications)
|
||||
* Control-shift-v: Paste from internal clipboard
|
||||
* Control-shift-doubleclick: Open in Lua the clicked UI element
|
||||
|
||||
Wireless Networking
|
||||
===================
|
||||
To establish one-way trust between two computers with modems, run the following in a shell prompt:
|
||||
|
||||
On the target computer, run:
|
||||
> password
|
||||
|
||||
On the source computer, run:
|
||||
> trust <target computer ID>
|
||||
|
||||
Running Custom Programs
|
||||
=======================
|
||||
To create a program that runs at startup, create a file in /usr/autorun to start the program. Example:
|
||||
|
||||
In file: /usr/autorun/startup.lua
|
||||
shell.openForegroundTab('myprogram')
|
||||
|
||||
There are 3 different ways to open tabs:
|
||||
|
||||
* shell.openTab : background tab
|
||||
* shell.openForegroundTab : focused tab
|
||||
* shell.openHiddenTab : appears only in the Tasks application
|
||||
|
||||
Copy / Paste
|
||||
============
|
||||
Opus can paste from both the system clipboard and the internal clipboard.
|
||||
|
||||
* control-v: for normal clipboard
|
||||
* control-shift-v: for internal clipboard
|
30
sys/help/Opus-Applications
Normal file
30
sys/help/Opus-Applications
Normal file
@ -0,0 +1,30 @@
|
||||
Opus applications are grouped into packages with a common theme.
|
||||
|
||||
To install a package, use either the System -> Packages program or the package command line program.
|
||||
|
||||
Shell usage:
|
||||
|
||||
> package list
|
||||
> package install <name>
|
||||
> package update <name>
|
||||
> package uninstall <name>
|
||||
|
||||
Package definitions are located in usr/apps/packages. This file can be modified to add custom packages.
|
||||
|
||||
Current stable packages
|
||||
=======================
|
||||
|
||||
* core
|
||||
Programming and miscellaneous applications. Also contains drivers needed for other packages.
|
||||
|
||||
* builder
|
||||
A program for creating structures from schematic files using a turtle (requires core).
|
||||
|
||||
* farms
|
||||
Various programs for farming resources (wood, crops, animals).
|
||||
|
||||
* milo
|
||||
An A/E like storage implementation (requires core).
|
||||
|
||||
* miners
|
||||
Mining programs.
|
15
sys/help/Overview
Normal file
15
sys/help/Overview
Normal file
@ -0,0 +1,15 @@
|
||||
Overview is the main application launcher.
|
||||
|
||||
Shortcut keys
|
||||
=============
|
||||
* s: Shell
|
||||
* l: Lua application
|
||||
* f: Files
|
||||
* e: Edit an application (or right-click)
|
||||
* control-n: Add a new application
|
||||
* delete: Delete an application
|
||||
|
||||
Adding a new application
|
||||
========================
|
||||
The run entry can be either a disk file or a URL.
|
||||
Icons must be in NFT format with a height of 3 and a width of 3 to 8 characters.
|
15
sys/help/pastebin
Normal file
15
sys/help/pastebin
Normal file
@ -0,0 +1,15 @@
|
||||
pastebin is a program for uploading files to and downloading files from pastebin.com. This is useful for sharing programs with other players.
|
||||
The HTTP API must be enabled in ComputerCraft.cfg to use this program.
|
||||
|
||||
ex:
|
||||
"pastebin put foo" will upload the file "foo" to pastebin.com, and print the URL.
|
||||
"pastebin get xq5gc7LB foo" will download the file from the URL http://pastebin.com/xq5gc7LB, and save it as "foo".
|
||||
"pastebin run CxaWmPrX" will download the file from the URL http://pastebin.com/CxaWmPrX, and immediately run it.
|
||||
|
||||
Functions in the pastebin API:
|
||||
pastebin.get( code, filepath )
|
||||
pastebin.put( filepath )
|
||||
pastebin.download( code )
|
||||
pastebin.upload( pastename, text )
|
||||
pastebin.parseCode( code )
|
||||
|
108
sys/init/1.device.lua
Normal file
108
sys/init/1.device.lua
Normal file
@ -0,0 +1,108 @@
|
||||
_G.requireInjector(_ENV)
|
||||
|
||||
local Peripheral = require('opus.peripheral')
|
||||
|
||||
_G.device = Peripheral.getList()
|
||||
|
||||
_G.device.terminal = _G.kernel.terminal
|
||||
_G.device.terminal.side = 'terminal'
|
||||
_G.device.terminal.type = 'terminal'
|
||||
_G.device.terminal.name = 'terminal'
|
||||
|
||||
_G.device.keyboard = {
|
||||
side = 'keyboard',
|
||||
type = 'keyboard',
|
||||
name = 'keyboard',
|
||||
hotkeys = { },
|
||||
state = { },
|
||||
}
|
||||
|
||||
_G.device.mouse = {
|
||||
side = 'mouse',
|
||||
type = 'mouse',
|
||||
name = 'mouse',
|
||||
state = { },
|
||||
}
|
||||
|
||||
local Input = require('opus.input')
|
||||
local Util = require('opus.util')
|
||||
|
||||
local device = _G.device
|
||||
local kernel = _G.kernel
|
||||
local keyboard = _G.device.keyboard
|
||||
local keys = _G.keys
|
||||
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
|
||||
end)
|
||||
|
||||
kernel.hook('peripheral_detach', function(_, eventData)
|
||||
local side = eventData[1]
|
||||
if side then
|
||||
for _, dev in pairs(Util.findAll(device, 'side', side)) do
|
||||
os.queueEvent('device_detach', dev.name)
|
||||
device[dev.name] = nil
|
||||
end
|
||||
end
|
||||
end)
|
||||
|
||||
local modifiers = Util.transpose {
|
||||
keys.leftCtrl, keys.rightCtrl,
|
||||
keys.leftShift, keys.rightShift,
|
||||
keys.leftAlt, keys.rightAlt,
|
||||
}
|
||||
|
||||
kernel.hook({ 'key', 'char', 'paste' }, function(event, eventData)
|
||||
local code = eventData[1]
|
||||
|
||||
-- maintain global keyboard modifier state
|
||||
if event == 'key' and modifiers[code] then
|
||||
keyboard.state[code] = true
|
||||
end
|
||||
|
||||
-- and fire hotkeys
|
||||
local hotkey = Input:translate(event, eventData[1], eventData[2])
|
||||
|
||||
if hotkey and keyboard.hotkeys[hotkey.code] then
|
||||
keyboard.hotkeys[hotkey.code](event, eventData)
|
||||
return true
|
||||
end
|
||||
end)
|
||||
|
||||
kernel.hook('key_up', function(_, eventData)
|
||||
local code = eventData[1]
|
||||
|
||||
if modifiers[code] then
|
||||
keyboard.state[code] = nil
|
||||
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
|
||||
end)
|
||||
|
||||
function keyboard.addHotkey(code, fn)
|
||||
keyboard.hotkeys[code] = fn
|
||||
end
|
||||
|
||||
function keyboard.removeHotkey(code)
|
||||
keyboard.hotkeys[code] = nil
|
||||
end
|
@ -1,11 +1,13 @@
|
||||
local fs = _G.fs
|
||||
|
||||
if fs.native then
|
||||
return
|
||||
end
|
||||
|
||||
_G.requireInjector(_ENV)
|
||||
local Util = require('util')
|
||||
local Util = require('opus.util')
|
||||
|
||||
local fs = _G.fs
|
||||
-- TODO: support getDrive for virtual nodes
|
||||
|
||||
fs.native = Util.shallowCopy(fs)
|
||||
|
||||
@ -14,7 +16,7 @@ local nativefs = { }
|
||||
|
||||
for k,fn in pairs(fs) do
|
||||
if type(fn) == 'function' then
|
||||
nativefs[k] = function(node, ...)
|
||||
nativefs[k] = function(_, ...)
|
||||
return fn(...)
|
||||
end
|
||||
end
|
||||
@ -88,6 +90,13 @@ function nativefs.exists(node, dir)
|
||||
return fs.native.exists(dir)
|
||||
end
|
||||
|
||||
function nativefs.getDrive(node, dir)
|
||||
if node.mountPoint == dir then
|
||||
return fs.native.getDrive(dir) or 'virt'
|
||||
end
|
||||
return fs.native.getDrive(dir)
|
||||
end
|
||||
|
||||
function nativefs.delete(node, dir)
|
||||
if node.mountPoint == dir then
|
||||
fs.unmount(dir)
|
||||
@ -113,6 +122,7 @@ local function splitpath(path)
|
||||
end
|
||||
|
||||
local function getNode(dir)
|
||||
if not dir then error('Invalid directory', 2) end
|
||||
local cd = fs.combine(dir, '')
|
||||
local parts = splitpath(cd)
|
||||
local node = fs.nodes
|
||||
@ -176,6 +186,8 @@ function fs.listEx(dir)
|
||||
end
|
||||
|
||||
function fs.copy(s, t)
|
||||
if not s then error('copy: bad argument #1') end
|
||||
if not t then error('copy: bad argument #2') end
|
||||
local sp = getNode(s)
|
||||
local tp = getNode(t)
|
||||
if sp == tp and sp.fs.copy then
|
||||
@ -208,6 +220,8 @@ function fs.find(spec) -- not optimized
|
||||
-- local files = node.fs.find(node, spec)
|
||||
local files = { }
|
||||
-- method from https://github.com/N70/deltaOS/blob/dev/vfs
|
||||
|
||||
-- REVISIT - see globbing in shellex package
|
||||
local function recurse_spec(results, path, spec)
|
||||
local segment = spec:match('([^/]*)'):gsub('/', '')
|
||||
local pattern = '^' .. segment:gsub("[%.%[%]%(%)%%%+%-%?%^%$]","%%%1"):gsub("%z","%%z"):gsub("%*","[^/]-") .. '$'
|
||||
@ -244,7 +258,7 @@ end
|
||||
local function getfstype(fstype)
|
||||
local vfs = fstypes[fstype]
|
||||
if not vfs then
|
||||
vfs = require('fs.' .. fstype)
|
||||
vfs = require('opus.fs.' .. fstype)
|
||||
fs.registerType(fstype, vfs)
|
||||
end
|
||||
return vfs
|
||||
@ -291,7 +305,8 @@ 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
|
||||
l = Util.trim(l)
|
||||
if #l > 0 and l:sub(1, 1) ~= '#' then
|
||||
local s, m = pcall(function()
|
||||
fs.mount(table.unpack(Util.matches(l)))
|
||||
end)
|
||||
@ -328,8 +343,8 @@ function fs.unmount(path)
|
||||
end
|
||||
end
|
||||
|
||||
function fs.registerType(name, fs)
|
||||
fstypes[name] = fs
|
||||
function fs.registerType(name, vfs)
|
||||
fstypes[name] = vfs
|
||||
end
|
||||
|
||||
function fs.getTypes()
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
x
Reference in New Issue
Block a user