commit 59850fd49befc928a4ce338f05608e4b443a9125 Author: Harald Albrecht Date: Thu Jan 3 20:47:03 2019 +0100 initial version of multibow diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..46eeb12 --- /dev/null +++ b/LICENSE @@ -0,0 +1,19 @@ +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. diff --git a/README.md b/README.md new file mode 100644 index 0000000..180c343 --- /dev/null +++ b/README.md @@ -0,0 +1,59 @@ +# Multibow + +GitHub: [github.com/thediveo/multibow](https://github.com/thediveo/multibow) + +Multibow turns a [Pimoroni Keybow](https://shop.pimoroni.com/products/keybow) +into a macro keyboard with multiple layouts, switchable at any time. (Keybow is +a solderless DIY 4x3 mechanical USB keyboard, powered by a Raspberry Pi. And +yes, these days even keyboards run Linux...) + +![Multibow on Keybow](multibow.jpg) + +Layouts included: +- Debug Go programs and packages in VisualStudio Code with its Go extension. +- "Empty" keyboard layout what does nothing, very useful when using cycling between different keyboard layouts to have one non-reacting layout. +- permanent layout for cycling between the other layouts and for changing the keybow LED brightness. + +## Licenses + +Multibow is (c) 2019 Harald Albrecht and is licensed under the MIT license, see +the [LICENSE](LICENSE) file. + +The file `keybow.lua` included from +[pimoroni/keybow-firmware](https://github.com/pimoroni/keybow-firmware) for +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/empty" -- empty, do-nothing layout. +``` + + +## Developing + +For some basic testing, run `lua test.lua` from the base directory of this +repository. It pulls in `keybow`, then mocks some functionality of it, and +finally starts `sdcard/keys.lua` as usual. + +This helps in detecting syntax and logic erros early, avoiding the +rinse-and-repeat cycle with copying to microSD card, starting the Keybow +hardware, and then wondering what went wrong, without any real clue. diff --git a/mock/keybow.lua b/mock/keybow.lua new file mode 100644 index 0000000..6d8fd46 --- /dev/null +++ b/mock/keybow.lua @@ -0,0 +1,210 @@ +keybow = {} + +local KEYCODES = "abcdefghijklmnopqrstuvwxyz1234567890\n\a\b\t -=[]\\#;'`,./" +local SHIFTED_KEYCODES = "ABCDEFGHIJKLMNOPQRSTUVWXYZ!@#$%^&*()\a\a\a\a\a_+{}|~:\"~<>?" + +keybow.LEFT_CTRL = 0 +keybow.LEFT_SHIFT = 1 +keybow.LEFT_ALT = 2 +keybow.LEFT_META = 3 + +keybow.RIGHT_CTRL = 4 +keybow.RIGHT_SHIFT = 5 +keybow.RIGHT_ALT = 6 +keybow.RIGHT_META = 7 + +keybow.ENTER = 0x28 +keybow.ESC = 0x29 +keybow.BACKSPACE = 0x2a +keybow.TAB = 0x2b +keybow.SPACE = 0x2c +keybow.CAPSLOCK = 0x39 + +keybow.LEFT_ARROW = 0x50 +keybow.RIGHT_ARROW = 0x4f +keybow.UP_ARROW = 0x52 +keybow.DOWN_ARROW = 0x51 + +keybow.F1 = 0x3a +keybow.F2 = 0x3b +keybow.F3 = 0x3c +keybow.F4 = 0x3d +keybow.F5 = 0x3e +keybow.F6 = 0x3f +keybow.F7 = 0x40 +keybow.F8 = 0x41 +keybow.F9 = 0x42 +keybow.F10 = 0x43 +keybow.F11 = 0x44 +keybow.F12 = 0x45 + +keybow.KEY_DOWN = true +keybow.KEY_UP = false + +-- Functions exposed from C + +function keybow.set_modifier(key, state) + keybow_set_modifier(key, state) +end + +function keybow.sleep(time) + keybow_sleep(time) +end + +function keybow.usleep(time) + keybow_usleep(time) +end + +function keybow.text(text) + for i = 1, #text do + local c = text:sub(i, i) + keybow.tap_key(c) + end + + keybow.set_modifier(keybow.LEFT_SHIFT, false) +end + +-- Lighting control + +function keybow.set_pixel(x, r, g, b) + keybow_set_pixel(x, r, g, b) +end + +function keybow.auto_lights(state) + keybow_auto_lights(state) +end + +function keybow.clear_lights() + keybow_clear_lights() +end + +function keybow.load_pattern(file) + keybow_load_pattern(file) +end + +-- Meta keys - ctrl, shift, alt and win/apple + +function keybow.tap_left_ctrl() + keybow.set_modifier(keybow.LEFT_CTRL, keybow.KEY_DOWN) + keybow.set_modifier(keybow.LEFT_CTRL, keybow.KEY_UP) +end + +function keybow.tap_right_ctrl() + keybow.set_modifier(keybow.RIGHT_CTRL, keybow.KEY_DOWN) + keybow.set_modifier(keybow.RIGHT_CTRL, keybow.KEY_UP) +end + +function keybow.tap_left_shift() + keybow.set_modifier(keybow.LEFT_SHIFT, keybow.KEY_DOWN) + keybow.set_modifier(keybow.LEFT_SHIFT, keybow.KEY_UP) +end + +function keybow.tap_right_shift() + keybow.set_modifier(keybow.RIGHT_SHIFT, keybow.KEY_DOWN) + keybow.set_modifier(keybow.RIGHT_SHIFT, keybow.KEY_UP) +end + +function keybow.tap_left_alt() + keybow.set_modifier(keybow.LEFT_ALT, keybow.KEY_DOWN) + keybow.set_modifier(keybow.LEFT_ALT, keybow.KEY_UP) +end + +function keybow.tap_right_alt() + keybow.set_modifier(keybow.RIGHT_ALT, keybow.KEY_DOWN) + keybow.set_modifier(keybow.RIGHT_ALT, keybow.KEY_UP) +end + +function keybow.tap_left_meta() + keybow.set_modifier(keybow.LEFT_META, keybow.KEY_DOWN) + keybow.set_modifier(keybow.LEFT_META, keybow.KEY_UP) +end + +function keybow.tap_right_meta() + keybow.set_modifier(keybow.RIGHT_META, keybow.KEY_DOWN) + keybow.set_modifier(keybow.RIGHT_META, keybow.KEY_UP) +end + +-- Function keys + +function keybow.tap_function_key(index) + index = 57 + index -- Offset to 0x39 (F1 is 0x3a) + keybow.set_key(index, true) + keybow.set_key(index, false) +end + +function keybow.ascii_to_shift(key) + if not (type(key) == "string") then + return false + end + + return SHIFTED_KEYCODES.find(key) ~= nil +end + +function keybow.ascii_to_hid(key) + if not (type(key) == "string") then + return key + end + + key = key:lower() + + code = KEYCODES:find(key) + + if code == nil then return nil end + + return code + 3 +end + +function keybow.set_key(key, pressed) + if type(key) == "string" then + local hid_code = nil + local shifted = SHIFTED_KEYCODES:find(key, 1, true) ~= nil + + if shifted then + hid_code = SHIFTED_KEYCODES:find(key, 1, true) + else + hid_code = KEYCODES:find(key, 1, true) + end + + if not (hid_code == nil) then + hid_code = hid_code + 3 + if shifted then keybow.set_modifier(keybow.LEFT_SHIFT, pressed) end + keybow_set_key(hid_code, pressed) + end + + else -- already a key code + keybow_set_key(key, pressed) + end +end + +function keybow.tap_enter() + keybow.set_key(keybow.ENTER, true) + keybow.set_key(keybow.ENTER, false) +end + +function keybow.tap_space() + keybow.set_key(keybow.SPACE, true) + keybow.set_key(keybow.SPACE, false) +end + +function keybow.tap_shift() + keybow.set_key(keybow.LEFT_SHIFT, true) + keybow.set_key(keybow.LEFT_SHIFT, false) +end + +function keybow.tap_tab() + keybow.set_key(keybow.TAB, true) + keybow.set_key(keybow.TAB, false) +end + +function keybow.tap_key(key) + keybow.set_key(key, true) + keybow.set_key(key, false) +end + +function keybow.press_key(key) + keybow.set_key(key, true) +end + +function keybow.release_key(key) + keybow.set_key(key, false) +end diff --git a/mock/mockkeybow.lua b/mock/mockkeybow.lua new file mode 100644 index 0000000..8a1efa9 --- /dev/null +++ b/mock/mockkeybow.lua @@ -0,0 +1,24 @@ +require "keybow" + +function keybow.set_pixel(pix, r, g, b) + -- print("set_pixel", pix, r, g, b) +end + +function keybow.auto_lights(onoff) + -- print("auto_lights", onoff) +end + +function keybow.clear_lights() + -- print("clear_lights") +end + +function keybow.tap_key(key) + print("tap_key", key) +end + +function keybow.set_modifier(mod, key) + print("set_modifier", mod, key) +end + +require "keys" +setup() diff --git a/multibow.jpg b/multibow.jpg new file mode 100644 index 0000000..035b069 Binary files /dev/null and b/multibow.jpg differ diff --git a/sdcard/keys.lua b/sdcard/keys.lua new file mode 100644 index 0000000..c719f77 --- /dev/null +++ b/sdcard/keys.lua @@ -0,0 +1,4 @@ +require "keybow" +require "layouts/shift" +require "layouts/vsc-golang" +require "layouts/empty" diff --git a/sdcard/layouts/empty.lua b/sdcard/layouts/empty.lua new file mode 100644 index 0000000..a7eb059 --- /dev/null +++ b/sdcard/layouts/empty.lua @@ -0,0 +1,22 @@ +-- An empty Multibow Keybow layout. Useful for "switching off" the keyboard, + +require "snippets/multibow" + +--[[ +The Keybow layout is as follows when in landscape orientation, with the USB +cable going off "northwards": + + ┋┋ +┌────┐ ┌────┐ ┌────┐ ┌────┐ +│ 11 │ │ 8 │ │ 5 │ │ 2 │ +└────┘ └────┘ └────┘ └────┘ +┌────┐ ┌────┐ ┌────┐ ┌────┐ +│ 10 │ │ 7 │ │ 4 │ │ 1 │ +└────┘ └────┘ └────┘ └────┘ +┌────┐ ┌────┐ ┌────┐ ┌────┐ +│ 9 │ │ 6 │ │ 3 │ │ 0 │ +└────┘ └────┘ └────┘ └────┘ + +]]-- + +mb.register_keymap({}, "empty") diff --git a/sdcard/layouts/shift.lua b/sdcard/layouts/shift.lua new file mode 100644 index 0000000..1ef526e --- /dev/null +++ b/sdcard/layouts/shift.lua @@ -0,0 +1,38 @@ +-- A permanent SHIFT layout toggler + +require "snippets/multibow" + +--[[ +The Keybow layout is as follows when in landscape orientation, with the USB +cable going off "northwards": + + ┋┋ +┌────┐ ┌────┐ ┌────┐ ┌────┐ +│ 11 │ │ 8 │ │ 5 │ │ 2 │ +└────┘ └────┘ └────┘ └────┘ +┌────┐ ┌────┐ ┌────┐ ┌────┐ +│ 10 │ │ 7 │ │ 4 │ │ 1 │ +└────┘ └────┘ └────┘ └────┘ +┌────┐ ┌────┐ ┌────┐ ┌────┐ +│ 9 │ │ 6 │ │ 3 │ │ 0 │ +└────┘ └────┘ └────┘ └────┘ + +]]-- + +shift = {} + +function shift.cycle(key) + print("permanent SHIFT") + mb.cycle_keymaps() +end + +function shift.brightness(key) + local b = mb.brightness + 0.3 + if b > 1 then; b = 0.4; end + mb.set_brightness(b) +end + +mb.register_permanent_keymap({ + [11] = {c={r=1, g=1, b=1}, h=shift.cycle}, + [8] = {c={r=0.5, g=0.5, b=0.5}, h=shift.brightness} +}, "shift") diff --git a/sdcard/layouts/vsc-golang.lua b/sdcard/layouts/vsc-golang.lua new file mode 100644 index 0000000..3956618 --- /dev/null +++ b/sdcard/layouts/vsc-golang.lua @@ -0,0 +1,74 @@ +-- VSC Go extension debug Keybow layout + +require "snippets/multibow" + +--[[ +The Keybow layout is as follows when in landscape orientation, with the USB +cable going off "northwards": + + ┋┋ +┌────┐ ┌────┐ ┌────┐ ┌────┐ +│ 11 │ │ 8 │ │ 5 │ │ 2 │ +└────┘ └────┘ └────┘ └────┘ +┌────┐ ┌────┐ ┌────┐ ┌────┐ +│ 10 │ │ 7 │ │ 4 │ │ 1 │ +└────┘ └────┘ └────┘ └────┘ +┌────┐ ┌────┐ ┌────┐ ┌────┐ +│ 9 │ │ 6 │ │ 3 │ │ 0 │ +└────┘ └────┘ └────┘ └────┘ + +]]-- + +RED = { r=1, g=0, b=0 } +YELLOW = { r=1, g=0.8, b=0 } +GREEN = { r=0, g=1, b=0 } +BLUE = { r=0, g=0, b=1 } +BLUECYAN = { r=0, g=0.7, b=1 } +BLUEGRAY = { r=0.7, g=0.7, b=1 } +CYAN = { r=0, g=1, b=1 } + + +-- AND NOW FOR SOMETHING DIFFERENT: THE REAL MEAT -- + +function debug_stop(key) + mb.tap(key, keybow.F5, keybow.LEFT_SHIFT) +end + +function debug_restart(key) + mb.tap(key, keybow.F5, keybow.LEFT_SHIFT, keybow.LEFT_CTRL) +end + +function debug_continue(key) + mb.tap(key, keybow.F5) +end + +function debug_stepover(key) + mb.tap(key, keybow.F10) +end + +function debug_stepinto(key) + mb.tap(key, keybow.F11) +end + +function debug_stepout(key) + mb.tap(key, keybow.F11, keybow.LEFT_SHIFT) +end + +function go_test_package(key) + mb.tap(key, "P", keybow.LEFT_SHIFT, keybow.LEFT_CTRL) + keybow.sleep(250) + keybow.text("go test package") + keybow.tap_enter() +end + + +mb.register_keymap({ + [10] = {c=RED, h=debug_stop}, + [7] = {c=YELLOW, h=debug_restart}, + [1] = {c=CYAN, h=go_test_package}, + + [9] = {c=GREEN, h=debug_continue}, + [6] = {c=BLUECYAN, h=debug_stepinto}, + [3] = {c=BLUE, h=debug_stepover}, + [0] = {c=BLUEGRAY, h=debug_stepout}, +}, 'vsc-golang-debug') diff --git a/sdcard/snippets/morekeys.lua b/sdcard/snippets/morekeys.lua new file mode 100644 index 0000000..a991c2e --- /dev/null +++ b/sdcard/snippets/morekeys.lua @@ -0,0 +1,108 @@ +--[[ +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 + +Copyright 2019 Harald Albrecht + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +]]-- + +require("keybow") + +keybow.SYSRQ = 0x46 +keybow.SCROLLLOCK = 0x47 +keybow.PAUSE = 0x48 +keybow.INSERT = 0x49 +keybow.DELETE = 0x4c +keybow.HOME = 0x4a +keybow.END = 0x4d +keybow.PAGEUP = 0x4b +keybow.PAGEOWN = 0x4e + +keybow.F13 = 0x68 +keybow.F14 = 0x69 +keybow.F15 = 0x6a +keybow.F16 = 0x6b +keybow.F17 = 0x6c +keybow.F18 = 0x6d +keybow.F19 = 0x6e +keybow.F20 = 0x6f +keybow.F21 = 0x70 +keybow.F22 = 0x71 +keybow.F23 = 0x72 +keybow.F24 = 0x73 + +keybow.KPSLASH = 0x54 +keybow.KPASTERISK = 0x55 +keybow.KPMINUS = 0x56 +keybow.KPPLUS = 0x57 +keybow.KPENTER = 0x58 +keybow.KP1 = 0x59 +keybow.KP2 = 0x5a +keybow.KP3 = 0x5b +keybow.KP4 = 0x5c +keybow.KP5 = 0x5d +keybow.KP6 = 0x5e +keybow.KP7 = 0x5f +keybow.KP8 = 0x60 +keybow.KP9 = 0x61 +keybow.KP0 = 0x62 +keybow.KPDOT = 0x63 +keybow.KPEQUAL = 0x67 + +keybow.COMPOSE = 0x65 +keybow.POWER = 0x66 + +keybow.OPEN = 0x74 +keybow.HELP = 0x75 +keybow.PROPS = 0x76 +keybow.FRONT = 0x77 +keybow.STOP = 0x78 +keybow.AGAIN = 0x79 +keybow.UNDO = 0x7a +keybow.CUT = 0x7b +keybow.COPY = 0x7c +keybow.PASTE = 0x7d +keybow.FIND = 0x7e +keybow.MUTE = 0x7f +keybow.VOLUMEUP = 0x80 +keybow.VOLUMEDOWN = 0x81 + +keybow.MEDIA_PLAYPAUSE = 0xe8 +keybow.MEDIA_STOPCD = 0xe9 +keybow.MEDIA_PREVIOUSSONG = 0xea +keybow.MEDIA_NEXTSONG = 0xeb +keybow.MEDIA_EJECTCD = 0xec +keybow.MEDIA_VOLUMEUP = 0xed +keybow.MEDIA_VOLUMEDOWN = 0xee +keybow.MEDIA_MUTE = 0xef +keybow.MEDIA_WWW = 0xf0 +keybow.MEDIA_BACK = 0xf1 +keybow.MEDIA_FORWARD = 0xf2 +keybow.MEDIA_STOP = 0xf3 +keybow.MEDIA_FIND = 0xf4 +keybow.MEDIA_SCROLLUP = 0xf5 +keybow.MEDIA_SCROLLDOWN = 0xf6 +keybow.MEDIA_EDIT = 0xf7 +keybow.MEDIA_SLEEP = 0xf8 +keybow.MEDIA_COFFEE = 0xf9 +keybow.MEDIA_REFRESH = 0xfa +keybow.MEDIA_CALC = 0xfb diff --git a/sdcard/snippets/multibow.lua b/sdcard/snippets/multibow.lua new file mode 100644 index 0000000..9b5e7f6 --- /dev/null +++ b/sdcard/snippets/multibow.lua @@ -0,0 +1,176 @@ +--[[ +Copyright 2019 Harald Albrecht + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +]]-- + +require "keybow" + +mb = {} +mb.path = (...):match("^(.-)[^%/]+$") + +require(mb.path .. "morekeys") +require(mb.path .. "routehandlers") + + +mb.brightness = 0.4 +mb.keymaps = {} +mb.permanent_keymaps = {} +mb.current_keymap_set = nil +mb.current_keymap_idx = 0 + + +-- +function mb.tap(keyno, key, ...) + for modifier_argno = 1, select('#', ...) do + local modifier = select(modifier_argno, ...) + if modifier then; keybow.set_modifier(modifier, keybow.KEY_DOWN); end + end + keybow.tap_key(key) + for modifier_argno = 1, select('#', ...) do + local modifier = select(modifier_argno, ...) + if modifier then; keybow.set_modifier(modifier, keybow.KEY_UP); end + end +end + + +-- Registers a keymap by name (and optional) index. The name is simply used +-- for allowing multiple SHIFT (modifier) levels for the same keyboard layout. +-- The indices are then used to differentiate between different SHIFT/modifier +-- levels of the same keyboard layout. +-- +-- Indices start at 0, this is the un-SHIFT-ed keymap for a layout. +-- +-- If this is the first keymap getting registered, then it will also made +-- activated. +function mb.register_keymap(keymap, name, index) + -- register + local kms = mb.keymaps[name] + kms = kms and kms or {} -- if name isn't known yet, create new keymap array for name + local index = index and #kms or 0 -- appends keymap if index is nil + kms[index] = keymap + mb.keymaps[name] = kms + -- ensure that first registered keymap also automatically gets activated + -- (albeit the LEDs will only update later). + if mb.current_keymap_set == nil then + mb.current_keymap_set = kms + mb.current_keymap_idx = index + end +end + + +-- Cycles through the available (non-permanent) keymaps. When switching to +-- the next keymap, we will always activate the index 0 layout. +function mb.cycle_keymaps() + local first_kms + local first_name + local next = false + local next_kms + local next_name + for name, kms in pairs(mb.keymaps) do + if first_kms == nil then + first_kms = kms; first_name = name + end + if kms == mb.current_keymap_set then + next = true + elseif next then + next_kms = kms; next_name = name + next = false + end + end + if next_kms == nil then + next_kms = first_kms; next_name = first_name + end + mb.activate_keymap(next_name, 0) +end + + +-- Activates a specific keymap by name and index. +function mb.activate_keymap(name, index) + print("activate_keymap", name, index) + local kms = mb.keymaps[name] + mb.current_keymap_set = kms + mb.current_keymap_idx = index + keybow.clear_lights() + mb.activate_leds() +end + + +-- Registers a permanent keymap (as opposed to switchable keymaps). As their +-- name suggest, permanent keymaps are permanently active and have priority +-- over any "standard" activated keymap. As they are permanent, there is no +-- "index" sub-layout mechanism, but instead all permanent keymaps are always +-- active. If permanent keymaps define overlapping keys, then the result is +-- undefined. +function mb.register_permanent_keymap(keymap, name) + -- register + mb.permanent_keymaps[name] = keymap + mb.activate_leds() +end + + +-- Sets the Keybow key LEDs maximum brightness, in the range [0.1..1]. +function mb.set_brightness(brightness) + if brightness < 0.1 then; brightness = 0.1; end + if brightness > 1 then; brightness = 1; end + mb.brightness = brightness + mb.activate_leds() +end + + +-- Sets key LED to specific color, taking brightness into consideration. +-- The color is a triple (table) with the elements r, g, and b. Each color +-- component is in the range [0..1]. +function mb.led(keyno, color) + if color ~= nil then + keybow.set_pixel(keyno, + color.r * mb.brightness * 255, color.g * mb.brightness * 255, color.b * mb.brightness * 255) + else + keybow.set_pixel(keyno, 0, 0, 0) + end +end + + +-- Restores Keybow LEDs according to current keymap and the permanent keymaps. +function mb.activate_leds() + -- current keymap + if mb.current_keymap_set ~= nil then + for keyno, keydef in pairs(mb.current_keymap_set[mb.current_keymap_idx]) do + print("LED", keyno) + mb.led(keyno, keydef.c) + end + end + -- permanent keymap(s) + for name, pkm in pairs(mb.permanent_keymaps) do + for keyno, keydef in pairs(pkm) do + print("pLED", keyno) + mb.led(keyno, keydef.c) + end + end +end + + +-- Disables the automatic Keybow lightshow and sets the key LED colors. +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. + keybow.auto_lights(false) + keybow.clear_lights() + mb.activate_leds() +end diff --git a/sdcard/snippets/routehandlers.lua b/sdcard/snippets/routehandlers.lua new file mode 100644 index 0000000..14abcc7 --- /dev/null +++ b/sdcard/snippets/routehandlers.lua @@ -0,0 +1,63 @@ +--[[ +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. +]]-- + +-- This all-key, central key router forwards Keybow key events to +-- their correct handlers, depending on which keyboard layout currently +-- is active. +function mb.route(keyno, pressed) + -- Check key in permanent keymaps + local keydef + for name, pkm in pairs(mb.permanent_keymaps) do + keydef = pkm[keyno] + if keydef ~= nil then; break; end + end + -- Check key in current keymap + if keydef == nil and mb.current_keymap_set ~= nil then + keydef = mb.current_keymap_set[mb.current_keymap_idx][keyno] + end + + if keydef == nil then; return; end + + if pressed then + for led = 0, 11 do + if led ~= keyno then; mb.led(led, {r=0, g=0, b=0}); end + end + print("route to key #", keyno) + keydef.h(keyno) + else + mb.activate_leds() + end +end + +-- Routes all keybow key handling through our central key router +function handle_key_00(pressed); mb.route(0, pressed); end +function handle_key_01(pressed); mb.route(1, pressed); end +function handle_key_02(pressed); mb.route(2, pressed); end +function handle_key_03(pressed); mb.route(3, pressed); end +function handle_key_04(pressed); mb.route(4, pressed); end +function handle_key_05(pressed); mb.route(5, pressed); end +function handle_key_06(pressed); mb.route(6, pressed); end +function handle_key_07(pressed); mb.route(7, pressed); end +function handle_key_08(pressed); mb.route(8, pressed); end +function handle_key_09(pressed); mb.route(9, pressed); end +function handle_key_10(pressed); mb.route(10, pressed); end +function handle_key_11(pressed); mb.route(11, pressed); end diff --git a/test.lua b/test.lua new file mode 100644 index 0000000..bec4718 --- /dev/null +++ b/test.lua @@ -0,0 +1,30 @@ +package.path = "./sdcard/?.lua;./mock/?.lua;" .. package.path + +require "mockkeybow" + +print() +print("**** key 00...") +handle_key_00(true) +handle_key_00(false) +print() +print("**** key 03...") +handle_key_03(true) +handle_key_03(false) +print() + +print("**** key 11...") +handle_key_11(true) +handle_key_11(false) + +print("**** key 00...") +handle_key_00(true) +handle_key_00(false) + +print() +print("**** key 11...") +handle_key_11(true) +handle_key_11(false) + +print("**** key 00...") +handle_key_00(true) +handle_key_00(false)