如何让一个窗口始终保持在最上层?

8

我创建了一个无边框的Qt/QML窗口,我想知道有哪些编程方式可以设置其“始终置顶”系统菜单标志。按下ALT+SPACE可以打开无边框窗口的系统菜单,通过点击“始终置顶”选项,窗口会一直保持在最上层,但我没有找到编程实现同样效果的方法。 Qt.WindowStaysOnTopHint不起作用,尝试使用wmctrl -r "window name" -b add,above也不行,即使wmctrl对其他窗口有效。 wmctrl对我的感兴趣的窗口无效,显然与wmctrl -l的机器名称列中的N/A有关:

francisco@Ubuntu:~$ wmctrl -l
0x02600006  0 Ubuntu Área de trabalho
0x03c00002  0 Ubuntu XdndCollectionWindowImp
0x03c00005  0 Ubuntu unity-launcher
0x03c00008  0 Ubuntu unity-panel
0x03c0000b  0 Ubuntu unity-dash
0x03c0000c  0 Ubuntu Hud
0x046000b3  0 Ubuntu How to make a window aways on top? - Stack Overflow - Mozilla Firefox
0x0520000b  0    N/A Qt Creator
0x05002396  0 Ubuntu francisco@Ubuntu: ~
0x0540000b  0    N/A backlight

我也按照这个步骤操作,但对于提问者来说,它对我也没有用,出现同样的问题。 _NET_WM_STATE_ABOVE被设置了,但是当聚焦窗口并再次检查标志时,它就不再存在了,只有在通过系统菜单点击时才会保留。

这是QML代码:https://gist.github.com/oblitum/8050586

相关的askubuntu问题:https://askubuntu.com/questions/394998

编辑

注意

在相关的askubuntu问题中,发现使用wmctrl针对某些窗口名称进行定位会出现错误。使用wmctrl -i -r <window id> -b add, above也可以解决此问题。


问题是Unity是否正确实现了这种行为。Unity是否提供一种让用户始终保持窗口置顶的方法? - Frank Osterfeld
我认为Frank理解了,他做出了相应的回复。;-) - László Papp
@LaszloPapp 我不明白,我是一个用户,我正在使用带有Unity的Ubuntu,我可以用鼠标设置标志,那又怎样? - oblitum
@LaszloPapp 我还没有确定那个。 - oblitum
我发现这个问题和答案非常有用,可惜被踩了... - oblitum
显示剩余3条评论
3个回答

8

EWMH规范明确指出:

_NET_WM_STATE_ABOVE和_NET_WM_STATE_BELOW主要用于用户偏好设置,不应该被应用程序使用来吸引对话框的注意力(在这种情况下应该使用Urgency提示,请参见“紧急情况”一章)。

因此窗口管理器没有责任去尊重直接通过XChangeProperty设置此属性的应用程序。这个属性只能通过向窗口管理器监听的根窗口发送客户端消息进行更改。

我不知道如何在高级GUI工具包(如Qt)中实现它,但是在普通的X11中可以这样做(请参阅EWMH规范,或者_wnck_change_state以获得示例实现)。

//file: test.c
//to build it, run
//shell> gcc test.c -lX11

#include <X11/Xlib.h>   

#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  */


// change a window's _NET_WM_STATE property so that it can be kept on top.
// @display: x11 display singleton.
// @xid    : the window to set on top.
Status x11_window_set_on_top (Display* display, Window xid)
{
    XEvent event;
    event.xclient.type = ClientMessage;
    event.xclient.serial = 0;
    event.xclient.send_event = True;
    event.xclient.display = display;
    event.xclient.window  = xid;
    event.xclient.message_type = XInternAtom (display, "_NET_WM_STATE", False);
    event.xclient.format = 32;

    event.xclient.data.l[0] = _NET_WM_STATE_ADD;
    event.xclient.data.l[1] = XInternAtom (display, "_NET_WM_STATE_ABOVE", False);
    event.xclient.data.l[2] = 0; //unused.
    event.xclient.data.l[3] = 0;
    event.xclient.data.l[4] = 0;

    return XSendEvent (display, DefaultRootWindow(display), False,
                       SubstructureRedirectMask|SubstructureNotifyMask, &event);
}

// a sample main function for testing.
// shell> ./a.out window_xid
int main (int argc, char** argv)
{
    Window xid = strtol (argv[1], NULL, 0); 
    Display* display = XOpenDisplay (NULL);

    x11_window_set_on_top (display, xid);

    XFlush (display); //for simplicity, no event loops here.

    XCloseDisplay (display);
}

请注意,在某些X11环境(例如Compiz)中,系统菜单是由一个独立的装饰程序而不是合成窗口管理器提供的。

0
在Go语言中,这就是你要做的:
import (
    "github.com/BurntSushi/xgb"
    "github.com/BurntSushi/xgb/xproto"
)

func (window *Window) AlwaysOnTop() {
    xid := xproto.Window(window.WinId())
    X, err := xgb.NewConn()
    if err != nil {
        log.Println(err)
        return
    }
    defer X.Close()

    state, err := xproto.InternAtom(X, false, uint16(len("_NET_WM_STATE")),
        "_NET_WM_STATE").Reply()
    if err != nil {
        log.Println(err)
        return
    }

    stateAbove, err := xproto.InternAtom(X, false,
        uint16(len("_NET_WM_STATE_ABOVE")), "_NET_WM_STATE_ABOVE").Reply()
    if err != nil {
        log.Println(err)
        return
    }

    evt := xproto.ClientMessageEvent{
        Window: xid,
        Format: 32,
        Type:   state.Atom,
        Data: xproto.ClientMessageDataUnionData32New([]uint32{
            _NET_WM_STATE_ADD,
            uint32(stateAbove.Atom),
            0,
            0,
            0,
        }),
    }

    err = xproto.SendEventChecked(X, false, xproto.Setup(X).DefaultScreen(X).Root,
        xproto.EventMaskSubstructureRedirect|xproto.EventMaskSubstructureNotify,
        string(evt.Bytes())).Check()
    if err != nil {
        log.Println(err)
    }
}

window.WinId() 是一个窗口的本地 X11 句柄。


-1

我希望我正确理解了问题,因为您正在尝试通过菜单选项将QML视图启动到“始终置顶”模式。

我在Windows上尝试了以下代码,并在我的主要程序中运行成功,以便始终将窗口显示在最上层,因此我相信视图对象也可以从菜单选项进行更改。

QApplication app(argc, argv);
QDeclarativeView viewer;
**viewer.setWindowFlags(Qt::WindowStaysOnTopHint);**
viewer.setSource(QUrl::fromLocalFile("TestView.qml"));
viewer.showNormal();
return app.exec();

谢谢,Zeeshan


抱歉,但正如我所说的,这个 WindowStaysOnTopHint 不起作用,QML 窗口在启动时已经设置了此标志。 - oblitum
感谢您的关注。当我使用WindowStaysOnTopHint设置窗口样式时,我发现我的窗口并不总是置顶的,因此一旦我应用了setWindowFlags(Qt::WindowStaysOnTopHint);窗口就会一直保持在最上层。因此,我认为QML在启动时没有默认设置此标志。我建议运行示例并观察其行为。 - Zeeshan
我尝试为QQuickWindow设置它的方式是mywindow->setFlags(mywindow->flags() | Qt::WindowStaysOnTopHint),但这并没有起作用。 - oblitum
1
我刚刚阅读了有关Qt:WindowsStaysOnTopHint的文档,其中提到在某些情况下,您可能还需要传递Qt :: X11BypassWindowManagerHint标志才能使其正常工作。 如需进一步了解,请访问http://qt-project.org/doc/qt-5.0/qtcore/qt.html#WindowType-enum链接,您将找到一个注释,其中指出:“请注意,在X11上的某些窗口管理器上,您还必须传递Qt :: X11BypassWindowManagerHint标志才能使此标志正常工作。” 希望这可以解决问题。 - Zeeshan

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