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

推荐订阅源

有赞技术团队
有赞技术团队
cs.CV updates on arXiv.org
cs.CV updates on arXiv.org
P
Palo Alto Networks Blog
C
Cisco Blogs
The Hacker News
The Hacker News
T
Threatpost
S
Schneier on Security
K
Kaspersky official blog
Spread Privacy
Spread Privacy
博客园_首页
钛媒体:引领未来商业与生活新知
钛媒体:引领未来商业与生活新知
NISL@THU
NISL@THU
量子位
cs.CL updates on arXiv.org
cs.CL updates on arXiv.org
Google DeepMind News
Google DeepMind News
Security Latest
Security Latest
博客园 - 司徒正美
云风的 BLOG
云风的 BLOG
博客园 - 叶小钗
H
Hackread – Cybersecurity News, Data Breaches, AI and More
N
News and Events Feed by Topic
爱范儿
爱范儿
P
Proofpoint News Feed
C
CERT Recently Published Vulnerability Notes
Project Zero
Project Zero
Application and Cybersecurity Blog
Application and Cybersecurity Blog
Cisco Talos Blog
Cisco Talos Blog
GbyAI
GbyAI
cs.AI updates on arXiv.org
cs.AI updates on arXiv.org
Apple Machine Learning Research
Apple Machine Learning Research
T
Tenable Blog
Threat Intelligence Blog | Flashpoint
Threat Intelligence Blog | Flashpoint
V
Vulnerabilities – Threatpost
Forbes - Security
Forbes - Security
博客园 - 三生石上(FineUI控件)
C
Cyber Attacks, Cyber Crime and Cyber Security
N
News and Events Feed by Topic
V
V2EX
Webroot Blog
Webroot Blog
The Register - Security
The Register - Security
OSCHINA 社区最新新闻
OSCHINA 社区最新新闻
阮一峰的网络日志
阮一峰的网络日志
freeCodeCamp Programming Tutorials: Python, JavaScript, Git & More
Blog — PlanetScale
Blog — PlanetScale
M
MIT News - Artificial intelligence
Scott Helme
Scott Helme
Simon Willison's Weblog
Simon Willison's Weblog
L
LangChain Blog
W
WeLiveSecurity
Cloudbric
Cloudbric

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

Ловим музу за клавиатуру: как айтишнику стать автором Что умеет 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 проставят все необходимые статусы. Вот теперь все готово.

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