From c28fde3fdac19fd5a5f614bb7983080031c924b3 Mon Sep 17 00:00:00 2001 From: Gerry Boland Date: Wed, 2 Nov 2016 16:46:53 +0000 Subject: Mirclient: update based on upstream development in lp:qtubuntu This is based on revision 360 of lp:qtubuntu. Main features/bugs fixed: - fix QQuickWidget-based app rendering - wire up Qt window types to Mir to enable desktop-based applications to function with a window manager - use QEGLPlatformContext and QEGLPBuffer instead of custom code - correctly populate and update list of QScreens - support for switching keyboard layouts - improve window resizing to fix visual glitching Change-Id: If816a858eb10b6356275d4b80c89a72562b3c29f Reviewed-by: Eirik Aavitsland Reviewed-by: Matti Paaso --- src/gui/configure.json | 2 +- src/plugins/platforms/mirclient/mirclient.pro | 18 + .../mirclient/qmirclientappstatecontroller.cpp | 102 +++ .../mirclient/qmirclientappstatecontroller.h | 62 ++ .../platforms/mirclient/qmirclientbackingstore.cpp | 12 +- .../platforms/mirclient/qmirclientbackingstore.h | 1 + .../platforms/mirclient/qmirclientclipboard.cpp | 303 +++----- .../platforms/mirclient/qmirclientclipboard.h | 37 +- .../platforms/mirclient/qmirclientcursor.cpp | 43 +- src/plugins/platforms/mirclient/qmirclientcursor.h | 2 +- .../mirclient/qmirclientdebugextension.cpp | 79 ++ .../platforms/mirclient/qmirclientdebugextension.h | 63 ++ .../mirclient/qmirclientdesktopwindow.cpp | 50 ++ .../platforms/mirclient/qmirclientdesktopwindow.h | 53 ++ .../platforms/mirclient/qmirclientglcontext.cpp | 159 ++-- .../platforms/mirclient/qmirclientglcontext.h | 28 +- .../platforms/mirclient/qmirclientinput.cpp | 331 ++++++--- src/plugins/platforms/mirclient/qmirclientinput.h | 10 +- .../platforms/mirclient/qmirclientintegration.cpp | 255 +++++-- .../platforms/mirclient/qmirclientintegration.h | 47 +- .../platforms/mirclient/qmirclientlogging.h | 24 +- .../mirclient/qmirclientnativeinterface.cpp | 103 ++- .../mirclient/qmirclientnativeinterface.h | 19 +- .../platforms/mirclient/qmirclientplugin.cpp | 15 +- src/plugins/platforms/mirclient/qmirclientplugin.h | 4 +- .../platforms/mirclient/qmirclientscreen.cpp | 278 +++---- src/plugins/platforms/mirclient/qmirclientscreen.h | 41 +- .../mirclient/qmirclientscreenobserver.cpp | 161 ++++ .../platforms/mirclient/qmirclientscreenobserver.h | 78 ++ .../platforms/mirclient/qmirclientwindow.cpp | 819 ++++++++++++++------- src/plugins/platforms/mirclient/qmirclientwindow.h | 50 +- 31 files changed, 2182 insertions(+), 1067 deletions(-) create mode 100644 src/plugins/platforms/mirclient/qmirclientappstatecontroller.cpp create mode 100644 src/plugins/platforms/mirclient/qmirclientappstatecontroller.h create mode 100644 src/plugins/platforms/mirclient/qmirclientdebugextension.cpp create mode 100644 src/plugins/platforms/mirclient/qmirclientdebugextension.h create mode 100644 src/plugins/platforms/mirclient/qmirclientdesktopwindow.cpp create mode 100644 src/plugins/platforms/mirclient/qmirclientdesktopwindow.h create mode 100644 src/plugins/platforms/mirclient/qmirclientscreenobserver.cpp create mode 100644 src/plugins/platforms/mirclient/qmirclientscreenobserver.h diff --git a/src/gui/configure.json b/src/gui/configure.json index f862b5866c..5ab0b98c69 100644 --- a/src/gui/configure.json +++ b/src/gui/configure.json @@ -168,7 +168,7 @@ "label": "Mir client libraries", "test": "qpa/mirclient", "sources": [ - { "type": "pkgConfig", "args": "egl mirclient ubuntu-platform-api" } + { "type": "pkgConfig", "args": "egl mirclient ubuntu-platform-api libcontent-hub" } ] }, "mtdev": { diff --git a/src/plugins/platforms/mirclient/mirclient.pro b/src/plugins/platforms/mirclient/mirclient.pro index 0ba63601a9..d2da7e6ca0 100644 --- a/src/plugins/platforms/mirclient/mirclient.pro +++ b/src/plugins/platforms/mirclient/mirclient.pro @@ -5,6 +5,9 @@ QT += \ theme_support-private eventdispatcher_support-private \ fontdatabase_support-private egl_support-private +qtHaveModule(linuxaccessibility_support-private): \ + QT += linuxaccessibility_support-private + DEFINES += MESA_EGL_NO_X11_HEADERS # CONFIG += c++11 # only enables C++0x QMAKE_CXXFLAGS += -fvisibility=hidden -fvisibility-inlines-hidden -std=c++11 -Werror -Wall @@ -13,9 +16,12 @@ QMAKE_LFLAGS += -std=c++11 -Wl,-no-undefined QMAKE_USE_PRIVATE += mirclient SOURCES = \ + qmirclientappstatecontroller.cpp \ qmirclientbackingstore.cpp \ qmirclientclipboard.cpp \ qmirclientcursor.cpp \ + qmirclientdebugextension.cpp \ + qmirclientdesktopwindow.cpp \ qmirclientglcontext.cpp \ qmirclientinput.cpp \ qmirclientintegration.cpp \ @@ -23,13 +29,17 @@ SOURCES = \ qmirclientplatformservices.cpp \ qmirclientplugin.cpp \ qmirclientscreen.cpp \ + qmirclientscreenobserver.cpp \ qmirclienttheme.cpp \ qmirclientwindow.cpp HEADERS = \ + qmirclientappstatecontroller.h \ qmirclientbackingstore.h \ qmirclientclipboard.h \ qmirclientcursor.h \ + qmirclientdebugextension.h \ + qmirclientdesktopwindow.h \ qmirclientglcontext.h \ qmirclientinput.h \ qmirclientintegration.h \ @@ -39,9 +49,17 @@ HEADERS = \ qmirclientplatformservices.h \ qmirclientplugin.h \ qmirclientscreen.h \ + qmirclientscreenobserver.h \ qmirclienttheme.h \ qmirclientwindow.h +# libxkbcommon +!qtConfig(xkbcommon-system) { + include(../../../3rdparty/xkbcommon.pri) +} else { + QMAKE_USE += xkbcommon +} + PLUGIN_TYPE = platforms PLUGIN_CLASS_NAME = MirServerIntegrationPlugin !equals(TARGET, $$QT_DEFAULT_QPA_PLUGIN): PLUGIN_EXTENDS = - diff --git a/src/plugins/platforms/mirclient/qmirclientappstatecontroller.cpp b/src/plugins/platforms/mirclient/qmirclientappstatecontroller.cpp new file mode 100644 index 0000000000..69fc9b7aa7 --- /dev/null +++ b/src/plugins/platforms/mirclient/qmirclientappstatecontroller.cpp @@ -0,0 +1,102 @@ +/**************************************************************************** +** +** Copyright (C) 2016 Canonical, Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of the plugins of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** 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 Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 3 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL3 included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 3 requirements +** will be met: https://www.gnu.org/licenses/lgpl-3.0.html. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 2.0 or (at your option) the GNU General +** Public license version 3 or 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.GPL2 and 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-2.0.html and +** https://www.gnu.org/licenses/gpl-3.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + + +#include "qmirclientappstatecontroller.h" + +#include + +/* + * QMirClientAppStateController - updates Qt's QApplication::applicationState property. + * + * Tries to avoid active-inactive-active invocations using a timer. The rapid state + * change can confuse some applications. + */ + +QMirClientAppStateController::QMirClientAppStateController() + : m_suspended(false) + , m_lastActive(true) +{ + m_inactiveTimer.setSingleShot(true); + m_inactiveTimer.setInterval(10); + QObject::connect(&m_inactiveTimer, &QTimer::timeout, []() + { + QWindowSystemInterface::handleApplicationStateChanged(Qt::ApplicationInactive); + }); +} + +void QMirClientAppStateController::setSuspended() +{ + m_inactiveTimer.stop(); + if (!m_suspended) { + m_suspended = true; + + QWindowSystemInterface::handleApplicationStateChanged(Qt::ApplicationSuspended); + } +} + +void QMirClientAppStateController::setResumed() +{ + m_inactiveTimer.stop(); + if (m_suspended) { + m_suspended = false; + + if (m_lastActive) { + QWindowSystemInterface::handleApplicationStateChanged(Qt::ApplicationActive); + } else { + QWindowSystemInterface::handleApplicationStateChanged(Qt::ApplicationInactive); + } + } +} + +void QMirClientAppStateController::setWindowFocused(bool focused) +{ + if (m_suspended) { + return; + } + + if (focused) { + m_inactiveTimer.stop(); + QWindowSystemInterface::handleApplicationStateChanged(Qt::ApplicationActive); + } else { + m_inactiveTimer.start(); + } + + m_lastActive = focused; +} diff --git a/src/plugins/platforms/mirclient/qmirclientappstatecontroller.h b/src/plugins/platforms/mirclient/qmirclientappstatecontroller.h new file mode 100644 index 0000000000..b3aa0022d9 --- /dev/null +++ b/src/plugins/platforms/mirclient/qmirclientappstatecontroller.h @@ -0,0 +1,62 @@ +/**************************************************************************** +** +** Copyright (C) 2016 Canonical, Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of the plugins of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** 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 Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 3 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL3 included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 3 requirements +** will be met: https://www.gnu.org/licenses/lgpl-3.0.html. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 2.0 or (at your option) the GNU General +** Public license version 3 or 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.GPL2 and 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-2.0.html and +** https://www.gnu.org/licenses/gpl-3.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + + +#ifndef QMIRCLIENTAPPSTATECONTROLLER_H +#define QMIRCLIENTAPPSTATECONTROLLER_H + +#include + +class QMirClientAppStateController +{ +public: + QMirClientAppStateController(); + + void setSuspended(); + void setResumed(); + + void setWindowFocused(bool focused); + +private: + bool m_suspended; + bool m_lastActive; + QTimer m_inactiveTimer; +}; + +#endif // QMIRCLIENTAPPSTATECONTROLLER_H diff --git a/src/plugins/platforms/mirclient/qmirclientbackingstore.cpp b/src/plugins/platforms/mirclient/qmirclientbackingstore.cpp index a4bb8864ab..51363619d9 100644 --- a/src/plugins/platforms/mirclient/qmirclientbackingstore.cpp +++ b/src/plugins/platforms/mirclient/qmirclientbackingstore.cpp @@ -61,6 +61,7 @@ QMirClientBackingStore::QMirClientBackingStore(QWindow* window) QMirClientBackingStore::~QMirClientBackingStore() { + mContext->makeCurrent(window()); // needed as QOpenGLTexture destructor assumes current context } void QMirClientBackingStore::flush(QWindow* window, const QRegion& region, const QPoint& offset) @@ -76,7 +77,6 @@ void QMirClientBackingStore::flush(QWindow* window, const QRegion& region, const mBlitter->create(); mBlitter->bind(); - mBlitter->setRedBlueSwizzle(true); mBlitter->blit(mTexture->textureId(), QMatrix4x4(), QOpenGLTextureBlitter::OriginTopLeft); mBlitter->release(); @@ -137,7 +137,9 @@ void QMirClientBackingStore::beginPaint(const QRegion& region) void QMirClientBackingStore::resize(const QSize& size, const QRegion& /*staticContents*/) { - mImage = QImage(size, QImage::Format_RGB32); + mImage = QImage(size, QImage::Format_RGBA8888); + + mContext->makeCurrent(window()); if (mTexture->isCreated()) mTexture->destroy(); @@ -147,3 +149,9 @@ QPaintDevice* QMirClientBackingStore::paintDevice() { return &mImage; } + +QImage QMirClientBackingStore::toImage() const +{ + // used by QPlatformBackingStore::composeAndFlush + return mImage; +} diff --git a/src/plugins/platforms/mirclient/qmirclientbackingstore.h b/src/plugins/platforms/mirclient/qmirclientbackingstore.h index 0c75e182ff..7644c77df2 100644 --- a/src/plugins/platforms/mirclient/qmirclientbackingstore.h +++ b/src/plugins/platforms/mirclient/qmirclientbackingstore.h @@ -58,6 +58,7 @@ public: void flush(QWindow* window, const QRegion& region, const QPoint& offset) override; void resize(const QSize& size, const QRegion& staticContents) override; QPaintDevice* paintDevice() override; + QImage toImage() const override; protected: void updateTexture(); diff --git a/src/plugins/platforms/mirclient/qmirclientclipboard.cpp b/src/plugins/platforms/mirclient/qmirclientclipboard.cpp index 4dff339f21..b9fc9b3b42 100644 --- a/src/plugins/platforms/mirclient/qmirclientclipboard.cpp +++ b/src/plugins/platforms/mirclient/qmirclientclipboard.cpp @@ -39,41 +39,36 @@ #include "qmirclientclipboard.h" +#include "qmirclientlogging.h" +#include "qmirclientwindow.h" +#include +#include +#include #include #include -#include -#include -#include - -// FIXME(loicm) The clipboard data format is not defined by Ubuntu Platform API -// which makes it impossible to have non-Qt applications communicate with Qt -// applications through the clipboard API. The solution would be to have -// Ubuntu Platform define the data format or propose an API that supports -// embedding different mime types in the clipboard. -// Data format: -// number of mime types (sizeof(int)) -// data layout ((4 * sizeof(int)) * number of mime types) -// mime type string offset (sizeof(int)) -// mime type string size (sizeof(int)) -// data offset (sizeof(int)) -// data size (sizeof(int)) -// data (n bytes) +// content-hub +#include -namespace { - -const int maxFormatsCount = 16; -const int maxBufferSize = 4 * 1024 * 1024; // 4 Mb - -} +// get this cumbersome nested namespace out of the way +using namespace com::ubuntu::content; QMirClientClipboard::QMirClientClipboard() : mMimeData(new QMimeData) - , mIsOutdated(true) - , mUpdatesDisabled(false) - , mDBusSetupDone(false) + , mContentHub(Hub::Client::instance()) { + connect(mContentHub, &Hub::pasteboardChanged, this, [this]() { + if (mClipboardState == QMirClientClipboard::SyncedClipboard) { + mClipboardState = QMirClientClipboard::OutdatedClipboard; + emitChanged(QClipboard::Clipboard); + } + }); + + connect(qGuiApp, &QGuiApplication::applicationStateChanged, + this, &QMirClientClipboard::onApplicationStateChanged); + + requestMimeData(); } QMirClientClipboard::~QMirClientClipboard() @@ -81,236 +76,106 @@ QMirClientClipboard::~QMirClientClipboard() delete mMimeData; } -void QMirClientClipboard::requestDBusClipboardContents() +QMimeData* QMirClientClipboard::mimeData(QClipboard::Mode mode) { - if (!mDBusSetupDone) { - setupDBus(); - } - - if (!mPendingGetContentsCall.isNull()) - return; + if (mode != QClipboard::Clipboard) + return nullptr; - QDBusPendingCall pendingCall = mDBusClipboard->asyncCall(QStringLiteral("GetContents")); + // Blocks dataChanged() signal from being emitted. Makes no sense to emit it from + // inside the data getter. + const QSignalBlocker blocker(this); - mPendingGetContentsCall = new QDBusPendingCallWatcher(pendingCall, this); + if (mClipboardState == OutdatedClipboard) { + updateMimeData(); + } else if (mClipboardState == SyncingClipboard) { + mPasteReply->waitForFinished(); + } - QObject::connect(mPendingGetContentsCall.data(), &QDBusPendingCallWatcher::finished, - this, &QMirClientClipboard::onDBusClipboardGetContentsFinished); + return mMimeData; } -void QMirClientClipboard::onDBusClipboardGetContentsFinished(QDBusPendingCallWatcher* call) +void QMirClientClipboard::setMimeData(QMimeData* mimeData, QClipboard::Mode mode) { - Q_ASSERT(call == mPendingGetContentsCall.data()); + QWindow *focusWindow = QGuiApplication::focusWindow(); + if (focusWindow && mode == QClipboard::Clipboard && mimeData != nullptr) { + QString surfaceId = static_cast(focusWindow->handle())->persistentSurfaceId(); - QDBusPendingReply reply = *call; - if (Q_UNLIKELY(reply.isError())) { - qCritical("QMirClientClipboard - Failed to get system clipboard contents via D-Bus. %s, %s", - qPrintable(reply.error().name()), qPrintable(reply.error().message())); - // TODO: Might try again later a number of times... - } else { - QByteArray serializedMimeData = reply.argumentAt<0>(); - updateMimeData(serializedMimeData); - } - call->deleteLater(); -} + QDBusPendingCall reply = mContentHub->createPaste(surfaceId, *mimeData); -void QMirClientClipboard::onDBusClipboardSetContentsFinished(QDBusPendingCallWatcher *call) -{ - QDBusPendingReply reply = *call; - if (Q_UNLIKELY(reply.isError())) { - qCritical("QMirClientClipboard - Failed to set the system clipboard contents via D-Bus. %s, %s", - qPrintable(reply.error().name()), qPrintable(reply.error().message())); - // TODO: Might try again later a number of times... - } - call->deleteLater(); -} - -void QMirClientClipboard::updateMimeData(const QByteArray &serializedMimeData) -{ - if (mUpdatesDisabled) - return; + // Don't care whether it succeeded + QDBusPendingCallWatcher *watcher = new QDBusPendingCallWatcher(reply, this); + connect(watcher, &QDBusPendingCallWatcher::finished, + watcher, &QObject::deleteLater); - QMimeData *newMimeData = deserializeMimeData(serializedMimeData); - if (newMimeData) { - delete mMimeData; - mMimeData = newMimeData; - mIsOutdated = false; + mMimeData = mimeData; + mClipboardState = SyncedClipboard; emitChanged(QClipboard::Clipboard); - } else { - qWarning("QMirClientClipboard - Got invalid serialized mime data. Ignoring it."); } } -void QMirClientClipboard::setupDBus() -{ - QDBusConnection dbusConnection = QDBusConnection::sessionBus(); - - bool ok = dbusConnection.connect( - QStringLiteral("com.canonical.QtMir"), - QStringLiteral("/com/canonical/QtMir/Clipboard"), - QStringLiteral("com.canonical.QtMir.Clipboard"), - QStringLiteral("ContentsChanged"), - this, SLOT(updateMimeData(QByteArray))); - if (Q_UNLIKELY(!ok)) - qCritical("QMirClientClipboard - Failed to connect to ContentsChanged signal form the D-Bus system clipboard."); - - mDBusClipboard = new QDBusInterface(QStringLiteral("com.canonical.QtMir"), - QStringLiteral("/com/canonical/QtMir/Clipboard"), - QStringLiteral("com.canonical.QtMir.Clipboard"), - dbusConnection); - - mDBusSetupDone = true; -} - -QByteArray QMirClientClipboard::serializeMimeData(QMimeData *mimeData) const +bool QMirClientClipboard::supportsMode(QClipboard::Mode mode) const { - Q_ASSERT(mimeData != nullptr); - - const QStringList formats = mimeData->formats(); - const int formatCount = qMin(formats.size(), maxFormatsCount); - const int headerSize = sizeof(int) + (formatCount * 4 * sizeof(int)); - int bufferSize = headerSize; - - for (int i = 0; i < formatCount; i++) - bufferSize += formats[i].size() + mimeData->data(formats[i]).size(); - - QByteArray serializedMimeData; - if (bufferSize <= maxBufferSize) { - // Serialize data. - serializedMimeData.resize(bufferSize); - { - char *buffer = serializedMimeData.data(); - int* header = reinterpret_cast(serializedMimeData.data()); - int offset = headerSize; - header[0] = formatCount; - for (int i = 0; i < formatCount; i++) { - const QByteArray data = mimeData->data(formats[i]); - const int formatOffset = offset; - const int formatSize = formats[i].size(); - const int dataOffset = offset + formatSize; - const int dataSize = data.size(); - memcpy(&buffer[formatOffset], formats[i].toLatin1().data(), formatSize); - memcpy(&buffer[dataOffset], data.data(), dataSize); - header[i*4+1] = formatOffset; - header[i*4+2] = formatSize; - header[i*4+3] = dataOffset; - header[i*4+4] = dataSize; - offset += formatSize + dataSize; - } - } - } else { - qWarning("QMirClientClipboard: Not sending contents (%d bytes) to the global clipboard as it's" - " bigger than the maximum allowed size of %d bytes", bufferSize, maxBufferSize); - } - - return serializedMimeData; + return mode == QClipboard::Clipboard; } -QMimeData *QMirClientClipboard::deserializeMimeData(const QByteArray &serializedMimeData) const +bool QMirClientClipboard::ownsMode(QClipboard::Mode mode) const { - if (static_cast(serializedMimeData.size()) < sizeof(int)) { - // Data is invalid - return nullptr; - } - - QMimeData *mimeData = new QMimeData; - - const char* const buffer = serializedMimeData.constData(); - const int* const header = reinterpret_cast(serializedMimeData.constData()); - - const int count = qMin(header[0], maxFormatsCount); - - for (int i = 0; i < count; i++) { - const int formatOffset = header[i*4+1]; - const int formatSize = header[i*4+2]; - const int dataOffset = header[i*4+3]; - const int dataSize = header[i*4+4]; - - if (formatOffset + formatSize <= serializedMimeData.size() - && dataOffset + dataSize <= serializedMimeData.size()) { - - QString mimeType = QString::fromLatin1(&buffer[formatOffset], formatSize); - QByteArray mimeDataBytes(&buffer[dataOffset], dataSize); - - mimeData->setData(mimeType, mimeDataBytes); - } - } - - return mimeData; + Q_UNUSED(mode); + return false; } -QMimeData* QMirClientClipboard::mimeData(QClipboard::Mode mode) +void QMirClientClipboard::onApplicationStateChanged(Qt::ApplicationState state) { - if (mode != QClipboard::Clipboard) - return nullptr; - - if (mIsOutdated && mPendingGetContentsCall.isNull()) { - requestDBusClipboardContents(); + if (state == Qt::ApplicationActive) { + // Only focused or active applications might be allowed to paste, so we probably + // missed changes in the clipboard while we were hidden, inactive or, more importantly, + // suspended. + requestMimeData(); } - - // Return whatever we have at the moment instead of blocking until we have something. - // - // This might be called during app startup just for the sake of checking if some - // "Paste" UI control should be enabled or not. - // We will emit QClipboard::changed() once we finally have something. - return mMimeData; } -void QMirClientClipboard::setMimeData(QMimeData* mimeData, QClipboard::Mode mode) +void QMirClientClipboard::updateMimeData() { - if (mode != QClipboard::Clipboard) + if (qGuiApp->applicationState() != Qt::ApplicationActive) { + // Don't even bother asking as content-hub would probably ignore our request (and should). return; - - if (!mPendingGetContentsCall.isNull()) { - // Ignore whatever comes from the system clipboard as we are going to change it anyway - QObject::disconnect(mPendingGetContentsCall.data(), 0, this, 0); - mUpdatesDisabled = true; - mPendingGetContentsCall->waitForFinished(); - mUpdatesDisabled = false; - delete mPendingGetContentsCall.data(); } - if (mimeData != nullptr) { - QByteArray serializedMimeData = serializeMimeData(mimeData); - if (!serializedMimeData.isEmpty()) { - setDBusClipboardContents(serializedMimeData); - } + delete mMimeData; - mMimeData = mimeData; + QWindow *focusWindow = QGuiApplication::focusWindow(); + if (focusWindow) { + QString surfaceId = static_cast(focusWindow->handle())->persistentSurfaceId(); + mMimeData = mContentHub->latestPaste(surfaceId); + mClipboardState = SyncedClipboard; emitChanged(QClipboard::Clipboard); } } -bool QMirClientClipboard::supportsMode(QClipboard::Mode mode) const -{ - return mode == QClipboard::Clipboard; -} - -bool QMirClientClipboard::ownsMode(QClipboard::Mode mode) const +void QMirClientClipboard::requestMimeData() { - Q_UNUSED(mode); - return false; -} - -void QMirClientClipboard::setDBusClipboardContents(const QByteArray &clipboardContents) -{ - if (!mDBusSetupDone) { - setupDBus(); + if (qGuiApp->applicationState() != Qt::ApplicationActive) { + // Don't even bother asking as content-hub would probably ignore our request (and should). + return; } - if (!mPendingSetContentsCall.isNull()) { - // Ignore any previous set call as we are going to overwrite it anyway - QObject::disconnect(mPendingSetContentsCall.data(), 0, this, 0); - mUpdatesDisabled = true; - mPendingSetContentsCall->waitForFinished(); - mUpdatesDisabled = false; - delete mPendingSetContentsCall.data(); + QWindow *focusWindow = QGuiApplication::focusWindow(); + if (!focusWindow) { + return; } - QDBusPendingCall pendingCall = mDBusClipboard->asyncCall(QStringLiteral("SetContents"), clipboardContents); - - mPendingSetContentsCall = new QDBusPendingCallWatcher(pendingCall, this); + QString surfaceId = static_cast(focusWindow->handle())->persistentSurfaceId(); + QDBusPendingCall reply = mContentHub->requestLatestPaste(surfaceId); + mClipboardState = SyncingClipboard; - QObject::connect(mPendingSetContentsCall.data(), &QDBusPendingCallWatcher::finished, - this, &QMirClientClipboard::onDBusClipboardSetContentsFinished); + mPasteReply = new QDBusPendingCallWatcher(reply, this); + connect(mPasteReply, &QDBusPendingCallWatcher::finished, + this, [this]() { + delete mMimeData; + mMimeData = mContentHub->paste(*mPasteReply); + mClipboardState = SyncedClipboard; + mPasteReply->deleteLater(); + mPasteReply = nullptr; + emitChanged(QClipboard::Clipboard); + }); } diff --git a/src/plugins/platforms/mirclient/qmirclientclipboard.h b/src/plugins/platforms/mirclient/qmirclientclipboard.h index 1394498320..09e9bcdf38 100644 --- a/src/plugins/platforms/mirclient/qmirclientclipboard.h +++ b/src/plugins/platforms/mirclient/qmirclientclipboard.h @@ -45,7 +45,15 @@ #include #include -class QDBusInterface; + +namespace com { + namespace ubuntu { + namespace content { + class Hub; + } + } +} + class QDBusPendingCallWatcher; class QMirClientClipboard : public QObject, public QPlatformClipboard @@ -61,31 +69,24 @@ public: bool supportsMode(QClipboard::Mode mode) const override; bool ownsMode(QClipboard::Mode mode) const override; - void requestDBusClipboardContents(); - private Q_SLOTS: - void onDBusClipboardGetContentsFinished(QDBusPendingCallWatcher*); - void onDBusClipboardSetContentsFinished(QDBusPendingCallWatcher*); - void updateMimeData(const QByteArray &serializedMimeData); + void onApplicationStateChanged(Qt::ApplicationState state); private: - void setupDBus(); - - QByteArray serializeMimeData(QMimeData *mimeData) const; - QMimeData *deserializeMimeData(const QByteArray &serializedMimeData) const; - - void setDBusClipboardContents(const QByteArray &clipboardContents); + void updateMimeData(); + void requestMimeData(); QMimeData *mMimeData; - bool mIsOutdated; - QPointer mDBusClipboard; + enum { + OutdatedClipboard, // Our mimeData is outdated, need to fetch latest from ContentHub + SyncingClipboard, // Our mimeData is outdated and we are waiting for ContentHub to reply with the latest paste + SyncedClipboard // Our mimeData is in sync with what ContentHub has + } mClipboardState{OutdatedClipboard}; - QPointer mPendingGetContentsCall; - QPointer mPendingSetContentsCall; + com::ubuntu::content::Hub *mContentHub; - bool mUpdatesDisabled; - bool mDBusSetupDone; + QDBusPendingCallWatcher *mPasteReply{nullptr}; }; #endif // QMIRCLIENTCLIPBOARD_H diff --git a/src/plugins/platforms/mirclient/qmirclientcursor.cpp b/src/plugins/platforms/mirclient/qmirclientcursor.cpp index a0da3fdd77..812cde95c6 100644 --- a/src/plugins/platforms/mirclient/qmirclientcursor.cpp +++ b/src/plugins/platforms/mirclient/qmirclientcursor.cpp @@ -1,6 +1,6 @@ /**************************************************************************** ** -** Copyright (C) 2015 Canonical, Ltd. +** Copyright (C) 2015-2016 Canonical, Ltd. ** Contact: https://www.qt.io/licensing/ ** ** This file is part of the plugins of the Qt Toolkit. @@ -45,35 +45,41 @@ #include +Q_LOGGING_CATEGORY(mirclientCursor, "qt.qpa.mirclient.cursor", QtWarningMsg) + QMirClientCursor::QMirClientCursor(MirConnection *connection) : mConnection(connection) { - mShapeToCursorName[Qt::ArrowCursor] = "left_ptr"; + /* + * TODO: Add the missing cursors to Mir (LP: #1388987) + * Those are the ones without a mir_ prefix, which are X11 cursors + * and won't be understood by any shell other than Unity8. + */ + mShapeToCursorName[Qt::ArrowCursor] = mir_arrow_cursor_name; mShapeToCursorName[Qt::UpArrowCursor] = "up_arrow"; - mShapeToCursorName[Qt::CrossCursor] = "cross"; - mShapeToCursorName[Qt::WaitCursor] = "watch"; - mShapeToCursorName[Qt::IBeamCursor] = "xterm"; - mShapeToCursorName[Qt::SizeVerCursor] = "size_ver"; - mShapeToCursorName[Qt::SizeHorCursor] = "size_hor"; - mShapeToCursorName[Qt::SizeBDiagCursor] = "size_bdiag"; - mShapeToCursorName[Qt::SizeFDiagCursor] = "size_fdiag"; - mShapeToCursorName[Qt::SizeAllCursor] = "size_all"; - mShapeToCursorName[Qt::BlankCursor] = "blank"; - mShapeToCursorName[Qt::SplitVCursor] = "split_v"; - mShapeToCursorName[Qt::SplitHCursor] = "split_h"; - mShapeToCursorName[Qt::PointingHandCursor] = "hand"; + mShapeToCursorName[Qt::CrossCursor] = mir_crosshair_cursor_name; + mShapeToCursorName[Qt::WaitCursor] = mir_busy_cursor_name; + mShapeToCursorName[Qt::IBeamCursor] = mir_caret_cursor_name; + mShapeToCursorName[Qt::SizeVerCursor] = mir_vertical_resize_cursor_name; + mShapeToCursorName[Qt::SizeHorCursor] = mir_horizontal_resize_cursor_name; + mShapeToCursorName[Qt::SizeBDiagCursor] = mir_diagonal_resize_bottom_to_top_cursor_name; + mShapeToCursorName[Qt::SizeFDiagCursor] = mir_diagonal_resize_top_to_bottom_cursor_name; + mShapeToCursorName[Qt::SizeAllCursor] = mir_omnidirectional_resize_cursor_name; + mShapeToCursorName[Qt::BlankCursor] = mir_disabled_cursor_name; + mShapeToCursorName[Qt::SplitVCursor] = mir_vsplit_resize_cursor_name; + mShapeToCursorName[Qt::SplitHCursor] = mir_hsplit_resize_cursor_name; + mShapeToCursorName[Qt::PointingHandCursor] = mir_pointing_hand_cursor_name; mShapeToCursorName[Qt::ForbiddenCursor] = "forbidden"; mShapeToCursorName[Qt::WhatsThisCursor] = "whats_this"; mShapeToCursorName[Qt::BusyCursor] = "left_ptr_watch"; - mShapeToCursorName[Qt::OpenHandCursor] = "openhand"; - mShapeToCursorName[Qt::ClosedHandCursor] = "closedhand"; + mShapeToCursorName[Qt::OpenHandCursor] = mir_open_hand_cursor_name; + mShapeToCursorName[Qt::ClosedHandCursor] = mir_closed_hand_cursor_name; mShapeToCursorName[Qt::DragCopyCursor] = "dnd-copy"; mShapeToCursorName[Qt::DragMoveCursor] = "dnd-move"; mShapeToCursorName[Qt::DragLinkCursor] = "dnd-link"; } namespace { -#if !defined(QT_NO_DEBUG) const char *qtCursorShapeToStr(Qt::CursorShape shape) { switch (shape) { @@ -127,7 +133,6 @@ const char *qtCursorShapeToStr(Qt::CursorShape shape) return "???"; } } -#endif // !defined(QT_NO_DEBUG) } // anonymous namespace void QMirClientCursor::changeCursor(QCursor *windowCursor, QWindow *window) @@ -144,7 +149,7 @@ void QMirClientCursor::changeCursor(QCursor *windowCursor, QWindow *window) if (windowCursor) { - DLOG("[ubuntumirclient QPA] changeCursor shape=%s, window=%p\n", qtCursorShapeToStr(windowCursor->shape()), window); + qCDebug(mirclientCursor, "changeCursor shape=%s, window=%p", qtCursorShapeToStr(windowCursor->shape()), window); if (!windowCursor->pixmap().isNull()) { configureMirCursorWithPixmapQCursor(surface, *windowCursor); } else if (windowCursor->shape() == Qt::BitmapCursor) { diff --git a/src/plugins/platforms/mirclient/qmirclientcursor.h b/src/plugins/platforms/mirclient/qmirclientcursor.h index 4ecc3d97ee..c5de23b272 100644 --- a/src/plugins/platforms/mirclient/qmirclientcursor.h +++ b/src/plugins/platforms/mirclient/qmirclientcursor.h @@ -1,6 +1,6 @@ /**************************************************************************** ** -** Copyright (C) 2015 Canonical, Ltd. +** Copyright (C) 2015-2016 Canonical, Ltd. ** Contact: https://www.qt.io/licensing/ ** ** This file is part of the plugins of the Qt Toolkit. diff --git a/src/plugins/platforms/mirclient/qmirclientdebugextension.cpp b/src/plugins/platforms/mirclient/qmirclientdebugextension.cpp new file mode 100644 index 0000000000..9aa934083d --- /dev/null +++ b/src/plugins/platforms/mirclient/qmirclientdebugextension.cpp @@ -0,0 +1,79 @@ +/**************************************************************************** +** +** Copyright (C) 2016 Canonical, Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of the plugins of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** 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 Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 3 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL3 included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 3 requirements +** will be met: https://www.gnu.org/licenses/lgpl-3.0.html. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 2.0 or (at your option) the GNU General +** Public license version 3 or 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.GPL2 and 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-2.0.html and +** https://www.gnu.org/licenses/gpl-3.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + + +#include "qmirclientdebugextension.h" + +#include "qmirclientlogging.h" + +// mir client debug +#include + +Q_LOGGING_CATEGORY(mirclientDebug, "qt.qpa.mirclient.debug") + +QMirClientDebugExtension::QMirClientDebugExtension() + : m_mirclientDebug(QStringLiteral("mirclient-debug-extension"), 1) + , m_mapper(nullptr) +{ + qCDebug(mirclientDebug) << "NOTICE: Loading mirclient-debug-extension"; + m_mapper = (MapperPrototype) m_mirclientDebug.resolve("mir_debug_surface_coords_to_screen"); + + if (!m_mirclientDebug.isLoaded()) { + qCWarning(mirclientDebug) << "ERROR: mirclient-debug-extension failed to load:" + << m_mirclientDebug.errorString(); + } else if (!m_mapper) { + qCWarning(mirclientDebug) << "ERROR: unable to find required symbols in mirclient-debug-extension:" + << m_mirclientDebug.errorString(); + } +} + +QPoint QMirClientDebugExtension::mapSurfacePointToScreen(MirSurface *surface, const QPoint &point) +{ + if (!m_mapper) { + return point; + } + + QPoint mappedPoint; + bool status = m_mapper(surface, point.x(), point.y(), &mappedPoint.rx(), &mappedPoint.ry()); + if (status) { + return mappedPoint; + } else { + return point; + } +} diff --git a/src/plugins/platforms/mirclient/qmirclientdebugextension.h b/src/plugins/platforms/mirclient/qmirclientdebugextension.h new file mode 100644 index 0000000000..0596561d77 --- /dev/null +++ b/src/plugins/platforms/mirclient/qmirclientdebugextension.h @@ -0,0 +1,63 @@ +/**************************************************************************** +** +** Copyright (C) 2016 Canonical, Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of the plugins of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** 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 Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 3 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL3 included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 3 requirements +** will be met: https://www.gnu.org/licenses/lgpl-3.0.html. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 2.0 or (at your option) the GNU General +** Public license version 3 or 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.GPL2 and 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-2.0.html and +** https://www.gnu.org/licenses/gpl-3.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + + +#ifndef QMIRCLIENTDEBUGEXTENSION_H +#define QMIRCLIENTDEBUGEXTENSION_H + +#include +#include +struct MirSurface; + +typedef bool (*MapperPrototype)(MirSurface* surface, int x, int y, int* screenX, int* screenY); + + +class QMirClientDebugExtension +{ +public: + QMirClientDebugExtension(); + + QPoint mapSurfacePointToScreen(MirSurface *, const QPoint &point); + +private: + QLibrary m_mirclientDebug; + MapperPrototype m_mapper; +}; + +#endif // QMIRCLIENTDEBUGEXTENSION_H diff --git a/src/plugins/platforms/mirclient/qmirclientdesktopwindow.cpp b/src/plugins/platforms/mirclient/qmirclientdesktopwindow.cpp new file mode 100644 index 0000000000..123f805c25 --- /dev/null +++ b/src/plugins/platforms/mirclient/qmirclientdesktopwindow.cpp @@ -0,0 +1,50 @@ +/**************************************************************************** +** +** Copyright (C) 2016 Canonical, Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of the plugins of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** 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 Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 3 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL3 included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 3 requirements +** will be met: https://www.gnu.org/licenses/lgpl-3.0.html. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 2.0 or (at your option) the GNU General +** Public license version 3 or 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.GPL2 and 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-2.0.html and +** https://www.gnu.org/licenses/gpl-3.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + + +#include "qmirclientdesktopwindow.h" + +// local +#include "qmirclientlogging.h" + +QMirClientDesktopWindow::QMirClientDesktopWindow(QWindow *window) + : QPlatformWindow(window) +{ + qCDebug(mirclient, "QMirClientDesktopWindow(window=%p)", window); +} diff --git a/src/plugins/platforms/mirclient/qmirclientdesktopwindow.h b/src/plugins/platforms/mirclient/qmirclientdesktopwindow.h new file mode 100644 index 0000000000..3ba54db826 --- /dev/null +++ b/src/plugins/platforms/mirclient/qmirclientdesktopwindow.h @@ -0,0 +1,53 @@ +/**************************************************************************** +** +** Copyright (C) 2016 Canonical, Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of the plugins of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** 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 Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 3 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL3 included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 3 requirements +** will be met: https://www.gnu.org/licenses/lgpl-3.0.html. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 2.0 or (at your option) the GNU General +** Public license version 3 or 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.GPL2 and 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-2.0.html and +** https://www.gnu.org/licenses/gpl-3.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + + +#ifndef QMIRCLIENTDESKTOPWINDOW_H +#define QMIRCLIENTDESKTOPWINDOW_H + +#include + +// TODO Implement it. For now it's just an empty, dummy class. +class QMirClientDesktopWindow : public QPlatformWindow +{ +public: + QMirClientDesktopWindow(QWindow*); +}; + +#endif // QMIRCLIENTDESKTOPWINDOW_H diff --git a/src/plugins/platforms/mirclient/qmirclientglcontext.cpp b/src/plugins/platforms/mirclient/qmirclientglcontext.cpp index 38eb0a4609..fc7d90d5ec 100644 --- a/src/plugins/platforms/mirclient/qmirclientglcontext.cpp +++ b/src/plugins/platforms/mirclient/qmirclientglcontext.cpp @@ -39,123 +39,94 @@ #include "qmirclientglcontext.h" -#include "qmirclientwindow.h" #include "qmirclientlogging.h" +#include "qmirclientwindow.h" + +#include #include +#include #include -#include - -#if !defined(QT_NO_DEBUG) -static void printOpenGLESConfig() { - static bool once = true; - if (once) { - const char* string = (const char*) glGetString(GL_VENDOR); - LOG("OpenGL ES vendor: %s", string); - string = (const char*) glGetString(GL_RENDERER); - LOG("OpenGL ES renderer: %s", string); - string = (const char*) glGetString(GL_VERSION); - LOG("OpenGL ES version: %s", string); - string = (const char*) glGetString(GL_SHADING_LANGUAGE_VERSION); - LOG("OpenGL ES Shading Language version: %s", string); - string = (const char*) glGetString(GL_EXTENSIONS); - LOG("OpenGL ES extensions: %s", string); - once = false; - } -} -#endif -static EGLenum api_in_use() +Q_LOGGING_CATEGORY(mirclientGraphics, "qt.qpa.mirclient.graphics", QtWarningMsg) + +namespace { + +void printEglConfig(EGLDisplay display, EGLConfig config) { -#ifdef QTUBUNTU_USE_OPENGL - return EGL_OPENGL_API; -#else - return EGL_OPENGL_ES_API; -#endif + Q_ASSERT(display != EGL_NO_DISPLAY); + Q_ASSERT(config != nullptr); + + const char *string = eglQueryString(display, EGL_VENDOR); + qCDebug(mirclientGraphics, "EGL vendor: %s", string); + + string = eglQueryString(display, EGL_VERSION); + qCDebug(mirclientGraphics, "EGL version: %s", string); + + string = eglQueryString(display, EGL_EXTENSIONS); + qCDebug(mirclientGraphics, "EGL extensions: %s", string); + + qCDebug(mirclientGraphics, "EGL configuration attributes:"); + q_printEglConfig(display, config); } -QMirClientOpenGLContext::QMirClientOpenGLContext(QMirClientScreen* screen, QMirClientOpenGLContext* share) +} // anonymous namespace + +QMirClientOpenGLContext::QMirClientOpenGLContext(const QSurfaceFormat &format, QPlatformOpenGLContext *share, + EGLDisplay display) + : QEGLPlatformContext(format, share, display, 0) { - ASSERT(screen != NULL); - mEglDisplay = screen->eglDisplay(); - mScreen = screen; - - // Create an OpenGL ES 2 context. - QVector attribs; - attribs.append(EGL_CONTEXT_CLIENT_VERSION); - attribs.append(2); - attribs.append(EGL_NONE); - ASSERT(eglBindAPI(api_in_use()) == EGL_TRUE); - - mEglContext = eglCreateContext(mEglDisplay, screen->eglConfig(), share ? share->eglContext() : EGL_NO_CONTEXT, - attribs.constData()); - DASSERT(mEglContext != EGL_NO_CONTEXT); + if (mirclientGraphics().isDebugEnabled()) { + printEglConfig(display, eglConfig()); + } } -QMirClientOpenGLContext::~QMirClientOpenGLContext() +static bool needsFBOReadBackWorkaround() { - ASSERT(eglDestroyContext(mEglDisplay, mEglContext) == EGL_TRUE); + static bool set = false; + static bool needsWorkaround = false; + + if (Q_UNLIKELY(!set)) { + const char *rendererString = reinterpret_cast(glGetString(GL_RENDERER)); + needsWorkaround = qstrncmp(rendererString, "Mali-400", 8) == 0 + || qstrncmp(rendererString, "Mali-T7", 7) == 0 + || qstrncmp(rendererString, "PowerVR Rogue G6200", 19) == 0; + set = true; + } + + return needsWorkaround; } bool QMirClientOpenGLContext::makeCurrent(QPlatformSurface* surface) { - DASSERT(surface->surface()->surfaceType() == QSurface::OpenGLSurface); - EGLSurface eglSurface = static_cast(surface)->eglSurface(); -#if defined(QT_NO_DEBUG) - eglBindAPI(api_in_use()); - eglMakeCurrent(mEglDisplay, eglSurface, eglSurface, mEglContext); -#else - ASSERT(eglBindAPI(api_in_use()) == EGL_TRUE); - ASSERT(eglMakeCurrent(mEglDisplay, eglSurface, eglSurface, mEglContext) == EGL_TRUE); - printOpenGLESConfig(); -#endif - - // When running on the emulator, shaders will be compiled using a thin wrapper around the desktop drivers. - // These wrappers might not support the precision qualifiers, so set the workaround flag to true. - const char *rendererString = reinterpret_cast(glGetString(GL_RENDERER)); - if (rendererString != 0 && qstrncmp(rendererString, "Android Emulator", 16) == 0) { + const bool ret = QEGLPlatformContext::makeCurrent(surface); + + if (Q_LIKELY(ret)) { QOpenGLContextPrivate *ctx_d = QOpenGLContextPrivate::get(context()); - ctx_d->workaround_missingPrecisionQualifiers = true; + if (!ctx_d->workaround_brokenFBOReadBack && needsFBOReadBackWorkaround()) { + ctx_d->workaround_brokenFBOReadBack = true; + } } - - return true; + return ret; } -void QMirClientOpenGLContext::doneCurrent() +// Following method used internally in the base class QEGLPlatformContext to access +// the egl surface of a QPlatformSurface/QMirClientWindow +EGLSurface QMirClientOpenGLContext::eglSurfaceForPlatformSurface(QPlatformSurface *surface) { -#if defined(QT_NO_DEBUG) - eglBindAPI(api_in_use()); - eglMakeCurrent(mEglDisplay, EGL_NO_SURFACE, EGL_NO_SURFACE, EGL_NO_CONTEXT); -#else - ASSERT(eglBindAPI(api_in_use()) == EGL_TRUE); - ASSERT(eglMakeCurrent(mEglDisplay, EGL_NO_SURFACE, EGL_NO_SURFACE, EGL_NO_CONTEXT) == EGL_TRUE); -#endif + if (surface->surface()->surfaceClass() == QSurface::Window) { + return static_cast(surface)->eglSurface(); + } else { + return static_cast(surface)->pbuffer(); + } } void QMirClientOpenGLContext::swapBuffers(QPlatformSurface* surface) { - QMirClientWindow *ubuntuWindow = static_cast(surface); - - EGLSurface eglSurface = ubuntuWindow->eglSurface(); -#if defined(QT_NO_DEBUG) - eglBindAPI(api_in_use()); - eglSwapBuffers(mEglDisplay, eglSurface); -#else - ASSERT(eglBindAPI(api_in_use()) == EGL_TRUE); - ASSERT(eglSwapBuffers(mEglDisplay, eglSurface) == EGL_TRUE); -#endif - - ubuntuWindow->onSwapBuffersDone(); -} + QEGLPlatformContext::swapBuffers(surface); -QFunctionPointer QMirClientOpenGLContext::getProcAddress(const char *procName) -{ -#if defined(QT_NO_DEBUG) - eglBindAPI(api_in_use()); -#else - ASSERT(eglBindAPI(api_in_use()) == EGL_TRUE); -#endif - QFunctionPointer proc = (QFunctionPointer) eglGetProcAddress(procName); - if (!proc) - proc = (QFunctionPointer) dlsym(RTLD_DEFAULT, procName); - return proc; + if (surface->surface()->surfaceClass() == QSurface::Window) { + // notify window on swap completion + auto platformWindow = static_cast(surface); + platformWindow->onSwapBuffersDone(); + } } diff --git a/src/plugins/platforms/mirclient/qmirclientglcontext.h b/src/plugins/platforms/mirclient/qmirclientglcontext.h index eac0b78c4e..92331a6fb1 100644 --- a/src/plugins/platforms/mirclient/qmirclientglcontext.h +++ b/src/plugins/platforms/mirclient/qmirclientglcontext.h @@ -42,28 +42,22 @@ #define QMIRCLIENTGLCONTEXT_H #include -#include "qmirclientscreen.h" +#include -class QMirClientOpenGLContext : public QPlatformOpenGLContext +#include + +class QMirClientOpenGLContext : public QEGLPlatformContext { public: - QMirClientOpenGLContext(QMirClientScreen* screen, QMirClientOpenGLContext* share); - virtual ~QMirClientOpenGLContext(); - - // QPlatformOpenGLContext methods. - QSurfaceFormat format() const override { return mScreen->surfaceFormat(); } - void swapBuffers(QPlatformSurface* surface) override; - bool makeCurrent(QPlatformSurface* surface) override; - void doneCurrent() override; - bool isValid() const override { return mEglContext != EGL_NO_CONTEXT; } - QFunctionPointer getProcAddress(const char *procName) override; + QMirClientOpenGLContext(const QSurfaceFormat &format, QPlatformOpenGLContext *share, + EGLDisplay display); - EGLContext eglContext() const { return mEglContext; } + // QEGLPlatformContext methods. + void swapBuffers(QPlatformSurface *surface) final; + bool makeCurrent(QPlatformSurface *surface) final; -private: - QMirClientScreen* mScreen; - EGLContext mEglContext; - EGLDisplay mEglDisplay; +protected: + EGLSurface eglSurfaceForPlatformSurface(QPlatformSurface *surface) final; }; #endif // QMIRCLIENTGLCONTEXT_H diff --git a/src/plugins/platforms/mirclient/qmirclientinput.cpp b/src/plugins/platforms/mirclient/qmirclientinput.cpp index b3b21ae0e3..ea13f3cc17 100644 --- a/src/plugins/platforms/mirclient/qmirclientinput.cpp +++ b/src/plugins/platforms/mirclient/qmirclientinput.cpp @@ -1,6 +1,6 @@ /**************************************************************************** ** -** Copyright (C) 2014-2015 Canonical, Ltd. +** Copyright (C) 2014-2016 Canonical, Ltd. ** Contact: https://www.qt.io/licensing/ ** ** This file is part of the plugins of the Qt Toolkit. @@ -48,21 +48,23 @@ #include "qmirclientorientationchangeevent_p.h" // Qt -#if !defined(QT_NO_DEBUG) #include -#endif #include #include -#include +#include #include #include +#include #include #include #include -#define LOG_EVENTS 0 +Q_LOGGING_CATEGORY(mirclientInput, "qt.qpa.mirclient.input", QtWarningMsg) + +namespace +{ // XKB Keysyms which do not map directly to Qt types (i.e. Unicode points) static const uint32_t KeyTable[] = { @@ -134,6 +136,27 @@ static const uint32_t KeyTable[] = { XKB_KEY_MultipleCandidate, Qt::Key_MultipleCandidate, XKB_KEY_PreviousCandidate, Qt::Key_PreviousCandidate, + // dead keys + XKB_KEY_dead_grave, Qt::Key_Dead_Grave, + XKB_KEY_dead_acute, Qt::Key_Dead_Acute, + XKB_KEY_dead_circumflex, Qt::Key_Dead_Circumflex, + XKB_KEY_dead_tilde, Qt::Key_Dead_Tilde, + XKB_KEY_dead_macron, Qt::Key_Dead_Macron, + XKB_KEY_dead_breve, Qt::Key_Dead_Breve, + XKB_KEY_dead_abovedot, Qt::Key_Dead_Abovedot, + XKB_KEY_dead_diaeresis, Qt::Key_Dead_Diaeresis, + XKB_KEY_dead_abovering, Qt::Key_Dead_Abovering, + XKB_KEY_dead_doubleacute, Qt::Key_Dead_Doubleacute, + XKB_KEY_dead_caron, Qt::Key_Dead_Caron, + XKB_KEY_dead_cedilla, Qt::Key_Dead_Cedilla, + XKB_KEY_dead_ogonek, Qt::Key_Dead_Ogonek, + XKB_KEY_dead_iota, Qt::Key_Dead_Iota, + XKB_KEY_dead_voiced_sound, Qt::Key_Dead_Voiced_Sound, + XKB_KEY_dead_semivoiced_sound, Qt::Key_Dead_Semivoiced_Sound, + XKB_KEY_dead_belowdot, Qt::Key_Dead_Belowdot, + XKB_KEY_dead_hook, Qt::Key_Dead_Hook, + XKB_KEY_dead_horn, Qt::Key_Dead_Horn, + XKB_KEY_Mode_switch, Qt::Key_Mode_switch, XKB_KEY_script_switch, Qt::Key_Mode_switch, XKB_KEY_XF86AudioRaiseVolume, Qt::Key_VolumeUp, @@ -144,14 +167,37 @@ static const uint32_t KeyTable[] = { 0, 0 }; -class QMirClientEvent : public QEvent +Qt::WindowState mirSurfaceStateToWindowState(MirSurfaceState state) +{ + switch (state) { + case mir_surface_state_fullscreen: + return Qt::WindowFullScreen; + case mir_surface_state_maximized: + case mir_surface_state_vertmaximized: + case mir_surface_state_horizmaximized: + return Qt::WindowMaximized; + case mir_surface_state_minimized: + return Qt::WindowMinimized; + case mir_surface_state_hidden: + // We should be handling this state separately. + Q_ASSERT(false); + case mir_surface_state_restored: + case mir_surface_state_unknown: + default: + return Qt::WindowNoState; + } +} + +} // namespace + +class UbuntuEvent : public QEvent { public: - QMirClientEvent(QMirClientWindow* window, const MirEvent *event, QEvent::Type type) + UbuntuEvent(QMirClientWindow* window, const MirEvent *event, QEvent::Type type) : QEvent(type), window(window) { nativeEvent = mir_event_ref(event); } - ~QMirClientEvent() + ~UbuntuEvent() { mir_event_unref(nativeEvent); } @@ -166,7 +212,7 @@ QMirClientInput::QMirClientInput(QMirClientClientIntegration* integration) , mEventFilterType(static_cast( integration->nativeInterface())->genericEventFilterType()) , mEventType(static_cast(QEvent::registerEventType())) - , mLastFocusedWindow(nullptr) + , mLastInputWindow(nullptr) { // Initialize touch device. mTouchDevice = new QTouchDevice; @@ -182,42 +228,47 @@ QMirClientInput::~QMirClientInput() // Qt will take care of deleting mTouchDevice. } -#if (LOG_EVENTS != 0) static const char* nativeEventTypeToStr(MirEventType t) { switch (t) { case mir_event_type_key: - return "mir_event_type_key"; + return "key"; case mir_event_type_motion: - return "mir_event_type_motion"; + return "motion"; case mir_event_type_surface: - return "mir_event_type_surface"; + return "surface"; case mir_event_type_resize: - return "mir_event_type_resize"; + return "resize"; case mir_event_type_prompt_session_state_change: - return "mir_event_type_prompt_session_state_change"; + return "prompt_session_state_change"; case mir_event_type_orientation: - return "mir_event_type_orientation"; + return "orientation"; case mir_event_type_close_surface: - return "mir_event_type_close_surface"; + return "close_surface"; case mir_event_type_input: - return "mir_event_type_input"; + return "input"; + case mir_event_type_keymap: + return "keymap"; + case mir_event_type_input_configuration: + return "input_configuration"; + case mir_event_type_surface_output: + return "surface_output"; + case mir_event_type_input_device_state: + return "input_device_state"; default: - DLOG("Invalid event type %d", t); - return "invalid"; + return "unknown"; } } -#endif // LOG_EVENTS != 0 void QMirClientInput::customEvent(QEvent* event) { - DASSERT(QThread::currentThread() == thread()); - QMirClientEvent* ubuntuEvent = static_cast(event); + Q_ASSERT(QThread::currentThread() == thread()); + UbuntuEvent* ubuntuEvent = static_cast(event); const MirEvent *nativeEvent = ubuntuEvent->nativeEvent; if ((ubuntuEvent->window == nullptr) || (ubuntuEvent->window->window() == nullptr)) { - qWarning("Attempted to deliver an event to a non-existent window, ignoring."); + qCWarning(mirclient) << "Attempted to deliver an event to a non-existent window, ignoring."; return; } @@ -226,13 +277,11 @@ void QMirClientInput::customEvent(QEvent* event) if (QWindowSystemInterface::handleNativeEvent( ubuntuEvent->window->window(), mEventFilterType, const_cast(static_cast(nativeEvent)), &result) == true) { - DLOG("event filtered out by native interface"); + qCDebug(mirclient, "event filtered out by native interface"); return; } - #if (LOG_EVENTS != 0) - LOG("QMirClientInput::customEvent(type=%s)", nativeEventTypeToStr(mir_event_get_type(nativeEvent))); - #endif + qCDebug(mirclientInput, "customEvent(type=%s)", nativeEventTypeToStr(mir_event_get_type(nativeEvent))); // Event dispatching. switch (mir_event_get_type(nativeEvent)) @@ -242,43 +291,30 @@ void QMirClientInput::customEvent(QEvent* event) break; case mir_event_type_resize: { - Q_ASSERT(ubuntuEvent->window->screen() == mIntegration->screen()); - auto resizeEvent = mir_event_get_resize_event(nativeEvent); - mIntegration->screen()->handleWindowSurfaceResize( - mir_resize_event_get_width(resizeEvent), - mir_resize_event_get_height(resizeEvent)); + // Enable workaround for Screen rotation + auto const targetWindow = ubuntuEvent->window; + if (targetWindow) { + auto const screen = static_cast(targetWindow->screen()); + if (screen) { + screen->handleWindowSurfaceResize( + mir_resize_event_get_width(resizeEvent), + mir_resize_event_get_height(resizeEvent)); + } - ubuntuEvent->window->handleSurfaceResized(mir_resize_event_get_width(resizeEvent), - mir_resize_event_get_height(resizeEvent)); + targetWindow->handleSurfaceResized( + mir_resize_event_get_width(resizeEvent), + mir_resize_event_get_height(resizeEvent)); + } break; } case mir_event_type_surface: - { - auto surfaceEvent = mir_event_get_surface_event(nativeEvent); - if (mir_surface_event_get_attribute(surfaceEvent) == mir_surface_attrib_focus) { - const bool focused = mir_surface_event_get_attribute_value(surfaceEvent) == mir_surface_focused; - // Mir may have sent a pair of focus lost/gained events, so we need to "peek" into the queue - // so that we don't deactivate windows prematurely. - if (focused) { - mPendingFocusGainedEvents--; - ubuntuEvent->window->handleSurfaceFocused(); - QWindowSystemInterface::handleWindowActivated(ubuntuEvent->window->window(), Qt::ActiveWindowFocusReason); - - // NB: Since processing of system events is queued, never check qGuiApp->applicationState() - // as it might be outdated. Always call handleApplicationStateChanged() with the latest - // state regardless. - QWindowSystemInterface::handleApplicationStateChanged(Qt::ApplicationActive); - - } else if (!mPendingFocusGainedEvents) { - DLOG("[ubuntumirclient QPA] No windows have focus"); - QWindowSystemInterface::handleWindowActivated(nullptr, Qt::ActiveWindowFocusReason); - QWindowSystemInterface::handleApplicationStateChanged(Qt::ApplicationInactive); - } - } + handleSurfaceEvent(ubuntuEvent->window, mir_event_get_surface_event(nativeEvent)); + break; + case mir_event_type_surface_output: + handleSurfaceOutputEvent(ubuntuEvent->window, mir_event_get_surface_output_event(nativeEvent)); break; - } case mir_event_type_orientation: dispatchOrientationEvent(ubuntuEvent->window->window(), mir_event_get_orientation_event(nativeEvent)); break; @@ -286,7 +322,7 @@ void QMirClientInput::customEvent(QEvent* event) QWindowSystemInterface::handleCloseEvent(ubuntuEvent->window->window()); break; default: - DLOG("unhandled event type: %d", static_cast(mir_event_get_type(nativeEvent))); + qCDebug(mirclient, "unhandled event type: %d", static_cast(mir_event_get_type(nativeEvent))); } } @@ -294,22 +330,11 @@ void QMirClientInput::postEvent(QMirClientWindow *platformWindow, const MirEvent { QWindow *window = platformWindow->window(); - const auto eventType = mir_event_get_type(event); - if (mir_event_type_surface == eventType) { - auto surfaceEvent = mir_event_get_surface_event(event); - if (mir_surface_attrib_focus == mir_surface_event_get_attribute(surfaceEvent)) { - const bool focused = mir_surface_event_get_attribute_value(surfaceEvent) == mir_surface_focused; - if (focused) { - mPendingFocusGainedEvents++; - } - } - } - - QCoreApplication::postEvent(this, new QMirClientEvent( + QCoreApplication::postEvent(this, new UbuntuEvent( platformWindow, event, mEventType)); if ((window->flags().testFlag(Qt::WindowTransparentForInput)) && window->parent()) { - QCoreApplication::postEvent(this, new QMirClientEvent( + QCoreApplication::postEvent(this, new UbuntuEvent( static_cast(platformWindow->QPlatformWindow::parent()), event, mEventType)); } @@ -365,15 +390,17 @@ void QMirClientInput::dispatchTouchEvent(QMirClientWindow *window, const MirInpu switch (touch_action) { case mir_touch_action_down: - mLastFocusedWindow = window; + mLastInputWindow = window; touchPoint.state = Qt::TouchPointPressed; break; case mir_touch_action_up: touchPoint.state = Qt::TouchPointReleased; break; case mir_touch_action_change: - default: touchPoint.state = Qt::TouchPointMoved; + break; + default: + Q_UNREACHABLE(); } touchPoints.append(touchPoint); @@ -384,22 +411,26 @@ void QMirClientInput::dispatchTouchEvent(QMirClientWindow *window, const MirInpu mTouchDevice, touchPoints); } -static uint32_t translateKeysym(uint32_t sym, char *string, size_t size) -{ - Q_UNUSED(size); - string[0] = '\0'; +static uint32_t translateKeysym(uint32_t sym, const QString &text) { + int code = 0; - if (sym >= XKB_KEY_F1 && sym <= XKB_KEY_F35) + QTextCodec *systemCodec = QTextCodec::codecForLocale(); + if (sym < 128 || (sym < 256 && systemCodec->mibEnum() == 4)) { + // upper-case key, if known + code = isprint((int)sym) ? toupper((int)sym) : 0; + } else if (sym >= XKB_KEY_F1 && sym <= XKB_KEY_F35) { return Qt::Key_F1 + (int(sym) - XKB_KEY_F1); - - for (int i = 0; KeyTable[i]; i += 2) { - if (sym == KeyTable[i]) - return KeyTable[i + 1]; + } else if (text.length() == 1 && text.unicode()->unicode() > 0x1f + && text.unicode()->unicode() != 0x7f + && !(sym >= XKB_KEY_dead_grave && sym <= XKB_KEY_dead_currency)) { + code = text.unicode()->toUpper().unicode(); + } else { + for (int i = 0; KeyTable[i]; i += 2) + if (sym == KeyTable[i]) + code = KeyTable[i + 1]; } - string[0] = sym; - string[1] = '\0'; - return toupper(sym); + return code; } namespace @@ -413,12 +444,15 @@ Qt::KeyboardModifiers qt_modifiers_from_mir(MirInputEventModifiers modifiers) if (modifiers & mir_input_event_modifier_ctrl) { q_modifiers |= Qt::ControlModifier; } - if (modifiers & mir_input_event_modifier_alt) { + if (modifiers & mir_input_event_modifier_alt_left) { q_modifiers |= Qt::AltModifier; } if (modifiers & mir_input_event_modifier_meta) { q_modifiers |= Qt::MetaModifier; } + if (modifiers & mir_input_event_modifier_alt_right) { + q_modifiers |= Qt::GroupSwitchModifier; + } return q_modifiers; } } @@ -429,34 +463,43 @@ void QMirClientInput::dispatchKeyEvent(QMirClientWindow *window, const MirInputE ulong timestamp = mir_input_event_get_event_time(event) / 1000000; xkb_keysym_t xk_sym = mir_keyboard_event_key_code(key_event); + quint32 scan_code = mir_keyboard_event_scan_code(key_event); + quint32 native_modifiers = mir_keyboard_event_modifiers(key_event); // Key modifier and unicode index mapping. - auto modifiers = qt_modifiers_from_mir(mir_keyboard_event_modifiers(key_event)); + auto modifiers = qt_modifiers_from_mir(native_modifiers); MirKeyboardAction action = mir_keyboard_event_action(key_event); QEvent::Type keyType = action == mir_keyboard_action_up ? QEvent::KeyRelease : QEvent::KeyPress; if (action == mir_keyboard_action_down) - mLastFocusedWindow = window; + mLastInputWindow = window; - char s[2]; - int sym = translateKeysym(xk_sym, s, sizeof(s)); - QString text = QString::fromLatin1(s); + QString text; + QVarLengthArray chars(32); + { + int result = xkb_keysym_to_utf8(xk_sym, chars.data(), chars.size()); + + if (result > 0) { + text = QString::fromUtf8(chars.constData()); + } + } + int sym = translateKeysym(xk_sym, text); bool is_auto_rep = action == mir_keyboard_action_repeat; QPlatformInputContext *context = QGuiApplicationPrivate::platformIntegration()->inputContext(); if (context) { - QKeyEvent qKeyEvent(keyType, sym, modifiers, text, is_auto_rep); + QKeyEvent qKeyEvent(keyType, sym, modifiers, scan_code, xk_sym, native_modifiers, text, is_auto_rep); qKeyEvent.setTimestamp(timestamp); if (context->filterEvent(&qKeyEvent)) { - DLOG("key event filtered out by input context"); + qCDebug(mirclient, "key event filtered out by input context"); return; } } - QWindowSystemInterface::handleKeyEvent(window->window(), timestamp, keyType, sym, modifiers, text, is_auto_rep); + QWindowSystemInterface::handleExtendedKeyEvent(window->window(), timestamp, keyType, sym, modifiers, scan_code, xk_sym, native_modifiers, text, is_auto_rep); } namespace @@ -481,14 +524,17 @@ Qt::MouseButtons extract_buttons(const MirPointerEvent *pev) void QMirClientInput::dispatchPointerEvent(QMirClientWindow *platformWindow, const MirInputEvent *ev) { - auto window = platformWindow->window(); - auto timestamp = mir_input_event_get_event_time(ev) / 1000000; + const auto window = platformWindow->window(); + const auto timestamp = mir_input_event_get_event_time(ev) / 1000000; + + const auto pev = mir_input_event_get_pointer_event(ev); + const auto action = mir_pointer_event_action(pev); + + const auto modifiers = qt_modifiers_from_mir(mir_pointer_event_modifiers(pev)); + const auto localPoint = QPointF(mir_pointer_event_axis_value(pev, mir_pointer_axis_x), + mir_pointer_event_axis_value(pev, mir_pointer_axis_y)); - auto pev = mir_input_event_get_pointer_event(ev); - auto action = mir_pointer_event_action(pev); - auto localPoint = QPointF(mir_pointer_event_axis_value(pev, mir_pointer_axis_x), - mir_pointer_event_axis_value(pev, mir_pointer_axis_y)); - auto modifiers = qt_modifiers_from_mir(mir_pointer_event_modifiers(pev)); + mLastInputWindow = platformWindow; switch (action) { case mir_pointer_action_button_up: @@ -499,7 +545,8 @@ void QMirClientInput::dispatchPointerEvent(QMirClientWindow *platformWindow, con const float vDelta = mir_pointer_event_axis_value(pev, mir_pointer_axis_vscroll); if (hDelta != 0 || vDelta != 0) { - const QPoint angleDelta = QPoint(hDelta * 15, vDelta * 15); + // QWheelEvent::DefaultDeltasPerStep = 120 but doesn't exist on vivid + const QPoint angleDelta(120 * hDelta, 120 * vDelta); QWindowSystemInterface::handleWheelEvent(window, timestamp, localPoint, window->position() + localPoint, QPoint(), angleDelta, modifiers, Qt::ScrollUpdate); } @@ -515,42 +562,32 @@ void QMirClientInput::dispatchPointerEvent(QMirClientWindow *platformWindow, con QWindowSystemInterface::handleLeaveEvent(window); break; default: - DLOG("Unrecognized pointer event"); + Q_UNREACHABLE(); } } -#if (LOG_EVENTS != 0) static const char* nativeOrientationDirectionToStr(MirOrientation orientation) { switch (orientation) { case mir_orientation_normal: return "Normal"; - break; case mir_orientation_left: return "Left"; - break; case mir_orientation_inverted: return "Inverted"; - break; case mir_orientation_right: return "Right"; - break; - default: - return "INVALID!"; } + Q_UNREACHABLE(); } -#endif void QMirClientInput::dispatchOrientationEvent(QWindow *window, const MirOrientationEvent *event) { MirOrientation mir_orientation = mir_orientation_event_get_direction(event); - #if (LOG_EVENTS != 0) - // Orientation event logging. - LOG("ORIENTATION direction: %s", nativeOrientationDirectionToStr(mir_orientation)); - #endif + qCDebug(mirclientInput, "orientation direction: %s", nativeOrientationDirectionToStr(mir_orientation)); if (!window->screen()) { - DLOG("Window has no associated screen, dropping orientation event"); + qCDebug(mirclient, "Window has no associated screen, dropping orientation event"); return; } @@ -569,7 +606,7 @@ void QMirClientInput::dispatchOrientationEvent(QWindow *window, const MirOrienta orientation = OrientationChangeEvent::RightUp; break; default: - DLOG("No such orientation %d", mir_orientation); + qCDebug(mirclient, "No such orientation %d", mir_orientation); return; } @@ -581,3 +618,61 @@ void QMirClientInput::dispatchOrientationEvent(QWindow *window, const MirOrienta new OrientationChangeEvent(OrientationChangeEvent::mType, orientation)); } +void QMirClientInput::handleSurfaceEvent(const QPointer &window, const MirSurfaceEvent *event) +{ + auto surfaceEventAttribute = mir_surface_event_get_attribute(event); + + switch (surfaceEventAttribute) { + case mir_surface_attrib_focus: { + window->handleSurfaceFocusChanged( + mir_surface_event_get_attribute_value(event) == mir_surface_focused); + break; + } + case mir_surface_attrib_visibility: { + window->handleSurfaceExposeChange( + mir_surface_event_get_attribute_value(event) == mir_surface_visibility_exposed); + break; + } + // Remaining attributes are ones client sets for server, and server should not override them + case mir_surface_attrib_state: { + MirSurfaceState state = static_cast(mir_surface_event_get_attribute_value(event)); + + if (state == mir_surface_state_hidden) { + window->handleSurfaceVisibilityChanged(false); + } else { + // it's visible! + window->handleSurfaceVisibilityChanged(true); + window->handleSurfaceStateChanged(mirSurfaceStateToWindowState(state)); + } + break; + } + case mir_surface_attrib_type: + case mir_surface_attrib_swapinterval: + case mir_surface_attrib_dpi: + case mir_surface_attrib_preferred_orientation: + case mir_surface_attribs: + break; + } +} + +void QMirClientInput::handleSurfaceOutputEvent(const QPointer &window, const MirSurfaceOutputEvent *event) +{ + const uint32_t outputId = mir_surface_output_event_get_output_id(event); + const int dpi = mir_surface_output_event_get_dpi(event); + const MirFormFactor formFactor = mir_surface_output_event_get_form_factor(event); + const float scale = mir_surface_output_event_get_scale(event); + + const auto screenObserver = mIntegration->screenObserver(); + QMirClientScreen *screen = screenObserver->findScreenWithId(outputId); + if (!screen) { + qCWarning(mirclient) << "Mir notified window" << window->window() << "on an unknown screen with id" << outputId; + return; + } + + screenObserver->handleScreenPropertiesChange(screen, dpi, formFactor, scale); + window->handleScreenPropertiesChange(formFactor, scale); + + if (window->screen() != screen) { + QWindowSystemInterface::handleWindowScreenChanged(window->window(), screen->screen()); + } +} diff --git a/src/plugins/platforms/mirclient/qmirclientinput.h b/src/plugins/platforms/mirclient/qmirclientinput.h index 3600ae7ece..263cb5e54e 100644 --- a/src/plugins/platforms/mirclient/qmirclientinput.h +++ b/src/plugins/platforms/mirclient/qmirclientinput.h @@ -1,6 +1,6 @@ /**************************************************************************** ** -** Copyright (C) 2014-2015 Canonical, Ltd. +** Copyright (C) 2014-2016 Canonical, Ltd. ** Contact: https://www.qt.io/licensing/ ** ** This file is part of the plugins of the Qt Toolkit. @@ -43,7 +43,6 @@ // Qt #include -#include #include @@ -63,7 +62,7 @@ public: void postEvent(QMirClientWindow* window, const MirEvent *event); QMirClientClientIntegration* integration() const { return mIntegration; } - QMirClientWindow *lastFocusedWindow() const {return mLastFocusedWindow; } + QMirClientWindow *lastInputWindow() const {return mLastInputWindow; } protected: void dispatchKeyEvent(QMirClientWindow *window, const MirInputEvent *event); @@ -72,6 +71,8 @@ protected: void dispatchInputEvent(QMirClientWindow *window, const MirInputEvent *event); void dispatchOrientationEvent(QWindow* window, const MirOrientationEvent *event); + void handleSurfaceEvent(const QPointer &window, const MirSurfaceEvent *event); + void handleSurfaceOutputEvent(const QPointer &window, const MirSurfaceOutputEvent *event); private: QMirClientClientIntegration* mIntegration; @@ -79,8 +80,7 @@ private: const QByteArray mEventFilterType; const QEvent::Type mEventType; - QMirClientWindow *mLastFocusedWindow; - QAtomicInt mPendingFocusGainedEvents; + QMirClientWindow *mLastInputWindow; }; #endif // QMIRCLIENTINPUT_H diff --git a/src/plugins/platforms/mirclient/qmirclientintegration.cpp b/src/plugins/platforms/mirclient/qmirclientintegration.cpp index 2c8740f070..eef96ee3de 100644 --- a/src/plugins/platforms/mirclient/qmirclientintegration.cpp +++ b/src/plugins/platforms/mirclient/qmirclientintegration.cpp @@ -1,6 +1,6 @@ /**************************************************************************** ** -** Copyright (C) 2014-2015 Canonical, Ltd. +** Copyright (C) 2014-2016 Canonical, Ltd. ** Contact: https://www.qt.io/licensing/ ** ** This file is part of the plugins of the Qt Toolkit. @@ -42,6 +42,8 @@ #include "qmirclientintegration.h" #include "qmirclientbackingstore.h" #include "qmirclientclipboard.h" +#include "qmirclientdebugextension.h" +#include "qmirclientdesktopwindow.h" #include "qmirclientglcontext.h" #include "qmirclientinput.h" #include "qmirclientlogging.h" @@ -51,56 +53,62 @@ #include "qmirclientwindow.h" // Qt +#include #include -#include #include #include #include +#include +#include #include #include +#ifndef QT_NO_ACCESSIBILITY +#include +#ifndef QT_NO_ACCESSIBILITY_ATSPI_BRIDGE +#include +#endif +#endif + #include +#include // platform-api #include #include #include -static void resumedCallback(const UApplicationOptions *options, void* context) +static void resumedCallback(const UApplicationOptions */*options*/, void* context) { - Q_UNUSED(options) - Q_UNUSED(context) - DASSERT(context != NULL); - if (qGuiApp->focusWindow()) { - QWindowSystemInterface::handleApplicationStateChanged(Qt::ApplicationActive); - } else { - QWindowSystemInterface::handleApplicationStateChanged(Qt::ApplicationInactive); - } + auto integration = static_cast(context); + integration->appStateController()->setResumed(); } -static void aboutToStopCallback(UApplicationArchive *archive, void* context) +static void aboutToStopCallback(UApplicationArchive */*archive*/, void* context) { - Q_UNUSED(archive) - DASSERT(context != NULL); - QMirClientClientIntegration* integration = static_cast(context); - QPlatformInputContext *inputContext = integration->inputContext(); + auto integration = static_cast(context); + auto inputContext = integration->inputContext(); if (inputContext) { inputContext->hideInputPanel(); } else { - qWarning("QMirClientClientIntegration aboutToStopCallback(): no input context"); + qCWarning(mirclient) << "aboutToStopCallback(): no input context"; } - QWindowSystemInterface::handleApplicationStateChanged(Qt::ApplicationSuspended); + integration->appStateController()->setSuspended(); } -QMirClientClientIntegration::QMirClientClientIntegration() +QMirClientClientIntegration::QMirClientClientIntegration(int argc, char **argv) : QPlatformIntegration() - , mNativeInterface(new QMirClientNativeInterface) + , mNativeInterface(new QMirClientNativeInterface(this)) , mFontDb(new QGenericUnixFontDatabase) , mServices(new QMirClientPlatformServices) - , mClipboard(new QMirClientClipboard) + , mAppStateController(new QMirClientAppStateController) , mScaleFactor(1.0) { - setupOptions(); - setupDescription(); + { + QStringList args = QCoreApplication::arguments(); + setupOptions(args); + QByteArray sessionName = generateSessionName(args); + setupDescription(sessionName); + } // Create new application instance mInstance = u_application_instance_new_from_description_with_options(mDesc, mOptions); @@ -110,19 +118,48 @@ QMirClientClientIntegration::QMirClientClientIntegration() "running, and the correct socket is being used and is accessible. The shell may have\n" "rejected the incoming connection, so check its log file"); - mNativeInterface->setMirConnection(u_application_instance_get_mir_connection(mInstance)); + mMirConnection = u_application_instance_get_mir_connection(mInstance); + + // Choose the default surface format suited to the Mir platform + QSurfaceFormat defaultFormat; + defaultFormat.setRedBufferSize(8); + defaultFormat.setGreenBufferSize(8); + defaultFormat.setBlueBufferSize(8); + QSurfaceFormat::setDefaultFormat(defaultFormat); + + // Initialize EGL. + mEglNativeDisplay = mir_connection_get_egl_native_display(mMirConnection); + ASSERT((mEglDisplay = eglGetDisplay(mEglNativeDisplay)) != EGL_NO_DISPLAY); + ASSERT(eglInitialize(mEglDisplay, nullptr, nullptr) == EGL_TRUE); + + // Has debug mode been requsted, either with "-testability" switch or QT_LOAD_TESTABILITY env var + bool testability = qEnvironmentVariableIsSet("QT_LOAD_TESTABILITY"); + for (int i=1; !testability && iscreenAdded(screen); }); + connect(mScreenObserver.data(), &QMirClientScreenObserver::screenRemoved, + this, &QMirClientClientIntegration::destroyScreen); + + Q_FOREACH (auto screen, mScreenObserver->screens()) { + screenAdded(screen); + } // Initialize input. - if (qEnvironmentVariableIsEmpty("QTUBUNTU_NO_INPUT")) { - mInput = new QMirClientInput(this); - mInputContext = QPlatformInputContextFactory::create(); - } else { - mInput = nullptr; - mInputContext = nullptr; - } + mInput = new QMirClientInput(this); + mInputContext = QPlatformInputContextFactory::create(); // compute the scale factor const int defaultGridUnit = 8; @@ -140,10 +177,9 @@ QMirClientClientIntegration::QMirClientClientIntegration() QMirClientClientIntegration::~QMirClientClientIntegration() { + eglTerminate(mEglDisplay); delete mInput; delete mInputContext; - for (QScreen *screen : QGuiApplication::screens()) - QPlatformIntegration::destroyScreen(screen->handle()); delete mServices; } @@ -152,9 +188,8 @@ QPlatformServices *QMirClientClientIntegration::services() const return mServices; } -void QMirClientClientIntegration::setupOptions() +void QMirClientClientIntegration::setupOptions(QStringList &args) { - QStringList args = QCoreApplication::arguments(); int argc = args.size() + 1; char **argv = new char*[argc]; for (int i = 0; i < argc - 1; i++) @@ -168,10 +203,11 @@ void QMirClientClientIntegration::setupOptions() delete [] argv; } -void QMirClientClientIntegration::setupDescription() +void QMirClientClientIntegration::setupDescription(QByteArray &sessionName) { mDesc = u_application_description_new(); - UApplicationId* id = u_application_id_new_from_stringn("QtUbuntu", 8); + + UApplicationId* id = u_application_id_new_from_stringn(sessionName.data(), sessionName.count()); u_application_description_set_application_id(mDesc, id); UApplicationLifecycleDelegate* delegate = u_application_lifecycle_delegate_new(); @@ -181,43 +217,66 @@ void QMirClientClientIntegration::setupDescription() u_application_description_set_application_lifecycle_delegate(mDesc, delegate); } -QPlatformWindow* QMirClientClientIntegration::createPlatformWindow(QWindow* window) const +QByteArray QMirClientClientIntegration::generateSessionName(QStringList &args) { - return const_cast(this)->createPlatformWindow(window); + // Try to come up with some meaningful session name to uniquely identify this session, + // helping with shell debugging + + if (args.count() == 0) { + return QByteArray("QtUbuntu"); + } if (args[0].contains("qmlscene")) { + return generateSessionNameFromQmlFile(args); + } else { + // use the executable name + QFileInfo fileInfo(args[0]); + return fileInfo.fileName().toLocal8Bit(); + } } -QPlatformWindow* QMirClientClientIntegration::createPlatformWindow(QWindow* window) +QByteArray QMirClientClientIntegration::generateSessionNameFromQmlFile(QStringList &args) { - return new QMirClientWindow(window, mClipboard, screen(), - mInput, u_application_instance_get_mir_connection(mInstance)); + Q_FOREACH (QString arg, args) { + if (arg.endsWith(".qml")) { + QFileInfo fileInfo(arg); + return fileInfo.fileName().toLocal8Bit(); + } + } + + // give up + return "qmlscene"; } -QMirClientScreen *QMirClientClientIntegration::screen() const +QPlatformWindow* QMirClientClientIntegration::createPlatformWindow(QWindow* window) const { - return static_cast(QGuiApplication::primaryScreen()->handle()); + if (window->type() == Qt::Desktop) { + // Desktop windows should not be backed up by a mir surface as they don't draw anything (nor should). + return new QMirClientDesktopWindow(window); + } else { + return new QMirClientWindow(window, mInput, mNativeInterface, mAppStateController.data(), + mEglDisplay, mMirConnection, mDebugExtension.data()); + } } bool QMirClientClientIntegration::hasCapability(QPlatformIntegration::Capability cap) const { switch (cap) { - case ThreadedPixmaps: - return true; - - case OpenGL: - return true; - - case ApplicationState: - return true; - case ThreadedOpenGL: if (qEnvironmentVariableIsEmpty("QTUBUNTU_NO_THREADED_OPENGL")) { return true; } else { - DLOG("ubuntumirclient: disabled threaded OpenGL"); + qCDebug(mirclient, "disabled threaded OpenGL"); return false; } + + case ThreadedPixmaps: + case OpenGL: + case ApplicationState: case MultipleWindows: case NonFullScreenWindows: +#if QT_VERSION > QT_VERSION_CHECK(5, 5, 0) + case SwitchableWidgetComposition: +#endif + case RasterGLSurface: // needed for QQuickWidget return true; default: return QPlatformIntegration::hasCapability(cap); @@ -237,14 +296,25 @@ QPlatformBackingStore* QMirClientClientIntegration::createPlatformBackingStore(Q QPlatformOpenGLContext* QMirClientClientIntegration::createPlatformOpenGLContext( QOpenGLContext* context) const { - return const_cast(this)->createPlatformOpenGLContext(context); -} - -QPlatformOpenGLContext* QMirClientClientIntegration::createPlatformOpenGLContext( - QOpenGLContext* context) -{ - return new QMirClientOpenGLContext(static_cast(context->screen()->handle()), - static_cast(context->shareHandle())); + QSurfaceFormat format(context->format()); + + auto platformContext = new QMirClientOpenGLContext(format, context->shareHandle(), mEglDisplay); + if (!platformContext->isValid()) { + // Older Intel Atom-based devices only support OpenGL 1.4 compatibility profile but by default + // QML asks for at least OpenGL 2.0. The XCB GLX backend ignores this request and returns a + // 1.4 context, but the XCB EGL backend tries to honor it, and fails. The 1.4 context appears to + // have sufficient capabilities on MESA (i915) to render correctly however. So reduce the default + // requested OpenGL version to 1.0 to ensure EGL will give us a working context (lp:1549455). + static const bool isMesa = QString(eglQueryString(mEglDisplay, EGL_VENDOR)).contains(QStringLiteral("Mesa")); + if (isMesa) { + qCDebug(mirclientGraphics, "Attempting to choose OpenGL 1.4 context which may suit Mesa"); + format.setMajorVersion(1); + format.setMinorVersion(4); + delete platformContext; + platformContext = new QMirClientOpenGLContext(format, context->shareHandle(), mEglDisplay); + } + } + return platformContext; } QStringList QMirClientClientIntegration::themeNames() const @@ -277,10 +347,65 @@ QVariant QMirClientClientIntegration::styleHint(StyleHint hint) const QPlatformClipboard* QMirClientClientIntegration::clipboard() const { - return mClipboard.data(); + static QPlatformClipboard *clipboard = nullptr; + if (!clipboard) { + clipboard = new QMirClientClipboard; + } + return clipboard; } QPlatformNativeInterface* QMirClientClientIntegration::nativeInterface() const { return mNativeInterface; } + +QPlatformOffscreenSurface *QMirClientClientIntegration::createPlatformOffscreenSurface( + QOffscreenSurface *surface) const +{ + return new QEGLPbuffer(mEglDisplay, surface->requestedFormat(), surface); +} + +void QMirClientClientIntegration::destroyScreen(QMirClientScreen *screen) +{ + // FIXME: on deleting a screen while a Window is on it, Qt will automatically + // move the window to the primaryScreen(). This will trigger a screenChanged + // signal, causing things like QQuickScreenAttached to re-fetch screen properties + // like DPI and physical size. However this is crashing, as Qt is calling virtual + // functions on QPlatformScreen, for reasons unclear. As workaround, move window + // to primaryScreen() before deleting the screen. Might be QTBUG-38650 + + QScreen *primaryScreen = QGuiApplication::primaryScreen(); + if (screen != primaryScreen->handle()) { + uint32_t movedWindowCount = 0; + Q_FOREACH (QWindow *w, QGuiApplication::topLevelWindows()) { + if (w->screen()->handle() == screen) { + QWindowSystemInterface::handleWindowScreenChanged(w, primaryScreen); + ++movedWindowCount; + } + } + if (movedWindowCount > 0) { + QWindowSystemInterface::flushWindowSystemEvents(); + } + } + + qCDebug(mirclient) << "Removing Screen with id" << screen->mirOutputId() << "and geometry" << screen->geometry(); +#if QT_VERSION < QT_VERSION_CHECK(5, 5, 0) + delete screen; +#else + QPlatformIntegration::destroyScreen(screen); +#endif +} + +#ifndef QT_NO_ACCESSIBILITY +QPlatformAccessibility *QMirClientClientIntegration::accessibility() const +{ +#if !defined(QT_NO_ACCESSIBILITY_ATSPI_BRIDGE) + if (!mAccessibility) { + Q_ASSERT_X(QCoreApplication::eventDispatcher(), "QMirClientIntegration", + "Initializing accessibility without event-dispatcher!"); + mAccessibility.reset(new QSpiAccessibleBridge()); + } +#endif + return mAccessibility.data(); +} +#endif diff --git a/src/plugins/platforms/mirclient/qmirclientintegration.h b/src/plugins/platforms/mirclient/qmirclientintegration.h index 97317a310a..035117f4da 100644 --- a/src/plugins/platforms/mirclient/qmirclientintegration.h +++ b/src/plugins/platforms/mirclient/qmirclientintegration.h @@ -44,20 +44,28 @@ #include #include +#include "qmirclientappstatecontroller.h" #include "qmirclientplatformservices.h" +#include "qmirclientscreenobserver.h" // platform-api #include #include -class QMirClientClipboard; +#include + +class QMirClientDebugExtension; class QMirClientInput; class QMirClientNativeInterface; class QMirClientScreen; +class MirConnection; + +class QMirClientClientIntegration : public QObject, public QPlatformIntegration +{ + Q_OBJECT -class QMirClientClientIntegration : public QPlatformIntegration { public: - QMirClientClientIntegration(); + QMirClientClientIntegration(int argc, char **argv); virtual ~QMirClientClientIntegration(); // QPlatformIntegration methods. @@ -74,14 +82,26 @@ public: QPlatformWindow* createPlatformWindow(QWindow* window) const override; QPlatformInputContext* inputContext() const override { return mInputContext; } QPlatformClipboard* clipboard() const override; + void initialize() override; + QPlatformOffscreenSurface *createPlatformOffscreenSurface(QOffscreenSurface *surface) const override; + QPlatformAccessibility *accessibility() const override; + + // New methods. + MirConnection *mirConnection() const { return mMirConnection; } + EGLDisplay eglDisplay() const { return mEglDisplay; } + EGLNativeDisplayType eglNativeDisplay() const { return mEglNativeDisplay; } + QMirClientAppStateController *appStateController() const { return mAppStateController.data(); } + QMirClientScreenObserver *screenObserver() const { return mScreenObserver.data(); } + QMirClientDebugExtension *debugExtension() const { return mDebugExtension.data(); } - QPlatformOpenGLContext* createPlatformOpenGLContext(QOpenGLContext* context); - QPlatformWindow* createPlatformWindow(QWindow* window); - QMirClientScreen* screen() const; +private Q_SLOTS: + void destroyScreen(QMirClientScreen *screen); private: - void setupOptions(); - void setupDescription(); + void setupOptions(QStringList &args); + void setupDescription(QByteArray &sessionName); + static QByteArray generateSessionName(QStringList &args); + static QByteArray generateSessionNameFromQmlFile(QStringList &args); QMirClientNativeInterface* mNativeInterface; QPlatformFontDatabase* mFontDb; @@ -90,13 +110,22 @@ private: QMirClientInput* mInput; QPlatformInputContext* mInputContext; - QSharedPointer mClipboard; + mutable QScopedPointer mAccessibility; + QScopedPointer mDebugExtension; + QScopedPointer mScreenObserver; + QScopedPointer mAppStateController; qreal mScaleFactor; + MirConnection *mMirConnection; + // Platform API stuff UApplicationOptions* mOptions; UApplicationDescription* mDesc; UApplicationInstance* mInstance; + + // EGL related + EGLDisplay mEglDisplay{EGL_NO_DISPLAY}; + EGLNativeDisplayType mEglNativeDisplay; }; #endif // QMIRCLIENTINTEGRATION_H diff --git a/src/plugins/platforms/mirclient/qmirclientlogging.h b/src/plugins/platforms/mirclient/qmirclientlogging.h index 0eb3adbdc7..4921864ced 100644 --- a/src/plugins/platforms/mirclient/qmirclientlogging.h +++ b/src/plugins/platforms/mirclient/qmirclientlogging.h @@ -41,23 +41,15 @@ #ifndef QMIRCLIENTLOGGING_H #define QMIRCLIENTLOGGING_H -// Logging and assertion macros. -#define LOG(...) qDebug(__VA_ARGS__) -#define LOG_IF(cond,...) do { if (cond) qDebug(__VA_ARGS__); } while (0) +#include + #define ASSERT(cond) ((!(cond)) ? qt_assert(#cond,__FILE__,__LINE__) : qt_noop()) -#define NOT_REACHED() qt_assert("Not reached!",__FILE__,__LINE__) -// Logging and assertion macros are compiled out for release builds. -#if !defined(QT_NO_DEBUG) -#define DLOG(...) LOG(__VA_ARGS__) -#define DLOG_IF(cond,...) LOG_IF((cond), __VA_ARGS__) -#define DASSERT(cond) ASSERT((cond)) -#define DNOT_REACHED() NOT_REACHED() -#else -#define DLOG(...) qt_noop() -#define DLOG_IF(cond,...) qt_noop() -#define DASSERT(cond) qt_noop() -#define DNOT_REACHED() qt_noop() -#endif +Q_DECLARE_LOGGING_CATEGORY(mirclient) +Q_DECLARE_LOGGING_CATEGORY(mirclientBufferSwap) +Q_DECLARE_LOGGING_CATEGORY(mirclientInput) +Q_DECLARE_LOGGING_CATEGORY(mirclientGraphics) +Q_DECLARE_LOGGING_CATEGORY(mirclientCursor) +Q_DECLARE_LOGGING_CATEGORY(mirclientDebug) #endif // QMIRCLIENTLOGGING_H diff --git a/src/plugins/platforms/mirclient/qmirclientnativeinterface.cpp b/src/plugins/platforms/mirclient/qmirclientnativeinterface.cpp index 033df6ba11..b85e6fedfa 100644 --- a/src/plugins/platforms/mirclient/qmirclientnativeinterface.cpp +++ b/src/plugins/platforms/mirclient/qmirclientnativeinterface.cpp @@ -42,32 +42,36 @@ #include "qmirclientnativeinterface.h" #include "qmirclientscreen.h" #include "qmirclientglcontext.h" +#include "qmirclientwindow.h" // Qt -#include +#include #include #include #include -class QMirClientResourceMap : public QMap +class UbuntuResourceMap : public QMap { public: - QMirClientResourceMap() + UbuntuResourceMap() : QMap() { insert("egldisplay", QMirClientNativeInterface::EglDisplay); insert("eglcontext", QMirClientNativeInterface::EglContext); insert("nativeorientation", QMirClientNativeInterface::NativeOrientation); insert("display", QMirClientNativeInterface::Display); insert("mirconnection", QMirClientNativeInterface::MirConnection); + insert("mirsurface", QMirClientNativeInterface::MirSurface); + insert("scale", QMirClientNativeInterface::Scale); + insert("formfactor", QMirClientNativeInterface::FormFactor); } }; -Q_GLOBAL_STATIC(QMirClientResourceMap, ubuntuResourceMap) +Q_GLOBAL_STATIC(UbuntuResourceMap, ubuntuResourceMap) -QMirClientNativeInterface::QMirClientNativeInterface() - : mGenericEventFilterType(QByteArrayLiteral("Event")) +QMirClientNativeInterface::QMirClientNativeInterface(const QMirClientClientIntegration *integration) + : mIntegration(integration) + , mGenericEventFilterType(QByteArrayLiteral("Event")) , mNativeOrientation(nullptr) - , mMirConnection(nullptr) { } @@ -88,7 +92,7 @@ void* QMirClientNativeInterface::nativeResourceForIntegration(const QByteArray & const ResourceType resourceType = ubuntuResourceMap()->value(lowerCaseResource); if (resourceType == QMirClientNativeInterface::MirConnection) { - return mMirConnection; + return mIntegration->mirConnection(); } else { return nullptr; } @@ -119,14 +123,11 @@ void* QMirClientNativeInterface::nativeResourceForWindow(const QByteArray& resou if (!ubuntuResourceMap()->contains(kLowerCaseResource)) return NULL; const ResourceType kResourceType = ubuntuResourceMap()->value(kLowerCaseResource); - if (kResourceType == QMirClientNativeInterface::EglDisplay) { - if (window) { - return static_cast(window->screen()->handle())->eglDisplay(); - } else { - return static_cast( - QGuiApplication::primaryScreen()->handle())->eglDisplay(); - } - } else if (kResourceType == QMirClientNativeInterface::NativeOrientation) { + + switch (kResourceType) { + case EglDisplay: + return mIntegration->eglDisplay(); + case NativeOrientation: // Return the device's native screen orientation. if (window) { QMirClientScreen *ubuntuScreen = static_cast(window->screen()->handle()); @@ -136,8 +137,19 @@ void* QMirClientNativeInterface::nativeResourceForWindow(const QByteArray& resou mNativeOrientation = new Qt::ScreenOrientation(platformScreen->nativeOrientation()); } return mNativeOrientation; - } else { - return NULL; + case MirSurface: + if (window) { + auto ubuntuWindow = static_cast(window->handle()); + if (ubuntuWindow) { + return ubuntuWindow->mirSurface(); + } else { + return nullptr; + } + } else { + return nullptr; + } + default: + return nullptr; } } @@ -147,10 +159,59 @@ void* QMirClientNativeInterface::nativeResourceForScreen(const QByteArray& resou if (!ubuntuResourceMap()->contains(kLowerCaseResource)) return NULL; const ResourceType kResourceType = ubuntuResourceMap()->value(kLowerCaseResource); + if (!screen) + screen = QGuiApplication::primaryScreen(); + auto ubuntuScreen = static_cast(screen->handle()); if (kResourceType == QMirClientNativeInterface::Display) { - if (!screen) - screen = QGuiApplication::primaryScreen(); - return static_cast(screen->handle())->eglNativeDisplay(); + return mIntegration->eglNativeDisplay(); + // Changes to the following properties are emitted via the QMirClientNativeInterface::screenPropertyChanged + // signal fired by QMirClientScreen. Connect to this signal for these properties updates. + // WARNING: code highly thread unsafe! + } else if (kResourceType == QMirClientNativeInterface::Scale) { + // In application code, read with: + // float scale = *reinterpret_cast(nativeResourceForScreen("scale", screen())); + return &ubuntuScreen->mScale; + } else if (kResourceType == QMirClientNativeInterface::FormFactor) { + return &ubuntuScreen->mFormFactor; } else return NULL; } + +// Changes to these properties are emitted via the QMirClientNativeInterface::windowPropertyChanged +// signal fired by QMirClientWindow. Connect to this signal for these properties updates. +QVariantMap QMirClientNativeInterface::windowProperties(QPlatformWindow *window) const +{ + QVariantMap propertyMap; + auto w = static_cast(window); + if (w) { + propertyMap.insert("scale", w->scale()); + propertyMap.insert("formFactor", w->formFactor()); + } + return propertyMap; +} + +QVariant QMirClientNativeInterface::windowProperty(QPlatformWindow *window, const QString &name) const +{ + auto w = static_cast(window); + if (!w) { + return QVariant(); + } + + if (name == QStringLiteral("scale")) { + return w->scale(); + } else if (name == QStringLiteral("formFactor")) { + return w->formFactor(); + } else { + return QVariant(); + } +} + +QVariant QMirClientNativeInterface::windowProperty(QPlatformWindow *window, const QString &name, const QVariant &defaultValue) const +{ + QVariant returnVal = windowProperty(window, name); + if (!returnVal.isValid()) { + return defaultValue; + } else { + return returnVal; + } +} diff --git a/src/plugins/platforms/mirclient/qmirclientnativeinterface.h b/src/plugins/platforms/mirclient/qmirclientnativeinterface.h index 78a440e956..eb601de301 100644 --- a/src/plugins/platforms/mirclient/qmirclientnativeinterface.h +++ b/src/plugins/platforms/mirclient/qmirclientnativeinterface.h @@ -43,11 +43,16 @@ #include +#include "qmirclientintegration.h" + +class QPlatformScreen; + class QMirClientNativeInterface : public QPlatformNativeInterface { + Q_OBJECT public: - enum ResourceType { EglDisplay, EglContext, NativeOrientation, Display, MirConnection }; + enum ResourceType { EglDisplay, EglContext, NativeOrientation, Display, MirConnection, MirSurface, Scale, FormFactor }; - QMirClientNativeInterface(); + QMirClientNativeInterface(const QMirClientClientIntegration *integration); ~QMirClientNativeInterface(); // QPlatformNativeInterface methods. @@ -59,14 +64,20 @@ public: void* nativeResourceForScreen(const QByteArray& resourceString, QScreen* screen) override; + QVariantMap windowProperties(QPlatformWindow *window) const override; + QVariant windowProperty(QPlatformWindow *window, const QString &name) const override; + QVariant windowProperty(QPlatformWindow *window, const QString &name, const QVariant &defaultValue) const override; + // New methods. const QByteArray& genericEventFilterType() const { return mGenericEventFilterType; } - void setMirConnection(void *mirConnection) { mMirConnection = mirConnection; } + +Q_SIGNALS: // New signals + void screenPropertyChanged(QPlatformScreen *screen, const QString &propertyName); private: + const QMirClientClientIntegration *mIntegration; const QByteArray mGenericEventFilterType; Qt::ScreenOrientation* mNativeOrientation; - void *mMirConnection; }; #endif // QMIRCLIENTNATIVEINTERFACE_H diff --git a/src/plugins/platforms/mirclient/qmirclientplugin.cpp b/src/plugins/platforms/mirclient/qmirclientplugin.cpp index 201b68c304..fc44edfe40 100644 --- a/src/plugins/platforms/mirclient/qmirclientplugin.cpp +++ b/src/plugins/platforms/mirclient/qmirclientplugin.cpp @@ -40,19 +40,16 @@ #include "qmirclientplugin.h" #include "qmirclientintegration.h" +#include "qmirclientlogging.h" -QStringList QMirClientIntegrationPlugin::keys() const -{ - QStringList list; - list << QStringLiteral("mirclient"); - return list; -} +Q_LOGGING_CATEGORY(mirclient, "qt.qpa.mirclient", QtWarningMsg) -QPlatformIntegration* QMirClientIntegrationPlugin::create(const QString &system, - const QStringList &) +QPlatformIntegration *QMirClientIntegrationPlugin::create(const QString &system, + const QStringList &/*paramList*/, + int &argc, char **argv) { if (system.toLower() == QLatin1String("mirclient")) { - return new QMirClientClientIntegration; + return new QMirClientClientIntegration(argc, argv); } else { return 0; } diff --git a/src/plugins/platforms/mirclient/qmirclientplugin.h b/src/plugins/platforms/mirclient/qmirclientplugin.h index 8b5da6e4f6..207d97b5af 100644 --- a/src/plugins/platforms/mirclient/qmirclientplugin.h +++ b/src/plugins/platforms/mirclient/qmirclientplugin.h @@ -49,8 +49,8 @@ class QMirClientIntegrationPlugin : public QPlatformIntegrationPlugin Q_PLUGIN_METADATA(IID QPlatformIntegrationFactoryInterface_iid FILE "mirclient.json") public: - QStringList keys() const; - QPlatformIntegration* create(const QString&, const QStringList&); + QPlatformIntegration *create(const QString &system, const QStringList ¶mList, + int &argc, char **argv) override; }; #endif // QMIRCLIENTPLUGIN_H diff --git a/src/plugins/platforms/mirclient/qmirclientscreen.cpp b/src/plugins/platforms/mirclient/qmirclientscreen.cpp index 0a2253e9e2..cc8db830aa 100644 --- a/src/plugins/platforms/mirclient/qmirclientscreen.cpp +++ b/src/plugins/platforms/mirclient/qmirclientscreen.cpp @@ -1,6 +1,6 @@ /**************************************************************************** ** -** Copyright (C) 2014-2015 Canonical, Ltd. +** Copyright (C) 2014-2016 Canonical, Ltd. ** Contact: https://www.qt.io/licensing/ ** ** This file is part of the plugins of the Qt Toolkit. @@ -42,11 +42,12 @@ #include "qmirclientscreen.h" #include "qmirclientlogging.h" #include "qmirclientorientationchangeevent_p.h" +#include "qmirclientnativeinterface.h" #include // Qt -#include +#include #include #include #include @@ -55,9 +56,7 @@ #include -static const int kSwapInterval = 1; - -#if !defined(QT_NO_DEBUG) +static const int overrideDevicePixelRatio = qgetenv("QT_DEVICE_PIXEL_RATIO").toInt(); static const char *orientationToStr(Qt::ScreenOrientation orientation) { switch (orientation) { @@ -71,173 +70,33 @@ static const char *orientationToStr(Qt::ScreenOrientation orientation) { return "inverted portrait"; case Qt::InvertedLandscapeOrientation: return "inverted landscape"; - default: - return "INVALID!"; } + Q_UNREACHABLE(); } -static void printEglConfig(EGLDisplay display, EGLConfig config) { - DASSERT(display != EGL_NO_DISPLAY); - DASSERT(config != nullptr); - static const struct { const EGLint attrib; const char* name; } kAttribs[] = { - { EGL_BUFFER_SIZE, "EGL_BUFFER_SIZE" }, - { EGL_ALPHA_SIZE, "EGL_ALPHA_SIZE" }, - { EGL_BLUE_SIZE, "EGL_BLUE_SIZE" }, - { EGL_GREEN_SIZE, "EGL_GREEN_SIZE" }, - { EGL_RED_SIZE, "EGL_RED_SIZE" }, - { EGL_DEPTH_SIZE, "EGL_DEPTH_SIZE" }, - { EGL_STENCIL_SIZE, "EGL_STENCIL_SIZE" }, - { EGL_CONFIG_CAVEAT, "EGL_CONFIG_CAVEAT" }, - { EGL_CONFIG_ID, "EGL_CONFIG_ID" }, - { EGL_LEVEL, "EGL_LEVEL" }, - { EGL_MAX_PBUFFER_HEIGHT, "EGL_MAX_PBUFFER_HEIGHT" }, - { EGL_MAX_PBUFFER_PIXELS, "EGL_MAX_PBUFFER_PIXELS" }, - { EGL_MAX_PBUFFER_WIDTH, "EGL_MAX_PBUFFER_WIDTH" }, - { EGL_NATIVE_RENDERABLE, "EGL_NATIVE_RENDERABLE" }, - { EGL_NATIVE_VISUAL_ID, "EGL_NATIVE_VISUAL_ID" }, - { EGL_NATIVE_VISUAL_TYPE, "EGL_NATIVE_VISUAL_TYPE" }, - { EGL_SAMPLES, "EGL_SAMPLES" }, - { EGL_SAMPLE_BUFFERS, "EGL_SAMPLE_BUFFERS" }, - { EGL_SURFACE_TYPE, "EGL_SURFACE_TYPE" }, - { EGL_TRANSPARENT_TYPE, "EGL_TRANSPARENT_TYPE" }, - { EGL_TRANSPARENT_BLUE_VALUE, "EGL_TRANSPARENT_BLUE_VALUE" }, - { EGL_TRANSPARENT_GREEN_VALUE, "EGL_TRANSPARENT_GREEN_VALUE" }, - { EGL_TRANSPARENT_RED_VALUE, "EGL_TRANSPARENT_RED_VALUE" }, - { EGL_BIND_TO_TEXTURE_RGB, "EGL_BIND_TO_TEXTURE_RGB" }, - { EGL_BIND_TO_TEXTURE_RGBA, "EGL_BIND_TO_TEXTURE_RGBA" }, - { EGL_MIN_SWAP_INTERVAL, "EGL_MIN_SWAP_INTERVAL" }, - { EGL_MAX_SWAP_INTERVAL, "EGL_MAX_SWAP_INTERVAL" }, - { -1, NULL } - }; - const char* string = eglQueryString(display, EGL_VENDOR); - LOG("EGL vendor: %s", string); - string = eglQueryString(display, EGL_VERSION); - LOG("EGL version: %s", string); - string = eglQueryString(display, EGL_EXTENSIONS); - LOG("EGL extensions: %s", string); - LOG("EGL configuration attibutes:"); - for (int index = 0; kAttribs[index].attrib != -1; index++) { - EGLint value; - if (eglGetConfigAttrib(display, config, kAttribs[index].attrib, &value)) - LOG(" %s: %d", kAttribs[index].name, static_cast(value)); - } -} -#endif - - const QEvent::Type OrientationChangeEvent::mType = static_cast(QEvent::registerEventType()); -static const MirDisplayOutput *find_active_output( - const MirDisplayConfiguration *conf) -{ - const MirDisplayOutput *output = NULL; - for (uint32_t d = 0; d < conf->num_outputs; d++) - { - const MirDisplayOutput *out = conf->outputs + d; - - if (out->used && - out->connected && - out->num_modes && - out->current_mode < out->num_modes) - { - output = out; - break; - } - } - - return output; -} -QMirClientScreen::QMirClientScreen(MirConnection *connection) - : mFormat(QImage::Format_RGB32) +QMirClientScreen::QMirClientScreen(const MirOutput *output, MirConnection *connection) + : mDevicePixelRatio(1.0) + , mFormat(QImage::Format_RGB32) , mDepth(32) + , mDpi{0} + , mFormFactor{mir_form_factor_unknown} + , mScale{1.0} , mOutputId(0) - , mSurfaceFormat() - , mEglDisplay(EGL_NO_DISPLAY) - , mEglConfig(nullptr) , mCursor(connection) { - // Initialize EGL. - ASSERT(eglBindAPI(EGL_OPENGL_ES_API) == EGL_TRUE); - - mEglNativeDisplay = mir_connection_get_egl_native_display(connection); - ASSERT((mEglDisplay = eglGetDisplay(mEglNativeDisplay)) != EGL_NO_DISPLAY); - ASSERT(eglInitialize(mEglDisplay, nullptr, nullptr) == EGL_TRUE); - - // Configure EGL buffers format. - mSurfaceFormat.setRedBufferSize(8); - mSurfaceFormat.setGreenBufferSize(8); - mSurfaceFormat.setBlueBufferSize(8); - mSurfaceFormat.setAlphaBufferSize(8); - mSurfaceFormat.setDepthBufferSize(24); - mSurfaceFormat.setStencilBufferSize(8); - if (!qEnvironmentVariableIsEmpty("QTUBUNTU_MULTISAMPLE")) { - mSurfaceFormat.setSamples(4); - DLOG("ubuntumirclient: setting MSAA to 4 samples"); - } -#ifdef QTUBUNTU_USE_OPENGL - mSurfaceFormat.setRenderableType(QSurfaceFormat::OpenGL); -#else - mSurfaceFormat.setRenderableType(QSurfaceFormat::OpenGLES); -#endif - mEglConfig = q_configFromGLFormat(mEglDisplay, mSurfaceFormat, true); - - #if !defined(QT_NO_DEBUG) - printEglConfig(mEglDisplay, mEglConfig); - #endif - - // Set vblank swap interval. - int swapInterval = kSwapInterval; - QByteArray swapIntervalString = qgetenv("QTUBUNTU_SWAPINTERVAL"); - if (!swapIntervalString.isEmpty()) { - bool ok; - swapInterval = swapIntervalString.toInt(&ok); - if (!ok) - swapInterval = kSwapInterval; - } - DLOG("ubuntumirclient: setting swap interval to %d", swapInterval); - eglSwapInterval(mEglDisplay, swapInterval); - - // Get screen resolution. - auto configDeleter = [](MirDisplayConfiguration *config) { mir_display_config_destroy(config); }; - using configUp = std::unique_ptr; - configUp displayConfig(mir_connection_create_display_config(connection), configDeleter); - ASSERT(displayConfig != nullptr); - - auto const displayOutput = find_active_output(displayConfig.get()); - ASSERT(displayOutput != nullptr); - - mOutputId = displayOutput->output_id; - - mPhysicalSize = QSizeF(displayOutput->physical_width_mm, displayOutput->physical_height_mm); - DLOG("ubuntumirclient: screen physical size: %.2fx%.2f", mPhysicalSize.width(), mPhysicalSize.height()); - - const MirDisplayMode *mode = &displayOutput->modes[displayOutput->current_mode]; - const int kScreenWidth = mode->horizontal_resolution; - const int kScreenHeight = mode->vertical_resolution; - DASSERT(kScreenWidth > 0 && kScreenHeight > 0); - - DLOG("ubuntumirclient: screen resolution: %dx%d", kScreenWidth, kScreenHeight); - - mGeometry = QRect(0, 0, kScreenWidth, kScreenHeight); - - DLOG("QQMirClientScreen::QQMirClientScreen (this=%p)", this); - - // Set the default orientation based on the initial screen dimmensions. - mNativeOrientation = (mGeometry.width() >= mGeometry.height()) ? Qt::LandscapeOrientation : Qt::PortraitOrientation; - - // If it's a landscape device (i.e. some tablets), start in landscape, otherwise portrait - mCurrentOrientation = (mNativeOrientation == Qt::LandscapeOrientation) ? Qt::LandscapeOrientation : Qt::PortraitOrientation; + setMirOutput(output); } QMirClientScreen::~QMirClientScreen() { - eglTerminate(mEglDisplay); } void QMirClientScreen::customEvent(QEvent* event) { - DASSERT(QThread::currentThread() == thread()); + Q_ASSERT(QThread::currentThread() == thread()); OrientationChangeEvent* oReadingEvent = static_cast(event); switch (oReadingEvent->mOrientation) { @@ -261,14 +120,10 @@ void QMirClientScreen::customEvent(QEvent* event) { Qt::InvertedLandscapeOrientation : Qt::InvertedPortraitOrientation; break; } - default: { - DLOG("QMirClientScreen::customEvent - Unknown orientation."); - return; - } } // Raise the event signal so that client apps know the orientation changed - DLOG("QMirClientScreen::customEvent - handling orientation change to %s", orientationToStr(mCurrentOrientation)); + qCDebug(mirclient, "QMirClientScreen::customEvent - handling orientation change to %s", orientationToStr(mCurrentOrientation)); QWindowSystemInterface::handleScreenOrientationChange(screen(), mCurrentOrientation); } @@ -289,7 +144,7 @@ void QMirClientScreen::handleWindowSurfaceResize(int windowWidth, int windowHeig mGeometry.setWidth(currGeometry.height()); mGeometry.setHeight(currGeometry.width()); - DLOG("QMirClientScreen::handleWindowSurfaceResize - new screen geometry (w=%d, h=%d)", + qCDebug(mirclient, "QMirClientScreen::handleWindowSurfaceResize - new screen geometry (w=%d, h=%d)", mGeometry.width(), mGeometry.height()); QWindowSystemInterface::handleScreenGeometryChange(screen(), mGeometry /* newGeometry */, @@ -300,7 +155,108 @@ void QMirClientScreen::handleWindowSurfaceResize(int windowWidth, int windowHeig } else { mCurrentOrientation = Qt::LandscapeOrientation; } - DLOG("QMirClientScreen::handleWindowSurfaceResize - new orientation %s",orientationToStr(mCurrentOrientation)); + qCDebug(mirclient, "QMirClientScreen::handleWindowSurfaceResize - new orientation %s",orientationToStr(mCurrentOrientation)); QWindowSystemInterface::handleScreenOrientationChange(screen(), mCurrentOrientation); } } + +void QMirClientScreen::setMirOutput(const MirOutput *output) +{ + // Physical screen size (in mm) + mPhysicalSize.setWidth(mir_output_get_physical_width_mm(output)); + mPhysicalSize.setHeight(mir_output_get_physical_height_mm(output)); + + // Pixel Format +// mFormat = qImageFormatFromMirPixelFormat(mir_output_get_current_pixel_format(output)); // GERRY: TODO + + // Pixel depth + mDepth = 8 * MIR_BYTES_PER_PIXEL(mir_output_get_current_pixel_format(output)); + + // Mode = Resolution & refresh rate + const MirOutputMode *mode = mir_output_get_current_mode(output); + mNativeGeometry.setX(mir_output_get_position_x(output)); + mNativeGeometry.setY(mir_output_get_position_y(output)); + mNativeGeometry.setWidth(mir_output_mode_get_width(mode)); + mNativeGeometry.setHeight(mir_output_mode_get_height(mode)); + + mRefreshRate = mir_output_mode_get_refresh_rate(mode); + + // UI scale & DPR + mScale = mir_output_get_scale_factor(output); + if (overrideDevicePixelRatio > 0) { + mDevicePixelRatio = overrideDevicePixelRatio; + } else { + mDevicePixelRatio = 1.0; // FIXME - need to determine suitable DPR for the specified scale + } + + mFormFactor = mir_output_get_form_factor(output); + + mOutputId = mir_output_get_id(output); + + mGeometry.setX(mNativeGeometry.x()); + mGeometry.setY(mNativeGeometry.y()); + mGeometry.setWidth(mNativeGeometry.width()); + mGeometry.setHeight(mNativeGeometry.height()); + + // Set the default orientation based on the initial screen dimensions. + mNativeOrientation = (mGeometry.width() >= mGeometry.height()) ? Qt::LandscapeOrientation : Qt::PortraitOrientation; + + // If it's a landscape device (i.e. some tablets), start in landscape, otherwise portrait + mCurrentOrientation = (mNativeOrientation == Qt::LandscapeOrientation) ? Qt::LandscapeOrientation : Qt::PortraitOrientation; +} + +void QMirClientScreen::updateMirOutput(const MirOutput *output) +{ + auto oldRefreshRate = mRefreshRate; + auto oldScale = mScale; + auto oldFormFactor = mFormFactor; + auto oldGeometry = mGeometry; + + setMirOutput(output); + + // Emit change signals in particular order + if (oldGeometry != mGeometry) { + QWindowSystemInterface::handleScreenGeometryChange(screen(), + mGeometry /* newGeometry */, + mGeometry /* newAvailableGeometry */); + } + + if (!qFuzzyCompare(mRefreshRate, oldRefreshRate)) { + QWindowSystemInterface::handleScreenRefreshRateChange(screen(), mRefreshRate); + } + + auto nativeInterface = static_cast(qGuiApp->platformNativeInterface()); + if (!qFuzzyCompare(mScale, oldScale)) { + nativeInterface->screenPropertyChanged(this, QStringLiteral("scale")); + } + if (mFormFactor != oldFormFactor) { + nativeInterface->screenPropertyChanged(this, QStringLiteral("formFactor")); + } +} + +void QMirClientScreen::setAdditionalMirDisplayProperties(float scale, MirFormFactor formFactor, int dpi) +{ + if (mDpi != dpi) { + mDpi = dpi; + QWindowSystemInterface::handleScreenLogicalDotsPerInchChange(screen(), dpi, dpi); + } + + auto nativeInterface = static_cast(qGuiApp->platformNativeInterface()); + if (!qFuzzyCompare(mScale, scale)) { + mScale = scale; + nativeInterface->screenPropertyChanged(this, QStringLiteral("scale")); + } + if (mFormFactor != formFactor) { + mFormFactor = formFactor; + nativeInterface->screenPropertyChanged(this, QStringLiteral("formFactor")); + } +} + +QDpi QMirClientScreen::logicalDpi() const +{ + if (mDpi > 0) { + return QDpi(mDpi, mDpi); + } else { + return QPlatformScreen::logicalDpi(); + } +} diff --git a/src/plugins/platforms/mirclient/qmirclientscreen.h b/src/plugins/platforms/mirclient/qmirclientscreen.h index b050836ada..b31cba1964 100644 --- a/src/plugins/platforms/mirclient/qmirclientscreen.h +++ b/src/plugins/platforms/mirclient/qmirclientscreen.h @@ -1,6 +1,6 @@ /**************************************************************************** ** -** Copyright (C) 2014-2015 Canonical, Ltd. +** Copyright (C) 2014-2016 Canonical, Ltd. ** Contact: https://www.qt.io/licensing/ ** ** This file is part of the plugins of the Qt Toolkit. @@ -43,17 +43,19 @@ #include #include -#include + +#include // just for MirFormFactor enum #include "qmirclientcursor.h" struct MirConnection; +struct MirOutput; class QMirClientScreen : public QObject, public QPlatformScreen { Q_OBJECT public: - QMirClientScreen(MirConnection *connection); + QMirClientScreen(const MirOutput *output, MirConnection *connection); virtual ~QMirClientScreen(); // QPlatformScreen methods. @@ -62,34 +64,43 @@ public: QRect geometry() const override { return mGeometry; } QRect availableGeometry() const override { return mGeometry; } QSizeF physicalSize() const override { return mPhysicalSize; } + qreal devicePixelRatio() const override { return mDevicePixelRatio; } + QDpi logicalDpi() const override; Qt::ScreenOrientation nativeOrientation() const override { return mNativeOrientation; } Qt::ScreenOrientation orientation() const override { return mNativeOrientation; } QPlatformCursor *cursor() const override { return const_cast(&mCursor); } - // New methods. - QSurfaceFormat surfaceFormat() const { return mSurfaceFormat; } - EGLDisplay eglDisplay() const { return mEglDisplay; } - EGLConfig eglConfig() const { return mEglConfig; } - EGLNativeDisplayType eglNativeDisplay() const { return mEglNativeDisplay; } + // Additional Screen properties from Mir + int mirOutputId() const { return mOutputId; } + MirFormFactor formFactor() const { return mFormFactor; } + float scale() const { return mScale; } + + // Internally used methods + void updateMirOutput(const MirOutput *output); + void setAdditionalMirDisplayProperties(float scale, MirFormFactor formFactor, int dpi); void handleWindowSurfaceResize(int width, int height); - uint32_t mirOutputId() const { return mOutputId; } // QObject methods. void customEvent(QEvent* event) override; private: - QRect mGeometry; + void setMirOutput(const MirOutput *output); + + QRect mGeometry, mNativeGeometry; QSizeF mPhysicalSize; + qreal mDevicePixelRatio; Qt::ScreenOrientation mNativeOrientation; Qt::ScreenOrientation mCurrentOrientation; QImage::Format mFormat; int mDepth; - uint32_t mOutputId; - QSurfaceFormat mSurfaceFormat; - EGLDisplay mEglDisplay; - EGLConfig mEglConfig; - EGLNativeDisplayType mEglNativeDisplay; + int mDpi; + qreal mRefreshRate; + MirFormFactor mFormFactor; + float mScale; + int mOutputId; QMirClientCursor mCursor; + + friend class QMirClientNativeInterface; }; #endif // QMIRCLIENTSCREEN_H diff --git a/src/plugins/platforms/mirclient/qmirclientscreenobserver.cpp b/src/plugins/platforms/mirclient/qmirclientscreenobserver.cpp new file mode 100644 index 0000000000..792aeca351 --- /dev/null +++ b/src/plugins/platforms/mirclient/qmirclientscreenobserver.cpp @@ -0,0 +1,161 @@ +/**************************************************************************** +** +** Copyright (C) 2016 Canonical, Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of the plugins of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** 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 Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 3 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL3 included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 3 requirements +** will be met: https://www.gnu.org/licenses/lgpl-3.0.html. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 2.0 or (at your option) the GNU General +** Public license version 3 or 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.GPL2 and 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-2.0.html and +** https://www.gnu.org/licenses/gpl-3.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + + +#include "qmirclientscreenobserver.h" +#include "qmirclientscreen.h" +#include "qmirclientwindow.h" +#include "qmirclientlogging.h" + +// Qt +#include +#include + +// Mir +#include +#include + +#include + +namespace { + static void displayConfigurationChangedCallback(MirConnection */*connection*/, void* context) + { + ASSERT(context != NULL); + QMirClientScreenObserver *observer = static_cast(context); + QMetaObject::invokeMethod(observer, "update"); + } + + const char *mirFormFactorToStr(MirFormFactor formFactor) + { + switch (formFactor) { + case mir_form_factor_unknown: return "unknown"; + case mir_form_factor_phone: return "phone"; + case mir_form_factor_tablet: return "tablet"; + case mir_form_factor_monitor: return "monitor"; + case mir_form_factor_tv: return "tv"; + case mir_form_factor_projector: return "projector"; + } + Q_UNREACHABLE(); + } +} // anonymous namespace + +QMirClientScreenObserver::QMirClientScreenObserver(MirConnection *mirConnection) + : mMirConnection(mirConnection) +{ + mir_connection_set_display_config_change_callback(mirConnection, ::displayConfigurationChangedCallback, this); + update(); +} + +void QMirClientScreenObserver::update() +{ + // Wrap MirDisplayConfiguration to always delete when out of scope + auto configDeleter = [](MirDisplayConfig *config) { mir_display_config_release(config); }; + using configUp = std::unique_ptr; + configUp displayConfig(mir_connection_create_display_configuration(mMirConnection), configDeleter); + + // Mir only tells us something changed, it is up to us to figure out what. + QList newScreenList; + QList oldScreenList = mScreenList; + mScreenList.clear(); + + for (int i = 0; i < mir_display_config_get_num_outputs(displayConfig.get()); i++) { + const MirOutput *output = mir_display_config_get_output(displayConfig.get(), i); + if (mir_output_is_enabled(output)) { + QMirClientScreen *screen = findScreenWithId(oldScreenList, mir_output_get_id(output)); + if (screen) { // we've already set up this display before + screen->updateMirOutput(output); + oldScreenList.removeAll(screen); + } else { + // new display, so create QMirClientScreen for it + screen = new QMirClientScreen(output, mMirConnection); + newScreenList.append(screen); + qCDebug(mirclient) << "Added Screen with id" << mir_output_get_id(output) + << "and geometry" << screen->geometry(); + } + mScreenList.append(screen); + } + } + + // Announce old & unused Screens, should be deleted by the slot + Q_FOREACH (const auto screen, oldScreenList) { + Q_EMIT screenRemoved(screen); + } + + /* + * Mir's MirDisplayOutput does not include formFactor or scale for some reason, but Qt + * will want that information on creating the QScreen. Only way we get that info is when + * Mir positions a Window on that Screen. See "handleScreenPropertiesChange" method + */ + + // Announce new Screens + Q_FOREACH (const auto screen, newScreenList) { + Q_EMIT screenAdded(screen); + } + + qCDebug(mirclient) << "======================================="; + for (auto screen: mScreenList) { + qCDebug(mirclient) << screen << "- id:" << screen->mirOutputId() + << "geometry:" << screen->geometry() + << "form factor:" << mirFormFactorToStr(screen->formFactor()) + << "scale:" << screen->scale(); + } + qCDebug(mirclient) << "======================================="; +} + +QMirClientScreen *QMirClientScreenObserver::findScreenWithId(int id) +{ + return findScreenWithId(mScreenList, id); +} + +QMirClientScreen *QMirClientScreenObserver::findScreenWithId(const QList &list, int id) +{ + Q_FOREACH (const auto screen, list) { + if (screen->mirOutputId() == id) { + return screen; + } + } + return nullptr; +} + +void QMirClientScreenObserver::handleScreenPropertiesChange(QMirClientScreen *screen, int dpi, + MirFormFactor formFactor, float scale) +{ + screen->setAdditionalMirDisplayProperties(scale, formFactor, dpi); +} + diff --git a/src/plugins/platforms/mirclient/qmirclientscreenobserver.h b/src/plugins/platforms/mirclient/qmirclientscreenobserver.h new file mode 100644 index 0000000000..ad927319c1 --- /dev/null +++ b/src/plugins/platforms/mirclient/qmirclientscreenobserver.h @@ -0,0 +1,78 @@ +/**************************************************************************** +** +** Copyright (C) 2016 Canonical, Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of the plugins of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** 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 Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 3 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL3 included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 3 requirements +** will be met: https://www.gnu.org/licenses/lgpl-3.0.html. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 2.0 or (at your option) the GNU General +** Public license version 3 or 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.GPL2 and 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-2.0.html and +** https://www.gnu.org/licenses/gpl-3.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + + +#ifndef QMIRCLIENTSCREENOBSERVER_H +#define QMIRCLIENTSCREENOBSERVER_H + +#include + +#include + +class QMirClientScreen; + +class QMirClientScreenObserver : public QObject +{ + Q_OBJECT + +public: + QMirClientScreenObserver(MirConnection *connection); + + QList screens() const { return mScreenList; } + QMirClientScreen *findScreenWithId(int id); + + void handleScreenPropertiesChange(QMirClientScreen *screen, int dpi, + MirFormFactor formFactor, float scale); + +Q_SIGNALS: + void screenAdded(QMirClientScreen *screen); + void screenRemoved(QMirClientScreen *screen); + +private Q_SLOTS: + void update(); + +private: + QMirClientScreen *findScreenWithId(const QList &list, int id); + void removeScreen(QMirClientScreen *screen); + + MirConnection *mMirConnection; + QList mScreenList; +}; + +#endif // QMIRCLIENTSCREENOBSERVER_H diff --git a/src/plugins/platforms/mirclient/qmirclientwindow.cpp b/src/plugins/platforms/mirclient/qmirclientwindow.cpp index 60b9cd7900..adc8ae652a 100644 --- a/src/plugins/platforms/mirclient/qmirclientwindow.cpp +++ b/src/plugins/platforms/mirclient/qmirclientwindow.cpp @@ -1,6 +1,6 @@ /**************************************************************************** ** -** Copyright (C) 2014-2015 Canonical, Ltd. +** Copyright (C) 2014-2016 Canonical, Ltd. ** Contact: https://www.qt.io/licensing/ ** ** This file is part of the plugins of the Qt Toolkit. @@ -40,26 +40,33 @@ // Local #include "qmirclientwindow.h" -#include "qmirclientclipboard.h" +#include "qmirclientdebugextension.h" +#include "qmirclientnativeinterface.h" #include "qmirclientinput.h" +#include "qmirclientintegration.h" #include "qmirclientscreen.h" #include "qmirclientlogging.h" #include +#include // Qt #include #include #include #include +#include // Platform API #include #include +Q_LOGGING_CATEGORY(mirclientBufferSwap, "qt.qpa.mirclient.bufferSwap", QtWarningMsg) + namespace { +const Qt::WindowType LowChromeWindowHint = (Qt::WindowType)0x00800000; // FIXME: this used to be defined by platform-api, but it's been removed in v3. Change ubuntu-keyboard to use // a different enum for window roles. @@ -87,40 +94,110 @@ EGLNativeWindowType nativeWindowFor(MirSurface *surf) return reinterpret_cast(mir_buffer_stream_get_egl_native_window(stream)); } -MirSurfaceState qtWindowStateToMirSurfaceState(Qt::WindowState state) +const char *qtWindowStateToStr(Qt::WindowState state) { switch (state) { case Qt::WindowNoState: - return mir_surface_state_restored; + return "NoState"; case Qt::WindowFullScreen: - return mir_surface_state_fullscreen; + return "FullScreen"; case Qt::WindowMaximized: - return mir_surface_state_maximized; + return "Maximized"; case Qt::WindowMinimized: - return mir_surface_state_minimized; - default: - LOG("Unexpected Qt::WindowState: %d", state); - return mir_surface_state_restored; + return "Minimized"; + case Qt::WindowActive: + return "Active"; } + Q_UNREACHABLE(); } -#if !defined(QT_NO_DEBUG) -const char *qtWindowStateToStr(Qt::WindowState state) +const char *mirSurfaceStateToStr(MirSurfaceState surfaceState) +{ + switch (surfaceState) { + case mir_surface_state_unknown: return "unknown"; + case mir_surface_state_restored: return "restored"; + case mir_surface_state_minimized: return "minimized"; + case mir_surface_state_maximized: return "vertmaximized"; + case mir_surface_state_vertmaximized: return "vertmaximized"; + case mir_surface_state_fullscreen: return "fullscreen"; + case mir_surface_state_horizmaximized: return "horizmaximized"; + case mir_surface_state_hidden: return "hidden"; + case mir_surface_states: Q_UNREACHABLE(); + } + Q_UNREACHABLE(); +} + +const char *mirPixelFormatToStr(MirPixelFormat pixelFormat) +{ + switch (pixelFormat) { + case mir_pixel_format_invalid: return "invalid"; + case mir_pixel_format_abgr_8888: return "ABGR8888"; + case mir_pixel_format_xbgr_8888: return "XBGR8888"; + case mir_pixel_format_argb_8888: return "ARGB8888"; + case mir_pixel_format_xrgb_8888: return "XRGB8888"; + case mir_pixel_format_bgr_888: return "BGR888"; + case mir_pixel_format_rgb_888: return "RGB888"; + case mir_pixel_format_rgb_565: return "RGB565"; + case mir_pixel_format_rgba_5551: return "RGBA5551"; + case mir_pixel_format_rgba_4444: return "RGBA4444"; + case mir_pixel_formats: Q_UNREACHABLE(); + } + Q_UNREACHABLE(); +} + +const char *mirSurfaceTypeToStr(MirSurfaceType type) +{ + switch (type) { + case mir_surface_type_normal: return "Normal"; /**< AKA "regular" */ + case mir_surface_type_utility: return "Utility"; /**< AKA "floating regular" */ + case mir_surface_type_dialog: return "Dialog"; + case mir_surface_type_gloss: return "Gloss"; + case mir_surface_type_freestyle: return "Freestyle"; + case mir_surface_type_menu: return "Menu"; + case mir_surface_type_inputmethod: return "Input Method"; /**< AKA "OSK" or handwriting etc. */ + case mir_surface_type_satellite: return "Satellite"; /**< AKA "toolbox"/"toolbar" */ + case mir_surface_type_tip: return "Tip"; /**< AKA "tooltip" */ + case mir_surface_types: Q_UNREACHABLE(); + } + return ""; +} + +MirSurfaceState qtWindowStateToMirSurfaceState(Qt::WindowState state) { switch (state) { case Qt::WindowNoState: - return "NoState"; + case Qt::WindowActive: + return mir_surface_state_restored; case Qt::WindowFullScreen: - return "FullScreen"; + return mir_surface_state_fullscreen; case Qt::WindowMaximized: - return "Maximized"; + return mir_surface_state_maximized; case Qt::WindowMinimized: - return "Minimized"; + return mir_surface_state_minimized; + } + return mir_surface_state_unknown; // should never be reached +} + +MirSurfaceType qtWindowTypeToMirSurfaceType(Qt::WindowType type) +{ + switch (type & Qt::WindowType_Mask) { + case Qt::Dialog: + return mir_surface_type_dialog; + case Qt::Sheet: + case Qt::Drawer: + return mir_surface_type_utility; + case Qt::Popup: + case Qt::Tool: + return mir_surface_type_menu; + case Qt::ToolTip: + return mir_surface_type_tip; + case Qt::SplashScreen: + return mir_surface_type_freestyle; + case Qt::Window: default: - return "!?"; + return mir_surface_type_normal; } } -#endif WId makeId() { @@ -128,14 +205,6 @@ WId makeId() return id++; } -MirPixelFormat defaultPixelFormatFor(MirConnection *connection) -{ - MirPixelFormat format; - unsigned int nformats; - mir_connection_get_available_surface_formats(connection, &format, 1, &nformats); - return format; -} - UAUiWindowRole roleFor(QWindow *window) { QVariant roleVariant = window->property("role"); @@ -155,52 +224,93 @@ QMirClientWindow *transientParentFor(QWindow *window) return parent ? static_cast(parent->handle()) : nullptr; } -Spec makeSurfaceSpec(QWindow *window, QMirClientInput *input, MirConnection *connection) -{ - const auto geom = window->geometry(); - const int width = geom.width() > 0 ? geom.width() : 1; - const int height = geom.height() > 0 ? geom.height() : 1; - const auto pixelFormat = defaultPixelFormatFor(connection); - - if (U_ON_SCREEN_KEYBOARD_ROLE == roleFor(window)) { - DLOG("[ubuntumirclient QPA] makeSurfaceSpec(window=%p) - creating input method surface (width=%d, height=%d", window, width, height); - return Spec{mir_connection_create_spec_for_input_method(connection, width, height, pixelFormat)}; - } - - const Qt::WindowType type = window->type(); - if (type == Qt::Popup) { - auto parent = transientParentFor(window); - if (parent == nullptr) { - //NOTE: We cannot have a parentless popup - - //try using the last surface to receive input as that will most likely be - //the one that caused this popup to be created - parent = input->lastFocusedWindow(); - } - if (parent) { - auto pos = geom.topLeft(); - pos -= parent->geometry().topLeft(); - MirRectangle location{pos.x(), pos.y(), 0, 0}; - DLOG("[ubuntumirclient QPA] makeSurfaceSpec(window=%p) - creating menu surface(width:%d, height:%d)", window, width, height); - return Spec{mir_connection_create_spec_for_menu( - connection, width, height, pixelFormat, parent->mirSurface(), - &location, mir_edge_attachment_any)}; - } else { - DLOG("[ubuntumirclient QPA] makeSurfaceSpec(window=%p) - cannot create a menu without a parent!", window); - } - } else if (type == Qt::Dialog) { - auto parent = transientParentFor(window); - if (parent) { - // Modal dialog - DLOG("[ubuntumirclient QPA] makeSurfaceSpec(window=%p) - creating modal dialog (width=%d, height=%d", window, width, height); - return Spec{mir_connection_create_spec_for_modal_dialog(connection, width, height, pixelFormat, parent->mirSurface())}; - } else { - // TODO: do Qt parentless dialogs have the same semantics as mir? - DLOG("[ubuntumirclient QPA] makeSurfaceSpec(window=%p) - creating parentless dialog (width=%d, height=%d)", window, width, height); - return Spec{mir_connection_create_spec_for_dialog(connection, width, height, pixelFormat)}; - } - } - DLOG("[ubuntumirclient QPA] makeSurfaceSpec(window=%p) - creating normal surface(type=0x%x, width=%d, height=%d)", window, type, width, height); - return Spec{mir_connection_create_spec_for_normal_surface(connection, width, height, pixelFormat)}; +bool requiresParent(const MirSurfaceType type) +{ + switch (type) { + case mir_surface_type_dialog: //FIXME - not quite what the specification dictates, but is what Mir's api dictates + case mir_surface_type_utility: + case mir_surface_type_gloss: + case mir_surface_type_menu: + case mir_surface_type_satellite: + case mir_surface_type_tip: + return true; + default: + return false; + } +} + +bool requiresParent(const Qt::WindowType type) +{ + return requiresParent(qtWindowTypeToMirSurfaceType(type)); +} + +bool isMovable(const Qt::WindowType type) +{ + auto mirType = qtWindowTypeToMirSurfaceType(type); + switch (mirType) { + case mir_surface_type_menu: + case mir_surface_type_tip: + return true; + default: + return false; + } +} + +Spec makeSurfaceSpec(QWindow *window, MirPixelFormat pixelFormat, QMirClientWindow *parentWindowHandle, + MirConnection *connection) +{ + const auto geometry = window->geometry(); + const int width = geometry.width() > 0 ? geometry.width() : 1; + const int height = geometry.height() > 0 ? geometry.height() : 1; + auto type = qtWindowTypeToMirSurfaceType(window->type()); + + if (U_ON_SCREEN_KEYBOARD_ROLE == roleFor(window)) { + type = mir_surface_type_inputmethod; + } + + MirRectangle location{geometry.x(), geometry.y(), 0, 0}; + MirSurface *parent = nullptr; + if (parentWindowHandle) { + parent = parentWindowHandle->mirSurface(); + // Qt uses absolute positioning, but Mir positions surfaces relative to parent. + location.top -= parentWindowHandle->geometry().top(); + location.left -= parentWindowHandle->geometry().left(); + } + + Spec spec; + + switch (type) { + case mir_surface_type_menu: + spec = Spec{mir_connection_create_spec_for_menu(connection, width, height, pixelFormat, parent, + &location, mir_edge_attachment_any)}; + break; + case mir_surface_type_dialog: + spec = Spec{mir_connection_create_spec_for_modal_dialog(connection, width, height, pixelFormat, parent)}; + break; + case mir_surface_type_utility: + spec = Spec{mir_connection_create_spec_for_dialog(connection, width, height, pixelFormat)}; + break; + case mir_surface_type_tip: +#if MIR_CLIENT_VERSION < MIR_VERSION_NUMBER(3, 4, 0) + spec = Spec{mir_connection_create_spec_for_tooltip(connection, width, height, pixelFormat, parent, + &location)}; +#else + spec = Spec{mir_connection_create_spec_for_tip(connection, width, height, pixelFormat, parent, + &location, mir_edge_attachment_any)}; +#endif + break; + case mir_surface_type_inputmethod: + spec = Spec{mir_connection_create_spec_for_input_method(connection, width, height, pixelFormat)}; + break; + default: + spec = Spec{mir_connection_create_spec_for_normal_surface(connection, width, height, pixelFormat)}; + break; + } + + qCDebug(mirclient, "makeSurfaceSpec(window=%p): %s spec (type=0x%x, position=(%d, %d)px, size=(%dx%d)px)", + window, mirSurfaceTypeToStr(type), window->type(), location.left, location.top, width, height); + + return std::move(spec); } void setSizingConstraints(MirSurfaceSpec *spec, const QSize& minSize, const QSize& maxSize, const QSize& increment) @@ -221,16 +331,30 @@ void setSizingConstraints(MirSurfaceSpec *spec, const QSize& minSize, const QSiz } } -MirSurface *createMirSurface(QWindow *window, QMirClientScreen *screen, QMirClientInput *input, MirConnection *connection) +MirSurface *createMirSurface(QWindow *window, int mirOutputId, QMirClientWindow *parentWindowHandle, + MirPixelFormat pixelFormat, MirConnection *connection, + mir_surface_event_callback inputCallback, void *inputContext) { - auto spec = makeSurfaceSpec(window, input, connection); + auto spec = makeSurfaceSpec(window, pixelFormat, parentWindowHandle, connection); + + // Install event handler as early as possible + mir_surface_spec_set_event_handler(spec.get(), inputCallback, inputContext); + const auto title = window->title().toUtf8(); mir_surface_spec_set_name(spec.get(), title.constData()); setSizingConstraints(spec.get(), window->minimumSize(), window->maximumSize(), window->sizeIncrement()); if (window->windowState() == Qt::WindowFullScreen) { - mir_surface_spec_set_fullscreen_on_output(spec.get(), screen->mirOutputId()); + mir_surface_spec_set_fullscreen_on_output(spec.get(), mirOutputId); + } + + if (window->flags() & LowChromeWindowHint) { + mir_surface_spec_set_shell_chrome(spec.get(), mir_shell_chrome_low); + } + + if (!window->isVisible()) { + mir_surface_spec_set_state(spec.get(), mir_surface_state_hidden); } auto surface = mir_surface_create_sync(spec.get()); @@ -238,83 +362,45 @@ MirSurface *createMirSurface(QWindow *window, QMirClientScreen *screen, QMirClie return surface; } -// FIXME - in order to work around https://bugs.launchpad.net/mir/+bug/1346633 -// we need to guess the panel height (3GU) -int panelHeight() -{ - if (qEnvironmentVariableIsSet("QT_MIRCLIENT_IGNORE_PANEL")) - return 0; - const int defaultGridUnit = 8; - int gridUnit = defaultGridUnit; - QByteArray gridUnitString = qgetenv("GRID_UNIT_PX"); - if (!gridUnitString.isEmpty()) { - bool ok; - gridUnit = gridUnitString.toInt(&ok); - if (!ok) { - gridUnit = defaultGridUnit; +QMirClientWindow *getParentIfNecessary(QWindow *window, QMirClientInput *input) +{ + QMirClientWindow *parentWindowHandle = nullptr; + if (requiresParent(window->type())) { + parentWindowHandle = transientParentFor(window); + if (parentWindowHandle == nullptr) { + // NOTE: Mir requires this surface have a parent. Try using the last surface to receive input as that will + // most likely be the one that caused this surface to be created + parentWindowHandle = input->lastInputWindow(); } } - return gridUnit * 3; + return parentWindowHandle; } -} //namespace - -class QMirClientSurface +MirPixelFormat disableAlphaBufferIfPossible(MirPixelFormat pixelFormat) { -public: - QMirClientSurface(QMirClientWindow *platformWindow, QMirClientScreen *screen, QMirClientInput *input, MirConnection *connection) - : mWindow(platformWindow->window()) - , mPlatformWindow(platformWindow) - , mInput(input) - , mConnection(connection) - , mMirSurface(createMirSurface(mWindow, screen, input, connection)) - , mEglDisplay(screen->eglDisplay()) - , mEglSurface(eglCreateWindowSurface(mEglDisplay, screen->eglConfig(), nativeWindowFor(mMirSurface), nullptr)) - , mVisible(false) - , mNeedsRepaint(false) - , mParented(mWindow->transientParent() || mWindow->parent()) - , mWindowState(mWindow->windowState()) - - { - mir_surface_set_event_handler(mMirSurface, surfaceEventCallback, this); - - // Window manager can give us a final size different from what we asked for - // so let's check what we ended up getting - MirSurfaceParameters parameters; - mir_surface_get_parameters(mMirSurface, ¶meters); - - auto geom = mWindow->geometry(); - geom.setWidth(parameters.width); - geom.setHeight(parameters.height); - if (mWindowState == Qt::WindowFullScreen) { - geom.setY(0); - } else { - geom.setY(panelHeight()); - } + switch (pixelFormat) { + case mir_pixel_format_abgr_8888: + return mir_pixel_format_xbgr_8888; + case mir_pixel_format_argb_8888: + return mir_pixel_format_xrgb_8888; + default: // can do nothing, leave it alone + return pixelFormat; + } +} +} //namespace - // Assume that the buffer size matches the surface size at creation time - mBufferSize = geom.size(); - platformWindow->QPlatformWindow::setGeometry(geom); - QWindowSystemInterface::handleGeometryChange(mWindow, geom); - DLOG("[ubuntumirclient QPA] created surface at (%d, %d) with size (%d, %d), title '%s', role: '%d'\n", - geom.x(), geom.y(), geom.width(), geom.height(), mWindow->title().toUtf8().constData(), roleFor(mWindow)); - } - ~QMirClientSurface() - { - if (mEglSurface != EGL_NO_SURFACE) - eglDestroySurface(mEglDisplay, mEglSurface); - if (mMirSurface) - mir_surface_release_sync(mMirSurface); - } +class UbuntuSurface +{ +public: + UbuntuSurface(QMirClientWindow *platformWindow, EGLDisplay display, QMirClientInput *input, MirConnection *connection); + ~UbuntuSurface(); - QMirClientSurface(QMirClientSurface const&) = delete; - QMirClientSurface& operator=(QMirClientSurface const&) = delete; + UbuntuSurface(const UbuntuSurface &) = delete; + UbuntuSurface& operator=(const UbuntuSurface &) = delete; - void resize(const QSize& newSize); - void setState(Qt::WindowState newState); - void setVisible(bool state); + void updateGeometry(const QRect &newGeometry); void updateTitle(const QString& title); void setSizingConstraints(const QSize& minSize, const QSize& maxSize, const QSize& increment); @@ -322,76 +408,151 @@ public: void handleSurfaceResized(int width, int height); int needsRepaint() const; + MirSurfaceState state() const { return mir_surface_get_state(mMirSurface); } + void setState(MirSurfaceState state); + + MirSurfaceType type() const { return mir_surface_get_type(mMirSurface); } + + void setShellChrome(MirShellChrome shellChrome); + EGLSurface eglSurface() const { return mEglSurface; } MirSurface *mirSurface() const { return mMirSurface; } + void setSurfaceParent(MirSurface*); + bool hasParent() const { return mParented; } + + QSurfaceFormat format() const { return mFormat; } + + bool mNeedsExposeCatchup; + + QString persistentSurfaceId(); + private: static void surfaceEventCallback(MirSurface* surface, const MirEvent *event, void* context); void postEvent(const MirEvent *event); - void updateSurface(); QWindow * const mWindow; QMirClientWindow * const mPlatformWindow; QMirClientInput * const mInput; MirConnection * const mConnection; + QMirClientWindow * mParentWindowHandle{nullptr}; - MirSurface * const mMirSurface; + MirSurface* mMirSurface; const EGLDisplay mEglDisplay; - const EGLSurface mEglSurface; + EGLSurface mEglSurface; - bool mVisible; bool mNeedsRepaint; bool mParented; - Qt::WindowState mWindowState; QSize mBufferSize; + QSurfaceFormat mFormat; + MirPixelFormat mPixelFormat; QMutex mTargetSizeMutex; QSize mTargetSize; + MirShellChrome mShellChrome; + QString mPersistentIdStr; }; -void QMirClientSurface::resize(const QSize& size) -{ - DLOG("[ubuntumirclient QPA] resize(window=%p, width=%d, height=%d)", mWindow, size.width(), size.height()); - - if (mWindowState == Qt::WindowFullScreen || mWindowState == Qt::WindowMaximized) { - DLOG("[ubuntumirclient QPA] resize(window=%p) - not resizing, window is maximized or fullscreen", mWindow); - return; +UbuntuSurface::UbuntuSurface(QMirClientWindow *platformWindow, EGLDisplay display, QMirClientInput *input, MirConnection *connection) + : mWindow(platformWindow->window()) + , mPlatformWindow(platformWindow) + , mInput(input) + , mConnection(connection) + , mEglDisplay(display) + , mNeedsRepaint(false) + , mParented(mWindow->transientParent() || mWindow->parent()) + , mFormat(mWindow->requestedFormat()) + , mShellChrome(mWindow->flags() & LowChromeWindowHint ? mir_shell_chrome_low : mir_shell_chrome_normal) +{ + // Have Qt choose most suitable EGLConfig for the requested surface format, and update format to reflect it + EGLConfig config = q_configFromGLFormat(display, mFormat, true); + if (config == 0) { + // Older Intel Atom-based devices only support OpenGL 1.4 compatibility profile but by default + // QML asks for at least OpenGL 2.0. The XCB GLX backend ignores this request and returns a + // 1.4 context, but the XCB EGL backend tries to honor it, and fails. The 1.4 context appears to + // have sufficient capabilities on MESA (i915) to render correctly however. So reduce the default + // requested OpenGL version to 1.0 to ensure EGL will give us a working context (lp:1549455). + static const bool isMesa = QString(eglQueryString(display, EGL_VENDOR)).contains(QStringLiteral("Mesa")); + if (isMesa) { + qCDebug(mirclientGraphics, "Attempting to choose OpenGL 1.4 context which may suit Mesa"); + mFormat.setMajorVersion(1); + mFormat.setMinorVersion(4); + config = q_configFromGLFormat(display, mFormat, true); + } + } + if (config == 0) { + qCritical() << "Qt failed to choose a suitable EGLConfig to suit the surface format" << mFormat; } - if (size.isEmpty()) { - DLOG("[ubuntumirclient QPA] resize(window=%p) - not resizing, size is empty", mWindow); - return; + mFormat = q_glFormatFromConfig(display, config, mFormat); + + // Have Mir decide the pixel format most suited to the chosen EGLConfig. This is the only way + // Mir will know what EGLConfig has been chosen - it cannot deduce it from the buffers. + mPixelFormat = mir_connection_get_egl_pixel_format(connection, display, config); + // But the chosen EGLConfig might have an alpha buffer enabled, even if not requested by the client. + // If that's the case, try to edit the chosen pixel format in order to disable the alpha buffer. + // This is an optimization for the compositor, as it can avoid blending this surface. + if (mWindow->requestedFormat().alphaBufferSize() < 0) { + mPixelFormat = disableAlphaBufferIfPossible(mPixelFormat); } - Spec spec{mir_connection_create_spec_for_changes(mConnection)}; - mir_surface_spec_set_width(spec.get(), size.width()); - mir_surface_spec_set_height(spec.get(), size.height()); - mir_surface_apply_spec(mMirSurface, spec.get()); + const auto outputId = static_cast(mWindow->screen()->handle())->mirOutputId(); + + mParentWindowHandle = getParentIfNecessary(mWindow, input); + + mMirSurface = createMirSurface(mWindow, outputId, mParentWindowHandle, mPixelFormat, connection, surfaceEventCallback, this); + mEglSurface = eglCreateWindowSurface(mEglDisplay, config, nativeWindowFor(mMirSurface), nullptr); + + mNeedsExposeCatchup = mir_surface_get_visibility(mMirSurface) == mir_surface_visibility_occluded; + + // Window manager can give us a final size different from what we asked for + // so let's check what we ended up getting + MirSurfaceParameters parameters; + mir_surface_get_parameters(mMirSurface, ¶meters); + + auto geom = mWindow->geometry(); + geom.setWidth(parameters.width); + geom.setHeight(parameters.height); + + // Assume that the buffer size matches the surface size at creation time + mBufferSize = geom.size(); + platformWindow->QPlatformWindow::setGeometry(geom); + QWindowSystemInterface::handleGeometryChange(mWindow, geom); + + qCDebug(mirclient) << "Created surface with geometry:" << geom << "title:" << mWindow->title() + << "role:" << roleFor(mWindow); + qCDebug(mirclientGraphics) + << "Requested format:" << mWindow->requestedFormat() + << "\nActual format:" << mFormat + << "with associated Mir pixel format:" << mirPixelFormatToStr(mPixelFormat); } -void QMirClientSurface::setState(Qt::WindowState newState) +UbuntuSurface::~UbuntuSurface() { - mir_wait_for(mir_surface_set_state(mMirSurface, qtWindowStateToMirSurfaceState(newState))); - mWindowState = newState; + if (mEglSurface != EGL_NO_SURFACE) + eglDestroySurface(mEglDisplay, mEglSurface); + if (mMirSurface) { + mir_surface_release_sync(mMirSurface); + } } -void QMirClientSurface::setVisible(bool visible) +void UbuntuSurface::updateGeometry(const QRect &newGeometry) { - if (mVisible == visible) - return; - - mVisible = visible; + qCDebug(mirclient,"updateGeometry(window=%p, width=%d, height=%d)", mWindow, + newGeometry.width(), newGeometry.height()); - if (mVisible) - updateSurface(); - - // TODO: Use the new mir_surface_state_hidden state instead of mir_surface_state_minimized. - // Will have to change qtmir and unity8 for that. - const auto newState = visible ? qtWindowStateToMirSurfaceState(mWindowState) : mir_surface_state_minimized; - mir_wait_for(mir_surface_set_state(mMirSurface, newState)); + Spec spec; + if (isMovable(mWindow->type())) { + spec = Spec{makeSurfaceSpec(mWindow, mPixelFormat, mParentWindowHandle, mConnection)}; + } else { + spec = Spec{mir_connection_create_spec_for_changes(mConnection)}; + mir_surface_spec_set_width(spec.get(), newGeometry.width()); + mir_surface_spec_set_height(spec.get(), newGeometry.height()); + } + mir_surface_apply_spec(mMirSurface, spec.get()); } -void QMirClientSurface::updateTitle(const QString& newTitle) +void UbuntuSurface::updateTitle(const QString& newTitle) { const auto title = newTitle.toUtf8(); Spec spec{mir_connection_create_spec_for_changes(mConnection)}; @@ -399,14 +560,14 @@ void QMirClientSurface::updateTitle(const QString& newTitle) mir_surface_apply_spec(mMirSurface, spec.get()); } -void QMirClientSurface::setSizingConstraints(const QSize& minSize, const QSize& maxSize, const QSize& increment) +void UbuntuSurface::setSizingConstraints(const QSize& minSize, const QSize& maxSize, const QSize& increment) { Spec spec{mir_connection_create_spec_for_changes(mConnection)}; ::setSizingConstraints(spec.get(), minSize, maxSize, increment); mir_surface_apply_spec(mMirSurface, spec.get()); } -void QMirClientSurface::handleSurfaceResized(int width, int height) +void UbuntuSurface::handleSurfaceResized(int width, int height) { QMutexLocker lock(&mTargetSizeMutex); @@ -419,7 +580,7 @@ void QMirClientSurface::handleSurfaceResized(int width, int height) mNeedsRepaint = mTargetSize.width() == width && mTargetSize.height() == height; } -int QMirClientSurface::needsRepaint() const +int UbuntuSurface::needsRepaint() const { if (mNeedsRepaint) { if (mTargetSize != mBufferSize) { @@ -436,12 +597,26 @@ int QMirClientSurface::needsRepaint() const return 0; } -void QMirClientSurface::onSwapBuffersDone() +void UbuntuSurface::setState(MirSurfaceState state) +{ + mir_wait_for(mir_surface_set_state(mMirSurface, state)); +} + +void UbuntuSurface::setShellChrome(MirShellChrome chrome) +{ + if (chrome != mShellChrome) { + auto spec = Spec{mir_connection_create_spec_for_changes(mConnection)}; + mir_surface_spec_set_shell_chrome(spec.get(), chrome); + mir_surface_apply_spec(mMirSurface, spec.get()); + + mShellChrome = chrome; + } +} + +void UbuntuSurface::onSwapBuffersDone() { -#if !defined(QT_NO_DEBUG) static int sFrameNumber = 0; ++sFrameNumber; -#endif EGLint eglSurfaceWidth = -1; EGLint eglSurfaceHeight = -1; @@ -452,7 +627,7 @@ void QMirClientSurface::onSwapBuffersDone() if (validSize && (mBufferSize.width() != eglSurfaceWidth || mBufferSize.height() != eglSurfaceHeight)) { - DLOG("[ubuntumirclient QPA] onSwapBuffersDone(window=%p) [%d] - size changed (%d, %d) => (%d, %d)", + qCDebug(mirclientBufferSwap, "onSwapBuffersDone(window=%p) [%d] - size changed (%d, %d) => (%d, %d)", mWindow, sFrameNumber, mBufferSize.width(), mBufferSize.height(), eglSurfaceWidth, eglSurfaceHeight); mBufferSize.rwidth() = eglSurfaceWidth; @@ -464,23 +639,21 @@ void QMirClientSurface::onSwapBuffersDone() mPlatformWindow->QPlatformWindow::setGeometry(newGeometry); QWindowSystemInterface::handleGeometryChange(mWindow, newGeometry); } else { -#if 0 - DLOG("[ubuntumirclient QPA] onSwapBuffersDone(window=%p) [%d] - buffer size (%d,%d)", + qCDebug(mirclientBufferSwap, "onSwapBuffersDone(window=%p) [%d] - buffer size (%d,%d)", mWindow, sFrameNumber, mBufferSize.width(), mBufferSize.height()); -#endif } } -void QMirClientSurface::surfaceEventCallback(MirSurface *surface, const MirEvent *event, void* context) +void UbuntuSurface::surfaceEventCallback(MirSurface *surface, const MirEvent *event, void* context) { Q_UNUSED(surface); Q_ASSERT(context != nullptr); - auto s = static_cast(context); + auto s = static_cast(context); s->postEvent(event); } -void QMirClientSurface::postEvent(const MirEvent *event) +void UbuntuSurface::postEvent(const MirEvent *event) { if (mir_event_type_resize == mir_event_get_type(event)) { // TODO: The current event queue just accumulates all resize events; @@ -490,7 +663,7 @@ void QMirClientSurface::postEvent(const MirEvent *event) const auto resizeEvent = mir_event_get_resize_event(event); const auto width = mir_resize_event_get_width(resizeEvent); const auto height = mir_resize_event_get_height(resizeEvent); - DLOG("[ubuntumirclient QPA] resizeEvent(window=%p, width=%d, height=%d)", mWindow, width, height); + qCDebug(mirclient, "resizeEvent(window=%p, width=%d, height=%d)", mWindow, width, height); QMutexLocker lock(&mTargetSizeMutex); mTargetSize.rwidth() = width; @@ -500,44 +673,57 @@ void QMirClientSurface::postEvent(const MirEvent *event) mInput->postEvent(mPlatformWindow, event); } -void QMirClientSurface::updateSurface() +void UbuntuSurface::setSurfaceParent(MirSurface* parent) { - DLOG("[ubuntumirclient QPA] updateSurface(window=%p)", mWindow); + qCDebug(mirclient, "setSurfaceParent(window=%p)", mWindow); - if (!mParented && mWindow->type() == Qt::Dialog) { - // The dialog may have been parented after creation time - // so morph it into a modal dialog - auto parent = transientParentFor(mWindow); - if (parent) { - DLOG("[ubuntumirclient QPA] updateSurface(window=%p) dialog now parented", mWindow); - mParented = true; - Spec spec{mir_connection_create_spec_for_changes(mConnection)}; - mir_surface_spec_set_parent(spec.get(), parent->mirSurface()); - mir_surface_apply_spec(mMirSurface, spec.get()); - } + mParented = true; + Spec spec{mir_connection_create_spec_for_changes(mConnection)}; + mir_surface_spec_set_parent(spec.get(), parent); + mir_surface_apply_spec(mMirSurface, spec.get()); +} + +QString UbuntuSurface::persistentSurfaceId() +{ + if (mPersistentIdStr.isEmpty()) { + MirPersistentId* mirPermaId = mir_surface_request_persistent_id_sync(mMirSurface); + mPersistentIdStr = mir_persistent_id_as_string(mirPermaId); + mir_persistent_id_release(mirPermaId); } + return mPersistentIdStr; } -QMirClientWindow::QMirClientWindow(QWindow *w, const QSharedPointer &clipboard, QMirClientScreen *screen, - QMirClientInput *input, MirConnection *connection) +QMirClientWindow::QMirClientWindow(QWindow *w, QMirClientInput *input, QMirClientNativeInterface *native, + QMirClientAppStateController *appState, EGLDisplay eglDisplay, + MirConnection *mirConnection, QMirClientDebugExtension *debugExt) : QObject(nullptr) , QPlatformWindow(w) , mId(makeId()) - , mClipboard(clipboard) - , mSurface(new QMirClientSurface{this, screen, input, connection}) + , mWindowState(w->windowState()) + , mWindowFlags(w->flags()) + , mWindowVisible(false) + , mAppStateController(appState) + , mDebugExtention(debugExt) + , mNativeInterface(native) + , mSurface(new UbuntuSurface{this, eglDisplay, input, mirConnection}) + , mScale(1.0) + , mFormFactor(mir_form_factor_unknown) { - DLOG("[ubuntumirclient QPA] QMirClientWindow(window=%p, screen=%p, input=%p, surf=%p)", w, screen, input, mSurface.get()); + mWindowExposed = mSurface->mNeedsExposeCatchup == false; + + qCDebug(mirclient, "QMirClientWindow(window=%p, screen=%p, input=%p, surf=%p) with title '%s', role: '%d'", + w, w->screen()->handle(), input, mSurface.get(), qPrintable(window()->title()), roleFor(window())); } QMirClientWindow::~QMirClientWindow() { - DLOG("[ubuntumirclient QPA] ~QMirClientWindow(window=%p)", this); + qCDebug(mirclient, "~QMirClientWindow(window=%p)", this); } void QMirClientWindow::handleSurfaceResized(int width, int height) { QMutexLocker lock(&mMutex); - DLOG("[ubuntumirclient QPA] handleSurfaceResize(window=%p, width=%d, height=%d)", window(), width, height); + qCDebug(mirclient, "handleSurfaceResize(window=%p, size=(%dx%d)px", window(), width, height); mSurface->handleSurfaceResized(width, height); @@ -547,88 +733,140 @@ void QMirClientWindow::handleSurfaceResized(int width, int height) // updated size but it still needs re-rendering so another redraw may be needed. // A mir API to drop the currently held buffer would help here, so that we wouldn't have to redraw twice auto const numRepaints = mSurface->needsRepaint(); - DLOG("[ubuntumirclient QPA] handleSurfaceResize(window=%p) redraw %d times", window(), numRepaints); + lock.unlock(); + qCDebug(mirclient, "handleSurfaceResize(window=%p) redraw %d times", window(), numRepaints); for (int i = 0; i < numRepaints; i++) { - DLOG("[ubuntumirclient QPA] handleSurfaceResize(window=%p) repainting width=%d, height=%d", window(), geometry().size().width(), geometry().size().height()); + qCDebug(mirclient, "handleSurfaceResize(window=%p) repainting size=(%dx%d)dp", window(), geometry().size().width(), geometry().size().height()); QWindowSystemInterface::handleExposeEvent(window(), QRect(QPoint(), geometry().size())); } } -void QMirClientWindow::handleSurfaceFocused() +void QMirClientWindow::handleSurfaceExposeChange(bool exposed) { - DLOG("[ubuntumirclient QPA] handleSurfaceFocused(window=%p)", window()); + QMutexLocker lock(&mMutex); + qCDebug(mirclient, "handleSurfaceExposeChange(window=%p, exposed=%s)", window(), exposed ? "true" : "false"); + + mSurface->mNeedsExposeCatchup = false; + if (mWindowExposed == exposed) return; + mWindowExposed = exposed; - // System clipboard contents might have changed while this window was unfocused and without - // this process getting notified about it because it might have been suspended (due to - // application lifecycle policies), thus unable to listen to any changes notified through - // D-Bus. - // Therefore let's ensure we are up to date with the system clipboard now that we are getting - // focused again. - mClipboard->requestDBusClipboardContents(); + lock.unlock(); + QWindowSystemInterface::handleExposeEvent(window(), QRect(QPoint(), geometry().size())); +} + +void QMirClientWindow::handleSurfaceFocusChanged(bool focused) +{ + qCDebug(mirclient, "handleSurfaceFocusChanged(window=%p, focused=%d)", window(), focused); + if (focused) { + mAppStateController->setWindowFocused(true); + QWindowSystemInterface::handleWindowActivated(window(), Qt::ActiveWindowFocusReason); + } else { + mAppStateController->setWindowFocused(false); + } +} + +void QMirClientWindow::handleSurfaceVisibilityChanged(bool visible) +{ + qCDebug(mirclient, "handleSurfaceVisibilityChanged(window=%p, visible=%d)", window(), visible); + + if (mWindowVisible == visible) return; + mWindowVisible = visible; + + QWindowSystemInterface::handleExposeEvent(window(), QRect(QPoint(), geometry().size())); +} + +void QMirClientWindow::handleSurfaceStateChanged(Qt::WindowState state) +{ + qCDebug(mirclient, "handleSurfaceStateChanged(window=%p, %s)", window(), qtWindowStateToStr(state)); + + if (mWindowState == state) return; + mWindowState = state; + + QWindowSystemInterface::handleWindowStateChanged(window(), state); } void QMirClientWindow::setWindowState(Qt::WindowState state) { QMutexLocker lock(&mMutex); - DLOG("[ubuntumirclient QPA] setWindowState(window=%p, %s)", this, qtWindowStateToStr(state)); - mSurface->setState(state); + qCDebug(mirclient, "setWindowState(window=%p, %s)", this, qtWindowStateToStr(state)); + + if (mWindowState == state) return; + mWindowState = state; + + lock.unlock(); + updateSurfaceState(); +} + +void QMirClientWindow::setWindowFlags(Qt::WindowFlags flags) +{ + QMutexLocker lock(&mMutex); + qCDebug(mirclient, "setWindowFlags(window=%p, 0x%x)", this, (int)flags); - updatePanelHeightHack(state); + if (mWindowFlags == flags) return; + mWindowFlags = flags; + + mSurface->setShellChrome(mWindowFlags & LowChromeWindowHint ? mir_shell_chrome_low : mir_shell_chrome_normal); } -/* - FIXME: Mir does not let clients know the position of their windows in the virtual - desktop space. So we have this ugly hack that assumes a phone situation where the - window is always on the top-left corner, right below the indicators panel if not - in fullscreen. - */ -void QMirClientWindow::updatePanelHeightHack(Qt::WindowState state) +QRect QMirClientWindow::geometry() const { - if (state == Qt::WindowFullScreen && geometry().y() != 0) { - QRect newGeometry = geometry(); - newGeometry.setY(0); - QPlatformWindow::setGeometry(newGeometry); - QWindowSystemInterface::handleGeometryChange(window(), newGeometry); - } else if (geometry().y() == 0) { - QRect newGeometry = geometry(); - newGeometry.setY(panelHeight()); - QPlatformWindow::setGeometry(newGeometry); - QWindowSystemInterface::handleGeometryChange(window(), newGeometry); + if (mDebugExtention) { + auto geom = QPlatformWindow::geometry(); + geom.moveTopLeft(mDebugExtention->mapSurfacePointToScreen(mSurface->mirSurface(), QPoint(0,0))); + return geom; + } else { + return QPlatformWindow::geometry(); } } void QMirClientWindow::setGeometry(const QRect& rect) { QMutexLocker lock(&mMutex); - DLOG("[ubuntumirclient QPA] setGeometry (window=%p, x=%d, y=%d, width=%d, height=%d)", - window(), rect.x(), rect.y(), rect.width(), rect.height()); - //NOTE: mir surfaces cannot be moved by the client so ignore the topLeft coordinates - const auto newSize = rect.size(); - auto newGeometry = geometry(); - newGeometry.setSize(newSize); - QPlatformWindow::setGeometry(newGeometry); + if (window()->windowState() == Qt::WindowFullScreen || window()->windowState() == Qt::WindowMaximized) { + qCDebug(mirclient, "setGeometry(window=%p) - not resizing, window is maximized or fullscreen", window()); + return; + } + + qCDebug(mirclient, "setGeometry (window=%p, position=(%d, %d)dp, size=(%dx%d)dp)", + window(), rect.x(), rect.y(), rect.width(), rect.height()); + // Immediately update internal geometry so Qt believes position updated + QRect newPosition(geometry()); + newPosition.moveTo(rect.topLeft()); + QPlatformWindow::setGeometry(newPosition); - mSurface->resize(newSize); + mSurface->updateGeometry(rect); + // Note: don't call handleGeometryChange here, wait to see what Mir replies with. } void QMirClientWindow::setVisible(bool visible) { QMutexLocker lock(&mMutex); - DLOG("[ubuntumirclient QPA] setVisible (window=%p, visible=%s)", window(), visible ? "true" : "false"); - - mSurface->setVisible(visible); - const QRect& exposeRect = visible ? QRect(QPoint(), geometry().size()) : QRect(); + qCDebug(mirclient, "setVisible (window=%p, visible=%s)", window(), visible ? "true" : "false"); + + if (mWindowVisible == visible) return; + mWindowVisible = visible; + + if (visible) { + if (!mSurface->hasParent() && window()->type() == Qt::Dialog) { + // The dialog may have been parented after creation time + // so morph it into a modal dialog + auto parent = transientParentFor(window()); + if (parent) { + mSurface->setSurfaceParent(parent->mirSurface()); + } + } + } lock.unlock(); - QWindowSystemInterface::handleExposeEvent(window(), exposeRect); - QWindowSystemInterface::flushWindowSystemEvents(); + updateSurfaceState(); + QWindowSystemInterface::handleExposeEvent(window(), QRect(QPoint(), geometry().size())); } void QMirClientWindow::setWindowTitle(const QString& title) { QMutexLocker lock(&mMutex); - DLOG("[ubuntumirclient QPA] setWindowTitle(window=%p) title=%s)", window(), title.toUtf8().constData()); + qCDebug(mirclient, "setWindowTitle(window=%p) title=%s)", window(), title.toUtf8().constData()); mSurface->updateTitle(title); } @@ -636,13 +874,33 @@ void QMirClientWindow::propagateSizeHints() { QMutexLocker lock(&mMutex); const auto win = window(); - DLOG("[ubuntumirclient QPA] propagateSizeHints(window=%p) min(%d,%d), max(%d,%d) increment(%d, %d)", - win, win->minimumSize().width(), win->minimumSize().height(), - win->maximumSize().width(), win->maximumSize().height(), - win->sizeIncrement().width(), win->sizeIncrement().height()); + qCDebug(mirclient, "propagateSizeHints(window=%p) min(%d,%d), max(%d,%d) increment(%d, %d)", + win, win->minimumSize().width(), win->minimumSize().height(), + win->maximumSize().width(), win->maximumSize().height(), + win->sizeIncrement().width(), win->sizeIncrement().height()); mSurface->setSizingConstraints(win->minimumSize(), win->maximumSize(), win->sizeIncrement()); } +bool QMirClientWindow::isExposed() const +{ + // mNeedsExposeCatchup because we need to render a frame to get the expose surface event from mir. + return mWindowVisible && (mWindowExposed || (mSurface && mSurface->mNeedsExposeCatchup)); +} + +QSurfaceFormat QMirClientWindow::format() const +{ + return mSurface->format(); +} + +QPoint QMirClientWindow::mapToGlobal(const QPoint &pos) const +{ + if (mDebugExtention) { + return mDebugExtention->mapSurfacePointToScreen(mSurface->mirSurface(), pos); + } else { + return pos; + } +} + void* QMirClientWindow::eglSurface() const { return mSurface->eglSurface(); @@ -662,4 +920,43 @@ void QMirClientWindow::onSwapBuffersDone() { QMutexLocker lock(&mMutex); mSurface->onSwapBuffersDone(); + + if (mSurface->mNeedsExposeCatchup) { + mSurface->mNeedsExposeCatchup = false; + mWindowExposed = false; + + lock.unlock(); + QWindowSystemInterface::handleExposeEvent(window(), QRect(QPoint(), geometry().size())); + } +} + +void QMirClientWindow::handleScreenPropertiesChange(MirFormFactor formFactor, float scale) +{ + // Update the scale & form factor native-interface properties for the windows affected + // as there is no convenient way to emit signals for those custom properties on a QScreen + if (formFactor != mFormFactor) { + mFormFactor = formFactor; + Q_EMIT mNativeInterface->windowPropertyChanged(this, QStringLiteral("formFactor")); + } + + if (!qFuzzyCompare(scale, mScale)) { + mScale = scale; + Q_EMIT mNativeInterface->windowPropertyChanged(this, QStringLiteral("scale")); + } +} + +void QMirClientWindow::updateSurfaceState() +{ + QMutexLocker lock(&mMutex); + MirSurfaceState newState = mWindowVisible ? qtWindowStateToMirSurfaceState(mWindowState) : + mir_surface_state_hidden; + qCDebug(mirclient, "updateSurfaceState (window=%p, surfaceState=%s)", window(), mirSurfaceStateToStr(newState)); + if (newState != mSurface->state()) { + mSurface->setState(newState); + } +} + +QString QMirClientWindow::persistentSurfaceId() +{ + return mSurface->persistentSurfaceId(); } diff --git a/src/plugins/platforms/mirclient/qmirclientwindow.h b/src/plugins/platforms/mirclient/qmirclientwindow.h index 025976c130..324e7691ff 100644 --- a/src/plugins/platforms/mirclient/qmirclientwindow.h +++ b/src/plugins/platforms/mirclient/qmirclientwindow.h @@ -1,6 +1,6 @@ /**************************************************************************** ** -** Copyright (C) 2014-2015 Canonical, Ltd. +** Copyright (C) 2014-2016 Canonical, Ltd. ** Contact: https://www.qt.io/licensing/ ** ** This file is part of the plugins of the Qt Toolkit. @@ -45,44 +45,74 @@ #include #include +#include // needed only for MirFormFactor enum + #include -class QMirClientClipboard; +#include + +class QMirClientAppStateController; +class QMirClientDebugExtension; +class QMirClientNativeInterface; class QMirClientInput; class QMirClientScreen; -class QMirClientSurface; -struct MirConnection; +class UbuntuSurface; struct MirSurface; +class MirConnection; class QMirClientWindow : public QObject, public QPlatformWindow { Q_OBJECT public: - QMirClientWindow(QWindow *w, const QSharedPointer &clipboard, QMirClientScreen *screen, - QMirClientInput *input, MirConnection *mirConnection); + QMirClientWindow(QWindow *w, QMirClientInput *input, QMirClientNativeInterface* native, + QMirClientAppStateController *appState, EGLDisplay eglDisplay, + MirConnection *mirConnection, QMirClientDebugExtension *debugExt); virtual ~QMirClientWindow(); // QPlatformWindow methods. WId winId() const override; + QRect geometry() const override; void setGeometry(const QRect&) override; void setWindowState(Qt::WindowState state) override; + void setWindowFlags(Qt::WindowFlags flags) override; void setVisible(bool visible) override; void setWindowTitle(const QString &title) override; void propagateSizeHints() override; + bool isExposed() const override; + + QPoint mapToGlobal(const QPoint &pos) const override; + QSurfaceFormat format() const override; + + // Additional Window properties exposed by NativeInterface + MirFormFactor formFactor() const { return mFormFactor; } + float scale() const { return mScale; } // New methods. void *eglSurface() const; MirSurface *mirSurface() const; void handleSurfaceResized(int width, int height); - void handleSurfaceFocused(); + void handleSurfaceExposeChange(bool exposed); + void handleSurfaceFocusChanged(bool focused); + void handleSurfaceVisibilityChanged(bool visible); + void handleSurfaceStateChanged(Qt::WindowState state); void onSwapBuffersDone(); + void handleScreenPropertiesChange(MirFormFactor formFactor, float scale); + QString persistentSurfaceId(); private: - void updatePanelHeightHack(Qt::WindowState); + void updateSurfaceState(); mutable QMutex mMutex; const WId mId; - const QSharedPointer mClipboard; - std::unique_ptr mSurface; + Qt::WindowState mWindowState; + Qt::WindowFlags mWindowFlags; + bool mWindowVisible; + bool mWindowExposed; + QMirClientAppStateController *mAppStateController; + QMirClientDebugExtension *mDebugExtention; + QMirClientNativeInterface *mNativeInterface; + std::unique_ptr mSurface; + float mScale; + MirFormFactor mFormFactor; }; #endif // QMIRCLIENTWINDOW_H -- cgit v1.2.3