[Qt C++] setWindowFlag 或是 move() 都沒有用:談談 Linux 視窗系統

這是遇到一個簡單的問題卻困擾了三天的紀錄。

視窗設計不如預期

這次我遇到的問題,是利用 Qt 為開發框架設計介面,有一些很基本的視窗顯示一直調整不好。我們通常可以調整視窗的上下層關係(Parent、Child),或是在螢幕上的初始位置、標題列是否顯示等等,當我在程式中設定完整了,視窗卻完全不受控制。

例如用得最多的 QWidget::setWindowFlag 調整標題列和放大縮小按鈕、對話框是否為互動視窗等等都失效。調整與主視窗的相對位置也沒有動靜,甚至是設定好某個對話框的 parent,正常來說 child 視窗預設會在 parent 的中間,而每次初始化都隨便亂跑。

影響桌面環境的視窗系統:X11 與 Wayland

至於到底是什麼蓋過了程式碼的設定,就要談談視窗系統(windowing system)。

視窗系統是圖形使用者介面(GUI)的構成元素,也就是說介面的字型、線條等等的長相,或是視窗的重疊關係、移動或改變尺寸,都與這個視窗系統相關。簡單來說,我們可以把這些看成是圖形化介面的 protocol,該怎麼顯示規則都訂好了。

而在 Linux 中有兩大視窗系統:X11 以及 Wayland。X11,或是 X Window System,是很經典的視窗系統,而相對較新的 Wayland 其實也已經出現十幾年了,較輕量並且漸漸取代 X11。雖然都是在 Linux 的桌面環境,但兩者處理不太一樣,所以知道視窗系統也很重要。可以利用:

echo $XDG_SESSION_TYPE

或是

[ -z $WAYLAND_DISPLAY ] || echo wayland

確認目前的桌面環境使用的視窗系統。

除了 Linux 之外,Microsoft 當然也有視窗系統,也就是我們熟悉的 Windows 系列(我到現在才發現它的名字這麼明示XD)。或許是因為我之前都是在 Windows 下開發,很難去直接察覺會是視窗系統的問題,直到近期大量接觸 Linux 才發現有這樣一個東西。

其實 Ubuntu 的視窗系統可以直接在登入畫面中更換,下方是查看系統設置的畫面,其中一項就是視窗系統。

問題癥結點:Wayland

經過以上介紹後,有沒有漸漸聞出一股解答漸漸浮現的味道?以下是 Qt 文件中針對 Wayland 特性的介紹:

On Wayland, programmatically setting or getting the position of a top-level window from the client-side is typically not supported. Technically speaking, it depends on the shell interface. For typical desktop compositors, however, the default shell interface will be XDG Shell, which does not support manual positioning of windows. In such cases, Qt will ignore calls to set the top-level position of a window, and, when queried, the window position will always be returned as QPoint(0, 0).

也就是說在 Wayland 的規定下,我們無法去指定視窗初始顯示位置,以及它沒有支援 top-level 視窗的設定,簡單來說子視窗預設並不會顯示在父視窗的中間,它初期永遠是出現在 (0, 0) 的位置或是上一層視窗的下一個排列位置。

解決方式

在發現問題時,我們是在 Ubuntu 22.04 開發,由於後續軟體要執行的作業系統的是 RHEL 那一系列的分支,而那個版本剛好比較舊一點點,預設是採用 X11,因此我們轉移開發環境後無痛解決,一些在程式中的多餘設定甚至可以刪除。

那如果今天我們軟體仍需要在 Ubuntu 較新的版本發行怎麼辦?

我一開始找到的方法:在 window flag 加上 QtCore.Qt.X11BypassWindowManagerHint 或是 QtCore.Qt.BypassWindowManagerHint

但是兩個對我來說都沒用,以下是我使用後成功的方式。我們可以在命令視窗中讓程式執行前先 export 環境變數,強制使用 X11 方式顯示:

export QT_QPA_PLATFORM=xcb

這一段也可以在 Qt Creator 中設定。

而如果單純是一次性的切換,就在命令視窗中用這個方式:

./XXX -platform xcb

XXX 是我們自己的執行檔。

當然最終顯示的結果仍是要看作業系統本身啦。像是前面我所提到的 QWidget::setWindowFlag 當初也以為萬用,可以自由設定視窗顯示方式。查資料後發現有個觀念蠻重要的:視窗系統才是大 Boss,不管程式碼設定上多完整,視窗管理都有可能繞過這些設定。

Wayland,純就外觀上來講蠻醜的,也不符合預期顯示模式
X11,外觀好看多了,也有照著設定描繪

沒想到只是換個視窗系統全部的問題都解決了。更令人煩躁的是,開發時很直覺會以為是自己程式問題,所以查詢時也容易往錯誤的方向,要不是剛好那陣子有個問題剛好也屬於程式碼無解,我也不會往上向作業系統那方面查詢的靈感。

另外最神奇的地方是,Ubuntu 22.04 即使目前的視窗系統是 Wayland,然而不論是重疊顯示、那些放大縮小按鍵的樣式等等,卻也是採用 X11!這題我目前無解啊……

中間有一度就是被此誤導,當時看其他軟體的對話框確實可以顯示在中間,因此一直以為和視窗系統沒關係,對照兩者差異後才發覺這個詭異之處。

很多框架都號稱跨平台,實際上還是有許多眉眉角角QQ


參考資料

讓我知道你在想什麼!

Picture of ALEX

ALEX

藍白拖愛好者,一事無成話偏多

Recent Posts

C++

NLP