Compare commits
127 Commits
ui-enhance
...
Merith-TK/
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
2b328ae309 | ||
|
|
4104750539 | ||
|
|
3150525ee2 | ||
|
|
7c5f749f02 | ||
|
|
7d9029c706 | ||
|
|
ce6a741690 | ||
|
|
16d233df5d | ||
|
|
f6d09abd54 | ||
|
|
bff84370b2 | ||
|
|
e74db02371 | ||
|
|
2b75fbd1ee | ||
|
|
65bf756084 | ||
|
|
2ff4ee5670 | ||
|
|
5488a7d732 | ||
|
|
37c4ca102c | ||
|
|
ac51771b12 | ||
|
|
6fdce03a10 | ||
|
|
ac91d8ed08 | ||
|
|
2c0db368fa | ||
|
|
a77deb72ec | ||
|
|
01d8d65178 | ||
|
|
f36e5470b1 | ||
|
|
2520f53046 | ||
|
|
f4494d6103 | ||
|
|
48f32946ec | ||
|
|
c24a5a7115 | ||
|
|
624af53f4e | ||
|
|
816ea366ab | ||
|
|
b45cd45bcb | ||
|
|
45f1bd1c5d | ||
|
|
1cb2c5f785 | ||
|
|
42bd4b2b69 | ||
|
|
156b604a58 | ||
|
|
7df4a47ba0 | ||
|
|
9bf91a8762 | ||
|
|
b69dcdeffa | ||
|
|
947f502c6d | ||
|
|
a2af4405e7 | ||
|
|
1bf6daedff | ||
|
|
2fdcc338ad | ||
|
|
4f39604c63 | ||
|
|
e9580d67eb | ||
|
|
039fa73749 | ||
|
|
2d942ef001 | ||
|
|
4d02f75a19 | ||
|
|
6c5cc508b1 | ||
|
|
e7fcd68a0f | ||
|
|
7dfa80f5f3 | ||
|
|
788f6e7de0 | ||
|
|
704ef46b62 | ||
|
|
d678eeeaca | ||
|
|
6a3b38922b | ||
|
|
6009f22d8e | ||
|
|
f951f10df5 | ||
|
|
5c4ab57ec8 | ||
|
|
0359a89e12 | ||
|
|
4796e9e77a | ||
|
|
18b7f540ab | ||
|
|
2105799524 | ||
|
|
b6f439e8dc | ||
|
|
41c0758857 | ||
|
|
d1565c62e0 | ||
|
|
f00bece02b | ||
|
|
9297223640 | ||
|
|
f38afbbd36 | ||
|
|
7b225a7747 | ||
|
|
a7069f7ea8 | ||
|
|
a4f4f34576 | ||
|
|
1cce4aad03 | ||
|
|
26bbb50981 | ||
|
|
97bfae10fb | ||
|
|
985830fcfd | ||
|
|
b93d69c261 | ||
|
|
a7e3318226 | ||
|
|
c7c594d6c3 | ||
|
|
90ce2bb1a5 | ||
|
|
2629f2a172 | ||
|
|
8279c1ae12 | ||
|
|
bd911e80e8 | ||
|
|
a3a819256f | ||
|
|
cfc18e10cd | ||
|
|
b0db0b86bd | ||
|
|
71bbd2d457 | ||
|
|
db9c05fa89 | ||
|
|
3f80faa0fe | ||
|
|
8b14399a20 | ||
|
|
4e6c5172d1 | ||
|
|
c24411717a | ||
|
|
51724ccd78 | ||
|
|
3d3e5400cf | ||
|
|
4018d4bcfb | ||
|
|
4485dd2bd5 | ||
|
|
72560b1de0 | ||
|
|
3d02230742 | ||
|
|
456d2d301f | ||
|
|
cdf8645c88 | ||
|
|
d7f41f2bce | ||
|
|
e2ba9e2a03 | ||
|
|
447f4daa92 | ||
|
|
5afb21b68e | ||
|
|
3488da649e | ||
|
|
7f92286fb1 | ||
|
|
4a46d67304 | ||
|
|
3cbdf7b97b | ||
|
|
74d3d1df33 | ||
|
|
9c97849cff | ||
|
|
c1cbcf6b61 | ||
|
|
49b986499c | ||
|
|
fae7596182 | ||
|
|
cb55b1daab | ||
|
|
fd61416750 | ||
|
|
c622b2f7fb | ||
|
|
98f4bf7a7e | ||
|
|
c5e0ac0af6 | ||
|
|
0188e886c0 | ||
|
|
55f444c37e | ||
|
|
3368fa3266 | ||
|
|
e721eb68b6 | ||
|
|
1543f4d624 | ||
|
|
287adb1235 | ||
|
|
e116caf16e | ||
|
|
d72ae3de4a | ||
|
|
602d12afc5 | ||
|
|
ef9f0e09b6 | ||
|
|
b0d2ce0199 | ||
|
|
7224d441ca | ||
|
|
cdd0b6c4d2 |
30
.github/ISSUE_TEMPLATE/bug_report.md
vendored
Normal file
30
.github/ISSUE_TEMPLATE/bug_report.md
vendored
Normal file
@@ -0,0 +1,30 @@
|
|||||||
|
---
|
||||||
|
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]
|
||||||
20
.github/ISSUE_TEMPLATE/feature_request.md
vendored
Normal file
20
.github/ISSUE_TEMPLATE/feature_request.md
vendored
Normal file
@@ -0,0 +1,20 @@
|
|||||||
|
---
|
||||||
|
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.
|
||||||
17
.github/ISSUE_TEMPLATES/bug.md
vendored
Normal file
17
.github/ISSUE_TEMPLATES/bug.md
vendored
Normal file
@@ -0,0 +1,17 @@
|
|||||||
|
---
|
||||||
|
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:
|
||||||
13
.github/ISSUE_TEMPLATES/feature.md
vendored
Normal file
13
.github/ISSUE_TEMPLATES/feature.md
vendored
Normal file
@@ -0,0 +1,13 @@
|
|||||||
|
---
|
||||||
|
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. -->
|
||||||
36
.github/workflows/main.yml
vendored
Normal file
36
.github/workflows/main.yml
vendored
Normal file
@@ -0,0 +1,36 @@
|
|||||||
|
# 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
1
.gitignore
vendored
@@ -1 +1,2 @@
|
|||||||
/ignore
|
/ignore
|
||||||
|
.project
|
||||||
|
|||||||
6
.opus_version
Normal file
6
.opus_version
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
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)
|
||||||
@@ -1,5 +1,7 @@
|
|||||||
# Opus OS for computercraft
|
# 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
|
## Features
|
||||||
* Multitasking OS - run programs in separate tabs
|
* Multitasking OS - run programs in separate tabs
|
||||||
* Telnet (wireless remote shell)
|
* Telnet (wireless remote shell)
|
||||||
@@ -14,5 +16,5 @@
|
|||||||
|
|
||||||
## Install
|
## Install
|
||||||
```
|
```
|
||||||
pastebin run uzghlbnc
|
pastebin run UzGHLbNC
|
||||||
```
|
```
|
||||||
|
|||||||
23
startup.lua
23
startup.lua
@@ -29,9 +29,10 @@ local function loadBootOptions()
|
|||||||
preload = { },
|
preload = { },
|
||||||
menu = {
|
menu = {
|
||||||
{ prompt = os.version() },
|
{ prompt = os.version() },
|
||||||
{ prompt = 'Opus' , args = { '/sys/boot/opus.boot' } },
|
{ prompt = 'Opus' , args = { '/sys/boot/opus.lua' } },
|
||||||
{ prompt = 'Opus Shell' , args = { '/sys/boot/opus.boot', 'sys/apps/shell.lua' } },
|
{ prompt = 'Opus Shell' , args = { '/sys/boot/opus.lua', '/sys/apps/shell.lua' } },
|
||||||
{ prompt = 'Opus Kiosk' , args = { '/sys/boot/kiosk.boot' } },
|
{ prompt = 'Opus Kiosk' , args = { '/sys/boot/kiosk.lua' } },
|
||||||
|
{ prompt = 'Opus TLCO' , args = { '/sys/boot/tlco.lua' } },
|
||||||
},
|
},
|
||||||
}))
|
}))
|
||||||
f.close()
|
f.close()
|
||||||
@@ -41,6 +42,20 @@ local function loadBootOptions()
|
|||||||
local options = textutils.unserialize(f.readAll())
|
local options = textutils.unserialize(f.readAll())
|
||||||
f.close()
|
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
|
return options
|
||||||
end
|
end
|
||||||
|
|
||||||
@@ -127,7 +142,7 @@ local function splash()
|
|||||||
local opus = {
|
local opus = {
|
||||||
'fffff00',
|
'fffff00',
|
||||||
'ffff07000',
|
'ffff07000',
|
||||||
'ff00770b00 4444',
|
'ff00770b00f4444',
|
||||||
'ff077777444444444',
|
'ff077777444444444',
|
||||||
'f07777744444444444',
|
'f07777744444444444',
|
||||||
'f0000777444444444',
|
'f0000777444444444',
|
||||||
|
|||||||
@@ -1,4 +1,3 @@
|
|||||||
local Alt = require('opus.alternate')
|
|
||||||
local Config = require('opus.config')
|
local Config = require('opus.config')
|
||||||
local Event = require('opus.event')
|
local Event = require('opus.event')
|
||||||
local pastebin = require('opus.http.pastebin')
|
local pastebin = require('opus.http.pastebin')
|
||||||
@@ -77,21 +76,65 @@ local Browser = UI.Page {
|
|||||||
grid = UI.ScrollingGrid {
|
grid = UI.ScrollingGrid {
|
||||||
columns = {
|
columns = {
|
||||||
{ heading = 'Name', key = 'name' },
|
{ heading = 'Name', key = 'name' },
|
||||||
{ key = 'flags', width = 2 },
|
{ key = 'flags', width = 3, textColor = 'lightGray' },
|
||||||
{ heading = 'Size', key = 'fsize', width = 5 },
|
{ heading = 'Size', key = 'fsize', width = 5, textColor = 'yellow' },
|
||||||
},
|
},
|
||||||
sortColumn = 'name',
|
sortColumn = 'name',
|
||||||
y = 2, ey = -2,
|
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 {
|
statusBar = UI.StatusBar {
|
||||||
columns = {
|
columns = {
|
||||||
{ key = 'status' },
|
{ key = 'status' },
|
||||||
{ key = 'totalSize', width = 6 },
|
{ 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 { },
|
notification = UI.Notification { },
|
||||||
associations = UI.SlideOut {
|
associations = UI.SlideOut {
|
||||||
backgroundColor = colors.cyan,
|
|
||||||
menuBar = UI.MenuBar {
|
menuBar = UI.MenuBar {
|
||||||
buttons = {
|
buttons = {
|
||||||
{ text = 'Save', event = 'save' },
|
{ text = 'Save', event = 'save' },
|
||||||
@@ -99,7 +142,7 @@ local Browser = UI.Page {
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
grid = UI.ScrollingGrid {
|
grid = UI.ScrollingGrid {
|
||||||
x = 2, ex = -6, y = 3, ey = -5,
|
x = 2, ex = -6, y = 3, ey = -8,
|
||||||
columns = {
|
columns = {
|
||||||
{ heading = 'Extension', key = 'name' },
|
{ heading = 'Extension', key = 'name' },
|
||||||
{ heading = 'Program', key = 'value' },
|
{ heading = 'Program', key = 'value' },
|
||||||
@@ -114,8 +157,11 @@ local Browser = UI.Page {
|
|||||||
x = -4, y = 6,
|
x = -4, y = 6,
|
||||||
text = '-', event = 'remove_entry', help = 'Remove',
|
text = '-', event = 'remove_entry', help = 'Remove',
|
||||||
},
|
},
|
||||||
|
[1] = UI.Window {
|
||||||
|
x = 2, y = -6, ex = -6, ey = -3,
|
||||||
|
},
|
||||||
form = UI.Form {
|
form = UI.Form {
|
||||||
x = 3, y = -3, ey = -2,
|
x = 3, y = -5, ex = -7, ey = -3,
|
||||||
margin = 1,
|
margin = 1,
|
||||||
manualControls = true,
|
manualControls = true,
|
||||||
[1] = UI.TextEntry {
|
[1] = UI.TextEntry {
|
||||||
@@ -130,16 +176,13 @@ local Browser = UI.Page {
|
|||||||
formLabel = 'Program', formKey = 'value',
|
formLabel = 'Program', formKey = 'value',
|
||||||
shadowText = 'program',
|
shadowText = 'program',
|
||||||
required = true,
|
required = true,
|
||||||
limit = 128,
|
|
||||||
},
|
},
|
||||||
add = UI.Button {
|
add = UI.Button {
|
||||||
x = -11, y = 1,
|
x = -11, y = 1,
|
||||||
text = 'Add', event = 'add_association',
|
text = 'Add', event = 'add_association',
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
statusBar = UI.StatusBar {
|
statusBar = UI.StatusBar { },
|
||||||
backgroundColor = colors.cyan,
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
accelerators = {
|
accelerators = {
|
||||||
[ 'control-q' ] = 'quit',
|
[ 'control-q' ] = 'quit',
|
||||||
@@ -167,7 +210,7 @@ function Browser:enable()
|
|||||||
self:setFocus(self.grid)
|
self:setFocus(self.grid)
|
||||||
end
|
end
|
||||||
|
|
||||||
function Browser.menuBar:getActive(menuItem)
|
function Browser.menuBar.getActive(_, menuItem)
|
||||||
local file = Browser.grid:getSelected()
|
local file = Browser.grid:getSelected()
|
||||||
if menuItem.flags == FILE then
|
if menuItem.flags == FILE then
|
||||||
return file and not file.isDir
|
return file and not file.isDir
|
||||||
@@ -175,56 +218,11 @@ function Browser.menuBar:getActive(menuItem)
|
|||||||
return true
|
return true
|
||||||
end
|
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, ...)
|
function Browser:setStatus(status, ...)
|
||||||
self.notification:info(string.format(status, ...))
|
self.notification:info(string.format(status, ...))
|
||||||
end
|
end
|
||||||
|
|
||||||
function Browser:unmarkAll()
|
function Browser.unmarkAll()
|
||||||
for _,m in pairs(marked) do
|
for _,m in pairs(marked) do
|
||||||
m.marked = false
|
m.marked = false
|
||||||
end
|
end
|
||||||
@@ -255,7 +253,6 @@ function Browser:getDirectory(directory)
|
|||||||
end
|
end
|
||||||
|
|
||||||
function Browser:updateDirectory(dir)
|
function Browser:updateDirectory(dir)
|
||||||
|
|
||||||
dir.size = 0
|
dir.size = 0
|
||||||
dir.totalSize = 0
|
dir.totalSize = 0
|
||||||
Util.clear(dir.files)
|
Util.clear(dir.files)
|
||||||
@@ -265,10 +262,11 @@ function Browser:updateDirectory(dir)
|
|||||||
dir.size = #files
|
dir.size = #files
|
||||||
for _, file in pairs(files) do
|
for _, file in pairs(files) do
|
||||||
file.fullName = fs.combine(dir.name, file.name)
|
file.fullName = fs.combine(dir.name, file.name)
|
||||||
file.flags = ''
|
file.flags = file.fstype or ' '
|
||||||
if not file.isDir then
|
if not file.isDir then
|
||||||
dir.totalSize = dir.totalSize + file.size
|
dir.totalSize = dir.totalSize + file.size
|
||||||
file.fsize = formatSize(file.size)
|
file.fsize = formatSize(file.size)
|
||||||
|
file.flags = file.flags .. ' '
|
||||||
else
|
else
|
||||||
if config.showDirSizes then
|
if config.showDirSizes then
|
||||||
file.size = fs.getSize(file.fullName, true)
|
file.size = fs.getSize(file.fullName, true)
|
||||||
@@ -276,11 +274,9 @@ function Browser:updateDirectory(dir)
|
|||||||
dir.totalSize = dir.totalSize + file.size
|
dir.totalSize = dir.totalSize + file.size
|
||||||
file.fsize = formatSize(file.size)
|
file.fsize = formatSize(file.size)
|
||||||
end
|
end
|
||||||
file.flags = 'D'
|
file.flags = file.flags .. 'D'
|
||||||
end
|
|
||||||
if file.isReadOnly then
|
|
||||||
file.flags = file.flags .. 'R'
|
|
||||||
end
|
end
|
||||||
|
file.flags = file.flags .. (file.isReadOnly and 'R' or ' ')
|
||||||
if config.showHidden or file.name:sub(1, 1) ~= '.' then
|
if config.showHidden or file.name:sub(1, 1) ~= '.' then
|
||||||
dir.files[file.fullName] = file
|
dir.files[file.fullName] = file
|
||||||
end
|
end
|
||||||
@@ -344,7 +340,7 @@ function Browser:eventHandler(event)
|
|||||||
local file = self.grid:getSelected()
|
local file = self.grid:getSelected()
|
||||||
|
|
||||||
if event.type == 'quit' then
|
if event.type == 'quit' then
|
||||||
Event.exitPullEvents()
|
UI:quit()
|
||||||
|
|
||||||
elseif event.type == 'edit' and file then
|
elseif event.type == 'edit' and file then
|
||||||
self:run('edit', file.name)
|
self:run('edit', file.name)
|
||||||
@@ -354,7 +350,7 @@ function Browser:eventHandler(event)
|
|||||||
self:setStatus('Started cloud edit')
|
self:setStatus('Started cloud edit')
|
||||||
|
|
||||||
elseif event.type == 'shell' then
|
elseif event.type == 'shell' then
|
||||||
self:run(Alt.get('shell'))
|
self:run('shell')
|
||||||
|
|
||||||
elseif event.type == 'refresh' then
|
elseif event.type == 'refresh' then
|
||||||
self:updateDirectory(self.dir)
|
self:updateDirectory(self.dir)
|
||||||
@@ -432,28 +428,25 @@ function Browser:eventHandler(event)
|
|||||||
|
|
||||||
elseif event.type == 'delete' then
|
elseif event.type == 'delete' then
|
||||||
if self:hasMarked() then
|
if self:hasMarked() then
|
||||||
local width = self.statusBar:getColumnWidth('status')
|
self.question:show()
|
||||||
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
|
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
|
elseif event.type == 'copy' or event.type == 'cut' then
|
||||||
if self:hasMarked() then
|
if self:hasMarked() then
|
||||||
@@ -472,7 +465,7 @@ function Browser:eventHandler(event)
|
|||||||
|
|
||||||
elseif event.type == 'paste' then
|
elseif event.type == 'paste' then
|
||||||
for _,m in pairs(copied) do
|
for _,m in pairs(copied) do
|
||||||
local s, m = pcall(function()
|
pcall(function()
|
||||||
if cutMode then
|
if cutMode then
|
||||||
fs.move(m.fullName, fs.combine(self.dir.name, m.name))
|
fs.move(m.fullName, fs.combine(self.dir.name, m.name))
|
||||||
else
|
else
|
||||||
@@ -549,6 +542,4 @@ local args = Util.parse(...)
|
|||||||
Browser:setDir(args[1] or shell.dir())
|
Browser:setDir(args[1] or shell.dir())
|
||||||
|
|
||||||
UI:setPage(Browser)
|
UI:setPage(Browser)
|
||||||
|
UI:start()
|
||||||
Event.pullEvents()
|
|
||||||
UI.term:reset()
|
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
|
local fuzzy = require('opus.fuzzy')
|
||||||
local UI = require('opus.ui')
|
local UI = require('opus.ui')
|
||||||
local Util = require('opus.util')
|
local Util = require('opus.util')
|
||||||
|
|
||||||
local colors = _G.colors
|
|
||||||
local help = _G.help
|
local help = _G.help
|
||||||
|
|
||||||
UI:configure('Help', ...)
|
UI:configure('Help', ...)
|
||||||
@@ -12,11 +12,11 @@ for _,topic in pairs(help.topics()) do
|
|||||||
end
|
end
|
||||||
|
|
||||||
UI:addPage('main', UI.Page {
|
UI:addPage('main', UI.Page {
|
||||||
labelText = UI.Text {
|
UI.Text {
|
||||||
x = 3, y = 2,
|
x = 3, y = 2,
|
||||||
value = 'Search',
|
value = 'Search',
|
||||||
},
|
},
|
||||||
filter = UI.TextEntry {
|
UI.TextEntry {
|
||||||
x = 10, y = 2, ex = -3,
|
x = 10, y = 2, ex = -3,
|
||||||
limit = 32,
|
limit = 32,
|
||||||
},
|
},
|
||||||
@@ -38,25 +38,24 @@ UI:addPage('main', UI.Page {
|
|||||||
|
|
||||||
elseif event.type == 'grid_select' then
|
elseif event.type == 'grid_select' then
|
||||||
if self.grid:getSelected() then
|
if self.grid:getSelected() then
|
||||||
local name = self.grid:getSelected().name
|
UI:setPage('topic', self.grid:getSelected().name)
|
||||||
|
|
||||||
UI:setPage('topic', name)
|
|
||||||
end
|
end
|
||||||
|
|
||||||
elseif event.type == 'text_change' then
|
elseif event.type == 'text_change' then
|
||||||
if not event.text then
|
if not event.text then
|
||||||
self.grid.values = topics
|
self.grid.sortColumn = 'lname'
|
||||||
else
|
else
|
||||||
self.grid.values = { }
|
self.grid.sortColumn = 'score'
|
||||||
for _,f in pairs(topics) do
|
self.grid.inverseSort = false
|
||||||
if string.find(f.lname, event.text:lower()) then
|
local pattern = event.text:lower()
|
||||||
table.insert(self.grid.values, f)
|
for _,v in pairs(self.grid.values) do
|
||||||
end
|
v.score = -fuzzy(v.lname, pattern)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
self.grid:update()
|
self.grid:update()
|
||||||
self.grid:setIndex(1)
|
self.grid:setIndex(1)
|
||||||
self.grid:draw()
|
self.grid:draw()
|
||||||
|
|
||||||
else
|
else
|
||||||
return UI.Page.eventHandler(self, event)
|
return UI.Page.eventHandler(self, event)
|
||||||
end
|
end
|
||||||
@@ -64,13 +63,12 @@ UI:addPage('main', UI.Page {
|
|||||||
})
|
})
|
||||||
|
|
||||||
UI:addPage('topic', UI.Page {
|
UI:addPage('topic', UI.Page {
|
||||||
backgroundColor = colors.black,
|
backgroundColor = 'black',
|
||||||
titleBar = UI.TitleBar {
|
titleBar = UI.TitleBar {
|
||||||
title = 'text',
|
title = 'text',
|
||||||
event = 'back',
|
event = 'back',
|
||||||
},
|
},
|
||||||
helpText = UI.TextArea {
|
helpText = UI.TextArea {
|
||||||
backgroundColor = colors.black,
|
|
||||||
x = 2, ex = -1, y = 3, ey = -2,
|
x = 2, ex = -1, y = 3, ey = -2,
|
||||||
},
|
},
|
||||||
accelerators = {
|
accelerators = {
|
||||||
|
|||||||
@@ -1,6 +1,3 @@
|
|||||||
-- Lua may be called from outside of shell - inject a require
|
|
||||||
_G.requireInjector(_ENV)
|
|
||||||
|
|
||||||
local History = require('opus.history')
|
local History = require('opus.history')
|
||||||
local UI = require('opus.ui')
|
local UI = require('opus.ui')
|
||||||
local Util = require('opus.util')
|
local Util = require('opus.util')
|
||||||
@@ -10,10 +7,8 @@ local os = _G.os
|
|||||||
local textutils = _G.textutils
|
local textutils = _G.textutils
|
||||||
local term = _G.term
|
local term = _G.term
|
||||||
|
|
||||||
local _exit
|
|
||||||
|
|
||||||
local sandboxEnv = setmetatable(Util.shallowCopy(_ENV), { __index = _G })
|
local sandboxEnv = setmetatable(Util.shallowCopy(_ENV), { __index = _G })
|
||||||
sandboxEnv.exit = function() _exit = true end
|
sandboxEnv.exit = function() UI:quit() end
|
||||||
sandboxEnv._echo = function( ... ) return { ... } end
|
sandboxEnv._echo = function( ... ) return { ... } end
|
||||||
_G.requireInjector(sandboxEnv)
|
_G.requireInjector(sandboxEnv)
|
||||||
|
|
||||||
@@ -34,7 +29,6 @@ local page = UI.Page {
|
|||||||
prompt = UI.TextEntry {
|
prompt = UI.TextEntry {
|
||||||
y = 2,
|
y = 2,
|
||||||
shadowText = 'enter command',
|
shadowText = 'enter command',
|
||||||
limit = 1024,
|
|
||||||
accelerators = {
|
accelerators = {
|
||||||
enter = 'command_enter',
|
enter = 'command_enter',
|
||||||
up = 'history_back',
|
up = 'history_back',
|
||||||
@@ -45,8 +39,9 @@ local page = UI.Page {
|
|||||||
},
|
},
|
||||||
tabs = UI.Tabs {
|
tabs = UI.Tabs {
|
||||||
y = 3,
|
y = 3,
|
||||||
[1] = UI.Tab {
|
formatted = UI.Tab {
|
||||||
tabTitle = 'Formatted',
|
title = 'Formatted',
|
||||||
|
index = 1,
|
||||||
grid = UI.ScrollingGrid {
|
grid = UI.ScrollingGrid {
|
||||||
columns = {
|
columns = {
|
||||||
{ heading = 'Key', key = 'name' },
|
{ heading = 'Key', key = 'name' },
|
||||||
@@ -56,19 +51,25 @@ local page = UI.Page {
|
|||||||
autospace = true,
|
autospace = true,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
[2] = UI.Tab {
|
output = UI.Tab {
|
||||||
tabTitle = 'Output',
|
title = 'Output',
|
||||||
|
index = 2,
|
||||||
|
backgroundColor = 'black',
|
||||||
output = UI.Embedded {
|
output = UI.Embedded {
|
||||||
visible = true,
|
y = 2,
|
||||||
maxScroll = 1000,
|
maxScroll = 1000,
|
||||||
backgroundColor = colors.black,
|
backgroundColor = 'black',
|
||||||
},
|
},
|
||||||
|
draw = function(self)
|
||||||
|
self:write(1, 1, string.rep('\131', self.width), 'black', 'primary')
|
||||||
|
self:drawChildren()
|
||||||
|
end,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
page.grid = page.tabs[1].grid
|
page.grid = page.tabs.formatted.grid
|
||||||
page.output = page.tabs[2].output
|
page.output = page.tabs.output.output
|
||||||
|
|
||||||
function page:setPrompt(value, focus)
|
function page:setPrompt(value, focus)
|
||||||
self.prompt:setValue(value)
|
self.prompt:setValue(value)
|
||||||
@@ -133,31 +134,18 @@ function page:eventHandler(event)
|
|||||||
self:executeStatement('_ENV')
|
self:executeStatement('_ENV')
|
||||||
command = nil
|
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
|
elseif event.type == 'tab_select' then
|
||||||
self:setFocus(self.prompt)
|
self:setFocus(self.prompt)
|
||||||
|
|
||||||
elseif event.type == 'show_output' then
|
elseif event.type == 'show_output' then
|
||||||
self.tabs:selectTab(self.tabs[2])
|
self.tabs:selectTab(self.tabs.output)
|
||||||
|
|
||||||
elseif event.type == 'autocomplete' then
|
elseif event.type == 'autocomplete' then
|
||||||
local value = self.prompt.value or ''
|
local value = self.prompt.value or ''
|
||||||
local sz = #value
|
local sz = #value
|
||||||
local pos = self.prompt.entry.pos
|
local pos = self.prompt.entry.pos
|
||||||
self:setPrompt(autocomplete(sandboxEnv, value, self.prompt.entry.pos))
|
self:setPrompt(autocomplete(sandboxEnv, value, self.prompt.entry.pos))
|
||||||
self.prompt:setPosition(pos + #value - sz)
|
self.prompt:setPosition(pos + #(self.prompt.value or '') - sz)
|
||||||
self.prompt:updateCursor()
|
self.prompt:updateCursor()
|
||||||
|
|
||||||
elseif event.type == 'device' then
|
elseif event.type == 'device' then
|
||||||
@@ -196,8 +184,7 @@ function page:eventHandler(event)
|
|||||||
command = nil
|
command = nil
|
||||||
self.grid:setValues(t)
|
self.grid:setValues(t)
|
||||||
self.grid:setIndex(1)
|
self.grid:setIndex(1)
|
||||||
self.grid:adjustWidth()
|
self.grid:draw()
|
||||||
self:draw()
|
|
||||||
end
|
end
|
||||||
return true
|
return true
|
||||||
|
|
||||||
@@ -243,8 +230,7 @@ function page:setResult(result)
|
|||||||
end
|
end
|
||||||
self.grid:setValues(t)
|
self.grid:setValues(t)
|
||||||
self.grid:setIndex(1)
|
self.grid:setIndex(1)
|
||||||
self.grid:adjustWidth()
|
self.grid:draw()
|
||||||
self:draw()
|
|
||||||
end
|
end
|
||||||
|
|
||||||
function page.grid:eventHandler(event)
|
function page.grid:eventHandler(event)
|
||||||
@@ -362,7 +348,7 @@ function page:executeStatement(statement)
|
|||||||
term.redirect(oterm)
|
term.redirect(oterm)
|
||||||
counter = counter + 1
|
counter = counter + 1
|
||||||
|
|
||||||
if s and m then
|
if s and type(m) ~= "nil" then
|
||||||
self:setResult(m)
|
self:setResult(m)
|
||||||
else
|
else
|
||||||
self.grid:setValues({ })
|
self.grid:setValues({ })
|
||||||
@@ -371,10 +357,6 @@ function page:executeStatement(statement)
|
|||||||
self:emit({ type = 'show_output' })
|
self:emit({ type = 'show_output' })
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
if _exit then
|
|
||||||
UI:exitPullEvents()
|
|
||||||
end
|
|
||||||
end
|
end
|
||||||
|
|
||||||
local args = Util.parse(...)
|
local args = Util.parse(...)
|
||||||
@@ -382,7 +364,8 @@ if args[1] then
|
|||||||
command = 'args[1]'
|
command = 'args[1]'
|
||||||
sandboxEnv.args = args
|
sandboxEnv.args = args
|
||||||
page:setResult(args[1])
|
page:setResult(args[1])
|
||||||
|
page:setPrompt(command)
|
||||||
end
|
end
|
||||||
|
|
||||||
UI:setPage(page)
|
UI:setPage(page)
|
||||||
UI:pullEvents()
|
UI:start()
|
||||||
|
|||||||
@@ -4,10 +4,8 @@ local Socket = require('opus.socket')
|
|||||||
local UI = require('opus.ui')
|
local UI = require('opus.ui')
|
||||||
local Util = require('opus.util')
|
local Util = require('opus.util')
|
||||||
|
|
||||||
local colors = _G.colors
|
|
||||||
local device = _G.device
|
local device = _G.device
|
||||||
local network = _G.network
|
local network = _G.network
|
||||||
local os = _G.os
|
|
||||||
local shell = _ENV.shell
|
local shell = _ENV.shell
|
||||||
|
|
||||||
UI:configure('Network', ...)
|
UI:configure('Network', ...)
|
||||||
@@ -56,14 +54,45 @@ local page = UI.Page {
|
|||||||
columns = gridColumns,
|
columns = gridColumns,
|
||||||
sortColumn = 'label',
|
sortColumn = 'label',
|
||||||
autospace = true,
|
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 {
|
ports = UI.SlideOut {
|
||||||
titleBar = UI.TitleBar {
|
titleBar = UI.TitleBar {
|
||||||
title = 'Ports',
|
title = 'Ports',
|
||||||
event = 'ports_hide',
|
event = 'ports_hide',
|
||||||
},
|
},
|
||||||
grid = UI.ScrollingGrid {
|
menuBar = UI.MenuBar {
|
||||||
y = 2,
|
y = 2,
|
||||||
|
buttons = {
|
||||||
|
{ text = 'Refresh', event = 'ports_update' },
|
||||||
|
}
|
||||||
|
},
|
||||||
|
grid = UI.ScrollingGrid {
|
||||||
|
y = 3,
|
||||||
columns = {
|
columns = {
|
||||||
{ heading = 'Port', key = 'port' },
|
{ heading = 'Port', key = 'port' },
|
||||||
{ heading = 'State', key = 'state' },
|
{ heading = 'State', key = 'state' },
|
||||||
@@ -72,31 +101,12 @@ local page = UI.Page {
|
|||||||
sortColumn = 'port',
|
sortColumn = 'port',
|
||||||
autospace = true,
|
autospace = true,
|
||||||
},
|
},
|
||||||
},
|
eventHandler = function(self, event)
|
||||||
help = UI.SlideOut {
|
if event.type == 'grid_select' then
|
||||||
backgroundColor = colors.cyan,
|
shell.openForegroundTab('Sniff ' .. event.selected.port)
|
||||||
x = 5, ex = -5, height = 8, y = -8,
|
end
|
||||||
titleBar = UI.TitleBar {
|
return UI.SlideOut.eventHandler(self, event)
|
||||||
title = 'Network Help',
|
end,
|
||||||
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 { },
|
notification = UI.Notification { },
|
||||||
accelerators = {
|
accelerators = {
|
||||||
@@ -127,13 +137,6 @@ local function sendCommand(host, command)
|
|||||||
end
|
end
|
||||||
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()
|
function page.ports.grid:update()
|
||||||
local transport = network:getTransport()
|
local transport = network:getTransport()
|
||||||
|
|
||||||
@@ -185,12 +188,14 @@ function page:eventHandler(event)
|
|||||||
|
|
||||||
elseif event.type == 'vnc' then
|
elseif event.type == 'vnc' then
|
||||||
shell.openForegroundTab('vnc.lua ' .. t.id)
|
shell.openForegroundTab('vnc.lua ' .. t.id)
|
||||||
|
--[[
|
||||||
os.queueEvent('overview_shortcut', {
|
os.queueEvent('overview_shortcut', {
|
||||||
title = t.label,
|
title = t.label,
|
||||||
category = "VNC",
|
category = "VNC",
|
||||||
icon = "\010\030 \009\009\031e\\\031 \031e/\031dn\010\030 \009\009 \031e\\/\031 \031bc",
|
icon = "\010\030 \009\009\031e\\\031 \031e/\031dn\010\030 \009\009 \031e\\/\031 \031bc",
|
||||||
run = "vnc.lua " .. t.id,
|
run = "vnc.lua " .. t.id,
|
||||||
})
|
})
|
||||||
|
--]]
|
||||||
|
|
||||||
elseif event.type == 'clear' then
|
elseif event.type == 'clear' then
|
||||||
Util.clear(network)
|
Util.clear(network)
|
||||||
@@ -209,17 +214,22 @@ function page:eventHandler(event)
|
|||||||
end
|
end
|
||||||
|
|
||||||
if event.type == 'help' then
|
if event.type == 'help' then
|
||||||
self.help:show()
|
shell.switchTab(shell.openTab('Help Networking'))
|
||||||
|
|
||||||
elseif event.type == 'ports' then
|
elseif event.type == 'ports' then
|
||||||
self.ports.grid:update()
|
self.ports.grid:update()
|
||||||
self.ports:show()
|
self.ports:show()
|
||||||
|
|
||||||
self.portsHandler = Event.onInterval(3, function()
|
-- self.portsHandler = Event.onInterval(3, function()
|
||||||
|
-- self.ports.grid:update()
|
||||||
|
-- self.ports.grid:draw()
|
||||||
|
-- self:sync()
|
||||||
|
-- end)
|
||||||
|
|
||||||
|
elseif event.type == 'ports_update' then
|
||||||
self.ports.grid:update()
|
self.ports.grid:update()
|
||||||
self.ports.grid:draw()
|
self.ports.grid:draw()
|
||||||
self:sync()
|
self:sync()
|
||||||
end)
|
|
||||||
|
|
||||||
elseif event.type == 'ports_hide' then
|
elseif event.type == 'ports_hide' then
|
||||||
Event.off(self.portsHandler)
|
Event.off(self.portsHandler)
|
||||||
@@ -230,7 +240,7 @@ function page:eventHandler(event)
|
|||||||
Config.update('network', config)
|
Config.update('network', config)
|
||||||
|
|
||||||
elseif event.type == 'quit' then
|
elseif event.type == 'quit' then
|
||||||
Event.exitPullEvents()
|
UI:quit()
|
||||||
end
|
end
|
||||||
UI.Page.eventHandler(self, event)
|
UI.Page.eventHandler(self, event)
|
||||||
end
|
end
|
||||||
@@ -243,33 +253,6 @@ function page.menuBar:getActive(menuItem)
|
|||||||
return menuItem.noCheck or not not t
|
return menuItem.noCheck or not not t
|
||||||
end
|
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()
|
Event.onInterval(1, function()
|
||||||
page.grid:update()
|
page.grid:update()
|
||||||
page.grid:draw()
|
page.grid:draw()
|
||||||
@@ -295,4 +278,4 @@ if not device.wireless_modem then
|
|||||||
end
|
end
|
||||||
|
|
||||||
UI:setPage(page)
|
UI:setPage(page)
|
||||||
UI:pullEvents()
|
UI:start()
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
local Alt = require('opus.alternate')
|
local Array = require('opus.array')
|
||||||
local class = require('opus.class')
|
local class = require('opus.class')
|
||||||
local Config = require('opus.config')
|
local Config = require('opus.config')
|
||||||
local Event = require('opus.event')
|
local Event = require('opus.event')
|
||||||
@@ -9,7 +9,6 @@ local Tween = require('opus.ui.tween')
|
|||||||
local UI = require('opus.ui')
|
local UI = require('opus.ui')
|
||||||
local Util = require('opus.util')
|
local Util = require('opus.util')
|
||||||
|
|
||||||
local colors = _G.colors
|
|
||||||
local device = _G.device
|
local device = _G.device
|
||||||
local fs = _G.fs
|
local fs = _G.fs
|
||||||
local os = _G.os
|
local os = _G.os
|
||||||
@@ -18,14 +17,25 @@ local shell = _ENV.shell
|
|||||||
local term = _G.term
|
local term = _G.term
|
||||||
local turtle = _G.turtle
|
local turtle = _G.turtle
|
||||||
|
|
||||||
|
--[[
|
||||||
|
turtle: 39x13
|
||||||
|
computer: 51x19
|
||||||
|
pocket: 26x20
|
||||||
|
]]
|
||||||
|
|
||||||
if not _ENV.multishell then
|
if not _ENV.multishell then
|
||||||
error('multishell is required')
|
error('multishell is required')
|
||||||
end
|
end
|
||||||
|
|
||||||
local REGISTRY_DIR = 'usr/.registry'
|
local REGISTRY_DIR = 'usr/.registry'
|
||||||
local DEFAULT_ICON = NFT.parse("\0308\0317\153\153\153\153\153\
|
|
||||||
\0307\0318\153\153\153\153\153\
|
-- iconExt:gsub('.', function(b) return '\\' .. b:byte() end)
|
||||||
\0308\0317\153\153\153\153\153")
|
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
|
-- overview
|
||||||
local uid = _ENV.multishell.getCurrent()
|
local uid = _ENV.multishell.getCurrent()
|
||||||
@@ -65,6 +75,7 @@ local function parseIcon(iconText)
|
|||||||
if icon.height > 3 or icon.width > 8 then
|
if icon.height > 3 or icon.width > 8 then
|
||||||
error('Must be an NFT image - 3 rows, 8 cols max')
|
error('Must be an NFT image - 3 rows, 8 cols max')
|
||||||
end
|
end
|
||||||
|
NFT.transparency(icon)
|
||||||
end
|
end
|
||||||
return icon
|
return icon
|
||||||
end)
|
end)
|
||||||
@@ -76,45 +87,39 @@ local function parseIcon(iconText)
|
|||||||
return s, m
|
return s, m
|
||||||
end
|
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 {
|
local page = UI.Page {
|
||||||
container = UI.Viewport {
|
container = UI.Viewport {
|
||||||
x = cx,
|
x = 9, y = 1,
|
||||||
y = cy,
|
},
|
||||||
|
tabBar = UI.TabBar {
|
||||||
|
ey = -2,
|
||||||
|
width = 8,
|
||||||
|
selectedBackgroundColor = 'primary',
|
||||||
|
backgroundColor = 'tertiary',
|
||||||
|
unselectedTextColor = 'lightGray',
|
||||||
|
layout = function(self)
|
||||||
|
self.height = nil
|
||||||
|
UI.TabBar.layout(self)
|
||||||
|
end,
|
||||||
},
|
},
|
||||||
tray = UI.Window {
|
tray = UI.Window {
|
||||||
y = -1, width = 8,
|
y = -1, width = 8,
|
||||||
backgroundColor = colors.lightGray,
|
backgroundColor = 'tertiary',
|
||||||
newApp = UI.Button {
|
newApp = UI.FlatButton {
|
||||||
|
x = 2,
|
||||||
text = '+', event = 'new',
|
text = '+', event = 'new',
|
||||||
},
|
},
|
||||||
--[[
|
mode = UI.FlatButton {
|
||||||
volume = UI.Button {
|
x = 4,
|
||||||
x = 3,
|
text = '=', event = 'display_mode',
|
||||||
text = '\15', event = 'volume',
|
},
|
||||||
},]]
|
help = UI.FlatButton {
|
||||||
|
x = 6,
|
||||||
|
text = '?', event = 'help',
|
||||||
|
},
|
||||||
},
|
},
|
||||||
editor = UI.SlideOut {
|
editor = UI.SlideOut {
|
||||||
y = -12, height = 12,
|
y = -12, height = 12,
|
||||||
backgroundColor = colors.cyan,
|
|
||||||
titleBar = UI.TitleBar {
|
titleBar = UI.TitleBar {
|
||||||
title = 'Edit Application',
|
title = 'Edit Application',
|
||||||
event = 'slide_hide',
|
event = 'slide_hide',
|
||||||
@@ -122,7 +127,7 @@ local page = UI.Page {
|
|||||||
form = UI.Form {
|
form = UI.Form {
|
||||||
y = 2, ey = -2,
|
y = 2, ey = -2,
|
||||||
[1] = UI.TextEntry {
|
[1] = UI.TextEntry {
|
||||||
formLabel = 'Title', formKey = 'title', limit = 11, help = 'Application title',
|
formLabel = 'Title', formKey = 'title', limit = 11, width = 13, help = 'Application title',
|
||||||
required = true,
|
required = true,
|
||||||
},
|
},
|
||||||
[2] = UI.TextEntry {
|
[2] = UI.TextEntry {
|
||||||
@@ -130,23 +135,50 @@ local page = UI.Page {
|
|||||||
required = true,
|
required = true,
|
||||||
},
|
},
|
||||||
[3] = UI.TextEntry {
|
[3] = UI.TextEntry {
|
||||||
formLabel = 'Category', formKey = 'category', limit = 11, help = 'Category of application',
|
formLabel = 'Category', formKey = 'category', limit = 6, width = 8, help = 'Category of application',
|
||||||
required = true,
|
required = true,
|
||||||
},
|
},
|
||||||
iconFile = UI.TextEntry {
|
editIcon = UI.Button {
|
||||||
x = 11, ex = -12, y = 7,
|
x = 11, y = 6,
|
||||||
limit = 128, help = 'Path to icon file',
|
text = 'Edit', event = 'editIcon', help = 'Edit icon file',
|
||||||
shadowText = 'Path to icon file',
|
|
||||||
},
|
},
|
||||||
loadIcon = UI.Button {
|
loadIcon = UI.Button {
|
||||||
x = 11, y = 9,
|
x = 11, y = 8,
|
||||||
|
text = 'Load', event = 'loadIcon', help = 'Load icon file',
|
||||||
|
},
|
||||||
|
helpIcon = UI.Button {
|
||||||
|
x = 11, y = 8,
|
||||||
text = 'Load', event = 'loadIcon', help = 'Load icon file',
|
text = 'Load', event = 'loadIcon', help = 'Load icon file',
|
||||||
},
|
},
|
||||||
image = UI.NftImage {
|
image = UI.NftImage {
|
||||||
backgroundColor = colors.black,
|
backgroundColor = 'black',
|
||||||
y = 7, x = 2, height = 3, width = 8,
|
y = 6, 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(),
|
notification = UI.Notification(),
|
||||||
statusBar = UI.StatusBar(),
|
statusBar = UI.StatusBar(),
|
||||||
},
|
},
|
||||||
@@ -205,7 +237,7 @@ local function loadApplications()
|
|||||||
return requirements[a.requires]
|
return requirements[a.requires]
|
||||||
end
|
end
|
||||||
|
|
||||||
return true -- Util.startsWith(a.run, 'http') or shell.resolveProgram(a.run)
|
return true
|
||||||
end)
|
end)
|
||||||
|
|
||||||
local categories = { }
|
local categories = { }
|
||||||
@@ -215,6 +247,7 @@ local function loadApplications()
|
|||||||
categories[f.category] = true
|
categories[f.category] = true
|
||||||
table.insert(buttons, {
|
table.insert(buttons, {
|
||||||
text = f.category,
|
text = f.category,
|
||||||
|
width = 8,
|
||||||
selected = config.currentCategory == f.category
|
selected = config.currentCategory == f.category
|
||||||
})
|
})
|
||||||
end
|
end
|
||||||
@@ -222,13 +255,13 @@ local function loadApplications()
|
|||||||
table.sort(buttons, function(a, b) return a.text < b.text end)
|
table.sort(buttons, function(a, b) return a.text < b.text end)
|
||||||
table.insert(buttons, 1, { text = 'Recent' })
|
table.insert(buttons, 1, { text = 'Recent' })
|
||||||
|
|
||||||
Util.removeByValue(page.children, page.tabBar)
|
for k,v in pairs(buttons) do
|
||||||
|
v.x = 1
|
||||||
|
v.y = k + 1
|
||||||
|
end
|
||||||
|
|
||||||
page:add {
|
page.tabBar.children = { }
|
||||||
tabBar = UI.VerticalTabBar {
|
page.tabBar:addButtons(buttons)
|
||||||
buttons = buttons,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
--page.tabBar:selectTab(config.currentCategory or 'Apps')
|
--page.tabBar:selectTab(config.currentCategory or 'Apps')
|
||||||
page.container:setCategory(config.currentCategory or 'Apps')
|
page.container:setCategory(config.currentCategory or 'Apps')
|
||||||
@@ -243,7 +276,6 @@ UI.Icon.defaults = {
|
|||||||
function UI.Icon:eventHandler(event)
|
function UI.Icon:eventHandler(event)
|
||||||
if event.type == 'mouse_click' then
|
if event.type == 'mouse_click' then
|
||||||
self:setFocus(self.button)
|
self:setFocus(self.button)
|
||||||
--self:emit({ type = self.button.event, button = self.button })
|
|
||||||
return true
|
return true
|
||||||
elseif event.type == 'mouse_doubleclick' then
|
elseif event.type == 'mouse_doubleclick' then
|
||||||
self:emit({ type = self.button.event, button = self.button })
|
self:emit({ type = self.button.event, button = self.button })
|
||||||
@@ -259,37 +291,23 @@ function page.container:setCategory(categoryName, animate)
|
|||||||
self.children = { }
|
self.children = { }
|
||||||
self:reset()
|
self:reset()
|
||||||
|
|
||||||
local function filter(it, f)
|
local filtered = { }
|
||||||
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
|
if categoryName == 'Recent' then
|
||||||
filtered = { }
|
|
||||||
|
|
||||||
for _,v in ipairs(config.Recent) do
|
for _,v in ipairs(config.Recent) do
|
||||||
local app = Util.find(applications, 'key', v)
|
local app = Util.find(applications, 'key', v)
|
||||||
if app then -- and fs.exists(app.run) then
|
if app then
|
||||||
table.insert(filtered, app)
|
table.insert(filtered, app)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
else
|
else
|
||||||
filtered = filter(applications, function(a)
|
filtered = Array.filter(applications, function(a)
|
||||||
return a.category == categoryName -- and fs.exists(a.run)
|
return a.category == categoryName
|
||||||
end)
|
end)
|
||||||
table.sort(filtered, function(a, b) return a.title < b.title end)
|
table.sort(filtered, function(a, b) return a.title < b.title end)
|
||||||
end
|
end
|
||||||
|
|
||||||
for _,program in ipairs(filtered) do
|
for _,program in ipairs(filtered) do
|
||||||
|
|
||||||
local icon
|
local icon
|
||||||
if extSupport and program.iconExt then
|
if extSupport and program.iconExt then
|
||||||
icon = parseIcon(program.iconExt)
|
icon = parseIcon(program.iconExt)
|
||||||
@@ -304,27 +322,43 @@ function page.container:setCategory(categoryName, animate)
|
|||||||
local title = ellipsis(program.title, 8)
|
local title = ellipsis(program.title, 8)
|
||||||
|
|
||||||
local width = math.max(icon.width + 2, #title + 2)
|
local width = math.max(icon.width + 2, #title + 2)
|
||||||
table.insert(self.children, UI.Icon({
|
if config.listMode then
|
||||||
width = width,
|
table.insert(self.children, UI.Icon {
|
||||||
image = UI.NftImage({
|
width = self.width - 2,
|
||||||
x = math.floor((width - icon.width) / 2) + 1,
|
height = 1,
|
||||||
image = icon,
|
UI.Button {
|
||||||
width = 5,
|
x = 1, ex = -1,
|
||||||
height = 3,
|
text = program.title,
|
||||||
}),
|
centered = false,
|
||||||
button = UI.Button({
|
backgroundColor = self:getProperty('backgroundColor'),
|
||||||
x = math.floor((width - #title - 2) / 2) + 1,
|
backgroundFocusColor = 'gray',
|
||||||
y = 4,
|
textColor = 'white',
|
||||||
text = title,
|
textFocusColor = 'white',
|
||||||
backgroundColor = self.backgroundColor,
|
event = 'button',
|
||||||
backgroundFocusColor = colors.gray,
|
app = program,
|
||||||
textColor = colors.white,
|
}
|
||||||
textFocusColor = colors.white,
|
})
|
||||||
width = #title + 2,
|
else
|
||||||
event = 'button',
|
table.insert(self.children, UI.Icon({
|
||||||
app = program,
|
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
|
||||||
end
|
end
|
||||||
|
|
||||||
local gutter = 2
|
local gutter = 2
|
||||||
@@ -334,7 +368,8 @@ function page.container:setCategory(categoryName, animate)
|
|||||||
local col, row = gutter, 2
|
local col, row = gutter, 2
|
||||||
local count = #self.children
|
local count = #self.children
|
||||||
|
|
||||||
local r = math.random(1, 5)
|
local r = math.random(1, 7)
|
||||||
|
local frames = 5
|
||||||
-- reposition all children
|
-- reposition all children
|
||||||
for k,child in ipairs(self.children) do
|
for k,child in ipairs(self.children) do
|
||||||
if r == 1 then
|
if r == 1 then
|
||||||
@@ -356,19 +391,27 @@ function page.container:setCategory(categoryName, animate)
|
|||||||
child.x = self.width
|
child.x = self.width
|
||||||
child.y = self.height - 3
|
child.y = self.height - 3
|
||||||
end
|
end
|
||||||
|
elseif r == 6 then
|
||||||
|
child.x = col
|
||||||
|
child.y = 1
|
||||||
|
elseif r == 7 then
|
||||||
|
child.x = 1
|
||||||
|
child.y = self.height - 3
|
||||||
end
|
end
|
||||||
child.tween = Tween.new(6, child, { x = col, y = row }, 'linear')
|
child.tween = Tween.new(frames, child, { x = col, y = row }, 'inQuad')
|
||||||
|
|
||||||
if not animate then
|
if not animate then
|
||||||
child.x = col
|
child.x = col
|
||||||
child.y = row
|
child.y = row
|
||||||
end
|
end
|
||||||
|
|
||||||
|
self:setViewHeight(row + (config.listMode and 1 or 4))
|
||||||
|
|
||||||
if k < count then
|
if k < count then
|
||||||
col = col + child.width
|
col = col + child.width
|
||||||
if col + self.children[k + 1].width + gutter - 2 > self.width then
|
if col + self.children[k + 1].width + gutter - 2 > self.width then
|
||||||
col = gutter
|
col = gutter
|
||||||
row = row + 5
|
row = row + (config.listMode and 1 or 5)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
@@ -378,15 +421,12 @@ function page.container:setCategory(categoryName, animate)
|
|||||||
local function transition()
|
local function transition()
|
||||||
local i = 1
|
local i = 1
|
||||||
return function()
|
return function()
|
||||||
self:clear()
|
|
||||||
for _,child in pairs(self.children) do
|
for _,child in pairs(self.children) do
|
||||||
child.tween:update(1)
|
child.tween:update(1)
|
||||||
child.x = math.floor(child.x)
|
child:move(math.floor(child.x), math.floor(child.y))
|
||||||
child.y = math.floor(child.y)
|
|
||||||
child:draw()
|
|
||||||
end
|
end
|
||||||
i = i + 1
|
i = i + 1
|
||||||
return i < 7
|
return i <= frames
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
self:addTransition(transition)
|
self:addTransition(transition)
|
||||||
@@ -428,16 +468,19 @@ function page:eventHandler(event)
|
|||||||
shell.switchTab(shell.openTab(event.button.app.run))
|
shell.switchTab(shell.openTab(event.button.app.run))
|
||||||
|
|
||||||
elseif event.type == 'shell' then
|
elseif event.type == 'shell' then
|
||||||
shell.switchTab(shell.openTab(Alt.get('shell')))
|
shell.switchTab(shell.openTab('shell'))
|
||||||
|
|
||||||
elseif event.type == 'lua' then
|
elseif event.type == 'lua' then
|
||||||
shell.switchTab(shell.openTab(Alt.get('lua')))
|
shell.switchTab(shell.openTab('Lua'))
|
||||||
|
|
||||||
elseif event.type == 'files' then
|
elseif event.type == 'files' then
|
||||||
shell.switchTab(shell.openTab(Alt.get('files')))
|
shell.switchTab(shell.openTab('Files'))
|
||||||
|
|
||||||
elseif event.type == 'network' then
|
elseif event.type == 'network' then
|
||||||
shell.switchTab(shell.openTab('network'))
|
shell.switchTab(shell.openTab('Network'))
|
||||||
|
|
||||||
|
elseif event.type == 'help' then
|
||||||
|
shell.switchTab(shell.openTab('Help Overview'))
|
||||||
|
|
||||||
elseif event.type == 'focus_change' then
|
elseif event.type == 'focus_change' then
|
||||||
if event.focused.parent.UIElement == 'Icon' then
|
if event.focused.parent.UIElement == 'Icon' then
|
||||||
@@ -473,6 +516,13 @@ function page:eventHandler(event)
|
|||||||
end
|
end
|
||||||
self.editor:show({ category = category })
|
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
|
elseif event.type == 'edit' then
|
||||||
local focused = page:getFocused()
|
local focused = page:getFocused()
|
||||||
if focused.app then
|
if focused.app then
|
||||||
@@ -480,7 +530,7 @@ function page:eventHandler(event)
|
|||||||
end
|
end
|
||||||
|
|
||||||
else
|
else
|
||||||
UI.Page.eventHandler(self, event)
|
return UI.Page.eventHandler(self, event)
|
||||||
end
|
end
|
||||||
return true
|
return true
|
||||||
end
|
end
|
||||||
@@ -502,11 +552,6 @@ function page.editor:show(app)
|
|||||||
self:focusFirst()
|
self:focusFirst()
|
||||||
end
|
end
|
||||||
|
|
||||||
function page.editor.form.image:draw()
|
|
||||||
self:clear()
|
|
||||||
UI.NftImage.draw(self)
|
|
||||||
end
|
|
||||||
|
|
||||||
function page.editor:updateApplications(app)
|
function page.editor:updateApplications(app)
|
||||||
if not app.key then
|
if not app.key then
|
||||||
app.key = SHA.compute(app.title)
|
app.key = SHA.compute(app.title)
|
||||||
@@ -516,36 +561,51 @@ function page.editor:updateApplications(app)
|
|||||||
loadApplications()
|
loadApplications()
|
||||||
end
|
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)
|
function page.editor:eventHandler(event)
|
||||||
if event.type == 'form_cancel' or event.type == 'cancel' then
|
if event.type == 'form_cancel' or event.type == 'cancel' then
|
||||||
self:hide()
|
self:hide()
|
||||||
|
|
||||||
elseif event.type == 'focus_change' then
|
elseif event.type == 'focus_change' then
|
||||||
self.statusBar:setStatus(event.focused.help or '')
|
self.statusBar:setStatus(event.focused.help or '')
|
||||||
self.statusBar:draw()
|
|
||||||
|
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)
|
||||||
|
|
||||||
elseif event.type == 'loadIcon' then
|
elseif event.type == 'loadIcon' then
|
||||||
local s, m = pcall(function()
|
self.file_open:show()
|
||||||
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
|
elseif event.type == 'form_invalid' then
|
||||||
self.notification:error(event.message)
|
self.notification:error(event.message)
|
||||||
@@ -554,8 +614,6 @@ function page.editor:eventHandler(event)
|
|||||||
local values = self.form.values
|
local values = self.form.values
|
||||||
self:hide()
|
self:hide()
|
||||||
self:updateApplications(values)
|
self:updateApplications(values)
|
||||||
--page:refresh()
|
|
||||||
--page:draw()
|
|
||||||
config.currentCategory = values.category
|
config.currentCategory = values.category
|
||||||
Config.update('Overview', config)
|
Config.update('Overview', config)
|
||||||
os.queueEvent('overview_refresh')
|
os.queueEvent('overview_refresh')
|
||||||
@@ -565,10 +623,6 @@ function page.editor:eventHandler(event)
|
|||||||
return true
|
return true
|
||||||
end
|
end
|
||||||
|
|
||||||
UI:setPages({
|
|
||||||
main = page,
|
|
||||||
})
|
|
||||||
|
|
||||||
local function reload()
|
local function reload()
|
||||||
loadApplications()
|
loadApplications()
|
||||||
page:refresh()
|
page:refresh()
|
||||||
@@ -594,5 +648,4 @@ end)
|
|||||||
loadApplications()
|
loadApplications()
|
||||||
|
|
||||||
UI:setPage(page)
|
UI:setPage(page)
|
||||||
|
UI:start()
|
||||||
UI:pullEvents()
|
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
local Ansi = require('opus.ansi')
|
local Ansi = require('opus.ansi')
|
||||||
|
local Config = require('opus.config')
|
||||||
local Packages = require('opus.packages')
|
local Packages = require('opus.packages')
|
||||||
local UI = require('opus.ui')
|
local UI = require('opus.ui')
|
||||||
local Util = require('opus.util')
|
local Util = require('opus.util')
|
||||||
@@ -8,9 +9,11 @@ local term = _G.term
|
|||||||
|
|
||||||
UI:configure('PackageManager', ...)
|
UI:configure('PackageManager', ...)
|
||||||
|
|
||||||
|
local config = Config.load('package')
|
||||||
|
|
||||||
local page = UI.Page {
|
local page = UI.Page {
|
||||||
grid = UI.ScrollingGrid {
|
grid = UI.ScrollingGrid {
|
||||||
x = 2, ex = 14, y = 2, ey = -5,
|
x = 2, ex = 14, y = 2, ey = -6,
|
||||||
values = { },
|
values = { },
|
||||||
columns = {
|
columns = {
|
||||||
{ heading = 'Package', key = 'name' },
|
{ heading = 'Package', key = 'name' },
|
||||||
@@ -21,13 +24,13 @@ local page = UI.Page {
|
|||||||
},
|
},
|
||||||
add = UI.Button {
|
add = UI.Button {
|
||||||
x = 2, y = -3,
|
x = 2, y = -3,
|
||||||
text = 'Install',
|
text = ' + ',
|
||||||
event = 'action',
|
event = 'action',
|
||||||
help = 'Install or update',
|
help = 'Install or update',
|
||||||
},
|
},
|
||||||
remove = UI.Button {
|
remove = UI.Button {
|
||||||
x = 12, y = -3,
|
x = 8, y = -3,
|
||||||
text = 'Remove ',
|
text = ' - ',
|
||||||
event = 'action',
|
event = 'action',
|
||||||
operation = 'uninstall',
|
operation = 'uninstall',
|
||||||
operationText = 'Remove',
|
operationText = 'Remove',
|
||||||
@@ -41,10 +44,17 @@ local page = UI.Page {
|
|||||||
},
|
},
|
||||||
description = UI.TextArea {
|
description = UI.TextArea {
|
||||||
x = 16, y = 3, ey = -5,
|
x = 16, y = 3, ey = -5,
|
||||||
marginRight = 0, marginLeft = 0,
|
marginRight = 2, marginLeft = 0,
|
||||||
|
},
|
||||||
|
UI.Checkbox {
|
||||||
|
x = 3, y = -5,
|
||||||
|
label = 'Compress',
|
||||||
|
textColor = 'yellow',
|
||||||
|
backgroundColor = 'primary',
|
||||||
|
value = config.compression,
|
||||||
|
help = 'Compress packages (experimental)',
|
||||||
},
|
},
|
||||||
action = UI.SlideOut {
|
action = UI.SlideOut {
|
||||||
backgroundColor = colors.cyan,
|
|
||||||
titleBar = UI.TitleBar {
|
titleBar = UI.TitleBar {
|
||||||
event = 'hide-action',
|
event = 'hide-action',
|
||||||
},
|
},
|
||||||
@@ -103,8 +113,6 @@ end
|
|||||||
function page.action:show()
|
function page.action:show()
|
||||||
self.output.win:clear()
|
self.output.win:clear()
|
||||||
UI.SlideOut.show(self)
|
UI.SlideOut.show(self)
|
||||||
--self.output:draw()
|
|
||||||
--self.output.win.redraw()
|
|
||||||
end
|
end
|
||||||
|
|
||||||
function page:run(operation, name)
|
function page:run(operation, name)
|
||||||
@@ -128,7 +136,6 @@ end
|
|||||||
function page:updateSelection(selected)
|
function page:updateSelection(selected)
|
||||||
self.add.operation = selected.installed and 'update' or 'install'
|
self.add.operation = selected.installed and 'update' or 'install'
|
||||||
self.add.operationText = 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.remove.inactive = not selected.installed
|
||||||
self.add:draw()
|
self.add:draw()
|
||||||
self.remove:draw()
|
self.remove:draw()
|
||||||
@@ -141,12 +148,16 @@ function page:eventHandler(event)
|
|||||||
elseif event.type == 'grid_focus_row' then
|
elseif event.type == 'grid_focus_row' then
|
||||||
local manifest = event.selected.manifest
|
local manifest = event.selected.manifest
|
||||||
|
|
||||||
self.description.value = string.format('%s%s\n\n%s%s',
|
self.description:setValue(string.format('%s%s\n\n%s%s',
|
||||||
Ansi.yellow, manifest.title,
|
Ansi.yellow, manifest.title,
|
||||||
Ansi.white, manifest.description)
|
Ansi.white, manifest.description))
|
||||||
self.description:draw()
|
self.description:draw()
|
||||||
self:updateSelection(event.selected)
|
self:updateSelection(event.selected)
|
||||||
|
|
||||||
|
elseif event.type == 'checkbox_change' then
|
||||||
|
config.compression = not config.compression
|
||||||
|
Config.update('package', config)
|
||||||
|
|
||||||
elseif event.type == 'updateall' then
|
elseif event.type == 'updateall' then
|
||||||
self.operation = 'updateall'
|
self.operation = 'updateall'
|
||||||
self.action.button.text = ' Begin '
|
self.action.button.text = ' Begin '
|
||||||
@@ -184,7 +195,7 @@ function page:eventHandler(event)
|
|||||||
self.action.button:draw()
|
self.action.button:draw()
|
||||||
|
|
||||||
elseif event.type == 'quit' then
|
elseif event.type == 'quit' then
|
||||||
UI:exitPullEvents()
|
UI:quit()
|
||||||
end
|
end
|
||||||
UI.Page.eventHandler(self, event)
|
UI.Page.eventHandler(self, event)
|
||||||
end
|
end
|
||||||
@@ -196,4 +207,4 @@ Packages:downloadList()
|
|||||||
page:loadPackages()
|
page:loadPackages()
|
||||||
page:sync()
|
page:sync()
|
||||||
|
|
||||||
UI:pullEvents()
|
UI:start()
|
||||||
|
|||||||
238
sys/apps/Partition.lua
Normal file
238
sys/apps/Partition.lua
Normal file
@@ -0,0 +1,238 @@
|
|||||||
|
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()
|
||||||
@@ -1,5 +1,3 @@
|
|||||||
local Alt = require('opus.alternate')
|
|
||||||
|
|
||||||
local kernel = _G.kernel
|
local kernel = _G.kernel
|
||||||
local os = _G.os
|
local os = _G.os
|
||||||
local shell = _ENV.shell
|
local shell = _ENV.shell
|
||||||
@@ -20,7 +18,7 @@ kernel.hook('kernel_focus', function(_, eventData)
|
|||||||
end
|
end
|
||||||
end
|
end
|
||||||
if nextTab == launcherTab then
|
if nextTab == launcherTab then
|
||||||
shell.switchTab(shell.openTab(Alt.get('shell')))
|
shell.switchTab(shell.openTab('shell'))
|
||||||
else
|
else
|
||||||
shell.switchTab(nextTab.uid)
|
shell.switchTab(nextTab.uid)
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -5,14 +5,13 @@ local Util = require('opus.util')
|
|||||||
local colors = _G.colors
|
local colors = _G.colors
|
||||||
local device = _G.device
|
local device = _G.device
|
||||||
local textutils = _G.textutils
|
local textutils = _G.textutils
|
||||||
local peripheral = _G.peripheral
|
|
||||||
local multishell = _ENV.multishell
|
local multishell = _ENV.multishell
|
||||||
|
|
||||||
local gridColumns = {}
|
local gridColumns = {}
|
||||||
table.insert(gridColumns, { heading = '#', key = 'id', width = 5, align = 'right' })
|
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 = 'Port', key = 'portid', width = 5, align = 'right' })
|
||||||
table.insert(gridColumns, { heading = 'Reply', key = 'replyid', width = 5, align = 'right' })
|
table.insert(gridColumns, { heading = 'Reply', key = 'replyid', width = 5, align = 'right' })
|
||||||
if UI.defaultDevice.width > 50 then
|
if UI.term.width > 50 then
|
||||||
table.insert(gridColumns, { heading = 'Dist', key = 'distance', width = 6, align = 'right' })
|
table.insert(gridColumns, { heading = 'Dist', key = 'distance', width = 6, align = 'right' })
|
||||||
end
|
end
|
||||||
table.insert(gridColumns, { heading = 'Msg', key = 'packetStr' })
|
table.insert(gridColumns, { heading = 'Msg', key = 'packetStr' })
|
||||||
@@ -42,12 +41,13 @@ local page = UI.Page {
|
|||||||
|
|
||||||
configSlide = UI.SlideOut {
|
configSlide = UI.SlideOut {
|
||||||
y = -11,
|
y = -11,
|
||||||
titleBar = UI.TitleBar { title = 'Sniffer Config', event = 'config_close' },
|
titleBar = UI.TitleBar { title = 'Sniffer Config', event = 'config_close', backgroundColor = colors.black },
|
||||||
accelerators = { ['backspace'] = 'config_close' },
|
accelerators = { ['backspace'] = 'config_close' },
|
||||||
configTabs = UI.Tabs {
|
configTabs = UI.Tabs {
|
||||||
y = 2,
|
y = 2,
|
||||||
filterTab = UI.Tab {
|
filterTab = UI.Tab {
|
||||||
tabTitle = 'Filter',
|
title = 'Filter',
|
||||||
|
noFill = true,
|
||||||
filterGridText = UI.Text {
|
filterGridText = UI.Text {
|
||||||
x = 2, y = 2,
|
x = 2, y = 2,
|
||||||
value = 'ID filter',
|
value = 'ID filter',
|
||||||
@@ -93,7 +93,7 @@ local page = UI.Page {
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
modemTab = UI.Tab {
|
modemTab = UI.Tab {
|
||||||
tabTitle = 'Modem',
|
title = 'Modem',
|
||||||
channelGrid = UI.ScrollingGrid {
|
channelGrid = UI.ScrollingGrid {
|
||||||
x = 2, y = 2,
|
x = 2, y = 2,
|
||||||
width = 12, height = 5,
|
width = 12, height = 5,
|
||||||
@@ -130,7 +130,6 @@ local page = UI.Page {
|
|||||||
title = 'Packet Information',
|
title = 'Packet Information',
|
||||||
event = 'packet_close',
|
event = 'packet_close',
|
||||||
},
|
},
|
||||||
backgroundColor = colors.cyan,
|
|
||||||
accelerators = {
|
accelerators = {
|
||||||
['backspace'] = 'packet_close',
|
['backspace'] = 'packet_close',
|
||||||
['left'] = 'prev_packet',
|
['left'] = 'prev_packet',
|
||||||
@@ -256,7 +255,7 @@ function page.packetSlide:eventHandler(event)
|
|||||||
page:setFocus(page.packetGrid)
|
page:setFocus(page.packetGrid)
|
||||||
|
|
||||||
elseif event.type == 'packet_lua' then
|
elseif event.type == 'packet_lua' then
|
||||||
multishell.openTab({ path = 'sys/apps/Lua.lua', args = { self.currentPacket.message }, focused = true })
|
multishell.openTab(_ENV, { path = 'sys/apps/Lua.lua', args = { self.currentPacket.message }, focused = true })
|
||||||
|
|
||||||
elseif event.type == 'prev_packet' then
|
elseif event.type == 'prev_packet' then
|
||||||
local c = self.currentPacket
|
local c = self.currentPacket
|
||||||
@@ -280,7 +279,7 @@ function page.packetSlide:eventHandler(event)
|
|||||||
end
|
end
|
||||||
|
|
||||||
function page.packetGrid:getDisplayValues(row)
|
function page.packetGrid:getDisplayValues(row)
|
||||||
local row = Util.shallowCopy(row)
|
row = Util.shallowCopy(row)
|
||||||
row.distance = Util.toBytes(Util.round(row.distance), 2)
|
row.distance = Util.toBytes(Util.round(row.distance), 2)
|
||||||
return row
|
return row
|
||||||
end
|
end
|
||||||
@@ -356,7 +355,7 @@ function page:eventHandler(event)
|
|||||||
self.packetSlide:show(event.selected)
|
self.packetSlide:show(event.selected)
|
||||||
|
|
||||||
elseif event.type == 'quit' then
|
elseif event.type == 'quit' then
|
||||||
Event.exitPullEvents()
|
UI:quit()
|
||||||
|
|
||||||
else return UI.Page.eventHandler(self, event)
|
else return UI.Page.eventHandler(self, event)
|
||||||
end
|
end
|
||||||
@@ -386,4 +385,4 @@ if args[1] then
|
|||||||
end
|
end
|
||||||
|
|
||||||
UI:setPage(page)
|
UI:setPage(page)
|
||||||
UI:pullEvents()
|
UI:start()
|
||||||
|
|||||||
@@ -6,62 +6,6 @@ local shell = _ENV.shell
|
|||||||
|
|
||||||
UI:configure('System', ...)
|
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 function loadDirectory(dir)
|
||||||
local plugins = { }
|
local plugins = { }
|
||||||
for _, file in pairs(fs.list(dir)) do
|
for _, file in pairs(fs.list(dir)) do
|
||||||
@@ -70,16 +14,69 @@ local function loadDirectory(dir)
|
|||||||
_G.printError('Error loading: ' .. file)
|
_G.printError('Error loading: ' .. file)
|
||||||
error(m or 'Unknown error')
|
error(m or 'Unknown error')
|
||||||
elseif s and m then
|
elseif s and m then
|
||||||
table.insert(plugins, { tab = m, name = m.tabTitle, description = m.description })
|
table.insert(plugins, { tab = m, name = m.title, description = m.description })
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
return plugins
|
return plugins
|
||||||
end
|
end
|
||||||
|
|
||||||
local programDir = fs.getDir(shell.getRunningProgram())
|
local programDir = fs.getDir(_ENV.arg[0])
|
||||||
local plugins = loadDirectory(fs.combine(programDir, 'system'), { })
|
local plugins = loadDirectory(fs.combine(programDir, 'system'), { })
|
||||||
|
|
||||||
systemPage.tabs.settings.grid:setValues(plugins)
|
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()
|
||||||
|
|
||||||
UI:setPage(systemPage)
|
elseif event.type == 'category_select' then
|
||||||
UI:pullEvents()
|
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()
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ local UI = require('opus.ui')
|
|||||||
|
|
||||||
local kernel = _G.kernel
|
local kernel = _G.kernel
|
||||||
local multishell = _ENV.multishell
|
local multishell = _ENV.multishell
|
||||||
|
local tasks = multishell and multishell.getTabs and multishell.getTabs() or kernel.routines
|
||||||
|
|
||||||
UI:configure('Tasks', ...)
|
UI:configure('Tasks', ...)
|
||||||
|
|
||||||
@@ -11,6 +12,7 @@ local page = UI.Page {
|
|||||||
buttons = {
|
buttons = {
|
||||||
{ text = 'Activate', event = 'activate' },
|
{ text = 'Activate', event = 'activate' },
|
||||||
{ text = 'Terminate', event = 'terminate' },
|
{ text = 'Terminate', event = 'terminate' },
|
||||||
|
{ text = 'Inspect', event = 'inspect' },
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
grid = UI.ScrollingGrid {
|
grid = UI.ScrollingGrid {
|
||||||
@@ -21,7 +23,7 @@ local page = UI.Page {
|
|||||||
{ heading = 'Status', key = 'status' },
|
{ heading = 'Status', key = 'status' },
|
||||||
{ heading = 'Time', key = 'timestamp' },
|
{ heading = 'Time', key = 'timestamp' },
|
||||||
},
|
},
|
||||||
values = kernel.routines,
|
values = tasks,
|
||||||
sortColumn = 'uid',
|
sortColumn = 'uid',
|
||||||
autospace = true,
|
autospace = true,
|
||||||
getDisplayValues = function (_, row)
|
getDisplayValues = function (_, row)
|
||||||
@@ -38,7 +40,7 @@ local page = UI.Page {
|
|||||||
},
|
},
|
||||||
accelerators = {
|
accelerators = {
|
||||||
[ 'control-q' ] = 'quit',
|
[ 'control-q' ] = 'quit',
|
||||||
space = 'activate',
|
[ ' ' ] = 'activate',
|
||||||
t = 'terminate',
|
t = 'terminate',
|
||||||
},
|
},
|
||||||
eventHandler = function (self, event)
|
eventHandler = function (self, event)
|
||||||
@@ -48,10 +50,16 @@ local page = UI.Page {
|
|||||||
multishell.setFocus(t.uid)
|
multishell.setFocus(t.uid)
|
||||||
elseif event.type == 'terminate' then
|
elseif event.type == 'terminate' then
|
||||||
multishell.terminate(t.uid)
|
multishell.terminate(t.uid)
|
||||||
|
elseif event.type == 'inspect' then
|
||||||
|
multishell.openTab(_ENV, {
|
||||||
|
path = 'sys/apps/Lua.lua',
|
||||||
|
args = { t },
|
||||||
|
focused = true,
|
||||||
|
})
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
if event.type == 'quit' then
|
if event.type == 'quit' then
|
||||||
Event.exitPullEvents()
|
UI:quit()
|
||||||
end
|
end
|
||||||
UI.Page.eventHandler(self, event)
|
UI.Page.eventHandler(self, event)
|
||||||
end
|
end
|
||||||
@@ -64,4 +72,4 @@ Event.onInterval(1, function()
|
|||||||
end)
|
end)
|
||||||
|
|
||||||
UI:setPage(page)
|
UI:setPage(page)
|
||||||
UI:pullEvents()
|
UI:start()
|
||||||
|
|||||||
54
sys/apps/Version.lua
Normal file
54
sys/apps/Version.lua
Normal file
@@ -0,0 +1,54 @@
|
|||||||
|
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()
|
||||||
@@ -33,87 +33,85 @@ https://github.com/kepler155c/opus]]
|
|||||||
local page = UI.Page {
|
local page = UI.Page {
|
||||||
wizard = UI.Wizard {
|
wizard = UI.Wizard {
|
||||||
ey = -2,
|
ey = -2,
|
||||||
pages = {
|
splash = UI.WizardPage {
|
||||||
splash = UI.WizardPage {
|
index = 1,
|
||||||
index = 1,
|
intro = UI.TextArea {
|
||||||
intro = UI.TextArea {
|
textColor = colors.yellow,
|
||||||
textColor = colors.yellow,
|
inactive = true,
|
||||||
inactive = true,
|
x = 3, ex = -3, y = 2, ey = -2,
|
||||||
x = 3, ex = -3, y = 2, ey = -2,
|
value = string.format(splashIntro, Ansi.white),
|
||||||
value = string.format(splashIntro, Ansi.white),
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
label = UI.WizardPage {
|
},
|
||||||
index = 2,
|
label = UI.WizardPage {
|
||||||
labelText = UI.Text {
|
index = 2,
|
||||||
x = 3, y = 2,
|
labelText = UI.Text {
|
||||||
value = 'Label'
|
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),
|
|
||||||
},
|
|
||||||
validate = function (self)
|
|
||||||
if self.label.value then
|
|
||||||
os.setComputerLabel(self.label.value)
|
|
||||||
end
|
|
||||||
return true
|
|
||||||
end,
|
|
||||||
},
|
},
|
||||||
password = UI.WizardPage {
|
label = UI.TextEntry {
|
||||||
index = 3,
|
x = 9, y = 2, ex = -3,
|
||||||
passwordLabel = UI.Text {
|
limit = 32,
|
||||||
x = 3, y = 2,
|
value = os.getComputerLabel(),
|
||||||
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 {
|
intro = UI.TextArea {
|
||||||
index = 4,
|
textColor = colors.yellow,
|
||||||
button = UI.Button {
|
inactive = true,
|
||||||
x = 3, y = -3,
|
x = 3, ex = -3, y = 4, ey = -3,
|
||||||
text = 'Open Package Manager',
|
value = string.format(labelIntro, Ansi.white),
|
||||||
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 {
|
validate = function (self)
|
||||||
index = 5,
|
if self.label.value then
|
||||||
intro = UI.TextArea {
|
os.setComputerLabel(self.label.value)
|
||||||
textColor = colors.yellow,
|
end
|
||||||
inactive = true,
|
return true
|
||||||
x = 3, ex = -3, y = 2, ey = -2,
|
end,
|
||||||
value = string.format(contributorsIntro, Ansi.white, Ansi.yellow, Ansi.white),
|
},
|
||||||
},
|
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),
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
@@ -131,7 +129,7 @@ function page:eventHandler(event)
|
|||||||
shell.openForegroundTab('PackageManager')
|
shell.openForegroundTab('PackageManager')
|
||||||
|
|
||||||
elseif event.type == 'wizard_complete' or event.type == 'cancel' then
|
elseif event.type == 'wizard_complete' or event.type == 'cancel' then
|
||||||
UI.exitPullEvents()
|
UI:quit()
|
||||||
|
|
||||||
else
|
else
|
||||||
return UI.Page.eventHandler(self, event)
|
return UI.Page.eventHandler(self, event)
|
||||||
@@ -140,4 +138,4 @@ function page:eventHandler(event)
|
|||||||
end
|
end
|
||||||
|
|
||||||
UI:setPage(page)
|
UI:setPage(page)
|
||||||
UI:pullEvents()
|
UI:start()
|
||||||
|
|||||||
24
sys/apps/compat.lua
Normal file
24
sys/apps/compat.lua
Normal file
@@ -0,0 +1,24 @@
|
|||||||
|
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
|
||||||
39
sys/apps/fileui.lua
Normal file
39
sys/apps/fileui.lua
Normal file
@@ -0,0 +1,39 @@
|
|||||||
|
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
|
||||||
14
sys/apps/genotp.lua
Normal file
14
sys/apps/genotp.lua
Normal file
@@ -0,0 +1,14 @@
|
|||||||
|
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)
|
||||||
@@ -49,7 +49,7 @@ page = UI.Page {
|
|||||||
backgroundColor = colors.red,
|
backgroundColor = colors.red,
|
||||||
y = '50%',
|
y = '50%',
|
||||||
properties = UI.Tab {
|
properties = UI.Tab {
|
||||||
tabTitle = 'Properties',
|
title = 'Properties',
|
||||||
grid = UI.ScrollingGrid {
|
grid = UI.ScrollingGrid {
|
||||||
headerBackgroundColor = colors.red,
|
headerBackgroundColor = colors.red,
|
||||||
sortColumn = 'key',
|
sortColumn = 'key',
|
||||||
@@ -64,7 +64,7 @@ page = UI.Page {
|
|||||||
},
|
},
|
||||||
methodsTab = UI.Tab {
|
methodsTab = UI.Tab {
|
||||||
index = 2,
|
index = 2,
|
||||||
tabTitle = 'Methods',
|
title = 'Methods',
|
||||||
grid = UI.ScrollingGrid {
|
grid = UI.ScrollingGrid {
|
||||||
ex = '50%',
|
ex = '50%',
|
||||||
headerBackgroundColor = colors.red,
|
headerBackgroundColor = colors.red,
|
||||||
@@ -85,7 +85,7 @@ page = UI.Page {
|
|||||||
},
|
},
|
||||||
events = UI.Tab {
|
events = UI.Tab {
|
||||||
index = 1,
|
index = 1,
|
||||||
tabTitle = 'Events',
|
title = 'Events',
|
||||||
UI.MenuBar {
|
UI.MenuBar {
|
||||||
y = -1,
|
y = -1,
|
||||||
backgroundColor = colors.red,
|
backgroundColor = colors.red,
|
||||||
@@ -110,7 +110,7 @@ page = UI.Page {
|
|||||||
self.grid:draw()
|
self.grid:draw()
|
||||||
|
|
||||||
elseif event.type == 'grid_select' then
|
elseif event.type == 'grid_select' then
|
||||||
multishell.openTab({
|
multishell.openTab(_ENV, {
|
||||||
path = 'sys/apps/Lua.lua',
|
path = 'sys/apps/Lua.lua',
|
||||||
args = { event.selected.raw },
|
args = { event.selected.raw },
|
||||||
focused = true,
|
focused = true,
|
||||||
@@ -133,6 +133,12 @@ page = UI.Page {
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
accelerators = {
|
||||||
|
['shift-right'] = 'size',
|
||||||
|
['shift-left' ] = 'size',
|
||||||
|
['shift-up' ] = 'size',
|
||||||
|
['shift-down' ] = 'size',
|
||||||
|
},
|
||||||
eventHandler = function (self, event)
|
eventHandler = function (self, event)
|
||||||
if event.type == 'focus_change' and isRelevant(event.focused) then
|
if event.type == 'focus_change' and isRelevant(event.focused) then
|
||||||
focused = event.focused
|
focused = event.focused
|
||||||
@@ -144,7 +150,6 @@ page = UI.Page {
|
|||||||
})
|
})
|
||||||
end
|
end
|
||||||
self.tabs.properties.grid:setValues(t)
|
self.tabs.properties.grid:setValues(t)
|
||||||
self.tabs.properties.grid:update()
|
|
||||||
self.tabs.properties.grid:draw()
|
self.tabs.properties.grid:draw()
|
||||||
|
|
||||||
t = { }
|
t = { }
|
||||||
@@ -156,7 +161,6 @@ page = UI.Page {
|
|||||||
end
|
end
|
||||||
end
|
end
|
||||||
self.tabs.methodsTab.grid:setValues(t)
|
self.tabs.methodsTab.grid:setValues(t)
|
||||||
self.tabs.methodsTab.grid:update()
|
|
||||||
self.tabs.methodsTab.grid:draw()
|
self.tabs.methodsTab.grid:draw()
|
||||||
|
|
||||||
elseif event.type == 'edit_property' then
|
elseif event.type == 'edit_property' then
|
||||||
@@ -168,6 +172,19 @@ page = UI.Page {
|
|||||||
|
|
||||||
elseif event.type == 'editor_apply' then
|
elseif event.type == 'editor_apply' then
|
||||||
self.editor:hide()
|
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
|
end
|
||||||
|
|
||||||
return UI.Page.eventHandler(self, event)
|
return UI.Page.eventHandler(self, event)
|
||||||
@@ -175,4 +192,4 @@ page = UI.Page {
|
|||||||
}
|
}
|
||||||
|
|
||||||
UI:setPage(page)
|
UI:setPage(page)
|
||||||
UI:pullEvents()
|
UI:start()
|
||||||
|
|||||||
@@ -1,5 +1,3 @@
|
|||||||
_G.requireInjector(_ENV)
|
|
||||||
|
|
||||||
local Event = require('opus.event')
|
local Event = require('opus.event')
|
||||||
local Util = require('opus.util')
|
local Util = require('opus.util')
|
||||||
|
|
||||||
|
|||||||
@@ -33,7 +33,7 @@ Event.on('generate_keypair', function()
|
|||||||
table.insert(keyPairs, { generateKeyPair() })
|
table.insert(keyPairs, { generateKeyPair() })
|
||||||
_G._syslog('Generated keypair in ' .. timer())
|
_G._syslog('Generated keypair in ' .. timer())
|
||||||
if #keyPairs >= 3 then
|
if #keyPairs >= 3 then
|
||||||
break
|
break
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end)
|
end)
|
||||||
|
|||||||
@@ -31,21 +31,14 @@ local function snmpConnection(socket)
|
|||||||
socket:write('pong')
|
socket:write('pong')
|
||||||
|
|
||||||
elseif msg.type == 'script' then
|
elseif msg.type == 'script' then
|
||||||
local env = setmetatable(Util.shallowCopy(_ENV), { __index = _G })
|
kernel.run(_ENV, {
|
||||||
local fn, err = load(msg.args, 'script', nil, env)
|
chunk = msg.args,
|
||||||
if fn then
|
title = 'script',
|
||||||
kernel.run({
|
})
|
||||||
fn = fn,
|
|
||||||
env = env,
|
|
||||||
title = 'script',
|
|
||||||
})
|
|
||||||
else
|
|
||||||
_G.printError(err)
|
|
||||||
end
|
|
||||||
|
|
||||||
elseif msg.type == 'scriptEx' then
|
elseif msg.type == 'scriptEx' then
|
||||||
local s, m = pcall(function()
|
local s, m = pcall(function()
|
||||||
local env = setmetatable(Util.shallowCopy(_ENV), { __index = _G })
|
local env = kernel.makeEnv(_ENV)
|
||||||
local fn, m = load(msg.args, 'script', nil, env)
|
local fn, m = load(msg.args, 'script', nil, env)
|
||||||
if not fn then
|
if not fn then
|
||||||
error(m)
|
error(m)
|
||||||
|
|||||||
@@ -1,9 +1,9 @@
|
|||||||
local Alt = require('opus.alternate')
|
|
||||||
local Event = require('opus.event')
|
local Event = require('opus.event')
|
||||||
local Socket = require('opus.socket')
|
local Socket = require('opus.socket')
|
||||||
local Util = require('opus.util')
|
local Util = require('opus.util')
|
||||||
|
|
||||||
local kernel = _G.kernel
|
local kernel = _G.kernel
|
||||||
|
local shell = _ENV.shell
|
||||||
local term = _G.term
|
local term = _G.term
|
||||||
local window = _G.window
|
local window = _G.window
|
||||||
|
|
||||||
@@ -41,18 +41,17 @@ local function telnetHost(socket, mode)
|
|||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
local shellThread = kernel.run({
|
local shellThread = kernel.run(_ENV, {
|
||||||
terminal = win,
|
|
||||||
window = win,
|
window = win,
|
||||||
title = mode .. ' client',
|
title = mode .. ' client',
|
||||||
hidden = true,
|
hidden = true,
|
||||||
co = coroutine.create(function()
|
fn = function()
|
||||||
Util.run(_ENV, Alt.get('shell'), table.unpack(termInfo.program))
|
Util.run(kernel.makeEnv(_ENV), shell.resolveProgram('shell'), table.unpack(termInfo.program))
|
||||||
if socket.queue then
|
if socket.queue then
|
||||||
socket:write(socket.queue)
|
socket:write(socket.queue)
|
||||||
end
|
end
|
||||||
socket:close()
|
socket:close()
|
||||||
end)
|
end,
|
||||||
})
|
})
|
||||||
|
|
||||||
Event.addRoutine(function()
|
Event.addRoutine(function()
|
||||||
|
|||||||
@@ -6,6 +6,22 @@ local Util = require('opus.util')
|
|||||||
|
|
||||||
local trustId = '01c3ba27fe01383a03a1785276d99df27c3edcef68fbf231ca'
|
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 function trustConnection(socket)
|
||||||
local data = socket:read(2)
|
local data = socket:read(2)
|
||||||
if data then
|
if data then
|
||||||
@@ -13,17 +29,22 @@ local function trustConnection(socket)
|
|||||||
if not password then
|
if not password then
|
||||||
socket:write({ msg = 'No password has been set' })
|
socket:write({ msg = 'No password has been set' })
|
||||||
else
|
else
|
||||||
local s
|
if validateData(data, password, socket.dhost) then
|
||||||
s, data = pcall(Crypto.decrypt, data, password)
|
print("Accepted trust from " .. socket.dhost)
|
||||||
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' })
|
socket:write({ success = true, msg = 'Trust accepted' })
|
||||||
else
|
return
|
||||||
socket:write({ msg = 'Invalid password' })
|
|
||||||
end
|
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
|
end
|
||||||
end
|
end
|
||||||
@@ -44,3 +65,12 @@ Event.addRoutine(function()
|
|||||||
end
|
end
|
||||||
end
|
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)
|
||||||
@@ -1,6 +1,9 @@
|
|||||||
local BulkGet = require('opus.bulkget')
|
local BulkGet = require('opus.bulkget')
|
||||||
|
local Config = require('opus.config')
|
||||||
local Git = require('opus.git')
|
local Git = require('opus.git')
|
||||||
|
local LZW = require('opus.compress.lzw')
|
||||||
local Packages = require('opus.packages')
|
local Packages = require('opus.packages')
|
||||||
|
local Tar = require('opus.compress.tar')
|
||||||
local Util = require('opus.util')
|
local Util = require('opus.util')
|
||||||
|
|
||||||
local fs = _G.fs
|
local fs = _G.fs
|
||||||
@@ -16,9 +19,8 @@ local function makeSandbox()
|
|||||||
end
|
end
|
||||||
|
|
||||||
local function Syntax(msg)
|
local function Syntax(msg)
|
||||||
_G.printError(msg)
|
print('Syntax: package list | install [name] ... | update [name] | updateall | uninstall [name]\n')
|
||||||
print('\nSyntax: Package list | install [name] ... | update [name] | uninstall [name]')
|
error(msg)
|
||||||
error(0)
|
|
||||||
end
|
end
|
||||||
|
|
||||||
local function progress(max)
|
local function progress(max)
|
||||||
@@ -76,6 +78,11 @@ local function install(name, isUpdate, ignoreDeps)
|
|||||||
local packageDir = fs.combine('packages', name)
|
local packageDir = fs.combine('packages', name)
|
||||||
|
|
||||||
local list = Git.list(manifest.repository)
|
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 showProgress = progress(Util.size(list))
|
||||||
|
|
||||||
local getList = { }
|
local getList = { }
|
||||||
@@ -96,6 +103,12 @@ local function install(name, isUpdate, ignoreDeps)
|
|||||||
if not isUpdate then
|
if not isUpdate then
|
||||||
runScript(manifest.install)
|
runScript(manifest.install)
|
||||||
end
|
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
|
end
|
||||||
|
|
||||||
if action == 'list' then
|
if action == 'list' then
|
||||||
@@ -152,6 +165,7 @@ if action == 'uninstall' then
|
|||||||
|
|
||||||
local packageDir = fs.combine('packages', name)
|
local packageDir = fs.combine('packages', name)
|
||||||
fs.delete(packageDir)
|
fs.delete(packageDir)
|
||||||
|
fs.delete(packageDir .. '.tar.lzw')
|
||||||
print('removed: ' .. packageDir)
|
print('removed: ' .. packageDir)
|
||||||
return
|
return
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -1,21 +1,12 @@
|
|||||||
local parentShell = _ENV.shell
|
local parentShell = _ENV.shell
|
||||||
|
|
||||||
_ENV.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 trace = require('opus.trace')
|
||||||
local Util = require('opus.util')
|
local Util = require('opus.util')
|
||||||
|
|
||||||
|
local fs = _G.fs
|
||||||
|
local settings = _G.settings
|
||||||
|
local shell = _ENV.shell
|
||||||
|
|
||||||
local DIR = (parentShell and parentShell.dir()) or ""
|
local DIR = (parentShell and parentShell.dir()) or ""
|
||||||
local PATH = (parentShell and parentShell.path()) or ".:/rom/programs"
|
local PATH = (parentShell and parentShell.path()) or ".:/rom/programs"
|
||||||
@@ -25,16 +16,16 @@ local tCompletionInfo = (parentShell and parentShell.getCompletionInfo()) or {}
|
|||||||
local bExit = false
|
local bExit = false
|
||||||
local tProgramStack = {}
|
local tProgramStack = {}
|
||||||
|
|
||||||
local function tokenise( ... )
|
local function tokenise(...)
|
||||||
local sLine = table.concat( { ... }, " " )
|
local sLine = table.concat({ ... }, ' ')
|
||||||
local tWords = {}
|
local tWords = { }
|
||||||
local bQuoted = false
|
local bQuoted = false
|
||||||
for match in string.gmatch( sLine .. "\"", "(.-)\"" ) do
|
for match in string.gmatch(sLine .. "\"", "(.-)\"") do
|
||||||
if bQuoted then
|
if bQuoted then
|
||||||
table.insert( tWords, match )
|
table.insert(tWords, match)
|
||||||
else
|
else
|
||||||
for m in string.gmatch( match, "[^ \t]+" ) do
|
for m in string.gmatch(match, "[^ \t]+") do
|
||||||
table.insert( tWords, m )
|
table.insert(tWords, m)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
bQuoted = not bQuoted
|
bQuoted = not bQuoted
|
||||||
@@ -43,37 +34,75 @@ local function tokenise( ... )
|
|||||||
return tWords
|
return tWords
|
||||||
end
|
end
|
||||||
|
|
||||||
local function run(env, ...)
|
local defaultHandlers = {
|
||||||
local args = tokenise(...)
|
function(env, command, args)
|
||||||
local command = table.remove(args, 1) or error('No such program')
|
return command:match("^(https?:)") and {
|
||||||
local isUrl = not not command:match("^(https?:)")
|
title = fs.getName(command),
|
||||||
|
path = command,
|
||||||
|
args = args,
|
||||||
|
load = Util.loadUrl,
|
||||||
|
env = env,
|
||||||
|
}
|
||||||
|
end,
|
||||||
|
|
||||||
local path, loadFn
|
function(env, command, args)
|
||||||
if isUrl then
|
command = env.shell.resolveProgram(command)
|
||||||
path = command
|
or error('No such program')
|
||||||
loadFn = Util.loadUrl
|
|
||||||
else
|
_G.requireInjector(env, fs.getDir(command))
|
||||||
path = shell.resolveProgram(command) or error('No such program')
|
return {
|
||||||
loadFn = loadfile
|
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 args = tokenise(...)
|
||||||
|
if #args == 0 then
|
||||||
|
error('No such program')
|
||||||
end
|
end
|
||||||
|
|
||||||
local fn, err = loadFn(path, env)
|
local pi = handleCommand(shell.makeEnv(_ENV), table.remove(args, 1), args)
|
||||||
if not fn then
|
|
||||||
error(err)
|
local O_v_O, err = pi.load(pi.path, pi.env)
|
||||||
|
if not O_v_O then
|
||||||
|
error(err, -1)
|
||||||
end
|
end
|
||||||
|
|
||||||
if _ENV.multishell then
|
if _ENV.multishell then
|
||||||
_ENV.multishell.setTitle(_ENV.multishell.getCurrent(), fs.getName(path):match('([^%.]+)'))
|
_ENV.multishell.setTitle(_ENV.multishell.getCurrent(), pi.title)
|
||||||
end
|
end
|
||||||
|
|
||||||
if isUrl then
|
tProgramStack[#tProgramStack + 1] = pi
|
||||||
tProgramStack[#tProgramStack + 1] = path -- path:match("^https?://([^/:]+:?[0-9]*/?.*)$")
|
|
||||||
else
|
|
||||||
tProgramStack[#tProgramStack + 1] = path
|
|
||||||
end
|
|
||||||
|
|
||||||
env[ "arg" ] = { [0] = path, table.unpack(args) }
|
pi.env[ "arg" ] = { [0] = pi.path, table.unpack(pi.args) }
|
||||||
local r = { fn(table.unpack(args)) }
|
local r = { O_v_O(table.unpack(pi.args)) }
|
||||||
|
|
||||||
tProgramStack[#tProgramStack] = nil
|
tProgramStack[#tProgramStack] = nil
|
||||||
|
|
||||||
@@ -88,10 +117,7 @@ function shell.run(...)
|
|||||||
oldTitle = _ENV.multishell.getTitle(_ENV.multishell.getCurrent())
|
oldTitle = _ENV.multishell.getTitle(_ENV.multishell.getCurrent())
|
||||||
end
|
end
|
||||||
|
|
||||||
local env = setmetatable(Util.shallowCopy(sandboxEnv), { __index = _G })
|
local r = { trace(run, ...) }
|
||||||
_G.requireInjector(env)
|
|
||||||
|
|
||||||
local r = { trace(run, env, ...) }
|
|
||||||
|
|
||||||
if _ENV.multishell then
|
if _ENV.multishell then
|
||||||
_ENV.multishell.setTitle(_ENV.multishell.getCurrent(), oldTitle or 'shell')
|
_ENV.multishell.setTitle(_ENV.multishell.getCurrent(), oldTitle or 'shell')
|
||||||
@@ -105,7 +131,14 @@ function shell.exit()
|
|||||||
end
|
end
|
||||||
|
|
||||||
function shell.dir() return DIR end
|
function shell.dir() return DIR end
|
||||||
function shell.setDir(d) DIR = d 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.path() return PATH end
|
function shell.path() return PATH end
|
||||||
function shell.setPath(p) PATH = p end
|
function shell.setPath(p) PATH = p end
|
||||||
|
|
||||||
@@ -118,49 +151,41 @@ function shell.resolve( _sPath )
|
|||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
function shell.resolveProgram( _sCommand )
|
function shell.resolveProgram(_sCommand)
|
||||||
if tAliases[_sCommand] ~= nil then
|
if tAliases[_sCommand] ~= nil then
|
||||||
_sCommand = tAliases[_sCommand]
|
_sCommand = tAliases[_sCommand]
|
||||||
end
|
end
|
||||||
|
|
||||||
if _sCommand:match("^(https?:)") then
|
local function check(f)
|
||||||
return _sCommand
|
return fs.exists(f) and not fs.isDir(f) and f
|
||||||
end
|
end
|
||||||
|
|
||||||
local path = shell.resolve(_sCommand)
|
local function inPath()
|
||||||
if fs.exists(path) and not fs.isDir(path) then
|
-- Otherwise, look on the path variable
|
||||||
return path
|
for sPath in string.gmatch(PATH or '', "[^:]+") do
|
||||||
end
|
sPath = fs.combine(sPath, _sCommand )
|
||||||
if fs.exists(path .. '.lua') then
|
if check(sPath) then
|
||||||
return path .. '.lua'
|
return sPath
|
||||||
|
end
|
||||||
|
if check(sPath .. '.lua') then
|
||||||
|
return sPath .. '.lua'
|
||||||
|
end
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
-- If the path is a global path, use it directly
|
-- so... even if you are in the rom directory and you run:
|
||||||
local sStartChar = string.sub( _sCommand, 1, 1 )
|
-- 'packages/common/edit.lua', allow this even though it
|
||||||
if sStartChar == "/" or sStartChar == "\\" then
|
-- does not use a leading slash. Ideally, fs.combine would
|
||||||
local sPath = fs.combine( "", _sCommand )
|
-- provide the leading slash... but it does not.
|
||||||
if fs.exists( sPath ) and not fs.isDir( sPath ) then
|
return (not _sCommand:find('/')) and inPath()
|
||||||
return sPath
|
or check(shell.resolve(_sCommand))
|
||||||
end
|
or check(shell.resolve(_sCommand) .. '.lua')
|
||||||
return nil
|
or check(_sCommand)
|
||||||
end
|
or check(_sCommand .. '.lua')
|
||||||
|
|
||||||
-- 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
|
|
||||||
-- Not found
|
|
||||||
return nil
|
|
||||||
end
|
end
|
||||||
|
|
||||||
function shell.programs( _bIncludeHidden )
|
function shell.programs(_bIncludeHidden)
|
||||||
local tItems = {}
|
local tItems = { }
|
||||||
|
|
||||||
-- Add programs from the path
|
-- Add programs from the path
|
||||||
for sPath in string.gmatch(PATH, "[^:]+") do
|
for sPath in string.gmatch(PATH, "[^:]+") do
|
||||||
@@ -177,96 +202,82 @@ function shell.programs( _bIncludeHidden )
|
|||||||
end
|
end
|
||||||
|
|
||||||
-- Sort and return
|
-- Sort and return
|
||||||
local tItemList = {}
|
local tItemList = { }
|
||||||
for sItem in pairs( tItems ) do
|
for sItem in pairs(tItems) do
|
||||||
table.insert( tItemList, sItem )
|
table.insert(tItemList, sItem)
|
||||||
end
|
end
|
||||||
table.sort( tItemList )
|
table.sort(tItemList)
|
||||||
return tItemList
|
return tItemList
|
||||||
end
|
end
|
||||||
|
|
||||||
local function completeProgram( sLine )
|
function shell.completeProgram(sLine)
|
||||||
if #sLine > 0 and string.sub( sLine, 1, 1 ) == "/" then
|
if #sLine > 0 and string.sub(sLine, 1, 1) == '/' then
|
||||||
-- Add programs from the root
|
-- Add programs from the root
|
||||||
return fs.complete( sLine, "", true, false )
|
return fs.complete(sLine, '', true, false)
|
||||||
else
|
end
|
||||||
local tResults = {}
|
|
||||||
local tSeen = {}
|
|
||||||
|
|
||||||
-- Add aliases
|
local tResults = { }
|
||||||
for sAlias in pairs( tAliases ) do
|
local tSeen = { }
|
||||||
if #sAlias > #sLine and string.sub( sAlias, 1, #sLine ) == sLine then
|
|
||||||
local sResult = string.sub( sAlias, #sLine + 1 )
|
-- Add aliases
|
||||||
if not tSeen[ sResult ] then
|
for sAlias in pairs( tAliases ) do
|
||||||
table.insert( tResults, sResult )
|
if #sAlias > #sLine and string.sub(sAlias, 1, #sLine) == sLine then
|
||||||
tSeen[ sResult ] = true
|
local sResult = string.sub(sAlias, #sLine + 1)
|
||||||
end
|
if not tSeen[sResult] then
|
||||||
|
table.insert(tResults, sResult .. ' ')
|
||||||
|
tSeen[sResult] = true
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
end
|
||||||
|
|
||||||
-- Add programs from the path
|
-- Add programs from the path
|
||||||
local tPrograms = shell.programs()
|
local tPrograms = shell.programs()
|
||||||
for n=1,#tPrograms do
|
for n=1,#tPrograms do
|
||||||
local sProgram = tPrograms[n]
|
local sProgram = tPrograms[n]
|
||||||
if #sProgram > #sLine and string.sub( sProgram, 1, #sLine ) == sLine then
|
if #sProgram >= #sLine and string.sub(sProgram, 1, #sLine) == sLine then
|
||||||
local sResult = string.sub( sProgram, #sLine + 1 )
|
local sResult = string.sub(sProgram, #sLine + 1)
|
||||||
if not tSeen[ sResult ] then
|
if not tSeen[sResult] then
|
||||||
table.insert( tResults, sResult )
|
table.insert(tResults, sResult .. ' ')
|
||||||
tSeen[ sResult ] = true
|
tSeen[sResult] = true
|
||||||
end
|
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
-- Sort and return
|
|
||||||
table.sort( tResults )
|
|
||||||
return tResults
|
|
||||||
end
|
end
|
||||||
end
|
|
||||||
|
|
||||||
local function completeProgramArgument( sProgram, nArgument, sPart, tPreviousParts )
|
-- Sort and return
|
||||||
local tInfo = tCompletionInfo[ sProgram ]
|
table.sort(tResults)
|
||||||
if tInfo then
|
return tResults
|
||||||
return tInfo.fnComplete( shell, nArgument, sPart, tPreviousParts )
|
|
||||||
end
|
|
||||||
return nil
|
|
||||||
end
|
end
|
||||||
|
|
||||||
function shell.complete(sLine)
|
function shell.complete(sLine)
|
||||||
if #sLine > 0 then
|
local tWords = tokenise(sLine)
|
||||||
local tWords = tokenise( sLine )
|
local nIndex = #tWords
|
||||||
local nIndex = #tWords
|
if string.sub(sLine, #sLine, #sLine) == ' ' and #Util.trim(sLine) > 0 then
|
||||||
if string.sub( sLine, #sLine, #sLine ) == " " then
|
nIndex = nIndex + 1
|
||||||
nIndex = nIndex + 1
|
|
||||||
end
|
|
||||||
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
|
|
||||||
|
|
||||||
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
|
|
||||||
end
|
end
|
||||||
end
|
|
||||||
|
|
||||||
function shell.completeProgram( sProgram )
|
if nIndex == 0 then
|
||||||
return completeProgram( sProgram )
|
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)
|
||||||
|
end
|
||||||
|
return results
|
||||||
|
|
||||||
|
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)
|
||||||
|
end
|
||||||
|
return results and #results > 0 and results
|
||||||
|
or fs.complete(sPart, shell.dir(), true, false)
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
function shell.setCompletionFunction(sProgram, fnComplete)
|
function shell.setCompletionFunction(sProgram, fnComplete)
|
||||||
@@ -278,23 +289,25 @@ function shell.getCompletionInfo()
|
|||||||
end
|
end
|
||||||
|
|
||||||
function shell.getRunningProgram()
|
function shell.getRunningProgram()
|
||||||
|
return tProgramStack[#tProgramStack] and tProgramStack[#tProgramStack].path
|
||||||
|
end
|
||||||
|
|
||||||
|
function shell.getRunningInfo()
|
||||||
return tProgramStack[#tProgramStack]
|
return tProgramStack[#tProgramStack]
|
||||||
end
|
end
|
||||||
|
|
||||||
function shell.setEnv(name, value)
|
-- convenience function for making a runnable env
|
||||||
_ENV[name] = value
|
function shell.makeEnv(env, dir)
|
||||||
sandboxEnv[name] = value
|
env = setmetatable(Util.shallowCopy(env), { __index = _G })
|
||||||
|
_G.requireInjector(env, dir)
|
||||||
|
return env
|
||||||
end
|
end
|
||||||
|
|
||||||
function shell.getEnv()
|
function shell.setAlias(_sCommand, _sProgram)
|
||||||
return sandboxEnv
|
|
||||||
end
|
|
||||||
|
|
||||||
function shell.setAlias( _sCommand, _sProgram )
|
|
||||||
tAliases[_sCommand] = _sProgram
|
tAliases[_sCommand] = _sProgram
|
||||||
end
|
end
|
||||||
|
|
||||||
function shell.clearAlias( _sCommand )
|
function shell.clearAlias(_sCommand)
|
||||||
tAliases[_sCommand] = nil
|
tAliases[_sCommand] = nil
|
||||||
end
|
end
|
||||||
|
|
||||||
@@ -313,7 +326,6 @@ function shell.newTab(tabInfo, ...)
|
|||||||
|
|
||||||
if path then
|
if path then
|
||||||
tabInfo.path = path
|
tabInfo.path = path
|
||||||
tabInfo.env = Util.shallowCopy(sandboxEnv)
|
|
||||||
tabInfo.args = args
|
tabInfo.args = args
|
||||||
tabInfo.title = fs.getName(path):match('([^%.]+)')
|
tabInfo.title = fs.getName(path):match('([^%.]+)')
|
||||||
|
|
||||||
@@ -321,25 +333,21 @@ function shell.newTab(tabInfo, ...)
|
|||||||
table.insert(tabInfo.args, 1, tabInfo.path)
|
table.insert(tabInfo.args, 1, tabInfo.path)
|
||||||
tabInfo.path = 'sys/apps/shell.lua'
|
tabInfo.path = 'sys/apps/shell.lua'
|
||||||
end
|
end
|
||||||
return _ENV.multishell.openTab(tabInfo)
|
return _ENV.multishell.openTab(_ENV, tabInfo)
|
||||||
end
|
end
|
||||||
return nil, 'No such program'
|
return nil, 'No such program'
|
||||||
end
|
end
|
||||||
|
|
||||||
function shell.openTab( ... )
|
if not _ENV.multishell then
|
||||||
-- needs to use multishell.launch .. so we can run with stock multishell
|
function shell.newTab()
|
||||||
local tWords = tokenise( ... )
|
error('Multishell is not available')
|
||||||
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
|
||||||
end
|
end
|
||||||
|
|
||||||
|
function shell.openTab(...)
|
||||||
|
return shell.newTab({ }, ...)
|
||||||
|
end
|
||||||
|
|
||||||
function shell.openForegroundTab( ... )
|
function shell.openForegroundTab( ... )
|
||||||
return shell.newTab({ focused = true }, ...)
|
return shell.newTab({ focused = true }, ...)
|
||||||
end
|
end
|
||||||
@@ -354,10 +362,7 @@ end
|
|||||||
|
|
||||||
local tArgs = { ... }
|
local tArgs = { ... }
|
||||||
if #tArgs > 0 then
|
if #tArgs > 0 then
|
||||||
local env = setmetatable(Util.shallowCopy(sandboxEnv), { __index = _G })
|
return run(...)
|
||||||
_G.requireInjector(env)
|
|
||||||
|
|
||||||
return run(env, ...)
|
|
||||||
end
|
end
|
||||||
|
|
||||||
local Config = require('opus.config')
|
local Config = require('opus.config')
|
||||||
@@ -374,23 +379,16 @@ local textutils = _G.textutils
|
|||||||
|
|
||||||
local oldTerm
|
local oldTerm
|
||||||
local terminal = term.current()
|
local terminal = term.current()
|
||||||
|
local _len = string.len
|
||||||
local _rep = string.rep
|
local _rep = string.rep
|
||||||
local _sub = string.sub
|
local _sub = string.sub
|
||||||
|
|
||||||
if not terminal.scrollUp then
|
|
||||||
terminal = Terminal.window(term.current())
|
|
||||||
terminal.setMaxScroll(200)
|
|
||||||
oldTerm = term.redirect(terminal)
|
|
||||||
end
|
|
||||||
|
|
||||||
local config = {
|
local config = {
|
||||||
color = {
|
color = {
|
||||||
textColor = colors.white,
|
textColor = colors.white,
|
||||||
commandTextColor = colors.yellow,
|
commandTextColor = colors.yellow,
|
||||||
directoryTextColor = colors.orange,
|
directoryTextColor = colors.orange,
|
||||||
directoryBackgroundColor = colors.black,
|
|
||||||
promptTextColor = colors.blue,
|
promptTextColor = colors.blue,
|
||||||
promptBackgroundColor = colors.black,
|
|
||||||
directoryColor = colors.green,
|
directoryColor = colors.green,
|
||||||
fileColor = colors.white,
|
fileColor = colors.white,
|
||||||
backgroundColor = colors.black,
|
backgroundColor = colors.black,
|
||||||
@@ -407,43 +405,15 @@ if not _colors.backgroundColor then
|
|||||||
_colors.fileColor = colors.white
|
_colors.fileColor = colors.white
|
||||||
end
|
end
|
||||||
|
|
||||||
local palette = { }
|
if not terminal.scrollUp then
|
||||||
for n = 1, 16 do
|
terminal = Terminal.window(term.current())
|
||||||
palette[2 ^ (n - 1)] = _sub("0123456789abcdef", n, n)
|
terminal.setMaxScroll(200)
|
||||||
|
oldTerm = term.redirect(terminal)
|
||||||
|
term.setBackgroundColor(_colors.backgroundColor)
|
||||||
|
term.clear()
|
||||||
end
|
end
|
||||||
|
|
||||||
if not term.isColor() then
|
local palette = terminal.canvas.palette
|
||||||
_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 function autocomplete(line)
|
||||||
local words = { }
|
local words = { }
|
||||||
@@ -457,14 +427,7 @@ local function autocomplete(line)
|
|||||||
words = { '' }
|
words = { '' }
|
||||||
end
|
end
|
||||||
|
|
||||||
local results
|
local results = shell.complete(line) or { }
|
||||||
|
|
||||||
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)
|
Util.filterInplace(results, function(f)
|
||||||
return not Util.key(results, f .. '/')
|
return not Util.key(results, f .. '/')
|
||||||
@@ -477,8 +440,8 @@ local function autocomplete(line)
|
|||||||
if #results == 1 then
|
if #results == 1 then
|
||||||
words[#words] = results[1]
|
words[#words] = results[1]
|
||||||
return table.concat(words, ' ')
|
return table.concat(words, ' ')
|
||||||
elseif #results > 1 then
|
|
||||||
|
|
||||||
|
elseif #results > 1 then
|
||||||
local function someComplete()
|
local function someComplete()
|
||||||
-- ugly (complete as much as possible)
|
-- ugly (complete as much as possible)
|
||||||
local word = words[#words] or ''
|
local word = words[#words] or ''
|
||||||
@@ -487,16 +450,16 @@ local function autocomplete(line)
|
|||||||
local ch
|
local ch
|
||||||
for _,f in ipairs(results) do
|
for _,f in ipairs(results) do
|
||||||
if #f < i then
|
if #f < i then
|
||||||
words[#words] = string.sub(f, 1, i - 1)
|
words[#words] = _sub(f, 1, i - 1)
|
||||||
return table.concat(words, ' ')
|
return table.concat(words, ' ')
|
||||||
end
|
end
|
||||||
if not ch then
|
if not ch then
|
||||||
ch = string.sub(f, i, i)
|
ch = _sub(f, i, i)
|
||||||
elseif string.sub(f, i, i) ~= ch then
|
elseif _sub(f, i, i) ~= ch then
|
||||||
if i == #word + 1 then
|
if i == #word + 1 then
|
||||||
return
|
return
|
||||||
end
|
end
|
||||||
words[#words] = string.sub(f, 1, i - 1)
|
words[#words] = _sub(f, 1, i - 1)
|
||||||
return table.concat(words, ' ')
|
return table.concat(words, ' ')
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
@@ -539,10 +502,9 @@ local function autocomplete(line)
|
|||||||
local tw = term.getSize()
|
local tw = term.getSize()
|
||||||
local nMaxLen = tw / 8
|
local nMaxLen = tw / 8
|
||||||
for _,sItem in pairs(results) do
|
for _,sItem in pairs(results) do
|
||||||
nMaxLen = math.max(string.len(sItem) + 1, nMaxLen)
|
nMaxLen = math.max(_len(sItem) + 1, nMaxLen)
|
||||||
end
|
end
|
||||||
local w = term.getSize()
|
local nCols = math.floor(tw / nMaxLen)
|
||||||
local nCols = math.floor(w / nMaxLen)
|
|
||||||
if #tDirs < nCols then
|
if #tDirs < nCols then
|
||||||
for _ = #tDirs + 1, nCols do
|
for _ = #tDirs + 1, nCols do
|
||||||
table.insert(tDirs, '')
|
table.insert(tDirs, '')
|
||||||
@@ -557,11 +519,9 @@ local function autocomplete(line)
|
|||||||
end
|
end
|
||||||
|
|
||||||
term.setTextColour(_colors.promptTextColor)
|
term.setTextColour(_colors.promptTextColor)
|
||||||
term.setBackgroundColor(_colors.promptBackgroundColor)
|
|
||||||
term.write("$ " )
|
term.write("$ " )
|
||||||
|
|
||||||
term.setTextColour(_colors.commandTextColor)
|
term.setTextColour(_colors.commandTextColor)
|
||||||
term.setBackgroundColor(_colors.backgroundColor)
|
|
||||||
return line
|
return line
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
@@ -588,17 +548,16 @@ local function shellRead(history)
|
|||||||
term.setCursorPos(3, cy)
|
term.setCursorPos(3, cy)
|
||||||
entry.value = entry.value or ''
|
entry.value = entry.value or ''
|
||||||
local filler = #entry.value < lastLen
|
local filler = #entry.value < lastLen
|
||||||
and string.rep(' ', lastLen - #entry.value)
|
and _rep(' ', lastLen - #entry.value)
|
||||||
or ''
|
or ''
|
||||||
local str = string.sub(entry.value, entry.scroll + 1, entry.width + entry.scroll) .. filler
|
local str = _sub(entry.value, entry.scroll + 1, entry.width + entry.scroll) .. filler
|
||||||
local fg = _rep(palette[_colors.commandTextColor], #str)
|
local fg = _rep(palette[_colors.commandTextColor], #str)
|
||||||
local bg = _rep(palette[_colors.backgroundColor], #str)
|
local bg = _rep(palette[_colors.backgroundColor], #str)
|
||||||
if entry.mark.active then
|
if entry.mark.active then
|
||||||
local sx = entry.mark.x - entry.scroll + 1
|
bg = _rep('f', entry.mark.x) ..
|
||||||
local ex = entry.mark.ex - entry.scroll + 1
|
_rep('7', entry.mark.ex - entry.mark.x) ..
|
||||||
bg = string.rep('f', sx - 1) ..
|
_rep('f', #entry.value - entry.mark.ex + #filler + 1)
|
||||||
string.rep('7', ex - sx) ..
|
bg = _sub(bg, entry.scroll + 1, entry.scroll + #str)
|
||||||
string.rep('f', #str - ex + 1)
|
|
||||||
end
|
end
|
||||||
term.blit(str, fg, bg)
|
term.blit(str, fg, bg)
|
||||||
updateCursor()
|
updateCursor()
|
||||||
@@ -650,6 +609,11 @@ local function shellRead(history)
|
|||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
elseif ie.code == 'control-l' then
|
||||||
|
term.clear()
|
||||||
|
term.setCursorPos(1, 0) -- Y:0 ?
|
||||||
|
break
|
||||||
|
|
||||||
else
|
else
|
||||||
entry:process(ie)
|
entry:process(ie)
|
||||||
entry.value = entry.value or ''
|
entry.value = entry.value or ''
|
||||||
@@ -661,6 +625,7 @@ local function shellRead(history)
|
|||||||
end
|
end
|
||||||
|
|
||||||
elseif event == "term_resize" then
|
elseif event == "term_resize" then
|
||||||
|
terminal.reposition(1, 1, oldTerm.getSize())
|
||||||
entry.width = term.getSize() - 3
|
entry.width = term.getSize() - 3
|
||||||
entry:updateScroll()
|
entry:updateScroll()
|
||||||
redraw()
|
redraw()
|
||||||
@@ -668,30 +633,26 @@ local function shellRead(history)
|
|||||||
end
|
end
|
||||||
|
|
||||||
print()
|
print()
|
||||||
term.setCursorBlink( false )
|
term.setCursorBlink(false)
|
||||||
return entry.value or ''
|
return entry.value or ''
|
||||||
end
|
end
|
||||||
|
|
||||||
local history = History.load('usr/.shell_history', 25)
|
local history = History.load('usr/.shell_history', 100)
|
||||||
|
|
||||||
term.setBackgroundColor(_colors.backgroundColor)
|
term.setBackgroundColor(_colors.backgroundColor)
|
||||||
term.clear()
|
|
||||||
|
|
||||||
if settings.get("motd.enabled") then
|
if settings.get("motd.enable") then
|
||||||
shell.run("motd")
|
shell.run("motd")
|
||||||
end
|
end
|
||||||
|
|
||||||
while not bExit do
|
while not bExit do
|
||||||
if config.displayDirectory then
|
if config.displayDirectory then
|
||||||
term.setTextColour(_colors.directoryTextColor)
|
term.setTextColour(_colors.directoryTextColor)
|
||||||
term.setBackgroundColor(_colors.directoryBackgroundColor)
|
|
||||||
print('==' .. os.getComputerLabel() .. ':/' .. DIR)
|
print('==' .. os.getComputerLabel() .. ':/' .. DIR)
|
||||||
end
|
end
|
||||||
term.setTextColour(_colors.promptTextColor)
|
term.setTextColour(_colors.promptTextColor)
|
||||||
term.setBackgroundColor(_colors.promptBackgroundColor)
|
|
||||||
term.write("$ " )
|
term.write("$ " )
|
||||||
term.setTextColour(_colors.commandTextColor)
|
term.setTextColour(_colors.commandTextColor)
|
||||||
term.setBackgroundColor(_colors.backgroundColor)
|
|
||||||
local sLine = shellRead(history)
|
local sLine = shellRead(history)
|
||||||
if bExit then -- terminated
|
if bExit then -- terminated
|
||||||
break
|
break
|
||||||
@@ -703,6 +664,11 @@ while not bExit do
|
|||||||
term.setTextColour(_colors.textColor)
|
term.setTextColour(_colors.textColor)
|
||||||
if #sLine > 0 then
|
if #sLine > 0 then
|
||||||
local result, err = shell.run(sLine)
|
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
|
if not result and err then
|
||||||
_G.printError(err)
|
_G.printError(err)
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ local UI = require('opus.ui')
|
|||||||
local kernel = _G.kernel
|
local kernel = _G.kernel
|
||||||
|
|
||||||
local aliasTab = UI.Tab {
|
local aliasTab = UI.Tab {
|
||||||
tabTitle = 'Aliases',
|
title = 'Aliases',
|
||||||
description = 'Shell aliases',
|
description = 'Shell aliases',
|
||||||
alias = UI.TextEntry {
|
alias = UI.TextEntry {
|
||||||
x = 2, y = 2, ex = -2,
|
x = 2, y = 2, ex = -2,
|
||||||
@@ -13,14 +13,13 @@ local aliasTab = UI.Tab {
|
|||||||
},
|
},
|
||||||
path = UI.TextEntry {
|
path = UI.TextEntry {
|
||||||
y = 3, x = 2, ex = -2,
|
y = 3, x = 2, ex = -2,
|
||||||
limit = 256,
|
|
||||||
shadowText = 'Program path',
|
shadowText = 'Program path',
|
||||||
accelerators = {
|
accelerators = {
|
||||||
enter = 'new_alias',
|
enter = 'new_alias',
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
grid = UI.Grid {
|
grid = UI.Grid {
|
||||||
y = 5,
|
x = 2, y = 5, ex = -2, ey = -2,
|
||||||
sortColumn = 'alias',
|
sortColumn = 'alias',
|
||||||
columns = {
|
columns = {
|
||||||
{ heading = 'Alias', key = 'alias' },
|
{ heading = 'Alias', key = 'alias' },
|
||||||
|
|||||||
@@ -1,80 +0,0 @@
|
|||||||
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
|
|
||||||
@@ -2,18 +2,17 @@ local Ansi = require('opus.ansi')
|
|||||||
local Config = require('opus.config')
|
local Config = require('opus.config')
|
||||||
local UI = require('opus.ui')
|
local UI = require('opus.ui')
|
||||||
|
|
||||||
local colors = _G.colors
|
|
||||||
|
|
||||||
-- -t80x30
|
|
||||||
|
|
||||||
if _G.http.websocket then
|
if _G.http.websocket then
|
||||||
local config = Config.load('cloud')
|
local config = Config.load('cloud')
|
||||||
|
|
||||||
local tab = UI.Tab {
|
local tab = UI.Tab {
|
||||||
tabTitle = 'Cloud',
|
title = 'Cloud',
|
||||||
description = 'Cloud Catcher options',
|
description = 'Cloud Catcher options',
|
||||||
|
[1] = UI.Window {
|
||||||
|
x = 2, y = 2, ex = -2, ey = 4,
|
||||||
|
},
|
||||||
key = UI.TextEntry {
|
key = UI.TextEntry {
|
||||||
x = 3, ex = -3, y = 2,
|
x = 3, ex = -3, y = 3,
|
||||||
limit = 32,
|
limit = 32,
|
||||||
value = config.key,
|
value = config.key,
|
||||||
shadowText = 'Cloud key',
|
shadowText = 'Cloud key',
|
||||||
@@ -22,14 +21,15 @@ if _G.http.websocket then
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
button = UI.Button {
|
button = UI.Button {
|
||||||
x = 3, y = 4,
|
x = -8, ex = -2, y = -2,
|
||||||
text = 'Update',
|
text = 'Apply',
|
||||||
event = 'update_key',
|
event = 'update_key',
|
||||||
},
|
},
|
||||||
labelText = UI.TextArea {
|
labelText = UI.TextArea {
|
||||||
x = 3, ex = -3, y = 6,
|
x = 2, ex = -2, y = 5, ey = -4,
|
||||||
textColor = colors.yellow,
|
textColor = 'yellow',
|
||||||
marginLeft = 0, marginRight = 0,
|
backgroundColor = 'black',
|
||||||
|
marginLeft = 1, marginRight = 1, marginTop = 1,
|
||||||
value = string.format(
|
value = string.format(
|
||||||
[[Use a non-changing cloud key. Note that only a single computer can use this session at one time.
|
[[Use a non-changing cloud key. Note that only a single computer can use this session at one time.
|
||||||
To obtain a key, visit:
|
To obtain a key, visit:
|
||||||
|
|||||||
@@ -16,12 +16,12 @@ local NftImages = {
|
|||||||
}
|
}
|
||||||
|
|
||||||
local tab = UI.Tab {
|
local tab = UI.Tab {
|
||||||
tabTitle = 'Disks Usage',
|
title = 'Disks Usage',
|
||||||
description = 'Visualise HDD and disks usage',
|
description = 'Visualise HDD and disks usage',
|
||||||
|
|
||||||
drives = UI.ScrollingGrid {
|
drives = UI.ScrollingGrid {
|
||||||
x = 2, y = 1,
|
x = 2, y = 2,
|
||||||
ex = '47%', ey = -7,
|
ex = '47%', ey = -8,
|
||||||
columns = {
|
columns = {
|
||||||
{ heading = 'Drive', key = 'name' },
|
{ heading = 'Drive', key = 'name' },
|
||||||
{ heading = 'Side' ,key = 'side', textColor = colors.yellow }
|
{ heading = 'Side' ,key = 'side', textColor = colors.yellow }
|
||||||
@@ -30,7 +30,7 @@ local tab = UI.Tab {
|
|||||||
},
|
},
|
||||||
infos = UI.Grid {
|
infos = UI.Grid {
|
||||||
x = '52%', y = 2,
|
x = '52%', y = 2,
|
||||||
ex = -2, ey = -4,
|
ex = -2, ey = -8,
|
||||||
disableHeader = true,
|
disableHeader = true,
|
||||||
unfocusedBackgroundSelectedColor = colors.black,
|
unfocusedBackgroundSelectedColor = colors.black,
|
||||||
inactive = true,
|
inactive = true,
|
||||||
@@ -40,18 +40,23 @@ local tab = UI.Tab {
|
|||||||
{ key = 'value', align = 'right', textColor = colors.yellow },
|
{ key = 'value', align = 'right', textColor = colors.yellow },
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
[1] = UI.Window {
|
||||||
|
x = 2, y = -6, ex = -2, ey = -2,
|
||||||
|
backgroundColor = colors.black,
|
||||||
|
},
|
||||||
progress = UI.ProgressBar {
|
progress = UI.ProgressBar {
|
||||||
x = 11, y = -2,
|
x = 11, y = -3,
|
||||||
ex = -2,
|
ex = -3,
|
||||||
},
|
},
|
||||||
percentage = UI.Text {
|
percentage = UI.Text {
|
||||||
x = 11, y = -3,
|
y = -4, width = 5,
|
||||||
ex = '47%',
|
x = 12,
|
||||||
align = 'center',
|
--align = 'center',
|
||||||
|
backgroundColor = colors.black,
|
||||||
},
|
},
|
||||||
icon = UI.NftImage {
|
icon = UI.NftImage {
|
||||||
x = 2, y = -5,
|
x = 2, y = -6, ey = -2,
|
||||||
|
backgroundColor = colors.black,
|
||||||
image = NFT.parse(NftImages.blank)
|
image = NFT.parse(NftImages.blank)
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
@@ -131,6 +136,17 @@ function tab:enable()
|
|||||||
self:updateDrives()
|
self:updateDrives()
|
||||||
self:updateInfo()
|
self:updateInfo()
|
||||||
UI.Tab.enable(self)
|
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
|
end
|
||||||
|
|
||||||
function tab:eventHandler(event)
|
function tab:eventHandler(event)
|
||||||
@@ -142,11 +158,4 @@ function tab:eventHandler(event)
|
|||||||
return true
|
return true
|
||||||
end
|
end
|
||||||
|
|
||||||
Event.on({ 'disk', 'disk_eject' }, function()
|
|
||||||
os.sleep(1)
|
|
||||||
tab:updateDrives()
|
|
||||||
tab:updateInfo()
|
|
||||||
tab:sync()
|
|
||||||
end)
|
|
||||||
|
|
||||||
return tab
|
return tab
|
||||||
|
|||||||
@@ -4,11 +4,11 @@ local colors = _G.colors
|
|||||||
local peripheral = _G.peripheral
|
local peripheral = _G.peripheral
|
||||||
local settings = _G.settings
|
local settings = _G.settings
|
||||||
|
|
||||||
local tab = UI.Tab {
|
return peripheral.find('monitor') and UI.Tab {
|
||||||
tabTitle = 'Kiosk',
|
title = 'Kiosk',
|
||||||
description = 'Kiosk options',
|
description = 'Kiosk options',
|
||||||
form = UI.Form {
|
form = UI.Form {
|
||||||
x = 2, ex = -2,
|
x = 2, y = 2, ex = -2, ey = 5,
|
||||||
manualControls = true,
|
manualControls = true,
|
||||||
monitor = UI.Chooser {
|
monitor = UI.Chooser {
|
||||||
formLabel = 'Monitor', formKey = 'monitor',
|
formLabel = 'Monitor', formKey = 'monitor',
|
||||||
@@ -22,41 +22,36 @@ local tab = UI.Tab {
|
|||||||
},
|
},
|
||||||
help = 'Adjust text scaling',
|
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 = { }
|
||||||
|
|
||||||
function tab:enable()
|
peripheral.find('monitor', function(side)
|
||||||
local choices = { }
|
table.insert(choices, { name = side, value = side })
|
||||||
|
end)
|
||||||
|
|
||||||
peripheral.find('monitor', function(side)
|
self.form.monitor.choices = choices
|
||||||
table.insert(choices, { name = side, value = side })
|
self.form.monitor.value = settings.get('kiosk.monitor')
|
||||||
end)
|
|
||||||
|
|
||||||
self.form.monitor.choices = choices
|
self.form.textScale.value = settings.get('kiosk.textscale')
|
||||||
self.form.monitor.value = settings.get('kiosk.monitor')
|
|
||||||
|
|
||||||
self.form.textScale.value = settings.get('kiosk.textscale')
|
UI.Tab.enable(self)
|
||||||
|
end,
|
||||||
UI.Tab.enable(self)
|
eventHandler = function(self, event)
|
||||||
end
|
if event.type == 'choice_change' then
|
||||||
|
if self.form.monitor.value then
|
||||||
function tab:eventHandler(event)
|
settings.set('kiosk.monitor', self.form.monitor.value)
|
||||||
if event.type == 'choice_change' then
|
end
|
||||||
if self.form.monitor.value then
|
if self.form.textScale.value then
|
||||||
settings.set('kiosk.monitor', self.form.monitor.value)
|
settings.set('kiosk.textscale', self.form.textScale.value)
|
||||||
|
end
|
||||||
|
settings.save('.settings')
|
||||||
end
|
end
|
||||||
if self.form.textScale.value then
|
|
||||||
settings.set('kiosk.textscale', self.form.textScale.value)
|
|
||||||
end
|
|
||||||
settings.save('.settings')
|
|
||||||
end
|
end
|
||||||
end
|
}
|
||||||
|
|
||||||
if peripheral.find('monitor') then
|
|
||||||
return tab
|
|
||||||
end
|
|
||||||
|
|||||||
@@ -4,23 +4,26 @@ local Util = require('opus.util')
|
|||||||
local fs = _G.fs
|
local fs = _G.fs
|
||||||
local os = _G.os
|
local os = _G.os
|
||||||
|
|
||||||
local labelTab = UI.Tab {
|
return UI.Tab {
|
||||||
tabTitle = 'Label',
|
title = 'Label',
|
||||||
description = 'Set the computer label',
|
description = 'Set the computer label',
|
||||||
labelText = UI.Text {
|
labelText = UI.Text {
|
||||||
x = 3, y = 2,
|
x = 3, y = 3,
|
||||||
value = 'Label'
|
value = 'Label'
|
||||||
},
|
},
|
||||||
label = UI.TextEntry {
|
label = UI.TextEntry {
|
||||||
x = 9, y = 2, ex = -4,
|
x = 9, y = 3, ex = -4,
|
||||||
limit = 32,
|
limit = 32,
|
||||||
value = os.getComputerLabel(),
|
value = os.getComputerLabel(),
|
||||||
accelerators = {
|
accelerators = {
|
||||||
enter = 'update_label',
|
enter = 'update_label',
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
[1] = UI.Window {
|
||||||
|
x = 2, y = 2, ex = -2, ey = 4,
|
||||||
|
},
|
||||||
grid = UI.ScrollingGrid {
|
grid = UI.ScrollingGrid {
|
||||||
y = 3,
|
x = 2, y = 5, ex = -2, ey = -2,
|
||||||
values = {
|
values = {
|
||||||
{ name = '', value = '' },
|
{ name = '', value = '' },
|
||||||
{ name = 'CC version', value = Util.getVersion() },
|
{ name = 'CC version', value = Util.getVersion() },
|
||||||
@@ -30,20 +33,18 @@ local labelTab = UI.Tab {
|
|||||||
{ name = 'Computer ID', value = tostring(os.getComputerID()) },
|
{ name = 'Computer ID', value = tostring(os.getComputerID()) },
|
||||||
{ name = 'Day', value = tostring(os.day()) },
|
{ name = 'Day', value = tostring(os.day()) },
|
||||||
},
|
},
|
||||||
|
disableHeader = true,
|
||||||
inactive = true,
|
inactive = true,
|
||||||
columns = {
|
columns = {
|
||||||
{ key = 'name', width = 12 },
|
{ key = 'name', width = 12 },
|
||||||
{ key = 'value' },
|
{ key = 'value', textColor = colors.yellow },
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
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' and self.label.value then
|
|
||||||
os.setComputerLabel(self.label.value)
|
|
||||||
self:emit({ type = 'success_message', message = 'Label updated' })
|
|
||||||
return true
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
return labelTab
|
|
||||||
|
|||||||
@@ -7,14 +7,17 @@ local fs = _G.fs
|
|||||||
local config = Config.load('multishell')
|
local config = Config.load('multishell')
|
||||||
|
|
||||||
local tab = UI.Tab {
|
local tab = UI.Tab {
|
||||||
tabTitle = 'Launcher',
|
title = 'Launcher',
|
||||||
description = 'Set the application launcher',
|
description = 'Set the application launcher',
|
||||||
|
[1] = UI.Window {
|
||||||
|
x = 2, y = 2, ex = -2, ey = 5,
|
||||||
|
},
|
||||||
launcherLabel = UI.Text {
|
launcherLabel = UI.Text {
|
||||||
x = 3, y = 2,
|
x = 3, y = 3,
|
||||||
value = 'Launcher',
|
value = 'Launcher',
|
||||||
},
|
},
|
||||||
launcher = UI.Chooser {
|
launcher = UI.Chooser {
|
||||||
x = 13, y = 2, width = 12,
|
x = 13, y = 3, width = 12,
|
||||||
choices = {
|
choices = {
|
||||||
{ name = 'Overview', value = 'sys/apps/Overview.lua' },
|
{ name = 'Overview', value = 'sys/apps/Overview.lua' },
|
||||||
{ name = 'Shell', value = 'sys/apps/ShellLauncher.lua' },
|
{ name = 'Shell', value = 'sys/apps/ShellLauncher.lua' },
|
||||||
@@ -22,18 +25,19 @@ local tab = UI.Tab {
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
custom = UI.TextEntry {
|
custom = UI.TextEntry {
|
||||||
x = 13, ex = -3, y = 3,
|
x = 13, ex = -3, y = 4,
|
||||||
limit = 128,
|
|
||||||
shadowText = 'File name',
|
shadowText = 'File name',
|
||||||
},
|
},
|
||||||
button = UI.Button {
|
button = UI.Button {
|
||||||
x = 3, y = 5,
|
x = -8, ex = -2, y = -2,
|
||||||
text = 'Update',
|
text = 'Apply',
|
||||||
event = 'update',
|
event = 'update',
|
||||||
},
|
},
|
||||||
labelText = UI.TextArea {
|
labelText = UI.TextArea {
|
||||||
x = 3, ex = -3, y = 7,
|
x = 2, ex = -2, y = 6, ey = -4,
|
||||||
|
backgroundColor = colors.black,
|
||||||
textColor = colors.yellow,
|
textColor = colors.yellow,
|
||||||
|
marginLeft = 1, marginRight = 1, marginTop = 1,
|
||||||
value = 'Choose an application launcher',
|
value = 'Choose an application launcher',
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,59 +2,61 @@ local Ansi = require('opus.ansi')
|
|||||||
local Config = require('opus.config')
|
local Config = require('opus.config')
|
||||||
local UI = require('opus.ui')
|
local UI = require('opus.ui')
|
||||||
|
|
||||||
|
local colors = _G.colors
|
||||||
local device = _G.device
|
local device = _G.device
|
||||||
|
|
||||||
local tab = UI.Tab {
|
return UI.Tab {
|
||||||
tabTitle = 'Network',
|
title = 'Network',
|
||||||
description = 'Networking options',
|
description = 'Networking options',
|
||||||
info = UI.TextArea {
|
info = UI.TextArea {
|
||||||
x = 3, y = 4,
|
x = 2, y = 5, ex = -2, ey = -2,
|
||||||
|
backgroundColor = colors.black,
|
||||||
|
marginLeft = 1, marginRight = 1, marginTop = 1,
|
||||||
value = string.format(
|
value = string.format(
|
||||||
[[%sSet the primary modem used for wireless communications.%s
|
[[%sSet the primary modem used for wireless communications.%s
|
||||||
|
|
||||||
Reboot to take effect.]], Ansi.yellow, Ansi.reset)
|
Reboot to take effect.]], Ansi.yellow, Ansi.reset)
|
||||||
},
|
},
|
||||||
|
[1] = UI.Window {
|
||||||
|
x = 2, y = 2, ex = -2, ey = 4,
|
||||||
|
},
|
||||||
label = UI.Text {
|
label = UI.Text {
|
||||||
x = 3, y = 2,
|
x = 3, y = 3,
|
||||||
value = 'Modem',
|
value = 'Modem',
|
||||||
},
|
},
|
||||||
modem = UI.Chooser {
|
modem = UI.Chooser {
|
||||||
x = 10, ex = -3, y = 2,
|
x = 10, ex = -3, y = 3,
|
||||||
nochoice = 'auto',
|
nochoice = 'auto',
|
||||||
},
|
},
|
||||||
}
|
enable = function(self)
|
||||||
|
local width = 7
|
||||||
|
local choices = {
|
||||||
|
{ name = 'auto', value = 'auto' },
|
||||||
|
{ name = 'disable', value = 'none' },
|
||||||
|
}
|
||||||
|
|
||||||
function tab:enable()
|
for k,v in pairs(device) do
|
||||||
local width = 7
|
if v.isWireless and v.isWireless() and k ~= 'wireless_modem' then
|
||||||
local choices = {
|
table.insert(choices, { name = k, value = v.name })
|
||||||
{ name = 'auto', value = 'auto' },
|
width = math.max(width, #k)
|
||||||
{ name = 'disable', value = 'none' },
|
end
|
||||||
}
|
end
|
||||||
|
|
||||||
for k,v in pairs(device) do
|
self.modem.choices = choices
|
||||||
if v.isWireless and v.isWireless() and k ~= 'wireless_modem' then
|
--self.modem.width = width + 4
|
||||||
table.insert(choices, { name = k, value = v.name })
|
|
||||||
width = math.max(width, #k)
|
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
|
||||||
end
|
end
|
||||||
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
|
|
||||||
|
|||||||
@@ -2,11 +2,12 @@ local Security = require('opus.security')
|
|||||||
local SHA = require('opus.crypto.sha2')
|
local SHA = require('opus.crypto.sha2')
|
||||||
local UI = require('opus.ui')
|
local UI = require('opus.ui')
|
||||||
|
|
||||||
local colors = _G.colors
|
return UI.Tab {
|
||||||
|
title = 'Password',
|
||||||
local passwordTab = UI.Tab {
|
|
||||||
tabTitle = 'Password',
|
|
||||||
description = 'Wireless network password',
|
description = 'Wireless network password',
|
||||||
|
[1] = UI.Window {
|
||||||
|
x = 2, y = 2, ex = -2, ey = 4,
|
||||||
|
},
|
||||||
newPass = UI.TextEntry {
|
newPass = UI.TextEntry {
|
||||||
x = 3, ex = -3, y = 3,
|
x = 3, ex = -3, y = 3,
|
||||||
limit = 32,
|
limit = 32,
|
||||||
@@ -17,28 +18,28 @@ local passwordTab = UI.Tab {
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
button = UI.Button {
|
button = UI.Button {
|
||||||
x = 3, y = 5,
|
x = -8, ex = -2, y = -2,
|
||||||
text = 'Update',
|
text = 'Apply',
|
||||||
event = 'update_password',
|
event = 'update_password',
|
||||||
},
|
},
|
||||||
info = UI.TextArea {
|
info = UI.TextArea {
|
||||||
x = 3, ex = -3, y = 7,
|
x = 2, ex = -2, y = 5, ey = -4,
|
||||||
textColor = colors.yellow,
|
backgroundColor = 'black',
|
||||||
|
textColor = 'yellow',
|
||||||
inactive = true,
|
inactive = true,
|
||||||
|
marginLeft = 1, marginRight = 1, marginTop = 1,
|
||||||
value = 'Add a password to enable other computers to connect to this one.',
|
value = 'Add a password to enable other computers to connect to this one.',
|
||||||
}
|
},
|
||||||
}
|
eventHandler = function(self, event)
|
||||||
function passwordTab:eventHandler(event)
|
if event.type == 'update_password' then
|
||||||
if event.type == 'update_password' then
|
if not self.newPass.value or #self.newPass.value == 0 then
|
||||||
if not self.newPass.value or #self.newPass.value == 0 then
|
self:emit({ type = 'error_message', message = 'Invalid password' })
|
||||||
self:emit({ type = 'error_message', message = 'Invalid password' })
|
|
||||||
|
|
||||||
else
|
else
|
||||||
Security.updatePassword(SHA.compute(self.newPass.value))
|
Security.updatePassword(SHA.compute(self.newPass.value))
|
||||||
self:emit({ type = 'success_message', message = 'Password updated' })
|
self:emit({ type = 'success_message', message = 'Password updated' })
|
||||||
|
end
|
||||||
|
return true
|
||||||
end
|
end
|
||||||
return true
|
|
||||||
end
|
end
|
||||||
end
|
}
|
||||||
|
|
||||||
return passwordTab
|
|
||||||
|
|||||||
@@ -3,12 +3,14 @@ local UI = require('opus.ui')
|
|||||||
local Util = require('opus.util')
|
local Util = require('opus.util')
|
||||||
|
|
||||||
local tab = UI.Tab {
|
local tab = UI.Tab {
|
||||||
tabTitle = 'Path',
|
title = 'Path',
|
||||||
description = 'Set the shell path',
|
description = 'Set the shell path',
|
||||||
tabClose = true,
|
tabClose = true,
|
||||||
|
[1] = UI.Window {
|
||||||
|
x = 2, y = 2, ex = -2, ey = 4,
|
||||||
|
},
|
||||||
entry = UI.TextEntry {
|
entry = UI.TextEntry {
|
||||||
x = 2, y = 2, ex = -2,
|
x = 3, y = 3, ex = -3,
|
||||||
limit = 256,
|
|
||||||
shadowText = 'enter new path',
|
shadowText = 'enter new path',
|
||||||
accelerators = {
|
accelerators = {
|
||||||
enter = 'update_path',
|
enter = 'update_path',
|
||||||
@@ -16,7 +18,7 @@ local tab = UI.Tab {
|
|||||||
help = 'add a new path',
|
help = 'add a new path',
|
||||||
},
|
},
|
||||||
grid = UI.Grid {
|
grid = UI.Grid {
|
||||||
y = 4, ey = -3,
|
x = 2, y = 6, ex = -2, ey = -3,
|
||||||
disableHeader = true,
|
disableHeader = true,
|
||||||
columns = { { key = 'value' } },
|
columns = { { key = 'value' } },
|
||||||
autospace = true,
|
autospace = true,
|
||||||
|
|||||||
@@ -3,12 +3,11 @@ local UI = require('opus.ui')
|
|||||||
local Util = require('opus.util')
|
local Util = require('opus.util')
|
||||||
|
|
||||||
local tab = UI.Tab {
|
local tab = UI.Tab {
|
||||||
tabTitle = 'Requires',
|
title = 'Requires',
|
||||||
description = 'Require path',
|
description = 'Require path',
|
||||||
tabClose = true,
|
tabClose = true,
|
||||||
entry = UI.TextEntry {
|
entry = UI.TextEntry {
|
||||||
x = 2, y = 2, ex = -2,
|
x = 2, y = 2, ex = -2,
|
||||||
limit = 256,
|
|
||||||
shadowText = 'Enter new require path',
|
shadowText = 'Enter new require path',
|
||||||
accelerators = {
|
accelerators = {
|
||||||
enter = 'update_path',
|
enter = 'update_path',
|
||||||
|
|||||||
@@ -2,48 +2,93 @@ local UI = require('opus.ui')
|
|||||||
|
|
||||||
local settings = _G.settings
|
local settings = _G.settings
|
||||||
|
|
||||||
if settings then
|
local transform = {
|
||||||
local settingsTab = UI.Tab {
|
string = tostring,
|
||||||
tabTitle = 'Settings',
|
number = tonumber,
|
||||||
description = 'Computercraft configurable settings',
|
}
|
||||||
grid = UI.Grid {
|
|
||||||
y = 2,
|
|
||||||
autospace = true,
|
|
||||||
sortColumn = 'name',
|
|
||||||
columns = {
|
|
||||||
{ heading = 'Setting', key = 'name' },
|
|
||||||
{ heading = 'Value', key = 'value' },
|
|
||||||
},
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
function settingsTab:enable()
|
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 {
|
||||||
|
y = 2,
|
||||||
|
value = UI.TextEntry {
|
||||||
|
formIndex = 1,
|
||||||
|
formLabel = 'Value',
|
||||||
|
formKey = '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)
|
||||||
local values = { }
|
local values = { }
|
||||||
for _,v in pairs(settings.getNames()) do
|
for _,v in pairs(settings.getNames()) do
|
||||||
local value = settings.get(v)
|
|
||||||
if not value then
|
|
||||||
value = false
|
|
||||||
end
|
|
||||||
table.insert(values, {
|
table.insert(values, {
|
||||||
name = v,
|
name = v,
|
||||||
value = value,
|
value = settings.get(v) or false,
|
||||||
})
|
})
|
||||||
end
|
end
|
||||||
self.grid:setValues(values)
|
self.grid:setValues(values)
|
||||||
|
self.grid:setIndex(1)
|
||||||
|
end,
|
||||||
|
enable = function(self)
|
||||||
|
self:reload()
|
||||||
UI.Tab.enable(self)
|
UI.Tab.enable(self)
|
||||||
end
|
end,
|
||||||
|
eventHandler = function(self, event)
|
||||||
function settingsTab:eventHandler(event)
|
|
||||||
if event.type == 'grid_select' then
|
if event.type == 'grid_select' then
|
||||||
if not event.selected.value or type(event.selected.value) == 'boolean' then
|
if type(event.selected.value) == 'boolean' then
|
||||||
event.selected.value = not event.selected.value
|
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
|
end
|
||||||
settings.set(event.selected.name, event.selected.value)
|
|
||||||
settings.save('.settings')
|
|
||||||
self.grid:draw()
|
|
||||||
return true
|
return true
|
||||||
end
|
end
|
||||||
end
|
end,
|
||||||
|
}
|
||||||
return settingsTab
|
|
||||||
end
|
|
||||||
|
|||||||
@@ -18,9 +18,7 @@ local defaults = {
|
|||||||
textColor = colors.white,
|
textColor = colors.white,
|
||||||
commandTextColor = colors.yellow,
|
commandTextColor = colors.yellow,
|
||||||
directoryTextColor = colors.orange,
|
directoryTextColor = colors.orange,
|
||||||
directoryBackgroundColor = colors.black,
|
|
||||||
promptTextColor = colors.blue,
|
promptTextColor = colors.blue,
|
||||||
promptBackgroundColor = colors.black,
|
|
||||||
directoryColor = colors.green,
|
directoryColor = colors.green,
|
||||||
fileColor = colors.white,
|
fileColor = colors.white,
|
||||||
backgroundColor = colors.black,
|
backgroundColor = colors.black,
|
||||||
@@ -28,7 +26,7 @@ local defaults = {
|
|||||||
local _colors = config.color or Util.shallowCopy(defaults)
|
local _colors = config.color or Util.shallowCopy(defaults)
|
||||||
|
|
||||||
local allSettings = { }
|
local allSettings = { }
|
||||||
for k, v in pairs(defaults) do
|
for k in pairs(defaults) do
|
||||||
table.insert(allSettings, { name = k })
|
table.insert(allSettings, { name = k })
|
||||||
end
|
end
|
||||||
|
|
||||||
@@ -38,29 +36,34 @@ if not _colors.backgroundColor then
|
|||||||
_colors.fileColor = colors.white
|
_colors.fileColor = colors.white
|
||||||
end
|
end
|
||||||
|
|
||||||
local tab = UI.Tab {
|
return UI.Tab {
|
||||||
tabTitle = 'Shell',
|
title = 'Shell',
|
||||||
description = 'Shell options',
|
description = 'Shell options',
|
||||||
grid1 = UI.ScrollingGrid {
|
grid1 = UI.ScrollingGrid {
|
||||||
y = 2, ey = -10, x = 3, ex = -16,
|
y = 2, ey = -10, x = 2, ex = -17,
|
||||||
disableHeader = true,
|
disableHeader = true,
|
||||||
columns = { { key = 'name' } },
|
columns = { { key = 'name' } },
|
||||||
values = allSettings,
|
values = allSettings,
|
||||||
sortColumn = 'name',
|
sortColumn = 'name',
|
||||||
},
|
},
|
||||||
grid2 = UI.ScrollingGrid {
|
grid2 = UI.ScrollingGrid {
|
||||||
y = 2, ey = -10, x = -14, ex = -3,
|
y = 2, ey = -10, x = -14, ex = -2,
|
||||||
disableHeader = true,
|
disableHeader = true,
|
||||||
columns = { { key = 'name' } },
|
columns = { { key = 'name' } },
|
||||||
values = allColors,
|
values = allColors,
|
||||||
sortColumn = 'name',
|
sortColumn = 'name',
|
||||||
},
|
getRowTextColor = function(self, row)
|
||||||
directoryLabel = UI.Text {
|
local selected = self.parent.grid1:getSelected()
|
||||||
x = 2, y = -2,
|
if _colors[selected.name] == row.value then
|
||||||
value = 'Display directory',
|
return colors.yellow
|
||||||
|
end
|
||||||
|
return UI.Grid.getRowTextColor(self, row)
|
||||||
|
end
|
||||||
},
|
},
|
||||||
directory = UI.Checkbox {
|
directory = UI.Checkbox {
|
||||||
x = 20, y = -2,
|
x = 2, y = -2,
|
||||||
|
labelBackgroundColor = colors.black,
|
||||||
|
label = 'Directory',
|
||||||
value = config.displayDirectory
|
value = config.displayDirectory
|
||||||
},
|
},
|
||||||
reset = UI.Button {
|
reset = UI.Button {
|
||||||
@@ -74,69 +77,57 @@ local tab = UI.Tab {
|
|||||||
event = 'update',
|
event = 'update',
|
||||||
},
|
},
|
||||||
display = UI.Window {
|
display = UI.Window {
|
||||||
x = 3, ex = -3, y = -8, height = 5,
|
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,
|
||||||
},
|
},
|
||||||
|
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
|
|
||||||
|
|||||||
89
sys/apps/system/theme.lua
Normal file
89
sys/apps/system/theme.lua
Normal file
@@ -0,0 +1,89 @@
|
|||||||
|
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
|
||||||
|
}
|
||||||
55
sys/apps/system/trust.lua
Normal file
55
sys/apps/system/trust.lua
Normal file
@@ -0,0 +1,55 @@
|
|||||||
|
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
|
||||||
|
}
|
||||||
@@ -9,7 +9,7 @@ kernel.hook('clipboard_copy', function(_, args)
|
|||||||
keyboard.clipboard = args[1]
|
keyboard.clipboard = args[1]
|
||||||
end)
|
end)
|
||||||
|
|
||||||
keyboard.addHotkey('shift-paste', function()
|
local function queuePaste()
|
||||||
local data = keyboard.clipboard
|
local data = keyboard.clipboard
|
||||||
|
|
||||||
if type(data) == 'table' then
|
if type(data) == 'table' then
|
||||||
@@ -20,4 +20,7 @@ keyboard.addHotkey('shift-paste', function()
|
|||||||
if data then
|
if data then
|
||||||
os.queueEvent('paste', data)
|
os.queueEvent('paste', data)
|
||||||
end
|
end
|
||||||
end)
|
end
|
||||||
|
|
||||||
|
kernel.hook('clipboard_paste', queuePaste)
|
||||||
|
keyboard.addHotkey('shift-paste', queuePaste)
|
||||||
|
|||||||
@@ -10,9 +10,8 @@ if multishell and multishell.getTabs then
|
|||||||
local tab = kernel.getFocused()
|
local tab = kernel.getFocused()
|
||||||
if tab and not tab.noTerminate then
|
if tab and not tab.noTerminate then
|
||||||
multishell.terminate(tab.uid)
|
multishell.terminate(tab.uid)
|
||||||
multishell.openTab({
|
multishell.openTab(tab.env, {
|
||||||
path = tab.path,
|
path = tab.path,
|
||||||
env = tab.env,
|
|
||||||
args = tab.args,
|
args = tab.args,
|
||||||
focused = true,
|
focused = true,
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -4,29 +4,18 @@
|
|||||||
|
|
||||||
local kernel = _G.kernel
|
local kernel = _G.kernel
|
||||||
local keyboard = _G.device.keyboard
|
local keyboard = _G.device.keyboard
|
||||||
local multishell = _ENV.multishell
|
|
||||||
local os = _G.os
|
local os = _G.os
|
||||||
local term = _G.term
|
|
||||||
|
|
||||||
local function systemLog()
|
local function systemLog()
|
||||||
local routine = kernel.getCurrent()
|
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)
|
kernel.hook('mouse_scroll', function(_, eventData)
|
||||||
local dir, y = eventData[1], eventData[3]
|
local dir, y = eventData[1], eventData[3]
|
||||||
|
|
||||||
if y > 1 then
|
if y > 1 then
|
||||||
local currentTab = kernel.getFocused()
|
local currentTab = kernel.getFocused()
|
||||||
if currentTab == routine then
|
if currentTab == routine then
|
||||||
if currentTab.terminal.scrollUp and not currentTab.terminal.noAutoScroll then
|
if currentTab.terminal.scrollUp then
|
||||||
if dir == -1 then
|
if dir == -1 then
|
||||||
currentTab.terminal.scrollUp()
|
currentTab.terminal.scrollUp()
|
||||||
else
|
else
|
||||||
@@ -50,7 +39,7 @@ local function systemLog()
|
|||||||
keyboard.removeHotkey('control-d')
|
keyboard.removeHotkey('control-d')
|
||||||
end
|
end
|
||||||
|
|
||||||
kernel.run({
|
kernel.run(_ENV, {
|
||||||
title = 'System Log',
|
title = 'System Log',
|
||||||
fn = systemLog,
|
fn = systemLog,
|
||||||
noTerminate = true,
|
noTerminate = true,
|
||||||
|
|||||||
@@ -1,24 +0,0 @@
|
|||||||
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')
|
|
||||||
deleteIfExists('sys/init/6.tl3.lua')
|
|
||||||
|
|
||||||
-- remove this file
|
|
||||||
deleteIfExists('sys/autorun/upgraded.lua')
|
|
||||||
53
sys/autorun/version.lua
Normal file
53
sys/autorun/version.lua
Normal file
@@ -0,0 +1,53 @@
|
|||||||
|
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
|
||||||
@@ -21,7 +21,7 @@ if mon then
|
|||||||
|
|
||||||
parallel.waitForAny(
|
parallel.waitForAny(
|
||||||
function()
|
function()
|
||||||
os.run(_ENV, '/sys/boot/opus.boot')
|
os.run(_ENV, '/sys/boot/opus.lua')
|
||||||
end,
|
end,
|
||||||
|
|
||||||
function()
|
function()
|
||||||
@@ -36,5 +36,5 @@ if mon then
|
|||||||
end
|
end
|
||||||
)
|
)
|
||||||
else
|
else
|
||||||
os.run(_ENV, '/sys/boot/opus.boot')
|
os.run(_ENV, '/sys/boot/opus.lua')
|
||||||
end
|
end
|
||||||
@@ -1,36 +0,0 @@
|
|||||||
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 = 'develop-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
|
|
||||||
58
sys/boot/opus.lua
Normal file
58
sys/boot/opus.lua
Normal file
@@ -0,0 +1,58 @@
|
|||||||
|
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
|
||||||
@@ -1,15 +0,0 @@
|
|||||||
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')
|
|
||||||
30
sys/boot/tlco.lua
Normal file
30
sys/boot/tlco.lua
Normal file
@@ -0,0 +1,30 @@
|
|||||||
|
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()
|
||||||
@@ -50,7 +50,10 @@
|
|||||||
title = "System",
|
title = "System",
|
||||||
category = "System",
|
category = "System",
|
||||||
icon = "\030 \0307\031f| \010\0307\031f---o\030 \031 \010\030 \009 \0307\031f| ",
|
icon = "\030 \0307\031f| \010\0307\031f---o\030 \031 \010\030 \009 \0307\031f| ",
|
||||||
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 = "22070b02424\
|
||||||
|
0277044\
|
||||||
|
7071724",
|
||||||
|
--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",
|
run = "System.lua",
|
||||||
},
|
},
|
||||||
[ "2a4d562b1d9a9c90bdede6fac8ce4f7402462b86" ] = {
|
[ "2a4d562b1d9a9c90bdede6fac8ce4f7402462b86" ] = {
|
||||||
@@ -133,4 +136,10 @@
|
|||||||
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",
|
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",
|
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",
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,5 +2,4 @@ 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/update.lua urlfs http://pastebin.com/raw/UzGHLbNC
|
||||||
sys/apps/Enchat.lua urlfs https://raw.githubusercontent.com/LDDestroier/enchat/master/enchat3.lua
|
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/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
|
rom/modules/main/opus linkfs sys/modules/opus
|
||||||
9
sys/help/Networking.txt
Normal file
9
sys/help/Networking.txt
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
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>
|
||||||
@@ -1,30 +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 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.
|
|
||||||
@@ -6,17 +6,7 @@ Shortcut Keys
|
|||||||
* Control-d: Show/toggle logging screen
|
* Control-d: Show/toggle logging screen
|
||||||
* Control-c: Copy (in most applications)
|
* Control-c: Copy (in most applications)
|
||||||
* Control-shift-v: Paste from internal clipboard
|
* Control-shift-v: Paste from internal clipboard
|
||||||
* Control-shift-doubleclick: Open in Lua the clicked UI element
|
* Control-shift-click: Open the clicked UI element in Lua
|
||||||
|
|
||||||
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
|
Running Custom Programs
|
||||||
=======================
|
=======================
|
||||||
@@ -6,10 +6,11 @@ Shortcut keys
|
|||||||
* l: Lua application
|
* l: Lua application
|
||||||
* f: Files
|
* f: Files
|
||||||
* e: Edit an application (or right-click)
|
* e: Edit an application (or right-click)
|
||||||
|
* n: Network
|
||||||
* control-n: Add a new application
|
* control-n: Add a new application
|
||||||
* delete: Delete an application
|
* delete: Delete an application
|
||||||
|
|
||||||
Adding a new application
|
Adding a new application
|
||||||
========================
|
========================
|
||||||
The run entry can be either a disk file or a URL.
|
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.
|
Icons must be in NFT format with a height of 3 and a width of 3 to 8 characters. Magenta is used for transparency.
|
||||||
13
sys/help/Packages.txt
Normal file
13
sys/help/Packages.txt
Normal file
@@ -0,0 +1,13 @@
|
|||||||
|
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.
|
||||||
@@ -1,5 +1,3 @@
|
|||||||
_G.requireInjector(_ENV)
|
|
||||||
|
|
||||||
local Peripheral = require('opus.peripheral')
|
local Peripheral = require('opus.peripheral')
|
||||||
|
|
||||||
_G.device = Peripheral.getList()
|
_G.device = Peripheral.getList()
|
||||||
|
|||||||
@@ -4,11 +4,8 @@ if fs.native then
|
|||||||
return
|
return
|
||||||
end
|
end
|
||||||
|
|
||||||
_G.requireInjector(_ENV)
|
|
||||||
local Util = require('opus.util')
|
local Util = require('opus.util')
|
||||||
|
|
||||||
-- TODO: support getDrive for virtual nodes
|
|
||||||
|
|
||||||
fs.native = Util.shallowCopy(fs)
|
fs.native = Util.shallowCopy(fs)
|
||||||
|
|
||||||
local fstypes = { }
|
local fstypes = { }
|
||||||
@@ -22,8 +19,11 @@ for k,fn in pairs(fs) do
|
|||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
function nativefs.list(node, dir)
|
function nativefs.resolve(_, dir)
|
||||||
|
return dir
|
||||||
|
end
|
||||||
|
|
||||||
|
function nativefs.list(node, dir)
|
||||||
local files
|
local files
|
||||||
if fs.native.isDir(dir) then
|
if fs.native.isDir(dir) then
|
||||||
files = fs.native.list(dir)
|
files = fs.native.list(dir)
|
||||||
@@ -83,6 +83,18 @@ function nativefs.isDir(node, dir)
|
|||||||
return fs.native.isDir(dir)
|
return fs.native.isDir(dir)
|
||||||
end
|
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)
|
function nativefs.exists(node, dir)
|
||||||
if node.mountPoint == dir then
|
if node.mountPoint == dir then
|
||||||
return true
|
return true
|
||||||
@@ -138,8 +150,10 @@ local function getNode(dir)
|
|||||||
return node
|
return node
|
||||||
end
|
end
|
||||||
|
|
||||||
|
fs.getNode = getNode
|
||||||
|
|
||||||
local methods = { 'delete', 'getFreeSpace', 'exists', 'isDir', 'getSize',
|
local methods = { 'delete', 'getFreeSpace', 'exists', 'isDir', 'getSize',
|
||||||
'isReadOnly', 'makeDir', 'getDrive', 'list', 'open' }
|
'isReadOnly', 'makeDir', 'getDrive', 'list', 'open', 'attributes' }
|
||||||
|
|
||||||
for _,m in pairs(methods) do
|
for _,m in pairs(methods) do
|
||||||
fs[m] = function(dir, ...)
|
fs[m] = function(dir, ...)
|
||||||
@@ -149,6 +163,12 @@ for _,m in pairs(methods) do
|
|||||||
end
|
end
|
||||||
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)
|
function fs.complete(partial, dir, includeFiles, includeSlash)
|
||||||
dir = fs.combine(dir, '')
|
dir = fs.combine(dir, '')
|
||||||
local node = getNode(dir)
|
local node = getNode(dir)
|
||||||
@@ -158,6 +178,13 @@ function fs.complete(partial, dir, includeFiles, includeSlash)
|
|||||||
return fs.native.complete(partial, dir, includeFiles, includeSlash)
|
return fs.native.complete(partial, dir, includeFiles, includeSlash)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
local displayFlags = {
|
||||||
|
urlfs = 'U',
|
||||||
|
linkfs = 'L',
|
||||||
|
ramfs = 'T',
|
||||||
|
netfs = 'N',
|
||||||
|
}
|
||||||
|
|
||||||
function fs.listEx(dir)
|
function fs.listEx(dir)
|
||||||
dir = fs.combine(dir, '')
|
dir = fs.combine(dir, '')
|
||||||
local node = getNode(dir)
|
local node = getNode(dir)
|
||||||
@@ -168,20 +195,22 @@ function fs.listEx(dir)
|
|||||||
local t = { }
|
local t = { }
|
||||||
local files = node.fs.list(node, dir)
|
local files = node.fs.list(node, dir)
|
||||||
|
|
||||||
pcall(function()
|
for _,f in ipairs(files) do
|
||||||
for _,f in ipairs(files) do
|
pcall(function()
|
||||||
local fullName = fs.combine(dir, f)
|
local fullName = fs.combine(dir, f)
|
||||||
|
local n = fs.getNode(fullName)
|
||||||
local file = {
|
local file = {
|
||||||
name = f,
|
name = f,
|
||||||
isDir = fs.isDir(fullName),
|
isDir = fs.isDir(fullName),
|
||||||
isReadOnly = fs.isReadOnly(fullName),
|
isReadOnly = fs.isReadOnly(fullName),
|
||||||
|
fstype = n.mountPoint == fullName and displayFlags[n.fstype],
|
||||||
}
|
}
|
||||||
if not file.isDir then
|
if not file.isDir then
|
||||||
file.size = fs.getSize(fullName)
|
file.size = fs.getSize(fullName)
|
||||||
end
|
end
|
||||||
table.insert(t, file)
|
table.insert(t, file)
|
||||||
end
|
end)
|
||||||
end)
|
end
|
||||||
return t
|
return t
|
||||||
end
|
end
|
||||||
|
|
||||||
@@ -206,12 +235,12 @@ function fs.copy(s, t)
|
|||||||
end
|
end
|
||||||
|
|
||||||
else
|
else
|
||||||
local sf = Util.readFile(s)
|
local sf = Util.readFile(s, 'rb')
|
||||||
if not sf then
|
if not sf then
|
||||||
error('No such file')
|
error('No such file')
|
||||||
end
|
end
|
||||||
|
|
||||||
Util.writeFile(t, sf)
|
Util.writeFile(t, sf, 'wb')
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
@@ -265,11 +294,17 @@ local function getfstype(fstype)
|
|||||||
end
|
end
|
||||||
|
|
||||||
function fs.mount(path, fstype, ...)
|
function fs.mount(path, fstype, ...)
|
||||||
|
|
||||||
local vfs = getfstype(fstype)
|
local vfs = getfstype(fstype)
|
||||||
if not vfs then
|
if not vfs then
|
||||||
error('Invalid file system type')
|
error('Invalid file system type')
|
||||||
end
|
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, ...)
|
local node = vfs.mount(path, ...)
|
||||||
if node then
|
if node then
|
||||||
local parts = splitpath(path)
|
local parts = splitpath(path)
|
||||||
@@ -284,12 +319,16 @@ function fs.mount(path, fstype, ...)
|
|||||||
tp.nodes[d] = Util.shallowCopy(tp)
|
tp.nodes[d] = Util.shallowCopy(tp)
|
||||||
tp.nodes[d].nodes = { }
|
tp.nodes[d].nodes = { }
|
||||||
tp.nodes[d].mountPoint = fs.combine(tp.mountPoint, d)
|
tp.nodes[d].mountPoint = fs.combine(tp.mountPoint, d)
|
||||||
|
tp.nodes[d].created = os.epoch('utc')
|
||||||
|
tp.nodes[d].modification = os.epoch('utc')
|
||||||
end
|
end
|
||||||
tp = tp.nodes[d]
|
tp = tp.nodes[d]
|
||||||
end
|
end
|
||||||
|
|
||||||
node.fs = vfs
|
node.fs = vfs
|
||||||
node.fstype = fstype
|
node.fstype = fstype
|
||||||
|
node.created = node.created or os.epoch('utc')
|
||||||
|
node.modification = node.modification or os.epoch('utc')
|
||||||
if not targetName then
|
if not targetName then
|
||||||
node.mountPoint = ''
|
node.mountPoint = ''
|
||||||
fs.nodes = node
|
fs.nodes = node
|
||||||
|
|||||||
@@ -1,3 +1,46 @@
|
|||||||
local fs = _G.fs
|
local fs = _G.fs
|
||||||
|
local os = _G.os
|
||||||
|
|
||||||
fs.loadTab('sys/etc/fstab')
|
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
|
||||||
@@ -10,6 +10,13 @@ if not fs.exists('usr/autorun') then
|
|||||||
fs.makeDir('usr/autorun')
|
fs.makeDir('usr/autorun')
|
||||||
end
|
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
|
-- TODO: Temporary
|
||||||
local upgrade = Util.readTable('usr/config/shell')
|
local upgrade = Util.readTable('usr/config/shell')
|
||||||
if upgrade and (not upgrade.upgraded or upgrade.upgraded ~= 1) then
|
if upgrade and (not upgrade.upgraded or upgrade.upgraded ~= 1) then
|
||||||
@@ -45,5 +52,3 @@ shell.setPath(table.concat(path, ':'))
|
|||||||
|
|
||||||
--_G.LUA_PATH = config.lua_path
|
--_G.LUA_PATH = config.lua_path
|
||||||
--_G.settings.set('mbs.shell.require_path', config.lua_path)
|
--_G.settings.set('mbs.shell.require_path', config.lua_path)
|
||||||
|
|
||||||
fs.loadTab('usr/config/fstab')
|
|
||||||
|
|||||||
@@ -1,5 +1,3 @@
|
|||||||
_G.requireInjector(_ENV)
|
|
||||||
|
|
||||||
local Config = require('opus.config')
|
local Config = require('opus.config')
|
||||||
|
|
||||||
local device = _G.device
|
local device = _G.device
|
||||||
@@ -17,7 +15,7 @@ do
|
|||||||
end
|
end
|
||||||
|
|
||||||
local function startNetwork()
|
local function startNetwork()
|
||||||
kernel.run({
|
kernel.run(_ENV, {
|
||||||
title = 'Net daemon',
|
title = 'Net daemon',
|
||||||
path = 'sys/apps/netdaemon.lua',
|
path = 'sys/apps/netdaemon.lua',
|
||||||
hidden = true,
|
hidden = true,
|
||||||
|
|||||||
31
sys/init/5.unpackage.lua
Normal file
31
sys/init/5.unpackage.lua
Normal file
@@ -0,0 +1,31 @@
|
|||||||
|
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
|
||||||
@@ -13,8 +13,14 @@ table.insert(helpPaths, '/sys/help')
|
|||||||
for name in pairs(Packages:installed()) do
|
for name in pairs(Packages:installed()) do
|
||||||
local packageDir = fs.combine('packages', name)
|
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)
|
table.insert(appPaths, 1, '/' .. packageDir)
|
||||||
local apiPath = fs.combine(packageDir, 'apis')
|
|
||||||
|
local apiPath = fs.combine(packageDir, 'apis') -- TODO: rename dir to 'modules' (someday)
|
||||||
if fs.exists(apiPath) then
|
if fs.exists(apiPath) then
|
||||||
fs.mount(fs.combine('rom/modules/main', name), 'linkfs', apiPath)
|
fs.mount(fs.combine('rom/modules/main', name), 'linkfs', apiPath)
|
||||||
end
|
end
|
||||||
@@ -23,12 +29,27 @@ for name in pairs(Packages:installed()) do
|
|||||||
if fs.exists(helpPath) then
|
if fs.exists(helpPath) then
|
||||||
table.insert(helpPaths, helpPath)
|
table.insert(helpPaths, helpPath)
|
||||||
end
|
end
|
||||||
|
|
||||||
local fstabPath = fs.combine(packageDir, 'etc/fstab')
|
|
||||||
if fs.exists(fstabPath) then
|
|
||||||
fs.loadTab(fstabPath)
|
|
||||||
end
|
|
||||||
end
|
end
|
||||||
|
|
||||||
help.setPath(table.concat(helpPaths, ':'))
|
help.setPath(table.concat(helpPaths, ':'))
|
||||||
shell.setPath(table.concat(appPaths, ':'))
|
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
|
||||||
|
|||||||
@@ -1,7 +1,5 @@
|
|||||||
_G.requireInjector(_ENV)
|
local Blit = require('opus.ui.blit')
|
||||||
|
|
||||||
local Config = require('opus.config')
|
local Config = require('opus.config')
|
||||||
local trace = require('opus.trace')
|
|
||||||
local Util = require('opus.util')
|
local Util = require('opus.util')
|
||||||
|
|
||||||
local colors = _G.colors
|
local colors = _G.colors
|
||||||
@@ -10,7 +8,6 @@ local kernel = _G.kernel
|
|||||||
local keys = _G.keys
|
local keys = _G.keys
|
||||||
local os = _G.os
|
local os = _G.os
|
||||||
local printError = _G.printError
|
local printError = _G.printError
|
||||||
local shell = _ENV.shell
|
|
||||||
local window = _G.window
|
local window = _G.window
|
||||||
|
|
||||||
local parentTerm = _G.device.terminal
|
local parentTerm = _G.device.terminal
|
||||||
@@ -20,9 +17,9 @@ local tabsDirty = false
|
|||||||
local closeInd = Util.getVersion() >= 1.76 and '\215' or '*'
|
local closeInd = Util.getVersion() >= 1.76 and '\215' or '*'
|
||||||
local multishell = { }
|
local multishell = { }
|
||||||
|
|
||||||
shell.setEnv('multishell', multishell)
|
_ENV.multishell = multishell
|
||||||
|
|
||||||
multishell.term = parentTerm --deprecated use device.terminal
|
kernel.window.reposition(1, 2, w, h - 1)
|
||||||
|
|
||||||
local config = {
|
local config = {
|
||||||
standard = {
|
standard = {
|
||||||
@@ -47,6 +44,7 @@ local config = {
|
|||||||
Config.load('multishell', config)
|
Config.load('multishell', config)
|
||||||
|
|
||||||
local _colors = parentTerm.isColor() and config.color or config.standard
|
local _colors = parentTerm.isColor() and config.color or config.standard
|
||||||
|
local palette = parentTerm.isColor() and Blit.colorPalette or Blit.grayscalePalette
|
||||||
|
|
||||||
local function redrawMenu()
|
local function redrawMenu()
|
||||||
if not tabsDirty then
|
if not tabsDirty then
|
||||||
@@ -94,57 +92,70 @@ function multishell.getTabs()
|
|||||||
return kernel.routines
|
return kernel.routines
|
||||||
end
|
end
|
||||||
|
|
||||||
function multishell.launch( tProgramEnv, sProgramPath, ... )
|
function multishell.launch(env, path, ...)
|
||||||
-- backwards compatibility
|
-- backwards compatibility
|
||||||
return multishell.openTab({
|
return multishell.openTab(env, {
|
||||||
env = tProgramEnv,
|
path = path,
|
||||||
path = sProgramPath,
|
|
||||||
args = { ... },
|
args = { ... },
|
||||||
})
|
})
|
||||||
end
|
end
|
||||||
|
|
||||||
local function xprun(env, path, ...)
|
local function chain(orig, fn)
|
||||||
setmetatable(env, { __index = _G })
|
if not orig then
|
||||||
local fn, m = loadfile(path, env)
|
return fn
|
||||||
if fn then
|
|
||||||
return trace(fn, ...)
|
|
||||||
end
|
end
|
||||||
return fn, m
|
|
||||||
|
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
|
||||||
|
})
|
||||||
end
|
end
|
||||||
|
|
||||||
function multishell.openTab(tab)
|
function multishell.openTab(env, tab)
|
||||||
if not tab.title and tab.path then
|
if not tab.title and tab.path then
|
||||||
tab.title = fs.getName(tab.path):match('([^%.]+)')
|
tab.title = fs.getName(tab.path):match('([^%.]+)')
|
||||||
end
|
end
|
||||||
tab.title = tab.title or 'untitled'
|
tab.title = tab.title or 'untitled'
|
||||||
tab.window = tab.window or window.create(parentTerm, 1, 2, w, h - 1, false)
|
tab.window = tab.window or window.create(parentTerm, 1, 2, w, h - 1, false)
|
||||||
tab.terminal = tab.terminal or tab.window
|
-- require('opus.terminal').window(parentTerm, 1, 2, w, h - 1, false)
|
||||||
|
tab.onExit = chain(tab.onExit, function(self, result, err, stack)
|
||||||
local routine = kernel.newRoutine(tab)
|
if not result and err and err ~= 'Terminated' then
|
||||||
|
self.terminal.setTextColor(colors.white)
|
||||||
routine.co = coroutine.create(function()
|
self.terminal.setCursorBlink(false)
|
||||||
local result, err
|
print('\nThe program terminated with an error.\n')
|
||||||
|
|
||||||
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
|
if tonumber(err) then
|
||||||
tab.terminal.setTextColor(colors.orange)
|
printError('Process exited with error code: ' .. err)
|
||||||
print('Process exited with error code: ' .. err)
|
|
||||||
elseif err then
|
elseif err then
|
||||||
printError(tostring(err))
|
printError(tostring(err))
|
||||||
end
|
end
|
||||||
tab.terminal.setTextColor(colors.white)
|
if type(stack) == 'table' and #stack > 0 then
|
||||||
print('\nPress enter to close')
|
local _, cy = self.terminal.getCursorPos()
|
||||||
routine.isDead = true
|
local _, th = self.terminal.getSize()
|
||||||
routine.hidden = false
|
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
|
||||||
redrawMenu()
|
redrawMenu()
|
||||||
while true do
|
while true do
|
||||||
local e, code = os.pullEventRaw('key')
|
local e, code = os.pullEventRaw('key')
|
||||||
@@ -155,14 +166,17 @@ function multishell.openTab(tab)
|
|||||||
end
|
end
|
||||||
end)
|
end)
|
||||||
|
|
||||||
kernel.launch(routine)
|
local routine, message = kernel.run(env, tab)
|
||||||
|
|
||||||
if tab.focused then
|
if routine then
|
||||||
multishell.setFocus(routine.uid)
|
if tab.focused then
|
||||||
else
|
multishell.setFocus(routine.uid)
|
||||||
redrawMenu()
|
else
|
||||||
|
redrawMenu()
|
||||||
|
end
|
||||||
end
|
end
|
||||||
return routine.uid
|
|
||||||
|
return routine and routine.uid, message
|
||||||
end
|
end
|
||||||
|
|
||||||
function multishell.hideTab(tabId)
|
function multishell.hideTab(tabId)
|
||||||
@@ -207,17 +221,11 @@ end)
|
|||||||
kernel.hook('multishell_redraw', function()
|
kernel.hook('multishell_redraw', function()
|
||||||
tabsDirty = false
|
tabsDirty = false
|
||||||
|
|
||||||
local function write(x, text, bg, fg)
|
local blit = Blit(w, {
|
||||||
parentTerm.setBackgroundColor(bg)
|
bg = _colors.tabBarBackgroundColor,
|
||||||
parentTerm.setTextColor(fg)
|
fg = _colors.textColor,
|
||||||
parentTerm.setCursorPos(x, 1)
|
palette = palette,
|
||||||
parentTerm.write(text)
|
})
|
||||||
end
|
|
||||||
|
|
||||||
local bg = _colors.tabBarBackgroundColor
|
|
||||||
parentTerm.setBackgroundColor(bg)
|
|
||||||
parentTerm.setCursorPos(1, 1)
|
|
||||||
parentTerm.clearLine()
|
|
||||||
|
|
||||||
local currentTab = kernel.getFocused()
|
local currentTab = kernel.getFocused()
|
||||||
|
|
||||||
@@ -254,21 +262,27 @@ kernel.hook('multishell_redraw', function()
|
|||||||
tabX = tabX + tab.width
|
tabX = tabX + tab.width
|
||||||
if tab ~= currentTab then
|
if tab ~= currentTab then
|
||||||
local textColor = tab.isDead and _colors.errorColor or _colors.textColor
|
local textColor = tab.isDead and _colors.errorColor or _colors.textColor
|
||||||
write(tab.sx, tab.title:sub(1, tab.width - 1),
|
blit:write(tab.sx, tab.title:sub(1, tab.width - 1),
|
||||||
_colors.backgroundColor, textColor)
|
_colors.backgroundColor, textColor)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
if currentTab then
|
if currentTab then
|
||||||
write(currentTab.sx - 1,
|
if currentTab.sx then
|
||||||
' ' .. currentTab.title:sub(1, currentTab.width - 1) .. ' ',
|
local textColor = currentTab.isDead and _colors.errorColor or _colors.focusTextColor
|
||||||
_colors.focusBackgroundColor, _colors.focusTextColor)
|
blit:write(currentTab.sx - 1,
|
||||||
|
' ' .. currentTab.title:sub(1, currentTab.width - 1) .. ' ',
|
||||||
|
_colors.focusBackgroundColor, textColor)
|
||||||
|
end
|
||||||
if not currentTab.noTerminate then
|
if not currentTab.noTerminate then
|
||||||
write(w, closeInd, _colors.backgroundColor, _colors.focusTextColor)
|
blit:write(w, closeInd, nil, _colors.focusTextColor)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
parentTerm.setCursorPos(1, 1)
|
||||||
|
parentTerm.blit(blit.text, blit.fg, blit.bg)
|
||||||
|
|
||||||
if currentTab and currentTab.window then
|
if currentTab and currentTab.window then
|
||||||
currentTab.window.restoreCursor()
|
currentTab.window.restoreCursor()
|
||||||
end
|
end
|
||||||
@@ -297,56 +311,65 @@ kernel.hook('term_resize', function(_, eventData)
|
|||||||
end)
|
end)
|
||||||
|
|
||||||
kernel.hook('mouse_click', function(_, eventData)
|
kernel.hook('mouse_click', function(_, eventData)
|
||||||
local x, y = eventData[2], eventData[3]
|
if not eventData[4] then
|
||||||
|
local x, y = eventData[2], eventData[3]
|
||||||
|
|
||||||
if y == 1 then
|
if y == 1 then
|
||||||
if x == 1 then
|
if x == 1 then
|
||||||
multishell.setFocus(overviewId)
|
multishell.setFocus(overviewId)
|
||||||
elseif x == w then
|
elseif x == w then
|
||||||
local currentTab = kernel.getFocused()
|
local currentTab = kernel.getFocused()
|
||||||
if currentTab then
|
if currentTab then
|
||||||
multishell.terminate(currentTab.uid)
|
multishell.terminate(currentTab.uid)
|
||||||
end
|
end
|
||||||
else
|
else
|
||||||
for _,tab in pairs(kernel.routines) do
|
for _,tab in pairs(kernel.routines) do
|
||||||
if not tab.hidden and tab.sx then
|
if not tab.hidden and tab.sx then
|
||||||
if x >= tab.sx and x <= tab.ex then
|
if x >= tab.sx and x <= tab.ex then
|
||||||
multishell.setFocus(tab.uid)
|
multishell.setFocus(tab.uid)
|
||||||
break
|
break
|
||||||
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
return true
|
||||||
end
|
end
|
||||||
return true
|
eventData[3] = eventData[3] - 1
|
||||||
end
|
end
|
||||||
eventData[3] = eventData[3] - 1
|
|
||||||
end)
|
end)
|
||||||
|
|
||||||
kernel.hook({ 'mouse_up', 'mouse_drag' }, function(_, eventData)
|
kernel.hook({ 'mouse_up', 'mouse_drag' }, function(_, eventData)
|
||||||
eventData[3] = eventData[3] - 1
|
if not eventData[4] then
|
||||||
|
eventData[3] = eventData[3] - 1
|
||||||
|
end
|
||||||
end)
|
end)
|
||||||
|
|
||||||
kernel.hook('mouse_scroll', function(_, eventData)
|
kernel.hook('mouse_scroll', function(_, eventData)
|
||||||
if eventData[3] == 1 then
|
if not eventData[4] then
|
||||||
return true
|
if eventData[3] == 1 then
|
||||||
|
return true
|
||||||
|
end
|
||||||
|
eventData[3] = eventData[3] - 1
|
||||||
end
|
end
|
||||||
eventData[3] = eventData[3] - 1
|
|
||||||
end)
|
end)
|
||||||
|
|
||||||
kernel.hook('kernel_ready', function()
|
kernel.hook('kernel_ready', function()
|
||||||
local env = Util.shallowCopy(shell.getEnv())
|
overviewId = multishell.openTab(_ENV, {
|
||||||
_G.requireInjector(env)
|
path = 'sys/apps/shell.lua',
|
||||||
|
args = { config.launcher or 'sys/apps/Overview.lua' },
|
||||||
overviewId = multishell.openTab({
|
|
||||||
path = config.launcher or 'sys/apps/Overview.lua',
|
|
||||||
isOverview = true,
|
isOverview = true,
|
||||||
noTerminate = true,
|
noTerminate = true,
|
||||||
focused = true,
|
focused = true,
|
||||||
title = '+',
|
title = '+',
|
||||||
env = env,
|
onExit = function(_, s, m)
|
||||||
|
if not s then
|
||||||
|
kernel.halt(s, m)
|
||||||
|
end
|
||||||
|
end,
|
||||||
})
|
})
|
||||||
|
multishell.setTitle(overviewId, '+')
|
||||||
|
|
||||||
multishell.openTab({
|
multishell.openTab(_ENV, {
|
||||||
path = 'sys/apps/shell.lua',
|
path = 'sys/apps/shell.lua',
|
||||||
args = { 'sys/apps/autorun.lua' },
|
args = { 'sys/apps/autorun.lua' },
|
||||||
title = 'Autorun',
|
title = 'Autorun',
|
||||||
|
|||||||
183
sys/kernel.lua
183
sys/kernel.lua
@@ -1,7 +1,6 @@
|
|||||||
_G.requireInjector(_ENV)
|
|
||||||
|
|
||||||
local Array = require('opus.array')
|
local Array = require('opus.array')
|
||||||
local Terminal = require('opus.terminal')
|
local Terminal = require('opus.terminal')
|
||||||
|
local trace = require('opus.trace')
|
||||||
local Util = require('opus.util')
|
local Util = require('opus.util')
|
||||||
|
|
||||||
_G.kernel = {
|
_G.kernel = {
|
||||||
@@ -21,7 +20,7 @@ local w, h = term.getSize()
|
|||||||
kernel.terminal = term.current()
|
kernel.terminal = term.current()
|
||||||
|
|
||||||
kernel.window = Terminal.window(kernel.terminal, 1, 1, w, h, false)
|
kernel.window = Terminal.window(kernel.terminal, 1, 1, w, h, false)
|
||||||
kernel.window.setMaxScroll(100)
|
kernel.window.setMaxScroll(200)
|
||||||
|
|
||||||
local focusedRoutineEvents = Util.transpose {
|
local focusedRoutineEvents = Util.transpose {
|
||||||
'char', 'key', 'key_up',
|
'char', 'key', 'key_up',
|
||||||
@@ -30,10 +29,8 @@ local focusedRoutineEvents = Util.transpose {
|
|||||||
}
|
}
|
||||||
|
|
||||||
_G._syslog = function(pattern, ...)
|
_G._syslog = function(pattern, ...)
|
||||||
local oldTerm = term.redirect(kernel.window)
|
|
||||||
kernel.window.scrollBottom()
|
kernel.window.scrollBottom()
|
||||||
Util.print(pattern, ...)
|
kernel.window.print(Util.tostring(pattern, ...))
|
||||||
term.redirect(oldTerm)
|
|
||||||
end
|
end
|
||||||
|
|
||||||
-- any function that runs in a kernel hook does not run in
|
-- any function that runs in a kernel hook does not run in
|
||||||
@@ -52,19 +49,23 @@ function kernel.hook(event, fn)
|
|||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
-- you can only unhook from within the function that hooked
|
-- you *should* only unhook from within the function that hooked
|
||||||
function kernel.unhook(event, fn)
|
function kernel.unhook(event, fn)
|
||||||
local eventHooks = kernel.hooks[event]
|
if type(event) == 'table' then
|
||||||
if eventHooks then
|
for _,v in pairs(event) do
|
||||||
Array.removeByValue(eventHooks, fn)
|
kernel.unhook(v, fn)
|
||||||
if #eventHooks == 0 then
|
end
|
||||||
kernel.hooks[event] = nil
|
else
|
||||||
|
local eventHooks = kernel.hooks[event]
|
||||||
|
if eventHooks then
|
||||||
|
Array.removeByValue(eventHooks, fn)
|
||||||
|
if #eventHooks == 0 then
|
||||||
|
kernel.hooks[event] = nil
|
||||||
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
local Routine = { }
|
|
||||||
|
|
||||||
local function switch(routine, previous)
|
local function switch(routine, previous)
|
||||||
if routine then
|
if routine then
|
||||||
if previous and previous.window then
|
if previous and previous.window then
|
||||||
@@ -82,6 +83,8 @@ local function switch(routine, previous)
|
|||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
local Routine = { }
|
||||||
|
|
||||||
function Routine:resume(event, ...)
|
function Routine:resume(event, ...)
|
||||||
if not self.co or coroutine.status(self.co) == 'dead' then
|
if not self.co or coroutine.status(self.co) == 'dead' then
|
||||||
return
|
return
|
||||||
@@ -91,35 +94,64 @@ function Routine:resume(event, ...)
|
|||||||
local previousTerm = term.redirect(self.terminal)
|
local previousTerm = term.redirect(self.terminal)
|
||||||
|
|
||||||
local previous = kernel.running
|
local previous = kernel.running
|
||||||
kernel.running = self -- stupid shell set title
|
kernel.running = self
|
||||||
local ok, result = coroutine.resume(self.co, event, ...)
|
local ok, result = coroutine.resume(self.co, event, ...)
|
||||||
kernel.running = previous
|
kernel.running = previous
|
||||||
|
|
||||||
if ok then
|
self.filter = result
|
||||||
self.filter = result
|
|
||||||
else
|
|
||||||
_G.printError(result)
|
|
||||||
end
|
|
||||||
|
|
||||||
self.terminal = term.current()
|
self.terminal = term.current()
|
||||||
term.redirect(previousTerm)
|
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
|
return ok, result
|
||||||
end
|
end
|
||||||
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()
|
function kernel.getFocused()
|
||||||
return kernel.routines[1]
|
return kernel.routines[1]
|
||||||
end
|
end
|
||||||
@@ -132,51 +164,34 @@ function kernel.getShell()
|
|||||||
return shell
|
return shell
|
||||||
end
|
end
|
||||||
|
|
||||||
function kernel.newRoutine(args)
|
-- 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)
|
||||||
kernel.UID = kernel.UID + 1
|
kernel.UID = kernel.UID + 1
|
||||||
|
|
||||||
local routine = setmetatable({
|
local routine = setmetatable({
|
||||||
uid = kernel.UID,
|
uid = kernel.UID,
|
||||||
timestamp = os.clock(),
|
timestamp = os.clock(),
|
||||||
terminal = kernel.window,
|
|
||||||
window = kernel.window,
|
window = kernel.window,
|
||||||
title = 'untitled',
|
title = 'untitled',
|
||||||
}, { __index = Routine })
|
}, { __index = Routine })
|
||||||
|
|
||||||
Util.merge(routine, args)
|
Util.merge(routine, args)
|
||||||
routine.env = args.env or Util.shallowCopy(shell.getEnv())
|
routine.env = args.env or kernel.makeEnv(env, routine.path and fs.getDir(routine.path))
|
||||||
|
routine.terminal = routine.terminal or routine.window
|
||||||
|
|
||||||
return routine
|
return routine
|
||||||
end
|
end
|
||||||
|
|
||||||
function kernel.launch(routine)
|
function kernel.run(env, args)
|
||||||
routine.co = routine.co or coroutine.create(function()
|
local routine = kernel.newRoutine(env, args)
|
||||||
local result, err
|
local s, m = routine:run()
|
||||||
|
return s and routine, m
|
||||||
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
|
end
|
||||||
|
|
||||||
function kernel.raise(uid)
|
function kernel.raise(uid)
|
||||||
@@ -194,8 +209,6 @@ function kernel.raise(uid)
|
|||||||
end
|
end
|
||||||
|
|
||||||
switch(routine, previous)
|
switch(routine, previous)
|
||||||
-- local previous = eventData[2]
|
|
||||||
-- local routine = kernel.find(previous)
|
|
||||||
return true
|
return true
|
||||||
end
|
end
|
||||||
return false
|
return false
|
||||||
@@ -223,8 +236,8 @@ function kernel.find(uid)
|
|||||||
return Util.find(kernel.routines, 'uid', uid)
|
return Util.find(kernel.routines, 'uid', uid)
|
||||||
end
|
end
|
||||||
|
|
||||||
function kernel.halt()
|
function kernel.halt(status, message)
|
||||||
os.queueEvent('kernel_halt')
|
os.queueEvent('kernel_halt', status, message)
|
||||||
end
|
end
|
||||||
|
|
||||||
function kernel.event(event, eventData)
|
function kernel.event(event, eventData)
|
||||||
@@ -266,19 +279,24 @@ function kernel.event(event, eventData)
|
|||||||
end
|
end
|
||||||
|
|
||||||
function kernel.start()
|
function kernel.start()
|
||||||
local s, m = pcall(function()
|
local s, m
|
||||||
|
local s2, m2 = pcall(function()
|
||||||
repeat
|
repeat
|
||||||
local eventData = { os.pullEventRaw() }
|
local eventData = { os.pullEventRaw() }
|
||||||
local event = table.remove(eventData, 1)
|
local event = table.remove(eventData, 1)
|
||||||
kernel.event(event, eventData)
|
kernel.event(event, eventData)
|
||||||
|
if event == 'kernel_halt' then
|
||||||
|
s = eventData[1]
|
||||||
|
m = eventData[2]
|
||||||
|
end
|
||||||
until event == 'kernel_halt'
|
until event == 'kernel_halt'
|
||||||
end)
|
end)
|
||||||
|
|
||||||
if not s then
|
if (not s and m) or (not s2 and m2) then
|
||||||
kernel.window.setVisible(true)
|
kernel.window.setVisible(true)
|
||||||
term.redirect(kernel.window)
|
term.redirect(kernel.window)
|
||||||
print('\nCrash detected\n')
|
print('\nCrash detected\n')
|
||||||
_G.printError(m)
|
_G.printError(m or m2)
|
||||||
end
|
end
|
||||||
term.redirect(kernel.terminal)
|
term.redirect(kernel.terminal)
|
||||||
end
|
end
|
||||||
@@ -295,9 +313,10 @@ local function init(...)
|
|||||||
for _,file in ipairs(files) do
|
for _,file in ipairs(files) do
|
||||||
local level = file:match('(%d).%S+.lua') or 99
|
local level = file:match('(%d).%S+.lua') or 99
|
||||||
if tonumber(level) <= runLevel then
|
if tonumber(level) <= runLevel then
|
||||||
|
-- All init programs run under the original shell
|
||||||
local s, m = shell.run(fs.combine(dir, file))
|
local s, m = shell.run(fs.combine(dir, file))
|
||||||
if not s then
|
if not s then
|
||||||
error(m)
|
error(m, -1)
|
||||||
end
|
end
|
||||||
os.sleep(0)
|
os.sleep(0)
|
||||||
end
|
end
|
||||||
@@ -311,15 +330,15 @@ local function init(...)
|
|||||||
term.redirect(kernel.window)
|
term.redirect(kernel.window)
|
||||||
shell.run('sys/apps/autorun.lua')
|
shell.run('sys/apps/autorun.lua')
|
||||||
|
|
||||||
local shellWindow = window.create(kernel.terminal, 1, 1, w, h, false)
|
local win = window.create(kernel.terminal, 1, 1, w, h, true)
|
||||||
local s, m = kernel.run({
|
local s, m = kernel.run(_ENV, {
|
||||||
title = args[1],
|
title = args[1],
|
||||||
path = 'sys/apps/shell.lua',
|
path = 'sys/apps/shell.lua',
|
||||||
args = args,
|
args = args,
|
||||||
haltOnExit = true,
|
window = win,
|
||||||
haltOnError = true,
|
onExit = function(_, s, m)
|
||||||
terminal = shellWindow,
|
kernel.halt(s, m)
|
||||||
window = shellWindow,
|
end,
|
||||||
})
|
})
|
||||||
if s then
|
if s then
|
||||||
kernel.raise(s.uid)
|
kernel.raise(s.uid)
|
||||||
@@ -330,11 +349,15 @@ local function init(...)
|
|||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
kernel.run({
|
kernel.run(_ENV, {
|
||||||
fn = init,
|
fn = init,
|
||||||
title = 'init',
|
title = 'init',
|
||||||
haltOnError = true,
|
|
||||||
args = { ... },
|
args = { ... },
|
||||||
|
onExit = function(_, status, message)
|
||||||
|
if not status then
|
||||||
|
kernel.halt(status, message)
|
||||||
|
end
|
||||||
|
end,
|
||||||
})
|
})
|
||||||
|
|
||||||
kernel.start()
|
kernel.start()
|
||||||
|
|||||||
@@ -1,48 +0,0 @@
|
|||||||
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
|
|
||||||
@@ -1,3 +1,5 @@
|
|||||||
|
local Util = require('opus.util')
|
||||||
|
|
||||||
local Array = { }
|
local Array = { }
|
||||||
|
|
||||||
function Array.filter(it, f)
|
function Array.filter(it, f)
|
||||||
@@ -14,9 +16,11 @@ function Array.removeByValue(t, e)
|
|||||||
for k,v in pairs(t) do
|
for k,v in pairs(t) do
|
||||||
if v == e then
|
if v == e then
|
||||||
table.remove(t, k)
|
table.remove(t, k)
|
||||||
break
|
return e
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
Array.find = Util.find
|
||||||
|
|
||||||
return Array
|
return Array
|
||||||
|
|||||||
142
sys/modules/opus/compress/lzw.lua
Normal file
142
sys/modules/opus/compress/lzw.lua
Normal file
@@ -0,0 +1,142 @@
|
|||||||
|
-- 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,
|
||||||
|
}
|
||||||
270
sys/modules/opus/compress/tar.lua
Normal file
270
sys/modules/opus/compress/tar.lua
Normal file
@@ -0,0 +1,270 @@
|
|||||||
|
|
||||||
|
-- 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,
|
||||||
|
}
|
||||||
@@ -1,7 +1,6 @@
|
|||||||
local Util = require('opus.util')
|
local Util = require('opus.util')
|
||||||
|
|
||||||
local fs = _G.fs
|
local fs = _G.fs
|
||||||
local shell = _ENV.shell
|
|
||||||
|
|
||||||
local Config = { }
|
local Config = { }
|
||||||
|
|
||||||
@@ -25,23 +24,6 @@ function Config.load(fname, data)
|
|||||||
return data
|
return data
|
||||||
end
|
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)
|
function Config.update(fname, data)
|
||||||
local filename = 'usr/config/' .. fname
|
local filename = 'usr/config/' .. fname
|
||||||
Util.writeTable(filename, data)
|
Util.writeTable(filename, data)
|
||||||
|
|||||||
@@ -100,7 +100,7 @@ local function crypt(data, key, nonce, cntr, round)
|
|||||||
cntr = tonumber(cntr) or 1
|
cntr = tonumber(cntr) or 1
|
||||||
round = tonumber(round) or 20
|
round = tonumber(round) or 20
|
||||||
|
|
||||||
local throttle = Util.throttle(function() _syslog('throttle') end)
|
local throttle = Util.throttle()
|
||||||
local out = {}
|
local out = {}
|
||||||
local state = initState(key, nonce, cntr)
|
local state = initState(key, nonce, cntr)
|
||||||
local blockAmt = math.floor(#data/64)
|
local blockAmt = math.floor(#data/64)
|
||||||
|
|||||||
@@ -41,16 +41,21 @@ end
|
|||||||
|
|
||||||
function Entry:updateScroll()
|
function Entry:updateScroll()
|
||||||
local ps = self.scroll
|
local ps = self.scroll
|
||||||
local value = _val(self.value)
|
local len = #_val(self.value)
|
||||||
if self.pos > #value then
|
if self.pos > len then
|
||||||
self.pos = #value
|
self.pos = len
|
||||||
self.scroll = 0 -- ??
|
self.scroll = 0 -- ??
|
||||||
end
|
end
|
||||||
if self.pos - self.scroll > self.width then
|
if self.pos - self.scroll > self.width then
|
||||||
self.scroll = self.pos - self.width
|
self.scroll = math.max(0, self.pos - self.width)
|
||||||
elseif self.pos < self.scroll then
|
elseif self.pos < self.scroll then
|
||||||
self.scroll = self.pos
|
self.scroll = self.pos
|
||||||
end
|
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
|
if ps ~= self.scroll then
|
||||||
self.textChanged = true
|
self.textChanged = true
|
||||||
end
|
end
|
||||||
@@ -217,6 +222,10 @@ function Entry:paste(ie)
|
|||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
function Entry.forcePaste()
|
||||||
|
os.queueEvent('clipboard_paste')
|
||||||
|
end
|
||||||
|
|
||||||
function Entry:clearLine()
|
function Entry:clearLine()
|
||||||
if #_val(self.value) > 0 then
|
if #_val(self.value) > 0 then
|
||||||
self:reset()
|
self:reset()
|
||||||
@@ -225,7 +234,9 @@ end
|
|||||||
|
|
||||||
function Entry:markBegin()
|
function Entry:markBegin()
|
||||||
if not self.mark.active then
|
if not self.mark.active then
|
||||||
self.mark.active = true
|
if #_val(self.value) > 0 then
|
||||||
|
self.mark.active = true
|
||||||
|
end
|
||||||
self.mark.anchor = { x = self.pos }
|
self.mark.anchor = { x = self.pos }
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
@@ -262,6 +273,8 @@ function Entry:markLeft()
|
|||||||
self:markBegin()
|
self:markBegin()
|
||||||
if self:moveLeft() then
|
if self:moveLeft() then
|
||||||
self:markFinish()
|
self:markFinish()
|
||||||
|
else
|
||||||
|
self.mark.continue = self.mark.active
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
@@ -269,6 +282,8 @@ function Entry:markRight()
|
|||||||
self:markBegin()
|
self:markBegin()
|
||||||
if self:moveRight() then
|
if self:moveRight() then
|
||||||
self:markFinish()
|
self:markFinish()
|
||||||
|
else
|
||||||
|
self.mark.continue = self.mark.active
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
@@ -296,6 +311,8 @@ function Entry:markNextWord()
|
|||||||
self:markBegin()
|
self:markBegin()
|
||||||
if self:moveWordRight() then
|
if self:moveWordRight() then
|
||||||
self:markFinish()
|
self:markFinish()
|
||||||
|
else
|
||||||
|
self.mark.continue = self.mark.active
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
@@ -303,6 +320,8 @@ function Entry:markPrevWord()
|
|||||||
self:markBegin()
|
self:markBegin()
|
||||||
if self:moveWordLeft() then
|
if self:moveWordLeft() then
|
||||||
self:markFinish()
|
self:markFinish()
|
||||||
|
else
|
||||||
|
self.mark.continue = self.mark.active
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
@@ -321,6 +340,8 @@ function Entry:markHome()
|
|||||||
self:markBegin()
|
self:markBegin()
|
||||||
if self:moveHome() then
|
if self:moveHome() then
|
||||||
self:markFinish()
|
self:markFinish()
|
||||||
|
else
|
||||||
|
self.mark.continue = self.mark.active
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
@@ -328,6 +349,8 @@ function Entry:markEnd()
|
|||||||
self:markBegin()
|
self:markBegin()
|
||||||
if self:moveEnd() then
|
if self:moveEnd() then
|
||||||
self:markFinish()
|
self:markFinish()
|
||||||
|
else
|
||||||
|
self.mark.continue = self.mark.active
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
@@ -363,9 +386,10 @@ local mappings = {
|
|||||||
--[ 'control-d' ] = Entry.cutNextWord,
|
--[ 'control-d' ] = Entry.cutNextWord,
|
||||||
[ 'control-x' ] = Entry.cut,
|
[ 'control-x' ] = Entry.cut,
|
||||||
[ 'paste' ] = Entry.paste,
|
[ 'paste' ] = Entry.paste,
|
||||||
-- [ 'control-y' ] = Entry.paste, -- well this won't work...
|
[ 'control-y' ] = Entry.forcePaste, -- well this won't work...
|
||||||
|
|
||||||
[ 'mouse_doubleclick' ] = Entry.markWord,
|
[ 'mouse_doubleclick' ] = Entry.markWord,
|
||||||
|
[ 'mouse_tripleclick' ] = Entry.markAll,
|
||||||
[ 'shift-left' ] = Entry.markLeft,
|
[ 'shift-left' ] = Entry.markLeft,
|
||||||
[ 'shift-right' ] = Entry.markRight,
|
[ 'shift-right' ] = Entry.markRight,
|
||||||
[ 'mouse_down' ] = Entry.markAnchor,
|
[ 'mouse_down' ] = Entry.markAnchor,
|
||||||
|
|||||||
@@ -58,6 +58,14 @@ function Routine:resume(event, ...)
|
|||||||
else
|
else
|
||||||
s, m = coroutine.resume(self.co, event, ...)
|
s, m = coroutine.resume(self.co, event, ...)
|
||||||
end
|
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
|
if self:isDead() then
|
||||||
self.co = nil
|
self.co = nil
|
||||||
self.filter = nil
|
self.filter = nil
|
||||||
|
|||||||
@@ -4,17 +4,21 @@ local linkfs = { }
|
|||||||
|
|
||||||
-- TODO: implement broken links
|
-- TODO: implement broken links
|
||||||
|
|
||||||
local methods = { 'exists', 'getFreeSpace', 'getSize',
|
local methods = { 'exists', 'getFreeSpace', 'getSize', 'attributes',
|
||||||
'isDir', 'isReadOnly', 'list', 'listEx', 'makeDir', 'open', 'getDrive' }
|
'isDir', 'isReadOnly', 'list', 'makeDir', 'open', 'getDrive' }
|
||||||
|
|
||||||
for _,m in pairs(methods) do
|
for _,m in pairs(methods) do
|
||||||
linkfs[m] = function(node, dir, ...)
|
linkfs[m] = function(node, dir, ...)
|
||||||
dir = dir:gsub(node.mountPoint, node.source, 1)
|
dir = linkfs.resolve(node, dir)
|
||||||
return fs[m](dir, ...)
|
return fs[m](dir, ...)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
function linkfs.mount(_, source)
|
function linkfs.resolve(node, dir)
|
||||||
|
return dir:gsub(node.mountPoint, node.source, 1)
|
||||||
|
end
|
||||||
|
|
||||||
|
function linkfs.mount(path, source)
|
||||||
if not source then
|
if not source then
|
||||||
error('Source is required')
|
error('Source is required')
|
||||||
end
|
end
|
||||||
@@ -22,6 +26,9 @@ function linkfs.mount(_, source)
|
|||||||
if not fs.exists(source) then
|
if not fs.exists(source) then
|
||||||
error('Source is missing')
|
error('Source is missing')
|
||||||
end
|
end
|
||||||
|
if path == source then
|
||||||
|
return
|
||||||
|
end
|
||||||
if fs.isDir(source) then
|
if fs.isDir(source) then
|
||||||
return {
|
return {
|
||||||
source = source,
|
source = source,
|
||||||
|
|||||||
@@ -6,7 +6,6 @@ local fs = _G.fs
|
|||||||
local netfs = { }
|
local netfs = { }
|
||||||
|
|
||||||
local function remoteCommand(node, msg)
|
local function remoteCommand(node, msg)
|
||||||
|
|
||||||
for _ = 1, 2 do
|
for _ = 1, 2 do
|
||||||
if not node.socket then
|
if not node.socket then
|
||||||
node.socket = Socket.connect(node.id, 139)
|
node.socket = Socket.connect(node.id, 139)
|
||||||
@@ -33,17 +32,17 @@ local function remoteCommand(node, msg)
|
|||||||
error('netfs: Connection failed', 2)
|
error('netfs: Connection failed', 2)
|
||||||
end
|
end
|
||||||
|
|
||||||
local methods = { 'delete', 'exists', 'getFreeSpace', 'makeDir', 'list', 'listEx' }
|
local methods = { 'delete', 'exists', 'getFreeSpace', 'makeDir', 'list', 'listEx', 'attributes' }
|
||||||
|
|
||||||
local function resolveDir(dir, node)
|
local function resolve(node, dir)
|
||||||
-- TODO: Wrong ! (does not support names with dashes)
|
-- TODO: Wrong ! (does not support names with dashes)
|
||||||
dir = dir:gsub(node.mountPoint, '', 1)
|
dir = dir:gsub(node.mountPoint, '', 1)
|
||||||
return fs.combine(node.directory, dir)
|
return fs.combine(node.source, dir)
|
||||||
end
|
end
|
||||||
|
|
||||||
for _,m in pairs(methods) do
|
for _,m in pairs(methods) do
|
||||||
netfs[m] = function(node, dir)
|
netfs[m] = function(node, dir)
|
||||||
dir = resolveDir(dir, node)
|
dir = resolve(node, dir)
|
||||||
|
|
||||||
return remoteCommand(node, {
|
return remoteCommand(node, {
|
||||||
fn = m,
|
fn = m,
|
||||||
@@ -52,14 +51,14 @@ for _,m in pairs(methods) do
|
|||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
function netfs.mount(_, id, directory)
|
function netfs.mount(_, id, source)
|
||||||
if not id or not tonumber(id) then
|
if not id or not tonumber(id) then
|
||||||
error('ramfs syntax: computerId [directory]')
|
error('ramfs syntax: computerId [directory]')
|
||||||
end
|
end
|
||||||
return {
|
return {
|
||||||
id = tonumber(id),
|
id = tonumber(id),
|
||||||
nodes = { },
|
nodes = { },
|
||||||
directory = directory or '',
|
source = source or '',
|
||||||
}
|
}
|
||||||
end
|
end
|
||||||
|
|
||||||
@@ -68,7 +67,7 @@ function netfs.getDrive()
|
|||||||
end
|
end
|
||||||
|
|
||||||
function netfs.complete(node, partial, dir, includeFiles, includeSlash)
|
function netfs.complete(node, partial, dir, includeFiles, includeSlash)
|
||||||
dir = resolveDir(dir, node)
|
dir = resolve(node, dir)
|
||||||
|
|
||||||
return remoteCommand(node, {
|
return remoteCommand(node, {
|
||||||
fn = 'complete',
|
fn = 'complete',
|
||||||
@@ -77,8 +76,8 @@ function netfs.complete(node, partial, dir, includeFiles, includeSlash)
|
|||||||
end
|
end
|
||||||
|
|
||||||
function netfs.copy(node, s, t)
|
function netfs.copy(node, s, t)
|
||||||
s = resolveDir(s, node)
|
s = resolve(node, s)
|
||||||
t = resolveDir(t, node)
|
t = resolve(node, t)
|
||||||
|
|
||||||
return remoteCommand(node, {
|
return remoteCommand(node, {
|
||||||
fn = 'copy',
|
fn = 'copy',
|
||||||
@@ -87,37 +86,37 @@ function netfs.copy(node, s, t)
|
|||||||
end
|
end
|
||||||
|
|
||||||
function netfs.isDir(node, dir)
|
function netfs.isDir(node, dir)
|
||||||
if dir == node.mountPoint and node.directory == '' then
|
if dir == node.mountPoint and node.source == '' then
|
||||||
return true
|
return true
|
||||||
end
|
end
|
||||||
return remoteCommand(node, {
|
return remoteCommand(node, {
|
||||||
fn = 'isDir',
|
fn = 'isDir',
|
||||||
args = { resolveDir(dir, node) },
|
args = { resolve(node, dir) },
|
||||||
})
|
})
|
||||||
end
|
end
|
||||||
|
|
||||||
function netfs.isReadOnly(node, dir)
|
function netfs.isReadOnly(node, dir)
|
||||||
if dir == node.mountPoint and node.directory == '' then
|
if dir == node.mountPoint and node.source == '' then
|
||||||
return false
|
return false
|
||||||
end
|
end
|
||||||
return remoteCommand(node, {
|
return remoteCommand(node, {
|
||||||
fn = 'isReadOnly',
|
fn = 'isReadOnly',
|
||||||
args = { resolveDir(dir, node) },
|
args = { resolve(node, dir) },
|
||||||
})
|
})
|
||||||
end
|
end
|
||||||
|
|
||||||
function netfs.getSize(node, dir)
|
function netfs.getSize(node, dir)
|
||||||
if dir == node.mountPoint and node.directory == '' then
|
if dir == node.mountPoint and node.source == '' then
|
||||||
return 0
|
return 0
|
||||||
end
|
end
|
||||||
return remoteCommand(node, {
|
return remoteCommand(node, {
|
||||||
fn = 'getSize',
|
fn = 'getSize',
|
||||||
args = { resolveDir(dir, node) },
|
args = { resolve(node, dir) },
|
||||||
})
|
})
|
||||||
end
|
end
|
||||||
|
|
||||||
function netfs.find(node, spec)
|
function netfs.find(node, spec)
|
||||||
spec = resolveDir(spec, node)
|
spec = resolve(node, spec)
|
||||||
local list = remoteCommand(node, {
|
local list = remoteCommand(node, {
|
||||||
fn = 'find',
|
fn = 'find',
|
||||||
args = { spec },
|
args = { spec },
|
||||||
@@ -131,8 +130,8 @@ function netfs.find(node, spec)
|
|||||||
end
|
end
|
||||||
|
|
||||||
function netfs.move(node, s, t)
|
function netfs.move(node, s, t)
|
||||||
s = resolveDir(s, node)
|
s = resolve(node, s)
|
||||||
t = resolveDir(t, node)
|
t = resolve(node, t)
|
||||||
|
|
||||||
return remoteCommand(node, {
|
return remoteCommand(node, {
|
||||||
fn = 'move',
|
fn = 'move',
|
||||||
@@ -141,7 +140,7 @@ function netfs.move(node, s, t)
|
|||||||
end
|
end
|
||||||
|
|
||||||
function netfs.open(node, fn, fl)
|
function netfs.open(node, fn, fl)
|
||||||
fn = resolveDir(fn, node)
|
fn = resolve(node, fn)
|
||||||
|
|
||||||
local vfh = remoteCommand(node, {
|
local vfh = remoteCommand(node, {
|
||||||
fn = 'open',
|
fn = 'open',
|
||||||
|
|||||||
@@ -9,15 +9,28 @@ function ramfs.mount(_, nodeType)
|
|||||||
return {
|
return {
|
||||||
nodes = { },
|
nodes = { },
|
||||||
size = 0,
|
size = 0,
|
||||||
|
created = os.epoch('utc'),
|
||||||
|
modification = os.epoch('utc'),
|
||||||
}
|
}
|
||||||
elseif nodeType == 'file' then
|
elseif nodeType == 'file' then
|
||||||
return {
|
return {
|
||||||
size = 0,
|
size = 0,
|
||||||
|
created = os.epoch('utc'),
|
||||||
|
modification = os.epoch('utc'),
|
||||||
}
|
}
|
||||||
end
|
end
|
||||||
error('ramfs syntax: [directory, file]')
|
error('ramfs syntax: [directory, file]')
|
||||||
end
|
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)
|
function ramfs.delete(node, dir)
|
||||||
if node.mountPoint == dir then
|
if node.mountPoint == dir then
|
||||||
fs.unmount(node.mountPoint)
|
fs.unmount(node.mountPoint)
|
||||||
@@ -40,8 +53,10 @@ function ramfs.makeDir(_, dir)
|
|||||||
fs.mount(dir, 'ramfs', 'directory')
|
fs.mount(dir, 'ramfs', 'directory')
|
||||||
end
|
end
|
||||||
|
|
||||||
function ramfs.isDir(node)
|
function ramfs.isDir(node, dir)
|
||||||
return not not node.nodes
|
if node.mountPoint == dir then
|
||||||
|
return not not node.nodes
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
function ramfs.getDrive()
|
function ramfs.getDrive()
|
||||||
@@ -64,32 +79,70 @@ function ramfs.list(node, dir)
|
|||||||
end
|
end
|
||||||
|
|
||||||
function ramfs.open(node, fn, fl)
|
function ramfs.open(node, fn, fl)
|
||||||
|
local modes = Util.transpose { 'r', 'w', 'rb', 'wb', 'a' }
|
||||||
if fl ~= 'r' and fl ~= 'w' and fl ~= 'rb' and fl ~= 'wb' then
|
if not modes[fl] then
|
||||||
error('Unsupported mode')
|
error('Unsupported mode')
|
||||||
end
|
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 fl == 'r' then
|
||||||
if node.mountPoint ~= fn then
|
if node.mountPoint ~= fn then
|
||||||
return
|
return
|
||||||
end
|
end
|
||||||
|
|
||||||
|
local c = type(node.contents) == 'table'
|
||||||
|
and string.char(table.unpack(node.contents))
|
||||||
|
or node.contents
|
||||||
|
|
||||||
local ctr = 0
|
local ctr = 0
|
||||||
local lines
|
local lines
|
||||||
return {
|
return {
|
||||||
read = function()
|
read = function(n)
|
||||||
ctr = ctr + 1
|
n = n or 1
|
||||||
return node.contents:sub(ctr, ctr)
|
if ctr >= node.size then
|
||||||
|
return
|
||||||
|
end
|
||||||
|
local t = c:sub(ctr + 1, ctr + n)
|
||||||
|
ctr = ctr + n
|
||||||
|
return t
|
||||||
end,
|
end,
|
||||||
readLine = function()
|
readLine = function()
|
||||||
if not lines then
|
if not lines then
|
||||||
lines = Util.split(node.contents)
|
lines = Util.split(c)
|
||||||
end
|
end
|
||||||
ctr = ctr + 1
|
ctr = ctr + 1
|
||||||
return lines[ctr]
|
return lines[ctr]
|
||||||
end,
|
end,
|
||||||
readAll = function()
|
readAll = function()
|
||||||
return node.contents
|
return c
|
||||||
end,
|
end,
|
||||||
close = function()
|
close = function()
|
||||||
lines = nil
|
lines = nil
|
||||||
@@ -121,11 +174,30 @@ function ramfs.open(node, fn, fl)
|
|||||||
return
|
return
|
||||||
end
|
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
|
local ctr = 0
|
||||||
return {
|
return {
|
||||||
read = function()
|
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
|
||||||
ctr = ctr + 1
|
ctr = ctr + 1
|
||||||
return node.contents[ctr]
|
return c[ctr]
|
||||||
end,
|
end,
|
||||||
close = function()
|
close = function()
|
||||||
end,
|
end,
|
||||||
@@ -137,7 +209,13 @@ function ramfs.open(node, fn, fl)
|
|||||||
local c = { }
|
local c = { }
|
||||||
return {
|
return {
|
||||||
write = function(b)
|
write = function(b)
|
||||||
table.insert(c, 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
|
||||||
end,
|
end,
|
||||||
flush = function()
|
flush = function()
|
||||||
node.contents = c
|
node.contents = c
|
||||||
|
|||||||
@@ -5,29 +5,46 @@ local fs = _G.fs
|
|||||||
|
|
||||||
local urlfs = { }
|
local urlfs = { }
|
||||||
|
|
||||||
function urlfs.mount(_, url)
|
function urlfs.mount(path, url, force)
|
||||||
if not url then
|
if not url then
|
||||||
error('URL is required')
|
error('URL is required')
|
||||||
end
|
end
|
||||||
return {
|
|
||||||
url = url,
|
-- 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,
|
||||||
}
|
}
|
||||||
end
|
end
|
||||||
|
|
||||||
function urlfs.delete(_, dir)
|
function urlfs.delete(node, path)
|
||||||
fs.unmount(dir)
|
if path == node.mountPoint then
|
||||||
|
fs.unmount(path)
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
function urlfs.exists()
|
function urlfs.exists(node, path)
|
||||||
return true
|
return path == node.mountPoint
|
||||||
end
|
end
|
||||||
|
|
||||||
function urlfs.getSize(node)
|
function urlfs.getSize(node, path)
|
||||||
return node.size or 0
|
return path == node.mountPoint and node.size or 0
|
||||||
end
|
end
|
||||||
|
|
||||||
function urlfs.isReadOnly()
|
function urlfs.isReadOnly()
|
||||||
return true
|
return false
|
||||||
end
|
end
|
||||||
|
|
||||||
function urlfs.isDir()
|
function urlfs.isDir()
|
||||||
@@ -50,14 +67,6 @@ function urlfs.open(node, fn, fl)
|
|||||||
|
|
||||||
local c = node.cache
|
local c = node.cache
|
||||||
if not c then
|
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)
|
c = Util.httpGet(node.url)
|
||||||
if c then
|
if c then
|
||||||
node.cache = c
|
node.cache = c
|
||||||
@@ -94,6 +103,9 @@ function urlfs.open(node, fn, fl)
|
|||||||
}
|
}
|
||||||
end
|
end
|
||||||
return {
|
return {
|
||||||
|
readAll = function()
|
||||||
|
return c
|
||||||
|
end,
|
||||||
read = function()
|
read = function()
|
||||||
ctr = ctr + 1
|
ctr = ctr + 1
|
||||||
return c:sub(ctr, ctr):byte()
|
return c:sub(ctr, ctr):byte()
|
||||||
|
|||||||
56
sys/modules/opus/fuzzy.lua
Normal file
56
sys/modules/opus/fuzzy.lua
Normal file
@@ -0,0 +1,56 @@
|
|||||||
|
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
|
||||||
@@ -24,9 +24,9 @@ function git.list(repository)
|
|||||||
|
|
||||||
local function getContents()
|
local function getContents()
|
||||||
local dataUrl = string.format(TREE_URL, user, repo, branch)
|
local dataUrl = string.format(TREE_URL, user, repo, branch)
|
||||||
local contents, msg = Util.httpGet(dataUrl,TREE_HEADERS)
|
local contents, msg = Util.httpGet(dataUrl, TREE_HEADERS)
|
||||||
if not contents then
|
if not contents then
|
||||||
error(_sformat('Failed to download %s\n%s', dataUrl, msg), 2)
|
error(string.format('Failed to download %s\n%s', dataUrl, msg), 2)
|
||||||
else
|
else
|
||||||
return json.decode(contents)
|
return json.decode(contents)
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -20,13 +20,12 @@ function GPS.locate(timeout, debug)
|
|||||||
|
|
||||||
local modem = device.wireless_modem
|
local modem = device.wireless_modem
|
||||||
local closeChannel = false
|
local closeChannel = false
|
||||||
local selfID = os.getComputerID()
|
if not modem.isOpen(GPS.CHANNEL_GPS) then
|
||||||
if not modem.isOpen(selfID) then
|
modem.open(GPS.CHANNEL_GPS)
|
||||||
modem.open(selfID)
|
|
||||||
closeChannel = true
|
closeChannel = true
|
||||||
end
|
end
|
||||||
|
|
||||||
modem.transmit(GPS.CHANNEL_GPS, selfID, "PING")
|
modem.transmit(GPS.CHANNEL_GPS, GPS.CHANNEL_GPS, "PING")
|
||||||
|
|
||||||
local fixes = {}
|
local fixes = {}
|
||||||
local pos = nil
|
local pos = nil
|
||||||
@@ -34,7 +33,7 @@ function GPS.locate(timeout, debug)
|
|||||||
while true do
|
while true do
|
||||||
local e, side, chan, reply, msg, dist = os.pullEvent()
|
local e, side, chan, reply, msg, dist = os.pullEvent()
|
||||||
if e == "modem_message" then
|
if e == "modem_message" then
|
||||||
if side == modem.side and chan == selfID and reply == GPS.CHANNEL_GPS and dist 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
|
if type(msg) == "table" and #msg == 3 and tonumber(msg[1]) and tonumber(msg[2]) and tonumber(msg[3]) then
|
||||||
local fix = {
|
local fix = {
|
||||||
position = vector.new(unpack(msg)),
|
position = vector.new(unpack(msg)),
|
||||||
@@ -60,12 +59,12 @@ function GPS.locate(timeout, debug)
|
|||||||
end
|
end
|
||||||
|
|
||||||
if closeChannel then
|
if closeChannel then
|
||||||
modem.close(selfID)
|
modem.close(GPS.CHANNEL_GPS)
|
||||||
end
|
end
|
||||||
if debug then
|
if debug then
|
||||||
print("Position is "..pos.x..","..pos.y..","..pos.z)
|
print("Position is "..pos.x..","..pos.y..","..pos.z)
|
||||||
end
|
end
|
||||||
return vector.new(pos.x, pos.y, pos.z)
|
return pos and vector.new(pos.x, pos.y, pos.z)
|
||||||
end
|
end
|
||||||
|
|
||||||
function GPS.isAvailable()
|
function GPS.isAvailable()
|
||||||
|
|||||||
@@ -1,40 +1,51 @@
|
|||||||
local function split(str, pattern)
|
-- https://www.lua.org/manual/5.1/manual.html#pdf-require
|
||||||
local t = { }
|
-- https://github.com/LuaDist/lua/blob/d2e7e7d4d43ff9068b279a617c5b2ca2c2771676/src/loadlib.c
|
||||||
local function helper(line) table.insert(t, line) return "" end
|
|
||||||
helper((str:gsub(pattern, helper)))
|
|
||||||
return t
|
|
||||||
end
|
|
||||||
|
|
||||||
local hasMain
|
local defaultPath = { }
|
||||||
local luaPaths = package and package.path and split(package.path, '(.-);') or { }
|
|
||||||
for i = 1, #luaPaths do
|
do
|
||||||
if luaPaths[i] == '?' or luaPaths[i] == '?.lua' or luaPaths[i] == '?/init.lua' then
|
local function split(str)
|
||||||
luaPaths[i] = nil
|
local t = { }
|
||||||
elseif string.find(luaPaths[i], '/rom/modules/main') then
|
local function helper(line) table.insert(t, line) return "" end
|
||||||
hasMain = true
|
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
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
table.insert(luaPaths, 1, '?.lua')
|
local DEFAULT_PATH = table.concat(defaultPath, ';')
|
||||||
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 fs = _G.fs
|
||||||
local os = _G.os
|
local os = _G.os
|
||||||
local string = _G.string
|
local string = _G.string
|
||||||
|
|
||||||
-- Add require and package to the environment
|
-- Add require and package to the environment
|
||||||
return function(env)
|
return function(env, programDir)
|
||||||
local function preloadSearcher(modname)
|
local function preloadSearcher(modname)
|
||||||
if env.package.preload[modname] then
|
if env.package.preload[modname] then
|
||||||
return function()
|
return function()
|
||||||
@@ -43,77 +54,75 @@ return function(env)
|
|||||||
end
|
end
|
||||||
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)
|
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('%.', '/')
|
local fname = modname:gsub('%.', '/')
|
||||||
|
|
||||||
for pattern in string.gmatch(env.package.path, "[^;]+") do
|
for pattern in string.gmatch(env.package.path, "[^;]+") do
|
||||||
local sPath = string.gsub(pattern, "%?", fname)
|
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
|
|
||||||
|
|
||||||
sPath = fs.combine(fs.getDir(env.shell.getRunningProgram() or ''), sPath)
|
if programDir and sPath:sub(1, 1) ~= "/" then
|
||||||
|
sPath = fs.combine(programDir, sPath)
|
||||||
end
|
end
|
||||||
if fs.exists(sPath) and not fs.isDir(sPath) then
|
if fs.exists(sPath) and not fs.isDir(sPath) then
|
||||||
return loadfile(sPath, env)
|
return loadfile(fs.combine(sPath, ''), env)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
-- place package and require function into env
|
-- place package and require function into env
|
||||||
env.package = {
|
env.package = {
|
||||||
path = env.LUA_PATH or _G.LUA_PATH or DEFAULT_PATH,
|
path = env.LUA_PATH or _G.LUA_PATH or DEFAULT_PATH,
|
||||||
config = '/\n:\n?\n!\n-',
|
cpath = '',
|
||||||
|
config = '/\n:\n?\n!\n-',
|
||||||
preload = { },
|
preload = { },
|
||||||
loaded = {
|
loaded = {
|
||||||
|
bit32 = bit32,
|
||||||
coroutine = coroutine,
|
coroutine = coroutine,
|
||||||
|
_G = env._G,
|
||||||
io = io,
|
io = io,
|
||||||
math = math,
|
math = math,
|
||||||
os = os,
|
os = os,
|
||||||
string = string,
|
string = string,
|
||||||
table = table,
|
table = table,
|
||||||
|
debug = debug,
|
||||||
|
utf8 = utf8,
|
||||||
},
|
},
|
||||||
loaders = {
|
loaders = {
|
||||||
preloadSearcher,
|
preloadSearcher,
|
||||||
loadedSearcher,
|
|
||||||
pathSearcher,
|
pathSearcher,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
env.package.loaded.package = env.package
|
||||||
|
|
||||||
|
local sentinel = { }
|
||||||
|
|
||||||
function env.require(modname)
|
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
|
for _,searcher in ipairs(env.package.loaders) do
|
||||||
local fn, msg = searcher(modname)
|
local fn, msg = searcher(modname)
|
||||||
if fn then
|
if type(fn) == 'function' then
|
||||||
local module, msg2 = fn(modname, env)
|
env.package.loaded[modname] = sentinel
|
||||||
if not module then
|
|
||||||
error(msg2 or (modname .. ' module returned nil'), 2)
|
local module = fn(modname, env) or true
|
||||||
end
|
|
||||||
env.package.loaded[modname] = module
|
env.package.loaded[modname] = module
|
||||||
return module
|
return module
|
||||||
end
|
end
|
||||||
if msg then
|
if msg then
|
||||||
error(msg, 2)
|
table.insert(t, msg)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
if #t > 0 then
|
||||||
|
error(table.concat(t, '\n'), 2)
|
||||||
|
end
|
||||||
error('Unable to find module ' .. modname, 2)
|
error('Unable to find module ' .. modname, 2)
|
||||||
end
|
end
|
||||||
|
|
||||||
return env.require -- backwards compatible
|
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -50,18 +50,20 @@ function input:toCode(ch, code)
|
|||||||
table.insert(result, 'alt')
|
table.insert(result, 'alt')
|
||||||
end
|
end
|
||||||
|
|
||||||
if keyboard.state[keys.leftShift] or keyboard.state[keys.rightShift] or
|
if ch then -- some weird things happen with control/command on mac
|
||||||
code == keys.leftShift or code == keys.rightShift then
|
if keyboard.state[keys.leftShift] or keyboard.state[keys.rightShift] or
|
||||||
if code and modifiers[code] then
|
code == keys.leftShift or code == keys.rightShift then
|
||||||
table.insert(result, 'shift')
|
if code and modifiers[code] then
|
||||||
elseif #ch == 1 then
|
table.insert(result, 'shift')
|
||||||
table.insert(result, ch:upper())
|
elseif #ch == 1 then
|
||||||
else
|
table.insert(result, ch:upper())
|
||||||
table.insert(result, 'shift')
|
else
|
||||||
|
table.insert(result, 'shift')
|
||||||
|
table.insert(result, ch)
|
||||||
|
end
|
||||||
|
elseif not code or not modifiers[code] then
|
||||||
table.insert(result, ch)
|
table.insert(result, ch)
|
||||||
end
|
end
|
||||||
elseif not code or not modifiers[code] then
|
|
||||||
table.insert(result, ch)
|
|
||||||
end
|
end
|
||||||
|
|
||||||
return table.concat(result, '-')
|
return table.concat(result, '-')
|
||||||
@@ -118,6 +120,7 @@ function input:translate(event, code, p1, p2)
|
|||||||
local buttons = { 'mouse_click', 'mouse_rightclick' }
|
local buttons = { 'mouse_click', 'mouse_rightclick' }
|
||||||
self.mch = buttons[code]
|
self.mch = buttons[code]
|
||||||
self.mfired = nil
|
self.mfired = nil
|
||||||
|
self.anchor = { x = p1, y = p2 }
|
||||||
return {
|
return {
|
||||||
code = input:toCode('mouse_down', 255),
|
code = input:toCode('mouse_down', 255),
|
||||||
button = code,
|
button = code,
|
||||||
@@ -132,6 +135,8 @@ function input:translate(event, code, p1, p2)
|
|||||||
button = code,
|
button = code,
|
||||||
x = p1,
|
x = p1,
|
||||||
y = p2,
|
y = p2,
|
||||||
|
dx = p1 - self.anchor.x,
|
||||||
|
dy = p2 - self.anchor.y,
|
||||||
}
|
}
|
||||||
|
|
||||||
elseif event == 'mouse_up' then
|
elseif event == 'mouse_up' then
|
||||||
@@ -141,18 +146,26 @@ function input:translate(event, code, p1, p2)
|
|||||||
p1 == self.x and p2 == self.y and
|
p1 == self.x and p2 == self.y and
|
||||||
(clock - self.timer < .5) then
|
(clock - self.timer < .5) then
|
||||||
|
|
||||||
self.mch = 'mouse_doubleclick'
|
self.clickCount = self.clickCount + 1
|
||||||
self.timer = nil
|
if self.clickCount == 3 then
|
||||||
|
self.mch = 'mouse_tripleclick'
|
||||||
|
self.timer = nil
|
||||||
|
self.clickCount = 1
|
||||||
|
else
|
||||||
|
self.mch = 'mouse_doubleclick'
|
||||||
|
end
|
||||||
else
|
else
|
||||||
self.timer = os.clock()
|
self.timer = os.clock()
|
||||||
self.x = p1
|
self.x = p1
|
||||||
self.y = p2
|
self.y = p2
|
||||||
|
self.clickCount = 1
|
||||||
end
|
end
|
||||||
self.mfired = input:toCode(self.mch, 255)
|
self.mfired = input:toCode(self.mch, 255)
|
||||||
else
|
else
|
||||||
self.mch = 'mouse_up'
|
self.mch = 'mouse_up'
|
||||||
self.mfired = input:toCode(self.mch, 255)
|
self.mfired = input:toCode(self.mch, 255)
|
||||||
end
|
end
|
||||||
|
|
||||||
return {
|
return {
|
||||||
code = self.mfired,
|
code = self.mfired,
|
||||||
button = code,
|
button = code,
|
||||||
@@ -176,11 +189,22 @@ function input:translate(event, code, p1, p2)
|
|||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
function input:test()
|
if not ({ ...})[1] then
|
||||||
|
local colors = _G.colors
|
||||||
|
local term = _G.term
|
||||||
|
|
||||||
while true do
|
while true do
|
||||||
local ch = self:translate(os.pullEvent())
|
local e = { os.pullEvent() }
|
||||||
|
local ch = input:translate(table.unpack(e))
|
||||||
if ch then
|
if ch then
|
||||||
Util.print(ch)
|
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')
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -8,6 +8,7 @@ Map.merge = Util.merge
|
|||||||
Map.shallowCopy = Util.shallowCopy
|
Map.shallowCopy = Util.shallowCopy
|
||||||
Map.find = Util.find
|
Map.find = Util.find
|
||||||
Map.filter = Util.filter
|
Map.filter = Util.filter
|
||||||
|
Map.transpose = Util.transpose
|
||||||
|
|
||||||
function Map.removeMatches(t, values)
|
function Map.removeMatches(t, values)
|
||||||
local function matchAll(entry)
|
local function matchAll(entry)
|
||||||
|
|||||||
@@ -1,16 +1,19 @@
|
|||||||
local Util = require('opus.util')
|
local Util = require('opus.util')
|
||||||
|
|
||||||
|
local colors = _G.colors
|
||||||
|
|
||||||
local NFT = { }
|
local NFT = { }
|
||||||
|
|
||||||
-- largely copied from http://www.computercraft.info/forums2/index.php?/topic/5029-145-npaintpro/
|
-- largely copied from http://www.computercraft.info/forums2/index.php?/topic/5029-145-npaintpro/
|
||||||
|
|
||||||
local tColourLookup = { }
|
local hexToColor = { }
|
||||||
for n = 1, 16 do
|
for n = 1, 16 do
|
||||||
tColourLookup[string.byte("0123456789abcdef", n, n)] = 2 ^ (n - 1)
|
hexToColor[string.sub("0123456789abcdef", n, n)] = 2 ^ (n - 1)
|
||||||
end
|
end
|
||||||
|
local colorToHex = Util.transpose(hexToColor)
|
||||||
|
|
||||||
local function getColourOf(hex)
|
local function getColourOf(hex)
|
||||||
return tColourLookup[hex:byte()]
|
return hexToColor[hex]
|
||||||
end
|
end
|
||||||
|
|
||||||
function NFT.parse(imageText)
|
function NFT.parse(imageText)
|
||||||
@@ -62,8 +65,22 @@ function NFT.parse(imageText)
|
|||||||
return image
|
return image
|
||||||
end
|
end
|
||||||
|
|
||||||
function NFT.load(path)
|
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)
|
local imageText = Util.readFile(path)
|
||||||
if not imageText then
|
if not imageText then
|
||||||
error('Unable to read image file')
|
error('Unable to read image file')
|
||||||
@@ -71,4 +88,35 @@ function NFT.load(path)
|
|||||||
return NFT.parse(imageText)
|
return NFT.parse(imageText)
|
||||||
end
|
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
|
return NFT
|
||||||
|
|||||||
@@ -57,7 +57,7 @@ end
|
|||||||
function Packages:downloadList()
|
function Packages:downloadList()
|
||||||
local packages = {
|
local packages = {
|
||||||
[ 'develop-1.8' ] = 'https://raw.githubusercontent.com/kepler155c/opus-apps/develop-1.8/packages.list',
|
[ 'develop-1.8' ] = 'https://raw.githubusercontent.com/kepler155c/opus-apps/develop-1.8/packages.list',
|
||||||
[ 'master-1.8' ] = 'https://pastebin.com/raw/pexZpAxt',
|
[ 'master-1.8' ] = 'https://raw.githubusercontent.com/kepler155c/opus-apps/master-1.8/packages.list',
|
||||||
}
|
}
|
||||||
|
|
||||||
if packages[_G.OPUS_BRANCH] then
|
if packages[_G.OPUS_BRANCH] then
|
||||||
|
|||||||
@@ -6,12 +6,11 @@ local Util = require('opus.util')
|
|||||||
|
|
||||||
local device = _G.device
|
local device = _G.device
|
||||||
local os = _G.os
|
local os = _G.os
|
||||||
local network = _G.network
|
|
||||||
|
|
||||||
local socketClass = { }
|
local socketClass = { }
|
||||||
|
|
||||||
function socketClass:read(timeout)
|
function socketClass:read(timeout)
|
||||||
local data, distance = network.getTransport().read(self)
|
local data, distance = _G.network.getTransport().read(self)
|
||||||
if data then
|
if data then
|
||||||
return data, distance
|
return data, distance
|
||||||
end
|
end
|
||||||
@@ -26,7 +25,7 @@ function socketClass:read(timeout)
|
|||||||
local e, id = os.pullEvent()
|
local e, id = os.pullEvent()
|
||||||
|
|
||||||
if e == 'transport_' .. self.uid then
|
if e == 'transport_' .. self.uid then
|
||||||
data, distance = network.getTransport().read(self)
|
data, distance = _G.network.getTransport().read(self)
|
||||||
if data then
|
if data then
|
||||||
os.cancelTimer(timerId)
|
os.cancelTimer(timerId)
|
||||||
return data, distance
|
return data, distance
|
||||||
@@ -47,7 +46,7 @@ end
|
|||||||
|
|
||||||
function socketClass:write(data)
|
function socketClass:write(data)
|
||||||
if self.connected then
|
if self.connected then
|
||||||
network.getTransport().write(self, {
|
_G.network.getTransport().write(self, {
|
||||||
type = 'DATA',
|
type = 'DATA',
|
||||||
seq = self.wseq,
|
seq = self.wseq,
|
||||||
data = data,
|
data = data,
|
||||||
@@ -58,7 +57,7 @@ end
|
|||||||
|
|
||||||
function socketClass:ping()
|
function socketClass:ping()
|
||||||
if self.connected then
|
if self.connected then
|
||||||
network.getTransport().ping(self)
|
_G.network.getTransport().ping(self)
|
||||||
return true
|
return true
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
@@ -72,7 +71,7 @@ function socketClass:close()
|
|||||||
self.connected = false
|
self.connected = false
|
||||||
end
|
end
|
||||||
device.wireless_modem.close(self.sport)
|
device.wireless_modem.close(self.sport)
|
||||||
network.getTransport().close(self)
|
_G.network.getTransport().close(self)
|
||||||
end
|
end
|
||||||
|
|
||||||
local Socket = { }
|
local Socket = { }
|
||||||
@@ -127,9 +126,9 @@ function Socket.connect(host, port, options)
|
|||||||
local socket = newSocket(host == os.getComputerID())
|
local socket = newSocket(host == os.getComputerID())
|
||||||
socket.dhost = tonumber(host)
|
socket.dhost = tonumber(host)
|
||||||
if options and options.keypair then
|
if options and options.keypair then
|
||||||
socket.privKey, socket.pubKey = unpack(options.keypair)
|
socket.privKey, socket.pubKey = table.unpack(options.keypair)
|
||||||
else
|
else
|
||||||
socket.privKey, socket.pubKey = network.getKeyPair()
|
socket.privKey, socket.pubKey = _G.network.getKeyPair()
|
||||||
end
|
end
|
||||||
local identifier = options and options.identifier or Security.getIdentifier()
|
local identifier = options and options.identifier or Security.getIdentifier()
|
||||||
|
|
||||||
@@ -159,7 +158,7 @@ function Socket.connect(host, port, options)
|
|||||||
socket.remotePubKey = Util.hexToByteArray(msg.pk)
|
socket.remotePubKey = Util.hexToByteArray(msg.pk)
|
||||||
socket.options = msg.options or { }
|
socket.options = msg.options or { }
|
||||||
setupCrypto(socket, true)
|
setupCrypto(socket, true)
|
||||||
network.getTransport().open(socket)
|
_G.network.getTransport().open(socket)
|
||||||
return socket
|
return socket
|
||||||
|
|
||||||
elseif msg.type == 'NOPASS' then
|
elseif msg.type == 'NOPASS' then
|
||||||
@@ -192,7 +191,7 @@ local function trusted(socket, msg, options)
|
|||||||
if data and data.ts and tonumber(data.ts) then
|
if data and data.ts and tonumber(data.ts) then
|
||||||
if math.abs(os.epoch('utc') - data.ts) < 4096 then
|
if math.abs(os.epoch('utc') - data.ts) < 4096 then
|
||||||
socket.remotePubKey = Util.hexToByteArray(data.pk)
|
socket.remotePubKey = Util.hexToByteArray(data.pk)
|
||||||
socket.privKey, socket.pubKey = network.getKeyPair()
|
socket.privKey, socket.pubKey = _G.network.getKeyPair()
|
||||||
setupCrypto(socket)
|
setupCrypto(socket)
|
||||||
return true
|
return true
|
||||||
end
|
end
|
||||||
@@ -241,7 +240,7 @@ function Socket.server(port, options)
|
|||||||
options = socket.options.ENCRYPT and { ENCRYPT = true },
|
options = socket.options.ENCRYPT and { ENCRYPT = true },
|
||||||
})
|
})
|
||||||
|
|
||||||
network.getTransport().open(socket)
|
_G.network.getTransport().open(socket)
|
||||||
return socket
|
return socket
|
||||||
|
|
||||||
else
|
else
|
||||||
|
|||||||
@@ -33,64 +33,69 @@ function Terminal.window(parent, sx, sy, w, h, isVisible)
|
|||||||
end
|
end
|
||||||
|
|
||||||
local win = { }
|
local win = { }
|
||||||
local maxScroll = 100
|
local maxScroll
|
||||||
local cx, cy = 1, 1
|
local cx, cy = 1, 1
|
||||||
local blink = false
|
local blink = false
|
||||||
local bg, fg = parent.getBackgroundColor(), parent.getTextColor()
|
local _bg, _fg = colors.black, colors.white
|
||||||
|
|
||||||
local canvas = Canvas({
|
win.canvas = Canvas({
|
||||||
x = sx,
|
x = sx,
|
||||||
y = sy,
|
y = sy,
|
||||||
width = w,
|
width = w,
|
||||||
height = h,
|
height = h,
|
||||||
isColor = parent.isColor(),
|
isColor = parent.isColor(),
|
||||||
offy = 0,
|
offy = 0,
|
||||||
|
bg = _bg,
|
||||||
|
fg = _fg,
|
||||||
})
|
})
|
||||||
|
|
||||||
win.canvas = canvas
|
|
||||||
|
|
||||||
local function update()
|
local function update()
|
||||||
if isVisible then
|
if isVisible then
|
||||||
canvas:render(parent)
|
win.canvas:render(parent)
|
||||||
win.setCursorPos(cx, cy)
|
win.setCursorPos(cx, cy)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
local function scrollTo(y)
|
local function scrollTo(y)
|
||||||
y = math.max(0, y)
|
y = math.max(0, y)
|
||||||
y = math.min(#canvas.lines - canvas.height, y)
|
y = math.min(#win.canvas.lines - win.canvas.height, y)
|
||||||
|
|
||||||
if y ~= canvas.offy then
|
if y ~= win.canvas.offy then
|
||||||
canvas.offy = y
|
win.canvas.offy = y
|
||||||
canvas:dirty()
|
win.canvas:dirty()
|
||||||
update()
|
update()
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
function win.write(str)
|
function win.write(str)
|
||||||
str = tostring(str) or ''
|
str = tostring(str) or ''
|
||||||
canvas:write(cx, cy + canvas.offy, str, bg, fg)
|
win.canvas:write(cx, cy + win.canvas.offy, str, win.canvas.bg, win.canvas.fg)
|
||||||
win.setCursorPos(cx + #str, cy)
|
win.setCursorPos(cx + #str, cy)
|
||||||
update()
|
update()
|
||||||
end
|
end
|
||||||
|
|
||||||
function win.blit(str, fg, bg)
|
function win.blit(str, fg, bg)
|
||||||
canvas:blit(cx, cy + canvas.offy, str, bg, fg)
|
win.canvas:blit(cx, cy + win.canvas.offy, str, bg, fg)
|
||||||
win.setCursorPos(cx + #str, cy)
|
win.setCursorPos(cx + #str, cy)
|
||||||
update()
|
update()
|
||||||
end
|
end
|
||||||
|
|
||||||
function win.clear()
|
function win.clear()
|
||||||
canvas.offy = 0
|
win.canvas.offy = 0
|
||||||
for i = #canvas.lines, canvas.height + 1, -1 do
|
for i = #win.canvas.lines, win.canvas.height + 1, -1 do
|
||||||
canvas.lines[i] = nil
|
win.canvas.lines[i] = nil
|
||||||
end
|
end
|
||||||
canvas:clear(bg, fg)
|
win.canvas:clear()
|
||||||
update()
|
update()
|
||||||
end
|
end
|
||||||
|
|
||||||
|
function win.getLine(n)
|
||||||
|
local line = win.canvas.lines[n]
|
||||||
|
return line.text, line.fg, line.bg
|
||||||
|
end
|
||||||
|
|
||||||
function win.clearLine()
|
function win.clearLine()
|
||||||
canvas:clearLine(cy + canvas.offy, bg, fg)
|
win.canvas:clearLine(cy + win.canvas.offy)
|
||||||
win.setCursorPos(cx, cy)
|
win.setCursorPos(cx, cy)
|
||||||
update()
|
update()
|
||||||
end
|
end
|
||||||
@@ -102,10 +107,14 @@ function Terminal.window(parent, sx, sy, w, h, isVisible)
|
|||||||
function win.setCursorPos(x, y)
|
function win.setCursorPos(x, y)
|
||||||
cx, cy = math.floor(x), math.floor(y)
|
cx, cy = math.floor(x), math.floor(y)
|
||||||
if isVisible then
|
if isVisible then
|
||||||
parent.setCursorPos(cx + canvas.x - 1, cy + canvas.y - 1)
|
parent.setCursorPos(cx + win.canvas.x - 1, cy + win.canvas.y - 1)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
function win.getCursorBlink()
|
||||||
|
return blink
|
||||||
|
end
|
||||||
|
|
||||||
function win.setCursorBlink(b)
|
function win.setCursorBlink(b)
|
||||||
blink = b
|
blink = b
|
||||||
if isVisible then
|
if isVisible then
|
||||||
@@ -114,12 +123,12 @@ function Terminal.window(parent, sx, sy, w, h, isVisible)
|
|||||||
end
|
end
|
||||||
|
|
||||||
function win.isColor()
|
function win.isColor()
|
||||||
return canvas.isColor
|
return win.canvas.isColor
|
||||||
end
|
end
|
||||||
win.isColour = win.isColor
|
win.isColour = win.isColor
|
||||||
|
|
||||||
function win.setTextColor(c)
|
function win.setTextColor(c)
|
||||||
fg = c
|
win.canvas.fg = c
|
||||||
end
|
end
|
||||||
win.setTextColour = win.setTextColor
|
win.setTextColour = win.setTextColor
|
||||||
|
|
||||||
@@ -139,38 +148,38 @@ function Terminal.window(parent, sx, sy, w, h, isVisible)
|
|||||||
win.setPaletteColour = win.setPaletteColor
|
win.setPaletteColour = win.setPaletteColor
|
||||||
|
|
||||||
function win.setBackgroundColor(c)
|
function win.setBackgroundColor(c)
|
||||||
bg = c
|
win.canvas.bg = c
|
||||||
end
|
end
|
||||||
win.setBackgroundColour = win.setBackgroundColor
|
win.setBackgroundColour = win.setBackgroundColor
|
||||||
|
|
||||||
function win.getSize()
|
function win.getSize()
|
||||||
return canvas.width, canvas.height
|
return win.canvas.width, win.canvas.height
|
||||||
end
|
end
|
||||||
|
|
||||||
function win.scroll(n)
|
function win.scroll(n)
|
||||||
n = n or 1
|
n = n or 1
|
||||||
if n > 0 then
|
if n > 0 then
|
||||||
local lines = #canvas.lines
|
local lines = #win.canvas.lines
|
||||||
for i = 1, n do
|
for i = 1, n do
|
||||||
canvas.lines[lines + i] = { }
|
win.canvas.lines[lines + i] = { }
|
||||||
canvas:clearLine(lines + i, bg, fg)
|
win.canvas:clearLine(lines + i)
|
||||||
end
|
end
|
||||||
while #canvas.lines > maxScroll do
|
while #win.canvas.lines > (maxScroll or win.canvas.height) do
|
||||||
table.remove(canvas.lines, 1)
|
table.remove(win.canvas.lines, 1)
|
||||||
end
|
end
|
||||||
scrollTo(#canvas.lines)
|
scrollTo(#win.canvas.lines)
|
||||||
canvas:dirty()
|
win.canvas:dirty()
|
||||||
update()
|
update()
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
function win.getTextColor()
|
function win.getTextColor()
|
||||||
return fg
|
return win.canvas.fg
|
||||||
end
|
end
|
||||||
win.getTextColour = win.getTextColor
|
win.getTextColour = win.getTextColor
|
||||||
|
|
||||||
function win.getBackgroundColor()
|
function win.getBackgroundColor()
|
||||||
return bg
|
return win.canvas.bg
|
||||||
end
|
end
|
||||||
win.getBackgroundColour = win.getBackgroundColor
|
win.getBackgroundColour = win.getBackgroundColor
|
||||||
|
|
||||||
@@ -178,7 +187,7 @@ function Terminal.window(parent, sx, sy, w, h, isVisible)
|
|||||||
if visible ~= isVisible then
|
if visible ~= isVisible then
|
||||||
isVisible = visible
|
isVisible = visible
|
||||||
if isVisible then
|
if isVisible then
|
||||||
canvas:dirty()
|
win.canvas:dirty()
|
||||||
update()
|
update()
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
@@ -186,7 +195,7 @@ function Terminal.window(parent, sx, sy, w, h, isVisible)
|
|||||||
|
|
||||||
function win.redraw()
|
function win.redraw()
|
||||||
if isVisible then
|
if isVisible then
|
||||||
canvas:dirty()
|
win.canvas:dirty()
|
||||||
update()
|
update()
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
@@ -194,27 +203,58 @@ function Terminal.window(parent, sx, sy, w, h, isVisible)
|
|||||||
function win.restoreCursor()
|
function win.restoreCursor()
|
||||||
if isVisible then
|
if isVisible then
|
||||||
win.setCursorPos(cx, cy)
|
win.setCursorPos(cx, cy)
|
||||||
win.setTextColor(fg)
|
win.setTextColor(win.canvas.fg)
|
||||||
win.setCursorBlink(blink)
|
win.setCursorBlink(blink)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
function win.getPosition()
|
function win.getPosition()
|
||||||
return canvas.x, canvas.y
|
return win.canvas.x, win.canvas.y
|
||||||
end
|
end
|
||||||
|
|
||||||
function win.reposition(x, y, width, height)
|
function win.reposition(x, y, width, height)
|
||||||
canvas.x, canvas.y = x, y
|
if not maxScroll then
|
||||||
canvas:resize(width or canvas.width, height or canvas.height)
|
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()
|
||||||
end
|
end
|
||||||
|
|
||||||
--[[ Additional methods ]]--
|
--[[ Additional methods ]]--
|
||||||
function win.scrollDown()
|
function win.scrollDown()
|
||||||
scrollTo(canvas.offy + 1)
|
scrollTo(win.canvas.offy + 1)
|
||||||
end
|
end
|
||||||
|
|
||||||
function win.scrollUp()
|
function win.scrollUp()
|
||||||
scrollTo(canvas.offy - 1)
|
scrollTo(win.canvas.offy - 1)
|
||||||
end
|
end
|
||||||
|
|
||||||
function win.scrollTop()
|
function win.scrollTop()
|
||||||
@@ -222,7 +262,7 @@ function Terminal.window(parent, sx, sy, w, h, isVisible)
|
|||||||
end
|
end
|
||||||
|
|
||||||
function win.scrollBottom()
|
function win.scrollBottom()
|
||||||
scrollTo(#canvas.lines)
|
scrollTo(#win.canvas.lines)
|
||||||
end
|
end
|
||||||
|
|
||||||
function win.setMaxScroll(ms)
|
function win.setMaxScroll(ms)
|
||||||
@@ -230,37 +270,108 @@ function Terminal.window(parent, sx, sy, w, h, isVisible)
|
|||||||
end
|
end
|
||||||
|
|
||||||
function win.getCanvas()
|
function win.getCanvas()
|
||||||
return canvas
|
return win.canvas
|
||||||
end
|
end
|
||||||
|
|
||||||
function win.getParent()
|
function win.getParent()
|
||||||
return parent
|
return parent
|
||||||
end
|
end
|
||||||
|
|
||||||
canvas:clear()
|
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()
|
||||||
|
|
||||||
return win
|
return win
|
||||||
end
|
end
|
||||||
|
|
||||||
-- get windows contents
|
-- get windows contents
|
||||||
function Terminal.getContents(win, parent)
|
function Terminal.getContents(win)
|
||||||
local oblit, oscp = parent.blit, parent.setCursorPos
|
if not win.getLine then
|
||||||
local lines = { }
|
error('window is required')
|
||||||
|
end
|
||||||
|
|
||||||
parent.blit = function(text, fg, bg)
|
local lines = { }
|
||||||
lines[#lines + 1] = {
|
local _, h = win.getSize()
|
||||||
|
|
||||||
|
for i = 1, h do
|
||||||
|
local text, fg, bg = win.getLine(i)
|
||||||
|
lines[i] = {
|
||||||
text = text,
|
text = text,
|
||||||
fg = fg,
|
fg = fg,
|
||||||
bg = bg,
|
bg = bg,
|
||||||
}
|
}
|
||||||
end
|
end
|
||||||
parent.setCursorPos = function() end
|
|
||||||
|
|
||||||
win.setVisible(true)
|
|
||||||
win.redraw()
|
|
||||||
|
|
||||||
parent.blit = oblit
|
|
||||||
parent.setCursorPos = oscp
|
|
||||||
|
|
||||||
return lines
|
return lines
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -12,6 +12,10 @@ local function traceback(x)
|
|||||||
return x
|
return x
|
||||||
end
|
end
|
||||||
|
|
||||||
|
if x and x:match(':%d+: 0$') then
|
||||||
|
return x
|
||||||
|
end
|
||||||
|
|
||||||
if debug_traceback then
|
if debug_traceback then
|
||||||
-- The parens are important, as they prevent a tail call occuring, meaning
|
-- The parens are important, as they prevent a tail call occuring, meaning
|
||||||
-- the stack level is preserved. This ensures the code behaves identically
|
-- the stack level is preserved. This ensures the code behaves identically
|
||||||
@@ -32,78 +36,68 @@ local function traceback(x)
|
|||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
local function trim_traceback(target, marker)
|
local function trim_traceback(stack)
|
||||||
local ttarget, tmarker = {}, {}
|
local trace = { }
|
||||||
for line in target:gmatch("([^\n]*)\n?") do ttarget[#ttarget + 1] = line end
|
local filters = {
|
||||||
for line in marker:gmatch("([^\n]*)\n?") do tmarker[#tmarker + 1] = line end
|
"%[C%]: in function 'xpcall'",
|
||||||
|
"(...tail calls...)",
|
||||||
|
"xpcall: $",
|
||||||
|
"trace.lua:%d+:",
|
||||||
|
"stack traceback:",
|
||||||
|
}
|
||||||
|
|
||||||
-- Trim identical suffixes
|
for line in stack:gmatch("([^\n]*)\n?") do table.insert(trace, line) end
|
||||||
local t_len, m_len = #ttarget, #tmarker
|
|
||||||
while t_len >= 3 and ttarget[t_len] == tmarker[m_len] do
|
local err = { }
|
||||||
table.remove(ttarget, t_len)
|
while true do
|
||||||
t_len, m_len = t_len - 1, m_len - 1
|
local line = table.remove(trace, 1)
|
||||||
|
if not line or line == 'stack traceback:' then
|
||||||
|
break
|
||||||
|
end
|
||||||
|
table.insert(err, line)
|
||||||
|
end
|
||||||
|
err = table.concat(err, '\n')
|
||||||
|
|
||||||
|
local function matchesFilter(line)
|
||||||
|
for _, filter in pairs(filters) do
|
||||||
|
if line:match(filter) then
|
||||||
|
return true
|
||||||
|
end
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
-- Trim elements from this file and xpcall invocations
|
local t = { }
|
||||||
while t_len >= 1 and ttarget[t_len]:find("^\tstack_trace%.lua:%d+:") or
|
for _, line in pairs(trace) do
|
||||||
ttarget[t_len] == "\t[C]: in function 'xpcall'" or ttarget[t_len] == " xpcall: " do
|
if not matchesFilter(line) then
|
||||||
table.remove(ttarget, t_len)
|
line = line:gsub("in function", "in"):gsub('%w+/', '')
|
||||||
t_len = t_len - 1
|
table.insert(t, line)
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
ttarget[#ttarget] = nil -- remove 2 calls added by the added xpcall
|
return err, t
|
||||||
ttarget[#ttarget] = nil
|
|
||||||
|
|
||||||
return ttarget
|
|
||||||
end
|
end
|
||||||
|
|
||||||
--- Run a function with
|
|
||||||
return function (fn, ...)
|
return function (fn, ...)
|
||||||
-- So this is rather grim: we need to get the full traceback and current one and remove
|
|
||||||
-- the common prefix
|
|
||||||
local trace
|
|
||||||
local args = { ... }
|
local args = { ... }
|
||||||
|
|
||||||
-- xpcall in Lua 5.1 does not accept parameters
|
|
||||||
-- which is not ideal
|
|
||||||
local res = table.pack(xpcall(function()
|
local res = table.pack(xpcall(function()
|
||||||
return fn(table.unpack(args))
|
return fn(table.unpack(args))
|
||||||
end, traceback))
|
end, traceback))
|
||||||
|
|
||||||
if not res[1] then
|
if not res[1] and res[2] ~= nil then
|
||||||
trace = traceback("trace.lua:1:")
|
local err, trace = trim_traceback(res[2])
|
||||||
end
|
|
||||||
local ok, err = res[1], res[2]
|
|
||||||
|
|
||||||
if not ok and err ~= nil then
|
if err:match(':%d+: 0$') then
|
||||||
trace = trim_traceback(err, trace)
|
return true
|
||||||
|
|
||||||
-- Find the position where the stack traceback actually starts
|
|
||||||
local trace_starts
|
|
||||||
for i = #trace, 1, -1 do
|
|
||||||
if trace[i] == "stack traceback:" then trace_starts = i; break end
|
|
||||||
end
|
end
|
||||||
|
|
||||||
for _, line in pairs(trace) do
|
if #trace > 0 then
|
||||||
_G._syslog(line)
|
_G._syslog('\n' .. err .. '\n' .. 'stack traceback:')
|
||||||
end
|
for _, v in ipairs(trace) do
|
||||||
|
_G._syslog(v)
|
||||||
-- If this traceback is more than 15 elements long, keep the first 9, last 5
|
|
||||||
-- and put an ellipsis between the rest
|
|
||||||
local max = 10
|
|
||||||
if trace_starts and #trace - trace_starts > max then
|
|
||||||
local keep_starts = trace_starts + 7
|
|
||||||
for i = #trace - trace_starts - max, 0, -1 do
|
|
||||||
table.remove(trace, keep_starts + i)
|
|
||||||
end
|
end
|
||||||
table.insert(trace, keep_starts, " ...")
|
|
||||||
end
|
end
|
||||||
|
|
||||||
for k, line in pairs(trace) do
|
return res[1], err, trace
|
||||||
trace[k] = line:gsub("in function", " in")
|
|
||||||
end
|
|
||||||
|
|
||||||
return false, table.remove(trace, 1), table.concat(trace, "\n")
|
|
||||||
end
|
end
|
||||||
|
|
||||||
return table.unpack(res, 1, res.n)
|
return table.unpack(res, 1, res.n)
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user