Своя оболочка MOffice Write (без Qt)
2026-10-04. Поручение владельца: «сделать такую же оболочку и использовать свою оболочку» (решение № 39). Снимки настоящего окна своей оболочки
ui/meridian-writeна собственном наборе компонентовui/meridian-ui— рядом со снимками прототипа на Qt 6.8 («Живая оболочка»). Страницы документов рисует ядро через тот же C ABImeridian-ffi, примеры — реальные docx GovDocs1 (общественное достояние). Анализ вариантов, оценки и план —docs/research/own-shell.md.
Как сделано
Окно, ввод и вывод кадра — свой слой meridian-platform
(Cocoa, Win32, X11 — без сторонних библиотек), растр — свой
meridian-raster (общий с ядром), диалог «Открыть» — свой;
текст — свой meridian-font (общий с ядром, сессия A: разбор
OpenType, метрики, контуры, кернинг из GPOS); шрифт интерфейса Inter 4.1
вшит. Всё остальное — свой код: компоненты MDS (копии
shell/mds/components/*.qml), ввод, иконки, лента с
адаптивным сворачиванием групп, холст, панели, палитра команд,
backstage. Цвета и размеры — из spec/design-tokens.json
через тот же генератор, что делает QML-токены. Все зависимости — классы
0–1 политики лицензий (cargo deny — ok), Qt не нужен ни при
сборке, ни при запуске.
Сравнение с Qt-прототипом
Сходство кадров считается SSIM (1.0 — совпадение до пикселя) по окнам
8×8 после размытия 1 px — тем же способом, каким ядро сравнивает
страницы с эталоном. Мелкие различия: образцы стилей у своей оболочки
набраны Carlito, как требует токен fontFamily.document (у
Qt на этой машине Carlito не установлен, и он берёт системный шрифт);
знак «⌄» и «⇧» — из запасного шрифта.
| Сцена | SSIM |
|---|---|
| Светлая тема, 1440×900, реальный docx (7 страниц) | 0.985 |
| Тёмная тема, docx с табуляциями | 0.983 |
| Палитра команд | 0.968 |
| Backstage, тёмная тема | 0.996 |
| Узкое окно 1024×700 (адаптивная лента) | 0.984 |
| Новый документ | 0.982 |
Светлая тема — своя оболочка

Светлая тема — Qt 6.8 (для сравнения)

Тёмная тема — своя оболочка

Палитра команд
⌘⇧P / Ctrl+Shift+P — своя оболочка

Список шрифтов на ленте — предпросмотр семейств

Панель «Навигация» — оглавление документа из ядра (FFI v0.3)
Заголовки с отступом по уровню (w:outlineLvl), текущий
раздел подсвечен по прокрутке, щелчок — переход к странице заголовка;
длинный список прокручивается колесом. Новый документ создаётся по
локали: для ru-RU — A4 с полями Word.

Вкладка «Вид», тема «Высокий контраст»

Фокус с клавиатуры — Tab дошёл до вкладки «Вид»
Tab и Shift+Tab ходят по всем кнопкам, вкладкам и строкам списков в
порядке кадра, стрелки — к ближайшему элементу в ту сторону (сначала в
том же ряду: по вкладкам, вниз — в команды ленты), Enter и пробел
нажимают элемент в фокусе. Рамка — по токенам
borderWidth.focus и focusOffset (2 px, цвет
акцента); видна только после работы с клавиатуры и прячется при щелчке
мышью. Открытая палитра, всплывающее окно, backstage или диалог
«Открыть» замыкают фокус внутри себя.

Подсказки клавиш (Alt) — буквы у команд вкладки «Главная»
Одиночный Alt (на Windows и F10) показывает буквы у «Файл» и вкладок ленты, буква открывает вкладку и показывает коды её команд, следующий код нажимает команду; Esc — шаг назад, щелчок мышью — закрыть. Коды подбираются по подписям без повторов; если команд больше, чем букв, коды на уровне двухбуквенные, как в Office. Набор на латинской раскладке переводится по положению клавиш (ЙЦУКЕН), поэтому подсказки работают при любой раскладке. На macOS подсказки не включаются: там так не принято, Option набирает символы.

Всплывающая подсказка — название и сочетание клавиш
После 600 мс наведения под элементом появляется плашка с его названием и, если есть, сочетанием клавиш. После щелчка подсказка молчит, пока указатель не уйдёт с элемента. Кнопки, на которых только глиф или иконка («Ж», «⁝≡»), подписаны по-человечески («Полужирный», «Маркеры»): эти же названия используют коды Alt, тесты и будущий мост доступности. Окно перерисовывает кадр по таймеру, без постоянного опроса.

Backstage — своя оболочка

Узкое окно 1024×700 — группы ленты сворачиваются, панели скрыты

Новый документ — пустой лист из ядра

Свой платформенный слой: настоящее окно macOS (снято системой)
Окно, ввод и вывод кадра — свой ui/meridian-platform без
сторонних библиотек (решение № 40): AppKit и CoreGraphics через objc
runtime. Снимок сделан утилитой macOS screencapture -l
только этого окна: системные кнопки поверх своего заголовка, тема и
Retina — от системы.

Меню macOS — своим кодом через Cocoa
Строка меню: «MOffice Write» (о программе, скрыть, выйти), «Файл»
(новый ⌘N, открыть ⌘O, закрыть ⌘W), «Правка» (стандартные пункты ОС —
заработают с правкой документа), «Вид» (палитра ⇧⌘P, лента, масштаб
⌘=/⌘-/⌘0, панели, темы), «Окно» (свернуть ⌘M, масштабировать). Свои
пункты шлют оболочке Event::Menu(id); клавиши с ⌘
перехватывает само меню, как у всех программ macOS. Проверено на
настоящем окне: MERIDIAN_DUMP_MENU=1 печатает меню,
прочитанное обратно из объектов NSMenu (пять меню, сочетания ⌘/⇧/⌥);
--menu 21 выполняет пункт «Тёмная тема» через
performActionForItemAtIndex: — кадр тёмный,
--menu 10 открывает палитру. Сочетания в палитре и
подсказках на macOS записаны знаками ОС (⌘N, ⇧⌘P, ⌃F1).
Linux (X11) — настоящий ввод под Xvfb
Тот же слой на X11 (Xlib подгружается при запуске). На сервере под
виртуальным экраном xdotool щёлкнул вкладку «Вид» и кнопку
«Тёмная» — снимок окна средствами X (import -window):

Набор с клавиатуры: Ctrl+Shift+P, латиница «kontr»,
затем в русской раскладке «тёмн» (с «ё») — найдена «Тёмная тема», Enter
включает её:

Linux (X11) — подсказки клавиш и изменение размера окна без рамки
Под Xvfb с оконным менеджером Openbox: xdotool key alt —
буквы у вкладок, затем «b» (на ЙЦУКЕН — «И») открывает «Вид» с кодами
команд; удержание Alt с автоповтором включает подсказки один раз (сверху
вниз — три кадра).

Окно без рамки оконного менеджера меняет размер за полосу у края (6
логических px, в углах 12): курсор меняется на стрелку, нажатие
передаётся оконному менеджеру через _NET_WM_MOVERESIZE.
Проверено xdotool: правый край — ширина 1440 → 1620 (до
края экрана), правый нижний угол — −147 × −97 px; щелчок внутри окна
после этого по-прежнему доходит до оболочки (вкладка «Ссылки»),
раскладка перестроилась под 1473×803.

На Windows и Linux кнопки окна рисует сама оболочка — справа в
заголовке, по 46 px, как в Windows 11: «Свернуть»,
«Развернуть/Восстановить» (значок меняется в развёрнутом окне),
«Закрыть» (при наведении красная); на macOS остаются системные кнопки.
Проверено под Openbox щелчками xdotool: развернуть —
1800×1200 на весь экран, восстановить — 1440×900 на прежнем месте,
свернуть — окно скрыто (IsUnMapped) и возвращается, закрыть
— выход программы с кодом 0.

Доступность — дерево для экранных чтецов (macOS)
Каждый кадр оболочка собирает дерево доступности из того, что уже
знает: подписи элементов, роли (кнопка, вкладка, строка списка), рамки и
фокус клавиатуры; порядок — смысловой (заголовок → лента → навигация →
документ → стили → строка состояния), при открытой палитре или диалоге —
только их элементы. На macOS дерево становится объектами
NSAccessibilityElement под представлением окна: VoiceOver
читает роли и подписи, нажатие экранным чтецом приходит оболочке
щелчком. Проверено на настоящем окне: дерево прочитано обратно из
объектов Cocoa (--ax-dump: 71 элемент,
AXRadioButton «Главная»,
AXButton «Закрыть документ»…),
--ax-press "Вид" через
accessibilityPerformPress открывает вкладку «Вид». Windows
(UI Automation) — следующий шаг.
Доступность на Linux — AT-SPI 2 через свой D-Bus
На Linux то же дерево отдаётся по AT-SPI 2 — протоколу, через который
Orca и другие экранные чтецы читают программы. Клиент D-Bus свой
(meridian-platform/src/dbus.rs, без libdbus): адрес
unix:path=/unix:abstract=, вход EXTERNAL,
сообщения little-endian со всеми типами D-Bus; кодирование сверено
побайтно с эталонным Hello. Мост (atspi.rs)
берёт адрес шины доступности у org.a11y.Bus (или из
AT_SPI_BUS_ADDRESS), встраивается в реестр
(Socket.Embed) и в своём потоке отвечает на вызовы:
приложение → окно (frame) → кнопки, вкладки и строки списка с
интерфейсами Accessible, Component (рамки в координатах экрана и окна,
попадание по точке), Action (click), Application и Cache.
DoAction приходит циклу окна событием
AccessibilityPress — как нажатие VoiceOver на macOS. Смена
состава, подписей и фокуса уходит сигналами
object:children-changed,
property-change:accessible-name,
state-changed:focused, фокус окна —
window:activate. NO_AT_BRIDGE=1 выключает
мост, как в GTK.
Проверено на сервере под Xvfb с настоящими
at-spi-bus-launcher и at-spi2-registryd и
независимым клиентом pyatspi (библиотека libatspi, на
которой работает Orca):
- в реестре — приложение «MOffice Write» (toolkit «MOffice»), у него окно «420596.docx — MOffice Write» в состоянии «активно», у окна 51 элемент: 37 кнопок и 14 вкладок с подписями и рамками, совпадающими с кадром;
- попадание по точке экрана находит «Палитру команд»;
doAction(0)на вкладке «Вставка» открывает её — на ленте появляются «Титульная страница», «Таблица», «Рисунки», «Диаграмма»; клиент получил 17 событий добавления и 14 удаления детей;Tabс клавиатуры (xdotool) переводит фокус — приходитstate-changed:focusedдля «Палитры команд» и «Документ 1»;- сама оболочка читает своё дерево обратно через шину
(
--ax-dump: 75 элементов) и нажимает «Вид» черезAction.DoAction(--ax-press "Вид": ok).
Редкие письменности — из системных шрифтов
Вшитых Inter, Carlito и Liberation хватает для латиницы, кириллицы и
греческого; китайский, японский, корейский, армянский, грузинский,
тайский и другие берутся из шрифтов ОС. Папки шрифтов просматриваются
один раз, файлы читаются по мере надобности — сначала по подсказке
письменности в имени, в коллекции выбирается обычное начертание;
найденное и ненайденное кэшируется, объём прочитанного ограничен. На
строку из семи письменностей macOS читается 5 файлов (Hiragino, Apple SD
Gothic Neo, Noto Sans Armenian, SF Georgian, Thonburi). Снимок — сцена
--scene scripts --system-fonts. Сложные письменности
(арабская, деванагари) пока без шейпинга — его даст
meridian-shape ядра.

Печать и экспорт в PDF
Печатает ядро: документ выводится в PDF/A-2b
(meridian_doc_export_pdf), а PDF отдаётся ОС — на macOS
системным окном печати (PDFKit, лист у окна), на Linux — в CUPS
(lp, принтер по умолчанию), на Windows — командой «печать»
программы для PDF. Вызов — ⌘P/Ctrl+P, палитра, меню «Файл», backstage
«Печать»; раздел «Экспорт» сохраняет PDF рядом с документом и копирует
путь. Проверено: сквозной тест (экспорт, путь, запрос печати); macOS —
печать системой в файл (MERIDIAN_PRINT_TO, без окна): 7
страниц из 7, PDF от Quartz, страница совпадает с документом; Linux —
CUPS в контейнере: без принтера понятная ошибка, с виртуальным
PDF-принтером — 7 страниц.
Анимации по токенам
Длительности и кривые — из spec/design-tokens.json
(duration.*, cubicBezier.*; генератор выдаёт
их и в QML, и в Rust). Полоска под активной вкладкой ленты переезжает к
новой за duration.base по кривой standard,
палитра появляется со сдвигом 8 px за duration.fast
(out), задержка подсказки —
duration.tooltipDelay. Пока идёт переход, окно рисует кадр
каждые 16 мс, потом перестаёт; при системной настройке «уменьшить
движение» (macOS, Windows) переходы мгновенные. Проверено: модульные
тесты кривых и переходов, сквозной (полоска в начале и в конце, сдвиг
палитры 8 px), настоящее окно — палитра из меню за 5 кадров, затем
покой.
Ввод через систему (IME) — macOS
Представление окна — клиент системы ввода текста
(NSTextInputClient): обычные нажатия идут через
interpretKeyEvents:, поэтому работают методы ввода
(китайский, японский, корейский) и «мёртвые» клавиши (⌥E → «é»);
черновик набора приходит оболочке событием ImePreedit и
рисуется подчёркнутым, подтверждённый текст — обычным вводом; окно
кандидатов ОС ставится у поля ввода (set_ime_area). Команды
с ⌘ и служебные клавиши идут оболочке напрямую, пока нет черновика.
Проверено на настоящем окне (--text-input): черновик
«にほん» → подтверждение «日本» → синтетические нажатия «ab тест» через
систему ввода — в палитре «日本ab тест»; черновик без подтверждения —
ниже.

На Linux текст идёт через метод ввода XIM
(XOpenIM/XCreateIC, XFilterEvent,
Xutf8LookupString): составные символы, «мёртвые» клавиши и
IME (ibus, fcitx через XMODIFIERS). Проверено под Xvfb на
sagr: раскладка us intl, xdotool key dead_acute e,
dead_acute a, dead_diaeresis u → в палитре
«éáü»; раскладка ru — «тёмн», как прежде.
Буфер обмена — свой на трёх ОС
macOS — NSPasteboard, Windows — Clipboard API
(CF_UNICODETEXT), Linux — выделение CLIPBOARD X11: чтение
через XConvertSelection, запись — владение выделением и
ответы на запросы других программ (TARGETS, UTF8_STRING, STRING). Сейчас
буфер работает в палитре (⌘V/Ctrl+V дописывает запрос) и в диалоге
«Открыть» (вставленный путь к папке — переход, к файлу — открытие;
⌘C/Ctrl+C — путь выделенного); правка документа подключится к нему в
фазе 1. Проверено: X11 под Xvfb — оболочка читает текст, положенный
xclip, а xclip читает текст оболочки
(кириллица и «✓»); macOS — чтение общего буфера совпадает с
pbpaste, круг записи и чтения — на отдельном именованном
буфере (MERIDIAN_PASTEBOARD), чтобы не трогать буфер
пользователя; Windows — сборка и clippy.
Linux x86_64 — тот же кадр, снятый на сервере без экрана
Сборка в контейнере rust на сервере sagr (Linux x86_64)
за 2 минуты; программа зависит только от libc,
libm и libgcc_s — X11 и Wayland подгружаются
при запуске, для сборки не нужны. Все 39 тестов проходят и там. Кадр
светлой темы с Linux совпадает с кадром macOS arm64 на 99,92 % пикселей
(остальное — сглаживание краёв на разном SIMD). Ниже — тёмная тема с
палитрой команд, снятая на Linux.

Что проверено
| Проверка | Результат |
|---|---|
| Тесты | 27 модульных meridian-ui, 17 модульных и 27
сквозных meridian-write, 1 модульный
meridian-platform — щелчки и клавиши как у пользователя
(тема с ленты, палитра с клавиатуры, вкладки документов, масштаб,
переход по страницам реального docx, сворачивание ленты и «подсмотр»,
список шрифтов, backstage и шаблоны, узкое окно, панели, пустое
состояние, оглавление, свой диалог «Открыть», фокус с клавиатуры и его
замыкание в палитре, подсказки клавиш с одно- и двухбуквенными кодами,
свои кнопки окна, всплывающие подсказки с задержкой, стрелки по вкладкам
и ленте, вставка и копирование, анимации, экспорт в PDF и печать, дерево
доступности, черновик IME) |
| Настоящее окно macOS | тема из системы, Retina 2×, фоновый рендер страниц ядра,
перетаскивание за свой заголовок, двойной щелчок — развернуть; дымовой
запуск just native-smoke |
| Linux x86_64 | сборка за 2 мин в контейнере rust на сервере, 39
тестов, безоконные снимки; clippy -D warnings чистый; кадр
совпадает с macOS на 99,92 % пикселей |
| Скорость | кадр 2880×1800 на CPU — 7,4 мс медиана
(just native-bench) |
| Размер | один файл ≈ 14 МБ (macOS arm64) — интерфейс, шрифты и ядро |
| Лицензии | cargo deny check — advisories, bans, licenses, sources:
ok; Inter (OFL-1.1) — в third_party.toml, NOTICE и
SBOM |
Как запустить
just native-run docs/shell/samples/420596.docxjust native-shotsПараметры окна те же, что у Qt-оболочки:
--theme light|dark|hc, --size 1440x900,
--scene palette|backstage|collapsed|font-popup|open-dialog|focus|keytips|tooltip,
--screenshot файл.png (безоконный снимок, работает и на
сервере без экрана). Горячие клавиши — как в Qt-версии
(⌘⇧P/Ctrl+Shift+P и Alt+Q —
палитра, ⌘N, ⌘O, ⌘W,
⌘=/⌘-/⌘0, Ctrl+Tab,
Ctrl+F1, ⌘⇧S), не зависят от раскладки.
Чего пока нет
Доступность на Windows (UI Automation) и текст документа для экранных
чтецов, черновик IME на Windows и Linux, GPU-рендер, привязки Snap —
план и оценки в own-shell.md §5. Правка
документа появится вместе с моделью правки ядра (фаза 1), как и у
Qt-прототипа.