/*
 * kbd_raw.h — эксклюзивный raw-канал клавиатуры: держит битовую карту
 * «клавиша N зажата ПРЯМО СЕЙЧАС», обновляемую напрямую из
 * прерывания (декодирует PS/2 Scan Code Set 2 — make/break, тот же
 * протокол, что и в docs/samples/sprinterKeybLib.asm).
 *
 * Прецедент и мотивация — applications/PoP/docs/PORT_PLAN.md §2:
 * ESTEX kbhit/getch/getkey/CTRLKEY — событийные (нажатие, без
 * release), обычные клавиши (не модификаторы) вообще не имеют
 * live-state в ESTEX; трамплин кадрового прерывания
 * (libc/irq/_irq_tramp.c) в штатном режиме отдаёт клавиатурные байты
 * ПРЯМО в DSS, не читая порт данных — байт из аппаратного приёмного
 * регистра можно прочитать только ОДИН раз, поэтому «подсмотреть, не
 * мешая DSS» невозможно технически.
 *
 * ЖИЗНЕННЫЙ ЦИКЛ И ГЛАВНОЕ СЛЕДСТВИЕ: пока kbd_raw_open() активен, ВЕСЬ
 * поток клавиатурных байт достаётся ЭТОМУ модулю — DSS их не видит.
 * kbhit/getch/getkey/CTRLKEY (в т.ч. kbd_mod_state) НЕ получают новых
 * событий, пока raw-канал открыт.  Приложение обязано проверять «выход»
 * (например ESC — KBD_ESC) через kbd_raw_down() само.  Перед экранами,
 * которым нужен обычный ESTEX-ввод (диалоги/меню на консольном I/O),
 * закрыть канал kbd_raw_close().  Паттерн один в один как у
 * cbl_open()/cbl_close() (<cbl.h>) — тот же приватный IM2-хук, не
 * чейнящийся к DSS.
 *
 * КОДЫ КЛАВИШ: значения ниже — общеизвестный стандарт AT/PS-2 Scan
 * Code Set 2 (не специфика Sprinter).  TODO до использования в PoC:
 * подтвердить в MAME, что реальный поток байт с SIO-A клавиатуры
 * Sprinter именно этот набор кодов (см. defer_unexplained_quirks).
 */
#ifndef KBD_RAW_H
#define KBD_RAW_H

#include <stdint.h>

/* Включить raw-канал: 0 / -1+errno (EBUSY — уже открыт; прочие коды —
 * см. errno.h, как у irq_install/cbl_open — общий IM2-механизм). */
int  kbd_raw_open(void);

/* Выключить raw-канал, вернуть клавиатуру DSS.  Идемпотентно; висит
 * на atexit (страховка, как у cbl_close). */
void kbd_raw_close(void);

/* Зажата ли клавиша code ПРЯМО СЕЙЧАС (0/1).  code вне 0..511 — 0
 * (защитно).  Для расширенных клавиш (стрелки и т.п., префикс 0xE0
 * на проводе) прибавить KBD_EXT к базовому коду. */
uint8_t kbd_raw_down(uint16_t code);

/* Восстановление после Rx-overrun SIO: звать РАЗ В КАДР (до чтения
 * kbd_raw_down).  Аппаратный FIFO SIO 3 байта; при длинных DI-окнах пачка
 * скан-кодов (напр. быстрый тап стрелки: make+break = 5 байт) переполняет
 * его → потерян break → залипшая клавиша.  Трамплин ловит overrun и
 * взводит флаг; kbd_raw_sync по флагу сбрасывает held-состояние всех
 * клавиш КРОМЕ модификаторов (Shift/Ctrl/Alt): PS/2 автоповторяет только
 * последнюю нажатую клавишу, поэтому обычные зажатые перечитаются
 * typematic'ом, а сброшенный модификатор восстановить нечем — он бы
 * «отваливался» при каждом overrun (см. kbd_raw_sync.c). */
void kbd_raw_sync(void);

/* Вычерпать приёмный FIFO ОПРОСОМ, не дожидаясь прерывания.  Возвращает
 * 0 (FIFO был пуст, ~40 тактов) или 1 (что-то вычерпано и декодировано в
 * ту же карту, что ведёт трамплин).
 *
 * Зачем: на каждый принятый байт запрос прерывания живёт единицы
 * микросекунд, и пропущенный импульс не «догоняется» — байт лежит в
 * 3-байтовом FIFO до следующего прерывания.  Пачки в 5 байт (тап стрелки;
 * при зажатом Shift — ещё и «fake shift» вокруг расширенного кода) при
 * этом переполняют FIFO: теряется make («нажатие не сработало») или break
 * (залипание).
 *
 * КАК ЕЁ ЗВАТЬ (замеры 2026-08-01, PoP roomtest — см.
 * applications/PoP/roomtest/TASKS.md, KBD-1):
 *  - «несколько вызовов за кадр, после тяжёлых фаз» НЕ ДАЁТ НИЧЕГО —
 *    потери те же, что без них: такие вызовы попадают в участки, где
 *    прерывания и так разрешены, и лишь дублируют трамплин;
 *  - работает только ПЛОТНЫЙ опрос, порядка раза в 0.5 мс.  Столько
 *    времени есть даром в ожидании кадра, поэтому штатный способ —
 *    повесить эту функцию idle-хуком графики:
 *        gfx_set_idle_hook(my_poll_wrapper);   // <gfx.h>
 *    Проверено: 35 нажатий стрелки с зажатым Shift → 35 дошедших make
 *    против 9 из 10 без хука.
 * Не ставить в игровой цикл «на всякий случай» — сначала померить.
 *
 * Тело идёт под DI и БЕЗУСЛОВНО делает EI на выходе: рассчитано на вызов
 * из главного цикла, из ISR звать нельзя. */
uint8_t kbd_raw_poll(void);

#define KBD_EXT        0x0100   /* база кода была расширенной (0xE0-префикс) */

/* Позиционные коды PS/2 Set 2 — то, что реально нужно платформеру.
 * TODO: подтвердить в MAME перед PoC (см. предупреждение выше). */
#define KBD_UP         (KBD_EXT | 0x75)
#define KBD_DOWN       (KBD_EXT | 0x72)
#define KBD_LEFT       (KBD_EXT | 0x6B)
#define KBD_RIGHT      (KBD_EXT | 0x74)
#define KBD_SPACE      0x29
#define KBD_ENTER      0x5A
#define KBD_ESC        0x76
#define KBD_LSHIFT     0x12
#define KBD_RSHIFT     0x59
#define KBD_LCTRL      0x14
#define KBD_LALT       0x11
#define KBD_RCTRL      (KBD_EXT | 0x14)
#define KBD_RALT       (KBD_EXT | 0x11)

#endif
