Merge branch 'develop-1.8' into master-1.8

This commit is contained in:
kepler155c@gmail.com 2019-11-10 18:51:51 -07:00
commit ee6af86da8
192 changed files with 14776 additions and 11766 deletions

View File

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

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

File diff suppressed because it is too large Load Diff

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View 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
View 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()

View File

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

View File

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

View File

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

View File

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

View 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)

View 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)

View File

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

View File

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

View File

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

View File

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

View 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)

View File

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

View File

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

View File

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

View File

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

View 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

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

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

View 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

View 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

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

View 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

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -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
View 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",
},
}

View File

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

View File

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

View File

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

View File

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

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

View File

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