Keybindings
Keybindings are the heart of a tiling window manager. In this tutorial, you'll learn how to add your own custom shortcuts.
Keybinding Anatomy
Every keybinding in SomeWM has four parts:
awful.key(
{ "Mod4" }, -- 1. Modifiers (table)
"Return", -- 2. Key
function() -- 3. Callback function
awful.spawn(terminal)
end,
{ description = "open a terminal", group = "launcher" } -- 4. Metadata
)
| Part | Description |
|---|---|
| Modifiers | Table of modifier keys: "Mod4", "Shift", "Control", "Mod1" (Alt) |
| Key | The key name: "Return", "space", "a", "F1", etc. |
| Callback | Function to execute when pressed |
| Metadata | Description and group for the help popup |
Adding a Global Keybinding
Global keybindings work anywhere, regardless of which window is focused.
Open your rc.lua and find the keybindings section. Add a new keybinding:
awful.keyboard.append_global_keybindings({
-- Your existing keybindings...
-- Add a new one:
awful.key({ modkey }, "b", function()
awful.spawn("firefox")
end, { description = "open browser", group = "launcher" }),
})
Press Mod4 + Ctrl + r to reload, then try Mod4 + b to open Firefox!
Adding a Client Keybinding
Client keybindings only work when a window is focused, and the focused window (c) is passed to your callback.
Find the client keybindings section in your rc.lua:
client.connect_signal("request::default_keybindings", function()
awful.keyboard.append_client_keybindings({
-- Existing keybindings...
-- Toggle window opacity:
awful.key({ modkey }, "o", function(c)
if c.opacity == 1 then
c.opacity = 0.8
else
c.opacity = 1
end
end, { description = "toggle opacity", group = "client" }),
})
end)
Now Mod4 + o will toggle transparency on the focused window.
Key Names Reference
For a complete list of modifier keys, key names, and mouse buttons, see the Key Names Reference.
Common modifiers: "Mod4" (Super), "Mod1" (Alt), "Shift", "Control"
Common keys: "Return" (Enter), "space", "Tab", "Escape", "Left"/"Right"/"Up"/"Down" (arrows)
Media Keys
Media keys work without modifiers:
awful.keyboard.append_global_keybindings({
-- Volume controls
awful.key({}, "XF86AudioRaiseVolume", function()
awful.spawn("wpctl set-volume @DEFAULT_AUDIO_SINK@ 5%+")
end, { description = "raise volume", group = "media" }),
awful.key({}, "XF86AudioLowerVolume", function()
awful.spawn("wpctl set-volume @DEFAULT_AUDIO_SINK@ 5%-")
end, { description = "lower volume", group = "media" }),
awful.key({}, "XF86AudioMute", function()
awful.spawn("wpctl set-mute @DEFAULT_AUDIO_SINK@ toggle")
end, { description = "toggle mute", group = "media" }),
-- Brightness controls
awful.key({}, "XF86MonBrightnessUp", function()
awful.spawn("brightnessctl s +5%")
end, { description = "increase brightness", group = "media" }),
awful.key({}, "XF86MonBrightnessDown", function()
awful.spawn("brightnessctl s 5%-")
end, { description = "decrease brightness", group = "media" }),
})
Organizing with Tables
As your config grows, keybindings can get messy. Here's a cleaner approach using tables:
-- Define keybindings as data
local my_global_keys = {
-- { modifiers, key, callback, description, group }
{ { modkey }, "b", function() awful.spawn("firefox") end, "browser", "launcher" },
{ { modkey }, "e", function() awful.spawn("thunar") end, "file manager", "launcher" },
{ { modkey, "Shift" }, "s", function() awful.spawn("flameshot gui") end, "screenshot", "utility" },
}
-- Convert table entries to keybindings
local function make_keybindings(definitions)
local keys = {}
for _, def in ipairs(definitions) do
table.insert(keys, awful.key(
def[1], -- modifiers
def[2], -- key
def[3], -- callback
{ description = def[4], group = def[5] }
))
end
return keys
end
-- Apply them
awful.keyboard.append_global_keybindings(make_keybindings(my_global_keys))
This makes it easy to scan and edit your keybindings at a glance.
Helper Functions
For common patterns, create helper functions:
local helpers = {
-- Toggle a client property
toggle_property = function(property)
return function(c)
c[property] = not c[property]
c:raise()
end
end,
-- Spawn a command
spawn = function(cmd)
return function()
awful.spawn(cmd)
end
end,
}
-- Use them in keybindings:
awful.keyboard.append_client_keybindings({
awful.key({ modkey }, "f", helpers.toggle_property("fullscreen"),
{ description = "toggle fullscreen", group = "client" }),
awful.key({ modkey }, "m", helpers.toggle_property("maximized"),
{ description = "toggle maximized", group = "client" }),
})
awful.keyboard.append_global_keybindings({
awful.key({ modkey }, "b", helpers.spawn("firefox"),
{ description = "browser", group = "launcher" }),
})
The Hotkeys Popup
Press Mod4 + s to see all your keybindings organized by group.
The popup automatically includes your keybindings if you set the description and group metadata.
Customizing the Popup
You can style the popup through theme variables:
-- In your theme.lua
theme.hotkeys_bg = "#282828"
theme.hotkeys_fg = "#ebdbb2"
theme.hotkeys_border_color = "#fabd2f"
theme.hotkeys_modifiers_fg = "#fe8019"
theme.hotkeys_label_bg = "#b8bb26"
Numrow Keybindings
For tag switching (1-9), use the declarative pattern with keygroup:
awful.keyboard.append_global_keybindings({
awful.key({
modifiers = { modkey },
keygroup = "numrow",
description = "view tag",
group = "tag",
on_press = function(index)
local screen = awful.screen.focused()
local tag = screen.tags[index]
if tag then
tag:view_only()
end
end,
}),
awful.key({
modifiers = { modkey, "Shift" },
keygroup = "numrow",
description = "move client to tag",
group = "tag",
on_press = function(index)
if client.focus then
local tag = client.focus.screen.tags[index]
if tag then
client.focus:move_to_tag(tag)
end
end
end,
}),
})
The keygroup = "numrow" automatically binds to keys 1-9.
Mouse Bindings
Mouse bindings work similarly, using awful.button:
client.connect_signal("request::default_mousebindings", function()
awful.mouse.append_client_mousebindings({
-- Left click to focus
awful.button({}, 1, function(c)
c:activate({ context = "mouse_click" })
end),
-- Mod4 + Left click to move
awful.button({ modkey }, 1, function(c)
c:activate({ context = "mouse_click", action = "mouse_move" })
end),
-- Mod4 + Right click to resize
awful.button({ modkey }, 3, function(c)
c:activate({ context = "mouse_click", action = "mouse_resize" })
end),
})
end)
Button numbers:
1= Left click2= Middle click3= Right click4= Scroll up5= Scroll down
Complete Example
Here's a complete keybindings module you could save as keybindings.lua:
-- keybindings.lua
local awful = require("awful")
local hotkeys_popup = require("awful.hotkeys_popup")
-- Helper to convert table format to keybindings
local function make_keys(definitions)
local keys = {}
for _, def in ipairs(definitions) do
table.insert(keys, awful.key(def[1], def[2], def[3],
{ description = def[4], group = def[5] }))
end
return keys
end
-- Global keybindings
local global_keys = {
-- Launchers
{ { modkey }, "Return", function() awful.spawn(terminal) end, "terminal", "launcher" },
{ { modkey }, "b", function() awful.spawn("firefox") end, "browser", "launcher" },
{ { modkey }, "e", function() awful.spawn("thunar") end, "file manager", "launcher" },
{ { modkey }, "r", function() awful.screen.focused().mypromptbox:run() end, "run prompt", "launcher" },
-- Awesome
{ { modkey }, "s", hotkeys_popup.show_help, "show help", "awesome" },
{ { modkey, "Control"}, "r", awesome.restart, "reload", "awesome" },
{ { modkey, "Shift" }, "q", awesome.quit, "quit", "awesome" },
-- Layout
{ { modkey }, "space", function() awful.layout.inc(1) end, "next layout", "layout" },
{ { modkey }, "h", function() awful.tag.incmwfact(-0.05) end, "shrink master", "layout" },
{ { modkey }, "l", function() awful.tag.incmwfact(0.05) end, "grow master", "layout" },
-- Focus
{ { modkey }, "j", function() awful.client.focus.byidx(1) end, "next window", "client" },
{ { modkey }, "k", function() awful.client.focus.byidx(-1) end, "prev window", "client" },
{ { modkey }, "Tab", function()
awful.client.focus.history.previous()
if client.focus then client.focus:raise() end
end, "last window", "client" },
}
-- Client keybindings
local client_keys = {
{ { modkey }, "f", function(c) c.fullscreen = not c.fullscreen; c:raise() end, "fullscreen", "client" },
{ { modkey }, "m", function(c) c.maximized = not c.maximized; c:raise() end, "maximize", "client" },
{ { modkey }, "n", function(c) c.minimized = true end, "minimize", "client" },
{ { modkey, "Shift" }, "c", function(c) c:kill() end, "close", "client" },
{ { modkey, "Control"}, "space", awful.client.floating.toggle, "float", "client" },
}
-- Apply global keybindings
awful.keyboard.append_global_keybindings(make_keys(global_keys))
-- Apply client keybindings
client.connect_signal("request::default_keybindings", function()
awful.keyboard.append_client_keybindings(make_keys(client_keys))
end)
-- Numrow tag switching (can't use table format for keygroups)
awful.keyboard.append_global_keybindings({
awful.key({
modifiers = { modkey },
keygroup = "numrow",
description = "view tag",
group = "tag",
on_press = function(index)
local tag = awful.screen.focused().tags[index]
if tag then tag:view_only() end
end,
}),
awful.key({
modifiers = { modkey, "Shift" },
keygroup = "numrow",
description = "move to tag",
group = "tag",
on_press = function(index)
if client.focus then
local tag = client.focus.screen.tags[index]
if tag then client.focus:move_to_tag(tag) end
end
end,
}),
})
Then in your rc.lua:
require("keybindings")
Troubleshooting
Keybinding not working
- Check for typos in key names (they're case-sensitive for modifiers)
- Look for conflicts with existing keybindings
- Make sure you reloaded config with Mod4 + Ctrl + r
Key name not found
Use xev (X11) or check the Default Keybindings reference for correct key names.
Callback errors
Check the notification for error messages. Common issues:
- Missing
localfor variables - Typos in function names
- Missing
endfor functions
Next Steps
- Key Names Reference - Complete list of key names and modifiers
- Default Keybindings - Built-in shortcuts
- Widgets - Create custom widgets
- awful.key (AwesomeWM docs) - Upstream API reference