Key Up/Down working.
This commit is contained in:
parent
b3d9961e78
commit
9bacd2c68f
23 changed files with 1225 additions and 50 deletions
162
examples/keys/keys.c
Normal file
162
examples/keys/keys.c
Normal file
|
|
@ -0,0 +1,162 @@
|
|||
// Visual keyboard demo: one square per JoeyKeyE, filled bright when
|
||||
// the key is held and dim otherwise. Press ESC to quit.
|
||||
//
|
||||
// The first paint fills every cell and does one full-surface present.
|
||||
// Thereafter the loop compares each cell against its previous drawn
|
||||
// state and only redraws + presents the cells that changed. Full-
|
||||
// surface chunky-to-planar conversion is expensive on 68000-class
|
||||
// hardware (hundreds of milliseconds for 320x200x4bpp on Amiga / ST);
|
||||
// per-cell rect presents keep the response tight regardless of how
|
||||
// fast the host can convert a full frame.
|
||||
|
||||
#include <stdio.h>
|
||||
|
||||
#include <joey/joey.h>
|
||||
|
||||
#define GRID_COLS 10
|
||||
#define GRID_ROWS 6
|
||||
#define CELL_W 28
|
||||
#define CELL_H 28
|
||||
#define GAP 4
|
||||
#define MARGIN_X 2
|
||||
#define MARGIN_Y 6
|
||||
|
||||
#define COLOR_BACKGROUND 0
|
||||
#define COLOR_UNLIT 1
|
||||
#define COLOR_LIT 2
|
||||
|
||||
static void buildPalette(SurfaceT *screen);
|
||||
static void drawCell(SurfaceT *screen, int16_t col, int16_t row, bool lit);
|
||||
static void drawAllCells(SurfaceT *screen);
|
||||
static void presentChangedCells(SurfaceT *screen);
|
||||
|
||||
// Keys laid out row-by-row. KEY_NONE cells stay blank. Shape roughly
|
||||
// resembles a real keyboard (top number row, then QWERTY rows, then a
|
||||
// cluster of modifiers / arrows / function keys).
|
||||
static const JoeyKeyE gKeyGrid[GRID_ROWS][GRID_COLS] = {
|
||||
{ KEY_1, KEY_2, KEY_3, KEY_4, KEY_5, KEY_6, KEY_7, KEY_8, KEY_9, KEY_0 },
|
||||
{ KEY_Q, KEY_W, KEY_E, KEY_R, KEY_T, KEY_Y, KEY_U, KEY_I, KEY_O, KEY_P },
|
||||
{ KEY_A, KEY_S, KEY_D, KEY_F, KEY_G, KEY_H, KEY_J, KEY_K, KEY_L, KEY_BACKSPACE },
|
||||
{ KEY_Z, KEY_X, KEY_C, KEY_V, KEY_B, KEY_N, KEY_M, KEY_LSHIFT, KEY_RSHIFT, KEY_TAB },
|
||||
{ KEY_SPACE, KEY_ESCAPE, KEY_RETURN, KEY_LCTRL, KEY_LALT, KEY_UP, KEY_DOWN, KEY_LEFT, KEY_RIGHT, KEY_NONE },
|
||||
{ KEY_F1, KEY_F2, KEY_F3, KEY_F4, KEY_F5, KEY_F6, KEY_F7, KEY_F8, KEY_F9, KEY_F10 }
|
||||
};
|
||||
|
||||
static bool gCellLit[GRID_ROWS][GRID_COLS];
|
||||
|
||||
|
||||
static void buildPalette(SurfaceT *screen) {
|
||||
uint16_t colors[SURFACE_COLORS_PER_PALETTE];
|
||||
uint16_t i;
|
||||
|
||||
for (i = 0; i < SURFACE_COLORS_PER_PALETTE; i++) {
|
||||
colors[i] = 0x0000;
|
||||
}
|
||||
colors[COLOR_BACKGROUND] = 0x0000; // black
|
||||
colors[COLOR_UNLIT] = 0x0333; // dark gray
|
||||
colors[COLOR_LIT] = 0x00F0; // bright green
|
||||
|
||||
paletteSet(screen, 0, colors);
|
||||
}
|
||||
|
||||
|
||||
static void drawCell(SurfaceT *screen, int16_t col, int16_t row, bool lit) {
|
||||
int16_t x;
|
||||
int16_t y;
|
||||
uint8_t color;
|
||||
|
||||
x = (int16_t)(MARGIN_X + col * (CELL_W + GAP));
|
||||
y = (int16_t)(MARGIN_Y + row * (CELL_H + GAP));
|
||||
color = lit ? COLOR_LIT : COLOR_UNLIT;
|
||||
fillRect(screen, x, y, CELL_W, CELL_H, color);
|
||||
}
|
||||
|
||||
|
||||
static void drawAllCells(SurfaceT *screen) {
|
||||
int16_t col;
|
||||
int16_t row;
|
||||
JoeyKeyE key;
|
||||
bool lit;
|
||||
|
||||
for (row = 0; row < GRID_ROWS; row++) {
|
||||
for (col = 0; col < GRID_COLS; col++) {
|
||||
key = gKeyGrid[row][col];
|
||||
if (key == KEY_NONE) {
|
||||
continue;
|
||||
}
|
||||
lit = joeyKeyDown(key);
|
||||
drawCell(screen, col, row, lit);
|
||||
gCellLit[row][col] = lit;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
static void presentChangedCells(SurfaceT *screen) {
|
||||
int16_t col;
|
||||
int16_t row;
|
||||
JoeyKeyE key;
|
||||
bool lit;
|
||||
int16_t x;
|
||||
int16_t y;
|
||||
|
||||
for (row = 0; row < GRID_ROWS; row++) {
|
||||
for (col = 0; col < GRID_COLS; col++) {
|
||||
key = gKeyGrid[row][col];
|
||||
if (key == KEY_NONE) {
|
||||
continue;
|
||||
}
|
||||
lit = joeyKeyDown(key);
|
||||
if (lit == gCellLit[row][col]) {
|
||||
continue;
|
||||
}
|
||||
drawCell(screen, col, row, lit);
|
||||
x = (int16_t)(MARGIN_X + col * (CELL_W + GAP));
|
||||
y = (int16_t)(MARGIN_Y + row * (CELL_H + GAP));
|
||||
surfacePresentRect(screen, x, y, CELL_W, CELL_H);
|
||||
gCellLit[row][col] = lit;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
int main(void) {
|
||||
JoeyConfigT config;
|
||||
SurfaceT *screen;
|
||||
|
||||
config.hostMode = HOST_MODE_TAKEOVER;
|
||||
config.codegenBytes = 32 * 1024;
|
||||
config.maxSurfaces = 4;
|
||||
config.audioBytes = 64 * 1024;
|
||||
config.assetBytes = 128 * 1024;
|
||||
|
||||
if (!joeyInit(&config)) {
|
||||
fprintf(stderr, "joeyInit failed: %s\n", joeyLastError());
|
||||
return 1;
|
||||
}
|
||||
|
||||
screen = surfaceGetScreen();
|
||||
if (screen == NULL) {
|
||||
fprintf(stderr, "surfaceGetScreen returned NULL\n");
|
||||
joeyShutdown();
|
||||
return 1;
|
||||
}
|
||||
|
||||
buildPalette(screen);
|
||||
scbSetRange(screen, 0, SURFACE_HEIGHT - 1, 0);
|
||||
surfaceClear(screen, COLOR_BACKGROUND);
|
||||
joeyInputPoll();
|
||||
drawAllCells(screen);
|
||||
surfacePresent(screen);
|
||||
|
||||
for (;;) {
|
||||
joeyInputPoll();
|
||||
if (joeyKeyPressed(KEY_ESCAPE)) {
|
||||
break;
|
||||
}
|
||||
presentChangedCells(screen);
|
||||
}
|
||||
|
||||
joeyShutdown();
|
||||
return 0;
|
||||
}
|
||||
47
include/joey/input.h
Normal file
47
include/joey/input.h
Normal file
|
|
@ -0,0 +1,47 @@
|
|||
// Keyboard input polling.
|
||||
//
|
||||
// Call joeyInputPoll() once per frame (typically right before
|
||||
// drawing) to refresh the keyboard state. After polling, the
|
||||
// joeyKey* predicates return the current state of every key:
|
||||
//
|
||||
// joeyKeyDown(k) -- is key k held down right now
|
||||
// joeyKeyPressed(k) -- rising edge since the previous poll
|
||||
// joeyKeyReleased(k) -- falling edge since the previous poll
|
||||
//
|
||||
// Edge predicates are one-shot: they return true only in the
|
||||
// frame the transition occurred and false thereafter.
|
||||
|
||||
#ifndef JOEYLIB_INPUT_H
|
||||
#define JOEYLIB_INPUT_H
|
||||
|
||||
#include "platform.h"
|
||||
#include "types.h"
|
||||
|
||||
typedef enum {
|
||||
KEY_NONE = 0,
|
||||
|
||||
KEY_A, KEY_B, KEY_C, KEY_D, KEY_E, KEY_F, KEY_G, KEY_H, KEY_I,
|
||||
KEY_J, KEY_K, KEY_L, KEY_M, KEY_N, KEY_O, KEY_P, KEY_Q, KEY_R,
|
||||
KEY_S, KEY_T, KEY_U, KEY_V, KEY_W, KEY_X, KEY_Y, KEY_Z,
|
||||
|
||||
KEY_0, KEY_1, KEY_2, KEY_3, KEY_4,
|
||||
KEY_5, KEY_6, KEY_7, KEY_8, KEY_9,
|
||||
|
||||
KEY_SPACE, KEY_ESCAPE, KEY_RETURN, KEY_TAB, KEY_BACKSPACE,
|
||||
|
||||
KEY_UP, KEY_DOWN, KEY_LEFT, KEY_RIGHT,
|
||||
|
||||
KEY_LSHIFT, KEY_RSHIFT, KEY_LCTRL, KEY_LALT,
|
||||
|
||||
KEY_F1, KEY_F2, KEY_F3, KEY_F4, KEY_F5,
|
||||
KEY_F6, KEY_F7, KEY_F8, KEY_F9, KEY_F10,
|
||||
|
||||
KEY_COUNT
|
||||
} JoeyKeyE;
|
||||
|
||||
void joeyInputPoll(void);
|
||||
bool joeyKeyDown(JoeyKeyE key);
|
||||
bool joeyKeyPressed(JoeyKeyE key);
|
||||
bool joeyKeyReleased(JoeyKeyE key);
|
||||
|
||||
#endif
|
||||
|
|
@ -13,5 +13,6 @@
|
|||
#include "palette.h"
|
||||
#include "draw.h"
|
||||
#include "present.h"
|
||||
#include "input.h"
|
||||
|
||||
#endif
|
||||
|
|
|
|||
|
|
@ -31,9 +31,11 @@ HELLO_SRC := $(EXAMPLES)/hello/hello.c
|
|||
HELLO_BIN := $(BINDIR)/Hello
|
||||
PATTERN_SRC := $(EXAMPLES)/pattern/pattern.c
|
||||
PATTERN_BIN := $(BINDIR)/Pattern
|
||||
KEYS_SRC := $(EXAMPLES)/keys/keys.c
|
||||
KEYS_BIN := $(BINDIR)/Keys
|
||||
|
||||
.PHONY: all amiga clean-amiga
|
||||
all amiga: $(LIB) $(HELLO_BIN) $(PATTERN_BIN)
|
||||
all amiga: $(LIB) $(HELLO_BIN) $(PATTERN_BIN) $(KEYS_BIN)
|
||||
|
||||
$(BUILD)/obj/core/%.o: $(SRC_CORE)/%.c
|
||||
@mkdir -p $(dir $@)
|
||||
|
|
@ -63,5 +65,9 @@ $(PATTERN_BIN): $(PATTERN_SRC) $(LIB)
|
|||
@mkdir -p $(dir $@)
|
||||
$(AMIGA_CC) $(CFLAGS) $< $(LIB) -o $@ $(LDFLAGS)
|
||||
|
||||
$(KEYS_BIN): $(KEYS_SRC) $(LIB)
|
||||
@mkdir -p $(dir $@)
|
||||
$(AMIGA_CC) $(CFLAGS) $< $(LIB) -o $@ $(LDFLAGS)
|
||||
|
||||
clean-amiga:
|
||||
rm -rf $(BUILD)
|
||||
|
|
|
|||
|
|
@ -27,9 +27,11 @@ HELLO_SRC := $(EXAMPLES)/hello/hello.c
|
|||
HELLO_BIN := $(BINDIR)/HELLO.PRG
|
||||
PATTERN_SRC := $(EXAMPLES)/pattern/pattern.c
|
||||
PATTERN_BIN := $(BINDIR)/PATTERN.PRG
|
||||
KEYS_SRC := $(EXAMPLES)/keys/keys.c
|
||||
KEYS_BIN := $(BINDIR)/KEYS.PRG
|
||||
|
||||
.PHONY: all atarist clean-atarist
|
||||
all atarist: $(LIB) $(HELLO_BIN) $(PATTERN_BIN)
|
||||
all atarist: $(LIB) $(HELLO_BIN) $(PATTERN_BIN) $(KEYS_BIN)
|
||||
|
||||
$(BUILD)/obj/core/%.o: $(SRC_CORE)/%.c
|
||||
@mkdir -p $(dir $@)
|
||||
|
|
@ -59,5 +61,9 @@ $(PATTERN_BIN): $(PATTERN_SRC) $(LIB)
|
|||
@mkdir -p $(dir $@)
|
||||
$(ST_CC) $(CFLAGS) $< $(LIB) -o $@ $(LDFLAGS)
|
||||
|
||||
$(KEYS_BIN): $(KEYS_SRC) $(LIB)
|
||||
@mkdir -p $(dir $@)
|
||||
$(ST_CC) $(CFLAGS) $< $(LIB) -o $@ $(LDFLAGS)
|
||||
|
||||
clean-atarist:
|
||||
rm -rf $(BUILD)
|
||||
|
|
|
|||
|
|
@ -25,9 +25,11 @@ HELLO_SRC := $(EXAMPLES)/hello/hello.c
|
|||
HELLO_BIN := $(BINDIR)/HELLO.EXE
|
||||
PATTERN_SRC := $(EXAMPLES)/pattern/pattern.c
|
||||
PATTERN_BIN := $(BINDIR)/PATTERN.EXE
|
||||
KEYS_SRC := $(EXAMPLES)/keys/keys.c
|
||||
KEYS_BIN := $(BINDIR)/KEYS.EXE
|
||||
|
||||
.PHONY: all dos clean-dos
|
||||
all dos: $(LIB) $(HELLO_BIN) $(PATTERN_BIN)
|
||||
all dos: $(LIB) $(HELLO_BIN) $(PATTERN_BIN) $(KEYS_BIN)
|
||||
|
||||
$(BUILD)/obj/core/%.o: $(SRC_CORE)/%.c
|
||||
@mkdir -p $(dir $@)
|
||||
|
|
@ -55,5 +57,10 @@ $(PATTERN_BIN): $(PATTERN_SRC) $(LIB)
|
|||
$(DOS_CC) $(CFLAGS) $< $(LIB) -o $@
|
||||
$(DOS_EMBED_DPMI) $@
|
||||
|
||||
$(KEYS_BIN): $(KEYS_SRC) $(LIB)
|
||||
@mkdir -p $(dir $@)
|
||||
$(DOS_CC) $(CFLAGS) $< $(LIB) -o $@
|
||||
$(DOS_EMBED_DPMI) $@
|
||||
|
||||
clean-dos:
|
||||
rm -rf $(BUILD)
|
||||
|
|
|
|||
15
make/iigs.mk
15
make/iigs.mk
|
|
@ -20,6 +20,8 @@ HELLO_SRC := $(EXAMPLES)/hello/hello.c
|
|||
HELLO_BIN := $(BINDIR)/HELLO
|
||||
PATTERN_SRC := $(EXAMPLES)/pattern/pattern.c
|
||||
PATTERN_BIN := $(BINDIR)/PATTERN
|
||||
KEYS_SRC := $(EXAMPLES)/keys/keys.c
|
||||
KEYS_BIN := $(BINDIR)/KEYS
|
||||
DISK_IMG := $(BINDIR)/joey.2mg
|
||||
|
||||
IIGS_PACKAGE := $(REPO_DIR)/toolchains/iigs/package-disk.sh
|
||||
|
|
@ -31,7 +33,7 @@ IIX_INCLUDES := \
|
|||
-I $(SRC_CORE)
|
||||
|
||||
.PHONY: all iigs iigs-disk clean-iigs
|
||||
all iigs: $(HELLO_BIN) $(PATTERN_BIN)
|
||||
all iigs: $(HELLO_BIN) $(PATTERN_BIN) $(KEYS_BIN)
|
||||
|
||||
# iix-build.sh takes MAIN.c first, then EXTRA sources (compiled with
|
||||
# #pragma noroot). The example source supplies main(); libjoey sources
|
||||
|
|
@ -48,13 +50,18 @@ $(PATTERN_BIN): $(PATTERN_SRC) $(LIB_SRCS) $(IIGS_BUILD)
|
|||
$(IIGS_BUILD) $(IIX_INCLUDES) -o $@ $(PATTERN_SRC) $(LIB_SRCS)
|
||||
$(IIGS_IIX) chtyp -t S16 $@
|
||||
|
||||
# Assemble an 800KB ProDOS 2img containing both examples, ready to
|
||||
$(KEYS_BIN): $(KEYS_SRC) $(LIB_SRCS) $(IIGS_BUILD)
|
||||
@mkdir -p $(dir $@)
|
||||
$(IIGS_BUILD) $(IIX_INCLUDES) -o $@ $(KEYS_SRC) $(LIB_SRCS)
|
||||
$(IIGS_IIX) chtyp -t S16 $@
|
||||
|
||||
# Assemble an 800KB ProDOS 2img containing the examples, ready to
|
||||
# mount in GSplus alongside a GS/OS boot volume.
|
||||
iigs-disk: $(DISK_IMG)
|
||||
|
||||
$(DISK_IMG): $(HELLO_BIN) $(PATTERN_BIN) $(IIGS_PACKAGE)
|
||||
$(DISK_IMG): $(HELLO_BIN) $(PATTERN_BIN) $(KEYS_BIN) $(IIGS_PACKAGE)
|
||||
@mkdir -p $(dir $@)
|
||||
$(IIGS_PACKAGE) $@ $(HELLO_BIN) $(PATTERN_BIN)
|
||||
$(IIGS_PACKAGE) $@ $(HELLO_BIN) $(PATTERN_BIN) $(KEYS_BIN)
|
||||
|
||||
clean-iigs:
|
||||
rm -rf $(BUILD)
|
||||
|
|
|
|||
|
|
@ -1,6 +1,5 @@
|
|||
#!/usr/bin/env bash
|
||||
# Launch the built Amiga example in FS-UAE. Defaults to PATTERN; pass
|
||||
# "hello" to run HELLO instead.
|
||||
# Launch the built Amiga example in FS-UAE. Defaults to PATTERN.
|
||||
#
|
||||
# Kickstart and Workbench:
|
||||
# - If toolchains/emulators/support/kickstart.rom is present, it is
|
||||
|
|
@ -18,6 +17,7 @@
|
|||
#
|
||||
# scripts/run-amiga.sh # runs Pattern
|
||||
# scripts/run-amiga.sh hello # runs Hello
|
||||
# scripts/run-amiga.sh keys # runs Keys
|
||||
|
||||
set -euo pipefail
|
||||
|
||||
|
|
@ -29,7 +29,8 @@ support=$repo/toolchains/emulators/support
|
|||
case $prog in
|
||||
hello) file=Hello ;;
|
||||
pattern) file=Pattern ;;
|
||||
*) echo "usage: $0 [hello|pattern]" >&2; exit 2 ;;
|
||||
keys) file=Keys ;;
|
||||
*) echo "usage: $0 [hello|pattern|keys]" >&2; exit 2 ;;
|
||||
esac
|
||||
|
||||
if [[ ! -f "$bin_dir/$file" ]]; then
|
||||
|
|
@ -54,6 +55,7 @@ trap 'mkdir -p "$dump_keep"; cp "$work"/*.txt "$dump_keep"/ 2>/dev/null; rm -rf
|
|||
mkdir -p "$work/s"
|
||||
cp "$bin_dir/Hello" "$work/" 2>/dev/null || true
|
||||
cp "$bin_dir/Pattern" "$work/" 2>/dev/null || true
|
||||
cp "$bin_dir/Keys" "$work/" 2>/dev/null || true
|
||||
# ':' prefix anchors to the root of the current volume; otherwise
|
||||
# AmigaDOS looks in C: and the command is not found.
|
||||
echo ":$file" > "$work/s/startup-sequence"
|
||||
|
|
|
|||
|
|
@ -1,12 +1,12 @@
|
|||
#!/usr/bin/env bash
|
||||
# Launch the built Atari ST example in Hatari. Defaults to PATTERN;
|
||||
# pass "hello" to run HELLO instead. Hatari autostarts the .PRG via
|
||||
# the --auto flag; EmuTOS (toolchains/emulators/support/emutos-512k.img)
|
||||
# provides the TOS ROM since the Ubuntu hatari package does not bundle
|
||||
# one.
|
||||
# Launch the built Atari ST example in Hatari. Defaults to PATTERN.
|
||||
# Hatari autostarts the .PRG via the --auto flag; EmuTOS
|
||||
# (toolchains/emulators/support/emutos-512k.img) provides the TOS ROM
|
||||
# since the Ubuntu hatari package does not bundle one.
|
||||
#
|
||||
# scripts/run-atarist.sh # runs PATTERN.PRG
|
||||
# scripts/run-atarist.sh hello # runs HELLO.PRG
|
||||
# scripts/run-atarist.sh keys # runs KEYS.PRG
|
||||
|
||||
set -euo pipefail
|
||||
|
||||
|
|
@ -17,7 +17,8 @@ bin_dir=$repo/build/atarist/bin
|
|||
case $prog in
|
||||
hello) file=HELLO.PRG ;;
|
||||
pattern) file=PATTERN.PRG ;;
|
||||
*) echo "usage: $0 [hello|pattern]" >&2; exit 2 ;;
|
||||
keys) file=KEYS.PRG ;;
|
||||
*) echo "usage: $0 [hello|pattern|keys]" >&2; exit 2 ;;
|
||||
esac
|
||||
|
||||
tos=$repo/toolchains/emulators/support/emutos-512k.img
|
||||
|
|
|
|||
|
|
@ -1,9 +1,9 @@
|
|||
#!/usr/bin/env bash
|
||||
# Launch the built DOS example in DOSBox. Defaults to PATTERN; pass
|
||||
# "hello" to run HELLO instead.
|
||||
# Launch the built DOS example in DOSBox. Defaults to PATTERN.
|
||||
#
|
||||
# scripts/run-dos.sh # runs PATTERN
|
||||
# scripts/run-dos.sh hello # runs HELLO
|
||||
# scripts/run-dos.sh keys # runs KEYS
|
||||
|
||||
set -euo pipefail
|
||||
|
||||
|
|
@ -14,7 +14,8 @@ bin_dir=$repo/build/dos/bin
|
|||
case $prog in
|
||||
hello) file=HELLO.EXE ;;
|
||||
pattern) file=PATTERN.EXE ;;
|
||||
*) echo "usage: $0 [hello|pattern]" >&2; exit 2 ;;
|
||||
keys) file=KEYS.EXE ;;
|
||||
*) echo "usage: $0 [hello|pattern|keys]" >&2; exit 2 ;;
|
||||
esac
|
||||
|
||||
if [[ ! -f "$bin_dir/$file" ]]; then
|
||||
|
|
|
|||
|
|
@ -2,15 +2,16 @@
|
|||
# Launch the built Apple IIgs examples in GSplus. GSplus is booted from
|
||||
# a GS/OS 6.0.4 System disk (toolchains/emulators/support/gsos-system.po)
|
||||
# with joey.2mg mounted as the data disk on slot 5 drive 2. The user
|
||||
# navigates to the JOEYLIB volume in Finder and double-clicks HELLO or
|
||||
# PATTERN to run it.
|
||||
# navigates to the JOEYLIB volume in Finder and double-clicks the
|
||||
# example to run it.
|
||||
#
|
||||
# Unlike the other emulators, GS/OS does not auto-run on boot -- it
|
||||
# drops to Finder. Pass "hello" or "pattern" to print a reminder of
|
||||
# which example to launch.
|
||||
# drops to Finder. The argument just prints a reminder of which
|
||||
# example to launch.
|
||||
#
|
||||
# scripts/run-iigs.sh # boots and waits for user (Pattern hint)
|
||||
# scripts/run-iigs.sh hello # same, but hints to click HELLO
|
||||
# scripts/run-iigs.sh # boots (Pattern hint)
|
||||
# scripts/run-iigs.sh hello # boots, hints HELLO
|
||||
# scripts/run-iigs.sh keys # boots, hints KEYS
|
||||
|
||||
set -euo pipefail
|
||||
|
||||
|
|
@ -21,19 +22,20 @@ gsplus=$repo/toolchains/emulators/gsplus/bin/gsplus
|
|||
rom=$repo/toolchains/emulators/support/apple-iigs.rom
|
||||
sys_disk=$repo/toolchains/emulators/support/gsos-system.po
|
||||
data_disk=$repo/build/iigs/bin/joey.2mg
|
||||
null_c600=$repo/toolchains/emulators/support/iigs-null-c600.rom
|
||||
|
||||
case $prog in
|
||||
hello|pattern) ;;
|
||||
*) echo "usage: $0 [hello|pattern]" >&2; exit 2 ;;
|
||||
hello|pattern|keys) ;;
|
||||
*) echo "usage: $0 [hello|pattern|keys]" >&2; exit 2 ;;
|
||||
esac
|
||||
|
||||
for f in "$gsplus" "$rom" "$sys_disk" "$data_disk"; do
|
||||
for f in "$gsplus" "$rom" "$sys_disk" "$data_disk" "$null_c600"; do
|
||||
if [[ ! -f $f ]]; then
|
||||
echo "missing: $f" >&2
|
||||
if [[ $f == "$data_disk" ]]; then
|
||||
echo "run 'make iigs-disk' to build it." >&2
|
||||
else
|
||||
echo "run ./toolchains/install.sh (ROM/system disk should have been staged)." >&2
|
||||
echo "run ./toolchains/install.sh (support files should have been staged)." >&2
|
||||
fi
|
||||
exit 1
|
||||
fi
|
||||
|
|
@ -47,6 +49,15 @@ trap 'rm -rf "$work"' EXIT
|
|||
cp "$sys_disk" "$work/boot.po"
|
||||
cp "$data_disk" "$work/joey.2mg"
|
||||
|
||||
# Stage the null slot-6 PROM as c600.rom in the work dir. GSplus loads
|
||||
# any file named c600.rom in its search path (cwd first) as the slot 6
|
||||
# ROM card, overriding the built-in Disk II firmware. The staged image
|
||||
# is 256 bytes with a leading RTS and no Pascal 1.1 firmware signature,
|
||||
# so the IIgs boot ROM's slot scan skips slot 6 and the two empty 5.25
|
||||
# drives never get probed. Source: toolchains/install.sh's
|
||||
# install_support_iigs_null_c600.
|
||||
cp "$null_c600" "$work/c600.rom"
|
||||
|
||||
target=$(echo "$prog" | tr '[:lower:]' '[:upper:]')
|
||||
cat <<EOF
|
||||
GSplus launching GS/OS 6.0.4.
|
||||
|
|
|
|||
|
|
@ -10,6 +10,7 @@
|
|||
#define JOEYLIB_HAL_H
|
||||
|
||||
#include "joey/core.h"
|
||||
#include "joey/input.h"
|
||||
#include "joey/surface.h"
|
||||
|
||||
// Per-port one-shot initialization. Called from joeyInit after config
|
||||
|
|
@ -34,4 +35,14 @@ void halPresentRect(const SurfaceT *src, int16_t x, int16_t y, uint16_t w, uint1
|
|||
// HAL failure, or NULL if none. Ports may return NULL always.
|
||||
const char *halLastError(void);
|
||||
|
||||
// Input: per-port keyboard setup, polling, and teardown.
|
||||
// halInputInit is called at the end of joeyInit; halInputShutdown
|
||||
// from joeyShutdown before halShutdown. halInputPoll refreshes the
|
||||
// core-owned key-state array (declared in core/inputInternal.h) --
|
||||
// the port writes true into gKeyState[key] for held keys. Keys the
|
||||
// port does not recognize simply stay zero.
|
||||
void halInputInit(void);
|
||||
void halInputShutdown(void);
|
||||
void halInputPoll(void);
|
||||
|
||||
#endif
|
||||
|
|
|
|||
|
|
@ -63,6 +63,8 @@ bool joeyInit(const JoeyConfigT *config) {
|
|||
return false;
|
||||
}
|
||||
|
||||
halInputInit();
|
||||
|
||||
gInitialized = true;
|
||||
return true;
|
||||
}
|
||||
|
|
@ -82,6 +84,7 @@ void joeyShutdown(void) {
|
|||
if (!gInitialized) {
|
||||
return;
|
||||
}
|
||||
halInputShutdown();
|
||||
halShutdown();
|
||||
surfaceFreeScreen();
|
||||
gInitialized = false;
|
||||
|
|
|
|||
43
src/core/input.c
Normal file
43
src/core/input.c
Normal file
|
|
@ -0,0 +1,43 @@
|
|||
// Public input API. Maintains two key-state buffers: the current
|
||||
// state populated by the port's halInputPoll, and the previous
|
||||
// state captured just before polling so rising/falling edges can
|
||||
// be computed without the port having to track them.
|
||||
|
||||
#include <string.h>
|
||||
|
||||
#include "joey/input.h"
|
||||
#include "hal.h"
|
||||
#include "inputInternal.h"
|
||||
|
||||
bool gKeyState[KEY_COUNT];
|
||||
bool gKeyPrev [KEY_COUNT];
|
||||
|
||||
|
||||
void joeyInputPoll(void) {
|
||||
memcpy(gKeyPrev, gKeyState, sizeof(gKeyState));
|
||||
halInputPoll();
|
||||
}
|
||||
|
||||
|
||||
bool joeyKeyDown(JoeyKeyE key) {
|
||||
if (key <= KEY_NONE || key >= KEY_COUNT) {
|
||||
return false;
|
||||
}
|
||||
return gKeyState[key];
|
||||
}
|
||||
|
||||
|
||||
bool joeyKeyPressed(JoeyKeyE key) {
|
||||
if (key <= KEY_NONE || key >= KEY_COUNT) {
|
||||
return false;
|
||||
}
|
||||
return gKeyState[key] && !gKeyPrev[key];
|
||||
}
|
||||
|
||||
|
||||
bool joeyKeyReleased(JoeyKeyE key) {
|
||||
if (key <= KEY_NONE || key >= KEY_COUNT) {
|
||||
return false;
|
||||
}
|
||||
return !gKeyState[key] && gKeyPrev[key];
|
||||
}
|
||||
17
src/core/inputInternal.h
Normal file
17
src/core/inputInternal.h
Normal file
|
|
@ -0,0 +1,17 @@
|
|||
// Internal input state shared between core and per-port HAL.
|
||||
//
|
||||
// Per-port halInputPoll() writes directly into gKeyState[]: set
|
||||
// entries to true for keys currently held, false for released keys.
|
||||
// The core compares gKeyState to gKeyPrev each joeyInputPoll to
|
||||
// derive edge events.
|
||||
|
||||
#ifndef JOEYLIB_INPUT_INTERNAL_H
|
||||
#define JOEYLIB_INPUT_INTERNAL_H
|
||||
|
||||
#include "joey/input.h"
|
||||
#include "joey/types.h"
|
||||
|
||||
extern bool gKeyState[KEY_COUNT];
|
||||
extern bool gKeyPrev [KEY_COUNT];
|
||||
|
||||
#endif
|
||||
|
|
@ -9,8 +9,10 @@
|
|||
// * Build a user copper list that WAITs for each display scanline
|
||||
// and MOVEs the 16 color registers with that line's palette.
|
||||
// * Install it via ViewPort.UCopIns + MakeScreen + RethinkDisplay.
|
||||
// * Rebuild on every halPresent since SCB and palettes may have
|
||||
// changed anywhere.
|
||||
// * Rebuild only when SCB or palette state differs from the last
|
||||
// presented frame (cached in gCachedScb / gCachedPalette). On
|
||||
// clean frames (typical game loop, where only pixel bytes change)
|
||||
// we skip AllocMem + MrgCop + LoadView + WaitTOF entirely.
|
||||
//
|
||||
// Deferred:
|
||||
// * Blitter-assisted c2p for speed on A500.
|
||||
|
|
@ -54,14 +56,28 @@ static void c2pRange(const SurfaceT *src, int16_t y0, int16_t y1)
|
|||
static void dumpCopperList(void);
|
||||
static void installCopperList(void);
|
||||
static void uploadFirstBandPalette(const SurfaceT *src);
|
||||
static void updateCopperIfNeeded(const SurfaceT *src);
|
||||
|
||||
// ----- Module state -----
|
||||
|
||||
static struct Screen *gScreen = NULL;
|
||||
struct Screen *gScreen = NULL; // shared with input.c
|
||||
static struct BitMap *gBitMap = NULL;
|
||||
static UBYTE *gPlanes[AMIGA_BITPLANES];
|
||||
static struct UCopList *gNewUCL = NULL; // built but not yet installed
|
||||
|
||||
// Cached SCB + palettes from the last present. halPresent* only needs
|
||||
// to rebuild/install the copper list when SCB assignments or palette
|
||||
// RGB values differ from what is already on screen; pure pixel updates
|
||||
// (which dominate a typical game loop and every frame of the keys
|
||||
// demo after the initial paint) leave both alone. MrgCop + LoadView +
|
||||
// WaitTOF is hundreds of milliseconds on a 7 MHz 68000, so skipping
|
||||
// them on clean frames is a major win.
|
||||
static uint8_t gCachedScb [SURFACE_HEIGHT];
|
||||
static uint16_t gCachedPalette[SURFACE_PALETTE_COUNT][SURFACE_COLORS_PER_PALETTE];
|
||||
static bool gCacheValid = false;
|
||||
|
||||
static bool paletteOrScbChanged(const SurfaceT *src);
|
||||
|
||||
// ----- Internal helpers (alphabetical) -----
|
||||
|
||||
// Convert a range of chunky scanlines [y0, y1) to Amiga planar.
|
||||
|
|
@ -299,6 +315,39 @@ static void dumpCopperList(void) {
|
|||
}
|
||||
|
||||
|
||||
// Returns true if the SCB table or palette RGB values differ from the
|
||||
// last presented frame, or if no frame has been presented yet.
|
||||
static bool paletteOrScbChanged(const SurfaceT *src) {
|
||||
if (!gCacheValid) {
|
||||
return true;
|
||||
}
|
||||
if (memcmp(gCachedScb, src->scb, sizeof(gCachedScb)) != 0) {
|
||||
return true;
|
||||
}
|
||||
if (memcmp(gCachedPalette, src->palette, sizeof(gCachedPalette)) != 0) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
// Rebuild and install the user copper list only if the palette/SCB
|
||||
// state visible to the display differs from what the surface carries
|
||||
// now. On clean frames we skip the AllocMem + MrgCop + LoadView +
|
||||
// WaitTOF chain entirely.
|
||||
static void updateCopperIfNeeded(const SurfaceT *src) {
|
||||
if (!paletteOrScbChanged(src)) {
|
||||
return;
|
||||
}
|
||||
uploadFirstBandPalette(src);
|
||||
buildCopperList(src);
|
||||
installCopperList();
|
||||
memcpy(gCachedScb, src->scb, sizeof(gCachedScb));
|
||||
memcpy(gCachedPalette, src->palette, sizeof(gCachedPalette));
|
||||
gCacheValid = true;
|
||||
}
|
||||
|
||||
|
||||
// Load the first band's palette into the screen's ColorMap so the
|
||||
// Intuition-generated frame-start copper writes those values on each
|
||||
// frame. This acts as a safety net: even if our user copper list does
|
||||
|
|
@ -367,10 +416,7 @@ void halPresent(const SurfaceT *src) {
|
|||
if (src == NULL || gScreen == NULL) {
|
||||
return;
|
||||
}
|
||||
uploadFirstBandPalette(src);
|
||||
buildCopperList(src);
|
||||
installCopperList();
|
||||
dumpCopperList();
|
||||
updateCopperIfNeeded(src);
|
||||
c2pRange(src, 0, SURFACE_HEIGHT);
|
||||
}
|
||||
|
||||
|
|
@ -382,11 +428,7 @@ void halPresentRect(const SurfaceT *src, int16_t x, int16_t y, uint16_t w, uint1
|
|||
if (src == NULL || gScreen == NULL) {
|
||||
return;
|
||||
}
|
||||
// Whole-scanline c2p for M2; rebuild the entire copper list since
|
||||
// SCB entries outside the rect may have changed too.
|
||||
uploadFirstBandPalette(src);
|
||||
buildCopperList(src);
|
||||
installCopperList();
|
||||
updateCopperIfNeeded(src);
|
||||
c2pRange(src, y, y + (int16_t)h);
|
||||
}
|
||||
|
||||
|
|
|
|||
173
src/port/amiga/input.c
Normal file
173
src/port/amiga/input.c
Normal file
|
|
@ -0,0 +1,173 @@
|
|||
// Amiga keyboard input: opens a backdrop borderless window on the
|
||||
// JoeyLib CUSTOMSCREEN to receive IDCMP_RAWKEY events, then drains
|
||||
// those messages each joeyInputPoll to update gKeyState.
|
||||
//
|
||||
// RAWKEY is the lowest-level IDCMP event Intuition exposes: ie_Code
|
||||
// carries the Amiga raw key code (press in 0x00..0x7F, release with
|
||||
// bit 7 set), which we map straight to JoeyKeyE entries. The backdrop
|
||||
// borderless window covers the whole screen without drawing over our
|
||||
// planar bitmap (c2p writes to Screen->BitMap->Planes directly), and
|
||||
// WFLG_ACTIVATE ensures keyboard focus lands on us from the start.
|
||||
|
||||
#include <string.h>
|
||||
|
||||
#include <exec/types.h>
|
||||
#include <exec/ports.h>
|
||||
#include <intuition/intuition.h>
|
||||
#include <intuition/screens.h>
|
||||
#include <devices/inputevent.h>
|
||||
|
||||
#include <proto/exec.h>
|
||||
#include <proto/intuition.h>
|
||||
|
||||
#include "hal.h"
|
||||
#include "inputInternal.h"
|
||||
|
||||
// ----- Constants -----
|
||||
|
||||
#define AMIGA_KEY_TABLE_SIZE 128
|
||||
#define AMIGA_KEY_RELEASE_BIT 0x80
|
||||
#define AMIGA_KEY_CODE_MASK 0x7F
|
||||
|
||||
// ----- External from hal.c -----
|
||||
|
||||
extern struct Screen *gScreen;
|
||||
|
||||
// ----- Prototypes -----
|
||||
|
||||
static void drainKeyMessages(void);
|
||||
|
||||
// ----- Module state -----
|
||||
|
||||
// Amiga raw-key code -> JoeyKeyE. Codes are layout-independent; the
|
||||
// OS has a separate keymap for producing characters.
|
||||
static const uint8_t gRawKeyToKey[AMIGA_KEY_TABLE_SIZE] = {
|
||||
[0x01] = KEY_1,
|
||||
[0x02] = KEY_2,
|
||||
[0x03] = KEY_3,
|
||||
[0x04] = KEY_4,
|
||||
[0x05] = KEY_5,
|
||||
[0x06] = KEY_6,
|
||||
[0x07] = KEY_7,
|
||||
[0x08] = KEY_8,
|
||||
[0x09] = KEY_9,
|
||||
[0x0A] = KEY_0,
|
||||
[0x10] = KEY_Q,
|
||||
[0x11] = KEY_W,
|
||||
[0x12] = KEY_E,
|
||||
[0x13] = KEY_R,
|
||||
[0x14] = KEY_T,
|
||||
[0x15] = KEY_Y,
|
||||
[0x16] = KEY_U,
|
||||
[0x17] = KEY_I,
|
||||
[0x18] = KEY_O,
|
||||
[0x19] = KEY_P,
|
||||
[0x20] = KEY_A,
|
||||
[0x21] = KEY_S,
|
||||
[0x22] = KEY_D,
|
||||
[0x23] = KEY_F,
|
||||
[0x24] = KEY_G,
|
||||
[0x25] = KEY_H,
|
||||
[0x26] = KEY_J,
|
||||
[0x27] = KEY_K,
|
||||
[0x28] = KEY_L,
|
||||
[0x31] = KEY_Z,
|
||||
[0x32] = KEY_X,
|
||||
[0x33] = KEY_C,
|
||||
[0x34] = KEY_V,
|
||||
[0x35] = KEY_B,
|
||||
[0x36] = KEY_N,
|
||||
[0x37] = KEY_M,
|
||||
[0x40] = KEY_SPACE,
|
||||
[0x41] = KEY_BACKSPACE,
|
||||
[0x42] = KEY_TAB,
|
||||
[0x44] = KEY_RETURN,
|
||||
[0x45] = KEY_ESCAPE,
|
||||
[0x4C] = KEY_UP,
|
||||
[0x4D] = KEY_DOWN,
|
||||
[0x4E] = KEY_RIGHT,
|
||||
[0x4F] = KEY_LEFT,
|
||||
[0x50] = KEY_F1,
|
||||
[0x51] = KEY_F2,
|
||||
[0x52] = KEY_F3,
|
||||
[0x53] = KEY_F4,
|
||||
[0x54] = KEY_F5,
|
||||
[0x55] = KEY_F6,
|
||||
[0x56] = KEY_F7,
|
||||
[0x57] = KEY_F8,
|
||||
[0x58] = KEY_F9,
|
||||
[0x59] = KEY_F10,
|
||||
[0x60] = KEY_LSHIFT,
|
||||
[0x61] = KEY_RSHIFT,
|
||||
[0x63] = KEY_LCTRL,
|
||||
[0x64] = KEY_LALT,
|
||||
};
|
||||
|
||||
static struct Window *gWindow = NULL;
|
||||
|
||||
// ----- Internal helpers -----
|
||||
|
||||
static void drainKeyMessages(void) {
|
||||
struct IntuiMessage *msg;
|
||||
UWORD rawCode;
|
||||
uint8_t code;
|
||||
uint8_t key;
|
||||
bool isRelease;
|
||||
|
||||
if (gWindow == NULL) {
|
||||
return;
|
||||
}
|
||||
while ((msg = (struct IntuiMessage *)GetMsg(gWindow->UserPort)) != NULL) {
|
||||
if (msg->Class == IDCMP_RAWKEY) {
|
||||
rawCode = msg->Code;
|
||||
isRelease = (rawCode & AMIGA_KEY_RELEASE_BIT) != 0;
|
||||
code = (uint8_t)(rawCode & AMIGA_KEY_CODE_MASK);
|
||||
key = gRawKeyToKey[code];
|
||||
if (key != KEY_NONE) {
|
||||
gKeyState[key] = !isRelease;
|
||||
}
|
||||
}
|
||||
ReplyMsg((struct Message *)msg);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// ----- HAL API (alphabetical) -----
|
||||
|
||||
void halInputInit(void) {
|
||||
memset(gKeyState, 0, sizeof(gKeyState));
|
||||
memset(gKeyPrev, 0, sizeof(gKeyPrev));
|
||||
|
||||
if (gScreen == NULL) {
|
||||
return;
|
||||
}
|
||||
|
||||
gWindow = OpenWindowTags(NULL,
|
||||
(ULONG)WA_CustomScreen, (ULONG)gScreen,
|
||||
(ULONG)WA_Left, (ULONG)0,
|
||||
(ULONG)WA_Top, (ULONG)0,
|
||||
(ULONG)WA_Width, (ULONG)gScreen->Width,
|
||||
(ULONG)WA_Height, (ULONG)gScreen->Height,
|
||||
(ULONG)WA_Flags, (ULONG)(WFLG_BACKDROP
|
||||
| WFLG_BORDERLESS
|
||||
| WFLG_ACTIVATE
|
||||
| WFLG_RMBTRAP
|
||||
| WFLG_NOCAREREFRESH),
|
||||
(ULONG)WA_IDCMP, (ULONG)IDCMP_RAWKEY,
|
||||
TAG_DONE);
|
||||
}
|
||||
|
||||
|
||||
void halInputPoll(void) {
|
||||
drainKeyMessages();
|
||||
}
|
||||
|
||||
|
||||
void halInputShutdown(void) {
|
||||
if (gWindow == NULL) {
|
||||
return;
|
||||
}
|
||||
drainKeyMessages();
|
||||
CloseWindow(gWindow);
|
||||
gWindow = NULL;
|
||||
}
|
||||
|
|
@ -73,6 +73,8 @@ static long writePrevPaletteRegs(void);
|
|||
static __attribute__((interrupt_handler)) void timerBIsr(void);
|
||||
static __attribute__((interrupt_handler)) void vblIsr(void);
|
||||
static void buildTransitions(const SurfaceT *src);
|
||||
static bool paletteOrScbChanged(const SurfaceT *src);
|
||||
static void refreshPaletteStateIfNeeded(const SurfaceT *src);
|
||||
|
||||
// ----- Module state -----
|
||||
|
||||
|
|
@ -117,6 +119,16 @@ static volatile uint16_t gLastBandCount = 0;
|
|||
static void (*gOldVblVec)(void) = NULL;
|
||||
static void (*gOldTimerBVec)(void) = NULL;
|
||||
|
||||
// Cached SCB + palette from the last present. flattenScbPalettes runs
|
||||
// 200 * 16 quantize conversions and buildTransitions rescans the full
|
||||
// SCB; neither is cheap on a 7 MHz 68000. In the typical game loop
|
||||
// (and every frame of the keys demo after the initial paint) SCB and
|
||||
// palette never change, so caching and skipping those passes keeps
|
||||
// rect presents down to just the c2p work.
|
||||
static uint8_t gCachedScb [SURFACE_HEIGHT];
|
||||
static uint16_t gCachedPalette[SURFACE_PALETTE_COUNT][SURFACE_COLORS_PER_PALETTE];
|
||||
static bool gCacheValid = false;
|
||||
|
||||
// ----- Internal helpers (alphabetical) -----
|
||||
|
||||
// Convert 16 chunky pixels (8 bytes 4bpp packed) to 4 ST planar words.
|
||||
|
|
@ -219,6 +231,37 @@ static void flattenScbPalettes(const SurfaceT *src) {
|
|||
}
|
||||
|
||||
|
||||
// Returns true if SCB or palette values differ from the last present.
|
||||
static bool paletteOrScbChanged(const SurfaceT *src) {
|
||||
if (!gCacheValid) {
|
||||
return true;
|
||||
}
|
||||
if (memcmp(gCachedScb, src->scb, sizeof(gCachedScb)) != 0) {
|
||||
return true;
|
||||
}
|
||||
if (memcmp(gCachedPalette, src->palette, sizeof(gCachedPalette)) != 0) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
// Rebuild the per-line palette table and band-transition table only
|
||||
// when the SCB/palette state has actually changed. Both are hot -- the
|
||||
// flatten pass runs 3200 palette entries through quantization -- so
|
||||
// skipping them on clean frames dominates rect-present timing.
|
||||
static void refreshPaletteStateIfNeeded(const SurfaceT *src) {
|
||||
if (!paletteOrScbChanged(src)) {
|
||||
return;
|
||||
}
|
||||
flattenScbPalettes(src);
|
||||
buildTransitions(src);
|
||||
memcpy(gCachedScb, src->scb, sizeof(gCachedScb));
|
||||
memcpy(gCachedPalette, src->palette, sizeof(gCachedPalette));
|
||||
gCacheValid = true;
|
||||
}
|
||||
|
||||
|
||||
// 12-bit $0RGB to STF 9-bit palette register (drops the low bit of
|
||||
// each 4-bit channel).
|
||||
static uint16_t quantizeColorToSt(uint16_t orgb) {
|
||||
|
|
@ -437,8 +480,7 @@ void halPresent(const SurfaceT *src) {
|
|||
if (src == NULL || !gModeSet) {
|
||||
return;
|
||||
}
|
||||
flattenScbPalettes(src);
|
||||
buildTransitions(src);
|
||||
refreshPaletteStateIfNeeded(src);
|
||||
c2pRange(src, 0, SURFACE_HEIGHT);
|
||||
}
|
||||
|
||||
|
|
@ -450,8 +492,7 @@ void halPresentRect(const SurfaceT *src, int16_t x, int16_t y, uint16_t w, uint1
|
|||
if (src == NULL || !gModeSet) {
|
||||
return;
|
||||
}
|
||||
flattenScbPalettes(src);
|
||||
buildTransitions(src);
|
||||
refreshPaletteStateIfNeeded(src);
|
||||
c2pRange(src, y, y + (int16_t)h);
|
||||
}
|
||||
|
||||
|
|
|
|||
218
src/port/atarist/input.c
Normal file
218
src/port/atarist/input.c
Normal file
|
|
@ -0,0 +1,218 @@
|
|||
// Atari ST keyboard input: replaces the TOS ikbdsys vector in the
|
||||
// KBDVECS table so every byte from the IKBD ACIA comes through our
|
||||
// packet-aware handler.
|
||||
//
|
||||
// The IKBD protocol mixes keyboard scan codes with mouse/joy packets.
|
||||
// Bytes 0x00-0x72 (with optional 0x80 break bit -> 0x80..0xF2) are
|
||||
// keyboard scan codes; 0xF6-0xFF are packet headers that announce a
|
||||
// fixed number of trailing bytes. We track how many trailing packet
|
||||
// bytes to discard; everything else is treated as a key event.
|
||||
//
|
||||
// The ISR writes into a private gIsrState buffer; halInputPoll copies
|
||||
// it into gKeyState under a raised IPL so the snapshot is atomic
|
||||
// relative to the ACIA interrupt. This two-buffer split is what makes
|
||||
// joeyKeyPressed edge detection work -- joeyInputPoll snapshots
|
||||
// gKeyState into gKeyPrev *before* halInputPoll runs, so gKeyState
|
||||
// must only advance during halInputPoll, never at interrupt time.
|
||||
|
||||
#include <string.h>
|
||||
|
||||
#include <mint/osbind.h>
|
||||
#include <mint/ostruct.h>
|
||||
|
||||
#include "hal.h"
|
||||
#include "inputInternal.h"
|
||||
|
||||
// ----- Constants -----
|
||||
|
||||
// IKBD ACIA data register; reading also clears the ACIA's interrupt
|
||||
// so the MFP ACIA line drops.
|
||||
#define ST_ACIA_DATA ((volatile uint8_t *)0xFFFFFC02L)
|
||||
|
||||
#define SCAN_BREAK_BIT 0x80
|
||||
#define SCAN_CODE_MASK 0x7F
|
||||
#define SCAN_TABLE_SIZE 128
|
||||
|
||||
#define PKT_STATUS 0xF6 // + 7 bytes
|
||||
#define PKT_ABS_MOUSE 0xF7 // + 5 bytes
|
||||
#define PKT_REL_MOUSE_MIN 0xF8 // 0xF8..0xFB + 2 bytes
|
||||
#define PKT_REL_MOUSE_MAX 0xFB
|
||||
#define PKT_TIME 0xFC // + 6 bytes
|
||||
#define PKT_JOY_BOTH 0xFD // + 2 bytes
|
||||
#define PKT_JOY0 0xFE // + 1 byte
|
||||
#define PKT_JOY1 0xFF // + 1 byte
|
||||
|
||||
// ----- Prototypes -----
|
||||
|
||||
static long patchIkbdVector(void);
|
||||
static long restoreIkbdVector(void);
|
||||
static long ikbdHandler(void);
|
||||
|
||||
// ----- Module state -----
|
||||
|
||||
// ST IKBD scan codes. Identical encoding to the PC AT set-1 for most
|
||||
// keys, which is why the table mirrors the DOS scan map.
|
||||
static const uint8_t gScanToKey[SCAN_TABLE_SIZE] = {
|
||||
[0x01] = KEY_ESCAPE,
|
||||
[0x02] = KEY_1,
|
||||
[0x03] = KEY_2,
|
||||
[0x04] = KEY_3,
|
||||
[0x05] = KEY_4,
|
||||
[0x06] = KEY_5,
|
||||
[0x07] = KEY_6,
|
||||
[0x08] = KEY_7,
|
||||
[0x09] = KEY_8,
|
||||
[0x0A] = KEY_9,
|
||||
[0x0B] = KEY_0,
|
||||
[0x0E] = KEY_BACKSPACE,
|
||||
[0x0F] = KEY_TAB,
|
||||
[0x10] = KEY_Q,
|
||||
[0x11] = KEY_W,
|
||||
[0x12] = KEY_E,
|
||||
[0x13] = KEY_R,
|
||||
[0x14] = KEY_T,
|
||||
[0x15] = KEY_Y,
|
||||
[0x16] = KEY_U,
|
||||
[0x17] = KEY_I,
|
||||
[0x18] = KEY_O,
|
||||
[0x19] = KEY_P,
|
||||
[0x1C] = KEY_RETURN,
|
||||
[0x1D] = KEY_LCTRL,
|
||||
[0x1E] = KEY_A,
|
||||
[0x1F] = KEY_S,
|
||||
[0x20] = KEY_D,
|
||||
[0x21] = KEY_F,
|
||||
[0x22] = KEY_G,
|
||||
[0x23] = KEY_H,
|
||||
[0x24] = KEY_J,
|
||||
[0x25] = KEY_K,
|
||||
[0x26] = KEY_L,
|
||||
[0x2A] = KEY_LSHIFT,
|
||||
[0x2C] = KEY_Z,
|
||||
[0x2D] = KEY_X,
|
||||
[0x2E] = KEY_C,
|
||||
[0x2F] = KEY_V,
|
||||
[0x30] = KEY_B,
|
||||
[0x31] = KEY_N,
|
||||
[0x32] = KEY_M,
|
||||
[0x36] = KEY_RSHIFT,
|
||||
[0x38] = KEY_LALT,
|
||||
[0x39] = KEY_SPACE,
|
||||
[0x3B] = KEY_F1,
|
||||
[0x3C] = KEY_F2,
|
||||
[0x3D] = KEY_F3,
|
||||
[0x3E] = KEY_F4,
|
||||
[0x3F] = KEY_F5,
|
||||
[0x40] = KEY_F6,
|
||||
[0x41] = KEY_F7,
|
||||
[0x42] = KEY_F8,
|
||||
[0x43] = KEY_F9,
|
||||
[0x44] = KEY_F10,
|
||||
[0x48] = KEY_UP,
|
||||
[0x4B] = KEY_LEFT,
|
||||
[0x4D] = KEY_RIGHT,
|
||||
[0x50] = KEY_DOWN,
|
||||
};
|
||||
|
||||
static _KBDVECS *gKbdvecs = NULL;
|
||||
static long (*gOldIkbdsys)(void) = NULL;
|
||||
static volatile uint8_t gPacketRemaining = 0;
|
||||
static bool gHooked = false;
|
||||
static volatile bool gIsrState[KEY_COUNT];
|
||||
|
||||
// ----- Internal helpers -----
|
||||
|
||||
// Runs in MFP ACIA interrupt context. Reads one byte from the ACIA,
|
||||
// consumes trailing packet bytes, and maps key codes into gKeyState.
|
||||
static long ikbdHandler(void) {
|
||||
uint8_t byte;
|
||||
uint8_t code;
|
||||
uint8_t key;
|
||||
bool isBreak;
|
||||
|
||||
byte = *ST_ACIA_DATA;
|
||||
|
||||
if (gPacketRemaining != 0) {
|
||||
gPacketRemaining = (uint8_t)(gPacketRemaining - 1);
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (byte >= PKT_STATUS) {
|
||||
switch (byte) {
|
||||
case PKT_STATUS:
|
||||
gPacketRemaining = 7;
|
||||
break;
|
||||
case PKT_ABS_MOUSE:
|
||||
gPacketRemaining = 5;
|
||||
break;
|
||||
case PKT_TIME:
|
||||
gPacketRemaining = 6;
|
||||
break;
|
||||
case PKT_JOY_BOTH:
|
||||
gPacketRemaining = 2;
|
||||
break;
|
||||
case PKT_JOY0:
|
||||
case PKT_JOY1:
|
||||
gPacketRemaining = 1;
|
||||
break;
|
||||
default:
|
||||
if (byte >= PKT_REL_MOUSE_MIN && byte <= PKT_REL_MOUSE_MAX) {
|
||||
gPacketRemaining = 2;
|
||||
}
|
||||
break;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
isBreak = (byte & SCAN_BREAK_BIT) != 0;
|
||||
code = (uint8_t)(byte & SCAN_CODE_MASK);
|
||||
key = gScanToKey[code];
|
||||
if (key != KEY_NONE) {
|
||||
gIsrState[key] = !isBreak;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
static long patchIkbdVector(void) {
|
||||
gOldIkbdsys = gKbdvecs->ikbdsys;
|
||||
gKbdvecs->ikbdsys = ikbdHandler;
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
static long restoreIkbdVector(void) {
|
||||
gKbdvecs->ikbdsys = gOldIkbdsys;
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
// ----- HAL API (alphabetical) -----
|
||||
|
||||
void halInputInit(void) {
|
||||
memset(gKeyState, 0, sizeof(gKeyState));
|
||||
memset(gKeyPrev, 0, sizeof(gKeyPrev));
|
||||
memset((void *)gIsrState, 0, sizeof(gIsrState));
|
||||
|
||||
gKbdvecs = (_KBDVECS *)Kbdvbase();
|
||||
gPacketRemaining = 0;
|
||||
Supexec(patchIkbdVector);
|
||||
gHooked = true;
|
||||
}
|
||||
|
||||
|
||||
// The ACIA ISR only writes gIsrState; bytes land at ~100 Hz max so the
|
||||
// ~60-byte memcpy is essentially never racing a write. Worst case is a
|
||||
// single key lagging one frame -- well under perceptible.
|
||||
void halInputPoll(void) {
|
||||
memcpy(gKeyState, (const void *)gIsrState, sizeof(gKeyState));
|
||||
}
|
||||
|
||||
|
||||
void halInputShutdown(void) {
|
||||
if (!gHooked) {
|
||||
return;
|
||||
}
|
||||
Supexec(restoreIkbdVector);
|
||||
gHooked = false;
|
||||
}
|
||||
|
|
@ -31,12 +31,18 @@
|
|||
|
||||
static void expandAndWriteLine(const SurfaceT *src, int16_t y, int16_t x, uint16_t w, uint8_t *dst);
|
||||
static void uploadPalette(const SurfaceT *src);
|
||||
static void uploadPaletteIfNeeded(const SurfaceT *src);
|
||||
|
||||
// ----- Module state -----
|
||||
|
||||
static uint8_t *gVgaMem = NULL;
|
||||
static bool gNearEnabled = false;
|
||||
|
||||
// Cached palette from the last present. VGA DAC programming is ~768
|
||||
// outportb calls; skip it when the source palette is unchanged.
|
||||
static uint16_t gCachedPalette[SURFACE_PALETTE_COUNT][SURFACE_COLORS_PER_PALETTE];
|
||||
static bool gCacheValid = false;
|
||||
|
||||
// ----- Internal helpers (alphabetical) -----
|
||||
|
||||
static void expandAndWriteLine(const SurfaceT *src, int16_t y, int16_t x, uint16_t w, uint8_t *dst) {
|
||||
|
|
@ -82,6 +88,16 @@ static void uploadPalette(const SurfaceT *src) {
|
|||
}
|
||||
|
||||
|
||||
static void uploadPaletteIfNeeded(const SurfaceT *src) {
|
||||
if (gCacheValid && memcmp(gCachedPalette, src->palette, sizeof(gCachedPalette)) == 0) {
|
||||
return;
|
||||
}
|
||||
uploadPaletteIfNeeded(src);
|
||||
memcpy(gCachedPalette, src->palette, sizeof(gCachedPalette));
|
||||
gCacheValid = true;
|
||||
}
|
||||
|
||||
|
||||
// ----- HAL API (alphabetical) -----
|
||||
|
||||
bool halInit(const JoeyConfigT *config) {
|
||||
|
|
@ -113,7 +129,7 @@ void halPresent(const SurfaceT *src) {
|
|||
if (src == NULL || gVgaMem == NULL) {
|
||||
return;
|
||||
}
|
||||
uploadPalette(src);
|
||||
uploadPaletteIfNeeded(src);
|
||||
for (y = 0; y < SURFACE_HEIGHT; y++) {
|
||||
expandAndWriteLine(src, y, 0, SURFACE_WIDTH, &gVgaMem[y * VGA_STRIDE]);
|
||||
}
|
||||
|
|
@ -127,7 +143,7 @@ void halPresentRect(const SurfaceT *src, int16_t x, int16_t y, uint16_t w, uint1
|
|||
if (src == NULL || gVgaMem == NULL) {
|
||||
return;
|
||||
}
|
||||
uploadPalette(src);
|
||||
uploadPaletteIfNeeded(src);
|
||||
yEnd = y + (int16_t)h;
|
||||
for (py = y; py < yEnd; py++) {
|
||||
expandAndWriteLine(src, py, x, w, &gVgaMem[py * VGA_STRIDE]);
|
||||
|
|
|
|||
170
src/port/dos/input.c
Normal file
170
src/port/dos/input.c
Normal file
|
|
@ -0,0 +1,170 @@
|
|||
// DOS keyboard input: hooks INT 9 to capture AT set-1 scan codes from
|
||||
// port 0x60. The ISR reads the scan code, maps it to a JoeyKeyE, updates
|
||||
// a private gIsrState buffer, and sends EOI to the PIC. halInputPoll
|
||||
// snapshots gIsrState into gKeyState with interrupts disabled.
|
||||
//
|
||||
// The two-buffer split is required for joeyKeyPressed edge detection.
|
||||
// joeyInputPoll does memcpy(gKeyPrev, gKeyState) *before* halInputPoll
|
||||
// runs, so whatever gKeyState holds at that moment becomes gKeyPrev.
|
||||
// If the ISR wrote directly to gKeyState, press/release events that
|
||||
// happened between polls would already have landed there -- the memcpy
|
||||
// would copy the new value into gKeyPrev and edge detection would see
|
||||
// gKeyState == gKeyPrev every frame.
|
||||
//
|
||||
// Because DJGPP runs in protected mode with paging, the ISR code and
|
||||
// any data it touches must be locked or a page fault at interrupt time
|
||||
// will hang the machine.
|
||||
|
||||
#include <dpmi.h>
|
||||
#include <go32.h>
|
||||
#include <pc.h>
|
||||
#include <string.h>
|
||||
|
||||
#include "hal.h"
|
||||
#include "inputInternal.h"
|
||||
|
||||
// ----- Constants -----
|
||||
|
||||
#define KB_DATA_PORT 0x60
|
||||
#define PIC_CMD_PORT 0x20
|
||||
#define PIC_EOI 0x20
|
||||
#define SCAN_BREAK_BIT 0x80
|
||||
#define SCAN_CODE_MASK 0x7F
|
||||
#define SCAN_EXTENDED 0xE0
|
||||
#define SCAN_TABLE_SIZE 128
|
||||
#define ISR_LOCK_SIZE 4096
|
||||
|
||||
// ----- Prototypes -----
|
||||
|
||||
static void keyboardIsr(void);
|
||||
|
||||
// ----- Module state -----
|
||||
|
||||
static const uint8_t gScanToKey[SCAN_TABLE_SIZE] = {
|
||||
[0x01] = KEY_ESCAPE,
|
||||
[0x02] = KEY_1,
|
||||
[0x03] = KEY_2,
|
||||
[0x04] = KEY_3,
|
||||
[0x05] = KEY_4,
|
||||
[0x06] = KEY_5,
|
||||
[0x07] = KEY_6,
|
||||
[0x08] = KEY_7,
|
||||
[0x09] = KEY_8,
|
||||
[0x0A] = KEY_9,
|
||||
[0x0B] = KEY_0,
|
||||
[0x0E] = KEY_BACKSPACE,
|
||||
[0x0F] = KEY_TAB,
|
||||
[0x10] = KEY_Q,
|
||||
[0x11] = KEY_W,
|
||||
[0x12] = KEY_E,
|
||||
[0x13] = KEY_R,
|
||||
[0x14] = KEY_T,
|
||||
[0x15] = KEY_Y,
|
||||
[0x16] = KEY_U,
|
||||
[0x17] = KEY_I,
|
||||
[0x18] = KEY_O,
|
||||
[0x19] = KEY_P,
|
||||
[0x1C] = KEY_RETURN,
|
||||
[0x1D] = KEY_LCTRL,
|
||||
[0x1E] = KEY_A,
|
||||
[0x1F] = KEY_S,
|
||||
[0x20] = KEY_D,
|
||||
[0x21] = KEY_F,
|
||||
[0x22] = KEY_G,
|
||||
[0x23] = KEY_H,
|
||||
[0x24] = KEY_J,
|
||||
[0x25] = KEY_K,
|
||||
[0x26] = KEY_L,
|
||||
[0x2A] = KEY_LSHIFT,
|
||||
[0x2C] = KEY_Z,
|
||||
[0x2D] = KEY_X,
|
||||
[0x2E] = KEY_C,
|
||||
[0x2F] = KEY_V,
|
||||
[0x30] = KEY_B,
|
||||
[0x31] = KEY_N,
|
||||
[0x32] = KEY_M,
|
||||
[0x36] = KEY_RSHIFT,
|
||||
[0x38] = KEY_LALT,
|
||||
[0x39] = KEY_SPACE,
|
||||
[0x3B] = KEY_F1,
|
||||
[0x3C] = KEY_F2,
|
||||
[0x3D] = KEY_F3,
|
||||
[0x3E] = KEY_F4,
|
||||
[0x3F] = KEY_F5,
|
||||
[0x40] = KEY_F6,
|
||||
[0x41] = KEY_F7,
|
||||
[0x42] = KEY_F8,
|
||||
[0x43] = KEY_F9,
|
||||
[0x44] = KEY_F10,
|
||||
[0x48] = KEY_UP,
|
||||
[0x4B] = KEY_LEFT,
|
||||
[0x4D] = KEY_RIGHT,
|
||||
[0x50] = KEY_DOWN,
|
||||
};
|
||||
|
||||
static _go32_dpmi_seginfo gOldHandler;
|
||||
static _go32_dpmi_seginfo gNewHandler;
|
||||
static bool gHooked = false;
|
||||
static volatile bool gIsrState[KEY_COUNT];
|
||||
|
||||
// ----- Internal helpers -----
|
||||
|
||||
static void keyboardIsr(void) {
|
||||
uint8_t scan;
|
||||
uint8_t code;
|
||||
uint8_t key;
|
||||
bool isBreak;
|
||||
|
||||
scan = inportb(KB_DATA_PORT);
|
||||
|
||||
if (scan != SCAN_EXTENDED) {
|
||||
isBreak = (scan & SCAN_BREAK_BIT) != 0;
|
||||
code = (uint8_t)(scan & SCAN_CODE_MASK);
|
||||
key = gScanToKey[code];
|
||||
if (key != KEY_NONE) {
|
||||
gIsrState[key] = !isBreak;
|
||||
}
|
||||
}
|
||||
|
||||
outportb(PIC_CMD_PORT, PIC_EOI);
|
||||
}
|
||||
|
||||
|
||||
// ----- HAL API (alphabetical) -----
|
||||
|
||||
void halInputInit(void) {
|
||||
memset(gKeyState, 0, sizeof(gKeyState));
|
||||
memset(gKeyPrev, 0, sizeof(gKeyPrev));
|
||||
memset((void *)gIsrState, 0, sizeof(gIsrState));
|
||||
|
||||
_go32_dpmi_lock_code(keyboardIsr, ISR_LOCK_SIZE);
|
||||
_go32_dpmi_lock_data((void *)gScanToKey, sizeof(gScanToKey));
|
||||
_go32_dpmi_lock_data((void *)gIsrState, sizeof(gIsrState));
|
||||
|
||||
_go32_dpmi_get_protected_mode_interrupt_vector(9, &gOldHandler);
|
||||
|
||||
gNewHandler.pm_offset = (unsigned long)keyboardIsr;
|
||||
gNewHandler.pm_selector = _go32_my_cs();
|
||||
if (_go32_dpmi_allocate_iret_wrapper(&gNewHandler) != 0) {
|
||||
return;
|
||||
}
|
||||
_go32_dpmi_set_protected_mode_interrupt_vector(9, &gNewHandler);
|
||||
gHooked = true;
|
||||
}
|
||||
|
||||
|
||||
void halInputPoll(void) {
|
||||
disable();
|
||||
memcpy(gKeyState, (const void *)gIsrState, sizeof(gKeyState));
|
||||
enable();
|
||||
}
|
||||
|
||||
|
||||
void halInputShutdown(void) {
|
||||
if (!gHooked) {
|
||||
return;
|
||||
}
|
||||
_go32_dpmi_set_protected_mode_interrupt_vector(9, &gOldHandler);
|
||||
_go32_dpmi_free_iret_wrapper(&gNewHandler);
|
||||
gHooked = false;
|
||||
}
|
||||
161
src/port/iigs/input.c
Normal file
161
src/port/iigs/input.c
Normal file
|
|
@ -0,0 +1,161 @@
|
|||
// Apple IIgs keyboard input via the classic Apple II softswitches.
|
||||
//
|
||||
// The first cut used the Event Manager, but that requires EMStartUp
|
||||
// from a toolbox-aware program, and our S16 binary does not go through
|
||||
// the full ToolBox bring-up -- calling GetNextEvent uninitialized
|
||||
// corrupted KEGS' emulation state. Softswitches have no such
|
||||
// dependency: they are live memory-mapped hardware that the monitor
|
||||
// ROM, BASIC, and ProDOS have always relied on.
|
||||
//
|
||||
// Tradeoff: $C000 reports the *last* key pressed, not a per-key matrix.
|
||||
// Holding multiple non-modifier keys simultaneously cannot be observed;
|
||||
// the demo and any game using this port sees one typable key at a time,
|
||||
// plus live shift/ctrl/option state from the modifier register at
|
||||
// $C025. This matches what every Apple II game ever shipped does, and
|
||||
// it is enough for feature parity with the other platforms on typical
|
||||
// "press a key, act on it" flows.
|
||||
//
|
||||
// Held-key state is synthesized via a TTL counter: a fresh strobe on
|
||||
// $C000 refreshes the TTL; each halInputPoll decays it; when TTL hits
|
||||
// zero we assume the key was released. KEY_TTL is sized to cover the
|
||||
// typematic initial delay so that a held key does not flicker.
|
||||
|
||||
#include <string.h>
|
||||
|
||||
#include <types.h>
|
||||
|
||||
#include "hal.h"
|
||||
#include "inputInternal.h"
|
||||
|
||||
// ----- Hardware registers -----
|
||||
|
||||
#define IIGS_KBD ((volatile uint8_t *)0x00C000L)
|
||||
#define IIGS_KBDSTRB ((volatile uint8_t *)0x00C010L)
|
||||
#define IIGS_MODIFIERS ((volatile uint8_t *)0x00C025L)
|
||||
|
||||
#define KBD_STROBE_BIT 0x80
|
||||
#define KBD_ASCII_MASK 0x7F
|
||||
|
||||
// $C025 layout (IIgs Hardware Reference): bit 0 = shift, bit 1 = ctrl,
|
||||
// bit 6 = option (Closed-Apple), bit 7 = command (Open-Apple).
|
||||
#define MOD_SHIFT 0x01
|
||||
#define MOD_CONTROL 0x02
|
||||
#define MOD_OPTION 0x40
|
||||
|
||||
// Polls a key stays "down" after the last observed strobe. Covers the
|
||||
// typematic initial delay so a held key does not flicker off/on between
|
||||
// repeats.
|
||||
#define KEY_TTL 45
|
||||
|
||||
#define ASCII_TABLE_SIZE 128
|
||||
|
||||
// Apple II arrow-key ASCII conventions.
|
||||
#define ASCII_LEFT 0x08
|
||||
#define ASCII_RIGHT 0x15
|
||||
#define ASCII_UP 0x0B
|
||||
#define ASCII_DOWN 0x0A
|
||||
#define ASCII_RETURN 0x0D
|
||||
#define ASCII_TAB 0x09
|
||||
#define ASCII_ESCAPE 0x1B
|
||||
#define ASCII_DELETE 0x7F
|
||||
#define ASCII_SPACE 0x20
|
||||
|
||||
// ----- Prototypes -----
|
||||
|
||||
static void buildAsciiTable(void);
|
||||
static void readModifierKeys(void);
|
||||
|
||||
// ----- Module state -----
|
||||
|
||||
// ASCII -> JoeyKeyE, filled once at halInputInit. ORCA/C is C89 and
|
||||
// does not accept designated initializers; runtime fill keeps lookup
|
||||
// O(1) instead of a 40-plus-case switch.
|
||||
static uint8_t gAsciiToKey[ASCII_TABLE_SIZE];
|
||||
static uint8_t gKeyTtl [KEY_COUNT];
|
||||
|
||||
// ----- Internal helpers -----
|
||||
|
||||
static void buildAsciiTable(void) {
|
||||
uint16_t i;
|
||||
|
||||
memset(gAsciiToKey, 0, sizeof(gAsciiToKey));
|
||||
|
||||
for (i = 'A'; i <= 'Z'; i++) {
|
||||
gAsciiToKey[i] = (uint8_t)(KEY_A + (i - 'A'));
|
||||
gAsciiToKey[i - 'A' + 'a'] = (uint8_t)(KEY_A + (i - 'A'));
|
||||
}
|
||||
for (i = '0'; i <= '9'; i++) {
|
||||
gAsciiToKey[i] = (uint8_t)(KEY_0 + (i - '0'));
|
||||
}
|
||||
|
||||
gAsciiToKey[ASCII_SPACE] = KEY_SPACE;
|
||||
gAsciiToKey[ASCII_ESCAPE] = KEY_ESCAPE;
|
||||
gAsciiToKey[ASCII_RETURN] = KEY_RETURN;
|
||||
gAsciiToKey[ASCII_TAB] = KEY_TAB;
|
||||
gAsciiToKey[ASCII_DELETE] = KEY_BACKSPACE;
|
||||
// The left-arrow key produces 0x08, which is also ASCII backspace
|
||||
// on a classic Apple II. Prefer the arrow interpretation since
|
||||
// there is a dedicated Delete key that reports 0x7F.
|
||||
gAsciiToKey[ASCII_LEFT] = KEY_LEFT;
|
||||
gAsciiToKey[ASCII_RIGHT] = KEY_RIGHT;
|
||||
gAsciiToKey[ASCII_UP] = KEY_UP;
|
||||
gAsciiToKey[ASCII_DOWN] = KEY_DOWN;
|
||||
}
|
||||
|
||||
|
||||
static void readModifierKeys(void) {
|
||||
uint8_t mods;
|
||||
|
||||
mods = *IIGS_MODIFIERS;
|
||||
gKeyState[KEY_LSHIFT] = (mods & MOD_SHIFT) != 0;
|
||||
gKeyState[KEY_LCTRL] = (mods & MOD_CONTROL) != 0;
|
||||
gKeyState[KEY_LALT] = (mods & MOD_OPTION) != 0;
|
||||
}
|
||||
|
||||
|
||||
// ----- HAL API (alphabetical) -----
|
||||
|
||||
void halInputInit(void) {
|
||||
memset(gKeyState, 0, sizeof(gKeyState));
|
||||
memset(gKeyPrev, 0, sizeof(gKeyPrev));
|
||||
memset(gKeyTtl, 0, sizeof(gKeyTtl));
|
||||
buildAsciiTable();
|
||||
|
||||
// Clear any pending strobe from before we started.
|
||||
(void)*IIGS_KBDSTRB;
|
||||
}
|
||||
|
||||
|
||||
void halInputPoll(void) {
|
||||
uint8_t kbd;
|
||||
uint8_t ascii;
|
||||
uint8_t key;
|
||||
uint16_t i;
|
||||
|
||||
for (i = 0; i < KEY_COUNT; i++) {
|
||||
if (gKeyTtl[i] > 0) {
|
||||
gKeyTtl[i]--;
|
||||
if (gKeyTtl[i] == 0) {
|
||||
gKeyState[i] = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
kbd = *IIGS_KBD;
|
||||
if (kbd & KBD_STROBE_BIT) {
|
||||
ascii = (uint8_t)(kbd & KBD_ASCII_MASK);
|
||||
key = gAsciiToKey[ascii];
|
||||
if (key != KEY_NONE) {
|
||||
gKeyState[key] = true;
|
||||
gKeyTtl[key] = KEY_TTL;
|
||||
}
|
||||
(void)*IIGS_KBDSTRB;
|
||||
}
|
||||
|
||||
readModifierKeys();
|
||||
}
|
||||
|
||||
|
||||
void halInputShutdown(void) {
|
||||
(void)*IIGS_KBDSTRB;
|
||||
}
|
||||
|
|
@ -1194,6 +1194,34 @@ install_support_gsos() {
|
|||
}
|
||||
|
||||
|
||||
install_support_iigs_null_c600() {
|
||||
local support_dir="$1"
|
||||
local dest="${support_dir}/iigs-null-c600.rom"
|
||||
local key="emu_support_iigs_null_c600"
|
||||
|
||||
if [ "${FORCE_INSTALL}" -eq 1 ]; then
|
||||
rm -f "${dest}"
|
||||
clear_done "${key}"
|
||||
fi
|
||||
if is_done "${key}" && [ -f "${dest}" ]; then
|
||||
ok "IIgs null slot-6 PROM already staged"
|
||||
STATUS[${key}]="ok"
|
||||
return 0
|
||||
fi
|
||||
|
||||
# 256-byte null PROM for slot 6. Leading RTS (so any accidental
|
||||
# call returns cleanly), then zero-filled -- no Pascal 1.1 firmware
|
||||
# signature, so the IIgs boot ROM's slot scan skips slot 6 and the
|
||||
# two empty 5.25 drives never get probed. run-iigs.sh stages this
|
||||
# into each session's work dir as c600.rom; GSplus picks it up from
|
||||
# cwd and overrides its built-in Disk II PROM.
|
||||
{ printf '\x60'; head -c 255 /dev/zero; } > "${dest}"
|
||||
mark_done "${key}"
|
||||
ok "IIgs null slot-6 PROM staged at ${dest}"
|
||||
STATUS[${key}]="ok"
|
||||
}
|
||||
|
||||
|
||||
install_emulator_support() {
|
||||
header "Emulator support files"
|
||||
local support_dir="${SCRIPT_DIR}/emulators/support"
|
||||
|
|
@ -1201,6 +1229,7 @@ install_emulator_support() {
|
|||
|
||||
install_support_emutos "${support_dir}"
|
||||
install_support_iigs_rom "${support_dir}"
|
||||
install_support_iigs_null_c600 "${support_dir}"
|
||||
install_support_gsos "${support_dir}"
|
||||
}
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue