1
0
mirror of https://github.com/kepler155c/opus synced 2025-05-02 07:24:13 +00:00

move multishell functionality to kernel

This commit is contained in:
kepler155c@gmail.com 2018-01-10 16:46:37 -05:00
parent 13ec8ea04f
commit d224f5df25
20 changed files with 467 additions and 409 deletions

View File

@ -58,33 +58,37 @@ local function nextUID()
return Event.uid - 1 return Event.uid - 1
end end
function Event.on(event, fn) function Event.on(events, fn)
events = type(events) == 'table' and events or { events }
local handler = setmetatable({
uid = nextUID(),
event = events,
fn = fn,
}, { __index = Routine })
for _,event in pairs(events) do
local handlers = Event.types[event] local handlers = Event.types[event]
if not handlers then if not handlers then
handlers = { } handlers = { }
Event.types[event] = handlers Event.types[event] = handlers
end end
local handler = {
uid = nextUID(),
event = event,
fn = fn,
}
handlers[handler.uid] = handler handlers[handler.uid] = handler
setmetatable(handler, { __index = Routine }) end
return handler return handler
end end
function Event.off(h) function Event.off(h)
if h and h.event then if h and h.event then
Event.types[h.event][h.uid] = nil for _,event in pairs(h.event) do
Event.types[event][h.uid] = nil
end
end end
end end
local function addTimer(interval, recurring, fn) local function addTimer(interval, recurring, fn)
local timerId = os.startTimer(interval) local timerId = os.startTimer(interval)
local handler local handler
@ -133,20 +137,18 @@ function Event.waitForEvent(event, timeout)
end end
function Event.addRoutine(fn) function Event.addRoutine(fn)
local r = { local r = setmetatable({
co = coroutine.create(fn), co = coroutine.create(fn),
uid = nextUID() uid = nextUID()
} }, { __index = Routine })
setmetatable(r, { __index = Routine })
Event.routines[r.uid] = r
Event.routines[r.uid] = r
r:resume() r:resume()
return r return r
end end
function Event.pullEvents(...) function Event.pullEvents(...)
for _, fn in ipairs({ ... }) do for _, fn in ipairs({ ... }) do
Event.addRoutine(fn) Event.addRoutine(fn)
end end
@ -162,7 +164,6 @@ function Event.exitPullEvents()
end end
local function processHandlers(event) local function processHandlers(event)
local handlers = Event.types[event] local handlers = Event.types[event]
if handlers then if handlers then
for _,h in pairs(handlers) do for _,h in pairs(handlers) do
@ -184,7 +185,6 @@ local function tokeys(t)
end end
local function processRoutines(...) local function processRoutines(...)
local keys = tokeys(Event.routines) local keys = tokeys(Event.routines)
for _,key in ipairs(keys) do for _,key in ipairs(keys) do
local r = Event.routines[key] local r = Event.routines[key]
@ -200,7 +200,6 @@ function Event.processEvent(e)
end end
function Event.pullEvent(eventType) function Event.pullEvent(eventType)
while true do while true do
local e = { os.pullEventRaw() } local e = { os.pullEventRaw() }

View File

@ -1,5 +1,6 @@
local Util = require('util') local Util = require('util')
local keyboard = _G.device.keyboard
local keys = _G.keys local keys = _G.keys
local os = _G.os local os = _G.os
@ -10,28 +11,31 @@ local modifiers = Util.transpose {
} }
local input = { local input = {
pressed = { }, state = { },
} }
function input:modifierPressed() function input:modifierPressed()
return self.pressed[keys.leftCtrl] or return keyboard.state[keys.leftCtrl] or
self.pressed[keys.rightCtrl] or keyboard.state[keys.rightCtrl] or
self.pressed[keys.leftAlt] or keyboard.state[keys.leftAlt] or
self.pressed[keys.rightAlt] keyboard.state[keys.rightAlt]
end end
function input:toCode(ch, code) function input:toCode(ch, code)
local result = { } local result = { }
if self.pressed[keys.leftCtrl] or self.pressed[keys.rightCtrl] then if keyboard.state[keys.leftCtrl] or keyboard.state[keys.rightCtrl] or
code == keys.leftCtrl or code == keys.rightCtrl then
table.insert(result, 'control') table.insert(result, 'control')
end end
if self.pressed[keys.leftAlt] or self.pressed[keys.rightAlt] then if keyboard.state[keys.leftAlt] or keyboard.state[keys.rightAlt] or
code == keys.leftAlt or code == keys.rightAlt then
table.insert(result, 'alt') table.insert(result, 'alt')
end end
if self.pressed[keys.leftShift] or self.pressed[keys.rightShift] then if keyboard.state[keys.leftShift] or keyboard.state[keys.rightShift] or
code == keys.leftShift or code == keys.rightShift then
if code and modifiers[code] then if code and modifiers[code] then
table.insert(result, 'shift') table.insert(result, 'shift')
elseif #ch == 1 then elseif #ch == 1 then
@ -48,7 +52,7 @@ function input:toCode(ch, code)
end end
function input:reset() function input:reset()
self.pressed = { } self.state = { }
self.fired = nil self.fired = nil
self.timer = nil self.timer = nil
@ -64,7 +68,7 @@ function input:translate(event, code, p1, p2)
return input:toCode(keys.getName(code), code) return input:toCode(keys.getName(code), code)
end end
else else
self.pressed[code] = true self.state[code] = true
if self:modifierPressed() and not modifiers[code] or code == 57 then if self:modifierPressed() and not modifiers[code] or code == 57 then
self.fired = true self.fired = true
return input:toCode(keys.getName(code), code) return input:toCode(keys.getName(code), code)
@ -81,18 +85,18 @@ function input:translate(event, code, p1, p2)
elseif event == 'key_up' then elseif event == 'key_up' then
if not self.fired then if not self.fired then
if self.pressed[code] then if self.state[code] then
self.fired = true self.fired = true
local ch = input:toCode(keys.getName(code), code) local ch = input:toCode(keys.getName(code), code)
self.pressed[code] = nil self.state[code] = nil
return ch return ch
end end
end end
self.pressed[code] = nil self.state[code] = nil
elseif event == 'paste' then elseif event == 'paste' then
self.pressed[keys.leftCtrl] = nil --self.state[keys.leftCtrl] = nil
self.pressed[keys.rightCtrl] = nil --self.state[keys.rightCtrl] = nil
self.fired = true self.fired = true
return input:toCode('paste', 255) return input:toCode('paste', 255)

View File

@ -68,6 +68,7 @@ local function encodeCommon(val, pretty, tabLevel, tTracking)
str = str .. encodeCommon(v, pretty, tabLevel, tTracking) str = str .. encodeCommon(v, pretty, tabLevel, tTracking)
end) end)
else else
debug(val)
arrEncoding(val, "{", "}", pairs, function(k,v) arrEncoding(val, "{", "}", pairs, function(k,v)
assert(type(k) == "string", "JSON object keys must be strings", 2) assert(type(k) == "string", "JSON object keys must be strings", 2)
str = str .. encodeCommon(k, pretty, tabLevel, tTracking) str = str .. encodeCommon(k, pretty, tabLevel, tTracking)
@ -94,6 +95,13 @@ function json.encodePretty(val)
return encodeCommon(val, true, 0, {}) return encodeCommon(val, true, 0, {})
end end
function json.encodeToFile(path, val)
local file = io.open(path, "w")
assert(file, "Unable to open file")
file:write(json.encodePretty(val))
file:close()
end
------------------------------------------------------------------ decoding ------------------------------------------------------------------ decoding
local decodeControls = {} local decodeControls = {}

View File

@ -2,6 +2,8 @@ local Event = require('event')
local Socket = require('socket') local Socket = require('socket')
local Util = require('util') local Util = require('util')
local os = _G.os
local Peripheral = Util.shallowCopy(_G.peripheral) local Peripheral = Util.shallowCopy(_G.peripheral)
function Peripheral.getList() function Peripheral.getList()
@ -176,12 +178,12 @@ local function getProxy(pi)
if proxy.type == 'monitor' then if proxy.type == 'monitor' then
Event.addRoutine(function() Event.addRoutine(function()
while true do while true do
local event = socket:read() local data = socket:read()
if not event then if not data then
break break
end end
if not Util.empty(event) then if data.fn and data.fn == 'event' then
os.queueEvent(table.unpack(event)) os.queueEvent(table.unpack(data.data))
end end
end end
end) end)

View File

@ -43,26 +43,23 @@ end
--[[-- Top Level Manager --]]-- --[[-- Top Level Manager --]]--
local Manager = class() local Manager = class()
function Manager:init() function Manager:init()
local running = false local function keyFunction(event, code, held)
local ch = Input:translate(event, code, held)
-- single thread all input events if ch and self.currentPage then
local function singleThread(event, fn) local target = self.currentPage.focused or self.currentPage
Event.on(event, function(_, ...) self:inputEvent(target,
if not running then { type = 'key', key = ch, element = target })
running = true self.currentPage:sync()
fn(...)
running = false
else
Input:translate(event, ...)
end end
end)
end end
Event.on('multishell_focus', function() local handlers = {
Input:reset() char = keyFunction,
end) key_up = keyFunction,
key = keyFunction,
singleThread('term_resize', function(side) term_resize = function(_, side)
if self.currentPage then if self.currentPage then
-- the parent doesn't have any children set... -- the parent doesn't have any children set...
-- that's why we have to resize both the parent and the current page -- that's why we have to resize both the parent and the current page
@ -75,9 +72,9 @@ function Manager:init()
self.currentPage:sync() self.currentPage:sync()
end end
end end
end) end,
singleThread('mouse_scroll', function(direction, x, y) mouse_scroll = function(_, direction, x, y)
if self.currentPage then if self.currentPage then
local event = self.currentPage:pointToChild(x, y) local event = self.currentPage:pointToChild(x, y)
local directions = { local directions = {
@ -90,18 +87,20 @@ function Manager:init()
{ type = 'key', key = directions[direction] }) { type = 'key', key = directions[direction] })
self.currentPage:sync() self.currentPage:sync()
end end
end) end,
-- this should be moved to the device ! -- this should be moved to the device !
singleThread('monitor_touch', function(side, x, y) monitor_touch = function(_, side, x, y)
Input:translate('mouse_click', 1, x, y)
local ch = Input:translate('mouse_up', 1, x, y)
if self.currentPage then if self.currentPage then
if self.currentPage.parent.device.side == side then if self.currentPage.parent.device.side == side then
self:click('mouse_click', 1, x, y) self:click(ch, 1, x, y)
end end
end end
end) end,
singleThread('mouse_click', function(button, x, y) mouse_click = function(_, button, x, y)
Input:translate('mouse_click', button, x, y) Input:translate('mouse_click', button, x, y)
if self.currentPage then if self.currentPage then
@ -113,9 +112,9 @@ function Manager:init()
end end
end end
end end
end) end,
singleThread('mouse_up', function(button, x, y) mouse_up = function(_, button, x, y)
local ch = Input:translate('mouse_up', button, x, y) local ch = Input:translate('mouse_up', button, x, y)
if ch == 'control-shift-mouse_click' then -- hack if ch == 'control-shift-mouse_click' then -- hack
@ -130,9 +129,9 @@ function Manager:init()
self:click(ch, button, x, y) self:click(ch, button, x, y)
end end
end end
end) end,
singleThread('mouse_drag', function(button, x, y) mouse_drag = function(_, button, x, y)
local ch = Input:translate('mouse_drag', button, x, y) local ch = Input:translate('mouse_drag', button, x, y)
if ch and self.currentPage then if ch and self.currentPage then
local event = self.currentPage:pointToChild(x, y) local event = self.currentPage:pointToChild(x, y)
@ -140,28 +139,23 @@ function Manager:init()
self:inputEvent(event.element, event) self:inputEvent(event.element, event)
self.currentPage:sync() self.currentPage:sync()
end end
end) end,
singleThread('paste', function(text) paste = function(_, text)
Input:translate('paste') Input:translate('paste')
self:emitEvent({ type = 'paste', text = text }) self:emitEvent({ type = 'paste', text = text })
self.currentPage:sync() self.currentPage:sync()
end,
}
-- use 1 handler to single thread all events
Event.on({
'char', 'key_up', 'key', 'term_resize',
'mouse_scroll', 'monitor_touch', 'mouse_click',
'mouse_up', 'mouse_drag', 'paste' },
function(event, ...)
handlers[event](event, ...)
end) end)
local function keyFunction(event, code, held)
local ch = Input:translate(event, code, held)
if ch and self.currentPage then
local target = self.currentPage.focused or self.currentPage
self:inputEvent(target,
{ type = 'key', key = ch, element = target })
self.currentPage:sync()
end
end
singleThread('char', function(code, held) keyFunction('char', code, held) end)
singleThread('key_up', function(code, held) keyFunction('key_up', code, held) end)
singleThread('key', function(code, held) keyFunction('key', code, held) end)
end end
function Manager:configure(appName, ...) function Manager:configure(appName, ...)

View File

@ -460,6 +460,8 @@ function Util.toBytes(n)
if not tonumber(n) then error('Util.toBytes: n must be a number', 2) end if not tonumber(n) then error('Util.toBytes: n must be a number', 2) end
if n >= 1000000 or n <= -1000000 then if n >= 1000000 or n <= -1000000 then
return string.format('%sM', math.floor(n/1000000 * 10) / 10) return string.format('%sM', math.floor(n/1000000 * 10) / 10)
elseif n >= 10000 or n <= -10000 then
return string.format('%sK', math.floor(n/1000))
elseif n >= 1000 or n <= -1000 then elseif n >= 1000 or n <= -1000 then
return string.format('%sK', math.floor(n/1000 * 10) / 10) return string.format('%sK', math.floor(n/1000 * 10) / 10)
end end
@ -549,7 +551,6 @@ end
-- end word wrapping -- end word wrapping
function Util.wordWrap(str, limit) function Util.wordWrap(str, limit)
local longLines = Util.split(str) local longLines = Util.split(str)
local lines = { } local lines = { }
@ -560,6 +561,23 @@ function Util.wordWrap(str, limit)
return lines return lines
end end
function Util.args(arg)
local tab = { remainder = { } }
local k = 1
while k <= #arg do
local v = arg[k]
if string.sub(v, 1, 1) == '-' then
local jopt = string.sub(v, 2)
tab[ jopt ] = arg[ k + 1 ]
k = k + 1
else
table.insert(tab.remainder, v)
end
k = k + 1
end
return tab
end
-- http://lua-users.org/wiki/AlternativeGetOpt -- http://lua-users.org/wiki/AlternativeGetOpt
local function getopt( arg, options ) local function getopt( arg, options )
local tab = {} local tab = {}

View File

@ -6,11 +6,12 @@ end
_G.requireInjector() _G.requireInjector()
local Config = require('config') local Config = require('config')
local Input = require('input')
local Util = require('util') local Util = require('util')
local colors = _G.colors local colors = _G.colors
local fs = _G.fs local fs = _G.fs
local kernel = _G.kernel
local keyboard = _G.device.keyboard
local keys = _G.keys local keys = _G.keys
local multishell = _ENV.multishell local multishell = _ENV.multishell
local os = _G.os local os = _G.os
@ -28,8 +29,6 @@ local overviewId
local runningTab local runningTab
local tabsDirty = false local tabsDirty = false
local closeInd = '*' local closeInd = '*'
local hooks = { }
local hotkeys = { }
local downState = { } local downState = { }
multishell.term = term.current() multishell.term = term.current()
@ -124,11 +123,11 @@ local function selectTab(tab)
if currentTab and currentTab ~= tab then if currentTab and currentTab ~= tab then
currentTab.window.setVisible(false) currentTab.window.setVisible(false)
if coroutine.status(currentTab.co) == 'suspended' then --if coroutine.status(currentTab.co) == 'suspended' then
-- the process that opens a new tab won't get the lose focus event -- the process that opens a new tab won't get the lose focus event
-- os.queueEvent('multishell_notifyfocus', currentTab.tabId) -- os.queueEvent('multishell_notifyfocus', currentTab.tabId)
--resumeTab(currentTab, 'multishell_losefocus') --resumeTab(currentTab, 'multishell_losefocus')
end --end
if tab and not currentTab.hidden then if tab and not currentTab.hidden then
tab.previousTabId = currentTab.tabId tab.previousTabId = currentTab.tabId
end end
@ -137,7 +136,9 @@ local function selectTab(tab)
if tab ~= currentTab then if tab ~= currentTab then
currentTab = tab currentTab = tab
tab.window.setVisible(true) tab.window.setVisible(true)
resumeTab(tab, 'multishell_focus') Util.clear(keyboard.state) --- reset keyboard state
-- why not just queue event with tab ID if we want to notify
--resumeTab(tab, 'multishell_focus')
end end
end end
@ -197,14 +198,6 @@ local function launchProcess(tab)
return tab return tab
end end
function multishell.addHotkey(code, fn)
hotkeys[code] = fn
end
function multishell.removeHotkey(code)
hotkeys[code] = nil
end
function multishell.getFocus() function multishell.getFocus()
return currentTab.tabId return currentTab.tabId
end end
@ -309,31 +302,7 @@ function multishell.getCount()
return Util.size(tabs) return Util.size(tabs)
end end
function multishell.hook(event, fn) kernel.hook('multishell_terminate', function(_, eventData)
if type(event) == 'table' then
for _,v in pairs(event) do
multishell.hook(v, fn)
end
else
if not hooks[event] then
hooks[event] = { }
end
table.insert(hooks[event], fn)
end
end
-- you can only unhook from within the function that hooked
function multishell.unhook(event, fn)
local eventHooks = hooks[event]
if eventHooks then
Util.removeByValue(eventHooks, fn)
if #eventHooks == 0 then
hooks[event] = nil
end
end
end
multishell.hook('multishell_terminate', function(_, eventData)
local tabId = eventData[1] or -1 local tabId = eventData[1] or -1
local tab = tabs[tabId] local tab = tabs[tabId]
@ -345,7 +314,7 @@ multishell.hook('multishell_terminate', function(_, eventData)
return true return true
end) end)
multishell.hook('multishell_redraw', function() kernel.hook('multishell_redraw', function()
tabsDirty = false tabsDirty = false
local function write(x, text, bg, fg) local function write(x, text, bg, fg)
@ -413,7 +382,7 @@ multishell.hook('multishell_redraw', function()
return true return true
end) end)
multishell.hook('term_resize', function(_, eventData) kernel.hook('term_resize', function(_, eventData)
if not eventData[1] then --- TEST if not eventData[1] then --- TEST
w,h = parentTerm.getSize() w,h = parentTerm.getSize()
@ -433,40 +402,16 @@ multishell.hook('term_resize', function(_, eventData)
end end
end) end)
-- downstate should be stored in the tab (maybe) --[[
multishell.hook('key_up', function(_, eventData) kernel.hook('key_up', function(_, eventData)
local code = eventData[1] local code = eventData[1]
if not keyboard.state[code] then
if downState[code] ~= currentTab then
downState[code] = nil
return true return true
end end
downState[code] = nil
end) end)
]]
multishell.hook('key', function(_, eventData) kernel.hook('mouse_click', function(_, eventData)
local code = eventData[1]
local firstPress = not eventData[2]
if firstPress then
downState[code] = currentTab
else
--key was pressed initially in a previous window
if downState[code] ~= currentTab then
return true
end
end
end)
multishell.hook({ 'key', 'key_up', 'char', 'paste' }, function(event, eventData)
local code = Input:translate(event, eventData[1], eventData[2])
if code and hotkeys[code] then
hotkeys[code](event, eventData)
end
end)
multishell.hook('mouse_click', function(_, eventData)
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
@ -492,7 +437,7 @@ multishell.hook('mouse_click', function(_, eventData)
eventData[3] = eventData[3] - 1 eventData[3] = eventData[3] - 1
end) end)
multishell.hook({ 'mouse_up', 'mouse_drag' }, function(event, eventData) kernel.hook({ 'mouse_up', 'mouse_drag' }, function(event, eventData)
if downState.mouse ~= currentTab then if downState.mouse ~= currentTab then
-- don't send mouse up as the mouse click event was on another window -- don't send mouse up as the mouse click event was on another window
if event == 'mouse_up' then if event == 'mouse_up' then
@ -504,7 +449,7 @@ multishell.hook({ 'mouse_up', 'mouse_drag' }, function(event, eventData)
eventData[3] = eventData[3] - 1 eventData[3] = eventData[3] - 1
end) end)
multishell.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
@ -592,7 +537,7 @@ while true do
local sEvent = table.remove(tEventData, 1) local sEvent = table.remove(tEventData, 1)
local stopPropagation local stopPropagation
local eventHooks = hooks[sEvent] local eventHooks = kernel.hooks[sEvent]
if eventHooks then if eventHooks then
for i = #eventHooks, 1, -1 do for i = #eventHooks, 1, -1 do
stopPropagation = eventHooks[i](sEvent, tEventData) stopPropagation = eventHooks[i](sEvent, tEventData)

View File

@ -5,21 +5,25 @@ local Socket = require('socket')
local Terminal = require('terminal') local Terminal = require('terminal')
local Util = require('util') local Util = require('util')
local multishell = _ENV.multishell
local os = _G.os local os = _G.os
local read = _G.read local read = _G.read
local term = _G.term local term = _G.term
local remoteId local args = Util.args({ ... })
local args = { ... }
if #args == 1 then local remoteId = tonumber(table.remove(args.remainder, 1))
remoteId = tonumber(args[1]) if not remoteId then
else
print('Enter host ID') print('Enter host ID')
remoteId = tonumber(read()) remoteId = tonumber(read())
end end
if not remoteId then if not remoteId then
error('Syntax: telnet <host ID>') error('Syntax: telnet [-title TITLE] ID [PROGRAM]')
end
if args.title then
multishell.setTitle(multishell.getCurrent(), args.title)
end end
print('connecting...') print('connecting...')
@ -39,6 +43,7 @@ socket:write({
width = w, width = w,
height = h, height = h,
isColor = ct.isColor(), isColor = ct.isColor(),
program = args.remainder,
}) })
Event.addRoutine(function() Event.addRoutine(function()
@ -48,7 +53,7 @@ Event.addRoutine(function()
break break
end end
for _,v in ipairs(data) do for _,v in ipairs(data) do
ct[v.f](unpack(v.args)) ct[v.f](table.unpack(v.args))
end end
end end
end) end)

View File

@ -2,16 +2,17 @@ _G.requireInjector()
local Util = require('util') local Util = require('util')
local multishell = _ENV.multishell local kernel = _G.kernel
local keyboard = _G.device.keyboard
local textutils = _G.textutils local textutils = _G.textutils
local data local data
multishell.hook('clipboard_copy', function(_, args) kernel.hook('clipboard_copy', function(_, args)
data = args[1] data = args[1]
end) end)
multishell.addHotkey('shift-paste', function(_, args) keyboard.addHotkey('shift-paste', function(_, args)
if type(data) == 'table' then if type(data) == 'table' then
local s, m = pcall(textutils.serialize, data) local s, m = pcall(textutils.serialize, data)
data = (s and m) or Util.tostring(data) data = (s and m) or Util.tostring(data)

View File

@ -2,10 +2,11 @@ _G.requireInjector()
local Util = require('util') local Util = require('util')
local keyboard = _G.device.keyboard
local multishell = _ENV.multishell local multishell = _ENV.multishell
-- overview -- overview
multishell.addHotkey('control-o', function() keyboard.addHotkey('control-o', function()
for _,tab in pairs(multishell.getTabs()) do for _,tab in pairs(multishell.getTabs()) do
if tab.isOverview then if tab.isOverview then
multishell.setFocus(tab.tabId) multishell.setFocus(tab.tabId)
@ -14,7 +15,7 @@ multishell.addHotkey('control-o', function()
end) end)
-- restart tab -- restart tab
multishell.addHotkey('control-backspace', function() keyboard.addHotkey('control-backspace', function()
local tabs = multishell.getTabs() local tabs = multishell.getTabs()
local tabId = multishell.getFocus() local tabId = multishell.getFocus()
local tab = tabs[tabId] local tab = tabs[tabId]
@ -28,7 +29,7 @@ multishell.addHotkey('control-backspace', function()
end) end)
-- next tab -- next tab
multishell.addHotkey('control-tab', function() keyboard.addHotkey('control-tab', function()
local tabs = multishell.getTabs() local tabs = multishell.getTabs()
local visibleTabs = { } local visibleTabs = { }
local currentTabId = multishell.getFocus() local currentTabId = multishell.getFocus()

View File

@ -2,7 +2,6 @@
local colors = _G.colors local colors = _G.colors
local fs = _G.fs local fs = _G.fs
local http = _G.http local http = _G.http
local shell = _ENV.shell
local term = _G.term local term = _G.term
local w, h = term.getSize() local w, h = term.getSize()
@ -40,6 +39,7 @@ local sandboxEnv = setmetatable({ }, { __index = _G })
for k,v in pairs(_ENV) do for k,v in pairs(_ENV) do
sandboxEnv[k] = v sandboxEnv[k] = v
end end
sandboxEnv.multishell = _ENV.multishell or { }
local function makeEnv() local function makeEnv()
local env = setmetatable({ }, { __index = _G }) local env = setmetatable({ }, { __index = _G })
@ -86,44 +86,7 @@ else
fs.mount('', 'gitfs', GIT_REPO) fs.mount('', 'gitfs', GIT_REPO)
end end
local Util = run('sys/apis/util.lua') run('sys/kernel.lua')
-- user environment
if not fs.exists('usr/apps') then
fs.makeDir('usr/apps')
end
if not fs.exists('usr/autorun') then
fs.makeDir('usr/autorun')
end
if not fs.exists('usr/etc/fstab') then
Util.writeFile('usr/etc/fstab', 'usr gitfs kepler155c/opus-apps/develop')
end
if not fs.exists('usr/config/shell') then
Util.writeTable('usr/config/shell', {
aliases = shell.aliases(),
path = 'usr/apps:sys/apps:' .. shell.path(),
lua_path = '/sys/apis:/usr/apis',
})
end
-- shell environment
local config = Util.readTable('usr/config/shell')
if config.aliases then
for k in pairs(shell.aliases()) do
shell.clearAlias(k)
end
for k,v in pairs(config.aliases) do
shell.setAlias(k, v)
end
end
shell.setPath(config.path)
sandboxEnv.LUA_PATH = config.lua_path
-- extensions
local dir = 'sys/extensions'
for _,file in ipairs(fs.list(dir)) do
run('sys/apps/shell', fs.combine(dir, file))
end
-- install user file systems -- install user file systems
fs.loadTab('usr/etc/fstab') fs.loadTab('usr/etc/fstab')

View File

@ -4,8 +4,88 @@ local Peripheral = require('peripheral')
_G.device = Peripheral.getList() _G.device = Peripheral.getList()
-- register the main term in the devices list
_G.device.terminal = _G.term.current() _G.device.terminal = _G.term.current()
_G.device.terminal.side = 'terminal' _G.device.terminal.side = 'terminal'
_G.device.terminal.type = 'terminal' _G.device.terminal.type = 'terminal'
_G.device.terminal.name = 'terminal' _G.device.terminal.name = 'terminal'
_G.device.keyboard = {
side = 'keyboard',
type = 'keyboard',
name = 'keyboard',
hotkeys = { },
state = { },
}
_G.device.mouse = {
side = 'mouse',
type = 'mouse',
name = 'mouse',
state = { },
}
local Input = require('input')
local Util = require('util')
local device = _G.device
local kernel = _G.kernel
local keyboard = _G.device.keyboard
local os = _G.os
kernel.hook('peripheral', function(_, eventData)
local side = eventData[1]
if side then
local dev = Peripheral.addDevice(device, side)
if dev then
os.queueEvent('device_attach', dev.name)
end
end
end)
kernel.hook('peripheral_detach', function(_, eventData)
local side = eventData[1]
if side then
local dev = Util.find(device, 'side', side)
if dev then
os.queueEvent('device_detach', dev.name)
device[dev.name] = nil
end
end
end)
kernel.hook({ 'key', 'key_up', 'char', 'paste' }, function(event, eventData)
local code = eventData[1]
-- maintain global keyboard state
if event == 'key' then
keyboard.state[code] = true
elseif event == 'key_up' then
if not keyboard.state[code] then
return true -- ensure key ups are only generated if a key down was sent
end
keyboard.state[code] = nil
end
-- and fire hotkeys
local hotkey = Input:translate(event, eventData[1], eventData[2])
if hotkey and keyboard.hotkeys[hotkey] then
keyboard.hotkeys[hotkey](event, eventData)
end
end)
function keyboard.addHotkey(code, fn)
keyboard.hotkeys[code] = fn
end
function keyboard.removeHotkey(code)
keyboard.hotkeys[code] = nil
end
kernel.hook('monitor_touch', function(event, eventData)
local monitor = Peripheral.getBySide(eventData[1])
if monitor and monitor.eventChannel then
monitor.eventChannel(event, table.unpack(eventData))
return true -- stop propagation
end
end)

View File

@ -0,0 +1,16 @@
local kernel = _G.kernel
local multishell = _ENV.multishell
_G.network = { }
kernel.hook('device_attach', function(_, eventData)
if eventData[1] == 'wireless_modem' then
local s, m = multishell.openTab({
path = 'sys/services/network.lua',
hidden = true
})
if not s and m then
debug(m)
end
end
end)

83
sys/kernel.lua Normal file
View File

@ -0,0 +1,83 @@
local sandboxEnv = setmetatable({ }, { __index = _G })
for k,v in pairs(_ENV) do
sandboxEnv[k] = v
end
_G.requireInjector()
local Util = require('util')
_G.kernel = {
hooks = { }
}
local kernel = _G.kernel
local fs = _G.fs
local shell = _ENV.shell
-- user environment
if not fs.exists('usr/apps') then
fs.makeDir('usr/apps')
end
if not fs.exists('usr/autorun') then
fs.makeDir('usr/autorun')
end
if not fs.exists('usr/etc/fstab') then
Util.writeFile('usr/etc/fstab', 'usr gitfs kepler155c/opus-apps/develop')
end
if not fs.exists('usr/config/shell') then
Util.writeTable('usr/config/shell', {
aliases = shell.aliases(),
path = 'usr/apps:sys/apps:' .. shell.path(),
lua_path = '/sys/apis:/usr/apis',
})
end
-- shell environment
local config = Util.readTable('usr/config/shell')
if config.aliases then
for k in pairs(shell.aliases()) do
shell.clearAlias(k)
end
for k,v in pairs(config.aliases) do
shell.setAlias(k, v)
end
end
shell.setPath(config.path)
_G.LUA_PATH = config.lua_path
-- any function that runs in a kernel hook does not run in
-- a separate coroutine or have a window. an error in a hook
-- function will crash the system.
function kernel.hook(event, fn)
if type(event) == 'table' then
for _,v in pairs(event) do
kernel.hook(v, fn)
end
else
if not kernel.hooks[event] then
kernel.hooks[event] = { }
end
table.insert(kernel.hooks[event], fn)
end
end
-- you can only unhook from within the function that hooked
function kernel.unhook(event, fn)
local eventHooks = kernel.hooks[event]
if eventHooks then
Util.removeByValue(eventHooks, fn)
if #eventHooks == 0 then
kernel.hooks[event] = nil
end
end
end
-- extensions
local dir = 'sys/extensions'
for _,file in ipairs(fs.list(dir)) do
local s, m = Util.run(sandboxEnv, 'sys/apps/shell', fs.combine(dir, file))
if not s then
error(m)
end
end

View File

@ -5,7 +5,6 @@
local Event = require('event') local Event = require('event')
local Peripheral = require('peripheral') local Peripheral = require('peripheral')
local Socket = require('socket') local Socket = require('socket')
local Util = require('util')
Event.addRoutine(function() Event.addRoutine(function()
print('peripheral: listening on port 189') print('peripheral: listening on port 189')
@ -19,6 +18,8 @@ Event.addRoutine(function()
if uri then if uri then
local peripheral = Peripheral.lookup(uri) local peripheral = Peripheral.lookup(uri)
-- only 1 proxy of this device can happen at one time
-- need to prevent multiple shares
if not peripheral then if not peripheral then
print('peripheral: invalid peripheral ' .. uri) print('peripheral: invalid peripheral ' .. uri)
else else
@ -28,7 +29,7 @@ Event.addRoutine(function()
} }
if peripheral.blit then if peripheral.blit then
peripheral = Util.shallowCopy(peripheral) --peripheral = Util.shallowCopy(peripheral)
peripheral.fastBlit = function(data) peripheral.fastBlit = function(data)
for _,v in ipairs(data) do for _,v in ipairs(data) do
peripheral[v.fn](unpack(v.args)) peripheral[v.fn](unpack(v.args))
@ -47,12 +48,12 @@ Event.addRoutine(function()
socket:write(proxy) socket:write(proxy)
if proxy.type == 'monitor' then if proxy.type == 'monitor' then
local h peripheral.eventChannel = function(...)
h = Event.on('monitor_touch', function(...) socket:write({
if not socket:write({ ... }) then fn = 'event',
Event.off(h) data = { ... }
})
end end
end)
end end
while true do while true do
@ -61,9 +62,14 @@ Event.addRoutine(function()
print('peripheral: lost connection from ' .. socket.dhost) print('peripheral: lost connection from ' .. socket.dhost)
break break
end end
if peripheral[data.fn] then
socket:write({ peripheral[data.fn](table.unpack(data.args)) }) socket:write({ peripheral[data.fn](table.unpack(data.args)) })
end end
end end
peripheral.eventChannel = nil
peripheral.fastBlit = nil
end
end end
end) end)
end end

View File

@ -48,7 +48,7 @@ local function telnetHost(socket)
end end
local shellThread = Event.addRoutine(function() local shellThread = Event.addRoutine(function()
os.run(_ENV, 'sys/apps/shell') os.run(_ENV, 'sys/apps/shell', table.unpack(termInfo.program))
Event.exitPullEvents() Event.exitPullEvents()
end) end)

View File

@ -1,47 +0,0 @@
_G.requireInjector()
local Event = require('event')
local Peripheral = require('peripheral')
local Util = require('util')
local colors = _G.colors
local device = _G.device
local multishell = _ENV.multishell
local os = _G.os
local term = _G.term
multishell.setTitle(multishell.getCurrent(), 'Devices')
local attachColor = colors.green
local detachColor = colors.red
if not term.isColor() then
attachColor = colors.white
detachColor = colors.lightGray
end
Event.on('peripheral', function(_, side)
if side then
local dev = Peripheral.addDevice(device, side)
if dev then
term.setTextColor(attachColor)
Util.print('[%s] %s attached', dev.side, dev.name)
os.queueEvent('device_attach', dev.name)
end
end
end)
Event.on('peripheral_detach', function(_, side)
if side then
local dev = Util.find(device, 'side', side)
if dev then
term.setTextColor(detachColor)
Util.print('[%s] %s detached', dev.side, dev.name)
os.queueEvent('device_detach', dev.name)
device[dev.name] = nil
end
end
end)
print('waiting for peripheral changes')
Event.pullEvents()

View File

@ -3,6 +3,7 @@ _G.requireInjector()
local Terminal = require('terminal') local Terminal = require('terminal')
local Util = require('util') local Util = require('util')
local keyboard = _G.device.keyboard
local multishell = _ENV.multishell local multishell = _ENV.multishell
local os = _G.os local os = _G.os
local term = _G.term local term = _G.term
@ -25,7 +26,7 @@ end
print('Debug started') print('Debug started')
print('Press ^d to activate debug window') print('Press ^d to activate debug window')
multishell.addHotkey('control-d', function() keyboard.addHotkey('control-d', function()
local currentId = multishell.getFocus() local currentId = multishell.getFocus()
if currentId ~= tabId then if currentId ~= tabId then
previousId = currentId previousId = currentId
@ -40,4 +41,4 @@ os.pullEventRaw('terminate')
print('Debug stopped') print('Debug stopped')
_G.debug = function() end _G.debug = function() end
multishell.removeHotkey('control-d') keyboard.removeHotkey('control-d')

View File

@ -1,22 +1,22 @@
_G.requireInjector() _G.requireInjector()
local Event = require('event')
local Util = require('util') local Util = require('util')
local device = _G.device local device = _G.device
local fs = _G.fs local fs = _G.fs
local multishell = _ENV.multishell local multishell = _ENV.multishell
local network = _G.network
local os = _G.os local os = _G.os
local printError = _G.printError local printError = _G.printError
local network = { } if not device.wireless_modem then
_G.network = network return
end
multishell.setTitle(multishell.getCurrent(), 'Net Daemon') multishell.setTitle(multishell.getCurrent(), 'Net Daemon')
local function netUp() print('Net daemon started')
_G.requireInjector()
local Event = require('event')
for _,file in pairs(fs.list('sys/network')) do for _,file in pairs(fs.list('sys/network')) do
local fn, msg = Util.run(_ENV, 'sys/network/' .. file) local fn, msg = Util.run(_ENV, 'sys/network/' .. file)
@ -41,33 +41,5 @@ local function netUp()
Event.pullEvent('network_down') Event.pullEvent('network_down')
Util.clear(_G.network) Util.clear(_G.network)
end
print('Net daemon started')
local function startNetwork()
print('Starting network services')
local success, msg = Util.runFunction(
Util.shallowCopy(_ENV), netUp)
if not success and msg then
printError(msg)
end
print('Network services stopped')
end
if device.wireless_modem then
startNetwork()
else
print('No modem detected')
end
while true do
local _, deviceName = os.pullEvent('device_attach')
if deviceName == 'wireless_modem' then
startNetwork()
end
end
print('Net daemon stopped') print('Net daemon stopped')

View File

@ -21,6 +21,7 @@ _G.transport = transport
function transport.open(socket) function transport.open(socket)
transport.sockets[socket.sport] = socket transport.sockets[socket.sport] = socket
socket.activityTimer = os.clock()
end end
function transport.read(socket) function transport.read(socket)
@ -34,16 +35,18 @@ function transport.write(socket, data)
--debug('>> ' .. Util.tostring({ type = 'DATA', seq = socket.wseq })) --debug('>> ' .. Util.tostring({ type = 'DATA', seq = socket.wseq }))
socket.transmit(socket.dport, socket.dhost, data) socket.transmit(socket.dport, socket.dhost, data)
local timerId = os.startTimer(3) --local timerId = os.startTimer(3)
transport.timers[timerId] = socket --transport.timers[timerId] = socket
socket.timers[socket.wseq] = timerId --socket.timers[socket.wseq] = timerId
socket.wseq = socket.wseq + 1 socket.wseq = socket.wseq + 1
end end
function transport.ping(socket) function transport.ping(socket)
--debug('>> ' .. Util.tostring({ type = 'DATA', seq = socket.wseq })) --debug('>> ' .. Util.tostring({ type = 'DATA', seq = socket.wseq }))
if os.clock() - socket.activityTimer > 10 then
socket.activityTimer = os.clock()
socket.transmit(socket.dport, socket.dhost, { socket.transmit(socket.dport, socket.dhost, {
type = 'PING', type = 'PING',
seq = -1, seq = -1,
@ -53,6 +56,7 @@ function transport.ping(socket)
transport.timers[timerId] = socket transport.timers[timerId] = socket
socket.timers[-1] = timerId socket.timers[-1] = timerId
end end
end
function transport.close(socket) function transport.close(socket)
transport.sockets[socket.sport] = nil transport.sockets[socket.sport] = nil
@ -67,7 +71,7 @@ while true do
local socket = transport.timers[timerId] local socket = transport.timers[timerId]
if socket and socket.connected then if socket and socket.connected then
print('transport timeout - closing socket ' .. socket.sport) debug('transport timeout - closing socket ' .. socket.sport)
socket:close() socket:close()
transport.timers[timerId] = nil transport.timers[timerId] = nil
end end
@ -88,18 +92,21 @@ while true do
if ackTimerId then if ackTimerId then
os.cancelTimer(ackTimerId) os.cancelTimer(ackTimerId)
socket.timers[msg.seq] = nil socket.timers[msg.seq] = nil
socket.activityTimer = os.clock()
transport.timers[ackTimerId] = nil transport.timers[ackTimerId] = nil
end end
elseif msg.type == 'PING' then elseif msg.type == 'PING' then
socket.activityTimer = os.clock()
socket.transmit(socket.dport, socket.dhost, { socket.transmit(socket.dport, socket.dhost, {
type = 'ACK', type = 'ACK',
seq = msg.seq, seq = msg.seq,
}) })
elseif msg.type == 'DATA' and msg.data then elseif msg.type == 'DATA' and msg.data then
socket.activityTimer = os.clock()
if msg.seq ~= socket.rseq then if msg.seq ~= socket.rseq then
print('transport seq error - closing socket ' .. socket.sport) debug('transport seq error - closing socket ' .. socket.sport)
socket:close() socket:close()
else else
socket.rseq = socket.rseq + 1 socket.rseq = socket.rseq + 1
@ -111,10 +118,10 @@ while true do
end end
--debug('>> ' .. Util.tostring({ type = 'ACK', seq = msg.seq })) --debug('>> ' .. Util.tostring({ type = 'ACK', seq = msg.seq }))
socket.transmit(socket.dport, socket.dhost, { --socket.transmit(socket.dport, socket.dhost, {
type = 'ACK', -- type = 'ACK',
seq = msg.seq, -- seq = msg.seq,
}) --})
end end
end end
end end