737c974400
- Split tests/ (libc feature tests) and examples/ (real apps); shared
app.mk in repo root, was examples/example.mk
- libc/io/* split into libc/{conio,env,errno,file,mouse,string,sys,
time,video}/ — clearer module boundaries
- New examples/mdview/: markdown viewer (Phases 1-5 + light nested
lists). Headers (H1-H4), HR, ulist/olist/quote with nesting via
leading spaces, fenced code blocks, inline emphasis (bold/italic/
underscore/code), wrap/unwrap mode with soft wrap (F2), horizontal
pan (← →) with '>' truncation indicator
- libc additions: scroll() in conio (ESTEX SCROLL), strlwr/strupr,
gets() test
- Makefile updates across tests/ for the new shared app.mk path
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
306 lines
8.4 KiB
C
306 lines
8.4 KiB
C
/*
|
||
* mouse.c — Sprinter mouse driver wrappers (RST 30h).
|
||
*
|
||
* All calls use the same pattern as ESTEX (push/pop IX around the RST)
|
||
* since the driver doesn't promise to preserve registers either.
|
||
*/
|
||
|
||
#include <mouse.h>
|
||
|
||
/* Scratch for READ_STATE — RST 30h clobbers HL/DE/A so we can't keep the
|
||
* state pointer in HL across the call. Explicit `= 0` so SDCC reserves
|
||
* real BSS storage — see memory/sdcc_static_storage_gotcha.md. */
|
||
static uint16_t mb_x = 0, mb_y = 0;
|
||
static uint8_t mb_buttons = 0;
|
||
|
||
int mouse_init(void) __naked
|
||
{
|
||
__asm
|
||
push ix
|
||
ld c, #0x00 ; INITIALIZATION
|
||
rst #0x30 ; MOUSE
|
||
pop ix
|
||
jr c, _mi_err
|
||
ld de, #0
|
||
ret
|
||
_mi_err:
|
||
ld de, #-1
|
||
ret
|
||
__endasm;
|
||
}
|
||
|
||
void mouse_show(void) __naked
|
||
{
|
||
__asm
|
||
push ix
|
||
ld c, #0x01 ; SHOW MOUSE CURSOR
|
||
rst #0x30 ; MOUSE
|
||
pop ix
|
||
ret
|
||
__endasm;
|
||
}
|
||
|
||
void mouse_hide(void) __naked
|
||
{
|
||
__asm
|
||
push ix
|
||
ld c, #0x02 ; HIDE MOUSE CURSOR
|
||
rst #0x30 ; MOUSE
|
||
pop ix
|
||
ret
|
||
__endasm;
|
||
}
|
||
|
||
void mouse_refresh(void) __naked
|
||
{
|
||
__asm
|
||
push ix
|
||
ld c, #0x83 ; MOUSE REFRESH
|
||
rst #0x30 ; MOUSE
|
||
pop ix
|
||
ret
|
||
__endasm;
|
||
}
|
||
|
||
void mouse_read(mouse_state_t *st) __naked
|
||
{
|
||
(void)st;
|
||
__asm
|
||
;; HL = state ptr on entry.
|
||
push ix
|
||
push hl ; stash ptr across RST
|
||
ld c, #0x03 ; READ MOUSE STATE
|
||
rst #0x30 ; MOUSE
|
||
;; Returns: A=buttons, HL=x, DE=y (CF=err but we ignore here)
|
||
ld (_mb_x), hl
|
||
ld (_mb_y), de
|
||
ld (_mb_buttons), a
|
||
pop hl ; restore state ptr
|
||
pop ix
|
||
|
||
;; Copy scratch → *st. Struct layout: x(2), y(2), buttons(1).
|
||
ld de, (_mb_x)
|
||
ld (hl), e
|
||
inc hl
|
||
ld (hl), d
|
||
inc hl
|
||
ld de, (_mb_y)
|
||
ld (hl), e
|
||
inc hl
|
||
ld (hl), d
|
||
inc hl
|
||
ld a, (_mb_buttons)
|
||
ld (hl), a
|
||
ret
|
||
__endasm;
|
||
}
|
||
|
||
void mouse_goto(int x, int y) __naked
|
||
{
|
||
(void)x; (void)y;
|
||
__asm
|
||
;; HL = x, DE = y.
|
||
push ix
|
||
ld c, #0x04 ; GOTO MOUSE CURSOR
|
||
rst #0x30 ; MOUSE
|
||
pop ix
|
||
ret
|
||
__endasm;
|
||
}
|
||
|
||
void mouse_bounds_x(int xmin, int xmax) __naked
|
||
{
|
||
(void)xmin; (void)xmax;
|
||
__asm
|
||
;; HL = xmin, DE = xmax.
|
||
push ix
|
||
ld c, #0x08 ; HORZ BOUNDS
|
||
rst #0x30 ; MOUSE
|
||
pop ix
|
||
ret
|
||
__endasm;
|
||
}
|
||
|
||
void mouse_bounds_y(int ymin, int ymax) __naked
|
||
{
|
||
(void)ymin; (void)ymax;
|
||
__asm
|
||
;; HL = ymin, DE = ymax.
|
||
push ix
|
||
ld c, #0x07 ; VERT BOUNDS
|
||
rst #0x30 ; MOUSE
|
||
pop ix
|
||
ret
|
||
__endasm;
|
||
}
|
||
|
||
/* ---- $09 LOAD CURSOR + $0B RETURN CURSOR ---------------------- */
|
||
/* Scratch for the IX-passing convention — RST 30h takes the bitmap
|
||
* pointer in IX, so we have to set it up explicitly.
|
||
*
|
||
* Initialised to 0 so SDCC reserves real BSS storage — uninitialised
|
||
* `static uint8_t` declarations can coalesce to a single address and
|
||
* stomp on each other. See memory/sdcc_static_storage_gotcha.md. */
|
||
static uint16_t mc_image = 0;
|
||
static uint8_t mc_width = 0;
|
||
static uint8_t mc_height = 0;
|
||
static uint8_t mc_hot_x = 0;
|
||
static uint8_t mc_hot_y = 0;
|
||
/* Saved struct pointer for mouse_get_cursor. SDCC __sdcccall(1) passes
|
||
* `c` in HL, then stashes it in DE around the inline asm. Our asm
|
||
* clobbers DE (driver returns hot_y/hot_x in D/E) so the post-asm
|
||
* `c->width = ...` writes would otherwise land at a garbage address.
|
||
* We park the pointer in BSS instead so SDCC re-fetches it from
|
||
* memory after the asm. */
|
||
static mouse_cursor_t *mc_dest = 0;
|
||
|
||
void mouse_load_cursor(const mouse_cursor_t *c)
|
||
{
|
||
/* Copy fields out of the C struct into our scratch globals so the
|
||
* asm side has well-known names. */
|
||
mc_image = (uint16_t)(uintptr_t)c->image;
|
||
mc_width = c->width;
|
||
mc_height = c->height;
|
||
mc_hot_x = c->hot_x;
|
||
mc_hot_y = c->hot_y;
|
||
__asm
|
||
push ix
|
||
ld ix, (_mc_image)
|
||
ld a, (_mc_height)
|
||
ld h, a
|
||
ld a, (_mc_width)
|
||
ld l, a
|
||
ld a, (_mc_hot_y)
|
||
ld d, a
|
||
ld a, (_mc_hot_x)
|
||
ld e, a
|
||
ld b, #0
|
||
ld c, #0x09 ; LOAD MOUSE CURSOR
|
||
rst #0x30 ; MOUSE
|
||
pop ix
|
||
__endasm;
|
||
}
|
||
|
||
void mouse_get_cursor(mouse_cursor_t *c)
|
||
{
|
||
mc_dest = c; /* park ptr in BSS */
|
||
mc_image = (uint16_t)(uintptr_t)c->image;
|
||
__asm
|
||
push ix
|
||
ld ix, (_mc_image) ; IX = bitmap buffer from caller
|
||
ld c, #0x0B ; RETURN CURSOR
|
||
rst #0x30 ; mouse driver, NOT ESTEX
|
||
;; Returns: H=height, L=width, D=hot_y, E=hot_x.
|
||
ld a, h
|
||
ld (_mc_height), a
|
||
ld a, l
|
||
ld (_mc_width), a
|
||
ld a, d
|
||
ld (_mc_hot_y), a
|
||
ld a, e
|
||
ld (_mc_hot_x), a
|
||
pop ix
|
||
__endasm;
|
||
/* Re-fetch the struct pointer from BSS — `c` (kept in DE by SDCC
|
||
* around the inline asm) was clobbered by the RST 30h above. */
|
||
mouse_cursor_t *p = mc_dest;
|
||
p->width = mc_width;
|
||
p->height = mc_height;
|
||
p->hot_x = mc_hot_x;
|
||
p->hot_y = mc_hot_y;
|
||
}
|
||
|
||
/* ---- $0E / $0F SENSITIVITY ------------------------------------ */
|
||
/* GET returns H=vert, L=horz in HL. We expose the two halves as
|
||
* separate getters so the simple "uint8_t" return ABI works cleanly. */
|
||
|
||
static int ms_query(void) __naked
|
||
{
|
||
__asm
|
||
push ix
|
||
ld c, #0x0E ; GET SENSITIVITY
|
||
rst #0x30 ; MOUSE
|
||
ld d, h
|
||
ld e, l
|
||
pop ix
|
||
ret
|
||
__endasm;
|
||
}
|
||
|
||
uint8_t mouse_get_sensitivity_x(void)
|
||
{
|
||
return (uint8_t)(ms_query() & 0xFF); /* E = horz */
|
||
}
|
||
|
||
uint8_t mouse_get_sensitivity_y(void)
|
||
{
|
||
return (uint8_t)(ms_query() >> 8); /* D = vert */
|
||
}
|
||
|
||
void mouse_set_sensitivity(uint8_t horz, uint8_t vert) __naked
|
||
{
|
||
(void)horz; (void)vert;
|
||
/* Pack into HL: H=vert, L=horz. */
|
||
__asm
|
||
push ix
|
||
ld h, l
|
||
ld l, a
|
||
ld c, #0x0F ; SET SENSITIVITY
|
||
rst #0x30 ; MOUSE
|
||
pop ix
|
||
__endasm;
|
||
}
|
||
|
||
/* ---- $81 CHANGE VIDEO MODE ------------------------------------ */
|
||
/* SDCC __sdcccall(1): single uint8_t arg arrives in A. */
|
||
void mouse_video_mode_changed(uint8_t mode) __naked
|
||
{
|
||
(void)mode;
|
||
__asm
|
||
push ix
|
||
;; A already holds the mode byte (from SDCC ABI).
|
||
ld c, #0x81 ; CHANGE VIDEO MODE
|
||
rst #0x30 ; MOUSE
|
||
pop ix
|
||
ret
|
||
__endasm;
|
||
}
|
||
|
||
/* CURSOR_TEXT_MODES ($0A):
|
||
* B = 0
|
||
* H = AND symbol mask L = XOR symbol mask
|
||
* D = AND attribute mask E = XOR attribute mask
|
||
*
|
||
* SDCC __sdcccall(1) gives us:
|
||
* sym_and in L (low byte of HL arg)
|
||
* sym_xor in E (low byte of DE arg)
|
||
* attr_and at [SP+2]
|
||
* attr_xor at [SP+3]
|
||
*/
|
||
void mouse_text_cursor(uint8_t sym_and, uint8_t sym_xor,
|
||
uint8_t attr_and, uint8_t attr_xor) __naked
|
||
{
|
||
(void)sym_and; (void)sym_xor; (void)attr_and; (void)attr_xor;
|
||
__asm
|
||
;; SDCC __sdcccall(1) for 4×uint8_t args:
|
||
;; arg1 sym_and → A
|
||
;; arg2 sym_xor → L
|
||
;; arg3 attr_and → stack low byte (packed into HL.L on caller, push HL)
|
||
;; arg4 attr_xor → stack high byte (HL.H pushed by caller)
|
||
pop iy ; return address
|
||
pop bc ; C = attr_and (low), B = attr_xor (high)
|
||
|
||
push ix
|
||
;; Target: H=sym_and, L=sym_xor, D=attr_and, E=attr_xor, B=0
|
||
ld h, a ; H = sym_and (from A)
|
||
; L already holds sym_xor
|
||
ld d, c ; D = attr_and
|
||
ld e, b ; E = attr_xor
|
||
ld b, #0
|
||
ld c, #0x0A ; CURSOR TEXT MODE
|
||
rst #0x30 ; MOUSE
|
||
pop ix
|
||
jp (iy)
|
||
__endasm;
|
||
}
|