keyboard: add keydown() in the model

This change adds a keydown() function that is synchronized with events,
ie. it returns the key state as seen by previously read events.

It also completely eliminates low-level repeat events, which are not
very meaningul as the keyboard scan frequency goes up (and would be
meaningless if KEYSC interrupts were used), and adapts getkey() by
giving it access to the current driver time through pollevent().
This commit is contained in:
Lephe 2019-09-28 19:22:47 +02:00
parent 86cd9b98d4
commit 95a3345326
No known key found for this signature in database
GPG key ID: 1BBA026E13FC0495
4 changed files with 254 additions and 98 deletions

1
TODO
View file

@ -18,7 +18,6 @@ Complementary elements on existing code.
* topti: support Unicode fonts * topti: support Unicode fonts
* hardware: fill in the HWMEM_FITTLB flag * hardware: fill in the HWMEM_FITTLB flag
* keyboard: think of extended functions * keyboard: think of extended functions
* keyboard: implement keydown() in an event-compliant way
* cpg: spread spectrum on fxcg50 * cpg: spread spectrum on fxcg50
* bopti: blending modes for monochrome bitmaps (use topti assembler) * bopti: blending modes for monochrome bitmaps (use topti assembler)
* display: use more of topti's assembler in drect() * display: use more of topti's assembler in drect()

View file

@ -5,43 +5,84 @@
#ifndef GINT_KEYBOARD #ifndef GINT_KEYBOARD
#define GINT_KEYBOARD #define GINT_KEYBOARD
/* Keyboard, key events and getkey() /* Keyboard, key events, keydown() and getkey()
gint's keyboard driver regularly scans the keyboard matrix and produces *key gint's keyboard driver regularly scans the keyboard matrix and produces *key
events*, the most primitive type of event exposed to the user. A key event events*. A key event basically says that a key has been pressed or released
basically says that a key has been pressed, held, or released at some point at some point in time. Key events are a faithful description of everything
in time. They are suited for real-time applications like games. that happens on the keyboard.
- pollevent() fetches the next queued keyboard event, and returns an event These key events are stored in the *event queue*, from where they can be
of type KEYEV_NONE if none is available. retrieved:
- waitevent() fetches the next queued keyboard event and waits if none is
available. The timeout can be configured.
GUI programs like the system applications will prefer using a GetKey()-like * pollevent() fetches the next keyboard event waiting in the queue. Events
functions that return a single key press at a time, heeds for releases, for will always be returned in chronological order. If there is no keyboard
SHIFT and ALPHA modifiers, handles backlight and return-to-menu. event pending (ie. if nothing happened since last time events were read),
this function returns a dummy event of type KEYEV_NONE.
* waitevent() fetches the next keyboard event waiting in the queue. If the
queue is empty, it waits until one becomes available or a timeout expires,
whichever comes first. The timeout can be configured.
- getkey_opt() is gint's enhanced GetKey()-like function, with support for When events have been read, other functions can be read to check whether a
certain key (or set of keys) is pressed.
* keydown() checks if the specified key is currently pressed. (*)
* keydown_all() checks if all keys of a specified list are pressed. The list
should end with value 0.
* keydown_any() checks if any key of a specified list is pressed. The list
should end with value 0.
(*) The keydown() functions do not report the absolute current state of the
key, but rather the state of the key according to the events that have
been retrieved so far. If the queue contains {KEY_A, KEYEV_DOWN} and
{KEY_A, KEYEV_UP}, the following sequence of events will happen:
keydown(KEY_A) -> 0
pollevent() -> {KEY_A, KEYEV_DOWN}
keydown(KEY_A) -> 1
pollevent() -> {KEY_A, KEYEV_UP}
keydown(KEY_A) -> 0
Synchronizing keydown() and the events increases the accuracy of
keyboard information for fast programs and its reliability for slow
programs.
When all events have been read from the queue, keydown() returns the
absolute current key state, which is what will happen 90% of the time.
Applications that are not interested in event contents but only in pressed
keys can automatically read all events from the queue before they start
using keydown():
* clearevents() reads all pending events from the input queue.
The previous functions are quite low-level. GUI programs that look like the
system applications will prefer using a GetKey()-like functions that return
a single key press at a time, heeds for releases, for SHIFT and ALPHA
modifiers, handles repeats, backlight and return-to-menu.
* getkey_opt() is gint's enhanced GetKey()-like function, with support for
custom repetition and many fine-tunable options. custom repetition and many fine-tunable options.
- getkey() is a specific call to getkey_opt(), that imitates GetKey(). */ * getkey() is a specific call to getkey_opt(), that imitates GetKey().
These functions introduce a new type of key event called KEYEV_HOLD which
represents a key repeat. */
#include <gint/defs/types.h> #include <gint/defs/types.h>
#include <gint/keycodes.h> #include <gint/keycodes.h>
/* key_event_t - any keyboard event /* key_event_t: Low-level or high-level keyboard event
This structure represents an event that occurs on the keyboard. This is a
low-level structure that is produced by the keyboard scanner. It reports key
presses, key releases, and key repeats.
These events are detected and reported each time the keyboard is scanned, This structure represents an event that occurs on the keyboard. It is first
which is 128 Hz by default, so you'll get 128 repeat events by second if a produced by the keyboard scanner with limited information, then possibly
key is kept pressed. We could filter the events to emit one only every enriched by getkey(). Events are produced each time the keyboard is scanned,
second, for example, but it's difficult to do it for all keys at the same which is 128 Hz by default. Hence, a key press and release occuring in less
time. Thus the control of repeat delays is left to getkey(). than 8 ms might not be detected.
When [mod = 1], attributes [shift] and [alpha] indicate whether the key has getkey() returns enriched events with [mod=1], in whic ase [shift] and
been modified. Only key press events returned by getkey() have [mod = 1]. [alpha] indicate whether the key has been modified. Only key press events
Note that you can't have, e.g. [key=KEY_SHIFT] and [mod=1] at the same time. returned by getkey() have [mod=1]. Note that you can't have, e.g.
[key=KEY_SHIFT] and [mod=1] at the same time.
The [time] attribute indicates when the event occurred. It is a snapshot of The [time] attribute indicates when the event occurred. It is a snapshot of
a time counter that increases at each keyboard scan and *wraps around every a time counter that increases at each keyboard scan and *wraps around every
@ -64,7 +105,7 @@ typedef struct
} GPACKED(4) key_event_t; } GPACKED(4) key_event_t;
/* Keyboard event types, as in the type field of key_event_t */ /* Keyboard event types, as in the [type] field of key_event_t */
enum enum
{ {
KEYEV_NONE = 0, /* No event available (poll() only) */ KEYEV_NONE = 0, /* No event available (poll() only) */
@ -87,23 +128,50 @@ enum
#endif #endif
//--- //---
// Keyboard functions // Event-level functions
//--- //---
/* pollevent() - poll the next keyboard event /* pollevent(): Poll the next keyboard event
This function returns the next yet-unpolled event from the keyboard buffer. This function returns the next event from the event queue, chronologically.
If no event is available, it returns a dummy event with type=KEYEV_NONE. If no event is available, it returns a dummy event with type=KEYEV_NONE
This function always returns events with mod=0. */ and time set to the current driver time. This function always returns events
with mod=0. */
key_event_t pollevent(void); key_event_t pollevent(void);
/* waitevent() - wait for the next keyboard event /* waitevent(): Wait for the next keyboard event
This function works as pollevent() but waits if no event is available. When This function works as pollevent() but waits if no event is available. When
timeout=NULL, it waits indefinitely. Otherwise, it waits until *timeout timeout=NULL, it waits indefinitely. Otherwise, it waits until *timeout
becomes non-zero. It is particularly suitable to set *timeout to 1 using a becomes non-zero. It is particularly suitable to set *timeout to 1 using a
timer with [timer_timeout] as callback. See <gint/timer.h>. */ timer with [timer_timeout] as callback. See <gint/timer.h>. */
key_event_t waitevent(volatile int *timeout); key_event_t waitevent(volatile int *timeout);
/* getkey() - wait for a pressed key /* clearevents(): Read all events waiting in the queue */
void clearevents(void);
//---
// Key state functions
//---
/* keydown(): Current key state
This function returns zero if the specified key is currently up (according
to the last events that have been processed) and non-zero if it is down. */
int keydown(int key);
/* keydown_all(): Check a set of keys for simultaneous input
Returns non-zero if all provided keys are down. The list should end with a 0
as terminator. */
int keydown_all(int key1, ...);
/* keydown_any(): Check a set of keys for any input
Returns nonzero if any one of the specified keys is currently pressed. The
sequence should be terminated by a 0 integer. */
int keydown_any(int key1, ...);
//---
// High-level functions
//---
/* getkey(): Wait for a key press
This function mimics the behavior of the fxlib GetKey(). It returns a This function mimics the behavior of the fxlib GetKey(). It returns a
key_event_t object where [mod=1], and where [shift] and [alpha] indicate key_event_t object where [mod=1], and where [shift] and [alpha] indicate
@ -133,16 +201,18 @@ enum {
GETKEY_REP_ARROWS = 0x10, GETKEY_REP_ARROWS = 0x10,
GETKEY_REP_ALL = 0x20, GETKEY_REP_ALL = 0x20,
/* No modifiers */
GETKEY_NONE = 0x00,
/* Default settings of getkey() */ /* Default settings of getkey() */
GETKEY_DEFAULT = 0x1f, GETKEY_DEFAULT = 0x1f,
}; };
/* getkey_opt() - enhanced getkey() /* getkey_opt(): Enhanced getkey()
This function enhances getkey() with more general features. An This function enhances getkey() with more general features. An
or-combination of option flags (see above) must be supplied as first or-combination of option flags (see above) must be supplied as first
argument; 0 stands for no option. getkey_opt() returns the same kind of argument; GETKEY_NONE stands for no option. getkey_opt() returns the same
values as getkey(). kind of events as getkey().
getkey_opt() supports a generic timeout function in the form of a volatile getkey_opt() supports a generic timeout function in the form of a volatile
pointer [timeout]. If it's NULL, getkey_opt() waits indefinitely. Otherwise, pointer [timeout]. If it's NULL, getkey_opt() waits indefinitely. Otherwise,
@ -155,7 +225,7 @@ enum {
Returns a key event of type KEYEV_DOWN or KEYEV_HOLD with [mod=1]. */ Returns a key event of type KEYEV_DOWN or KEYEV_HOLD with [mod=1]. */
key_event_t getkey_opt(int options, volatile int *timeout); key_event_t getkey_opt(int options, volatile int *timeout);
/* getkey_repeat() - set repeat delays for getkey() /* getkey_repeat(): Set repeat delays for getkey()
This function updates the repeat delays of getkey() and getkey_opt(). The This function updates the repeat delays of getkey() and getkey_opt(). The
unit of the argument is in milliseconds, but the granularity of the delay is unit of the argument is in milliseconds, but the granularity of the delay is

View file

@ -30,15 +30,11 @@ key_event_t getkey_opt(int opt, volatile int *timeout)
static int rep_key = 0; static int rep_key = 0;
/* Number of repeats already emitted */ /* Number of repeats already emitted */
static int rep_count = 0; static int rep_count = 0;
/* Scan intervals elapsed since last repeat */ /* Keyboard time when the key was pressed */
static int rep_time = 0; static int rep_time = 0;
while(1) switch((ev = waitevent(timeout)).type) while(1) switch((ev = pollevent()).type)
{ {
/* Timeout has expired, return KEYEV_NONE */
case KEYEV_NONE:
return ev;
/* Key press: handle modifiers or return an event */ /* Key press: handle modifiers or return an event */
case KEYEV_DOWN: case KEYEV_DOWN:
key = ev.key; key = ev.key;
@ -75,45 +71,52 @@ key_event_t getkey_opt(int opt, volatile int *timeout)
} }
/* Return current event */ /* Return current event */
rep_key = key; rep_key = key;
rep_count = 0; rep_count = 0;
rep_time = 0; rep_time = ev.time;
ev.mod = 1; ev.mod = 1;
ev.shift = shift; ev.shift = shift;
ev.alpha = alpha; ev.alpha = alpha;
return ev; return ev;
/* Return new events when a key is held (maybe) */ /* If nothing happens, stop or wait for a repeat to occur */
case KEYEV_HOLD: case KEYEV_NONE:
if(ev.key != rep_key) break; /* Timeout has expired, return KEYEV_NONE */
if(timeout && *timeout) return ev;
/* Check that this key can be repeated */ /* Check that the last pressed key can be repeated */
int arrow = (rep_key == KEY_LEFT || rep_key == KEY_RIGHT || int arrow = (rep_key == KEY_LEFT || rep_key == KEY_RIGHT ||
rep_key == KEY_UP || rep_key == KEY_DOWN); rep_key == KEY_UP || rep_key == KEY_DOWN);
if(!(opt & GETKEY_REP_ALL) && !(opt & GETKEY_REP_ARROWS && if(!rep_key || !(
arrow)) break; (opt & GETKEY_REP_ALL) ||
(opt & GETKEY_REP_ARROWS && arrow)
)) break;
/* If the key is key pressed long enough, create a new event */ /* If the key is key pressed long enough, create a new event */
int target = (rep_count) ? rep_next : rep_first; int duration = (int16_t)(ev.time - rep_time);
if(++rep_time < target) break;
rep_time -= target; int target = (rep_count) ? rep_next : rep_first;
if(duration < target) break;
rep_time += target;
rep_count++; rep_count++;
ev.mod = 1; ev.mod = 1;
ev.shift = shift; ev.shift = shift;
ev.alpha = alpha; ev.alpha = alpha;
ev.type = KEYEV_HOLD;
ev.key = rep_key;
return ev; return ev;
/* Reset repeating information if the repeated key is released */ /* Reset repeating information if the repeated key is released */
case KEYEV_UP: case KEYEV_UP:
if(ev.key != rep_key) break; if(ev.key != rep_key) break;
rep_key = 0; rep_key = 0;
rep_count = 0; rep_count = 0;
rep_time = 0; rep_time = 0;
break; break;
} }
} }

View file

@ -8,10 +8,13 @@
#include <gint/clock.h> #include <gint/clock.h>
#include <gint/keyboard.h> #include <gint/keyboard.h>
#include <gint/drivers/iokbd.h>
#include <gint/defs/attributes.h> #include <gint/defs/attributes.h>
#include <gint/defs/types.h>
#include <gint/drivers/iokbd.h>
#include <gint/hardware.h> #include <gint/hardware.h>
#include <stdarg.h>
//--- //---
// Keyboard buffer // Keyboard buffer
//--- //---
@ -20,34 +23,39 @@
internal state with the hardware state and generates events accordingly. internal state with the hardware state and generates events accordingly.
Events can be seen as a delta-encoding of the keyboard state over time. Events can be seen as a delta-encoding of the keyboard state over time.
The user which sums up these events to maintain a full keyboard state must To ensure that adding pending events to the last-read state always gives the
get a correct result. As a consequence, if an event cannot be generated internal driger state, this array is not updated if the generation of an
(whatever the reason), the driver's internal copy of the keyboard state must event fails. (Most likely the even will be regenerated at the next scan.) */
not be updated (probably the event will be re-emitted at the next scan). */
GDATA static volatile uint8_t state[12] = { 0 }; GDATA 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. */
GDATA static uint8_t current[12] = { 0 };
/* A driver event, which is a change in a full row instead of a single key. */ /* A driver event, which is a change in a full row instead of a single key. */
typedef struct typedef struct
{ {
uint time :12; /* Locally unique time identifier */ uint16_t time; /* Locally unique time identifier */
uint row :4; /* Row number */ uint8_t row; /* Row number */
uint old :8; /* Key status for the old row */ uint8_t changed; /* Keys that changed state */
uint new :8; /* Key status for the new row */ uint8_t state; /* Key state for the new row */
} driver_event_t; } driver_event_t;
/* The keyboard event buffer. This is a circular list defined by [buffer_start] /* The keyboard event buffer. This is a circular list defined by [buffer_start]
and [buffer_end]. To avoid an ambiguity when start = end, the buffer is not and [buffer_end]. To avoid an ambiguity when start == end, there must always
allowed to be full (at least one free cell must be remaining). */ be at least one free entry. */
GBSS static driver_event_t buffer[KEYBOARD_QUEUE_SIZE]; GBSS static driver_event_t buffer[KEYBOARD_QUEUE_SIZE];
/* Buffer bounds */ /* Buffer bounds */
GDATA static int buffer_start = 0; GDATA static int buffer_start = 0;
GDATA static int buffer_end = 0; GDATA static int buffer_end = 0;
/* Current time, in keyboard-scanning ticks */ /* Current time, in keyboard-scanning ticks */
GDATA static int time = 0; GDATA static uint time = 0;
/* buffer_push() - add an event in the keyboard buffer /* buffer_push(): Add an event in the keyboard buffer
Returns non-zero if the event cannot be pushed. */ Returns non-zero if the event cannot be pushed. */
static int buffer_push(driver_event_t ev) static int buffer_push(driver_event_t ev)
{ {
@ -59,7 +67,7 @@ static int buffer_push(driver_event_t ev)
return 0; return 0;
} }
/* buffer_poll() - generate key events from the buffer /* buffer_poll(): Generate key events from the buffer
Sets [ev] and returns zero on success, otherwise non-zero. */ Sets [ev] and returns zero on success, otherwise non-zero. */
static int buffer_poll(driver_event_t *ev) static int buffer_poll(driver_event_t *ev)
{ {
@ -70,7 +78,7 @@ static int buffer_poll(driver_event_t *ev)
return 0; return 0;
} }
/* keysc_frame() - generate a round of events for the current frame */ /* keysc_frame(): Generate driver events from KEYSC state */
static void keysc_frame(void) static void keysc_frame(void)
{ {
GALIGNED(2) uint8_t scan[12] = { 0 }; GALIGNED(2) uint8_t scan[12] = { 0 };
@ -90,13 +98,13 @@ static void keysc_frame(void)
/* Compare new data with the internal state. */ /* Compare new data with the internal state. */
int old = state[row]; int old = state[row];
int new = scan[row]; int new = scan[row];
if(old == new && !new) continue; if(old == new) continue;
driver_event_t ev = { driver_event_t ev = {
.time = time, .time = time,
.row = row, .row = row,
.old = old, .changed = old ^ new,
.new = new, .state = new,
}; };
/* Update internal status if the event could be pushed */ /* Update internal status if the event could be pushed */
@ -113,41 +121,59 @@ key_event_t pollevent(void)
/* Number of pending events in the previous buffer */ /* Number of pending events in the previous buffer */
static int events_pending = 0; static int events_pending = 0;
/* Use pending events first, then poll the driver buffer */ /* Use pending events first, if they exist */
if(events_pending > 0) return events[--events_pending]; 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; driver_event_t ev;
if(buffer_poll(&ev)) return (key_event_t){ .type = KEYEV_NONE }; 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*/ /* Generate new key events and return the first of them*/
int old = ev.old << 1; int changed = ev.changed;
int new = ev.new; int state = ev.state;
for(int code = ((ev.row ^ 1) << 4) | 0x7; code & 0x7; code--) for(int code = ((ev.row ^ 1) << 4) | 0x7; code & 0x7; code--)
{ {
int kind = (old & 2) | (new & 1); if(changed & 1)
old >>= 1; {
new >>= 1; key_event_t keyev = {
.time = ev.time,
.type = 2 - (state & 1),
.key = code,
};
events[events_pending++] = keyev;
}
if(!kind) continue; changed >>= 1;
state >>= 1;
key_event_t keyev = {
.time = ev.time,
.type = kind,
.key = code,
};
events[events_pending++] = keyev;
} }
return events[--events_pending]; /* Call recursively to avoid duplicating the event emission code */
return pollevent();
} }
/* waitevent() - wait for the next keyboard event */ /* waitevent() - wait for the next keyboard event */
key_event_t waitevent(volatile int *timeout) key_event_t waitevent(volatile int *timeout)
{ {
key_event_t none = { .type = KEYEV_NONE };
while(1) while(1)
{ {
key_event_t ev = pollevent(); key_event_t ev = pollevent();
@ -157,7 +183,65 @@ key_event_t waitevent(volatile int *timeout)
sleep(); sleep();
} }
return none; key_event_t ev = { .type = KEYEV_NONE, .time = time };
return ev;
}
/* clearevents(): Read all events waiting in the queue */
void clearevents(void)
{
while(pollevent().type != KEYEV_NONE);
}
//---
// Immediate key access
//---
/* keydown(): Current key state */
int keydown(int key)
{
int row = (key >> 4) ^ 1;
int col = 0x80 >> (key & 0x7);
return (current[row] & col) != 0;
}
/* keydown_all(): Check a set of keys for simultaneous input
Returns non-zero if all provided keys are down. The list should end with an
integer 0 as terminator. */
int keydown_all(int key, ...)
{
va_list args;
va_start(args, key);
int st = 1;
while(key && st)
{
st = keydown(key);
key = va_arg(args, int);
}
va_end(args);
return st;
}
/* keydown_any(): Check a set of keys for any input
Returns nonzero if any one of the specified keys is currently pressed. THe
sequence should be terminated by a 0 integer. */
int keydown_any(int key, ...)
{
va_list args;
va_start(args, key);
int st = 0;
while(key && !st)
{
st = keydown(key);
key = va_arg(args, int);
}
va_end(args);
return st;
} }
//--- //---