c++ - 使用 Wayland 在 Linux 上获取 Capslock 状态

标签 c++ qt qt5 wayland

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

我们正在使用 Qt5,因此我可以为此使用的 Qt API 越多越好。我看到 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 上弹出。

最佳答案

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

我在这里使用的是 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 中的错误,要么是协议(protocol)的异常(我不知道是哪个)。按 Capslock 键将触发内部 lambda 回调两次:第一次是 locked 中的位 2|设置并在未设置的情况下第二次释放 key 。没有可靠的方法可以根据传递的其他参数过滤掉第二次激活(我在 depressed != 0 时尝试忽略,但它没有按预期工作)。之后按任何其他键将第三次触发回调,并再次设置该位。因此,当您实际键入时,代码可以正常工作,但当您只是按下 Capslock 时,行为会很奇怪,并且不如其他平台的解决方案可靠。由于 Plasma 托盘中的 Capslock 指示器也有同样的问题,我认为这是一个错误。

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

关于c++ - 使用 Wayland 在 Linux 上获取 Capslock 状态,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/58499240/

相关文章:

c++ - 有没有办法在不覆盖实际分配的情况下使用 vulkan 内部分配回调?

c++ - 重载 'min(int&, int&)' 的调用不明确

c++ - 条件循环 : variable vs function?

python - 为项目选择合适的 GUI 框架

c++ - 为什么程序退出功能在 Release模式下不起作用

c++ - 如何在 C++ 中使用 C 空括号函数?

c++ - 内存管理 - addwidget 是否分配父级

qt - 如何使用 Qt/KDE 将 base64 值转换为十六进制值?

c++ - 我可以为 2 个或更多应用程序进行 QSettings 设置吗?

c++ - qmake:检测目标位宽(32 位或 64 位)