Add KeyEventTracker helper class

This class should help plugins that implement `onKeyswitchEvent()` to ensure
that they won't process the same event more than once.

Signed-off-by: Michael Richters <gedankenexperimenter@gmail.com>
pull/1024/head
Michael Richters 4 years ago
parent 1946e1de0b
commit 015b8e3140
No known key found for this signature in database
GPG Key ID: 1288FD13E4EEF0C0

@ -0,0 +1,104 @@
/* Kaleidoscope - Firmware for computer input devices
* Copyright (C) 2021 Keyboard.io, Inc.
*
* This program is free software: you can redistribute it and/or modify it under
* the terms of the GNU General Public License as published by the Free Software
* Foundation, version 3.
*
* This program is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
* FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
* details.
*
* You should have received a copy of the GNU General Public License along with
* this program. If not, see <http://www.gnu.org/licenses/>.
*/
#pragma once
#include "kaleidoscope/event_handler_result.h" // for EventHandlerResult
#include "kaleidoscope/KeyEvent.h" // for KeyEvent, KeyEventId
namespace kaleidoscope {
/// An event tracker for plugins that implement `onKeyswitchEvent()`
///
/// Plugins that implement the `onKeyswitchEvent()` are required to agree to a
/// contract wherein they promise to only process each event once. They may
/// delay an event by returning `EventHandlerResult::ABORT` when they first
/// encounter an event, then re-start it (in response to a subsequent event or a
/// timeout) by calling `Runtime.handleKeyswitchEvent()`. When they do so, they
/// must take pains to ensure that they ignore that event when it gets passed to
/// their `onKeyswitchEvent()` handler a second time. In addition, a subsequent
/// plugin's `onKeyswitchEvent()` handler might do the same thing, and the event
/// must be ignored in that case as well. This `KeyEventTracker` class is a
/// helper that makes it easy for plugins to abide by the terms of the
/// `onKeyswitchEvent()` contract.
///
/// All that's required is adding a private member variable to the plugin's
/// class definition, as follows:
///
/// ```c++
/// class MyPlugin : public Plugin {
/// private:
/// KeyEventTracker event_tracker_;
/// public:
/// EventHandlerResult onKeyswitchEvent(KeyEvent &event);
/// };
/// ```
///
/// Then, in the definition of that plugin's `onKeyswitchEvent()` function, use
/// the tracker's `shouldIgnore()` function to let already-processed events pass
/// through:
///
/// ```c++
/// EventHandlerResult MyPlugin::onKeyswitchEvent(KeyEvent &event) {
/// if (event_tracker_.shouldIgnore(event))
/// return EventHandlerResult::OK;
/// ...
/// }
/// ```
///
/// As a side effect, the `shouldIgnore()` function will record the `id` of the
/// event, if it is not one that the tracker has seen "recently". It works by
/// keeping track of the "newest" event ID that it has seen, by subtracting the
/// ID of the event passed to it from the current "newest" event ID. If the
/// result is greater than zero, the event is considered to be "new" and the
/// tracked ID is updated. The mechanism isn't perfect; if 128 (`KeyEventId`
/// stores ony byte of data) new event IDs are generated without any of them
/// reaching the plugin's `onKeyswitchEvent()` handler, it is theoretically
/// possible to overflow and a new event will be treated as old.
class KeyEventTracker {
private:
// The ID of the "newest" event this tracker has seen.
KeyEventId last_id_seen_{-1};
public:
/// Check if an event should be ignored by the client plugin
///
/// This function should be called by a plugin's `onKeyswitchEvent()` handler
/// function to determined if the `event` has an `id` member that this tracker
/// has seen "recently".
bool shouldIgnore(const KeyEvent &event) {
return !isNew(event);
}
/// Report if a given event is "new" to this tracker
///
/// Returns `true` if the input event is newer than the latest event the
/// tracker has seen. If it is newer, also update the tracker to reflect the
/// new event ID.
bool isNew(const KeyEvent &event) {
KeyEventId offset = event.id() - last_id_seen_;
if (offset > 0) {
last_id_seen_ = event.id();
return true;
}
return false;
}
};
} // namespace kaleidoscope
Loading…
Cancel
Save