Browse Source

changes due to linting; refactors keybow mock stuff to go inside spec/

develop
Harald Albrecht 5 years ago
parent
commit
6fd78c1bf1
  1. 4
      .busted
  2. 38
      .luacheckrc
  3. 66
      README.md
  4. 12
      check.sh
  5. 16
      mock/mocked-keybow.lua
  6. 4
      sdcard/layouts/empty.lua
  7. 17
      sdcard/layouts/kdenlive.lua
  8. 7
      sdcard/layouts/keymap-template.lua
  9. 16
      sdcard/layouts/shift.lua
  10. 39
      sdcard/layouts/vsc-golang.lua
  11. 158
      sdcard/snippets/mb/keymaps.lua
  12. 22
      sdcard/snippets/mb/keys.lua
  13. 12
      sdcard/snippets/mb/leds.lua
  14. 15
      sdcard/snippets/mb/morekeys.lua
  15. 9
      sdcard/snippets/mb/routehandlers.lua
  16. 139
      sdcard/snippets/multibow.lua
  17. 2
      setup-tests.sh
  18. 2
      spec/hwkeys.lua
  19. 4
      spec/hwkeys_spec.lua
  20. 4
      spec/layouts/empty_spec.lua
  21. 12
      spec/layouts/kdenlive_spec.lua
  22. 2
      spec/layouts/keymap-template_spec.lua
  23. 10
      spec/layouts/shift_spec.lua
  24. 2
      spec/layouts/vsc-golang_spec.lua
  25. 0
      spec/mock/keybow.lua
  26. 54
      spec/mock/mocked-keybow.lua
  27. 50
      spec/mocked-keybow_spec.lua
  28. 4
      spec/snippets/keys_spec.lua
  29. 2
      spec/snippets/leds_spec.lua
  30. 6
      spec/snippets/multibow_spec.lua
  31. 31
      spec/snippets/routehandlers_spec.lua

4
.busted

@ -1,4 +1,4 @@
-- Configuration for "busted" TDD tool -- Configuration for "busted" TDD tool to unit test Multibow
--[[ --[[
Copyright 2019 Harald Albrecht Copyright 2019 Harald Albrecht
@ -24,7 +24,7 @@ SOFTWARE.
return { return {
default = { default = {
lpath = "./sdcard/?.lua;./mock/?.lua", lpath = "./sdcard/?.lua;./spec/mock/?.lua",
-- Provides an "insl" convenience replacement for busted's insulate() using -- Provides an "insl" convenience replacement for busted's insulate() using
-- a fixed descriptive text ... or rather, icon. Please not that "insl" -- a fixed descriptive text ... or rather, icon. Please not that "insl"
-- not only rhymes with "insulation", but even more so with the German -- not only rhymes with "insulation", but even more so with the German

38
.luacheckrc

@ -1,5 +1,37 @@
std = { -- Configuration for "luacheck"ing 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.
]]--
stds.multibow = {
read_globals = { read_globals = {
"insl", "require" -- 3rd party stuff that doesn't exactly play by the new Lua module rules...
"keybow",
-- our unit testing setup
"insl", "inslit"
} }
} }
std = "max+multibow"
exclude_files = { "spec/mock/keybow.lua" }

66
README.md

@ -18,6 +18,34 @@ And yes, this is probably a New Year's project slightly gone overboard ...
what sane reason is there to end up with a Lua-scripted multi-layout keyboard what sane reason is there to end up with a Lua-scripted multi-layout keyboard
"operating" system and a bunch of automated unit test cases? "operating" system and a bunch of automated unit test cases?
## Installation
1. Download the [Pibow
firmware](https://github.com/pimoroni/keybow-firmware/releases) and copy
all files inside its `sdcard/` subdirectory onto an empty, FAT32 formatted
microSD card. Copy only the files **inside** `sdcard/`, but do **not**
place them into a ~~`sdcard`~~ directory on your microSD card.
2. Download all files from the `sdcard/` subdirectory of this repository and
then copy them onto the microSD card. This will overwrite but one file
`key.lua`, all other files are new.
## Multiple Keyboard Layouts
To enable one or more multibow keyboard layouts, edit `sdcard/keys.lua`
accordingly in order to "`require`" them. The default configuration looks as
follows:
```lua
require "layouts/shift" -- for cycling between layouts.
require "layouts/vsc-golang" -- debugging Go programs in VisualStudio Code.
require "layouts/kdenlive" -- editing video using Kdenlive.
require "layouts/empty" -- empty, do-nothing layout.
```
> You can disable a specific keyboard layout by simply putting two dashes `--`
> in front of the `require "..."`, making it look like `--require "..."`.
## Layouts ## Layouts
The default setup activates the following macro keyboard layouts shown below. The default setup activates the following macro keyboard layouts shown below.
@ -56,13 +84,19 @@ Debug Go programs and packages in VisualStudio Code with its Go extension.
_coming soon..._ _coming soon..._
### SHIFT ### SHIFT Overlay
A SHIFT key, with Keybow LED brightness and keyboard layout cycle control. This layout provides a SHIFT key. Only when pressed and held, two additional
keys become active for controlling the brightness of the Keybow LEDs and for
switching between multiple keyboard layouts.
Simply pressing and then immediately releasing the SHIFT key without pressing
any of the other keys activates the SHIFT layer in other Multibow keyboard
layouts that are SHIFT-aware.
> **NOTE:** press and hold SHIFT, then use →LAYOUT and 🔆BRIGHT. The SHIFT key > **NOTE:** press and hold SHIFT, then use →LAYOUT and 🔆BRIGHT. The SHIFT key
> is always active, regardless of keyboard layout. The other keys in this layout > is always active, regardless of keyboard layout. The other keys in this
> only become active _while_ holding SHIFT. > layout become only active _while_ holding SHIFT.
```text ```text
╔════╗ ╔╌╌╌╌╗ ╔╌╌╌╌╗ ┌╌╌╌╌┐ ╔════╗ ╔╌╌╌╌╗ ╔╌╌╌╌╗ ┌╌╌╌╌┐
@ -169,30 +203,6 @@ The file `keybow.lua` included from
testing purposes is licensed under the MIT license, as declared by Pimoroni's testing purposes is licensed under the MIT license, as declared by Pimoroni's
keybow-firmware GitHub repository. keybow-firmware GitHub repository.
## Installation
1. Download the [Pibow
firmware](https://github.com/pimoroni/keybow-firmware/releases) and copy
all files inside its `sdcard/` subdirectory onto an empty, FAT32 formatted
microSD card. Copy only the files **inside** `sdcard/`, but do **not**
place them into a ~~`sdcard`~~ directory on your microSD card.
2. Download all files from the `sdcard/` subdirectory of this repository and
then copy them onto the microSD card. This will overwrite but one file
`key.lua`, all other files are new.
## Multiple Keyboard Layouts
To enable one or more multibow keyboard layouts, edit `sdcard/keys.lua`
accordingly to require them. The default configuration is as follows:
```lua
require "layouts/shift" -- for cycling between layouts.
require "layouts/vsc-golang" -- debugging Go programs in VisualStudio Code.
require "layouts/kdenlive" -- editing video using Kdenlive.
require "layouts/empty" -- empty, do-nothing layout.
```
## Developing ## Developing
Whether you want to dry-run your own keyboard layout or to hack Multibow: use Whether you want to dry-run your own keyboard layout or to hack Multibow: use

12
check.sh

@ -0,0 +1,12 @@
#!/bin/bash
hascmd() {
command -v "$1" >/dev/null
}
if ! hascmd busted || ! hascmd luacheck ; then
echo "missing busted TDD library and luacheck Lua static source code checker; trying to install..."
bash ./setup-tests.sh
fi
echo "testing..."
busted
echo "linting..."
luacheck -q ./sdcard ./spec

16
mock/mocked-keybow.lua

@ -1,16 +0,0 @@
local busted=require "busted"
require "keybow"
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")
busted.stub(keybow, "sleep") -- FIXME
busted.stub(keybow, "usleep") -- FIXME
return keybow

4
sdcard/layouts/empty.lua

@ -1,5 +1,5 @@
-- An empty Multibow Keybow layout. Useful for "switching off" any active -- An empty Multibow layout. Useful for "switching off" any active keymaps,
-- keymaps, with only the permanent keymaps (SHIFT, etc) being still in place. -- with only the permanent keymaps (SHIFT, etc) being still in place.
--[[ --[[
Copyright 2019 Harald Albrecht Copyright 2019 Harald Albrecht

17
sdcard/layouts/kdenlive.lua

@ -1,3 +1,6 @@
-- A Multibow keyboard layout for the Kdenlive (https://kdenlive.org/) open
-- source non-linear video editor.
--[[ --[[
Copyright 2019 Harald Albrecht Copyright 2019 Harald Albrecht
@ -20,15 +23,15 @@ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE. SOFTWARE.
]]-- ]]--
-- allow users to set their own configuration before req'ing this -- allow users to set their own configuration before req'ing this
-- module, in order to control the key layout. For defaults, please see -- module, in order to control the key layout. For defaults, please see
-- below. -- below.
local k = _G.kdenlive or {} -- module local k = _G.kdenlive or {} -- module
require("keybow")
local mk = require("snippets/morekeys")
local mb = require("snippets/multibow") local mb = require("snippets/multibow")
-- luacheck: ignore 614
--[[ --[[
The Keybow layout is as follows when in landscape orientation, with the USB The Keybow layout is as follows when in landscape orientation, with the USB
cable going off "northwards": cable going off "northwards":
@ -77,7 +80,7 @@ end
-- Unshift to primary keymap. For simplification, use it with the "anykey" -- Unshift to primary keymap. For simplification, use it with the "anykey"
-- release handlers, see below. -- release handlers, see below.
function k.unshift(keyno) function k.unshift(_)
mb.activate_keymap(k.keymap.name) mb.activate_keymap(k.keymap.name)
end end
@ -97,15 +100,15 @@ k.keymap = k.init_color({
name="kdenlive", name="kdenlive",
[k.KEY_ZONE_BEGIN] = {press=function() mb.tap("I") end}, [k.KEY_ZONE_BEGIN] = {press=function() mb.tap("I") end},
[k.KEY_ZONE_END] = {press=function() mb.tap("O") end}, [k.KEY_ZONE_END] = {press=function() mb.tap("O") end},
[k.KEY_CLIP_BEGIN] = {press=function() mb.tap(mk.HOME) end}, [k.KEY_CLIP_BEGIN] = {press=function() mb.tap(keybow.HOME) end},
[k.KEY_CLIP_END] = {press=function() mb.tap(mk.END) end}, [k.KEY_CLIP_END] = {press=function() mb.tap(keybow.END) end},
[k.KEY_PLAY_AROUND_MOUSE] = {press=function() k.play_around_mouse(keybow.SPACE, keybow.LEFT_CTRL) end}, [k.KEY_PLAY_AROUND_MOUSE] = {press=function() k.play_around_mouse(keybow.SPACE, keybow.LEFT_CTRL) end},
}, k.COLOR_UNSHIFTED) }, k.COLOR_UNSHIFTED)
k.keymap_shifted = k.init_color({ k.keymap_shifted = k.init_color({
name="kdenlive-shifted", name="kdenlive-shifted",
secondary=true, secondary=true,
[k.KEY_PROJECT_BEGIN] = {press=function() mb.tap(mk.HOME, keybow.LEFT_CTRL) end}, [k.KEY_PROJECT_BEGIN] = {press=function() mb.tap(keybow.HOME, keybow.LEFT_CTRL) end},
[k.KEY_PROJECT_END] = {press=function() mb.tap(mk.END, keybow.LEFT_CTRL) end}, [k.KEY_PROJECT_END] = {press=function() mb.tap(keybow.END, keybow.LEFT_CTRL) end},
[k.KEY_PLAY_AROUND_MOUSE] = {press=function() k.play_around_mouse(keybow.SPACE, keybow.LEFT_ALT) end}, [k.KEY_PLAY_AROUND_MOUSE] = {press=function() k.play_around_mouse(keybow.SPACE, keybow.LEFT_ALT) end},
[-1] = {release=k.unshift}, [-1] = {release=k.unshift},
}, k.COLOR_SHIFTED) }, k.COLOR_SHIFTED)

7
sdcard/layouts/keymap-template.lua

@ -1,5 +1,4 @@
-- An empty Multibow Keybow layout. Useful for "switching off" any active -- A Multibow template layout, useful for starting your own keymap layouts.
-- keymaps, with only the permanent keymaps (SHIFT, etc) being still in place.
--[[ --[[
Copyright 2019 Harald Albrecht Copyright 2019 Harald Albrecht
@ -45,11 +44,11 @@ cable going off "northwards":
]]-- ]]--
-- Some action on a certain key press... -- Some action on a certain key press...
function km.mypress(keyno) function km.mypress(keyno) -- luacheck: ignore 212
end end
-- Some action on a certain key release... -- Some action on a certain key release...
function km.myrelease(keyno) function km.myrelease(keyno) -- luacheck: ignore 212
end end
-- The keymap layout... -- The keymap layout...

16
sdcard/layouts/shift.lua

@ -1,4 +1,6 @@
-- A permanent "SHIFT" keymap for cycling keymaps and LED brightness control. -- A permanent "SHIFT" Multibow keymap layout for cycling keymaps, LED
-- brightness control, and adding SHIFT layers to other Multibow keyboard
-- (multi) layouts.
--[[ --[[
Copyright 2019 Harald Albrecht Copyright 2019 Harald Albrecht
@ -29,6 +31,7 @@ local shift = _G.shift or {} -- module
local mb = require "snippets/multibow" local mb = require "snippets/multibow"
-- luacheck: ignore 614
--[[ --[[
The Keybow layout is as follows when in landscape orientation, with the USB The Keybow layout is as follows when in landscape orientation, with the USB
cable going off "northwards": cable going off "northwards":
@ -55,7 +58,7 @@ shift.KEY_SHIFT = shift.KEY_SHIFT or 11
shift.KEY_LAYOUT = shift.KEY_LAYOUT or 8 shift.KEY_LAYOUT = shift.KEY_LAYOUT or 8
shift.KEY_BRIGHTNESS = shift.KEY_BRIGHTNESS or 5 shift.KEY_BRIGHTNESS = shift.KEY_BRIGHTNESS or 5
shift.BRIGHTNESS_LEVELS = shift.BRIGHTNESS_LEVELS or { 70, 100, 40 } shift.BRIGHTNESS_LEVELS = shift.BRIGHTNESS_LEVELS or { 70, 100, 40 }
-- Internal flag for detecting SHIFT press-release sequences without any SHIFTed -- Internal flag for detecting SHIFT press-release sequences without any SHIFTed
@ -79,12 +82,12 @@ end
-- Remember how many grabbed keys are pressed, so we won't ungrab later until -- Remember how many grabbed keys are pressed, so we won't ungrab later until
-- all keys have been released. -- all keys have been released.
function shift.any_press(keyno) function shift.any_press(_)
grabbed_key_count = grabbed_key_count + 1 grabbed_key_count = grabbed_key_count + 1
end end
-- Only ungrab after last key has been released -- Only ungrab after last key has been released
function shift.any_release(keyno) function shift.any_release(_)
if grabbed_key_count > 0 then if grabbed_key_count > 0 then
grabbed_key_count = grabbed_key_count - 1 grabbed_key_count = grabbed_key_count - 1
if grabbed_key_count == 0 then if grabbed_key_count == 0 then
@ -101,16 +104,13 @@ end
-- SHIFT press: switches into grabbed SHIFT mode, activating the in-SHIFT keys -- SHIFT press: switches into grabbed SHIFT mode, activating the in-SHIFT keys
-- for brightness change, keymap cycling, et cetera. -- for brightness change, keymap cycling, et cetera.
function shift.shift(key) 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 shift_only = true
shift.any_press(key) shift.any_press(key)
mb.grab(shift.keymap_shifted.name) mb.grab(shift.keymap_shifted.name)
end end
-- Cycles to the next primary keyboard layout (keymap) -- Cycles to the next primary keyboard layout (keymap)
function shift.cycle(key) function shift.cycle(_)
shift_only = false shift_only = false
mb.cycle_primary_keymaps() mb.cycle_primary_keymaps()
end end

39
sdcard/layouts/vsc-golang.lua

@ -1,4 +1,27 @@
-- VSC Go extension debug Keybow layout -- A Multibow keyboard layout for the VisualStudio Go extension
-- (https://github.com/Microsoft/vscode-go).
--[[
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.
]]--
local vscgo = _G.vscgo or {} -- module local vscgo = _G.vscgo or {} -- module
@ -52,31 +75,31 @@ vscgo.COLOR_STEPOUT = vscgo.COLOR_STEPOUT or vscgo.BLUEGRAY
-- AND NOW FOR SOMETHING DIFFERENT: THE REAL MEAT -- -- AND NOW FOR SOMETHING DIFFERENT: THE REAL MEAT --
function vscgo.debug_stop(key) function vscgo.debug_stop(_)
mb.tap(keybow.F5, keybow.LEFT_SHIFT) mb.tap(keybow.F5, keybow.LEFT_SHIFT)
end end
function vscgo.debug_restart(key) function vscgo.debug_restart(_)
mb.tap(keybow.F5, keybow.LEFT_SHIFT, keybow.LEFT_CTRL) mb.tap(keybow.F5, keybow.LEFT_SHIFT, keybow.LEFT_CTRL)
end end
function vscgo.debug_continue(key) function vscgo.debug_continue(_)
mb.tap(keybow.F5) mb.tap(keybow.F5)
end end
function vscgo.debug_stepover(key) function vscgo.debug_stepover(_)
mb.tap(keybow.F10) mb.tap(keybow.F10)
end end
function vscgo.debug_stepinto(key) function vscgo.debug_stepinto(_)
mb.tap(keybow.F11) mb.tap(keybow.F11)
end end
function vscgo.debug_stepout(key) function vscgo.debug_stepout(_)
mb.tap(keybow.F11, keybow.LEFT_SHIFT) mb.tap(keybow.F11, keybow.LEFT_SHIFT)
end end
function vscgo.go_test_package(key) function vscgo.go_test_package(_)
mb.tap("P", keybow.LEFT_SHIFT, keybow.LEFT_CTRL) mb.tap("P", keybow.LEFT_SHIFT, keybow.LEFT_CTRL)
keybow.sleep(250) keybow.sleep(250)
keybow.text("go test package") keybow.text("go test package")

158
sdcard/snippets/mb/keymaps.lua

@ -0,0 +1,158 @@
-- Multibow internal "module" implementing keymap-related management and
-- handling.
--[[
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.
]]--
-- luacheck: globals mb
-- 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
-- registered.
mb.primary_keymaps = {}
-- The currently activated keymap.
mb.current_keymap = nil
-- A temporary keymap while grabbing.
mb.grab_keymap = nil
-- 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.
--
-- A primary keymap is any keymap without either a "permanent" or "secondary"
-- table element. Users can cycle through primary keymaps using the "shift"
-- permanent keyboard layout.
--
-- permanent keymaps (marked by table element "permanent=true") are always
-- active, thus they don't need to be activated.
--
-- Secondary keymaps (marked by table element "secondary=true") are intended
-- as SHIFT/modifier layers. As such the get ignored by cycling, but instead
-- need to be activated explicitly. The "shift" permanent keyboard layout
-- automates this.
--
-- If this is the first keymap getting registered, then it will also made
-- activated.
function mb.register_keymap(keymap)
local name = keymap.name
-- register
mb.keymaps[name] = keymap
-- ensure that first registered keymap also automatically gets activated
-- (albeit the LEDs will only update later). Also maintain the (ordered)
-- sequence of registered primary keymaps.
if not (keymap.permanent or keymap.secondary) then
mb.current_keymap = mb.current_keymap or keymap
table.insert(mb.primary_keymaps, keymap)
end
end
-- Returns the list of currently registered keymaps; this list is a table,
-- with its registered keymaps at indices 1, 2, ...
function mb.registered_keymaps()
local keymaps = {}
for _, keymap in pairs(mb.keymaps) do
table.insert(keymaps, keymap)
end
return keymaps
end
-- Returns the list of currently registered *primary* keymaps, in the same order
-- as they were registered. First primary is at index 1, second at 2, ...
function mb.registered_primary_keymaps()
return mb.primary_keymaps
end
-- Cycles through the available (primary) keymaps, ignoring secondary and
-- permanent keymaps. This is convenient for assigning primary keymap switching
-- using a key on the Keybow device itself.
function mb.cycle_primary_keymaps()
local km = mb.current_keymap
if km == nil then return end
-- If this is a secondary keymap, locate its corresponding primary keymap.
if km.secondary then
if not km.shift_to then
-- No SHIFT's shift_to cyclic chain available, so rely on the naming
-- schema instead and try to locate the primary keymap with the first
-- match instead. This assumes that the name of the secondary keymaps
-- have some suffix and thus are longer than the name of their
-- corresponding primary keymap.
for _, pkm in ipairs(mb.primary_keymaps) do
if string.sub(km.name, 1, #pkm.name) == pkm.name then
km = pkm
break
end
end
-- Checks if locating the primary keymap failed and then bails out
-- immediately.
if km.secondary then return end
else
-- Follows the cyclic chain of SHIFT's shift_to keymaps, until we get
-- to the primary keymap in the cycle, or until we have completed one
-- cycle.
repeat
km = km.shift_to
if not km or km == mb.current_keymap then
return
end
until not(km.secondary)
end
end
-- Move on to the next primary keymap, rolling over at the end of our list.
for idx, pkm in ipairs(mb.primary_keymaps) do
if pkm == km then
idx = idx + 1
if idx > #mb.primary_keymaps then idx = 1 end
mb.activate_keymap(mb.primary_keymaps[idx].name)
end
end
end
-- Activates a specific keymap by name. Please note that it isn't necessary
-- to "activate" permanent keymaps at all (and thus this deed cannot be done).
function mb.activate_keymap(name)
name = type(name) == "table" and name.name or name
local keymap = mb.keymaps[name]
if keymap and not keymap.permanent then
mb.current_keymap = keymap
mb.activate_leds()
end
end
-- Sets a "grabbing" keymap that takes (temporarily) grabs all keys. While a
-- grab keymap is in place, key presses and releases will only be routed to
-- the grab keymap, but never to the permanent keymaps, nor the previously
-- "active" primary keymap.
function mb.grab(name)
name = type(name) == "table" and name.name or name
mb.grab_keymap = mb.keymaps[name]
mb.activate_leds()
end
-- Removes a "grabbing" keymap, thus reactivating the permanent keymaps, as
-- well as the previously active primary keymap.
function mb.ungrab()
mb.grab_keymap = nil
mb.activate_leds()
end

22
sdcard/snippets/mb/keys.lua

@ -1,4 +1,5 @@
-- Part of Multibow -- Multibow internal "module" implementing convenience functions for sending
-- key presses to the USB host to which the Keybow device is connected to.
--[[ --[[
Copyright 2019 Harald Albrecht Copyright 2019 Harald Albrecht
@ -22,6 +23,16 @@ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE. SOFTWARE.
]]-- ]]--
-- luacheck: globals mb
-- Default delay between rapidly (repeated) key presses, can be overridden.
mb.KEY_DELAY_MS = mb.KEY_DELAY_MS or 100
-- Delay between key presses
function mb.delay()
keybow.sleep(mb.KEY_DELAY_MS)
end
-- Sends a single key tap to the USB host, optionally with modifier keys, such -- 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" -- 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 -- parameter can be a string or a Keybow key code, such as keybow.HOME, et
@ -36,15 +47,14 @@ end
function mb.tap_times(key, times, ...) function mb.tap_times(key, times, ...)
for modifier_argno = 1, select("#", ...) do for modifier_argno = 1, select("#", ...) do
local modifier = select(modifier_argno, ...) local modifier = select(modifier_argno, ...)
if modifier then; keybow.set_modifier(modifier, keybow.KEY_DOWN); end if modifier then keybow.set_modifier(modifier, keybow.KEY_DOWN) end
end end
for tap = 1, times do for _ = 1, times do
keybow.tap_key(key) keybow.tap_key(key)
keybow.sleep(100) mb.delay()
end end
for modifier_argno = 1, select("#", ...) do for modifier_argno = 1, select("#", ...) do
local modifier = select(modifier_argno, ...) local modifier = select(modifier_argno, ...)
if modifier then; keybow.set_modifier(modifier, keybow.KEY_UP); end if modifier then keybow.set_modifier(modifier, keybow.KEY_UP) end
end end
end end

12
sdcard/snippets/mb/leds.lua

@ -1,4 +1,5 @@
-- Part of Multibow -- Multibow internal "module" implementing Keybow LED-related functionality,
-- such as brightness control and "lighting up" a (multibow) keymap.
--[[ --[[
Copyright 2019 Harald Albrecht Copyright 2019 Harald Albrecht
@ -23,6 +24,8 @@ SOFTWARE.
]]-- ]]--
-- luacheck: globals mb
mb.MIN_BRIGHTNESS = mb.MIN_BRIGHTNESS or 0.1 mb.MIN_BRIGHTNESS = mb.MIN_BRIGHTNESS or 0.1
-- Default LED brightness in the [0.1..1] range. -- Default LED brightness in the [0.1..1] range.
@ -60,7 +63,7 @@ function mb.led(keyno, color)
keybow.set_pixel(keyno, 0, 0, 0) keybow.set_pixel(keyno, 0, 0, 0)
end end
end end
-- Restores Keybow LEDs according to current keymap and the permanent keymaps. -- Restores Keybow LEDs according to current keymap and the permanent keymaps.
function mb.activate_leds() function mb.activate_leds()
keybow.clear_lights() keybow.clear_lights()
@ -74,14 +77,14 @@ function mb.activate_leds()
end end
-- ...then update LEDs from permanent keymap(s), as this ensures that -- ...then update LEDs from permanent keymap(s), as this ensures that
-- the permanent keymaps take precedence. -- the permanent keymaps take precedence.
for name, keymap in pairs(mb.keymaps) do for _, keymap in pairs(mb.keymaps) do
if keymap.permanent then if keymap.permanent then
mb.activate_keymap_leds(keymap) mb.activate_keymap_leds(keymap)
end end
end end
end end
end end
-- Helper function that iterates over all keymap elements but skipping non-key -- Helper function that iterates over all keymap elements but skipping non-key
-- bindings. -- bindings.
function mb.activate_keymap_leds(keymap) function mb.activate_keymap_leds(keymap)
@ -93,4 +96,3 @@ function mb.activate_keymap_leds(keymap)
end end
end end
end end

15
sdcard/snippets/morekeys.lua → sdcard/snippets/mb/morekeys.lua

@ -1,9 +1,9 @@
--[[ -- Multibow module providing additional USB HID keycode definitions to augment
Provide additional keybow USB HID key definitions. -- the existing keybow definitions. For more information about USB HID
-- keyboard scan codes, for instance, see:
For more information about USB HID keyboard scan codes, for instance, -- https://gist.github.com/MightyPork/6da26e382a7ad91b5496ee55fdc73db2
see: https://gist.github.com/MightyPork/6da26e382a7ad91b5496ee55fdc73db2
--[[
Copyright 2019 Harald Albrecht Copyright 2019 Harald Albrecht
Permission is hereby granted, free of charge, to any person obtaining a copy Permission is hereby granted, free of charge, to any person obtaining a copy
@ -27,6 +27,11 @@ SOFTWARE.
require("keybow") require("keybow")
-- Tell luacheck that it is okay in this specific case to change the keybow
-- global.
-- luacheck: globals keybow
keybow.SYSRQ = 0x46 keybow.SYSRQ = 0x46
keybow.SCROLLLOCK = 0x47 keybow.SCROLLLOCK = 0x47
keybow.PAUSE = 0x48 keybow.PAUSE = 0x48

9
sdcard/snippets/mb/routehandlers.lua

@ -1,4 +1,6 @@
-- Part of Multibow -- Multibow internal "module" implementing routing Keybow hardware key presses
-- and releases from the Keybow Lua firmware to our keymaps with their own key
-- handlers.
--[[ --[[
Copyright 2019 Harald Albrecht Copyright 2019 Harald Albrecht
@ -22,6 +24,9 @@ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE. SOFTWARE.
]] -- ]] --
-- luacheck: globals mb
-- This all-key, central key router forwards Keybow key events to their -- This all-key, central key router forwards Keybow key events to their
-- correct handlers, depending on which keyboard layout currently is active. -- correct handlers, depending on which keyboard layout currently is active.
-- --
@ -47,7 +52,7 @@ function mb.route(keyno, pressed)
-- No grab in place, so continue checking for a matching key in the -- No grab in place, so continue checking for a matching key in the
-- permanent keymaps first. Remember, there cannot be "any" handlers -- permanent keymaps first. Remember, there cannot be "any" handlers
-- with permanent keymaps. -- with permanent keymaps.
for name, keymap in pairs(mb.keymaps) do for _, keymap in pairs(mb.keymaps) do
if keymap.permanent then if keymap.permanent then
keydef = keymap[keyno] keydef = keymap[keyno]
if keydef then if keydef then

139
sdcard/snippets/multibow.lua

@ -1,3 +1,6 @@
-- "Multibow" is a Lua module for Pimoroni's Keybow firmware that offers and
-- manages multiple keyboard layouts.
--[[ --[[
Copyright 2019 Harald Albrecht Copyright 2019 Harald Albrecht
@ -20,149 +23,25 @@ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE. SOFTWARE.
]]-- ]]--
mb = {} -- module -- luacheck: globals mb
mb = mb or {} -- module
require "keybow" require "keybow"
-- Pulls in the individual modules that make up Multibow.
mb.path = (...):match("^(.-)[^%/]+$") mb.path = (...):match("^(.-)[^%/]+$")
require(mb.path .. "morekeys") require(mb.path .. "mb/morekeys")
require(mb.path .. "mb/keymaps")
require(mb.path .. "mb/keys") require(mb.path .. "mb/keys")
require(mb.path .. "mb/routehandlers") require(mb.path .. "mb/routehandlers")
require(mb.path .. "mb/leds") require(mb.path .. "mb/leds")
-- 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
-- registered.
mb.primary_keymaps = {}
-- The currently activated keymap.
mb.current_keymap = nil
-- A temporary keymap while grabbing.
mb.grab_keymap = nil
-- 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.
--
-- A primary keymap is any keymap without either a "permanent" or "secondary"
-- table element. Users can cycle through primary keymaps using the "shift"
-- permanent keyboard layout.
--
-- permanent keymaps (marked by table element "permanent=true") are always
-- active, thus they don't need to be activated.
--
-- Secondary keymaps (marked by table element "secondary=true") are intended
-- as SHIFT/modifier layers. As such the get ignored by cycling, but instead
-- need to be activated explicitly. The "shift" permanent keyboard layout
-- automates this.
--
-- If this is the first keymap getting registered, then it will also made
-- activated.
function mb.register_keymap(keymap)
local name = keymap.name
-- register
mb.keymaps[name] = keymap
-- ensure that first registered keymap also automatically gets activated
-- (albeit the LEDs will only update later). Also maintain the (ordered)
-- sequence of registered primary keymaps.
if not (keymap.permanent or keymap.secondary) then
mb.current_keymap = mb.current_keymap or keymap
table.insert(mb.primary_keymaps, keymap)
end
end
-- Returns the list of currently registered keymaps; this list is a table,
-- with its registered keymaps at indices 1, 2, ...
function mb.registered_keymaps()
local keymaps = {}
for name, keymap in pairs(mb.keymaps) do
table.insert(keymaps, keymap)
end
return keymaps
end
-- Returns the list of currently registered primary keymaps, in the same order
-- as they were registered. First primary is at index 1, second at 2, ...
function mb.registered_primary_keymaps()
return mb.primary_keymaps
end
-- Cycles through the available (primary) keymaps, ignoring secondary and
-- permanent keymaps. This is convenient for assigning primary keymap switching
-- using a key on the Keybow device itself.
function mb.cycle_primary_keymaps()
local km = mb.current_keymap
if km == nil then return end
-- If this is a secondary keymap, locate its corresponding primary keymap.
if km.secondary then
if not km.shift_to then
-- No SHIFT's shift_to cyclic chain available, so rely on the naming
-- schema instead and try to locate the primary keymap with the first
-- match instead. This assumes that the name of the secondary keymaps
-- have some suffix and thus are longer than the name of their
-- corresponding primary keymap.
for idx, pkm in ipairs(mb.primary_keymaps) do
if string.sub(km.name, 1, #pkm.name) == pkm.name then
km = pkm
break
end
end
-- Checks if locating the primary keymap failed and then bails out
-- immediately.
if km.secondary then return end
else
-- Follows the cyclic chain of SHIFT's shift_to keymaps, until we get
-- to the primary keymap in the cycle, or until we have completed one
-- cycle.
repeat
km = km.shift_to
if not km or km == mb.current_keymap then
return
end
until not(km.secondary)
end
end
-- Move on to the next primary keymap, rolling over at the end of our list.
for idx, pkm in ipairs(mb.primary_keymaps) do
if pkm == km then
idx = idx + 1
if idx > #mb.primary_keymaps then idx = 1 end
mb.activate_keymap(mb.primary_keymaps[idx].name)
end
end
end
-- Activates a specific keymap by name. Please note that it isn't necessary
-- to "activate" permanent keymaps at all (and thus this deed cannot be done).
function mb.activate_keymap(name)
local keymap = mb.keymaps[name]
if keymap and not keymap.permanent then
mb.current_keymap = keymap
mb.activate_leds()
end
end
--
function mb.grab(name)
mb.grab_keymap = mb.keymaps[name]
mb.activate_leds()
end
function mb.ungrab()
mb.grab_keymap = nil
mb.activate_leds()
end
-- Disables the automatic Keybow lightshow and sets the key LED colors. This -- Disables the automatic Keybow lightshow and sets the key LED colors. This
-- is a well-known (hook) function that gets called by the Keybow firmware -- is a well-known (hook) function that gets called by the Keybow firmware
-- after initialization immediately before waiting for key events. -- after initialization immediately before waiting for key events.
-- luacheck: globals setup
function setup() function setup()
-- Disables the automatic keybow lightshow and switches all key LEDs off -- Disables the automatic keybow lightshow and switches all key LEDs off
-- because the LEDs might be in a random state after power on. -- because the LEDs might be in a random state after power on.

2
setup-tests.sh

@ -6,3 +6,5 @@ sudo apt-get install --yes lua5.3 liblua5.3-dev
sudo update-alternatives --install /usr/bin/lua lua /usr/bin/lua5.3 10 sudo update-alternatives --install /usr/bin/lua lua /usr/bin/lua5.3 10
sudo apt-get install --yes luarocks sudo apt-get install --yes luarocks
sudo luarocks install busted sudo luarocks install busted
sudo luarocks install luasocket
sudo luarocks install luacheck

2
spec/hwkeys.lua

@ -24,7 +24,7 @@ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE. SOFTWARE.
]]-- ]]--
hwk = {} -- module local hwk = {} -- module
-- Convenience: returns the name of a Keybow key handler function for the -- Convenience: returns the name of a Keybow key handler function for the
-- given key number. -- given key number.

4
spec/hwkeys_spec.lua

@ -20,6 +20,8 @@ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE. SOFTWARE.
]]-- ]]--
-- luacheck: globals handle_key_00 handle_key_01
local hwk = require("spec/hwkeys") local hwk = require("spec/hwkeys")
describe("Keybow hardware key handler module", function() describe("Keybow hardware key handler module", function()
@ -61,7 +63,7 @@ describe("Keybow hardware key handler module", function()
assert.equals(2, #seq) assert.equals(2, #seq)
assert.same({true, false}, seq) assert.same({true, false}, seq)
end) end)
end) end)
end) end)

4
spec/layouts/empty_spec.lua

@ -30,11 +30,11 @@ describe("empty multibow keymap", function()
it("installs a single empty primary keymap", function() it("installs a single empty primary keymap", function()
-- Sanity check that there are no registered keymaps yet. -- Sanity check that there are no registered keymaps yet.
assert.is.equal(#mb.registered_keymaps(), 0) assert.is.equal(#mb.registered_keymaps(), 0)
local empty = require("layouts/empty") local empty = require("layouts/empty")
assert.is_not_nil(empty) -- we're going over the top here... assert.is_not_nil(empty) -- we're going over the top here...
assert.is_not_nil(empty.keymap) -- ...even more so. assert.is_not_nil(empty.keymap) -- ...even more so.
-- empty must register exactly one keymap, and it must be -- empty must register exactly one keymap, and it must be
-- a primary keymap, not permanent or secondary. -- a primary keymap, not permanent or secondary.
local kms = mb.registered_keymaps() local kms = mb.registered_keymaps()

12
spec/layouts/kdenlive_spec.lua

@ -30,7 +30,7 @@ describe("Kdenlive keymap", function()
local mb = require("snippets/multibow") local mb = require("snippets/multibow")
local k = require("layouts/kdenlive") local k = require("layouts/kdenlive")
assert.is.equal(k.keymap.name, mb.current_keymap.name) assert.is.equal(k.keymap.name, mb.current_keymap.name)
local kms = mb.registered_keymaps() local kms = mb.registered_keymaps()
assert.is.equal(2, #kms) assert.is.equal(2, #kms)
end) end)
@ -58,19 +58,19 @@ describe("Kdenlive keymap", function()
end end
end end
end) end)
inslit("automatically un-shifts after key press", function() inslit("automatically un-shifts after key press", function()
local some_key = shift.KEY_SHIFT ~= 0 and 0 or 1 local some_key = shift.KEY_SHIFT ~= 0 and 0 or 1
for round = 1, 2 do for round = 1, 2 do -- luacheck: ignore 213
for round = 1, 2 do for round = 1, 2 do -- luacheck: ignore 213 423
assert.equals(k.keymap.name, mb.current_keymap.name) assert.equals(k.keymap.name, mb.current_keymap.name)
hwk.tap(shift.KEY_SHIFT) hwk.tap(shift.KEY_SHIFT)
assert.equals(k.keymap_shifted.name, mb.current_keymap.name) assert.equals(k.keymap_shifted.name, mb.current_keymap.name)
hwk.tap(some_key) hwk.tap(some_key)
assert.equals(k.keymap.name, mb.current_keymap.name) assert.equals(k.keymap.name, mb.current_keymap.name)
end end
for round = 1, 2 do for round = 1, 2 do -- luacheck: ignore 213 423
hwk.tap(shift.KEY_SHIFT) hwk.tap(shift.KEY_SHIFT)
assert.equals(k.keymap_shifted.name, mb.current_keymap.name) assert.equals(k.keymap_shifted.name, mb.current_keymap.name)
hwk.tap(shift.KEY_SHIFT) hwk.tap(shift.KEY_SHIFT)
@ -100,7 +100,7 @@ describe("Kdenlive keymap", function()
hwk.tap(k.KEY_PLAY_AROUND_MOUSE) hwk.tap(k.KEY_PLAY_AROUND_MOUSE)
assert.spy(s).was.called() assert.spy(s).was.called()
hwk.tap(shift.KEY_SHIFT) hwk.tap(shift.KEY_SHIFT)
hwk.tap(k.KEY_PLAY_AROUND_MOUSE) hwk.tap(k.KEY_PLAY_AROUND_MOUSE)
end) end)

2
spec/layouts/keymap-template_spec.lua

@ -31,7 +31,7 @@ describe("template multibow keymap", function()
inslit("installs a single primary keymap", function() inslit("installs a single primary keymap", function()
assert.is_not_nil(kmt) -- we're going over the top here... assert.is_not_nil(kmt) -- we're going over the top here...
assert.is_not_nil(kmt.keymap) -- ...even more so. assert.is_not_nil(kmt.keymap) -- ...even more so.
-- empty must register exactly one keymap, and it must be -- empty must register exactly one keymap, and it must be
-- a primary keymap, not permanent or secondary. -- a primary keymap, not permanent or secondary.
local kms = mb.registered_keymaps() local kms = mb.registered_keymaps()

10
spec/layouts/shift_spec.lua

@ -38,7 +38,7 @@ describe("SHIFT multibow keymap", function()
-- SHIFT must register exactly two keymaps, a primary and a secondary one. -- SHIFT must register exactly two keymaps, a primary and a secondary one.
local kms = mb.registered_keymaps() local kms = mb.registered_keymaps()
assert.is.equal(#kms, 2) assert.is.equal(#kms, 2)
for idx, keymap in ipairs(kms) do for _, keymap in ipairs(kms) do
if keymap == "shift" then if keymap == "shift" then
assert.is_falsy(keymap.permanent) assert.is_falsy(keymap.permanent)
assert.is_falsy(keymap.secondary) assert.is_falsy(keymap.secondary)
@ -47,8 +47,6 @@ describe("SHIFT multibow keymap", function()
assert.is_true(keymap.secondary) assert.is_true(keymap.secondary)
end end
end end
default_key_shift = shift.KEY_SHIFT
end) end)
inslit("accepts changes form default", function() inslit("accepts changes form default", function()
@ -99,7 +97,7 @@ describe("SHIFT multibow keymap", function()
-- but that SHIFT followed by another function doesn't shift. -- but that SHIFT followed by another function doesn't shift.
shift.shift_secondary_keymap:clear() shift.shift_secondary_keymap:clear()
for idx, key in ipairs({ for _, key in ipairs({
shift.KEY_LAYOUT, shift.KEY_LAYOUT,
shift.KEY_BRIGHTNESS shift.KEY_BRIGHTNESS
}) do }) do
@ -117,7 +115,7 @@ describe("SHIFT multibow keymap", function()
} }
local keymap_shifted = { local keymap_shifted = {
name="test-shifted", name="test-shifted",
[0]={press=function(key) end} [0]={press=function(_) end}
} }
keymap.shift_to = keymap_shifted keymap.shift_to = keymap_shifted
keymap_shifted.shift_to = keymap keymap_shifted.shift_to = keymap
@ -189,7 +187,7 @@ describe("SHIFT multibow keymap", function()
s:clear() s:clear()
hwk.press(shift.KEY_SHIFT) hwk.press(shift.KEY_SHIFT)
assert.spy(s).was.called_with( assert.spy(s).was.called_with(
shift.KEY_BRIGHTNESS, shift.KEY_BRIGHTNESS,
shift.next_brightness_color()) shift.next_brightness_color())
-- cycles to next brightness -- cycles to next brightness
hwk.tap(shift.KEY_BRIGHTNESS) hwk.tap(shift.KEY_BRIGHTNESS)

2
spec/layouts/vsc-golang_spec.lua

@ -21,7 +21,7 @@ SOFTWARE.
]]-- ]]--
require "mocked-keybow" require "mocked-keybow"
local hwk = require("spec/hwkeys") require("spec/hwkeys")
describe("VSC golang keymap", function() describe("VSC golang keymap", function()

0
mock/keybow.lua → spec/mock/keybow.lua

54
spec/mock/mocked-keybow.lua

@ -0,0 +1,54 @@
-- Mocks some parts of the Keybow Lua module during unit tests, so we can run
-- the tests outside the Keybow firmware on a standard (full-blown) Lua host
-- system.
--[[
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.
]]--
local busted=require("busted")
local sock=require("socket")
require "keybow"
-- luacheck: globals keybow.no_delay
keybow.no_delay = keybow.no_delay or true
busted.stub(keybow, "auto_lights")
busted.stub(keybow, "clear_lights")
busted.stub(keybow, "load_pattern")
busted.stub(keybow, "set_pixel")
busted.stub(keybow, "set_key")
busted.stub(keybow, "set_modifier")
busted.stub(keybow, "tap_key")
-- luacheck: globals keybow.sleep
function keybow.sleep(ms)
if not keybow.no_delay then
sock.sleep(ms / 1000)
end
end
-- luacheck: globals keybow.usleep
function keybow.usleep(us)
keybow.sleep(us / 1000)
end
return keybow -- adhere to Lua's (new) module rules

50
spec/mocked-keybow_spec.lua

@ -0,0 +1,50 @@
--[[
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.
]]--
-- luacheck: globals keybow.no_delay
require("mocked-keybow")
describe("Mocked Keybow API", function()
local sock=require("socket")
local sleep = function(time, factor, sf, on)
local old = keybow.no_delay
keybow.no_delay = not on
local start = sock.gettime()
sf(time)
local delay = (sock.gettime() - start) * factor
keybow.no_delay = old
return delay
end
it("delays ms or not", function()
assert.is_true(sleep(10, 1000, keybow.sleep, true) >= 10)
assert.is_true(sleep(10, 1000, keybow.sleep, false) < 10)
end)
it("delays us or not", function()
assert.is_true(sleep(10, 1000*1000, keybow.usleep, true) >= 10)
assert.is_true(sleep(10, 1000*1000, keybow.usleep, false) < 10)
end)
end)

4
spec/snippets/keys_spec.lua

@ -44,7 +44,7 @@ describe("multibow keys", function()
mb.tap("x", keybow.LEFT_CTRL, keybow.LEFT_SHIFT) mb.tap("x", keybow.LEFT_CTRL, keybow.LEFT_SHIFT)
assert.spy(tap).was.called(1) assert.spy(tap).was.called(1)
assert.spy(mod).was.called(4) assert.spy(mod).was.called(4)
for _, ud in pairs({keybow.KEY_DOWN, keybow.KEY_UP}) do 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_CTRL, ud)
assert.spy(mod).was.called_with(keybow.LEFT_SHIFT, ud) assert.spy(mod).was.called_with(keybow.LEFT_SHIFT, ud)
end end
@ -61,7 +61,7 @@ describe("multibow keys", function()
assert.spy(tap).was.called(3) assert.spy(tap).was.called(3)
assert.spy(tap).was.called_with("x") assert.spy(tap).was.called_with("x")
assert.spy(mod).was.called(2) assert.spy(mod).was.called(2)
for _, ud in pairs({keybow.KEY_DOWN, keybow.KEY_UP}) do 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_CTRL, ud)
end end
end) end)

2
spec/snippets/leds_spec.lua

@ -40,7 +40,7 @@ describe("multibow LEDs", function()
end) end)
it("cycles brightness", function() it("cycles brightness", function()
function f(b, scale) local f = function(b, scale)
local copy = table.pack(table.unpack(b)) local copy = table.pack(table.unpack(b))
local len = #b local len = #b
for i = 1, len do for i = 1, len do

6
spec/snippets/multibow_spec.lua

@ -109,8 +109,8 @@ describe("multibow", function()
-- on purpose, the names of the primary keymaps are in reverse lexical order, -- on purpose, the names of the primary keymaps are in reverse lexical order,
-- to make sure that cycling follows the registration order, but not the -- to make sure that cycling follows the registration order, but not the
-- name order. -- name order.
local prim1km = { name= "last" } local prim1km = { name= "last", shift_to=nil }
local sec1km = { name="last-shift", secondary=true, shift_to } local sec1km = { name="last-shift", secondary=true, shift_to=nil }
prim1km.shift_to = sec1km prim1km.shift_to = sec1km
local prim2km = { name= "first" } local prim2km = { name= "first" }
mb.register_keymap(prim1km) mb.register_keymap(prim1km)
@ -130,7 +130,7 @@ describe("multibow", function()
inslit("sets up multibow, activates lights", function() inslit("sets up multibow, activates lights", function()
local s = spy.on(_G, "setup") local s = spy.on(_G, "setup")
local al = spy.on(mb, "activate_leds") local al = spy.on(mb, "activate_leds")
_G.setup() _G.setup()
assert.spy(s).was.called(1) assert.spy(s).was.called(1)
assert.spy(al).was.called(1) assert.spy(al).was.called(1)

31
spec/snippets/routehandlers_spec.lua

@ -23,26 +23,26 @@ SOFTWARE.
require "mocked-keybow" require "mocked-keybow"
local hwk = require("spec/hwkeys") local hwk = require("spec/hwkeys")
describe("multibow routehandlers", function() describe("Multibow route handlers", function()
-- ensure to get a fresh multibow module instance each time we run -- ensure to get a fresh multibow module instance each time we run
-- an isolated test... -- an isolated test...
local mb local mb
local spies = mock({ local spies = mock({
prim_key_press=function(key) end, prim_key_press=function(_) end,
prim_key_release=function(key) end, prim_key_release=function(_) end,
prim_otherkey_press=function(key) end, prim_otherkey_press=function(_) end,
prim_otherkey_release=function(key) end, prim_otherkey_release=function(_) end,
sec_key_press=function(key) end, sec_key_press=function(_) end,
sec_key_release=function(key) end, sec_key_release=function(_) end,
perm_key_press=function(key) end, perm_key_press=function(_) end,
perm_key_release=function(key) end, perm_key_release=function(_) end,
grab_key_press=function(key) end, grab_key_press=function(_) end,
grab_key_release=function(key) end, grab_key_release=function(_) end,
}) })
local primary_keymap = { local primary_keymap = {
@ -67,10 +67,9 @@ describe("multibow routehandlers", function()
} }
before_each(function() before_each(function()
require("keybow")
mb = require("snippets/multibow") mb = require("snippets/multibow")
-- make sure to clear our spies -- make sure to clear our spies
for name, schlapphut in pairs(spies) do for _, schlapphut in pairs(spies) do
schlapphut:clear() schlapphut:clear()
end end
end) end)

Loading…
Cancel
Save