Xlib居中窗口

10

我正在编写一个使用 Xlib 的应用程序,我希望窗口居中显示。我已经使用了 XMoveWindow(desktopWidth - width) / 2, (desktopHeight - height) / 2,这使得窗口大致处于正确的位置。

但是问题在于宽度和高度是客户端区域的大小,而不是整个窗口的大小。是否有办法让我获取窗口的整个大小?

由于我正在使用 GlxOpenGL,所以我需要使用 Xlib。我不想使用 SDL,也不想使用庞大的图形库。


1
我怀疑一般情况下这是不可能的。有些窗口管理器(例如Compiz)完全独立于客户端窗口绘制窗口装饰。客户端不知道也无法知道装饰的大小(除非使用某些特定于Compiz的技巧)。在大多数其他窗口管理器中,客户端窗口的父级或祖先是WM装饰窗口,您可以检查它以了解装饰的大小。您仍然需要调用XMoveWindow来移动客户区域,但可以调整装饰的大小。 - n. m.
@n.m. 您可以在XSetWindowAttributes中使用override_redirect来防止WM装饰。 - luser droog
@luserdroog:是的,但那不是问题的关键... - n. m.
2个回答

11
有多种方法可以实现这个目标,具体取决于你为什么要这样做。前两种方法大多数窗口管理器都“官方支持”并在规范中描述,然后就变成了脆弱的黑客攻击。

语义化

规范鼓励您使用_NET_WM_WINDOW_TYPE而不是设置位置,如果有意义的话。请参见http://standards.freedesktop.org/wm-spec/wm-spec-1.3.html#id2507144 例如,DIALOG类型(或设置了WM_TRANSIENT_FOR提示的窗口)通常会居中于其父窗口或屏幕上,而_NET_WM_WINDOW_TYPE_SPLASH(闪屏)类型通常会居中于屏幕上。这里的“通常”意味着“明智的窗口管理器可能会将其居中,而使用奇怪的窗口管理器的人不是您的问题,请让他们受苦”。
(另一个类似的提示,但不适用于此处,是_NET_WM_STATE_FULLSCREEN,它避免手动调整大小/定位以全屏显示。)
如果语义提示有效,则处理定位的窗口管理器代码应该比人工编码更加智能,例如它可能处理多头设置。设置适当的语义类型还可以使WM在定位之外更加智能。
重力
如果规格中没有帮助您的语义提示,则可以手动居中。重要的是要注意,窗口管理器允许忽略手动位置请求,并且其中一些可能会这样做。某些窗口管理器只有在您在WM_NORMAL_HINTS中设置USPosition标志时才会满足请求(此标志仅应在用户明确请求位置时设置,例如使用-geometry命令行选项)。其他人可能总是忽略请求。但是,您可能可以忽略这样做的WM;用户选择使用该WM。
你需补偿窗口装饰(标题栏等)的方式是使用 WM_NORMAL_HINTS 的 win_gravity 字段,该字段最初在 ICCCM 中定义(参见 http://tronche.com/gui/x/icccm/sec-4.html#s-4.1.2.3 ),但在 EWMH 中的实现说明中更好地指定了。http://standards.freedesktop.org/wm-spec/latest/ar01s09.html#id2570420 关于 WM_NORMAL_HINTS,请参见http://tronche.com/gui/x/xlib/ICC/client-to-window-manager/wm-normal-hints.html#XSizeHints(注意:属性类型为 WM_SIZE_HINTS ,而属性名称为 WM_NORMAL_HINTS,因此涉及两个不同的原子名称)。
要居中,您需要将win_gravity设置为Center,这样就可以定位窗口(包括其装饰)的中心而不是左上角。
win_gravity很少使用,并且在某些窗口管理器中可能存在错误,因为没有人去编码/测试它,但在更主流的窗口管理器中应该可以工作。
更新,可能会混淆的点:X协议中还有其他“引力”,特别是CreateWindow请求允许您设置“bit_gravity”和“win_gravity”;这些与XSizeHints.win_gravity不同。CreateWindow引力描述了当调整窗口大小时如何处理窗口的内容(像素/子窗口)。
基于猜测装饰大小的黑客
这是一种脆弱的黑客,但是...您可以尝试找出装饰大小,然后将其合并到定位中。
要获取窗口装饰的大小,一个方法是_NET_FRAME_EXTENTS提示,请参见http://standards.freedesktop.org/wm-spec/latest/ar01s05.html#id2569862 对于老式的窗口管理器(但不包括新型合成窗口管理器,尽管这些希望支持_NET_FRAME_EXTENTS),窗口装饰是一个X窗口,因此您可以获取其父窗口并查看其大小。
这两种方法的问题在于,在添加装饰之前必须映射窗口,因此您必须映射;等待获取MapNotify事件;然后获取装饰大小;然后移动窗口。这将不幸地导致用户可见的闪烁(窗口最初将出现,然后移动)。我认为没有办法在映射之前获取窗口装饰大小。
进一步深入可怕的黑客领域,您可以假设对于第一个映射的窗口之后的窗口,装饰将与先前映射的窗口匹配。(这不是一个合理的假设:不同类型的窗口可能具有不同的装饰。)
实施说明:请记住,装饰窗口随时可能被销毁,这将导致任何涉及该窗口的未完成的Xlib请求中的X错误,并默认退出您的程序。为避免这种情况,请在触摸不属于您的客户端的窗口时设置X错误处理程序。
覆盖重定向

使用override redirect是一种有副作用的大炮,如果你的目标只是居中一个窗口,这不是一个好主意。

如果在创建窗口时设置override redirect标志,则窗口管理器将不会管理其大小、位置、堆叠顺序、装饰或映射状态(窗口管理器对ConfigureRequest和MapRequest的重定向被覆盖)。

这对于用户认为是窗口的任何内容来说都是一个非常糟糕的想法。它通常用于工具提示和弹出菜单。如果在窗口上设置override redirect,则所有正常的窗口管理UI都将被破坏,堆叠顺序将最终变得基本随机(窗口将倾向于卡在顶部或底部,或者更糟的是与另一个客户端陷入无限循环的堆叠战斗中)。

但是,被override-redirected的窗口将没有装饰并且不会被WM触及,因此您可以确保将其居中而不受干扰。

(如果你只想要没有装饰,请使用像SPLASH这样的语义类型或使用“MWM”提示,不要使用override redirect。)

总结

简短的答案是,如果有适用的语义提示,请设置语义提示;否则使用XSizeHints.win_gravity=Center。

你可以看到为什么人们使用工具包和SDL ;-) 客户端与窗口管理器交互中存在许多奇怪的历史遗留问题和边角情况,设置窗口位置只是开始兴奋的开端!

我认为重力与装饰补偿无关。 - n. m.
它会影响整个窗口,包括框架,这就是为什么会对其产生影响。请参阅 http://standards.freedesktop.org/wm-spec/latest/ar01s09.html#id2570420 CenterGravity 将参考点设置为“_frame_窗口的中心”。你可能在想传递给 XCreateWindow() 的位重力和窗口重力,它们与窗口调整大小时窗口内容受到的影响有关;但它们与 WM_NORMAL_HINTS 中的重力字段无关。XSetWindowAttributes.win_gravity 和 XSizeHints.win_gravity 使用相同的 win_gravity 名称有些混淆,但它们是不同的东西。 - Havoc P
啊哈,谢谢。我不知道那个。由于某种原因,我确信 WM 在客户端窗口而不是框架窗口方面处理这些事情。 - n. m.

0
win_gravity并不常用,因为没有人费心编码/测试,所以在某些窗口管理器中可能存在缺陷,但在更主流的窗口管理器中应该可以正常工作。
显然Unity没有实现这个功能。测试表明XCB_GRAVITY_STATIC没有被尊重,并且通过快速查看Unity源代码,我找不到实现规范的这部分代码。

我看到有人对我的回答给了一个-1。我的回答不是胡乱猜测,我已经调查过了,并在这里创建了一个错误报告 https://bugreports.qt.io/browse/QTBUG-67757。 - gatis paeglis

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