Compare commits
15 Commits
Kan18/deve
...
ui-enhance
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
5933f8c40f | ||
|
|
e703c7f7b6 | ||
|
|
9d2a76f4ea | ||
|
|
3e41996b9b | ||
|
|
9eeec8719c | ||
|
|
775871c548 | ||
|
|
cd6ef0da50 | ||
|
|
8fe6e0806c | ||
|
|
7659b81d49 | ||
|
|
cd58ecd861 | ||
|
|
d88ef00652 | ||
|
|
fc1a308193 | ||
|
|
3313fb986c | ||
|
|
613212e751 | ||
|
|
5a874c1944 |
30
.github/ISSUE_TEMPLATE/bug_report.md
vendored
30
.github/ISSUE_TEMPLATE/bug_report.md
vendored
@@ -1,30 +0,0 @@
|
||||
---
|
||||
name: Bug report
|
||||
about: Create a report to help us improve
|
||||
title: ''
|
||||
labels: bug
|
||||
assignees: ''
|
||||
|
||||
---
|
||||
|
||||
**Describe the bug**
|
||||
A clear and concise description of what the bug is.
|
||||
|
||||
**To Reproduce**
|
||||
Steps to reproduce the behavior:
|
||||
1. Go to '...'
|
||||
2. Click on '....'
|
||||
3. Scroll down to '....'
|
||||
4. See error
|
||||
|
||||
**Expected behavior**
|
||||
A clear and concise description of what you expected to happen.
|
||||
|
||||
**Screenshots**
|
||||
If applicable, add screenshots to help explain your problem.
|
||||
|
||||
**Versions**
|
||||
What version of Minecraft, CC:Tweaked, Plethora (if applicable), Opus branch are you using
|
||||
- MC : [e.g. 1.12.2]
|
||||
- CC:T : [e.g. 1.88]
|
||||
- Opus : [e.g. develop-1.8]
|
||||
20
.github/ISSUE_TEMPLATE/feature_request.md
vendored
20
.github/ISSUE_TEMPLATE/feature_request.md
vendored
@@ -1,20 +0,0 @@
|
||||
---
|
||||
name: Feature request
|
||||
about: Suggest an idea for this project
|
||||
title: ''
|
||||
labels: enhancement
|
||||
assignees: ''
|
||||
|
||||
---
|
||||
|
||||
**Is your feature request related to a problem? Please describe.**
|
||||
A clear and concise description of what the problem is. Ex. I'm always frustrated when [...]
|
||||
|
||||
**Describe the solution you'd like**
|
||||
A clear and concise description of what you want to happen.
|
||||
|
||||
**Describe alternatives you've considered**
|
||||
A clear and concise description of any alternative solutions or features you've considered.
|
||||
|
||||
**Additional context**
|
||||
Add any other context or screenshots about the feature request here.
|
||||
17
.github/ISSUE_TEMPLATES/bug.md
vendored
17
.github/ISSUE_TEMPLATES/bug.md
vendored
@@ -1,17 +0,0 @@
|
||||
---
|
||||
name: Bug Report
|
||||
about: Did something go wrong? File an issue!
|
||||
title: Good titles include first line of stack trace or short summary of problem
|
||||
labels: bug
|
||||
---
|
||||
<!--- THIS IS A COMMENT. IT WILL NOT APPEAR IN THE FINAL ISSUE, DO NOT DELETE THESE. -->
|
||||
# Details
|
||||
<!--- Put a description of the bug here. (Ex. I crashed when running Opus.) -->
|
||||
## Further context
|
||||
<!--- Stack trace (surrounded in three backticks, ‘‘‘), supplementary media such as screenshots and video, etc -->
|
||||
## Versions
|
||||
Branch:
|
||||
|
||||
Opus Version: <!--- (Do NOT put Latest unless you are unsure) -->
|
||||
|
||||
CraftOS Version:
|
||||
13
.github/ISSUE_TEMPLATES/feature.md
vendored
13
.github/ISSUE_TEMPLATES/feature.md
vendored
@@ -1,13 +0,0 @@
|
||||
---
|
||||
name: Enhancement
|
||||
about: Suggest a new feature or change to Opus.
|
||||
labels: enhancement
|
||||
---
|
||||
<!--- THIS IS A COMMENT. IT WILL NOT APPEAR IN THE FINAL ISSUE. DO NOT REMOVE THEM. -->
|
||||
# Summary
|
||||
<!--- Summarize what you want. -->
|
||||
## Additional Context
|
||||
<!--- Comcept art, screenshots, and other relevant media. -->
|
||||
## Related
|
||||
<!--- Delete this category if not used. -->
|
||||
<!--- Use this category for relevant links, if present. -->
|
||||
36
.github/workflows/main.yml
vendored
36
.github/workflows/main.yml
vendored
@@ -1,36 +0,0 @@
|
||||
# This is a basic workflow to help you get started with Actions
|
||||
|
||||
name: CI
|
||||
|
||||
# Controls when the action will run. Triggers the workflow on push or pull request
|
||||
# events but only for the master branch
|
||||
on:
|
||||
push:
|
||||
branches: [ develop-1.8 ]
|
||||
pull_request:
|
||||
branches: [ develop-1.8 ]
|
||||
|
||||
# A workflow run is made up of one or more jobs that can run sequentially or in parallel
|
||||
jobs:
|
||||
# This workflow contains a single job called "build"
|
||||
build:
|
||||
# The type of runner that the job will run on
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
# Steps represent a sequence of tasks that will be executed as part of the job
|
||||
steps:
|
||||
# Checks-out your repository under $GITHUB_WORKSPACE, so your job can access it
|
||||
- uses: actions/checkout@v2
|
||||
|
||||
- name: Create version file
|
||||
run: |
|
||||
echo `date` > .opus_version
|
||||
git log >> .opus_version
|
||||
|
||||
- name: Commit version file
|
||||
uses: alexesprit/action-update-file@main
|
||||
with:
|
||||
branch: 'develop-1.8'
|
||||
file-path: .opus_version
|
||||
commit-msg: Update version date
|
||||
github-token: ${{ secrets.GITHUB_TOKEN }}
|
||||
@@ -1,6 +0,0 @@
|
||||
Mon Jul 4 04:09:12 UTC 2022
|
||||
commit 3150525ee2024fc605669093b89f75f0c741a81f
|
||||
Author: Kan18 <24967425+Kan18@users.noreply.github.com>
|
||||
Date: Mon Jul 4 00:08:59 2022 -0400
|
||||
|
||||
Fix #48 (shell resolving issue) (#58)
|
||||
@@ -1,7 +1,5 @@
|
||||
# Opus OS for computercraft
|
||||
|
||||
<img src="https://github.com/kepler155c/opus-wiki/blob/master/assets/images/opus.gif?raw=true" width="540" height="360">
|
||||
|
||||
## Features
|
||||
* Multitasking OS - run programs in separate tabs
|
||||
* Telnet (wireless remote shell)
|
||||
@@ -16,5 +14,5 @@
|
||||
|
||||
## Install
|
||||
```
|
||||
pastebin run UzGHLbNC
|
||||
pastebin run uzghlbnc
|
||||
```
|
||||
|
||||
23
startup.lua
23
startup.lua
@@ -29,10 +29,9 @@ local function loadBootOptions()
|
||||
preload = { },
|
||||
menu = {
|
||||
{ prompt = os.version() },
|
||||
{ prompt = 'Opus' , args = { '/sys/boot/opus.lua' } },
|
||||
{ prompt = 'Opus Shell' , args = { '/sys/boot/opus.lua', '/sys/apps/shell.lua' } },
|
||||
{ prompt = 'Opus Kiosk' , args = { '/sys/boot/kiosk.lua' } },
|
||||
{ prompt = 'Opus TLCO' , args = { '/sys/boot/tlco.lua' } },
|
||||
{ prompt = 'Opus' , args = { '/sys/boot/opus.boot' } },
|
||||
{ prompt = 'Opus Shell' , args = { '/sys/boot/opus.boot', 'sys/apps/shell.lua' } },
|
||||
{ prompt = 'Opus Kiosk' , args = { '/sys/boot/kiosk.boot' } },
|
||||
},
|
||||
}))
|
||||
f.close()
|
||||
@@ -42,20 +41,6 @@ local function loadBootOptions()
|
||||
local options = textutils.unserialize(f.readAll())
|
||||
f.close()
|
||||
|
||||
-- Backwards compatibility for .startup.boot files created before sys/boot files' extensions were changed
|
||||
local changed = false
|
||||
for _, item in pairs(options.menu) do
|
||||
if item.args and item.args[1]:match("/?sys/boot/%l+%.boot") then
|
||||
item.args[1] = item.args[1]:gsub("%.boot", "%.lua")
|
||||
changed = true
|
||||
end
|
||||
end
|
||||
if changed then
|
||||
local f = fs.open(".startup.boot", "w")
|
||||
f.write(textutils.serialize(options))
|
||||
f.close()
|
||||
end
|
||||
|
||||
return options
|
||||
end
|
||||
|
||||
@@ -142,7 +127,7 @@ local function splash()
|
||||
local opus = {
|
||||
'fffff00',
|
||||
'ffff07000',
|
||||
'ff00770b00f4444',
|
||||
'ff00770b00 4444',
|
||||
'ff077777444444444',
|
||||
'f07777744444444444',
|
||||
'f0000777444444444',
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
local Alt = require('opus.alternate')
|
||||
local Config = require('opus.config')
|
||||
local Event = require('opus.event')
|
||||
local pastebin = require('opus.http.pastebin')
|
||||
@@ -76,8 +77,8 @@ local Browser = UI.Page {
|
||||
grid = UI.ScrollingGrid {
|
||||
columns = {
|
||||
{ heading = 'Name', key = 'name' },
|
||||
{ key = 'flags', width = 3, textColor = 'lightGray' },
|
||||
{ heading = 'Size', key = 'fsize', width = 5, textColor = 'yellow' },
|
||||
{ key = 'flags', width = 2 },
|
||||
{ heading = 'Size', key = 'fsize', width = 5 },
|
||||
},
|
||||
sortColumn = 'name',
|
||||
y = 2, ey = -2,
|
||||
@@ -176,6 +177,7 @@ local Browser = UI.Page {
|
||||
formLabel = 'Program', formKey = 'value',
|
||||
shadowText = 'program',
|
||||
required = true,
|
||||
limit = 128,
|
||||
},
|
||||
add = UI.Button {
|
||||
x = -11, y = 1,
|
||||
@@ -210,7 +212,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
|
||||
@@ -222,7 +224,7 @@ 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
|
||||
@@ -262,11 +264,10 @@ function Browser:updateDirectory(dir)
|
||||
dir.size = #files
|
||||
for _, file in pairs(files) do
|
||||
file.fullName = fs.combine(dir.name, file.name)
|
||||
file.flags = file.fstype or ' '
|
||||
file.flags = ''
|
||||
if not file.isDir then
|
||||
dir.totalSize = dir.totalSize + file.size
|
||||
file.fsize = formatSize(file.size)
|
||||
file.flags = file.flags .. ' '
|
||||
else
|
||||
if config.showDirSizes then
|
||||
file.size = fs.getSize(file.fullName, true)
|
||||
@@ -274,9 +275,11 @@ function Browser:updateDirectory(dir)
|
||||
dir.totalSize = dir.totalSize + file.size
|
||||
file.fsize = formatSize(file.size)
|
||||
end
|
||||
file.flags = file.flags .. 'D'
|
||||
file.flags = 'D'
|
||||
end
|
||||
if file.isReadOnly then
|
||||
file.flags = file.flags .. 'R'
|
||||
end
|
||||
file.flags = file.flags .. (file.isReadOnly and 'R' or ' ')
|
||||
if config.showHidden or file.name:sub(1, 1) ~= '.' then
|
||||
dir.files[file.fullName] = file
|
||||
end
|
||||
@@ -350,7 +353,7 @@ function Browser:eventHandler(event)
|
||||
self:setStatus('Started cloud edit')
|
||||
|
||||
elseif event.type == 'shell' then
|
||||
self:run('shell')
|
||||
self:run(Alt.get('shell'))
|
||||
|
||||
elseif event.type == 'refresh' then
|
||||
self:updateDirectory(self.dir)
|
||||
@@ -465,7 +468,7 @@ function Browser:eventHandler(event)
|
||||
|
||||
elseif event.type == 'paste' then
|
||||
for _,m in pairs(copied) do
|
||||
pcall(function()
|
||||
local s, m = pcall(function()
|
||||
if cutMode then
|
||||
fs.move(m.fullName, fs.combine(self.dir.name, m.name))
|
||||
else
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
local fuzzy = require('opus.fuzzy')
|
||||
local UI = require('opus.ui')
|
||||
local Util = require('opus.util')
|
||||
|
||||
@@ -43,13 +42,13 @@ UI:addPage('main', UI.Page {
|
||||
|
||||
elseif event.type == 'text_change' then
|
||||
if not event.text then
|
||||
self.grid.sortColumn = 'lname'
|
||||
self.grid.values = topics
|
||||
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)
|
||||
self.grid.values = { }
|
||||
for _,f in pairs(topics) do
|
||||
if string.find(f.lname, event.text:lower()) then
|
||||
table.insert(self.grid.values, f)
|
||||
end
|
||||
end
|
||||
end
|
||||
self.grid:update()
|
||||
|
||||
@@ -1,3 +1,6 @@
|
||||
-- Lua may be called from outside of shell - inject a require
|
||||
_G.requireInjector(_ENV)
|
||||
|
||||
local History = require('opus.history')
|
||||
local UI = require('opus.ui')
|
||||
local Util = require('opus.util')
|
||||
@@ -7,8 +10,10 @@ local os = _G.os
|
||||
local textutils = _G.textutils
|
||||
local term = _G.term
|
||||
|
||||
local _exit
|
||||
|
||||
local sandboxEnv = setmetatable(Util.shallowCopy(_ENV), { __index = _G })
|
||||
sandboxEnv.exit = function() UI:quit() end
|
||||
sandboxEnv.exit = function() _exit = true end
|
||||
sandboxEnv._echo = function( ... ) return { ... } end
|
||||
_G.requireInjector(sandboxEnv)
|
||||
|
||||
@@ -29,6 +34,7 @@ local page = UI.Page {
|
||||
prompt = UI.TextEntry {
|
||||
y = 2,
|
||||
shadowText = 'enter command',
|
||||
limit = 1024,
|
||||
accelerators = {
|
||||
enter = 'command_enter',
|
||||
up = 'history_back',
|
||||
@@ -39,9 +45,8 @@ local page = UI.Page {
|
||||
},
|
||||
tabs = UI.Tabs {
|
||||
y = 3,
|
||||
formatted = UI.Tab {
|
||||
title = 'Formatted',
|
||||
index = 1,
|
||||
[1] = UI.Tab {
|
||||
tabTitle = 'Formatted',
|
||||
grid = UI.ScrollingGrid {
|
||||
columns = {
|
||||
{ heading = 'Key', key = 'name' },
|
||||
@@ -51,9 +56,8 @@ local page = UI.Page {
|
||||
autospace = true,
|
||||
},
|
||||
},
|
||||
output = UI.Tab {
|
||||
title = 'Output',
|
||||
index = 2,
|
||||
[2] = UI.Tab {
|
||||
tabTitle = 'Output',
|
||||
backgroundColor = 'black',
|
||||
output = UI.Embedded {
|
||||
y = 2,
|
||||
@@ -68,8 +72,8 @@ local page = UI.Page {
|
||||
},
|
||||
}
|
||||
|
||||
page.grid = page.tabs.formatted.grid
|
||||
page.output = page.tabs.output.output
|
||||
page.grid = page.tabs[1].grid
|
||||
page.output = page.tabs[2].output
|
||||
|
||||
function page:setPrompt(value, focus)
|
||||
self.prompt:setValue(value)
|
||||
@@ -134,11 +138,24 @@ function page:eventHandler(event)
|
||||
self:executeStatement('_ENV')
|
||||
command = nil
|
||||
|
||||
elseif event.type == 'hide_output' then
|
||||
self.output:disable()
|
||||
|
||||
self.titleBar.oy = -1
|
||||
self.titleBar.event = 'show_output'
|
||||
self.titleBar.closeInd = '^'
|
||||
self.titleBar:resize()
|
||||
|
||||
self.grid.ey = -2
|
||||
self.grid:resize()
|
||||
|
||||
self:draw()
|
||||
|
||||
elseif event.type == 'tab_select' then
|
||||
self:setFocus(self.prompt)
|
||||
|
||||
elseif event.type == 'show_output' then
|
||||
self.tabs:selectTab(self.tabs.output)
|
||||
self.tabs:selectTab(self.tabs[2])
|
||||
|
||||
elseif event.type == 'autocomplete' then
|
||||
local value = self.prompt.value or ''
|
||||
@@ -184,7 +201,7 @@ function page:eventHandler(event)
|
||||
command = nil
|
||||
self.grid:setValues(t)
|
||||
self.grid:setIndex(1)
|
||||
self.grid:draw()
|
||||
self:draw()
|
||||
end
|
||||
return true
|
||||
|
||||
@@ -230,7 +247,7 @@ function page:setResult(result)
|
||||
end
|
||||
self.grid:setValues(t)
|
||||
self.grid:setIndex(1)
|
||||
self.grid:draw()
|
||||
self:draw()
|
||||
end
|
||||
|
||||
function page.grid:eventHandler(event)
|
||||
@@ -348,7 +365,7 @@ function page:executeStatement(statement)
|
||||
term.redirect(oterm)
|
||||
counter = counter + 1
|
||||
|
||||
if s and type(m) ~= "nil" then
|
||||
if s and m then
|
||||
self:setResult(m)
|
||||
else
|
||||
self.grid:setValues({ })
|
||||
@@ -357,6 +374,10 @@ function page:executeStatement(statement)
|
||||
self:emit({ type = 'show_output' })
|
||||
end
|
||||
end
|
||||
|
||||
if _exit then
|
||||
UI:quit()
|
||||
end
|
||||
end
|
||||
|
||||
local args = Util.parse(...)
|
||||
|
||||
@@ -6,6 +6,7 @@ local Util = require('opus.util')
|
||||
|
||||
local device = _G.device
|
||||
local network = _G.network
|
||||
local os = _G.os
|
||||
local shell = _ENV.shell
|
||||
|
||||
UI:configure('Network', ...)
|
||||
@@ -85,14 +86,8 @@ local page = UI.Page {
|
||||
title = 'Ports',
|
||||
event = 'ports_hide',
|
||||
},
|
||||
menuBar = UI.MenuBar {
|
||||
y = 2,
|
||||
buttons = {
|
||||
{ text = 'Refresh', event = 'ports_update' },
|
||||
}
|
||||
},
|
||||
grid = UI.ScrollingGrid {
|
||||
y = 3,
|
||||
y = 2,
|
||||
columns = {
|
||||
{ heading = 'Port', key = 'port' },
|
||||
{ heading = 'State', key = 'state' },
|
||||
@@ -108,6 +103,30 @@ local page = UI.Page {
|
||||
return UI.SlideOut.eventHandler(self, event)
|
||||
end,
|
||||
},
|
||||
help = UI.SlideOut {
|
||||
x = 5, ex = -5, height = 8, y = -8,
|
||||
titleBar = UI.TitleBar {
|
||||
title = 'Network Help',
|
||||
event = 'slide_hide',
|
||||
},
|
||||
text = UI.TextArea {
|
||||
x = 1, y = 2,
|
||||
marginLeft = 1,
|
||||
value = [[
|
||||
|
||||
In order to connect to another computer:
|
||||
|
||||
1. The target computer must have a password set (run 'password' from the shell prompt).
|
||||
|
||||
2. From this computer, click trust and enter the password for that computer.
|
||||
|
||||
This only needs to be done once.
|
||||
]],
|
||||
},
|
||||
accelerators = {
|
||||
q = 'slide_hide',
|
||||
}
|
||||
},
|
||||
notification = UI.Notification { },
|
||||
accelerators = {
|
||||
t = 'telnet',
|
||||
@@ -188,14 +207,12 @@ function page:eventHandler(event)
|
||||
|
||||
elseif event.type == 'vnc' then
|
||||
shell.openForegroundTab('vnc.lua ' .. t.id)
|
||||
--[[
|
||||
os.queueEvent('overview_shortcut', {
|
||||
title = t.label,
|
||||
category = "VNC",
|
||||
icon = "\010\030 \009\009\031e\\\031 \031e/\031dn\010\030 \009\009 \031e\\/\031 \031bc",
|
||||
run = "vnc.lua " .. t.id,
|
||||
})
|
||||
--]]
|
||||
|
||||
elseif event.type == 'clear' then
|
||||
Util.clear(network)
|
||||
@@ -214,22 +231,17 @@ function page:eventHandler(event)
|
||||
end
|
||||
|
||||
if event.type == 'help' then
|
||||
shell.switchTab(shell.openTab('Help Networking'))
|
||||
self.help:show()
|
||||
|
||||
elseif event.type == 'ports' then
|
||||
self.ports.grid:update()
|
||||
self.ports:show()
|
||||
|
||||
-- self.portsHandler = Event.onInterval(3, function()
|
||||
-- self.ports.grid:update()
|
||||
-- self.ports.grid:draw()
|
||||
-- self:sync()
|
||||
-- end)
|
||||
|
||||
elseif event.type == 'ports_update' then
|
||||
self.portsHandler = Event.onInterval(3, function()
|
||||
self.ports.grid:update()
|
||||
self.ports.grid:draw()
|
||||
self:sync()
|
||||
end)
|
||||
|
||||
elseif event.type == 'ports_hide' then
|
||||
Event.off(self.portsHandler)
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
local Alt = require('opus.alternate')
|
||||
local Array = require('opus.array')
|
||||
local class = require('opus.class')
|
||||
local Config = require('opus.config')
|
||||
@@ -28,14 +29,12 @@ if not _ENV.multishell then
|
||||
end
|
||||
|
||||
local REGISTRY_DIR = 'usr/.registry'
|
||||
|
||||
-- iconExt:gsub('.', function(b) return '\\' .. b:byte() end)
|
||||
local DEFAULT_ICON = NFT.parse('\30\55\31\48\136\140\140\140\132\
|
||||
\30\48\31\55\149\31\48\128\128\128\30\55\149\
|
||||
\30\55\31\48\138\143\143\143\133')
|
||||
local TRANS_ICON = NFT.parse('\0302\0312\32\32\32\32\32\
|
||||
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")
|
||||
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')
|
||||
\0302\0312\32\32\32\32\32")
|
||||
|
||||
-- overview
|
||||
local uid = _ENV.multishell.getCurrent()
|
||||
@@ -51,7 +50,7 @@ local config = {
|
||||
}
|
||||
Config.load('Overview', config)
|
||||
|
||||
local extSupport = Util.supportsExtChars()
|
||||
local extSupport = Util.getVersion() >= 1.76
|
||||
|
||||
local applications = { }
|
||||
local buttons = { }
|
||||
@@ -96,7 +95,6 @@ local page = UI.Page {
|
||||
width = 8,
|
||||
selectedBackgroundColor = 'primary',
|
||||
backgroundColor = 'tertiary',
|
||||
unselectedTextColor = 'lightGray',
|
||||
layout = function(self)
|
||||
self.height = nil
|
||||
UI.TabBar.layout(self)
|
||||
@@ -468,16 +466,16 @@ function page:eventHandler(event)
|
||||
shell.switchTab(shell.openTab(event.button.app.run))
|
||||
|
||||
elseif event.type == 'shell' then
|
||||
shell.switchTab(shell.openTab('shell'))
|
||||
shell.switchTab(shell.openTab(Alt.get('shell')))
|
||||
|
||||
elseif event.type == 'lua' then
|
||||
shell.switchTab(shell.openTab('Lua'))
|
||||
shell.switchTab(shell.openTab(Alt.get('lua')))
|
||||
|
||||
elseif event.type == 'files' then
|
||||
shell.switchTab(shell.openTab('Files'))
|
||||
shell.switchTab(shell.openTab(Alt.get('files')))
|
||||
|
||||
elseif event.type == 'network' then
|
||||
shell.switchTab(shell.openTab('Network'))
|
||||
shell.switchTab(shell.openTab('network'))
|
||||
|
||||
elseif event.type == 'help' then
|
||||
shell.switchTab(shell.openTab('Help Overview'))
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
local Ansi = require('opus.ansi')
|
||||
local Config = require('opus.config')
|
||||
local Packages = require('opus.packages')
|
||||
local UI = require('opus.ui')
|
||||
local Util = require('opus.util')
|
||||
@@ -9,11 +8,9 @@ local term = _G.term
|
||||
|
||||
UI:configure('PackageManager', ...)
|
||||
|
||||
local config = Config.load('package')
|
||||
|
||||
local page = UI.Page {
|
||||
grid = UI.ScrollingGrid {
|
||||
x = 2, ex = 14, y = 2, ey = -6,
|
||||
x = 2, ex = 14, y = 2, ey = -5,
|
||||
values = { },
|
||||
columns = {
|
||||
{ heading = 'Package', key = 'name' },
|
||||
@@ -24,13 +21,13 @@ local page = UI.Page {
|
||||
},
|
||||
add = UI.Button {
|
||||
x = 2, y = -3,
|
||||
text = ' + ',
|
||||
text = 'Install',
|
||||
event = 'action',
|
||||
help = 'Install or update',
|
||||
},
|
||||
remove = UI.Button {
|
||||
x = 8, y = -3,
|
||||
text = ' - ',
|
||||
x = 12, y = -3,
|
||||
text = 'Remove ',
|
||||
event = 'action',
|
||||
operation = 'uninstall',
|
||||
operationText = 'Remove',
|
||||
@@ -44,15 +41,7 @@ local page = UI.Page {
|
||||
},
|
||||
description = UI.TextArea {
|
||||
x = 16, y = 3, ey = -5,
|
||||
marginRight = 2, marginLeft = 0,
|
||||
},
|
||||
UI.Checkbox {
|
||||
x = 3, y = -5,
|
||||
label = 'Compress',
|
||||
textColor = 'yellow',
|
||||
backgroundColor = 'primary',
|
||||
value = config.compression,
|
||||
help = 'Compress packages (experimental)',
|
||||
marginRight = 0, marginLeft = 0,
|
||||
},
|
||||
action = UI.SlideOut {
|
||||
titleBar = UI.TitleBar {
|
||||
@@ -113,6 +102,8 @@ end
|
||||
function page.action:show()
|
||||
self.output.win:clear()
|
||||
UI.SlideOut.show(self)
|
||||
--self.output:draw()
|
||||
--self.output.win.redraw()
|
||||
end
|
||||
|
||||
function page:run(operation, name)
|
||||
@@ -136,6 +127,7 @@ end
|
||||
function page:updateSelection(selected)
|
||||
self.add.operation = selected.installed and 'update' or 'install'
|
||||
self.add.operationText = selected.installed and 'Update' or 'Install'
|
||||
self.add.text = selected.installed and 'Update' or 'Install'
|
||||
self.remove.inactive = not selected.installed
|
||||
self.add:draw()
|
||||
self.remove:draw()
|
||||
@@ -148,16 +140,12 @@ function page:eventHandler(event)
|
||||
elseif event.type == 'grid_focus_row' then
|
||||
local manifest = event.selected.manifest
|
||||
|
||||
self.description:setValue(string.format('%s%s\n\n%s%s',
|
||||
self.description.value = string.format('%s%s\n\n%s%s',
|
||||
Ansi.yellow, manifest.title,
|
||||
Ansi.white, manifest.description))
|
||||
Ansi.white, manifest.description)
|
||||
self.description:draw()
|
||||
self:updateSelection(event.selected)
|
||||
|
||||
elseif event.type == 'checkbox_change' then
|
||||
config.compression = not config.compression
|
||||
Config.update('package', config)
|
||||
|
||||
elseif event.type == 'updateall' then
|
||||
self.operation = 'updateall'
|
||||
self.action.button.text = ' Begin '
|
||||
|
||||
@@ -1,238 +0,0 @@
|
||||
local Ansi = require('opus.ansi')
|
||||
local Event = require('opus.event')
|
||||
local UI = require('opus.ui')
|
||||
local Util = require('opus.util')
|
||||
|
||||
local fs = _G.fs
|
||||
local peripheral = _G.peripheral
|
||||
|
||||
local source, target
|
||||
|
||||
local function getDriveInfo(tgt)
|
||||
local total = 0
|
||||
local throttle = Util.throttle()
|
||||
|
||||
tgt = fs.combine(tgt, '')
|
||||
local src = fs.getNode(tgt).source or tgt
|
||||
|
||||
local function recurse(path)
|
||||
throttle()
|
||||
if fs.isDir(path) then
|
||||
if path ~= src then
|
||||
total = total + 500
|
||||
end
|
||||
for _, v in pairs(fs.native.list(path)) do
|
||||
recurse(fs.combine(path, v))
|
||||
end
|
||||
else
|
||||
local sz = fs.getSize(path)
|
||||
total = total + math.max(500, sz)
|
||||
end
|
||||
end
|
||||
|
||||
recurse(src)
|
||||
|
||||
local drive = fs.getDrive(src)
|
||||
return {
|
||||
path = tgt,
|
||||
drive = drive,
|
||||
type = peripheral.getType(drive) or drive,
|
||||
used = total,
|
||||
free = fs.getFreeSpace(src),
|
||||
mountPoint = src,
|
||||
}
|
||||
end
|
||||
|
||||
local function getDrives(exclude)
|
||||
local drives = { }
|
||||
|
||||
for _, path in pairs(fs.native.list('/')) do
|
||||
local side = fs.getDrive(path)
|
||||
if side and not drives[side] and not fs.isReadOnly(path) and side ~= exclude then
|
||||
if side == 'hdd' then
|
||||
path = ''
|
||||
end
|
||||
drives[side] = getDriveInfo(path)
|
||||
end
|
||||
end
|
||||
return drives
|
||||
end
|
||||
|
||||
local page = UI.Page {
|
||||
wizard = UI.Wizard {
|
||||
ey = -2,
|
||||
partitions = UI.WizardPage {
|
||||
index = 1,
|
||||
info = UI.TextArea {
|
||||
x = 3, y = 2, ex = -3, ey = 5,
|
||||
value = [[Move the contents of a directory to another disk. A link will be created to point to that location.]]
|
||||
},
|
||||
grid = UI.Grid {
|
||||
x = 2, y = 7, ex = -2, ey = -2,
|
||||
columns = {
|
||||
{ heading = 'Path', key = 'path', textColor = 'yellow', width = 10 },
|
||||
{ heading = 'Mount Point', key = 'mountPoint' },
|
||||
{ heading = 'Used', key = 'used', width = 6 },
|
||||
},
|
||||
sortColumn = 'path',
|
||||
getDisplayValues = function (_, row)
|
||||
row = Util.shallowCopy(row)
|
||||
row.used = Util.toBytes(row.used)
|
||||
return row
|
||||
end,
|
||||
enable = function(self)
|
||||
Event.onTimeout(0, function()
|
||||
local mounts = {
|
||||
usr = getDriveInfo('usr/config'),
|
||||
packages = getDriveInfo('packages'),
|
||||
}
|
||||
self:setValues(mounts)
|
||||
self:draw()
|
||||
self:sync()
|
||||
end)
|
||||
self:setValues({ })
|
||||
UI.Grid.enable(self)
|
||||
end,
|
||||
},
|
||||
validate = function(self)
|
||||
target = self.grid:getSelected()
|
||||
return not not target
|
||||
end,
|
||||
},
|
||||
mounts = UI.WizardPage {
|
||||
index = 2,
|
||||
info = UI.TextArea {
|
||||
x = 3, y = 2, ex = -3, ey = 5,
|
||||
value = [[Select the target disk. Labeled computers can be inserted into disk drives for larger volumes.]]
|
||||
},
|
||||
grid = UI.Grid {
|
||||
x = 2, y = 7, ex = -2, ey = -2,
|
||||
columns = {
|
||||
{ heading = 'Path', key = 'path', textColor = 'yellow', width = 10 },
|
||||
{ heading = 'Type', key = 'type' },
|
||||
{ heading = 'Side', key = 'drive' },
|
||||
{ heading = 'Free', key = 'free', width = 6 },
|
||||
},
|
||||
sortColumn = 'path',
|
||||
getDisplayValues = function (_, row)
|
||||
row = Util.shallowCopy(row)
|
||||
row.free = Util.toBytes(row.free)
|
||||
return row
|
||||
end,
|
||||
getRowTextColor = function(self, row)
|
||||
if row.free < target.used then
|
||||
return 'lightGray'
|
||||
end
|
||||
return UI.Grid.getRowTextColor(self, row)
|
||||
end,
|
||||
enable = function(self)
|
||||
Event.on({ 'disk', 'disk_eject', 'partition_update' }, function()
|
||||
self:setValues(getDrives(target.drive))
|
||||
self:draw()
|
||||
self:sync()
|
||||
end)
|
||||
os.queueEvent('partition_update')
|
||||
self:setValues({ })
|
||||
UI.Grid.enable(self)
|
||||
end,
|
||||
},
|
||||
validate = function(self)
|
||||
source = self.grid:getSelected()
|
||||
if not source then
|
||||
self:emit({ type = 'notify', message = 'No drive selected' })
|
||||
elseif source.free < target.used then
|
||||
self:emit({ type = 'notify', message = 'Insufficient disk space' })
|
||||
else
|
||||
return true
|
||||
end
|
||||
end,
|
||||
},
|
||||
confirm = UI.WizardPage {
|
||||
index = 3,
|
||||
info = UI.TextArea {
|
||||
x = 2, y = 2, ex = -2, ey = -2,
|
||||
marginTop = 1, marginLeft = 1,
|
||||
backgroundColor = 'black',
|
||||
},
|
||||
enable = function(self)
|
||||
local fstab = Util.readFile('usr/etc/fstab')
|
||||
local lines = { }
|
||||
table.insert(lines, string.format('%sReview changes%s\n', Ansi.yellow, Ansi.reset))
|
||||
if fstab then
|
||||
for _,l in ipairs(Util.split(fstab)) do
|
||||
l = Util.trim(l)
|
||||
if #l > 0 and l:sub(1, 1) ~= '#' then
|
||||
local m = Util.matches(l)
|
||||
if m and m[1] and m[1] == target.path then
|
||||
table.insert(lines, string.format('Removed from usr/etc/fstab:\n%s%s%s\n', Ansi.red, l, Ansi.reset))
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
local t = target.path
|
||||
local s = fs.combine(source.path .. '/' .. target.path, '')
|
||||
if t ~= s then
|
||||
table.insert(lines, string.format('Added to usr/etc/fstab:\n%s%s linkfs %s%s\n', Ansi.green, t, s, Ansi.reset))
|
||||
end
|
||||
|
||||
table.insert(lines, string.format('Move directory:\n%s/%s -> /%s', Ansi.green, target.mountPoint, s))
|
||||
|
||||
self.info:setText(table.concat(lines, '\n'))
|
||||
UI.WizardPage.enable(self)
|
||||
end,
|
||||
validate = function(self)
|
||||
if self.changesApplied then
|
||||
return true
|
||||
end
|
||||
local fstab = Util.readFile('usr/etc/fstab')
|
||||
local lines = { }
|
||||
if fstab then
|
||||
for _,l in ipairs(Util.split(fstab)) do
|
||||
table.insert(lines, l)
|
||||
l = Util.trim(l)
|
||||
if #l > 0 and l:sub(1, 1) ~= '#' then
|
||||
local m = Util.matches(l)
|
||||
if m and m[1] and m[1] == target.path then
|
||||
fs.unmount(m[1])
|
||||
table.remove(lines)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
local t = target.path
|
||||
local s = fs.combine(source.path .. '/' .. target.path, '')
|
||||
|
||||
fs.move('/' .. target.mountPoint, '/' .. s)
|
||||
|
||||
if t ~= s then
|
||||
table.insert(lines, string.format('%s linkfs %s', t, s))
|
||||
fs.mount(t, 'linkfs', s)
|
||||
end
|
||||
|
||||
Util.writeFile('usr/etc/fstab', table.concat(lines, '\n'))
|
||||
|
||||
self.parent.nextButton.text = 'Exit'
|
||||
self.parent.cancelButton:disable()
|
||||
self.parent.previousButton:disable()
|
||||
|
||||
self.changesApplied = true
|
||||
self.info:setValue('Changes have been applied')
|
||||
self.parent:draw()
|
||||
end,
|
||||
},
|
||||
},
|
||||
notification = UI.Notification { },
|
||||
eventHandler = function(self, event)
|
||||
if event.type == 'notify' then
|
||||
self.notification:error(event.message)
|
||||
elseif event.type == 'accept' or event.type == 'cancel' then
|
||||
UI:quit()
|
||||
end
|
||||
return UI.Page.eventHandler(self, event)
|
||||
end,
|
||||
}
|
||||
|
||||
UI:disableEffects()
|
||||
UI:setPage(page)
|
||||
UI:start()
|
||||
@@ -1,3 +1,5 @@
|
||||
local Alt = require('opus.alternate')
|
||||
|
||||
local kernel = _G.kernel
|
||||
local os = _G.os
|
||||
local shell = _ENV.shell
|
||||
@@ -18,7 +20,7 @@ kernel.hook('kernel_focus', function(_, eventData)
|
||||
end
|
||||
end
|
||||
if nextTab == launcherTab then
|
||||
shell.switchTab(shell.openTab('shell'))
|
||||
shell.switchTab(shell.openTab(Alt.get('shell')))
|
||||
else
|
||||
shell.switchTab(nextTab.uid)
|
||||
end
|
||||
|
||||
@@ -46,7 +46,7 @@ local page = UI.Page {
|
||||
configTabs = UI.Tabs {
|
||||
y = 2,
|
||||
filterTab = UI.Tab {
|
||||
title = 'Filter',
|
||||
tabTitle = 'Filter',
|
||||
noFill = true,
|
||||
filterGridText = UI.Text {
|
||||
x = 2, y = 2,
|
||||
@@ -93,7 +93,7 @@ local page = UI.Page {
|
||||
},
|
||||
},
|
||||
modemTab = UI.Tab {
|
||||
title = 'Modem',
|
||||
tabTitle = 'Modem',
|
||||
channelGrid = UI.ScrollingGrid {
|
||||
x = 2, y = 2,
|
||||
width = 12, height = 5,
|
||||
@@ -255,7 +255,7 @@ function page.packetSlide:eventHandler(event)
|
||||
page:setFocus(page.packetGrid)
|
||||
|
||||
elseif event.type == 'packet_lua' then
|
||||
multishell.openTab(_ENV, { path = 'sys/apps/Lua.lua', args = { self.currentPacket.message }, focused = true })
|
||||
multishell.openTab({ path = 'sys/apps/Lua.lua', args = { self.currentPacket.message }, focused = true })
|
||||
|
||||
elseif event.type == 'prev_packet' then
|
||||
local c = self.currentPacket
|
||||
|
||||
@@ -6,6 +6,62 @@ local shell = _ENV.shell
|
||||
|
||||
UI:configure('System', ...)
|
||||
|
||||
local systemPage = UI.Page {
|
||||
tabs = UI.Tabs {
|
||||
settings = UI.Tab {
|
||||
tabTitle = 'Category',
|
||||
grid = UI.ScrollingGrid {
|
||||
x = 2, y = 2, ex = -2, ey = -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:quit()
|
||||
|
||||
elseif event.type == 'success_message' then
|
||||
self.notification:success(event.message)
|
||||
|
||||
elseif event.type == 'info_message' then
|
||||
self.notification:info(event.message)
|
||||
|
||||
elseif event.type == 'error_message' then
|
||||
self.notification:error(event.message)
|
||||
|
||||
elseif event.type == 'tab_activate' then
|
||||
event.activated:focusFirst()
|
||||
|
||||
else
|
||||
return UI.Page.eventHandler(self, event)
|
||||
end
|
||||
return true
|
||||
end
|
||||
|
||||
local function loadDirectory(dir)
|
||||
local plugins = { }
|
||||
for _, file in pairs(fs.list(dir)) do
|
||||
@@ -14,69 +70,16 @@ local function loadDirectory(dir)
|
||||
_G.printError('Error loading: ' .. file)
|
||||
error(m or 'Unknown error')
|
||||
elseif s and m then
|
||||
table.insert(plugins, { tab = m, name = m.title, description = m.description })
|
||||
table.insert(plugins, { tab = m, name = m.tabTitle, description = m.description })
|
||||
end
|
||||
end
|
||||
return plugins
|
||||
end
|
||||
|
||||
local programDir = fs.getDir(_ENV.arg[0])
|
||||
local programDir = fs.getDir(shell.getRunningProgram())
|
||||
local plugins = loadDirectory(fs.combine(programDir, 'system'), { })
|
||||
|
||||
local page = UI.Page {
|
||||
tabs = UI.Tabs {
|
||||
settings = UI.Tab {
|
||||
title = 'Category',
|
||||
grid = UI.ScrollingGrid {
|
||||
x = 2, y = 2, ex = -2, ey = -2,
|
||||
columns = {
|
||||
{ heading = 'Name', key = 'name' },
|
||||
{ heading = 'Description', key = 'description' },
|
||||
},
|
||||
sortColumn = 'name',
|
||||
autospace = true,
|
||||
values = plugins,
|
||||
},
|
||||
accelerators = {
|
||||
grid_select = 'category_select',
|
||||
}
|
||||
},
|
||||
},
|
||||
notification = UI.Notification(),
|
||||
accelerators = {
|
||||
[ 'control-q' ] = 'quit',
|
||||
},
|
||||
eventHandler = function(self, event)
|
||||
if event.type == 'quit' then
|
||||
UI:quit()
|
||||
systemPage.tabs.settings.grid:setValues(plugins)
|
||||
|
||||
elseif event.type == 'category_select' then
|
||||
local tab = event.selected.tab
|
||||
|
||||
if not self.tabs[tab.title] then
|
||||
self.tabs:add({ [ tab.title ] = tab })
|
||||
end
|
||||
self.tabs:selectTab(tab)
|
||||
return true
|
||||
|
||||
elseif event.type == 'success_message' then
|
||||
self.notification:success(event.message)
|
||||
|
||||
elseif event.type == 'info_message' then
|
||||
self.notification:info(event.message)
|
||||
|
||||
elseif event.type == 'error_message' then
|
||||
self.notification:error(event.message)
|
||||
|
||||
elseif event.type == 'tab_activate' then
|
||||
event.activated:focusFirst()
|
||||
|
||||
else
|
||||
return UI.Page.eventHandler(self, event)
|
||||
end
|
||||
return true
|
||||
end,
|
||||
}
|
||||
|
||||
UI:setPage(page)
|
||||
UI:setPage(systemPage)
|
||||
UI:start()
|
||||
|
||||
@@ -12,7 +12,6 @@ local page = UI.Page {
|
||||
buttons = {
|
||||
{ text = 'Activate', event = 'activate' },
|
||||
{ text = 'Terminate', event = 'terminate' },
|
||||
{ text = 'Inspect', event = 'inspect' },
|
||||
},
|
||||
},
|
||||
grid = UI.ScrollingGrid {
|
||||
@@ -40,7 +39,7 @@ local page = UI.Page {
|
||||
},
|
||||
accelerators = {
|
||||
[ 'control-q' ] = 'quit',
|
||||
[ ' ' ] = 'activate',
|
||||
space = 'activate',
|
||||
t = 'terminate',
|
||||
},
|
||||
eventHandler = function (self, event)
|
||||
@@ -50,12 +49,6 @@ local page = UI.Page {
|
||||
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
|
||||
|
||||
@@ -1,54 +0,0 @@
|
||||
local Config = require('opus.config')
|
||||
local UI = require('opus.ui')
|
||||
|
||||
local shell = _ENV.shell
|
||||
|
||||
local config = Config.load('version')
|
||||
if not config.current then
|
||||
return
|
||||
end
|
||||
|
||||
UI:setPage(UI.Page {
|
||||
UI.Text {
|
||||
x = 2, y = 2, ex = -2,
|
||||
align = 'center',
|
||||
value = 'Opus has been updated.',
|
||||
textColor = 'yellow',
|
||||
},
|
||||
UI.TextArea {
|
||||
x = 2, y = 4, ey = -8,
|
||||
value = config.details,
|
||||
},
|
||||
UI.Button {
|
||||
x = 2, y = -6, width = 21,
|
||||
event = 'skip',
|
||||
text = 'Skip this version',
|
||||
},
|
||||
UI.Button {
|
||||
x = 2, y = -4, width = 21,
|
||||
event = 'remind',
|
||||
text = 'Remind me tomorrow',
|
||||
},
|
||||
UI.Button {
|
||||
x = 2, y = -2, width = 21,
|
||||
event = 'update',
|
||||
text = 'Update'
|
||||
},
|
||||
eventHandler = function(self, event)
|
||||
if event.type == 'skip' then
|
||||
config.skip = config.current
|
||||
Config.update('version', config)
|
||||
UI:quit()
|
||||
|
||||
elseif event.type == 'remind' then
|
||||
UI:quit()
|
||||
|
||||
elseif event.type == 'update' then
|
||||
shell.openForegroundTab('update update')
|
||||
UI:quit()
|
||||
end
|
||||
return UI.Page.eventHandler(self, event)
|
||||
end,
|
||||
})
|
||||
|
||||
UI:start()
|
||||
@@ -33,85 +33,87 @@ https://github.com/kepler155c/opus]]
|
||||
local page = UI.Page {
|
||||
wizard = UI.Wizard {
|
||||
ey = -2,
|
||||
splash = UI.WizardPage {
|
||||
index = 1,
|
||||
intro = UI.TextArea {
|
||||
textColor = colors.yellow,
|
||||
inactive = true,
|
||||
x = 3, ex = -3, y = 2, ey = -2,
|
||||
value = string.format(splashIntro, Ansi.white),
|
||||
pages = {
|
||||
splash = UI.WizardPage {
|
||||
index = 1,
|
||||
intro = UI.TextArea {
|
||||
textColor = colors.yellow,
|
||||
inactive = true,
|
||||
x = 3, ex = -3, y = 2, ey = -2,
|
||||
value = string.format(splashIntro, Ansi.white),
|
||||
},
|
||||
},
|
||||
},
|
||||
label = UI.WizardPage {
|
||||
index = 2,
|
||||
labelText = UI.Text {
|
||||
x = 3, y = 2,
|
||||
value = 'Label'
|
||||
label = UI.WizardPage {
|
||||
index = 2,
|
||||
labelText = UI.Text {
|
||||
x = 3, y = 2,
|
||||
value = 'Label'
|
||||
},
|
||||
label = UI.TextEntry {
|
||||
x = 9, y = 2, ex = -3,
|
||||
limit = 32,
|
||||
value = os.getComputerLabel(),
|
||||
},
|
||||
intro = UI.TextArea {
|
||||
textColor = colors.yellow,
|
||||
inactive = true,
|
||||
x = 3, ex = -3, y = 4, ey = -3,
|
||||
value = string.format(labelIntro, Ansi.white),
|
||||
},
|
||||
validate = function (self)
|
||||
if self.label.value then
|
||||
os.setComputerLabel(self.label.value)
|
||||
end
|
||||
return true
|
||||
end,
|
||||
},
|
||||
label = UI.TextEntry {
|
||||
x = 9, y = 2, ex = -3,
|
||||
limit = 32,
|
||||
value = os.getComputerLabel(),
|
||||
password = UI.WizardPage {
|
||||
index = 3,
|
||||
passwordLabel = UI.Text {
|
||||
x = 3, y = 2,
|
||||
value = 'Password'
|
||||
},
|
||||
newPass = UI.TextEntry {
|
||||
x = 12, ex = -3, y = 2,
|
||||
limit = 32,
|
||||
mask = true,
|
||||
shadowText = 'password',
|
||||
},
|
||||
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,
|
||||
},
|
||||
intro = UI.TextArea {
|
||||
textColor = colors.yellow,
|
||||
inactive = true,
|
||||
x = 3, ex = -3, y = 4, ey = -3,
|
||||
value = string.format(labelIntro, Ansi.white),
|
||||
packages = UI.WizardPage {
|
||||
index = 4,
|
||||
button = UI.Button {
|
||||
x = 3, y = -3,
|
||||
text = 'Open Package Manager',
|
||||
event = 'packages',
|
||||
},
|
||||
intro = UI.TextArea {
|
||||
textColor = colors.yellow,
|
||||
inactive = true,
|
||||
x = 3, ex = -3, y = 2, ey = -4,
|
||||
value = string.format(packagesIntro, Ansi.white),
|
||||
},
|
||||
},
|
||||
validate = function (self)
|
||||
if self.label.value then
|
||||
os.setComputerLabel(self.label.value)
|
||||
end
|
||||
return true
|
||||
end,
|
||||
},
|
||||
password = UI.WizardPage {
|
||||
index = 3,
|
||||
passwordLabel = UI.Text {
|
||||
x = 3, y = 2,
|
||||
value = 'Password'
|
||||
},
|
||||
newPass = UI.TextEntry {
|
||||
x = 12, ex = -3, y = 2,
|
||||
limit = 32,
|
||||
mask = true,
|
||||
shadowText = 'password',
|
||||
},
|
||||
intro = UI.TextArea {
|
||||
textColor = colors.yellow,
|
||||
inactive = true,
|
||||
x = 3, ex = -3, y = 5, ey = -3,
|
||||
value = string.format(passwordIntro, Ansi.white),
|
||||
},
|
||||
validate = function (self)
|
||||
if type(self.newPass.value) == "string" and #self.newPass.value > 0 then
|
||||
Security.updatePassword(SHA.compute(self.newPass.value))
|
||||
end
|
||||
return true
|
||||
end,
|
||||
},
|
||||
packages = UI.WizardPage {
|
||||
index = 4,
|
||||
button = UI.Button {
|
||||
x = 3, y = -3,
|
||||
text = 'Open Package Manager',
|
||||
event = 'packages',
|
||||
},
|
||||
intro = UI.TextArea {
|
||||
textColor = colors.yellow,
|
||||
inactive = true,
|
||||
x = 3, ex = -3, y = 2, ey = -4,
|
||||
value = string.format(packagesIntro, Ansi.white),
|
||||
},
|
||||
},
|
||||
contributors = UI.WizardPage {
|
||||
index = 5,
|
||||
intro = UI.TextArea {
|
||||
textColor = colors.yellow,
|
||||
inactive = true,
|
||||
x = 3, ex = -3, y = 2, ey = -2,
|
||||
value = string.format(contributorsIntro, Ansi.white, Ansi.yellow, Ansi.white),
|
||||
contributors = UI.WizardPage {
|
||||
index = 5,
|
||||
intro = UI.TextArea {
|
||||
textColor = colors.yellow,
|
||||
inactive = true,
|
||||
x = 3, ex = -3, y = 2, ey = -2,
|
||||
value = string.format(contributorsIntro, Ansi.white, Ansi.yellow, Ansi.white),
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
|
||||
@@ -1,24 +0,0 @@
|
||||
local Util = require('opus.util')
|
||||
|
||||
-- some programs expect to be run in the global scope
|
||||
-- ie. busted, moonscript
|
||||
|
||||
-- create a new environment mimicing pure lua
|
||||
|
||||
local fs = _G.fs
|
||||
local shell = _ENV.shell
|
||||
|
||||
local env = Util.shallowCopy(_G)
|
||||
Util.merge(env, _ENV)
|
||||
env._G = env
|
||||
|
||||
env.arg = { ... }
|
||||
env.arg[0] = shell.resolveProgram(table.remove(env.arg, 1) or error('file name is required'))
|
||||
|
||||
_G.requireInjector(env, fs.getDir(env.arg[0]))
|
||||
|
||||
local s, m = Util.run(env, env.arg[0], table.unpack(env.arg))
|
||||
|
||||
if not s then
|
||||
error(m, -1)
|
||||
end
|
||||
@@ -17,7 +17,7 @@ local page = UI.Page {
|
||||
UI:quit()
|
||||
end
|
||||
|
||||
return UI.Page.eventHandler(self, event)
|
||||
return UI.FileSelect.eventHandler(self, event)
|
||||
end,
|
||||
}
|
||||
|
||||
|
||||
@@ -1,22 +0,0 @@
|
||||
local SHA = require("opus.crypto.sha2")
|
||||
|
||||
local acceptableCharacters = {}
|
||||
for c = 0, 127 do
|
||||
local char = string.char(c)
|
||||
-- exclude potentially ambiguous characters
|
||||
if char:match("[1-9a-zA-Z]") and char:match("[^OIl]") then
|
||||
table.insert(acceptableCharacters, char)
|
||||
end
|
||||
end
|
||||
local acceptableCharactersLen = #acceptableCharacters
|
||||
local password = ""
|
||||
|
||||
for i = 1, 10 do
|
||||
password = password .. acceptableCharacters[math.random(acceptableCharactersLen)]
|
||||
end
|
||||
|
||||
os.queueEvent("set_otp", SHA.compute(password))
|
||||
|
||||
print("This allows one other device to permanently gain access to this device.")
|
||||
print("Use the trust settings in System to revert this.")
|
||||
print("Your one-time password is: " .. password)
|
||||
@@ -49,7 +49,7 @@ page = UI.Page {
|
||||
backgroundColor = colors.red,
|
||||
y = '50%',
|
||||
properties = UI.Tab {
|
||||
title = 'Properties',
|
||||
tabTitle = 'Properties',
|
||||
grid = UI.ScrollingGrid {
|
||||
headerBackgroundColor = colors.red,
|
||||
sortColumn = 'key',
|
||||
@@ -64,7 +64,7 @@ page = UI.Page {
|
||||
},
|
||||
methodsTab = UI.Tab {
|
||||
index = 2,
|
||||
title = 'Methods',
|
||||
tabTitle = 'Methods',
|
||||
grid = UI.ScrollingGrid {
|
||||
ex = '50%',
|
||||
headerBackgroundColor = colors.red,
|
||||
@@ -85,7 +85,7 @@ page = UI.Page {
|
||||
},
|
||||
events = UI.Tab {
|
||||
index = 1,
|
||||
title = 'Events',
|
||||
tabTitle = 'Events',
|
||||
UI.MenuBar {
|
||||
y = -1,
|
||||
backgroundColor = colors.red,
|
||||
@@ -110,7 +110,7 @@ page = UI.Page {
|
||||
self.grid:draw()
|
||||
|
||||
elseif event.type == 'grid_select' then
|
||||
multishell.openTab(_ENV, {
|
||||
multishell.openTab({
|
||||
path = 'sys/apps/Lua.lua',
|
||||
args = { event.selected.raw },
|
||||
focused = true,
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
_G.requireInjector(_ENV)
|
||||
|
||||
local Event = require('opus.event')
|
||||
local Util = require('opus.util')
|
||||
|
||||
|
||||
@@ -59,10 +59,6 @@ local function sambaConnection(socket)
|
||||
print('samba: Connection closed')
|
||||
end
|
||||
|
||||
local function sanitizeLabel(computer)
|
||||
return (computer.id.."_"..computer.label:gsub("[%c%.\"'/%*]", "")):sub(1, 40)
|
||||
end
|
||||
|
||||
Event.addRoutine(function()
|
||||
print('samba: listening on port 139')
|
||||
|
||||
@@ -83,10 +79,10 @@ Event.addRoutine(function()
|
||||
end)
|
||||
|
||||
Event.on('network_attach', function(_, computer)
|
||||
fs.mount(fs.combine('network', sanitizeLabel(computer)), 'netfs', computer.id)
|
||||
fs.mount(fs.combine('network', computer.label), 'netfs', computer.id)
|
||||
end)
|
||||
|
||||
Event.on('network_detach', function(_, computer)
|
||||
print('samba: detaching ' .. sanitizeLabel(computer))
|
||||
fs.unmount(fs.combine('network', sanitizeLabel(computer)))
|
||||
print('samba: detaching ' .. computer.label)
|
||||
fs.unmount(fs.combine('network', computer.label))
|
||||
end)
|
||||
|
||||
@@ -31,14 +31,21 @@ local function snmpConnection(socket)
|
||||
socket:write('pong')
|
||||
|
||||
elseif msg.type == 'script' then
|
||||
kernel.run(_ENV, {
|
||||
chunk = msg.args,
|
||||
title = 'script',
|
||||
})
|
||||
local env = setmetatable(Util.shallowCopy(_ENV), { __index = _G })
|
||||
local fn, err = load(msg.args, 'script', nil, env)
|
||||
if fn then
|
||||
kernel.run({
|
||||
fn = fn,
|
||||
env = env,
|
||||
title = 'script',
|
||||
})
|
||||
else
|
||||
_G.printError(err)
|
||||
end
|
||||
|
||||
elseif msg.type == 'scriptEx' then
|
||||
local s, m = pcall(function()
|
||||
local env = kernel.makeEnv(_ENV)
|
||||
local env = setmetatable(Util.shallowCopy(_ENV), { __index = _G })
|
||||
local fn, m = load(msg.args, 'script', nil, env)
|
||||
if not fn then
|
||||
error(m)
|
||||
@@ -152,7 +159,7 @@ local function getSlots()
|
||||
end
|
||||
|
||||
local function sendInfo()
|
||||
if os.clock() - infoTimer >= 5 then -- don't flood
|
||||
if os.clock() - infoTimer >= 1 then -- don't flood
|
||||
infoTimer = os.clock()
|
||||
info.label = os.getComputerLabel()
|
||||
info.uptime = math.floor(os.clock())
|
||||
@@ -194,25 +201,16 @@ local function sendInfo()
|
||||
end
|
||||
end
|
||||
|
||||
local function cleanNetwork()
|
||||
-- every 10 seconds, send out this computer's info
|
||||
Event.onInterval(10, function()
|
||||
sendInfo()
|
||||
for _,c in pairs(_G.network) do
|
||||
local elapsed = os.clock()-c.timestamp
|
||||
if c.active and elapsed > 50 then
|
||||
if c.active and elapsed > 15 then
|
||||
c.active = false
|
||||
os.queueEvent('network_detach', c)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
-- every 30 seconds, send out this computer's info
|
||||
-- send with offset so that messages are evenly distributed and do not all come at once
|
||||
Event.onTimeout(math.random() * 30, function()
|
||||
sendInfo()
|
||||
cleanNetwork()
|
||||
Event.onInterval(30, function()
|
||||
sendInfo()
|
||||
cleanNetwork()
|
||||
end)
|
||||
end)
|
||||
|
||||
Event.on('turtle_response', function()
|
||||
@@ -222,5 +220,4 @@ Event.on('turtle_response', function()
|
||||
end
|
||||
end)
|
||||
|
||||
-- send info early so that computers show soon after booting
|
||||
Event.onTimeout(math.random() * 2 + 1, sendInfo)
|
||||
Event.onTimeout(1, sendInfo)
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
local Alt = require('opus.alternate')
|
||||
local Event = require('opus.event')
|
||||
local Socket = require('opus.socket')
|
||||
local Util = require('opus.util')
|
||||
|
||||
local kernel = _G.kernel
|
||||
local shell = _ENV.shell
|
||||
local term = _G.term
|
||||
local window = _G.window
|
||||
|
||||
@@ -41,17 +41,18 @@ local function telnetHost(socket, mode)
|
||||
end
|
||||
end
|
||||
|
||||
local shellThread = kernel.run(_ENV, {
|
||||
local shellThread = kernel.run({
|
||||
terminal = win,
|
||||
window = win,
|
||||
title = mode .. ' client',
|
||||
hidden = true,
|
||||
fn = function()
|
||||
Util.run(kernel.makeEnv(_ENV), shell.resolveProgram('shell'), table.unpack(termInfo.program))
|
||||
co = coroutine.create(function()
|
||||
Util.run(_ENV, Alt.get('shell'), table.unpack(termInfo.program))
|
||||
if socket.queue then
|
||||
socket:write(socket.queue)
|
||||
end
|
||||
socket:close()
|
||||
end,
|
||||
end)
|
||||
})
|
||||
|
||||
Event.addRoutine(function()
|
||||
|
||||
@@ -6,22 +6,6 @@ local Util = require('opus.util')
|
||||
|
||||
local trustId = '01c3ba27fe01383a03a1785276d99df27c3edcef68fbf231ca'
|
||||
|
||||
local oneTimePassword -- nil by default
|
||||
|
||||
local function validateData(data, password, dhost)
|
||||
local s
|
||||
s, data = pcall(Crypto.decrypt, data, password)
|
||||
|
||||
if s and data and type(data) == "table" and data.pk and data.dh == dhost then
|
||||
local trustList = Util.readTable('usr/.known_hosts') or { }
|
||||
trustList[data.dh] = data.pk
|
||||
Util.writeTable('usr/.known_hosts', trustList)
|
||||
return true
|
||||
else
|
||||
return false
|
||||
end
|
||||
end
|
||||
|
||||
local function trustConnection(socket)
|
||||
local data = socket:read(2)
|
||||
if data then
|
||||
@@ -29,22 +13,17 @@ local function trustConnection(socket)
|
||||
if not password then
|
||||
socket:write({ msg = 'No password has been set' })
|
||||
else
|
||||
if validateData(data, password, socket.dhost) then
|
||||
print("Accepted trust from " .. socket.dhost)
|
||||
local s
|
||||
s, data = pcall(Crypto.decrypt, data, password)
|
||||
if s and data and data.pk and data.dh == socket.dhost then
|
||||
local trustList = Util.readTable('usr/.known_hosts') or { }
|
||||
trustList[data.dh] = data.pk
|
||||
Util.writeTable('usr/.known_hosts', trustList)
|
||||
|
||||
socket:write({ success = true, msg = 'Trust accepted' })
|
||||
return
|
||||
else
|
||||
socket:write({ msg = 'Invalid password' })
|
||||
end
|
||||
|
||||
if oneTimePassword then
|
||||
if validateData(data, oneTimePassword, socket.dhost) then
|
||||
print("Accepted trust from " .. socket.dhost .. "using one-time password")
|
||||
socket:write({ success = true, msg = 'Trust accepted - this one-time password will not be usable again' })
|
||||
oneTimePassword = nil -- Make sure nobody can use the one-time password again
|
||||
return
|
||||
end
|
||||
end
|
||||
|
||||
socket:write({ msg = 'Invalid password' })
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -65,12 +44,3 @@ Event.addRoutine(function()
|
||||
end
|
||||
end
|
||||
end)
|
||||
|
||||
Event.addRoutine(function()
|
||||
while true do
|
||||
local _event, password = os.pullEvent("set_otp")
|
||||
|
||||
oneTimePassword = password
|
||||
print("got new one-time password")
|
||||
end
|
||||
end)
|
||||
@@ -1,9 +1,6 @@
|
||||
local BulkGet = require('opus.bulkget')
|
||||
local Config = require('opus.config')
|
||||
local Git = require('opus.git')
|
||||
local LZW = require('opus.compress.lzw')
|
||||
local Packages = require('opus.packages')
|
||||
local Tar = require('opus.compress.tar')
|
||||
local Util = require('opus.util')
|
||||
|
||||
local fs = _G.fs
|
||||
@@ -19,8 +16,9 @@ local function makeSandbox()
|
||||
end
|
||||
|
||||
local function Syntax(msg)
|
||||
print('Syntax: package list | install [name] ... | update [name] | updateall | uninstall [name]\n')
|
||||
error(msg)
|
||||
_G.printError(msg)
|
||||
print('\nSyntax: Package list | install [name] ... | update [name] | uninstall [name]')
|
||||
error(0)
|
||||
end
|
||||
|
||||
local function progress(max)
|
||||
@@ -78,11 +76,6 @@ local function install(name, isUpdate, ignoreDeps)
|
||||
local packageDir = fs.combine('packages', name)
|
||||
|
||||
local list = Git.list(manifest.repository)
|
||||
-- clear out contents before install/update
|
||||
-- TODO: figure out whether to run
|
||||
-- install/uninstall for the package
|
||||
fs.delete(packageDir)
|
||||
|
||||
local showProgress = progress(Util.size(list))
|
||||
|
||||
local getList = { }
|
||||
@@ -103,12 +96,6 @@ local function install(name, isUpdate, ignoreDeps)
|
||||
if not isUpdate then
|
||||
runScript(manifest.install)
|
||||
end
|
||||
|
||||
if Config.load('package').compression then
|
||||
local c = Tar.tar_string(packageDir)
|
||||
Util.writeFile(packageDir .. '.tar.lzw', LZW.compress(c), 'wb')
|
||||
fs.delete(packageDir)
|
||||
end
|
||||
end
|
||||
|
||||
if action == 'list' then
|
||||
@@ -165,7 +152,6 @@ if action == 'uninstall' then
|
||||
|
||||
local packageDir = fs.combine('packages', name)
|
||||
fs.delete(packageDir)
|
||||
fs.delete(packageDir .. '.tar.lzw')
|
||||
print('removed: ' .. packageDir)
|
||||
return
|
||||
end
|
||||
|
||||
@@ -1,12 +1,21 @@
|
||||
local parentShell = _ENV.shell
|
||||
|
||||
_ENV.shell = { }
|
||||
|
||||
local trace = require('opus.trace')
|
||||
local Util = require('opus.util')
|
||||
local fs = _G.fs
|
||||
local settings = _G.settings
|
||||
local shell = _ENV.shell
|
||||
|
||||
local fs = _G.fs
|
||||
local settings = _G.settings
|
||||
local shell = _ENV.shell
|
||||
local sandboxEnv = setmetatable({ }, { __index = _G })
|
||||
for k,v in pairs(_ENV) do
|
||||
sandboxEnv[k] = v
|
||||
end
|
||||
sandboxEnv.shell = shell
|
||||
|
||||
_G.requireInjector(_ENV)
|
||||
|
||||
local trace = require('opus.trace')
|
||||
local Util = require('opus.util')
|
||||
|
||||
local DIR = (parentShell and parentShell.dir()) or ""
|
||||
local PATH = (parentShell and parentShell.path()) or ".:/rom/programs"
|
||||
@@ -16,16 +25,16 @@ local tCompletionInfo = (parentShell and parentShell.getCompletionInfo()) or {}
|
||||
local bExit = false
|
||||
local tProgramStack = {}
|
||||
|
||||
local function tokenise(...)
|
||||
local sLine = table.concat({ ... }, ' ')
|
||||
local tWords = { }
|
||||
local function tokenise( ... )
|
||||
local sLine = table.concat( { ... }, " " )
|
||||
local tWords = {}
|
||||
local bQuoted = false
|
||||
for match in string.gmatch(sLine .. "\"", "(.-)\"") do
|
||||
for match in string.gmatch( sLine .. "\"", "(.-)\"" ) do
|
||||
if bQuoted then
|
||||
table.insert(tWords, match)
|
||||
table.insert( tWords, match )
|
||||
else
|
||||
for m in string.gmatch(match, "[^ \t]+") do
|
||||
table.insert(tWords, m)
|
||||
for m in string.gmatch( match, "[^ \t]+" ) do
|
||||
table.insert( tWords, m )
|
||||
end
|
||||
end
|
||||
bQuoted = not bQuoted
|
||||
@@ -34,75 +43,37 @@ local function tokenise(...)
|
||||
return tWords
|
||||
end
|
||||
|
||||
local defaultHandlers = {
|
||||
function(env, command, args)
|
||||
return command:match("^(https?:)") and {
|
||||
title = fs.getName(command),
|
||||
path = command,
|
||||
args = args,
|
||||
load = Util.loadUrl,
|
||||
env = env,
|
||||
}
|
||||
end,
|
||||
|
||||
function(env, command, args)
|
||||
command = env.shell.resolveProgram(command)
|
||||
or error('No such program')
|
||||
|
||||
_G.requireInjector(env, fs.getDir(command))
|
||||
return {
|
||||
title = fs.getName(command):match('([^%.]+)'),
|
||||
path = command,
|
||||
args = args,
|
||||
load = loadfile,
|
||||
env = env,
|
||||
}
|
||||
end,
|
||||
}
|
||||
|
||||
function shell.getHandlers()
|
||||
if parentShell and parentShell.getHandlers then
|
||||
return parentShell.getHandlers()
|
||||
end
|
||||
return defaultHandlers
|
||||
end
|
||||
|
||||
local handlers = shell.getHandlers()
|
||||
|
||||
function shell.registerHandler(fn)
|
||||
table.insert(handlers, 1, fn)
|
||||
end
|
||||
|
||||
local function handleCommand(env, command, args)
|
||||
for _,v in pairs(handlers) do
|
||||
local pi = v(env, command, args)
|
||||
if pi then
|
||||
return pi
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
local function run(...)
|
||||
local function run(env, ...)
|
||||
local args = tokenise(...)
|
||||
if #args == 0 then
|
||||
error('No such program')
|
||||
local command = table.remove(args, 1) or error('No such program')
|
||||
local isUrl = not not command:match("^(https?:)")
|
||||
|
||||
local path, loadFn
|
||||
if isUrl then
|
||||
path = command
|
||||
loadFn = Util.loadUrl
|
||||
else
|
||||
path = shell.resolveProgram(command) or error('No such program')
|
||||
loadFn = loadfile
|
||||
end
|
||||
|
||||
local pi = handleCommand(shell.makeEnv(_ENV), table.remove(args, 1), args)
|
||||
|
||||
local O_v_O, err = pi.load(pi.path, pi.env)
|
||||
if not O_v_O then
|
||||
error(err, -1)
|
||||
local fn, err = loadFn(path, env)
|
||||
if not fn then
|
||||
error(err)
|
||||
end
|
||||
|
||||
if _ENV.multishell then
|
||||
_ENV.multishell.setTitle(_ENV.multishell.getCurrent(), pi.title)
|
||||
_ENV.multishell.setTitle(_ENV.multishell.getCurrent(), fs.getName(path):match('([^%.]+)'))
|
||||
end
|
||||
|
||||
tProgramStack[#tProgramStack + 1] = pi
|
||||
tProgramStack[#tProgramStack + 1] = {
|
||||
path = path, -- path:match("^https?://([^/:]+:?[0-9]*/?.*)$")
|
||||
env = env,
|
||||
args = args,
|
||||
}
|
||||
|
||||
pi.env[ "arg" ] = { [0] = pi.path, table.unpack(pi.args) }
|
||||
local r = { O_v_O(table.unpack(pi.args)) }
|
||||
env[ "arg" ] = { [0] = path, table.unpack(args) }
|
||||
local r = { fn(table.unpack(args)) }
|
||||
|
||||
tProgramStack[#tProgramStack] = nil
|
||||
|
||||
@@ -117,7 +88,10 @@ function shell.run(...)
|
||||
oldTitle = _ENV.multishell.getTitle(_ENV.multishell.getCurrent())
|
||||
end
|
||||
|
||||
local r = { trace(run, ...) }
|
||||
local env = setmetatable(Util.shallowCopy(sandboxEnv), { __index = _G })
|
||||
_G.requireInjector(env)
|
||||
|
||||
local r = { trace(run, env, ...) }
|
||||
|
||||
if _ENV.multishell then
|
||||
_ENV.multishell.setTitle(_ENV.multishell.getCurrent(), oldTitle or 'shell')
|
||||
@@ -131,14 +105,7 @@ function shell.exit()
|
||||
end
|
||||
|
||||
function shell.dir() return DIR end
|
||||
function shell.setDir(d)
|
||||
d = fs.combine(d, '')
|
||||
if not fs.isDir(d) then
|
||||
error("Not a directory", 2)
|
||||
end
|
||||
DIR = d
|
||||
end
|
||||
|
||||
function shell.setDir(d) DIR = d end
|
||||
function shell.path() return PATH end
|
||||
function shell.setPath(p) PATH = p end
|
||||
|
||||
@@ -151,41 +118,49 @@ function shell.resolve( _sPath )
|
||||
end
|
||||
end
|
||||
|
||||
function shell.resolveProgram(_sCommand)
|
||||
function shell.resolveProgram( _sCommand )
|
||||
if tAliases[_sCommand] ~= nil then
|
||||
_sCommand = tAliases[_sCommand]
|
||||
end
|
||||
|
||||
local function check(f)
|
||||
return fs.exists(f) and not fs.isDir(f) and f
|
||||
if _sCommand:match("^(https?:)") then
|
||||
return _sCommand
|
||||
end
|
||||
|
||||
local function inPath()
|
||||
-- Otherwise, look on the path variable
|
||||
for sPath in string.gmatch(PATH or '', "[^:]+") do
|
||||
sPath = fs.combine(sPath, _sCommand )
|
||||
if check(sPath) then
|
||||
return sPath
|
||||
end
|
||||
if check(sPath .. '.lua') then
|
||||
return sPath .. '.lua'
|
||||
end
|
||||
local path = shell.resolve(_sCommand)
|
||||
if fs.exists(path) and not fs.isDir(path) then
|
||||
return path
|
||||
end
|
||||
if fs.exists(path .. '.lua') then
|
||||
return path .. '.lua'
|
||||
end
|
||||
|
||||
-- If the path is a global path, use it directly
|
||||
local sStartChar = string.sub( _sCommand, 1, 1 )
|
||||
if sStartChar == "/" or sStartChar == "\\" then
|
||||
local sPath = fs.combine( "", _sCommand )
|
||||
if fs.exists( sPath ) and not fs.isDir( sPath ) then
|
||||
return sPath
|
||||
end
|
||||
return nil
|
||||
end
|
||||
|
||||
-- Otherwise, look on the path variable
|
||||
for sPath in string.gmatch(PATH or '', "[^:]+") do
|
||||
sPath = fs.combine(sPath, _sCommand )
|
||||
if fs.exists( sPath ) and not fs.isDir( sPath ) then
|
||||
return sPath
|
||||
end
|
||||
if fs.exists(sPath .. '.lua') then
|
||||
return sPath .. '.lua'
|
||||
end
|
||||
end
|
||||
|
||||
-- so... even if you are in the rom directory and you run:
|
||||
-- 'packages/common/edit.lua', allow this even though it
|
||||
-- does not use a leading slash. Ideally, fs.combine would
|
||||
-- provide the leading slash... but it does not.
|
||||
return (not _sCommand:find('/')) and inPath()
|
||||
or check(shell.resolve(_sCommand))
|
||||
or check(shell.resolve(_sCommand) .. '.lua')
|
||||
or check(_sCommand)
|
||||
or check(_sCommand .. '.lua')
|
||||
-- Not found
|
||||
return nil
|
||||
end
|
||||
|
||||
function shell.programs(_bIncludeHidden)
|
||||
local tItems = { }
|
||||
function shell.programs( _bIncludeHidden )
|
||||
local tItems = {}
|
||||
|
||||
-- Add programs from the path
|
||||
for sPath in string.gmatch(PATH, "[^:]+") do
|
||||
@@ -202,84 +177,98 @@ function shell.programs(_bIncludeHidden)
|
||||
end
|
||||
|
||||
-- Sort and return
|
||||
local tItemList = { }
|
||||
for sItem in pairs(tItems) do
|
||||
table.insert(tItemList, sItem)
|
||||
local tItemList = {}
|
||||
for sItem in pairs( tItems ) do
|
||||
table.insert( tItemList, sItem )
|
||||
end
|
||||
table.sort(tItemList)
|
||||
table.sort( tItemList )
|
||||
return tItemList
|
||||
end
|
||||
|
||||
function shell.completeProgram(sLine)
|
||||
if #sLine > 0 and string.sub(sLine, 1, 1) == '/' then
|
||||
local function completeProgram( sLine )
|
||||
if #sLine > 0 and string.sub( sLine, 1, 1 ) == "/" then
|
||||
-- Add programs from the root
|
||||
return fs.complete(sLine, '', true, false)
|
||||
end
|
||||
return fs.complete( sLine, "", true, false )
|
||||
else
|
||||
local tResults = {}
|
||||
local tSeen = {}
|
||||
|
||||
local tResults = { }
|
||||
local tSeen = { }
|
||||
|
||||
-- Add aliases
|
||||
for sAlias in pairs( tAliases ) do
|
||||
if #sAlias > #sLine and string.sub(sAlias, 1, #sLine) == sLine then
|
||||
local sResult = string.sub(sAlias, #sLine + 1)
|
||||
if not tSeen[sResult] then
|
||||
table.insert(tResults, sResult .. ' ')
|
||||
tSeen[sResult] = true
|
||||
-- Add aliases
|
||||
for sAlias in pairs( tAliases ) do
|
||||
if #sAlias > #sLine and string.sub( sAlias, 1, #sLine ) == sLine then
|
||||
local sResult = string.sub( sAlias, #sLine + 1 )
|
||||
if not tSeen[ sResult ] then
|
||||
table.insert( tResults, sResult )
|
||||
tSeen[ sResult ] = true
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
-- Add programs from the path
|
||||
local tPrograms = shell.programs()
|
||||
for n=1,#tPrograms do
|
||||
local sProgram = tPrograms[n]
|
||||
if #sProgram >= #sLine and string.sub(sProgram, 1, #sLine) == sLine then
|
||||
local sResult = string.sub(sProgram, #sLine + 1)
|
||||
if not tSeen[sResult] then
|
||||
table.insert(tResults, sResult .. ' ')
|
||||
tSeen[sResult] = true
|
||||
-- Add programs from the path
|
||||
local tPrograms = shell.programs()
|
||||
for n=1,#tPrograms do
|
||||
local sProgram = tPrograms[n]
|
||||
if #sProgram > #sLine and string.sub( sProgram, 1, #sLine ) == sLine then
|
||||
local sResult = string.sub( sProgram, #sLine + 1 )
|
||||
if not tSeen[ sResult ] then
|
||||
table.insert( tResults, sResult )
|
||||
tSeen[ sResult ] = true
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
-- Sort and return
|
||||
table.sort(tResults)
|
||||
return tResults
|
||||
-- Sort and return
|
||||
table.sort( tResults )
|
||||
return tResults
|
||||
end
|
||||
end
|
||||
|
||||
local function completeProgramArgument( sProgram, nArgument, sPart, tPreviousParts )
|
||||
local tInfo = tCompletionInfo[ sProgram ]
|
||||
if tInfo then
|
||||
return tInfo.fnComplete( shell, nArgument, sPart, tPreviousParts )
|
||||
end
|
||||
return nil
|
||||
end
|
||||
|
||||
function shell.complete(sLine)
|
||||
local tWords = tokenise(sLine)
|
||||
local nIndex = #tWords
|
||||
if string.sub(sLine, #sLine, #sLine) == ' ' and #Util.trim(sLine) > 0 then
|
||||
nIndex = nIndex + 1
|
||||
end
|
||||
|
||||
if nIndex == 0 then
|
||||
return fs.complete('', shell.dir(), true, false)
|
||||
|
||||
elseif nIndex == 1 then
|
||||
local results = shell.completeProgram(tWords[1] or '')
|
||||
for _, v in pairs(fs.complete(table.concat(tWords, ' '), shell.dir(), true, false)) do
|
||||
table.insert(results, v)
|
||||
if #sLine > 0 then
|
||||
local tWords = tokenise( sLine )
|
||||
local nIndex = #tWords
|
||||
if string.sub( sLine, #sLine, #sLine ) == " " then
|
||||
nIndex = nIndex + 1
|
||||
end
|
||||
return results
|
||||
if nIndex == 1 then
|
||||
local sBit = tWords[1] or ""
|
||||
local sPath = shell.resolveProgram( sBit )
|
||||
if tCompletionInfo[ sPath ] then
|
||||
return { " " }
|
||||
else
|
||||
local tResults = completeProgram( sBit )
|
||||
for n=1,#tResults do
|
||||
local sResult = tResults[n]
|
||||
local cPath = shell.resolveProgram( sBit .. sResult )
|
||||
if tCompletionInfo[ cPath ] then
|
||||
tResults[n] = sResult .. " "
|
||||
end
|
||||
end
|
||||
return tResults
|
||||
end
|
||||
|
||||
else
|
||||
local sPath = shell.resolveProgram(tWords[1])
|
||||
local sPart = tWords[nIndex] or ''
|
||||
local tPreviousParts = tWords
|
||||
tPreviousParts[nIndex] = nil
|
||||
local results
|
||||
local tInfo = tCompletionInfo[sPath]
|
||||
if tInfo then
|
||||
results = tInfo.fnComplete(shell, nIndex - 1, sPart, tPreviousParts)
|
||||
elseif nIndex > 1 then
|
||||
local sPath = shell.resolveProgram( tWords[1] )
|
||||
local sPart = tWords[nIndex] or ""
|
||||
local tPreviousParts = tWords
|
||||
tPreviousParts[nIndex] = nil
|
||||
return completeProgramArgument( sPath , nIndex - 1, sPart, tPreviousParts )
|
||||
end
|
||||
return results and #results > 0 and results
|
||||
or fs.complete(sPart, shell.dir(), true, false)
|
||||
end
|
||||
end
|
||||
|
||||
function shell.completeProgram( sProgram )
|
||||
return completeProgram( sProgram )
|
||||
end
|
||||
|
||||
function shell.setCompletionFunction(sProgram, fnComplete)
|
||||
tCompletionInfo[sProgram] = { fnComplete = fnComplete }
|
||||
end
|
||||
@@ -296,18 +285,20 @@ function shell.getRunningInfo()
|
||||
return tProgramStack[#tProgramStack]
|
||||
end
|
||||
|
||||
-- convenience function for making a runnable env
|
||||
function shell.makeEnv(env, dir)
|
||||
env = setmetatable(Util.shallowCopy(env), { __index = _G })
|
||||
_G.requireInjector(env, dir)
|
||||
return env
|
||||
function shell.setEnv(name, value)
|
||||
_ENV[name] = value
|
||||
sandboxEnv[name] = value
|
||||
end
|
||||
|
||||
function shell.setAlias(_sCommand, _sProgram)
|
||||
function shell.getEnv()
|
||||
return sandboxEnv
|
||||
end
|
||||
|
||||
function shell.setAlias( _sCommand, _sProgram )
|
||||
tAliases[_sCommand] = _sProgram
|
||||
end
|
||||
|
||||
function shell.clearAlias(_sCommand)
|
||||
function shell.clearAlias( _sCommand )
|
||||
tAliases[_sCommand] = nil
|
||||
end
|
||||
|
||||
@@ -326,6 +317,7 @@ function shell.newTab(tabInfo, ...)
|
||||
|
||||
if path then
|
||||
tabInfo.path = path
|
||||
tabInfo.env = Util.shallowCopy(sandboxEnv)
|
||||
tabInfo.args = args
|
||||
tabInfo.title = fs.getName(path):match('([^%.]+)')
|
||||
|
||||
@@ -333,21 +325,25 @@ function shell.newTab(tabInfo, ...)
|
||||
table.insert(tabInfo.args, 1, tabInfo.path)
|
||||
tabInfo.path = 'sys/apps/shell.lua'
|
||||
end
|
||||
return _ENV.multishell.openTab(_ENV, tabInfo)
|
||||
return _ENV.multishell.openTab(tabInfo)
|
||||
end
|
||||
return nil, 'No such program'
|
||||
end
|
||||
|
||||
if not _ENV.multishell then
|
||||
function shell.newTab()
|
||||
error('Multishell is not available')
|
||||
function shell.openTab( ... )
|
||||
-- needs to use multishell.launch .. so we can run with stock multishell
|
||||
local tWords = tokenise( ... )
|
||||
local sCommand = tWords[1]
|
||||
if sCommand then
|
||||
local sPath = shell.resolveProgram(sCommand)
|
||||
if sPath == "sys/apps/shell.lua" then
|
||||
return _ENV.multishell.launch(Util.shallowCopy(sandboxEnv), sPath, table.unpack(tWords, 2))
|
||||
else
|
||||
return _ENV.multishell.launch(Util.shallowCopy(sandboxEnv), "sys/apps/shell.lua", sCommand, table.unpack(tWords, 2))
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
function shell.openTab(...)
|
||||
return shell.newTab({ }, ...)
|
||||
end
|
||||
|
||||
function shell.openForegroundTab( ... )
|
||||
return shell.newTab({ focused = true }, ...)
|
||||
end
|
||||
@@ -362,7 +358,10 @@ end
|
||||
|
||||
local tArgs = { ... }
|
||||
if #tArgs > 0 then
|
||||
return run(...)
|
||||
local env = setmetatable(Util.shallowCopy(sandboxEnv), { __index = _G })
|
||||
_G.requireInjector(env)
|
||||
|
||||
return run(env, ...)
|
||||
end
|
||||
|
||||
local Config = require('opus.config')
|
||||
@@ -379,16 +378,23 @@ local textutils = _G.textutils
|
||||
|
||||
local oldTerm
|
||||
local terminal = term.current()
|
||||
local _len = string.len
|
||||
local _rep = string.rep
|
||||
local _sub = string.sub
|
||||
|
||||
if not terminal.scrollUp then
|
||||
terminal = Terminal.window(term.current())
|
||||
terminal.setMaxScroll(200)
|
||||
oldTerm = term.redirect(terminal)
|
||||
end
|
||||
|
||||
local config = {
|
||||
color = {
|
||||
textColor = colors.white,
|
||||
commandTextColor = colors.yellow,
|
||||
directoryTextColor = colors.orange,
|
||||
directoryBackgroundColor = colors.black,
|
||||
promptTextColor = colors.blue,
|
||||
promptBackgroundColor = colors.black,
|
||||
directoryColor = colors.green,
|
||||
fileColor = colors.white,
|
||||
backgroundColor = colors.black,
|
||||
@@ -405,15 +411,43 @@ if not _colors.backgroundColor then
|
||||
_colors.fileColor = colors.white
|
||||
end
|
||||
|
||||
if not terminal.scrollUp then
|
||||
terminal = Terminal.window(term.current())
|
||||
terminal.setMaxScroll(200)
|
||||
oldTerm = term.redirect(terminal)
|
||||
term.setBackgroundColor(_colors.backgroundColor)
|
||||
term.clear()
|
||||
local palette = { }
|
||||
for n = 1, 16 do
|
||||
palette[2 ^ (n - 1)] = _sub("0123456789abcdef", n, n)
|
||||
end
|
||||
|
||||
local palette = terminal.canvas.palette
|
||||
if not term.isColor() then
|
||||
_colors = { }
|
||||
for k, v in pairs(config.color) do
|
||||
_colors[k] = Terminal.colorToGrayscale(v)
|
||||
end
|
||||
for n = 1, 16 do
|
||||
palette[2 ^ (n - 1)] = _sub("088888878877787f", n, n)
|
||||
end
|
||||
end
|
||||
|
||||
local function autocompleteArgument(program, words)
|
||||
local word = ''
|
||||
if #words > 1 then
|
||||
word = words[#words]
|
||||
end
|
||||
|
||||
local tInfo = tCompletionInfo[program]
|
||||
return tInfo.fnComplete(shell, #words - 1, word, words)
|
||||
end
|
||||
|
||||
local function autocompleteAnything(line, words)
|
||||
local results = shell.complete(line)
|
||||
|
||||
if results and #results == 0 and #words == 1 then
|
||||
results = nil
|
||||
end
|
||||
if not results then
|
||||
results = fs.complete(words[#words] or '', shell.dir(), true, false)
|
||||
end
|
||||
|
||||
return results
|
||||
end
|
||||
|
||||
local function autocomplete(line)
|
||||
local words = { }
|
||||
@@ -427,7 +461,14 @@ local function autocomplete(line)
|
||||
words = { '' }
|
||||
end
|
||||
|
||||
local results = shell.complete(line) or { }
|
||||
local results
|
||||
|
||||
local program = shell.resolveProgram(words[1])
|
||||
if tCompletionInfo[program] then
|
||||
results = autocompleteArgument(program, words) or { }
|
||||
else
|
||||
results = autocompleteAnything(line, words) or { }
|
||||
end
|
||||
|
||||
Util.filterInplace(results, function(f)
|
||||
return not Util.key(results, f .. '/')
|
||||
@@ -440,8 +481,8 @@ local function autocomplete(line)
|
||||
if #results == 1 then
|
||||
words[#words] = results[1]
|
||||
return table.concat(words, ' ')
|
||||
|
||||
elseif #results > 1 then
|
||||
|
||||
local function someComplete()
|
||||
-- ugly (complete as much as possible)
|
||||
local word = words[#words] or ''
|
||||
@@ -450,16 +491,16 @@ local function autocomplete(line)
|
||||
local ch
|
||||
for _,f in ipairs(results) do
|
||||
if #f < i then
|
||||
words[#words] = _sub(f, 1, i - 1)
|
||||
words[#words] = string.sub(f, 1, i - 1)
|
||||
return table.concat(words, ' ')
|
||||
end
|
||||
if not ch then
|
||||
ch = _sub(f, i, i)
|
||||
elseif _sub(f, i, i) ~= ch then
|
||||
ch = string.sub(f, i, i)
|
||||
elseif string.sub(f, i, i) ~= ch then
|
||||
if i == #word + 1 then
|
||||
return
|
||||
end
|
||||
words[#words] = _sub(f, 1, i - 1)
|
||||
words[#words] = string.sub(f, 1, i - 1)
|
||||
return table.concat(words, ' ')
|
||||
end
|
||||
end
|
||||
@@ -502,9 +543,10 @@ local function autocomplete(line)
|
||||
local tw = term.getSize()
|
||||
local nMaxLen = tw / 8
|
||||
for _,sItem in pairs(results) do
|
||||
nMaxLen = math.max(_len(sItem) + 1, nMaxLen)
|
||||
nMaxLen = math.max(string.len(sItem) + 1, nMaxLen)
|
||||
end
|
||||
local nCols = math.floor(tw / nMaxLen)
|
||||
local w = term.getSize()
|
||||
local nCols = math.floor(w / nMaxLen)
|
||||
if #tDirs < nCols then
|
||||
for _ = #tDirs + 1, nCols do
|
||||
table.insert(tDirs, '')
|
||||
@@ -519,9 +561,11 @@ local function autocomplete(line)
|
||||
end
|
||||
|
||||
term.setTextColour(_colors.promptTextColor)
|
||||
term.setBackgroundColor(_colors.promptBackgroundColor)
|
||||
term.write("$ " )
|
||||
|
||||
term.setTextColour(_colors.commandTextColor)
|
||||
term.setBackgroundColor(_colors.backgroundColor)
|
||||
return line
|
||||
end
|
||||
end
|
||||
@@ -548,16 +592,17 @@ local function shellRead(history)
|
||||
term.setCursorPos(3, cy)
|
||||
entry.value = entry.value or ''
|
||||
local filler = #entry.value < lastLen
|
||||
and _rep(' ', lastLen - #entry.value)
|
||||
and string.rep(' ', lastLen - #entry.value)
|
||||
or ''
|
||||
local str = _sub(entry.value, entry.scroll + 1, entry.width + entry.scroll) .. filler
|
||||
local str = string.sub(entry.value, entry.scroll + 1, entry.width + entry.scroll) .. filler
|
||||
local fg = _rep(palette[_colors.commandTextColor], #str)
|
||||
local bg = _rep(palette[_colors.backgroundColor], #str)
|
||||
if entry.mark.active then
|
||||
bg = _rep('f', entry.mark.x) ..
|
||||
_rep('7', entry.mark.ex - entry.mark.x) ..
|
||||
_rep('f', #entry.value - entry.mark.ex + #filler + 1)
|
||||
bg = _sub(bg, entry.scroll + 1, entry.scroll + #str)
|
||||
local sx = entry.mark.x - entry.scroll + 1
|
||||
local ex = entry.mark.ex - entry.scroll + 1
|
||||
bg = string.rep('f', sx - 1) ..
|
||||
string.rep('7', ex - sx) ..
|
||||
string.rep('f', #str - ex + 1)
|
||||
end
|
||||
term.blit(str, fg, bg)
|
||||
updateCursor()
|
||||
@@ -609,11 +654,6 @@ 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 ''
|
||||
@@ -625,7 +665,6 @@ local function shellRead(history)
|
||||
end
|
||||
|
||||
elseif event == "term_resize" then
|
||||
terminal.reposition(1, 1, oldTerm.getSize())
|
||||
entry.width = term.getSize() - 3
|
||||
entry:updateScroll()
|
||||
redraw()
|
||||
@@ -633,26 +672,30 @@ local function shellRead(history)
|
||||
end
|
||||
|
||||
print()
|
||||
term.setCursorBlink(false)
|
||||
term.setCursorBlink( false )
|
||||
return entry.value or ''
|
||||
end
|
||||
|
||||
local history = History.load('usr/.shell_history', 100)
|
||||
local history = History.load('usr/.shell_history', 25)
|
||||
|
||||
term.setBackgroundColor(_colors.backgroundColor)
|
||||
term.clear()
|
||||
|
||||
if settings.get("motd.enable") then
|
||||
if settings.get("motd.enabled") then
|
||||
shell.run("motd")
|
||||
end
|
||||
|
||||
while not bExit do
|
||||
if config.displayDirectory then
|
||||
term.setTextColour(_colors.directoryTextColor)
|
||||
term.setBackgroundColor(_colors.directoryBackgroundColor)
|
||||
print('==' .. os.getComputerLabel() .. ':/' .. DIR)
|
||||
end
|
||||
term.setTextColour(_colors.promptTextColor)
|
||||
term.setBackgroundColor(_colors.promptBackgroundColor)
|
||||
term.write("$ " )
|
||||
term.setTextColour(_colors.commandTextColor)
|
||||
term.setBackgroundColor(_colors.backgroundColor)
|
||||
local sLine = shellRead(history)
|
||||
if bExit then -- terminated
|
||||
break
|
||||
@@ -664,11 +707,6 @@ while not bExit do
|
||||
term.setTextColour(_colors.textColor)
|
||||
if #sLine > 0 then
|
||||
local result, err = shell.run(sLine)
|
||||
local cx = term.getCursorPos()
|
||||
if cx ~= 1 then
|
||||
print()
|
||||
end
|
||||
term.setBackgroundColor(_colors.backgroundColor)
|
||||
if not result and err then
|
||||
_G.printError(err)
|
||||
end
|
||||
|
||||
@@ -4,7 +4,7 @@ local UI = require('opus.ui')
|
||||
local kernel = _G.kernel
|
||||
|
||||
local aliasTab = UI.Tab {
|
||||
title = 'Aliases',
|
||||
tabTitle = 'Aliases',
|
||||
description = 'Shell aliases',
|
||||
alias = UI.TextEntry {
|
||||
x = 2, y = 2, ex = -2,
|
||||
@@ -13,6 +13,7 @@ local aliasTab = UI.Tab {
|
||||
},
|
||||
path = UI.TextEntry {
|
||||
y = 3, x = 2, ex = -2,
|
||||
limit = 256,
|
||||
shadowText = 'Program path',
|
||||
accelerators = {
|
||||
enter = 'new_alias',
|
||||
|
||||
77
sys/apps/system/alternate.lua
Normal file
77
sys/apps/system/alternate.lua
Normal file
@@ -0,0 +1,77 @@
|
||||
local Array = require('opus.array')
|
||||
local Config = require('opus.config')
|
||||
local UI = require('opus.ui')
|
||||
|
||||
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' },
|
||||
},
|
||||
getRowTextColor = function(self, row)
|
||||
if row == self.values[1] then
|
||||
return 'yellow'
|
||||
end
|
||||
return UI.Grid.getRowTextColor(self, row)
|
||||
end,
|
||||
},
|
||||
statusBar = UI.StatusBar {
|
||||
values = 'Double-click to set as preferred'
|
||||
},
|
||||
}
|
||||
|
||||
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
|
||||
@@ -6,7 +6,7 @@ if _G.http.websocket then
|
||||
local config = Config.load('cloud')
|
||||
|
||||
local tab = UI.Tab {
|
||||
title = 'Cloud',
|
||||
tabTitle = 'Cloud',
|
||||
description = 'Cloud Catcher options',
|
||||
[1] = UI.Window {
|
||||
x = 2, y = 2, ex = -2, ey = 4,
|
||||
|
||||
@@ -16,7 +16,7 @@ local NftImages = {
|
||||
}
|
||||
|
||||
local tab = UI.Tab {
|
||||
title = 'Disks Usage',
|
||||
tabTitle = 'Disks Usage',
|
||||
description = 'Visualise HDD and disks usage',
|
||||
|
||||
drives = UI.ScrollingGrid {
|
||||
@@ -136,17 +136,6 @@ function tab:enable()
|
||||
self:updateDrives()
|
||||
self:updateInfo()
|
||||
UI.Tab.enable(self)
|
||||
self.handler = Event.on({ 'disk', 'disk_eject' }, function()
|
||||
os.sleep(1)
|
||||
tab:updateDrives()
|
||||
tab:updateInfo()
|
||||
tab:sync()
|
||||
end)
|
||||
end
|
||||
|
||||
function tab:disable()
|
||||
Event.off(self.handler)
|
||||
UI.Tab.disable(self)
|
||||
end
|
||||
|
||||
function tab:eventHandler(event)
|
||||
@@ -158,4 +147,11 @@ function tab:eventHandler(event)
|
||||
return true
|
||||
end
|
||||
|
||||
Event.on({ 'disk', 'disk_eject' }, function()
|
||||
os.sleep(1)
|
||||
tab:updateDrives()
|
||||
tab:updateInfo()
|
||||
tab:sync()
|
||||
end)
|
||||
|
||||
return tab
|
||||
|
||||
@@ -5,7 +5,7 @@ local peripheral = _G.peripheral
|
||||
local settings = _G.settings
|
||||
|
||||
return peripheral.find('monitor') and UI.Tab {
|
||||
title = 'Kiosk',
|
||||
tabTitle = 'Kiosk',
|
||||
description = 'Kiosk options',
|
||||
form = UI.Form {
|
||||
x = 2, y = 2, ex = -2, ey = 5,
|
||||
|
||||
@@ -5,7 +5,7 @@ local fs = _G.fs
|
||||
local os = _G.os
|
||||
|
||||
return UI.Tab {
|
||||
title = 'Label',
|
||||
tabTitle = 'Label',
|
||||
description = 'Set the computer label',
|
||||
labelText = UI.Text {
|
||||
x = 3, y = 3,
|
||||
@@ -26,12 +26,12 @@ return UI.Tab {
|
||||
x = 2, y = 5, ex = -2, ey = -2,
|
||||
values = {
|
||||
{ name = '', value = '' },
|
||||
{ name = 'CC version', value = ("%d.%d"):format(Util.getVersion()) },
|
||||
{ name = 'Lua version', value = _VERSION },
|
||||
{ name = 'MC version', value = Util.getMinecraftVersion() },
|
||||
{ name = 'Disk free', value = Util.toBytes(fs.getFreeSpace('/')) },
|
||||
{ name = 'Computer ID', value = tostring(os.getComputerID()) },
|
||||
{ name = 'Day', value = tostring(os.day()) },
|
||||
{ name = 'CC version', value = Util.getVersion() },
|
||||
{ name = 'Lua version', value = _VERSION },
|
||||
{ name = 'MC version', value = Util.getMinecraftVersion() },
|
||||
{ name = 'Disk free', value = Util.toBytes(fs.getFreeSpace('/')) },
|
||||
{ name = 'Computer ID', value = tostring(os.getComputerID()) },
|
||||
{ name = 'Day', value = tostring(os.day()) },
|
||||
},
|
||||
disableHeader = true,
|
||||
inactive = true,
|
||||
|
||||
@@ -7,7 +7,7 @@ local fs = _G.fs
|
||||
local config = Config.load('multishell')
|
||||
|
||||
local tab = UI.Tab {
|
||||
title = 'Launcher',
|
||||
tabTitle = 'Launcher',
|
||||
description = 'Set the application launcher',
|
||||
[1] = UI.Window {
|
||||
x = 2, y = 2, ex = -2, ey = 5,
|
||||
@@ -26,6 +26,7 @@ local tab = UI.Tab {
|
||||
},
|
||||
custom = UI.TextEntry {
|
||||
x = 13, ex = -3, y = 4,
|
||||
limit = 128,
|
||||
shadowText = 'File name',
|
||||
},
|
||||
button = UI.Button {
|
||||
|
||||
@@ -6,7 +6,7 @@ local colors = _G.colors
|
||||
local device = _G.device
|
||||
|
||||
return UI.Tab {
|
||||
title = 'Network',
|
||||
tabTitle = 'Network',
|
||||
description = 'Networking options',
|
||||
info = UI.TextArea {
|
||||
x = 2, y = 5, ex = -2, ey = -2,
|
||||
|
||||
@@ -3,7 +3,7 @@ local SHA = require('opus.crypto.sha2')
|
||||
local UI = require('opus.ui')
|
||||
|
||||
return UI.Tab {
|
||||
title = 'Password',
|
||||
tabTitle = 'Password',
|
||||
description = 'Wireless network password',
|
||||
[1] = UI.Window {
|
||||
x = 2, y = 2, ex = -2, ey = 4,
|
||||
|
||||
@@ -3,7 +3,7 @@ local UI = require('opus.ui')
|
||||
local Util = require('opus.util')
|
||||
|
||||
local tab = UI.Tab {
|
||||
title = 'Path',
|
||||
tabTitle = 'Path',
|
||||
description = 'Set the shell path',
|
||||
tabClose = true,
|
||||
[1] = UI.Window {
|
||||
@@ -11,6 +11,7 @@ local tab = UI.Tab {
|
||||
},
|
||||
entry = UI.TextEntry {
|
||||
x = 3, y = 3, ex = -3,
|
||||
limit = 256,
|
||||
shadowText = 'enter new path',
|
||||
accelerators = {
|
||||
enter = 'update_path',
|
||||
|
||||
@@ -3,11 +3,12 @@ local UI = require('opus.ui')
|
||||
local Util = require('opus.util')
|
||||
|
||||
local tab = UI.Tab {
|
||||
title = 'Requires',
|
||||
tabTitle = 'Requires',
|
||||
description = 'Require path',
|
||||
tabClose = true,
|
||||
entry = UI.TextEntry {
|
||||
x = 2, y = 2, ex = -2,
|
||||
limit = 256,
|
||||
shadowText = 'Enter new require path',
|
||||
accelerators = {
|
||||
enter = 'update_path',
|
||||
|
||||
@@ -8,7 +8,7 @@ local transform = {
|
||||
}
|
||||
|
||||
return settings and UI.Tab {
|
||||
title = 'Settings',
|
||||
tabTitle = 'Settings',
|
||||
description = 'Computercraft settings',
|
||||
grid = UI.Grid {
|
||||
x = 2, y = 2, ex = -2, ey = -2,
|
||||
@@ -27,6 +27,7 @@ return settings and UI.Tab {
|
||||
form = UI.Form {
|
||||
y = 2,
|
||||
value = UI.TextEntry {
|
||||
limit = 256,
|
||||
formIndex = 1,
|
||||
formLabel = 'Value',
|
||||
formKey = 'value',
|
||||
|
||||
@@ -18,7 +18,9 @@ local defaults = {
|
||||
textColor = colors.white,
|
||||
commandTextColor = colors.yellow,
|
||||
directoryTextColor = colors.orange,
|
||||
directoryBackgroundColor = colors.black,
|
||||
promptTextColor = colors.blue,
|
||||
promptBackgroundColor = colors.black,
|
||||
directoryColor = colors.green,
|
||||
fileColor = colors.white,
|
||||
backgroundColor = colors.black,
|
||||
@@ -37,7 +39,7 @@ if not _colors.backgroundColor then
|
||||
end
|
||||
|
||||
return UI.Tab {
|
||||
title = 'Shell',
|
||||
tabTitle = 'Shell',
|
||||
description = 'Shell options',
|
||||
grid1 = UI.ScrollingGrid {
|
||||
y = 2, ey = -10, x = 2, ex = -17,
|
||||
@@ -84,12 +86,12 @@ return UI.Tab {
|
||||
if config.displayDirectory then
|
||||
self:write(1, 1,
|
||||
'==' .. os.getComputerLabel() .. ':/dir/etc',
|
||||
_colors.backgroundColor, _colors.directoryTextColor)
|
||||
_colors.directoryBackgroundColor, _colors.directoryTextColor)
|
||||
offset = 1
|
||||
end
|
||||
|
||||
self:write(1, 1 + offset, '$ ',
|
||||
_colors.backgroundColor, _colors.promptTextColor)
|
||||
_colors.promptBackgroundColor, _colors.promptTextColor)
|
||||
|
||||
self:write(3, 1 + offset, 'ls /',
|
||||
_colors.backgroundColor, _colors.commandTextColor)
|
||||
|
||||
@@ -12,12 +12,12 @@ for k,v in pairs(colors) do
|
||||
end
|
||||
|
||||
local allSettings = { }
|
||||
for k,v in pairs(UI.theme.colors) do
|
||||
for k,v in pairs(UI.colors) do
|
||||
allSettings[k] = { name = k, value = v }
|
||||
end
|
||||
|
||||
return UI.Tab {
|
||||
title = 'Theme',
|
||||
tabTitle = 'Theme',
|
||||
description = 'Theme colors',
|
||||
grid1 = UI.ScrollingGrid {
|
||||
y = 2, ey = -10, x = 2, ex = -17,
|
||||
|
||||
@@ -1,55 +0,0 @@
|
||||
local UI = require("opus.ui")
|
||||
local Util = require("opus.util")
|
||||
local SHA = require('opus.crypto.sha2')
|
||||
|
||||
local function split(s)
|
||||
local b = ""
|
||||
for i = 1, #s, 5 do
|
||||
b = b .. s:sub(i, i+4)
|
||||
if i ~= #s-4 then
|
||||
b = b .. "-"
|
||||
end
|
||||
end
|
||||
return b
|
||||
end
|
||||
|
||||
return UI.Tab {
|
||||
title = 'Trust',
|
||||
description = 'Manage trusted devices',
|
||||
grid = UI.Grid {
|
||||
x = 2, y = 2, ex = -2, ey = -3,
|
||||
autospace = true,
|
||||
sortColumn = 'id',
|
||||
columns = {
|
||||
{ heading = 'Computer ID', key = 'id'},
|
||||
{ heading = 'Identity', key = 'pkey'}
|
||||
}
|
||||
},
|
||||
statusBar = UI.StatusBar { values = 'double-click to revoke trust' },
|
||||
reload = function(self)
|
||||
local values = {}
|
||||
for k,v in pairs(Util.readTable('usr/.known_hosts') or {}) do
|
||||
table.insert(values, {
|
||||
id = k,
|
||||
pkey = split(SHA.compute(v):sub(-20):upper()) -- Obfuscate private key for visual ident
|
||||
})
|
||||
end
|
||||
self.grid:setValues(values)
|
||||
self.grid:setIndex(1)
|
||||
end,
|
||||
enable = function(self)
|
||||
self:reload()
|
||||
UI.Tab.enable(self)
|
||||
end,
|
||||
eventHandler = function(self, event)
|
||||
if event.type == 'grid_select' then
|
||||
local hosts = Util.readTable('usr/.known_hosts')
|
||||
hosts[event.selected.id] = nil
|
||||
Util.writeTable('usr/.known_hosts', hosts)
|
||||
self:reload()
|
||||
else
|
||||
return UI.Tab.eventHandler(self, event)
|
||||
end
|
||||
return true
|
||||
end
|
||||
}
|
||||
@@ -10,8 +10,9 @@ if multishell and multishell.getTabs then
|
||||
local tab = kernel.getFocused()
|
||||
if tab and not tab.noTerminate then
|
||||
multishell.terminate(tab.uid)
|
||||
multishell.openTab(tab.env, {
|
||||
multishell.openTab({
|
||||
path = tab.path,
|
||||
env = tab.env,
|
||||
args = tab.args,
|
||||
focused = true,
|
||||
})
|
||||
|
||||
@@ -4,18 +4,29 @@
|
||||
|
||||
local kernel = _G.kernel
|
||||
local keyboard = _G.device.keyboard
|
||||
local multishell = _ENV.multishell
|
||||
local os = _G.os
|
||||
local term = _G.term
|
||||
|
||||
local function systemLog()
|
||||
local routine = kernel.getCurrent()
|
||||
|
||||
if multishell and multishell.openTab then
|
||||
local w, h = kernel.window.getSize()
|
||||
kernel.window.reposition(1, 2, w, h - 1)
|
||||
|
||||
routine.terminal = kernel.window
|
||||
routine.window = kernel.window
|
||||
term.redirect(kernel.window)
|
||||
end
|
||||
|
||||
kernel.hook('mouse_scroll', function(_, eventData)
|
||||
local dir, y = eventData[1], eventData[3]
|
||||
|
||||
if y > 1 then
|
||||
local currentTab = kernel.getFocused()
|
||||
if currentTab == routine then
|
||||
if currentTab.terminal.scrollUp then
|
||||
if currentTab.terminal.scrollUp and not currentTab.terminal.noAutoScroll then
|
||||
if dir == -1 then
|
||||
currentTab.terminal.scrollUp()
|
||||
else
|
||||
@@ -39,7 +50,7 @@ local function systemLog()
|
||||
keyboard.removeHotkey('control-d')
|
||||
end
|
||||
|
||||
kernel.run(_ENV, {
|
||||
kernel.run({
|
||||
title = 'System Log',
|
||||
fn = systemLog,
|
||||
noTerminate = true,
|
||||
|
||||
24
sys/autorun/upgraded.lua
Normal file
24
sys/autorun/upgraded.lua
Normal file
@@ -0,0 +1,24 @@
|
||||
local fs = _G.fs
|
||||
|
||||
local function deleteIfExists(path)
|
||||
if fs.exists(path) then
|
||||
fs.delete(path)
|
||||
print("Deleted outdated file at: "..path)
|
||||
end
|
||||
end
|
||||
-- cleanup outdated files
|
||||
deleteIfExists('sys/apps/shell')
|
||||
deleteIfExists('sys/etc/app.db')
|
||||
deleteIfExists('sys/extensions')
|
||||
deleteIfExists('sys/network')
|
||||
deleteIfExists('startup')
|
||||
deleteIfExists('sys/apps/system/turtle.lua')
|
||||
deleteIfExists('sys/autorun/gps.lua')
|
||||
deleteIfExists('sys/autorun/gpshost.lua')
|
||||
deleteIfExists('sys/apps/network/redserver.lua')
|
||||
deleteIfExists('sys/apis')
|
||||
deleteIfExists('sys/autorun/apps.lua')
|
||||
deleteIfExists('sys/init/6.tl3.lua')
|
||||
|
||||
-- remove this file
|
||||
-- deleteIfExists('sys/autorun/upgraded.lua')
|
||||
@@ -1,53 +0,0 @@
|
||||
local Config = require('opus.config')
|
||||
local Util = require('opus.util')
|
||||
|
||||
local fs = _G.fs
|
||||
local shell = _ENV.shell
|
||||
|
||||
local URL = 'https://raw.githubusercontent.com/kepler155c/opus/%s/.opus_version'
|
||||
|
||||
if fs.exists('.opus_version') then
|
||||
local f = fs.open('.opus_version', 'r')
|
||||
local date = f.readLine()
|
||||
f.close()
|
||||
date = type(date) == 'string' and Util.split(date)[1]
|
||||
|
||||
local today = os.date('%j')
|
||||
local config = Config.load('version', {
|
||||
packages = date,
|
||||
checked = today,
|
||||
})
|
||||
|
||||
-- check if packages need an update
|
||||
if date ~= config.packages then
|
||||
config.packages = date
|
||||
Config.update('version', config)
|
||||
print('Updating packages')
|
||||
shell.run('package updateall')
|
||||
os.reboot()
|
||||
end
|
||||
|
||||
if type(date) == 'string' and #date > 0 then
|
||||
if config.checked ~= today then
|
||||
config.checked = today
|
||||
Config.update('version', config)
|
||||
print('Checking for new version')
|
||||
pcall(function()
|
||||
local c = Util.httpGet(string.format(URL, _G.OPUS_BRANCH))
|
||||
if c then
|
||||
local lines = Util.split(c)
|
||||
local revdate = table.remove(lines, 1)
|
||||
if date ~= revdate and config.skip ~= revdate then
|
||||
config.current = revdate
|
||||
config.details = table.concat(lines, '\n')
|
||||
Config.update('version', config)
|
||||
print('New version available')
|
||||
if _ENV.multishell then
|
||||
shell.openForegroundTab('sys/apps/Version.lua')
|
||||
end
|
||||
end
|
||||
end
|
||||
end)
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -21,7 +21,7 @@ if mon then
|
||||
|
||||
parallel.waitForAny(
|
||||
function()
|
||||
os.run(_ENV, '/sys/boot/opus.lua')
|
||||
os.run(_ENV, '/sys/boot/opus.boot')
|
||||
end,
|
||||
|
||||
function()
|
||||
@@ -36,5 +36,5 @@ if mon then
|
||||
end
|
||||
)
|
||||
else
|
||||
os.run(_ENV, '/sys/boot/opus.lua')
|
||||
os.run(_ENV, '/sys/boot/opus.boot')
|
||||
end
|
||||
36
sys/boot/opus.boot
Normal file
36
sys/boot/opus.boot
Normal file
@@ -0,0 +1,36 @@
|
||||
local fs = _G.fs
|
||||
|
||||
local sandboxEnv = setmetatable({ }, { __index = _G })
|
||||
for k,v in pairs(_ENV) do
|
||||
sandboxEnv[k] = v
|
||||
end
|
||||
|
||||
local function run(file, ...)
|
||||
local env = setmetatable({ }, { __index = _G })
|
||||
for k,v in pairs(sandboxEnv) do
|
||||
env[k] = v
|
||||
end
|
||||
|
||||
local s, m = loadfile(file, env)
|
||||
if s then
|
||||
return s(...)
|
||||
end
|
||||
error('Error loading ' .. file .. '\n' .. m)
|
||||
end
|
||||
|
||||
_G._syslog = function() end
|
||||
_G.OPUS_BRANCH = '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
|
||||
@@ -1,58 +0,0 @@
|
||||
local fs = _G.fs
|
||||
|
||||
-- override bios function to use the global scope of the current env
|
||||
function _G.loadstring(string, chunkname)
|
||||
return load(string, chunkname, nil, getfenv(2)._G)
|
||||
end
|
||||
|
||||
-- override bios function to include the actual filename
|
||||
function _G.loadfile(filename, mode, env)
|
||||
-- Support the previous `loadfile(filename, env)` form instead.
|
||||
if type(mode) == "table" and env == nil then
|
||||
mode, env = nil, mode
|
||||
end
|
||||
|
||||
local file = fs.open(filename, "r")
|
||||
if not file then return nil, "File not found" end
|
||||
|
||||
local func, err = load(file.readAll(), '@' .. filename, mode, env)
|
||||
file.close()
|
||||
return func, err
|
||||
end
|
||||
|
||||
local sandboxEnv = setmetatable({ }, { __index = _G })
|
||||
for k,v in pairs(_ENV) do
|
||||
sandboxEnv[k] = v
|
||||
end
|
||||
|
||||
-- Install require shim
|
||||
_G.requireInjector = loadfile('sys/modules/opus/injector.lua', _ENV)()
|
||||
|
||||
local function run(file, ...)
|
||||
local env = setmetatable({ }, { __index = _G })
|
||||
for k,v in pairs(sandboxEnv) do
|
||||
env[k] = v
|
||||
end
|
||||
|
||||
_G.requireInjector(env)
|
||||
|
||||
local s, m = loadfile(file, env)
|
||||
if s then
|
||||
return s(...)
|
||||
end
|
||||
error('Error loading ' .. file .. '\n' .. m)
|
||||
end
|
||||
|
||||
_G._syslog = function() end
|
||||
_G.OPUS_BRANCH = 'develop-1.8'
|
||||
|
||||
local s, m = pcall(run, 'sys/apps/shell.lua', 'sys/kernel.lua', ...)
|
||||
|
||||
if not s then
|
||||
print('\nError loading Opus OS\n')
|
||||
_G.printError(m .. '\n')
|
||||
end
|
||||
|
||||
if fs.restore then
|
||||
fs.restore()
|
||||
end
|
||||
15
sys/boot/tlco.boot
Normal file
15
sys/boot/tlco.boot
Normal file
@@ -0,0 +1,15 @@
|
||||
local pullEvent = os.pullEventRaw
|
||||
local shutdown = os.shutdown
|
||||
|
||||
os.pullEventRaw = function()
|
||||
error('')
|
||||
end
|
||||
|
||||
os.shutdown = function()
|
||||
os.pullEventRaw = pullEvent
|
||||
os.shutdown = shutdown
|
||||
|
||||
os.run(getfenv(1), 'sys/boot/opus.boot')
|
||||
end
|
||||
|
||||
os.queueEvent('modem_message')
|
||||
@@ -1,30 +0,0 @@
|
||||
local run = os.run
|
||||
local shutdown = os.shutdown
|
||||
|
||||
local args = {...} -- keep the args so that they can be passed to opus.lua
|
||||
|
||||
os.run = function()
|
||||
os.run = run
|
||||
end
|
||||
|
||||
os.shutdown = function()
|
||||
os.shutdown = shutdown
|
||||
|
||||
_ENV.multishell = nil -- prevent sys/apps/shell.lua erroring for odd reasons
|
||||
|
||||
local success, err = pcall(function()
|
||||
run(_ENV, 'sys/boot/opus.lua', table.unpack(args))
|
||||
end)
|
||||
term.redirect(term.native())
|
||||
if success then
|
||||
print("Opus OS abruptly stopped.")
|
||||
else
|
||||
printError("Opus OS errored.")
|
||||
printError(err)
|
||||
end
|
||||
print("Press any key to continue.")
|
||||
os.pullEvent("key")
|
||||
shutdown()
|
||||
end
|
||||
|
||||
shell.exit()
|
||||
@@ -136,10 +136,4 @@
|
||||
iconExt = "\030 \031 \128\0307\143\131\131\131\131\143\030 \128\010\0307\031 \129\0317\128\0319\136\0309\031b\136\132\0307\0319\132\0317\128\031 \130\010\030 \0317\130\143\0307\128\128\128\128\030 \143\129",
|
||||
run = "/rom/programs/fun/dj",
|
||||
},
|
||||
[ "4dbdd221e957eff27cc47796f3ed8447290f71c7ad8b95e5bd828b31c1858f15" ] = {
|
||||
title = "Partition",
|
||||
category = "System",
|
||||
iconExt = "\30\55\31\55\128\30\48\135\131\139\30\55\128\128\128\10\30\48\31\55\149\31\48\128\30\55\145\30\48\31\56\140\30\55\157\144\144\10\30\55\31\55\128\31\48\139\143\135\31\55\128\31\56\142\133",
|
||||
run = "Partition",
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,4 +2,5 @@ sys/apps/pain.lua urlfs https://github.com/LDDestroier/CC/raw/master/pain.lua
|
||||
sys/apps/update.lua urlfs http://pastebin.com/raw/UzGHLbNC
|
||||
sys/apps/Enchat.lua urlfs https://raw.githubusercontent.com/LDDestroier/enchat/master/enchat3.lua
|
||||
sys/apps/cloud.lua urlfs https://cloud-catcher.squiddev.cc/cloud.lua
|
||||
sys/apps/nfttrans.lua urlfs https://pastebin.com/raw/e8XrzeDY
|
||||
rom/modules/main/opus linkfs sys/modules/opus
|
||||
@@ -1,9 +0,0 @@
|
||||
Wireless Networking
|
||||
===================
|
||||
To establish one-way trust between two computers with modems, run the following in a shell prompt:
|
||||
|
||||
On the target computer, run:
|
||||
> password
|
||||
|
||||
On the source computer, run:
|
||||
> trust <target computer ID>
|
||||
@@ -6,7 +6,17 @@ Shortcut Keys
|
||||
* Control-d: Show/toggle logging screen
|
||||
* Control-c: Copy (in most applications)
|
||||
* Control-shift-v: Paste from internal clipboard
|
||||
* Control-shift-click: Open the clicked UI element in Lua
|
||||
* Control-shift-doubleclick: Open in Lua the clicked UI element
|
||||
|
||||
Wireless Networking
|
||||
===================
|
||||
To establish one-way trust between two computers with modems, run the following in a shell prompt:
|
||||
|
||||
On the target computer, run:
|
||||
> password
|
||||
|
||||
On the source computer, run:
|
||||
> trust <target computer ID>
|
||||
|
||||
Running Custom Programs
|
||||
=======================
|
||||
30
sys/help/Opus-Applications
Normal file
30
sys/help/Opus-Applications
Normal file
@@ -0,0 +1,30 @@
|
||||
Opus applications are grouped into packages with a common theme.
|
||||
|
||||
To install a package, use either the System -> Packages program or the package command line program.
|
||||
|
||||
Shell usage:
|
||||
|
||||
> package list
|
||||
> package install <name>
|
||||
> package update <name>
|
||||
> package uninstall <name>
|
||||
|
||||
Package definitions are located in usr/apps/packages. This file can be modified to add custom packages.
|
||||
|
||||
Current stable packages
|
||||
=======================
|
||||
|
||||
* core
|
||||
Programming and miscellaneous applications. Also contains drivers needed for other packages.
|
||||
|
||||
* builder
|
||||
A program for creating structures from schematic files using a turtle (requires core).
|
||||
|
||||
* farms
|
||||
Various programs for farming resources (wood, crops, animals).
|
||||
|
||||
* milo
|
||||
An A/E like storage implementation (requires core).
|
||||
|
||||
* miners
|
||||
Mining programs.
|
||||
@@ -1,13 +0,0 @@
|
||||
Opus applications are grouped into packages with a common theme.
|
||||
|
||||
To install a package, use either the System -> Packages program or the package command line program.
|
||||
|
||||
Shell usage:
|
||||
|
||||
> package list
|
||||
> package install <name>
|
||||
> package update <name>
|
||||
> package updateall
|
||||
> package uninstall <name>
|
||||
|
||||
Package definitions are located in usr/config/packages. This file can be modified to add custom packages.
|
||||
@@ -19,10 +19,6 @@ for k,fn in pairs(fs) do
|
||||
end
|
||||
end
|
||||
|
||||
function nativefs.resolve(_, dir)
|
||||
return dir
|
||||
end
|
||||
|
||||
function nativefs.list(node, dir)
|
||||
local files
|
||||
if fs.native.isDir(dir) then
|
||||
@@ -83,18 +79,6 @@ function nativefs.isDir(node, dir)
|
||||
return fs.native.isDir(dir)
|
||||
end
|
||||
|
||||
function nativefs.attributes(node, path)
|
||||
if node.mountPoint == path then
|
||||
return {
|
||||
created = node.created or os.epoch('utc'),
|
||||
modification = node.modification or os.epoch('utc'),
|
||||
isDir = not not node.nodes,
|
||||
size = node.size or 0,
|
||||
}
|
||||
end
|
||||
return fs.native.attributes(path)
|
||||
end
|
||||
|
||||
function nativefs.exists(node, dir)
|
||||
if node.mountPoint == dir then
|
||||
return true
|
||||
@@ -150,10 +134,8 @@ local function getNode(dir)
|
||||
return node
|
||||
end
|
||||
|
||||
fs.getNode = getNode
|
||||
|
||||
local methods = { 'delete', 'getFreeSpace', 'exists', 'isDir', 'getSize',
|
||||
'isReadOnly', 'makeDir', 'getDrive', 'list', 'open', 'attributes' }
|
||||
'isReadOnly', 'makeDir', 'getDrive', 'list', 'open' }
|
||||
|
||||
for _,m in pairs(methods) do
|
||||
fs[m] = function(dir, ...)
|
||||
@@ -163,12 +145,6 @@ for _,m in pairs(methods) do
|
||||
end
|
||||
end
|
||||
|
||||
-- if a link, return the source for this link
|
||||
function fs.resolve(dir)
|
||||
local n = getNode(dir)
|
||||
return n.fs.resolve and n.fs.resolve(n, dir) or dir
|
||||
end
|
||||
|
||||
function fs.complete(partial, dir, includeFiles, includeSlash)
|
||||
dir = fs.combine(dir, '')
|
||||
local node = getNode(dir)
|
||||
@@ -178,13 +154,6 @@ function fs.complete(partial, dir, includeFiles, includeSlash)
|
||||
return fs.native.complete(partial, dir, includeFiles, includeSlash)
|
||||
end
|
||||
|
||||
local displayFlags = {
|
||||
urlfs = 'U',
|
||||
linkfs = 'L',
|
||||
ramfs = 'T',
|
||||
netfs = 'N',
|
||||
}
|
||||
|
||||
function fs.listEx(dir)
|
||||
dir = fs.combine(dir, '')
|
||||
local node = getNode(dir)
|
||||
@@ -195,22 +164,20 @@ function fs.listEx(dir)
|
||||
local t = { }
|
||||
local files = node.fs.list(node, dir)
|
||||
|
||||
for _,f in ipairs(files) do
|
||||
pcall(function()
|
||||
pcall(function()
|
||||
for _,f in ipairs(files) do
|
||||
local fullName = fs.combine(dir, f)
|
||||
local n = fs.getNode(fullName)
|
||||
local file = {
|
||||
name = f,
|
||||
isDir = fs.isDir(fullName),
|
||||
isReadOnly = fs.isReadOnly(fullName),
|
||||
fstype = n.mountPoint == fullName and displayFlags[n.fstype],
|
||||
}
|
||||
if not file.isDir then
|
||||
file.size = fs.getSize(fullName)
|
||||
end
|
||||
table.insert(t, file)
|
||||
end)
|
||||
end
|
||||
end
|
||||
end)
|
||||
return t
|
||||
end
|
||||
|
||||
@@ -235,12 +202,12 @@ function fs.copy(s, t)
|
||||
end
|
||||
|
||||
else
|
||||
local sf = Util.readFile(s, 'rb')
|
||||
local sf = Util.readFile(s)
|
||||
if not sf then
|
||||
error('No such file')
|
||||
end
|
||||
|
||||
Util.writeFile(t, sf, 'wb')
|
||||
Util.writeFile(t, sf)
|
||||
end
|
||||
end
|
||||
|
||||
@@ -298,13 +265,6 @@ function fs.mount(path, fstype, ...)
|
||||
if not vfs then
|
||||
error('Invalid file system type')
|
||||
end
|
||||
|
||||
-- get the mount point for the path
|
||||
-- ie. if packages is mapped to disk/packages
|
||||
-- and a request to mount /packages/foo
|
||||
-- then use disk/packages/foo as the mountPoint
|
||||
path = fs.resolve(path)
|
||||
|
||||
local node = vfs.mount(path, ...)
|
||||
if node then
|
||||
local parts = splitpath(path)
|
||||
@@ -319,16 +279,12 @@ function fs.mount(path, fstype, ...)
|
||||
tp.nodes[d] = Util.shallowCopy(tp)
|
||||
tp.nodes[d].nodes = { }
|
||||
tp.nodes[d].mountPoint = fs.combine(tp.mountPoint, d)
|
||||
tp.nodes[d].created = os.epoch('utc')
|
||||
tp.nodes[d].modification = os.epoch('utc')
|
||||
end
|
||||
tp = tp.nodes[d]
|
||||
end
|
||||
|
||||
node.fs = vfs
|
||||
node.fstype = fstype
|
||||
node.created = node.created or os.epoch('utc')
|
||||
node.modification = node.modification or os.epoch('utc')
|
||||
if not targetName then
|
||||
node.mountPoint = ''
|
||||
fs.nodes = node
|
||||
@@ -394,4 +350,4 @@ function fs.restore()
|
||||
local native = fs.native
|
||||
Util.clear(fs)
|
||||
Util.merge(fs, native)
|
||||
end
|
||||
end
|
||||
|
||||
@@ -1,46 +1,3 @@
|
||||
local fs = _G.fs
|
||||
local os = _G.os
|
||||
local fs = _G.fs
|
||||
|
||||
fs.loadTab('sys/etc/fstab')
|
||||
|
||||
-- add some Lua compatibility functions
|
||||
function os.remove(a)
|
||||
if fs.exists(a) then
|
||||
local s = pcall(fs.delete, a)
|
||||
return s and true or nil, a .. ': Unable to remove file'
|
||||
end
|
||||
return nil, a .. ': No such file or directory'
|
||||
end
|
||||
|
||||
os.execute = function(cmd)
|
||||
local env = _G.getfenv(2)
|
||||
if not cmd then
|
||||
return env.shell and 1 or 0
|
||||
end
|
||||
|
||||
if not env.shell then
|
||||
return 0
|
||||
end
|
||||
|
||||
local s, m = env.shell.run('sys/apps/shell.lua ' .. cmd)
|
||||
|
||||
if not s then
|
||||
return 1, m
|
||||
end
|
||||
|
||||
return 0
|
||||
end
|
||||
|
||||
os.tmpname = function()
|
||||
local fname
|
||||
repeat
|
||||
fname = 'tmp/a' .. math.random(1, 32768)
|
||||
until not fs.exists(fname)
|
||||
|
||||
return fname
|
||||
end
|
||||
|
||||
-- non-standard - will raise error instead
|
||||
os.exit = function(code)
|
||||
error(code or 0)
|
||||
end
|
||||
@@ -10,13 +10,6 @@ if not fs.exists('usr/autorun') then
|
||||
fs.makeDir('usr/autorun')
|
||||
end
|
||||
|
||||
-- move the fstab out of config so that the config directory
|
||||
-- can be remapped to another disk (and for consistency)
|
||||
if fs.exists('usr/config/fstab') and not fs.exists('usr/etc/fstab') then
|
||||
fs.move('usr/config/fstab', 'usr/etc/fstab')
|
||||
end
|
||||
fs.loadTab('usr/etc/fstab')
|
||||
|
||||
-- TODO: Temporary
|
||||
local upgrade = Util.readTable('usr/config/shell')
|
||||
if upgrade and (not upgrade.upgraded or upgrade.upgraded ~= 1) then
|
||||
@@ -52,3 +45,5 @@ shell.setPath(table.concat(path, ':'))
|
||||
|
||||
--_G.LUA_PATH = config.lua_path
|
||||
--_G.settings.set('mbs.shell.require_path', config.lua_path)
|
||||
|
||||
fs.loadTab('usr/config/fstab')
|
||||
|
||||
@@ -15,7 +15,7 @@ do
|
||||
end
|
||||
|
||||
local function startNetwork()
|
||||
kernel.run(_ENV, {
|
||||
kernel.run({
|
||||
title = 'Net daemon',
|
||||
path = 'sys/apps/netdaemon.lua',
|
||||
hidden = true,
|
||||
|
||||
@@ -1,31 +0,0 @@
|
||||
local LZW = require('opus.compress.lzw')
|
||||
local Tar = require('opus.compress.tar')
|
||||
local Util = require('opus.util')
|
||||
|
||||
local fs = _G.fs
|
||||
|
||||
if not fs.exists('packages') or not fs.isDir('packages') then
|
||||
return
|
||||
end
|
||||
|
||||
for _, name in pairs(fs.list('packages')) do
|
||||
local fullName = fs.combine('packages', name)
|
||||
local packageName = name:match('(.+)%.tar%.lzw$')
|
||||
if packageName and not fs.isDir(fullName) then
|
||||
local dir = fs.combine('packages', packageName)
|
||||
if not fs.exists(dir) then
|
||||
local s, m = pcall(function()
|
||||
fs.mount(dir, 'ramfs', 'directory')
|
||||
|
||||
local c = Util.readFile(fullName, 'rb')
|
||||
|
||||
Tar.untar_string(LZW.decompress(c), dir)
|
||||
end)
|
||||
if not s then
|
||||
fs.delete(dir)
|
||||
print('failed to extract ' .. fullName)
|
||||
print(m)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -13,14 +13,8 @@ table.insert(helpPaths, '/sys/help')
|
||||
for name in pairs(Packages:installed()) do
|
||||
local packageDir = fs.combine('packages', name)
|
||||
|
||||
local fstabPath = fs.combine(packageDir, 'etc/fstab')
|
||||
if fs.exists(fstabPath) then
|
||||
fs.loadTab(fstabPath)
|
||||
end
|
||||
|
||||
table.insert(appPaths, 1, '/' .. packageDir)
|
||||
|
||||
local apiPath = fs.combine(packageDir, 'apis') -- TODO: rename dir to 'modules' (someday)
|
||||
local apiPath = fs.combine(packageDir, 'apis')
|
||||
if fs.exists(apiPath) then
|
||||
fs.mount(fs.combine('rom/modules/main', name), 'linkfs', apiPath)
|
||||
end
|
||||
@@ -29,6 +23,11 @@ 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, ':'))
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
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
|
||||
@@ -8,18 +9,19 @@ 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
|
||||
local w,h = parentTerm.getSize()
|
||||
local overviewId
|
||||
local tabsDirty = false
|
||||
local closeInd = Util.supportsExtChars() and '\215' or '*'
|
||||
local closeInd = Util.getVersion() >= 1.76 and '\215' or '*'
|
||||
local multishell = { }
|
||||
|
||||
_ENV.multishell = multishell
|
||||
shell.setEnv('multishell', multishell)
|
||||
|
||||
kernel.window.reposition(1, 2, w, h - 1)
|
||||
multishell.term = parentTerm --deprecated use device.terminal
|
||||
|
||||
local config = {
|
||||
standard = {
|
||||
@@ -92,70 +94,57 @@ function multishell.getTabs()
|
||||
return kernel.routines
|
||||
end
|
||||
|
||||
function multishell.launch(env, path, ...)
|
||||
function multishell.launch( tProgramEnv, sProgramPath, ... )
|
||||
-- backwards compatibility
|
||||
return multishell.openTab(env, {
|
||||
path = path,
|
||||
return multishell.openTab({
|
||||
env = tProgramEnv,
|
||||
path = sProgramPath,
|
||||
args = { ... },
|
||||
})
|
||||
end
|
||||
|
||||
local function chain(orig, fn)
|
||||
if not orig then
|
||||
return fn
|
||||
local function xprun(env, path, ...)
|
||||
setmetatable(env, { __index = _G })
|
||||
local fn, m = loadfile(path, env)
|
||||
if fn then
|
||||
return trace(fn, ...)
|
||||
end
|
||||
|
||||
if type(orig) == 'table' then
|
||||
table.insert(orig, fn)
|
||||
return orig
|
||||
end
|
||||
|
||||
return setmetatable({ orig, fn }, {
|
||||
__call = function(self, ...)
|
||||
for _,v in pairs(self) do
|
||||
v(...)
|
||||
end
|
||||
end
|
||||
})
|
||||
return fn, m
|
||||
end
|
||||
|
||||
function multishell.openTab(env, tab)
|
||||
function multishell.openTab(tab)
|
||||
if not tab.title and tab.path then
|
||||
tab.title = fs.getName(tab.path):match('([^%.]+)')
|
||||
end
|
||||
tab.title = tab.title or 'untitled'
|
||||
tab.window = tab.window or window.create(parentTerm, 1, 2, w, h - 1, false)
|
||||
-- require('opus.terminal').window(parentTerm, 1, 2, w, h - 1, false)
|
||||
tab.onExit = chain(tab.onExit, function(self, result, err, stack)
|
||||
if not result and err and err ~= 'Terminated' then
|
||||
self.terminal.setTextColor(colors.white)
|
||||
self.terminal.setCursorBlink(false)
|
||||
print('\nThe program terminated with an error.\n')
|
||||
tab.terminal = tab.terminal or tab.window
|
||||
|
||||
local routine = kernel.newRoutine(tab)
|
||||
|
||||
routine.co = coroutine.create(function()
|
||||
local result, err
|
||||
|
||||
if tab.fn then
|
||||
result, err = Util.runFunction(routine.env, tab.fn, table.unpack(tab.args or { } ))
|
||||
elseif tab.path then
|
||||
result, err = xprun(routine.env, tab.path, table.unpack(tab.args or { } ))
|
||||
else
|
||||
err = 'multishell: invalid tab'
|
||||
end
|
||||
|
||||
if not result and err and err ~= 'Terminated' or (err and err ~= 0) then
|
||||
tab.terminal.setBackgroundColor(colors.black)
|
||||
if tonumber(err) then
|
||||
printError('Process exited with error code: ' .. err)
|
||||
tab.terminal.setTextColor(colors.orange)
|
||||
print('Process exited with error code: ' .. err)
|
||||
elseif err then
|
||||
printError(tostring(err))
|
||||
end
|
||||
if type(stack) == 'table' and #stack > 0 then
|
||||
local _, cy = self.terminal.getCursorPos()
|
||||
local _, th = self.terminal.getSize()
|
||||
self.terminal.setTextColor(colors.white)
|
||||
if cy < th - 4 then
|
||||
print('\nstack traceback:')
|
||||
for _, v in ipairs(stack or { }) do
|
||||
_, cy = self.terminal.getCursorPos()
|
||||
if cy > th - 3 then
|
||||
print(' ...')
|
||||
break
|
||||
end
|
||||
print(v)
|
||||
end
|
||||
end
|
||||
end
|
||||
self.terminal.setTextColor(parentTerm.isColor() and colors.yellow or colors.white)
|
||||
_G.write('\nPress enter to close')
|
||||
self.isDead = true
|
||||
self.hidden = false
|
||||
tab.terminal.setTextColor(colors.white)
|
||||
print('\nPress enter to close')
|
||||
routine.isDead = true
|
||||
routine.hidden = false
|
||||
redrawMenu()
|
||||
while true do
|
||||
local e, code = os.pullEventRaw('key')
|
||||
@@ -166,17 +155,14 @@ function multishell.openTab(env, tab)
|
||||
end
|
||||
end)
|
||||
|
||||
local routine, message = kernel.run(env, tab)
|
||||
kernel.launch(routine)
|
||||
|
||||
if routine then
|
||||
if tab.focused then
|
||||
multishell.setFocus(routine.uid)
|
||||
else
|
||||
redrawMenu()
|
||||
end
|
||||
if tab.focused then
|
||||
multishell.setFocus(routine.uid)
|
||||
else
|
||||
redrawMenu()
|
||||
end
|
||||
|
||||
return routine and routine.uid, message
|
||||
return routine.uid
|
||||
end
|
||||
|
||||
function multishell.hideTab(tabId)
|
||||
@@ -270,10 +256,9 @@ kernel.hook('multishell_redraw', function()
|
||||
|
||||
if currentTab then
|
||||
if currentTab.sx then
|
||||
local textColor = currentTab.isDead and _colors.errorColor or _colors.focusTextColor
|
||||
blit:write(currentTab.sx - 1,
|
||||
' ' .. currentTab.title:sub(1, currentTab.width - 1) .. ' ',
|
||||
_colors.focusBackgroundColor, textColor)
|
||||
_colors.focusBackgroundColor, _colors.focusTextColor)
|
||||
end
|
||||
if not currentTab.noTerminate then
|
||||
blit:write(w, closeInd, nil, _colors.focusTextColor)
|
||||
@@ -311,65 +296,52 @@ kernel.hook('term_resize', function(_, eventData)
|
||||
end)
|
||||
|
||||
kernel.hook('mouse_click', function(_, eventData)
|
||||
if not eventData[4] then
|
||||
local x, y = eventData[2], eventData[3]
|
||||
local x, y = eventData[2], eventData[3]
|
||||
|
||||
if y == 1 then
|
||||
if x == 1 then
|
||||
multishell.setFocus(overviewId)
|
||||
elseif x == w then
|
||||
local currentTab = kernel.getFocused()
|
||||
if currentTab then
|
||||
multishell.terminate(currentTab.uid)
|
||||
end
|
||||
else
|
||||
for _,tab in pairs(kernel.routines) do
|
||||
if not tab.hidden and tab.sx then
|
||||
if x >= tab.sx and x <= tab.ex then
|
||||
multishell.setFocus(tab.uid)
|
||||
break
|
||||
end
|
||||
if y == 1 then
|
||||
if x == 1 then
|
||||
multishell.setFocus(overviewId)
|
||||
elseif x == w then
|
||||
local currentTab = kernel.getFocused()
|
||||
if currentTab then
|
||||
multishell.terminate(currentTab.uid)
|
||||
end
|
||||
else
|
||||
for _,tab in pairs(kernel.routines) do
|
||||
if not tab.hidden and tab.sx then
|
||||
if x >= tab.sx and x <= tab.ex then
|
||||
multishell.setFocus(tab.uid)
|
||||
break
|
||||
end
|
||||
end
|
||||
end
|
||||
return true
|
||||
end
|
||||
eventData[3] = eventData[3] - 1
|
||||
return true
|
||||
end
|
||||
eventData[3] = eventData[3] - 1
|
||||
end)
|
||||
|
||||
kernel.hook({ 'mouse_up', 'mouse_drag' }, function(_, eventData)
|
||||
if not eventData[4] then
|
||||
eventData[3] = eventData[3] - 1
|
||||
end
|
||||
eventData[3] = eventData[3] - 1
|
||||
end)
|
||||
|
||||
kernel.hook('mouse_scroll', function(_, eventData)
|
||||
if not eventData[4] then
|
||||
if eventData[3] == 1 then
|
||||
return true
|
||||
end
|
||||
eventData[3] = eventData[3] - 1
|
||||
if eventData[3] == 1 then
|
||||
return true
|
||||
end
|
||||
eventData[3] = eventData[3] - 1
|
||||
end)
|
||||
|
||||
kernel.hook('kernel_ready', function()
|
||||
overviewId = multishell.openTab(_ENV, {
|
||||
path = 'sys/apps/shell.lua',
|
||||
args = { config.launcher or 'sys/apps/Overview.lua' },
|
||||
overviewId = multishell.openTab({
|
||||
path = config.launcher or 'sys/apps/Overview.lua',
|
||||
isOverview = true,
|
||||
noTerminate = true,
|
||||
focused = true,
|
||||
title = '+',
|
||||
onExit = function(_, s, m)
|
||||
if not s then
|
||||
kernel.halt(s, m)
|
||||
end
|
||||
end,
|
||||
})
|
||||
multishell.setTitle(overviewId, '+')
|
||||
|
||||
multishell.openTab(_ENV, {
|
||||
multishell.openTab({
|
||||
path = 'sys/apps/shell.lua',
|
||||
args = { 'sys/apps/autorun.lua' },
|
||||
title = 'Autorun',
|
||||
|
||||
181
sys/kernel.lua
181
sys/kernel.lua
@@ -1,6 +1,5 @@
|
||||
local Array = require('opus.array')
|
||||
local Terminal = require('opus.terminal')
|
||||
local trace = require('opus.trace')
|
||||
local Util = require('opus.util')
|
||||
|
||||
_G.kernel = {
|
||||
@@ -20,7 +19,7 @@ local w, h = term.getSize()
|
||||
kernel.terminal = term.current()
|
||||
|
||||
kernel.window = Terminal.window(kernel.terminal, 1, 1, w, h, false)
|
||||
kernel.window.setMaxScroll(200)
|
||||
kernel.window.setMaxScroll(100)
|
||||
|
||||
local focusedRoutineEvents = Util.transpose {
|
||||
'char', 'key', 'key_up',
|
||||
@@ -29,8 +28,10 @@ local focusedRoutineEvents = Util.transpose {
|
||||
}
|
||||
|
||||
_G._syslog = function(pattern, ...)
|
||||
local oldTerm = term.redirect(kernel.window)
|
||||
kernel.window.scrollBottom()
|
||||
kernel.window.print(Util.tostring(pattern, ...))
|
||||
Util.print(pattern, ...)
|
||||
term.redirect(oldTerm)
|
||||
end
|
||||
|
||||
-- any function that runs in a kernel hook does not run in
|
||||
@@ -49,23 +50,19 @@ function kernel.hook(event, fn)
|
||||
end
|
||||
end
|
||||
|
||||
-- you *should* only unhook from within the function that hooked
|
||||
-- you can only unhook from within the function that hooked
|
||||
function kernel.unhook(event, fn)
|
||||
if type(event) == 'table' then
|
||||
for _,v in pairs(event) do
|
||||
kernel.unhook(v, fn)
|
||||
end
|
||||
else
|
||||
local eventHooks = kernel.hooks[event]
|
||||
if eventHooks then
|
||||
Array.removeByValue(eventHooks, fn)
|
||||
if #eventHooks == 0 then
|
||||
kernel.hooks[event] = nil
|
||||
end
|
||||
local eventHooks = kernel.hooks[event]
|
||||
if eventHooks then
|
||||
Array.removeByValue(eventHooks, fn)
|
||||
if #eventHooks == 0 then
|
||||
kernel.hooks[event] = nil
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
local Routine = { }
|
||||
|
||||
local function switch(routine, previous)
|
||||
if routine then
|
||||
if previous and previous.window then
|
||||
@@ -83,8 +80,6 @@ local function switch(routine, previous)
|
||||
end
|
||||
end
|
||||
|
||||
local Routine = { }
|
||||
|
||||
function Routine:resume(event, ...)
|
||||
if not self.co or coroutine.status(self.co) == 'dead' then
|
||||
return
|
||||
@@ -94,64 +89,35 @@ function Routine:resume(event, ...)
|
||||
local previousTerm = term.redirect(self.terminal)
|
||||
|
||||
local previous = kernel.running
|
||||
kernel.running = self
|
||||
kernel.running = self -- stupid shell set title
|
||||
local ok, result = coroutine.resume(self.co, event, ...)
|
||||
kernel.running = previous
|
||||
|
||||
self.filter = result
|
||||
if ok then
|
||||
self.filter = result
|
||||
else
|
||||
_G.printError(result)
|
||||
end
|
||||
|
||||
self.terminal = term.current()
|
||||
term.redirect(previousTerm)
|
||||
|
||||
if not ok and self.haltOnError then
|
||||
error(result, -1)
|
||||
end
|
||||
if coroutine.status(self.co) == 'dead' then
|
||||
Array.removeByValue(kernel.routines, self)
|
||||
if #kernel.routines > 0 then
|
||||
switch(kernel.routines[1])
|
||||
end
|
||||
if self.haltOnExit then
|
||||
kernel.halt()
|
||||
end
|
||||
end
|
||||
return ok, result
|
||||
end
|
||||
end
|
||||
|
||||
function Routine:run()
|
||||
self.co = self.co or coroutine.create(function()
|
||||
local result, err, fn, stack
|
||||
|
||||
if self.fn then
|
||||
fn = self.fn
|
||||
_G.setfenv(fn, self.env)
|
||||
elseif self.path then
|
||||
fn, err = loadfile(self.path, self.env)
|
||||
elseif self.chunk then
|
||||
fn, err = load(self.chunk, self.title, nil, self.env)
|
||||
end
|
||||
|
||||
if fn then
|
||||
result, err, stack = trace(fn, table.unpack(self.args or { } ))
|
||||
else
|
||||
err = err or 'kernel: invalid routine'
|
||||
end
|
||||
|
||||
pcall(self.onExit, self, result, err, stack)
|
||||
self:cleanup()
|
||||
|
||||
if not result then
|
||||
error(err)
|
||||
end
|
||||
end)
|
||||
|
||||
table.insert(kernel.routines, self)
|
||||
|
||||
return self:resume()
|
||||
end
|
||||
|
||||
-- override if any post processing is required
|
||||
function Routine:onExit(status, message) -- self, status, message
|
||||
if not status and message ~= 'Terminated' then
|
||||
_G.printError(message)
|
||||
end
|
||||
end
|
||||
|
||||
function Routine:cleanup()
|
||||
Array.removeByValue(kernel.routines, self)
|
||||
if #kernel.routines > 0 then
|
||||
switch(kernel.routines[1])
|
||||
end
|
||||
end
|
||||
|
||||
function kernel.getFocused()
|
||||
return kernel.routines[1]
|
||||
end
|
||||
@@ -164,34 +130,51 @@ function kernel.getShell()
|
||||
return shell
|
||||
end
|
||||
|
||||
-- each routine inherits the parent's env
|
||||
function kernel.makeEnv(env, dir)
|
||||
env = setmetatable(Util.shallowCopy(env or _ENV), { __index = _G })
|
||||
_G.requireInjector(env, dir)
|
||||
return env
|
||||
end
|
||||
|
||||
function kernel.newRoutine(env, args)
|
||||
function kernel.newRoutine(args)
|
||||
kernel.UID = kernel.UID + 1
|
||||
|
||||
local routine = setmetatable({
|
||||
uid = kernel.UID,
|
||||
timestamp = os.clock(),
|
||||
terminal = kernel.window,
|
||||
window = kernel.window,
|
||||
title = 'untitled',
|
||||
}, { __index = Routine })
|
||||
|
||||
Util.merge(routine, args)
|
||||
routine.env = args.env or kernel.makeEnv(env, routine.path and fs.getDir(routine.path))
|
||||
routine.terminal = routine.terminal or routine.window
|
||||
routine.env = args.env or Util.shallowCopy(shell.getEnv())
|
||||
|
||||
return routine
|
||||
end
|
||||
|
||||
function kernel.run(env, args)
|
||||
local routine = kernel.newRoutine(env, args)
|
||||
local s, m = routine:run()
|
||||
return s and routine, m
|
||||
function kernel.launch(routine)
|
||||
routine.co = routine.co or coroutine.create(function()
|
||||
local result, err
|
||||
|
||||
if routine.fn then
|
||||
result, err = Util.runFunction(routine.env, routine.fn, table.unpack(routine.args or { } ))
|
||||
elseif routine.path then
|
||||
result, err = Util.run(routine.env, routine.path, table.unpack(routine.args or { } ))
|
||||
else
|
||||
err = 'kernel: invalid routine'
|
||||
end
|
||||
|
||||
if not result and err ~= 'Terminated' then
|
||||
error(err or 'Error occurred', 2)
|
||||
end
|
||||
end)
|
||||
|
||||
table.insert(kernel.routines, routine)
|
||||
|
||||
local s, m = routine:resume()
|
||||
|
||||
return not s and s or routine.uid, m
|
||||
end
|
||||
|
||||
function kernel.run(args)
|
||||
local routine = kernel.newRoutine(args)
|
||||
kernel.launch(routine)
|
||||
return routine
|
||||
end
|
||||
|
||||
function kernel.raise(uid)
|
||||
@@ -209,6 +192,8 @@ function kernel.raise(uid)
|
||||
end
|
||||
|
||||
switch(routine, previous)
|
||||
-- local previous = eventData[2]
|
||||
-- local routine = kernel.find(previous)
|
||||
return true
|
||||
end
|
||||
return false
|
||||
@@ -236,8 +221,8 @@ function kernel.find(uid)
|
||||
return Util.find(kernel.routines, 'uid', uid)
|
||||
end
|
||||
|
||||
function kernel.halt(status, message)
|
||||
os.queueEvent('kernel_halt', status, message)
|
||||
function kernel.halt()
|
||||
os.queueEvent('kernel_halt')
|
||||
end
|
||||
|
||||
function kernel.event(event, eventData)
|
||||
@@ -279,24 +264,19 @@ function kernel.event(event, eventData)
|
||||
end
|
||||
|
||||
function kernel.start()
|
||||
local s, m
|
||||
local s2, m2 = pcall(function()
|
||||
local s, m = pcall(function()
|
||||
repeat
|
||||
local eventData = { os.pullEventRaw() }
|
||||
local event = table.remove(eventData, 1)
|
||||
kernel.event(event, eventData)
|
||||
if event == 'kernel_halt' then
|
||||
s = eventData[1]
|
||||
m = eventData[2]
|
||||
end
|
||||
until event == 'kernel_halt'
|
||||
end)
|
||||
|
||||
if (not s and m) or (not s2 and m2) then
|
||||
if not s then
|
||||
kernel.window.setVisible(true)
|
||||
term.redirect(kernel.window)
|
||||
print('\nCrash detected\n')
|
||||
_G.printError(m or m2)
|
||||
_G.printError(m)
|
||||
end
|
||||
term.redirect(kernel.terminal)
|
||||
end
|
||||
@@ -313,10 +293,9 @@ local function init(...)
|
||||
for _,file in ipairs(files) do
|
||||
local level = file:match('(%d).%S+.lua') or 99
|
||||
if tonumber(level) <= runLevel then
|
||||
-- All init programs run under the original shell
|
||||
local s, m = shell.run(fs.combine(dir, file))
|
||||
if not s then
|
||||
error(m, -1)
|
||||
error(m)
|
||||
end
|
||||
os.sleep(0)
|
||||
end
|
||||
@@ -330,15 +309,15 @@ local function init(...)
|
||||
term.redirect(kernel.window)
|
||||
shell.run('sys/apps/autorun.lua')
|
||||
|
||||
local win = window.create(kernel.terminal, 1, 1, w, h, true)
|
||||
local s, m = kernel.run(_ENV, {
|
||||
local shellWindow = window.create(kernel.terminal, 1, 1, w, h, false)
|
||||
local s, m = kernel.run({
|
||||
title = args[1],
|
||||
path = 'sys/apps/shell.lua',
|
||||
args = args,
|
||||
window = win,
|
||||
onExit = function(_, s, m)
|
||||
kernel.halt(s, m)
|
||||
end,
|
||||
haltOnExit = true,
|
||||
haltOnError = true,
|
||||
terminal = shellWindow,
|
||||
window = shellWindow,
|
||||
})
|
||||
if s then
|
||||
kernel.raise(s.uid)
|
||||
@@ -349,15 +328,11 @@ local function init(...)
|
||||
end
|
||||
end
|
||||
|
||||
kernel.run(_ENV, {
|
||||
kernel.run({
|
||||
fn = init,
|
||||
title = 'init',
|
||||
haltOnError = true,
|
||||
args = { ... },
|
||||
onExit = function(_, status, message)
|
||||
if not status then
|
||||
kernel.halt(status, message)
|
||||
end
|
||||
end,
|
||||
})
|
||||
|
||||
kernel.start()
|
||||
|
||||
48
sys/modules/opus/alternate.lua
Normal file
48
sys/modules/opus/alternate.lua
Normal file
@@ -0,0 +1,48 @@
|
||||
local Array = require('opus.array')
|
||||
local Config = require('opus.config')
|
||||
local Util = require('opus.util')
|
||||
|
||||
local function getConfig()
|
||||
return Config.load('alternate', {
|
||||
shell = {
|
||||
'sys/apps/shell.lua',
|
||||
'rom/programs/shell.lua',
|
||||
},
|
||||
lua = {
|
||||
'sys/apps/Lua.lua',
|
||||
'rom/programs/lua.lua',
|
||||
},
|
||||
files = {
|
||||
'sys/apps/Files.lua',
|
||||
}
|
||||
})
|
||||
end
|
||||
|
||||
local Alt = { }
|
||||
|
||||
function Alt.get(key)
|
||||
return getConfig()[key][1]
|
||||
end
|
||||
|
||||
function Alt.set(key, value)
|
||||
local config = getConfig()
|
||||
Array.removeByValue(config[key], value)
|
||||
table.insert(config[key], 1, value)
|
||||
Config.update('alternate', config)
|
||||
end
|
||||
|
||||
function Alt.remove(key, value)
|
||||
local config = getConfig()
|
||||
Array.removeByValue(config[key], value)
|
||||
Config.update('alternate', config)
|
||||
end
|
||||
|
||||
function Alt.add(key, value)
|
||||
local config = getConfig()
|
||||
if not Util.contains(config[key], value) then
|
||||
table.insert(config[key], value)
|
||||
Config.update('alternate', config)
|
||||
end
|
||||
end
|
||||
|
||||
return Alt
|
||||
@@ -1,142 +0,0 @@
|
||||
-- see: https://github.com/Rochet2/lualzw
|
||||
-- MIT License - Copyright (c) 2016 Rochet2
|
||||
|
||||
local char = string.char
|
||||
local type = type
|
||||
local sub = string.sub
|
||||
local tconcat = table.concat
|
||||
|
||||
local SIGC = 'LZWC'
|
||||
|
||||
local basedictcompress = {}
|
||||
local basedictdecompress = {}
|
||||
for i = 0, 255 do
|
||||
local ic, iic = char(i), char(i, 0)
|
||||
basedictcompress[ic] = iic
|
||||
basedictdecompress[iic] = ic
|
||||
end
|
||||
|
||||
local function dictAddA(str, dict, a, b)
|
||||
if a >= 256 then
|
||||
a, b = 0, b+1
|
||||
if b >= 256 then
|
||||
dict = {}
|
||||
b = 1
|
||||
end
|
||||
end
|
||||
dict[str] = char(a,b)
|
||||
a = a+1
|
||||
return dict, a, b
|
||||
end
|
||||
|
||||
local function compress(input)
|
||||
if type(input) ~= "string" then
|
||||
error ("string expected, got "..type(input))
|
||||
end
|
||||
local len = #input
|
||||
if len <= 1 then
|
||||
return input
|
||||
end
|
||||
|
||||
local dict = {}
|
||||
local a, b = 0, 1
|
||||
|
||||
local result = { SIGC }
|
||||
local resultlen = 1
|
||||
local n = 2
|
||||
local word = ""
|
||||
for i = 1, len do
|
||||
local c = sub(input, i, i)
|
||||
local wc = word..c
|
||||
if not (basedictcompress[wc] or dict[wc]) then
|
||||
local write = basedictcompress[word] or dict[word]
|
||||
if not write then
|
||||
error "algorithm error, could not fetch word"
|
||||
end
|
||||
result[n] = write
|
||||
resultlen = resultlen + #write
|
||||
n = n+1
|
||||
if len <= resultlen then
|
||||
return input
|
||||
end
|
||||
dict, a, b = dictAddA(wc, dict, a, b)
|
||||
word = c
|
||||
else
|
||||
word = wc
|
||||
end
|
||||
end
|
||||
result[n] = basedictcompress[word] or dict[word]
|
||||
resultlen = resultlen+#result[n]
|
||||
if len <= resultlen then
|
||||
return input
|
||||
end
|
||||
return tconcat(result)
|
||||
end
|
||||
|
||||
local function dictAddB(str, dict, a, b)
|
||||
if a >= 256 then
|
||||
a, b = 0, b+1
|
||||
if b >= 256 then
|
||||
dict = {}
|
||||
b = 1
|
||||
end
|
||||
end
|
||||
dict[char(a,b)] = str
|
||||
a = a+1
|
||||
return dict, a, b
|
||||
end
|
||||
|
||||
local function decompress(input)
|
||||
if type(input) ~= "string" then
|
||||
error( "string expected, got "..type(input))
|
||||
end
|
||||
|
||||
if #input < 4 then
|
||||
return input
|
||||
end
|
||||
|
||||
local control = sub(input, 1, 4)
|
||||
if control ~= SIGC then
|
||||
return input
|
||||
end
|
||||
input = sub(input, 5)
|
||||
local len = #input
|
||||
|
||||
if len < 2 then
|
||||
error("invalid input - not a compressed string")
|
||||
end
|
||||
|
||||
local dict = {}
|
||||
local a, b = 0, 1
|
||||
|
||||
local result = {}
|
||||
local n = 1
|
||||
local last = sub(input, 1, 2)
|
||||
result[n] = basedictdecompress[last] or dict[last]
|
||||
n = n+1
|
||||
for i = 3, len, 2 do
|
||||
local code = sub(input, i, i+1)
|
||||
local lastStr = basedictdecompress[last] or dict[last]
|
||||
if not lastStr then
|
||||
error( "could not find last from dict. Invalid input?")
|
||||
end
|
||||
local toAdd = basedictdecompress[code] or dict[code]
|
||||
if toAdd then
|
||||
result[n] = toAdd
|
||||
n = n+1
|
||||
dict, a, b = dictAddB(lastStr..sub(toAdd, 1, 1), dict, a, b)
|
||||
else
|
||||
local tmp = lastStr..sub(lastStr, 1, 1)
|
||||
result[n] = tmp
|
||||
n = n+1
|
||||
dict, a, b = dictAddB(tmp, dict, a, b)
|
||||
end
|
||||
last = code
|
||||
end
|
||||
return tconcat(result)
|
||||
end
|
||||
|
||||
return {
|
||||
compress = compress,
|
||||
decompress = decompress,
|
||||
}
|
||||
@@ -1,270 +0,0 @@
|
||||
|
||||
-- see: https://github.com/luarocks/luarocks/blob/master/src/luarocks/tools/tar.lua
|
||||
-- A pure-Lua implementation of untar (unpacking .tar archives)
|
||||
local Util = require('opus.util')
|
||||
|
||||
local fs = _G.fs
|
||||
local _sub = string.sub
|
||||
|
||||
local blocksize = 512
|
||||
|
||||
local function get_typeflag(flag)
|
||||
if flag == "0" or flag == "\0" then return "file"
|
||||
elseif flag == "1" then return "link"
|
||||
elseif flag == "2" then return "symlink" -- "reserved" in POSIX, "symlink" in GNU
|
||||
elseif flag == "3" then return "character"
|
||||
elseif flag == "4" then return "block"
|
||||
elseif flag == "5" then return "directory"
|
||||
elseif flag == "6" then return "fifo"
|
||||
elseif flag == "7" then return "contiguous" -- "reserved" in POSIX, "contiguous" in GNU
|
||||
elseif flag == "x" then return "next file"
|
||||
elseif flag == "g" then return "global extended header"
|
||||
elseif flag == "L" then return "long name"
|
||||
elseif flag == "K" then return "long link name"
|
||||
end
|
||||
return "unknown"
|
||||
end
|
||||
|
||||
local function octal_to_number(octal)
|
||||
local exp = 0
|
||||
local number = 0
|
||||
octal = octal:gsub("%s", "")
|
||||
for i = #octal,1,-1 do
|
||||
local digit = tonumber(octal:sub(i,i))
|
||||
if not digit then
|
||||
break
|
||||
end
|
||||
number = number + (digit * 8^exp)
|
||||
exp = exp + 1
|
||||
end
|
||||
return number
|
||||
end
|
||||
|
||||
local function checksum_header(block)
|
||||
local sum = 256
|
||||
for i = 1,148 do
|
||||
local b = block:byte(i) or 0
|
||||
sum = sum + b
|
||||
end
|
||||
for i = 157,500 do
|
||||
local b = block:byte(i) or 0
|
||||
sum = sum + b
|
||||
end
|
||||
return sum
|
||||
end
|
||||
|
||||
local function nullterm(s)
|
||||
return s:match("^[^%z]*")
|
||||
end
|
||||
|
||||
local function read_header_block(block)
|
||||
local header = {}
|
||||
header.name = nullterm(block:sub(1,100))
|
||||
header.mode = nullterm(block:sub(101,108)):gsub(" ", "")
|
||||
header.uid = octal_to_number(nullterm(block:sub(109,116)))
|
||||
header.gid = octal_to_number(nullterm(block:sub(117,124)))
|
||||
header.size = octal_to_number(nullterm(block:sub(125,136)))
|
||||
header.mtime = octal_to_number(nullterm(block:sub(137,148)))
|
||||
header.chksum = octal_to_number(nullterm(block:sub(149,156)))
|
||||
header.typeflag = get_typeflag(block:sub(157,157))
|
||||
header.linkname = nullterm(block:sub(158,257))
|
||||
header.magic = block:sub(258,263)
|
||||
header.version = block:sub(264,265)
|
||||
header.uname = nullterm(block:sub(266,297))
|
||||
header.gname = nullterm(block:sub(298,329))
|
||||
header.devmajor = octal_to_number(nullterm(block:sub(330,337)))
|
||||
header.devminor = octal_to_number(nullterm(block:sub(338,345)))
|
||||
header.prefix = block:sub(346,500)
|
||||
if not checksum_header(block) == header.chksum then
|
||||
return false, "Failed header checksum"
|
||||
end
|
||||
return header
|
||||
end
|
||||
|
||||
local function untar_stream(tar_handle, destdir, verbose)
|
||||
assert(type(destdir) == "string")
|
||||
|
||||
local long_name, long_link_name
|
||||
local ok, err
|
||||
|
||||
local make_dir = function(a)
|
||||
if not fs.exists(a) then
|
||||
fs.makeDir(a)
|
||||
end
|
||||
return true
|
||||
end
|
||||
|
||||
while true do
|
||||
local block
|
||||
repeat
|
||||
block = tar_handle:read(blocksize)
|
||||
until (not block) or checksum_header(block) > 256
|
||||
if not block then break end
|
||||
if #block < blocksize then
|
||||
ok, err = nil, "Invalid block size -- corrupted file?"
|
||||
break
|
||||
end
|
||||
local header
|
||||
header, err = read_header_block(block)
|
||||
if not header then
|
||||
ok = false
|
||||
break
|
||||
end
|
||||
|
||||
local file_data = tar_handle:read(math.ceil(header.size / blocksize) * blocksize):sub(1,header.size)
|
||||
|
||||
if header.typeflag == "long name" then
|
||||
long_name = nullterm(file_data)
|
||||
elseif header.typeflag == "long link name" then
|
||||
long_link_name = nullterm(file_data)
|
||||
else
|
||||
if long_name then
|
||||
header.name = long_name
|
||||
long_name = nil
|
||||
end
|
||||
if long_link_name then
|
||||
header.name = long_link_name
|
||||
long_link_name = nil
|
||||
end
|
||||
end
|
||||
local pathname = fs.combine(destdir, header.name)
|
||||
|
||||
if header.typeflag == "directory" then
|
||||
ok, err = make_dir(pathname)
|
||||
if not ok then
|
||||
break
|
||||
end
|
||||
elseif header.typeflag == "file" then
|
||||
local dirname = fs.getDir(pathname)
|
||||
if dirname ~= "" then
|
||||
ok, err = make_dir(dirname)
|
||||
if not ok then
|
||||
break
|
||||
end
|
||||
end
|
||||
local file_handle
|
||||
if verbose then
|
||||
print(pathname)
|
||||
end
|
||||
file_handle, err = io.open(pathname, "wb")
|
||||
if not file_handle then
|
||||
ok = nil
|
||||
break
|
||||
end
|
||||
file_handle:write(file_data)
|
||||
file_handle:close()
|
||||
end
|
||||
end
|
||||
|
||||
return ok, err
|
||||
end
|
||||
|
||||
local function untar_string(str, destdir, verbose)
|
||||
local ctr = 1
|
||||
local len = #str
|
||||
local handle = {
|
||||
read = function(_, n)
|
||||
if ctr < len then
|
||||
local s = _sub(str, ctr, ctr + n - 1)
|
||||
ctr = ctr + n
|
||||
return s
|
||||
end
|
||||
end
|
||||
}
|
||||
return untar_stream(handle, destdir, verbose)
|
||||
end
|
||||
|
||||
local function untar(filename, destdir, verbose)
|
||||
assert(type(filename) == "string")
|
||||
assert(type(destdir) == "string")
|
||||
|
||||
local tar_handle = io.open(filename, "rb")
|
||||
if not tar_handle then return nil, "Error opening file "..filename end
|
||||
|
||||
local ok, err = untar_stream(tar_handle, destdir, verbose)
|
||||
|
||||
tar_handle:close()
|
||||
return ok, err
|
||||
end
|
||||
|
||||
local function create_header_block(filename, abspath)
|
||||
local block = ('\0'):rep(blocksize)
|
||||
|
||||
local function number_to_octal(n)
|
||||
return ('%o'):format(n)
|
||||
end
|
||||
|
||||
local function ins(pos, istr)
|
||||
block = block:sub(1, pos - 1) .. istr .. block:sub(pos + #istr)
|
||||
end
|
||||
|
||||
ins(1, filename) -- header
|
||||
ins(125, number_to_octal(fs.getSize(abspath)))
|
||||
ins(157, '0') -- typeflag
|
||||
|
||||
ins(149, number_to_octal(checksum_header(block)))
|
||||
|
||||
return block
|
||||
end
|
||||
|
||||
local function tar_stream(tar_handle, root, files)
|
||||
if not files then
|
||||
files = { }
|
||||
local function recurse(rel)
|
||||
local abs = fs.combine(root, rel)
|
||||
for _,f in ipairs(fs.list(abs)) do
|
||||
local fullName = fs.combine(abs, f)
|
||||
if fs.isDir(fullName) then -- skip virtual dirs
|
||||
recurse(fs.combine(rel, f))
|
||||
else
|
||||
table.insert(files, fs.combine(rel, f))
|
||||
end
|
||||
end
|
||||
end
|
||||
recurse('')
|
||||
end
|
||||
|
||||
for _, file in pairs(files) do
|
||||
local abs = fs.combine(root, file)
|
||||
local block = create_header_block(file, abs)
|
||||
tar_handle:write(block)
|
||||
local f = Util.readFile(abs, 'rb')
|
||||
tar_handle:write(f)
|
||||
local padding = #f % blocksize
|
||||
if padding > 0 then
|
||||
tar_handle:write(('\0'):rep(blocksize - padding))
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
local function tar_string(root, files)
|
||||
local t = { }
|
||||
local handle = {
|
||||
write = function(_, s)
|
||||
table.insert(t, s)
|
||||
end
|
||||
}
|
||||
tar_stream(handle, root, files)
|
||||
return table.concat(t)
|
||||
end
|
||||
|
||||
-- the bare minimum for this program to untar
|
||||
local function tar(filename, root, files)
|
||||
assert(type(filename) == "string")
|
||||
assert(type(root) == "string")
|
||||
|
||||
local tar_handle = io.open(filename, "wb")
|
||||
if not tar_handle then return nil, "Error opening file "..filename end
|
||||
|
||||
local ok, err = tar_stream(tar_handle, root, files)
|
||||
|
||||
tar_handle:close()
|
||||
return ok, err
|
||||
end
|
||||
|
||||
return {
|
||||
tar = tar,
|
||||
untar = untar,
|
||||
tar_string = tar_string,
|
||||
untar_string = untar_string,
|
||||
}
|
||||
@@ -100,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()
|
||||
local throttle = Util.throttle(function() _syslog('throttle') end)
|
||||
local out = {}
|
||||
local state = initState(key, nonce, cntr)
|
||||
local blockAmt = math.floor(#data/64)
|
||||
|
||||
@@ -47,13 +47,13 @@ function Entry:updateScroll()
|
||||
self.scroll = 0 -- ??
|
||||
end
|
||||
if self.pos - self.scroll > self.width then
|
||||
self.scroll = math.max(0, self.pos - self.width)
|
||||
self.scroll = self.pos - self.width
|
||||
elseif self.pos < self.scroll then
|
||||
self.scroll = self.pos
|
||||
end
|
||||
if self.scroll > 0 then
|
||||
if self.scroll + self.width > len then
|
||||
self.scroll = math.max(0, len - self.width)
|
||||
self.scroll = len - self.width
|
||||
end
|
||||
end
|
||||
if ps ~= self.scroll then
|
||||
@@ -222,7 +222,7 @@ function Entry:paste(ie)
|
||||
end
|
||||
end
|
||||
|
||||
function Entry.forcePaste()
|
||||
function Entry:forcePaste()
|
||||
os.queueEvent('clipboard_paste')
|
||||
end
|
||||
|
||||
@@ -234,9 +234,7 @@ end
|
||||
|
||||
function Entry:markBegin()
|
||||
if not self.mark.active then
|
||||
if #_val(self.value) > 0 then
|
||||
self.mark.active = true
|
||||
end
|
||||
self.mark.active = true
|
||||
self.mark.anchor = { x = self.pos }
|
||||
end
|
||||
end
|
||||
@@ -273,8 +271,6 @@ function Entry:markLeft()
|
||||
self:markBegin()
|
||||
if self:moveLeft() then
|
||||
self:markFinish()
|
||||
else
|
||||
self.mark.continue = self.mark.active
|
||||
end
|
||||
end
|
||||
|
||||
@@ -282,8 +278,6 @@ function Entry:markRight()
|
||||
self:markBegin()
|
||||
if self:moveRight() then
|
||||
self:markFinish()
|
||||
else
|
||||
self.mark.continue = self.mark.active
|
||||
end
|
||||
end
|
||||
|
||||
@@ -311,8 +305,6 @@ function Entry:markNextWord()
|
||||
self:markBegin()
|
||||
if self:moveWordRight() then
|
||||
self:markFinish()
|
||||
else
|
||||
self.mark.continue = self.mark.active
|
||||
end
|
||||
end
|
||||
|
||||
@@ -320,8 +312,6 @@ function Entry:markPrevWord()
|
||||
self:markBegin()
|
||||
if self:moveWordLeft() then
|
||||
self:markFinish()
|
||||
else
|
||||
self.mark.continue = self.mark.active
|
||||
end
|
||||
end
|
||||
|
||||
@@ -340,8 +330,6 @@ function Entry:markHome()
|
||||
self:markBegin()
|
||||
if self:moveHome() then
|
||||
self:markFinish()
|
||||
else
|
||||
self.mark.continue = self.mark.active
|
||||
end
|
||||
end
|
||||
|
||||
@@ -349,8 +337,6 @@ function Entry:markEnd()
|
||||
self:markBegin()
|
||||
if self:moveEnd() then
|
||||
self:markFinish()
|
||||
else
|
||||
self.mark.continue = self.mark.active
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
@@ -58,14 +58,6 @@ function Routine:resume(event, ...)
|
||||
else
|
||||
s, m = coroutine.resume(self.co, event, ...)
|
||||
end
|
||||
|
||||
if not s and event ~= 'terminate' then
|
||||
if m and type(debug) == 'table' and debug.traceback then
|
||||
local t = (debug.traceback(self.co, 1)) or ''
|
||||
m = m .. '\n' .. t:match('%d\n(.+)')
|
||||
end
|
||||
end
|
||||
|
||||
if self:isDead() then
|
||||
self.co = nil
|
||||
self.filter = nil
|
||||
|
||||
@@ -4,21 +4,17 @@ local linkfs = { }
|
||||
|
||||
-- TODO: implement broken links
|
||||
|
||||
local methods = { 'exists', 'getFreeSpace', 'getSize', 'attributes',
|
||||
'isDir', 'isReadOnly', 'list', 'makeDir', 'open', 'getDrive' }
|
||||
local methods = { 'exists', 'getFreeSpace', 'getSize',
|
||||
'isDir', 'isReadOnly', 'list', 'listEx', 'makeDir', 'open', 'getDrive' }
|
||||
|
||||
for _,m in pairs(methods) do
|
||||
linkfs[m] = function(node, dir, ...)
|
||||
dir = linkfs.resolve(node, dir)
|
||||
dir = dir:gsub(node.mountPoint, node.source, 1)
|
||||
return fs[m](dir, ...)
|
||||
end
|
||||
end
|
||||
|
||||
function linkfs.resolve(node, dir)
|
||||
return dir:gsub(node.mountPoint, node.source, 1)
|
||||
end
|
||||
|
||||
function linkfs.mount(path, source)
|
||||
function linkfs.mount(_, source)
|
||||
if not source then
|
||||
error('Source is required')
|
||||
end
|
||||
@@ -26,9 +22,6 @@ function linkfs.mount(path, source)
|
||||
if not fs.exists(source) then
|
||||
error('Source is missing')
|
||||
end
|
||||
if path == source then
|
||||
return
|
||||
end
|
||||
if fs.isDir(source) then
|
||||
return {
|
||||
source = source,
|
||||
@@ -72,4 +65,4 @@ function linkfs.move(node, s, t)
|
||||
return fs.move(s, t)
|
||||
end
|
||||
|
||||
return linkfs
|
||||
return linkfs
|
||||
|
||||
@@ -6,6 +6,7 @@ local fs = _G.fs
|
||||
local netfs = { }
|
||||
|
||||
local function remoteCommand(node, msg)
|
||||
|
||||
for _ = 1, 2 do
|
||||
if not node.socket then
|
||||
node.socket = Socket.connect(node.id, 139)
|
||||
@@ -32,17 +33,17 @@ local function remoteCommand(node, msg)
|
||||
error('netfs: Connection failed', 2)
|
||||
end
|
||||
|
||||
local methods = { 'delete', 'exists', 'getFreeSpace', 'makeDir', 'list', 'listEx', 'attributes' }
|
||||
local methods = { 'delete', 'exists', 'getFreeSpace', 'makeDir', 'list', 'listEx' }
|
||||
|
||||
local function resolve(node, dir)
|
||||
local function resolveDir(dir, node)
|
||||
-- TODO: Wrong ! (does not support names with dashes)
|
||||
dir = dir:gsub(node.mountPoint, '', 1)
|
||||
return fs.combine(node.source, dir)
|
||||
return fs.combine(node.directory, dir)
|
||||
end
|
||||
|
||||
for _,m in pairs(methods) do
|
||||
netfs[m] = function(node, dir)
|
||||
dir = resolve(node, dir)
|
||||
dir = resolveDir(dir, node)
|
||||
|
||||
return remoteCommand(node, {
|
||||
fn = m,
|
||||
@@ -51,14 +52,14 @@ for _,m in pairs(methods) do
|
||||
end
|
||||
end
|
||||
|
||||
function netfs.mount(_, id, source)
|
||||
function netfs.mount(_, id, directory)
|
||||
if not id or not tonumber(id) then
|
||||
error('ramfs syntax: computerId [directory]')
|
||||
end
|
||||
return {
|
||||
id = tonumber(id),
|
||||
nodes = { },
|
||||
source = source or '',
|
||||
directory = directory or '',
|
||||
}
|
||||
end
|
||||
|
||||
@@ -67,7 +68,7 @@ function netfs.getDrive()
|
||||
end
|
||||
|
||||
function netfs.complete(node, partial, dir, includeFiles, includeSlash)
|
||||
dir = resolve(node, dir)
|
||||
dir = resolveDir(dir, node)
|
||||
|
||||
return remoteCommand(node, {
|
||||
fn = 'complete',
|
||||
@@ -76,8 +77,8 @@ function netfs.complete(node, partial, dir, includeFiles, includeSlash)
|
||||
end
|
||||
|
||||
function netfs.copy(node, s, t)
|
||||
s = resolve(node, s)
|
||||
t = resolve(node, t)
|
||||
s = resolveDir(s, node)
|
||||
t = resolveDir(t, node)
|
||||
|
||||
return remoteCommand(node, {
|
||||
fn = 'copy',
|
||||
@@ -86,37 +87,37 @@ function netfs.copy(node, s, t)
|
||||
end
|
||||
|
||||
function netfs.isDir(node, dir)
|
||||
if dir == node.mountPoint and node.source == '' then
|
||||
if dir == node.mountPoint and node.directory == '' then
|
||||
return true
|
||||
end
|
||||
return remoteCommand(node, {
|
||||
fn = 'isDir',
|
||||
args = { resolve(node, dir) },
|
||||
args = { resolveDir(dir, node) },
|
||||
})
|
||||
end
|
||||
|
||||
function netfs.isReadOnly(node, dir)
|
||||
if dir == node.mountPoint and node.source == '' then
|
||||
if dir == node.mountPoint and node.directory == '' then
|
||||
return false
|
||||
end
|
||||
return remoteCommand(node, {
|
||||
fn = 'isReadOnly',
|
||||
args = { resolve(node, dir) },
|
||||
args = { resolveDir(dir, node) },
|
||||
})
|
||||
end
|
||||
|
||||
function netfs.getSize(node, dir)
|
||||
if dir == node.mountPoint and node.source == '' then
|
||||
if dir == node.mountPoint and node.directory == '' then
|
||||
return 0
|
||||
end
|
||||
return remoteCommand(node, {
|
||||
fn = 'getSize',
|
||||
args = { resolve(node, dir) },
|
||||
args = { resolveDir(dir, node) },
|
||||
})
|
||||
end
|
||||
|
||||
function netfs.find(node, spec)
|
||||
spec = resolve(node, spec)
|
||||
spec = resolveDir(spec, node)
|
||||
local list = remoteCommand(node, {
|
||||
fn = 'find',
|
||||
args = { spec },
|
||||
@@ -130,8 +131,8 @@ function netfs.find(node, spec)
|
||||
end
|
||||
|
||||
function netfs.move(node, s, t)
|
||||
s = resolve(node, s)
|
||||
t = resolve(node, t)
|
||||
s = resolveDir(s, node)
|
||||
t = resolveDir(t, node)
|
||||
|
||||
return remoteCommand(node, {
|
||||
fn = 'move',
|
||||
@@ -140,7 +141,7 @@ function netfs.move(node, s, t)
|
||||
end
|
||||
|
||||
function netfs.open(node, fn, fl)
|
||||
fn = resolve(node, fn)
|
||||
fn = resolveDir(fn, node)
|
||||
|
||||
local vfh = remoteCommand(node, {
|
||||
fn = 'open',
|
||||
|
||||
@@ -9,28 +9,15 @@ function ramfs.mount(_, nodeType)
|
||||
return {
|
||||
nodes = { },
|
||||
size = 0,
|
||||
created = os.epoch('utc'),
|
||||
modification = os.epoch('utc'),
|
||||
}
|
||||
elseif nodeType == 'file' then
|
||||
return {
|
||||
size = 0,
|
||||
created = os.epoch('utc'),
|
||||
modification = os.epoch('utc'),
|
||||
}
|
||||
end
|
||||
error('ramfs syntax: [directory, file]')
|
||||
end
|
||||
|
||||
function ramfs.attributes(node)
|
||||
return {
|
||||
created = node.created,
|
||||
isDir = not not node.nodes,
|
||||
modification = node.modification,
|
||||
size = node.size,
|
||||
}
|
||||
end
|
||||
|
||||
function ramfs.delete(node, dir)
|
||||
if node.mountPoint == dir then
|
||||
fs.unmount(node.mountPoint)
|
||||
@@ -53,10 +40,8 @@ function ramfs.makeDir(_, dir)
|
||||
fs.mount(dir, 'ramfs', 'directory')
|
||||
end
|
||||
|
||||
function ramfs.isDir(node, dir)
|
||||
if node.mountPoint == dir then
|
||||
return not not node.nodes
|
||||
end
|
||||
function ramfs.isDir(node)
|
||||
return not not node.nodes
|
||||
end
|
||||
|
||||
function ramfs.getDrive()
|
||||
@@ -79,70 +64,32 @@ function ramfs.list(node, dir)
|
||||
end
|
||||
|
||||
function ramfs.open(node, fn, fl)
|
||||
local modes = Util.transpose { 'r', 'w', 'rb', 'wb', 'a' }
|
||||
if not modes[fl] then
|
||||
|
||||
if fl ~= 'r' and fl ~= 'w' and fl ~= 'rb' and fl ~= 'wb' then
|
||||
error('Unsupported mode')
|
||||
end
|
||||
|
||||
if fl == 'a' then
|
||||
if node.mountPoint ~= fn then
|
||||
fl = 'w'
|
||||
else
|
||||
local c = type(node.contents) == 'table'
|
||||
and string.char(table.unpack(node.contents))
|
||||
or node.contents
|
||||
or ''
|
||||
|
||||
return {
|
||||
write = function(str)
|
||||
c = c .. str
|
||||
end,
|
||||
writeLine = function(str)
|
||||
c = c .. str .. '\n'
|
||||
end,
|
||||
flush = function()
|
||||
node.contents = c
|
||||
node.size = #c
|
||||
end,
|
||||
close = function()
|
||||
node.contents = c
|
||||
node.size = #c
|
||||
c = nil
|
||||
end,
|
||||
}
|
||||
end
|
||||
end
|
||||
|
||||
if fl == 'r' then
|
||||
if node.mountPoint ~= fn then
|
||||
return
|
||||
end
|
||||
|
||||
local c = type(node.contents) == 'table'
|
||||
and string.char(table.unpack(node.contents))
|
||||
or node.contents
|
||||
|
||||
local ctr = 0
|
||||
local lines
|
||||
return {
|
||||
read = function(n)
|
||||
n = n or 1
|
||||
if ctr >= node.size then
|
||||
return
|
||||
end
|
||||
local t = c:sub(ctr + 1, ctr + n)
|
||||
ctr = ctr + n
|
||||
return t
|
||||
read = function()
|
||||
ctr = ctr + 1
|
||||
return node.contents:sub(ctr, ctr)
|
||||
end,
|
||||
readLine = function()
|
||||
if not lines then
|
||||
lines = Util.split(c)
|
||||
lines = Util.split(node.contents)
|
||||
end
|
||||
ctr = ctr + 1
|
||||
return lines[ctr]
|
||||
end,
|
||||
readAll = function()
|
||||
return c
|
||||
return node.contents
|
||||
end,
|
||||
close = function()
|
||||
lines = nil
|
||||
@@ -174,30 +121,11 @@ function ramfs.open(node, fn, fl)
|
||||
return
|
||||
end
|
||||
|
||||
local c = node.contents
|
||||
if type(node.contents) == 'string' then
|
||||
c = { }
|
||||
for i = 1, node.size do
|
||||
c[i] = node.contents:sub(i, i):byte()
|
||||
end
|
||||
end
|
||||
|
||||
local ctr = 0
|
||||
return {
|
||||
readAll = function()
|
||||
return string.char(table.unpack(c))
|
||||
end,
|
||||
read = function(n)
|
||||
if n and n > 1 and ctr < node.size then
|
||||
-- some programs open in rb, when it should have
|
||||
-- been opened in r - attempt to support multiple read
|
||||
-- if nils are present in data, this will fail
|
||||
local t = string.char(table.unpack(c, ctr + 1, ctr + n))
|
||||
ctr = ctr + n
|
||||
return t
|
||||
end
|
||||
read = function()
|
||||
ctr = ctr + 1
|
||||
return c[ctr]
|
||||
return node.contents[ctr]
|
||||
end,
|
||||
close = function()
|
||||
end,
|
||||
@@ -209,13 +137,7 @@ function ramfs.open(node, fn, fl)
|
||||
local c = { }
|
||||
return {
|
||||
write = function(b)
|
||||
if type(b) == 'number' then
|
||||
table.insert(c, b)
|
||||
else
|
||||
for i = 1, #b do
|
||||
table.insert(c, b:sub(i, i):byte())
|
||||
end
|
||||
end
|
||||
table.insert(c, b)
|
||||
end,
|
||||
flush = function()
|
||||
node.contents = c
|
||||
|
||||
@@ -5,46 +5,29 @@ local fs = _G.fs
|
||||
|
||||
local urlfs = { }
|
||||
|
||||
function urlfs.mount(path, url, force)
|
||||
function urlfs.mount(_, url)
|
||||
if not url then
|
||||
error('URL is required')
|
||||
end
|
||||
|
||||
-- only mount if the file does not exist already
|
||||
if not fs.exists(path) or force then
|
||||
return {
|
||||
url = url,
|
||||
created = os.epoch('utc'),
|
||||
modification = os.epoch('utc'),
|
||||
}
|
||||
end
|
||||
end
|
||||
|
||||
function urlfs.attributes(node, path)
|
||||
return path == node.mountPoint and {
|
||||
created = node.created,
|
||||
isDir = false,
|
||||
modification = node.modification,
|
||||
size = node.size or 0,
|
||||
return {
|
||||
url = url,
|
||||
}
|
||||
end
|
||||
|
||||
function urlfs.delete(node, path)
|
||||
if path == node.mountPoint then
|
||||
fs.unmount(path)
|
||||
end
|
||||
function urlfs.delete(_, dir)
|
||||
fs.unmount(dir)
|
||||
end
|
||||
|
||||
function urlfs.exists(node, path)
|
||||
return path == node.mountPoint
|
||||
function urlfs.exists()
|
||||
return true
|
||||
end
|
||||
|
||||
function urlfs.getSize(node, path)
|
||||
return path == node.mountPoint and node.size or 0
|
||||
function urlfs.getSize(node)
|
||||
return node.size or 0
|
||||
end
|
||||
|
||||
function urlfs.isReadOnly()
|
||||
return false
|
||||
return true
|
||||
end
|
||||
|
||||
function urlfs.isDir()
|
||||
@@ -67,6 +50,14 @@ function urlfs.open(node, fn, fl)
|
||||
|
||||
local c = node.cache
|
||||
if not c then
|
||||
--[[
|
||||
if node.url:match("^(rttps?:)") then
|
||||
local s, response = rttp.get(node.url)
|
||||
c = s and response.statusCode == 200 and response.data
|
||||
else
|
||||
c = Util.httpGet(node.url)
|
||||
end
|
||||
]]--
|
||||
c = Util.httpGet(node.url)
|
||||
if c then
|
||||
node.cache = c
|
||||
@@ -103,9 +94,6 @@ function urlfs.open(node, fn, fl)
|
||||
}
|
||||
end
|
||||
return {
|
||||
readAll = function()
|
||||
return c
|
||||
end,
|
||||
read = function()
|
||||
ctr = ctr + 1
|
||||
return c:sub(ctr, ctr):byte()
|
||||
|
||||
@@ -1,56 +1,21 @@
|
||||
local find = string.find
|
||||
local floor = math.floor
|
||||
local min = math.min
|
||||
local max = math.max
|
||||
local sub = string.sub
|
||||
-- Based on Squid's fuzzy search
|
||||
-- https://github.com/SquidDev-CC/artist/blob/vnext/artist/lib/match.lua
|
||||
--
|
||||
-- not very fuzzy anymore
|
||||
|
||||
-- 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
|
||||
local SCORE_WEIGHT = 1000
|
||||
local LEADING_LETTER_PENALTY = -30
|
||||
local LEADING_LETTER_PENALTY_MAX = -90
|
||||
|
||||
local _find = string.find
|
||||
local _max = math.max
|
||||
|
||||
return function(str, pattern)
|
||||
local start = _find(str, pattern, 1, true)
|
||||
if start then
|
||||
-- All letters before the current one are considered leading, so add them to our penalty
|
||||
return SCORE_WEIGHT
|
||||
+ _max(LEADING_LETTER_PENALTY * (start - 1), LEADING_LETTER_PENALTY_MAX)
|
||||
- (#str - #pattern)
|
||||
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
|
||||
|
||||
@@ -20,12 +20,13 @@ function GPS.locate(timeout, debug)
|
||||
|
||||
local modem = device.wireless_modem
|
||||
local closeChannel = false
|
||||
if not modem.isOpen(GPS.CHANNEL_GPS) then
|
||||
modem.open(GPS.CHANNEL_GPS)
|
||||
local selfID = os.getComputerID()
|
||||
if not modem.isOpen(selfID) then
|
||||
modem.open(selfID)
|
||||
closeChannel = true
|
||||
end
|
||||
|
||||
modem.transmit(GPS.CHANNEL_GPS, GPS.CHANNEL_GPS, "PING")
|
||||
modem.transmit(GPS.CHANNEL_GPS, selfID, "PING")
|
||||
|
||||
local fixes = {}
|
||||
local pos = nil
|
||||
@@ -33,7 +34,7 @@ function GPS.locate(timeout, debug)
|
||||
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 side == modem.side and chan == selfID 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)),
|
||||
@@ -59,7 +60,7 @@ function GPS.locate(timeout, debug)
|
||||
end
|
||||
|
||||
if closeChannel then
|
||||
modem.close(GPS.CHANNEL_GPS)
|
||||
modem.close(selfID)
|
||||
end
|
||||
if debug then
|
||||
print("Position is "..pos.x..","..pos.y..","..pos.z)
|
||||
|
||||
@@ -1,51 +1,40 @@
|
||||
-- https://www.lua.org/manual/5.1/manual.html#pdf-require
|
||||
-- https://github.com/LuaDist/lua/blob/d2e7e7d4d43ff9068b279a617c5b2ca2c2771676/src/loadlib.c
|
||||
local function split(str, pattern)
|
||||
local t = { }
|
||||
local function helper(line) table.insert(t, line) return "" end
|
||||
helper((str:gsub(pattern, helper)))
|
||||
return t
|
||||
end
|
||||
|
||||
local defaultPath = { }
|
||||
|
||||
do
|
||||
local function split(str)
|
||||
local t = { }
|
||||
local function helper(line) table.insert(t, line) return "" end
|
||||
helper((str:gsub('(.-);', helper)))
|
||||
return t
|
||||
end
|
||||
|
||||
local function insert(p)
|
||||
for _,v in pairs(defaultPath) do
|
||||
if v == p then
|
||||
return
|
||||
end
|
||||
end
|
||||
table.insert(defaultPath, p)
|
||||
|
||||
end
|
||||
|
||||
local paths = '?.lua;?/init.lua;'
|
||||
paths = paths .. '/usr/modules/?.lua;/usr/modules/?/init.lua;'
|
||||
paths = paths .. '/rom/modules/main/?;/rom/modules/main/?.lua;/rom/modules/main/?/init.lua;'
|
||||
paths = paths .. '/sys/modules/?.lua;/sys/modules/?/init.lua'
|
||||
|
||||
for _,v in pairs(split(paths)) do
|
||||
insert(v)
|
||||
end
|
||||
|
||||
local luaPaths = package and package.path and split(package.path) or { }
|
||||
for _,v in pairs(luaPaths) do
|
||||
if v ~= '?' then
|
||||
insert(v)
|
||||
end
|
||||
local hasMain
|
||||
local luaPaths = package and package.path and split(package.path, '(.-);') or { }
|
||||
for i = 1, #luaPaths do
|
||||
if luaPaths[i] == '?' or luaPaths[i] == '?.lua' or luaPaths[i] == '?/init.lua' then
|
||||
luaPaths[i] = nil
|
||||
elseif string.find(luaPaths[i], '/rom/modules/main') then
|
||||
hasMain = true
|
||||
end
|
||||
end
|
||||
|
||||
local DEFAULT_PATH = table.concat(defaultPath, ';')
|
||||
table.insert(luaPaths, 1, '?.lua')
|
||||
table.insert(luaPaths, 2, '?/init.lua')
|
||||
table.insert(luaPaths, 3, '/usr/modules/?.lua')
|
||||
table.insert(luaPaths, 4, '/usr/modules/?/init.lua')
|
||||
if not hasMain then
|
||||
table.insert(luaPaths, 5, '/rom/modules/main/?')
|
||||
table.insert(luaPaths, 6, '/rom/modules/main/?.lua')
|
||||
table.insert(luaPaths, 7, '/rom/modules/main/?/init.lua')
|
||||
end
|
||||
table.insert(luaPaths, '/sys/modules/?.lua')
|
||||
table.insert(luaPaths, '/sys/modules/?/init.lua')
|
||||
|
||||
local DEFAULT_PATH = table.concat(luaPaths, ';')
|
||||
|
||||
local fs = _G.fs
|
||||
local os = _G.os
|
||||
local string = _G.string
|
||||
|
||||
-- Add require and package to the environment
|
||||
return function(env, programDir)
|
||||
return function(env)
|
||||
local function preloadSearcher(modname)
|
||||
if env.package.preload[modname] then
|
||||
return function()
|
||||
@@ -54,75 +43,77 @@ return function(env, programDir)
|
||||
end
|
||||
end
|
||||
|
||||
local function loadedSearcher(modname)
|
||||
if env.package.loaded[modname] then
|
||||
return function()
|
||||
return env.package.loaded[modname]
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
local sentinel = { }
|
||||
|
||||
local function pathSearcher(modname)
|
||||
if env.package.loaded[modname] == sentinel then
|
||||
error("loop or previous error loading module '" .. modname .. "'", 0)
|
||||
end
|
||||
env.package.loaded[modname] = sentinel
|
||||
|
||||
local fname = modname:gsub('%.', '/')
|
||||
|
||||
for pattern in string.gmatch(env.package.path, "[^;]+") do
|
||||
local sPath = string.gsub(pattern, "%?", fname)
|
||||
-- TODO: if there's no shell, we should not be checking relative paths below
|
||||
-- as they will resolve to root directory
|
||||
if env.shell and
|
||||
type(env.shell.getRunningProgram) == 'function' and
|
||||
sPath:sub(1, 1) ~= "/" then
|
||||
|
||||
if programDir and sPath:sub(1, 1) ~= "/" then
|
||||
sPath = fs.combine(programDir, sPath)
|
||||
sPath = fs.combine(fs.getDir(env.shell.getRunningProgram() or ''), sPath)
|
||||
end
|
||||
if fs.exists(sPath) and not fs.isDir(sPath) then
|
||||
return loadfile(fs.combine(sPath, ''), env)
|
||||
return loadfile(sPath, env)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
-- place package and require function into env
|
||||
env.package = {
|
||||
path = env.LUA_PATH or _G.LUA_PATH or DEFAULT_PATH,
|
||||
cpath = '',
|
||||
config = '/\n:\n?\n!\n-',
|
||||
path = env.LUA_PATH or _G.LUA_PATH or DEFAULT_PATH,
|
||||
config = '/\n:\n?\n!\n-',
|
||||
preload = { },
|
||||
loaded = {
|
||||
bit32 = bit32,
|
||||
loaded = {
|
||||
coroutine = coroutine,
|
||||
_G = env._G,
|
||||
io = io,
|
||||
math = math,
|
||||
os = os,
|
||||
string = string,
|
||||
table = table,
|
||||
debug = debug,
|
||||
utf8 = utf8,
|
||||
},
|
||||
loaders = {
|
||||
preloadSearcher,
|
||||
loadedSearcher,
|
||||
pathSearcher,
|
||||
}
|
||||
}
|
||||
env.package.loaded.package = env.package
|
||||
|
||||
local sentinel = { }
|
||||
|
||||
function env.require(modname)
|
||||
if env.package.loaded[modname] then
|
||||
if env.package.loaded[modname] == sentinel then
|
||||
error("loop or previous error loading module '" .. modname .. "'", 0)
|
||||
end
|
||||
return env.package.loaded[modname]
|
||||
end
|
||||
|
||||
local t = { }
|
||||
for _,searcher in ipairs(env.package.loaders) do
|
||||
local fn, msg = searcher(modname)
|
||||
if type(fn) == 'function' then
|
||||
env.package.loaded[modname] = sentinel
|
||||
|
||||
local module = fn(modname, env) or true
|
||||
|
||||
if fn then
|
||||
local module, msg2 = fn(modname, env)
|
||||
if not module then
|
||||
error(msg2 or (modname .. ' module returned nil'), 2)
|
||||
end
|
||||
env.package.loaded[modname] = module
|
||||
return module
|
||||
end
|
||||
if msg then
|
||||
table.insert(t, msg)
|
||||
error(msg, 2)
|
||||
end
|
||||
end
|
||||
|
||||
if #t > 0 then
|
||||
error(table.concat(t, '\n'), 2)
|
||||
end
|
||||
error('Unable to find module ' .. modname, 2)
|
||||
end
|
||||
|
||||
return env.require -- backwards compatible
|
||||
end
|
||||
|
||||
@@ -189,22 +189,11 @@ function input:translate(event, code, p1, p2)
|
||||
end
|
||||
end
|
||||
|
||||
if not ({ ...})[1] then
|
||||
local colors = _G.colors
|
||||
local term = _G.term
|
||||
|
||||
function input:test()
|
||||
while true do
|
||||
local e = { os.pullEvent() }
|
||||
local ch = input:translate(table.unpack(e))
|
||||
local ch = self:translate(os.pullEvent())
|
||||
if ch then
|
||||
term.setTextColor(colors.white)
|
||||
print(table.unpack(e))
|
||||
term.setTextColor(colors.lime)
|
||||
local t = { }
|
||||
for k,v in pairs(ch) do
|
||||
table.insert(t, k .. ':' .. v)
|
||||
end
|
||||
print('--> ' .. table.concat(t, ' ') .. '\n')
|
||||
Util.print(ch)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@@ -39,8 +39,7 @@ if register_global_module_table then
|
||||
_G[global_module_name] = json
|
||||
end
|
||||
|
||||
-- this was incompatible because we use fs later
|
||||
--local _ENV = nil -- blocking globals in Lua 5.2
|
||||
local _ENV = nil -- blocking globals in Lua 5.2
|
||||
|
||||
pcall (function()
|
||||
-- Enable access to blocked metatables.
|
||||
|
||||
@@ -8,7 +8,6 @@ Map.merge = Util.merge
|
||||
Map.shallowCopy = Util.shallowCopy
|
||||
Map.find = Util.find
|
||||
Map.filter = Util.filter
|
||||
Map.transpose = Util.transpose
|
||||
|
||||
function Map.removeMatches(t, values)
|
||||
local function matchAll(entry)
|
||||
|
||||
@@ -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://raw.githubusercontent.com/kepler155c/opus-apps/master-1.8/packages.list',
|
||||
[ 'master-1.8' ] = 'https://pastebin.com/raw/pexZpAxt',
|
||||
}
|
||||
|
||||
if packages[_G.OPUS_BRANCH] then
|
||||
|
||||
@@ -6,11 +6,12 @@ local Util = require('opus.util')
|
||||
|
||||
local device = _G.device
|
||||
local os = _G.os
|
||||
local network = _G.network
|
||||
|
||||
local socketClass = { }
|
||||
|
||||
function socketClass:read(timeout)
|
||||
local data, distance = _G.network.getTransport().read(self)
|
||||
local data, distance = network.getTransport().read(self)
|
||||
if data then
|
||||
return data, distance
|
||||
end
|
||||
@@ -25,7 +26,7 @@ function socketClass:read(timeout)
|
||||
local e, id = os.pullEvent()
|
||||
|
||||
if e == 'transport_' .. self.uid then
|
||||
data, distance = _G.network.getTransport().read(self)
|
||||
data, distance = network.getTransport().read(self)
|
||||
if data then
|
||||
os.cancelTimer(timerId)
|
||||
return data, distance
|
||||
@@ -46,7 +47,7 @@ end
|
||||
|
||||
function socketClass:write(data)
|
||||
if self.connected then
|
||||
_G.network.getTransport().write(self, {
|
||||
network.getTransport().write(self, {
|
||||
type = 'DATA',
|
||||
seq = self.wseq,
|
||||
data = data,
|
||||
@@ -57,7 +58,7 @@ end
|
||||
|
||||
function socketClass:ping()
|
||||
if self.connected then
|
||||
_G.network.getTransport().ping(self)
|
||||
network.getTransport().ping(self)
|
||||
return true
|
||||
end
|
||||
end
|
||||
@@ -71,7 +72,7 @@ function socketClass:close()
|
||||
self.connected = false
|
||||
end
|
||||
device.wireless_modem.close(self.sport)
|
||||
_G.network.getTransport().close(self)
|
||||
network.getTransport().close(self)
|
||||
end
|
||||
|
||||
local Socket = { }
|
||||
@@ -126,9 +127,9 @@ function Socket.connect(host, port, options)
|
||||
local socket = newSocket(host == os.getComputerID())
|
||||
socket.dhost = tonumber(host)
|
||||
if options and options.keypair then
|
||||
socket.privKey, socket.pubKey = table.unpack(options.keypair)
|
||||
socket.privKey, socket.pubKey = unpack(options.keypair)
|
||||
else
|
||||
socket.privKey, socket.pubKey = _G.network.getKeyPair()
|
||||
socket.privKey, socket.pubKey = network.getKeyPair()
|
||||
end
|
||||
local identifier = options and options.identifier or Security.getIdentifier()
|
||||
|
||||
@@ -158,7 +159,7 @@ function Socket.connect(host, port, options)
|
||||
socket.remotePubKey = Util.hexToByteArray(msg.pk)
|
||||
socket.options = msg.options or { }
|
||||
setupCrypto(socket, true)
|
||||
_G.network.getTransport().open(socket)
|
||||
network.getTransport().open(socket)
|
||||
return socket
|
||||
|
||||
elseif msg.type == 'NOPASS' then
|
||||
@@ -191,7 +192,7 @@ local function trusted(socket, msg, options)
|
||||
if data and data.ts and tonumber(data.ts) then
|
||||
if math.abs(os.epoch('utc') - data.ts) < 4096 then
|
||||
socket.remotePubKey = Util.hexToByteArray(data.pk)
|
||||
socket.privKey, socket.pubKey = _G.network.getKeyPair()
|
||||
socket.privKey, socket.pubKey = network.getKeyPair()
|
||||
setupCrypto(socket)
|
||||
return true
|
||||
end
|
||||
@@ -240,7 +241,7 @@ function Socket.server(port, options)
|
||||
options = socket.options.ENCRYPT and { ENCRYPT = true },
|
||||
})
|
||||
|
||||
_G.network.getTransport().open(socket)
|
||||
network.getTransport().open(socket)
|
||||
return socket
|
||||
|
||||
else
|
||||
|
||||
@@ -33,10 +33,10 @@ function Terminal.window(parent, sx, sy, w, h, isVisible)
|
||||
end
|
||||
|
||||
local win = { }
|
||||
local maxScroll
|
||||
local maxScroll = 100
|
||||
local cx, cy = 1, 1
|
||||
local blink = false
|
||||
local _bg, _fg = colors.black, colors.white
|
||||
local _bg, _fg = parent.getBackgroundColor(), parent.getTextColor()
|
||||
|
||||
win.canvas = Canvas({
|
||||
x = sx,
|
||||
@@ -164,7 +164,7 @@ function Terminal.window(parent, sx, sy, w, h, isVisible)
|
||||
win.canvas.lines[lines + i] = { }
|
||||
win.canvas:clearLine(lines + i)
|
||||
end
|
||||
while #win.canvas.lines > (maxScroll or win.canvas.height) do
|
||||
while #win.canvas.lines > maxScroll do
|
||||
table.remove(win.canvas.lines, 1)
|
||||
end
|
||||
scrollTo(#win.canvas.lines)
|
||||
@@ -213,39 +213,8 @@ function Terminal.window(parent, sx, sy, w, h, isVisible)
|
||||
end
|
||||
|
||||
function win.reposition(x, y, width, height)
|
||||
if not maxScroll then
|
||||
win.canvas:move(x, y)
|
||||
win.canvas:resize(width or win.canvas.width, height or win.canvas.height)
|
||||
return
|
||||
end
|
||||
|
||||
-- special processing for scrolling terminal like windows
|
||||
local delta = height - win.canvas.height
|
||||
|
||||
if delta > 0 then -- grow
|
||||
for _ = 1, delta do
|
||||
win.canvas.lines[#win.canvas.lines + 1] = { }
|
||||
win.canvas:clearLine(#win.canvas.lines)
|
||||
end
|
||||
|
||||
elseif delta < 0 then -- shrink
|
||||
for _ = delta + 1, 0 do
|
||||
if cy < win.canvas.height then
|
||||
win.canvas.lines[#win.canvas.lines] = nil
|
||||
else
|
||||
cy = cy - 1
|
||||
win.canvas.offy = win.canvas.offy + 1
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
win.canvas:resizeBuffer(width, #win.canvas.lines)
|
||||
|
||||
win.canvas.height = height
|
||||
win.canvas.width = width
|
||||
win.canvas:move(x, y)
|
||||
|
||||
update()
|
||||
win.canvas.x, win.canvas.y = x, y
|
||||
win.canvas:resize(width or win.canvas.width, height or win.canvas.height)
|
||||
end
|
||||
|
||||
--[[ Additional methods ]]--
|
||||
@@ -277,79 +246,6 @@ function Terminal.window(parent, sx, sy, w, h, isVisible)
|
||||
return parent
|
||||
end
|
||||
|
||||
function win.writeX(sText)
|
||||
-- expect(1, sText, "string", "number")
|
||||
local nLinesPrinted = 0
|
||||
local function newLine()
|
||||
if cy + 1 <= win.canvas.height then
|
||||
cx, cy = 1, cy + 1
|
||||
else
|
||||
cx, cy = 1, win.canvas.height
|
||||
win.scroll(1)
|
||||
end
|
||||
nLinesPrinted = nLinesPrinted + 1
|
||||
end
|
||||
|
||||
-- Print the line with proper word wrapping
|
||||
sText = tostring(sText)
|
||||
while #sText > 0 do
|
||||
local whitespace = string.match(sText, "^[ \t]+")
|
||||
if whitespace then
|
||||
-- Print whitespace
|
||||
win.write(whitespace)
|
||||
sText = string.sub(sText, #whitespace + 1)
|
||||
end
|
||||
|
||||
local newline = string.match(sText, "^\n")
|
||||
if newline then
|
||||
-- Print newlines
|
||||
newLine()
|
||||
sText = string.sub(sText, 2)
|
||||
end
|
||||
|
||||
local text = string.match(sText, "^[^ \t\n]+")
|
||||
if text then
|
||||
sText = string.sub(sText, #text + 1)
|
||||
if #text > win.canvas.width then
|
||||
-- Print a multiline word
|
||||
while #text > 0 do
|
||||
if cx > win.canvas.width then
|
||||
newLine()
|
||||
end
|
||||
win.write(text)
|
||||
text = string.sub(text, win.canvas.width - cx + 2)
|
||||
end
|
||||
else
|
||||
-- Print a word normally
|
||||
if cx + #text - 1 > win.canvas.width then
|
||||
newLine()
|
||||
end
|
||||
win.write(text)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
return nLinesPrinted
|
||||
end
|
||||
|
||||
function win.print(...)
|
||||
local vis = isVisible
|
||||
isVisible = false
|
||||
local nLinesPrinted = 0
|
||||
local nLimit = select("#", ...)
|
||||
for n = 1, nLimit do
|
||||
local s = tostring(select(n, ...))
|
||||
if n < nLimit then
|
||||
s = s .. "\t"
|
||||
end
|
||||
nLinesPrinted = nLinesPrinted + win.writeX(s)
|
||||
end
|
||||
nLinesPrinted = nLinesPrinted + win.writeX("\n")
|
||||
isVisible = vis
|
||||
update()
|
||||
return nLinesPrinted
|
||||
end
|
||||
|
||||
win.canvas:clear()
|
||||
|
||||
return win
|
||||
|
||||
@@ -12,10 +12,6 @@ local function traceback(x)
|
||||
return x
|
||||
end
|
||||
|
||||
if x and x:match(':%d+: 0$') then
|
||||
return x
|
||||
end
|
||||
|
||||
if debug_traceback then
|
||||
-- The parens are important, as they prevent a tail call occuring, meaning
|
||||
-- the stack level is preserved. This ensures the code behaves identically
|
||||
@@ -36,68 +32,78 @@ local function traceback(x)
|
||||
end
|
||||
end
|
||||
|
||||
local function trim_traceback(stack)
|
||||
local trace = { }
|
||||
local filters = {
|
||||
"%[C%]: in function 'xpcall'",
|
||||
"(...tail calls...)",
|
||||
"xpcall: $",
|
||||
"trace.lua:%d+:",
|
||||
"stack traceback:",
|
||||
}
|
||||
local function trim_traceback(target, marker)
|
||||
local ttarget, tmarker = {}, {}
|
||||
for line in target:gmatch("([^\n]*)\n?") do ttarget[#ttarget + 1] = line end
|
||||
for line in marker:gmatch("([^\n]*)\n?") do tmarker[#tmarker + 1] = line end
|
||||
|
||||
for line in stack:gmatch("([^\n]*)\n?") do table.insert(trace, line) end
|
||||
|
||||
local err = { }
|
||||
while true do
|
||||
local line = table.remove(trace, 1)
|
||||
if not line or line == 'stack traceback:' then
|
||||
break
|
||||
end
|
||||
table.insert(err, line)
|
||||
end
|
||||
err = table.concat(err, '\n')
|
||||
|
||||
local function matchesFilter(line)
|
||||
for _, filter in pairs(filters) do
|
||||
if line:match(filter) then
|
||||
return true
|
||||
end
|
||||
end
|
||||
-- Trim identical suffixes
|
||||
local t_len, m_len = #ttarget, #tmarker
|
||||
while t_len >= 3 and ttarget[t_len] == tmarker[m_len] do
|
||||
table.remove(ttarget, t_len)
|
||||
t_len, m_len = t_len - 1, m_len - 1
|
||||
end
|
||||
|
||||
local t = { }
|
||||
for _, line in pairs(trace) do
|
||||
if not matchesFilter(line) then
|
||||
line = line:gsub("in function", "in"):gsub('%w+/', '')
|
||||
table.insert(t, line)
|
||||
end
|
||||
-- Trim elements from this file and xpcall invocations
|
||||
while t_len >= 1 and ttarget[t_len]:find("^\tstack_trace%.lua:%d+:") or
|
||||
ttarget[t_len] == "\t[C]: in function 'xpcall'" or ttarget[t_len] == " xpcall: " do
|
||||
table.remove(ttarget, t_len)
|
||||
t_len = t_len - 1
|
||||
end
|
||||
|
||||
return err, t
|
||||
ttarget[#ttarget] = nil -- remove 2 calls added by the added xpcall
|
||||
ttarget[#ttarget] = nil
|
||||
|
||||
return ttarget
|
||||
end
|
||||
|
||||
--- Run a function with
|
||||
return function (fn, ...)
|
||||
-- So this is rather grim: we need to get the full traceback and current one and remove
|
||||
-- the common prefix
|
||||
local trace
|
||||
local args = { ... }
|
||||
|
||||
-- xpcall in Lua 5.1 does not accept parameters
|
||||
-- which is not ideal
|
||||
local res = table.pack(xpcall(function()
|
||||
return fn(table.unpack(args))
|
||||
end, traceback))
|
||||
|
||||
if not res[1] and res[2] ~= nil then
|
||||
local err, trace = trim_traceback(res[2])
|
||||
if not res[1] then
|
||||
trace = traceback("trace.lua:1:")
|
||||
end
|
||||
local ok, err = res[1], res[2]
|
||||
|
||||
if err:match(':%d+: 0$') then
|
||||
return true
|
||||
if not ok and err ~= nil then
|
||||
trace = trim_traceback(err, trace)
|
||||
|
||||
-- Find the position where the stack traceback actually starts
|
||||
local trace_starts
|
||||
for i = #trace, 1, -1 do
|
||||
if trace[i] == "stack traceback:" then trace_starts = i; break end
|
||||
end
|
||||
|
||||
if #trace > 0 then
|
||||
_G._syslog('\n' .. err .. '\n' .. 'stack traceback:')
|
||||
for _, v in ipairs(trace) do
|
||||
_G._syslog(v)
|
||||
for _, line in pairs(trace) do
|
||||
_G._syslog(line)
|
||||
end
|
||||
|
||||
-- If this traceback is more than 15 elements long, keep the first 9, last 5
|
||||
-- and put an ellipsis between the rest
|
||||
local max = 10
|
||||
if trace_starts and #trace - trace_starts > max then
|
||||
local keep_starts = trace_starts + 7
|
||||
for i = #trace - trace_starts - max, 0, -1 do
|
||||
table.remove(trace, keep_starts + i)
|
||||
end
|
||||
table.insert(trace, keep_starts, " ...")
|
||||
end
|
||||
|
||||
return res[1], err, trace
|
||||
for k, line in pairs(trace) do
|
||||
trace[k] = line:gsub("in function", " in")
|
||||
end
|
||||
|
||||
return false, table.remove(trace, 1), table.concat(trace, "\n")
|
||||
end
|
||||
|
||||
return table.unpack(res, 1, res.n)
|
||||
|
||||
@@ -37,14 +37,13 @@ local textutils = _G.textutils
|
||||
local UI = { }
|
||||
function UI:init()
|
||||
self.devices = { }
|
||||
self.theme = {
|
||||
colors = {
|
||||
primary = colors.green,
|
||||
secondary = colors.lightGray,
|
||||
tertiary = colors.gray,
|
||||
}
|
||||
self.theme = { }
|
||||
self.extChars = Util.getVersion() >= 1.76
|
||||
self.colors = {
|
||||
primary = colors.green,
|
||||
secondary = colors.lightGray,
|
||||
tertiary = colors.gray,
|
||||
}
|
||||
self.extChars = Util.supportsExtChars()
|
||||
|
||||
local function keyFunction(event, code, held)
|
||||
local ie = Input:translate(event, code, held)
|
||||
@@ -75,11 +74,11 @@ function UI:init()
|
||||
term_resize = resize,
|
||||
monitor_resize = resize,
|
||||
|
||||
mouse_scroll = function(_, direction, x, y, side)
|
||||
mouse_scroll = function(_, direction, x, y)
|
||||
local ie = Input:translate('mouse_scroll', direction, x, y)
|
||||
|
||||
local currentPage = self:getActivePage()
|
||||
if currentPage and currentPage.parent.device.side == side then
|
||||
if currentPage then
|
||||
local event = currentPage:pointToChild(x, y)
|
||||
event.type = ie.code
|
||||
event.ie = { code = ie.code, x = event.x, y = event.y }
|
||||
@@ -97,41 +96,45 @@ function UI:init()
|
||||
end
|
||||
end,
|
||||
|
||||
mouse_click = function(_, button, x, y, side)
|
||||
mouse_click = function(_, button, x, y)
|
||||
local ie = Input:translate('mouse_click', button, x, y)
|
||||
|
||||
local currentPage = self:getActivePage()
|
||||
if currentPage and currentPage.parent.device.side == side then
|
||||
local event = currentPage:pointToChild(x, y)
|
||||
if event.element.focus and not event.element.inactive then
|
||||
currentPage:setFocus(event.element)
|
||||
currentPage:sync()
|
||||
if currentPage then
|
||||
if not currentPage.parent.device.side then
|
||||
local event = currentPage:pointToChild(x, y)
|
||||
if event.element.focus and not event.element.inactive then
|
||||
currentPage:setFocus(event.element)
|
||||
currentPage:sync()
|
||||
end
|
||||
self:click(currentPage, ie)
|
||||
end
|
||||
self:click(currentPage, ie)
|
||||
end
|
||||
end,
|
||||
|
||||
mouse_up = function(_, button, x, y, side)
|
||||
mouse_up = function(_, button, x, y)
|
||||
local ie = Input:translate('mouse_up', button, x, y)
|
||||
local currentPage = self:getActivePage()
|
||||
|
||||
if ie.code == 'control-shift-mouse_click' then -- hack
|
||||
local event = currentPage:pointToChild(x, y)
|
||||
_ENV.multishell.openTab(_ENV, {
|
||||
_ENV.multishell.openTab({
|
||||
path = 'sys/apps/Lua.lua',
|
||||
args = { event.element, self, _ENV },
|
||||
args = { event.element },
|
||||
focused = true })
|
||||
|
||||
elseif ie and currentPage and currentPage.parent.device.side == side then
|
||||
self:click(currentPage, ie)
|
||||
elseif ie and currentPage then
|
||||
if not currentPage.parent.device.side then
|
||||
self:click(currentPage, ie)
|
||||
end
|
||||
end
|
||||
end,
|
||||
|
||||
mouse_drag = function(_, button, x, y, side)
|
||||
mouse_drag = function(_, button, x, y)
|
||||
local ie = Input:translate('mouse_drag', button, x, y)
|
||||
local currentPage = self:getActivePage()
|
||||
|
||||
if ie and currentPage and currentPage.parent.device.side == side then
|
||||
if ie and currentPage then
|
||||
self:click(currentPage, ie)
|
||||
end
|
||||
end,
|
||||
@@ -203,10 +206,6 @@ function UI:loadTheme(filename)
|
||||
end
|
||||
Util.deepMerge(self.theme, theme)
|
||||
end
|
||||
for k,v in pairs(self.theme.colors) do
|
||||
Canvas.colorPalette[k] = Canvas.colorPalette[v]
|
||||
Canvas.grayscalePalette[k] = Canvas.grayscalePalette[v]
|
||||
end
|
||||
end
|
||||
|
||||
function UI:generateTheme(filename)
|
||||
@@ -676,7 +675,7 @@ function UI.Window:drawChildren()
|
||||
end
|
||||
|
||||
UI.Window.docs.getDoc = [[getDoc(STRING method)
|
||||
Get the documentation for a method.]]
|
||||
Gets the documentation for a method.]]
|
||||
function UI.Window:getDoc(method)
|
||||
local m = getmetatable(self) -- get the class for this instance
|
||||
repeat
|
||||
@@ -744,8 +743,6 @@ function UI.Window:clear(bg, fg)
|
||||
Canvas.clear(self, bg or self:getProperty('backgroundColor'), fg or self:getProperty('textColor'))
|
||||
end
|
||||
|
||||
UI.Window.docs.clearLine = [[clearLine(NUMBER y, opt COLOR bg)
|
||||
Clears the specified line.]]
|
||||
function UI.Window:clearLine(y, bg)
|
||||
self:write(1, y, _rep(' ', self.width), bg)
|
||||
end
|
||||
@@ -763,10 +760,6 @@ function UI.Window:fillArea(x, y, width, height, fillChar, bg, fg)
|
||||
end
|
||||
end
|
||||
|
||||
UI.Window.docs.write = [[write(NUMBER x, NUMBER y, STRING text, opt COLOR bg, opt COLOR fg)
|
||||
Write text to the canvas.
|
||||
If colors are not specified, the colors from the base class will be used.
|
||||
If the base class does not have colors defined, colors will be inherited from the parent container.]]
|
||||
function UI.Window:write(x, y, text, bg, fg)
|
||||
Canvas.write(self, x, y, text, bg or self:getProperty('backgroundColor'), fg or self:getProperty('textColor'))
|
||||
end
|
||||
@@ -921,13 +914,6 @@ function UI.Window:addTransition(effect, args, canvas)
|
||||
self.parent:addTransition(effect, args, canvas or self)
|
||||
end
|
||||
|
||||
UI.Window.docs.emit = [[emit(TABLE event)
|
||||
Send an event to the element. The event handler for the element is called.
|
||||
If the event handler returns true, then no further processing is done.
|
||||
If the event handler does not return true, then the event is sent to the parent element
|
||||
and continues up the element tree.
|
||||
If an accelerator is defined, the accelerated event is processed in the same manner.
|
||||
Accelerators are useful for making events unique.]]
|
||||
function UI.Window:emit(event)
|
||||
local parent = self
|
||||
while parent do
|
||||
@@ -989,7 +975,6 @@ function UI.Device:postInit()
|
||||
self.device.setTextScale = function() end
|
||||
end
|
||||
|
||||
self._obg = term.getBackgroundColor()
|
||||
self.device.setTextScale(self.textScale)
|
||||
self.width, self.height = self.device.getSize()
|
||||
self.isColor = self.device.isColor()
|
||||
@@ -1026,7 +1011,8 @@ function UI.Device:setTextScale(textScale)
|
||||
end
|
||||
|
||||
function UI.Device:reset()
|
||||
self.device.setBackgroundColor(self._obg)
|
||||
self.device.setBackgroundColor(colors.black)
|
||||
self.device.setTextColor(colors.white)
|
||||
self.device.clear()
|
||||
self.device.setCursorPos(1, 1)
|
||||
end
|
||||
@@ -1126,6 +1112,13 @@ end
|
||||
|
||||
loadComponents()
|
||||
UI:loadTheme('usr/config/ui.theme')
|
||||
Util.merge(UI.Window.defaults, UI.theme.Window)
|
||||
Util.merge(UI.colors, UI.theme.colors)
|
||||
UI:setDefaultDevice(UI.Device())
|
||||
|
||||
for k,v in pairs(UI.colors) do
|
||||
Canvas.colorPalette[k] = Canvas.colorPalette[v]
|
||||
Canvas.grayscalePalette[k] = Canvas.grayscalePalette[v]
|
||||
end
|
||||
|
||||
return UI
|
||||
|
||||
@@ -361,8 +361,7 @@ function Canvas:__renderLayers(device, offset, doubleBuffer)
|
||||
y = region[2] - offset.y,
|
||||
ex = region[3] - offset.x,
|
||||
ey = region[4] - offset.y },
|
||||
{ x = region[1], y = region[2] },
|
||||
doubleBuffer)
|
||||
{ x = region[1], y = region[2] }, doubleBuffer)
|
||||
end
|
||||
self.regions = nil
|
||||
|
||||
|
||||
@@ -19,8 +19,6 @@ UI.Button.defaults = {
|
||||
[ ' ' ] = 'button_activate',
|
||||
enter = 'button_activate',
|
||||
mouse_click = 'button_activate',
|
||||
mouse_doubleclick = 'button_activate',
|
||||
mouse_tripleclick = 'button_activate',
|
||||
}
|
||||
}
|
||||
function UI.Button:layout()
|
||||
|
||||
@@ -12,11 +12,10 @@ UI.Checkbox.defaults = {
|
||||
textColor = 'white',
|
||||
backgroundColor = 'black',
|
||||
backgroundFocusColor = 'lightGray',
|
||||
event = 'checkbox_change',
|
||||
height = 1,
|
||||
width = 3,
|
||||
accelerators = {
|
||||
[ ' ' ] = 'checkbox_toggle',
|
||||
space = 'checkbox_toggle',
|
||||
mouse_click = 'checkbox_toggle',
|
||||
}
|
||||
}
|
||||
@@ -53,7 +52,7 @@ end
|
||||
function UI.Checkbox:eventHandler(event)
|
||||
if event.type == 'checkbox_toggle' then
|
||||
self.value = not self.value
|
||||
self:emit({ type = self.event, checked = self.value, element = self })
|
||||
self:emit({ type = 'checkbox_change', checked = self.value, element = self })
|
||||
self:draw()
|
||||
return true
|
||||
end
|
||||
|
||||
@@ -11,12 +11,11 @@ end
|
||||
|
||||
UI.CheckboxGrid = class(UI.Grid)
|
||||
UI.CheckboxGrid.defaults = {
|
||||
UIElement = 'CheckboxGrid',
|
||||
checkedKey = 'checked',
|
||||
accelerators = {
|
||||
[ ' ' ] = 'grid_toggle',
|
||||
key_enter = 'grid_toggle',
|
||||
},
|
||||
UIElement = 'CheckboxGrid',
|
||||
checkedKey = 'checked',
|
||||
accelerators = {
|
||||
space = 'grid_toggle',
|
||||
},
|
||||
}
|
||||
function UI.CheckboxGrid:drawRow(sb, row, focused, bg, fg)
|
||||
local ind = focused and self.focusIndicator or ' '
|
||||
@@ -32,7 +31,7 @@ function UI.CheckboxGrid:drawRow(sb, row, focused, bg, fg)
|
||||
end
|
||||
|
||||
function UI.CheckboxGrid:eventHandler(event)
|
||||
if event.type == 'grid_toggle' and self.selected then
|
||||
if event.type == 'key_enter' and self.selected then
|
||||
self.selected.checked = not self.selected.checked
|
||||
self:draw()
|
||||
self:emit({ type = 'grid_check', checked = self.selected, element = self })
|
||||
|
||||
@@ -15,7 +15,7 @@ UI.Chooser.defaults = {
|
||||
rightIndicator = UI.extChars and '\187' or '>',
|
||||
height = 1,
|
||||
accelerators = {
|
||||
[ ' ' ] = 'choice_next',
|
||||
space = 'choice_next',
|
||||
right = 'choice_next',
|
||||
left = 'choice_prev',
|
||||
}
|
||||
@@ -40,7 +40,7 @@ function UI.Chooser:draw()
|
||||
local value = choice and choice.name or self.nochoice
|
||||
|
||||
self:write(1, 1, self.leftIndicator, self.backgroundColor, colors.black)
|
||||
self:write(2, 1, ' ' .. Util.widthify(tostring(value), self.width - 4) .. ' ', bg, fg)
|
||||
self:write(2, 1, ' ' .. Util.widthify(tostring(value), self.width-4) .. ' ', bg, fg)
|
||||
self:write(self.width, 1, self.rightIndicator, self.backgroundColor, colors.black)
|
||||
end
|
||||
|
||||
@@ -54,7 +54,7 @@ function UI.Chooser:eventHandler(event)
|
||||
local choice
|
||||
if not k then k = 0 end
|
||||
if k and k < #self.choices then
|
||||
choice = self.choices[k + 1]
|
||||
choice = self.choices[k+1]
|
||||
else
|
||||
choice = self.choices[1]
|
||||
end
|
||||
@@ -62,12 +62,11 @@ function UI.Chooser:eventHandler(event)
|
||||
self:emit({ type = 'choice_change', value = self.value, element = self, choice = choice })
|
||||
self:draw()
|
||||
return true
|
||||
|
||||
elseif event.type == 'choice_prev' then
|
||||
local _,k = Util.find(self.choices, 'value', self.value)
|
||||
local choice
|
||||
if k and k > 1 then
|
||||
choice = self.choices[k - 1]
|
||||
choice = self.choices[k-1]
|
||||
else
|
||||
choice = self.choices[#self.choices]
|
||||
end
|
||||
@@ -75,7 +74,6 @@ function UI.Chooser:eventHandler(event)
|
||||
self:emit({ type = 'choice_change', value = self.value, element = self, choice = choice })
|
||||
self:draw()
|
||||
return true
|
||||
|
||||
elseif event.type == 'mouse_click' or event.type == 'mouse_doubleclick' then
|
||||
if event.x == 1 then
|
||||
self:emit({ type = 'choice_prev' })
|
||||
|
||||
@@ -1,74 +1,86 @@
|
||||
local class = require('opus.class')
|
||||
local UI = require('opus.ui')
|
||||
local Util = require('opus.util')
|
||||
local UI = require('opus.ui')
|
||||
local Util = require('opus.util')
|
||||
|
||||
local fs = _G.fs
|
||||
local colors = _G.colors
|
||||
local fs = _G.fs
|
||||
|
||||
UI.FileSelect = class(UI.Window)
|
||||
UI.FileSelect.defaults = {
|
||||
UIElement = 'FileSelect',
|
||||
}
|
||||
function UI.FileSelect:postInit()
|
||||
self.grid = UI.ScrollingGrid {
|
||||
x = 2, y = 2, ex = -2, ey = -4,
|
||||
dir = '/',
|
||||
sortColumn = 'name',
|
||||
columns = {
|
||||
{ heading = 'Name', key = 'name' },
|
||||
{ heading = 'Size', key = 'size', width = 5 }
|
||||
},
|
||||
getDisplayValues = function(_, row)
|
||||
return {
|
||||
name = row.name,
|
||||
size = row.size and Util.toBytes(row.size),
|
||||
}
|
||||
end,
|
||||
getRowTextColor = function(_, file)
|
||||
return file.isDir and 'cyan' or file.isReadOnly and 'pink' or 'white'
|
||||
end,
|
||||
sortCompare = function(self, a, b)
|
||||
if self.sortColumn == 'size' then
|
||||
return a.size < b.size
|
||||
end
|
||||
if a.isDir == b.isDir then
|
||||
return a.name:lower() < b.name:lower()
|
||||
end
|
||||
return a.isDir
|
||||
end,
|
||||
draw = function(self)
|
||||
local files = fs.listEx(self.dir)
|
||||
if #self.dir > 0 then
|
||||
table.insert(files, {
|
||||
name = '..',
|
||||
isDir = true,
|
||||
})
|
||||
end
|
||||
self:setValues(files)
|
||||
self:setIndex(1)
|
||||
UI.Grid.draw(self)
|
||||
end,
|
||||
}
|
||||
self.path = UI.TextEntry {
|
||||
x = 2, y = -2, ex = -11,
|
||||
accelerators = {
|
||||
enter = 'path_enter',
|
||||
}
|
||||
}
|
||||
self.cancel = UI.Button {
|
||||
x = -9, y = -2,
|
||||
text = 'Cancel',
|
||||
event = 'select_cancel',
|
||||
}
|
||||
self.grid = UI.ScrollingGrid {
|
||||
x = 2, y = 2, ex = -2, ey = -4,
|
||||
dir = '/',
|
||||
sortColumn = 'name',
|
||||
columns = {
|
||||
{ heading = 'Name', key = 'name' },
|
||||
{ heading = 'Size', key = 'size', width = 5 }
|
||||
},
|
||||
getDisplayValues = function(_, row)
|
||||
if row.size then
|
||||
row = Util.shallowCopy(row)
|
||||
row.size = Util.toBytes(row.size)
|
||||
end
|
||||
return row
|
||||
end,
|
||||
getRowTextColor = function(_, file)
|
||||
if file.isDir then
|
||||
return colors.cyan
|
||||
end
|
||||
if file.isReadOnly then
|
||||
return colors.pink
|
||||
end
|
||||
return colors.white
|
||||
end,
|
||||
sortCompare = function(self, a, b)
|
||||
if self.sortColumn == 'size' then
|
||||
return a.size < b.size
|
||||
end
|
||||
if a.isDir == b.isDir then
|
||||
return a.name:lower() < b.name:lower()
|
||||
end
|
||||
return a.isDir
|
||||
end,
|
||||
draw = function(self)
|
||||
local files = fs.listEx(self.dir)
|
||||
if #self.dir > 0 then
|
||||
table.insert(files, {
|
||||
name = '..',
|
||||
isDir = true,
|
||||
})
|
||||
end
|
||||
self:setValues(files)
|
||||
self:setIndex(1)
|
||||
UI.Grid.draw(self)
|
||||
end,
|
||||
}
|
||||
self.path = UI.TextEntry {
|
||||
x = 2,
|
||||
y = -2,
|
||||
ex = -11,
|
||||
limit = 256,
|
||||
accelerators = {
|
||||
enter = 'path_enter',
|
||||
}
|
||||
}
|
||||
self.cancel = UI.Button {
|
||||
text = 'Cancel',
|
||||
x = -9,
|
||||
y = -2,
|
||||
event = 'select_cancel',
|
||||
}
|
||||
end
|
||||
|
||||
function UI.FileSelect:draw()
|
||||
self:fillArea(1, 1, self.width, self.height, string.rep('\127', self.width), 'black', 'gray')
|
||||
self:drawChildren()
|
||||
self:fillArea(1, 1, self.width, self.height, string.rep('\127', self.width), colors.black, colors.gray)
|
||||
self:drawChildren()
|
||||
end
|
||||
|
||||
function UI.FileSelect:enable(path)
|
||||
self:setPath(path or '')
|
||||
UI.Window.enable(self)
|
||||
self:setPath(path or '')
|
||||
UI.Window.enable(self)
|
||||
end
|
||||
|
||||
function UI.FileSelect:setPath(path)
|
||||
@@ -86,21 +98,21 @@ function UI.FileSelect:eventHandler(event)
|
||||
if event.selected.isDir then
|
||||
self.grid:draw()
|
||||
self.path:draw()
|
||||
else
|
||||
self:emit({ type = 'select_file', file = '/' .. self.path.value, element = self })
|
||||
end
|
||||
return true
|
||||
else
|
||||
self:emit({ type = 'select_file', file = '/' .. self.path.value, element = self })
|
||||
end
|
||||
return true
|
||||
|
||||
elseif event.type == 'path_enter' then
|
||||
if self.path.value then
|
||||
if fs.isDir(self.path.value) then
|
||||
self:setPath(self.path.value)
|
||||
self.grid:draw()
|
||||
self.path:draw()
|
||||
else
|
||||
self:emit({ type = 'select_file', file = '/' .. self.path.value, element = self })
|
||||
end
|
||||
end
|
||||
return true
|
||||
elseif event.type == 'path_enter' then
|
||||
if self.path.value then
|
||||
if fs.isDir(self.path.value) then
|
||||
self:setPath(self.path.value)
|
||||
self.grid:draw()
|
||||
self.path:draw()
|
||||
else
|
||||
self:emit({ type = 'select_file', file = '/' .. self.path.value, element = self })
|
||||
end
|
||||
end
|
||||
return true
|
||||
end
|
||||
end
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user