diff --git a/mock/mocked-keybow.lua b/mock/mocked-keybow.lua index 86f6b8d..b3c3351 100644 --- a/mock/mocked-keybow.lua +++ b/mock/mocked-keybow.lua @@ -5,6 +5,7 @@ busted.stub(keybow, "auto_lights") busted.stub(keybow, "clear_lights") busted.stub(keybow, "load_pattern") busted.stub(keybow, "set_pixel") +-- keybow.set_pixel = function(led, r, g, b) print("LED #"..led..", "..r..", "..g..", "..b) end busted.stub(keybow, "set_key") busted.stub(keybow, "set_modifier") busted.stub(keybow, "tap_key") diff --git a/sdcard/layouts/kdenlive.lua b/sdcard/layouts/kdenlive.lua index b91c3ce..140d531 100644 --- a/sdcard/layouts/kdenlive.lua +++ b/sdcard/layouts/kdenlive.lua @@ -28,21 +28,20 @@ local k = _G.kdenlive or {} -- module local mb = require "snippets/multibow" - -k = {} -- module - -- Key colors for unshifted and shifted keys; keep them rather muted in order -- to not distract your video editing work. -k.UNSHIFTED_COLOR = {r=0, g=50, b=0} -k.SHIFTED_COLOR = {r=50, g=0, b=0} +k.UNSHIFTED_COLOR = {r=0, g=1, b=0} +k.SHIFTED_COLOR = {r=1, g=0, b=0} k.keymap = { name="kdenlive", - [9] = {c={0,1,0}} + [9] = {c=k.UNSHIFTED_COLOR}, + [6] = {c=k.UNSHIFTED_COLOR}, } k.keymap_shifted = { name="kdenlive-shifted", secondary=true, - [9] = {c={1,0,0}} + [6] = {c=k.SHIFTED_COLOR}, + [3] = {c=k.SHIFTED_COLOR}, } k.keymap.shift_to = k.keymap_shifted k.keymap_shifted.shift_to = k.keymap diff --git a/sdcard/layouts/shift.lua b/sdcard/layouts/shift.lua index 7f05f00..2dbc0ae 100644 --- a/sdcard/layouts/shift.lua +++ b/sdcard/layouts/shift.lua @@ -59,11 +59,10 @@ shift.KEY_BRIGHTNESS = shift.KEY_BRIGHTNESS or 5 -- Internal flag for detecting SHIFT press-release sequences without any SHIFTed -- function. local shift_only = false -local in_shift = false -local other_key = false +local grabbed_key_count = 0 --- Switch to the next SHIFT layer within the currently active keyboard layout. +-- Switches to the next SHIFT layer within the currently active keyboard layout. -- SHIFT layer(s) are wired up as a circular list of keymaps, linked using their -- "shift_to" elements. function shift.shift_secondary_keymap() @@ -74,40 +73,42 @@ function shift.shift_secondary_keymap() end -function shift.release_other() - other_key = false - if not in_shift then - mb.ungrab() +-- Remember how many grabbed keys are pressed, so we won't ungrab later until +-- all keys have been released. +function shift.any_press(keyno) + grabbed_key_count = grabbed_key_count + 1 +end + +-- Only ungrab after last key has been released +function shift.any_release(keyno) + if grabbed_key_count > 0 then + grabbed_key_count = grabbed_key_count - 1 + if grabbed_key_count == 0 then + -- Ungrabs after last key released. + mb.ungrab() + -- And switches between keymaps within the same set. + if shift_only then + shift.shift_secondary_keymap() + end + end end end --- SHIFT press: switches into grabbed SHIFT mode, activating brightness change, --- keymap cycling, et cetera. -function shift.grab(key) - mb.grab("shift-shifted") - in_shift = true - other_key = false -- play safe. +-- SHIFT press: switches into grabbed SHIFT mode, activating the in-SHIFT keys +-- for brightness change, keymap cycling, et cetera. +function shift.shift(key) + grabbed_keys = 1 -- includes myself; this is necessary as the grab "any" + -- handler will not register the SHIFT press, because it + -- wasn't grabbed yet. shift_only = true -end - - --- SHIFT release: leaves (grabbed) SHIFT mode, but only if there is no other --- (bound) key currently being held. -function shift.release(key) - in_shift = false - if not other_key then - mb.ungrab() - end - if shift_only then - shift.shift_secondary_keymap() - end + shift.any_press(key) + mb.grab(shift.keymap_shifted.name) end -- Cycles to the next primary keyboard layout (keymap) function shift.cycle(key) - other_key = true shift_only = false mb.cycle_primary_keymaps() end @@ -116,7 +117,6 @@ end -- Changes the Keybow LED brightness, by cycling through different brightness -- levels function shift.brightness(key) - other_key = true shift_only = false local b = mb.brightness + 0.3 if b > 1 then; b = 0.4; end @@ -129,12 +129,13 @@ end shift.keymap = { name="shift", permanent=true, - [shift.KEY_SHIFT] = {c={r=1, g=1, b=1}, press=shift.grab, release=shift.release}, + [shift.KEY_SHIFT] = {c={r=1, g=1, b=1}, press=shift.shift}, } shift.keymap_shifted = { name="shift-shifted", secondary=true, - [shift.KEY_SHIFT] = {c={r=1, g=1, b=1}, press=shift.grab, release=shift.release}, + [-1] = {press=shift.any_press, release=shift.any_release}, + [shift.KEY_SHIFT] = {c={r=1, g=1, b=1}}, [shift.KEY_LAYOUT] = {c={r=0, g=1, b=1}, press=shift.cycle, release=shift.release_other}, [shift.KEY_BRIGHTNESS] = {c={r=0.5, g=0.5, b=0.5}, press=shift.brightness, release=shift.release_other} diff --git a/sdcard/snippets/routehandlers.lua b/sdcard/snippets/routehandlers.lua index 8a1c242..8e593bf 100644 --- a/sdcard/snippets/routehandlers.lua +++ b/sdcard/snippets/routehandlers.lua @@ -24,10 +24,11 @@ SOFTWARE. -- their correct handlers, depending on which keyboard layout currently -- is active. function mb.route(keyno, pressed) - local keydef + local keydef, grabbed_any_keydef -- Checks for a keymap grab being enforced at this time... if mb.grab_keymap then keydef = mb.grab_keymap[keyno] + grabbed_any_keydef = mb.grab_keymap[-1] else -- Checks for key in permanent keymaps first... for name, keymap in pairs(mb.keymaps) do @@ -45,7 +46,7 @@ function mb.route(keyno, pressed) end -- Bails out if no key definition to route to could be found. - if not keydef then + if not (keydef or grabbed_any_keydef) then return end @@ -57,11 +58,17 @@ function mb.route(keyno, pressed) mb.led(led, {r = 0, g = 0, b = 0}) end end - if keydef.press then + if grabbed_any_keydef and grabbed_any_keydef.press then + grabbed_any_keydef.press(keyno) + end + if keydef and keydef.press then keydef.press(keyno) end else - if keydef.release then + if grabbed_any_keydef and grabbed_any_keydef.release then + grabbed_any_keydef.release(keyno) + end + if keydef and keydef.release then keydef.release(keyno) end mb.activate_leds() diff --git a/spec/integration_spec.lua b/spec/integration_spec.lua index 63f0048..c851d06 100644 --- a/spec/integration_spec.lua +++ b/spec/integration_spec.lua @@ -36,7 +36,9 @@ local hwk = require("spec/hwkeys") describe("final Multibow integration", function() - it("correctly integrates", function() + _G.setup() + + it("integrates all keymaps", function() local kms = mb.registered_keymaps() -- shift: 2 registered keymaps -- vsc-golang: 1 reg keymap diff --git a/spec/layouts/empty_spec.lua b/spec/layouts/empty_spec.lua index 226080f..231e04b 100644 --- a/spec/layouts/empty_spec.lua +++ b/spec/layouts/empty_spec.lua @@ -38,7 +38,7 @@ describe("empty multibow keymap", function() -- empty must register exactly one keymap, and it must be -- a primary keymap, not permanent or secondary. local kms = mb.registered_keymaps() - assert.is.equal(#kms, 1) + assert.is.equal(1, #kms) local keymap = kms[1] assert.is_falsy(keymap.permanent) assert.is_falsy(keymap.secondary) diff --git a/spec/layouts/kdenlive_spec.lua b/spec/layouts/kdenlive_spec.lua index 7f9a0de..32bd12a 100644 --- a/spec/layouts/kdenlive_spec.lua +++ b/spec/layouts/kdenlive_spec.lua @@ -20,16 +20,36 @@ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ]]-- +require "mocked-keybow" local hwk = require("spec/hwkeys") -local mb = require("snippets/multibow") -local k = require("layouts/kdenlive") describe("Kdenlive keymap", function() it("...", function() + local mb = require("snippets/multibow") + local k = require("layouts/kdenlive") + assert.is.equal(k.keymap.name, mb.current_keymap.name) + local kms = mb.registered_keymaps() assert.is.equal(2, #kms) end) + describe("with setup", function() + + local hwk, mb, k + + before_each(function() + mb = require("snippets/multibow") + require("layouts/shift") + k = require("layouts/kdenlive") + _G.setup() + end) + + inslit("", function() + assert.is.equal(k.keymap.name, mb.current_keymap.name) + end) + + end) + end) diff --git a/spec/layouts/shift_spec.lua b/spec/layouts/shift_spec.lua index 7d48213..cccea34 100644 --- a/spec/layouts/shift_spec.lua +++ b/spec/layouts/shift_spec.lua @@ -27,44 +27,36 @@ local mb = require("snippets/multibow") describe("SHIFT multibow keymap", function() - insl(function() + inslit("installs the SHIFT keymap", function() + -- Sanity check that there are no registered keymaps yet. + assert.is.equal(#mb.registered_keymaps(), 0) - it("installs the SHIFT keymap", function() - -- Sanity check that there are no registered keymaps yet. - assert.is.equal(#mb.registered_keymaps(), 0) + local shift = require("layouts/shift") + assert.is_not_nil(shift) -- we're going slightly over the top here... + assert.is_not_nil(shift.keymap) - local shift = require("layouts/shift") - assert.is_not_nil(shift) -- we're going slightly over the top here... - assert.is_not_nil(shift.keymap) - - -- SHIFT must register exactly two keymaps, a primary and a secondary one. - local kms = mb.registered_keymaps() - assert.is.equal(#kms, 2) - for idx, keymap in ipairs(kms) do - if keymap == "shift" then - assert.is_falsy(keymap.permanent) - assert.is_falsy(keymap.secondary) - elseif keymap == "shift-shifted" then - assert.is_falsy(keymap.permanent) - assert.is_true(keymap.secondary) - end + -- SHIFT must register exactly two keymaps, a primary and a secondary one. + local kms = mb.registered_keymaps() + assert.is.equal(#kms, 2) + for idx, keymap in ipairs(kms) do + if keymap == "shift" then + assert.is_falsy(keymap.permanent) + assert.is_falsy(keymap.secondary) + elseif keymap == "shift-shifted" then + assert.is_falsy(keymap.permanent) + assert.is_true(keymap.secondary) end + end - default_key_shift = shift.KEY_SHIFT - end) - + default_key_shift = shift.KEY_SHIFT end) - insl(function() - - it("accepts changes form default", function() - local override = 42 - - _G.shift = { KEY_SHIFT=override } - local shift = require("layouts/shift") - assert.is.equal(shift.KEY_SHIFT, override) - end) + inslit("accepts changes form default", function() + local override = 42 + _G.shift = { KEY_SHIFT=override } + local shift = require("layouts/shift") + assert.is.equal(shift.KEY_SHIFT, override) end) describe("SHIFTY", function() @@ -77,74 +69,81 @@ describe("SHIFT multibow keymap", function() shift = require("layouts/shift") end) - insl(function() - it("SHIFT grabs", function() - spy.on(mb, "grab") - spy.on(mb, "ungrab") + inslit("SHIFT grabs", function() + spy.on(mb, "grab") + spy.on(mb, "ungrab") - -- route in the SHIFT permanent keymap + -- route in the SHIFT permanent keymap + hwk.press(shift.KEY_SHIFT) + assert.spy(mb.grab).was.called(1) + assert.spy(mb.grab).was.called_with(shift.keymap_shifted.name) + + -- route in the shifted(!) SHIFT keymap, so this checks + -- that we ungrab correctly + mb.grab:clear() + hwk.release(shift.KEY_SHIFT) + assert.spy(mb.grab).was_not.called() + assert.spy(mb.ungrab).was.called(1) + + mb.grab:revert() + mb.ungrab:revert() + end) + + inslit("only lonely SHIFT triggers shift and no dangling grabs", function() + stub(shift, "shift_secondary_keymap") + + -- test that lonely SHIFT triggers... + hwk.tap(shift.KEY_SHIFT) + assert.is_nil(mb.grab_keymap) + assert.stub(shift.shift_secondary_keymap).was.called(1) + + -- but that SHIFT followed by another function doesn't shift. + shift.shift_secondary_keymap:clear() + for idx, key in ipairs({ + shift.KEY_LAYOUT, + shift.KEY_BRIGHTNESS + }) do hwk.press(shift.KEY_SHIFT) - assert.spy(mb.grab).was.called(1) - assert.spy(mb.grab).was.called_with(shift.keymap_shifted.name) - - -- route in the shifted(!) SHIFT keymap, so this checks - -- that we ungrab correctly - mb.grab:clear() + hwk.tap(key) hwk.release(shift.KEY_SHIFT) - assert.spy(mb.grab).was_not.called() - assert.spy(mb.ungrab).was.called(1) - - mb.grab:revert() - mb.ungrab:revert() - end) + assert.is_nil(mb.grab_keymap) + assert.stub(shift.shift_secondary_keymap).was_not.called() + end end) - insl(function() - it("only lonely SHIFT triggers shift", function() - stub(shift, "shift_secondary_keymap") + inslit("lonly SHIFTs shift around", function() + local keymap = { + name="test" + } + local keymap_shifted = { + name="test-shifted", + [0]={press=function(key) end} + } + keymap.shift_to = keymap_shifted + keymap_shifted.shift_to = keymap + mb.register_keymap(keymap) + mb.register_keymap(keymap_shifted) + assert.is.equal(mb.current_keymap, keymap) - -- test that lonely SHIFT triggers... - hwk.tap(shift.KEY_SHIFT) - assert.stub(shift.shift_secondary_keymap).was.called(1) + spy.on(mb, "activate_keymap") + local s = stub(keymap_shifted[0], "press") - -- but that SHIFT followed by another function doesn't shift. - shift.shift_secondary_keymap:clear() - for idx, key in ipairs({ - shift.KEY_LAYOUT, - shift.KEY_BRIGHTNESS - }) do - hwk.press(shift.KEY_SHIFT) - hwk.tap(key) - hwk.release(shift.KEY_SHIFT) - assert.stub(shift.shift_secondary_keymap).was_not.called() - end - end) - end) + hwk.tap(shift.KEY_SHIFT) + assert.spy(mb.activate_keymap).was.called_with(keymap_shifted.name) + assert.is.equal(mb.current_keymap, keymap_shifted) - insl(function() - it("lonly SHIFTs shift around", function() - local keymap = { - name="test" - } - local keymap_shifted = { - name="test-shifted" - } - keymap.shift_to = keymap_shifted - keymap_shifted.shift_to = keymap - mb.register_keymap(keymap) - mb.register_keymap(keymap_shifted) - assert.is.equal(mb.current_keymap, keymap) + hwk.tap(0) + assert.stub(s).was.called(1) - spy.on(mb, "activate_keymap") + hwk.tap(shift.KEY_SHIFT) + assert.is.equal(mb.current_keymap, keymap) - hwk.tap(shift.KEY_SHIFT) - assert.spy(mb.activate_keymap).was.called_with(keymap_shifted.name) - assert.is.equal(mb.current_keymap, keymap_shifted) - hwk.tap(shift.KEY_SHIFT) - assert.is.equal(mb.current_keymap, keymap) + s:clear() + hwk.tap(0) + assert.stub(s).was_not.called() - mb.activate_keymap:revert() - end) + mb.activate_keymap:revert() + s:revert() end) insl(function() diff --git a/spec/snippets/routehandlers_spec.lua b/spec/snippets/routehandlers_spec.lua index ce906e2..274a79e 100644 --- a/spec/snippets/routehandlers_spec.lua +++ b/spec/snippets/routehandlers_spec.lua @@ -62,6 +62,7 @@ describe("routehandlers", function() } local grab_keymap = { name="grab", + [-1]={press=spies.grab_key_press, release=spies.grab_key_release}, [0]={press=spies.grab_key_press, release=spies.grab_key_release} } @@ -172,11 +173,18 @@ describe("routehandlers", function() mb.register_keymap(grab_keymap) mb.grab(grab_keymap.name) + -- grab routes to grab handler *AND* grab any handler assert.spy(spies.grab_key_press).was.called(0) hwk.press(0) - assert.spy(spies.grab_key_press).was.called(1) + -- remember: this grab has an "any" handler + assert.spy(spies.grab_key_press).was.called(2) assert.spy(spies.grab_key_press).was.called_with(0) + spies.grab_key_press:clear() + hwk.press(1) + assert.spy(spies.grab_key_press).was.called(1) + assert.spy(spies.grab_key_press).was.called_with(1) + spies.grab_key_press:clear() mb.ungrab() hwk.press(0)