Eww 配置完全指南:用 yuck 语言编写 ElKowars wacky widgets 窗口与控件
【免费下载链接】ewwElKowars wacky widgets项目地址: https://gitcode.com/gh_mirrors/ew/eww
本篇指南以 Eww(ElKowars wacky widgets)官方配置文档为核心,系统讲解如何用其自带的yuck配置语言从零编写窗口(window)与控件(widget):包括defwindow各属性(几何、锚点、堆叠、WM 交互)、defwidget自定义控件、四类变量的定义与更新、literal动态控件、窗口 ID 与参数(--arg)、for列表渲染以及配置文件的拆分管理。读完本文,你将能够独立编写一套可运行、可维护、可动态更新的 Eww 桌面配置,并理解配置解析与执行的底层机制。
配置基础:文件位置、yuck 与 CSS/SCSS
Eww 使用自研的yuck语言声明控件结构与内容、窗口的几何/位置/行为,以及控件中使用的状态与数据。yuck基于 S 表达式(S-expressions),熟悉 Lisp 类语言的话会非常容易上手:
- 使用 Vim 时可借助 yuck.vim 获得编辑器支持;
- 使用 VSCode 时可安装 yuck-vscode 扩展获得语法高亮与格式化;
- 官方还推荐使用 parinfer 来简化 S 表达式的括号维护。
样式方面,Eww 使用 CSS 或 SCSS 定义外观。需要特别注意的是:Eww 依赖 GTK 自身的 CSS 引擎,因此虽然支持 Web 上相当一部分 CSS,但并非全部支持——例如部分动画特性,以及绝大多数布局相关属性(flexbox、float、绝对定位、width/height)都不受支持。布局应交给box等容器控件完成。
开始前,需要创建两个文件:eww.yuck和eww.scss(也可以叫eww.css),它们必须放在$XDG_CONFIG_HOME/eww目录下(通常是~/.config/eww)。
从源码结构看,yuck 配置的解析链路位于 crates/yuck/src/parser/parser.lalrpop:词法层面把配置拆分为(、)、[、]、关键字(:foo)、符号(foo)、simplexpr({...}表达式)与注释;语法层面则由Ast枚举(crates/yuck/src/parser/ast.rs)统一表达列表、数组、关键字、符号与内联表达式,供后续配置生成阶段使用。
创建第一个窗口:defwindow
窗口是 Eww 的顶层结构。通过defwindow声明窗口名称、位置、几何与内容。官方示例:
(defwindow example :monitor 0 :geometry (geometry :x "0%" :y "20px" :width "90%" :height "30px" :anchor "top center") :stacking "fg" :reserve (struts :distance "40px" :side "top") :windowtype "dock" :wm-ignore false "example content")这里定义了一个名为example的窗口,内容为文本"example content"。保存后即可用下面的命令打开窗口:
eww open example窗口定义在源码中的结构体为 crates/yuck/src/config/window_definition.rs 中的WindowDefinition,它包含名称、参数列表、几何、堆叠、monitor、内容 widget、是否可缩放、失焦关闭以及后端选项等字段;解析时通过expect_key_values()读取:monitor、:resizable、:stacking、:geometry、:unfocus-close等键值对。
defwindow 通用属性
| 属性 | 说明 |
|---|---|
monitor | 窗口显示在哪个显示器上,细节见下文 |
geometry | 窗口的几何信息 |
unfocus-close | 窗口失去键盘焦点时是否自动关闭 |
此外WindowDefinition还支持resizable属性(未指定时默认true,见eval_resizable实现)。
monitor 属性
monitor字段支持以下取值(对应 crates/yuck/src/config/monitor.rs 中的MonitorIdentifier枚举):
- 字符串
<primary>:Eww 尝试识别主显示器(在 Wayland 上可能失败); - 整数:显示器索引;
- 显示器名称字符串;
- 包含显示器匹配器 JSON 数组的字符串,例如
'["<primary>", "HDMI-A-1", "PHL 345B1C", 0]'。Eww 会按顺序尝试匹配,从而支持优雅的回落(fallback)。
geometry 属性
| 属性 | 说明 |
|---|---|
x,y | 窗口位置,可用px或%单位,相对于anchor |
width,height | 窗口尺寸,可用px或%单位 |
anchor | 窗口锚点,取center,或top/center/bottom与left/center/right的组合 |
源码中几何由 crates/yuck/src/config/window_geometry.rs 的WindowGeometryDef表示(anchor_point、offset、size三部分)。锚点解析(AnchorPoint::from_str)接受center或形如top left的两个词,且不区分left top与top left的书写顺序。坐标单位由 crates/yuck/src/value/coords.rs 的NumWithUnit处理:支持px(省略单位时默认px)与%两种,%按容器尺寸比例换算,例如"55.5%"是合法的百分比写法。
后端专属属性:X11 与 Wayland
依据运行环境不同,defwindow还提供不同的额外属性(源码统一在 crates/yuck/src/config/backend_window_options.rs 的BackendWindowOptionsDef::from_attrs中解析,X11 与 Wayland 选项共存于同一个配置结构中)。
X11
| 属性 | 说明 |
|---|---|
stacking | 窗口在堆栈中的位置,取值fg、bg |
wm-ignore | 窗口管理器是否忽略该窗口(适合 dashboard 类、无需与其他窗口交互的控件)。注意:开启后部分其他属性将不生效。取true或false |
reserve | 指定窗口管理器为窗口预留空间的方式,常用于不应遮挡其他窗口的 bar |
windowtype | 窗口类型,窗口管理器据此决定处理方式。取值normal、dock、toolbar、dialog、desktop。默认:指定了reserve时为dock,否则为normal |
从源码看,X11 侧还支持sticky属性(让窗口粘在所有工作区),wm-ignore的默认值由是否指定windowtype/reserve决定。reserve使用(struts :distance "40px" :side "top")形式,其中side支持left/right/top/bottom(以及单字母缩写l/r/t/b),distance为长度值;X11WindowType的完整取值还包括utility、notification(源码 crates/yuck/src/config/backend_window_options.rs)。
Wayland
| 属性 | 说明 |
|---|---|
stacking | 窗口在堆栈中的位置,取值fg、bg、overlay、bottom |
exclusive | 合成器是否自动为窗口预留空间,取true或false;若为true,:anchor必须包含center |
focusable | 窗口是否可被聚焦(需要使用键盘的控件必须开启)。取值none、exclusive、ondemand |
namespace | 设置 eww 使用的 Wayland layersurface 命名空间,接受字符串值 |
stacking的完整取值(含fg/bg/bottom/overlay及对应的Foreground/Background/Bottom/Overlay枚举)定义在 crates/yuck/src/config/window_definition.rs。
第一个自定义控件:defwidget
接下来为窗口添加实际内容。官方示例:
(defwidget greeter [?text name] (box :orientation "horizontal" :halign "center" text (button :onclick "notify-send 'Hello' 'Hello, ${name}'" "Greet")))在窗口定义中调用该控件:
(defwindow example ; ... values omitted (greeter :text "Say hello!" :name "Tim"))逐步解读:
- 创建名为
greeter的控件,接收两个属性text与name; ?text表示text属性是可选的,省略时其值为空字符串"";name属性必须提供。
属性声明的解析实现在 crates/yuck/src/config/attributes.rs 的AttrSpec::from_ast:符号以?开头即标记为可选,否则为必填。
控件体内使用box并设置若干属性。一个控件定义只能包含一个子控件——否则 Eww 无法确定应该垂直还是水平排列、如何留间距,因此多个子元素必须用box之类的容器包裹。box内部引用了传入的text属性,以及一个按钮;按钮的onclick中用字符串插值语法"${name}"引用传入的name,这让你可以在字符串内方便地引用任意变量——${...}内还有更多能力,见表达式语言。
之后像使用内置控件一样调用greeter并提供所需属性即可。内置控件的完整清单见控件文档。
控件定义WidgetDefinition的解析在 crates/yuck/src/config/widget_definition.rs:除参数列表外,源码还会校验控件体是否超过一个子控件,若超出一个会给出明确诊断提示用box包裹。
在控件中渲染子元素(children)
配置变大后,你可能会把通用功能拆成可复用的包装控件。Eww 允许自定义控件像box、button等内置控件一样接收子元素,使用children占位符:
(defwidget labeled-container [name] (box :class "container" name (children)))然后按预期使用:
(labeled-container :name "foo" (button :onclick "notify-send hey ho" "click me"))还可以通过nth属性引用特定位置的子元素,构造更复杂的结构:
(defwidget two-boxes [] (box (box :class "first" (children :nth 0)) (box :class "second" (children :nth 1))))children与for在源码中都是WidgetUse的特殊变体(crates/yuck/src/config/widget_use.rs):ChildrenWidgetUse携带可选的nth_expr表达式,for则是带元素变量名、来源表达式与循环体的LoopWidgetUse。
添加动态内容:四类变量
控件中显示时间、日期等动态数据需要用到变量。这些用户自定义变量在所有控件中全局可见,变量一旦变化,控件中的值会立即更新。变量共有四类:基础变量(basic)、轮询变量(polling)、监听变量(listening)与内置的 "magic" 变量。
基础变量(defvar)
(defvar foo "initial value")这是最简单的变量类型,永远不会自动变化,只能通过命令显式更新:
eww update foo="new value"适合变化频率极低、或由外部脚本触发变化的场景;也可以让 Eww 内的按钮通过把onclick设为eww update ...来改变控件显示内容。源码 crates/yuck/src/config/var_definition.rs 显示defvar的格式为(defvar name "initial-value"),initial_value最终以DynVal形式存储。
轮询变量(defpoll)
(defvar time-visible false) ; 用于下方变量的 :run-while 属性 ; 当该变量变为 true 时,轮询启动并按给定间隔更新 (defpoll time :interval "1s" :initial "initial-value" ; 可选,默认启动时立即轮询一次 :run-while time-visible ; 可选,默认 'true' `date +%H:%M:%S`)轮询变量以固定间隔重复运行提供的 shell 脚本,是最常用的变量类型,适合反复获取的快速数据:时间、日期、待更新的软件包、天气、电池电量等。
:initial可指定初始值,避免 Eww 启动时等待命令结果,从而加快启动速度;- 外部更新轮询变量与基础变量一样使用
eww update; - 也可以用
eww poll <变量名>在常规间隔之外(甚至变量完全没在运行时)强制轮询一次。
源码 crates/yuck/src/config/script_var_definition.rs 中PollScriptVar的结构与之一一对应:interval通过as_duration解析(支持1s、500ms等格式),run_while_expr缺省时默认为字面量true,:initial缺省时初始值为空字符串。命令以反引号包裹的 shell 脚本形式存储(VarSource::Shell)。
监听变量(deflisten)
(deflisten foo :initial "whatever" `tail -F /tmp/some_file`)监听变量可能是最容易混淆的一种:它只运行一次脚本,然后持续读取其输出,每当脚本输出新的一行,变量值就更新为该行。上面例子中foo初始为"whatever",每当/tmp/some_file追加新行时随之更新。
当你有能自行监控某个值的脚本、希望操作发生时立即生效时,监听变量非常合适。音量、亮度、运行时增删的工作区、当前聚焦桌面/标签的监控等是最常见的用例。这类变量尤其高效,条件允许时应优先使用。
典型例子:
xprop -spy -root _NET_CURRENT_DESKTOP:每次当前桌面变化时输出当前聚焦桌面;playerctl --follow metadata --format {{title}}:监控当前播放的歌曲。
ListenScriptVar(同上文件)结构更简单:只包含名称、命令字符串与可选的初始值,缺省初始值为空字符串。
内置 "magic" 变量
除了自定义变量,Eww 直接提供了一些开箱即用的值,例如 CPU 和 RAM 使用率。这些值大多以 JSON 形式存放,可配合表达式语言的 JSON 访问语法读取。全部 magic 变量列表见 magic-vars.md。
magic 变量的具体实现位于 crates/eww/src/config/inbuilt.rs,示例配置 examples/eww-bar/eww.yuck 中也有典型用法,如{EWW_RAM.used_mem_perc}、{round((1 - (EWW_DISK["/"].free / EWW_DISK["/"].total)) * 100, 0)}。
动态生成控件:literal
有时需要动态改变的不仅是文本、值或颜色,而是整个控件结构——例如展示数量未知的列表(如通知)、或以更复杂方式改变控件结构。这时可以使用 Eww 最强大的特性之一:literal控件。
(defvar variable_containing_yuck "(box (button 'foo') (button 'bar'))") ; 然后在你的控件内部使用: (literal :content variable_containing_yuck)literal接收一个字符串(通常存放在变量中),该字符串包含一棵完整的 yuck 控件树,Eww 读取后渲染出对应控件;每当内容变化,控件都会重新渲染。
需要注意:literal并非高效,务必只在必要时使用!其实现见 crates/eww/src/widgets/widget_definitions.rs:运行时把内容作为新的 yuck 文本解析(load_yuck_str),再按常规流程构建 GTK 控件树。
窗口参数与 ID:一份配置,多个实例
某些场景下需要让同一份窗口配置服务于多个窗口,这时就要用到参数(arguments)与 ID(ids)。
窗口 ID
ID 可通过open命令的--id指定,默认取窗口配置名。ID 允许你同时开启多个同名窗口实例,例如:
eww open my_bar --screen 0 --id primary eww open my_bar --screen 1 --id secondary使用open-many时遵循下面的结构,同样地,未给 ID 时默认使用窗口配置名:
eww open-many my_config:primary my_config:secondary注意上面的例子没有设置screen——它通过--arg系统传入,详见下文。
窗口参数(--arg)
仅靠 ID 可能还不够,比如希望 1080p 与 4K 显示器使用不同 class,或在不同位置/尺寸打开窗口——这时就需要参数。
请注意:这些参数是常量(CONSTANT),窗口打开后无法更新。
在窗口里定义参数与在控件中完全相同:
(defwindow my_bar [arg1 ?arg2] :geometry (geometry :x "0%" :y "6px" :width "100%" :height { arg1 == "small" ? "30px" : "40px" } :anchor "top center") :stacking "bg" :windowtype "dock" :reserve (struts :distance "50px" :side "top") (my_widget :arg2 arg2))这里有两个参数arg1与arg2(后者可选)。打开窗口时必须通过open的--arg选项提供非可选参数:
eww open my_bar --id primary --arg arg1=some_value --arg arg2=another_valueopen-many的写法如下:
# 注意:`--arg` 选项必须放在所有窗口名之后 eww open-many my_bar:primary --arg primary:arg1=some_value --arg primary:arg2=another_value用这种方法,可以在每个窗口的参数里定义screen、anchor、pos、size,效果等同于在open命令中直接给出--screen、--anchor等选项。这些“特殊”参数设置方式略有不同,但全部可以被--arg覆盖:
id— 若参数列表中包含id,它会被设为--id指定的值(未指定时取配置名),可用于通过 eww 命令关闭当前窗口;screen— 若指定了screen,它会被设为--screen的值,这样其他控件也能访问屏幕相关信息。
参数解析在命令行层面由 crates/eww/src/opts.rs 完成(--arg的parse_var_update_arg、open-many的parse_window_id_args分别支持var=value与window_id:var=value两种语法),screen/pos/size/anchor/duration会被提取为窗口初始化信息,其余进入参数表。窗口打开时,crates/eww/src/window_arguments.rs 的get_local_window_variables会校验:必填参数缺失或出现意外参数都会报错。
open-many 中 --arg 的更多细节
由于open-many的--arg处理机制,不必为每个参数指定 ID:未指定 ID 的参数会应用到所有窗口,例如
eww open-many my_bar:primary my_bar:secondary --arg gui_size="small"这样所有 bar 使用相同配置。
此外,即便窗口没有指定 ID(ID 默认为窗口配置名),仍可针对该窗口单独设置参数,直接用窗口配置名即可:
eww open-many my_primary_bar --arg my_primary_bar:screen=0用 for 从 JSON 生成控件列表
要展示一组值,可以用for元素:它基于 JSON 数组生成一组元素并填入容器。
(defvar my-json "[1, 2, 3]") ; 然后在你的控件内部使用: (box (for entry in my-json (button :onclick "notify-send 'click' 'button ${entry}'" entry)))这在很多场景都很实用,例如从工作区的 JSON 表示生成工作区列表。多数情况下它可以替代literal,并且应当优先使用。
for的语法在 crates/yuck/src/config/widget_use.rs 的LoopWidgetUse中定义:依次解析元素变量名、in关键字、来源表达式与循环体,来源表达式必须是可求值为 JSON 数组的 simplexpr。
关于更高级数据结构的声明与使用,参见数据结构示例。
拆分配置:include 与独立配置目录
随着时间推移配置会越来越大,Eww 支持把配置拆分成多个文件,有两种方式:
使用 include
(include "./path/to/your/file.yuck")任何 yuck 文件都可以通过include指令导入其他 yuck 文件的内容。源码层面,crates/yuck/src/config/toplevel.rs 的Include会递归加载被包含文件并合并其顶层声明(defvar/defpoll/deflisten/defwidget/defwindow/include均被递归处理),变量重复定义会报错。
使用独立的 eww 配置目录
如果想更进一步分离不同控件,可以在任意位置新建 eww 配置文件夹,然后通过给每个命令加--config /path/to/your/config/dir标志让 eww 使用该配置目录:
eww --config /path/to/your/config/dir ...务必在所有eww 调用中都带上该标志,包括eww kill、eww logs等。这会启动一个独立的 eww 守护进程实例,与主配置拥有独立的日志和状态。
--config是全局选项(见 crates/eww/src/opts.rs 的RawOpt::config字段),与--debug、--force-wayland、--logs、--no-daemonize、--restart一样可作用于所有子命令。
与配置相关的常用 CLI 命令
除本文提到的eww open、eww open-many、eww update、eww poll、eww kill、eww logs外,结合 crates/eww/src/opts.rs 中的子命令定义,配置调试阶段常用的还有:
| 命令 | 作用 |
|---|---|
eww reload(别名r) | 重新加载配置与 CSS |
eww close-all(别名ca) | 关闭所有窗口但不杀掉守护进程 |
eww state(-a/--all) | 打印当前打开窗口用到的所有变量 |
eww get <变量名> | 获取某个变量的当前值 |
eww list-windows | 列出已定义的窗口 |
eww active-windows | 按<window_id>: <window_name>格式列出活动窗口 |
eww debug | 打印 eww 视角下的控件结构,排查配置解析问题或提交 bug 时很有用 |
eww graph | 以 graphviz dot 格式打印作用域图结构 |
完整示例:官方 eww-bar
仓库中的 examples/eww-bar/eww.yuck 是一个综合示例,融合了本文大部分知识点:用defwidget组合出workspaces、music、metric等可复用控件;用deflisten监听 playerctl 播放状态;用defpoll轮询音量与时间;defwindow定义 dock 窗口并通过:reserve (struts ...)为顶栏预留空间;onclick中调用wmctrl、amixer等外部命令与 Eww 交互。配合 examples/eww-bar/eww.scss 中的 SCSS 样式,可以作为从零搭建自己状态栏的起点。
【免费下载链接】ewwElKowars wacky widgets项目地址: https://gitcode.com/gh_mirrors/ew/eww
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考