From a5557c75767f7201d5df8c6f95c162044072ef10 Mon Sep 17 00:00:00 2001 From: user Date: Thu, 16 Jul 2026 16:07:01 +0200 Subject: osk: toggle-able on-screen keyboard on the window layer A joypad-driven keyboard that costs zero permanent screen space: it lives on the GB window layer, so SELECT just flips LCDC bit 5 (window enable) and the terminal's background layer underneath is never disturbed. - src/joypad.asm: read $FF00 with edge detection (wPadCur/Prev/New). - src/osk.asm: 3x20 key grid (letters, digits, space, punctuation, plus Enter/Backspace) built once into spare bank-1 VRAM tiles (104+) and laid out on the $9C00 window map, docked to the bottom 3 rows. The highlighted key is a palette swap on its window attribute byte (CGB BG palette 1 = inverted), so moving the cursor is 1-2 attribute writes with no tile rebuilding. SELECT toggles, d-pad moves, A types. - KGetc's console poll loop now polls the joypad and osk_handle each iteration; a key press returns its byte to the reader exactly like a serial byte, so the shell is oblivious to the input source. Verified in the emulator (via --keys): SELECT shows the keyboard, the highlight tracks the d-pad, and typing "ls"+Enter runs the command and lists the files; a second SELECT hides it and reclaims the full 18 rows. Known limitation: the OSK overlays the bottom 3 terminal rows, so if the prompt has scrolled to the very bottom the current input line can be hidden. A follow-up can add a scroll region (terminal uses rows 0-14 while the OSK is up). Input over serial still works unchanged. --- src/boot.asm | 1 + src/joypad.asm | 61 ++++++++++ src/osk.asm | 339 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++ src/syscall.asm | 14 ++- src/term.asm | 3 +- 5 files changed, 411 insertions(+), 7 deletions(-) create mode 100644 src/joypad.asm create mode 100644 src/osk.asm (limited to 'src') diff --git a/src/boot.asm b/src/boot.asm index 973bc8e..7eed4a8 100644 --- a/src/boot.asm +++ b/src/boot.asm @@ -57,6 +57,7 @@ KernelInit: ld [wCurProc+1], a call fs_init ; mount the disk (format on first boot; persists) call term_init ; set up the LCD text terminal + call osk_init ; build the on-screen keyboard (window layer) ; terminal starts blank; shell output flows in via KPutc ; --- spawn the init task (pid 1); it fork()s the rest --- diff --git a/src/joypad.asm b/src/joypad.asm new file mode 100644 index 0000000..73283ac --- /dev/null +++ b/src/joypad.asm @@ -0,0 +1,61 @@ +; ============================================================================= +; joypad.asm - read the Game Boy joypad ($FF00) with edge detection. +; ============================================================================= +INCLUDE "include/gbos.inc" + +DEF rP1 EQU $FF00 + +DEF PAD_A EQU $01 +DEF PAD_B EQU $02 +DEF PAD_SELECT EQU $04 +DEF PAD_START EQU $08 +DEF PAD_RIGHT EQU $10 +DEF PAD_LEFT EQU $20 +DEF PAD_UP EQU $40 +DEF PAD_DOWN EQU $80 + +SECTION "pad_state", WRAM0 +wPadCur:: DS 1 ; currently held (1 = pressed) +wPadPrev:: DS 1 ; held on the previous poll +wPadNew:: DS 1 ; newly pressed this poll (cur & ~prev) + +SECTION "pad", ROM0 + +; read_joypad -> A = state, 1=pressed: +; bit0 A, 1 B, 2 Select, 3 Start, 4 Right, 5 Left, 6 Up, 7 Down +read_joypad:: + ld a, $20 ; P14=0: select d-pad + ld [rP1], a + ld a, [rP1] + ld a, [rP1] ; let the lines settle + cpl + and $0F ; right/left/up/down (bits 0-3) + swap a ; -> bits 4-7 + ld b, a + ld a, $10 ; P15=0: select buttons + ld [rP1], a + ld a, [rP1] + ld a, [rP1] + ld a, [rP1] + ld a, [rP1] ; buttons bounce more; settle longer + cpl + and $0F ; A/B/Select/Start (bits 0-3) + or b + ld c, a + ld a, $30 ; deselect both + ld [rP1], a + ld a, c + ret + +; pad_poll - refresh wPadCur/wPadPrev/wPadNew. +pad_poll:: + ld a, [wPadCur] + ld [wPadPrev], a + call read_joypad + ld [wPadCur], a + ld b, a + ld a, [wPadPrev] + cpl + and b ; newly pressed edges + ld [wPadNew], a + ret diff --git a/src/osk.asm b/src/osk.asm new file mode 100644 index 0000000..b8ee611 --- /dev/null +++ b/src/osk.asm @@ -0,0 +1,339 @@ +; ============================================================================= +; osk.asm - on-screen keyboard on the WINDOW layer (CGB). +; +; The terminal owns the BG layer ($9800 map, tiles 0-359). The OSK lives on the +; window ($9C00 map) so toggling it is just LCDC bit 5 (window enable) - the +; terminal underneath is never touched. Key glyphs are built once into spare +; bank-1 VRAM tiles (104+). The highlighted key is a palette swap on its window +; attribute byte (palette 1 = inverted), so moving the cursor is 1-2 byte writes +; with no tile rebuilding. SELECT toggles; d-pad moves; A types into KGetc. +; ============================================================================= +INCLUDE "include/gbos.inc" + +DEF rVBK EQU $FF4F +DEF rBCPS EQU $FF68 +DEF rBCPD EQU $FF69 +DEF rWY EQU $FF4A +DEF rWX EQU $FF4B + +DEF OSK_ROWS EQU 3 +DEF OSK_COLS EQU 20 +DEF OSK_KEYS EQU OSK_ROWS * OSK_COLS +DEF OSK_TILE0 EQU 104 ; first OSK tile index (bank 1; terminal uses 0-103) +DEF OSK_MAP EQU $9C00 ; window tilemap + +SECTION "osk_state", WRAM0 +wOskVisible:: DS 1 +wOskRow:: DS 1 +wOskCol:: DS 1 +wOskBuild:: DS 1 ; scratch for the init build loop + +SECTION "osk_keys", ROM0 +; type values, row-major (OSK_ROWS x OSK_COLS) +osk_keys: + db "abcdefghijklmnopqrst" + db "uvwxyz0123456789 .-/" + db 10, 8, ":;,=+*_!?()[]<>@#~" + +SECTION "osk", ROM0 + +; ----------------------------------------------------------------------------- +; osk_disp - A = key type value -> B = left glyph, C = right glyph (2-char label) +; ----------------------------------------------------------------------------- +osk_disp: + cp 10 + jr nz, .n1 + ld b, "C" + ld c, "R" + ret ; enter +.n1 + cp 8 + jr nz, .n2 + ld b, "<" + ld c, "-" + ret ; backspace +.n2 + cp " " + jr nz, .n3 + ld b, "S" + ld c, "P" + ret ; space +.n3 + ld b, a ; regular: char in left cell + ld c, " " + ret + +; ----------------------------------------------------------------------------- +; osk_init - build key tiles, lay out the window map, set up palette 1. Window +; starts disabled. +; ----------------------------------------------------------------------------- +osk_init:: + ; CGB BG palette 1 = inverted: color0 black, color1 white + ld a, $88 ; BCPS index 8 (palette 1, color 0), auto-inc + ld [rBCPS], a + xor a + ld [rBCPD], a + ld [rBCPD], a ; c0 = $0000 black + ld a, $FF + ld [rBCPD], a + ld a, $7F + ld [rBCPD], a ; c1 = $7FFF white + + ; build the key tiles (no cursor underline) + xor a + ld [wCurMask], a + ld [wOskBuild], a +.build + ld a, [wOskBuild] + ld e, a + ld d, 0 + ld hl, osk_keys + add hl, de + ld a, [hl] + call osk_disp ; B=left, C=right + ld a, [wOskBuild] + add OSK_TILE0 + ld e, a + ld d, 1 ; VRAM bank 1 + call build_tile + ld a, [wOskBuild] + inc a + ld [wOskBuild], a + cp OSK_KEYS + jr c, .build + xor a + ld [rVBK], a + + ; window map indices (bank 0): [r,c] = OSK_TILE0 + r*20 + c + ld c, 0 +.i_row + ld a, c + call .rowbase ; A = OSK_TILE0 + row*20 (clobbers HL/DE) + ld e, a + call .maprow ; HL = OSK_MAP + row*32 (preserves E) + ld b, OSK_COLS +.i_col + ld a, e + ld [hl+], a + inc e + dec b + jr nz, .i_col + inc c + ld a, c + cp OSK_ROWS + jr c, .i_row + + ; window map attributes (bank 1): $08 = bank-1 tile, palette 0 + ld a, 1 + ld [rVBK], a + ld c, 0 +.a_row + call .maprow + ld b, OSK_COLS +.a_col + ld a, $08 + ld [hl+], a + dec b + jr nz, .a_col + inc c + ld a, c + cp OSK_ROWS + jr c, .a_row + xor a + ld [rVBK], a + + ; window position: 3 rows at the bottom (screen rows 15-17) + ld a, 7 + ld [rWX], a + ld a, 120 + ld [rWY], a + ; select window map $9C00 (LCDC bit 6); leave window disabled (bit 5 = 0) + ld a, [rLCDC] + or %01000000 + ld [rLCDC], a + + xor a + ld [wOskVisible], a + ld [wOskRow], a + ld [wOskCol], a + call osk_highlight + ret + +; HL = OSK_MAP + C*32 +.maprow + ld a, c + ld h, 0 + ld l, a + add hl, hl + add hl, hl + add hl, hl + add hl, hl + add hl, hl + ld a, h + add HIGH(OSK_MAP) + ld h, a + ret +; A(row) -> A = OSK_TILE0 + row*20 +.rowbase + ld h, 0 + ld l, a + add hl, hl + add hl, hl ; *4 + ld d, h + ld e, l + add hl, hl + add hl, hl ; *16 + add hl, de ; *20 + ld a, l + add OSK_TILE0 + ret + +; ----------------------------------------------------------------------------- +; osk_set_pal - write palette A (0 or 1) into the current (wOskRow,wOskCol) +; window attribute. +; ----------------------------------------------------------------------------- +osk_set_pal: + or $08 ; bank-1 bit + ld e, a ; save attr byte + ld a, 1 + ld [rVBK], a + ld a, [wOskRow] + ld h, 0 + ld l, a + add hl, hl + add hl, hl + add hl, hl + add hl, hl + add hl, hl ; row*32 + ld a, h + add HIGH(OSK_MAP) + ld h, a + ld a, [wOskCol] + add l + ld l, a + ld a, h + adc 0 + ld h, a + ld a, e + ld [hl], a + xor a + ld [rVBK], a + ret + +osk_highlight: + ld a, 1 + jr osk_set_pal +osk_unhighlight: + xor a + jr osk_set_pal + +; ----------------------------------------------------------------------------- +; osk_toggle - show/hide the keyboard (window enable, LCDC bit 5). +; ----------------------------------------------------------------------------- +osk_toggle:: + ld a, [wOskVisible] + xor 1 + ld [wOskVisible], a + or a + jr z, .hide + ld a, [rLCDC] + or %00100000 + ld [rLCDC], a + ret +.hide + ld a, [rLCDC] + and %11011111 + ld [rLCDC], a + ret + +; movement (with clamping); each moves the highlight +osk_up: + ld a, [wOskRow] + or a + ret z + call osk_unhighlight + ld hl, wOskRow + dec [hl] + jr osk_highlight +osk_down: + ld a, [wOskRow] + cp OSK_ROWS-1 + ret z + call osk_unhighlight + ld hl, wOskRow + inc [hl] + jr osk_highlight +osk_left: + ld a, [wOskCol] + or a + ret z + call osk_unhighlight + ld hl, wOskCol + dec [hl] + jr osk_highlight +osk_right: + ld a, [wOskCol] + cp OSK_COLS-1 + ret z + call osk_unhighlight + ld hl, wOskCol + inc [hl] + jr osk_highlight + +; -> A = type value of the selected key +osk_selected: + ld a, [wOskRow] + ld h, 0 + ld l, a + add hl, hl + add hl, hl + ld d, h + ld e, l + add hl, hl + add hl, hl + add hl, de ; row*20 + ld a, [wOskCol] + add l + ld l, a + ld a, h + adc 0 + ld h, a + ld de, osk_keys + add hl, de + ld a, [hl] + ret + +; ----------------------------------------------------------------------------- +; osk_handle - process the latest pad edges. Returns CF set + A = typed byte if +; the user pressed A over a key, else CF clear. (SELECT toggles; d-pad moves.) +; ----------------------------------------------------------------------------- +osk_handle:: + ld a, [wPadNew] + bit 2, a ; SELECT + jr z, .nosel + call osk_toggle +.nosel + ld a, [wOskVisible] + or a + jr z, .none + ld a, [wPadNew] + bit 6, a + call nz, osk_up + ld a, [wPadNew] + bit 7, a + call nz, osk_down + ld a, [wPadNew] + bit 5, a + call nz, osk_left + ld a, [wPadNew] + bit 4, a + call nz, osk_right + ld a, [wPadNew] + bit 0, a ; A: type the selected key + jr z, .none + call osk_selected + scf + ret +.none + or a ; CF = 0 + ret diff --git a/src/syscall.asm b/src/syscall.asm index 870a2ff..cb2c996 100644 --- a/src/syscall.asm +++ b/src/syscall.asm @@ -122,17 +122,21 @@ KGetc:: jp sys_getb ; B=fd -> A=byte, CF=EOF .console .poll + call pad_poll ; poll joypad; drive the on-screen keyboard + call osk_handle ; CF set + A = byte if a key was typed + jr c, .done ld a, $80 ld [rSC], a ld a, [rSC] bit 7, a - jr z, .got + jr nz, .yield + ld a, [rSB] ; A = serial byte +.done + and a ; CF = 0 (not EOF) + ret +.yield call SchedYield jr .poll -.got - ld a, [rSB] - and a ; CF = 0 - ret ; ----------------------------------------------------------------------------- ; KPutc(E = byte) - write to this process's stdout (console/file). diff --git a/src/term.asm b/src/term.asm index 10cdccc..ba9ff93 100644 --- a/src/term.asm +++ b/src/term.asm @@ -31,7 +31,6 @@ DEF wVram EQU $D584 ; 2 DEF wCurRow EQU $D586 ; cursor screen row 0..17 DEF wCurCol EQU $D587 ; cursor screen col 0..39 DEF wCursorOn EQU $D588 ; draw the cursor? -DEF wCurMask EQU $D589 ; underline bits to OR into a tile's last row DEF wRT_sr EQU $D58A ; render_tile scratch DEF wRT_tcol EQU $D58B DEF wRT_cL EQU $D58C @@ -230,7 +229,7 @@ glyph_ptr: ; build_tile - build tile for tile-position DE (E=index, D=bank) from chars ; B (left) and C (right). ORs wCurMask into the last pixel row (cursor). ; ----------------------------------------------------------------------------- -build_tile: +build_tile:: ld a, d ld [rVBK], a ; VRAM bank for this tile ld h, 0 -- cgit v1.3.1-sl0p