From: kosmirror <kos...@us...> - 2025-05-16 23:49:15
|
This is an automated email from the git hooks/post-receive script. It was generated because a ref change was pushed to the repository containing the project "A pseudo Operating System for the Dreamcast.". The branch, master has been updated via ea4b4439718347a99c4e11bd2adb76cd11904bfd (commit) via 935e15970613fcef798a8209508d0c6cdd07d759 (commit) via 5cd80d7cc4cc755de074451e416136d954bddf74 (commit) via 21320c8b608eb387a205c64402c0eb3079b8daf0 (commit) via c3a6c177c54eb861cf0d41b5f5e8c91e3095718e (commit) via 523a7282d0a861ab75d6a92742ba9865fb1738d2 (commit) from 941c33acdc8d57e139942a12c45408db77607cec (commit) Those revisions listed above that are new to this repository have not appeared on any other notification email; so we list those revisions in full, below. - Log ----------------------------------------------------------------- commit ea4b4439718347a99c4e11bd2adb76cd11904bfd Author: QuzarDC <qu...@co...> Date: Sun May 4 01:06:59 2025 -0400 Remove old duplicated keymaps. The `kbd_enqueue` function had its own copies of the US region keymaps that there was no reason for. Now the global key queue will instead be set by the standard US region keymap to maintain the same general behavior. The differences are that key 0x28 is now 10 rather than 13, and that shift functionality can be triggered by caps lock. commit 935e15970613fcef798a8209508d0c6cdd07d759 Author: QuzarDC <qu...@co...> Date: Tue Apr 15 22:53:35 2025 -0400 Demonstrate event handler in keyboard test. commit 5cd80d7cc4cc755de074451e416136d954bddf74 Author: QuzarDC <qu...@co...> Date: Tue Apr 15 18:05:48 2025 -0400 Add event-driven callback mechanism. Allows registration of a callback to be invoked whenever a key changes states (similar to how Qt, GTk, and high-level widgeting frameworks process key events). commit 21320c8b608eb387a205c64402c0eb3079b8daf0 Author: QuzarDC <qu...@co...> Date: Tue Apr 15 14:04:59 2025 -0400 Enhance key state tracking. This change shifts key state tracking from 3 states (not pressed, pressed, was pressed) to the full 4 possible combinations of is/was up/down. This added no storage overhead, and significantly simplifies the per-poll update that decays 'pressed' into 'was pressed' and 'was pressed' into 'not pressed' into a bit shift. Compatibility defines were left in place to help the process of updating, and compatibility should be maintained. To note, this still requires updates to the old SDL port as it was not using the defines provided for determining key state and was instead presuming the values mapped to SDL defines. commit c3a6c177c54eb861cf0d41b5f5e8c91e3095718e Author: QuzarDC <qu...@co...> Date: Mon Apr 14 23:15:00 2025 -0400 Rename MAX_PRESSED_KEYS define. So that it matches our general naming scheme with kbd prefix. commit 523a7282d0a861ab75d6a92742ba9865fb1738d2 Author: QuzarDC <qu...@co...> Date: Mon Apr 14 23:13:03 2025 -0400 Clean up types in the keyboard's private state. ----------------------------------------------------------------------- Summary of changes: examples/dreamcast/libdream/keyboard/keyboard.c | 95 +++++++-- kernel/arch/dreamcast/hardware/maple/keyboard.c | 148 +++++++------- kernel/arch/dreamcast/include/dc/maple/keyboard.h | 230 ++++++++++++++++++---- 3 files changed, 345 insertions(+), 128 deletions(-) diff --git a/examples/dreamcast/libdream/keyboard/keyboard.c b/examples/dreamcast/libdream/keyboard/keyboard.c index 2ff10b44..ca49c064 100644 --- a/examples/dreamcast/libdream/keyboard/keyboard.c +++ b/examples/dreamcast/libdream/keyboard/keyboard.c @@ -4,11 +4,24 @@ /* Display constants */ #define SCREEN_WIDTH 640 #define SCREEN_HEIGHT 480 - -void kb_test(void) { +#define MARGIN_HORIZONTAL 20 +#define MARGIN_VERTICAL 20 +#define PATTERN_OFFSET 64 + +/* Structure holding cursor state */ +typedef struct { + int x; + int y; + mutex_t m; +} cursor_t; + +/* Function for performing textual input style polling logic by + popping keys off of the pending keyboard queue to process. +*/ +static void kb_test(cursor_t *cursor) { maple_device_t *cont, *kbd; cont_state_t *state; - int k, x = 20, y = 20 + BFONT_HEIGHT; + int k; printf("Now doing keyboard test\n"); @@ -29,14 +42,6 @@ void kb_test(void) { if(!kbd) continue; - thd_sleep(10); - - /* Check for keyboard input */ - /* if (kbd_poll(mkb)) { - printf("Error checking keyboard status\n"); - return; - } */ - /* Keep popping keys while there are more enqueued. */ while((k = kbd_queue_pop(kbd, true)) != KBD_QUEUE_END) { /* Quit if ESC key is pressed. */ @@ -50,35 +55,83 @@ void kb_test(void) { printf("Special key %04x\n", k); /* Handle every key that isn't the RETURN key. */ - if(k != '\r') { + else if(k != '\r') { + mutex_lock(&cursor->m); /* Draw the key we just pressed. */ - bfont_draw(vram_s + y * SCREEN_WIDTH + x, SCREEN_WIDTH, 0, k); + bfont_draw(vram_s + cursor->y * SCREEN_WIDTH + cursor->x, + SCREEN_WIDTH, 0, k); /* Advance the cursor horizontally. */ - x += BFONT_THIN_WIDTH; - } - else { - x = 20; - y += BFONT_HEIGHT; + cursor->x += BFONT_THIN_WIDTH; + + /* Implement wrapping if we're at the end of the line. */ + if(cursor->x >= (SCREEN_WIDTH - MARGIN_HORIZONTAL)) { + cursor->x = MARGIN_HORIZONTAL; + cursor->y += BFONT_HEIGHT; + } + + mutex_unlock(&cursor->m); } } + } +} - thd_sleep(10); +/* Asynchronous callback function which is invoked by the keyboard + driver any time a key's state changes. This is what you want to + use when using the keyboard as a traditional controller-like input- + device. +*/ +static void on_key_event(maple_device_t *dev, kbd_key_t key, + key_state_t state, kbd_mods_t mods, + kbd_leds_t leds, void *user_data) { + /* Retrieve keyboard state from maple device. */ + kbd_state_t *kbd_state = kbd_get_state(dev); + /* Fetch cursor from generic userdata pointer. */ + cursor_t *cursor = user_data; + + /* Print keyboard address + key ID and state change type. */ + printf("[%c%u] %c: %s\n", + 'A' + dev->port, dev->unit, + kbd_key_to_ascii(key, 1, mods, leds), + state.value == KEY_STATE_CHANGED_DOWN? "PRESSED" : "RELEASED"); + + /* Check whether the RETURN key was pressed (but not held): */ + if(key == KBD_KEY_ENTER && state.value == KEY_STATE_CHANGED_DOWN) { + if(mutex_trylock(&cursor->m)) return; + /* Advance cursor to the next line. */ + cursor->x = MARGIN_HORIZONTAL; + cursor->y += BFONT_HEIGHT; + mutex_unlock(&cursor->m); } } int main(int argc, char **argv) { int x, y; + /* Initialize our cursor in the top-left. */ + cursor_t cursor = { + .x = MARGIN_HORIZONTAL, + .y = MARGIN_VERTICAL + BFONT_HEIGHT, + .m = ERRORCHECK_MUTEX_INITIALIZER + }; + for(y = 0; y < SCREEN_HEIGHT; y++) for(x = 0; x < SCREEN_WIDTH; x++) { - int c = (x ^ y) & 255; + int c = ((x % PATTERN_OFFSET) ^ + (y % PATTERN_OFFSET)) & 0xff; vram_s[y * SCREEN_WIDTH + x] = ((c >> 3) << 12) | ((c >> 2) << 5) | ((c >> 3) << 0); } - kb_test(); + /* Install a custom keyboard event handler which will listen for + key state changes, passing it our cursor. This drives the keyboard + as a controller-like input device. + */ + kbd_set_event_handler(on_key_event, &cursor); + + /* Run our main logic which drives the keyboard as a text-input device. */ + kb_test(&cursor); return EXIT_SUCCESS; } diff --git a/kernel/arch/dreamcast/hardware/maple/keyboard.c b/kernel/arch/dreamcast/hardware/maple/keyboard.c index 9fd1b22c..7241bc71 100644 --- a/kernel/arch/dreamcast/hardware/maple/keyboard.c +++ b/kernel/arch/dreamcast/hardware/maple/keyboard.c @@ -41,12 +41,14 @@ typedef struct kbd_state_private { You should not access this variable directly. Please use the appropriate function to access it. */ kbd_q_key_t key_queue[KBD_QUEUE_SIZE]; - int queue_tail; /**< \brief Key queue tail. */ - int queue_head; /**< \brief Key queue head. */ - volatile int queue_len; /**< \brief Current length of queue. */ - - kbd_key_t kbd_repeat_key; /**< \brief Key that is repeating. */ - uint64_t kbd_repeat_timer; /**< \brief Time that the next repeat will trigger. */ + size_t queue_tail; /**< \brief Key queue tail. */ + size_t queue_head; /**< \brief Key queue head. */ + volatile size_t queue_len; /**< \brief Current length of queue. */ + + struct { + kbd_key_t key; /**< \brief Key that is repeating. */ + uint64_t timeout; /**< \brief Time that the next repeat will trigger. */ + } repeater; } kbd_state_private_t; /* These are global timings for key repeat. It would be possible to put @@ -66,6 +68,23 @@ void kbd_set_repeat_timing(uint16_t start, uint16_t interval) { repeat_timing.interval = interval; } +static struct { + kbd_event_handler_t cb; + void *ud; +} event_handler = { + NULL, NULL +}; + +void kbd_set_event_handler(kbd_event_handler_t callback, void *user_data) { + event_handler.cb = callback; + event_handler.ud = user_data; +} + +void kbd_get_event_handler(kbd_event_handler_t *callback, void **user_data) { + *callback = event_handler.cb; + *user_data = event_handler.ud; +} + /* Keyboard keymap. This structure represents a mapping from raw key values to ASCII values, if @@ -409,6 +428,16 @@ static const kbd_keymap_internal_t keymaps[] = { } }; +char kbd_key_to_ascii(kbd_key_t key, kbd_region_t region, kbd_mods_t mods, kbd_leds_t leds) { + + if(mods.ralt || (mods.lctrl && mods.lalt)) + return keymaps[region - 1].alt[key]; + else if((mods.raw & KBD_MOD_SHIFT) || leds.caps_lock) + return keymaps[region - 1].shifted[key]; + else + return keymaps[region - 1].base[key]; +} + /* The keyboard queue (global for now) */ static volatile int kbd_queue_active = 1; static volatile int kbd_queue_tail = 0, kbd_queue_head = 0; @@ -432,28 +461,6 @@ void kbd_set_queue(int active) { NOTE: We are only calling this within an IRQ context, so operations on kbd_state::queue_size are essentially atomic. */ static int kbd_enqueue(kbd_state_private_t *state, kbd_key_t keycode) { - static const char keymap_noshift[] = { - /*0*/ 0, 0, 0, 0, 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', - 'j', 'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r', 's', 't', - 'u', 'v', 'w', 'x', 'y', 'z', - /*1e*/ '1', '2', '3', '4', '5', '6', '7', '8', '9', '0', - /*28*/ 13, 27, 8, 9, 32, '-', '=', '[', ']', '\\', 0, ';', '\'', - /*35*/ '`', ',', '.', '/', 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - /*46*/ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - /*53*/ 0, '/', '*', '-', '+', 13, '1', '2', '3', '4', '5', '6', - /*5f*/ '7', '8', '9', '0', '.', 0 - }; - static const char keymap_shift[] = { - /*0*/ 0, 0, 0, 0, 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', - 'J', 'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R', 'S', 'T', - 'U', 'V', 'W', 'X', 'Y', 'Z', - /*1e*/ '!', '@', '#', '$', '%', '^', '&', '*', '(', ')', - /*28*/ 13, 27, 8, 9, 32, '_', '+', '{', '}', '|', 0, ':', '"', - /*35*/ '~', '<', '>', '?', 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - /*46*/ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - /*53*/ 0, '/', '*', '-', '+', 13, '1', '2', '3', '4', '5', '6', - /*5f*/ '7', '8', '9', '0', '.', 0 - }; uint16_t ascii = 0; /* Don't bother with bad keycodes. */ @@ -473,13 +480,9 @@ static int kbd_enqueue(kbd_state_private_t *state, kbd_key_t keycode) { if(!kbd_queue_active) return 0; - /* Figure out its key queue value */ - if(keycode < sizeof(keymap_noshift)) { - if(state->base.cond.modifiers.raw & KBD_MOD_SHIFT) - ascii = keymap_shift[keycode]; - else - ascii = keymap_noshift[keycode]; - } + /* Figure out its key queue value. */ + ascii = (uint16_t)kbd_key_to_ascii(keycode, KBD_REGION_US, + state->base.cond.modifiers, state->base.cond.leds); if(ascii == 0) ascii = ((uint16_t)keycode) << 8; @@ -522,16 +525,6 @@ kbd_state_t *kbd_get_state(maple_device_t *device) { return (kbd_state_t *)device->status; } -char kbd_key_to_ascii(kbd_key_t key, kbd_region_t region, kbd_mods_t mods, kbd_leds_t leds) { - - if(mods.ralt || (mods.lctrl && mods.lalt)) - return keymaps[region - 1].alt[key]; - else if((mods.raw & KBD_MOD_SHIFT) || leds.caps_lock) - return keymaps[region - 1].shifted[key]; - else - return keymaps[region - 1].base[key]; -} - /* Take a key off of a specific key queue. */ int kbd_queue_pop(maple_device_t *dev, bool xlat) { kbd_state_private_t *state = (kbd_state_private_t *)dev->status; @@ -567,52 +560,58 @@ static void kbd_check_poll(maple_frame_t *frm) { kbd_state_private_t *pstate = (kbd_state_private_t *)frm->dev->status; kbd_state_t *state = &pstate->base; kbd_cond_t *cond = (kbd_cond_t *)&state->cond; - int i; + size_t i; /* If the modifier keys have changed, end the key repeating. */ if(state->last_modifiers.raw != cond->modifiers.raw) { - pstate->kbd_repeat_key = KBD_KEY_NONE; - pstate->kbd_repeat_timer = 0; + pstate->repeater.key = KBD_KEY_NONE; + pstate->repeater.timeout = 0; } /* Update modifiers. */ state->last_modifiers = cond->modifiers; + /* Update all key states */ + for(i = 0; i < KBD_MAX_KEYS; ++i) { + state->key_states[i].raw = (state->key_states[i].raw << 1) & KEY_STATE_MASK; + } + /* Process all pressed keys */ - for(i = 0; i < MAX_PRESSED_KEYS; i++) { + for(i = 0; i < KBD_MAX_PRESSED_KEYS; i++) { /* Once we get to a 'none', the rest will be 'none' */ if(cond->keys[i] == KBD_KEY_NONE) { /* This could be used to indicate how many keys are pressed by setting it to ~i or i+1 or similar. This could be useful, but would make it a weird exception. */ /* If the first key in the key array is none, there are no non-modifer keys pressed at all. */ - if(i==0) state->matrix[KBD_KEY_NONE] = KEY_STATE_PRESSED; + if(!i) state->key_states[KBD_KEY_NONE].is_down = true; break; } /* Between None and A are error indicators. This would be a good place to do... something. If an error occurs the whole array will be error.*/ else if(cond->keys[i]>KBD_KEY_NONE && cond->keys[i]<KBD_KEY_A) { - state->matrix[cond->keys[i]] = KEY_STATE_PRESSED; + state->key_states[cond->keys[i]].is_down = true; break; } /* The rest of the keys are treated normally */ else { + /* Update key state */ + state->key_states[cond->keys[i]].is_down = true; + /* If the key hadn't been pressed. */ - if(state->matrix[cond->keys[i]] == KEY_STATE_NONE) { - state->matrix[cond->keys[i]] = KEY_STATE_PRESSED; + if(state->key_states[cond->keys[i]].value == KEY_STATE_CHANGED_DOWN) { kbd_enqueue(pstate, cond->keys[i]); - pstate->kbd_repeat_key = cond->keys[i]; + pstate->repeater.key = cond->keys[i]; if(repeat_timing.start) - pstate->kbd_repeat_timer = timer_ms_gettime64() + repeat_timing.start; + pstate->repeater.timeout = timer_ms_gettime64() + repeat_timing.start; } /* If the key was already being pressed and was our one allowed repeating key, then... */ - else if(state->matrix[cond->keys[i]] == KEY_STATE_WAS_PRESSED) { - state->matrix[cond->keys[i]] = KEY_STATE_PRESSED; - if(pstate->kbd_repeat_key == cond->keys[i]) { + else if(state->key_states[cond->keys[i]].value == KEY_STATE_HELD_DOWN) { + if(pstate->repeater.key == cond->keys[i]) { /* If repeat timing is enabled, bail if under interval */ if(repeat_timing.start) { uint64_t time = timer_ms_gettime64(); - if(time >= (pstate->kbd_repeat_timer)) - pstate->kbd_repeat_timer = time + repeat_timing.interval; + if(time >= (pstate->repeater.timeout)) + pstate->repeater.timeout = time + repeat_timing.interval; else continue; } @@ -620,23 +619,28 @@ static void kbd_check_poll(maple_frame_t *frm) { kbd_enqueue(pstate, cond->keys[i]); } } - else assert_msg(0, "invalid key matrix array detected"); + else assert_msg(0, "invalid key_states array detected"); } } - /* Now normalize the key matrix */ - /* If it was determined no keys are pressed, wipe the matrix */ - if(state->matrix[KBD_KEY_NONE] == KEY_STATE_PRESSED) - memset (state->matrix, KEY_STATE_NONE, KBD_MAX_KEYS); - /* Otherwise, walk through the whole matrix */ - else { - for(i = 0; i < KBD_MAX_KEYS; i++) { - if(state->matrix[i] == KEY_STATE_NONE) continue; + /* If we are using the event callback, check if any need called. */ + if(!event_handler.cb) return; + + for(i = KBD_KEY_A; i < KBD_MAX_KEYS; i++) { + switch(state->key_states[i].value) { + case KEY_STATE_CHANGED_DOWN: + case KEY_STATE_CHANGED_UP: + event_handler.cb(frm->dev, i, state->key_states[i], + cond->modifiers, cond->leds, event_handler.ud); + break; - else if(state->matrix[i] == KEY_STATE_WAS_PRESSED) state->matrix[i] = KEY_STATE_NONE; + case KEY_STATE_HELD_DOWN: + case KEY_STATE_HELD_UP: + break; - else if(state->matrix[i] == KEY_STATE_PRESSED) state->matrix[i] = KEY_STATE_WAS_PRESSED; - else assert_msg(0, "invalid key matrix array detected"); + default: + assert_msg(0, "Invalid key state found during callback check loop."); + break; } } } diff --git a/kernel/arch/dreamcast/include/dc/maple/keyboard.h b/kernel/arch/dreamcast/include/dc/maple/keyboard.h index a1da9a65..ea9862b7 100644 --- a/kernel/arch/dreamcast/include/dc/maple/keyboard.h +++ b/kernel/arch/dreamcast/include/dc/maple/keyboard.h @@ -313,27 +313,109 @@ typedef enum __packed kbd_key { char kbd_key_to_ascii(kbd_key_t key, kbd_region_t region, kbd_mods_t mods, kbd_leds_t leds); -/** \defgroup key_states Key States - \brief States each key can be in. +/** \defgroup key_state_grp Key States + \brief Types associated with key states + \ingroup kbd_status_grp + + Key states are represented by the kbd_state_t union type. The state may be + accessed by: + 1. Directly comparing key_state_t::value to a \ref key_state_value_t. + 2. Directly using a convenience bit field. + 3. Bitwise `AND` of key_state_t::raw with one of the + \ref key_state_flags. + + @{ +*/ + +/** \defgroup key_state_flags Flags + \brief Keyboard key state bit flags + + A key_state_t is a combination of two flags: + 1. `KEY_STATE_IS_DOWN`: whether the key is currently pressed + this frame. + 2. `KEY_STATE_WAS_DOWN`: whether the key was previously pressed + last frame. + + Between these two flags, you can know whether a key state transition + event has occurred (when the two flags have different values). - These are the different 'states' each key can be in. They are stored in - kbd_state_t->matrix, and manipulated/checked by kbd_check_poll. + \sa key_state_t::raw, key_state_value_t - none-> pressed or none - was pressed-> pressed or none - pressed-> was_pressed @{ */ -#define KEY_STATE_NONE 0 -#define KEY_STATE_WAS_PRESSED 1 -#define KEY_STATE_PRESSED 2 +#define KEY_STATE_IS_DOWN BIT(0) /**< \brief If key is currenty down */ +#define KEY_STATE_WAS_DOWN BIT(1) /**< \brief If key was previously down */ +/** \brief Mask of all key state flags */ +#define KEY_STATE_MASK (KEY_STATE_IS_DOWN | KEY_STATE_WAS_DOWN) +/** @} */ + +/** \brief Creates a packed key_state_t + + This macro is used to pack two frames worth of key state information + into a key_state_t, one bit per frame. +*/ +#define KEY_STATE_PACK(is_down, was_down) \ + (((!!(is_down))? KEY_STATE_IS_DOWN : 0) | \ + ((!!(was_down))? KEY_STATE_WAS_DOWN : 0)) + +/** \brief Valid values for key_state_t::value + + Enumerates each of the 4 different states a key can be in, + by combining two frames worth of key down information + into two bits. + + \note + Two of the values are for `HELD` states, meaning the same state has been + observed for both the current and the previous frame, while the other two + values are for `CHANGE` states, meaning the current frame has a different + state from the previous frame. + + \sa key_state_flags, key_state_t::value +*/ +typedef enum __packed key_state_value { + /** \brief Key has been in an up state for at least the last two frames */ + KEY_STATE_HELD_UP = KEY_STATE_PACK(false, false), + /** \brief Key transitioned from up to pressed this frame */ + KEY_STATE_CHANGED_DOWN = KEY_STATE_PACK(true, false), + /** \brief Key transitioned from down to released this frame */ + KEY_STATE_CHANGED_UP = KEY_STATE_PACK(false, true), + /** \brief Key has been held down for at least the last two frames */ + KEY_STATE_HELD_DOWN = KEY_STATE_PACK(true, true), +} key_state_value_t; + +/* Short-term compatibility helpers. */ +static const uint8_t KEY_STATE_NONE __depr("Please use KEY_STATE_HELD_UP.") = KEY_STATE_HELD_UP; +static const uint8_t KEY_STATE_WAS_PRESSED __depr("Please use KEY_STATE_CHANGED_UP.") = KEY_STATE_CHANGED_UP; +static const uint8_t KEY_STATE_PRESSED __depr("Please see key_state_value_t.") = KEY_STATE_HELD_DOWN; + +/** \brief Keyboard Key State + + Union containing the the previous and current frames' state information for + a keyboard key. + + \sa key_state_flags, key_state_value_t, kbd_state::key_states +*/ +typedef union key_state { + /** \brief Convenience Bitfields */ + struct { + bool is_down : 1; /**< \brief Whether down the current frame */ + bool was_down : 1; /**< \brief Whether down the previous frame */ + uint8_t : 6; + }; + key_state_value_t value; /**< \brief Enum for specific state */ + uint8_t raw; /**< \brief Packed 8-bit unsigned integer of bitflags */ +} key_state_t; + /** @} */ /** \brief Maximum number of keys the DC can read simultaneously. This is a hardware constant. The define prevents the magic number '6' from appearing. **/ -#define MAX_PRESSED_KEYS 6 +#define KBD_MAX_PRESSED_KEYS 6 + +/* Short-term compatibility helper. */ +static const int MAX_PRESSED_KEYS __depr("Please use KBD_MAX_PRESSED_KEYS.") = KBD_MAX_PRESSED_KEYS; ...<truncated>... hooks/post-receive -- A pseudo Operating System for the Dreamcast. |