| name | awesome-cairo |
| description | Work with Cairo surfaces, custom widget drawing, and theme asset generation in this AwesomeWM config. Use when creating painted surfaces, custom widget :draw() methods, gradient backgrounds, icon recoloring, shape functions, or cropping/transforming images. |
AwesomeWM Cairo Surface & Drawing Guide
This skill encodes the exact Cairo (lgi.cairo) patterns used across this
AwesomeWM config — from theme asset generation and surface cropping to custom
widget drawing and gradient backgrounds.
When to Use
- Creating or modifying a Cairo surface (e.g., generating icon images, cropping
a surface, compositing multiple layers)
- Implementing a custom widget with a
:draw(cr, width, height) method
- Working with gradient backgrounds (linear, radial) in theme or UI components
- Recoloring icons via
gears.color.recolor_image
- Building shape functions like
modules/shapes/init.lua
- Generating theme assets via the
theme_assets.lua pattern
- Debugging drawing glitches in container backgrounds, borders, or client shapes
Architecture
All Cairo access goes through lgi (Lua GObject introspection):
local cairo = require("lgi").cairo
Key modules in this codebase that use Cairo directly:
| Module | File | Purpose |
|---|
modules/shapes | modules/shapes/init.lua | Shape function factories (rrect, circle, squircle) |
modules/crop_surface | modules/crop_surface/init.lua | Aspect-ratio cropping of Cairo surfaces |
upstream/beautiful/theme_assets | upstream/beautiful/theme_assets.lua | Generating taglist squares, AwesomeWM name, layout icons |
upstream/gears/surface | upstream/gears/surface.lua | Surface loading, saving, scaling, compositing |
upstream/gears/color | upstream/gears/color.lua | Color parsing, gradient creation, icon recoloring |
upstream/wibox/drawable | upstream/wibox/drawable.lua | Main widget drawing pipeline (background, wallpaper, dirty regions) |
upstream/wibox/hierarchy | upstream/wibox/hierarchy.lua | Per-widget hierarchy drawing with groups and transforms |
upstream/wibox/container/background | upstream/background.lua | Background rendering with shape clipping, gradients, masks |
modules/layouts/navigator | modules/layouts/navigator.lua | Custom widget with :draw() method painting gradients + shapes |
1. Basic Surface Creation
Creating a blank surface and drawing on it
local cairo = require("lgi").cairo
local img = cairo.ImageSurface(cairo.Format.ARGB32, width, height)
local mask = cairo.ImageSurface(cairo.Format.A1, width, height)
local rgb = cairo.ImageSurface(cairo.Format.RGB24, width, height)
local cr = cairo.Context(img)
Surface from existing data (used in naughty notifications)
local stride = cairo.Format.stride_for_width(format, w)
local surf = cairo.ImageSurface.create_for_data(pixels, format, w, h, stride)
Loading surfaces from files
local surface = require("gears.surface")
local surf = surface.load_silently(path, false)
local ok, surf = pcall(surface.load_silently, path, false)
Getting surface properties
local w, h = surface.get_size(surf)
2. Drawing Operations
Fills and Strokes
cr:fill()
cr:stroke()
cr:fill_preserve()
cr:paint()
cr:paint_with_alpha(0.5)
Rectangles
cr:rectangle(x, y, width, height)
cr:fill()
cr:set_source(color("#ff0000"))
cr:rectangle(0, 0, width, height)
cr:fill()
Arcs and Circles
cr:arc(cx, cy, r, 0, 2 * math.pi)
cr:fill()
cr:arc(w - i - ri, ri, ri, -math.pi / 2, 0)
cr:arc(w - i - ri, h - i - ri, ri, 0, math.pi / 2)
cr:arc(i + ri, h - i - ri, ri, math.pi / 2, math.pi)
cr:arc(i + ri, ri, ri, math.pi, 3 * math.pi / 2)
Lines and Curves
cr:move_to(x, y)
cr:line_to(x, y)
cr:rel_move_to(dx, dy)
cr:rel_line_to(dx, dy)
cr:curve_to(cp1x, cp1y, cp2x, cp2y, endx, endy)
cr:rel_curve_to(dx1, dy1, dx2, dy2, dx3, dy3)
cr:close_path()
cr:set_line_width(width)
Path Management
cr:new_path()
Example: Navigator gradient bars (from modules/layouts/navigator.lua)
for i = 1, num do
local cc = i % 2 == 1 and bg1 or bg2
local l = i * style.gradstep
cr:set_source(color(cc))
cr:move_to(0, (i - 1) * style.gradstep)
cr:rel_line_to(0, style.gradstep)
cr:rel_line_to(l, -l)
cr:rel_line_to(-style.gradstep, 0)
cr:close_path()
cr:fill()
end
3. Colors and Sources
Setting Solid Colors
local gears_color = require("gears.color")
cr:set_source(gears_color("#ff0000"))
cr:set_source(gears_color("#ff0000cc"))
cr:set_source_rgba(r, g, b, a)
cr:set_source_rgb(r, g, b)
Setting Source from Another Surface
cr:set_source_surface(source_surf, offset_x, offset_y)
cr:paint()
cr.operator = cairo.Operator.SOURCE
cr.operator = cairo.Operator.OVER
cr.operator = cairo.Operator.CLEAR
Icon Recoloring (heavily used in this config)
local gcolor = require("gears.color")
local colored_icon = gcolor.recolor_image(icon_path, beautiful.fg)
local colored_icon = gcolor.recolor_image(icon_path, "#ffffff")
Checking Source Types
if cairo.Surface:is_type_of(pattern) then
pattern = cairo.Pattern.create_for_surface(pattern)
end
if not cairo.Pattern:is_type_of(pattern) then
end
4. Gradients
Linear Gradients (used extensively in theme)
local cairo = require("lgi").cairo
local pattern = cairo.Pattern.create_linear(x0, y0, x1, y1)
pattern:add_color_stop_rgba(0, r1, g1, b1, a1)
pattern:add_color_stop_rgba(1, r2, g2, b2, a2)
cr:set_source(pattern)
cr:paint()
Convenience via gears.color:
local pattern = gears_color("linear:0,0:0,32:0,#ff0000:1,#00ff00")
beautiful.bg_gradient_button =
"linear:0,0:0,32:0," .. beautiful.black .. ":1," .. beautiful.bg_dark
Radial Gradients
local pattern = cairo.Pattern.create_radial(cx0, cy0, r0, cx1, cy1, r1)
pattern:add_color_stop_rgba(0, r, g, b, a)
pattern:add_color_stop_rgba(1, r2, g2, b2, a2)
String-based Gradient Parsing (from upstream/gears/color.lua)
5. Surface Manipulation
Cropping to Aspect Ratio (from modules/crop_surface/init.lua)
This is the exact pattern used in this codebase for cropping images:
local function crop_to_aspect_ratio(ratio, surf)
local old_w, old_h = gears.surface.get_size(surf)
local old_ratio = old_w / old_h
if old_ratio == ratio then return surf end
local new_h, new_w = old_h, old_w
local offset_h, offset_w = 0, 0
if old_ratio < ratio then
new_h = math.ceil(old_w * (1 / ratio))
offset_h = math.ceil((old_h - new_h) / 2)
else
new_w = math.ceil(old_h * ratio)
offset_w = math.ceil((old_w - new_w) / 2)
end
local out_surf = cairo.ImageSurface(cairo.Format.ARGB32, new_w, new_h)
local cr = cairo.Context(out_surf)
cr:set_source_surface(surf, -offset_w, -offset_h)
cr.operator = cairo.Operator.SOURCE
cr:paint()
return out_surf
end
Compositing One Surface onto Another
local function composite(dest_surf, source_surf, dx, dy)
local cr = cairo.Context(dest_surf)
cr:set_source_surface(source_surf, dx, dy)
cr.operator = cairo.Operator.OVER
cr:paint()
return dest_surf
end
Surface Duplication and Transformation
local surface = require("gears.surface")
local dup = surface.duplicate_surface(surf)
local scaled = surface.scale(surf, new_width, new_height)
6. Groups and Operators
Drawing Groups (used extensively in wibox containers)
Groups allow compositing a set of drawing operations as a single unit:
cr:push_group()
cr:rectangle(x, y, w, h)
cr:set_source(color("#ff0000"))
cr:fill()
cr:pop_group_to_source()
cr:paint()
local pattern = cr:pop_group()
cr:set_source(pattern)
cr:paint()
cr:push_group_with_content(cairo.Content.COLOR_ALPHA)
cr:push_group_with_content(cairo.Content.ALPHA)
Operators
cr.operator = cairo.Operator.SOURCE
cr.operator = cairo.Operator.OVER
cr.operator = cairo.Operator.CLEAR
cr:set_operator(cairo.Operator.CLEAR)
cr:move_to(x, y)
cr:rel_line_to(...)
cr:stroke()
cr:set_operator(cairo.Operator.OVER)
Mask Pattern (from upstream/wibox/container/background.lua)
cr:push_group_with_content(cairo.Content.ALPHA)
cr:set_source_rgba(0, 0, 0, 1)
cr:rectangle(x, y, w, h)
cr:fill()
cr:set_source_rgba(0, 0, 0, 0)
local mask = cr:pop_group()
cr:set_source(source_pattern)
cr:mask(mask)
7. Custom Widget Drawing
Pattern: make_widget() with fit() and draw()
Used in modules/layouts/navigator.lua for custom-painted widgets:
local wibox = require("wibox")
local function make_custom_widget(args)
local widg = wibox.widget.base.make_widget()
widg._data = { value = args.initial_value or 0 }
function widg:set_value(val)
if widg._data.value ~= val then
widg._data.value = val
self:emit_signal("widget::redraw_needed")
end
end
function widg:fit(_, width, height)
return width, height
end
function widg:draw(_, cr, width, height)
cr:set_source(color("#ff0000"))
cr:rectangle(0, 0, width, height)
cr:fill()
cr:set_source(color("#ffffff"))
cr:move_to(width / 2, height / 2)
end
return widg
end
Key details:
- Always call
self:emit_signal("widget::redraw_needed") when data changes
:fit() returns the natural dimensions (the widget's preferred size)
:draw() receives a pre-clipped Cairo context — coordinate system starts at (0,0)
- The default
wibox.widget.base.make_widget() supports all signal and layout machinery
Shape Functions (from modules/shapes/init.lua)
Shape functions are closures that take (cr, width, height) and draw a path:
local function rrect(radius)
return function(cr, w, h)
gears.shape.rounded_rect(cr, w, h, dpi(radius))
end
end
local widget = wibox.widget {
shape = shapes.rrect(10),
widget = wibox.container.background,
}
Common shape functions from modules/shapes:
| Function | Description |
|---|
rrect(rad) | Rounded rectangle, all corners radius rad |
rbar() | Rounded bar (pill shape) |
prrect(tl, tr, br, bl, rad) | Partially rounded rect — specify which corners |
circle(rad) | Circle/ellipse |
squircle(rad, inset) | Squircle with inset border |
8. Theme Asset Generation (from upstream/beautiful/theme_assets.lua)
Taglist Squares (solid fill / outline)
function theme_assets.taglist_squares_sel(size, fg)
local img = cairo.ImageSurface(cairo.Format.ARGB32, size, size)
local cr = cairo.Context(img)
cr:set_source(gears_color(fg))
cr:paint()
return img
end
function theme_assets.taglist_squares_unsel(size, fg)
local img = cairo.ImageSurface(cairo.Format.ARGB32, size, size)
local cr = cairo.Context(img)
cr:set_source(gears_color(fg))
cr:set_line_width(size / 4)
cr:rectangle(0, 0, size, size)
cr:stroke()
return img
end
Generating Named Assets with Recoloring (from theme_assets)
for _, layout_data in ipairs(layouts) do
local name = layout_data[1]
local image = layout_data[2]
theme[name] = recolor_image(image, color)
end
Putting Text on a Surface (theme_assets gen_awesome_name)
local PangoCairo = lgi.PangoCairo
local ctx = PangoCairo.font_map_get_default():create_context()
9. Drawing Pipeline (for debugging)
When debugging display issues, understand that the widget draw pipeline is:
wibox/drawable.lua: main loop → computes dirty region
└─ drawable:draw() → creates cairo.Context → paints background + wallpaper
└─ wibox/hierarchy.lua:draw() → per-widget
├─ cr:save() → cr:transform(matrix) → cr:clip()
├─ cr:push_group()
│ └─ widget:draw(cr, width, height) ← YOUR CUSTOM DRAW CODE
├─ cr:pop_group_to_source()
└─ cr:paint_with_alpha(opacity)
└─ cr:restore()
For container backgrounds (the most common drawing in this config):
wibox/container/background.lua:draw()
├─ cr:save()
├─ Apply shape clipping (if shape is set)
├─ Draw background gradient/color/image
├─ Draw foreground
├─ cr:restore()
└─ Draw children (the widget content)
10. Common Patterns in this Config
Gradient Button Backgrounds
beautiful.bg_gradient_button = "linear:0,0:0,32:0,#1a1a2e:1,#16213e"
beautiful.bg_gradient_button_alt = "linear:0,0:0,32:0,#16213e:1,#0f3460"
beautiful.bg_gradient_recessed = "linear:0,0:0,32:0,#0f3460:1,#1a1a2e"
bg = beautiful.bg_gradient_button
Icon Recoloring Pipeline
local image = gears.color.recolor_image(icon_svg_path, beautiful.fg)
widget:set_image(image)
Surface Cropping for Circular/Avatar Images
local img = gears.surface.load_silently(path)
local cropped = crop_to_aspect_ratio(1, img)
wibox.widget {
image = cropped,
shape = shapes.circle(),
widget = wibox.container.background,
}
Widget-to-Cairo Example: Progress Bars, Arc Charts
The upstream widgets (wibox.widget.progressbar, wibox.container.arcchart) use
:draw() methods with Cairo. For custom progress visualization, study these:
function widg:draw(_, cr, width, height)
cr:set_source(color(bg))
cr:rectangle(0, 0, width, height)
cr:fill()
cr:rectangle(0, 0, width * self._data.value, height)
cr:set_source(color(fg))
cr:fill()
end
Image with Background Layer
local img = cairo.ImageSurface(cairo.Format.ARGB32, size, size)
local cr = cairo.Context(img)
cr:set_source(gears_color(bg_color))
cr:arc(center, center, radius, 0, 2 * math.pi)
cr:fill()
cr:set_source_surface(icon_surface, offset, offset)
cr.operator = cairo.Operator.OVER
cr:paint()
return img
Red Flags
- Do NOT call
cr:finish() or cr:destroy() — Lua's GC handles surface cleanup
- Do NOT assume surface dimensions without calling
gears.surface.get_size()
- Do NOT use
os.execute() for image processing — use Cairo
- Do NOT hardcode pixel values — always use
dpi() for theme-aware sizing
- Do NOT use
cairo.Context.create(surface) — use cairo.Context(surface) instead
- ALWAYS use
gears.color() to parse color strings rather than manual hex parsing
- ALWAYS call
cr:save() before modifying transforms/clip and cr:restore() after
- When drawing in a widget
:draw() method, the context is already clipped — do NOT call cr:clip() unless absolutely necessary
cr.operator is set as an assignment (cr.operator = cairo.Operator.OVER), NOT a method call