Linux Qt 无边框窗体的移动以及禁止使用窗体的最大化
Linux Qt 无边框窗体移动的问题
网上有多种移动无边框 Qt 程序的方法,集中在通过 MousePressEvent 以及 MouseMoveEvent 两个事件的处理上,不过在 Linux 平台上,他们实现的移动效果不能满足我的要求,因为在 Gnome 的环境中无法移动出屏幕左侧(Ubuntu 系统中),这一点很不友好;在 QML 程序中,通过对 MouseArea 中重载 onClicked 函数以及 onPositionChanged 函数实现窗体移动,这两个函数的原理也是与 MousePressEvent 类似,实现的效果也类似,而且由于 QML 的绘制是由 OpenGl 进行的绘制,在 Qt5 中,这一部分还存在 BUG,移动时,窗体会出现闪烁的问题。
为了解决这个问题,通过对【网易云音乐 Linux 版】这个软件的分析,发现他调用的是 XLIB 库进行的窗体移动,所以为了实现类似的效果,通过 Google 了解到了如何通过 Xlib 库进行窗体移动,不过一般都没有实际的 Example,只查到可以通过
XSendEvent
设置
_NET_WM_MOVERESIZE
选项实现,正当我不知道如何使用的时候,无意间跳转到 github 网站,然后在 github 站内搜索,这里就会有许多的参考用例,在 Linux 平台许多的窗体程序就是调用 XLIB 进行的窗体移动,通过一番调试,最后成型的代码如下:
void System::MoveWindow(void)
XEvent event;
memset(&event, 0, sizeof(XEvent));
const auto pos = QCursor::pos();
Display *display = QX11Info::display();
event.xclient.type = ClientMessage;
event.xclient.message_type =
XInternAtom(display, "_NET_WM_MOVERESIZE", False);
event.xclient.display = display;
//wid 是当前程序的 window id,可以通过 QWidget->wId()获得,QWidget 必须实例化
event.xclient.window = wid;
event.xclient.format = 32;
event.xclient.data.l[0] = pos.x();
event.xclient.data.l[1] = pos.y();
event.xclient.data.l[2] = 8;
event.xclient.data.l[3] = Button1;
event.xclient.data.l[4] = 1;
XUngrabPointer(display, CurrentTime);
XSendEvent(display, QX11Info::appRootWindow(QX11Info::appScreen()),
False, SubstructureNotifyMask | SubstructureRedirectMask,
&event);
XFlush(display);
Linux Qt 无边框窗体禁止最大化与 Resize
即使我们定义了无边框的窗体,Linux 窗体管理器仍然会接管到你的窗体,当你对一个窗体按下 ALT + Space
时可以调出一个 Menu,他可以控制窗体的最大最小化以及移动等属性,这些设置都属于 Window Manager 处理的,不是你的 QT 程序能够接管的部分,假如我们并不希望我们的程序能够被最大化,那么仅仅使用 Qt 内部的隐藏最大化按钮是不能完全解决这个问题的,所以我们仍然需要用到 Xlib,为什么 Xlib 能够管理到 Window Manager 呢?因为 Xorg 程序是一个服务程序,整个系统中仅仅存在这个一个图形显示程序,其他的所有的图形程序都只是一个客户端,都需要将自己的图形处理发送给 Xorg 进行处理,也就是我能控制到 Xorg 就能控制整个系统的图形,只要我知道这个图形程序的 Wid,也是前文中提到的 WID,窗口句柄。
言归正传,既然知道控制 Xorg 就能控制整个窗体,那么 XLIB 就是控制 Xorg 的一个库,通过调用这个库就可以达到控制 Xorg 的效果,这一点上文的窗体移动已经看的出来了,为了实现大小固定, Qt 程序可以调用 setFixedSize 实现,但是我不知道为什么在 Qt 5.6 中,这个函数会将最小化按钮也禁止掉,也就是说我一旦设置了大小固定,那么最小化也不允许,我不知道这个是 QT 的 BUG,还是他们官方故意弄的一个狗屁设定,反正我需要的效果仅仅只是不允许最大化也不能 resize 窗体即可,但是最小化你的给我留着啊。好吧,既然 Qt 做不到那就只能请 Xlib 了,通过查阅 XLIB 官方手册,有专门讲到这一个功能的实现(好奇怪,还专门写了),要实现这个效果,只需要将窗体的 MaxSize 以及 MinSize 设置为一样就行了,那么问题来了,难道 Qt 中不是调用这个参数实现的?于是我又重新试了不调用 setFixedSize 而是去使用 Qt 中的 setMaxiumWidth 与 setMiniumWidth 函数进行测试,结果和 setFixedSize 一样,把我逼急了就去查看了 Qt 的源码,发现 setFixedSize 本来就是调用的这两个函数去固定长与宽的,我靠,也就是 Qt 还是做不到,难道 XLIB 的手册有错误?抱着试一试的态度我就去试试 XLIB 来设置大小,还是没有 example,老规矩,查 github.com 去找别人的代码,我去,居然成功了,还真是手册要求的那样,那么 QT 为什么会将最小化也禁止掉呢?脑子抽了?
XSizeHints *hints = XAllocSizeHints();
long userhints;
XGetWMNormalHints(QX11Info::display(), view.winId(), hints, &userhints);
hints->max_width = hints->min_width = 590;
hints->max_height = hints->min_height = 435;
hints->flags |= PMaxSize | PMinSize;
XSetWMNormalHints(QX11Info::display(), view.winId(), hints);
XFree(hints);
XFlush(QX11Info::display());
[2016-08-17 更新]
最近重新调整这个软件的时候发现,在低版本的 RHEL6 上进行静态 Qt 的编译发现上面这一段禁止窗体 Resize 的代码无法得到正确的效果(PS,Ubuntu 14.10 以及 RHEL7 版本可以得到效果),这就让我蛋疼了,因为软件需要能够全平台覆盖(所有的 RHEL6 以及 Ubuntu 14.04 以上的 Linux 版本),看起来这一段代码是不能够满足我的要求,那么只能继续使用 Qt 库自带的 setFixedSize 这个函数,虽然不能最小化但是为了全平台我也没有办法,当然不是真的没有办法,为了验证为什么 setFixedSize 会将最小化也一同禁止掉,特意查看了 Qt 4.8.7 的源码,代码如下:
if (q->minimumSize() == q->maximumSize()) {
// fixed size, remove the resize handle (since mwm/dtwm
// isn't smart enough to do it itself)
mwmhints.flags |= MWM_HINTS_FUNCTIONS;
if (mwmhints.functions == MWM_FUNC_ALL) {
mwmhints.functions = MWM_FUNC_MOVE;
} else {
mwmhints.functions &= ~MWM_FUNC_RESIZE;
if (mwmhints.decorations == MWM_DECOR_ALL) {
mwmhints.flags |= MWM_HINTS_DECORATIONS;
mwmhints.decorations = (MWM_DECOR_BORDER
| MWM_DECOR_TITLE
| MWM_DECOR_MENU);
} else {
mwmhints.decorations &= ~MWM_DECOR_RESIZEH;
if (q->windowFlags() & Qt::WindowMinimizeButtonHint) {
mwmhints.flags |= MWM_HINTS_DECORATIONS;
mwmhints.decorations |= MWM_DECOR_MINIMIZE;
mwmhints.functions |= MWM_FUNC_MINIMIZE;
if (q->windowFlags() & Qt::WindowMaximizeButtonHint) {
mwmhints.flags |= MWM_HINTS_DECORATIONS;
mwmhints.decorations |= MWM_DECOR_MAXIMIZE;
mwmhints.functions |= MWM_FUNC_MAXIMIZE;
if (q->windowFlags() & Qt::WindowCloseButtonHint)
mwmhints.functions |= MWM_FUNC_CLOSE;
源码位于 src/gui/kernel/qwidget_x11.cpp 中,可以看的出来,当 maximumSize == minimumSize
时,Qt 会对 Linux 的窗口管理器发送一些设置参数,其中就包括了 MWM_FUNC_MINIMIZE
,也就是当窗体的 windowFlags
不包含 Qt::WindowMinimizeButtonHint
这个标记时,就会禁用掉最小化,看到这里我就明白了,原来当设置 setFixedSize 时,如果不显式的声明需要最小化,系统是会将最小化也一同禁止掉的,同样的,也可以在 setFixedSize 是打开最大化属性,关闭属性等等。
基于此,我只需要在 setFixedSize 函数调用之前设置窗体的最小化标记显式声明即可。
w.setWindowFlags(Qt::WindowMinimizeButtonHint | Qt::FramelessWindowHint);