粗略調查 XQueryPointer 背後的運作

Posted by 每特17劃 on 2023-03-10

粗略調查 XQueryPointer 背後的運作

這兩天於聊天頻道看到一個問題:

請問這個是函數封裝嗎~對c++還不太熟悉 我搜尋找不到 XQueryPointer 的 function 定義

註: 程式碼類似於 barrier/XWindowsImpl.cpp :

Bool XWindowsImpl::XQueryPointer(Display* display, Window w,
                                 Window* root_return, Window* child_return,
                                 int* root_x_return, int* root_y_return,
                                 int* win_x_return,  int* win_y_return,
                                 unsigned int* mask_return)
{
    return ::XQueryPointer(display, w, root_return, child_return, root_x_return,
                           root_y_return, win_x_return, win_y_return,
                           mask_return);
}

因爲,昨晚剛好有聊到 Xwindow System 及 libinput 的話題,加上剛好有人問到 QueryPointer 的問題,便 trace code 一番,並分享了一下看到的內容跟過程。

C++ 的 :: 開頭語法的疑問

很久沒用 C++ 了,看到 ::XLockDisplay(display);:: 開頭的寫法,一時不知道是什麼意思。 後來查到了, :: 是指 global namespace 。

參考資料如下:

程式架構

上面程式感覺像 Design Patterns 中的一段 p.63

Imgur

用途大約是:

  • XWindowsImpl 是要實作的類別, 而產生的物件是符合某個 XWindows 的通用規範。ex: IXWindowsImpl
  • 呼叫 XWindowsImpl::XQueryPointer() 時,背後會呼叫既存的 ::XQueryPointer()

XQueryPointer

我對後面發生什麼事好奇,請 ChatGPT 生一段 sample code 給我, 如下(截圖):

from Xlib import X, display

# 顯示器物件
d = display.Display()

# 螢幕物件
screen = d.screen()

# 根視窗
root = screen.root

# 取得滑鼠位置
pointer = root.query_pointer()

# 輸出滑鼠位置
print("滑鼠位置: (%d, %d)" % (pointer.root_x, pointer.root_y))

(註: 要裝 python-xlib 套件)

程式真的有執行如下圖

Imgur

下一步,根據裏面主要函式 root.query_pointer() 這個線索,去 GitHub 查背後內容, 找到 source code 片段:

    def query_pointer(self):
        return request.QueryPointer(display = self.display,
                                    window = self.id)

看出這有種類似發送 HTTP request 的味道, 進一步查 QueryPointer 的實作,找到 source code 如下:

class QueryPointer(rq.ReplyRequest):
    _request = rq.Struct(
        rq.Opcode(38),
        rq.Pad(1),
        rq.RequestLength(),
        rq.Window('window')
        )

    _reply = rq.Struct(
        rq.ReplyCode(),
        rq.Card8('same_screen'),
        rq.Card16('sequence_number'),
        rq.ReplyLength(),
        rq.Window('root'),
        rq.Window('child', (X.NONE, )),
        rq.Int16('root_x'),
        rq.Int16('root_y'),
        rq.Int16('win_x'),
        rq.Int16('win_y'),
        rq.Card16('mask'),
        rq.Pad(6),
        )

根據上面的程式片段,我注意到有 opcode38 這兩個關鍵字。 依此線索,之後在 google 查到相關的兩處相關定義:

概況

從上述的線索跟取得的資訊,推測取得滑鼠座標的框架大概是:

  • 視窗系統背後是 client, server 架構

  • 使用者應用程式,屬 client 端

  • User App 依通訊標準(XWindow prototol) 產生 request 封包,並向系統內的 XWindow server 發送要求。

    • 產生 request 封包有多種方式
      • 以 C/C++ 透過 libx11 (Xlib.h, libX11.so) 來生成 QueryPointer request 封包
      • 以 Python 透過 python-xlib, 來生成 QueryPointer request 封包
      • 以 Rust 生成 QueryPointer request 封包
  • XWindow server 處理完後回應資料給 User App。收到回應後的 User App 再繼續它的後續動作。

  • C/C++ => libX11.so => 產生 QueryPointer 要求封包 => 發給 Xwindow System(server)

  • Python => python-xlib(底層應該還是 libX11.so) => 產生 QueryPointer 要求封包 => 發給 Xwindow System(server)

Server 在哪?

在 Linux 上用 ps 指令查詢,找到 X server:

/usr/lib/Xorg :0 -seat seat0 -auth /run/lightdm/root/:0 -nolisten tcp vt7 -novtswitch

以下述的 netstat 指令找 Xorg 相關的線索:

sudo netstat -n -p | grep Xorg

得到結果如下:

unix  3      [ ]         STREAM     CONNECTED     21239    548/Xorg             @/tmp/.X11-unix/X0
unix  3      [ ]         STREAM     CONNECTED     31689    548/Xorg             @/tmp/.X11-unix/X0
unix  3      [ ]         STREAM     CONNECTED     19362    548/Xorg             @/tmp/.X11-unix/X0
unix  3      [ ]         STREAM     CONNECTED     24421    548/Xorg             @/tmp/.X11-unix/X0
unix  3      [ ]         STREAM     CONNECTED     24419    548/Xorg             @/tmp/.X11-unix/X0
unix  3      [ ]         STREAM     CONNECTED     25019    548/Xorg             @/tmp/.X11-unix/X0

大略得知, 系統內由 Xorg 運行 XWindow server ,並於 @/tmp/.X11-unix/X0 接收 User Apps 發來的 request.

X client 從那裏知道 X server 的指向位置?

透過 DISPLAY 這個環境變數,例如:

export DISPLAY=:0

Xephyr :42 &
DISPLAY=:42 ssh -Y host

註: Xephyr 爲一個 nested X server ,可在目前的 Xwindow 下執行另一個 X server.

Server 於何處接 XQueryPointer 的 request?

一樣進 GitHub 查 QueryPointer。 但直接查, 搜尋結果太多太雜。(如圖)

Imgur

憑經驗, freedesktop.org 是 XWindow 發展的主力組織之一,在搜尋 GitHub 搜尋時加上 org:freedesktop 的範圍條件, 可大幅減少搜尋雜音,從 32K 多筆結果精簡到約 50 多筆資料(如圖):

Imgur

進一步檢視後,不久就找到處理 QueryPointer request 的 source code:

Imgur

此處再往下,預期能進一步找到 XWindow server 去取得 keyboard/mouse 數據的動作。

2023-03-19 試用 Rust 寫 sample code

準備目錄與檔案如下:

./test/
├── build.rs
├── Cargo.toml
└── src
    └── main.rs

Cargo.toml:

[package]
name = "test"
version = "0.1.0"
edition = "2021"

[dependencies]
x11 = "2.21.0"

main.rs:

extern crate x11;

use std::ptr;
use x11::xlib::{XOpenDisplay, XCloseDisplay, XDefaultRootWindow, XQueryPointer};

fn main() {
    unsafe {
        let display = XOpenDisplay(ptr::null());
        let root_window = XDefaultRootWindow(display);

        let mut root_return = 0;
        let mut child_return = 0;
        let mut root_x_return = 0;
        let mut root_y_return = 0;
        let mut win_x_return = 0;
        let mut win_y_return = 0;
        let mut mask_return = 0;
        XQueryPointer(
            display,
            root_window,
            &mut root_return,
            &mut child_return,
            &mut root_x_return,
            &mut root_y_return,
            &mut win_x_return,
            &mut win_y_return,
            &mut mask_return
        );

        println!("Mouse position: ({}, {})", root_x_return, root_y_return);

        XCloseDisplay(display);
    }
}

build.rs:

fn main(){
    println!("cargo:rustc-link-lib=X11");
}

( 註: build.rs 是特殊檔案,用於編譯參數設定。Ref: Build Scripts - The Cargo Book )

然後再執行:

cargo build
cargo run

預期會得到結果如下:

    Finished dev [unoptimized + debuginfo] target(s) in 0.02s
     Running `target/debug/test`
Mouse position: (653, 967)