From dd564f094a9202b086b3e4ee14ce0a686dbecceb Mon Sep 17 00:00:00 2001 From: Lephe Date: Fri, 5 Mar 2021 09:31:34 +0100 Subject: [PATCH] keydev: add a keyboard device layer to abstract away globals The new keyboard device (keydev) interface implements the kernel's view of a keyboard providing input events. Its main role is to abstract all the globals of the KEYSC driver and getkey functions into a separate object: the "keyboard device". The device implements event transformations such as modifiers and repeats, instead of leaving them to getkey. While this can seem surprising at first, a real keyboard controller is responsible for repeats and modifier actions depend on the state of the keyboard which is only tracked in real-time. In this commit, getkey() has not changed yet apart from indirectly using the keydev interface with pollevent(). It will be changed soon to use event transforms in keydev_read(), and will be left in charge of providing repeat profiles, handling return-to-menu, backlight changes and timeouts, all of which are user convenience features. --- CMakeLists.txt | 1 + include/gint/defs/attributes.h | 2 + include/gint/defs/types.h | 2 + include/gint/drivers/keydev.h | 264 +++++++++++++++++++++++++ include/gint/keyboard.h | 6 - src/keysc/keydev.c | 349 +++++++++++++++++++++++++++++++++ src/keysc/keysc.c | 189 +++--------------- 7 files changed, 647 insertions(+), 166 deletions(-) create mode 100644 include/gint/drivers/keydev.h create mode 100644 src/keysc/keydev.c diff --git a/CMakeLists.txt b/CMakeLists.txt index 6f8e6fc..3f9adc0 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -29,6 +29,7 @@ set(SOURCES_COMMON src/keysc/getkey.c src/keysc/iokbd.c src/keysc/keycodes.c + src/keysc/keydev.c src/keysc/keysc.c src/kprint/kprint.c src/kprint/kformat_fp.c diff --git a/include/gint/defs/attributes.h b/include/gint/defs/attributes.h index aa2f9c7..0dda841 100644 --- a/include/gint/defs/attributes.h +++ b/include/gint/defs/attributes.h @@ -28,6 +28,8 @@ GCC cannot optimize access size, and reads to memory-mapped I/O with invalid access sizes silently fail - honestly you don't want this to happen */ #define GPACKED(x) __attribute__((packed, aligned(x))) +/* Packed enumerations */ +#define GPACKEDENUM __attribute__((packed)) /* Transparent unions */ #define GTRANSPARENT __attribute__((transparent_union)) diff --git a/include/gint/defs/types.h b/include/gint/defs/types.h index 75cbb1b..c42b2b0 100644 --- a/include/gint/defs/types.h +++ b/include/gint/defs/types.h @@ -11,6 +11,8 @@ #include /* For all fixed-width integer types */ #include +/* For human-readable boolean types */ +#include /* Fixed-width types for bit fields are quite meaningless */ typedef unsigned int uint; diff --git a/include/gint/drivers/keydev.h b/include/gint/drivers/keydev.h new file mode 100644 index 0000000..4d95824 --- /dev/null +++ b/include/gint/drivers/keydev.h @@ -0,0 +1,264 @@ +//--- +// gint:keydev - Kernel's keyboard input devices +//--- + +#include + +#ifndef GINT_KEYDEV +#define GINT_KEYDEV + +/* Size of the buffer event queue */ +#define KEYBOARD_QUEUE_SIZE 32 + +/* keydev_event_t: Change of state in a full row */ +typedef struct { + /* Device time for the event */ + uint16_t time; + /* Keys that changed state */ + uint8_t changed; + /* Row number */ + uint8_t row :4; + /* Type of change, either KEYEV_DOWN or KEYEV_UP */ + uint8_t kind :4; + +} keydev_event_t; + +/* Event transforms + + Every keyboard input device has a built-in event stream editor that can + transform the events in various ways to change the abstraction details, from + the raw pollevent() with no transforms to the highest-level getkey(). + + Because many transforms like need to observe the state + of the keyboard at the time of the event to transform, this process needs to + be performed right when events are requested and cannot be done later + without keeping around a lot of information about past states. + + Just as switching from pollevent() to getkey() requires some caution, + changing the transform settings while keys are pressed or modifiers are + active minimally impacts the output of the editor. Here is a summary of the + interactions: + + * and are only concerned about the + current event and can be turned ON and OFF at any time without problems. + + * and only look at the state of the keyboard + with keydev_keydown() and are not affected by other options. + + * and only trigger when a SHIFT or ALPHA + press-release is performed when the keyboard is idle. + -> If turned ON while a key is pressed, nothing happens until a further + SHIFT or ALPHA key press while the keyboard is idle. + -> If turned OFF while (1) a modifier key is being pressed, or (2) a + modifier key has been pressed and released (charging up the delayed + modifier), the charge is lost. + Thus turning delayed modifiers ON->OFF->ON drops the current charge. + + * has the most interactions. + -> Toggling or during a repeat takes + effect immediately, so a streak can start with instant modifiers + disabled, enable them mid-flight, and start producing repeating events + with instant modifiers. + -> Switching or during a modified streak + drops the modifier immediately. + -> Switching ON any modifier transform breaks a SHIFT or ALPHA streak. + -> Switching OFF all modifier transforms allows a currently-pressed SHIFT + or ALPHA key to start a repeat streak, which (due to the way candidates + for repeats are considered) can only be used to turn a incomplete + delayed modifier charge or an unused instant modifier into a streak. + + Generally speaking changing options while keys are pressed is safe, but it + is better to do it while the device is idle for more consistency. + + pollevent() provides the raw events and bypasses the transforms. However, + the information needed to resume transforming is still tracked when + generating raw events: for instance, which key is a candidate for repetition + is still tracked (by contrast, getkey() needs special code to catch up if + pollevent() unqueued events that it needed). Delayed modifiers are not + tracked because they can be charged only if the transform is enabled when + the modifier key is pressed. */ +enum { + /* Delayed SHIFT: Pressing then immediately releasing SHIFT when the + keyboard is idle applies SHIFT to the next repeat streak. */ + KEYDEV_TR_DELAYED_SHIFT = 0x01, + /* Delayed ALPHA: Idem with the ALPHA key */ + KEYDEV_TR_DELAYED_ALPHA = 0x02, + /* Instant SHIFT: Each individual event of every repeat streak gets + SHIFT applied if SHIFT is pressed at the time of the repeat. */ + KEYDEV_TR_INSTANT_SHIFT = 0x04, + /* Instant ALPHA: Idem with the ALPHA key */ + KEYDEV_TR_INSTANT_ALPHA = 0x08, + /* Repeats: Keys are repeated according to a repeat filter function */ + KEYDEV_TR_REPEATS = 0x10, + /* Delete Modifiers: Remove modifier keys from generated events, which + are generally useless when delayed/instant SHIFT/ALPHA are used */ + KEYDEV_TR_DELETE_MODIFIERS = 0x20, + /* Delete Releases: Remove KEYEV_UP events */ + KEYDEV_TR_DELETE_RELEASES = 0x40, +}; + +/* keydev_transform_t: Full specification for transforms on the event stream */ +typedef struct { + /* List of enabled transforms. The order is cannot be changed because + any other order than the default produces useless results. This is + an OR-combination of KEYDEV_TR_* flags. */ + int enabled; + + /* Repeater function. This function is called whenever (key) is pressed + (KEYEV_DOWN) or repeated (KEYEV_HOLD), and should indicate how long + to wait until the next repeat. This setting is meaningful only if + is enabled. + + -> (key) is the key currently being repeated. + -> (duration) is the time since it was first pressed (us). + -> (count) is the number of repeats produced so far. + + The function should either return -1 to disable all further repeats, + or a positive number of microseconds until the next repeat. Note + that the precision of the delay is limited by the speed at which the + keyboard is scanned, which is nowhere near microsecond-level. By + default (128 Hz) the precision is about 7.8 ms. */ + int (*repeater)(int key, int duration, int count); + +} GPACKEDENUM keydev_transform_t; + +/* keydev_t: Keyboard device + + This structure represents the state and settings of a keyboard device that + provides input to the kernel and applications. The notion of keyboard device + is useful for demo/replays that input events without the physical keyboard, + and a couple of corner uses like control over USB. + + The keyboard device has built-in event transformations, which modifty the + stream of events by adding information, combining modifiers, and removing + undesired events. Because the event transformation reky on the current state + of the keyboard, they must be run by the driver whenever events are read, so + they are tied to the device. + + The default keyboard functions pollevent(), waitevent(), getkey() and + keydown() are shortcuts for keydev functions using the physical keyboard as + their input. */ +typedef struct { + /* Latest state of keys we are aware of. At every processing step, the + different between this and the fresh information is queued and this + is updated. state_now is identical to the real state obtained from + the device unless earlier events failed to be queued, in which case + a difference is maintained so they will be reconsidered later. */ + GALIGNED(4) uint8_t state_now[12]; + /* State of keys based on produced events. (state_queue + queue) is + always identical to (state_now). When the queue is empty both states + are the same. This is the user's view of the keyboard. */ + GALIGNED(4) uint8_t state_queue[12]; + /* Current device time in scanning-ticks */ + uint time; + /* Last time when repeats were considered */ + uint time_repeats; + + /* Event queue (circular buffer) */ + keydev_event_t queue[KEYBOARD_QUEUE_SIZE]; + /* Next event in queue, position after last event in queue */ + int8_t queue_next; + int8_t queue_end; + /* Number of events lost because of missing queue space */ + uint events_lost; + /* Crafted events waiting to be picked up */ + key_event_t out[8]; + int out_size; + + /* Event transforms */ + keydev_transform_t tr; + + // + + /* delayed_* is set when the delayed modifier is active (after a press/ + release). pressed_* is set between the press and the release. */ + uint pressed_shift :1; + uint pressed_alpha :1; + uint delayed_shift :1; + uint delayed_alpha :1; + uint :4; + + // + + /* Candidate key for repeats (or 0 if no key is candidate yet) */ + int rep_key; + /* Number of repeats alreay sent */ + int rep_count; + /* Time since key was first pressed (us) */ + int rep_time; + /* Delay until next repeat, set by the repeat planner (us) */ + int rep_delay; + +} keydev_t; + +/* keydev_std(): Standard keyboard input device + Returns the keyboard device structure for the calculator's keyboard. */ +keydev_t *keydev_std(void); + +//--- +// Functions for keyboard device drivers +//--- + +/* keydev_init(): Initialize a keyboard device to its idle state */ +void keydev_init(keydev_t *d); + +/* keydev_process_state(): Process the new keyboard states for events + This function compares the provided scanned state with the stored state and + generates events to catch up. This should be used by scan-based keyboard + devices after a scan. */ +void keydev_process_state(keydev_t *d, uint8_t state[12]); + +/* keydev_process_key(): Process a new key state for events + This function compares the provided key state with the stored state and + generates events to catch up. This should be used by event-based keyboard + devices (such as demo replays) to feed in new events. */ +void keydev_process_key(keydev_t *d, int keycode, bool state); + +/* keydev_tick(): Prepare the next tick + This function maintains time trackers in the device and should be called in + each frame after the scanning is finished and the keydev_process_*() + functions have been called. The timer elapsed since the last tick should + be specified as well. */ +void keydev_tick(keydev_t *d, uint us); + +//--- +// Low-level API to read events from the device +//--- + +/* keydev_unqueue_event(): Retrieve the next keyboard event in queue + + This source provides the queued KEYEV_UP and KEYEV_DOWN events generated + from the regular scans in chronological order. It does not generate the + KEYEV_HOLD events though, keyev_repeat_event() must be used to obtain these + at the end of every tick. */ +key_event_t keydev_unqueue_event(keydev_t *d); + +/* keydev_repeat_event(): Generate a repeat event if applicable + + At the end of every scan tick (or later if the application is unable to keep + up), this source will generate a repeat event if the repeat transform is + enabled and the conditions for a repeat are satisfied. */ +key_event_t keydev_repeat_event(keydev_t *d); + +/* keydev_idle(): Check if all keys are released + A list of keys to ignore can be specified as variable arguments. The list + must be terminated by a 0 keycode. */ +bool keydev_idle(keydev_t *d, ...); + +//--- +// High-level API to read from the device +//--- + +/* keydev_keydown(): Check if a key is down according to generated events */ +bool keydev_keydown(keydev_t *d, int key); + +/* keydev_transform(): Obtain current transform parameters */ +keydev_transform_t keydev_transform(keydev_t *d); + +/* keydev_set_transform(): Set transform parameters */ +void keydev_set_transform(keydev_t *d, keydev_transform_t tr); + +/* keydev_read(): Retrieve the next transformed event */ +key_event_t keydev_read(keydev_t *d); + +#endif /* GINT_KEYDEV */ diff --git a/include/gint/keyboard.h b/include/gint/keyboard.h index dbb372b..a7f7317 100644 --- a/include/gint/keyboard.h +++ b/include/gint/keyboard.h @@ -114,12 +114,6 @@ enum KEYEV_HOLD = 3, /* A key that was pressed has been held down */ }; -/* Size of the buffer event queue, can be customized using gint's configure - script before compiling the library. Better be a power of 2. */ -#ifndef KEYBOARD_QUEUE_SIZE -#define KEYBOARD_QUEUE_SIZE 32 -#endif - /* Keyboard frequency analysis is a runtime setting since gint 2.4. This macro is preserved for compatibility until gint 3. */ #define KEYBOARD_SCAN_FREQUENCY keysc_scan_frequency() diff --git a/src/keysc/keydev.c b/src/keysc/keydev.c new file mode 100644 index 0000000..3d87411 --- /dev/null +++ b/src/keysc/keydev.c @@ -0,0 +1,349 @@ +//--- +// gint:keydev - Generic input handling on keyboard devices +//--- + +#include +#include +#include +#include +#include +#include + +void keydev_init(keydev_t *d) +{ + memset(d, 0, sizeof *d); +} + +//--- +// Driver event generation +//--- + +/* queue_push(): Add an event in a device's buffer + Returns false if the event cannot be pushed. */ +static bool queue_push(keydev_t *d, keydev_event_t e) +{ + int next = (d->queue_end + 1) % KEYBOARD_QUEUE_SIZE; + if(next == d->queue_next) + { + d->events_lost++; + return false; + } + + d->queue[d->queue_end] = e; + d->queue_end = next; + return true; +} + +/* queue_poll(): Generate key events from the buffer + Sets (*e) and returns true on success, otherwise false. */ +static bool queue_poll(keydev_t *d, keydev_event_t *e) +{ + if(d->queue_next == d->queue_end) return false; + + *e = d->queue[d->queue_next]; + d->queue_next = (d->queue_next + 1) % KEYBOARD_QUEUE_SIZE; + return true; +} + +/* keydev_process_state(): Process the new keyboard states for events */ +void keydev_process_state(keydev_t *d, uint8_t scan[12]) +{ + /* Compare new data with the internal state. Push releases before + presses so that a key change occurring within a single analysis + frame can be performed. This happens all the time when going back to + the main MENU via gint_osmenu() on a keybind. */ + for(int row = 0; row < 12; row++) + { + int diff = ~scan[row] & d->state_now[row]; + if(!diff) continue; + + /* Update internal status if the event can be pushed */ + keydev_event_t e = { d->time, diff, row, KEYEV_UP }; + if(queue_push(d, e)) d->state_now[row] &= scan[row]; + } + for(int row = 0; row < 12; row++) + { + int diff = scan[row] & ~d->state_now[row]; + if(!diff) continue; + + keydev_event_t e = { d->time, diff, row, KEYEV_DOWN }; + if(queue_push(d, e)) d->state_now[row] |= scan[row]; + } +} + +/* keydev_process_key(): Process a new key state for events */ +void keydev_process_key(keydev_t *d, int keycode, bool state) +{ + /* If the key has changed state, push an event */ + int row = (keycode >> 4) ^ 1; + int col = 0x80 >> (keycode & 0x7); + + int prev = d->state_now[row] & col; + if(state && !prev) + { + keydev_event_t e = { d->time, col, row, KEYEV_DOWN }; + if(queue_push(d, e)) d->state_now[row] |= col; + } + else if(!state && prev) + { + keydev_event_t e = { d->time, col, row, KEYEV_UP }; + if(queue_push(d, e)) d->state_now[row] &= ~col; + } +} + +void keydev_tick(keydev_t *d, uint us) +{ + d->time++; + + if(d->rep_key != 0) + { + if(d->rep_delay >= 0) + d->rep_delay = max(d->rep_delay - (int)us, 0); + d->rep_time += us; + } +} + +//--- +// Keyboard event generation +//--- + +static bool can_repeat(keydev_t *d, int key) +{ + int tr = d->tr.enabled; + int shift = tr & (KEYDEV_TR_DELAYED_SHIFT | KEYDEV_TR_INSTANT_SHIFT); + int alpha = tr & (KEYDEV_TR_DELAYED_ALPHA | KEYDEV_TR_INSTANT_ALPHA); + + return !(key == KEY_SHIFT && shift) && !(key == KEY_ALPHA && alpha); +} + +/* keydev_unqueue_event(): Retrieve the next keyboard event in queue */ +key_event_t keydev_unqueue_event(keydev_t *d) +{ + /* Every device event is unfolded into up to 8 keyboard events, stored + temporarily in the driver's structure. */ + keydev_event_t e; + key_event_t kev = { .type = KEYEV_NONE, .time = d->time }; + + /* If there are no events, generate some */ + if(d->out_size == 0) + { + if(!queue_poll(d, &e)) return kev; + int changed = e.changed; + kev.type = e.kind; + + for(int code = ((e.row ^ 1) << 4) | 0x7; code & 0x7; code--) + { + if(changed & 1) + { + kev.key = code; + d->out[d->out_size++] = kev; + } + changed >>= 1; + } + } + + /* Return one of the available events */ + kev = d->out[--(d->out_size)]; + + /* Update the event state accordingly */ + int row = (kev.key >> 4) ^ 1; + int col = 0x80 >> (kev.key & 0x7); + + if(kev.type == KEYEV_DOWN) + { + d->state_queue[row] |= col; + /* Mark this key as the currently repeating one */ + if(d->rep_key == 0 && can_repeat(d, kev.key)) + { + d->rep_key = kev.key; + d->rep_count = -1; + d->rep_time = 0; + d->rep_delay = 0; + } + } + if(kev.type == KEYEV_UP) + { + d->state_queue[row] &= ~col; + /* End the current repeating streak */ + if(d->rep_key == kev.key) + { + d->rep_key = 0; + d->rep_count = -1; + d->rep_time = -1; + d->rep_delay = -1; + d->delayed_shift = 0; + d->delayed_alpha = 0; + } + } + + return kev; +} + +/* keydev_repeat_event(): Generate a repeat event if applicable */ +key_event_t keydev_repeat_event(keydev_t *d) +{ + key_event_t e = { .type = KEYEV_NONE, .time = d->time }; + /* is disabled */ + if(!(d->tr.enabled & KEYDEV_TR_REPEATS)) return e; + /* No key is being repeated, or it's too early */ + if(!d->rep_key || d->rep_delay != 0) return e; + /* Key is blocked by transform options modified during the streak */ + if(!can_repeat(d, d->rep_key)) return e; + + /* Plan the next repeat the currently-pressed key */ + int elapsed = (int16_t)(d->time - d->rep_time); + d->rep_delay = -1; + d->rep_count++; + + /* Returning < 0 will block further repeats */ + if(d->tr.repeater) + d->rep_delay = d->tr.repeater(d->rep_key,elapsed,d->rep_count); + + /* Don't return an event on the first call (it's a KEYEV_DOWN) */ + if(!d->rep_count) return e; + + e.key = d->rep_key; + e.type = KEYEV_HOLD; + return e; +} + +/* keydev_keydown(): Check if a key is down according to generated events */ +bool keydev_keydown(keydev_t *d, int key) +{ + int row = (key >> 4) ^ 1; + int col = 0x80 >> (key & 0x7); + + return (d->state_queue[row] & col) != 0; +} + +/* keydev_idle(): Check if all keys are released */ +bool keydev_idle(keydev_t *d, ...) +{ + uint32_t *state = (void *)d->state_queue; + uint32_t check[3] = { state[0], state[1], state[2] }; + int key; + + va_list args; + va_start(args, d); + while((key = va_arg(args, int))) + { + int row = (key >> 4) ^ 1; + int col = 0x80 >> (key & 0x7); + ((uint8_t *)check)[row] &= ~col; + } + va_end(args); + + return (check[0] == 0) && (check[1] == 0) && (check[2] == 0); +} + +//--- +// Event transforms +//--- + +/* keydev_transform(): Obtain current transform parameters */ +keydev_transform_t keydev_transform(keydev_t *d) +{ + return d->tr; +} + +/* keydev_set_transform(): Set transform parameters */ +void keydev_set_transform(keydev_t *d, keydev_transform_t tr) +{ + int change = d->tr.enabled ^ tr.enabled; + + if(change & KEYDEV_TR_DELAYED_SHIFT) + { + d->pressed_shift = 0; + d->delayed_shift = 0; + } + if(change & KEYDEV_TR_DELAYED_ALPHA) + { + d->pressed_alpha = 0; + d->delayed_alpha = 0; + } + + d->tr = tr; +} + +/* keydev_read(): Retrieve the next transformed event */ +key_event_t keydev_read(keydev_t *d) +{ + #define opt(NAME) (d->tr.enabled & KEYDEV_TR_ ## NAME) + key_event_t e; + + while(1) + { + /* Repeat at the end of every tick, or if we're late */ + bool empty = (d->queue_next == d->queue_end) && !d->out_size; + bool end_of_tick = (d->time_repeats == d->time - 1) && empty; + bool late_repeat = (d->time_repeats <= d->time - 2); + + if(end_of_tick || late_repeat) + e = keydev_repeat_event(d); + if(e.type == KEYEV_NONE) + e = keydev_unqueue_event(d); + if(e.type == KEYEV_NONE) + return e; + + int k = e.key; + + // and + + if(e.type == KEYEV_DOWN || e.type == KEYEV_HOLD) + { + if(opt(INSTANT_SHIFT) && k != KEY_SHIFT) + e.shift |= keydev_keydown(d, KEY_SHIFT); + if(opt(INSTANT_ALPHA) && k != KEY_ALPHA) + e.alpha |= keydev_keydown(d, KEY_ALPHA); + + } + + // and + + if(opt(DELAYED_SHIFT)) + { + if(e.type == KEYEV_DOWN && k == KEY_SHIFT) + { + d->pressed_shift |= keydev_idle(d,KEY_SHIFT,0); + } + else if(e.type != KEYEV_UP && k == d->rep_key) + { + e.shift |= d->delayed_shift; + d->pressed_shift = 0; + } + else if(e.type == KEYEV_UP && d->pressed_shift) + { + d->pressed_shift = 0; + d->delayed_shift = 1; + } + } + + if(opt(DELAYED_ALPHA)) + { + if(e.type == KEYEV_DOWN && k == KEY_ALPHA) + { + d->pressed_alpha |= keydev_idle(d,KEY_ALPHA,0); + } + else if(e.type != KEYEV_UP && k == d->rep_key) + { + e.alpha |= d->delayed_alpha; + d->pressed_alpha = 0; + } + else if(e.type == KEYEV_UP && d->pressed_alpha) + { + d->pressed_alpha = 0; + d->delayed_alpha = 1; + } + } + + // + if(opt(DELETE_MODIFIERS) && !can_repeat(d, k)) continue; + + // + if(opt(DELETE_RELEASES) && e.type == KEYEV_UP) continue; + + return e; + } + + #undef opt +} diff --git a/src/keysc/keysc.c b/src/keysc/keysc.c index 6aac52a..8c180ae 100644 --- a/src/keysc/keysc.c +++ b/src/keysc/keysc.c @@ -7,6 +7,7 @@ #include #include #include +#include #include #include @@ -18,78 +19,23 @@ #include /* Keyboard scan frequency in Hertz. Start with 128 Hz, this frequency *must - be high* for the keyboard to work! */ + be high* for the keyboard to work! Reading at low frequencies produces a lot + of artifacts. See https://www.casiopeia.net/forum/viewtopic.php?p=20592. */ static int scan_frequency = 128; +/* Approximation in microseconds, used by the timer and repeat delays */ +static uint32_t scan_frequency_us = 7812; /* 1000000 / scan_frequency */ + /* Keyboard scanner timer */ static int keysc_tid = -1; +/* Keyboard input device for this Key Scan Interface */ +static keydev_t dev_keysc; -//--- -// Keyboard buffer -//--- - -/* The driver's internal state. At each step of time, this file compares the - internal state with the hardware state and generates events accordingly. - Events can be seen as a delta-encoding of the keyboard state over time. - - To ensure that adding pending events to the last-read state always gives the - internal driver state, this array is not updated if the generation of an - event fails. (Most likely the even will be regenerated at the next scan.) */ -static volatile uint8_t state[12] = { 0 }; - -/* The driver's current event state. This state corresponds to the sum of all - events sent to the user so far.When the event queue is empty, this is equal - to [state]. For each generated event, this array is updated to reflect the - user's view of the keyboard. */ -static uint8_t current[12] = { 0 }; - -/* A driver event, which is a common change in a full row */ -typedef struct +/* keydev_std(): Standard keyboard input device */ +keydev_t *keydev_std(void) { - uint16_t time; /* Locally unique time identifier */ - uint8_t changed; /* Keys that changed state */ - uint8_t row :4; /* Row number */ - uint8_t kind :4; /* Type of change, either KEYEV_DOWN or KEYEV_UP */ - -} driver_event_t; - -/* The keyboard event buffer. This is a circular list defined by [buffer_start] - and [buffer_end]. To avoid an ambiguity when start == end, there must always - be at least one free entry. */ -GBSS static driver_event_t buffer[KEYBOARD_QUEUE_SIZE]; -/* Buffer bounds */ -static int buffer_start = 0; -static int buffer_end = 0; - -/* Current time, in keyboard-scanning ticks */ -static uint time = 0; - -/* buffer_push(): Add an event in the keyboard buffer - Returns non-zero if the event cannot be pushed. */ -static int buffer_push(driver_event_t ev) -{ - int next = (buffer_end + 1) % KEYBOARD_QUEUE_SIZE; - if(next == buffer_start) return 1; - - buffer[buffer_end] = ev; - buffer_end = next; - return 0; + return &dev_keysc; } -/* buffer_poll(): Generate key events from the buffer - Sets [ev] and returns zero on success, otherwise non-zero. */ -static int buffer_poll(driver_event_t *ev) -{ - if(buffer_start == buffer_end) return 1; - - *ev = buffer[buffer_start]; - buffer_start = (buffer_start + 1) % KEYBOARD_QUEUE_SIZE; - return 0; -} - -//--- -// Keyboard scanning -//--- - /* keysc_scan_frequency(): Get the current keyboard scan frequency in Hertz */ int keysc_scan_frequency(void) { @@ -99,9 +45,7 @@ int keysc_scan_frequency(void) /* keysc_scan_frequency_us(): Get keyboard scan delay in microseconds */ uint32_t keysc_scan_frequency_us(void) { - int delay = 1000000 / scan_frequency; - if(delay == 0) delay = 1; - return delay; + return scan_frequency_us; } /* keysc_set_scan_frequency(): Set the keyboard scan frequency in Hertz */ @@ -110,20 +54,21 @@ void keysc_set_scan_frequency(int freq) if(freq < 64) freq = 64; if(freq > 32768) freq = 32768; scan_frequency = freq; - - if(keysc_tid < 0) return; - uint32_t TCOR = timer_delay(keysc_tid, keysc_scan_frequency_us(), 0); - timer_reload(keysc_tid, TCOR); + scan_frequency_us = 1000000 / freq; getkey_refresh_delays(); + + if(keysc_tid < 0) return; + uint32_t TCOR = timer_delay(keysc_tid, scan_frequency_us, 0); + timer_reload(keysc_tid, TCOR); } -/* keysc_frame(): Generate driver events from KEYSC state */ -static void keysc_frame(void) +/* keysc_tick(): Update the keyboard to the next state */ +static int keysc_tick(void) { GALIGNED(2) uint8_t scan[12] = { 0 }; - /* First scan the key matrix: from I/O ports on SH3, KEYSC on SH4 */ + /* Scan the key matrix: from I/O ports on SH3, KEYSC on SH4 */ if(isSH3()) iokbd_scan(scan); else { @@ -133,82 +78,15 @@ static void keysc_frame(void) for(int i = 0; i < 6; i++) array[i] = KEYSC[i]; } - /* Compare new data with the internal state. Push releases before - presses so that a key change occurring within a single analysis - frame can be performed. This happens all the time when going back to - the main MENU via gint_osmenu() on a keybind. */ - for(int row = 0; row < 12; row++) - { - int diff = ~scan[row] & state[row]; - if(!diff) continue; - - /* Update internal status if the event could be pushed */ - driver_event_t ev = { time, diff, row, KEYEV_UP }; - if(!buffer_push(ev)) state[row] &= scan[row]; - } - for(int row = 0; row < 12; row++) - { - int diff = scan[row] & ~state[row]; - if(!diff) continue; - - driver_event_t ev = { time, diff, row, KEYEV_DOWN }; - if(!buffer_push(ev)) state[row] |= scan[row]; - } + keydev_process_state(&dev_keysc, scan); + keydev_tick(&dev_keysc, scan_frequency_us); + return TIMER_CONTINUE; } /* pollevent() - poll the next keyboard event */ key_event_t pollevent(void) { - /* Every time a driver event is unqueued, its key events are generated - and put in this small buffer */ - static key_event_t events[8]; - /* Number of pending events in the previous buffer */ - static int events_pending = 0; - - /* Use pending events first, if they exist */ - - if(events_pending > 0) - { - key_event_t ev = events[--events_pending]; - - /* Update the current state buffer according to [ev] */ - int row = (ev.key >> 4) ^ 1; - int col = 0x80 >> (ev.key & 0x7); - - if(ev.type == KEYEV_DOWN) current[row] |= col; - if(ev.type == KEYEV_UP) current[row] &= ~col; - - return ev; - } - - /* If not key event is pending, generate a chunk by polling the driver - event queue */ - - driver_event_t ev; - if(buffer_poll(&ev)) - { - key_event_t ev = { .type = KEYEV_NONE, .time = time }; - return ev; - } - - /* Generate new key events and return the first of them*/ - int changed = ev.changed; - for(int code = ((ev.row ^ 1) << 4) | 0x7; code & 0x7; code--) - { - if(changed & 1) - { - key_event_t keyev = { - .time = ev.time, - .type = ev.kind, - .key = code, - }; - events[events_pending++] = keyev; - } - changed >>= 1; - } - - /* Call recursively to avoid duplicating the event emission code */ - return pollevent(); + return keydev_unqueue_event(&dev_keysc); } /* waitevent() - wait for the next keyboard event */ @@ -223,7 +101,7 @@ key_event_t waitevent(volatile int *timeout) sleep(); } - key_event_t ev = { .type = KEYEV_NONE, .time = time }; + key_event_t ev = { .type = KEYEV_NONE, .time = dev_keysc.time }; return ev; } @@ -240,10 +118,7 @@ void clearevents(void) /* keydown(): Current key state */ int keydown(int key) { - int row = (key >> 4) ^ 1; - int col = 0x80 >> (key & 0x7); - - return (current[row] & col) != 0; + return keydev_keydown(&dev_keysc, key); } /* keydown_all(): Check a set of keys for simultaneous input @@ -288,21 +163,15 @@ int keydown_any(int key, ...) // Driver initialization //--- -static int callback(void) -{ - keysc_frame(); - time++; - return 0; -} - -/* init() - setup the support timer */ static void init(void) { + keydev_init(&dev_keysc); + /* Set the default repeat times (milliseconds) */ getkey_repeat(400, 40); /* The timer will be stopped when the timer driver is unloaded */ - keysc_tid = timer_setup(TIMER_ANY,keysc_scan_frequency_us(),callback); + keysc_tid = timer_setup(TIMER_ANY, scan_frequency_us, keysc_tick); if(keysc_tid >= 0) timer_start(keysc_tid); gint[HWKBD] = HW_LOADED | (isSH3() ? HWKBD_IO : HWKBD_KSI);