mirror of
https://github.com/kepler155c/opus
synced 2025-12-13 11:58:06 +00:00
format and installer branches
This commit is contained in:
@@ -1,4 +1,4 @@
|
||||
_G.requireInjector()
|
||||
_G.requireInjector(_ENV)
|
||||
|
||||
local UI = require('ui')
|
||||
local Util = require('util')
|
||||
@@ -10,89 +10,89 @@ UI:configure('Help', ...)
|
||||
|
||||
local topics = { }
|
||||
for _,topic in pairs(help.topics()) do
|
||||
if help.lookup(topic) then
|
||||
table.insert(topics, { name = topic })
|
||||
end
|
||||
if help.lookup(topic) then
|
||||
table.insert(topics, { name = topic })
|
||||
end
|
||||
end
|
||||
|
||||
local page = UI.Page {
|
||||
labelText = UI.Text {
|
||||
x = 3, y = 2,
|
||||
value = 'Search',
|
||||
},
|
||||
filter = UI.TextEntry {
|
||||
x = 10, y = 2, ex = -3,
|
||||
limit = 32,
|
||||
},
|
||||
grid = UI.ScrollingGrid {
|
||||
y = 4,
|
||||
values = topics,
|
||||
columns = {
|
||||
{ heading = 'Topic', key = 'name' },
|
||||
},
|
||||
sortColumn = 'name',
|
||||
},
|
||||
accelerators = {
|
||||
q = 'quit',
|
||||
enter = 'grid_select',
|
||||
},
|
||||
labelText = UI.Text {
|
||||
x = 3, y = 2,
|
||||
value = 'Search',
|
||||
},
|
||||
filter = UI.TextEntry {
|
||||
x = 10, y = 2, ex = -3,
|
||||
limit = 32,
|
||||
},
|
||||
grid = UI.ScrollingGrid {
|
||||
y = 4,
|
||||
values = topics,
|
||||
columns = {
|
||||
{ heading = 'Topic', key = 'name' },
|
||||
},
|
||||
sortColumn = 'name',
|
||||
},
|
||||
accelerators = {
|
||||
q = 'quit',
|
||||
enter = 'grid_select',
|
||||
},
|
||||
}
|
||||
|
||||
local topicPage = UI.Page {
|
||||
backgroundColor = colors.black,
|
||||
titleBar = UI.TitleBar {
|
||||
title = 'text',
|
||||
previousPage = true,
|
||||
},
|
||||
helpText = UI.TextArea {
|
||||
backgroundColor = colors.black,
|
||||
x = 2, ex = -1, y = 3, ey = -2,
|
||||
},
|
||||
accelerators = {
|
||||
q = 'back',
|
||||
backspace = 'back',
|
||||
},
|
||||
backgroundColor = colors.black,
|
||||
titleBar = UI.TitleBar {
|
||||
title = 'text',
|
||||
previousPage = true,
|
||||
},
|
||||
helpText = UI.TextArea {
|
||||
backgroundColor = colors.black,
|
||||
x = 2, ex = -1, y = 3, ey = -2,
|
||||
},
|
||||
accelerators = {
|
||||
q = 'back',
|
||||
backspace = 'back',
|
||||
},
|
||||
}
|
||||
|
||||
function topicPage:eventHandler(event)
|
||||
if event.type == 'back' then
|
||||
UI:setPreviousPage()
|
||||
end
|
||||
return UI.Page.eventHandler(self, event)
|
||||
if event.type == 'back' then
|
||||
UI:setPreviousPage()
|
||||
end
|
||||
return UI.Page.eventHandler(self, event)
|
||||
end
|
||||
|
||||
function page:eventHandler(event)
|
||||
if event.type == 'quit' then
|
||||
UI:exitPullEvents()
|
||||
if event.type == 'quit' then
|
||||
UI:exitPullEvents()
|
||||
|
||||
elseif event.type == 'grid_select' then
|
||||
if self.grid:getSelected() then
|
||||
local name = self.grid:getSelected().name
|
||||
local f = help.lookup(name)
|
||||
elseif event.type == 'grid_select' then
|
||||
if self.grid:getSelected() then
|
||||
local name = self.grid:getSelected().name
|
||||
local f = help.lookup(name)
|
||||
|
||||
topicPage.titleBar.title = name
|
||||
topicPage.helpText:setText(Util.readFile(f))
|
||||
topicPage.titleBar.title = name
|
||||
topicPage.helpText:setText(Util.readFile(f))
|
||||
|
||||
UI:setPage(topicPage)
|
||||
end
|
||||
UI:setPage(topicPage)
|
||||
end
|
||||
|
||||
elseif event.type == 'text_change' then
|
||||
if #event.text == 0 then
|
||||
self.grid.values = topics
|
||||
else
|
||||
self.grid.values = { }
|
||||
for _,f in pairs(topics) do
|
||||
if string.find(f.name, event.text) then
|
||||
table.insert(self.grid.values, f)
|
||||
end
|
||||
end
|
||||
end
|
||||
self.grid:update()
|
||||
self.grid:setIndex(1)
|
||||
self.grid:draw()
|
||||
else
|
||||
return UI.Page.eventHandler(self, event)
|
||||
end
|
||||
elseif event.type == 'text_change' then
|
||||
if #event.text == 0 then
|
||||
self.grid.values = topics
|
||||
else
|
||||
self.grid.values = { }
|
||||
for _,f in pairs(topics) do
|
||||
if string.find(f.name, event.text) then
|
||||
table.insert(self.grid.values, f)
|
||||
end
|
||||
end
|
||||
end
|
||||
self.grid:update()
|
||||
self.grid:setIndex(1)
|
||||
self.grid:draw()
|
||||
else
|
||||
return UI.Page.eventHandler(self, event)
|
||||
end
|
||||
end
|
||||
|
||||
UI:setPage(page)
|
||||
|
||||
456
sys/apps/Installer.lua
Normal file
456
sys/apps/Installer.lua
Normal file
@@ -0,0 +1,456 @@
|
||||
local colors = _G.colors
|
||||
local fs = _G.fs
|
||||
local http = _G.http
|
||||
local install = _ENV.install
|
||||
local os = _G.os
|
||||
|
||||
local requireInjector
|
||||
if not install.testing then
|
||||
local url ='https://raw.githubusercontent.com/kepler155c/opus/master/sys/apis/injector.lua'
|
||||
requireInjector = load(http.get(url).readAll())()
|
||||
else
|
||||
requireInjector = _G.requireInjector
|
||||
end
|
||||
|
||||
requireInjector(_ENV)
|
||||
|
||||
if not install.testing then
|
||||
if package then
|
||||
for _ = 1, 4 do
|
||||
table.remove(package.loaders, 1)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
local Git = require('git')
|
||||
local UI = require('ui')
|
||||
local Util = require('util')
|
||||
|
||||
local currentFile = ''
|
||||
local currentProgress = 0
|
||||
local cancelEvent
|
||||
|
||||
local args = { ... }
|
||||
local steps = install.steps[args[1] or 'install']
|
||||
|
||||
if not steps then
|
||||
error('Invalid install type')
|
||||
end
|
||||
|
||||
local mode = steps[#steps]
|
||||
|
||||
if UI.term.width < 32 then
|
||||
cancelEvent = 'quit'
|
||||
end
|
||||
|
||||
local page = UI.Page {
|
||||
backgroundColor = colors.cyan,
|
||||
titleBar = UI.TitleBar {
|
||||
event = cancelEvent,
|
||||
},
|
||||
wizard = UI.Wizard {
|
||||
y = 2, ey = -2,
|
||||
},
|
||||
notification = UI.Notification(),
|
||||
accelerators = {
|
||||
q = 'quit',
|
||||
},
|
||||
}
|
||||
|
||||
local pages = {
|
||||
splash = UI.Viewport { },
|
||||
review = UI.Viewport { },
|
||||
license = UI.Viewport {
|
||||
backgroundColor = colors.black,
|
||||
},
|
||||
branch = UI.Window {
|
||||
grid = UI.ScrollingGrid {
|
||||
ey = -3,
|
||||
columns = {
|
||||
{ heading = 'Branch', key = 'branch' },
|
||||
{ heading = 'Description', key = 'description' },
|
||||
},
|
||||
values = install.branches,
|
||||
autospace = true,
|
||||
},
|
||||
},
|
||||
files = UI.Window {
|
||||
grid = UI.ScrollingGrid {
|
||||
ey = -3,
|
||||
columns = {
|
||||
{ heading = 'Files', key = 'file' },
|
||||
},
|
||||
sortColumn = 'file',
|
||||
},
|
||||
},
|
||||
install = UI.Window {
|
||||
progressBar = UI.ProgressBar {
|
||||
y = -1,
|
||||
},
|
||||
},
|
||||
uninstall = UI.Window {
|
||||
progressBar = UI.ProgressBar {
|
||||
y = -1,
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
local function getFileList()
|
||||
if install.gitRepo then
|
||||
local gitFiles = Git.list(string.format('%s/%s', install.gitRepo, install.gitBranch or 'master'))
|
||||
install.files = { }
|
||||
install.diskspace = 0
|
||||
for path, entry in pairs(gitFiles) do
|
||||
install.files[path] = entry.url
|
||||
install.diskspace = install.diskspace + entry.size
|
||||
end
|
||||
end
|
||||
|
||||
if not install.files or Util.empty(install.files) then
|
||||
error('File list is missing or empty')
|
||||
end
|
||||
end
|
||||
|
||||
--[[ Splash ]]--
|
||||
function pages.splash:enable()
|
||||
page.titleBar.title = 'Installer v1.0'
|
||||
UI.Viewport.enable(self)
|
||||
end
|
||||
|
||||
function pages.splash:draw()
|
||||
self:clear()
|
||||
self:setCursorPos(1, 1)
|
||||
self:print(
|
||||
string.format('%s v%s\n', install.title, install.version), nil, colors.yellow)
|
||||
self:print(
|
||||
string.format('By: %s\n\n%s\n', install.author, install.description))
|
||||
|
||||
self.ymax = self.cursorY
|
||||
end
|
||||
|
||||
--[[ License ]]--
|
||||
function pages.license:enable()
|
||||
page.titleBar.title = 'License Review'
|
||||
page.wizard.nextButton.text = 'Accept'
|
||||
UI.Viewport.enable(self)
|
||||
end
|
||||
|
||||
function pages.license:draw()
|
||||
self:clear()
|
||||
self:setCursorPos(1, 1)
|
||||
self:print(
|
||||
string.format('Copyright (c) %s %s\n\n', install.copyrightYear,
|
||||
install.copyrightHolders),
|
||||
nil, colors.yellow)
|
||||
self:print(install.license)
|
||||
|
||||
self.ymax = self.cursorY + 1
|
||||
end
|
||||
|
||||
--[[ Review ]]--
|
||||
function pages.review:enable()
|
||||
if mode == 'uninstall' then
|
||||
page.nextButton.text = 'Remove'
|
||||
page.titleBar.title = 'Remove Installed Files'
|
||||
else
|
||||
page.wizard.nextButton.text = 'Begin'
|
||||
page.titleBar.title = 'Download and Install'
|
||||
end
|
||||
UI.Viewport.enable(self)
|
||||
end
|
||||
|
||||
function pages.review:draw()
|
||||
self:clear()
|
||||
self:setCursorPos(1, 1)
|
||||
|
||||
local text = 'Ready to begin installation.\n\nProceeding will download and install the files to the hard drive.'
|
||||
if mode == 'uninstall' then
|
||||
text = 'Ready to begin.\n\nProceeding will remove the files previously installed.'
|
||||
end
|
||||
self:print(text)
|
||||
|
||||
self.ymax = self.cursorY + 1
|
||||
end
|
||||
|
||||
--[[ Files ]]--
|
||||
function pages.files:enable()
|
||||
page.titleBar.title = 'Review Files'
|
||||
self.grid.values = { }
|
||||
for k,v in pairs(install.files) do
|
||||
table.insert(self.grid.values, { file = k, code = v })
|
||||
end
|
||||
self.grid:update()
|
||||
UI.Window.enable(self)
|
||||
end
|
||||
|
||||
function pages.files:draw()
|
||||
self:clear()
|
||||
|
||||
local 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
|
||||
|
||||
if install.diskspace then
|
||||
|
||||
local bg = self.backgroundColor
|
||||
|
||||
local diskFree = fs.getFreeSpace('/')
|
||||
if install.diskspace > diskFree then
|
||||
bg = colors.red
|
||||
end
|
||||
|
||||
local text = string.format('Space Required: %s, Free: %s',
|
||||
formatSize(install.diskspace), formatSize(diskFree))
|
||||
|
||||
if #text > self.width then
|
||||
text = string.format('Space: %s Free: %s',
|
||||
formatSize(install.diskspace), formatSize(diskFree))
|
||||
end
|
||||
|
||||
self:write(1, self.height, Util.widthify(text, self.width), bg)
|
||||
end
|
||||
self.grid:draw()
|
||||
end
|
||||
|
||||
--[[
|
||||
function pages.files:view(url)
|
||||
local s, m = pcall(function()
|
||||
page.notification:info('Downloading')
|
||||
page:sync()
|
||||
Util.download(url, '/.source')
|
||||
end)
|
||||
page.notification:disable()
|
||||
if s then
|
||||
shell.run('edit /.source')
|
||||
fs.delete('/.source')
|
||||
page:draw()
|
||||
page.notification:cancel()
|
||||
else
|
||||
page.notification:error(m:gsub('.*: (.*)', '%1'))
|
||||
end
|
||||
end
|
||||
|
||||
function pages.files:eventHandler(event)
|
||||
if event.type == 'grid_select' then
|
||||
self:view(event.selected.code)
|
||||
return true
|
||||
end
|
||||
end
|
||||
--]]
|
||||
|
||||
local function drawCommon(self)
|
||||
if currentFile then
|
||||
self:write(1, 3, 'File:')
|
||||
self:write(1, 4, Util.widthify(currentFile, self.width))
|
||||
else
|
||||
self:write(1, 3, 'Finished')
|
||||
end
|
||||
if self.failed then
|
||||
self:write(1, 5, Util.widthify(self.failed, self.width), colors.red)
|
||||
end
|
||||
self:write(1, self.height - 1, 'Progress')
|
||||
|
||||
self.progressBar.value = currentProgress
|
||||
self.progressBar:draw()
|
||||
self:sync()
|
||||
end
|
||||
|
||||
--[[ Branch ]]--
|
||||
function pages.branch:enable()
|
||||
page.titleBar.title = 'Select Branch'
|
||||
UI.Window.enable(self)
|
||||
end
|
||||
|
||||
function pages.branch:eventHandler(event)
|
||||
-- user is navigating to next view (not previous)
|
||||
if event.type == 'enable_view' and event.next then
|
||||
install.gitBranch = self.grid:getSelected().branch
|
||||
getFileList()
|
||||
end
|
||||
end
|
||||
|
||||
--[[ Install ]]--
|
||||
function pages.install:enable()
|
||||
page.wizard.cancelButton:disable()
|
||||
page.wizard.previousButton:disable()
|
||||
page.wizard.nextButton:disable()
|
||||
|
||||
page.titleBar.title = 'Installing...'
|
||||
page.titleBar.event = nil
|
||||
|
||||
UI.Window.enable(self)
|
||||
|
||||
page:draw()
|
||||
page:sync()
|
||||
|
||||
local i = 0
|
||||
local numFiles = Util.size(install.files)
|
||||
for filename,url in pairs(install.files) do
|
||||
currentFile = filename
|
||||
currentProgress = i / numFiles * 100
|
||||
self:draw(self)
|
||||
self:sync()
|
||||
local s, m = pcall(function()
|
||||
Util.download(url, fs.combine(install.directory or '', filename))
|
||||
end)
|
||||
if not s then
|
||||
self.failed = m:gsub('.*: (.*)', '%1')
|
||||
break
|
||||
end
|
||||
i = i + 1
|
||||
end
|
||||
|
||||
if not self.failed then
|
||||
currentProgress = 100
|
||||
currentFile = nil
|
||||
|
||||
if install.postInstall then
|
||||
local s, m = pcall(function() install.postInstall(page, UI) end)
|
||||
if not s then
|
||||
self.failed = m:gsub('.*: (.*)', '%1')
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
page.wizard.nextButton.text = 'Exit'
|
||||
page.wizard.nextButton.event = 'quit'
|
||||
if not self.failed and install.rebootAfter then
|
||||
page.wizard.nextButton.text = 'Reboot'
|
||||
page.wizard.nextButton.event = 'reboot'
|
||||
end
|
||||
|
||||
page.wizard.nextButton:enable()
|
||||
page:draw()
|
||||
page:sync()
|
||||
|
||||
if not self.failed and Util.key(args, 'automatic') then
|
||||
if install.rebootAfter then
|
||||
os.reboot()
|
||||
else
|
||||
UI:exitPullEvents()
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
function pages.install:draw()
|
||||
self:clear()
|
||||
local text = 'The files are being installed'
|
||||
if #text > self.width then
|
||||
text = 'Installing files'
|
||||
end
|
||||
self:write(1, 1, text, nil, colors.yellow)
|
||||
|
||||
drawCommon(self)
|
||||
end
|
||||
|
||||
--[[ Uninstall ]]--
|
||||
function pages.uninstall:enable()
|
||||
page.wizard.cancelButton:disable()
|
||||
page.wizard.previousButton:disable()
|
||||
page.wizard.nextButton:disable()
|
||||
|
||||
page.titleBar.title = 'Uninstalling...'
|
||||
page.titleBar.event = nil
|
||||
|
||||
page:draw()
|
||||
page:sync()
|
||||
|
||||
UI.Window.enable(self)
|
||||
|
||||
local function pruneDir(dir)
|
||||
if #dir > 0 then
|
||||
if fs.exists(dir) then
|
||||
local files = fs.list(dir)
|
||||
if #files == 0 then
|
||||
fs.delete(dir)
|
||||
pruneDir(fs.getDir(dir))
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
local i = 0
|
||||
local numFiles = Util.size(install.files)
|
||||
for k in pairs(install.files) do
|
||||
currentFile = k
|
||||
currentProgress = i / numFiles * 100
|
||||
self:draw()
|
||||
self:sync()
|
||||
fs.delete(k)
|
||||
pruneDir(fs.getDir(k))
|
||||
i = i + 1
|
||||
end
|
||||
|
||||
currentProgress = 100
|
||||
currentFile = nil
|
||||
|
||||
page.wizard.nextButton.text = 'Exit'
|
||||
page.wizard.nextButton.event = 'quit'
|
||||
page.wizard.nextButton:enable()
|
||||
|
||||
page:draw()
|
||||
page:sync()
|
||||
end
|
||||
|
||||
function pages.uninstall:draw()
|
||||
self:clear()
|
||||
self:write(1, 1, 'Uninstalling files', nil, colors.yellow)
|
||||
drawCommon(self)
|
||||
end
|
||||
|
||||
function page:eventHandler(event)
|
||||
if event.type == 'cancel' then
|
||||
UI:exitPullEvents()
|
||||
|
||||
elseif event.type == 'reboot' then
|
||||
os.reboot()
|
||||
|
||||
elseif event.type == 'quit' then
|
||||
UI:exitPullEvents()
|
||||
|
||||
else
|
||||
return UI.Page.eventHandler(self, event)
|
||||
end
|
||||
return true
|
||||
end
|
||||
|
||||
function page:enable()
|
||||
UI.Page.enable(self)
|
||||
self:setFocus(page.wizard.nextButton)
|
||||
if UI.term.width < 32 then
|
||||
page.wizard.cancelButton:disable()
|
||||
page.wizard.previousButton.x = 2
|
||||
end
|
||||
end
|
||||
|
||||
getFileList()
|
||||
|
||||
local wizardPages = { }
|
||||
for k,v in ipairs(steps) do
|
||||
if not pages[v] then
|
||||
error('Invalid step: ' .. v)
|
||||
end
|
||||
wizardPages[k] = pages[v]
|
||||
wizardPages[k].index = k
|
||||
wizardPages[k].x = 2
|
||||
wizardPages[k].y = 2
|
||||
wizardPages[k].ey = -3
|
||||
wizardPages[k].ex = -2
|
||||
end
|
||||
page.wizard:add(wizardPages)
|
||||
|
||||
if Util.key(steps, 'install') and install.preInstall then
|
||||
install.preInstall(page, UI)
|
||||
end
|
||||
|
||||
UI:setPage(page)
|
||||
local s, m = pcall(function() UI:pullEvents() end)
|
||||
if not s then
|
||||
UI.term:reset()
|
||||
_G.printError(m)
|
||||
end
|
||||
@@ -1,6 +1,5 @@
|
||||
_G.requireInjector(_ENV)
|
||||
|
||||
local Event = require('event')
|
||||
local History = require('history')
|
||||
local UI = require('ui')
|
||||
local Util = require('util')
|
||||
@@ -10,8 +9,10 @@ local os = _G.os
|
||||
local textutils = _G.textutils
|
||||
local term = _G.term
|
||||
|
||||
local _exit
|
||||
|
||||
local sandboxEnv = setmetatable(Util.shallowCopy(_ENV), { __index = _G })
|
||||
sandboxEnv.exit = function() Event.exitPullEvents() end
|
||||
sandboxEnv.exit = function() _exit = true end
|
||||
sandboxEnv._echo = function( ... ) return { ... } end
|
||||
_G.requireInjector(sandboxEnv)
|
||||
|
||||
@@ -19,7 +20,6 @@ UI:configure('Lua', ...)
|
||||
|
||||
local command = ''
|
||||
local history = History.load('usr/.lua_history', 25)
|
||||
local extChars = Util.getVersion() > 1.76
|
||||
|
||||
local page = UI.Page {
|
||||
menuBar = UI.MenuBar {
|
||||
@@ -41,10 +41,6 @@ local page = UI.Page {
|
||||
[ 'control-space' ] = 'autocomplete',
|
||||
},
|
||||
},
|
||||
indicator = UI.Text {
|
||||
backgroundColor = colors.black,
|
||||
y = 2, x = -1, width = 1,
|
||||
},
|
||||
grid = UI.ScrollingGrid {
|
||||
y = 3, ey = -2,
|
||||
columns = {
|
||||
@@ -62,21 +58,10 @@ local page = UI.Page {
|
||||
},
|
||||
output = UI.Embedded {
|
||||
y = -6,
|
||||
backgroundColor = colors.gray,
|
||||
},
|
||||
--notification = UI.Notification(),
|
||||
}
|
||||
|
||||
function page.indicator:showResult(s)
|
||||
local values = {
|
||||
[ true ] = { c = colors.green, i = (extChars and '\003') or '+' },
|
||||
[ false ] = { c = colors.red, i = 'x' }
|
||||
}
|
||||
|
||||
self.textColor = values[s].c
|
||||
self.value = values[s].i
|
||||
self:draw()
|
||||
end
|
||||
|
||||
function page:setPrompt(value, focus)
|
||||
self.prompt:setValue(value)
|
||||
self.prompt.scroll = 0
|
||||
@@ -133,12 +118,12 @@ end
|
||||
|
||||
function page:eventHandler(event)
|
||||
if event.type == 'global' then
|
||||
self:setPrompt('', true)
|
||||
self:setPrompt('_G', true)
|
||||
self:executeStatement('_G')
|
||||
command = nil
|
||||
|
||||
elseif event.type == 'local' then
|
||||
self:setPrompt('', true)
|
||||
self:setPrompt('_ENV', true)
|
||||
self:executeStatement('_ENV')
|
||||
command = nil
|
||||
|
||||
@@ -341,8 +326,11 @@ end
|
||||
function page:executeStatement(statement)
|
||||
command = statement
|
||||
|
||||
local s, m
|
||||
local oterm = term.redirect(self.output.win)
|
||||
local s, m = self:rawExecute(command)
|
||||
pcall(function()
|
||||
s, m = self:rawExecute(command)
|
||||
end)
|
||||
if not s then
|
||||
_G.printError(m)
|
||||
end
|
||||
@@ -353,14 +341,14 @@ function page:executeStatement(statement)
|
||||
else
|
||||
self.grid:setValues({ })
|
||||
self.grid:draw()
|
||||
if m then
|
||||
if not self.output.enabled then
|
||||
self:emit({ type = 'show_output' })
|
||||
end
|
||||
--self.notification:error(m, 5)
|
||||
if m and not self.output.enabled then
|
||||
self:emit({ type = 'show_output' })
|
||||
end
|
||||
end
|
||||
self.indicator:showResult(not not s)
|
||||
|
||||
if _exit then
|
||||
UI:exitPullEvents()
|
||||
end
|
||||
end
|
||||
|
||||
local args = { ... }
|
||||
@@ -371,5 +359,4 @@ if args[1] then
|
||||
end
|
||||
|
||||
UI:setPage(page)
|
||||
Event.pullEvents()
|
||||
UI.term:reset()
|
||||
UI:pullEvents()
|
||||
|
||||
@@ -15,196 +15,196 @@ local shell = _ENV.shell
|
||||
UI:configure('Network', ...)
|
||||
|
||||
local gridColumns = {
|
||||
{ heading = 'Label', key = 'label' },
|
||||
{ heading = 'Dist', key = 'distance' },
|
||||
{ heading = 'Status', key = 'status' },
|
||||
{ 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', width = 5 })
|
||||
table.insert(gridColumns, { heading = 'Uptime', key = 'uptime' })
|
||||
table.insert(gridColumns, { heading = 'Fuel', key = 'fuel', width = 5 })
|
||||
table.insert(gridColumns, { heading = 'Uptime', key = 'uptime' })
|
||||
end
|
||||
|
||||
local page = UI.Page {
|
||||
menuBar = UI.MenuBar {
|
||||
buttons = {
|
||||
{ text = 'Connect', dropdown = {
|
||||
{ text = 'Telnet t', event = 'telnet' },
|
||||
{ text = 'VNC v', event = 'vnc' },
|
||||
UI.MenuBar.spacer,
|
||||
{ text = 'Reboot r', event = 'reboot' },
|
||||
} },
|
||||
--{ text = 'Chat', event = 'chat' },
|
||||
{ text = 'Trust', dropdown = {
|
||||
{ text = 'Establish', event = 'trust' },
|
||||
{ text = 'Remove', event = 'untrust' },
|
||||
} },
|
||||
{ text = 'Help', event = 'help' },
|
||||
},
|
||||
},
|
||||
grid = UI.ScrollingGrid {
|
||||
y = 2,
|
||||
values = network,
|
||||
columns = gridColumns,
|
||||
sortColumn = 'label',
|
||||
autospace = true,
|
||||
},
|
||||
notification = UI.Notification { },
|
||||
accelerators = {
|
||||
t = 'telnet',
|
||||
v = 'vnc',
|
||||
r = 'reboot',
|
||||
q = 'quit',
|
||||
c = 'clear',
|
||||
},
|
||||
menuBar = UI.MenuBar {
|
||||
buttons = {
|
||||
{ text = 'Connect', dropdown = {
|
||||
{ text = 'Telnet t', event = 'telnet' },
|
||||
{ text = 'VNC v', event = 'vnc' },
|
||||
UI.MenuBar.spacer,
|
||||
{ text = 'Reboot r', event = 'reboot' },
|
||||
} },
|
||||
--{ text = 'Chat', event = 'chat' },
|
||||
{ text = 'Trust', dropdown = {
|
||||
{ text = 'Establish', event = 'trust' },
|
||||
{ text = 'Remove', event = 'untrust' },
|
||||
} },
|
||||
{ text = 'Help', event = 'help' },
|
||||
},
|
||||
},
|
||||
grid = UI.ScrollingGrid {
|
||||
y = 2,
|
||||
values = network,
|
||||
columns = gridColumns,
|
||||
sortColumn = 'label',
|
||||
autospace = true,
|
||||
},
|
||||
notification = UI.Notification { },
|
||||
accelerators = {
|
||||
t = 'telnet',
|
||||
v = 'vnc',
|
||||
r = 'reboot',
|
||||
q = 'quit',
|
||||
c = 'clear',
|
||||
},
|
||||
}
|
||||
|
||||
local function sendCommand(host, command)
|
||||
if not device.wireless_modem then
|
||||
page.notification:error('Wireless modem not present')
|
||||
return
|
||||
end
|
||||
if not device.wireless_modem then
|
||||
page.notification:error('Wireless modem not present')
|
||||
return
|
||||
end
|
||||
|
||||
page.notification:info('Connecting')
|
||||
page:sync()
|
||||
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
|
||||
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' then
|
||||
multishell.openTab({
|
||||
path = 'sys/apps/telnet.lua',
|
||||
focused = true,
|
||||
args = { t.id },
|
||||
title = t.label,
|
||||
})
|
||||
elseif event.type == 'vnc' then
|
||||
multishell.openTab({
|
||||
path = 'sys/apps/vnc.lua',
|
||||
focused = true,
|
||||
args = { t.id },
|
||||
title = t.label,
|
||||
})
|
||||
elseif event.type == 'clear' then
|
||||
Util.clear(network)
|
||||
page.grid:update()
|
||||
page.grid:draw()
|
||||
local t = self.grid:getSelected()
|
||||
if t then
|
||||
if event.type == 'telnet' then
|
||||
multishell.openTab({
|
||||
path = 'sys/apps/telnet.lua',
|
||||
focused = true,
|
||||
args = { t.id },
|
||||
title = t.label,
|
||||
})
|
||||
elseif event.type == 'vnc' then
|
||||
multishell.openTab({
|
||||
path = 'sys/apps/vnc.lua',
|
||||
focused = true,
|
||||
args = { t.id },
|
||||
title = t.label,
|
||||
})
|
||||
elseif event.type == 'clear' then
|
||||
Util.clear(network)
|
||||
page.grid:update()
|
||||
page.grid:draw()
|
||||
|
||||
elseif event.type == 'trust' then
|
||||
shell.openForegroundTab('trust ' .. t.id)
|
||||
elseif event.type == 'trust' then
|
||||
shell.openForegroundTab('trust ' .. t.id)
|
||||
|
||||
elseif event.type == 'untrust' then
|
||||
local trustList = Util.readTable('usr/.known_hosts') or { }
|
||||
trustList[t.id] = nil
|
||||
Util.writeTable('usr/.known_hosts', trustList)
|
||||
elseif event.type == 'untrust' then
|
||||
local trustList = Util.readTable('usr/.known_hosts') or { }
|
||||
trustList[t.id] = nil
|
||||
Util.writeTable('usr/.known_hosts', trustList)
|
||||
|
||||
elseif event.type == 'chat' then
|
||||
multishell.openTab({
|
||||
path = 'sys/apps/shell',
|
||||
args = { 'chat join opusChat-' .. t.id .. ' guest-' .. os.getComputerID() },
|
||||
title = 'Chatroom',
|
||||
focused = true,
|
||||
})
|
||||
elseif event.type == 'reboot' then
|
||||
sendCommand(t.id, 'reboot')
|
||||
elseif event.type == 'chat' then
|
||||
multishell.openTab({
|
||||
path = 'sys/apps/shell',
|
||||
args = { 'chat join opusChat-' .. t.id .. ' guest-' .. os.getComputerID() },
|
||||
title = 'Chatroom',
|
||||
focused = true,
|
||||
})
|
||||
elseif event.type == 'reboot' then
|
||||
sendCommand(t.id, 'reboot')
|
||||
|
||||
elseif event.type == 'shutdown' then
|
||||
sendCommand(t.id, 'shutdown')
|
||||
end
|
||||
end
|
||||
if event.type == 'help' then
|
||||
UI:setPage(UI.Dialog {
|
||||
title = 'Network Help',
|
||||
height = 10,
|
||||
backgroundColor = colors.white,
|
||||
text = UI.TextArea {
|
||||
x = 2, y = 2,
|
||||
backgroundColor = colors.white,
|
||||
value = [[
|
||||
elseif event.type == 'shutdown' then
|
||||
sendCommand(t.id, 'shutdown')
|
||||
end
|
||||
end
|
||||
if event.type == 'help' then
|
||||
UI:setPage(UI.Dialog {
|
||||
title = 'Network Help',
|
||||
height = 10,
|
||||
backgroundColor = colors.white,
|
||||
text = UI.TextArea {
|
||||
x = 2, y = 2,
|
||||
backgroundColor = colors.white,
|
||||
value = [[
|
||||
In order to connect to another computer:
|
||||
|
||||
1. The target computer must have a password set (run 'password' from the shell prompt).
|
||||
2. From this computer, click trust and enter the password for that computer.
|
||||
1. The target computer must have a password set (run 'password' from the shell prompt).
|
||||
2. From this computer, click trust and enter the password for that computer.
|
||||
|
||||
This only needs to be done once.
|
||||
]],
|
||||
},
|
||||
accelerators = {
|
||||
q = 'cancel',
|
||||
}
|
||||
})
|
||||
elseif event.type == 'quit' then
|
||||
Event.exitPullEvents()
|
||||
end
|
||||
UI.Page.eventHandler(self, event)
|
||||
]],
|
||||
},
|
||||
accelerators = {
|
||||
q = 'cancel',
|
||||
}
|
||||
})
|
||||
elseif event.type == 'quit' then
|
||||
Event.exitPullEvents()
|
||||
end
|
||||
UI.Page.eventHandler(self, event)
|
||||
end
|
||||
|
||||
function page.menuBar:getActive(menuItem)
|
||||
local t = page.grid:getSelected()
|
||||
if menuItem.event == 'untrust' then
|
||||
local trustList = Util.readTable('usr/.known_hosts') or { }
|
||||
return t and trustList[t.id]
|
||||
end
|
||||
return not not t
|
||||
local t = page.grid:getSelected()
|
||||
if menuItem.event == 'untrust' then
|
||||
local trustList = Util.readTable('usr/.known_hosts') or { }
|
||||
return t and trustList[t.id]
|
||||
end
|
||||
return not not t
|
||||
end
|
||||
|
||||
function page.grid:getRowTextColor(row, selected)
|
||||
if not row.active then
|
||||
return colors.orange
|
||||
end
|
||||
return UI.Grid.getRowTextColor(self, 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
|
||||
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.onInterval(1, function()
|
||||
page.grid:update()
|
||||
page.grid:draw()
|
||||
page:sync()
|
||||
page.grid:update()
|
||||
page.grid:draw()
|
||||
page:sync()
|
||||
end)
|
||||
|
||||
Event.on('device_attach', function(_, deviceName)
|
||||
if deviceName == 'wireless_modem' then
|
||||
page.notification:success('Modem connected')
|
||||
page:sync()
|
||||
end
|
||||
if deviceName == 'wireless_modem' then
|
||||
page.notification:success('Modem connected')
|
||||
page:sync()
|
||||
end
|
||||
end)
|
||||
|
||||
Event.on('device_detach', function(_, deviceName)
|
||||
if deviceName == 'wireless_modem' then
|
||||
page.notification:error('Wireless modem not attached')
|
||||
page:sync()
|
||||
end
|
||||
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')
|
||||
page.notification:error('Wireless modem not attached')
|
||||
end
|
||||
|
||||
UI:setPage(page)
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
_G.requireInjector()
|
||||
_G.requireInjector(_ENV)
|
||||
|
||||
local class = require('class')
|
||||
local Config = require('config')
|
||||
@@ -18,7 +18,7 @@ local term = _G.term
|
||||
local turtle = _G.turtle
|
||||
|
||||
if not _ENV.multishell then
|
||||
error('multishell is required')
|
||||
error('multishell is required')
|
||||
end
|
||||
|
||||
local REGISTRY_DIR = 'usr/.registry'
|
||||
@@ -26,8 +26,8 @@ local REGISTRY_DIR = 'usr/.registry'
|
||||
UI:configure('Overview', ...)
|
||||
|
||||
local config = {
|
||||
Recent = { },
|
||||
currentCategory = 'Apps',
|
||||
Recent = { },
|
||||
currentCategory = 'Apps',
|
||||
}
|
||||
Config.load('Overview', config)
|
||||
|
||||
@@ -35,51 +35,51 @@ local applications = { }
|
||||
|
||||
local function loadApplications()
|
||||
|
||||
local requirements = {
|
||||
turtle = function() return turtle end,
|
||||
advancedTurtle = function() return turtle and term.isColor() end,
|
||||
advanced = function() return term.isColor() end,
|
||||
pocket = function() return pocket end,
|
||||
advancedPocket = function() return pocket and term.isColor() end,
|
||||
advancedComputer = function() return not turtle and not pocket and term.isColor() end,
|
||||
}
|
||||
local requirements = {
|
||||
turtle = function() return turtle end,
|
||||
advancedTurtle = function() return turtle and term.isColor() end,
|
||||
advanced = function() return term.isColor() end,
|
||||
pocket = function() return pocket end,
|
||||
advancedPocket = function() return pocket and term.isColor() end,
|
||||
advancedComputer = function() return not turtle and not pocket and term.isColor() end,
|
||||
}
|
||||
|
||||
applications = Util.readTable('sys/etc/app.db')
|
||||
applications = Util.readTable('sys/etc/app.db')
|
||||
|
||||
if fs.exists('usr/etc/apps') then
|
||||
local dbs = fs.list('usr/etc/apps')
|
||||
for _, db in pairs(dbs) do
|
||||
local apps = Util.readTable('usr/etc/apps/' .. db) or { }
|
||||
Util.merge(applications, apps)
|
||||
end
|
||||
end
|
||||
if fs.exists('usr/etc/apps') then
|
||||
local dbs = fs.list('usr/etc/apps')
|
||||
for _, db in pairs(dbs) do
|
||||
local apps = Util.readTable('usr/etc/apps/' .. db) or { }
|
||||
Util.merge(applications, apps)
|
||||
end
|
||||
end
|
||||
|
||||
if fs.exists(REGISTRY_DIR) then
|
||||
local files = fs.list(REGISTRY_DIR)
|
||||
for _,file in pairs(files) do
|
||||
local app = Util.readTable(fs.combine(REGISTRY_DIR, file))
|
||||
if app and app.key then
|
||||
app.filename = fs.combine(REGISTRY_DIR, file)
|
||||
applications[app.key] = app
|
||||
end
|
||||
end
|
||||
end
|
||||
if fs.exists(REGISTRY_DIR) then
|
||||
local files = fs.list(REGISTRY_DIR)
|
||||
for _,file in pairs(files) do
|
||||
local app = Util.readTable(fs.combine(REGISTRY_DIR, file))
|
||||
if app and app.key then
|
||||
app.filename = fs.combine(REGISTRY_DIR, file)
|
||||
applications[app.key] = app
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
Util.each(applications, function(v, k) v.key = k end)
|
||||
applications = Util.filter(applications, function(a)
|
||||
if a.disabled then
|
||||
return false
|
||||
end
|
||||
Util.each(applications, function(v, k) v.key = k end)
|
||||
applications = Util.filter(applications, function(a)
|
||||
if a.disabled then
|
||||
return false
|
||||
end
|
||||
|
||||
if a.requires then
|
||||
local fn = requirements[a.requires]
|
||||
if fn and not fn() then
|
||||
return false
|
||||
end
|
||||
end
|
||||
if a.requires then
|
||||
local fn = requirements[a.requires]
|
||||
if fn and not fn() then
|
||||
return false
|
||||
end
|
||||
end
|
||||
|
||||
return true -- Util.startsWidth(a.run, 'http') or shell.resolveProgram(a.run)
|
||||
end)
|
||||
return true -- Util.startsWidth(a.run, 'http') or shell.resolveProgram(a.run)
|
||||
end)
|
||||
end
|
||||
|
||||
loadApplications()
|
||||
@@ -92,457 +92,457 @@ 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
|
||||
if #s > len then
|
||||
s = s:sub(1, len - 2) .. '..'
|
||||
end
|
||||
return s
|
||||
end
|
||||
|
||||
local buttons = { }
|
||||
local categories = { }
|
||||
for _,f in pairs(applications) do
|
||||
if not categories[f.category] then
|
||||
categories[f.category] = true
|
||||
table.insert(buttons, { text = f.category })
|
||||
end
|
||||
if not categories[f.category] then
|
||||
categories[f.category] = true
|
||||
table.insert(buttons, { text = f.category })
|
||||
end
|
||||
end
|
||||
table.sort(buttons, function(a, b) return a.text < b.text end)
|
||||
table.insert(buttons, 1, { text = 'Recent' })
|
||||
table.insert(buttons, { text = '+', event = 'new' })
|
||||
|
||||
local function parseIcon(iconText)
|
||||
local icon
|
||||
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)
|
||||
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
|
||||
if s then
|
||||
return icon
|
||||
end
|
||||
|
||||
return s, m
|
||||
return s, m
|
||||
end
|
||||
|
||||
UI.VerticalTabBar = class(UI.TabBar)
|
||||
function UI.VerticalTabBar:setParent()
|
||||
self.x = 1
|
||||
self.width = 8
|
||||
self.height = nil
|
||||
self.ey = -1
|
||||
UI.TabBar.setParent(self)
|
||||
for k,c in pairs(self.children) do
|
||||
c.x = 1
|
||||
c.y = k + 1
|
||||
c.ox, c.oy = c.x, c.y
|
||||
c.ow = 8
|
||||
c.width = 8
|
||||
end
|
||||
self.x = 1
|
||||
self.width = 8
|
||||
self.height = nil
|
||||
self.ey = -1
|
||||
UI.TabBar.setParent(self)
|
||||
for k,c in pairs(self.children) do
|
||||
c.x = 1
|
||||
c.y = k + 1
|
||||
c.ox, c.oy = c.x, c.y
|
||||
c.ow = 8
|
||||
c.width = 8
|
||||
end
|
||||
end
|
||||
|
||||
local cx = 9
|
||||
local cy = 1
|
||||
if sx < 30 then
|
||||
UI.VerticalTabBar = UI.TabBar
|
||||
cx = 1
|
||||
cy = 2
|
||||
UI.VerticalTabBar = UI.TabBar
|
||||
cx = 1
|
||||
cy = 2
|
||||
end
|
||||
|
||||
local page = UI.Page {
|
||||
tabBar = UI.VerticalTabBar {
|
||||
buttons = buttons,
|
||||
},
|
||||
container = UI.Viewport {
|
||||
x = cx,
|
||||
y = cy,
|
||||
},
|
||||
notification = UI.Notification(),
|
||||
accelerators = {
|
||||
r = 'refresh',
|
||||
e = 'edit',
|
||||
f = 'files',
|
||||
s = 'shell',
|
||||
l = 'lua',
|
||||
[ 'control-n' ] = 'new',
|
||||
delete = 'delete',
|
||||
},
|
||||
tabBar = UI.VerticalTabBar {
|
||||
buttons = buttons,
|
||||
},
|
||||
container = UI.Viewport {
|
||||
x = cx,
|
||||
y = cy,
|
||||
},
|
||||
notification = UI.Notification(),
|
||||
accelerators = {
|
||||
r = 'refresh',
|
||||
e = 'edit',
|
||||
f = 'files',
|
||||
s = 'shell',
|
||||
l = 'lua',
|
||||
[ 'control-n' ] = 'new',
|
||||
delete = 'delete',
|
||||
},
|
||||
}
|
||||
|
||||
UI.Icon = class(UI.Window)
|
||||
UI.Icon.defaults = {
|
||||
UIElement = 'Icon',
|
||||
width = 14,
|
||||
height = 4,
|
||||
UIElement = 'Icon',
|
||||
width = 14,
|
||||
height = 4,
|
||||
}
|
||||
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)
|
||||
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, animate)
|
||||
|
||||
-- reset the viewport window
|
||||
self.children = { }
|
||||
self.offy = 0
|
||||
-- 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 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
|
||||
local filtered
|
||||
|
||||
if categoryName == 'Recent' then
|
||||
filtered = { }
|
||||
if categoryName == 'Recent' then
|
||||
filtered = { }
|
||||
|
||||
for _,v in ipairs(config.Recent) do
|
||||
local app = Util.find(applications, 'key', v)
|
||||
if app then -- and fs.exists(app.run) then
|
||||
table.insert(filtered, app)
|
||||
end
|
||||
end
|
||||
for _,v in ipairs(config.Recent) do
|
||||
local app = Util.find(applications, 'key', v)
|
||||
if app then -- 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
|
||||
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
|
||||
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 icon
|
||||
if program.icon then
|
||||
icon = parseIcon(program.icon)
|
||||
end
|
||||
if not icon then
|
||||
icon = defaultIcon
|
||||
end
|
||||
|
||||
local title = elipse(program.title, 8)
|
||||
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,
|
||||
backgroundFocusColor = colors.gray,
|
||||
textColor = colors.white,
|
||||
textFocusColor = colors.white,
|
||||
width = #title + 2,
|
||||
event = 'button',
|
||||
app = program,
|
||||
}),
|
||||
}))
|
||||
end
|
||||
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,
|
||||
backgroundFocusColor = colors.gray,
|
||||
textColor = colors.white,
|
||||
textFocusColor = colors.white,
|
||||
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
|
||||
local gutter = 2
|
||||
if UI.term.width <= 26 then
|
||||
gutter = 1
|
||||
end
|
||||
local col, row = gutter, 2
|
||||
local count = #self.children
|
||||
|
||||
local r = math.random(1, 5)
|
||||
-- reposition all children
|
||||
for k,child in ipairs(self.children) do
|
||||
if r == 1 then
|
||||
child.x = math.random(1, self.width)
|
||||
child.y = math.random(1, self.height)
|
||||
elseif r == 2 then
|
||||
child.x = self.width
|
||||
child.y = self.height
|
||||
elseif r == 3 then
|
||||
child.x = math.floor(self.width / 2)
|
||||
child.y = math.floor(self.height / 2)
|
||||
elseif r == 4 then
|
||||
child.x = self.width - col
|
||||
child.y = row
|
||||
elseif r == 5 then
|
||||
child.x = col
|
||||
child.y = row
|
||||
if k == #self.children then
|
||||
child.x = self.width
|
||||
child.y = self.height
|
||||
end
|
||||
end
|
||||
child.tween = Tween.new(6, child, { x = col, y = row }, 'linear')
|
||||
local r = math.random(1, 5)
|
||||
-- reposition all children
|
||||
for k,child in ipairs(self.children) do
|
||||
if r == 1 then
|
||||
child.x = math.random(1, self.width)
|
||||
child.y = math.random(1, self.height)
|
||||
elseif r == 2 then
|
||||
child.x = self.width
|
||||
child.y = self.height
|
||||
elseif r == 3 then
|
||||
child.x = math.floor(self.width / 2)
|
||||
child.y = math.floor(self.height / 2)
|
||||
elseif r == 4 then
|
||||
child.x = self.width - col
|
||||
child.y = row
|
||||
elseif r == 5 then
|
||||
child.x = col
|
||||
child.y = row
|
||||
if k == #self.children then
|
||||
child.x = self.width
|
||||
child.y = self.height
|
||||
end
|
||||
end
|
||||
child.tween = Tween.new(6, child, { x = col, y = row }, 'linear')
|
||||
|
||||
if not animate then
|
||||
child.x = col
|
||||
child.y = row
|
||||
end
|
||||
if not animate then
|
||||
child.x = col
|
||||
child.y = row
|
||||
end
|
||||
|
||||
if k < count then
|
||||
col = col + child.width
|
||||
if col + self.children[k + 1].width + gutter - 2 > self.width then
|
||||
col = gutter
|
||||
row = row + 5
|
||||
end
|
||||
end
|
||||
end
|
||||
if k < count then
|
||||
col = col + child.width
|
||||
if col + self.children[k + 1].width + gutter - 2 > self.width then
|
||||
col = gutter
|
||||
row = row + 5
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
self:initChildren()
|
||||
if animate then -- need to fix transitions under layers
|
||||
local function transition(args)
|
||||
local i = 1
|
||||
return function(device)
|
||||
self:clear()
|
||||
for _,child in pairs(self.children) do
|
||||
child.tween:update(1)
|
||||
child.x = math.floor(child.x)
|
||||
child.y = math.floor(child.y)
|
||||
child:draw()
|
||||
end
|
||||
args.canvas:blit(device, args, args)
|
||||
i = i + 1
|
||||
return i < 7
|
||||
end
|
||||
end
|
||||
self:addTransition(transition)
|
||||
end
|
||||
self:initChildren()
|
||||
if animate then -- need to fix transitions under layers
|
||||
local function transition(args)
|
||||
local i = 1
|
||||
return function(device)
|
||||
self:clear()
|
||||
for _,child in pairs(self.children) do
|
||||
child.tween:update(1)
|
||||
child.x = math.floor(child.x)
|
||||
child.y = math.floor(child.y)
|
||||
child:draw()
|
||||
end
|
||||
args.canvas:blit(device, args, args)
|
||||
i = i + 1
|
||||
return i < 7
|
||||
end
|
||||
end
|
||||
self:addTransition(transition)
|
||||
end
|
||||
end
|
||||
|
||||
function page:refresh()
|
||||
local pos = self.container.offy
|
||||
self:focusFirst(self)
|
||||
self.container:setCategory(config.currentCategory)
|
||||
self.container:setScrollPosition(pos)
|
||||
local pos = self.container.offy
|
||||
self:focusFirst(self)
|
||||
self.container:setCategory(config.currentCategory)
|
||||
self.container:setScrollPosition(pos)
|
||||
end
|
||||
|
||||
function page:resize()
|
||||
UI.Page.resize(self)
|
||||
self:refresh()
|
||||
UI.Page.resize(self)
|
||||
self:refresh()
|
||||
end
|
||||
|
||||
function page:eventHandler(event)
|
||||
|
||||
if event.type == 'tab_select' then
|
||||
self.container:setCategory(event.button.text, true)
|
||||
self.container:draw()
|
||||
if event.type == 'tab_select' then
|
||||
self.container:setCategory(event.button.text, true)
|
||||
self.container:draw()
|
||||
|
||||
config.currentCategory = event.button.text
|
||||
Config.update('Overview', config)
|
||||
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.key then
|
||||
table.remove(config.Recent, k)
|
||||
break
|
||||
end
|
||||
end
|
||||
table.insert(config.Recent, 1, event.button.app.key)
|
||||
if #config.Recent > maxRecent then
|
||||
table.remove(config.Recent, maxRecent + 1)
|
||||
end
|
||||
Config.update('Overview', config)
|
||||
shell.switchTab(shell.openTab(event.button.app.run))
|
||||
elseif event.type == 'button' then
|
||||
for k,v in ipairs(config.Recent) do
|
||||
if v == event.button.app.key then
|
||||
table.remove(config.Recent, k)
|
||||
break
|
||||
end
|
||||
end
|
||||
table.insert(config.Recent, 1, event.button.app.key)
|
||||
if #config.Recent > maxRecent then
|
||||
table.remove(config.Recent, maxRecent + 1)
|
||||
end
|
||||
Config.update('Overview', config)
|
||||
shell.switchTab(shell.openTab(event.button.app.run))
|
||||
|
||||
elseif event.type == 'shell' then
|
||||
shell.switchTab(shell.openTab('sys/apps/shell'))
|
||||
elseif event.type == 'shell' then
|
||||
shell.switchTab(shell.openTab('sys/apps/shell'))
|
||||
|
||||
elseif event.type == 'lua' then
|
||||
shell.switchTab(shell.openTab('sys/apps/Lua.lua'))
|
||||
elseif event.type == 'lua' then
|
||||
shell.switchTab(shell.openTab('sys/apps/Lua.lua'))
|
||||
|
||||
elseif event.type == 'files' then
|
||||
shell.switchTab(shell.openTab('sys/apps/Files.lua'))
|
||||
elseif event.type == 'files' then
|
||||
shell.switchTab(shell.openTab('sys/apps/Files.lua'))
|
||||
|
||||
elseif event.type == 'focus_change' then
|
||||
if event.focused.parent.UIElement == 'Icon' then
|
||||
event.focused.parent:scrollIntoView()
|
||||
end
|
||||
elseif event.type == 'focus_change' then
|
||||
if event.focused.parent.UIElement == 'Icon' then
|
||||
event.focused.parent:scrollIntoView()
|
||||
end
|
||||
|
||||
elseif event.type == 'refresh' then -- remove this after fixing notification
|
||||
loadApplications()
|
||||
self:refresh()
|
||||
self:draw()
|
||||
self.notification:success('Refreshed')
|
||||
elseif event.type == 'refresh' then -- remove this after fixing notification
|
||||
loadApplications()
|
||||
self:refresh()
|
||||
self:draw()
|
||||
self.notification:success('Refreshed')
|
||||
|
||||
elseif event.type == 'delete' then
|
||||
local focused = page:getFocused()
|
||||
if focused.app then
|
||||
focused.app.disabled = true
|
||||
local filename = focused.app.filename or fs.combine(REGISTRY_DIR, focused.app.key)
|
||||
Util.writeTable(filename, focused.app)
|
||||
loadApplications()
|
||||
page:refresh()
|
||||
page:draw()
|
||||
self.notification:success('Removed')
|
||||
end
|
||||
elseif event.type == 'delete' then
|
||||
local focused = page:getFocused()
|
||||
if focused.app then
|
||||
focused.app.disabled = true
|
||||
local filename = focused.app.filename or fs.combine(REGISTRY_DIR, focused.app.key)
|
||||
Util.writeTable(filename, focused.app)
|
||||
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 == '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
|
||||
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
|
||||
else
|
||||
UI.Page.eventHandler(self, event)
|
||||
end
|
||||
return true
|
||||
end
|
||||
|
||||
local formWidth = math.max(UI.term.width - 8, 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 = '',
|
||||
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)
|
||||
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.Dialog.enable(self)
|
||||
self:focusFirst()
|
||||
local icon
|
||||
if app.icon then
|
||||
icon = parseIcon(app.icon)
|
||||
end
|
||||
self.form.image:setImage(icon)
|
||||
end
|
||||
UI.Dialog.enable(self)
|
||||
self:focusFirst()
|
||||
end
|
||||
|
||||
function editor.form.image:draw()
|
||||
self:clear()
|
||||
UI.NftImage.draw(self)
|
||||
self:clear()
|
||||
UI.NftImage.draw(self)
|
||||
end
|
||||
|
||||
function editor:updateApplications(app)
|
||||
if not app.key then
|
||||
app.key = SHA1.sha1(app.title)
|
||||
end
|
||||
local filename = app.filename or fs.combine(REGISTRY_DIR, app.key)
|
||||
Util.writeTable(filename, app)
|
||||
loadApplications()
|
||||
if not app.key then
|
||||
app.key = SHA1.sha1(app.title)
|
||||
end
|
||||
local filename = app.filename or fs.combine(REGISTRY_DIR, app.key)
|
||||
Util.writeTable(filename, app)
|
||||
loadApplications()
|
||||
end
|
||||
|
||||
function editor:eventHandler(event)
|
||||
|
||||
if event.type == 'form_cancel' or event.type == 'cancel' then
|
||||
UI:setPreviousPage()
|
||||
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 == '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,
|
||||
})
|
||||
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 == 'loadIcon' then
|
||||
local fileui = FileUI({
|
||||
x = self.x,
|
||||
y = self.y,
|
||||
z = 2,
|
||||
width = self.width,
|
||||
height = self.height,
|
||||
})
|
||||
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_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.Dialog.eventHandler(self, event)
|
||||
end
|
||||
return true
|
||||
elseif event.type == 'form_complete' then
|
||||
local values = self.form.values
|
||||
UI:setPreviousPage()
|
||||
self:updateApplications(values)
|
||||
page:refresh()
|
||||
page:draw()
|
||||
else
|
||||
return UI.Dialog.eventHandler(self, event)
|
||||
end
|
||||
return true
|
||||
end
|
||||
|
||||
UI:setPages({
|
||||
editor = editor,
|
||||
main = page,
|
||||
editor = editor,
|
||||
main = page,
|
||||
})
|
||||
|
||||
Event.on('os_register_app', function()
|
||||
loadApplications()
|
||||
page:refresh()
|
||||
page:draw()
|
||||
page:sync()
|
||||
loadApplications()
|
||||
page:refresh()
|
||||
page:draw()
|
||||
page:sync()
|
||||
end)
|
||||
|
||||
page.tabBar:selectTab(config.currentCategory or 'Apps')
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
_G.requireInjector()
|
||||
_G.requireInjector(_ENV)
|
||||
|
||||
local Config = require('config')
|
||||
local Security = require('security')
|
||||
@@ -15,307 +15,313 @@ local turtle = _G.turtle
|
||||
UI:configure('System', ...)
|
||||
|
||||
local env = {
|
||||
path = shell.path(),
|
||||
aliases = shell.aliases(),
|
||||
lua_path = _ENV.LUA_PATH,
|
||||
path = shell.path(),
|
||||
aliases = shell.aliases(),
|
||||
lua_path = _ENV.LUA_PATH,
|
||||
}
|
||||
Config.load('shell', env)
|
||||
|
||||
local systemPage = UI.Page {
|
||||
tabs = UI.Tabs {
|
||||
pathTab = UI.Window {
|
||||
tabTitle = 'Path',
|
||||
entry = UI.TextEntry {
|
||||
x = 2, y = 2, ex = -2,
|
||||
limit = 256,
|
||||
value = shell.path(),
|
||||
shadowText = 'enter system path',
|
||||
accelerators = {
|
||||
enter = 'update_path',
|
||||
},
|
||||
},
|
||||
grid = UI.Grid {
|
||||
y = 4,
|
||||
disableHeader = true,
|
||||
columns = { { key = 'value' } },
|
||||
autospace = true,
|
||||
},
|
||||
},
|
||||
tabs = UI.Tabs {
|
||||
pathTab = UI.Window {
|
||||
tabTitle = 'Path',
|
||||
entry = UI.TextEntry {
|
||||
x = 2, y = 2, ex = -2,
|
||||
limit = 256,
|
||||
value = shell.path(),
|
||||
shadowText = 'enter system path',
|
||||
accelerators = {
|
||||
enter = 'update_path',
|
||||
},
|
||||
},
|
||||
grid = UI.Grid {
|
||||
y = 4,
|
||||
disableHeader = true,
|
||||
columns = { { key = 'value' } },
|
||||
autospace = true,
|
||||
},
|
||||
},
|
||||
|
||||
aliasTab = UI.Window {
|
||||
tabTitle = 'Alias',
|
||||
alias = UI.TextEntry {
|
||||
x = 2, y = 2, ex = -2,
|
||||
limit = 32,
|
||||
shadowText = 'Alias',
|
||||
},
|
||||
path = UI.TextEntry {
|
||||
y = 3, x = 2, ex = -2,
|
||||
limit = 256,
|
||||
shadowText = 'Program path',
|
||||
accelerators = {
|
||||
enter = 'new_alias',
|
||||
},
|
||||
},
|
||||
grid = UI.Grid {
|
||||
y = 5,
|
||||
sortColumn = 'alias',
|
||||
columns = {
|
||||
{ heading = 'Alias', key = 'alias' },
|
||||
{ heading = 'Program', key = 'path' },
|
||||
},
|
||||
accelerators = {
|
||||
delete = 'delete_alias',
|
||||
},
|
||||
},
|
||||
},
|
||||
aliasTab = UI.Window {
|
||||
tabTitle = 'Alias',
|
||||
alias = UI.TextEntry {
|
||||
x = 2, y = 2, ex = -2,
|
||||
limit = 32,
|
||||
shadowText = 'Alias',
|
||||
},
|
||||
path = UI.TextEntry {
|
||||
y = 3, x = 2, ex = -2,
|
||||
limit = 256,
|
||||
shadowText = 'Program path',
|
||||
accelerators = {
|
||||
enter = 'new_alias',
|
||||
},
|
||||
},
|
||||
grid = UI.Grid {
|
||||
y = 5,
|
||||
sortColumn = 'alias',
|
||||
columns = {
|
||||
{ heading = 'Alias', key = 'alias' },
|
||||
{ heading = 'Program', key = 'path' },
|
||||
},
|
||||
accelerators = {
|
||||
delete = 'delete_alias',
|
||||
},
|
||||
},
|
||||
},
|
||||
|
||||
passwordTab = UI.Window {
|
||||
tabTitle = 'Password',
|
||||
oldPass = UI.TextEntry {
|
||||
x = 2, y = 2, ex = -2,
|
||||
limit = 32,
|
||||
mask = true,
|
||||
shadowText = 'old password',
|
||||
inactive = not Security.getPassword(),
|
||||
},
|
||||
newPass = UI.TextEntry {
|
||||
y = 3, x = 2, ex = -2,
|
||||
limit = 32,
|
||||
mask = true,
|
||||
shadowText = 'new password',
|
||||
accelerators = {
|
||||
enter = 'new_password',
|
||||
},
|
||||
},
|
||||
button = UI.Button {
|
||||
x = 2, y = 5,
|
||||
text = 'Update',
|
||||
event = 'update_password',
|
||||
},
|
||||
info = UI.TextArea {
|
||||
x = 2, ex = -2,
|
||||
y = 7,
|
||||
inactive = true,
|
||||
value = 'Add a password to enable other computers to connect to this one.',
|
||||
}
|
||||
},
|
||||
passwordTab = UI.Window {
|
||||
tabTitle = 'Password',
|
||||
oldPass = UI.TextEntry {
|
||||
x = 2, y = 2, ex = -2,
|
||||
limit = 32,
|
||||
mask = true,
|
||||
shadowText = 'old password',
|
||||
inactive = not Security.getPassword(),
|
||||
},
|
||||
newPass = UI.TextEntry {
|
||||
y = 3, x = 2, ex = -2,
|
||||
limit = 32,
|
||||
mask = true,
|
||||
shadowText = 'new password',
|
||||
accelerators = {
|
||||
enter = 'new_password',
|
||||
},
|
||||
},
|
||||
button = UI.Button {
|
||||
x = 2, y = 5,
|
||||
text = 'Update',
|
||||
event = 'update_password',
|
||||
},
|
||||
info = UI.TextArea {
|
||||
x = 2, ex = -2,
|
||||
y = 7,
|
||||
inactive = true,
|
||||
value = 'Add a password to enable other computers to connect to this one.',
|
||||
}
|
||||
},
|
||||
|
||||
infoTab = UI.Window {
|
||||
tabTitle = 'Info',
|
||||
labelText = UI.Text {
|
||||
x = 3, y = 2,
|
||||
value = 'Label'
|
||||
},
|
||||
label = UI.TextEntry {
|
||||
x = 9, y = 2, ex = -4,
|
||||
limit = 32,
|
||||
value = os.getComputerLabel(),
|
||||
accelerators = {
|
||||
enter = 'update_label',
|
||||
},
|
||||
},
|
||||
grid = UI.ScrollingGrid {
|
||||
y = 3,
|
||||
values = {
|
||||
{ name = '', value = '' },
|
||||
{ name = 'CC version', value = Util.getVersion() },
|
||||
{ name = 'Lua version', value = _VERSION },
|
||||
{ name = 'MC version', value = Util.getMinecraftVersion() },
|
||||
{ name = 'Disk free', value = Util.toBytes(fs.getFreeSpace('/')) },
|
||||
{ name = 'Computer ID', value = tostring(os.getComputerID()) },
|
||||
{ name = 'Day', value = tostring(os.day()) },
|
||||
},
|
||||
inactive = true,
|
||||
columns = {
|
||||
{ key = 'name', width = 12 },
|
||||
{ key = 'value' },
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
notification = UI.Notification(),
|
||||
accelerators = {
|
||||
q = 'quit',
|
||||
},
|
||||
infoTab = UI.Window {
|
||||
tabTitle = 'Info',
|
||||
labelText = UI.Text {
|
||||
x = 3, y = 2,
|
||||
value = 'Label'
|
||||
},
|
||||
label = UI.TextEntry {
|
||||
x = 9, y = 2, ex = -4,
|
||||
limit = 32,
|
||||
value = os.getComputerLabel(),
|
||||
accelerators = {
|
||||
enter = 'update_label',
|
||||
},
|
||||
},
|
||||
grid = UI.ScrollingGrid {
|
||||
y = 3,
|
||||
values = {
|
||||
{ name = '', value = '' },
|
||||
{ name = 'CC version', value = Util.getVersion() },
|
||||
{ name = 'Lua version', value = _VERSION },
|
||||
{ name = 'MC version', value = Util.getMinecraftVersion() },
|
||||
{ name = 'Disk free', value = Util.toBytes(fs.getFreeSpace('/')) },
|
||||
{ name = 'Computer ID', value = tostring(os.getComputerID()) },
|
||||
{ name = 'Day', value = tostring(os.day()) },
|
||||
},
|
||||
inactive = true,
|
||||
columns = {
|
||||
{ key = 'name', width = 12 },
|
||||
{ key = 'value' },
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
notification = UI.Notification(),
|
||||
accelerators = {
|
||||
q = 'quit',
|
||||
},
|
||||
}
|
||||
|
||||
if turtle then
|
||||
local Home = require('turtle.home')
|
||||
local Home = require('turtle.home')
|
||||
|
||||
local values = { }
|
||||
Config.load('gps', values.home or { })
|
||||
local values = { }
|
||||
Config.load('gps', values.home or { })
|
||||
|
||||
systemPage.tabs:add({
|
||||
gpsTab = UI.Window {
|
||||
tabTitle = 'GPS',
|
||||
labelText = UI.Text {
|
||||
x = 3, y = 2,
|
||||
value = 'On restart, return to this location'
|
||||
},
|
||||
grid = UI.Grid {
|
||||
x = 3, ex = -3, y = 4,
|
||||
height = 2,
|
||||
values = values,
|
||||
inactive = true,
|
||||
columns = {
|
||||
{ heading = 'x', key = 'x' },
|
||||
{ heading = 'y', key = 'y' },
|
||||
{ heading = 'z', key = 'z' },
|
||||
},
|
||||
},
|
||||
button1 = UI.Button {
|
||||
x = 3, y = 7,
|
||||
text = 'Set home',
|
||||
event = 'gps_set',
|
||||
},
|
||||
button2 = UI.Button {
|
||||
ex = -3, y = 7, width = 7,
|
||||
text = 'Clear',
|
||||
event = 'gps_clear',
|
||||
},
|
||||
},
|
||||
})
|
||||
function systemPage.tabs.gpsTab:eventHandler(event)
|
||||
if event.type == 'gps_set' then
|
||||
systemPage.notification:info('Determining location', 10)
|
||||
systemPage:sync()
|
||||
if Home.set() then
|
||||
Config.load('gps', values)
|
||||
self.grid:setValues(values.home or { })
|
||||
self.grid:draw()
|
||||
systemPage.notification:success('Location set')
|
||||
else
|
||||
systemPage.notification:error('Unable to determine location')
|
||||
end
|
||||
return true
|
||||
elseif event.type == 'gps_clear' then
|
||||
fs.delete('usr/config/gps')
|
||||
self.grid:setValues({ })
|
||||
self.grid:draw()
|
||||
return true
|
||||
end
|
||||
end
|
||||
systemPage.tabs:add({
|
||||
gpsTab = UI.Window {
|
||||
tabTitle = 'GPS',
|
||||
labelText = UI.Text {
|
||||
x = 3, y = 2,
|
||||
value = 'On restart, return to this location'
|
||||
},
|
||||
grid = UI.Grid {
|
||||
x = 3, ex = -3, y = 4,
|
||||
height = 2,
|
||||
values = values,
|
||||
inactive = true,
|
||||
columns = {
|
||||
{ heading = 'x', key = 'x' },
|
||||
{ heading = 'y', key = 'y' },
|
||||
{ heading = 'z', key = 'z' },
|
||||
},
|
||||
},
|
||||
button1 = UI.Button {
|
||||
x = 3, y = 7,
|
||||
text = 'Set home',
|
||||
event = 'gps_set',
|
||||
},
|
||||
button2 = UI.Button {
|
||||
ex = -3, y = 7, width = 7,
|
||||
text = 'Clear',
|
||||
event = 'gps_clear',
|
||||
},
|
||||
},
|
||||
})
|
||||
function systemPage.tabs.gpsTab:eventHandler(event)
|
||||
if event.type == 'gps_set' then
|
||||
systemPage.notification:info('Determining location', 10)
|
||||
systemPage:sync()
|
||||
if Home.set() then
|
||||
Config.load('gps', values)
|
||||
self.grid:setValues(values.home or { })
|
||||
self.grid:draw()
|
||||
systemPage.notification:success('Location set')
|
||||
else
|
||||
systemPage.notification:error('Unable to determine location')
|
||||
end
|
||||
return true
|
||||
elseif event.type == 'gps_clear' then
|
||||
fs.delete('usr/config/gps')
|
||||
self.grid:setValues({ })
|
||||
self.grid:draw()
|
||||
return true
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
if settings then
|
||||
local values = { }
|
||||
for _,v in pairs(settings.getNames()) do
|
||||
table.insert(values, {
|
||||
name = v,
|
||||
value = not not settings.get(v),
|
||||
})
|
||||
end
|
||||
local values = { }
|
||||
for _,v in pairs(settings.getNames()) do
|
||||
local value = settings.get(v)
|
||||
if not value then
|
||||
value = false
|
||||
end
|
||||
table.insert(values, {
|
||||
name = v,
|
||||
value = value,
|
||||
})
|
||||
end
|
||||
|
||||
systemPage.tabs:add({
|
||||
settingsTab = UI.Window {
|
||||
tabTitle = 'Settings',
|
||||
grid = UI.Grid {
|
||||
y = 1,
|
||||
values = values,
|
||||
autospace = true,
|
||||
sortColumn = 'name',
|
||||
columns = {
|
||||
{ heading = 'Setting', key = 'name' },
|
||||
{ heading = 'Value', key = 'value' },
|
||||
},
|
||||
},
|
||||
}
|
||||
})
|
||||
function systemPage.tabs.settingsTab:eventHandler(event)
|
||||
if event.type == 'grid_select' then
|
||||
event.selected.value = not event.selected.value
|
||||
settings.set(event.selected.name, event.selected.value)
|
||||
settings.save('.settings')
|
||||
self.grid:draw()
|
||||
return true
|
||||
end
|
||||
end
|
||||
systemPage.tabs:add({
|
||||
settingsTab = UI.Window {
|
||||
tabTitle = 'Settings',
|
||||
grid = UI.Grid {
|
||||
y = 1,
|
||||
values = values,
|
||||
autospace = true,
|
||||
sortColumn = 'name',
|
||||
columns = {
|
||||
{ heading = 'Setting', key = 'name' },
|
||||
{ heading = 'Value', key = 'value' },
|
||||
},
|
||||
},
|
||||
}
|
||||
})
|
||||
function systemPage.tabs.settingsTab:eventHandler(event)
|
||||
if event.type == 'grid_select' then
|
||||
if not event.selected.value or type(event.selected.value) == 'boolean' then
|
||||
event.selected.value = not event.selected.value
|
||||
end
|
||||
settings.set(event.selected.name, event.selected.value)
|
||||
settings.save('.settings')
|
||||
self.grid:draw()
|
||||
return true
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
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)
|
||||
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('shell', env)
|
||||
systemPage.notification:success('reboot to take effect')
|
||||
return true
|
||||
end
|
||||
if event.type == 'update_path' then
|
||||
env.path = self.entry.value
|
||||
self.grid:setIndex(self.grid:getIndex())
|
||||
self.grid:draw()
|
||||
Config.update('shell', env)
|
||||
systemPage.notification:success('reboot to take effect')
|
||||
return true
|
||||
end
|
||||
end
|
||||
|
||||
function systemPage.tabs.aliasTab.grid:draw()
|
||||
self.values = { }
|
||||
for k,v in pairs(env.aliases) do
|
||||
table.insert(self.values, { alias = k, path = v })
|
||||
end
|
||||
self:update()
|
||||
UI.Grid.draw(self)
|
||||
self.values = { }
|
||||
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('shell', env)
|
||||
systemPage.notification:success('reboot to take effect')
|
||||
return true
|
||||
if event.type == 'delete_alias' then
|
||||
env.aliases[self.grid:getSelected().alias] = nil
|
||||
self.grid:setIndex(self.grid:getIndex())
|
||||
self.grid:draw()
|
||||
Config.update('shell', 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('shell', env)
|
||||
systemPage.notification:success('reboot to take effect')
|
||||
return true
|
||||
end
|
||||
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('shell', env)
|
||||
systemPage.notification:success('reboot to take effect')
|
||||
return true
|
||||
end
|
||||
end
|
||||
|
||||
function systemPage.tabs.passwordTab:eventHandler(event)
|
||||
if event.type == 'update_password' then
|
||||
if #self.newPass.value == 0 then
|
||||
systemPage.notification:error('Invalid password')
|
||||
elseif Security.getPassword() and not Security.verifyPassword(SHA1.sha1(self.oldPass.value)) then
|
||||
systemPage.notification:error('Passwords do not match')
|
||||
else
|
||||
Security.updatePassword(SHA1.sha1(self.newPass.value))
|
||||
self.oldPass.inactive = false
|
||||
systemPage.notification:success('Password updated')
|
||||
end
|
||||
if event.type == 'update_password' then
|
||||
if #self.newPass.value == 0 then
|
||||
systemPage.notification:error('Invalid password')
|
||||
elseif Security.getPassword() and not Security.verifyPassword(SHA1.sha1(self.oldPass.value)) then
|
||||
systemPage.notification:error('Passwords do not match')
|
||||
else
|
||||
Security.updatePassword(SHA1.sha1(self.newPass.value))
|
||||
self.oldPass.inactive = false
|
||||
systemPage.notification:success('Password updated')
|
||||
end
|
||||
|
||||
return true
|
||||
end
|
||||
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
|
||||
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
|
||||
UI:exitPullEvents()
|
||||
elseif event.type == 'tab_activate' then
|
||||
event.activated:focusFirst()
|
||||
else
|
||||
return UI.Page.eventHandler(self, event)
|
||||
end
|
||||
return true
|
||||
if event.type == 'quit' then
|
||||
UI:exitPullEvents()
|
||||
elseif event.type == 'tab_activate' then
|
||||
event.activated:focusFirst()
|
||||
else
|
||||
return UI.Page.eventHandler(self, event)
|
||||
end
|
||||
return true
|
||||
end
|
||||
|
||||
UI:setPage(systemPage)
|
||||
|
||||
@@ -10,66 +10,62 @@ local multishell = _ENV.multishell
|
||||
UI:configure('Tasks', ...)
|
||||
|
||||
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 = 'uid', width = 3 },
|
||||
{ heading = 'Title', key = 'title' },
|
||||
{ heading = 'Status', key = 'status' },
|
||||
{ heading = 'Time', key = 'timestamp' },
|
||||
},
|
||||
values = kernel.routines,
|
||||
sortColumn = 'uid',
|
||||
autospace = true,
|
||||
},
|
||||
accelerators = {
|
||||
q = 'quit',
|
||||
space = 'activate',
|
||||
t = 'terminate',
|
||||
},
|
||||
menuBar = UI.MenuBar {
|
||||
buttons = {
|
||||
{ text = 'Activate', event = 'activate' },
|
||||
{ text = 'Terminate', event = 'terminate' },
|
||||
},
|
||||
},
|
||||
grid = UI.ScrollingGrid {
|
||||
y = 2,
|
||||
columns = {
|
||||
{ heading = 'ID', key = 'uid', width = 3 },
|
||||
{ heading = 'Title', key = 'title' },
|
||||
{ heading = 'Status', key = 'status' },
|
||||
{ heading = 'Time', key = 'timestamp' },
|
||||
},
|
||||
values = kernel.routines,
|
||||
sortColumn = 'uid',
|
||||
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.uid)
|
||||
elseif event.type == 'terminate' then
|
||||
multishell.terminate(t.uid)
|
||||
end
|
||||
end
|
||||
if event.type == 'quit' then
|
||||
Event.exitPullEvents()
|
||||
end
|
||||
UI.Page.eventHandler(self, event)
|
||||
local t = self.grid:getSelected()
|
||||
if t then
|
||||
if event.type == 'activate' or event.type == 'grid_select' then
|
||||
multishell.setFocus(t.uid)
|
||||
elseif event.type == 'terminate' then
|
||||
multishell.terminate(t.uid)
|
||||
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("%sm", math.floor(elapsed/6)/10)
|
||||
end
|
||||
if row.isDead then
|
||||
row.status = 'error'
|
||||
else
|
||||
row.status = coroutine.status(row.co)
|
||||
end
|
||||
return 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("%sm", math.floor(elapsed/6)/10)
|
||||
end
|
||||
row.status = row.isDead and 'error' or coroutine.status(row.co)
|
||||
return row
|
||||
end
|
||||
|
||||
Event.onInterval(1, function()
|
||||
page.grid:update()
|
||||
page.grid:draw()
|
||||
page:sync()
|
||||
page.grid:update()
|
||||
page.grid:draw()
|
||||
page:sync()
|
||||
end)
|
||||
|
||||
UI:setPage(page)
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
_G.requireInjector()
|
||||
_G.requireInjector(_ENV)
|
||||
|
||||
local Event = require('event')
|
||||
local Util = require('util')
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
_G.requireInjector()
|
||||
_G.requireInjector(_ENV)
|
||||
|
||||
local Security = require('security')
|
||||
local SHA1 = require('sha1')
|
||||
@@ -7,6 +7,6 @@ local Terminal = require('terminal')
|
||||
local password = Terminal.readPassword('Enter new password: ')
|
||||
|
||||
if password then
|
||||
Security.updatePassword(SHA1.sha1(password))
|
||||
print('Password updated')
|
||||
Security.updatePassword(SHA1.sha1(password))
|
||||
print('Password updated')
|
||||
end
|
||||
|
||||
966
sys/apps/shell
966
sys/apps/shell
File diff suppressed because it is too large
Load Diff
@@ -1,4 +1,4 @@
|
||||
_G.requireInjector()
|
||||
_G.requireInjector(_ENV)
|
||||
|
||||
local Event = require('event')
|
||||
local Socket = require('socket')
|
||||
@@ -14,73 +14,73 @@ local options, args = Util.args({ ... })
|
||||
|
||||
local remoteId = tonumber(table.remove(args, 1) or '')
|
||||
if not remoteId then
|
||||
print('Enter host ID')
|
||||
remoteId = tonumber(read())
|
||||
print('Enter host ID')
|
||||
remoteId = tonumber(read())
|
||||
end
|
||||
|
||||
if not remoteId then
|
||||
error('Syntax: telnet [-title TITLE] ID [PROGRAM]')
|
||||
error('Syntax: telnet [-title TITLE] ID [PROGRAM]')
|
||||
end
|
||||
|
||||
if options.title and multishell then
|
||||
multishell.setTitle(multishell.getCurrent(), options.title)
|
||||
multishell.setTitle(multishell.getCurrent(), options.title)
|
||||
end
|
||||
|
||||
local socket, msg = Socket.connect(remoteId, 23)
|
||||
|
||||
if not socket then
|
||||
error(msg)
|
||||
error(msg)
|
||||
end
|
||||
|
||||
local ct = Util.shallowCopy(term.current())
|
||||
if not ct.isColor() then
|
||||
Terminal.toGrayscale(ct)
|
||||
Terminal.toGrayscale(ct)
|
||||
end
|
||||
|
||||
local w, h = ct.getSize()
|
||||
socket:write({
|
||||
width = w,
|
||||
height = h,
|
||||
isColor = ct.isColor(),
|
||||
program = args,
|
||||
pos = { ct.getCursorPos() },
|
||||
width = w,
|
||||
height = h,
|
||||
isColor = ct.isColor(),
|
||||
program = args,
|
||||
pos = { ct.getCursorPos() },
|
||||
})
|
||||
|
||||
Event.addRoutine(function()
|
||||
while true do
|
||||
local data = socket:read()
|
||||
if not data then
|
||||
break
|
||||
end
|
||||
for _,v in ipairs(data) do
|
||||
ct[v.f](table.unpack(v.args))
|
||||
end
|
||||
end
|
||||
while true do
|
||||
local data = socket:read()
|
||||
if not data then
|
||||
break
|
||||
end
|
||||
for _,v in ipairs(data) do
|
||||
ct[v.f](table.unpack(v.args))
|
||||
end
|
||||
end
|
||||
end)
|
||||
|
||||
--ct.clear()
|
||||
--ct.setCursorPos(1, 1)
|
||||
|
||||
local filter = Util.transpose {
|
||||
'char', 'paste', 'key', 'key_up', 'terminate',
|
||||
'mouse_scroll', 'mouse_click', 'mouse_drag', 'mouse_up',
|
||||
'char', 'paste', 'key', 'key_up', 'terminate',
|
||||
'mouse_scroll', 'mouse_click', 'mouse_drag', 'mouse_up',
|
||||
}
|
||||
|
||||
while true do
|
||||
local e = { os.pullEventRaw() }
|
||||
local event = e[1]
|
||||
local e = { os.pullEventRaw() }
|
||||
local event = e[1]
|
||||
|
||||
if filter[event] then
|
||||
socket:write(e)
|
||||
else
|
||||
Event.processEvent(e)
|
||||
end
|
||||
if filter[event] then
|
||||
socket:write(e)
|
||||
else
|
||||
Event.processEvent(e)
|
||||
end
|
||||
|
||||
if not socket.connected then
|
||||
if not socket.connected then
|
||||
-- print()
|
||||
-- print('Connection lost')
|
||||
-- print('Press enter to exit')
|
||||
-- pcall(read)
|
||||
break
|
||||
end
|
||||
break
|
||||
end
|
||||
end
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
_G.requireInjector()
|
||||
_G.requireInjector(_ENV)
|
||||
|
||||
local Crypto = require('crypto')
|
||||
local Security = require('security')
|
||||
@@ -12,27 +12,27 @@ local remoteId
|
||||
local args = { ... }
|
||||
|
||||
if #args == 1 then
|
||||
remoteId = tonumber(args[1])
|
||||
remoteId = tonumber(args[1])
|
||||
else
|
||||
print('Enter host ID')
|
||||
remoteId = tonumber(_G.read())
|
||||
print('Enter host ID')
|
||||
remoteId = tonumber(_G.read())
|
||||
end
|
||||
|
||||
if not remoteId then
|
||||
error('Syntax: trust <host ID>')
|
||||
error('Syntax: trust <host ID>')
|
||||
end
|
||||
|
||||
local password = Terminal.readPassword('Enter password: ')
|
||||
|
||||
if not password then
|
||||
error('Invalid password')
|
||||
error('Invalid password')
|
||||
end
|
||||
|
||||
print('connecting...')
|
||||
local socket, msg = Socket.connect(remoteId, 19)
|
||||
|
||||
if not socket then
|
||||
error(msg)
|
||||
error(msg)
|
||||
end
|
||||
|
||||
local publicKey = Security.getPublicKey()
|
||||
@@ -43,9 +43,9 @@ local data = socket:read(2)
|
||||
socket:close()
|
||||
|
||||
if data and data.success then
|
||||
print(data.msg)
|
||||
print(data.msg)
|
||||
elseif data then
|
||||
error(data.msg)
|
||||
error(data.msg)
|
||||
else
|
||||
error('No response')
|
||||
error('No response')
|
||||
end
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
_G.requireInjector()
|
||||
_G.requireInjector(_ENV)
|
||||
|
||||
local Event = require('event')
|
||||
local Socket = require('socket')
|
||||
@@ -12,35 +12,35 @@ local term = _G.term
|
||||
local remoteId
|
||||
local args = { ... }
|
||||
if #args == 1 then
|
||||
remoteId = tonumber(args[1])
|
||||
remoteId = tonumber(args[1])
|
||||
else
|
||||
print('Enter host ID')
|
||||
remoteId = tonumber(_G.read())
|
||||
print('Enter host ID')
|
||||
remoteId = tonumber(_G.read())
|
||||
end
|
||||
|
||||
if not remoteId then
|
||||
error('Syntax: vnc <host ID>')
|
||||
error('Syntax: vnc <host ID>')
|
||||
end
|
||||
|
||||
if multishell then
|
||||
multishell.setTitle(multishell.getCurrent(), 'VNC-' .. remoteId)
|
||||
multishell.setTitle(multishell.getCurrent(), 'VNC-' .. remoteId)
|
||||
end
|
||||
|
||||
print('connecting...')
|
||||
local socket, msg = Socket.connect(remoteId, 5900)
|
||||
|
||||
if not socket then
|
||||
error(msg)
|
||||
error(msg)
|
||||
end
|
||||
|
||||
local function writeTermInfo()
|
||||
local w, h = term.getSize()
|
||||
socket:write({
|
||||
type = 'termInfo',
|
||||
width = w,
|
||||
height = h,
|
||||
isColor = term.isColor(),
|
||||
})
|
||||
local w, h = term.getSize()
|
||||
socket:write({
|
||||
type = 'termInfo',
|
||||
width = w,
|
||||
height = h,
|
||||
isColor = term.isColor(),
|
||||
})
|
||||
end
|
||||
|
||||
writeTermInfo()
|
||||
@@ -48,53 +48,53 @@ writeTermInfo()
|
||||
local ct = Util.shallowCopy(term.current())
|
||||
|
||||
if not ct.isColor() then
|
||||
Terminal.toGrayscale(ct)
|
||||
Terminal.toGrayscale(ct)
|
||||
end
|
||||
|
||||
Event.addRoutine(function()
|
||||
while true do
|
||||
local data = socket:read()
|
||||
if not data then
|
||||
break
|
||||
end
|
||||
for _,v in ipairs(data) do
|
||||
ct[v.f](unpack(v.args))
|
||||
end
|
||||
end
|
||||
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', 'mouse_up',
|
||||
'char', 'paste', 'key', 'key_up',
|
||||
'mouse_scroll', 'mouse_click', 'mouse_drag', 'mouse_up',
|
||||
})
|
||||
|
||||
while true do
|
||||
local e = Event.pullEvent()
|
||||
local event = e[1]
|
||||
local e = Event.pullEvent()
|
||||
local event = e[1]
|
||||
|
||||
if not socket.connected then
|
||||
print()
|
||||
print('Connection lost')
|
||||
print('Press enter to exit')
|
||||
_G.read()
|
||||
break
|
||||
end
|
||||
if not socket.connected then
|
||||
print()
|
||||
print('Connection lost')
|
||||
print('Press enter to exit')
|
||||
_G.read()
|
||||
break
|
||||
end
|
||||
|
||||
if filter[event] then
|
||||
socket:write({
|
||||
type = 'shellRemote',
|
||||
event = e,
|
||||
})
|
||||
elseif event == 'term_resize' then
|
||||
writeTermInfo()
|
||||
elseif event == 'terminate' then
|
||||
socket:close()
|
||||
ct.setBackgroundColor(colors.black)
|
||||
ct.clear()
|
||||
ct.setCursorPos(1, 1)
|
||||
break
|
||||
end
|
||||
if filter[event] then
|
||||
socket:write({
|
||||
type = 'shellRemote',
|
||||
event = e,
|
||||
})
|
||||
elseif event == 'term_resize' then
|
||||
writeTermInfo()
|
||||
elseif event == 'terminate' then
|
||||
socket:close()
|
||||
ct.setBackgroundColor(colors.black)
|
||||
ct.clear()
|
||||
ct.setCursorPos(1, 1)
|
||||
break
|
||||
end
|
||||
end
|
||||
|
||||
Reference in New Issue
Block a user