diff --git a/src/plugins/platforms/mirclient/mirclient.pro b/src/plugins/platforms/mirclient/mirclient.pro index 033ce579b9..0851e8d719 100644 --- a/src/plugins/platforms/mirclient/mirclient.pro +++ b/src/plugins/platforms/mirclient/mirclient.pro @@ -21,6 +21,7 @@ PKGCONFIG += egl mirclient ubuntu-platform-api SOURCES = \ qmirclientbackingstore.cpp \ qmirclientclipboard.cpp \ + qmirclientcursor.cpp \ qmirclientglcontext.cpp \ qmirclientinput.cpp \ qmirclientintegration.cpp \ @@ -34,6 +35,7 @@ SOURCES = \ HEADERS = \ qmirclientbackingstore.h \ qmirclientclipboard.h \ + qmirclientcursor.h \ qmirclientglcontext.h \ qmirclientinput.h \ qmirclientintegration.h \ diff --git a/src/plugins/platforms/mirclient/qmirclientclipboard.cpp b/src/plugins/platforms/mirclient/qmirclientclipboard.cpp index aa2ddf2103..53246f66c0 100644 --- a/src/plugins/platforms/mirclient/qmirclientclipboard.cpp +++ b/src/plugins/platforms/mirclient/qmirclientclipboard.cpp @@ -87,12 +87,12 @@ void QMirClientClipboard::requestDBusClipboardContents() if (!mPendingGetContentsCall.isNull()) return; - QDBusPendingCall pendingCall = mDBusClipboard->asyncCall("GetContents"); + QDBusPendingCall pendingCall = mDBusClipboard->asyncCall(QStringLiteral("GetContents")); mPendingGetContentsCall = new QDBusPendingCallWatcher(pendingCall, this); - QObject::connect(mPendingGetContentsCall.data(), SIGNAL(finished(QDBusPendingCallWatcher*)), - this, SLOT(onDBusClipboardGetContentsFinished(QDBusPendingCallWatcher*))); + QObject::connect(mPendingGetContentsCall.data(), &QDBusPendingCallWatcher::finished, + this, &QMirClientClipboard::onDBusClipboardGetContentsFinished); } void QMirClientClipboard::onDBusClipboardGetContentsFinished(QDBusPendingCallWatcher* call) @@ -143,18 +143,18 @@ void QMirClientClipboard::setupDBus() QDBusConnection dbusConnection = QDBusConnection::sessionBus(); bool ok = dbusConnection.connect( - "com.canonical.QtMir", - "/com/canonical/QtMir/Clipboard", - "com.canonical.QtMir.Clipboard", - "ContentsChanged", + QStringLiteral("com.canonical.QtMir"), + QStringLiteral("/com/canonical/QtMir/Clipboard"), + QStringLiteral("com.canonical.QtMir.Clipboard"), + QStringLiteral("ContentsChanged"), this, SLOT(updateMimeData(QByteArray))); if (!ok) { qCritical("QMirClientClipboard - Failed to connect to ContentsChanged signal form the D-Bus system clipboard."); } - mDBusClipboard = new QDBusInterface("com.canonical.QtMir", - "/com/canonical/QtMir/Clipboard", - "com.canonical.QtMir.Clipboard", + mDBusClipboard = new QDBusInterface(QStringLiteral("com.canonical.QtMir"), + QStringLiteral("/com/canonical/QtMir/Clipboard"), + QStringLiteral("com.canonical.QtMir.Clipboard"), dbusConnection); mDBusSetupDone = true; @@ -162,6 +162,8 @@ void QMirClientClipboard::setupDBus() QByteArray QMirClientClipboard::serializeMimeData(QMimeData *mimeData) 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)); @@ -180,12 +182,13 @@ QByteArray QMirClientClipboard::serializeMimeData(QMimeData *mimeData) const 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 = mimeData->data(formats[i]).size(); + const int dataSize = data.size(); memcpy(&buffer[formatOffset], formats[i].toLatin1().data(), formatSize); - memcpy(&buffer[dataOffset], mimeData->data(formats[i]).data(), dataSize); + memcpy(&buffer[dataOffset], data.data(), dataSize); header[i*4+1] = formatOffset; header[i*4+2] = formatSize; header[i*4+3] = dataOffset; @@ -265,13 +268,15 @@ void QMirClientClipboard::setMimeData(QMimeData* mimeData, QClipboard::Mode mode delete mPendingGetContentsCall.data(); } - QByteArray serializedMimeData = serializeMimeData(mimeData); - if (!serializedMimeData.isEmpty()) { - setDBusClipboardContents(serializedMimeData); - } + if (mimeData != nullptr) { + QByteArray serializedMimeData = serializeMimeData(mimeData); + if (!serializedMimeData.isEmpty()) { + setDBusClipboardContents(serializedMimeData); + } - mMimeData = mimeData; - emitChanged(QClipboard::Clipboard); + mMimeData = mimeData; + emitChanged(QClipboard::Clipboard); + } } bool QMirClientClipboard::supportsMode(QClipboard::Mode mode) const @@ -287,6 +292,10 @@ bool QMirClientClipboard::ownsMode(QClipboard::Mode mode) const void QMirClientClipboard::setDBusClipboardContents(const QByteArray &clipboardContents) { + if (!mDBusSetupDone) { + setupDBus(); + } + if (!mPendingSetContentsCall.isNull()) { // Ignore any previous set call as we are going to overwrite it anyway QObject::disconnect(mPendingSetContentsCall.data(), 0, this, 0); @@ -296,10 +305,10 @@ void QMirClientClipboard::setDBusClipboardContents(const QByteArray &clipboardCo delete mPendingSetContentsCall.data(); } - QDBusPendingCall pendingCall = mDBusClipboard->asyncCall("SetContents", clipboardContents); + QDBusPendingCall pendingCall = mDBusClipboard->asyncCall(QStringLiteral("SetContents"), clipboardContents); mPendingSetContentsCall = new QDBusPendingCallWatcher(pendingCall, this); - QObject::connect(mPendingSetContentsCall.data(), SIGNAL(finished(QDBusPendingCallWatcher*)), - this, SLOT(onDBusClipboardSetContentsFinished(QDBusPendingCallWatcher*))); + QObject::connect(mPendingSetContentsCall.data(), &QDBusPendingCallWatcher::finished, + this, &QMirClientClipboard::onDBusClipboardSetContentsFinished); } diff --git a/src/plugins/platforms/mirclient/qmirclientcursor.cpp b/src/plugins/platforms/mirclient/qmirclientcursor.cpp new file mode 100644 index 0000000000..1d6ec8391e --- /dev/null +++ b/src/plugins/platforms/mirclient/qmirclientcursor.cpp @@ -0,0 +1,201 @@ +/**************************************************************************** +** +** Copyright (C) 2015 Canonical, Ltd. +** Contact: http://www.qt.io/licensing/ +** +** This file is part of the plugins of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL3$ +** 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 http://www.qt.io/terms-conditions. For further +** information use the contact form at http://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.LGPLv3 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.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 later as published by the Free +** Software Foundation and appearing in the file LICENSE.GPL included in +** the packaging of this file. Please review the following information to +** ensure the GNU General Public License version 2.0 requirements will be +** met: http://www.gnu.org/licenses/gpl-2.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + + +#include "qmirclientcursor.h" + +#include "qmirclientlogging.h" +#include "qmirclientwindow.h" + +#include + +QMirClientCursor::QMirClientCursor(MirConnection *connection) + : mConnection(connection) +{ + mShapeToCursorName[Qt::ArrowCursor] = "left_ptr"; + 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::ForbiddenCursor] = "forbidden"; + mShapeToCursorName[Qt::WhatsThisCursor] = "whats_this"; + mShapeToCursorName[Qt::BusyCursor] = "left_ptr_watch"; + mShapeToCursorName[Qt::OpenHandCursor] = "openhand"; + mShapeToCursorName[Qt::ClosedHandCursor] = "closedhand"; + 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) { + case Qt::ArrowCursor: + return "Arrow"; + case Qt::UpArrowCursor: + return "UpArrow"; + case Qt::CrossCursor: + return "Cross"; + case Qt::WaitCursor: + return "Wait"; + case Qt::IBeamCursor: + return "IBeam"; + case Qt::SizeVerCursor: + return "SizeVer"; + case Qt::SizeHorCursor: + return "SizeHor"; + case Qt::SizeBDiagCursor: + return "SizeBDiag"; + case Qt::SizeFDiagCursor: + return "SizeFDiag"; + case Qt::SizeAllCursor: + return "SizeAll"; + case Qt::BlankCursor: + return "Blank"; + case Qt::SplitVCursor: + return "SplitV"; + case Qt::SplitHCursor: + return "SplitH"; + case Qt::PointingHandCursor: + return "PointingHand"; + case Qt::ForbiddenCursor: + return "Forbidden"; + case Qt::WhatsThisCursor: + return "WhatsThis"; + case Qt::BusyCursor: + return "Busy"; + case Qt::OpenHandCursor: + return "OpenHand"; + case Qt::ClosedHandCursor: + return "ClosedHand"; + case Qt::DragCopyCursor: + return "DragCopy"; + case Qt::DragMoveCursor: + return "DragMove"; + case Qt::DragLinkCursor: + return "DragLink"; + case Qt::BitmapCursor: + return "Bitmap"; + default: + return "???"; + } +} +#endif // !defined(QT_NO_DEBUG) +} // anonymous namespace + +void QMirClientCursor::changeCursor(QCursor *windowCursor, QWindow *window) +{ + if (!window) { + return; + } + + MirSurface *surface = static_cast(window->handle())->mirSurface(); + + if (!surface) { + return; + } + + + if (windowCursor) { + DLOG("[ubuntumirclient QPA] changeCursor shape=%s, window=%p\n", qtCursorShapeToStr(windowCursor->shape()), window); + if (!windowCursor->pixmap().isNull()) { + configureMirCursorWithPixmapQCursor(surface, *windowCursor); + } else if (windowCursor->shape() == Qt::BitmapCursor) { + // TODO: Implement bitmap cursor support + applyDefaultCursorConfiguration(surface); + } else { + const auto &cursorName = mShapeToCursorName.value(windowCursor->shape(), QByteArray("left_ptr")); + auto cursorConfiguration = mir_cursor_configuration_from_name(cursorName.data()); + mir_surface_configure_cursor(surface, cursorConfiguration); + mir_cursor_configuration_destroy(cursorConfiguration); + } + } else { + applyDefaultCursorConfiguration(surface); + } + +} + +void QMirClientCursor::configureMirCursorWithPixmapQCursor(MirSurface *surface, QCursor &cursor) +{ + QImage image = cursor.pixmap().toImage(); + + if (image.format() != QImage::Format_ARGB32) { + image.convertToFormat(QImage::Format_ARGB32); + } + + MirBufferStream *bufferStream = mir_connection_create_buffer_stream_sync(mConnection, + image.width(), image.height(), mir_pixel_format_argb_8888, mir_buffer_usage_software); + + { + MirGraphicsRegion region; + mir_buffer_stream_get_graphics_region(bufferStream, ®ion); + + char *regionLine = region.vaddr; + Q_ASSERT(image.bytesPerLine() <= region.stride); + for (int i = 0; i < image.height(); ++i) { + memcpy(regionLine, image.scanLine(i), image.bytesPerLine()); + regionLine += region.stride; + } + } + + mir_buffer_stream_swap_buffers_sync(bufferStream); + + { + auto configuration = mir_cursor_configuration_from_buffer_stream(bufferStream, cursor.hotSpot().x(), cursor.hotSpot().y()); + mir_surface_configure_cursor(surface, configuration); + mir_cursor_configuration_destroy(configuration); + } + + mir_buffer_stream_release_sync(bufferStream); +} + +void QMirClientCursor::applyDefaultCursorConfiguration(MirSurface *surface) +{ + auto cursorConfiguration = mir_cursor_configuration_from_name("left_ptr"); + mir_surface_configure_cursor(surface, cursorConfiguration); + mir_cursor_configuration_destroy(cursorConfiguration); +} diff --git a/src/plugins/platforms/mirclient/qmirclientcursor.h b/src/plugins/platforms/mirclient/qmirclientcursor.h new file mode 100644 index 0000000000..8bb151ddda --- /dev/null +++ b/src/plugins/platforms/mirclient/qmirclientcursor.h @@ -0,0 +1,61 @@ +/**************************************************************************** +** +** Copyright (C) 2015 Canonical, Ltd. +** Contact: http://www.qt.io/licensing/ +** +** This file is part of the plugins of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL3$ +** 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 http://www.qt.io/terms-conditions. For further +** information use the contact form at http://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.LGPLv3 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.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 later as published by the Free +** Software Foundation and appearing in the file LICENSE.GPL included in +** the packaging of this file. Please review the following information to +** ensure the GNU General Public License version 2.0 requirements will be +** met: http://www.gnu.org/licenses/gpl-2.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + + +#ifndef QMIRCLIENTCURSOR_H +#define QMIRCLIENTCURSOR_H + +#include + +#include +#include + +struct MirConnection; +struct MirSurface; + +class QMirClientCursor : public QPlatformCursor +{ +public: + QMirClientCursor(MirConnection *connection); + void changeCursor(QCursor *windowCursor, QWindow *window) override; +private: + void configureMirCursorWithPixmapQCursor(MirSurface *surface, QCursor &cursor); + void applyDefaultCursorConfiguration(MirSurface *surface); + QMap mShapeToCursorName; + MirConnection *mConnection; +}; + +#endif // QMIRCLIENTCURSOR_H diff --git a/src/plugins/platforms/mirclient/qmirclientglcontext.cpp b/src/plugins/platforms/mirclient/qmirclientglcontext.cpp index bfba5051e5..01db3b8d61 100644 --- a/src/plugins/platforms/mirclient/qmirclientglcontext.cpp +++ b/src/plugins/platforms/mirclient/qmirclientglcontext.cpp @@ -130,19 +130,7 @@ void QMirClientOpenGLContext::swapBuffers(QPlatformSurface* surface) ASSERT(eglSwapBuffers(mEglDisplay, eglSurface) == EGL_TRUE); #endif - // "Technique" copied from mir, in examples/eglapp.c around line 96 - EGLint newBufferWidth = -1; - EGLint newBufferHeight = -1; - /* - * Querying the surface (actually the current buffer) dimensions here is - * the only truly safe way to be sure that the dimensions we think we - * have are those of the buffer being rendered to. But this should be - * improved in future; https://bugs.launchpad.net/mir/+bug/1194384 - */ - eglQuerySurface(mEglDisplay, eglSurface, EGL_WIDTH, &newBufferWidth); - eglQuerySurface(mEglDisplay, eglSurface, EGL_HEIGHT, &newBufferHeight); - - ubuntuWindow->onBuffersSwapped_threadSafe(newBufferWidth, newBufferHeight); + ubuntuWindow->onSwapBuffersDone(); } void (*QMirClientOpenGLContext::getProcAddress(const QByteArray& procName)) () diff --git a/src/plugins/platforms/mirclient/qmirclientglcontext.h b/src/plugins/platforms/mirclient/qmirclientglcontext.h index cc40298259..29c196ce5c 100644 --- a/src/plugins/platforms/mirclient/qmirclientglcontext.h +++ b/src/plugins/platforms/mirclient/qmirclientglcontext.h @@ -53,7 +53,7 @@ public: bool makeCurrent(QPlatformSurface* surface) override; void doneCurrent() override; bool isValid() const override { return mEglContext != EGL_NO_CONTEXT; } - void (*getProcAddress(const QByteArray& procName)) (); + void (*getProcAddress(const QByteArray& procName)) () override; EGLContext eglContext() const { return mEglContext; } diff --git a/src/plugins/platforms/mirclient/qmirclientinput.cpp b/src/plugins/platforms/mirclient/qmirclientinput.cpp index 56bc21f420..addeda634c 100644 --- a/src/plugins/platforms/mirclient/qmirclientinput.cpp +++ b/src/plugins/platforms/mirclient/qmirclientinput.cpp @@ -163,6 +163,7 @@ QMirClientInput::QMirClientInput(QMirClientClientIntegration* integration) , mEventFilterType(static_cast( integration->nativeInterface())->genericEventFilterType()) , mEventType(static_cast(QEvent::registerEventType())) + , mLastFocusedWindow(nullptr) { // Initialize touch device. mTouchDevice = new QTouchDevice; @@ -234,7 +235,7 @@ void QMirClientInput::customEvent(QEvent* event) switch (mir_event_get_type(nativeEvent)) { case mir_event_type_input: - dispatchInputEvent(ubuntuEvent->window->window(), mir_event_get_input_event(nativeEvent)); + dispatchInputEvent(ubuntuEvent->window, mir_event_get_input_event(nativeEvent)); break; case mir_event_type_resize: { @@ -246,7 +247,7 @@ void QMirClientInput::customEvent(QEvent* event) mir_resize_event_get_width(resizeEvent), mir_resize_event_get_height(resizeEvent)); - ubuntuEvent->window->handleSurfaceResize(mir_resize_event_get_width(resizeEvent), + ubuntuEvent->window->handleSurfaceResized(mir_resize_event_get_width(resizeEvent), mir_resize_event_get_height(resizeEvent)); break; } @@ -254,8 +255,24 @@ void QMirClientInput::customEvent(QEvent* event) { auto surfaceEvent = mir_event_get_surface_event(nativeEvent); if (mir_surface_event_get_attribute(surfaceEvent) == mir_surface_attrib_focus) { - ubuntuEvent->window->handleSurfaceFocusChange(mir_surface_event_get_attribute_value(surfaceEvent) == - mir_surface_focused); + 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); + } } break; } @@ -274,6 +291,17 @@ 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( platformWindow, event, mEventType)); @@ -284,7 +312,7 @@ void QMirClientInput::postEvent(QMirClientWindow *platformWindow, const MirEvent } } -void QMirClientInput::dispatchInputEvent(QWindow *window, const MirInputEvent *ev) +void QMirClientInput::dispatchInputEvent(QMirClientWindow *window, const MirInputEvent *ev) { switch (mir_input_event_get_type(ev)) { @@ -302,7 +330,7 @@ void QMirClientInput::dispatchInputEvent(QWindow *window, const MirInputEvent *e } } -void QMirClientInput::dispatchTouchEvent(QWindow *window, const MirInputEvent *ev) +void QMirClientInput::dispatchTouchEvent(QMirClientWindow *window, const MirInputEvent *ev) { const MirTouchEvent *tev = mir_input_event_get_touch_event(ev); @@ -333,6 +361,7 @@ void QMirClientInput::dispatchTouchEvent(QWindow *window, const MirInputEvent *e switch (touch_action) { case mir_touch_action_down: + mLastFocusedWindow = window; touchPoint.state = Qt::TouchPointPressed; break; case mir_touch_action_up: @@ -347,7 +376,7 @@ void QMirClientInput::dispatchTouchEvent(QWindow *window, const MirInputEvent *e } ulong timestamp = mir_input_event_get_event_time(ev) / 1000000; - QWindowSystemInterface::handleTouchEvent(window, timestamp, + QWindowSystemInterface::handleTouchEvent(window->window(), timestamp, mTouchDevice, touchPoints); } @@ -390,7 +419,7 @@ Qt::KeyboardModifiers qt_modifiers_from_mir(MirInputEventModifiers modifiers) } } -void QMirClientInput::dispatchKeyEvent(QWindow *window, const MirInputEvent *event) +void QMirClientInput::dispatchKeyEvent(QMirClientWindow *window, const MirInputEvent *event) { const MirKeyboardEvent *key_event = mir_input_event_get_keyboard_event(event); @@ -404,6 +433,9 @@ void QMirClientInput::dispatchKeyEvent(QWindow *window, const MirInputEvent *eve QEvent::Type keyType = action == mir_keyboard_action_up ? QEvent::KeyRelease : QEvent::KeyPress; + if (action == mir_keyboard_action_down) + mLastFocusedWindow = window; + char s[2]; int sym = translateKeysym(xk_sym, s, sizeof(s)); QString text = QString::fromLatin1(s); @@ -420,7 +452,7 @@ void QMirClientInput::dispatchKeyEvent(QWindow *window, const MirInputEvent *eve } } - QWindowSystemInterface::handleKeyEvent(window, timestamp, keyType, sym, modifiers, text, is_auto_rep); + QWindowSystemInterface::handleKeyEvent(window->window(), timestamp, keyType, sym, modifiers, text, is_auto_rep); } namespace @@ -433,27 +465,54 @@ Qt::MouseButtons extract_buttons(const MirPointerEvent *pev) if (mir_pointer_event_button_state(pev, mir_pointer_button_secondary)) buttons |= Qt::RightButton; if (mir_pointer_event_button_state(pev, mir_pointer_button_tertiary)) - buttons |= Qt::MidButton; + buttons |= Qt::MiddleButton; + if (mir_pointer_event_button_state(pev, mir_pointer_button_back)) + buttons |= Qt::BackButton; + if (mir_pointer_event_button_state(pev, mir_pointer_button_forward)) + buttons |= Qt::ForwardButton; - // TODO: Should mir back and forward buttons exist? - // should they be Qt::X button 1 and 2? return buttons; } } -void QMirClientInput::dispatchPointerEvent(QWindow *window, const MirInputEvent *ev) +void QMirClientInput::dispatchPointerEvent(QMirClientWindow *platformWindow, const MirInputEvent *ev) { + auto window = platformWindow->window(); auto timestamp = mir_input_event_get_event_time(ev) / 1000000; 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)); - auto buttons = extract_buttons(pev); - auto local_point = QPointF(mir_pointer_event_axis_value(pev, mir_pointer_axis_x), - mir_pointer_event_axis_value(pev, mir_pointer_axis_y)); + switch (action) { + case mir_pointer_action_button_up: + case mir_pointer_action_button_down: + case mir_pointer_action_motion: + { + const float hDelta = mir_pointer_event_axis_value(pev, mir_pointer_axis_hscroll); + const float vDelta = mir_pointer_event_axis_value(pev, mir_pointer_axis_vscroll); - QWindowSystemInterface::handleMouseEvent(window, timestamp, local_point, local_point /* Should we omit global point instead? */, - buttons, modifiers); + if (hDelta != 0 || vDelta != 0) { + const QPoint angleDelta = QPoint(hDelta * 15, vDelta * 15); + QWindowSystemInterface::handleWheelEvent(window, timestamp, localPoint, window->position() + localPoint, + QPoint(), angleDelta, modifiers, Qt::ScrollUpdate); + } + auto buttons = extract_buttons(pev); + QWindowSystemInterface::handleMouseEvent(window, timestamp, localPoint, window->position() + localPoint /* Should we omit global point instead? */, + buttons, modifiers); + break; + } + case mir_pointer_action_enter: + QWindowSystemInterface::handleEnterEvent(window, localPoint, window->position() + localPoint); + break; + case mir_pointer_action_leave: + QWindowSystemInterface::handleLeaveEvent(window); + break; + default: + DLOG("Unrecognized pointer event"); + } } #if (LOG_EVENTS != 0) diff --git a/src/plugins/platforms/mirclient/qmirclientinput.h b/src/plugins/platforms/mirclient/qmirclientinput.h index c987d18c12..3ed887419e 100644 --- a/src/plugins/platforms/mirclient/qmirclientinput.h +++ b/src/plugins/platforms/mirclient/qmirclientinput.h @@ -40,6 +40,7 @@ // Qt #include +#include #include @@ -59,12 +60,13 @@ public: void postEvent(QMirClientWindow* window, const MirEvent *event); QMirClientClientIntegration* integration() const { return mIntegration; } + QMirClientWindow *lastFocusedWindow() const {return mLastFocusedWindow; } protected: - void dispatchKeyEvent(QWindow *window, const MirInputEvent *event); - void dispatchPointerEvent(QWindow *window, const MirInputEvent *event); - void dispatchTouchEvent(QWindow *window, const MirInputEvent *event); - void dispatchInputEvent(QWindow *window, const MirInputEvent *event); + void dispatchKeyEvent(QMirClientWindow *window, const MirInputEvent *event); + void dispatchPointerEvent(QMirClientWindow *window, const MirInputEvent *event); + void dispatchTouchEvent(QMirClientWindow *window, const MirInputEvent *event); + void dispatchInputEvent(QMirClientWindow *window, const MirInputEvent *event); void dispatchOrientationEvent(QWindow* window, const MirOrientationEvent *event); @@ -73,6 +75,9 @@ private: QTouchDevice* mTouchDevice; const QByteArray mEventFilterType; const QEvent::Type mEventType; + + QMirClientWindow *mLastFocusedWindow; + QAtomicInt mPendingFocusGainedEvents; }; #endif // QMIRCLIENTINPUT_H diff --git a/src/plugins/platforms/mirclient/qmirclientintegration.cpp b/src/plugins/platforms/mirclient/qmirclientintegration.cpp index a234f4eac6..4b2572ce0d 100644 --- a/src/plugins/platforms/mirclient/qmirclientintegration.cpp +++ b/src/plugins/platforms/mirclient/qmirclientintegration.cpp @@ -35,6 +35,18 @@ ****************************************************************************/ +// Local +#include "qmirclientintegration.h" +#include "qmirclientbackingstore.h" +#include "qmirclientclipboard.h" +#include "qmirclientglcontext.h" +#include "qmirclientinput.h" +#include "qmirclientlogging.h" +#include "qmirclientnativeinterface.h" +#include "qmirclientscreen.h" +#include "qmirclienttheme.h" +#include "qmirclientwindow.h" + // Qt #include #include @@ -45,18 +57,6 @@ #include #include -// Local -#include "qmirclientbackingstore.h" -#include "qmirclientclipboard.h" -#include "qmirclientglcontext.h" -#include "qmirclientinput.h" -#include "qmirclientintegration.h" -#include "qmirclientlogging.h" -#include "qmirclientnativeinterface.h" -#include "qmirclientscreen.h" -#include "qmirclienttheme.h" -#include "qmirclientwindow.h" - // platform-api #include #include @@ -67,8 +67,11 @@ static void resumedCallback(const UApplicationOptions *options, void* context) Q_UNUSED(options) Q_UNUSED(context) DASSERT(context != NULL); - QCoreApplication::postEvent(QCoreApplication::instance(), - new QEvent(QEvent::ApplicationActivate)); + if (qGuiApp->focusWindow()) { + QWindowSystemInterface::handleApplicationStateChanged(Qt::ApplicationActive); + } else { + QWindowSystemInterface::handleApplicationStateChanged(Qt::ApplicationInactive); + } } static void aboutToStopCallback(UApplicationArchive *archive, void* context) @@ -76,9 +79,13 @@ static void aboutToStopCallback(UApplicationArchive *archive, void* context) Q_UNUSED(archive) DASSERT(context != NULL); QMirClientClientIntegration* integration = static_cast(context); - integration->inputContext()->hideInputPanel(); - QCoreApplication::postEvent(QCoreApplication::instance(), - new QEvent(QEvent::ApplicationDeactivate)); + QPlatformInputContext *inputContext = integration->inputContext(); + if (inputContext) { + inputContext->hideInputPanel(); + } else { + qWarning("QMirClientClientIntegration aboutToStopCallback(): no input context"); + } + QWindowSystemInterface::handleApplicationStateChanged(Qt::ApplicationSuspended); } QMirClientClientIntegration::QMirClientClientIntegration() @@ -100,6 +107,8 @@ 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)); + // Create default screen. mScreen = new QMirClientScreen(u_application_instance_get_mir_connection(mInstance)); screenAdded(mScreen); @@ -176,10 +185,8 @@ QPlatformWindow* QMirClientClientIntegration::createPlatformWindow(QWindow* wind QPlatformWindow* QMirClientClientIntegration::createPlatformWindow(QWindow* window) { - QPlatformWindow* platformWindow = new QMirClientWindow( - window, mClipboard, static_cast(mScreen), mInput, u_application_instance_get_mir_connection(mInstance)); - platformWindow->requestActivateWindow(); - return platformWindow; + return new QMirClientWindow(window, mClipboard, static_cast(mScreen), + mInput, u_application_instance_get_mir_connection(mInstance)); } bool QMirClientClientIntegration::hasCapability(QPlatformIntegration::Capability cap) const @@ -187,11 +194,12 @@ bool QMirClientClientIntegration::hasCapability(QPlatformIntegration::Capability switch (cap) { case ThreadedPixmaps: return true; - break; case OpenGL: return true; - break; + + case ApplicationState: + return true; case ThreadedOpenGL: if (qEnvironmentVariableIsEmpty("QTUBUNTU_NO_THREADED_OPENGL")) { @@ -200,8 +208,9 @@ bool QMirClientClientIntegration::hasCapability(QPlatformIntegration::Capability DLOG("ubuntumirclient: disabled threaded OpenGL"); return false; } - break; - + case MultipleWindows: + case NonFullScreenWindows: + return true; default: return QPlatformIntegration::hasCapability(cap); } @@ -262,3 +271,8 @@ QPlatformClipboard* QMirClientClientIntegration::clipboard() const { return mClipboard.data(); } + +QPlatformNativeInterface* QMirClientClientIntegration::nativeInterface() const +{ + return mNativeInterface; +} diff --git a/src/plugins/platforms/mirclient/qmirclientintegration.h b/src/plugins/platforms/mirclient/qmirclientintegration.h index 2960209691..e41cbe2cee 100644 --- a/src/plugins/platforms/mirclient/qmirclientintegration.h +++ b/src/plugins/platforms/mirclient/qmirclientintegration.h @@ -49,6 +49,7 @@ class QMirClientClipboard; class QMirClientInput; +class QMirClientNativeInterface; class QMirClientScreen; class QMirClientClientIntegration : public QPlatformIntegration { @@ -59,7 +60,7 @@ public: // QPlatformIntegration methods. bool hasCapability(QPlatformIntegration::Capability cap) const override; QAbstractEventDispatcher *createEventDispatcher() const override; - QPlatformNativeInterface* nativeInterface() const override { return mNativeInterface; } + QPlatformNativeInterface* nativeInterface() const override; QPlatformBackingStore* createPlatformBackingStore(QWindow* window) const override; QPlatformOpenGLContext* createPlatformOpenGLContext(QOpenGLContext* context) const override; QPlatformFontDatabase* fontDatabase() const override { return mFontDb; } @@ -79,7 +80,7 @@ private: void setupOptions(); void setupDescription(); - QPlatformNativeInterface* mNativeInterface; + QMirClientNativeInterface* mNativeInterface; QPlatformFontDatabase* mFontDb; QMirClientPlatformServices* mServices; diff --git a/src/plugins/platforms/mirclient/qmirclientnativeinterface.cpp b/src/plugins/platforms/mirclient/qmirclientnativeinterface.cpp index a0bb932df3..1b4c20153b 100644 --- a/src/plugins/platforms/mirclient/qmirclientnativeinterface.cpp +++ b/src/plugins/platforms/mirclient/qmirclientnativeinterface.cpp @@ -35,17 +35,17 @@ ****************************************************************************/ +// Local +#include "qmirclientnativeinterface.h" +#include "qmirclientscreen.h" +#include "qmirclientglcontext.h" + // Qt #include #include #include #include -// Local -#include "qmirclientnativeinterface.h" -#include "qmirclientscreen.h" -#include "qmirclientglcontext.h" - class QMirClientResourceMap : public QMap { public: @@ -55,6 +55,7 @@ public: insert("eglcontext", QMirClientNativeInterface::EglContext); insert("nativeorientation", QMirClientNativeInterface::NativeOrientation); insert("display", QMirClientNativeInterface::Display); + insert("mirconnection", QMirClientNativeInterface::MirConnection); } }; @@ -63,6 +64,7 @@ Q_GLOBAL_STATIC(QMirClientResourceMap, ubuntuResourceMap) QMirClientNativeInterface::QMirClientNativeInterface() : mGenericEventFilterType(QByteArrayLiteral("Event")) , mNativeOrientation(nullptr) + , mMirConnection(nullptr) { } @@ -72,6 +74,23 @@ QMirClientNativeInterface::~QMirClientNativeInterface() mNativeOrientation = nullptr; } +void* QMirClientNativeInterface::nativeResourceForIntegration(const QByteArray &resourceString) +{ + const QByteArray lowerCaseResource = resourceString.toLower(); + + if (!ubuntuResourceMap()->contains(lowerCaseResource)) { + return nullptr; + } + + const ResourceType resourceType = ubuntuResourceMap()->value(lowerCaseResource); + + if (resourceType == QMirClientNativeInterface::MirConnection) { + return mMirConnection; + } else { + return nullptr; + } +} + void* QMirClientNativeInterface::nativeResourceForContext( const QByteArray& resourceString, QOpenGLContext* context) { diff --git a/src/plugins/platforms/mirclient/qmirclientnativeinterface.h b/src/plugins/platforms/mirclient/qmirclientnativeinterface.h index 84f03bb915..7df646e73a 100644 --- a/src/plugins/platforms/mirclient/qmirclientnativeinterface.h +++ b/src/plugins/platforms/mirclient/qmirclientnativeinterface.h @@ -42,12 +42,13 @@ class QMirClientNativeInterface : public QPlatformNativeInterface { public: - enum ResourceType { EglDisplay, EglContext, NativeOrientation, Display }; + enum ResourceType { EglDisplay, EglContext, NativeOrientation, Display, MirConnection }; QMirClientNativeInterface(); ~QMirClientNativeInterface(); // QPlatformNativeInterface methods. + void* nativeResourceForIntegration(const QByteArray &resource) override; void* nativeResourceForContext(const QByteArray& resourceString, QOpenGLContext* context) override; void* nativeResourceForWindow(const QByteArray& resourceString, @@ -57,10 +58,12 @@ public: // New methods. const QByteArray& genericEventFilterType() const { return mGenericEventFilterType; } + void setMirConnection(void *mirConnection) { mMirConnection = mirConnection; } private: const QByteArray mGenericEventFilterType; Qt::ScreenOrientation* mNativeOrientation; + void *mMirConnection; }; #endif // QMIRCLIENTNATIVEINTERFACE_H diff --git a/src/plugins/platforms/mirclient/qmirclientorientationchangeevent_p.h b/src/plugins/platforms/mirclient/qmirclientorientationchangeevent_p.h index 24d7307faa..2a1ed9c09f 100644 --- a/src/plugins/platforms/mirclient/qmirclientorientationchangeevent_p.h +++ b/src/plugins/platforms/mirclient/qmirclientorientationchangeevent_p.h @@ -43,15 +43,7 @@ class OrientationChangeEvent : public QEvent { public: - enum Orientation { - Undefined = 0, - TopUp, - TopDown, - LeftUp, - RightUp, - FaceUp, - FaceDown - }; + enum Orientation { TopUp, LeftUp, TopDown, RightUp }; OrientationChangeEvent(QEvent::Type type, Orientation orientation) : QEvent(type) diff --git a/src/plugins/platforms/mirclient/qmirclientplugin.cpp b/src/plugins/platforms/mirclient/qmirclientplugin.cpp index 203a1cbfd8..75561f7fd3 100644 --- a/src/plugins/platforms/mirclient/qmirclientplugin.cpp +++ b/src/plugins/platforms/mirclient/qmirclientplugin.cpp @@ -41,14 +41,14 @@ QStringList QMirClientIntegrationPlugin::keys() const { QStringList list; - list << "mirclient"; + list << QStringLiteral("mirclient"); return list; } QPlatformIntegration* QMirClientIntegrationPlugin::create(const QString &system, const QStringList &) { - if (system.toLower() == "mirclient") { + if (system.toLower() == QLatin1String("mirclient")) { return new QMirClientClientIntegration; } else { return 0; diff --git a/src/plugins/platforms/mirclient/qmirclientscreen.cpp b/src/plugins/platforms/mirclient/qmirclientscreen.cpp index 5c4b1cd0d6..3eb01f816a 100644 --- a/src/plugins/platforms/mirclient/qmirclientscreen.cpp +++ b/src/plugins/platforms/mirclient/qmirclientscreen.cpp @@ -1,6 +1,6 @@ /**************************************************************************** ** -** Copyright (C) 2014 Canonical, Ltd. +** Copyright (C) 2014-2015 Canonical, Ltd. ** Contact: http://www.qt.io/licensing/ ** ** This file is part of the plugins of the Qt Toolkit. @@ -35,6 +35,11 @@ ****************************************************************************/ +// local +#include "qmirclientscreen.h" +#include "qmirclientlogging.h" +#include "qmirclientorientationchangeevent_p.h" + #include // Qt @@ -45,12 +50,7 @@ #include #include -// local -#include "qmirclientscreen.h" -#include "qmirclientlogging.h" -#include "qmirclientorientationchangeevent_p.h" - -#include "memory" +#include static const int kSwapInterval = 1; @@ -149,9 +149,11 @@ static const MirDisplayOutput *find_active_output( QMirClientScreen::QMirClientScreen(MirConnection *connection) : mFormat(QImage::Format_RGB32) , mDepth(32) + , mOutputId(0) , mSurfaceFormat() , mEglDisplay(EGL_NO_DISPLAY) , mEglConfig(nullptr) + , mCursor(connection) { // Initialize EGL. ASSERT(eglBindAPI(EGL_OPENGL_ES_API) == EGL_TRUE); @@ -203,6 +205,11 @@ QMirClientScreen::QMirClientScreen(MirConnection *connection) 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; diff --git a/src/plugins/platforms/mirclient/qmirclientscreen.h b/src/plugins/platforms/mirclient/qmirclientscreen.h index 5d9325354f..a6b4f442da 100644 --- a/src/plugins/platforms/mirclient/qmirclientscreen.h +++ b/src/plugins/platforms/mirclient/qmirclientscreen.h @@ -1,6 +1,6 @@ /**************************************************************************** ** -** Copyright (C) 2014 Canonical, Ltd. +** Copyright (C) 2014-2015 Canonical, Ltd. ** Contact: http://www.qt.io/licensing/ ** ** This file is part of the plugins of the Qt Toolkit. @@ -42,6 +42,8 @@ #include #include +#include "qmirclientcursor.h" + struct MirConnection; class QMirClientScreen : public QObject, public QPlatformScreen @@ -56,8 +58,10 @@ public: int depth() const override { return mDepth; } QRect geometry() const override { return mGeometry; } QRect availableGeometry() const override { return mGeometry; } + QSizeF physicalSize() const override { return mPhysicalSize; } 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; } @@ -65,20 +69,24 @@ public: EGLConfig eglConfig() const { return mEglConfig; } EGLNativeDisplayType eglNativeDisplay() const { return mEglNativeDisplay; } void handleWindowSurfaceResize(int width, int height); + uint32_t mirOutputId() const { return mOutputId; } // QObject methods. - void customEvent(QEvent* event); + void customEvent(QEvent* event) override; private: QRect mGeometry; + QSizeF mPhysicalSize; Qt::ScreenOrientation mNativeOrientation; Qt::ScreenOrientation mCurrentOrientation; QImage::Format mFormat; int mDepth; + uint32_t mOutputId; QSurfaceFormat mSurfaceFormat; EGLDisplay mEglDisplay; EGLConfig mEglConfig; EGLNativeDisplayType mEglNativeDisplay; + QMirClientCursor mCursor; }; #endif // QMIRCLIENTSCREEN_H diff --git a/src/plugins/platforms/mirclient/qmirclientwindow.cpp b/src/plugins/platforms/mirclient/qmirclientwindow.cpp index 3d1e5377e5..9a72c2f9dc 100644 --- a/src/plugins/platforms/mirclient/qmirclientwindow.cpp +++ b/src/plugins/platforms/mirclient/qmirclientwindow.cpp @@ -36,16 +36,16 @@ // Local +#include "qmirclientwindow.h" #include "qmirclientclipboard.h" #include "qmirclientinput.h" -#include "qmirclientwindow.h" #include "qmirclientscreen.h" #include "qmirclientlogging.h" +#include + // Qt #include -#include -#include #include #include #include @@ -55,25 +55,46 @@ #include -#define IS_OPAQUE_FLAG 1 - namespace { + +// 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. +enum UAUiWindowRole { + U_MAIN_ROLE = 1, + U_DASH_ROLE, + U_INDICATOR_ROLE, + U_NOTIFICATIONS_ROLE, + U_GREETER_ROLE, + U_LAUNCHER_ROLE, + U_ON_SCREEN_KEYBOARD_ROLE, + U_SHUTDOWN_DIALOG_ROLE, +}; + +struct MirSpecDeleter +{ + void operator()(MirSurfaceSpec *spec) { mir_surface_spec_release(spec); } +}; + +using Spec = std::unique_ptr; + +EGLNativeWindowType nativeWindowFor(MirSurface *surf) +{ + auto stream = mir_surface_get_buffer_stream(surf); + return reinterpret_cast(mir_buffer_stream_get_egl_native_window(stream)); +} + MirSurfaceState qtWindowStateToMirSurfaceState(Qt::WindowState state) { switch (state) { case Qt::WindowNoState: return mir_surface_state_restored; - case Qt::WindowFullScreen: return mir_surface_state_fullscreen; - case Qt::WindowMaximized: return mir_surface_state_maximized; - case Qt::WindowMinimized: return mir_surface_state_minimized; - default: LOG("Unexpected Qt::WindowState: %d", state); return mir_surface_state_restored; @@ -86,117 +107,137 @@ const char *qtWindowStateToStr(Qt::WindowState state) switch (state) { case Qt::WindowNoState: return "NoState"; - case Qt::WindowFullScreen: return "FullScreen"; - case Qt::WindowMaximized: return "Maximized"; - case Qt::WindowMinimized: return "Minimized"; - default: return "!?"; } } #endif -} // anonymous namespace - -class QMirClientWindowPrivate +WId makeId() { -public: - void createEGLSurface(EGLNativeWindowType nativeWindow); - void destroyEGLSurface(); - int panelHeight(); - - QMirClientScreen* screen; - EGLSurface eglSurface; - WId id; - QMirClientInput* input; - Qt::WindowState state; - MirConnection *connection; - MirSurface* surface; - QSize bufferSize; - QMutex mutex; - QSharedPointer clipboard; -}; - -static void eventCallback(MirSurface* surface, const MirEvent *event, void* context) -{ - (void) surface; - DASSERT(context != NULL); - QMirClientWindow* platformWindow = static_cast(context); - platformWindow->priv()->input->postEvent(platformWindow, event); -} - -static void surfaceCreateCallback(MirSurface* surface, void* context) -{ - DASSERT(context != NULL); - QMirClientWindow* platformWindow = static_cast(context); - platformWindow->priv()->surface = surface; - - mir_surface_set_event_handler(surface, eventCallback, context); -} - -QMirClientWindow::QMirClientWindow(QWindow* w, QSharedPointer clipboard, QMirClientScreen* screen, - QMirClientInput* input, MirConnection* connection) - : QObject(nullptr), QPlatformWindow(w) -{ - DASSERT(screen != NULL); - - d = new QMirClientWindowPrivate; - d->screen = screen; - d->eglSurface = EGL_NO_SURFACE; - d->input = input; - d->state = window()->windowState(); - d->connection = connection; - d->clipboard = clipboard; - static int id = 1; - d->id = id++; - - // Use client geometry if set explicitly, use available screen geometry otherwise. - QPlatformWindow::setGeometry(window()->geometry().isValid() && window()->geometry() != screen->geometry() ? - window()->geometry() : screen->availableGeometry()); - createWindow(); - DLOG("QMirClientWindow::QMirClientWindow (this=%p, w=%p, screen=%p, input=%p)", this, w, screen, input); + return id++; } -QMirClientWindow::~QMirClientWindow() +MirPixelFormat defaultPixelFormatFor(MirConnection *connection) { - DLOG("QMirClientWindow::~QMirClientWindow"); - d->destroyEGLSurface(); - - mir_surface_release_sync(d->surface); - - delete d; + MirPixelFormat format; + unsigned int nformats; + mir_connection_get_available_surface_formats(connection, &format, 1, &nformats); + return format; } -void QMirClientWindowPrivate::createEGLSurface(EGLNativeWindowType nativeWindow) +UAUiWindowRole roleFor(QWindow *window) { - DLOG("QMirClientWindowPrivate::createEGLSurface (this=%p, nativeWindow=%p)", - this, reinterpret_cast(nativeWindow)); + QVariant roleVariant = window->property("role"); + if (!roleVariant.isValid()) + return U_MAIN_ROLE; - eglSurface = eglCreateWindowSurface(screen->eglDisplay(), screen->eglConfig(), - nativeWindow, nullptr); + uint role = roleVariant.toUInt(); + if (role < U_MAIN_ROLE || role > U_SHUTDOWN_DIALOG_ROLE) + return U_MAIN_ROLE; - DASSERT(eglSurface != EGL_NO_SURFACE); + return static_cast(role); } -void QMirClientWindowPrivate::destroyEGLSurface() +QMirClientWindow *transientParentFor(QWindow *window) { - DLOG("QMirClientWindowPrivate::destroyEGLSurface (this=%p)", this); - if (eglSurface != EGL_NO_SURFACE) { - eglDestroySurface(screen->eglDisplay(), eglSurface); - eglSurface = EGL_NO_SURFACE; + QWindow *parent = window->transientParent(); + 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)}; +} + +void setSizingConstraints(MirSurfaceSpec *spec, const QSize& minSize, const QSize& maxSize, const QSize& increment) +{ + mir_surface_spec_set_min_width(spec, minSize.width()); + mir_surface_spec_set_min_height(spec, minSize.height()); + if (maxSize.width() >= minSize.width()) { + mir_surface_spec_set_max_width(spec, maxSize.width()); + } + if (maxSize.height() >= minSize.height()) { + mir_surface_spec_set_max_height(spec, maxSize.height()); + } + if (increment.width() > 0) { + mir_surface_spec_set_width_increment(spec, increment.width()); + } + if (increment.height() > 0) { + mir_surface_spec_set_height_increment(spec, increment.height()); } } +MirSurface *createMirSurface(QWindow *window, QMirClientScreen *screen, QMirClientInput *input, MirConnection *connection) +{ + auto spec = makeSurfaceSpec(window, input, connection); + 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()); + } + + auto surface = mir_surface_create_sync(spec.get()); + Q_ASSERT(mir_surface_is_valid(surface)); + return surface; +} + // FIXME - in order to work around https://bugs.launchpad.net/mir/+bug/1346633 -// we need to guess the panel height (3GU + 2DP) -int QMirClientWindowPrivate::panelHeight() +// we need to guess the panel height (3GU) +int panelHeight() { if (qEnvironmentVariableIsSet("QT_MIRCLIENT_IGNORE_PANEL")) return 0; @@ -210,245 +251,412 @@ int QMirClientWindowPrivate::panelHeight() gridUnit = defaultGridUnit; } } - qreal densityPixelRatio = static_cast(gridUnit) / defaultGridUnit; - return gridUnit * 3 + qFloor(densityPixelRatio) * 2; + return gridUnit * 3; } -namespace -{ -static MirPixelFormat -mir_choose_default_pixel_format(MirConnection *connection) -{ - MirPixelFormat format[mir_pixel_formats]; - unsigned int nformats; +} //namespace - mir_connection_get_available_surface_formats(connection, - format, mir_pixel_formats, &nformats); +class QMirClientSurface +{ +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()) - return format[0]; -} + { + 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()); + } + + // 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); + } + + QMirClientSurface(QMirClientSurface const&) = delete; + QMirClientSurface& operator=(QMirClientSurface const&) = delete; + + void resize(const QSize& newSize); + void setState(Qt::WindowState newState); + void setVisible(bool state); + void updateTitle(const QString& title); + void setSizingConstraints(const QSize& minSize, const QSize& maxSize, const QSize& increment); + + void onSwapBuffersDone(); + void handleSurfaceResized(int width, int height); + int needsRepaint() const; + + EGLSurface eglSurface() const { return mEglSurface; } + MirSurface *mirSurface() const { return mMirSurface; } + +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; + + MirSurface * const mMirSurface; + const EGLDisplay mEglDisplay; + const EGLSurface mEglSurface; + + bool mVisible; + bool mNeedsRepaint; + bool mParented; + Qt::WindowState mWindowState; + QSize mBufferSize; + + QMutex mTargetSizeMutex; + QSize mTargetSize; +}; + +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; + } + + if (size.isEmpty()) { + DLOG("[ubuntumirclient QPA] resize(window=%p) - not resizing, size is empty", mWindow); + return; + } + + 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()); } -void QMirClientWindow::createWindow() +void QMirClientSurface::setState(Qt::WindowState newState) { - DLOG("QMirClientWindow::createWindow (this=%p)", this); + mir_wait_for(mir_surface_set_state(mMirSurface, qtWindowStateToMirSurfaceState(newState))); + mWindowState = newState; +} - // FIXME: remove this remnant of an old platform-api enum - needs ubuntu-keyboard update - const int SCREEN_KEYBOARD_ROLE = 7; - // Get surface role and flags. - QVariant roleVariant = window()->property("role"); - int role = roleVariant.isValid() ? roleVariant.toUInt() : 1; // 1 is the default role for apps. - QVariant opaqueVariant = window()->property("opaque"); - uint flags = opaqueVariant.isValid() ? - opaqueVariant.toUInt() ? static_cast(IS_OPAQUE_FLAG) : 0 : 0; +void QMirClientSurface::setVisible(bool visible) +{ + if (mVisible == visible) + return; - // FIXME(loicm) Opaque flag is forced for now for non-system sessions (applications) for - // performance reasons. - flags |= static_cast(IS_OPAQUE_FLAG); + mVisible = visible; - const QByteArray title = (!window()->title().isNull()) ? window()->title().toUtf8() : "Window 1"; // legacy title - const int panelHeight = d->panelHeight(); + 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)); +} + +void QMirClientSurface::updateTitle(const QString& newTitle) +{ + const auto title = newTitle.toUtf8(); + Spec spec{mir_connection_create_spec_for_changes(mConnection)}; + mir_surface_spec_set_name(spec.get(), title.constData()); + mir_surface_apply_spec(mMirSurface, spec.get()); +} + +void QMirClientSurface::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) +{ + QMutexLocker lock(&mTargetSizeMutex); + + // mir's resize event is mainly a signal that we need to redraw our content. We use the + // width/height as identifiers to figure out if this is the latest surface resize event + // that has posted, discarding any old ones. This avoids issuing too many redraw events. + // see TODO in postEvent as the ideal way we should handle this. + // The actual buffer size may or may have not changed at this point, so let the rendering + // thread drive the window geometry updates. + mNeedsRepaint = mTargetSize.width() == width && mTargetSize.height() == height; +} + +int QMirClientSurface::needsRepaint() const +{ + if (mNeedsRepaint) { + if (mTargetSize != mBufferSize) { + //If the buffer hasn't changed yet, we need at least two redraws, + //once to get the new buffer size and propagate the geometry changes + //and the second to redraw the content at the new size + return 2; + } else { + // The buffer size has already been updated so we only need one redraw + // to render at the new size + return 1; + } + } + return 0; +} + +void QMirClientSurface::onSwapBuffersDone() +{ #if !defined(QT_NO_DEBUG) - LOG("panelHeight: '%d'", panelHeight); - LOG("role: '%d'", role); - LOG("flags: '%s'", (flags & static_cast(1)) ? "Opaque" : "NotOpaque"); - LOG("title: '%s'", title.constData()); + static int sFrameNumber = 0; + ++sFrameNumber; #endif - // Get surface geometry. - QRect geometry; - if (d->state == Qt::WindowFullScreen) { - printf("QMirClientWindow - fullscreen geometry\n"); - geometry = screen()->geometry(); - } else if (d->state == Qt::WindowMaximized) { - printf("QMirClientWindow - maximized geometry\n"); - geometry = screen()->availableGeometry(); - /* - * FIXME: Autopilot relies on being able to convert coordinates relative of the window - * into absolute screen coordinates. Mir does not allow this, see bug lp:1346633 - * Until there's a correct way to perform this transformation agreed, this horrible hack - * guesses the transformation heuristically. - * - * Assumption: this method only used on phone devices! - */ - geometry.setY(panelHeight); + EGLint eglSurfaceWidth = -1; + EGLint eglSurfaceHeight = -1; + eglQuerySurface(mEglDisplay, mEglSurface, EGL_WIDTH, &eglSurfaceWidth); + eglQuerySurface(mEglDisplay, mEglSurface, EGL_HEIGHT, &eglSurfaceHeight); + + const bool validSize = eglSurfaceWidth > 0 && eglSurfaceHeight > 0; + + if (validSize && (mBufferSize.width() != eglSurfaceWidth || mBufferSize.height() != eglSurfaceHeight)) { + + DLOG("[ubuntumirclient QPA] onSwapBuffersDone(window=%p) [%d] - size changed (%d, %d) => (%d, %d)", + mWindow, sFrameNumber, mBufferSize.width(), mBufferSize.height(), eglSurfaceWidth, eglSurfaceHeight); + + mBufferSize.rwidth() = eglSurfaceWidth; + mBufferSize.rheight() = eglSurfaceHeight; + + QRect newGeometry = mPlatformWindow->geometry(); + newGeometry.setSize(mBufferSize); + + mPlatformWindow->QPlatformWindow::setGeometry(newGeometry); + QWindowSystemInterface::handleGeometryChange(mWindow, newGeometry); } else { - printf("QMirClientWindow - regular geometry\n"); - geometry = this->geometry(); - geometry.setY(panelHeight); - } - - DLOG("[ubuntumirclient QPA] creating surface at (%d, %d) with size (%d, %d) with title '%s'\n", - geometry.x(), geometry.y(), geometry.width(), geometry.height(), title.data()); - - MirSurfaceSpec *spec; - if (role == SCREEN_KEYBOARD_ROLE) - { - spec = mir_connection_create_spec_for_input_method(d->connection, geometry.width(), - geometry.height(), mir_choose_default_pixel_format(d->connection)); - } - else - { - spec = mir_connection_create_spec_for_normal_surface(d->connection, geometry.width(), - geometry.height(), mir_choose_default_pixel_format(d->connection)); - } - mir_surface_spec_set_name(spec, title.data()); - - // Create platform window - mir_wait_for(mir_surface_create(spec, surfaceCreateCallback, this)); - mir_surface_spec_release(spec); - - DASSERT(d->surface != NULL); - d->createEGLSurface((EGLNativeWindowType)mir_buffer_stream_get_egl_native_window(mir_surface_get_buffer_stream(d->surface))); - - if (d->state == Qt::WindowFullScreen) { - // TODO: We could set this on creation once surface spec supports it (mps already up) - mir_wait_for(mir_surface_set_state(d->surface, mir_surface_state_fullscreen)); - } - - // 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(d->surface, ¶meters); - - geometry.setWidth(parameters.width); - geometry.setHeight(parameters.height); - } - - DLOG("[ubuntumirclient QPA] created surface has size (%d, %d)", - geometry.width(), geometry.height()); - - // Assume that the buffer size matches the surface size at creation time - d->bufferSize = geometry.size(); - - // Tell Qt about the geometry. - QWindowSystemInterface::handleGeometryChange(window(), geometry); - QPlatformWindow::setGeometry(geometry); -} - -void QMirClientWindow::moveResize(const QRect& rect) -{ - (void) rect; - // TODO: Not yet supported by mir. -} - -void QMirClientWindow::handleSurfaceResize(int width, int height) -{ - QMutexLocker(&d->mutex); - LOG("QMirClientWindow::handleSurfaceResize(width=%d, height=%d)", width, height); - - // The current buffer size hasn't actually changed. so just render on it and swap - // buffers in the hope that the next buffer will match the surface size advertised - // in this event. - // But since this event is processed by a thread different from the one that swaps - // buffers, you can never know if this information is already outdated as there's - // no synchronicity whatsoever between the processing of resize events and the - // consumption of buffers. - if (d->bufferSize.width() != width || d->bufferSize.height() != height) { - QWindowSystemInterface::handleExposeEvent(window(), geometry()); - QWindowSystemInterface::flushWindowSystemEvents(); +#if 0 + DLOG("[ubuntumirclient QPA] onSwapBuffersDone(window=%p) [%d] - buffer size (%d,%d)", + mWindow, sFrameNumber, mBufferSize.width(), mBufferSize.height()); +#endif } } -void QMirClientWindow::handleSurfaceFocusChange(bool focused) +void QMirClientSurface::surfaceEventCallback(MirSurface *surface, const MirEvent *event, void* context) { - LOG("QMirClientWindow::handleSurfaceFocusChange(focused=%s)", focused ? "true" : "false"); - QWindow *activatedWindow = focused ? window() : nullptr; + Q_UNUSED(surface); + Q_ASSERT(context != nullptr); - // System clipboard contents might have changed while this window was unfocused and wihtout + auto s = static_cast(context); + s->postEvent(event); +} + +void QMirClientSurface::postEvent(const MirEvent *event) +{ + if (mir_event_type_resize == mir_event_get_type(event)) { + // TODO: The current event queue just accumulates all resize events; + // It would be nicer if we could update just one event if that event has not been dispatched. + // As a workaround, we use the width/height as an identifier of this latest event + // so the event handler (handleSurfaceResized) can discard/ignore old ones. + 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); + + QMutexLocker lock(&mTargetSizeMutex); + mTargetSize.rwidth() = width; + mTargetSize.rheight() = height; + } + + mInput->postEvent(mPlatformWindow, event); +} + +void QMirClientSurface::updateSurface() +{ + DLOG("[ubuntumirclient QPA] updateSurface(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()); + } + } +} + +QMirClientWindow::QMirClientWindow(QWindow *w, const QSharedPointer &clipboard, QMirClientScreen *screen, + QMirClientInput *input, MirConnection *connection) + : QObject(nullptr) + , QPlatformWindow(w) + , mId(makeId()) + , mClipboard(clipboard) + , mSurface(new QMirClientSurface{this, screen, input, connection}) +{ + DLOG("[ubuntumirclient QPA] QMirClientWindow(window=%p, screen=%p, input=%p, surf=%p)", w, screen, input, mSurface.get()); +} + +QMirClientWindow::~QMirClientWindow() +{ + DLOG("[ubuntumirclient QPA] ~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); + + mSurface->handleSurfaceResized(width, height); + + // This resize event could have occurred just after the last buffer swap for this window. + // This means the client may still be holding a buffer with the older size. The first redraw call + // will then render at the old size. After swapping the client now will get a new buffer with the + // 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); + 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()); + QWindowSystemInterface::handleExposeEvent(window(), QRect(QPoint(), geometry().size())); + } +} + +void QMirClientWindow::handleSurfaceFocused() +{ + DLOG("[ubuntumirclient QPA] handleSurfaceFocused(window=%p)", window()); + + // 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. - if (focused) { - d->clipboard->requestDBusClipboardContents(); - } - - QWindowSystemInterface::handleWindowActivated(activatedWindow, Qt::ActiveWindowFocusReason); + mClipboard->requestDBusClipboardContents(); } void QMirClientWindow::setWindowState(Qt::WindowState state) { - QMutexLocker(&d->mutex); - DLOG("QMirClientWindow::setWindowState (this=%p, %s)", this, qtWindowStateToStr(state)); + QMutexLocker lock(&mMutex); + DLOG("[ubuntumirclient QPA] setWindowState(window=%p, %s)", this, qtWindowStateToStr(state)); + mSurface->setState(state); - if (state == d->state) - return; + updatePanelHeightHack(state); +} - // TODO: Perhaps we should check if the states are applied? - mir_wait_for(mir_surface_set_state(d->surface, qtWindowStateToMirSurfaceState(state))); - d->state = state; +/* + 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) +{ + 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); + } } void QMirClientWindow::setGeometry(const QRect& rect) { - DLOG("QMirClientWindow::setGeometry (this=%p)", this); + 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()); - bool doMoveResize; + //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); - { - QMutexLocker(&d->mutex); - QPlatformWindow::setGeometry(rect); - doMoveResize = d->state != Qt::WindowFullScreen && d->state != Qt::WindowMaximized; - } - - if (doMoveResize) { - moveResize(rect); - } + mSurface->resize(newSize); } void QMirClientWindow::setVisible(bool visible) { - QMutexLocker(&d->mutex); - DLOG("QMirClientWindow::setVisible (this=%p, visible=%s)", this, visible ? "true" : "false"); + QMutexLocker lock(&mMutex); + DLOG("[ubuntumirclient QPA] setVisible (window=%p, visible=%s)", window(), visible ? "true" : "false"); - if (visible) { - mir_wait_for(mir_surface_set_state(d->surface, qtWindowStateToMirSurfaceState(d->state))); + mSurface->setVisible(visible); + const QRect& exposeRect = visible ? QRect(QPoint(), geometry().size()) : QRect(); - QWindowSystemInterface::handleExposeEvent(window(), QRect()); - QWindowSystemInterface::flushWindowSystemEvents(); - } else { - // TODO: Use the new mir_surface_state_hidden state instead of mir_surface_state_minimized. - // Will have to change qtmir and unity8 for that. - mir_wait_for(mir_surface_set_state(d->surface, mir_surface_state_minimized)); - } + lock.unlock(); + QWindowSystemInterface::handleExposeEvent(window(), exposeRect); + QWindowSystemInterface::flushWindowSystemEvents(); +} + +void QMirClientWindow::setWindowTitle(const QString& title) +{ + QMutexLocker lock(&mMutex); + DLOG("[ubuntumirclient QPA] setWindowTitle(window=%p) title=%s)", window(), title.toUtf8().constData()); + mSurface->updateTitle(title); +} + +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()); + mSurface->setSizingConstraints(win->minimumSize(), win->maximumSize(), win->sizeIncrement()); } void* QMirClientWindow::eglSurface() const { - return d->eglSurface; + return mSurface->eglSurface(); +} + +MirSurface *QMirClientWindow::mirSurface() const +{ + return mSurface->mirSurface(); } WId QMirClientWindow::winId() const { - return d->id; + return mId; } -void QMirClientWindow::onBuffersSwapped_threadSafe(int newBufferWidth, int newBufferHeight) +void QMirClientWindow::onSwapBuffersDone() { - QMutexLocker(&d->mutex); - - bool sizeKnown = newBufferWidth > 0 && newBufferHeight > 0; - - if (sizeKnown && (d->bufferSize.width() != newBufferWidth || - d->bufferSize.height() != newBufferHeight)) { - - DLOG("QMirClientWindow::onBuffersSwapped_threadSafe - buffer size changed from (%d,%d) to (%d,%d)", - d->bufferSize.width(), d->bufferSize.height(), newBufferWidth, newBufferHeight); - - d->bufferSize.rwidth() = newBufferWidth; - d->bufferSize.rheight() = newBufferHeight; - - QRect newGeometry; - - newGeometry = geometry(); - newGeometry.setWidth(d->bufferSize.width()); - newGeometry.setHeight(d->bufferSize.height()); - - QPlatformWindow::setGeometry(newGeometry); - QWindowSystemInterface::handleGeometryChange(window(), newGeometry, QRect()); - } + QMutexLocker lock(&mMutex); + mSurface->onSwapBuffersDone(); } diff --git a/src/plugins/platforms/mirclient/qmirclientwindow.h b/src/plugins/platforms/mirclient/qmirclientwindow.h index f342669544..4ec7879949 100644 --- a/src/plugins/platforms/mirclient/qmirclientwindow.h +++ b/src/plugins/platforms/mirclient/qmirclientwindow.h @@ -40,20 +40,23 @@ #include #include +#include -#include +#include class QMirClientClipboard; class QMirClientInput; class QMirClientScreen; -class QMirClientWindowPrivate; +class QMirClientSurface; +struct MirConnection; +struct MirSurface; class QMirClientWindow : public QObject, public QPlatformWindow { Q_OBJECT public: - QMirClientWindow(QWindow *w, QSharedPointer clipboard, QMirClientScreen *screen, - QMirClientInput *input, MirConnection *mir_connection); + QMirClientWindow(QWindow *w, const QSharedPointer &clipboard, QMirClientScreen *screen, + QMirClientInput *input, MirConnection *mirConnection); virtual ~QMirClientWindow(); // QPlatformWindow methods. @@ -61,20 +64,22 @@ public: void setGeometry(const QRect&) override; void setWindowState(Qt::WindowState state) override; void setVisible(bool visible) override; + void setWindowTitle(const QString &title) override; + void propagateSizeHints() override; // New methods. - void* eglSurface() const; - void handleSurfaceResize(int width, int height); - void handleSurfaceFocusChange(bool focused); - void onBuffersSwapped_threadSafe(int newBufferWidth, int newBufferHeight); - - QMirClientWindowPrivate* priv() { return d; } + void *eglSurface() const; + MirSurface *mirSurface() const; + void handleSurfaceResized(int width, int height); + void handleSurfaceFocused(); + void onSwapBuffersDone(); private: - void createWindow(); - void moveResize(const QRect& rect); - - QMirClientWindowPrivate *d; + void updatePanelHeightHack(Qt::WindowState); + mutable QMutex mMutex; + const WId mId; + const QSharedPointer mClipboard; + std::unique_ptr mSurface; }; #endif // QMIRCLIENTWINDOW_H