diff options
-rw-r--r-- | src/compositor/compositor_api/qwaylandkeyboard.cpp | 41 | ||||
-rw-r--r-- | src/compositor/compositor_api/qwaylandkeyboard.h | 2 | ||||
-rw-r--r-- | src/compositor/compositor_api/qwaylandkeyboard_p.h | 3 | ||||
-rw-r--r-- | src/compositor/compositor_api/qwaylandseat.cpp | 44 | ||||
-rw-r--r-- | src/compositor/compositor_api/qwaylandseat.h | 1 | ||||
-rw-r--r-- | tests/auto/compositor/compositor/compositor.pro | 2 | ||||
-rw-r--r-- | tests/auto/compositor/compositor/mockkeyboard.cpp | 101 | ||||
-rw-r--r-- | tests/auto/compositor/compositor/mockkeyboard.h | 50 | ||||
-rw-r--r-- | tests/auto/compositor/compositor/mockseat.cpp | 5 | ||||
-rw-r--r-- | tests/auto/compositor/compositor/mockseat.h | 4 | ||||
-rw-r--r-- | tests/auto/compositor/compositor/tst_compositor.cpp | 139 |
11 files changed, 382 insertions, 10 deletions
diff --git a/src/compositor/compositor_api/qwaylandkeyboard.cpp b/src/compositor/compositor_api/qwaylandkeyboard.cpp index 7ab8bff9e..c172bca85 100644 --- a/src/compositor/compositor_api/qwaylandkeyboard.cpp +++ b/src/compositor/compositor_api/qwaylandkeyboard.cpp @@ -54,6 +54,7 @@ #if QT_CONFIG(xkbcommon_evdev) #include <sys/mman.h> #include <sys/types.h> +#include <qwaylandxkb_p.h> #endif QT_BEGIN_NAMESPACE @@ -196,6 +197,32 @@ void QWaylandKeyboardPrivate::modifiers(uint32_t serial, uint32_t mods_depressed } } +#if QT_CONFIG(xkbcommon_evdev) +void QWaylandKeyboardPrivate::maybeUpdateXkbScanCodeTable() +{ + if (!scanCodesByQtKey.isEmpty() || !xkbState()) + return; + + if (xkb_keymap *keymap = xkb_state_get_keymap(xkb_state)) { + xkb_keymap_key_for_each(keymap, [](xkb_keymap *keymap, xkb_keycode_t keycode, void *d){ + auto *scanCodesByQtKey = static_cast<QMap<ScanCodeKey, uint>*>(d); + uint numLayouts = xkb_keymap_num_layouts_for_key(keymap, keycode); + for (uint layout = 0; layout < numLayouts; ++layout) { + const xkb_keysym_t *syms = nullptr; + xkb_keymap_key_get_syms_by_level(keymap, keycode, layout, 0, &syms); + if (!syms) + continue; + + Qt::KeyboardModifiers mods = {}; + int qtKey = QWaylandXkb::keysymToQtKey(syms[0], mods).first; + if (qtKey != 0) + scanCodesByQtKey->insert({layout, qtKey}, keycode); + } + }, &scanCodesByQtKey); + } +} +#endif + void QWaylandKeyboardPrivate::updateModifierState(uint code, uint32_t state) { #if QT_CONFIG(xkbcommon_evdev) @@ -355,6 +382,7 @@ void QWaylandKeyboardPrivate::createXKBKeymap() struct xkb_keymap *xkbKeymap = xkb_keymap_new_from_names(xkb_context, &rule_names, static_cast<xkb_keymap_compile_flags>(0)); if (xkbKeymap) { + scanCodesByQtKey.clear(); createXKBState(xkbKeymap); xkb_keymap_unref(xkbKeymap); } else { @@ -561,4 +589,17 @@ void QWaylandKeyboard::addClient(QWaylandClient *client, uint32_t id, uint32_t v d->add(client->client(), id, qMin<uint32_t>(QtWaylandServer::wl_keyboard::interfaceVersion(), version)); } +uint QWaylandKeyboard::toScanCode(int qtKey) const +{ + uint scanCode = 0; +#if QT_CONFIG(xkbcommon_evdev) + Q_D(const QWaylandKeyboard); + const_cast<QWaylandKeyboardPrivate *>(d)->maybeUpdateXkbScanCodeTable(); + scanCode = d->scanCodesByQtKey.value({d->group, qtKey}, 0); +#else + Q_UNUSED(qtKey); +#endif + return scanCode; +} + QT_END_NAMESPACE diff --git a/src/compositor/compositor_api/qwaylandkeyboard.h b/src/compositor/compositor_api/qwaylandkeyboard.h index 580f0c4ed..e5d5e086e 100644 --- a/src/compositor/compositor_api/qwaylandkeyboard.h +++ b/src/compositor/compositor_api/qwaylandkeyboard.h @@ -82,6 +82,8 @@ public: virtual void addClient(QWaylandClient *client, uint32_t id, uint32_t version); + uint toScanCode(int qtKey) const; + Q_SIGNALS: void focusChanged(QWaylandSurface *surface); void repeatRateChanged(quint32 repeatRate); diff --git a/src/compositor/compositor_api/qwaylandkeyboard_p.h b/src/compositor/compositor_api/qwaylandkeyboard_p.h index 6db312cfb..cd1f27956 100644 --- a/src/compositor/compositor_api/qwaylandkeyboard_p.h +++ b/src/compositor/compositor_api/qwaylandkeyboard_p.h @@ -89,6 +89,7 @@ public: #if QT_CONFIG(xkbcommon_evdev) struct xkb_state *xkbState() const { return xkb_state; } uint32_t xkbModsMask() const { return modsDepressed | modsLatched | modsLocked; } + void maybeUpdateXkbScanCodeTable(); #endif void keyEvent(uint code, uint32_t state); @@ -131,6 +132,8 @@ private: size_t keymap_size; int keymap_fd = -1; char *keymap_area = nullptr; + using ScanCodeKey = std::pair<uint,int>; // group/layout and QtKey + QMap<ScanCodeKey, uint> scanCodesByQtKey; struct xkb_context *xkb_context = nullptr; struct xkb_state *xkb_state = nullptr; #endif diff --git a/src/compositor/compositor_api/qwaylandseat.cpp b/src/compositor/compositor_api/qwaylandseat.cpp index c67e6020d..957f5ea83 100644 --- a/src/compositor/compositor_api/qwaylandseat.cpp +++ b/src/compositor/compositor_api/qwaylandseat.cpp @@ -440,10 +440,50 @@ void QWaylandSeat::sendFullKeyEvent(QKeyEvent *event) return; if (!d->keyboard.isNull() && !event->isAutoRepeat()) { + + uint scanCode = event->nativeScanCode(); + if (scanCode == 0) + scanCode = d->keyboard->toScanCode(event->key()); + + if (scanCode == 0) { + qWarning() << "Can't send Wayland key event: Unable to get a valid scan code"; + return; + } + if (event->type() == QEvent::KeyPress) - d->keyboard->sendKeyPressEvent(event->nativeScanCode()); + d->keyboard->sendKeyPressEvent(scanCode); else if (event->type() == QEvent::KeyRelease) - d->keyboard->sendKeyReleaseEvent(event->nativeScanCode()); + d->keyboard->sendKeyReleaseEvent(scanCode); + } +} + +/*! + * \qmlmethod void QtWaylandCompositor::WaylandSeat::sendKeyEvent(int qtKey, bool pressed) + * \since 5.12 + * + * Sends a key press or release to the keyboard device. + */ + +/*! + * Sends a key press or release to the keyboard device. + * + * \since 5.12 + */ +void QWaylandSeat::sendKeyEvent(int qtKey, bool pressed) +{ + Q_D(QWaylandSeat); + if (!keyboardFocus()) { + qWarning("Cannot send Wayland key event, no keyboard focus, fix the compositor"); + return; + } + + if (auto scanCode = d->keyboard->toScanCode(qtKey)) { + if (pressed) + d->keyboard->sendKeyPressEvent(scanCode); + else + d->keyboard->sendKeyReleaseEvent(scanCode); + } else { + qWarning() << "Can't send Wayland key event: Unable to get scan code for" << Qt::Key(qtKey); } } diff --git a/src/compositor/compositor_api/qwaylandseat.h b/src/compositor/compositor_api/qwaylandseat.h index f438b6639..6a4d6e176 100644 --- a/src/compositor/compositor_api/qwaylandseat.h +++ b/src/compositor/compositor_api/qwaylandseat.h @@ -97,6 +97,7 @@ public: void sendKeyReleaseEvent(uint code); void sendFullKeyEvent(QKeyEvent *event); + Q_INVOKABLE void sendKeyEvent(int qtKey, bool pressed); uint sendTouchPointEvent(QWaylandSurface *surface, int id, const QPointF &point, Qt::TouchPointState state); Q_INVOKABLE uint sendTouchPointPressed(QWaylandSurface *surface, int id, const QPointF &position); diff --git a/tests/auto/compositor/compositor/compositor.pro b/tests/auto/compositor/compositor/compositor.pro index 2919fa4bb..68b18c974 100644 --- a/tests/auto/compositor/compositor/compositor.pro +++ b/tests/auto/compositor/compositor/compositor.pro @@ -21,6 +21,7 @@ SOURCES += \ mockclient.cpp \ mockseat.cpp \ testseat.cpp \ + mockkeyboard.cpp \ mockpointer.cpp HEADERS += \ @@ -29,4 +30,5 @@ HEADERS += \ mockclient.h \ mockseat.h \ testseat.h \ + mockkeyboard.h \ mockpointer.h diff --git a/tests/auto/compositor/compositor/mockkeyboard.cpp b/tests/auto/compositor/compositor/mockkeyboard.cpp new file mode 100644 index 000000000..e5f5f8d36 --- /dev/null +++ b/tests/auto/compositor/compositor/mockkeyboard.cpp @@ -0,0 +1,101 @@ +/**************************************************************************** +** +** Copyright (C) 2018 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of the test suite of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:GPL-EXCEPT$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3 as published by the Free Software +** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-3.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "mockkeyboard.h" + +void keyboardKeymap(void *keyboard, struct wl_keyboard *wl_keyboard, uint32_t format, int32_t fd, uint32_t size) +{ + Q_UNUSED(keyboard); + Q_UNUSED(wl_keyboard); + Q_UNUSED(format); + Q_UNUSED(fd); + Q_UNUSED(size); +} + +void keyboardEnter(void *keyboard, struct wl_keyboard *wl_keyboard, uint32_t serial, struct wl_surface *surface, struct wl_array *keys) +{ + Q_UNUSED(wl_keyboard); + Q_UNUSED(serial); + Q_UNUSED(surface); + Q_UNUSED(keys); + + static_cast<MockKeyboard *>(keyboard)->m_enteredSurface = surface; +} + +void keyboardLeave(void *keyboard, struct wl_keyboard *wl_keyboard, uint32_t serial, struct wl_surface *surface) +{ + Q_UNUSED(wl_keyboard); + Q_UNUSED(serial); + Q_UNUSED(surface); + + static_cast<MockKeyboard *>(keyboard)->m_enteredSurface = nullptr; +} + +void keyboardKey(void *keyboard, struct wl_keyboard *wl_keyboard, uint32_t serial, uint32_t time, uint32_t key, uint32_t state) +{ + Q_UNUSED(keyboard); + Q_UNUSED(wl_keyboard); + Q_UNUSED(serial); + Q_UNUSED(time); + Q_UNUSED(key); + Q_UNUSED(state); + auto kb = static_cast<MockKeyboard *>(keyboard); + kb->m_lastKeyCode = key; + kb->m_lastKeyState = state; +} + +void keyboardModifiers(void *keyboard, struct wl_keyboard *wl_keyboard, uint32_t serial, uint32_t mods_depressed, uint32_t mods_latched, uint32_t mods_locked, uint32_t group) +{ + Q_UNUSED(keyboard); + Q_UNUSED(wl_keyboard); + Q_UNUSED(serial); + Q_UNUSED(mods_depressed); + Q_UNUSED(mods_latched); + Q_UNUSED(mods_locked); + auto kb = static_cast<MockKeyboard *>(keyboard); + kb->m_group = group; +} + +static const struct wl_keyboard_listener keyboardListener = { + keyboardKeymap, + keyboardEnter, + keyboardLeave, + keyboardKey, + keyboardModifiers +}; + +MockKeyboard::MockKeyboard(wl_seat *seat) + : m_keyboard(wl_seat_get_keyboard(seat)) +{ + wl_keyboard_add_listener(m_keyboard, &keyboardListener, this); +} + +MockKeyboard::~MockKeyboard() +{ + wl_keyboard_destroy(m_keyboard); +} diff --git a/tests/auto/compositor/compositor/mockkeyboard.h b/tests/auto/compositor/compositor/mockkeyboard.h new file mode 100644 index 000000000..1090db597 --- /dev/null +++ b/tests/auto/compositor/compositor/mockkeyboard.h @@ -0,0 +1,50 @@ +/**************************************************************************** +** +** Copyright (C) 2018 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of the test suite of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:GPL-EXCEPT$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3 as published by the Free Software +** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-3.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef MOCKKEYBOARD_H +#define MOCKKEYBOARD_H + +#include <QObject> +#include <wayland-client.h> + +class MockKeyboard : public QObject +{ + Q_OBJECT + +public: + explicit MockKeyboard(wl_seat *seat); + ~MockKeyboard() override; + + wl_keyboard *m_keyboard = nullptr; + wl_surface *m_enteredSurface = nullptr; + uint m_lastKeyCode = 0; + uint m_lastKeyState = 0; + uint m_group = 0; +}; + +#endif // MOCKKEYBOARD_H diff --git a/tests/auto/compositor/compositor/mockseat.cpp b/tests/auto/compositor/compositor/mockseat.cpp index 052c2f90b..ce873c129 100644 --- a/tests/auto/compositor/compositor/mockseat.cpp +++ b/tests/auto/compositor/compositor/mockseat.cpp @@ -31,14 +31,11 @@ MockSeat::MockSeat(wl_seat *seat) : m_seat(seat) , m_pointer(new MockPointer(seat)) + , m_keyboard(new MockKeyboard(seat)) { - // Bind to the keyboard interface so that the compositor has - // the right resource associations - m_keyboard = wl_seat_get_keyboard(seat); } MockSeat::~MockSeat() { - wl_keyboard_destroy(m_keyboard); wl_seat_destroy(m_seat); } diff --git a/tests/auto/compositor/compositor/mockseat.h b/tests/auto/compositor/compositor/mockseat.h index 73d289281..f8c103ed4 100644 --- a/tests/auto/compositor/compositor/mockseat.h +++ b/tests/auto/compositor/compositor/mockseat.h @@ -29,6 +29,7 @@ #define MOCKSEAT #include "mockpointer.h" +#include "mockkeyboard.h" #include <QObject> #include <wayland-client.h> @@ -41,12 +42,13 @@ public: MockSeat(wl_seat *seat); ~MockSeat() override; MockPointer *pointer() const { return m_pointer.data(); } + MockKeyboard *keyboard() const { return m_keyboard.data(); } wl_seat *m_seat = nullptr; - wl_keyboard *m_keyboard = nullptr; private: QScopedPointer<MockPointer> m_pointer; + QScopedPointer<MockKeyboard> m_keyboard; }; #endif diff --git a/tests/auto/compositor/compositor/tst_compositor.cpp b/tests/auto/compositor/compositor/tst_compositor.cpp index 575ea6466..2f00f2a83 100644 --- a/tests/auto/compositor/compositor/tst_compositor.cpp +++ b/tests/auto/compositor/compositor/tst_compositor.cpp @@ -40,10 +40,12 @@ #include <QtGui/QScreen> #include <QtWaylandCompositor/QWaylandXdgShellV5> #include <QtWaylandCompositor/private/qwaylandxdgshellv6_p.h> +#include <QtWaylandCompositor/private/qwaylandkeyboard_p.h> #include <QtWaylandCompositor/QWaylandIviApplication> #include <QtWaylandCompositor/QWaylandIviSurface> #include <QtWaylandCompositor/QWaylandSurface> #include <QtWaylandCompositor/QWaylandResource> +#include <QtWaylandCompositor/QWaylandKeymap> #include <qwayland-xdg-shell.h> #include <qwayland-ivi-application.h> @@ -56,6 +58,11 @@ class tst_WaylandCompositor : public QObject private slots: void init(); void seatCapabilities(); +#if QT_CONFIG(xkbcommon_evdev) + void simpleKeyboard(); + void keyboardKeymaps(); + void keyboardLayoutSwitching(); +#endif void keyboardGrab(); void seatCreation(); void seatKeyboardFocus(); @@ -160,6 +167,121 @@ void tst_WaylandCompositor::multipleClients() QTRY_COMPARE(compositor.surfaces.size(), 0); } +#if QT_CONFIG(xkbcommon_evdev) + +void tst_WaylandCompositor::simpleKeyboard() +{ + TestCompositor compositor; + compositor.create(); + + QWaylandSeat* seat = compositor.defaultSeat(); + seat->keymap()->setLayout("us"); + + MockClient client; + + QTRY_COMPARE(client.m_seats.size(), 1); + MockKeyboard *mockKeyboard = client.m_seats.at(0)->keyboard(); + + wl_surface *mockSurface = client.createSurface(); + QTRY_COMPARE(compositor.surfaces.size(), 1); + seat->setKeyboardFocus(compositor.surfaces.at(0)); + + compositor.flushClients(); + QTRY_COMPARE(mockKeyboard->m_enteredSurface, mockSurface); + + seat->sendKeyEvent(Qt::Key_A, true); + compositor.flushClients(); + QTRY_COMPARE(mockKeyboard->m_lastKeyState, 1u); + QTRY_COMPARE(mockKeyboard->m_lastKeyCode, 30u); // 30 is the scan code for A on us keyboard layouts + + seat->sendKeyEvent(Qt::Key_A, false); + compositor.flushClients(); + QTRY_COMPARE(mockKeyboard->m_lastKeyState, 0u); + QTRY_COMPARE(mockKeyboard->m_lastKeyCode, 30u); + + seat->sendKeyEvent(Qt::Key_Super_L, true); + seat->sendKeyEvent(Qt::Key_Super_L, false); + compositor.flushClients(); + QTRY_COMPARE(mockKeyboard->m_lastKeyCode, 125u); +} + +void tst_WaylandCompositor::keyboardKeymaps() +{ + TestCompositor compositor; + compositor.create(); + QWaylandSeat* seat = compositor.defaultSeat(); + MockClient client; + QTRY_COMPARE(client.m_seats.size(), 1); + MockKeyboard *mockKeyboard = client.m_seats.at(0)->keyboard(); + client.createSurface(); + QTRY_COMPARE(compositor.surfaces.size(), 1); + seat->setKeyboardFocus(compositor.surfaces.at(0)); + + seat->keymap()->setLayout("us"); + + seat->sendKeyEvent(Qt::Key_Y, true); + seat->sendKeyEvent(Qt::Key_Y, false); + compositor.flushClients(); + QTRY_COMPARE(mockKeyboard->m_lastKeyCode, 21u); + + seat->sendKeyEvent(Qt::Key_Z, true); + seat->sendKeyEvent(Qt::Key_Z, false); + compositor.flushClients(); + QTRY_COMPARE(mockKeyboard->m_lastKeyCode, 44u); + + seat->keymap()->setLayout("de"); // In the German layout y and z have changed places + + seat->sendKeyEvent(Qt::Key_Y, true); + seat->sendKeyEvent(Qt::Key_Y, false); + compositor.flushClients(); + QTRY_COMPARE(mockKeyboard->m_lastKeyCode, 44u); + + seat->sendKeyEvent(Qt::Key_Z, true); + seat->sendKeyEvent(Qt::Key_Z, false); + compositor.flushClients(); + QTRY_COMPARE(mockKeyboard->m_lastKeyCode, 21u); +} + +void tst_WaylandCompositor::keyboardLayoutSwitching() +{ + TestCompositor compositor; + compositor.create(); + QWaylandSeat* seat = compositor.defaultSeat(); + MockClient client; + QTRY_COMPARE(client.m_seats.size(), 1); + MockKeyboard *mockKeyboard = client.m_seats.at(0)->keyboard(); + client.createSurface(); + QTRY_COMPARE(compositor.surfaces.size(), 1); + seat->setKeyboardFocus(compositor.surfaces.at(0)); + + seat->keymap()->setLayout("us,de"); + seat->keymap()->setOptions("grp:lalt_toggle"); //toggle keyboard layout with left alt + + compositor.flushClients(); + QTRY_COMPARE(mockKeyboard->m_group, 0u); + + seat->sendKeyEvent(Qt::Key_Y, true); + seat->sendKeyEvent(Qt::Key_Y, false); + compositor.flushClients(); + QTRY_COMPARE(mockKeyboard->m_lastKeyCode, 21u); + + // It's not currently possible to switch layouts programmatically with the public APIs + // We will just fake it with the private APIs here. + auto keyboardPrivate = QWaylandKeyboardPrivate::get(seat->keyboard()); + const uint leftAltCode = 64; + keyboardPrivate->updateModifierState(leftAltCode, WL_KEYBOARD_KEY_STATE_PRESSED); + keyboardPrivate->updateModifierState(leftAltCode, WL_KEYBOARD_KEY_STATE_RELEASED); + compositor.flushClients(); + QTRY_COMPARE(mockKeyboard->m_group, 1u); + + seat->sendKeyEvent(Qt::Key_Y, true); + seat->sendKeyEvent(Qt::Key_Y, false); + compositor.flushClients(); + QTRY_COMPARE(mockKeyboard->m_lastKeyCode, 44u); +} + +#endif // QT_CONFIG(xkbcommon_evdev) + void tst_WaylandCompositor::keyboardGrab() { TestCompositor compositor; @@ -448,13 +570,24 @@ void tst_WaylandCompositor::seatKeyboardFocus() // Create client after all the input devices have been set up as the mock client // does not dynamically listen to new seats MockClient client; + + QTRY_COMPARE(client.m_seats.size(), 1); + MockKeyboard *mockKeyboard = client.m_seats.first()->keyboard(); + QVERIFY(mockKeyboard); + QCOMPARE(mockKeyboard->m_enteredSurface, nullptr); + wl_surface *surface = client.createSurface(); QTRY_COMPARE(compositor.surfaces.size(), 1); QWaylandSurface *waylandSurface = compositor.surfaces.at(0); - QWaylandSeat* dev = compositor.defaultSeat(); - dev->setKeyboardFocus(waylandSurface); - QTRY_COMPARE(compositor.defaultSeat()->keyboardFocus(), waylandSurface); + QWaylandSeat* seat = compositor.defaultSeat(); + QVERIFY(seat->setKeyboardFocus(waylandSurface)); + QCOMPARE(compositor.defaultSeat()->keyboardFocus(), waylandSurface); + + compositor.flushClients(); + + qDebug() << mockKeyboard->m_enteredSurface; + QTRY_COMPARE(mockKeyboard->m_enteredSurface, surface); wl_surface_destroy(surface); QTRY_VERIFY(compositor.surfaces.size() == 0); |