2010年8月27日 星期五

親手打造 Window Manager - 監聽 Screen 上的 XEvent

Standard
在 Linux 等 Unix's like 的系統上,擁有數十載歷史的 X11 通常為主流的圖形介面支援方案,雖架構看似老舊,卻在世界上眾高手的努力下,成就了現在的各種極絢麗的桌面環境,而最為眾人所知的,便是 Compiz 這類『視窗管理器(Window Manager)』,其運用 3D 技術帶來震憾的桌面體驗,因此在目前主流的 Linux 發行版中,無一不預設搭載。而許多人在此時,才真正了解 Window Manager 的厲害和重要性,新興的系統中,無論是 Moblin 還是 Google Chrome OS,也都特地為自家系統打造專屬的 Window Manager。

然而,Window Manager 實作細節雖看起來複雜,但實際上沒有想像中這麼艱澀,單純透過 X Protocol 和 API 去控制 window 的行為,這類工作極為簡單,而真正令人感到棘手的,是在事件(event)處理和 ICCCM/EWMH 的部份。ICCCM/EWMH 現在是由 [FreeDesktop.org] 所維護,其定義了一系列的屬性,用來記錄視窗環境的狀態,並可供所有 X Client 存取,而 Window Manager 要做的就是成為一個常註程式(Daemon),並隨時去提供和更新這些狀態。不過由於 ICCCM/EWMH 這部份細節繁雜,暫不在本文討論範疇(有興趣可自行參閱 FreeDesktop.org EWMH Spec),我們主要是討論如何監聽畫面上視窗的 event。

一般 window 監聽自己的 XEvent 是家常便飯,只要有以 Xlib 寫過純 X Application 的人應該都多少有些了解,會比較不瞭解的部份,就是該如何去監聽整個 screen 上的 event,但在這之前,我們有必要先大致瞭解 X window 的架構。通常在 X 上所呈現的桌面環境,是一層層 window 疊起來而成,舉例來說,桌面上常見的工具列(Panel),上面的狀態 Icon ,如:網路管理、Pidgin Icon 等,都是一個個 window 放在 Panel 的 window 之上,而在 Panel 之後,到最底層便是 screen 的 Root Window,Root Window 在所有 window 之下。

所以,若要做到監聽整個 screen 下所有 window 的 event,想當然爾就是對 screen 的 Root Window 做監聽,但單單只是監聽 Root Window 並無法取得所有 Sub Window 的 event,必須利用 X 定義的 SubstructureRedirectMask 以及 SubstructureNotifyMask 此類 event mask 達成目的。在程式中,我們只需使用 XChangeWindowAttributes() 去設定 Root Window 的 Attributes,將 event mask 代入就可以。

這是簡單的範例:
XSetWindowAttributes attr;

attr.event_mask = SubstructureRedirectMask | SubstructureNotifyMask |
        ColormapChangeMask | ButtonPressMask | PropertyChangeMask |
        EnterWindowMask;

XChangeWindowAttributes(display, screen->root, CWEventMask, &attr);
XSync(display, False);

當 Root Window 的 Attributes 被設定後,我們的程式便可以去監聽 Screen 下所有 Window 的 XEvent,當有程式被執行要顯示在畫面上時,就會收到 ConfigureRequest 和 MapRequest Event。