☰
在 Awesome 中使用 Cairo 与 LGI:从命名映射到自定义绘制实战
2026/10/8 1:38:09 网站建设 项目流程
  • 操作系统

【免费下载链接】awesome

awesome window manager

项目地址:https://gitcode.com/gh_mirrors/awes/awesome
点击查看免费下载

Cairo 是 Awesome 内部使用的 2D 图形库,无论是wibox、awful.wibar、gears.wallpaper还是awful.titlebar,底层都依托 Cairo 表面(surface)完成绘制。本文讲解如何通过 LGI(Lua GObject Introspection)在纯 Lua 代码中直接调用 Cairo、Pango、PangoCairo、RSVG 等 C 库,并深入仓库源码,展示如何手工创建 Cairo 表面、绘制路径,并把绘制结果挂接到bgimage上作为 wibox 的背景。

LGI 是什么:无需 C 胶水代码的 GObject 绑定

Awesome 的界面接口绝大部分建立在名为LGI的库之上。LGI 借助 GObject-introspection 框架,把 GTK、GLib、Cairo、Pango、PangoCairo、RSVG 等 C 库的能力以 Lua 原生 API 的形式暴露出来,使开发者不必编写任何"胶水"C 代码即可调用这些库。

这种方式的主要优势是:

  • 省时:无需为每个函数手写 C 绑定;
  • 功能全:C 库的完整特性被免费暴露出来。

其主要劣势也很明显:

  • 缺乏面向 Lua 的成体系文档与示例,LGI 自身文档中的示例往往不能直接说明某个具体 API 该如何使用;
  • 调用其他 API 往往需要反复试错,当 introspection 数据缺失或不准确时甚至根本无法调用;
  • 直接使用底层 API 很容易导致崩溃;
  • 程序有责任正确检查返回值与错误值——这一点在写自定义绘制代码时尤其重要。

LGI 的版本有硬性要求。在 lib/gears/surface.lua 中可以看到,gears.surface在加载时会解析require('lgi.version'),若主版本号小于等于 0 且(次版本小于 8,或等于 8 但修订号小于 0),则直接报错"lgi too old, need at least version 0.8.0"。也就是说,在 Awesome 中直接使用 LGI,需要系统安装lgi 0.8.0 或更高版本。

从 C 函数到 Lua 命名空间:LGI 的映射规则

GObject 与 GNOME 系的 C 库遵循 C 语言惯例,用下划线模拟命名空间(例如cairo_image_surface_create、CAIRO_FORMAT_ARGB32)。LGI 则把这类命名整理成真正的 Lua 命名空间 API,规则非常直观:

C 函数 → Lua 方法

-- C 语言: cairo_image_surface_create() -- LGI 等价形式: lgi.cairo.ImageSurface.create()

C 枚举 → Lua 命名空间常量

-- C 语言: CAIRO_FORMAT_ARGB32 -- LGI 等价形式: lgi.cairo.Format.ARGB32

C 面向对象的函数式调用 → Lua 冒号方法调用

-- C 语言(需要把 "类" 对象作为第一个参数传入): cairo_line_to(cr, x, y) -- LGI 等价形式: cr:line_to(x, y)

但映射并非毫无例外,命名上存在不一致。例如 Cairo 在 LGI 中叫lgi.cairo,而 GLib 却叫lgi.GLib。要弄清这类差异,最好的实验方式不是反复重启 Awesome,而是在终端打开lua交互解释器,直接用print探查:

print("This will print a table address:", require("lgi").cairo) print("This will print an error:", require("lgi").Cairo)

第一条会打印出lgi.cairo模块的 table 地址,第二条则会因模块不存在而报错——这正是探索 LGI 命名空间最快捷的方法。

在 rc.lua 或模块中引入 LGI 库

不建议在使用函数的地方临时写require,而应在rc.lua或你的 Lua 模块顶部统一引入所需库:

local cairo = require("lgi").cairo

这一习惯同样体现在仓库源码中。例如 lib/gears/color.lua 的顶部就是:

local lgi = require("lgi") local cairo = lgi.cairo local Pango = lgi.Pango

lib/gears/surface.lua 也采用了同样的模式:

local cairo = require("lgi").cairo local GdkPixbuf = require("lgi").GdkPixbuf

Cairo 核心概念

Cairo 是一个被 Awesome、GNOME、XFCE 广泛使用的 2D 图形库,允许你在某个surface(表面)上绘制路径(path)等图形。Awesome 内部大量使用它,能够在 Lua 层直接调用是相当强大的能力。

使用 Cairo 前,需要理解以下五组核心概念。

Surface(表面)

Surface 是发生绘制的区域,即画布的载体。它包含多种类型:

  • 带透明度的彩色图像表面:ARGB32
  • 不带透明度的彩色图像表面:RGB24
  • 带透明度的单色图像表面:A8
  • 不带透明度的单色图像表面:A1
  • SVG 矢量表面
  • 原生(XCB)表面
  • Framebuffer 以及其他对 Awesome 而言较少用到的表面类型

在 lib/gears/surface.lua 中可以看到get_default函数:当参数为nil时,默认创建一个cairo.ImageSurface(cairo.Format.ARGB32, 0, 0)。此外,该文件完整实现了"任意参数 → Cairo surface"的归一化逻辑(load_uncached_silently、load_silently等):传入的是 surface 则直接返回;传入字符串则视为文件名,通过GdkPixbuf.Pixbuf.new_from_file加载为 pixbuf,再经awesome.pixbuf_to_surface转成 surface;其他类型则强制转换。这正是bgimage既能接受文件路径、也能接受已有 surface 的原因。

Sources(源)

Source 是绘制所用的"颜料",可以是颜色、图案(pattern)或渐变。Awesome 中常见的 source 统一由gears.color提供。

gears.color(lib/gears/color.lua)实际就是一个"cairo pattern 对象工厂":Awesome 中几乎所有需要颜色的地方,都会把参数交给gears.color,由其调用create_pattern从字符串或 table 生成 pattern。它可以创建:

  • 纯色(solid)pattern:十六进制颜色#ff8000即生成纯色 pattern;也有限支持命名颜色(如red);
  • 线性渐变(linear)pattern:字符串形式"x0,y0:x1,y1:<stops>",或 table 形式{ type = "linear", from = { x0, y0 }, to = { x1, y1 }, stops = { ... } };
  • 径向渐变(radial)pattern:字符串形式"x0,y0,r0:x1,y1,r1:<stops>",或 table 形式{ type = "radial", from = { x0, y0, r0 }, to = { x1, y1, r1 }, stops = { ... } };
  • PNG 图案 pattern:由surface.load(file)加载图片后,通过cairo.Pattern.create_for_surface创建,并设置cairo.Extend.REPEAT平铺。

这些实现都封装在 lib/gears/color.lua 的create_solid_pattern、create_linear_pattern、create_radial_pattern、create_png_pattern中,并通过color.types表把solid/png/linear/radial四种类型串起来。

需要特别注意:不要修改gears.color返回的 pattern(例如调用:set_matrix()),因为该函数带缓存,修改会带来意想不到的副作用;如需修改,应改用create_pattern_uncached(见 lib/gears/color.lua 的注释说明)。

Context 与 Path(上下文与路径)

Context(上下文)是程序与 surface 之间的代理,内部持有一条path(路径)。路径可以是直线、圆、矩形等图形,可以是闭合的也可以不闭合(即形状)。

所有对 surface 的绘制操作都必须经由 context 完成。当前路径会一直累积延伸,直到它被某个操作"消费"并被重置(见下一节)。在此之前,surface 上什么都画不出来。例如:

cr:rectangle(0 , 0 , 10, 10) cr:rectangle(10, 10, 10, 10)

这两条语句本身不会产生任何可见效果,只有把操作真正应用(apply)到 context 上,图形才会被绘制。

Context 还持有一个变换矩阵(transformation matrix),它会在执行操作时被应用。Awesome 中对应gears.matrix(lib/gears/matrix.lua),该模块实现了仿射变换的描述与计算,提供create、create_translate、create_scale、create_rotate、create_rotate_at等构造器,例如:

matrix.create(xx, yx, xy, yy, x0, y0) matrix.create_translate(x, y) -- 平移 matrix.create_scale(sx, sy) -- 缩放 matrix.create_rotate(angle) -- 旋转(弧度)

Operations(操作)

对路径可执行多种操作,最常见的四种:

  • fill(填充):用当前 source 填充路径内部;
  • stroke(描边):用当前 source 绘制路径轮廓线;
  • mask(遮罩):把当前 source 当作 alpha 遮罩,配合当前 operator 进行绘制;
  • clip(裁剪):裁剪 surface 的工作区,使后续所有操作都不会影响裁剪区域之外的部分。

这些操作在仓库源码中有大量实际运用。例如 lib/gears/wallpaper.lua 在绘制壁纸时会先通过source:create_similar(cairo.Content.COLOR, root_width, root_height)创建目标表面,然后cr = cairo.Context(target)建立 context,并设置cr.operator = cairo.Operator.SOURCE再执行绘制;当 source 是 surface 时,还会先转为cairo.Pattern.create_for_surface(pattern)(同文件 L104-L105)。这些正是文档所述概念在生产代码中的直接体现。

Operators(操作符)

Operator(操作符)是在应用操作时使用的修饰符。Cairo 提供多种 Porter-Duff 组合模式,控制新绘制的像素与已有像素如何合成,常见的如SOURCE、OVER、CLEAR、IN、OUT、ATOP等。在 lib/gears/wallpaper.lua 中可以看到,壁纸平铺(repeat)逻辑同时设置了pattern.extend = cairo.Extend.REPEAT与cr.operator = cairo.Operator.SOURCE,实现以SOURCE模式无残留地平铺图案。

Cairo 在 Awesome 中的角色

Awesome 中的wibox、awful.wibar、gears.wallpaper和awful.titlebar都包含 Cairo surface,可以通过drawinAPI 访问,这让 widget 可以直接使用 Cairo context 进行绘制。想要了解 widget 的工作原理与更多示例,可阅读 声明式布局系统 与 创建新 widget 两篇文章。

从源码角度看,widget 的 Cairo surface 实际上由wibox.drawable(lib/wibox/drawable.lua)统一管理。它是"可以被绘制到的东西"的抽象,内部维护 widget context(包含 screen、dpi、drawable 等字段,见 L33-L58),并为 wibox 提供set_bgimage(L278)。而 lib/wibox/init.lua 中的wibox:set_bgimage只是把参数转发给self._drawable:set_bgimage,并发出property::bgimage信号。

wibox.container.background则展示了bgimage的完整消费链路(lib/wibox/container/background.lua):

if bgimage then if type(self._private.bgimage) == "function" then self._private.bgimage(context, cr, width, height, unpack(self._private.bgimage_args)) else local pattern = cairo.Pattern.create_for_surface(self._private.bgimage) ...

也就是说,bgimage既可以直接给一个 surface(会被包装成 pattern),也可以给一个函数——该函数会收到(context, cr, width, height, ...)参数,在绘制时被调用,从而让你用 Cairo context 画出任意自定义背景。

手工创建 Surface 并绘制:最简完整示例

也可以脱离 wibox 手工创建 surface。以下是最简单的可运行示例(与 lib/gears/surface.lua 的用法一致):

-- 创建一块 50x50 的 ARGB32 图像表面 local img = cairo.ImageSurface.create(cairo.Format.ARGB32, 50, 50) -- 为这块表面创建 context local cr = cairo.Context(img) -- 设置红色 source cr:set_source_rgb(1, 0, 0) -- 等价写法(通过 gears.color 生成 pattern): cr:set_source(gears.color("#ff0000")) -- 在 context 上添加一个 10px 的正方形路径,位于 x=10, y=10 cr:rectangle(10, 10, 10, 10) -- 真正把矩形绘制到 img 上 cr:fill()

这段代码把"建立 surface → 建立 context → 设置 source → 构建 path → 应用操作"五个环节完整走了一遍,与文档中 Surface、Source、Context/Path、Operations 的概念一一对应。测试目录中也能找到同模式的真实用法,例如 tests/examples/awful/template.lua 创建cairo.ImageSurface.create(cairo.Format.ARGB32, 1, 1),以及其中大量cr:set_source(...)、cr:set_source_rgb(...)、cr:set_source_rgba(...)调用。

绘制好的img可以立即作为bgimage使用,挂到wibox、awful.wibar或wibox.container.background上:

screen.primary.mywibox.bgimage = img

更实用的一种写法是给bgimage传一个绘制函数,让每次重绘都能基于当前的 context 动态绘制(wibox 会在需要时调用该函数):

screen.primary.mywibox.bgimage = function(context, cr, width, height) -- 在 wibox 的 Cairo context 上直接绘制 cr:set_source_rgb(1, 0, 0) cr:rectangle(10, 10, 10, 10) cr:fill() end

实战要点与常见坑

  • 版本前提:LGI 版本需不低于 0.8.0,否则gears.surface会在加载时直接报错(见 lib/gears/surface.lua)。
  • 返回值与错误检查:直接使用底层 API 容易崩溃,务必检查返回值与错误状态,这是使用低层接口时程序员不可推卸的责任。
  • source 不要被缓存污染:用gears.color拿到的 pattern 带有缓存,不要对它调用:set_matrix()等修改操作;确有修改需求请走create_pattern_uncached。
  • 路径要"消费":只调用cr:rectangle(...)不会产生任何绘制,必须接着调用fill()/stroke()等操作,路径才会被应用到 surface。
  • 文件路径或 surface 皆可:bgimage接受文件名、已有 surface,也接受绘制函数;字符串路径会被gears.surface通过 GdkPixbuf 自动加载并转换为 surface。
  • 调试命名空间:lua解释器里用print(require("lgi").cairo)这类表达式快速探测模块与大小写,避免反复重启 Awesome 试错。

总结

LGI 把 Cairo、Pango、GLib 等 C 库以 Lua 命名空间方式暴露给 Awesome,映射规则简单(函数、枚举、方法各有固定转换模式),但存在个别大小写不一致,需要借助lua解释器快速验证。掌握 Surface、Source、Context/Path、Operation、Operator 五组概念后,就可以手工创建 Cairo 表面、自由绘制路径,并将结果作为bgimage注入wibox、awful.wibar或wibox.container.background。仓库中gears.color、gears.surface、gears.matrix、gears.wallpaper、wibox.drawable与wibox.container.background等模块就是这套机制的完整参考实现,写作自定义绘制代码时可以直接对照阅读。

  • 操作系统

【免费下载链接】awesome

awesome window manager

项目地址:https://gitcode.com/gh_mirrors/awes/awesome
点击查看免费下载

相关推荐

上一篇:掌握AI情绪识别:5步快速上手Emotion-detection开源项目
下一篇:mpv JavaScript 脚本开发指南:API 全解、事件循环与 CommonJS 模块系统

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

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询