From c86fcbd6afcbe6763c4c802d9b3e2274d5b6f1ca Mon Sep 17 00:00:00 2001 From: Zack Buhman Date: Thu, 8 Jun 2023 22:50:07 +0000 Subject: [PATCH] editor: add example This adds a simple text editor with basic visual line-editing capabilities. --- .gitignore | 1 + Makefile | 7 + common/copy.hpp | 29 ++- common/intback.hpp | 106 +++++++++++ common/keyboard.py | 47 ++++- common/keyboard.txt | 5 + common/minmax.hpp | 13 ++ common/printable.txt | 96 +++++----- editor/editor.hpp | 383 ++++++++++++++++++++++++++++++++++++++++ editor/main_saturn.cpp | 257 +++++++++++++++++++++++++++ editor/test_editor.cpp | 310 ++++++++++++++++++++++++++++++++ smpc/input_keyboard.cpp | 2 +- tools/Makefile | 4 +- tools/ttf-bitmap.cpp | 130 ++++++++++++++ tools/ttf-convert.cpp | 51 +----- wordle/main_saturn.cpp | 146 +++------------ 16 files changed, 1358 insertions(+), 229 deletions(-) create mode 100644 common/intback.hpp create mode 100644 common/minmax.hpp create mode 100644 editor/editor.hpp create mode 100644 editor/main_saturn.cpp create mode 100644 editor/test_editor.cpp create mode 100644 tools/ttf-bitmap.cpp diff --git a/.gitignore b/.gitignore index eace014..ae39754 100644 --- a/.gitignore +++ b/.gitignore @@ -9,6 +9,7 @@ *.out res/mai.data tools/ttf-convert +tools/ttf-bitmap common/keyboard.cpp common/keyboard.hpp wordle/word_list.hpp \ No newline at end of file diff --git a/Makefile b/Makefile index 81a0718..a25c41d 100644 --- a/Makefile +++ b/Makefile @@ -91,6 +91,13 @@ scsp/sound_cpu__slot.elf: scsp/sound_cpu__slot.o m68k/slot.bin.o scsp/sound_cpu__interrupt.elf: scsp/sound_cpu__interrupt.o m68k/interrupt.bin.o sh/lib1funcs.o res/sperrypc.font.bin.o common/draw_font.o common/palette.o +res/sperrypc.bitmap.bin: tools/ttf-bitmap + ./tools/ttf-bitmap 20 7f res/Bm437_SperryPC_CGA.otb $@ + +editor/main_saturn.o: common/keyboard.hpp editor/editor.hpp + +editor/main_saturn.elf: editor/main_saturn.o res/sperrypc.bitmap.bin.o sh/lib1funcs.o common/keyboard.o saturn/start.o + # clean clean: clean-sh clean-sh: diff --git a/common/copy.hpp b/common/copy.hpp index d9c1274..e28562d 100644 --- a/common/copy.hpp +++ b/common/copy.hpp @@ -1,19 +1,44 @@ +#pragma once + #include template inline void copy(T * dst, const T * src, int32_t n) noexcept { + n = n / (sizeof (T)); while (n > 0) { *dst++ = *src++; - n -= (sizeof (T)); + n--; } } template inline void fill(T * dst, const T src, int32_t n) noexcept { + n = n / (sizeof (T)); while (n > 0) { *dst++ = src; - n -= (sizeof (T)); + n--; + } +} + +template +inline void move(T * dst, const T * src, int32_t n) noexcept +{ + n = n / (sizeof (T)); + if (dst < src) { + // d s + // 0123456789 + while (n > 0) { + *dst++ = *src++; + n--; + } + } else { + // s d + // 0123456789 + while (n) { + n--; + dst[n] = src[n]; + } } } diff --git a/common/intback.hpp b/common/intback.hpp new file mode 100644 index 0000000..4138c29 --- /dev/null +++ b/common/intback.hpp @@ -0,0 +1,106 @@ +namespace intback { + enum intback_fsm { + PORT_STATUS = 0, + PERIPHERAL_ID, + DATA1, + DATA2, + DATA3, + DATA4, + FSM_NEXT + }; + + struct intback_state { + uint8_t fsm; + uint8_t controller_ix; + uint8_t port_ix; + uint8_t oreg_ix; + + uint8_t port_connected; + uint8_t data_size; + uint8_t peripheral_type; + uint8_t kbd_bits; + }; + + typedef void (*keyboard_func_ptr)(const enum keysym k, uint8_t kbd_bits); + + static intback_state state; + + inline void keyboard_fsm(keyboard_func_ptr callback) + { + if ((smpc.reg.SR & SR__PDL) != 0) { + // PDL == 1; 1st peripheral data + state.oreg_ix = 0; + state.controller_ix = 0; + state.port_ix = 0; + state.fsm = PORT_STATUS; + + state.port_connected = 0; + state.data_size = 0; + state.peripheral_type = 0; + state.kbd_bits = 0; + } + + /* + This intback handling is oversimplified: + + - up to 2 controllers may be (directly) connected + - multitaps are not parsed correctly + */ + while (state.oreg_ix < 32) { + reg8 const& oreg = smpc.reg.oreg[state.oreg_ix++]; + switch (state.fsm) { + case PORT_STATUS: + state.port_connected = (PORT_STATUS__CONNECTORS(oreg) == 1); + if (state.port_connected) { + if (PORT_STATUS__MULTITAP_ID(oreg) != 0xf) { + // this port is not directly connected; abort parse: + goto abort; + } + } else { + state.fsm = FSM_NEXT; + } + break; + case PERIPHERAL_ID: + state.peripheral_type = PERIPHERAL_ID__TYPE(oreg); + state.data_size = PERIPHERAL_ID__DATA_SIZE(oreg); + break; + case DATA1: + break; + case DATA2: + break; + case DATA3: + state.kbd_bits = oreg & 0b1111; + break; + case DATA4: + { + uint32_t keysym = oreg; + enum keysym k = scancode_to_keysym(keysym); + callback(k, state.kbd_bits); + } + break; + default: + break; + } + + if ((state.fsm >= PERIPHERAL_ID && state.data_size <= 0) || state.fsm == FSM_NEXT) { + if (state.port_ix == 1) + break; + else { + state.port_ix++; + state.controller_ix++; + state.fsm = PORT_STATUS; + } + } else { + state.fsm++; + state.data_size--; + } + } + + if ((smpc.reg.SR & SR__NPE) != 0) { + smpc.reg.ireg[0] = INTBACK__IREG0__CONTINUE; + } else { + abort: + smpc.reg.ireg[0] = INTBACK__IREG0__BREAK; + } + } +} diff --git a/common/keyboard.py b/common/keyboard.py index 2033775..fb979c0 100644 --- a/common/keyboard.py +++ b/common/keyboard.py @@ -20,7 +20,8 @@ def parse_printable(): for line in f: if not line.strip(): continue - yield line.strip().split() + k, v1, v2 = line.strip().split() + yield k, (v1, v2) scancodes = set() @@ -46,7 +47,15 @@ keymap = build_keymap() printable = dict(parse_printable()) -printable["SPACE"] = ' ' +printable["SPACE"] = (' ', ' ') + +def e(s): + if s == '\\': + return '\\\\' + elif s == '\'': + return '\\\'' + else: + return s import sys if sys.argv[1] == "header": @@ -62,8 +71,9 @@ if sys.argv[1] == "header": print() print("enum keysym scancode_to_keysym(uint32_t scancode);") - print("char16_t keysym_to_char16(enum keysym k);") + print("int32_t keysym_to_char(enum keysym k, bool shift);") + if sys.argv[1] == "definition": print("#include ") print("#include \"keyboard.hpp\"") @@ -81,15 +91,38 @@ if sys.argv[1] == "definition": print() - print("char16_t keysym_to_char16(enum keysym k)") + print("static constexpr inline int32_t unshift_char(enum keysym k)") print("{") print(" switch(k) {") for keysym, _ in keymap: if keysym in printable: value = printable[keysym] - if value == '\\': - value = '\\\\'; - print(f" case keysym::{keysym}: return '{value}';") + print(f" case keysym::{keysym}: return '{e(value[0])}';") print(" default: return -1;") print(" }") print("}") + + print() + + print("static constexpr inline int32_t shift_char(enum keysym k)") + print("{") + print(" switch(k) {") + for keysym, _ in keymap: + if keysym in printable: + value = printable[keysym] + print(f" case keysym::{keysym}: return '{e(value[1])}';") + print(" default: return -1;") + print(" }") + print("}") + + print() + + print("""int32_t keysym_to_char(enum keysym k, bool shift) +{ + if (shift) { + return shift_char(k); + } else { + return unshift_char(k); + } +} +""") diff --git a/common/keyboard.txt b/common/keyboard.txt index 2eb0677..7d539fb 100644 --- a/common/keyboard.txt +++ b/common/keyboard.txt @@ -93,3 +93,8 @@ PAGE_UP 8b DELETE 85 END 88 PAGE_DOWN 8c + +ARROW_LEFT 86 +ARROW_UP 89 +ARROW_DOWN 8A +ARROW_RIGHT 8d diff --git a/common/minmax.hpp b/common/minmax.hpp new file mode 100644 index 0000000..f2da769 --- /dev/null +++ b/common/minmax.hpp @@ -0,0 +1,13 @@ +#pragma once + +template +static inline constexpr T min(T a, T b) +{ + return a > b ? b : a; +} + +template +static inline constexpr T max(T a, T b) +{ + return a > b ? a : b; +} diff --git a/common/printable.txt b/common/printable.txt index 544cbb3..56c07c3 100644 --- a/common/printable.txt +++ b/common/printable.txt @@ -1,52 +1,52 @@ -K1 1 -K2 2 -K3 3 -K4 4 -K5 5 -K6 6 -K7 7 -K8 8 -K9 9 -K0 0 -MINUS - -CAROT ^ -JPY $ +K1 1 ! +K2 2 " +K3 3 # +K4 4 $ +K5 5 % +K6 6 & +K7 7 ' +K8 8 ( +K9 9 ) +K0 0 ~ +MINUS - = +CAROT ^ ~ +JPY $ | -Q q -W w -E e -R r -T t -Y y -U u -I i -O o -P p -AT @ -LEFT_SQUARE [ +Q q Q +W w W +E e E +R r R +T t T +Y y Y +U u U +I i I +O o O +P p P +AT @ ` +LEFT_SQUARE [ { -A a -S s -D d -F f -G g -H h -J j -K k -L l -SEMICOLON ; -COLON : -RIGHT_SQUARE ] +A a A +S s S +D d D +F f F +G g G +H h H +J j J +K k K +L l L +SEMICOLON ; + +COLON : * +RIGHT_SQUARE ] } -Z z -X x -C c -V v -B b -N n -M m -COMMA , -PERIOD . -FORWARD_SLASH / -BACK_SLASH \ +Z z Z +X x X +C c C +V v V +B b B +N n N +M m M +COMMA , < +PERIOD . > +FORWARD_SLASH / ? +BACK_SLASH \ _ diff --git a/editor/editor.hpp b/editor/editor.hpp new file mode 100644 index 0000000..69de5dc --- /dev/null +++ b/editor/editor.hpp @@ -0,0 +1,383 @@ +#pragma once + +#include + +#include "../common/copy.hpp" +#include "../common/minmax.hpp" + +namespace editor { + +template +struct line { + uint8_t buf[C]; + int32_t length; +}; + +struct cursor { + int32_t row; + int32_t col; +}; + +template +struct buffer { + line row[R]; + line * lines[R]; + int32_t length; + int32_t alloc_ix; + struct { + int32_t cell_width; + int32_t cell_height; + int32_t top; + int32_t left; + } window; + struct cursor cursor; + + typedef line line_type; + + inline static constexpr int32_t rows_max_length(){return R;} + inline static constexpr int32_t cols_max_length(){return C;} + + inline constexpr buffer(int32_t width, int32_t height); + inline constexpr line * allocate(); + inline constexpr void deallocate(line *& l); + inline constexpr bool put(uint8_t c); + inline constexpr bool backspace(); + inline constexpr bool cursor_left(); + inline constexpr bool cursor_right(); + inline constexpr bool cursor_up(); + inline constexpr bool cursor_down(); + inline constexpr bool cursor_home(); + inline constexpr bool cursor_end(); + inline constexpr bool enter(); +private: + inline constexpr void scroll_left(); + inline constexpr void scroll_right(); + inline constexpr void scroll_up(); + inline constexpr void scroll_down(); +}; + +template +inline constexpr buffer::buffer(int32_t width, int32_t height) +{ + this->length = 1; + this->alloc_ix = 0; + this->window.top = 0; + this->window.left = 0; + this->window.cell_height = height; + this->window.cell_width = width; + this->cursor.row = 0; + this->cursor.col = 0; + + for (int32_t i = 0; i < R; i++) { + this->row[i].length = -1; + this->lines[i] = nullptr; + + for (int32_t j = 0; j < C; j++) { + this->row[i].buf[j] = 0x7f; + } + } +} + +template +inline constexpr line * buffer::allocate() +{ + line * l; + while ((l = &this->row[this->alloc_ix])->length != -1) { + // R must be a power of 2 + static_assert((R & (R - 1)) == 0); + this->alloc_ix = (this->alloc_ix + 1) & (R - 1); + } + l->length = 0; + return l; +} + +template +inline constexpr void buffer::deallocate(line *& l) +{ + // does not touch alloc_ix + fill(l->buf, 0x7f, l->length); + l->length = -1; + l = nullptr; +} + +template +inline constexpr bool buffer::put(const uint8_t c) +{ + struct cursor& cur = this->cursor; + line *& l = this->lines[cur.row]; + if (l == nullptr) { + l = this->allocate(); + } + + // v + // 0123 + if (l->length >= C || cur.col >= C || cur.col < 0) + return false; + + if (l->length > cur.col) { + // c (5) + // 01234 + // 012a34 + // 4, 3, (5 - 3) + move(&l->buf[cur.col+1], &l->buf[cur.col], l->length - cur.col); + } + l->buf[cur.col] = c; + l->length++; + cur.col++; + scroll_right(); + + return true; +} + +template +inline constexpr bool buffer::backspace() +{ + struct cursor& cur = this->cursor; + + if (cur.col < 0 || cur.col > C) + return false; + + line *& l = this->lines[cur.row]; + if (l == nullptr) { + // is it possible to get in this state? + return false; + } + if (l->length < 0) { + // is it possible to get in this state? + return false; + } + + if (cur.col == 0) { + if (cur.row == 0) return false; + // combine this line with the previous line + line *& lp = this->lines[cur.row-1]; + if (lp == nullptr) lp = allocate(); + // blindly truncate overflowing lines + auto length = min(l->length - cur.col, C - lp->length); + if (length > 0) move(&lp->buf[lp->length], &l->buf[cur.col], length); + + cur.col = lp->length; + scroll_right(); + lp->length += length; + deallocate(l); + // 0 a + // 1 + // 2 _ (cur.row, deleted) + // 3 b + int32_t n_lines = this->length - (cur.row + 1); + move(&this->lines[cur.row], &this->lines[cur.row+1], (sizeof (line*)) * n_lines); + this->length--; + this->lines[this->length] = nullptr; + cur.row--; + scroll_up(); + } else { + // c + // 01234 + auto length = l->length - cur.col; + if (length > 0) move(&l->buf[cur.col-1], &l->buf[cur.col], length); + + cur.col--; + scroll_left(); + l->length--; + l->buf[l->length] = 0x7f; + } + + return true; +} + +template +inline constexpr bool buffer::cursor_left() +{ + struct cursor& cur = this->cursor; + + if (cur.col <= 0) { + if (cur.row <= 0) + return false; + + cur.row--; + scroll_up(); + + line const * const l = this->lines[cur.row]; + int32_t length = (l == nullptr) ? 0 : l->length; + cur.col = length; + scroll_right(); + } else { + cur.col--; + scroll_left(); + } + + return true; +} + +template +inline constexpr bool buffer::cursor_right() +{ + struct cursor& cur = this->cursor; + + line const * const l = this->lines[cur.row]; + int32_t length = (l == nullptr) ? 0 : l->length; + if (cur.col >= length) { + if (cur.row >= this->length) + return false; + + cur.row++; + scroll_down(); + + cur.col = 0; + scroll_left(); + } else { + cur.col++; + scroll_right(); + } + + return true; +} + +template +inline constexpr bool buffer::cursor_up() +{ + struct cursor& cur = this->cursor; + + if (cur.row <= 0) + return false; + + cur.row--; + scroll_up(); + + line const * const l = this->lines[cur.row]; + int32_t length = (l == nullptr) ? 0 : l->length; + cur.col = min(cur.col, length); + scroll_left(); + return true; +} + +template +inline constexpr bool buffer::cursor_down() +{ + struct cursor& cur = this->cursor; + + if (cur.row >= this->length) + return false; + + cur.row++; + scroll_down(); + + line const * const l = this->lines[cur.row]; + int32_t length = (l == nullptr) ? 0 : l->length; + cur.col = min(cur.col, length); + scroll_left(); + return true; +} + +template +inline constexpr bool buffer::cursor_home() +{ + struct cursor& cur = this->cursor; + + line const * const l = this->lines[cur.row]; + if (l == nullptr) + return false; + + cur.col = 0; + scroll_left(); + return true; +} + +template +inline constexpr bool buffer::cursor_end() +{ + struct cursor& cur = this->cursor; + + line const * const l = this->lines[cur.row]; + if (l == nullptr) + return false; + + cur.col = l->length; + scroll_right(); + return true; +} + +template +inline constexpr bool buffer::enter() +{ + struct cursor& cur = this->cursor; + + if (cur.row >= R || cur.row < 0) + return false; + + if (this->length > cur.row) { + // first, move all lines down one + int32_t n_lines = this->length - (cur.row + 1); + // don't care about nullptr here, this never dereferences + move(&this->lines[cur.row+2], &this->lines[cur.row+1], (sizeof (line*)) * n_lines); + } + // column-wise copy of the cursor position to the newly-created line + line * old_l = this->lines[cur.row]; + line * new_l = allocate(); + // v + // 01234 (5) + if (old_l != nullptr) { + new_l->length = old_l->length - cur.col; + if (new_l->length > 0) { + old_l->length -= new_l->length; + copy(&new_l->buf[0], &old_l->buf[cur.col], new_l->length); + fill(&old_l->buf[cur.col], 0x7f, new_l->length); + } + } else { + // nothing to do, new_l->length is already 0 + } + + cur.row++; + scroll_down(); + this->lines[cur.row] = new_l; + cur.col = 0; + scroll_left(); + // because copy() is used instead of put(), length needs to be + // incremented here. + this->length++; + return true; +} + +template +inline constexpr void buffer::scroll_up() +{ + if (this->cursor.row < this->window.top) + this->window.top = this->cursor.row; +} + +template +inline constexpr void buffer::scroll_down() +{ + // 0: a - + // 1: bv - + + // 0: a + // 1: b - + // 2: cv - + + if (this->cursor.row >= this->window.top + this->window.cell_height) + this->window.top = (this->cursor.row - (this->window.cell_height - 1)); +} + +template +inline constexpr void buffer::scroll_left() +{ + if (this->cursor.col < this->window.left) + this->window.left = this->cursor.col; +} + +template +inline constexpr void buffer::scroll_right() +{ + // 0: a - + // 1: bv - + + // 0: a + // 1: b - + // 2: cv - + + if (this->cursor.col >= this->window.left + this->window.cell_width) + this->window.left = (this->cursor.col - (this->window.cell_width - 1)); +} + +} diff --git a/editor/main_saturn.cpp b/editor/main_saturn.cpp new file mode 100644 index 0000000..a824e7b --- /dev/null +++ b/editor/main_saturn.cpp @@ -0,0 +1,257 @@ +#include + +#include "vdp2.h" +#include "smpc.h" +#include "scu.h" +#include "sh2.h" + +#include "../common/copy.hpp" +#include "../common/vdp2_func.hpp" +#include "../common/keyboard.hpp" +#include "../common/intback.hpp" + +#include "editor.hpp" + +extern void * _sperrypc_bitmap_start __asm("_binary_res_sperrypc_bitmap_bin_start"); + +using buffer_type = editor::buffer<64, 64>; +constexpr int32_t viewport_max_col = 320 / 8; +constexpr int32_t viewport_max_row = 240 / 8; + +static buffer_type buffer {viewport_max_col, viewport_max_row}; + +void palette_data() +{ + vdp2.cram.u16[1 + 0 ] = 0; + vdp2.cram.u16[2 + 0 ] = 0x7fff; + + vdp2.cram.u16[1 + 16] = 0x7fff; + vdp2.cram.u16[2 + 16] = 0; +} + +namespace pix_fmt_4bpp +{ + constexpr inline uint32_t + bit(uint8_t n, int32_t i) + { + i &= 7; + auto b = (n >> (7 - i)) & 1; + return ((b + 1) << ((7 - i) * 4)); + } + + constexpr inline uint32_t + bits(uint8_t n) + { + return + bit(n, 0) | bit(n, 1) | bit(n, 2) | bit(n, 3) + | bit(n, 4) | bit(n, 5) | bit(n, 6) | bit(n, 7); + } + + static_assert(bits(0b1100'1110) == 0x2211'2221); + static_assert(bits(0b1010'0101) == 0x2121'1212); + static_assert(bits(0b1000'0000) == 0x2111'1111); +} + +void cell_data() +{ + const uint8_t * buf = reinterpret_cast(&_sperrypc_bitmap_start); + + for (int ix = 0; ix <= (0x7f - 0x20); ix++) { + for (int y = 0; y < 8; y++) { + uint8_t row = buf[ix * 8 + y]; + vdp2.vram.u32[(ix * 8) + y] = pix_fmt_4bpp::bits(row); + } + } +} + +struct modifiers { + uint8_t left_shift; + uint8_t right_shift; +}; + +static modifiers modifier_state = { 0 }; + +void keyboard_callback(const enum keysym k, uint8_t kbd_bits) +{ + int32_t shift = modifier_state.left_shift || modifier_state.right_shift; + int32_t c = keysym_to_char(k, shift); + if (KEYBOARD__MAKE(kbd_bits)) { + if (k == keysym::UNKNOWN) + return; + + if (c != -1) { + buffer.put(c); + } else { + switch (k) { + case keysym::BACKSPACE : buffer.backspace(); break; + case keysym::ARROW_LEFT : buffer.cursor_left(); break; + case keysym::ARROW_RIGHT: buffer.cursor_right(); break; + case keysym::ARROW_UP : buffer.cursor_up(); break; + case keysym::ARROW_DOWN : buffer.cursor_down(); break; + case keysym::HOME : buffer.cursor_home(); break; + case keysym::END : buffer.cursor_end(); break; + case keysym::ENTER : buffer.enter(); break; + case keysym::LEFT_SHIFT : modifier_state.left_shift = 1; break; + case keysym::RIGHT_SHIFT: modifier_state.right_shift = 1; break; + default: break; + } + } + } else if (KEYBOARD__BREAK(kbd_bits)) { + switch (k) { + case keysym::LEFT_SHIFT : modifier_state.left_shift = 0; break; + case keysym::RIGHT_SHIFT: modifier_state.right_shift = 0; break; + default: break; + } + } +} + +extern "C" +void smpc_int(void) __attribute__ ((interrupt_handler)); +void smpc_int(void) +{ + scu.reg.IST &= ~(IST__SMPC); + scu.reg.IMS = ~(IMS__SMPC | IMS__V_BLANK_IN); + + intback::keyboard_fsm(keyboard_callback); +} + +constexpr int32_t plane_a = 2; +constexpr inline int32_t plane_offset(int32_t n) { return n * 0x2000; } + +constexpr int32_t page_size = 64 * 64 * 2; // N0PNB__1WORD (16-bit) +constexpr int32_t plane_size = page_size * 1; + +constexpr int32_t cell_size = (8 * 8) / 2; // N0CHCN__16_COLOR (4-bit) +constexpr int32_t character_size = cell_size * (1 * 1); // N0CHSZ__1x1_CELL +constexpr int32_t page_width = 64; + +static int plane_ix = 0; + +inline void +set_char(int32_t x, int32_t y, uint8_t palette, uint8_t c) +{ + const auto ix = (plane_offset(plane_a + plane_ix) / 2) + (y * page_width) + x; + vdp2.vram.u16[ix] = + PATTERN_NAME_TABLE_1WORD__PALETTE(palette) + | PATTERN_NAME_TABLE_1WORD__CHARACTER((c - 0x20)); +} + +void render() +{ + for (int row = 0; row < buffer.window.cell_height; row++) { + const buffer_type::line_type * l = buffer.lines[buffer.window.top + row]; + + for (int col = 0; col < buffer.window.cell_width; col++) { + uint8_t c; + + c = ' '; + if (l != nullptr && (buffer.window.left + col) < l->length) + c = l->buf[buffer.window.left + col]; + + set_char(col, row, 0, c); + } + } + editor::cursor& cur = buffer.cursor; + const buffer_type::line_type * l = buffer.lines[cur.row]; + uint8_t c = (l != nullptr && cur.col < l->length) ? l->buf[cur.col] : ' '; + set_char(cur.col - buffer.window.left, cur.row - buffer.window.top, 1, c); +} + +extern "C" +void v_blank_in_int(void) __attribute__ ((interrupt_handler)); +void v_blank_in_int() +{ + scu.reg.IST &= ~(IST__V_BLANK_IN); + scu.reg.IMS = ~(IMS__SMPC | IMS__V_BLANK_IN); + + // flip planes; + vdp2.reg.MPABN0 = MPABN0__N0MPB(0) | MPABN0__N0MPA(plane_a + plane_ix); + plane_ix = !plane_ix; + + // wait at least 300us, as specified in the SMPC manual. + // It appears reading FRC.H is mandatory and *must* occur before FRC.L on real + // hardware. + while ((sh2.reg.FTCSR & FTCSR__OVF) == 0 && sh2.reg.FRC.H == 0 && sh2.reg.FRC.L < 63); + + if ((vdp2.reg.TVSTAT & TVSTAT__VBLANK) != 0) { + // on real hardware, SF contains uninitialized garbage bits other than the + // lsb. + while ((smpc.reg.SF & 1) != 0); + + smpc.reg.SF = 0; + + smpc.reg.ireg[0] = INTBACK__IREG0__STATUS_DISABLE; + smpc.reg.ireg[1] = ( INTBACK__IREG1__PERIPHERAL_DATA_ENABLE + | INTBACK__IREG1__PORT2_15BYTE + | INTBACK__IREG1__PORT1_15BYTE + ); + smpc.reg.ireg[2] = INTBACK__IREG2__MAGIC; + + smpc.reg.COMREG = COMREG__INTBACK; + } + + render(); +} + +void main() +{ + v_blank_in(); + + // DISP: Please make sure to change this bit from 0 to 1 during V blank. + vdp2.reg.TVMD = ( TVMD__DISP | TVMD__LSMD__NON_INTERLACE + | TVMD__VRESO__240 | TVMD__HRESO__NORMAL_320); + + /* set the color mode to 5bits per channel, 1024 colors */ + vdp2.reg.RAMCTL = RAMCTL__CRMD__RGB_5BIT_1024; + + /* enable display of NBG0 */ + vdp2.reg.BGON = BGON__N0ON; + + /* set character format for NBG0 to palettized 16 color + set enable "cell format" for NBG0 + set character size for NBG0 to 1x1 cell */ + vdp2.reg.CHCTLA = CHCTLA__N0CHCN__16_COLOR + | CHCTLA__N0BMEN__CELL_FORMAT + | CHCTLA__N0CHSZ__1x1_CELL; + /* "Note: In color RAM modes 0 and 2, 2048-color becomes 1024-color" */ + + /* use 1-word (16-bit) pattern names */ + vdp2.reg.PNCN0 = PNCN0__N0PNB__1WORD; + + /* plane size */ + vdp2.reg.PLSZ = PLSZ__N0PLSZ__1x1; + + /* map plane offset + 1-word: value of bit 6-0 * 0x2000 + 2-word: value of bit 5-0 * 0x4000 + */ + vdp2.reg.MPOFN = MPOFN__N0MP(0); // bits 8~6 + vdp2.reg.MPABN0 = MPABN0__N0MPB(0) | MPABN0__N0MPA(plane_a); // bits 5~0 + vdp2.reg.MPCDN0 = MPABN0__N0MPD(0) | MPABN0__N0MPC(0); // bits 5~0 + + // zeroize character/cell data from 0 up to plane_a_offset + fill(&vdp2.vram.u32[(0 / 4)], 0, plane_offset(plane_a)); + + // zeroize plane_a; `0` is the ascii 0x20 ("space") which doubles as + // "transparency" character. + fill(&vdp2.vram.u32[(plane_offset(plane_a) / 4)], 0, plane_size * 2); + + palette_data(); + cell_data(); + + // free-running timer + sh2.reg.TCR = TCR__CKS__INTERNAL_DIV128; + sh2.reg.FTCSR = 0; + + // initialize smpc + smpc.reg.DDR1 = 0; // INPUT + smpc.reg.DDR2 = 0; // INPUT + smpc.reg.IOSEL = 0; // SMPC control + smpc.reg.EXLE = 0; // + + sh2_vec[SCU_VEC__SMPC] = (u32)(&smpc_int); + sh2_vec[SCU_VEC__V_BLANK_IN] = (u32)(&v_blank_in_int); + + scu.reg.IST = 0; + scu.reg.IMS = ~(IMS__SMPC | IMS__V_BLANK_IN); +} diff --git a/editor/test_editor.cpp b/editor/test_editor.cpp new file mode 100644 index 0000000..dc291db --- /dev/null +++ b/editor/test_editor.cpp @@ -0,0 +1,310 @@ +#include +#include + +#include "editor.hpp" + +using namespace editor; + +static void test_allocate() +{ + buffer<8, 4> b {4, 2}; + decltype(b)::line_type * l; + + assert(b.row[0].length == -1); + assert(b.row[1].length == -1); + assert(b.row[2].length == -1); + assert(b.row[3].length == -1); + + l = b.allocate(); + assert(b.row[0].length == 0); + assert(b.row[1].length == -1); + assert(b.row[2].length == -1); + assert(b.row[3].length == -1); + assert(l == &b.row[0]); + + l = b.allocate(); + assert(b.row[0].length == 0); + assert(b.row[1].length == 0); + assert(b.row[2].length == -1); + assert(b.row[3].length == -1); + assert(l == &b.row[1]); + + l = b.allocate(); + assert(b.row[0].length == 0); + assert(b.row[1].length == 0); + assert(b.row[2].length == 0); + assert(b.row[3].length == -1); + assert(l == &b.row[2]); + + l = b.allocate(); + assert(b.row[0].length == 0); + assert(b.row[1].length == 0); + assert(b.row[2].length == 0); + assert(b.row[3].length == 0); + assert(l == &b.row[3]); + + l = &b.row[1]; + b.deallocate(l); + assert(b.row[0].length == 0); + assert(b.row[1].length == -1); + assert(b.row[2].length == 0); + assert(b.row[3].length == 0); + + l = b.allocate(); + assert(b.row[0].length == 0); + assert(b.row[1].length == 0); + assert(b.row[2].length == 0); + assert(b.row[3].length == 0); + assert(l == &b.row[1]); +} + +static void test_put() +{ + // v + // "as" -> "abs" + buffer<8, 4> b {4, 2}; + decltype(b)::line_type * l; + + assert(b.cursor.col == 0); + assert(b.cursor.row == 0); + assert(b.length == 1); + + b.put('a'); + l = b.lines[0]; + assert(l = &b.row[0]); + assert(l->length == 1); + assert(l->buf[0] == 'a'); + assert(l->buf[1] == 0x7f); + assert(b.cursor.col == 1); + assert(b.cursor.row == 0); + assert(b.length == 1); + + b.put('b'); + l = b.lines[0]; + assert(l->length == 2); + assert(l->buf[0] == 'a'); + assert(l->buf[1] == 'b'); + assert(l->buf[2] == 0x7f); + assert(b.cursor.col == 2); + assert(b.cursor.row == 0); + assert(b.length == 1); + + b.cursor.col = 1; + b.put('c'); + l = b.lines[0]; + assert(l->length == 3); + assert(l->buf[0] == 'a'); + assert(l->buf[1] == 'c'); + assert(l->buf[2] == 'b'); + assert(l->buf[3] == 0x7f); + assert(b.cursor.col == 2); + assert(b.cursor.row == 0); + assert(b.length == 1); +} + +void test_backspace() +{ + buffer<8, 4> b {4, 2}; + decltype(b)::line_type * l; + + b.put('a'); + l = b.lines[0]; + assert(l->length == 1); + assert(l->buf[0] == 'a'); + + assert(b.backspace() == true); + assert(l->length == 0); + assert(l->buf[0] == 0x7f); + + assert(b.backspace() == false); + + b.put('b'); + b.put('c'); + b.put('d'); + b.put('e'); + b.cursor.col = 2; + assert(l->length == 4); + + //"bcde" + assert(b.backspace() == true); + assert(l->buf[0] == 'b'); + assert(l->buf[1] == 'd'); + assert(l->buf[2] == 'e'); + assert(l->buf[3] == 0x7f); + assert(l->length == 3); +} + +void test_enter() +{ + // [0] asDf + // [1] qwer + + // [0] as + // [1] Df + // [2] qwer + buffer<8, 4> b {4, 2}; + + b.cursor.row = 0; + b.cursor.col = 0; + b.put('a'); + b.put('s'); + b.put('d'); + b.put('f'); + + b.cursor.row = 1; + b.cursor.col = 0; + b.put('q'); + b.put('w'); + b.put('e'); + b.put('r'); + + assert(b.length == 2); + assert(b.lines[0]->buf[0] == 'a'); + assert(b.lines[0]->buf[3] == 'f'); + assert(b.lines[1]->buf[0] == 'q'); + assert(b.lines[1]->buf[3] == 'r'); + + b.cursor.row = 0; + b.cursor.col = 2; + b.enter(); + + assert(b.length == 3); + assert(b.lines[0]->length == 2); + assert(b.lines[1]->length == 2); + assert(b.lines[2]->length == 4); + assert(b.lines[0]->buf[0] == 'a'); + assert(b.lines[0]->buf[1] == 's'); + + assert(b.lines[1]->buf[0] == 'd'); + assert(b.lines[1]->buf[1] == 'f'); + + assert(b.lines[2]->buf[0] == 'q'); + assert(b.lines[2]->buf[1] == 'w'); + assert(b.lines[2]->buf[2] == 'e'); + assert(b.lines[2]->buf[3] == 'r'); +} + +void test_enter_backspace1() +{ + // abcd + + // ab + // + // cd + + // abcd + + // ab + // cd + + buffer<8, 4> b {4, 2}; + + b.put('a'); + b.put('b'); + b.put('c'); + b.put('d'); + b.cursor_left(); + b.cursor_left(); + b.enter(); + b.enter(); + assert(b.length == 3); + b.backspace(); + b.backspace(); + assert(b.length == 1); + b.enter(); + assert(b.length == 2); + + assert(b.lines[0]->buf[0] == 'a'); + assert(b.lines[0]->buf[1] == 'b'); + assert(b.lines[1]->buf[0] == 'c'); + assert(b.lines[1]->buf[1] == 'd'); +} + +void test_enter_backspace2() +{ + buffer<8, 4> b {4, 2}; + + b.put('a'); + b.enter(); + assert(b.cursor.row == 1); + assert(b.cursor.col == 0); + assert(b.length == 2); + b.backspace(); + assert(b.cursor.row == 0); + assert(b.cursor.col == 1); + assert(b.length == 1); +} + +void test_enter_scroll() +{ + buffer<8, 4> b {4, 2}; + + assert(b.window.top == 0); + b.put('a'); + assert(b.window.top == 0); + b.enter(); + assert(b.window.top == 0); + b.put('b'); + assert(b.window.top == 0); + b.enter(); + assert(b.cursor.row == 2); + assert(b.window.top == 1); + b.put('c'); + assert(b.window.top == 1); + b.enter(); + assert(b.window.top == 2); + b.put('d'); + assert(b.window.top == 2); +} + +void test_first_enter() +{ + buffer<8, 4> b {4, 2}; + + b.enter(); + assert(b.length == 2); +} + +void test_enter_backspace3() +{ + // a + // + // + // b + + buffer<8, 8> b {4, 2}; + + b.put('a'); + b.enter(); + b.enter(); + b.enter(); + b.put('b'); + b.cursor_up(); + assert(b.lines[0]->buf[0] == 'a'); + assert(b.lines[1]->length == 0); + assert(b.lines[2]->length == 0); + assert(b.lines[3]->buf[0] == 'b'); + assert(b.lines[4] == nullptr); + assert(b.length == 4); + + b.backspace(); + assert(b.length == 3); + assert(b.lines[0]->buf[0] == 'a'); + assert(b.lines[1]->length == 0); + assert(b.lines[2]->buf[0] == 'b'); + assert(b.lines[3] == nullptr); +} + +int main() +{ + test_allocate(); + test_put(); + test_backspace(); + test_enter_backspace1(); + test_enter_backspace2(); + test_enter_backspace3(); + test_enter_scroll(); + test_first_enter(); + + return 0; +} diff --git a/smpc/input_keyboard.cpp b/smpc/input_keyboard.cpp index 825402a..dfcb6e4 100644 --- a/smpc/input_keyboard.cpp +++ b/smpc/input_keyboard.cpp @@ -243,7 +243,7 @@ void smpc_int(void) { print_hex(str_num, 2, keysym); enum keysym k = scancode_to_keysym(keysym); - char16_t c = keysym_to_char16(k); + int32_t c = keysym_to_char(k, false); if (k != keysym::UNKNOWN && c != -1) { text[0] = c; x += draw_font::horizontal_string(font_state, diff --git a/tools/Makefile b/tools/Makefile index cade8b0..872b23b 100644 --- a/tools/Makefile +++ b/tools/Makefile @@ -2,7 +2,7 @@ CFLAGS = -Og -Wall -Wextra -Werror -ggdb -Wno-error=unused-parameter -Wno-error= CFLAGS += $(shell pkg-config --cflags freetype2) LDFLAGS = $(shell pkg-config --libs freetype2) -all: ttf-convert +all: ttf-convert ttf-bitmap %.o: %.cpp $(CXX) $(CFLAGS) -c $< -o $@ @@ -11,7 +11,7 @@ all: ttf-convert $(CXX) $(LDFLAGS) $< -o $@ clean: - rm -f *.o ttf-convert + rm -f *.o ttf-convert ttf-bitmap .SUFFIXES: .INTERMEDIATE: diff --git a/tools/ttf-bitmap.cpp b/tools/ttf-bitmap.cpp new file mode 100644 index 0000000..55782e9 --- /dev/null +++ b/tools/ttf-bitmap.cpp @@ -0,0 +1,130 @@ +#include +#include + +#include +#include + +#include +#include FT_FREETYPE_H + +int32_t +load_bitmap_char(FT_Face face, + FT_ULong char_code, + uint8_t * buf) +{ + FT_Error error; + FT_UInt glyph_index = FT_Get_Char_Index(face, char_code); + + error = FT_Load_Glyph(face, glyph_index, FT_LOAD_DEFAULT); + if (error) { + std::cerr << "FT_Load_Glyph " << FT_Error_String(error) << '\n'; + return -1; + } + + assert(face->glyph->format == FT_GLYPH_FORMAT_BITMAP); + //printf("num_grays %d\n", face->glyph->bitmap.num_grays); + //printf("pitch %d\n", face->glyph->bitmap.pitch); + //printf("width %d\n", face->glyph->bitmap.width); + assert(face->glyph->bitmap.width == 8); + //printf("char_code %lx rows %d\n", char_code, face->glyph->bitmap.rows); + assert((face->glyph->bitmap.rows % 8) == 0); + assert(face->glyph->bitmap.width / face->glyph->bitmap.pitch == 8); + + for (int y = 0; y < (int)face->glyph->bitmap.rows; y++) { + uint8_t * row = &face->glyph->bitmap.buffer[y * face->glyph->bitmap.pitch]; + uint8_t row_out = 0; + for (unsigned int x = 0; x < face->glyph->bitmap.width; x++) { + int bit; + if (x < face->glyph->bitmap.width) { + bit = (row[x / 8] >> (7 - (x % 8))) & 1; + } else { + bit = 0; + } + //std::cerr << (bit ? "█" : " "); + row_out |= (bit << (7 - (x % 8))); + } + //std::cerr << '\n'; + buf[y] = row_out; + } + + // 'pitch' is bytes; 'width' is pixels + return face->glyph->bitmap.rows * face->glyph->bitmap.pitch; +} + +template +constexpr inline T +parse_num(decltype(std::hex)& format, const char * s) +{ + T n; + std::stringstream ss; + ss << format << s; + ss >> n; + return n; +} + +int main(int argc, char *argv[]) +{ + FT_Library library; + FT_Face face; + FT_Error error; + + if (argc != 5) { + std::cerr << "usage: " << argv[0] << " [start-hex] [end-hex] [font-file-path] [output-file-path]\n\n"; + std::cerr << " ex: " << argv[0] << " 3000 30ff ipagp.ttf font.bin\n"; + return -1; + } + + auto start = parse_num(std::hex, argv[1]); + auto end = parse_num(std::hex, argv[2]); + auto font_file_path = argv[3]; + auto output_file_path = argv[4]; + + error = FT_Init_FreeType(&library); + if (error) { + std::cerr << "FT_Init_FreeType\n"; + return -1; + } + + error = FT_New_Face(library, font_file_path, 0, &face); + if (error) { + std::cerr << "FT_New_Face\n"; + return -1; + } + + error = FT_Select_Size(face, 0); + if (error) { + std::cerr << "FT_Select_Size: " << FT_Error_String(error) << ' ' << error << '\n'; + return -1; + } + + assert(end > start); + + uint32_t pixels_per_glyph = (face->size->metrics.height * face->size->metrics.max_advance); + assert(pixels_per_glyph % 8 == 0); + uint32_t bytes_per_glyph = pixels_per_glyph / 8; + uint32_t num_glyphs = (end - start) + 1; + uint8_t buf[bytes_per_glyph * num_glyphs]; + + uint32_t bitmap_offset = 0; + for (uint32_t char_code = start; char_code <= end; char_code++) { + int32_t bitmap_size = load_bitmap_char(face, + char_code, + &buf[bitmap_offset]); + if (bitmap_size < 0) { + std::cerr << "load_bitmap_char error\n"; + return -1; + } + + bitmap_offset += bitmap_size; + assert(bitmap_offset < (sizeof (buf))); + } + std::cerr << "bitmap_offset: 0x" << std::dec << bitmap_offset << '\n'; + + FILE * out = fopen(output_file_path, "w"); + if (out == NULL) { + perror("fopen(w)"); + return -1; + } + fwrite(reinterpret_cast(&buf), bitmap_offset, 1, out); + fclose(out); +} diff --git a/tools/ttf-convert.cpp b/tools/ttf-convert.cpp index 39d8446..6d51e4b 100644 --- a/tools/ttf-convert.cpp +++ b/tools/ttf-convert.cpp @@ -11,47 +11,6 @@ #include "../common/font.hpp" -/* -int load_bitmap_char(FT_Face face, FT_ULong char_code, uint8_t * buf) -{ - FT_Error error; - FT_UInt glyph_index = FT_Get_Char_Index(face, char_code); - - error = FT_Load_Glyph(face, glyph_index, FT_LOAD_DEFAULT); - if (error) { - printf("FT_Load_Glyph %s\n", FT_Error_String(error)); - return 0; - } - - assert(face->glyph->format == FT_GLYPH_FORMAT_BITMAP); - printf("num_grays %d\n", face->glyph->bitmap.num_grays); - printf("pitch %d\n", face->glyph->bitmap.pitch); - printf("width %d\n", face->glyph->bitmap.width); - assert(face->glyph->bitmap.width == 16); - printf("char_code %lx rows %d\n", char_code, face->glyph->bitmap.rows); - assert(face->glyph->bitmap.rows == 8 || (face->glyph->bitmap.rows % 8) == 0); - - for (int y = 0; y < (int)face->glyph->bitmap.rows; y++) { - uint8_t * row = &face->glyph->bitmap.buffer[y * face->glyph->bitmap.pitch]; - uint8_t row_out = 0; - for (int x = 0; x < face->glyph->bitmap.width; x++) { - int bit; - if (x < (int)face->glyph->bitmap.width) { - bit = (row[x / 8] >> (7 - (x % 8))) & 1; - } else { - bit = 0; - } - fprintf(stderr, bit ? "█" : " "); - row_out |= (bit << x); - } - fprintf(stderr, "\n"); - //buf[y] = row_out; - } - - return face->glyph->bitmap.rows * face->glyph->bitmap.pitch; -} -*/ - int32_t load_outline_char(const FT_Face face, const FT_ULong char_code, @@ -180,21 +139,13 @@ int main(int argc, char *argv[]) return -1; } - /* - error = FT_Select_Size(face, 0); - if (error) { - fprintf(stderr, "FT_Select_Size: %s %d\n", FT_Error_String(error), error); - return -1; - } - */ - std::stringstream ss3; int font_size; ss3 << std::dec << argv[3]; ss3 >> font_size; std::cerr << "font_size: " << font_size << '\n'; - error = FT_Set_Pixel_Sizes (face, 0, font_size); + error = FT_Set_Pixel_Sizes(face, 0, font_size); if (error) { std::cerr << "FT_Set_Pixel_Sizes: " << FT_Error_String(error) << error << '\n'; return -1; diff --git a/wordle/main_saturn.cpp b/wordle/main_saturn.cpp index 8afe441..90405b6 100644 --- a/wordle/main_saturn.cpp +++ b/wordle/main_saturn.cpp @@ -10,6 +10,7 @@ #include "../common/draw_font.hpp" #include "../common/palette.hpp" #include "../common/vdp2_func.hpp" +#include "../common/intback.hpp" #include "wordle.hpp" #include "draw.hpp" @@ -25,28 +26,6 @@ static struct draw_state draw_state; static struct wordle::screen wordle_state; -#define assert(n) if ((n) == 0) while (1); - -enum intback_fsm { - PORT_STATUS = 0, - PERIPHERAL_ID, - DATA1, - DATA2, - DATA3, - DATA4, - FSM_NEXT -}; - -struct intback_state { - int fsm; - int controller_ix; - int port_ix; -}; - -static intback_state intback; -static int oreg_ix; - - struct xorshift32_state { uint32_t a; }; @@ -63,107 +42,36 @@ uint32_t xorshift32(struct xorshift32_state *state) static xorshift32_state random_state = { 0x12345678 }; static uint32_t frame_count = 0; +void keyboard_callback(const enum keysym k, uint8_t kbd_bits) +{ + if (!KEYBOARD__MAKE(kbd_bits)) + return; + + int32_t c = keysym_to_char(k, false); + if (k == keysym::UNKNOWN) + return; + + if (c >= 'a' && c <= 'z') { + // uppercase + wordle::type_letter(wordle_state, c); + } else if (k == keysym::ENTER) { + wordle::confirm_word(wordle_state); + } else if (k == keysym::BACKSPACE) { + wordle::backspace(wordle_state); + } else if (k == keysym::ESC) { + random_state.a += frame_count; + uint32_t rand = xorshift32(&random_state); + wordle::init_screen(wordle_state, rand); + } +} + void smpc_int(void) __attribute__ ((interrupt_handler)); -void smpc_int(void) { +void smpc_int(void) +{ scu.reg.IST &= ~(IST__SMPC); scu.reg.IMS = ~(IMS__SMPC | IMS__V_BLANK_IN); - if ((smpc.reg.SR & SR__PDL) != 0) { - // PDL == 1; 1st peripheral data - oreg_ix = 0; - intback.controller_ix = 0; - intback.port_ix = 0; - intback.fsm = PORT_STATUS; - } - - int port_connected = 0; - int data_size = 0; - int peripheral_type = 0; - (void)peripheral_type; - int kbd_bits = 0; - - /* - This intback handling is oversimplified: - - - up to 2 controllers may be connected - - multitaps are not parsed correctly - */ - - while (oreg_ix < 32) { - reg8 const& oreg = smpc.reg.oreg[oreg_ix++]; - switch (intback.fsm) { - case PORT_STATUS: - port_connected = (PORT_STATUS__CONNECTORS(oreg) == 1); - if (port_connected) { - assert(PORT_STATUS__MULTITAP_ID(oreg) == 0xf); - } else { - intback.fsm = FSM_NEXT; - } - break; - case PERIPHERAL_ID: - peripheral_type = PERIPHERAL_ID__TYPE(oreg); - data_size = PERIPHERAL_ID__DATA_SIZE(oreg); - break; - case DATA1: - { - //controller_state& c = intback.controller[intback.controller_ix]; - } - break; - case DATA2: - break; - case DATA3: - kbd_bits = oreg & 0b1111; - break; - case DATA4: - { - uint32_t keysym = oreg; - - if (kbd_bits & 0b1000) { // Make - enum keysym k = scancode_to_keysym(keysym); - char16_t c = keysym_to_char16(k); - if (k != keysym::UNKNOWN) { - if (c >= 'a' && c <= 'z') { - // uppercase - wordle::type_letter(wordle_state, c); - } else if (k == keysym::ENTER) { - wordle::confirm_word(wordle_state); - } else if (k == keysym::BACKSPACE) { - wordle::backspace(wordle_state); - } else if (k == keysym::ESC) { - random_state.a += frame_count; - uint32_t rand = xorshift32(&random_state); - wordle::init_screen(wordle_state, rand); - } - } - - } else if (kbd_bits & 0b0001) { // Break - - } - } - break; - default: - break; - } - - if ((intback.fsm >= PERIPHERAL_ID && data_size <= 0) || intback.fsm == FSM_NEXT) { - if (intback.port_ix == 1) - break; - else { - intback.port_ix++; - intback.controller_ix++; - intback.fsm = PORT_STATUS; - } - } else { - intback.fsm++; - data_size--; - } - } - - if ((smpc.reg.SR & SR__NPE) != 0) { - smpc.reg.ireg[0] = INTBACK__IREG0__CONTINUE; - } else { - smpc.reg.ireg[0] = INTBACK__IREG0__BREAK; - } + intback::keyboard_fsm(keyboard_callback); } // rendering