Перейти к содержанию

Архитектура

Приложение — перенос 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 dev DevTools можно открыть временной строкой 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): prebuilds node-hid и usb совместимы с Electron через N-API.

Тесты

Тесты офлайновые и повторяют Python-набор. Состав фикстур и обновление эталонов описаны в разделе Тесты и фикстуры.