fix(snipCommands): Copy never clobbers the clipboard on failed capture

buildCopyCommand stages the crop to a file and gates wl-copy + the
success notification on magick's exit status AND a non-empty file:
- piping magick straight into wl-copy cleared the clipboard and fired
  a false 'Copied to clipboard' whenever the crop failed or was empty
- the save branch's [ -f ] check passed on tee-created empty files
- pre-clean prevents a stale .crop.png from a killed run passing the gate
- wl-copy failure now notifies ('Copy failed' / 'Saved, copy failed')
- cleanup is ;-chained so the source screenshot never leaks on failure
This commit is contained in:
DJ committed 2026-07-05 09:40:52 -04:00
1 parent 2fa907c451
commit 1d785947de
2 files changed
+23 -9

No files matched your search

+1
View File
@@ -22,6 +22,7 @@ and this project adheres to [Conventional Commits](https://www.conventionalcommi
### Fixed
- **Edge-Straddling Window Targeting** - Clicking a window or layer that hangs off the monitor's left/top edge now selects it (matching its hover highlight) instead of dismissing the overlay; validity no longer sign-checks the region's origin. The capture is trimmed to the window's visible on-screen extent — screen clamping used to slide such regions fully on-screen at full size, which would have captured unrelated content beside the window
- **Copy Failure Handling** - A failed capture in Copy mode no longer clears the clipboard or reports a false "Copied to clipboard"; failures now show an explicit "Copy failed" notification and leave no zero-byte files behind
- **Freehand Closed Loops** - A freehand loop that ends precisely on its starting pixel is now captured as the drawn shape instead of being discarded as a click. Consequence: any Freehand gesture with 2+ motion samples counts as a draw even if it returns to its start pixel — a jittery click there now captures a tiny shape rather than selecting the content region under the cursor (matching how Freehand already treated jitter with any net drift)
- **Invisible Region Selection** - Clicks no longer select regions whose highlight type is disabled (`regionSelector.targetRegions.*: false`); hit-testing now follows the same gating as the visuals. Note this also changes Freehand mode: a stationary click on a window (never highlighted there) previously captured that window silently — it now dismisses the overlay unless the click lands inside a highlighted content region, which stays selectable in Freehand, consistent with what's on screen
@@ -63,28 +63,41 @@ Singleton {
return `notify-send '${title}' "${body}" -a 'hypr-lens' & disown`;
}
// Copy to clipboard (optionally also saves to disk if copyAlsoSaves is true)
// Copy to clipboard (optionally also saves to disk if copyAlsoSaves is true).
// The crop is staged to a file and everything downstream is gated on [ -s ]:
// piping magick straight into wl-copy would clear the clipboard (and fire a
// false success notification) whenever the crop fails or emits nothing.
// Cleanup is ;-chained so the source screenshot never leaks on failure.
function buildCopyCommand(screenshotPath: string, rx: int, ry: int, rw: int, rh: int, saveDir: string, alsoSave: bool, polygon: string): list<string> {
const cropBase = buildCropBase(screenshotPath, rx, ry, rw, rh, polygon, false);
const cropToStdout = `${cropBase} -`;
const cleanup = buildCleanup(screenshotPath);
if (!alsoSave) {
return ["bash", "-c", `${cropToStdout} | wl-copy && \
{ ${buildNotify("Copied to clipboard", "")}; } && \
const cropTmp = StringUtils.shellSingleQuoteEscape(screenshotPath + ".crop.png");
// Pre-clean guards against a stale crop from a killed prior run satisfying
// the gate; the gate itself needs magick's exit status AND a non-empty file
// ([ -s ] alone would bless a truncated PNG from a disk-full/killed magick)
return ["bash", "-c", `rm -f '${cropTmp}'; \
if ${cropBase} '${cropTmp}' && [ -s '${cropTmp}' ]; then \
if wl-copy < '${cropTmp}'; then ${buildNotify("Copied to clipboard", "")}; else ${buildNotify("Copy failed", "")}; fi; \
else \
${buildNotify("Copy failed", "")}; \
fi; \
rm -f '${cropTmp}'; \
${cleanup}`];
}
const expandedSaveDir = resolveSavePath(saveDir);
// Saved-but-copy-failed is a reachable state (dead compositor, wl-copy missing)
// and must say so — the file exists and the user needs to know where
return [
"bash", "-c",
`${buildSaveSetup(expandedSaveDir)} && \
${cropToStdout} | tee >(wl-copy) > "$savePath" && \
if [ -f "$savePath" ]; then \
${buildNotify("Copied & saved", "$savePath")}; \
`if ${buildSaveSetup(expandedSaveDir)} && ${cropBase} "$savePath" && [ -s "$savePath" ]; then \
if wl-copy < "$savePath"; then ${buildNotify("Copied & saved", "$savePath")}; else ${buildNotify("Saved, copy failed", "$savePath")}; fi; \
else \
rm -f "$savePath"; \
${buildNotify("Copy failed", "")}; \
fi && \
fi; \
${cleanup}`
];
}