aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authoruser <user@clank>2026-07-16 16:07:01 +0200
committeruser <user@clank>2026-07-16 16:07:01 +0200
commita5557c75767f7201d5df8c6f95c162044072ef10 (patch)
treeed4ddd29ba3e6411b1b20f41fcbf8f67c023c93e
parentsyscall: mirror console output to the LCD terminal (diff)
downloadgbos-a5557c75767f7201d5df8c6f95c162044072ef10.tar.gz
gbos-a5557c75767f7201d5df8c6f95c162044072ef10.tar.xz
gbos-a5557c75767f7201d5df8c6f95c162044072ef10.zip
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.
-rw-r--r--include/gbos.inc1
-rw-r--r--shot.ppmbin0 -> 69135 bytes
-rw-r--r--src/boot.asm1
-rw-r--r--src/joypad.asm61
-rw-r--r--src/osk.asm339
-rw-r--r--src/syscall.asm14
-rw-r--r--src/term.asm3
7 files changed, 412 insertions, 7 deletions
diff --git a/include/gbos.inc b/include/gbos.inc
index c4804c4..92b82c9 100644
--- a/include/gbos.inc
+++ b/include/gbos.inc
@@ -11,6 +11,7 @@ DEF GBOS_INC EQU 1
DEF rIF EQU $FF0F ; interrupt flags
DEF rIE EQU $FFFF ; interrupt enable
DEF rSVBK EQU $FF70 ; CGB WRAM bank select ($D000-$DFFF): 1..7
+DEF wCurMask EQU $D589 ; term/osk: cursor underline bits OR'd into a tile's last row
DEF rKEY1 EQU $FF4D ; CGB speed switch
DEF rSB EQU $FF01 ; serial data
DEF rSC EQU $FF02 ; serial control
diff --git a/shot.ppm b/shot.ppm
new file mode 100644
index 0000000..1bb0ff7
--- /dev/null
+++ b/shot.ppm
Binary files differ
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