14 Commits

Author SHA1 Message Date
Anavrins
e3a0222ef0 fix: up-to-date package on master branch 2021-02-15 21:31:43 -05:00
kepler155c
136adff7b1 Delete .opus_version 2020-04-30 21:56:26 -06:00
github-actions[bot]
60ab8377b5 Update version date 2020-05-01 03:39:14 +00:00
kepler155c
6a58db5e1c Delete .opus_version 2020-04-30 21:32:52 -06:00
github-actions[bot]
d692116631 Update version date 2020-05-01 03:27:43 +00:00
kepler155c
af1ef38147 Delete .opus_version 2020-04-30 20:24:23 -06:00
github-actions[bot]
58c3a1529f Update version date 2020-05-01 02:23:31 +00:00
kepler155c
6f87c5a8a7 Delete .opus_version 2020-04-30 20:08:20 -06:00
github-actions[bot]
7891f9863a Update version date 2020-05-01 01:09:13 +00:00
Anavrins
0fd349a487 Fix wrong branch name
this also fixes packages loading on master-1.8
2020-01-15 00:12:29 -05:00
kepler155c@gmail.com
cef5b21921 update to master 2019-11-10 18:52:28 -07:00
kepler155c@gmail.com
ee6af86da8 Merge branch 'develop-1.8' into master-1.8 2019-11-10 18:51:51 -07:00
kepler155c
16843bdb78 Update git.lua 2019-01-18 14:25:44 -05:00
kepler155c
f21ff42e44 Update README.md 2018-12-30 13:53:20 -05:00
148 changed files with 3453 additions and 6345 deletions

View File

@@ -1,30 +0,0 @@
---
name: Bug report
about: Create a report to help us improve
title: ''
labels: bug
assignees: ''
---
**Describe the bug**
A clear and concise description of what the bug is.
**To Reproduce**
Steps to reproduce the behavior:
1. Go to '...'
2. Click on '....'
3. Scroll down to '....'
4. See error
**Expected behavior**
A clear and concise description of what you expected to happen.
**Screenshots**
If applicable, add screenshots to help explain your problem.
**Versions**
What version of Minecraft, CC:Tweaked, Plethora (if applicable), Opus branch are you using
- MC : [e.g. 1.12.2]
- CC:T : [e.g. 1.88]
- Opus : [e.g. develop-1.8]

View File

@@ -1,20 +0,0 @@
---
name: Feature request
about: Suggest an idea for this project
title: ''
labels: enhancement
assignees: ''
---
**Is your feature request related to a problem? Please describe.**
A clear and concise description of what the problem is. Ex. I'm always frustrated when [...]
**Describe the solution you'd like**
A clear and concise description of what you want to happen.
**Describe alternatives you've considered**
A clear and concise description of any alternative solutions or features you've considered.
**Additional context**
Add any other context or screenshots about the feature request here.

View File

@@ -1,17 +0,0 @@
---
name: Bug Report
about: Did something go wrong? File an issue!
title: Good titles include first line of stack trace or short summary of problem
labels: bug
---
<!--- THIS IS A COMMENT. IT WILL NOT APPEAR IN THE FINAL ISSUE, DO NOT DELETE THESE. -->
# Details
<!--- Put a description of the bug here. (Ex. I crashed when running Opus.) -->
## Further context
<!--- Stack trace (surrounded in three backticks, ), supplementary media such as screenshots and video, etc -->
## Versions
Branch:
Opus Version: <!--- (Do NOT put Latest unless you are unsure) -->
CraftOS Version:

View File

@@ -1,13 +0,0 @@
---
name: Enhancement
about: Suggest a new feature or change to Opus.
labels: enhancement
---
<!--- THIS IS A COMMENT. IT WILL NOT APPEAR IN THE FINAL ISSUE. DO NOT REMOVE THEM. -->
# Summary
<!--- Summarize what you want. -->
## Additional Context
<!--- Comcept art, screenshots, and other relevant media. -->
## Related
<!--- Delete this category if not used. -->
<!--- Use this category for relevant links, if present. -->

View File

@@ -1,36 +0,0 @@
# This is a basic workflow to help you get started with Actions
name: CI
# Controls when the action will run. Triggers the workflow on push or pull request
# events but only for the master branch
on:
push:
branches: [ develop-1.8 ]
pull_request:
branches: [ develop-1.8 ]
# A workflow run is made up of one or more jobs that can run sequentially or in parallel
jobs:
# This workflow contains a single job called "build"
build:
# The type of runner that the job will run on
runs-on: ubuntu-latest
# Steps represent a sequence of tasks that will be executed as part of the job
steps:
# Checks-out your repository under $GITHUB_WORKSPACE, so your job can access it
- uses: actions/checkout@v2
- name: Create version file
run: |
echo `date` > .opus_version
git log >> .opus_version
- name: Commit version file
uses: alexesprit/action-update-file@main
with:
branch: 'develop-1.8'
file-path: .opus_version
commit-msg: Update version date
github-token: ${{ secrets.GITHUB_TOKEN }}

1
.gitignore vendored
View File

@@ -1,2 +1 @@
/ignore
.project

View File

@@ -1,6 +0,0 @@
Mon Jul 4 04:09:12 UTC 2022
commit 3150525ee2024fc605669093b89f75f0c741a81f
Author: Kan18 <24967425+Kan18@users.noreply.github.com>
Date: Mon Jul 4 00:08:59 2022 -0400
Fix #48 (shell resolving issue) (#58)

View File

@@ -1,7 +1,5 @@
# Opus OS for computercraft
<img src="https://github.com/kepler155c/opus-wiki/blob/master/assets/images/opus.gif?raw=true" width="540" height="360">
## Features
* Multitasking OS - run programs in separate tabs
* Telnet (wireless remote shell)
@@ -16,5 +14,6 @@
## Install
```
pastebin run UzGHLbNC
pastebin run uzghlbnc
reboot
```

View File

@@ -29,10 +29,9 @@ local function loadBootOptions()
preload = { },
menu = {
{ prompt = os.version() },
{ prompt = 'Opus' , args = { '/sys/boot/opus.lua' } },
{ prompt = 'Opus Shell' , args = { '/sys/boot/opus.lua', '/sys/apps/shell.lua' } },
{ prompt = 'Opus Kiosk' , args = { '/sys/boot/kiosk.lua' } },
{ prompt = 'Opus TLCO' , args = { '/sys/boot/tlco.lua' } },
{ prompt = 'Opus' , args = { '/sys/boot/opus.boot' } },
{ prompt = 'Opus Shell' , args = { '/sys/boot/opus.boot', 'sys/apps/shell.lua' } },
{ prompt = 'Opus Kiosk' , args = { '/sys/boot/kiosk.boot' } },
},
}))
f.close()
@@ -42,20 +41,6 @@ local function loadBootOptions()
local options = textutils.unserialize(f.readAll())
f.close()
-- Backwards compatibility for .startup.boot files created before sys/boot files' extensions were changed
local changed = false
for _, item in pairs(options.menu) do
if item.args and item.args[1]:match("/?sys/boot/%l+%.boot") then
item.args[1] = item.args[1]:gsub("%.boot", "%.lua")
changed = true
end
end
if changed then
local f = fs.open(".startup.boot", "w")
f.write(textutils.serialize(options))
f.close()
end
return options
end
@@ -142,7 +127,7 @@ local function splash()
local opus = {
'fffff00',
'ffff07000',
'ff00770b00f4444',
'ff00770b00 4444',
'ff077777444444444',
'f07777744444444444',
'f0000777444444444',

View File

@@ -1,3 +1,4 @@
local Alt = require('opus.alternate')
local Config = require('opus.config')
local Event = require('opus.event')
local pastebin = require('opus.http.pastebin')
@@ -76,65 +77,21 @@ local Browser = UI.Page {
grid = UI.ScrollingGrid {
columns = {
{ heading = 'Name', key = 'name' },
{ key = 'flags', width = 3, textColor = 'lightGray' },
{ heading = 'Size', key = 'fsize', width = 5, textColor = 'yellow' },
{ key = 'flags', width = 2 },
{ heading = 'Size', key = 'fsize', width = 5 },
},
sortColumn = 'name',
y = 2, ey = -2,
sortCompare = function(self, 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,
getRowTextColor = function(_, file)
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,
eventHandler = function(self, event)
if event.type == 'copy' then -- let copy be handled by parent
return false
end
return UI.ScrollingGrid.eventHandler(self, event)
end
},
statusBar = UI.StatusBar {
columns = {
{ key = 'status' },
{ key = 'totalSize', width = 6 },
},
draw = function(self)
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,
},
question = UI.Question {
y = -2, x = -19,
label = 'Delete',
},
notification = UI.Notification { },
associations = UI.SlideOut {
backgroundColor = colors.cyan,
menuBar = UI.MenuBar {
buttons = {
{ text = 'Save', event = 'save' },
@@ -142,7 +99,7 @@ local Browser = UI.Page {
},
},
grid = UI.ScrollingGrid {
x = 2, ex = -6, y = 3, ey = -8,
x = 2, ex = -6, y = 3, ey = -5,
columns = {
{ heading = 'Extension', key = 'name' },
{ heading = 'Program', key = 'value' },
@@ -157,11 +114,8 @@ local Browser = UI.Page {
x = -4, y = 6,
text = '-', event = 'remove_entry', help = 'Remove',
},
[1] = UI.Window {
x = 2, y = -6, ex = -6, ey = -3,
},
form = UI.Form {
x = 3, y = -5, ex = -7, ey = -3,
x = 3, y = -3, ey = -2,
margin = 1,
manualControls = true,
[1] = UI.TextEntry {
@@ -176,13 +130,16 @@ local Browser = UI.Page {
formLabel = 'Program', formKey = 'value',
shadowText = 'program',
required = true,
limit = 128,
},
add = UI.Button {
x = -11, y = 1,
text = 'Add', event = 'add_association',
},
},
statusBar = UI.StatusBar { },
statusBar = UI.StatusBar {
backgroundColor = colors.cyan,
},
},
accelerators = {
[ 'control-q' ] = 'quit',
@@ -210,7 +167,7 @@ function Browser:enable()
self:setFocus(self.grid)
end
function Browser.menuBar.getActive(_, menuItem)
function Browser.menuBar:getActive(menuItem)
local file = Browser.grid:getSelected()
if menuItem.flags == FILE then
return file and not file.isDir
@@ -218,11 +175,56 @@ function Browser.menuBar.getActive(_, menuItem)
return true
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)
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:eventHandler(event)
if event.type == 'copy' then -- let copy be handled by parent
return false
end
return UI.ScrollingGrid.eventHandler(self, event)
end
function Browser.statusBar:draw()
if self.parent.dir then
local info = '#:' .. Util.size(self.parent.dir.files)
local numMarked = Util.size(marked)
if numMarked > 0 then
info = info .. ' M:' .. numMarked
end
self:setValue('info', info)
self:setValue('totalSize', formatSize(self.parent.dir.totalSize))
UI.StatusBar.draw(self)
end
end
function Browser:setStatus(status, ...)
self.notification:info(string.format(status, ...))
end
function Browser.unmarkAll()
function Browser:unmarkAll()
for _,m in pairs(marked) do
m.marked = false
end
@@ -253,6 +255,7 @@ function Browser:getDirectory(directory)
end
function Browser:updateDirectory(dir)
dir.size = 0
dir.totalSize = 0
Util.clear(dir.files)
@@ -262,11 +265,10 @@ function Browser:updateDirectory(dir)
dir.size = #files
for _, file in pairs(files) do
file.fullName = fs.combine(dir.name, file.name)
file.flags = file.fstype or ' '
file.flags = ''
if not file.isDir then
dir.totalSize = dir.totalSize + file.size
file.fsize = formatSize(file.size)
file.flags = file.flags .. ' '
else
if config.showDirSizes then
file.size = fs.getSize(file.fullName, true)
@@ -274,9 +276,11 @@ function Browser:updateDirectory(dir)
dir.totalSize = dir.totalSize + file.size
file.fsize = formatSize(file.size)
end
file.flags = file.flags .. 'D'
file.flags = 'D'
end
if file.isReadOnly then
file.flags = file.flags .. 'R'
end
file.flags = file.flags .. (file.isReadOnly and 'R' or ' ')
if config.showHidden or file.name:sub(1, 1) ~= '.' then
dir.files[file.fullName] = file
end
@@ -340,7 +344,7 @@ function Browser:eventHandler(event)
local file = self.grid:getSelected()
if event.type == 'quit' then
UI:quit()
Event.exitPullEvents()
elseif event.type == 'edit' and file then
self:run('edit', file.name)
@@ -350,7 +354,7 @@ function Browser:eventHandler(event)
self:setStatus('Started cloud edit')
elseif event.type == 'shell' then
self:run('shell')
self:run(Alt.get('shell'))
elseif event.type == 'refresh' then
self:updateDirectory(self.dir)
@@ -428,25 +432,28 @@ function Browser:eventHandler(event)
elseif event.type == 'delete' then
if self:hasMarked() then
self.question:show()
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 _,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
return true
elseif event.type == 'question_yes' then
for _,m in pairs(marked) do
pcall(fs.delete, m.fullName)
end
marked = { }
self:updateDirectory(self.dir)
self.question:hide()
self.statusBar:draw()
self.grid:draw()
self:setFocus(self.grid)
elseif event.type == 'question_no' then
self.question:hide()
self:setFocus(self.grid)
elseif event.type == 'copy' or event.type == 'cut' then
if self:hasMarked() then
@@ -465,7 +472,7 @@ function Browser:eventHandler(event)
elseif event.type == 'paste' then
for _,m in pairs(copied) do
pcall(function()
local s, m = pcall(function()
if cutMode then
fs.move(m.fullName, fs.combine(self.dir.name, m.name))
else
@@ -542,4 +549,6 @@ local args = Util.parse(...)
Browser:setDir(args[1] or shell.dir())
UI:setPage(Browser)
UI:start()
Event.pullEvents()
UI.term:reset()

View File

@@ -1,24 +1,27 @@
local fuzzy = require('opus.fuzzy')
local UI = require('opus.ui')
local Util = require('opus.util')
local colors = _G.colors
local help = _G.help
UI:configure('Help', ...)
local topics = { }
for _,topic in pairs(help.topics()) do
table.insert(topics, { name = topic, lname = topic:lower() })
if help.lookup(topic) then
table.insert(topics, { name = topic })
end
end
UI:addPage('main', UI.Page {
UI.Text {
local page = UI.Page {
labelText = UI.Text {
x = 3, y = 2,
value = 'Search',
},
UI.TextEntry {
filter = UI.TextEntry {
x = 10, y = 2, ex = -3,
limit = 32,
transform = 'lowercase',
},
grid = UI.ScrollingGrid {
y = 4,
@@ -26,71 +29,76 @@ UI:addPage('main', UI.Page {
columns = {
{ heading = 'Topic', key = 'name' },
},
sortColumn = 'lname',
sortColumn = 'name',
},
accelerators = {
[ 'control-q' ] = 'quit',
enter = 'grid_select',
},
eventHandler = function(self, event)
if event.type == 'quit' then
UI:quit()
}
elseif event.type == 'grid_select' then
if self.grid:getSelected() then
UI:setPage('topic', self.grid:getSelected().name)
end
elseif event.type == 'text_change' then
if not event.text then
self.grid.sortColumn = 'lname'
else
self.grid.sortColumn = 'score'
self.grid.inverseSort = false
local pattern = event.text:lower()
for _,v in pairs(self.grid.values) do
v.score = -fuzzy(v.lname, pattern)
end
end
self.grid:update()
self.grid:setIndex(1)
self.grid:draw()
else
return UI.Page.eventHandler(self, event)
end
end,
})
UI:addPage('topic', UI.Page {
backgroundColor = 'black',
local topicPage = UI.Page {
backgroundColor = colors.black,
titleBar = UI.TitleBar {
title = 'text',
event = 'back',
},
helpText = UI.TextArea {
backgroundColor = colors.black,
x = 2, ex = -1, y = 3, ey = -2,
},
accelerators = {
[ 'control-q' ] = 'back',
backspace = 'back',
},
enable = function(self, name)
local f = help.lookup(name)
}
self.titleBar.title = name
self.helpText:setText(f and Util.readFile(f) or 'No help available for ' .. name)
function topicPage:enable(name)
local f = help.lookup(name)
return UI.Page.enable(self)
end,
eventHandler = function(self, event)
if event.type == 'back' then
UI:setPage('main')
self.titleBar.title = name
self.helpText:setText(f and Util.readFile(f) or 'No help available for ' .. name)
return UI.Page.enable(self)
end
function topicPage:eventHandler(event)
if event.type == 'back' then
UI:setPage(page)
end
return UI.Page.eventHandler(self, event)
end
function page:eventHandler(event)
if event.type == 'quit' then
UI:exitPullEvents()
elseif event.type == 'grid_select' then
if self.grid:getSelected() then
local name = self.grid:getSelected().name
UI:setPage(topicPage, name)
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
end
local args = Util.parse(...)
UI:setPage(args[1] and 'topic' or 'main', args[1])
UI:start()
UI:setPage(args[1] and topicPage or page, args[1])
UI:pullEvents()

View File

@@ -1,3 +1,6 @@
-- Lua may be called from outside of shell - inject a require
_G.requireInjector(_ENV)
local History = require('opus.history')
local UI = require('opus.ui')
local Util = require('opus.util')
@@ -7,8 +10,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() UI:quit() end
sandboxEnv.exit = function() _exit = true end
sandboxEnv._echo = function( ... ) return { ... } end
_G.requireInjector(sandboxEnv)
@@ -29,6 +34,7 @@ local page = UI.Page {
prompt = UI.TextEntry {
y = 2,
shadowText = 'enter command',
limit = 1024,
accelerators = {
enter = 'command_enter',
up = 'history_back',
@@ -39,9 +45,8 @@ local page = UI.Page {
},
tabs = UI.Tabs {
y = 3,
formatted = UI.Tab {
title = 'Formatted',
index = 1,
[1] = UI.Tab {
tabTitle = 'Formatted',
grid = UI.ScrollingGrid {
columns = {
{ heading = 'Key', key = 'name' },
@@ -51,25 +56,19 @@ local page = UI.Page {
autospace = true,
},
},
output = UI.Tab {
title = 'Output',
index = 2,
backgroundColor = 'black',
[2] = UI.Tab {
tabTitle = 'Output',
output = UI.Embedded {
y = 2,
visible = true,
maxScroll = 1000,
backgroundColor = 'black',
backgroundColor = colors.black,
},
draw = function(self)
self:write(1, 1, string.rep('\131', self.width), 'black', 'primary')
self:drawChildren()
end,
},
},
}
page.grid = page.tabs.formatted.grid
page.output = page.tabs.output.output
page.grid = page.tabs[1].grid
page.output = page.tabs[2].output
function page:setPrompt(value, focus)
self.prompt:setValue(value)
@@ -134,18 +133,30 @@ function page:eventHandler(event)
self:executeStatement('_ENV')
command = nil
elseif event.type == 'hide_output' then
self.output:disable()
self.titleBar.oy = -1
self.titleBar.event = 'show_output'
self.titleBar.closeInd = '^'
self.titleBar:resize()
self.grid.ey = -2
self.grid:resize()
self:draw()
elseif event.type == 'tab_select' then
self:setFocus(self.prompt)
elseif event.type == 'show_output' then
self.tabs:selectTab(self.tabs.output)
self.tabs:selectTab(self.tabs[2])
elseif event.type == 'autocomplete' then
local value = self.prompt.value or ''
local sz = #value
local sz = #self.prompt.value
local pos = self.prompt.entry.pos
self:setPrompt(autocomplete(sandboxEnv, value, self.prompt.entry.pos))
self.prompt:setPosition(pos + #(self.prompt.value or '') - sz)
self:setPrompt(autocomplete(sandboxEnv, self.prompt.value, self.prompt.entry.pos))
self.prompt:setPosition(pos + #self.prompt.value - sz)
self.prompt:updateCursor()
elseif event.type == 'device' then
@@ -166,7 +177,7 @@ function page:eventHandler(event)
history:reset()
elseif event.type == 'command_enter' then
local s = tostring(self.prompt.value or '')
local s = tostring(self.prompt.value)
if #s > 0 then
self:executeStatement(s)
@@ -184,7 +195,8 @@ function page:eventHandler(event)
command = nil
self.grid:setValues(t)
self.grid:setIndex(1)
self.grid:draw()
self.grid:adjustWidth()
self:draw()
end
return true
@@ -230,7 +242,8 @@ function page:setResult(result)
end
self.grid:setValues(t)
self.grid:setIndex(1)
self.grid:draw()
self.grid:adjustWidth()
self:draw()
end
function page.grid:eventHandler(event)
@@ -348,7 +361,7 @@ function page:executeStatement(statement)
term.redirect(oterm)
counter = counter + 1
if s and type(m) ~= "nil" then
if s and m then
self:setResult(m)
else
self.grid:setValues({ })
@@ -357,6 +370,10 @@ function page:executeStatement(statement)
self:emit({ type = 'show_output' })
end
end
if _exit then
UI:exitPullEvents()
end
end
local args = Util.parse(...)
@@ -364,8 +381,7 @@ if args[1] then
command = 'args[1]'
sandboxEnv.args = args
page:setResult(args[1])
page:setPrompt(command)
end
UI:setPage(page)
UI:start()
UI:pullEvents()

View File

@@ -4,8 +4,10 @@ local Socket = require('opus.socket')
local UI = require('opus.ui')
local Util = require('opus.util')
local colors = _G.colors
local device = _G.device
local network = _G.network
local os = _G.os
local shell = _ENV.shell
UI:configure('Network', ...)
@@ -54,45 +56,14 @@ local page = UI.Page {
columns = gridColumns,
sortColumn = 'label',
autospace = true,
getRowTextColor = function(self, row, selected)
if not row.active then
return 'lightGray'
end
return UI.Grid.getRowTextColor(self, row, selected)
end,
getDisplayValues = function(_, row)
row = Util.shallowCopy(row)
if row.uptime then
if row.uptime < 60 then
row.uptime = string.format("%ds", math.floor(row.uptime))
elseif row.uptime < 3600 then
row.uptime = string.format("%sm", math.floor(row.uptime / 60))
else
row.uptime = string.format("%sh", math.floor(row.uptime / 3600))
end
end
if row.fuel then
row.fuel = row.fuel > 0 and Util.toBytes(row.fuel) or ''
end
if row.distance then
row.distance = Util.toBytes(Util.round(row.distance, 1))
end
return row
end,
},
ports = UI.SlideOut {
titleBar = UI.TitleBar {
title = 'Ports',
event = 'ports_hide',
},
menuBar = UI.MenuBar {
y = 2,
buttons = {
{ text = 'Refresh', event = 'ports_update' },
}
},
grid = UI.ScrollingGrid {
y = 3,
y = 2,
columns = {
{ heading = 'Port', key = 'port' },
{ heading = 'State', key = 'state' },
@@ -101,12 +72,31 @@ local page = UI.Page {
sortColumn = 'port',
autospace = true,
},
eventHandler = function(self, event)
if event.type == 'grid_select' then
shell.openForegroundTab('Sniff ' .. event.selected.port)
end
return UI.SlideOut.eventHandler(self, event)
end,
},
help = UI.SlideOut {
backgroundColor = colors.cyan,
x = 5, ex = -5, height = 8, y = -8,
titleBar = UI.TitleBar {
title = 'Network Help',
event = 'slide_hide',
},
text = UI.TextArea {
x = 2, y = 2,
backgroundColor = colors.cyan,
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.
This only needs to be done once.
]],
},
accelerators = {
q = 'slide_hide',
}
},
notification = UI.Notification { },
accelerators = {
@@ -137,6 +127,13 @@ local function sendCommand(host, command)
end
end
function page.ports:eventHandler(event)
if event.type == 'grid_select' then
shell.openForegroundTab('Sniff ' .. event.selected.port)
end
return UI.SlideOut.eventHandler(self, event)
end
function page.ports.grid:update()
local transport = network:getTransport()
@@ -188,14 +185,12 @@ function page:eventHandler(event)
elseif event.type == 'vnc' then
shell.openForegroundTab('vnc.lua ' .. t.id)
--[[
os.queueEvent('overview_shortcut', {
title = t.label,
category = "VNC",
icon = "\010\030 \009\009\031e\\\031 \031e/\031dn\010\030 \009\009 \031e\\/\031 \031bc",
run = "vnc.lua " .. t.id,
})
--]]
elseif event.type == 'clear' then
Util.clear(network)
@@ -214,22 +209,17 @@ function page:eventHandler(event)
end
if event.type == 'help' then
shell.switchTab(shell.openTab('Help Networking'))
self.help:show()
elseif event.type == 'ports' then
self.ports.grid:update()
self.ports:show()
-- self.portsHandler = Event.onInterval(3, function()
-- self.ports.grid:update()
-- self.ports.grid:draw()
-- self:sync()
-- end)
elseif event.type == 'ports_update' then
self.portsHandler = Event.onInterval(3, function()
self.ports.grid:update()
self.ports.grid:draw()
self:sync()
end)
elseif event.type == 'ports_hide' then
Event.off(self.portsHandler)
@@ -240,7 +230,7 @@ function page:eventHandler(event)
Config.update('network', config)
elseif event.type == 'quit' then
UI:quit()
Event.exitPullEvents()
end
UI.Page.eventHandler(self, event)
end
@@ -253,6 +243,33 @@ function page.menuBar:getActive(menuItem)
return menuItem.noCheck or not not t
end
function page.grid:getRowTextColor(row, selected)
if not row.active then
return colors.lightGray
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))
elseif row.uptime < 3600 then
row.uptime = string.format("%sm", math.floor(row.uptime / 60))
else
row.uptime = string.format("%sh", math.floor(row.uptime / 3600))
end
end
if row.fuel then
row.fuel = row.fuel > 0 and Util.toBytes(row.fuel) or ''
end
if row.distance then
row.distance = Util.toBytes(Util.round(row.distance, 1))
end
return row
end
Event.onInterval(1, function()
page.grid:update()
page.grid:draw()
@@ -278,4 +295,4 @@ if not device.wireless_modem then
end
UI:setPage(page)
UI:start()
UI:pullEvents()

View File

@@ -1,4 +1,4 @@
local Array = require('opus.array')
local Alt = require('opus.alternate')
local class = require('opus.class')
local Config = require('opus.config')
local Event = require('opus.event')
@@ -9,6 +9,7 @@ local Tween = require('opus.ui.tween')
local UI = require('opus.ui')
local Util = require('opus.util')
local colors = _G.colors
local device = _G.device
local fs = _G.fs
local os = _G.os
@@ -17,31 +18,14 @@ local shell = _ENV.shell
local term = _G.term
local turtle = _G.turtle
--[[
turtle: 39x13
computer: 51x19
pocket: 26x20
]]
if not _ENV.multishell then
error('multishell is required')
end
local REGISTRY_DIR = 'usr/.registry'
-- iconExt:gsub('.', function(b) return '\\' .. b:byte() end)
local DEFAULT_ICON = NFT.parse('\30\55\31\48\136\140\140\140\132\
\30\48\31\55\149\31\48\128\128\128\30\55\149\
\30\55\31\48\138\143\143\143\133')
local TRANS_ICON = NFT.parse('\0302\0312\32\32\32\32\32\
\0302\0312\32\32\32\32\32\
\0302\0312\32\32\32\32\32')
-- overview
local uid = _ENV.multishell.getCurrent()
device.keyboard.addHotkey('control-o', function()
_ENV.multishell.setFocus(uid)
end)
local DEFAULT_ICON = NFT.parse("\0308\0317\153\153\153\153\153\
\0307\0318\153\153\153\153\153\
\0308\0317\153\153\153\153\153")
UI:configure('Overview', ...)
@@ -75,7 +59,6 @@ local function parseIcon(iconText)
if icon.height > 3 or icon.width > 8 then
error('Must be an NFT image - 3 rows, 8 cols max')
end
NFT.transparency(icon)
end
return icon
end)
@@ -87,39 +70,45 @@ local function parseIcon(iconText)
return s, m
end
UI.VerticalTabBar = class(UI.TabBar)
function UI.VerticalTabBar:setParent()
self.x = 1
self.width = 8
self.height = nil
self.ey = -2
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
local page = UI.Page {
container = UI.Viewport {
x = 9, y = 1,
},
tabBar = UI.TabBar {
ey = -2,
width = 8,
selectedBackgroundColor = 'primary',
backgroundColor = 'tertiary',
unselectedTextColor = 'lightGray',
layout = function(self)
self.height = nil
UI.TabBar.layout(self)
end,
x = cx,
y = cy,
},
tray = UI.Window {
y = -1, width = 8,
backgroundColor = 'tertiary',
newApp = UI.FlatButton {
x = 2,
backgroundColor = colors.lightGray,
newApp = UI.Button {
text = '+', event = 'new',
},
mode = UI.FlatButton {
x = 4,
text = '=', event = 'display_mode',
},
help = UI.FlatButton {
x = 6,
text = '?', event = 'help',
},
--[[
volume = UI.Button {
x = 3,
text = '\15', event = 'volume',
},]]
},
editor = UI.SlideOut {
y = -12, height = 12,
backgroundColor = colors.cyan,
titleBar = UI.TitleBar {
title = 'Edit Application',
event = 'slide_hide',
@@ -127,7 +116,7 @@ local page = UI.Page {
form = UI.Form {
y = 2, ey = -2,
[1] = UI.TextEntry {
formLabel = 'Title', formKey = 'title', limit = 11, width = 13, help = 'Application title',
formLabel = 'Title', formKey = 'title', limit = 11, help = 'Application title',
required = true,
},
[2] = UI.TextEntry {
@@ -135,50 +124,23 @@ local page = UI.Page {
required = true,
},
[3] = UI.TextEntry {
formLabel = 'Category', formKey = 'category', limit = 6, width = 8, help = 'Category of application',
formLabel = 'Category', formKey = 'category', limit = 11, help = 'Category of application',
required = true,
},
editIcon = UI.Button {
x = 11, y = 6,
text = 'Edit', event = 'editIcon', help = 'Edit icon file',
iconFile = UI.TextEntry {
x = 11, ex = -12, y = 7,
limit = 128, help = 'Path to icon file',
shadowText = 'Path to icon file',
},
loadIcon = UI.Button {
x = 11, y = 8,
text = 'Load', event = 'loadIcon', help = 'Load icon file',
},
helpIcon = UI.Button {
x = 11, y = 8,
x = 11, y = 9,
text = 'Load', event = 'loadIcon', help = 'Load icon file',
},
image = UI.NftImage {
backgroundColor = 'black',
y = 6, x = 2, height = 3, width = 8,
backgroundColor = colors.black,
y = 7, x = 2, height = 3, width = 8,
},
},
file_open = UI.FileSelect {
modal = true,
enable = function() end,
transitionHint = 'expandUp',
show = function(self)
UI.FileSelect.enable(self)
self:focusFirst()
self:draw()
end,
disable = function(self)
UI.FileSelect.disable(self)
self.parent:focusFirst()
-- need to recapture as we are opening a modal within another modal
self.parent:capture(self.parent)
end,
eventHandler = function(self, event)
if event.type == 'select_cancel' then
self:disable()
elseif event.type == 'select_file' then
self:disable()
end
return UI.FileSelect.eventHandler(self, event)
end,
},
notification = UI.Notification(),
statusBar = UI.StatusBar(),
},
@@ -189,7 +151,6 @@ local page = UI.Page {
f = 'files',
s = 'shell',
l = 'lua',
n = 'network',
[ 'control-n' ] = 'new',
delete = 'delete',
},
@@ -237,7 +198,7 @@ local function loadApplications()
return requirements[a.requires]
end
return true
return true -- Util.startsWith(a.run, 'http') or shell.resolveProgram(a.run)
end)
local categories = { }
@@ -247,7 +208,6 @@ local function loadApplications()
categories[f.category] = true
table.insert(buttons, {
text = f.category,
width = 8,
selected = config.currentCategory == f.category
})
end
@@ -255,13 +215,13 @@ local function loadApplications()
table.sort(buttons, function(a, b) return a.text < b.text end)
table.insert(buttons, 1, { text = 'Recent' })
for k,v in pairs(buttons) do
v.x = 1
v.y = k + 1
end
Util.removeByValue(page.children, page.tabBar)
page.tabBar.children = { }
page.tabBar:addButtons(buttons)
page:add {
tabBar = UI.VerticalTabBar {
buttons = buttons,
},
}
--page.tabBar:selectTab(config.currentCategory or 'Apps')
page.container:setCategory(config.currentCategory or 'Apps')
@@ -276,6 +236,7 @@ UI.Icon.defaults = {
function UI.Icon:eventHandler(event)
if event.type == 'mouse_click' then
self:setFocus(self.button)
--self:emit({ type = self.button.event, button = self.button })
return true
elseif event.type == 'mouse_doubleclick' then
self:emit({ type = self.button.event, button = self.button })
@@ -291,23 +252,37 @@ function page.container:setCategory(categoryName, animate)
self.children = { }
self:reset()
local filtered = { }
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, 'key', v)
if app then
if app then -- and fs.exists(app.run) then
table.insert(filtered, app)
end
end
else
filtered = Array.filter(applications, function(a)
return a.category == categoryName
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 extSupport and program.iconExt then
icon = parseIcon(program.iconExt)
@@ -322,43 +297,27 @@ function page.container:setCategory(categoryName, animate)
local title = ellipsis(program.title, 8)
local width = math.max(icon.width + 2, #title + 2)
if config.listMode then
table.insert(self.children, UI.Icon {
width = self.width - 2,
height = 1,
UI.Button {
x = 1, ex = -1,
text = program.title,
centered = false,
backgroundColor = self:getProperty('backgroundColor'),
backgroundFocusColor = 'gray',
textColor = 'white',
textFocusColor = 'white',
event = 'button',
app = program,
}
})
else
table.insert(self.children, UI.Icon({
width = width,
image = UI.NftImage({
x = math.floor((width - icon.width) / 2) + 1,
image = icon,
}),
button = UI.Button({
x = math.floor((width - #title - 2) / 2) + 1,
y = 4,
text = title,
backgroundColor = self:getProperty('backgroundColor'),
backgroundFocusColor = 'gray',
textColor = 'white',
textFocusColor = 'white',
width = #title + 2,
event = 'button',
app = program,
}),
}))
end
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
@@ -368,8 +327,7 @@ function page.container:setCategory(categoryName, animate)
local col, row = gutter, 2
local count = #self.children
local r = math.random(1, 7)
local frames = 5
local r = math.random(1, 5)
-- reposition all children
for k,child in ipairs(self.children) do
if r == 1 then
@@ -391,27 +349,19 @@ function page.container:setCategory(categoryName, animate)
child.x = self.width
child.y = self.height - 3
end
elseif r == 6 then
child.x = col
child.y = 1
elseif r == 7 then
child.x = 1
child.y = self.height - 3
end
child.tween = Tween.new(frames, child, { x = col, y = row }, 'inQuad')
child.tween = Tween.new(6, child, { x = col, y = row }, 'linear')
if not animate then
child.x = col
child.y = row
end
self:setViewHeight(row + (config.listMode and 1 or 4))
if k < count then
col = col + child.width
if col + self.children[k + 1].width + gutter - 2 > self.width then
col = gutter
row = row + (config.listMode and 1 or 5)
row = row + 5
end
end
end
@@ -421,12 +371,15 @@ function page.container:setCategory(categoryName, animate)
local function transition()
local i = 1
return function()
self:clear()
for _,child in pairs(self.children) do
child.tween:update(1)
child:move(math.floor(child.x), math.floor(child.y))
child.x = math.floor(child.x)
child.y = math.floor(child.y)
child:draw()
end
i = i + 1
return i <= frames
return i < 7
end
end
self:addTransition(transition)
@@ -468,19 +421,13 @@ function page:eventHandler(event)
shell.switchTab(shell.openTab(event.button.app.run))
elseif event.type == 'shell' then
shell.switchTab(shell.openTab('shell'))
shell.switchTab(shell.openTab(Alt.get('shell')))
elseif event.type == 'lua' then
shell.switchTab(shell.openTab('Lua'))
shell.switchTab(shell.openTab(Alt.get('lua')))
elseif event.type == 'files' then
shell.switchTab(shell.openTab('Files'))
elseif event.type == 'network' then
shell.switchTab(shell.openTab('Network'))
elseif event.type == 'help' then
shell.switchTab(shell.openTab('Help Overview'))
shell.switchTab(shell.openTab(Alt.get('files')))
elseif event.type == 'focus_change' then
if event.focused.parent.UIElement == 'Icon' then
@@ -516,13 +463,6 @@ function page:eventHandler(event)
end
self.editor:show({ category = category })
elseif event.type == 'display_mode' then
config.listMode = not config.listMode
Config.update('Overview', config)
loadApplications()
self:refresh()
self:draw()
elseif event.type == 'edit' then
local focused = page:getFocused()
if focused.app then
@@ -530,7 +470,7 @@ function page:eventHandler(event)
end
else
return UI.Page.eventHandler(self, event)
UI.Page.eventHandler(self, event)
end
return true
end
@@ -552,6 +492,11 @@ function page.editor:show(app)
self:focusFirst()
end
function page.editor.form.image:draw()
self:clear()
UI.NftImage.draw(self)
end
function page.editor:updateApplications(app)
if not app.key then
app.key = SHA.compute(app.title)
@@ -561,51 +506,36 @@ function page.editor:updateApplications(app)
loadApplications()
end
function page.editor:loadImage(filename)
local s, m = pcall(function()
local iconLines = Util.readFile(filename)
if not iconLines then
error('Must be an NFT image - 3 rows, 8 cols max')
end
local icon, m = parseIcon(iconLines)
if not icon then
error(m)
end
if extSupport then
self.form.values.iconExt = iconLines
else
self.form.values.icon = iconLines
end
self.form.image:setImage(icon)
self.form.image:draw()
end)
if not s and m then
local msg = m:gsub('.*: (.*)', '%1')
self.notification:error(msg)
end
end
function page.editor:eventHandler(event)
if event.type == 'form_cancel' or event.type == 'cancel' then
self:hide()
elseif event.type == 'focus_change' then
self.statusBar:setStatus(event.focused.help or '')
elseif event.type == 'editIcon' then
local filename = '/tmp/editing.nft'
NFT.save(self.form.image.image or TRANS_ICON, filename)
local success = shell.run('pain.lua ' .. filename)
self.parent:dirty(true)
if success then
self:loadImage(filename)
end
elseif event.type == 'select_file' then
self:loadImage(event.file)
self.statusBar:draw()
elseif event.type == 'loadIcon' then
self.file_open:show()
local s, m = pcall(function()
local iconLines = Util.readFile(self.form.iconFile.value)
if not iconLines then
error('Must be an NFT image - 3 rows, 8 cols max')
end
local icon, m = parseIcon(iconLines)
if not icon then
error(m)
end
if extSupport then
self.form.values.iconExt = iconLines
else
self.form.values.icon = iconLines
end
self.form.image:setImage(icon)
self.form.image:draw()
end)
if not s and m then
local msg = m:gsub('.*: (.*)', '%1')
self.notification:error(msg)
end
elseif event.type == 'form_invalid' then
self.notification:error(event.message)
@@ -614,6 +544,8 @@ function page.editor:eventHandler(event)
local values = self.form.values
self:hide()
self:updateApplications(values)
--page:refresh()
--page:draw()
config.currentCategory = values.category
Config.update('Overview', config)
os.queueEvent('overview_refresh')
@@ -623,6 +555,10 @@ function page.editor:eventHandler(event)
return true
end
UI:setPages({
main = page,
})
local function reload()
loadApplications()
page:refresh()
@@ -648,4 +584,5 @@ end)
loadApplications()
UI:setPage(page)
UI:start()
UI:pullEvents()

View File

@@ -1,5 +1,4 @@
local Ansi = require('opus.ansi')
local Config = require('opus.config')
local Packages = require('opus.packages')
local UI = require('opus.ui')
local Util = require('opus.util')
@@ -9,11 +8,9 @@ local term = _G.term
UI:configure('PackageManager', ...)
local config = Config.load('package')
local page = UI.Page {
grid = UI.ScrollingGrid {
x = 2, ex = 14, y = 2, ey = -6,
x = 2, ex = 14, y = 2, ey = -5,
values = { },
columns = {
{ heading = 'Package', key = 'name' },
@@ -24,13 +21,13 @@ local page = UI.Page {
},
add = UI.Button {
x = 2, y = -3,
text = ' + ',
text = 'Install',
event = 'action',
help = 'Install or update',
},
remove = UI.Button {
x = 8, y = -3,
text = ' - ',
x = 12, y = -3,
text = 'Remove ',
event = 'action',
operation = 'uninstall',
operationText = 'Remove',
@@ -44,17 +41,10 @@ local page = UI.Page {
},
description = UI.TextArea {
x = 16, y = 3, ey = -5,
marginRight = 2, marginLeft = 0,
},
UI.Checkbox {
x = 3, y = -5,
label = 'Compress',
textColor = 'yellow',
backgroundColor = 'primary',
value = config.compression,
help = 'Compress packages (experimental)',
marginRight = 0, marginLeft = 0,
},
action = UI.SlideOut {
backgroundColor = colors.cyan,
titleBar = UI.TitleBar {
event = 'hide-action',
},
@@ -113,6 +103,8 @@ end
function page.action:show()
self.output.win:clear()
UI.SlideOut.show(self)
--self.output:draw()
--self.output.win.redraw()
end
function page:run(operation, name)
@@ -136,6 +128,7 @@ end
function page:updateSelection(selected)
self.add.operation = selected.installed and 'update' or 'install'
self.add.operationText = selected.installed and 'Update' or 'Install'
self.add.text = selected.installed and 'Update' or 'Install'
self.remove.inactive = not selected.installed
self.add:draw()
self.remove:draw()
@@ -148,16 +141,12 @@ function page:eventHandler(event)
elseif event.type == 'grid_focus_row' then
local manifest = event.selected.manifest
self.description:setValue(string.format('%s%s\n\n%s%s',
self.description.value = string.format('%s%s\n\n%s%s',
Ansi.yellow, manifest.title,
Ansi.white, manifest.description))
Ansi.white, manifest.description)
self.description:draw()
self:updateSelection(event.selected)
elseif event.type == 'checkbox_change' then
config.compression = not config.compression
Config.update('package', config)
elseif event.type == 'updateall' then
self.operation = 'updateall'
self.action.button.text = ' Begin '
@@ -195,7 +184,7 @@ function page:eventHandler(event)
self.action.button:draw()
elseif event.type == 'quit' then
UI:quit()
UI:exitPullEvents()
end
UI.Page.eventHandler(self, event)
end
@@ -207,4 +196,4 @@ Packages:downloadList()
page:loadPackages()
page:sync()
UI:start()
UI:pullEvents()

View File

@@ -1,238 +0,0 @@
local Ansi = require('opus.ansi')
local Event = require('opus.event')
local UI = require('opus.ui')
local Util = require('opus.util')
local fs = _G.fs
local peripheral = _G.peripheral
local source, target
local function getDriveInfo(tgt)
local total = 0
local throttle = Util.throttle()
tgt = fs.combine(tgt, '')
local src = fs.getNode(tgt).source or tgt
local function recurse(path)
throttle()
if fs.isDir(path) then
if path ~= src then
total = total + 500
end
for _, v in pairs(fs.native.list(path)) do
recurse(fs.combine(path, v))
end
else
local sz = fs.getSize(path)
total = total + math.max(500, sz)
end
end
recurse(src)
local drive = fs.getDrive(src)
return {
path = tgt,
drive = drive,
type = peripheral.getType(drive) or drive,
used = total,
free = fs.getFreeSpace(src),
mountPoint = src,
}
end
local function getDrives(exclude)
local drives = { }
for _, path in pairs(fs.native.list('/')) do
local side = fs.getDrive(path)
if side and not drives[side] and not fs.isReadOnly(path) and side ~= exclude then
if side == 'hdd' then
path = ''
end
drives[side] = getDriveInfo(path)
end
end
return drives
end
local page = UI.Page {
wizard = UI.Wizard {
ey = -2,
partitions = UI.WizardPage {
index = 1,
info = UI.TextArea {
x = 3, y = 2, ex = -3, ey = 5,
value = [[Move the contents of a directory to another disk. A link will be created to point to that location.]]
},
grid = UI.Grid {
x = 2, y = 7, ex = -2, ey = -2,
columns = {
{ heading = 'Path', key = 'path', textColor = 'yellow', width = 10 },
{ heading = 'Mount Point', key = 'mountPoint' },
{ heading = 'Used', key = 'used', width = 6 },
},
sortColumn = 'path',
getDisplayValues = function (_, row)
row = Util.shallowCopy(row)
row.used = Util.toBytes(row.used)
return row
end,
enable = function(self)
Event.onTimeout(0, function()
local mounts = {
usr = getDriveInfo('usr/config'),
packages = getDriveInfo('packages'),
}
self:setValues(mounts)
self:draw()
self:sync()
end)
self:setValues({ })
UI.Grid.enable(self)
end,
},
validate = function(self)
target = self.grid:getSelected()
return not not target
end,
},
mounts = UI.WizardPage {
index = 2,
info = UI.TextArea {
x = 3, y = 2, ex = -3, ey = 5,
value = [[Select the target disk. Labeled computers can be inserted into disk drives for larger volumes.]]
},
grid = UI.Grid {
x = 2, y = 7, ex = -2, ey = -2,
columns = {
{ heading = 'Path', key = 'path', textColor = 'yellow', width = 10 },
{ heading = 'Type', key = 'type' },
{ heading = 'Side', key = 'drive' },
{ heading = 'Free', key = 'free', width = 6 },
},
sortColumn = 'path',
getDisplayValues = function (_, row)
row = Util.shallowCopy(row)
row.free = Util.toBytes(row.free)
return row
end,
getRowTextColor = function(self, row)
if row.free < target.used then
return 'lightGray'
end
return UI.Grid.getRowTextColor(self, row)
end,
enable = function(self)
Event.on({ 'disk', 'disk_eject', 'partition_update' }, function()
self:setValues(getDrives(target.drive))
self:draw()
self:sync()
end)
os.queueEvent('partition_update')
self:setValues({ })
UI.Grid.enable(self)
end,
},
validate = function(self)
source = self.grid:getSelected()
if not source then
self:emit({ type = 'notify', message = 'No drive selected' })
elseif source.free < target.used then
self:emit({ type = 'notify', message = 'Insufficient disk space' })
else
return true
end
end,
},
confirm = UI.WizardPage {
index = 3,
info = UI.TextArea {
x = 2, y = 2, ex = -2, ey = -2,
marginTop = 1, marginLeft = 1,
backgroundColor = 'black',
},
enable = function(self)
local fstab = Util.readFile('usr/etc/fstab')
local lines = { }
table.insert(lines, string.format('%sReview changes%s\n', Ansi.yellow, Ansi.reset))
if fstab then
for _,l in ipairs(Util.split(fstab)) do
l = Util.trim(l)
if #l > 0 and l:sub(1, 1) ~= '#' then
local m = Util.matches(l)
if m and m[1] and m[1] == target.path then
table.insert(lines, string.format('Removed from usr/etc/fstab:\n%s%s%s\n', Ansi.red, l, Ansi.reset))
end
end
end
end
local t = target.path
local s = fs.combine(source.path .. '/' .. target.path, '')
if t ~= s then
table.insert(lines, string.format('Added to usr/etc/fstab:\n%s%s linkfs %s%s\n', Ansi.green, t, s, Ansi.reset))
end
table.insert(lines, string.format('Move directory:\n%s/%s -> /%s', Ansi.green, target.mountPoint, s))
self.info:setText(table.concat(lines, '\n'))
UI.WizardPage.enable(self)
end,
validate = function(self)
if self.changesApplied then
return true
end
local fstab = Util.readFile('usr/etc/fstab')
local lines = { }
if fstab then
for _,l in ipairs(Util.split(fstab)) do
table.insert(lines, l)
l = Util.trim(l)
if #l > 0 and l:sub(1, 1) ~= '#' then
local m = Util.matches(l)
if m and m[1] and m[1] == target.path then
fs.unmount(m[1])
table.remove(lines)
end
end
end
end
local t = target.path
local s = fs.combine(source.path .. '/' .. target.path, '')
fs.move('/' .. target.mountPoint, '/' .. s)
if t ~= s then
table.insert(lines, string.format('%s linkfs %s', t, s))
fs.mount(t, 'linkfs', s)
end
Util.writeFile('usr/etc/fstab', table.concat(lines, '\n'))
self.parent.nextButton.text = 'Exit'
self.parent.cancelButton:disable()
self.parent.previousButton:disable()
self.changesApplied = true
self.info:setValue('Changes have been applied')
self.parent:draw()
end,
},
},
notification = UI.Notification { },
eventHandler = function(self, event)
if event.type == 'notify' then
self.notification:error(event.message)
elseif event.type == 'accept' or event.type == 'cancel' then
UI:quit()
end
return UI.Page.eventHandler(self, event)
end,
}
UI:disableEffects()
UI:setPage(page)
UI:start()

View File

@@ -1,3 +1,5 @@
local Alt = require('opus.alternate')
local kernel = _G.kernel
local os = _G.os
local shell = _ENV.shell
@@ -18,11 +20,11 @@ kernel.hook('kernel_focus', function(_, eventData)
end
end
if nextTab == launcherTab then
shell.switchTab(shell.openTab('shell'))
shell.switchTab(shell.openTab(Alt.get('shell')))
else
shell.switchTab(nextTab.uid)
end
end
end)
os.pullEventRaw('kernel_halt')
os.pullEventRaw('kernel_halt')

View File

@@ -5,13 +5,14 @@ local Util = require('opus.util')
local colors = _G.colors
local device = _G.device
local textutils = _G.textutils
local peripheral = _G.peripheral
local multishell = _ENV.multishell
local gridColumns = {}
table.insert(gridColumns, { heading = '#', key = 'id', width = 5, align = 'right' })
table.insert(gridColumns, { heading = 'Port', key = 'portid', width = 5, align = 'right' })
table.insert(gridColumns, { heading = 'Reply', key = 'replyid', width = 5, align = 'right' })
if UI.term.width > 50 then
if UI.defaultDevice.width > 50 then
table.insert(gridColumns, { heading = 'Dist', key = 'distance', width = 6, align = 'right' })
end
table.insert(gridColumns, { heading = 'Msg', key = 'packetStr' })
@@ -41,13 +42,12 @@ local page = UI.Page {
configSlide = UI.SlideOut {
y = -11,
titleBar = UI.TitleBar { title = 'Sniffer Config', event = 'config_close', backgroundColor = colors.black },
titleBar = UI.TitleBar { title = 'Sniffer Config', event = 'config_close' },
accelerators = { ['backspace'] = 'config_close' },
configTabs = UI.Tabs {
y = 2,
filterTab = UI.Tab {
title = 'Filter',
noFill = true,
tabTitle = 'Filter',
filterGridText = UI.Text {
x = 2, y = 2,
value = 'ID filter',
@@ -93,7 +93,7 @@ local page = UI.Page {
},
},
modemTab = UI.Tab {
title = 'Modem',
tabTitle = 'Modem',
channelGrid = UI.ScrollingGrid {
x = 2, y = 2,
width = 12, height = 5,
@@ -130,6 +130,7 @@ local page = UI.Page {
title = 'Packet Information',
event = 'packet_close',
},
backgroundColor = colors.cyan,
accelerators = {
['backspace'] = 'packet_close',
['left'] = 'prev_packet',
@@ -255,7 +256,7 @@ function page.packetSlide:eventHandler(event)
page:setFocus(page.packetGrid)
elseif event.type == 'packet_lua' then
multishell.openTab(_ENV, { path = 'sys/apps/Lua.lua', args = { self.currentPacket.message }, focused = true })
multishell.openTab({ path = 'sys/apps/Lua.lua', args = { self.currentPacket.message }, focused = true })
elseif event.type == 'prev_packet' then
local c = self.currentPacket
@@ -279,7 +280,7 @@ function page.packetSlide:eventHandler(event)
end
function page.packetGrid:getDisplayValues(row)
row = Util.shallowCopy(row)
local row = Util.shallowCopy(row)
row.distance = Util.toBytes(Util.round(row.distance), 2)
return row
end
@@ -306,16 +307,14 @@ end
function page:enable()
modemConfig.modems = {}
Util.each(_G.device, function(dev)
if dev.type == "modem" then
modemConfig.modems[dev.side] = {
type = dev.isWireless() and 'Wireless' or 'Wired',
side = dev.side,
openChannels = { },
device = dev,
loaded = false
}
end
peripheral.find('modem', function(side, dev)
modemConfig.modems[side] = {
type = dev.isWireless() and 'Wireless' or 'Wired',
side = side,
openChannels = { },
device = dev,
loaded = false
}
end)
modemConfig.currentModem = device.wireless_modem and
modemConfig.modems[device.wireless_modem.side] or
@@ -355,7 +354,7 @@ function page:eventHandler(event)
self.packetSlide:show(event.selected)
elseif event.type == 'quit' then
UI:quit()
Event.exitPullEvents()
else return UI.Page.eventHandler(self, event)
end
@@ -385,4 +384,4 @@ if args[1] then
end
UI:setPage(page)
UI:start()
UI:pullEvents()

View File

@@ -6,6 +6,62 @@ local shell = _ENV.shell
UI:configure('System', ...)
local systemPage = UI.Page {
tabs = UI.Tabs {
settings = UI.Tab {
tabTitle = 'Category',
grid = UI.ScrollingGrid {
y = 2,
columns = {
{ heading = 'Name', key = 'name' },
{ heading = 'Description', key = 'description' },
},
sortColumn = 'name',
autospace = true,
},
},
},
notification = UI.Notification(),
accelerators = {
[ 'control-q' ] = 'quit',
},
}
function systemPage.tabs.settings:eventHandler(event)
if event.type == 'grid_select' then
local tab = event.selected.tab
if not systemPage.tabs[tab.tabTitle] then
systemPage.tabs:add({ [ tab.tabTitle ] = tab })
tab:disable()
end
systemPage.tabs:selectTab(tab)
self.parent:draw()
return true
end
end
function systemPage:eventHandler(event)
if event.type == 'quit' then
UI:exitPullEvents()
elseif event.type == 'success_message' then
self.notification:success(event.message)
elseif event.type == 'info_message' then
self.notification:info(event.message)
elseif event.type == 'error_message' then
self.notification:error(event.message)
elseif event.type == 'tab_activate' then
event.activated:focusFirst()
else
return UI.Page.eventHandler(self, event)
end
return true
end
local function loadDirectory(dir)
local plugins = { }
for _, file in pairs(fs.list(dir)) do
@@ -14,69 +70,16 @@ local function loadDirectory(dir)
_G.printError('Error loading: ' .. file)
error(m or 'Unknown error')
elseif s and m then
table.insert(plugins, { tab = m, name = m.title, description = m.description })
table.insert(plugins, { tab = m, name = m.tabTitle, description = m.description })
end
end
return plugins
end
local programDir = fs.getDir(_ENV.arg[0])
local programDir = fs.getDir(shell.getRunningProgram())
local plugins = loadDirectory(fs.combine(programDir, 'system'), { })
local page = UI.Page {
tabs = UI.Tabs {
settings = UI.Tab {
title = 'Category',
grid = UI.ScrollingGrid {
x = 2, y = 2, ex = -2, ey = -2,
columns = {
{ heading = 'Name', key = 'name' },
{ heading = 'Description', key = 'description' },
},
sortColumn = 'name',
autospace = true,
values = plugins,
},
accelerators = {
grid_select = 'category_select',
}
},
},
notification = UI.Notification(),
accelerators = {
[ 'control-q' ] = 'quit',
},
eventHandler = function(self, event)
if event.type == 'quit' then
UI:quit()
systemPage.tabs.settings.grid:setValues(plugins)
elseif event.type == 'category_select' then
local tab = event.selected.tab
if not self.tabs[tab.title] then
self.tabs:add({ [ tab.title ] = tab })
end
self.tabs:selectTab(tab)
return true
elseif event.type == 'success_message' then
self.notification:success(event.message)
elseif event.type == 'info_message' then
self.notification:info(event.message)
elseif event.type == 'error_message' then
self.notification:error(event.message)
elseif event.type == 'tab_activate' then
event.activated:focusFirst()
else
return UI.Page.eventHandler(self, event)
end
return true
end,
}
UI:setPage(page)
UI:start()
UI:setPage(systemPage)
UI:pullEvents()

View File

@@ -3,7 +3,6 @@ local UI = require('opus.ui')
local kernel = _G.kernel
local multishell = _ENV.multishell
local tasks = multishell and multishell.getTabs and multishell.getTabs() or kernel.routines
UI:configure('Tasks', ...)
@@ -12,7 +11,6 @@ local page = UI.Page {
buttons = {
{ text = 'Activate', event = 'activate' },
{ text = 'Terminate', event = 'terminate' },
{ text = 'Inspect', event = 'inspect' },
},
},
grid = UI.ScrollingGrid {
@@ -23,48 +21,44 @@ local page = UI.Page {
{ heading = 'Status', key = 'status' },
{ heading = 'Time', key = 'timestamp' },
},
values = tasks,
values = kernel.routines,
sortColumn = 'uid',
autospace = true,
getDisplayValues = function (_, row)
local elapsed = os.clock()-row.timestamp
return {
uid = row.uid,
title = row.title,
status = row.isDead and 'error' or coroutine.status(row.co),
timestamp = elapsed < 60 and
string.format("%ds", math.floor(elapsed)) or
string.format("%sm", math.floor(elapsed/6)/10),
}
end
},
accelerators = {
[ 'control-q' ] = 'quit',
[ ' ' ] = 'activate',
space = 'activate',
t = 'terminate',
},
eventHandler = function (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)
elseif event.type == 'inspect' then
multishell.openTab(_ENV, {
path = 'sys/apps/Lua.lua',
args = { t },
focused = true,
})
end
end
if event.type == 'quit' then
UI:quit()
end
UI.Page.eventHandler(self, event)
end
}
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)
end
function page.grid:getDisplayValues(row)
local elapsed = os.clock()-row.timestamp
return {
uid = row.uid,
title = row.title,
status = row.isDead and 'error' or coroutine.status(row.co),
timestamp = elapsed < 60 and
string.format("%ds", math.floor(elapsed)) or
string.format("%sm", math.floor(elapsed/6)/10),
}
end
Event.onInterval(1, function()
page.grid:update()
page.grid:draw()
@@ -72,4 +66,4 @@ Event.onInterval(1, function()
end)
UI:setPage(page)
UI:start()
UI:pullEvents()

View File

@@ -1,54 +0,0 @@
local Config = require('opus.config')
local UI = require('opus.ui')
local shell = _ENV.shell
local config = Config.load('version')
if not config.current then
return
end
UI:setPage(UI.Page {
UI.Text {
x = 2, y = 2, ex = -2,
align = 'center',
value = 'Opus has been updated.',
textColor = 'yellow',
},
UI.TextArea {
x = 2, y = 4, ey = -8,
value = config.details,
},
UI.Button {
x = 2, y = -6, width = 21,
event = 'skip',
text = 'Skip this version',
},
UI.Button {
x = 2, y = -4, width = 21,
event = 'remind',
text = 'Remind me tomorrow',
},
UI.Button {
x = 2, y = -2, width = 21,
event = 'update',
text = 'Update'
},
eventHandler = function(self, event)
if event.type == 'skip' then
config.skip = config.current
Config.update('version', config)
UI:quit()
elseif event.type == 'remind' then
UI:quit()
elseif event.type == 'update' then
shell.openForegroundTab('update update')
UI:quit()
end
return UI.Page.eventHandler(self, event)
end,
})
UI:start()

View File

@@ -33,91 +33,111 @@ https://github.com/kepler155c/opus]]
local page = UI.Page {
wizard = UI.Wizard {
ey = -2,
splash = UI.WizardPage {
index = 1,
intro = UI.TextArea {
textColor = colors.yellow,
inactive = true,
x = 3, ex = -3, y = 2, ey = -2,
value = string.format(splashIntro, Ansi.white),
pages = {
splash = UI.WizardPage {
index = 1,
intro = UI.TextArea {
textColor = colors.yellow,
inactive = true,
x = 3, ex = -3, y = 2, ey = -2,
value = string.format(splashIntro, Ansi.white),
},
},
},
label = UI.WizardPage {
index = 2,
labelText = UI.Text {
x = 3, y = 2,
value = 'Label'
label = UI.WizardPage {
index = 2,
labelText = UI.Text {
x = 3, y = 2,
value = 'Label'
},
label = UI.TextEntry {
x = 9, y = 2, ex = -3,
limit = 32,
value = os.getComputerLabel(),
},
intro = UI.TextArea {
textColor = colors.yellow,
inactive = true,
x = 3, ex = -3, y = 4, ey = -3,
value = string.format(labelIntro, Ansi.white),
},
},
label = UI.TextEntry {
x = 9, y = 2, ex = -3,
limit = 32,
value = os.getComputerLabel(),
password = UI.WizardPage {
index = 3,
passwordLabel = UI.Text {
x = 3, y = 2,
value = 'Password'
},
newPass = UI.TextEntry {
x = 12, ex = -3, y = 2,
limit = 32,
mask = true,
shadowText = 'password',
},
--[[
groupLabel = UI.Text {
x = 3, y = 3,
value = 'Group'
},
group = UI.TextEntry {
x = 12, ex = -3, y = 3,
limit = 32,
shadowText = 'network group',
},
]]
intro = UI.TextArea {
textColor = colors.yellow,
inactive = true,
x = 3, ex = -3, y = 5, ey = -3,
value = string.format(passwordIntro, Ansi.white),
},
},
intro = UI.TextArea {
textColor = colors.yellow,
inactive = true,
x = 3, ex = -3, y = 4, ey = -3,
value = string.format(labelIntro, Ansi.white),
packages = UI.WizardPage {
index = 4,
button = UI.Button {
x = 3, y = -3,
text = 'Open Package Manager',
event = 'packages',
},
intro = UI.TextArea {
textColor = colors.yellow,
inactive = true,
x = 3, ex = -3, y = 2, ey = -4,
value = string.format(packagesIntro, Ansi.white),
},
},
validate = function (self)
if self.label.value then
os.setComputerLabel(self.label.value)
end
return true
end,
},
password = UI.WizardPage {
index = 3,
passwordLabel = UI.Text {
x = 3, y = 2,
value = 'Password'
},
newPass = UI.TextEntry {
x = 12, ex = -3, y = 2,
limit = 32,
mask = true,
shadowText = 'password',
},
intro = UI.TextArea {
textColor = colors.yellow,
inactive = true,
x = 3, ex = -3, y = 5, ey = -3,
value = string.format(passwordIntro, Ansi.white),
},
validate = function (self)
if type(self.newPass.value) == "string" and #self.newPass.value > 0 then
Security.updatePassword(SHA.compute(self.newPass.value))
end
return true
end,
},
packages = UI.WizardPage {
index = 4,
button = UI.Button {
x = 3, y = -3,
text = 'Open Package Manager',
event = 'packages',
},
intro = UI.TextArea {
textColor = colors.yellow,
inactive = true,
x = 3, ex = -3, y = 2, ey = -4,
value = string.format(packagesIntro, Ansi.white),
},
},
contributors = UI.WizardPage {
index = 5,
intro = UI.TextArea {
textColor = colors.yellow,
inactive = true,
x = 3, ex = -3, y = 2, ey = -2,
value = string.format(contributorsIntro, Ansi.white, Ansi.yellow, Ansi.white),
contributors = UI.WizardPage {
index = 5,
intro = UI.TextArea {
textColor = colors.yellow,
inactive = true,
x = 3, ex = -3, y = 2, ey = -2,
value = string.format(contributorsIntro, Ansi.white, Ansi.yellow, Ansi.white),
},
},
},
},
notification = UI.Notification { },
}
function page.wizard.pages.label:validate()
os.setComputerLabel(self.label.value)
return true
end
function page.wizard.pages.password:validate()
if type(self.newPass.value) == "string" and #self.newPass.value > 0 then
Security.updatePassword(SHA.compute(self.newPass.value))
end
--[[
if #self.group.value > 0 then
local config = Config.load('os')
config.group = self.group.value
Config.update('os', config)
end
]]
return true
end
function page:eventHandler(event)
if event.type == 'skip' then
self.wizard:emit({ type = 'nextView' })
@@ -129,7 +149,7 @@ function page:eventHandler(event)
shell.openForegroundTab('PackageManager')
elseif event.type == 'wizard_complete' or event.type == 'cancel' then
UI:quit()
UI.exitPullEvents()
else
return UI.Page.eventHandler(self, event)
@@ -138,4 +158,4 @@ function page:eventHandler(event)
end
UI:setPage(page)
UI:start()
UI:pullEvents()

View File

@@ -1,24 +0,0 @@
local Util = require('opus.util')
-- some programs expect to be run in the global scope
-- ie. busted, moonscript
-- create a new environment mimicing pure lua
local fs = _G.fs
local shell = _ENV.shell
local env = Util.shallowCopy(_G)
Util.merge(env, _ENV)
env._G = env
env.arg = { ... }
env.arg[0] = shell.resolveProgram(table.remove(env.arg, 1) or error('file name is required'))
_G.requireInjector(env, fs.getDir(env.arg[0]))
local s, m = Util.run(env, env.arg[0], table.unpack(env.arg))
if not s then
error(m, -1)
end

View File

@@ -1,39 +0,0 @@
local UI = require('opus.ui')
local Util = require('opus.util')
local shell = _ENV.shell
local multishell = _ENV.multishell
-- fileui [--path=path] [--exec=filename] [--title=title]
local page = UI.Page {
fileselect = UI.FileSelect { },
eventHandler = function(self, event)
if event.type == 'select_file' then
self.selected = event.file
UI:quit()
elseif event.type == 'select_cancel' then
UI:quit()
end
return UI.Page.eventHandler(self, event)
end,
}
local _, args = Util.parse(...)
if args.title and multishell then
multishell.setTitle(multishell.getCurrent(), args.title)
end
UI:setPage(page, args.path)
UI:start()
UI.term:setCursorBlink(false)
if args.exec and page.selected then
shell.openForegroundTab(string.format('%s %s', args.exec, page.selected))
return
end
return page.selected

View File

@@ -1,14 +0,0 @@
local SHA = require("opus.crypto.sha2")
local acceptableCharacters = {"0", "1", "2", "3", "4", "5", "6", "7", "8", "9", "a", "b", "c", "d", "e", "f", "g", "h", "i", "j", "k", "l", "m", "n", "o", "p", "q", "r", "s", "t", "u", "v", "w", "x", "y", "z"}
local acceptableCharactersLen = #acceptableCharacters
local password = ""
for _i = 1, 8 do
password = password .. acceptableCharacters[math.random(acceptableCharactersLen)]
end
os.queueEvent("set_otp", SHA.compute(password))
print("Your one-time password is: " .. password)

View File

@@ -1,195 +0,0 @@
local UI = require('opus.ui')
local Util = require('opus.util')
local colors = _G.colors
local multishell = _ENV.multishell
local name = ({ ... })[1] or error('Syntax: inspect COMPONENT')
local events = { }
local page, lastEvent, focused
local function isRelevant(el)
return page.testContainer == el or el.parent and isRelevant(el.parent)
end
local emitter = UI.Window.emit
function UI.Window:emit(event)
if event ~= lastEvent and isRelevant(self) then
lastEvent = event
local t = { }
for k,v in pairs(event) do
if k ~= 'type' and k ~= 'recorded' then
table.insert(t, k .. ':' .. (type(v) == 'table' and (v.UIElement and v.uid or 'tbl') or tostring(v)))
end
end
table.insert(events, 1, { type = event.type, value = table.concat(t, ' '), raw = event })
while #events > 20 do
table.remove(events)
end
page.tabs.events.grid:update()
if page.tabs.events.enabled then
page.tabs.events.grid:draw()
end
end
return emitter(self, event)
end
-- do not load component until emit hook is in place
local component = UI[name] and UI[name]() or error('Invalid component')
if not component.example then
error('No example present')
end
page = UI.Page {
testContainer = UI.Window {
ey = '50%',
testing = component.example(),
},
tabs = UI.Tabs {
backgroundColor = colors.red,
y = '50%',
properties = UI.Tab {
title = 'Properties',
grid = UI.ScrollingGrid {
headerBackgroundColor = colors.red,
sortColumn = 'key',
columns = {
{ heading = 'key', key = 'key' },
{ heading = 'value', key = 'value', }
},
accelerators = {
grid_select = 'edit_property',
},
},
},
methodsTab = UI.Tab {
index = 2,
title = 'Methods',
grid = UI.ScrollingGrid {
ex = '50%',
headerBackgroundColor = colors.red,
sortColumn = 'key',
columns = {
{ heading = 'key', key = 'key' },
},
},
docs = UI.TextArea {
x = '50%',
backgroundColor = colors.black,
},
eventHandler = function (self, event)
if event.type == 'grid_focus_row' and focused then
self.docs:setText(focused:getDoc(event.selected.key) or '')
end
end,
},
events = UI.Tab {
index = 1,
title = 'Events',
UI.MenuBar {
y = -1,
backgroundColor = colors.red,
buttons = {
{ text = 'Clear' },
}
},
grid = UI.ScrollingGrid {
ey = -2,
headerBackgroundColor = colors.red,
values = events,
autospace = true,
columns = {
{ heading = 'type', key = 'type' },
{ heading = 'value', key = 'value', }
},
},
eventHandler = function (self, event)
if event.type == 'button_press' then
Util.clear(self.grid.values)
self.grid:update()
self.grid:draw()
elseif event.type == 'grid_select' then
multishell.openTab(_ENV, {
path = 'sys/apps/Lua.lua',
args = { event.selected.raw },
focused = true,
})
end
end
}
},
editor = UI.SlideOut {
y = -4, height = 4,
backgroundColor = colors.green,
titleBar = UI.TitleBar {
event = 'editor_cancel',
title = 'Enter value',
},
entry = UI.TextEntry {
y = 3, x = 2, ex = 10,
accelerators = {
enter = 'editor_apply',
},
},
},
accelerators = {
['shift-right'] = 'size',
['shift-left' ] = 'size',
['shift-up' ] = 'size',
['shift-down' ] = 'size',
},
eventHandler = function (self, event)
if event.type == 'focus_change' and isRelevant(event.focused) then
focused = event.focused
local t = { }
for k,v in pairs(event.focused) do
table.insert(t, {
key = k,
value = tostring(v),
})
end
self.tabs.properties.grid:setValues(t)
self.tabs.properties.grid:draw()
t = { }
for k,v in pairs(getmetatable(event.focused)) do
if type(v) == 'function' then
table.insert(t, {
key = k,
})
end
end
self.tabs.methodsTab.grid:setValues(t)
self.tabs.methodsTab.grid:draw()
elseif event.type == 'edit_property' then
self.editor.entry.value = event.selected.value
self.editor:show()
elseif event.type == 'editor_cancel' then
self.editor:hide()
elseif event.type == 'editor_apply' then
self.editor:hide()
elseif event.type == 'size' then
local sizing = {
['shift-right'] = { 1, 0 },
['shift-left' ] = { -1, 0 },
['shift-up' ] = { 0, -1 },
['shift-down' ] = { 0, 1 },
}
self.ox = math.max(self.ox + sizing[event.ie.code][1], 1)
self.oy = math.max(self.oy + sizing[event.ie.code][2], 1)
UI.term:clear()
self:resize()
self:draw()
end
return UI.Page.eventHandler(self, event)
end
}
UI:setPage(page)
UI:start()

View File

@@ -1,3 +1,5 @@
_G.requireInjector(_ENV)
local Event = require('opus.event')
local Util = require('opus.util')

View File

@@ -10,30 +10,30 @@ local keyPairs = { }
local function generateKeyPair()
local key = { }
for _ = 1, 32 do
table.insert(key, math.random(0, 0xFF))
table.insert(key, ("%02x"):format(math.random(0, 0xFF)))
end
local privateKey = setmetatable(key, Util.byteArrayMT)
local privateKey = Util.hexToByteArray(table.concat(key))
return privateKey, ECC.publicKey(privateKey)
end
getmetatable(network).__index.getKeyPair = function()
local keys = table.remove(keyPairs)
os.queueEvent('generate_keypair')
if not keys then
return generateKeyPair()
end
return table.unpack(keys)
local keys = table.remove(keyPairs)
os.queueEvent('generate_keypair')
if not keys then
return generateKeyPair()
end
return table.unpack(keys)
end
-- Generate key pairs in the background as this is a time-consuming process
Event.on('generate_keypair', function()
while true do
os.sleep(5)
local timer = Util.timer()
table.insert(keyPairs, { generateKeyPair() })
_G._syslog('Generated keypair in ' .. timer())
if #keyPairs >= 3 then
break
end
end
while true do
os.sleep(5)
local timer = Util.timer()
table.insert(keyPairs, { generateKeyPair() })
_G._syslog('Generated keypair in ' .. timer())
if #keyPairs >= 3 then
break
end
end
end)

View File

@@ -31,14 +31,21 @@ local function snmpConnection(socket)
socket:write('pong')
elseif msg.type == 'script' then
kernel.run(_ENV, {
chunk = msg.args,
title = 'script',
})
local env = setmetatable(Util.shallowCopy(_ENV), { __index = _G })
local fn, err = load(msg.args, 'script', nil, env)
if fn then
kernel.run({
fn = fn,
env = env,
title = 'script',
})
else
_G.printError(err)
end
elseif msg.type == 'scriptEx' then
local s, m = pcall(function()
local env = kernel.makeEnv(_ENV)
local env = setmetatable(Util.shallowCopy(_ENV), { __index = _G })
local fn, m = load(msg.args, 'script', nil, env)
if not fn then
error(m)

View File

@@ -1,9 +1,9 @@
local Alt = require('opus.alternate')
local Event = require('opus.event')
local Socket = require('opus.socket')
local Util = require('opus.util')
local kernel = _G.kernel
local shell = _ENV.shell
local term = _G.term
local window = _G.window
@@ -41,17 +41,18 @@ local function telnetHost(socket, mode)
end
end
local shellThread = kernel.run(_ENV, {
local shellThread = kernel.run({
terminal = win,
window = win,
title = mode .. ' client',
hidden = true,
fn = function()
Util.run(kernel.makeEnv(_ENV), shell.resolveProgram('shell'), table.unpack(termInfo.program))
co = coroutine.create(function()
Util.run(_ENV, Alt.get('shell'), table.unpack(termInfo.program))
if socket.queue then
socket:write(socket.queue)
end
socket:close()
end,
end)
})
Event.addRoutine(function()

View File

@@ -6,22 +6,6 @@ local Util = require('opus.util')
local trustId = '01c3ba27fe01383a03a1785276d99df27c3edcef68fbf231ca'
local oneTimePassword -- nil by default
local function validateData(data, password, dhost)
local s
s, data = pcall(Crypto.decrypt, data, password)
if s and data and type(data) == "table" and data.pk and data.dh == dhost then
local trustList = Util.readTable('usr/.known_hosts') or { }
trustList[data.dh] = data.pk
Util.writeTable('usr/.known_hosts', trustList)
return true
else
return false
end
end
local function trustConnection(socket)
local data = socket:read(2)
if data then
@@ -29,22 +13,17 @@ local function trustConnection(socket)
if not password then
socket:write({ msg = 'No password has been set' })
else
if validateData(data, password, socket.dhost) then
print("Accepted trust from " .. socket.dhost)
local s
s, data = pcall(Crypto.decrypt, data, password)
if s and data and data.pk and data.dh == socket.dhost then
local trustList = Util.readTable('usr/.known_hosts') or { }
trustList[data.dh] = data.pk
Util.writeTable('usr/.known_hosts', trustList)
socket:write({ success = true, msg = 'Trust accepted' })
return
else
socket:write({ msg = 'Invalid password' })
end
if oneTimePassword then
if validateData(data, oneTimePassword, socket.dhost) then
print("Accepted trust from " .. socket.dhost .. "using one-time password")
socket:write({ success = true, msg = 'Trust accepted - this one-time password will not be usable again' })
oneTimePassword = nil -- Make sure nobody can use the one-time password again
return
end
end
socket:write({ msg = 'Invalid password' })
end
end
end
@@ -65,12 +44,3 @@ Event.addRoutine(function()
end
end
end)
Event.addRoutine(function()
while true do
local _event, password = os.pullEvent("set_otp")
oneTimePassword = password
print("got new one-time password")
end
end)

View File

@@ -1,9 +1,6 @@
local BulkGet = require('opus.bulkget')
local Config = require('opus.config')
local Git = require('opus.git')
local LZW = require('opus.compress.lzw')
local Packages = require('opus.packages')
local Tar = require('opus.compress.tar')
local Util = require('opus.util')
local fs = _G.fs
@@ -19,8 +16,9 @@ local function makeSandbox()
end
local function Syntax(msg)
print('Syntax: package list | install [name] ... | update [name] | updateall | uninstall [name]\n')
error(msg)
_G.printError(msg)
print('\nSyntax: Package list | install [name] ... | update [name] | uninstall [name]')
error(0)
end
local function progress(max)
@@ -78,11 +76,6 @@ local function install(name, isUpdate, ignoreDeps)
local packageDir = fs.combine('packages', name)
local list = Git.list(manifest.repository)
-- clear out contents before install/update
-- TODO: figure out whether to run
-- install/uninstall for the package
fs.delete(packageDir)
local showProgress = progress(Util.size(list))
local getList = { }
@@ -103,12 +96,6 @@ local function install(name, isUpdate, ignoreDeps)
if not isUpdate then
runScript(manifest.install)
end
if Config.load('package').compression then
local c = Tar.tar_string(packageDir)
Util.writeFile(packageDir .. '.tar.lzw', LZW.compress(c), 'wb')
fs.delete(packageDir)
end
end
if action == 'list' then
@@ -165,7 +152,6 @@ if action == 'uninstall' then
local packageDir = fs.combine('packages', name)
fs.delete(packageDir)
fs.delete(packageDir .. '.tar.lzw')
print('removed: ' .. packageDir)
return
end

View File

@@ -1,12 +1,21 @@
local parentShell = _ENV.shell
_ENV.shell = { }
local trace = require('opus.trace')
local Util = require('opus.util')
local fs = _G.fs
local settings = _G.settings
local shell = _ENV.shell
local fs = _G.fs
local settings = _G.settings
local shell = _ENV.shell
local sandboxEnv = setmetatable({ }, { __index = _G })
for k,v in pairs(_ENV) do
sandboxEnv[k] = v
end
sandboxEnv.shell = shell
_G.requireInjector(_ENV)
local trace = require('opus.trace')
local Util = require('opus.util')
local DIR = (parentShell and parentShell.dir()) or ""
local PATH = (parentShell and parentShell.path()) or ".:/rom/programs"
@@ -16,16 +25,16 @@ local tCompletionInfo = (parentShell and parentShell.getCompletionInfo()) or {}
local bExit = false
local tProgramStack = {}
local function tokenise(...)
local sLine = table.concat({ ... }, ' ')
local tWords = { }
local function tokenise( ... )
local sLine = table.concat( { ... }, " " )
local tWords = {}
local bQuoted = false
for match in string.gmatch(sLine .. "\"", "(.-)\"") do
for match in string.gmatch( sLine .. "\"", "(.-)\"" ) do
if bQuoted then
table.insert(tWords, match)
table.insert( tWords, match )
else
for m in string.gmatch(match, "[^ \t]+") do
table.insert(tWords, m)
for m in string.gmatch( match, "[^ \t]+" ) do
table.insert( tWords, m )
end
end
bQuoted = not bQuoted
@@ -34,75 +43,37 @@ local function tokenise(...)
return tWords
end
local defaultHandlers = {
function(env, command, args)
return command:match("^(https?:)") and {
title = fs.getName(command),
path = command,
args = args,
load = Util.loadUrl,
env = env,
}
end,
function(env, command, args)
command = env.shell.resolveProgram(command)
or error('No such program')
_G.requireInjector(env, fs.getDir(command))
return {
title = fs.getName(command):match('([^%.]+)'),
path = command,
args = args,
load = loadfile,
env = env,
}
end,
}
function shell.getHandlers()
if parentShell and parentShell.getHandlers then
return parentShell.getHandlers()
end
return defaultHandlers
end
local handlers = shell.getHandlers()
function shell.registerHandler(fn)
table.insert(handlers, 1, fn)
end
local function handleCommand(env, command, args)
for _,v in pairs(handlers) do
local pi = v(env, command, args)
if pi then
return pi
end
end
end
local function run(...)
local function run(env, ...)
local args = tokenise(...)
if #args == 0 then
error('No such program')
local command = table.remove(args, 1) or error('No such program')
local isUrl = not not command:match("^(https?:)")
local path, loadFn
if isUrl then
path = command
loadFn = Util.loadUrl
else
path = shell.resolveProgram(command) or error('No such program')
loadFn = loadfile
end
local pi = handleCommand(shell.makeEnv(_ENV), table.remove(args, 1), args)
local O_v_O, err = pi.load(pi.path, pi.env)
if not O_v_O then
error(err, -1)
local fn, err = loadFn(path, env)
if not fn then
error(err)
end
if _ENV.multishell then
_ENV.multishell.setTitle(_ENV.multishell.getCurrent(), pi.title)
_ENV.multishell.setTitle(_ENV.multishell.getCurrent(), fs.getName(path):match('([^%.]+)'))
end
tProgramStack[#tProgramStack + 1] = pi
if isUrl then
tProgramStack[#tProgramStack + 1] = path -- path:match("^https?://([^/:]+:?[0-9]*/?.*)$")
else
tProgramStack[#tProgramStack + 1] = path
end
pi.env[ "arg" ] = { [0] = pi.path, table.unpack(pi.args) }
local r = { O_v_O(table.unpack(pi.args)) }
env[ "arg" ] = { [0] = path, table.unpack(args) }
local r = { fn(table.unpack(args)) }
tProgramStack[#tProgramStack] = nil
@@ -117,7 +88,10 @@ function shell.run(...)
oldTitle = _ENV.multishell.getTitle(_ENV.multishell.getCurrent())
end
local r = { trace(run, ...) }
local env = setmetatable(Util.shallowCopy(sandboxEnv), { __index = _G })
_G.requireInjector(env)
local r = { trace(run, env, ...) }
if _ENV.multishell then
_ENV.multishell.setTitle(_ENV.multishell.getCurrent(), oldTitle or 'shell')
@@ -131,14 +105,7 @@ function shell.exit()
end
function shell.dir() return DIR end
function shell.setDir(d)
d = fs.combine(d, '')
if not fs.isDir(d) then
error("Not a directory", 2)
end
DIR = d
end
function shell.setDir(d) DIR = d end
function shell.path() return PATH end
function shell.setPath(p) PATH = p end
@@ -151,41 +118,49 @@ function shell.resolve( _sPath )
end
end
function shell.resolveProgram(_sCommand)
function shell.resolveProgram( _sCommand )
if tAliases[_sCommand] ~= nil then
_sCommand = tAliases[_sCommand]
end
local function check(f)
return fs.exists(f) and not fs.isDir(f) and f
if _sCommand:match("^(https?:)") then
return _sCommand
end
local function inPath()
-- Otherwise, look on the path variable
for sPath in string.gmatch(PATH or '', "[^:]+") do
sPath = fs.combine(sPath, _sCommand )
if check(sPath) then
return sPath
end
if check(sPath .. '.lua') then
return sPath .. '.lua'
end
local path = shell.resolve(_sCommand)
if fs.exists(path) and not fs.isDir(path) then
return path
end
if fs.exists(path .. '.lua') then
return path .. '.lua'
end
-- If the path is a global path, use it directly
local sStartChar = string.sub( _sCommand, 1, 1 )
if sStartChar == "/" or sStartChar == "\\" then
local sPath = fs.combine( "", _sCommand )
if fs.exists( sPath ) and not fs.isDir( sPath ) then
return sPath
end
return nil
end
-- Otherwise, look on the path variable
for sPath in string.gmatch(PATH or '', "[^:]+") do
sPath = fs.combine(sPath, _sCommand )
if fs.exists( sPath ) and not fs.isDir( sPath ) then
return sPath
end
if fs.exists(sPath .. '.lua') then
return sPath .. '.lua'
end
end
-- so... even if you are in the rom directory and you run:
-- 'packages/common/edit.lua', allow this even though it
-- does not use a leading slash. Ideally, fs.combine would
-- provide the leading slash... but it does not.
return (not _sCommand:find('/')) and inPath()
or check(shell.resolve(_sCommand))
or check(shell.resolve(_sCommand) .. '.lua')
or check(_sCommand)
or check(_sCommand .. '.lua')
-- Not found
return nil
end
function shell.programs(_bIncludeHidden)
local tItems = { }
function shell.programs( _bIncludeHidden )
local tItems = {}
-- Add programs from the path
for sPath in string.gmatch(PATH, "[^:]+") do
@@ -202,84 +177,98 @@ function shell.programs(_bIncludeHidden)
end
-- Sort and return
local tItemList = { }
for sItem in pairs(tItems) do
table.insert(tItemList, sItem)
local tItemList = {}
for sItem in pairs( tItems ) do
table.insert( tItemList, sItem )
end
table.sort(tItemList)
table.sort( tItemList )
return tItemList
end
function shell.completeProgram(sLine)
if #sLine > 0 and string.sub(sLine, 1, 1) == '/' then
local function completeProgram( sLine )
if #sLine > 0 and string.sub( sLine, 1, 1 ) == "/" then
-- Add programs from the root
return fs.complete(sLine, '', true, false)
end
return fs.complete( sLine, "", true, false )
else
local tResults = {}
local tSeen = {}
local tResults = { }
local tSeen = { }
-- Add aliases
for sAlias in pairs( tAliases ) do
if #sAlias > #sLine and string.sub(sAlias, 1, #sLine) == sLine then
local sResult = string.sub(sAlias, #sLine + 1)
if not tSeen[sResult] then
table.insert(tResults, sResult .. ' ')
tSeen[sResult] = true
-- Add aliases
for sAlias in pairs( tAliases ) do
if #sAlias > #sLine and string.sub( sAlias, 1, #sLine ) == sLine then
local sResult = string.sub( sAlias, #sLine + 1 )
if not tSeen[ sResult ] then
table.insert( tResults, sResult )
tSeen[ sResult ] = true
end
end
end
end
-- Add programs from the path
local tPrograms = shell.programs()
for n=1,#tPrograms do
local sProgram = tPrograms[n]
if #sProgram >= #sLine and string.sub(sProgram, 1, #sLine) == sLine then
local sResult = string.sub(sProgram, #sLine + 1)
if not tSeen[sResult] then
table.insert(tResults, sResult .. ' ')
tSeen[sResult] = true
-- Add programs from the path
local tPrograms = shell.programs()
for n=1,#tPrograms do
local sProgram = tPrograms[n]
if #sProgram > #sLine and string.sub( sProgram, 1, #sLine ) == sLine then
local sResult = string.sub( sProgram, #sLine + 1 )
if not tSeen[ sResult ] then
table.insert( tResults, sResult )
tSeen[ sResult ] = true
end
end
end
end
-- Sort and return
table.sort(tResults)
return tResults
-- Sort and return
table.sort( tResults )
return tResults
end
end
local function completeProgramArgument( sProgram, nArgument, sPart, tPreviousParts )
local tInfo = tCompletionInfo[ sProgram ]
if tInfo then
return tInfo.fnComplete( shell, nArgument, sPart, tPreviousParts )
end
return nil
end
function shell.complete(sLine)
local tWords = tokenise(sLine)
local nIndex = #tWords
if string.sub(sLine, #sLine, #sLine) == ' ' and #Util.trim(sLine) > 0 then
nIndex = nIndex + 1
end
if nIndex == 0 then
return fs.complete('', shell.dir(), true, false)
elseif nIndex == 1 then
local results = shell.completeProgram(tWords[1] or '')
for _, v in pairs(fs.complete(table.concat(tWords, ' '), shell.dir(), true, false)) do
table.insert(results, v)
if #sLine > 0 then
local tWords = tokenise( sLine )
local nIndex = #tWords
if string.sub( sLine, #sLine, #sLine ) == " " then
nIndex = nIndex + 1
end
return results
if nIndex == 1 then
local sBit = tWords[1] or ""
local sPath = shell.resolveProgram( sBit )
if tCompletionInfo[ sPath ] then
return { " " }
else
local tResults = completeProgram( sBit )
for n=1,#tResults do
local sResult = tResults[n]
local cPath = shell.resolveProgram( sBit .. sResult )
if tCompletionInfo[ cPath ] then
tResults[n] = sResult .. " "
end
end
return tResults
end
else
local sPath = shell.resolveProgram(tWords[1])
local sPart = tWords[nIndex] or ''
local tPreviousParts = tWords
tPreviousParts[nIndex] = nil
local results
local tInfo = tCompletionInfo[sPath]
if tInfo then
results = tInfo.fnComplete(shell, nIndex - 1, sPart, tPreviousParts)
elseif nIndex > 1 then
local sPath = shell.resolveProgram( tWords[1] )
local sPart = tWords[nIndex] or ""
local tPreviousParts = tWords
tPreviousParts[nIndex] = nil
return completeProgramArgument( sPath , nIndex - 1, sPart, tPreviousParts )
end
return results and #results > 0 and results
or fs.complete(sPart, shell.dir(), true, false)
end
end
function shell.completeProgram( sProgram )
return completeProgram( sProgram )
end
function shell.setCompletionFunction(sProgram, fnComplete)
tCompletionInfo[sProgram] = { fnComplete = fnComplete }
end
@@ -289,25 +278,23 @@ function shell.getCompletionInfo()
end
function shell.getRunningProgram()
return tProgramStack[#tProgramStack] and tProgramStack[#tProgramStack].path
end
function shell.getRunningInfo()
return tProgramStack[#tProgramStack]
end
-- convenience function for making a runnable env
function shell.makeEnv(env, dir)
env = setmetatable(Util.shallowCopy(env), { __index = _G })
_G.requireInjector(env, dir)
return env
function shell.setEnv(name, value)
_ENV[name] = value
sandboxEnv[name] = value
end
function shell.setAlias(_sCommand, _sProgram)
function shell.getEnv()
return sandboxEnv
end
function shell.setAlias( _sCommand, _sProgram )
tAliases[_sCommand] = _sProgram
end
function shell.clearAlias(_sCommand)
function shell.clearAlias( _sCommand )
tAliases[_sCommand] = nil
end
@@ -326,6 +313,7 @@ function shell.newTab(tabInfo, ...)
if path then
tabInfo.path = path
tabInfo.env = Util.shallowCopy(sandboxEnv)
tabInfo.args = args
tabInfo.title = fs.getName(path):match('([^%.]+)')
@@ -333,21 +321,25 @@ function shell.newTab(tabInfo, ...)
table.insert(tabInfo.args, 1, tabInfo.path)
tabInfo.path = 'sys/apps/shell.lua'
end
return _ENV.multishell.openTab(_ENV, tabInfo)
return _ENV.multishell.openTab(tabInfo)
end
return nil, 'No such program'
end
if not _ENV.multishell then
function shell.newTab()
error('Multishell is not available')
function shell.openTab( ... )
-- needs to use multishell.launch .. so we can run with stock multishell
local tWords = tokenise( ... )
local sCommand = tWords[1]
if sCommand then
local sPath = shell.resolveProgram(sCommand)
if sPath == "sys/apps/shell.lua" then
return _ENV.multishell.launch(Util.shallowCopy(sandboxEnv), sPath, table.unpack(tWords, 2))
else
return _ENV.multishell.launch(Util.shallowCopy(sandboxEnv), "sys/apps/shell.lua", sCommand, table.unpack(tWords, 2))
end
end
end
function shell.openTab(...)
return shell.newTab({ }, ...)
end
function shell.openForegroundTab( ... )
return shell.newTab({ focused = true }, ...)
end
@@ -362,7 +354,10 @@ end
local tArgs = { ... }
if #tArgs > 0 then
return run(...)
local env = setmetatable(Util.shallowCopy(sandboxEnv), { __index = _G })
_G.requireInjector(env)
return run(env, ...)
end
local Config = require('opus.config')
@@ -379,16 +374,23 @@ local textutils = _G.textutils
local oldTerm
local terminal = term.current()
local _len = string.len
local _rep = string.rep
local _sub = string.sub
if not terminal.scrollUp then
terminal = Terminal.window(term.current())
terminal.setMaxScroll(200)
oldTerm = term.redirect(terminal)
end
local config = {
color = {
textColor = colors.white,
commandTextColor = colors.yellow,
directoryTextColor = colors.orange,
directoryBackgroundColor = colors.black,
promptTextColor = colors.blue,
promptBackgroundColor = colors.black,
directoryColor = colors.green,
fileColor = colors.white,
backgroundColor = colors.black,
@@ -405,15 +407,43 @@ if not _colors.backgroundColor then
_colors.fileColor = colors.white
end
if not terminal.scrollUp then
terminal = Terminal.window(term.current())
terminal.setMaxScroll(200)
oldTerm = term.redirect(terminal)
term.setBackgroundColor(_colors.backgroundColor)
term.clear()
local palette = { }
for n = 1, 16 do
palette[2 ^ (n - 1)] = _sub("0123456789abcdef", n, n)
end
local palette = terminal.canvas.palette
if not term.isColor() then
_colors = { }
for k, v in pairs(config.color) do
_colors[k] = Terminal.colorToGrayscale(v)
end
for n = 1, 16 do
palette[2 ^ (n - 1)] = _sub("088888878877787f", n, n)
end
end
local function autocompleteArgument(program, words)
local word = ''
if #words > 1 then
word = words[#words]
end
local tInfo = tCompletionInfo[program]
return tInfo.fnComplete(shell, #words - 1, word, words)
end
local function autocompleteAnything(line, words)
local results = shell.complete(line)
if results and #results == 0 and #words == 1 then
results = nil
end
if not results then
results = fs.complete(words[#words] or '', shell.dir(), true, false)
end
return results
end
local function autocomplete(line)
local words = { }
@@ -427,7 +457,14 @@ local function autocomplete(line)
words = { '' }
end
local results = shell.complete(line) or { }
local results
local program = shell.resolveProgram(words[1])
if tCompletionInfo[program] then
results = autocompleteArgument(program, words) or { }
else
results = autocompleteAnything(line, words) or { }
end
Util.filterInplace(results, function(f)
return not Util.key(results, f .. '/')
@@ -440,8 +477,8 @@ local function autocomplete(line)
if #results == 1 then
words[#words] = results[1]
return table.concat(words, ' ')
elseif #results > 1 then
local function someComplete()
-- ugly (complete as much as possible)
local word = words[#words] or ''
@@ -450,16 +487,16 @@ local function autocomplete(line)
local ch
for _,f in ipairs(results) do
if #f < i then
words[#words] = _sub(f, 1, i - 1)
words[#words] = string.sub(f, 1, i - 1)
return table.concat(words, ' ')
end
if not ch then
ch = _sub(f, i, i)
elseif _sub(f, i, i) ~= ch then
ch = string.sub(f, i, i)
elseif string.sub(f, i, i) ~= ch then
if i == #word + 1 then
return
end
words[#words] = _sub(f, 1, i - 1)
words[#words] = string.sub(f, 1, i - 1)
return table.concat(words, ' ')
end
end
@@ -502,9 +539,10 @@ local function autocomplete(line)
local tw = term.getSize()
local nMaxLen = tw / 8
for _,sItem in pairs(results) do
nMaxLen = math.max(_len(sItem) + 1, nMaxLen)
nMaxLen = math.max(string.len(sItem) + 1, nMaxLen)
end
local nCols = math.floor(tw / nMaxLen)
local w = term.getSize()
local nCols = math.floor(w / nMaxLen)
if #tDirs < nCols then
for _ = #tDirs + 1, nCols do
table.insert(tDirs, '')
@@ -519,9 +557,11 @@ local function autocomplete(line)
end
term.setTextColour(_colors.promptTextColor)
term.setBackgroundColor(_colors.promptBackgroundColor)
term.write("$ " )
term.setTextColour(_colors.commandTextColor)
term.setBackgroundColor(_colors.backgroundColor)
return line
end
end
@@ -546,18 +586,18 @@ local function shellRead(history)
end
local _,cy = term.getCursorPos()
term.setCursorPos(3, cy)
entry.value = entry.value or ''
local filler = #entry.value < lastLen
and _rep(' ', lastLen - #entry.value)
and string.rep(' ', lastLen - #entry.value)
or ''
local str = _sub(entry.value, entry.scroll + 1, entry.width + entry.scroll) .. filler
local str = string.sub(entry.value, entry.scroll + 1, entry.width + entry.scroll) .. filler
local fg = _rep(palette[_colors.commandTextColor], #str)
local bg = _rep(palette[_colors.backgroundColor], #str)
if entry.mark.active then
bg = _rep('f', entry.mark.x) ..
_rep('7', entry.mark.ex - entry.mark.x) ..
_rep('f', #entry.value - entry.mark.ex + #filler + 1)
bg = _sub(bg, entry.scroll + 1, entry.scroll + #str)
local sx = entry.mark.x - entry.scroll + 1
local ex = entry.mark.ex - entry.scroll + 1
bg = string.rep('f', sx - 1) ..
string.rep('7', ex - sx) ..
string.rep('f', #str - ex + 1)
end
term.blit(str, fg, bg)
updateCursor()
@@ -595,7 +635,6 @@ local function shellRead(history)
redraw()
elseif ie.code == 'tab' then
entry.value = entry.value or ''
if entry.pos == #entry.value then
local cline = autocomplete(entry.value)
if cline then
@@ -609,14 +648,8 @@ local function shellRead(history)
end
end
elseif ie.code == 'control-l' then
term.clear()
term.setCursorPos(1, 0) -- Y:0 ?
break
else
entry:process(ie)
entry.value = entry.value or ''
if entry.textChanged then
redraw()
elseif entry.posChanged then
@@ -625,7 +658,6 @@ local function shellRead(history)
end
elseif event == "term_resize" then
terminal.reposition(1, 1, oldTerm.getSize())
entry.width = term.getSize() - 3
entry:updateScroll()
redraw()
@@ -633,26 +665,30 @@ local function shellRead(history)
end
print()
term.setCursorBlink(false)
return entry.value or ''
term.setCursorBlink( false )
return entry.value
end
local history = History.load('usr/.shell_history', 100)
local history = History.load('usr/.shell_history', 25)
term.setBackgroundColor(_colors.backgroundColor)
term.clear()
if settings.get("motd.enable") then
if settings.get("motd.enabled") then
shell.run("motd")
end
while not bExit do
if config.displayDirectory then
term.setTextColour(_colors.directoryTextColor)
term.setBackgroundColor(_colors.directoryBackgroundColor)
print('==' .. os.getComputerLabel() .. ':/' .. DIR)
end
term.setTextColour(_colors.promptTextColor)
term.setBackgroundColor(_colors.promptBackgroundColor)
term.write("$ " )
term.setTextColour(_colors.commandTextColor)
term.setBackgroundColor(_colors.backgroundColor)
local sLine = shellRead(history)
if bExit then -- terminated
break
@@ -664,11 +700,6 @@ while not bExit do
term.setTextColour(_colors.textColor)
if #sLine > 0 then
local result, err = shell.run(sLine)
local cx = term.getCursorPos()
if cx ~= 1 then
print()
end
term.setBackgroundColor(_colors.backgroundColor)
if not result and err then
_G.printError(err)
end

View File

@@ -4,7 +4,7 @@ local UI = require('opus.ui')
local kernel = _G.kernel
local aliasTab = UI.Tab {
title = 'Aliases',
tabTitle = 'Aliases',
description = 'Shell aliases',
alias = UI.TextEntry {
x = 2, y = 2, ex = -2,
@@ -13,13 +13,14 @@ local aliasTab = UI.Tab {
},
path = UI.TextEntry {
y = 3, x = 2, ex = -2,
limit = 256,
shadowText = 'Program path',
accelerators = {
enter = 'new_alias',
},
},
grid = UI.Grid {
x = 2, y = 5, ex = -2, ey = -2,
y = 5,
sortColumn = 'alias',
columns = {
{ heading = 'Alias', key = 'alias' },

View File

@@ -0,0 +1,80 @@
local Array = require('opus.array')
local Config = require('opus.config')
local UI = require('opus.ui')
local colors = _G.colors
local tab = UI.Tab {
tabTitle = 'Preferred',
description = 'Select preferred applications',
apps = UI.ScrollingGrid {
x = 2, y = 2,
ex = 12, ey = -3,
columns = {
{ key = 'name' },
},
sortColumn = 'name',
disableHeader = true,
},
choices = UI.Grid {
x = 14, y = 2,
ex = -2, ey = -3,
disableHeader = true,
columns = {
{ key = 'file' },
}
},
statusBar = UI.StatusBar {
values = 'Double-click to set as preferred'
},
}
function tab.choices:getRowTextColor(row)
if row == self.values[1] then
return colors.yellow
end
return UI.Grid.getRowTextColor(self, row)
end
function tab:updateChoices()
local app = self.apps:getSelected().name
local choices = { }
for _, v in pairs(self.config[app]) do
table.insert(choices, { file = v })
end
self.choices:setValues(choices)
self.choices:draw()
end
function tab:enable()
self.config = Config.load('alternate')
local apps = { }
for k, _ in pairs(self.config) do
table.insert(apps, { name = k })
end
self.apps:setValues(apps)
self:updateChoices()
UI.Tab.enable(self)
end
function tab:eventHandler(event)
if event.type == 'grid_focus_row' and event.element == self.apps then
self:updateChoices()
elseif event.type == 'grid_select' and event.element == self.choices then
local app = self.apps:getSelected().name
Array.removeByValue(self.config[app], event.selected.file)
table.insert(self.config[app], 1, event.selected.file)
self:updateChoices()
Config.update('alternate', self.config)
else
return UI.Tab.eventHandler(self, event)
end
return true
end
return tab

View File

@@ -2,17 +2,18 @@ local Ansi = require('opus.ansi')
local Config = require('opus.config')
local UI = require('opus.ui')
local colors = _G.colors
-- -t80x30
if _G.http.websocket then
local config = Config.load('cloud')
local tab = UI.Tab {
title = 'Cloud',
tabTitle = 'Cloud',
description = 'Cloud Catcher options',
[1] = UI.Window {
x = 2, y = 2, ex = -2, ey = 4,
},
key = UI.TextEntry {
x = 3, ex = -3, y = 3,
x = 3, ex = -3, y = 2,
limit = 32,
value = config.key,
shadowText = 'Cloud key',
@@ -21,15 +22,14 @@ if _G.http.websocket then
},
},
button = UI.Button {
x = -8, ex = -2, y = -2,
text = 'Apply',
x = 3, y = 4,
text = 'Update',
event = 'update_key',
},
labelText = UI.TextArea {
x = 2, ex = -2, y = 5, ey = -4,
textColor = 'yellow',
backgroundColor = 'black',
marginLeft = 1, marginRight = 1, marginTop = 1,
x = 3, ex = -3, y = 6,
textColor = colors.yellow,
marginLeft = 0, marginRight = 0,
value = string.format(
[[Use a non-changing cloud key. Note that only a single computer can use this session at one time.
To obtain a key, visit:
@@ -42,7 +42,7 @@ To obtain a key, visit:
function tab:eventHandler(event)
if event.type == 'update_key' then
if self.key.value then
if #self.key.value > 0 then
config.key = self.key.value
else
config.key = nil

View File

@@ -16,12 +16,12 @@ local NftImages = {
}
local tab = UI.Tab {
title = 'Disks Usage',
tabTitle = 'Disks Usage',
description = 'Visualise HDD and disks usage',
drives = UI.ScrollingGrid {
x = 2, y = 2,
ex = '47%', ey = -8,
x = 2, y = 1,
ex = '47%', ey = -7,
columns = {
{ heading = 'Drive', key = 'name' },
{ heading = 'Side' ,key = 'side', textColor = colors.yellow }
@@ -30,7 +30,7 @@ local tab = UI.Tab {
},
infos = UI.Grid {
x = '52%', y = 2,
ex = -2, ey = -8,
ex = -2, ey = -4,
disableHeader = true,
unfocusedBackgroundSelectedColor = colors.black,
inactive = true,
@@ -40,23 +40,18 @@ local tab = UI.Tab {
{ key = 'value', align = 'right', textColor = colors.yellow },
}
},
[1] = UI.Window {
x = 2, y = -6, ex = -2, ey = -2,
backgroundColor = colors.black,
},
progress = UI.ProgressBar {
x = 11, y = -3,
ex = -3,
x = 11, y = -2,
ex = -2,
},
percentage = UI.Text {
y = -4, width = 5,
x = 12,
--align = 'center',
backgroundColor = colors.black,
x = 11, y = -3,
ex = '47%',
align = 'center',
},
icon = UI.NftImage {
x = 2, y = -6, ey = -2,
backgroundColor = colors.black,
x = 2, y = -5,
image = NFT.parse(NftImages.blank)
},
}
@@ -136,17 +131,6 @@ function tab:enable()
self:updateDrives()
self:updateInfo()
UI.Tab.enable(self)
self.handler = Event.on({ 'disk', 'disk_eject' }, function()
os.sleep(1)
tab:updateDrives()
tab:updateInfo()
tab:sync()
end)
end
function tab:disable()
Event.off(self.handler)
UI.Tab.disable(self)
end
function tab:eventHandler(event)
@@ -158,4 +142,11 @@ function tab:eventHandler(event)
return true
end
Event.on({ 'disk', 'disk_eject' }, function()
os.sleep(1)
tab:updateDrives()
tab:updateInfo()
tab:sync()
end)
return tab

View File

@@ -4,11 +4,11 @@ local colors = _G.colors
local peripheral = _G.peripheral
local settings = _G.settings
return peripheral.find('monitor') and UI.Tab {
title = 'Kiosk',
local tab = UI.Tab {
tabTitle = 'Kiosk',
description = 'Kiosk options',
form = UI.Form {
x = 2, y = 2, ex = -2, ey = 5,
x = 2, ex = -2,
manualControls = true,
monitor = UI.Chooser {
formLabel = 'Monitor', formKey = 'monitor',
@@ -22,36 +22,41 @@ return peripheral.find('monitor') and UI.Tab {
},
help = 'Adjust text scaling',
},
labelText = UI.TextArea {
x = 2, ex = -2, y = 5,
textColor = colors.yellow,
value = 'Settings apply to kiosk mode selected during startup'
},
},
labelText = UI.TextArea {
x = 2, ex = -2, y = 7, ey = -2,
textColor = colors.yellow,
backgroundColor = colors.black,
value = 'Settings apply to kiosk mode selected during startup'
},
enable = function(self)
local choices = { }
peripheral.find('monitor', function(side)
table.insert(choices, { name = side, value = side })
end)
self.form.monitor.choices = choices
self.form.monitor.value = settings.get('kiosk.monitor')
self.form.textScale.value = settings.get('kiosk.textscale')
UI.Tab.enable(self)
end,
eventHandler = function(self, event)
if event.type == 'choice_change' then
if self.form.monitor.value then
settings.set('kiosk.monitor', self.form.monitor.value)
end
if self.form.textScale.value then
settings.set('kiosk.textscale', self.form.textScale.value)
end
settings.save('.settings')
end
end
}
function tab:enable()
local choices = { }
peripheral.find('monitor', function(side)
table.insert(choices, { name = side, value = side })
end)
self.form.monitor.choices = choices
self.form.monitor.value = settings.get('kiosk.monitor')
self.form.textScale.value = settings.get('kiosk.textscale')
UI.Tab.enable(self)
end
function tab:eventHandler(event)
if event.type == 'choice_change' then
if self.form.monitor.value then
settings.set('kiosk.monitor', self.form.monitor.value)
end
if self.form.textScale.value then
settings.set('kiosk.textscale', self.form.textScale.value)
end
settings.save('.settings')
end
end
if peripheral.find('monitor') then
return tab
end

View File

@@ -4,26 +4,23 @@ local Util = require('opus.util')
local fs = _G.fs
local os = _G.os
return UI.Tab {
title = 'Label',
local labelTab = UI.Tab {
tabTitle = 'Label',
description = 'Set the computer label',
labelText = UI.Text {
x = 3, y = 3,
x = 3, y = 2,
value = 'Label'
},
label = UI.TextEntry {
x = 9, y = 3, ex = -4,
x = 9, y = 2, ex = -4,
limit = 32,
value = os.getComputerLabel(),
accelerators = {
enter = 'update_label',
},
},
[1] = UI.Window {
x = 2, y = 2, ex = -2, ey = 4,
},
grid = UI.ScrollingGrid {
x = 2, y = 5, ex = -2, ey = -2,
y = 3,
values = {
{ name = '', value = '' },
{ name = 'CC version', value = Util.getVersion() },
@@ -33,18 +30,20 @@ return UI.Tab {
{ name = 'Computer ID', value = tostring(os.getComputerID()) },
{ name = 'Day', value = tostring(os.day()) },
},
disableHeader = true,
inactive = true,
columns = {
{ key = 'name', width = 12 },
{ key = 'value', textColor = colors.yellow },
{ key = 'value' },
},
},
eventHandler = function(self, event)
if event.type == 'update_label' and self.label.value then
os.setComputerLabel(self.label.value)
self:emit({ type = 'success_message', message = 'Label updated' })
return true
end
end,
}
function labelTab:eventHandler(event)
if event.type == 'update_label' then
os.setComputerLabel(self.label.value)
self:emit({ type = 'success_message', message = 'Label updated' })
return true
end
end
return labelTab

View File

@@ -7,17 +7,14 @@ local fs = _G.fs
local config = Config.load('multishell')
local tab = UI.Tab {
title = 'Launcher',
tabTitle = 'Launcher',
description = 'Set the application launcher',
[1] = UI.Window {
x = 2, y = 2, ex = -2, ey = 5,
},
launcherLabel = UI.Text {
x = 3, y = 3,
x = 3, y = 2,
value = 'Launcher',
},
launcher = UI.Chooser {
x = 13, y = 3, width = 12,
x = 13, y = 2, width = 12,
choices = {
{ name = 'Overview', value = 'sys/apps/Overview.lua' },
{ name = 'Shell', value = 'sys/apps/ShellLauncher.lua' },
@@ -25,19 +22,18 @@ local tab = UI.Tab {
},
},
custom = UI.TextEntry {
x = 13, ex = -3, y = 4,
x = 13, ex = -3, y = 3,
limit = 128,
shadowText = 'File name',
},
button = UI.Button {
x = -8, ex = -2, y = -2,
text = 'Apply',
x = 3, y = 5,
text = 'Update',
event = 'update',
},
labelText = UI.TextArea {
x = 2, ex = -2, y = 6, ey = -4,
backgroundColor = colors.black,
x = 3, ex = -3, y = 7,
textColor = colors.yellow,
marginLeft = 1, marginRight = 1, marginTop = 1,
value = 'Choose an application launcher',
},
}

View File

@@ -2,61 +2,59 @@ local Ansi = require('opus.ansi')
local Config = require('opus.config')
local UI = require('opus.ui')
local colors = _G.colors
local device = _G.device
return UI.Tab {
title = 'Network',
local tab = UI.Tab {
tabTitle = 'Network',
description = 'Networking options',
info = UI.TextArea {
x = 2, y = 5, ex = -2, ey = -2,
backgroundColor = colors.black,
marginLeft = 1, marginRight = 1, marginTop = 1,
x = 3, y = 4,
value = string.format(
[[%sSet the primary modem used for wireless communications.%s
Reboot to take effect.]], Ansi.yellow, Ansi.reset)
},
[1] = UI.Window {
x = 2, y = 2, ex = -2, ey = 4,
},
label = UI.Text {
x = 3, y = 3,
x = 3, y = 2,
value = 'Modem',
},
modem = UI.Chooser {
x = 10, ex = -3, y = 3,
x = 10, ex = -3, y = 2,
nochoice = 'auto',
},
enable = function(self)
local width = 7
local choices = {
{ name = 'auto', value = 'auto' },
{ name = 'disable', value = 'none' },
}
}
for k,v in pairs(device) do
if v.isWireless and v.isWireless() and k ~= 'wireless_modem' then
table.insert(choices, { name = k, value = v.name })
width = math.max(width, #k)
end
end
function tab:enable()
local width = 7
local choices = {
{ name = 'auto', value = 'auto' },
{ name = 'disable', value = 'none' },
}
self.modem.choices = choices
--self.modem.width = width + 4
local config = Config.load('os')
self.modem.value = config.wirelessModem or 'auto'
UI.Tab.enable(self)
end,
eventHandler = function(self, event)
if event.type == 'choice_change' then
local config = Config.load('os')
config.wirelessModem = self.modem.value
Config.update('os', config)
self:emit({ type = 'success_message', message = 'reboot to take effect' })
return true
for k,v in pairs(device) do
if v.isWireless and v.isWireless() and k ~= 'wireless_modem' then
table.insert(choices, { name = k, value = v.name })
width = math.max(width, #k)
end
end
}
self.modem.choices = choices
--self.modem.width = width + 4
local config = Config.load('os')
self.modem.value = config.wirelessModem or 'auto'
UI.Tab.enable(self)
end
function tab:eventHandler(event)
if event.type == 'choice_change' then
local config = Config.load('os')
config.wirelessModem = self.modem.value
Config.update('os', config)
self:emit({ type = 'success_message', message = 'reboot to take effect' })
return true
end
end
return tab

View File

@@ -2,12 +2,11 @@ local Security = require('opus.security')
local SHA = require('opus.crypto.sha2')
local UI = require('opus.ui')
return UI.Tab {
title = 'Password',
local colors = _G.colors
local passwordTab = UI.Tab {
tabTitle = 'Password',
description = 'Wireless network password',
[1] = UI.Window {
x = 2, y = 2, ex = -2, ey = 4,
},
newPass = UI.TextEntry {
x = 3, ex = -3, y = 3,
limit = 32,
@@ -18,28 +17,28 @@ return UI.Tab {
},
},
button = UI.Button {
x = -8, ex = -2, y = -2,
text = 'Apply',
x = 3, y = 5,
text = 'Update',
event = 'update_password',
},
info = UI.TextArea {
x = 2, ex = -2, y = 5, ey = -4,
backgroundColor = 'black',
textColor = 'yellow',
x = 3, ex = -3, y = 7,
textColor = colors.yellow,
inactive = true,
marginLeft = 1, marginRight = 1, marginTop = 1,
value = 'Add a password to enable other computers to connect to this one.',
},
eventHandler = function(self, event)
if event.type == 'update_password' then
if not self.newPass.value or #self.newPass.value == 0 then
self:emit({ type = 'error_message', message = 'Invalid password' })
else
Security.updatePassword(SHA.compute(self.newPass.value))
self:emit({ type = 'success_message', message = 'Password updated' })
end
return true
end
end
}
}
function passwordTab:eventHandler(event)
if event.type == 'update_password' then
if not self.newPass.value or #self.newPass.value == 0 then
self:emit({ type = 'error_message', message = 'Invalid password' })
else
Security.updatePassword(SHA.compute(self.newPass.value))
self:emit({ type = 'success_message', message = 'Password updated' })
end
return true
end
end
return passwordTab

View File

@@ -3,14 +3,12 @@ local UI = require('opus.ui')
local Util = require('opus.util')
local tab = UI.Tab {
title = 'Path',
tabTitle = 'Path',
description = 'Set the shell path',
tabClose = true,
[1] = UI.Window {
x = 2, y = 2, ex = -2, ey = 4,
},
entry = UI.TextEntry {
x = 3, y = 3, ex = -3,
x = 2, y = 2, ex = -2,
limit = 256,
shadowText = 'enter new path',
accelerators = {
enter = 'update_path',
@@ -18,7 +16,7 @@ local tab = UI.Tab {
help = 'add a new path',
},
grid = UI.Grid {
x = 2, y = 6, ex = -2, ey = -3,
y = 4, ey = -3,
disableHeader = true,
columns = { { key = 'value' } },
autospace = true,
@@ -61,7 +59,7 @@ function tab:save()
end
function tab:eventHandler(event)
if event.type == 'update_path' and self.entry.value then
if event.type == 'update_path' then
table.insert(self.grid.values, {
value = self.entry.value,
})

View File

@@ -3,11 +3,12 @@ local UI = require('opus.ui')
local Util = require('opus.util')
local tab = UI.Tab {
title = 'Requires',
tabTitle = 'Requires',
description = 'Require path',
tabClose = true,
entry = UI.TextEntry {
x = 2, y = 2, ex = -2,
limit = 256,
shadowText = 'Enter new require path',
accelerators = {
enter = 'update_path',

View File

@@ -2,93 +2,48 @@ local UI = require('opus.ui')
local settings = _G.settings
local transform = {
string = tostring,
number = tonumber,
}
return settings and UI.Tab {
title = 'Settings',
description = 'Computercraft settings',
grid = UI.Grid {
x = 2, y = 2, ex = -2, ey = -2,
sortColumn = 'name',
columns = {
{ heading = 'Setting', key = 'name' },
{ heading = 'Value', key = 'value' },
},
},
editor = UI.SlideOut {
y = -6, height = 6,
titleBar = UI.TitleBar {
event = 'slide_hide',
title = 'Enter value',
},
form = UI.Form {
if settings then
local settingsTab = UI.Tab {
tabTitle = 'Settings',
description = 'Computercraft configurable settings',
grid = UI.Grid {
y = 2,
value = UI.TextEntry {
formIndex = 1,
formLabel = 'Value',
formKey = 'value',
autospace = true,
sortColumn = 'name',
columns = {
{ heading = 'Setting', key = 'name' },
{ heading = 'Value', key = 'value' },
},
validateField = function(self, entry)
if entry.value then
return transform[self.type](entry.value)
end
return true
end,
},
accelerators = {
form_cancel = 'slide_hide',
},
show = function(self, entry)
self.form.type = type(entry.value) or 'string'
self.form:setValues(entry)
self.titleBar.title = entry.name
UI.SlideOut.show(self)
end,
eventHandler = function(self, event)
if event.type == 'form_complete' then
if not event.values.value then
settings.unset(event.values.name)
self.parent:reload()
else
event.values.value = transform[self.form.type](event.values.value)
settings.set(event.values.name, event.values.value)
end
self.parent.grid:draw()
self:hide()
settings.save('.settings')
end
return UI.SlideOut.eventHandler(self, event)
end,
},
reload = function(self)
}
function settingsTab:enable()
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 = settings.get(v) or false,
value = value,
})
end
self.grid:setValues(values)
self.grid:setIndex(1)
end,
enable = function(self)
self:reload()
UI.Tab.enable(self)
end,
eventHandler = function(self, event)
end
function settingsTab:eventHandler(event)
if event.type == 'grid_select' then
if type(event.selected.value) == 'boolean' then
if not event.selected.value or type(event.selected.value) == 'boolean' then
event.selected.value = not event.selected.value
settings.set(event.selected.name, event.selected.value)
settings.save('.settings')
self.grid:draw()
else
self.editor:show(event.selected)
end
settings.set(event.selected.name, event.selected.value)
settings.save('.settings')
self.grid:draw()
return true
end
end,
}
end
return settingsTab
end

View File

@@ -18,7 +18,9 @@ local defaults = {
textColor = colors.white,
commandTextColor = colors.yellow,
directoryTextColor = colors.orange,
directoryBackgroundColor = colors.black,
promptTextColor = colors.blue,
promptBackgroundColor = colors.black,
directoryColor = colors.green,
fileColor = colors.white,
backgroundColor = colors.black,
@@ -26,7 +28,7 @@ local defaults = {
local _colors = config.color or Util.shallowCopy(defaults)
local allSettings = { }
for k in pairs(defaults) do
for k, v in pairs(defaults) do
table.insert(allSettings, { name = k })
end
@@ -36,34 +38,29 @@ if not _colors.backgroundColor then
_colors.fileColor = colors.white
end
return UI.Tab {
title = 'Shell',
local tab = UI.Tab {
tabTitle = 'Shell',
description = 'Shell options',
grid1 = UI.ScrollingGrid {
y = 2, ey = -10, x = 2, ex = -17,
y = 2, ey = -10, x = 3, ex = -16,
disableHeader = true,
columns = { { key = 'name' } },
values = allSettings,
sortColumn = 'name',
},
grid2 = UI.ScrollingGrid {
y = 2, ey = -10, x = -14, ex = -2,
y = 2, ey = -10, x = -14, ex = -3,
disableHeader = true,
columns = { { key = 'name' } },
values = allColors,
sortColumn = 'name',
getRowTextColor = function(self, row)
local selected = self.parent.grid1:getSelected()
if _colors[selected.name] == row.value then
return colors.yellow
end
return UI.Grid.getRowTextColor(self, row)
end
},
directoryLabel = UI.Text {
x = 2, y = -2,
value = 'Display directory',
},
directory = UI.Checkbox {
x = 2, y = -2,
labelBackgroundColor = colors.black,
label = 'Directory',
x = 20, y = -2,
value = config.displayDirectory
},
reset = UI.Button {
@@ -77,57 +74,69 @@ return UI.Tab {
event = 'update',
},
display = UI.Window {
x = 2, ex = -2, y = -8, height = 5,
draw = function(self)
self:clear(_colors.backgroundColor)
local offset = 0
if config.displayDirectory then
self:write(1, 1,
'==' .. os.getComputerLabel() .. ':/dir/etc',
_colors.backgroundColor, _colors.directoryTextColor)
offset = 1
end
self:write(1, 1 + offset, '$ ',
_colors.backgroundColor, _colors.promptTextColor)
self:write(3, 1 + offset, 'ls /',
_colors.backgroundColor, _colors.commandTextColor)
self:write(1, 2 + offset, 'sys usr',
_colors.backgroundColor, _colors.directoryColor)
self:write(1, 3 + offset, 'startup',
_colors.backgroundColor, _colors.fileColor)
end,
x = 3, ex = -3, y = -8, height = 5,
},
eventHandler = function(self, event)
if event.type =='checkbox_change' then
config.displayDirectory = not not event.checked
self.display:draw()
elseif event.type == 'grid_focus_row' and event.element == self.grid1 then
self.grid2:draw()
elseif event.type == 'grid_select' and event.element == self.grid2 then
_colors[self.grid1:getSelected().name] = event.selected.value
self.display:draw()
self.grid2:draw()
elseif event.type == 'reset' then
config.color = defaults
config.displayDirectory = true
self.directory.value = true
_colors = Util.shallowCopy(defaults)
Config.update('shellprompt', config)
self:draw()
elseif event.type == 'update' then
config.color = _colors
Config.update('shellprompt', config)
end
return UI.Tab.eventHandler(self, event)
end
}
function tab.grid2:getRowTextColor(row)
local selected = tab.grid1:getSelected()
if _colors[selected.name] == row.value then
return colors.yellow
end
return UI.Grid.getRowTextColor(self, row)
end
function tab.display:draw()
self:clear(_colors.backgroundColor)
local offset = 0
if config.displayDirectory then
self:write(1, 1,
'==' .. os.getComputerLabel() .. ':/dir/etc',
_colors.directoryBackgroundColor, _colors.directoryTextColor)
offset = 1
end
self:write(1, 1 + offset, '$ ',
_colors.promptBackgroundColor, _colors.promptTextColor)
self:write(3, 1 + offset, 'ls /',
_colors.backgroundColor, _colors.commandTextColor)
self:write(1, 2 + offset, 'sys usr',
_colors.backgroundColor, _colors.directoryColor)
self:write(1, 3 + offset, 'startup',
_colors.backgroundColor, _colors.fileColor)
end
function tab:eventHandler(event)
if event.type =='checkbox_change' then
config.displayDirectory = not not event.checked
self.display:draw()
elseif event.type == 'grid_focus_row' and event.element == self.grid1 then
self.grid2:draw()
elseif event.type == 'grid_select' and event.element == self.grid2 then
_colors[tab.grid1:getSelected().name] = event.selected.value
self.display:draw()
self.grid2:draw()
elseif event.type == 'reset' then
config.color = defaults
config.displayDirectory = true
self.directory.value = true
_colors = Util.shallowCopy(defaults)
Config.update('shellprompt', config)
self:draw()
elseif event.type == 'update' then
config.color = _colors
Config.update('shellprompt', config)
end
return UI.Tab.eventHandler(self, event)
end
return tab

View File

@@ -1,89 +0,0 @@
local Config = require('opus.config')
local UI = require('opus.ui')
local Util = require('opus.util')
local colors = _G.colors
local allColors = { }
for k,v in pairs(colors) do
if type(v) == 'number' then
table.insert(allColors, { name = k, value = v })
end
end
local allSettings = { }
for k,v in pairs(UI.theme.colors) do
allSettings[k] = { name = k, value = v }
end
return UI.Tab {
title = 'Theme',
description = 'Theme colors',
grid1 = UI.ScrollingGrid {
y = 2, ey = -10, x = 2, ex = -17,
disableHeader = true,
columns = { { key = 'name' } },
values = allSettings,
sortColumn = 'name',
},
grid2 = UI.ScrollingGrid {
y = 2, ey = -10, x = -14, ex = -2,
disableHeader = true,
columns = { { key = 'name' } },
values = allColors,
sortColumn = 'name',
getRowTextColor = function(self, row)
local selected = self.parent.grid1:getSelected()
if selected.value == row.value then
return colors.yellow
end
return UI.Grid.getRowTextColor(self, row)
end
},
button = UI.Button {
x = -9, y = -2,
text = 'Update',
event = 'update',
},
display = UI.Window {
x = 2, ex = -2, y = -8, height = 5,
textColor = colors.black,
backgroundColor = colors.black,
draw = function(self)
self:clear()
self:write(1, 1, Util.widthify(' Local Global Device', self.width),
allSettings.secondary.value)
self:write(2, 2, 'enter command ',
colors.black, colors.gray)
self:write(1, 3, ' Formatted ',
allSettings.primary.value)
self:write(12, 3, Util.widthify(' Output ', self.width - 11),
allSettings.tertiary.value)
self:write(1, 4, Util.widthify(' Key', self.width),
allSettings.primary.value)
end,
},
eventHandler = function(self, event)
if event.type == 'grid_focus_row' and event.element == self.grid1 then
self.grid2:draw()
elseif event.type == 'grid_select' and event.element == self.grid2 then
self.grid1:getSelected().value = event.selected.value
self.display:draw()
self.grid2:draw()
elseif event.type == 'update' then
local config = Config.load('ui.theme', { colors = { } })
for k,v in pairs(allSettings) do
config.colors[k] = v.value
end
Config.update('ui.theme', config)
end
return UI.Tab.eventHandler(self, event)
end
}

View File

@@ -1,55 +0,0 @@
local UI = require("opus.ui")
local Util = require("opus.util")
local SHA = require('opus.crypto.sha2')
local function split(s)
local b = ""
for i = 1, #s, 5 do
b = b .. s:sub(i, i+4)
if i ~= #s-4 then
b = b .. "-"
end
end
return b
end
return UI.Tab {
title = 'Trust',
description = 'Manage trusted devices',
grid = UI.Grid {
x = 2, y = 2, ex = -2, ey = -3,
autospace = true,
sortColumn = 'id',
columns = {
{ heading = 'Computer ID', key = 'id'},
{ heading = 'Identity', key = 'pkey'}
}
},
statusBar = UI.StatusBar { values = 'double-click to revoke trust' },
reload = function(self)
local values = {}
for k,v in pairs(Util.readTable('usr/.known_hosts') or {}) do
table.insert(values, {
id = k,
pkey = split(SHA.compute(v):sub(-20):upper()) -- Obfuscate private key for visual ident
})
end
self.grid:setValues(values)
self.grid:setIndex(1)
end,
enable = function(self)
self:reload()
UI.Tab.enable(self)
end,
eventHandler = function(self, event)
if event.type == 'grid_select' then
local hosts = Util.readTable('usr/.known_hosts')
hosts[event.selected.id] = nil
Util.writeTable('usr/.known_hosts', hosts)
self:reload()
else
return UI.Tab.eventHandler(self, event)
end
return true
end
}

View File

@@ -9,7 +9,7 @@ kernel.hook('clipboard_copy', function(_, args)
keyboard.clipboard = args[1]
end)
local function queuePaste()
keyboard.addHotkey('shift-paste', function()
local data = keyboard.clipboard
if type(data) == 'table' then
@@ -20,7 +20,4 @@ local function queuePaste()
if data then
os.queueEvent('paste', data)
end
end
kernel.hook('clipboard_paste', queuePaste)
keyboard.addHotkey('shift-paste', queuePaste)
end)

View File

@@ -1,5 +1,3 @@
local fs = _G.fs
local function completeMultipleChoice(sText, tOptions, bAddSpaces)
local tResults = { }
for n = 1,#tOptions do
@@ -22,14 +20,3 @@ _ENV.shell.setCompletionFunction("sys/apps/package.lua",
return completeMultipleChoice(text, { "install ", "update ", "uninstall ", "updateall ", "refresh" })
end
end)
_ENV.shell.setCompletionFunction("sys/apps/inspect.lua",
function(_, index, text)
if index == 1 then
local components = { }
for _, f in pairs(fs.list('sys/modules/opus/ui/components')) do
table.insert(components, (f:gsub("%.lua$", "")))
end
return completeMultipleChoice(text, components)
end
end)

View File

@@ -4,44 +4,58 @@ local kernel = _G.kernel
local keyboard = _G.device.keyboard
local multishell = _ENV.multishell
if multishell and multishell.getTabs then
-- restart tab
keyboard.addHotkey('control-backspace', function()
local tab = kernel.getFocused()
if tab and not tab.noTerminate then
multishell.terminate(tab.uid)
multishell.openTab(tab.env, {
path = tab.path,
args = tab.args,
focused = true,
})
end
end)
if not multishell or not multishell.getTabs then
return
end
-- overview
keyboard.addHotkey('control-o', function()
for _,tab in pairs(multishell.getTabs()) do
if tab.isOverview then
multishell.setFocus(tab.uid)
end
end
end)
-- restart tab
keyboard.addHotkey('control-backspace', function()
local uid = multishell.getFocus()
local tab = kernel.find(uid)
if not tab.isOverview then
multishell.terminate(uid)
multishell.openTab({
path = tab.path,
env = tab.env,
args = tab.args,
focused = true,
})
end
end)
-- next tab
keyboard.addHotkey('control-tab', function()
local tabs = multishell.getTabs()
local visibleTabs = { }
local currentTab = kernel.getFocused()
local currentTabId = multishell.getFocus()
local function compareTab(a, b)
return a.uid < b.uid
end
for _,tab in Util.spairs(kernel.routines, compareTab) do
for _,tab in Util.spairs(tabs, compareTab) do
if not tab.hidden and not tab.noFocus then
table.insert(visibleTabs, tab)
end
end
for k,tab in ipairs(visibleTabs) do
if tab.uid == currentTab.uid then
if tab.uid == currentTabId then
if k < #visibleTabs then
kernel.raise(visibleTabs[k + 1].uid)
multishell.setFocus(visibleTabs[k + 1].uid)
return
end
end
end
if #visibleTabs > 0 then
kernel.raise(visibleTabs[1].uid)
multishell.setFocus(visibleTabs[1].uid)
end
end)

View File

@@ -4,18 +4,29 @@
local kernel = _G.kernel
local keyboard = _G.device.keyboard
local multishell = _ENV.multishell
local os = _G.os
local term = _G.term
local function systemLog()
local routine = kernel.getCurrent()
if multishell and multishell.openTab then
local w, h = kernel.window.getSize()
kernel.window.reposition(1, 2, w, h - 1)
routine.terminal = kernel.window
routine.window = kernel.window
term.redirect(kernel.window)
end
kernel.hook('mouse_scroll', function(_, eventData)
local dir, y = eventData[1], eventData[3]
if y > 1 then
local currentTab = kernel.getFocused()
if currentTab == routine then
if currentTab.terminal.scrollUp then
if currentTab.terminal.scrollUp and not currentTab.terminal.noAutoScroll then
if dir == -1 then
currentTab.terminal.scrollUp()
else
@@ -39,9 +50,16 @@ local function systemLog()
keyboard.removeHotkey('control-d')
end
kernel.run(_ENV, {
title = 'System Log',
fn = systemLog,
noTerminate = true,
hidden = true,
})
if multishell and multishell.openTab then
multishell.openTab({
title = 'System Log',
fn = systemLog,
noTerminate = true,
hidden = true,
})
else
kernel.run({
title = 'Syslog',
fn = systemLog,
})
end

20
sys/autorun/upgraded.lua Normal file
View File

@@ -0,0 +1,20 @@
local fs = _G.fs
local function deleteIfExists(path)
if fs.exists(path) then
fs.delete(path)
print("Deleted outdated file at: "..path)
end
end
-- cleanup outdated files
deleteIfExists('sys/apps/shell')
deleteIfExists('sys/etc/app.db')
deleteIfExists('sys/extensions')
deleteIfExists('sys/network')
deleteIfExists('startup')
deleteIfExists('sys/apps/system/turtle.lua')
deleteIfExists('sys/autorun/gps.lua')
deleteIfExists('sys/autorun/gpshost.lua')
deleteIfExists('sys/apps/network/redserver.lua')
deleteIfExists('sys/apis')
deleteIfExists('sys/autorun/apps.lua')

View File

@@ -1,53 +0,0 @@
local Config = require('opus.config')
local Util = require('opus.util')
local fs = _G.fs
local shell = _ENV.shell
local URL = 'https://raw.githubusercontent.com/kepler155c/opus/%s/.opus_version'
if fs.exists('.opus_version') then
local f = fs.open('.opus_version', 'r')
local date = f.readLine()
f.close()
date = type(date) == 'string' and Util.split(date)[1]
local today = os.date('%j')
local config = Config.load('version', {
packages = date,
checked = today,
})
-- check if packages need an update
if date ~= config.packages then
config.packages = date
Config.update('version', config)
print('Updating packages')
shell.run('package updateall')
os.reboot()
end
if type(date) == 'string' and #date > 0 then
if config.checked ~= today then
config.checked = today
Config.update('version', config)
print('Checking for new version')
pcall(function()
local c = Util.httpGet(string.format(URL, _G.OPUS_BRANCH))
if c then
local lines = Util.split(c)
local revdate = table.remove(lines, 1)
if date ~= revdate and config.skip ~= revdate then
config.current = revdate
config.details = table.concat(lines, '\n')
Config.update('version', config)
print('New version available')
if _ENV.multishell then
shell.openForegroundTab('sys/apps/Version.lua')
end
end
end
end)
end
end
end

View File

@@ -21,7 +21,7 @@ if mon then
parallel.waitForAny(
function()
os.run(_ENV, '/sys/boot/opus.lua')
os.run(_ENV, '/sys/boot/opus.boot')
end,
function()
@@ -36,5 +36,5 @@ if mon then
end
)
else
os.run(_ENV, '/sys/boot/opus.lua')
os.run(_ENV, '/sys/boot/opus.boot')
end

36
sys/boot/opus.boot Normal file
View File

@@ -0,0 +1,36 @@
local fs = _G.fs
local sandboxEnv = setmetatable({ }, { __index = _G })
for k,v in pairs(_ENV) do
sandboxEnv[k] = v
end
local function run(file, ...)
local env = setmetatable({ }, { __index = _G })
for k,v in pairs(sandboxEnv) do
env[k] = v
end
local s, m = loadfile(file, env)
if s then
return s(...)
end
error('Error loading ' .. file .. '\n' .. m)
end
_G._syslog = function() end
_G.OPUS_BRANCH = 'master-1.8'
-- Install require shim
_G.requireInjector = run('sys/modules/opus/injector.lua')
local s, m = pcall(run, 'sys/apps/shell.lua', 'sys/kernel.lua', ...)
if not s then
print('\nError loading Opus OS\n')
_G.printError(m .. '\n')
end
if fs.restore then
fs.restore()
end

View File

@@ -1,58 +0,0 @@
local fs = _G.fs
-- override bios function to use the global scope of the current env
function _G.loadstring(string, chunkname)
return load(string, chunkname, nil, getfenv(2)._G)
end
-- override bios function to include the actual filename
function _G.loadfile(filename, mode, env)
-- Support the previous `loadfile(filename, env)` form instead.
if type(mode) == "table" and env == nil then
mode, env = nil, mode
end
local file = fs.open(filename, "r")
if not file then return nil, "File not found" end
local func, err = load(file.readAll(), '@' .. filename, mode, env)
file.close()
return func, err
end
local sandboxEnv = setmetatable({ }, { __index = _G })
for k,v in pairs(_ENV) do
sandboxEnv[k] = v
end
-- Install require shim
_G.requireInjector = loadfile('sys/modules/opus/injector.lua', _ENV)()
local function run(file, ...)
local env = setmetatable({ }, { __index = _G })
for k,v in pairs(sandboxEnv) do
env[k] = v
end
_G.requireInjector(env)
local s, m = loadfile(file, env)
if s then
return s(...)
end
error('Error loading ' .. file .. '\n' .. m)
end
_G._syslog = function() end
_G.OPUS_BRANCH = 'develop-1.8'
local s, m = pcall(run, 'sys/apps/shell.lua', 'sys/kernel.lua', ...)
if not s then
print('\nError loading Opus OS\n')
_G.printError(m .. '\n')
end
if fs.restore then
fs.restore()
end

15
sys/boot/tlco.boot Normal file
View File

@@ -0,0 +1,15 @@
local pullEvent = os.pullEventRaw
local shutdown = os.shutdown
os.pullEventRaw = function()
error('')
end
os.shutdown = function()
os.pullEventRaw = pullEvent
os.shutdown = shutdown
os.run(getfenv(1), 'sys/boot/opus.boot')
end
os.queueEvent('modem_message')

View File

@@ -1,30 +0,0 @@
local run = os.run
local shutdown = os.shutdown
local args = {...} -- keep the args so that they can be passed to opus.lua
os.run = function()
os.run = run
end
os.shutdown = function()
os.shutdown = shutdown
_ENV.multishell = nil -- prevent sys/apps/shell.lua erroring for odd reasons
local success, err = pcall(function()
run(_ENV, 'sys/boot/opus.lua', table.unpack(args))
end)
term.redirect(term.native())
if success then
print("Opus OS abruptly stopped.")
else
printError("Opus OS errored.")
printError(err)
end
print("Press any key to continue.")
os.pullEvent("key")
shutdown()
end
shell.exit()

View File

@@ -50,10 +50,7 @@
title = "System",
category = "System",
icon = "\030 \0307\031f| \010\0307\031f---o\030 \031 \010\030 \009 \0307\031f| ",
iconExt = "22€070†b02‹4Ÿ24\
02—7Ž704ˆ4€€€€\
7ƒ07„1ƒ7‹24ƒƒ",
--iconExt = "\030 \0318\138\0308\031 \130\0318\128\031 \129\030 \0318\133\010\030 \0318\143\0308\128\0317\143\0318\128\030 \143\010\030 \0318\138\135\143\139\133",
iconExt = "\030 \0318\138\0308\031 \130\0318\128\031 \129\030 \0318\133\010\030 \0318\143\0308\128\0317\143\0318\128\030 \143\010\030 \0318\138\135\143\139\133",
run = "System.lua",
},
[ "2a4d562b1d9a9c90bdede6fac8ce4f7402462b86" ] = {
@@ -136,10 +133,4 @@
iconExt = "\030 \031 \128\0307\143\131\131\131\131\143\030 \128\010\0307\031 \129\0317\128\0319\136\0309\031b\136\132\0307\0319\132\0317\128\031 \130\010\030 \0317\130\143\0307\128\128\128\128\030 \143\129",
run = "/rom/programs/fun/dj",
},
[ "4dbdd221e957eff27cc47796f3ed8447290f71c7ad8b95e5bd828b31c1858f15" ] = {
title = "Partition",
category = "System",
iconExt = "\30\55\31\55\128\30\48\135\131\139\30\55\128\128\128\10\30\48\31\55\149\31\48\128\30\55\145\30\48\31\56\140\30\55\157\144\144\10\30\55\31\55\128\31\48\139\143\135\31\55\128\31\56\142\133",
run = "Partition",
}
}

View File

@@ -2,4 +2,5 @@ sys/apps/pain.lua urlfs https://github.com/LDDestroier/CC/raw/master/pain.lua
sys/apps/update.lua urlfs http://pastebin.com/raw/UzGHLbNC
sys/apps/Enchat.lua urlfs https://raw.githubusercontent.com/LDDestroier/enchat/master/enchat3.lua
sys/apps/cloud.lua urlfs https://cloud-catcher.squiddev.cc/cloud.lua
sys/apps/nfttrans.lua urlfs https://pastebin.com/raw/e8XrzeDY
rom/modules/main/opus linkfs sys/modules/opus

View File

@@ -1,9 +0,0 @@
Wireless Networking
===================
To establish one-way trust between two computers with modems, run the following in a shell prompt:
On the target computer, run:
> password
On the source computer, run:
> trust <target computer ID>

View File

@@ -6,7 +6,17 @@ Shortcut Keys
* Control-d: Show/toggle logging screen
* Control-c: Copy (in most applications)
* Control-shift-v: Paste from internal clipboard
* Control-shift-click: Open the clicked UI element in Lua
* Control-shift-doubleclick: Open in Lua the clicked UI element
Wireless Networking
===================
To establish one-way trust between two computers with modems, run the following in a shell prompt:
On the target computer, run:
> password
On the source computer, run:
> trust <target computer ID>
Running Custom Programs
=======================

View File

@@ -0,0 +1,30 @@
Opus applications are grouped into packages with a common theme.
To install a package, use either the System -> Packages program or the package command line program.
Shell usage:
> package list
> package install <name>
> package update <name>
> package uninstall <name>
Package definitions are located in usr/apps/packages. This file can be modified to add custom packages.
Current stable packages
=======================
* core
Programming and miscellaneous applications. Also contains drivers needed for other packages.
* builder
A program for creating structures from schematic files using a turtle (requires core).
* farms
Various programs for farming resources (wood, crops, animals).
* milo
An A/E like storage implementation (requires core).
* miners
Mining programs.

View File

@@ -6,11 +6,10 @@ Shortcut keys
* l: Lua application
* f: Files
* e: Edit an application (or right-click)
* n: Network
* control-n: Add a new application
* delete: Delete an application
Adding a new application
========================
The run entry can be either a disk file or a URL.
Icons must be in NFT format with a height of 3 and a width of 3 to 8 characters. Magenta is used for transparency.
Icons must be in NFT format with a height of 3 and a width of 3 to 8 characters.

View File

@@ -1,13 +0,0 @@
Opus applications are grouped into packages with a common theme.
To install a package, use either the System -> Packages program or the package command line program.
Shell usage:
> package list
> package install <name>
> package update <name>
> package updateall
> package uninstall <name>
Package definitions are located in usr/config/packages. This file can be modified to add custom packages.

View File

@@ -1,3 +1,5 @@
_G.requireInjector(_ENV)
local Peripheral = require('opus.peripheral')
_G.device = Peripheral.getList()

View File

@@ -4,8 +4,11 @@ if fs.native then
return
end
_G.requireInjector(_ENV)
local Util = require('opus.util')
-- TODO: support getDrive for virtual nodes
fs.native = Util.shallowCopy(fs)
local fstypes = { }
@@ -19,11 +22,8 @@ for k,fn in pairs(fs) do
end
end
function nativefs.resolve(_, dir)
return dir
end
function nativefs.list(node, dir)
local files
if fs.native.isDir(dir) then
files = fs.native.list(dir)
@@ -83,18 +83,6 @@ function nativefs.isDir(node, dir)
return fs.native.isDir(dir)
end
function nativefs.attributes(node, path)
if node.mountPoint == path then
return {
created = node.created or os.epoch('utc'),
modification = node.modification or os.epoch('utc'),
isDir = not not node.nodes,
size = node.size or 0,
}
end
return fs.native.attributes(path)
end
function nativefs.exists(node, dir)
if node.mountPoint == dir then
return true
@@ -150,10 +138,8 @@ local function getNode(dir)
return node
end
fs.getNode = getNode
local methods = { 'delete', 'getFreeSpace', 'exists', 'isDir', 'getSize',
'isReadOnly', 'makeDir', 'getDrive', 'list', 'open', 'attributes' }
'isReadOnly', 'makeDir', 'getDrive', 'list', 'open' }
for _,m in pairs(methods) do
fs[m] = function(dir, ...)
@@ -163,12 +149,6 @@ for _,m in pairs(methods) do
end
end
-- if a link, return the source for this link
function fs.resolve(dir)
local n = getNode(dir)
return n.fs.resolve and n.fs.resolve(n, dir) or dir
end
function fs.complete(partial, dir, includeFiles, includeSlash)
dir = fs.combine(dir, '')
local node = getNode(dir)
@@ -178,13 +158,6 @@ function fs.complete(partial, dir, includeFiles, includeSlash)
return fs.native.complete(partial, dir, includeFiles, includeSlash)
end
local displayFlags = {
urlfs = 'U',
linkfs = 'L',
ramfs = 'T',
netfs = 'N',
}
function fs.listEx(dir)
dir = fs.combine(dir, '')
local node = getNode(dir)
@@ -195,22 +168,20 @@ function fs.listEx(dir)
local t = { }
local files = node.fs.list(node, dir)
for _,f in ipairs(files) do
pcall(function()
pcall(function()
for _,f in ipairs(files) do
local fullName = fs.combine(dir, f)
local n = fs.getNode(fullName)
local file = {
name = f,
isDir = fs.isDir(fullName),
isReadOnly = fs.isReadOnly(fullName),
fstype = n.mountPoint == fullName and displayFlags[n.fstype],
}
if not file.isDir then
file.size = fs.getSize(fullName)
end
table.insert(t, file)
end)
end
end
end)
return t
end
@@ -235,12 +206,12 @@ function fs.copy(s, t)
end
else
local sf = Util.readFile(s, 'rb')
local sf = Util.readFile(s)
if not sf then
error('No such file')
end
Util.writeFile(t, sf, 'wb')
Util.writeFile(t, sf)
end
end
@@ -294,17 +265,11 @@ local function getfstype(fstype)
end
function fs.mount(path, fstype, ...)
local vfs = getfstype(fstype)
if not vfs then
error('Invalid file system type')
end
-- get the mount point for the path
-- ie. if packages is mapped to disk/packages
-- and a request to mount /packages/foo
-- then use disk/packages/foo as the mountPoint
path = fs.resolve(path)
local node = vfs.mount(path, ...)
if node then
local parts = splitpath(path)
@@ -319,16 +284,12 @@ function fs.mount(path, fstype, ...)
tp.nodes[d] = Util.shallowCopy(tp)
tp.nodes[d].nodes = { }
tp.nodes[d].mountPoint = fs.combine(tp.mountPoint, d)
tp.nodes[d].created = os.epoch('utc')
tp.nodes[d].modification = os.epoch('utc')
end
tp = tp.nodes[d]
end
node.fs = vfs
node.fstype = fstype
node.created = node.created or os.epoch('utc')
node.modification = node.modification or os.epoch('utc')
if not targetName then
node.mountPoint = ''
fs.nodes = node
@@ -394,4 +355,4 @@ function fs.restore()
local native = fs.native
Util.clear(fs)
Util.merge(fs, native)
end
end

View File

@@ -1,46 +1,3 @@
local fs = _G.fs
local os = _G.os
local fs = _G.fs
fs.loadTab('sys/etc/fstab')
-- add some Lua compatibility functions
function os.remove(a)
if fs.exists(a) then
local s = pcall(fs.delete, a)
return s and true or nil, a .. ': Unable to remove file'
end
return nil, a .. ': No such file or directory'
end
os.execute = function(cmd)
local env = _G.getfenv(2)
if not cmd then
return env.shell and 1 or 0
end
if not env.shell then
return 0
end
local s, m = env.shell.run('sys/apps/shell.lua ' .. cmd)
if not s then
return 1, m
end
return 0
end
os.tmpname = function()
local fname
repeat
fname = 'tmp/a' .. math.random(1, 32768)
until not fs.exists(fname)
return fname
end
-- non-standard - will raise error instead
os.exit = function(code)
error(code or 0)
end

View File

@@ -10,13 +10,6 @@ if not fs.exists('usr/autorun') then
fs.makeDir('usr/autorun')
end
-- move the fstab out of config so that the config directory
-- can be remapped to another disk (and for consistency)
if fs.exists('usr/config/fstab') and not fs.exists('usr/etc/fstab') then
fs.move('usr/config/fstab', 'usr/etc/fstab')
end
fs.loadTab('usr/etc/fstab')
-- TODO: Temporary
local upgrade = Util.readTable('usr/config/shell')
if upgrade and (not upgrade.upgraded or upgrade.upgraded ~= 1) then
@@ -52,3 +45,5 @@ shell.setPath(table.concat(path, ':'))
--_G.LUA_PATH = config.lua_path
--_G.settings.set('mbs.shell.require_path', config.lua_path)
fs.loadTab('usr/config/fstab')

View File

@@ -1,3 +1,5 @@
_G.requireInjector(_ENV)
local Config = require('opus.config')
local device = _G.device
@@ -15,7 +17,7 @@ do
end
local function startNetwork()
kernel.run(_ENV, {
kernel.run({
title = 'Net daemon',
path = 'sys/apps/netdaemon.lua',
hidden = true,

View File

@@ -1,31 +0,0 @@
local LZW = require('opus.compress.lzw')
local Tar = require('opus.compress.tar')
local Util = require('opus.util')
local fs = _G.fs
if not fs.exists('packages') or not fs.isDir('packages') then
return
end
for _, name in pairs(fs.list('packages')) do
local fullName = fs.combine('packages', name)
local packageName = name:match('(.+)%.tar%.lzw$')
if packageName and not fs.isDir(fullName) then
local dir = fs.combine('packages', packageName)
if not fs.exists(dir) then
local s, m = pcall(function()
fs.mount(dir, 'ramfs', 'directory')
local c = Util.readFile(fullName, 'rb')
Tar.untar_string(LZW.decompress(c), dir)
end)
if not s then
fs.delete(dir)
print('failed to extract ' .. fullName)
print(m)
end
end
end
end

View File

@@ -13,14 +13,8 @@ table.insert(helpPaths, '/sys/help')
for name in pairs(Packages:installed()) do
local packageDir = fs.combine('packages', name)
local fstabPath = fs.combine(packageDir, 'etc/fstab')
if fs.exists(fstabPath) then
fs.loadTab(fstabPath)
end
table.insert(appPaths, 1, '/' .. packageDir)
local apiPath = fs.combine(packageDir, 'apis') -- TODO: rename dir to 'modules' (someday)
local apiPath = fs.combine(packageDir, 'apis')
if fs.exists(apiPath) then
fs.mount(fs.combine('rom/modules/main', name), 'linkfs', apiPath)
end
@@ -29,27 +23,12 @@ for name in pairs(Packages:installed()) do
if fs.exists(helpPath) then
table.insert(helpPaths, helpPath)
end
local fstabPath = fs.combine(packageDir, 'etc/fstab')
if fs.exists(fstabPath) then
fs.loadTab(fstabPath)
end
end
help.setPath(table.concat(helpPaths, ':'))
shell.setPath(table.concat(appPaths, ':'))
local function runDir(directory)
local files = fs.list(directory)
table.sort(files)
for _,file in ipairs(files) do
os.sleep(0)
local result, err = shell.run(directory .. '/' .. file)
if not result and err then
_G.printError('\n' .. err)
end
end
end
for _, package in pairs(Packages:installedSorted()) do
local packageDir = 'packages/' .. package.name .. '/init'
if fs.exists(packageDir) and fs.isDir(packageDir) then
runDir(packageDir)
end
end

View File

@@ -1,5 +1,7 @@
local Blit = require('opus.ui.blit')
_G.requireInjector(_ENV)
local Config = require('opus.config')
local trace = require('opus.trace')
local Util = require('opus.util')
local colors = _G.colors
@@ -8,6 +10,7 @@ local kernel = _G.kernel
local keys = _G.keys
local os = _G.os
local printError = _G.printError
local shell = _ENV.shell
local window = _G.window
local parentTerm = _G.device.terminal
@@ -17,9 +20,9 @@ local tabsDirty = false
local closeInd = Util.getVersion() >= 1.76 and '\215' or '*'
local multishell = { }
_ENV.multishell = multishell
shell.setEnv('multishell', multishell)
kernel.window.reposition(1, 2, w, h - 1)
multishell.term = parentTerm --deprecated use device.terminal
local config = {
standard = {
@@ -44,7 +47,6 @@ local config = {
Config.load('multishell', config)
local _colors = parentTerm.isColor() and config.color or config.standard
local palette = parentTerm.isColor() and Blit.colorPalette or Blit.grayscalePalette
local function redrawMenu()
if not tabsDirty then
@@ -92,70 +94,57 @@ function multishell.getTabs()
return kernel.routines
end
function multishell.launch(env, path, ...)
function multishell.launch( tProgramEnv, sProgramPath, ... )
-- backwards compatibility
return multishell.openTab(env, {
path = path,
return multishell.openTab({
env = tProgramEnv,
path = sProgramPath,
args = { ... },
})
end
local function chain(orig, fn)
if not orig then
return fn
local function xprun(env, path, ...)
setmetatable(env, { __index = _G })
local fn, m = loadfile(path, env)
if fn then
return trace(fn, ...)
end
if type(orig) == 'table' then
table.insert(orig, fn)
return orig
end
return setmetatable({ orig, fn }, {
__call = function(self, ...)
for _,v in pairs(self) do
v(...)
end
end
})
return fn, m
end
function multishell.openTab(env, tab)
function multishell.openTab(tab)
if not tab.title and tab.path then
tab.title = fs.getName(tab.path):match('([^%.]+)')
end
tab.title = tab.title or 'untitled'
tab.window = tab.window or window.create(parentTerm, 1, 2, w, h - 1, false)
-- require('opus.terminal').window(parentTerm, 1, 2, w, h - 1, false)
tab.onExit = chain(tab.onExit, function(self, result, err, stack)
if not result and err and err ~= 'Terminated' then
self.terminal.setTextColor(colors.white)
self.terminal.setCursorBlink(false)
print('\nThe program terminated with an error.\n')
tab.terminal = tab.terminal or tab.window
local routine = kernel.newRoutine(tab)
routine.co = coroutine.create(function()
local result, err
if tab.fn then
result, err = Util.runFunction(routine.env, tab.fn, table.unpack(tab.args or { } ))
elseif tab.path then
result, err = xprun(routine.env, tab.path, table.unpack(tab.args or { } ))
else
err = 'multishell: invalid tab'
end
if not result and err and err ~= 'Terminated' or (err and err ~= 0) then
tab.terminal.setBackgroundColor(colors.black)
if tonumber(err) then
printError('Process exited with error code: ' .. err)
tab.terminal.setTextColor(colors.orange)
print('Process exited with error code: ' .. err)
elseif err then
printError(tostring(err))
end
if type(stack) == 'table' and #stack > 0 then
local _, cy = self.terminal.getCursorPos()
local _, th = self.terminal.getSize()
self.terminal.setTextColor(colors.white)
if cy < th - 4 then
print('\nstack traceback:')
for _, v in ipairs(stack or { }) do
_, cy = self.terminal.getCursorPos()
if cy > th - 3 then
print(' ...')
break
end
print(v)
end
end
end
self.terminal.setTextColor(parentTerm.isColor() and colors.yellow or colors.white)
_G.write('\nPress enter to close')
self.isDead = true
self.hidden = false
tab.terminal.setTextColor(colors.white)
print('\nPress enter to close')
routine.isDead = true
routine.hidden = false
redrawMenu()
while true do
local e, code = os.pullEventRaw('key')
@@ -166,17 +155,14 @@ function multishell.openTab(env, tab)
end
end)
local routine, message = kernel.run(env, tab)
kernel.launch(routine)
if routine then
if tab.focused then
multishell.setFocus(routine.uid)
else
redrawMenu()
end
if tab.focused then
multishell.setFocus(routine.uid)
else
redrawMenu()
end
return routine and routine.uid, message
return routine.uid
end
function multishell.hideTab(tabId)
@@ -221,11 +207,17 @@ end)
kernel.hook('multishell_redraw', function()
tabsDirty = false
local blit = Blit(w, {
bg = _colors.tabBarBackgroundColor,
fg = _colors.textColor,
palette = palette,
})
local function write(x, text, bg, fg)
parentTerm.setBackgroundColor(bg)
parentTerm.setTextColor(fg)
parentTerm.setCursorPos(x, 1)
parentTerm.write(text)
end
local bg = _colors.tabBarBackgroundColor
parentTerm.setBackgroundColor(bg)
parentTerm.setCursorPos(1, 1)
parentTerm.clearLine()
local currentTab = kernel.getFocused()
@@ -262,27 +254,21 @@ kernel.hook('multishell_redraw', function()
tabX = tabX + tab.width
if tab ~= currentTab then
local textColor = tab.isDead and _colors.errorColor or _colors.textColor
blit:write(tab.sx, tab.title:sub(1, tab.width - 1),
write(tab.sx, tab.title:sub(1, tab.width - 1),
_colors.backgroundColor, textColor)
end
end
end
if currentTab then
if currentTab.sx then
local textColor = currentTab.isDead and _colors.errorColor or _colors.focusTextColor
blit:write(currentTab.sx - 1,
' ' .. currentTab.title:sub(1, currentTab.width - 1) .. ' ',
_colors.focusBackgroundColor, textColor)
end
write(currentTab.sx - 1,
' ' .. currentTab.title:sub(1, currentTab.width - 1) .. ' ',
_colors.focusBackgroundColor, _colors.focusTextColor)
if not currentTab.noTerminate then
blit:write(w, closeInd, nil, _colors.focusTextColor)
write(w, closeInd, _colors.backgroundColor, _colors.focusTextColor)
end
end
parentTerm.setCursorPos(1, 1)
parentTerm.blit(blit.text, blit.fg, blit.bg)
if currentTab and currentTab.window then
currentTab.window.restoreCursor()
end
@@ -311,65 +297,56 @@ kernel.hook('term_resize', function(_, eventData)
end)
kernel.hook('mouse_click', function(_, eventData)
if not eventData[4] then
local x, y = eventData[2], eventData[3]
local x, y = eventData[2], eventData[3]
if y == 1 then
if x == 1 then
multishell.setFocus(overviewId)
elseif x == w then
local currentTab = kernel.getFocused()
if currentTab then
multishell.terminate(currentTab.uid)
end
else
for _,tab in pairs(kernel.routines) do
if not tab.hidden and tab.sx then
if x >= tab.sx and x <= tab.ex then
multishell.setFocus(tab.uid)
break
end
if y == 1 then
if x == 1 then
multishell.setFocus(overviewId)
elseif x == w then
local currentTab = kernel.getFocused()
if currentTab then
multishell.terminate(currentTab.uid)
end
else
for _,tab in pairs(kernel.routines) do
if not tab.hidden and tab.sx then
if x >= tab.sx and x <= tab.ex then
multishell.setFocus(tab.uid)
break
end
end
end
return true
end
eventData[3] = eventData[3] - 1
return true
end
eventData[3] = eventData[3] - 1
end)
kernel.hook({ 'mouse_up', 'mouse_drag' }, function(_, eventData)
if not eventData[4] then
eventData[3] = eventData[3] - 1
end
eventData[3] = eventData[3] - 1
end)
kernel.hook('mouse_scroll', function(_, eventData)
if not eventData[4] then
if eventData[3] == 1 then
return true
end
eventData[3] = eventData[3] - 1
if eventData[3] == 1 then
return true
end
eventData[3] = eventData[3] - 1
end)
kernel.hook('kernel_ready', function()
overviewId = multishell.openTab(_ENV, {
path = 'sys/apps/shell.lua',
args = { config.launcher or 'sys/apps/Overview.lua' },
local env = Util.shallowCopy(shell.getEnv())
_G.requireInjector(env)
overviewId = multishell.openTab({
path = config.launcher or 'sys/apps/Overview.lua',
isOverview = true,
noTerminate = true,
focused = true,
title = '+',
onExit = function(_, s, m)
if not s then
kernel.halt(s, m)
end
end,
env = env,
})
multishell.setTitle(overviewId, '+')
multishell.openTab(_ENV, {
multishell.openTab({
path = 'sys/apps/shell.lua',
args = { 'sys/apps/autorun.lua' },
title = 'Autorun',

View File

@@ -1,6 +1,7 @@
_G.requireInjector(_ENV)
local Array = require('opus.array')
local Terminal = require('opus.terminal')
local trace = require('opus.trace')
local Util = require('opus.util')
_G.kernel = {
@@ -20,7 +21,7 @@ local w, h = term.getSize()
kernel.terminal = term.current()
kernel.window = Terminal.window(kernel.terminal, 1, 1, w, h, false)
kernel.window.setMaxScroll(200)
kernel.window.setMaxScroll(100)
local focusedRoutineEvents = Util.transpose {
'char', 'key', 'key_up',
@@ -29,8 +30,10 @@ local focusedRoutineEvents = Util.transpose {
}
_G._syslog = function(pattern, ...)
local oldTerm = term.redirect(kernel.window)
kernel.window.scrollBottom()
kernel.window.print(Util.tostring(pattern, ...))
Util.print(pattern, ...)
term.redirect(oldTerm)
end
-- any function that runs in a kernel hook does not run in
@@ -49,23 +52,19 @@ function kernel.hook(event, fn)
end
end
-- you *should* only unhook from within the function that hooked
-- you can only unhook from within the function that hooked
function kernel.unhook(event, fn)
if type(event) == 'table' then
for _,v in pairs(event) do
kernel.unhook(v, fn)
end
else
local eventHooks = kernel.hooks[event]
if eventHooks then
Array.removeByValue(eventHooks, fn)
if #eventHooks == 0 then
kernel.hooks[event] = nil
end
local eventHooks = kernel.hooks[event]
if eventHooks then
Array.removeByValue(eventHooks, fn)
if #eventHooks == 0 then
kernel.hooks[event] = nil
end
end
end
local Routine = { }
local function switch(routine, previous)
if routine then
if previous and previous.window then
@@ -83,8 +82,6 @@ local function switch(routine, previous)
end
end
local Routine = { }
function Routine:resume(event, ...)
if not self.co or coroutine.status(self.co) == 'dead' then
return
@@ -94,64 +91,35 @@ function Routine:resume(event, ...)
local previousTerm = term.redirect(self.terminal)
local previous = kernel.running
kernel.running = self
kernel.running = self -- stupid shell set title
local ok, result = coroutine.resume(self.co, event, ...)
kernel.running = previous
self.filter = result
if ok then
self.filter = result
else
_G.printError(result)
end
self.terminal = term.current()
term.redirect(previousTerm)
if not ok and self.haltOnError then
error(result, -1)
end
if coroutine.status(self.co) == 'dead' then
Array.removeByValue(kernel.routines, self)
if #kernel.routines > 0 then
switch(kernel.routines[1])
end
if self.haltOnExit then
kernel.halt()
end
end
return ok, result
end
end
function Routine:run()
self.co = self.co or coroutine.create(function()
local result, err, fn, stack
if self.fn then
fn = self.fn
_G.setfenv(fn, self.env)
elseif self.path then
fn, err = loadfile(self.path, self.env)
elseif self.chunk then
fn, err = load(self.chunk, self.title, nil, self.env)
end
if fn then
result, err, stack = trace(fn, table.unpack(self.args or { } ))
else
err = err or 'kernel: invalid routine'
end
pcall(self.onExit, self, result, err, stack)
self:cleanup()
if not result then
error(err)
end
end)
table.insert(kernel.routines, self)
return self:resume()
end
-- override if any post processing is required
function Routine:onExit(status, message) -- self, status, message
if not status and message ~= 'Terminated' then
_G.printError(message)
end
end
function Routine:cleanup()
Array.removeByValue(kernel.routines, self)
if #kernel.routines > 0 then
switch(kernel.routines[1])
end
end
function kernel.getFocused()
return kernel.routines[1]
end
@@ -164,34 +132,51 @@ function kernel.getShell()
return shell
end
-- each routine inherits the parent's env
function kernel.makeEnv(env, dir)
env = setmetatable(Util.shallowCopy(env or _ENV), { __index = _G })
_G.requireInjector(env, dir)
return env
end
function kernel.newRoutine(env, args)
function kernel.newRoutine(args)
kernel.UID = kernel.UID + 1
local routine = setmetatable({
uid = kernel.UID,
timestamp = os.clock(),
terminal = kernel.window,
window = kernel.window,
title = 'untitled',
}, { __index = Routine })
Util.merge(routine, args)
routine.env = args.env or kernel.makeEnv(env, routine.path and fs.getDir(routine.path))
routine.terminal = routine.terminal or routine.window
routine.env = args.env or Util.shallowCopy(shell.getEnv())
return routine
end
function kernel.run(env, args)
local routine = kernel.newRoutine(env, args)
local s, m = routine:run()
return s and routine, m
function kernel.launch(routine)
routine.co = routine.co or coroutine.create(function()
local result, err
if routine.fn then
result, err = Util.runFunction(routine.env, routine.fn, table.unpack(routine.args or { } ))
elseif routine.path then
result, err = Util.run(routine.env, routine.path, table.unpack(routine.args or { } ))
else
err = 'kernel: invalid routine'
end
if not result and err ~= 'Terminated' then
error(err or 'Error occurred', 2)
end
end)
table.insert(kernel.routines, routine)
local s, m = routine:resume()
return not s and s or routine.uid, m
end
function kernel.run(args)
local routine = kernel.newRoutine(args)
kernel.launch(routine)
return routine
end
function kernel.raise(uid)
@@ -209,6 +194,8 @@ function kernel.raise(uid)
end
switch(routine, previous)
-- local previous = eventData[2]
-- local routine = kernel.find(previous)
return true
end
return false
@@ -236,8 +223,8 @@ function kernel.find(uid)
return Util.find(kernel.routines, 'uid', uid)
end
function kernel.halt(status, message)
os.queueEvent('kernel_halt', status, message)
function kernel.halt()
os.queueEvent('kernel_halt')
end
function kernel.event(event, eventData)
@@ -279,24 +266,19 @@ function kernel.event(event, eventData)
end
function kernel.start()
local s, m
local s2, m2 = pcall(function()
local s, m = pcall(function()
repeat
local eventData = { os.pullEventRaw() }
local event = table.remove(eventData, 1)
kernel.event(event, eventData)
if event == 'kernel_halt' then
s = eventData[1]
m = eventData[2]
end
until event == 'kernel_halt'
end)
if (not s and m) or (not s2 and m2) then
if not s then
kernel.window.setVisible(true)
term.redirect(kernel.window)
print('\nCrash detected\n')
_G.printError(m or m2)
_G.printError(m)
end
term.redirect(kernel.terminal)
end
@@ -313,10 +295,9 @@ local function init(...)
for _,file in ipairs(files) do
local level = file:match('(%d).%S+.lua') or 99
if tonumber(level) <= runLevel then
-- All init programs run under the original shell
local s, m = shell.run(fs.combine(dir, file))
if not s then
error(m, -1)
error(m)
end
os.sleep(0)
end
@@ -330,15 +311,15 @@ local function init(...)
term.redirect(kernel.window)
shell.run('sys/apps/autorun.lua')
local win = window.create(kernel.terminal, 1, 1, w, h, true)
local s, m = kernel.run(_ENV, {
local shellWindow = window.create(kernel.terminal, 1, 1, w, h, false)
local s, m = kernel.run({
title = args[1],
path = 'sys/apps/shell.lua',
args = args,
window = win,
onExit = function(_, s, m)
kernel.halt(s, m)
end,
haltOnExit = true,
haltOnError = true,
terminal = shellWindow,
window = shellWindow,
})
if s then
kernel.raise(s.uid)
@@ -349,15 +330,11 @@ local function init(...)
end
end
kernel.run(_ENV, {
kernel.run({
fn = init,
title = 'init',
haltOnError = true,
args = { ... },
onExit = function(_, status, message)
if not status then
kernel.halt(status, message)
end
end,
})
kernel.start()

View File

@@ -0,0 +1,48 @@
local Array = require('opus.array')
local Config = require('opus.config')
local Util = require('opus.util')
local function getConfig()
return Config.load('alternate', {
shell = {
'sys/apps/shell.lua',
'rom/programs/shell.lua',
},
lua = {
'sys/apps/Lua.lua',
'rom/programs/lua.lua',
},
files = {
'sys/apps/Files.lua',
}
})
end
local Alt = { }
function Alt.get(key)
return getConfig()[key][1]
end
function Alt.set(key, value)
local config = getConfig()
Array.removeByValue(config[key], value)
table.insert(config[key], 1, value)
Config.update('alternate', config)
end
function Alt.remove(key, value)
local config = getConfig()
Array.removeByValue(config[key], value)
Config.update('alternate', config)
end
function Alt.add(key, value)
local config = getConfig()
if not Util.contains(config[key], value) then
table.insert(config[key], value)
Config.update('alternate', config)
end
end
return Alt

View File

@@ -1,5 +1,3 @@
local Util = require('opus.util')
local Array = { }
function Array.filter(it, f)
@@ -16,11 +14,9 @@ function Array.removeByValue(t, e)
for k,v in pairs(t) do
if v == e then
table.remove(t, k)
return e
break
end
end
end
Array.find = Util.find
return Array

View File

@@ -1,142 +0,0 @@
-- see: https://github.com/Rochet2/lualzw
-- MIT License - Copyright (c) 2016 Rochet2
local char = string.char
local type = type
local sub = string.sub
local tconcat = table.concat
local SIGC = 'LZWC'
local basedictcompress = {}
local basedictdecompress = {}
for i = 0, 255 do
local ic, iic = char(i), char(i, 0)
basedictcompress[ic] = iic
basedictdecompress[iic] = ic
end
local function dictAddA(str, dict, a, b)
if a >= 256 then
a, b = 0, b+1
if b >= 256 then
dict = {}
b = 1
end
end
dict[str] = char(a,b)
a = a+1
return dict, a, b
end
local function compress(input)
if type(input) ~= "string" then
error ("string expected, got "..type(input))
end
local len = #input
if len <= 1 then
return input
end
local dict = {}
local a, b = 0, 1
local result = { SIGC }
local resultlen = 1
local n = 2
local word = ""
for i = 1, len do
local c = sub(input, i, i)
local wc = word..c
if not (basedictcompress[wc] or dict[wc]) then
local write = basedictcompress[word] or dict[word]
if not write then
error "algorithm error, could not fetch word"
end
result[n] = write
resultlen = resultlen + #write
n = n+1
if len <= resultlen then
return input
end
dict, a, b = dictAddA(wc, dict, a, b)
word = c
else
word = wc
end
end
result[n] = basedictcompress[word] or dict[word]
resultlen = resultlen+#result[n]
if len <= resultlen then
return input
end
return tconcat(result)
end
local function dictAddB(str, dict, a, b)
if a >= 256 then
a, b = 0, b+1
if b >= 256 then
dict = {}
b = 1
end
end
dict[char(a,b)] = str
a = a+1
return dict, a, b
end
local function decompress(input)
if type(input) ~= "string" then
error( "string expected, got "..type(input))
end
if #input < 4 then
return input
end
local control = sub(input, 1, 4)
if control ~= SIGC then
return input
end
input = sub(input, 5)
local len = #input
if len < 2 then
error("invalid input - not a compressed string")
end
local dict = {}
local a, b = 0, 1
local result = {}
local n = 1
local last = sub(input, 1, 2)
result[n] = basedictdecompress[last] or dict[last]
n = n+1
for i = 3, len, 2 do
local code = sub(input, i, i+1)
local lastStr = basedictdecompress[last] or dict[last]
if not lastStr then
error( "could not find last from dict. Invalid input?")
end
local toAdd = basedictdecompress[code] or dict[code]
if toAdd then
result[n] = toAdd
n = n+1
dict, a, b = dictAddB(lastStr..sub(toAdd, 1, 1), dict, a, b)
else
local tmp = lastStr..sub(lastStr, 1, 1)
result[n] = tmp
n = n+1
dict, a, b = dictAddB(tmp, dict, a, b)
end
last = code
end
return tconcat(result)
end
return {
compress = compress,
decompress = decompress,
}

View File

@@ -1,270 +0,0 @@
-- see: https://github.com/luarocks/luarocks/blob/master/src/luarocks/tools/tar.lua
-- A pure-Lua implementation of untar (unpacking .tar archives)
local Util = require('opus.util')
local fs = _G.fs
local _sub = string.sub
local blocksize = 512
local function get_typeflag(flag)
if flag == "0" or flag == "\0" then return "file"
elseif flag == "1" then return "link"
elseif flag == "2" then return "symlink" -- "reserved" in POSIX, "symlink" in GNU
elseif flag == "3" then return "character"
elseif flag == "4" then return "block"
elseif flag == "5" then return "directory"
elseif flag == "6" then return "fifo"
elseif flag == "7" then return "contiguous" -- "reserved" in POSIX, "contiguous" in GNU
elseif flag == "x" then return "next file"
elseif flag == "g" then return "global extended header"
elseif flag == "L" then return "long name"
elseif flag == "K" then return "long link name"
end
return "unknown"
end
local function octal_to_number(octal)
local exp = 0
local number = 0
octal = octal:gsub("%s", "")
for i = #octal,1,-1 do
local digit = tonumber(octal:sub(i,i))
if not digit then
break
end
number = number + (digit * 8^exp)
exp = exp + 1
end
return number
end
local function checksum_header(block)
local sum = 256
for i = 1,148 do
local b = block:byte(i) or 0
sum = sum + b
end
for i = 157,500 do
local b = block:byte(i) or 0
sum = sum + b
end
return sum
end
local function nullterm(s)
return s:match("^[^%z]*")
end
local function read_header_block(block)
local header = {}
header.name = nullterm(block:sub(1,100))
header.mode = nullterm(block:sub(101,108)):gsub(" ", "")
header.uid = octal_to_number(nullterm(block:sub(109,116)))
header.gid = octal_to_number(nullterm(block:sub(117,124)))
header.size = octal_to_number(nullterm(block:sub(125,136)))
header.mtime = octal_to_number(nullterm(block:sub(137,148)))
header.chksum = octal_to_number(nullterm(block:sub(149,156)))
header.typeflag = get_typeflag(block:sub(157,157))
header.linkname = nullterm(block:sub(158,257))
header.magic = block:sub(258,263)
header.version = block:sub(264,265)
header.uname = nullterm(block:sub(266,297))
header.gname = nullterm(block:sub(298,329))
header.devmajor = octal_to_number(nullterm(block:sub(330,337)))
header.devminor = octal_to_number(nullterm(block:sub(338,345)))
header.prefix = block:sub(346,500)
if not checksum_header(block) == header.chksum then
return false, "Failed header checksum"
end
return header
end
local function untar_stream(tar_handle, destdir, verbose)
assert(type(destdir) == "string")
local long_name, long_link_name
local ok, err
local make_dir = function(a)
if not fs.exists(a) then
fs.makeDir(a)
end
return true
end
while true do
local block
repeat
block = tar_handle:read(blocksize)
until (not block) or checksum_header(block) > 256
if not block then break end
if #block < blocksize then
ok, err = nil, "Invalid block size -- corrupted file?"
break
end
local header
header, err = read_header_block(block)
if not header then
ok = false
break
end
local file_data = tar_handle:read(math.ceil(header.size / blocksize) * blocksize):sub(1,header.size)
if header.typeflag == "long name" then
long_name = nullterm(file_data)
elseif header.typeflag == "long link name" then
long_link_name = nullterm(file_data)
else
if long_name then
header.name = long_name
long_name = nil
end
if long_link_name then
header.name = long_link_name
long_link_name = nil
end
end
local pathname = fs.combine(destdir, header.name)
if header.typeflag == "directory" then
ok, err = make_dir(pathname)
if not ok then
break
end
elseif header.typeflag == "file" then
local dirname = fs.getDir(pathname)
if dirname ~= "" then
ok, err = make_dir(dirname)
if not ok then
break
end
end
local file_handle
if verbose then
print(pathname)
end
file_handle, err = io.open(pathname, "wb")
if not file_handle then
ok = nil
break
end
file_handle:write(file_data)
file_handle:close()
end
end
return ok, err
end
local function untar_string(str, destdir, verbose)
local ctr = 1
local len = #str
local handle = {
read = function(_, n)
if ctr < len then
local s = _sub(str, ctr, ctr + n - 1)
ctr = ctr + n
return s
end
end
}
return untar_stream(handle, destdir, verbose)
end
local function untar(filename, destdir, verbose)
assert(type(filename) == "string")
assert(type(destdir) == "string")
local tar_handle = io.open(filename, "rb")
if not tar_handle then return nil, "Error opening file "..filename end
local ok, err = untar_stream(tar_handle, destdir, verbose)
tar_handle:close()
return ok, err
end
local function create_header_block(filename, abspath)
local block = ('\0'):rep(blocksize)
local function number_to_octal(n)
return ('%o'):format(n)
end
local function ins(pos, istr)
block = block:sub(1, pos - 1) .. istr .. block:sub(pos + #istr)
end
ins(1, filename) -- header
ins(125, number_to_octal(fs.getSize(abspath)))
ins(157, '0') -- typeflag
ins(149, number_to_octal(checksum_header(block)))
return block
end
local function tar_stream(tar_handle, root, files)
if not files then
files = { }
local function recurse(rel)
local abs = fs.combine(root, rel)
for _,f in ipairs(fs.list(abs)) do
local fullName = fs.combine(abs, f)
if fs.isDir(fullName) then -- skip virtual dirs
recurse(fs.combine(rel, f))
else
table.insert(files, fs.combine(rel, f))
end
end
end
recurse('')
end
for _, file in pairs(files) do
local abs = fs.combine(root, file)
local block = create_header_block(file, abs)
tar_handle:write(block)
local f = Util.readFile(abs, 'rb')
tar_handle:write(f)
local padding = #f % blocksize
if padding > 0 then
tar_handle:write(('\0'):rep(blocksize - padding))
end
end
end
local function tar_string(root, files)
local t = { }
local handle = {
write = function(_, s)
table.insert(t, s)
end
}
tar_stream(handle, root, files)
return table.concat(t)
end
-- the bare minimum for this program to untar
local function tar(filename, root, files)
assert(type(filename) == "string")
assert(type(root) == "string")
local tar_handle = io.open(filename, "wb")
if not tar_handle then return nil, "Error opening file "..filename end
local ok, err = tar_stream(tar_handle, root, files)
tar_handle:close()
return ok, err
end
return {
tar = tar,
untar = untar,
tar_string = tar_string,
untar_string = untar_string,
}

View File

@@ -1,6 +1,7 @@
local Util = require('opus.util')
local fs = _G.fs
local shell = _ENV.shell
local Config = { }
@@ -24,6 +25,23 @@ function Config.load(fname, data)
return data
end
function Config.loadWithCheck(fname, data)
local filename = 'usr/config/' .. fname
if not fs.exists(filename) then
Config.load(fname, data)
print()
print('The configuration file has been created.')
print('The file name is: ' .. filename)
print()
_G.printError('Press enter to configure')
_G.read()
shell.run('edit ' .. filename)
end
return Config.load(fname, data)
end
function Config.update(fname, data)
local filename = 'usr/config/' .. fname
Util.writeTable(filename, data)

View File

@@ -12,11 +12,12 @@ local band = bit32.band
local blshift = bit32.lshift
local brshift = bit32.arshift
local textutils = _G.textutils
local mt = Util.byteArrayMT
local mod = 2^32
local tau = {("expand 16-byte k"):byte(1,-1)}
local sigma = {("expand 32-byte k"):byte(1,-1)}
local null32 = {("A"):rep(32):byte(1,-1)}
local null12 = {("A"):rep(12):byte(1,-1)}
local function rotl(n, b)
local s = n/(2^(32-b))
@@ -90,6 +91,22 @@ local function serialize(state)
return r
end
local mt = {
__tostring = function(a) return string.char(table.unpack(a)) end,
__index = {
toHex = function(self) return ("%02x"):rep(#self):format(table.unpack(self)) end,
isEqual = function(self, t)
if type(t) ~= "table" then return false end
if #self ~= #t then return false end
local ret = 0
for i = 1, #self do
ret = bit32.bor(ret, bxor(self[i], t[i]))
end
return ret == 0
end
}
}
local function crypt(data, key, nonce, cntr, round)
assert(type(key) == "table", "ChaCha20: Invalid key format ("..type(key).."), must be table")
assert(type(nonce) == "table", "ChaCha20: Invalid nonce format ("..type(nonce).."), must be table")
@@ -100,7 +117,7 @@ local function crypt(data, key, nonce, cntr, round)
cntr = tonumber(cntr) or 1
round = tonumber(round) or 20
local throttle = Util.throttle()
local throttle = Util.throttle(function() _syslog('throttle') end)
local out = {}
local state = initState(key, nonce, cntr)
local blockAmt = math.floor(#data/64)
@@ -116,12 +133,15 @@ local function crypt(data, key, nonce, cntr, round)
out[#out+1] = bxor(block[j], ks[j])
end
throttle()
--if i % 1000 == 0 then
throttle()
--os.queueEvent("")
--os.pullEvent("")
--end
end
return setmetatable(out, mt)
end
-- Helper functions
local function genNonce(len)
local nonce = {}
for i = 1, len do
@@ -150,9 +170,6 @@ end
local obj = {}
local rng_mt = {['__index'] = obj}
-- PRNG object
local null32 = {("A"):rep(32):byte(1,-1)}
local null12 = {("A"):rep(12):byte(1,-1)}
function obj:nextInt(byte)
if not byte or byte < 1 or byte > 6 then error("Can only return 1-6 bytes", 2) end
local output = 0

View File

@@ -1,12 +1,9 @@
local fq = require('opus.crypto.ecc.fq')
local elliptic = require('opus.crypto.ecc.elliptic')
local sha256 = require('opus.crypto.sha2')
local Util = require('opus.util')
local os = _G.os
local unpack = table.unpack
local mt = Util.byteArrayMT
local q = {1372, 62520, 47765, 8105, 45059, 9616, 65535, 65535, 65535, 65535, 65535, 65532}
@@ -30,7 +27,7 @@ local function publicKey(sk)
local Y = elliptic.scalarMulG(x)
local pk = elliptic.pointEncode(Y)
return setmetatable(pk, mt)
return pk
end
local function exchange(sk, pk)
@@ -65,7 +62,7 @@ local function sign(sk, message)
sig[#sig + 1] = s[i]
end
return setmetatable(sig, mt)
return sig
end
local function verify(pk, message, sig)

View File

@@ -9,7 +9,6 @@ local bnot = bit32 and bit32.bnot or bit.bnot
local bxor = bit32 and bit32.bxor or bit.bxor
local blshift = bit32 and bit32.lshift or bit.blshift
local upack = unpack or table.unpack
local mt = Util.byteArrayMT
local function rrotate(n, b)
local s = n/(2^b)
@@ -69,16 +68,17 @@ end
local function digestblock(w, C)
for j = 17, 64 do
local s0 = bxor(rrotate(w[j-15], 7), rrotate(w[j-15], 18), brshift(w[j-15], 3))
local s1 = bxor(rrotate(w[j-2], 17), rrotate(w[j-2], 19), brshift(w[j-2], 10))
-- local v = w[j-15]
local s0 = bxor(bxor(rrotate(w[j-15], 7), rrotate(w[j-15], 18)), brshift(w[j-15], 3))
local s1 = bxor(bxor(rrotate(w[j-2], 17), rrotate(w[j-2], 19)), brshift(w[j-2], 10))
w[j] = (w[j-16] + s0 + w[j-7] + s1)%mod32
end
local a, b, c, d, e, f, g, h = upack(C)
for j = 1, 64 do
local S1 = bxor(rrotate(e, 6), rrotate(e, 11), rrotate(e, 25))
local S1 = bxor(bxor(rrotate(e, 6), rrotate(e, 11)), rrotate(e, 25))
local ch = bxor(band(e, f), band(bnot(e), g))
local temp1 = (h + S1 + ch + K[j] + w[j])%mod32
local S0 = bxor(rrotate(a, 2), rrotate(a, 13), rrotate(a, 22))
local S0 = bxor(bxor(rrotate(a, 2), rrotate(a, 13)), rrotate(a, 22))
local maj = bxor(bxor(band(a, b), band(a, c)), band(b, c))
local temp2 = (S0 + maj)%mod32
h, g, f, e, d, c, b, a = g, f, e, (d+temp1)%mod32, c, b, a, (temp1+temp2)%mod32
@@ -94,6 +94,22 @@ local function digestblock(w, C)
return C
end
local mt = {
__tostring = function(a) return string.char(upack(a)) end,
__index = {
toHex = function(self) return ("%02x"):rep(#self):format(upack(self)) end,
isEqual = function(self, t)
if type(t) ~= "table" then return false end
if #self ~= #t then return false end
local ret = 0
for i = 1, #self do
ret = bit32.bor(ret, bxor(self[i], t[i]))
end
return ret == 0
end
}
}
local function toBytes(t, n)
local b = {}
for i = 1, n do

View File

@@ -2,87 +2,67 @@ local class = require('opus.class')
local os = _G.os
-- convert value to a string (supporting nils or numbers in value)
local function _val(a)
return a and tostring(a) or ''
end
local Entry = class()
function Entry:init(args)
self.pos = 0
self.scroll = 0
self.value = args.value
self.value = ''
self.width = args.width or 256
self.limit = args.limit or 1024
self.mark = { }
self.offset = args.offset or 1
self.transform = args.transform or function(a) return a end
end
function Entry:reset()
self.pos = 0
self.scroll = 0
self.value = nil
self.value = ''
self.mark = { }
end
function Entry:nextWord()
local value = _val(self.value)
return select(2, value:find("[%s%p]?%w[%s%p]", self.pos + 1)) or #value
return select(2, self.value:find("[%s%p]?%w[%s%p]", self.pos + 1)) or #self.value
end
function Entry:prevWord()
local value = _val(self.value)
local x = #value - (self.pos - 1)
local _, n = value:reverse():find("[%s%p]?%w[%s%p]", x)
return n and #value - n + 1 or 0
local x = #self.value - (self.pos - 1)
local _, n = self.value:reverse():find("[%s%p]?%w[%s%p]", x)
return n and #self.value - n + 1 or 0
end
function Entry:updateScroll()
local ps = self.scroll
local len = #_val(self.value)
if self.pos > len then
self.pos = len
if self.pos > #self.value then
self.pos = #self.value
self.scroll = 0 -- ??
end
if self.pos - self.scroll > self.width then
self.scroll = math.max(0, self.pos - self.width)
self.scroll = self.pos - self.width
elseif self.pos < self.scroll then
self.scroll = self.pos
end
if self.scroll > 0 then
if self.scroll + self.width > len then
self.scroll = math.max(0, len - self.width)
end
end
if ps ~= self.scroll then
self.textChanged = true
end
end
function Entry:copyText(cx, ex)
-- this should be transformed (ie. if number)
return _val(self.value):sub(cx + 1, ex)
return self.value:sub(cx + 1, ex)
end
function Entry:insertText(x, text)
text = tostring(self.transform(text) or '')
if #text > 0 then
local value = _val(self.value)
if #value + #text > self.limit then
text = text:sub(1, self.limit-#value)
end
self.value = self.transform(value:sub(1, x) .. text .. value:sub(x + 1))
self.pos = self.pos + #text
if #self.value + #text > self.limit then
text = text:sub(1, self.limit-#self.value)
end
self.value = self.value:sub(1, x) .. text .. self.value:sub(x + 1)
self.pos = self.pos + #text
end
function Entry:deleteText(sx, ex)
local value = _val(self.value)
local front = value:sub(1, sx)
local back = value:sub(ex + 1, #value)
self.value = self.transform(front .. back)
local front = self.value:sub(1, sx)
local back = self.value:sub(ex + 1, #self.value)
self.value = front .. back
self.pos = sx
end
@@ -94,7 +74,7 @@ function Entry:moveLeft()
end
function Entry:moveRight()
if self.pos < #_val(self.value) then
if self.pos < #self.value then
self.pos = self.pos + 1
return true
end
@@ -108,14 +88,14 @@ function Entry:moveHome()
end
function Entry:moveEnd()
if self.pos ~= #_val(self.value) then
self.pos = #_val(self.value)
if self.pos ~= #self.value then
self.pos = #self.value
return true
end
end
function Entry:moveTo(ie)
self.pos = math.max(0, math.min(ie.x + self.scroll - self.offset, #_val(self.value)))
self.pos = math.max(0, math.min(ie.x + self.scroll - self.offset, #self.value))
end
function Entry:backspace()
@@ -127,7 +107,7 @@ function Entry:backspace()
end
function Entry:moveWordRight()
if self.pos < #_val(self.value) then
if self.pos < #self.value then
self.pos = self:nextWord(self.value, self.pos + 1)
return true
end
@@ -143,7 +123,7 @@ end
function Entry:delete()
if self.mark.active then
self:deleteText(self.mark.x, self.mark.ex)
elseif self.pos < #_val(self.value) then
elseif self.pos < #self.value then
self:deleteText(self.pos, self.pos + 1)
end
end
@@ -157,16 +137,15 @@ function Entry:cutFromStart()
end
function Entry:cutToEnd()
local value = _val(self.value)
if self.pos < #value then
local text = self:copyText(self.pos, #value)
self:deleteText(self.pos, #value)
if self.pos < #self.value then
local text = self:copyText(self.pos, #self.value)
self:deleteText(self.pos, #self.value)
os.queueEvent('clipboard_copy', text)
end
end
function Entry:cutNextWord()
if self.pos < #_val(self.value) then
if self.pos < #self.value then
local ex = self:nextWord(self.value, self.pos)
local text = self:copyText(self.pos, ex)
self:deleteText(self.pos, ex)
@@ -191,7 +170,7 @@ function Entry:insertChar(ie)
end
function Entry:copy()
if #_val(self.value) > 0 then
if #self.value > 0 then
self.mark.continue = true
if self.mark.active then
self:copyMarked()
@@ -222,21 +201,15 @@ function Entry:paste(ie)
end
end
function Entry.forcePaste()
os.queueEvent('clipboard_paste')
end
function Entry:clearLine()
if #_val(self.value) > 0 then
if #self.value > 0 then
self:reset()
end
end
function Entry:markBegin()
if not self.mark.active then
if #_val(self.value) > 0 then
self.mark.active = true
end
self.mark.active = true
self.mark.anchor = { x = self.pos }
end
end
@@ -260,21 +233,16 @@ function Entry:unmark()
end
function Entry:markAnchor(ie)
local wasMarking = self.mark.active
self:unmark()
self:moveTo(ie)
self:markBegin()
self:markFinish()
self.textChanged = wasMarking
end
function Entry:markLeft()
self:markBegin()
if self:moveLeft() then
self:markFinish()
else
self.mark.continue = self.mark.active
end
end
@@ -282,8 +250,6 @@ function Entry:markRight()
self:markBegin()
if self:moveRight() then
self:markFinish()
else
self.mark.continue = self.mark.active
end
end
@@ -291,7 +257,7 @@ function Entry:markWord(ie)
local index = 1
self:moveTo(ie)
while true do
local s, e = _val(self.value):find('%w+', index)
local s, e = self.value:find('%w+', index)
if not s or s - 1 > self.pos then
break
end
@@ -311,8 +277,6 @@ function Entry:markNextWord()
self:markBegin()
if self:moveWordRight() then
self:markFinish()
else
self.mark.continue = self.mark.active
end
end
@@ -320,18 +284,16 @@ function Entry:markPrevWord()
self:markBegin()
if self:moveWordLeft() then
self:markFinish()
else
self.mark.continue = self.mark.active
end
end
function Entry:markAll()
if #_val(self.value) > 0 then
if #self.value > 0 then
self.mark.anchor = { x = 1 }
self.mark.active = true
self.mark.continue = true
self.mark.x = 0
self.mark.ex = #_val(self.value)
self.mark.ex = #self.value
self.textChanged = true
end
end
@@ -340,8 +302,6 @@ function Entry:markHome()
self:markBegin()
if self:moveHome() then
self:markFinish()
else
self.mark.continue = self.mark.active
end
end
@@ -349,8 +309,6 @@ function Entry:markEnd()
self:markBegin()
if self:moveEnd() then
self:markFinish()
else
self.mark.continue = self.mark.active
end
end
@@ -386,10 +344,9 @@ local mappings = {
--[ 'control-d' ] = Entry.cutNextWord,
[ 'control-x' ] = Entry.cut,
[ 'paste' ] = Entry.paste,
[ 'control-y' ] = Entry.forcePaste, -- well this won't work...
-- [ 'control-y' ] = Entry.paste, -- well this won't work...
[ 'mouse_doubleclick' ] = Entry.markWord,
[ 'mouse_tripleclick' ] = Entry.markAll,
[ 'shift-left' ] = Entry.markLeft,
[ 'shift-right' ] = Entry.markRight,
[ 'mouse_down' ] = Entry.markAnchor,
@@ -416,10 +373,6 @@ function Entry:process(ie)
action(self, ie)
if not self.value or #_val(self.value) == 0 then
self.value = nil
end
self.textChanged = self.textChanged or self.value ~= line
self.posChanged = pos ~= self.pos
self:updateScroll()

View File

@@ -58,14 +58,6 @@ function Routine:resume(event, ...)
else
s, m = coroutine.resume(self.co, event, ...)
end
if not s and event ~= 'terminate' then
if m and type(debug) == 'table' and debug.traceback then
local t = (debug.traceback(self.co, 1)) or ''
m = m .. '\n' .. t:match('%d\n(.+)')
end
end
if self:isDead() then
self.co = nil
self.filter = nil

View File

@@ -4,21 +4,17 @@ local linkfs = { }
-- TODO: implement broken links
local methods = { 'exists', 'getFreeSpace', 'getSize', 'attributes',
'isDir', 'isReadOnly', 'list', 'makeDir', 'open', 'getDrive' }
local methods = { 'exists', 'getFreeSpace', 'getSize',
'isDir', 'isReadOnly', 'list', 'listEx', 'makeDir', 'open', 'getDrive' }
for _,m in pairs(methods) do
linkfs[m] = function(node, dir, ...)
dir = linkfs.resolve(node, dir)
dir = dir:gsub(node.mountPoint, node.source, 1)
return fs[m](dir, ...)
end
end
function linkfs.resolve(node, dir)
return dir:gsub(node.mountPoint, node.source, 1)
end
function linkfs.mount(path, source)
function linkfs.mount(_, source)
if not source then
error('Source is required')
end
@@ -26,9 +22,6 @@ function linkfs.mount(path, source)
if not fs.exists(source) then
error('Source is missing')
end
if path == source then
return
end
if fs.isDir(source) then
return {
source = source,
@@ -72,4 +65,4 @@ function linkfs.move(node, s, t)
return fs.move(s, t)
end
return linkfs
return linkfs

View File

@@ -6,6 +6,7 @@ local fs = _G.fs
local netfs = { }
local function remoteCommand(node, msg)
for _ = 1, 2 do
if not node.socket then
node.socket = Socket.connect(node.id, 139)
@@ -32,17 +33,17 @@ local function remoteCommand(node, msg)
error('netfs: Connection failed', 2)
end
local methods = { 'delete', 'exists', 'getFreeSpace', 'makeDir', 'list', 'listEx', 'attributes' }
local methods = { 'delete', 'exists', 'getFreeSpace', 'makeDir', 'list', 'listEx' }
local function resolve(node, dir)
local function resolveDir(dir, node)
-- TODO: Wrong ! (does not support names with dashes)
dir = dir:gsub(node.mountPoint, '', 1)
return fs.combine(node.source, dir)
return fs.combine(node.directory, dir)
end
for _,m in pairs(methods) do
netfs[m] = function(node, dir)
dir = resolve(node, dir)
dir = resolveDir(dir, node)
return remoteCommand(node, {
fn = m,
@@ -51,14 +52,14 @@ for _,m in pairs(methods) do
end
end
function netfs.mount(_, id, source)
function netfs.mount(_, id, directory)
if not id or not tonumber(id) then
error('ramfs syntax: computerId [directory]')
end
return {
id = tonumber(id),
nodes = { },
source = source or '',
directory = directory or '',
}
end
@@ -67,7 +68,7 @@ function netfs.getDrive()
end
function netfs.complete(node, partial, dir, includeFiles, includeSlash)
dir = resolve(node, dir)
dir = resolveDir(dir, node)
return remoteCommand(node, {
fn = 'complete',
@@ -76,8 +77,8 @@ function netfs.complete(node, partial, dir, includeFiles, includeSlash)
end
function netfs.copy(node, s, t)
s = resolve(node, s)
t = resolve(node, t)
s = resolveDir(s, node)
t = resolveDir(t, node)
return remoteCommand(node, {
fn = 'copy',
@@ -86,37 +87,37 @@ function netfs.copy(node, s, t)
end
function netfs.isDir(node, dir)
if dir == node.mountPoint and node.source == '' then
if dir == node.mountPoint and node.directory == '' then
return true
end
return remoteCommand(node, {
fn = 'isDir',
args = { resolve(node, dir) },
args = { resolveDir(dir, node) },
})
end
function netfs.isReadOnly(node, dir)
if dir == node.mountPoint and node.source == '' then
if dir == node.mountPoint and node.directory == '' then
return false
end
return remoteCommand(node, {
fn = 'isReadOnly',
args = { resolve(node, dir) },
args = { resolveDir(dir, node) },
})
end
function netfs.getSize(node, dir)
if dir == node.mountPoint and node.source == '' then
if dir == node.mountPoint and node.directory == '' then
return 0
end
return remoteCommand(node, {
fn = 'getSize',
args = { resolve(node, dir) },
args = { resolveDir(dir, node) },
})
end
function netfs.find(node, spec)
spec = resolve(node, spec)
spec = resolveDir(spec, node)
local list = remoteCommand(node, {
fn = 'find',
args = { spec },
@@ -130,8 +131,8 @@ function netfs.find(node, spec)
end
function netfs.move(node, s, t)
s = resolve(node, s)
t = resolve(node, t)
s = resolveDir(s, node)
t = resolveDir(t, node)
return remoteCommand(node, {
fn = 'move',
@@ -140,7 +141,7 @@ function netfs.move(node, s, t)
end
function netfs.open(node, fn, fl)
fn = resolve(node, fn)
fn = resolveDir(fn, node)
local vfh = remoteCommand(node, {
fn = 'open',

View File

@@ -9,28 +9,15 @@ function ramfs.mount(_, nodeType)
return {
nodes = { },
size = 0,
created = os.epoch('utc'),
modification = os.epoch('utc'),
}
elseif nodeType == 'file' then
return {
size = 0,
created = os.epoch('utc'),
modification = os.epoch('utc'),
}
end
error('ramfs syntax: [directory, file]')
end
function ramfs.attributes(node)
return {
created = node.created,
isDir = not not node.nodes,
modification = node.modification,
size = node.size,
}
end
function ramfs.delete(node, dir)
if node.mountPoint == dir then
fs.unmount(node.mountPoint)
@@ -53,10 +40,8 @@ function ramfs.makeDir(_, dir)
fs.mount(dir, 'ramfs', 'directory')
end
function ramfs.isDir(node, dir)
if node.mountPoint == dir then
return not not node.nodes
end
function ramfs.isDir(node)
return not not node.nodes
end
function ramfs.getDrive()
@@ -79,70 +64,28 @@ function ramfs.list(node, dir)
end
function ramfs.open(node, fn, fl)
local modes = Util.transpose { 'r', 'w', 'rb', 'wb', 'a' }
if not modes[fl] then
if fl ~= 'r' and fl ~= 'w' and fl ~= 'rb' and fl ~= 'wb' then
error('Unsupported mode')
end
if fl == 'a' then
if node.mountPoint ~= fn then
fl = 'w'
else
local c = type(node.contents) == 'table'
and string.char(table.unpack(node.contents))
or node.contents
or ''
return {
write = function(str)
c = c .. str
end,
writeLine = function(str)
c = c .. str .. '\n'
end,
flush = function()
node.contents = c
node.size = #c
end,
close = function()
node.contents = c
node.size = #c
c = nil
end,
}
end
end
if fl == 'r' then
if node.mountPoint ~= fn then
return
end
local c = type(node.contents) == 'table'
and string.char(table.unpack(node.contents))
or node.contents
local ctr = 0
local lines
return {
read = function(n)
n = n or 1
if ctr >= node.size then
return
end
local t = c:sub(ctr + 1, ctr + n)
ctr = ctr + n
return t
end,
readLine = function()
if not lines then
lines = Util.split(c)
lines = Util.split(node.contents)
end
ctr = ctr + 1
return lines[ctr]
end,
readAll = function()
return c
return node.contents
end,
close = function()
lines = nil
@@ -174,30 +117,11 @@ function ramfs.open(node, fn, fl)
return
end
local c = node.contents
if type(node.contents) == 'string' then
c = { }
for i = 1, node.size do
c[i] = node.contents:sub(i, i):byte()
end
end
local ctr = 0
return {
readAll = function()
return string.char(table.unpack(c))
end,
read = function(n)
if n and n > 1 and ctr < node.size then
-- some programs open in rb, when it should have
-- been opened in r - attempt to support multiple read
-- if nils are present in data, this will fail
local t = string.char(table.unpack(c, ctr + 1, ctr + n))
ctr = ctr + n
return t
end
read = function()
ctr = ctr + 1
return c[ctr]
return node.contents[ctr]
end,
close = function()
end,
@@ -209,13 +133,7 @@ function ramfs.open(node, fn, fl)
local c = { }
return {
write = function(b)
if type(b) == 'number' then
table.insert(c, b)
else
for i = 1, #b do
table.insert(c, b:sub(i, i):byte())
end
end
table.insert(c, b)
end,
flush = function()
node.contents = c

View File

@@ -5,46 +5,29 @@ local fs = _G.fs
local urlfs = { }
function urlfs.mount(path, url, force)
function urlfs.mount(_, url)
if not url then
error('URL is required')
end
-- only mount if the file does not exist already
if not fs.exists(path) or force then
return {
url = url,
created = os.epoch('utc'),
modification = os.epoch('utc'),
}
end
end
function urlfs.attributes(node, path)
return path == node.mountPoint and {
created = node.created,
isDir = false,
modification = node.modification,
size = node.size or 0,
return {
url = url,
}
end
function urlfs.delete(node, path)
if path == node.mountPoint then
fs.unmount(path)
end
function urlfs.delete(_, dir)
fs.unmount(dir)
end
function urlfs.exists(node, path)
return path == node.mountPoint
function urlfs.exists()
return true
end
function urlfs.getSize(node, path)
return path == node.mountPoint and node.size or 0
function urlfs.getSize(node)
return node.size or 0
end
function urlfs.isReadOnly()
return false
return true
end
function urlfs.isDir()
@@ -67,6 +50,14 @@ function urlfs.open(node, fn, fl)
local c = node.cache
if not c then
--[[
if node.url:match("^(rttps?:)") then
local s, response = rttp.get(node.url)
c = s and response.statusCode == 200 and response.data
else
c = Util.httpGet(node.url)
end
]]--
c = Util.httpGet(node.url)
if c then
node.cache = c
@@ -83,10 +74,6 @@ function urlfs.open(node, fn, fl)
if fl == 'r' then
return {
read = function()
ctr = ctr + 1
return c:sub(ctr, ctr)
end,
readLine = function()
if not lines then
lines = Util.split(c)
@@ -103,9 +90,6 @@ function urlfs.open(node, fn, fl)
}
end
return {
readAll = function()
return c
end,
read = function()
ctr = ctr + 1
return c:sub(ctr, ctr):byte()

View File

@@ -1,56 +0,0 @@
local find = string.find
local floor = math.floor
local min = math.min
local max = math.max
local sub = string.sub
-- https://rosettacode.org/wiki/Jaro_distance (ported to lua)
return function(s1, s2)
local l1, l2 = #s1, #s2;
if l1 == 0 then
return l2 == 0 and 1.0 or 0.0
end
local match_distance = max(floor(max(l1, l2) / 2) - 1, 0)
local s1_matches = { }
local s2_matches = { }
local matches = 0
for i = 1, l1 do
local _end = min(i + match_distance + 1, l2)
for k = max(1, i - match_distance), _end do
if not s2_matches[k] and sub(s1, i, i) == sub(s2, k, k) then
s1_matches[i] = true
s2_matches[k] = true
matches = matches + 1
break
end
end
end
if matches == 0 then
return 0.0
end
local t = 0.0
local k = 1
for i = 1, l1 do
if s1_matches[i] then
while not s2_matches[k] do
k = k + 1
end
if sub(s1, i, i) ~= sub(s2, k, k) then
t = t + 0.5
end
k = k + 1
end
end
-- provide a major boost for exact matches
local b = 0.0
if find(s1, s2, 1, true) then
b = b + .5
end
local m = matches
return (m / l1 + m / l2 + (m - t) / m) / 3.0 + b
end

View File

@@ -3,13 +3,15 @@ local Util = require('opus.util')
local TREE_URL = 'https://api.github.com/repos/%s/%s/git/trees/%s?recursive=1'
local FILE_URL = 'https://raw.githubusercontent.com/%s/%s/%s/%s'
local TREE_HEADERS = {}
local git = { }
if _G._GIT_API_KEY then
TREE_HEADERS.Authorization = 'token ' .. _G._GIT_API_KEY
TREE_URL = TREE_URL .. '&access_token=' .. _G._GIT_API_KEY
end
local fs = _G.fs
local os = _G.os
function git.list(repository)
local t = Util.split(repository, '(.-)/')
@@ -24,10 +26,8 @@ function git.list(repository)
local function getContents()
local dataUrl = string.format(TREE_URL, user, repo, branch)
local contents, msg = Util.httpGet(dataUrl, TREE_HEADERS)
if not contents then
error(string.format('Failed to download %s\n%s', dataUrl, msg), 2)
else
local contents = Util.download(dataUrl)
if contents then
return json.decode(contents)
end
end

View File

@@ -1,70 +1,17 @@
local Util = require('opus.util')
local GPS = { }
GPS.CHANNEL_GPS = 65534
local device = _G.device
local vector = _G.vector
local gps = _G.gps
function GPS.locate(timeout, debug)
if not device.wireless_modem then
if debug then
print('No wireless modem attached')
end
return nil
local pt = { }
timeout = timeout or 10
pt.x, pt.y, pt.z = gps.locate(timeout, debug)
if pt.x then
return pt
end
if debug then
print('Finding position...')
end
local modem = device.wireless_modem
local closeChannel = false
if not modem.isOpen(GPS.CHANNEL_GPS) then
modem.open(GPS.CHANNEL_GPS)
closeChannel = true
end
modem.transmit(GPS.CHANNEL_GPS, GPS.CHANNEL_GPS, "PING")
local fixes = {}
local pos = nil
local timer = os.startTimer(timeout or 1)
while true do
local e, side, chan, reply, msg, dist = os.pullEvent()
if e == "modem_message" then
if side == modem.side and chan == GPS.CHANNEL_GPS and reply == GPS.CHANNEL_GPS and dist then
if type(msg) == "table" and #msg == 3 and tonumber(msg[1]) and tonumber(msg[2]) and tonumber(msg[3]) then
local fix = {
position = vector.new(unpack(msg)),
distance = dist,
}
if debug then
print(fix.distance..' meters from '..fix.position:tostring())
end
if fix.distance == 0 then
pos = fix.position
else
fixes[#fixes+1] = fix
if #fixes > 3 then
pos = GPS.trilaterate(fixes)
if pos then break end
end
end
end
end
elseif e == "timer" and side == timer then
break
end
end
if closeChannel then
modem.close(GPS.CHANNEL_GPS)
end
if debug then
print("Position is "..pos.x..","..pos.y..","..pos.z)
end
return pos and vector.new(pos.x, pos.y, pos.z)
end
function GPS.isAvailable()
@@ -119,26 +66,26 @@ local function trilaterate(A, B, C)
local result1 = result + (ez * z)
local result2 = result - (ez * z)
local rounded1, rounded2 = result1:round(0.01), result2:round(0.01)
local rounded1, rounded2 = result1:round(), result2:round()
if rounded1.x ~= rounded2.x or rounded1.y ~= rounded2.y or rounded1.z ~= rounded2.z then
return rounded1, rounded2
else
return rounded1
end
end
return result:round(0.01)
return result:round()
end
local function narrow( p1, p2, fix )
local dist1 = math.abs( (p1 - fix.position):length() - fix.distance )
local dist2 = math.abs( (p2 - fix.position):length() - fix.distance )
if math.abs(dist1 - dist2) < 0.01 then
if math.abs(dist1 - dist2) < 0.05 then
return p1, p2
elseif dist1 < dist2 then
return p1:round(0.01)
return p1:round()
else
return p2:round(0.01)
return p2:round()
end
end
-- end stock gps api
@@ -151,7 +98,7 @@ function GPS.trilaterate(tFixes)
if pos2 then
pos1, pos2 = narrow(pos1, pos2, tFixes[1])
end
if not pos2 and pos1 and not (pos1.x ~= pos1.x) then
if not pos2 then
return pos1, attemps
end
end

View File

@@ -1,51 +1,40 @@
-- https://www.lua.org/manual/5.1/manual.html#pdf-require
-- https://github.com/LuaDist/lua/blob/d2e7e7d4d43ff9068b279a617c5b2ca2c2771676/src/loadlib.c
local function split(str, pattern)
local t = { }
local function helper(line) table.insert(t, line) return "" end
helper((str:gsub(pattern, helper)))
return t
end
local defaultPath = { }
do
local function split(str)
local t = { }
local function helper(line) table.insert(t, line) return "" end
helper((str:gsub('(.-);', helper)))
return t
end
local function insert(p)
for _,v in pairs(defaultPath) do
if v == p then
return
end
end
table.insert(defaultPath, p)
end
local paths = '?.lua;?/init.lua;'
paths = paths .. '/usr/modules/?.lua;/usr/modules/?/init.lua;'
paths = paths .. '/rom/modules/main/?;/rom/modules/main/?.lua;/rom/modules/main/?/init.lua;'
paths = paths .. '/sys/modules/?.lua;/sys/modules/?/init.lua'
for _,v in pairs(split(paths)) do
insert(v)
end
local luaPaths = package and package.path and split(package.path) or { }
for _,v in pairs(luaPaths) do
if v ~= '?' then
insert(v)
end
local hasMain
local luaPaths = package and package.path and split(package.path, '(.-);') or { }
for i = 1, #luaPaths do
if luaPaths[i] == '?' or luaPaths[i] == '?.lua' or luaPaths[i] == '?/init.lua' then
luaPaths[i] = nil
elseif string.find(luaPaths[i], '/rom/modules/main') then
hasMain = true
end
end
local DEFAULT_PATH = table.concat(defaultPath, ';')
table.insert(luaPaths, 1, '?.lua')
table.insert(luaPaths, 2, '?/init.lua')
table.insert(luaPaths, 3, '/usr/modules/?.lua')
table.insert(luaPaths, 4, '/usr/modules/?/init.lua')
if not hasMain then
table.insert(luaPaths, 5, '/rom/modules/main/?')
table.insert(luaPaths, 6, '/rom/modules/main/?.lua')
table.insert(luaPaths, 7, '/rom/modules/main/?/init.lua')
end
table.insert(luaPaths, '/sys/modules/?.lua')
table.insert(luaPaths, '/sys/modules/?/init.lua')
local DEFAULT_PATH = table.concat(luaPaths, ';')
local fs = _G.fs
local os = _G.os
local string = _G.string
-- Add require and package to the environment
return function(env, programDir)
return function(env)
local function preloadSearcher(modname)
if env.package.preload[modname] then
return function()
@@ -54,75 +43,77 @@ return function(env, programDir)
end
end
local function loadedSearcher(modname)
if env.package.loaded[modname] then
return function()
return env.package.loaded[modname]
end
end
end
local sentinel = { }
local function pathSearcher(modname)
if env.package.loaded[modname] == sentinel then
error("loop or previous error loading module '" .. modname .. "'", 0)
end
env.package.loaded[modname] = sentinel
local fname = modname:gsub('%.', '/')
for pattern in string.gmatch(env.package.path, "[^;]+") do
local sPath = string.gsub(pattern, "%?", fname)
-- TODO: if there's no shell, we should not be checking relative paths below
-- as they will resolve to root directory
if env.shell and
type(env.shell.getRunningProgram) == 'function' and
sPath:sub(1, 1) ~= "/" then
if programDir and sPath:sub(1, 1) ~= "/" then
sPath = fs.combine(programDir, sPath)
sPath = fs.combine(fs.getDir(env.shell.getRunningProgram() or ''), sPath)
end
if fs.exists(sPath) and not fs.isDir(sPath) then
return loadfile(fs.combine(sPath, ''), env)
return loadfile(sPath, env)
end
end
end
-- place package and require function into env
env.package = {
path = env.LUA_PATH or _G.LUA_PATH or DEFAULT_PATH,
cpath = '',
config = '/\n:\n?\n!\n-',
path = env.LUA_PATH or _G.LUA_PATH or DEFAULT_PATH,
config = '/\n:\n?\n!\n-',
preload = { },
loaded = {
bit32 = bit32,
loaded = {
coroutine = coroutine,
_G = env._G,
io = io,
math = math,
os = os,
string = string,
table = table,
debug = debug,
utf8 = utf8,
},
loaders = {
preloadSearcher,
loadedSearcher,
pathSearcher,
}
}
env.package.loaded.package = env.package
local sentinel = { }
function env.require(modname)
if env.package.loaded[modname] then
if env.package.loaded[modname] == sentinel then
error("loop or previous error loading module '" .. modname .. "'", 0)
end
return env.package.loaded[modname]
end
local t = { }
for _,searcher in ipairs(env.package.loaders) do
local fn, msg = searcher(modname)
if type(fn) == 'function' then
env.package.loaded[modname] = sentinel
local module = fn(modname, env) or true
if fn then
local module, msg2 = fn(modname, env)
if not module then
error(msg2 or (modname .. ' module returned nil'), 2)
end
env.package.loaded[modname] = module
return module
end
if msg then
table.insert(t, msg)
error(msg, 2)
end
end
if #t > 0 then
error(table.concat(t, '\n'), 2)
end
error('Unable to find module ' .. modname, 2)
end
return env.require -- backwards compatible
end

View File

@@ -50,20 +50,18 @@ function input:toCode(ch, code)
table.insert(result, 'alt')
end
if ch then -- some weird things happen with control/command on mac
if keyboard.state[keys.leftShift] or keyboard.state[keys.rightShift] or
code == keys.leftShift or code == keys.rightShift then
if code and modifiers[code] then
table.insert(result, 'shift')
elseif #ch == 1 then
table.insert(result, ch:upper())
else
table.insert(result, 'shift')
table.insert(result, ch)
end
elseif not code or not modifiers[code] then
if keyboard.state[keys.leftShift] or keyboard.state[keys.rightShift] or
code == keys.leftShift or code == keys.rightShift then
if code and modifiers[code] then
table.insert(result, 'shift')
elseif #ch == 1 then
table.insert(result, ch:upper())
else
table.insert(result, 'shift')
table.insert(result, ch)
end
elseif not code or not modifiers[code] then
table.insert(result, ch)
end
return table.concat(result, '-')
@@ -120,7 +118,6 @@ function input:translate(event, code, p1, p2)
local buttons = { 'mouse_click', 'mouse_rightclick' }
self.mch = buttons[code]
self.mfired = nil
self.anchor = { x = p1, y = p2 }
return {
code = input:toCode('mouse_down', 255),
button = code,
@@ -135,8 +132,6 @@ function input:translate(event, code, p1, p2)
button = code,
x = p1,
y = p2,
dx = p1 - self.anchor.x,
dy = p2 - self.anchor.y,
}
elseif event == 'mouse_up' then
@@ -146,26 +141,18 @@ function input:translate(event, code, p1, p2)
p1 == self.x and p2 == self.y and
(clock - self.timer < .5) then
self.clickCount = self.clickCount + 1
if self.clickCount == 3 then
self.mch = 'mouse_tripleclick'
self.timer = nil
self.clickCount = 1
else
self.mch = 'mouse_doubleclick'
end
self.mch = 'mouse_doubleclick'
self.timer = nil
else
self.timer = os.clock()
self.x = p1
self.y = p2
self.clickCount = 1
end
self.mfired = input:toCode(self.mch, 255)
else
self.mch = 'mouse_up'
self.mfired = input:toCode(self.mch, 255)
end
return {
code = self.mfired,
button = code,
@@ -189,22 +176,11 @@ function input:translate(event, code, p1, p2)
end
end
if not ({ ...})[1] then
local colors = _G.colors
local term = _G.term
function input:test()
while true do
local e = { os.pullEvent() }
local ch = input:translate(table.unpack(e))
local ch = self:translate(os.pullEvent())
if ch then
term.setTextColor(colors.white)
print(table.unpack(e))
term.setTextColor(colors.lime)
local t = { }
for k,v in pairs(ch) do
table.insert(t, k .. ':' .. v)
end
print('--> ' .. table.concat(t, ' ') .. '\n')
Util.print(ch)
end
end
end

View File

@@ -8,7 +8,6 @@ Map.merge = Util.merge
Map.shallowCopy = Util.shallowCopy
Map.find = Util.find
Map.filter = Util.filter
Map.transpose = Util.transpose
function Map.removeMatches(t, values)
local function matchAll(entry)

View File

@@ -1,19 +1,16 @@
local Util = require('opus.util')
local colors = _G.colors
local NFT = { }
-- largely copied from http://www.computercraft.info/forums2/index.php?/topic/5029-145-npaintpro/
local hexToColor = { }
local tColourLookup = { }
for n = 1, 16 do
hexToColor[string.sub("0123456789abcdef", n, n)] = 2 ^ (n - 1)
tColourLookup[string.byte("0123456789abcdef", n, n)] = 2 ^ (n - 1)
end
local colorToHex = Util.transpose(hexToColor)
local function getColourOf(hex)
return hexToColor[hex]
return tColourLookup[hex:byte()]
end
function NFT.parse(imageText)
@@ -65,22 +62,8 @@ function NFT.parse(imageText)
return image
end
function NFT.transparency(image)
for y = 1, image.height do
for _,key in pairs(Util.keys(image.fg[y])) do
if image.fg[y][key] == colors.magenta then
image.fg[y][key] = nil
end
end
for _,key in pairs(Util.keys(image.bg[y])) do
if image.bg[y][key] == colors.magenta then
image.bg[y][key] = nil
end
end
end
end
function NFT.load(path)
local imageText = Util.readFile(path)
if not imageText then
error('Unable to read image file')
@@ -88,35 +71,4 @@ function NFT.load(path)
return NFT.parse(imageText)
end
function NFT.save(image, filename)
local bgcode, txcode = '\30', '\31'
local output = { }
for y = 1, image.height do
local lastBG, lastFG
if image.text[y] then
for x = 1, #image.text[y] do
local bg = image.bg[y][x] or colors.magenta
if bg ~= lastBG then
lastBG = bg
table.insert(output, bgcode .. colorToHex[bg])
end
local fg = image.fg[y][x] or colors.magenta
if fg ~= lastFG then
lastFG = fg
table.insert(output, txcode .. colorToHex[fg])
end
table.insert(output, image.text[y][x])
end
end
if y < image.height then
table.insert(output, '\n')
end
end
Util.writeFile(filename, table.concat(output))
end
return NFT

View File

@@ -6,11 +6,12 @@ local Util = require('opus.util')
local device = _G.device
local os = _G.os
local network = _G.network
local socketClass = { }
function socketClass:read(timeout)
local data, distance = _G.network.getTransport().read(self)
local data, distance = network.getTransport().read(self)
if data then
return data, distance
end
@@ -25,7 +26,7 @@ function socketClass:read(timeout)
local e, id = os.pullEvent()
if e == 'transport_' .. self.uid then
data, distance = _G.network.getTransport().read(self)
data, distance = network.getTransport().read(self)
if data then
os.cancelTimer(timerId)
return data, distance
@@ -46,7 +47,7 @@ end
function socketClass:write(data)
if self.connected then
_G.network.getTransport().write(self, {
network.getTransport().write(self, {
type = 'DATA',
seq = self.wseq,
data = data,
@@ -57,7 +58,7 @@ end
function socketClass:ping()
if self.connected then
_G.network.getTransport().ping(self)
network.getTransport().ping(self)
return true
end
end
@@ -71,7 +72,7 @@ function socketClass:close()
self.connected = false
end
device.wireless_modem.close(self.sport)
_G.network.getTransport().close(self)
network.getTransport().close(self)
end
local Socket = { }
@@ -125,11 +126,7 @@ function Socket.connect(host, port, options)
local socket = newSocket(host == os.getComputerID())
socket.dhost = tonumber(host)
if options and options.keypair then
socket.privKey, socket.pubKey = table.unpack(options.keypair)
else
socket.privKey, socket.pubKey = _G.network.getKeyPair()
end
socket.privKey, socket.pubKey = network.getKeyPair()
local identifier = options and options.identifier or Security.getIdentifier()
socket.transmit(port, socket.sport, {
@@ -138,7 +135,7 @@ function Socket.connect(host, port, options)
dhost = socket.dhost,
t = Crypto.encrypt({ -- this is not that much data...
ts = os.epoch('utc'),
pk = socket.pubKey:toHex(),
pk = Util.byteArrayToHex(socket.pubKey),
}, Util.hexToByteArray(identifier)),
})
@@ -158,7 +155,7 @@ function Socket.connect(host, port, options)
socket.remotePubKey = Util.hexToByteArray(msg.pk)
socket.options = msg.options or { }
setupCrypto(socket, true)
_G.network.getTransport().open(socket)
network.getTransport().open(socket)
return socket
elseif msg.type == 'NOPASS' then
@@ -191,7 +188,7 @@ local function trusted(socket, msg, options)
if data and data.ts and tonumber(data.ts) then
if math.abs(os.epoch('utc') - data.ts) < 4096 then
socket.remotePubKey = Util.hexToByteArray(data.pk)
socket.privKey, socket.pubKey = _G.network.getKeyPair()
socket.privKey, socket.pubKey = network.getKeyPair()
setupCrypto(socket)
return true
end
@@ -236,11 +233,11 @@ function Socket.server(port, options)
type = 'CONN',
dhost = socket.dhost,
shost = socket.shost,
pk = socket.pubKey:toHex(),
pk = Util.byteArrayToHex(socket.pubKey),
options = socket.options.ENCRYPT and { ENCRYPT = true },
})
_G.network.getTransport().open(socket)
network.getTransport().open(socket)
return socket
else

View File

@@ -33,69 +33,64 @@ function Terminal.window(parent, sx, sy, w, h, isVisible)
end
local win = { }
local maxScroll
local maxScroll = 100
local cx, cy = 1, 1
local blink = false
local _bg, _fg = colors.black, colors.white
local bg, fg = parent.getBackgroundColor(), parent.getTextColor()
win.canvas = Canvas({
local canvas = Canvas({
x = sx,
y = sy,
width = w,
height = h,
isColor = parent.isColor(),
offy = 0,
bg = _bg,
fg = _fg,
})
win.canvas = canvas
local function update()
if isVisible then
win.canvas:render(parent)
canvas:render(parent)
win.setCursorPos(cx, cy)
end
end
local function scrollTo(y)
y = math.max(0, y)
y = math.min(#win.canvas.lines - win.canvas.height, y)
y = math.min(#canvas.lines - canvas.height, y)
if y ~= win.canvas.offy then
win.canvas.offy = y
win.canvas:dirty()
if y ~= canvas.offy then
canvas.offy = y
canvas:dirty()
update()
end
end
function win.write(str)
str = tostring(str) or ''
win.canvas:write(cx, cy + win.canvas.offy, str, win.canvas.bg, win.canvas.fg)
canvas:write(cx, cy + canvas.offy, str, bg, fg)
win.setCursorPos(cx + #str, cy)
update()
end
function win.blit(str, fg, bg)
win.canvas:blit(cx, cy + win.canvas.offy, str, bg, fg)
canvas:blit(cx, cy + canvas.offy, str, bg, fg)
win.setCursorPos(cx + #str, cy)
update()
end
function win.clear()
win.canvas.offy = 0
for i = #win.canvas.lines, win.canvas.height + 1, -1 do
win.canvas.lines[i] = nil
canvas.offy = 0
for i = #canvas.lines, canvas.height + 1, -1 do
canvas.lines[i] = nil
end
win.canvas:clear()
canvas:clear(bg, fg)
update()
end
function win.getLine(n)
local line = win.canvas.lines[n]
return line.text, line.fg, line.bg
end
function win.clearLine()
win.canvas:clearLine(cy + win.canvas.offy)
canvas:clearLine(cy + canvas.offy, bg, fg)
win.setCursorPos(cx, cy)
update()
end
@@ -107,14 +102,10 @@ function Terminal.window(parent, sx, sy, w, h, isVisible)
function win.setCursorPos(x, y)
cx, cy = math.floor(x), math.floor(y)
if isVisible then
parent.setCursorPos(cx + win.canvas.x - 1, cy + win.canvas.y - 1)
parent.setCursorPos(cx + canvas.x - 1, cy + canvas.y - 1)
end
end
function win.getCursorBlink()
return blink
end
function win.setCursorBlink(b)
blink = b
if isVisible then
@@ -123,12 +114,12 @@ function Terminal.window(parent, sx, sy, w, h, isVisible)
end
function win.isColor()
return win.canvas.isColor
return canvas.isColor
end
win.isColour = win.isColor
function win.setTextColor(c)
win.canvas.fg = c
fg = c
end
win.setTextColour = win.setTextColor
@@ -148,38 +139,38 @@ function Terminal.window(parent, sx, sy, w, h, isVisible)
win.setPaletteColour = win.setPaletteColor
function win.setBackgroundColor(c)
win.canvas.bg = c
bg = c
end
win.setBackgroundColour = win.setBackgroundColor
function win.getSize()
return win.canvas.width, win.canvas.height
return canvas.width, canvas.height
end
function win.scroll(n)
n = n or 1
if n > 0 then
local lines = #win.canvas.lines
local lines = #canvas.lines
for i = 1, n do
win.canvas.lines[lines + i] = { }
win.canvas:clearLine(lines + i)
canvas.lines[lines + i] = { }
canvas:clearLine(lines + i, bg, fg)
end
while #win.canvas.lines > (maxScroll or win.canvas.height) do
table.remove(win.canvas.lines, 1)
while #canvas.lines > maxScroll do
table.remove(canvas.lines, 1)
end
scrollTo(#win.canvas.lines)
win.canvas:dirty()
scrollTo(#canvas.lines)
canvas:dirty()
update()
end
end
function win.getTextColor()
return win.canvas.fg
return fg
end
win.getTextColour = win.getTextColor
function win.getBackgroundColor()
return win.canvas.bg
return bg
end
win.getBackgroundColour = win.getBackgroundColor
@@ -187,7 +178,7 @@ function Terminal.window(parent, sx, sy, w, h, isVisible)
if visible ~= isVisible then
isVisible = visible
if isVisible then
win.canvas:dirty()
canvas:dirty()
update()
end
end
@@ -195,7 +186,7 @@ function Terminal.window(parent, sx, sy, w, h, isVisible)
function win.redraw()
if isVisible then
win.canvas:dirty()
canvas:dirty()
update()
end
end
@@ -203,58 +194,27 @@ function Terminal.window(parent, sx, sy, w, h, isVisible)
function win.restoreCursor()
if isVisible then
win.setCursorPos(cx, cy)
win.setTextColor(win.canvas.fg)
win.setTextColor(fg)
win.setCursorBlink(blink)
end
end
function win.getPosition()
return win.canvas.x, win.canvas.y
return canvas.x, canvas.y
end
function win.reposition(x, y, width, height)
if not maxScroll then
win.canvas:move(x, y)
win.canvas:resize(width or win.canvas.width, height or win.canvas.height)
return
end
-- special processing for scrolling terminal like windows
local delta = height - win.canvas.height
if delta > 0 then -- grow
for _ = 1, delta do
win.canvas.lines[#win.canvas.lines + 1] = { }
win.canvas:clearLine(#win.canvas.lines)
end
elseif delta < 0 then -- shrink
for _ = delta + 1, 0 do
if cy < win.canvas.height then
win.canvas.lines[#win.canvas.lines] = nil
else
cy = cy - 1
win.canvas.offy = win.canvas.offy + 1
end
end
end
win.canvas:resizeBuffer(width, #win.canvas.lines)
win.canvas.height = height
win.canvas.width = width
win.canvas:move(x, y)
update()
canvas.x, canvas.y = x, y
canvas:resize(width or canvas.width, height or canvas.height)
end
--[[ Additional methods ]]--
function win.scrollDown()
scrollTo(win.canvas.offy + 1)
scrollTo(canvas.offy + 1)
end
function win.scrollUp()
scrollTo(win.canvas.offy - 1)
scrollTo(canvas.offy - 1)
end
function win.scrollTop()
@@ -262,7 +222,7 @@ function Terminal.window(parent, sx, sy, w, h, isVisible)
end
function win.scrollBottom()
scrollTo(#win.canvas.lines)
scrollTo(#canvas.lines)
end
function win.setMaxScroll(ms)
@@ -270,108 +230,37 @@ function Terminal.window(parent, sx, sy, w, h, isVisible)
end
function win.getCanvas()
return win.canvas
return canvas
end
function win.getParent()
return parent
end
function win.writeX(sText)
-- expect(1, sText, "string", "number")
local nLinesPrinted = 0
local function newLine()
if cy + 1 <= win.canvas.height then
cx, cy = 1, cy + 1
else
cx, cy = 1, win.canvas.height
win.scroll(1)
end
nLinesPrinted = nLinesPrinted + 1
end
-- Print the line with proper word wrapping
sText = tostring(sText)
while #sText > 0 do
local whitespace = string.match(sText, "^[ \t]+")
if whitespace then
-- Print whitespace
win.write(whitespace)
sText = string.sub(sText, #whitespace + 1)
end
local newline = string.match(sText, "^\n")
if newline then
-- Print newlines
newLine()
sText = string.sub(sText, 2)
end
local text = string.match(sText, "^[^ \t\n]+")
if text then
sText = string.sub(sText, #text + 1)
if #text > win.canvas.width then
-- Print a multiline word
while #text > 0 do
if cx > win.canvas.width then
newLine()
end
win.write(text)
text = string.sub(text, win.canvas.width - cx + 2)
end
else
-- Print a word normally
if cx + #text - 1 > win.canvas.width then
newLine()
end
win.write(text)
end
end
end
return nLinesPrinted
end
function win.print(...)
local vis = isVisible
isVisible = false
local nLinesPrinted = 0
local nLimit = select("#", ...)
for n = 1, nLimit do
local s = tostring(select(n, ...))
if n < nLimit then
s = s .. "\t"
end
nLinesPrinted = nLinesPrinted + win.writeX(s)
end
nLinesPrinted = nLinesPrinted + win.writeX("\n")
isVisible = vis
update()
return nLinesPrinted
end
win.canvas:clear()
canvas:clear()
return win
end
-- get windows contents
function Terminal.getContents(win)
if not win.getLine then
error('window is required')
end
function Terminal.getContents(win, parent)
local oblit, oscp = parent.blit, parent.setCursorPos
local lines = { }
local _, h = win.getSize()
for i = 1, h do
local text, fg, bg = win.getLine(i)
lines[i] = {
parent.blit = function(text, fg, bg)
lines[#lines + 1] = {
text = text,
fg = fg,
bg = bg,
}
end
parent.setCursorPos = function() end
win.setVisible(true)
win.redraw()
parent.blit = oblit
parent.setCursorPos = oscp
return lines
end

Some files were not shown because too many files have changed in this diff Show More