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
* hardware: fill in the HWMEM_FITTLB flag
* keyboard: think of extended functions
* keyboard: implement keydown() in an event-compliant way
* cpg: spread spectrum on fxcg50
* bopti: blending modes for monochrome bitmaps (use topti assembler)
* display: use more of topti's assembler in drect()

View file

@ -5,43 +5,84 @@
#ifndef 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
events*, the most primitive type of event exposed to the user. A key event
basically says that a key has been pressed, held, or released at some point
in time. They are suited for real-time applications like games.
events*. A key event basically says that a key has been pressed or released
at some point in time. Key events are a faithful description of everything
that happens on the keyboard.
- pollevent() fetches the next queued keyboard event, and returns an event
of type KEYEV_NONE if none is available.
- waitevent() fetches the next queued keyboard event and waits if none is
available. The timeout can be configured.
These key events are stored in the *event queue*, from where they can be
retrieved:
GUI programs 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 backlight and return-to-menu.
* pollevent() fetches the next keyboard event waiting in the queue. Events
will always be returned in chronological order. If there is no keyboard
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.
- 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/keycodes.h>
/* key_event_t - any 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.
/* key_event_t: Low-level or high-level keyboard event
These events are detected and reported each time the keyboard is scanned,
which is 128 Hz by default, so you'll get 128 repeat events by second if a
key is kept pressed. We could filter the events to emit one only every
second, for example, but it's difficult to do it for all keys at the same
time. Thus the control of repeat delays is left to getkey().
This structure represents an event that occurs on the keyboard. It is first
produced by the keyboard scanner with limited information, then possibly
enriched by getkey(). Events are produced each time the keyboard is scanned,
which is 128 Hz by default. Hence, a key press and release occuring in less
than 8 ms might not be detected.
When [mod = 1], attributes [shift] and [alpha] indicate whether the key has
been modified. Only key press events returned by getkey() have [mod = 1].
Note that you can't have, e.g. [key=KEY_SHIFT] and [mod=1] at the same time.
getkey() returns enriched events with [mod=1], in whic ase [shift] and
[alpha] indicate whether the key has been modified. Only key press events
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
a time counter that increases at each keyboard scan and *wraps around every
@ -64,7 +105,7 @@ typedef struct
} 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
{
KEYEV_NONE = 0, /* No event available (poll() only) */
@ -87,23 +128,50 @@ enum
#endif
//---
// Keyboard functions
// Event-level functions
//---
/* pollevent() - poll the next keyboard event
This function returns the next yet-unpolled event from the keyboard buffer.
If no event is available, it returns a dummy event with type=KEYEV_NONE.
This function always returns events with mod=0. */
/* pollevent(): Poll the next keyboard event
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
and time set to the current driver time. This function always returns events
with mod=0. */
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
timeout=NULL, it waits indefinitely. Otherwise, it waits until *timeout
becomes non-zero. It is particularly suitable to set *timeout to 1 using a
timer with [timer_timeout] as callback. See <gint/timer.h>. */
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
key_event_t object where [mod=1], and where [shift] and [alpha] indicate
@ -133,16 +201,18 @@ enum {
GETKEY_REP_ARROWS = 0x10,
GETKEY_REP_ALL = 0x20,
/* No modifiers */
GETKEY_NONE = 0x00,
/* Default settings of getkey() */
GETKEY_DEFAULT = 0x1f,
};
/* getkey_opt() - enhanced getkey()
/* getkey_opt(): Enhanced getkey()
This function enhances getkey() with more general features. An
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
values as getkey().
argument; GETKEY_NONE stands for no option. getkey_opt() returns the same
kind of events as getkey().
getkey_opt() supports a generic timeout function in the form of a volatile
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]. */
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
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;
/* Number of repeats already emitted */
static int rep_count = 0;
/* Scan intervals elapsed since last repeat */
/* Keyboard time when the key was pressed */
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 */
case KEYEV_DOWN:
key = ev.key;
@ -75,45 +71,52 @@ key_event_t getkey_opt(int opt, volatile int *timeout)
}
/* Return current event */
rep_key = key;
rep_key = key;
rep_count = 0;
rep_time = 0;
rep_time = ev.time;
ev.mod = 1;
ev.shift = shift;
ev.alpha = alpha;
return ev;
/* Return new events when a key is held (maybe) */
case KEYEV_HOLD:
if(ev.key != rep_key) break;
/* If nothing happens, stop or wait for a repeat to occur */
case KEYEV_NONE:
/* 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 ||
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 &&
arrow)) break;
if(!rep_key || !(
(opt & GETKEY_REP_ALL) ||
(opt & GETKEY_REP_ARROWS && arrow)
)) break;
/* If the key is key pressed long enough, create a new event */
int target = (rep_count) ? rep_next : rep_first;
if(++rep_time < target) break;
int duration = (int16_t)(ev.time - rep_time);
rep_time -= target;
int target = (rep_count) ? rep_next : rep_first;
if(duration < target) break;
rep_time += target;
rep_count++;
ev.mod = 1;
ev.shift = shift;
ev.alpha = alpha;
ev.type = KEYEV_HOLD;
ev.key = rep_key;
return ev;
/* Reset repeating information if the repeated key is released */
case KEYEV_UP:
if(ev.key != rep_key) break;
rep_key = 0;
rep_key = 0;
rep_count = 0;
rep_time = 0;
rep_time = 0;
break;
}
}

View file

@ -8,10 +8,13 @@
#include <gint/clock.h>
#include <gint/keyboard.h>
#include <gint/drivers/iokbd.h>
#include <gint/defs/attributes.h>
#include <gint/defs/types.h>
#include <gint/drivers/iokbd.h>
#include <gint/hardware.h>
#include <stdarg.h>
//---
// Keyboard buffer
//---
@ -20,34 +23,39 @@
internal state with the hardware state and generates events accordingly.
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
get a correct result. As a consequence, if an event cannot be generated
(whatever the reason), the driver's internal copy of the keyboard state must
not be updated (probably the event will be re-emitted at the next scan). */
To ensure that adding pending events to the last-read state always gives the
internal driger state, this array is not updated if the generation of an
event fails. (Most likely the even will be regenerated at the next scan.) */
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. */
typedef struct
{
uint time :12; /* Locally unique time identifier */
uint row :4; /* Row number */
uint old :8; /* Key status for the old row */
uint new :8; /* Key status for the new row */
uint16_t time; /* Locally unique time identifier */
uint8_t row; /* Row number */
uint8_t changed; /* Keys that changed state */
uint8_t state; /* Key state for the new row */
} 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, the buffer is not
allowed to be full (at least one free cell must be remaining). */
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 */
GDATA static int buffer_start = 0;
GDATA static int buffer_end = 0;
/* 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. */
static int buffer_push(driver_event_t ev)
{
@ -59,7 +67,7 @@ static int buffer_push(driver_event_t ev)
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. */
static int buffer_poll(driver_event_t *ev)
{
@ -70,7 +78,7 @@ static int buffer_poll(driver_event_t *ev)
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)
{
GALIGNED(2) uint8_t scan[12] = { 0 };
@ -90,13 +98,13 @@ static void keysc_frame(void)
/* Compare new data with the internal state. */
int old = state[row];
int new = scan[row];
if(old == new && !new) continue;
if(old == new) continue;
driver_event_t ev = {
.time = time,
.row = row,
.old = old,
.new = new,
.time = time,
.row = row,
.changed = old ^ new,
.state = new,
};
/* 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 */
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;
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*/
int old = ev.old << 1;
int new = ev.new;
int changed = ev.changed;
int state = ev.state;
for(int code = ((ev.row ^ 1) << 4) | 0x7; code & 0x7; code--)
{
int kind = (old & 2) | (new & 1);
old >>= 1;
new >>= 1;
if(changed & 1)
{
key_event_t keyev = {
.time = ev.time,
.type = 2 - (state & 1),
.key = code,
};
events[events_pending++] = keyev;
}
if(!kind) continue;
key_event_t keyev = {
.time = ev.time,
.type = kind,
.key = code,
};
events[events_pending++] = keyev;
changed >>= 1;
state >>= 1;
}
return events[--events_pending];
/* Call recursively to avoid duplicating the event emission code */
return pollevent();
}
/* waitevent() - wait for the next keyboard event */
key_event_t waitevent(volatile int *timeout)
{
key_event_t none = { .type = KEYEV_NONE };
while(1)
{
key_event_t ev = pollevent();
@ -157,7 +183,65 @@ key_event_t waitevent(volatile int *timeout)
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;
}
//---