492 lines
15 KiB
C
492 lines
15 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>
|
|
|
|
// --------------------------------------------------------------------
|
|
// hardware
|
|
// --------------------------------------------------------------------
|
|
|
|
#define KBD_DEBUG // comment out to disable the debug interface completely
|
|
#include "./keyboard/controller.c"
|
|
#include "./keyboard/keyboard.h"
|
|
|
|
// --------------------------------------------------------------------
|
|
// types and forward declarations
|
|
// --------------------------------------------------------------------
|
|
|
|
typedef uint8_t u8;
|
|
typedef uint16_t u16;
|
|
|
|
typedef enum StickyState {
|
|
StickyNone,
|
|
StickyOnceDown,
|
|
StickyOnceUp,
|
|
} StickyState;
|
|
|
|
typedef u8 keycode;
|
|
typedef u16 media_keycode;
|
|
typedef u8 layer;
|
|
|
|
typedef void (*keyfunc)(void);
|
|
|
|
#include "./main.h"
|
|
|
|
// ----------------------------------------------------------------------------
|
|
// layout data
|
|
// ----------------------------------------------------------------------------
|
|
|
|
#include "./keyboard/layout.c"
|
|
// defines:
|
|
// #define KB_Layers #{Layers.size}
|
|
// static const keycode PROGMEM _kb_layout[KB_LAYERS][KB_ROWS][KB_COLUMNS];
|
|
// static const keyfunc PROGMEM _kb_layout_press[KB_LAYERS][KB_ROWS][KB_COLUMNS];
|
|
// static const keyfunc 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 layer layers_pressed[KB_ROWS][KB_COLUMNS];
|
|
|
|
static u8 current_row;
|
|
static u8 current_col;
|
|
static layer current_layer;
|
|
static keycode current_keycode;
|
|
static bool current_is_pressed;
|
|
static bool sticky_done;
|
|
|
|
static bool layers_active[KB_LAYERS];
|
|
static StickyState layers_sticky[KB_LAYERS];
|
|
static layer layers_top = 0;
|
|
|
|
// ----------------------------------------------------------------------------
|
|
|
|
int main() {
|
|
kb_init();
|
|
usb_init();
|
|
while (!usb_configured());
|
|
|
|
// initialize layers
|
|
init_layers();
|
|
|
|
// never return
|
|
main_key_loop();
|
|
return 0;
|
|
}
|
|
|
|
// --------------------------------------------------------------------------------------
|
|
|
|
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);
|
|
|
|
// - execute key functions when their key changes 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)
|
|
for (u8 row=0; row<KB_ROWS; row++) {
|
|
for (u8 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 = layers_top;
|
|
layers_pressed[row][col] = current_layer;
|
|
} else {
|
|
current_layer = layers_pressed[row][col];
|
|
}
|
|
|
|
// set remaining vars, and "execute" key
|
|
current_row = row;
|
|
current_col = col;
|
|
current_keycode = kb_keycode(current_layer, current_row, current_col);
|
|
exec_key();
|
|
}
|
|
}
|
|
}
|
|
|
|
// 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);
|
|
}
|
|
}
|
|
|
|
// execute the keypress or keyrelease function (if it exists) of the key at the current possition
|
|
void exec_key(void) {
|
|
void (*key_function)(void) =
|
|
( (current_is_pressed)
|
|
? kb_keyfunc_press(current_layer, current_row, current_col)
|
|
: kb_keyfunc_release(current_layer, current_row, current_col) );
|
|
|
|
if (key_function) { (*key_function)(); }
|
|
|
|
// FIXME
|
|
// 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 (layer_top_sticky() == StickyOnceUp && sticky_done) {
|
|
layer_disable_top();
|
|
}
|
|
}
|
|
|
|
// ----------------------------------------------------------------------------
|
|
// layer functions
|
|
// ----------------------------------------------------------------------------
|
|
|
|
void init_layers() {
|
|
for (layer l=0; l < KB_LAYERS; l++) {
|
|
layers_active[l] = false;
|
|
layers_sticky[l] = StickyNone;
|
|
}
|
|
layers_active[0] = true;
|
|
}
|
|
|
|
|
|
// find highest active layer
|
|
layer highest_active_layer(layer offset) {
|
|
if (offset < layers_top) {
|
|
for (layer 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 if highest active layer is sticky
|
|
StickyState layer_top_sticky() {
|
|
return layer_sticky(layers_top);
|
|
}
|
|
|
|
// return if layer is sticky
|
|
StickyState layer_sticky(layer l) {
|
|
if (l < KB_LAYERS) {
|
|
return layers_sticky[l];
|
|
}
|
|
return StickyNone;
|
|
}
|
|
|
|
// enable a layer
|
|
void layer_enable(layer l, StickyState sticky) {
|
|
// FIXME split off sticky part
|
|
if (l >= KB_LAYERS) { return; }
|
|
|
|
layers_active[l] = true;
|
|
layers_sticky[l] = sticky;
|
|
|
|
if (l > layers_top) {
|
|
layers_top = l;
|
|
}
|
|
}
|
|
|
|
// disable a layer
|
|
void layer_disable(layer l) {
|
|
// base layer stays always on
|
|
if (l >= KB_LAYERS || l == 0) { return; }
|
|
|
|
layers_active[l] = false;
|
|
|
|
if (layers_sticky[l] != StickyNone) {
|
|
debug_printf("sticky %d up!\n", l);
|
|
}
|
|
layers_sticky[l] = StickyNone;
|
|
|
|
if (l == layers_top) {
|
|
layers_top = highest_active_layer(1);
|
|
}
|
|
}
|
|
|
|
// disable the highest active layer
|
|
void layer_disable_top() {
|
|
layer_disable(layers_top);
|
|
}
|
|
|
|
// return layer offset elements below the top
|
|
layer layer_peek(layer offset) {
|
|
return highest_active_layer(offset);
|
|
}
|
|
|
|
bool is_layer_enable(keyfunc f) {
|
|
if (f == &kbfun_layer_enable || f == &kbfun_layer_sticky) {
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
bool is_layer_disable(keyfunc f) {
|
|
if (f == &kbfun_layer_disable || f == &kbfun_layer_sticky) {
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
void layer_enable_upto(layer max_layer) {
|
|
// FIXME clean this up
|
|
|
|
// pressing a key implicitly activates all lower layers as well
|
|
for (layer l=0; l <= KB_LAYERS; l++) {
|
|
void (*key_function)(void) = kb_keyfunc_press(l, current_row, current_col);
|
|
|
|
if (is_layer_enable(key_function)) {
|
|
layer enable_layer = (layer) kb_keycode(l, current_row, current_col);
|
|
if (enable_layer <= max_layer) {
|
|
layer_enable(enable_layer, StickyNone);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// ----------------------------------------------------------------------------
|
|
// layout info
|
|
// ----------------------------------------------------------------------------
|
|
|
|
keycode kb_keycode (layer l, u8 row, u8 col) { return (keycode) pgm_read_byte(&(_kb_layout[l][row][col])); }
|
|
keyfunc kb_keyfunc_press (layer l, u8 row, u8 col) { return (keyfunc) pgm_read_word(&(_kb_layout_press[l][row][col])); }
|
|
keyfunc kb_keyfunc_release (layer l, u8 row, u8 col) { return (keyfunc) pgm_read_word(&(_kb_layout_release[l][row][col])); }
|
|
|
|
// ----------------------------------------------------------------------------
|
|
// keyfunc primitives
|
|
// ----------------------------------------------------------------------------
|
|
|
|
// basic keypresses
|
|
void _kbfun_press_release(bool press, keycode key) {
|
|
// no-op
|
|
if (key == 0) { return; }
|
|
|
|
if (press) {
|
|
_kbfun_press(key);
|
|
} else {
|
|
_kbfun_release(key);
|
|
}
|
|
}
|
|
|
|
void _kbfun_press(keycode key) {
|
|
// modifier keys
|
|
switch (key) {
|
|
case KEY_LeftControl: keyboard_modifier_keys |= (1<<0); return;
|
|
case KEY_LeftShift: keyboard_modifier_keys |= (1<<1); return;
|
|
case KEY_LeftAlt: keyboard_modifier_keys |= (1<<2); return;
|
|
case KEY_LeftGUI: keyboard_modifier_keys |= (1<<3); return;
|
|
case KEY_RightControl: keyboard_modifier_keys |= (1<<4); return;
|
|
case KEY_RightShift: keyboard_modifier_keys |= (1<<5); return;
|
|
case KEY_RightAlt: keyboard_modifier_keys |= (1<<6); return;
|
|
case KEY_RightGUI: keyboard_modifier_keys |= (1<<7); return;
|
|
}
|
|
|
|
// all others
|
|
for (u8 i=0; i < sizeof(keyboard_keys); i++) {
|
|
if (keyboard_keys[i] == 0) {
|
|
keyboard_keys[i] = key;
|
|
return;
|
|
}
|
|
}
|
|
}
|
|
|
|
void _kbfun_release(keycode key) {
|
|
// modifier keys
|
|
switch (key) {
|
|
case KEY_LeftControl: keyboard_modifier_keys &= ~(1<<0); return;
|
|
case KEY_LeftShift: keyboard_modifier_keys &= ~(1<<1); return;
|
|
case KEY_LeftAlt: keyboard_modifier_keys &= ~(1<<2); return;
|
|
case KEY_LeftGUI: keyboard_modifier_keys &= ~(1<<3); return;
|
|
case KEY_RightControl: keyboard_modifier_keys &= ~(1<<4); return;
|
|
case KEY_RightShift: keyboard_modifier_keys &= ~(1<<5); return;
|
|
case KEY_RightAlt: keyboard_modifier_keys &= ~(1<<6); return;
|
|
case KEY_RightGUI: keyboard_modifier_keys &= ~(1<<7); return;
|
|
}
|
|
|
|
// all others
|
|
for (u8 i=0; i < sizeof(keyboard_keys); i++) {
|
|
if (keyboard_keys[i] == key) {
|
|
keyboard_keys[i] = 0;
|
|
return;
|
|
}
|
|
}
|
|
}
|
|
|
|
bool _kbfun_is_pressed(keycode key) {
|
|
// modifier keys
|
|
switch (key) {
|
|
case KEY_LeftControl: return (keyboard_modifier_keys & (1<<0));
|
|
case KEY_LeftShift: return (keyboard_modifier_keys & (1<<1));
|
|
case KEY_LeftAlt: return (keyboard_modifier_keys & (1<<2));
|
|
case KEY_LeftGUI: return (keyboard_modifier_keys & (1<<3));
|
|
case KEY_RightControl: return (keyboard_modifier_keys & (1<<4));
|
|
case KEY_RightShift: return (keyboard_modifier_keys & (1<<5));
|
|
case KEY_RightAlt: return (keyboard_modifier_keys & (1<<6));
|
|
case KEY_RightGUI: return (keyboard_modifier_keys & (1<<7));
|
|
}
|
|
|
|
// all others
|
|
for (u8 i=0; i < sizeof(keyboard_keys); i++) {
|
|
if (keyboard_keys[i] == key) {
|
|
return true;
|
|
}
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
void _kbfun_mediakey_press_release(bool press, keycode key) {
|
|
media_keycode media_key = _media_code_lookup_table[key];
|
|
if (press) {
|
|
consumer_key = media_key;
|
|
} else {
|
|
// only one media key can be pressed at a time, so only clear most recent one
|
|
if (media_key == consumer_key) {
|
|
consumer_key = 0;
|
|
}
|
|
}
|
|
}
|
|
|
|
// ----------------------------------------------------------------------------
|
|
// basic keyfuncs
|
|
// ----------------------------------------------------------------------------
|
|
|
|
bool key_is_modifier(keycode key) {
|
|
switch (key) {
|
|
case KEY_LeftControl: return true;
|
|
case KEY_LeftShift: return true;
|
|
case KEY_LeftAlt: return true;
|
|
case KEY_LeftGUI: return true;
|
|
case KEY_RightControl: return true;
|
|
case KEY_RightShift: return true;
|
|
case KEY_RightAlt: return true;
|
|
case KEY_RightGUI: return true;
|
|
default: return false;
|
|
}
|
|
}
|
|
|
|
// normal key
|
|
void kbfun_press_release() {
|
|
sticky_done = ! key_is_modifier(current_keycode);
|
|
_kbfun_press_release(current_is_pressed, current_keycode);
|
|
}
|
|
|
|
// media key
|
|
void kbfun_mediakey_press_release() {
|
|
sticky_done = true;
|
|
keycode key = current_keycode;
|
|
_kbfun_mediakey_press_release(current_is_pressed, key);
|
|
}
|
|
|
|
// enable layer
|
|
void kbfun_layer_enable() {
|
|
layer l = (layer) current_keycode;
|
|
layer_enable_upto(l);
|
|
}
|
|
|
|
// disable layer
|
|
void kbfun_layer_disable() {
|
|
// letting go off a key releases *all* layers on that key
|
|
for (layer l=0; l <= KB_LAYERS; l++) {
|
|
void (*key_function)(void) = kb_keyfunc_release(l, current_row, current_col);
|
|
|
|
if (is_layer_disable(key_function)) {
|
|
layer disable_layer = (layer) kb_keycode(l, current_row, current_col);
|
|
layer_disable(disable_layer);
|
|
}
|
|
}
|
|
}
|
|
|
|
// sticky layer key
|
|
void kbfun_layer_sticky() {
|
|
layer l = (layer) current_keycode;
|
|
StickyState top_sticky = layer_top_sticky();
|
|
|
|
if (current_is_pressed) {
|
|
if (l != layers_top) {
|
|
// only the topmost layer on the stack should be in sticky once state
|
|
if (top_sticky == StickyOnceDown || top_sticky == StickyOnceUp) {
|
|
layer_disable_top();
|
|
}
|
|
layer_enable(l, StickyOnceDown);
|
|
debug_printf("sticky %d down!\n", l);
|
|
|
|
// this should be the only place we care about this flag being cleared
|
|
sticky_done = false;
|
|
}
|
|
} else {
|
|
if (layer_sticky(l) == StickyOnceDown) {
|
|
// When releasing this sticky key, pop the layer always
|
|
layer_disable(l);
|
|
|
|
if (!sticky_done) {
|
|
// re-enable the sticky key if we didn't actually use it yet
|
|
layer_enable(l, StickyOnceUp);
|
|
debug_printf("sticky %d still down!\n", l);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// ----------------------------------------------------------------------------
|
|
// combo keyfuncs
|
|
// ----------------------------------------------------------------------------
|
|
|
|
void _kbfun_combo_press_release(keycode combo_key) {
|
|
_kbfun_press_release(current_is_pressed, combo_key);
|
|
kbfun_press_release();
|
|
}
|
|
|
|
void kbfun_shift_press_release() { _kbfun_combo_press_release(KEY_LeftShift); } // +shift
|
|
void kbfun_control_press_release() { _kbfun_combo_press_release(KEY_LeftControl); } // +control
|
|
void kbfun_alt_press_release() { _kbfun_combo_press_release(KEY_LeftAlt); } // +alt
|
|
void kbfun_win_press_release() { _kbfun_combo_press_release(KEY_LeftGUI); } // +win
|
|
|
|
// capslock
|
|
void kbfun_2_keys_capslock_press_release() {
|
|
static u8 keys_pressed;
|
|
static bool lshift_pressed;
|
|
static bool rshift_pressed;
|
|
|
|
keycode key = current_keycode;
|
|
|
|
if (!current_is_pressed) { keys_pressed--; }
|
|
|
|
// take care of the key that was actually pressed
|
|
_kbfun_press_release(current_is_pressed, key);
|
|
|
|
// 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++; }
|
|
}
|