Получить состояние Capslock в Linux с Wayland - PullRequest
1 голос
/ 22 октября 2019

Я борюсь со следующей задачей: для нашего кроссплатформенного приложения я хочу включить предупреждение о блокировке для пользователя. Это прекрасно работает в Windows и macOS и немного излишне сложно, но выполнимо в Linux с X11, хотя я не могу выяснить, как это сделать правильно в Wayland.

Мы используем Qt5, поэтому чем больше API Qt яМожно использовать для этого, тем лучше. Я вижу, что Qt имеет очень обширную среду Wayland, но, похоже, она предназначена в первую очередь для написания вашего собственного композитора, а не для доступа к специфике базового плагина платформы.

Вот код, который у меня есть:

#include <QGuiApplication>
#include <qpa/qplatformnativeinterface.h>

// namespace required to avoid name clashes with declarations in XKBlib.h
namespace X11
{
#include <X11/XKBlib.h>
}

void checkCapslockState()
{
    // ... Windows and macOS one-liners

    // Here starts the Linux mess.
    // At least I can query the display with this for both X11 and Wayland.
    QPlatformNativeInterface* native = QGuiApplication::platformNativeInterface();
    auto* display = native->nativeResourceForWindow("display", nullptr);
    if (!display) {
        return;
    }

    const QString platform = QGuiApplication::platformName();
    if (platform == "xcb") {
        unsigned state = 0;
        if (X11::XkbGetIndicatorState(reinterpret_cast<X11::Display*>(display), XkbUseCoreKbd, &state) == Success) {
            // works fine
            newCapslockState = ((state & 1u) != 0);
        }
    } else if (platform == "wayland") {
        // but how to proceed here?
        // struct wl_display* waylandDisplay = reinterpret_cast<struct wl_display*>(display);
    }

    // ...
}

Насколько я понимаю, мне нужно каким-то образом овладеть объектом Wayland wl_seat, который содержит информацию о wl_keyboard. Однако я не могу найти способ получить доступ к этим объектам только из одного объекта wl_display, не создавая всевозможные контексты. Само приложение Qt уже работает как клиент Wayland, так что, я бы предположил, должен быть способ доступа к этим объектам. К сожалению, документация Wayland по этому вопросу очень скудна и непрозрачна для тех, кто не знаком со всей архитектурой, а база пользователей Wayland все еще слишком мала, чтобы что-то всплыло в Google.

1 Ответ

0 голосов
/ 31 октября 2019

Я нашел решение, но я далеко не удовлетворен им.

Я использую здесь KWayland, но, конечно, можно использовать простой Wayland C API. Будьте готовы написать 200-300 дополнительных строк стандартного кода. KWayland немного абстрагируется, но все еще довольно многословно. Даже решение X11 короче, не говоря уже об однострочности Windows и macOS.

Я не думаю, что это состояние, в котором Wayland будет довольно успешным в долгосрочной перспективе. Ничего страшного в том, чтобы иметь такой контроль на самом низком уровне, но для этого нужны правильные абстракции высокого уровня.

Короче говоря, вот полный код для всех платформ:

#include <QGuiApplication>

#if defined(Q_OS_WIN)
#include <windows.h>
#elif defined(Q_OS_MACOS)
#include <CoreGraphics/CGEventSource.h>
#elif defined(Q_OS_UNIX)
#include <qpa/qplatformnativeinterface.h>
// namespace required to avoid name clashes with declarations in XKBlib.h
namespace X11
{
#include <X11/XKBlib.h>
}
#include <KF5/KWayland/Client/registry.h>
#include <KF5/KWayland/Client/seat.h>
#include <KF5/KWayland/Client/keyboard.h>
#endif

void MyCls::checkCapslockState()
{
    const QString platform = QGuiApplication::platformName();

#if defined(Q_OS_WIN)

    newCapslockState = (GetKeyState(VK_CAPITAL) == 1);

#elif defined(Q_OS_MACOS)

    newCapslockState = ((CGEventSourceFlagsState(kCGEventSourceStateHIDSystemState) & kCGEventFlagMaskAlphaShift) != 0);

#elif defined(Q_OS_UNIX)

    // get platform display
    QPlatformNativeInterface* native = QGuiApplication::platformNativeInterface();
    auto* display = native->nativeResourceForWindow("display", nullptr);
    if (!display) {
        return;
    }

    if (platform == "xcb") {
        unsigned state = 0;
        if (X11::XkbGetIndicatorState(reinterpret_cast<X11::Display*>(display), XkbUseCoreKbd, &state) == Success) {
            newCapslockState = ((state & 1u) != 0);
        }
    } else if (platform == "wayland") {
        if (!m_wlRegistry) {
            auto* wlDisplay = reinterpret_cast<struct wl_display*>(display);
            m_wlRegistry.reset(new KWayland::Client::Registry());
            m_wlRegistry->create(wlDisplay);
            m_wlRegistry->setup();

            // wait for a seat to be announced
            connect(m_wlRegistry.data(), &KWayland::Client::Registry::seatAnnounced, [this](quint32 name, quint32 version) {
                auto* wlSeat = new KWayland::Client::Seat(m_wlRegistry.data());
                wlSeat->setup(m_wlRegistry->bindSeat(name, version));

                // wait for a keyboard to become available in the seat
                connect(wlSeat, &KWayland::Client::Seat::hasKeyboardChanged, [wlSeat, this](bool hasKeyboard) {
                    if (hasKeyboard) {
                        auto* keyboard = wlSeat->createKeyboard(wlSeat);

                        // listen for a modifier change
                        connect(keyboard, &KWayland::Client::Keyboard::modifiersChanged,
                            [this](quint32 depressed, quint32 latched, quint32 locked, quint32 group) {
                            Q_UNUSED(depressed)
                            Q_UNUSED(latched)
                            Q_UNUSED(group)
                            newCapslockState = (locked & 2u) != 0;

                            // emit signals etc. here to notify outer non-callback
                            // context of the new value of newCapslockState
                        });
                    }
                });
            });
        }
    }

    // do something with the newCapslockState state for any
    // platform other than Wayland
}

m_wlRegistry определяется как QScopedPointer<KWayland::Client::Registry> элемент в заголовочном файле.

Это решение в основном работает, но страдает либо ошибкой в ​​KWin, либо странностью протокола (я не знаю, какой). Нажатие клавиши Capslock вызовет внутренний лямбда-обратный вызов дважды: первый раз с установленным битом 2 в locked и второй раз при отпускании ключа без него. Не существует надежного способа отфильтровать эту вторую активацию на основе других переданных параметров (я пытался игнорировать, когда depressed != 0, но он не работает должным образом). Нажатие любой другой клавиши после этого вызовет обратный вызов в третий раз с вновь установленным битом. Поэтому, когда вы на самом деле печатаете, код работает, но когда вы просто нажимаете Capslock, поведение странное и менее надежное, чем решения для других платформ. Поскольку индикатор Capslock на панели Plasma имеет ту же проблему, я предполагаю, что это ошибка.

В качестве решения для KDE существует другой интерфейс, который можно прослушивать, который называется org_kde_kwin_keystate (https://github.com/KDE/kwayland/blob/master/src/client/protocols/keystate.xml). Однако, когда я тестировал его на виртуальной машине KDE Neon, композитор не объявил об этом расширении протокола, поэтому я не смог его использовать.

...