使用 Wayland 在 Linux 上获取 Capslock 状态

Get Capslock state on Linux with Wayland

本文关键字:获取 Capslock 状态 Linux Wayland 使用      更新时间:2023-10-16

我正在努力完成以下任务:对于我们的跨平台应用程序,我想为用户启用大写警告。这在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);
}
// ...
}

我的理解是,我必须以某种方式掌握 Waylandwl_seat对象,其中包含有关wl_keyboard的信息。但是,如果不实例化各种上下文,我就无法找到仅从wl_display对象访问这些对象的方法。Qt应用程序本身已经作为Wayland客户端运行,所以我认为应该有一种方法可以访问这些对象。不幸的是,Wayland关于这方面的文档非常稀疏,对于不熟悉整个架构的人来说非常不透明,而且Wayland的用户群仍然太小,以至于谷歌上的东西突然出现。

我找到了一个解决方案,但我对此远不满意。

我在这里使用KWayland,但当然可以使用普通的Wayland C API。不过,请准备好编写 200-300 行额外的样板代码。夸兰德有点抽象,但它仍然很冗长。即使是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 键将触发内部 lambda 回调两次:第一次是设置了位 2,第二次是在未设置的情况下释放位locked2。没有可靠的方法可以根据传递的其他参数过滤掉第二次激活(我尝试在depressed != 0时忽略,但它没有按预期工作(。之后按任何其他键将第三次触发回调,并再次设置位。因此,当您实际键入时,代码可以工作,但是当您只是按Capslock时,行为很奇怪,并且不如其他平台的解决方案可靠。由于等离子托盘中的Capslock指示器具有相同的问题,因此我认为这是一个错误。

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