diff --git a/sys/apis/event.lua b/sys/apis/event.lua index 9e8d90b..d177bae 100644 --- a/sys/apis/event.lua +++ b/sys/apis/event.lua @@ -58,33 +58,37 @@ local function nextUID() return Event.uid - 1 end -function Event.on(event, fn) +function Event.on(events, fn) + events = type(events) == 'table' and events or { events } - local handlers = Event.types[event] - if not handlers then - handlers = { } - Event.types[event] = handlers - end - - local handler = { + local handler = setmetatable({ uid = nextUID(), - event = event, + event = events, fn = fn, - } - handlers[handler.uid] = handler - setmetatable(handler, { __index = Routine }) + }, { __index = Routine }) + + for _,event in pairs(events) do + local handlers = Event.types[event] + if not handlers then + handlers = { } + Event.types[event] = handlers + end + + handlers[handler.uid] = handler + end return handler end function Event.off(h) 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 local function addTimer(interval, recurring, fn) - local timerId = os.startTimer(interval) local handler @@ -133,20 +137,18 @@ function Event.waitForEvent(event, timeout) end function Event.addRoutine(fn) - local r = { + local r = setmetatable({ co = coroutine.create(fn), uid = nextUID() - } - setmetatable(r, { __index = Routine }) - Event.routines[r.uid] = r + }, { __index = Routine }) + Event.routines[r.uid] = r r:resume() return r end function Event.pullEvents(...) - for _, fn in ipairs({ ... }) do Event.addRoutine(fn) end @@ -162,7 +164,6 @@ function Event.exitPullEvents() end local function processHandlers(event) - local handlers = Event.types[event] if handlers then for _,h in pairs(handlers) do @@ -184,7 +185,6 @@ local function tokeys(t) end local function processRoutines(...) - local keys = tokeys(Event.routines) for _,key in ipairs(keys) do local r = Event.routines[key] @@ -200,7 +200,6 @@ function Event.processEvent(e) end function Event.pullEvent(eventType) - while true do local e = { os.pullEventRaw() } diff --git a/sys/apis/input.lua b/sys/apis/input.lua index 1d6d3ba..9bb2f9f 100644 --- a/sys/apis/input.lua +++ b/sys/apis/input.lua @@ -1,7 +1,8 @@ local Util = require('util') -local keys = _G.keys -local os = _G.os +local keyboard = _G.device.keyboard +local keys = _G.keys +local os = _G.os local modifiers = Util.transpose { keys.leftCtrl, keys.rightCtrl, @@ -10,28 +11,31 @@ local modifiers = Util.transpose { } local input = { - pressed = { }, + state = { }, } function input:modifierPressed() - return self.pressed[keys.leftCtrl] or - self.pressed[keys.rightCtrl] or - self.pressed[keys.leftAlt] or - self.pressed[keys.rightAlt] + return keyboard.state[keys.leftCtrl] or + keyboard.state[keys.rightCtrl] or + keyboard.state[keys.leftAlt] or + keyboard.state[keys.rightAlt] end function input:toCode(ch, code) 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') 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') 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 table.insert(result, 'shift') elseif #ch == 1 then @@ -48,7 +52,7 @@ function input:toCode(ch, code) end function input:reset() - self.pressed = { } + self.state = { } self.fired = nil self.timer = nil @@ -64,7 +68,7 @@ function input:translate(event, code, p1, p2) return input:toCode(keys.getName(code), code) end else - self.pressed[code] = true + self.state[code] = true if self:modifierPressed() and not modifiers[code] or code == 57 then self.fired = true return input:toCode(keys.getName(code), code) @@ -81,18 +85,18 @@ function input:translate(event, code, p1, p2) elseif event == 'key_up' then if not self.fired then - if self.pressed[code] then + if self.state[code] then self.fired = true local ch = input:toCode(keys.getName(code), code) - self.pressed[code] = nil + self.state[code] = nil return ch end end - self.pressed[code] = nil + self.state[code] = nil elseif event == 'paste' then - self.pressed[keys.leftCtrl] = nil - self.pressed[keys.rightCtrl] = nil + --self.state[keys.leftCtrl] = nil + --self.state[keys.rightCtrl] = nil self.fired = true return input:toCode('paste', 255) @@ -147,4 +151,4 @@ function input:test() end end -return input +return input \ No newline at end of file diff --git a/sys/apis/json.lua b/sys/apis/json.lua index 6fd5e37..af65f80 100644 --- a/sys/apis/json.lua +++ b/sys/apis/json.lua @@ -68,6 +68,7 @@ local function encodeCommon(val, pretty, tabLevel, tTracking) str = str .. encodeCommon(v, pretty, tabLevel, tTracking) end) else +debug(val) arrEncoding(val, "{", "}", pairs, function(k,v) assert(type(k) == "string", "JSON object keys must be strings", 2) str = str .. encodeCommon(k, pretty, tabLevel, tTracking) @@ -94,6 +95,13 @@ function json.encodePretty(val) return encodeCommon(val, true, 0, {}) 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 local decodeControls = {} diff --git a/sys/apis/peripheral.lua b/sys/apis/peripheral.lua index 6e4e794..2e3b4d1 100644 --- a/sys/apis/peripheral.lua +++ b/sys/apis/peripheral.lua @@ -2,6 +2,8 @@ local Event = require('event') local Socket = require('socket') local Util = require('util') +local os = _G.os + local Peripheral = Util.shallowCopy(_G.peripheral) function Peripheral.getList() @@ -176,12 +178,12 @@ local function getProxy(pi) if proxy.type == 'monitor' then Event.addRoutine(function() while true do - local event = socket:read() - if not event then + local data = socket:read() + if not data then break end - if not Util.empty(event) then - os.queueEvent(table.unpack(event)) + if data.fn and data.fn == 'event' then + os.queueEvent(table.unpack(data.data)) end end end) diff --git a/sys/apis/ui.lua b/sys/apis/ui.lua index fc9f5d7..f12ec8b 100644 --- a/sys/apis/ui.lua +++ b/sys/apis/ui.lua @@ -43,111 +43,6 @@ end --[[-- Top Level Manager --]]-- local Manager = class() function Manager:init() - local running = false - - -- single thread all input events - local function singleThread(event, fn) - Event.on(event, function(_, ...) - if not running then - running = true - fn(...) - running = false - else - Input:translate(event, ...) - end - end) - end - - Event.on('multishell_focus', function() - Input:reset() - end) - - singleThread('term_resize', function(side) - if self.currentPage then - -- the parent doesn't have any children set... - -- that's why we have to resize both the parent and the current page - -- kinda makes sense - if self.currentPage.parent.device.side == side then - self.currentPage.parent:resize() - - self.currentPage:resize() - self.currentPage:draw() - self.currentPage:sync() - end - end - end) - - singleThread('mouse_scroll', function(direction, x, y) - if self.currentPage then - local event = self.currentPage:pointToChild(x, y) - local directions = { - [ -1 ] = 'up', - [ 1 ] = 'down' - } - -- revisit - should send out scroll_up and scroll_down events - -- let the element convert them to up / down - self:inputEvent(event.element, - { type = 'key', key = directions[direction] }) - self.currentPage:sync() - end - end) - - -- this should be moved to the device ! - singleThread('monitor_touch', function(side, x, y) - if self.currentPage then - if self.currentPage.parent.device.side == side then - self:click('mouse_click', 1, x, y) - end - end - end) - - singleThread('mouse_click', function(button, x, y) - Input:translate('mouse_click', button, x, y) - - if self.currentPage then - if not self.currentPage.parent.device.side then - local event = self.currentPage:pointToChild(x, y) - if event.element.focus and not event.element.inactive then - self.currentPage:setFocus(event.element) - self.currentPage:sync() - end - end - end - end) - - singleThread('mouse_up', function(button, x, y) - local ch = Input:translate('mouse_up', button, x, y) - - if ch == 'control-shift-mouse_click' then -- hack - local event = self.currentPage:pointToChild(x, y) - _ENV.multishell.openTab({ - path = 'sys/apps/Lua.lua', - args = { event.element }, - focused = true }) - - elseif ch and self.currentPage then - if not self.currentPage.parent.device.side then - self:click(ch, button, x, y) - end - end - end) - - singleThread('mouse_drag', function(button, x, y) - local ch = Input:translate('mouse_drag', button, x, y) - if ch and self.currentPage then - local event = self.currentPage:pointToChild(x, y) - event.type = ch - self:inputEvent(event.element, event) - self.currentPage:sync() - end - end) - - singleThread('paste', function(text) - Input:translate('paste') - self:emitEvent({ type = 'paste', text = text }) - self.currentPage:sync() - end) - local function keyFunction(event, code, held) local ch = Input:translate(event, code, held) @@ -159,9 +54,108 @@ function Manager:init() 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) + local handlers = { + char = keyFunction, + key_up = keyFunction, + key = keyFunction, + + term_resize = function(_, side) + if self.currentPage then + -- the parent doesn't have any children set... + -- that's why we have to resize both the parent and the current page + -- kinda makes sense + if self.currentPage.parent.device.side == side then + self.currentPage.parent:resize() + + self.currentPage:resize() + self.currentPage:draw() + self.currentPage:sync() + end + end + end, + + mouse_scroll = function(_, direction, x, y) + if self.currentPage then + local event = self.currentPage:pointToChild(x, y) + local directions = { + [ -1 ] = 'up', + [ 1 ] = 'down' + } + -- revisit - should send out scroll_up and scroll_down events + -- let the element convert them to up / down + self:inputEvent(event.element, + { type = 'key', key = directions[direction] }) + self.currentPage:sync() + end + end, + + -- this should be moved to the device ! + 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.parent.device.side == side then + self:click(ch, 1, x, y) + end + end + end, + + mouse_click = function(_, button, x, y) + Input:translate('mouse_click', button, x, y) + + if self.currentPage then + if not self.currentPage.parent.device.side then + local event = self.currentPage:pointToChild(x, y) + if event.element.focus and not event.element.inactive then + self.currentPage:setFocus(event.element) + self.currentPage:sync() + end + end + end + end, + + mouse_up = function(_, button, x, y) + local ch = Input:translate('mouse_up', button, x, y) + + if ch == 'control-shift-mouse_click' then -- hack + local event = self.currentPage:pointToChild(x, y) + _ENV.multishell.openTab({ + path = 'sys/apps/Lua.lua', + args = { event.element }, + focused = true }) + + elseif ch and self.currentPage then + if not self.currentPage.parent.device.side then + self:click(ch, button, x, y) + end + end + end, + + mouse_drag = function(_, button, x, y) + local ch = Input:translate('mouse_drag', button, x, y) + if ch and self.currentPage then + local event = self.currentPage:pointToChild(x, y) + event.type = ch + self:inputEvent(event.element, event) + self.currentPage:sync() + end + end, + + paste = function(_, text) + Input:translate('paste') + self:emitEvent({ type = 'paste', text = text }) + 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 function Manager:configure(appName, ...) diff --git a/sys/apis/util.lua b/sys/apis/util.lua index 47e9296..ee5e926 100644 --- a/sys/apis/util.lua +++ b/sys/apis/util.lua @@ -460,6 +460,8 @@ function Util.toBytes(n) if not tonumber(n) then error('Util.toBytes: n must be a number', 2) end if n >= 1000000 or n <= -1000000 then 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 return string.format('%sK', math.floor(n/1000 * 10) / 10) end @@ -549,7 +551,6 @@ end -- end word wrapping function Util.wordWrap(str, limit) - local longLines = Util.split(str) local lines = { } @@ -560,6 +561,23 @@ function Util.wordWrap(str, limit) return lines 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 local function getopt( arg, options ) local tab = {} diff --git a/sys/apps/multishell b/sys/apps/multishell index c0b24f7..e09ec09 100644 --- a/sys/apps/multishell +++ b/sys/apps/multishell @@ -6,11 +6,12 @@ end _G.requireInjector() local Config = require('config') -local Input = require('input') local Util = require('util') local colors = _G.colors local fs = _G.fs +local kernel = _G.kernel +local keyboard = _G.device.keyboard local keys = _G.keys local multishell = _ENV.multishell local os = _G.os @@ -28,8 +29,6 @@ local overviewId local runningTab local tabsDirty = false local closeInd = '*' -local hooks = { } -local hotkeys = { } local downState = { } multishell.term = term.current() @@ -124,11 +123,11 @@ local function selectTab(tab) if currentTab and currentTab ~= tab then 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 -- os.queueEvent('multishell_notifyfocus', currentTab.tabId) --resumeTab(currentTab, 'multishell_losefocus') - end + --end if tab and not currentTab.hidden then tab.previousTabId = currentTab.tabId end @@ -137,7 +136,9 @@ local function selectTab(tab) if tab ~= currentTab then currentTab = tab 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 @@ -197,14 +198,6 @@ local function launchProcess(tab) return tab end -function multishell.addHotkey(code, fn) - hotkeys[code] = fn -end - -function multishell.removeHotkey(code) - hotkeys[code] = nil -end - function multishell.getFocus() return currentTab.tabId end @@ -309,31 +302,7 @@ function multishell.getCount() return Util.size(tabs) end -function multishell.hook(event, fn) - 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) +kernel.hook('multishell_terminate', function(_, eventData) local tabId = eventData[1] or -1 local tab = tabs[tabId] @@ -345,7 +314,7 @@ multishell.hook('multishell_terminate', function(_, eventData) return true end) -multishell.hook('multishell_redraw', function() +kernel.hook('multishell_redraw', function() tabsDirty = false local function write(x, text, bg, fg) @@ -413,7 +382,7 @@ multishell.hook('multishell_redraw', function() return true end) -multishell.hook('term_resize', function(_, eventData) +kernel.hook('term_resize', function(_, eventData) if not eventData[1] then --- TEST w,h = parentTerm.getSize() @@ -433,40 +402,16 @@ multishell.hook('term_resize', function(_, eventData) 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] - - if downState[code] ~= currentTab then - downState[code] = nil + if not keyboard.state[code] then return true end - downState[code] = nil end) +]] -multishell.hook('key', 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) +kernel.hook('mouse_click', function(_, eventData) local x, y = eventData[2], eventData[3] if y == 1 then if x == 1 then @@ -492,7 +437,7 @@ multishell.hook('mouse_click', function(_, eventData) eventData[3] = eventData[3] - 1 end) -multishell.hook({ 'mouse_up', 'mouse_drag' }, function(event, eventData) +kernel.hook({ 'mouse_up', 'mouse_drag' }, function(event, eventData) if downState.mouse ~= currentTab then -- don't send mouse up as the mouse click event was on another window if event == 'mouse_up' then @@ -504,7 +449,7 @@ multishell.hook({ 'mouse_up', 'mouse_drag' }, function(event, eventData) eventData[3] = eventData[3] - 1 end) -multishell.hook('mouse_scroll', function(_, eventData) +kernel.hook('mouse_scroll', function(_, eventData) local dir, y = eventData[1], eventData[3] if y == 1 then @@ -592,7 +537,7 @@ while true do local sEvent = table.remove(tEventData, 1) local stopPropagation - local eventHooks = hooks[sEvent] + local eventHooks = kernel.hooks[sEvent] if eventHooks then for i = #eventHooks, 1, -1 do stopPropagation = eventHooks[i](sEvent, tEventData) diff --git a/sys/apps/telnet.lua b/sys/apps/telnet.lua index 20702ac..16ce3a6 100644 --- a/sys/apps/telnet.lua +++ b/sys/apps/telnet.lua @@ -5,21 +5,25 @@ local Socket = require('socket') local Terminal = require('terminal') local Util = require('util') -local os = _G.os -local read = _G.read -local term = _G.term +local multishell = _ENV.multishell +local os = _G.os +local read = _G.read +local term = _G.term -local remoteId -local args = { ... } -if #args == 1 then - remoteId = tonumber(args[1]) -else +local args = Util.args({ ... }) + +local remoteId = tonumber(table.remove(args.remainder, 1)) +if not remoteId then print('Enter host ID') remoteId = tonumber(read()) end if not remoteId then - error('Syntax: telnet ') + error('Syntax: telnet [-title TITLE] ID [PROGRAM]') +end + +if args.title then + multishell.setTitle(multishell.getCurrent(), args.title) end print('connecting...') @@ -39,6 +43,7 @@ socket:write({ width = w, height = h, isColor = ct.isColor(), + program = args.remainder, }) Event.addRoutine(function() @@ -48,7 +53,7 @@ Event.addRoutine(function() break end for _,v in ipairs(data) do - ct[v.f](unpack(v.args)) + ct[v.f](table.unpack(v.args)) end end end) diff --git a/sys/autorun/clipboard.lua b/sys/autorun/clipboard.lua index d20e3da..cec3f55 100644 --- a/sys/autorun/clipboard.lua +++ b/sys/autorun/clipboard.lua @@ -2,16 +2,17 @@ _G.requireInjector() local Util = require('util') -local multishell = _ENV.multishell -local textutils = _G.textutils +local kernel = _G.kernel +local keyboard = _G.device.keyboard +local textutils = _G.textutils local data -multishell.hook('clipboard_copy', function(_, args) +kernel.hook('clipboard_copy', function(_, args) data = args[1] end) -multishell.addHotkey('shift-paste', function(_, args) +keyboard.addHotkey('shift-paste', function(_, args) if type(data) == 'table' then local s, m = pcall(textutils.serialize, data) data = (s and m) or Util.tostring(data) diff --git a/sys/autorun/hotkeys.lua b/sys/autorun/hotkeys.lua index e37bd99..50ffb65 100644 --- a/sys/autorun/hotkeys.lua +++ b/sys/autorun/hotkeys.lua @@ -2,10 +2,11 @@ _G.requireInjector() local Util = require('util') +local keyboard = _G.device.keyboard local multishell = _ENV.multishell -- overview -multishell.addHotkey('control-o', function() +keyboard.addHotkey('control-o', function() for _,tab in pairs(multishell.getTabs()) do if tab.isOverview then multishell.setFocus(tab.tabId) @@ -14,7 +15,7 @@ multishell.addHotkey('control-o', function() end) -- restart tab -multishell.addHotkey('control-backspace', function() +keyboard.addHotkey('control-backspace', function() local tabs = multishell.getTabs() local tabId = multishell.getFocus() local tab = tabs[tabId] @@ -28,7 +29,7 @@ multishell.addHotkey('control-backspace', function() end) -- next tab -multishell.addHotkey('control-tab', function() +keyboard.addHotkey('control-tab', function() local tabs = multishell.getTabs() local visibleTabs = { } local currentTabId = multishell.getFocus() diff --git a/sys/boot/multishell.boot b/sys/boot/multishell.boot index dae20a1..9cf0e7e 100644 --- a/sys/boot/multishell.boot +++ b/sys/boot/multishell.boot @@ -2,7 +2,6 @@ local colors = _G.colors local fs = _G.fs local http = _G.http -local shell = _ENV.shell local term = _G.term local w, h = term.getSize() @@ -40,6 +39,7 @@ local sandboxEnv = setmetatable({ }, { __index = _G }) for k,v in pairs(_ENV) do sandboxEnv[k] = v end +sandboxEnv.multishell = _ENV.multishell or { } local function makeEnv() local env = setmetatable({ }, { __index = _G }) @@ -86,44 +86,7 @@ else fs.mount('', 'gitfs', GIT_REPO) end -local Util = run('sys/apis/util.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 +run('sys/kernel.lua') -- install user file systems fs.loadTab('usr/etc/fstab') diff --git a/sys/extensions/device.lua b/sys/extensions/device.lua index 4b9cfda..d1c7f3f 100644 --- a/sys/extensions/device.lua +++ b/sys/extensions/device.lua @@ -4,8 +4,88 @@ local Peripheral = require('peripheral') _G.device = Peripheral.getList() --- register the main term in the devices list _G.device.terminal = _G.term.current() _G.device.terminal.side = 'terminal' _G.device.terminal.type = '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) diff --git a/sys/extensions/network.lua b/sys/extensions/network.lua new file mode 100644 index 0000000..d6fce09 --- /dev/null +++ b/sys/extensions/network.lua @@ -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) diff --git a/sys/kernel.lua b/sys/kernel.lua new file mode 100644 index 0000000..e97a3d2 --- /dev/null +++ b/sys/kernel.lua @@ -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 diff --git a/sys/network/peripheral.lua b/sys/network/peripheral.lua index 1ec79cc..8f5e300 100644 --- a/sys/network/peripheral.lua +++ b/sys/network/peripheral.lua @@ -5,7 +5,6 @@ local Event = require('event') local Peripheral = require('peripheral') local Socket = require('socket') -local Util = require('util') Event.addRoutine(function() print('peripheral: listening on port 189') @@ -19,6 +18,8 @@ Event.addRoutine(function() if uri then 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 print('peripheral: invalid peripheral ' .. uri) else @@ -28,7 +29,7 @@ Event.addRoutine(function() } if peripheral.blit then - peripheral = Util.shallowCopy(peripheral) + --peripheral = Util.shallowCopy(peripheral) peripheral.fastBlit = function(data) for _,v in ipairs(data) do peripheral[v.fn](unpack(v.args)) @@ -47,12 +48,12 @@ Event.addRoutine(function() socket:write(proxy) if proxy.type == 'monitor' then - local h - h = Event.on('monitor_touch', function(...) - if not socket:write({ ... }) then - Event.off(h) - end - end) + peripheral.eventChannel = function(...) + socket:write({ + fn = 'event', + data = { ... } + }) + end end while true do @@ -61,8 +62,13 @@ Event.addRoutine(function() print('peripheral: lost connection from ' .. socket.dhost) break end - socket:write({ peripheral[data.fn](table.unpack(data.args)) }) + if peripheral[data.fn] then + socket:write({ peripheral[data.fn](table.unpack(data.args)) }) + end end + + peripheral.eventChannel = nil + peripheral.fastBlit = nil end end end) diff --git a/sys/network/telnet.lua b/sys/network/telnet.lua index 2b98db8..93b3567 100644 --- a/sys/network/telnet.lua +++ b/sys/network/telnet.lua @@ -48,7 +48,7 @@ local function telnetHost(socket) end local shellThread = Event.addRoutine(function() - os.run(_ENV, 'sys/apps/shell') + os.run(_ENV, 'sys/apps/shell', table.unpack(termInfo.program)) Event.exitPullEvents() end) diff --git a/sys/services/device.lua b/sys/services/device.lua deleted file mode 100644 index 5ea8941..0000000 --- a/sys/services/device.lua +++ /dev/null @@ -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() diff --git a/sys/services/log.lua b/sys/services/log.lua index 4702700..7284da6 100644 --- a/sys/services/log.lua +++ b/sys/services/log.lua @@ -3,6 +3,7 @@ _G.requireInjector() local Terminal = require('terminal') local Util = require('util') +local keyboard = _G.device.keyboard local multishell = _ENV.multishell local os = _G.os local term = _G.term @@ -25,7 +26,7 @@ end print('Debug started') print('Press ^d to activate debug window') -multishell.addHotkey('control-d', function() +keyboard.addHotkey('control-d', function() local currentId = multishell.getFocus() if currentId ~= tabId then previousId = currentId @@ -40,4 +41,4 @@ os.pullEventRaw('terminate') print('Debug stopped') _G.debug = function() end -multishell.removeHotkey('control-d') +keyboard.removeHotkey('control-d') diff --git a/sys/services/network.lua b/sys/services/network.lua index 7ba9dd0..98420b1 100644 --- a/sys/services/network.lua +++ b/sys/services/network.lua @@ -1,73 +1,45 @@ _G.requireInjector() -local Util = require('util') +local Event = require('event') +local Util = require('util') local device = _G.device local fs = _G.fs local multishell = _ENV.multishell +local network = _G.network local os = _G.os local printError = _G.printError -local network = { } -_G.network = network +if not device.wireless_modem then + return +end multishell.setTitle(multishell.getCurrent(), 'Net Daemon') -local function netUp() - _G.requireInjector() - - local Event = require('event') - - for _,file in pairs(fs.list('sys/network')) do - local fn, msg = Util.run(_ENV, 'sys/network/' .. file) - if not fn then - printError(msg) - end - end - - Event.on('device_detach', function() - if not device.wireless_modem then - Event.exitPullEvents() - end - end) - - Event.pullEvents() - - for _,c in pairs(network) do - c.active = false - os.queueEvent('network_detach', c) - end - os.queueEvent('network_down') - Event.pullEvent('network_down') - - 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 +for _,file in pairs(fs.list('sys/network')) do + local fn, msg = Util.run(_ENV, 'sys/network/' .. file) + if not fn 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() +Event.on('device_detach', function() + if not device.wireless_modem then + Event.exitPullEvents() end +end) + +Event.pullEvents() + +for _,c in pairs(network) do + c.active = false + os.queueEvent('network_detach', c) end +os.queueEvent('network_down') +Event.pullEvent('network_down') + +Util.clear(_G.network) print('Net daemon stopped') diff --git a/sys/services/transort.lua b/sys/services/transort.lua index 07ac754..c163509 100644 --- a/sys/services/transort.lua +++ b/sys/services/transort.lua @@ -21,6 +21,7 @@ _G.transport = transport function transport.open(socket) transport.sockets[socket.sport] = socket + socket.activityTimer = os.clock() end function transport.read(socket) @@ -34,24 +35,27 @@ function transport.write(socket, data) --debug('>> ' .. Util.tostring({ type = 'DATA', seq = socket.wseq })) socket.transmit(socket.dport, socket.dhost, data) - local timerId = os.startTimer(3) + --local timerId = os.startTimer(3) - transport.timers[timerId] = socket - socket.timers[socket.wseq] = timerId + --transport.timers[timerId] = socket + --socket.timers[socket.wseq] = timerId socket.wseq = socket.wseq + 1 end function transport.ping(socket) --debug('>> ' .. Util.tostring({ type = 'DATA', seq = socket.wseq })) - socket.transmit(socket.dport, socket.dhost, { - type = 'PING', - seq = -1, - }) + if os.clock() - socket.activityTimer > 10 then + socket.activityTimer = os.clock() + socket.transmit(socket.dport, socket.dhost, { + type = 'PING', + seq = -1, + }) - local timerId = os.startTimer(3) - transport.timers[timerId] = socket - socket.timers[-1] = timerId + local timerId = os.startTimer(3) + transport.timers[timerId] = socket + socket.timers[-1] = timerId + end end function transport.close(socket) @@ -67,7 +71,7 @@ while true do local socket = transport.timers[timerId] if socket and socket.connected then - print('transport timeout - closing socket ' .. socket.sport) + debug('transport timeout - closing socket ' .. socket.sport) socket:close() transport.timers[timerId] = nil end @@ -88,18 +92,21 @@ while true do if ackTimerId then os.cancelTimer(ackTimerId) socket.timers[msg.seq] = nil + socket.activityTimer = os.clock() transport.timers[ackTimerId] = nil end elseif msg.type == 'PING' then + socket.activityTimer = os.clock() socket.transmit(socket.dport, socket.dhost, { type = 'ACK', seq = msg.seq, }) elseif msg.type == 'DATA' and msg.data then + socket.activityTimer = os.clock() if msg.seq ~= socket.rseq then - print('transport seq error - closing socket ' .. socket.sport) + debug('transport seq error - closing socket ' .. socket.sport) socket:close() else socket.rseq = socket.rseq + 1 @@ -111,10 +118,10 @@ while true do end --debug('>> ' .. Util.tostring({ type = 'ACK', seq = msg.seq })) - socket.transmit(socket.dport, socket.dhost, { - type = 'ACK', - seq = msg.seq, - }) + --socket.transmit(socket.dport, socket.dhost, { + -- type = 'ACK', + -- seq = msg.seq, + --}) end end end