← Writings

Rebuilding My AwesomeWM Workflow in Hyprland

How I rebuilt my AwesomeWM workflow in Hyprland, preserving per-monitor numbered workspaces, familiar keybindings, a minimal master layout, and a compact Waybar setup with a little shell glue.

I recently moved my desktop setup from AwesomeWM to Hyprland. I wasn’t looking for a fresh start in the usual sense. I wanted the newer Wayland compositor, but I also wanted to keep the muscle memory and window-management model that had made AwesomeWM work for me for the last 15-ish years.

That meant the migration was less about making Hyprland look impressive and more about preserving a specific workflow:

  • Numbered workspaces that behave like Awesome tags.
  • A compact top bar with local workspace numbers, window title, tray, volume, battery, and clock.
  • Laptop and external monitor workspaces that feel separate, even when displays are hotplugged.

TL;DR: find my dotfiles here

The Workflow I Wanted to Keep

I’m getting old. My muscle memory of SUPER+1..9 for navigating awesome tags is ingrained and I ain’t got the time nor the patience to unlearn it. Having the same numbered tags on an external display is sooo nice. I can have 18 workspaces and easily jump between them.

  • On the laptop display, SUPER+1 means workspace 1.
  • On the external display, SUPER+1 means workspace 1 of the external display.
  • Moving between workspaces does not require remembering a global list of monitor assignments.

Hyprland workspaces are global, so this does not map one-to-one. You can assign workspaces to monitors, but there is still one global workspace namespace. My main goal was to hide that global namespace from my day-to-day workflow.

The Workspace Mapping

Here’s where I landed:

  • Laptop workspaces use 1-9.
  • External monitor workspaces use 10-18.
  • Waybar displays both sets as 1-9.

So internally, Hyprland sees a global workspace list. Externally, I get the per-monitor tag feeling I wanted.

The keybindings call a small shell script instead of dispatching directly to Hyprland:

bind = $mod, 1, exec, ~/.config/hypr/scripts/awesome-workspaces switch 1
bind = $mod, 2, exec, ~/.config/hypr/scripts/awesome-workspaces switch 2
bind = $mod, 3, exec, ~/.config/hypr/scripts/awesome-workspaces switch 3

The script checks the focused monitor. If the focused monitor is the laptop, key 1 maps to workspace 1. If the focused monitor is external, key 1 maps to workspace 10.

workspace_for_key() {
    key="$1"
    monitor="$(focused_monitor)"

    if [ "$monitor" = "$laptop" ]; then
        echo "$key"
    else
        echo $((key + 9))
    fi
}

This keeps the binding layer boring. I still press the same keys, and the script handles the monitor-specific translation.

Handling Monitor Changes

This was actually my main gripe with awesome that hadn’t gotten around to fixing. By default, disconnecting the external display dumped any open windows on any tags of the external display to tag 1 of the main display. No fun.

When an external monitor is connected, I want workspaces 10-18 assigned to it. When that monitor disappears, that needs to be handled gracefully.

reconcile() {
    external="$(external_monitor)"

    i=1
    while [ "$i" -le 9 ]; do
        hyprctl keyword workspace "$i, monitor:$laptop, persistent:true" >/dev/null
        i=$((i + 1))
    done

    i=10
    while [ "$i" -le 18 ]; do
        if [ -n "$external" ]; then
            hyprctl keyword workspace "$i, monitor:$external, persistent:true" >/dev/null
            hyprctl dispatch moveworkspacetomonitor "$i $external" >/dev/null 2>&1 || true
        else
            move_workspace_windows "$i" "$((i - 9))"
            hyprctl keyword workspace "$i, persistent:false" >/dev/null
        fi
        i=$((i + 1))
    done
}

The watcher listens to Hyprland’s event socket with socat when available. If that is not available, it falls back to polling.

exec-once = ~/.config/hypr/scripts/awesome-workspaces watch

Making Waybar Match the Illusion

The other half of the trick is Waybar. If Hyprland uses workspaces 10-18 for the external monitor, I do not want the bar to show those numbers literally. I want the external monitor to have its own local-looking 1-9 set.

Waybar’s workspace icons make that easy:

"format-icons": {
  "1": "1",
  "2": "2",
  "3": "3",
  "10": "1",
  "11": "2",
  "12": "3"
}

The full config maps all of 10-18 back to 1-9. It also uses "all-outputs": false so each bar only shows the workspaces relevant to its monitor.

There is one extra bit of CSS to prevent stale hotplug assignments from showing both workspace sets on one bar:

#workspaces button:not(.hosting-monitor) {
  color: transparent;
  background: transparent;
  border: 0;
  font-size: 0;
  min-width: 0;
  min-height: 0;
  padding: 0;
  margin: 0;
  opacity: 0;
}

The result is a compact bar that behaves more like an Awesome wibar than a generic Hyprland workspace list.

What Hyprland Made Easy

Some pieces translated cleanly:

  • Keybindings are straightforward to express.
  • Auto reload on config save
  • Waybar is soooo much easier than my custom Awesomewm widgets
  • I have my external display centered above my laptop display. This worked out of the box vs the xrandr based bash script I needed for Awesomewm.

The End

So, I realize that I’m not really using hyprland the way it was designed to be used. I’m okay with that. Like I said, I’m an old dog and I don’t have time to learn new tricks.