使用 Wayland 在 Linux 上获取 Capslock 状态

Posted

技术标签:

【中文标题】使用 Wayland 在 Linux 上获取 Capslock 状态【英文标题】:Get Capslock state on Linux with Wayland 【发布时间】:2019-10-22 07:37:11 【问题描述】:

我正在努力完成以下任务:对于我们的跨平台应用程序,我想为用户启用大写锁定警告。这在 Windows 和 macOS 上完美运行,有点不必要的复杂,但在带有 X11 的 Linux 上是可行的,尽管我不知道如何在 Wayland 上正确地做到这一点。

我们使用的是 Qt5,所以我可以使用的 Qt API 越多越好。我看到 Qt 有一个非常广泛的 Wayland 框架,但它似乎主要是为编写自己的合成器而设计的,而不是用于访问底层平台插件的细节。

这是我所拥有的代码:

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

// namespace required to avoid name ***es 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 上出现了一些东西。

【问题讨论】:

我只是在谷歌上搜索了半个小时,因为我很难相信这不仅仅是keyboardModifiers() 的电话。 :-( 如果没有其他帮助,您仍然可以选择通过源代码woboq: qt5/qtwayland/ online 进行斗争。虽然,对于其他 Qt 问题,我意识到某些有用的东西有时在内部可用,但(无论出于何种原因)被 API 隐藏,即无法访问在应用中。至少,这些东西的实现仍然可以作为“备忘单”。 不幸的是,Capslock 不是真正的修饰符。我能想到的唯一 Qt 解决方案是检查字符是否为大写,但没有按下 shift。但是,这仅适用于显式按键事件,并且仅当键入的字符是字母字符并且您实际上正在键入时(例如,不是复制粘贴)。是的,Qt 倾向于隐藏有用的东西。遗憾的是,即使对于像 QPlatformNativeInterface 这样有用的 API,我也必须使用 Qt 私有标头。我想这就是 OOP 的诅咒。 对于复制粘贴,CAPS-LOCK 的状态应该是无关紧要的,不是吗?所以,你对键盘事件的“解决方法”对我来说听起来很合理......此外,在按下任何键之前不提供警告可能会被认为是令人讨厌的事情,但我(我自己)可以忍受这个...... 我仍然想显示警告,而不仅仅是在用户已经输入内容之后,然后必须显示密码字段以更正之后输入的其他字符。此外,字母字符仅构成可能的密码符号的一小部分。如果用户不使用英语,则该部分甚至更小。我认为这是一个非常肮脏且不可靠的解决方法。 嗯,我已经浏览过了,但没有什么立即有用的东西。 【参考方案1】:

我找到了解决方案,但我对它还很不满意。

我在这里使用 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 ***es 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&lt;KWayland::Client::Registry&gt; 成员。

这个解决方案基本上是可行的,但是会受到 KWin 中的错误或协议的怪异(我不知道是哪一个)的影响。按下 Capslock 键将触发内部 lambda 回调两次:第一次设置locked 中的第 2 位,第二次在未设置的情况下释放键。没有可靠的方法可以根据传递的其他参数过滤掉第二次激活(我尝试在depressed != 0 时忽略,但它没有按预期工作)。之后按任何其他键将再次触发回调并再次设置该位。因此,当您实际键入时,代码可以工作,但是当您只是按下 Capslock 时,这种行为很奇怪,并且比其他平台的解决方案更不可靠。由于等离子托盘中的 Capslock 指示器有同样的问题,我认为这是一个错误。

作为特定于 KDE 的解决方案,似乎还有另一个可以收听的接口,称为 org_kde_kwin_keystate (https://github.com/KDE/kwayland/blob/master/src/client/protocols/keystate.xml)。但是,当我在 KDE Neon VM 中测试它时,合成器并没有宣布这个协议扩展,所以我无法使用它。

【讨论】:

以上是关于使用 Wayland 在 Linux 上获取 Capslock 状态的主要内容,如果未能解决你的问题,请参考以下文章

linux (wayland) 上的 SoapUI - 高 DPI/4K 缩放问题

基于am5718的ARM-Linux开发wayland和weston的介绍

Wayland架构

wayland详解

转载Wayland与Weston原理介绍

Qt 使应用程序始终在 Weston/Wayland 平台上