ITADN
Vladimir-csp/uwsm · 文件 下载 ZIP
文件最后提交记录最后更新时间
README.md
以下内容由 AI 翻译,如有问题请点此提交 issue 反馈

通用 Wayland 会话管理器

提供一组 Systemd 单元和辅助工具,用于设置环境并管理独立的 Wayland 合成器会话。

除了环境设置/清理外,它让 Systemd 完成大部分工作,并且不需要在后台运行任何额外的守护进程(在最轻量的情况下,除了一个微小的 waitpid 进程和一个简单的 shell 信号处理器)。

此设置提供了稳健的会话管理、可覆盖的合成器感知和会话感知的环境管理、XDG 自动启动、与登录会话的双向绑定、干净的关闭,以及针对 systemd 会话管理中一系列虽小但烦人的陷阱的解决方案。

对于合成器而言,这是一个卸载以下工作的机会:Systemd 集成、会话/XDG 自动启动管理、Systemd/DBus 激活环境交互及其注意事项。

[!IMPORTANT] 该项目目前处于稳定阶段,正在进行缓慢的重构。 虽然不计划进行重大更改,但请留意带有破坏性更改的提交,这些更改由感叹号指示(例如 fix!: ...chore!: ...feat!: ... 等)。

[!IMPORTANT] v0.26.0 现在对瞬态会话变量进行了特殊处理:XDG_SEATXDG_SEAT_PATHXDG_SESSION_IDXDG_SESSION_PATHXDG_VTNR。 由于这些变量具有高度的会话特定性,因此不会导出到激活环境中。 相反,它们通过指向运行时文件的 EnvironmentFile= 指令传递给合成器单元。 uwsm app 会自动将它们传递给启动的服务。 此外,静态单元文件现在是永久的,shell 代码已移至 libexec 目录中的单独文件。 仅当其他来源为空时,合成器的可执行文件基名才会自动添加到 XDG_CURRENT_DESKTOP。

[!NOTE] 强烈建议使用 dbus-broker 作为 D-Bus 守护进程 实现。除其他好处外,它复用 systemd 激活 环境,而不是使用单独的环境。这简化了环境 管理并允许适当的清理。也支持参考 D-Bus 实现, 但它不允许取消设置变量,因此通过将其设置为空字符串 来执行尽力而为的清理。正确清理参考 D-Bus 守护进程 单独环境的唯一方法是运行 loginctl terminate-user ""

uwsm select (via whiptail)

概念与特性

使用 systemd 单元和依赖项进行启动、运行和关闭。
  • 绑定到 graphical-session-pre.targetgraphical-session.targetxdg-desktop-autostart.target 的基本 结构
  • 添加自定义嵌套切片 app-graphical.slicebackground-graphical.slicesession-graphical.slice,用于放置应用并在退出时 干净地终止它们。
  • 提供便捷的 将应用启动到这些切片中的方式
Systemd 单元在设计时考虑了层次结构和通用性。
  • 使用说明符的模板化单元。
  • 尽可能从通用到具体进行命名。
  • 允许使用高级 name-.d 覆盖文件。
登录会话与图形会话之间的双向绑定。

使用 waitpid 工具(或内置的 shim)配合原生 systemd 机制,uwsm 将登录会话(session-N.scope 系统 单元)的生命周期绑定到图形会话(一组用户单元),反之亦然。

合成器特定行为可通过插件进行调整。

当前包含:

  • sway
  • wayfire
  • labwc
  • hyprland
  • niri
  • mango

上述内容的 LXQt 变体由实验性的 LXQt Universal Wayland Session 提供。

幂等地(好吧,尽力幂等地)处理环境。
  • 启动时,一个专用单元通过以下方式准备环境:
    • 要么加载由 uwsm start 命令保存的环境上下文,要么自行加载 shell 配置文件
    • 从反转序列 ${XDG_CONFIG_HOME}:${XDG_CONFIG_DIRS}:${XDG_DATA_DIRS}(按 优先级递增)的每个目录中加载 uwsm/envuwsm/env.d/*uwsm/env-${desktop}uwsm/env-${desktop}.d/* 文件,其中 ${desktop}${XDG_CURRENT_DESKTOP} 的每个小写项
  • 准备前后环境状态的差异被导出到 systemd 用户管理器(如果使用参考 D-Bus 实现,则也导出到 D-Bus 激活环境)
  • 关闭时,之前导出的变量从 systemd 用户管理器中取消设置 (参考 D-Bus 守护进程的激活环境不支持取消设置, 因此这些变量被置空 (!))
  • 用于导出和清理的变量列表通过以下算法确定:
    • 比较准备过程前后的环境
    • 与预定义列表进行布尔运算
    • uwsm finalize 操作手动导出的变量
可与 XDG 数据层级中的 `wayland-sessions` 的 Desktop 条目协同工作, 和/或被包含在其中。
  • 主动从 Desktop 条目中选择并启动合成器(该条目用作 合成器实例 ID):
    • 从条目中获取的数据(可通过 CLI 参数修改或覆盖):
      • Exec 用于参数列表
      • DesktopNames 用于 XDG_CURRENT_DESKTOPXDG_SESSION_DESKTOP
      • NameComment 用于单元 Description
    • 条目可以在 ${XDG_DATA_HOME}/wayland-sessions/ 中被覆盖、屏蔽或添加
    • 可选的交互式选择器(需要 whiptail),选择结果保存在 ${XDG_CONFIG_HOME}/uwsm/default-id 中,默认值从中读取,并回退 直至 ${XDG_CONFIG_DIRS}:${XDG_DATA_DIRS}
    • Desktop 条目 actions 受支持
  • 通过登录/显示管理器经由 Desktop 条目启动。
可以使用任意合成器命令行运行,或者从桌面条目(保存为单元 drop-in)中获取它(连同其他数据)。
wayland-wm-env@${compositor}.service.d/50_custom.conf
wayland-wm@${compositor}.service.d/50_custom.conf
提供更好的对 XDG 自启动应用的控制。
  • XDG 自启动服务(app-*@autostart.service 单元)被放置在 app-graphical.slice 中,该服务在合成器停止之前接收停止操作。
  • 可以通过停止和启动 wayland-session-xdg-autostart@${compositor}.target 进行批量控制
尽力通过单元之间的依赖网络干净地关闭会话。

提供的基本单元文件(如果项目使用 static-units=disabled 构建,则可能是瞬时的):

background-graphical.slice
app-graphical.slice
session-graphical.slice
wayland-session-envelope@.target
wayland-session-pre@.target
wayland-session-shutdown.target
wayland-session-xdg-autostart@.target
wayland-session@.target
wayland-wm-app-daemon.service
wayland-wm-env@.service
wayland-wm@.service
wayland-session-bindpid@.service
wayland-session-waitenv.service

托管的生成文件(根据 -U 选项,位于 ${XDG_RUNTIME_DIR}/systemd/user${XDG_CONFIG_HOME}/systemd/user):

合成器元数据自定义 drop-in:

wayland-wm-env@${compositor}.service.d/50_custom.conf
wayland-wm@${compositor}.service.d/50_custom.conf

其他单位的调整:

app-@autostart.service.d/slice-tweak.conf
plasma-xdg-desktop-portal-kde.service.d/order-tweak.conf

参见下方 Longer story 章节的 描述。

为各种操作提供辅助函数和工具。
  • uwsm finalize:用于显式地将变量导出到激活环境, 以及向合成器报告单元就绪状态(合成器服务单元使用 Type=notify
  • uwsm check may-start:用于检查登录时启动的条件(用于 集成到登录 shell 配置文件中)
  • uwsm app:用于在适当的切片中作为范围或服务启动应用程序
    • 支持桌面条目或普通可执行文件
    • 支持在终端中/启动终端 (提议的 xdg-terminal-exec)
    • 灵活的单元元数据支持
  • uwsm-app:一个简单快速的 shell 客户端,用于 uwsm 的 app-daemon 功能, 是 uwsm app 的直接替代品。守护进程(按需启动)负责 查找请求的桌面条目,解析并生成命令供 客户端执行。这避免了重复启动 python 的开销, 并提高了应用程序启动速度。
  • uuctl:可选的图形化(需要类似 dmenu 的菜单)工具,用于管理 用户单元。
  • fumon:可选的后台服务,用于通知失败的单元 (需要来自 libnotifylibnotify-bin 包的 notify-send)。
  • ttyautolock:可选的后台服务,用于在其 TTY 失去焦点时锁定会话(需要来自 inotify-tools 包的 inotifywait)。

安装与基本配置

1. 构建与安装

检出最后一个带版本标签的提交。未打标签的提交为进行中(WIP)。

直接构建并安装 Python 项目。
meson setup --prefix=/usr/local -Duuctl=enabled -Dfumon=enabled -Duwsm-app=enabled -Dttyautolock=enabled build
meson install -C build

该示例启用了本项目中可用的可选工具 uuctlfumonttyautolockuwsm-app(参见上方 concepts section 中的 helpers and tools 折叠内容)。

构建并安装 deb 包。

阅读并运行 ./build-deb.sh -i

或者,

IFS='()' read -r _ current_version _ < debian/changelog
sudo apt install devscripts
mk-build-deps
sudo apt install --mark-auto ./uwsm-build-deps_${current_version}_all.deb
dpkg-buildpackage -b -tc --no-sign
sudo apt install ../uwsm_${current_version}_all.deb
Arch.

pacman -S uwsm

NixOS 选项。

使用 programs.uwsm.enable 启用它,并使用 programs.uwsm.waylandCompositors 配置可用的合成器。请参阅 选项说明 以获取更多信息。

运行时依赖项:

  • python 模块:
    • xdg (pyxdg)
    • dbus (dbus_python)
  • waitpid(可选,但推荐用于资源;来自 util-linuxutil-linux-extra 软件包)
  • whiptail(可选,用于 select 功能;来自 whiptaillibnewt 软件包)
  • 类似 dmenu 的菜单(可选;用于 uuctl 脚本),支持:
    • vicinae
    • fuzzel
    • walker
    • wofi
    • rofi
    • hyprlauncher
    • tofi
    • bemenu
    • wmenu
    • dmenu
  • notify-send(可选,用于 uwsm app 命令的反馈以及 可选的失败单元监控 fumon 服务;来自 libnotify-binlibnotify 软件包)
  • inotifywait(可选,用于 ttyautolock 脚本和服务;来自 inotify-tools 软件包)

2. 服务启动通知和合成器设置的变量

这部分可能比较棘手。

简而言之;如果你的合成器将 WAYLAND_DISPLAY(以及随之而来的 DISPLAY,或其他重要或有用的变量)放入 systemd 激活 环境中,uwsm 将自动使一切正常工作,请继续第 3 节。

否则,请配置合成器在其启动结束时运行 uwsm finalize 命令。它将 以最佳方式处理将 WAYLAND_DISPLAYDISPLAY(如果已设置) 变量放入激活环境,并向 systemd 发出单元就绪信号。

如果已知合成器会设置有用的变量,但激活环境中缺少这些变量。

将变量名称作为参数传递给 uwsm finalize,或者追加UWSM_FINALIZE_VARNAMES 变量中的空白分隔列表中(请提前执行,即在 env 文件或 shell 配置文件中)。

sway 配置的示例片段(这些变量已通过 sway 插件中的 UWSM_FINALIZE_VARNAMES var 覆盖,此处列出仅为清晰起见):

exec exec uwsm finalize SWAYSOCK I3SOCK XCURSOR_SIZE XCURSOR_THEME

未定义的变量将被静默忽略。

如果图形会话过早到达,即合成器将其他变量放入激活环境的时间远晚于 `WAYLAND_DISPLAY`,以至于下游单元无法获取。

Append 将变量名追加到 UWSM_WAIT_VARNAMES 变量中的空白分隔列表(请提前执行,即在 env 文件或 shell 配置文件中执行)。这将使 uwsm 延迟图形会话的启动,直到这些变量出现在 systemd 激活环境中。

根据具体情况,将此与 uwsm finalize 命令结合使用,以将更多变量放入激活环境,并更精确地控制 uwsm 的延迟机制。

请注意,uwsm finalize 会跳过未定义的变量,因此请确保 UWSM_WAIT_VARNAMES 中列出的所有变量确实已被设置,或使用显式赋值作为标记。示例:

# in env file:
export UWSM_WAIT_VARNAMES="${UWSM_WAIT_VARNAMES} FINALIZED"

# in compositor's autostart:
uwsm finalize FINALIZED="I'm here" SWAYSOCK I3SOCK XCURSOR_SIZE XCURSOR_THEME

您还可以调整 UWSM_WAIT_VARNAMES_SETTLETIME(float,默认值:0.2)以 更改在找到所有预期变量后的暂停时长。

技术细节

wayland-wm@${compositor}.service 内部,在执行合成器本身之前, uwsm 会 fork 一个进程,用于探测 systemd 激活环境中的 WAYLAND_DISPLAY 变量以及 UWSM_WAIT_VARNAMES 变量中列出的变量 (以空白字符分隔)。当所有预期的变量出现时,它会暂停 UWSM_WAIT_VARNAMES_SETTLETIME 秒(浮点数,默认值:0.2),然后发出单元就绪信号。它还会使用单元启动时激活环境状态与稳定暂停结束时的状态之间的差异来更新清理列表。如果使用经典 D-Bus 实现,该差异也会同步到其激活环境中。

一个独立的单元 wayland-session-waitenv.service 会与合成器一起启动, 其排序顺序与 graphical-session-pre.target 之后、graphical-session.target 之前类似。它也以相同的方式等待相同的变量, 然后成功退出(或超时)。它的作用是,在合成器过早发出就绪信号时,延迟 graphical-session.target 的激活。或者,如果预期的变量未出现,则使启动失败。

uwsm finalize 命令会用合成器设置的关键变量填充 systemd 和 D-Bus 环境:WAYLAND_DISPLAY(必需)和 DISPLAY(如果 存在)。可选变量按名称从参数和 UWSM_FINALIZE_VARNAMES 变量中获取,该变量也由插件预填充。D-Bus 实现的怪癖会被处理。未定义的变量会被静默忽略。任何导出的变量也会被添加到清理列表中。

单元启动的超时时间为 10 秒。

3. 应用程序与切片

应用程序应在其自身的用户级 systemd 单元中启动。

某些应用程序(通常是旨在在图形会话中自动启动的那些)会附带其自身的单元。使用以下命令进行检查和启用: systemctl --user enable ...

更多信息
  • systemctl --user enable this-app.service(如果它提供 WantedBy=graphical-session.target
  • systemctl --user add-wants graphical-session.target that-app.service(如果它 不提供)

最终,自动启动的单元应同时具有依赖关系和排序(被某个目标想要,并在该目标之后排序),参见 example-units

或者直接使用 systemctl --user start not-a-service.service 开始。

单元可以通过标准的 systemd 机制进行完全或部分覆盖/编辑。

其他应用程序随附 XDG 自动启动条目。Systemd 会自动 将它们转换为 app-*@autostart.service 单元并启动。

更多信息

它们的 OnlyShowIn=/NotShowIn= 列表应与 $XDG_CURRENT_DESKTOP 条目保持一致,或者不存在。自动启动条目可以通过在 ${XDG_CONFIG_HOME}/autostart/ 中复制并编辑 它们来覆盖。生成的 app-*@autostart.service 单元可通过 drop-in 进行编辑。

另请参阅 example-units 并参考 Desktop Application Autostart Specification

要启动任何其他应用,请使用:

uwsm app -- {executable|entry.desktop[:action]} [args ...]

一次性应用启动器可以直接启动,但要么配置为通过 uwsm app 运行 程序,要么包裹在 shell 表达式中以将输出(最好是 Desktop Entry 路径或 ID)传递给 uwsm app

并非所有启动器都能提供 Desktop Entry ID,大多数仅提供 生成的命令,因此单元将缺乏详细的描述。

Fuzzel 自 1.14.0 起通过 DESKTOP_ENTRY_* 变量导出应用元数据,希望 能开启一个良好的趋势。这些变量被 uwsm app 透明地支持。 参见 相关 PR 以获取更多信息。

一些示例:

启动器方式说明元数据
vicinaeguiVicinae 设置 > 扩展 > 应用程序 > 启动前缀=uwsm app --
fuzzelcommandfuzzel "--launch-prefix=uwsm app --"env
fuzzelconfiglaunch-prefix=uwsm app --env
albertenv varALBERT_APPLICATIONS_COMMAND_PREFIX: uwsm;app;--
albertshellALBERT_APPLICATIONS_COMMAND_PREFIX="uwsm;app;--" albert
walkerconfigapp_launch_prefix = "uwsm app -- "no
wofishelluwsm app -- "$(wofi --show drun --define=drun-print_desktop_file=true | sed -E "s/(\.desktop) /\1:/")"entry
wofishelluwsm app -- "$(D=$(wofi --show drun --define=drun-print_desktop_file=true); case "$D" in *'.desktop '*) echo "${D%.desktop *}.desktop:${D#*.desktop }";; *) echo "$D";; esac)"entry
hyprlauncherconfigdesktop_launch_prefix = uwsm app --no
tofishelluwsm app -- $(tofi-drun)no
roficommandrofi -show drun -run-command "uwsm app -- {cmd}"no

Compositor 本身运行在 session.slice 中,该单元在部分资源分配上具有优先级。将所有应用累积在那里是一种糟糕的做法,而在 compositor 单元内部累积进程则是极其糟糕的做法。

更快的替代方案

uwsm app 在某些配置下可能相当缓慢,这是由于重复的 python 启动开销所致。

包含的可选 uwsm-app 脚本利用 uwsm 的按需应用守护进程,以实现重复交互式启动时的更高响应性。

app2unit 是一种更快的 shell 替代方案,功能对等。它也可以在 uwsm 环境之外使用,并且可以与 Fuzzel 集成。

runapp 是一个快速的 C++ 可执行文件,尽管其功能尚不完整。

背景与细节

uwsmsession.slice 中以 wayland-wm@${compositor}.service 启动合成器的服务。

合成器的子进程将成为其单元的一部分,这对于短生命周期的单次命令(例如音量调节)可能基本没问题。 但合成器单元内的进程在启动完成并受到限制之前,会短暂地访问其通知套接字。这可能导致 意想不到的后果,例如合成器单元被错误地声明为进入停止状态。

Systemd 文档 建议将应用程序作为其自身的单元(scope 或 service)启动。app.slice 将是默认目标,background.slicesession.slice 分别可用于低优先级的非交互任务和高优先级的 响应性感知任务,(参见 man systemd.special

uwsm 提供了一种处理此问题的便捷方式:特殊的嵌套 slice,这些 slice 也会在 wayland-wm@${compositor}.service 关闭之前接收停止操作:

  • app-graphical.slice(默认目标)
  • background-graphical.slice
  • session-graphical.slice

XDG 自动启动条目的 app-*@autostart.service 单元也会被修改,以在 app-graphical.slice 中启动。

要在其中一个 slice 内启动应用程序,请使用:

uwsm app [-s a|b|s|custom.slice] [-t scope|service] -- your_app [with args]

通过有效的 ID](https://specifications.freedesktop.org/desktop-entry-spec/latest/file-naming.html#desktop-file-id) 启动桌面条目也受支持(可选地附加一个 操作 ID](https://specifications.freedesktop.org/desktop-entry-spec/latest/extra-actions.html) 通过 : 追加):

uwsm app [-s a|b|s|custom.slice] [-t scope|service] -- your_app.desktop[:action] [with args]

在这种情况下,args 必须根据 XDG Desktop Entry Specification 被入口点或其选定的操作所支持。

也支持指定可执行文件或桌面入口文件的路径。

如果任何带破折号的参数是预留给要启动的应用程序的,请始终使用 -- 来消除命令行歧义。

Scopes 是通过 uwsm app 启动应用程序的默认单元类型,它们 就地执行,行为类似于简单命令,继承源环境 和 pty。

Services 由 systemd 用户管理器在后台启动,并被 赋予基于 systemd 激活环境当前状态的环境;其输出被路由到日志。uwsm app 将在 启动后立即返回。这允许对应用程序进行更多控制,例如 使用更新的环境重启它。

用于启动应用程序的 sway 配置示例片段:

Launch proposed 默认终端:

bindsym --to-code $mod+t exec exec uwsm app -T

Fuzzel 有一个非常实用的 launch-prefix 选项:

bindsym --to-code $mod+r exec exec fuzzel --launch-prefix='uwsm app --'

Walker 可以通过在配置中设置 app_launch_prefix 变量来为启动的应用程序添加前缀,因此 "app_launch_prefix": "uwsm app -- "

通过桌面条目启动 SpaceFM:

bindsym --to-code $mod+e exec exec uwsm app spacefm.desktop

Featherpad 桌面条目具有“standalone-window”操作:

bindsym --to-code $mod+n exec exec uwsm app featherpad.desktop:standalone-window

启动的应用的 Unit 类型可以通过 -t service|scope 参数进行控制, 或通过 UWSM_APP_UNIT_TYPE 环境变量设置其默认值。

4. 环境与 shell 配置文件

在图形会话运行中,环境变量通常分为三大类:

  • 所有/某些应用程序需要看到的环境变量
  • 合成器需要看到的环境变量
  • 合成器设置且图形应用程序需要看到的环境变量(这在 第 2 节中已介绍)

针对前两类,用户级变量应放置位置的总结:

  • 对于用户的 systemd 服务,包括合成器:在 ${XDG_CONFIG_HOME}/environment.d/*.conf中定义。这不会影响登录会话 或 systemd 用户管理器本身(参见 man 5 environment.d)。
  • 对于登录 shell 上下文和 uwsm 环境预加载器,包括插件: 在你的 shell 配置文件中导出。
  • 对于 uwsm 管理的图形会话:在 ${XDG_CONFIG_HOME}/uwsm/env${XDG_CONFIG_HOME}/uwsm/env.d/my_vars中导出
  • 对于特定合成器的 uwsm 管理的图形会话:在 ${XDG_CONFIG_HOME}/uwsm/env-${desktop}${XDG_CONFIG_HOME}/uwsm/env-${desktop}.d/my_vars中导出

选择适合你需求的范围。

如果启动是通过 uwsm start 命令发起的,其环境将被 保存并被环境预加载器读取(由于 uwsm start 应该 从登录会话上下文中启动,因此假设登录 shell 的配置文件已经被加载)。

否则,环境预加载器将自行加载 POSIX shell(/bin/sh)配置文件 (/etc/profile${HOME}/.profile)。其他 shell 与 这些文件的兼容性可能有所不同。

操作

语法与行为

-h|--help 选项可用于 uwsm 及其所有子命令。

基础:

uwsm start [options] -- ${compositor} [arguments]

始终使用 -- 来消除命令行歧义,如果有任何带破折号的参数是预留给启动的 compositor 的。

${compositor} 可以是一个可执行文件或有效的 桌面条目 ID (可选地通过 ':' 附加一个 action ID ),或者是特殊值之一:select|default

如果 ${compositor} 作为路径给出,或者给出了 -F 选项,则会启用“硬编码”模式: 生成的命令行将始终写入 unit drop-ins,并包含 uwsm start 所见的可执行文件的完整路径。如果在桌面条目的 Exec 中遇到可执行文件路径,该路径也会被写入。

用于提供更多元数据的可选参数:

  • -[a|e]D DesktopName1[:DesktopName2:...]:追加(-a)或独占设置 (-e${XDG_CURRENT_DESKTOP}
  • -N Name
  • -C "Compositor description"

如果需要,参数和元数据将存储在 specifier unit drop-ins 中。

uwsm start ... 命令将等待图形会话结束,同时保持其所在的登录会话处于打开状态。如果启动图形会话的进程结束,图形会话也会停用。

一些细节
uwsm start \
	[-[a|e]D DesktopName1[:DesktopName2:...]] \
	[-N Name] \
	[-C "Compositor description"] \
	[-F] \
	[-g|-G seconds] \
	[-o] \
	[-U run|home] \
	[-t] \
	[-n] \
	-- ${compositor} [with "any complex" --arguments]

如果 ${compositor} 是一个桌面条目 ID,uwsm 将在 wayland-sessions 数据层级中查找它。Exec 将用于命令行,并且 DesktopNames 将填充 $XDG_CURRENT_DESKTOPNameComment 将进入 单元的描述。

在命令行上提供的参数会被追加到来自 会话桌面条目的命令行中(与应用条目不同);不会进行 任何参数处理。(如果您遇到任何 wayland-sessions 桌面条目,其 % 字段 需要改变此行为,请 提交错误报告]。)

如果您想要自定义随桌面条目提供的合成器执行, 请将其复制到 ~/.local/share/wayland-sessions/ 并按您的喜好进行修改, 包括添加 操作

如果 ${compositor}selectdefaultuwsm 将调用一个菜单来选择 wayland-sessions 数据层级中可用的桌面条目(包括它们的 操作)。选择会被保存,之前的选择会被高亮显示(或者在 default 的情况下 立即启动)。所选条目用作实例 ID。

还有一个单独的 select 操作(uwsm select),它仅选择并 保存默认 ${compositor},而不做其他任何事情,这对于无缝 shell 配置文件集成非常有用。

uwsm 还会等待系统 graphical.target 的激活,如果超时,或者如果在队列中未找到 graphical.target, 则发出警告或中止。此行为可以通过 -g|-G 选项进行控制。然而,它无法 在系统 graphical.target 变为未激活状态时自动停止。

uwsm start ... 将执行的操作:

  • 为元数据和运行时或主目录中的调整准备单元 drop-in。
  • 派生一个受 TERMHUP 信号保护的过程,该过程将查找未来 合成器单元的 MainPID 并等待其结束,确保登录会话在图形会话结束前保持打开。
  • 启动指向 uwsm 自身 PID 的 wayland-session-bindpid@.service 单元, 以便在 uwsm(或登录会话)结束时安排图形会话的关闭。
  • 最后,用 systemctl 命令替换自身,该命令将实际启动 合成器单元,并在 wayland 会话运行期间等待。

从哪里启动

Shell 配置文件集成

若要在虚拟控制台 1 上登录后自动启动,且 systemd 位于 graphical.target,请将此代码(或等效代码)添加到你的 shell 配置文件中:

if uwsm check may-start && uwsm select; then
	exec uwsm start default
fi

主要语句应当由一个条件进行保护,该条件在不希望运行 uwsm start 的情况下返回 false。即,如果这是 ~/.profile 且 uwsm 环境预加载器自行加载它(如果合成器单元在未使用 uwsm start 命令的情况下被激活,它就可以这样做)。

uwsm check may-start 子命令充当一组有用的检查。 默认情况下:用户的 dbus 可用,父进程是登录 shell(进程名称 以 - 开头),tty1 在前台,登录会话的 tty 匹配,登录 会话是本地会话,系统的 graphical.target 处于活动或激活状态,用户的 graphical-session.target 及其他相关单元处于非活动状态。

此外,为了方便起见,环境预加载器定义了 IN_UWSM_ENV_PRELOADER=true 变量(未导出),可以从 shell 配置文件中探测该变量以有条件地执行操作。

uwsm select 显示 whiptail 菜单,用于从 wayland-sessions 目录中选择默认桌面条目。此时可以取消并继续 使用正常的登录 shell。

在 shell 配置文件中,exec 会导致 uwsm 替换登录 shell,并将其绑定到 用户的登录会话。

uwsm start default 启动之前选择的默认合成器。

从显示管理器

要从显示/登录管理器启动 uwsm,可以在桌面 条目中使用 uwsm

这些条目放置在 XDG_DATA_DIRSwayland-sessions 子目录中。 通常,如果 XDG_DATA_DIRS 包含 /usr/local/share/usr/share,用户 定义的条目将放置在 /usr/local/share/wayland-sessions 中。

示例 /usr/local/share/wayland-sessions/my-compositor-uwsm.desktop

[Desktop Entry]
Name=My compositor (with UWSM)
Comment=My cool compositor, UWSM session

# a reference to another entry (preferred since some DMs may fail on quoted arguments)
Exec=uwsm start -- my-compositor.desktop

# or a full command line with metadata and executable
#Exec=uwsm start -N "My compositor" -D mycompositor:mylib -C "My cool compositor" -- mywm

# invalidates entry if uwsm is missing
TryExec=uwsm

DesktopNames=mycompositor;mylib
Type=Application

需要注意的事项:

  • Exec= 中的命令应以 uwsm start 开头。
  • 如果命令引用了可执行文件,条目的键应在参数中镜像,否则 uwsm 将无法访问这些字符串。
  • 它不应指向自身(作为桌面条目 ID 和操作 ID 的组合)。
  • 它不应指向同样使用 uwsm 的桌面条目 ID 和操作 ID。

此类条目可能会被 uwsm 本身发现并使用,例如在 shell 配置文件集成情况下,或手动启动时。遵循上述原则可确保 uwsm 正确识别自身,并在条目中解析请求的参数,且无任何副作用。

某些显示管理器可能无法正确处理 引号 。在这种情况下,解决方法是使用单词参数和/或指向另一个条目。

或者,如果显示管理器支持包装命令/脚本,可以在其中插入 uwsm 以接收条目和操作 ID,或解析后的命令行。

需要测试和反馈。

如何停止

以下任一方式:

  • loginctl terminate-user "" (这将结束当前用户的所有登录会话和单元,适用于重置所有内容,包括运行时单元、环境等。)
  • loginctl terminate-session "$XDG_SESSION_ID" (这将结束启动 uwsm 所在的登录会话,等待前一个登录 shell 进程退出的特殊单元 wayland-session-bindpid@.service 将退出并停止图形会话单元。空参数仅在从登录会话作用域内部调用 loginctl 时有效,因此从图形会话单元调用时应使用变量)
  • uwsm stop (停止图形会话单元。如果 uwsm start 替换了登录 shell,则登录会话将结束)
  • systemctl --user stop wayland-wm@*.service (实际上与上一个相同)

不要使用合成器的原生退出机制或直接杀死其进程,这会将合成器从所有客户端下方强行移除,并干扰有序的单元停用序列。

更长的故事,深入内部机制

一些扩展示例以及通过过多的 shell 代码部分重现某些行为,仅用于更深入的说明。

深入

启动和绑定

根据 static-units 构建选项,单元文件是提供的还是即时生成的。如果需要,自定义 drop-in 始终会生成。Tweak drop-in 可以通过 -t 选项禁用。

运行 uwsm start -o ${compositor} 以用它们填充 systemd/user/ 配置,并且不做其他任何事情(-o)。目标 rung 可以是 $XDG_RUNTIME_DIR$XDG_CONFIG_HOME,具体取决于 $UWSM_UNIT_RUNG-U 选项。另一个 rung 中管理的文件将被删除。

任何剩余参数都会追加到合成器参数列表中(即使 ${compositor} 是一个桌面条目)。使用 -- 以消除歧义:

uwsm start -o -- ${compositor} with "any complex" --arguments

桌面条目可以在 ${XDG_DATA_HOME}/wayland-sessions/ 中被覆盖或添加。

基本单元文件集:

  • 绑定到标准 systemd 用户级目标的模板化目标
    • wayland-session-pre@.target
    • wayland-session@.target
    • wayland-session-xdg-autostart@.target
  • 模板化服务
    • wayland-wm-env@.service - 环境预加载服务
    • wayland-wm@.service - 主合成器服务
    • wayland-wm-app-daemon.service - 快速应用命令生成器
  • 嵌套在标准 systemd 用户级切片中的应用切片
    • app-graphical.slice
    • background-graphical.slice
    • session-graphical.slice
  • 元数据 drop-in
    • wayland-wm-env@${compositor}.service.d/50_custom.conf, wayland-wm@${compositor}.service.d/50_custom.conf - 如果参数和/或 各种名称、可执行文件路径在命令行中给出,它们将放在这里。
  • 调整
    • app-@autostart.service.d/slice-tweak.conf - 将 XDG 自动启动应用分配给 app-graphical.slice
    • plasma-xdg-desktop-portal-kde.service.d/order-tweak.conf - 为 KDE 桌面门户添加排序 After=graphical-session.target
  • 元数据、关闭和清理单元
    • wayland-session-envelope@.target - 发起/传播启动和 关闭,本身在启动时启动,在整个启动过程、 会话生命周期和关闭期间保持运行。
    • wayland-session-bindpid@.service - 为给定的 PID 启动 waitpid 实用程序。在停用时会调用 wayland-session-shutdown.targetuwsm start 在将自己替换为 shell 信号处理程序和 systemctl 单元启动命令之前, 启动指向自身的此单元。
    • wayland-session-shutdown.target - 与操作单元冲突。 由 wayland-wm*@*.servicewayland-session-bindpid@*.service 单元的停用触发,无论成功或失败。但 也可以手动调用以进行关闭。

在单元文件生成后,可以通过以下方式启动合成器: systemctl --user start wayland-wm@${compositor}.service

但这样运行会使其完全脱离登录会话或启动它的任何进程。要修复此问题,请使用 wayland-session-bindpid@.service 来 跟踪登录 shell 的 PID($$),并在其退出时停止图形会话:

systemctl --user start wayland-session-bindpid@$$.service

添加 --wait 以在会话结束前保持终端,使用 exec 以复用其 PID 并用 systemctl 调用替换登录 shell,同时使用 wayland-session-envelope@${compositor}.target 代替主服务, 以在结束时等待适当的环境拆除。

exec systemctl --user start --wait wayland-session-envelope@${compositor}.target

这使得登录 shell 的结束也意味着 wayland 会话的结束,反之亦然。

或者,为了优雅地处理父 login 进程,可以在 shell 中使用 trap 来处理 TERMHUP 信号,以保护用于分叉 systemctl 进程的 shell 免受直接终止,并在收到 SIGTERM 时运行 systemctl --user stop wayland-wm@${compositor}.service(同样受信号保护)。

当在 graphical-session-pre.target 启动期间启动 wayland-wm-env@.service 时,会启动 uwsm aux prepare-env ${compositor}(带有共享的自定义参数集)。

它会查找由 uwsm start 命令保存的环境变量,然后运行 shell 代码来 准备环境。该代码会加载 POSIX shell 配置文件(如果未找到来自 uwsm start 的环境变量)、通用的以及匹配桌面的 uwsm/env*uwsm/env*.d/* 文件,以及插件所规定的一切。

shell 代码结束时的环境状态会返回给主进程。 uwsm 还足够智能,能够找到与当前 TTY 关联的登录会话, 并在 uwsm start 保存的上下文中未找到时设置 $XDG_SESSION_ID$XDG_VTNR。在这种情况下,还会检查 wayland-session-bindpid@.service 是否处于活动状态,如果未找到指向登录会话领导者的实例,则会自动启动一个,作为绑定到 登录会话生命周期的最后最佳努力。

初始 env(即激活环境的状态) 与完成所有 source 和设置操作后的差异,加上 Varnames.always_export, 减去 Varnames.never_export,会被添加到 systemd 用户管理器和 D-Bus 的激活环境中。

这些变量名,加上 Varnames.always_cleanup 减去 Varnames.never_cleanup,会被写入运行时目录中的清理列表文件。

启动完成

如果合成器至少将 WAYLAND_DISPLAY 放入 systemd 激活环境中,则无需执行此步骤:uwsm 将自动检测此情况并处理其余部分。 如果出现问题,可以通过组合使用 uwsm finalize 命令和配置变量 UWSM_FINALIZE_VARNAMESUWSM_WAIT_VARNAMESUWSM_WAIT_VARNAMES_SETTLETIME 来修复启动问题。

wayland-wm@.service 使用 Type=notify 并等待合成器发出 已启动状态的信号。激活环境还需要接收诸如 WAYLAND_DISPLAY 等关键 变量,才能成功启动图形应用程序。

wayland-wm@.service 内部的一个分叉进程会等待 WAYLAND_DISPLAY 以及 UWSM_WAIT_VARNAMES 中提到的所有变量,然后发出单元就绪信号, 并将通知套接字的访问权限从 all 限制为 exec。它还会将 自单元启动以来观察到的任何差异追加到变量清理列表中。

独立的 wayland-session-waitenv.service 执行相同的等待操作, 要么成功退出以允许 graphical-session.target 继续,要么 超时,从而导致所有进程终止。

uwsm finalize [VAR [VAR2...]] 可以由合成器运行,本质上它 执行类似于以下操作的动作:

dbus-update-activation-environment WAYLAND_DISPLAY DISPLAY [VAR [VAR3...]]
systemctl --user import-environment WAYLAND_DISPLAY DISPLAY [VAR [VAR3...]]
systemd-notify READY=1 NOTIFYACCESS=exec

(dbus-update-activation-environment 操作等效项是冗余的, dbus-broker 会自动跳过)

额外的变量名取自 UWSM_FINALIZE_VARNAMES var。

仅使用已定义的变量。未被 Varnames.never_cleanup 集合列入黑名单的变量也会被添加到运行时目录的清理列表中。

停止

只需停止主服务: systemctl --user stop "wayland-wm@${compositor}.service",其余部分将由 systemd 停止。

通配符 systemctl --user stop "wayland-wm@*.service" 同样有效, 停止 wayland-session@*.target 也可以。

或者激活关机目标: systemctl --user start wayland-session-shutdown.target

如果 wayland-session-bindpid@.service 的某个实例处于活动状态并指向登录会话中的 PID,上述任何停止命令同时也充当注销 命令。

wayland-wm-env@${compositor}.service 停止时,uwsm aux cleanup-env 将被启动。它会在运行时目录中查找任何清理文件(uwsm/env_cleanup_*.list)。 列出的变量,加上 Varnames.always_cleanup 减去 Varnames.never_cleanup,将在 D-Bus 激活环境中被清空,并从 systemd 用户管理器环境中取消设置。

当没有合成器在运行时,单元可以被移除(-r),由 uwsm stop -r 执行。

将合成器添加到 -r 以仅移除自定义 drop-in: uwsm stop -r ${compositor}

配置文件集成

此示例执行与之前描述的 check may-start + start 子命令 组合相同的功能:如果系统处于 graphical.target,则在 tty1 登录时自动启动 wayland 会话

此处筛选是否处于交互式登录 shell 至关重要[ "${0}" != "${0#-}" ])。wayland-wm-env@${compositor}.service 会加载 配置文件,如果无条件运行,可能会导致严重的循环。其他 条件是建议:

MY_COMPOSITOR=sway
if [ "${0}" != "${0#-}" ] &&
   ! systemctl --user is-active -q wayland-wm@*.service &&
   [ "$XDG_VTNR" = "1" ] &&
   {
       # wait while graphical.target is in startup queue
       while case "$(systemctl list-jobs --plain --no-legend --full graphical.target)" in
       *start*) true ;; *) false ;; esac; do
         sleep 1
       done
       systemctl is-active -q graphical.target
   }
then
    # generate units
    uwsm start -o ${MY_COMPOSITOR}

    # save login environment
    mkdir -p "$XDG_RUNTIME_DIR/uwsm"
    env -0 > "$XDG_RUNTIME_DIR/uwsm/env_login"

    # bind wayland session to login shell PID $$ and start compositor
    echo Starting ${MY_COMPOSITOR} compositor
    systemctl --user start wayland-session-bindpid@$$.service

    # do not die right away on signals, stop gracefully
    trap "trap '' TERM HUP INT; systemctl --user stop --wait wayland-wm@${MY_COMPOSITOR}.service; wait \$SCPID; exit" TERM HUP INT
    {
    	trap '' TERM HUP INT
    	exec systemctl --user start --wait wayland-wm@${MY_COMPOSITOR}.service
    } &
    SCPID=$!
    # hold session open
    wait $SCPID
fi

uwsm start 也会用轻量级 shell 信号处理器替换自身, 该处理器采用类似算法,在合成器单元被停用之前保持登录会话打开, 并防止登录会话和 login 进程过早结束。

合成器特定操作

Shell 插件在环境准备期间提供合成器特定的功能。

命名为 ${__WM_BIN_ID__}.sh,它们应仅包含特定命名的函数。

${__WM_BIN_ID__} 是通过应用 s/(^[^a-zA-Z]|[^a-zA-Z0-9_])+/_/ 并将合成器命令行的第 0 项转换为小写而派生的。

它用作插件 ID 和函数名中的后缀。

插件可用的变量:

  • __WM_ID__ - 合成器 ID,start 的有效第一个参数。
  • __WM_ID_UNIT_STRING__ - 转义用于 systemd 单元名称的合成器 ID。
  • __WM_BIN_ID__ - 处理后的合成器 argv 的第一项。
  • __WM_DESKTOP_NAMES__ - 来自条目 DesktopNames=-D CLI 参数的 : 分隔的桌面名称。
  • __WM_FIRST_DESKTOP_NAME__ - 上述的第一项。
  • __WM_DESKTOP_NAMES_LOWERCASE__ - 与上述相同,但为小写。
  • __WM_FIRST_DESKTOP_NAME_LOWERCASE__ - 上述的第一项。
  • __WM_DESKTOP_NAMES_EXCLUSIVE__ - (true|false) 表示 __WM_DESKTOP_NAMES__ 来自 CLI 参数,并标记为独占。
  • __OIFS__ - 包含 shell 默认字段分隔符(空格、制表符、换行符),以便 方便地恢复。

标准函数:

  • load_wm_env - 用于加载 env 文件的标准函数
  • process_config_dirs - 由 load_wm_env 调用,遍历整个 XDG Config 和系统 XDG Data 层次结构(优先级递减)
  • in_each_config_dir - 由 process_config_dirs 为每个配置目录调用, 目前什么也不做
  • process_config_dirs_reversed - 由 load_wm_env 调用,与 process_config_dirs 相同,但顺序相反(优先级递增)
  • in_each_config_dir_reversed - 由 process_config_dirs_reversed 为 每个配置目录调用,加载 uwsm/envuwsm/env.d/*uwsm/env-${desktop}uwsm/env-${desktop}.d/* 文件
  • source_file - 加载 $1 文件,为日志提供消息。

有关更多辅助函数,请参阅 uwsm/main.py 内部的代码。

插件可以添加以替换标准函数的功能:

  • quirks__${__WM_BIN_ID__} - 在加载环境之前调用。
  • load_wm_env__${__WM_BIN_ID__}
  • process_config_dirs_reversed__${__WM_BIN_ID__}
  • in_each_config_dir_reversed__${__WM_BIN_ID__}
  • process_config_dirs__${__WM_BIN_ID__}
  • in_each_config_dir__${__WM_BIN_ID__}

如果需要组合效果,原始函数仍然可以显式调用。

示例:

#!/bin/false

# function to make arbitrary actions before loading environment
quirks__my_cool_wm() {
  # here additional vars can be set or unset
  export I_WANT_THIS_IN_SESSION=yes
  unset I_DO_NOT_WANT_THAT
  # or prepare a config for compositor
  # or set a var to modify what sourcing uwsm/env, uwsm/env-${__WM_ID__}
  # in the next stage will do
  ...
  # add a var to be exported by uwsm finalize:
  UWSM_FINALIZE_VARNAMES="${UWSM_FINALIZE_VARNAMES}${UWSM_FINALIZE_VARNAMES:+ }ANOTHER_VAR1 ANOTHER_VAR2"
  # add a var to wait and depend on before graphical session:
  UWSM_WAIT_VARNAMES="${UWSM_WAIT_VARNAMES}${UWSM_WAIT_VARNAMES:+ }ANOTHER_VAR1 ANOTHER_VAR2"
}

in_each_config_dir_reversed__my_cool_wm() {
  # custom mechanism for loading of env files (or a stub)
  # replaces standard function, but we want it also
  # so call it explicitly
  in_each_config_dir_reversed "$1"
  # and additionally source our file
  source_file "${1}/${__WM_ID__}/env"
}

致谢

受以下项目启发并借鉴了部分技术:

特别感谢:

  • @skewballfox 在 python 方面提供帮助,并向我推荐了有用的工具。
  • @notpeelz 改进了提交流程、meson、模块化,并创建了 AUR 软件包。
  • @YaLTeR 提出了一个想法,促成了自动合成器启动检测的实现。
  • @izmyname 在 Hyprland 一侧进行了集成和文档工作。
  • @basil 进行了 LXQt 集成和文档工作。