Compare commits
158 Commits
master-1.8
...
develop-1.
| Author | SHA1 | Date | |
|---|---|---|---|
| 100b23793c | |||
|
|
9b502553e0 | ||
|
|
c247097e20 | ||
|
|
2597724dab | ||
|
|
5f153721ea | ||
|
|
0a42551ab7 | ||
|
|
76b310d873 | ||
|
|
f26f443b9d | ||
|
|
8fbcc7b8bc | ||
|
|
f7ba900930 | ||
|
|
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 | ||
|
|
39522ee5b1 | ||
|
|
369070e19c | ||
|
|
210c5f5a11 | ||
|
|
942f0bda92 | ||
|
|
cc80e08407 | ||
|
|
9e3cf50ccc | ||
|
|
6204c46cc4 | ||
|
|
db2a28f04c | ||
|
|
28b2ba3386 | ||
|
|
31f43067bf | ||
|
|
424fff7842 | ||
|
|
1e675a2e35 | ||
|
|
ffa412c59d | ||
|
|
a3a8c64be8 | ||
|
|
efa1a5bbf5 | ||
|
|
14057c2bf9 | ||
|
|
3241326a2f | ||
|
|
db48031c7c | ||
|
|
0a828fecc5 | ||
|
|
65c6ebf711 | ||
|
|
053003f429 | ||
|
|
25405f15c8 |
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,27 +1,24 @@
|
|||||||
|
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', ...)
|
||||||
|
|
||||||
local topics = { }
|
local topics = { }
|
||||||
for _,topic in pairs(help.topics()) do
|
for _,topic in pairs(help.topics()) do
|
||||||
if help.lookup(topic) then
|
table.insert(topics, { name = topic, lname = topic:lower() })
|
||||||
table.insert(topics, { name = topic })
|
|
||||||
end
|
|
||||||
end
|
end
|
||||||
|
|
||||||
local page = 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,
|
||||||
transform = 'lowercase',
|
|
||||||
},
|
},
|
||||||
grid = UI.ScrollingGrid {
|
grid = UI.ScrollingGrid {
|
||||||
y = 4,
|
y = 4,
|
||||||
@@ -29,76 +26,71 @@ local page = UI.Page {
|
|||||||
columns = {
|
columns = {
|
||||||
{ heading = 'Topic', key = 'name' },
|
{ heading = 'Topic', key = 'name' },
|
||||||
},
|
},
|
||||||
sortColumn = 'name',
|
sortColumn = 'lname',
|
||||||
},
|
},
|
||||||
accelerators = {
|
accelerators = {
|
||||||
[ 'control-q' ] = 'quit',
|
[ 'control-q' ] = 'quit',
|
||||||
enter = 'grid_select',
|
enter = 'grid_select',
|
||||||
},
|
},
|
||||||
}
|
eventHandler = function(self, event)
|
||||||
|
if event.type == 'quit' then
|
||||||
|
UI:quit()
|
||||||
|
|
||||||
local topicPage = UI.Page {
|
elseif event.type == 'grid_select' then
|
||||||
backgroundColor = colors.black,
|
if self.grid:getSelected() then
|
||||||
|
UI:setPage('topic', self.grid:getSelected().name)
|
||||||
|
end
|
||||||
|
|
||||||
|
elseif event.type == 'text_change' then
|
||||||
|
if not event.text then
|
||||||
|
self.grid.sortColumn = 'lname'
|
||||||
|
else
|
||||||
|
self.grid.sortColumn = 'score'
|
||||||
|
self.grid.inverseSort = false
|
||||||
|
local pattern = event.text:lower()
|
||||||
|
for _,v in pairs(self.grid.values) do
|
||||||
|
v.score = -fuzzy(v.lname, pattern)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
self.grid:update()
|
||||||
|
self.grid:setIndex(1)
|
||||||
|
self.grid:draw()
|
||||||
|
|
||||||
|
else
|
||||||
|
return UI.Page.eventHandler(self, event)
|
||||||
|
end
|
||||||
|
end,
|
||||||
|
})
|
||||||
|
|
||||||
|
UI:addPage('topic', UI.Page {
|
||||||
|
backgroundColor = 'black',
|
||||||
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 = {
|
||||||
[ 'control-q' ] = 'back',
|
[ 'control-q' ] = 'back',
|
||||||
backspace = 'back',
|
backspace = 'back',
|
||||||
},
|
},
|
||||||
}
|
enable = function(self, name)
|
||||||
|
local f = help.lookup(name)
|
||||||
|
|
||||||
function topicPage:enable(name)
|
self.titleBar.title = name
|
||||||
local f = help.lookup(name)
|
self.helpText:setText(f and Util.readFile(f) or 'No help available for ' .. name)
|
||||||
|
|
||||||
self.titleBar.title = name
|
return UI.Page.enable(self)
|
||||||
self.helpText:setText(f and Util.readFile(f) or 'No help available for ' .. name)
|
end,
|
||||||
|
eventHandler = function(self, event)
|
||||||
return UI.Page.enable(self)
|
if event.type == 'back' then
|
||||||
end
|
UI:setPage('main')
|
||||||
|
|
||||||
function topicPage:eventHandler(event)
|
|
||||||
if event.type == 'back' then
|
|
||||||
UI:setPage(page)
|
|
||||||
end
|
|
||||||
return UI.Page.eventHandler(self, event)
|
|
||||||
end
|
|
||||||
|
|
||||||
function page:eventHandler(event)
|
|
||||||
if event.type == 'quit' then
|
|
||||||
UI:exitPullEvents()
|
|
||||||
|
|
||||||
elseif event.type == 'grid_select' then
|
|
||||||
if self.grid:getSelected() then
|
|
||||||
local name = self.grid:getSelected().name
|
|
||||||
|
|
||||||
UI:setPage(topicPage, name)
|
|
||||||
end
|
end
|
||||||
|
|
||||||
elseif event.type == 'text_change' then
|
|
||||||
if #event.text == 0 then
|
|
||||||
self.grid.values = topics
|
|
||||||
else
|
|
||||||
self.grid.values = { }
|
|
||||||
for _,f in pairs(topics) do
|
|
||||||
if string.find(f.name, event.text) then
|
|
||||||
table.insert(self.grid.values, f)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
self.grid:update()
|
|
||||||
self.grid:setIndex(1)
|
|
||||||
self.grid:draw()
|
|
||||||
else
|
|
||||||
return UI.Page.eventHandler(self, event)
|
return UI.Page.eventHandler(self, event)
|
||||||
end
|
end,
|
||||||
end
|
})
|
||||||
|
|
||||||
local args = Util.parse(...)
|
local args = Util.parse(...)
|
||||||
UI:setPage(args[1] and topicPage or page, args[1])
|
UI:setPage(args[1] and 'topic' or 'main', args[1])
|
||||||
UI:pullEvents()
|
UI:start()
|
||||||
|
|||||||
@@ -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,30 +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 sz = #self.prompt.value
|
local value = self.prompt.value or ''
|
||||||
|
local sz = #value
|
||||||
local pos = self.prompt.entry.pos
|
local pos = self.prompt.entry.pos
|
||||||
self:setPrompt(autocomplete(sandboxEnv, self.prompt.value, self.prompt.entry.pos))
|
self:setPrompt(autocomplete(sandboxEnv, value, self.prompt.entry.pos))
|
||||||
self.prompt:setPosition(pos + #self.prompt.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
|
||||||
@@ -177,7 +166,7 @@ function page:eventHandler(event)
|
|||||||
history:reset()
|
history:reset()
|
||||||
|
|
||||||
elseif event.type == 'command_enter' then
|
elseif event.type == 'command_enter' then
|
||||||
local s = tostring(self.prompt.value)
|
local s = tostring(self.prompt.value or '')
|
||||||
|
|
||||||
if #s > 0 then
|
if #s > 0 then
|
||||||
self:executeStatement(s)
|
self:executeStatement(s)
|
||||||
@@ -195,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
|
||||||
|
|
||||||
@@ -242,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)
|
||||||
@@ -361,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({ })
|
||||||
@@ -370,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(...)
|
||||||
@@ -381,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,31 @@ 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
|
||||||
|
local uid = _ENV.multishell.getCurrent()
|
||||||
|
device.keyboard.addHotkey('control-o', function()
|
||||||
|
_ENV.multishell.setFocus(uid)
|
||||||
|
end)
|
||||||
|
|
||||||
UI:configure('Overview', ...)
|
UI:configure('Overview', ...)
|
||||||
|
|
||||||
@@ -35,7 +51,7 @@ local config = {
|
|||||||
}
|
}
|
||||||
Config.load('Overview', config)
|
Config.load('Overview', config)
|
||||||
|
|
||||||
local extSupport = Util.getVersion() >= 1.76
|
local extSupport = Util.supportsExtChars()
|
||||||
|
|
||||||
local applications = { }
|
local applications = { }
|
||||||
local buttons = { }
|
local buttons = { }
|
||||||
@@ -59,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)
|
||||||
@@ -70,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',
|
||||||
@@ -116,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 {
|
||||||
@@ -124,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(),
|
||||||
},
|
},
|
||||||
@@ -151,6 +189,7 @@ local page = UI.Page {
|
|||||||
f = 'files',
|
f = 'files',
|
||||||
s = 'shell',
|
s = 'shell',
|
||||||
l = 'lua',
|
l = 'lua',
|
||||||
|
n = 'network',
|
||||||
[ 'control-n' ] = 'new',
|
[ 'control-n' ] = 'new',
|
||||||
delete = 'delete',
|
delete = 'delete',
|
||||||
},
|
},
|
||||||
@@ -198,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 = { }
|
||||||
@@ -208,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
|
||||||
@@ -215,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')
|
||||||
@@ -236,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 })
|
||||||
@@ -252,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)
|
||||||
@@ -297,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
|
||||||
@@ -327,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
|
||||||
@@ -349,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
|
||||||
@@ -371,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)
|
||||||
@@ -421,13 +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
|
||||||
|
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
|
||||||
@@ -463,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
|
||||||
@@ -470,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
|
||||||
@@ -492,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)
|
||||||
@@ -506,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)
|
||||||
@@ -544,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')
|
||||||
@@ -555,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()
|
||||||
@@ -584,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,11 +18,11 @@ 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
|
||||||
end
|
end
|
||||||
end)
|
end)
|
||||||
|
|
||||||
os.pullEventRaw('kernel_halt')
|
os.pullEventRaw('kernel_halt')
|
||||||
|
|||||||
@@ -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
|
||||||
@@ -307,14 +306,16 @@ end
|
|||||||
|
|
||||||
function page:enable()
|
function page:enable()
|
||||||
modemConfig.modems = {}
|
modemConfig.modems = {}
|
||||||
peripheral.find('modem', function(side, dev)
|
Util.each(_G.device, function(dev)
|
||||||
modemConfig.modems[side] = {
|
if dev.type == "modem" then
|
||||||
type = dev.isWireless() and 'Wireless' or 'Wired',
|
modemConfig.modems[dev.side] = {
|
||||||
side = side,
|
type = dev.isWireless() and 'Wireless' or 'Wired',
|
||||||
openChannels = { },
|
side = dev.side,
|
||||||
device = dev,
|
openChannels = { },
|
||||||
loaded = false
|
device = dev,
|
||||||
}
|
loaded = false
|
||||||
|
}
|
||||||
|
end
|
||||||
end)
|
end)
|
||||||
modemConfig.currentModem = device.wireless_modem and
|
modemConfig.currentModem = device.wireless_modem and
|
||||||
modemConfig.modems[device.wireless_modem.side] or
|
modemConfig.modems[device.wireless_modem.side] or
|
||||||
@@ -354,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
|
||||||
@@ -384,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,43 +23,47 @@ 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)
|
||||||
|
local elapsed = os.clock()-row.timestamp
|
||||||
|
return {
|
||||||
|
uid = row.uid,
|
||||||
|
title = row.title,
|
||||||
|
status = row.isDead and 'error' or coroutine.status(row.co),
|
||||||
|
timestamp = elapsed < 60 and
|
||||||
|
string.format("%ds", math.floor(elapsed)) or
|
||||||
|
string.format("%sm", math.floor(elapsed/6)/10),
|
||||||
|
}
|
||||||
|
end
|
||||||
},
|
},
|
||||||
accelerators = {
|
accelerators = {
|
||||||
[ 'control-q' ] = 'quit',
|
[ 'control-q' ] = 'quit',
|
||||||
space = 'activate',
|
[ ' ' ] = 'activate',
|
||||||
t = 'terminate',
|
t = 'terminate',
|
||||||
},
|
},
|
||||||
}
|
eventHandler = function (self, event)
|
||||||
|
local t = self.grid:getSelected()
|
||||||
function page:eventHandler(event)
|
if t then
|
||||||
local t = self.grid:getSelected()
|
if event.type == 'activate' or event.type == 'grid_select' then
|
||||||
if t then
|
multishell.setFocus(t.uid)
|
||||||
if event.type == 'activate' or event.type == 'grid_select' then
|
elseif event.type == 'terminate' then
|
||||||
multishell.setFocus(t.uid)
|
multishell.terminate(t.uid)
|
||||||
elseif event.type == 'terminate' then
|
elseif event.type == 'inspect' then
|
||||||
multishell.terminate(t.uid)
|
multishell.openTab(_ENV, {
|
||||||
|
path = 'sys/apps/Lua.lua',
|
||||||
|
args = { t },
|
||||||
|
focused = true,
|
||||||
|
})
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
if event.type == 'quit' then
|
||||||
|
UI:quit()
|
||||||
|
end
|
||||||
|
UI.Page.eventHandler(self, event)
|
||||||
end
|
end
|
||||||
if event.type == 'quit' then
|
}
|
||||||
Event.exitPullEvents()
|
|
||||||
end
|
|
||||||
UI.Page.eventHandler(self, event)
|
|
||||||
end
|
|
||||||
|
|
||||||
function page.grid:getDisplayValues(row)
|
|
||||||
local elapsed = os.clock()-row.timestamp
|
|
||||||
return {
|
|
||||||
uid = row.uid,
|
|
||||||
title = row.title,
|
|
||||||
status = row.isDead and 'error' or coroutine.status(row.co),
|
|
||||||
timestamp = elapsed < 60 and
|
|
||||||
string.format("%ds", math.floor(elapsed)) or
|
|
||||||
string.format("%sm", math.floor(elapsed/6)/10),
|
|
||||||
}
|
|
||||||
end
|
|
||||||
|
|
||||||
Event.onInterval(1, function()
|
Event.onInterval(1, function()
|
||||||
page.grid:update()
|
page.grid:update()
|
||||||
@@ -66,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,111 +33,91 @@ 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),
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
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',
|
|
||||||
},
|
|
||||||
--[[
|
|
||||||
groupLabel = UI.Text {
|
|
||||||
x = 3, y = 3,
|
|
||||||
value = 'Group'
|
|
||||||
},
|
|
||||||
group = UI.TextEntry {
|
|
||||||
x = 12, ex = -3, y = 3,
|
|
||||||
limit = 32,
|
|
||||||
shadowText = 'network group',
|
|
||||||
},
|
|
||||||
]]
|
|
||||||
intro = UI.TextArea {
|
|
||||||
textColor = colors.yellow,
|
|
||||||
inactive = true,
|
|
||||||
x = 3, ex = -3, y = 5, ey = -3,
|
|
||||||
value = string.format(passwordIntro, Ansi.white),
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
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),
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
notification = UI.Notification { },
|
notification = UI.Notification { },
|
||||||
}
|
}
|
||||||
|
|
||||||
function page.wizard.pages.label:validate()
|
|
||||||
os.setComputerLabel(self.label.value)
|
|
||||||
return true
|
|
||||||
end
|
|
||||||
|
|
||||||
function page.wizard.pages.password:validate()
|
|
||||||
if type(self.newPass.value) == "string" and #self.newPass.value > 0 then
|
|
||||||
Security.updatePassword(SHA.compute(self.newPass.value))
|
|
||||||
end
|
|
||||||
--[[
|
|
||||||
if #self.group.value > 0 then
|
|
||||||
local config = Config.load('os')
|
|
||||||
config.group = self.group.value
|
|
||||||
Config.update('os', config)
|
|
||||||
end
|
|
||||||
]]
|
|
||||||
return true
|
|
||||||
end
|
|
||||||
|
|
||||||
function page:eventHandler(event)
|
function page:eventHandler(event)
|
||||||
if event.type == 'skip' then
|
if event.type == 'skip' then
|
||||||
self.wizard:emit({ type = 'nextView' })
|
self.wizard:emit({ type = 'nextView' })
|
||||||
@@ -149,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)
|
||||||
@@ -158,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
|
||||||
22
sys/apps/genotp.lua
Normal file
22
sys/apps/genotp.lua
Normal file
@@ -0,0 +1,22 @@
|
|||||||
|
local SHA = require("opus.crypto.sha2")
|
||||||
|
|
||||||
|
local acceptableCharacters = {}
|
||||||
|
for c = 0, 127 do
|
||||||
|
local char = string.char(c)
|
||||||
|
-- exclude potentially ambiguous characters
|
||||||
|
if char:match("[1-9a-zA-Z]") and char:match("[^OIl]") then
|
||||||
|
table.insert(acceptableCharacters, char)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
local acceptableCharactersLen = #acceptableCharacters
|
||||||
|
local password = ""
|
||||||
|
|
||||||
|
for i = 1, 10 do
|
||||||
|
password = password .. acceptableCharacters[math.random(acceptableCharactersLen)]
|
||||||
|
end
|
||||||
|
|
||||||
|
os.queueEvent("set_otp", SHA.compute(password))
|
||||||
|
|
||||||
|
print("This allows one other device to permanently gain access to this device.")
|
||||||
|
print("Use the trust settings in System to revert this.")
|
||||||
|
print("Your one-time password is: " .. password)
|
||||||
195
sys/apps/inspect.lua
Normal file
195
sys/apps/inspect.lua
Normal file
@@ -0,0 +1,195 @@
|
|||||||
|
local UI = require('opus.ui')
|
||||||
|
local Util = require('opus.util')
|
||||||
|
|
||||||
|
local colors = _G.colors
|
||||||
|
local multishell = _ENV.multishell
|
||||||
|
|
||||||
|
local name = ({ ... })[1] or error('Syntax: inspect COMPONENT')
|
||||||
|
local events = { }
|
||||||
|
local page, lastEvent, focused
|
||||||
|
|
||||||
|
local function isRelevant(el)
|
||||||
|
return page.testContainer == el or el.parent and isRelevant(el.parent)
|
||||||
|
end
|
||||||
|
|
||||||
|
local emitter = UI.Window.emit
|
||||||
|
function UI.Window:emit(event)
|
||||||
|
if event ~= lastEvent and isRelevant(self) then
|
||||||
|
lastEvent = event
|
||||||
|
local t = { }
|
||||||
|
for k,v in pairs(event) do
|
||||||
|
if k ~= 'type' and k ~= 'recorded' then
|
||||||
|
table.insert(t, k .. ':' .. (type(v) == 'table' and (v.UIElement and v.uid or 'tbl') or tostring(v)))
|
||||||
|
end
|
||||||
|
end
|
||||||
|
table.insert(events, 1, { type = event.type, value = table.concat(t, ' '), raw = event })
|
||||||
|
while #events > 20 do
|
||||||
|
table.remove(events)
|
||||||
|
end
|
||||||
|
page.tabs.events.grid:update()
|
||||||
|
if page.tabs.events.enabled then
|
||||||
|
page.tabs.events.grid:draw()
|
||||||
|
end
|
||||||
|
end
|
||||||
|
return emitter(self, event)
|
||||||
|
end
|
||||||
|
|
||||||
|
-- do not load component until emit hook is in place
|
||||||
|
local component = UI[name] and UI[name]() or error('Invalid component')
|
||||||
|
if not component.example then
|
||||||
|
error('No example present')
|
||||||
|
end
|
||||||
|
|
||||||
|
page = UI.Page {
|
||||||
|
testContainer = UI.Window {
|
||||||
|
ey = '50%',
|
||||||
|
testing = component.example(),
|
||||||
|
},
|
||||||
|
tabs = UI.Tabs {
|
||||||
|
backgroundColor = colors.red,
|
||||||
|
y = '50%',
|
||||||
|
properties = UI.Tab {
|
||||||
|
title = 'Properties',
|
||||||
|
grid = UI.ScrollingGrid {
|
||||||
|
headerBackgroundColor = colors.red,
|
||||||
|
sortColumn = 'key',
|
||||||
|
columns = {
|
||||||
|
{ heading = 'key', key = 'key' },
|
||||||
|
{ heading = 'value', key = 'value', }
|
||||||
|
},
|
||||||
|
accelerators = {
|
||||||
|
grid_select = 'edit_property',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
methodsTab = UI.Tab {
|
||||||
|
index = 2,
|
||||||
|
title = 'Methods',
|
||||||
|
grid = UI.ScrollingGrid {
|
||||||
|
ex = '50%',
|
||||||
|
headerBackgroundColor = colors.red,
|
||||||
|
sortColumn = 'key',
|
||||||
|
columns = {
|
||||||
|
{ heading = 'key', key = 'key' },
|
||||||
|
},
|
||||||
|
},
|
||||||
|
docs = UI.TextArea {
|
||||||
|
x = '50%',
|
||||||
|
backgroundColor = colors.black,
|
||||||
|
},
|
||||||
|
eventHandler = function (self, event)
|
||||||
|
if event.type == 'grid_focus_row' and focused then
|
||||||
|
self.docs:setText(focused:getDoc(event.selected.key) or '')
|
||||||
|
end
|
||||||
|
end,
|
||||||
|
},
|
||||||
|
events = UI.Tab {
|
||||||
|
index = 1,
|
||||||
|
title = 'Events',
|
||||||
|
UI.MenuBar {
|
||||||
|
y = -1,
|
||||||
|
backgroundColor = colors.red,
|
||||||
|
buttons = {
|
||||||
|
{ text = 'Clear' },
|
||||||
|
}
|
||||||
|
},
|
||||||
|
grid = UI.ScrollingGrid {
|
||||||
|
ey = -2,
|
||||||
|
headerBackgroundColor = colors.red,
|
||||||
|
values = events,
|
||||||
|
autospace = true,
|
||||||
|
columns = {
|
||||||
|
{ heading = 'type', key = 'type' },
|
||||||
|
{ heading = 'value', key = 'value', }
|
||||||
|
},
|
||||||
|
},
|
||||||
|
eventHandler = function (self, event)
|
||||||
|
if event.type == 'button_press' then
|
||||||
|
Util.clear(self.grid.values)
|
||||||
|
self.grid:update()
|
||||||
|
self.grid:draw()
|
||||||
|
|
||||||
|
elseif event.type == 'grid_select' then
|
||||||
|
multishell.openTab(_ENV, {
|
||||||
|
path = 'sys/apps/Lua.lua',
|
||||||
|
args = { event.selected.raw },
|
||||||
|
focused = true,
|
||||||
|
})
|
||||||
|
end
|
||||||
|
end
|
||||||
|
}
|
||||||
|
},
|
||||||
|
editor = UI.SlideOut {
|
||||||
|
y = -4, height = 4,
|
||||||
|
backgroundColor = colors.green,
|
||||||
|
titleBar = UI.TitleBar {
|
||||||
|
event = 'editor_cancel',
|
||||||
|
title = 'Enter value',
|
||||||
|
},
|
||||||
|
entry = UI.TextEntry {
|
||||||
|
y = 3, x = 2, ex = 10,
|
||||||
|
accelerators = {
|
||||||
|
enter = 'editor_apply',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
accelerators = {
|
||||||
|
['shift-right'] = 'size',
|
||||||
|
['shift-left' ] = 'size',
|
||||||
|
['shift-up' ] = 'size',
|
||||||
|
['shift-down' ] = 'size',
|
||||||
|
},
|
||||||
|
eventHandler = function (self, event)
|
||||||
|
if event.type == 'focus_change' and isRelevant(event.focused) then
|
||||||
|
focused = event.focused
|
||||||
|
local t = { }
|
||||||
|
for k,v in pairs(event.focused) do
|
||||||
|
table.insert(t, {
|
||||||
|
key = k,
|
||||||
|
value = tostring(v),
|
||||||
|
})
|
||||||
|
end
|
||||||
|
self.tabs.properties.grid:setValues(t)
|
||||||
|
self.tabs.properties.grid:draw()
|
||||||
|
|
||||||
|
t = { }
|
||||||
|
for k,v in pairs(getmetatable(event.focused)) do
|
||||||
|
if type(v) == 'function' then
|
||||||
|
table.insert(t, {
|
||||||
|
key = k,
|
||||||
|
})
|
||||||
|
end
|
||||||
|
end
|
||||||
|
self.tabs.methodsTab.grid:setValues(t)
|
||||||
|
self.tabs.methodsTab.grid:draw()
|
||||||
|
|
||||||
|
elseif event.type == 'edit_property' then
|
||||||
|
self.editor.entry.value = event.selected.value
|
||||||
|
self.editor:show()
|
||||||
|
|
||||||
|
elseif event.type == 'editor_cancel' then
|
||||||
|
self.editor:hide()
|
||||||
|
|
||||||
|
elseif event.type == 'editor_apply' then
|
||||||
|
self.editor:hide()
|
||||||
|
|
||||||
|
elseif event.type == 'size' then
|
||||||
|
local sizing = {
|
||||||
|
['shift-right'] = { 1, 0 },
|
||||||
|
['shift-left' ] = { -1, 0 },
|
||||||
|
['shift-up' ] = { 0, -1 },
|
||||||
|
['shift-down' ] = { 0, 1 },
|
||||||
|
}
|
||||||
|
self.ox = math.max(self.ox + sizing[event.ie.code][1], 1)
|
||||||
|
self.oy = math.max(self.oy + sizing[event.ie.code][2], 1)
|
||||||
|
UI.term:clear()
|
||||||
|
self:resize()
|
||||||
|
self:draw()
|
||||||
|
end
|
||||||
|
|
||||||
|
return UI.Page.eventHandler(self, event)
|
||||||
|
end
|
||||||
|
}
|
||||||
|
|
||||||
|
UI:setPage(page)
|
||||||
|
UI:start()
|
||||||
@@ -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')
|
||||||
|
|
||||||
|
|||||||
@@ -10,30 +10,30 @@ local keyPairs = { }
|
|||||||
local function generateKeyPair()
|
local function generateKeyPair()
|
||||||
local key = { }
|
local key = { }
|
||||||
for _ = 1, 32 do
|
for _ = 1, 32 do
|
||||||
table.insert(key, ("%02x"):format(math.random(0, 0xFF)))
|
table.insert(key, math.random(0, 0xFF))
|
||||||
end
|
end
|
||||||
local privateKey = Util.hexToByteArray(table.concat(key))
|
local privateKey = setmetatable(key, Util.byteArrayMT)
|
||||||
return privateKey, ECC.publicKey(privateKey)
|
return privateKey, ECC.publicKey(privateKey)
|
||||||
end
|
end
|
||||||
|
|
||||||
getmetatable(network).__index.getKeyPair = function()
|
getmetatable(network).__index.getKeyPair = function()
|
||||||
local keys = table.remove(keyPairs)
|
local keys = table.remove(keyPairs)
|
||||||
os.queueEvent('generate_keypair')
|
os.queueEvent('generate_keypair')
|
||||||
if not keys then
|
if not keys then
|
||||||
return generateKeyPair()
|
return generateKeyPair()
|
||||||
end
|
end
|
||||||
return table.unpack(keys)
|
return table.unpack(keys)
|
||||||
end
|
end
|
||||||
|
|
||||||
-- Generate key pairs in the background as this is a time-consuming process
|
-- Generate key pairs in the background as this is a time-consuming process
|
||||||
Event.on('generate_keypair', function()
|
Event.on('generate_keypair', function()
|
||||||
while true do
|
while true do
|
||||||
os.sleep(5)
|
os.sleep(5)
|
||||||
local timer = Util.timer()
|
local timer = Util.timer()
|
||||||
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)
|
||||||
|
|||||||
@@ -59,6 +59,10 @@ local function sambaConnection(socket)
|
|||||||
print('samba: Connection closed')
|
print('samba: Connection closed')
|
||||||
end
|
end
|
||||||
|
|
||||||
|
local function sanitizeLabel(computer)
|
||||||
|
return (computer.id.."_"..computer.label:gsub("[%c%.\"'/%*]", "")):sub(1, 40)
|
||||||
|
end
|
||||||
|
|
||||||
Event.addRoutine(function()
|
Event.addRoutine(function()
|
||||||
print('samba: listening on port 139')
|
print('samba: listening on port 139')
|
||||||
|
|
||||||
@@ -79,10 +83,10 @@ Event.addRoutine(function()
|
|||||||
end)
|
end)
|
||||||
|
|
||||||
Event.on('network_attach', function(_, computer)
|
Event.on('network_attach', function(_, computer)
|
||||||
fs.mount(fs.combine('network', computer.label), 'netfs', computer.id)
|
fs.mount(fs.combine('network', sanitizeLabel(computer)), 'netfs', computer.id)
|
||||||
end)
|
end)
|
||||||
|
|
||||||
Event.on('network_detach', function(_, computer)
|
Event.on('network_detach', function(_, computer)
|
||||||
print('samba: detaching ' .. computer.label)
|
print('samba: detaching ' .. sanitizeLabel(computer))
|
||||||
fs.unmount(fs.combine('network', computer.label))
|
fs.unmount(fs.combine('network', sanitizeLabel(computer)))
|
||||||
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)
|
||||||
@@ -159,7 +152,7 @@ local function getSlots()
|
|||||||
end
|
end
|
||||||
|
|
||||||
local function sendInfo()
|
local function sendInfo()
|
||||||
if os.clock() - infoTimer >= 1 then -- don't flood
|
if os.clock() - infoTimer >= 5 then -- don't flood
|
||||||
infoTimer = os.clock()
|
infoTimer = os.clock()
|
||||||
info.label = os.getComputerLabel()
|
info.label = os.getComputerLabel()
|
||||||
info.uptime = math.floor(os.clock())
|
info.uptime = math.floor(os.clock())
|
||||||
@@ -201,16 +194,25 @@ local function sendInfo()
|
|||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
-- every 10 seconds, send out this computer's info
|
local function cleanNetwork()
|
||||||
Event.onInterval(10, function()
|
|
||||||
sendInfo()
|
|
||||||
for _,c in pairs(_G.network) do
|
for _,c in pairs(_G.network) do
|
||||||
local elapsed = os.clock()-c.timestamp
|
local elapsed = os.clock()-c.timestamp
|
||||||
if c.active and elapsed > 15 then
|
if c.active and elapsed > 50 then
|
||||||
c.active = false
|
c.active = false
|
||||||
os.queueEvent('network_detach', c)
|
os.queueEvent('network_detach', c)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
-- every 30 seconds, send out this computer's info
|
||||||
|
-- send with offset so that messages are evenly distributed and do not all come at once
|
||||||
|
Event.onTimeout(math.random() * 30, function()
|
||||||
|
sendInfo()
|
||||||
|
cleanNetwork()
|
||||||
|
Event.onInterval(30, function()
|
||||||
|
sendInfo()
|
||||||
|
cleanNetwork()
|
||||||
|
end)
|
||||||
end)
|
end)
|
||||||
|
|
||||||
Event.on('turtle_response', function()
|
Event.on('turtle_response', function()
|
||||||
@@ -220,4 +222,5 @@ Event.on('turtle_response', function()
|
|||||||
end
|
end
|
||||||
end)
|
end)
|
||||||
|
|
||||||
Event.onTimeout(1, sendInfo)
|
-- send info early so that computers show soon after booting
|
||||||
|
Event.onTimeout(math.random() * 2 + 1, sendInfo)
|
||||||
|
|||||||
@@ -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
|
||||||
@@ -586,18 +546,18 @@ local function shellRead(history)
|
|||||||
end
|
end
|
||||||
local _,cy = term.getCursorPos()
|
local _,cy = term.getCursorPos()
|
||||||
term.setCursorPos(3, cy)
|
term.setCursorPos(3, cy)
|
||||||
|
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()
|
||||||
@@ -635,6 +595,7 @@ local function shellRead(history)
|
|||||||
redraw()
|
redraw()
|
||||||
|
|
||||||
elseif ie.code == 'tab' then
|
elseif ie.code == 'tab' then
|
||||||
|
entry.value = entry.value or ''
|
||||||
if entry.pos == #entry.value then
|
if entry.pos == #entry.value then
|
||||||
local cline = autocomplete(entry.value)
|
local cline = autocomplete(entry.value)
|
||||||
if cline then
|
if cline then
|
||||||
@@ -648,8 +609,14 @@ 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 ''
|
||||||
if entry.textChanged then
|
if entry.textChanged then
|
||||||
redraw()
|
redraw()
|
||||||
elseif entry.posChanged then
|
elseif entry.posChanged then
|
||||||
@@ -658,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()
|
||||||
@@ -665,30 +633,26 @@ local function shellRead(history)
|
|||||||
end
|
end
|
||||||
|
|
||||||
print()
|
print()
|
||||||
term.setCursorBlink( false )
|
term.setCursorBlink(false)
|
||||||
return entry.value
|
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
|
||||||
@@ -700,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:
|
||||||
@@ -42,7 +42,7 @@ To obtain a key, visit:
|
|||||||
|
|
||||||
function tab:eventHandler(event)
|
function tab:eventHandler(event)
|
||||||
if event.type == 'update_key' then
|
if event.type == 'update_key' then
|
||||||
if #self.key.value > 0 then
|
if self.key.value then
|
||||||
config.key = self.key.value
|
config.key = self.key.value
|
||||||
else
|
else
|
||||||
config.key = nil
|
config.key = nil
|
||||||
|
|||||||
@@ -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,46 +4,47 @@ 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 = ("%d.%d"):format(Util.getVersion()) },
|
||||||
{ name = 'Lua version', value = _VERSION },
|
{ name = 'Lua version', value = _VERSION },
|
||||||
{ name = 'MC version', value = Util.getMinecraftVersion() },
|
{ name = 'MC version', value = Util.getMinecraftVersion() },
|
||||||
{ name = 'Disk free', value = Util.toBytes(fs.getFreeSpace('/')) },
|
{ name = 'Disk free', value = Util.toBytes(fs.getFreeSpace('/')) },
|
||||||
{ 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' 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,
|
||||||
@@ -59,7 +61,7 @@ function tab:save()
|
|||||||
end
|
end
|
||||||
|
|
||||||
function tab:eventHandler(event)
|
function tab:eventHandler(event)
|
||||||
if event.type == 'update_path' then
|
if event.type == 'update_path' and self.entry.value then
|
||||||
table.insert(self.grid.values, {
|
table.insert(self.grid.values, {
|
||||||
value = self.entry.value,
|
value = self.entry.value,
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -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)
|
||||||
|
|||||||
@@ -1,3 +1,5 @@
|
|||||||
|
local fs = _G.fs
|
||||||
|
|
||||||
local function completeMultipleChoice(sText, tOptions, bAddSpaces)
|
local function completeMultipleChoice(sText, tOptions, bAddSpaces)
|
||||||
local tResults = { }
|
local tResults = { }
|
||||||
for n = 1,#tOptions do
|
for n = 1,#tOptions do
|
||||||
@@ -20,3 +22,14 @@ _ENV.shell.setCompletionFunction("sys/apps/package.lua",
|
|||||||
return completeMultipleChoice(text, { "install ", "update ", "uninstall ", "updateall ", "refresh" })
|
return completeMultipleChoice(text, { "install ", "update ", "uninstall ", "updateall ", "refresh" })
|
||||||
end
|
end
|
||||||
end)
|
end)
|
||||||
|
|
||||||
|
_ENV.shell.setCompletionFunction("sys/apps/inspect.lua",
|
||||||
|
function(_, index, text)
|
||||||
|
if index == 1 then
|
||||||
|
local components = { }
|
||||||
|
for _, f in pairs(fs.list('sys/modules/opus/ui/components')) do
|
||||||
|
table.insert(components, (f:gsub("%.lua$", "")))
|
||||||
|
end
|
||||||
|
return completeMultipleChoice(text, components)
|
||||||
|
end
|
||||||
|
end)
|
||||||
|
|||||||
@@ -4,58 +4,44 @@ local kernel = _G.kernel
|
|||||||
local keyboard = _G.device.keyboard
|
local keyboard = _G.device.keyboard
|
||||||
local multishell = _ENV.multishell
|
local multishell = _ENV.multishell
|
||||||
|
|
||||||
if not multishell or not multishell.getTabs then
|
if multishell and multishell.getTabs then
|
||||||
return
|
-- restart tab
|
||||||
end
|
keyboard.addHotkey('control-backspace', function()
|
||||||
|
local tab = kernel.getFocused()
|
||||||
-- overview
|
if tab and not tab.noTerminate then
|
||||||
keyboard.addHotkey('control-o', function()
|
multishell.terminate(tab.uid)
|
||||||
for _,tab in pairs(multishell.getTabs()) do
|
multishell.openTab(tab.env, {
|
||||||
if tab.isOverview then
|
path = tab.path,
|
||||||
multishell.setFocus(tab.uid)
|
args = tab.args,
|
||||||
|
focused = true,
|
||||||
|
})
|
||||||
end
|
end
|
||||||
end
|
end)
|
||||||
end)
|
end
|
||||||
|
|
||||||
-- restart tab
|
|
||||||
keyboard.addHotkey('control-backspace', function()
|
|
||||||
local uid = multishell.getFocus()
|
|
||||||
local tab = kernel.find(uid)
|
|
||||||
if not tab.isOverview then
|
|
||||||
multishell.terminate(uid)
|
|
||||||
multishell.openTab({
|
|
||||||
path = tab.path,
|
|
||||||
env = tab.env,
|
|
||||||
args = tab.args,
|
|
||||||
focused = true,
|
|
||||||
})
|
|
||||||
end
|
|
||||||
end)
|
|
||||||
|
|
||||||
-- next tab
|
-- next tab
|
||||||
keyboard.addHotkey('control-tab', function()
|
keyboard.addHotkey('control-tab', function()
|
||||||
local tabs = multishell.getTabs()
|
|
||||||
local visibleTabs = { }
|
local visibleTabs = { }
|
||||||
local currentTabId = multishell.getFocus()
|
local currentTab = kernel.getFocused()
|
||||||
|
|
||||||
local function compareTab(a, b)
|
local function compareTab(a, b)
|
||||||
return a.uid < b.uid
|
return a.uid < b.uid
|
||||||
end
|
end
|
||||||
for _,tab in Util.spairs(tabs, compareTab) do
|
for _,tab in Util.spairs(kernel.routines, compareTab) do
|
||||||
if not tab.hidden and not tab.noFocus then
|
if not tab.hidden and not tab.noFocus then
|
||||||
table.insert(visibleTabs, tab)
|
table.insert(visibleTabs, tab)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
for k,tab in ipairs(visibleTabs) do
|
for k,tab in ipairs(visibleTabs) do
|
||||||
if tab.uid == currentTabId then
|
if tab.uid == currentTab.uid then
|
||||||
if k < #visibleTabs then
|
if k < #visibleTabs then
|
||||||
multishell.setFocus(visibleTabs[k + 1].uid)
|
kernel.raise(visibleTabs[k + 1].uid)
|
||||||
return
|
return
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
if #visibleTabs > 0 then
|
if #visibleTabs > 0 then
|
||||||
multishell.setFocus(visibleTabs[1].uid)
|
kernel.raise(visibleTabs[1].uid)
|
||||||
end
|
end
|
||||||
end)
|
end)
|
||||||
|
|||||||
@@ -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,16 +39,9 @@ local function systemLog()
|
|||||||
keyboard.removeHotkey('control-d')
|
keyboard.removeHotkey('control-d')
|
||||||
end
|
end
|
||||||
|
|
||||||
if multishell and multishell.openTab then
|
kernel.run(_ENV, {
|
||||||
multishell.openTab({
|
title = 'System Log',
|
||||||
title = 'System Log',
|
fn = systemLog,
|
||||||
fn = systemLog,
|
noTerminate = true,
|
||||||
noTerminate = true,
|
hidden = true,
|
||||||
hidden = true,
|
})
|
||||||
})
|
|
||||||
else
|
|
||||||
kernel.run({
|
|
||||||
title = 'Syslog',
|
|
||||||
fn = systemLog,
|
|
||||||
})
|
|
||||||
end
|
|
||||||
|
|||||||
@@ -1,20 +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')
|
|
||||||
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
|
||||||
@@ -355,4 +394,4 @@ function fs.restore()
|
|||||||
local native = fs.native
|
local native = fs.native
|
||||||
Util.clear(fs)
|
Util.clear(fs)
|
||||||
Util.merge(fs, native)
|
Util.merge(fs, native)
|
||||||
end
|
end
|
||||||
@@ -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,19 +8,18 @@ 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
|
||||||
local w,h = parentTerm.getSize()
|
local w,h = parentTerm.getSize()
|
||||||
local overviewId
|
local overviewId
|
||||||
local tabsDirty = false
|
local tabsDirty = false
|
||||||
local closeInd = Util.getVersion() >= 1.76 and '\215' or '*'
|
local closeInd = Util.supportsExtChars() 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)
|
||||||
|
|||||||
@@ -12,12 +12,11 @@ local band = bit32.band
|
|||||||
local blshift = bit32.lshift
|
local blshift = bit32.lshift
|
||||||
local brshift = bit32.arshift
|
local brshift = bit32.arshift
|
||||||
local textutils = _G.textutils
|
local textutils = _G.textutils
|
||||||
|
local mt = Util.byteArrayMT
|
||||||
|
|
||||||
local mod = 2^32
|
local mod = 2^32
|
||||||
local tau = {("expand 16-byte k"):byte(1,-1)}
|
local tau = {("expand 16-byte k"):byte(1,-1)}
|
||||||
local sigma = {("expand 32-byte k"):byte(1,-1)}
|
local sigma = {("expand 32-byte k"):byte(1,-1)}
|
||||||
local null32 = {("A"):rep(32):byte(1,-1)}
|
|
||||||
local null12 = {("A"):rep(12):byte(1,-1)}
|
|
||||||
|
|
||||||
local function rotl(n, b)
|
local function rotl(n, b)
|
||||||
local s = n/(2^(32-b))
|
local s = n/(2^(32-b))
|
||||||
@@ -91,22 +90,6 @@ local function serialize(state)
|
|||||||
return r
|
return r
|
||||||
end
|
end
|
||||||
|
|
||||||
local mt = {
|
|
||||||
__tostring = function(a) return string.char(table.unpack(a)) end,
|
|
||||||
__index = {
|
|
||||||
toHex = function(self) return ("%02x"):rep(#self):format(table.unpack(self)) end,
|
|
||||||
isEqual = function(self, t)
|
|
||||||
if type(t) ~= "table" then return false end
|
|
||||||
if #self ~= #t then return false end
|
|
||||||
local ret = 0
|
|
||||||
for i = 1, #self do
|
|
||||||
ret = bit32.bor(ret, bxor(self[i], t[i]))
|
|
||||||
end
|
|
||||||
return ret == 0
|
|
||||||
end
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
local function crypt(data, key, nonce, cntr, round)
|
local function crypt(data, key, nonce, cntr, round)
|
||||||
assert(type(key) == "table", "ChaCha20: Invalid key format ("..type(key).."), must be table")
|
assert(type(key) == "table", "ChaCha20: Invalid key format ("..type(key).."), must be table")
|
||||||
assert(type(nonce) == "table", "ChaCha20: Invalid nonce format ("..type(nonce).."), must be table")
|
assert(type(nonce) == "table", "ChaCha20: Invalid nonce format ("..type(nonce).."), must be table")
|
||||||
@@ -117,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)
|
||||||
@@ -133,15 +116,12 @@ local function crypt(data, key, nonce, cntr, round)
|
|||||||
out[#out+1] = bxor(block[j], ks[j])
|
out[#out+1] = bxor(block[j], ks[j])
|
||||||
end
|
end
|
||||||
|
|
||||||
--if i % 1000 == 0 then
|
throttle()
|
||||||
throttle()
|
|
||||||
--os.queueEvent("")
|
|
||||||
--os.pullEvent("")
|
|
||||||
--end
|
|
||||||
end
|
end
|
||||||
return setmetatable(out, mt)
|
return setmetatable(out, mt)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
-- Helper functions
|
||||||
local function genNonce(len)
|
local function genNonce(len)
|
||||||
local nonce = {}
|
local nonce = {}
|
||||||
for i = 1, len do
|
for i = 1, len do
|
||||||
@@ -170,6 +150,9 @@ end
|
|||||||
local obj = {}
|
local obj = {}
|
||||||
local rng_mt = {['__index'] = obj}
|
local rng_mt = {['__index'] = obj}
|
||||||
|
|
||||||
|
-- PRNG object
|
||||||
|
local null32 = {("A"):rep(32):byte(1,-1)}
|
||||||
|
local null12 = {("A"):rep(12):byte(1,-1)}
|
||||||
function obj:nextInt(byte)
|
function obj:nextInt(byte)
|
||||||
if not byte or byte < 1 or byte > 6 then error("Can only return 1-6 bytes", 2) end
|
if not byte or byte < 1 or byte > 6 then error("Can only return 1-6 bytes", 2) end
|
||||||
local output = 0
|
local output = 0
|
||||||
|
|||||||
@@ -1,9 +1,12 @@
|
|||||||
local fq = require('opus.crypto.ecc.fq')
|
local fq = require('opus.crypto.ecc.fq')
|
||||||
local elliptic = require('opus.crypto.ecc.elliptic')
|
local elliptic = require('opus.crypto.ecc.elliptic')
|
||||||
local sha256 = require('opus.crypto.sha2')
|
local sha256 = require('opus.crypto.sha2')
|
||||||
|
local Util = require('opus.util')
|
||||||
|
|
||||||
|
|
||||||
local os = _G.os
|
local os = _G.os
|
||||||
local unpack = table.unpack
|
local unpack = table.unpack
|
||||||
|
local mt = Util.byteArrayMT
|
||||||
|
|
||||||
local q = {1372, 62520, 47765, 8105, 45059, 9616, 65535, 65535, 65535, 65535, 65535, 65532}
|
local q = {1372, 62520, 47765, 8105, 45059, 9616, 65535, 65535, 65535, 65535, 65535, 65532}
|
||||||
|
|
||||||
@@ -27,7 +30,7 @@ local function publicKey(sk)
|
|||||||
local Y = elliptic.scalarMulG(x)
|
local Y = elliptic.scalarMulG(x)
|
||||||
local pk = elliptic.pointEncode(Y)
|
local pk = elliptic.pointEncode(Y)
|
||||||
|
|
||||||
return pk
|
return setmetatable(pk, mt)
|
||||||
end
|
end
|
||||||
|
|
||||||
local function exchange(sk, pk)
|
local function exchange(sk, pk)
|
||||||
@@ -62,7 +65,7 @@ local function sign(sk, message)
|
|||||||
sig[#sig + 1] = s[i]
|
sig[#sig + 1] = s[i]
|
||||||
end
|
end
|
||||||
|
|
||||||
return sig
|
return setmetatable(sig, mt)
|
||||||
end
|
end
|
||||||
|
|
||||||
local function verify(pk, message, sig)
|
local function verify(pk, message, sig)
|
||||||
|
|||||||
@@ -9,6 +9,7 @@ local bnot = bit32 and bit32.bnot or bit.bnot
|
|||||||
local bxor = bit32 and bit32.bxor or bit.bxor
|
local bxor = bit32 and bit32.bxor or bit.bxor
|
||||||
local blshift = bit32 and bit32.lshift or bit.blshift
|
local blshift = bit32 and bit32.lshift or bit.blshift
|
||||||
local upack = unpack or table.unpack
|
local upack = unpack or table.unpack
|
||||||
|
local mt = Util.byteArrayMT
|
||||||
|
|
||||||
local function rrotate(n, b)
|
local function rrotate(n, b)
|
||||||
local s = n/(2^b)
|
local s = n/(2^b)
|
||||||
@@ -68,17 +69,16 @@ end
|
|||||||
|
|
||||||
local function digestblock(w, C)
|
local function digestblock(w, C)
|
||||||
for j = 17, 64 do
|
for j = 17, 64 do
|
||||||
-- local v = w[j-15]
|
local s0 = bxor(rrotate(w[j-15], 7), rrotate(w[j-15], 18), brshift(w[j-15], 3))
|
||||||
local s0 = bxor(bxor(rrotate(w[j-15], 7), rrotate(w[j-15], 18)), brshift(w[j-15], 3))
|
local s1 = bxor(rrotate(w[j-2], 17), rrotate(w[j-2], 19), brshift(w[j-2], 10))
|
||||||
local s1 = bxor(bxor(rrotate(w[j-2], 17), rrotate(w[j-2], 19)), brshift(w[j-2], 10))
|
|
||||||
w[j] = (w[j-16] + s0 + w[j-7] + s1)%mod32
|
w[j] = (w[j-16] + s0 + w[j-7] + s1)%mod32
|
||||||
end
|
end
|
||||||
local a, b, c, d, e, f, g, h = upack(C)
|
local a, b, c, d, e, f, g, h = upack(C)
|
||||||
for j = 1, 64 do
|
for j = 1, 64 do
|
||||||
local S1 = bxor(bxor(rrotate(e, 6), rrotate(e, 11)), rrotate(e, 25))
|
local S1 = bxor(rrotate(e, 6), rrotate(e, 11), rrotate(e, 25))
|
||||||
local ch = bxor(band(e, f), band(bnot(e), g))
|
local ch = bxor(band(e, f), band(bnot(e), g))
|
||||||
local temp1 = (h + S1 + ch + K[j] + w[j])%mod32
|
local temp1 = (h + S1 + ch + K[j] + w[j])%mod32
|
||||||
local S0 = bxor(bxor(rrotate(a, 2), rrotate(a, 13)), rrotate(a, 22))
|
local S0 = bxor(rrotate(a, 2), rrotate(a, 13), rrotate(a, 22))
|
||||||
local maj = bxor(bxor(band(a, b), band(a, c)), band(b, c))
|
local maj = bxor(bxor(band(a, b), band(a, c)), band(b, c))
|
||||||
local temp2 = (S0 + maj)%mod32
|
local temp2 = (S0 + maj)%mod32
|
||||||
h, g, f, e, d, c, b, a = g, f, e, (d+temp1)%mod32, c, b, a, (temp1+temp2)%mod32
|
h, g, f, e, d, c, b, a = g, f, e, (d+temp1)%mod32, c, b, a, (temp1+temp2)%mod32
|
||||||
@@ -94,22 +94,6 @@ local function digestblock(w, C)
|
|||||||
return C
|
return C
|
||||||
end
|
end
|
||||||
|
|
||||||
local mt = {
|
|
||||||
__tostring = function(a) return string.char(upack(a)) end,
|
|
||||||
__index = {
|
|
||||||
toHex = function(self) return ("%02x"):rep(#self):format(upack(self)) end,
|
|
||||||
isEqual = function(self, t)
|
|
||||||
if type(t) ~= "table" then return false end
|
|
||||||
if #self ~= #t then return false end
|
|
||||||
local ret = 0
|
|
||||||
for i = 1, #self do
|
|
||||||
ret = bit32.bor(ret, bxor(self[i], t[i]))
|
|
||||||
end
|
|
||||||
return ret == 0
|
|
||||||
end
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
local function toBytes(t, n)
|
local function toBytes(t, n)
|
||||||
local b = {}
|
local b = {}
|
||||||
for i = 1, n do
|
for i = 1, n do
|
||||||
|
|||||||
@@ -2,67 +2,87 @@ local class = require('opus.class')
|
|||||||
|
|
||||||
local os = _G.os
|
local os = _G.os
|
||||||
|
|
||||||
|
-- convert value to a string (supporting nils or numbers in value)
|
||||||
|
local function _val(a)
|
||||||
|
return a and tostring(a) or ''
|
||||||
|
end
|
||||||
|
|
||||||
local Entry = class()
|
local Entry = class()
|
||||||
|
|
||||||
function Entry:init(args)
|
function Entry:init(args)
|
||||||
self.pos = 0
|
self.pos = 0
|
||||||
self.scroll = 0
|
self.scroll = 0
|
||||||
self.value = ''
|
self.value = args.value
|
||||||
self.width = args.width or 256
|
self.width = args.width or 256
|
||||||
self.limit = args.limit or 1024
|
self.limit = args.limit or 1024
|
||||||
self.mark = { }
|
self.mark = { }
|
||||||
self.offset = args.offset or 1
|
self.offset = args.offset or 1
|
||||||
|
self.transform = args.transform or function(a) return a end
|
||||||
end
|
end
|
||||||
|
|
||||||
function Entry:reset()
|
function Entry:reset()
|
||||||
self.pos = 0
|
self.pos = 0
|
||||||
self.scroll = 0
|
self.scroll = 0
|
||||||
self.value = ''
|
self.value = nil
|
||||||
self.mark = { }
|
self.mark = { }
|
||||||
end
|
end
|
||||||
|
|
||||||
function Entry:nextWord()
|
function Entry:nextWord()
|
||||||
return select(2, self.value:find("[%s%p]?%w[%s%p]", self.pos + 1)) or #self.value
|
local value = _val(self.value)
|
||||||
|
return select(2, value:find("[%s%p]?%w[%s%p]", self.pos + 1)) or #value
|
||||||
end
|
end
|
||||||
|
|
||||||
function Entry:prevWord()
|
function Entry:prevWord()
|
||||||
local x = #self.value - (self.pos - 1)
|
local value = _val(self.value)
|
||||||
local _, n = self.value:reverse():find("[%s%p]?%w[%s%p]", x)
|
local x = #value - (self.pos - 1)
|
||||||
return n and #self.value - n + 1 or 0
|
local _, n = value:reverse():find("[%s%p]?%w[%s%p]", x)
|
||||||
|
return n and #value - n + 1 or 0
|
||||||
end
|
end
|
||||||
|
|
||||||
function Entry:updateScroll()
|
function Entry:updateScroll()
|
||||||
local ps = self.scroll
|
local ps = self.scroll
|
||||||
if self.pos > #self.value then
|
local len = #_val(self.value)
|
||||||
self.pos = #self.value
|
if self.pos > len then
|
||||||
|
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
|
||||||
end
|
end
|
||||||
|
|
||||||
function Entry:copyText(cx, ex)
|
function Entry:copyText(cx, ex)
|
||||||
return self.value:sub(cx + 1, ex)
|
-- this should be transformed (ie. if number)
|
||||||
|
return _val(self.value):sub(cx + 1, ex)
|
||||||
end
|
end
|
||||||
|
|
||||||
function Entry:insertText(x, text)
|
function Entry:insertText(x, text)
|
||||||
if #self.value + #text > self.limit then
|
text = tostring(self.transform(text) or '')
|
||||||
text = text:sub(1, self.limit-#self.value)
|
if #text > 0 then
|
||||||
|
local value = _val(self.value)
|
||||||
|
if #value + #text > self.limit then
|
||||||
|
text = text:sub(1, self.limit-#value)
|
||||||
|
end
|
||||||
|
self.value = self.transform(value:sub(1, x) .. text .. value:sub(x + 1))
|
||||||
|
self.pos = self.pos + #text
|
||||||
end
|
end
|
||||||
self.value = self.value:sub(1, x) .. text .. self.value:sub(x + 1)
|
|
||||||
self.pos = self.pos + #text
|
|
||||||
end
|
end
|
||||||
|
|
||||||
function Entry:deleteText(sx, ex)
|
function Entry:deleteText(sx, ex)
|
||||||
local front = self.value:sub(1, sx)
|
local value = _val(self.value)
|
||||||
local back = self.value:sub(ex + 1, #self.value)
|
local front = value:sub(1, sx)
|
||||||
self.value = front .. back
|
local back = value:sub(ex + 1, #value)
|
||||||
|
self.value = self.transform(front .. back)
|
||||||
self.pos = sx
|
self.pos = sx
|
||||||
end
|
end
|
||||||
|
|
||||||
@@ -74,7 +94,7 @@ function Entry:moveLeft()
|
|||||||
end
|
end
|
||||||
|
|
||||||
function Entry:moveRight()
|
function Entry:moveRight()
|
||||||
if self.pos < #self.value then
|
if self.pos < #_val(self.value) then
|
||||||
self.pos = self.pos + 1
|
self.pos = self.pos + 1
|
||||||
return true
|
return true
|
||||||
end
|
end
|
||||||
@@ -88,14 +108,14 @@ function Entry:moveHome()
|
|||||||
end
|
end
|
||||||
|
|
||||||
function Entry:moveEnd()
|
function Entry:moveEnd()
|
||||||
if self.pos ~= #self.value then
|
if self.pos ~= #_val(self.value) then
|
||||||
self.pos = #self.value
|
self.pos = #_val(self.value)
|
||||||
return true
|
return true
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
function Entry:moveTo(ie)
|
function Entry:moveTo(ie)
|
||||||
self.pos = math.max(0, math.min(ie.x + self.scroll - self.offset, #self.value))
|
self.pos = math.max(0, math.min(ie.x + self.scroll - self.offset, #_val(self.value)))
|
||||||
end
|
end
|
||||||
|
|
||||||
function Entry:backspace()
|
function Entry:backspace()
|
||||||
@@ -107,7 +127,7 @@ function Entry:backspace()
|
|||||||
end
|
end
|
||||||
|
|
||||||
function Entry:moveWordRight()
|
function Entry:moveWordRight()
|
||||||
if self.pos < #self.value then
|
if self.pos < #_val(self.value) then
|
||||||
self.pos = self:nextWord(self.value, self.pos + 1)
|
self.pos = self:nextWord(self.value, self.pos + 1)
|
||||||
return true
|
return true
|
||||||
end
|
end
|
||||||
@@ -123,7 +143,7 @@ end
|
|||||||
function Entry:delete()
|
function Entry:delete()
|
||||||
if self.mark.active then
|
if self.mark.active then
|
||||||
self:deleteText(self.mark.x, self.mark.ex)
|
self:deleteText(self.mark.x, self.mark.ex)
|
||||||
elseif self.pos < #self.value then
|
elseif self.pos < #_val(self.value) then
|
||||||
self:deleteText(self.pos, self.pos + 1)
|
self:deleteText(self.pos, self.pos + 1)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
@@ -137,15 +157,16 @@ function Entry:cutFromStart()
|
|||||||
end
|
end
|
||||||
|
|
||||||
function Entry:cutToEnd()
|
function Entry:cutToEnd()
|
||||||
if self.pos < #self.value then
|
local value = _val(self.value)
|
||||||
local text = self:copyText(self.pos, #self.value)
|
if self.pos < #value then
|
||||||
self:deleteText(self.pos, #self.value)
|
local text = self:copyText(self.pos, #value)
|
||||||
|
self:deleteText(self.pos, #value)
|
||||||
os.queueEvent('clipboard_copy', text)
|
os.queueEvent('clipboard_copy', text)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
function Entry:cutNextWord()
|
function Entry:cutNextWord()
|
||||||
if self.pos < #self.value then
|
if self.pos < #_val(self.value) then
|
||||||
local ex = self:nextWord(self.value, self.pos)
|
local ex = self:nextWord(self.value, self.pos)
|
||||||
local text = self:copyText(self.pos, ex)
|
local text = self:copyText(self.pos, ex)
|
||||||
self:deleteText(self.pos, ex)
|
self:deleteText(self.pos, ex)
|
||||||
@@ -170,7 +191,7 @@ function Entry:insertChar(ie)
|
|||||||
end
|
end
|
||||||
|
|
||||||
function Entry:copy()
|
function Entry:copy()
|
||||||
if #self.value > 0 then
|
if #_val(self.value) > 0 then
|
||||||
self.mark.continue = true
|
self.mark.continue = true
|
||||||
if self.mark.active then
|
if self.mark.active then
|
||||||
self:copyMarked()
|
self:copyMarked()
|
||||||
@@ -201,15 +222,21 @@ function Entry:paste(ie)
|
|||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
function Entry.forcePaste()
|
||||||
|
os.queueEvent('clipboard_paste')
|
||||||
|
end
|
||||||
|
|
||||||
function Entry:clearLine()
|
function Entry:clearLine()
|
||||||
if #self.value > 0 then
|
if #_val(self.value) > 0 then
|
||||||
self:reset()
|
self:reset()
|
||||||
end
|
end
|
||||||
end
|
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
|
||||||
@@ -233,16 +260,21 @@ function Entry:unmark()
|
|||||||
end
|
end
|
||||||
|
|
||||||
function Entry:markAnchor(ie)
|
function Entry:markAnchor(ie)
|
||||||
|
local wasMarking = self.mark.active
|
||||||
self:unmark()
|
self:unmark()
|
||||||
self:moveTo(ie)
|
self:moveTo(ie)
|
||||||
self:markBegin()
|
self:markBegin()
|
||||||
self:markFinish()
|
self:markFinish()
|
||||||
|
|
||||||
|
self.textChanged = wasMarking
|
||||||
end
|
end
|
||||||
|
|
||||||
function Entry:markLeft()
|
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
|
||||||
|
|
||||||
@@ -250,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
|
||||||
|
|
||||||
@@ -257,7 +291,7 @@ function Entry:markWord(ie)
|
|||||||
local index = 1
|
local index = 1
|
||||||
self:moveTo(ie)
|
self:moveTo(ie)
|
||||||
while true do
|
while true do
|
||||||
local s, e = self.value:find('%w+', index)
|
local s, e = _val(self.value):find('%w+', index)
|
||||||
if not s or s - 1 > self.pos then
|
if not s or s - 1 > self.pos then
|
||||||
break
|
break
|
||||||
end
|
end
|
||||||
@@ -277,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
|
||||||
|
|
||||||
@@ -284,16 +320,18 @@ 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
|
||||||
|
|
||||||
function Entry:markAll()
|
function Entry:markAll()
|
||||||
if #self.value > 0 then
|
if #_val(self.value) > 0 then
|
||||||
self.mark.anchor = { x = 1 }
|
self.mark.anchor = { x = 1 }
|
||||||
self.mark.active = true
|
self.mark.active = true
|
||||||
self.mark.continue = true
|
self.mark.continue = true
|
||||||
self.mark.x = 0
|
self.mark.x = 0
|
||||||
self.mark.ex = #self.value
|
self.mark.ex = #_val(self.value)
|
||||||
self.textChanged = true
|
self.textChanged = true
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
@@ -302,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
|
||||||
|
|
||||||
@@ -309,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
|
||||||
|
|
||||||
@@ -344,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,
|
||||||
@@ -373,6 +416,10 @@ function Entry:process(ie)
|
|||||||
|
|
||||||
action(self, ie)
|
action(self, ie)
|
||||||
|
|
||||||
|
if not self.value or #_val(self.value) == 0 then
|
||||||
|
self.value = nil
|
||||||
|
end
|
||||||
|
|
||||||
self.textChanged = self.textChanged or self.value ~= line
|
self.textChanged = self.textChanged or self.value ~= line
|
||||||
self.posChanged = pos ~= self.pos
|
self.posChanged = pos ~= self.pos
|
||||||
self:updateScroll()
|
self:updateScroll()
|
||||||
|
|||||||
@@ -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,
|
||||||
@@ -65,4 +72,4 @@ function linkfs.move(node, s, t)
|
|||||||
return fs.move(s, t)
|
return fs.move(s, t)
|
||||||
end
|
end
|
||||||
|
|
||||||
return linkfs
|
return linkfs
|
||||||
@@ -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,28 +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(n)
|
||||||
|
n = n or 1
|
||||||
|
if ctr >= node.size then
|
||||||
|
return
|
||||||
|
end
|
||||||
|
local t = c:sub(ctr + 1, ctr + n)
|
||||||
|
ctr = ctr + n
|
||||||
|
return t
|
||||||
|
end,
|
||||||
readLine = function()
|
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
|
||||||
@@ -117,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,
|
||||||
@@ -133,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
|
||||||
@@ -74,6 +83,10 @@ function urlfs.open(node, fn, fl)
|
|||||||
|
|
||||||
if fl == 'r' then
|
if fl == 'r' then
|
||||||
return {
|
return {
|
||||||
|
read = function()
|
||||||
|
ctr = ctr + 1
|
||||||
|
return c:sub(ctr, ctr)
|
||||||
|
end,
|
||||||
readLine = function()
|
readLine = function()
|
||||||
if not lines then
|
if not lines then
|
||||||
lines = Util.split(c)
|
lines = Util.split(c)
|
||||||
@@ -90,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
|
||||||
@@ -3,10 +3,11 @@ local Util = require('opus.util')
|
|||||||
|
|
||||||
local TREE_URL = 'https://api.github.com/repos/%s/%s/git/trees/%s?recursive=1'
|
local TREE_URL = 'https://api.github.com/repos/%s/%s/git/trees/%s?recursive=1'
|
||||||
local FILE_URL = 'https://raw.githubusercontent.com/%s/%s/%s/%s'
|
local FILE_URL = 'https://raw.githubusercontent.com/%s/%s/%s/%s'
|
||||||
|
local TREE_HEADERS = {}
|
||||||
local git = { }
|
local git = { }
|
||||||
|
|
||||||
if _G._GIT_API_KEY then
|
if _G._GIT_API_KEY then
|
||||||
TREE_URL = TREE_URL .. '&access_token=' .. _G._GIT_API_KEY
|
TREE_HEADERS.Authorization = 'token ' .. _G._GIT_API_KEY
|
||||||
end
|
end
|
||||||
|
|
||||||
function git.list(repository)
|
function git.list(repository)
|
||||||
@@ -23,8 +24,10 @@ 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 = Util.download(dataUrl)
|
local contents, msg = Util.httpGet(dataUrl, TREE_HEADERS)
|
||||||
if contents then
|
if not contents then
|
||||||
|
error(string.format('Failed to download %s\n%s', dataUrl, msg), 2)
|
||||||
|
else
|
||||||
return json.decode(contents)
|
return json.decode(contents)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -1,17 +1,70 @@
|
|||||||
local Util = require('opus.util')
|
local Util = require('opus.util')
|
||||||
|
|
||||||
local GPS = { }
|
local GPS = { }
|
||||||
|
GPS.CHANNEL_GPS = 65534
|
||||||
|
|
||||||
local device = _G.device
|
local device = _G.device
|
||||||
local gps = _G.gps
|
local vector = _G.vector
|
||||||
|
|
||||||
function GPS.locate(timeout, debug)
|
function GPS.locate(timeout, debug)
|
||||||
local pt = { }
|
if not device.wireless_modem then
|
||||||
timeout = timeout or 10
|
if debug then
|
||||||
pt.x, pt.y, pt.z = gps.locate(timeout, debug)
|
print('No wireless modem attached')
|
||||||
if pt.x then
|
end
|
||||||
return pt
|
return nil
|
||||||
end
|
end
|
||||||
|
|
||||||
|
if debug then
|
||||||
|
print('Finding position...')
|
||||||
|
end
|
||||||
|
|
||||||
|
local modem = device.wireless_modem
|
||||||
|
local closeChannel = false
|
||||||
|
if not modem.isOpen(GPS.CHANNEL_GPS) then
|
||||||
|
modem.open(GPS.CHANNEL_GPS)
|
||||||
|
closeChannel = true
|
||||||
|
end
|
||||||
|
|
||||||
|
modem.transmit(GPS.CHANNEL_GPS, GPS.CHANNEL_GPS, "PING")
|
||||||
|
|
||||||
|
local fixes = {}
|
||||||
|
local pos = nil
|
||||||
|
local timer = os.startTimer(timeout or 1)
|
||||||
|
while true do
|
||||||
|
local e, side, chan, reply, msg, dist = os.pullEvent()
|
||||||
|
if e == "modem_message" then
|
||||||
|
if side == modem.side and chan == GPS.CHANNEL_GPS and reply == GPS.CHANNEL_GPS and dist then
|
||||||
|
if type(msg) == "table" and #msg == 3 and tonumber(msg[1]) and tonumber(msg[2]) and tonumber(msg[3]) then
|
||||||
|
local fix = {
|
||||||
|
position = vector.new(unpack(msg)),
|
||||||
|
distance = dist,
|
||||||
|
}
|
||||||
|
if debug then
|
||||||
|
print(fix.distance..' meters from '..fix.position:tostring())
|
||||||
|
end
|
||||||
|
if fix.distance == 0 then
|
||||||
|
pos = fix.position
|
||||||
|
else
|
||||||
|
fixes[#fixes+1] = fix
|
||||||
|
if #fixes > 3 then
|
||||||
|
pos = GPS.trilaterate(fixes)
|
||||||
|
if pos then break end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
elseif e == "timer" and side == timer then
|
||||||
|
break
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
if closeChannel then
|
||||||
|
modem.close(GPS.CHANNEL_GPS)
|
||||||
|
end
|
||||||
|
if debug then
|
||||||
|
print("Position is "..pos.x..","..pos.y..","..pos.z)
|
||||||
|
end
|
||||||
|
return pos and vector.new(pos.x, pos.y, pos.z)
|
||||||
end
|
end
|
||||||
|
|
||||||
function GPS.isAvailable()
|
function GPS.isAvailable()
|
||||||
@@ -66,26 +119,26 @@ local function trilaterate(A, B, C)
|
|||||||
local result1 = result + (ez * z)
|
local result1 = result + (ez * z)
|
||||||
local result2 = result - (ez * z)
|
local result2 = result - (ez * z)
|
||||||
|
|
||||||
local rounded1, rounded2 = result1:round(), result2:round()
|
local rounded1, rounded2 = result1:round(0.01), result2:round(0.01)
|
||||||
if rounded1.x ~= rounded2.x or rounded1.y ~= rounded2.y or rounded1.z ~= rounded2.z then
|
if rounded1.x ~= rounded2.x or rounded1.y ~= rounded2.y or rounded1.z ~= rounded2.z then
|
||||||
return rounded1, rounded2
|
return rounded1, rounded2
|
||||||
else
|
else
|
||||||
return rounded1
|
return rounded1
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
return result:round()
|
return result:round(0.01)
|
||||||
end
|
end
|
||||||
|
|
||||||
local function narrow( p1, p2, fix )
|
local function narrow( p1, p2, fix )
|
||||||
local dist1 = math.abs( (p1 - fix.position):length() - fix.distance )
|
local dist1 = math.abs( (p1 - fix.position):length() - fix.distance )
|
||||||
local dist2 = math.abs( (p2 - fix.position):length() - fix.distance )
|
local dist2 = math.abs( (p2 - fix.position):length() - fix.distance )
|
||||||
|
|
||||||
if math.abs(dist1 - dist2) < 0.05 then
|
if math.abs(dist1 - dist2) < 0.01 then
|
||||||
return p1, p2
|
return p1, p2
|
||||||
elseif dist1 < dist2 then
|
elseif dist1 < dist2 then
|
||||||
return p1:round()
|
return p1:round(0.01)
|
||||||
else
|
else
|
||||||
return p2:round()
|
return p2:round(0.01)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
-- end stock gps api
|
-- end stock gps api
|
||||||
@@ -98,7 +151,7 @@ function GPS.trilaterate(tFixes)
|
|||||||
if pos2 then
|
if pos2 then
|
||||||
pos1, pos2 = narrow(pos1, pos2, tFixes[1])
|
pos1, pos2 = narrow(pos1, pos2, tFixes[1])
|
||||||
end
|
end
|
||||||
if not pos2 then
|
if not pos2 and pos1 and not (pos1.x ~= pos1.x) then
|
||||||
return pos1, attemps
|
return pos1, attemps
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -39,7 +39,8 @@ if register_global_module_table then
|
|||||||
_G[global_module_name] = json
|
_G[global_module_name] = json
|
||||||
end
|
end
|
||||||
|
|
||||||
local _ENV = nil -- blocking globals in Lua 5.2
|
-- this was incompatible because we use fs later
|
||||||
|
--local _ENV = nil -- blocking globals in Lua 5.2
|
||||||
|
|
||||||
pcall (function()
|
pcall (function()
|
||||||
-- Enable access to blocked metatables.
|
-- Enable access to blocked metatables.
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user