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: