1
0
mirror of https://github.com/kepler155c/opus synced 2024-12-27 09:00:41 +00:00

new release

This commit is contained in:
kepler155c@gmail.com 2018-12-08 13:25:30 -05:00
commit 3953ef08cc
35 changed files with 1356 additions and 369 deletions

View File

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

View File

@ -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
processHandlers(e[1]) if propagate then
processRoutines(table.unpack(e)) processHandlers(e[1])
processRoutines(table.unpack(e))
end
if Event.terminate then if Event.terminate then
return { 'terminate' } return { 'terminate' }

View File

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

View File

@ -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
if not _G.GIT then
_G.GIT = { }
end
function git.list(repository)
local t = Util.split(repository, '(.-)/') local t = Util.split(repository, '(.-)/')
local user = t[1] local user = table.remove(t, 1)
local repo = t[2] local repo = table.remove(t, 1)
local branch = t[3] or 'master' local branch = table.remove(t, 1) or 'master'
local path
local dataUrl = string.format(TREE_URL, user, repo, branch) if not Util.empty(t) then
local contents = Util.download(dataUrl) path = table.concat(t, '/') .. '/'
if not contents then
error('Invalid repository')
end 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 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")
list[v.path] = { if not path then
url = string.format(FILE_URL, user, repo, branch, v.path), list[v.path] = {
size = v.size, 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
end end

View File

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

View File

@ -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,17 +55,21 @@ 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], {
name = name, name = name,
type = ptype, type = ptype,
side = side, side = side,
}) })
end
return deviceList[name]
end end
return deviceList[name]
end end
function Peripheral.getBySide(side) function Peripheral.getBySide(side)

View File

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

View File

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

View File

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

View File

@ -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
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 = ' ' 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
currentView:emit({ type = 'enable_view', next = nextView, current = currentView }) 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 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)
currentView:emit({ type = 'enable_view', prev = nextView, current = currentView }) if nextView then
self.index = self.index - 1
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,16 +3282,18 @@ function UI.Form:createForm()
end end
end end
table.insert(self.children, UI.Button { if not self.manualControls then
y = -self.margin, x = -12 - self.margin, table.insert(self.children, UI.Button {
text = 'Ok', y = -self.margin, x = -12 - self.margin,
event = 'form_ok', text = 'Ok',
}) event = 'form_ok',
table.insert(self.children, UI.Button { })
y = -self.margin, x = -7 - self.margin, table.insert(self.children, UI.Button {
text = 'Cancel', y = -self.margin, x = -7 - self.margin,
event = 'form_cancel', text = 'Cancel',
}) event = 'form_cancel',
})
end
end end
function UI.Form:validateField(field) function UI.Form:validateField(field)
@ -3198,25 +3302,47 @@ 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
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 return true
end end
function UI.Form:eventHandler(event) function UI.Form:eventHandler(event)
if event.type == 'form_ok' then if event.type == 'form_ok' then
for _,child in pairs(self.children) do if not self:save() then
if child.formKey then return false
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
end end
self:emit({ type = self.event, UIElement = self }) self:emit({ type = self.event, UIElement = self })
else else

View File

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

View File

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

View File

@ -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()
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.grid:draw()
page:sync() page:sync()
end) end)

View File

@ -1,14 +1,15 @@
_G.requireInjector(_ENV) _G.requireInjector(_ENV)
local class = require('class') local class = require('class')
local Config = require('config') 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 SHA1 = require('sha1') local Packages = require('packages')
local Tween = require('ui.tween') local SHA1 = require('sha1')
local UI = require('ui') local Tween = require('ui.tween')
local Util = require('util') local UI = require('ui')
local Util = require('util')
local colors = _G.colors local colors = _G.colors
local fs = _G.fs local fs = _G.fs
@ -32,25 +33,28 @@ 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')
for _, db in pairs(dbs) do if fs.exists(path) then
local apps = Util.readTable('usr/etc/apps/' .. db) or { } local dbs = fs.list(path)
Util.merge(applications, apps) for _, db in pairs(dbs) do
local apps = Util.readTable(fs.combine(path, db)) or { }
Util.merge(applications, apps)
end
end end
end end
@ -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
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:setImage(icon)
self.form.image:draw() self.form.image:draw()
end) end)

158
sys/apps/PackageManager.lua Normal file
View 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()

View File

@ -141,61 +141,63 @@ local systemPage = UI.Page {
} }
if turtle then 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 = { } systemPage.tabs:add({
Config.load('gps', values.home and { values.home } or { }) gpsTab = UI.Window {
tabTitle = 'GPS',
systemPage.tabs:add({ labelText = UI.Text {
gpsTab = UI.Window { x = 3, y = 2,
tabTitle = 'GPS', value = 'On restart, return to this location'
labelText = UI.Text { },
x = 3, y = 2, grid = UI.Grid {
value = 'On restart, return to this location' x = 3, ex = -3, y = 4,
}, height = 2,
grid = UI.Grid { values = values,
x = 3, ex = -3, y = 4, inactive = true,
height = 2, columns = {
values = values, { heading = 'x', key = 'x' },
inactive = true, { heading = 'y', key = 'y' },
columns = { { heading = 'z', key = 'z' },
{ 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, function systemPage.tabs.gpsTab:eventHandler(event)
text = 'Set home', if event.type == 'gps_set' then
event = 'gps_set', systemPage.notification:info('Determining location', 10)
}, systemPage:sync()
button2 = UI.Button { if Home.set() then
ex = -3, y = 7, width = 7, Config.load('gps', values)
text = 'Clear', self.grid:setValues(values.home and { values.home } or { })
event = 'gps_clear', self.grid:draw()
}, systemPage.notification:success('Location set')
}, else
}) systemPage.notification:error('Unable to determine location')
function systemPage.tabs.gpsTab:eventHandler(event) end
if event.type == 'gps_set' then return true
systemPage.notification:info('Determining location', 10) elseif event.type == 'gps_clear' then
systemPage:sync() fs.delete('usr/config/gps')
if Home.set() then self.grid:setValues({ })
Config.load('gps', values)
self.grid:setValues(values.home and { values.home } or { })
self.grid:draw() self.grid:draw()
systemPage.notification:success('Location set') return true
else
systemPage.notification:error('Unable to determine location')
end 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)
end end
if settings then if settings then

97
sys/apps/package.lua Normal file
View 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')

View File

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

View File

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

View File

@ -1,12 +1,13 @@
_G.requireInjector(_ENV) _G.requireInjector(_ENV)
local Event = require('event') local Event = require('event')
local Socket = require('socket') local Socket = require('socket')
local Terminal = require('terminal') local Terminal = require('terminal')
local Util = require('util') 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,75 +27,104 @@ 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()
local w, h = term.getSize() local w, h = term.getSize()
socket:write({ socket:write({
type = 'termInfo', type = 'termInfo',
width = w, width = w,
height = h, height = h,
isColor = term.isColor(), 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 while true do
local data = socket:read() local e = Event.pullEvent()
if not data then local event = e[1]
if not socket.connected then
break break
end 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 end
end) return false, "Connection Lost"
end
ct.clear()
ct.setCursorPos(1, 1)
local filter = Util.transpose({
'char', 'paste', 'key', 'key_up',
'mouse_scroll', 'mouse_click', 'mouse_drag', 'mouse_up',
})
while true do while true do
local e = Event.pullEvent() term.clear()
local event = e[1] term.setCursorPos(1, 1)
if not socket.connected then print('connecting...')
print() local s, m = connect()
print('Connection lost') if s then
print('Press enter to exit')
_G.read()
break break
end end
if filter[event] then term.setBackgroundColor(colors.black)
socket:write({ term.setTextColor(colors.white)
type = 'shellRemote', term.clear()
event = e, term.setCursorPos(1, 1)
}) print(m)
elseif event == 'term_resize' then print('\nPress any key to exit')
writeTermInfo() print('\nRetrying in ... ')
elseif event == 'terminate' then local x, y = term.getCursorPos()
socket:close() for i = 5, 1, -1 do
ct.setBackgroundColor(colors.black) local timerId = os.startTimer(1)
ct.clear() term.setCursorPos(x, y)
ct.setCursorPos(1, 1) term.write(i)
break repeat
local e, id = os.pullEvent()
if e == 'char' or e == 'key' then
return
end
until e == 'timer' and id == timerId
end end
end end

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -1,5 +1,10 @@
local kernel = _G.kernel _G.requireInjector(_ENV)
local os = _G.os
local Config = require('config')
local device = _G.device
local kernel = _G.kernel
local os = _G.os
_G.network = { } _G.network = { }
@ -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]]
startNetwork() if dev and dev.type == 'modem' then
if setModem(dev) then
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

View 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, ':')

View File

@ -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,8 +797,12 @@ function turtle.getSummedInventory()
end end
function turtle.has(item, count) function turtle.has(item, count)
local slot = turtle.getSummedInventory()[item] if item:match('.*:%d') then
return slot and slot.count >= (count or 1) 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 end
function turtle.getFilledSlots(startSlot) function turtle.getFilledSlots(startSlot)
@ -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

View File

@ -1,7 +1,8 @@
_G.requireInjector(_ENV) _G.requireInjector(_ENV)
local Config = require('config') local Config = require('config')
local Util = require('util') local Packages = require('packages')
local Util = require('util')
local colors = _G.colors local colors = _G.colors
local fs = _G.fs local fs = _G.fs
@ -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

View File

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

View File

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

View File

@ -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
if not info.status and device.neuralInterface.getMetaOwner then pcall(function()
info.status = 'health: ' .. if not info.status and device.neuralInterface.getMetaOwner then
math.floor(device.neuralInterface.getMetaOwner().health / info.status = 'health: ' ..
device.neuralInterface.getMetaOwner().maxHealth * 100) math.floor(device.neuralInterface.getMetaOwner().health /
end device.neuralInterface.getMetaOwner().maxHealth * 100)
end
end)
end end
device.wireless_modem.transmit(999, os.getComputerID(), info) device.wireless_modem.transmit(999, os.getComputerID(), info)
end end

View File

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