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.
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 reloadHyprpm 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 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 testThe 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.
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.
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.
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.
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/hyprflowSee website development and deployment for Bunny provisioning, GitHub variables and secrets, dry runs, and rollback instructions.