diff --git a/TODO b/TODO index 2694055..11b6117 100644 --- a/TODO +++ b/TODO @@ -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() diff --git a/include/gint/keyboard.h b/include/gint/keyboard.h index 56a72f7..5a1e34e 100644 --- a/include/gint/keyboard.h +++ b/include/gint/keyboard.h @@ -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 #include -/* 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 . */ 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 diff --git a/src/keysc/getkey.c b/src/keysc/getkey.c index 5aae8d7..0997526 100644 --- a/src/keysc/getkey.c +++ b/src/keysc/getkey.c @@ -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; } } diff --git a/src/keysc/keysc.c b/src/keysc/keysc.c index fc159aa..9d842ee 100644 --- a/src/keysc/keysc.c +++ b/src/keysc/keysc.c @@ -8,10 +8,13 @@ #include #include -#include #include +#include +#include #include +#include + //--- // 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; } //---