Как стать автором
Обновить

Комментарии 37

НЛО прилетело и опубликовало эту надпись здесь
Вы очень правы, действительно раздражает. Учёл, модификаторы запрещают трансляцию.
НЛО прилетело и опубликовало эту надпись здесь
В принципе, нет проблем отправить в приложение два кода — транслированный и чистый.

Решение не в модификаторах, и не в "режимах" (это боль), а в том, чтобы разделить событие нажатия клавиши и ввода символа. И иметь 2 кода — код клавиши в латинской раскладке для хоткеев (Ctrl + S независимо от раскладки) и Unicode код введенного символа.


Далее, я рекомендую вам не завязываться на раскладки везде. В Линуксе, в иксах, например, вы должны при имитации ввода (искуственном создании событий нажатий клавиш) указать скан-код клавиши. Это причиняет боль разработчикам серверов вроде VNC, так как с клиента может прийти код отсутствующего в раскладке символа (иероглиф какой-нибудь), и где взять его скан-код на системе, в которой нет японской раскладки? Это приводит к хакам вроде "временно добавить в текущую раскладку японский символ с кодом 255, отправить событие keydown/keyup с кодом 255, убрать символ из раскладки". И вся эта жесть из-за плохого проектирования в иксах, так как это проектировали явно англоязычные разработчики, не понимающие проблем ввода на других языках.


По этой же причине, в дебиане с виртуальной клавиатуры нельзя вводить символы, которых нет в установленных раскладках. Фиг вам, а не японские символы без японской раскладки.


Чтобы спроектировать ввод правильно, надо рассмотреть типичные сценарии использования клавиатурных событий:


  • А. побуквенный ввод текста в текстовом редакторе, самое банальное. Нужно получать от системы код Unicode символа, а также коды управляющих клавиш (стрелки, backspace и тд).
  • Б. хоткеи. Нужен не зависящий от раскладки код — скан-код либо латинская буква. Последнее лучше, так как не зависит от модели клавиатуры и аппаратной платформы (у экранных клавиатур нет скан-кодов).
  • В. виртуальная машина или эмулятор компьютера, игры. Требуется имитирование IBM-совместимой клавиатуры и получение "сырых" нажатий с минимумом воздействий или фильтрации. Например, нажатие shift + A просто должно передаваться в программу как нажатие shift + A и все, без учета раскладок, смены регистра букв, состояния капс-лока и тд.
  • Г. VNC-сервер, средства автоматизации — нужно вбрасывать в систему искуственные события нажатия клавиш, пришедших от клиента
  • Д. консолечка — нужно корректно обрабатывать комбинации вроде Ctrl + C или Esc + A.
  • Е. ввод текста на иностранных языках с использованием IME, а также со всяких умных экранных клавиатур (это когда мы печатаем, появляется список вариантов слова и мы его выбираем) — нужно по мере набора передавать слова или их части, которые могут откатываться или коммититься в редактор.

Исходя из этого, как мне кажется, пригодилась бы такая структура события:


keydown/keyup (нажатие клавиши):


  • modifiers
  • ibm_scan_code — аппаратный скан-код IBM (для эмуляторов и ВМ)
  • latin_code — код клавиши в латинской раскладке без учета модификаторов, например "S", "1" (для хоткеев)

input (ввод символа, коммит слова из IME, нажатие управляющей клавиши):


  • modifiers
  • text (введенная буква или текст)
  • keysym (код управляющей клавиши вроде Enter, если нажата она. Для комбинаций вроде Ctrl + C в keysym копируется latin_code)

typing (ввод пока не набранного до конца слова в IME или вирт. клавиатуре)


  • text

Вернемся к сценариям:


Физическая клавиатура генерирует события keydown/up, которые порождают события input. Виртуальная клавиатура, VNC-сервер или скрипты автоматизации генерируют события input, из которых воссоздаются события keydown/up. При этом ввод эмодзи, например, не позволит заполнить поля ibm_scan_code или latin_code в событии keydown, так как для эмодзи нет скан-кодов.


А. Текстовый редактор использует только событие input, и берет либо поле text, либо код клавиши из keysym.
Б. Для хоткеев используются поля latin_code и modifiers в событиях keydown/up. Либо keysym + modifiers в input (?)
В. ВМ и эмуляторы, игры используют событие keydown/up и поле latin_code либо ibm_scan_code
Г. Описано выше, вбрасываются события input, по которым воссоздаются keydown/keyup
Д. Для распознавания нажатий вроде Ctrl + C используется событие input, поле keysym + modifiers, с помощью библиотечной функции, которая по событию возвращает подходящий консольный код. Либо используется событие keydown, поля latin_code + modifiers
Е. Используются события typing и input


Вот как-то так.


И, конечно, не стоит изобретать свои коды клавиш, а стоит взять те же иксы или что-то еще готовое.

(Искреннее спасибо за подробный разбор. Всё очень по делу.)

Из VNC не приезжают IBM scan codes, приезжают как раз иксовые коды.

Сам по себе IBM scan code — штука мерзкая, а ближе к принтскрину и вообще сумасшедшая, не хочется его применять как опорный.

Может быть, обойтись троицей?

— Нетранслированный x11 code или ascii char без локализации
— char транслированный по полной — если нажат ctrl, то 00-1F, если локальный кеймап, то кириллица/японица, что там — в UTF32. Короче, tty char
— модификаторы

Кажется, эта схема закрывает всё и хорошо ложится на VNC и X11.

Скан-коды я оставил исключительно для сценариев вроде DOS эмуляторов или вирт. машин, где нужно передавать внутрь скан-коды IBM-клавиатуры. Но сейчас я подумал, что в принципе DOS эмулятор может использоваться совместно с VNC или экранной клавиатурой, которые не знают ничего о скан-кодах, и может быть, имеет смысл опираться на latin_code + modifiers и из него формировать скан-коды IBM в таких приложениях с помощью готовой библиотечной функции. Плюс, у пользователя может стоять DVORAK-раскладка и в вирт. машину надо передавать переставленные местами скан-коды.


То, что вы предложили выше "отключать трансляцию при управляющих комбинациях клавиш" не годится, так как иногда "горячие" клавиши не используют модификаторы. Например, в графическом редакторе инструменты переключаются просто буквами без модификаторов.


Потому важно, на мой взгляд:


  • разделять события нажатия клавиш и события ввода символов или текста (типичный пример — IME, где текст коммитится только после выбора слова, а не при нажатии каждой клавиши или Compose key, где может быть несколько последовательных нажатий клавиш, которые вставляют один символ в итоге, например Compose + 1 + 2 вставляет ½)
  • всегда передавать как latin_code, который нужен для реализации горячих клавиш и управления в играх. Для сценариев вроде вирт. клавиатур или VNC, latin_code должен воссоздаваться из приходящих кодов символов (например, на основе самых популярных раскладок, хотя еще правильнее было бы, если бы VNC-клиент передавал и latin_code, и юникодный символ. Так как в VNC передаются юникодные коды, и так как VNC-сервер не знает раскладку на клиенте, он не может в 100% ситуаций корректно восстановить latin_code и корректно передавать нажатия управляющих комбинаций при любых комбинациях раскладок на клиенте и сервере. Это проблема в дизайне протокола VNC)
  • в событии ввода текста передавать введенный текст, который может быть как одним символов, так и целым словом при использовании IME (хотя, можно при выборе слова в IME передавать по событию на каждую букву, но передавать слово целиком в одном событии логичнее)

Единственное, в чем я не уверен — надо ли передавать управляющие комбинации, не генерирующие текст (например, Backspace, Left, Ctrl + C) в событии input или нет (только в keydown/keyup). Если их не передавать все выглядит логичнее, но писать эмулятор консоли будет труднее, так как ему нужен и текст и управляющие клавиши и придется обрабатывать все события и разделять нажатия из них. Передача управляющих клавиш в событии input с пустым текстом решает эту проблему, хоть и делает дизайн событий менее красивым.


Код символа для консоли, думаю, нет смысла передавать отдельным полем, его можно получать с помощью библиотечной функции из других полей. Так как он нужен только в одном-единственном приложении — эмуляторе терминала.


Кстати, скажу вам еще одни грабли в иксах. Если вы назначаете какую-то горячую клавишу на комбинацию только из модификаторов (например, Ctrl + SHift для переключения раскладки), то вы не можете вводить другие комбинации с этими модификаторами, например Ctrl + Shift + A, так как переключение раскладки "проглатывает" нажатие клавиши Shift или Ctrl. Другой пример — использование как горячих клавиш одиночного нажатия на Win и Win + буква будет конфликтовать.


Правильно было бы обрабатывать комбинации только из модификаторов не в момент нажатия, а в момент отпускания, и тогда конфликтов не будет.

Ну вот то, что я написал выше, кажется, полностью подходит.

keyCode — или код функциональной клавиши (тот же X11 code), или latin1 char. Используется именно как управляющая клавиша в контексте команды и для восстановления кода в эмуляторах. 1:1 соответствует клавише. Не модифицируется раскладкой.

ttyChar — только printable, с трансляцией раскладки. Если нет printable кода, то ноль. По нулю можно переключаться на командные символы из keyCode в строках ввода, где нет командной модальности.

modifiers — все биты альтов и контролов, keyUp и прочие meta.
в vi эа ситуация интересно решается: сопоставление символов в разных раскладках.
НЛО прилетело и опубликовало эту надпись здесь

К счастью для нас с вами, linux это опенсурс, а значит у нас есть возможность спроектировать и может даже внедрить более удачный стандарт.

НЛО прилетело и опубликовало эту надпись здесь
1. Зачем прятать ссылки под спойлер?
2. Подскажите, где можно увидеть реализацию rect_add? Никак не могу найти её в репозитории. Какую-то непривычную структуру он (репозиторий) у вас имеет…
А почему GUI не реализовали полностью в юзер спэйсе, зачем ядру знать про контролы?
Есть две причины.

1. Был в Фантоме такой эксперимент, как реализация API KolibriOS. А там в апи входят контролы. Кстати, некоторые приложения Колибри запустить удалось, но подробных спек на апи нет, а разработчики на связь не вышли, так что он пока на паузе.

2. Быстрее
В Колибри очень подробные спецки на АПИ, просто по причине их простоты.

Заодно отмечу твое/ваше визионерство — 10 лет назад про персистентность ОС даже и не думали, а тут появились SSD/FlashMem/Intel идеи итп, что сделало идею ФантомОС [почти] реализуемой…

Но не поддерживаю затею с VMкодом в ОС — она себя за это время дискредитировала — слишком большие затраты ресурсов (10х).
Не подробные. Когда начинаешь писать вылезает миллион вопросов, и ответов нет. Навскидку первый: как распаковать бинарник приложения. Имеющаяся документация на этот счёт ошибочна.

VM будет закрыт JIT-ом.
Навскидку первый — в КолибриАПИ просто нет функции распаковки приложений. В молоко. Впрочем это тут оффтоп.

Не знаю ни одного удачного JITa, все проигрывают AOT. Кстати, мой комментарий был про применения пары JIT/GC, т.к. интерпретация байт кода почти не требует ресурсов, только медленная.
1. Системный вызов номер 7. Запуск приложения. Я не нашёл никого, кто бы чётко понимал, как работает распаковщик. В итоге удалось сделать запуск только распакованных приложений.

То есть, в теории даже есть сишный код распаковщика и структура заголовка упакованного образа приложения, но сделанная по имеющемуся описанию реализация не работает.

Если вдруг Вы в курсе, как оно должно быть устроено, то, может быть, подскажете? Код вот тут:

github.com/dzavalishin/phantomuserland/blob/master/oldtree/kernel/phantom/elf.c#L131

2. Сравнение JIT/AOT — тема сложная и небанальная.

— Есть много критериев. Объём кода. Объём данных. Скорость работы. Объём данных — если на типовом сценарии вылезаем из кеша — может убить скорость.

— Есть много сценариев. Монотонные операции и работа на сильно разных данных. Во втором случае JIT может давать катастрофический выигрыш за счёт перекомпиляции на ходу с учётом анализа фактического графа исполнения и направлений переходов.

— Есть разный код. Ява способна свернуть константы на пачку функций внутрь цепочки вызовов и оптимизировать то, что статический линкер никак не может.

И т.п.

Ну и тупые тесты показывают, что jvm vs gcc даёт вполне сравнимые результаты. Где-то в пользу си, где-то в пользу Явы.

www.stefankrause.net/wp/?p=4

www.codenet.ru/webmast/java/javavscpp.php

Ну и, собственно, явский JIT не проигрывает прямой компиляции. Да и, в целом, никаких причин для того, чтобы ему проигрывать не видно.

Другое дело, что и до джита Фантому ещё надо дожить. А вот, кстати, АОТ сделать проще.
Такую вещь давно хотел спросить. Допустим у меня сложное ресурсоёмкое приложение, вроде современной AAA игры, которой требуется быстро обрабатывать большой массив данных. Причём изначально эти данные закодированы (сжаты) по соображениям экономии места.
Т.е. во первых мне ни горячо ни холодно от того, что данные как бы уже находятся в памяти и у меня есть на них указатель, потому что во первых это скорее всего медленная память (hdd, flash), а во вторых они там в непригодном для использования виде. А во вторых быстрой (оперативной) памяти всё равно не хватает и необходимо следить за тем что в неё помещается, а от чего можно отказаться. Причём статистические алгоритмы, встроенные в операционную систему явно будут проигрывать движку игры, которых хотя бы знает с чем работает и как данные связаны друг с другом. Ну и в третьих, существование легковесных алгоритмов сжатия вроде lz4, zsdt может сделать нецелесообразным вытеснение данных в медленную память (swap), т.к. их проще и быстрее декодировать и распарсить из оригинального источника.
В связи с этим хотелось бы понять, какой смысл в персистентности, при учёте, что тут приходится имитировать функции чтения/записи из обычной OS, а так же вручную следить за тем, что находится в быстрой памяти. Или такие системы, как PhantomOS по определению имеют практический смысл только в условиях использования гипотетической так и не выпущенной MRAM, имеющей объём hdd, а ресурс и скорость оперативки?
Никакие игры никогда не работают с данными, которые отсутствуют в оперативной памяти. Любая оперативная память приложения виртуальна. И нет гарантии, что она подкреплена физической памятью. Ни в какой ОС.

Изначально сжатые данные приложением разжимаются при начале работы.

Имитировать функции чтения из обычной ОС (ФС?) не приходится. Фантом легко хранит содержимое скомпрессированного объекта просто в строке и из неё же можно распарсить финальный объект.

Вот тут есть пример программы для Фантома: phantomdox.readthedocs.io/en/latest/#example-of-phantom-program

Обратите внимание на строки

bmp = new .internal.bitmap();
bmp.loadFromString(getBackgroundImage());


По сути они сводятся к

bmp.loadFromString(import "../resources/backgrounds/weather_window.ppm");


Конструкция import возвращает строковую константу, инициализированную содержимым файла в момент компиляции.

Эта строка будет содержать в данном случае картинку в файловом формате.

loadFromString парсит и конвертирует в финальный битмап, который и живёт в персистентной переменной.

И нет гарантии, что она подкреплена физической памятью. Ни в какой ОС.
Функции VirtualLock в Windows и mlock в unix смотрят на это утверждение с удивлением…
Ок, согласен. :) Но, всё же, это не типовой сценарий. И рут нужен. И ограничения есть.

А технически то запереть странички и в Фантоме можно, тут разницы нет.
Так-то да, хотя есть специализированные программы, вроде СУБД, которые нужные привилегии без проблем получают…

На самом деле просто есть одна вещь, которую давно хотелось иметь в виде прикладного API, но которую никто не делать. Это обработчик для проецирования в память. Т.е. при создании некоторой области памяти, для неё определяется функция-генератор и один или более источников — файловых дескрипторов или указателей (для фантома только последнее). При обращении к указанной области памяти система вызывает обработчик, который генерирует весь объект или его части. Теперь система не сохраняет содержимое объекта в swap, а просто выбрасывает его и воссоздаёт вызовом функции-генератора по необходимости. В более сложном случае может быть даже определена функция, управляющая очерёдностью выкидывания страниц из памяти. Так же, такие объекты, созданные в режиме только чтение и зависящие только от внешних источников могли бы становиться разделяемыми, если определены в разделяемой библиотеке.
Собственно, практическое использование могло бы быть уже в коде, отвечающим за рендеринг шрифтов. Сейчас вы рендрите шрифты в битмапы, а их храните в памяти для отрисовки. Т.е. количество глифов перемножаем на используемые размеры, перемножаем на цвет символов, перемножаем на количество шрифтов в системе, учитываем, что современный качественный рендеринг шрифтов использует субпиксельное позиционирование, а порой ещё и сглаживание с фоном… Т.е. количество отрисованных глифов быстро становится астрономическим. И всё это хранится в несжатом виде, а потом попадает на диск и лежит там вечно. Ну или придётся городить отдельный уборщик мусора для шрифтов, который будет искать что уже никому не нужно и удалять ссылки, чтобы потом системный сборщик мусора наконец удалил это всё.
Ну или это помещается в такой вот генерируемый объект и выкидывается как только появляется потребность в памяти, а потому рисуется заново по мере необходимости.
Или как второй этап делается собственная библиотека, которая на первом этапе читает содержимое ttf, переводит в более удобный формат и имеет более низкий приоритет на удаление, т.к. эти данные более компактны и чаще востребованы. В вот уже генерируемый объект от этого объекта хранит кеш глифов с алгоритмом удаления, выкидывающим из памяти в первую очередь давно не использованное и редко используемое, чтобы старьё и разовые глифы не занимали память…

Я просто думал, что такое будет естественным для фантома и хотел узнать как его реализовали (на что получился похож API), но увы, до такого похоже ещё очень долго…
Мысль довольно очевидная, такой тикет у Фантома даже стоит в планах.

Тут бы надо разделять две сущности. Генерацию объекта по факту потребности в нём и memory mapping. Первое вполне возможно без второго, вообще говоря. Запрашиваем глиф, если его нет в кеше рендеринга, то рисуем и выдаём. При исчезновении всех ссылающихся на глиф объектов он остаётся только в кеше. Кеш прореживаем по LRU.

Мне отображение в память больше виделось как инструмент для доступа к сетевым ресурсам.
*возможно в фантоме. В обычно OS как раз проецирование файлов в память самое близкое что можно почувствовать. Был файл на диске, а теперь немного магии OS и получилась область памяти.

А насчёт тикета — хорошо. Придётся подождать пока из этого что-то выйдет…
И нет гарантии, что она подкреплена физической памятью. Ни в какой ОС.

Неправда. Отключите на Линуксе своп и все аллокации на куче будут в RAM.

Гарантии — нет. Программы разрабатываются исходя из предположения, что пейджинг включен. Хотя, конечно, по факту на сегодня типовой сценарий — памяти заведомо больше, чем рабочее множество суммы приложений.

Вы забыли про overcommit memory в линуксе — до первой записи страницы не мапятся на физическую память.

Ну да, в ответ надо добавить "и отключить overcommit".

Вы используете одну общую очередь для событий отрисовки и событий ввода? В этом случае при скоплении тяжелых отрисовочных задач ввод может тормозить.


Битмапы на каждое окно мне не особо нравятся. Вы оцениваете, какой будет расход памяти на экране 4K с 20 полноэкранными окнами? Я знаю, что это тенденция во всех современных дисплейных серверах, но неужели нельзя хотя бы на скрытые окна не тратить память? Это хорошо работает на смартфонах, где окно одно и занимает весь экран, но на десктопе это ведь неэкономично.

Да чёрт с ней, она дешевеет с каждым годом, память-то. Ну и требования персистентности диктуют. Есть гарантия, что рестарт приложения выдаст юзеру предыдущую картинку его окна мгновенно.
Насчёт одной очереди — если отрисовка встала, то мы всё равно не увидим эффекта от ввода. Хотя, конечно, сделать две отдельные очереди не так и сложно. Меня эта мысль тоже посещала.

А почему вы завязываетесь на древний способ 2д отрисовки — возней с пикселями, копиями, битмапами, кешированием и софтварной растеризацией? Сейчас практически на каждом устройстве есть поддержка как минимум OpenGL ES а это значит что графическую подсистему можно построить так что заниматься растеризацией и прочей возней с пикселями будет видеокарта а со стороны программы или os достаточно просто на каждый фрейм отрисовки передать на gpu массив объектов данных примитивов (если это прямоугольик то координаты вершин или точка + ширина-высота, если кривая безье то координаты контрольных точек и т.д) — и уже на видеокарте в шейдерах будет происходить отрисовка. Причем примитивы можно бесконечно усложнять поскольку шейдеры можно программировать.
Что касается взаимодействия отдельных программ-окон то тут есть 2 варианта
1) Каждое окно будет лично взаимодействовать с видеокартой — вызывать функцию отрисовки (draw-call) но результат будет сохраняться во временный буфер-текстурку а потом os еще раз вызовет отрисовку композируя из нескольких текстурок итоговую картинку. Причем надо заметить что результат отрисовки каждого окна можно хранить на видеокарте а не заниматься копированием между cpu и gpu
2) Можно попробовать построить взаимодействие так чтобы вызов отрисовки (draw-call) на видеокарте был только один — для этого собираем результат от каждого окна и дополнительно препроцессим смещая координаты — либо на уровне массива примитивов (через постпроцессинг либо через некий общий апи рисования) смещая координаты каждого примитива с соотвествии со смещением каждого окна либо на уровне компиляции кода шейдеров добавить дополнительный код который будет относительно каждого окна добавлять смещение для gl_Position

По сути Вы совершенно правы, так и надо. Более того, я проделал серию экспериментов с софтверным GL-ем в качестве базиса для оконной системы.

Но — на всё рук не хватает, да и акселерированные драйвера современных видеокарт — это ад и израиль, несколько подходов к снаряду вида «что бы такого втянуть целым кусочком» пока кроме глубокой задумчивости результатов не дали. :)

Так что пока я тактически отступил к 2D.

Присоединяйтесь, сделайте это, будет действительно здорово. Я уже представляю себе шелл с трёхмеркой и окнами, которые сложены в стопочку в углу экрана. :)

Некие мысли по поводу контролов.
Думаю, можно выделить два подхода к построению UI:


  • контролы рисует система, повинуясь вызовам API (напр. GDI+). За обработку ввода в этом случае также отвечает система.
  • система просто предоставляет буфер, куда приложение рисует своими силами (Qt QML, WPF, UWP итд, а также б-гмерзкие веб-технологии). За обработку ввода внутри окна отвечает приложение.

Оба подхода имеют как свои преимущества, так и недостатки. В последнее время все больше второй подход применяется. С одной стороны — возможность создавать графически насыщенные приложения, не пытаться вписаться в ограничения ОС, а при кроссплатформенности пытаться привести разные графические подсистемы к общему знаменателю. С другой стороны — по-сути, дублирование функциональности, неумение (или плохое) адаптироваться к системной теме (опять-таки костылями).


И глядя на все это безобразие, я иногда думаю, что сейчас проще вообще делать ОС/графическую подсистему, которая не умеет рисовать ничего внутри окна, а только обеспечивать оконный менеджер, композинг, декорации окна итп.

Зарегистрируйтесь на Хабре, чтобы оставить комментарий

Публикации

Истории