mirror of
https://github.com/kepler155c/opus
synced 2024-12-27 09:00:41 +00:00
new release
This commit is contained in:
commit
3953ef08cc
@ -15,8 +15,13 @@ function Config.load(fname, data)
|
|||||||
if not fs.exists(filename) then
|
if not fs.exists(filename) then
|
||||||
Util.writeTable(filename, data)
|
Util.writeTable(filename, data)
|
||||||
else
|
else
|
||||||
Util.merge(data, Util.readTable(filename) or { })
|
local contents = Util.readTable(filename) or
|
||||||
|
error('Configuration file is corrupt:' .. filename)
|
||||||
|
|
||||||
|
Util.merge(data, contents)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
return data
|
||||||
end
|
end
|
||||||
|
|
||||||
function Config.loadWithCheck(fname, data)
|
function Config.loadWithCheck(fname, data)
|
||||||
@ -33,7 +38,7 @@ function Config.loadWithCheck(fname, data)
|
|||||||
shell.run('edit ' .. filename)
|
shell.run('edit ' .. filename)
|
||||||
end
|
end
|
||||||
|
|
||||||
Config.load(fname, data)
|
return Config.load(fname, data)
|
||||||
end
|
end
|
||||||
|
|
||||||
function Config.update(fname, data)
|
function Config.update(fname, data)
|
||||||
|
@ -1,4 +1,5 @@
|
|||||||
local os = _G.os
|
local os = _G.os
|
||||||
|
local table = _G.table
|
||||||
|
|
||||||
local Event = {
|
local Event = {
|
||||||
uid = 1, -- unique id for handlers
|
uid = 1, -- unique id for handlers
|
||||||
@ -6,8 +7,29 @@ local Event = {
|
|||||||
types = { }, -- event handlers
|
types = { }, -- event handlers
|
||||||
timers = { }, -- named timers
|
timers = { }, -- named timers
|
||||||
terminate = false,
|
terminate = false,
|
||||||
|
free = { },
|
||||||
}
|
}
|
||||||
|
|
||||||
|
-- Use a pool of coroutines for event handlers
|
||||||
|
local function createCoroutine(h)
|
||||||
|
local co = table.remove(Event.free)
|
||||||
|
if not co then
|
||||||
|
co = coroutine.create(function(_, ...)
|
||||||
|
local args = { ... }
|
||||||
|
while true do
|
||||||
|
h.fn(table.unpack(args))
|
||||||
|
h.co = nil
|
||||||
|
table.insert(Event.free, co)
|
||||||
|
args = { coroutine.yield() }
|
||||||
|
h = table.remove(args, 1)
|
||||||
|
h.co = co
|
||||||
|
end
|
||||||
|
end)
|
||||||
|
end
|
||||||
|
h.primeCo = true -- TODO: fix...
|
||||||
|
return co
|
||||||
|
end
|
||||||
|
|
||||||
local Routine = { }
|
local Routine = { }
|
||||||
|
|
||||||
function Routine:isDead()
|
function Routine:isDead()
|
||||||
@ -24,18 +46,20 @@ function Routine:terminate()
|
|||||||
end
|
end
|
||||||
|
|
||||||
function Routine:resume(event, ...)
|
function Routine:resume(event, ...)
|
||||||
--if coroutine.status(self.co) == 'running' then
|
|
||||||
--return
|
|
||||||
--end
|
|
||||||
|
|
||||||
if not self.co then
|
if not self.co then
|
||||||
error('Cannot resume a dead routine')
|
error('Cannot resume a dead routine')
|
||||||
end
|
end
|
||||||
|
|
||||||
if not self.filter or self.filter == event or event == "terminate" then
|
if not self.filter or self.filter == event or event == "terminate" then
|
||||||
local s, m = coroutine.resume(self.co, event, ...)
|
local s, m
|
||||||
|
if self.primeCo then
|
||||||
if coroutine.status(self.co) == 'dead' then
|
-- Only need self passed when using a coroutine from the pool
|
||||||
|
s, m = coroutine.resume(self.co, self, event, ...)
|
||||||
|
self.primeCo = nil
|
||||||
|
else
|
||||||
|
s, m = coroutine.resume(self.co, event, ...)
|
||||||
|
end
|
||||||
|
if self:isDead() then
|
||||||
self.co = nil
|
self.co = nil
|
||||||
self.filter = nil
|
self.filter = nil
|
||||||
Event.routines[self.uid] = nil
|
Event.routines[self.uid] = nil
|
||||||
@ -83,8 +107,14 @@ end
|
|||||||
function Event.off(h)
|
function Event.off(h)
|
||||||
if h and h.event then
|
if h and h.event then
|
||||||
for _,event in pairs(h.event) do
|
for _,event in pairs(h.event) do
|
||||||
|
local handler = Event.types[event][h.uid]
|
||||||
|
if handler then
|
||||||
|
handler:terminate()
|
||||||
|
end
|
||||||
Event.types[event][h.uid] = nil
|
Event.types[event][h.uid] = nil
|
||||||
end
|
end
|
||||||
|
elseif h and h.co then
|
||||||
|
h:terminate()
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
@ -107,7 +137,12 @@ local function addTimer(interval, recurring, fn)
|
|||||||
end
|
end
|
||||||
|
|
||||||
function Event.onInterval(interval, fn)
|
function Event.onInterval(interval, fn)
|
||||||
return addTimer(interval, true, fn)
|
return Event.addRoutine(function()
|
||||||
|
while true do
|
||||||
|
os.sleep(interval)
|
||||||
|
fn()
|
||||||
|
end
|
||||||
|
end)
|
||||||
end
|
end
|
||||||
|
|
||||||
function Event.onTimeout(timeout, fn)
|
function Event.onTimeout(timeout, fn)
|
||||||
@ -136,6 +171,18 @@ function Event.waitForEvent(event, timeout)
|
|||||||
until e[1] == 'timer' and e[2] == timerId
|
until e[1] == 'timer' and e[2] == timerId
|
||||||
end
|
end
|
||||||
|
|
||||||
|
-- Set a handler for the terminate event. Within the function, return
|
||||||
|
-- true or false to indicate whether the event should be propagated to
|
||||||
|
-- all sub-threads
|
||||||
|
function Event.onTerminate(fn)
|
||||||
|
Event.termFn = fn
|
||||||
|
end
|
||||||
|
|
||||||
|
function Event.termFn()
|
||||||
|
Event.terminate = true
|
||||||
|
return true -- propagate
|
||||||
|
end
|
||||||
|
|
||||||
function Event.addRoutine(fn)
|
function Event.addRoutine(fn)
|
||||||
local r = setmetatable({
|
local r = setmetatable({
|
||||||
co = coroutine.create(fn),
|
co = coroutine.create(fn),
|
||||||
@ -171,7 +218,7 @@ local function processHandlers(event)
|
|||||||
for _,h in pairs(handlers) do
|
for _,h in pairs(handlers) do
|
||||||
if not h.co then
|
if not h.co then
|
||||||
-- callbacks are single threaded (only 1 co per handler)
|
-- callbacks are single threaded (only 1 co per handler)
|
||||||
h.co = coroutine.create(h.fn)
|
h.co = createCoroutine(h)
|
||||||
Event.routines[h.uid] = h
|
Event.routines[h.uid] = h
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
@ -204,11 +251,16 @@ end
|
|||||||
function Event.pullEvent(eventType)
|
function Event.pullEvent(eventType)
|
||||||
while true do
|
while true do
|
||||||
local e = { os.pullEventRaw() }
|
local e = { os.pullEventRaw() }
|
||||||
|
local propagate = true -- don't like this...
|
||||||
|
|
||||||
Event.terminate = Event.terminate or e[1] == 'terminate'
|
if e[1] == 'terminate' then
|
||||||
|
propagate = Event.termFn()
|
||||||
|
end
|
||||||
|
|
||||||
|
if propagate then
|
||||||
processHandlers(e[1])
|
processHandlers(e[1])
|
||||||
processRoutines(table.unpack(e))
|
processRoutines(table.unpack(e))
|
||||||
|
end
|
||||||
|
|
||||||
if Event.terminate then
|
if Event.terminate then
|
||||||
return { 'terminate' }
|
return { 'terminate' }
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
local Socket = require('socket')
|
local Socket = require('socket')
|
||||||
local synchronized = require('sync')
|
local synchronized = require('sync').sync
|
||||||
|
|
||||||
local fs = _G.fs
|
local fs = _G.fs
|
||||||
|
|
||||||
|
@ -1,27 +1,51 @@
|
|||||||
local json = require('json')
|
local json = require('json')
|
||||||
local Util = require('util')
|
local Util = require('util')
|
||||||
|
|
||||||
|
-- Limit queries to once per minecraft day
|
||||||
|
-- TODO: will not work if time is stopped
|
||||||
|
|
||||||
local TREE_URL = 'https://api.github.com/repos/%s/%s/git/trees/%s?recursive=1'
|
local TREE_URL = 'https://api.github.com/repos/%s/%s/git/trees/%s?recursive=1'
|
||||||
local FILE_URL = 'https://raw.githubusercontent.com/%s/%s/%s/%s'
|
local FILE_URL = 'https://raw.githubusercontent.com/%s/%s/%s/%s'
|
||||||
|
|
||||||
local git = { }
|
local git = { }
|
||||||
|
|
||||||
function git.list(repository)
|
local fs = _G.fs
|
||||||
|
local os = _G.os
|
||||||
|
|
||||||
local t = Util.split(repository, '(.-)/')
|
if not _G.GIT then
|
||||||
|
_G.GIT = { }
|
||||||
local user = t[1]
|
|
||||||
local repo = t[2]
|
|
||||||
local branch = t[3] or 'master'
|
|
||||||
|
|
||||||
local dataUrl = string.format(TREE_URL, user, repo, branch)
|
|
||||||
local contents = Util.download(dataUrl)
|
|
||||||
|
|
||||||
if not contents then
|
|
||||||
error('Invalid repository')
|
|
||||||
end
|
end
|
||||||
|
|
||||||
local data = json.decode(contents)
|
function git.list(repository)
|
||||||
|
local t = Util.split(repository, '(.-)/')
|
||||||
|
|
||||||
|
local user = table.remove(t, 1)
|
||||||
|
local repo = table.remove(t, 1)
|
||||||
|
local branch = table.remove(t, 1) or 'master'
|
||||||
|
local path
|
||||||
|
|
||||||
|
if not Util.empty(t) then
|
||||||
|
path = table.concat(t, '/') .. '/'
|
||||||
|
end
|
||||||
|
|
||||||
|
local cacheKey = table.concat({ user, repo, branch }, '-')
|
||||||
|
local fname = fs.combine('.git', cacheKey)
|
||||||
|
|
||||||
|
local function getContents()
|
||||||
|
if fs.exists(fname) then
|
||||||
|
local contents = Util.readTable(fname)
|
||||||
|
if contents and contents.data == os.day() then
|
||||||
|
return contents.data
|
||||||
|
end
|
||||||
|
fs.delete(fname)
|
||||||
|
end
|
||||||
|
local dataUrl = string.format(TREE_URL, user, repo, branch)
|
||||||
|
local contents = Util.download(dataUrl)
|
||||||
|
if contents then
|
||||||
|
return json.decode(contents)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
local data = getContents() or error('Invalid repository')
|
||||||
|
|
||||||
if data.message and data.message:find("API rate limit exceeded") then
|
if data.message and data.message:find("API rate limit exceeded") then
|
||||||
error("Out of API calls, try again later")
|
error("Out of API calls, try again later")
|
||||||
@ -31,15 +55,26 @@ function git.list(repository)
|
|||||||
error("Invalid repository")
|
error("Invalid repository")
|
||||||
end
|
end
|
||||||
|
|
||||||
local list = { }
|
if not fs.exists(fname) then
|
||||||
|
Util.writeTable('.git/' .. cacheKey, { day = os.day(), data = data })
|
||||||
|
end
|
||||||
|
|
||||||
|
local list = { }
|
||||||
for _,v in pairs(data.tree) do
|
for _,v in pairs(data.tree) do
|
||||||
if v.type == "blob" then
|
if v.type == "blob" then
|
||||||
v.path = v.path:gsub("%s","%%20")
|
v.path = v.path:gsub("%s","%%20")
|
||||||
|
if not path then
|
||||||
list[v.path] = {
|
list[v.path] = {
|
||||||
url = string.format(FILE_URL, user, repo, branch, v.path),
|
url = string.format(FILE_URL, user, repo, branch, v.path),
|
||||||
size = v.size,
|
size = v.size,
|
||||||
}
|
}
|
||||||
|
elseif Util.startsWith(v.path, path) then
|
||||||
|
local p = string.sub(v.path, #path)
|
||||||
|
list[p] = {
|
||||||
|
url = string.format(FILE_URL, user, repo, branch, path .. p),
|
||||||
|
size = v.size,
|
||||||
|
}
|
||||||
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@ -68,7 +68,6 @@ local function encodeCommon(val, pretty, tabLevel, tTracking)
|
|||||||
str = str .. encodeCommon(v, pretty, tabLevel, tTracking)
|
str = str .. encodeCommon(v, pretty, tabLevel, tTracking)
|
||||||
end)
|
end)
|
||||||
else
|
else
|
||||||
debug(val)
|
|
||||||
arrEncoding(val, "{", "}", pairs, function(k,v)
|
arrEncoding(val, "{", "}", pairs, function(k,v)
|
||||||
assert(type(k) == "string", "JSON object keys must be strings", 2)
|
assert(type(k) == "string", "JSON object keys must be strings", 2)
|
||||||
str = str .. encodeCommon(k, pretty, tabLevel, tTracking)
|
str = str .. encodeCommon(k, pretty, tabLevel, tTracking)
|
||||||
|
60
sys/apis/packages.lua
Normal file
60
sys/apis/packages.lua
Normal file
@ -0,0 +1,60 @@
|
|||||||
|
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
|
@ -28,9 +28,9 @@ function Peripheral.addDevice(deviceList, side)
|
|||||||
end
|
end
|
||||||
|
|
||||||
if ptype == 'modem' then
|
if ptype == 'modem' then
|
||||||
if Peripheral.call(name, 'isWireless') then
|
if not Peripheral.call(name, 'isWireless') then
|
||||||
ptype = 'wireless_modem'
|
-- ptype = 'wireless_modem'
|
||||||
else
|
-- else
|
||||||
ptype = 'wired_modem'
|
ptype = 'wired_modem'
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
@ -55,7 +55,10 @@ function Peripheral.addDevice(deviceList, side)
|
|||||||
end
|
end
|
||||||
|
|
||||||
-- this can randomly fail
|
-- this can randomly fail
|
||||||
pcall(function() deviceList[name] = Peripheral.wrap(side) end)
|
if not deviceList[name] then
|
||||||
|
pcall(function()
|
||||||
|
deviceList[name] = Peripheral.wrap(side)
|
||||||
|
end)
|
||||||
|
|
||||||
if deviceList[name] then
|
if deviceList[name] then
|
||||||
Util.merge(deviceList[name], {
|
Util.merge(deviceList[name], {
|
||||||
@ -63,10 +66,11 @@ function Peripheral.addDevice(deviceList, side)
|
|||||||
type = ptype,
|
type = ptype,
|
||||||
side = side,
|
side = side,
|
||||||
})
|
})
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
return deviceList[name]
|
return deviceList[name]
|
||||||
end
|
end
|
||||||
end
|
|
||||||
|
|
||||||
function Peripheral.getBySide(side)
|
function Peripheral.getBySide(side)
|
||||||
return Util.find(Peripheral.getList(), 'side', side)
|
return Util.find(Peripheral.getList(), 'side', side)
|
||||||
|
@ -48,6 +48,13 @@ function Point.copy(pt)
|
|||||||
return { x = pt.x, y = pt.y, z = pt.z }
|
return { x = pt.x, y = pt.y, z = pt.z }
|
||||||
end
|
end
|
||||||
|
|
||||||
|
function Point.round(pt)
|
||||||
|
pt.x = Util.round(pt.x)
|
||||||
|
pt.y = Util.round(pt.y)
|
||||||
|
pt.z = Util.round(pt.z)
|
||||||
|
return pt
|
||||||
|
end
|
||||||
|
|
||||||
function Point.same(pta, ptb)
|
function Point.same(pta, ptb)
|
||||||
return pta.x == ptb.x and
|
return pta.x == ptb.x and
|
||||||
pta.y == ptb.y and
|
pta.y == ptb.y and
|
||||||
@ -144,14 +151,21 @@ function Point.calculateMoves(pta, ptb, distance)
|
|||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
if ptb.heading then
|
if not ptb.heading then
|
||||||
if heading ~= ptb.heading then
|
return moves, heading, moves
|
||||||
moves = moves + Point.calculateTurns(heading, ptb.heading)
|
|
||||||
heading = ptb.heading
|
|
||||||
end
|
|
||||||
end
|
end
|
||||||
|
|
||||||
return moves, heading
|
-- calc turns as slightly less than moves
|
||||||
|
local weighted = moves
|
||||||
|
if heading ~= ptb.heading then
|
||||||
|
local turns = Point.calculateTurns(heading, ptb.heading)
|
||||||
|
moves = moves + turns
|
||||||
|
local wturns = { [0] = 0, [1] = .9, [2] = 1.9 }
|
||||||
|
weighted = weighted + wturns[turns]
|
||||||
|
heading = ptb.heading
|
||||||
|
end
|
||||||
|
|
||||||
|
return moves, heading, weighted
|
||||||
end
|
end
|
||||||
|
|
||||||
-- given a set of points, find the one taking the least moves
|
-- given a set of points, find the one taking the least moves
|
||||||
@ -164,7 +178,7 @@ function Point.closest(reference, pts)
|
|||||||
for _,pt in pairs(pts) do
|
for _,pt in pairs(pts) do
|
||||||
local distance = Point.turtleDistance(reference, pt)
|
local distance = Point.turtleDistance(reference, pt)
|
||||||
if distance < lm then
|
if distance < lm then
|
||||||
local m = Point.calculateMoves(reference, pt, distance)
|
local _, _, m = Point.calculateMoves(reference, pt, distance)
|
||||||
if m < lm then
|
if m < lm then
|
||||||
lpt = pt
|
lpt = pt
|
||||||
lm = m
|
lm = m
|
||||||
|
@ -81,7 +81,8 @@ local function loopback(port, sport, msg)
|
|||||||
end
|
end
|
||||||
|
|
||||||
local function newSocket(isLoopback)
|
local function newSocket(isLoopback)
|
||||||
for i = 16384, 32767 do
|
for _ = 16384, 32767 do
|
||||||
|
local i = math.random(16384, 32767)
|
||||||
if not device.wireless_modem.isOpen(i) then
|
if not device.wireless_modem.isOpen(i) then
|
||||||
local socket = {
|
local socket = {
|
||||||
shost = os.getComputerID(),
|
shost = os.getComputerID(),
|
||||||
@ -128,6 +129,7 @@ function Socket.connect(host, port)
|
|||||||
local e, id, sport, dport, msg = os.pullEvent()
|
local e, id, sport, dport, msg = os.pullEvent()
|
||||||
if e == 'modem_message' and
|
if e == 'modem_message' and
|
||||||
sport == socket.sport and
|
sport == socket.sport and
|
||||||
|
type(msg) == 'table' and
|
||||||
msg.dhost == socket.shost then
|
msg.dhost == socket.shost then
|
||||||
|
|
||||||
os.cancelTimer(timerId)
|
os.cancelTimer(timerId)
|
||||||
@ -171,7 +173,7 @@ local function trusted(msg, port)
|
|||||||
local data = Crypto.decrypt(msg.t or '', pubKey)
|
local data = Crypto.decrypt(msg.t or '', pubKey)
|
||||||
|
|
||||||
--local sharedKey = modexp(pubKey, exchange.secretKey, public.primeMod)
|
--local sharedKey = modexp(pubKey, exchange.secretKey, public.primeMod)
|
||||||
return data.ts and tonumber(data.ts) and math.abs(os.time() - data.ts) < 1
|
return data.ts and tonumber(data.ts) and math.abs(os.time() - data.ts) < 24
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
@ -184,6 +186,7 @@ function Socket.server(port)
|
|||||||
|
|
||||||
if sport == port and
|
if sport == port and
|
||||||
msg and
|
msg and
|
||||||
|
type(msg) == 'table' and
|
||||||
msg.dhost == os.getComputerID() and
|
msg.dhost == os.getComputerID() and
|
||||||
msg.type == 'OPEN' then
|
msg.type == 'OPEN' then
|
||||||
|
|
||||||
|
18
sys/apis/sound.lua
Normal file
18
sys/apis/sound.lua
Normal file
@ -0,0 +1,18 @@
|
|||||||
|
local peripheral = _G.peripheral
|
||||||
|
|
||||||
|
local Sound = {
|
||||||
|
_volume = 1,
|
||||||
|
}
|
||||||
|
|
||||||
|
function Sound.play(sound, vol)
|
||||||
|
local speaker = peripheral.find('speaker')
|
||||||
|
if speaker then
|
||||||
|
speaker.playSound('minecraft:' .. sound, vol or Sound._volume)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
function Sound.setVolume(volume)
|
||||||
|
Sound._volume = math.max(0, math.min(volume, 1))
|
||||||
|
end
|
||||||
|
|
||||||
|
return Sound
|
@ -1,26 +1,61 @@
|
|||||||
local syncLocks = { }
|
local Sync = {
|
||||||
|
syncLocks = { }
|
||||||
|
}
|
||||||
|
|
||||||
local os = _G.os
|
local os = _G.os
|
||||||
|
|
||||||
return function(obj, fn)
|
function Sync.sync(obj, fn)
|
||||||
local key = tostring(obj)
|
local key = tostring(obj)
|
||||||
if syncLocks[key] then
|
if Sync.syncLocks[key] then
|
||||||
local cos = tostring(coroutine.running())
|
local cos = tostring(coroutine.running())
|
||||||
table.insert(syncLocks[key], cos)
|
table.insert(Sync.syncLocks[key], cos)
|
||||||
repeat
|
repeat
|
||||||
local _, co = os.pullEvent('sync_lock')
|
local _, co = os.pullEvent('sync_lock')
|
||||||
until co == cos
|
until co == cos
|
||||||
else
|
else
|
||||||
syncLocks[key] = { }
|
Sync.syncLocks[key] = { }
|
||||||
end
|
end
|
||||||
local s, m = pcall(fn)
|
local s, m = pcall(fn)
|
||||||
local co = table.remove(syncLocks[key], 1)
|
local co = table.remove(Sync.syncLocks[key], 1)
|
||||||
if co then
|
if co then
|
||||||
os.queueEvent('sync_lock', co)
|
os.queueEvent('sync_lock', co)
|
||||||
else
|
else
|
||||||
syncLocks[key] = nil
|
Sync.syncLocks[key] = nil
|
||||||
end
|
end
|
||||||
if not s then
|
if not s then
|
||||||
error(m)
|
error(m)
|
||||||
end
|
end
|
||||||
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
|
||||||
|
266
sys/apis/ui.lua
266
sys/apis/ui.lua
@ -3,6 +3,7 @@ local class = require('class')
|
|||||||
local Event = require('event')
|
local Event = require('event')
|
||||||
local Input = require('input')
|
local Input = require('input')
|
||||||
local Peripheral = require('peripheral')
|
local Peripheral = require('peripheral')
|
||||||
|
local Sound = require('sound')
|
||||||
local Transition = require('ui.transition')
|
local Transition = require('ui.transition')
|
||||||
local Util = require('util')
|
local Util = require('util')
|
||||||
|
|
||||||
@ -306,6 +307,9 @@ function Manager:setDefaultDevice(dev)
|
|||||||
end
|
end
|
||||||
|
|
||||||
function Manager:addPage(name, page)
|
function Manager:addPage(name, page)
|
||||||
|
if not self.pages then
|
||||||
|
self.pages = { }
|
||||||
|
end
|
||||||
self.pages[name] = page
|
self.pages[name] = page
|
||||||
end
|
end
|
||||||
|
|
||||||
@ -940,6 +944,7 @@ function UI.Device:postInit()
|
|||||||
end
|
end
|
||||||
|
|
||||||
function UI.Device:resize()
|
function UI.Device:resize()
|
||||||
|
self.device.setTextScale(self.textScale)
|
||||||
self.width, self.height = self.device.getSize()
|
self.width, self.height = self.device.getSize()
|
||||||
self.lines = { }
|
self.lines = { }
|
||||||
self.canvas:resize(self.width, self.height)
|
self.canvas:resize(self.width, self.height)
|
||||||
@ -1055,6 +1060,17 @@ function UI.StringBuffer:insert(s, width)
|
|||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
function UI.StringBuffer:insertRight(s, width)
|
||||||
|
local len = #tostring(s or '')
|
||||||
|
if len > width then
|
||||||
|
s = _sub(s, 1, width)
|
||||||
|
end
|
||||||
|
if len < width then
|
||||||
|
table.insert(self.buffer, _rep(' ', width - len))
|
||||||
|
end
|
||||||
|
table.insert(self.buffer, s)
|
||||||
|
end
|
||||||
|
|
||||||
function UI.StringBuffer:get(sep)
|
function UI.StringBuffer:get(sep)
|
||||||
return Util.widthify(table.concat(self.buffer, sep or ''), self.bufSize)
|
return Util.widthify(table.concat(self.buffer, sep or ''), self.bufSize)
|
||||||
end
|
end
|
||||||
@ -1205,7 +1221,7 @@ function UI.Page:focusNext()
|
|||||||
end
|
end
|
||||||
|
|
||||||
function UI.Page:setFocus(child)
|
function UI.Page:setFocus(child)
|
||||||
if not child.focus then
|
if not child or not child.focus then
|
||||||
return
|
return
|
||||||
end
|
end
|
||||||
|
|
||||||
@ -1218,7 +1234,8 @@ function UI.Page:setFocus(child)
|
|||||||
self.focused = child
|
self.focused = child
|
||||||
if not child.focused then
|
if not child.focused then
|
||||||
child.focused = true
|
child.focused = true
|
||||||
self:emit({ type = 'focus_change', focused = child })
|
child:emit({ type = 'focus_change', focused = child })
|
||||||
|
--self:emit({ type = 'focus_change', focused = child })
|
||||||
end
|
end
|
||||||
|
|
||||||
child:focus()
|
child:focus()
|
||||||
@ -1250,6 +1267,7 @@ UI.Grid.defaults = {
|
|||||||
backgroundSelectedColor = colors.gray,
|
backgroundSelectedColor = colors.gray,
|
||||||
headerBackgroundColor = colors.cyan,
|
headerBackgroundColor = colors.cyan,
|
||||||
headerTextColor = colors.white,
|
headerTextColor = colors.white,
|
||||||
|
headerSortColor = colors.yellow,
|
||||||
unfocusedTextSelectedColor = colors.white,
|
unfocusedTextSelectedColor = colors.white,
|
||||||
unfocusedBackgroundSelectedColor = colors.gray,
|
unfocusedBackgroundSelectedColor = colors.gray,
|
||||||
focusIndicator = '>',
|
focusIndicator = '>',
|
||||||
@ -1407,6 +1425,18 @@ function UI.Grid:getSelected()
|
|||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
function UI.Grid:setSelected(name, value)
|
||||||
|
if self.sorted then
|
||||||
|
for k,v in pairs(self.sorted) do
|
||||||
|
if self.values[v][name] == value then
|
||||||
|
self:setIndex(k)
|
||||||
|
return
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
self:setIndex(1)
|
||||||
|
end
|
||||||
|
|
||||||
function UI.Grid:focus()
|
function UI.Grid:focus()
|
||||||
self:drawRows()
|
self:drawRows()
|
||||||
end
|
end
|
||||||
@ -1459,7 +1489,7 @@ function UI.Grid:update()
|
|||||||
end
|
end
|
||||||
|
|
||||||
function UI.Grid:drawHeadings()
|
function UI.Grid:drawHeadings()
|
||||||
local sb = UI.StringBuffer(self.width)
|
local x = 1
|
||||||
for _,col in ipairs(self.columns) do
|
for _,col in ipairs(self.columns) do
|
||||||
local ind = ' '
|
local ind = ' '
|
||||||
if col.key == self.sortColumn then
|
if col.key == self.sortColumn then
|
||||||
@ -1469,9 +1499,13 @@ function UI.Grid:drawHeadings()
|
|||||||
ind = self.sortIndicator
|
ind = self.sortIndicator
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
sb:insert(ind .. col.heading, col.cw + 1)
|
self:write(x,
|
||||||
|
1,
|
||||||
|
Util.widthify(ind .. col.heading, col.cw + 1),
|
||||||
|
self.headerBackgroundColor,
|
||||||
|
col.key == self.sortColumn and self.headerSortColor or self.headerTextColor)
|
||||||
|
x = x + col.cw + 1
|
||||||
end
|
end
|
||||||
self:write(1, 1, sb:get(), self.headerBackgroundColor, self.headerTextColor)
|
|
||||||
end
|
end
|
||||||
|
|
||||||
function UI.Grid:sortCompare(a, b)
|
function UI.Grid:sortCompare(a, b)
|
||||||
@ -1508,7 +1542,11 @@ function UI.Grid:drawRows()
|
|||||||
end
|
end
|
||||||
|
|
||||||
for _,col in pairs(self.columns) do
|
for _,col in pairs(self.columns) do
|
||||||
|
if col.justify == 'right' then
|
||||||
|
sb:insertRight(ind .. safeValue(row[col.key] or ''), col.cw + 1)
|
||||||
|
else
|
||||||
sb:insert(ind .. safeValue(row[col.key] or ''), col.cw + 1)
|
sb:insert(ind .. safeValue(row[col.key] or ''), col.cw + 1)
|
||||||
|
end
|
||||||
ind = ' '
|
ind = ' '
|
||||||
end
|
end
|
||||||
|
|
||||||
@ -1603,13 +1641,12 @@ function UI.Grid:eventHandler(event)
|
|||||||
local col = 2
|
local col = 2
|
||||||
for _,c in ipairs(self.columns) do
|
for _,c in ipairs(self.columns) do
|
||||||
if event.x < col + c.cw then
|
if event.x < col + c.cw then
|
||||||
if self.sortColumn == c.key then
|
self:emit({
|
||||||
self:setInverseSort(not self.inverseSort)
|
type = 'grid_sort',
|
||||||
else
|
sortColumn = c.key,
|
||||||
self.sortColumn = c.key
|
inverseSort = self.sortColumn == c.key and not self.inverseSort,
|
||||||
self:setInverseSort(false)
|
element = self,
|
||||||
end
|
})
|
||||||
self:draw()
|
|
||||||
break
|
break
|
||||||
end
|
end
|
||||||
col = col + c.cw + 1
|
col = col + c.cw + 1
|
||||||
@ -1632,6 +1669,10 @@ function UI.Grid:eventHandler(event)
|
|||||||
end
|
end
|
||||||
return false
|
return false
|
||||||
|
|
||||||
|
elseif event.type == 'grid_sort' then
|
||||||
|
self.sortColumn = event.sortColumn
|
||||||
|
self:setInverseSort(event.inverseSort)
|
||||||
|
self:draw()
|
||||||
elseif event.type == 'scroll_down' then
|
elseif event.type == 'scroll_down' then
|
||||||
self:setIndex(self.index + 1)
|
self:setIndex(self.index + 1)
|
||||||
elseif event.type == 'scroll_up' then
|
elseif event.type == 'scroll_up' then
|
||||||
@ -1854,6 +1895,8 @@ function UI.TitleBar:draw()
|
|||||||
sb:center(string.format(' %s ', self.title))
|
sb:center(string.format(' %s ', self.title))
|
||||||
if self.previousPage or self.event then
|
if self.previousPage or self.event then
|
||||||
sb:insert(-1, self.closeInd)
|
sb:insert(-1, self.closeInd)
|
||||||
|
else
|
||||||
|
sb:insert(-2, self.frameChar)
|
||||||
end
|
end
|
||||||
self:write(1, 1, sb:get())
|
self:write(1, 1, sb:get())
|
||||||
end
|
end
|
||||||
@ -2286,56 +2329,52 @@ function UI.Wizard:add(pages)
|
|||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
function UI.Wizard:enable()
|
function UI.Wizard:getPage(index)
|
||||||
|
return Util.find(self.pages, 'index', index)
|
||||||
|
end
|
||||||
|
|
||||||
|
function UI.Wizard:enable(...)
|
||||||
self.enabled = true
|
self.enabled = true
|
||||||
for _,child in ipairs(self.children) do
|
self.index = 1
|
||||||
if not child.index then
|
local initial = self:getPage(1)
|
||||||
child:enable()
|
for _,child in pairs(self.children) do
|
||||||
elseif child.index == 1 then
|
if child == initial or not child.index then
|
||||||
child:enable()
|
child:enable(...)
|
||||||
else
|
else
|
||||||
child:disable()
|
child:disable()
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
self:emit({ type = 'enable_view', next = Util.find(self.pages, 'index', 1) })
|
self:emit({ type = 'enable_view', next = initial })
|
||||||
end
|
end
|
||||||
|
|
||||||
function UI.Wizard:nextView()
|
function UI.Wizard:isViewValid()
|
||||||
local currentView = Util.find(self.pages, 'enabled', true)
|
local currentView = self:getPage(self.index)
|
||||||
local nextView = Util.find(self.pages, 'index', currentView.index + 1)
|
return not currentView.validate and true or currentView:validate()
|
||||||
|
|
||||||
if nextView then
|
|
||||||
self:emit({ type = 'enable_view', view = nextView })
|
|
||||||
self:addTransition('slideLeft')
|
|
||||||
currentView:disable()
|
|
||||||
nextView:enable()
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
function UI.Wizard:prevView()
|
|
||||||
local currentView = Util.find(self.pages, 'enabled', true)
|
|
||||||
local nextView = Util.find(self.pages, 'index', currentView.index - 1)
|
|
||||||
|
|
||||||
if nextView then
|
|
||||||
self:emit({ type = 'enable_view', view = nextView })
|
|
||||||
self:addTransition('slideRight')
|
|
||||||
currentView:disable()
|
|
||||||
nextView:enable()
|
|
||||||
end
|
|
||||||
end
|
end
|
||||||
|
|
||||||
function UI.Wizard:eventHandler(event)
|
function UI.Wizard:eventHandler(event)
|
||||||
if event.type == 'nextView' then
|
if event.type == 'nextView' then
|
||||||
local currentView = Util.find(self.pages, 'enabled', true)
|
local currentView = self:getPage(self.index)
|
||||||
local nextView = Util.find(self.pages, 'index', currentView.index + 1)
|
if self:isViewValid() then
|
||||||
|
self.index = self.index + 1
|
||||||
|
local nextView = self:getPage(self.index)
|
||||||
currentView:emit({ type = 'enable_view', next = nextView, current = currentView })
|
currentView:emit({ type = 'enable_view', next = nextView, current = currentView })
|
||||||
|
end
|
||||||
|
|
||||||
elseif event.type == 'previousView' then
|
elseif event.type == 'previousView' then
|
||||||
local currentView = Util.find(self.pages, 'enabled', true)
|
local currentView = self:getPage(self.index)
|
||||||
local nextView = Util.find(self.pages, 'index', currentView.index - 1)
|
local nextView = self:getPage(self.index - 1)
|
||||||
|
if nextView then
|
||||||
|
self.index = self.index - 1
|
||||||
currentView:emit({ type = 'enable_view', prev = nextView, current = currentView })
|
currentView:emit({ type = 'enable_view', prev = nextView, current = currentView })
|
||||||
|
end
|
||||||
return true
|
return true
|
||||||
|
|
||||||
|
elseif event.type == 'wizard_complete' then
|
||||||
|
if self:isViewValid() then
|
||||||
|
self:emit({ type = 'accept' })
|
||||||
|
end
|
||||||
|
|
||||||
elseif event.type == 'enable_view' then
|
elseif event.type == 'enable_view' then
|
||||||
if event.current then
|
if event.current then
|
||||||
if event.next then
|
if event.next then
|
||||||
@ -2347,18 +2386,20 @@ function UI.Wizard:eventHandler(event)
|
|||||||
end
|
end
|
||||||
|
|
||||||
local current = event.next or event.prev
|
local current = event.next or event.prev
|
||||||
if Util.find(self.pages, 'index', current.index - 1) then
|
if not current then error('property "index" is required on wizard pages') end
|
||||||
|
|
||||||
|
if self:getPage(self.index - 1) then
|
||||||
self.previousButton:enable()
|
self.previousButton:enable()
|
||||||
else
|
else
|
||||||
self.previousButton:disable()
|
self.previousButton:disable()
|
||||||
end
|
end
|
||||||
|
|
||||||
if Util.find(self.pages, 'index', current.index + 1) then
|
if self:getPage(self.index + 1) then
|
||||||
self.nextButton.text = 'Next >'
|
self.nextButton.text = 'Next >'
|
||||||
self.nextButton.event = 'nextView'
|
self.nextButton.event = 'nextView'
|
||||||
else
|
else
|
||||||
self.nextButton.text = 'Accept'
|
self.nextButton.text = 'Accept'
|
||||||
self.nextButton.event = 'accept'
|
self.nextButton.event = 'wizard_complete'
|
||||||
end
|
end
|
||||||
-- a new current view
|
-- a new current view
|
||||||
current:enable()
|
current:enable()
|
||||||
@ -2381,12 +2422,12 @@ function UI.SlideOut:enable()
|
|||||||
self.enabled = false
|
self.enabled = false
|
||||||
end
|
end
|
||||||
|
|
||||||
function UI.SlideOut:show()
|
function UI.SlideOut:show(...)
|
||||||
self:addTransition('expandUp')
|
self:addTransition('expandUp')
|
||||||
self.canvas:setVisible(true)
|
self.canvas:setVisible(true)
|
||||||
self.enabled = true
|
self.enabled = true
|
||||||
for _,child in pairs(self.children) do
|
for _,child in pairs(self.children) do
|
||||||
child:enable()
|
child:enable(...)
|
||||||
end
|
end
|
||||||
self:draw()
|
self:draw()
|
||||||
self:capture(self)
|
self:capture(self)
|
||||||
@ -2494,6 +2535,7 @@ end
|
|||||||
|
|
||||||
function UI.Notification:error(value, timeout)
|
function UI.Notification:error(value, timeout)
|
||||||
self.backgroundColor = colors.red
|
self.backgroundColor = colors.red
|
||||||
|
Sound.play('entity.villager.no', .5)
|
||||||
self:display(value, timeout)
|
self:display(value, timeout)
|
||||||
end
|
end
|
||||||
|
|
||||||
@ -2544,9 +2586,10 @@ UI.Throttle = class(UI.Window)
|
|||||||
UI.Throttle.defaults = {
|
UI.Throttle.defaults = {
|
||||||
UIElement = 'Throttle',
|
UIElement = 'Throttle',
|
||||||
backgroundColor = colors.gray,
|
backgroundColor = colors.gray,
|
||||||
height = 6,
|
bordercolor = colors.cyan,
|
||||||
|
height = 4,
|
||||||
width = 10,
|
width = 10,
|
||||||
timeout = .095,
|
timeout = .075,
|
||||||
ctr = 0,
|
ctr = 0,
|
||||||
image = {
|
image = {
|
||||||
' //) (O )~@ &~&-( ?Q ',
|
' //) (O )~@ &~&-( ?Q ',
|
||||||
@ -2562,6 +2605,7 @@ function UI.Throttle:setParent()
|
|||||||
end
|
end
|
||||||
|
|
||||||
function UI.Throttle:enable()
|
function UI.Throttle:enable()
|
||||||
|
self.c = os.clock()
|
||||||
self.enabled = false
|
self.enabled = false
|
||||||
end
|
end
|
||||||
|
|
||||||
@ -2570,27 +2614,26 @@ function UI.Throttle:disable()
|
|||||||
self.enabled = false
|
self.enabled = false
|
||||||
self.canvas:removeLayer()
|
self.canvas:removeLayer()
|
||||||
self.canvas = nil
|
self.canvas = nil
|
||||||
self.c = nil
|
self.ctr = 0
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
function UI.Throttle:update()
|
function UI.Throttle:update()
|
||||||
local cc = os.clock()
|
local cc = os.clock()
|
||||||
if not self.c then
|
if cc > self.c + self.timeout then
|
||||||
self.c = cc
|
|
||||||
elseif cc > self.c + self.timeout then
|
|
||||||
os.sleep(0)
|
os.sleep(0)
|
||||||
self.c = os.clock()
|
self.c = os.clock()
|
||||||
self.enabled = true
|
self.enabled = true
|
||||||
if not self.canvas then
|
if not self.canvas then
|
||||||
self.canvas = self:addLayer(self.backgroundColor, colors.cyan)
|
self.canvas = self:addLayer(self.backgroundColor, self.borderColor)
|
||||||
self.canvas:setVisible(true)
|
self.canvas:setVisible(true)
|
||||||
self:clear(colors.cyan)
|
self:clear(self.borderColor)
|
||||||
end
|
end
|
||||||
local image = self.image[self.ctr + 1]
|
local image = self.image[self.ctr + 1]
|
||||||
local width = self.width - 2
|
local width = self.width - 2
|
||||||
for i = 0, #self.image do
|
for i = 0, #self.image do
|
||||||
self:write(2, i + 2, image:sub(width * i + 1, width * i + width), colors.black, colors.white)
|
self:write(2, i + 1, image:sub(width * i + 1, width * i + width),
|
||||||
|
self.backgroundColor, self.textColor)
|
||||||
end
|
end
|
||||||
|
|
||||||
self.ctr = (self.ctr + 1) % #self.image
|
self.ctr = (self.ctr + 1) % #self.image
|
||||||
@ -2913,6 +2956,9 @@ UI.Chooser.defaults = {
|
|||||||
choices = { },
|
choices = { },
|
||||||
nochoice = 'Select',
|
nochoice = 'Select',
|
||||||
backgroundFocusColor = colors.lightGray,
|
backgroundFocusColor = colors.lightGray,
|
||||||
|
textInactiveColor = colors.gray,
|
||||||
|
leftIndicator = '<',
|
||||||
|
rightIndicator = '>',
|
||||||
height = 1,
|
height = 1,
|
||||||
}
|
}
|
||||||
function UI.Chooser:setParent()
|
function UI.Chooser:setParent()
|
||||||
@ -2933,14 +2979,15 @@ function UI.Chooser:draw()
|
|||||||
if self.focused then
|
if self.focused then
|
||||||
bg = self.backgroundFocusColor
|
bg = self.backgroundFocusColor
|
||||||
end
|
end
|
||||||
|
local fg = self.inactive and self.textInactiveColor or self.textColor
|
||||||
local choice = Util.find(self.choices, 'value', self.value)
|
local choice = Util.find(self.choices, 'value', self.value)
|
||||||
local value = self.nochoice
|
local value = self.nochoice
|
||||||
if choice then
|
if choice then
|
||||||
value = choice.name
|
value = choice.name
|
||||||
end
|
end
|
||||||
self:write(1, 1, '<', bg, colors.black)
|
self:write(1, 1, self.leftIndicator, self.backgroundColor, colors.black)
|
||||||
self:write(2, 1, ' ' .. Util.widthify(value, self.width-4) .. ' ', bg)
|
self:write(2, 1, ' ' .. Util.widthify(tostring(value), self.width-4) .. ' ', bg, fg)
|
||||||
self:write(self.width, 1, '>', bg, colors.black)
|
self:write(self.width, 1, self.rightIndicator, self.backgroundColor, colors.black)
|
||||||
end
|
end
|
||||||
|
|
||||||
function UI.Chooser:focus()
|
function UI.Chooser:focus()
|
||||||
@ -2951,22 +2998,26 @@ function UI.Chooser:eventHandler(event)
|
|||||||
if event.type == 'key' then
|
if event.type == 'key' then
|
||||||
if event.key == 'right' or event.key == 'space' then
|
if event.key == 'right' or event.key == 'space' then
|
||||||
local _,k = Util.find(self.choices, 'value', self.value)
|
local _,k = Util.find(self.choices, 'value', self.value)
|
||||||
|
local choice
|
||||||
if k and k < #self.choices then
|
if k and k < #self.choices then
|
||||||
self.value = self.choices[k+1].value
|
choice = self.choices[k+1]
|
||||||
else
|
else
|
||||||
self.value = self.choices[1].value
|
choice = self.choices[1]
|
||||||
end
|
end
|
||||||
self:emit({ type = 'choice_change', value = self.value })
|
self.value = choice.value
|
||||||
|
self:emit({ type = 'choice_change', value = self.value, element = self, choice = choice })
|
||||||
self:draw()
|
self:draw()
|
||||||
return true
|
return true
|
||||||
elseif event.key == 'left' then
|
elseif event.key == 'left' then
|
||||||
local _,k = Util.find(self.choices, 'value', self.value)
|
local _,k = Util.find(self.choices, 'value', self.value)
|
||||||
|
local choice
|
||||||
if k and k > 1 then
|
if k and k > 1 then
|
||||||
self.value = self.choices[k-1].value
|
choice = self.choices[k-1]
|
||||||
else
|
else
|
||||||
self.value = self.choices[#self.choices].value
|
choice = self.choices[#self.choices]
|
||||||
end
|
end
|
||||||
self:emit({ type = 'choice_change', value = self.value })
|
self.value = choice.value
|
||||||
|
self:emit({ type = 'choice_change', value = self.value, element = self, choice = choice })
|
||||||
self:draw()
|
self:draw()
|
||||||
return true
|
return true
|
||||||
end
|
end
|
||||||
@ -2981,6 +3032,57 @@ function UI.Chooser:eventHandler(event)
|
|||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
--[[-- Chooser --]]--
|
||||||
|
UI.Checkbox = class(UI.Window)
|
||||||
|
UI.Checkbox.defaults = {
|
||||||
|
UIElement = 'Checkbox',
|
||||||
|
nochoice = 'Select',
|
||||||
|
checkedIndicator = 'X',
|
||||||
|
leftMarker = '[',
|
||||||
|
rightMarker = ']',
|
||||||
|
value = false,
|
||||||
|
textColor = colors.white,
|
||||||
|
backgroundColor = colors.black,
|
||||||
|
backgroundFocusColor = colors.lightGray,
|
||||||
|
height = 1,
|
||||||
|
width = 3,
|
||||||
|
accelerators = {
|
||||||
|
space = 'checkbox_toggle',
|
||||||
|
mouse_click = 'checkbox_toggle',
|
||||||
|
}
|
||||||
|
}
|
||||||
|
function UI.Checkbox:draw()
|
||||||
|
local bg = self.backgroundColor
|
||||||
|
if self.focused then
|
||||||
|
bg = self.backgroundFocusColor
|
||||||
|
end
|
||||||
|
if type(self.value) == 'string' then
|
||||||
|
self.value = nil -- TODO: fix form
|
||||||
|
end
|
||||||
|
local text = string.format('[%s]', not self.value and ' ' or self.checkedIndicator)
|
||||||
|
self:write(1, 1, text, bg)
|
||||||
|
self:write(1, 1, self.leftMarker, self.backgroundColor, self.textColor)
|
||||||
|
self:write(2, 1, not self.value and ' ' or self.checkedIndicator, bg)
|
||||||
|
self:write(3, 1, self.rightMarker, self.backgroundColor, self.textColor)
|
||||||
|
end
|
||||||
|
|
||||||
|
function UI.Checkbox:focus()
|
||||||
|
self:draw()
|
||||||
|
end
|
||||||
|
|
||||||
|
function UI.Checkbox:reset()
|
||||||
|
self.value = false
|
||||||
|
end
|
||||||
|
|
||||||
|
function UI.Checkbox:eventHandler(event)
|
||||||
|
if event.type == 'checkbox_toggle' then
|
||||||
|
self.value = not self.value
|
||||||
|
self:emit({ type = 'checkbox_change', checked = self.value, element = self })
|
||||||
|
self:draw()
|
||||||
|
return true
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
--[[-- Text --]]--
|
--[[-- Text --]]--
|
||||||
UI.Text = class(UI.Window)
|
UI.Text = class(UI.Window)
|
||||||
UI.Text.defaults = {
|
UI.Text.defaults = {
|
||||||
@ -3180,6 +3282,7 @@ function UI.Form:createForm()
|
|||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
if not self.manualControls then
|
||||||
table.insert(self.children, UI.Button {
|
table.insert(self.children, UI.Button {
|
||||||
y = -self.margin, x = -12 - self.margin,
|
y = -self.margin, x = -12 - self.margin,
|
||||||
text = 'Ok',
|
text = 'Ok',
|
||||||
@ -3191,6 +3294,7 @@ function UI.Form:createForm()
|
|||||||
event = 'form_cancel',
|
event = 'form_cancel',
|
||||||
})
|
})
|
||||||
end
|
end
|
||||||
|
end
|
||||||
|
|
||||||
function UI.Form:validateField(field)
|
function UI.Form:validateField(field)
|
||||||
if field.required then
|
if field.required then
|
||||||
@ -3198,11 +3302,17 @@ function UI.Form:validateField(field)
|
|||||||
return false, 'Field is required'
|
return false, 'Field is required'
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
if field.validate == 'numeric' then
|
||||||
|
if #tostring(field.value) > 0 then
|
||||||
|
if not tonumber(field.value) then
|
||||||
|
return false, 'Invalid number'
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
return true
|
return true
|
||||||
end
|
end
|
||||||
|
|
||||||
function UI.Form:eventHandler(event)
|
function UI.Form:save()
|
||||||
if event.type == 'form_ok' then
|
|
||||||
for _,child in pairs(self.children) do
|
for _,child in pairs(self.children) do
|
||||||
if child.formKey then
|
if child.formKey then
|
||||||
local s, m = self:validateField(child)
|
local s, m = self:validateField(child)
|
||||||
@ -3215,9 +3325,25 @@ function UI.Form:eventHandler(event)
|
|||||||
end
|
end
|
||||||
for _,child in pairs(self.children) do
|
for _,child in pairs(self.children) do
|
||||||
if child.formKey then
|
if child.formKey then
|
||||||
|
if (child.pruneEmpty and type(child.value) == 'string' and #child.value == 0) or
|
||||||
|
(child.pruneEmpty and type(child.value) == 'boolean' and not child.value) then
|
||||||
|
self.values[child.formKey] = nil
|
||||||
|
elseif child.validate == 'numeric' then
|
||||||
|
self.values[child.formKey] = tonumber(child.value)
|
||||||
|
else
|
||||||
self.values[child.formKey] = child.value
|
self.values[child.formKey] = child.value
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
return true
|
||||||
|
end
|
||||||
|
|
||||||
|
function UI.Form:eventHandler(event)
|
||||||
|
if event.type == 'form_ok' then
|
||||||
|
if not self:save() then
|
||||||
|
return false
|
||||||
|
end
|
||||||
self:emit({ type = self.event, UIElement = self })
|
self:emit({ type = self.event, UIElement = self })
|
||||||
else
|
else
|
||||||
return UI.Window.eventHandler(self, event)
|
return UI.Window.eventHandler(self, event)
|
||||||
|
@ -137,7 +137,7 @@ function Canvas:writeBlit(x, y, text, bg, fg)
|
|||||||
if bg then
|
if bg then
|
||||||
bg = _sub(bg, 2 - x)
|
bg = _sub(bg, 2 - x)
|
||||||
end
|
end
|
||||||
if bg then
|
if fg then
|
||||||
fg = _sub(fg, 2 - x)
|
fg = _sub(fg, 2 - x)
|
||||||
end
|
end
|
||||||
width = width + x - 1
|
width = width + x - 1
|
||||||
@ -149,7 +149,7 @@ function Canvas:writeBlit(x, y, text, bg, fg)
|
|||||||
if bg then
|
if bg then
|
||||||
bg = _sub(bg, 1, self.width - x + 1)
|
bg = _sub(bg, 1, self.width - x + 1)
|
||||||
end
|
end
|
||||||
if bg then
|
if fg then
|
||||||
fg = _sub(fg, 1, self.width - x + 1)
|
fg = _sub(fg, 1, self.width - x + 1)
|
||||||
end
|
end
|
||||||
width = #text
|
width = #text
|
||||||
|
@ -172,6 +172,20 @@ function Util.deepMerge(obj, args)
|
|||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
-- remove table entries if passed function returns false
|
||||||
|
function Util.prune(t, fn)
|
||||||
|
for _,k in pairs(Util.keys(t)) do
|
||||||
|
local v = t[k]
|
||||||
|
if type(v) == 'table' then
|
||||||
|
t[k] = Util.prune(v, fn)
|
||||||
|
end
|
||||||
|
if not fn(t[k]) then
|
||||||
|
t[k] = nil
|
||||||
|
end
|
||||||
|
end
|
||||||
|
return t
|
||||||
|
end
|
||||||
|
|
||||||
function Util.transpose(t)
|
function Util.transpose(t)
|
||||||
local tt = { }
|
local tt = { }
|
||||||
for k,v in pairs(t) do
|
for k,v in pairs(t) do
|
||||||
@ -207,6 +221,7 @@ function Util.findAll(t, name, value)
|
|||||||
end
|
end
|
||||||
|
|
||||||
function Util.shallowCopy(t)
|
function Util.shallowCopy(t)
|
||||||
|
if not t then error('Util.shallowCopy: invalid table', 2) end
|
||||||
local t2 = { }
|
local t2 = { }
|
||||||
for k,v in pairs(t) do
|
for k,v in pairs(t) do
|
||||||
t2[k] = v
|
t2[k] = v
|
||||||
@ -342,6 +357,7 @@ function Util.readFile(fname)
|
|||||||
end
|
end
|
||||||
|
|
||||||
function Util.writeFile(fname, data)
|
function Util.writeFile(fname, data)
|
||||||
|
if not fname or not data then error('Util.writeFile: invalid parameters', 2) end
|
||||||
local file = io.open(fname, "w")
|
local file = io.open(fname, "w")
|
||||||
if not file then
|
if not file then
|
||||||
error('Unable to open ' .. fname, 2)
|
error('Unable to open ' .. fname, 2)
|
||||||
@ -415,7 +431,7 @@ end
|
|||||||
function Util.download(url, filename)
|
function Util.download(url, filename)
|
||||||
local contents, msg = Util.httpGet(url)
|
local contents, msg = Util.httpGet(url)
|
||||||
if not contents then
|
if not contents then
|
||||||
error(string.format('Failed to download %s\n%s', url, msg))
|
error(string.format('Failed to download %s\n%s', url, msg), 2)
|
||||||
end
|
end
|
||||||
|
|
||||||
if filename then
|
if filename then
|
||||||
@ -475,6 +491,7 @@ function Util.insertString(str, istr, pos)
|
|||||||
end
|
end
|
||||||
|
|
||||||
function Util.split(str, pattern)
|
function Util.split(str, pattern)
|
||||||
|
if not str then error('Util.split: Invalid parameters', 2) end
|
||||||
pattern = pattern or "(.-)\n"
|
pattern = pattern or "(.-)\n"
|
||||||
local t = {}
|
local t = {}
|
||||||
local function helper(line) table.insert(t, line) return "" end
|
local function helper(line) table.insert(t, line) return "" end
|
||||||
@ -491,7 +508,7 @@ function Util.matches(str, pattern)
|
|||||||
return t
|
return t
|
||||||
end
|
end
|
||||||
|
|
||||||
function Util.startsWidth(s, match)
|
function Util.startsWith(s, match)
|
||||||
return string.sub(s, 1, #match) == match
|
return string.sub(s, 1, #match) == match
|
||||||
end
|
end
|
||||||
|
|
||||||
@ -653,7 +670,6 @@ function Util.getOptions(options, args, ignoreInvalid)
|
|||||||
end
|
end
|
||||||
end
|
end
|
||||||
return true, Util.size(rawOptions)
|
return true, Util.size(rawOptions)
|
||||||
|
|
||||||
end
|
end
|
||||||
|
|
||||||
return Util
|
return Util
|
||||||
|
@ -1,5 +1,6 @@
|
|||||||
_G.requireInjector(_ENV)
|
_G.requireInjector(_ENV)
|
||||||
|
|
||||||
|
local Config = require('config')
|
||||||
local Event = require('event')
|
local Event = require('event')
|
||||||
local Socket = require('socket')
|
local Socket = require('socket')
|
||||||
local UI = require('ui')
|
local UI = require('ui')
|
||||||
@ -20,6 +21,9 @@ local gridColumns = {
|
|||||||
{ heading = 'Status', key = 'status' },
|
{ heading = 'Status', key = 'status' },
|
||||||
}
|
}
|
||||||
|
|
||||||
|
local trusted = Util.readTable('usr/.known_hosts')
|
||||||
|
local config = Config.load('network', { })
|
||||||
|
|
||||||
if UI.term.width >= 30 then
|
if UI.term.width >= 30 then
|
||||||
table.insert(gridColumns, { heading = 'Fuel', key = 'fuel', width = 5 })
|
table.insert(gridColumns, { heading = 'Fuel', key = 'fuel', width = 5 })
|
||||||
table.insert(gridColumns, { heading = 'Uptime', key = 'uptime' })
|
table.insert(gridColumns, { heading = 'Uptime', key = 'uptime' })
|
||||||
@ -39,7 +43,16 @@ local page = UI.Page {
|
|||||||
{ text = 'Establish', event = 'trust' },
|
{ text = 'Establish', event = 'trust' },
|
||||||
{ text = 'Remove', event = 'untrust' },
|
{ text = 'Remove', event = 'untrust' },
|
||||||
} },
|
} },
|
||||||
{ text = 'Help', event = 'help' },
|
{ text = 'Help', event = 'help', noCheck = true },
|
||||||
|
{
|
||||||
|
text = '\206',
|
||||||
|
x = -3,
|
||||||
|
dropdown = {
|
||||||
|
{ text = 'Show all', event = 'show_all', noCheck = true },
|
||||||
|
UI.MenuBar.spacer,
|
||||||
|
{ text = 'Show trusted', event = 'show_trusted', noCheck = true },
|
||||||
|
},
|
||||||
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
grid = UI.ScrollingGrid {
|
grid = UI.ScrollingGrid {
|
||||||
@ -143,6 +156,16 @@ This only needs to be done once.
|
|||||||
q = 'cancel',
|
q = 'cancel',
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
|
elseif event.type == 'show_all' then
|
||||||
|
config.showTrusted = false
|
||||||
|
self.grid:setValues(network)
|
||||||
|
Config.update('network', config)
|
||||||
|
|
||||||
|
elseif event.type == 'show_trusted' then
|
||||||
|
config.showTrusted = true
|
||||||
|
Config.update('network', config)
|
||||||
|
|
||||||
elseif event.type == 'quit' then
|
elseif event.type == 'quit' then
|
||||||
Event.exitPullEvents()
|
Event.exitPullEvents()
|
||||||
end
|
end
|
||||||
@ -155,7 +178,7 @@ function page.menuBar:getActive(menuItem)
|
|||||||
local trustList = Util.readTable('usr/.known_hosts') or { }
|
local trustList = Util.readTable('usr/.known_hosts') or { }
|
||||||
return t and trustList[t.id]
|
return t and trustList[t.id]
|
||||||
end
|
end
|
||||||
return not not t
|
return menuItem.noCheck or not not t
|
||||||
end
|
end
|
||||||
|
|
||||||
function page.grid:getRowTextColor(row, selected)
|
function page.grid:getRowTextColor(row, selected)
|
||||||
@ -184,7 +207,17 @@ function page.grid:getDisplayValues(row)
|
|||||||
end
|
end
|
||||||
|
|
||||||
Event.onInterval(1, function()
|
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()
|
page.grid:update()
|
||||||
|
end
|
||||||
page.grid:draw()
|
page.grid:draw()
|
||||||
page:sync()
|
page:sync()
|
||||||
end)
|
end)
|
||||||
|
@ -5,6 +5,7 @@ local Config = require('config')
|
|||||||
local Event = require('event')
|
local Event = require('event')
|
||||||
local FileUI = require('ui.fileui')
|
local FileUI = require('ui.fileui')
|
||||||
local NFT = require('nft')
|
local NFT = require('nft')
|
||||||
|
local Packages = require('packages')
|
||||||
local SHA1 = require('sha1')
|
local SHA1 = require('sha1')
|
||||||
local Tween = require('ui.tween')
|
local Tween = require('ui.tween')
|
||||||
local UI = require('ui')
|
local UI = require('ui')
|
||||||
@ -32,27 +33,30 @@ local config = {
|
|||||||
Config.load('Overview', config)
|
Config.load('Overview', config)
|
||||||
|
|
||||||
local applications = { }
|
local applications = { }
|
||||||
|
local extSupport = Util.getVersion() >= 1.76
|
||||||
|
|
||||||
local function loadApplications()
|
local function loadApplications()
|
||||||
|
|
||||||
local requirements = {
|
local requirements = {
|
||||||
turtle = function() return turtle end,
|
turtle = not not turtle,
|
||||||
advancedTurtle = function() return turtle and term.isColor() end,
|
advancedTurtle = turtle and term.isColor(),
|
||||||
advanced = function() return term.isColor() end,
|
advanced = term.isColor(),
|
||||||
pocket = function() return pocket end,
|
pocket = not not pocket,
|
||||||
advancedPocket = function() return pocket and term.isColor() end,
|
advancedPocket = pocket and term.isColor(),
|
||||||
advancedComputer = function() return not turtle and not pocket and term.isColor() end,
|
advancedComputer = not turtle and not pocket and term.isColor(),
|
||||||
}
|
}
|
||||||
|
|
||||||
applications = Util.readTable('sys/etc/app.db')
|
applications = Util.readTable('sys/etc/app.db')
|
||||||
|
|
||||||
if fs.exists('usr/etc/apps') then
|
for dir in pairs(Packages:installed()) do
|
||||||
local dbs = fs.list('usr/etc/apps')
|
local path = fs.combine('packages/' .. dir, 'etc/apps')
|
||||||
|
if fs.exists(path) then
|
||||||
|
local dbs = fs.list(path)
|
||||||
for _, db in pairs(dbs) do
|
for _, db in pairs(dbs) do
|
||||||
local apps = Util.readTable('usr/etc/apps/' .. db) or { }
|
local apps = Util.readTable(fs.combine(path, db)) or { }
|
||||||
Util.merge(applications, apps)
|
Util.merge(applications, apps)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
end
|
||||||
|
|
||||||
if fs.exists(REGISTRY_DIR) then
|
if fs.exists(REGISTRY_DIR) then
|
||||||
local files = fs.list(REGISTRY_DIR)
|
local files = fs.list(REGISTRY_DIR)
|
||||||
@ -72,13 +76,10 @@ local function loadApplications()
|
|||||||
end
|
end
|
||||||
|
|
||||||
if a.requires then
|
if a.requires then
|
||||||
local fn = requirements[a.requires]
|
return requirements[a.requires]
|
||||||
if fn and not fn() then
|
|
||||||
return false
|
|
||||||
end
|
|
||||||
end
|
end
|
||||||
|
|
||||||
return true -- Util.startsWidth(a.run, 'http') or shell.resolveProgram(a.run)
|
return true -- Util.startsWith(a.run, 'http') or shell.resolveProgram(a.run)
|
||||||
end)
|
end)
|
||||||
end
|
end
|
||||||
|
|
||||||
@ -117,7 +118,7 @@ local function parseIcon(iconText)
|
|||||||
icon = NFT.parse(iconText)
|
icon = NFT.parse(iconText)
|
||||||
if icon then
|
if icon then
|
||||||
if icon.height > 3 or icon.width > 8 then
|
if icon.height > 3 or icon.width > 8 then
|
||||||
error('Invalid size')
|
error('Must be an NFT image - 3 rows, 8 cols max')
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
return icon
|
return icon
|
||||||
@ -174,6 +175,10 @@ local page = UI.Page {
|
|||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if extSupport then
|
||||||
|
page.container.backgroundColor = colors.black
|
||||||
|
end
|
||||||
|
|
||||||
UI.Icon = class(UI.Window)
|
UI.Icon = class(UI.Window)
|
||||||
UI.Icon.defaults = {
|
UI.Icon.defaults = {
|
||||||
UIElement = 'Icon',
|
UIElement = 'Icon',
|
||||||
@ -194,7 +199,6 @@ function UI.Icon:eventHandler(event)
|
|||||||
end
|
end
|
||||||
|
|
||||||
function page.container:setCategory(categoryName, animate)
|
function page.container:setCategory(categoryName, animate)
|
||||||
|
|
||||||
-- reset the viewport window
|
-- reset the viewport window
|
||||||
self.children = { }
|
self.children = { }
|
||||||
self.offy = 0
|
self.offy = 0
|
||||||
@ -231,7 +235,10 @@ function page.container:setCategory(categoryName, animate)
|
|||||||
for _,program in ipairs(filtered) do
|
for _,program in ipairs(filtered) do
|
||||||
|
|
||||||
local icon
|
local icon
|
||||||
if program.icon then
|
if extSupport and program.iconExt then
|
||||||
|
icon = parseIcon(program.iconExt)
|
||||||
|
end
|
||||||
|
if not icon and program.icon then
|
||||||
icon = parseIcon(program.icon)
|
icon = parseIcon(program.icon)
|
||||||
end
|
end
|
||||||
if not icon then
|
if not icon then
|
||||||
@ -344,7 +351,6 @@ function page:resize()
|
|||||||
end
|
end
|
||||||
|
|
||||||
function page:eventHandler(event)
|
function page:eventHandler(event)
|
||||||
|
|
||||||
if event.type == 'tab_select' then
|
if event.type == 'tab_select' then
|
||||||
self.container:setCategory(event.button.text, true)
|
self.container:setCategory(event.button.text, true)
|
||||||
self.container:draw()
|
self.container:draw()
|
||||||
@ -455,7 +461,10 @@ function editor:enable(app)
|
|||||||
self.form:setValues(app)
|
self.form:setValues(app)
|
||||||
|
|
||||||
local icon
|
local icon
|
||||||
if app.icon then
|
if extSupport and app.iconExt then
|
||||||
|
icon = parseIcon(app.iconExt)
|
||||||
|
end
|
||||||
|
if not icon and app.icon then
|
||||||
icon = parseIcon(app.icon)
|
icon = parseIcon(app.icon)
|
||||||
end
|
end
|
||||||
self.form.image:setImage(icon)
|
self.form.image:setImage(icon)
|
||||||
@ -479,7 +488,6 @@ function editor:updateApplications(app)
|
|||||||
end
|
end
|
||||||
|
|
||||||
function editor:eventHandler(event)
|
function editor:eventHandler(event)
|
||||||
|
|
||||||
if event.type == 'form_cancel' or event.type == 'cancel' then
|
if event.type == 'form_cancel' or event.type == 'cancel' then
|
||||||
UI:setPreviousPage()
|
UI:setPreviousPage()
|
||||||
|
|
||||||
@ -501,13 +509,17 @@ function editor:eventHandler(event)
|
|||||||
local s, m = pcall(function()
|
local s, m = pcall(function()
|
||||||
local iconLines = Util.readFile(fileName)
|
local iconLines = Util.readFile(fileName)
|
||||||
if not iconLines then
|
if not iconLines then
|
||||||
error('Unable to load file')
|
error('Must be an NFT image - 3 rows, 8 cols max')
|
||||||
end
|
end
|
||||||
local icon, m = parseIcon(iconLines)
|
local icon, m = parseIcon(iconLines)
|
||||||
if not icon then
|
if not icon then
|
||||||
error(m)
|
error(m)
|
||||||
end
|
end
|
||||||
|
if extSupport then
|
||||||
|
self.form.values.iconExt = iconLines
|
||||||
|
else
|
||||||
self.form.values.icon = iconLines
|
self.form.values.icon = iconLines
|
||||||
|
end
|
||||||
self.form.image:setImage(icon)
|
self.form.image:setImage(icon)
|
||||||
self.form.image:draw()
|
self.form.image:draw()
|
||||||
end)
|
end)
|
||||||
|
158
sys/apps/PackageManager.lua
Normal file
158
sys/apps/PackageManager.lua
Normal file
@ -0,0 +1,158 @@
|
|||||||
|
_G.requireInjector(_ENV)
|
||||||
|
|
||||||
|
local Ansi = require('ansi')
|
||||||
|
local Packages = require('packages')
|
||||||
|
local UI = require('ui')
|
||||||
|
|
||||||
|
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,
|
||||||
|
values = { },
|
||||||
|
columns = {
|
||||||
|
{ heading = 'Package', key = 'name' },
|
||||||
|
},
|
||||||
|
sortColumn = 'name',
|
||||||
|
autospace = true,
|
||||||
|
help = 'Select a package',
|
||||||
|
},
|
||||||
|
add = UI.Button {
|
||||||
|
x = -4, y = 4,
|
||||||
|
text = '+',
|
||||||
|
event = 'action',
|
||||||
|
help = 'Install or update',
|
||||||
|
},
|
||||||
|
remove = UI.Button {
|
||||||
|
x = -4, y = 6,
|
||||||
|
text = '-',
|
||||||
|
event = 'action',
|
||||||
|
operation = 'uninstall',
|
||||||
|
operationText = 'Remove',
|
||||||
|
help = 'Remove',
|
||||||
|
},
|
||||||
|
description = UI.TextArea {
|
||||||
|
x = 2, y = 9, ey = -2,
|
||||||
|
--backgroundColor = colors.white,
|
||||||
|
},
|
||||||
|
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',
|
||||||
|
},
|
||||||
|
output = UI.Embedded {
|
||||||
|
y = 6, ey = -2, x = 2, ex = -2,
|
||||||
|
},
|
||||||
|
statusBar = UI.StatusBar {
|
||||||
|
backgroundColor = colors.cyan,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
function page.grid:getRowTextColor(row, selected)
|
||||||
|
if row.installed then
|
||||||
|
return colors.yellow
|
||||||
|
end
|
||||||
|
return UI.Grid.getRowTextColor(self, row, selected)
|
||||||
|
end
|
||||||
|
|
||||||
|
function page.action:show()
|
||||||
|
UI.SlideOut.show(self)
|
||||||
|
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)
|
||||||
|
term.redirect(oterm)
|
||||||
|
self.action.output:draw()
|
||||||
|
end
|
||||||
|
|
||||||
|
function page:updateSelection(selected)
|
||||||
|
self.add.operation = selected.installed and 'update' or 'install'
|
||||||
|
self.add.operationText = selected.installed and 'Update' or 'Install'
|
||||||
|
end
|
||||||
|
|
||||||
|
function page:eventHandler(event)
|
||||||
|
if event.type == 'focus_change' then
|
||||||
|
self.statusBar:setStatus(event.focused.help)
|
||||||
|
|
||||||
|
elseif event.type == 'grid_focus_row' then
|
||||||
|
local manifest = event.selected.manifest
|
||||||
|
|
||||||
|
self.description.value = string.format('%s%s\n\n%s%s',
|
||||||
|
Ansi.yellow, manifest.title,
|
||||||
|
Ansi.white, manifest.description)
|
||||||
|
self.description:draw()
|
||||||
|
self:updateSelection(event.selected)
|
||||||
|
|
||||||
|
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.event = 'begin'
|
||||||
|
self.action:show()
|
||||||
|
end
|
||||||
|
|
||||||
|
elseif event.type == 'hide-action' then
|
||||||
|
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)
|
||||||
|
|
||||||
|
self:updateSelection(selected)
|
||||||
|
self.action.button.text = 'Done'
|
||||||
|
self.action.button.event = 'hide-action'
|
||||||
|
self.action.button:draw()
|
||||||
|
|
||||||
|
elseif event.type == 'quit' then
|
||||||
|
UI:exitPullEvents()
|
||||||
|
end
|
||||||
|
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)
|
||||||
|
UI:pullEvents()
|
@ -141,8 +141,9 @@ local systemPage = UI.Page {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if turtle then
|
if turtle then
|
||||||
|
pcall(function()
|
||||||
local Home = require('turtle.home')
|
local Home = require('turtle.home')
|
||||||
|
-- TODO: dont rely on turtle.home
|
||||||
local values = { }
|
local values = { }
|
||||||
Config.load('gps', values.home and { values.home } or { })
|
Config.load('gps', values.home and { values.home } or { })
|
||||||
|
|
||||||
@ -196,6 +197,7 @@ if turtle then
|
|||||||
return true
|
return true
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
end)
|
||||||
end
|
end
|
||||||
|
|
||||||
if settings then
|
if settings then
|
||||||
|
97
sys/apps/package.lua
Normal file
97
sys/apps/package.lua
Normal file
@ -0,0 +1,97 @@
|
|||||||
|
_G.requireInjector(_ENV)
|
||||||
|
|
||||||
|
local Git = require('git')
|
||||||
|
local Packages = require('packages')
|
||||||
|
local Util = require('util')
|
||||||
|
|
||||||
|
local fs = _G.fs
|
||||||
|
local term = _G.term
|
||||||
|
|
||||||
|
local args = { ... }
|
||||||
|
local action = table.remove(args, 1)
|
||||||
|
|
||||||
|
local function Syntax(msg)
|
||||||
|
_G.printError(msg)
|
||||||
|
print('\nSyntax: Package list | install [name] ... | update [name] | uninstall [name]')
|
||||||
|
error(0)
|
||||||
|
end
|
||||||
|
|
||||||
|
local function progress(max)
|
||||||
|
-- modified from: https://pastebin.com/W5ZkVYSi (apemanzilla)
|
||||||
|
local _, y = term.getCursorPos()
|
||||||
|
local wide, _ = term.getSize()
|
||||||
|
term.setCursorPos(1, y)
|
||||||
|
term.write("[")
|
||||||
|
term.setCursorPos(wide - 6, y)
|
||||||
|
term.write("]")
|
||||||
|
local done = 0
|
||||||
|
return function()
|
||||||
|
done = done + 1
|
||||||
|
local value = done / max
|
||||||
|
term.setCursorPos(2,y)
|
||||||
|
term.write(("="):rep(math.floor(value * (wide - 8))))
|
||||||
|
local percent = math.floor(value * 100) .. "%"
|
||||||
|
term.setCursorPos(wide - percent:len(),y)
|
||||||
|
term.write(percent)
|
||||||
|
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()
|
||||||
|
end
|
||||||
|
end
|
||||||
|
return
|
||||||
|
end
|
||||||
|
|
||||||
|
if action == 'list' then
|
||||||
|
for k in pairs(Packages:list()) do
|
||||||
|
Util.print('[%s] %s', Packages:isInstalled(k) and 'x' or ' ', k)
|
||||||
|
end
|
||||||
|
return
|
||||||
|
end
|
||||||
|
|
||||||
|
if action == 'install' then
|
||||||
|
local name = args[1] or Syntax('Invalid package')
|
||||||
|
if Packages:isInstalled(name) then
|
||||||
|
error('Package is already installed')
|
||||||
|
end
|
||||||
|
install(name)
|
||||||
|
print('installation complete')
|
||||||
|
return
|
||||||
|
end
|
||||||
|
|
||||||
|
if action == 'update' then
|
||||||
|
local name = args[1] or Syntax('Invalid package')
|
||||||
|
if not Packages:isInstalled(name) then
|
||||||
|
error('Package is not installed')
|
||||||
|
end
|
||||||
|
install(name)
|
||||||
|
print('update complete')
|
||||||
|
return
|
||||||
|
end
|
||||||
|
|
||||||
|
if action == 'uninstall' then
|
||||||
|
local name = args[1] or Syntax('Invalid package')
|
||||||
|
if not Packages:isInstalled(name) then
|
||||||
|
error('Package is not installed')
|
||||||
|
end
|
||||||
|
local packageDir = fs.combine('packages', name)
|
||||||
|
fs.delete(packageDir)
|
||||||
|
print('removed: ' .. packageDir)
|
||||||
|
return
|
||||||
|
end
|
||||||
|
|
||||||
|
Syntax('Invalid command')
|
@ -365,7 +365,7 @@ local term = _G.term
|
|||||||
local textutils = _G.textutils
|
local textutils = _G.textutils
|
||||||
|
|
||||||
local terminal = term.current()
|
local terminal = term.current()
|
||||||
Terminal.scrollable(terminal, 100)
|
--Terminal.scrollable(terminal, 100)
|
||||||
terminal.noAutoScroll = true
|
terminal.noAutoScroll = true
|
||||||
|
|
||||||
local config = {
|
local config = {
|
||||||
@ -568,10 +568,10 @@ local function shellRead(history)
|
|||||||
local ie = Input:translate(event, p1, p2, p3)
|
local ie = Input:translate(event, p1, p2, p3)
|
||||||
if ie then
|
if ie then
|
||||||
if ie.code == 'scroll_up' then
|
if ie.code == 'scroll_up' then
|
||||||
terminal.scrollUp()
|
--terminal.scrollUp()
|
||||||
|
|
||||||
elseif ie.code == 'scroll_down' then
|
elseif ie.code == 'scroll_down' then
|
||||||
terminal.scrollDown()
|
--terminal.scrollDown()
|
||||||
|
|
||||||
elseif ie.code == 'terminate' then
|
elseif ie.code == 'terminate' then
|
||||||
bExit = true
|
bExit = true
|
||||||
|
@ -10,7 +10,7 @@ local os = _G.os
|
|||||||
local read = _G.read
|
local read = _G.read
|
||||||
local term = _G.term
|
local term = _G.term
|
||||||
|
|
||||||
local options, args = Util.args({ ... })
|
local args = { ... }
|
||||||
|
|
||||||
local remoteId = tonumber(table.remove(args, 1) or '')
|
local remoteId = tonumber(table.remove(args, 1) or '')
|
||||||
if not remoteId then
|
if not remoteId then
|
||||||
@ -19,11 +19,11 @@ if not remoteId then
|
|||||||
end
|
end
|
||||||
|
|
||||||
if not remoteId then
|
if not remoteId then
|
||||||
error('Syntax: telnet [-title TITLE] ID [PROGRAM]')
|
error('Syntax: telnet ID [PROGRAM] [ARGS]')
|
||||||
end
|
end
|
||||||
|
|
||||||
if options.title and multishell then
|
if multishell then
|
||||||
multishell.setTitle(multishell.getCurrent(), options.title)
|
multishell.setTitle(multishell.getCurrent(), 'Telnet ' .. remoteId)
|
||||||
end
|
end
|
||||||
|
|
||||||
local socket, msg = Socket.connect(remoteId, 23)
|
local socket, msg = Socket.connect(remoteId, 23)
|
||||||
|
@ -7,6 +7,7 @@ local Util = require('util')
|
|||||||
|
|
||||||
local colors = _G.colors
|
local colors = _G.colors
|
||||||
local multishell = _ENV.multishell
|
local multishell = _ENV.multishell
|
||||||
|
local os = _G.os
|
||||||
local term = _G.term
|
local term = _G.term
|
||||||
|
|
||||||
local remoteId
|
local remoteId
|
||||||
@ -26,11 +27,11 @@ if multishell then
|
|||||||
multishell.setTitle(multishell.getCurrent(), 'VNC-' .. remoteId)
|
multishell.setTitle(multishell.getCurrent(), 'VNC-' .. remoteId)
|
||||||
end
|
end
|
||||||
|
|
||||||
print('connecting...')
|
local function connect()
|
||||||
local socket, msg = Socket.connect(remoteId, 5900)
|
local socket, msg = Socket.connect(remoteId, 5900)
|
||||||
|
|
||||||
if not socket then
|
if not socket then
|
||||||
error(msg)
|
return false, msg
|
||||||
end
|
end
|
||||||
|
|
||||||
local function writeTermInfo()
|
local function writeTermInfo()
|
||||||
@ -51,6 +52,9 @@ if not ct.isColor() then
|
|||||||
Terminal.toGrayscale(ct)
|
Terminal.toGrayscale(ct)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
ct.clear()
|
||||||
|
ct.setCursorPos(1, 1)
|
||||||
|
|
||||||
Event.addRoutine(function()
|
Event.addRoutine(function()
|
||||||
while true do
|
while true do
|
||||||
local data = socket:read()
|
local data = socket:read()
|
||||||
@ -63,9 +67,6 @@ Event.addRoutine(function()
|
|||||||
end
|
end
|
||||||
end)
|
end)
|
||||||
|
|
||||||
ct.clear()
|
|
||||||
ct.setCursorPos(1, 1)
|
|
||||||
|
|
||||||
local filter = Util.transpose({
|
local filter = Util.transpose({
|
||||||
'char', 'paste', 'key', 'key_up',
|
'char', 'paste', 'key', 'key_up',
|
||||||
'mouse_scroll', 'mouse_click', 'mouse_drag', 'mouse_up',
|
'mouse_scroll', 'mouse_click', 'mouse_drag', 'mouse_up',
|
||||||
@ -76,10 +77,6 @@ while true do
|
|||||||
local event = e[1]
|
local event = e[1]
|
||||||
|
|
||||||
if not socket.connected then
|
if not socket.connected then
|
||||||
print()
|
|
||||||
print('Connection lost')
|
|
||||||
print('Press enter to exit')
|
|
||||||
_G.read()
|
|
||||||
break
|
break
|
||||||
end
|
end
|
||||||
|
|
||||||
@ -95,6 +92,39 @@ while true do
|
|||||||
ct.setBackgroundColor(colors.black)
|
ct.setBackgroundColor(colors.black)
|
||||||
ct.clear()
|
ct.clear()
|
||||||
ct.setCursorPos(1, 1)
|
ct.setCursorPos(1, 1)
|
||||||
|
return true
|
||||||
|
end
|
||||||
|
end
|
||||||
|
return false, "Connection Lost"
|
||||||
|
end
|
||||||
|
|
||||||
|
while true do
|
||||||
|
term.clear()
|
||||||
|
term.setCursorPos(1, 1)
|
||||||
|
|
||||||
|
print('connecting...')
|
||||||
|
local s, m = connect()
|
||||||
|
if s then
|
||||||
break
|
break
|
||||||
end
|
end
|
||||||
|
|
||||||
|
term.setBackgroundColor(colors.black)
|
||||||
|
term.setTextColor(colors.white)
|
||||||
|
term.clear()
|
||||||
|
term.setCursorPos(1, 1)
|
||||||
|
print(m)
|
||||||
|
print('\nPress any key to exit')
|
||||||
|
print('\nRetrying in ... ')
|
||||||
|
local x, y = term.getCursorPos()
|
||||||
|
for i = 5, 1, -1 do
|
||||||
|
local timerId = os.startTimer(1)
|
||||||
|
term.setCursorPos(x, y)
|
||||||
|
term.write(i)
|
||||||
|
repeat
|
||||||
|
local e, id = os.pullEvent()
|
||||||
|
if e == 'char' or e == 'key' then
|
||||||
|
return
|
||||||
|
end
|
||||||
|
until e == 'timer' and id == timerId
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
@ -11,7 +11,7 @@ for k,v in pairs(_ENV) do
|
|||||||
sandboxEnv[k] = v
|
sandboxEnv[k] = v
|
||||||
end
|
end
|
||||||
|
|
||||||
_G.debug = function() end
|
_G._debug = function() end
|
||||||
|
|
||||||
local function makeEnv()
|
local function makeEnv()
|
||||||
local env = setmetatable({ }, { __index = _G })
|
local env = setmetatable({ }, { __index = _G })
|
||||||
|
107
sys/etc/app.db
107
sys/etc/app.db
@ -1,10 +1,21 @@
|
|||||||
{
|
{
|
||||||
|
[ "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" ] = {
|
[ "53ebc572b4a44802ba114729f07bdaaf5409a9d7" ] = {
|
||||||
|
title = "Network",
|
||||||
category = "Apps",
|
category = "Apps",
|
||||||
icon = "\0304 \030 \
|
icon = "\0304 \030 \
|
||||||
\030f \0304 \0307 \030 \031 \031f)\
|
\030f \0304 \0307 \030 \031 \031f)\
|
||||||
\030f \0304 \0307 \030 \031f)",
|
\030f \0304 \0307 \030 \031f)",
|
||||||
title = "Network",
|
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",
|
run = "Network.lua",
|
||||||
},
|
},
|
||||||
c7116629a6a855cb774d9c7c8ad822fd83c71fb5 = {
|
c7116629a6a855cb774d9c7c8ad822fd83c71fb5 = {
|
||||||
@ -13,6 +24,9 @@
|
|||||||
icon = "\0304\031f \030f\0310o..\0304\031f \
|
icon = "\0304\031f \030f\0310o..\0304\031f \
|
||||||
\0304\031f \030f\0310.o.\0304\031f \
|
\0304\031f \030f\0310.o.\0304\031f \
|
||||||
\0304\031f - ",
|
\0304\031f - ",
|
||||||
|
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",
|
run = "rom/programs/reboot",
|
||||||
},
|
},
|
||||||
fb91e24fa52d8d2b32937bf04d843f730319a902 = {
|
fb91e24fa52d8d2b32937bf04d843f730319a902 = {
|
||||||
@ -21,6 +35,9 @@
|
|||||||
icon = "\0301\03171\03180\030 \031 \
|
icon = "\0301\03171\03180\030 \031 \
|
||||||
\0301\03181\030 \031 \
|
\0301\03181\030 \031 \
|
||||||
\0301\03170\03180\03171\0307\031f>",
|
\0301\03170\03180\03171\0307\031f>",
|
||||||
|
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",
|
run = "http://pastebin.com/raw/UzGHLbNC",
|
||||||
},
|
},
|
||||||
c47ae15370cfe1ed2781eedc1dc2547d12d9e972 = {
|
c47ae15370cfe1ed2781eedc1dc2547d12d9e972 = {
|
||||||
@ -29,6 +46,9 @@
|
|||||||
icon = " \031f?\031 \
|
icon = " \031f?\031 \
|
||||||
\031f?\031 \
|
\031f?\031 \
|
||||||
\031f?",
|
\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",
|
run = "Help.lua",
|
||||||
},
|
},
|
||||||
b0832074630eb731d7fbe8074de48a90cd9bb220 = {
|
b0832074630eb731d7fbe8074de48a90cd9bb220 = {
|
||||||
@ -37,46 +57,77 @@
|
|||||||
icon = "\030f \
|
icon = "\030f \
|
||||||
\030f\0310lua>\031 \
|
\030f\0310lua>\031 \
|
||||||
\030f ",
|
\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",
|
run = "sys/apps/Lua.lua",
|
||||||
},
|
},
|
||||||
df485c871329671f46570634d63216761441bcd6 = {
|
|
||||||
title = "Devices",
|
|
||||||
category = "System",
|
|
||||||
icon = "\0304 \030 \
|
|
||||||
\030f \0304 \0307 \030 \031 \031f_\
|
|
||||||
\030f \0304 \0307 \030 \031f/",
|
|
||||||
run = "Devices.lua",
|
|
||||||
},
|
|
||||||
bc0792d8dc81e8aa30b987246a5ce97c40cd6833 = {
|
bc0792d8dc81e8aa30b987246a5ce97c40cd6833 = {
|
||||||
title = "System",
|
title = "System",
|
||||||
category = "System",
|
category = "System",
|
||||||
icon = " \0307\031f| \
|
icon = " \0307\031f| \
|
||||||
\0307\031f---o\030 \031 \
|
\0307\031f---o\030 \031 \
|
||||||
\0307\031f| ",
|
\0307\031f| ",
|
||||||
|
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",
|
run = "System.lua",
|
||||||
},
|
},
|
||||||
c5497bca58468ae64aed6c0fd921109217988db3 = {
|
|
||||||
title = "Events",
|
|
||||||
category = "System",
|
|
||||||
icon = "\0304\031f \030 \0311e\
|
|
||||||
\030f\031f \0304 \030 \0311ee\031f \
|
|
||||||
\030f\031f \0304 \030 \0311e\031f ",
|
|
||||||
run = "Events.lua",
|
|
||||||
},
|
|
||||||
[ "2a4d562b1d9a9c90bdede6fac8ce4f7402462b86" ] = {
|
[ "2a4d562b1d9a9c90bdede6fac8ce4f7402462b86" ] = {
|
||||||
title = "Tasks",
|
title = "Tasks",
|
||||||
category = "System",
|
category = "System",
|
||||||
icon = "\030f\031f \0315/\
|
icon = "\030f\031f \0315/\
|
||||||
\030f\031f \0315/\\/ \
|
\030f\031f \0315/\\/ \
|
||||||
\030f\0315/\031f ",
|
\030f\0315/\031f ",
|
||||||
|
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",
|
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" ] = {
|
[ "6ce6c512ea433a7fc5c8841628e7696cd0ff7f2b" ] = {
|
||||||
title = "Files",
|
title = "Files",
|
||||||
category = "Apps",
|
category = "Apps",
|
||||||
icon = "\0300\0317==\031 \0307 \
|
icon = "\0300\0317==\031 \0307 \
|
||||||
\0300\0317====\
|
\0300\0317====\
|
||||||
\0300\0317====",
|
\0300\0317====",
|
||||||
|
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",
|
run = "Files.lua",
|
||||||
},
|
},
|
||||||
[ "7fddb7d8d1d60b1eeefa9af01082e0811d4b484d" ] = {
|
[ "7fddb7d8d1d60b1eeefa9af01082e0811d4b484d" ] = {
|
||||||
@ -85,12 +136,18 @@
|
|||||||
icon = "\0304\031f \
|
icon = "\0304\031f \
|
||||||
\0304\031f \030f\0310zz\031 \
|
\0304\031f \030f\0310zz\031 \
|
||||||
\0304\031f \030f ",
|
\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",
|
run = "/rom/programs/shutdown",
|
||||||
},
|
},
|
||||||
bdc1fd5d3c0f3dcfd55d010426e61bf9451e680d = {
|
bdc1fd5d3c0f3dcfd55d010426e61bf9451e680d = {
|
||||||
title = "Shell",
|
title = "Shell",
|
||||||
category = "Apps",
|
category = "Apps",
|
||||||
icon = "\030f\0314\151\131\131\131\131\
|
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\0314> \0310_ \
|
||||||
\030f\0314\149\030f ",
|
\030f\0314\149\030f ",
|
||||||
run = "shell",
|
run = "shell",
|
||||||
@ -127,6 +184,9 @@
|
|||||||
icon = "\030d \030 \030e \030 \
|
icon = "\030d \030 \030e \030 \
|
||||||
\030d \030 \
|
\030d \030 \
|
||||||
\030d ",
|
\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",
|
run = "/rom/programs/fun/worm",
|
||||||
},
|
},
|
||||||
[ "9f46ca3ef617166776ef6014a58d4e66859caa62" ] = {
|
[ "9f46ca3ef617166776ef6014a58d4e66859caa62" ] = {
|
||||||
@ -135,6 +195,17 @@
|
|||||||
icon = " \030f \
|
icon = " \030f \
|
||||||
\030f \0307 \
|
\030f \0307 \
|
||||||
\030f \0307 \0300 ",
|
\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",
|
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",
|
||||||
|
},
|
||||||
}
|
}
|
||||||
|
@ -8,6 +8,15 @@
|
|||||||
Button = {
|
Button = {
|
||||||
--focusIndicator = '\183',
|
--focusIndicator = '\183',
|
||||||
},
|
},
|
||||||
|
Checkbox = {
|
||||||
|
checkedIndicator = '\4',
|
||||||
|
leftMarker = '\124',
|
||||||
|
rightMarker = '\124',
|
||||||
|
},
|
||||||
|
Chooser = {
|
||||||
|
leftIndicator = '\17',
|
||||||
|
rightIndicator = '\16',
|
||||||
|
},
|
||||||
Grid = {
|
Grid = {
|
||||||
focusIndicator = '\183',
|
focusIndicator = '\183',
|
||||||
inverseSortIndicator = '\24',
|
inverseSortIndicator = '\24',
|
||||||
|
@ -33,12 +33,25 @@ local keyboard = _G.device.keyboard
|
|||||||
local mouse = _G.device.mouse
|
local mouse = _G.device.mouse
|
||||||
local os = _G.os
|
local os = _G.os
|
||||||
|
|
||||||
|
local drivers = { }
|
||||||
|
|
||||||
kernel.hook('peripheral', function(_, eventData)
|
kernel.hook('peripheral', function(_, eventData)
|
||||||
local side = eventData[1]
|
local side = eventData[1]
|
||||||
if side then
|
if side then
|
||||||
local dev = Peripheral.addDevice(device, side)
|
local dev = Peripheral.addDevice(device, side)
|
||||||
if dev then
|
if dev then
|
||||||
os.queueEvent('device_attach', dev.name)
|
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
|
end
|
||||||
end)
|
end)
|
||||||
@ -48,7 +61,12 @@ kernel.hook('peripheral_detach', function(_, eventData)
|
|||||||
if side then
|
if side then
|
||||||
local dev = Util.find(device, 'side', side)
|
local dev = Util.find(device, 'side', side)
|
||||||
if dev then
|
if dev then
|
||||||
os.queueEvent('device_detach', dev.name)
|
os.queueEvent('device_detach', dev.name, dev)
|
||||||
|
if dev._children then
|
||||||
|
for _,v in pairs(dev._children) do
|
||||||
|
os.queueEvent('peripheral_detach', v.name)
|
||||||
|
end
|
||||||
|
end
|
||||||
device[dev.name] = nil
|
device[dev.name] = nil
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
@ -109,3 +127,60 @@ kernel.hook('monitor_touch', function(event, eventData)
|
|||||||
return true -- stop propagation
|
return true -- stop propagation
|
||||||
end
|
end
|
||||||
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
|
||||||
|
@ -11,10 +11,10 @@ end
|
|||||||
if not fs.exists('usr/autorun') then
|
if not fs.exists('usr/autorun') then
|
||||||
fs.makeDir('usr/autorun')
|
fs.makeDir('usr/autorun')
|
||||||
end
|
end
|
||||||
if not fs.exists('usr/config/fstab') then
|
--if not fs.exists('usr/config/fstab') then
|
||||||
Util.writeFile('usr/config/fstab',
|
-- Util.writeFile('usr/config/fstab',
|
||||||
'usr gitfs kepler155c/opus-apps/' .. _G.OPUS_BRANCH)
|
-- 'usr gitfs kepler155c/opus-apps/' .. _G.OPUS_BRANCH)
|
||||||
end
|
--end
|
||||||
|
|
||||||
if not fs.exists('usr/config/shell') then
|
if not fs.exists('usr/config/shell') then
|
||||||
Util.writeTable('usr/config/shell', {
|
Util.writeTable('usr/config/shell', {
|
||||||
@ -24,6 +24,17 @@ if not fs.exists('usr/config/shell') then
|
|||||||
})
|
})
|
||||||
end
|
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')
|
local config = Util.readTable('usr/config/shell')
|
||||||
if config.aliases then
|
if config.aliases then
|
||||||
for k in pairs(shell.aliases()) do
|
for k in pairs(shell.aliases()) do
|
||||||
|
@ -1,3 +1,8 @@
|
|||||||
|
_G.requireInjector(_ENV)
|
||||||
|
|
||||||
|
local Config = require('config')
|
||||||
|
|
||||||
|
local device = _G.device
|
||||||
local kernel = _G.kernel
|
local kernel = _G.kernel
|
||||||
local os = _G.os
|
local os = _G.os
|
||||||
|
|
||||||
@ -11,15 +16,44 @@ local function startNetwork()
|
|||||||
})
|
})
|
||||||
end
|
end
|
||||||
|
|
||||||
|
local function setModem(dev)
|
||||||
|
if not device.wireless_modem and dev.isWireless() then
|
||||||
|
local config = Config.load('os', { })
|
||||||
|
if not config.wirelessModem or dev.name == config.wirelessModem then
|
||||||
|
device.wireless_modem = dev
|
||||||
|
os.queueEvent('device_attach', 'wireless_modem')
|
||||||
|
return dev
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
-- create a psuedo-device named 'wireleess_modem'
|
||||||
kernel.hook('device_attach', function(_, eventData)
|
kernel.hook('device_attach', function(_, eventData)
|
||||||
if eventData[1] == 'wireless_modem' then
|
local dev = device[eventData[1]]
|
||||||
|
if dev and dev.type == 'modem' then
|
||||||
|
if setModem(dev) then
|
||||||
startNetwork()
|
startNetwork()
|
||||||
end
|
end
|
||||||
|
end
|
||||||
end)
|
end)
|
||||||
|
|
||||||
if _G.device.wireless_modem then
|
kernel.hook('device_detach', function(_, eventData)
|
||||||
|
if device.wireless_modem and eventData[1] == device.wireless_modem.name then
|
||||||
|
device['wireless_modem'] = nil
|
||||||
|
os.queueEvent('device_detach', 'wireless_modem')
|
||||||
|
end
|
||||||
|
end)
|
||||||
|
|
||||||
|
for _,dev in pairs(device) do
|
||||||
|
if dev.type == 'modem' then
|
||||||
|
if setModem(dev) then
|
||||||
|
break
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
if device.wireless_modem then
|
||||||
print('waiting for network...')
|
print('waiting for network...')
|
||||||
startNetwork()
|
startNetwork()
|
||||||
os.pullEvent('network_up')
|
os.pullEvent('network_up')
|
||||||
end
|
end
|
||||||
|
|
||||||
|
45
sys/extensions/6.packages.lua
Normal file
45
sys/extensions/6.packages.lua
Normal file
@ -0,0 +1,45 @@
|
|||||||
|
_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, ':')
|
@ -7,7 +7,7 @@ _G.requireInjector(_ENV)
|
|||||||
local Pathing = require('turtle.pathfind')
|
local Pathing = require('turtle.pathfind')
|
||||||
local GPS = require('gps')
|
local GPS = require('gps')
|
||||||
local Point = require('point')
|
local Point = require('point')
|
||||||
local synchronized = require('sync')
|
local synchronized = require('sync').sync
|
||||||
local Util = require('util')
|
local Util = require('util')
|
||||||
|
|
||||||
local os = _G.os
|
local os = _G.os
|
||||||
@ -458,6 +458,7 @@ function turtle.back()
|
|||||||
end
|
end
|
||||||
|
|
||||||
local function moveTowardsX(dx)
|
local function moveTowardsX(dx)
|
||||||
|
if not tonumber(dx) then error('moveTowardsX: Invalid arguments') end
|
||||||
local direction = dx - turtle.point.x
|
local direction = dx - turtle.point.x
|
||||||
local move
|
local move
|
||||||
|
|
||||||
@ -662,11 +663,11 @@ function turtle._goto(pt)
|
|||||||
local dx, dy, dz, dh = pt.x, pt.y, pt.z, pt.heading
|
local dx, dy, dz, dh = pt.x, pt.y, pt.z, pt.heading
|
||||||
if not turtle.gotoSingleTurn(dx, dy, dz, dh) then
|
if not turtle.gotoSingleTurn(dx, dy, dz, dh) then
|
||||||
if not gotoMultiTurn(dx, dy, dz) then
|
if not gotoMultiTurn(dx, dy, dz) then
|
||||||
return false
|
return false, 'Failed to reach location'
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
turtle.setHeading(dh)
|
turtle.setHeading(dh)
|
||||||
return true
|
return pt
|
||||||
end
|
end
|
||||||
|
|
||||||
-- avoid lint errors
|
-- avoid lint errors
|
||||||
@ -738,6 +739,8 @@ function turtle.getSlot(indexOrId, slots)
|
|||||||
}
|
}
|
||||||
end
|
end
|
||||||
|
|
||||||
|
-- inconsistent return value
|
||||||
|
-- null is returned if indexOrId is a string and no item is present
|
||||||
return {
|
return {
|
||||||
qty = 0, -- deprecate
|
qty = 0, -- deprecate
|
||||||
count = 0,
|
count = 0,
|
||||||
@ -794,9 +797,13 @@ function turtle.getSummedInventory()
|
|||||||
end
|
end
|
||||||
|
|
||||||
function turtle.has(item, count)
|
function turtle.has(item, count)
|
||||||
|
if item:match('.*:%d') then
|
||||||
local slot = turtle.getSummedInventory()[item]
|
local slot = turtle.getSummedInventory()[item]
|
||||||
return slot and slot.count >= (count or 1)
|
return slot and slot.count >= (count or 1)
|
||||||
end
|
end
|
||||||
|
local slot = turtle.getSlot(item)
|
||||||
|
return slot and slot.count > 0
|
||||||
|
end
|
||||||
|
|
||||||
function turtle.getFilledSlots(startSlot)
|
function turtle.getFilledSlots(startSlot)
|
||||||
startSlot = startSlot or 1
|
startSlot = startSlot or 1
|
||||||
@ -966,6 +973,18 @@ function turtle.addWorldBlock(pt)
|
|||||||
Pathing.addBlock(pt)
|
Pathing.addBlock(pt)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
local movementStrategy = turtle.pathfind
|
||||||
|
|
||||||
|
function turtle.setMovementStrategy(strategy)
|
||||||
|
if strategy == 'pathing' then
|
||||||
|
movementStrategy = turtle.pathfind
|
||||||
|
elseif strategy == 'goto' then
|
||||||
|
movementStrategy = turtle._goto
|
||||||
|
else
|
||||||
|
error('Invalid movement strategy')
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
function turtle.faceAgainst(pt, options) -- 4 sided
|
function turtle.faceAgainst(pt, options) -- 4 sided
|
||||||
options = options or { }
|
options = options or { }
|
||||||
options.dest = { }
|
options.dest = { }
|
||||||
@ -980,7 +999,7 @@ function turtle.faceAgainst(pt, options) -- 4 sided
|
|||||||
})
|
})
|
||||||
end
|
end
|
||||||
|
|
||||||
return turtle.pathfind(Point.closest(turtle.point, options.dest), options)
|
return movementStrategy(Point.closest(turtle.point, options.dest), options)
|
||||||
end
|
end
|
||||||
|
|
||||||
-- move against this point
|
-- move against this point
|
||||||
@ -1012,7 +1031,7 @@ function turtle.moveAgainst(pt, options) -- 6 sided
|
|||||||
})
|
})
|
||||||
end
|
end
|
||||||
|
|
||||||
return turtle.pathfind(Point.closest(turtle.point, options.dest), options)
|
return movementStrategy(Point.closest(turtle.point, options.dest), options)
|
||||||
end
|
end
|
||||||
|
|
||||||
local actionsAt = {
|
local actionsAt = {
|
||||||
@ -1097,7 +1116,7 @@ local function _actionAt(action, pt, ...)
|
|||||||
direction = 'up'
|
direction = 'up'
|
||||||
end
|
end
|
||||||
|
|
||||||
if turtle.pathfind(apt) then
|
if movementStrategy(apt) then
|
||||||
return action[direction](...)
|
return action[direction](...)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
_G.requireInjector(_ENV)
|
_G.requireInjector(_ENV)
|
||||||
|
|
||||||
local Config = require('config')
|
local Config = require('config')
|
||||||
|
local Packages = require('packages')
|
||||||
local Util = require('util')
|
local Util = require('util')
|
||||||
|
|
||||||
local colors = _G.colors
|
local colors = _G.colors
|
||||||
@ -32,6 +33,7 @@ local config = {
|
|||||||
backgroundColor = colors.gray,
|
backgroundColor = colors.gray,
|
||||||
tabBarBackgroundColor = colors.gray,
|
tabBarBackgroundColor = colors.gray,
|
||||||
focusBackgroundColor = colors.gray,
|
focusBackgroundColor = colors.gray,
|
||||||
|
errorColor = colors.black,
|
||||||
},
|
},
|
||||||
color = {
|
color = {
|
||||||
textColor = colors.lightGray,
|
textColor = colors.lightGray,
|
||||||
@ -40,6 +42,7 @@ local config = {
|
|||||||
backgroundColor = colors.gray,
|
backgroundColor = colors.gray,
|
||||||
tabBarBackgroundColor = colors.gray,
|
tabBarBackgroundColor = colors.gray,
|
||||||
focusBackgroundColor = colors.gray,
|
focusBackgroundColor = colors.gray,
|
||||||
|
errorColor = colors.red,
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
Config.load('multishell', config)
|
Config.load('multishell', config)
|
||||||
@ -129,6 +132,7 @@ function multishell.openTab(tab)
|
|||||||
print('\nPress enter to close')
|
print('\nPress enter to close')
|
||||||
routine.isDead = true
|
routine.isDead = true
|
||||||
routine.hidden = false
|
routine.hidden = false
|
||||||
|
redrawMenu()
|
||||||
while true do
|
while true do
|
||||||
local e, code = os.pullEventRaw('key')
|
local e, code = os.pullEventRaw('key')
|
||||||
if e == 'terminate' or e == 'key' and code == keys.enter then
|
if e == 'terminate' or e == 'key' and code == keys.enter then
|
||||||
@ -252,8 +256,9 @@ kernel.hook('multishell_redraw', function()
|
|||||||
tab.ex = tabX + tab.width
|
tab.ex = tabX + tab.width
|
||||||
tabX = tabX + tab.width
|
tabX = tabX + tab.width
|
||||||
if tab ~= currentTab then
|
if tab ~= currentTab then
|
||||||
|
local textColor = tab.isDead and _colors.errorColor or _colors.textColor
|
||||||
write(tab.sx, tab.title:sub(1, tab.width - 1),
|
write(tab.sx, tab.title:sub(1, tab.width - 1),
|
||||||
_colors.backgroundColor, _colors.textColor)
|
_colors.backgroundColor, textColor)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
@ -371,6 +376,10 @@ local function startup()
|
|||||||
end
|
end
|
||||||
|
|
||||||
runDir('sys/autorun', shell.run)
|
runDir('sys/autorun', shell.run)
|
||||||
|
for name in pairs(Packages:installed()) do
|
||||||
|
local packageDir = 'packages/' .. name .. '/autorun'
|
||||||
|
runDir(packageDir, shell.run)
|
||||||
|
end
|
||||||
runDir('usr/autorun', shell.run)
|
runDir('usr/autorun', shell.run)
|
||||||
|
|
||||||
if not success then
|
if not success then
|
||||||
|
@ -28,12 +28,18 @@ local focusedRoutineEvents = Util.transpose {
|
|||||||
'paste', 'terminate',
|
'paste', 'terminate',
|
||||||
}
|
}
|
||||||
|
|
||||||
_G.debug = function(pattern, ...)
|
_G._debug = function(pattern, ...)
|
||||||
local oldTerm = term.redirect(kernel.window)
|
local oldTerm = term.redirect(kernel.window)
|
||||||
Util.print(pattern, ...)
|
Util.print(pattern, ...)
|
||||||
term.redirect(oldTerm)
|
term.redirect(oldTerm)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
if not _G.debug then -- don't clobber lua debugger
|
||||||
|
function _G.debug(...)
|
||||||
|
_G._debug(...)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
-- any function that runs in a kernel hook does not run in
|
-- any function that runs in a kernel hook does not run in
|
||||||
-- a separate coroutine or have a window. an error in a hook
|
-- a separate coroutine or have a window. an error in a hook
|
||||||
-- function will crash the system.
|
-- function will crash the system.
|
||||||
|
@ -83,11 +83,15 @@ Event.on('modem_message', function(_, _, dport, dhost, request)
|
|||||||
if dport == 80 and dhost == computerId and type(request) == 'table' then
|
if dport == 80 and dhost == computerId and type(request) == 'table' then
|
||||||
if request.method == 'GET' then
|
if request.method == 'GET' then
|
||||||
local query
|
local query
|
||||||
|
if not request.path or type(request.path) ~= 'string' then
|
||||||
|
return
|
||||||
|
end
|
||||||
local path = request.path:gsub('%?(.*)', function(v)
|
local path = request.path:gsub('%?(.*)', function(v)
|
||||||
query = parseQuery(v)
|
query = parseQuery(v)
|
||||||
return ''
|
return ''
|
||||||
end)
|
end)
|
||||||
if fs.isDir(path) then
|
if fs.isDir(path) then
|
||||||
|
-- TODO: more validation
|
||||||
modem.transmit(request.replyPort, request.replyAddress, {
|
modem.transmit(request.replyPort, request.replyAddress, {
|
||||||
statusCode = 200,
|
statusCode = 200,
|
||||||
contentType = 'table/directory',
|
contentType = 'table/directory',
|
||||||
|
@ -150,11 +150,13 @@ local function sendInfo()
|
|||||||
end
|
end
|
||||||
if device.neuralInterface then
|
if device.neuralInterface then
|
||||||
info.status = device.neuralInterface.status
|
info.status = device.neuralInterface.status
|
||||||
|
pcall(function()
|
||||||
if not info.status and device.neuralInterface.getMetaOwner then
|
if not info.status and device.neuralInterface.getMetaOwner then
|
||||||
info.status = 'health: ' ..
|
info.status = 'health: ' ..
|
||||||
math.floor(device.neuralInterface.getMetaOwner().health /
|
math.floor(device.neuralInterface.getMetaOwner().health /
|
||||||
device.neuralInterface.getMetaOwner().maxHealth * 100)
|
device.neuralInterface.getMetaOwner().maxHealth * 100)
|
||||||
end
|
end
|
||||||
|
end)
|
||||||
end
|
end
|
||||||
device.wireless_modem.transmit(999, os.getComputerID(), info)
|
device.wireless_modem.transmit(999, os.getComputerID(), info)
|
||||||
end
|
end
|
||||||
|
@ -33,7 +33,7 @@ function transport.read(socket)
|
|||||||
end
|
end
|
||||||
|
|
||||||
function transport.write(socket, data)
|
function transport.write(socket, data)
|
||||||
--debug('>> ' .. Util.tostring({ type = 'DATA', seq = socket.wseq }))
|
--_debug('>> ' .. Util.tostring({ type = 'DATA', seq = socket.wseq }))
|
||||||
socket.transmit(socket.dport, socket.dhost, data)
|
socket.transmit(socket.dport, socket.dhost, data)
|
||||||
|
|
||||||
--local timerId = os.startTimer(3)
|
--local timerId = os.startTimer(3)
|
||||||
@ -45,7 +45,7 @@ function transport.write(socket, data)
|
|||||||
end
|
end
|
||||||
|
|
||||||
function transport.ping(socket)
|
function transport.ping(socket)
|
||||||
--debug('>> ' .. Util.tostring({ type = 'DATA', seq = socket.wseq }))
|
--_debug('>> ' .. Util.tostring({ type = 'DATA', seq = socket.wseq }))
|
||||||
if os.clock() - socket.activityTimer > 10 then
|
if os.clock() - socket.activityTimer > 10 then
|
||||||
socket.activityTimer = os.clock()
|
socket.activityTimer = os.clock()
|
||||||
socket.transmit(socket.dport, socket.dhost, {
|
socket.transmit(socket.dport, socket.dhost, {
|
||||||
@ -78,9 +78,12 @@ Event.on('modem_message', function(_, _, dport, dhost, msg, distance)
|
|||||||
local socket = transport.sockets[dport]
|
local socket = transport.sockets[dport]
|
||||||
if socket and socket.connected then
|
if socket and socket.connected then
|
||||||
|
|
||||||
--if msg.type then debug('<< ' .. Util.tostring(msg)) end
|
--if msg.type then _debug('<< ' .. Util.tostring(msg)) end
|
||||||
|
if socket.co and coroutine.status(socket.co) == 'dead' then
|
||||||
|
_G._debug('socket coroutine dead')
|
||||||
|
socket:close()
|
||||||
|
|
||||||
if msg.type == 'DISC' then
|
elseif msg.type == 'DISC' then
|
||||||
-- received disconnect from other end
|
-- received disconnect from other end
|
||||||
if socket.connected then
|
if socket.connected then
|
||||||
os.queueEvent('transport_' .. socket.uid)
|
os.queueEvent('transport_' .. socket.uid)
|
||||||
@ -108,9 +111,9 @@ Event.on('modem_message', function(_, _, dport, dhost, msg, distance)
|
|||||||
socket.activityTimer = os.clock()
|
socket.activityTimer = os.clock()
|
||||||
if msg.seq ~= socket.rseq then
|
if msg.seq ~= socket.rseq then
|
||||||
print('transport seq error - closing socket ' .. socket.sport)
|
print('transport seq error - closing socket ' .. socket.sport)
|
||||||
debug(msg.data)
|
_debug(msg.data)
|
||||||
debug('current ' .. socket.rseq)
|
_debug('current ' .. socket.rseq)
|
||||||
debug('expected ' .. msg.seq)
|
_debug('expected ' .. msg.seq)
|
||||||
-- socket:close()
|
-- socket:close()
|
||||||
-- os.queueEvent('transport_' .. socket.uid)
|
-- os.queueEvent('transport_' .. socket.uid)
|
||||||
else
|
else
|
||||||
@ -122,7 +125,7 @@ Event.on('modem_message', function(_, _, dport, dhost, msg, distance)
|
|||||||
os.queueEvent('transport_' .. socket.uid)
|
os.queueEvent('transport_' .. socket.uid)
|
||||||
end
|
end
|
||||||
|
|
||||||
--debug('>> ' .. Util.tostring({ type = 'ACK', seq = msg.seq }))
|
--_debug('>> ' .. Util.tostring({ type = 'ACK', seq = msg.seq }))
|
||||||
--socket.transmit(socket.dport, socket.dhost, {
|
--socket.transmit(socket.dport, socket.dhost, {
|
||||||
-- type = 'ACK',
|
-- type = 'ACK',
|
||||||
-- seq = msg.seq,
|
-- seq = msg.seq,
|
||||||
|
Loading…
Reference in New Issue
Block a user