From fc243a9c12fdf00768d1fe3af7483e8d2ed10a7d Mon Sep 17 00:00:00 2001 From: "kepler155c@gmail.com" Date: Sun, 11 Dec 2016 14:24:52 -0500 Subject: [PATCH] Initial commit --- README.md | 1 + apps/Appstore.lua | 386 ++++ apps/Events.lua | 118 ++ apps/Files.lua | 375 ++++ apps/Help.lua | 84 + apps/Lua.lua | 247 +++ apps/Network.lua | 153 ++ apps/Overview.lua | 445 ++++ apps/Peripherals.lua | 213 ++ apps/Pim.lua | 105 + apps/Script.lua | 561 ++++++ apps/System.lua | 190 ++ apps/Tabs.lua | 78 + apps/builder.lua | 2064 +++++++++++++++++++ apps/cat.lua | 24 + apps/edit.lua | 1184 +++++++++++ apps/logMonitor.lua | 102 + apps/mirror.lua | 84 + apps/mirrorHost.lua | 45 + apps/multishell | 600 ++++++ apps/pickup.lua | 343 ++++ apps/pickupRemote.lua | 229 +++ apps/recorder.lua | 529 +++++ apps/scripts/abort | 1 + apps/scripts/follow | 120 ++ apps/scripts/goHome | 1 + apps/scripts/moveTo | 29 + apps/scripts/reboot | 1 + apps/scripts/setHome | 1 + apps/scripts/shutdown | 1 + apps/scripts/summon | 72 + apps/scripts/update | 1 + apps/shell | 623 ++++++ apps/simpleMiner.lua | 570 ++++++ apps/storageActivity.lua | 194 ++ apps/storageManager.lua | 917 +++++++++ apps/supplier.lua | 392 ++++ apps/t.lua | 98 + apps/telnet.lua | 82 + apps/trace.lua | 52 + apps/update.lua | 6 + apps/vnc.lua | 88 + autorun/gps.lua | 51 + startup | 45 + sys/apis/blocks.lua | 893 ++++++++ sys/apis/chestProvider.lua | 103 + sys/apis/class.lua | 46 + sys/apis/config.lua | 24 + sys/apis/deflatelua.lua | 870 ++++++++ sys/apis/event.lua | 177 ++ sys/apis/fileui.lua | 142 ++ sys/apis/fs/gitfs.lua | 16 + sys/apis/fs/linkfs.lua | 61 + sys/apis/fs/netfs.lua | 161 ++ sys/apis/fs/ramfs.lua | 153 ++ sys/apis/fs/urlfs.lua | 78 + sys/apis/git.lua | 40 + sys/apis/glasses.lua | 196 ++ sys/apis/gps.lua | 152 ++ sys/apis/history.lua | 47 + sys/apis/injector.lua | 69 + sys/apis/input.lua | 112 + sys/apis/json.lua | 213 ++ sys/apis/jumper/core/assert.lua | 105 + sys/apis/jumper/core/bheap.lua | 175 ++ sys/apis/jumper/core/heuristics.lua | 98 + sys/apis/jumper/core/lookuptable.lua | 32 + sys/apis/jumper/core/node.lua | 100 + sys/apis/jumper/core/path.lua | 201 ++ sys/apis/jumper/core/utils.lua | 168 ++ sys/apis/jumper/grid.lua | 429 ++++ sys/apis/jumper/pathfinder.lua | 412 ++++ sys/apis/jumper/search/astar.lua | 88 + sys/apis/jumper/search/bfs.lua | 46 + sys/apis/logger.lua | 133 ++ sys/apis/me.lua | 165 ++ sys/apis/meProvider.lua | 137 ++ sys/apis/message.lua | 106 + sys/apis/nft.lua | 76 + sys/apis/peripheral.lua | 95 + sys/apis/point.lua | 147 ++ sys/apis/process.lua | 111 + sys/apis/profile.lua | 50 + sys/apis/require.lua | 58 + sys/apis/schematic.lua | 1178 +++++++++++ sys/apis/sha1.lua | 297 +++ sys/apis/socket.lua | 211 ++ sys/apis/sync.lua | 24 + sys/apis/tableDB.lua | 53 + sys/apis/terminal.lua | 145 ++ sys/apis/ui.lua | 2801 ++++++++++++++++++++++++++ sys/apis/util.lua | 530 +++++ sys/boot/default.boot | 3 + sys/boot/multishell.boot | 33 + sys/boot/tlco.boot | 37 + sys/extensions/device.lua | 8 + sys/extensions/os.lua | 134 ++ sys/extensions/pathfind.lua | 223 ++ sys/extensions/scheduler.lua | 78 + sys/extensions/tgps.lua | 40 + sys/extensions/tl3.lua | 861 ++++++++ sys/extensions/vfs.lua | 311 +++ sys/network/samba.lua | 87 + sys/network/snmp.lua | 166 ++ sys/network/telnet.lua | 96 + sys/network/vnc.lua | 85 + sys/services/device.lua | 39 + sys/services/gpshost.lua | 43 + sys/services/log.lua | 38 + sys/services/network.lua | 66 + 110 files changed, 25577 insertions(+) create mode 100644 README.md create mode 100644 apps/Appstore.lua create mode 100644 apps/Events.lua create mode 100644 apps/Files.lua create mode 100644 apps/Help.lua create mode 100644 apps/Lua.lua create mode 100644 apps/Network.lua create mode 100644 apps/Overview.lua create mode 100644 apps/Peripherals.lua create mode 100644 apps/Pim.lua create mode 100644 apps/Script.lua create mode 100644 apps/System.lua create mode 100644 apps/Tabs.lua create mode 100644 apps/builder.lua create mode 100644 apps/cat.lua create mode 100644 apps/edit.lua create mode 100644 apps/logMonitor.lua create mode 100644 apps/mirror.lua create mode 100644 apps/mirrorHost.lua create mode 100644 apps/multishell create mode 100644 apps/pickup.lua create mode 100644 apps/pickupRemote.lua create mode 100644 apps/recorder.lua create mode 100644 apps/scripts/abort create mode 100644 apps/scripts/follow create mode 100644 apps/scripts/goHome create mode 100644 apps/scripts/moveTo create mode 100644 apps/scripts/reboot create mode 100644 apps/scripts/setHome create mode 100644 apps/scripts/shutdown create mode 100644 apps/scripts/summon create mode 100644 apps/scripts/update create mode 100644 apps/shell create mode 100644 apps/simpleMiner.lua create mode 100644 apps/storageActivity.lua create mode 100644 apps/storageManager.lua create mode 100644 apps/supplier.lua create mode 100644 apps/t.lua create mode 100644 apps/telnet.lua create mode 100644 apps/trace.lua create mode 100644 apps/update.lua create mode 100644 apps/vnc.lua create mode 100644 autorun/gps.lua create mode 100644 startup create mode 100644 sys/apis/blocks.lua create mode 100644 sys/apis/chestProvider.lua create mode 100644 sys/apis/class.lua create mode 100644 sys/apis/config.lua create mode 100644 sys/apis/deflatelua.lua create mode 100644 sys/apis/event.lua create mode 100644 sys/apis/fileui.lua create mode 100644 sys/apis/fs/gitfs.lua create mode 100644 sys/apis/fs/linkfs.lua create mode 100644 sys/apis/fs/netfs.lua create mode 100644 sys/apis/fs/ramfs.lua create mode 100644 sys/apis/fs/urlfs.lua create mode 100644 sys/apis/git.lua create mode 100644 sys/apis/glasses.lua create mode 100644 sys/apis/gps.lua create mode 100644 sys/apis/history.lua create mode 100644 sys/apis/injector.lua create mode 100644 sys/apis/input.lua create mode 100644 sys/apis/json.lua create mode 100644 sys/apis/jumper/core/assert.lua create mode 100644 sys/apis/jumper/core/bheap.lua create mode 100644 sys/apis/jumper/core/heuristics.lua create mode 100644 sys/apis/jumper/core/lookuptable.lua create mode 100644 sys/apis/jumper/core/node.lua create mode 100644 sys/apis/jumper/core/path.lua create mode 100644 sys/apis/jumper/core/utils.lua create mode 100644 sys/apis/jumper/grid.lua create mode 100644 sys/apis/jumper/pathfinder.lua create mode 100644 sys/apis/jumper/search/astar.lua create mode 100644 sys/apis/jumper/search/bfs.lua create mode 100644 sys/apis/logger.lua create mode 100644 sys/apis/me.lua create mode 100644 sys/apis/meProvider.lua create mode 100644 sys/apis/message.lua create mode 100644 sys/apis/nft.lua create mode 100644 sys/apis/peripheral.lua create mode 100644 sys/apis/point.lua create mode 100644 sys/apis/process.lua create mode 100644 sys/apis/profile.lua create mode 100644 sys/apis/require.lua create mode 100644 sys/apis/schematic.lua create mode 100644 sys/apis/sha1.lua create mode 100644 sys/apis/socket.lua create mode 100644 sys/apis/sync.lua create mode 100644 sys/apis/tableDB.lua create mode 100644 sys/apis/terminal.lua create mode 100644 sys/apis/ui.lua create mode 100644 sys/apis/util.lua create mode 100644 sys/boot/default.boot create mode 100644 sys/boot/multishell.boot create mode 100644 sys/boot/tlco.boot create mode 100644 sys/extensions/device.lua create mode 100644 sys/extensions/os.lua create mode 100644 sys/extensions/pathfind.lua create mode 100644 sys/extensions/scheduler.lua create mode 100644 sys/extensions/tgps.lua create mode 100644 sys/extensions/tl3.lua create mode 100644 sys/extensions/vfs.lua create mode 100644 sys/network/samba.lua create mode 100644 sys/network/snmp.lua create mode 100644 sys/network/telnet.lua create mode 100644 sys/network/vnc.lua create mode 100644 sys/services/device.lua create mode 100644 sys/services/gpshost.lua create mode 100644 sys/services/log.lua create mode 100644 sys/services/network.lua diff --git a/README.md b/README.md new file mode 100644 index 0000000..00bcb6e --- /dev/null +++ b/README.md @@ -0,0 +1 @@ +# test \ No newline at end of file diff --git a/apps/Appstore.lua b/apps/Appstore.lua new file mode 100644 index 0000000..f344242 --- /dev/null +++ b/apps/Appstore.lua @@ -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() diff --git a/apps/Events.lua b/apps/Events.lua new file mode 100644 index 0000000..5bf6804 --- /dev/null +++ b/apps/Events.lua @@ -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() diff --git a/apps/Files.lua b/apps/Files.lua new file mode 100644 index 0000000..0424c09 --- /dev/null +++ b/apps/Files.lua @@ -0,0 +1,375 @@ +require = requireInjector(getfenv(1)) +local Util = require('util') +local Event = require('event') +local UI = require('ui') + +local cleanEnv = Util.shallowCopy(getfenv(1)) +cleanEnv.require = nil + +multishell.setTitle(multishell.getCurrent(), 'Files') +UI:configure('Files', ...) + +local copied = { } +local marked = { } +local directories = { } +local hidden = true +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' }, + { text = 'Quit q', event = 'quit' }, + } + }, + editMenu = UI.DropMenu { + buttons = { + { text = 'Mark m', event = 'mark' }, + { text = 'Cut ^x', event = 'cut' }, + { text = 'Copy ^c', event = 'copy' }, + { text = 'Paste ^v', event = 'paste' }, + { text = 'Delete del', event = 'delete' }, + { text = 'Unmark all u', event = 'unmark' }, + } + }, + viewMenu = UI.DropMenu { + buttons = { + { text = 'Refresh r', event = 'refresh' }, + { text = 'Hidden ^h', event = 'toggle_hidden' }, + } + }, + 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 - 19 }, + { '', '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.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 + file.flags = 'D' + end + if file.isReadOnly then + file.flags = file.flags .. 'R' + end + if not hidden 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 + hidden = not hidden + self:updateDirectory(self.dir) + self.grid:draw() + if hidden then + self:setStatus('Hiding hidden') + else + self:setStatus('Displaying hidden') + 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() diff --git a/apps/Help.lua b/apps/Help.lua new file mode 100644 index 0000000..ab2f75c --- /dev/null +++ b/apps/Help.lua @@ -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() diff --git a/apps/Lua.lua b/apps/Lua.lua new file mode 100644 index 0000000..c4493d1 --- /dev/null +++ b/apps/Lua.lua @@ -0,0 +1,247 @@ +require = requireInjector(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 = requireInjector(sandboxEnv) +setmetatable(sandboxEnv, { __index = _G }) + +multishell.setTitle(multishell.getCurrent(), 'Lua') +UI:configure('Lua', ...) + +local command = '' +local history = History.load('.lua_history', 25) + +local resultsPage = UI.Page({ + menuBar = UI.MenuBar({ + buttons = { + { text = 'Local', event = 'local' }, + { text = 'Global', event = 'global' }, + { text = 'Device', event = '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', + }, + }), + grid = UI.ScrollingGrid({ + y = 3, + columns = { + { heading = 'Key', key = 'name' }, + { heading = 'Value', key = 'value' }, + }, + sortColumn = 'name', + autospace = true, + }), + notification = UI.Notification(), +}) + +function resultsPage: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 + resultsPage:setFocus(self.prompt) + end +end + +function resultsPage:enable() + self:setFocus(self.prompt) + UI.Page.enable(self) +end + +function resultsPage:eventHandler(event) + + if event.type == 'global' then + resultsPage:setPrompt('', true) + self:executeStatement('getfenv(0)') + command = nil + + elseif event.type == 'local' then + resultsPage:setPrompt('', true) + self:executeStatement('getfenv(1)') + command = nil + + elseif event.type == 'device' then + resultsPage: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 resultsPage: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 resultsPage.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 + resultsPage:setPrompt(commandAppend()) + end + elseif event.type == 'grid_select' then + resultsPage:setPrompt(commandAppend(), true) + resultsPage:executeStatement(commandAppend()) + else + return UI.Grid.eventHandler(self, event) + end + return true +end + +function resultsPage: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 resultsPage:executeStatement(statement) + + command = statement + + local s, m = self:rawExecute(command) + + if s and m then + self:setResult(m) + else + self.grid:setValues({ }) + self.grid:draw() + if m then + self.notification:error(m, 5) + end + end +end + +UI:setPage(resultsPage) +Event.pullEvents() +UI.term:reset() diff --git a/apps/Network.lua b/apps/Network.lua new file mode 100644 index 0000000..fc0d4d8 --- /dev/null +++ b/apps/Network.lua @@ -0,0 +1,153 @@ +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 = 'Reboot', event = 'reboot' }, + }, + }), + grid = UI.ScrollingGrid({ + y = 2, + values = network, + columns = gridColumns, + sortColumn = 'label', + autospace = true, + }), + notification = UI.Notification(), + accelerators = { + q = 'quit', + c = 'clear', + }, +}) + +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.selected + 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 == '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 + +function page.grid:draw() + self:adjustWidth() + UI.Grid.draw(self) + if page.notification.enabled then + page.notification:draw() + end +end + +function updateComputers() + 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(updateComputers) +UI.term:reset() diff --git a/apps/Overview.lua b/apps/Overview.lua new file mode 100644 index 0000000..6fd5143 --- /dev/null +++ b/apps/Overview.lua @@ -0,0 +1,445 @@ +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') + +multishell.setTitle(multishell.getCurrent(), 'Overview') +UI:configure('Overview', ...) + +local config = { + Recent = { }, + currentCategory = 'Apps', +} +local applications = { } + +Config.load('Overview', config) +Config.load('apps', applications) + +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 = col + child.y = row + + 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() +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() + 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 = '/apps/shell', + args = { event.button.app.run }, + focused = true, + }) + + elseif event.type == 'shell' then + multishell.openTab({ + path = '/apps/shell', + focused = true, + }) + + elseif event.type == 'lua' then + multishell.openTab({ + path = '/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 == 'refresh' then + applications = { } + Config.load('apps', applications) + self:refresh() + self:draw() + self.notification:success('Refreshed') + + elseif event.type == 'delete' then + local focused = page:getFocused() + if focused.app then + local _,k = Util.find(applications, 'run', focused.app.run) + if k then + table.remove(applications, k) + Config.update('apps', applications) + page:refresh() + page:draw() + self.notification:success('Removed') + end + 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 gutter = math.floor((UI.term.width - formWidth) / 2) + 1 + +local editor = UI.Page({ + backgroundColor = colors.blue, + form = UI.Form({ + fields = { + { label = 'Title', key = 'title', width = 15, limit = 11, display = UI.Form.D.entry, + help = 'Application title' }, + { label = 'Run', key = 'run', width = formWidth - 11, limit = 100, display = UI.Form.D.entry, + help = 'Full path to application' }, + { label = 'Category', key = 'category', width = 15, limit = 11, display = UI.Form.D.entry, + help = 'Category of application' }, + { text = 'Accept', event = 'accept', display = UI.Form.D.button, + x = 1, y = 9, width = 10 }, + { text = 'Cancel', event = 'cancel', display = UI.Form.D.button, + x = formWidth - 11, y = 9, width = 10 }, + }, + labelWidth = 8, + x = gutter + 1, + y = math.max(2, math.floor((UI.term.height - 9) / 2)), + height = 9, + width = UI.term.width - (gutter * 2), + image = UI.NftImage({ + y = 5, + x = 1, + height = 3, + width = 8, + }), + button = UI.Button({ + x = 10, + y = 6, + text = 'Load icon', + width = 11, + event = 'loadIcon', + }), + }), + statusBar = UI.StatusBar(), + notification = UI.Notification(), + iconFile = '', +}) + +function editor:enable(app) + if app then + self.original = app + self.form:setValues(Util.shallowCopy(app)) + + local icon + if app.icon then + icon = parseIcon(app.icon) + end + self.form.image:setImage(icon) + + self:setFocus(self.form.children[1]) + end + UI.Page.enable(self) +end + +function editor.form.image:draw() + self:clear() + UI.NftImage.draw(self) +end + +function editor:updateApplications(app, original) + if original.run then + local _,k = Util.find(applications, 'run', original.run) + if k then + applications[k] = nil + end + end + table.insert(applications, app) + Config.update('apps', applications) +end + +function editor:eventHandler(event) + + if 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 + 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 + self.notification:error(m:gsub('.*: (.*)', '%1')) + end + end + end) + + elseif event.type == 'accept' then + local values = self.form.values + if #values.run > 0 and #values.title > 0 and #values.category > 0 then + UI:setPreviousPage() + self:updateApplications(values, self.original) + page:refresh() + page:draw() + else + self.notification:error('Require fields missing') + --self.statusBar:setStatus('Require fields missing') + --self.statusBar:draw() + end + else + return UI.Page.eventHandler(self, event) + end + return true +end + +UI:setPages({ + editor = editor, + main = page, +}) + +Event.addHandler('os_register_app', function() + applications = { } + Config.load('apps', applications) + 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() diff --git a/apps/Peripherals.lua b/apps/Peripherals.lua new file mode 100644 index 0000000..ccd3e21 --- /dev/null +++ b/apps/Peripherals.lua @@ -0,0 +1,213 @@ +require = requireInjector(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, + }), + container = UI.Window({ + y = 8, + height = UI.term.height-8, + viewportConsole = UI.ViewportWindow({ + 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 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.grid:update() + self.grid:setIndex(1) + + self.statusBar:setStatus(self.peripheral.type) + UI.Page.enable(self) +end + +function methodsPage.container.viewportConsole:draw() + if methodsPage.grid:getSelected() then + methodsPage:drawMethodInfo(self, methodsPage.grid:getSelected()) + end +end + +function methodsPage:eventHandler(event) + if event.type == 'back' then + UI:setPage(peripheralsPage) + return true + elseif event.type == 'grid_focus_row' then + self.container.viewportConsole.height = 1 + self.container.viewportConsole.offset = 0 + self.container.viewportConsole.y = 1 + self:drawMethodInfo(self.container.viewportConsole, event.selected) + end + return UI.Page.eventHandler(self, event) +end + +function methodsPage:drawMethodInfo(c, method) + + c:clear() + c:setCursorPos(1, 1) + + if method.noext then + c:print('No extended Information') + return 2 + 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.height = c.cursorY + 1 + + term.setBackgroundColor(colors.black) + return y +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() diff --git a/apps/Pim.lua b/apps/Pim.lua new file mode 100644 index 0000000..e3dea2f --- /dev/null +++ b/apps/Pim.lua @@ -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() diff --git a/apps/Script.lua b/apps/Script.lua new file mode 100644 index 0000000..3a69aec --- /dev/null +++ b/apps/Script.lua @@ -0,0 +1,561 @@ +require = requireInjector(getfenv(1)) +local Event = require('event') +local UI = require('ui') +local Socket = require('socket') +local Config = require('config') + +local GROUPS_PATH = '/apps/groups' +local SCRIPTS_PATH = '/apps/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