Project: Complete Workspace Switcher
Problem
Build a production-quality workspace switcher widget. It must show all workspaces, highlight the active one, display window icons, support click-to-switch, and integrate with multiple monitors. It should feel responsive and modern.
Naive Approach
A simple Repeater with colored rectangles. No window previews. No per-monitor awareness. No animations. This barely qualifies as a switcher.
MentalModel
A workspace switcher visualizes three layers: workspaces (numbered containers), windows (icons inside each workspace), and focus (the active indicator). Each layer is a model driving a delegate. Composition of models creates the full picture.
Idea
Combine Hyprland.workspaces with Hyprland.clientList (from the Hyprland singleton, a model of all windows). Group clients by workspace ID. Display workspaces as horizontal items, each containing its windows as small icons. Animate transitions.
Build It
import Quickshell
import Quickshell.Hyprland
ListView {
orientation: ListView.Horizontal
model: Hyprland.workspaces
spacing: 4
delegate: Item {
width: 60
height: 40
required property var modelData
Rectangle {
anchors.fill: parent
radius: 6
color: modelData.isActive ? "#5294e2" : "#2a2a2a"
opacity: modelData.windows > 0 ? 1.0 : 0.5
Behavior on color {
ColorAnimation { duration: 150 }
}
Column {
anchors.centerIn: parent
spacing: 2
Text {
anchors.horizontalCenter: parent.horizontalCenter
text: modelData.id
color: "#ffffff"
font.bold: true
}
Row {
anchors.horizontalCenter: parent.horizontalCenter
spacing: 2
Repeater {
model: windowIconsFor(modelData.id)
delegate: Rectangle {
width: 8
height: 8
radius: 2
color: "#88ffffff"
}
}
}
}
}
MouseArea {
anchors.fill: parent
onClicked: Hyprland.dispatch("workspace", modelData.id)
}
function windowIconsFor(workspaceId: int): var {
var icons = []
for (var i = 0; i < Hyprland.clientList.length; i++) {
if (Hyprland.clientList.get(i).workspace.id === workspaceId) {
icons.push(Hyprland.clientList.get(i))
}
}
return icons
}
}
}BuildIt Breakdown
The ListView scrolls horizontally through workspaces. Each delegate shows the workspace number and dot indicators for windows. The windowIconsFor function filters clientList by workspace. Clicking dispatches workspace to switch. The ColorAnimation on Behavior gives smooth transitions when the active workspace changes.
Improve It
Use Hyprland.clientModel (a proper QML model) instead of iterating clientList. Show actual window icons from .desktop files. Add drag-and-drop reordering. Show workspace names instead of numbers. Add a popup preview on hover showing client titles.
CommonMistake
Calling Hyprland.clientList in a tight loop inside a delegate. clientList is a JS array — accessing it repeatedly in a delegate property binding triggers re-evaluation on every change. Use a QML model or cache the result instead.
Under the Hood
Hyprland.clientList is rebuilt from scratch each time a window opens, closes, or moves. It is based on hyprctl clients -j output. For production shells, consider maintaining your own incremental model using socketEvent (openwindow / closewindow) to avoid full-list rebuilds.
Exercises
Add the workspace switcher to your top bar with basic active highlighting.
Implement per-monitor workspace lists that only show workspaces on the current monitor.
Build a workspace overview (like GNOME Activities) that shows tiled window previews when you hover over a workspace indicator.