多人 Wayland:在 Linux 上实现多个鼠标光标
Wayland 原生支持多个逻辑座位,允许多个人使用同一台电脑并共享同一桌面环境,拥有独立的鼠标光标和键盘焦点。此“多人”计算模型促进了实时协作,如配对编程、游戏以及共同使用应用程序,而无需像 CRDT 这样的复杂同步算法。
Wayland 多座位架构
Wayland 的多座位支持需要在软件栈的四个不同层次之间进行集成。如果任意层缺乏支持,多光标功能将无法工作。
- Wayland 协议:核心
wayland协议深度集成了多座位支持。每个输入事件都关联到wl_pointer或wl_keyboard,而它们又连接到wl_seat。这使系统能够按座位对输入事件进行分组,并在座位被添加或移除时触发事件。 - Wayland 合成器:合成器(窗口管理器)必须实现该协议,以管理多个光标并按座位处理窗口焦点。
- GUI 库:GTK 或 SDL 等库必须能够识别事件来源的座位。
- 应用程序:最终程序必须编写为能够处理多个并发输入流。
合成器支持与配置
不同的 Wayland 合成器在多座位功能的实现上差异显著。
Sway
Sway 提供了出色的多座位支持,包括按座位的窗口焦点。用户可以在运行时使用命令 swaymsg seat <name> assign <input_identifier> 将输入设备分配给座位。然而,目前存在一个限制:将设备分配给新座位时会将其添加到已有分配中,而不是替换旧的分配,可能导致一个鼠标控制多个光标(Sway issue #3491)。
Weston
Weston 作为参考实现,支持多个光标和按座位的窗口焦点。其配置比 Sway 更复杂,需要使用 udev 规则为特定输入设备设置 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 参数将光标渲染为输出流中的叠加层。为了获得最佳性能,可使用 --gpu 参数通过 VA-API 启用 H.264 编码。
技术挑战与局限性
实现多座位支持会带来显著的 UI 和架构挑战:
- 窗口焦点假设:许多 GUI 工具包假设一次只能有一个窗口处于活动状态。正如社区贡献者指出的,当多个座位同时聚焦不同窗口时,
gtk_application_get_active_window或QApplication::activeWindow等函数会失效。 - 输入冲突:当一个座位尝试调整窗口大小或移动窗口,而另一个座位正在与之交互时,会出现边缘情况。在某些合成器中,关闭窗口时如果另一个座位正在拖动该窗口,第二个光标可能会消失。
- 应用逻辑:大多数面向终端用户的应用程序并未针对多光标输入进行设计。例如,Kitty 终端只接受第一个座位的输入,而 Alacritty 接受所有座位的输入,但缺乏每座位的上下文。
“控件的焦点是难点。最终你需要在应用中实现每个座位的焦点版本。”