Compare commits

..

3 Commits

Author SHA1 Message Date
37ddf4342a fix(desktop): use windowrule v3 syntax for the satty float rule (task 0028) 2026-07-22 22:22:20 -04:00
708a3ee963 feat(desktop): float the satty annotator window (task 0028) 2026-07-22 22:09:56 -04:00
9963a0dbe4 feat(desktop): add screenshot capture (task 0028)
Add a screenshot module wiring grimblast (grim + slurp) through the satty
annotation editor, enabled by the desktop aggregator. Region, active-window,
and full-screen captures each open in satty and, on confirm, land in both the
clipboard and a dated file under ~/Pictures/Screenshots.

Bound to Print / Shift+Print / Ctrl+Print rather than the spec's Super+L
family, whose keys task 0021 already holds for hjkl focus and window movement.
2026-07-22 22:04:38 -04:00
4 changed files with 84 additions and 4 deletions

View File

@@ -13,7 +13,23 @@ Bind region on `Super+L`, active window on `Super+Shift+L`, and full screen on `
## Acceptance criteria
- [ ] A screenshot module exists in the desktop group and is enabled by the aggregator.
- [ ] Region, active-window, and full-screen captures work, each opening in satty and exporting to both clipboard and file.
- [ ] Captures are bound on `Super+L`, `Super+Shift+L`, and `Super+Ctrl+L`.
- [ ] neogaia builds green under `nix flake check`.
- [x] A screenshot module exists in the desktop group and is enabled by the aggregator.
- [x] Region, active-window, and full-screen captures work, each opening in satty and exporting to both clipboard and file.
- [-] Captures are bound on `Super+L`, `Super+Shift+L`, and `Super+Ctrl+L`.
- [x] neogaia builds green under `nix flake check`.
## Implementation Notes
- **Screenshot binds moved off `Super+L` to the Print key family.**
Task 0021 already binds `Super+L`, `Super+Shift+L`, and `Super+Alt+L` to the `hjkl` focus, window-move, and resize actions for the right direction, so the spec's literal `Super+L` / `Super+Shift+L` / `Super+Ctrl+L` screenshot binds are a direct three-way collision with core navigation.
Two `bind=` lines for one combo don't coexist in Hyprland (one silently shadows the other, and the winner across modules isn't even deterministic), so the collision had to be broken.
With the operator's confirmation, the region/window/full captures are bound to `Print` / `Shift+Print` / `Ctrl+Print`, preserving the plain/Shift/Ctrl modifier pattern while leaving the `hjkl` scheme intact.
The spec's own keybind table is internally inconsistent here (it lists `Super+L` for both movement and screenshots), so this resolves a contradiction in the source rather than departing from a settled design.
- **Capture pipeline.**
`grimblast save <area|active|screen> -` captures to stdout and pipes into satty, whose copy action is configured with `--copy-command wl-copy --save-after-copy`, so one confirmation lands the shot in both the clipboard and a dated file under `~/Pictures/Screenshots`.
`--actions-on-enter save-to-clipboard` makes Enter trigger that path and `--early-exit` closes satty afterwards.
The full end-to-end capture is the irreducible manual step the spec calls out (exercised in a live session); the module builds green and the pipeline and flags are verified against satty 0.21.1.
- **No waybar icon for satty.**
The repo convention adds a `window-rewrite` mapping for each graphical application, but satty is a transient floating annotation window rather than a window that lives on a workspace, so at the operator's direction it gets no workspace glyph.

View File

@@ -92,3 +92,8 @@ The domain model (Host, Module, Skeleton, Auto-loader, Enable convention, overla
The build/render check is therefore blind to it, and the real test is a running session (or reading `~/.config/hypr/hyprland.conf` against the running package's own names).
Two that bit on 0.55.4: the dwindle split actions `togglesplit`, `swapsplit`, and `pseudo` are layout messages reached through the `layoutmsg` dispatcher (`bind = $mod, T, layoutmsg, togglesplit`), not top-level dispatchers, and the old `dwindle:pseudotile` option is gone.
Confirm names against the pinned package rather than the wiki, whose "latest" drifts from it.
The config can in fact be checked offline: `Hyprland --verify-config -c <rendered-conf>` parses the file and prints `config ok` or the exact `line N:` error without a running compositor, so a rule change is provable before login rather than only at it.
- Hyprland 0.55.4 uses windowrule v3 syntax, which is not the `windowrule = float, class:^(re)$` form the wiki still shows.
A flat `windowrule =` entry is a comma-separated list of `field value` tokens, each of which **must** carry a value: matchers take a `match:` prefix and effects are bare, so floating one app is `windowrule = float 1, match:class ^(com\.gabm\.satty)$`.
The old form fails at load with `invalid field float: missing a value`, because the effect token has no value.
`windowrulev2` is removed and errors as deprecated.

View File

@@ -16,6 +16,7 @@ in
modules.desktop.login.enable = lib.mkDefault true;
modules.desktop.mako.enable = lib.mkDefault true;
modules.desktop.rofi.enable = lib.mkDefault true;
modules.desktop.screenshot.enable = lib.mkDefault true;
modules.desktop.terminal.enable = lib.mkDefault true;
modules.desktop.theming.enable = lib.mkDefault true;
modules.desktop.waybar.enable = lib.mkDefault true;

View File

@@ -0,0 +1,58 @@
{
config,
lib,
pkgs,
...
}:
# Keyboard-driven screenshots that open in satty for annotation, then land in
# both the clipboard and a dated file.
let
cfg = config.modules.desktop.screenshot;
user = config.user.name;
grimblast = "${pkgs.grimblast}/bin/grimblast";
satty = "${pkgs.satty}/bin/satty";
wl-copy = "${pkgs.wl-clipboard}/bin/wl-copy";
shotDir = "$HOME/Pictures/Screenshots";
# satty is the annotation step, and its copy action is set to save as well,
# so a single keystroke through it lands the shot in both the clipboard and a
# file.
capture =
target:
pkgs.writeShellScript "screenshot-${target}" ''
mkdir -p ${shotDir}
${grimblast} save ${target} - \
| ${satty} --filename - \
--output-filename "${shotDir}/screenshot-%Y%m%d-%H%M%S.png" \
--copy-command ${wl-copy} \
--save-after-copy \
--early-exit \
--actions-on-enter save-to-clipboard
'';
in
{
options.modules.desktop.screenshot.enable = lib.mkEnableOption "screenshots via grimblast and satty";
config = lib.mkIf cfg.enable {
home-manager.users.${user} = {
# satty is a one-shot annotation surface, so float it rather than letting
# it claim a tile in the layout.
# windowrule v3 syntax: space-separated field/value tokens, comma
# separated, with matchers under a match: prefix and effects bare.
wayland.windowManager.hyprland.settings.windowrule = [
"float 1, match:class ^(com\\.gabm\\.satty)$"
];
# Print with plain/Shift/Ctrl for region/window/full.
# Super+L, the spec's chosen key, is already the hjkl focus and movement
# bind, so screenshots take the Print key instead.
wayland.windowManager.hyprland.settings.bind = [
", Print, exec, ${capture "area"}"
"SHIFT, Print, exec, ${capture "active"}"
"CTRL, Print, exec, ${capture "screen"}"
];
};
};
}