Switching to Sway on Wayland: A Custom Window Focus Solution
After a long period of procrastination, I decided to start using Wayland instead of X11.
While Sway promises i3 compatibility, it is not 100% compatible, and some tools I used with i3 are not working. One such tool is wmfocus, which allows rapid focusing on a specific window. It displays a letter for each window in the workspace, so you can easily switch to it by pressing the corresponding letter. This is especially useful when you have many windows open, particularly on multiple screens.
Screenshoot:
Unfortunately, wmfocus does not work with Wayland windows, and there is an open issue about it (GitHub issue). While waiting for the issue to be resolved, I looked for alternatives and ways to implement similar functionality.
I gathered some ideas from Reddit discussions and put together a solution that works for me.
The idea is to assign a mark to each window and switch to that window using its assigned letter. This requires the title bar to be visible for all windows. It’s not as elegant a solution as wmfocus, but it gets the job done.
1. In your Sway config file, configure the title bar to be visible for all windows:
# enable title bar
default_border normal
2. Create mark.sh
script:
#!/bin/bash
if ! command -v jq &> /dev/null; then
echo "The 'jq' command is required but not installed. Please visit https://jqlang.github.io/jq/download/ to download and install it."
exit 1
fi
# Get a list of existing marks
existing_marks=$(swaymsg -t get_tree | jq -r '.. | select(.type? == "con" and .marks != null) | .marks[]')
# Define the list of possible alphanumeric marks (5-9, a-z, A-Z)
possible_marks=({5..9} {a..z} {A..Z})
current_mark=$(swaymsg -t get_tree | jq -r '.. | select(.focused? == true) | .marks[]?')
# skip if window already have a mark
if ! [ $current_mark ]; then
# Find the first unused mark
for mark in "${possible_marks[@]}"; do
if ! grep -qw "$mark" <<<"$existing_marks"; then
# Assign the unused mark to the currently focused window
swaymsg mark "$mark"
exit 0
fi
done
# If all marks are used, print a message (optional)
# echo "No available marks left"
exit 1
fi
Make the script executable:
chmod 755 mark.sh
Now you can test the script. Open a terminal window and execute mark.sh
in it. A new mark should be added and visible in the title bar.
3. Update the Sway config file to run the script for every window and add a mode for choosing a mark
# run ~/mark.sh script for every window
for_window [all] exec --no-startup-id PATH_WITH_MARK_SCRIPT/mark.sh
# add mode for choosing a mark
set $goto_mark "Go to mark?"
bindsym $mod+i mode $goto_mark
mode $goto_mark {
# Numeric marks 0-9
bindsym 1 [con_mark="1"] focus, mode "default"
bindsym 2 [con_mark="2"] focus, mode "default"
bindsym 3 [con_mark="3"] focus, mode "default"
bindsym 4 [con_mark="4"] focus, mode "default"
bindsym 5 [con_mark="5"] focus, mode "default"
bindsym 6 [con_mark="6"] focus, mode "default"
bindsym 7 [con_mark="7"] focus, mode "default"
bindsym 8 [con_mark="8"] focus, mode "default"
bindsym 9 [con_mark="9"] focus, mode "default"
bindsym 0 [con_mark="0"] focus, mode "default"
# Lowercase marks a-z
bindsym a [con_mark="a"] focus, mode "default"
bindsym b [con_mark="b"] focus, mode "default"
bindsym c [con_mark="c"] focus, mode "default"
bindsym d [con_mark="d"] focus, mode "default"
bindsym e [con_mark="e"] focus, mode "default"
bindsym f [con_mark="f"] focus, mode "default"
bindsym g [con_mark="g"] focus, mode "default"
bindsym h [con_mark="h"] focus, mode "default"
bindsym i [con_mark="i"] focus, mode "default"
bindsym j [con_mark="j"] focus, mode "default"
bindsym k [con_mark="k"] focus, mode "default"
bindsym l [con_mark="l"] focus, mode "default"
bindsym m [con_mark="m"] focus, mode "default"
bindsym n [con_mark="n"] focus, mode "default"
bindsym o [con_mark="o"] focus, mode "default"
bindsym p [con_mark="p"] focus, mode "default"
bindsym q [con_mark="q"] focus, mode "default"
bindsym r [con_mark="r"] focus, mode "default"
bindsym s [con_mark="s"] focus, mode "default"
bindsym t [con_mark="t"] focus, mode "default"
bindsym u [con_mark="u"] focus, mode "default"
bindsym v [con_mark="v"] focus, mode "default"
bindsym w [con_mark="w"] focus, mode "default"
bindsym x [con_mark="x"] focus, mode "default"
bindsym y [con_mark="y"] focus, mode "default"
bindsym z [con_mark="z"] focus, mode "default"
# Uppercase marks A-Z
bindsym Shift+A [con_mark="A"] focus, mode "default"
bindsym Shift+B [con_mark="B"] focus, mode "default"
bindsym Shift+C [con_mark="C"] focus, mode "default"
bindsym Shift+D [con_mark="D"] focus, mode "default"
bindsym Shift+E [con_mark="E"] focus, mode "default"
bindsym Shift+F [con_mark="F"] focus, mode "default"
bindsym Shift+G [con_mark="G"] focus, mode "default"
bindsym Shift+H [con_mark="H"] focus, mode "default"
bindsym Shift+I [con_mark="I"] focus, mode "default"
bindsym Shift+J [con_mark="J"] focus, mode "default"
bindsym Shift+K [con_mark="K"] focus, mode "default"
bindsym Shift+L [con_mark="L"] focus, mode "default"
bindsym Shift+M [con_mark="M"] focus, mode "default"
bindsym Shift+N [con_mark="N"] focus, mode "default"
bindsym Shift+O [con_mark="O"] focus, mode "default"
bindsym Shift+P [con_mark="P"] focus, mode "default"
bindsym Shift+Q [con_mark="Q"] focus, mode "default"
bindsym Shift+R [con_mark="R"] focus, mode "default"
bindsym Shift+S [con_mark="S"] focus, mode "default"
bindsym Shift+T [con_mark="T"] focus, mode "default"
bindsym Shift+U [con_mark="U"] focus, mode "default"
bindsym Shift+V [con_mark="V"] focus, mode "default"
bindsym Shift+W [con_mark="W"] focus, mode "default"
bindsym Shift+X [con_mark="X"] focus, mode "default"
bindsym Shift+Y [con_mark="Y"] focus, mode "default"
bindsym Shift+Z [con_mark="Z"] focus, mode "default"
# Return to default mode
bindsym Return mode "default"
bindsym Escape mode "default"
}
Replace PATH_WITH_MARK_SCRIPT
with the path where you saved the mark.sh
script.
4. Reload the Sway config and test the script by pressing mod+i
, then a letter to switch to the window with that mark.
The core of this solution is to use a custom script, mark.sh
, to assign a mark to each window as it opens. The mark.sh
script uses the jq utility to parse the JSON output of the swaymsg -t get_tree
command.
The goal is to switch to a window with the minimum number of key presses. Although i3-input
would be ideal for this, it unfortunately lacks a replacement in Sway (see GitHub issue). I tried Zenity, Rofi, and other dialog/menu utilities, but all of them require pressing Enter to complete the selection, which doesn’t match the ergonomics I wanted.
Later, I found that I could use binding modes to achieve this.