diff --git a/sys/apis/input.lua b/sys/apis/input.lua new file mode 100644 index 0000000..32c5f1f --- /dev/null +++ b/sys/apis/input.lua @@ -0,0 +1,129 @@ +local Util = require('util') + +local keys = _G.keys +local os = _G.os + +local modifiers = Util.transpose { + keys.leftCtrl, keys.rightCtrl, + keys.leftShift, keys.rightShift, + --keys.leftAlt, keys.rightAlt, +} + +local input = { + pressed = { }, +} + +function input:toCode(code) + + local ch = self.ch or keys.getName(code) + local result = { } + + if self.pressed[keys.leftCtrl] or self.pressed[keys.rightCtrl] then + table.insert(result, 'control') + end + + --if self.pressed[keys.leftAlt] or self.pressed[keys.rightAlt] then + -- table.insert(result, 'alt') + --end + + if self.pressed[keys.leftShift] or self.pressed[keys.rightShift] then + if modifiers[code] or #ch > 1 then + table.insert(result, 'shift') + else + ch = ch:upper() + end + end + + if not modifiers[code] then + table.insert(result, ch) + end + + return table.concat(result, '-') +end + +function input:reset() + self.pressed = { } + self.ch = nil + self.fired = nil + self.timer = nil +end + +function input:translate(event, code, p1, p2) + if event == 'key' then + if p1 then -- key is held down + if not modifiers[code] then + self.fired = input:toCode(code) + return self.fired + end + else + self.fired = nil + self.ch = nil + self.pressed[code] = true + end + + elseif event == 'char' then + self.ch = code + + elseif event == 'key_up' then + if not self.fired then + if self.pressed[code] then + self.fired = input:toCode(code) + self.pressed[code] = nil + return self.fired + end + end + + self.pressed[code] = nil + + elseif event == 'mouse_click' then + + local buttons = { 'mouse_click', 'mouse_rightclick' } + self.ch = buttons[code] + self.fired = nil + --self.fired = input:toCode(0) + --return self.fired + + elseif event == 'mouse_drag' then + self.ch = 'mouse_drag' + self.fired = input:toCode(0) + return self.fired + + elseif event == 'mouse_up' then + if not self.fired then + local clock = os.clock() + if self.timer and + p1 == self.x and p2 == self.y and + (clock - self.timer < .5) then + + self.ch = 'mouse_doubleclick' + self.timer = nil + else + self.timer = os.clock() + self.x = p1 + self.y = p2 + end + self.fired = input:toCode(0) + else + self.ch = 'mouse_up' + self.fired = input:toCode(0) + end + return self.fired + + elseif event == "mouse_scroll" then + local directions = { + [ -1 ] = 'scrollUp', + [ 1 ] = 'scrollDown' + } + self.ch = directions[code] + return input:toCode(0) + + elseif event == 'paste' then + self.ch = 'paste' + self.pressed[keys.leftCtrl] = nil + self.pressed[keys.rightCtrl] = nil + self.fired = input:toCode(0) + return self.fired + end +end + +return input diff --git a/sys/apis/ui.lua b/sys/apis/ui.lua index def7c3b..91bd3b4 100644 --- a/sys/apis/ui.lua +++ b/sys/apis/ui.lua @@ -1,6 +1,7 @@ local Canvas = require('ui.canvas') local class = require('class') local Event = require('event') +local Input = require('input') local Transition = require('ui.transition') local Util = require('util') @@ -43,9 +44,6 @@ end --[[-- Top Level Manager --]]-- local Manager = class() function Manager:init() - local control = false - local shift = false - local mouseDragged = false local running = false -- single thread all input events @@ -55,10 +53,16 @@ function Manager:init() 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... @@ -99,16 +103,9 @@ function Manager:init() end) singleThread('mouse_click', function(button, x, y) + Input:translate('mouse_click', button, x, y) - mouseDragged = false - if button == 1 and shift and control then -- debug hack - local event = self:pointToChild(self.target, x, y) - multishell.openTab({ - path = 'sys/apps/Lua.lua', - args = { event.element, self:dump(self.currentPage) }, - focused = true }) - - elseif self.currentPage then + if self.currentPage then if not self.currentPage.parent.device.side then local event = self:pointToChild(self.target, x, y) if event.element.focus and not event.element.inactive then @@ -120,75 +117,64 @@ function Manager:init() end) singleThread('mouse_up', function(button, x, y) + local ch = Input:translate('mouse_up', button, x, y) - if self.currentPage and not mouseDragged then + if ch == 'control-shift-mouse_click' then -- hack + local event = self:pointToChild(self.target, x, y) + multishell.openTab({ + path = 'sys/apps/Lua.lua', + args = { event.element, self:dump(self.currentPage) }, + focused = true }) + + elseif ch and self.currentPage then if not self.currentPage.parent.device.side then - self:click(button, x, y) + self:click(ch, button, x, y) end end end) singleThread('mouse_drag', function(button, x, y) - - mouseDragged = true - if self.target then + local ch = Input:translate('mouse_drag', button, x, y) + if ch and self.target then local event = self:pointToChild(self.target, x, y) -- revisit - should send out scroll_up and scroll_down events -- let the element convert them to up / down self:inputEvent(event.element, - { type = 'mouse_drag', button = button, x = event.x, y = event.y }) + { type = ch, button = button, x = event.x, y = event.y }) self.currentPage:sync() end end) singleThread('paste', function(text) + Input:translate('paste') self:emitEvent({ type = 'paste', text = text }) self.currentPage:sync() end) singleThread('char', function(ch) - control = false - if self.currentPage then + Input:translate('char', ch) + end) + + singleThread('key_up', function(code) + local ch = Input:translate('key_up', code) + + if ch and self.currentPage then local target = self.currentPage.focused or self.currentPage - self:inputEvent(target, { type = 'key', key = ch }) + self:inputEvent(target, + { type = 'key', key = ch, element = target }) self.currentPage:sync() end end) - singleThread('key_up', function(code) - if code == keys.leftCtrl or code == keys.rightCtrl then - control = false - elseif code == keys.leftShift or code == keys.rightShift then - shift = false - end - end) + singleThread('key', function(code, held) + local ch = Input:translate('key', code, held) - singleThread('key', function(code) - local ch = keys.getName(code) - if not ch then - return - end - - if code == keys.leftCtrl or code == keys.rightCtrl then - control = true - elseif code == keys.leftShift or code == keys.rightShift then - shift = true - elseif control then - ch = 'control-' .. ch - elseif shift and ch == 'tab' then - ch = 'shiftTab' - end - - -- filter out a through z and numbers as they will be get picked up - -- as char events - if ch and #ch > 1 and (code < 2 or code > 11) then - if self.currentPage then - local target = self.currentPage.focused or self.currentPage - self:inputEvent(target, - { type = 'key', key = ch, element = target }) - self.currentPage:sync() - end + 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) end @@ -306,7 +292,7 @@ function Manager:pointToChild(parent, x, y) } end -function Manager:click(button, x, y) +function Manager:click(code, button, x, y) if self.target then local target = self.target @@ -322,38 +308,23 @@ function Manager:click(button, x, y) local clickEvent = self:pointToChild(target, x, y) - if button == 1 then - local c = os.clock() - - --if self.doubleClickTimer then - -- debug(c - self.doubleClickTimer) - --end - - if self.doubleClickTimer and (c - self.doubleClickTimer < 1.9) and - self.doubleClickX == x and self.doubleClickY == y and - self.doubleClickElement == clickEvent.element then - button = 3 - self.doubleClickTimer = nil - else - self.doubleClickTimer = c - self.doubleClickX = x - self.doubleClickY = y - self.doubleClickElement = clickEvent.element + if code == 'mouse_doubleclick' then + if self.doubleClickElement ~= clickEvent.element then + return end else - self.doubleClickTimer = nil + self.doubleClickElement = clickEvent.element end - local events = { 'mouse_click', 'mouse_rightclick', 'mouse_doubleclick' } - clickEvent.button = button - clickEvent.type = events[button] - clickEvent.key = events[button] + clickEvent.type = code + clickEvent.key = code if clickEvent.element.focus then self.currentPage:setFocus(clickEvent.element) end if not self:inputEvent(clickEvent.element, clickEvent) then + --[[ if button == 3 then -- if the double-click was not captured -- send through a single-click @@ -362,6 +333,7 @@ function Manager:click(button, x, y) clickEvent.key = events[1] self:inputEvent(clickEvent.element, clickEvent) end + ]] end self.currentPage:sync() @@ -1152,7 +1124,7 @@ UI.Page.defaults = { down = 'focus_next', enter = 'focus_next', tab = 'focus_next', - shiftTab = 'focus_prev', + ['shift-tab' ] = 'focus_prev', up = 'focus_prev', }, backgroundColor = colors.cyan, diff --git a/sys/apps/multishell b/sys/apps/multishell index 3f334b7..00cfe1b 100644 --- a/sys/apps/multishell +++ b/sys/apps/multishell @@ -6,6 +6,7 @@ end _G.requireInjector() local Config = require('config') +local Input = require('input') local Opus = require('opus') local Util = require('util') @@ -27,9 +28,7 @@ local overviewTab local runningTab local tabsDirty = false local closeInd = '*' -local redrawTimer local hooks = { } -local control local hotkeys = { } local downState = { } @@ -49,7 +48,7 @@ if not os.getComputerLabel() then end end -if Util.getVersion() >= 1.79 then +if Util.getVersion() >= 1.76 then closeInd = '\215' end @@ -85,33 +84,6 @@ local function redrawMenu() end end -local function selectTab(tab) - if not tab then - for _,ftab in pairs(tabs) do - if not ftab.hidden then - tab = ftab - break - end - end - end - - if not tab then - tab = overviewTab - end - - if currentTab and currentTab ~= tab then - currentTab.window.setVisible(false) - if tab and not currentTab.hidden then - tab.previousTabId = currentTab.tabId - end - end - - if tab then - currentTab = tab - tab.window.setVisible(true) - end -end - local function resumeTab(tab, event, eventData) if not tab or coroutine.status(tab.co) == 'dead' then return @@ -136,6 +108,39 @@ local function resumeTab(tab, event, eventData) end end +local function selectTab(tab) + if not tab then + for _,ftab in pairs(tabs) do + if not ftab.hidden then + tab = ftab + break + end + end + end + + if not tab then + tab = overviewTab + end + + if currentTab and currentTab ~= tab then + currentTab.window.setVisible(false) + 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 + if tab and not currentTab.hidden then + tab.previousTabId = currentTab.tabId + end + end + + if tab ~= currentTab then + currentTab = tab + tab.window.setVisible(true) + resumeTab(tab, 'multishell_focus') + end +end + local function nextTabId() _tabId = _tabId + 1 return _tabId @@ -227,10 +232,10 @@ function multishell.getTitle(tabId) end end -function multishell.setTitle(tabId, sTitle) +function multishell.setTitle(tabId, title) local tab = tabs[tabId] if tab then - tab.title = sTitle or '' + tab.title = title or '' redrawMenu() end end @@ -332,10 +337,20 @@ function multishell.showMessage(text) text = text .. string.rep(' ', w - #text - 3) end parentTerm.write(text) - redrawTimer = os.startTimer(2) if currentTab then currentTab.window.restoreCursor() end + local redrawTimer = os.startTimer(2) + + local redraw + function redraw(event, eventData) + if eventData[1] == redrawTimer then + redrawMenu() + multishell.unhook(event, redraw) + return true + end + end + multishell.hook('timer', redraw) end function multishell.hook(event, fn) @@ -347,7 +362,18 @@ function multishell.hook(event, fn) if not hooks[event] then hooks[event] = { } end - table.insert(hooks[event], 1, fn) + 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 @@ -366,22 +392,29 @@ end) multishell.hook('multishell_redraw', function() tabsDirty = false - parentTerm.setBackgroundColor( _colors.tabBarBackgroundColor ) - if currentTab and currentTab.isOverview then - parentTerm.setTextColor( _colors.focusTextColor ) - else - parentTerm.setTextColor( _colors.tabBarTextColor ) + local function write(x, text, bg, fg) + parentTerm.setBackgroundColor(bg) + parentTerm.setTextColor(fg) + parentTerm.setCursorPos(x, 1) + parentTerm.write(text) end - parentTerm.setCursorPos( 1, 1 ) + + local bg = _colors.tabBarBackgroundColor + parentTerm.setBackgroundColor(bg) + parentTerm.setCursorPos(1, 1) parentTerm.clearLine() - parentTerm.write('+') + + if currentTab and currentTab.isOverview then + write(1, '+', bg, _colors.focusTextColor) + else + write(1, '+', bg, _colors.tabBarTextColor) + end local tabX = 2 local function compareTab(a, b) return a.tabId < b.tabId end for _,tab in Util.spairs(tabs, compareTab) do - if tab.hidden and tab ~= currentTab or tab.isOverview then tab.sx = nil tab.ex = nil @@ -394,21 +427,15 @@ multishell.hook('multishell_redraw', function() for _,tab in Util.spairs(tabs) do if tab.sx then if tab == currentTab then - parentTerm.setTextColor(_colors.focusTextColor) - parentTerm.setBackgroundColor(_colors.focusBackgroundColor) + write(tab.sx, tab.title, _colors.focusBackgroundColor, _colors.focusTextColor) else - parentTerm.setTextColor(_colors.textColor) - parentTerm.setBackgroundColor(_colors.backgroundColor) + write(tab.sx, tab.title, _colors.backgroundColor, _colors.textColor) end - parentTerm.setCursorPos(tab.sx, 1) - parentTerm.write(tab.title) end end + if currentTab and not currentTab.isOverview then - parentTerm.setTextColor(_colors.focusTextColor) - parentTerm.setBackgroundColor(_colors.backgroundColor) - parentTerm.setCursorPos( w, 1 ) - parentTerm.write(closeInd) + write(w, closeInd, _colors.backgroundColor, _colors.focusTextColor) end if currentTab then @@ -439,14 +466,11 @@ multishell.hook('term_resize', function(_, eventData) end end) --- downstate should be stored in the tab +-- downstate should be stored in the tab (maybe) multishell.hook('key_up', function(_, eventData) local code = eventData[1] - if code == keys.leftCtrl or code == keys.rightCtrl then - control = false - end if downState[code] ~= currentTab then downState[code] = nil return true @@ -454,25 +478,12 @@ multishell.hook('key_up', function(_, eventData) downState[code] = nil end) -multishell.hook('char', function() - control = false -- is this right ?? -end) - multishell.hook('key', function(_, eventData) local code = eventData[1] local firstPress = not eventData[2] if firstPress then downState[code] = currentTab - if code == keys.leftCtrl or code == keys.rightCtrl then - control = true - elseif control then - local hotkey = hotkeys[code] - --control = false - if hotkey then - hotkey() - end - end else --key was pressed initially in a previous window if downState[code] ~= currentTab then @@ -481,7 +492,15 @@ multishell.hook('key', function(_, eventData) end end) -multishell.hook({ 'mouse_click' }, function(_, eventData) +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] if y == 1 then if x == 1 then @@ -519,7 +538,7 @@ multishell.hook({ 'mouse_up', 'mouse_drag' }, function(event, eventData) eventData[3] = eventData[3] - 1 end) -multishell.hook({ 'mouse_scroll' }, function(_, eventData) +multishell.hook('mouse_scroll', function(_, eventData) local dir, y = eventData[1], eventData[3] if y == 1 then @@ -586,14 +605,10 @@ while true do local sEvent = table.remove(tEventData, 1) local stopPropagation - if sEvent == 'timer' and tEventData[1] == redrawTimer then - redrawMenu() - end - local eventHooks = hooks[sEvent] if eventHooks then - for _,fn in pairs(eventHooks) do - stopPropagation = fn(sEvent, tEventData) + for i = #eventHooks, 1, -1 do + stopPropagation = eventHooks[i](sEvent, tEventData) if stopPropagation then break end diff --git a/sys/autorun/clipboard.lua b/sys/autorun/clipboard.lua index fccf262..d20e3da 100644 --- a/sys/autorun/clipboard.lua +++ b/sys/autorun/clipboard.lua @@ -2,44 +2,20 @@ _G.requireInjector() local Util = require('util') -local keys = _G.keys local multishell = _ENV.multishell local textutils = _G.textutils -local clipboard = { } - -function clipboard.getText() - if clipboard.data then - if type(clipboard.data) == 'table' then - local s, m = pcall(textutils.serialize, clipboard.data) - clipboard.data = (s and m) or Util.tostring(clipboard.data) - end - return Util.tostring(clipboard.data) - end -end - -function clipboard.useInternal(mode) - if mode ~= clipboard.internal then - clipboard.internal = mode - local text = 'Clipboard (^m): ' .. ((mode and 'internal') or 'normal') - multishell.showMessage(text) - end -end +local data multishell.hook('clipboard_copy', function(_, args) - clipboard.data = args[1] - if clipboard.data then - clipboard.useInternal(true) - end + data = args[1] end) -multishell.hook('paste', function(_, args) - if clipboard.internal then - args[1] = clipboard.getText() or '' +multishell.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) end -end) - --- control-m - toggle clipboard mode -multishell.addHotkey(keys.m, function() - clipboard.useInternal(not clipboard.internal) + -- replace the event paste data with our internal data + args[1] = Util.tostring(data or '') end) diff --git a/sys/autorun/hotkeys.lua b/sys/autorun/hotkeys.lua index 24e1154..e37bd99 100644 --- a/sys/autorun/hotkeys.lua +++ b/sys/autorun/hotkeys.lua @@ -2,12 +2,10 @@ _G.requireInjector() local Util = require('util') -local keys = _G.keys - local multishell = _ENV.multishell --- control-o - overview -multishell.addHotkey(keys.o, function() +-- overview +multishell.addHotkey('control-o', function() for _,tab in pairs(multishell.getTabs()) do if tab.isOverview then multishell.setFocus(tab.tabId) @@ -15,8 +13,8 @@ multishell.addHotkey(keys.o, function() end end) --- control-backspace - restart tab -multishell.addHotkey(keys.backspace, function() +-- restart tab +multishell.addHotkey('control-backspace', function() local tabs = multishell.getTabs() local tabId = multishell.getFocus() local tab = tabs[tabId] @@ -29,8 +27,8 @@ multishell.addHotkey(keys.backspace, function() end end) --- control-tab - next tab -multishell.addHotkey(keys.tab, function() +-- next tab +multishell.addHotkey('control-tab', function() local tabs = multishell.getTabs() local visibleTabs = { } local currentTabId = multishell.getFocus() diff --git a/sys/services/log.lua b/sys/services/log.lua index 447539b..4702700 100644 --- a/sys/services/log.lua +++ b/sys/services/log.lua @@ -3,7 +3,6 @@ _G.requireInjector() local Terminal = require('terminal') local Util = require('util') -local keys = _G.keys local multishell = _ENV.multishell local os = _G.os local term = _G.term @@ -26,7 +25,7 @@ end print('Debug started') print('Press ^d to activate debug window') -multishell.addHotkey(keys.d, function() +multishell.addHotkey('control-d', function() local currentId = multishell.getFocus() if currentId ~= tabId then previousId = currentId @@ -41,4 +40,4 @@ os.pullEventRaw('terminate') print('Debug stopped') _G.debug = function() end -multishell.removeHotkey(keys.d) +multishell.removeHotkey('control-d')