diff --git a/.busted b/.busted index 9274259..e1b6b02 100644 --- a/.busted +++ b/.busted @@ -35,6 +35,7 @@ return { .. "function insl(f) _BUSTED.insulate(_INSL, f) end;" .. "function inslit(d, f) _BUSTED.insulate(_INSL, function() _BUSTED.it(d, f) end) end;" , - verbose = true + verbose = true, + recursive = true } } \ No newline at end of file diff --git a/sdcard/layouts/kdenlive.lua b/sdcard/layouts/kdenlive.lua index 5c11e50..bea8ba4 100644 --- a/sdcard/layouts/kdenlive.lua +++ b/sdcard/layouts/kdenlive.lua @@ -25,39 +25,45 @@ SOFTWARE. -- below. local k = _G.kdenlive or {} -- module -local mb = require "snippets/multibow" +require("keybow") +local mk = require("snippets/morekeys") +local mb = require("snippets/multibow") + + +k.KEY_CLIP_BEGIN = k.KEY_CLIP_BEGIN or 9 +k.KEY_PROJECT_BEGIN = k.KEY_PROJECT_BEGIN or 9 + +k.KEY_CLIP_END = k.KEY_CLIP_END or 0 +k.KEY_PROJECT_END = k.KEY_PROJECT_END or 0 + +-- (Default) key colors for unshifted and shifted keys. +k.COLOR_UNSHIFTED = k.COLOR_UNSHIFTED or {r=0, g=1, b=0} +k.COLOR_SHIFTED = k.COLOR_SHIFTED or {r=1, g=0, b=0} -- Unshift to primary keymap. For simplification, use it with the "anykey" -- release handlers, see below. function k.unshift(keyno) - if keyno == 11 then - print(debug.traceback()) - else - mb.activate_keymap(k.keymap.name) - end + mb.activate_keymap(k.keymap.name) end --- 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=1, b=0} -k.SHIFTED_COLOR = {r=1, g=0, b=0} k.keymap = { name="kdenlive", - [9] = {c=k.UNSHIFTED_COLOR}, - [6] = {c=k.UNSHIFTED_COLOR}, + [k.KEY_CLIP_BEGIN] = {c=k.COLOR_UNSHIFTED, press=function() mb.tap(mk.HOME) end}, + [k.KEY_CLIP_END] = {c=k.COLOR_UNSHIFTED, press=function() mb.tap(mk.END) end}, } k.keymap_shifted = { name="kdenlive-shifted", secondary=true, + [k.KEY_PROJECT_BEGIN] = {c=k.COLOR_SHIFTED, press=function() mb.tap(mk.HOME, keybow.LEFT_CTRL) end}, + [k.KEY_PROJECT_END] = {c=k.COLOR_SHIFTED, press=function() mb.tap(mk.END, keybow.LEFT_CTRL) end}, [-1] = {release=k.unshift}, - [6] = {c=k.SHIFTED_COLOR}, - [3] = {c=k.SHIFTED_COLOR}, } k.keymap.shift_to = k.keymap_shifted k.keymap_shifted.shift_to = k.keymap + mb.register_keymap(k.keymap) mb.register_keymap(k.keymap_shifted) -return k -- module \ No newline at end of file +return k -- module diff --git a/sdcard/layouts/vsc-golang.lua b/sdcard/layouts/vsc-golang.lua index 32d07fb..92beeb7 100644 --- a/sdcard/layouts/vsc-golang.lua +++ b/sdcard/layouts/vsc-golang.lua @@ -20,7 +20,7 @@ cable going off "northwards": ╔════╗ ╔════╗ ╔════╗ ╔════╗ ║ 9 ║ ║ 6 ║ ║ 3 ║ ║ 0 ║ ╚════╝ ╚════╝ ╚════╝ ╚════╝ - ▮▶ ⭢STEP ⮧INTO ⮥OUT + ▮▶ ⮧INTO ⭢STEP ⮥OUT ]]-- @@ -33,13 +33,21 @@ vscgo.KEY_STEPINTO = vscgo.KEY_STEPINTO or 6 vscgo.KEY_STEPOVER = vscgo.KEY_STEPOVER or 3 vscgo.KEY_STEPOUT = vscgo.KEY_STEPOUT or 0 -RED = { r=1, g=0, b=0 } -YELLOW = { r=1, g=0.8, b=0 } -GREEN = { r=0, g=1, b=0 } -BLUE = { r=0, g=0, b=1 } -BLUECYAN = { r=0, g=0.7, b=1 } -BLUEGRAY = { r=0.7, g=0.7, b=1 } -CYAN = { r=0, g=1, b=1 } +vscgo.RED = { r=1, g=0, b=0 } +vscgo.YELLOW = { r=1, g=0.7, b=0 } +vscgo.GREEN = { r=0, g=1, b=0 } +vscgo.BLUE = { r=0, g=0, b=1 } +vscgo.BLUECYAN = { r=0, g=0.7, b=1 } +vscgo.BLUEGRAY = { r=0.7, g=0.7, b=1 } +vscgo.CYAN = { r=0, g=1, b=1 } + +vscgo.COLOR_STOP = vscgo.COLOR_STOP or vscgo.RED +vscgo.COLOR_RELOAD = vscgo.COLOR_RELOAD or vscgo.YELLOW +vscgo.COLOR_TESTPKG = vscgo.COLOR_TESTPKG or vscgo.CYAN +vscgo.COLOR_CONT = vscgo.COLOR_CONT or vscgo.GREEN +vscgo.COLOR_STEPINTO = vscgo.COLOR_STEPINTO or vscgo.BLUECYAN +vscgo.COLOR_STEPOVER = vscgo.COLOR_STEPOVER or vscgo.BLUE +vscgo.COLOR_STEPOUT = vscgo.COLOR_STEPOUT or vscgo.BLUEGRAY -- AND NOW FOR SOMETHING DIFFERENT: THE REAL MEAT -- @@ -77,14 +85,14 @@ end vscgo.keymap = { name="vsc-golang-debug", - [vscgo.KEY_STOP] = {c=RED, press=vscgo.debug_stop}, - [vscgo.KEY_RELOAD] = {c=YELLOW, press=vscgo.debug_restart}, - [vscgo.KEY_TESTPKG] = {c=CYAN, press=vscgo.go_test_package}, + [vscgo.KEY_STOP] = {c=vscgo.COLOR_STOP, press=vscgo.debug_stop}, + [vscgo.KEY_RELOAD] = {c=vscgo.COLOR_RELOAD, press=vscgo.debug_restart}, + [vscgo.KEY_TESTPKG] = {c=vscgo.COLOR_TESTPKG, press=vscgo.go_test_package}, - [vscgo.KEY_CONT] = {c=GREEN, press=vscgo.debug_continue}, - [vscgo.KEY_STEPINTO] = {c=BLUECYAN, press=vscgo.debug_stepinto}, - [vscgo.KEY_STEPOVER] = {c=BLUE, press=vscgo.debug_stepover}, - [vscgo.KEY_STEPOUT] = {c=BLUEGRAY, press=vscgo.debug_stepout}, + [vscgo.KEY_CONT] = {c=vscgo.COLOR_CONT, press=vscgo.debug_continue}, + [vscgo.KEY_STEPINTO] = {c=vscgo.COLOR_STEPINTO, press=vscgo.debug_stepinto}, + [vscgo.KEY_STEPOVER] = {c=vscgo.COLOR_STEPOVER, press=vscgo.debug_stepover}, + [vscgo.KEY_STEPOUT] = {c=vscgo.COLOR_STEPOUT, press=vscgo.debug_stepout}, } mb.register_keymap(vscgo.keymap) diff --git a/sdcard/snippets/mb/keys.lua b/sdcard/snippets/mb/keys.lua new file mode 100644 index 0000000..fff3611 --- /dev/null +++ b/sdcard/snippets/mb/keys.lua @@ -0,0 +1,48 @@ +-- Part of Multibow + +--[[ +Copyright 2019 Harald Albrecht + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +]]-- + +-- Sends a single key tap to the USB host, optionally with modifier keys, such +-- as SHIFT (keybow.LEFT_SHIFT), CTRL (keybow.LEFT_CTRL), et cetera. The "key" +-- parameter can be a string or a Keybow key code, such as keybow.HOME, et +-- cetera. +function mb.tap(key, ...) + mb.tap_times(key, 1, ...) +end + +-- Taps the same key multiple times... +function mb.tap_times(key, times, ...) + for modifier_argno = 1, select("#", ...) do + local modifier = select(modifier_argno, ...) + if modifier then; keybow.set_modifier(modifier, keybow.KEY_DOWN); end + end + for tap = 1, times do + keybow.tap_key(key) + keybow.sleep(100) + end + for modifier_argno = 1, select("#", ...) do + local modifier = select(modifier_argno, ...) + if modifier then; keybow.set_modifier(modifier, keybow.KEY_UP); end + end + end + \ No newline at end of file diff --git a/sdcard/snippets/mb/leds.lua b/sdcard/snippets/mb/leds.lua new file mode 100644 index 0000000..668cb9f --- /dev/null +++ b/sdcard/snippets/mb/leds.lua @@ -0,0 +1,35 @@ +-- Part of Multibow + +--[[ +Copyright 2019 Harald Albrecht + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +]]-- + + +-- Default LED brightness in the [0.1..1] range. +mb.brightness = 1 + +-- Sets the Keybow key LEDs maximum brightness, in the range [0.1..1]. +function mb.set_brightness(brightness) + if brightness < 0.1 then brightness = 0.1 end + if brightness > 1 then brightness = 1 end + mb.brightness = brightness + mb.activate_leds() +end diff --git a/sdcard/snippets/routehandlers.lua b/sdcard/snippets/mb/routehandlers.lua similarity index 99% rename from sdcard/snippets/routehandlers.lua rename to sdcard/snippets/mb/routehandlers.lua index b2e4529..55aa567 100644 --- a/sdcard/snippets/routehandlers.lua +++ b/sdcard/snippets/mb/routehandlers.lua @@ -1,3 +1,5 @@ +-- Part of Multibow + --[[ Copyright 2019 Harald Albrecht diff --git a/sdcard/snippets/morekeys.lua b/sdcard/snippets/morekeys.lua index a991c2e..eb84eb2 100644 --- a/sdcard/snippets/morekeys.lua +++ b/sdcard/snippets/morekeys.lua @@ -106,3 +106,6 @@ keybow.MEDIA_SLEEP = 0xf8 keybow.MEDIA_COFFEE = 0xf9 keybow.MEDIA_REFRESH = 0xfa keybow.MEDIA_CALC = 0xfb + + +return keybow -- module diff --git a/sdcard/snippets/multibow.lua b/sdcard/snippets/multibow.lua index 40f93bf..fb6f922 100644 --- a/sdcard/snippets/multibow.lua +++ b/sdcard/snippets/multibow.lua @@ -27,10 +27,13 @@ require "keybow" mb.path = (...):match("^(.-)[^%/]+$") require(mb.path .. "morekeys") -require(mb.path .. "routehandlers") +require(mb.path .. "mb/keys") +require(mb.path .. "mb/routehandlers") +require(mb.path .. "mb/leds") -mb.brightness = 0.4 +-- Internal variables for housekeeping... + -- The registered keymaps, indexed by their names (.name field). mb.keymaps = {} -- The ordered sequence of primary keymap, in the sequence they were @@ -38,24 +41,10 @@ mb.keymaps = {} mb.primary_keymaps = {} -- The currently activated keymap. mb.current_keymap = nil - -- A temporary keymap while grabbing. mb.grab_keymap = nil --- -function mb.tap(keyno, key, ...) - for modifier_argno = 1, select('#', ...) do - local modifier = select(modifier_argno, ...) - if modifier then; keybow.set_modifier(modifier, keybow.KEY_DOWN); end - end - keybow.tap_key(key) - for modifier_argno = 1, select('#', ...) do - local modifier = select(modifier_argno, ...) - if modifier then; keybow.set_modifier(modifier, keybow.KEY_UP); end - end -end - -- Registers a keymap (by name), so it can be easily activated later by its name. -- Multiple keymaps can be registered. Keymaps can be either "primary" by -- default, or permanent or secondary keymaps. @@ -171,14 +160,6 @@ function mb.ungrab() end --- Sets the Keybow key LEDs maximum brightness, in the range [0.1..1]. -function mb.set_brightness(brightness) - if brightness < 0.1 then; brightness = 0.1; end - if brightness > 1 then; brightness = 1; end - mb.brightness = brightness - mb.activate_leds() -end - -- Sets key LED to specific color, taking brightness into consideration. -- The color is a triple (table) with the elements r, g, and b. Each color -- component is in the range [0..1]. diff --git a/spec/layouts/kdenlive_spec.lua b/spec/layouts/kdenlive_spec.lua index ebaf3a5..48f6e35 100644 --- a/spec/layouts/kdenlive_spec.lua +++ b/spec/layouts/kdenlive_spec.lua @@ -26,7 +26,7 @@ local hwk = require("spec/hwkeys") describe("Kdenlive keymap", function() - it("...", function() + it("initializes", function() local mb = require("snippets/multibow") local k = require("layouts/kdenlive") assert.is.equal(k.keymap.name, mb.current_keymap.name) @@ -46,26 +46,55 @@ describe("Kdenlive keymap", function() _G.setup() end) + it("colors its keys", function() + for _, keymap in pairs(mb.registered_keymaps()) do + if string.sub(keymap.name, 1, #"kdenlive") == "kdenlive" then + for keyno = 0, 11 do + local keydef = keymap[keyno] + if keydef then + assert.is_truthy(keydef.c) + end + end + end + end + end) + inslit("automatically un-shifts after key press", function() local some_key = shift.KEY_SHIFT ~= 0 and 0 or 1 for round = 1, 2 do for round = 1, 2 do - assert.is.equal(k.keymap.name, mb.current_keymap.name) + assert.equals(k.keymap.name, mb.current_keymap.name) hwk.tap(shift.KEY_SHIFT) - assert.is.equal(k.keymap_shifted.name, mb.current_keymap.name) + assert.equals(k.keymap_shifted.name, mb.current_keymap.name) hwk.tap(some_key) - assert.is.equal(k.keymap.name, mb.current_keymap.name) + assert.equals(k.keymap.name, mb.current_keymap.name) end for round = 1, 2 do hwk.tap(shift.KEY_SHIFT) - assert.is.equal(k.keymap_shifted.name, mb.current_keymap.name) + assert.equals(k.keymap_shifted.name, mb.current_keymap.name) hwk.tap(shift.KEY_SHIFT) - assert.is.equal(k.keymap.name, mb.current_keymap.name) + assert.equals(k.keymap.name, mb.current_keymap.name) end end end) + inslit("taps unshifted", function() + local s = spy.on(mb, "tap") + local sm = spy.on(keybow, "set_modifier") + + hwk.tap(k.KEY_PROJECT_BEGIN) + assert.spy(s).was.called(1) + assert.spy(sm).was_not.called() + + s:clear() + hwk.tap(shift.KEY_SHIFT) + assert.equals(k.keymap_shifted.name, mb.current_keymap.name) + hwk.tap(k.KEY_CLIP_BEGIN) + assert.spy(s).was.called(1) + assert.spy(sm).was.called() + end) + end) end) diff --git a/spec/layouts/vsc-golang_spec.lua b/spec/layouts/vsc-golang_spec.lua new file mode 100644 index 0000000..9a26aea --- /dev/null +++ b/spec/layouts/vsc-golang_spec.lua @@ -0,0 +1,48 @@ +--[[ +Copyright 2019 Harald Albrecht + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +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") + +describe("VSC golang keymap", function() + + local mb, go + + it("initializes", function() + mb = require("snippets/multibow") + go = require("layouts/vsc-golang") + assert.is.equal(go.keymap.name, mb.current_keymap.name) + assert.is.equal(1, #mb.registered_keymaps()) + end) + + it("colors its keys", function() + for _, keymap in pairs({go.keymap}) do + for keyno = 0, 11 do + local keydef = keymap[keyno] + if keydef then + assert.is_truthy(keydef.c) + end + end + end + end) + +end) diff --git a/spec/snippets/keys_spec.lua b/spec/snippets/keys_spec.lua new file mode 100644 index 0000000..8025e65 --- /dev/null +++ b/spec/snippets/keys_spec.lua @@ -0,0 +1,69 @@ +--[[ +Copyright 2019 Harald Albrecht + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +]]-- + +require "mocked-keybow" +local mb = require("snippets/multibow") + +describe("multibow keys", function() + + local tap = spy.on(keybow, "tap_key") + local mod = spy.on(keybow, "set_modifier") + + before_each(function() + tap:clear() + mod:clear() + end) + + it("taps a plain honest key", function() + mb.tap("x") + assert.spy(tap).was.called(1) + assert.spy(tap).was.called_with("x") + assert.spy(mod).was_not.called() + end) + + it("taps a plain honest key", function() + mb.tap("x", keybow.LEFT_CTRL, keybow.LEFT_SHIFT) + assert.spy(tap).was.called(1) + assert.spy(mod).was.called(4) + for _, ud in pairs({keybow.KEY_DOWN, keybow.KEY_UP}) do + assert.spy(mod).was.called_with(keybow.LEFT_CTRL, ud) + assert.spy(mod).was.called_with(keybow.LEFT_SHIFT, ud) + end + end) + + it("taps the same key repeatedly", function() + mb.tap_times("x", 3) + assert.spy(tap).was.called(3) + assert.spy(tap).was.called_with("x") + end) + + it("taps the same key repeatedly with modifiers", function() + mb.tap_times("x", 3, keybow.LEFT_CTRL) + assert.spy(tap).was.called(3) + assert.spy(tap).was.called_with("x") + assert.spy(mod).was.called(2) + for _, ud in pairs({keybow.KEY_DOWN, keybow.KEY_UP}) do + assert.spy(mod).was.called_with(keybow.LEFT_CTRL, ud) + end + end) + +end) diff --git a/spec/snippets/leds_spec.lua b/spec/snippets/leds_spec.lua new file mode 100644 index 0000000..cac3d81 --- /dev/null +++ b/spec/snippets/leds_spec.lua @@ -0,0 +1,39 @@ +--[[ +Copyright 2019 Harald Albrecht + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +]]-- + +require "mocked-keybow" +local mb = require("snippets/multibow") + +describe("multibow LEDs", function() + + it("controls brightness", function() + mb.set_brightness(0.5) + assert.equals(0.5, mb.brightness) + + mb.set_brightness(1.1) + assert.equals(1.0, mb.brightness) + + mb.set_brightness(0) + assert.equals(0.1, mb.brightness) + end) + +end) diff --git a/spec/snippets/routehandlers_spec.lua b/spec/snippets/routehandlers_spec.lua index 274a79e..aeb3bca 100644 --- a/spec/snippets/routehandlers_spec.lua +++ b/spec/snippets/routehandlers_spec.lua @@ -23,7 +23,7 @@ SOFTWARE. require "mocked-keybow" local hwk = require("spec/hwkeys") -describe("routehandlers", function() +describe("multibow routehandlers", function() -- ensure to get a fresh multibow module instance each time we run -- an isolated test...