news 2026/9/14 0:21:29

Seelen UI 窗口管理器平铺布局完全指南:用声明式插件树定义 Tiling Window Manager 布局

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
Seelen UI 窗口管理器平铺布局完全指南:用声明式插件树定义 Tiling Window Manager 布局

Seelen UI 窗口管理器平铺布局完全指南:用声明式插件树定义 Tiling Window Manager 布局

【免费下载链接】Seelen-UIThe Fully Customizable Desktop Environment for Windows 10/11.项目地址: https://gitcode.com/GitHub_Trending/se/Seelen-UI

Seelen UI(Windows 10/11 的桌面环境)内置了一个平铺窗口管理器(Tiling Window Manager,@seelen/window-manager),而它的布局完全由用户自定义:你不需要写任何脚本,只需编写一段声明式的 YAML 插件资源,描述一棵"布局树",Rust 核心就会把它解析为实时运行的平铺规则。本文基于仓库文档 wm-layouts.md 完整讲解该布局 Schema(TwmPlugin/TwmPluginNode/TwmCondition)、节点字段、条件表达式、运行时解析原理,并给出仓库内置的 wm-columns、wm-bspwm、wm-grid 三个完整可用的布局配置,让你能够从零写出属于自己的平铺布局。

说明:本文引用的源码相对路径以仓库根目录为起点,可对照阅读:wm-layouts.md、plugin-guidelines.md、twm.rs、wm_layout.rs、state_v2.rs。

1. 布局即插件:窗口管理器的声明式扩展机制

在 Seelen UI 中,不存在独立的 "Layout" 资源种类。一个平铺布局本质上就是一个Plugin资源,只是它的target固定为@seelen/window-manager。要理解这一点,需要先了解 Seelen UI 的插件机制(详见 plugin-guidelines.md):

  • 插件是一个纯声明文件:只有idtarget和一个自由格式的plugin载荷,本身不定义运行时、沙箱或任何执行模型;
  • Seelen UI 核心不做通用的plugin解析/校验/执行,只负责把资源路由给target指向的 widget;
  • 每个 widget 自己定义它期望的plugin结构:工具栏插件可能定义带 JS 回调的按钮(见 toolbar-plugins.md),Dock 插件可能定义图标项(见 dock-plugins.md),而窗口管理器插件则定义一棵无任何代码的静态布局树

因此,"窗口管理器布局"完全由@seelen/window-manager这个 widget 自己拥有:Schema、解析、执行都在这一个 widget 内,Seelen UI 核心没有任何针对它的特判。与工具栏、Dock 插件不同,WM 布局完全没有脚本(scripts)——它是一棵被 Rust 代码遍历的声明式树,而不是被当作 JS 求值的程序。

2.plugin载荷:TwmPlugin

布局资源的最小骨架如下(对应 Rust 结构体 TwmPlugin,structure字段被serdecamelCase反序列化):

id: "@yourname/my-layout" target: "@seelen/window-manager" plugin: structure: # 一棵 TwmPluginNode 树;省略或为 null 则退化为纯浮动布局 type: Horizontal children: - type: Leaf - type: Leaf

关键规则:

  • structure省略或为null时,该布局下没有任何窗口被平铺,所有窗口浮动(源码注释:null means no tiling, only float layout)。
  • structure的根节点就是布局树根,类型必须是容器(Vertical/Horizontal)或单槽节点(Leaf/Stack)。
  • 其余字段(如metadataicon)与通用插件一致,可参考 plugin-guidelines.md 中的metadata.yml模板;plugin块内同样支持!include引入外部文件。

3.TwmPluginNode:节点字段与四种节点类型

布局树由节点(Node)组成,每个节点(包括根节点)都支持以下字段(对应 TwmPluginNode):

字段类型默认值含义
type/kindLeaf|Stack|Vertical|Horizontal节点类型。YAML 中typekind可互换(#[serde(alias = "type")]
lifetimePermanent|TemporalPermanent节点清空后是否保留,还是被清理回收
prioritynumber1兄弟节点间的遍历顺序——越小越先被尝试
growFactornumber1.0该节点占父容器剩余空间的相对份额
conditionTwmCondition或省略门控该节点当前是否接收新窗口,见 §5
childrenTwmPluginNode[][]仅对Vertical/Horizontal有意义;Leaf/Stack忽略
maxStackSizenumber 或省略3仅对Stack有意义;省略/null表示不限大小
stackPolicyManual|AutoWhenOverflow|AutoAuto仅对Stack有意义,见 §4

源码中 Default 实现 确认了上述默认值:kind = Leaflifetime = Permanentpriority = 1grow_factor = 1.0max_stack_size = Some(3)stack_policy = Auto

3.1 节点类型(Node Kinds)

对应枚举 TwmNodeKind:

类型角色
Leaf单窗口槽位,仅在空时接收一个窗口
Stack多个窗口在同一个槽位中标签式堆叠,受stackPolicy/maxStackSize约束
Vertical容器——把children上下(top/bottom)切分,自身不直接持有窗口
Horizontal容器——把children左右(left/right)切分,自身不直接持有窗口

从源码注释看,四者的几何语义分别是:"node will not grow, this is the final node"(Leaf,不生长、是末端节点)、"node will grow on z-axis"(Stack,沿 Z 轴生长)、"node will grow on y-axis"(Vertical,沿 Y 轴生长)、"node will grow on x-axis"(Horizontal,沿 X 轴生长)。

4. Stack 节点的stackPolicy

只有Stack节点关心stackPolicy(对应枚举 TwmStackPolicy,默认Auto):

行为
Auto(默认)自由接收窗口,直到达到maxStackSize
AutoWhenOverflow只有当其他节点都无法再接收窗口时才接收
Manual从不自动接收窗口,只能通过用户显式操作放入

配合maxStackSize(默认3;设为null表示不限)即可控制堆叠行为。从运行时看,Manual堆叠一般由用户显式命令(如把窗口"入栈")触发,而 normalize 会把只剩 ≤1 个窗口的Manual堆叠自动折叠回Leaf,避免残留无意义节点。

5.conditionTwmCondition布尔表达式

condition是一个针对当前平铺状态求值的布尔表达式,用于门控"节点当前是否接收窗口"。它对应枚举 TwmCondition,支持四种形式:compareandornot

5.1 基础比较

condition: compare: left: tiling-windows # 或 is-reindexing op: ge # eq | ne | lt | le | gt | ge right: 4

5.2 复合条件

condition: or: - compare: { left: tiling-windows, op: eq, right: 3 } - compare: { left: tiling-windows, op: ge, right: 5 }
condition: and: - compare: { left: tiling-windows, op: ge, right: 2 } - compare: { left: tiling-windows, op: lt, right: 8 }
condition: not: compare: { left: is-reindexing, op: eq, right: true }

注意:and/or/not在 Rust 中分别由TwmCondition::And(Box, Box)Or(Box, Box)Not(Box)表示;evaluate采用短路求值(见 twm.rs)。YAML 里or/and下列表语义上等价于逐项二元组合。

5.3 操作数(Operands)

left可用的操作数对应枚举 Operand,经#[serde(rename_all = "kebab-case")]序列化为:

操作数
tiling-windows当前工作区中已平铺窗口的数量(ctx.tiling_windows: usize
is-reindexingWM 当前是否正在重索引(批量重新分配窗口)中(ctx.is_reindexing: bool

5.4 比较符(Comparators)

op可选eqneltlegtge,对应 Comparator,与right(一个普通 YAML/JSON 值:number / string / boolean,取决于操作数)做标准数值或相等比较。源码的 compare 实现 有一个值得注意的细节:当左右两侧都是 number 时,按浮点数做完整的大小比较;否则只支持eq/ne(其余比较符一律返回false)——所以对布尔操作数(如is-reindexing)只能用eq/ne

6. 从声明树到运行时树:TwmRuntimeTree的解析与窗口分配

运行时,Seelen UI 会解析当前激活布局的Plugin资源,把structure解析为TwmPluginNode,再转换成运行时树(TwmRuntimeTree)。转换入口是 TwmRuntimeTree::from_plugin,配合 insert_plugin_node 递归展开:

  • kindlifetimeprioritygrowFactorconditionmaxStackSizestackPolicy原样拷贝
  • growFactor同时用作初始值和运行时用户可调整的实时 grow factor(切换布局或执行 reset_sizes 时会从initial_grow_factor重置);
  • 运行时专有状态(已分配窗口、活动窗口、屏幕矩形等)初始化为空。

此后,每当有窗口需要槽位,WM 就按priority顺序遍历整棵树,依次询问每个节点accepts_windows?(见 try_add_window)。判定过程分两层:

  1. 条件门控:若节点带condition,先基于当前上下文{ tiling-windows, is-reindexing }求值(上下文构造见 get_context);
  2. 类型规则Leaf仅在空时接收;Stack按其stackPolicy/maxStackSize接收;容器节点从不直接接收,只有它们的子节点接收。

若正常路径找不到可接收节点,还会走一次"溢出"通道(accepts_windows_on_overflow,即AutoWhenOverflow堆叠的兜底)。

6.1 重索引(Reindex)与is-reindexing

为了支持"tiling-windows < 4时换一种排布"这类逻辑,每当树结构或窗口数量发生显著变化,WM 会执行 reindex_windows:把符合条件的窗口(跳过 ≥2 窗口的Manual堆叠)全部抽出、清空节点,然后按窗口进入时间先后排序(最早的先放),在is-reindexing: true的上下文里重新依次放入各节点。放不下的窗口作为"残余"(residual)返回,交给浮动层。add_to_tiled/remove_window都会触发重索引(见 add_to_tiled),这就是is-reindexing操作数存在的意义——避免条件表达式在重索引中途读到中间态而抖动。

6.2 布局切换与 Monocle

工作区与布局 ID 的对应关系缓存在TwmStatelayout_cache中;当用户在设置里切换布局时,on_settings_changed 会比较新旧 ID,change_layout 会把旧树中所有平铺窗口抽出来、把浮动窗口保留,再按新布局的from_plugin树重新分配——放不下的继续浮动。

Monocle 模式(单窗口放大)并不是独立的机制:它只是同一套 Schema 的另一种实例——{ kind: Stack, maxStackSize: null }(不限大小的堆叠、单一槽位)。源码 TwmPlugin::monocle() 正是这样构造的,运行时 toggle_monocle 在 monocle 布局与用户配置布局之间切换(快捷键Win + M)。这也解释了为什么 monocle 下所有窗口都以标签页形式叠在一起、只显示一个。

7. 完整示例:仓库内置的三个布局

以下三个布局直接取自仓库真实配置文件(src/static/plugins/下的wm_columnswm_bspwm_grid),是验证本文 Schema 的最佳范本。

7.1@default/wm-columns:最扁平的三列布局

配置见 wm_columns/metadata.yml,三棵固定列,无任何条件:

id: "@default/wm-columns" target: "@seelen/window-manager" plugin: structure: type: Horizontal children: - type: Leaf - type: Leaf - type: Leaf

7.2@default/wm-bspwm:递归二叉空间分割

配置见 wm_bsp/metadata.yml,前 6 个窗口按 bspwm 风格递归二分,并用priority控制填充顺序(注意注释标注的窗口序号):

id: "@default/wm-bspwm" target: "@seelen/window-manager" plugin: structure: type: Horizontal children: - type: Leaf # 第 1 个窗口 - type: Vertical children: - type: Leaf # 第 2 个窗口 - type: Horizontal children: - type: Vertical priority: 2 children: - type: Horizontal priority: 2 children: - type: Leaf # 第 5 个窗口 - type: Leaf # 第 6 个窗口 - type: Leaf # 第 4 个窗口 priority: 1 - type: Leaf # 第 3 个窗口 priority: 1

布局逻辑:Horizontal根先左右一分为二;左侧叶子装第 1 个窗口,右侧Vertical再上下切,上方装第 2 个窗口,下方Horizontal再切……每一层都靠priority决定同层子节点的填充先后,最终得到递归平衡的 bsp 树。

7.3@default/wm-grid:基于condition的动态网格

配置见 wm_grid/metadata.yml,是最能体现condition威力的示例——同一个布局随平铺窗口数量自动重塑形态:

id: "@default/wm-grid" target: "@seelen/window-manager" plugin: structure: type: Horizontal children: - type: Vertical priority: 3 condition: compare: { left: tiling-windows, op: ge, right: 4 } children: - type: Leaf priority: 3 condition: compare: { left: tiling-windows, op: ge, right: 7 } - type: Leaf - type: Leaf - type: Vertical priority: 1 children: - type: Leaf priority: 3 condition: compare: { left: tiling-windows, op: eq, right: 8 } - type: Leaf priority: 1 - type: Leaf priority: 2 condition: or: - compare: { left: tiling-windows, op: eq, right: 3 } - compare: { left: tiling-windows, op: ge, right: 5 } - type: Vertical priority: 2 children: - type: Leaf priority: 3 condition: compare: { left: tiling-windows, op: ge, right: 6 } - type: Leaf - type: Leaf

读法示范:

  • 中间的Verticalpriority: 1)总是最优先填充:它容纳第 1、2 个窗口,且仅当tiling-windows == 3>= 5时其第 3 个叶子才开放(形成 3 窗口/5 窗口以上的列);
  • 右侧列priority: 2第 3 个叶子要求>= 6,左侧列要求>= 4——窗口多了才逐步启用这些列;
  • 顶部的额外叶子进一步细分:左列第 1 个叶子要>= 7,中列第 1 个叶子要求恰好== 8

由于窗口分配时按priority遍历并实时求值condition(配合重索引),这个布局就能做到:窗口少时是简单的单列/双列,窗口增多后逐步长出第三列和细分槽位,窗口数变化后自动重排。

8. 将布局放入仓库并启用

  • 布局即插件资源:把上述 YAML(含idtargetplugin.structure)作为独立的插件包放入src/static/plugins/下(参考wm_columns/wm_bsp/wm_grid的目录结构,含metadata.ymli18n/下的display_name.yml/description.yml);
  • 安装后,在 Seelen UI 设置中为该工作区选择布局 ID;运行时由 state_v2.rs 中的TwmState负责按工作区缓存布局、监听设置变更并热切换(change_layout会无损迁移已有窗口,放不下的进入浮动层);
  • 想进一步控制窗口行为,可结合设置项(如auto_stacking_by_category,见 state_v2.rs 中按窗口类别自动堆叠的逻辑)与TwmReservation(左/右/上/下/入栈/浮动的手动安置,见 twm.rs)。

结语

Seelen UI 的平铺布局把"布局"彻底数据化:TwmPlugin定义载荷、TwmPluginNode定义树、TwmCondition定义动态门控,而 wm_layout.rs 与 state_v2.rs 中的 Rust 实现负责把声明变成实时树、按priority遍历分配窗口并在条件变化时重索引。掌握这套 Schema 后,你可以仅凭 YAML 写出列布局、bsp 布局、随窗口数量自适应的网格布局乃至自定义 Monocle——整个过程零脚本、纯声明,这正是"插件即纯声明、行为归 widget"这一设计哲学在窗口管理器上的完整落地。

【免费下载链接】Seelen-UIThe Fully Customizable Desktop Environment for Windows 10/11.项目地址: https://gitcode.com/GitHub_Trending/se/Seelen-UI

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/9/14 0:15:24

阿里云人像分割API本地调试最小实践:AliPicDemo详解

简介&#xff1a;本资源是一个基于阿里开放平台图像处理能力实现的一键抠图功能的C#/.NET实战示例项目&#xff0c;面向.NET初学者与图像处理入门开发者&#xff0c;解决本地快速集成云AI服务进行人像/物体智能分割的实际需求。压缩包共305个文件&#xff0c;包含123个运行依赖…

作者头像 李华
网站建设 2026/9/14 0:13:17

TigerBeetle Balance Bounds:用链接转账为账户余额实现上下界约束

TigerBeetle Balance Bounds&#xff1a;用链接转账为账户余额实现上下界约束 【免费下载链接】tigerbeetle The financial transactions database designed for mission critical safety and performance. 项目地址: https://gitcode.com/GitHub_Trending/ti/tigerbeetle …

作者头像 李华
网站建设 2026/9/14 0:07:54

MATLAB梯度下降实战:从收敛几何到调参与调试

简介&#xff1a;梯度下降法是机器学习和深度学习领域应用广泛的优化方法&#xff0c;原理简单且实用&#xff0c;其核心思想是沿当前点负梯度方向迭代更新参数&#xff0c;逐步逼近目标函数的局部最小值。这份MATLAB实现专门演示最速梯度下降法的完整流程&#xff0c;面向正在…

作者头像 李华
网站建设 2026/9/14 0:03:46

MARS488替代ADIS16375全流程:从硬件适配到软件移植的实操指南

做替代选型这件事&#xff0c;最怕的不是芯片本身有问题&#xff0c;而是你拿新芯片直接焊上去&#xff0c;发现飞控输出的姿态开始漂&#xff0c;却分不清是驱动没写好、减震没做好&#xff0c;还是芯片性能本身就差。最近我同时接了无人机和AGV两个项目&#xff0c;都在做MAR…

作者头像 李华
网站建设 2026/9/14 0:03:14

语音情感识别实战:Keras实现LSTM、CNN、SVM与MLP多模型对比

简介&#xff1a;面向语音情感识别入门与进阶开发者&#xff0c;这份基于Keras的项目源码完整实现了LSTM、CNN、SVM、MLP四种模型&#xff0c;兼容Python3.8与Keras/TensorFlow2环境。压缩包内含49个文件&#xff0c;大小约70.31MB&#xff0c;主体包括Python脚本、yaml/json配…

作者头像 李华
网站建设 2026/9/14 0:02:03

第8章 Application

第8章 Application&#x1f4c5; 2026年09月12日&#x1f464; 东塬一老翁&#x1f4c2; 第三篇 SAI Framework Core第8章 Application本章大纲Application 定义Framework ApplicationApplication 生命周期Application 初始化Application 启动Application 运行Application 结…

作者头像 李华