From: kosmirror <kos...@us...> - 2025-07-25 03:42:04
|
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 1a93911dd0cd976c39cccb46f9be616417be14f2 (commit) from c1cb580581d86fa0ed5cef83706e136bd540617f (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 1a93911dd0cd976c39cccb46f9be616417be14f2 Author: Troy Davis <tr...@ya...> Date: Thu Jul 24 20:41:30 2025 -0700 Fix keyboard LED state handling and add keyrawtest example (#1095) * Fix keyboard LED state handling and add keyrawtest example This change improves keyboard driver behavior by correctly tracking Caps Lock, Num Lock, and Scroll Lock LED states. It also adds a keyrawtest example which captures raw keyboard events using kbd_queue_pop(dev, 0), logs printable and special key inputs, and displays LED state changes in real time. Useful for debugging non-ASCII keys and verifying modifier behavior. ----------------------------------------------------------------------- Summary of changes: .../keyboard/{keytest => keyrawtest}/Makefile | 6 +- .../dreamcast/keyboard/keyrawtest/keyrawtest.c | 210 +++++++++++++++++++++ kernel/arch/dreamcast/hardware/maple/keyboard.c | 112 +++++++++-- 3 files changed, 309 insertions(+), 19 deletions(-) copy examples/dreamcast/keyboard/{keytest => keyrawtest}/Makefile (81%) create mode 100644 examples/dreamcast/keyboard/keyrawtest/keyrawtest.c diff --git a/examples/dreamcast/keyboard/keytest/Makefile b/examples/dreamcast/keyboard/keyrawtest/Makefile similarity index 81% copy from examples/dreamcast/keyboard/keytest/Makefile copy to examples/dreamcast/keyboard/keyrawtest/Makefile index 3806fa68..cdd792ab 100644 --- a/examples/dreamcast/keyboard/keytest/Makefile +++ b/examples/dreamcast/keyboard/keyrawtest/Makefile @@ -1,10 +1,12 @@ # KallistiOS ##version## # +# Copyright (C) 2025 Troy Davis +# # examples/dreamcast/keyboard/keytest/Makefile # -TARGET = keytest.elf -OBJS = keytest.o +TARGET = keyrawtest.elf +OBJS = keyrawtest.o all: rm-elf $(TARGET) diff --git a/examples/dreamcast/keyboard/keyrawtest/keyrawtest.c b/examples/dreamcast/keyboard/keyrawtest/keyrawtest.c new file mode 100644 index 00000000..e1143f8f --- /dev/null +++ b/examples/dreamcast/keyboard/keyrawtest/keyrawtest.c @@ -0,0 +1,210 @@ +/* KallistiOS ##version## + + keyrawtest.c + Copyright (C) 2018 Donald Haase + Copyright (C) 2025 Troy Davis + + This example demonstrates raw keyboard event handling on the Dreamcast, + expanding on the original keytest example. + + Improvements include: + - Uses kbd_queue_pop(dev, 0) to capture all key events, not just ASCII-typed ones. + - Displays the current LED state (Caps Lock, Num Lock, Scroll Lock) for each event. + - Logs raw scancodes and attempts to name special keys for debugging purposes. + - Displays printable ASCII characters on screen, as in the original example. + + This test is useful for: + - Verifying key mappings and modifier behavior. + - Confirming proper LED state toggling and reporting. + - Debugging keyboard driver improvements or hardware compatibility. + + Future enhancements could include: + - Multiple keyboard support. + - Non-US layout testing. + - Enhanced visual feedback or UI. +*/ + +#define WIDTH 640 +#define HEIGHT 480 +#define STARTLINE 20 +#define CHARSPERLINE 40 +#define CHARSPERTEST 120 + +#include <assert.h> +#include <kos.h> + +extern uint16 *vram_s; + +static cont_state_t *first_kbd_state; +static maple_device_t *first_kbd_dev = NULL; + +/* Track how many times we try to find a keyboard before just quitting. */ +static uint8 no_kbd_loop = 0; +/* This is set up to have multiple tests in the future. */ +static uint8 test_phase = 0; + +// Add this helper function above basic_typing() +static const char *kbd_key_name(kbd_key_t key) { + switch (key) { + case KBD_KEY_NONE: return "NONE"; + case KBD_KEY_ERROR: return "ERROR"; + case KBD_KEY_ERR2: return "ERR2"; + case KBD_KEY_ERR3: return "ERR3"; + case KBD_KEY_ENTER: return "ENTER"; + case KBD_KEY_ESCAPE: return "ESC"; + case KBD_KEY_BACKSPACE: return "BACKSPACE"; + case KBD_KEY_TAB: return "TAB"; + case KBD_KEY_SPACE: return "SPACE"; + case KBD_KEY_CAPSLOCK: return "CAPSLOCK"; + case KBD_KEY_INSERT: return "INSERT"; + case KBD_KEY_HOME: return "HOME"; + case KBD_KEY_PGUP: return "PGUP"; + case KBD_KEY_DEL: return "DEL"; + case KBD_KEY_END: return "END"; + case KBD_KEY_PGDOWN: return "PGDOWN"; + case KBD_KEY_RIGHT: return "RIGHT"; + case KBD_KEY_LEFT: return "LEFT"; + case KBD_KEY_DOWN: return "DOWN"; + case KBD_KEY_UP: return "UP"; + case KBD_KEY_PRINT: return "PRINT"; + case KBD_KEY_SCRLOCK: return "SCRLOCK"; + case KBD_KEY_PAUSE: return "PAUSE"; + case KBD_KEY_F1: return "F1"; + case KBD_KEY_F2: return "F2"; + case KBD_KEY_F3: return "F3"; + case KBD_KEY_F4: return "F4"; + case KBD_KEY_F5: return "F5"; + case KBD_KEY_F6: return "F6"; + case KBD_KEY_F7: return "F7"; + case KBD_KEY_F8: return "F8"; + case KBD_KEY_F9: return "F9"; + case KBD_KEY_F10: return "F10"; + case KBD_KEY_F11: return "F11"; + case KBD_KEY_F12: return "F12"; + case KBD_KEY_PAD_NUMLOCK: return "PAD_NUMLOCK"; + case KBD_KEY_PAD_DIVIDE: return "PAD_DIVIDE"; + case KBD_KEY_PAD_MULTIPLY: return "PAD_MULTIPLY"; + case KBD_KEY_PAD_MINUS: return "PAD_MINUS"; + case KBD_KEY_PAD_PLUS: return "PAD_PLUS"; + case KBD_KEY_PAD_ENTER: return "PAD_ENTER"; + case KBD_KEY_PAD_0: return "PAD_0"; + case KBD_KEY_PAD_1: return "PAD_1"; + case KBD_KEY_PAD_2: return "PAD_2"; + case KBD_KEY_PAD_3: return "PAD_3"; + case KBD_KEY_PAD_4: return "PAD_4"; + case KBD_KEY_PAD_5: return "PAD_5"; + case KBD_KEY_PAD_6: return "PAD_6"; + case KBD_KEY_PAD_7: return "PAD_7"; + case KBD_KEY_PAD_8: return "PAD_8"; + case KBD_KEY_PAD_9: return "PAD_9"; + case KBD_KEY_PAD_PERIOD: return "PAD_PERIOD"; + case KBD_KEY_S3: return "S3"; + default: return NULL; + } +} + +static void basic_typing(void) +{ + int charcount = 0; + int lines = 0; + uint32_t offset = ((STARTLINE + (lines * BFONT_HEIGHT)) * WIDTH); + bfont_draw_str(vram_s + offset, WIDTH, 1, "Test of raw typing. Enter 120 keys: "); + offset = ((STARTLINE + ((++lines) * BFONT_HEIGHT)) * WIDTH); + + while (charcount < CHARSPERTEST) { + int raw = kbd_queue_pop(first_kbd_dev, 0); + if(raw == KBD_QUEUE_END) continue; + + // Decode raw: 0x00FF = key, 0xFF00 = modifiers, 0xFF0000 = LEDs + kbd_key_t key = (kbd_key_t)(raw & 0xFF); + kbd_mods_t mods = { .raw = (raw >> 8) & 0xFF }; + kbd_leds_t leds = { .raw = (raw >> 16) & 0xFF }; + + kbd_state_t *kbd = maple_dev_status(first_kbd_dev); + if(!kbd) continue; + + char ascii = kbd_key_to_ascii(key, kbd->region, mods, leds); + + printf("LEDs: caps=%d num=%d scroll=%d\n", leds.caps_lock, leds.num_lock, leds.scroll_lock); + + // Show printable ASCII characters on screen + if(ascii >= 32 && ascii <= 126) { + bfont_draw(vram_s + offset, WIDTH, 1, ascii); + offset += BFONT_THIN_WIDTH; + charcount++; + if(!(charcount % CHARSPERLINE)) { + offset = ((STARTLINE + ((++lines) * BFONT_HEIGHT)) * WIDTH); + } + } + + // Log everything + char debug[128]; + const char *keyname = kbd_key_name(key); + + if(ascii >= 32 && ascii <= 126) { + snprintf(debug, sizeof(debug), + "RAW 0x%02X | ascii: %c | shift:%d caps:%d ctrl:%d alt:%d s1:%d s2:%d", + key, ascii, + mods.lshift || mods.rshift, + leds.caps_lock, + mods.lctrl || mods.rctrl, + mods.lalt || mods.ralt, + mods.s1, + mods.s2 + ); + } else if(keyname) { + snprintf(debug, sizeof(debug), + "RAW 0x%02X | key: %s | shift:%d caps:%d ctrl:%d alt:%d s1:%d s2:%d", + key, keyname, + mods.lshift || mods.rshift, + leds.caps_lock, + mods.lctrl || mods.rctrl, + mods.lalt || mods.ralt, + mods.s1, + mods.s2 + ); + } else { + snprintf(debug, sizeof(debug), + "RAW 0x%02X | key: 0x%02X | shift:%d caps:%d ctrl:%d alt:%d s1:%d s2:%d", + key, key, + mods.lshift || mods.rshift, + leds.caps_lock, + mods.lctrl || mods.rctrl, + mods.lalt || mods.ralt, + mods.s1, + mods.s2 + ); + } + + printf("%s\n", debug); + } +} + +int main(int argc, char **argv) +{ + for(;;) { + /* If the dev is null, refresh it. */ + while(first_kbd_dev == NULL) { + first_kbd_dev = maple_enum_type(0, MAPLE_FUNC_KEYBOARD); + /* If it's *still* null, wait a bit and check again. */ + if(first_kbd_dev == NULL) { + timer_spin_sleep(500); + no_kbd_loop++; + } + if( no_kbd_loop >= 25 ) return -1; + } + /* Reset the timeout counter */ + no_kbd_loop = 0; + + first_kbd_state = (cont_state_t *) maple_dev_status(first_kbd_dev); + if(first_kbd_state == NULL) assert_msg(0, "Invalid Keyboard state returned"); + + if(test_phase == 0) + basic_typing(); + else + break; + + test_phase++; + } + return 0; +} \ No newline at end of file diff --git a/kernel/arch/dreamcast/hardware/maple/keyboard.c b/kernel/arch/dreamcast/hardware/maple/keyboard.c index cfa087ef..13d3d409 100644 --- a/kernel/arch/dreamcast/hardware/maple/keyboard.c +++ b/kernel/arch/dreamcast/hardware/maple/keyboard.c @@ -6,6 +6,7 @@ Copyright (C) 2018, 2025 Donald Haase Copyright (C) 2024 Paul Cercueil Copyright (C) 2025 Falco Girgis + Copyright (C) 2025 Troy Davis */ #include <assert.h> @@ -45,12 +46,15 @@ typedef struct kbd_state_private { size_t queue_head; /**< \brief Key queue head. */ volatile size_t queue_len; /**< \brief Current length of queue. */ + kbd_leds_t leds; /**< \brief Persistent LED state for toggles */ + 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 them in the state, but I don't see a reason to. It seems unreasonable that one might want different repeat @@ -429,15 +433,31 @@ 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) { + // Only apply Caps Lock logic to AâZ keys + bool is_letter = (key >= KBD_KEY_A && key <= KBD_KEY_Z); + bool shift_effect = (mods.raw & KBD_MOD_SHIFT); + + if(is_letter && leds.caps_lock) { + shift_effect = !shift_effect; // Caps Lock toggles Shift effect + } + + // Handle keypad keys when Num Lock is involved + if(key >= KBD_KEY_PAD_1 && key <= KBD_KEY_PAD_PERIOD) { + if(leds.num_lock) { + return keymaps[region - 1].base[key]; // Treat like number/punctuation + } else { + return 0; // No ASCII when Num Lock is off (acts as navigation) + } + } - if(mods.ralt || (mods.lctrl && mods.lalt)) + if(mods.ralt || (mods.lctrl && mods.lalt)) { return keymaps[region - 1].alt[key]; - else if((mods.raw & KBD_MOD_SHIFT) || leds.caps_lock) + } else if(shift_effect) { return keymaps[region - 1].shifted[key]; - else + } 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; @@ -482,7 +502,7 @@ static int kbd_enqueue(kbd_state_private_t *state, kbd_key_t 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); + state->base.cond.modifiers, state->leds); if(ascii == 0) ascii = ((uint16_t)keycode) << 8; @@ -545,12 +565,13 @@ int kbd_queue_pop(maple_device_t *dev, bool xlat) { irq_restore(irqs); if(!xlat) - return (int)(rv.key & (rv.mods.raw << 8) & (rv.leds.raw << 16)); + return (int)(rv.key | (rv.mods.raw << 8) | (rv.leds.raw << 16)); - if((ascii = kbd_key_to_ascii(rv.key, state->base.region, rv.mods, rv.leds))) + ascii = kbd_key_to_ascii(rv.key, state->base.region, rv.mods, rv.leds); + if(ascii != 0) return (int)ascii; else - return (int)(rv.key << 8); + return (int)(rv.key | (rv.mods.raw << 8) | (rv.leds.raw << 16)); } /* Update the keyboard status; this will handle debounce handling as well as @@ -558,8 +579,9 @@ int kbd_queue_pop(maple_device_t *dev, bool xlat) { words so that we can store "special" keys as such. */ 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; + kbd_leds_t *leds = &pstate->leds; // persistent LED state + kbd_state_t *state = &pstate->base; + kbd_cond_t *cond = (kbd_cond_t *)&state->cond; size_t i; /* If the modifier keys have changed, end the key repeating. */ @@ -596,27 +618,83 @@ static void kbd_check_poll(maple_frame_t *frm) { else { /* Update key state */ state->key_states[cond->keys[i]].is_down = true; + kbd_key_t key = cond->keys[i]; + + // Handle toggle keys by modifying persistent LED state + if(key == KBD_KEY_CAPSLOCK && state->key_states[key].value == KEY_STATE_CHANGED_DOWN) { + leds->caps_lock ^= 1; + } else if(key == KBD_KEY_PAD_NUMLOCK && state->key_states[key].value == KEY_STATE_CHANGED_DOWN) { + leds->num_lock ^= 1; + } else if(key == KBD_KEY_SCRLOCK && state->key_states[key].value == KEY_STATE_CHANGED_DOWN) { + leds->scroll_lock ^= 1; + } + // Sync persistent LEDs into the state used by enqueue and event handlers + pstate->leds = *leds; + + // Substitute navigation keys if Num Lock is OFF + if(!leds->num_lock) { + switch (key) { + case KBD_KEY_PAD_8: key = KBD_KEY_UP; break; + case KBD_KEY_PAD_2: key = KBD_KEY_DOWN; break; + case KBD_KEY_PAD_4: key = KBD_KEY_LEFT; break; + case KBD_KEY_PAD_6: key = KBD_KEY_RIGHT; break; + case KBD_KEY_PAD_7: key = KBD_KEY_HOME; break; + case KBD_KEY_PAD_1: key = KBD_KEY_END; break; + case KBD_KEY_PAD_9: key = KBD_KEY_PGUP; break; + case KBD_KEY_PAD_3: key = KBD_KEY_PGDOWN; break; + case KBD_KEY_PAD_5: key = KBD_KEY_NONE; break; + case KBD_KEY_PAD_0: key = KBD_KEY_INSERT; break; + case KBD_KEY_PAD_PERIOD: key = KBD_KEY_DEL; break; + default: break; + } + } + + // Sync persistent LED state to cond + state->cond.leds = pstate->leds; /* If the key hadn't been pressed. */ if(state->key_states[cond->keys[i]].value == KEY_STATE_CHANGED_DOWN) { - kbd_enqueue(pstate, cond->keys[i]); - pstate->repeater.key = cond->keys[i]; - if(repeat_timing.start) - pstate->repeater.timeout = timer_ms_gettime64() + repeat_timing.start; + if(key != KBD_KEY_NONE) { + kbd_enqueue(pstate, key); + pstate->repeater.key = cond->keys[i]; + if(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->key_states[cond->keys[i]].value == KEY_STATE_HELD_DOWN) { + kbd_key_t held_key = cond->keys[i]; + + // Apply the same substitution again for held keys + if(!leds->num_lock) { + switch (held_key) { + case KBD_KEY_PAD_8: held_key = KBD_KEY_UP; break; + case KBD_KEY_PAD_2: held_key = KBD_KEY_DOWN; break; + case KBD_KEY_PAD_4: held_key = KBD_KEY_LEFT; break; + case KBD_KEY_PAD_6: held_key = KBD_KEY_RIGHT; break; + case KBD_KEY_PAD_7: held_key = KBD_KEY_HOME; break; + case KBD_KEY_PAD_1: held_key = KBD_KEY_END; break; + case KBD_KEY_PAD_9: held_key = KBD_KEY_PGUP; break; + case KBD_KEY_PAD_3: held_key = KBD_KEY_PGDOWN; break; + case KBD_KEY_PAD_5: held_key = KBD_KEY_NONE; break; + case KBD_KEY_PAD_0: held_key = KBD_KEY_INSERT; break; + case KBD_KEY_PAD_PERIOD: held_key = KBD_KEY_DEL; break; + default: break; + } + } + 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->repeater.timeout)) + if(time >= pstate->repeater.timeout) pstate->repeater.timeout = time + repeat_timing.interval; else continue; } - kbd_enqueue(pstate, cond->keys[i]); + if(held_key != KBD_KEY_NONE) { + kbd_enqueue(pstate, held_key); + } } } else assert_msg(0, "invalid key_states array detected"); hooks/post-receive -- A pseudo Operating System for the Dreamcast. |