mirror of
https://github.com/ByteWelder/Tactility.git
synced 2026-10-17 02:45:05 +00:00
Implement async keyboard API
This commit is contained in:
parent
65ee223876
commit
996aea8dac
@ -77,11 +77,11 @@ struct KeyboardKeyData {
|
|||||||
bool alt;
|
bool alt;
|
||||||
/**
|
/**
|
||||||
* @brief Standard USB HID keyboard usage code for this key (USB HID Usage Tables, page 0x07),
|
* @brief Standard USB HID keyboard usage code for this key (USB HID Usage Tables, page 0x07),
|
||||||
* or 0 if this driver doesn't compute one (most don't - `key` is the only field most consumers
|
* or 0 if this driver doesn't compute one (most don't - `key` is the only field most consumers need).
|
||||||
* need). Populated by drivers whose hardware layout maps cleanly onto HID usage codes, so
|
* Populated by drivers whose hardware layout maps cleanly onto HID usage codes, so consumers that
|
||||||
* consumers that want to mirror physical key presses as real HID reports (e.g. USB HID output)
|
* want to mirror physical key presses as real HID reports (e.g. USB HID output) don't have to
|
||||||
* don't have to reverse-engineer one out of `key`'s codepoint encoding - which is lossy for
|
* reverse-engineer one out of `key`'s codepoint encoding.
|
||||||
* keys with no Unicode/LVGL representation at all, e.g. F1-F12.
|
* Encoding is lossy for keys with no Unicode/LVGL representation at all, e.g. F1-F12.
|
||||||
*/
|
*/
|
||||||
uint8_t hid_keycode;
|
uint8_t hid_keycode;
|
||||||
/**
|
/**
|
||||||
@ -91,12 +91,35 @@ struct KeyboardKeyData {
|
|||||||
uint8_t hid_modifier;
|
uint8_t hid_modifier;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Number of key events that can be queued per subscription in the default
|
||||||
|
* subscribe/unsubscribe/poll implementation before older subscribers' fanned-out events start
|
||||||
|
* being dropped for that subscriber (silently, preserving FIFO order of what's already queued).
|
||||||
|
*/
|
||||||
|
#define KEYBOARD_EVENT_QUEUE_CAPACITY 4
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Caller-owned subscription node for keyboard_subscribe()/keyboard_unsubscribe()/
|
||||||
|
* keyboard_poll().
|
||||||
|
* @warning `internal` is private to keyboard.cpp; do not read or write it directly.
|
||||||
|
*/
|
||||||
|
struct KeyboardEventSubscription {
|
||||||
|
struct {
|
||||||
|
struct Device* device;
|
||||||
|
struct KeyboardKeyData queue[KEYBOARD_EVENT_QUEUE_CAPACITY];
|
||||||
|
uint8_t head;
|
||||||
|
uint8_t count;
|
||||||
|
struct KeyboardEventSubscription* next;
|
||||||
|
} internal;
|
||||||
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief API for keyboard drivers.
|
* @brief API for keyboard drivers.
|
||||||
*/
|
*/
|
||||||
struct KeyboardApi {
|
struct KeyboardApi {
|
||||||
/**
|
/**
|
||||||
* @brief Reads the next pending key event, if any.
|
* @brief Reads the next pending key event, if any.
|
||||||
|
* May be NULL if the driver instead pushes events itself via keyboard_emit_key() (e.g. from a dedicated interrupt-handling task).
|
||||||
* @param[in] device the keyboard device
|
* @param[in] device the keyboard device
|
||||||
* @param[out] data the key event data
|
* @param[out] data the key event data
|
||||||
* @retval ERROR_NONE when the operation was successful
|
* @retval ERROR_NONE when the operation was successful
|
||||||
@ -104,7 +127,7 @@ struct KeyboardApi {
|
|||||||
error_t (*read_key)(struct Device* device, struct KeyboardKeyData* data);
|
error_t (*read_key)(struct Device* device, struct KeyboardKeyData* data);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief Returns the baclight if the keyboard has one.
|
* @brief Returns the backlight if the keyboard has one.
|
||||||
* @warning Returns a referenced device. Must call device_put() afterwards.
|
* @warning Returns a referenced device. Must call device_put() afterwards.
|
||||||
* @param[in] device the keyboard device
|
* @param[in] device the keyboard device
|
||||||
* @param[out] backlight_device the output backlight device
|
* @param[out] backlight_device the output backlight device
|
||||||
@ -114,10 +137,10 @@ struct KeyboardApi {
|
|||||||
error_t (*get_backlight)(struct Device* device, struct Device** backlight_device);
|
error_t (*get_backlight)(struct Device* device, struct Device** backlight_device);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief Optional: reports whether the keyboard is physically present right now. Only
|
* @brief Optional: reports whether the keyboard is physically present right now.
|
||||||
* meaningful for hot-pluggable/detachable keyboards (e.g. a removable accessory) whose
|
* Only meaningful for hot-pluggable/detachable keyboards (e.g. a removable accessory) whose
|
||||||
* kernel device is constructed and started once at boot regardless of physical attachment -
|
* kernel device is constructed and started once at boot regardless of physical attachment.
|
||||||
* leave NULL for a keyboard that's always physically present whenever its device is active
|
* Leave NULL for a keyboard that's always physically present whenever its device is active
|
||||||
* (the common case; callers must treat NULL the same as "always present").
|
* (the common case; callers must treat NULL the same as "always present").
|
||||||
* @param[in] device the keyboard device
|
* @param[in] device the keyboard device
|
||||||
* @return true if physically attached/present
|
* @return true if physically attached/present
|
||||||
@ -127,9 +150,22 @@ struct KeyboardApi {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief Reads the next pending key event using the specified keyboard device.
|
* @brief Reads the next pending key event using the specified keyboard device.
|
||||||
|
* @retval ERROR_NOT_SUPPORTED the driver has no KeyboardApi::read_key (it pushes events via
|
||||||
|
* keyboard_emit_key() instead)
|
||||||
*/
|
*/
|
||||||
error_t keyboard_read_key(struct Device* device, struct KeyboardKeyData* data);
|
error_t keyboard_read_key(struct Device* device, struct KeyboardKeyData* data);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Push a key event from @a device to every current subscriber (see keyboard_subscribe()).
|
||||||
|
* For a driver whose hardware delivers key events asynchronously (e.g. from a dedicated
|
||||||
|
* interrupt-handling task) rather than on demand - KeyboardApi::read_key may be left NULL when
|
||||||
|
* the driver calls this instead.
|
||||||
|
* @warning Not ISR-safe (takes a mutex); call from a task, not directly from interrupt context.
|
||||||
|
* @param[in] device the keyboard device the event originated from
|
||||||
|
* @param[in] data the key event
|
||||||
|
*/
|
||||||
|
void keyboard_emit_key(struct Device* device, struct KeyboardKeyData data);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief Returns the backlight if the keyboard has one.
|
* @brief Returns the backlight if the keyboard has one.
|
||||||
* @warning Returns a referenced device. Must call device_put() afterwards.
|
* @warning Returns a referenced device. Must call device_put() afterwards.
|
||||||
@ -141,13 +177,34 @@ error_t keyboard_read_key(struct Device* device, struct KeyboardKeyData* data);
|
|||||||
error_t keyboard_get_backlight(struct Device* device, struct Device** backlight_device);
|
error_t keyboard_get_backlight(struct Device* device, struct Device** backlight_device);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief Whether the keyboard device is physically present right now. True when the driver
|
* @brief Whether the keyboard device is physically present right now.
|
||||||
* doesn't implement KeyboardApi::is_present (i.e. it's always physically present whenever its
|
* Returns true when the driver doesn't implement KeyboardApi::is_present
|
||||||
* device is active) - see that field's doc comment.
|
|
||||||
* @param[in] device the keyboard device
|
* @param[in] device the keyboard device
|
||||||
*/
|
*/
|
||||||
bool keyboard_is_present(struct Device* device);
|
bool keyboard_is_present(struct Device* device);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Register @a sub for async key events from @a device, derived from `read_key`.
|
||||||
|
* @param[in,out] sub subscription to register; owns the storage, must stay alive (and stationary)
|
||||||
|
* until unsubscribed
|
||||||
|
* @retval ERROR_NONE on success
|
||||||
|
*/
|
||||||
|
error_t keyboard_subscribe(struct Device* device, struct KeyboardEventSubscription* sub);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Remove a subscription previously registered with keyboard_subscribe().
|
||||||
|
* @retval ERROR_NONE on success
|
||||||
|
*/
|
||||||
|
error_t keyboard_unsubscribe(struct Device* device, struct KeyboardEventSubscription* sub);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Non-blocking: pop the next event for @a sub if one is already queued.
|
||||||
|
* @warning Never blocks.
|
||||||
|
* @retval ERROR_NONE @a out_data was filled
|
||||||
|
* @retval ERROR_TIMEOUT nothing queued right now
|
||||||
|
*/
|
||||||
|
error_t keyboard_poll(struct Device* device, struct KeyboardEventSubscription* sub, struct KeyboardKeyData* out_data);
|
||||||
|
|
||||||
extern const struct DeviceType KEYBOARD_TYPE;
|
extern const struct DeviceType KEYBOARD_TYPE;
|
||||||
|
|
||||||
#ifdef __cplusplus
|
#ifdef __cplusplus
|
||||||
|
|||||||
@ -3,13 +3,49 @@
|
|||||||
#include <tactility/drivers/keyboard.h>
|
#include <tactility/drivers/keyboard.h>
|
||||||
#include <tactility/error.h>
|
#include <tactility/error.h>
|
||||||
|
|
||||||
|
#include <tactility/concurrent/mutex.h>
|
||||||
|
|
||||||
#define KEYBOARD_DRIVER_API(driver) ((struct KeyboardApi*)driver->api)
|
#define KEYBOARD_DRIVER_API(driver) ((struct KeyboardApi*)driver->api)
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Intrusive singly-linked list of subscriptions, filtered by `device` on fan-out. Guarded by a
|
||||||
|
* single coarse-grained mutex - same rationale as app_event.cpp's subscriptions_mutex: fanning
|
||||||
|
* out here is just a struct copy, never caller code.
|
||||||
|
*/
|
||||||
|
static KeyboardEventSubscription* subscriptions = nullptr;
|
||||||
|
|
||||||
|
struct KeyboardEventMutex {
|
||||||
|
Mutex handle {};
|
||||||
|
KeyboardEventMutex() { mutex_construct(&handle); }
|
||||||
|
~KeyboardEventMutex() { mutex_destruct(&handle); }
|
||||||
|
};
|
||||||
|
|
||||||
|
static KeyboardEventMutex subscriptions_mutex;
|
||||||
|
|
||||||
|
// Pushes `data` onto every subscription for `device` except `exclude` (pass nullptr to include
|
||||||
|
// all of them). Caller must hold subscriptions_mutex.
|
||||||
|
static void fan_out_to_subscribers(Device* device, const KeyboardKeyData& data, const KeyboardEventSubscription* exclude) {
|
||||||
|
for (KeyboardEventSubscription* sub = subscriptions; sub != nullptr; sub = sub->internal.next) {
|
||||||
|
if (sub == exclude || sub->internal.device != device) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if (sub->internal.count < KEYBOARD_EVENT_QUEUE_CAPACITY) {
|
||||||
|
uint8_t tail = (sub->internal.head + sub->internal.count) % KEYBOARD_EVENT_QUEUE_CAPACITY;
|
||||||
|
sub->internal.queue[tail] = data;
|
||||||
|
sub->internal.count++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
extern "C" {
|
extern "C" {
|
||||||
|
|
||||||
error_t keyboard_read_key(Device* device, KeyboardKeyData* data) {
|
error_t keyboard_read_key(Device* device, KeyboardKeyData* data) {
|
||||||
const auto* driver = device_get_driver(device);
|
const auto* driver = device_get_driver(device);
|
||||||
|
|
||||||
|
if (KEYBOARD_DRIVER_API(driver)->read_key == nullptr) {
|
||||||
|
return ERROR_NOT_SUPPORTED;
|
||||||
|
}
|
||||||
|
|
||||||
// Default the modifier/HID fields here rather than in each driver: only drivers whose hardware
|
// Default the modifier/HID fields here rather than in each driver: only drivers whose hardware
|
||||||
// can report them set them, and the rest would otherwise leave whatever the caller's stack held.
|
// can report them set them, and the rest would otherwise leave whatever the caller's stack held.
|
||||||
data->ctrl = false;
|
data->ctrl = false;
|
||||||
@ -20,6 +56,12 @@ error_t keyboard_read_key(Device* device, KeyboardKeyData* data) {
|
|||||||
return KEYBOARD_DRIVER_API(driver)->read_key(device, data);
|
return KEYBOARD_DRIVER_API(driver)->read_key(device, data);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void keyboard_emit_key(Device* device, KeyboardKeyData data) {
|
||||||
|
mutex_lock(&subscriptions_mutex.handle);
|
||||||
|
fan_out_to_subscribers(device, data, nullptr);
|
||||||
|
mutex_unlock(&subscriptions_mutex.handle);
|
||||||
|
}
|
||||||
|
|
||||||
error_t keyboard_get_backlight(Device* device, Device** backlight_device) {
|
error_t keyboard_get_backlight(Device* device, Device** backlight_device) {
|
||||||
const auto* driver = device_get_driver(device);
|
const auto* driver = device_get_driver(device);
|
||||||
|
|
||||||
@ -40,8 +82,69 @@ bool keyboard_is_present(Device* device) {
|
|||||||
return KEYBOARD_DRIVER_API(driver)->is_present(device);
|
return KEYBOARD_DRIVER_API(driver)->is_present(device);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
error_t keyboard_subscribe(Device* device, KeyboardEventSubscription* sub) {
|
||||||
|
mutex_lock(&subscriptions_mutex.handle);
|
||||||
|
|
||||||
|
// Avoid cyclic subscription list that would loop forever
|
||||||
|
if (subscriptions == sub) {
|
||||||
|
mutex_unlock(&subscriptions_mutex.handle);
|
||||||
|
return ERROR_INVALID_STATE;
|
||||||
|
}
|
||||||
|
|
||||||
|
sub->internal.device = device;
|
||||||
|
sub->internal.head = 0;
|
||||||
|
sub->internal.count = 0;
|
||||||
|
sub->internal.next = subscriptions;
|
||||||
|
subscriptions = sub;
|
||||||
|
|
||||||
|
mutex_unlock(&subscriptions_mutex.handle);
|
||||||
|
return ERROR_NONE;
|
||||||
|
}
|
||||||
|
|
||||||
|
error_t keyboard_unsubscribe(Device*, KeyboardEventSubscription* sub) {
|
||||||
|
error_t result = ERROR_NOT_FOUND;
|
||||||
|
|
||||||
|
mutex_lock(&subscriptions_mutex.handle);
|
||||||
|
for (KeyboardEventSubscription** link = &subscriptions; *link != nullptr; link = &(*link)->internal.next) {
|
||||||
|
if (*link == sub) {
|
||||||
|
*link = sub->internal.next;
|
||||||
|
result = ERROR_NONE;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
mutex_unlock(&subscriptions_mutex.handle);
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
error_t keyboard_poll(Device* device, KeyboardEventSubscription* sub, KeyboardKeyData* out_data) {
|
||||||
|
mutex_lock(&subscriptions_mutex.handle);
|
||||||
|
|
||||||
|
if (sub->internal.count > 0) {
|
||||||
|
*out_data = sub->internal.queue[sub->internal.head];
|
||||||
|
sub->internal.head = (sub->internal.head + 1) % KEYBOARD_EVENT_QUEUE_CAPACITY;
|
||||||
|
sub->internal.count--;
|
||||||
|
mutex_unlock(&subscriptions_mutex.handle);
|
||||||
|
return ERROR_NONE;
|
||||||
|
}
|
||||||
|
|
||||||
|
// No queued event of our own - try a synchronous read (if the driver has one; a driver that
|
||||||
|
// only pushes via keyboard_emit_key() has no read_key, and there's simply nothing new yet).
|
||||||
|
KeyboardKeyData data;
|
||||||
|
if (keyboard_read_key(device, &data) != ERROR_NONE || data.key == 0) {
|
||||||
|
mutex_unlock(&subscriptions_mutex.handle);
|
||||||
|
return ERROR_TIMEOUT;
|
||||||
|
}
|
||||||
|
|
||||||
|
fan_out_to_subscribers(device, data, sub);
|
||||||
|
|
||||||
|
*out_data = data;
|
||||||
|
mutex_unlock(&subscriptions_mutex.handle);
|
||||||
|
return ERROR_NONE;
|
||||||
|
}
|
||||||
|
|
||||||
const DeviceType KEYBOARD_TYPE {
|
const DeviceType KEYBOARD_TYPE {
|
||||||
.name = "keyboard"
|
.name = "keyboard",
|
||||||
};
|
};
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
168
TactilityKernel/tests/source/keyboard_test.cpp
Normal file
168
TactilityKernel/tests/source/keyboard_test.cpp
Normal file
@ -0,0 +1,168 @@
|
|||||||
|
#include "doctest.h"
|
||||||
|
#include <tactility/device.h>
|
||||||
|
#include <tactility/driver.h>
|
||||||
|
#include <tactility/drivers/keyboard.h>
|
||||||
|
#include <tactility/module.h>
|
||||||
|
|
||||||
|
static Module keyboard_test_module = {
|
||||||
|
.name = "keyboard_test_module",
|
||||||
|
.start = nullptr,
|
||||||
|
.stop = nullptr
|
||||||
|
};
|
||||||
|
|
||||||
|
static int read_key_call_count = 0;
|
||||||
|
|
||||||
|
// First call reports a key press, every call after that reports nothing pending (key == 0),
|
||||||
|
// matching the convention every real driver uses (see e.g. sdl_keyboard_read_key).
|
||||||
|
static error_t fake_read_key(Device*, KeyboardKeyData* data) {
|
||||||
|
read_key_call_count++;
|
||||||
|
if (read_key_call_count == 1) {
|
||||||
|
data->key = 'a';
|
||||||
|
data->pressed = true;
|
||||||
|
data->continue_reading = false;
|
||||||
|
} else {
|
||||||
|
data->key = 0;
|
||||||
|
data->pressed = false;
|
||||||
|
data->continue_reading = false;
|
||||||
|
}
|
||||||
|
return ERROR_NONE;
|
||||||
|
}
|
||||||
|
|
||||||
|
static const KeyboardApi fake_keyboard_api = {
|
||||||
|
.read_key = fake_read_key,
|
||||||
|
.get_backlight = nullptr,
|
||||||
|
.is_present = nullptr,
|
||||||
|
};
|
||||||
|
|
||||||
|
static Driver fake_keyboard_driver = {
|
||||||
|
.name = "fake_keyboard_driver",
|
||||||
|
.compatible = (const char*[]) { "keyboard_test,fake", nullptr },
|
||||||
|
.start_device = nullptr,
|
||||||
|
.stop_device = nullptr,
|
||||||
|
.api = &fake_keyboard_api,
|
||||||
|
.device_type = &KEYBOARD_TYPE,
|
||||||
|
.owner = &keyboard_test_module,
|
||||||
|
.internal = nullptr,
|
||||||
|
};
|
||||||
|
|
||||||
|
// No read_key: this driver only ever pushes events via keyboard_emit_key().
|
||||||
|
static const KeyboardApi fake_emit_only_keyboard_api = {
|
||||||
|
.read_key = nullptr,
|
||||||
|
.get_backlight = nullptr,
|
||||||
|
.is_present = nullptr,
|
||||||
|
};
|
||||||
|
|
||||||
|
static Driver fake_emit_only_keyboard_driver = {
|
||||||
|
.name = "fake_emit_only_keyboard_driver",
|
||||||
|
.compatible = (const char*[]) { "keyboard_test,fake_emit_only", nullptr },
|
||||||
|
.start_device = nullptr,
|
||||||
|
.stop_device = nullptr,
|
||||||
|
.api = &fake_emit_only_keyboard_api,
|
||||||
|
.device_type = &KEYBOARD_TYPE,
|
||||||
|
.owner = &keyboard_test_module,
|
||||||
|
.internal = nullptr,
|
||||||
|
};
|
||||||
|
|
||||||
|
TEST_CASE("keyboard_poll's default implementation fans out a read_key result to other subscribers") {
|
||||||
|
read_key_call_count = 0;
|
||||||
|
|
||||||
|
static Device fake_device {
|
||||||
|
.name = "fake_keyboard_device",
|
||||||
|
.config = nullptr,
|
||||||
|
.parent = nullptr,
|
||||||
|
};
|
||||||
|
|
||||||
|
CHECK_EQ(driver_construct_add(&fake_keyboard_driver), ERROR_NONE);
|
||||||
|
CHECK_EQ(device_construct_add(&fake_device, "keyboard_test,fake"), ERROR_NONE);
|
||||||
|
CHECK_EQ(device_start(&fake_device), ERROR_NONE);
|
||||||
|
|
||||||
|
KeyboardEventSubscription sub_a {};
|
||||||
|
KeyboardEventSubscription sub_b {};
|
||||||
|
CHECK_EQ(keyboard_subscribe(&fake_device, &sub_a), ERROR_NONE);
|
||||||
|
CHECK_EQ(keyboard_subscribe(&fake_device, &sub_b), ERROR_NONE);
|
||||||
|
|
||||||
|
KeyboardKeyData out {};
|
||||||
|
CHECK_EQ(keyboard_poll(&fake_device, &sub_a, &out), ERROR_NONE);
|
||||||
|
CHECK_EQ(out.key, 'a');
|
||||||
|
CHECK_EQ(read_key_call_count, 1);
|
||||||
|
|
||||||
|
// sub_b receives the same event without a second hardware read - it was fanned out by sub_a's poll.
|
||||||
|
CHECK_EQ(keyboard_poll(&fake_device, &sub_b, &out), ERROR_NONE);
|
||||||
|
CHECK_EQ(out.key, 'a');
|
||||||
|
CHECK_EQ(read_key_call_count, 1);
|
||||||
|
|
||||||
|
// sub_b's queue is now empty; polling again falls through to a real (empty) hardware read.
|
||||||
|
CHECK_EQ(keyboard_poll(&fake_device, &sub_b, &out), ERROR_TIMEOUT);
|
||||||
|
CHECK_EQ(read_key_call_count, 2);
|
||||||
|
|
||||||
|
CHECK_EQ(keyboard_unsubscribe(&fake_device, &sub_a), ERROR_NONE);
|
||||||
|
CHECK_EQ(keyboard_unsubscribe(&fake_device, &sub_b), ERROR_NONE);
|
||||||
|
CHECK_EQ(device_stop(&fake_device), ERROR_NONE);
|
||||||
|
CHECK_EQ(device_remove(&fake_device), ERROR_NONE);
|
||||||
|
CHECK_EQ(device_destruct(&fake_device), ERROR_NONE);
|
||||||
|
CHECK_EQ(driver_remove_destruct(&fake_keyboard_driver), ERROR_NONE);
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_CASE("keyboard_emit_key delivers to subscribers of a driver with no read_key") {
|
||||||
|
static Device fake_device {
|
||||||
|
.name = "fake_emit_only_keyboard_device",
|
||||||
|
.config = nullptr,
|
||||||
|
.parent = nullptr,
|
||||||
|
};
|
||||||
|
|
||||||
|
CHECK_EQ(driver_construct_add(&fake_emit_only_keyboard_driver), ERROR_NONE);
|
||||||
|
CHECK_EQ(device_construct_add(&fake_device, "keyboard_test,fake_emit_only"), ERROR_NONE);
|
||||||
|
CHECK_EQ(device_start(&fake_device), ERROR_NONE);
|
||||||
|
|
||||||
|
KeyboardEventSubscription sub {};
|
||||||
|
CHECK_EQ(keyboard_subscribe(&fake_device, &sub), ERROR_NONE);
|
||||||
|
|
||||||
|
// Nothing pushed yet, and there's no read_key to fall back on.
|
||||||
|
KeyboardKeyData out {};
|
||||||
|
CHECK_EQ(keyboard_poll(&fake_device, &sub, &out), ERROR_TIMEOUT);
|
||||||
|
|
||||||
|
KeyboardKeyData emitted { .key = 'z', .pressed = true, .continue_reading = false };
|
||||||
|
keyboard_emit_key(&fake_device, emitted);
|
||||||
|
|
||||||
|
CHECK_EQ(keyboard_poll(&fake_device, &sub, &out), ERROR_NONE);
|
||||||
|
CHECK_EQ(out.key, 'z');
|
||||||
|
|
||||||
|
CHECK_EQ(keyboard_unsubscribe(&fake_device, &sub), ERROR_NONE);
|
||||||
|
CHECK_EQ(device_stop(&fake_device), ERROR_NONE);
|
||||||
|
CHECK_EQ(device_remove(&fake_device), ERROR_NONE);
|
||||||
|
CHECK_EQ(device_destruct(&fake_device), ERROR_NONE);
|
||||||
|
CHECK_EQ(driver_remove_destruct(&fake_emit_only_keyboard_driver), ERROR_NONE);
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_CASE("keyboard_unsubscribe stops a subscription from receiving further fanned-out events") {
|
||||||
|
read_key_call_count = 0;
|
||||||
|
|
||||||
|
static Device fake_device {
|
||||||
|
.name = "fake_keyboard_device_2",
|
||||||
|
.config = nullptr,
|
||||||
|
.parent = nullptr,
|
||||||
|
};
|
||||||
|
|
||||||
|
CHECK_EQ(driver_construct_add(&fake_keyboard_driver), ERROR_NONE);
|
||||||
|
CHECK_EQ(device_construct_add(&fake_device, "keyboard_test,fake"), ERROR_NONE);
|
||||||
|
CHECK_EQ(device_start(&fake_device), ERROR_NONE);
|
||||||
|
|
||||||
|
KeyboardEventSubscription sub_a {};
|
||||||
|
KeyboardEventSubscription sub_b {};
|
||||||
|
CHECK_EQ(keyboard_subscribe(&fake_device, &sub_a), ERROR_NONE);
|
||||||
|
CHECK_EQ(keyboard_subscribe(&fake_device, &sub_b), ERROR_NONE);
|
||||||
|
CHECK_EQ(keyboard_unsubscribe(&fake_device, &sub_b), ERROR_NONE);
|
||||||
|
|
||||||
|
KeyboardKeyData out {};
|
||||||
|
CHECK_EQ(keyboard_poll(&fake_device, &sub_a, &out), ERROR_NONE);
|
||||||
|
CHECK_EQ(out.key, 'a');
|
||||||
|
|
||||||
|
// sub_b was unsubscribed before the poll, so nothing was fanned out to it.
|
||||||
|
CHECK_EQ(keyboard_poll(&fake_device, &sub_b, &out), ERROR_TIMEOUT);
|
||||||
|
|
||||||
|
CHECK_EQ(keyboard_unsubscribe(&fake_device, &sub_a), ERROR_NONE);
|
||||||
|
CHECK_EQ(device_stop(&fake_device), ERROR_NONE);
|
||||||
|
CHECK_EQ(device_remove(&fake_device), ERROR_NONE);
|
||||||
|
CHECK_EQ(device_destruct(&fake_device), ERROR_NONE);
|
||||||
|
CHECK_EQ(driver_remove_destruct(&fake_keyboard_driver), ERROR_NONE);
|
||||||
|
}
|
||||||
Loading…
x
Reference in New Issue
Block a user