Skip to content
sandwichfarmPublic

About

A workspace navigation plugin that replicates Apple iTune's "Cover Flow" animation.

Topics

Resources

Stars

1 star

Watchers

0 watching

Forks

Repository files navigation

Hyprflow

Classic Cover Flow for Hyprland workspaces.

hyprflow-website-readme-video.mp4

Watch the website demo · Original navigation recording · Rapid reversal recording · Square calibration

Hyprflow turns workspace snapshots into a perspective stack with a front-facing selection and fading floor reflections. The current desktop contracts into its card; accepting a workspace expands that card back into the desktop.

The reconstruction follows original iTunes 7 and 7.1 captures. Geometry, reconstructed timing, and the workspace-specific transitions are documented in the animation specification.

Install with hyprpm

Stable targets are Hyprland 0.56.x and 0.57.0 with the OpenGL renderer. A separate experimental Vulkan build and qualification uses an isolated, patched upstream WIP runtime; releases 0.56.x and 0.57.0 have no Vulkan renderer. Install using Hyprland's plugin manager from a terminal in your Hyprland session:

hyprpm update
hyprpm add https://github.com/sandwichfarm/hyprflow
hyprpm enable hyprflow
hyprpm reload

Hyprpm downloads the source, builds against its matching Hyprland headers, and manages the installed library. Run it as your normal user. A C++23 compiler, Make, and the development dependencies are still required; you do not need a manual clone or a prebuilt binary.

Next, add the quick-start bindings to your Hyprland Lua configuration. Super + Tab opens the flow; Left / Right browse, Return activates, and Escape cancels. The guide also covers startup loading, updating, and removing the plugin.

Build from source (development)

Build against the headers matching the compositor that will load the plugin. Hyprland plugins do not have a stable binary ABI. Hyprflow checks the full API hash at load time and rejects a mismatch.

pkg-config --modversion hyprland
make -j2
make test

The artifact is build/hyprflow.so. The build uses C++23, the installed Hyprland development headers and their dependencies, Lua 5.4, EGL, GLES, and Pango/Cairo. No libraries are downloaded or vendored. One source tree builds against Hyprland 0.56.0–0.56.2 and 0.57.0 with the OpenGL renderer; it selects the matching compositor API from the headers it is compiled against. Experimental Vulkan support requires the exact runtime and separate build described in the qualification guide. Other compositor ABIs and unpatched Vulkan runtimes are not qualified.

Try it in a nested session

Run from an existing Wayland session. The helper creates an isolated compositor and configuration under /tmp/hf-*. This command captures its printed session directory in session_dir:

session_dir=$(python3 scripts/nested-session.py start | python3 -c 'import json,sys; print(json.load(sys.stdin)["directory"])')
python3 scripts/nested-session.py place "$session_dir"
python3 scripts/nested-session.py fixtures "$session_dir"
python3 scripts/nested-session.py ctl "$session_dir" plugin load "$PWD/build/hyprflow.so"

start opens the owned window silently on host workspace 16, without taking focus. place sizes that window and keeps it unpinned on workspace 16. Switch there manually for interactive testing; hidden Wayland windows can pause frame callbacks. --headless start creates a virtual output for background tests when the selected GPU/backend supports headless rendering. Every plugin operation targets the nested instance explicitly. The daily compositor’s plugin list and configuration remain unchanged.

In the nested window, press F10, then Left/Right or 1–7. Press Return to activate the selection; Escape returns to the original workspace.

python3 scripts/nested-session.py ctl "$session_dir" repl 'return hl.plugin.hyprflow.status()'
python3 scripts/nested-session.py capture "$session_dir" /tmp/hyprflow.png
python3 scripts/nested-session.py stop "$session_dir"

Stop restores the owned window’s geometry while keeping it on workspace 16, then exits only the recorded compositor process. Do not rebuild a library while that file is loaded: unload it first, build, then load the new artifact.

Configure

config/hyprflow.lua supplies the default Lua bindings and the modal hyprflow submap. Load the built plugin, then source this file in the intended Hyprland session. The defaults are:

Binding Action
Super + Tab Open; toggle reverses entry or closing
Super + Alt + Left / Right Open and move selection
Super + Alt + 1–9 Open and jump to a workspace
Left / Right while open Move selection
1–9 while open Animate to that workspace
Return Expand and activate selection
Escape Return to original workspace

Change the bindings freely. The independently assignable Lua actions are hl.plugin.hyprflow.toggle(), left(), right(), jump(id_or_name), accept(), and cancel(). The corresponding dispatchers are hyprflow:toggle, hyprflow:left, hyprflow:right, hyprflow:jump, hyprflow:accept, and hyprflow:cancel. With Hyprland’s Lua IPC, invoke an action using hyprctl eval, or retrieve status() using hyprctl repl.

plugin.hyprflow.workspace_count defaults to 9, with an allowed range of 1–32. It adds numeric cards without creating real workspaces until you accept one. Existing workspaces on the focused monitor are included, including named workspaces. Workspaces on other monitors and special workspaces are excluded from navigation. The active special overlay is included in the original card.

plugin.hyprflow.select_on_release defaults to false. Set it to true to activate the selected workspace when you release all modifiers held when the flow opened. Releasing Tab or an arrow alone keeps it open. The existing Lua press bindings work without release bindings; see the cycling example and details.

plugin.hyprflow.include_empty_workspaces defaults to true. Set it to false to omit empty workspaces and numeric placeholders. The workspace list updates when coverflow opens; if no eligible workspace has windows, it stays closed.

Size, spacing, and borders update while the flow is open. Cards follow the workspace aspect ratio. The appearance controls are:

Option Default Meaning
workspace_scale 1.0 Size multiplier, 0.1–2.0; card width is min(0.58 × height, 0.38 × width) × scale; height follows workspace aspect
workspace_spread 0.18 Inactive-card spacing in card widths, 0–1; preserves the nearest cover’s position
workspace_overlay 0 Set to 1 to show the current workspace behind the switcher
background_color rgb(000000) Stage color or desktop tint, including color alpha
background_opacity 1.0 Tint opacity, 0–1; use 0 for an unobscured desktop
background_blur 0.0 Desktop backdrop Gaussian radius, 0–64 logical pixels
reflection_opacity 0.34 Reflection strength, 0–1; zero disables reflections
center_y 0.4 Vertical card center as a fraction of monitor height, 0–1
show_labels 1 Set to 0 to hide workspace captions
border_width 0 Inset border width in logical pixels, 0–32; zero disables borders
border_color rgba(ffffffff) Default solid color or multicolor gradient
border_color_current empty Override for the workspace active when the flow opened
border_color_focus empty Override for the selected destination; takes precedence over current

Like Hyprexpo’s modern border settings, one color selects a solid border and multiple colors select a gradient. There is no ignored legacy border_style switch. See configuration and examples for syntax, inheritance, validation, and test commands.

By default, selection is separate from activation. A jump travels through intermediate cards and leaves the flow open until accepted or canceled. With select_on_release enabled, releasing the opening modifiers accepts it too. Invalid targets return an error. Navigation clamps at the ends. The previous submap and client focus are restored on exit. Keep the supplied submap’s catchall binding to consume unrelated keys, including keys that could otherwise reach an input method.

Design and verification

Cards use the complete workspace aspect ratio without stretching, cropping, or matte padding. Overlay mode keeps the original workspace visible and refreshed behind the cards; blur affects only that backdrop. Snapshots are taken on entry and refreshed for the card nearest the center. Far cards are discarded outside the visible stack to bound texture memory.

The animation uses a continuous focus position, true projective texture mapping, and a frame-rate-independent critically damped spring. Rapid reversal preserves velocity. Native workspace activation happens at the end of expansion, with its ordinary slide animation suppressed.

make check
make format-check
python3 scripts/capture-proof.py "$session_dir" artifacts/proof --video
python3 scripts/verify-input.py "$session_dir" artifacts/input-proof
python3 scripts/capture-stress.py "$session_dir" artifacts/stress --sha256 "$(sha256sum build/hyprflow.so | cut -d' ' -f1)"
python3 scripts/verify-lifecycle.py "$session_dir" artifacts/lifecycle --sha256 "$(sha256sum build/hyprflow.so | cut -d' ' -f1)"

To verify workspace filtering, load the build in a fresh nested session without fixtures, then run python3 scripts/verify-workspaces.py "$session_dir". The proof creates and closes its own windows and restores the nested configuration afterward.

The proof harness uses real Wayland virtual-keyboard events, compositor state, screenshots, and an optional wf-recorder recording. It needs the installed kitty, grim, and wf-recorder tools. Reference screenshots are comparison material, never runtime assets. Automated proofs temporarily disable only the nested compositor’s parent keyboard, preventing held host modifiers from contaminating virtual key events; they restore that device and the exact nested configuration afterward.

The initial release artifact passed 11 bound-key actions, 15 stress actions, input isolation, and 20 lifecycle checks, including ten unload/reload cycles. Both accepted and canceled endpoint screenshots match the corresponding native workspace pixel for pixel. See the verification report for exact artifact identity, receipts, and limits.

Resource Purpose
Animation specification Measured geometry, motion, transition contract
Reference provenance Historical captures and source hashes
Acceptance gates Required behavior and evidence
Verification report Build, runtime, screenshot, and lifecycle evidence
Default configuration Assignable bindings and options
Appearance settings Workspace proportions, overlay, opacity, blur, reflections, labels, and borders

Exact Apple easing constants and pixel equivalence to every iTunes release are unknown. This implementation reconstructs the documented appearance and motion; the desktop entry and exit are Hyprflow-specific adaptations.

Website and documentation

The static landing page and VitePress documentation share a copper and mint palette. The combined build serves the docs at /docs/.

npm ci
npm run dev       # http://127.0.0.1:5173 and /docs/; hot reload for both
npm run build     # combined output in dist/
npm run preview   # http://127.0.0.1:4173 and /docs/

Requires Node.js 22.12+ (CI uses Node 24). Run npm test for deployment-script tests. GitHub Actions builds PRs and deploys main to Bunny after configuration:

npm run setup:bunny -- --repo sandwichfarm/hyprflow

See website development and deployment for Bunny provisioning, GitHub variables and secrets, dry runs, and rollback instructions.

About

A workspace navigation plugin that replicates Apple iTune's "Cover Flow" animation.

Topics

Resources

Stars

1 star

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages