diff options
author | Johan Klokkhammer Helsing <johan.helsing@qt.io> | 2019-05-07 03:01:52 +0200 |
---|---|---|
committer | Johan Klokkhammer Helsing <johan.helsing@qt.io> | 2019-05-07 12:39:12 +0200 |
commit | 17b1705ce4c8bc9ca19a6b424a00a045134b3aac (patch) | |
tree | 03e7b7d461064dd007179cd29da9c024e6c8f190 /src/client | |
parent | 4ab3cbcc5c4da6384e9081993c5ba24303a382bd (diff) | |
parent | a658a10f6a42e67bd762f87851c23cc1c1e3b141 (diff) |
Merge remote-tracking branch 'origin/5.13' into dev
Conflicts:
src/client/qwaylanddisplay_p.h
src/client/qwaylandwindow.cpp
Change-Id: I50eb5c83a8b81e4bdb032b68d41f429b17d0a74d
Diffstat (limited to 'src/client')
-rw-r--r-- | src/client/client.pro | 7 | ||||
-rw-r--r-- | src/client/qwaylanddisplay.cpp | 99 | ||||
-rw-r--r-- | src/client/qwaylanddisplay_p.h | 31 | ||||
-rw-r--r-- | src/client/qwaylandinputcontext.cpp | 15 | ||||
-rw-r--r-- | src/client/qwaylandinputdevice.cpp | 281 | ||||
-rw-r--r-- | src/client/qwaylandinputdevice_p.h | 56 | ||||
-rw-r--r-- | src/client/qwaylandintegration.cpp | 63 | ||||
-rw-r--r-- | src/client/qwaylandintegration_p.h | 4 | ||||
-rw-r--r-- | src/client/qwaylandscreen.cpp | 52 | ||||
-rw-r--r-- | src/client/qwaylandscreen_p.h | 7 | ||||
-rw-r--r-- | src/client/qwaylandwindow.cpp | 202 | ||||
-rw-r--r-- | src/client/qwaylandwindow_p.h | 19 |
12 files changed, 546 insertions, 290 deletions
diff --git a/src/client/client.pro b/src/client/client.pro index 4be288c81..ff9e845f1 100644 --- a/src/client/client.pro +++ b/src/client/client.pro @@ -15,8 +15,9 @@ use_gold_linker: CONFIG += no_linker_version_script CONFIG -= precompile_header CONFIG += link_pkgconfig wayland-scanner -qtConfig(xkbcommon): \ - QMAKE_USE_PRIVATE += xkbcommon +qtConfig(xkbcommon) { + QT_FOR_PRIVATE += xkbcommon_support-private +} qtHaveModule(linuxaccessibility_support_private): \ QT += linuxaccessibility_support_private @@ -49,7 +50,6 @@ SOURCES += qwaylandintegration.cpp \ qwaylandtouch.cpp \ qwaylandqtkey.cpp \ ../shared/qwaylandmimehelper.cpp \ - ../shared/qwaylandxkb.cpp \ ../shared/qwaylandinputmethodeventbuilder.cpp \ qwaylandabstractdecoration.cpp \ qwaylanddecorationfactory.cpp \ @@ -84,7 +84,6 @@ HEADERS += qwaylandintegration_p.h \ qtwaylandclientglobal_p.h \ ../shared/qwaylandinputmethodeventbuilder_p.h \ ../shared/qwaylandmimehelper_p.h \ - ../shared/qwaylandxkb_p.h \ ../shared/qwaylandsharedmemoryformathelper_p.h \ qtConfig(clipboard) { diff --git a/src/client/qwaylanddisplay.cpp b/src/client/qwaylanddisplay.cpp index 9a4c76b1d..7c32b6bf5 100644 --- a/src/client/qwaylanddisplay.cpp +++ b/src/client/qwaylanddisplay.cpp @@ -70,6 +70,8 @@ #include <QtWaylandClient/private/qwayland-text-input-unstable-v2.h> +#include <QtCore/private/qcore_unix_p.h> + #include <QtCore/QAbstractEventDispatcher> #include <QtGui/qpa/qwindowsysteminterface.h> #include <QtGui/private/qguiapplication_p.h> @@ -91,13 +93,6 @@ struct wl_surface *QWaylandDisplay::createSurface(void *handle) return surface; } -QWaylandShellSurface *QWaylandDisplay::createShellSurface(QWaylandWindow *window) -{ - if (!mWaylandIntegration->shellIntegration()) - return nullptr; - return mWaylandIntegration->shellIntegration()->createShellSurface(window); -} - struct ::wl_region *QWaylandDisplay::createRegion(const QRegion &qregion) { struct ::wl_region *region = mCompositor.create_region(); @@ -111,12 +106,18 @@ struct ::wl_region *QWaylandDisplay::createRegion(const QRegion &qregion) ::wl_subsurface *QWaylandDisplay::createSubSurface(QWaylandWindow *window, QWaylandWindow *parent) { if (!mSubCompositor) { + qCWarning(lcQpaWayland) << "Can't create subsurface, not supported by the compositor."; return nullptr; } return mSubCompositor->get_subsurface(window->wlSurface(), parent->wlSurface()); } +QWaylandShellIntegration *QWaylandDisplay::shellIntegration() const +{ + return mWaylandIntegration->shellIntegration(); +} + QWaylandClientBufferIntegration * QWaylandDisplay::clientBufferIntegration() const { return mWaylandIntegration->clientBufferIntegration(); @@ -143,7 +144,18 @@ QWaylandDisplay::QWaylandDisplay(QWaylandIntegration *waylandIntegration) mWindowManagerIntegration.reset(new QWaylandWindowManagerIntegration(this)); +#if QT_CONFIG(xkbcommon) + mXkbContext.reset(xkb_context_new(XKB_CONTEXT_NO_FLAGS)); + if (!mXkbContext) + qCWarning(lcQpaWayland, "failed to create xkb context"); +#endif + forceRoundTrip(); + + if (!mWaitingScreens.isEmpty()) { + // Give wl_output.done and zxdg_output_v1.done events a chance to arrive + forceRoundTrip(); + } } QWaylandDisplay::~QWaylandDisplay(void) @@ -158,6 +170,7 @@ QWaylandDisplay::~QWaylandDisplay(void) QWindowSystemInterface::handleScreenRemoved(screen); } mScreens.clear(); + qDeleteAll(mWaitingScreens); #if QT_CONFIG(wayland_datadevice) delete mDndSelectionHandler.take(); @@ -194,7 +207,6 @@ void QWaylandDisplay::flushRequests() wl_display_flush(mDisplay); } - void QWaylandDisplay::blockingReadEvents() { if (wl_display_dispatch(mDisplay) < 0) { @@ -208,6 +220,41 @@ void QWaylandDisplay::exitWithError() ::exit(1); } +wl_event_queue *QWaylandDisplay::createEventQueue() +{ + return wl_display_create_queue(mDisplay); +} + +void QWaylandDisplay::dispatchQueueWhile(wl_event_queue *queue, std::function<bool ()> condition, int timeout) +{ + if (!condition()) + return; + + QElapsedTimer timer; + timer.start(); + struct pollfd pFd = qt_make_pollfd(wl_display_get_fd(mDisplay), POLLIN); + while (timeout == -1 || timer.elapsed() < timeout) { + while (wl_display_prepare_read_queue(mDisplay, queue) != 0) + wl_display_dispatch_queue_pending(mDisplay, queue); + + wl_display_flush(mDisplay); + + const int remaining = qMax(timeout - timer.elapsed(), 0ll); + const int pollTimeout = timeout == -1 ? -1 : remaining; + if (qt_poll_msecs(&pFd, 1, pollTimeout) > 0) + wl_display_read_events(mDisplay); + else + wl_display_cancel_read(mDisplay); + + if (wl_display_dispatch_queue_pending(mDisplay, queue) < 0) { + checkError(); + exitWithError(); + } + if (!condition()) + break; + } +} + QWaylandScreen *QWaylandDisplay::screenForOutput(struct wl_output *output) const { for (int i = 0; i < mScreens.size(); ++i) { @@ -218,6 +265,14 @@ QWaylandScreen *QWaylandDisplay::screenForOutput(struct wl_output *output) const return nullptr; } +void QWaylandDisplay::handleScreenInitialized(QWaylandScreen *screen) +{ + if (!mWaitingScreens.removeOne(screen)) + return; + mScreens.append(screen); + QWindowSystemInterface::handleScreenAdded(screen); +} + void QWaylandDisplay::waitForScreens() { flushRequests(); @@ -242,11 +297,7 @@ void QWaylandDisplay::registry_global(uint32_t id, const QString &interface, uin struct ::wl_registry *registry = object(); if (interface == QStringLiteral("wl_output")) { - QWaylandScreen *screen = new QWaylandScreen(this, version, id); - mScreens.append(screen); - // We need to get the output events before creating surfaces - forceRoundTrip(); - QWindowSystemInterface::handleScreenAdded(screen); + mWaitingScreens << new QWaylandScreen(this, version, id); } else if (interface == QStringLiteral("wl_compositor")) { mCompositorVersion = qMin((int)version, 3); mCompositor.init(registry, id, mCompositorVersion); @@ -267,11 +318,11 @@ void QWaylandDisplay::registry_global(uint32_t id, const QString &interface, uin mTouchExtension.reset(new QWaylandTouchExtension(this, id)); } else if (interface == QStringLiteral("zqt_key_v1")) { mQtKeyExtension.reset(new QWaylandQtKeyExtension(this, id)); - } else if (interface == QStringLiteral("zwp_text_input_manager_v2")) { + } else if (interface == QStringLiteral("zwp_text_input_manager_v2") && !mClientSideInputContextRequested) { mTextInputManager.reset(new QtWayland::zwp_text_input_manager_v2(registry, id, 1)); - foreach (QWaylandInputDevice *inputDevice, mInputDevices) { + for (QWaylandInputDevice *inputDevice : qAsConst(mInputDevices)) inputDevice->setTextInput(new QWaylandTextInput(this, mTextInputManager->get_text_input(inputDevice->wl_seat()))); - } + mWaylandIntegration->reconfigureInputContext(); } else if (interface == QStringLiteral("qt_hardware_integration")) { bool disableHardwareIntegration = qEnvironmentVariableIntValue("QT_WAYLAND_DISABLE_HW_INTEGRATION"); if (!disableHardwareIntegration) { @@ -282,7 +333,7 @@ void QWaylandDisplay::registry_global(uint32_t id, const QString &interface, uin } } else if (interface == QLatin1String("zxdg_output_manager_v1")) { mXdgOutputManager.reset(new QtWayland::zxdg_output_manager_v1(registry, id, qMin(2, int(version)))); - for (auto *screen : qAsConst(mScreens)) + for (auto *screen : qAsConst(mWaitingScreens)) screen->initXdgOutput(xdgOutputManager()); forceRoundTrip(); } @@ -299,6 +350,14 @@ void QWaylandDisplay::registry_global_remove(uint32_t id) RegistryGlobal &global = mGlobals[i]; if (global.id == id) { if (global.interface == QStringLiteral("wl_output")) { + for (auto *screen : mWaitingScreens) { + if (screen->outputId() == id) { + mWaitingScreens.removeOne(screen); + delete screen; + break; + } + } + foreach (QWaylandScreen *screen, mScreens) { if (screen->outputId() == id) { mScreens.removeOne(screen); @@ -307,6 +366,12 @@ void QWaylandDisplay::registry_global_remove(uint32_t id) } } } + if (global.interface == QStringLiteral("zwp_text_input_manager_v2")) { + mTextInputManager.reset(); + for (QWaylandInputDevice *inputDevice : qAsConst(mInputDevices)) + inputDevice->setTextInput(nullptr); + mWaylandIntegration->reconfigureInputContext(); + } mGlobals.removeAt(i); break; } diff --git a/src/client/qwaylanddisplay_p.h b/src/client/qwaylanddisplay_p.h index c518cb94e..2c7ed3231 100644 --- a/src/client/qwaylanddisplay_p.h +++ b/src/client/qwaylanddisplay_p.h @@ -63,6 +63,12 @@ #include <QtWaylandClient/private/qtwaylandclientglobal_p.h> #include <QtWaylandClient/private/qwaylandshm_p.h> +#include <qpa/qplatforminputcontextfactory_p.h> + +#if QT_CONFIG(xkbcommon) +#include <QtXkbCommonSupport/private/qxkbcommon_p.h> +#endif + struct wl_cursor_image; QT_BEGIN_NAMESPACE @@ -92,8 +98,8 @@ class QWaylandQtKeyExtension; class QWaylandWindow; class QWaylandIntegration; class QWaylandHardwareIntegration; -class QWaylandShellSurface; class QWaylandSurface; +class QWaylandShellIntegration; class QWaylandCursor; class QWaylandCursorTheme; @@ -110,18 +116,23 @@ public: QWaylandDisplay(QWaylandIntegration *waylandIntegration); ~QWaylandDisplay(void) override; +#if QT_CONFIG(xkbcommon) + struct xkb_context *xkbContext() const { return mXkbContext.get(); } +#endif + QList<QWaylandScreen *> screens() const { return mScreens; } QWaylandScreen *screenForOutput(struct wl_output *output) const; + void handleScreenInitialized(QWaylandScreen *screen); struct wl_surface *createSurface(void *handle); - QWaylandShellSurface *createShellSurface(QWaylandWindow *window); struct ::wl_region *createRegion(const QRegion &qregion); struct ::wl_subsurface *createSubSurface(QWaylandWindow *window, QWaylandWindow *parent); + QWaylandShellIntegration *shellIntegration() const; QWaylandClientBufferIntegration *clientBufferIntegration() const; - QWaylandWindowManagerIntegration *windowManagerIntegration() const; + #if QT_CONFIG(cursor) QWaylandCursor *waylandCursor(); QWaylandCursorTheme *loadCursorTheme(const QString &name, int pixelSize); @@ -145,6 +156,7 @@ public: QWaylandHardwareIntegration *hardwareIntegration() const { return mHardwareIntegration.data(); } QtWayland::zxdg_output_manager_v1 *xdgOutputManager() const { return mXdgOutputManager.data(); } + bool usingInputContextFromCompositor() const { return mUsingInputContextFromCompositor; } struct RegistryGlobal { uint32_t id; @@ -181,6 +193,9 @@ public: void handleKeyboardFocusChanged(QWaylandInputDevice *inputDevice); void handleWindowDestroyed(QWaylandWindow *window); + wl_event_queue *createEventQueue(); + void dispatchQueueWhile(wl_event_queue *queue, std::function<bool()> condition, int timeout = -1); + public slots: void blockingReadEvents(); void flushRequests(); @@ -206,6 +221,7 @@ private: struct wl_display *mDisplay = nullptr; QtWayland::wl_compositor mCompositor; QScopedPointer<QWaylandShm> mShm; + QList<QWaylandScreen *> mWaitingScreens; QList<QWaylandScreen *> mScreens; QList<QWaylandInputDevice *> mInputDevices; QList<Listener> mRegistryListeners; @@ -238,8 +254,17 @@ private: struct wl_callback *mSyncCallback = nullptr; static const wl_callback_listener syncCallbackListener; + bool mClientSideInputContextRequested = !QPlatformInputContextFactory::requested().isNull(); + bool mUsingInputContextFromCompositor = false; + void registry_global(uint32_t id, const QString &interface, uint32_t version) override; void registry_global_remove(uint32_t id) override; + +#if QT_CONFIG(xkbcommon) + QXkbCommon::ScopedXKBContext mXkbContext; +#endif + + friend class QWaylandIntegration; }; } diff --git a/src/client/qwaylandinputcontext.cpp b/src/client/qwaylandinputcontext.cpp index 55e7641e5..1d34f06cc 100644 --- a/src/client/qwaylandinputcontext.cpp +++ b/src/client/qwaylandinputcontext.cpp @@ -50,7 +50,6 @@ #include "qwaylandinputdevice_p.h" #include "qwaylandinputmethodeventbuilder_p.h" #include "qwaylandwindow_p.h" -#include "qwaylandxkb_p.h" QT_BEGIN_NAMESPACE @@ -315,6 +314,7 @@ void QWaylandTextInput::zwp_text_input_v2_delete_surrounding_text(uint32_t befor void QWaylandTextInput::zwp_text_input_v2_keysym(uint32_t time, uint32_t sym, uint32_t state, uint32_t modifiers) { +#if QT_CONFIG(xkbcommon) if (m_resetCallback) { qCDebug(qLcQpaInputMethods()) << "discard keysym: reset not confirmed"; return; @@ -325,13 +325,18 @@ void QWaylandTextInput::zwp_text_input_v2_keysym(uint32_t time, uint32_t sym, ui Qt::KeyboardModifiers qtModifiers = modifiersToQtModifiers(modifiers); - QEvent::Type type = QWaylandXkb::toQtEventType(state); - QString text; - int qtkey; - std::tie(qtkey, text) = QWaylandXkb::keysymToQtKey(sym, qtModifiers); + QEvent::Type type = state == WL_KEYBOARD_KEY_STATE_PRESSED ? QEvent::KeyPress : QEvent::KeyRelease; + QString text = QXkbCommon::lookupStringNoKeysymTransformations(sym); + int qtkey = QXkbCommon::keysymToQtKey(sym, qtModifiers); QWindowSystemInterface::handleKeyEvent(QGuiApplication::focusWindow(), time, type, qtkey, qtModifiers, text); +#else + Q_UNUSED(time); + Q_UNUSED(sym); + Q_UNUSED(state); + Q_UNUSED(modifiers); +#endif } void QWaylandTextInput::zwp_text_input_v2_language(const QString &language) diff --git a/src/client/qwaylandinputdevice.cpp b/src/client/qwaylandinputdevice.cpp index 89b50f0ad..f0fd99563 100644 --- a/src/client/qwaylandinputdevice.cpp +++ b/src/client/qwaylandinputdevice.cpp @@ -52,7 +52,6 @@ #include "qwaylandcursor_p.h" #include "qwaylanddisplay_p.h" #include "qwaylandshmbackingstore_p.h" -#include "../shared/qwaylandxkb_p.h" #include "qwaylandinputcontext_p.h" #include <QtGui/private/qpixmap_raster_p.h> @@ -71,10 +70,6 @@ #include <QtGui/QGuiApplication> -#if QT_CONFIG(xkbcommon) -#include <xkbcommon/xkbcommon-compose.h> -#endif - QT_BEGIN_NAMESPACE namespace QtWaylandClient { @@ -84,85 +79,51 @@ Q_LOGGING_CATEGORY(lcQpaWaylandInput, "qt.qpa.wayland.input"); QWaylandInputDevice::Keyboard::Keyboard(QWaylandInputDevice *p) : mParent(p) { - connect(&mRepeatTimer, SIGNAL(timeout()), this, SLOT(repeatKey())); + mRepeatTimer.callOnTimeout([&]() { + if (!focusWindow()) { + // We destroyed the keyboard focus surface, but the server didn't get the message yet... + // or the server didn't send an enter event first. + return; + } + mRepeatTimer.setInterval(mRepeatRate); + handleKey(mRepeatKey.time, QEvent::KeyRelease, mRepeatKey.key, mRepeatKey.modifiers, + mRepeatKey.code, mRepeatKey.nativeVirtualKey, mRepeatKey.nativeModifiers, + mRepeatKey.text, true); + handleKey(mRepeatKey.time, QEvent::KeyPress, mRepeatKey.key, mRepeatKey.modifiers, + mRepeatKey.code, mRepeatKey.nativeVirtualKey, mRepeatKey.nativeModifiers, + mRepeatKey.text, true); + }); } #if QT_CONFIG(xkbcommon) -bool QWaylandInputDevice::Keyboard::createDefaultKeyMap() +bool QWaylandInputDevice::Keyboard::createDefaultKeymap() { - if (mXkbContext && mXkbMap && mXkbState) { - return true; - } + struct xkb_context *ctx = mParent->mQDisplay->xkbContext(); + if (!ctx) + return false; - xkb_rule_names names; - names.rules = strdup("evdev"); - names.model = strdup("pc105"); - names.layout = strdup("us"); - names.variant = strdup(""); - names.options = strdup(""); - - mXkbContext = xkb_context_new(xkb_context_flags(0)); - if (mXkbContext) { - mXkbMap = xkb_map_new_from_names(mXkbContext, &names, xkb_map_compile_flags(0)); - if (mXkbMap) { - mXkbState = xkb_state_new(mXkbMap); - } - } + struct xkb_rule_names names; + names.rules = "evdev"; + names.model = "pc105"; + names.layout = "us"; + names.variant = ""; + names.options = ""; - if (!mXkbContext || !mXkbMap || !mXkbState) { - qWarning() << "xkb_map_new_from_names failed, no key input"; - return false; - } - createComposeState(); - return true; -} + mXkbKeymap.reset(xkb_keymap_new_from_names(ctx, &names, XKB_KEYMAP_COMPILE_NO_FLAGS)); + if (mXkbKeymap) + mXkbState.reset(xkb_state_new(mXkbKeymap.get())); -void QWaylandInputDevice::Keyboard::releaseKeyMap() -{ - if (mXkbState) - xkb_state_unref(mXkbState); - if (mXkbMap) - xkb_map_unref(mXkbMap); - if (mXkbContext) - xkb_context_unref(mXkbContext); -} - -void QWaylandInputDevice::Keyboard::createComposeState() -{ - static const char *locale = nullptr; - if (!locale) { - locale = getenv("LC_ALL"); - if (!locale) - locale = getenv("LC_CTYPE"); - if (!locale) - locale = getenv("LANG"); - if (!locale) - locale = "C"; + if (!mXkbKeymap || !mXkbState) { + qCWarning(lcQpaWayland, "failed to create default keymap"); + return false; } - mXkbComposeTable = xkb_compose_table_new_from_locale(mXkbContext, locale, XKB_COMPOSE_COMPILE_NO_FLAGS); - if (mXkbComposeTable) - mXkbComposeState = xkb_compose_state_new(mXkbComposeTable, XKB_COMPOSE_STATE_NO_FLAGS); -} - -void QWaylandInputDevice::Keyboard::releaseComposeState() -{ - if (mXkbComposeState) - xkb_compose_state_unref(mXkbComposeState); - if (mXkbComposeTable) - xkb_compose_table_unref(mXkbComposeTable); - mXkbComposeState = nullptr; - mXkbComposeTable = nullptr; + return true; } - #endif QWaylandInputDevice::Keyboard::~Keyboard() { -#if QT_CONFIG(xkbcommon) - releaseComposeState(); - releaseKeyMap(); -#endif if (mFocus) QWindowSystemInterface::handleWindowActivated(nullptr); if (mParent->mVersion >= 3) @@ -402,9 +363,9 @@ QWaylandInputDevice::QWaylandInputDevice(QWaylandDisplay *display, int version, } #endif - if (mQDisplay->textInputManager()) { - mTextInput = new QWaylandTextInput(mQDisplay, mQDisplay->textInputManager()->get_text_input(wl_seat())); - } + if (mQDisplay->textInputManager()) + mTextInput.reset(new QWaylandTextInput(mQDisplay, mQDisplay->textInputManager()->get_text_input(wl_seat()))); + } QWaylandInputDevice::~QWaylandInputDevice() @@ -487,12 +448,12 @@ QWaylandDataDevice *QWaylandInputDevice::dataDevice() const void QWaylandInputDevice::setTextInput(QWaylandTextInput *textInput) { - mTextInput = textInput; + mTextInput.reset(textInput); } QWaylandTextInput *QWaylandInputDevice::textInput() const { - return mTextInput; + return mTextInput.data(); } void QWaylandInputDevice::removeMouseButtonFromState(Qt::MouseButton button) @@ -521,6 +482,17 @@ QPointF QWaylandInputDevice::pointerSurfacePosition() const return mPointer ? mPointer->mSurfacePos : QPointF(); } +QList<int> QWaylandInputDevice::possibleKeys(const QKeyEvent *event) const +{ +#if QT_CONFIG(xkbcommon) + if (mKeyboard && mKeyboard->mXkbState) + return QXkbCommon::possibleKeys(mKeyboard->mXkbState.get(), event); +#else + Q_UNUSED(event); +#endif + return {}; +} + Qt::KeyboardModifiers QWaylandInputDevice::modifiers() const { if (!mKeyboard) @@ -537,7 +509,7 @@ Qt::KeyboardModifiers QWaylandInputDevice::Keyboard::modifiers() const if (!mXkbState) return ret; - ret = QWaylandXkb::modifiers(mXkbState); + ret = QXkbCommon::modifiers(mXkbState.get()); #endif return ret; @@ -1045,8 +1017,10 @@ bool QWaylandInputDevice::Pointer::isDefinitelyTerminated(QtWayland::wl_pointer: void QWaylandInputDevice::Keyboard::keyboard_keymap(uint32_t format, int32_t fd, uint32_t size) { + mKeymapFormat = format; #if QT_CONFIG(xkbcommon) if (format != WL_KEYBOARD_KEYMAP_FORMAT_XKB_V1) { + qCWarning(lcQpaWayland) << "unknown keymap format:" << format; close(fd); return; } @@ -1057,21 +1031,19 @@ void QWaylandInputDevice::Keyboard::keyboard_keymap(uint32_t format, int32_t fd, return; } - // Release the old keymap resources in the case they were already created in - // the key event or when the compositor issues a new map - releaseComposeState(); - releaseKeyMap(); + mXkbKeymap.reset(xkb_keymap_new_from_string(mParent->mQDisplay->xkbContext(), map_str, + XKB_KEYMAP_FORMAT_TEXT_V1, + XKB_KEYMAP_COMPILE_NO_FLAGS)); + QXkbCommon::verifyHasLatinLayout(mXkbKeymap.get()); - mXkbContext = xkb_context_new(XKB_CONTEXT_NO_FLAGS); - mXkbMap = xkb_map_new_from_string(mXkbContext, map_str, XKB_KEYMAP_FORMAT_TEXT_V1, XKB_KEYMAP_COMPILE_NO_FLAGS); munmap(map_str, size); close(fd); - mXkbState = xkb_state_new(mXkbMap); - createComposeState(); - + if (mXkbKeymap) + mXkbState.reset(xkb_state_new(mXkbKeymap.get())); + else + mXkbState.reset(nullptr); #else - Q_UNUSED(format); Q_UNUSED(fd); Q_UNUSED(size); #endif @@ -1118,28 +1090,34 @@ void QWaylandInputDevice::Keyboard::keyboard_leave(uint32_t time, struct wl_surf handleFocusLost(); } -static void sendKey(QWindow *tlw, ulong timestamp, QEvent::Type type, int key, Qt::KeyboardModifiers modifiers, - quint32 nativeScanCode, quint32 nativeVirtualKey, quint32 nativeModifiers, - const QString& text = QString(), bool autorep = false, ushort count = 1) +void QWaylandInputDevice::Keyboard::handleKey(ulong timestamp, QEvent::Type type, int key, + Qt::KeyboardModifiers modifiers, quint32 nativeScanCode, + quint32 nativeVirtualKey, quint32 nativeModifiers, + const QString &text, bool autorepeat, ushort count) { QPlatformInputContext *inputContext = QGuiApplicationPrivate::platformIntegration()->inputContext(); bool filtered = false; - if (inputContext) { - QKeyEvent event(type, key, modifiers, nativeScanCode, nativeVirtualKey, nativeModifiers, - text, autorep, count); + if (inputContext && !mParent->mQDisplay->usingInputContextFromCompositor()) { + QKeyEvent event(type, key, modifiers, nativeScanCode, nativeVirtualKey, + nativeModifiers, text, autorepeat, count); event.setTimestamp(timestamp); filtered = inputContext->filterEvent(&event); } if (!filtered) { - QWindowSystemInterface::handleExtendedKeyEvent(tlw, timestamp, type, key, modifiers, - nativeScanCode, nativeVirtualKey, nativeModifiers, text, autorep, count); + QWindowSystemInterface::handleExtendedKeyEvent(focusWindow()->window(), timestamp, type, key, modifiers, + nativeScanCode, nativeVirtualKey, nativeModifiers, text, autorepeat, count); } } void QWaylandInputDevice::Keyboard::keyboard_key(uint32_t serial, uint32_t time, uint32_t key, uint32_t state) { + if (mKeymapFormat != WL_KEYBOARD_KEYMAP_FORMAT_XKB_V1 && mKeymapFormat != WL_KEYBOARD_KEYMAP_FORMAT_NO_KEYMAP) { + qCWarning(lcQpaWayland) << Q_FUNC_INFO << "unknown keymap format:" << mKeymapFormat; + return; + } + auto *window = focusWindow(); if (!window) { // We destroyed the keyboard focus surface, but the server didn't get the message yet... @@ -1147,102 +1125,52 @@ void QWaylandInputDevice::Keyboard::keyboard_key(uint32_t serial, uint32_t time, return; } - uint32_t code = key + 8; - bool isDown = state != WL_KEYBOARD_KEY_STATE_RELEASED; - QEvent::Type type = isDown ? QEvent::KeyPress : QEvent::KeyRelease; - QString text; - int qtkey = key + 8; // qt-compositor substracts 8 for some reason mParent->mSerial = serial; + const bool isDown = state != WL_KEYBOARD_KEY_STATE_RELEASED; if (isDown) mParent->mQDisplay->setLastInputDevice(mParent, serial, window); + if (mKeymapFormat == WL_KEYBOARD_KEYMAP_FORMAT_XKB_V1) { #if QT_CONFIG(xkbcommon) - if (!createDefaultKeyMap()) { - return; - } + if ((!mXkbKeymap || !mXkbState) && !createDefaultKeymap()) + return; - QString composedText; - xkb_keysym_t sym = xkb_state_key_get_one_sym(mXkbState, code); - if (mXkbComposeState) { - if (isDown) - xkb_compose_state_feed(mXkbComposeState, sym); - xkb_compose_status status = xkb_compose_state_get_status(mXkbComposeState); - - switch (status) { - case XKB_COMPOSE_COMPOSED: { - int size = xkb_compose_state_get_utf8(mXkbComposeState, nullptr, 0); - QVarLengthArray<char, 32> buffer(size + 1); - xkb_compose_state_get_utf8(mXkbComposeState, buffer.data(), buffer.size()); - composedText = QString::fromUtf8(buffer.constData()); - sym = xkb_compose_state_get_one_sym(mXkbComposeState); - xkb_compose_state_reset(mXkbComposeState); - } break; - case XKB_COMPOSE_COMPOSING: - case XKB_COMPOSE_CANCELLED: - return; - case XKB_COMPOSE_NOTHING: - break; - } - } + auto code = key + 8; // map to wl_keyboard::keymap_format::keymap_format_xkb_v1 - Qt::KeyboardModifiers modifiers = mParent->modifiers(); + xkb_keysym_t sym = xkb_state_key_get_one_sym(mXkbState.get(), code); - std::tie(qtkey, text) = QWaylandXkb::keysymToQtKey(sym, modifiers); + Qt::KeyboardModifiers modifiers = mParent->modifiers(); - if (!composedText.isNull()) - text = composedText; + int qtkey = QXkbCommon::keysymToQtKey(sym, modifiers, mXkbState.get(), code); + QString text = QXkbCommon::lookupString(mXkbState.get(), code); - sendKey(window->window(), time, type, qtkey, modifiers, code, sym, mNativeModifiers, text); -#else - // Generic fallback for single hard keys: Assume 'key' is a Qt key code. - sendKey(window->window(), time, type, qtkey, Qt::NoModifier, code, 0, 0); -#endif + QEvent::Type type = isDown ? QEvent::KeyPress : QEvent::KeyRelease; + handleKey(time, type, qtkey, modifiers, code, sym, mNativeModifiers, text); - if (state == WL_KEYBOARD_KEY_STATE_PRESSED -#if QT_CONFIG(xkbcommon) - && xkb_keymap_key_repeats(mXkbMap, code) -#endif - ) { - mRepeatKey = qtkey; - mRepeatCode = code; - mRepeatTime = time; - mRepeatText = text; -#if QT_CONFIG(xkbcommon) - mRepeatSym = sym; + if (state == WL_KEYBOARD_KEY_STATE_PRESSED && xkb_keymap_key_repeats(mXkbKeymap.get(), code)) { + mRepeatKey.key = qtkey; + mRepeatKey.code = code; + mRepeatKey.time = time; + mRepeatKey.text = text; + mRepeatKey.modifiers = modifiers; + mRepeatKey.nativeModifiers = mNativeModifiers; + mRepeatKey.nativeVirtualKey = sym; + mRepeatTimer.setInterval(mRepeatDelay); + mRepeatTimer.start(); + } else if (mRepeatKey.code == code) { + mRepeatTimer.stop(); + } +#else + Q_UNUSED(time); + Q_UNUSED(key); + qCWarning(lcQpaWayland, "xkbcommon not available on this build, not performing key mapping"); + return; #endif - mRepeatTimer.setInterval(mRepeatDelay); - mRepeatTimer.start(); - } else if (mRepeatCode == code) { - mRepeatTimer.stop(); - } -} - -void QWaylandInputDevice::Keyboard::repeatKey() -{ - auto *window = focusWindow(); - if (!window) { - // We destroyed the keyboard focus surface, but the server didn't get the message yet... - // or the server didn't send an enter event first. + } else if (mKeymapFormat == WL_KEYBOARD_KEYMAP_FORMAT_NO_KEYMAP) { + // raw scan code return; } - - mRepeatTimer.setInterval(mRepeatRate); - sendKey(window->window(), mRepeatTime, QEvent::KeyRelease, mRepeatKey, modifiers(), mRepeatCode, -#if QT_CONFIG(xkbcommon) - mRepeatSym, mNativeModifiers, -#else - 0, 0, -#endif - mRepeatText, true); - - sendKey(window->window(), mRepeatTime, QEvent::KeyPress, mRepeatKey, modifiers(), mRepeatCode, -#if QT_CONFIG(xkbcommon) - mRepeatSym, mNativeModifiers, -#else - 0, 0, -#endif - mRepeatText, true); } void QWaylandInputDevice::Keyboard::handleFocusDestroyed() @@ -1275,12 +1203,11 @@ void QWaylandInputDevice::Keyboard::keyboard_modifiers(uint32_t serial, Q_UNUSED(serial); #if QT_CONFIG(xkbcommon) if (mXkbState) - xkb_state_update_mask(mXkbState, + xkb_state_update_mask(mXkbState.get(), mods_depressed, mods_latched, mods_locked, 0, 0, group); mNativeModifiers = mods_depressed | mods_latched | mods_locked; #else - Q_UNUSED(serial); Q_UNUSED(mods_depressed); Q_UNUSED(mods_latched); Q_UNUSED(mods_locked); diff --git a/src/client/qwaylandinputdevice_p.h b/src/client/qwaylandinputdevice_p.h index 404bcf571..f44f1ab1f 100644 --- a/src/client/qwaylandinputdevice_p.h +++ b/src/client/qwaylandinputdevice_p.h @@ -54,6 +54,7 @@ #include <QtWaylandClient/private/qtwaylandclientglobal_p.h> #include <QtWaylandClient/private/qwaylandwindow_p.h> +#include <QtCore/QScopedPointer> #include <QSocketNotifier> #include <QObject> #include <QTimer> @@ -64,8 +65,7 @@ #include <QtWaylandClient/private/qwayland-wayland.h> #if QT_CONFIG(xkbcommon) -#include <xkbcommon/xkbcommon.h> -#include <xkbcommon/xkbcommon-keysyms.h> +#include <QtXkbCommonSupport/private/qxkbcommon_p.h> #endif #include <QtCore/QDebug> @@ -76,11 +76,6 @@ struct wl_cursor_image; #endif -#if QT_CONFIG(xkbcommon) -struct xkb_compose_state; -struct xkb_compose_table; -#endif - QT_BEGIN_NAMESPACE namespace QtWaylandClient { @@ -130,6 +125,8 @@ public: QWaylandWindow *keyboardFocus() const; QWaylandWindow *touchFocus() const; + QList<int> possibleKeys(const QKeyEvent *event) const; + QPointF pointerSurfacePosition() const; Qt::KeyboardModifiers modifiers() const; @@ -166,7 +163,7 @@ private: Pointer *mPointer = nullptr; Touch *mTouch = nullptr; - QWaylandTextInput *mTextInput = nullptr; + QScopedPointer<QWaylandTextInput> mTextInput; uint32_t mTime = 0; uint32_t mSerial = 0; @@ -215,41 +212,44 @@ public: QWaylandInputDevice *mParent = nullptr; ::wl_surface *mFocus = nullptr; -#if QT_CONFIG(xkbcommon) - xkb_context *mXkbContext = nullptr; - xkb_keymap *mXkbMap = nullptr; - xkb_state *mXkbState = nullptr; - xkb_compose_table *mXkbComposeTable = nullptr; - xkb_compose_state *mXkbComposeState = nullptr; -#endif + uint32_t mNativeModifiers = 0; - int mRepeatKey; - uint32_t mRepeatCode; - uint32_t mRepeatTime; + struct repeatKey { + int key; + uint32_t code; + uint32_t time; + QString text; + Qt::KeyboardModifiers modifiers; + uint32_t nativeVirtualKey; + uint32_t nativeModifiers; + } mRepeatKey; + + QTimer mRepeatTimer; int mRepeatRate = 25; int mRepeatDelay = 400; - QString mRepeatText; -#if QT_CONFIG(xkbcommon) - xkb_keysym_t mRepeatSym; -#endif - QTimer mRepeatTimer; + + uint32_t mKeymapFormat = WL_KEYBOARD_KEYMAP_FORMAT_XKB_V1; Qt::KeyboardModifiers modifiers() const; private slots: - void repeatKey(); void handleFocusDestroyed(); void handleFocusLost(); private: #if QT_CONFIG(xkbcommon) - bool createDefaultKeyMap(); - void releaseKeyMap(); - void createComposeState(); - void releaseComposeState(); + bool createDefaultKeymap(); #endif + void handleKey(ulong timestamp, QEvent::Type type, int key, Qt::KeyboardModifiers modifiers, + quint32 nativeScanCode, quint32 nativeVirtualKey, quint32 nativeModifiers, + const QString &text, bool autorepeat = false, ushort count = 1); +#if QT_CONFIG(xkbcommon) + QXkbCommon::ScopedXKBKeymap mXkbKeymap; + QXkbCommon::ScopedXKBState mXkbState; +#endif + friend class QWaylandInputDevice; }; class Q_WAYLAND_CLIENT_EXPORT QWaylandInputDevice::Pointer : public QObject, public QtWayland::wl_pointer diff --git a/src/client/qwaylandintegration.cpp b/src/client/qwaylandintegration.cpp index 45957629f..ea2b50b4a 100644 --- a/src/client/qwaylandintegration.cpp +++ b/src/client/qwaylandintegration.cpp @@ -90,6 +90,10 @@ #include <QtLinuxAccessibilitySupport/private/bridge_p.h> #endif +#if QT_CONFIG(xkbcommon) +#include <QtXkbCommonSupport/private/qxkbcommon_p.h> +#endif + QT_BEGIN_NAMESPACE namespace QtWaylandClient { @@ -146,20 +150,8 @@ QWaylandIntegration::QWaylandIntegration() #if QT_CONFIG(draganddrop) mDrag.reset(new QWaylandDrag(mDisplay.data())); #endif - QString icStr = QPlatformInputContextFactory::requested(); - if (!icStr.isNull()) { - mInputContext.reset(QPlatformInputContextFactory::create(icStr)); - } else { - //try to use the input context using the wl_text_input interface - QPlatformInputContext *ctx = new QWaylandInputContext(mDisplay.data()); - mInputContext.reset(ctx); - - //use the traditional way for on screen keyboards for now - if (!mInputContext.data()->isValid()) { - ctx = QPlatformInputContextFactory::create(); - mInputContext.reset(ctx); - } - } + + reconfigureInputContext(); } QWaylandIntegration::~QWaylandIntegration() @@ -301,6 +293,13 @@ QWaylandDisplay *QWaylandIntegration::display() const return mDisplay.data(); } +QList<int> QWaylandIntegration::possibleKeys(const QKeyEvent *event) const +{ + if (auto *seat = mDisplay->currentInputDevice()) + return seat->possibleKeys(event); + return {}; +} + QStringList QWaylandIntegration::themeNames() const { return GenericWaylandTheme::themeNames(); @@ -462,6 +461,42 @@ void QWaylandIntegration::initializeInputDeviceIntegration() } } +void QWaylandIntegration::reconfigureInputContext() +{ + if (!mDisplay) { + // This function can be called from QWaylandDisplay::registry_global() when we + // are in process of constructing QWaylandDisplay. Configuring input context + // in that case is done by calling reconfigureInputContext() from QWaylandIntegration + // constructor, after QWaylandDisplay has been constructed. + return; + } + + const QString &requested = QPlatformInputContextFactory::requested(); + if (requested == QLatin1String("qtvirtualkeyboard")) + qCWarning(lcQpaWayland) << "qtvirtualkeyboard currently is not supported at client-side," + " use QT_IM_MODULE=qtvirtualkeyboard at compositor-side."; + + if (requested.isNull()) + mInputContext.reset(new QWaylandInputContext(mDisplay.data())); + else + mInputContext.reset(QPlatformInputContextFactory::create(requested)); + + const QString defaultInputContext(QStringLiteral("compose")); + if ((!mInputContext || !mInputContext->isValid()) && requested != defaultInputContext) + mInputContext.reset(QPlatformInputContextFactory::create(defaultInputContext)); + +#if QT_CONFIG(xkbcommon) + QXkbCommon::setXkbContext(mInputContext.data(), mDisplay->xkbContext()); +#endif + + // Even if compositor-side input context handling has been requested, we fallback to + // client-side handling if compositor does not provide the text-input extension. This + // is why we need to check here which input context actually is being used. + mDisplay->mUsingInputContextFromCompositor = qobject_cast<QWaylandInputContext *>(mInputContext.data()); + + qCDebug(lcQpaWayland) << "using input method:" << inputContext()->metaObject()->className(); +} + QWaylandShellIntegration *QWaylandIntegration::createShellIntegration(const QString &integrationName) { if (QWaylandShellIntegrationFactory::keys().contains(integrationName)) { diff --git a/src/client/qwaylandintegration_p.h b/src/client/qwaylandintegration_p.h index 944f635bb..3aef2c4d9 100644 --- a/src/client/qwaylandintegration_p.h +++ b/src/client/qwaylandintegration_p.h @@ -106,6 +106,8 @@ public: QWaylandDisplay *display() const; + QList<int> possibleKeys(const QKeyEvent *event) const override; + QStringList themeNames() const override; QPlatformTheme *createPlatformTheme(const QString &name) const override; @@ -116,6 +118,8 @@ public: virtual QWaylandServerBufferIntegration *serverBufferIntegration() const; virtual QWaylandShellIntegration *shellIntegration() const; + void reconfigureInputContext(); + private: // NOTE: mDisplay *must* be destructed after mDrag and mClientBufferIntegration // and mShellIntegration. diff --git a/src/client/qwaylandscreen.cpp b/src/client/qwaylandscreen.cpp index ee6f949aa..d116a807b 100644 --- a/src/client/qwaylandscreen.cpp +++ b/src/client/qwaylandscreen.cpp @@ -40,6 +40,7 @@ #include "qwaylandscreen_p.h" #include "qwaylanddisplay_p.h" +#include "qwaylandintegration_p.h" #include "qwaylandcursor_p.h" #include "qwaylandwindow_p.h" @@ -60,6 +61,14 @@ QWaylandScreen::QWaylandScreen(QWaylandDisplay *waylandDisplay, int version, uin { if (auto *xdgOutputManager = waylandDisplay->xdgOutputManager()) initXdgOutput(xdgOutputManager); + + if (version < WL_OUTPUT_DONE_SINCE_VERSION) { + qCWarning(lcQpaWayland) << "wl_output done event not supported by compositor," + << "QScreen may not work correctly"; + mWaylandDisplay->forceRoundTrip(); // Give the compositor a chance to send geometry etc. + mOutputDone = true; // Fake the done event + maybeInitialize(); + } } QWaylandScreen::~QWaylandScreen() @@ -68,6 +77,24 @@ QWaylandScreen::~QWaylandScreen() zxdg_output_v1::destroy(); } +void QWaylandScreen::maybeInitialize() +{ + Q_ASSERT(!mInitialized); + + if (!mOutputDone) + return; + + if (mWaylandDisplay->xdgOutputManager() && !mXdgOutputDone) + return; + + mInitialized = true; + mWaylandDisplay->handleScreenInitialized(this); + + updateOutputProperties(); + if (zxdg_output_v1::isInitialized()) + updateXdgOutputProperties(); +} + void QWaylandScreen::initXdgOutput(QtWayland::zxdg_output_manager_v1 *xdgOutputManager) { Q_ASSERT(xdgOutputManager); @@ -233,10 +260,15 @@ void QWaylandScreen::output_scale(int32_t factor) void QWaylandScreen::output_done() { - // the done event is sent after all the geometry and the mode events are sent, - // and the last mode event to be sent is the active one, so we can trust the - // values of mGeometry and mRefreshRate here + mOutputDone = true; + if (mInitialized) + updateOutputProperties(); + else + maybeInitialize(); +} +void QWaylandScreen::updateOutputProperties() +{ if (mTransform >= 0) { bool isPortrait = mGeometry.height() > mGeometry.width(); switch (mTransform) { @@ -263,7 +295,9 @@ void QWaylandScreen::output_done() QWindowSystemInterface::handleScreenOrientationChange(screen(), m_orientation); mTransform = -1; } + QWindowSystemInterface::handleScreenRefreshRateChange(screen(), refreshRate()); + if (!zxdg_output_v1::isInitialized()) QWindowSystemInterface::handleScreenGeometryChange(screen(), geometry(), geometry()); } @@ -281,7 +315,11 @@ void QWaylandScreen::zxdg_output_v1_logical_size(int32_t width, int32_t height) void QWaylandScreen::zxdg_output_v1_done() { - QWindowSystemInterface::handleScreenGeometryChange(screen(), geometry(), geometry()); + mXdgOutputDone = true; + if (mInitialized) + updateXdgOutputProperties(); + else + maybeInitialize(); } void QWaylandScreen::zxdg_output_v1_name(const QString &name) @@ -289,6 +327,12 @@ void QWaylandScreen::zxdg_output_v1_name(const QString &name) mOutputName = name; } +void QWaylandScreen::updateXdgOutputProperties() +{ + Q_ASSERT(zxdg_output_v1::isInitialized()); + QWindowSystemInterface::handleScreenGeometryChange(screen(), geometry(), geometry()); +} + } // namespace QtWaylandClient QT_END_NAMESPACE diff --git a/src/client/qwaylandscreen_p.h b/src/client/qwaylandscreen_p.h index 4ef58c0c1..e9e07d9cd 100644 --- a/src/client/qwaylandscreen_p.h +++ b/src/client/qwaylandscreen_p.h @@ -71,6 +71,8 @@ public: QWaylandScreen(QWaylandDisplay *waylandDisplay, int version, uint32_t id); ~QWaylandScreen() override; + void maybeInitialize(); + void initXdgOutput(QtWayland::zxdg_output_manager_v1 *xdgOutputManager); QWaylandDisplay *display() const; @@ -116,12 +118,14 @@ private: int32_t transform) override; void output_scale(int32_t factor) override; void output_done() override; + void updateOutputProperties(); // XdgOutput void zxdg_output_v1_logical_position(int32_t x, int32_t y) override; void zxdg_output_v1_logical_size(int32_t width, int32_t height) override; void zxdg_output_v1_done() override; void zxdg_output_v1_name(const QString &name) override; + void updateXdgOutputProperties(); int m_outputId; QWaylandDisplay *mWaylandDisplay = nullptr; @@ -137,6 +141,9 @@ private: QSize mPhysicalSize; QString mOutputName; Qt::ScreenOrientation m_orientation = Qt::PrimaryOrientation; + bool mOutputDone = false; + bool mXdgOutputDone = false; + bool mInitialized = false; #if QT_CONFIG(cursor) QScopedPointer<QWaylandCursor> mWaylandCursor; diff --git a/src/client/qwaylandwindow.cpp b/src/client/qwaylandwindow.cpp index d0cb63172..9bc400f8a 100644 --- a/src/client/qwaylandwindow.cpp +++ b/src/client/qwaylandwindow.cpp @@ -51,6 +51,7 @@ #include "qwaylandnativeinterface_p.h" #include "qwaylanddecorationfactory_p.h" #include "qwaylandshmbackingstore_p.h" +#include "qwaylandshellintegration_p.h" #include <QtCore/QFileInfo> #include <QtCore/QPointer> @@ -62,6 +63,7 @@ #include <QtGui/private/qwindow_p.h> #include <QtCore/QDebug> +#include <QtCore/QThread> QT_BEGIN_NAMESPACE @@ -74,6 +76,7 @@ QWaylandWindow *QWaylandWindow::mMouseGrab = nullptr; QWaylandWindow::QWaylandWindow(QWindow *window) : QPlatformWindow(window) , mDisplay(waylandScreen()->display()) + , mFrameQueue(mDisplay->createEventQueue()) , mResizeAfterSwap(qEnvironmentVariableIsSet("QT_WAYLAND_RESIZE_AFTER_SWAP")) { static WId id = 1; @@ -127,8 +130,9 @@ void QWaylandWindow::initWindow() } } else if (shouldCreateShellSurface()) { Q_ASSERT(!mShellSurface); + Q_ASSERT(mDisplay->shellIntegration()); - mShellSurface = mDisplay->createShellSurface(this); + mShellSurface = mDisplay->shellIntegration()->createShellSurface(this); if (mShellSurface) { // Set initial surface title setWindowTitle(window()->title()); @@ -204,6 +208,9 @@ void QWaylandWindow::initializeWlSurface() bool QWaylandWindow::shouldCreateShellSurface() const { + if (!mDisplay->shellIntegration()) + return false; + if (shouldCreateSubSurface()) return false; @@ -339,7 +346,7 @@ void QWaylandWindow::setGeometry(const QRect &rect) sendExposeEvent(exposeGeometry); if (mShellSurface) - mShellSurface->setWindowGeometry(windowGeometry()); + mShellSurface->setWindowGeometry(windowContentGeometry()); } void QWaylandWindow::resizeFromApplyConfigure(const QSize &sizeWithMargins, const QPoint &offset) @@ -357,6 +364,8 @@ void QWaylandWindow::sendExposeEvent(const QRect &rect) { if (!(mShellSurface && mShellSurface->handleExpose(rect))) QWindowSystemInterface::handleExposeEvent(window(), rect); + else + qCDebug(lcQpaWayland) << "sendExposeEvent: intercepted by shell extension, not sending"; mLastExposeGeometry = rect; } @@ -494,15 +503,8 @@ void QWaylandWindow::applyConfigure() void QWaylandWindow::attach(QWaylandBuffer *buffer, int x, int y) { Q_ASSERT(!buffer->committed()); - if (mFrameCallback) { - wl_callback_destroy(mFrameCallback); - mFrameCallback = nullptr; - } - if (buffer) { - mFrameCallback = mSurface->frame(); - wl_callback_add_listener(mFrameCallback, &QWaylandWindow::callbackListener, this); - mWaitingForFrameSync = true; + handleUpdate(); buffer->setBusy(); mSurface->attach(buffer->buffer(), x, y); @@ -566,32 +568,61 @@ void QWaylandWindow::commit() } const wl_callback_listener QWaylandWindow::callbackListener = { - QWaylandWindow::frameCallback + [](void *data, wl_callback *callback, uint32_t time) { + Q_UNUSED(callback); + Q_UNUSED(time); + auto *window = static_cast<QWaylandWindow*>(data); + if (window->thread() != QThread::currentThread()) + QMetaObject::invokeMethod(window, [=] { window->handleFrameCallback(); }, Qt::QueuedConnection); + else + window->handleFrameCallback(); + } }; -void QWaylandWindow::frameCallback(void *data, struct wl_callback *callback, uint32_t time) +void QWaylandWindow::handleFrameCallback() { - Q_UNUSED(time); - Q_UNUSED(callback); - QWaylandWindow *self = static_cast<QWaylandWindow*>(data); + bool wasExposed = isExposed(); - self->mWaitingForFrameSync = false; - if (self->mUpdateRequested) { - self->mUpdateRequested = false; - self->deliverUpdateRequest(); + if (mFrameCallbackTimerId != -1) { + killTimer(mFrameCallbackTimerId); + mFrameCallbackTimerId = -1; } + + mWaitingForFrameCallback = false; + mFrameCallbackTimedOut = false; + + if (!wasExposed && isExposed()) + sendExposeEvent(QRect(QPoint(), geometry().size())); + if (wasExposed && hasPendingUpdateRequest()) + deliverUpdateRequest(); } QMutex QWaylandWindow::mFrameSyncMutex; -void QWaylandWindow::waitForFrameSync() +bool QWaylandWindow::waitForFrameSync(int timeout) { QMutexLocker locker(&mFrameSyncMutex); - if (!mWaitingForFrameSync) - return; - mDisplay->flushRequests(); - while (mWaitingForFrameSync) - mDisplay->blockingReadEvents(); + if (!mWaitingForFrameCallback) + return true; + + wl_proxy_set_queue(reinterpret_cast<wl_proxy *>(mFrameCallback), mFrameQueue); + mDisplay->dispatchQueueWhile(mFrameQueue, [&]() { return mWaitingForFrameCallback; }, timeout); + + if (mWaitingForFrameCallback) { + qCDebug(lcWaylandBackingstore) << "Didn't receive frame callback in time, window should now be inexposed"; + mFrameCallbackTimedOut = true; + mWaitingForUpdate = false; + sendExposeEvent(QRect()); + } + + // Stop current frame timer if any, can't use killTimer directly, because we might be on a diffent thread + if (mFrameCallbackTimerId != -1) { + int id = mFrameCallbackTimerId; + mFrameCallbackTimerId = -1; + QMetaObject::invokeMethod(this, [=] { killTimer(id); }, Qt::QueuedConnection); + } + + return !mWaitingForFrameCallback; } QMargins QWaylandWindow::frameMargins() const @@ -613,7 +644,7 @@ QSize QWaylandWindow::surfaceSize() const * Window geometry as defined by the xdg-shell spec (in wl_surface coordinates) * topLeft is where the shadow stops and the decorations border start. */ -QRect QWaylandWindow::windowGeometry() const +QRect QWaylandWindow::windowContentGeometry() const { return QRect(QPoint(), surfaceSize()); } @@ -833,7 +864,7 @@ void QWaylandWindow::handleMouse(QWaylandInputDevice *inputDevice, const QWaylan #if QT_CONFIG(cursor) if (e.type == QWaylandPointerEvent::Enter) { - QRect contentGeometry = windowGeometry().marginsRemoved(frameMargins()); + QRect contentGeometry = windowContentGeometry().marginsRemoved(frameMargins()); if (contentGeometry.contains(e.local.toPoint())) restoreMouseCursor(inputDevice); } @@ -944,9 +975,19 @@ void QWaylandWindow::requestActivateWindow() bool QWaylandWindow::isExposed() const { + if (!window()->isVisible()) + return false; + + if (mFrameCallbackTimedOut) + return false; + if (mShellSurface) - return window()->isVisible() && mShellSurface->isExposed(); - return QPlatformWindow::isExposed(); + return mShellSurface->isExposed(); + + if (mSubSurfaceWindow) + return mSubSurfaceWindow->parent()->isExposed(); + + return !(shouldCreateShellSurface() || shouldCreateSubSurface()); } bool QWaylandWindow::isActive() const @@ -1015,12 +1056,107 @@ QVariant QWaylandWindow::property(const QString &name, const QVariant &defaultVa return m_properties.value(name, defaultValue); } +void QWaylandWindow::timerEvent(QTimerEvent *event) +{ + if (event->timerId() == mFallbackUpdateTimerId) { + killTimer(mFallbackUpdateTimerId); + mFallbackUpdateTimerId = -1; + qCDebug(lcWaylandBackingstore) << "mFallbackUpdateTimer timed out"; + + if (!isExposed()) { + qCDebug(lcWaylandBackingstore) << "Fallback update timer: Window not exposed," + << "not delivering update request."; + return; + } + + if (mWaitingForUpdate && hasPendingUpdateRequest() && !mWaitingForFrameCallback) { + qCWarning(lcWaylandBackingstore) << "Delivering update request through fallback timer," + << "may not be in sync with display"; + deliverUpdateRequest(); + } + } + + if (event->timerId() == mFrameCallbackTimerId) { + killTimer(mFrameCallbackTimerId); + mFrameCallbackTimerId = -1; + qCDebug(lcWaylandBackingstore) << "Didn't receive frame callback in time, window should now be inexposed"; + mFrameCallbackTimedOut = true; + mWaitingForUpdate = false; + sendExposeEvent(QRect()); + } +} + void QWaylandWindow::requestUpdate() { - if (!mWaitingForFrameSync) - QPlatformWindow::requestUpdate(); - else - mUpdateRequested = true; + Q_ASSERT(hasPendingUpdateRequest()); // should be set by QPA + + // If we have a frame callback all is good and will be taken care of there + if (mWaitingForFrameCallback) + return; + + // If we've already called deliverUpdateRequest(), but haven't seen any attach+commit/swap yet + if (mWaitingForUpdate) { + // Ideally, we should just have returned here, but we're not guaranteed that the client + // will actually update, so start this timer to deliver another request update after a while + // *IF* the client doesn't update. + int fallbackTimeout = 100; + mFallbackUpdateTimerId = startTimer(fallbackTimeout); + return; + } + + // Some applications (such as Qt Quick) depend on updates being delivered asynchronously, + // so use invokeMethod to delay the delivery a bit. + QMetaObject::invokeMethod(this, [this] { + // Things might have changed in the meantime + if (hasPendingUpdateRequest() && !mWaitingForUpdate && !mWaitingForFrameCallback) + deliverUpdateRequest(); + }, Qt::QueuedConnection); +} + +// Should be called whenever we commit a buffer (directly through wl_surface.commit or indirectly +// with eglSwapBuffers) to know when it's time to commit the next one. +// Can be called from the render thread (without locking anything) so make sure to not make races in this method. +void QWaylandWindow::handleUpdate() +{ + // TODO: Should sync subsurfaces avoid requesting frame callbacks? + + if (mFrameCallback) { + wl_callback_destroy(mFrameCallback); + mFrameCallback = nullptr; + } + + if (mFallbackUpdateTimerId != -1) { + // Ideally, we would stop the fallback timer here, but since we're on another thread, + // it's not allowed. Instead we set mFallbackUpdateTimer to -1 here, so we'll just + // ignore it if it times out before it's cleaned up by the invokeMethod call. + int id = mFallbackUpdateTimerId; + mFallbackUpdateTimerId = -1; + QMetaObject::invokeMethod(this, [=] { killTimer(id); }, Qt::QueuedConnection); + } + + mFrameCallback = mSurface->frame(); + wl_callback_add_listener(mFrameCallback, &QWaylandWindow::callbackListener, this); + mWaitingForFrameCallback = true; + mWaitingForUpdate = false; + + // Stop current frame timer if any, can't use killTimer directly, see comment above. + if (mFrameCallbackTimerId != -1) { + int id = mFrameCallbackTimerId; + mFrameCallbackTimerId = -1; + QMetaObject::invokeMethod(this, [=] { killTimer(id); }, Qt::QueuedConnection); + } + + // Start a timer for handling the case when the compositor stops sending frame callbacks. + QMetaObject::invokeMethod(this, [=] { // Again; can't do it directly + if (mWaitingForFrameCallback) + mFrameCallbackTimerId = startTimer(100); + }, Qt::QueuedConnection); +} + +void QWaylandWindow::deliverUpdateRequest() +{ + mWaitingForUpdate = true; + QPlatformWindow::deliverUpdateRequest(); } void QWaylandWindow::addAttachOffset(const QPoint point) diff --git a/src/client/qwaylandwindow_p.h b/src/client/qwaylandwindow_p.h index 3e9b43851..5eadc02c8 100644 --- a/src/client/qwaylandwindow_p.h +++ b/src/client/qwaylandwindow_p.h @@ -121,11 +121,11 @@ public: void commit(); - void waitForFrameSync(); + bool waitForFrameSync(int timeout); QMargins frameMargins() const override; QSize surfaceSize() const; - QRect windowGeometry() const; + QRect windowContentGeometry() const; QWaylandSurface *waylandSurface() const { return mSurface.data(); } ::wl_surface *wlSurface(); @@ -194,7 +194,10 @@ public: bool startSystemMove(const QPoint &pos) override; + void timerEvent(QTimerEvent *event) override; void requestUpdate() override; + void handleUpdate(); + void deliverUpdateRequest() override; public slots: void applyConfigure(); @@ -214,10 +217,17 @@ protected: Qt::MouseButtons mMousePressedInContentArea = Qt::NoButton; WId mWindowId; - bool mWaitingForFrameSync = false; + bool mWaitingForFrameCallback = false; + bool mFrameCallbackTimedOut = false; // Whether the frame callback has timed out + int mFrameCallbackTimerId = -1; // Started on commit, reset on frame callback struct ::wl_callback *mFrameCallback = nullptr; + struct ::wl_event_queue *mFrameQueue = nullptr; QWaitCondition mFrameSyncWait; + // True when we have called deliverRequestUpdate, but the client has not yet attached a new buffer + bool mWaitingForUpdate = false; + int mFallbackUpdateTimerId = -1; // Started when waiting for app to commit + QMutex mResizeLock; bool mWaitingToApplyConfigure = false; bool mCanResize = true; @@ -254,11 +264,10 @@ private: void handleMouseEventWithDecoration(QWaylandInputDevice *inputDevice, const QWaylandPointerEvent &e); void handleScreensChanged(); - bool mUpdateRequested = false; QRect mLastExposeGeometry; static const wl_callback_listener callbackListener; - static void frameCallback(void *data, struct wl_callback *wl_callback, uint32_t time); + void handleFrameCallback(); static QMutex mFrameSyncMutex; static QWaylandWindow *mMouseGrab; |