Eww(ElKowar's Wacky Widgets)入门指南:在任意窗口管理器上构建自绘 Widget 系统
【免费下载链接】ewwElKowars wacky widgets项目地址: https://gitcode.com/gh_mirrors/ew/eww
Eww(ElKowar's Wacky Widgets)是一个基于 Rust 编写的独立 Widget 系统,它让你可以像在 AwesomeWM 中那样自由创建自定义小组件,而其关键优势在于与窗口管理器(WM)完全解耦——无论你使用 i3、bspwm、Hyprland 还是 KDE,都能用同一套配置构建状态栏、桌面小组件或悬浮面板。本文以仓库根目录的 README.md 为骨架,结合 docs/src/eww.md、docs/src/configuration.md 与源码实现,完整讲解 Eww 的安装构建、yuck 配置语言、窗口与组件定义、动态变量体系以及命令行操作,读完后你将能够独立搭建一套属于自己的 Eww Widget 环境。
什么是 Eww
Eww 是ElKowar's Wacky Widgets的缩写,是一个用 Rust 编写的独立 Widget 系统,允许你在任意窗口管理器中实现完全自定义的组件。它的核心理念与 AwesomeWM 类似,但有一个本质区别:它不依赖任何特定的窗口管理器。
从仓库的 crates/eww/Cargo.toml 可以看到,Eww 当前版本为0.6.0,基于 GTK3 构建,并针对 X11 与 Wayland 分别提供后端支持:
[features] default = ["x11", "wayland"] x11 = ["gdkx11", "x11rb"] wayland = ["gtk-layer-shell"]- X11 后端依赖
gdkx11与x11rb(后者带有randr特性,用于多显示器管理); - Wayland 后端依赖
gtk-layer-shell,通过 Layer Shell 协议实现层叠窗口效果。
整个项目是一个 Cargo workspace,由多个 crate 组成,其职责划分可以从根目录的 Cargo.toml 中看出:
| crate | 作用 |
|---|---|
crates/eww | 主程序,包含 CLI、daemon、窗口管理与 widget 构建逻辑 |
crates/yuck | yuck 配置语言的解析器与 AST 处理 |
crates/simplexpr | 内嵌的表达式语言(用于{ ... }与${ ... }) |
crates/notifier_host | 系统托盘(systray)的 StatusNotifier 宿主实现 |
crates/eww_shared_util | 共享工具(如 VarName、Span 等) |
安装与构建
前置依赖
安装 Eww 需要rustc与cargo。官方文档强烈建议使用 rustup 安装 Rust 工具链,而不是依赖系统包管理器提供的版本。
此外,Eww 在编译和运行时尚需若干动态库。不同发行版的包名可能不同,以下是 Arch Linux 上对应的包名清单:
gtk3(提供libgdk-3、libgtk-3)gtk-layer-shell(仅 Wayland 需要)pango(libpango)gdk-pixbuf2(libgdk_pixbuf-2)libdbusmenu-gtk3cairo(libcairo、libcairo-gobject)glib2(libgio、libglib-2、libgobject-2)gcc-libs(libgcc)glibc
注意:编译 Eww 时,通常还需要对应发行版的
-devel变体包(包含头文件)。
编译与运行
git clone https://github.com/elkowar/eww cd eww cargo build --release --no-default-features --features x11如果你使用 Wayland,请改用以下命令构建:
cargo build --release --no-default-features --features=wayland构建完成后,进入输出目录并赋予可执行权限:
cd target/release chmod +x ./eww然后启动 daemon 并打开窗口:
./eww daemon ./eww open <window_name>这里的eww daemon会在后台启动常驻进程(可在 crates/eww/src/opts.rs 的Action枚举中看到daemon子命令,别名d),eww open则通知 daemon 渲染并显示指定窗口。
编写 Eww 配置
Eww 使用自研的yuck配置语言(基于 S 表达式,与 Lisp 系语言类似)声明组件结构、窗口几何与行为以及动态数据。样式则使用CSS/SCSS定义。由于 Eww 依赖 GTK 自己的 CSS 引擎,Web 上常见的 CSS 并非全部支持——动画特性以及大部分布局相关属性(如 flexbox、float、绝对定位、width/height)不受支持。
配置需要两个文件:eww.yuck与eww.scss(也可以叫eww.css),它们必须放在$XDG_CONFIG_HOME/eww下(通常是~/.config/eww)。
仓库中的 examples/eww-bar/eww.yuck 与 examples/eww-bar/eww.scss 提供了一个完整可运行的状态栏示例,下文将反复引用它。
创建第一个窗口
窗口是 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")定义完成后,运行eww open example即可打开窗口。在 examples/eww-bar/eww.yuck 中可以看到一个真实的状态栏窗口定义——它使用了:monitor 0、dock窗口类型、struts预留空间(reserve),并将bar组件渲染为内容。
defwindow属性一览
| 属性 | 说明 |
|---|---|
monitor | 窗口显示在哪个显示器上,详见下方说明 |
geometry | 窗口的几何信息 |
unfocus-close | 窗口失去键盘焦点时自动关闭 |
monitor属性支持以下取值:
- 字符串
<primary>:让 Eww 尝试识别主显示器(在 Wayland 上可能失败); - 整数:显示器索引;
- 显示器名称;
- 包含显示器匹配器 JSON 数组的字符串,如
'["<primary>", "HDMI-A-1", "PHL 345B1C", 0]',Eww 会按顺序尝试匹配并提供回退。
geometry属性:
| 属性 | 说明 |
|---|---|
x、y | 窗口位置,支持px或%,相对于anchor计算 |
width、height | 窗口尺寸,支持px或% |
anchor | 窗口锚点,取值为center或top/center/bottom与left/center/right的组合 |
平台相关属性
根据 X11 或 Wayland,defwindow还支持额外属性。
X11 专属:
| 属性 | 说明 |
|---|---|
stacking | 窗口在层叠中的位置,取值fg、bg |
wm-ignore | 是否让窗口管理器忽略该窗口,适合仪表盘式组件(true/false);注意开启后部分其他属性将失效 |
reserve | 让窗口管理器为窗口预留空间,适合状态栏避免与其他窗口重叠 |
windowtype | 窗口类型,取值normal、dock、toolbar、dialog、desktop;默认值为:指定了reserve时为dock,否则为normal |
Wayland 专属:
| 属性 | 说明 |
|---|---|
stacking | 层叠位置,取值fg、bg、overlay、bottom |
exclusive | 合成器是否自动为窗口预留空间(true/false);为true时:anchor必须包含center |
focusable | 窗口是否可聚焦,取值none、exclusive、ondemand;使用键盘交互的组件必须开启 |
namespace | 设置 Wayland Layer Shell 的 namespace,接受字符串 |
定义自己的 Widget
窗口内容由 widget 组成。用defwidget定义带参数的自定义组件:
(defwidget greeter [?text name] (box :orientation "horizontal" :halign "center" text (button :onclick "notify-send 'Hello' 'Hello, ${name}'" "Greet")))然后在窗口中调用它:
(defwindow example ; ... 其他属性省略 (greeter :text "Say hello!" :name "Tim"))几个要点:
greeter接收两个属性:text与name。?text声明该属性可选(缺省时值为空字符串""),而name必须提供;- widget 定义体只能包含一个顶层 widget,否则 Eww 无法确定排列方向与间距,因此多子元素时必须用
box包裹; onclick中的"${name}"是字符串插值语法,可以在字符串中引用任意变量;${...}内还能写完整的表达式,详见 docs/src/expression_language.md(官方文档位置为docs/src/expression_language.md,对应线上章节 "Eww expressions")。
渲染子组件:children占位符
当配置变大时,可以把通用结构抽成可复用的包裹型组件,它也能像box、button一样接收子元素:
(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))))添加动态内容:Eww 的变量体系
Eww 的变量是全局可见的,一旦变量值改变,引用它的 widget 会自动刷新。变量分为四类:基础变量、轮询变量、监听变量和内建 "magic" 变量。
基础变量(defvar)
(defvar foo "initial value")基础变量不会自动变化,必须显式通过eww update foo="new value"更新。适合低频变化、由外部脚本驱动的值;也可以让组件内的按钮通过onclick调用eww update来改变界面内容。
轮询变量(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 update外部赋值外,还可以用eww poll在常规间隔之外强制轮询(即使变量当前未在轮询)。
在 examples/eww-bar/eww.yuck 中可以看到实际用法——用defpoll每 1 秒执行scripts/getvol获取音量、每 10 秒执行date获取时间。
监听变量(deflisten)
(deflisten foo :initial "whatever" `tail -F /tmp/some_file`)监听变量只运行一次脚本,然后持续读取其输出:每当脚本输出新的一行,变量值就更新为该行。上述例子中foo初始为"whatever",之后每当/tmp/some_file追加新行即更新。
监听变量适合需要即时响应变化且脚本自身能持续监控的场景,例如音量、亮度、运行时增删的 workspace、当前聚焦的桌面/标签等。它非常高效,应当优先使用。文档中给出的典型命令有:
xprop -spy -root _NET_CURRENT_DESKTOP:监听当前桌面变化;playerctl --follow metadata --format {{title}}:监听正在播放的歌曲。
examples/eww-bar/eww.yuck 中的music变量即用deflisten+playerctl --follow实现实时歌名展示。
内建 "magic" 变量
Eww 开箱即用地提供一些系统变量(如 CPU、内存使用率),多数以 JSON 形式承载数据,可用 JSON 访问语法取值(如示例中的EWW_RAM.used_mem_perc、EWW_DISK["/"].free)。完整的 magic 变量列表见 docs/src/magic-vars.md。
用literal动态生成组件树
当需要动态生成整个组件结构(而非仅仅改变文本、颜色)时,例如展示数量未知的通知列表,可以使用literal组件:
(defvar variable_containing_yuck "(box (button 'foo') (button 'bar'))") ; 然后在组件内使用: (literal :content variable_containing_yuck)literal接收一个包含单个 yuck 组件树的字符串,Eww 会解析并渲染它,内容变化时自动重渲染。注意此功能效率不高,仅在必要时使用。
窗口 ID 与窗口参数
当需要用一个窗口配置生成多个实例时(例如每个显示器一个状态栏),ID 与参数系统非常有用。
窗口 ID
open命令可通过--id指定 ID,缺省时 ID 为窗口配置名:
eww open my_bar --screen 0 --id primary eww open my_bar --screen 1 --id secondaryopen-many使用名称:ID的结构(省略 ID 时同样回退为配置名):
eww open-many my_config:primary my_config:secondary窗口参数
参数用于让同一配置的多个窗口呈现差异(如 1080p 与 4K 屏幕用不同 class、不同尺寸或位置)。注意:这些参数在窗口打开后是常量,无法更新。
窗口定义参数的方式与 widget 完全相同:
(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))打开时用--arg传参(可选参数可省略):
eww open my_bar --id primary --arg arg1=some_value --arg arg2=another_valueopen-many的写法是(注意--arg必须放在所有窗口名之后):
# 注意:--arg 必须放在所有窗口名称之后 eww open-many my_bar:primary --arg primary:arg1=some_value --arg primary:arg2=another_value在open命令中,--screen、--anchor、--pos、--size等选项的效果,也可以通过把同名参数写进--arg来实现。另外有两个特殊参数:
id:若参数列表中出现id,会被设置为--id指定的值(缺省为配置名),可在 Eww 命令中用它关闭当前窗口;screen:若指定screen,会被设置为--screen的值,供其他组件访问屏幕相关信息。
open-many参数的更多细节
open-many的--arg不要求每个参数都带 ID 前缀——不带 ID 的参数会应用到所有窗口:
eww open-many my_bar:primary my_bar:secondary --arg gui_size="small"如果窗口没有显式指定 ID(即 ID 回退为配置名),也可以用配置名来定向传参:
eww open-many my_primary_bar --arg my_primary_bar:screen=0从源码 crates/eww/src/opts.rs 可以看出,open-many的窗口参数以(String, String, VarName, DynVal)四元组形式解析,而parse_window_config_and_id使用split_once(':')解析名称:ID,未含冒号时 ID 回退为名称本身(见 crates/eww/src/opts.rs)。
用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 生成 workspace 列表等场景非常有用,多数情况下可以替代literal且应优先使用。更复杂的数据结构示例见 examples/data-structures/eww.yuck。
拆分你的配置
配置变大后可以拆分成多个文件,有两种方式:
使用include
(include "./path/to/your/file.yuck")单个 yuck 文件可以用include指令导入任意其他 yuck 文件的内容。
使用独立的配置目录
如果希望进一步分离不同 widget 集合,可以创建独立配置目录,然后给每一个Eww 命令都加上--config /path/to/your/config/dir参数(包括eww kill、eww logs等)。这会在主配置之外启动一个拥有独立日志与状态的 daemon 实例。该参数在 crates/eww/src/opts.rs 中被定义为全局参数:override path to configuration directory (directory that contains eww.yuck and eww.(s)css)。
表达式语言速览
yuck 内置一套表达式语言,可放在配置中任意{ ... }位置或字符串插值"foo ${ ... } bar"内,用于条件判断、数学运算与 JSON 访问。示例:
(box "Some math: ${12 + foo * 10}" (button :class {button_active ? "active" : "inactive"} :onclick "toggle_thing" {button_active ? "disable" : "enable"}))支持的特性包括:
- 数学运算:
+、-、*、/、% - 比较:
==、!=、>、<、<=、>= - 布尔运算:
||、&&、! - 正则匹配
=~(Rust 正则风格,左侧为正则、右侧为字符串,如workspace.name =~ '^special:.+$') - Elvis 运算符
?::左侧为""或 JSONnull时返回右侧,否则返回左侧 - 安全访问运算符
?./?.[index]:左侧为空字符串或 JSONnull时返回null;注意对空 JSON 字符串('""')做索引是错误,且若左侧存在但不是对象/数组(如 Number、String)仍会报错 - 条件表达式:
condition ? 'value' : 'other value' - 字面量与变量引用:
12、'hi'、true、some_variable - JSON 访问:
object.field、array[12]、object["field"](需要变量持有合法的 JSON 字符串)
常用函数包括round、floor、ceil、三角函数(弧度制)、min/max、powi/powf、log、degtorad/radtodeg、replace/search/matches/captures、strlength/arraylength/objectlength、jq(内部基于 jaq)、get_env、formattime与formatbytes。完整函数签名与参数说明见 docs/src/expression_language.md。
Eww 命令行速查
从 crates/eww/src/opts.rs 的Action枚举可以确认当前版本完整的子命令集合,其中常用的包括:
| 命令 | 别名 | 作用 |
|---|---|---|
eww daemon | d | 启动 Eww 常驻 daemon |
eww open | o | 打开窗口(支持--id、--screen、--pos、--size、--anchor、--toggle、--duration、--arg) |
eww open-many | - | 一次打开多个窗口 |
eww close | c | 关闭窗口 |
eww close-all | ca | 关闭所有窗口(不杀 daemon) |
eww update | u | 更新变量值 |
eww poll | - | 强制轮询轮询变量 |
eww reload | r | 重载配置 |
eww kill | k | 终止 daemon |
eww logs | - | 查看并跟踪 Eww 日志 |
eww state | - | 打印当前所有打开的窗口使用的变量(--all显示全部) |
eww get | - | 获取变量值 |
eww list-windows | - | 列出已配置窗口名 |
eww active-windows | - | 以<window_id>: <window_name>格式显示活动窗口 ID |
eww ping | - | 探测 Eww server 是否可达 |
eww inspector | debugger | 打开 GTK 调试器 |
eww debug | - | 打印 Eww 眼中的组件树结构(排障、报 bug 时提供上下文) |
eww graph | - | 以 graphviz dot 格式输出 scope graph 结构 |
全局参数还包括--debug(输出调试日志)、--force-wayland(强制使用 Wayland 后端)、--config(指定配置目录)、--logs(执行命令后跟随日志输出)、--no-daemonize(不进行 daemon 化)与--restart(执行命令前完全重启 daemon)。
进阶阅读
- docs/src/configuration.md:yuck 配置语言的完整参考(窗口属性、变量、
literal、for、include等) - docs/src/expression_language.md:表达式语言全量函数与运算符说明
- docs/src/widgets.md:内建组件(
box、label、button、scale等)文档 - docs/src/magic-vars.md:内建 magic 变量列表
- docs/src/working_with_gtk.md:GTK 主题化技巧
- docs/src/troubleshooting.md:常见问题排查
- examples/eww-bar/eww.yuck 与 examples/eww-bar/eww.scss:完整的状态栏示例(含 SCSS 样式)
- examples/data-structures/eww.yuck:JSON 数据结构在配置中的应用示例
另外,Eww 采用 MIT 许可证(见根目录 LICENSE),版本演进记录见 CHANGELOG.md。
【免费下载链接】ewwElKowars wacky widgets项目地址: https://gitcode.com/gh_mirrors/ew/eww
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考