news 2026/9/23 7:07:42

Eww(ElKowar‘s Wacky Widgets)入门指南:在任意窗口管理器上构建自绘 Widget 系统

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
Eww(ElKowar‘s Wacky Widgets)入门指南:在任意窗口管理器上构建自绘 Widget 系统

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 后端依赖gdkx11x11rb(后者带有randr特性,用于多显示器管理);
  • Wayland 后端依赖gtk-layer-shell,通过 Layer Shell 协议实现层叠窗口效果。

整个项目是一个 Cargo workspace,由多个 crate 组成,其职责划分可以从根目录的 Cargo.toml 中看出:

crate作用
crates/eww主程序,包含 CLI、daemon、窗口管理与 widget 构建逻辑
crates/yuckyuck 配置语言的解析器与 AST 处理
crates/simplexpr内嵌的表达式语言(用于{ ... }${ ... }
crates/notifier_host系统托盘(systray)的 StatusNotifier 宿主实现
crates/eww_shared_util共享工具(如 VarName、Span 等)

安装与构建

前置依赖

安装 Eww 需要rustccargo。官方文档强烈建议使用 rustup 安装 Rust 工具链,而不是依赖系统包管理器提供的版本。

此外,Eww 在编译和运行时尚需若干动态库。不同发行版的包名可能不同,以下是 Arch Linux 上对应的包名清单:

  • gtk3(提供libgdk-3libgtk-3
  • gtk-layer-shell仅 Wayland 需要
  • pangolibpango
  • gdk-pixbuf2libgdk_pixbuf-2
  • libdbusmenu-gtk3
  • cairolibcairolibcairo-gobject
  • glib2libgiolibglib-2libgobject-2
  • gcc-libslibgcc
  • 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.yuckeww.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 0dock窗口类型、struts预留空间(reserve),并将bar组件渲染为内容。

defwindow属性一览

属性说明
monitor窗口显示在哪个显示器上,详见下方说明
geometry窗口的几何信息
unfocus-close窗口失去键盘焦点时自动关闭

monitor属性支持以下取值:

  • 字符串<primary>:让 Eww 尝试识别主显示器(在 Wayland 上可能失败);
  • 整数:显示器索引;
  • 显示器名称;
  • 包含显示器匹配器 JSON 数组的字符串,如'["<primary>", "HDMI-A-1", "PHL 345B1C", 0]',Eww 会按顺序尝试匹配并提供回退。

geometry属性

属性说明
xy窗口位置,支持px%,相对于anchor计算
widthheight窗口尺寸,支持px%
anchor窗口锚点,取值为centertop/center/bottomleft/center/right的组合

平台相关属性

根据 X11 或 Wayland,defwindow还支持额外属性。

X11 专属

属性说明
stacking窗口在层叠中的位置,取值fgbg
wm-ignore是否让窗口管理器忽略该窗口,适合仪表盘式组件(true/false);注意开启后部分其他属性将失效
reserve让窗口管理器为窗口预留空间,适合状态栏避免与其他窗口重叠
windowtype窗口类型,取值normaldocktoolbardialogdesktop;默认值为:指定了reserve时为dock,否则为normal

Wayland 专属

属性说明
stacking层叠位置,取值fgbgoverlaybottom
exclusive合成器是否自动为窗口预留空间(true/false);为true:anchor必须包含center
focusable窗口是否可聚焦,取值noneexclusiveondemand;使用键盘交互的组件必须开启
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接收两个属性:textname?text声明该属性可选(缺省时值为空字符串""),而name必须提供;
  • widget 定义体只能包含一个顶层 widget,否则 Eww 无法确定排列方向与间距,因此多子元素时必须用box包裹;
  • onclick中的"${name}"是字符串插值语法,可以在字符串中引用任意变量;${...}内还能写完整的表达式,详见 docs/src/expression_language.md(官方文档位置为docs/src/expression_language.md,对应线上章节 "Eww expressions")。

渲染子组件:children占位符

当配置变大时,可以把通用结构抽成可复用的包裹型组件,它也能像boxbutton一样接收子元素:

(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_percEWW_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 secondary

open-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_value

open-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 killeww 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'truesome_variable
  • JSON 访问:object.fieldarray[12]object["field"](需要变量持有合法的 JSON 字符串)

常用函数包括roundfloorceil、三角函数(弧度制)、min/maxpowi/powflogdegtorad/radtodegreplace/search/matches/capturesstrlength/arraylength/objectlengthjq(内部基于 jaq)、get_envformattimeformatbytes。完整函数签名与参数说明见 docs/src/expression_language.md。

Eww 命令行速查

从 crates/eww/src/opts.rs 的Action枚举可以确认当前版本完整的子命令集合,其中常用的包括:

命令别名作用
eww daemond启动 Eww 常驻 daemon
eww openo打开窗口(支持--id--screen--pos--size--anchor--toggle--duration--arg
eww open-many-一次打开多个窗口
eww closec关闭窗口
eww close-allca关闭所有窗口(不杀 daemon)
eww updateu更新变量值
eww poll-强制轮询轮询变量
eww reloadr重载配置
eww killk终止 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 inspectordebugger打开 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 配置语言的完整参考(窗口属性、变量、literalforinclude等)
  • docs/src/expression_language.md:表达式语言全量函数与运算符说明
  • docs/src/widgets.md:内建组件(boxlabelbuttonscale等)文档
  • 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),仅供参考

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

人员名单管理避坑指南:3种写法对比,面试必问不慌

人员名单管理避坑指南:3种写法对比,面试必问不慌 配置环境就卡半天,改个依赖包重启三次服务还是报错,这种绝望感谁懂?别急着甩锅给电脑,大概率是你处理数据的方式太原始。 在Java和Go的面试中, 面试必问…

作者头像 李华
网站建设 2026/9/23 7:07:32

车载智能语音系统实战项目源码拆解

车载智能语音系统实战项目源码拆解 学会语法却不知怎么搭项目,这是很多开发者的死穴。 你背熟了 Python 的类定义,Java 的线程池,Go 的协程,但一提到车载智能语音系统,脑子就是一片空白。 别急,今天直接上源码,带你拆解一个真实的实战项目核心逻辑。 入口定位:从唤醒到响应的链路…

作者头像 李华
网站建设 2026/9/23 7:07:26

手机买电影票系统实战:面试必问的并发陷阱

手机买电影票系统实战:面试必问的并发陷阱 刚接手劳务班组管理项目,第一周就让我崩溃了。 为了演示“手机买电影票”功能,我花了一整天配环境。 结果跑起来全是报错,面试被问得哑口无言。 这不仅是配置问题,更是底层逻辑没搞清。 今天把这套坑全填平,全是实战经验。 概念速懂:为什么手机买票会卡死…

作者头像 李华
网站建设 2026/9/23 7:07:10

3个实战项目搞定Microsoft CRM开发

3个实战项目搞定Microsoft CRM开发 看了一堆教程还是不会写项目?别急,这是90%新手的通病。理论懂了,手一放键盘就废。 真正让你上手的,不是视频,是 实战项目 。 今天不讲虚的,直接带你从零搭一个能跑的Microsoft CRM集成方案。 基于微软官方开发者文档和 官方源码仓库…

作者头像 李华
网站建设 2026/9/23 7:06:20

SpringBoot校园足球社团管理平台开发实战

1. 项目概述与核心价值校园足球社团管理平台是基于SpringBoot框架开发的毕业设计项目&#xff0c;旨在解决高校足球社团日常运营中的管理痛点。作为一个完整的实战项目&#xff0c;它涵盖了从需求分析到源码实现的完整开发流程&#xff0c;特别适合计算机相关专业学生作为毕业设…

作者头像 李华
网站建设 2026/9/23 7:06:17

面试被问3d全息影像原理?手写实现优化实战

面试被问3d全息影像原理?手写实现优化实战 上周参加一家头部游戏公司的后端面试,面试官盯着我的简历问:“你做过3d全息影像的实时渲染服务吗?说说底层原理。”我愣了两秒,脑子里全是Three.js的API,却答不上来WebGL的Shader编译开销和GPU显存交换机制。那一刻真尴尬,差点被刷掉。…

作者头像 李华