Revision control

++ // Copyright (C) 2017 The Qt Company Ltd.
++ // Copyright (C) 2017 Klarälvdalens Datakonsult AB (KDAB).
++ // SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only
++ // Qt-Security score:critical reason:network-protocol
++
++ #include "qtwaylandcompositorglobal_p.h"
++ #include "qwaylandkeyboard.h"
++ #include "qwaylandkeyboard_p.h"
++ #include <QtWaylandCompositor/QWaylandKeymap>
++ #include <QtWaylandCompositor/QWaylandCompositor>
++ #include <QtWaylandCompositor/QWaylandSeat>
++ #include <QtWaylandCompositor/QWaylandClient>
++
++ #include <QtCore/QFile>
++ #include <QtCore/QStandardPaths>
++
++ #include <QKeyEvent>
++ #include <fcntl.h>
++ #include <unistd.h>
++ #if QT_CONFIG(xkbcommon)
++ #include <sys/mman.h>
++ #include <sys/types.h>
++ #include <xkbcommon/xkbcommon-names.h>
++
++ // xkbcommon 1.8 and later defines, otherwise we define it by hand
++ #ifndef XKB_MOD_NAME_MOD5
++ #define XKB_MOD_NAME_MOD5 "Mod5"
++ #endif
++ #endif
++
++ QT_BEGIN_NAMESPACE
++
++ QWaylandKeyboardPrivate::QWaylandKeyboardPrivate(QWaylandSeat *seat)
++ : seat(seat)
++ {
++ }
++
++ QWaylandKeyboardPrivate::~QWaylandKeyboardPrivate()
++ {
++ #if QT_CONFIG(xkbcommon)
++ if (xkbContext()) {
++ if (keymap_area)
++ munmap(keymap_area, keymap_size);
++ if (keymap_fd >= 0)
++ close(keymap_fd);
++ }
++ #endif
++ }
++
++ QWaylandKeyboardPrivate *QWaylandKeyboardPrivate::get(QWaylandKeyboard *keyboard)
++ {
++ return keyboard->d_func();
++ }
++
++ void QWaylandKeyboardPrivate::checkFocusResource(Resource *keyboardResource)
++ {
++ if (!keyboardResource || !focus)
++ return;
++
++ // this is already the current resource, do no send enter twice
++ if (focusResource == keyboardResource)
++ return;
++
++ // check if new wl_keyboard resource is from the client owning the focus surface
++ if (wl_resource_get_client(focus->resource()) == keyboardResource->client()) {
++ sendEnter(focus, keyboardResource);
++ focusResource = keyboardResource;
++ }
++ }
++
++ void QWaylandKeyboardPrivate::sendEnter(QWaylandSurface *surface, Resource *keyboardResource)
++ {
++ uint32_t serial = compositor()->nextSerial();
++ send_modifiers(keyboardResource->handle, serial, modsDepressed, modsLatched, modsLocked, group);
++ send_enter(keyboardResource->handle, serial, surface->resource(), QByteArray::fromRawData((char *)keys.data(), keys.size() * sizeof(uint32_t)));
++ }
++
++ void QWaylandKeyboardPrivate::focused(QWaylandSurface *surface)
++ {
++ if (surface && surface->isCursorSurface())
++ surface = nullptr;
++ if (focus != surface) {
++ if (focusResource) {
++ uint32_t serial = compositor()->nextSerial();
++ send_leave(focusResource->handle, serial, focus->resource());
++ }
++ focusDestroyListener.reset();
++ if (surface)
++ focusDestroyListener.listenForDestruction(surface->resource());
++ }
++
++ Resource *resource = surface ? resourceMap().value(surface->waylandClient()) : 0;
++
++ if (resource && (focus != surface || focusResource != resource))
++ sendEnter(surface, resource);
++
++ focusResource = resource;
++ focus = surface;
++ Q_EMIT q_func()->focusChanged(focus);
++ }
++
++
++ void QWaylandKeyboardPrivate::keyboard_bind_resource(wl_keyboard::Resource *resource)
++ {
++ // Send repeat information
++ if (resource->version() >= WL_KEYBOARD_REPEAT_INFO_SINCE_VERSION)
++ send_repeat_info(resource->handle, repeatRate, repeatDelay);
++
++ #if QT_CONFIG(xkbcommon)
++ if (xkbContext()) {
++ send_keymap(resource->handle, WL_KEYBOARD_KEYMAP_FORMAT_XKB_V1,
++ keymap_fd, keymap_size);
++ } else
++ #endif
++ {
++ int null_fd = open("/dev/null", O_RDONLY);
++ send_keymap(resource->handle, WL_KEYBOARD_KEYMAP_FORMAT_NO_KEYMAP,
++ null_fd, 0);
++ close(null_fd);
++ }
++ checkFocusResource(resource);
++ }
++
++ void QWaylandKeyboardPrivate::keyboard_destroy_resource(wl_keyboard::Resource *resource)
++ {
++ if (focusResource == resource)
++ focusResource = nullptr;
++ }
++
++ void QWaylandKeyboardPrivate::keyboard_release(wl_keyboard::Resource *resource)
++ {
++ wl_resource_destroy(resource->handle);
++ }
++
++ void QWaylandKeyboardPrivate::keyEvent(uint code, uint32_t state)
++ {
++ uint key = toWaylandKey(code);
++
++ if (state == WL_KEYBOARD_KEY_STATE_PRESSED) {
++ keys << key;
++ } else {
++ keys.removeAll(key);
++ }
++ }
++
++ void QWaylandKeyboardPrivate::sendKeyEvent(uint code, uint32_t state)
++ {
++ uint32_t time = compositor()->currentTimeMsecs();
++ uint32_t serial = compositor()->nextSerial();
++ uint key = toWaylandKey(code);
++ if (focusResource)
++ send_key(focusResource->handle, serial, time, key, state);
++ }
++
++ #if QT_CONFIG(xkbcommon)
++ void QWaylandKeyboardPrivate::maybeUpdateXkbScanCodeTable()
++ {
++ if (!scanCodesByQtKey.isEmpty() || !xkbState())
++ return;
++
++ if (xkb_keymap *keymap = xkb_state_get_keymap(xkbState())) {
++ 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 = QXkbCommon::keysymToQtKey(syms[0], mods, nullptr, 0, false, false);
++ if (qtKey != 0)
++ scanCodesByQtKey->insert({layout, qtKey}, keycode);
++ }
++ }, &scanCodesByQtKey);
++
++ shiftIndex = xkb_keymap_mod_get_index(keymap, XKB_MOD_NAME_SHIFT);
++ controlIndex = xkb_keymap_mod_get_index(keymap, XKB_MOD_NAME_CTRL);
++ altIndex = xkb_keymap_mod_get_index(keymap, XKB_MOD_NAME_ALT);
++ mod5Index = xkb_keymap_mod_get_index(keymap, XKB_MOD_NAME_MOD5); // AltGr / ISO_Level3_Shift
++ }
++ }
++
++ void QWaylandKeyboardPrivate::resetKeyboardState()
++ {
++ if (!xkbContext())
++ return;
++
++ while (!keys.isEmpty()) {
++ uint32_t code = fromWaylandKey(keys.first());
++ keyEvent(code, WL_KEYBOARD_KEY_STATE_RELEASED);
++ updateModifierState(code, WL_KEYBOARD_KEY_STATE_RELEASED);
++ }
++ }
++ #endif
++
++ void QWaylandKeyboardPrivate::updateModifierState(uint code, uint32_t state)
++ {
++ #if QT_CONFIG(xkbcommon)
++ if (!xkbContext())
++ return;
++
++ xkb_state_update_key(xkbState(), code, state == WL_KEYBOARD_KEY_STATE_PRESSED ? XKB_KEY_DOWN : XKB_KEY_UP);
++
++ uint32_t modsDepressed = xkb_state_serialize_mods(xkbState(), XKB_STATE_MODS_DEPRESSED);
++ uint32_t modsLatched = xkb_state_serialize_mods(xkbState(), XKB_STATE_MODS_LATCHED);
++ uint32_t modsLocked = xkb_state_serialize_mods(xkbState(), XKB_STATE_MODS_LOCKED);
++ uint32_t group = xkb_state_serialize_layout(xkbState(), XKB_STATE_LAYOUT_EFFECTIVE);
++
++ if (this->modsDepressed == modsDepressed
++ && this->modsLatched == modsLatched
++ && this->modsLocked == modsLocked
++ && this->group == group)
++ return;
++
++ this->modsDepressed = modsDepressed;
++ this->modsLatched = modsLatched;
++ this->modsLocked = modsLocked;
++ this->group = group;
++
++ if (focusResource) {
++ send_modifiers(focusResource->handle, compositor()->nextSerial(), modsDepressed,
++ modsLatched, modsLocked, group);
++
++ Qt::KeyboardModifiers currentState = Qt::NoModifier;
++ if (xkb_state_mod_index_is_active(xkbState(), shiftIndex, XKB_STATE_MODS_EFFECTIVE) == 1)
++ currentState |= Qt::ShiftModifier;
++ if (xkb_state_mod_index_is_active(xkbState(), controlIndex, XKB_STATE_MODS_EFFECTIVE) == 1)
++ currentState |= Qt::ControlModifier;
++ if (xkb_state_mod_index_is_active(xkbState(), altIndex, XKB_STATE_MODS_EFFECTIVE) == 1)
++ currentState |= Qt::AltModifier;
++ if (mod5Index != XKB_MOD_INVALID
++ && xkb_state_mod_index_is_active(xkbState(), mod5Index, XKB_STATE_MODS_EFFECTIVE) == 1)
++ currentState |= Qt::GroupSwitchModifier;
++ currentModifierState = currentState;
++ }
++ #else
++ Q_UNUSED(code);
++ Q_UNUSED(state);
++ #endif
++ }
++
++ // If there is no key currently pressed, update the keymap right away.
++ // Otherwise, delay the update when keys are released
++ void QWaylandKeyboardPrivate::maybeUpdateKeymap()
++ {
++ // There must be no keys pressed when changing the keymap,
++ if (!pendingKeymap || !keys.isEmpty())
++ return;
++
++ pendingKeymap = false;
++ #if QT_CONFIG(xkbcommon)
++ if (!xkbContext())
++ return;
++
++ createXKBKeymap();
++ const auto resMap = resourceMap();
++ for (Resource *res : resMap) {
++ send_keymap(res->handle, WL_KEYBOARD_KEYMAP_FORMAT_XKB_V1, keymap_fd, keymap_size);
++ }
++
++ xkb_state_update_mask(xkbState(), 0, modsLatched, modsLocked, 0, 0, 0);
++ if (focusResource)
++ send_modifiers(focusResource->handle,
++ compositor()->nextSerial(),
++ modsDepressed,
++ modsLatched,
++ modsLocked,
++ group);
++ #endif
++ }
++
++ // In all current XKB keymaps there's a constant offset of 8 (for historical
++ // reasons) from hardware/evdev scancodes to XKB keycodes. On X11, we pass
++ // XKB keycodes (as sent by X server) via QKeyEvent::nativeScanCode. eglfs+evdev
++ // adds 8 for consistency, see qtbase/05c07c7636012ebb4131ca099ca4ea093af76410.
++ // eglfs+libinput also adds 8, for the same reason. Wayland protocol uses
++ // hardware/evdev scancodes, thus we need to subtract 8 before sending the event
++ // out and add it when mapping back.
++ #define QTWAYLANDKEYBOARD_XKB_HISTORICAL_OFFSET 8
++
++ uint QWaylandKeyboardPrivate::fromWaylandKey(const uint key)
++ {
++ #if QT_CONFIG(xkbcommon)
++ const uint offset = QTWAYLANDKEYBOARD_XKB_HISTORICAL_OFFSET;
++ return key + offset;
++ #else
++ return key;
++ #endif
++ }
++
++ uint QWaylandKeyboardPrivate::toWaylandKey(const uint nativeScanCode)
++ {
++ #if QT_CONFIG(xkbcommon)
++ const uint offset = QTWAYLANDKEYBOARD_XKB_HISTORICAL_OFFSET;
++ Q_ASSERT(nativeScanCode >= offset);
++ return nativeScanCode - offset;
++ #else
++ return nativeScanCode;
++ #endif
++ }
++
++ #if QT_CONFIG(xkbcommon)
++ static int createAnonymousFile(size_t size)
++ {
++ QString path = QStandardPaths::writableLocation(QStandardPaths::RuntimeLocation);
++ if (path.isEmpty())
++ return -1;
++
++ QByteArray name = QFile::encodeName(path + QStringLiteral("/qtwayland-XXXXXX"));
++
++ int fd = mkstemp(name.data());
++ if (fd < 0)
++ return -1;
++
++ long flags = fcntl(fd, F_GETFD);
++ if (flags == -1 || fcntl(fd, F_SETFD, flags | FD_CLOEXEC) == -1) {
++ close(fd);
++ fd = -1;
++ }
++ unlink(name.constData());
++
++ if (fd < 0)
++ return -1;
++
++ if (ftruncate(fd, size) < 0) {
++ close(fd);
++ return -1;
++ }
++
++ return fd;
++ }
++
++ void QWaylandKeyboardPrivate::createXKBState(xkb_keymap *keymap)
++ {
++ char *keymap_str = xkb_keymap_get_as_string(keymap, XKB_KEYMAP_FORMAT_TEXT_V1);
++ if (!keymap_str) {
++ qWarning("Failed to compile global XKB keymap");
++ return;
++ }
++
++ keymap_size = strlen(keymap_str) + 1;
++ if (keymap_fd >= 0)
++ close(keymap_fd);
++ keymap_fd = createAnonymousFile(keymap_size);
++ if (keymap_fd < 0) {
++ qWarning("Failed to create anonymous file of size %lu", static_cast<unsigned long>(keymap_size));
++ return;
++ }
++
++ keymap_area = static_cast<char *>(mmap(nullptr, keymap_size, PROT_READ | PROT_WRITE, MAP_SHARED, keymap_fd, 0));
++ if (keymap_area == MAP_FAILED) {
++ close(keymap_fd);
++ keymap_fd = -1;
++ qWarning("Failed to map shared memory segment");
++ return;
++ }
++
++ strcpy(keymap_area, keymap_str);
++ free(keymap_str);
++
++ mXkbState.reset(xkb_state_new(keymap));
++ if (!mXkbState)
++ qWarning("Failed to create XKB state");
++ }
++
++ void QWaylandKeyboardPrivate::createXKBKeymap()
++ {
++ if (!xkbContext())
++ return;
++
++ QWaylandKeymap *keymap = seat->keymap();
++ QByteArray rules = keymap->rules().toLocal8Bit();
++ QByteArray model = keymap->model().toLocal8Bit();
++ QByteArray layout = keymap->layout().toLocal8Bit();
++ QByteArray variant = keymap->variant().toLocal8Bit();
++ QByteArray options = keymap->options().toLocal8Bit();
++
++ if (!layout.isEmpty() && !layout.contains("us")) {
++ // This is needed for shortucts like "ctrl+c" to function even when
++ // user has selected only non-latin keyboard layouts, e.g. 'ru'.
++ layout.append(",us");
++ variant.append(",");
++ }
++
++ struct xkb_rule_names rule_names = {
++ rules.constData(),
++ model.constData(),
++ layout.constData(),
++ variant.constData(),
++ options.constData()
++ };
++
++ QXkbCommon::ScopedXKBKeymap xkbKeymap(xkb_keymap_new_from_names(xkbContext(), &rule_names,
++ XKB_KEYMAP_COMPILE_NO_FLAGS));
++ if (xkbKeymap) {
++ scanCodesByQtKey.clear();
++ createXKBState(xkbKeymap.get());
++ } else {
++ qWarning("Failed to load the '%s' XKB keymap.", qPrintable(keymap->layout()));
++ }
++ }
++ #endif // QT_CONFIG(xkbcommon)
++
++ void QWaylandKeyboardPrivate::sendRepeatInfo()
++ {
++ const auto resMap = resourceMap();
++ for (Resource *resource : resMap) {
++ if (resource->version() >= WL_KEYBOARD_REPEAT_INFO_SINCE_VERSION)
++ send_repeat_info(resource->handle, repeatRate, repeatDelay);
++ }
++ }
++
++ /*!
++ * \class QWaylandKeyboard
++ * \inmodule QtWaylandCompositor
++ * \since 5.8
++ * \brief The QWaylandKeyboard class represents a keyboard device.
++ *
++ * This class provides access to the keyboard device in a QWaylandSeat. It corresponds to
++ * the Wayland interface wl_keyboard.
++ */
++
++ /*!
++ * Constructs a QWaylandKeyboard for the given \a seat and with the given \a parent.
++ */
++ QWaylandKeyboard::QWaylandKeyboard(QWaylandSeat *seat, QObject *parent)
++ : QWaylandObject(* new QWaylandKeyboardPrivate(seat), parent)
++ {
++ Q_D(QWaylandKeyboard);
++ connect(&d->focusDestroyListener, &QWaylandDestroyListener::fired, this, &QWaylandKeyboard::focusDestroyed);
++ auto keymap = seat->keymap();
++ connect(keymap, &QWaylandKeymap::layoutChanged, this, &QWaylandKeyboard::updateKeymap);
++ connect(keymap, &QWaylandKeymap::variantChanged, this, &QWaylandKeyboard::updateKeymap);
++ connect(keymap, &QWaylandKeymap::optionsChanged, this, &QWaylandKeyboard::updateKeymap);
++ connect(keymap, &QWaylandKeymap::rulesChanged, this, &QWaylandKeyboard::updateKeymap);
++ connect(keymap, &QWaylandKeymap::modelChanged, this, &QWaylandKeyboard::updateKeymap);
++ #if QT_CONFIG(xkbcommon)
++ d->createXKBKeymap();
++ #endif
++ }
++
++ /*!
++ * Returns the seat for this QWaylandKeyboard.
++ */
++ QWaylandSeat *QWaylandKeyboard::seat() const
++ {
++ Q_D(const QWaylandKeyboard);
++ return d->seat;
++ }
++
++ /*!
++ * Returns the compositor for this QWaylandKeyboard.
++ */
++ QWaylandCompositor *QWaylandKeyboard::compositor() const
++ {
++ Q_D(const QWaylandKeyboard);
++ return d->seat->compositor();
++ }
++
++ /*!
++ * \internal
++ */
++ void QWaylandKeyboard::focusDestroyed(void *data)
++ {
++ Q_UNUSED(data);
++ Q_D(QWaylandKeyboard);
++ d->focusDestroyListener.reset();
++
++ d->focus = nullptr;
++ d->focusResource = nullptr;
++ }
++
++ void QWaylandKeyboard::updateKeymap()
++ {
++ Q_D(QWaylandKeyboard);
++ d->pendingKeymap = true;
++ d->maybeUpdateKeymap();
++ }
++
++ /*!
++ * Returns the client that currently has keyboard focus.
++ */
++ QWaylandClient *QWaylandKeyboard::focusClient() const
++ {
++ Q_D(const QWaylandKeyboard);
++ if (!d->focusResource)
++ return nullptr;
++ return QWaylandClient::fromWlClient(compositor(), d->focusResource->client());
++ }
++
++ /*!
++ * Sends the current key modifiers to \a client with the given \a serial.
++ */
++ void QWaylandKeyboard::sendKeyModifiers(QWaylandClient *client, uint32_t serial)
++ {
++ Q_D(QWaylandKeyboard);
++ QtWaylandServer::wl_keyboard::Resource *resource = d->resourceMap().value(client->client());
++ if (resource)
++ d->send_modifiers(resource->handle, serial, d->modsDepressed, d->modsLatched, d->modsLocked, d->group);
++ }
++
++ /*!
++ * Sends a key press event with the key \a code to the current keyboard focus.
++ */
++ void QWaylandKeyboard::sendKeyPressEvent(uint code)
++ {
++ Q_D(QWaylandKeyboard);
++ d->sendKeyEvent(code, WL_KEYBOARD_KEY_STATE_PRESSED);
++ }
++
++ /*!
++ * Sends a key release event with the key \a code to the current keyboard focus.
++ */
++ void QWaylandKeyboard::sendKeyReleaseEvent(uint code)
++ {
++ Q_D(QWaylandKeyboard);
++ d->sendKeyEvent(code, WL_KEYBOARD_KEY_STATE_RELEASED);
++ }
++
++ void QWaylandKeyboardPrivate::checkAndRepairModifierState(QKeyEvent *ke)
++ {
++ #if QT_CONFIG(xkbcommon)
++ if (ke->modifiers() != currentModifierState) {
++ if (focusResource && ke->key() != Qt::Key_Shift
++ && ke->key() != Qt::Key_Control && ke->key() != Qt::Key_Alt
++ && ke->key() != Qt::Key_AltGr) {
++ // Only repair the state for non-modifier keys
++ // ### slightly awkward because the standard modifier handling
++ // is done by QtWayland::WindowSystemEventHandler after the
++ // key event is delivered
++ uint32_t mods = 0;
++
++ if (shiftIndex == 0 && controlIndex == 0)
++ maybeUpdateXkbScanCodeTable();
++
++ if (ke->modifiers() & Qt::ShiftModifier)
++ mods |= 1 << shiftIndex;
++ if (ke->modifiers() & Qt::ControlModifier)
++ mods |= 1 << controlIndex;
++ if (ke->modifiers() & Qt::AltModifier)
++ mods |= 1 << altIndex;
++ if ((ke->modifiers() & Qt::GroupSwitchModifier) && mod5Index != XKB_MOD_INVALID)
++ mods |= 1 << mod5Index;
++ qCDebug(qLcWaylandCompositor) << "Keyboard modifier state mismatch detected for event" << ke << "state:" << currentModifierState << "repaired:" << Qt::hex << mods;
++ // Preserve modsLocked so that NumLock and CapsLock state is not lost
++ send_modifiers(focusResource->handle, compositor()->nextSerial(), mods,
++ 0, modsLocked, group);
++ currentModifierState = ke->modifiers();
++ }
++ }
++ #else
++ Q_UNUSED(ke);
++ #endif
++ }
++
++ /*!
++ * \property QWaylandKeyboard::repeatRate
++ *
++ * This property holds the rate at which key repeat events are generated by the
++ * keyboard, expressed in characters per second.
++ */
++
++ /*!
++ * Returns the current repeat rate.
++ */
++ quint32 QWaylandKeyboard::repeatRate() const
++ {
++ Q_D(const QWaylandKeyboard);
++ return d->repeatRate;
++ }
++
++ /*!
++ * Sets the repeat rate to \a rate.
++ */
++ void QWaylandKeyboard::setRepeatRate(quint32 rate)
++ {
++ Q_D(QWaylandKeyboard);
++
++ if (d->repeatRate == rate)
++ return;
++
++ d->sendRepeatInfo();
++
++ d->repeatRate = rate;
++ Q_EMIT repeatRateChanged(rate);
++ }
++
++ /*!
++ * \property QWaylandKeyboard::repeatDelay
++ *
++ * This property holds the delay before key repeat events begin, expressed in
++ * milliseconds.
++ */
++
++ /*!
++ * Returns the current repeat delay.
++ */
++ quint32 QWaylandKeyboard::repeatDelay() const
++ {
++ Q_D(const QWaylandKeyboard);
++ return d->repeatDelay;
++ }
++
++ /*!
++ * Sets the repeat delay to \a delay.
++ */
++ void QWaylandKeyboard::setRepeatDelay(quint32 delay)
++ {
++ Q_D(QWaylandKeyboard);
++
++ if (d->repeatDelay == delay)
++ return;
++
++ d->sendRepeatInfo();
++
++ d->repeatDelay = delay;
++ Q_EMIT repeatDelayChanged(delay);
++ }
++
++ /*!
++ * Returns the currently focused surface.
++ */
++ QWaylandSurface *QWaylandKeyboard::focus() const
++ {
++ Q_D(const QWaylandKeyboard);
++ return d->focus;
++ }
++
++ /*!
++ * Sets the current focus to \a surface.
++ */
++ void QWaylandKeyboard::setFocus(QWaylandSurface *surface)
++ {
++ Q_D(QWaylandKeyboard);
++ d->focused(surface);
++ }
++
++ /*!
++ * \internal
++ */
++ void QWaylandKeyboard::addClient(QWaylandClient *client, uint32_t id, uint32_t version)
++ {
++ Q_D(QWaylandKeyboard);
++ d->add(client->client(), id, qMin<uint32_t>(QtWaylandServer::wl_keyboard::interfaceVersion(), version));
++ }
++
++ uint QWaylandKeyboard::keyToScanCode(int qtKey) const
++ {
++ uint scanCode = 0;
++ #if QT_CONFIG(xkbcommon)
++ 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
++
++ #include "moc_qwaylandkeyboard.cpp"