15 Commits

Author SHA1 Message Date
kepler155c@gmail.com
5933f8c40f can now use named colors 2020-04-21 22:32:12 -06:00
kepler155c@gmail.com
e703c7f7b6 changes for deprecated ui methods 2020-04-17 20:40:34 -06:00
kepler155c@gmail.com
9d2a76f4ea color rework + cleanup 2020-04-16 23:13:19 -06:00
kepler155c@gmail.com
3e41996b9b cleanup + theme editor 2020-04-14 14:12:04 -06:00
kepler155c@gmail.com
9eeec8719c cleanup 2020-04-12 18:46:26 -06:00
kepler155c@gmail.com
775871c548 mouse triple click + textEntry scroll ind 2020-04-10 22:51:18 -06:00
kepler155c@gmail.com
cd6ef0da50 use layout() where appropriate and cleanup 2020-04-09 16:08:54 -06:00
kepler155c@gmail.com
8fe6e0806c refactor + new transitions 2020-04-06 00:12:46 -06:00
kepler155c@gmail.com
7659b81d49 more editor work 2020-04-04 20:56:53 -06:00
kepler155c@gmail.com
cd58ecd861 minor tweaks 2020-04-03 18:57:57 -06:00
kepler155c@gmail.com
d88ef00652 bugfixes + tweaks for editor 2.0 2020-04-02 21:28:42 -06:00
kepler155c@gmail.com
fc1a308193 list mode for overview 2020-03-31 23:43:45 -06:00
kepler155c@gmail.com
3313fb986c minor tweaks 2020-03-31 17:23:12 -06:00
kepler155c@gmail.com
613212e751 Merge remote-tracking branch 'origin/develop-1.8' into ui-enhancements-2.0 2020-03-31 09:59:15 -06:00
kepler155c@gmail.com
5a874c1944 canvas overhaul 2020-03-31 09:57:23 -06:00
114 changed files with 1501 additions and 2953 deletions

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -1,3 +1,4 @@
local Alt = require('opus.alternate')
local Config = require('opus.config') local Config = require('opus.config')
local Event = require('opus.event') local Event = require('opus.event')
local pastebin = require('opus.http.pastebin') local pastebin = require('opus.http.pastebin')
@@ -76,8 +77,8 @@ local Browser = UI.Page {
grid = UI.ScrollingGrid { grid = UI.ScrollingGrid {
columns = { columns = {
{ heading = 'Name', key = 'name' }, { heading = 'Name', key = 'name' },
{ key = 'flags', width = 3, textColor = 'lightGray' }, { key = 'flags', width = 2 },
{ heading = 'Size', key = 'fsize', width = 5, textColor = 'yellow' }, { heading = 'Size', key = 'fsize', width = 5 },
}, },
sortColumn = 'name', sortColumn = 'name',
y = 2, ey = -2, y = 2, ey = -2,
@@ -176,6 +177,7 @@ local Browser = UI.Page {
formLabel = 'Program', formKey = 'value', formLabel = 'Program', formKey = 'value',
shadowText = 'program', shadowText = 'program',
required = true, required = true,
limit = 128,
}, },
add = UI.Button { add = UI.Button {
x = -11, y = 1, x = -11, y = 1,
@@ -210,7 +212,7 @@ function Browser:enable()
self:setFocus(self.grid) self:setFocus(self.grid)
end end
function Browser.menuBar.getActive(_, menuItem) function Browser.menuBar:getActive(menuItem)
local file = Browser.grid:getSelected() local file = Browser.grid:getSelected()
if menuItem.flags == FILE then if menuItem.flags == FILE then
return file and not file.isDir return file and not file.isDir
@@ -222,7 +224,7 @@ function Browser:setStatus(status, ...)
self.notification:info(string.format(status, ...)) self.notification:info(string.format(status, ...))
end end
function Browser.unmarkAll() function Browser:unmarkAll()
for _,m in pairs(marked) do for _,m in pairs(marked) do
m.marked = false m.marked = false
end end
@@ -262,11 +264,10 @@ function Browser:updateDirectory(dir)
dir.size = #files dir.size = #files
for _, file in pairs(files) do for _, file in pairs(files) do
file.fullName = fs.combine(dir.name, file.name) file.fullName = fs.combine(dir.name, file.name)
file.flags = file.fstype or ' ' file.flags = ''
if not file.isDir then if not file.isDir then
dir.totalSize = dir.totalSize + file.size dir.totalSize = dir.totalSize + file.size
file.fsize = formatSize(file.size) file.fsize = formatSize(file.size)
file.flags = file.flags .. ' '
else else
if config.showDirSizes then if config.showDirSizes then
file.size = fs.getSize(file.fullName, true) file.size = fs.getSize(file.fullName, true)
@@ -274,9 +275,11 @@ function Browser:updateDirectory(dir)
dir.totalSize = dir.totalSize + file.size dir.totalSize = dir.totalSize + file.size
file.fsize = formatSize(file.size) file.fsize = formatSize(file.size)
end end
file.flags = file.flags .. 'D' file.flags = 'D'
end
if file.isReadOnly then
file.flags = file.flags .. 'R'
end end
file.flags = file.flags .. (file.isReadOnly and 'R' or ' ')
if config.showHidden or file.name:sub(1, 1) ~= '.' then if config.showHidden or file.name:sub(1, 1) ~= '.' then
dir.files[file.fullName] = file dir.files[file.fullName] = file
end end
@@ -350,7 +353,7 @@ function Browser:eventHandler(event)
self:setStatus('Started cloud edit') self:setStatus('Started cloud edit')
elseif event.type == 'shell' then elseif event.type == 'shell' then
self:run('shell') self:run(Alt.get('shell'))
elseif event.type == 'refresh' then elseif event.type == 'refresh' then
self:updateDirectory(self.dir) self:updateDirectory(self.dir)
@@ -465,7 +468,7 @@ function Browser:eventHandler(event)
elseif event.type == 'paste' then elseif event.type == 'paste' then
for _,m in pairs(copied) do for _,m in pairs(copied) do
pcall(function() local s, m = pcall(function()
if cutMode then if cutMode then
fs.move(m.fullName, fs.combine(self.dir.name, m.name)) fs.move(m.fullName, fs.combine(self.dir.name, m.name))
else else

View File

@@ -1,4 +1,3 @@
local fuzzy = require('opus.fuzzy')
local UI = require('opus.ui') local UI = require('opus.ui')
local Util = require('opus.util') local Util = require('opus.util')
@@ -43,13 +42,13 @@ UI:addPage('main', UI.Page {
elseif event.type == 'text_change' then elseif event.type == 'text_change' then
if not event.text then if not event.text then
self.grid.sortColumn = 'lname' self.grid.values = topics
else else
self.grid.sortColumn = 'score' self.grid.values = { }
self.grid.inverseSort = false for _,f in pairs(topics) do
local pattern = event.text:lower() if string.find(f.lname, event.text:lower()) then
for _,v in pairs(self.grid.values) do table.insert(self.grid.values, f)
v.score = -fuzzy(v.lname, pattern) end
end end
end end
self.grid:update() self.grid:update()

View File

@@ -1,3 +1,6 @@
-- Lua may be called from outside of shell - inject a require
_G.requireInjector(_ENV)
local History = require('opus.history') local History = require('opus.history')
local UI = require('opus.ui') local UI = require('opus.ui')
local Util = require('opus.util') local Util = require('opus.util')
@@ -7,8 +10,10 @@ local os = _G.os
local textutils = _G.textutils local textutils = _G.textutils
local term = _G.term local term = _G.term
local _exit
local sandboxEnv = setmetatable(Util.shallowCopy(_ENV), { __index = _G }) local sandboxEnv = setmetatable(Util.shallowCopy(_ENV), { __index = _G })
sandboxEnv.exit = function() UI:quit() end sandboxEnv.exit = function() _exit = true end
sandboxEnv._echo = function( ... ) return { ... } end sandboxEnv._echo = function( ... ) return { ... } end
_G.requireInjector(sandboxEnv) _G.requireInjector(sandboxEnv)
@@ -29,6 +34,7 @@ local page = UI.Page {
prompt = UI.TextEntry { prompt = UI.TextEntry {
y = 2, y = 2,
shadowText = 'enter command', shadowText = 'enter command',
limit = 1024,
accelerators = { accelerators = {
enter = 'command_enter', enter = 'command_enter',
up = 'history_back', up = 'history_back',
@@ -39,9 +45,8 @@ local page = UI.Page {
}, },
tabs = UI.Tabs { tabs = UI.Tabs {
y = 3, y = 3,
formatted = UI.Tab { [1] = UI.Tab {
title = 'Formatted', tabTitle = 'Formatted',
index = 1,
grid = UI.ScrollingGrid { grid = UI.ScrollingGrid {
columns = { columns = {
{ heading = 'Key', key = 'name' }, { heading = 'Key', key = 'name' },
@@ -51,9 +56,8 @@ local page = UI.Page {
autospace = true, autospace = true,
}, },
}, },
output = UI.Tab { [2] = UI.Tab {
title = 'Output', tabTitle = 'Output',
index = 2,
backgroundColor = 'black', backgroundColor = 'black',
output = UI.Embedded { output = UI.Embedded {
y = 2, y = 2,
@@ -68,8 +72,8 @@ local page = UI.Page {
}, },
} }
page.grid = page.tabs.formatted.grid page.grid = page.tabs[1].grid
page.output = page.tabs.output.output page.output = page.tabs[2].output
function page:setPrompt(value, focus) function page:setPrompt(value, focus)
self.prompt:setValue(value) self.prompt:setValue(value)
@@ -134,11 +138,24 @@ function page:eventHandler(event)
self:executeStatement('_ENV') self:executeStatement('_ENV')
command = nil command = nil
elseif event.type == 'hide_output' then
self.output:disable()
self.titleBar.oy = -1
self.titleBar.event = 'show_output'
self.titleBar.closeInd = '^'
self.titleBar:resize()
self.grid.ey = -2
self.grid:resize()
self:draw()
elseif event.type == 'tab_select' then elseif event.type == 'tab_select' then
self:setFocus(self.prompt) self:setFocus(self.prompt)
elseif event.type == 'show_output' then elseif event.type == 'show_output' then
self.tabs:selectTab(self.tabs.output) self.tabs:selectTab(self.tabs[2])
elseif event.type == 'autocomplete' then elseif event.type == 'autocomplete' then
local value = self.prompt.value or '' local value = self.prompt.value or ''
@@ -184,7 +201,7 @@ function page:eventHandler(event)
command = nil command = nil
self.grid:setValues(t) self.grid:setValues(t)
self.grid:setIndex(1) self.grid:setIndex(1)
self.grid:draw() self:draw()
end end
return true return true
@@ -230,7 +247,7 @@ function page:setResult(result)
end end
self.grid:setValues(t) self.grid:setValues(t)
self.grid:setIndex(1) self.grid:setIndex(1)
self.grid:draw() self:draw()
end end
function page.grid:eventHandler(event) function page.grid:eventHandler(event)
@@ -348,7 +365,7 @@ function page:executeStatement(statement)
term.redirect(oterm) term.redirect(oterm)
counter = counter + 1 counter = counter + 1
if s and type(m) ~= "nil" then if s and m then
self:setResult(m) self:setResult(m)
else else
self.grid:setValues({ }) self.grid:setValues({ })
@@ -357,6 +374,10 @@ function page:executeStatement(statement)
self:emit({ type = 'show_output' }) self:emit({ type = 'show_output' })
end end
end end
if _exit then
UI:quit()
end
end end
local args = Util.parse(...) local args = Util.parse(...)

View File

@@ -6,6 +6,7 @@ local Util = require('opus.util')
local device = _G.device local device = _G.device
local network = _G.network local network = _G.network
local os = _G.os
local shell = _ENV.shell local shell = _ENV.shell
UI:configure('Network', ...) UI:configure('Network', ...)
@@ -85,14 +86,8 @@ local page = UI.Page {
title = 'Ports', title = 'Ports',
event = 'ports_hide', event = 'ports_hide',
}, },
menuBar = UI.MenuBar {
y = 2,
buttons = {
{ text = 'Refresh', event = 'ports_update' },
}
},
grid = UI.ScrollingGrid { grid = UI.ScrollingGrid {
y = 3, y = 2,
columns = { columns = {
{ heading = 'Port', key = 'port' }, { heading = 'Port', key = 'port' },
{ heading = 'State', key = 'state' }, { heading = 'State', key = 'state' },
@@ -108,6 +103,30 @@ local page = UI.Page {
return UI.SlideOut.eventHandler(self, event) return UI.SlideOut.eventHandler(self, event)
end, 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 { }, notification = UI.Notification { },
accelerators = { accelerators = {
t = 'telnet', t = 'telnet',
@@ -188,14 +207,12 @@ function page:eventHandler(event)
elseif event.type == 'vnc' then elseif event.type == 'vnc' then
shell.openForegroundTab('vnc.lua ' .. t.id) shell.openForegroundTab('vnc.lua ' .. t.id)
--[[
os.queueEvent('overview_shortcut', { os.queueEvent('overview_shortcut', {
title = t.label, title = t.label,
category = "VNC", category = "VNC",
icon = "\010\030 \009\009\031e\\\031 \031e/\031dn\010\030 \009\009 \031e\\/\031 \031bc", icon = "\010\030 \009\009\031e\\\031 \031e/\031dn\010\030 \009\009 \031e\\/\031 \031bc",
run = "vnc.lua " .. t.id, run = "vnc.lua " .. t.id,
}) })
--]]
elseif event.type == 'clear' then elseif event.type == 'clear' then
Util.clear(network) Util.clear(network)
@@ -214,22 +231,17 @@ function page:eventHandler(event)
end end
if event.type == 'help' then if event.type == 'help' then
shell.switchTab(shell.openTab('Help Networking')) self.help:show()
elseif event.type == 'ports' then elseif event.type == 'ports' then
self.ports.grid:update() self.ports.grid:update()
self.ports:show() self.ports:show()
-- self.portsHandler = Event.onInterval(3, function() self.portsHandler = Event.onInterval(3, function()
-- self.ports.grid:update()
-- self.ports.grid:draw()
-- self:sync()
-- end)
elseif event.type == 'ports_update' then
self.ports.grid:update() self.ports.grid:update()
self.ports.grid:draw() self.ports.grid:draw()
self:sync() self:sync()
end)
elseif event.type == 'ports_hide' then elseif event.type == 'ports_hide' then
Event.off(self.portsHandler) Event.off(self.portsHandler)

View File

@@ -1,3 +1,4 @@
local Alt = require('opus.alternate')
local Array = require('opus.array') local Array = require('opus.array')
local class = require('opus.class') local class = require('opus.class')
local Config = require('opus.config') local Config = require('opus.config')
@@ -28,14 +29,12 @@ if not _ENV.multishell then
end end
local REGISTRY_DIR = 'usr/.registry' local REGISTRY_DIR = 'usr/.registry'
local DEFAULT_ICON = NFT.parse("\0308\0317\153\153\153\153\153\
-- iconExt:gsub('.', function(b) return '\\' .. b:byte() end) \0307\0318\153\153\153\153\153\
local DEFAULT_ICON = NFT.parse('\30\55\31\48\136\140\140\140\132\ \0308\0317\153\153\153\153\153")
\30\48\31\55\149\31\48\128\128\128\30\55\149\ local TRANS_ICON = NFT.parse("\0302\0312\32\32\32\32\32\
\30\55\31\48\138\143\143\143\133')
local TRANS_ICON = NFT.parse('\0302\0312\32\32\32\32\32\
\0302\0312\32\32\32\32\32\ \0302\0312\32\32\32\32\32\
\0302\0312\32\32\32\32\32') \0302\0312\32\32\32\32\32")
-- overview -- overview
local uid = _ENV.multishell.getCurrent() local uid = _ENV.multishell.getCurrent()
@@ -51,7 +50,7 @@ local config = {
} }
Config.load('Overview', config) Config.load('Overview', config)
local extSupport = Util.supportsExtChars() local extSupport = Util.getVersion() >= 1.76
local applications = { } local applications = { }
local buttons = { } local buttons = { }
@@ -96,7 +95,6 @@ local page = UI.Page {
width = 8, width = 8,
selectedBackgroundColor = 'primary', selectedBackgroundColor = 'primary',
backgroundColor = 'tertiary', backgroundColor = 'tertiary',
unselectedTextColor = 'lightGray',
layout = function(self) layout = function(self)
self.height = nil self.height = nil
UI.TabBar.layout(self) UI.TabBar.layout(self)
@@ -468,16 +466,16 @@ function page:eventHandler(event)
shell.switchTab(shell.openTab(event.button.app.run)) shell.switchTab(shell.openTab(event.button.app.run))
elseif event.type == 'shell' then elseif event.type == 'shell' then
shell.switchTab(shell.openTab('shell')) shell.switchTab(shell.openTab(Alt.get('shell')))
elseif event.type == 'lua' then elseif event.type == 'lua' then
shell.switchTab(shell.openTab('Lua')) shell.switchTab(shell.openTab(Alt.get('lua')))
elseif event.type == 'files' then elseif event.type == 'files' then
shell.switchTab(shell.openTab('Files')) shell.switchTab(shell.openTab(Alt.get('files')))
elseif event.type == 'network' then elseif event.type == 'network' then
shell.switchTab(shell.openTab('Network')) shell.switchTab(shell.openTab('network'))
elseif event.type == 'help' then elseif event.type == 'help' then
shell.switchTab(shell.openTab('Help Overview')) shell.switchTab(shell.openTab('Help Overview'))

View File

@@ -1,5 +1,4 @@
local Ansi = require('opus.ansi') local Ansi = require('opus.ansi')
local Config = require('opus.config')
local Packages = require('opus.packages') local Packages = require('opus.packages')
local UI = require('opus.ui') local UI = require('opus.ui')
local Util = require('opus.util') local Util = require('opus.util')
@@ -9,11 +8,9 @@ local term = _G.term
UI:configure('PackageManager', ...) UI:configure('PackageManager', ...)
local config = Config.load('package')
local page = UI.Page { local page = UI.Page {
grid = UI.ScrollingGrid { grid = UI.ScrollingGrid {
x = 2, ex = 14, y = 2, ey = -6, x = 2, ex = 14, y = 2, ey = -5,
values = { }, values = { },
columns = { columns = {
{ heading = 'Package', key = 'name' }, { heading = 'Package', key = 'name' },
@@ -24,13 +21,13 @@ local page = UI.Page {
}, },
add = UI.Button { add = UI.Button {
x = 2, y = -3, x = 2, y = -3,
text = ' + ', text = 'Install',
event = 'action', event = 'action',
help = 'Install or update', help = 'Install or update',
}, },
remove = UI.Button { remove = UI.Button {
x = 8, y = -3, x = 12, y = -3,
text = ' - ', text = 'Remove ',
event = 'action', event = 'action',
operation = 'uninstall', operation = 'uninstall',
operationText = 'Remove', operationText = 'Remove',
@@ -44,15 +41,7 @@ local page = UI.Page {
}, },
description = UI.TextArea { description = UI.TextArea {
x = 16, y = 3, ey = -5, x = 16, y = 3, ey = -5,
marginRight = 2, marginLeft = 0, marginRight = 0, marginLeft = 0,
},
UI.Checkbox {
x = 3, y = -5,
label = 'Compress',
textColor = 'yellow',
backgroundColor = 'primary',
value = config.compression,
help = 'Compress packages (experimental)',
}, },
action = UI.SlideOut { action = UI.SlideOut {
titleBar = UI.TitleBar { titleBar = UI.TitleBar {
@@ -113,6 +102,8 @@ end
function page.action:show() function page.action:show()
self.output.win:clear() self.output.win:clear()
UI.SlideOut.show(self) UI.SlideOut.show(self)
--self.output:draw()
--self.output.win.redraw()
end end
function page:run(operation, name) function page:run(operation, name)
@@ -136,6 +127,7 @@ end
function page:updateSelection(selected) function page:updateSelection(selected)
self.add.operation = selected.installed and 'update' or 'install' self.add.operation = selected.installed and 'update' or 'install'
self.add.operationText = selected.installed and 'Update' or 'Install' self.add.operationText = selected.installed and 'Update' or 'Install'
self.add.text = selected.installed and 'Update' or 'Install'
self.remove.inactive = not selected.installed self.remove.inactive = not selected.installed
self.add:draw() self.add:draw()
self.remove:draw() self.remove:draw()
@@ -148,16 +140,12 @@ function page:eventHandler(event)
elseif event.type == 'grid_focus_row' then elseif event.type == 'grid_focus_row' then
local manifest = event.selected.manifest local manifest = event.selected.manifest
self.description: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.yellow, manifest.title,
Ansi.white, manifest.description)) Ansi.white, manifest.description)
self.description:draw() self.description:draw()
self:updateSelection(event.selected) self:updateSelection(event.selected)
elseif event.type == 'checkbox_change' then
config.compression = not config.compression
Config.update('package', config)
elseif event.type == 'updateall' then elseif event.type == 'updateall' then
self.operation = 'updateall' self.operation = 'updateall'
self.action.button.text = ' Begin ' self.action.button.text = ' Begin '

View File

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

View File

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

View File

@@ -46,7 +46,7 @@ local page = UI.Page {
configTabs = UI.Tabs { configTabs = UI.Tabs {
y = 2, y = 2,
filterTab = UI.Tab { filterTab = UI.Tab {
title = 'Filter', tabTitle = 'Filter',
noFill = true, noFill = true,
filterGridText = UI.Text { filterGridText = UI.Text {
x = 2, y = 2, x = 2, y = 2,
@@ -93,7 +93,7 @@ local page = UI.Page {
}, },
}, },
modemTab = UI.Tab { modemTab = UI.Tab {
title = 'Modem', tabTitle = 'Modem',
channelGrid = UI.ScrollingGrid { channelGrid = UI.ScrollingGrid {
x = 2, y = 2, x = 2, y = 2,
width = 12, height = 5, width = 12, height = 5,
@@ -255,7 +255,7 @@ function page.packetSlide:eventHandler(event)
page:setFocus(page.packetGrid) page:setFocus(page.packetGrid)
elseif event.type == 'packet_lua' then elseif event.type == 'packet_lua' then
multishell.openTab(_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 elseif event.type == 'prev_packet' then
local c = self.currentPacket local c = self.currentPacket

View File

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

View File

@@ -12,7 +12,6 @@ local page = UI.Page {
buttons = { buttons = {
{ text = 'Activate', event = 'activate' }, { text = 'Activate', event = 'activate' },
{ text = 'Terminate', event = 'terminate' }, { text = 'Terminate', event = 'terminate' },
{ text = 'Inspect', event = 'inspect' },
}, },
}, },
grid = UI.ScrollingGrid { grid = UI.ScrollingGrid {
@@ -40,7 +39,7 @@ local page = UI.Page {
}, },
accelerators = { accelerators = {
[ 'control-q' ] = 'quit', [ 'control-q' ] = 'quit',
[ ' ' ] = 'activate', space = 'activate',
t = 'terminate', t = 'terminate',
}, },
eventHandler = function (self, event) eventHandler = function (self, event)
@@ -50,12 +49,6 @@ local page = UI.Page {
multishell.setFocus(t.uid) multishell.setFocus(t.uid)
elseif event.type == 'terminate' then elseif event.type == 'terminate' then
multishell.terminate(t.uid) multishell.terminate(t.uid)
elseif event.type == 'inspect' then
multishell.openTab(_ENV, {
path = 'sys/apps/Lua.lua',
args = { t },
focused = true,
})
end end
end end
if event.type == 'quit' then if event.type == 'quit' then

View File

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

View File

@@ -33,85 +33,87 @@ https://github.com/kepler155c/opus]]
local page = UI.Page { local page = UI.Page {
wizard = UI.Wizard { wizard = UI.Wizard {
ey = -2, ey = -2,
splash = UI.WizardPage { pages = {
index = 1, splash = UI.WizardPage {
intro = UI.TextArea { index = 1,
textColor = colors.yellow, intro = UI.TextArea {
inactive = true, textColor = colors.yellow,
x = 3, ex = -3, y = 2, ey = -2, inactive = true,
value = string.format(splashIntro, Ansi.white), x = 3, ex = -3, y = 2, ey = -2,
value = string.format(splashIntro, Ansi.white),
},
}, },
}, label = UI.WizardPage {
label = UI.WizardPage { index = 2,
index = 2, labelText = UI.Text {
labelText = UI.Text { x = 3, y = 2,
x = 3, y = 2, value = 'Label'
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 { password = UI.WizardPage {
x = 9, y = 2, ex = -3, index = 3,
limit = 32, passwordLabel = UI.Text {
value = os.getComputerLabel(), 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 { packages = UI.WizardPage {
textColor = colors.yellow, index = 4,
inactive = true, button = UI.Button {
x = 3, ex = -3, y = 4, ey = -3, x = 3, y = -3,
value = string.format(labelIntro, Ansi.white), 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) contributors = UI.WizardPage {
if self.label.value then index = 5,
os.setComputerLabel(self.label.value) intro = UI.TextArea {
end textColor = colors.yellow,
return true inactive = true,
end, x = 3, ex = -3, y = 2, ey = -2,
}, value = string.format(contributorsIntro, Ansi.white, Ansi.yellow, Ansi.white),
password = UI.WizardPage { },
index = 3,
passwordLabel = UI.Text {
x = 3, y = 2,
value = 'Password'
},
newPass = UI.TextEntry {
x = 12, ex = -3, y = 2,
limit = 32,
mask = true,
shadowText = 'password',
},
intro = UI.TextArea {
textColor = colors.yellow,
inactive = true,
x = 3, ex = -3, y = 5, ey = -3,
value = string.format(passwordIntro, Ansi.white),
},
validate = function (self)
if type(self.newPass.value) == "string" and #self.newPass.value > 0 then
Security.updatePassword(SHA.compute(self.newPass.value))
end
return true
end,
},
packages = UI.WizardPage {
index = 4,
button = UI.Button {
x = 3, y = -3,
text = 'Open Package Manager',
event = 'packages',
},
intro = UI.TextArea {
textColor = colors.yellow,
inactive = true,
x = 3, ex = -3, y = 2, ey = -4,
value = string.format(packagesIntro, Ansi.white),
},
},
contributors = UI.WizardPage {
index = 5,
intro = UI.TextArea {
textColor = colors.yellow,
inactive = true,
x = 3, ex = -3, y = 2, ey = -2,
value = string.format(contributorsIntro, Ansi.white, Ansi.yellow, Ansi.white),
}, },
}, },
}, },

View File

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

View File

@@ -17,7 +17,7 @@ local page = UI.Page {
UI:quit() UI:quit()
end end
return UI.Page.eventHandler(self, event) return UI.FileSelect.eventHandler(self, event)
end, end,
} }

View File

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

View File

@@ -49,7 +49,7 @@ page = UI.Page {
backgroundColor = colors.red, backgroundColor = colors.red,
y = '50%', y = '50%',
properties = UI.Tab { properties = UI.Tab {
title = 'Properties', tabTitle = 'Properties',
grid = UI.ScrollingGrid { grid = UI.ScrollingGrid {
headerBackgroundColor = colors.red, headerBackgroundColor = colors.red,
sortColumn = 'key', sortColumn = 'key',
@@ -64,7 +64,7 @@ page = UI.Page {
}, },
methodsTab = UI.Tab { methodsTab = UI.Tab {
index = 2, index = 2,
title = 'Methods', tabTitle = 'Methods',
grid = UI.ScrollingGrid { grid = UI.ScrollingGrid {
ex = '50%', ex = '50%',
headerBackgroundColor = colors.red, headerBackgroundColor = colors.red,
@@ -85,7 +85,7 @@ page = UI.Page {
}, },
events = UI.Tab { events = UI.Tab {
index = 1, index = 1,
title = 'Events', tabTitle = 'Events',
UI.MenuBar { UI.MenuBar {
y = -1, y = -1,
backgroundColor = colors.red, backgroundColor = colors.red,
@@ -110,7 +110,7 @@ page = UI.Page {
self.grid:draw() self.grid:draw()
elseif event.type == 'grid_select' then elseif event.type == 'grid_select' then
multishell.openTab(_ENV, { multishell.openTab({
path = 'sys/apps/Lua.lua', path = 'sys/apps/Lua.lua',
args = { event.selected.raw }, args = { event.selected.raw },
focused = true, focused = true,

View File

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

View File

@@ -59,10 +59,6 @@ local function sambaConnection(socket)
print('samba: Connection closed') print('samba: Connection closed')
end end
local function sanitizeLabel(computer)
return (computer.id.."_"..computer.label:gsub("[%c%.\"'/%*]", "")):sub(1, 40)
end
Event.addRoutine(function() Event.addRoutine(function()
print('samba: listening on port 139') print('samba: listening on port 139')
@@ -83,10 +79,10 @@ Event.addRoutine(function()
end) end)
Event.on('network_attach', function(_, computer) 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) end)
Event.on('network_detach', function(_, computer) Event.on('network_detach', function(_, computer)
print('samba: detaching ' .. sanitizeLabel(computer)) print('samba: detaching ' .. computer.label)
fs.unmount(fs.combine('network', sanitizeLabel(computer))) fs.unmount(fs.combine('network', computer.label))
end) end)

View File

@@ -31,14 +31,21 @@ local function snmpConnection(socket)
socket:write('pong') socket:write('pong')
elseif msg.type == 'script' then elseif msg.type == 'script' then
kernel.run(_ENV, { local env = setmetatable(Util.shallowCopy(_ENV), { __index = _G })
chunk = msg.args, local fn, err = load(msg.args, 'script', nil, env)
title = 'script', if fn then
}) kernel.run({
fn = fn,
env = env,
title = 'script',
})
else
_G.printError(err)
end
elseif msg.type == 'scriptEx' then elseif msg.type == 'scriptEx' then
local s, m = pcall(function() local s, m = pcall(function()
local env = kernel.makeEnv(_ENV) local env = setmetatable(Util.shallowCopy(_ENV), { __index = _G })
local fn, m = load(msg.args, 'script', nil, env) local fn, m = load(msg.args, 'script', nil, env)
if not fn then if not fn then
error(m) error(m)
@@ -152,7 +159,7 @@ local function getSlots()
end end
local function sendInfo() local function sendInfo()
if os.clock() - infoTimer >= 5 then -- don't flood if os.clock() - infoTimer >= 1 then -- don't flood
infoTimer = os.clock() infoTimer = os.clock()
info.label = os.getComputerLabel() info.label = os.getComputerLabel()
info.uptime = math.floor(os.clock()) info.uptime = math.floor(os.clock())
@@ -194,25 +201,16 @@ local function sendInfo()
end end
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 for _,c in pairs(_G.network) do
local elapsed = os.clock()-c.timestamp local elapsed = os.clock()-c.timestamp
if c.active and elapsed > 50 then if c.active and elapsed > 15 then
c.active = false c.active = false
os.queueEvent('network_detach', c) os.queueEvent('network_detach', c)
end end
end end
end
-- every 30 seconds, send out this computer's info
-- send with offset so that messages are evenly distributed and do not all come at once
Event.onTimeout(math.random() * 30, function()
sendInfo()
cleanNetwork()
Event.onInterval(30, function()
sendInfo()
cleanNetwork()
end)
end) end)
Event.on('turtle_response', function() Event.on('turtle_response', function()
@@ -222,5 +220,4 @@ Event.on('turtle_response', function()
end end
end) end)
-- send info early so that computers show soon after booting Event.onTimeout(1, sendInfo)
Event.onTimeout(math.random() * 2 + 1, sendInfo)

View File

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

View File

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

View File

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

View File

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

View File

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

View 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

View File

@@ -6,7 +6,7 @@ if _G.http.websocket then
local config = Config.load('cloud') local config = Config.load('cloud')
local tab = UI.Tab { local tab = UI.Tab {
title = 'Cloud', tabTitle = 'Cloud',
description = 'Cloud Catcher options', description = 'Cloud Catcher options',
[1] = UI.Window { [1] = UI.Window {
x = 2, y = 2, ex = -2, ey = 4, x = 2, y = 2, ex = -2, ey = 4,

View File

@@ -16,7 +16,7 @@ local NftImages = {
} }
local tab = UI.Tab { local tab = UI.Tab {
title = 'Disks Usage', tabTitle = 'Disks Usage',
description = 'Visualise HDD and disks usage', description = 'Visualise HDD and disks usage',
drives = UI.ScrollingGrid { drives = UI.ScrollingGrid {
@@ -136,17 +136,6 @@ function tab:enable()
self:updateDrives() self:updateDrives()
self:updateInfo() self:updateInfo()
UI.Tab.enable(self) UI.Tab.enable(self)
self.handler = Event.on({ 'disk', 'disk_eject' }, function()
os.sleep(1)
tab:updateDrives()
tab:updateInfo()
tab:sync()
end)
end
function tab:disable()
Event.off(self.handler)
UI.Tab.disable(self)
end end
function tab:eventHandler(event) function tab:eventHandler(event)
@@ -158,4 +147,11 @@ function tab:eventHandler(event)
return true return true
end end
Event.on({ 'disk', 'disk_eject' }, function()
os.sleep(1)
tab:updateDrives()
tab:updateInfo()
tab:sync()
end)
return tab return tab

View File

@@ -5,7 +5,7 @@ local peripheral = _G.peripheral
local settings = _G.settings local settings = _G.settings
return peripheral.find('monitor') and UI.Tab { return peripheral.find('monitor') and UI.Tab {
title = 'Kiosk', tabTitle = 'Kiosk',
description = 'Kiosk options', description = 'Kiosk options',
form = UI.Form { form = UI.Form {
x = 2, y = 2, ex = -2, ey = 5, x = 2, y = 2, ex = -2, ey = 5,

View File

@@ -5,7 +5,7 @@ local fs = _G.fs
local os = _G.os local os = _G.os
return UI.Tab { return UI.Tab {
title = 'Label', tabTitle = 'Label',
description = 'Set the computer label', description = 'Set the computer label',
labelText = UI.Text { labelText = UI.Text {
x = 3, y = 3, x = 3, y = 3,
@@ -26,12 +26,12 @@ return UI.Tab {
x = 2, y = 5, ex = -2, ey = -2, x = 2, y = 5, ex = -2, ey = -2,
values = { values = {
{ name = '', value = '' }, { name = '', value = '' },
{ name = 'CC version', value = ("%d.%d"):format(Util.getVersion()) }, { name = 'CC version', value = Util.getVersion() },
{ name = 'Lua version', value = _VERSION }, { name = 'Lua version', value = _VERSION },
{ name = 'MC version', value = Util.getMinecraftVersion() }, { name = 'MC version', value = Util.getMinecraftVersion() },
{ name = 'Disk free', value = Util.toBytes(fs.getFreeSpace('/')) }, { name = 'Disk free', value = Util.toBytes(fs.getFreeSpace('/')) },
{ name = 'Computer ID', value = tostring(os.getComputerID()) }, { name = 'Computer ID', value = tostring(os.getComputerID()) },
{ name = 'Day', value = tostring(os.day()) }, { name = 'Day', value = tostring(os.day()) },
}, },
disableHeader = true, disableHeader = true,
inactive = true, inactive = true,

View File

@@ -7,7 +7,7 @@ local fs = _G.fs
local config = Config.load('multishell') local config = Config.load('multishell')
local tab = UI.Tab { local tab = UI.Tab {
title = 'Launcher', tabTitle = 'Launcher',
description = 'Set the application launcher', description = 'Set the application launcher',
[1] = UI.Window { [1] = UI.Window {
x = 2, y = 2, ex = -2, ey = 5, x = 2, y = 2, ex = -2, ey = 5,
@@ -26,6 +26,7 @@ local tab = UI.Tab {
}, },
custom = UI.TextEntry { custom = UI.TextEntry {
x = 13, ex = -3, y = 4, x = 13, ex = -3, y = 4,
limit = 128,
shadowText = 'File name', shadowText = 'File name',
}, },
button = UI.Button { button = UI.Button {

View File

@@ -6,7 +6,7 @@ local colors = _G.colors
local device = _G.device local device = _G.device
return UI.Tab { return UI.Tab {
title = 'Network', tabTitle = 'Network',
description = 'Networking options', description = 'Networking options',
info = UI.TextArea { info = UI.TextArea {
x = 2, y = 5, ex = -2, ey = -2, x = 2, y = 5, ex = -2, ey = -2,

View File

@@ -3,7 +3,7 @@ local SHA = require('opus.crypto.sha2')
local UI = require('opus.ui') local UI = require('opus.ui')
return UI.Tab { return UI.Tab {
title = 'Password', tabTitle = 'Password',
description = 'Wireless network password', description = 'Wireless network password',
[1] = UI.Window { [1] = UI.Window {
x = 2, y = 2, ex = -2, ey = 4, x = 2, y = 2, ex = -2, ey = 4,

View File

@@ -3,7 +3,7 @@ local UI = require('opus.ui')
local Util = require('opus.util') local Util = require('opus.util')
local tab = UI.Tab { local tab = UI.Tab {
title = 'Path', tabTitle = 'Path',
description = 'Set the shell path', description = 'Set the shell path',
tabClose = true, tabClose = true,
[1] = UI.Window { [1] = UI.Window {
@@ -11,6 +11,7 @@ local tab = UI.Tab {
}, },
entry = UI.TextEntry { entry = UI.TextEntry {
x = 3, y = 3, ex = -3, x = 3, y = 3, ex = -3,
limit = 256,
shadowText = 'enter new path', shadowText = 'enter new path',
accelerators = { accelerators = {
enter = 'update_path', enter = 'update_path',

View File

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

View File

@@ -8,7 +8,7 @@ local transform = {
} }
return settings and UI.Tab { return settings and UI.Tab {
title = 'Settings', tabTitle = 'Settings',
description = 'Computercraft settings', description = 'Computercraft settings',
grid = UI.Grid { grid = UI.Grid {
x = 2, y = 2, ex = -2, ey = -2, x = 2, y = 2, ex = -2, ey = -2,
@@ -27,6 +27,7 @@ return settings and UI.Tab {
form = UI.Form { form = UI.Form {
y = 2, y = 2,
value = UI.TextEntry { value = UI.TextEntry {
limit = 256,
formIndex = 1, formIndex = 1,
formLabel = 'Value', formLabel = 'Value',
formKey = 'value', formKey = 'value',

View File

@@ -18,7 +18,9 @@ local defaults = {
textColor = colors.white, textColor = colors.white,
commandTextColor = colors.yellow, commandTextColor = colors.yellow,
directoryTextColor = colors.orange, directoryTextColor = colors.orange,
directoryBackgroundColor = colors.black,
promptTextColor = colors.blue, promptTextColor = colors.blue,
promptBackgroundColor = colors.black,
directoryColor = colors.green, directoryColor = colors.green,
fileColor = colors.white, fileColor = colors.white,
backgroundColor = colors.black, backgroundColor = colors.black,
@@ -37,7 +39,7 @@ if not _colors.backgroundColor then
end end
return UI.Tab { return UI.Tab {
title = 'Shell', tabTitle = 'Shell',
description = 'Shell options', description = 'Shell options',
grid1 = UI.ScrollingGrid { grid1 = UI.ScrollingGrid {
y = 2, ey = -10, x = 2, ex = -17, y = 2, ey = -10, x = 2, ex = -17,
@@ -84,12 +86,12 @@ return UI.Tab {
if config.displayDirectory then if config.displayDirectory then
self:write(1, 1, self:write(1, 1,
'==' .. os.getComputerLabel() .. ':/dir/etc', '==' .. os.getComputerLabel() .. ':/dir/etc',
_colors.backgroundColor, _colors.directoryTextColor) _colors.directoryBackgroundColor, _colors.directoryTextColor)
offset = 1 offset = 1
end end
self:write(1, 1 + offset, '$ ', self:write(1, 1 + offset, '$ ',
_colors.backgroundColor, _colors.promptTextColor) _colors.promptBackgroundColor, _colors.promptTextColor)
self:write(3, 1 + offset, 'ls /', self:write(3, 1 + offset, 'ls /',
_colors.backgroundColor, _colors.commandTextColor) _colors.backgroundColor, _colors.commandTextColor)

View File

@@ -12,12 +12,12 @@ for k,v in pairs(colors) do
end end
local allSettings = { } 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 } allSettings[k] = { name = k, value = v }
end end
return UI.Tab { return UI.Tab {
title = 'Theme', tabTitle = 'Theme',
description = 'Theme colors', description = 'Theme colors',
grid1 = UI.ScrollingGrid { grid1 = UI.ScrollingGrid {
y = 2, ey = -10, x = 2, ex = -17, y = 2, ey = -10, x = 2, ex = -17,

View File

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

View File

@@ -10,8 +10,9 @@ if multishell and multishell.getTabs then
local tab = kernel.getFocused() local tab = kernel.getFocused()
if tab and not tab.noTerminate then if tab and not tab.noTerminate then
multishell.terminate(tab.uid) multishell.terminate(tab.uid)
multishell.openTab(tab.env, { multishell.openTab({
path = tab.path, path = tab.path,
env = tab.env,
args = tab.args, args = tab.args,
focused = true, focused = true,
}) })

View File

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

24
sys/autorun/upgraded.lua Normal file
View 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')

View File

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

View File

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

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

@@ -0,0 +1,36 @@
local fs = _G.fs
local sandboxEnv = setmetatable({ }, { __index = _G })
for k,v in pairs(_ENV) do
sandboxEnv[k] = v
end
local function run(file, ...)
local env = setmetatable({ }, { __index = _G })
for k,v in pairs(sandboxEnv) do
env[k] = v
end
local s, m = loadfile(file, env)
if s then
return s(...)
end
error('Error loading ' .. file .. '\n' .. m)
end
_G._syslog = function() end
_G.OPUS_BRANCH = '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

View File

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

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

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

View File

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

View File

@@ -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", iconExt = "\030 \031 \128\0307\143\131\131\131\131\143\030 \128\010\0307\031 \129\0317\128\0319\136\0309\031b\136\132\0307\0319\132\0317\128\031 \130\010\030 \0317\130\143\0307\128\128\128\128\030 \143\129",
run = "/rom/programs/fun/dj", run = "/rom/programs/fun/dj",
}, },
[ "4dbdd221e957eff27cc47796f3ed8447290f71c7ad8b95e5bd828b31c1858f15" ] = {
title = "Partition",
category = "System",
iconExt = "\30\55\31\55\128\30\48\135\131\139\30\55\128\128\128\10\30\48\31\55\149\31\48\128\30\55\145\30\48\31\56\140\30\55\157\144\144\10\30\55\31\55\128\31\48\139\143\135\31\55\128\31\56\142\133",
run = "Partition",
}
} }

View File

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

View File

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

View File

@@ -6,7 +6,17 @@ Shortcut Keys
* Control-d: Show/toggle logging screen * Control-d: Show/toggle logging screen
* Control-c: Copy (in most applications) * Control-c: Copy (in most applications)
* Control-shift-v: Paste from internal clipboard * Control-shift-v: Paste from internal clipboard
* Control-shift-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 Running Custom Programs
======================= =======================

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -15,7 +15,7 @@ do
end end
local function startNetwork() local function startNetwork()
kernel.run(_ENV, { kernel.run({
title = 'Net daemon', title = 'Net daemon',
path = 'sys/apps/netdaemon.lua', path = 'sys/apps/netdaemon.lua',
hidden = true, hidden = true,

View File

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

View File

@@ -13,14 +13,8 @@ table.insert(helpPaths, '/sys/help')
for name in pairs(Packages:installed()) do for name in pairs(Packages:installed()) do
local packageDir = fs.combine('packages', name) local packageDir = fs.combine('packages', name)
local fstabPath = fs.combine(packageDir, 'etc/fstab')
if fs.exists(fstabPath) then
fs.loadTab(fstabPath)
end
table.insert(appPaths, 1, '/' .. packageDir) table.insert(appPaths, 1, '/' .. packageDir)
local apiPath = fs.combine(packageDir, 'apis')
local apiPath = fs.combine(packageDir, 'apis') -- TODO: rename dir to 'modules' (someday)
if fs.exists(apiPath) then if fs.exists(apiPath) then
fs.mount(fs.combine('rom/modules/main', name), 'linkfs', apiPath) fs.mount(fs.combine('rom/modules/main', name), 'linkfs', apiPath)
end end
@@ -29,6 +23,11 @@ for name in pairs(Packages:installed()) do
if fs.exists(helpPath) then if fs.exists(helpPath) then
table.insert(helpPaths, helpPath) table.insert(helpPaths, helpPath)
end end
local fstabPath = fs.combine(packageDir, 'etc/fstab')
if fs.exists(fstabPath) then
fs.loadTab(fstabPath)
end
end end
help.setPath(table.concat(helpPaths, ':')) help.setPath(table.concat(helpPaths, ':'))

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -100,7 +100,7 @@ local function crypt(data, key, nonce, cntr, round)
cntr = tonumber(cntr) or 1 cntr = tonumber(cntr) or 1
round = tonumber(round) or 20 round = tonumber(round) or 20
local throttle = Util.throttle() local throttle = Util.throttle(function() _syslog('throttle') end)
local out = {} local out = {}
local state = initState(key, nonce, cntr) local state = initState(key, nonce, cntr)
local blockAmt = math.floor(#data/64) local blockAmt = math.floor(#data/64)

View File

@@ -47,13 +47,13 @@ function Entry:updateScroll()
self.scroll = 0 -- ?? self.scroll = 0 -- ??
end end
if self.pos - self.scroll > self.width then if self.pos - self.scroll > self.width then
self.scroll = math.max(0, self.pos - self.width) self.scroll = self.pos - self.width
elseif self.pos < self.scroll then elseif self.pos < self.scroll then
self.scroll = self.pos self.scroll = self.pos
end end
if self.scroll > 0 then if self.scroll > 0 then
if self.scroll + self.width > len then if self.scroll + self.width > len then
self.scroll = math.max(0, len - self.width) self.scroll = len - self.width
end end
end end
if ps ~= self.scroll then if ps ~= self.scroll then
@@ -222,7 +222,7 @@ function Entry:paste(ie)
end end
end end
function Entry.forcePaste() function Entry:forcePaste()
os.queueEvent('clipboard_paste') os.queueEvent('clipboard_paste')
end end
@@ -234,9 +234,7 @@ end
function Entry:markBegin() function Entry:markBegin()
if not self.mark.active then if not self.mark.active then
if #_val(self.value) > 0 then self.mark.active = true
self.mark.active = true
end
self.mark.anchor = { x = self.pos } self.mark.anchor = { x = self.pos }
end end
end end
@@ -273,8 +271,6 @@ function Entry:markLeft()
self:markBegin() self:markBegin()
if self:moveLeft() then if self:moveLeft() then
self:markFinish() self:markFinish()
else
self.mark.continue = self.mark.active
end end
end end
@@ -282,8 +278,6 @@ function Entry:markRight()
self:markBegin() self:markBegin()
if self:moveRight() then if self:moveRight() then
self:markFinish() self:markFinish()
else
self.mark.continue = self.mark.active
end end
end end
@@ -311,8 +305,6 @@ function Entry:markNextWord()
self:markBegin() self:markBegin()
if self:moveWordRight() then if self:moveWordRight() then
self:markFinish() self:markFinish()
else
self.mark.continue = self.mark.active
end end
end end
@@ -320,8 +312,6 @@ function Entry:markPrevWord()
self:markBegin() self:markBegin()
if self:moveWordLeft() then if self:moveWordLeft() then
self:markFinish() self:markFinish()
else
self.mark.continue = self.mark.active
end end
end end
@@ -340,8 +330,6 @@ function Entry:markHome()
self:markBegin() self:markBegin()
if self:moveHome() then if self:moveHome() then
self:markFinish() self:markFinish()
else
self.mark.continue = self.mark.active
end end
end end
@@ -349,8 +337,6 @@ function Entry:markEnd()
self:markBegin() self:markBegin()
if self:moveEnd() then if self:moveEnd() then
self:markFinish() self:markFinish()
else
self.mark.continue = self.mark.active
end end
end end

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -1,56 +1,21 @@
local find = string.find -- Based on Squid's fuzzy search
local floor = math.floor -- https://github.com/SquidDev-CC/artist/blob/vnext/artist/lib/match.lua
local min = math.min --
local max = math.max -- not very fuzzy anymore
local sub = string.sub
-- https://rosettacode.org/wiki/Jaro_distance (ported to lua) local SCORE_WEIGHT = 1000
return function(s1, s2) local LEADING_LETTER_PENALTY = -30
local l1, l2 = #s1, #s2; local LEADING_LETTER_PENALTY_MAX = -90
if l1 == 0 then
return l2 == 0 and 1.0 or 0.0 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 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 end

View File

@@ -20,12 +20,13 @@ function GPS.locate(timeout, debug)
local modem = device.wireless_modem local modem = device.wireless_modem
local closeChannel = false local closeChannel = false
if not modem.isOpen(GPS.CHANNEL_GPS) then local selfID = os.getComputerID()
modem.open(GPS.CHANNEL_GPS) if not modem.isOpen(selfID) then
modem.open(selfID)
closeChannel = true closeChannel = true
end end
modem.transmit(GPS.CHANNEL_GPS, GPS.CHANNEL_GPS, "PING") modem.transmit(GPS.CHANNEL_GPS, selfID, "PING")
local fixes = {} local fixes = {}
local pos = nil local pos = nil
@@ -33,7 +34,7 @@ function GPS.locate(timeout, debug)
while true do while true do
local e, side, chan, reply, msg, dist = os.pullEvent() local e, side, chan, reply, msg, dist = os.pullEvent()
if e == "modem_message" then if e == "modem_message" then
if side == modem.side and chan == 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 if type(msg) == "table" and #msg == 3 and tonumber(msg[1]) and tonumber(msg[2]) and tonumber(msg[3]) then
local fix = { local fix = {
position = vector.new(unpack(msg)), position = vector.new(unpack(msg)),
@@ -59,7 +60,7 @@ function GPS.locate(timeout, debug)
end end
if closeChannel then if closeChannel then
modem.close(GPS.CHANNEL_GPS) modem.close(selfID)
end end
if debug then if debug then
print("Position is "..pos.x..","..pos.y..","..pos.z) print("Position is "..pos.x..","..pos.y..","..pos.z)

View File

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

View File

@@ -189,22 +189,11 @@ function input:translate(event, code, p1, p2)
end end
end end
if not ({ ...})[1] then function input:test()
local colors = _G.colors
local term = _G.term
while true do while true do
local e = { os.pullEvent() } local ch = self:translate(os.pullEvent())
local ch = input:translate(table.unpack(e))
if ch then if ch then
term.setTextColor(colors.white) Util.print(ch)
print(table.unpack(e))
term.setTextColor(colors.lime)
local t = { }
for k,v in pairs(ch) do
table.insert(t, k .. ':' .. v)
end
print('--> ' .. table.concat(t, ' ') .. '\n')
end end
end end
end end

View File

@@ -39,8 +39,7 @@ if register_global_module_table then
_G[global_module_name] = json _G[global_module_name] = json
end 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() pcall (function()
-- Enable access to blocked metatables. -- Enable access to blocked metatables.

View File

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

View File

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

View File

@@ -6,11 +6,12 @@ local Util = require('opus.util')
local device = _G.device local device = _G.device
local os = _G.os local os = _G.os
local network = _G.network
local socketClass = { } local socketClass = { }
function socketClass:read(timeout) function socketClass:read(timeout)
local data, distance = _G.network.getTransport().read(self) local data, distance = network.getTransport().read(self)
if data then if data then
return data, distance return data, distance
end end
@@ -25,7 +26,7 @@ function socketClass:read(timeout)
local e, id = os.pullEvent() local e, id = os.pullEvent()
if e == 'transport_' .. self.uid then if e == 'transport_' .. self.uid then
data, distance = _G.network.getTransport().read(self) data, distance = network.getTransport().read(self)
if data then if data then
os.cancelTimer(timerId) os.cancelTimer(timerId)
return data, distance return data, distance
@@ -46,7 +47,7 @@ end
function socketClass:write(data) function socketClass:write(data)
if self.connected then if self.connected then
_G.network.getTransport().write(self, { network.getTransport().write(self, {
type = 'DATA', type = 'DATA',
seq = self.wseq, seq = self.wseq,
data = data, data = data,
@@ -57,7 +58,7 @@ end
function socketClass:ping() function socketClass:ping()
if self.connected then if self.connected then
_G.network.getTransport().ping(self) network.getTransport().ping(self)
return true return true
end end
end end
@@ -71,7 +72,7 @@ function socketClass:close()
self.connected = false self.connected = false
end end
device.wireless_modem.close(self.sport) device.wireless_modem.close(self.sport)
_G.network.getTransport().close(self) network.getTransport().close(self)
end end
local Socket = { } local Socket = { }
@@ -126,9 +127,9 @@ function Socket.connect(host, port, options)
local socket = newSocket(host == os.getComputerID()) local socket = newSocket(host == os.getComputerID())
socket.dhost = tonumber(host) socket.dhost = tonumber(host)
if options and options.keypair then if options and options.keypair then
socket.privKey, socket.pubKey = table.unpack(options.keypair) socket.privKey, socket.pubKey = unpack(options.keypair)
else else
socket.privKey, socket.pubKey = _G.network.getKeyPair() socket.privKey, socket.pubKey = network.getKeyPair()
end end
local identifier = options and options.identifier or Security.getIdentifier() 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.remotePubKey = Util.hexToByteArray(msg.pk)
socket.options = msg.options or { } socket.options = msg.options or { }
setupCrypto(socket, true) setupCrypto(socket, true)
_G.network.getTransport().open(socket) network.getTransport().open(socket)
return socket return socket
elseif msg.type == 'NOPASS' then 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 data and data.ts and tonumber(data.ts) then
if math.abs(os.epoch('utc') - data.ts) < 4096 then if math.abs(os.epoch('utc') - data.ts) < 4096 then
socket.remotePubKey = Util.hexToByteArray(data.pk) socket.remotePubKey = Util.hexToByteArray(data.pk)
socket.privKey, socket.pubKey = _G.network.getKeyPair() socket.privKey, socket.pubKey = network.getKeyPair()
setupCrypto(socket) setupCrypto(socket)
return true return true
end end
@@ -240,7 +241,7 @@ function Socket.server(port, options)
options = socket.options.ENCRYPT and { ENCRYPT = true }, options = socket.options.ENCRYPT and { ENCRYPT = true },
}) })
_G.network.getTransport().open(socket) network.getTransport().open(socket)
return socket return socket
else else

View File

@@ -33,10 +33,10 @@ function Terminal.window(parent, sx, sy, w, h, isVisible)
end end
local win = { } local win = { }
local maxScroll local maxScroll = 100
local cx, cy = 1, 1 local cx, cy = 1, 1
local blink = false local blink = false
local _bg, _fg = colors.black, colors.white local _bg, _fg = parent.getBackgroundColor(), parent.getTextColor()
win.canvas = Canvas({ win.canvas = Canvas({
x = sx, x = sx,
@@ -164,7 +164,7 @@ function Terminal.window(parent, sx, sy, w, h, isVisible)
win.canvas.lines[lines + i] = { } win.canvas.lines[lines + i] = { }
win.canvas:clearLine(lines + i) win.canvas:clearLine(lines + i)
end end
while #win.canvas.lines > (maxScroll or win.canvas.height) do while #win.canvas.lines > maxScroll do
table.remove(win.canvas.lines, 1) table.remove(win.canvas.lines, 1)
end end
scrollTo(#win.canvas.lines) scrollTo(#win.canvas.lines)
@@ -213,39 +213,8 @@ function Terminal.window(parent, sx, sy, w, h, isVisible)
end end
function win.reposition(x, y, width, height) function win.reposition(x, y, width, height)
if not maxScroll then win.canvas.x, win.canvas.y = x, y
win.canvas:move(x, y) win.canvas:resize(width or win.canvas.width, height or win.canvas.height)
win.canvas:resize(width or win.canvas.width, height or win.canvas.height)
return
end
-- special processing for scrolling terminal like windows
local delta = height - win.canvas.height
if delta > 0 then -- grow
for _ = 1, delta do
win.canvas.lines[#win.canvas.lines + 1] = { }
win.canvas:clearLine(#win.canvas.lines)
end
elseif delta < 0 then -- shrink
for _ = delta + 1, 0 do
if cy < win.canvas.height then
win.canvas.lines[#win.canvas.lines] = nil
else
cy = cy - 1
win.canvas.offy = win.canvas.offy + 1
end
end
end
win.canvas:resizeBuffer(width, #win.canvas.lines)
win.canvas.height = height
win.canvas.width = width
win.canvas:move(x, y)
update()
end end
--[[ Additional methods ]]-- --[[ Additional methods ]]--
@@ -277,79 +246,6 @@ function Terminal.window(parent, sx, sy, w, h, isVisible)
return parent return parent
end 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() win.canvas:clear()
return win return win

View File

@@ -12,10 +12,6 @@ local function traceback(x)
return x return x
end end
if x and x:match(':%d+: 0$') then
return x
end
if debug_traceback then if debug_traceback then
-- The parens are important, as they prevent a tail call occuring, meaning -- The parens are important, as they prevent a tail call occuring, meaning
-- the stack level is preserved. This ensures the code behaves identically -- the stack level is preserved. This ensures the code behaves identically
@@ -36,68 +32,78 @@ local function traceback(x)
end end
end end
local function trim_traceback(stack) local function trim_traceback(target, marker)
local trace = { } local ttarget, tmarker = {}, {}
local filters = { for line in target:gmatch("([^\n]*)\n?") do ttarget[#ttarget + 1] = line end
"%[C%]: in function 'xpcall'", for line in marker:gmatch("([^\n]*)\n?") do tmarker[#tmarker + 1] = line end
"(...tail calls...)",
"xpcall: $",
"trace.lua:%d+:",
"stack traceback:",
}
for line in stack:gmatch("([^\n]*)\n?") do table.insert(trace, line) end -- Trim identical suffixes
local t_len, m_len = #ttarget, #tmarker
local err = { } while t_len >= 3 and ttarget[t_len] == tmarker[m_len] do
while true do table.remove(ttarget, t_len)
local line = table.remove(trace, 1) t_len, m_len = t_len - 1, m_len - 1
if not line or line == 'stack traceback:' then
break
end
table.insert(err, line)
end
err = table.concat(err, '\n')
local function matchesFilter(line)
for _, filter in pairs(filters) do
if line:match(filter) then
return true
end
end
end end
local t = { } -- Trim elements from this file and xpcall invocations
for _, line in pairs(trace) do while t_len >= 1 and ttarget[t_len]:find("^\tstack_trace%.lua:%d+:") or
if not matchesFilter(line) then ttarget[t_len] == "\t[C]: in function 'xpcall'" or ttarget[t_len] == " xpcall: " do
line = line:gsub("in function", "in"):gsub('%w+/', '') table.remove(ttarget, t_len)
table.insert(t, line) t_len = t_len - 1
end
end end
return err, t ttarget[#ttarget] = nil -- remove 2 calls added by the added xpcall
ttarget[#ttarget] = nil
return ttarget
end end
--- Run a function with
return function (fn, ...) return function (fn, ...)
-- So this is rather grim: we need to get the full traceback and current one and remove
-- the common prefix
local trace
local args = { ... } local args = { ... }
-- xpcall in Lua 5.1 does not accept parameters
-- which is not ideal
local res = table.pack(xpcall(function() local res = table.pack(xpcall(function()
return fn(table.unpack(args)) return fn(table.unpack(args))
end, traceback)) end, traceback))
if not res[1] and res[2] ~= nil then if not res[1] then
local err, trace = trim_traceback(res[2]) trace = traceback("trace.lua:1:")
end
local ok, err = res[1], res[2]
if err:match(':%d+: 0$') then if not ok and err ~= nil then
return true 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 end
if #trace > 0 then for _, line in pairs(trace) do
_G._syslog('\n' .. err .. '\n' .. 'stack traceback:') _G._syslog(line)
for _, v in ipairs(trace) do end
_G._syslog(v)
-- If this traceback is more than 15 elements long, keep the first 9, last 5
-- and put an ellipsis between the rest
local max = 10
if trace_starts and #trace - trace_starts > max then
local keep_starts = trace_starts + 7
for i = #trace - trace_starts - max, 0, -1 do
table.remove(trace, keep_starts + i)
end end
table.insert(trace, keep_starts, " ...")
end end
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 end
return table.unpack(res, 1, res.n) return table.unpack(res, 1, res.n)

View File

@@ -37,14 +37,13 @@ local textutils = _G.textutils
local UI = { } local UI = { }
function UI:init() function UI:init()
self.devices = { } self.devices = { }
self.theme = { self.theme = { }
colors = { self.extChars = Util.getVersion() >= 1.76
primary = colors.green, self.colors = {
secondary = colors.lightGray, primary = colors.green,
tertiary = colors.gray, secondary = colors.lightGray,
} tertiary = colors.gray,
} }
self.extChars = Util.supportsExtChars()
local function keyFunction(event, code, held) local function keyFunction(event, code, held)
local ie = Input:translate(event, code, held) local ie = Input:translate(event, code, held)
@@ -75,11 +74,11 @@ function UI:init()
term_resize = resize, term_resize = resize,
monitor_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 ie = Input:translate('mouse_scroll', direction, x, y)
local currentPage = self:getActivePage() local currentPage = self:getActivePage()
if currentPage and currentPage.parent.device.side == side then if currentPage then
local event = currentPage:pointToChild(x, y) local event = currentPage:pointToChild(x, y)
event.type = ie.code event.type = ie.code
event.ie = { code = ie.code, x = event.x, y = event.y } event.ie = { code = ie.code, x = event.x, y = event.y }
@@ -97,41 +96,45 @@ function UI:init()
end end
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 ie = Input:translate('mouse_click', button, x, y)
local currentPage = self:getActivePage() local currentPage = self:getActivePage()
if currentPage and currentPage.parent.device.side == side then if currentPage then
local event = currentPage:pointToChild(x, y) if not currentPage.parent.device.side then
if event.element.focus and not event.element.inactive then local event = currentPage:pointToChild(x, y)
currentPage:setFocus(event.element) if event.element.focus and not event.element.inactive then
currentPage:sync() currentPage:setFocus(event.element)
currentPage:sync()
end
self:click(currentPage, ie)
end end
self:click(currentPage, ie)
end end
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 ie = Input:translate('mouse_up', button, x, y)
local currentPage = self:getActivePage() local currentPage = self:getActivePage()
if ie.code == 'control-shift-mouse_click' then -- hack if ie.code == 'control-shift-mouse_click' then -- hack
local event = currentPage:pointToChild(x, y) local event = currentPage:pointToChild(x, y)
_ENV.multishell.openTab(_ENV, { _ENV.multishell.openTab({
path = 'sys/apps/Lua.lua', path = 'sys/apps/Lua.lua',
args = { event.element, self, _ENV }, args = { event.element },
focused = true }) focused = true })
elseif ie and currentPage and currentPage.parent.device.side == side then elseif ie and currentPage then
self:click(currentPage, ie) if not currentPage.parent.device.side then
self:click(currentPage, ie)
end
end 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 ie = Input:translate('mouse_drag', button, x, y)
local currentPage = self:getActivePage() local currentPage = self:getActivePage()
if ie and currentPage and currentPage.parent.device.side == side then if ie and currentPage then
self:click(currentPage, ie) self:click(currentPage, ie)
end end
end, end,
@@ -203,10 +206,6 @@ function UI:loadTheme(filename)
end end
Util.deepMerge(self.theme, theme) Util.deepMerge(self.theme, theme)
end end
for k,v in pairs(self.theme.colors) do
Canvas.colorPalette[k] = Canvas.colorPalette[v]
Canvas.grayscalePalette[k] = Canvas.grayscalePalette[v]
end
end end
function UI:generateTheme(filename) function UI:generateTheme(filename)
@@ -676,7 +675,7 @@ function UI.Window:drawChildren()
end end
UI.Window.docs.getDoc = [[getDoc(STRING method) UI.Window.docs.getDoc = [[getDoc(STRING method)
Get the documentation for a method.]] Gets the documentation for a method.]]
function UI.Window:getDoc(method) function UI.Window:getDoc(method)
local m = getmetatable(self) -- get the class for this instance local m = getmetatable(self) -- get the class for this instance
repeat repeat
@@ -744,8 +743,6 @@ function UI.Window:clear(bg, fg)
Canvas.clear(self, bg or self:getProperty('backgroundColor'), fg or self:getProperty('textColor')) Canvas.clear(self, bg or self:getProperty('backgroundColor'), fg or self:getProperty('textColor'))
end end
UI.Window.docs.clearLine = [[clearLine(NUMBER y, opt COLOR bg)
Clears the specified line.]]
function UI.Window:clearLine(y, bg) function UI.Window:clearLine(y, bg)
self:write(1, y, _rep(' ', self.width), bg) self:write(1, y, _rep(' ', self.width), bg)
end end
@@ -763,10 +760,6 @@ function UI.Window:fillArea(x, y, width, height, fillChar, bg, fg)
end end
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) 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')) Canvas.write(self, x, y, text, bg or self:getProperty('backgroundColor'), fg or self:getProperty('textColor'))
end end
@@ -921,13 +914,6 @@ function UI.Window:addTransition(effect, args, canvas)
self.parent:addTransition(effect, args, canvas or self) self.parent:addTransition(effect, args, canvas or self)
end 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) function UI.Window:emit(event)
local parent = self local parent = self
while parent do while parent do
@@ -989,7 +975,6 @@ function UI.Device:postInit()
self.device.setTextScale = function() end self.device.setTextScale = function() end
end end
self._obg = term.getBackgroundColor()
self.device.setTextScale(self.textScale) self.device.setTextScale(self.textScale)
self.width, self.height = self.device.getSize() self.width, self.height = self.device.getSize()
self.isColor = self.device.isColor() self.isColor = self.device.isColor()
@@ -1026,7 +1011,8 @@ function UI.Device:setTextScale(textScale)
end end
function UI.Device:reset() function UI.Device:reset()
self.device.setBackgroundColor(self._obg) self.device.setBackgroundColor(colors.black)
self.device.setTextColor(colors.white)
self.device.clear() self.device.clear()
self.device.setCursorPos(1, 1) self.device.setCursorPos(1, 1)
end end
@@ -1126,6 +1112,13 @@ end
loadComponents() loadComponents()
UI:loadTheme('usr/config/ui.theme') 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()) 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 return UI

View File

@@ -361,8 +361,7 @@ function Canvas:__renderLayers(device, offset, doubleBuffer)
y = region[2] - offset.y, y = region[2] - offset.y,
ex = region[3] - offset.x, ex = region[3] - offset.x,
ey = region[4] - offset.y }, ey = region[4] - offset.y },
{ x = region[1], y = region[2] }, { x = region[1], y = region[2] }, doubleBuffer)
doubleBuffer)
end end
self.regions = nil self.regions = nil

View File

@@ -19,8 +19,6 @@ UI.Button.defaults = {
[ ' ' ] = 'button_activate', [ ' ' ] = 'button_activate',
enter = 'button_activate', enter = 'button_activate',
mouse_click = 'button_activate', mouse_click = 'button_activate',
mouse_doubleclick = 'button_activate',
mouse_tripleclick = 'button_activate',
} }
} }
function UI.Button:layout() function UI.Button:layout()

View File

@@ -12,11 +12,10 @@ UI.Checkbox.defaults = {
textColor = 'white', textColor = 'white',
backgroundColor = 'black', backgroundColor = 'black',
backgroundFocusColor = 'lightGray', backgroundFocusColor = 'lightGray',
event = 'checkbox_change',
height = 1, height = 1,
width = 3, width = 3,
accelerators = { accelerators = {
[ ' ' ] = 'checkbox_toggle', space = 'checkbox_toggle',
mouse_click = 'checkbox_toggle', mouse_click = 'checkbox_toggle',
} }
} }
@@ -53,7 +52,7 @@ end
function UI.Checkbox:eventHandler(event) function UI.Checkbox:eventHandler(event)
if event.type == 'checkbox_toggle' then if event.type == 'checkbox_toggle' then
self.value = not self.value 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() self:draw()
return true return true
end end

View File

@@ -11,12 +11,11 @@ end
UI.CheckboxGrid = class(UI.Grid) UI.CheckboxGrid = class(UI.Grid)
UI.CheckboxGrid.defaults = { UI.CheckboxGrid.defaults = {
UIElement = 'CheckboxGrid', UIElement = 'CheckboxGrid',
checkedKey = 'checked', checkedKey = 'checked',
accelerators = { accelerators = {
[ ' ' ] = 'grid_toggle', space = 'grid_toggle',
key_enter = 'grid_toggle', },
},
} }
function UI.CheckboxGrid:drawRow(sb, row, focused, bg, fg) function UI.CheckboxGrid:drawRow(sb, row, focused, bg, fg)
local ind = focused and self.focusIndicator or ' ' local ind = focused and self.focusIndicator or ' '
@@ -32,7 +31,7 @@ function UI.CheckboxGrid:drawRow(sb, row, focused, bg, fg)
end end
function UI.CheckboxGrid:eventHandler(event) 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.selected.checked = not self.selected.checked
self:draw() self:draw()
self:emit({ type = 'grid_check', checked = self.selected, element = self }) self:emit({ type = 'grid_check', checked = self.selected, element = self })

View File

@@ -15,7 +15,7 @@ UI.Chooser.defaults = {
rightIndicator = UI.extChars and '\187' or '>', rightIndicator = UI.extChars and '\187' or '>',
height = 1, height = 1,
accelerators = { accelerators = {
[ ' ' ] = 'choice_next', space = 'choice_next',
right = 'choice_next', right = 'choice_next',
left = 'choice_prev', left = 'choice_prev',
} }
@@ -40,7 +40,7 @@ function UI.Chooser:draw()
local value = choice and choice.name or self.nochoice local value = choice and choice.name or self.nochoice
self:write(1, 1, self.leftIndicator, self.backgroundColor, colors.black) 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) self:write(self.width, 1, self.rightIndicator, self.backgroundColor, colors.black)
end end
@@ -54,7 +54,7 @@ function UI.Chooser:eventHandler(event)
local choice local choice
if not k then k = 0 end if not k then k = 0 end
if k and k < #self.choices then if k and k < #self.choices then
choice = self.choices[k + 1] choice = self.choices[k+1]
else else
choice = self.choices[1] choice = self.choices[1]
end end
@@ -62,12 +62,11 @@ function UI.Chooser:eventHandler(event)
self:emit({ type = 'choice_change', value = self.value, element = self, choice = choice }) self:emit({ type = 'choice_change', value = self.value, element = self, choice = choice })
self:draw() self:draw()
return true return true
elseif event.type == 'choice_prev' then elseif event.type == 'choice_prev' then
local _,k = Util.find(self.choices, 'value', self.value) local _,k = Util.find(self.choices, 'value', self.value)
local choice local choice
if k and k > 1 then if k and k > 1 then
choice = self.choices[k - 1] choice = self.choices[k-1]
else else
choice = self.choices[#self.choices] choice = self.choices[#self.choices]
end end
@@ -75,7 +74,6 @@ function UI.Chooser:eventHandler(event)
self:emit({ type = 'choice_change', value = self.value, element = self, choice = choice }) self:emit({ type = 'choice_change', value = self.value, element = self, choice = choice })
self:draw() self:draw()
return true return true
elseif event.type == 'mouse_click' or event.type == 'mouse_doubleclick' then elseif event.type == 'mouse_click' or event.type == 'mouse_doubleclick' then
if event.x == 1 then if event.x == 1 then
self:emit({ type = 'choice_prev' }) self:emit({ type = 'choice_prev' })

View File

@@ -1,74 +1,86 @@
local class = require('opus.class') local class = require('opus.class')
local UI = require('opus.ui') local UI = require('opus.ui')
local Util = require('opus.util') local Util = require('opus.util')
local fs = _G.fs local colors = _G.colors
local fs = _G.fs
UI.FileSelect = class(UI.Window) UI.FileSelect = class(UI.Window)
UI.FileSelect.defaults = { UI.FileSelect.defaults = {
UIElement = 'FileSelect', UIElement = 'FileSelect',
} }
function UI.FileSelect:postInit() function UI.FileSelect:postInit()
self.grid = UI.ScrollingGrid { self.grid = UI.ScrollingGrid {
x = 2, y = 2, ex = -2, ey = -4, x = 2, y = 2, ex = -2, ey = -4,
dir = '/', dir = '/',
sortColumn = 'name', sortColumn = 'name',
columns = { columns = {
{ heading = 'Name', key = 'name' }, { heading = 'Name', key = 'name' },
{ heading = 'Size', key = 'size', width = 5 } { heading = 'Size', key = 'size', width = 5 }
}, },
getDisplayValues = function(_, row) getDisplayValues = function(_, row)
return { if row.size then
name = row.name, row = Util.shallowCopy(row)
size = row.size and Util.toBytes(row.size), row.size = Util.toBytes(row.size)
} end
end, return row
getRowTextColor = function(_, file) end,
return file.isDir and 'cyan' or file.isReadOnly and 'pink' or 'white' getRowTextColor = function(_, file)
end, if file.isDir then
sortCompare = function(self, a, b) return colors.cyan
if self.sortColumn == 'size' then end
return a.size < b.size if file.isReadOnly then
end return colors.pink
if a.isDir == b.isDir then end
return a.name:lower() < b.name:lower() return colors.white
end end,
return a.isDir sortCompare = function(self, a, b)
end, if self.sortColumn == 'size' then
draw = function(self) return a.size < b.size
local files = fs.listEx(self.dir) end
if #self.dir > 0 then if a.isDir == b.isDir then
table.insert(files, { return a.name:lower() < b.name:lower()
name = '..', end
isDir = true, return a.isDir
}) end,
end draw = function(self)
self:setValues(files) local files = fs.listEx(self.dir)
self:setIndex(1) if #self.dir > 0 then
UI.Grid.draw(self) table.insert(files, {
end, name = '..',
} isDir = true,
self.path = UI.TextEntry { })
x = 2, y = -2, ex = -11, end
accelerators = { self:setValues(files)
enter = 'path_enter', self:setIndex(1)
} UI.Grid.draw(self)
} end,
self.cancel = UI.Button { }
x = -9, y = -2, self.path = UI.TextEntry {
text = 'Cancel', x = 2,
event = 'select_cancel', y = -2,
} ex = -11,
limit = 256,
accelerators = {
enter = 'path_enter',
}
}
self.cancel = UI.Button {
text = 'Cancel',
x = -9,
y = -2,
event = 'select_cancel',
}
end end
function UI.FileSelect:draw() function UI.FileSelect:draw()
self:fillArea(1, 1, self.width, self.height, string.rep('\127', self.width), 'black', 'gray') self:fillArea(1, 1, self.width, self.height, string.rep('\127', self.width), colors.black, colors.gray)
self:drawChildren() self:drawChildren()
end end
function UI.FileSelect:enable(path) function UI.FileSelect:enable(path)
self:setPath(path or '') self:setPath(path or '')
UI.Window.enable(self) UI.Window.enable(self)
end end
function UI.FileSelect:setPath(path) function UI.FileSelect:setPath(path)
@@ -86,21 +98,21 @@ function UI.FileSelect:eventHandler(event)
if event.selected.isDir then if event.selected.isDir then
self.grid:draw() self.grid:draw()
self.path:draw() self.path:draw()
else else
self:emit({ type = 'select_file', file = '/' .. self.path.value, element = self }) self:emit({ type = 'select_file', file = '/' .. self.path.value, element = self })
end end
return true return true
elseif event.type == 'path_enter' then elseif event.type == 'path_enter' then
if self.path.value then if self.path.value then
if fs.isDir(self.path.value) then if fs.isDir(self.path.value) then
self:setPath(self.path.value) self:setPath(self.path.value)
self.grid:draw() self.grid:draw()
self.path:draw() self.path:draw()
else else
self:emit({ type = 'select_file', file = '/' .. self.path.value, element = self }) self:emit({ type = 'select_file', file = '/' .. self.path.value, element = self })
end end
end end
return true return true
end end
end end

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