AwesomeWM Tutorial


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
helpers.lua
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
theme/init.lua
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:

powermenu.lua
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

powermenu.lua
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

powermenu.lua
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

binds.lua
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:

powermenu.lua
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

powermenu.lua
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

powermenu.lua
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

Layout
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

powermenu.lua
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

powermenu.lua
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:

powermenu.lua
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.

powermenu.lua
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:

powermenu.lua
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

powermenu.lua
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

rc.lua
1require("ui.powermenu") -- or whatever your path is
07 July 2023
By Namish