1
0
mirror of https://github.com/kepler155c/opus synced 2025-10-21 02:37:48 +00:00

major directory reorganize

This commit is contained in:
kepler155c@gmail.com
2017-05-20 18:27:26 -04:00
parent 7954c79d66
commit c8147ef9e8
85 changed files with 67 additions and 59 deletions

View File

@@ -0,0 +1,8 @@
{
icon = "\030 \0310=\0300 \030 XX\0300\031f \030 \
\030 \031f \0300 \030 \
\030 \031f \0310o \031f \0310o\031f ",
category = "System",
title = "AppStore",
run = "Appstore.lua",
}

View File

@@ -0,0 +1,8 @@
{
icon = "\0304\031f \030 \0311e\
\030f\031f \0304 \030 \0311ee\031f \
\030f\031f \0304 \030 \0311e\031f ",
title = "Events",
category = "System",
run = "Events.lua",
}

8
sys/apps/.overview/Files Normal file
View File

@@ -0,0 +1,8 @@
{
icon = "\0300\0317==\031 \0307 \
\0300\0317====\
\0300\0317====",
title = "Files",
category = "Apps",
run = "Files.lua",
}

8
sys/apps/.overview/Help Normal file
View File

@@ -0,0 +1,8 @@
{
icon = " \031d?\031 \
\031d?\031 \
\031d?",
title = "Help",
category = "Apps",
run = "Help.lua",
}

8
sys/apps/.overview/Lua Normal file
View File

@@ -0,0 +1,8 @@
{
icon = "\030f \
\030f\0310lua>\031 \
\030f ",
title = "Lua",
category = "Apps",
run = "Lua.lua",
}

View File

@@ -0,0 +1,9 @@
{
title = "Network",
category = "Apps",
requires = "wireless_modem",
icon = "\0304 \030 \
\030f \0304 \0307 \030 \031 \031f)\
\030f \0304 \0307 \030 \031f)",
run = "Network.lua",
}

View File

@@ -0,0 +1,8 @@
{
icon = "\0304 \030 \
\030f \0304 \0307 \030 \031 \031f_\
\030f \0304 \0307 \030 \031f/",
title = "Devices",
category = "System",
run = "Peripherals.lua",
}

View File

@@ -0,0 +1,9 @@
{
title = "Scripts",
category = "Apps",
requires = "wireless_modem",
icon = "\0300\0317if\031 \0307 \
\0300\0317turt\
\0300\0317retu",
run = "Script.lua",
}

View File

@@ -0,0 +1,8 @@
{
icon = " \0307\031f| \
\0307\031f---o\030 \031 \
\0307\031f| ",
title = "System",
category = "System",
run = "System.lua",
}

8
sys/apps/.overview/Tabs Normal file
View File

@@ -0,0 +1,8 @@
{
icon = "\0307 \0303\0317__\0307\031 \
\0303 \
\0303 ",
title = "Tabs",
category = "System",
run = "Tabs.lua",
}

View File

@@ -0,0 +1,5 @@
{
category = "Apps",
title = "Turtles",
run = "Turtles.lua",
}

View File

@@ -0,0 +1,8 @@
{
icon = "\030f\0310You \031 \
\030f\0310Ther\030 \031 \
\030f\0314?\031f \031 \030 ",
title = "Adventure",
category = "Games",
run = "rom/programs/fun/adventure",
}

View File

@@ -0,0 +1,8 @@
{
icon = "\0317_____\
\030e\031c###\0308\0317=\030e\031c#\
\030e\031c#\0307\031f.\030e\031c###",
title = "Builder",
category = "Apps",
run = "builder.lua",
}

8
sys/apps/.overview/dj Normal file
View File

@@ -0,0 +1,8 @@
{
icon = " \030f \
\030f \0307 \
\030f \0307 \0300 ",
title = "DJ",
category = "Games",
run = "/rom/programs/fun/dj",
}

View File

@@ -0,0 +1,8 @@
{
icon = "\030f \0302 \
\0309 \0302 \0301 \
\030e \0309 \0301 ",
title = "Falling",
category = "Games",
run = "rom/programs/pocket/falling",
}

View File

@@ -0,0 +1,8 @@
{
icon = "\0304\031f \030f\0310o..\0304\031f \
\0304\031f \030f\0310.o.\0304\031f \
\0304\031f - ",
title = "Reboot",
category = "System",
run = "rom/programs/reboot",
}

View File

@@ -0,0 +1,8 @@
{
icon = "\030 \031f \031b \031foo \
\030 \031f \030e\031b \030 \031f/\
\030 \031b \030e \030 \031f\\",
category = "Apps",
title = "Recorder",
run = "recorder.lua",
}

View File

@@ -0,0 +1,8 @@
{
icon = "\0307 \0308 \0307 \
\0308\031b> \030b\0310>\0308\0318 \
\0307 ",
title = "Redirection",
category = "Games",
run = "rom/programs/fun/advanced/redirection",
}

8
sys/apps/.overview/shell Normal file
View File

@@ -0,0 +1,8 @@
{
icon = "\0304 \030 \
\0304 \030f\0314> \0310_\031 \
\0304 \030f \030 ",
title = "Shell",
category = "Apps",
run = "shell",
}

View File

@@ -0,0 +1,8 @@
{
icon = "\0304\031f \
\0304\031f \030f\0310zz\031 \
\0304\031f \030f ",
title = "Shutdown",
category = "System",
run = "/rom/programs/shutdown",
}

View File

@@ -0,0 +1,8 @@
{
icon = " \0315\\\030 \031 \
\0304\031f _ \030 \031c/\0315\\\
\0304 ",
title = "Miner",
category = "Apps",
run = "simpleMiner.lua",
}

View File

@@ -0,0 +1,8 @@
{
icon = "\0318/\030f\031 \030 \0318\\\
\030f \0308\0319o\030f\031 \
\0318\\\030f\031 \030 \0318/",
title = "Activity",
category = "Apps",
run = "storageActivity.lua",
}

View File

@@ -0,0 +1,8 @@
{
icon = "\0307 \
\0307 \0308\0311 \0305 \0308\031 \0307 \0308 \0301 \
\0307 ",
title = "Storage",
category = "Apps",
run = "storageManager.lua",
}

View File

@@ -0,0 +1,8 @@
{
icon = " \0314>\0310_\
\031f)))\031 \
\0314>\0310_\031 ",
title = "Telnet",
category = "Apps",
run = "telnet.lua",
}

View File

@@ -0,0 +1,8 @@
{
icon = "\0301\03171\03180\030 \031 \
\0301\03181\030 \031 \
\0301\03170\03180\03171\0307\031f>",
title = "Update",
category = "System",
run = "update.lua",
}

8
sys/apps/.overview/vnc Normal file
View File

@@ -0,0 +1,8 @@
{
icon = "\
\031e\\\031 \031e/\031dn\
\031e\\/\031 \0319c",
title = "VNC",
category = "Apps",
run = "vnc.lua",
}

8
sys/apps/.overview/worm Normal file
View File

@@ -0,0 +1,8 @@
{
icon = "\030d \030 \030e \030 \
\030d \030 \
\030d ",
title = "Worm",
category = "Games",
run = "/rom/programs/fun/worm",
}

386
sys/apps/Appstore.lua Normal file
View File

@@ -0,0 +1,386 @@
require = requireInjector(getfenv(1))
local Util = require('util')
local class = require('class')
local UI = require('ui')
local Event = require('event')
local sandboxEnv = Util.shallowCopy(getfenv(1))
setmetatable(sandboxEnv, { __index = _G })
multishell.setTitle(multishell.getCurrent(), 'App Store')
UI:configure('Appstore', ...)
local sources = {
{ text = "STD Default",
event = 'source',
url = "http://pastebin.com/raw/zVws7eLq" }, --stock
{ text = "Discover",
event = 'source',
generateName = true,
url = "http://pastebin.com/raw/9bXfCz6M" }, --owned by dannysmc95
{ text = "Opus",
event = 'source',
url = "http://pastebin.com/raw/ajQ91Rmn" },
}
shell.setDir('/apps')
function downloadApp(app)
local h
if type(app.url) == "table" then
h = contextualGet(app.url[1])
else
h = http.get(app.url)
end
if h then
local contents = h.readAll()
h:close()
return contents
end
end
function runApp(app, checkExists, ...)
local path, fn
local args = { ... }
if checkExists and fs.exists(fs.combine('/apps', app.name)) then
path = fs.combine('/apps', app.name)
else
local program = downloadApp(app)
fn = function()
if not program then
error('Failed to download')
end
local fn = loadstring(program, app.name)
if not fn then
error('Failed to download')
end
setfenv(fn, sandboxEnv)
fn(unpack(args))
end
end
multishell.openTab({
title = app.name,
env = sandboxEnv,
path = path,
fn = fn,
focused = true,
})
return true, 'Running program'
end
local installApp = function(app)
local program = downloadApp(app)
if not program then
return false, "Failed to download"
end
local fullPath = fs.combine('/apps', app.name)
Util.writeFile(fullPath, program)
return true, 'Installed as ' .. fullPath
end
local viewApp = function(app)
local program = downloadApp(app)
if not program then
return false, "Failed to download"
end
Util.writeFile('/.source', program)
shell.openForegroundTab('edit /.source')
return true
end
local getSourceListing = function(source)
local contents = http.get(source.url)
if contents then
local fn = loadstring(contents.readAll(), source.text)
contents.close()
local env = { std = { } }
setmetatable(env, { __index = _G })
setfenv(fn, env)
fn()
if env.contextualGet then
contextualGet = env.contextualGet
end
source.storeURLs = env.std.storeURLs
source.storeCatagoryNames = env.std.storeCatagoryNames
if source.storeURLs and source.storeCatagoryNames then
for k,v in pairs(source.storeURLs) do
if source.generateName then
v.name = v.title:match('(%w+)')
if not v.name or #v.name == 0 then
v.name = tostring(k)
else
v.name = v.name:lower()
end
else
v.name = k
end
v.categoryName = source.storeCatagoryNames[v.catagory]
v.ltitle = v.title:lower()
end
end
end
end
local appPage = UI.Page({
backgroundColor = UI.ViewportWindow.defaults.backgroundColor,
menuBar = UI.MenuBar({
showBackButton = not os.isPocket(),
buttons = {
{ text = 'Install', event = 'install' },
{ text = 'Run', event = 'run' },
{ text = 'View', event = 'view' },
{ text = 'Remove', event = 'uninstall', name = 'removeButton' },
},
}),
container = UI.Window({
x = 2,
y = 3,
height = UI.term.height - 3,
width = UI.term.width - 2,
viewport = UI.ViewportWindow(),
}),
notification = UI.Notification(),
accelerators = {
q = 'back',
backspace = 'back',
},
})
function appPage.container.viewport:draw()
local app = self.parent.parent.app
local str = string.format(
'By: %s\nCategory: %s\nFile name: %s\n\n%s',
app.creator, app.categoryName, app.name, app.description)
self:clear()
local y = self:wrappedWrite(1, 1, app.title, self.width, nil, colors.yellow)
self.height = self:wrappedWrite(1, y, str, self.width)
if appPage.notification.enabled then
appPage.notification:draw()
end
end
function appPage:enable(source, app)
self.source = source
self.app = app
UI.Page.enable(self)
self.container.viewport:setScrollPosition(0)
if fs.exists(fs.combine('/apps', app.name)) then
self.menuBar.removeButton:enable('Remove')
else
self.menuBar.removeButton:disable('Remove')
end
end
function appPage:eventHandler(event)
if event.type == 'back' then
UI:setPreviousPage()
elseif event.type == 'run' then
self.notification:info('Running program', 3)
self:sync()
runApp(self.app, true)
elseif event.type == 'view' then
self.notification:info('Downloading program', 3)
self:sync()
viewApp(self.app)
elseif event.type == 'uninstall' then
if self.app.runOnly then
s,m = runApp(self.app, false, 'uninstall')
else
fs.delete(fs.combine('/apps', self.app.name))
self.notification:success("Uninstalled " .. self.app.name, 3)
self:focusFirst(self)
self.menuBar.removeButton:disable('Remove')
self.menuBar:draw()
os.unregisterApp(fs.combine('/apps', self.app.name))
end
elseif event.type == 'install' then
self.notification:info("Installing", 3)
self:sync()
local s, m
if self.app.runOnly then
s,m = runApp(self.app, false)
else
s,m = installApp(self.app)
end
if s then
self.notification:success(m, 3)
if not self.app.runOnly then
self.menuBar.removeButton:enable('Remove')
self.menuBar:draw()
local category = 'Apps'
if self.app.catagoryName == 'Game' then
category = 'Games'
end
os.registerApp({
run = fs.combine('/apps', self.app.name),
title = self.app.title,
category = category,
icon = self.app.icon,
})
end
else
self.notification:error(m, 3)
end
else
return UI.Page.eventHandler(self, event)
end
return true
end
local categoryPage = UI.Page({
menuBar = UI.MenuBar({
buttons = {
{ text = 'Catalog', event = 'dropdown', dropdown = 'sourceMenu' },
{ text = 'Category', event = 'dropdown', dropdown = 'categoryMenu' },
},
}),
sourceMenu = UI.DropMenu({
buttons = sources,
}),
grid = UI.ScrollingGrid({
y = 2,
height = UI.term.height - 2,
columns = {
{ heading = 'Title', key = 'title' },
},
sortColumn = 'title',
autospace = true,
}),
statusBar = UI.StatusBar(),
accelerators = {
l = 'lua',
q = 'quit',
},
})
function categoryPage:setCategory(source, name, index)
self.grid.values = { }
for k,v in pairs(source.storeURLs) do
if index == 0 or index == v.catagory then
table.insert(self.grid.values, v)
end
end
self.statusBar:setStatus(string.format('%s: %s', source.text, name))
self.grid:update()
self.grid:setIndex(1)
end
function categoryPage:setSource(source)
if not source.categoryMenu then
self.statusBar:setStatus('Loading...')
self.statusBar:draw()
self:sync()
getSourceListing(source)
if not source.storeURLs then
error('Unable to download application list')
end
local buttons = { }
for k,v in Util.spairs(source.storeCatagoryNames,
function(a, b) return a:lower() < b:lower() end) do
if v ~= 'Operating System' then
table.insert(buttons, {
text = v,
event = 'category',
index = k,
})
end
end
source.categoryMenu = UI.DropMenu({
y = 2,
x = 1,
buttons = buttons,
})
source.index, source.name = Util.first(source.storeCatagoryNames)
categoryPage:add({
categoryMenu = source.categoryMenu
})
end
self.source = source
self.categoryMenu = source.categoryMenu
categoryPage:setCategory(source, source.name, source.index)
end
function categoryPage.grid:sortCompare(a, b)
return a.ltitle < b.ltitle
end
function categoryPage.grid:getRowTextColor(row, selected)
if fs.exists(fs.combine('/apps', row.name)) then
return colors.orange
end
return UI.Grid:getRowTextColor(row, selected)
end
function categoryPage:eventHandler(event)
if event.type == 'grid_select' or event.type == 'select' then
UI:setPage(appPage, self.source, self.grid:getSelected())
elseif event.type == 'category' then
self:setCategory(self.source, event.button.text, event.button.index)
self:setFocus(self.grid)
self:draw()
elseif event.type == 'source' then
self:setFocus(self.grid)
self:setSource(event.button)
self:draw()
elseif event.type == 'quit' then
Event.exitPullEvents()
else
return UI.Page.eventHandler(self, event)
end
return true
end
print("Retrieving catalog list")
categoryPage:setSource(sources[1])
UI:setPage(categoryPage)
Event.pullEvents()
UI.term:reset()

118
sys/apps/Events.lua Normal file
View File

@@ -0,0 +1,118 @@
require = requireInjector(getfenv(1))
local Event = require('event')
local UI = require('ui')
multishell.setTitle(multishell.getCurrent(), 'Events')
UI:configure('Events', ...)
local page = UI.Page({
menuBar = UI.MenuBar({
buttons = {
{ text = 'Filter', event = 'filter' },
{ text = 'Reset', event = 'reset' },
{ text = 'Pause ', event = 'toggle', name = 'pauseButton' },
},
}),
grid = UI.Grid({
y = 2,
columns = {
{ heading = 'Event', key = 'event' },
{ key = 'p1' },
{ key = 'p2' },
{ key = 'p3' },
{ key = 'p4' },
{ key = 'p5' },
},
autospace = true,
}),
accelerators = {
f = 'filter',
p = 'toggle',
r = 'reset',
c = 'clear',
q = 'quit',
},
filtered = { },
})
function page:eventHandler(event)
if event.type == 'filter' then
local entry = self.grid:getSelected()
self.filtered[entry.event] = true
elseif event.type == 'toggle' then
self.paused = not self.paused
if self.paused then
self.menuBar.pauseButton.text = 'Resume'
else
self.menuBar.pauseButton.text = 'Pause '
end
self.menuBar:draw()
elseif event.type == 'reset' then
self.filtered = { }
self.grid:setValues({ })
self.grid:draw()
if self.paused then
self:emit({ type = 'toggle' })
end
elseif event.type == 'clear' then
self.grid:setValues({ })
self.grid:draw()
elseif event.type == 'quit' then
Event.exitPullEvents()
elseif event.type == 'focus_change' then
if event.focused == self.grid then
if not self.paused then
self:emit({ type = 'toggle' })
end
end
else
return UI.Page.eventHandler(self, event)
end
return true
end
function page.grid:draw()
self:adjustWidth()
UI.Grid.draw(self)
end
function eventLoop()
local function tovalue(s)
if type(s) == 'table' then
return 'table'
end
return s
end
while true do
local e = { os.pullEvent() }
if not page.paused and not page.filtered[e[1]] then
table.insert(page.grid.values, 1, {
event = e[1],
p1 = tovalue(e[2]),
p2 = tovalue(e[3]),
p3 = tovalue(e[4]),
p4 = tovalue(e[5]),
p5 = tovalue(e[6]),
})
if #page.grid.values > page.grid.height - 1 then
table.remove(page.grid.values, #page.grid.values)
end
page.grid:update()
page.grid:draw()
page:sync()
end
end
end
UI:setPage(page)
Event.pullEvents(eventLoop)
UI.term:reset()

414
sys/apps/Files.lua Normal file
View File

@@ -0,0 +1,414 @@
require = requireInjector(getfenv(1))
local Util = require('util')
local Event = require('event')
local UI = require('ui')
local Config = require('config')
local cleanEnv = Util.shallowCopy(getfenv(1))
cleanEnv.require = nil
multishell.setTitle(multishell.getCurrent(), 'Files')
UI:configure('Files', ...)
local config = {
showHidden = false,
showDirSizes = false,
}
Config.load('Files', config)
local copied = { }
local marked = { }
local directories = { }
local cutMode = false
function formatSize(size)
if size >= 1000000 then
return string.format('%dM', math.floor(size/1000000, 2))
elseif size >= 1000 then
return string.format('%dK', math.floor(size/1000, 2))
end
return size
end
local Browser = UI.Page {
menuBar = UI.MenuBar {
buttons = {
{ text = '^-', event = 'updir' },
{ text = 'File', event = 'dropdown', dropdown = 'fileMenu' },
{ text = 'Edit', event = 'dropdown', dropdown = 'editMenu' },
{ text = 'View', event = 'dropdown', dropdown = 'viewMenu' },
},
},
fileMenu = UI.DropMenu {
buttons = {
{ text = 'Run', event = 'run' },
{ text = 'Edit e', event = 'edit' },
{ text = 'Shell s', event = 'shell' },
UI.Text { value = ' ------------ ' },
{ text = 'Quit q', event = 'quit' },
UI.Text { },
}
},
editMenu = UI.DropMenu {
buttons = {
{ text = 'Cut ^x', event = 'cut' },
{ text = 'Copy ^c', event = 'copy' },
{ text = 'Paste ^v', event = 'paste' },
UI.Text { value = ' --------------- ' },
{ text = 'Mark m', event = 'mark' },
{ text = 'Unmark all u', event = 'unmark' },
UI.Text { value = ' --------------- ' },
{ text = 'Delete del', event = 'delete' },
UI.Text { },
}
},
viewMenu = UI.DropMenu {
buttons = {
{ text = 'Refresh r', event = 'refresh' },
{ text = 'Hidden ^h', event = 'toggle_hidden' },
{ text = 'Dir Size ^s', event = 'toggle_dirSize' },
UI.Text { },
}
},
grid = UI.ScrollingGrid {
columns = {
{ heading = 'Name', key = 'name', width = UI.term.width-11 },
{ key = 'flags', width = 2 },
{ heading = 'Size', key = 'fsize', width = 6 },
},
sortColumn = 'name',
y = 2,
height = UI.term.height-2,
},
statusBar = UI.StatusBar {
columns = {
{ '', 'status', UI.term.width - 8 },
--{ '', 'info', 10 },
{ 'Size: ', 'totalSize', 8 },
},
},
accelerators = {
q = 'quit',
e = 'edit',
s = 'shell',
r = 'refresh',
space = 'mark',
backspace = 'updir',
m = 'move',
u = 'unmark',
d = 'delete',
delete = 'delete',
[ 'control-h' ] = 'toggle_hidden',
[ 'control-x' ] = 'cut',
[ 'control-c' ] = 'copy',
paste = 'paste',
},
}
function Browser:enable()
UI.Page.enable(self)
self:setFocus(self.grid)
end
function Browser.grid:sortCompare(a, b)
if self.sortColumn == 'fsize' then
return a.size < b.size
elseif self.sortColumn == 'flags' then
return a.flags < b.flags
end
if a.isDir == b.isDir then
return a.name:lower() < b.name:lower()
end
return a.isDir
end
function Browser.grid:getRowTextColor(file, selected)
if file.marked then
return colors.green
end
if file.isDir then
return colors.cyan
end
if file.isReadOnly then
return colors.pink
end
return colors.white
end
function Browser.grid:getRowBackgroundColorX(file, selected)
if selected then
return colors.gray
end
return self.backgroundColor
end
function Browser.grid:eventHandler(event)
if event.type == 'copy' then -- let copy be handled by parent
return false
end
return UI.Grid.eventHandler(self, event)
end
function Browser.statusBar:draw()
if self.parent.dir then
local info = '#:' .. Util.size(self.parent.dir.files)
local numMarked = Util.size(marked)
if numMarked > 0 then
info = info .. ' M:' .. numMarked
end
self:setValue('info', info)
self:setValue('totalSize', formatSize(self.parent.dir.totalSize))
UI.StatusBar.draw(self)
end
end
function Browser:setStatus(status, ...)
self.statusBar:timedStatus(string.format(status, ...))
end
function Browser:unmarkAll()
for k,m in pairs(marked) do
m.marked = false
end
Util.clear(marked)
end
function Browser:getDirectory(directory)
local s, dir = pcall(function()
local dir = directories[directory]
if not dir then
dir = {
name = directory,
size = 0,
files = { },
totalSize = 0,
index = 1
}
directories[directory] = dir
end
self:updateDirectory(dir)
return dir
end)
return s, dir
end
function Browser:updateDirectory(dir)
dir.size = 0
dir.totalSize = 0
Util.clear(dir.files)
local files = fs.list(dir.name, true)
if files then
dir.size = #files
for _, file in pairs(files) do
file.fullName = fs.combine(dir.name, file.name)
file.directory = directory
file.flags = ''
if not file.isDir then
dir.totalSize = dir.totalSize + file.size
file.fsize = formatSize(file.size)
else
if config.showDirSizes then
file.size = fs.getSize(file.fullName, true)
dir.totalSize = dir.totalSize + file.size
file.fsize = formatSize(file.size)
end
file.flags = 'D'
end
if file.isReadOnly then
file.flags = file.flags .. 'R'
end
if config.showHidden or file.name:sub(1, 1) ~= '.' then
dir.files[file.fullName] = file
end
end
end
-- self.grid:update()
-- self.grid:setIndex(dir.index)
self.grid:setValues(dir.files)
end
function Browser:setDir(dirName, noStatus)
self:unmarkAll()
if self.dir then
self.dir.index = self.grid:getIndex()
end
DIR = fs.combine('', dirName)
shell.setDir(DIR)
local s, dir = self:getDirectory(DIR)
if s then
self.dir = dir
elseif noStatus then
error(dir)
else
self:setStatus(dir)
self:setDir('', true)
return
end
if not noStatus then
self.statusBar:setValue('status', '/' .. self.dir.name)
self.statusBar:draw()
end
self.grid:setIndex(self.dir.index)
end
function Browser:run(path, ...)
local tabId = multishell.launch(cleanEnv, path, ...)
multishell.setFocus(tabId)
end
function Browser:hasMarked()
if Util.size(marked) == 0 then
local file = self.grid:getSelected()
if file then
file.marked = true
marked[file.fullName] = file
self.grid:draw()
end
end
return Util.size(marked) > 0
end
function Browser:eventHandler(event)
local file = self.grid:getSelected()
if event.type == 'quit' then
Event.exitPullEvents()
elseif event.type == 'edit' and file then
self:run('/apps/shell', 'edit', file.name)
elseif event.type == 'shell' then
self:run('/apps/shell')
elseif event.type == 'refresh' then
self:updateDirectory(self.dir)
self.grid:draw()
self:setStatus('Refreshed')
elseif event.type == 'toggle_hidden' then
config.showHidden = not config.showHidden
Config.update('Files', config)
self:updateDirectory(self.dir)
self.grid:draw()
if not config.showHidden then
self:setStatus('Hiding hidden')
else
self:setStatus('Displaying hidden')
end
elseif event.type == 'toggle_dirSize' then
config.showDirSizes = not config.showDirSizes
Config.update('Files', config)
self:updateDirectory(self.dir)
self.grid:draw()
if config.showDirSizes then
self:setStatus('Displaying dir sizes')
end
elseif event.type == 'mark' and file then
file.marked = not file.marked
if file.marked then
marked[file.fullName] = file
else
marked[file.fullName] = nil
end
self.grid:draw()
self.statusBar:draw()
elseif event.type == 'unmark' then
self:unmarkAll()
self.grid:draw()
self:setStatus('Marked files cleared')
elseif event.type == 'grid_select' or event.type == 'run' then
if file then
if file.isDir then
self:setDir(file.fullName)
else
self:run('/apps/shell', file.name)
end
end
elseif event.type == 'updir' then
local dir = (self.dir.name:match("(.*/)"))
self:setDir(dir or '/')
elseif event.type == 'delete' then
if self:hasMarked() then
local width = self.statusBar:getColumnWidth('status')
self.statusBar:setColumnWidth('status', UI.term.width)
self.statusBar:setValue('status', 'Delete marked? (y/n)')
self.statusBar:draw()
self.statusBar:sync()
local _, ch = os.pullEvent('char')
if ch == 'y' or ch == 'Y' then
for k,m in pairs(marked) do
pcall(function()
fs.delete(m.fullName)
end)
end
end
marked = { }
self.statusBar:setColumnWidth('status', width)
self.statusBar:setValue('status', '/' .. self.dir.name)
self:updateDirectory(self.dir)
self.statusBar:draw()
self.grid:draw()
self:setFocus(self.grid)
end
elseif event.type == 'copy' or event.type == 'cut' then
if self:hasMarked() then
cutMode = event.type == 'cut'
Util.clear(copied)
Util.merge(copied, marked)
--self:unmarkAll()
self.grid:draw()
self:setStatus('Copied %d file(s)', Util.size(copied))
end
elseif event.type == 'paste' then
for k,m in pairs(copied) do
local s, m = pcall(function()
if cutMode then
fs.move(m.fullName, fs.combine(self.dir.name, m.name))
else
fs.copy(m.fullName, fs.combine(self.dir.name, m.name))
end
end)
end
self:updateDirectory(self.dir)
self.grid:draw()
self:setStatus('Pasted ' .. Util.size(copied) .. ' file(s)')
else
return UI.Page.eventHandler(self, event)
end
self:setFocus(self.grid)
return true
end
--[[-- Startup logic --]]--
local args = { ... }
Browser:setDir(args[1] or shell.dir())
UI:setPage(Browser)
Event.pullEvents()
UI.term:reset()

84
sys/apps/Help.lua Normal file
View File

@@ -0,0 +1,84 @@
require = requireInjector(getfenv(1))
local Event = require('event')
local UI = require('ui')
multishell.setTitle(multishell.getCurrent(), 'Help')
UI:configure('Help', ...)
local files = { }
for _,f in pairs(fs.list('/rom/help')) do
table.insert(files, { name = f })
end
local page = UI.Page({
labelText = UI.Text({
y = 2,
x = 3,
value = 'Search',
}),
filter = UI.TextEntry({
y = 2,
x = 10,
width = UI.term.width - 13,
limit = 32,
}),
grid = UI.ScrollingGrid({
y = 4,
height = UI.term.height - 4,
values = files,
columns = {
{ heading = 'Name', key = 'name', width = 12 },
},
sortColumn = 'name',
}),
statusBar = UI.StatusBar(),
accelerators = {
q = 'quit',
},
})
local function showHelp(name)
UI.term:reset()
shell.run('help ' .. name)
print('Press enter to return')
read()
end
function page:eventHandler(event)
if event.type == 'quit' then
Event.exitPullEvents()
elseif event.type == 'key' and event.key == 'enter' then
showHelp(self.grid:getSelected().name)
self:setFocus(self.filter)
self:draw()
elseif event.type == 'grid_select' then
showHelp(event.selected.name)
self:setFocus(self.filter)
self:draw()
elseif event.type == 'text_change' then
local text = event.text
if #text == 0 then
self.grid.values = files
else
self.grid.values = { }
for _,f in pairs(files) do
if string.find(f.name, text) then
table.insert(self.grid.values, f)
end
end
end
self.grid:update()
self.grid:setIndex(1)
self.grid:draw()
else
UI.Page.eventHandler(self, event)
end
end
UI:setPage(page)
Event.pullEvents()
UI.term:reset()

314
sys/apps/Lua.lua Normal file
View File

@@ -0,0 +1,314 @@
local injector = requireInjector or load(http.get('http://pastebin.com/raw/c0TWsScv').readAll())()
require = injector(getfenv(1))
local Util = require('util')
local UI = require('ui')
local Event = require('event')
local History = require('history')
local sandboxEnv = Util.shallowCopy(getfenv(1))
sandboxEnv.exit = function() Event.exitPullEvents() end
sandboxEnv.require = injector(sandboxEnv)
setmetatable(sandboxEnv, { __index = _G })
multishell.setTitle(multishell.getCurrent(), 'Lua')
UI:configure('Lua', ...)
local command = ''
local history = History.load('usr/.lua_history', 25)
local page = UI.Page({
menuBar = UI.MenuBar({
buttons = {
{ text = 'Local', event = 'local' },
{ text = 'Global', event = 'global' },
{ text = 'Device', event = 'device', name = 'Device' },
},
}),
prompt = UI.TextEntry({
y = 2,
shadowText = 'enter command',
backgroundFocusColor = colors.black,
limit = 256,
accelerators = {
enter = 'command_enter',
up = 'history_back',
down = 'history_forward',
mouse_rightclick = 'clear_prompt',
-- [ 'control-space' ] = 'autocomplete',
},
}),
grid = UI.ScrollingGrid({
y = 3,
columns = {
{ heading = 'Key', key = 'name' },
{ heading = 'Value', key = 'value' },
},
sortColumn = 'name',
autospace = true,
}),
notification = UI.Notification(),
})
function page:setPrompt(value, focus)
self.prompt:setValue(value)
self.prompt.scroll = 0
self.prompt:setPosition(#value)
self.prompt:updateScroll()
if value:sub(-1) == ')' then
self.prompt:setPosition(#value - 1)
end
self.prompt:draw()
if focus then
page:setFocus(self.prompt)
end
end
function page:enable()
self:setFocus(self.prompt)
UI.Page.enable(self)
end
local function autocomplete(env, oLine, x)
local sLine = oLine:sub(1, x)
local nStartPos = sLine:find("[a-zA-Z0-9_%.]+$")
if nStartPos then
sLine = sLine:sub(nStartPos)
end
if #sLine > 0 then
local results = textutils.complete(sLine, env)
if #results == 0 then
-- setError('No completions available')
elseif #results == 1 then
return Util.insertString(oLine, results[1], x + 1)
elseif #results > 1 then
local prefix = results[1]
for n = 1, #results do
local result = results[n]
while #prefix > 0 do
if result:find(prefix, 1, true) == 1 then
break
end
prefix = prefix:sub(1, #prefix - 1)
end
end
if #prefix > 0 then
return Util.insertString(oLine, prefix, x + 1)
else
-- setStatus('Too many results')
end
end
end
return oLine
end
function page:eventHandler(event)
if event.type == 'global' then
self:setPrompt('', true)
self:executeStatement('getfenv(0)')
command = nil
elseif event.type == 'local' then
self:setPrompt('', true)
self:executeStatement('getfenv(1)')
command = nil
elseif event.type == 'autocomplete' then
local sz = #self.prompt.value
local pos = self.prompt.pos
self:setPrompt(autocomplete(sandboxEnv, self.prompt.value, self.prompt.pos))
self.prompt:setPosition(pos + #self.prompt.value - sz)
self.prompt:updateCursor()
elseif event.type == 'device' then
if not _G.device then
sandboxEnv.device = { }
for _,side in pairs(peripheral.getNames()) do
local key = string.format('%s:%s', peripheral.getType(side), side)
sandboxEnv.device[ key ] = peripheral.wrap(side)
end
end
self:setPrompt('device', true)
self:executeStatement('device')
elseif event.type == 'history_back' then
local value = history.back()
if value then
self:setPrompt(value)
end
elseif event.type == 'history_forward' then
self:setPrompt(history.forward() or '')
elseif event.type == 'clear_prompt' then
self:setPrompt('')
history.setPosition(#history.entries + 1)
elseif event.type == 'command_enter' then
local s = tostring(self.prompt.value)
if #s > 0 then
history.add(s)
self:executeStatement(s)
else
local t = { }
for k = #history.entries, 1, -1 do
table.insert(t, {
name = #t + 1,
value = history.entries[k],
isHistory = true,
pos = k,
})
end
history.setPosition(#history.entries + 1)
command = nil
self.grid:setValues(t)
self.grid:setIndex(1)
self.grid:adjustWidth()
self:draw()
end
return true
else
return UI.Page.eventHandler(self, event)
end
return true
end
function page:setResult(result)
local t = { }
local function safeValue(v)
local t = type(v)
if t == 'string' or t == 'number' then
return v
end
return tostring(v)
end
if type(result) == 'table' then
for k,v in pairs(result) do
local entry = {
name = safeValue(k),
rawName = k,
value = safeValue(v),
rawValue = v,
}
if type(v) == 'table' then
if Util.size(v) == 0 then
entry.value = 'table: (empty)'
else
entry.value = 'table'
end
end
table.insert(t, entry)
end
else
table.insert(t, {
name = type(result),
value = tostring(result),
rawValue = result,
})
end
self.grid:setValues(t)
self.grid:setIndex(1)
self.grid:adjustWidth()
self:draw()
end
function page.grid:eventHandler(event)
local entry = self:getSelected()
local function commandAppend()
if entry.isHistory then
history.setPosition(entry.pos)
return entry.value
end
if type(entry.rawValue) == 'function' then
if command then
return command .. '.' .. entry.name .. '()'
end
return entry.name .. '()'
end
if command then
if type(entry.rawName) == 'number' then
return command .. '[' .. entry.name .. ']'
end
if entry.name:match("%W") or
entry.name:sub(1, 1):match("%d") then
return command .. "['" .. tostring(entry.name) .. "']"
end
return command .. '.' .. entry.name
end
return entry.name
end
if event.type == 'grid_focus_row' then
if self.focused then
page:setPrompt(commandAppend())
end
elseif event.type == 'grid_select' then
page:setPrompt(commandAppend(), true)
page:executeStatement(commandAppend())
elseif event.type == 'copy' then
if entry then
clipboard.setData(entry.rawValue)
end
else
return UI.Grid.eventHandler(self, event)
end
return true
end
function page:rawExecute(s)
local fn, m = loadstring("return (" .. s .. ')', 'lua')
if not fn then
fn, m = loadstring(s, 'lua')
end
if fn then
setfenv(fn, sandboxEnv)
fn, m = pcall(fn)
end
return fn, m
end
function page:executeStatement(statement)
command = statement
local s, m = self:rawExecute(command)
if s and m then
self:setResult(m)
elseif s and type(m) == 'boolean' then
self:setResult(m)
else
self.grid:setValues({ })
self.grid:draw()
if m then
self.notification:error(m, 5)
end
end
end
local args = { ... }
if args[1] then
command = 'args[1]'
sandboxEnv.args = args
page:setResult(args[1])
end
UI:setPage(page)
Event.pullEvents()
UI.term:reset()

148
sys/apps/Network.lua Normal file
View File

@@ -0,0 +1,148 @@
require = requireInjector(getfenv(1))
local Event = require('event')
local UI = require('ui')
local Socket = require('socket')
multishell.setTitle(multishell.getCurrent(), 'Network')
UI:configure('Network', ...)
local gridColumns = {
{ heading = 'Label', key = 'label' },
{ heading = 'Dist', key = 'distance' },
{ heading = 'Status', key = 'status' },
}
if UI.term.width >= 30 then
table.insert(gridColumns, { heading = 'Fuel', key = 'fuel' })
table.insert(gridColumns, { heading = 'Uptime', key = 'uptime' })
end
local page = UI.Page {
menuBar = UI.MenuBar {
buttons = {
{ text = 'Telnet', event = 'telnet' },
{ text = 'VNC', event = 'vnc' },
{ text = 'Trust', event = 'trust' },
{ text = 'Reboot', event = 'reboot' },
},
},
grid = UI.ScrollingGrid {
y = 2,
values = network,
columns = gridColumns,
sortColumn = 'label',
autospace = true,
},
notification = UI.Notification { },
accelerators = {
q = 'quit',
c = 'clear',
},
}
local function sendCommand(host, command)
if not device.wireless_modem then
page.notification:error('Wireless modem not present')
return
end
page.notification:info('Connecting')
page:sync()
local socket = Socket.connect(host, 161)
if socket then
socket:write({ type = command })
socket:close()
page.notification:success('Command sent')
else
page.notification:error('Failed to connect')
end
end
function page:eventHandler(event)
local t = self.grid:getSelected()
if t then
if event.type == 'telnet' or event.type == 'grid_select' then
multishell.openTab({
path = '/apps/telnet.lua',
focused = true,
args = { t.id },
title = t.label,
})
elseif event.type == 'vnc' then
multishell.openTab({
path = '/apps/vnc.lua',
focused = true,
args = { t.id },
title = t.label,
})
elseif event.type == 'trust' then
shell.openForegroundTab('trust ' .. t.id)
elseif event.type == 'reboot' then
sendCommand(t.id, 'reboot')
elseif event.type == 'shutdown' then
sendCommand(t.id, 'shutdown')
end
end
if event.type == 'quit' then
Event.exitPullEvents()
end
UI.Page.eventHandler(self, event)
end
function page.grid:getRowTextColor(row, selected)
if not row.active then
return colors.orange
end
return UI.Grid.getRowTextColor(self, row, selected)
end
function page.grid:getDisplayValues(row)
row = Util.shallowCopy(row)
if row.uptime then
if row.uptime < 60 then
row.uptime = string.format("%ds", math.floor(row.uptime))
else
row.uptime = string.format("%sm", math.floor(row.uptime/6)/10)
end
end
if row.fuel then
row.fuel = Util.toBytes(row.fuel)
end
if row.distance then
row.distance = Util.round(row.distance, 1)
end
return row
end
Event.addThread(function()
while true do
page.grid:update()
page.grid:draw()
page:sync()
os.sleep(1)
end
end)
Event.addHandler('device_attach', function(h, deviceName)
if deviceName == 'wireless_modem' then
page.notification:success('Modem connected')
page:sync()
end
end)
Event.addHandler('device_detach', function(h, deviceName)
if deviceName == 'wireless_modem' then
page.notification:error('Wireless modem not attached')
page:sync()
end
end)
if not device.wireless_modem then
page.notification:error('Wireless modem not attached')
end
UI:setPage(page)
Event.pullEvents()
UI.term:reset()

478
sys/apps/Overview.lua Normal file
View File

@@ -0,0 +1,478 @@
require = requireInjector(getfenv(1))
local Util = require('util')
local Event = require('event')
local UI = require('ui')
local Config = require('config')
local NFT = require('nft')
local class = require('class')
local FileUI = require('fileui')
local Tween = require('tween')
multishell.setTitle(multishell.getCurrent(), 'Overview')
UI:configure('Overview', ...)
local config = {
Recent = { },
currentCategory = 'Apps',
}
local applications = { }
Config.load('Overview', config)
local function loadApplications()
Util.clear(applications)
local apps = fs.list('sys/apps/.overview')
for _,app in pairs(apps) do
local data = Util.readTable('sys/apps/.overview/' .. app)
if data then
data.filename = 'sys/apps/.overview/' .. app
table.insert(applications, data)
end
end
end
loadApplications()
local defaultIcon = NFT.parse([[
8071180
8007180
7180071]])
local sx, sy = term.current().getSize()
local maxRecent = math.ceil(sx * sy / 62)
local function elipse(s, len)
if #s > len then
s = s:sub(1, len - 2) .. '..'
end
return s
end
local buttons = { }
local categories = { }
table.insert(buttons, { text = 'Recent', event = 'category' })
for _,f in pairs(applications) do
if not categories[f.category] then
categories[f.category] = true
table.insert(buttons, { text = f.category, event = 'category' })
end
end
table.insert(buttons, { text = '+', event = 'new' })
local function parseIcon(iconText)
local icon
local s, m = pcall(function()
icon = NFT.parse(iconText)
if icon then
if icon.height > 3 or icon.width > 8 then
error('Invalid size')
end
end
return icon
end)
if s then
return icon
end
return s, m
end
local page = UI.Page {
tabBar = UI.TabBar {
buttons = buttons,
},
container = UI.ViewportWindow {
y = 2,
},
notification = UI.Notification(),
accelerators = {
r = 'refresh',
e = 'edit',
s = 'shell',
l = 'lua',
[ 'control-l' ] = 'refresh',
[ 'control-n' ] = 'new',
delete = 'delete',
},
}
function page:draw()
self.tabBar:draw()
self.container:draw()
end
UI.Icon = class(UI.Window)
function UI.Icon:init(args)
local defaults = {
UIElement = 'Icon',
width = 14,
height = 4,
}
UI.setProperties(defaults, args)
UI.Window.init(self, defaults)
end
function UI.Icon:eventHandler(event)
if event.type == 'mouse_click' then
self:setFocus(self.button)
return true
elseif event.type == 'mouse_doubleclick' then
self:emit({ type = self.button.event, button = self.button })
elseif event.type == 'mouse_rightclick' then
self:setFocus(self.button)
self:emit({ type = 'edit', button = self.button })
end
return UI.Window.eventHandler(self, event)
end
function page.container:setCategory(categoryName)
-- reset the viewport window
self.children = { }
self.offy = 0
local function filter(it, f)
local ot = { }
for _,v in pairs(it) do
if f(v) then
table.insert(ot, v)
end
end
return ot
end
local filtered
if categoryName == 'Recent' then
filtered = { }
for _,v in ipairs(config.Recent) do
local app = Util.find(applications, 'run', v)
if app and fs.exists(app.run) then
table.insert(filtered, app)
end
end
else
filtered = filter(applications, function(a)
return a.category == categoryName -- and fs.exists(a.run)
end)
table.sort(filtered, function(a, b) return a.title < b.title end)
end
for _,program in ipairs(filtered) do
local icon
if program.icon then
icon = parseIcon(program.icon)
end
if not icon then
icon = defaultIcon
end
local title = elipse(program.title, 8)
local width = math.max(icon.width + 2, #title + 2)
table.insert(self.children, UI.Icon({
width = width,
image = UI.NftImage({
x = math.floor((width - icon.width) / 2) + 1,
image = icon,
width = 5,
height = 3,
}),
button = UI.Button({
x = math.floor((width - #title - 2) / 2) + 1,
y = 4,
text = title,
backgroundColor = self.backgroundColor,
width = #title + 2,
event = 'button',
app = program,
}),
}))
end
local gutter = 2
if UI.term.width <= 26 then
gutter = 1
end
local col, row = gutter, 2
local count = #self.children
-- reposition all children
for k,child in ipairs(self.children) do
child.x = -10
child.y = math.floor(self.height)
child.tween = Tween.new(6, child, { x = col, y = row }, 'outSine')
if k < count then
col = col + child.width
if col + self.children[k + 1].width + gutter - 2 > UI.term.width then
col = gutter
row = row + 5
end
end
end
self:initChildren()
self.animate = true
end
function page.container:draw()
if self.animate then
self.animate = false
for i = 1, 6 do
for _,child in ipairs(self.children) do
child.tween:update(1)
child.x = math.floor(child.x)
child.y = math.floor(child.y)
end
UI.ViewportWindow.draw(self)
self:sync()
os.sleep()
end
else
UI.ViewportWindow.draw(self)
end
end
function page:refresh()
local pos = self.container.offy
self:focusFirst(self)
self.container:setCategory(config.currentCategory)
self.container:setScrollPosition(pos)
end
function page:resize()
self:refresh()
UI.Page.resize(self)
end
function page:eventHandler(event)
if event.type == 'category' then
self.tabBar:selectTab(event.button.text)
self.container:setCategory(event.button.text)
self.container:draw()
self:sync()
config.currentCategory = event.button.text
Config.update('Overview', config)
elseif event.type == 'button' then
for k,v in ipairs(config.Recent) do
if v == event.button.app.run then
table.remove(config.Recent, k)
break
end
end
table.insert(config.Recent, 1, event.button.app.run)
if #config.Recent > maxRecent then
table.remove(config.Recent, maxRecent + 1)
end
Config.update('Overview', config)
multishell.openTab({
path = 'sys/apps/shell',
args = { event.button.app.run },
focused = true,
})
elseif event.type == 'shell' then
multishell.openTab({
path = 'sys/apps/shell',
focused = true,
})
elseif event.type == 'lua' then
multishell.openTab({
path = 'sys/apps/Lua.lua',
focused = true,
})
elseif event.type == 'focus_change' then
if event.focused.parent.UIElement == 'Icon' then
event.focused.parent:scrollIntoView()
end
elseif event.type == 'tab_change' then
if event.current > event.last then
--self.container:setTransition(UI.effect.slideLeft)
else
--self.container:setTransition(UI.effect.slideRight)
end
elseif event.type == 'refresh' then
loadApplications()
self:refresh()
self:draw()
self.notification:success('Refreshed')
elseif event.type == 'delete' then
local focused = page:getFocused()
if focused.app then
fs.delete(focused.app.filename)
loadApplications()
page:refresh()
page:draw()
self.notification:success('Removed')
end
elseif event.type == 'new' then
local category = 'Apps'
if config.currentCategory ~= 'Recent' then
category = config.currentCategory or 'Apps'
end
UI:setPage('editor', { category = category })
elseif event.type == 'edit' then
local focused = page:getFocused()
if focused.app then
UI:setPage('editor', focused.app)
end
else
UI.Page.eventHandler(self, event)
end
return true
end
local formWidth = math.max(UI.term.width - 14, 26)
local editor = UI.Dialog {
height = 11,
width = formWidth,
title = 'Edit application',
form = UI.Form {
y = 2,
height = 9,
title = UI.TextEntry {
formLabel = 'Title', formKey = 'title', limit = 11, help = 'Application title',
required = true,
},
run = UI.TextEntry {
formLabel = 'Run', formKey = 'run', limit = 100, help = 'Full path to application',
required = true,
},
category = UI.TextEntry {
formLabel = 'Category', formKey = 'category', limit = 11, help = 'Category of application',
required = true,
},
loadIcon = UI.Button {
x = 11, y = 6,
text = 'Icon', event = 'loadIcon', help = 'Select icon'
},
image = UI.NftImage {
y = 6,
x = 2,
height = 3,
width = 8,
},
},
statusBar = UI.StatusBar(),
iconFile = '',
}
function editor:enable(app)
if app then
self.form:setValues(app)
local icon
if app.icon then
icon = parseIcon(app.icon)
end
self.form.image:setImage(icon)
end
UI.Page.enable(self)
self:focusFirst()
end
function editor.form.image:draw()
self:clear()
UI.NftImage.draw(self)
end
function editor:updateApplications(app)
if not app.filename then
app.filename = 'sys/apps/.overview/' .. app.title
end
Util.writeTable(app.filename, app)
loadApplications()
end
function editor:eventHandler(event)
if event.type == 'form_cancel' or event.type == 'cancel' then
UI:setPreviousPage()
elseif event.type == 'focus_change' then
self.statusBar:setStatus(event.focused.help or '')
self.statusBar:draw()
elseif event.type == 'loadIcon' then
local fileui = FileUI({
x = self.x,
y = self.y,
z = 2,
width = self.width,
height = self.height,
})
--fileui:setTransition(UI.effect.explode)
UI:setPage(fileui, fs.getDir(self.iconFile), function(fileName)
if fileName then
self.iconFile = fileName
local s, m = pcall(function()
local iconLines = Util.readFile(fileName)
if not iconLines then
error('Unable to load file')
end
local icon, m = parseIcon(iconLines)
if not icon then
error(m)
end
self.form.values.icon = iconLines
self.form.image:setImage(icon)
self.form.image:draw()
end)
if not s and m then
local msg = m:gsub('.*: (.*)', '%1')
page.notification:error(msg)
end
end
end)
elseif event.type == 'form_invalid' then
page.notification:error(event.message)
elseif event.type == 'form_complete' then
local values = self.form.values
UI:setPreviousPage()
self:updateApplications(values)
page:refresh()
page:draw()
else
return UI.Page.eventHandler(self, event)
end
return true
end
UI:setPages({
editor = editor,
main = page,
})
Event.addHandler('os_register_app', function()
loadApplications()
page:refresh()
page:draw()
page:sync()
end)
page.tabBar:selectTab(config.currentCategory or 'Apps')
page.container:setCategory(config.currentCategory or 'Apps')
UI:setPage(page)
Event.pullEvents()
UI.term:reset()

219
sys/apps/Peripherals.lua Normal file
View File

@@ -0,0 +1,219 @@
local injector = requireInjector or load(http.get('http://pastebin.com/raw/c0TWsScv').readAll())()
require = injector(getfenv(1))
local Util = require('util')
local Event = require('event')
local UI = require('ui')
multishell.setTitle(multishell.getCurrent(), 'Devices')
--[[ -- PeripheralsPage -- ]] --
local peripheralsPage = UI.Page {
grid = UI.ScrollingGrid {
columns = {
{ heading = 'Type', key = 'type' },
{ heading = 'Side', key = 'side' },
},
sortColumn = 'type',
height = UI.term.height - 1,
autospace = true,
},
statusBar = UI.StatusBar {
status = 'Select peripheral'
},
accelerators = {
q = 'quit',
},
}
function peripheralsPage.grid:draw()
local sides = peripheral.getNames()
Util.clear(self.values)
for _,side in pairs(sides) do
table.insert(self.values, {
type = peripheral.getType(side),
side = side
})
end
self:update()
self:adjustWidth()
UI.Grid.draw(self)
end
function peripheralsPage:updatePeripherals()
if UI:getCurrentPage() == self then
self.grid:draw()
self:sync()
end
end
function peripheralsPage:eventHandler(event)
if event.type == 'quit' then
Event.exitPullEvents()
elseif event.type == 'grid_select' then
UI:setPage('methods', event.selected)
end
return UI.Page.eventHandler(self, event)
end
--[[ -- MethodsPage -- ]] --
local methodsPage = UI.Page {
grid = UI.ScrollingGrid {
columns = {
{ heading = 'Name', key = 'name', width = UI.term.width }
},
sortColumn = 'name',
height = 7,
},
viewportConsole = UI.ViewportWindow {
y = 8,
height = UI.term.height - 8,
backgroundColor = colors.brown,
},
statusBar = UI.StatusBar {
status = 'q to return',
},
accelerators = {
q = 'back',
backspace = 'back',
},
}
function methodsPage:enable(p)
self.peripheral = p or self.peripheral
local p = peripheral.wrap(self.peripheral.side)
if p.getDocs then
self.grid.values = { }
for k,v in pairs(p.getDocs()) do
table.insert(self.grid.values, {
name = k,
doc = v,
})
end
elseif not p.getAdvancedMethodsData then
self.grid.values = { }
for name,f in pairs(p) do
table.insert(self.grid.values, {
name = name,
noext = true,
})
end
else
self.grid.values = p.getAdvancedMethodsData()
for name,f in pairs(self.grid.values) do
f.name = name
end
end
self.viewportConsole.offy = 0
self.grid:update()
self.grid:setIndex(1)
self.statusBar:setStatus(self.peripheral.type)
UI.Page.enable(self)
end
function methodsPage:eventHandler(event)
if event.type == 'back' then
UI:setPage(peripheralsPage)
return true
elseif event.type == 'grid_focus_row' then
self.viewportConsole.offy = 0
self.viewportConsole:draw()
end
return UI.Page.eventHandler(self, event)
end
function methodsPage.viewportConsole:draw()
local c = self
local method = methodsPage.grid:getSelected()
c:clear()
c:setCursorPos(1, 1)
if method.noext then
c.cursorY = 2
c:print('No extended Information')
return 2
end
if method.doc then
c:print(method.doc, nil, colors.yellow)
c.ymax = c.cursorY + 1
return
end
if method.description then
c:print(method.description)
end
c.cursorY = c.cursorY + 2
c.cursorX = 1
if method.returnTypes ~= '()' then
c:print(method.returnTypes .. ' ', nil, colors.yellow)
end
c:print(method.name, nil, colors.black)
c:print('(')
local maxArgLen = 1
for k,arg in ipairs(method.args) do
if #arg.description > 0 then
maxArgLen = math.max(#arg.name, maxArgLen)
end
local argName = arg.name
local fg = colors.green
if arg.optional then
argName = string.format('[%s]', arg.name)
fg = colors.orange
end
c:print(argName, nil, fg)
if k < #method.args then
c:print(', ')
end
end
c:print(')')
c.cursorY = c.cursorY + 1
if #method.args > 0 then
for _,arg in ipairs(method.args) do
if #arg.description > 0 then
c.cursorY = c.cursorY + 1
c.cursorX = 1
local fg = colors.green
if arg.optional then
fg = colors.orange
end
c:print(arg.name .. ': ', nil, fg)
c.cursorX = maxArgLen + 3
c:print(arg.description, nil, nil, maxArgLen + 3)
end
end
end
c.ymax = c.cursorY + 1
end
Event.addHandler('peripheral', function()
peripheralsPage:updatePeripherals()
end)
Event.addHandler('peripheral_detach', function()
peripheralsPage:updatePeripherals()
end)
UI:setPage(peripheralsPage)
UI:setPages({
methods = methodsPage,
})
Event.pullEvents()
UI.term:reset()

105
sys/apps/Pim.lua Normal file
View File

@@ -0,0 +1,105 @@
require = requireInjector(getfenv(1))
local Event = require('event')
local UI = require('ui')
local Config = require('config')
multishell.setTitle(multishell.getCurrent(), 'PIM')
local inventory = { }
local mode = 'sync'
if not device.pim then
error('PIM not attached')
end
local page = UI.Page({
menu = UI.Menu({
centered = true,
y = 2,
menuItems = {
{ prompt = 'Learn', event = 'learn', help = '' },
},
}),
statusBar = UI.StatusBar({
columns = {
{ 'Status', 'status', UI.term.width - 7 },
{ 'Mode', 'mode', 7 }
}
}),
accelerators = {
q = 'quit',
},
})
local function learn()
if device.pim.getInventorySize() > 0 then
local stacks = device.pim.getAllStacks(false)
Config.update('pim', stacks)
mode = 'sync'
page.statusBar:setValue('status', 'Learned inventory')
end
page.statusBar:setValue('mode', mode)
page.statusBar:draw()
end
function page:eventHandler(event)
if event.type == 'learn' then
mode = 'learn'
learn()
elseif event.type == 'quit' then
Event.exitPullEvents()
end
return UI.Page.eventHandler(self, event)
end
local function inInventory(s)
for _,i in pairs(inventory) do
if i.id == s.id then
return true
end
end
end
local function pimWatcher()
local playerOn = false
while true do
if device.pim.getInventorySize() > 0 and not playerOn then
playerOn = true
if mode == 'learn' then
learn()
else
local stacks = device.pim.getAllStacks(false)
for k,stack in pairs(stacks) do
if not inInventory(stack) then
device.pim.pushItem('down', k, stack.qty)
end
end
page.statusBar:setValue('status', 'Synchronized')
page.statusBar:draw()
end
elseif device.pim.getInventorySize() == 0 and playerOn then
page.statusBar:setValue('status', 'No player')
page.statusBar:draw()
playerOn = false
end
os.sleep(1)
end
end
Config.load('pim', inventory)
if Util.empty(inventory) then
mode = 'learn'
end
page.statusBar:setValue('mode', mode)
UI:setPage(page)
Event.pullEvents(pimWatcher)
UI.term:reset()

565
sys/apps/Script.lua Normal file
View File

@@ -0,0 +1,565 @@
require = requireInjector(getfenv(1))
local Event = require('event')
local UI = require('ui')
local Socket = require('socket')
local Config = require('config')
local GROUPS_PATH = 'usr/groups'
local SCRIPTS_PATH = 'usr/scripts'
multishell.setTitle(multishell.getCurrent(), 'Script')
UI:configure('script', ...)
local config = {
showGroups = false,
variables = [[{
COMPUTER_ID = os.getComputerID(),
}]],
}
Config.load('script', config)
local width = math.floor(UI.term.width / 2) - 1
if UI.term.width % 2 ~= 0 then
width = width + 1
end
function processVariables(script)
local fn = loadstring('return ' .. config.variables)
if fn then
local variables = fn()
for k,v in pairs(variables) do
local token = string.format('{%s}', k)
script = script:gsub(token, v)
end
end
return script
end
function invokeScript(computer, scriptName)
local script = Util.readFile(scriptName)
if not script then
print('Unable to read script file')
end
local socket = Socket.connect(computer.id, 161)
if not socket then
print('Unable to connect to ' .. computer.id)
return
end
script = processVariables(script)
Util.print('Running %s on %s', scriptName, computer.label)
socket:write({ type = 'script', args = script })
--[[
local response = socket:read(2)
if response and response.result then
if type(response.result) == 'table' then
print(textutils.serialize(response.result))
else
print(tostring(response.result))
end
else
printError('No response')
end
--]]
socket:close()
end
function runScript(computerOrGroup, scriptName)
if computerOrGroup.id then
invokeScript(computerOrGroup, scriptName)
else
local list = computerOrGroup.list
if computerOrGroup.path then
list = Util.readTable(computerOrGroup.path)
end
if list then
for _,computer in pairs(list) do
invokeScript(computer, scriptName)
end
end
end
end
local function getActiveComputers(t)
t = t or { }
Util.clear(t)
for k,computer in pairs(_G.network) do
if computer.active then
t[k] = computer
end
end
return t
end
local function getTurtleList()
local turtles = {
label = 'Turtles',
list = { },
}
for k,computer in pairs(getActiveComputers()) do
if computer.fuel then
turtles.list[k] = computer
end
end
return turtles
end
local args = { ... }
if #args == 2 then
local key = args[1]
local script = args[2]
local target
if tonumber(key) then
target = _G.network[tonumber(key)]
elseif key == 'All' then
target = {
list = Util.shallowCopy(getActiveComputers()),
}
elseif key == 'Localhost' then
target = { id = os.getComputerID() }
elseif key == 'Turtles' then
target = getTurtleList()
else
target = Util.readTable(fs.combine(GROUPS_PATH, key))
end
if not target then
error('Syntax: Script <ID or group> <script>')
end
runScript(target, fs.combine(SCRIPTS_PATH, script))
return
end
local function getListing(t, path)
Util.clear(t)
local files = fs.list(path)
for _,f in pairs(files) do
table.insert(t, { label = f, path = fs.combine(path, f) })
end
end
local mainPage = UI.Page({
menuBar = UI.MenuBar({
buttons = {
{ text = 'Groups', event = 'groups' },
{ text = 'Scripts', event = 'scripts' },
{ text = 'Toggle', event = 'toggle' },
},
}),
computers = UI.ScrollingGrid({
y = 2,
height = UI.term.height-3,
columns = {
{ heading = 'Label', key = 'label', width = width },
},
width = width,
sortColumn = 'label',
}),
scripts = UI.ScrollingGrid({
columns = {
{ heading = 'Name', key = 'label', width = width },
},
sortColumn = 'label',
height = UI.term.height - 3,
width = width,
x = UI.term.width - width + 1,
y = 2,
}),
statusBar = UI.StatusBar({
columns = {
{ '', 'status', 4 },
{ '', 'fuelF', 5 },
{ '', 'distanceF', 4 },
},
autospace = true,
}),
accelerators = {
q = 'quit',
},
})
local editorPage = UI.Page({
menuBar = UI.MenuBar({
showBackButton = true,
buttons = {
{ text = 'Save', event = 'save', help = 'Save this group' },
},
}),
grid1 = UI.ScrollingGrid({
columns = {
{ heading = 'Name', key = 'label', width = width },
},
sortColumn = 'label',
height = UI.term.height - 4,
width = width,
y = 3,
}),
right = UI.Button({
text = '>',
event = 'right',
x = width - 2,
y = 2,
width = 3,
}),
left = UI.Button({
text = '<',
event = 'left',
x = UI.term.width - width + 1,
y = 2,
width = 3,
}),
grid2 = UI.ScrollingGrid({
columns = {
{ heading = 'Name', key = 'label', width = width },
},
sortColumn = 'label',
height = UI.term.height - 4,
width = width,
x = UI.term.width - width + 1,
y = 3,
}),
statusBar = UI.StatusBar(),
accelerators = {
q = 'back',
},
})
local groupsPage = UI.Page({
menuBar = UI.MenuBar({
showBackButton = true,
buttons = {
{ text = 'Add', event = 'add' },
{ text = 'Edit', event = 'edit' },
{ text = 'Delete', event = 'delete' },
},
}),
grid = UI.ScrollingGrid({
y = 2,
height = UI.term.height-2,
columns = {
{ heading = 'Name', key = 'label' },
},
sortColumn = 'label',
autospace = true,
}),
statusBar = UI.StatusBar(),
accelerators = {
q = 'back',
},
})
local scriptsPage = UI.Page({
menuBar = UI.MenuBar({
showBackButton = true,
buttons = {
{ text = 'Add', event = 'add' },
{ text = 'Edit', event = 'edit' },
{ text = 'Delete', event = 'delete' },
},
}),
grid = UI.ScrollingGrid({
y = 2,
height = UI.term.height-2,
columns = {
{ heading = 'Name', key = 'label' },
},
sortColumn = 'label',
autospace = true,
}),
statusBar = UI.StatusBar(),
accelerators = {
a = 'add',
e = 'edit',
delete = 'delete',
q = 'back',
},
})
function editorPage:enable()
self:focusFirst()
local groupPath = fs.combine(GROUPS_PATH, self.groupName)
if fs.exists(groupPath) then
self.grid1.values = Util.readTable(groupPath)
else
Util.clear(self.grid1.values)
end
self.grid1:update()
UI.Page.enable(self)
end
function editorPage.grid2:draw()
getActiveComputers(self.values)
for k in pairs(editorPage.grid1.values) do
self.values[k] = nil
end
self:update()
UI.ScrollingGrid.draw(self)
end
function editorPage:eventHandler(event)
if event.type == 'back' then
UI:setPage(groupsPage)
elseif event.type == 'left' then
local computer = self.grid2:getSelected()
self.grid1.values[computer.id] = computer
self.grid1:update()
self.grid1:draw()
self.grid2:draw()
elseif event.type == 'right' then
local computer = self.grid1:getSelected()
self.grid1.values[computer.id] = nil
self.grid1:update()
self.grid1:draw()
self.grid2:draw()
elseif event.type == 'save' then
Util.writeTable(fs.combine(GROUPS_PATH, self.groupName), self.grid1.values)
UI:setPage(groupsPage)
end
return UI.Page.eventHandler(self, event)
end
local function nameDialog(f)
local dialog = UI.Dialog({
-- x = (UI.term.width - 28) / 2,
width = 22,
title = 'Enter Name',
form = UI.Form {
x = 2, rex = -2, y = 2,
textEntry = UI.TextEntry({ y = 3, width = 20, limit = 20 })
},
})
dialog.eventHandler = function(self, event)
if event.type == 'form_complete' then
local name = self.form.textEntry.value
if name then
f(name)
else
self.statusBar:timedStatus('Invalid Name', 3)
end
return true
elseif event.type == 'form_cancel' or event.type == 'cancel' then
UI:setPreviousPage()
else
return UI.Dialog.eventHandler(self, event)
end
end
dialog:setFocus(dialog.form.textEntry)
UI:setPage(dialog)
end
function groupsPage:draw()
getListing(self.grid.values, GROUPS_PATH)
self.grid:update()
UI.Page.draw(self)
end
function groupsPage:enable()
self:focusFirst()
UI.Page.enable(self)
end
function groupsPage:eventHandler(event)
if event.type == 'back' then
UI:setPage(mainPage)
elseif event.type == 'add' then
nameDialog(function(name)
editorPage.groupName = name
UI:setPage(editorPage)
end)
elseif event.type == 'delete' then
fs.delete(fs.combine(GROUPS_PATH, self.grid:getSelected().label))
self:draw()
elseif event.type == 'edit' then
editorPage.groupName = self.grid:getSelected().label
UI:setPage(editorPage)
end
return UI.Page.eventHandler(self, event)
end
function scriptsPage:draw()
getListing(self.grid.values, SCRIPTS_PATH)
self.grid:update()
UI.Page.draw(self)
end
function scriptsPage:enable()
self:focusFirst()
UI.Page.enable(self)
end
function scriptsPage:eventHandler(event)
if event.type == 'back' then
UI:setPreviousPage()
elseif event.type == 'add' then
nameDialog(function(name)
shell.run('edit ' .. fs.combine(SCRIPTS_PATH, name))
UI:setPreviousPage()
end)
elseif event.type == 'edit' then
local name = fs.combine(SCRIPTS_PATH, self.grid:getSelected().label)
shell.run('edit ' .. name)
self:draw()
elseif event.type == 'delete' then
local name = fs.combine(SCRIPTS_PATH, self.grid:getSelected().label)
fs.delete(name)
self:draw()
end
return UI.Page.eventHandler(self, event)
end
function mainPage:eventHandler(event)
if event.type == 'quit' then
Event.exitPullEvents()
elseif event.type == 'groups' then
UI:setPage(groupsPage)
elseif event.type == 'scripts' then
UI:setPage(scriptsPage)
elseif event.type == 'toggle' then
config.showGroups = not config.showGroups
local text = 'Computers'
if config.showGroups then
text = 'Groups'
end
-- self.statusBar.toggleButton.text = text
self:draw()
Config.update('script', config)
elseif event.type == 'grid_focus_row' then
local computer = self.computers:getSelected()
self.statusBar.values = { computer }
self.statusBar:draw()
elseif event.type == 'grid_select' then
local script = self.scripts:getSelected()
local computer = self.computers:getSelected()
self:clear()
self:sync()
self.enabled = false
runScript(computer, script.path)
print()
print('Press any key to continue...')
while true do
local e = os.pullEvent()
if e == 'char' or e == 'key' or e == 'mouse_click' then
break
end
end
self.enabled = true
self:draw()
end
return UI.Page.eventHandler(self, event)
end
function mainPage.statusBar:draw()
local computer = self.values[1]
if computer then
if computer.fuel then
computer.fuelF = string.format("%dk", math.floor(computer.fuel/1000))
end
if computer.distance then
computer.distanceF = Util.round(computer.distance, 1)
end
mainPage.statusBar:adjustWidth()
end
UI.StatusBar.draw(self)
end
function mainPage:draw()
getListing(self.scripts.values, SCRIPTS_PATH)
if config.showGroups then
getListing(self.computers.values, GROUPS_PATH)
table.insert(self.computers.values, {
label = 'All',
list = getActiveComputers(),
})
table.insert(self.computers.values, getTurtleList())
table.insert(self.computers.values, {
label = 'Localhost',
id = os.getComputerID(),
})
else
getActiveComputers(self.computers.values)
end
self.scripts:update()
self.computers:update()
UI.Page.draw(self)
end
if not fs.exists(SCRIPTS_PATH) then
fs.makeDir(SCRIPTS_PATH)
end
if not fs.exists(GROUPS_PATH) then
fs.makeDir(GROUPS_PATH)
end
Event.addHandler('network_attach', function()
if mainPage.enabled then
mainPage:draw()
end
end)
Event.addHandler('network_detach', function()
if mainPage.enabled then
mainPage:draw()
end
end)
function statusUpdate()
while true do
if mainPage.enabled then
local selected = mainPage.computers:getSelected()
if selected then
local computer = _G.network[selected.id]
mainPage.statusBar.values = { computer }
mainPage.statusBar:draw()
mainPage:sync()
end
end
os.sleep(1)
end
end
UI:setPage(mainPage)
Event.pullEvents(statusUpdate)
UI.term:reset()

186
sys/apps/System.lua Normal file
View File

@@ -0,0 +1,186 @@
require = requireInjector(getfenv(1))
local Event = require('event')
local UI = require('ui')
local Config = require('config')
multishell.setTitle(multishell.getCurrent(), 'System')
UI:configure('System', ...)
local env = {
path = shell.path(),
aliases = shell.aliases(),
lua_path = LUA_PATH,
}
Config.load('multishell', env)
UI.TextEntry.defaults.backgroundFocusColor = colors.black
local systemPage = UI.Page {
backgroundColor = colors.blue,
tabs = UI.Tabs {
pathTab = UI.Window {
tabTitle = 'Path',
entry = UI.TextEntry {
x = 2, y = 2, rex = -2,
limit = 256,
value = shell.path(),
shadowText = 'enter system path',
accelerators = {
enter = 'update_path',
},
},
grid = UI.Grid {
y = 4,
values = paths,
disableHeader = true,
columns = { { key = 'value' } },
autospace = true,
},
},
aliasTab = UI.Window {
tabTitle = 'Aliases',
alias = UI.TextEntry {
x = 2, y = 2, rex = -2,
limit = 32,
shadowText = 'Alias',
},
path = UI.TextEntry {
y = 3, x = 2, rex = -2,
limit = 256,
shadowText = 'Program path',
accelerators = {
enter = 'new_alias',
},
},
grid = UI.Grid {
y = 5,
values = aliases,
autospace = true,
sortColumn = 'alias',
columns = {
{ heading = 'Alias', key = 'alias' },
{ heading = 'Program', key = 'path' },
},
accelerators = {
delete = 'delete_alias',
},
},
},
infoTab = UI.Window {
tabTitle = 'Info',
labelText = UI.Text {
x = 3, y = 2,
value = 'Label'
},
label = UI.TextEntry {
x = 9, y = 2, rex = -4,
limit = 32,
value = os.getComputerLabel(),
backgroundFocusColor = colors.black,
accelerators = {
enter = 'update_label',
},
},
grid = UI.ScrollingGrid {
y = 4,
values = {
{ name = 'CC version', value = os.getVersion() },
{ name = 'Lua version', value = _VERSION },
{ name = 'MC version', value = _MC_VERSION or 'unknown' },
{ name = 'Disk free', value = Util.toBytes(fs.getFreeSpace('/')) },
{ name = 'Computer ID', value = tostring(os.getComputerID()) },
{ name = 'Day', value = tostring(os.day()) },
},
selectable = false,
backgroundColor = colors.blue,
columns = {
{ key = 'name', width = 12 },
{ key = 'value', width = UI.term.width - 15 },
},
},
},
},
notification = UI.Notification(),
accelerators = {
q = 'quit',
},
}
function systemPage.tabs.pathTab.grid:draw()
self.values = { }
for _,v in ipairs(Util.split(env.path, '(.-):')) do
table.insert(self.values, { value = v })
end
self:update()
UI.Grid.draw(self)
end
function systemPage.tabs.pathTab:eventHandler(event)
if event.type == 'update_path' then
env.path = self.entry.value
self.grid:setIndex(self.grid:getIndex())
self.grid:draw()
Config.update('multishell', env)
systemPage.notification:success('reboot to take effect')
return true
end
end
function systemPage.tabs.aliasTab.grid:draw()
self.values = { }
local aliases = { }
for k,v in pairs(env.aliases) do
table.insert(self.values, { alias = k, path = v })
end
self:update()
UI.Grid.draw(self)
end
function systemPage.tabs.aliasTab:eventHandler(event)
if event.type == 'delete_alias' then
env.aliases[self.grid:getSelected().alias] = nil
self.grid:setIndex(self.grid:getIndex())
self.grid:draw()
Config.update('multishell', env)
systemPage.notification:success('reboot to take effect')
return true
elseif event.type == 'new_alias' then
env.aliases[self.alias.value] = self.path.value
self.alias:reset()
self.path:reset()
self:draw()
self:setFocus(self.alias)
Config.update('multishell', env)
systemPage.notification:success('reboot to take effect')
return true
end
end
function systemPage.tabs.infoTab:eventHandler(event)
if event.type == 'update_label' then
os.setComputerLabel(self.label.value)
systemPage.notification:success('Label updated')
return true
end
end
function systemPage:eventHandler(event)
if event.type == 'quit' then
Event.exitPullEvents()
elseif event.type == 'tab_activate' then
event.activated:focusFirst()
else
return UI.Page.eventHandler(self, event)
end
return true
end
UI:setPage(systemPage)
Event.pullEvents()
UI.term:reset()

73
sys/apps/Tabs.lua Normal file
View File

@@ -0,0 +1,73 @@
require = requireInjector(getfenv(1))
local UI = require('ui')
local Event = require('event')
multishell.setTitle(multishell.getCurrent(), 'Tabs')
UI:configure('Tabs', ...)
local page = UI.Page {
menuBar = UI.MenuBar {
buttons = {
{ text = 'Activate', event = 'activate' },
{ text = 'Terminate', event = 'terminate' },
},
},
grid = UI.ScrollingGrid {
y = 2,
columns = {
{ heading = 'ID', key = 'tabId' },
{ heading = 'Title', key = 'title' },
{ heading = 'Status', key = 'status' },
{ heading = 'Time', key = 'timestamp' },
},
values = multishell.getTabs(),
sortColumn = 'title',
autospace = true,
},
accelerators = {
q = 'quit',
space = 'activate',
t = 'terminate',
},
}
function page:eventHandler(event)
local t = self.grid:getSelected()
if t then
if event.type == 'activate' or event.type == 'grid_select' then
multishell.setFocus(t.tabId)
elseif event.type == 'terminate' then
multishell.terminate(t.tabId)
end
end
if event.type == 'quit' then
Event.exitPullEvents()
end
UI.Page.eventHandler(self, event)
end
function page.grid:getDisplayValues(row)
row = Util.shallowCopy(row)
local elapsed = os.clock()-row.timestamp
if elapsed < 60 then
row.timestamp = string.format("%ds", math.floor(elapsed))
else
row.timestamp = string.format("%fm", math.floor(elapsed/6)/10)
end
if row.isDead then
row.status = 'error'
else
row.status = coroutine.status(row.co)
end
return row
end
Event.addTimer(1, true, function()
page.grid:update()
page.grid:draw()
page:sync()
end)
UI:setPage(page)
Event.pullEvents()
UI.term:reset()

328
sys/apps/Turtles.lua Normal file
View File

@@ -0,0 +1,328 @@
require = requireInjector(getfenv(1))
local UI = require('ui')
local Socket = require('socket')
local Terminal = require('terminal')
local TableDB = require('tableDB')
multishell.setTitle(multishell.getCurrent(), 'Turtles')
UI.Button.defaults.focusIndicator = ' '
UI:configure('Turtles', ...)
local options = {
turtle = { arg = 'i', type = 'number', value = -1,
desc = 'Turtle ID' },
tab = { arg = 's', type = 'string', value = 'inventory',
desc = 'Selected tab to display' },
help = { arg = 'h', type = 'flag', value = false,
desc = 'Displays the options' },
}
local SCRIPTS_PATH = '/apps/scripts'
local nullTerm = Terminal.getNullTerm(term.current())
local turtles = { }
local policies = {
{ label = 'none' },
{ label = 'digOnly' },
{ label = 'attackOnly' },
{ label = 'digAttack' },
{ label = 'turtleSafe' },
}
local itemInfoDB = TableDB({
fileName = 'items.db'
})
itemInfoDB:load()
local page = UI.Page {
moveUp = UI.Button {
x = 5, y = 2,
text = '/\\',
fn = 'turtle.up',
},
moveDown = UI.Button {
x = 5, y = 4,
text = '\\/',
fn = 'turtle.down',
},
moveForward = UI.Button {
x = 9, y = 3,
text = '>',
fn = 'turtle.forward',
},
moveBack = UI.Button {
x = 2, y = 3,
text = '<',
fn = 'turtle.back',
},
turnLeft = UI.Button {
x = 2, y = 6,
text = '<-',
fn = 'turtle.turnLeft',
},
turnRight = UI.Button {
x = 8, y = 6,
text = '->',
fn = 'turtle.turnRight',
},
--[[
policy = UI.Chooser {
x = 2, y = 8,
choices = {
{ name = ' None ', value = 'none' },
{ name = ' Safe ', value = 'turtleSafe' },
},
},
]]
coords = UI.Window {
x = 14, y = 2, height = 5, rex = -2,
},
tabs = UI.Tabs {
x = 1, y = 8, rey = -2,
scripts = UI.Grid {
tabTitle = 'Run',
backgroundColor = UI.TabBar.defaults.selectedBackgroundColor,
columns = {
{ heading = '', key = 'label' },
},
disableHeader = true,
sortColumn = 'label',
autospace = true,
},
turtles = UI.Grid {
tabTitle = 'Sel',
backgroundColor = UI.TabBar.defaults.selectedBackgroundColor,
columns = {
{ heading = 'label', key = 'label' },
{ heading = 'Dist', key = 'distance' },
{ heading = 'Status', key = 'status' },
{ heading = 'Fuel', key = 'fuel' },
},
disableHeader = true,
sortColumn = 'label',
autospace = true,
},
inventory = UI.Grid {
backgroundColor = UI.TabBar.defaults.selectedBackgroundColor,
tabTitle = 'Inv',
columns = {
{ heading = '', key = 'qty', width = 2 },
{ heading = 'Inventory', key = 'id', width = UI.term.width - 5 },
},
disableHeader = true,
sortColumn = 'index',
},
policy = UI.Grid {
tabTitle = 'Mod',
backgroundColor = UI.TabBar.defaults.selectedBackgroundColor,
columns = {
{ heading = 'label', key = 'label' },
},
values = policies,
disableHeader = true,
sortColumn = 'label',
autospace = true,
},
},
statusBar = UI.StatusBar(),
notification = UI.Notification(),
accelerators = {
q = 'quit',
},
}
function page:enable(turtle)
self.turtle = turtle
UI.Page.enable(self)
end
function page:runFunction(script, nowrap)
local socket = Socket.connect(self.turtle.id, 161)
if not socket then
self.notification:error('Unable to connect')
return
end
if not nowrap then
script = 'turtle.run(' .. script .. ')'
end
socket:write({ type = 'script', args = script })
socket:close()
end
function page:runScript(scriptName)
local cmd = string.format('Script %d %s', self.turtle.id, scriptName)
local ot = term.redirect(nullTerm)
pcall(function() shell.run(cmd) end)
term.redirect(ot)
end
function page.coords:draw()
local t = self.parent.turtle
if t then
self:clear()
self:setCursorPos(1, 1)
self:print(string.format('%s\nx: %d\ny: %d\nz: %d\nFuel: %s\n',
t.coordSystem, t.point.x, t.point.y, t.point.z, Util.toBytes(t.fuel)))
end
end
--[[ Inventory Tab ]]--
function page.tabs.inventory:getRowTextColor(row, selected)
if page.turtle and row.selected then
return colors.yellow
end
return UI.Grid.getRowTextColor(self, row, selected)
end
function page.tabs.inventory:draw()
local t = page.turtle
Util.clear(self.values)
if t then
for _,v in ipairs(t.inventory) do
if v.qty > 0 then
table.insert(self.values, v)
if v.index == t.slotIndex then
v.selected = true
end
if v.id then
local item = itemInfoDB:get({ v.id, v.dmg })
if item then
v.id = item.displayName
else
v.id = v.id:gsub('.*:(.*)', '%1')
end
end
end
end
end
self:adjustWidth()
self:update()
UI.Grid.draw(self)
end
function page.tabs.inventory:eventHandler(event)
if event.type == 'grid_select' then
local fn = string.format('turtle.select(%d)', event.selected.index)
page:runFunction(fn)
else
return UI.Grid.eventHandler(self, event)
end
return true
end
function page.tabs.scripts:draw()
Util.clear(self.values)
local files = fs.list(SCRIPTS_PATH)
for _,f in pairs(files) do
table.insert(self.values, { label = f, path = fs.combine(SCRIPTS_PATH, f) })
end
self:update()
UI.Grid.draw(self)
end
function page.tabs.scripts:eventHandler(event)
if event.type == 'grid_select' then
page:runScript(event.selected.label)
else
return UI.Grid.eventHandler(self, event)
end
return true
end
function page.tabs.turtles:getDisplayValues(row)
row = Util.shallowCopy(row)
if row.fuel then
row.fuel = Util.toBytes(row.fuel)
end
if row.distance then
row.distance = Util.round(row.distance, 1)
end
return row
end
function page.tabs.turtles:draw()
Util.clear(self.values)
for _,v in pairs(network) do
if v.fuel then
table.insert(self.values, v)
end
end
self:update()
UI.Grid.draw(self)
end
function page.tabs.turtles:eventHandler(event)
if event.type == 'grid_select' then
page.turtle = event.selected
else
return UI.Grid.eventHandler(self, event)
end
return true
end
function page.statusBar:draw()
local t = self.parent.turtle
if t then
local status = string.format('%s [ %s ]', t.status, Util.round(t.distance, 2))
self:setStatus(status, true)
end
UI.StatusBar.draw(self)
end
function page:eventHandler(event)
if event.type == 'quit' then
UI:exitPullEvents()
elseif event.type == 'button_press' then
if event.button.fn then
self:runFunction(event.button.fn, event.button.nowrap)
elseif event.button.script then
self:runScript(event.button.script)
end
else
return UI.Page.eventHandler(self, event)
end
return true
end
function page:enable()
UI.Page.enable(self)
-- self.tabs:activateTab(page.tabs.turtles)
end
local function updateThread()
while true do
if page.turtle then
local t = _G.network[page.turtle.id]
page.turtle = t
page:draw()
page:sync()
end
os.sleep(1)
end
end
if not Util.getOptions(options, { ... }, true) then
return
end
if options.turtle.value >= 0 then
for i = 1, 10 do
page.turtle = _G.network[options.turtle.value]
if page.turtle then
break
end
os.sleep(1)
end
end
UI:setPage(page)
page.tabs:activateTab(page.tabs[options.tab.value])
UI:pullEvents(updateThread)
UI.term:reset()

35
sys/apps/base64dl.lua Normal file
View File

@@ -0,0 +1,35 @@
require = requireInjector(getfenv(1))
Base64 = require('base64')
local args = { ... }
if not args[2] then
error('Syntax: base64dl <file name> <url>')
end
local c = http.get(args[2])
if not c then
error('unable to open url')
end
local data = c.readAll()
c.close()
print('size: ' .. #data)
local decoded = Base64.decode(data)
print('decoded: ' .. #decoded)
local file = io.open(shell.resolve(args[1]), "wb")
if not file then
error('Unable to open ' .. args[1], 2)
end
for k,b in ipairs(decoded) do
if (k % 1000) == 0 then
os.sleep(0)
end
file:write(b)
end
file:close()
print('done')

2155
sys/apps/builder.lua Normal file

File diff suppressed because it is too large Load Diff

24
sys/apps/cat.lua Normal file
View File

@@ -0,0 +1,24 @@
local args = { ... }
if #args < 1 then
error('cat <filename>')
end
local fileName = shell.resolve(args[1])
if not fs.exists(fileName) then
error('not a file: ' .. args[1])
end
local file = fs.open(fileName, 'r')
if not file then
error('unable to open ' .. args[1])
end
while true do
local line = file.readLine()
if not line then
break
end
print(line)
end
file.close()

1270
sys/apps/edit.lua Normal file

File diff suppressed because it is too large Load Diff

41
sys/apps/itemsDB.lua Normal file
View File

@@ -0,0 +1,41 @@
local injector = requireInjector or load(http.get('http://pastebin.com/raw/c0TWsScv').readAll())()
require = injector(getfenv(1))
local RefinedProvider = require('refinedProvider')
local TableDB = require('tableDB')
local controller = RefinedProvider()
if not controller:isValid() then
error('Refined storage controller not found')
end
local itemInfoDB = TableDB({
fileName = 'items.db'
})
itemInfoDB:load()
local items = controller:listItems()
local keys = {
'fields',
'damage',
'displayName',
'maxCount',
'maxDamage',
'name',
'nbtHash',
'rawName',
}
for _, item in pairs(items) do
local t = { }
for _,key in pairs(keys) do
t[key] = item[key]
end
itemInfoDB:add({ item.name, item.damage, item.nbtHash }, t)
end
itemInfoDB:flush()

102
sys/apps/logMonitor.lua Normal file
View File

@@ -0,0 +1,102 @@
require = requireInjector(getfenv(1))
local Event = require('event')
local Message = require('message')
local UI = require('ui')
multishell.setTitle(multishell.getCurrent(), 'Log Monitor')
if not device.wireless_modem then
error('Wireless modem is required')
end
device.wireless_modem.open(59998)
local ids = { }
local messages = { }
local terminal = UI.term
if device.openperipheral_bridge then
UI.Glasses = require('glasses')
terminal = UI.Glasses({
x = 4,
y = 175,
height = 40,
width = 64,
textScale = .5,
backgroundOpacity = .65,
})
elseif device.monitor then
terminal = UI.Device({
deviceType = 'monitor',
textScale = .5
})
end
terminal:clear()
function getClient(id)
if not ids[id] then
ids[id] = {
titleBar = UI.TitleBar({ title = 'ID: ' .. id, parent = terminal }),
scrollingText = UI.ScrollingText({ parent = terminal })
}
local clientCount = Util.size(ids)
local clientHeight = math.floor((terminal.height - clientCount) / clientCount)
terminal:clear()
local y = 1
for k,v in pairs(ids) do
v.titleBar.y = y
y = y + 1
v.scrollingText.height = clientHeight
v.scrollingText.y = y
y = y + clientHeight
v.scrollingText:clear()
v.titleBar:draw()
v.scrollingText:draw()
end
end
return ids[id]
end
local function logWriter()
while true do
os.pullEvent('logMessage')
local t = { }
while #messages > 0 do
local msg = messages[1]
table.remove(messages, 1)
local client = getClient(msg.id)
client.scrollingText:appendLine(string.format('%d %s', math.floor(os.clock()), msg.text))
t[msg.id] = client
end
for _,client in pairs(t) do
client.scrollingText:draw()
end
terminal:sync()
end
end
Message.addHandler('log', function(h, id, msg)
table.insert(messages, { id = id, text = msg.contents })
os.queueEvent('logMessage')
end)
Event.addHandler('monitor_touch', function()
terminal:reset()
ids = { }
end)
Event.addHandler('mouse_click', function()
terminal:reset()
ids = { }
end)
Event.addHandler('char', function()
Event.exitPullEvents()
end)
Event.pullEvents(logWriter)
terminal:reset()

24
sys/apps/mirror.lua Normal file
View File

@@ -0,0 +1,24 @@
require = requireInjector(getfenv(1))
local Terminal = require('terminal')
local args = { ... }
local mon = device[table.remove(args, 1) or 'monitor']
if not mon then
error('mirror: Invalid device')
end
mon.clear()
mon.setTextScale(.5)
mon.setCursorPos(1, 1)
local oterm = Terminal.copy(term.current())
Terminal.mirror(term.current(), mon)
term.current().getSize = mon.getSize
if #args > 0 then
shell.run(unpack(args))
Terminal.copy(oterm, term.current())
mon.setCursorBlink(false)
end

83
sys/apps/mirrorClient.lua Normal file
View File

@@ -0,0 +1,83 @@
require = requireInjector(getfenv(1))
local Socket = require('socket')
local Terminal = require('terminal')
local Logger = require('logger')
local process = require('process')
Logger.setScreenLogging()
local remoteId
local args = { ... }
if #args == 1 then
remoteId = tonumber(args[1])
else
print('Enter host ID')
remoteId = tonumber(read())
end
if not remoteId then
error('Syntax: mirrorClient <host ID>')
end
local function wrapTerm(socket)
local methods = { 'blit', 'clear', 'clearLine', 'setCursorPos', 'write',
'setTextColor', 'setTextColour', 'setBackgroundColor',
'setBackgroundColour', 'scroll', 'setCursorBlink', }
socket.term = multishell.term
socket.oldTerm = Util.shallowCopy(socket.term)
for _,k in pairs(methods) do
socket.term[k] = function(...)
if not socket.queue then
socket.queue = { }
os.queueEvent('mirror_flush')
end
table.insert(socket.queue, {
f = k,
args = { ... },
})
socket.oldTerm[k](...)
end
end
end
while true do
print('connecting...')
local socket
while true do
socket = Socket.connect(remoteId, 5901)
if socket then
break
end
os.sleep(3)
end
print('connected')
wrapTerm(socket)
os.queueEvent('term_resize')
while true do
local e = process:pullEvent('mirror_flush')
if e == 'terminate' then
break
end
if not socket.connected then
break
end
if socket.queue then
socket:write(socket.queue)
socket.queue = nil
end
end
for k,v in pairs(socket.oldTerm) do
socket.term[k] = v
end
socket:close()
end

55
sys/apps/mirrorHost.lua Normal file
View File

@@ -0,0 +1,55 @@
require = requireInjector(getfenv(1))
local Socket = require('socket')
local Logger = require('logger')
local process = require('process')
Logger.setScreenLogging()
local args = { ... }
local mon = device[args[1] or 'monitor']
if not mon then
error('Monitor not attached')
end
mon.setBackgroundColor(colors.black)
mon.clear()
while true do
local socket = Socket.server(5901)
print('mirror: connection from ' .. socket.dhost)
local updateThread = process:newThread('updateThread', function()
while true do
local data = socket:read()
if not data then
break
end
for _,v in ipairs(data) do
mon[v.f](unpack(v.args))
end
end
end)
-- ensure socket is connected
process:newThread('pinger', function()
while true do
os.sleep(3)
if not socket:ping() then
break
end
end
end)
while true do
process:pullEvent('modem_message')
if updateThread:isDead() then
break
end
end
print('connection lost')
socket:close()
end

605
sys/apps/multishell Normal file
View File

@@ -0,0 +1,605 @@
-- Default label
if not os.getComputerLabel() then
local id = os.getComputerID()
if turtle then
os.setComputerLabel('turtle_' .. id)
elseif pocket then
os.setComputerLabel('pocket_' .. id)
else
os.setComputerLabel('computer_' .. id)
end
end
multishell.term = term.current()
local defaultEnv = Util.shallowCopy(getfenv(1))
require = requireInjector(getfenv(1))
local Config = require('config')
-- Begin multishell
local parentTerm = term.current()
local w,h = parentTerm.getSize()
local tabs = {}
local currentTab
local _tabId = 0
local overviewTab
local runningTab
local tabsDirty = false
local config = {
standard = {
focusTextColor = colors.lightGray,
focusBackgroundColor = colors.gray,
textColor = colors.black,
backgroundColor = colors.lightGray,
tabBarTextColor = colors.black,
tabBarBackgroundColor = colors.lightGray,
},
color = {
focusTextColor = colors.white,
focusBackgroundColor = colors.brown,
textColor = colors.gray,
backgroundColor = colors.brown,
tabBarTextColor = colors.lightGray,
tabBarBackgroundColor = colors.brown,
},
-- path = '.:/apps:' .. shell.path():sub(3),
path = 'usr/apps:sys/apps:' .. shell.path(),
}
Config.load('multishell', config)
shell.setPath(config.path)
if config.aliases then
for k in pairs(shell.aliases()) do
shell.clearAlias(k)
end
for k,v in pairs(config.aliases) do
shell.setAlias(k, v)
end
end
local _colors = config.standard
if parentTerm.isColor() then
_colors = config.color
end
local function redrawMenu()
if not tabsDirty then
os.queueEvent('multishell', 'draw')
tabsDirty = true
end
end
-- Draw menu
local function draw()
tabsDirty = false
parentTerm.setBackgroundColor( _colors.tabBarBackgroundColor )
if currentTab and currentTab.isOverview then
parentTerm.setTextColor( _colors.focusTextColor )
else
parentTerm.setTextColor( _colors.tabBarTextColor )
end
parentTerm.setCursorPos( 1, 1 )
parentTerm.clearLine()
parentTerm.write('+')
local tabX = 2
local function compareTab(a, b)
return a.tabId < b.tabId
end
for _,tab in Util.spairs(tabs, compareTab) do
if tab.hidden and tab ~= currentTab or tab.isOverview then
tab.sx = nil
tab.ex = nil
else
tab.sx = tabX + 1
tab.ex = tabX + #tab.title
tabX = tabX + #tab.title + 1
end
end
for _,tab in Util.spairs(tabs) do
if tab.sx then
if tab == currentTab then
parentTerm.setTextColor(_colors.focusTextColor)
parentTerm.setBackgroundColor(_colors.focusBackgroundColor)
else
parentTerm.setTextColor(_colors.textColor)
parentTerm.setBackgroundColor(_colors.backgroundColor)
end
parentTerm.setCursorPos(tab.sx, 1)
parentTerm.write(tab.title)
end
end
if currentTab and not currentTab.isOverview then
parentTerm.setTextColor(_colors.textColor)
parentTerm.setBackgroundColor(_colors.backgroundColor)
parentTerm.setCursorPos( w, 1 )
parentTerm.write('*')
end
if currentTab then
currentTab.window.restoreCursor()
end
end
local function selectTab( tab )
if not tab then
for _,ftab in pairs(tabs) do
if not ftab.hidden then
tab = ftab
break
end
end
end
if not tab then
tab = overviewTab
end
if currentTab and currentTab ~= tab then
currentTab.window.setVisible(false)
if tab and not currentTab.hidden then
tab.previousTabId = currentTab.tabId
end
end
if tab then
currentTab = tab
tab.window.setVisible(true)
end
end
local function resumeTab(tab, event, eventData)
if not tab or coroutine.status(tab.co) == 'dead' then
return
end
if not tab.filter or tab.filter == event or event == "terminate" then
eventData = eventData or { }
term.redirect(tab.terminal)
local previousTab = runningTab
runningTab = tab
local ok, result = coroutine.resume(tab.co, event, unpack(eventData))
tab.terminal = term.current()
if ok then
tab.filter = result
else
printError(result)
end
runningTab = previousTab
return ok, result
end
end
local function nextTabId()
_tabId = _tabId + 1
return _tabId
end
local function launchProcess(tab)
tab.tabId = nextTabId()
tab.timestamp = os.clock()
tab.window = window.create(parentTerm, 1, 2, w, h - 1, false)
tab.terminal = tab.window
tab.env = Util.shallowCopy(tab.env or defaultEnv)
tab.co = coroutine.create(function()
local result, err
if tab.fn then
result, err = Util.runFunction(tab.env, tab.fn, table.unpack(tab.args or { } ))
elseif tab.path then
result, err = os.run(tab.env, tab.path, table.unpack(tab.args or { } ))
else
err = 'multishell: invalid tab'
end
if not result and err ~= 'Terminated' then
if err then
printError(tostring(err))
end
printError('Press enter to exit')
tab.isDead = true
while true do
local e, code = os.pullEventRaw('key')
if e == 'terminate' or e == 'key' and code == keys.enter then
if tab.isOverview then
os.queueEvent('multishell', 'terminate')
end
break
end
end
end
tabs[tab.tabId] = nil
if tab == currentTab then
local previousTab
if tab.previousTabId then
previousTab = tabs[tab.previousTabId]
end
selectTab(previousTab)
end
redrawMenu()
end)
tabs[tab.tabId] = tab
resumeTab(tab)
return tab
end
local function resizeWindows()
local windowY = 2
local windowHeight = h-1
local keys = Util.keys(tabs)
for _,key in pairs(keys) do
local tab = tabs[key]
local x,y = tab.window.getCursorPos()
if y > windowHeight then
tab.window.scroll( y - windowHeight )
tab.window.setCursorPos( x, windowHeight )
end
tab.window.reposition( 1, windowY, w, windowHeight )
end
-- Pass term_resize to all processes
local keys = Util.keys(tabs)
for _,key in pairs(keys) do
resumeTab(tabs[key], "term_resize")
end
end
local control
local hotkeys = { }
local function processKeyEvent(event, code)
if event == 'key_up' then
if code == keys.leftCtrl or code == keys.rightCtrl then
control = false
end
elseif event == 'char' then
control = false
elseif event == 'key' then
if code == keys.leftCtrl or code == keys.rightCtrl then
control = true
elseif control then
local hotkey = hotkeys[code]
control = false
if hotkey then
hotkey()
end
end
end
end
function multishell.addHotkey(code, fn)
hotkeys[code] = fn
end
function multishell.removeHotkey(code)
hotkeys[code] = nil
end
function multishell.getFocus()
return currentTab.tabId
end
function multishell.setFocus(tabId)
local tab = tabs[tabId]
if tab then
selectTab(tab)
redrawMenu()
return true
end
return false
end
function multishell.getTitle(tabId)
local tab = tabs[tabId]
if tab then
return tab.title
end
end
function multishell.setTitle(tabId, sTitle)
local tab = tabs[tabId]
if tab then
tab.title = sTitle or ''
redrawMenu()
end
end
function multishell.getCurrent()
if runningTab then
return runningTab.tabId
end
end
function multishell.getTab(tabId)
return tabs[tabId]
end
function multishell.terminate(tabId)
local tab = tabs[tabId]
if tab and not tab.isOverview then
if coroutine.status(tab.co) ~= 'dead' then
--os.queueEvent('multishell', 'terminate', tab)
resumeTab(tab, "terminate")
else
tabs[tabId] = nil
if tab == currentTab then
local previousTab
if tab.previousTabId then
previousTab = tabs[tab.previousTabId]
end
selectTab(previousTab)
end
redrawMenu()
end
end
end
function multishell.getTabs()
return tabs
end
function multishell.launch( tProgramEnv, sProgramPath, ... )
-- backwards compatibility
return multishell.openTab({
env = tProgramEnv,
path = sProgramPath,
args = { ... },
})
end
function multishell.openTab(tab)
if not tab.title and tab.path then
tab.title = fs.getName(tab.path)
end
tab.title = tab.title or 'untitled'
local previousTerm = term.current()
launchProcess(tab)
term.redirect(previousTerm)
if tab.hidden then
if coroutine.status(tab.co) == 'dead' or tab.isDead then
tab.hidden = false
end
elseif tab.focused then
multishell.setFocus(tab.tabId)
else
redrawMenu()
end
return tab.tabId
end
function multishell.hideTab(tabId)
local tab = tabs[tabId]
if tab then
tab.hidden = true
redrawMenu()
end
end
function multishell.unhideTab(tabId)
local tab = tabs[tabId]
if tab then
tab.hidden = false
redrawMenu()
end
end
function multishell.getCount()
local count
for _,tab in pairs(tabs) do
count = count + 1
end
return count
end
-- control-o - overview
multishell.addHotkey(24, function()
multishell.setFocus(overviewTab.tabId)
end)
-- control-backspace
multishell.addHotkey(14, function()
local tabId = multishell.getFocus()
local tab = tabs[tabId]
if not tab.isOverview then
os.queueEvent('multishell', 'terminateTab', tabId)
tab = Util.shallowCopy(tab)
tab.isDead = false
tab.focused = true
multishell.openTab(tab)
end
end)
-- control-tab - next tab
multishell.addHotkey(15, function()
local function compareTab(a, b)
return a.tabId < b.tabId
end
local visibleTabs = { }
for _,tab in Util.spairs(tabs, compareTab) do
if not tab.hidden then
table.insert(visibleTabs, tab)
end
end
for k,tab in ipairs(visibleTabs) do
if tab.tabId == currentTab.tabId then
if k < #visibleTabs then
multishell.setFocus(visibleTabs[k + 1].tabId)
return
end
end
end
if #visibleTabs > 0 then
multishell.setFocus(visibleTabs[1].tabId)
end
end)
local function startup()
local hasError
local function runDir(directory, desc, open)
if not fs.exists(directory) then
return
end
local files = fs.list(directory)
table.sort(files)
for _,file in ipairs(files) do
print(desc .. file)
os.sleep(0)
local result, err = open(directory .. '/' .. file)
if not result then
printError(err)
hasError = true
end
end
end
runDir('sys/extensions', '[ ext ] ', shell.run)
local overviewId = multishell.openTab({
path = 'sys/apps/Overview.lua',
focused = true,
hidden = true,
isOverview = true,
})
overviewTab = tabs[overviewId]
runDir('sys/services', '[ svc ] ', shell.openHiddenTab)
runDir('sys/autorun', '[ aut ] ', shell.run)
runDir('usr/autorun', '[ aut ] ', shell.run)
if hasError then
error('An autorun program has errored')
end
end
-- Begin
parentTerm.clear()
multishell.openTab({
focused = true,
fn = startup,
env = defaultEnv,
title = 'Autorun',
})
if not overviewTab or coroutine.status(overviewTab.co) == 'dead' then
--error('Overview aborted')
end
if not currentTab then
multishell.setFocus(overviewTab.tabId)
end
draw()
while true do
-- Get the event
local tEventData = { os.pullEventRaw() }
local sEvent = table.remove(tEventData, 1)
if sEvent == 'key_up' then
processKeyEvent(sEvent, tEventData[1])
end
if sEvent == "term_resize" then
-- Resize event
w,h = parentTerm.getSize()
resizeWindows()
redrawMenu()
elseif sEvent == 'multishell' then
local action = tEventData[1]
if action == 'terminate' then
break
elseif action == 'terminateTab' then
multishell.terminate(tEventData[2])
elseif action == 'draw' then
draw()
end
elseif sEvent == "char" or
sEvent == "key" or
sEvent == "paste" or
sEvent == "terminate" then
processKeyEvent(sEvent, tEventData[1])
-- Keyboard event - Passthrough to current process
resumeTab(currentTab, sEvent, tEventData)
elseif sEvent == "mouse_click" then
local button, x, y = tEventData[1], tEventData[2], tEventData[3]
if y == 1 and os.locked then
-- ignore
elseif y == 1 then
-- Switch process
local w, h = parentTerm.getSize()
if x == 1 then
multishell.setFocus(overviewTab.tabId)
elseif x == w then
if currentTab then
multishell.terminate(currentTab.tabId)
end
else
for _,tab in pairs(tabs) do
if not tab.hidden and tab.sx then
if x >= tab.sx and x <= tab.ex then
multishell.setFocus(tab.tabId)
break
end
end
end
end
elseif currentTab then
-- Passthrough to current process
resumeTab(currentTab, sEvent, { button, x, y-1 })
end
elseif sEvent == "mouse_drag" or sEvent == "mouse_scroll" then
-- Other mouse event
local p1, x, y = tEventData[1], tEventData[2], tEventData[3]
if currentTab and (y ~= 1) then
if currentTab.terminal.scrollUp then
if p1 == -1 then
currentTab.terminal.scrollUp()
else
currentTab.terminal.scrollDown()
end
else
-- Passthrough to current process
resumeTab(currentTab, sEvent, { p1, x, y-1 })
end
end
else
-- Other event
-- Passthrough to all processes
local keys = Util.keys(tabs)
for _,key in pairs(keys) do
resumeTab(tabs[key], sEvent, tEventData)
end
end
end

10
sys/apps/password.lua Normal file
View File

@@ -0,0 +1,10 @@
require = requireInjector(getfenv(1))
local SHA1 = require('sha1')
local Terminal = require('terminal')
local password = Terminal.readPassword('Enter new password: ')
if password then
os.updatePassword(SHA1.sha1(password))
print('Password updated')
end

343
sys/apps/pickup.lua Normal file
View File

@@ -0,0 +1,343 @@
require = requireInjector(getfenv(1))
local GPS = require('gps')
local Socket = require('socket')
local MEProvider = require('meProvider')
local Logger = require('logger')
local Point = require('point')
local process = require('process')
if not device.wireless_modem then
error('Modem is required')
end
Logger.setWirelessLogging()
if not turtle then
error('Can only be run on a turtle')
end
turtle.clearMoveCallback()
local gps = GPS.getPointAndHeading()
if not gps then
error('could not get gps location')
end
turtle.setPoint(gps)
local blocks = { }
local meProvider = MEProvider()
local items = { }
local pickups = Util.readTable('pickup.tbl') or { }
local cells = Util.readTable('cells.tbl') or { }
local refills = Util.readTable('refills.tbl') or { }
local fluids = Util.readTable('fluids.tbl') or { }
local chestPt = turtle.loadLocation('chest')
local chargePt = turtle.loadLocation('charge')
local fuel = {
item = {
id = 'minecraft:coal',
dmg = 0,
},
qty = 64
}
local slots
turtle.setMoveCallback(function(action, pt)
if slots then
for _,slot in pairs(slots) do
if turtle.getItemCount(slot.index) ~= slot.qty then
printError('Slots changed')
process:terminate()
end
end
end
end)
function refuel()
if turtle.getFuelLevel() < 5000 then
print('refueling')
turtle.status = 'refueling'
gotoPoint(chestPt, true)
dropOff(chestPt)
while turtle.getFuelLevel() < 5000 do
turtle.select(1)
meProvider:provide(fuel.item, fuel.qty, 1)
turtle.refuel(64)
print(turtle.getFuelLevel())
os.sleep(1)
end
end
end
function pickUp(pt)
turtle.status = 'picking up'
gotoPoint(pt, true)
while true do
if not turtle.selectOpenSlot() then
dropOff(chestPt)
gotoPoint(pt, true)
end
turtle.select(1)
if not turtle.suckDown(64) then
return
end
end
end
function dropOff(pt)
if turtle.selectSlotWithItems() then
gotoPoint(pt, true)
turtle.emptyInventory(turtle.dropDown)
if pt == chestPt then
print('refreshing items')
items = meProvider:refresh()
end
end
end
function gotoPoint(pt, doDetect)
slots = turtle.getInventory()
while not turtle.pathfind(pt, blocks) do
if turtle.abort then
error('aborted')
end
turtle.status = 'blocked'
os.sleep(5)
end
if doDetect and not turtle.detectDown() then
error('Missing target')
end
end
function checkCell(pt)
if not turtle.selectOpenSlot() then
dropOff(chestPt)
end
print('checking cell')
turtle.status = 'recharging'
gotoPoint(pt, true)
local c = peripheral.wrap('bottom')
local energy = c.getMaxEnergyStored() -
c.getEnergyStored()
if energy > 20000 then
print('charging cell')
turtle.selectOpenSlot()
turtle.digDown()
gotoPoint(chargePt, true)
turtle.dropDown()
os.sleep(energy / 20000)
turtle.suckDown()
print('replacing cell')
gotoPoint(pt)
if not turtle.placeDown() then
error('could not place down cell')
end
end
end
function fluid(points)
print('checking fluid')
turtle.status = 'fluiding'
gotoPoint(points.source, true)
turtle.select(1)
turtle.digDown()
gotoPoint(points.target)
if not turtle.placeDown() then
error('could not place fluid container')
end
os.sleep(5)
turtle.digDown()
gotoPoint(points.source)
turtle.placeDown()
end
function refill(entry)
dropOff(chestPt)
turtle.status = 'refilling'
gotoPoint(chestPt)
for _,item in pairs(entry.items) do
meProvider:provide(item, tonumber(item.qty), turtle.selectOpenSlot())
end
if turtle.selectSlotWithItems() then
if entry.point then
dropOff(entry.point)
end
end
end
function oldRefill(points)
gotoPoint(points.source)
repeat until not turtle.suckDown(64)
if points.target then
dropOff(points.target)
end
if points.targets then
for k,target in pairs(points.targets) do
dropOff(target)
end
end
dropOff(points.source)
dropOff(chestPt)
end
local function makeKey(pt)
return string.format('%d:%d:%d', pt.x, pt.y, pt.z)
end
local function pickupHost(socket)
while true do
local data = socket:read()
if not data then
print('pickup: closing connection to ' .. socket.dhost)
return
end
print('command: ' .. data.type)
if data.type == 'pickup' then
local key = makeKey(data.point)
pickups[key] = data.point
Util.writeTable('pickup.tbl', pickups)
socket:write( { type = "response", response = 'added' })
elseif data.type == 'items' then
socket:write( { type = "response", response = items })
elseif data.type == 'refill' then
local key = makeKey(data.entry.point)
refills[key] = data.entry
Util.writeTable('refills.tbl', refills)
socket:write( { type = "response", response = 'added' })
elseif data.type == 'setPickup' then
chestPt = data.point
turtle.storeLocation('chest', chestPt)
socket:write( { type = "response", response = 'Location set' })
elseif data.type == 'setRecharge' then
chargePt = data.point
turtle.storeLocation('charge', chargePt)
socket:write( { type = "response", response = 'Location set' })
elseif data.type == 'charge' then
local key = makeKey(data.point)
cells[key] = data.point
Util.writeTable('cells.tbl', cells)
socket:write( { type = "response", response = 'added' })
elseif data.type == 'fluid' then
elseif data.type == 'clear' then
local key = makeKey(data.point)
refills[key] = nil
cells[key] = nil
fluids[key] = nil
pickups[key] = nil
Util.writeTable('refills.tbl', refills)
Util.writeTable('cells.tbl', cells)
Util.writeTable('fluids.tbl', fluids)
Util.writeTable('pickup.tbl', pickups)
socket:write( { type = "response", response = 'cleared' })
else
print('unknown command')
end
end
end
process:newThread('pickup', function()
while true do
print('waiting for connection on port 5222')
local socket = Socket.server(5222)
print('pickup: connection from ' .. socket.dhost)
process:newThread('pickup_connection', function() pickupHost(socket) end)
end
end)
local function eachEntry(t, fn)
local keys = Util.keys(t)
for _,key in pairs(keys) do
if t[key] then
if turtle.abort then
return
end
fn(t[key])
end
end
end
local function eachClosestEntry(t, fn)
local points = { }
for k,v in pairs(t) do
v = Util.shallowCopy(v)
v.key = k
table.insert(points, v)
end
while not Util.empty(points) do
local closest = Point.closest(turtle.point, points)
if turtle.abort then
return
end
if t[closest.key] then
fn(closest)
end
for k,v in pairs(points) do
if v.key == closest.key then
table.remove(points, k)
break
end
end
end
end
refuel()
turtle.abort = false
local deliveryThread = process:newThread('deliveries', function()
while true do
if chestPt then
eachClosestEntry(pickups, pickUp)
eachEntry(refills, refill)
refuel()
end
eachEntry(fluids, fluid)
if chargePt then
eachEntry(cells, checkCell)
end
print('sleeping')
turtle.status = 'sleeping'
if turtle.abort then
printError('aborted')
break
end
os.sleep(60)
end
end)
turtle.run(function()
while true do
local e = process:pullEvent()
if e == 'terminate' or deliveryThread:isDead() then
break
end
end
end)
process:threadEvent('terminate')

229
sys/apps/pickupRemote.lua Normal file
View File

@@ -0,0 +1,229 @@
if not device.wireless_modem then
error('Wireless modem is required')
end
require = requireInjector(getfenv(1))
local GPS = require('gps')
local Event = require('event')
local UI = require('ui')
local Socket = require('socket')
multishell.setTitle(multishell.getCurrent(), 'Pickup Remote')
local id
local mainPage = UI.Page({
menu = UI.Menu({
centered = true,
y = 2,
menuItems = {
{ prompt = 'Pickup', event = 'pickup', help = 'Pickup items from this location' },
{ prompt = 'Charge cell', event = 'charge', help = 'Recharge this cell' },
{ prompt = 'Refill', event = 'refill', help = 'Recharge this cell' },
{ prompt = 'Set pickup location', event = 'setPickup', help = 'Recharge this cell' },
{ prompt = 'Set recharge location', event = 'setRecharge', help = 'Recharge this cell' },
{ prompt = 'Clear', event = 'clear', help = 'Remove this location' },
},
}),
statusBar = UI.StatusBar(),
accelerators = {
q = 'quit',
},
})
local refillPage = UI.Page({
menuBar = UI.MenuBar({
y = 1,
buttons = {
{ text = 'Done', event = 'done', help = 'Pickup items from this location' },
{ text = 'Back', event = 'back', help = 'Recharge this cell' },
},
}),
grid1 = UI.ScrollingGrid({
columns = {
{ heading = 'Name', key = 'name', width = UI.term.width-9 },
{ heading = 'Qty', key = 'fQty', width = 5 },
},
sortColumn = 'name',
height = 8,
y = 3,
}),
grid2 = UI.ScrollingGrid({
columns = {
{ heading = 'Name', key = 'name', width = UI.term.width-9 },
{ heading = 'Qty', key = 'qty', width = 5 },
},
sortColumn = 'name',
height = 4,
y = 12,
}),
statusBar = UI.StatusBar(),
accelerators = {
q = 'quit',
},
})
refillPage.menuBar:add({
filter = UI.TextEntry({
x = UI.term.width-10,
width = 10,
})
})
local function sendCommand(cmd)
local socket = Socket.connect(id, 5222)
if not socket then
mainPage.statusBar:timedStatus('Unable to connect', 3)
return
end
socket:write(cmd)
local m = socket:read(3)
socket:close()
if m then
return m.response
end
mainPage.statusBar:timedStatus('No response', 3)
end
local function getPoint()
local gpt = GPS.getPoint()
if not gpt then
mainPage.statusBar:timedStatus('Unable to get location', 3)
end
return gpt
end
function refillPage:eventHandler(event)
if event.type == 'grid_select' then
local item = {
name = event.selected.name,
id = event.selected.id,
dmg = event.selected.dmg,
qty = 0,
}
local dialog = UI.Dialog({
x = 1,
width = UI.term.width,
text = UI.Text({ x = 3, y = 3, value = 'Quantity' }),
textEntry = UI.TextEntry({ x = 14, y = 3 })
})
dialog.eventHandler = function(self, event)
if event.type == 'accept' then
local l = tonumber(self.textEntry.value)
if l and l <= 1024 and l > 0 then
item.qty = self.textEntry.value
table.insert(refillPage.grid2.values, item)
refillPage.grid2:update()
UI:setPreviousPage()
else
self.statusBar:timedStatus('Invalid Quantity', 3)
end
return true
end
return UI.Dialog.eventHandler(self, event)
end
dialog.titleBar.title = item.name
dialog:setFocus(dialog.textEntry)
UI:setPage(dialog)
elseif event.type == 'text_change' then
local text = event.text
if #text == 0 then
self.grid1.values = self.allItems
else
self.grid1.values = { }
for _,item in pairs(self.allItems) do
if string.find(item.lname, text) then
table.insert(self.grid1.values, item)
end
end
end
--self.grid:adjustWidth()
self.grid1:update()
self.grid1:setIndex(1)
self.grid1:draw()
elseif event.type == 'back' then
UI:setPreviousPage()
elseif event.type == 'done' then
UI:setPage(mainPage)
local pt = getPoint()
if pt then
local response = sendCommand({ type = 'refill', entry = { point = pt, items = self.grid2.values } })
if response then
mainPage.statusBar:timedStatus(response, 3)
end
end
elseif event.type == 'grid_focus_row' then
self.statusBar:setStatus(event.selected.id .. ':' .. event.selected.dmg)
self.statusBar:draw()
end
return UI.Page.eventHandler(self, event)
end
function refillPage:enable()
for _,item in pairs(self.allItems) do
item.lname = string.lower(item.name)
item.fQty = Util.toBytes(item.qty)
end
self.grid1:setValues(self.allItems)
self.menuBar.filter.value = ''
self.menuBar.filter.pos = 1
self:setFocus(self.menuBar.filter)
UI.Page.enable(self)
end
function mainPage:eventHandler(event)
if event.type == 'quit' then
Event.exitPullEvents()
elseif event.type == 'refill' then
local response = sendCommand({ type = 'items' })
if response then
refillPage.allItems = response
refillPage.grid2:setValues({ })
UI:setPage(refillPage)
end
elseif event.type == 'pickup' or event.type == 'setPickup' or
event.type == 'setRecharge' or event.type == 'charge' or
event.type == 'clear' then
local pt = getPoint()
if pt then
local response = sendCommand({ type = event.type, point = pt })
if response then
self.statusBar:timedStatus(response, 3)
end
end
end
return UI.Page.eventHandler(self, event)
end
local args = { ... }
if #args == 1 then
id = tonumber(args[1])
end
if not id then
error('Syntax: pickupRemote <turtle ID>')
end
UI:setPage(mainPage)
Event.pullEvents()
UI.term:reset()

539
sys/apps/recorder.lua Normal file
View File

@@ -0,0 +1,539 @@
-- +---------------------+------------+---------------------+
-- | | | |
-- | | RecGif | |
-- | | | |
-- +---------------------+------------+---------------------+
local version = "Version 1.1.6"
-- Records your terminal and saves the result as an animating GIF.
-- http://www.computercraft.info/forums2/index.php?/topic/24840-recgif/
-- ----------------------------------------------------------
-- Original code by Bomb Bloke
-- Modified to integrate with opus os
local recTerm, oldTerm, arg, showInput, skipLast, lastDelay, curInput = {}, Util.shallowCopy(multishell.term), {...}, false, false, 2, ""
local curBlink, oldBlink, tTerm, buffer, colourNum, xPos, yPos, oldXPos, oldYPos, tCol, bCol, xSize, ySize = false, false, {}, {}, {}, 1, 1, 1, 1, colours.white, colours.black, oldTerm.getSize()
local greys, buttons = {["0"] = true, ["7"] = true, ["8"] = true, ["f"] = true}, {"l", "r", "m"}
local charW, charH, chars, resp
local filename
local calls = { }
local curCalls = { delay = 0 }
local callListCount = 0
local callCount = 0
local function showSyntax()
print('Gif Recorder by Bomb Bloke\n')
print('Syntax: recGif [-i] [-s] [-ld:<delay>] filename')
print(' -i : show input')
print(' -s : skip last')
print(' -ld : last delay')
end
for i = #arg, 1, -1 do
local curArg = arg[i]:lower()
if curArg == "-i" then
showInput, ySize = true, ySize + 1
table.remove(arg, i)
elseif curArg == "-s" then
skipLast = true
table.remove(arg, i)
elseif curArg:sub(1, 4) == "-ld:" then
curArg = tonumber(curArg:sub(5))
if curArg then lastDelay = curArg end
table.remove(arg, i)
elseif curArg == '-?' then
showSyntax()
return
elseif i ~= #arg then
showSyntax()
printError('\nInvalid argument')
return
end
end
print('Press control-p to stop recording')
local filename = arg[#arg]
if not filename then
print('Enter file name:')
filename = read()
end
if #filename == 0 then
showSyntax()
print()
error('Invalid file name')
end
print('Initializing...')
fs.mount('.recGif', 'ramfs', 'directory')
fs.mount('.recGif/GIF', 'urlfs', 'http://pastebin.com/raw/5uk9uRjC')
fs.mount('.recGif/package', 'urlfs', 'http://pastebin.com/raw/cUYTGbpb')
-- don't pollute global env
local function loadAPI(filename, env)
local apiEnv = Util.shallowCopy(env)
apiEnv.shell = nil
apiEnv.multishell = nil
setmetatable(apiEnv, { __index = _G })
local fn = loadfile(filename, apiEnv)
fn()
return apiEnv
end
package = loadAPI('.recGif/package', getfenv(1))
GIF = loadAPI('.recGif/GIF', getfenv(1))
local oldDir = shell.dir()
shell.setDir('.recGif')
shell.run("package get Y0eLUPtr")
shell.setDir(oldDir)
local function snooze()
local myEvent = tostring({})
os.queueEvent(myEvent)
os.pullEvent(myEvent)
end
local function safeString(text)
local newText = {}
for i = 1, #text do
local val = text:byte(i)
newText[i] = (val > 31 and val < 127) and val or 63
end
return string.char(unpack(newText))
end
local function safeCol(text, subst)
local newText = {}
for i = 1, #text do
local val = text:sub(i, i)
newText[i] = greys[val] and val or subst
end
return table.concat(newText)
end
-- Build a terminal that records stuff:
recTerm = multishell.term
for key, func in pairs(oldTerm) do
recTerm[key] = function(...)
local result = { func(...) }
if callCount == 0 then
os.queueEvent('capture_frame')
end
callCount = callCount + 1
curCalls[callCount] = { key, ... }
return unpack(result)
end
end
local tabId = multishell.getCurrent()
multishell.addHotkey(25, function()
os.queueEvent('recorder_stop')
end)
local tabs = multishell.getTabs()
for _,tab in pairs(tabs) do
if tab.isOverview then
multishell.hideTab(tabId)
multishell.setFocus(tab.tabId)
os.queueEvent('term_resize')
break
end
end
local curTime = os.clock() - 1
while true do
local event = { os.pullEventRaw() }
if event[1] == 'recorder_stop' or event[1] == 'terminate' then
break
end
if event[1] == 'capture_frame' then
local newTime = os.clock()
if callListCount > 0 then
calls[callListCount].delay = (newTime - curTime)
end
curTime = newTime
callListCount = callListCount + 1
calls[callListCount] = curCalls
curCalls, callCount = { delay = 0 }, 0
end
end
multishell.removeHotkey(25)
for k,fn in pairs(oldTerm) do
multishell.term[k] = fn
end
multishell.unhideTab(tabId)
multishell.setFocus(tabId)
if #calls[#calls] == 0 then calls[#calls] = nil end
if skipLast and #calls > 1 then calls[#calls] = nil end
calls[#calls].delay = lastDelay
print(string.format("Encoding %d frames...", #calls))
--Util.writeTable('tmp/raw.txt', calls)
-- Perform a quick re-parse of the recorded data (adding frames for when the cursor blinks):
do
local callListCount, tempCalls, blink, oldBlink, curBlink, blinkDelay = 1, {}, false, false, true, 0
for i = 1, #calls - 1 do
curCalls = calls[i]
tempCalls[callListCount] = curCalls
for j = 1, #curCalls do if curCalls[j][1] == "setCursorBlink" then blink = curCalls[j][2] end end
if blink then
if blinkDelay == 0 then
curCalls[#curCalls + 1] = {"toggleCur", curBlink}
blinkDelay, curBlink = 0.4, not curBlink
end
while tempCalls[callListCount].delay > blinkDelay do
local remainder = tempCalls[callListCount].delay - blinkDelay
tempCalls[callListCount].delay = blinkDelay
callListCount = callListCount + 1
tempCalls[callListCount] = {{"toggleCur", curBlink}, ["delay"] = remainder}
blinkDelay, curBlink = 0.4, not curBlink
end
blinkDelay = blinkDelay - tempCalls[callListCount].delay
else
if oldBlink then curCalls[#curCalls + 1] = {"toggleCur", false} end
blinkDelay = (curCalls.delay - blinkDelay) % 0.4
end
callListCount, oldBlink = callListCount + 1, blink
end
tempCalls[callListCount] = calls[#calls]
tempCalls[callListCount][#tempCalls[callListCount] + 1] = {"toggleCur", false}
calls, curCalls = tempCalls, nil
end
snooze()
-- Load font data:
do
local ascii, counter = GIF.toPaintutils(GIF.flattenGIF(GIF.loadGIF(".recGif/ascii.gif"))), 0
local newFont, ybump, xbump = #ascii ~= #ascii[1], 0, 0
charW, charH, chars = newFont and #ascii[1] / 16 or #ascii[1] * 3 / 64, #ascii / 16, {}
for yy = 0, newFont and 15 or 7 do
for xx = 0, 15 do
local newChar, length = {}, 0
-- Place in 2d grid of bools:
for y = 1, charH do
local newRow = {}
for x = 1, charW do
local set = ascii[y + ybump][x + xbump] == 1
if set and x > length then length = x end
newRow[x] = set
end
newChar[y] = newRow
end
-- Center:
if not newFont then for y = 1, charH do for x = 1, math.floor((charW - length) / 2) do table.insert(newChar[y], 1, false) end end end
chars[counter] = newChar
counter, xbump = counter + 1, xbump + (newFont and charW or charH)
end
xbump, ybump = 0, ybump + charH
end
end
snooze()
-- Terminal data translation:
do
local hex, counter = "0123456789abcdef", 1
for i = 1, 16 do
colourNum[counter] = hex:sub(i, i)
counter = counter * 2
end
end
for y = 1, ySize do
buffer[y] = {}
for x = 1, xSize do buffer[y][x] = {" ", colourNum[tCol], colourNum[bCol]} end
end
if showInput then for x = 1, xSize do buffer[ySize][x][3] = colourNum[colours.lightGrey] end end
tTerm.blit = function(text, fgCol, bgCol)
if xPos > xSize or xPos + #text - 1 < 1 or yPos < 1 or yPos > ySize then return end
if not _HOST then text = safeString(text) end
if not term.isColour() then
fgCol = safeCol(fgCol, "0")
bgCol = safeCol(bgCol, "f")
end
if xPos < 1 then
text = text:sub(2 - xPos)
fgCol = fgCol:sub(2 - xPos)
bgCol = bgCol:sub(2 - xPos)
xPos = 1
end
if xPos + #text - 1 > xSize then
text = text:sub(1, xSize - xPos + 1)
fgCol = fgCol:sub(1, xSize - xPos + 1)
bgCol = bgCol:sub(1, xSize - xPos + 1)
end
for x = 1, #text do
buffer[yPos][xPos + x - 1][1] = text:sub(x, x)
buffer[yPos][xPos + x - 1][2] = fgCol:sub(x, x)
buffer[yPos][xPos + x - 1][3] = bgCol:sub(x, x)
end
xPos = xPos + #text
end
tTerm.write = function(text)
text = tostring(text)
tTerm.blit(text, string.rep(colourNum[tCol], #text), string.rep(colourNum[bCol], #text))
end
tTerm.clearLine = function()
local oldXPos = xPos
xPos = 1
tTerm.write(string.rep(" ", xSize))
xPos = oldXPos
end
tTerm.clear = function()
local oldXPos, oldYPos = xPos, yPos
for y = 1, ySize do
xPos, yPos = 1, y
tTerm.write(string.rep(" ", xSize))
end
xPos, yPos = oldXPos, oldYPos
end
tTerm.setCursorPos = function(x, y)
xPos, yPos = math.floor(x), math.floor(y)
end
tTerm.setTextColour = function(col)
tCol = col
end
tTerm.setTextColor = function(col)
tCol = col
end
tTerm.setBackgroundColour = function(col)
bCol = col
end
tTerm.setBackgroundColor = function(col)
bCol = col
end
tTerm.scroll = function(lines)
if math.abs(lines) < ySize then
local oldXPos, oldYPos = xPos, yPos
for y = 1, ySize do
if y + lines > 0 and y + lines <= ySize then
for x = 1, xSize do
xPos, yPos = x, y
tTerm.blit(buffer[y + lines][x][1], buffer[y + lines][x][2], buffer[y + lines][x][3])
end
else
yPos = y
tTerm.clearLine()
end
end
xPos, yPos = oldXPos, oldYPos
else tTerm.clear() end
end
tTerm.toggleCur = function(newBlink)
curBlink = newBlink
end
tTerm.newInput = function(input)
local oldTC, oldBC, oldX, oldY = tCol, bCol, xPos, yPos
tCol, bCol, xPos, yPos, ySize, input = colours.grey, colours.lightGrey, 1, ySize + 1, ySize + 1, input .. " "
while #curInput + #input + 1 > xSize do curInput = curInput:sub(curInput:find(" ") + 1) end
curInput = curInput .. input .. " "
tTerm.clearLine()
tTerm.write(curInput)
tCol, bCol, xPos, yPos, ySize = oldTC, oldBC, oldX, oldY, ySize - 1
end
tTerm.key = function(key)
tTerm.newInput((not keys.getName(key)) and "unknownKey" or keys.getName(key))
end
tTerm.mouse_click = function(button, x, y)
tTerm.newInput(buttons[button] .. "C@" .. tostring(x) .. "x" .. tostring(y))
end
local image = {["width"] = xSize * charW, ["height"] = ySize * charH}
for i = 1, #calls do
local xMin, yMin, xMax, yMax, oldBuffer, curCalls, changed = xSize + 1, ySize + 1, 0, 0, {}, calls[i], false
calls[i] = nil
for y = 1, ySize do
oldBuffer[y] = {}
for x = 1, xSize do oldBuffer[y][x] = {buffer[y][x][1], buffer[y][x][2], buffer[y][x][3], buffer[y][x][4]} end
end
snooze()
if showInput then ySize = ySize - 1 end
for j = 1, #curCalls do if tTerm[curCalls[j][1]] then tTerm[curCalls[j][1]](unpack(curCalls[j], 2)) end end
if showInput then ySize = ySize + 1 end
if i > 1 then
for yy = 1, ySize do for xx = 1, xSize do if buffer[yy][xx][1] ~= oldBuffer[yy][xx][1] or (buffer[yy][xx][2] ~= oldBuffer[yy][xx][2] and buffer[yy][xx][1] ~= " ") or buffer[yy][xx][3] ~= oldBuffer[yy][xx][3] then
changed = true
if xx < xMin then xMin = xx end
if xx > xMax then xMax = xx end
if yy < yMin then yMin = yy end
if yy > yMax then yMax = yy end
end end end
else xMin, yMin, xMax, yMax, changed = 1, 1, xSize, ySize, true end
if oldBlink and (xPos ~= oldXPos or yPos ~= oldYPos or not curBlink) and oldXPos > 0 and oldYPos > 0 and oldXPos <= xSize and oldYPos <= ySize then
changed = true
if oldXPos < xMin then xMin = oldXPos end
if oldXPos > xMax then xMax = oldXPos end
if oldYPos < yMin then yMin = oldYPos end
if oldYPos > yMax then yMax = oldYPos end
buffer[oldYPos][oldXPos][4] = false
end
if curBlink and (xPos ~= oldXPos or yPos ~= oldYPos or not oldBlink) and xPos > 0 and yPos > 0 and xPos <= xSize and yPos <= ySize then
changed = true
if xPos < xMin then xMin = xPos end
if xPos > xMax then xMax = xPos end
if yPos < yMin then yMin = yPos end
if yPos > yMax then yMax = yPos end
buffer[yPos][xPos][4] = true
end
oldBlink, oldXPos, oldYPos = curBlink, xPos, yPos
local thisFrame = {
["xstart"] = (xMin - 1) * charW,
["ystart"] = (yMin - 1) * charH,
["xend"] = (xMax - xMin + 1) * charW,
["yend"] = (yMax - yMin + 1) * charH,
["delay"] = curCalls.delay,
["disposal"] = 1
}
for y = 1, (yMax - yMin + 1) * charH do
local row = {}
for x = 1, (xMax - xMin + 1) * charW do row[x] = " " end
thisFrame[y] = row
end
snooze()
for yy = yMin, yMax do
local yBump = (yy - yMin) * charH
for xx = xMin, xMax do if buffer[yy][xx][1] ~= oldBuffer[yy][xx][1] or (buffer[yy][xx][2] ~= oldBuffer[yy][xx][2] and buffer[yy][xx][1] ~= " ") or buffer[yy][xx][3] ~= oldBuffer[yy][xx][3] or buffer[yy][xx][4] ~= oldBuffer[yy][xx][4] or i == 1 then
local thisChar, thisT, thisB, xBump = chars[buffer[yy][xx][1]:byte()], buffer[yy][xx][2], buffer[yy][xx][3], (xx - xMin) * charW
if thisChar then
for y = 1, charH do
for x = 1, charW do
local ch = thisChar[y][x] and thisT or thisB
thisFrame[y + yBump][x + xBump] = ch
end
end
end
if buffer[yy][xx][4] then
thisT, thisChar = colourNum[tCol], chars[95]
for y = 1, charH do for x = 1, charW do if thisChar[y][x] then thisFrame[y + yBump][x + xBump] = thisT end end end
end
end end
for y = yBump + 1, yBump + charH do
local skip, chars, row = 0, {}, {}
for x = 1, #thisFrame[y] do
if thisFrame[y][x] == " " then
if #chars > 0 then
row[#row + 1] = table.concat(chars)
chars = {}
end
skip = skip + 1
else
if skip > 0 then
row[#row + 1] = skip
skip = 0
end
chars[#chars + 1] = thisFrame[y][x]
end
end
if #chars > 0 then row[#row + 1] = table.concat(chars) end
thisFrame[y] = row
end
snooze()
end
if changed then
image[#image + 1] = thisFrame
else
image[#image].delay = image[#image].delay + curCalls.delay
end
end
buffer = nil
GIF.saveGIF(image, filename)
fs.unmount('.recGif')
print("Encode complete")

535
sys/apps/refinedManager.lua Normal file
View File

@@ -0,0 +1,535 @@
local injector = requireInjector or load(http.get('http://pastebin.com/raw/c0TWsScv').readAll())()
require = injector(getfenv(1))
local UI = require('ui')
local RefinedProvider = require('refinedProvider')
local Terminal = require('terminal')
local Peripheral = require('peripheral')
local controller = RefinedProvider()
if not controller:isValid() then
error('Refined storage controller not found')
end
multishell.setTitle(multishell.getCurrent(), 'Storage Manager')
function getItem(items, inItem, ignoreDamage)
for _,item in pairs(items) do
if item.name == inItem.name then
if ignoreDamage then
return item
elseif item.damage == inItem.damage and item.nbtHash == inItem.nbtHash then
return item
end
end
end
end
local function uniqueKey(item)
return table.concat({ item.name, item.damage, item.nbtHash }, ':')
end
function mergeResources(t)
local resources = Util.readTable('resource.limits') or { }
for _,v in pairs(resources) do
v.low = tonumber(v.low) -- backwards compatibility
local item = getItem(t, v)
if item then
item.low = v.low
item.auto = v.auto
item.ignoreDamage = v.ignoreDamage
item.rsControl = v.rsControl
item.rsDevice = v.rsDevice
item.rsSide = v.rsSide
else
v.count = 0
table.insert(t, v)
end
end
for _,v in pairs(t) do
v.lname = v.displayName:lower()
end
end
function filterItems(t, filter)
if filter then
local r = {}
filter = filter:lower()
for k,v in pairs(t) do
if string.find(v.lname, filter) then
table.insert(r, v)
end
end
return r
end
return t
end
function craftItems(itemList, allItems)
for _,item in pairs(itemList) do
local cItem = getItem(allItems, item)
if controller:isCrafting(item) then
item.status = '(crafting)'
elseif item.rsControl then
item.status = 'Activated'
elseif not cItem then
item.status = '(no recipe)'
else
local count = item.count
while count >= 1 do -- try to request smaller quantities until successful
local s, m = pcall(function()
item.status = '(no recipe)'
if not controller:craft(cItem, count) then
item.status = '(missing ingredients)'
error('failed')
end
item.status = '(crafting)'
end)
if s then
break -- successfully requested crafting
end
count = math.floor(count / 2)
end
end
end
end
function getAutocraftItems()
local t = Util.readTable('resource.limits') or { }
local itemList = { }
for _,res in pairs(t) do
if res.auto then
res.count = 4 -- this could be higher to increase autocrafting speed
table.insert(itemList, res)
end
end
return itemList
end
local function getItemWithQty(items, res, ignoreDamage)
local item = getItem(items, res, ignoreDamage)
if item then
if ignoreDamage then
local count = 0
for _,v in pairs(items) do
if item.name == v.name and item.nbtHash == v.nbtHash then
if item.maxDamage > 0 or item.damage == v.damage then
count = count + v.count
end
end
end
item.count = count
end
end
return item
end
function watchResources(items)
local itemList = { }
local t = Util.readTable('resource.limits') or { }
for k, res in pairs(t) do
res.low = tonumber(res.low) -- backwards compatibility
local item = getItemWithQty(items, res, res.ignoreDamage)
if not item then
item = {
damage = res.damage,
nbtHash = res.nbtHash,
name = res.name,
displayName = res.displayName,
count = 0
}
end
if res.low and item.count < res.low then
if res.ignoreDamage then
item.damage = 0
end
table.insert(itemList, {
damage = item.damage,
nbtHash = item.nbtHash,
count = res.low - item.count,
name = item.name,
displayName = item.displayName,
status = '',
rsControl = res.rsControl,
})
end
if res.rsControl and res.rsDevice and res.rsSide then
pcall(function()
device[res.rsDevice].setOutput(res.rsSide, item.count < res.low)
end)
end
end
return itemList
end
itemPage = UI.Page {
backgroundColor = colors.lightGray,
titleBar = UI.TitleBar {
title = 'Limit Resource',
previousPage = true,
event = 'form_cancel',
backgroundColor = colors.green
},
displayName = UI.Window {
x = 5, y = 3, width = UI.term.width - 10, height = 3,
},
form = UI.Form {
x = 4, y = 6, height = 10, rex = -4,
[1] = UI.TextEntry {
width = 7,
backgroundColor = colors.gray,
backgroundFocusColor = colors.gray,
formLabel = 'Min', formKey = 'low', help = 'Craft if below min'
},
[2] = UI.Chooser {
width = 7,
formLabel = 'Autocraft', formKey = 'auto',
nochoice = 'No',
choices = {
{ name = 'Yes', value = true },
{ name = 'No', value = false },
},
help = 'Craft until out of ingredients'
},
[3] = UI.Chooser {
width = 7,
formLabel = 'Ignore Dmg', formKey = 'ignoreDamage',
nochoice = 'No',
choices = {
{ name = 'Yes', value = true },
{ name = 'No', value = false },
},
help = 'Ignore damage of item'
},
[4] = UI.Chooser {
width = 7,
formLabel = 'RS Control', formKey = 'rsControl',
nochoice = 'No',
choices = {
{ name = 'Yes', value = true },
{ name = 'No', value = false },
},
help = 'Control via redstone'
},
[5] = UI.Chooser {
width = 25,
formLabel = 'RS Device', formKey = 'rsDevice',
--choices = devices,
help = 'Redstone Device'
},
[6] = UI.Chooser {
width = 10,
formLabel = 'RS Side', formKey = 'rsSide',
--nochoice = 'No',
choices = {
{ name = 'up', value = 'up' },
{ name = 'down', value = 'down' },
{ name = 'east', value = 'east' },
{ name = 'north', value = 'north' },
{ name = 'west', value = 'west' },
{ name = 'south', value = 'south' },
},
help = 'Output side'
},
},
statusBar = UI.StatusBar { }
}
function itemPage.displayName:draw()
local item = self.parent.item
local str = string.format('Name: %s\nDamage: %d', item.displayName, item.damage)
if item.nbtHash then
str = str .. string.format('\nNBT: %s\n', item.nbtHash)
end
self:setCursorPos(1, 1)
self:print(str)
end
function itemPage:enable(item)
self.item = item
self.form:setValues(item)
self.titleBar.title = item.name
local devices = self.form[5].choices
Util.clear(devices)
for _,device in pairs(device) do
if device.setOutput then
table.insert(devices, { name = device.name, value = device.name })
end
end
if Util.size(devices) == 0 then
table.insert(devices, { name = 'None found', values = '' })
end
UI.Page.enable(self)
self:focusFirst()
end
function itemPage:eventHandler(event)
if event.type == 'form_cancel' then
UI:setPreviousPage()
elseif event.type == 'focus_change' then
self.statusBar:setStatus(event.focused.help)
self.statusBar:draw()
elseif event.type == 'form_complete' then
local values = self.form.values
local t = Util.readTable('resource.limits') or { }
local keys = { 'name', 'displayName', 'auto', 'low', 'damage',
'maxDamage', 'nbtHash', 'ignoreDamage',
'rsControl', 'rsDevice', 'rsSide', }
local filtered = { }
for _,key in pairs(keys) do
filtered[key] = values[key]
end
filtered.low = tonumber(filtered.low)
filtered.ignoreDamage = filtered.ignoreDamage == true
filtered.auto = filtered.auto == true
filtered.rsControl = filtered.rsControl == true
if filtered.ignoreDamage then
filtered.damage = 0
end
t[uniqueKey(filtered)] = filtered
Util.writeTable('resource.limits', t)
UI:setPreviousPage()
else
return UI.Page.eventHandler(self, event)
end
return true
end
listingPage = UI.Page {
menuBar = UI.MenuBar {
buttons = {
{ text = 'Forget', event = 'forget' },
},
},
grid = UI.Grid {
y = 2, height = UI.term.height - 2,
columns = {
{ heading = 'Name', key = 'displayName', width = UI.term.width - 14 },
{ heading = 'Qty', key = 'count', width = 5 },
{ heading = 'Min', key = 'low', width = 4 },
},
sortColumn = 'lname',
},
statusBar = UI.StatusBar {
backgroundColor = colors.gray,
width = UI.term.width,
filterText = UI.Text {
x = 2, width = 6,
value = 'Filter',
},
filter = UI.TextEntry {
x = 9, rex = -12,
limit = 50,
},
refresh = UI.Button {
rx = -9, width = 8,
text = 'Refresh',
event = 'refresh',
},
},
accelerators = {
r = 'refresh',
q = 'quit',
}
}
function listingPage.grid:getRowTextColor(row, selected)
if row.is_craftable then -- not implemented
return colors.yellow
end
return UI.Grid:getRowTextColor(row, selected)
end
function listingPage.grid:getDisplayValues(row)
row = Util.shallowCopy(row)
row.count = Util.toBytes(row.count)
if row.low then
row.low = Util.toBytes(row.low)
end
return row
end
function listingPage.statusBar:draw()
return UI.Window.draw(self)
end
function listingPage.statusBar.filter:eventHandler(event)
if event.type == 'mouse_rightclick' then
self.value = ''
self:draw()
local page = UI:getCurrentPage()
page.filter = nil
page:applyFilter()
page.grid:draw()
page:setFocus(self)
end
return UI.TextEntry.eventHandler(self, event)
end
function listingPage:eventHandler(event)
if event.type == 'quit' then
UI:exitPullEvents()
elseif event.type == 'grid_select' then
local selected = event.selected
UI:setPage('item', selected)
elseif event.type == 'refresh' then
self:refresh()
self.grid:draw()
self.statusBar.filter:focus()
elseif event.type == 'forget' then
local item = self.grid:getSelected()
if item then
local resources = Util.readTable('resource.limits') or { }
resources[uniqueKey(item)] = nil
Util.writeTable('resource.limits', resources)
self.statusBar:timedStatus('Forgot: ' .. item.name, 3)
self:refresh()
self.grid:draw()
end
elseif event.type == 'text_change' then
self.filter = event.text
if #self.filter == 0 then
self.filter = nil
end
self:applyFilter()
self.grid:draw()
self.statusBar.filter:focus()
else
UI.Page.eventHandler(self, event)
end
return true
end
function listingPage:enable()
self:refresh()
self:setFocus(self.statusBar.filter)
UI.Page.enable(self)
end
function listingPage:refresh()
self.allItems = controller:listItems()
mergeResources(self.allItems)
self:applyFilter()
end
function listingPage:applyFilter()
local t = filterItems(self.allItems, self.filter)
self.grid:setValues(t)
end
local function jobMonitor(jobList)
local mon = Peripheral.getByType('monitor')
if mon then
mon = UI.Device({
device = device.monitor,
textScale = .5,
})
else
mon = UI.Device({
device = Terminal.getNullTerm(term.current())
})
end
jobListGrid = UI.Grid {
parent = mon,
sortColumn = 'displayName',
columns = {
{ heading = 'Qty', key = 'count', width = 6 },
{ heading = 'Crafting', key = 'displayName', width = mon.width / 2 - 10 },
{ heading = 'Status', key = 'status', width = mon.width - 10 },
},
}
return jobListGrid
end
UI:setPages({
listing = listingPage,
item = itemPage,
})
UI:setPage(listingPage)
listingPage:setFocus(listingPage.statusBar.filter)
local jobListGrid = jobMonitor()
jobListGrid:draw()
jobListGrid:sync()
function craftingThread()
while true do
os.sleep(5)
--pcall(function()
local items = controller:listItems()
if not controller:isOnline() then
jobListGrid.parent:clear()
jobListGrid.parent:centeredWrite(math.ceil(jobListGrid.parent.height/2), 'Power failure')
jobListGrid:sync()
elseif Util.size(items) == 0 then
jobListGrid.parent:clear()
jobListGrid.parent:centeredWrite(math.ceil(jobListGrid.parent.height/2), 'No items in system')
jobListGrid:sync()
else
local itemList = watchResources(items)
jobListGrid:setValues(itemList)
jobListGrid:draw()
jobListGrid:sync()
craftItems(itemList, items)
--jobListGrid:update()
jobListGrid:draw()
jobListGrid:sync()
itemList = getAutocraftItems() -- autocrafted items don't show on job monitor
craftItems(itemList, items)
end
--end)
end
end
UI:pullEvents(craftingThread)
UI.term:reset()
jobListGrid.parent:reset()

1
sys/apps/scripts/abort Normal file
View File

@@ -0,0 +1 @@
turtle.abortAction()

121
sys/apps/scripts/follow Normal file
View File

@@ -0,0 +1,121 @@
local function follow(id)
require = requireInjector(getfenv(1))
local GPS = require('gps')
local Socket = require('socket')
local Point = require('point')
local process = require('process')
turtle.status = 'follow ' .. id
local pt = GPS.getPointAndHeading()
if not pt or not pt.heading then
error('turtle: No GPS found')
end
turtle.setPoint(pt)
local socket = Socket.connect(id, 161)
if not socket then
error('turtle: Unable to connect to ' .. id)
return
end
local lastPoint
local following = false
local followThread = process:newThread('follower', function()
while true do
local function getRemotePoint()
if not turtle.abort then
if socket:write({ type = 'gps' }) then
return socket:read(3)
end
end
end
-- sometimes gps will fail if moving
local pt, d
for i = 1, 3 do
pt, d = getRemotePoint()
if pt then
break
end
os.sleep(.5)
end
if not pt or turtle.abort then
error('Did not receive GPS location')
end
if not lastPoint or (lastPoint.x ~= pt.x or lastPoint.y ~= pt.y or lastPoint.z ~= pt.z) then
if following then
turtle.abort = true
while following do
os.sleep(.1)
end
turtle.abort = false
end
-- check if gps is inaccurate (player moving too fast)
if d < Point.pythagoreanDistance(turtle.point, pt) + 10 then
lastPoint = Point.copy(pt)
following = true
process:newThread('turtle_follow', function()
local pts = {
{ x = pt.x + 2, z = pt.z, y = pt.y },
{ x = pt.x - 2, z = pt.z, y = pt.y },
{ x = pt.x, z = pt.z + 2, y = pt.y },
{ x = pt.x, z = pt.z - 2, y = pt.y },
}
local cpt = Point.closest(turtle.point, pts)
local blocks = { }
local function addBlocks(tpt)
table.insert(blocks, tpt)
local apts = Point.adjacentPoints(tpt)
for _,apt in pairs(apts) do
table.insert(blocks, apt)
end
end
-- don't run into player
addBlocks(pt)
addBlocks({ x = pt.x, z = pt.z, y = pt.y + 1 })
if turtle.pathfind(cpt, blocks) then
turtle.headTowards(pt)
end
following = false
end)
end
end
os.sleep(.5)
end
end)
while true do
local e = process:pullEvent()
if e == 'terminate' or followThread:isDead() or e =='turtle_abort' then
process:threadEvent('terminate')
break
end
end
socket:close()
return true
end
local s, m = turtle.run(function() follow({COMPUTER_ID}) end)
if not s and m then
error(m)
end

1
sys/apps/scripts/goHome Normal file
View File

@@ -0,0 +1 @@
turtle.run(turtle.gotoGPSHome)

30
sys/apps/scripts/moveTo Normal file
View File

@@ -0,0 +1,30 @@
turtle.run(function()
require = requireInjector(getfenv(1))
local GPS = require('gps')
local Socket = require('socket')
local id = {COMPUTER_ID}
local pt = GPS.getPointAndHeading()
if not pt or not pt.heading then
error('turtle: No GPS found')
end
turtle.setPoint(pt)
local socket = Socket.connect(id, 161)
if not socket then
error('turtle: Unable to connect to ' .. id)
end
socket:write({ type = 'gps' })
local pt = socket:read(3)
if not pt then
error('turtle: No GPS response')
end
if not turtle.pathfind(pt, nil, 64) then
error('Unable to go to location')
end
end)

100
sys/apps/scripts/obsidian Normal file
View File

@@ -0,0 +1,100 @@
require = requireInjector(getfenv(1))
local Point = require('point')
local checkedNodes, nodes
local function addNode(node)
for i = 0, 3 do
local hi = turtle.getHeadingInfo(i)
local testNode = { x = node.x + hi.xd, z = node.z + hi.zd }
local key = table.concat({ testNode.x, testNode.z }, ':')
if not checkedNodes[key] then
nodes[key] = testNode
end
end
end
local function findObsidian()
repeat
local node = { x = turtle.point.x, z = turtle.point.z }
local key = table.concat({ node.x, node.z }, ':')
checkedNodes[key] = true
nodes[key] = nil
local _,b = turtle.inspectDown()
if b and (b.name == 'minecraft:lava' or b.name == 'minecraft:flowing_lava') then
if turtle.selectSlot('minecraft:water_bucket') then
while true do
if turtle.up() then
break
end
print('stuck')
end
turtle.placeDown()
os.sleep(2)
turtle.placeDown()
turtle.down()
turtle.select(1)
_, b = turtle.inspectDown()
end
end
if turtle.getCount(16) > 0 then
print('Inventory full')
print('Enter to continue...')
read()
end
if b and b.name == 'minecraft:obsidian' then
turtle.digDown()
addNode(node)
else
turtle.digDown()
end
print(string.format('%d nodes remaining', Util.size(nodes)))
if Util.size(nodes) == 0 then
break
end
local node = Point.closest(turtle.point, nodes)
if not turtle.gotoPoint(node) then
break
end
until turtle.abort
end
turtle.reset()
turtle.setPolicy(turtle.policies.digOnly)
local s, m = turtle.run(function()
repeat
checkedNodes = { }
nodes = { }
local _,b = turtle.inspectDown()
if not b or b.name ~= 'minecraft:obsidian' then
break
end
findObsidian()
if not turtle.selectSlot('minecraft:water_bucket') then
break
end
turtle.goto(0, 0)
turtle.placeDown()
os.sleep(2)
turtle.placeDown()
turtle.down()
turtle.select(1)
until turtle.abort
end)
turtle.goto(0, 0, 0, 0)
turtle.reset()
if not s and m then
error(m)
end

1
sys/apps/scripts/reboot Normal file
View File

@@ -0,0 +1 @@
os.reboot()

1
sys/apps/scripts/setHome Normal file
View File

@@ -0,0 +1 @@
turtle.run(turtle.setGPSHome)

View File

@@ -0,0 +1 @@
os.shutdown()

73
sys/apps/scripts/summon Normal file
View File

@@ -0,0 +1,73 @@
local function summon(id)
require = requireInjector(getfenv(1))
local GPS = require('gps')
local Socket = require('socket')
local Point = require('point')
turtle.status = 'GPSing'
turtle.setPoint({ x = 0, y = 0, z = 0, heading = 0 })
local pts = {
[ 1 ] = { x = 0, z = 0, y = 0 },
[ 2 ] = { x = 4, z = 0, y = 0 },
[ 3 ] = { x = 2, z = -2, y = 2 },
[ 4 ] = { x = 2, z = 2, y = 2 },
}
local tFixes = { }
local socket = Socket.connect(id, 161)
if not socket then
error('turtle: Unable to connect to ' .. id)
end
local function getDistance()
socket:write({ type = 'ping' })
local _, d = socket:read(5)
return d
end
local function doGPS()
tFixes = { }
for i = 1, 4 do
if not turtle.gotoPoint(pts[i]) then
error('turtle: Unable to perform GPS maneuver')
end
local distance = getDistance()
if not distance then
error('turtle: No response from ' .. id)
end
table.insert(tFixes, {
position = vector.new(turtle.point.x, turtle.point.y, turtle.point.z),
distance = distance
})
end
return true
end
if not doGPS() then
turtle.turnAround()
turtle.setPoint({ x = 0, y = 0, z = 0, heading = 0})
if not doGPS() then
socket:close()
return false
end
end
socket:close()
local pos = GPS.trilaterate(tFixes)
if pos then
local pt = { x = pos.x, y = pos.y, z = pos.z }
local _, h = Point.calculateMoves(turtle.getPoint(), pt)
local hi = turtle.getHeadingInfo(h)
turtle.status = 'recalling'
turtle.pathfind({ x = pt.x - hi.xd, z = pt.z - hi.zd, y = pt.y - hi.yd, heading = h })
else
error("turtle: Could not determine position")
end
end
turtle.run(function() summon({COMPUTER_ID}) end)

1
sys/apps/scripts/update Normal file
View File

@@ -0,0 +1 @@
shell.run('/apps/update.lua')

623
sys/apps/shell Normal file
View File

@@ -0,0 +1,623 @@
local parentShell = shell
shell = { }
local sandboxEnv = Util.shallowCopy(getfenv(1))
setmetatable(sandboxEnv, { __index = _G })
local DIR = (parentShell and parentShell.dir()) or ""
local PATH = (parentShell and parentShell.path()) or ".:/rom/programs"
local ALIASES = (parentShell and parentShell.aliases()) or {}
local tCompletionInfo = (parentShell and parentShell.getCompletionInfo()) or {}
local bExit = false
local tProgramStack = {}
local function parseCommandLine( ... )
local sLine = table.concat( { ... }, " " )
local tWords = {}
local bQuoted = false
for match in string.gmatch( sLine .. "\"", "(.-)\"" ) do
if bQuoted then
table.insert( tWords, match )
else
for m in string.gmatch( match, "[^ \t]+" ) do
table.insert( tWords, m )
end
end
bQuoted = not bQuoted
end
return table.remove(tWords, 1), tWords
end
-- Install shell API
function shell.run(...)
local path, args = parseCommandLine(...)
path = shell.resolveProgram(path)
if path then
tProgramStack[#tProgramStack + 1] = path
local oldTitle
if multishell and multishell.getTitle then
oldTitle = multishell.getTitle(multishell.getCurrent())
multishell.setTitle(multishell.getCurrent(), fs.getName(path))
end
local result, err = os.run(Util.shallowCopy(sandboxEnv), path, unpack(args))
if multishell then
local title = 'shell'
if #tProgramStack > 0 then
title = fs.getName(tProgramStack[#tProgramStack])
end
multishell.setTitle(multishell.getCurrent(), oldTitle or 'shell')
end
return result, err
end
return false, 'No such program'
end
function shell.exit()
bExit = true
end
function shell.dir() return DIR end
function shell.setDir(d) DIR = d end
function shell.path() return PATH end
function shell.setPath(p) PATH = p end
function shell.resolve( _sPath )
local sStartChar = string.sub( _sPath, 1, 1 )
if sStartChar == "/" or sStartChar == "\\" then
return fs.combine( "", _sPath )
else
return fs.combine(DIR, _sPath )
end
end
function shell.resolveProgram( _sCommand )
local sPath = PATH or ''
if ALIASES[ _sCommand ] ~= nil then
_sCommand = ALIASES[ _sCommand ]
end
local path = shell.resolve(_sCommand)
if fs.exists(path) and not fs.isDir(path) then
return path
end
if fs.exists(path .. '.lua') then
return path .. '.lua'
end
-- If the path is a global path, use it directly
local sStartChar = string.sub( _sCommand, 1, 1 )
if sStartChar == "/" or sStartChar == "\\" then
local sPath = fs.combine( "", _sCommand )
if fs.exists( sPath ) and not fs.isDir( sPath ) then
return sPath
end
return nil
end
-- Otherwise, look on the path variable
for sPath in string.gmatch(sPath, "[^:]+") do
sPath = fs.combine( shell.resolve(sPath), _sCommand )
if fs.exists( sPath ) and not fs.isDir( sPath ) then
return sPath
end
if fs.exists(sPath .. '.lua') then
return sPath .. '.lua'
end
end
-- Not found
return nil
end
function shell.programs( _bIncludeHidden )
local tItems = {}
-- Add programs from the path
for sPath in string.gmatch(PATH, "[^:]+") do
sPath = shell.resolve(sPath)
if fs.isDir( sPath ) then
local tList = fs.list( sPath )
for n,sFile in pairs( tList ) do
if not fs.isDir( fs.combine( sPath, sFile ) ) and
(_bIncludeHidden or string.sub( sFile, 1, 1 ) ~= ".") then
tItems[ sFile ] = true
end
end
end
end
-- Sort and return
local tItemList = {}
for sItem, b in pairs( tItems ) do
table.insert( tItemList, sItem )
end
table.sort( tItemList )
return tItemList
end
function shell.complete(sLine) end
function shell.completeProgram(sProgram) end
function shell.setCompletionFunction(sProgram, fnComplete)
tCompletionInfo[sProgram] = { fnComplete = fnComplete }
end
function shell.getCompletionInfo()
return tCompletionInfo
end
function shell.getRunningProgram()
return tProgramStack[#tProgramStack]
end
function shell.set(name, value)
getfenv(1)[name] = value
end
function shell.get(name)
return getfenv(1)[name]
end
function shell.setAlias( _sCommand, _sProgram )
ALIASES[ _sCommand ] = _sProgram
end
function shell.clearAlias( _sCommand )
ALIASES[ _sCommand ] = nil
end
function shell.aliases()
local tCopy = {}
for sAlias, sCommand in pairs(ALIASES) do
tCopy[sAlias] = sCommand
end
return tCopy
end
function shell.newTab(tabInfo, ...)
local path, args = parseCommandLine(...)
path = shell.resolveProgram(path)
if path then
tabInfo.path = path
tabInfo.env = sandboxEnv
tabInfo.args = args
tabInfo.title = fs.getName(path)
return multishell.openTab(tabInfo)
end
return nil, 'No such program'
end
function shell.openTab( ... )
return shell.newTab({ }, ...)
end
function shell.openForegroundTab( ... )
return shell.newTab({ focused = true }, ...)
end
function shell.openHiddenTab( ... )
return shell.newTab({ hidden = true }, ...)
end
function shell.switchTab(tabId)
multishell.setFocus(tabId)
end
local tArgs = { ... }
if #tArgs > 0 then
-- "shell x y z"
-- Run the program specified in this new shell
local s, m = shell.run( ... )
if not s and m ~= 'Terminated' then
error(m or '')
end
return s, m
end
require = requireInjector(getfenv(1))
local Config = require('config')
local History = require('history')
local config = {
standard = {
textColor = colors.white,
commandTextColor = colors.lightGray,
directoryTextColor = colors.gray,
directoryBackgroundColor = colors.black,
promptTextColor = colors.gray,
promptBackgroundColor = colors.black,
directoryColor = colors.gray,
},
color = {
textColor = colors.white,
commandTextColor = colors.yellow,
directoryTextColor = colors.orange,
directoryBackgroundColor = colors.black,
promptTextColor = colors.blue,
promptBackgroundColor = colors.black,
directoryColor = colors.green,
},
displayDirectory = true,
}
--Config.load('shell', config)
local _colors = config.standard
if term.isColor() then
_colors = config.color
end
local function autocompleteFile(results, words)
local function getBaseDir(path)
if #path > 1 then
if path:sub(-1) ~= '/' then
path = fs.getDir(path)
end
end
if path:sub(1, 1) == '/' then
path = fs.combine(path, '')
else
path = fs.combine(shell.dir(), path)
end
while not fs.isDir(path) do
path = fs.getDir(path)
end
return path
end
local function getRawPath(path)
local baseDir = ''
if path:sub(1, 1) ~= '/' then
baseDir = shell.dir()
end
if #path > 1 then
if path:sub(-1) ~= '/' then
path = fs.getDir(path)
end
end
if fs.isDir(fs.combine(baseDir, path)) then
return path
end
return fs.getDir(path)
end
local match = words[#words] or ''
local startDir = getBaseDir(match)
local rawPath = getRawPath(match)
if fs.isDir(startDir) then
local files = fs.list(startDir)
debug({ rawPath, startDir })
for _,f in pairs(files) do
local path = fs.combine(rawPath, f)
if fs.isDir(fs.combine(startDir, f)) then
results[path .. '/'] = 'directory'
else
results[path .. ' '] = 'program'
end
end
end
end
local function autocompleteProgram(results, words)
if #words == 1 then
local files = shell.programs(true)
for _,f in ipairs(files) do
results[f .. ' '] = 'program'
end
for f in pairs(ALIASES) do
results[f .. ' '] = 'program'
end
end
end
local function autocompleteArgument(results, program, words)
local word = ''
if #words > 1 then
word = words[#words]
end
local tInfo = tCompletionInfo[program]
local args = tInfo.fnComplete(shell, #words - 1, word, words)
if args then
Util.filterInplace(args, function(f)
return not Util.key(args, f .. '/')
end)
for _,arg in ipairs(args) do
results[word .. arg] = 'argument'
end
end
end
local function autocomplete(line, suggestions)
local words = { }
for word in line:gmatch("%S+") do
table.insert(words, word)
end
if line:match(' $') then
table.insert(words, '')
end
local results = { }
if #words == 0 then
files = autocompleteFile(results, words)
else
local program = shell.resolveProgram(words[1])
if tCompletionInfo[program] then
autocompleteArgument(results, program, words)
else
autocompleteProgram(results, words)
autocompleteFile(results, words)
end
end
local match = words[#words] or ''
local files = { }
for f in pairs(results) do
if f:sub(1, #match) == match then
table.insert(files, f)
end
end
if #files == 1 then
words[#words] = files[1]
return table.concat(words, ' ')
elseif #files > 1 and suggestions then
print()
local word = words[#words] or ''
local prefix = word:match("(.*/)") or ''
if #prefix > 0 then
for _,f in ipairs(files) do
if f:match("^" .. prefix) ~= prefix then
prefix = ''
break
end
end
end
local tDirs, tFiles = { }, { }
for _,f in ipairs(files) do
if results[f] == 'directory' then
f = f:gsub(prefix, '', 1)
table.insert(tDirs, f)
else
f = f:gsub(prefix, '', 1)
table.insert(tFiles, f)
end
end
table.sort(tDirs)
table.sort(tFiles)
if #tDirs > 0 and #tDirs < #tFiles then
local w = term.getSize()
local nMaxLen = w / 8
for n, sItem in pairs(files) do
nMaxLen = math.max(string.len(sItem) + 1, nMaxLen)
end
local nCols = math.floor(w / nMaxLen)
if #tDirs < nCols then
for i = #tDirs + 1, nCols do
table.insert(tDirs, '')
end
end
end
if #tDirs > 0 then
textutils.tabulate(_colors.directoryColor, tDirs, colors.white, tFiles)
else
textutils.tabulate(colors.white, tFiles)
end
term.setTextColour(_colors.promptTextColor)
term.setBackgroundColor(_colors.promptBackgroundColor)
write("$ " )
term.setTextColour(_colors.commandTextColor)
term.setBackgroundColor(colors.black)
return line
elseif #files > 1 then
-- ugly (complete as much as possible)
local word = words[#words] or ''
local i = #word + 1
while true do
local ch
for _,f in ipairs(files) do
if #f < i then
words[#words] = string.sub(f, 1, i - 1)
return table.concat(words, ' ')
end
if not ch then
ch = string.sub(f, i, i)
elseif string.sub(f, i, i) ~= ch then
if i == #word + 1 then
return
end
words[#words] = string.sub(f, 1, i - 1)
return table.concat(words, ' ')
end
end
i = i + 1
end
end
end
local function shellRead(_tHistory )
term.setCursorBlink( true )
local sLine = ""
local nHistoryPos
local nPos = 0
local lastPattern
local w = term.getSize()
local sx = term.getCursorPos()
local function redraw( sReplace )
local nScroll = 0
if sx + nPos >= w then
nScroll = (sx + nPos) - w
end
local cx,cy = term.getCursorPos()
term.setCursorPos( sx, cy )
if sReplace then
term.write( string.rep( sReplace, math.max( string.len(sLine) - nScroll, 0 ) ) )
else
term.write( string.sub( sLine, nScroll + 1 ) )
end
term.setCursorPos( sx + nPos - nScroll, cy )
end
while true do
local sEvent, param, param2 = os.pullEventRaw()
if sEvent == "char" then
sLine = string.sub( sLine, 1, nPos ) .. param .. string.sub( sLine, nPos + 1 )
nPos = nPos + 1
redraw()
elseif sEvent == "paste" then
sLine = string.sub( sLine, 1, nPos ) .. param .. string.sub( sLine, nPos + 1 )
nPos = nPos + string.len( param )
redraw()
elseif sEvent == 'mouse_click' and param == 2 then
redraw(string.rep(' ', #sLine))
sLine = ''
nPos = 0
redraw()
elseif sEvent == 'terminate' then
bExit = true
break
elseif sEvent == "key" then
if param == keys.enter then
-- Enter
break
elseif param == keys.tab then
if nPos == #sLine then
local showSuggestions = lastPattern == sLine
lastPattern = sLine
local cline = autocomplete(sLine, showSuggestions)
if cline then
sLine = cline
nPos = #sLine
redraw()
end
end
elseif param == keys.left then
if nPos > 0 then
nPos = nPos - 1
redraw()
end
elseif param == keys.right then
if nPos < string.len(sLine) then
redraw(" ")
nPos = nPos + 1
redraw()
end
elseif param == keys.up or param == keys.down then
if _tHistory then
redraw(" ")
if param == keys.up then
if nHistoryPos == nil then
if #_tHistory > 0 then
nHistoryPos = #_tHistory
end
elseif nHistoryPos > 1 then
nHistoryPos = nHistoryPos - 1
end
else
if nHistoryPos == #_tHistory then
nHistoryPos = nil
elseif nHistoryPos ~= nil then
nHistoryPos = nHistoryPos + 1
end
end
if nHistoryPos then
sLine = _tHistory[nHistoryPos]
nPos = string.len( sLine )
else
sLine = ""
nPos = 0
end
redraw()
end
elseif param == keys.backspace then
if nPos > 0 then
redraw(" ")
sLine = string.sub( sLine, 1, nPos - 1 ) .. string.sub( sLine, nPos + 1 )
nPos = nPos - 1
redraw()
end
elseif param == keys.home then
redraw(" ")
nPos = 0
redraw()
elseif param == keys.delete then
if nPos < string.len(sLine) then
redraw(" ")
sLine = string.sub( sLine, 1, nPos ) .. string.sub( sLine, nPos + 2 )
redraw()
end
elseif param == keys["end"] then
redraw(" ")
nPos = string.len(sLine)
redraw()
end
elseif sEvent == "term_resize" then
w = term.getSize()
redraw()
end
end
local cx, cy = term.getCursorPos()
term.setCursorPos( w + 1, cy )
print()
term.setCursorBlink( false )
return sLine
end
local history = History.load('usr/.shell_history', 25)
while not bExit do
if config.displayDirectory then
term.setTextColour(_colors.directoryTextColor)
term.setBackgroundColor(_colors.directoryBackgroundColor)
print('==' .. os.getComputerLabel() .. ':/' .. DIR)
end
term.setTextColour(_colors.promptTextColor)
term.setBackgroundColor(_colors.promptBackgroundColor)
write("$ " )
term.setTextColour(_colors.commandTextColor)
term.setBackgroundColor(colors.black)
local sLine = shellRead(history.entries)
if bExit then -- terminated
break
end
sLine = Util.trim(sLine)
if #sLine > 0 and sLine ~= 'exit' then
history.add(sLine)
end
term.setTextColour(_colors.textColor)
if #sLine > 0 then
local result, err = shell.run( sLine )
if not result then
printError(err)
end
end
end

631
sys/apps/simpleMiner.lua Normal file
View File

@@ -0,0 +1,631 @@
require = requireInjector(getfenv(1))
local Point = require('point')
local Logger = require('logger')
if device and device.wireless_modem then
Logger.setWirelessLogging()
end
local args = { ... }
local options = {
chunks = { arg = 'c', type = 'number', value = -1,
desc = 'Number of chunks to mine' },
depth = { arg = 'd', type = 'number', value = 9000,
desc = 'Mining depth' },
-- enderChest = { arg = 'e', type = 'flag', value = false,
-- desc = 'Use ender chest' },
resume = { arg = 'r', type = 'flag', value = false,
desc = 'Resume mining' },
fortunePick = { arg = 'p', type = 'string', value = nil,
desc = 'Pick to use with CCTweaks toolhost' },
setTrash = { arg = 's', type = 'flag', value = false,
desc = 'Set trash items' },
help = { arg = 'h', type = 'flag', value = false,
desc = 'Displays the options' },
}
local fortuneBlocks = {
[ 'minecraft:redstone_ore' ] = true,
[ 'minecraft:lapis_ore' ] = true,
[ 'minecraft:coal_ore' ] = true,
[ 'minecraft:diamond_ore' ] = true,
[ 'minecraft:emerald_ore' ] = true,
}
local MIN_FUEL = 7500
local LOW_FUEL = 1500
local MAX_FUEL = 100000
if not term.isColor() then
MAX_FUEL = 20000
end
local mining = {
diameter = 1,
chunkIndex = 0,
chunks = -1,
}
local trash
local boreDirection
function getChunkCoordinates(diameter, index, x, z)
local dirs = { -- circumference of grid
{ xd = 0, zd = 1, heading = 1 }, -- south
{ xd = -1, zd = 0, heading = 2 },
{ xd = 0, zd = -1, heading = 3 },
{ xd = 1, zd = 0, heading = 0 } -- east
}
-- always move east when entering the next diameter
if index == 0 then
dirs[4].x = x + 16
dirs[4].z = z
return dirs[4]
end
dir = dirs[math.floor(index / (diameter - 1)) + 1]
dir.x = x + dir.xd * 16
dir.z = z + dir.zd * 16
return dir
end
function getBoreLocations(x, z)
local locations = {}
while true do
local a = math.abs(z)
local b = math.abs(x)
if x > 0 and z > 0 or
x < 0 and z < 0 then
-- rotate coords
a = math.abs(x)
b = math.abs(z)
end
if (a % 5 == 0 and b % 5 == 0) or
(a % 5 == 2 and b % 5 == 1) or
(a % 5 == 4 and b % 5 == 2) or
(a % 5 == 1 and b % 5 == 3) or
(a % 5 == 3 and b % 5 == 4) then
table.insert(locations, { x = x, z = z, y = 0 })
end
if z % 2 == 0 then -- forward dir
if (x + 1) % 16 == 0 then
z = z + 1
else
x = x + 1
end
else
if (x - 1) % 16 == 15 then
if (z + 1) % 16 == 0 then
break
end
z = z + 1
else
x = x - 1
end
end
end
return locations
end
-- get the bore location closest to the miner
local function getClosestLocation(points, b)
local key = 1
local leastMoves = 9000
for k,pt in pairs(points) do
local moves = Point.calculateMoves(turtle.point, pt)
if moves < leastMoves then
key = k
leastMoves = moves
if leastMoves == 0 then
break
end
end
end
return table.remove(points, key)
end
function getCornerOf(c)
return math.floor(c.x / 16) * 16, math.floor(c.z / 16) * 16
end
function nextChunk()
local x, z = getCornerOf({ x = mining.x, z = mining.z })
local points = math.pow(mining.diameter, 2) - math.pow(mining.diameter-2, 2)
mining.chunkIndex = mining.chunkIndex + 1
if mining.chunkIndex >= points then
mining.diameter = mining.diameter + 2
mining.chunkIndex = 0
end
if mining.chunks ~= -1 then
local chunks = math.pow(mining.diameter-2, 2) + mining.chunkIndex
if chunks >= mining.chunks then
return false
end
end
local nc = getChunkCoordinates(mining.diameter, mining.chunkIndex, x, z)
mining.locations = getBoreLocations(nc.x, nc.z)
-- enter next chunk
mining.x = nc.x
mining.z = nc.z
Util.writeTable('mining.progress', mining)
return true
end
function addTrash()
if not trash then
trash = { }
end
local slots = turtle.getFilledSlots()
for k,slot in pairs(slots) do
trash[slot.iddmg] = true
end
trash['minecraft:bucket:0'] = nil
Util.writeTable('mining.trash', trash)
end
function log(text)
print(text)
Logger.log('mineWorker', text)
end
function status(status)
turtle.status = status
log(status)
end
function refuel()
if turtle.getFuelLevel() < MIN_FUEL then
local oldStatus = turtle.status
status('refueling')
if turtle.selectSlot('minecraft:coal:0') then
local qty = turtle.getItemCount()
print('refueling ' .. qty)
turtle.refuel(qty)
end
if turtle.getFuelLevel() < MIN_FUEL then
log('desperate fueling')
turtle.eachFilledSlot(function(slot)
if turtle.getFuelLevel() < MIN_FUEL then
turtle.select(slot.index)
turtle.refuel(64)
end
end)
end
log('Fuel: ' .. turtle.getFuelLevel())
status(oldStatus)
end
turtle.select(1)
end
function enderChestUnload()
log('unloading')
turtle.select(1)
if not Util.tryTimed(5, function()
turtle.digDown()
return turtle.placeDown()
end) then
log('placedown failed')
else
turtle.reconcileInventory(slots, turtle.dropDown)
turtle.select(1)
turtle.drop(64)
turtle.digDown()
end
end
function safeGoto(x, z, y, h)
local oldStatus = turtle.status
while not turtle.pathfind({ x = x, z = z, y = y, heading = h }) do
--status('stuck')
if turtle.abort then
return false
end
--os.sleep(1)
end
turtle.status = oldStatus
return true
end
function safeGotoY(y)
local oldStatus = turtle.status
while not turtle.gotoY(y) do
status('stuck')
if turtle.abort then
return false
end
os.sleep(1)
end
turtle.status = oldStatus
return true
end
function makeWalkableTunnel(action, tpt, pt)
if action ~= 'turn' and not Point.compare(tpt, { x = 0, z = 0 }) then -- not at source
if not Point.compare(tpt, pt) then -- not at dest
local r, block = turtle.inspectUp()
if r and not turtle.isTurtleAtSide('top') then
if block.name ~= 'minecraft:cobblestone' and
block.name ~= 'minecraft:chest' then
turtle.digUp()
end
end
end
end
end
function normalChestUnload()
local oldStatus = turtle.status
status('unloading')
local pt = Util.shallowCopy(turtle.point)
safeGotoY(0)
turtle.setMoveCallback(function(action, tpt)
makeWalkableTunnel(action, tpt, { x = pt.x, z = pt.z })
end)
safeGoto(0, 0)
if not turtle.detectUp() then
error('no chest')
end
local slots = turtle.getFilledSlots()
for _,slot in pairs(slots) do
if not trash[slot.iddmg] and
slot.iddmg ~= 'minecraft:bucket:0' and
slot.id ~= 'minecraft:diamond_pickaxe' and
slot.id ~= 'cctweaks:toolHost' then
if slot.id ~= options.fortunePick.value then
turtle.select(slot.index)
turtle.dropUp(64)
end
end
end
turtle.select(1)
safeGoto(pt.x, pt.z, 0, pt.heading)
turtle.clearMoveCallback()
safeGotoY(pt.y)
status(oldStatus)
end
function ejectTrash()
local cobbleSlotCount = 0
turtle.eachFilledSlot(function(slot)
if slot.iddmg == 'minecraft:cobblestone:0' then
cobbleSlotCount = cobbleSlotCount + 1
end
if trash[slot.iddmg] then
-- retain 1 slot with cobble in order to indicate active mining
if slot.iddmg ~= 'minecraft:cobblestone:0' or cobbleSlotCount > 1 then
turtle.select(slot.index)
turtle.dropDown(64)
end
end
end)
end
function mineable(action)
local r, block = action.inspect()
if not r then
return false
end
if block.name == 'minecraft:chest' then
collectDrops(action.suck)
end
if turtle.getFuelLevel() < (MAX_FUEL - 1000) then
if block.name == 'minecraft:lava' or block.name == 'minecraft:flowing_lava' then
if turtle.selectSlot('minecraft:bucket:0') then
if action.place() then
log('Lava! ' .. turtle.getFuelLevel())
turtle.refuel()
log(turtle.getFuelLevel())
end
turtle.select(1)
end
return false
end
end
if action.side == 'bottom' then
return block.name
end
if trash[block.name .. ':0'] then
return false
end
return block.name
end
function fortuneDig(action, blockName)
if options.fortunePick.value and fortuneBlocks[blockName] then
turtle.selectSlot('cctweaks:toolHost')
turtle.equipRight()
turtle.selectSlot(options.fortunePick.value)
repeat until not turtle.dig()
turtle.selectSlot('minecraft:diamond_pickaxe')
turtle.equipRight()
turtle.select(1)
return true
end
end
function mine(action)
local blockName = mineable(action)
if blockName then
checkSpace()
--collectDrops(action.suck)
if not fortuneDig(action, blockName) then
action.dig()
end
end
end
function bore()
local loc = turtle.point
local level = loc.y
turtle.select(1)
status('boring down')
boreDirection = 'down'
while true do
if turtle.abort then
status('aborting')
return false
end
if loc.y <= -mining.depth then
break
end
if turtle.point.y < -2 then
-- turtle.setDigPolicy(turtle.digPolicies.turtleSafe)
end
mine(turtle.getAction('down'))
if not Util.tryTimed(3, turtle.down) then
break
end
if loc.y < level - 1 then
mine(turtle.getAction('forward'))
turtle.turnRight()
mine(turtle.getAction('forward'))
end
end
boreDirection = 'up'
status('boring up')
turtle.turnRight()
mine(turtle.getAction('forward'))
turtle.turnRight()
mine(turtle.getAction('forward'))
turtle.turnLeft()
while true do
if turtle.abort then
status('aborting')
return false
end
if turtle.point.y > -2 then
-- turtle.setDigPolicy(turtle.digPolicies.turtleSafe)
end
while not Util.tryTimed(3, turtle.up) do
status('stuck')
end
if turtle.status == 'stuck' then
status('boring up')
end
if loc.y >= level - 1 then
break
end
mine(turtle.getAction('forward'))
turtle.turnLeft()
mine(turtle.getAction('forward'))
end
if turtle.getFuelLevel() < LOW_FUEL then
refuel()
local veryMinFuel = Point.turtleDistance(turtle.point, { x = 0, y = 0, z = 0}) + 512
if turtle.getFuelLevel() < veryMinFuel then
log('Not enough fuel to continue')
return false
end
end
return true
end
function checkSpace()
if turtle.getItemCount(16) > 0 then
refuel()
local oldStatus = turtle.status
status('condensing')
ejectTrash()
turtle.condense()
local lastSlot = 16
if boreDirection == 'down' then
lastSlot = 15
end
if turtle.getItemCount(lastSlot) > 0 then
unload()
end
status(oldStatus)
turtle.select(1)
end
end
function collectDrops(suckAction)
for i = 1, 50 do
if not suckAction() then
break
end
checkSpace()
end
end
function Point.compare(pta, ptb)
if pta.x == ptb.x and pta.z == ptb.z then
if pta.y and ptb.y then
return pta.y == ptb.y
end
return true
end
return false
end
function inspect(action, name)
local r, block = action.inspect()
if r and block.name == name then
return true
end
end
function boreCommand()
local pt = getClosestLocation(mining.locations, turtle.point)
turtle.setMoveCallback(function(action, tpt)
makeWalkableTunnel(action, tpt, pt)
end)
safeGotoY(0)
safeGoto(pt.x, pt.z, 0)
turtle.clearMoveCallback()
-- location is either mined, currently being mined or is the
-- dropoff point for a turtle
if inspect(turtle.getAction('up'), 'minecraft:cobblestone') or
inspect(turtle.getAction('up'), 'minecraft:chest') or
inspect(turtle.getAction('down'), 'minecraft:cobblestone') then
return true
end
turtle.digUp()
turtle.placeUp('minecraft:cobblestone:0')
local success = bore()
safeGotoY(0) -- may have aborted
turtle.digUp()
if success then
turtle.placeDown('minecraft:cobblestone:0') -- cap with cobblestone to indicate this spot was mined out
end
return success
end
if not Util.getOptions(options, args) then
return
end
mining.depth = options.depth.value
mining.chunks = options.chunks.value
unload = normalChestUnload
--if options.enderChest.value then
-- unload = enderChestUnload
--end
mining.x = 0
mining.z = 0
mining.locations = getBoreLocations(0, 0)
trash = Util.readTable('mining.trash')
if options.resume.value then
mining = Util.readTable('mining.progress')
elseif fs.exists('mining.progress') then
print('use -r to resume')
read()
end
if not trash or options.setTrash.value then
print('Add trash blocks, press enter when ready')
read()
addTrash()
end
if not turtle.getSlot('minecraft:bucket:0') or
not turtle.getSlot('minecraft:cobblestone:0') then
print('Add bucket and cobblestone, press enter when ready')
read()
end
if options.fortunePick.value then
local s = turtle.getSlot(options.fortunePick.value)
if not s then
error('fortunePick not found: ' .. options.fortunePick.value)
end
if not turtle.getSlot('cctweaks:toolHost:0') then
error('CCTweaks tool host not found')
end
trash[s.iddmg] = nil
trash['minecraft:diamond_pickaxe:0'] = nil
trash['cctweaks:toolHost:0'] = nil
end
_G._p = trash
local function main()
repeat
while #mining.locations > 0 do
status('searching')
if not boreCommand() then
return
end
Util.writeTable('mining.progress', mining)
end
until not nextChunk()
end
turtle.run(function()
turtle.reset()
turtle.setPolicy(turtle.policies.digAttack)
turtle.setDigPolicy(turtle.digPolicies.turtleSafe)
unload()
status('mining')
local s, m = pcall(function() main() end)
if not s and m then
printError(m)
end
safeGotoY(0)
safeGoto(0, 0, 0, 0)
unload()
turtle.reset()
end)

View File

@@ -0,0 +1,170 @@
require = requireInjector(getfenv(1))
local Util = require('util')
local Event = require('event')
local UI = require('ui')
local RefinedProvider = require('refinedProvider')
local MEProvider = require('meProvider')
local storage = RefinedProvider()
if not storage:isValid() then
storage = MEProvider()
end
if not storage:isValid() then
error('Not connected to a storage device')
end
multishell.setTitle(multishell.getCurrent(), 'Storage Activity')
UI:configure('StorageActivity', ...)
local changedPage = UI.Page({
grid = UI.Grid({
columns = {
{ heading = 'Qty', key = 'qty', width = 5 },
{ heading = 'Change', key = 'change', width = 6 },
{ heading = 'Name', key = 'display_name', width = UI.term.width - 15 },
},
sortColumn = 'display_name',
rey = -6,
}),
buttons = UI.Window({
ry = -4,
height = 5,
backgroundColor = colors.gray,
prevButton = UI.Button({
event = 'previous',
backgroundColor = colors.lightGray,
x = 2,
y = 2,
height = 3,
width = 5,
text = ' < '
}),
resetButton = UI.Button({
event = 'reset',
backgroundColor = colors.lightGray,
x = 8,
y = 2,
height = 3,
rex = -8,
text = 'Reset'
}),
nextButton = UI.Button({
event = 'next',
backgroundColor = colors.lightGray,
rx = -5,
y = 2,
height = 3,
width = 5,
text = ' > '
})
}),
accelerators = {
q = 'quit',
}
})
function changedPage.grid:getDisplayValues(row)
row = Util.shallowCopy(row)
local ind = '+'
if row.change < 0 then
ind = ''
end
row.change = ind .. Util.toBytes(row.change)
row.qty = Util.toBytes(row.qty)
return row
end
function changedPage:eventHandler(event)
if event.type == 'reset' then
self.lastItems = nil
self.grid:setValues({ })
self.grid:clear()
self.grid:draw()
elseif event.type == 'next' then
self.grid:nextPage()
elseif event.type == 'previous' then
self.grid:previousPage()
elseif event.type == 'quit' then
Event.exitPullEvents()
else
return UI.Page.eventHandler(self, event)
end
return true
end
local function uniqueKey(item)
return table.concat({ item.name, item.damage, item.nbtHash }, ':')
end
function changedPage:refresh()
local t = storage:listItems('all')
if not t or Util.empty(t) then
self:clear()
self:centeredWrite(math.ceil(self.height/2), 'Communication failure')
return
end
for k,v in pairs(t) do
t[k] = Util.shallowCopy(v)
end
if not self.lastItems then
self.lastItems = t
self.grid:setValues({ })
else
local changedItems = {}
for _,v in pairs(self.lastItems) do
found = false
for k2,v2 in pairs(t) do
if uniqueKey(v) == uniqueKey(v2) then
if v.qty ~= v2.qty then
local c = Util.shallowCopy(v2)
c.lastQty = v.qty
table.insert(changedItems, c)
end
table.remove(t, k2)
found = true
break
end
end
-- New item
if not found then
local c = Util.shallowCopy(v)
c.lastQty = v.qty
c.qty = 0
table.insert(changedItems, c)
end
end
-- No items left
for k,v in pairs(t) do
v.lastQty = 0
table.insert(changedItems, v)
end
for k,v in pairs(changedItems) do
v.change = v.qty - v.lastQty
end
self.grid:setValues(changedItems)
end
self.grid:draw()
end
Event.addTimer(5, true, function()
changedPage:refresh()
changedPage:sync()
end)
UI:setPage(changedPage)
UI:pullEvents()
UI.term:reset()

905
sys/apps/storageManager.lua Normal file
View File

@@ -0,0 +1,905 @@
require = requireInjector(getfenv(1))
local Event = require('event')
local UI = require('ui')
local ME = require('me')
local Config = require('config')
local Logger = require('logger')
-- Must be a crafty turtle with duck antenna !
-- 3 wide monitor (any side of turtle)
-- Config location is /sys/config/storageMonitor
-- adjust directions in that file if needed
local config = {
trashDirection = 'up', -- trash /chest in relation to interface
turtleDirection = 'down', -- turtle in relation to interface
noCraftingStorage = 'false' -- no ME crafting (or ability to tell if powered - use with caution)
}
Config.load('storageMonitor', config)
if not device.tileinterface then
error('ME interface not found')
end
local duckAntenna
if device.workbench then
local oppositeSide = {
[ 'left' ] = 'right',
[ 'right' ] = 'left'
}
local duckAntennaSide = oppositeSide[device.workbench.side]
duckAntenna = peripheral.wrap(duckAntennaSide)
end
--if not device.monitor then
-- error('Monitor not found')
--end
ME.setDevice(device.tileinterface)
local jobListGrid
local craftingPaused = false
multishell.setTitle(multishell.getCurrent(), 'Storage Manager')
Logger.disable()
function getItem(items, inItem, ignore_dmg)
for _,item in pairs(items) do
if item.id == inItem.id then
if ignore_dmg and ignore_dmg == 'yes' then
return item
elseif item.dmg == inItem.dmg and item.nbt_hash == inItem.nbt_hash then
return item
end
end
end
end
local function uniqueKey(item)
local key = item.id .. ':' .. item.dmg
if item.nbt_hash then
key = key .. ':' .. item.nbt_hash
end
return key
end
function mergeResources(t)
local resources = Util.readTable('resource.limits')
resources = resources or { }
for _,item in pairs(t) do
item.has_recipe = false
end
for _,v in pairs(resources) do
local item = getItem(t, v)
if item then
item.limit = tonumber(v.limit)
item.low = tonumber(v.low)
item.auto = v.auto
item.ignore_dmg = v.ignore_dmg
else
v.qty = 0
v.limit = tonumber(v.limit)
v.low = tonumber(v.low)
v.auto = v.auto
v.ignore_dmg = v.ignore_dmg
table.insert(t, v)
end
end
recipes = Util.readTable('recipes') or { }
for _,v in pairs(recipes) do
local item = getItem(t, v)
if item then
item.has_recipe = true
else
v.qty = 0
v.limit = nil
v.low = nil
v.has_recipe = true
v.auto = 'no'
v.ignore_dmg = 'no'
v.has_recipe = 'true'
table.insert(t, v)
end
end
end
function filterItems(t, filter)
local r = {}
if filter then
filter = filter:lower()
for k,v in pairs(t) do
if string.find(v.lname, filter) then
table.insert(r, v)
end
end
else
return t
end
return r
end
function sumItems(items)
local t = {}
for _,item in pairs(items) do
local key = uniqueKey(item)
local summedItem = t[key]
if summedItem then
summedItem.qty = summedItem.qty + item.qty
else
summedItem = Util.shallowCopy(item)
t[key] = summedItem
end
end
return t
end
function isGridClear()
for i = 1, 16 do
if turtle.getItemCount(i) ~= 0 then
return false
end
end
return true
end
local function clearGrid()
for i = 1, 16 do
local count = turtle.getItemCount(i)
if count > 0 then
ME.insert(i, count, config.turtleDirection)
if turtle.getItemCount(i) ~= 0 then
return false
end
end
end
return true
end
function turtleCraft(recipe, originalItem)
for k,v in pairs(recipe.ingredients) do
-- ugh
local dmg = v.dmg
if v.max_dmg and v.max_dmg > 0 then
local item = ME.getItemDetail({ id = v.id, nbt_hash = v.nbt_hash }, false)
if item then
dmg = item.dmg
end
end
if not ME.extract(v.id, dmg, v.nbt_hash, v.qty, config.turtleDirection, k) then
clearGrid()
originalItem.status = v.name .. ' (extract failed)'
return false
end
end
if not turtle.craft() then
clearGrid()
return false
end
clearGrid()
return true
end
function craftItem(items, recipes, item, originalItem, itemList)
local key = uniqueKey(item)
local recipe = recipes[key]
if recipe then
if not isGridClear() then
return
end
local summedItems = sumItems(recipe.ingredients)
for i = 1, math.ceil(item.qty / recipe.qty) do
local failed = false -- try to craft all components (use all CPUs available)
for _,ingredient in pairs(summedItems) do
local ignore_dmg = 'no'
if ingredient.max_dmg and ingredient.max_dmg > 0 then
ignore_dmg = 'yes'
end
local qty = ME.getItemCount(ingredient.id, ingredient.dmg, ingredient.nbt_hash, ignore_dmg)
if qty < ingredient.qty then
originalItem.status = ingredient.name .. ' (crafting)'
ingredient.qty = ingredient.qty - qty
if not craftItem(items, recipes, ingredient, originalItem, itemList) then
failed = true
end
end
end
if failed then
return false
end
if not failed and not turtleCraft(recipe, originalItem) then
Logger.debug('turtle failed to craft ' .. item.name)
return false
end
end
return true
else
local meItem = getItem(items, item)
if not meItem or not meItem.is_craftable then
if item.id == originalItem.id and item.dmg == originalItem.dmg then
originalItem.status = '(not craftable)'
else
originalItem.status = item.name .. ' (missing)'
end
else
if item.id == originalItem.id and item.dmg == originalItem.dmg then
item.meCraft = true
return false
end
-- find it in the list of items to be crafted
for _,v in pairs(itemList) do
if v.id == item.id and v.dmg == item.dmg and v.nbt_hash == item.nbt_hash then
v.qty = item.qty + v.qty
return false
end
end
-- add to the item list
table.insert(itemList, {
id = item.id,
dmg = item.dmg,
nbt_hash = item.nbt_hash,
qty = item.qty,
name = item.name,
meCraft = true,
status = ''
})
end
end
return false
end
function craftItems(itemList)
local recipes = Util.readTable('recipes') or { }
local items = ME.getAvailableItems()
-- turtle craft anything we can, build up list for ME items
local keys = Util.keys(itemList)
for _,key in pairs(keys) do
local item = itemList[key]
craftItem(items, recipes, item, item, itemList)
end
-- second pass is to request crafting from ME with aggregated items
for _,item in pairs(itemList) do
if item.meCraft then
local alreadyCrafting = false
local jobList = ME.getJobList()
for _,v in pairs(jobList) do
if v.id == item.id and v.dmg == item.dmg and v.nbt_hash == item.nbt_hash then
alreadyCrafting = true
end
end
if alreadyCrafting then
item.status = '(crafting)'
elseif not ME.isCPUAvailable() then
item.status = '(waiting)'
else
item.status = '(failed)'
local qty = item.qty
while qty >= 1 do -- try to request smaller quantities until successful
if ME.craft(item.id, item.dmg, item.nbt_hash, qty) then
item.status = '(crafting)'
break -- successfully requested crafting
end
qty = math.floor(qty / 2)
end
end
end
end
end
-- AE 1 (obsolete)
function isCrafting(jobList, id, dmg)
for _, job in pairs(jobList) do
if job.id == id and job.dmg == dmg then
return job
end
end
end
local nullDevice = {
setCursorPos = function(...) end,
write = function(...) end,
getSize = function() return 13, 20 end,
isColor = function() return false end,
setBackgroundColor = function(...) end,
setTextColor = function(...) end,
clear = function(...) end,
}
local function jobMonitor(jobList)
local mon
if device.monitor then
mon = UI.Device({
deviceType = 'monitor',
textScale = .5,
})
else
mon = UI.Device({
device = nullDevice
})
end
jobListGrid = UI.Grid({
parent = mon,
sortColumn = 'name',
columns = {
{ heading = 'Qty', key = 'qty', width = 6 },
{ heading = 'Crafting', key = 'name', width = mon.width / 2 - 10 },
{ heading = 'Status', key = 'status', width = mon.width - 10 },
},
})
end
function getAutocraftItems(items)
local t = Util.readTable('resource.limits') or { }
local itemList = { }
for _,res in pairs(t) do
if res.auto and res.auto == 'yes' then
res.qty = 4 -- this could be higher to increase autocrafting speed
table.insert(itemList, res)
end
end
return itemList
end
local function getItemWithQty(items, res, ignore_dmg)
local item = getItem(items, res, ignore_dmg)
if item then
if ignore_dmg and ignore_dmg == 'yes' then
local qty = 0
for _,v in pairs(items) do
if item.id == v.id and item.nbt_hash == v.nbt_hash then
if item.max_dmg > 0 or item.dmg == v.dmg then
qty = qty + v.qty
end
end
end
item.qty = qty
end
end
return item
end
function watchResources(items)
local itemList = { }
local t = Util.readTable('resource.limits') or { }
for k, res in pairs(t) do
local item = getItemWithQty(items, res, res.ignore_dmg)
res.limit = tonumber(res.limit)
res.low = tonumber(res.low)
if not item then
item = {
id = res.id,
dmg = res.dmg,
nbt_hash = res.nbt_hash,
name = res.name,
qty = 0
}
end
if res.limit and item.qty > res.limit then
Logger.debug("Purging " .. item.qty-res.limit .. " " .. res.name)
if not ME.extract(item.id, item.dmg, item.nbt_hash, item.qty - res.limit, config.trashDirection) then
Logger.debug('Failed to purge ' .. res.name)
end
elseif res.low and item.qty < res.low then
if res.ignore_dmg and res.ignore_dmg == 'yes' then
item.dmg = 0
end
table.insert(itemList, {
id = item.id,
dmg = item.dmg,
nbt_hash = item.nbt_hash,
qty = res.low - item.qty,
name = item.name,
status = ''
})
end
end
return itemList
end
itemPage = UI.Page {
backgroundColor = colors.lightGray,
titleBar = UI.TitleBar {
title = 'Limit Resource',
previousPage = true,
event = 'form_cancel',
backgroundColor = colors.green
},
idField = UI.Text {
x = 5, y = 3, width = UI.term.width - 10,
},
form = UI.Form {
x = 4, y = 4, height = 8, rex = -4,
[1] = UI.TextEntry {
width = 7,
backgroundColor = colors.gray,
backgroundFocusColor = colors.gray,
formLabel = 'Min', formKey = 'low', help = 'Craft if below min'
},
[2] = UI.TextEntry {
width = 7,
backgroundColor = colors.gray,
backgroundFocusColor = colors.gray,
formLabel = 'Max', formKey = 'limit', help = 'Eject if above max'
},
[3] = UI.Chooser {
width = 7,
formLabel = 'Autocraft', formKey = 'auto',
nochoice = 'No',
choices = {
{ name = 'Yes', value = 'yes' },
{ name = 'No', value = 'no' },
},
help = 'Craft until out of ingredients'
},
[4] = UI.Chooser {
width = 7,
formLabel = 'Ignore Dmg', formKey = 'ignore_dmg',
nochoice = 'No',
choices = {
{ name = 'Yes', value = 'yes' },
{ name = 'No', value = 'no' },
},
help = 'Ignore damage of item'
},
},
statusBar = UI.StatusBar { }
}
function itemPage:enable()
UI.Page.enable(self)
self:focusFirst()
end
function itemPage:eventHandler(event)
if event.type == 'form_cancel' then
UI:setPreviousPage()
elseif event.type == 'focus_change' then
self.statusBar:setStatus(event.focused.help)
self.statusBar:draw()
elseif event.type == 'form_complete' then
local values = self.form.values
local t = Util.readTable('resource.limits') or { }
for k,v in pairs(t) do
if v.id == values.id and v.dmg == values.dmg then
table.remove(t, k)
break
end
end
local keys = { 'name', 'auto', 'id', 'low', 'dmg', 'max_dmg', 'nbt_hash', 'limit', 'ignore_dmg' }
local filtered = { }
for _,key in pairs(keys) do
filtered[key] = values[key]
end
table.insert(t, filtered)
Util.writeTable('resource.limits', t)
UI:setPreviousPage()
else
return UI.Page.eventHandler(self, event)
end
return true
end
listingPage = UI.Page {
menuBar = UI.MenuBar {
buttons = {
{ text = 'Learn', event = 'learn' },
{ text = 'Forget', event = 'forget' },
},
},
grid = UI.Grid {
y = 2, height = UI.term.height - 2,
columns = {
{ heading = 'Name', key = 'name' , width = 22 },
{ heading = 'Qty', key = 'qty' , width = 5 },
{ heading = 'Min', key = 'low' , width = 4 },
{ heading = 'Max', key = 'limit', width = 4 },
},
sortColumn = 'name',
},
statusBar = UI.StatusBar {
backgroundColor = colors.gray,
width = UI.term.width,
filterText = UI.Text {
x = 2, width = 6,
value = 'Filter',
},
filter = UI.TextEntry {
x = 9, width = 19,
limit = 50,
},
refresh = UI.Button {
x = 31, width = 8,
text = 'Refresh',
event = 'refresh',
},
},
accelerators = {
r = 'refresh',
q = 'quit',
}
}
function listingPage.grid:getRowTextColor(row, selected)
if row.is_craftable then
return colors.yellow
end
if row.has_recipe then
if selected then
return colors.blue
end
return colors.lightBlue
end
return UI.Grid:getRowTextColor(row, selected)
end
function listingPage.grid:getDisplayValues(row)
row = Util.shallowCopy(row)
row.qty = Util.toBytes(row.qty)
if row.low then
row.low = Util.toBytes(row.low)
end
if row.limit then
row.limit = Util.toBytes(row.limit)
end
return row
end
function listingPage.statusBar:draw()
return UI.Window.draw(self)
end
function listingPage.statusBar.filter:eventHandler(event)
if event.type == 'mouse_rightclick' then
self.value = ''
self:draw()
local page = UI:getCurrentPage()
page.filter = nil
page:applyFilter()
page.grid:draw()
page:setFocus(self)
end
return UI.TextEntry.eventHandler(self, event)
end
function listingPage:eventHandler(event)
if event.type == 'quit' then
Event.exitPullEvents()
elseif event.type == 'grid_select' then
local selected = event.selected
itemPage.form:setValues(selected)
itemPage.titleBar.title = selected.name
itemPage.idField.value = selected.id
UI:setPage('item')
elseif event.type == 'refresh' then
self:refresh()
self.grid:draw()
elseif event.type == 'learn' then
if not duckAntenna then
self.statusBar:timedStatus('Missing peripherals', 3)
else
UI:setPage('craft')
end
elseif event.type == 'forget' then
local item = self.grid:getSelected()
if item then
local recipes = Util.readTable('recipes') or { }
local key = uniqueKey(item)
local recipe = recipes[key]
if recipe then
recipes[key] = nil
Util.writeTable('recipes', recipes)
end
local resources = Util.readTable('resource.limits') or { }
for k,v in pairs(resources) do
if v.id == item.id and v.dmg == item.dmg then
table.remove(resources, k)
Util.writeTable('resource.limits', resources)
break
end
end
self.statusBar:timedStatus('Forgot: ' .. item.name, 3)
self:refresh()
self.grid:draw()
end
elseif event.type == 'text_change' then
self.filter = event.text
if #self.filter == 0 then
self.filter = nil
end
self:applyFilter()
self.grid:draw()
self.statusBar.filter:focus()
else
UI.Page.eventHandler(self, event)
end
return true
end
function listingPage:enable()
self:refresh()
self:setFocus(self.statusBar.filter)
UI.Page.enable(self)
end
function listingPage:refresh()
self.allItems = ME.getAvailableItems('all')
mergeResources(self.allItems)
Util.each(self.allItems, function(item)
item.lname = item.name:lower()
end)
self:applyFilter()
end
function listingPage:applyFilter()
local t = filterItems(self.allItems, self.filter)
self.grid:setValues(t)
end
-- without duck antenna
local function getTurtleInventory()
local inventory = { }
for i = 1,16 do
if turtle.getItemCount(i) > 0 then
turtle.select(i)
local item = turtle.getItemDetail()
inventory[i] = {
id = item.name,
dmg = item.damage,
qty = item.count,
name = item.name,
}
end
end
return inventory
end
-- Strip off color prefix
local function safeString(text)
local val = text:byte(1)
if val < 32 or val > 128 then
local newText = {}
for i = 4, #text do
local val = text:byte(i)
newText[i - 3] = (val > 31 and val < 127) and val or 63
end
return string.char(unpack(newText))
end
return text
end
local function filter(t, filter)
local keys = Util.keys(t)
for _,key in pairs(keys) do
if not Util.key(filter, key) then
t[key] = nil
end
end
end
local function learnRecipe(page)
local t = Util.readTable('recipes') or { }
local recipe = { }
local ingredients = duckAntenna.getAllStacks(false) -- getTurtleInventory()
if ingredients then
turtle.select(1)
if turtle.craft() then
recipe = duckAntenna.getAllStacks(false) -- getTurtleInventory()
if recipe and recipe[1] then
recipe = recipe[1]
local key = uniqueKey(recipe)
clearGrid()
recipe.name = safeString(recipe.display_name)
filter(recipe, { 'name', 'id', 'dmg', 'nbt_hash', 'qty', 'max_size' })
for _,ingredient in pairs(ingredients) do
ingredient.name = safeString(ingredient.display_name)
filter(ingredient, { 'name', 'id', 'dmg', 'nbt_hash', 'qty', 'max_size', 'max_dmg' })
if ingredient.max_dmg > 0 then -- let's try this...
ingredient.dmg = 0
end
end
recipe.ingredients = ingredients
recipe.ignore_dmg = 'no'
t[key] = recipe
Util.writeTable('recipes', t)
listingPage.statusBar.filter:setValue(recipe.name)
listingPage.statusBar:timedStatus('Learned: ' .. recipe.name, 3)
listingPage.filter = recipe.name
listingPage:refresh()
listingPage.grid:draw()
return true
end
else
page.statusBar:timedStatus('Failed to craft', 3)
end
else
page.statusBar:timedStatus('No recipe defined', 3)
end
end
craftPage = UI.Dialog {
height = 7, width = UI.term.width - 6,
backgroundColor = colors.lightGray,
titleBar = UI.TitleBar {
title = 'Learn Recipe',
previousPage = true,
},
idField = UI.Text {
x = 5,
y = 3,
width = UI.term.width - 10,
value = 'Place recipe in turtle'
},
accept = UI.Button {
rx = -13, ry = -2,
text = 'Ok', event = 'accept',
},
cancel = UI.Button {
rx = -8, ry = -2,
text = 'Cancel', event = 'cancel'
},
statusBar = UI.StatusBar {
status = 'Crafting paused'
}
}
function craftPage:enable()
craftingPaused = true
self:focusFirst()
UI.Dialog.enable(self)
end
function craftPage:disable()
craftingPaused = false
UI.Dialog.disable(self)
end
function craftPage:eventHandler(event)
if event.type == 'cancel' then
UI:setPreviousPage()
elseif event.type == 'accept' then
if learnRecipe(self) then
UI:setPreviousPage()
end
else
return UI.Dialog.eventHandler(self, event)
end
return true
end
UI:setPages({
listing = listingPage,
item = itemPage,
craft = craftPage,
})
UI:setPage(listingPage)
listingPage:setFocus(listingPage.statusBar.filter)
clearGrid()
jobMonitor()
jobListGrid:draw()
jobListGrid:sync()
function craftingThread()
while true do
os.sleep(5)
if not craftingPaused then
local items = ME.getAvailableItems()
if Util.size(items) == 0 then
jobListGrid.parent:clear()
jobListGrid.parent:centeredWrite(math.ceil(jobListGrid.parent.height/2), 'No items in system')
jobListGrid:sync()
elseif config.noCraftingStorage ~= 'true' and #ME.getCraftingCPUs() <= 0 then -- only way to determine if AE is online
jobListGrid.parent:clear()
jobListGrid.parent:centeredWrite(math.ceil(jobListGrid.parent.height/2), 'Power failure')
jobListGrid:sync()
else
local itemList = watchResources(items)
jobListGrid:setValues(itemList)
jobListGrid:draw()
jobListGrid:sync()
craftItems(itemList)
jobListGrid:update()
jobListGrid:draw()
jobListGrid:sync()
itemList = getAutocraftItems(items) -- autocrafted items don't show on job monitor
craftItems(itemList)
end
end
end
end
Event.pullEvents(craftingThread)
UI.term:reset()
jobListGrid.parent:reset()

403
sys/apps/supplier.lua Normal file
View File

@@ -0,0 +1,403 @@
require = requireInjector(getfenv(1))
local Logger = require('logger')
local Message = require('message')
local Event = require('event')
local Point = require('point')
local TableDB = require('tableDB')
local MEProvider = require('meProvider')
local ChestProvider = require('chestProvider')
if os.getVersion() == 1.8 then
ChestProvider = require('chestProvider18')
end
if not device.wireless_modem then
error('No wireless modem detected')
end
Logger.filter('modem_send', 'event', 'ui')
Logger.setWirelessLogging()
local __BUILDER_ID = 6
local Builder = {
version = '1.70',
ccVersion = nil,
slots = { },
index = 1,
fuelItem = { id = 'minecraft:coal', dmg = 0 },
resupplying = true,
ready = true,
}
--[[-- maxStackDB --]]--
local maxStackDB = TableDB({
fileName = 'maxstack.db',
tabledef = {
autokeys = false,
type = 'simple',
columns = {
{ label = 'Key', type = 'key', length = 8 },
{ label = 'Quantity', type = 'number', length = 2 }
}
}
})
function maxStackDB:get(id, dmg)
return self.data[id .. ':' .. dmg] or 64
end
function Builder:dumpInventory()
local success = true
for i = 1, 16 do
local qty = turtle.getItemCount(i)
if qty > 0 then
self.itemProvider:insert(i, qty)
end
if turtle.getItemCount(i) ~= 0 then
success = false
end
end
turtle.select(1)
return success
end
function Builder:dumpInventoryWithCheck()
while not self:dumpInventory() do
Builder:log('Unable to dump inventory')
print('Provider is full or missing - make space or replace')
print('Press enter to continue')
turtle.setHeading(0)
self.ready = false
read()
end
self.ready = true
end
function Builder:autocraft(supplies)
local t = { }
for i,s in pairs(supplies) do
local key = s.id .. ':' .. s.dmg
local item = t[key]
if not item then
item = {
id = s.id,
dmg = s.dmg,
qty = 0,
}
t[key] = item
end
item.qty = item.qty + (s.need-s.qty)
end
Builder.itemProvider:craftItems(t)
end
function Builder:refuel()
while turtle.getFuelLevel() < 4000 and self.fuelItem do
Builder:log('Refueling')
turtle.select(1)
self.itemProvider:provide(self.fuelItem, 64, 1)
if turtle.getItemCount(1) == 0 then
Builder:log('Out of fuel, add coal to chest/ME system')
turtle.setHeading(0)
os.sleep(5)
else
turtle.refuel(64)
end
end
end
function Builder:log(...)
Logger.log('supplier', ...)
Util.print(...)
end
function Builder:getSupplies()
Builder.itemProvider:refresh()
local t = { }
for _,s in ipairs(self.slots) do
if s.need > 0 then
local item = Builder.itemProvider:getItemInfo(s.id, s.dmg)
if item then
if item.name then
s.name = item.name
end
local qty = math.min(s.need-s.qty, item.qty)
if qty + s.qty > item.max_size then
maxStackDB:add({ s.id, s.dmg }, item.max_size)
maxStackDB.dirty = true
maxStackDB:flush()
qty = item.max_size
s.need = qty
end
if qty > 0 then
self.itemProvider:provide(item, qty, s.index)
s.qty = turtle.getItemCount(s.index)
end
end
end
if s.qty < s.need then
table.insert(t, s)
local name = s.name or s.id .. ':' .. s.dmg
Builder:log('Need %d %s', s.need - s.qty, name)
end
end
return t
end
local function moveTowardsX(dx)
local direction = dx - turtle.point.x
local move
if direction == 0 then
return false
end
if direction > 0 and turtle.point.heading == 0 or
direction < 0 and turtle.point.heading == 2 then
move = turtle.forward
else
move = turtle.back
end
return move()
end
local function moveTowardsZ(dz)
local direction = dz - turtle.point.z
local move
if direction == 0 then
return false
end
if direction > 0 and turtle.point.heading == 1 or
direction < 0 and turtle.point.heading == 3 then
move = turtle.forward
else
move = turtle.back
end
return move()
end
function Builder:finish()
Builder.resupplying = true
Builder.ready = false
if turtle.gotoLocation('supplies') then
turtle.setHeading(0)
os.sleep(.1) -- random 'Computer is not connected' error...
Builder:dumpInventory()
Event.exitPullEvents()
print('Finished')
end
end
function Builder:gotoBuilder()
if Builder.lastPoint then
turtle.status = 'tracking'
while true do
local pt = Point.copy(Builder.lastPoint)
pt.y = pt.y + 3
if turtle.point.y ~= pt.y then
turtle.gotoY(pt.y)
else
local distance = Point.turtleDistance(turtle.point, pt)
if distance <= 3 then
Builder:log('Synchronized')
break
end
if turtle.point.heading % 2 == 0 then
if turtle.point.x == pt.x then
turtle.headTowardsZ(pt.z)
moveTowardsZ(pt.z)
else
moveTowardsX(pt.x)
end
elseif turtle.point.z ~= pt.z then
moveTowardsZ(pt.z)
else
turtle.headTowardsX(pt.x)
moveTowardsX(pt.x)
end
end
end
end
end
Message.addHandler('builder',
function(h, id, msg, distance)
if not id or id ~= __BUILDER_ID then
return
end
if not Builder.resupplying then
local pt = msg.contents
pt.y = pt.y + 3
turtle.status = 'supervising'
turtle.gotoYfirst(pt)
end
end)
Message.addHandler('supplyList',
function(h, id, msg, distance)
if not id or id ~= __BUILDER_ID then
return
end
turtle.status = 'resupplying'
Builder.resupplying = true
Builder.slots = msg.contents.slots
Builder.slotUid = msg.contents.uid
Builder:log('Received supply list ' .. Builder.slotUid)
os.sleep(0)
if not turtle.gotoLocation('supplies') then
Builder:log('Failed to go to supply location')
self.ready = false
Event.exitPullEvents()
end
os.sleep(.2) -- random 'Computer is not connected' error...
Builder:dumpInventoryWithCheck()
Builder:refuel()
while true do
local supplies = Builder:getSupplies()
if #supplies == 0 then
break
end
turtle.setHeading(0)
Builder:autocraft(supplies)
turtle.status = 'waiting'
os.sleep(5)
end
Builder:log('Got all supplies')
os.sleep(0)
Builder:gotoBuilder()
Builder.resupplying = false
end)
Message.addHandler('needSupplies',
function(h, id, msg, distance)
if not id or id ~= __BUILDER_ID then
return
end
if Builder.resupplying or msg.contents.uid ~= Builder.slotUid then
Builder:log('No supplies ready')
Message.send(__BUILDER_ID, 'gotSupplies')
else
turtle.status = 'supplying'
Builder:log('Supplying')
os.sleep(0)
local pt = msg.contents.point
pt.y = turtle.getPoint().y
pt.heading = nil
if not turtle.gotoYfirst(pt) then -- location of builder
Builder.resupplying = true
Message.send(__BUILDER_ID, 'gotSupplies')
os.sleep(0)
if not turtle.gotoLocation('supplies') then
Builder:log('failed to go to supply location')
self.ready = false
Event.exitPullEvents()
end
return
end
pt.y = pt.y - 2 -- location where builder should go for the chest to be above
turtle.select(15)
turtle.placeDown()
os.sleep(.1) -- random computer not connected error
local p = ChestProvider({ direction = 'up', wrapSide = 'bottom' })
for i = 1, 16 do
p:insert(i, 64)
end
Message.send(__BUILDER_ID, 'gotSupplies', { supplies = true, point = pt })
Message.waitForMessage('thanks', 5, __BUILDER_ID)
--os.sleep(0)
--p.condenseItems()
for i = 1, 16 do
p:extract(i, 64)
end
turtle.digDown()
turtle.status = 'waiting'
end
end)
Message.addHandler('finished',
function(h, id)
if not id or id ~= __BUILDER_ID then
return
end
Builder:finish()
end)
Event.addHandler('turtle_abort',
function()
turtle.abort = false
turtle.status = 'aborting'
Builder:finish()
end)
local function onTheWay() -- parallel routine
while true do
local e, side, _id, id, msg, distance = os.pullEvent('modem_message')
if Builder.ready then
if id == __BUILDER_ID and msg and msg.type then
if msg.type == 'needSupplies' then
Message.send(__BUILDER_ID, 'gotSupplies', { supplies = true })
elseif msg.type == 'builder' then
Builder.lastPoint = msg.contents
end
end
end
end
end
local args = {...}
if #args < 1 then
error('Supply id for builder')
end
__BUILDER_ID = tonumber(args[1])
maxStackDB:load()
turtle.setPoint({ x = -1, z = -2, y = 0, heading = 0 })
turtle.saveLocation('supplies')
Builder.itemProvider = MEProvider()
if not Builder.itemProvider:isValid() then
Builder.itemProvider = ChestProvider()
if not Builder.itemProvider:isValid() then
error('A chest or ME interface must be below turtle')
end
end
turtle.run(function()
Event.pullEvents(onTheWay)
end)

98
sys/apps/t.lua Normal file
View File

@@ -0,0 +1,98 @@
function doCommand(command, moves)
--[[
if command == 'sl' then
local pt = GPS.getPoint()
if pt then
turtle.storeLocation(moves, pt)
end
return
end
--]]
local function format(value)
if type(value) == 'boolean' then
if value then return 'true' end
return 'false'
end
if type(value) ~= 'table' then
return value
end
local str
for k,v in pairs(value) do
if not str then
str = '{ '
else
str = str .. ', '
end
str = str .. k .. '=' .. tostring(v)
end
if str then
str = str .. ' }'
else
str = '{ }'
end
return str
end
local function runCommand(fn, arg)
local r = { fn(arg) }
if r[2] then
print(format(r[1]) .. ': ' .. format(r[2]))
elseif r[1] then
print(format(r[1]))
end
return r[1]
end
local cmds = {
[ 's' ] = turtle.select,
[ 'rf' ] = turtle.refuel,
[ 'gh' ] = function() turtle.pathfind({ x = 0, y = 0, z = 0, heading = 0}) end,
}
local repCmds = {
[ 'u' ] = turtle.up,
[ 'd' ] = turtle.down,
[ 'f' ] = turtle.forward,
[ 'r' ] = turtle.turnRight,
[ 'l' ] = turtle.turnLeft,
[ 'ta' ] = turtle.turnAround,
[ 'DD' ] = turtle.digDown,
[ 'DU' ] = turtle.digUp,
[ 'D' ] = turtle.dig,
[ 'p' ] = turtle.place,
[ 'pu' ] = turtle.placeUp,
[ 'pd' ] = turtle.placeDown,
[ 'b' ] = turtle.back,
[ 'gfl' ] = turtle.getFuelLevel,
[ 'gp' ] = turtle.getPoint,
[ 'R' ] = function() turtle.setPoint({x = 0, y = 0, z = 0, heading = 0}) return turtle.point end
}
if cmds[command] then
runCommand(cmds[command], moves)
elseif repCmds[command] then
for i = 1, moves do
if not runCommand(repCmds[command]) then
break
end
end
end
end
local args = {...}
if #args > 0 then
doCommand(args[1], args[2] or 1)
else
print('Enter command (q to quit):')
while true do
local cmd = read()
if cmd == 'q' then break
end
args = { }
cmd:gsub('%w+', function(w) table.insert(args, w) end)
doCommand(args[1], args[2] or 1)
end
end

81
sys/apps/telnet.lua Normal file
View File

@@ -0,0 +1,81 @@
require = requireInjector(getfenv(1))
local process = require('process')
local Socket = require('socket')
local Terminal = require('terminal')
local remoteId
local args = { ... }
if #args == 1 then
remoteId = tonumber(args[1])
else
print('Enter host ID')
remoteId = tonumber(read())
end
if not remoteId then
error('Syntax: telnet <host ID>')
end
print('connecting...')
local socket = Socket.connect(remoteId, 23)
if not socket then
error('Unable to connect to ' .. remoteId .. ' on port 23')
end
local ct = Util.shallowCopy(term.current())
if not ct.isColor() then
Terminal.toGrayscale(ct)
end
local w, h = ct.getSize()
socket:write({
type = 'termInfo',
width = w,
height = h,
isColor = ct.isColor(),
})
process:newThread('telnet_read', 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)
ct.clear()
ct.setCursorPos(1, 1)
local filter = Util.transpose({
'char', 'paste', 'key', 'key_up', 'mouse_scroll', 'mouse_click', 'mouse_drag',
})
while true do
local e = { process:pullEvent() }
local event = e[1]
if not socket.connected then
print()
print('Connection lost')
print('Press enter to exit')
read()
break
end
if filter[event] then
if not socket:write({ type = 'shellRemote', event = e }) then
socket:close()
break
end
elseif event == 'terminate' then
socket:close()
break
end
end

52
sys/apps/trace.lua Normal file
View File

@@ -0,0 +1,52 @@
local args = {...}
if not args[1] then
print("Usage:")
print(shell.getRunningProgram() .. " <program> [program arguments, ...]")
return
end
local path = shell.resolveProgram(args[1]) or shell.resolve(args[1])
-- here be dragons
if fs.exists(path) then
local eshell = setmetatable({getRunningProgram=function() return path end}, {__index = shell})
local env = setmetatable({shell=eshell}, {__index=_ENV})
local f = fs.open(path, "r")
local d = f.readAll()
f.close()
local func, e = load(d, fs.getName(path), nil, env)
if not func then
printError("Syntax error:")
printError(" " .. e)
else
table.remove(args, 1)
xpcall(function() func(unpack(args)) end, function(err)
local trace = {}
local i, hitEnd, _, e = 4, false
repeat
_, e = pcall(function() error("<tracemarker>", i) end)
i = i + 1
if e == "xpcall: <tracemarker>" then
hitEnd = true
break
end
table.insert(trace, e)
until i > 10
table.remove(trace)
if err:match("^" .. trace[1]:match("^(.-:%d+)")) then table.remove(trace, 1) end
printError("\nProgram has crashed! Stack trace:")
printError(err)
for i, v in ipairs(trace) do
printError(" at " .. v:match("^(.-:%d+)"))
end
if not hitEnd then
printError(" ...")
end
end)
end
else
printError("program not found")
end

48
sys/apps/trust.lua Normal file
View File

@@ -0,0 +1,48 @@
require = requireInjector(getfenv(1))
local Socket = require('socket')
local SHA1 = require('sha1')
local Terminal = require('terminal')
local Crypto = require('crypto')
local remoteId
local args = { ... }
if #args == 1 then
remoteId = tonumber(args[1])
else
print('Enter host ID')
remoteId = tonumber(read())
end
if not remoteId then
error('Syntax: trust <host ID>')
end
local password = Terminal.readPassword('Enter password: ')
if not password then
error('Invalid password')
end
print('connecting...')
local socket = Socket.connect(remoteId, 19)
if not socket then
error('Unable to connect to ' .. remoteId .. ' on port 19')
end
local publicKey = os.getPublicKey()
local password = SHA1.sha1(password)
socket:write(Crypto.encrypt({ pk = publicKey, dh = os.getComputerID() }, password))
local data = socket:read(2)
socket:close()
if data and data.success then
print(data.msg)
elseif data then
error(data.msg)
else
error('No response')
end

2
sys/apps/update.lua Normal file
View File

@@ -0,0 +1,2 @@
local options = table.concat({ ... }, ' ')
shell.run('pastebin run sj4VMVJj ' .. options)

88
sys/apps/vnc.lua Normal file
View File

@@ -0,0 +1,88 @@
require = requireInjector(getfenv(1))
local process = require('process')
local Socket = require('socket')
local Terminal = require('terminal')
local remoteId
local args = { ... }
if #args == 1 then
remoteId = tonumber(args[1])
else
print('Enter host ID')
remoteId = tonumber(read())
end
if not remoteId then
error('Syntax: vnc <host ID>')
end
multishell.setTitle(multishell.getCurrent(), 'VNC-' .. remoteId)
print('connecting...')
local socket = Socket.connect(remoteId, 5900)
if not socket then
error('Unable to connect to ' .. remoteId .. ' on port 5900')
end
local w, h = term.getSize()
socket:write({
type = 'termInfo',
width = w,
height = h,
isColor = term.isColor(),
})
local ct = Util.shallowCopy(term.current())
if not ct.isColor() then
Terminal.toGrayscale(ct)
end
process:newThread('vnc_read', 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)
ct.clear()
ct.setCursorPos(1, 1)
while true do
local e = { process:pullEvent() }
local event = e[1]
if not socket.connected then
print()
print('Connection lost')
print('Press enter to exit')
read()
break
end
if event == 'char' or
event == 'paste' or
event == 'key' or
event == 'key_up' or
event == 'mouse_scroll' or
event == 'mouse_click' or
event == 'mouse_drag' then
socket:write({
type = 'shellRemote',
event = e,
})
elseif event == 'terminate' then
socket:close()
ct.setBackgroundColor(colors.black)
ct.clear()
ct.setCursorPos(1, 1)
break
end
end