Important
- Maintained for Hyprland
v0.54.3andv0.55.0.
This repository started from the original Hyprtasking project and has diverged into a narrower, maintenance-first branch.
The main differences from the upstream fork source are:
- this branch is maintained for explicit audited Hyprland targets, currently
0.54.3and0.55.0, instead of trying to span a wide range of compositor releases - Hyprland-sensitive behavior is pushed behind
src/compat/, with explicit compat ownership, runtime wrappers, and audited hook contracts - the public runtime surface is intentionally small: grid overview, mouse trigger/selection/drag/drop, gestures, directional movement, submap-backed keyboard selection, and health checks
- release validation is much stricter, with version-contract checks, compat audits, boundary audits, smoke tests, manual runtime checklists, and machine-readable outputs for tooling
- overview stability has been hardened around renderer re-entry, focus handoff, hover and keyboard selection, and runtime lifecycle edge cases
- overview mouse interaction now uses a direct
CInputManager::onMouseButtonhook on the supported runtime, which fixed the drag and right-click selection regression that appeared after the earlier refactors - bare key bindings for overview navigation now use a Hyprland submap that the plugin enters/exits automatically, which eliminates keypress interference during normal workspace use
- gated runtime tracing is built in through
plugin:hyprtasking:debug:trace, so maintainers can enable deep diagnostics without re-adding ad hoc logging - maintainer and user-facing documentation now lives in
docs/, including the debugging playbook, compat contract, troubleshooting guide, and the incident write-up for the overview input regression
If you want the original wider-compatibility project posture, read the upstream repository. If you want the branch that tracks the current maintenance work, testing, and debugging workflow described in this README, use this repository.
demo_small.mp4
- Grid overview
- Mouse controls
- Exit into workspace (hover, click)
- Drag and drop windows
- Keyboard controls
- Switch workspaces with direction
- Multi-monitor support (tested)
- Monitor scaling support (tested)
- Animation support
- Configurability
- Number of visible workspaces
- Toggle behavior
- Toggle keybind
- Touch and gesture support
yay -S hyprtasking
paru -S hyprtasking
hyprpm add https://github.com/douglas/hyprtasking
hyprpm enable hyprtasking
Add hyprtasking to your flake inputs and pin Hyprland to the supported
target you run. This example uses v0.55.0; v0.54.3 is also supported.
# flake.nix
{
inputs = {
hyprland.url = "github:hyprwm/Hyprland/v0.55.0";
hyprtasking = {
url = "github:douglas/hyprtasking";
inputs.hyprland.follows = "hyprland";
};
};
# ...
}Include the plugin in the hyprland home manager options
# home.nix
{ inputs, ... }:
{
wayland.windowManager.hyprland = {
plugins = [
inputs.hyprtasking.packages.${pkgs.system}.hyprtasking
];
}
}To build, have hyprland headers installed on the system and then:
meson setup build
cd build && meson compile
Then use hyprctl plugin load to load the absolute path to the .so file:
hyprctl plugin load "$(realpath libhyprtasking.so)"
hyprctl reload only reloads configuration and does not reload the plugin
binary. After rebuilding the plugin, unload/reload the .so explicitly.
For a quick live smoke check after the plugin is loaded:
bash scripts/smoke-live.sh all
Before updating the plugin to a new Hyprland package/runtime, run the compat audit against the
matching Hyprland source tree. The source tree is audit-only; builds must use the installed Arch
hyprland package headers from pkg-config:
bash scripts/audit-compat.sh /path/to/Hyprland
bash scripts/audit-compat-surface.sh /path/to/Hyprland
bash scripts/audit-compat-coverage.sh
bash scripts/update-supported-hyprland.sh /path/to/Hyprland
To verify the repo still keeps direct Hyprland internals behind src/compat/:
bash scripts/audit-boundary.sh
bash scripts/audit-config-keys.sh
bash scripts/audit-guidelines.sh
For tooling, the boundary audit also supports machine-readable output:
AUDIT_BOUNDARY_FORMAT=json bash scripts/audit-boundary.sh
AUDIT_CONFIG_KEYS_FORMAT=json bash scripts/audit-config-keys.sh
Supported smoke subcommands:
all: unload/load the plugin, verify dispatcher registration, run runtime safety probes, run toggle/move probes, then reload Hyprlandstress: run a heavier live cycle with repeated unload/load, toggle/move, reload, and reload-open checksdispatchers: verify thattoggle,move,select,commit, andhealthdispatchers are registered and still return the expected probe responses without changing overview statesafety: verify invalid grid dimensions, gesture distances, mouse button conflicts, and gesture finger conflicts disable runtime cleanlyload-unload: validate one or more live unload/load cycles forbuild/libhyprtasking.sotoggle: validate toggle and directional move dispatchersreload: reload Hyprland and wait for the plugin to become ready againreload-open: reload Hyprland while the overview is open, then verify the plugin still respondsmanual: print the manual compositor checks that still need eyes on the result
Optional environment:
HYPRLAND_INSTANCE_SIGNATURE: target a specific live Hyprland session. If unset, the script picks the first active instance fromhyprctl instances.PLUGIN_PATH: override the plugin path used byload-unloadLOAD_UNLOAD_CYCLES: number of unload/load cycles to run forload-unloadTOGGLE_CYCLES: number of open/move/close toggle cycles to run fortoggleorallRELOAD_CYCLES: number of reload cycles to run forreload,reload-open, orallPRINT_MANUAL_FOLLOW_UP: set to0to suppress the checklist print at the end ofallorstress
stress uses stronger defaults when the cycle variables are left at 1:
LOAD_UNLOAD_CYCLES=3TOGGLE_CYCLES=3RELOAD_CYCLES=2
The repo-specific maintenance and debugging docs live under docs/:
docs/debugging-playbook.md: step-by-step workflow for diagnosing build, compat, hook, input, render, and focus regressionsdocs/overview-input-regression.md: distilled lessons from the overview click/drag regression that required moving click handling to the directCInputManager::onMouseButtonhookdocs/architecture.md: diagrams for the current public runtime surface, reload lifecycle, and release gatesdocs/compat-contract.md: the Hyprland-facing compat ownership map and drift-prone contractsdocs/supported-hyprland.md: supported patch-version matrix and one-command update workflowdocs/troubleshooting.md: user-facing guide for diagnosing input problems including bare-key binding interference and input method conflicts on Wayland
This branch is no longer just the original feature set with small local fixes. The current maintenance work has added:
- a dedicated compatibility layer for Hyprland-facing runtime and renderer contracts
- a pruned dispatcher/config surface focused on the core workflows this branch actively verifies
- repo-local release gates and smoke scripts that distinguish version drift, hook startup failures, dispatcher registration failures, and runtime behavior regressions
- structured JSON output for release, compat, boundary, and manual-check flows
- explicit maintainer docs for debugging, incident learnings, and compat ownership
- a built-in trace path at
/tmp/hyprtasking-trace.log, gated byplugin:hyprtasking:debug:trace - the direct input-manager mouse-button hook that restored stable drag and right-click workspace selection on the supported Hyprland line
- automatic submap management for bare key bindings so overview keyboard navigation does not interfere with normal keypress delivery
flowchart LR
user[Hyprland binds and gestures]
dispatch[Hyprtasking dispatchers]
listeners[Event::bus listeners]
mouse[Mouse hook]
manager[HTManager]
view[HTView]
layout[Grid layout]
compat[src/compat]
hyprland[Hyprland runtime]
user --> dispatch
user --> listeners
hyprland --> mouse
dispatch --> manager
listeners --> manager
mouse --> manager
manager --> view
view --> layout
manager --> compat
view --> compat
layout --> compat
compat --> listeners
compat --> mouse
compat --> hyprland
The only public dispatchers are toggle, move, select, commit, and
health. Bare-key navigation uses the
hyprtasking submap with select and commit; config reload always cancels
active overview runtime state and reinitializes grid positions.
Hyprtasking is maintained for explicit audited Hyprland targets. The current
supported targets are 0.54.3 and 0.55.0.
Update workflow:
- Verify that the installed package headers and the live runtime are aligned:
bash scripts/check-version-contract.sh
- Run the supported-version update workflow against the target Hyprland source tree:
bash scripts/update-supported-hyprland.sh /path/to/Hyprland
- If any audit fails, first confirm that the target tree is one of the
supported exact versions, then update only the compat layer in
src/compat/. - Rebuild and run:
meson compile -C build
meson test -C build
bash scripts/smoke-live.sh all
- Finish with the manual compositor checks printed by
bash scripts/smoke-live.sh manual.
For the same flow through one command:
HYPRLAND_SOURCE=/path/to/Hyprland bash scripts/release-check.sh
For a single local release-prep command, run:
bash scripts/release-check.sh
release-check.sh defaults to the heavier stress smoke mode. Override with
SMOKE_MODE=all if you want the lighter path, or set PRINT_MANUAL_CHECKLIST=0
to skip printing the manual compositor checklist. The script suppresses the
intermediate smoke-script reminder so the manual checklist is printed once. It
also runs a repo-local compat boundary audit plus a load-unload cycle and
separate dispatchers probe first so architectural regressions and
hook/registration failures are easier to distinguish from later runtime smoke
failures. It always runs scripts/check-version-contract.sh first. If
HYPRLAND_SOURCE is set, it then runs scripts/audit-compat.sh and
scripts/audit-compat-surface.sh against that source tree before continuing
with the boundary audit, normal build, test, smoke, and manual-check flow.
The default build directory is build; if that directory is absent and a local
build-055 directory exists, the script uses build-055. Set BUILD_DIR to
override this explicitly.
To print only one manual scenario after the automated checks, set
MANUAL_SCENARIO:
MANUAL_SCENARIO=gesture bash scripts/release-check.sh
For tooling, request one machine-readable result for the full release gate:
RELEASE_CHECK_FORMAT=json HYPRLAND_SOURCE=/path/to/Hyprland bash scripts/release-check.sh
audit-compat.sh prints the detected Hyprland version, the accepted support
targets, and the required hook/API contracts it verified so version drift is
easier to diagnose during updates. On failure, it also prints the missing
contract list and the first place to patch. The full ownership map lives in
docs/compat-contract.md.
Hook discovery uses findFunctionsByName() and requires an exact signature
match. Ambiguous or missing hook symbols disable Hyprtasking for the session
instead of falling back to deprecated lookup paths.
audit-compat-surface.sh does the same for the runtime-sensitive wrapper
surface under src/compat/, covering focus, cursor, drag-controller,
workspace/window/monitor accessor, and render-pass contracts that previously
required only manual review after a Hyprland bump.
check-version-contract.sh is the hard gate for the supported targets. It fails if
the installed hyprland package, the running Hyprland instance, or the
optional audited source tree are not aligned on the same supported exact
version.
audit-boundary.sh also enforces that direct Hyprland focus mutation stays out
of layout/render code, so overview rendering cannot silently start reasserting
monitor focus again. It also fails if render-stage style entrypoints reappear
outside src/compat/, which helps catch render-recursion regressions before
release.
audit-config-keys.sh enforces that every config key used through HTConfig
is registered in initializeConfig(). This prevents render-path null
dereferences from missing config registrations. Runtime code reads a typed
snapshot refreshed at plugin startup, config reload, and runtime readiness
checks, so grid, mouse, gesture, and trace settings are validated before
input/render paths use them.
audit-guidelines.sh hard-gates key Hyprland plugin best practices for this
branch: no thread primitives in runtime code, and no new function-hook call
sites or function-hook removal sites outside the approved compat/runtime hook
layer.
When RELEASE_CHECK_FORMAT=json is used, release-check.sh now includes
structured audit_results for the compat, compat-surface, compat-coverage,
boundary, and config-keys audits, plus the selected manual_checklist.
Audit exit codes:
2: required Hyprland source file missing3: unsupported Hyprland target for this plugin branch4: supported target, but one or more audited contracts drifted
For tooling or CI wrappers, request machine-readable output:
AUDIT_FORMAT=json bash scripts/audit-compat.sh /path/to/Hyprland
AUDIT_FORMAT=json bash scripts/audit-compat-surface.sh /path/to/Hyprland
The manual compositor checklist is also available directly:
bash scripts/manual-runtime-check.sh
For tooling, request the same checklist in machine-readable form:
CHECKLIST_FORMAT=json bash scripts/manual-runtime-check.sh typing-focus
Supported checklist scenarios:
all: print the full manual checklistdrag: focus on overview drag/drop behaviortyping-focus: focus on immediate typing andCtrl+Lafter right-click workspace selectiongesture: focus on gesture interruption and recoveryreload-open: focus on reload while the overview is visiblemonitor-remove: focus on monitor removal while runtime state is activerender-reentry: focus on reopening the overview immediately after workspace selection and reloadhook-sunset: re-test the event-bus mouse path on every supported Hyprland target before keeping the direct mouse-button hook
When RELEASE_CHECK_FORMAT=json is used, release-check.sh now includes the
selected manual checklist as a nested manual_checklist object, so wrappers can
carry the required human verification steps alongside the automated stage
results.
- Bind
hyprtasking:toggle, allto a keybind to open/close the overlay on all monitors. - Bind
hyprtasking:toggle, cursorto a keybind to open the overlay on one monitor and close on all monitors. - Swipe up/down on a touchpad device to open/close the overlay on one monitor.
- See below for configuration options.
- Workspace Transitioning:
- Open the overlay, then use right click to switch to a workspace
- Use arrow keys to navigate between workspaces and Enter to commit (requires submap config, see Configuration)
- Use the directional dispatchers
hyprtasking:moveto switch to a workspace
- Window management:
- Left click to drag and drop windows around
Example below:
bind = SUPER, tab, hyprtasking:toggle, cursor
bind = SUPER, space, hyprtasking:toggle, all
bind = SUPER, H, hyprtasking:move, left
bind = SUPER, J, hyprtasking:move, down
bind = SUPER, K, hyprtasking:move, up
bind = SUPER, L, hyprtasking:move, right
# Bare key bindings (no modifier) must go inside a Hyprland submap so they
# only intercept keypresses while the overview is open. The plugin enters
# the "hyprtasking" submap automatically when the overview opens and exits
# it when the overview closes. Without a submap, bare key bindings cause
# Hyprland to intercept every keypress globally — even when the overview is
# closed — which drops the next keypress in the application.
submap = hyprtasking
bind = , LEFT, hyprtasking:select, left
bind = , RIGHT, hyprtasking:select, right
bind = , UP, hyprtasking:select, up
bind = , DOWN, hyprtasking:select, down
bind = , RETURN, hyprtasking:commit,
bind = , ESCAPE, hyprtasking:toggle, cursor
submap = reset
# Important: define these binds in sourced config files (`source = ...`) or load
# them once (`exec-once = hyprctl keyword source ...`). Re-sourcing the same
# submap block repeatedly at runtime stacks duplicate binds, which can make one
# arrow keypress navigate multiple workspaces.
plugin {
hyprtasking {
drag_button = 0x110 # left mouse button
select_button = 0x111 # right mouse button
# for other mouse buttons see <linux/input-event-codes.h>
gestures {
enabled = true
move_fingers = 3
move_distance = 300
open_fingers = 4
open_distance = 300
open_positive = true
}
grid {
rows = 3
cols = 3
loop = false
}
}
}
Selection + Enter recipe:
bind = SUPER, tab, hyprtasking:toggle, cursor
submap = hyprtasking
bind = , LEFT, hyprtasking:select, left
bind = , RIGHT, hyprtasking:select, right
bind = , UP, hyprtasking:select, up
bind = , DOWN, hyprtasking:select, down
bind = , RETURN, hyprtasking:commit,
bind = , ESCAPE, hyprtasking:toggle, cursor
submap = resetQuick mismatch check:
hyprctl plugin list
hyprctl keyword source ~/.config/hypr/personal-hyprtasking.conf
hyprctl dispatch hyprtasking:healthIf keyword source prints invalid dispatcher and your config still uses
hyprtasking:navigate or hyprtasking:commit_selection, switch those binds to
hyprtasking:select and hyprtasking:commit.
Configs that depended on conditional wrapper dispatchers should instead use
the hyprtasking submap shown above so bare keys only exist while the overview
is open.
-
hyprtasking:toggle, ARGtakes in 1 argument that is eithercursororall- if the argument is
all, then- if all overviews are hidden, then all overviews will be shown
- otherwise all overviews will be hidden
- if the argument is
cursor, then- if current monitor's overview is hidden, then it will be shown
- otherwise all overviews will be hidden
- if the argument is
-
hyprtasking:move, ARGtakes in 1 argument that is one ofup,down,left,right- when dispatched, hyprtasking will switch workspaces with a nice animation
-
hyprtasking:select, ARGtakes in 1 argument: one ofup,down,left,right- directional selection updates the keyboard highlight; use
hyprtasking:committo switch - intended for use inside the
hyprtaskingsubmap so that bare arrow keys work only while the overview is open
- directional selection updates the keyboard highlight; use
-
hyprtasking:commitswitches to the currently selected workspace and closes the overview- intended for use inside the
hyprtaskingsubmap so that bare Enter works only while the overview is open
- intended for use inside the
-
hyprtasking:healthprints runtime status and the disable reason (if runtime was disabled) in both logs and notification output. Passjsonfor machine-readable hook and Event::bus listener diagnostics.
Reload behavior is not configurable: hyprctl reload cancels active overview
runtime state and reinitializes grid positions so the compositor never keeps a
half-open overview across a config reload.
Invalid safety-sensitive config disables Hyprtasking for the current session and
passes input/rendering back to Hyprland. This includes non-positive grid
dimensions, duplicate drag/select mouse buttons, duplicate gesture finger
counts, and non-positive gesture distances. Run
hyprctl dispatch hyprtasking:health json to inspect the failure source and
reason.
open_fingers and move_fingers should use different values to avoid one
gesture driving both actions.
- 3-finger swipe up/down toggles overview, 4-finger swipe moves workspaces:
gestures:open_fingers = 3gestures:move_fingers = 4
- 4-finger swipe up/down toggles overview, 3-finger swipe moves workspaces:
gestures:open_fingers = 4gestures:move_fingers = 3
Use gestures:open_positive to pick which swipe direction opens the overview:
1 means positive swipe opens and negative closes, 0 reverses this.
All options are prefixed with plugin:hyprtasking:.
Visual styling uses fixed built-in defaults.
| Option | Type | Description | Default |
|---|---|---|---|
debug:trace |
Hyprlang::INT |
Enables runtime trace output to /tmp/hyprtasking-trace.log for debugging. Use a config entry for persistent tracing. Runtime toggles with hyprctl keyword plugin:hyprtasking:debug:trace 1 are applied after a config reload or plugin reload. |
0 |
drag_button |
Hyprlang::INT |
The mouse button to use to drag windows around. Must differ from select_button; conflicts disable runtime for safety. |
0x110 |
select_button |
Hyprlang::INT |
The mouse button to use to select a workspace. Must differ from drag_button; conflicts disable runtime for safety. |
0x111 |
gestures:enabled |
Hyprlang::INT |
Whether or not to enable gestures | true |
gestures:move_fingers |
Hyprlang::INT |
The number of fingers to use for the "move" gesture. Keep different from open_fingers to avoid gesture overlap. |
3 |
gestures:move_distance |
Hyprlang::FLOAT |
How large of a swipe on the touchpad corresponds to the width of a workspace. Must be > 0; invalid values disable runtime for safety. |
300.f |
gestures:open_fingers |
Hyprlang::INT |
The number of fingers to use for the open/close gesture. Keep different from move_fingers to avoid gesture overlap. |
4 |
gestures:open_distance |
Hyprlang::FLOAT |
How large of a swipe on the touchpad is needed for the "open" gesture. Must be > 0; invalid values disable runtime for safety. |
300.f |
gestures:open_positive |
Hyprlang::INT |
true if swiping up should open the overlay, false otherwise |
true |
grid:rows |
Hyprlang::INT |
The number of rows to display on the grid overlay. Must be >= 1; invalid values disable runtime for safety. |
3 |
grid:cols |
Hyprlang::INT |
The number of columns to display on the grid overlay. Must be >= 1; invalid values disable runtime for safety. |
3 |
grid:loop |
Hyprlang::INT |
When enabled, moving right at the far right of the grid will wrap around to the leftmost workspace, etc. | false |