mirror of
https://github.com/kepler155c/opus
synced 2024-12-26 00:20:26 +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
|
||||
Util.writeTable(filename, data)
|
||||
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
|
||||
|
||||
return data
|
||||
end
|
||||
|
||||
function Config.loadWithCheck(fname, data)
|
||||
@ -33,7 +38,7 @@ function Config.loadWithCheck(fname, data)
|
||||
shell.run('edit ' .. filename)
|
||||
end
|
||||
|
||||
Config.load(fname, data)
|
||||
return Config.load(fname, data)
|
||||
end
|
||||
|
||||
function Config.update(fname, data)
|
||||
|
@ -1,4 +1,5 @@
|
||||
local os = _G.os
|
||||
local os = _G.os
|
||||
local table = _G.table
|
||||
|
||||
local Event = {
|
||||
uid = 1, -- unique id for handlers
|
||||
@ -6,8 +7,29 @@ local Event = {
|
||||
types = { }, -- event handlers
|
||||
timers = { }, -- named timers
|
||||
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 = { }
|
||||
|
||||
function Routine:isDead()
|
||||
@ -24,18 +46,20 @@ function Routine:terminate()
|
||||
end
|
||||
|
||||
function Routine:resume(event, ...)
|
||||
--if coroutine.status(self.co) == 'running' then
|
||||
--return
|
||||
--end
|
||||
|
||||
if not self.co then
|
||||
error('Cannot resume a dead routine')
|
||||
end
|
||||
|
||||
if not self.filter or self.filter == event or event == "terminate" then
|
||||
local s, m = coroutine.resume(self.co, event, ...)
|
||||
|
||||
if coroutine.status(self.co) == 'dead' then
|
||||
local s, m
|
||||
if self.primeCo 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.filter = nil
|
||||
Event.routines[self.uid] = nil
|
||||
@ -83,8 +107,14 @@ end
|
||||
function Event.off(h)
|
||||
if h and h.event then
|
||||
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
|
||||
end
|
||||
elseif h and h.co then
|
||||
h:terminate()
|
||||
end
|
||||
end
|
||||
|
||||
@ -107,7 +137,12 @@ local function addTimer(interval, recurring, fn)
|
||||
end
|
||||
|
||||
function Event.onInterval(interval, fn)
|
||||
return addTimer(interval, true, fn)
|
||||
return Event.addRoutine(function()
|
||||
while true do
|
||||
os.sleep(interval)
|
||||
fn()
|
||||
end
|
||||
end)
|
||||
end
|
||||
|
||||
function Event.onTimeout(timeout, fn)
|
||||
@ -136,6 +171,18 @@ function Event.waitForEvent(event, timeout)
|
||||
until e[1] == 'timer' and e[2] == timerId
|
||||
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)
|
||||
local r = setmetatable({
|
||||
co = coroutine.create(fn),
|
||||
@ -171,7 +218,7 @@ local function processHandlers(event)
|
||||
for _,h in pairs(handlers) do
|
||||
if not h.co then
|
||||
-- callbacks are single threaded (only 1 co per handler)
|
||||
h.co = coroutine.create(h.fn)
|
||||
h.co = createCoroutine(h)
|
||||
Event.routines[h.uid] = h
|
||||
end
|
||||
end
|
||||
@ -204,11 +251,16 @@ end
|
||||
function Event.pullEvent(eventType)
|
||||
while true do
|
||||
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
|
||||
|
||||
processHandlers(e[1])
|
||||
processRoutines(table.unpack(e))
|
||||
if propagate then
|
||||
processHandlers(e[1])
|
||||
processRoutines(table.unpack(e))
|
||||
end
|
||||
|
||||
if Event.terminate then
|
||||
return { 'terminate' }
|
||||
|
@ -1,5 +1,5 @@
|
||||
local Socket = require('socket')
|
||||
local synchronized = require('sync')
|
||||
local synchronized = require('sync').sync
|
||||
|
||||
local fs = _G.fs
|
||||
|
||||
|
@ -1,27 +1,51 @@
|
||||
local json = require('json')
|
||||
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 FILE_URL = 'https://raw.githubusercontent.com/%s/%s/%s/%s'
|
||||
|
||||
local git = { }
|
||||
|
||||
function git.list(repository)
|
||||
local fs = _G.fs
|
||||
local os = _G.os
|
||||
|
||||
if not _G.GIT then
|
||||
_G.GIT = { }
|
||||
end
|
||||
|
||||
function git.list(repository)
|
||||
local t = Util.split(repository, '(.-)/')
|
||||
|
||||
local user = t[1]
|
||||
local repo = t[2]
|
||||
local branch = t[3] or 'master'
|
||||
local user = table.remove(t, 1)
|
||||
local repo = table.remove(t, 1)
|
||||
local branch = table.remove(t, 1) or 'master'
|
||||
local path
|
||||
|
||||
local dataUrl = string.format(TREE_URL, user, repo, branch)
|
||||
local contents = Util.download(dataUrl)
|
||||
|
||||
if not contents then
|
||||
error('Invalid repository')
|
||||
if not Util.empty(t) then
|
||||
path = table.concat(t, '/') .. '/'
|
||||
end
|
||||
|
||||
local data = json.decode(contents)
|
||||
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
|
||||
error("Out of API calls, try again later")
|
||||
@ -31,15 +55,26 @@ function git.list(repository)
|
||||
error("Invalid repository")
|
||||
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
|
||||
if v.type == "blob" then
|
||||
v.path = v.path:gsub("%s","%%20")
|
||||
list[v.path] = {
|
||||
url = string.format(FILE_URL, user, repo, branch, v.path),
|
||||
size = v.size,
|
||||
}
|
||||
if not path then
|
||||
list[v.path] = {
|
||||
url = string.format(FILE_URL, user, repo, branch, v.path),
|
||||
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
|
||||
|
||||
|
@ -68,7 +68,6 @@ local function encodeCommon(val, pretty, tabLevel, tTracking)
|
||||
str = str .. encodeCommon(v, pretty, tabLevel, tTracking)
|
||||
end)
|
||||
else
|
||||
debug(val)
|
||||
arrEncoding(val, "{", "}", pairs, function(k,v)
|
||||
assert(type(k) == "string", "JSON object keys must be strings", 2)
|
||||
str = str .. encodeCommon(k, pretty, tabLevel, tTracking)
|
||||
|
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
|
||||
|
||||
if ptype == 'modem' then
|
||||
if Peripheral.call(name, 'isWireless') then
|
||||
ptype = 'wireless_modem'
|
||||
else
|
||||
if not Peripheral.call(name, 'isWireless') then
|
||||
-- ptype = 'wireless_modem'
|
||||
-- else
|
||||
ptype = 'wired_modem'
|
||||
end
|
||||
end
|
||||
@ -55,17 +55,21 @@ function Peripheral.addDevice(deviceList, side)
|
||||
end
|
||||
|
||||
-- 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
|
||||
Util.merge(deviceList[name], {
|
||||
name = name,
|
||||
type = ptype,
|
||||
side = side,
|
||||
})
|
||||
|
||||
return deviceList[name]
|
||||
if deviceList[name] then
|
||||
Util.merge(deviceList[name], {
|
||||
name = name,
|
||||
type = ptype,
|
||||
side = side,
|
||||
})
|
||||
end
|
||||
end
|
||||
|
||||
return deviceList[name]
|
||||
end
|
||||
|
||||
function Peripheral.getBySide(side)
|
||||
|
@ -48,6 +48,13 @@ function Point.copy(pt)
|
||||
return { x = pt.x, y = pt.y, z = pt.z }
|
||||
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)
|
||||
return pta.x == ptb.x and
|
||||
pta.y == ptb.y and
|
||||
@ -144,14 +151,21 @@ function Point.calculateMoves(pta, ptb, distance)
|
||||
end
|
||||
end
|
||||
|
||||
if ptb.heading then
|
||||
if heading ~= ptb.heading then
|
||||
moves = moves + Point.calculateTurns(heading, ptb.heading)
|
||||
heading = ptb.heading
|
||||
end
|
||||
if not ptb.heading then
|
||||
return moves, heading, moves
|
||||
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
|
||||
|
||||
-- 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
|
||||
local distance = Point.turtleDistance(reference, pt)
|
||||
if distance < lm then
|
||||
local m = Point.calculateMoves(reference, pt, distance)
|
||||
local _, _, m = Point.calculateMoves(reference, pt, distance)
|
||||
if m < lm then
|
||||
lpt = pt
|
||||
lm = m
|
||||
|
@ -81,7 +81,8 @@ local function loopback(port, sport, msg)
|
||||
end
|
||||
|
||||
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
|
||||
local socket = {
|
||||
shost = os.getComputerID(),
|
||||
@ -128,6 +129,7 @@ function Socket.connect(host, port)
|
||||
local e, id, sport, dport, msg = os.pullEvent()
|
||||
if e == 'modem_message' and
|
||||
sport == socket.sport and
|
||||
type(msg) == 'table' and
|
||||
msg.dhost == socket.shost then
|
||||
|
||||
os.cancelTimer(timerId)
|
||||
@ -171,7 +173,7 @@ local function trusted(msg, port)
|
||||
local data = Crypto.decrypt(msg.t or '', pubKey)
|
||||
|
||||
--local sharedKey = modexp(pubKey, exchange.secretKey, public.primeMod)
|
||||
return data.ts and tonumber(data.ts) and math.abs(os.time() - data.ts) < 1
|
||||
return data.ts and tonumber(data.ts) and math.abs(os.time() - data.ts) < 24
|
||||
end
|
||||
end
|
||||
|
||||
@ -184,6 +186,7 @@ function Socket.server(port)
|
||||
|
||||
if sport == port and
|
||||
msg and
|
||||
type(msg) == 'table' and
|
||||
msg.dhost == os.getComputerID() and
|
||||
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
|
||||
|
||||
return function(obj, fn)
|
||||
function Sync.sync(obj, fn)
|
||||
local key = tostring(obj)
|
||||
if syncLocks[key] then
|
||||
if Sync.syncLocks[key] then
|
||||
local cos = tostring(coroutine.running())
|
||||
table.insert(syncLocks[key], cos)
|
||||
table.insert(Sync.syncLocks[key], cos)
|
||||
repeat
|
||||
local _, co = os.pullEvent('sync_lock')
|
||||
until co == cos
|
||||
else
|
||||
syncLocks[key] = { }
|
||||
Sync.syncLocks[key] = { }
|
||||
end
|
||||
local s, m = pcall(fn)
|
||||
local co = table.remove(syncLocks[key], 1)
|
||||
local co = table.remove(Sync.syncLocks[key], 1)
|
||||
if co then
|
||||
os.queueEvent('sync_lock', co)
|
||||
else
|
||||
syncLocks[key] = nil
|
||||
Sync.syncLocks[key] = nil
|
||||
end
|
||||
if not s then
|
||||
error(m)
|
||||
end
|
||||
end
|
||||
|
||||
function Sync.lock(obj)
|
||||
local key = tostring(obj)
|
||||
if Sync.syncLocks[key] then
|
||||
local cos = tostring(coroutine.running())
|
||||
table.insert(Sync.syncLocks[key], cos)
|
||||
repeat
|
||||
local _, co = os.pullEvent('sync_lock')
|
||||
until co == cos
|
||||
else
|
||||
Sync.syncLocks[key] = { }
|
||||
end
|
||||
end
|
||||
|
||||
function Sync.release(obj)
|
||||
local key = tostring(obj)
|
||||
if not Sync.syncLocks[key] then
|
||||
error('Sync.release: Lock was not obtained', 2)
|
||||
end
|
||||
local co = table.remove(Sync.syncLocks[key], 1)
|
||||
if co then
|
||||
os.queueEvent('sync_lock', co)
|
||||
else
|
||||
Sync.syncLocks[key] = nil
|
||||
end
|
||||
end
|
||||
|
||||
function Sync.isLocked(obj)
|
||||
local key = tostring(obj)
|
||||
return not not Sync.syncLocks[key]
|
||||
end
|
||||
|
||||
return Sync
|
||||
|
316
sys/apis/ui.lua
316
sys/apis/ui.lua
@ -3,6 +3,7 @@ local class = require('class')
|
||||
local Event = require('event')
|
||||
local Input = require('input')
|
||||
local Peripheral = require('peripheral')
|
||||
local Sound = require('sound')
|
||||
local Transition = require('ui.transition')
|
||||
local Util = require('util')
|
||||
|
||||
@ -306,6 +307,9 @@ function Manager:setDefaultDevice(dev)
|
||||
end
|
||||
|
||||
function Manager:addPage(name, page)
|
||||
if not self.pages then
|
||||
self.pages = { }
|
||||
end
|
||||
self.pages[name] = page
|
||||
end
|
||||
|
||||
@ -940,6 +944,7 @@ function UI.Device:postInit()
|
||||
end
|
||||
|
||||
function UI.Device:resize()
|
||||
self.device.setTextScale(self.textScale)
|
||||
self.width, self.height = self.device.getSize()
|
||||
self.lines = { }
|
||||
self.canvas:resize(self.width, self.height)
|
||||
@ -1055,6 +1060,17 @@ function UI.StringBuffer:insert(s, width)
|
||||
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)
|
||||
return Util.widthify(table.concat(self.buffer, sep or ''), self.bufSize)
|
||||
end
|
||||
@ -1205,7 +1221,7 @@ function UI.Page:focusNext()
|
||||
end
|
||||
|
||||
function UI.Page:setFocus(child)
|
||||
if not child.focus then
|
||||
if not child or not child.focus then
|
||||
return
|
||||
end
|
||||
|
||||
@ -1218,7 +1234,8 @@ function UI.Page:setFocus(child)
|
||||
self.focused = child
|
||||
if not child.focused then
|
||||
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
|
||||
|
||||
child:focus()
|
||||
@ -1250,6 +1267,7 @@ UI.Grid.defaults = {
|
||||
backgroundSelectedColor = colors.gray,
|
||||
headerBackgroundColor = colors.cyan,
|
||||
headerTextColor = colors.white,
|
||||
headerSortColor = colors.yellow,
|
||||
unfocusedTextSelectedColor = colors.white,
|
||||
unfocusedBackgroundSelectedColor = colors.gray,
|
||||
focusIndicator = '>',
|
||||
@ -1407,6 +1425,18 @@ function UI.Grid:getSelected()
|
||||
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()
|
||||
self:drawRows()
|
||||
end
|
||||
@ -1459,7 +1489,7 @@ function UI.Grid:update()
|
||||
end
|
||||
|
||||
function UI.Grid:drawHeadings()
|
||||
local sb = UI.StringBuffer(self.width)
|
||||
local x = 1
|
||||
for _,col in ipairs(self.columns) do
|
||||
local ind = ' '
|
||||
if col.key == self.sortColumn then
|
||||
@ -1469,9 +1499,13 @@ function UI.Grid:drawHeadings()
|
||||
ind = self.sortIndicator
|
||||
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
|
||||
self:write(1, 1, sb:get(), self.headerBackgroundColor, self.headerTextColor)
|
||||
end
|
||||
|
||||
function UI.Grid:sortCompare(a, b)
|
||||
@ -1508,7 +1542,11 @@ function UI.Grid:drawRows()
|
||||
end
|
||||
|
||||
for _,col in pairs(self.columns) do
|
||||
sb:insert(ind .. safeValue(row[col.key] or ''), col.cw + 1)
|
||||
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)
|
||||
end
|
||||
ind = ' '
|
||||
end
|
||||
|
||||
@ -1603,13 +1641,12 @@ function UI.Grid:eventHandler(event)
|
||||
local col = 2
|
||||
for _,c in ipairs(self.columns) do
|
||||
if event.x < col + c.cw then
|
||||
if self.sortColumn == c.key then
|
||||
self:setInverseSort(not self.inverseSort)
|
||||
else
|
||||
self.sortColumn = c.key
|
||||
self:setInverseSort(false)
|
||||
end
|
||||
self:draw()
|
||||
self:emit({
|
||||
type = 'grid_sort',
|
||||
sortColumn = c.key,
|
||||
inverseSort = self.sortColumn == c.key and not self.inverseSort,
|
||||
element = self,
|
||||
})
|
||||
break
|
||||
end
|
||||
col = col + c.cw + 1
|
||||
@ -1632,6 +1669,10 @@ function UI.Grid:eventHandler(event)
|
||||
end
|
||||
return false
|
||||
|
||||
elseif event.type == 'grid_sort' then
|
||||
self.sortColumn = event.sortColumn
|
||||
self:setInverseSort(event.inverseSort)
|
||||
self:draw()
|
||||
elseif event.type == 'scroll_down' then
|
||||
self:setIndex(self.index + 1)
|
||||
elseif event.type == 'scroll_up' then
|
||||
@ -1854,6 +1895,8 @@ function UI.TitleBar:draw()
|
||||
sb:center(string.format(' %s ', self.title))
|
||||
if self.previousPage or self.event then
|
||||
sb:insert(-1, self.closeInd)
|
||||
else
|
||||
sb:insert(-2, self.frameChar)
|
||||
end
|
||||
self:write(1, 1, sb:get())
|
||||
end
|
||||
@ -2286,56 +2329,52 @@ function UI.Wizard:add(pages)
|
||||
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
|
||||
for _,child in ipairs(self.children) do
|
||||
if not child.index then
|
||||
child:enable()
|
||||
elseif child.index == 1 then
|
||||
child:enable()
|
||||
self.index = 1
|
||||
local initial = self:getPage(1)
|
||||
for _,child in pairs(self.children) do
|
||||
if child == initial or not child.index then
|
||||
child:enable(...)
|
||||
else
|
||||
child:disable()
|
||||
end
|
||||
end
|
||||
self:emit({ type = 'enable_view', next = Util.find(self.pages, 'index', 1) })
|
||||
self:emit({ type = 'enable_view', next = initial })
|
||||
end
|
||||
|
||||
function UI.Wizard:nextView()
|
||||
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('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
|
||||
function UI.Wizard:isViewValid()
|
||||
local currentView = self:getPage(self.index)
|
||||
return not currentView.validate and true or currentView:validate()
|
||||
end
|
||||
|
||||
function UI.Wizard:eventHandler(event)
|
||||
if event.type == 'nextView' then
|
||||
local currentView = Util.find(self.pages, 'enabled', true)
|
||||
local nextView = Util.find(self.pages, 'index', currentView.index + 1)
|
||||
currentView:emit({ type = 'enable_view', next = nextView, current = currentView })
|
||||
local currentView = self:getPage(self.index)
|
||||
if self:isViewValid() then
|
||||
self.index = self.index + 1
|
||||
local nextView = self:getPage(self.index)
|
||||
currentView:emit({ type = 'enable_view', next = nextView, current = currentView })
|
||||
end
|
||||
|
||||
elseif event.type == 'previousView' then
|
||||
local currentView = Util.find(self.pages, 'enabled', true)
|
||||
local nextView = Util.find(self.pages, 'index', currentView.index - 1)
|
||||
currentView:emit({ type = 'enable_view', prev = nextView, current = currentView })
|
||||
local currentView = self:getPage(self.index)
|
||||
local nextView = self:getPage(self.index - 1)
|
||||
if nextView then
|
||||
self.index = self.index - 1
|
||||
currentView:emit({ type = 'enable_view', prev = nextView, current = currentView })
|
||||
end
|
||||
return true
|
||||
|
||||
elseif event.type == 'wizard_complete' then
|
||||
if self:isViewValid() then
|
||||
self:emit({ type = 'accept' })
|
||||
end
|
||||
|
||||
elseif event.type == 'enable_view' then
|
||||
if event.current then
|
||||
if event.next then
|
||||
@ -2347,18 +2386,20 @@ function UI.Wizard:eventHandler(event)
|
||||
end
|
||||
|
||||
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()
|
||||
else
|
||||
self.previousButton:disable()
|
||||
end
|
||||
|
||||
if Util.find(self.pages, 'index', current.index + 1) then
|
||||
if self:getPage(self.index + 1) then
|
||||
self.nextButton.text = 'Next >'
|
||||
self.nextButton.event = 'nextView'
|
||||
else
|
||||
self.nextButton.text = 'Accept'
|
||||
self.nextButton.event = 'accept'
|
||||
self.nextButton.event = 'wizard_complete'
|
||||
end
|
||||
-- a new current view
|
||||
current:enable()
|
||||
@ -2381,12 +2422,12 @@ function UI.SlideOut:enable()
|
||||
self.enabled = false
|
||||
end
|
||||
|
||||
function UI.SlideOut:show()
|
||||
function UI.SlideOut:show(...)
|
||||
self:addTransition('expandUp')
|
||||
self.canvas:setVisible(true)
|
||||
self.enabled = true
|
||||
for _,child in pairs(self.children) do
|
||||
child:enable()
|
||||
child:enable(...)
|
||||
end
|
||||
self:draw()
|
||||
self:capture(self)
|
||||
@ -2494,6 +2535,7 @@ end
|
||||
|
||||
function UI.Notification:error(value, timeout)
|
||||
self.backgroundColor = colors.red
|
||||
Sound.play('entity.villager.no', .5)
|
||||
self:display(value, timeout)
|
||||
end
|
||||
|
||||
@ -2544,9 +2586,10 @@ UI.Throttle = class(UI.Window)
|
||||
UI.Throttle.defaults = {
|
||||
UIElement = 'Throttle',
|
||||
backgroundColor = colors.gray,
|
||||
height = 6,
|
||||
bordercolor = colors.cyan,
|
||||
height = 4,
|
||||
width = 10,
|
||||
timeout = .095,
|
||||
timeout = .075,
|
||||
ctr = 0,
|
||||
image = {
|
||||
' //) (O )~@ &~&-( ?Q ',
|
||||
@ -2562,6 +2605,7 @@ function UI.Throttle:setParent()
|
||||
end
|
||||
|
||||
function UI.Throttle:enable()
|
||||
self.c = os.clock()
|
||||
self.enabled = false
|
||||
end
|
||||
|
||||
@ -2570,27 +2614,26 @@ function UI.Throttle:disable()
|
||||
self.enabled = false
|
||||
self.canvas:removeLayer()
|
||||
self.canvas = nil
|
||||
self.c = nil
|
||||
self.ctr = 0
|
||||
end
|
||||
end
|
||||
|
||||
function UI.Throttle:update()
|
||||
local cc = os.clock()
|
||||
if not self.c then
|
||||
self.c = cc
|
||||
elseif cc > self.c + self.timeout then
|
||||
if cc > self.c + self.timeout then
|
||||
os.sleep(0)
|
||||
self.c = os.clock()
|
||||
self.enabled = true
|
||||
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:clear(colors.cyan)
|
||||
self:clear(self.borderColor)
|
||||
end
|
||||
local image = self.image[self.ctr + 1]
|
||||
local width = self.width - 2
|
||||
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
|
||||
|
||||
self.ctr = (self.ctr + 1) % #self.image
|
||||
@ -2913,6 +2956,9 @@ UI.Chooser.defaults = {
|
||||
choices = { },
|
||||
nochoice = 'Select',
|
||||
backgroundFocusColor = colors.lightGray,
|
||||
textInactiveColor = colors.gray,
|
||||
leftIndicator = '<',
|
||||
rightIndicator = '>',
|
||||
height = 1,
|
||||
}
|
||||
function UI.Chooser:setParent()
|
||||
@ -2933,14 +2979,15 @@ function UI.Chooser:draw()
|
||||
if self.focused then
|
||||
bg = self.backgroundFocusColor
|
||||
end
|
||||
local fg = self.inactive and self.textInactiveColor or self.textColor
|
||||
local choice = Util.find(self.choices, 'value', self.value)
|
||||
local value = self.nochoice
|
||||
if choice then
|
||||
value = choice.name
|
||||
end
|
||||
self:write(1, 1, '<', bg, colors.black)
|
||||
self:write(2, 1, ' ' .. Util.widthify(value, self.width-4) .. ' ', bg)
|
||||
self:write(self.width, 1, '>', bg, colors.black)
|
||||
self:write(1, 1, self.leftIndicator, self.backgroundColor, colors.black)
|
||||
self:write(2, 1, ' ' .. Util.widthify(tostring(value), self.width-4) .. ' ', bg, fg)
|
||||
self:write(self.width, 1, self.rightIndicator, self.backgroundColor, colors.black)
|
||||
end
|
||||
|
||||
function UI.Chooser:focus()
|
||||
@ -2951,22 +2998,26 @@ function UI.Chooser:eventHandler(event)
|
||||
if event.type == 'key' then
|
||||
if event.key == 'right' or event.key == 'space' then
|
||||
local _,k = Util.find(self.choices, 'value', self.value)
|
||||
local choice
|
||||
if k and k < #self.choices then
|
||||
self.value = self.choices[k+1].value
|
||||
choice = self.choices[k+1]
|
||||
else
|
||||
self.value = self.choices[1].value
|
||||
choice = self.choices[1]
|
||||
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()
|
||||
return true
|
||||
elseif event.key == 'left' then
|
||||
local _,k = Util.find(self.choices, 'value', self.value)
|
||||
local choice
|
||||
if k and k > 1 then
|
||||
self.value = self.choices[k-1].value
|
||||
choice = self.choices[k-1]
|
||||
else
|
||||
self.value = self.choices[#self.choices].value
|
||||
choice = self.choices[#self.choices]
|
||||
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()
|
||||
return true
|
||||
end
|
||||
@ -2981,6 +3032,57 @@ function UI.Chooser:eventHandler(event)
|
||||
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 --]]--
|
||||
UI.Text = class(UI.Window)
|
||||
UI.Text.defaults = {
|
||||
@ -3180,16 +3282,18 @@ function UI.Form:createForm()
|
||||
end
|
||||
end
|
||||
|
||||
table.insert(self.children, UI.Button {
|
||||
y = -self.margin, x = -12 - self.margin,
|
||||
text = 'Ok',
|
||||
event = 'form_ok',
|
||||
})
|
||||
table.insert(self.children, UI.Button {
|
||||
y = -self.margin, x = -7 - self.margin,
|
||||
text = 'Cancel',
|
||||
event = 'form_cancel',
|
||||
})
|
||||
if not self.manualControls then
|
||||
table.insert(self.children, UI.Button {
|
||||
y = -self.margin, x = -12 - self.margin,
|
||||
text = 'Ok',
|
||||
event = 'form_ok',
|
||||
})
|
||||
table.insert(self.children, UI.Button {
|
||||
y = -self.margin, x = -7 - self.margin,
|
||||
text = 'Cancel',
|
||||
event = 'form_cancel',
|
||||
})
|
||||
end
|
||||
end
|
||||
|
||||
function UI.Form:validateField(field)
|
||||
@ -3198,25 +3302,47 @@ function UI.Form:validateField(field)
|
||||
return false, 'Field is required'
|
||||
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
|
||||
end
|
||||
|
||||
function UI.Form:save()
|
||||
for _,child in pairs(self.children) do
|
||||
if child.formKey then
|
||||
local s, m = self:validateField(child)
|
||||
if not s then
|
||||
self:setFocus(child)
|
||||
self:emit({ type = 'form_invalid', message = m, field = child })
|
||||
return false
|
||||
end
|
||||
end
|
||||
end
|
||||
for _,child in pairs(self.children) do
|
||||
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
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
return true
|
||||
end
|
||||
|
||||
function UI.Form:eventHandler(event)
|
||||
if event.type == 'form_ok' then
|
||||
for _,child in pairs(self.children) do
|
||||
if child.formKey then
|
||||
local s, m = self:validateField(child)
|
||||
if not s then
|
||||
self:setFocus(child)
|
||||
self:emit({ type = 'form_invalid', message = m, field = child })
|
||||
return false
|
||||
end
|
||||
end
|
||||
end
|
||||
for _,child in pairs(self.children) do
|
||||
if child.formKey then
|
||||
self.values[child.formKey] = child.value
|
||||
end
|
||||
if not self:save() then
|
||||
return false
|
||||
end
|
||||
self:emit({ type = self.event, UIElement = self })
|
||||
else
|
||||
|
@ -137,7 +137,7 @@ function Canvas:writeBlit(x, y, text, bg, fg)
|
||||
if bg then
|
||||
bg = _sub(bg, 2 - x)
|
||||
end
|
||||
if bg then
|
||||
if fg then
|
||||
fg = _sub(fg, 2 - x)
|
||||
end
|
||||
width = width + x - 1
|
||||
@ -149,7 +149,7 @@ function Canvas:writeBlit(x, y, text, bg, fg)
|
||||
if bg then
|
||||
bg = _sub(bg, 1, self.width - x + 1)
|
||||
end
|
||||
if bg then
|
||||
if fg then
|
||||
fg = _sub(fg, 1, self.width - x + 1)
|
||||
end
|
||||
width = #text
|
||||
|
@ -172,6 +172,20 @@ function Util.deepMerge(obj, args)
|
||||
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)
|
||||
local tt = { }
|
||||
for k,v in pairs(t) do
|
||||
@ -207,6 +221,7 @@ function Util.findAll(t, name, value)
|
||||
end
|
||||
|
||||
function Util.shallowCopy(t)
|
||||
if not t then error('Util.shallowCopy: invalid table', 2) end
|
||||
local t2 = { }
|
||||
for k,v in pairs(t) do
|
||||
t2[k] = v
|
||||
@ -342,6 +357,7 @@ function Util.readFile(fname)
|
||||
end
|
||||
|
||||
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")
|
||||
if not file then
|
||||
error('Unable to open ' .. fname, 2)
|
||||
@ -415,7 +431,7 @@ end
|
||||
function Util.download(url, filename)
|
||||
local contents, msg = Util.httpGet(url)
|
||||
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
|
||||
|
||||
if filename then
|
||||
@ -475,6 +491,7 @@ function Util.insertString(str, istr, pos)
|
||||
end
|
||||
|
||||
function Util.split(str, pattern)
|
||||
if not str then error('Util.split: Invalid parameters', 2) end
|
||||
pattern = pattern or "(.-)\n"
|
||||
local t = {}
|
||||
local function helper(line) table.insert(t, line) return "" end
|
||||
@ -491,7 +508,7 @@ function Util.matches(str, pattern)
|
||||
return t
|
||||
end
|
||||
|
||||
function Util.startsWidth(s, match)
|
||||
function Util.startsWith(s, match)
|
||||
return string.sub(s, 1, #match) == match
|
||||
end
|
||||
|
||||
@ -653,7 +670,6 @@ function Util.getOptions(options, args, ignoreInvalid)
|
||||
end
|
||||
end
|
||||
return true, Util.size(rawOptions)
|
||||
|
||||
end
|
||||
|
||||
return Util
|
||||
|
@ -1,5 +1,6 @@
|
||||
_G.requireInjector(_ENV)
|
||||
|
||||
local Config = require('config')
|
||||
local Event = require('event')
|
||||
local Socket = require('socket')
|
||||
local UI = require('ui')
|
||||
@ -20,6 +21,9 @@ local gridColumns = {
|
||||
{ heading = 'Status', key = 'status' },
|
||||
}
|
||||
|
||||
local trusted = Util.readTable('usr/.known_hosts')
|
||||
local config = Config.load('network', { })
|
||||
|
||||
if UI.term.width >= 30 then
|
||||
table.insert(gridColumns, { heading = 'Fuel', key = 'fuel', width = 5 })
|
||||
table.insert(gridColumns, { heading = 'Uptime', key = 'uptime' })
|
||||
@ -39,7 +43,16 @@ local page = UI.Page {
|
||||
{ text = 'Establish', event = 'trust' },
|
||||
{ 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 {
|
||||
@ -143,6 +156,16 @@ This only needs to be done once.
|
||||
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
|
||||
Event.exitPullEvents()
|
||||
end
|
||||
@ -155,7 +178,7 @@ function page.menuBar:getActive(menuItem)
|
||||
local trustList = Util.readTable('usr/.known_hosts') or { }
|
||||
return t and trustList[t.id]
|
||||
end
|
||||
return not not t
|
||||
return menuItem.noCheck or not not t
|
||||
end
|
||||
|
||||
function page.grid:getRowTextColor(row, selected)
|
||||
@ -184,7 +207,17 @@ function page.grid:getDisplayValues(row)
|
||||
end
|
||||
|
||||
Event.onInterval(1, function()
|
||||
page.grid:update()
|
||||
local t = { }
|
||||
if config.showTrusted then
|
||||
for k,v in pairs(network) do
|
||||
if trusted[k] then
|
||||
t[k] = v
|
||||
end
|
||||
end
|
||||
page.grid:setValues(t)
|
||||
else
|
||||
page.grid:update()
|
||||
end
|
||||
page.grid:draw()
|
||||
page:sync()
|
||||
end)
|
||||
|
@ -1,14 +1,15 @@
|
||||
_G.requireInjector(_ENV)
|
||||
|
||||
local class = require('class')
|
||||
local Config = require('config')
|
||||
local Event = require('event')
|
||||
local FileUI = require('ui.fileui')
|
||||
local NFT = require('nft')
|
||||
local SHA1 = require('sha1')
|
||||
local Tween = require('ui.tween')
|
||||
local UI = require('ui')
|
||||
local Util = require('util')
|
||||
local class = require('class')
|
||||
local Config = require('config')
|
||||
local Event = require('event')
|
||||
local FileUI = require('ui.fileui')
|
||||
local NFT = require('nft')
|
||||
local Packages = require('packages')
|
||||
local SHA1 = require('sha1')
|
||||
local Tween = require('ui.tween')
|
||||
local UI = require('ui')
|
||||
local Util = require('util')
|
||||
|
||||
local colors = _G.colors
|
||||
local fs = _G.fs
|
||||
@ -32,25 +33,28 @@ local config = {
|
||||
Config.load('Overview', config)
|
||||
|
||||
local applications = { }
|
||||
local extSupport = Util.getVersion() >= 1.76
|
||||
|
||||
local function loadApplications()
|
||||
|
||||
local requirements = {
|
||||
turtle = function() return turtle end,
|
||||
advancedTurtle = function() return turtle and term.isColor() end,
|
||||
advanced = function() return term.isColor() end,
|
||||
pocket = function() return pocket end,
|
||||
advancedPocket = function() return pocket and term.isColor() end,
|
||||
advancedComputer = function() return not turtle and not pocket and term.isColor() end,
|
||||
turtle = not not turtle,
|
||||
advancedTurtle = turtle and term.isColor(),
|
||||
advanced = term.isColor(),
|
||||
pocket = not not pocket,
|
||||
advancedPocket = pocket and term.isColor(),
|
||||
advancedComputer = not turtle and not pocket and term.isColor(),
|
||||
}
|
||||
|
||||
applications = Util.readTable('sys/etc/app.db')
|
||||
|
||||
if fs.exists('usr/etc/apps') then
|
||||
local dbs = fs.list('usr/etc/apps')
|
||||
for _, db in pairs(dbs) do
|
||||
local apps = Util.readTable('usr/etc/apps/' .. db) or { }
|
||||
Util.merge(applications, apps)
|
||||
for dir in pairs(Packages:installed()) do
|
||||
local path = fs.combine('packages/' .. dir, 'etc/apps')
|
||||
if fs.exists(path) then
|
||||
local dbs = fs.list(path)
|
||||
for _, db in pairs(dbs) do
|
||||
local apps = Util.readTable(fs.combine(path, db)) or { }
|
||||
Util.merge(applications, apps)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@ -72,13 +76,10 @@ local function loadApplications()
|
||||
end
|
||||
|
||||
if a.requires then
|
||||
local fn = requirements[a.requires]
|
||||
if fn and not fn() then
|
||||
return false
|
||||
end
|
||||
return requirements[a.requires]
|
||||
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
|
||||
|
||||
@ -117,7 +118,7 @@ local function parseIcon(iconText)
|
||||
icon = NFT.parse(iconText)
|
||||
if icon 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
|
||||
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.defaults = {
|
||||
UIElement = 'Icon',
|
||||
@ -194,7 +199,6 @@ function UI.Icon:eventHandler(event)
|
||||
end
|
||||
|
||||
function page.container:setCategory(categoryName, animate)
|
||||
|
||||
-- reset the viewport window
|
||||
self.children = { }
|
||||
self.offy = 0
|
||||
@ -231,7 +235,10 @@ function page.container:setCategory(categoryName, animate)
|
||||
for _,program in ipairs(filtered) do
|
||||
|
||||
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)
|
||||
end
|
||||
if not icon then
|
||||
@ -344,7 +351,6 @@ function page:resize()
|
||||
end
|
||||
|
||||
function page:eventHandler(event)
|
||||
|
||||
if event.type == 'tab_select' then
|
||||
self.container:setCategory(event.button.text, true)
|
||||
self.container:draw()
|
||||
@ -455,7 +461,10 @@ function editor:enable(app)
|
||||
self.form:setValues(app)
|
||||
|
||||
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)
|
||||
end
|
||||
self.form.image:setImage(icon)
|
||||
@ -479,7 +488,6 @@ function editor:updateApplications(app)
|
||||
end
|
||||
|
||||
function editor:eventHandler(event)
|
||||
|
||||
if event.type == 'form_cancel' or event.type == 'cancel' then
|
||||
UI:setPreviousPage()
|
||||
|
||||
@ -501,13 +509,17 @@ function editor:eventHandler(event)
|
||||
local s, m = pcall(function()
|
||||
local iconLines = Util.readFile(fileName)
|
||||
if not iconLines then
|
||||
error('Unable to load file')
|
||||
error('Must be an NFT image - 3 rows, 8 cols max')
|
||||
end
|
||||
local icon, m = parseIcon(iconLines)
|
||||
if not icon then
|
||||
error(m)
|
||||
end
|
||||
self.form.values.icon = iconLines
|
||||
if extSupport then
|
||||
self.form.values.iconExt = iconLines
|
||||
else
|
||||
self.form.values.icon = iconLines
|
||||
end
|
||||
self.form.image:setImage(icon)
|
||||
self.form.image:draw()
|
||||
end)
|
||||
|
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,61 +141,63 @@ local systemPage = UI.Page {
|
||||
}
|
||||
|
||||
if turtle then
|
||||
local Home = require('turtle.home')
|
||||
pcall(function()
|
||||
local Home = require('turtle.home')
|
||||
-- TODO: dont rely on turtle.home
|
||||
local values = { }
|
||||
Config.load('gps', values.home and { values.home } or { })
|
||||
|
||||
local values = { }
|
||||
Config.load('gps', values.home and { values.home } or { })
|
||||
|
||||
systemPage.tabs:add({
|
||||
gpsTab = UI.Window {
|
||||
tabTitle = 'GPS',
|
||||
labelText = UI.Text {
|
||||
x = 3, y = 2,
|
||||
value = 'On restart, return to this location'
|
||||
},
|
||||
grid = UI.Grid {
|
||||
x = 3, ex = -3, y = 4,
|
||||
height = 2,
|
||||
values = values,
|
||||
inactive = true,
|
||||
columns = {
|
||||
{ heading = 'x', key = 'x' },
|
||||
{ heading = 'y', key = 'y' },
|
||||
{ heading = 'z', key = 'z' },
|
||||
systemPage.tabs:add({
|
||||
gpsTab = UI.Window {
|
||||
tabTitle = 'GPS',
|
||||
labelText = UI.Text {
|
||||
x = 3, y = 2,
|
||||
value = 'On restart, return to this location'
|
||||
},
|
||||
grid = UI.Grid {
|
||||
x = 3, ex = -3, y = 4,
|
||||
height = 2,
|
||||
values = values,
|
||||
inactive = true,
|
||||
columns = {
|
||||
{ heading = 'x', key = 'x' },
|
||||
{ heading = 'y', key = 'y' },
|
||||
{ heading = 'z', key = 'z' },
|
||||
},
|
||||
},
|
||||
button1 = UI.Button {
|
||||
x = 3, y = 7,
|
||||
text = 'Set home',
|
||||
event = 'gps_set',
|
||||
},
|
||||
button2 = UI.Button {
|
||||
ex = -3, y = 7, width = 7,
|
||||
text = 'Clear',
|
||||
event = 'gps_clear',
|
||||
},
|
||||
},
|
||||
button1 = UI.Button {
|
||||
x = 3, y = 7,
|
||||
text = 'Set home',
|
||||
event = 'gps_set',
|
||||
},
|
||||
button2 = UI.Button {
|
||||
ex = -3, y = 7, width = 7,
|
||||
text = 'Clear',
|
||||
event = 'gps_clear',
|
||||
},
|
||||
},
|
||||
})
|
||||
function systemPage.tabs.gpsTab:eventHandler(event)
|
||||
if event.type == 'gps_set' then
|
||||
systemPage.notification:info('Determining location', 10)
|
||||
systemPage:sync()
|
||||
if Home.set() then
|
||||
Config.load('gps', values)
|
||||
self.grid:setValues(values.home and { values.home } or { })
|
||||
})
|
||||
function systemPage.tabs.gpsTab:eventHandler(event)
|
||||
if event.type == 'gps_set' then
|
||||
systemPage.notification:info('Determining location', 10)
|
||||
systemPage:sync()
|
||||
if Home.set() then
|
||||
Config.load('gps', values)
|
||||
self.grid:setValues(values.home and { values.home } or { })
|
||||
self.grid:draw()
|
||||
systemPage.notification:success('Location set')
|
||||
else
|
||||
systemPage.notification:error('Unable to determine location')
|
||||
end
|
||||
return true
|
||||
elseif event.type == 'gps_clear' then
|
||||
fs.delete('usr/config/gps')
|
||||
self.grid:setValues({ })
|
||||
self.grid:draw()
|
||||
systemPage.notification:success('Location set')
|
||||
else
|
||||
systemPage.notification:error('Unable to determine location')
|
||||
return true
|
||||
end
|
||||
return true
|
||||
elseif event.type == 'gps_clear' then
|
||||
fs.delete('usr/config/gps')
|
||||
self.grid:setValues({ })
|
||||
self.grid:draw()
|
||||
return true
|
||||
end
|
||||
end
|
||||
end)
|
||||
end
|
||||
|
||||
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 terminal = term.current()
|
||||
Terminal.scrollable(terminal, 100)
|
||||
--Terminal.scrollable(terminal, 100)
|
||||
terminal.noAutoScroll = true
|
||||
|
||||
local config = {
|
||||
@ -568,10 +568,10 @@ local function shellRead(history)
|
||||
local ie = Input:translate(event, p1, p2, p3)
|
||||
if ie then
|
||||
if ie.code == 'scroll_up' then
|
||||
terminal.scrollUp()
|
||||
--terminal.scrollUp()
|
||||
|
||||
elseif ie.code == 'scroll_down' then
|
||||
terminal.scrollDown()
|
||||
--terminal.scrollDown()
|
||||
|
||||
elseif ie.code == 'terminate' then
|
||||
bExit = true
|
||||
|
@ -10,7 +10,7 @@ local os = _G.os
|
||||
local read = _G.read
|
||||
local term = _G.term
|
||||
|
||||
local options, args = Util.args({ ... })
|
||||
local args = { ... }
|
||||
|
||||
local remoteId = tonumber(table.remove(args, 1) or '')
|
||||
if not remoteId then
|
||||
@ -19,11 +19,11 @@ if not remoteId then
|
||||
end
|
||||
|
||||
if not remoteId then
|
||||
error('Syntax: telnet [-title TITLE] ID [PROGRAM]')
|
||||
error('Syntax: telnet ID [PROGRAM] [ARGS]')
|
||||
end
|
||||
|
||||
if options.title and multishell then
|
||||
multishell.setTitle(multishell.getCurrent(), options.title)
|
||||
if multishell then
|
||||
multishell.setTitle(multishell.getCurrent(), 'Telnet ' .. remoteId)
|
||||
end
|
||||
|
||||
local socket, msg = Socket.connect(remoteId, 23)
|
||||
|
148
sys/apps/vnc.lua
148
sys/apps/vnc.lua
@ -1,12 +1,13 @@
|
||||
_G.requireInjector(_ENV)
|
||||
|
||||
local Event = require('event')
|
||||
local Socket = require('socket')
|
||||
local Terminal = require('terminal')
|
||||
local Util = require('util')
|
||||
local Event = require('event')
|
||||
local Socket = require('socket')
|
||||
local Terminal = require('terminal')
|
||||
local Util = require('util')
|
||||
|
||||
local colors = _G.colors
|
||||
local multishell = _ENV.multishell
|
||||
local os = _G.os
|
||||
local term = _G.term
|
||||
|
||||
local remoteId
|
||||
@ -26,75 +27,104 @@ if multishell then
|
||||
multishell.setTitle(multishell.getCurrent(), 'VNC-' .. remoteId)
|
||||
end
|
||||
|
||||
print('connecting...')
|
||||
local socket, msg = Socket.connect(remoteId, 5900)
|
||||
local function connect()
|
||||
local socket, msg = Socket.connect(remoteId, 5900)
|
||||
|
||||
if not socket then
|
||||
error(msg)
|
||||
end
|
||||
if not socket then
|
||||
return false, msg
|
||||
end
|
||||
|
||||
local function writeTermInfo()
|
||||
local w, h = term.getSize()
|
||||
socket:write({
|
||||
type = 'termInfo',
|
||||
width = w,
|
||||
height = h,
|
||||
isColor = term.isColor(),
|
||||
local function writeTermInfo()
|
||||
local w, h = term.getSize()
|
||||
socket:write({
|
||||
type = 'termInfo',
|
||||
width = w,
|
||||
height = h,
|
||||
isColor = term.isColor(),
|
||||
})
|
||||
end
|
||||
|
||||
writeTermInfo()
|
||||
|
||||
local ct = Util.shallowCopy(term.current())
|
||||
|
||||
if not ct.isColor() then
|
||||
Terminal.toGrayscale(ct)
|
||||
end
|
||||
|
||||
ct.clear()
|
||||
ct.setCursorPos(1, 1)
|
||||
|
||||
Event.addRoutine(function()
|
||||
while true do
|
||||
local data = socket:read()
|
||||
if not data then
|
||||
break
|
||||
end
|
||||
for _,v in ipairs(data) do
|
||||
ct[v.f](unpack(v.args))
|
||||
end
|
||||
end
|
||||
end)
|
||||
|
||||
local filter = Util.transpose({
|
||||
'char', 'paste', 'key', 'key_up',
|
||||
'mouse_scroll', 'mouse_click', 'mouse_drag', 'mouse_up',
|
||||
})
|
||||
end
|
||||
|
||||
writeTermInfo()
|
||||
|
||||
local ct = Util.shallowCopy(term.current())
|
||||
|
||||
if not ct.isColor() then
|
||||
Terminal.toGrayscale(ct)
|
||||
end
|
||||
|
||||
Event.addRoutine(function()
|
||||
while true do
|
||||
local data = socket:read()
|
||||
if not data then
|
||||
local e = Event.pullEvent()
|
||||
local event = e[1]
|
||||
|
||||
if not socket.connected then
|
||||
break
|
||||
end
|
||||
for _,v in ipairs(data) do
|
||||
ct[v.f](unpack(v.args))
|
||||
|
||||
if filter[event] then
|
||||
socket:write({
|
||||
type = 'shellRemote',
|
||||
event = e,
|
||||
})
|
||||
elseif event == 'term_resize' then
|
||||
writeTermInfo()
|
||||
elseif event == 'terminate' then
|
||||
socket:close()
|
||||
ct.setBackgroundColor(colors.black)
|
||||
ct.clear()
|
||||
ct.setCursorPos(1, 1)
|
||||
return true
|
||||
end
|
||||
end
|
||||
end)
|
||||
|
||||
ct.clear()
|
||||
ct.setCursorPos(1, 1)
|
||||
|
||||
local filter = Util.transpose({
|
||||
'char', 'paste', 'key', 'key_up',
|
||||
'mouse_scroll', 'mouse_click', 'mouse_drag', 'mouse_up',
|
||||
})
|
||||
return false, "Connection Lost"
|
||||
end
|
||||
|
||||
while true do
|
||||
local e = Event.pullEvent()
|
||||
local event = e[1]
|
||||
term.clear()
|
||||
term.setCursorPos(1, 1)
|
||||
|
||||
if not socket.connected then
|
||||
print()
|
||||
print('Connection lost')
|
||||
print('Press enter to exit')
|
||||
_G.read()
|
||||
print('connecting...')
|
||||
local s, m = connect()
|
||||
if s then
|
||||
break
|
||||
end
|
||||
|
||||
if filter[event] then
|
||||
socket:write({
|
||||
type = 'shellRemote',
|
||||
event = e,
|
||||
})
|
||||
elseif event == 'term_resize' then
|
||||
writeTermInfo()
|
||||
elseif event == 'terminate' then
|
||||
socket:close()
|
||||
ct.setBackgroundColor(colors.black)
|
||||
ct.clear()
|
||||
ct.setCursorPos(1, 1)
|
||||
break
|
||||
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
|
||||
|
@ -11,7 +11,7 @@ for k,v in pairs(_ENV) do
|
||||
sandboxEnv[k] = v
|
||||
end
|
||||
|
||||
_G.debug = function() end
|
||||
_G._debug = function() end
|
||||
|
||||
local function makeEnv()
|
||||
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" ] = {
|
||||
title = "Network",
|
||||
category = "Apps",
|
||||
icon = "\0304 \030 \
|
||||
\030f \0304 \0307 \030 \031 \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",
|
||||
},
|
||||
c7116629a6a855cb774d9c7c8ad822fd83c71fb5 = {
|
||||
@ -13,6 +24,9 @@
|
||||
icon = "\0304\031f \030f\0310o..\0304\031f \
|
||||
\0304\031f \030f\0310.o.\0304\031f \
|
||||
\0304\031f - ",
|
||||
iconExt = "\0307\031f\135\0300\0317\159\0307\0310\144\031f\139\
|
||||
\0300\0317\131\0307\0310\147\0300\0317\156\131\
|
||||
\130\143\143\129",
|
||||
run = "rom/programs/reboot",
|
||||
},
|
||||
fb91e24fa52d8d2b32937bf04d843f730319a902 = {
|
||||
@ -21,6 +35,9 @@
|
||||
icon = "\0301\03171\03180\030 \031 \
|
||||
\0301\03181\030 \031 \
|
||||
\0301\03170\03180\03171\0307\031f>",
|
||||
iconExt = "\031f\128\0313\152\131\131\132\031f\128\
|
||||
\0313\139\159\129\0303\031f\159\129\139\
|
||||
\031f\128\0313\136\0303\031f\143\143\030f\0313\134\031f\128",
|
||||
run = "http://pastebin.com/raw/UzGHLbNC",
|
||||
},
|
||||
c47ae15370cfe1ed2781eedc1dc2547d12d9e972 = {
|
||||
@ -29,6 +46,9 @@
|
||||
icon = " \031f?\031 \
|
||||
\031f?\031 \
|
||||
\031f?",
|
||||
iconExt = "\0300\031f\129\030f\0310\131\0300\031f\148\030f\0310\148\
|
||||
\030 \031 \0300\031f\131\030f\0310\142\129\
|
||||
\030 \031 \0300\031f\131\030f\128",
|
||||
run = "Help.lua",
|
||||
},
|
||||
b0832074630eb731d7fbe8074de48a90cd9bb220 = {
|
||||
@ -37,46 +57,77 @@
|
||||
icon = "\030f \
|
||||
\030f\0310lua>\031 \
|
||||
\030f ",
|
||||
iconExt = "\0300\031f\151\030f\128\0300\159\159\159\030f\0310\144\0304\031f\159\030f\128\
|
||||
\0300\031f\149\030f\128\0300\149\149\151\145\030f\128\0314\153\
|
||||
\130\131\130\131\130\131\0314\130\031f\128",
|
||||
run = "sys/apps/Lua.lua",
|
||||
},
|
||||
df485c871329671f46570634d63216761441bcd6 = {
|
||||
title = "Devices",
|
||||
category = "System",
|
||||
icon = "\0304 \030 \
|
||||
\030f \0304 \0307 \030 \031 \031f_\
|
||||
\030f \0304 \0307 \030 \031f/",
|
||||
run = "Devices.lua",
|
||||
},
|
||||
bc0792d8dc81e8aa30b987246a5ce97c40cd6833 = {
|
||||
title = "System",
|
||||
category = "System",
|
||||
icon = " \0307\031f| \
|
||||
\0307\031f---o\030 \031 \
|
||||
\0307\031f| ",
|
||||
iconExt = "\0318\138\0308\031f\130\0318\128\031f\129\030f\0318\133\
|
||||
\0318\143\0308\128\0317\143\0318\128\030f\143\
|
||||
\0318\138\135\143\139\133",
|
||||
run = "System.lua",
|
||||
},
|
||||
c5497bca58468ae64aed6c0fd921109217988db3 = {
|
||||
title = "Events",
|
||||
category = "System",
|
||||
icon = "\0304\031f \030 \0311e\
|
||||
\030f\031f \0304 \030 \0311ee\031f \
|
||||
\030f\031f \0304 \030 \0311e\031f ",
|
||||
run = "Events.lua",
|
||||
},
|
||||
[ "2a4d562b1d9a9c90bdede6fac8ce4f7402462b86" ] = {
|
||||
title = "Tasks",
|
||||
category = "System",
|
||||
icon = "\030f\031f \0315/\
|
||||
\030f\031f \0315/\\/ \
|
||||
\030f\0315/\031f ",
|
||||
iconExt = "\031f\128\128\0305\159\030f\128\0305\159\030f\0315\134\031f\128\
|
||||
\031f\128\0315\152\129\137\0305\031f\158\139\030f\0317 \
|
||||
\0315\134\031f\128\128\128\128\0305\154\030f\0317 ",
|
||||
run = "Tasks.lua",
|
||||
},
|
||||
[ "a0365977708b7387ee9ce2c13e5820e6e11732cb" ] = {
|
||||
title = "Pain",
|
||||
category = "Apps",
|
||||
icon = "\030 \031f\0307\031f\159\030 \159\030 \
|
||||
\030 \031f\0308\031f\135\0307\0318\144\140\030f\0317\159\143\031c\139\0302\135\030f\0312\157\
|
||||
\030 \031f\030f\0318\143\133\0312\136\0302\031f\159\159\143\131\030f\0312\132",
|
||||
run = "http://pastebin.com/raw/wJQ7jav0",
|
||||
},
|
||||
[ "48d6857f6b2869d031f463b13aa34df47e18c548" ] = {
|
||||
title = "Breakout",
|
||||
category = "Games",
|
||||
icon = "\0301\031f \0309 \030c \030b \030e \030c \0306 \
|
||||
\030 \031f \
|
||||
\030 \031f \0300 \0310 ",
|
||||
iconExt = "\030 \031f\030f\0319\144\030d\031f\159\030b\159\030f\0311\144\031b\144\030c\031f\159\030f\0311\144\
|
||||
\030 \031f\030f\0311\130\031b\129\0319\130\031e\130\0310\144\031d\129\0316\129\
|
||||
\030 \031f\030f\0310\136\140\140\030 ",
|
||||
run = "https://gist.github.com/LDDestroier/c7528d95bc0103545c2a/raw",
|
||||
},
|
||||
[ "53a5d150062b1e03206b9e15854b81060e3c7552" ] = {
|
||||
title = "Minesweeper",
|
||||
category = "Games",
|
||||
icon = "\030f\031f \03131\0308\031f \030f\031d2\
|
||||
\030f\031f \031d2\03131\0308\031f \030f\03131\
|
||||
\030f\03131\0308\031f \030f\03131\031e3",
|
||||
run = "https://pastebin.com/raw/nsKrHTbN",
|
||||
},
|
||||
[ "01c933b2a36ad8ed2d54089cb2903039046c1216" ] = {
|
||||
title = "Enchat",
|
||||
icon = "\030e\031f\151\030f\031e\156\0311\140\0314\140\0315\140\031d\140\031b\140\031a\132\
|
||||
\030f\0314\128\030e\031f\132\030f\031e\132\0318nchat\
|
||||
\030f\031e\138\141\0311\140\0314\140\0315\132\0317v\03183\031a\132",
|
||||
category = "Apps",
|
||||
run = "https://raw.githubusercontent.com/LDDestroier/enchat/master/enchat3.lua",
|
||||
},
|
||||
[ "6ce6c512ea433a7fc5c8841628e7696cd0ff7f2b" ] = {
|
||||
title = "Files",
|
||||
category = "Apps",
|
||||
icon = "\0300\0317==\031 \0307 \
|
||||
\0300\0317====\
|
||||
\0300\0317====",
|
||||
iconExt = "\030 \031f\0300\031f\136\140\132\0308\130\030f\0318\144\
|
||||
\030 \031f\030f\0310\157\0300\031f\147\030f\0310\142\143\149\
|
||||
\030 \031f\0300\031f\136\140\132\140\030f\0310\149",
|
||||
run = "Files.lua",
|
||||
},
|
||||
[ "7fddb7d8d1d60b1eeefa9af01082e0811d4b484d" ] = {
|
||||
@ -85,12 +136,18 @@
|
||||
icon = "\0304\031f \
|
||||
\0304\031f \030f\0310zz\031 \
|
||||
\0304\031f \030f ",
|
||||
iconExt = "\030e\031f\135\030f\031e\148\030e\128\031f\151\139\
|
||||
\030e\031e\128\030f\031f\128\031e\143\031f\128\030e\031e\128\
|
||||
\031e\139\030e\031f\130\131\129\030f\031e\135",
|
||||
run = "/rom/programs/shutdown",
|
||||
},
|
||||
bdc1fd5d3c0f3dcfd55d010426e61bf9451e680d = {
|
||||
title = "Shell",
|
||||
category = "Apps",
|
||||
icon = "\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 ",
|
||||
run = "shell",
|
||||
@ -127,6 +184,9 @@
|
||||
icon = "\030d \030 \030e \030 \
|
||||
\030d \030 \
|
||||
\030d ",
|
||||
iconExt = "\030 \031f\0305\031f\151\030f\0315\135\131\0305\031f\146\
|
||||
\030 \031f\030f\0315\130\141\0305\031f\139\030f\0315\130\
|
||||
\030 \031f\0305\031f\146\143\030f\0315\158\031e\130",
|
||||
run = "/rom/programs/fun/worm",
|
||||
},
|
||||
[ "9f46ca3ef617166776ef6014a58d4e66859caa62" ] = {
|
||||
@ -135,6 +195,17 @@
|
||||
icon = " \030f \
|
||||
\030f \0307 \
|
||||
\030f \0307 \0300 ",
|
||||
iconExt = "\031f\128\0307\143\131\131\131\131\143\030f\128\
|
||||
\0307\031f\129\0317\128\0319\136\0309\031b\136\132\0307\0319\132\0317\128\031f\130\
|
||||
\0317\130\143\0307\128\128\128\128\030f\143\129",
|
||||
run = "/rom/programs/fun/dj",
|
||||
},
|
||||
[ "76b849f460640bc789c433894382fb5acbac42a2" ] = {
|
||||
title = "Tron",
|
||||
category = "Games",
|
||||
iconExt = "\030 \031f\030b\031f\143\030f\128\128\030b\143\143\143\030f\128\128\
|
||||
\030 \031f\0309\031b\140\030b\031f\151\030f\031b\131\0307\148\0317\128\030b\151\030f\031b\131\148\
|
||||
\030 \031f\030f\031b\131\031f\128\031b\131\0317\131\031f\128\0317\131\031b\131\031f\128",
|
||||
run = "https://raw.githubusercontent.com/LDDestroier/CC/master/tron.lua",
|
||||
},
|
||||
}
|
||||
|
@ -8,6 +8,15 @@
|
||||
Button = {
|
||||
--focusIndicator = '\183',
|
||||
},
|
||||
Checkbox = {
|
||||
checkedIndicator = '\4',
|
||||
leftMarker = '\124',
|
||||
rightMarker = '\124',
|
||||
},
|
||||
Chooser = {
|
||||
leftIndicator = '\17',
|
||||
rightIndicator = '\16',
|
||||
},
|
||||
Grid = {
|
||||
focusIndicator = '\183',
|
||||
inverseSortIndicator = '\24',
|
||||
|
@ -33,12 +33,25 @@ local keyboard = _G.device.keyboard
|
||||
local mouse = _G.device.mouse
|
||||
local os = _G.os
|
||||
|
||||
local drivers = { }
|
||||
|
||||
kernel.hook('peripheral', function(_, eventData)
|
||||
local side = eventData[1]
|
||||
if side then
|
||||
local dev = Peripheral.addDevice(device, side)
|
||||
if dev then
|
||||
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)
|
||||
@ -48,7 +61,12 @@ kernel.hook('peripheral_detach', function(_, eventData)
|
||||
if side then
|
||||
local dev = Util.find(device, 'side', side)
|
||||
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
|
||||
end
|
||||
end
|
||||
@ -109,3 +127,60 @@ kernel.hook('monitor_touch', function(event, eventData)
|
||||
return true -- stop propagation
|
||||
end
|
||||
end)
|
||||
|
||||
local function createDevice(name, devType, method, manipulator)
|
||||
local dev = {
|
||||
name = name,
|
||||
side = name,
|
||||
type = devType,
|
||||
}
|
||||
local methods = {
|
||||
'drop', 'getDocs', 'getItem', 'getItemMeta', 'getTransferLocations',
|
||||
'list', 'pullItems', 'pushItems', 'size', 'suck',
|
||||
}
|
||||
if manipulator[method] then
|
||||
for _,k in pairs(methods) do
|
||||
dev[k] = function(...)
|
||||
return manipulator[method]()[k](...)
|
||||
end
|
||||
end
|
||||
if not manipulator._children then
|
||||
manipulator._children = { dev }
|
||||
else
|
||||
table.insert(manipulator._children, dev)
|
||||
end
|
||||
device[name] = dev
|
||||
end
|
||||
end
|
||||
|
||||
drivers['manipulator'] = function(dev)
|
||||
if dev.getName then
|
||||
local name
|
||||
pcall(function()
|
||||
name = dev.getName()
|
||||
end)
|
||||
if name then
|
||||
if dev.getInventory then
|
||||
createDevice(name .. ':inventory', 'inventory', 'getInventory', dev)
|
||||
end
|
||||
if dev.getEquipment then
|
||||
createDevice(name .. ':equipment', 'equipment', 'getEquipment', dev)
|
||||
end
|
||||
if dev.getEnder then
|
||||
createDevice(name .. ':enderChest', 'enderChest', 'getEnder', dev)
|
||||
end
|
||||
|
||||
return dev._children
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
-- initialize drivers
|
||||
for _,v in pairs(device) do
|
||||
if drivers[v.type] then
|
||||
local s, m = pcall(drivers[v.type], v)
|
||||
if not s and m then
|
||||
_G.printError(m)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
@ -11,10 +11,10 @@ end
|
||||
if not fs.exists('usr/autorun') then
|
||||
fs.makeDir('usr/autorun')
|
||||
end
|
||||
if not fs.exists('usr/config/fstab') then
|
||||
Util.writeFile('usr/config/fstab',
|
||||
'usr gitfs kepler155c/opus-apps/' .. _G.OPUS_BRANCH)
|
||||
end
|
||||
--if not fs.exists('usr/config/fstab') then
|
||||
-- Util.writeFile('usr/config/fstab',
|
||||
-- 'usr gitfs kepler155c/opus-apps/' .. _G.OPUS_BRANCH)
|
||||
--end
|
||||
|
||||
if not fs.exists('usr/config/shell') then
|
||||
Util.writeTable('usr/config/shell', {
|
||||
@ -24,6 +24,17 @@ if not fs.exists('usr/config/shell') then
|
||||
})
|
||||
end
|
||||
|
||||
if not fs.exists('usr/config/packages') then
|
||||
local packages = {
|
||||
[ 'develop-1.8' ] = 'https://pastebin.com/raw/WhEiNGZE',
|
||||
[ 'master-1.8' ] = 'https://pastebin.com/raw/pexZpAxt',
|
||||
}
|
||||
|
||||
if packages[_G.OPUS_BRANCH] then
|
||||
Util.download(packages[_G.OPUS_BRANCH], 'usr/config/packages')
|
||||
end
|
||||
end
|
||||
|
||||
local config = Util.readTable('usr/config/shell')
|
||||
if config.aliases then
|
||||
for k in pairs(shell.aliases()) do
|
||||
|
@ -1,5 +1,10 @@
|
||||
local kernel = _G.kernel
|
||||
local os = _G.os
|
||||
_G.requireInjector(_ENV)
|
||||
|
||||
local Config = require('config')
|
||||
|
||||
local device = _G.device
|
||||
local kernel = _G.kernel
|
||||
local os = _G.os
|
||||
|
||||
_G.network = { }
|
||||
|
||||
@ -11,15 +16,44 @@ local function startNetwork()
|
||||
})
|
||||
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)
|
||||
if eventData[1] == 'wireless_modem' then
|
||||
startNetwork()
|
||||
local dev = device[eventData[1]]
|
||||
if dev and dev.type == 'modem' then
|
||||
if setModem(dev) then
|
||||
startNetwork()
|
||||
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...')
|
||||
startNetwork()
|
||||
os.pullEvent('network_up')
|
||||
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 GPS = require('gps')
|
||||
local Point = require('point')
|
||||
local synchronized = require('sync')
|
||||
local synchronized = require('sync').sync
|
||||
local Util = require('util')
|
||||
|
||||
local os = _G.os
|
||||
@ -458,6 +458,7 @@ function turtle.back()
|
||||
end
|
||||
|
||||
local function moveTowardsX(dx)
|
||||
if not tonumber(dx) then error('moveTowardsX: Invalid arguments') end
|
||||
local direction = dx - turtle.point.x
|
||||
local move
|
||||
|
||||
@ -662,11 +663,11 @@ function turtle._goto(pt)
|
||||
local dx, dy, dz, dh = pt.x, pt.y, pt.z, pt.heading
|
||||
if not turtle.gotoSingleTurn(dx, dy, dz, dh) then
|
||||
if not gotoMultiTurn(dx, dy, dz) then
|
||||
return false
|
||||
return false, 'Failed to reach location'
|
||||
end
|
||||
end
|
||||
turtle.setHeading(dh)
|
||||
return true
|
||||
return pt
|
||||
end
|
||||
|
||||
-- avoid lint errors
|
||||
@ -738,6 +739,8 @@ function turtle.getSlot(indexOrId, slots)
|
||||
}
|
||||
end
|
||||
|
||||
-- inconsistent return value
|
||||
-- null is returned if indexOrId is a string and no item is present
|
||||
return {
|
||||
qty = 0, -- deprecate
|
||||
count = 0,
|
||||
@ -794,8 +797,12 @@ function turtle.getSummedInventory()
|
||||
end
|
||||
|
||||
function turtle.has(item, count)
|
||||
local slot = turtle.getSummedInventory()[item]
|
||||
return slot and slot.count >= (count or 1)
|
||||
if item:match('.*:%d') then
|
||||
local slot = turtle.getSummedInventory()[item]
|
||||
return slot and slot.count >= (count or 1)
|
||||
end
|
||||
local slot = turtle.getSlot(item)
|
||||
return slot and slot.count > 0
|
||||
end
|
||||
|
||||
function turtle.getFilledSlots(startSlot)
|
||||
@ -966,6 +973,18 @@ function turtle.addWorldBlock(pt)
|
||||
Pathing.addBlock(pt)
|
||||
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
|
||||
options = options or { }
|
||||
options.dest = { }
|
||||
@ -980,7 +999,7 @@ function turtle.faceAgainst(pt, options) -- 4 sided
|
||||
})
|
||||
end
|
||||
|
||||
return turtle.pathfind(Point.closest(turtle.point, options.dest), options)
|
||||
return movementStrategy(Point.closest(turtle.point, options.dest), options)
|
||||
end
|
||||
|
||||
-- move against this point
|
||||
@ -1012,7 +1031,7 @@ function turtle.moveAgainst(pt, options) -- 6 sided
|
||||
})
|
||||
end
|
||||
|
||||
return turtle.pathfind(Point.closest(turtle.point, options.dest), options)
|
||||
return movementStrategy(Point.closest(turtle.point, options.dest), options)
|
||||
end
|
||||
|
||||
local actionsAt = {
|
||||
@ -1097,7 +1116,7 @@ local function _actionAt(action, pt, ...)
|
||||
direction = 'up'
|
||||
end
|
||||
|
||||
if turtle.pathfind(apt) then
|
||||
if movementStrategy(apt) then
|
||||
return action[direction](...)
|
||||
end
|
||||
end
|
||||
|
@ -1,7 +1,8 @@
|
||||
_G.requireInjector(_ENV)
|
||||
|
||||
local Config = require('config')
|
||||
local Util = require('util')
|
||||
local Config = require('config')
|
||||
local Packages = require('packages')
|
||||
local Util = require('util')
|
||||
|
||||
local colors = _G.colors
|
||||
local fs = _G.fs
|
||||
@ -32,6 +33,7 @@ local config = {
|
||||
backgroundColor = colors.gray,
|
||||
tabBarBackgroundColor = colors.gray,
|
||||
focusBackgroundColor = colors.gray,
|
||||
errorColor = colors.black,
|
||||
},
|
||||
color = {
|
||||
textColor = colors.lightGray,
|
||||
@ -40,6 +42,7 @@ local config = {
|
||||
backgroundColor = colors.gray,
|
||||
tabBarBackgroundColor = colors.gray,
|
||||
focusBackgroundColor = colors.gray,
|
||||
errorColor = colors.red,
|
||||
},
|
||||
}
|
||||
Config.load('multishell', config)
|
||||
@ -129,6 +132,7 @@ function multishell.openTab(tab)
|
||||
print('\nPress enter to close')
|
||||
routine.isDead = true
|
||||
routine.hidden = false
|
||||
redrawMenu()
|
||||
while true do
|
||||
local e, code = os.pullEventRaw('key')
|
||||
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
|
||||
tabX = tabX + tab.width
|
||||
if tab ~= currentTab then
|
||||
local textColor = tab.isDead and _colors.errorColor or _colors.textColor
|
||||
write(tab.sx, tab.title:sub(1, tab.width - 1),
|
||||
_colors.backgroundColor, _colors.textColor)
|
||||
_colors.backgroundColor, textColor)
|
||||
end
|
||||
end
|
||||
end
|
||||
@ -371,6 +376,10 @@ local function startup()
|
||||
end
|
||||
|
||||
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)
|
||||
|
||||
if not success then
|
||||
|
@ -28,12 +28,18 @@ local focusedRoutineEvents = Util.transpose {
|
||||
'paste', 'terminate',
|
||||
}
|
||||
|
||||
_G.debug = function(pattern, ...)
|
||||
_G._debug = function(pattern, ...)
|
||||
local oldTerm = term.redirect(kernel.window)
|
||||
Util.print(pattern, ...)
|
||||
term.redirect(oldTerm)
|
||||
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
|
||||
-- a separate coroutine or have a window. an error in a hook
|
||||
-- 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 request.method == 'GET' then
|
||||
local query
|
||||
if not request.path or type(request.path) ~= 'string' then
|
||||
return
|
||||
end
|
||||
local path = request.path:gsub('%?(.*)', function(v)
|
||||
query = parseQuery(v)
|
||||
return ''
|
||||
end)
|
||||
if fs.isDir(path) then
|
||||
-- TODO: more validation
|
||||
modem.transmit(request.replyPort, request.replyAddress, {
|
||||
statusCode = 200,
|
||||
contentType = 'table/directory',
|
||||
|
@ -150,11 +150,13 @@ local function sendInfo()
|
||||
end
|
||||
if device.neuralInterface then
|
||||
info.status = device.neuralInterface.status
|
||||
if not info.status and device.neuralInterface.getMetaOwner then
|
||||
info.status = 'health: ' ..
|
||||
math.floor(device.neuralInterface.getMetaOwner().health /
|
||||
device.neuralInterface.getMetaOwner().maxHealth * 100)
|
||||
end
|
||||
pcall(function()
|
||||
if not info.status and device.neuralInterface.getMetaOwner then
|
||||
info.status = 'health: ' ..
|
||||
math.floor(device.neuralInterface.getMetaOwner().health /
|
||||
device.neuralInterface.getMetaOwner().maxHealth * 100)
|
||||
end
|
||||
end)
|
||||
end
|
||||
device.wireless_modem.transmit(999, os.getComputerID(), info)
|
||||
end
|
||||
|
@ -33,7 +33,7 @@ function transport.read(socket)
|
||||
end
|
||||
|
||||
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)
|
||||
|
||||
--local timerId = os.startTimer(3)
|
||||
@ -45,7 +45,7 @@ function transport.write(socket, data)
|
||||
end
|
||||
|
||||
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
|
||||
socket.activityTimer = os.clock()
|
||||
socket.transmit(socket.dport, socket.dhost, {
|
||||
@ -78,9 +78,12 @@ Event.on('modem_message', function(_, _, dport, dhost, msg, distance)
|
||||
local socket = transport.sockets[dport]
|
||||
if socket and socket.connected then
|
||||
|
||||
--if msg.type then debug('<< ' .. Util.tostring(msg)) end
|
||||
--if 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
|
||||
if socket.connected then
|
||||
os.queueEvent('transport_' .. socket.uid)
|
||||
@ -108,9 +111,9 @@ Event.on('modem_message', function(_, _, dport, dhost, msg, distance)
|
||||
socket.activityTimer = os.clock()
|
||||
if msg.seq ~= socket.rseq then
|
||||
print('transport seq error - closing socket ' .. socket.sport)
|
||||
debug(msg.data)
|
||||
debug('current ' .. socket.rseq)
|
||||
debug('expected ' .. msg.seq)
|
||||
_debug(msg.data)
|
||||
_debug('current ' .. socket.rseq)
|
||||
_debug('expected ' .. msg.seq)
|
||||
-- socket:close()
|
||||
-- os.queueEvent('transport_' .. socket.uid)
|
||||
else
|
||||
@ -122,7 +125,7 @@ Event.on('modem_message', function(_, _, dport, dhost, msg, distance)
|
||||
os.queueEvent('transport_' .. socket.uid)
|
||||
end
|
||||
|
||||
--debug('>> ' .. Util.tostring({ type = 'ACK', seq = msg.seq }))
|
||||
--_debug('>> ' .. Util.tostring({ type = 'ACK', seq = msg.seq }))
|
||||
--socket.transmit(socket.dport, socket.dhost, {
|
||||
-- type = 'ACK',
|
||||
-- seq = msg.seq,
|
||||
|
Loading…
Reference in New Issue
Block a user