fixes primary keymap cycling

This commit is contained in:
Harald Albrecht 2019-01-06 21:53:17 +01:00
parent f2682864fb
commit 9848f34d27
2 changed files with 150 additions and 78 deletions

View File

@ -31,8 +31,15 @@ require(mb.path .. "routehandlers")
mb.brightness = 0.4 mb.brightness = 0.4
-- The registered keymaps, indexed by their names (.name field).
mb.keymaps = {} 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 mb.current_keymap = nil
-- A temporary keymap while grabbing.
mb.grab_keymap = nil mb.grab_keymap = nil
@ -49,7 +56,6 @@ function mb.tap(keyno, key, ...)
end end
end end
-- Registers a keymap (by name), so it can be easily activated later by its name. -- 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 -- Multiple keymaps can be registered. Keymaps can be either "primary" by
-- default, or permanent or secondary keymaps. -- default, or permanent or secondary keymaps.
@ -73,13 +79,14 @@ function mb.register_keymap(keymap)
-- register -- register
mb.keymaps[name] = keymap mb.keymaps[name] = keymap
-- ensure that first registered keymap also automatically gets activated -- ensure that first registered keymap also automatically gets activated
-- (albeit the LEDs will only update later). -- (albeit the LEDs will only update later). Also maintain the (ordered)
-- sequence of registered primary keymaps.
if not (keymap.permanent or keymap.secondary) then if not (keymap.permanent or keymap.secondary) then
mb.current_keymap = mb.current_keymap or keymap mb.current_keymap = mb.current_keymap or keymap
table.insert(mb.primary_keymaps, keymap)
end end
end end
-- Returns the list of currently registered keymaps; this list is a table, -- Returns the list of currently registered keymaps; this list is a table,
-- with its registered keymaps at indices 1, 2, ... -- with its registered keymaps at indices 1, 2, ...
function mb.registered_keymaps() function mb.registered_keymaps()
@ -90,35 +97,57 @@ function mb.registered_keymaps()
return keymaps return keymaps
end 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 -- Cycles through the available (primary) keymaps, ignoring secondary and
-- permanent keymaps. This is convenient for assigning primary keymap switching -- permanent keymaps. This is convenient for assigning primary keymap switching
-- using a key on the Keybow device itself. -- using a key on the Keybow device itself.
function mb.cycle_primary_keymaps() function mb.cycle_primary_keymaps()
local first_name local km = mb.current_keymap
local next = false if km == nil then return end
local next_name -- If this is a secondary keymap, locate its corresponding primary keymap.
for name, keymap in pairs(mb.keymaps) do if km.secondary then
if not (keymap.permanent or keymap.secondary) then if not km.shift_to then
-- Remembers the first "primary" keymap. -- No SHIFT's shift_to cyclic chain available, so rely on the naming
first_name = first_name or name -- schema instead and try to locate the primary keymap with the first
-- Notice if we just pass the current keymap and then make sure to pick -- match instead. This assumes that the name of the secondary keymaps
-- up the following primary keymap. -- have some suffix and thus are longer than the name of their
if keymap == mb.current_keymap then -- corresponding primary keymap.
next = true for idx, pkm in ipairs(mb.primary_keymaps) do
elseif next then if string.sub(km.name, 1, #pkm.name) == pkm.name then
next_name = name km = pkm
next = false break
end
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
end end
next_name = next_name or first_name -- Move on to the next primary keymap, rolling over at the end of our list.
if first_name then for idx, pkm in ipairs(mb.primary_keymaps) do
mb.activate_keymap(next_name) 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
end end
-- Activates a specific keymap by name. Please note that it isn't necessary -- 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). -- to "activate" permanent keymaps at all (and thus this deed cannot be done).
function mb.activate_keymap(name) function mb.activate_keymap(name)
@ -150,7 +179,6 @@ function mb.set_brightness(brightness)
mb.activate_leds() mb.activate_leds()
end end
-- Sets key LED to specific color, taking brightness into consideration. -- 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 -- The color is a triple (table) with the elements r, g, and b. Each color
-- component is in the range [0..1]. -- component is in the range [0..1].
@ -163,7 +191,6 @@ function mb.led(keyno, color)
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()
@ -185,7 +212,6 @@ function mb.activate_leds()
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)
@ -197,7 +223,6 @@ function mb.activate_keymap_leds(keymap)
end end
end 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.
@ -209,4 +234,5 @@ function setup()
mb.activate_leds() mb.activate_leds()
end end
return mb -- module return mb -- module

View File

@ -33,69 +33,115 @@ describe("multibow", function()
mb = require("snippets/multibow") mb = require("snippets/multibow")
end) end)
insl(function() inslit("adds permanent keyboard layout, but doesn't activate it", function()
it("adds permanent keyboard layout, but doesn't activate it", function() local permkm = {
local permkm = { name="permanent",
name="permanent", permanent=true
permanent=true }
} assert.is_nil(mb.keymaps["permanent"])
assert.is_nil(mb.keymaps["permanent"]) mb.register_keymap(permkm)
mb.register_keymap(permkm) assert.is.equal(mb.keymaps["permanent"], permkm)
assert.is.equal(mb.keymaps["permanent"], permkm) assert.is_nil(mb.current_keymap)
assert.is_nil(mb.current_keymap)
end)
end) end)
insl(function() inslit("checks multibow module is fresh again", function()
it("checks multibow module is fresh again", function() assert.is_nil(mb.keymaps["permanent"])
assert.is_nil(mb.keymaps["permanent"])
end)
end) end)
insl(function() inslit("adds permanent, then two primary layouts, activates only first primary layout", function()
it("adds permanent, then two primary layouts, activates only first primary layout", function() local permkm = {
local permkm = { name="permanent",
name="permanent", permanent=true
permanent=true }
} mb.register_keymap(permkm)
mb.register_keymap(permkm) local prim1km = { name="bavaria-one" }
local prim1km = { name="bavaria-one" } local prim2km = { name="primary-two" }
local prim2km = { name="primary-two" } mb.register_keymap(prim1km)
mb.register_keymap(prim1km) mb.register_keymap(prim2km)
mb.register_keymap(prim2km) assert.is.equal(prim1km, mb.current_keymap)
assert.is.equal(mb.current_keymap, prim1km)
end)
end) end)
insl(function() inslit("sequence of primary keymaps is in registration order", function()
it("adds secondary, then primary layout, activates only primary layout", function() local prim1km = { name="last" }
local primkm = { name="berlin" } local prim2km = { name="first" }
local seckm = { name="munich", secondary=true } mb.register_keymap(prim1km)
mb.register_keymap(seckm) mb.register_keymap(prim2km)
mb.register_keymap(primkm) assert.is.same(mb.registered_primary_keymaps(), {prim1km, prim2km})
assert.is.equal(mb.current_keymap, primkm)
end)
end) end)
insl(function() inslit("adds secondary, then primary layout, activates only primary layout", function()
it("sets up multibow, activates lights", function() local primkm = { name="berlin" }
local s = spy.on(_G, "setup") local seckm = { name="munich", secondary=true }
local al = spy.on(mb, "activate_leds") mb.register_keymap(seckm)
mb.register_keymap(primkm)
_G.setup() assert.is.equal(primkm, mb.current_keymap)
assert.spy(s).was.called(1)
assert.spy(al).was.called(1)
s:revert()
al:revert()
end)
end) end)
insl(function() inslit("cycles primary keymaps based on primary-secondary names substring match", function()
it("has more keys", function() -- on purpose, the names of the primary keymaps are in reverse lexical order,
assert.is_not_nil(keybow.F13) -- to make sure that cycling follows the registration order, but not the
assert.is.equal(keybow.F13, 0x68) -- name order.
end) local prim1km = { name= "last" }
local sec1km = { name="last-shift", secondary=true }
local sec2km = { name="xlast-shift", secondary=true}
local prim2km = { name= "first" }
mb.register_keymap(prim1km)
mb.register_keymap(prim2km)
mb.register_keymap(sec1km)
mb.register_keymap(sec2km)
assert.is.equal(4, #mb.registered_keymaps())
assert.is.same(mb.registered_primary_keymaps(), {prim1km, prim2km})
-- cycles from secondary to next primary
mb.activate_keymap(sec1km.name)
mb.cycle_primary_keymaps()
assert.is.equal(prim2km.name, mb.current_keymap.name)
-- cycles from last primary to first primary
mb.cycle_primary_keymaps()
assert.is.equal(prim1km.name, mb.current_keymap.name)
-- cannot cycle from misnamed secondary without shift_to
mb.activate_keymap(sec2km.name)
mb.cycle_primary_keymaps()
assert.is.equal(sec2km.name, mb.current_keymap.name)
end)
inslit("cycles primary keymaps based on shift_to", 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 }
prim1km.shift_to = sec1km
local prim2km = { name= "first" }
mb.register_keymap(prim1km)
mb.register_keymap(prim2km)
mb.register_keymap(sec1km)
assert.is.same(mb.registered_primary_keymaps(), {prim1km, prim2km})
-- cycles from secondary to next primary
mb.activate_keymap(sec1km.name)
mb.cycle_primary_keymaps()
assert.is.equal(prim2km.name, mb.current_keymap.name)
-- cycles from last primary to first primary
mb.cycle_primary_keymaps()
assert.is.equal(prim1km.name, mb.current_keymap.name)
end)
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)
s:revert()
al:revert()
end)
inslit("has more keys", function()
assert.is_not_nil(keybow.F13)
assert.is.equal(0x68, keybow.F13)
end) end)
end) end)