Widgets
Widgets are the building blocks of your SomeWM interface. In this tutorial, you'll build a custom clock widget from scratch to understand how the widget system works.
Widget Basics
Everything visible in SomeWM is made of widgets. There are three main types:
| Type | Purpose | Examples |
|---|---|---|
| Primitives | Display content | textbox, imagebox, progressbar |
| Containers | Modify a single widget | background, margin, constraint |
| Layouts | Arrange multiple widgets | fixed, flex, align |
Widgets compose together like nesting boxes:
-- A textbox inside a margin inside a background
wibox.widget {
{
{
text = "Hello",
widget = wibox.widget.textbox,
},
margins = 4,
widget = wibox.container.margin,
},
bg = "#ff0000",
widget = wibox.container.background,
}
Your First Textbox
Let's start simple - a text widget:
local wibox = require("wibox")
local my_text = wibox.widget {
text = "Hello, SomeWM!",
widget = wibox.widget.textbox,
}
That's it! my_text is now a widget you can add to your wibar.
Building a Clock Widget
Now let's build something useful - a clock that updates every second.
Step 1: Create the Widget
Create a file ~/.config/somewm/widgets/clock.lua:
-- widgets/clock.lua
local wibox = require("wibox")
local gears = require("gears")
local clock = wibox.widget {
text = "",
widget = wibox.widget.textbox,
}
return clock
Step 2: Add Time Updates
We need to update the clock regularly. Use gears.timer:
-- widgets/clock.lua
local wibox = require("wibox")
local gears = require("gears")
local clock = wibox.widget {
text = "",
widget = wibox.widget.textbox,
}
-- Update function
local function update_clock()
clock.text = os.date("%H:%M:%S")
end
-- Timer that runs every second
gears.timer {
timeout = 1,
autostart = true,
call_now = true, -- Run immediately on startup
callback = update_clock,
}
return clock
Step 3: Use it in Your Config
In your rc.lua, require and add the widget:
-- Near the top, after other requires
local my_clock = require("widgets.clock")
-- In your wibar setup (screen.connect_signal "request::desktop_decoration")
s.mywibox = awful.wibar {
position = "top",
screen = s,
widget = {
layout = wibox.layout.align.horizontal,
{ -- Left
layout = wibox.layout.fixed.horizontal,
mylauncher,
s.mytaglist,
},
s.mytasklist, -- Middle
{ -- Right
layout = wibox.layout.fixed.horizontal,
my_clock, -- Add your widget here!
s.mylayoutbox,
},
},
}
Reload with Mod4 + Ctrl + r and you should see your clock!
Adding an Icon
Let's enhance the clock with an icon using imagebox:
-- widgets/clock.lua
local wibox = require("wibox")
local gears = require("gears")
local beautiful = require("beautiful")
-- Icon widget
local clock_icon = wibox.widget {
image = "/usr/share/icons/Adwaita/16x16/status/clock-symbolic.png",
resize = true,
widget = wibox.widget.imagebox,
}
-- Text widget
local clock_text = wibox.widget {
text = "",
widget = wibox.widget.textbox,
}
-- Combine them with a layout
local clock = wibox.widget {
clock_icon,
clock_text,
spacing = 4,
layout = wibox.layout.fixed.horizontal,
}
-- Update function
local function update_clock()
clock_text.text = os.date("%H:%M")
end
gears.timer {
timeout = 60, -- Every minute is enough now
autostart = true,
call_now = true,
callback = update_clock,
}
return clock
Styling with Containers
Add some visual polish with containers:
local clock = wibox.widget {
{
{
clock_icon,
clock_text,
spacing = 4,
layout = wibox.layout.fixed.horizontal,
},
margins = 4,
widget = wibox.container.margin,
},
bg = beautiful.bg_focus,
fg = beautiful.fg_focus,
shape = gears.shape.rounded_rect,
widget = wibox.container.background,
}
This adds:
- Margin container - 4px padding around the content
- Background container - colored background and rounded corners
Adding a Tooltip
Tooltips show extra info on hover:
local awful = require("awful")
-- After creating your widget
local clock_tooltip = awful.tooltip {
objects = { clock },
timer_function = function()
return os.date("%A, %B %d, %Y")
end,
}
Now hovering shows the full date!
Adding Click Handlers
Make widgets interactive with add_button:
clock:add_button(awful.button({}, 1, function()
-- Left click: show notification
local naughty = require("naughty")
naughty.notify {
title = "Current Time",
text = os.date("%A, %B %d, %Y\n%H:%M:%S"),
}
end))
clock:add_button(awful.button({}, 3, function()
-- Right click: spawn calendar app
awful.spawn("gnome-calendar")
end))
Button numbers: 1 = left, 2 = middle, 3 = right.
Complete Example
Here's a polished clock widget with all features:
-- widgets/clock.lua
local awful = require("awful")
local wibox = require("wibox")
local gears = require("gears")
local beautiful = require("beautiful")
local naughty = require("naughty")
-- Icon (use any clock icon you have)
local clock_icon = wibox.widget {
image = gears.color.recolor_image(
"/usr/share/icons/Adwaita/symbolic/status/clock-symbolic.svg",
beautiful.fg_normal
),
forced_width = 16,
forced_height = 16,
widget = wibox.widget.imagebox,
}
-- Time text
local clock_text = wibox.widget {
text = "",
font = beautiful.font,
widget = wibox.widget.textbox,
}
-- Combine with styling
local clock = wibox.widget {
{
{
clock_icon,
clock_text,
spacing = 6,
layout = wibox.layout.fixed.horizontal,
},
left = 8,
right = 8,
widget = wibox.container.margin,
},
bg = beautiful.bg_normal,
widget = wibox.container.background,
}
-- Hover effect
clock:connect_signal("mouse::enter", function(w)
w.bg = beautiful.bg_focus
end)
clock:connect_signal("mouse::leave", function(w)
w.bg = beautiful.bg_normal
end)
-- Tooltip with full date
awful.tooltip {
objects = { clock },
timer_function = function()
return os.date("%A, %B %d, %Y")
end,
}
-- Click to show notification
clock:add_button(awful.button({}, 1, function()
naughty.notify {
title = "Current Time",
text = os.date("%A, %B %d, %Y\n%H:%M:%S"),
timeout = 5,
}
end))
-- Update function
local function update_clock()
clock_text.text = os.date(" %H:%M")
end
-- Timer
gears.timer {
timeout = 60,
autostart = true,
call_now = true,
callback = update_clock,
}
return clock
Common Widget Patterns
Running Shell Commands
For widgets that need system info (battery, volume, etc.):
local function update_battery()
awful.spawn.easy_async_with_shell(
"cat /sys/class/power_supply/BAT1/capacity",
function(stdout)
battery_text.text = stdout:gsub("\n", "") .. "%"
end
)
end
gears.timer {
timeout = 30,
autostart = true,
call_now = true,
callback = update_battery,
}
Reacting to Signals
For real-time updates based on events:
-- Listen for volume changes
awesome.connect_signal("volume::update", function()
update_volume_widget()
end)
-- In your keybinding when volume changes:
awful.spawn.easy_async("wpctl set-volume @DEFAULT_AUDIO_SINK@ 5%+", function()
awesome.emit_signal("volume::update")
end)
Getting Children by ID
For complex widgets, use IDs to access nested parts:
local my_widget = wibox.widget {
{
id = "text_role",
text = "",
widget = wibox.widget.textbox,
},
{
id = "icon_role",
widget = wibox.widget.imagebox,
},
layout = wibox.layout.fixed.horizontal,
}
-- Later, update specific children:
local text = my_widget:get_children_by_id("text_role")[1]
text.text = "Updated!"
Widget Reference
Primitives
| Widget | Use |
|---|---|
wibox.widget.textbox | Display text |
wibox.widget.imagebox | Display images/icons |
wibox.widget.progressbar | Progress bars |
wibox.widget.slider | Sliders |
wibox.widget.checkbox | Checkboxes |
wibox.widget.graph | Line graphs |
Containers
| Container | Effect |
|---|---|
wibox.container.background | Add bg color, fg color, shape |
wibox.container.margin | Add padding |
wibox.container.constraint | Set min/max size |
wibox.container.place | Align widget (center, left, etc.) |
wibox.container.rotate | Rotate content |
Layouts
| Layout | Arrangement |
|---|---|
wibox.layout.fixed.horizontal | Side by side, fixed sizes |
wibox.layout.fixed.vertical | Stacked, fixed sizes |
wibox.layout.flex.horizontal | Side by side, equal widths |
wibox.layout.flex.vertical | Stacked, equal heights |
wibox.layout.align.horizontal | Left, center, right sections |
wibox.layout.align.vertical | Top, center, bottom sections |
Troubleshooting
Widget not showing
- Make sure you
returnthe widget from your module - Check that it's added to the wibar layout
- Try adding
forced_widthandforced_heightto see if it has size
Timer not running
Make sure autostart = true is set:
gears.timer {
timeout = 1,
autostart = true, -- Don't forget this!
callback = update_function,
}
Images not loading
Check the path and use absolute paths:
-- Instead of relative path
image = "~/.config/somewm/icons/clock.svg"
-- Use absolute or expand it
image = os.getenv("HOME") .. "/.config/somewm/icons/clock.svg"
Next Steps
- Wibar - Build a complete status bar
- Theme - Style your widgets
- AwesomeWM Widget Docs - Full widget reference