Revision control

++ // Copyright (C) 2016 The Qt Company Ltd.
++ // SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only
++ // Qt-Security score:significant reason:default
++
++ #include <QtVirtualKeyboard/private/qvirtualkeyboard_global_p.h>
++ #include <QtVirtualKeyboard/private/desktopinputpanel_p.h>
++ #include <QtVirtualKeyboard/private/appinputpanel_p_p.h>
++ #include <QtVirtualKeyboard/private/inputview_p.h>
++ #include <QtVirtualKeyboard/private/platforminputcontext_p.h>
++ #include <QtVirtualKeyboard/private/qvirtualkeyboardinputcontext_p.h>
++ #include <QtVirtualKeyboard/qvirtualkeyboardinputcontext.h>
++ #include <QtVirtualKeyboard/private/settings_p.h>
++ #include <QGuiApplication>
++ #include <QQmlEngine>
++ #include <QScreen>
++ #include <QtVirtualKeyboard/private/virtualkeyboarddebug_p.h>
++ #include <qpa/qplatformnativeinterface.h>
++ #include <QtCore/private/qobject_p.h>
++ #include <QtCore/QLibraryInfo>
++ #include <QtCore/qpointer.h>
++ #include <QtGui/qscreen.h>
++
++ #include <algorithm>
++
++ QT_BEGIN_NAMESPACE
++ namespace QtVirtualKeyboard {
++
++ class DesktopInputPanelPrivate : public AppInputPanelPrivate
++ {
++ public:
++ enum WindowingSystem {
++ Windows,
++ Xcb,
++ Other,
++ };
++
++ DesktopInputPanelPrivate() :
++ AppInputPanelPrivate(),
++ view(),
++ keyboardRect(),
++ previewRect(),
++ previewVisible(false),
++ previewBindingActive(false),
++ windowingSystem(Other)
++ {
++ const QString platformName = QGuiApplication::platformName();
++ if (platformName == QLatin1String("windows"))
++ windowingSystem = Windows;
++ else if (platformName == QLatin1String("xcb"))
++ windowingSystem = Xcb;
++ }
++
++ QScopedPointer<InputView> view;
++ QMetaObject::Connection m_focusWindowVisibleConnection;
++ QMetaObject::Connection m_focusWindowScreenConnection;
++ QPointer<QScreen> m_screen;
++ QPointer<QScreen> m_focusScreen;
++ QRectF keyboardRect;
++ QRectF previewRect;
++ bool previewVisible;
++ bool previewBindingActive;
++ WindowingSystem windowingSystem;
++ };
++
++ /*!
++ \class QtVirtualKeyboard::DesktopInputPanel
++ \internal
++ */
++
++ DesktopInputPanel::DesktopInputPanel(QObject *parent) :
++ AppInputPanel(*new DesktopInputPanelPrivate(), parent)
++ {
++ /* Activate the alpha buffer for this application.
++ */
++ QQuickWindow::setDefaultAlphaBuffer(true);
++
++ connect(Settings::instance(), &Settings::screenNameChanged,
++ this, &DesktopInputPanel::updateEffectiveScreen);
++ if (qGuiApp) {
++ /* Re-evaluate the effective screen when screens come and go, so that
++ a configured screen is picked up if it is connected later, and
++ released when it disappears. */
++ connect(qGuiApp, &QGuiApplication::screenAdded,
++ this, &DesktopInputPanel::updateEffectiveScreen);
++ connect(qGuiApp, &QGuiApplication::screenRemoved,
++ this, &DesktopInputPanel::updateEffectiveScreen);
++ }
++ }
++
++ DesktopInputPanel::~DesktopInputPanel()
++ {
++ }
++
++ void DesktopInputPanel::show()
++ {
++ AppInputPanel::show();
++ Q_D(DesktopInputPanel);
++ if (d->view) {
++ if (auto *screen = d->m_screen.isNull() ? QGuiApplication::primaryScreen() : d->m_screen.data())
++ repositionView(screen->availableGeometry());
++ d->view->show();
++ }
++ }
++
++ void DesktopInputPanel::hide()
++ {
++ AppInputPanel::hide();
++ Q_D(DesktopInputPanel);
++ if (d->view)
++ d->view->hide();
++ }
++
++ bool DesktopInputPanel::isVisible() const
++ {
++ return AppInputPanel::isVisible();
++ }
++
++ void DesktopInputPanel::setInputRect(const QRect &inputRect)
++ {
++ Q_D(DesktopInputPanel);
++ d->keyboardRect = inputRect;
++ updateInputRegion();
++ }
++
++ void DesktopInputPanel::createView()
++ {
++ Q_D(DesktopInputPanel);
++ if (!d->view) {
++ if (qGuiApp) {
++ connect(qGuiApp, SIGNAL(focusWindowChanged(QWindow*)), SLOT(focusWindowChanged(QWindow*)));
++ focusWindowChanged(qGuiApp->focusWindow());
++ }
++ d->view.reset(new InputView());
++ d->view->setFlags(Qt::FramelessWindowHint | Qt::WindowStaysOnTopHint | Qt::WindowDoesNotAcceptFocus);
++ /* Set appropriate WindowType for target environment.
++ There seems to be no common type which would
++ work in all environments. The purpose of this
++ flag is to avoid the window from capturing focus,
++ as well as hiding it from the task bar. */
++ switch (d->windowingSystem) {
++ case DesktopInputPanelPrivate::Xcb:
++ d->view->setFlags(d->view->flags() | Qt::Window | Qt::BypassWindowManagerHint);
++ break;
++ default:
++ d->view->setFlags(d->view->flags() | Qt::Tool);
++ break;
++ }
++ d->view->setColor(QColor(Qt::transparent));
++ #if defined(QT_STATIC)
++ d->view->setSource(QUrl(QLatin1String("qrc:///qt-project.org/imports/QtQuick/VirtualKeyboard/InputPanel.qml")));
++ #else
++ d->view->loadFromModule("QtQuick.VirtualKeyboard", "InputPanel");
++ #endif
++ if (QGuiApplication *app = qGuiApp)
++ connect(app, SIGNAL(aboutToQuit()), SLOT(destroyView()));
++ }
++ }
++
++ void DesktopInputPanel::destroyView()
++ {
++ Q_D(DesktopInputPanel);
++ d->view.reset();
++ d->previewBindingActive = false;
++ }
++
++ void DesktopInputPanel::repositionView(const QRect &rect)
++ {
++ Q_D(DesktopInputPanel);
++ VIRTUALKEYBOARD_DEBUG() << "DesktopInputPanel::repositionView():" << rect;
++ if (d->view && d->view->geometry() != rect) {
++ QVirtualKeyboardInputContext *inputContext = qobject_cast<PlatformInputContext *>(parent())->inputContext();
++ if (inputContext) {
++ inputContext->setAnimating(true);
++ if (!d->previewBindingActive) {
++ QVirtualKeyboardInputContextPrivate *inputContextPrivate = inputContext->priv();
++ QObject::connect(inputContextPrivate, &QVirtualKeyboardInputContextPrivate::previewRectangleChanged, this, &DesktopInputPanel::previewRectangleChanged);
++ QObject::connect(inputContextPrivate, &QVirtualKeyboardInputContextPrivate::previewVisibleChanged, this, &DesktopInputPanel::previewVisibleChanged);
++ d->previewBindingActive = true;
++ }
++ }
++ d->view->setResizeMode(QQuickView::SizeViewToRootObject);
++ setInputRect(QRect());
++ d->view->setGeometry(rect);
++ d->view->setResizeMode(QQuickView::SizeRootObjectToView);
++ if (inputContext)
++ inputContext->setAnimating(false);
++ }
++ }
++
++ void DesktopInputPanel::focusWindowChanged(QWindow *focusWindow)
++ {
++ Q_D(DesktopInputPanel);
++ disconnect(d->m_focusWindowVisibleConnection);
++ disconnect(d->m_focusWindowScreenConnection);
++ if (focusWindow) {
++ d->m_focusWindowVisibleConnection =
++ connect(focusWindow, &QWindow::visibleChanged,
++ this, &DesktopInputPanel::focusWindowVisibleChanged);
++ d->m_focusWindowScreenConnection =
++ connect(focusWindow, &QWindow::screenChanged,
++ this, &DesktopInputPanel::screenChanged);
++ screenChanged(focusWindow->screen());
++ } else {
++ /* Do not clear the tracked focus screen here: the application may
++ simply have lost focus to another application. Re-evaluate so that
++ a configured screen still takes effect. */
++ updateEffectiveScreen();
++ }
++ }
++
++ void DesktopInputPanel::screenChanged(QScreen *screen)
++ {
++ Q_D(DesktopInputPanel);
++ d->m_focusScreen = screen;
++ updateEffectiveScreen();
++ }
++
++ void DesktopInputPanel::updateEffectiveScreen()
++ {
++ Q_D(DesktopInputPanel);
++
++ QScreen *screen = nullptr;
++ const QString screenName = Settings::instance()->screenName();
++ if (!screenName.isEmpty()) {
++ const auto screens = QGuiApplication::screens();
++ const auto it = std::find_if(screens.cbegin(), screens.cend(),
++ [&screenName](QScreen *candidate) {
++ return candidate->name() == screenName;
++ });
++ if (it != screens.cend())
++ screen = *it;
++ else
++ VIRTUALKEYBOARD_DEBUG() << "DesktopInputPanel: configured screen" << screenName
++ << "not found, falling back to the default screen";
++ }
++
++ if (!screen)
++ screen = d->m_focusScreen.data();
++
++ if (d->m_screen.data() == screen)
++ return;
++
++ if (!d->m_screen.isNull()) {
++ disconnect(d->m_screen.data(), &QScreen::availableGeometryChanged,
++ this, &DesktopInputPanel::repositionView);
++ }
++
++ d->m_screen = screen;
++
++ if (!d->m_screen.isNull()) {
++ connect(screen, &QScreen::availableGeometryChanged,
++ this, &DesktopInputPanel::repositionView, Qt::UniqueConnection);
++ repositionView(d->m_screen->availableGeometry());
++ }
++ }
++
++ void DesktopInputPanel::focusWindowVisibleChanged(bool visible)
++ {
++ if (!visible) {
++ QVirtualKeyboardInputContext *inputContext = qobject_cast<PlatformInputContext *>(parent())->inputContext();
++ if (inputContext)
++ inputContext->priv()->hideInputPanel();
++ }
++ }
++
++ void DesktopInputPanel::previewRectangleChanged()
++ {
++ Q_D(DesktopInputPanel);
++ QVirtualKeyboardInputContext *inputContext = qobject_cast<PlatformInputContext *>(parent())->inputContext();
++ d->previewRect = inputContext->priv()->previewRectangle();
++ if (d->previewVisible)
++ updateInputRegion();
++ }
++
++ void DesktopInputPanel::previewVisibleChanged()
++ {
++ Q_D(DesktopInputPanel);
++ QVirtualKeyboardInputContext *inputContext = qobject_cast<PlatformInputContext *>(parent())->inputContext();
++ d->previewVisible = inputContext->priv()->previewVisible();
++ if (d->view->isVisible())
++ updateInputRegion();
++ }
++
++ void DesktopInputPanel::updateInputRegion()
++ {
++ Q_D(DesktopInputPanel);
++
++ if (d->view.isNull())
++ return;
++
++ // Make sure the native window is created
++ if (!d->view->handle())
++ d->view->create();
++
++ if (d->keyboardRect.isEmpty()) {
++ /* The view is a transparent window covering the available geometry of
++ the screen. Leaving it without an input region would make it consume
++ all mouse and touch input on that area, so restrict the input region
++ to a region outside the window instead. An empty QRegion cannot be
++ used for this, because that resets the mask.
++ */
++ d->view->setMask(QRegion(-1, -1, 1, 1));
++ return;
++ }
++
++ QRegion inputRegion(d->keyboardRect.toRect());
++ if (d->previewVisible && !d->previewRect.isEmpty())
++ inputRegion += d->previewRect.toRect();
++
++ d->view->setMask(inputRegion);
++ }
++
++ } // namespace QtVirtualKeyboard
++ QT_END_NAMESPACE