跳转至

10 · 主题编译流水线

主题切换不是“换一张壁纸”。一个主题源要生成终端、编辑器、Hyprland、Quickshell 等多个消费者的配置,再以原子方式成为活动版本。

输入、编译与输出

flowchart LR
    Builtin[$OMARCHY_PATH/themes/name] --> Stage[next-theme]
    User[~/.config/omarchy/themes/name] --> Stage
    Stage --> Colors[colors.toml]
    Colors --> Templates[default/themed/*.tpl]
    UserTpl[~/.config/omarchy/themed] --> Templates
    Templates --> Outputs[终端/编辑器/Hypr/Shell 配置]
    Outputs --> Atomic[current/theme]
    Atomic --> Runtime[Shell IPC + wallpaper + app hooks]

主题因此更接近一个小型编译器:colors.toml 是输入 token,template 是目标后端,current/theme 是活动产物。

omarchy-theme-set 的事务边界

切换过程有明确顺序:

  1. 获取 theme lock,防止两个切换并发;
  2. 创建干净的 next-theme staging;
  3. 复制内置主题,再叠加用户主题;
  4. 若缺 colors.toml,尝试从 legacy Alacritty 配置迁出颜色;
  5. 执行 themed templates;
  6. 保存旧 wallpaper 供过渡;
  7. 原子替换 current/theme;
  8. 写 theme.name;
  9. 更新 wallpaper,并经 IPC 推送 Shell 颜色/token;
  10. 释放锁;
  11. 并行执行应用 retint/restart;
  12. 触发 theme-set 用户 hook。
stateDiagram-v2
    [*] --> Locked
    Locked --> Staged
    Staged --> Compiled
    Compiled --> Validated
    Validated --> Swapped: atomic replace
    Swapped --> Visible: IPC/wallpaper
    Visible --> PostHooks
    PostHooks --> [*]
    Staged --> Failed
    Compiled --> Failed
    Failed --> [*]: current 保持旧版本

关键点是原子 swap 发生在完整编译后。读取者只会看到旧主题或新主题,不会看到“新终端颜色 + 旧 Shell token”的 staging 半成品。

模板系统

default/themed/*.tpl 把同一 palette 映射到不同配置格式。helper 支持:

  • 十六进制与 RGB 形式;
  • 颜色混合;
  • gradient 生成;
  • Shell surface 的 border 规范;
  • 主题局部 shell.<section>.toml 合并。

用户模板目录允许添加新消费者,而不用 fork 整个主题设置脚本。模板的输出路径属于契约,修改时要考虑应用是否热加载或需要 restart hook。

Shell 主题分两层

colors.toml 提供基础 palette;shell.toml 提供 surface role、间距、字号、圆角等语义 token。Shell 中最终表现为 Color、Style 与 Border singleton。

机器级 ~/.config/omarchy/shell.toml 覆盖活动主题并被实时监视。例如用户调大文字后切换主题,文字尺寸不应恢复默认。这里“主题选择”和“机器可访问性偏好”是两份正交状态。

远程主题是数据,不是代码

该快照最重要的安全变化之一,是对通过 git 安装的主题增加 staging 过滤:

  • 拒绝所有 *.lua;
  • 拒绝终端程序配置等代码型文件;
  • 拒绝 vscode.json 等可携带命令的目标;
  • 遍历/复制时不跟随符号链接;
  • URL 与主题名先经过格式约束;
  • 只有经过 filter 的 staging 可以进入真正安装路径。
flowchart TD
    Remote[远程 git 主题] --> Clone[临时 clone]
    Clone --> Name[名称/URL 校验]
    Name --> Walk[不跟随 symlink 遍历]
    Walk --> Filter{允许的数据文件?}
    Filter -->|否| Reject[拒绝安装]
    Filter -->|是| Stage[受控 staging]
    Stage --> Install[用户 themes 目录]

为什么特别拒绝 Lua?Hyprland 会 require 当前主题相关 Lua;若远程主题能写入那里,用户的一次“换配色”就变成自动执行代码。终端/编辑器配置也可能有命令执行能力,不能仅因扩展名不是 .sh 就当作纯数据。

用户自己创建或明确链接的本地工作主题拥有不同信任语义。系统允许高级用户写代码,但不让远程主题安装命令偷偷跨越授权边界。

并行 post action 的取舍

原子 swap 与 Shell/wallpaper 是用户可见主路径;不同应用的 retint/restart 在解锁后并行进行。这样慢应用不会长期占 theme lock,下一次操作也不必等待所有非核心消费者。

代价是短时间内个别应用可能还是旧颜色。设计选择“核心一致性优先、外围最终一致”,并用 hook 给用户扩展收尾动作。

headless 安装

安装阶段可能没有 Shell。主题设置仍应生成活动状态与 background link,只跳过需要图形 IPC 的步骤。这体现了一个通用原则:持久状态变更不应依赖当前 UI 恰好在运行。

失败与恢复

失败位置 结果
source/theme 验证 current 不变
template 编译 staging 丢弃,current 不变
原子 swap 前 用户继续使用旧主题
Shell IPC 文件状态已完成;运行时可 restart/reload 恢复
某应用 retint 核心主题已生效;该外围应用可单独重试
user hook 不应回滚已可见主题

关键源码索引

下一章看 Shell 如何把系统服务翻译成桌面体验:11 · 桌面服务适配层。