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

推荐订阅源

大猫的无限游戏
大猫的无限游戏
Security Archives - TechRepublic
Security Archives - TechRepublic
T
Threat Research - Cisco Blogs
A
Arctic Wolf
S
Securelist
O
OpenAI News
T
Threatpost
Forbes - Security
Forbes - Security
N
News and Events Feed by Topic
S
Secure Thoughts
H
Heimdal Security Blog
S
Security Affairs
P
Privacy International News Feed
C
Cisco Blogs
C
CERT Recently Published Vulnerability Notes
Cyberwarzone
Cyberwarzone
N
News and Events Feed by Topic
Threat Intelligence Blog | Flashpoint
Threat Intelligence Blog | Flashpoint
P
Palo Alto Networks Blog
S
Security @ Cisco Blogs
Hacker News - Newest:
Hacker News - Newest: "LLM"
博客园 - 三生石上(FineUI控件)
月光博客
月光博客
T
Tailwind CSS Blog
Exploit-DB.com RSS Feed
Exploit-DB.com RSS Feed
奇客Solidot–传递最新科技情报
奇客Solidot–传递最新科技情报
Hacker News: Ask HN
Hacker News: Ask HN
T
Troy Hunt's Blog
S
SegmentFault 最新的问题
腾讯CDC
V
Visual Studio Blog
Last Week in AI
Last Week in AI
H
Hacker News: Front Page
cs.CV updates on arXiv.org
cs.CV updates on arXiv.org
OSCHINA 社区最新新闻
OSCHINA 社区最新新闻
Project Zero
Project Zero
WordPress大学
WordPress大学
NISL@THU
NISL@THU
博客园 - 【当耐特】
博客园 - Franky
Webroot Blog
Webroot Blog
博客园_首页
T
Tenable Blog
雷峰网
雷峰网
Google Online Security Blog
Google Online Security Blog
阮一峰的网络日志
阮一峰的网络日志
V2EX - 技术
V2EX - 技术
Recent Commits to openclaw:main
Recent Commits to openclaw:main
L
Lohrmann on Cybersecurity
The Hacker News
The Hacker News

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

Ловим музу за клавиатуру: как айтишнику стать автором Что умеет 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 за минуты Опыт разработчика как экономика внимания Автономность как точка невозврата: кто будет субъектом в цифровом будущем Обучение ИИ в «диких» условиях: как рутинные действия превращаются в датасеты Как измерить LLM для задач кибербеза: обзор открытых бенчмарков Где хранить код? Сравнение GitHub, GitLab и Bitbucket Математика объясняет, почему нормальное распределение встречается повсюду Почему ваш FinOps не работает: 12 тезисов от практиков Как подписать проектную документацию УКЭП с использованием бесплатных лицензий Pilot Адаптивное администрирование Sigla Vision Я грузил уран в бочки, а потом 20 лет строил ИТ в атомной отрасли Чем позвонить с Эвереста? История и обзор спутниковой связи. Часть 2 Как языковая модель помогает контролировать качество инструктажей по охране труда в металлургии Как не передать на desktop свой IP в РКН Анатомия SAP Privileges: как устроено управление правами в macOS MoneyDev: Сказка про три главных слова Обновлённый токенизатор видео K-VAE 2.0 от Сбера Как сделать диспетчеризацию дома на 1284 квартиры почти бесплатно Как мы разогнали железную дорогу Мы дали агентам рутину. Теперь надо решить — что делать с освободившимся временем Токсичный контент, промпт-хакинг и защита ИИ — всё о Guardrails для LLM Умный город начинается с точного взгляда: как «Фалькон Тех» меняет пространство к лучшему Навайбкодил приложение для анализа графов Почему Дюну так интересно читать? Упрощаем работу с рутиной или как стать Гендальфом Белым Деконструкция Go: CPU, RAM и что там происходит. Go Assembler база. Часть 1.1 Какие профессии исчезнут из-за ИИ, а какие появятся? И что с этим делать Как мы построили IT-отдел, где хочется расти: архитектурные встречи, прозрачные метрики и книжные подарки Rufler: Делаем из Claude Code автономный рой через один YAML-конфиг Sing-box и белый список приложений Как построить надёжный обмен сообщениями в микросервисах: лучшие практики для enterprise OpenAI строит MLM-пирамиду, а McKinsey и Accenture помогают ей в этом Дом, который не построил Фишер (Часть 2) «Сверхзвуковой математик» против «Вдумчивого логиста»: битва алгоритмов 3D-упаковки Мультимодальные модели – грубый и дорогой инструмент Разговоры ничего не стоят. Код тоже Проверки физических лиц: с кого начнет ФНС Топ-10 бесплатных нейросетей для создания видео в 2026 году Первые слои кода: как наши решения сегодня определяют архитектуру ИИ на десятилетия Разработка нового статического анализатора: PVS-Studio JavaScript Поиск уязвимостей ПО: базовый минимум или роскошный максимум Почему оценка персонала не работает как инструмент управления Как мы разработали ИИ-ассистента и сократили рутину продуктовой команды на 50% Как я ушел из найма, нажарил косточек и продал на маркетплейсах на 168 млн в год Когда 1С:ERP уже внедрена, а нормального производственного плана всё ещё нет Как я сделал Claude мультимодальным, подключив к нему Qwen Omni Как приглашение на вакансию мечты превращается в атаку Infrastructure as Code: философия и лучшие практики IaC Тестируем Yandex Code Assistant на задаче, в которой нужно хранить секреты nxs-universal-chart v3.0: новое поколение универсального Helm-чарта Callback Injection: Техника, которая отправила Microsoft Defender в глухой нокаут «Все идеи на стол»: митап как способ вывести проект из тупика Сегодня я узнал нечто новое о GPU благодаря багу в своей игре Как заставить LLM ̶ ̶г̶а̶л̶л̶ю̶ ̶ эволюционировать Карта событий как фундамент аналитики: практический кейс для E-commerce Что выбрать для AI: x86, ARM или RISC-V? Дайджест железа за март Роль соматических мутаций в развитии аутоиммунных заболеваний: путь к избирательной терапии Mythos от Anthropic — тревожный сигнал для всех, а не только для банков Guardrails для LLM на Java: как приручить промпт‑инъекции и токсичные ответы Green-VLA: как мы собрали VLA-модель для реального антропоморфного робота и не потеряли обобщение Финансовая гонка вооружений: почему умные люди добровольно в ней участвуют Эра ИИ-агентов наступила: выбираем лучшего цифрового сотрудника # Практический опыт внедрения WinCC Redundancy на производственном предприятии Сделал MVP за 3 дня, а потом неделю прикручивал оплату. Оно того стоило? Физика против Маска: почему Starship V3 может оказаться ещё одной катастрофой Нефть Венесуэлы: крупнейшие запасы в мире, но не крупнейшая нефтяная держава JPA 4. Переосмысление Hibernate Почему зеркальная фотокамера Nikon D5 десятилетней давности идеально подошла для миссии «Артемида-2» Проект «Уровень-Спутник» или как мы сделали платформу для гидрологов «Замедлиться, чтобы ускориться»: почему ИИ повышает цену ошибок в требованиях и архитектуре Как с нуля поднять трафик IT-компании на 1657% при бюджете 55 тыс. и выжить Pixel-perfect Downsampling — идеальная отрисовка 50 миллионов точек без потерь
Делаем свое контекстное меню для полей ввода в 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 проставят все необходимые статусы. Вот теперь все готово.

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