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
@ -24,7 +24,7 @@ SOFTWARE.
return {
default = {
lpath = "./sdcard/?.lua;./mock/?.lua",
lpath = "./sdcard/?.lua;./spec/mock/?.lua",
-- Provides an "insl" convenience replacement for busted's insulate() using
-- a fixed descriptive text ... or rather, icon. Please not that "insl"
-- 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 = {
"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
"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
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..._
### 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
> is always active, regardless of keyboard layout. The other keys in this layout
> only become active _while_ holding SHIFT.
> is always active, regardless of keyboard layout. The other keys in this
> layout become only active _while_ holding SHIFT.
```text
╔════╗ ╔╌╌╌╌╗ ╔╌╌╌╌╗ ┌╌╌╌╌┐
@ -169,30 +203,6 @@ The file `keybow.lua` included from
testing purposes is licensed under the MIT license, as declared by Pimoroni's
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
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
-- keymaps, with only the permanent keymaps (SHIFT, etc) being still in place.
-- An empty Multibow layout. Useful for "switching off" any active keymaps,
-- with only the permanent keymaps (SHIFT, etc) being still in place.
--[[
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
@ -20,15 +23,15 @@ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
]]--
-- allow users to set their own configuration before req'ing this
-- module, in order to control the key layout. For defaults, please see
-- below.
local k = _G.kdenlive or {} -- module
require("keybow")
local mk = require("snippets/morekeys")
local mb = require("snippets/multibow")
-- luacheck: ignore 614
--[[
The Keybow layout is as follows when in landscape orientation, with the USB
cable going off "northwards":
@ -77,7 +80,7 @@ end
-- Unshift to primary keymap. For simplification, use it with the "anykey"
-- release handlers, see below.
function k.unshift(keyno)
function k.unshift(_)
mb.activate_keymap(k.keymap.name)
end
@ -97,15 +100,15 @@ k.keymap = k.init_color({
name="kdenlive",
[k.KEY_ZONE_BEGIN] = {press=function() mb.tap("I") 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_END] = {press=function() mb.tap(mk.END) end},
[k.KEY_CLIP_BEGIN] = {press=function() mb.tap(keybow.HOME) 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.COLOR_UNSHIFTED)
k.keymap_shifted = k.init_color({
name="kdenlive-shifted",
secondary=true,
[k.KEY_PROJECT_BEGIN] = {press=function() mb.tap(mk.HOME, keybow.LEFT_CTRL) end},
[k.KEY_PROJECT_END] = {press=function() mb.tap(mk.END, 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(keybow.END, keybow.LEFT_CTRL) end},
[k.KEY_PLAY_AROUND_MOUSE] = {press=function() k.play_around_mouse(keybow.SPACE, keybow.LEFT_ALT) end},
[-1] = {release=k.unshift},
}, k.COLOR_SHIFTED)

7
sdcard/layouts/keymap-template.lua

@ -1,5 +1,4 @@
-- An empty Multibow Keybow layout. Useful for "switching off" any active
-- keymaps, with only the permanent keymaps (SHIFT, etc) being still in place.
-- A Multibow template layout, useful for starting your own keymap layouts.
--[[
Copyright 2019 Harald Albrecht
@ -45,11 +44,11 @@ cable going off "northwards":
]]--
-- Some action on a certain key press...
function km.mypress(keyno)
function km.mypress(keyno) -- luacheck: ignore 212
end
-- Some action on a certain key release...
function km.myrelease(keyno)
function km.myrelease(keyno) -- luacheck: ignore 212
end
-- 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
@ -29,6 +31,7 @@ local shift = _G.shift or {} -- module
local mb = require "snippets/multibow"
-- luacheck: ignore 614
--[[
The Keybow layout is as follows when in landscape orientation, with the USB
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_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
@ -79,12 +82,12 @@ end
-- Remember how many grabbed keys are pressed, so we won't ungrab later until
-- all keys have been released.
function shift.any_press(keyno)
function shift.any_press(_)
grabbed_key_count = grabbed_key_count + 1
end
-- Only ungrab after last key has been released
function shift.any_release(keyno)
function shift.any_release(_)
if grabbed_key_count > 0 then
grabbed_key_count = grabbed_key_count - 1
if grabbed_key_count == 0 then
@ -101,16 +104,13 @@ end
-- 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
shift.any_press(key)
mb.grab(shift.keymap_shifted.name)
end
-- Cycles to the next primary keyboard layout (keymap)
function shift.cycle(key)
function shift.cycle(_)
shift_only = false
mb.cycle_primary_keymaps()
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
@ -52,31 +75,31 @@ vscgo.COLOR_STEPOUT = vscgo.COLOR_STEPOUT or vscgo.BLUEGRAY
-- AND NOW FOR SOMETHING DIFFERENT: THE REAL MEAT --
function vscgo.debug_stop(key)
function vscgo.debug_stop(_)
mb.tap(keybow.F5, keybow.LEFT_SHIFT)
end
function vscgo.debug_restart(key)
function vscgo.debug_restart(_)
mb.tap(keybow.F5, keybow.LEFT_SHIFT, keybow.LEFT_CTRL)
end
function vscgo.debug_continue(key)
function vscgo.debug_continue(_)
mb.tap(keybow.F5)
end
function vscgo.debug_stepover(key)
function vscgo.debug_stepover(_)
mb.tap(keybow.F10)
end
function vscgo.debug_stepinto(key)
function vscgo.debug_stepinto(_)
mb.tap(keybow.F11)
end
function vscgo.debug_stepout(key)
function vscgo.debug_stepout(_)
mb.tap(keybow.F11, keybow.LEFT_SHIFT)
end
function vscgo.go_test_package(key)
function vscgo.go_test_package(_)
mb.tap("P", keybow.LEFT_SHIFT, keybow.LEFT_CTRL)
keybow.sleep(250)
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
@ -22,6 +23,16 @@ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
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
-- 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
@ -36,15 +47,14 @@ end
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
if modifier then keybow.set_modifier(modifier, keybow.KEY_DOWN) end
end
for tap = 1, times do
for _ = 1, times do
keybow.tap_key(key)
keybow.sleep(100)
mb.delay()
end
for modifier_argno = 1, select("#", ...) do
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

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
@ -23,6 +24,8 @@ SOFTWARE.
]]--
-- luacheck: globals mb
mb.MIN_BRIGHTNESS = mb.MIN_BRIGHTNESS or 0.1
-- 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)
end
end
-- Restores Keybow LEDs according to current keymap and the permanent keymaps.
function mb.activate_leds()
keybow.clear_lights()
@ -74,14 +77,14 @@ function mb.activate_leds()
end
-- ...then update LEDs from permanent keymap(s), as this ensures that
-- the permanent keymaps take precedence.
for name, keymap in pairs(mb.keymaps) do
for _, keymap in pairs(mb.keymaps) do
if keymap.permanent then
mb.activate_keymap_leds(keymap)
end
end
end
end
-- Helper function that iterates over all keymap elements but skipping non-key
-- bindings.
function mb.activate_keymap_leds(keymap)
@ -93,4 +96,3 @@ function mb.activate_keymap_leds(keymap)
end
end
end

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

@ -1,9 +1,9 @@
--[[
Provide additional keybow USB HID key definitions.
For more information about USB HID keyboard scan codes, for instance,
see: https://gist.github.com/MightyPork/6da26e382a7ad91b5496ee55fdc73db2
-- Multibow module providing additional USB HID keycode definitions to augment
-- the existing keybow definitions. For more information about USB HID
-- keyboard scan codes, for instance, see:
-- https://gist.github.com/MightyPork/6da26e382a7ad91b5496ee55fdc73db2
--[[
Copyright 2019 Harald Albrecht
Permission is hereby granted, free of charge, to any person obtaining a copy
@ -27,6 +27,11 @@ SOFTWARE.
require("keybow")
-- Tell luacheck that it is okay in this specific case to change the keybow
-- global.
-- luacheck: globals keybow
keybow.SYSRQ = 0x46
keybow.SCROLLLOCK = 0x47
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
@ -22,6 +24,9 @@ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
]] --
-- luacheck: globals mb
-- This all-key, central key router forwards Keybow key events to their
-- 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
-- permanent keymaps first. Remember, there cannot be "any" handlers
-- with permanent keymaps.
for name, keymap in pairs(mb.keymaps) do
for _, keymap in pairs(mb.keymaps) do
if keymap.permanent then
keydef = keymap[keyno]
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
@ -20,149 +23,25 @@ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
]]--
mb = {} -- module
-- luacheck: globals mb
mb = mb or {} -- module
require "keybow"
-- Pulls in the individual modules that make up Multibow.
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/routehandlers")
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
-- is a well-known (hook) function that gets called by the Keybow firmware
-- after initialization immediately before waiting for key events.
-- luacheck: globals setup
function setup()
-- Disables the automatic keybow lightshow and switches all key LEDs off
-- 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 apt-get install --yes luarocks
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.
]]--
hwk = {} -- module
local hwk = {} -- module
-- Convenience: returns the name of a Keybow key handler function for the
-- 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.
]]--
-- luacheck: globals handle_key_00 handle_key_01
local hwk = require("spec/hwkeys")
describe("Keybow hardware key handler module", function()
@ -61,7 +63,7 @@ describe("Keybow hardware key handler module", function()
assert.equals(2, #seq)
assert.same({true, false}, seq)
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()
-- Sanity check that there are no registered keymaps yet.
assert.is.equal(#mb.registered_keymaps(), 0)
local empty = require("layouts/empty")
assert.is_not_nil(empty) -- we're going over the top here...
assert.is_not_nil(empty.keymap) -- ...even more so.
-- empty must register exactly one keymap, and it must be
-- a primary keymap, not permanent or secondary.
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 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)
@ -58,19 +58,19 @@ describe("Kdenlive keymap", function()
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
for round = 1, 2 do -- luacheck: ignore 213
for round = 1, 2 do -- luacheck: ignore 213 423
assert.equals(k.keymap.name, mb.current_keymap.name)
hwk.tap(shift.KEY_SHIFT)
assert.equals(k.keymap_shifted.name, mb.current_keymap.name)
hwk.tap(some_key)
assert.equals(k.keymap.name, mb.current_keymap.name)
end
for round = 1, 2 do
for round = 1, 2 do -- luacheck: ignore 213 423
hwk.tap(shift.KEY_SHIFT)
assert.equals(k.keymap_shifted.name, mb.current_keymap.name)
hwk.tap(shift.KEY_SHIFT)
@ -100,7 +100,7 @@ describe("Kdenlive keymap", function()
hwk.tap(k.KEY_PLAY_AROUND_MOUSE)
assert.spy(s).was.called()
hwk.tap(shift.KEY_SHIFT)
hwk.tap(k.KEY_PLAY_AROUND_MOUSE)
end)

2
spec/layouts/keymap-template_spec.lua

@ -31,7 +31,7 @@ describe("template multibow 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.keymap) -- ...even more so.
-- empty must register exactly one keymap, and it must be
-- a primary keymap, not permanent or secondary.
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.
local kms = mb.registered_keymaps()
assert.is.equal(#kms, 2)
for idx, keymap in ipairs(kms) do
for _, keymap in ipairs(kms) do
if keymap == "shift" then
assert.is_falsy(keymap.permanent)
assert.is_falsy(keymap.secondary)
@ -47,8 +47,6 @@ describe("SHIFT multibow keymap", function()
assert.is_true(keymap.secondary)
end
end
default_key_shift = shift.KEY_SHIFT
end)
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.
shift.shift_secondary_keymap:clear()
for idx, key in ipairs({
for _, key in ipairs({
shift.KEY_LAYOUT,
shift.KEY_BRIGHTNESS
}) do
@ -117,7 +115,7 @@ describe("SHIFT multibow keymap", function()
}
local keymap_shifted = {
name="test-shifted",
[0]={press=function(key) end}
[0]={press=function(_) end}
}
keymap.shift_to = keymap_shifted
keymap_shifted.shift_to = keymap
@ -189,7 +187,7 @@ describe("SHIFT multibow keymap", function()
s:clear()
hwk.press(shift.KEY_SHIFT)
assert.spy(s).was.called_with(
shift.KEY_BRIGHTNESS,
shift.KEY_BRIGHTNESS,
shift.next_brightness_color())
-- cycles to next brightness
hwk.tap(shift.KEY_BRIGHTNESS)

2
spec/layouts/vsc-golang_spec.lua

@ -21,7 +21,7 @@ SOFTWARE.
]]--
require "mocked-keybow"
local hwk = require("spec/hwkeys")
require("spec/hwkeys")
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)
assert.spy(tap).was.called(1)
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_SHIFT, ud)
end
@ -61,7 +61,7 @@ describe("multibow keys", function()
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
for _, ud in pairs({keybow.KEY_DOWN, keybow.KEY_UP}) do
assert.spy(mod).was.called_with(keybow.LEFT_CTRL, ud)
end
end)

2
spec/snippets/leds_spec.lua

@ -40,7 +40,7 @@ describe("multibow LEDs", function()
end)
it("cycles brightness", function()
function f(b, scale)
local f = function(b, scale)
local copy = table.pack(table.unpack(b))
local len = #b
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,
-- to make sure that cycling follows the registration order, but not the
-- name order.
local prim1km = { name= "last" }
local sec1km = { name="last-shift", secondary=true, shift_to }
local prim1km = { name= "last", shift_to=nil }
local sec1km = { name="last-shift", secondary=true, shift_to=nil }
prim1km.shift_to = sec1km
local prim2km = { name= "first" }
mb.register_keymap(prim1km)
@ -130,7 +130,7 @@ describe("multibow", function()
inslit("sets up multibow, activates lights", function()
local s = spy.on(_G, "setup")
local al = spy.on(mb, "activate_leds")
_G.setup()
assert.spy(s).was.called(1)
assert.spy(al).was.called(1)

31
spec/snippets/routehandlers_spec.lua

@ -23,26 +23,26 @@ SOFTWARE.
require "mocked-keybow"
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
-- an isolated test...
local mb
local spies = mock({
prim_key_press=function(key) end,
prim_key_release=function(key) end,
prim_otherkey_press=function(key) end,
prim_otherkey_release=function(key) end,
sec_key_press=function(key) end,
sec_key_release=function(key) end,
perm_key_press=function(key) end,
perm_key_release=function(key) end,
grab_key_press=function(key) end,
grab_key_release=function(key) end,
prim_key_press=function(_) end,
prim_key_release=function(_) end,
prim_otherkey_press=function(_) end,
prim_otherkey_release=function(_) end,
sec_key_press=function(_) end,
sec_key_release=function(_) end,
perm_key_press=function(_) end,
perm_key_release=function(_) end,
grab_key_press=function(_) end,
grab_key_release=function(_) end,
})
local primary_keymap = {
@ -67,10 +67,9 @@ describe("multibow routehandlers", function()
}
before_each(function()
require("keybow")
mb = require("snippets/multibow")
-- make sure to clear our spies
for name, schlapphut in pairs(spies) do
for _, schlapphut in pairs(spies) do
schlapphut:clear()
end
end)

Loading…
Cancel
Save