Skip to content

[新功能] 为 Linux 的 X11 和 wayland 提供弹出系统菜单支持 #202

@Wing-summer

Description

@Wing-summer

您好作者,目前该库只提供了 Windows 上的系统菜单弹出支持,对于 Linux 常见的桌面协议 X11 和 wayland 并没有提供支持。我尝试去实现功能支持,目前已经实现了,在 KDE 桌面环境测试,使用 Qt 6.10,弹出系统菜单的逻辑都在实现AbstractWindowContext::virtual_hook,下面是实现代码:

X11

编译通过需要链接 X11

在对应实现的实现文件需要引入头文件:

#include <X11/Xlib.h>
#include <X11/Xatom.h>
#undef CursorShape    // 避免和 Qt::CursorShape 冲突

然后在virtual_hook中实现:

if (id == ShowSystemMenuHook) {
    auto *x11app = qApp->nativeInterface<QNativeInterface::QX11Application>();
    if (!x11app)
        return;
    auto display = x11app->display();
    if (!display)
        return;
    // use window id (XID)
    Window xwin = static_cast<Window>(m_windowId);
    Atom atom = XInternAtom(display, "_GTK_SHOW_WINDOW_MENU", False);
    if (atom == None)
        return; // WM might not support this atom
    auto pos = static_cast<QPoint *>(data);
    XEvent ev;
    memset(&ev, 0, sizeof(ev));
    ev.xclient.type = ClientMessage;
    ev.xclient.window = xwin;
    ev.xclient.message_type = atom;
    ev.xclient.format = 32;
    
    ev.xclient.data.l[0] = 3;        // button
    ev.xclient.data.l[1] = pos->x(); // root x
    ev.xclient.data.l[2] = pos->y(); // root y
    ev.xclient.data.l[3] = 0;
    // send to the root so WM receives it
    Window root = DefaultRootWindow(display);
    XSendEvent(display, root, False, SubstructureRedirectMask | SubstructureNotifyMask,
               &ev);
    XFlush(display);
} 

Wayland

编译通过需要链接 wayland-client

在对应实现的实现文件需要引入头文件:

#include <QtGui/qpa/qplatformnativeinterface.h>
#include <wayland-client.h>

需要增加 xdg_toplevel_show_window_menu 实现函数(参考 QT 代码):

static inline void xdg_toplevel_show_window_menu(struct xdg_toplevel *xdg_toplevel,
                                                 struct wl_seat *seat, uint32_t serial,
                                                 int32_t x, int32_t y) {
    constexpr auto XDG_TOPLEVEL_SHOW_WINDOW_MENU = 4;
    wl_proxy_marshal_flags((struct wl_proxy *) xdg_toplevel, XDG_TOPLEVEL_SHOW_WINDOW_MENU,
                           NULL, wl_proxy_get_version((struct wl_proxy *) xdg_toplevel), 0,
                           seat, serial, x, y);
}

然后在virtual_hook中实现:

if (id == ShowSystemMenuHook) {
    auto *native = QGuiApplication::platformNativeInterface();
    auto *waylandApp = qApp->nativeInterface<QNativeInterface::QWaylandApplication>();
    if (!waylandApp) {
        return;
    }
    uint serial = waylandApp->lastInputSerial();
    wl_seat *seat = waylandApp->lastInputSeat();
    if (serial == 0 || !seat) {
        return;
    }
    void *rawToplevel = native->nativeResourceForWindow("xdg_toplevel", m_windowHandle);
    if (!rawToplevel) {
        return;
    }
    xdg_toplevel *toplevel = static_cast<xdg_toplevel *>(rawToplevel);
    auto pos = static_cast<QPoint *>(data);
    int lx = pos->x();
    int ly = pos->y();
    xdg_toplevel_show_window_menu(toplevel, seat, serial, lx, ly);

    wl_display *d = waylandApp->display();
    if (d){
        wl_display_flush(d);
    }
}

补充说明

  1. 对应的桌面协议需要链接对应的库,这样对于一个库来说不够解耦而过于笨重,不能为了支持这两种协议都要链接,这个是不可取的,是否要考虑类似 QtPlugin 的设计,这个我希望您拿主意,合并这个功能和考虑这个设计,会造成库架构的调整。
  2. 在 KDE 测试由于我桌面只有 wayland ,所以测试 x11 使用的是 xwayland 。功能测试的时候发现系统菜单弹出始终会偏移一段距离,我分析不出来为什么,麻烦您如果有业余时间能够深入研究一下。

Metadata

Metadata

Assignees

Labels

enhancementNew feature or request

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions