Tips for organizing Sway keybindings

Tips for organizing Sway keybindings
Photo of a Boardsource Unicorne keyboard and Kensington Expert trackball

Here's how I set up my keybindings for the Sway window manager for Linux.

Most of the tips here would apply to any keyboard, but let's get one detail out of the way first that's specific to 40% keyboards like the Corne. It doesn't have a number row, and this first tip addresses that.

How to switch to numbered workspaces without a number row on a 40% keyboard.

Sway supports workspaces numbered 1 through 10. On a full-size keyboard, the sway to switch to each workspace would be "Super-1", "Super-2", etc. There's no number row on a Corne keyboard. The Super key is also known as the Windows Key, the Option Key or the Meta key.

My solution to that was to treat the the top row of letters, the QWERTY row as if it was a number row, so my keybindings are set up like this:

# Positions for latters match 1 to 10
bindsym $mod+q workspace number 1
bindsym $mod+w workspace number 2
bindsym $mod+e workspace number 3
bindsym $mod+r workspace number 4
bindsym $mod+t workspace number 5
bindsym $mod+y workspace number 6
bindsym $mod+u workspace number 7
bindsym $mod+i workspace number 8
bindsym $mod+o workspace number 9
bindsym $mod+p workspace number 10

So in my head, I'm thinking about 1 to 10. Think of it as a layer for the Super key. When I press the Super key, to my brain and it's like the keys do represent 1-0, but without requiring additional layer switching.

Keybinding Layers: main, danger, utility

My main Sway keybinding "layer" contains contain actions and needs to be fast to access. These are all the keybindings that require simply the Super key as a prefix.

I use the word "layers" in quotes, because like my workspace switching above, this is a mental model that's useful for me. The reality is that these layers are different keybinding modifier combinations and not related to how layers are implemented in programmable keyboards.

My "danger" layer of Sway keybindings contains commands that I don't want to trigger by accident, including closing windows and suspending. These keybindings all have the "Super+Shift" prefix.

My "utility" layer of Sway keybindings is full of bindings for launching utility apps that I use frequently throughout the day. This includes a number of a Dmenu-based utilities as a calculator, terminal and password manager.

To recap:

Sway keybinding prefix Use
Super Main: Window management and commonly-used commands
Super+Shift Danger: Suspending, closing windows, reloading
Super+Control Utility: Launch common utility apps

One thing you'll see below is that my layers are a little fluid. I have utilities on the main layer and some window management bindings on the utility layer, but the mental model was still helpful for me as a starting point!

Example visual keybinding layout

When I was planning my Sway keybindings, I found it useful useful to create a visual representation of the layout that showed how all three layers fit together. The one I'm showing here from is outdated, but it gives you an idea what you can do. In mine, I put names for the main layer bindings as the middle label, the danger/shifted bindings as the upper label and the lower label as the "Utility layer" bindings.

Outdated but perhaps useful of example of using to mock up how custom Sway keybindings are arranged.

Visual tools to review keybindings

While learning new keybindings, another useful tool is a utility app that can parse your Sway config file display all the keybindings and what they do. There are a couple that can do this, both thanks to the Regolith Desktop project.

This task is complicated because a proposed change to add proper IPC support to query keybindings to Sway has not been merged. However, Regolith had a creative solution that works fairly well, which is use a structured comment format for keybindings. The tools then parse the special comment from the file instead of the bindings themselves. Here's an example comment which shows a name, description and binding. For the web I've used a Window emoji but on my desktop I can use a "Nerd Font" Windows icon to match the Windows (Super) key.

## Dmenu // Emoji Picker // 🪟 <Ctrl> E ##

Regolith is based on Ubuntu so you may have best luck finding these tools for Ubuntu Linux, but you may find them packaged elsewhere as well.

Remontoire is the older tool and is a basic keybinding viewer. It's essentially a "cheat sheet" to glance it. You can bring it up and then browse it. It's pretty, built ultimately I found it worked just as well to open my config file in a text editor instead.

CDN media
Screenshot of Remontoire - A cheatsheet utility for Sway keybindings.

Ilia has a different project scope. It is designed as a general-purpose "picker" and launcher, like Rofi or Fuzzel. It has special support for parsing the Sway "smart comment" format and in some cases can execute the bindings right from this interface.

Screenshot of Ilia - An app launcher and picker that can view and (sometimes) execute keybindings.

My experience is that this category of app is useful for a couple weeks while learning a lot of new bindings at once. If you need to look up a binding only rarely, opening the config file works well enough, or if it's used that rarely, maybe it's not worth having a binding at all.

Sway main layer bindings

Here are my "main" bindings that use the Super profix, seen here as $mod:

## Apps // Browser (Personal) // 🪟 M ##
bindsym $mod+m exec /opt/vivaldi/vivaldi --profile-directory='Profile 1'

## Apps // Browser (Work)  // 🪟 / ##
bindsym $mod+slash exec /opt/vivaldi/vivaldi --profile-directory=Default

## Dmenu // Clipboard Manager // 🪟 G ##
# Note the "-T" flag belongs to `clipman`, not dmenu!
bindsym $mod+g exec clipman pick --tool=CUSTOM -T'dmenu --prompt=✂️❯ '

# Switch to a window by letter, like Tmux "Q" or Vimium "F"
## Containers // Visually Switch by Letter // 🪟 B ##
bindsym $mod+b exec --no-startup-id wmfocus

## Dmenu // Desktop App Launcher // 🪟 D ##
bindsym $mod+d exec fuzzel

## Containers // Focus Parent Container // 🪟 A ##
bindsym $mod+a focus parent
## Containers // Focus Child Container // 🪟 C ##
bindsym $mod+c focus child

## Containers // Move Focus Direction // 🪟 HJKL ##
bindsym $mod+h focus left
bindsym $mod+j focus down
bindsym $mod+k focus up
bindsym $mod+l focus right

## Containers // Vertical Split // 🪟 V ##
bindsym $mod+v split horizontal

## Containers // Horizontal Split // 🪟 H ##
bindsym $mod+s split vertical

## Containers // Fullscreen Focused Container // 🪟 F ##
bindsym $mod+f fullscreen toggle

# Plus and Minus: change gaps interactively
## Workspaces // Decrease Gaps // 🪟 - ##
## Workspaces // Increase Gaps // 🪟 = ##
bindsym $mod+minus gaps inner current minus 6
bindsym $mod+equal gaps inner current plus 6

## Workspaces // Move to workspace // 🪟 QWERTYUIOP ##
# Positions for latters match 1 to 10
bindsym $mod+q workspace number 1
bindsym $mod+w workspace number 2
bindsym $mod+e workspace number 3
bindsym $mod+r workspace number 4
bindsym $mod+t workspace number 5
bindsym $mod+y workspace number 6
bindsym $mod+u workspace number 7
bindsym $mod+i workspace number 8
bindsym $mod+o workspace number 9
bindsym $mod+p workspace number 10

## Workspaces // Previous Window // <Alt> Tab ##
bindsym Alt+Tab exec swayr switch-workspace

## Workspaces // Previous Window Menu // 🪟 Tab ##
# It's ordered by last use, and includes icons, app Ids and bold titles.
bindsym $mod+Tab exec swayr switch-window

## Workspaces // Update current workspace Name // 🪟 N ##
bindsym $mod+n exec

# vim: filetype=swayconfig

The final binding there is for renaming the workspace. In my workflow, I will give a certain workspaces a name for the current task. I wrote it in "Fish" because I like the Fish shell syntax. Feel-free to port to Bash!

# Set the current output name based on user-prompted input
#set cur_output (swaymsg -t get_outputs | jq -r 'map(.focused) | index(true)')
#set newname (dmenu-wl -m $cur_output --bottom --height 40 --prompt-only "New name for this workspace");
set num (swaymsg -t get_workspaces | jq -r "map(select(.focused))[0].num"); 
set newname (echo '' | fuzzel --dmenu --lines=0 --anchor bottom -p 'New name for this workspace: ');
swaymsg "rename workspace to $num:$newname

Some of the Fuzzel flags I'm using are in the git version. Once 2.0 is released, they should generally work.

Danger Layer

These are my Sway keybindings that use the Super+Shift prefix. They are more destructive or dangerous or I otherwise want to make it a bit harder to trigger them be accident. To be honest, there's only so much room on the main layer.

## Containers // Kill Focused Container // 🪟  / ##
bindsym $mod+Shift+slash kill

## Containers // Move Container // 🪟  HJKL ##
bindsym $mod+Shift+h move left
bindsym $mod+Shift+j move down
bindsym $mod+Shift+k move up
bindsym $mod+Shift+l move right

## Floating // Toggle window tiling / floating // 🪟  F ##
bindsym $mod+Shift+f floating toggle

## Floating // Toggle focus tiling / floating // 🪟  , ##
bindsym $mod+Shift+comma focus mode_toggle

## Workspaces // Move Container to Workspace, Follow It // 🪟  QWERTYUIOP ##
bindsym $mod+Shift+q move container to workspace number 1; workspace  number 1
bindsym $mod+Shift+w move container to workspace number 2; workspace  number 2
bindsym $mod+Shift+e move container to workspace number 3; workspace  number 3
bindsym $mod+Shift+r move container to workspace number 4; workspace  number 4
bindsym $mod+Shift+t move container to workspace number 5; workspace  number 5
bindsym $mod+Shift+y move container to workspace number 6; workspace  number 6
bindsym $mod+Shift+u move container to workspace number 7; workspace  number 7
bindsym $mod+Shift+i move container to workspace number 8; workspace  number 8
bindsym $mod+Shift+o move container to workspace number 9; workspace  number 9
bindsym $mod+Shift+p move container to workspace number 10; workspace number 10

## Sway Session // Reload Config File // 🪟  C ##
bindsym $mod+Shift+c reload, exec systemctl --user restart kanshi

## Sway Session // Suspend // 🪟  S ##
bindsym --locked $mod+Shift+s exec sudo /sbin/systemctl suspend

## Screen // Toggle Laptop Screen On/Off // 🪟   M ##
# TODO: Adapt this to disable the current output.
# I May need a script to find the current output
bindsym $mod+Shift+m output eDP-1 toggle

# vim: filetype=swayconfig

Utility Layer

These are my Sway keybindings that use the Super+Control prefix. These are primarily little utilities I use. Many are based on the dmenu "picker" concept. On my system, dmenu launches Fuzzel in dmenu mode.

## Apps // Start a terminal // 🪟 Space ##
# I tried just $mod+space, but accidentally triggered it daily.
bindsym $mod+Ctrl+space exec wezterm

## Dmenu // Emoji Picker // 🪟 <Ctrl> E ##
bindsym $mod+Ctrl+e exec env BEMOJI_CLIP_CMD="wl-copy --trim-newline" BEMOJI_PICKER_CMD=dmenu bemoji

## Dmenu // Open Downloads // 🪟 <Ctrl> D ##
bindsym $mod+Ctrl+d exec fish -c 'cd ~/Downloads; rifle (ls -td * | head -10 | dmenu --prompt "⬇️❯ ")'
## Dmenu // Open Work Downloads // 🪟 <Ctrl> A ##
bindsym $mod+Ctrl+a exec fish -c 'cd ~/RADownloads; rifle (ls -td * | head -10 | dmenu --prompt "🤠❯ ")'

## Dmenu // Network Manager // 🪟 <Ctrl> N ##
bindsym $mod+Ctrl+n exec networkmanager_dmenu

## Dmenu // OTP Codes (Yubikey) // 🪟 <Ctrl> O ##
bindsym $mod+Ctrl+o exec yubikey-oath-dmenu --menu-cmd 'dmenu --prompt "TOTP❯ "' --clipboard-cmd wl-copy --clipboard

## Dmenu // Password Manager // 🪟 <Ctrl> P ##
bindsym $mod+Ctrl+p exec dmenu-fictional-password-manager

## Apps // Calculator // 🪟 <Ctrl> C ##
# Once speedcrunch is open, Ctrl->R to copy last Result
bindsym $mod+Ctrl+c exec speedcrunch 

# These next bindings aren't for utilities, but use the same letters as the main layer for easier mnemonic memory for related tasks. 

## Layout // Switch Layout to Vertical   // 🪟 <Ctrl> V ##
## Layout // Switch Layout to Horizontal // 🪟 <Ctrl> S ##
## Layout // Switch Layout to Tabbed/Stacking // 🪟 <Ctrl> T ##
bindsym $mod+Ctrl+s layout splitv
bindsym $mod+Ctrl+v layout splith
# I usually use tabbed, but if I press the key again, toggle to stacking
bindsym $mod+Ctrl+t layout toggle tabbed stacking

## Workspaces // Move to Left Monitor // 🪟 <Ctrl> H ##
## Workspaces // Move to Right Monitor // 🪟 <Ctrl> L ##
bindsym  $mod+Ctrl+h move workspace to output left
bindsym  $mod+Ctrl+l move workspace to output right

# vim: filetype=swayconfig

Media Keys

Media keys are different because they don't use a modifer and also because I want them to work even when the screen is locked. For example, the screen has locked but music is still playing and I need to quickly stop the audio.

## Media // Play/Pause Audio // ⏯️  ##
bindsym --locked XF86AudioPlay exec playerctl play-pause
bindsym --locked XF86AudioPause exec playerctl pause

## Media // Next Track //  ⏭️  ##
## Media // Prev Track // ⏮  ##
bindsym --locked XF86AudioNext exec playerctl next
bindsym --locked XF86AudioPrev exec playerctl previous

## Media // Volume Up // 🔊 ##
## Media // Volume Down // 🔉 ##
## Media // Volume Mute // 🔇 ##
# @DEFAULT_SINK@ is a useds by pactl to reference the default output, but it might
# not be the current output.
bindsym --locked XF86AudioRaiseVolume  exec pactl set-sink-volume @DEFAULT_SINK@ +3%
bindsym --locked XF86AudioLowerVolume  exec pactl set-sink-volume @DEFAULT_SINK@ -3%
# Mute is not currently mapped on my external keyboard. I usually use pause instead.
bindsym --locked XF86AudioMute         exec pactl set-sink-mute @DEFAULT_SINK@ toggle

## Screen // Brightness Up // 🔆 ##
## Screen // Brightness Down // 🔅 ##
bindsym XF86MonBrightnessDown exec brightnessctl set 5%-
bindsym XF86MonBrightnessUp   exec brightnessctl set +5%

Resize/Move Mode

Sway supports "modes" which you can enter with one keybinding and they have their own special keybindings until you exit the mod. This allows another conceptual "layer" of keybindings without using more and more modifiers. I use just one such mode, for resizing and windows and in the case of floating windows, moving them.

This mode originally included a number of other keybindings but I've trimmed it here to the ones I actually remember and use. I would sometimes get stuck in the mode, which is why there are many keys dedicated to leaving it!

First, to enter the mode, there's a binding on the main layer. I tried to choose a key combination that was harder to trigger:

## Resize/Move Mode // Enter Resize/Move Mode // Super . ##
bindsym $mod+period $enterResize

And here's the mode definition. Depending on your monitor size, you might scale the pixel counts.

mode "Resize / Move Mode" {

        ## Resize/Move Mode // Resize Floating Windows by 192px // hjkl ##
        ## Resize/Move Mode // Resize Tiling Windows by 5% // hjkl ##
        bindsym h resize shrink width 192 px or 5 ppt
        bindsym j resize shrink height 192 px or 5 ppt
        bindsym k resize grow height 192 px or 5 ppt
        bindsym l resize grow width 192 px or 5 ppt

        ## Resize/Move Mode // Move Floating Windows by 192px // <Up><Down><Left><Right> ##
        bindsym Up move up 192 px
        bindsym Left move left 192 px
        bindsym Down move down 192 px
        bindsym Right move right 192 px

        # back to normal: Enter, Escape, Super, Space or Super-R
        ## Resize/Move Mode // Exit Mode // <Return>, <Esc>, <Space> ##
        bindsym Return $leaveResize
        bindsym Escape $leaveResize
        bindsym $mod+r $leaveResize
        bindsym $mod   $leaveResize
        bindsym space  $leaveResize

I have no keybindings for using the "scratchpad" feature of Sway because after some experimentation I don't use the feature. If I want to hide something, I put it on a workspace I can't see. If I need to use a little utility, I open it, use it and close it.

One more tip for the markstos Corne layout

I use the markstos Corne layout, which puts both the Control and Super key in the same thumb cluster. So how can the Super+Control combination be pressed?

The markstos Corne layout

There answer is most modifiers, including Control, Are one-shot modifiers, meaning that they can be tapped instead of held. So for "Super+Control", I tap Control and the hold "Super" before pressing the next key.

Final thoughts: What should be a keybinding?

There's a limit to how many keybindings I can remember. For app launching keybindings, I limited myself to the two that are by far the most frequent for me, opening a terminal and opening a browser. For the rest of my apps, I can open a Fuzzel and quickly select them with a few characters.

Actions that I use on a daily basis are good candidates for keybindings. Some I thought would be useful are later removed. If I can't remember or or forget it exists, that's a strong sign that I'm not using it frequently enough to be worth keeping. Enjoy Sway!