149 Commits

Author SHA1 Message Date
Merith
2b328ae309 stopgap fix for opus icons on newer MC 2022-07-04 13:08:31 +00:00
github-actions[bot]
4104750539 Update version date 2022-07-04 04:09:14 +00:00
Kan18
3150525ee2 Fix #48 (shell resolving issue) (#58) 2022-07-04 00:08:59 -04:00
github-actions[bot]
7c5f749f02 Update version date 2021-11-17 04:00:12 +00:00
xAnavrins
7d9029c706 Reduce peripheral calls in Network app
Add a manual refresh button instead
2021-11-16 22:59:19 -05:00
github-actions[bot]
ce6a741690 Update version date 2021-02-20 01:11:28 +00:00
Anavrins
16d233df5d Actually working bug/feature templates 2021-02-19 20:11:13 -05:00
github-actions[bot]
f6d09abd54 Update version date 2021-02-20 00:57:43 +00:00
Tomo
bff84370b2 Create issue templates (#50) 2021-02-19 19:57:25 -05:00
github-actions[bot]
e74db02371 Update version date 2021-02-16 02:29:40 +00:00
Anavrins
2b75fbd1ee fix: up-to-date package on master branch 2021-02-15 21:28:57 -05:00
github-actions[bot]
65bf756084 Update version date 2020-12-31 02:50:07 +00:00
Luca
2ff4ee5670 fix: boolean value false is now correctly displayed in Lua.lua (#51) 2020-12-30 21:49:51 -05:00
github-actions[bot]
5488a7d732 Update version date 2020-10-26 21:19:02 +00:00
Anavrins
37c4ca102c Maybe fix github actions 2020-10-26 17:18:37 -04:00
Anavrins
ac51771b12 Fix kiosk bug from #37 2020-10-25 02:40:21 -04:00
github-actions[bot]
6fdce03a10 Update version date 2020-08-24 05:41:41 +00:00
Boom
ac91d8ed08 Trust manager (#46) 2020-08-24 01:41:25 -04:00
github-actions[bot]
2c0db368fa Update version date 2020-08-24 05:20:58 +00:00
Kan18
a77deb72ec Add a one-time password system (#47)
This commit adds a one-time password system. Users can generate
passwords by using the `genotp` command (or manually queuing a set_otp
event with the SHA-256 hash of the one-time password they want to set)
and these passwords will be valid, along with the normal password,
either until the computer shuts down (or the net daemon is killed), a
new one-time password is generated, or the one-time password is used.
2020-08-24 01:20:42 -04:00
github-actions[bot]
01d8d65178 Update version date 2020-08-21 23:15:31 +00:00
Anavrins
f36e5470b1 Anonymize GPS
Damn you gollark!
2020-08-21 19:15:07 -04:00
github-actions[bot]
2520f53046 Update version date 2020-08-04 02:00:28 +00:00
Kan18
f4494d6103 Fix untar (#42)
small fix - untar_stream is supposed to be getting the handle, not the filename
2020-08-03 20:00:11 -06:00
github-actions[bot]
48f32946ec Update version date 2020-07-26 00:52:59 +00:00
kepler155c@gmail.com
c24a5a7115 help cleanup 2020-07-25 18:52:41 -06:00
github-actions[bot]
624af53f4e Update version date 2020-07-25 00:41:36 +00:00
Kan18
816ea366ab TLCO fix & boot file extension change (#37)
This commit fixes the TLCO boot option (which hasn't been working for a
while now), and also changes boot file extensions from .boot to .lua.
2020-07-24 18:41:21 -06:00
github-actions[bot]
b45cd45bcb Update version date 2020-07-25 00:37:14 +00:00
Kan18
45f1bd1c5d Put pastebin link in the right case (Fixes #40) (#41)
Pastebin recently removed support for case-insensitive links with no warning. This puts the original casing of the paste ID in README.md, instead of all-lowercase. Some other things might also need to be changed!
2020-07-24 18:36:57 -06:00
github-actions[bot]
1cb2c5f785 Update version date 2020-06-16 01:55:00 +00:00
kepler155c@gmail.com
42bd4b2b69 remove preferred apps/alternates - same can be accomplished using aliases 2020-06-15 19:54:42 -06:00
github-actions[bot]
156b604a58 Update version date 2020-06-14 02:26:36 +00:00
kepler155c@gmail.com
7df4a47ba0 oops 2020-06-13 20:26:17 -06:00
github-actions[bot]
9bf91a8762 Update version date 2020-06-13 18:18:21 +00:00
kepler155c@gmail.com
b69dcdeffa shell cleanup 2020-06-13 12:18:04 -06:00
github-actions[bot]
947f502c6d Update version date 2020-06-11 19:29:04 +00:00
kepler155c@gmail.com
a2af4405e7 require cleanup + compatibility fixes 2020-06-11 13:28:43 -06:00
github-actions[bot]
1bf6daedff Update version date 2020-06-11 01:46:52 +00:00
kepler155c@gmail.com
2fdcc338ad transparent compatiblity for moonscript 2020-06-10 19:46:34 -06:00
github-actions[bot]
4f39604c63 Update version date 2020-06-10 00:17:16 +00:00
kepler155c@gmail.com
e9580d67eb pure lua compatiblity fixes for moonlight, busted, etc 2020-06-09 18:16:51 -06:00
github-actions[bot]
039fa73749 Update version date 2020-06-06 17:00:25 +00:00
kepler155c@gmail.com
2d942ef001 experimental packages fix https://github.com/kepler155c/opus/issues/35 2020-06-06 11:00:07 -06:00
github-actions[bot]
4d02f75a19 Update version date 2020-06-06 03:38:48 +00:00
kepler155c@gmail.com
6c5cc508b1 moonscript support + cleanup 2020-06-05 21:38:26 -06:00
github-actions[bot]
e7fcd68a0f Update version date 2020-06-04 04:31:10 +00:00
kepler155c
7dfa80f5f3 Update README.md 2020-06-03 22:30:54 -06:00
github-actions[bot]
788f6e7de0 Update version date 2020-06-04 03:12:11 +00:00
kepler155c@gmail.com
704ef46b62 packages stored in compressed state - experimental (with config option) 2020-06-03 21:11:54 -06:00
github-actions[bot]
d678eeeaca Update version date 2020-06-04 02:41:07 +00:00
kepler155c@gmail.com
6a3b38922b packages stored in compressed state - experimental (with config option) 2020-06-03 20:40:48 -06:00
github-actions[bot]
6009f22d8e Update version date 2020-06-01 22:53:29 +00:00
kepler155c@gmail.com
f951f10df5 Merge branch 'develop-1.8' of https://github.com/kepler155c/opus into develop-1.8 2020-06-01 16:53:09 -06:00
kepler155c@gmail.com
5c4ab57ec8 binary support fixes 2020-06-01 16:53:01 -06:00
github-actions[bot]
0359a89e12 Update version date 2020-06-01 05:50:47 +00:00
kepler155c@gmail.com
4796e9e77a ramfs bugfixes 2020-05-31 23:50:30 -06:00
github-actions[bot]
18b7f540ab Update version date 2020-05-31 02:06:14 +00:00
kepler155c@gmail.com
2105799524 minor cleanup 2020-05-30 20:05:53 -06:00
github-actions[bot]
b6f439e8dc Update version date 2020-05-29 04:55:20 +00:00
kepler155c@gmail.com
41c0758857 Merge branch 'develop-1.8' of https://github.com/kepler155c/opus into develop-1.8 2020-05-28 22:54:53 -06:00
kepler155c@gmail.com
d1565c62e0 oops in loadfile replacement 2020-05-28 22:54:48 -06:00
github-actions[bot]
f00bece02b Update version date 2020-05-29 02:15:35 +00:00
kepler155c@gmail.com
9297223640 proper fix for builtin broken http.get - try 2 2020-05-28 20:15:19 -06:00
github-actions[bot]
f38afbbd36 Update version date 2020-05-29 02:10:25 +00:00
kepler155c@gmail.com
7b225a7747 proper fix for builtin broken http.get 2020-05-28 20:10:01 -06:00
github-actions[bot]
a7069f7ea8 Update version date 2020-05-26 03:48:56 +00:00
kepler155c@gmail.com
a4f4f34576 minor cleanup 2020-05-25 21:48:37 -06:00
github-actions[bot]
1cce4aad03 Update version date 2020-05-24 03:45:25 +00:00
kepler155c@gmail.com
26bbb50981 debugger support 2020-05-23 21:45:05 -06:00
github-actions[bot]
97bfae10fb Update version date 2020-05-19 23:09:31 +00:00
kepler155c@gmail.com
985830fcfd better fuzzy matching + vfs type flag in Files 2020-05-19 17:09:10 -06:00
github-actions[bot]
b93d69c261 Update version date 2020-05-18 01:37:11 +00:00
kepler155c@gmail.com
a7e3318226 add standard lua os methods, another fix for vfs links within links, allow write on urlfs mounted files 2020-05-17 19:36:33 -06:00
github-actions[bot]
c7c594d6c3 Update version date 2020-05-13 03:26:28 +00:00
kepler155c@gmail.com
90ce2bb1a5 Improved error messages 2020-05-12 21:25:37 -06:00
github-actions[bot]
2629f2a172 Update version date 2020-05-11 23:26:37 +00:00
kepler155c@gmail.com
8279c1ae12 environments, error messages, and stack traces, oh my!
Changed the way processes are launched.
multishell.openTab and kernel.run now accept the current environment as a parameter.
That new process inherits a copy of the passed environment.
Reduces complexity as the calling process is not required to create a suitable env.
Stack traces have been greatly improved and now include the stack for coroutines that error.
2020-05-11 17:25:58 -06:00
github-actions[bot]
bd911e80e8 Update version date 2020-05-10 20:04:57 +00:00
kepler155c@gmail.com
a3a819256f fix resizing scrolling terminals 2020-05-10 14:04:20 -06:00
github-actions[bot]
cfc18e10cd Update version date 2020-05-09 04:33:20 +00:00
kepler155c@gmail.com
b0db0b86bd kernel improvements 2020-05-08 22:32:44 -06:00
github-actions[bot]
71bbd2d457 Update version date 2020-05-08 17:10:12 +00:00
devomaa
db9c05fa89 Update Overview.lua to work with N hotkey (#33) 2020-05-08 11:09:32 -06:00
github-actions[bot]
3f80faa0fe Update version date 2020-05-08 03:35:30 +00:00
kepler155c@gmail.com
8b14399a20 a temporary fix for vfs/delete 2020-05-07 21:34:50 -06:00
github-actions[bot]
4e6c5172d1 Update version date 2020-05-07 00:01:53 +00:00
kepler155c@gmail.com
c24411717a optionally show value for slider component - remove some unneeded limits for textEntries 2020-05-06 18:01:10 -06:00
github-actions[bot]
51724ccd78 Update version date 2020-05-05 23:26:40 +00:00
kepler155c@gmail.com
3d3e5400cf tidy up env creation - round 1 2020-05-05 17:25:58 -06:00
github-actions[bot]
4018d4bcfb Update version date 2020-05-05 06:46:37 +00:00
Anavrins
4485dd2bd5 Remove default limit in TextEntry 2020-05-05 02:45:42 -04:00
github-actions[bot]
72560b1de0 Update version date 2020-05-05 03:07:05 +00:00
kepler155c@gmail.com
3d02230742 major oops - fix poluted envs 2020-05-04 21:06:23 -06:00
Anavrins
456d2d301f Merge branch 'develop-1.8' of https://github.com/kepler155c/opus into develop-1.8 2020-05-04 21:19:41 -04:00
github-actions[bot]
cdf8645c88 Update version date 2020-05-04 22:45:09 +00:00
kepler155c@gmail.com
d7f41f2bce multishell/kernel support for windows on different devices 2020-05-04 16:41:35 -06:00
Anavrins
e2ba9e2a03 Fix bug in CheckboxGrid 2020-05-04 03:55:55 -04:00
github-actions[bot]
447f4daa92 Update version date 2020-05-02 05:16:44 +00:00
kepler155c@gmail.com
5afb21b68e more work on update notification 2020-05-01 23:16:10 -06:00
github-actions[bot]
3488da649e Update version date 2020-05-02 05:10:19 +00:00
kepler155c@gmail.com
7f92286fb1 more work on update notification 2020-05-01 23:09:46 -06:00
github-actions[bot]
4a46d67304 Update version date 2020-05-02 04:40:52 +00:00
kepler155c@gmail.com
3cbdf7b97b more work on update notification 2020-05-01 22:40:17 -06:00
github-actions[bot]
74d3d1df33 Update version date 2020-05-02 04:26:51 +00:00
kepler155c@gmail.com
9c97849cff more work on update notification 2020-05-01 22:26:15 -06:00
github-actions[bot]
c1cbcf6b61 Update version date 2020-05-01 04:14:02 +00:00
kepler155c@gmail.com
49b986499c Merge branch 'develop-1.8' of https://github.com/kepler155c/opus into develop-1.8 2020-04-30 22:13:09 -06:00
kepler155c@gmail.com
fae7596182 update notification 2020-04-30 22:13:03 -06:00
github-actions[bot]
cb55b1daab Update version date 2020-05-01 04:07:10 +00:00
kepler155c@gmail.com
fd61416750 Merge branch 'develop-1.8' of https://github.com/kepler155c/opus into develop-1.8 2020-04-30 22:05:57 -06:00
kepler155c@gmail.com
c622b2f7fb update notification 2020-04-30 22:05:23 -06:00
github-actions[bot]
98f4bf7a7e Update version date 2020-05-01 03:57:20 +00:00
kepler155c@gmail.com
c5e0ac0af6 github actions test 2020-04-30 21:56:44 -06:00
kepler155c@gmail.com
0188e886c0 github actions test 2020-04-30 21:48:55 -06:00
kepler155c@gmail.com
55f444c37e github actions test 2020-04-30 21:38:38 -06:00
kepler155c@gmail.com
3368fa3266 update notification 2020-04-30 21:27:07 -06:00
kepler155c@gmail.com
e721eb68b6 update notification 2020-04-30 20:22:44 -06:00
kepler155c@gmail.com
1543f4d624 github actions test 2020-04-30 19:08:29 -06:00
kepler155c@gmail.com
287adb1235 auto upgrade packages on base opus update - github actions wip 2020-04-30 18:51:36 -06:00
kepler155c@gmail.com
e116caf16e fix netfs issues - implement fs.attributes 2020-04-27 15:44:09 -06:00
kepler155c@gmail.com
d72ae3de4a vfs bugfix 2020-04-26 22:07:18 -06:00
kepler155c@gmail.com
602d12afc5 vfs bugfix 2020-04-26 20:36:50 -06:00
kepler155c@gmail.com
ef9f0e09b6 partition manager + tab/wizard rework 2020-04-26 19:39:58 -06:00
kepler155c@gmail.com
b0d2ce0199 minor bugfixes - cleanup 2020-04-22 23:37:12 -06:00
kepler155c
7224d441ca Ui enhancements 2.0 (#31)
* canvas overhaul

* minor tweaks

* list mode for overview

* bugfixes + tweaks for editor 2.0

* minor tweaks

* more editor work

* refactor + new transitions

* use layout() where appropriate and cleanup

* mouse triple click + textEntry scroll ind

* cleanup

* cleanup + theme editor

* color rework + cleanup

* changes for deprecated ui methods

* can now use named colors
2020-04-21 22:40:59 -06:00
Anavrins
cdd0b6c4d2 Temporary fix for blit crashing with spaces 2020-04-20 19:33:22 -04:00
Anavrins
39522ee5b1 Cleanup 2020-03-30 02:07:20 -04:00
Anavrins
369070e19c Merge pull request #30 from Wojbie/Git-fix
Update git.lua to use headers authorization.
2020-02-09 21:35:52 -05:00
Wojbie
210c5f5a11 Update git.lua to use headers authorization. 2020-02-09 23:20:03 +01:00
Anavrins
942f0bda92 GPS overhaul
Changes how GPS works to avoid returning ambiguous coordinates and nan errors
2019-12-27 01:11:36 -05:00
Anavrins
cc80e08407 gps.lua: Added nil and nan checks 2019-12-11 11:38:12 -05:00
Anavrins
9e3cf50ccc socket.lua: Option to pass user generated keypairs 2019-12-11 11:20:12 -05:00
Anavrins
6204c46cc4 Fix empty TextEntry in cloud config 2019-12-11 11:18:02 -05:00
kepler155c@gmail.com
db2a28f04c Merge branch 'develop-1.8' of https://github.com/kepler155c/opus into develop-1.8 2019-12-07 12:05:03 -07:00
kepler155c@gmail.com
28b2ba3386 tweaks 2019-12-07 12:04:58 -07:00
Anavrins
31f43067bf Change how Sniff.lua detects modem 2019-12-06 20:14:18 -05:00
kepler155c@gmail.com
424fff7842 apparently read was added to fs.open "r" mode in 2017 2019-12-05 13:44:35 -07:00
kepler155c@gmail.com
1e675a2e35 revert multiple sub-canvas mouse click 2019-12-01 13:25:26 -07:00
kepler155c@gmail.com
ffa412c59d ui fixes 2019-11-18 14:32:10 -07:00
kepler155c@gmail.com
a3a8c64be8 UI docs 2019-11-16 22:12:02 -07:00
kepler155c@gmail.com
efa1a5bbf5 clipping for transistions + tab ordering via index + more examples 2019-11-15 12:51:44 -07:00
kepler155c@gmail.com
14057c2bf9 moar ui examples 2019-11-14 15:43:20 -07:00
kepler155c@gmail.com
3241326a2f accelerators for any event + more inspect examples 2019-11-13 21:50:00 -07:00
kepler155c@gmail.com
db48031c7c funnel all events through emit 2019-11-13 15:17:23 -07:00
kepler155c@gmail.com
0a828fecc5 oops 2019-11-13 14:38:24 -07:00
kepler155c@gmail.com
65c6ebf711 properly handle empty text entry fields (including transformations) 2019-11-13 14:24:43 -07:00
kepler155c@gmail.com
053003f429 inspect cleanup 2019-11-12 23:04:31 -07:00
kepler155c@gmail.com
25405f15c8 UI inspector 2019-11-12 21:13:17 -07:00
149 changed files with 6341 additions and 3445 deletions

30
.github/ISSUE_TEMPLATE/bug_report.md vendored Normal file
View 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]

View 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
View 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
View 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
View 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
View File

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

6
.opus_version Normal file
View 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)

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

238
sys/apps/Partition.lua Normal file
View 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()

View File

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

View File

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

View File

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

View File

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

54
sys/apps/Version.lua Normal file
View 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()

View File

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

24
sys/apps/compat.lua Normal file
View 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
View File

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

14
sys/apps/genotp.lua Normal file
View File

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

195
sys/apps/inspect.lua Normal file
View 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()

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -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

View File

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

View File

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

View File

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

View File

@@ -4,23 +4,26 @@ local Util = require('opus.util')
local fs = _G.fs
local os = _G.os
local labelTab = UI.Tab {
tabTitle = 'Label',
return UI.Tab {
title = 'Label',
description = 'Set the computer label',
labelText = UI.Text {
x = 3, y = 2,
x = 3, y = 3,
value = 'Label'
},
label = UI.TextEntry {
x = 9, y = 2, ex = -4,
x = 9, y = 3, ex = -4,
limit = 32,
value = os.getComputerLabel(),
accelerators = {
enter = 'update_label',
},
},
[1] = UI.Window {
x = 2, y = 2, ex = -2, ey = 4,
},
grid = UI.ScrollingGrid {
y = 3,
x = 2, y = 5, ex = -2, ey = -2,
values = {
{ name = '', value = '' },
{ name = 'CC version', value = Util.getVersion() },
@@ -30,20 +33,18 @@ local labelTab = UI.Tab {
{ name = 'Computer ID', value = tostring(os.getComputerID()) },
{ name = 'Day', value = tostring(os.day()) },
},
disableHeader = true,
inactive = true,
columns = {
{ key = 'name', width = 12 },
{ key = 'value' },
{ 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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -2,48 +2,93 @@ local UI = require('opus.ui')
local settings = _G.settings
if settings then
local settingsTab = UI.Tab {
tabTitle = 'Settings',
description = 'Computercraft configurable settings',
grid = UI.Grid {
y = 2,
autospace = true,
sortColumn = 'name',
columns = {
{ heading = 'Setting', key = 'name' },
{ heading = 'Value', key = 'value' },
},
},
}
local transform = {
string = tostring,
number = tonumber,
}
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 = { }
for _,v in pairs(settings.getNames()) do
local value = settings.get(v)
if not value then
value = false
end
table.insert(values, {
name = v,
value = value,
value = settings.get(v) or false,
})
end
self.grid:setValues(values)
self.grid:setIndex(1)
end,
enable = function(self)
self:reload()
UI.Tab.enable(self)
end
function settingsTab:eventHandler(event)
end,
eventHandler = function(self, event)
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
settings.set(event.selected.name, event.selected.value)
settings.save('.settings')
self.grid:draw()
else
self.editor:show(event.selected)
end
settings.set(event.selected.name, event.selected.value)
settings.save('.settings')
self.grid:draw()
return true
end
end
return settingsTab
end
end,
}

View File

@@ -18,9 +18,7 @@ local defaults = {
textColor = colors.white,
commandTextColor = colors.yellow,
directoryTextColor = colors.orange,
directoryBackgroundColor = colors.black,
promptTextColor = colors.blue,
promptBackgroundColor = colors.black,
directoryColor = colors.green,
fileColor = colors.white,
backgroundColor = colors.black,
@@ -28,7 +26,7 @@ local defaults = {
local _colors = config.color or Util.shallowCopy(defaults)
local allSettings = { }
for k, v in pairs(defaults) do
for k in pairs(defaults) do
table.insert(allSettings, { name = k })
end
@@ -38,29 +36,34 @@ if not _colors.backgroundColor then
_colors.fileColor = colors.white
end
local tab = UI.Tab {
tabTitle = 'Shell',
return UI.Tab {
title = 'Shell',
description = 'Shell options',
grid1 = UI.ScrollingGrid {
y = 2, ey = -10, x = 3, ex = -16,
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 = -3,
y = 2, ey = -10, x = -14, ex = -2,
disableHeader = true,
columns = { { key = 'name' } },
values = allColors,
sortColumn = 'name',
},
directoryLabel = UI.Text {
x = 2, y = -2,
value = 'Display directory',
getRowTextColor = function(self, row)
local selected = self.parent.grid1:getSelected()
if _colors[selected.name] == row.value then
return colors.yellow
end
return UI.Grid.getRowTextColor(self, row)
end
},
directory = UI.Checkbox {
x = 20, y = -2,
x = 2, y = -2,
labelBackgroundColor = colors.black,
label = 'Directory',
value = config.displayDirectory
},
reset = UI.Button {
@@ -74,69 +77,57 @@ local tab = UI.Tab {
event = 'update',
},
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
View 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
View 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
}

View File

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

View File

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

View File

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

View File

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

View File

@@ -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
View 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

View File

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

View File

@@ -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
View 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

View File

@@ -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
View 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()

View File

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

View File

@@ -2,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/Enchat.lua urlfs https://raw.githubusercontent.com/LDDestroier/enchat/master/enchat3.lua
sys/apps/cloud.lua urlfs https://cloud-catcher.squiddev.cc/cloud.lua
sys/apps/nfttrans.lua urlfs https://pastebin.com/raw/e8XrzeDY
rom/modules/main/opus linkfs sys/modules/opus

9
sys/help/Networking.txt Normal file
View 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>

View File

@@ -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.

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

31
sys/init/5.unpackage.lua Normal file
View 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

View File

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

View File

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

View File

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

View File

@@ -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

View File

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

View 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,
}

View 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,
}

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -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

View File

@@ -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 FILE_URL = 'https://raw.githubusercontent.com/%s/%s/%s/%s'
local TREE_HEADERS = {}
local git = { }
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
function git.list(repository)
@@ -23,8 +24,10 @@ function git.list(repository)
local function getContents()
local dataUrl = string.format(TREE_URL, user, repo, branch)
local contents = Util.download(dataUrl)
if contents then
local contents, msg = Util.httpGet(dataUrl, TREE_HEADERS)
if not contents then
error(string.format('Failed to download %s\n%s', dataUrl, msg), 2)
else
return json.decode(contents)
end
end

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -57,7 +57,7 @@ end
function Packages:downloadList()
local packages = {
[ 'develop-1.8' ] = 'https://raw.githubusercontent.com/kepler155c/opus-apps/develop-1.8/packages.list',
[ 'master-1.8' ] = 'https://pastebin.com/raw/pexZpAxt',
[ 'master-1.8' ] = 'https://raw.githubusercontent.com/kepler155c/opus-apps/master-1.8/packages.list',
}
if packages[_G.OPUS_BRANCH] then

View File

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

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