X11/Xlib:窗口始终置顶

6
一个窗口应该始终保持在所有其他窗口的顶部。在纯x11 / xlib中是否可能实现这一点?搜索“Always on top”和“x11” / “xlib”没有返回有用的结果。
如果可能的话,我会避免使用GTK +等工具包。
我正在使用带有gnome桌面的Ubuntu。在窗口菜单中,有一个“始终置顶”的选项。这是由X服务器还是窗口管理器提供的?如果是后者,是否有一个通用函数可以为几乎任何wm调用?或者如何以“X11通用”方式完成此操作?
编辑:我实现了fizzer的答案,现在有以下代码:
XSelectInput(this->display, this->window,
    ButtonPressMask |
    StructureNotifyMask |
    ExposureMask |
    KeyPressMask |
    PropertyChangeMask |
    VisibilityChangeMask ); 
// ...
// In a loop:
if (XPending(this->display) >= 0)
{
    XNextEvent(this->display, &ev);
    switch(ev.type) {
    // ...
    case VisibilityNotify:
        XRaiseWindow(this->display, this->window);
        XFlush(this->display);
    break;
    // ...
    }
}

但是,即使我的掩码正确,事件处理和触发也几乎从未执行过?!


几乎所有的低层窗口工具包都是用这两种语言或者两种语言之一定义的,所以如果可以做到,那么就可以用C/C++来实现。这并不是一个语言问题。 - Marcelo Cantos
修改了问题。确保问题不是关于是否可以使用C/C++,而是关于是否可以仅使用x11/xlib的最小绑定实现。 - Atmocreations
你能获取到其他事件类型吗? - fizzer
另外,你的窗口是否处于顶层 - 也就是根窗口的一个子窗口?你只能相对于兄弟窗口提升窗口。 - fizzer
没有头绪。大约12年没做过X了,之前是XFixes。 - fizzer
显示剩余2条评论
4个回答

15
#define _NET_WM_STATE_REMOVE        0    // remove/unset property
#define _NET_WM_STATE_ADD           1    // add/set property
#define _NET_WM_STATE_TOGGLE        2    // toggle property

Bool MakeAlwaysOnTop(Display* display, Window root, Window mywin)
{
    Atom wmStateAbove = XInternAtom( display, "_NET_WM_STATE_ABOVE", 1 );
    if( wmStateAbove != None ) {
        printf( "_NET_WM_STATE_ABOVE has atom of %ld\n", (long)wmStateAbove );
    } else {
        printf( "ERROR: cannot find atom for _NET_WM_STATE_ABOVE !\n" );
        return False;
    }
    
    Atom wmNetWmState = XInternAtom( display, "_NET_WM_STATE", 1 );
    if( wmNetWmState != None ) {
        printf( "_NET_WM_STATE has atom of %ld\n", (long)wmNetWmState );
    } else {
        printf( "ERROR: cannot find atom for _NET_WM_STATE !\n" );
        return False;
    }

    // set window always on top hint
    if( wmStateAbove != None )
    {
        XClientMessageEvent xclient;
        memset( &xclient, 0, sizeof (xclient) );
        //
        //window  = the respective client window
        //message_type = _NET_WM_STATE
        //format = 32
        //data.l[0] = the action, as listed below
        //data.l[1] = first property to alter
        //data.l[2] = second property to alter
        //data.l[3] = source indication (0-unk,1-normal app,2-pager)
        //other data.l[] elements = 0
        //
        xclient.type = ClientMessage;
        xclient.window = mywin;              // GDK_WINDOW_XID(window);
        xclient.message_type = wmNetWmState; //gdk_x11_get_xatom_by_name_for_display( display, "_NET_WM_STATE" );
        xclient.format = 32;
        xclient.data.l[0] = _NET_WM_STATE_ADD; // add ? _NET_WM_STATE_ADD : _NET_WM_STATE_REMOVE;
        xclient.data.l[1] = wmStateAbove;      //gdk_x11_atom_to_xatom_for_display (display, state1);
        xclient.data.l[2] = 0;                 //gdk_x11_atom_to_xatom_for_display (display, state2);
        xclient.data.l[3] = 0;
        xclient.data.l[4] = 0;
        //gdk_wmspec_change_state( FALSE, window,
        //  gdk_atom_intern_static_string ("_NET_WM_STATE_BELOW"),
        //  GDK_NONE );
        XSendEvent( display,
          //mywin - wrong, not app window, send to root window!
          root, // <-- DefaultRootWindow( display )
          False,
          SubstructureRedirectMask | SubstructureNotifyMask,
          (XEvent *)&xclient );

        XFlush(display);

        return True;
    }

    return False;
}

1
一个绝妙的解决方案,谢谢!我喜欢评论中与GDK的比较!不过我很好奇,为什么你没有设置xclient.send_event呢? - yatg
1
必须在调用XSendEvent之后调用XFlush,否则它无法将窗口置于最上层。 - yatg
1
晚了点评论,但是XSendEvent会为您设置xclient.send_event。 - Joe Sewell

7

不要使用XRaiseWindow()来尝试保持在顶部,某些窗口管理器将完全忽略它。对于那些没有忽略的窗口管理器,请考虑如果多个应用程序尝试执行此操作会发生什么。砰!这就是为什么窗口管理器负责堆栈窗口而不是应用程序的原因。

你需要使用扩展窗口管理器提示(EWMH)中定义的协议进行操作,请参阅:http://www.freedesktop.org/wiki/Specifications/wm-spec

具体来说,您需要使用_NET_WM_STATE_ABOVE,这就是“始终在顶部”菜单项的工作方式。

如果您没有使用工具包,则需要习惯于在工具包源代码中搜寻以了解如何执行操作。在这种情况下,您可以查看GTK +的X11后端中的gdk_window_set_keep_above()函数。这将显示如何使用_NET_WM_STATE_ABOVE提示。


谢谢。你说得没错,不使用XRaiseWindow()是正确的选择。这也是我的想法。但我之前已经看过gdk_window_set_keep_above(),并尝试向特定窗口发送XEvent "_NET_WM_STATE_ABOVE",但这根本没有任何作用... 我正在使用Compiz和Metacity。 - Atmocreations
在这里可能会犯一些错误,比如获取XSendEvent的细节错误,或者在尚未映射的窗口上使用事件时应该设置属性。此外,WM可能会忽略某些语义窗口类型的消息。无论如何,如果您发布一个单文件可编译的测试程序,人们可以尝试进行诊断。 - Havoc P
嗨Havoc!我想知道你是否能够给我提供GDK后端源代码的链接,这样我就可以看到他们如何执行gdk_window_set_keep_above。另外,如果你能够给我提供GTK源代码的链接,我也想研究一下gtk_window_set_above:https://developer.gnome.org/gtk3/unstable/GtkWindow.html#gtk-window-set-keep-above - yatg

2
我多年前就在Xlib中写过类似的东西。几行代码而已。当你的窗口部分遮挡时,会收到一个VisibilityNotify事件,然后调用XRaiseWindow函数即可。注意两个“始终置顶”的窗口重叠的情况。

3
这不是一个好主意,因为任何时候如果有两个应用程序这样做,它们会“争夺”顶部。相反,在EWMH中有一个专门的提示。 - Havoc P
在极少数情况下,如果您没有窗口管理器,那么这种技术几乎是您唯一的选择,但在这种情况下,您很可能完全掌控正在运行的内容,因此避免两个窗口竞争最高位置要简单得多。 - Michael Kohne

-8
例如,使用Actual Title Buttons(http://www.actualtools.com/titlebuttons/)等工具。它可以让任何窗口始终保持在最上层,卷起来,透明度等等。

3
这不是关于微软Windows或标题栏中的可点击按钮,你完全没有理解重点。 - Atmocreations

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接