Multi-player Wayland:在 Linux 上實現多個滑鼠游標
Wayland 原生支援多個邏輯座位(logical seats),允許多人使用單台電腦,並在共享同一個桌面環境的同時,擁有獨立的滑鼠游標與鍵盤焦點。這種「多人遊戲式」的運算模式為結對編程(pair programming)、遊戲以及共同應用程式使用提供了即時協作的便利,且不需要像 CRDTs 這樣複雜的同步演算法。
Wayland 多座位架構
Wayland 的多座位支援需要整合軟體棧中的四個不同層級。如果任何一個層級缺乏支援,多游標功能將會失效。
- Wayland Protocol:核心的
wayland協定已深度整合了多座位支援。每個輸入事件都與一個wl_pointer或wl_keyboard連結,而後者又與一個wl_seat連結。這使得系統可以按座位對輸入事件進行分組,並在新增或移除座位時觸發事件。 - Wayland Compositor:合成器(視窗管理器)必須實作該協定,以管理多個游標並處理每個座位的視窗焦點。
- GUI Library:像 GTK 或 SDL 這樣的函式庫必須能夠識別事件是從哪個座位發出的。
- Application:最終的程式必須編寫成可以處理多個並行輸入流的形式。
合成器支援與配置
不同的 Wayland 合成器在多座位功能的實作上差異很大。
Sway
Sway 提供卓越的多座位支援,包括每個座位的視窗焦點。使用者可以在執行時使用指令 swaymsg seat <name> assign <input_identifier> 將輸入裝置分配給座位。然而,目前存在一個限制:將裝置分配給新座位時,它會被加入到現有的分配中而不是取代舊的,這可能導致一個滑鼠控制多個游標(Sway issue #3491)。
Weston
作為參考實作的 Weston,支援多個游標與每個座位的視窗焦點。其配置比 Sway 更複雜,需要使用 udev rules 為特定的輸入裝置設定 ENV{WL_SEAT} 屬性。例如,需要在 /run/udev/rules.d/00-multiseat.rules 中加入類似 ATTRS{name}==="Name of your mouse" ENV{WL_SEAT}="second" 的規則來將裝置區分為不同的座位。
River 與 Niri
River 提供了一種非單體式的方法,透過專用的 river-input-management-v1 協定來建立與銷毀座位。Niri 目前尚缺乏官方的多座位支援,儘管存在支援最多兩個座位的實驗性分支。
圖形函式庫相容性
GTK
GTK4(以及 GTK3)在概念上透過事件上的 get_seat 方法支援多座位。然而,大多數標準 GTK 元件並不尊重多座位輸入,而是將所有輸入視為單一裝置。此外,當程式運行時新增座位時,GTK 無法通知應用程式(GTK work item #8327)。
SDL
SDL 3 (v3.3.4+) 支援多座位功能,前提是 SDL_VIDEODRIVER 環境變數已設定為 wayland。事件包含一個用於識別座位的 which 欄位。一個已知的限制是,在絕對滑鼠模式下 which 欄位會被強制設為 0;必須透過 SDL_SetWindowRelativeMouseMode 啟用相對模式才能接收到實際的裝置 ID(SDL issue #16027)。
透過 wayvnc 進行遠端協作
使用 wayvnc (v0.8.0+) 可以實現遠端多游標協作,它實作了 ext-transient-seat-v1 協定。透過使用 --transient-seat 旗標,wayvnc 會為每個傳入的連線建立一個新的虛擬座位,讓每位遠端使用者擁有自己的滑鼠與鍵盤。
為了確保所有使用者都能看到彼此的動作,建議使用 --render-cursor 旗標將游標作為疊層(overlays)渲染在輸出流中。為了獲得最佳效能,可以使用 --gpu 旗標透過 VA-API 啟用 H.264 編碼。
技術挑戰與限制
實作多座位支援會帶來顯著的 UI 與架構挑戰:
- 視窗焦點假設:許多 GUI 工具包假設一次只能有一個視窗處於活動狀態。正如社群貢獻者所指出的,當多個座位同時聚焦於不同的視窗時,像
gtk_application_get_active_window或QApplication::activeWindow這樣的函式會失效。 - 輸入衝突:當一個座位嘗試調整或移動視窗,而另一個座位正在與該視窗互動時,會發生邊際情況。在某些合成器中,當另一個座位正在拖動視窗時關閉視窗,可能會導致第二個游標消失。
- 應用程式邏輯:大多數終端使用者應用程式並非針對多游標輸入而設計。例如,Kitty 終端機會忽略除第一個座位以外的所有輸入,而 Alacritty 雖然接受來自所有座位的輸入,但卻缺乏針對每個座位的上下文(context)。
「控制項的焦點是困難的部分。你最終會在應用程式中需要一個針對每個座位版本的焦點機制。」