Create AwesomeWM popup components with this codebase's singleton, gobject, and OSD patterns. Use when adding a popup, wiring mutual-exclusion, or implementing control panel applets.
التثبيت
التثبيت باستخدام Codex أو Claude انسخ هذا Prompt والصقه في Codex أو Claude أو مساعد آخر ليراجع صفحة Skill ويثبّتها لك.
Create AwesomeWM popup components with this codebase's singleton, gobject, and OSD patterns. Use when adding a popup, wiring mutual-exclusion, or implementing control panel applets.
AwesomeWM Popup Patterns
This skill encodes the exact patterns used for popup UI components in this
AwesomeWM configuration. All popups follow the singleton + gobject + gtable.crush
pattern with _private state, mutual-exclusion signal wiring in ui/init.lua,
and centralized click-to-hide via click_to_hide.popup().
When to Use
Adding a new popup (directory + init.lua + registration in ui/init.lua)
Modifying show/hide/toggle logic on an existing popup
Wiring mutual-exclusion signals between popups
Creating a control panel applet (button.lua + page.lua submodule pattern)
Creating an on-screen-display popup (volume, brightness, etc.)
Adding click-to-hide or Escape-to-dismiss behavior
Reviewing or refactoring an existing popup
File Structure
ui/popups/{name}/
├── init.lua # Popup singleton (required)
├── submodule.lua # Optional submodule for complex popups
└── icons/ # Optional SVG icons directory
# Control panel applets (submodules of control_panel):
ui/popups/control_panel/{applet_name}/
├── init.lua # Applet widget (instantiable, not singleton)
├── button.lua # Toggle/reveal button for control panel
└── page.lua # Full-page view (for applets with drill-down)
Popup Singleton Pattern
Every popup is a singleton — exactly one instance created lazily via
get_default(). The pattern mirrors service singletons.
OSD popups — show() is called many times in rapid succession
(volume changes), and they auto-hide via timer. Example:
on_screen_display/volume/init.lua.
Registration in ui/init.lua
Require the popup singleton at the top of ui/init.lua:
local my_popup = require("ui.popups.my_popup").get_default()
Wire mutual-exclusion — connect property::shown signals so only one
popup is visible at a time:
bottom_right (most common — control_panel, battery)
placement = function(c)
awful.placement.bottom_right(c, {
honor_workarea = true,
margins = {
bottom = (c.screen.bar and c.screen.bar.height or0)
+ (beautiful.useless_gap or0),
right = beautiful.useless_gap or0,
},
})
end
bottom_left (launcher)
placement = function(c)
awful.placement.bottom_left(c, {
honor_workarea = true,
margins = {
bottom = (c.screen.bar and c.screen.bar.height or0)
+ (beautiful.useless_gap or0),
left = beautiful.useless_gap or0,
},
})
end
centered (powermenu)
placement = awful.placement.centered,
OSD Popup Pattern
On-screen-display popups (volume, brightness, layouts) use a lighter-weight
pattern — no animation, no backdrop, auto-hide timer. Located in
ui/popups/on_screen_display/.
OSDs connect to service signals for automatic display:
-- In bar module or keybind handler:local osd_volume = require("ui.popups.on_screen_display.volume").get_default()
-- Connect to audio servicelocal audio = require("service.audio").get_default()
audio:connect_signal("default-sink::volume", function(_, value, is_muted)
osd_volume:show(value, is_muted)
end)
Control Panel Integration
If a popup belongs in the control panel (ui/popups/control_panel/), it
follows the applet pattern with button/page/init submodules.
Applet Structure
ui/popups/control_panel/{applet_name}/
├── init.lua # Main applet widget (instantiable, returns wibox.widget)
├── button.lua # Toggle/reveal button in control panel header
└── page.lua # Full-page drill-down view (for complex applets)
init.lua Pattern (instantiable widget)
Unlike popups, applet widgets are not singletons — they return a
callable module that creates widget instances:
localfunctionnew()local ret = wibox.widget({
widget = wibox.container.background,
bg = beautiful.bg_alt .. "aa",
shape = shapes.rrect(10),
-- ... content with id'd children ...
})
-- Connect to services via signal wiringlocal service = require("service.some_service").get_default()
service:connect_signal("property::value", function(_, val)local child = ret:get_children_by_id("some-id")[1]
child:set_markup(tostring(val))
end)
return ret
endreturnsetmetatable({
new = new,
}, {
__call = function(_, ...)return new(...) end,
})
button.lua Pattern
Buttons connect to service signals to update their state display:
local wibox = require("wibox")
local beautiful = require("beautiful")
local service = require("service.some_service").get_default()
localfunctionnew()local ret = wibox.widget({
widget = wibox.container.background,
-- ... button with id="reveal-button" ...
})
-- Connect to service signals to update state
service:connect_signal("property::state", function(_, state)local btn = ret:get_children_by_id("reveal-button")[1]
-- Update button appearance based on stateend)
return ret
endreturnsetmetatable({ new = new }, { __call = function(_, ...)return new(...) end })
The control panel's init.lua wires button clicks to page navigation:
Pages are full-view replacements for the control panel's main-layout:
local wibox = require("wibox")
local beautiful = require("beautiful")
local dpi = beautiful.xresources.apply_dpi
localfunctionnew()return wibox.widget({
layout = wibox.layout.fixed.vertical,
spacing = dpi(6),
-- ... page content ...-- Must include a close button with id="bottombar-close-button":
{
id = "bottombar-close-button",
widget = wibox.widget.textbox,
-- ... styled as a back/close button ...
},
})
endreturnsetmetatable({ new = new }, { __call = function(_, ...)return new(...) end })
Registration in control_panel/init.lua
-- 1. Requirelocal my_applet = require("ui.popups.control_panel.my_applet")
local my_button = require("ui.popups.control_panel.my_applet.button")
local my_page = require("ui.popups.control_panel.my_applet.page")
-- 2. In constructor:
wp.my_applet = my_applet()
wp.my_button = my_button()
wp.my_page = my_page()
-- 3. Wire button to page
wp.my_button:get_children_by_id("reveal-button")[1]:buttons({
awful.button({}, 1, function() ret:setup_my_page() end),
})
-- 4. Wire page close button back to main
wp.my_page:get_children_by_id("bottombar-close-button")[1]:buttons({
awful.button({}, 1, function() ret:setup_main_page() end),
})
-- 5. Add navigation methodsfunctioncontrol_panel:setup_my_page()local wp = self._private
local main_layout = self.widget:get_children_by_id("main-layout")[1]
main_layout:reset()
main_layout:add(wp.my_page)
end
Signal Wiring Convention
Popup-to-Service Wiring
Popups connect to services using entity::event signal convention:
Service
Signal
Usage
service.audio
default-sink::volume
Volume changed
service.audio
default-sink::mute
Mute toggled
service.audio
default-source::volume
Mic volume changed
service.audio
default-source::mute
Mic mute toggled
service.battery
property::level
Battery level changed
service.battery
property::is_charging
Charging state changed
service.system_info
property::cpu_usage
CPU usage updated
General pattern:
local svc = require("service.some_service").get_default()
svc:connect_signal("entity::event", function(_, value)-- Update widgetend)
Efficiency: Only Update When Visible
For data-heavy popups, gate updates so they only refresh when the popup is
visible: