gedankenexperimenter / Kaleidoscope

Firmware for the Keyboardio Model 01 and other keyboards with AVR or ARM MCUs.
http://keyboard.io
GNU General Public License v3.0
0 stars 1 forks source link

Event-driven design & modelling the keyboard #2

Open gedankenexperimenter opened 6 years ago

gedankenexperimenter commented 6 years ago

I've done some more thinking about a new design for Kaleidoscope, and came up with an idea that I think makes several important plugins much, much simpler (and require less data for storing state). The key concept is to replace liveCompositeKeymap[] with a similar array – let's call it keyboard_state[]. This array will store the current mapped Key objects for all the pressed keys on the keyboard. For any key that's not pressed, it will have the value Key_Transparent, and any key that's masked will have the value Key_NoKey. When I physical key is released, that will cause its entry in keyboard_state[] to be set to Key_Transparent (removing any mask, just like what happens in current Kaleidoscope).

Furthermore, I'll push off the keymap to a new plugin (or maybe have it built in): Kaleidoscope-Keymap. This plugin will probably be first (but maybe not) in the order when processing keyswitch events, and will do the lookups in the active_layers[] table when a key press event occurs, and copy that value to the corresponding entry in keyboard_state[] – but only if the entry is Key_Transparent; if something else has already set that value, it leaves it alone. I believe this will make several plugins easier to write, and more efficient.

gedankenexperimenter commented 6 years ago

Plugin (hook) ordering

Once we're getting on key press/release event at a time, and each plugin can look at the whole state of the keyboard instead of just one at a time, it becomes more obvious how to order plugins. In fact, rather than just one hook point for key events, we could have several, in order to better guarantee that plugins get used in the correct order.

In fact, maybe a plugin shouldn't be able to alter keyboard_state[] directly. Kaleidoscope could guarantee that any key whose keycode is set can't change while that key is still (logically) pressed. This would mean that Keymap would be last, and would only set the state of a newly-pressed key if no other plugin had already done so. The downside of this is that it prevents plugins from storing temporary state in the high bits of the Key structure. So maybe instead of that, keyboard_state[]'s items could be more than just the Key objects, but also include a byte for plugins to use to store state information. Maybe it's best to just advise plugin authors to be very careful when modifying entries in keyboard_state[] instead.

One big upside of this change is that (generally speaking), once a key is pressed, its resulting keycode won't change until that key is released, so rollover for a key used by Unshifter that has two different keycodes based on the (physical) shift state won't produce any unwanted characters in the output.

gedankenexperimenter commented 6 years ago

StickyKeys plugin

One of the plugins that I want to re-write for this new Kaleidoscope is OneShot, which doesn't have any real deficiencies that I'm aware of, but which might be simpler to implement in this system. My design is to have a key marked as "sticky" either in the keymap or in a separate table, and when that key is pressed, it goes through a state transition. The first time it's pressed, it becomes "sticky", and the next time it toggles off, it puts its keycode back in the keyboard_state[]. The next time it gets pressed (if it's still in that state), it transitions to the "locked" state. When a non-sticky key is released, keys in the "sticky" state are cleared as well, but not keys in the "locked" state. Pressing a key in the "locked" state clears both "sticky" and "locked" bits. That's all that's necessary, unless we must have timeouts, which can (and should) be dealt with in a different hook, and this code doesn't need to be aware of that at all.

In my design concept, there's only one timeout, and the sticky key sets that global timeout when it's released (if it's still sticky). At the end of the timeout, if no keys have been pressed, all sticky keys are cleared (but not locked ones).

gedankenexperimenter commented 6 years ago

Layer changes

I want to re-think ShiftToLayer() & LockLayer() and how they interact with each other. In particular, the problem is ShiftToLayer(), which needs to check and see if the layer should be deactivated when switching off. That's not so easy, because we can't tell if that layer got activated by LockLayer().

Maybe this can be done by re-purposing the layerState bitfield, and making it only show layers that are in the "locked" state – or perhaps using a second bitfield for the locked state, if that's more convenient. Then we only deactivate the layer if both the lock is cleared, and there's no held shift-to-layer key.

gedankenexperimenter commented 6 years ago

Timers

I want to implement a Timer class (or namespace) that will allow plugins to share code (I hope) and deal correctly with overflow of the 32-bit value returned by millis(). I'm thinking multiple inheritance might work, but I'm not sure. Another idea is to have plugins call a timer-registration function from their init() methods, which will assign an index to each plugin (and, incidentally, count the plugins registered. Then the timer can test once per cycle, and call a timer hook function that passes the index of the plugin that set the alarm, allowing all the others to ignore it. This might not be worth all the trouble, though.