惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

罗磊的独立博客
Recent Announcements
Recent Announcements
奇客Solidot–传递最新科技情报
奇客Solidot–传递最新科技情报
有赞技术团队
有赞技术团队
J
Java Code Geeks
T
The Blog of Author Tim Ferriss
MyScale Blog
MyScale Blog
人人都是产品经理
人人都是产品经理
aimingoo的专栏
aimingoo的专栏
U
Unit 42
The GitHub Blog
The GitHub Blog
云风的 BLOG
云风的 BLOG
T
Tailwind CSS Blog
H
Hackread – Cybersecurity News, Data Breaches, AI and More
酷 壳 – CoolShell
酷 壳 – CoolShell
博客园 - 三生石上(FineUI控件)
Apple Machine Learning Research
Apple Machine Learning Research
小众软件
小众软件
Hugging Face - Blog
Hugging Face - Blog
博客园 - 司徒正美
腾讯CDC
I
InfoQ
GbyAI
GbyAI
博客园_首页

Все публикации подряд на Хабре

Ловим музу за клавиатуру: как айтишнику стать автором Что умеет Midjourney в 2026? Мой немного грустный разбор этого шикарного инструмента Никто не любит писать тесты, но ИИ может исправить это IPv8 выглядит как мечта. Поэтому почти наверняка не взлетит Производители вернули в продажу материнки с DDR3. Что происходит? Управление агентом с телефона через Telegram теперь в KodaCode От координации к лидерству: как меняется роль руководителя разработки Я сделала родителям бизнес вместо пенсии: зарабатываем 70 тысяч, мама не даёт продать В три раза быстрее приемка товара и оптимизация трудозатрат на 73%: как «РСТ-Инвент» помог Gulliver Group ИИ-шечный мир победил? О влиянии искусственного интеллекта на игропром Кремль снижает давление на Телеграмм пока Европа строит интернет по паспорту Как CEO, CTO и CIO за 8 часов собрали ИИ-директора, который умеет держать позицию под давлением Как (не) потерять домен за выходные Вместо 8 разных VPS: как я организовал практику студентам на одном сервере Почему твой Open Source проект не замечают? R&D: искусство управления неопределенностью в разработке AI-дефляция: вакансий для разработчиков больше, а рост зарплат — худший за 15 лет Мы отдали управление роботами OpenClaw. Что из этого вышло Галактический ID: система идентификации для всех форм разумной жизни Шесть основ бизнес-анализа: начинаем с вопроса «Кто в игре?» Код-ревью, в котором дело не в коде Данные переехали. Команда — нет Системной подход к сдаче OSWE в 2025 Почему комната управления реактором покрашена в цвет морской пены 4 YAML-файла вместо PySpark: как аналитикам строить пайплайны без разработчиков LLM-агент для поиска свободных доменов: автоматизируем подбор Когда, зачем и как правильно начинать новую сессию в Claude Code? Как я заставил нейросеть писать макросы для FreeCAD Анатомия ИИ‑агента для подбора персонала. От тысячи резюме к топ‑10 за минуты Опыт разработчика как экономика внимания
Делаем свое контекстное меню для полей ввода в Qt на моби...
0xC0DE1E55 · 2026-06-28 · via Все публикации подряд на Хабре

...или как самому запатчить Qt

Не так давно возникла задача в Qt QML проекте обработать вставку изображения в поле ввода. Понимаю, что не каждый день приходится писать кроссплатформенные клиенты для настольных и мобильных систем, но если у тебя была похожая задача, то данная статья может быть тебе интересна.

Задача, в целом, не звучит сложной, если речь о настольных системах, но речь как раз пойдет о мобильных системах, и там все гораздо сложнее. Все потому, что Qt полностью рисует свою QML сцену в специальном слое, который, обычно далек от нативных представлений мобильных систем. Более того, все текстовые поля в Qt рисуются полностью самим Qt, а на мобильных системах нужно работать с клавиатурой устройства и хорошо бы при этом показывать не только виртуальную клавиатуру, но и "пины" для работы с текстом. На обеих платформах Qt поступает следующим образом: "под сценой" создается специальный элемент, который реализует нативную работу с клавиатурой и ее событиями. Все события от нативного элемента передаются в C++ часть и обратно, в том числе меню и синхронизация пинов, из-за чего часто возникают различные баги, связанные с рассинхронизацией событий. В общем работа с текстовыми полями и виртуальной клавиатурой в Qt зачастую больно и не приятно.

Хуже всего то, что в Qt не дали особого доступа для работы с нативной частью меню, закрыв это все за платформенными абстракциями. В обеих системах меню управляется самим Qt и наполнение или внешний вид сменить не получится. Ситуация хуже всего на Android: там меню и близко не похоже на системное, при этом выглядит как меню из Windows95.

Исследуем проблемы

Когда мы работаем с полем ввода, то Qt на мобильных системах автоматически наполняет меню, например, если что-то есть в буфере обмена, то в меню появится кнопка “Вставить”. Решение кажется простым: перехватываем действие “Вставить”, если там изображение — игнорируем событие и реализуем нужную нам логику, в противном случае не трогаем событие и передаем управление дальше в Qt.

Первая проблема, которая тут кроется, заключается в том, что к меню-то как раз и нет доступа. Добавить обработчик на кнопку "Вставить" не получится в классическом понимании. Тут можно воспользоваться тем, что (почти) все объекты в Qt унаследованы от QObject, поэтому можно воспользоваться методом QObject::installEventFilter и посмотреть, что за события приходят в поле ввода. Признаться честно, я ожидал что-то необычное, но оказалось, что в момент выбора пункта меню "Вставить" в поле ввода приходит простое событие сочетаний клавиш QKeyEvent, а комбинация там — всем известный Ctrl+V. Ну вот, можем зацепиться за событие и отфильтровать его, а что дальше?

А дальше интереснее. Пока я возился с кнопкой "Вставить", оказалось, что ее просто нет в списке пунктов меню в тот момент, когда в системном буфере обмена есть изображение. Обидно за потраченное время, но это хотя бы опыт и в будущем может пригодиться.

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

Но если заглянуть в то, как устроен canPaste, то окажется, что он работает с mime-data своего буффера обмена и вызывает "под капотом" QQuickTextControl::canInsertFromMimeData работа с изображениями в котором и не предполагалась:

bool QQuickTextControl::canInsertFromMimeData(const QMimeData *source) const
{
    Q_D(const QQuickTextControl);
    if (d->acceptRichText)
        return source->hasText()
            || source->hasHtml()
            || source->hasFormat(QLatin1String("application/x-qrichtext"))
            || source->hasFormat(QLatin1String("application/x-qt-richtext"));
    else
        return source->hasText();
}

Несмотря на то, что в QMimeData есть метод hasImage, в этой функции его не проверяют. А если заглянуть в буфер обмена через QGuiApplication::clipboard, то выяснится, что не смотря на то, что системный буфер содержит изображение, его вообще нет в QMimeData текущего буфера Qt.

Что же делать?

Времени было уже потрачено достаточно, поэтому сдаваться так просто не хотелось, и я продолжил искать решения дальше.

У полей ввода в QML есть свойство inputMethodHints, которое отвечает за различные режимы работы поля ввода: скрытый режим ввода пароля, ввод email-адреса, только цифры, и так далее. Если читать документацию про это свойство с точки зрения QML-компонентов, то ничего интересного там не получится найти. Но эти значения берутся из enum Qt::InputMethodHint в Qt Namespace, который хорошо документирован.

Если посмотреть на enum Qt::InputMethodHint, то можно найти интересные режимы ввода:

Qt::ImhNoEditMenu	0x800	Do not use built-in edit menu. This flag was introduced in Qt 5.11.
Qt::ImhNoTextHandles	0x1000	Do not use built-in text cursor and selection handles. This flag was introduced in Qt 5.11.

И вот, казалось бы флаг Qt::ImhNoEditMenu — то что нам нужно: отключаем меню, пины остаются. При таком подходе мы уже сможем показать свое меню, со стилями, которые нам нужны, а, главное, мы можем расширить наполнение и поведение пунктов меню.

Но при проверке оказалось, что этот флаг не работает в Android, в то время как на iOS флаг ведет себя так, как это описано в документации. При этом флаг Qt::ImhNoTextHandles работает на обеих платформах, но он отключает и пины для выделения и меню. В таком случае мы, конечно же, можем сделать свое меню, но с выделением будут проблемы, так как пинов нет. Да, можно "сверху" (по оси Z) над полем ввода положить MouseArea, перехватывать все жесты и сделать свои пины, но через небольшой промежуток времени при реализации данного подхода в дверь начинают стучаться санитары и переживают, все ли у нас в порядке.

Где сломалось?

Ничего не остается как пойти и разобраться, а что же сломалось? И как это починить?

Сначала я посмотрел в Java реализацию того, как работают с полями. Но достаточно быстро стало понятно что особо интересного там ничего нет: меню рисуется действительно в Android, но его наполнение приходит из C++, а обработчики действий уходят обратно в C++.

Платформенная абстракция находится в модуле Qt base, и разделена на плагины. Поэтому, скачав исходный код модуля, я быстро, по нужным флагам нашел, что нас интересуют два файла:

src/plugins/platforms/android/qandroidinputcontext.h src/plugins/platforms/android/qandroidinputcontext.cpp

В коде есть функция QAndroidInputContext::updateSelectionHandles, которая как раз и отвечает за наполнение меню и положение пинов на экране. Наполнение кнопок осуществляется с помощью битовых флагов в uint32_t buttons. По какой-то причине флаг Qt::ImhNoEditMenu в этой функции вообще не проверяется. Поэтому наша задача просто оставлять buttons пустым (== 0), тогда, когда флаг Qt::ImhNoEditMenu установлен для поля ввода.

Для этого добавляем свою функцию проверки установки флага:

bool QAndroidInputContext::isImhNoEditMenuSet()
{
    QSharedPointer<QInputMethodQueryEvent> query = focusObjectInputMethodQuery();
    if (query.isNull())
        return false;
    return query->value(Qt::ImHints).toUInt() & Qt::ImhNoEditMenu;
}

И в функции QAndroidInputContext::updateSelectionHandles проверяем установку этого флага: если установлен, то оставляем buttons == 0, иначе используем логику, которая была реализована до нашего вмешательства.

Собираем модуль и подменяем в своей установке Qt получившуюся библиотеку, не забыв при этом, конечно же, сделать бэкапы оригинальной библиотеки. Собрать, кстати, проще всего через Qt Creator той же версии тулкита, как и версия исходного кода Qt base. Так не нужно будет прописывать пути до нужных CMake-файлов вручную.

Запускаемся с новой библиотекой и обнаруживаем, что с установленным флагом Qt::ImhNoEditMenu для поля ввода в Android поведение такое же как в iOS: меню нет, пины есть, все остальное работает штатно. Проблема решена!

Патчим Qt

В принципе, на этом можно было бы закончить: собрать свою версию Qt с этим патчем, закинуть в свой CI/CD и забыть. Но вдруг кто-то столкнется с такой же ситуацией? Статья это хорошо, но давайте поправим Qt, чтобы было меньше боли у тех, кто с этим столкнется.

В целом, в Qt Wiki есть статья, описывающая как правильно вносить изменения в Qt, но там достаточно большое описание для общего случая, поэтому, я коротко опишу как это происходит, по горячим следам.

У Qt большая инфраструктура: есть сайт, блог, форум, багтрекер, система контроля версий. Первым делом необходимо зарегистрировать Qt account, это общий аккаунт для всех сервисов.

После регистрации идем в Qt Jira и заводим баг по своей проблеме. Так больше шансов, что патч быстро попадет в upstream. Я, например, допустил эту ошибку и мейнтейнеры в обсуждении коммита спросили что-то в духе "А зачем?". Тут ничего сложного, заполняем обязательные поля, платформу и модуль, где воспроизводится и максимально детально описываем проблему.

Затем идем в Qt Gerrit и настраиваем там свой аккаунт:

  • Добавляем почту, это обязательно, без этого не получится вносить изменения; анонимно вносить изменения можно, об этом есть в wiki, но я не углублялся.

  • Реальное имя, не Антоша_убийца_666, а реальное имя, вместе с почтой вы навсегда останетесь в истории git Qt; можно добавить отдельно никнейм, если хочется но его будет видно только в обсуждении.

  • Добавляем SSH-ключ для своей учетной записи, думаю, тот, кто пользуется GitHub/GitLab делал это не один раз, но вот тут можно прочитать детальнее, как это сделать.

  • Внизу страницы настроек добавляем соглашение (Agreements), в моем случае это Individual contributor agreement, коммерческой лицензии у меня нет.

После того, как все настроено и готово можно склонировать репозиторий следующей командой:

git clone git://code.qt.io/qt/qt5.git qt6

Корневой репозиторий — это просто супер-репозиторий, состоящий из git-сабмодулей. Все сабмодули нам инициализировать не нужно, достаточно только склонировать Qt base следующей командой:

cd qt6 perl init-repository --module-subset=qtbase

Переходим в папку Qt base и создаем ветку, в которой будем делать свое исправление:

cd qtbase
git checkout dev
git pull
git checkout -b fix/android-imh-no-edit-menu

У каждого коммита должен быть уникальный Change-Id. Этот Id проставляется автоматически, с помощью git-hook, который необходимо загрузить отдельно. По не понятной мне причине, стандартные скрипты из wiki не завелись, поэтому скачать нужный hook пришлось вручную следующим образом:

gitdir=$(git rev-parse --git-dir)
curl -Lo ${gitdir}/hooks/commit-msg https://codereview.qt-project.org/tools/hooks/commit-msg
chmod +x ${gitdir}/hooks/commit-msg

После внесения изменений формируем наш коммит практически как обычно, но не указываем commit message. Вам будет предложено заполнить его в редакторе по умолчанию по заранее готовому шаблону. Указываем коротко суть проблемы, далее описание, можно указать ChangeLog и модуль, в котором были изменения. Важно не забыть указать и какой баг исправляет данный патч, поэтому в конце сообщения добавляем Fixes: QTBUG-xxxx, где xxxx - номер вашего бага в Qt-bugtracker.

git add .
git commit

Все готово, отправляем наш патч, указав вместо <gerrit_username> ваш Username в Gerrit на странице Settings:

git push ssh://<gerrit_username>@codereview.qt-project.org:29418/qt/qtbase.git HEAD:refs/for/dev

На этом этапе запустится Early Warning System скрипт, который запускает различные тесты перед отправкой коммита. Если замечаний нет, то наш патч отправится на ревью. В самом Gerrit нам необходимо добавить ревьюверов, так у патча больше шансов быстрее пройти ревью.

Вообще, для новичков в этом деле, как и у меня, ревьювер был назначен автоматически. Но можно добавить мейнтейнеров модуля, а так же посмотреть с помощью git-blame кто вносил изменения в код, рядом с вашим патчем.

Если у ревьюверов будут замечания, то необходимо внести изменения и отправить их по-новой, либо обсудить в комментариях предложения.

Изменеия в Gerrit сделаны с помощью patch set, и устроены немного иначе. В Qt Gerrit принята политика одного коммита на одно изменение и на это есть несколько причин:

  • Gerrit не merge-based — каждый патч это один коммит, который в итоге попадает в репозиторий как единица. Серия patch set’ов это просто история правок одного коммита.

  • Ревьюерам удобнее с этим работать — они видят изменения между patch set’ами и сразу понимают что исправлено по замечаниям.

  • Qt Contribution Guidelines прямо требуют атомарные логические коммиты — один feature/patch = один коммит.

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

git add .
git commit --amend
git push ssh://<gerrit_username>@codereview.qt-project.org:29418/qt/qtbase.git HEAD:refs/for/dev

После прохождения ревью и исправления всех замечаний на странице патча появится кнопка Stage, нужно будет пропустить его через CI/CD и если все пройдет успешно, то патч автоматически будет влит в целевую ветку. Иногда CI/CD падает с ошибками в других ветках и даже модулях, так что может получиться так, что Stage придется запускать несколько раз, это нормально.

Если все пройдет успешно, то на странице с патчем статус будет помечен как Merged. Армия ботов "вольет" ваши изменения в нужные ветки, а в Jira проставят все необходимые статусы. Вот теперь все готово.

Ссылка на баг и на его патч.