/**************************************************************************** ** ** Copyright (C) 2019 The Qt Company Ltd. ** Contact: https://www.qt.io/licensing/ ** ** This file is part of the Qt Virtual Keyboard module of the Qt Toolkit. ** ** $QT_BEGIN_LICENSE:GPL$ ** Commercial License Usage ** Licensees holding valid commercial Qt licenses may use this file in ** accordance with the commercial license agreement provided with the ** Software or, alternatively, in accordance with the terms contained in ** a written agreement between you and The Qt Company. For licensing terms ** and conditions see https://www.qt.io/terms-conditions. For further ** information use the contact form at https://www.qt.io/contact-us. ** ** GNU General Public License Usage ** Alternatively, this file may be used under the terms of the GNU ** General Public License version 3 or (at your option) any later version ** approved by the KDE Free Qt Foundation. The licenses are as published by ** the Free Software Foundation and appearing in the file LICENSE.GPL3 ** included in the packaging of this file. Please review the following ** information to ensure the GNU General Public License requirements will ** be met: https://www.gnu.org/licenses/gpl-3.0.html. ** ** $QT_END_LICENSE$ ** ****************************************************************************/ #include #include #include #include #include "handleatspievents.h" #include "vkbhidetimer.h" #include "atspi/atspi.h" namespace { const QString KAtspiBusLauncher = "at-spi-bus-launcher"; const QString KAtspiRegistryd = "at-spi2-registryd"; const int KProsessIsRunning = 0; } Q_LOGGING_CATEGORY(lcHandleAtspiEvents, "qt.virtualkeyboard.tests.manual.x11vkbwrapper.handleatspievents") /** * @brief focusEventFromInput Called when a widget is focused. * @param event * @param user_data */ void focusEventFromInput(AtspiEvent *event, void *user_data) { qCDebug(lcHandleAtspiEvents) << Q_FUNC_INFO; auto *handleATSPIEvents = static_cast(user_data); handleATSPIEvents->gotFocusEventFromInput(event); } /** * @brief HandleATSPIEvents::HandleATSPIEvents * @param parent */ HandleATSPIEvents::HandleATSPIEvents(QObject *parent) : QObject(parent), m_keyboardVisible(false), m_focuses(0) { } /** * @brief HandleATSPIEvents::~HandleATSPIEvents */ HandleATSPIEvents::~HandleATSPIEvents() { qCDebug(lcHandleAtspiEvents) << Q_FUNC_INFO; m_focuses.clear(); if (!atspi_event_listener_deregister_from_callback(focusEventFromInput, static_cast(this), "object:state-changed:focused", nullptr)) { qWarning() << "Error occurred: Problem deregistering focus listener"; } } /** * @brief HandleATSPIEvents::init * @return false if at-spi is not running or callback regitering fail */ bool HandleATSPIEvents::init() { qCDebug(lcHandleAtspiEvents) << Q_FUNC_INFO; /** Check that At-spi is running */ if (KProsessIsRunning != system(QString("pidof -x %1 > /dev/null").arg(KAtspiBusLauncher).toLatin1().data()) || KProsessIsRunning != system(QString("pidof -x %1 > /dev/null").arg(KAtspiRegistryd).toLatin1().data())) { qWarning() << "One or both of the At-Spi related processes are not running."; return false; } GError *error = nullptr; /** Registered the spi events to monitor focus and show on editable widgets. */ if (!atspi_event_listener_register_from_callback(focusEventFromInput, static_cast(this), nullptr, "object:state-changed:focused", &error)){ qWarning() << Q_FUNC_INFO << "Error occurred: ATSPI listener register failed. Error message:" << error->message; return false; } QObject::connect(QGuiApplication::inputMethod(), &QInputMethod::visibleChanged, [this] { this->setKeyboardVisible(QGuiApplication::inputMethod()->isVisible()); }); return true; } /** * @brief handleATSPIEvents::setKeyboardVisible * @param visible */ void HandleATSPIEvents::setKeyboardVisible(const bool visible) { if (m_keyboardVisible != visible) { m_keyboardVisible = visible; qCDebug(lcHandleAtspiEvents) << "SET VKB visible " << visible; if (m_keyboardVisible && !QGuiApplication::inputMethod()->isVisible()) { QGuiApplication::inputMethod()->show(); } else { QGuiApplication::inputMethod()->hide(); } } } /** * @brief handleATSPIEvents::storeFocusElement * @param role * @param focus */ void HandleATSPIEvents::storeFocusElement(const qint8 role) { m_focuses.append(role); qCDebug(lcHandleAtspiEvents) << "*****INSERTED FOCUS ELEMENT*****" << role << "TOTAL:" << m_focuses.length(); } /** * @brief handleATSPIEvents::isThereFocus * AT-SPI focus in/out events are received in random order and for some * objects AT-SPI doesn't send any focus OUT event at all. * This function keeps track if there's an accepted type of object in focus and * knows to release/ignore the objects that do not receive focus OUT event. * @param role */ bool HandleATSPIEvents::isThereFocus(const qint8 role) { qCDebug(lcHandleAtspiEvents) << " FOCUS ELEMENT to EXAMINE: " << role; qint8 roleValue = ATSPI_ROLE_INVALID; for (auto iter = m_focuses.begin() ; iter != m_focuses.end() ; iter++) { roleValue = *iter; if (roleValue == role || roleValue == ATSPI_ROLE_DOCUMENT_WEB || roleValue == ATSPI_ROLE_ENTRY || roleValue == ATSPI_ROLE_LINK) { qCDebug(lcHandleAtspiEvents) << "*****REMOVING FOCUS ELEMENT*****: " << *iter; m_focuses.erase(iter--); } } m_focuses.squeeze(); return !m_focuses.isEmpty(); } /** * @brief handleATSPIEvents::gotFocusEventFromInput * @param event */ void HandleATSPIEvents::gotFocusEventFromInput(const AtspiEvent *event) { qCDebug(lcHandleAtspiEvents) << Q_FUNC_INFO << event->type << event->detail1 << event->detail2 << QTime::currentTime().toString(); GError *error = nullptr; AtspiStateSet *state_set = atspi_accessible_get_state_set(event->source); AtspiRole role = atspi_accessible_get_role(event->source, &error); if (error) { qCDebug(lcHandleAtspiEvents) << Q_FUNC_INFO << "Event error message:" << error->message; } qCDebug(lcHandleAtspiEvents) << "ATSPI focus event received. Object role=" << role; if ((((role == ATSPI_ROLE_TERMINAL) || (role == ATSPI_ROLE_PANEL) || (role == ATSPI_ROLE_TABLE_CELL) || (((role == ATSPI_ROLE_TEXT) || (role == ATSPI_ROLE_PASSWORD_TEXT) || (role == ATSPI_ROLE_SECTION) || (role == ATSPI_ROLE_PARAGRAPH) || (role = ATSPI_ROLE_ENTRY)) && state_set && atspi_state_set_contains(state_set, ATSPI_STATE_EDITABLE))))) { if (event->detail1) { qCDebug(lcHandleAtspiEvents) << "ACCEPTED FOCUS IN"; VkbHideTimer::getInstance()->startTimer(); this->storeFocusElement(role); if (!m_keyboardVisible) { setKeyboardVisible(true); } } else if (m_keyboardVisible && !isThereFocus(role)) { setKeyboardVisible(false); } } else { qCDebug(lcHandleAtspiEvents) << " ELSE: SET VKB visible FALSE"; setKeyboardVisible(false); m_focuses.clear(); m_focuses.squeeze(); } }