581 lines
19 KiB
C
581 lines
19 KiB
C
/* ----------------------------------------------------------------------------
|
|
* main()
|
|
* ----------------------------------------------------------------------------
|
|
* Copyright (c) 2012 Ben Blazak <benblazak.dev@gmail.com>
|
|
* Released under The MIT License (MIT) (see "license.md")
|
|
* Project located at <https://github.com/benblazak/ergodox-firmware>
|
|
* ------------------------------------------------------------------------- */
|
|
|
|
#include <stdbool.h>
|
|
#include <stdint.h>
|
|
#include "./keyboard/controller.c"
|
|
#include "./keyboard/keyboard.h"
|
|
|
|
// --------------------------------------------------------------------
|
|
// types and forward declarations
|
|
// --------------------------------------------------------------------
|
|
|
|
typedef void (*void_funptr_t)(void);
|
|
|
|
typedef enum StickyState {
|
|
StickyNone,
|
|
StickyOnceDown,
|
|
StickyOnceUp,
|
|
StickyLock,
|
|
} StickyState;
|
|
|
|
#include "./main.h"
|
|
|
|
// ----------------------------------------------------------------------------
|
|
// layout data
|
|
// ----------------------------------------------------------------------------
|
|
|
|
#include "./keyboard/layout.c"
|
|
// defines:
|
|
// #define KB_Layers #{Layers.size}
|
|
// static const uint8_t PROGMEM _kb_layout[KB_LAYERS][KB_ROWS][KB_COLUMNS];
|
|
// static const void_funptr_t PROGMEM _kb_layout_press[KB_LAYERS][KB_ROWS][KB_COLUMNS];
|
|
// static const void_funptr_t PROGMEM _kb_layout_release[KB_LAYERS][KB_ROWS][KB_COLUMNS];
|
|
|
|
// ----------------------------------------------------------------------------
|
|
// globals
|
|
// ----------------------------------------------------------------------------
|
|
static bool _kb_is_pressed[KB_ROWS][KB_COLUMNS];
|
|
static bool (*kb_is_pressed)[KB_ROWS][KB_COLUMNS] = &_kb_is_pressed;
|
|
|
|
static bool _kb_was_pressed[KB_ROWS][KB_COLUMNS];
|
|
static bool (*kb_was_pressed)[KB_ROWS][KB_COLUMNS] = &_kb_was_pressed;
|
|
|
|
static bool kb_was_transparent[KB_ROWS][KB_COLUMNS];
|
|
static uint8_t layers_pressed[KB_ROWS][KB_COLUMNS];
|
|
|
|
static uint8_t current_layer;
|
|
static uint8_t layer_offset;
|
|
static uint8_t current_row;
|
|
static uint8_t current_col;
|
|
static bool current_is_pressed;
|
|
static bool non_trans_key_pressed;
|
|
static bool trans_key_pressed;
|
|
|
|
static bool layers_active[KB_LAYERS];
|
|
static StickyState layers_sticky[KB_LAYERS];
|
|
static uint8_t layers_top = 0;
|
|
|
|
// ----------------------------------------------------------------------------
|
|
|
|
/*
|
|
* main()
|
|
*/
|
|
int main(void) {
|
|
kb_init(); // does controller initialization too
|
|
|
|
usb_init();
|
|
while (!usb_configured());
|
|
|
|
// initialize layers
|
|
main_init_layers();
|
|
|
|
// never return
|
|
main_key_loop();
|
|
return 0;
|
|
}
|
|
|
|
|
|
// ----------------------------------------------------------------------------
|
|
// layer functions
|
|
// ----------------------------------------------------------------------------
|
|
|
|
void main_init_layers() {
|
|
for (uint8_t layer=0; layer < KB_LAYERS; layer++) {
|
|
layers_active[layer] = false;
|
|
layers_sticky[layer] = StickyNone;
|
|
}
|
|
layers_active[0] = true;
|
|
}
|
|
|
|
|
|
// find highest active layer
|
|
uint8_t _highest_active_layer(uint8_t offset) {
|
|
if (offset < layers_top) {
|
|
for (uint8_t l = layers_top - offset; l > 0 && l < KB_LAYERS; l--) {
|
|
if (layers_active[l]) { return l; }
|
|
}
|
|
}
|
|
|
|
// the base layer is always active
|
|
return 0;
|
|
}
|
|
|
|
// return the highest active layer
|
|
uint8_t main_layers_top_layer() {
|
|
return layers_top;
|
|
}
|
|
|
|
// return if highest active layer is sticky
|
|
StickyState main_layers_top_sticky() {
|
|
return main_layers_sticky(layers_top);
|
|
}
|
|
|
|
// return if layer is sticky
|
|
StickyState main_layers_sticky(uint8_t layer) {
|
|
if (layer < KB_LAYERS) {
|
|
return layers_sticky[layer];
|
|
}
|
|
return StickyNone;
|
|
}
|
|
|
|
// enable a layer
|
|
void main_layers_enable(uint8_t layer, StickyState sticky) {
|
|
if (layer >= KB_LAYERS) { return; }
|
|
|
|
layers_active[layer] = true;
|
|
layers_sticky[layer] = sticky;
|
|
|
|
if (layer > layers_top) {
|
|
layers_top = layer;
|
|
}
|
|
}
|
|
|
|
// disable a layer
|
|
void main_layers_disable(uint8_t layer) {
|
|
// base layer stays always on
|
|
if (layer >= KB_LAYERS || layer == 0) { return; }
|
|
|
|
layers_active[layer] = false;
|
|
layers_sticky[layer] = StickyNone;
|
|
|
|
if (layer == layers_top) {
|
|
layers_top = _highest_active_layer(1);
|
|
}
|
|
}
|
|
|
|
// disable the highest active layer
|
|
void main_layers_disable_top() {
|
|
main_layers_disable(layers_top);
|
|
}
|
|
|
|
// return layer offset elements below the top
|
|
uint8_t main_layers_peek(uint8_t offset) {
|
|
return _highest_active_layer(offset);
|
|
}
|
|
|
|
// execute the keypress or keyrelease function (if it exists) of the key at the current possition
|
|
void main_exec_key(void) {
|
|
void (*key_function)(void) =
|
|
( (current_is_pressed)
|
|
? kb_layout_press_get(current_layer, current_row, current_col)
|
|
: kb_layout_release_get(current_layer, current_row, current_col) );
|
|
|
|
if (key_function) {
|
|
(*key_function)();
|
|
}
|
|
|
|
// If the current layer is in the sticky once up state and a key defined
|
|
// for this layer (a non-transparent key) was pressed, pop the layer
|
|
if (main_layers_top_sticky() == StickyOnceUp && non_trans_key_pressed) {
|
|
main_layers_disable_top();
|
|
}
|
|
}
|
|
|
|
// ----------------------------------------------------------------------------
|
|
|
|
uint8_t kb_layout_get(uint8_t layer, uint8_t row, uint8_t column) {
|
|
return (uint8_t) pgm_read_byte(&(_kb_layout[layer][row][column] ));
|
|
}
|
|
|
|
void_funptr_t kb_layout_press_get(uint8_t layer, uint8_t row, uint8_t column) {
|
|
return (void_funptr_t) pgm_read_word(&(_kb_layout_press[layer][row][column] ));
|
|
}
|
|
|
|
void_funptr_t kb_layout_release_get(uint8_t layer, uint8_t row, uint8_t column) {
|
|
return (void_funptr_t) pgm_read_word(&(_kb_layout_release[layer][row][column]));
|
|
}
|
|
|
|
/*
|
|
* Generate a normal keypress or keyrelease
|
|
*
|
|
* Arguments
|
|
* - press: whether to generate a keypress (true) or keyrelease (false)
|
|
* - keycode: the keycode to use
|
|
*
|
|
* Note
|
|
* - Because of the way USB does things, what this actually does is either add
|
|
* or remove 'keycode' from the list of currently pressed keys, to be sent at
|
|
* the end of the current cycle (see main.c)
|
|
*/
|
|
void _kbfun_press_release(bool press, uint8_t keycode) {
|
|
// no-op
|
|
if (keycode == 0) {
|
|
return;
|
|
}
|
|
|
|
// modifier keys
|
|
switch (keycode) {
|
|
case KEY_LeftControl: (press) ? (keyboard_modifier_keys |= (1<<0)) : (keyboard_modifier_keys &= ~(1<<0)); return;
|
|
case KEY_LeftShift: (press) ? (keyboard_modifier_keys |= (1<<1)) : (keyboard_modifier_keys &= ~(1<<1)); return;
|
|
case KEY_LeftAlt: (press) ? (keyboard_modifier_keys |= (1<<2)) : (keyboard_modifier_keys &= ~(1<<2)); return;
|
|
case KEY_LeftGUI: (press) ? (keyboard_modifier_keys |= (1<<3)) : (keyboard_modifier_keys &= ~(1<<3)); return;
|
|
case KEY_RightControl: (press) ? (keyboard_modifier_keys |= (1<<4)) : (keyboard_modifier_keys &= ~(1<<4)); return;
|
|
case KEY_RightShift: (press) ? (keyboard_modifier_keys |= (1<<5)) : (keyboard_modifier_keys &= ~(1<<5)); return;
|
|
case KEY_RightAlt: (press) ? (keyboard_modifier_keys |= (1<<6)) : (keyboard_modifier_keys &= ~(1<<6)); return;
|
|
case KEY_RightGUI: (press) ? (keyboard_modifier_keys |= (1<<7)) : (keyboard_modifier_keys &= ~(1<<7)); return;
|
|
}
|
|
|
|
// all others
|
|
for (uint8_t i=0; i<6; i++) {
|
|
if (press) {
|
|
if (keyboard_keys[i] == 0) {
|
|
keyboard_keys[i] = keycode;
|
|
return;
|
|
}
|
|
} else {
|
|
if (keyboard_keys[i] == keycode) {
|
|
keyboard_keys[i] = 0;
|
|
return;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
/*
|
|
* Is the given keycode pressed?
|
|
*/
|
|
bool _kbfun_is_pressed(uint8_t keycode) {
|
|
// modifier keys
|
|
switch (keycode) {
|
|
case KEY_LeftControl: if (keyboard_modifier_keys & (1<<0)) { return true; }
|
|
case KEY_LeftShift: if (keyboard_modifier_keys & (1<<1)) { return true; }
|
|
case KEY_LeftAlt: if (keyboard_modifier_keys & (1<<2)) { return true; }
|
|
case KEY_LeftGUI: if (keyboard_modifier_keys & (1<<3)) { return true; }
|
|
case KEY_RightControl: if (keyboard_modifier_keys & (1<<4)) { return true; }
|
|
case KEY_RightShift: if (keyboard_modifier_keys & (1<<5)) { return true; }
|
|
case KEY_RightAlt: if (keyboard_modifier_keys & (1<<6)) { return true; }
|
|
case KEY_RightGUI: if (keyboard_modifier_keys & (1<<7)) { return true; }
|
|
}
|
|
|
|
// all others
|
|
for (uint8_t i=0; i<6; i++)
|
|
if (keyboard_keys[i] == keycode) {
|
|
return true;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
void _kbfun_mediakey_press_release(bool press, uint8_t keycode) {
|
|
uint16_t mediakey_code = _media_code_lookup_table[keycode];
|
|
if (press) {
|
|
consumer_key = mediakey_code;
|
|
} else {
|
|
// Only one key can be pressed at a time so only clear the keypress for
|
|
// active key (most recently pressed)
|
|
if (mediakey_code == consumer_key) {
|
|
consumer_key = 0;
|
|
}
|
|
}
|
|
}
|
|
|
|
uint8_t _kbfun_get_keycode() {
|
|
return kb_layout_get(current_layer, current_row, current_col);
|
|
}
|
|
|
|
// ----------------------------------------------------------------------------
|
|
// basic
|
|
// ----------------------------------------------------------------------------
|
|
|
|
void kbfun_press_release() {
|
|
if (!trans_key_pressed) {
|
|
non_trans_key_pressed = true;
|
|
}
|
|
kbfun_press_release_preserve_sticky();
|
|
}
|
|
|
|
/*
|
|
* Generate a normal keypress or keyrelease
|
|
* While basing the sticky key state transition on whether
|
|
* kbfun_press_release() was called after kbfun_transparent() generally
|
|
* works in practice, it is not always the desired behavior. One of the
|
|
* benefits of sticky keys is avoiding key chording, so we want to make sure
|
|
* that standard modifiers do not interrupt the sticky key cycle. Use
|
|
* kbfun_press_release_preserve_sticky() if you want to define a standard
|
|
* modifier key (shift, control, alt, gui) on the sticky layer instead of
|
|
* defining the key to be transparent for the layer.
|
|
*/
|
|
void kbfun_press_release_preserve_sticky() {
|
|
uint8_t keycode = _kbfun_get_keycode();
|
|
_kbfun_press_release(current_is_pressed, keycode);
|
|
}
|
|
|
|
/*
|
|
* Toggle the key pressed or unpressed
|
|
*/
|
|
void kbfun_toggle(void) {
|
|
uint8_t keycode = _kbfun_get_keycode();
|
|
bool is_pressed = _kbfun_is_pressed(keycode);
|
|
_kbfun_press_release(!is_pressed, keycode);
|
|
}
|
|
|
|
/*
|
|
* Execute the key that would have been executed if the current layer was not
|
|
* active
|
|
*/
|
|
void kbfun_transparent(void) {
|
|
// TODO maybe re-implement this cleaner?
|
|
trans_key_pressed = true;
|
|
layer_offset++;
|
|
current_layer = main_layers_peek(layer_offset);
|
|
layers_pressed[current_row][current_col] = current_layer;
|
|
main_exec_key();
|
|
}
|
|
|
|
|
|
// ----------------------------------------------------------------------------
|
|
// layer helper functions
|
|
// ----------------------------------------------------------------------------
|
|
|
|
static bool is_layer_enable(void_funptr_t f) {
|
|
if (f == &kbfun_layer_enable || f == &kbfun_layer_sticky) {
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
static bool is_layer_disable(void_funptr_t f) {
|
|
if (f == &kbfun_layer_disable || f == &kbfun_layer_sticky) {
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
static void layer_enable_upto(uint8_t max_layer) {
|
|
// FIXME clean this up
|
|
|
|
// pressing a key implicitly activates all lower layers as well
|
|
for (uint8_t layer=0; layer <= KB_LAYERS; layer++) {
|
|
void (*key_function)(void) = kb_layout_press_get(layer, current_row, current_col);
|
|
|
|
if (is_layer_enable(key_function)) {
|
|
uint8_t enable_layer = kb_layout_get(layer, current_row, current_col);
|
|
if (enable_layer <= max_layer) {
|
|
main_layers_enable(enable_layer, StickyNone);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// ----------------------------------------------------------------------------
|
|
// layer functions
|
|
// ----------------------------------------------------------------------------
|
|
|
|
// enable given layer
|
|
void kbfun_layer_enable() {
|
|
uint8_t layer = _kbfun_get_keycode();
|
|
|
|
// FIXME useful for anything?
|
|
// Only the topmost layer on the stack should be in sticky once state, pop
|
|
// the top layer if it is in sticky once state
|
|
/* uint8_t topSticky = main_layers_top_sticky(); */
|
|
/* if (topSticky == StickyOnceDown || topSticky == StickyOnceUp) { */
|
|
/* main_layers_disable_top(); */
|
|
/* } */
|
|
|
|
layer_enable_upto(layer);
|
|
}
|
|
|
|
// disable given layer
|
|
void kbfun_layer_disable() {
|
|
/* uint8_t layer = _kbfun_get_keycode(); */
|
|
// FIXME clean this up
|
|
|
|
// letting go off a key releases *all* layers on that key
|
|
for (uint8_t layer=0; layer <= KB_LAYERS; layer++) {
|
|
void (*key_function)(void) = kb_layout_release_get(layer, current_row, current_col);
|
|
|
|
if (is_layer_disable(key_function)) {
|
|
uint8_t disable_layer = kb_layout_get(layer, current_row, current_col);
|
|
main_layers_disable(disable_layer);
|
|
}
|
|
}
|
|
}
|
|
|
|
/*
|
|
* This function gives similar behavior to sticky keys for modifiers available
|
|
* on most operating systems.
|
|
* 1) One time down (set on key press) - The layer was not active and the key
|
|
* has been pressed but not yet released. The layer is pushed in the one
|
|
* time down state.
|
|
* 2) One time up (set on key release) - The layer was active when the layer
|
|
* sticky key was released. If a key on this layer (not set to
|
|
* transparent) was pressed before the key was released, the layer will be
|
|
* popped. If a non-transparent key was not pressed, the layer is popped
|
|
* and pushed again in the one time up state.
|
|
* 3) Locked (set on key press) - The layer was active and in the one time up
|
|
* state when the layer sticky key was pressed again. The layer will be
|
|
* popped if the function is invoked on a subsequent keypress.
|
|
*/
|
|
void kbfun_layer_sticky() {
|
|
uint8_t layer = _kbfun_get_keycode();
|
|
uint8_t topLayer = main_layers_top_layer();
|
|
StickyState topSticky = main_layers_top_sticky();
|
|
|
|
if (current_is_pressed) {
|
|
if (topLayer == layer) {
|
|
// FIXME
|
|
/* if (topSticky == StickyOnceUp) { */
|
|
/* main_layers_enable(layer, StickyLock); */
|
|
/* } */
|
|
} else {
|
|
// only the topmost layer on the stack should be in sticky once state
|
|
if (topSticky == StickyOnceDown || topSticky == StickyOnceUp) {
|
|
main_layers_disable_top();
|
|
}
|
|
main_layers_enable(layer, StickyOnceDown);
|
|
|
|
// this should be the only place we care about this flag being cleared
|
|
non_trans_key_pressed = false;
|
|
}
|
|
} else {
|
|
if (main_layers_sticky(layer) == StickyOnceDown) {
|
|
// When releasing this sticky key, pop the layer always
|
|
main_layers_disable(layer);
|
|
|
|
if (!non_trans_key_pressed) {
|
|
// If no key defined for this layer (a non-transparent key)
|
|
// was pressed, push the layer again, but in the
|
|
// StickyOnceUp state
|
|
main_layers_enable(layer, StickyOnceUp);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// ----------------------------------------------------------------------------
|
|
// special
|
|
// ----------------------------------------------------------------------------
|
|
|
|
/*
|
|
* Generate a 'shift' press or release before the normal keypress or release
|
|
*/
|
|
void kbfun_shift_press_release(void) {
|
|
_kbfun_press_release(current_is_pressed, KEY_LeftShift);
|
|
kbfun_press_release();
|
|
}
|
|
|
|
/*
|
|
* Generate a 'control' press or release before the normal keypress or release
|
|
*/
|
|
void kbfun_control_press_release(void) {
|
|
_kbfun_press_release(current_is_pressed, KEY_LeftControl);
|
|
kbfun_press_release();
|
|
}
|
|
|
|
/*
|
|
* When assigned to two keys (e.g. the physical left and right shift keys)
|
|
* (in both the press and release matrices), pressing and holding down one of
|
|
* the keys will make the second key toggle capslock
|
|
*
|
|
* If either of the shifts are pressed when the second key is pressed, they
|
|
* wil be released so that capslock will register properly when pressed.
|
|
* Capslock will then be pressed and released, and the original state of the
|
|
* shifts will be restored
|
|
*/
|
|
void kbfun_2_keys_capslock_press_release(void) {
|
|
static uint8_t keys_pressed;
|
|
static bool lshift_pressed;
|
|
static bool rshift_pressed;
|
|
|
|
uint8_t keycode = _kbfun_get_keycode();
|
|
|
|
if (!current_is_pressed) { keys_pressed--; }
|
|
|
|
// take care of the key that was actually pressed
|
|
_kbfun_press_release(current_is_pressed, keycode);
|
|
|
|
// take care of capslock (only on the press of the 2nd key)
|
|
if (keys_pressed == 1 && current_is_pressed) {
|
|
// save the state of left and right shift
|
|
lshift_pressed = _kbfun_is_pressed(KEY_LeftShift);
|
|
rshift_pressed = _kbfun_is_pressed(KEY_RightShift);
|
|
// disable both
|
|
_kbfun_press_release(false, KEY_LeftShift);
|
|
_kbfun_press_release(false, KEY_RightShift);
|
|
|
|
// press capslock, then release it
|
|
_kbfun_press_release(true, KEY_CapsLock); usb_keyboard_send();
|
|
_kbfun_press_release(false, KEY_CapsLock); usb_keyboard_send();
|
|
|
|
// restore the state of left and right shift
|
|
if (lshift_pressed) { _kbfun_press_release(true, KEY_LeftShift); }
|
|
if (rshift_pressed) { _kbfun_press_release(true, KEY_RightShift); }
|
|
}
|
|
|
|
if (current_is_pressed) { keys_pressed++; }
|
|
}
|
|
|
|
/*
|
|
* Generate a keypress for a media key
|
|
*/
|
|
void kbfun_mediakey_press_release(void) {
|
|
uint8_t keycode = _kbfun_get_keycode();
|
|
_kbfun_mediakey_press_release(current_is_pressed, keycode);
|
|
}
|
|
|
|
// --------------------------------------------------------------------------------------
|
|
|
|
void main_key_loop() {
|
|
for (;;) {
|
|
// swap `kb_is_pressed` and `kb_was_pressed`, then update
|
|
bool (*temp)[KB_ROWS][KB_COLUMNS] = kb_was_pressed;
|
|
kb_was_pressed = kb_is_pressed;
|
|
kb_is_pressed = temp;
|
|
|
|
kb_update_matrix(*kb_is_pressed);
|
|
|
|
// this loop is responsible to
|
|
// - "execute" keys when they change state
|
|
// - keep track of which layers the keys were on when they were pressed
|
|
// (so they can be released using the function from that layer)
|
|
//
|
|
// note
|
|
// - everything else is the key function's responsibility
|
|
// - see the keyboard layout file ("keyboard/layout/*.c") for
|
|
// which key is assigned which function (per layer)
|
|
// - see "lib/key-functions/public/*.c" for the function definitions
|
|
for (uint8_t row=0; row<KB_ROWS; row++) {
|
|
for (uint8_t col=0; col<KB_COLUMNS; col++) {
|
|
current_is_pressed = (*kb_is_pressed)[row][col];
|
|
bool was_pressed = (*kb_was_pressed)[row][col];
|
|
|
|
if (current_is_pressed != was_pressed) {
|
|
if (current_is_pressed) {
|
|
current_layer = main_layers_top_layer();
|
|
layers_pressed[row][col] = current_layer;
|
|
trans_key_pressed = false;
|
|
} else {
|
|
current_layer = layers_pressed[row][col];
|
|
trans_key_pressed = kb_was_transparent[row][col];
|
|
}
|
|
|
|
// set remaining vars, and "execute" key
|
|
current_row = row;
|
|
current_col = col;
|
|
layer_offset = 0;
|
|
main_exec_key();
|
|
kb_was_transparent[row][col] = trans_key_pressed;
|
|
}
|
|
}
|
|
}
|
|
|
|
// send the USB report (even if nothing's changed)
|
|
usb_keyboard_send();
|
|
usb_extra_consumer_send();
|
|
|
|
// debounce in ms; see keyswitch spec for necessary value
|
|
_delay_ms(5);
|
|
}
|
|
}
|
|
|
|
// ---------------------------------------------------------------------------------
|
|
// generated keyboard layout
|
|
// ---------------------------------------------------------------------------------
|