Архитектура¶
Приложение — перенос katana.py на TypeScript: те же байты, тот же порядок транзакций, тот же формат YAML-профиля.
Новых возможностей протокола приложение не добавляет.
Репозиторий приложения не зависит от Python: эталоны и фикстуры лежат в tests/fixtures/, а обновляются генераторами Python-репозитория.
Структура¶
src/
├── shared/ Код без Node и DOM: работает и в main, и в окне
│ ├── protocol/
│ │ ├── constants.ts Опкоды, режимы, направления, действия, группы клавиш
│ │ ├── keymap.ts KEYMAP, BLIND и имена клавиш
│ │ ├── colors.ts Именованные цвета CSS
│ │ └── codec.ts build_payload, parse_*, bind-записи, блоб и XML макросов
│ ├── profile.ts Секции профиля, валидация, сборка таблицы paint
│ └── ipc.ts Типы и каналы IPC (KatanaApi)
├── main/
│ ├── index.ts Окно: contextIsolation, без nodeIntegration
│ ├── ipc.ts Обработчики IPC, диалоги, предпросмотр, last-profile.json
│ ├── profile.ts Чтение и запись YAML, apply в порядке cmd_apply
│ └── device/
│ ├── hid.ts Поиск интерфейса 0 и транспорт node-hid
│ ├── katana.ts Класс Katana: транзакции, ACK, суммы, пауза 40 мс
│ ├── commands.ts Команды уровня CLI: lighting, raw, paint, remap, macros, scan
│ ├── service.ts Единая очередь операций и журнал
│ └── reset.ts USB reset через пакет usb
├── preload/ Мост window.katana — реализация KatanaApi
└── renderer/src/
├── state.tsx Состояние: профиль, статус, журнал, автосохранение
├── App.tsx Панель профиля, вкладки, тестовая зона, общий обработчик нажатий
├── keyEvents.ts KeyboardEvent.code → LED-индекс, шина нажатий
├── keyboard/
│ ├── Keyboard.tsx SVG-клавиатура: выбор щелчком, рамкой и чекбоксами групп
│ ├── layout.json Геометрия из вендорской раскладки
│ ├── simulate.ts Приблизительная имитация 19 режимов
│ ├── observed.ts Таблицы, снятые с клавиатуры
│ └── ModePreview.tsx Предпросмотр: живые кадры или имитация
├── components/ Палитра цветов со своими цветами, значок-подсказка InfoTip
└── tabs/ Вкладки окна
Разделение процессов¶
Окно не обращается к железу напрямую: все вызовы идут через window.katana в main-процесс.
| Слой | Ответственность |
|---|---|
| renderer | Интерфейс, состояние профиля, предпросмотр |
| preload | Мост window.katana, реализующий KatanaApi |
| main | Доступ к устройству, файлам и диалогам |
| shared | Протокол, кодеки и типы, общие для всех слоёв |
Окно создаётся с contextIsolation и без nodeIntegration.
Очередь операций¶
KatanaService выстраивает операции в одну очередь, потому что прошивка не переносит пересекающиеся транзакции.
- пока операция выполняется, кнопки записи в окне недоступны;
- устройство открывается на время операции и закрывается;
- переподключение клавиатуры подхватывается без перезапуска приложения.
Предпросмотр раскраски¶
Живой предпросмотр включён всегда и коалесцирует кадры: если пришло несколько, пишется только последний, всегда без save.
Main-процесс помнит последние таблицу и яркость: таблица пишется только при изменении, а режим 0x80 — при первом кадре или смене яркости.
Ползунок «Яркость» на вкладке меняет сами цвета выбранных клавиш, а не яркость показа, поэтому кадр всегда уходит с одной и той же яркостью.
Живой кадр прошивка отдаёт умноженным на яркость показа, поэтому вкладка делит его обратно, чтобы чтение не затемняло раскраску.
Выбор клавиш общий для вкладок «Раскраска» и «Действия».
Нажатия на физической клавиатуре приходят в keyEvents.ts из общего обработчика в App.tsx; в полях ввода они не перехватываются, исключение — тестовая зона.
В «Раскраске» нажатие выбирает клавишу, повторное снимает выбор, а группы и ряды стали чекбоксами с промежуточным состоянием.
Палитра показывает цвет выбранных клавиш: у одного цвета подсвечен свотч и заполнено поле ввода, у разных цветов палитра пуста, без выбора — недоступна.
Свои цвета лежат вторым рядом свотчей и хранятся в localStorage окна.
Предпросмотр режимов¶
Пока вкладка «Подсветка» видна, окно включает поток liveFrames(true), а main в общей очереди читает readLiveFrame() и шлёт кадры событием katana:live-frame.
Схема чтения: f5 → 9 GET → commit, без save, пауза 5 мс, около 15 кадров в секунду.
Поток уступает очередь: как только в неё встаёт другая операция, он выходит и перезапускается после неё. Ошибки в журнал не пишутся, повтор — через секунду.
Если кадры не приходят дольше 1,5 секунды или клавиатура не подключена, simulate.ts считает кадры по формулам, около 20 кадров в секунду.
Имитация¶
Формулы подобраны по записям живых кадров:
| Режим | Поле фазы |
|---|---|
static спектр, ripple |
радиальное от клавиши O |
rotate |
угловое |
flow, wind |
линейное |
mountains |
две «горки»: сверху вниз и снизу вверх |
Периоды сняты на скорости 2, рост периодов со скоростью задаёт speedFactor, шлейф длится 0,3–0,4 секунды.
У mountains две волны-«горки» с вершиной в середине блока: одна идёт сверху вниз, другая снизу вверх.
Они смещены на полпериода и перекрываются, поэтому волна выглядит непрерывной, с острым углом посередине, и каждая клавиша вспыхивает дважды за период 1,35 секунды.
Параметры mountains в simulate.ts сняты с живой клавиатуры: 298 кадров потока 04 f5 за 25 секунд, затем подгонка по этим кадрам.
Средняя корреляция имитации с железом — 0,75, период 1,351 секунды, скорость фронта около 10 шагов в секунду, наклон гребня — одна клавиша на ряд, хвост вспышки 0,39 секунды.
Таблицы, которые формулой не описать, лежат в observed.ts:
- неподвижный кадр
static-спектра (STATIC_SPECTRUM); - палитра случайных цветов спектра, 86 цветов;
- шаг огонька
marqueeдля каждой клавиши; - начальный оттенок клавиш
flower.
Реактивные режимы повторяют записи с нажатиями: keypress гаснет и fade разгорается за 2,9 секунды, horizontal даёт волну со шлейфом по ряду, vertical — полосу без шлейфа, splash — узкое кольцо.
Спектральный fade помнит цвет клавиши после нажатия, поэтому prunePresses хранит последнее нажатие каждой клавиши.
Нажатия из тестовой зоны и щелчки по клавишам предпросмотра идут через keyEvents.ts.
Окно и меню¶
- штатное меню отключено целиком (
Menu.setApplicationMenu(null)), вместе с ним пропадают его ускорители; - для отладки в
npm run devDevTools можно открыть временной строкойwin.webContents.openDevTools()вsrc/main/index.ts; - внешние ссылки открываются в системном браузере, новые окна Electron не создаются;
- сведения о приложении приходят через
window.katana.appInfo(): название, версия, автор, лицензия и адрес берутся изpackage.json, версии среды — изprocess.versions.
Конфигурации отладки VS Code и команды сборки описаны в разделе Запуск и отладка.
Пакеты¶
Конфигурация electron-builder лежит в разделе build файла package.json.
70-ganss-katana.rulesкладётся вresources/;build/linux/after-install.shустанавливает правило в/etc/udev/rules.d/;after-remove.shудаляет правило;- нативные модули распаковываются из asar (
asarUnpack), пересборка не нужна (npmRebuild: false): prebuildsnode-hidиusbсовместимы с Electron через N-API.
Тесты¶
Тесты офлайновые и повторяют Python-набор. Состав фикстур и обновление эталонов описаны в разделе Тесты и фикстуры.