Requirements and Setup
NOTE - This tutorial was made considering you have a basic experience is lua
This tutorial requires awesome-git
and imagemagick
. I am using nixos and if you use it too, you can install it via nixpkgs-f2k.
- make a
helpers.lua
file with these functions
01local helpers = {}
02local gears = require("gears")
03
04helpers.rrect = function(radius)
05 radius = radius or dpi(4)
06 return function(cr, width, height)
07 gears.shape.rounded_rect(cr, width, height, radius)
08 end
09end
10
11helpers.colorizeText = function(txt, fg)
12 if fg == "" then
13 fg = "#ffffff"
14 end
15
16 return "<span foreground='" .. fg .. "'>" .. txt .. "</span>"
17end
18
19return helpers
- have these in your
theme/init.lua
file
01local gfs = require("gears.filesystem")
02local theme_path = gfs.get_configuration_dir() .. "/theme/"
03
04theme.sans = "IBM Plex Sans"
05theme.icofont = 'Material Design Icons'
06theme.wall = "path to your wall"
07theme.bg = "#000000"
08theme.fg = "#ffffff"
09theme.err = "#c14d53"
10theme.profilepicture = theme_path .. "/path/to/pfp.jpg"
11theme.scrheight = 1080
12theme.scrwidth = 1920
So let us start without wasting any more time. You need some libraries to implement this. Add this on the top of your file:
1local awful = require("awful")
2local wibox = require("wibox")
3local beautiful = require("beautiful")
4local dpi = beautiful.xresources.apply_dpi
5local helpers = require("helpers")
6local gfs = require("gears.filesystem")
Defining commands
- signals in awesome
awesome.connect_signal()
: This function is used to create a callback for a specific signal. When that signal is called, the callback function is executed
awesome.emit_signal()
: This function is used to trigger a signal. When this function is called, all the registered callbacks associated with it are called
This is an example of how signals work
1-- creating a signal
2awesome.connect_signal("send::notification", function()
3 awful.spawn.with_shell("notify-send 'Hello World!' 'Notification from chadcat7'")
4end)
5
6-- calling the signal
7awesome.emit_signal("send::notification")
- awful.spawn()
awful.spawn
is a function that allows the execution of external commands or programs. great thing about them is that they do not block io.popen
like os.execute
1-- launch firefox
2awful.spawn.with_shell("firefox")
Now that we have learned the basics of signals and spawn we can define our commands
01local powerofficon = ""
02local rebooticon = ""
03local suspendicon = ""
04local exiticon = ""
05local lockicon = ""
06
07local poweroffcommand = function()
08 awful.spawn.with_shell("poweroff")
09 awesome.emit_signal('hide::exit')
10end
11
12local rebootcommand = function()
13 awful.spawn.with_shell("reboot")
14 awesome.emit_signal('hide::exit')
15end
16
17local suspendcommand = function()
18 awesome.emit_signal('hide::exit')
19 awful.spawn.with_shell("systemctl suspend")
20end
21
22local exitcommand = function()
23 awesome.quit()
24end
25
26local lockcommand = function()
27 awesome.emit_signal('hide::exit')
28 awesome.emit_signal('toggle::lock')
29end
Making A Simple Toggleable Display
- wibox
wibox is simple UI element in awesomewm that can be turned into anything you want. for now we want it to be a widget
01awful.screen.connect_for_each_screen(function(s)
02 local exit = wibox({
03 screen = s,
04 width = beautiful.scrwidth,
05 height = beautiful.scrheight,
06 bg = beautiful.bg .. '00',
07 ontop = true,
08 visible = false,
09 })
10
11 exit:setup {
12 layout = wibox.layout.stack
13 }
14 awful.placement.centered(exit)
15 awesome.connect_signal("toggle::exit", function()
16 exit.visible = not exit.visible
17 end)
18 awesome.connect_signal("show::exit", function()
19 exit.visible = true
20 end)
21 awesome.connect_signal("hide::exit", function()
22 exit.visible = false
23 end)
24end)
25
woah this is a lot of code, let us break down what happen
- awful.screen.connect_for_each_screen
well this is required to render your widget in all the screens you have
wibox
exit is the main the big widget that we are going to display on the screen. to set its background to transparent i have added 00
to the current background color
exit:setup
is required to put widgets inside this widget to actually display something other than a blank box
awful.placement.centered
places the widget at the center of the screen.
At the end you will see that I have used signals again. This time they are being used to make a signal for toggling the exit
wibox.
To make this work with a keybind add this to your keybind files
1 awful.key {
2 modifiers = { mod.super },
3 key = 'x',
4 description = 'powermenu',
5 group = 'awesome',
6 on_press = function()
7 awesome.emit_signal('toggle::exit') -- calling the signal
8 end,
9 },
Creating the elements
To create widgets that we will add to our main wibox, we use wibox.widget
. To make a button to close and open, we use:
01local close = wibox.widget {
02 {
03 align = 'center',
04 font = beautiful.icofont .. " 24",
05 markup = helpers.colorizeText('', beautiful.err),
06 widget = wibox.widget.textbox,
07 },
08 widget = wibox.container.place,
09 halign = 'left',
10 buttons = {
11 awful.button({}, 1, function()
12 awesome.emit_signal('hide::exit')
13 end)
14 },
15}
Exaplaining the code -
widget = wibox.container.place
:
This tells us what type of widget are we trying to display. Every widget requires atleast 1 widget
or layout
property. wibox.container.place
allows to place smaller widgets into larger space.
halign
Stands for horizontal alignment that can only be used with wibox.container.place
. The left
part should be pretty clear. (hint: it moves the element to the left side)
buttons
This is used to add mouse interactions. The 2nd argument specifies what type of argument. 1 means left click, 3 means right, 2 means middle, 4 means scroll up and 5 means scroll down. The third arguments lets us tell what we actually wanna execute.
Making all the buttons at once
Now instead of create 5 wibox.widgets
, we can create one function to make them for us
01local createButton = function(icon, cmd, name)
02 local button = wibox.widget {
03 {
04 {
05 {
06 align = 'center',
07 font = beautiful.icofont .. " 35",
08 markup = helpers.colorizeText(icon, '#ffffff'),
09 widget = wibox.widget.textbox
10 },
11 margins = 40,
12 widget = wibox.container.margin
13 },
14 shape = helpers.rrect(10),
15 bg = '#ffffff' .. '10',
16 buttons = {
17 awful.button({}, 1, function()
18 cmd()
19 end)
20 },
21 widget = wibox.container.background
22 },
23 {
24 align = 'center',
25 font = beautiful.sans .. " Bold 16",
26 markup = helpers.colorizeText(name, '#ffffff'),
27 widget = wibox.widget.textbox
28 },
29 spacing = 20,
30 layout = wibox.layout.fixed.vertical
31 }
32 return button
33end
34
35
36
37local poweroffbutton = createButton(powerofficon, poweroffcommand, "Poweroff")
38local rebootbutton = createButton(rebooticon, rebootcommand, "Reboot")
39local lockbutton = createButton(lockicon, lockcommand, "Lock")
40local suspendbutton = createButton(suspendicon, suspendcommand, "Sleep")
41local exitbutton = createButton(exiticon, exitcommand, "Exit")
Now we can add all of our elements in a single big element
01local box = wibox.widget {
02 {
03 {
04 {
05 {
06 {
07 {
08 image = beautiful.profilepicture,
09 forced_height = 200,
10 opacity = 0.7,
11 clip_shape = helpers.rrect(100),
12 forced_width = 200,
13 halign = 'center',
14 widget = wibox.widget.imagebox
15 },
16 widget = wibox.container.background,
17 border_width = dpi(7),
18 forced_width = dpi(200),
19 forced_height = dpi(200),
20 shape = helpers.rrect(100),
21 border_color = '#ffffff'
22 },
23 widget = wibox.container.place,
24 halign = 'center',
25 },
26 {
27 markup = helpers.colorizeText("Namish Pande", beautiful.fg),
28 font = beautiful.sans .. " Semibold 16",
29 align = 'center',
30 valign = 'center',
31 widget = wibox.widget.textbox,
32 },
33 layout = wibox.layout.fixed.vertical,
34 spacing = 10,
35 },
36 {
37 markup = helpers.colorizeText("Choose Wisely, Explorer!", beautiful.fg),
38 font = beautiful.sans .. " Light 46",
39 align = 'center',
40 valign = 'center',
41 widget = wibox.widget.textbox,
42 },
43 {
44 {
45 poweroffbutton,
46 rebootbutton,
47 lockbutton,
48 suspendbutton,
49 exitbutton,
50 layout = wibox.layout.fixed.horizontal,
51 spacing = 40,
52 },
53 widget = wibox.container.margin,
54 top = 40,
55 },
56 spacing = 0,
57 layout = wibox.layout.fixed.vertical
58 },
59 widget = wibox.container.place,
60 halign = 'center',
61 },
62 widget = wibox.container.margin,
63 bottom = 80,
64}
to make this structure a bit less confusing here what is actually happening
01margin >> vertical layout
02| -place widget to place in center
03| | -vertical layout
04| | | -the picture
05| | | -the name
06| | -the message
07| | -margin
08| | | -horizontal layout
09| | | | -poweroffbutton
10| | | | -restartbutton
11| | | | -lockbutton
12| | | | -sleepbutton
13| | | | -exitbutton
Adding the background image
This is where imagemagick
comes into play. But first we will have to make a widget for it
1local back = wibox.widget {
2 id = "bg",
3 widget = wibox.widget.imagebox,
4 forced_height = beautiful.scrheight,
5 horizontal_fit_policy = "fit",
6 vertical_fit_policy = "fit",
7 forced_width = beautiful.scrwidth,
8}
Now we will execute a command with awful.spawn.easy_async_with_shell
to use imagemagick
to convert our wallpaper into a blurred image. easy_async_with_shell
will help us set the background image after the image has been converted
01local makeImage = function()
02 os.execute("mkdir -p ~/.cache/awesome/blur/")
03 local cmd = 'convert ' ..
04 beautiful.wall .. ' -modulate 50 -filter Gaussian -blur 0x6 ~/.cache/awesome/blur/wall.png'
05 awful.spawn.easy_async_with_shell(cmd, function()
06 local blurwall = gfs.get_cache_dir() .. "blur/wall.png"
07 back.image = blurwall
08 end)
09end
10makeImage()
Adding all the elements together
now we can update the exit:setup
to be:
01exit:setup {
02 back,
03 {
04 {
05 close,
06 box,
07 nil,
08 expand = 'none',
09 layout = wibox.layout.align.vertical,
10 },
11 margins = dpi(15),
12 widget = wibox.container.margin,
13 },
14 layout = wibox.layout.stack
15}
the wibox.layout.stack
allows widget to be placed on top of each other
Listening For Keypresses
to listen for keypresses we can use the awful.keygrabber()
. This will help us to execute the comands without using mouse.
01local exit_screen_grabber = awful.keygrabber({
02 auto_start = true,
03 stop_event = "release",
04 keypressed_callback = function(_, _, key, _)
05 if key == "s" then
06 suspendcommand()
07 elseif key == "e" then
08 exitcommand()
09 elseif key == "l" then
10 lockcommand()
11 elseif key == "p" then
12 poweroffcommand()
13 elseif key == "r" then
14 rebootcommand()
15 elseif key == "Escape" or key == "q" or key == "x" then
16 awesome.emit_signal("hide::exit")
17 end
18 end,
19})
as we want it start right after opening our widget we set the auto_start
to true
Controlling the keygrabber
To start and end the keygrabber, we can edit the toggle signals as follows:
01 awesome.connect_signal("toggle::exit", function()
02 if exit.visible then
03 exit_screen_grabber:stop()
04 exit.visible = false
05 else
06 exit.visible = true
07 exit_screen_grabber:start()
08 end
09 end)
10 awesome.connect_signal("show::exit", function()
11 exit_screen_grabber:start()
12 exit.visible = true
13 end)
14 awesome.connect_signal("hide::exit", function()
15 exit_screen_grabber:stop()
16 exit.visible = false
17 end)
Final Code
And we are done! Congratulations on making it this far! This is the full code
001local awful = require("awful")
002local wibox = require("wibox")
003local beautiful = require("beautiful")
004local dpi = beautiful.xresources.apply_dpi
005local helpers = require("helpers")
006local gfs = require("gears.filesystem")
007
008local powerofficon = ""
009local rebooticon = ""
010local suspendicon = ""
011local exiticon = ""
012local lockicon = ""
013
014local poweroffcommand = function()
015 awful.spawn.with_shell("poweroff")
016 awesome.emit_signal('hide::exit')
017end
018
019local rebootcommand = function()
020 awful.spawn.with_shell("reboot")
021 awesome.emit_signal('hide::exit')
022end
023
024local suspendcommand = function()
025 awesome.emit_signal('hide::exit')
026 awful.spawn.with_shell("systemctl suspend")
027end
028
029local exitcommand = function()
030 awesome.quit()
031end
032
033local lockcommand = function()
034 awesome.emit_signal('hide::exit')
035 awesome.emit_signal('toggle::lock')
036end
037
038local close = wibox.widget {
039 {
040 align = 'center',
041 font = beautiful.icofont .. " 24",
042 markup = helpers.colorizeText('', beautiful.err),
043 widget = wibox.widget.textbox,
044 },
045 widget = wibox.container.place,
046 halign = 'left',
047 buttons = {
048 awful.button({}, 1, function()
049 awesome.emit_signal('hide::exit')
050 end)
051 },
052}
053
054local createButton = function(icon, cmd, name)
055 local button = wibox.widget {
056 {
057 {
058 {
059 id = 'text_role',
060 align = 'center',
061 font = beautiful.icofont .. " 35",
062 markup = helpers.colorizeText(icon, '#ffffff'),
063 widget = wibox.widget.textbox
064 },
065 margins = 40,
066 widget = wibox.container.margin
067 },
068 shape = helpers.rrect(10),
069 bg = '#ffffff' .. '10',
070 buttons = {
071 awful.button({}, 1, function()
072 cmd()
073 end)
074 },
075 widget = wibox.container.background
076 },
077 {
078 id = 'text_role',
079 align = 'center',
080 font = beautiful.sans .. " Bold 16",
081 markup = helpers.colorizeText(name, '#ffffff'),
082 widget = wibox.widget.textbox
083 },
084 spacing = 20,
085 layout = wibox.layout.fixed.vertical
086 }
087 return button
088end
089
090
091
092local poweroffbutton = createButton(powerofficon, poweroffcommand, "Poweroff")
093local rebootbutton = createButton(rebooticon, rebootcommand, "Reboot")
094local lockbutton = createButton(lockicon, lockcommand, "Lock")
095local suspendbutton = createButton(suspendicon, suspendcommand, "Sleep")
096local exitbutton = createButton(exiticon, exitcommand, "Exit")
097
098
099local box = wibox.widget {
100 {
101 {
102 {
103 {
104 {
105 {
106 image = beautiful.profilepicture,
107 forced_height = 200,
108 opacity = 0.7,
109 clip_shape = helpers.rrect(100),
110 forced_width = 200,
111 halign = 'center',
112 widget = wibox.widget.imagebox
113 },
114 widget = wibox.container.background,
115 border_width = dpi(7),
116 forced_width = dpi(200),
117 forced_height = dpi(200),
118 shape = helpers.rrect(100),
119 border_color = '#ffffff'
120 },
121 widget = wibox.container.place,
122 halign = 'center',
123 },
124 {
125 markup = helpers.colorizeText("Namish Pande", beautiful.fg),
126 font = beautiful.sans .. " Semibold 16",
127 align = 'center',
128 valign = 'center',
129 widget = wibox.widget.textbox,
130 },
131 layout = wibox.layout.fixed.vertical,
132 spacing = 10,
133 },
134 {
135 markup = helpers.colorizeText("Choose Wisely, Explorer!", beautiful.fg),
136 font = beautiful.sans .. " Light 46",
137 align = 'center',
138 valign = 'center',
139 widget = wibox.widget.textbox,
140 },
141 {
142 {
143 poweroffbutton,
144 rebootbutton,
145 lockbutton,
146 suspendbutton,
147 exitbutton,
148 layout = wibox.layout.fixed.horizontal,
149 spacing = 40,
150 },
151 widget = wibox.container.margin,
152 top = 40,
153 },
154 spacing = 0,
155 layout = wibox.layout.fixed.vertical
156 },
157 widget = wibox.container.place,
158 halign = 'center',
159 },
160 widget = wibox.container.margin,
161 bottom = 80,
162}
163local exit_screen_grabber = awful.keygrabber({
164 auto_start = true,
165 stop_event = "release",
166 keypressed_callback = function(_, _, key, _)
167 if key == "s" then
168 suspendcommand()
169 elseif key == "e" then
170 exitcommand()
171 elseif key == "l" then
172 lockcommand()
173 elseif key == "p" then
174 poweroffcommand()
175 elseif key == "r" then
176 rebootcommand()
177 elseif key == "Escape" or key == "q" or key == "x" then
178 awesome.emit_signal("hide::exit")
179 end
180 end,
181})
182
183awful.screen.connect_for_each_screen(function(s)
184 local exit = wibox({
185 shape = helpers.rrect(0),
186 screen = s,
187 width = beautiful.scrwidth,
188 height = beautiful.scrheight,
189 bg = beautiful.bg .. '00',
190 ontop = true,
191 visible = false,
192 })
193 local back = wibox.widget {
194 id = "bg",
195 widget = wibox.widget.imagebox,
196 forced_height = beautiful.scrheight,
197 horizontal_fit_policy = "fit",
198 vertical_fit_policy = "fit",
199 forced_width = beautiful.scrwidth,
200 }
201 exit:setup {
202 back,
203 {
204 {
205 close,
206 box,
207 nil,
208 expand = 'none',
209 layout = wibox.layout.align.vertical,
210 },
211 margins = dpi(15),
212 widget = wibox.container.margin,
213 },
214 layout = wibox.layout.stack
215 }
216 local makeImage = function()
217 os.execute("mkdir -p ~/.cache/awesome/blur/")
218 local cmd = 'convert ' ..
219 beautiful.wall .. ' -modulate 50 -filter Gaussian -blur 0x6 ~/.cache/awesome/blur/wall.png'
220 awful.spawn.easy_async_with_shell(cmd, function()
221 local blurwall = gfs.get_cache_dir() .. "blur/wall.png"
222 back.image = blurwall
223 end)
224 end
225 makeImage()
226 awful.placement.centered(exit)
227 awesome.connect_signal("toggle::exit", function()
228 if exit.visible then
229 exit_screen_grabber:stop()
230 exit.visible = false
231 else
232 exit.visible = true
233 exit_screen_grabber:start()
234 end
235 end)
236 awesome.connect_signal("show::exit", function()
237 exit_screen_grabber:start()
238 exit.visible = true
239 end)
240 awesome.connect_signal("hide::exit", function()
241 exit_screen_grabber:stop()
242 exit.visible = false
243 end)
244end)
245
And now to make it work, you can require the whole thing in your main file
1require("ui.powermenu") -- or whatever your path is