diff --git a/examples/Features/GhostInTheFirmware/GhostInTheFirmware.ino b/examples/Features/GhostInTheFirmware/GhostInTheFirmware.ino index aa6b417a..ca74d94d 100644 --- a/examples/Features/GhostInTheFirmware/GhostInTheFirmware.ino +++ b/examples/Features/GhostInTheFirmware/GhostInTheFirmware.ino @@ -20,115 +20,85 @@ #include #include +// This sketch is set up to demonstrate the GhostInTheFirmware plugin. The left +// palm key will activate the plugin, virtually pressing each key on the bottom +// row in sequence, and lighting up the keys using the Stalker LED effect. It +// will type out the letters from A to N, but the right palm key can be used to +// toggle the custom EventDropper plugin to suppress USB output. + // *INDENT-OFF* KEYMAPS( [0] = KEYMAP_STACKED - (___, ___, ___, ___, ___, ___, M(0), + (___, ___, ___, ___, ___, ___, ___, ___, ___, ___, ___, ___, ___, ___, ___, ___, ___, ___, ___, ___, - ___, ___, ___, ___, ___, ___, ___, + Key_A, Key_B, Key_C, Key_D, Key_E, Key_F, Key_G, ___, ___, ___, ___, - ___, + M(0), ___, ___, ___, ___, ___, ___, ___, ___, ___, ___, ___, ___, ___, ___, ___, ___, ___, ___, ___, ___, - ___, ___, ___, ___, ___, ___, ___, + Key_H, Key_I, Key_J, Key_K, Key_L, Key_M, Key_N, ___, ___, ___, ___, - ___), + M(1)), ) // *INDENT-ON* -class EventDropper_ : public kaleidoscope::Plugin { - public: - EventDropper_() {} +namespace kaleidoscope { +namespace plugin { - kaleidoscope::EventHandlerResult onKeyswitchEvent(Key &mapped_key, KeyAddr key_addr, uint8_t key_state) { - return kaleidoscope::EventHandlerResult::EVENT_CONSUMED; +class EventDropper : public Plugin { + public: + EventHandlerResult onKeyEvent(KeyEvent &event) { + if (active_) + return EventHandlerResult::EVENT_CONSUMED; + return EventHandlerResult::OK; + } + void toggle() { + active_ = !active_; } + private: + bool active_ = false; }; -static EventDropper_ EventDropper; +} // namespace plugin +} // namespace kaleidoscope + +kaleidoscope::plugin::EventDropper EventDropper; -const macro_t *macroAction(uint8_t macro_index, uint8_t key_state) { - if (macro_index == 0 && keyToggledOn(key_state)) +const macro_t *macroAction(uint8_t macro_id, KeyEvent &event) { + if (macro_id == 0 && keyToggledOn(event.state)) GhostInTheFirmware.activate(); + if (macro_id == 1 && keyToggledOn(event.state)) + EventDropper.toggle(); return MACRO_NONE; } static const kaleidoscope::plugin::GhostInTheFirmware::GhostKey ghost_keys[] PROGMEM = { - {0, 6, 200, 50}, - {0, 5, 200, 50}, - {0, 4, 200, 50}, - {0, 3, 200, 50}, - {0, 2, 200, 50}, - {0, 1, 200, 50}, - {0, 0, 200, 50}, - {1, 0, 200, 50}, - {1, 1, 200, 50}, - {1, 2, 200, 50}, - {1, 3, 200, 50}, - {1, 4, 200, 50}, - {1, 5, 200, 50}, - {1, 6, 200, 50}, - {2, 6, 200, 50}, - {2, 5, 200, 50}, - {2, 4, 200, 50}, - {2, 3, 200, 50}, - {2, 2, 200, 50}, - {2, 1, 200, 50}, - {2, 0, 200, 50}, - {3, 0, 200, 50}, - {3, 1, 200, 50}, - {3, 3, 200, 50}, - {3, 4, 200, 50}, - {3, 5, 200, 50}, - {0, 7, 200, 50}, - {1, 7, 200, 50}, - {2, 7, 200, 50}, - {3, 7, 200, 50}, - {3, 6, 200, 50}, - - {3, 9, 200, 50}, - {3, 8, 200, 50}, - {2, 8, 200, 50}, - {1, 8, 200, 50}, - {0, 8, 200, 50}, - {3, 10, 200, 50}, - {3, 11, 200, 50}, - {3, 12, 200, 50}, - {3, 13, 200, 50}, - {3, 14, 200, 50}, - {3, 15, 200, 50}, - {2, 15, 200, 50}, - {2, 14, 200, 50}, - {2, 13, 200, 50}, - {2, 12, 200, 50}, - {2, 11, 200, 50}, - {2, 10, 200, 50}, - {2, 9, 200, 50}, - {1, 9, 200, 50}, - {1, 10, 200, 50}, - {1, 11, 200, 50}, - {1, 12, 200, 50}, - {1, 13, 200, 50}, - {1, 14, 200, 50}, - {1, 15, 200, 50}, - {0, 15, 200, 50}, - {0, 14, 200, 50}, - {0, 13, 200, 50}, - {0, 12, 200, 50}, - {0, 11, 200, 50}, - {0, 10, 200, 50}, - {0, 9, 200, 50}, - - {0, 0, 0, 0} + {KeyAddr(3, 0), 200, 50}, + {KeyAddr(3, 1), 200, 50}, + {KeyAddr(3, 2), 200, 50}, + {KeyAddr(3, 3), 200, 50}, + {KeyAddr(3, 4), 200, 50}, + {KeyAddr(3, 5), 200, 50}, + {KeyAddr(2, 6), 200, 50}, + {KeyAddr(2, 9), 200, 50}, + {KeyAddr(3, 10), 200, 50}, + {KeyAddr(3, 11), 200, 50}, + {KeyAddr(3, 12), 200, 50}, + {KeyAddr(3, 13), 200, 50}, + {KeyAddr(3, 14), 200, 50}, + {KeyAddr(3, 15), 200, 50}, + + {KeyAddr::none(), 0, 0} }; KALEIDOSCOPE_INIT_PLUGINS(GhostInTheFirmware, + LEDControl, StalkerEffect, Macros, EventDropper); diff --git a/plugins/Kaleidoscope-GhostInTheFirmware/README.md b/plugins/Kaleidoscope-GhostInTheFirmware/README.md index a726ccea..28aed430 100644 --- a/plugins/Kaleidoscope-GhostInTheFirmware/README.md +++ b/plugins/Kaleidoscope-GhostInTheFirmware/README.md @@ -22,16 +22,16 @@ that. #include #include -const macro_t *macroAction(uint8_t macro_index, uint8_t key_state) { - if (macro_index == 0 && keyToggledOn(key_state)) +const macro_t *macroAction(uint8_t macro_id, KeyEvent& event) { + if (macro_id == 0 && keyToggledOn(event.state)) GhostInTheFirmware.activate(); return MACRO_NONE; } static const kaleidoscope::plugin::GhostInTheFirmware::GhostKey ghost_keys[] PROGMEM = { - {0, 0, 200, 50}, - {0, 0, 0} + {KeyAddr(0, 0), 200, 50}, + {KeyAddr::none(), 0, 0} }; KALEIDOSCOPE_INIT_PLUGINS(GhostInTheFirmware, @@ -59,13 +59,14 @@ methods and properties: ### `.ghost_keys` > Set this property to the sequence of keys to press, by assigning a sequence to -> this variable. Each element is a quartett of `row`, `column`, a `pressTime`, -> and a `delay`. Each of these will be pressed in different cycles, unlike -> macros which play back within a single cycle. -> -> The key at `row`, `column` will be held for `pressTime` milliseconds, and -> after an additional `delay` milliseconds, the plugin will move on to the next -> entry in the sequence. +> this variable. Each element is a `GhostKey` object, comprised of a `KeyAddr` +> (the location of a key on the keyboard), a duration of the key press (in +> milliseconds), and a delay after the key release until the next one is pressed +> (also in milliseconds). + +> This `ghost_keys` array *MUST* end with the sentinal value of +> `{KeyAddr::none(), 0, 0}` to ensure that GhostInTheFirmware doesn't read past +> the end of the array. > > The sequence *MUST* reside in `PROGMEM`. diff --git a/plugins/Kaleidoscope-GhostInTheFirmware/src/kaleidoscope/plugin/GhostInTheFirmware.cpp b/plugins/Kaleidoscope-GhostInTheFirmware/src/kaleidoscope/plugin/GhostInTheFirmware.cpp index 2929f07c..978ea3e8 100644 --- a/plugins/Kaleidoscope-GhostInTheFirmware/src/kaleidoscope/plugin/GhostInTheFirmware.cpp +++ b/plugins/Kaleidoscope-GhostInTheFirmware/src/kaleidoscope/plugin/GhostInTheFirmware.cpp @@ -18,54 +18,63 @@ #include "kaleidoscope/Runtime.h" #include #include "kaleidoscope/keyswitch_state.h" +#include "kaleidoscope/progmem_helpers.h" namespace kaleidoscope { namespace plugin { const GhostInTheFirmware::GhostKey *GhostInTheFirmware::ghost_keys; -bool GhostInTheFirmware::is_active_; -bool GhostInTheFirmware::is_pressed_; -uint16_t GhostInTheFirmware::current_pos_; -uint32_t GhostInTheFirmware::start_time_; -uint16_t GhostInTheFirmware::press_timeout_; -uint16_t GhostInTheFirmware::delay_timeout_; +bool GhostInTheFirmware::is_active_ = false; +uint16_t GhostInTheFirmware::current_pos_ = 0; +uint16_t GhostInTheFirmware::start_time_; void GhostInTheFirmware::activate(void) { is_active_ = true; } -EventHandlerResult GhostInTheFirmware::beforeReportingState() { +EventHandlerResult GhostInTheFirmware::afterEachCycle() { if (!is_active_) return EventHandlerResult::OK; - if (press_timeout_ == 0) { - press_timeout_ = pgm_read_word(&(ghost_keys[current_pos_].pressTime)); - delay_timeout_ = pgm_read_word(&(ghost_keys[current_pos_].delay)); + // This stores the current GhostKey in the active sequence. + static GhostKey ghost_key{KeyAddr::none(), 0, 0}; - if (press_timeout_ == 0) { + // When a ghost key has finished playing, it sets its delay to zero, + // indicating that it's time to read the next one from memory. + if (ghost_key.delay == 0) { + // Read the settings for the key from PROGMEM: + loadFromProgmem(ghost_keys[current_pos_], ghost_key); + // The end of the sequence is marked by a GhostKey with an invalid KeyAddr + // value (i.e. KeyAddr::none()). If we read this sentinel value, reset and + // deactivate. + if (!ghost_key.addr.isValid()) { current_pos_ = 0; + ghost_key.delay = 0; is_active_ = false; return EventHandlerResult::OK; } - is_pressed_ = true; + // If we're not at the end of the sequence, send the first keypress event, + // and start the timer. + Runtime.handleKeyEvent(KeyEvent(ghost_key.addr, IS_PRESSED)); start_time_ = Runtime.millisAtCycleStart(); - } else { - if (is_pressed_ && Runtime.hasTimeExpired(start_time_, press_timeout_)) { - is_pressed_ = false; - start_time_ = Runtime.millisAtCycleStart(); - - byte row = pgm_read_byte(&(ghost_keys[current_pos_].row)); - byte col = pgm_read_byte(&(ghost_keys[current_pos_].col)); - - handleKeyswitchEvent(Key_NoKey, KeyAddr(row, col), WAS_PRESSED); - } else if (is_pressed_) { - byte row = pgm_read_byte(&(ghost_keys[current_pos_].row)); - byte col = pgm_read_byte(&(ghost_keys[current_pos_].col)); - handleKeyswitchEvent(Key_NoKey, KeyAddr(row, col), IS_PRESSED); - } else if (Runtime.hasTimeExpired(start_time_, delay_timeout_)) { - current_pos_++; - press_timeout_ = 0; + } else if (ghost_key.addr.isValid()) { + // If the ghost key's address is still valid, that means that the virtual + // key is still being held. + if (Runtime.hasTimeExpired(start_time_, ghost_key.press_time)) { + // The key press has timed out, so we send the release event. + Runtime.handleKeyEvent(KeyEvent(ghost_key.addr, WAS_PRESSED)); + // Next, we invalidate the ghost key's address to prevent checking the + // hold timeout again, then restart the timer for checking the delay. + ghost_key.addr.clear(); + start_time_ = Runtime.millisAtCycleStart(); } + + } else if (Runtime.hasTimeExpired(start_time_, ghost_key.delay)) { + // The ghost key has been (virtually) pressed and released, and its delay + // has now elapsed, so we set the delay to zero and increment the index + // value to indicate that the next key should be loaded in the next cycle. + ghost_key.delay = 0; + ++current_pos_; } return EventHandlerResult::OK; diff --git a/plugins/Kaleidoscope-GhostInTheFirmware/src/kaleidoscope/plugin/GhostInTheFirmware.h b/plugins/Kaleidoscope-GhostInTheFirmware/src/kaleidoscope/plugin/GhostInTheFirmware.h index 38dd5598..76dab510 100644 --- a/plugins/Kaleidoscope-GhostInTheFirmware/src/kaleidoscope/plugin/GhostInTheFirmware.h +++ b/plugins/Kaleidoscope-GhostInTheFirmware/src/kaleidoscope/plugin/GhostInTheFirmware.h @@ -23,29 +23,23 @@ namespace kaleidoscope { namespace plugin { class GhostInTheFirmware : public kaleidoscope::Plugin { public: - typedef struct { - byte row; - byte col; - uint16_t pressTime; + struct GhostKey { + KeyAddr addr; + uint16_t press_time; uint16_t delay; - } GhostKey; + }; static const GhostKey *ghost_keys; GhostInTheFirmware(void) {} static void activate(void); - EventHandlerResult beforeReportingState(); + EventHandlerResult afterEachCycle(); private: static bool is_active_; - static bool is_pressed_; static uint16_t current_pos_; - static uint32_t start_time_; - static uint16_t press_timeout_; - static uint16_t delay_timeout_; - - static void loopHook(bool is_post_clear); + static uint16_t start_time_; }; } }