Skip to content

Flameshot v14: the keyboard flow, and the duplicated bar

home/apps/flameshot.nix. v14 from the UNSTABLE channel (through flake.nix's overlay) plus the config and the keyboard-flow scripts. The binds (Print, SUPER+SHIFT+S, and the "screenshot" submap) live in home/desktop/hypr/lua/keybinds.lua.

Capture goes through the portal

org.freedesktop.portal.Screenshot, served by xdg-desktop-portal-wlr. The -hyprland one only DECLARES the interface, it does not implement it; see desktop.md. With no direct grim and no useGrimAdapter, there is no "grim … GNOME" warning.

NB: the .ini comes from /nix/store (read-only), so changes through the GUI do NOT persist. Edit the module and rebuild. Qt QSettings does NOT accept an inline comment in the .ini.

The keyboard flow, at parity with the Arch v14

v14 ALWAYS shows a monitor picker on a multi-monitor setup. There is no skipping it, not even with --region. The picker only accepts a mouse CLICK.

So SUPER+SHIFT+S opens the picker and enters a submap, and 1/2 SYNTHESIZE the click on the right monitor's preview (move the cursor, then send_shortcut mouse:272).

With ONE monitor there is no picker, and that was the other half of the single-monitor bug, fixed on 30/08/2026. v14 opens the selection overlay straight away, so flameshot-screenshot counts the ACTIVE monitors and only enters the submap when there are 2 or more. Entering it with a single monitor HIJACKED flameshot's own keys: 1/2 moved the cursor to the middle of the overlay and synthesized a left click, which drags a bogus selection instead of capturing a screen, and Esc left through flameshot-cancel instead of flameshot's own cancel.

The flameshot window has an EMPTY class plus the title flameshot, so the selectors and the window rule (rules.lua) match on the TITLE.

flameshot-pick resolves the target's slice DYNAMICALLY: the previews sit side by side in PHYSICAL order (monitors sorted by X, left to right), so it finds the target's 0-based index among the ACTIVE monitors and clicks the center of slice i of n. Nothing is hardcoded, so it survives a turned-off monitor or a rearrangement. If the target is not active, it just resets the submap and exits.

The watcher resets the submap when flameshot closes (a mouse click on the picker, an internal Esc, or the 60 s timeout), otherwise 1/2 would stay hijacked afterwards. flameshot-cancel is the explicit Esc path.

The duplicated bar

By the time the overlay is up, its FROZEN frame has already been captured, WITH the bar in it. Hiding the LIVE bar at that point kills the duplicate WITHOUT taking it out of the shot.

On Hyprland a normal window NEVER covers a top layer, and the bar lives in one. There is no window rule to invert that (an open feature request, hyprwm/Hyprland#4847), so hiding is the only path.

It only hides if flameshot actually opened, otherwise the bar would disappear for nothing, and it ALWAYS comes back, the 60 s timeout included.

The aliases

They stay next to the tool and not in home/shell/zsh.nix, the same convention as eza and bat, which live in cli.nix. zsh.nix keeps only the shell and system ones.

VERIFIED on this machine's v14/Wayland: only gui opens the monitor picker; full and screen --number capture DIRECTLY, with no picker. So the Arch aliases still hold; what does not hold is --region, which v14 ignores.

Alias Command
screenshot flameshot gui, an interactive selection
scfull both screens, to the clipboard
sc1 the secondary (my.monitors.secondary), to the clipboard
sc2 the main one (my.monitors.primary), to the clipboard

--number is a Qt screen index and NOT a monitor name, and it was a measured literal here until 30/08/2026, guarded by nothing but a "if the layout changes, MEASURE AGAIN". It also broke with the TV disconnected: index 1 stops existing, so sc1 answered Requested screen exceeds screen count and exited 2.

flameshot-screen takes the NAME, which does come from my.monitors (rule 11), and DERIVES the index: Qt numbers the screens in the order the compositor advertises the wl_outputs, which is Hyprland's monitor id order, so the index is the target's position among the ACTIVE monitors sorted by id. A monitor that is not connected gets a notification instead of a capture, and the numbers still match the screenshot submap (1 = the secondary, 2 = the main one).

MEASURED on 30/08/2026 with a HEADLESS output standing in for the TV (hyprctl output create headless HDMI-A-3, which monitors.lua then places at -1920x0 exactly like the real one): --number 0 captured the LG and --number 1 the stand-in, which is the id order (DP-2 = 0, HDMI-A-3 = 1) and the same mapping that had been measured by hand against the wallpapers.

Two small things

runtimeInputs is mandatory on these scripts, since writeShellApplication uses a restricted PATH, not the user's. And Pictures/Screenshots/.keep exists because flameshot does not create the output folder reliably on its own.