Своя оболочка MOffice без Qt: анализ, решение, прототип
2026-10-04, сессия B «Платформа». Поручение владельца: «Нам нужно сделать такую же оболочку и использовать свою оболочку» — то есть оболочку с тем же видом и поведением, что у прототипа на Qt 6.8 (B3), но на собственном коде, без Qt. Решение записано в библию (§4, §6, §12, решение № 39) и в
docs/13-owner-decisions.md§1. Код —ui/meridian-ui(набор компонентов) иui/meridian-write(окно Write); снимки и сравнение — страница «Своя оболочка».
1. Почему не Qt
| Довод | Суть |
|---|---|
| Коммерческая Qt, скорее всего, недоступна | По открытым источникам Qt Group с марта 2022 не продаёт коммерческие
лицензии и поддержку в России
(docs/legal/qt-quote-request.md). Ждать ответа и строить на
этом план — риск для всей оболочки |
| LGPL противоречит решению № 35 | «Полностью свой код под своей лицензией», класс 2 (LGPL) не используем. У LGPL обязательства: только динамическая линковка, право пользователя заменить библиотеки Qt, выкладка исходников Qt, текст LGPL в поставке, сложности с магазинами приложений |
| Зависимость от вендора | Лицензионные условия, цены (≈ 3 600–3 950 $ за разработчика в год) и санкционная политика — вне нашего контроля |
| Два языка и мост | C++/QML + Rust через C ABI: две сборочные системы (CMake + cargo), две команды, отладка на стыке |
| Размер и поставка | Qt — десятки мегабайт библиотек рядом с программой; своя оболочка — один файл ≈ 14 МБ (macOS arm64, release) со шрифтами и ядром |
2. Что рассматривали
| Вариант | Лицензия | Почему нет / да |
|---|---|---|
| Qt 6 коммерческая | платная | вероятный отказ продаж в РФ; дорого; вендор |
| Qt 6 LGPLv3 | класс 2 | противоречит решению № 35, ограничения поставки |
| Slint (Rust) | GPLv3 / «royalty-free» с условиями / коммерческая | не класс 0–1; условия бесплатной лицензии меняются вендором |
| GTK 4 | LGPL | класс 2; на Windows и macOS чужой вид |
| Flutter | BSD-3 | второй язык (Dart) и свой движок на C++; FFI с ядром; вид Material/Cupertino — переделывать всё |
| Electron / Tauri (WebView) | MIT / Apache | веб-движок: память, задержка ввода, разный WebView на ОС; офисный пакет с тяжёлым холстом — не наш путь (библия §4) |
| egui | MIT/Apache | немедленный режим близок к нашему, но вид и ввод под инструменты, ленту и MDS всё равно писать самим; плюс зависимость от чужой архитектуры |
| iced, Xilem/Masonry, GPUI | MIT / Apache | iced — архитектура Elm, своя модель виджетов; Xilem — ранняя стадия; GPUI привязан к Zed и неполон на Windows |
Свой набор meridian-ui на «кирпичах» класса
0–1 |
наш код + MIT/Apache | выбрано: тот же растр и текст, что в ядре; полный контроль над видом MDS; один язык; одна сборка |
3. Что взяли и что написали сами
| Слой | Берём (класс 0–1) | Пишем сами |
|---|---|---|
| Окно, события ОС, HiDPI, Wayland/X11/Win32/Cocoa | winit 0.30 (Apache-2.0; без Adwaita-рамки — она тянула
заброшенный ttf-parser) |
перетаскивание за свой заголовок, двойной щелчок — развернуть, отступ под кнопки окна macOS |
| Вывод кадра в окно | softbuffer 0.4 (MIT/Apache) — без GPU |
преобразование кадра, кэш масок |
| Растр | tiny-skia 0.12 (BSD-3) — тот же, что в
ядре |
слой рисования paint.rs: обрезка без масок для
прямоугольников, построчное копирование страниц, тени |
| Текст | harfrust (MIT) + skrifa (MIT/Apache) —
те же, что в ядре (с 2026-10-04 — свой
meridian-font, §8) |
text.rs: комплект шрифтов, подстановка недостающих
символов, синтетический курсив, кэши, усечение «…», перенос |
| Шрифты | Inter 4.1 (OFL-1.1) — токен fontFamily.ui; Carlito и
Liberation — из ядра |
— |
| Иконки | — | svg.rs: свой разбор подмножества SVG (контуры с дугами
по SVG 1.1 F.6, круги, прямоугольники) |
| Токены | spec/design-tokens.json |
генератор scripts/gen_shell_assets.py выдаёт и QML, и
Rust (tokens.rs, icons_gen.rs) — один источник
истины |
| Ввод | — | ui.rs: немедленный режим, попадание по прошлому кадру,
верхний элемент, щелчок/двойной щелчок, модальные слои, обрезка |
| Компоненты MDS | — | widgets.rs: копии
shell/mds/components/*.qml — кнопки 4 видов, кнопки и
переключатели ленты, поле со списком, карточка стиля, вкладка документа,
боковая панель, ползунок масштаба |
| Окно Write | — | app.rs, ribbon.rs: заголовок, лента 8
вкладок с адаптивным сворачиванием групп (перенос
RibbonPage.relayout()), холст, панели, строка состояния,
палитра команд, backstage, списки |
| Ядро | C ABI meridian-ffi v0.2 — тот же контракт, что
у Qt-оболочки |
kernel.rs: безопасная обёртка и фоновый рендер страниц
(2–4 потока, кэш растров) |
| Диалог «Открыть» | rfd 0.15 (MIT; на Linux — портал XDG по D-Bus, без
GTK) |
— |
4. Результат прототипа (2026-10-04)
| Показатель | Значение |
|---|---|
| Сходство с Qt-оболочкой (SSIM по 6 снимкам 1440×900 и 1024×700, 1.0 — до пикселя) | светлая 0.985, тёмная 0.983, палитра 0.968, backstage 0.996, узкое окно 0.984, новый документ 0.982 |
| Время кадра 2880×1800 (Retina 2×), CPU M5 Max, release | 8,7 мс медиана, 10,3 мс 95-й процентиль (было 27 мс до оптимизаций: обрезка без масок, построчное копирование страниц, тени полосками, без лишней заливки кадра) |
| Размер программы (macOS arm64, release) | ≈ 14 МБ одним файлом — интерфейс, шрифты, ядро; Qt рядом не нужен |
| Объём кода | ≈ 7 200 строк Rust (из них 520 — сгенерированные токены и иконки); Qt-версия — ≈ 3 400 строк QML/C++ поверх Qt |
| Тесты | 39: 19 модульных meridian-ui (цвет, геометрия, текст,
разбор всех 71 иконки, рисование и обрезка, ввод, размеры компонентов),
8 модульных meridian-write (ядро, рендер в фоне, лента,
поиск палитры), 12 сквозных (щелчки и клавиши как у
пользователя: тема, палитра, вкладки, масштаб, навигация по реальному
docx, сворачивание ленты, списки, backstage, узкое окно, панели) |
| Настоящее окно | macOS: тема из системы, Retina 2×, фоновый рендер страниц,
перетаскивание, --quit-after/--dump-frame для дымовой
проверки |
| Linux x86_64 | сборка за 2 мин в контейнере rust на sagr, 39 тестов,
clippy -D warnings чистый; зависимости программы — только
libc/libm/libgcc_s (X11/Wayland
подгружаются при запуске); кадр совпадает с macOS arm64 на 99,92 %
пикселей |
| Лицензии | cargo deny: advisories, bans, licenses, sources — ok;
THIRD_PARTY.md/NOTICE/SBOM перегенерированы
(Inter — в third_party.toml) |
Найдено и исправлено по ходу (тестами): результат щелчка показывался кадром позже — теперь после события сразу строится ещё один кадр; маска обрезки копировалась на каждый примитив (5 МБ) — теперь по указателю и только для фигур на краю.
5. Чего ещё нет (честно) и оценка
| Область | Что нужно (своим кодом, решение № 40) | Оценка, чел.-мес. |
|---|---|---|
| Доступность | macOS — сделано 2026-10-04: дерево доступности из
подписей, ролей (кнопка, вкладка, строка списка), рамок и порядка фокуса
(группы: заголовок → лента → панели → документ → строка состояния;
модальный слой — только он) → объекты
NSAccessibilityElement под представлением, нажатие экранным
чтецом → щелчок; Linux — сделано 2026-10-05: AT-SPI 2
через свой клиент D-Bus (Embed в реестр,
Accessible/Component/Action/Application/Cache, сигналы состава, подписей
и фокуса), проверено клиентом pyatspi под Xvfb; дальше — UI Automation
(Windows), текст документа и проверка с VoiceOver/NVDA/Orca |
1–1,5 |
| Ввод текста в документ | буфер обмена — сделано 2026-10-04 (NSPasteboard,
Win32 Clipboard, выделение CLIPBOARD X11 с ответами другим программам;
вставка в палитру и путей в диалог «Открыть», копирование пути);
IME на macOS — сделано 2026-10-04
(NSTextInputClient: набор через
interpretKeyEvents:, черновик setMarkedText: →
Event::ImePreedit, окно кандидатов у поля ввода, «мёртвые»
клавиши; в палитре); XIM на Linux — сделано
(XOpenIM/XCreateIC, XFilterEvent,
Xutf8LookupString: составные символы, «мёртвые» клавиши,
IME через XMODIFIERS); IMM32/TSF (Windows, сейчас —
подтверждённый текст через WM_CHAR), курсор и выделение —
вместе с моделью правки ядра (фаза 1) |
0,8–1,3 |
| GPU-рендер | свой путь через Metal / Direct3D / Vulkan для 4K/5K и анимаций — по
замерам; CPU-путь (meridian-raster, 7,4 мс на 2880×1800)
остаётся основным |
1,5–2 |
| Подсказки, анимации, фокус с клавиатуры | фокус с клавиатуры и подсказки клавиш Alt — сделано
2026-10-04: Tab/Shift+Tab по всем кнопкам и вкладкам, Enter и
пробел нажимают, рамка по токенам
borderWidth.focus/focusOffset, модальные слои
замыкают фокус; одиночный Alt (F10 на Windows) — коды у вкладок и команд
по подписям, двухбуквенные при нехватке букв, любая раскладка;
всплывающие подсказки — сделано: 600 мс, название и
сочетание клавиш, человеческие подписи кнопок-иконок, перерисовка по
таймеру; стрелки — сделано: при видимой рамке фокус
идёт к ближайшему элементу в ту сторону, сначала в том же ряду (вкладки,
команды ленты, списки); анимации — сделано:
anim.rs (cubic-bezier и длительности из токенов, переходы
по элементу, смена цели с текущего значения), полоска вкладок ленты и
появление палитры, «уменьшить движение» ОС (macOS, Windows), кадры по 16
мс только пока идёт переход |
— |
| Своя рамка окна на Windows/Linux | изменение размера за края — сделано 2026-10-04: X11
— полоса у края 6 px (углы 12), курсоры сторон,
_NET_WM_MOVERESIZE, проверено под Openbox; Windows — рамка
системы сохранена (WM_NCCALCSIZE), верх —
HTTOP; свои кнопки окна (свернуть,
развернуть/восстановить, закрыть) на Windows и Linux — сделано,
проверено под Openbox; дальше — меню привязок Snap (Windows 11) при
наведении на «Развернуть» |
0,1 |
| Меню macOS, печать, перетаскивание файлов внутрь, несколько окон | меню macOS — сделано 2026-10-04 своим кодом в
meridian-platform (декларативное описание, свои и
стандартные пункты, Event::Menu, проверено на настоящем
окне); печать — сделано 2026-10-04: PDF/A-2b ядра →
macOS — системное окно печати (PDFKit, лист у окна), Linux — CUPS
(lp), Windows — команда «печать» для PDF; раздел backstage
«Экспорт» (PDF рядом с документом, копировать путь); перетаскивание
файлов — уже есть; дальше — несколько окон |
1–1,5 |
| Шрифты для редких письменностей в интерфейсе | сделано 2026-10-04: подстановка из системных
шрифтов своим кодом — папки ОС один раз, файлы лениво по подсказкам
письменности (CJK, хангыль, армянский, грузинский, тайский, иврит,
арабский, деванагари), в коллекциях — обычное начертание, кэш и лимиты;
дальше — шейпинг сложных письменностей в интерфейсе через
meridian-shape сессии A |
0,2 |
| Windows-проверка и CI-матрица трёх ОС | Linux — свой раннер на sagr (сделано 2026-10-04,
just ci на каждый push); Windows и macOS — нужны
машины |
0,5 |
| Итого до продуктового паритета с Qt | ≈ 8–11 (против лицензий Qt ≈ 30–145 тыс. $ в год и обязательств LGPL) |
6. План
- Фаза 0 (сейчас) — прототип
ui/= вид и поведение Qt-прототипа; Qt-оболочкаshell/остаётся эталоном для сравнения снимков (SSIM) до паритета и не распространяется. - Фаза 1 — разработка Write идёт на своей оболочке; доступность, IME и буфер обмена — первыми (вместе с моделью правки); GPU-рендер — по замерам на 4K.
- После паритета —
shell/(Qt) удаляется из репозитория, решение № 36 закрывается; запрос в Qt Sales не нужен.
7. Риски и меры
| Риск | Мера |
|---|---|
| Доступность хуже, чем у Qt | свой мост к API доступности с фазы 1 (порядок фокуса уже есть); тесты с экранными чтецами в критериях готовности |
| Ввод сложных письменностей (IME) | свой IME в meridian-platform + шейпер ядра
meridian-shape; корпус ru/en/ar/he/hi уже есть у A3 |
| Производительность на слабых ПК | CPU-путь уже 7,4 мс на 2880×1800; GPU-путь — фаза 1; замер на эталонном ноутбуке в CI |
| Объём своего кода | компоненты — копии готовой дизайн-системы MDS, поведение проверяется снимками и сквозными тестами |
8. «Весь код свой» в оболочке (решение № 40)
Владелец подтвердил 2026-10-04: и в оболочке чужие библиотеки заменяются своим кодом («переписать всё»). Порядок и состояние:
| Что было | Чем заменяем | Состояние |
|---|---|---|
rfd (системный диалог «Открыть», на Linux —
ashpd/zbus) |
свой диалог CM-01 в интерфейсе
(ui/meridian-write/src/filedialog.rs): места, путь, список,
сведения, фильтр; одинаков на всех ОС |
сделано 2026-10-04; −41 сторонний крейт |
tiny-skia (растр) |
свой kernel/crates/meridian-raster (зона B, общий с
ядром — шаг 6 плана ядра): пути, сглаженная заливка non-zero/even-odd
(16 подстрок × 1/256 px), обводка со стыками и концами, маски, картинки
с аффинным преобразованием, source-over, запись PNG на своём DEFLATE
meridian-archive |
сделано 2026-10-04: кадры оболочки совпадают с
tiny-skia — SSIM 0,9999, кадр 2880×1800 7,2 мс (было
8,7); детерминизм — только целые и базовые операции f32. Ядро
(meridian-render) переводит сессия A |
winit + softbuffer (окно, ввод, вывод
кадра) |
свой ui/meridian-platform без зависимостей: macOS —
AppKit и CoreGraphics через objc runtime (своё представление
MeridianView, CGImage, libdispatch); Linux — X11 через
подгружаемый Xlib (EWMH-перетаскивание и разворот, XPutImage, раскладки
с кириллицей, Xft.dpi, будильник на сокетах и poll); Windows — Win32 и
GDI (свой заголовок через WM_NCCALCSIZE, SetDIBitsToDevice, DPI на
монитор, тема из реестра, PostMessage) |
сделано 2026-10-04: macOS — настоящее окно снято
системой (screencapture -l); Linux — Xvfb на sagr: окно,
щелчки xdotool (вкладка «Вид» → «Тёмная»), палитра
Ctrl+Shift+P, набор латиницей и кириллицей («тёмн» с «ё»),
Enter; дымовой выход; Windows — сборка и clippy -D warnings
чистые (кросс-проверка), запуск — на машине с Windows (пока нет); −55
сторонних крейтов |
harfrust + skrifa (текст) |
meridian-font сессии A (OpenType: cmap, hmtx, kern,
контуры glyf/CFF; шейпер — шаг 8 плана ядра) |
сделано 2026-10-04: ui/meridian-ui
зависит только от своих крейтов (meridian-raster,
meridian-font); кадры совпадают с harfrust/skrifa — SSIM
0,9962–0,9995 (светлая 0,9995, палитра 0,9985, «Открыть» 0,9990,
оглавление 0,9962), кадр 2880×1800 8,0 мс. С парами
GPOS от A (f8312e2) кернинг Inter совпадает с HarfBuzz:
SSIM 0,9999–1,0000, кадр 7,4 мс; harfrust
ещё приходит в сборку через раскладку ядра
(meridian-layout, зона A) |
| Шрифт Inter (данные) | остаётся данными (OFL) — решение владельца по данным | — |